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.
This commit is contained in:
2026-10-19 18:46:58 +08:00
parent f5c3cde2c2
commit 6a49d2209e
164 changed files with 12286 additions and 2368 deletions
+114 -18
View File
@@ -14,7 +14,7 @@ Read [README.md](README.md) for setup, configuration, and operational behavior.
- `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/results/`, `src/memory/`, and `src/learning/` retain their
- `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
@@ -35,7 +35,11 @@ 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 chat/task/learning role defaults in that file.
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,
@@ -67,8 +71,12 @@ OpenCode configuration and plugins.
## Runtime and Security
Production chat requests require Bearer authorization and `X-Project-Id`, verified
by TJWater Server. Preserve actor/project/user ownership checks on session create,
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.
@@ -89,37 +97,113 @@ the external OpenCode server password, and trusted assets loaded through
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. Do not replace enforcement with prompt
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 `.agent-workspaces/`, separate from
protected `data/` and `logs/`. `src/runtime/conversationWorkspace.ts` creates one
`conversation-<uuid>` per native session, with writable `files/`, `tmp/`, and
`.cache/`; the location root is read-only and Shell starts in `files/`.
`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, and deleting chat metadata does not remove their directories. Do not
clean them as build artifacts or describe them as automatically disposable.
Startup probes, warmup, and failed creation clean their own temporary workspaces.
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 the workspace, Agent data, Skill files, and OpenCode
session database. Mounting only `/app/data` does not preserve session files.
Result-reference TTL cleanup is separate from workspace retention.
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
@@ -130,11 +214,23 @@ 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, interactive approval outside that
allowlist, read-only background task access, and disabled MCP in learning review.
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.
@@ -175,7 +271,7 @@ verification; configuration/catalog checks must not invoke model inference.
## Repository Hygiene and Delivery
Never commit secrets, private environment files, generated runtime data, logs,
dependencies, `.agent-workspaces/`, session dumps, or result references. Avoid
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: