Files
next-tjwater-agent/AGENTS.md
T
jiang 6a49d2209e feat(agent): unify MCP tools and adopt Responses delivery
Move project and host queries into native MCP with host-owned OAuth/context,
while retaining memory, skills, actions and publication plugins. Add nearby-model
queries and explicit chat/task/learning model defaults.

Adopt Responses HTTP/SSE and native steer/queue inbox delivery, preserving scoped
session ownership, pending input edits and investigation communication.

Disable local-provider discovery: Server /health allowed native vLLM to probe
/v1/models on port 8000. A native before/after regression prevents recurrence.

Validation: 170 focused Bun tests; service/plugin typechecks; native discovery,
provider-discovery and input-inbox smoke checks; no paid model inference.
2026-10-19 18:46:58 +08:00

17 KiB

Repository Guidelines

Scope and Structure

This repository is the experimental TJWater supply Agent service. Keep drainage data, workflows, layer IDs, and terminology in the drainage repositories. Read README.md for setup, configuration, and operational behavior.

  • src/server.ts assembles dependencies and starts the service.
  • src/routes/ owns HTTP/SSE wiring; keep orchestration thin.
  • src/context/ owns local and Server-verified request context.
  • src/product/ owns typed TJWater Server adapters and business contracts.
  • src/frontendAction/ owns controlled browser actions and their receipts.
  • src/uiEnvelope/ owns the agent-ui@1 compatibility protocol and registry.
  • src/runtime/ owns OpenCode lifecycle, readiness, and workspace binding.
  • src/sandbox/ and scripts/landlock_sandbox.py own Shell enforcement.
  • src/sessions/, src/memory/, and src/learning/ retain their respective persistence responsibilities.
  • .opencode/ contains trusted service instructions, plugins, and tools.
  • .opencode/agent-plugin.json describes agents, plugins, Skill storage, and MCP configuration sources; .opencode/README.md explains their management.
  • tests/ contains focused tests and opt-in native integration scripts.

Use TypeScript ESM, two-space indentation, kebab-case module filenames, and explicit types at public boundaries. Preserve established service patterns before adding new abstractions. Backend access must use typed tools/adapters; do not introduce cli/, tjwater_cli, or a free-form business command bridge. The existing approved sandbox Shell is the only local command execution path.

Model Configuration

agent-models.json is the single model configuration source. An explicit AGENT_MODEL_CONFIG_PATH selects a complete replacement; relative paths resolve from the process working directory. agent-models.local.json is ignored by Git and the Docker build.

  • Define provider IDs, api_url, API key references, models, aliases, capabilities, limits, and role defaults in that file. Group role defaults under defaults: chat, task and learning_gate are all required explicit nonempty model references, with no role inheritance. Keep model definitions under providers.<id>.models and preserve public provider/model references. Legacy top-level role keys are rejected.
  • Generate OpenCode definitions and the public chat model menu from it through src/chat/modelConfig.ts and src/runtime/opencode2-config.ts.
  • Do not add independent model defaults in opencode.json, agent frontmatter, routes, learning code, or environment variables. Legacy OPENCODE_MODEL, OPERATIONAL_TASK_MODEL, and OPENCODE_MODEL_OPTIONS no longer select models.
  • Use model_id when the Agent-facing name differs from the upstream name; preserve existing public references when correcting upstream aliases.
  • Keep real API keys in ignored .local.env or deployment environment variables; use {env:NAME} references in model files. Existing system environment values take precedence over .local.env.
  • Never return provider URLs, keys, headers, or effective credential settings in model menus, validation reports, status endpoints, or logs.

After model/provider changes, run bun run check:models. It launches an isolated OpenCode and validates all non-disabled models, including hidden task/learning models, against effective native definitions and upstream /models inventories. A self-declared model.list entry alone is not validation. disabled skips a model; selectable: false only hides it from the chat menu and does not skip it. Do not use paid inference as the default configuration check.

The checker lives in .opencode/model-check.ts, with shared report/target types in src/runtime/model-check.ts and the command in scripts/check-models.ts. Preserve safe errors and cancellation. SIGINT/SIGTERM must abort model checking, wait for its owned OpenCode process to exit, and clean its temporary directories. Unsupported catalogs, absent upstream names, authentication failures, and invalid inventories must fail validation. Changes require an Agent restart; client mode also requires synchronized external OpenCode configuration and plugins.

Runtime and Security

Production chat always requires Server-verified Bearer authorization. Personal chat uses /api/v1/access-context without a project; project chat also requires X-Project-Id and /api/v1/agent-auth-context. Personal chat must never acquire project business credentials or fall back from failed verification to local identity. Use a user-specific personal key for memory and chat storage. Preserve actor/scope/user ownership checks on session create, read, actions, and MCP status. Development-only local context does not grant business-data access. Never accept a tool-supplied identity, project scope, authorization header, or arbitrary Server URL.

Complete the OS sandbox probe, OpenCode health check, warmup session, business tool discovery, enabled-model validation, workspace-root check, and MCP configuration check before HTTP starts listening. A failure must fail startup. Preserve /health fields ready, warmed_up, and sandbox.

Use the V2 SDK and trusted plugin registration. A cold Location may need plugin initialization before its tool/model catalog is usable. Verify actual tool IDs; a version string or MCP connection status alone does not prove availability. Keep native call IDs and the existing frontend SSE contracts.

Client mode requires the same stable AGENT_INTERNAL_TOKEN in both processes, the external OpenCode server password, and trusted assets loaded through serviceOpencodeConfig. Its absolute TJWATER_AGENT_WORKSPACE_ROOT must match the Agent workspace root. Prefer bun run start:integration:isolated when an existing user service must remain available. Stop only processes started for the current task; do not terminate unrelated services. The isolated integration launcher must pin OPENCODE_DB under its own data root rather than inherit a caller's database override. After that environment stops, its XDG cache may be regenerated; preserve native data, snapshots, chat workspaces and audit records unless their deletion is explicitly requested.

Preserve static sensitive-path denials in opencode.json, explicit approval for Shell/file edits, and disabled subagents for ordinary chat, task collaboration, and learning. Only the trusted active task_execution coordinator may launch investigation-reader, at most two foreground children and one level deep. Readers inherit only task-scoped query access, never report/action publication, Shell, file, or recursive delegation permissions. Do not replace enforcement with prompt instructions. The Shell replacement must retain Landlock/seccomp, credential isolation, cancellation, process-group cleanup, and denied alternate host Shell entry points. Code Mode must not bypass these controls.

Workspaces and Persistence

CONVERSATION_WORKSPACE_ROOT defaults to data/session-workspaces/. src/runtime/conversationWorkspace.ts owns validated directory paths and native bindings. Persistent sessions use actor-*/personal/session-<sha256>/ or actor-*/project-*/session-<sha256>/. Each contains host-owned chat/ and a workspace/ child with writable files/, tmp/, and .cache/.

src/sessions/workspace-store.ts owns chat directories and immutable ownership files. Inject the same store into metadata, UI state and transcript stores in src/server.ts. Keep data/session-metadata/ as the owner/history index; new messages, transcripts and learning state go only to chat/. Preserve full identity checks on reads and lists, session filename collision rejection, and serialized ownership creation. Scope changes in the frontend start a new conversation and use independent history caches; they do not silently reassign existing sessions.

Bind OpenCode only to workspace/. Its file tools must check the native session hash as well as canonical paths; Shell cannot reach sibling chat/. Chat directories are 0700, record files 0600; never place transcripts or bearer credentials in model-writable directories. Unscoped conversation-<uuid> directories are retained for temporary probes/generation and legacy migration.

Keep canonical-path and symlink checks, per-session access restrictions, native session directory bindings, and empty independent workspaces for new branches. Do not copy source code, credentials, or other sessions into a workspace.

Generate scoped native read/edit permissions for managed workspaces without opening all of data/. Preserve sensitive-path denials and later administrator rules; the plugin must still limit file access to the current session. Keep Shell execution approval and kernel restrictions. Prefer session-relative file paths.

Legacy .agent-workspaces/, integration conversation-workspaces/, and flat conversation directories are moved before continued execution, preserving files, then rebound through the native Session API. Failed binding updates must be retryable after a filesystem move, and existing destination directories must never be overwritten. Read-only MCP status queries may resolve moved files without moving sessions.

At startup copy legacy UI states into the indexed owner's chat directory and transcripts into their recorded actor/scope directory, and indexed learning state into chat/learning.json. Archive original bytes under PERSISTENCE_BACKUP_DIR/legacy-chat/ only after verifying the backup. Keep a versioned migration manifest; never overwrite new records during repeated migration or guess an owner for an unindexed UI state. Legacy UI/transcript/learning storage environment variables select migration sources, not a second active record store. Verified migration backups may be removed when explicitly requested; preserve the migration receipt and active scoped records. Do not recreate empty legacy UI/transcript/learning directories when workspace storage is configured.

Workspaces contain persistent session files. They currently have no expiration cleanup. Deleting a session marks its index and host-owned chat directory before cancelling work, then removes native history, chat content, learning state, calculation files and that session's legacy records/backups. Keep minimal owner and deletion markers; queued writes and migration must not revive deleted chats. Cleanup errors must return a retryable response, never success with unfinished cleanup. Archived chats retain their records. Session lists use a scoped in-process index updated by the owning store; one Agent process owns writes to each data root. Do not mutate index files behind that cache. Do not clean session files as build artifacts or describe them as automatically disposable. Startup probes, warmup, title and learning helpers, and failed creation clean their own native sessions and temporary workspaces. Cleanup must validate scope, canonical paths and session hash and wait for native execution to stop. Changing the root requires preserving directory bindings and associated native session data; do not silently move or delete existing user files.

Deployment must preserve Agent data, Skill files, and the OpenCode session database. With the default root, mounting /app/data also preserves session files; custom workspace roots require their own persistent mount.

Independent result-reference storage, its TTL loop, and the result-ref read API have been retired after checking that no business producer or consumer uses them. Store file results in a session workspace and reference them by session ID plus relative path. Do not replace scoped access checks with arbitrary host paths or reintroduce a separate result-reference store without an actual consumer.

MCP and Business Boundaries

Project and host read-only query tools use the native mcp_tjwater MCP connection. Public web search uses OpenCode's native websearch; do not restore a Server search endpoint, Bocha adapter, or duplicate MCP web-search tool. Native browser_* automation tools are disabled. Preserve the separate TJWater presentation tools, frontend action receipts, task presentation events, and the complete memory_manager / skill_manager plugin interfaces. Do not restore standalone plugin query registrations, legacy typed query tools, or custom internal HTTP query routes. memory_manager and skill_manager retain their complete read/write plugin interface; publication, actions, messaging writes, map presentation and todowrite also remain plugins. OpenCode supplies native session metadata on MCP calls; authenticate the private loopback channel before resolving host ownership or verified direct task readers. Keep product OAuth credentials and context handles in the Agent host. Project queries fail explicitly when the upstream Server MCP is disabled; never fall back to legacy query bridges.

Keep the .opencode/agent-plugin.json descriptor and package descriptions aligned with actual IDs and paths when agent/plugin assets change. The descriptor is a documentation index, not a runtime configuration file or native OpenCode plugin manifest. Do not duplicate model definitions, Skill inventories, MCP connections, or credentials in it. Use the established configuration builder and source files. Skill storage is shared by the deployment instance; do not describe it as isolated per user or project, and preserve the existing validated persistence workflow.

Configure external MCP only through administrator-owned mcp.servers in opencode.json or OPENCODE_CONFIG_CONTENT; server names use the mcp_ prefix. Preserve exact read-only tool allow rules and approval outside that allowlist. Interactive chat follows the selected round mode: request asks the user; always automatically answers pending native permission requests, including Shell, file edits and MCP, with once. Do not persist automatic grants into later rounds or bypass explicit denials, sandboxing, business authorization, read-only background task access, or disabled MCP in learning review. Broad wildcard allows and server readOnlyHint values are not read-only grants. Apply the same policy to Code Mode and generic MCP resource access.

First-party product MCP OAuth discovers protected-resource and OAuth/OIDC metadata, pins the configured resource and issuer, and sends the canonical resource on both grants. Keep public discovery credential-free, bounded and redirect-free. Send client secrets only to a validated issuer's advertised token endpoint; preserve Basic/post authentication, metadata invalidation on 401, and explicit failure for an incompatible resource indicator. Do not restore a hardcoded Keycloak token path or an audience-only fallback.

Local stdio MCP is a trusted host process, not a sandbox Shell process. Preserve its explicit environment filtering and never forward Agent/OpenCode credentials. Return only sanitized server status and actual tool IDs to the browser.

Keep business state authoritative in Server. Background execution uses persisted task_id, run_id, and execution_request_id, with explicit claim, heartbeat, and complete/fail transitions. Plans, reports, browser receipts, and final text are distinct from execution completion. See docs/execution-plan.md and docs/agent-ui-protocol.md.

Commands and Verification

Run commands from this repository with Bun:

bun install
bun run dev
bun run start
bun run check
bun run check:models
bun run start:integration:isolated

bun run check type-checks both the service and .opencode; this repository has no separate lint command. Run focused tests for the changed behavior. For model and readiness changes, the relevant suite is:

bun test tests/chat/models.test.ts tests/opencode/modelCheck.test.ts tests/runtime/opencode.test.ts tests/runtime/modelCheckCommand.test.ts tests/frontendAction/callIdPlugin.test.ts

Do not over-test reversible low-impact edits. Documentation-only changes need local link/path and command validation plus git diff --check, without starting services or calling providers. Tests must not depend on mutable data/ files. Use opt-in native smoke scripts only when actual integration behavior needs verification; configuration/catalog checks must not invoke model inference.

Repository Hygiene and Delivery

Never commit secrets, private environment files, generated runtime data, logs, dependencies, session workspaces, session dumps, or generated result files. Avoid printing private configuration contents while inspecting a workspace.

Preserve unrelated dirty and untracked work. Keep README.md useful to people: configuration locations, runnable setup steps, model checks, workspace retention, and current capabilities. Update it when those operational behaviors change. Use concise Conventional Commit subjects and scope commits to requested files. Do not push or deploy without authorization for the current task.