Files
next-tjwater-agent/README.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

516 lines
50 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TJWater 供水 Agent
这个服务把供水 WebGIS 中的对话、数据查询、地图操作和工况任务接到 OpenCode 2。浏览器负责展示和交互,Agent 编排工具,TJWater Server 校验身份、项目权限并保存业务状态。
本仓库仍是实验服务,与历史 `TJWaterAgent` 仓库独立。排水业务由排水 Agent 仓库维护。
## 配置文件在哪里
| 要修改什么 | 文件或位置 | 说明 |
| --- | --- | --- |
| 模型、provider、API 地址、默认模型 | [agent-models.json](agent-models.json) | 模型配置的唯一入口 |
| API key、服务地址、端口等环境变量 | 仓库根目录 `.local.env` | 本地私有文件,已被 Git 忽略;字段参考 [.env.example](.env.example) |
| 工具权限、外部 MCP 连接 | [opencode.json](opencode.json) | 不再单独配置模型和 provider |
| Agent 的业务指令 | [.opencode/agents/instruction.md](.opencode/agents/instruction.md) | 指令文件不指定模型 |
| 后台调查与只读子代理 | [.opencode/agents/investigation.md](.opencode/agents/investigation.md)、[investigation-reader.md](.opencode/agents/investigation-reader.md) | 主对话禁用子代理,后台任务按可信用途选择独立配置 |
| Agent、插件、Skill 和 MCP 的管理索引 | [.opencode/agent-plugin.json](.opencode/agent-plugin.json) | 描述用途和实际配置入口;管理方法见 [.opencode/README.md](.opencode/README.md) |
| 代码修改规则 | [AGENTS.md](AGENTS.md) | 面向维护代码的 Agent |
服务自动读取根目录的 `.local.env`,已有系统环境变量优先。普通启动不会自动读取 `.env`;部署时应由容器或进程管理器注入环境变量。
本地 MCP issuer 可在 `NODE_ENV=development` 下使用内网 HTTP:把 `TJWATER_MCP_DEV_HTTP_ISSUER_URI` 设置为与 `TJWATER_MCP_ISSUER_URI` 完全相同的 RFC1918 IPv4 issuer。该例外只允许此认证服务的同源 HTTP 发现与令牌端点,不放行远程 HTTP MCP resource。Server 本地实例也须使用同一 issuer、相同白名单及 `ENVIRONMENT=development`;正式 `.env` 使用 HTTPS 域名,白名单留空。issuer 必须与令牌 `iss` 完全一致。
环境文件只保留简短分组和必要注释。MCP 的 `CLIENT_ID` 填 Keycloak 客户端 ID,`CLIENT_SECRET` 单独填写 Credentials 页的密钥。后端连接使用 `TJWATER_SERVER_BASE_URL` / `TJWATER_SERVER_TIMEOUT_MS`,当前版本不读取旧的 `TJWATER_API_*` 名称。
### 后台调查子代理
`serviceOpencodeConfig` 区分三个原生配置:`instruction` 用于主对话、人工协作和学习,
禁止子代理;`investigation` 用于服务端标记的后台任务,只能调用 `investigation-reader`;
后者只有任务上下文和任务数据查询两项工具。保持全局 `subagent: deny`,不通过用户提示
或前端参数放开。父调查使用 `agent-models.json` 的 task 模型,子代理沿用父会话模型。
每个后台调查最多同时两个只读子代理,仅一层,使用并行工具调用批次及前台等待,
不启用脱离父调用的 background 子任务,不恢复旧子会话。查询保存原有证据快照,
项目身份和凭据由服务端从原生直接父子关系核验,仅在父任务运行期间代理读取;
凭据不复制给子会话。子代理不发布进度、正式报告或动作,不进行审批、控制或 Shell 操作。
主 Agent 核对实际证据后统一发布,可用 Mermaid 表示真实并行分支与汇总,图不驱动调度。
配置生效需要 Agent/OpenCode 加载更新的可信插件;外部 client 模式须同步更新两侧配置。
无需数据库迁移。独立验证:`bun tests/integration/task-agent-smoke.ts`,只使用临时数据库
和原生权限接口,不调用模型或真实任务。实际模型并行与动态图仍须业务联调确认。
如果不想修改仓库自带的模型配置,可以复制为 `agent-models.local.json`,再在 `.local.env` 中设置:
```dotenv
AGENT_MODEL_CONFIG_PATH=./agent-models.local.json
```
该文件已被 Git 和镜像构建忽略。它会替换整份模型配置,不与 `agent-models.json` 合并;相对路径按启动目录解析。
## 调查消息与稳定引用
主聊天通过 `mcp_tjwater_list_investigation_tasks`、`mcp_tjwater_get_investigation_task` 获取 Server 已有的
`task_id`、`run_id` 及 `agent_session_id`,不读取其他用户聊天或内部推理。
`send_task_message` 指定任务和轮次,Server 保存后再经 OpenCode `session.synthetic`
以 `steer` 投递。`mcp_tjwater_read_agent_messages` 查询当前会话消息,`reply_agent_message`
只沿已建立的通信关系回复。入队、进入上下文和收到回复是不同状态。
执行中的主聊天接收来信;空闲时仅保存和提醒,下次交互再处理,不自动唤醒。
普通消息不新增调查轮次、不自动写为人工证据,也不授权工单执行或设备控制。
正式新轮次仍创建新的 Run 和执行会话;旧轮次未投递消息不会自动转投。
消息由 Server 持久化,现有工况 Worker 补偿投递;浏览器关闭不影响调查。
部署前需要先应用 Server 的 `024_agent_messages.sql` 并发布 Server,再更新
Agent/OpenCode 可信工具及 Worker,最后更新前端。不会自动迁移数据库。
回退前保留消息表与会话数据;不新增外部账号、消息中间件或环境变量。
## 本地启动
在本仓库目录执行命令。运行环境需要 Bun、OpenCode 2、Python 3 和 `libseccomp2`,Linux 内核需支持 Landlock ABI 4 或更高版本。服务启动时会实际探测沙箱,失败会停止启动。
仓库使用 OpenCode 正式版 `2.0.27`,CLI、客户端和插件依赖保持同版。继续使用正式包提供的 `opencode2` 兼容命令,避免与本机旧版 `opencode` 冲突。安装并检查 CLI:
```bash
bun add --global --trust --exact @opencode/cli@2.0.27
opencode2 --version
bun install
```
首次配置时创建本地环境文件,已有文件则直接编辑:
```bash
test -e .local.env || cp .env.example .local.env
chmod 600 .local.env
```
在 `.local.env` 中填写启用的 provider 所需密钥,以及 TJWater Server 的实际地址。下面只列字段,密钥占位值需要替换:
```dotenv
HOST=127.0.0.1
PORT=8787
OPENCODE_MODE=embedded
TJWATER_SERVER_BASE_URL=http://127.0.0.1:8000
WATERNETWORK_API_KEY=replace-with-your-key
```
默认仅启用 WaterNetwork,填写 `WATERNETWORK_API_KEY` 即可。增加其他 provider 时,还需提供对应凭据,并将默认模型引用指向有效的已配置模型。
配置完成后检查模型,再启动服务:
```bash
bun run check:models
bun run dev
```
另开一个终端查看启动结果,地址和端口按实际配置替换:
```bash
curl --fail http://127.0.0.1:8787/health
```
正常响应中 `ok`、`ready`、`warmed_up` 均为 `true`,并包含 OpenCode 版本和沙箱探测结果。`bun run dev` 使用 watch 模式;无 watch 的启动命令是 `bun run start`。修改模型配置或密钥后需重启服务,不依赖 watch 自动加载配置文件。
开发环境允许无凭据的本地对话,使用共享的本地开发身份。生产聊天始终要求 `Authorization: Bearer ...`:个人会话不发送项目头,Server 通过 `/api/v1/access-context` 验证用户;项目会话还发送 `X-Project-Id`,通过 `/api/v1/agent-auth-context` 验证项目权限。项目 ID 使用 Server 返回的 UUID,本地上下文头不能替代产品鉴权。个人会话不能调用需要项目凭据的 TJWater 业务 API。
## 配置模型和自定义 provider
[agent-models.json](agent-models.json) 同时控制聊天菜单和 OpenCode 的模型定义。`defaults` 集中选择各用途的默认模型,`providers` 保存供应商连接及其模型目录。当前仅包含地址为 `https://llm.waternetwork.cn/v1` 的 `waternetwork` provider,保留两个模型:`gpt-6-luna` 显示为“快速”,使用闪电图标,上下文配置为 272,000 tokens;`gpt-6.1-sol` 显示为“专家”,使用星光图标,上下文配置为 475,000 tokens。
三个默认模型分别用于:
| 字段 | 用途 | 当前配置 |
| --- | --- | --- |
| `defaults.chat` | 普通聊天默认值,必填 | `waternetwork/gpt-6-luna` |
| `defaults.task` | 后台工况任务首选模型,必填 | `waternetwork/gpt-6-luna` |
| `defaults.learning_gate` | 判断是否需要学习复盘,必填 | `waternetwork/gpt-6-luna` |
三项均须显式填写,即使使用同一个模型也不能省略;缺失、空值或空白字符串会在加载配置时被拒绝,不自动继承其他用途。三个引用都必须指向已定义、启用且声明支持文本输出和工具调用的模型;聊天默认模型还必须在菜单中可选。
Agent 关闭 OpenCode 内置的 vLLM、LM Studio 和 Ollama 自动发现插件,避免其定期探测本机端口;例如 vLLM 默认探测 `127.0.0.1:8000`,会把 TJWater Server 的 `/health` 成功响应当作继续请求 `/v1/models` 的依据。需要使用本地模型时,在 `providers` 中显式配置地址和模型即可,不依赖自动发现。此设置由 `serviceOpencodeConfig` 统一生成,独立 OpenCode 客户端模式也必须同步该配置,修改后需重启运行进程。
可运行 `bun tests/integration/provider-discovery-smoke.ts` 验证:隔离实例先复现旧配置的探测请求,再确认当前配置不再探测,并保留显式配置的本地模型;测试仅请求本地模拟服务,不调用模型推理或业务服务。
旧配置文件的顶层 `default_model`、`task_model`、`learning_gate_model` 须分别迁移到 `defaults.chat`、`defaults.task`、`defaults.learning_gate`;旧字段和新旧混用均会被拒绝。通过 `AGENT_MODEL_CONFIG_PATH` 指定的替代配置也使用此结构。公开模型 ID 仍为 `provider/模型名`,已有会话的模型引用无需迁移。
### 使用 WaterNetwork 的最小配置
下面是一份完整示例,三个用途都使用 `gpt-6.1-sol`。可以将它保存到自己的模型配置文件中:
```json
{
"defaults": {
"chat": "waternetwork/gpt-6.1-sol",
"task": "waternetwork/gpt-6.1-sol",
"learning_gate": "waternetwork/gpt-6.1-sol"
},
"providers": {
"waternetwork": {
"name": "WaterNetwork",
"api_url": "https://llm.waternetwork.cn/v1",
"api_key": "{env:WATERNETWORK_API_KEY}",
"models": {
"gpt-6.1-sol": {
"name": "通用助手",
"description": "对话与任务处理",
"icon": "sparkle",
"limit": { "context": 475000 }
}
}
}
}
}
```
新增其他 provider 时,在 `providers` 下增加同样结构的一项,并换成自己的地址、密钥变量名和模型名。provider 名只允许字母、数字、下划线和连字符。不同供应商可以配置同名模型,使用完整的 `provider/模型名` 引用区分,能力和上下文限制分别配置;各用途可通过 `defaults` 选择不同供应商的模型。
`api_url` 填 API 基础地址,例如以 `/v1` 结尾的地址,不填 `/chat/completions`;不允许在地址中嵌入账号、密码、查询参数或片段。默认使用 OpenAI compatible 运行包,真实密钥始终放在环境变量中。
| 模型字段 | 怎么填写 |
| --- | --- |
| `models` 的键 | Agent 使用的模型名;完整引用为 `provider/模型名` |
| `model_id` | 上游实际接收的名称,省略时与键名相同;支持 `org/model` 这样的名称 |
| `name`、`description`、`icon` | 聊天菜单的名称、说明和图标;图标可用 `bolt` 或 `sparkle` |
| `disabled: true` | 禁用模型,不进入聊天菜单,也不参与运行时校验 |
| `selectable: false` | 从聊天菜单隐藏,仍可供后台或学习使用,仍需通过校验 |
| `capabilities` | 工具调用和输入、输出类型;默认声明支持工具调用和文本输入、输出 |
| `limit`、`compatibility` | provider 所需的上下文限制和兼容设置,按实际服务能力填写 |
DeepSeek provider 已停用,默认角色不再引用它。重新启用 Flash 时,模型名使用 `deepseek-flash`;其他自定义别名可通过 `model_id` 映射到上游名称。配置文件使用标准 JSON,移除停用条目或给模型设置 `disabled: true`,不使用注释禁用配置。
旧的 `OPENCODE_MODEL`、`OPERATIONAL_TASK_MODEL`、`OPENCODE_MODEL_OPTIONS` 环境变量已不控制模型选择。在 `opencode.json` 或指令文件中另写模型定义也不会覆盖统一配置。
### 保存后检查模型
```bash
bun run check:models
```
该命令启动独立的 OpenCode 2,核对所有未禁用模型的注册结果、启用状态、工具能力声明和实际请求名称,再由插件使用 OpenCode 生效的 provider 配置读取上游 `/models` 目录。手工注册了一个名字,也必须在上游目录中找到对应名称才能通过。
命令不发送模型推理请求,结束后清理自己启动的进程和临时目录。检查通过表示模型名称和运行时配置一致;模型的推理效果、额度和实际工具调用表现需在使用时验证。
使用 Ctrl+C 或发送 SIGTERM 取消检查时,命令会等待自己的 OpenCode 子进程退出,再清理临时目录。
Agent 启动时也执行同一检查,失败则不监听 HTTP。常见结果如下:
| 检查结果 | 排查方向 |
| --- | --- |
| `not_advertised` | 上游没有该名称,检查 `model_id` 和 provider 的模型目录 |
| `not_registered`、`not_enabled` | OpenCode 未加载或未启用该定义;client 模式需同步配置 |
| `wire_id_mismatch`、`no_tools` | 运行时的名称映射或工具能力声明与配置不一致 |
| `credentials_unavailable` | OpenCode 没有取得该 provider 的凭据 |
| `catalog_unavailable` | 检查网络、API 基础地址或鉴权;HTTP 401/403 应排查凭据和权限 |
| `catalog_invalid` | `/models` 返回的数据结构不符合预期 |
| `unsupported_catalog` | 当前检查器不支持该运行包的目录接口 |
检查器支持 OpenAI compatible、OpenAI、OpenRouter 和 xAI 运行包的 `/models` 目录,其他运行包会拒绝启动。报告不输出密钥、连接地址或上游原始错误内容。
## Agent 与 OpenCode 的运行方式
默认 `OPENCODE_MODE=embedded`,Agent 启动和关闭自己的 OpenCode 子进程,并自动管理连接密码和内部工具桥密钥。`OPENCODE_COMMAND` 可指定 CLI 的绝对路径,`OPENCODE_PORT=0` 让系统分配空闲端口。
`OPENCODE_MODE=client` 用于连接单独管理的 OpenCode 服务,需要配置 `OPENCODE_CLIENT_BASE_URL`、`OPENCODE_CLIENT_PASSWORD`,并在两个进程中设置相同的稳定 `AGENT_INTERNAL_TOKEN`。
外部 OpenCode 还需通过 `serviceOpencodeConfig` 加载同一份模型配置、可信插件包 `.opencode/tjwater-plugin/` 和业务指令。其 `TJWATER_AGENT_WORKSPACE_ROOT` 必须与 Agent 的 `CONVERSATION_WORKSPACE_ROOT` 指向同一个绝对目录,同时设置 `TJWATER_AGENT_INTERNAL_BASE_URL` 指向 Agent 内部工具桥。只启动一个普通 OpenCode 服务不足以完成接入;缺少业务工具、模型检查 RPC 或目录绑定会导致预热失败。
要保留已有服务进行隔离联调,可以使用仓库启动器:
```bash
bun run start:integration:isolated
```
默认端口为 Agent `18787`、OpenCode `14096`,数据保存在 `data/integration-18787/`,不复用默认会话数据。可通过 `INTEGRATION_AGENT_PORT`、`INTEGRATION_OPENCODE_PORT` 和 `INTEGRATION_DATA_DIR` 修改。启动器负责配对配置和停止自己创建的两个进程。
联调目录保存该环境的聊天、任务会话索引、原生执行记录和审计日志。聊天同样按用户、个人或项目范围、会话保存到其 `session-workspaces/`。原生数据库固定为该目录的 `xdg-data/opencode/opencode.db`,不继承外部 `OPENCODE_DB` 覆盖,确保联调运行使用独立数据库。
停止联调环境后,可清理 `xdg-cache/` 中的模型目录缓存和 Bun 编译缓存;下次启动会重建。`xdg-data/` 包含原生数据库与文件快照,`session-workspaces/` 保存聊天和计算文件,`logs/` 保存审计信息。清理整个联调目录会丢失这些历史记录,不能把它们当作缓存处理。
## `data/session-workspaces` 是什么
这是用户的会话工作空间,默认根目录为 `CONVERSATION_WORKSPACE_ROOT=./data/session-workspaces`。按用户、范围和会话组织,个人会话不需要归属项目:
```text
data/
└── session-workspaces/
└── actor-<用户键>/
├── personal/
│ └── session-<会话 ID 的 SHA-256>/
└── project-<项目键>/
└── session-<会话 ID 的 SHA-256>/
├── chat/
│ ├── owner.json 会话与用户、范围的绑定
│ ├── messages.json 完整前端聊天状态、工具与计划展示
│ ├── transcript.json 用于搜索和学习的问答摘要
│ └── learning.json 学习检查和复盘进度
└── workspace/
├── files/ 生成文件、计算脚本、结果和较长的 Shell 输出
├── tmp/ 临时文件
└── .cache/ 运行缓存
```
两类会话使用相同的内部结构。用户和项目键由可信身份派生,不直接使用用户名作为路径;`owner.json` 保存原会话 ID,便于核对目录。`data/session-metadata/` 保留为会话归属与历史列表的索引,完整聊天状态、问答摘要和学习进度写入对应工作空间的 `chat/`。索引按用户和范围缓存在服务进程内,写入时同步更新;同一数据目录使用一个 Agent 写入进程。手工修改索引后需要重启服务。
OpenCode 的 `location.directory` 绑定 `workspace/`,不绑定整个工作空间,恢复时继续使用计算文件。分支得到独立计算目录,只复制用户选择保留的聊天上下文。旧 `.agent-workspaces/`、联调 `conversation-workspaces/` 和平铺 `conversation-<uuid>/` 在恢复执行时迁入可信用户的对应 `workspace/`,保留文件并更新原生绑定;目标冲突不覆盖,绑定失败可重试。
启动时将旧 `data/session-ui-states/`、`data/session-transcripts/` 和 `data/session-learning-state/` 迁入对应 `chat/`,不覆盖已有新记录。旧文件经逐字节备份核对后,归档到 `PERSISTENCE_BACKUP_DIR/legacy-chat/`,原迁移目录不再保留已归档副本。备份按用户、范围和会话存放,文件名包含内容哈希;`migration.json` 记录版本、来源和迁移结果,来源目录未变化时跳过重复导入。无归属的记录继续保留在旧目录,不分配给任意用户。三个旧存储环境变量仅指定迁移来源,新写入由工作空间根目录统一管理。
迁移核对完成后,可以清理已迁入工作空间的旧记录备份和空迁移目录;保留 `legacy-chat/migration.json` 作为完成记录。服务使用工作空间存储时不会重新创建三个旧目录。当前本机的 139 份旧记录备份已核对并清理,现行记录继续保存在各会话的 `chat/` 中。
迁移备份尚保留时,回滚到旧存储实现前需将其恢复到旧目录:UI 文件名按会话 ID 的旧 `slugify` 规则计算,摘要为 `<actorKey>__<projectKey>__<sessionId>.json`,学习进度为 `<sessionId>.json`。恢复时保留现有工作空间,不覆盖更新的记录。已经清理迁移备份的部署,需要使用其他部署备份恢复旧存储;只回滚代码不会恢复旧记录文件。
Shell 默认在 `workspace/files/` 中运行,只能写入计算目录中的三个子目录,计算目录根只读。`chat/` 只由 Agent 服务读写,目录权限为 `0700`、记录文件为 `0600`,模型文件工具及 Shell 不能访问。OpenCode 仅为受管理的计算目录增加文件权限,插件还验证目录属于当前原生会话;其他业务记录、聊天记录和 `logs/` 继续禁止访问。目录隔离配合 Landlock 和 seccomp 限制文件访问、网络与进程行为;目录本身不等于 OS 沙箱。限制覆盖 Shell 及其子进程,OpenCode 服务和类型化业务工具仍由可信宿主进程运行。本地 stdio MCP 也不在这个沙箱中。
Shell 和文件修改仍经过原生权限审批。聊天选择“请求批准”(`approval_mode=request`)时逐次询问;选择“始终允许”(`approval_mode=always`)后,Agent 自动批准本轮进入审批流程的请求,包括 Shell、文件修改和 MCP 请求。每次仅回复原生 `once`,不把本轮选择保存为后续轮次授权;明确拒绝的操作不会进入自动批准,业务权限、MCP 会话策略和沙箱限制继续执行。只读白名单工具可直接执行,不因选择“请求批准”而增加询问。
Shell 不能联网或访问源码、密钥、其他会话;业务查询和地址解析使用 MCP,网页搜索使用原生 `websearch`。超时单位为秒,默认 120 秒、最大 1800 秒;stdout 默认限制 1 MiB、stderr 最多 256 KiB、单文件最多 128 MiB。较长输出以文件保存在 `files/`,工具返回预览和路径;取消、超时、超限和命令结束均清理该进程组,不保留后台进程。
工作区已被 Git 和镜像构建忽略,没有自动过期清理。归档会话保留记录;删除会话会取消聊天和学习任务,删除原生会话、聊天内容、学习进度、计算目录及该会话的旧记录和迁移备份。索引与 `chat/` 保留最小删除标记,阻止迟到写入和旧数据重新导入。清理失败返回 `503 SESSION_CLEANUP_PENDING`,会话已从历史和搜索隐藏,可重试同一删除请求。审计日志、已形成的记忆、共享 Skills 和 Server 业务报告按各自职责保留,见 [用户隔离与会话存储](docs/user-isolation.md)。
启动探测、预热、标题生成、学习检查、复盘和创建失败产生的内部会话会在结束时释放原生会话及自己的计算目录。未确认原生绑定的历史空目录不会被批量删除。
删除仍被会话引用的目录会丢失生成文件,并可能使会话后续文件操作失败。需要改变根目录时,应同步保留会话数据库及目录绑定,不能只改环境变量后丢弃原目录。服务目前没有工作区附件下载接口。
持久化部署需要分别保存:
| 数据 | 默认位置 | 用途 |
| --- | --- | --- |
| 会话文件 | `data/session-workspaces/` | 文件计算和生成结果 |
| Agent 数据 | `data/` | 会话元数据、对话记录、记忆、学习状态和会话文件 |
| Skill 文件 | `.opencode/skills/` | 工具可读取的 Skill,目录可由 `OPENCODE_SKILLS_ROOT_DIR` 指定 |
| OpenCode 数据 | OpenCode 的 XDG 数据目录 | 原生会话数据库;部署时保留对应数据卷 |
| 日志 | `logs/` | 运行审计和学习日志 |
默认配置下,持久化 `data/` 会同时保留 Agent 记录和会话生成文件;OpenCode 数据库与 Skill 目录仍需分别持久化。覆盖工作区根目录时,应单独挂载对应位置。
专用 OpenCode 实例可通过现有 `OPENCODE_DB` 指定独立数据库路径并纳入备份;client 模式需要在外部 OpenCode 服务侧设置。已有共享库需要停机备份后再安排迁移,不应在服务运行时直接移动。
独立的 `result-refs` 存储、TTL 清理和 `/result-ref/:result_ref` 读取接口已移除。当前业务没有使用它们产生或读取结果。文件型结果保存在原生计算目录的 `files/` 中,用会话 ID 和相对路径引用,例如 `files/results/analysis.json`;Agent 使用当前会话的原生文件工具读取,保留跨会话和敏感路径校验。图表及地图展示继续使用现有类型化前端动作。浏览器附件下载接口尚未开放。
## 当前能做什么
| 场景 | 工具或协议 | 说明 |
| --- | --- | --- |
| 查询供水数据 | `mcp_tjwater_*` | 启用 Server MCP 后发现25项项目查询和10项受控分析,供普通项目会话与绑定调查使用 |
| 地图与图表交互 | `locate_features`、`zoom_to_map`、`show_chart`、`view_history` | 通过 `frontend-action@1` 等待浏览器实际回执 |
| SCADA 地图分析 | `render_scada_analysis`、`clear_scada_analysis` | 展示和清除受控叠加层 |
| 网页搜索 | OpenCode 原生 `websearch` | 搜索服务由 OpenCode 配置管理,不经过 TJWater Server |
| 地址解析 | `mcp_tjwater_geocode` | 调用 Server 的天地图接口,模型不能指定任意 URL 或鉴权头 |
| 工况任务 | 查询、计划、报告、行动建议和人工证据工具 | 通过 Server 的任务与运行身份访问业务 |
| 调查实时进度与资料展示 | `publish_task_progress`、`publish_task_presentation` | 仅后台任务执行可发布;Server 保存进度并记录展示事件,不等待浏览器 |
| 会话与学习 | `mcp_tjwater_session_search`、`memory_manager`、`skill_manager` | 按会话、用户和项目范围管理记录、记忆和 Skill |
| 本地计算 | `shell` | 在会话 OS 沙箱中运行经过审批的命令 |
| 外部资料和工具 | MCP 客户端 | 由管理员配置,并按会话权限调用 |
配置构建器禁用 OpenCode 原生 `opencode.browser` 插件及其子插件,`browser_*` 自动化工具不再注册,浏览器权限也明确拒绝。业务前端继续使用上表的地图、图表和 SCADA 展示插件;`present_answer` / `final_answer` 返回交互界面与最终回答,`publish_task_presentation` 发布后台任务展示事件,`memory_manager` / `skill_manager` 保留完整读写插件接口。
历史查询的时间窗口最长 24 小时,SCADA 字段限 `monitored_value` 和 `cleaned_value`;物理指标和单位以设备元数据为准。历史查询由 Server 校验只读权限,清洗操作仍需清洗权限。`view_history` 当前最多展示 100 个非空样本,不代表完整高频历史;缺失资产和历史缺数会返回实际状态。
网页搜索只使用 OpenCode 原生 `websearch`,搜索服务及其凭据由 OpenCode 管理,保留管理员的服务选择;TJWater Server 的 Bocha 接口、配置和 MCP 搜索工具已移除。搜索结果是外部资料,不自动保存为调查系统证据。`mcp_tjwater_geocode` 返回 `center=[lon,lat]`、`source_crs=EPSG:4326`,可用于地图定位;天地图密钥仍由 Server 管理。
仓库默认搜索服务为 Exa,通过 `opencode.json` 的 `websearch.provider` 配置。启动时检查原生搜索工具、服务目录及有效配置,不发送实际搜索。`bun run check:websearch` 在独立 OpenCode 中检查目录;`bun run check:websearch --search` 额外搜索一次公开的 OpenCode 文档,输出服务 ID 与结果条数,不调用模型推理,也不接管现有实例。该检查使用临时原生数据库和配置目录;仅在全局 OpenCode 账户存储中的搜索凭据不会被复制。
后台执行由 worker 提交 Server 已保存的 `task_id`、`run_id`、`execution_request_id`。Agent claim 成功后每 20 秒发送心跳,运行正常结束显式调用 `complete`,失败或中止调用 `fail`。Server 决定活动运行和租约状态;计划、报告或 `final_answer` 的发布不代表业务任务完成。执行计划规则见 [工况执行计划](docs/execution-plan.md)。
后台调查在真实步骤开始或路线变化时发布完整进度,Mermaid 节点的显式状态和供水要素引用随进度保存;图可重绘,todo 不决定图或运行状态。资料展示命令支持要素高亮/闪烁、图表开关和证据图层,图层几何与数值由 Server 从已保存证据提取。工具返回 `recorded` 表示展示请求已记录,不表示浏览器显示成功。前端 Dev 实时跟随仅作用于浏览器;无人查看或关闭跟随时调查照常运行,不回放历史展示,也不重新触发调查。普通聊天仍使用现有 `frontend-action@1` 浏览器回执。
交互会话遇到 `credential_refresh_required` 后,前端更新凭据;令牌只通过鉴权头传递,工具请求最多重试一次,403 不触发续期。后台 worker 使用已有 OAuth provider 更新凭据,不等待浏览器;无法写回失败时仍由 Server 的租约恢复处理。
服务保留 `agent-ui@1` UIEnvelope 兼容接口,目前该注册表为空。当前 6 个浏览器动作使用独立的 `frontend-action@1` 注册表;前端需声明能力并回传动作结果。待办使用 `todoread`、`todowrite`,通过 `todo_update` 更新前端。协议见 [Agent UI 协议](docs/agent-ui-protocol.md)。
大结果文件中转、供水瓶颈和服务区算法仍未从旧仓库迁入。
## 配置外部 MCP
在 `opencode.json` 的 `mcp.servers` 中添加服务,或由部署环境提供 `OPENCODE_CONFIG_CONTENT`。默认未连接任何外部 MCP。连接地址、命令和凭据由管理员配置,不接受浏览器或模型临时提交。
下面给出远程 HTTP 和本地 stdio 两种配置片段,示例均禁用,填写真实连接信息后再启用:
```json
{
"mcp": {
"timeout": { "startup": 10000, "catalog": 10000, "execution": 60000 },
"servers": {
"mcp_docs": {
"type": "remote",
"url": "https://mcp.example.com/mcp",
"protocol": "auto",
"oauth": false,
"headers": { "Authorization": "Bearer {env:MCP_DOCS_TOKEN}" },
"disabled": true
},
"mcp_company": {
"type": "local",
"command": ["node", "/opt/company-mcp/server.js"],
"cwd": "/opt/company-mcp",
"environment": { "MCP_API_KEY": "{env:COMPANY_MCP_API_KEY}" },
"protocol": "legacy",
"disabled": true
}
}
}
}
```
服务名必须以 `mcp_` 开头。远程默认使用 `auto`,本地默认使用 `legacy`;代码还支持显式协议值 `2026-07-28`。超时单位为毫秒,默认启动和目录查询各 10 秒、执行 60 秒,可按服务覆盖。`codemode` 默认开启,改为 `false` 时直接暴露工具,两种方式使用同一权限策略。
管理员核实为只读的工具,可以在 `permissions` 中添加精确的自动放行规则;名称以会话 MCP 状态接口返回的工具 ID 为准,后续匹配的拒绝规则仍生效:
```json
{ "action": "mcp_docs_search", "resource": "*", "effect": "allow" }
```
宽泛的 `mcp_* allow`、服务自报的 `readOnlyHint` 和历史审批不构成只读白名单。普通聊天和任务协作中,白名单外调用需用户审批;后台任务只能调用白名单工具,学习复盘禁用 MCP。通用 MCP 资源读取也受会话策略约束,外部结果不能直接替代 Server 保存的业务证据。
需要 OAuth 的服务应省略 `oauth: false`,由管理员以同一服务账号和 XDG 数据目录执行 `opencode2 mcp auth mcp_docs`。TJWater 的 Bearer 和项目头不会自动转交外部 MCP。
本地 stdio MCP 是管理员信任的宿主进程,不受会话 Shell 沙箱约束。插件收敛继承环境,只传入基础运行变量及明确配置的 MCP 环境;该进程仍有服务账号的宿主文件和网络权限。
会话 MCP 状态接口返回服务名、类型、状态、配置协议和工具 ID,不返回连接地址、命令或凭据。状态可能为 `connected`、`pending`、`disabled`、`failed`、`needs_auth`;配置协议不代表实际协商版本,连接成功也不代表工具目录已加载。结构错误会阻止启动,可选服务连接失败或等待 OAuth 不影响核心服务就绪。
## 前端与任务接入
Agent 服务的 API 前缀是 `/api/v1`,路径不再包含 `/agent/chat`。生产请求始终携带产品 Bearer,项目会话额外携带项目头;涉及会话的操作还校验用户及个人或项目范围。前端 Agent 面板可选择“个人会话”或“当前项目”,切换会开启新会话,历史分别加载,不改变已有会话归属。任务执行与任务协作始终属于项目。
| 方法 | 路径 | 用途 |
| --- | --- | --- |
| GET | `/health` | OpenCode 和沙箱就绪状态,无聊天前缀 |
| GET | `/models` | 聊天模型菜单与默认值,不含 provider 凭据 |
| POST | `/session` | 创建会话 |
| GET | `/sessions`、`/session/:session_id` | 列表和会话内容 |
| DELETE | `/session/:session_id` | 删除聊天记录及其会话工作区 |
| POST | `/responses` | Responses 文本请求;stream=true 返回 SSE,否则返回 Response JSON |
| GET | `/session/:session_id/inputs` | 当前会话尚未处理的用户消息 |
| PATCH | `/session/:session_id/inputs/:input_id` | 将未处理消息切换为 steer 或 queue |
| GET | `/session/:session_id/stream` | 订阅会话流 |
| POST | `/abort` | 中止运行 |
| POST | `/permission/:request_id/reply` | 回复工具审批 |
| POST | `/question/:request_id/reply`、`/question/:request_id/reject` | 回复或拒绝澄清问题 |
| POST | `/session/:session_id/credentials` | 刷新运行凭据 |
| GET | `/ui-registry`、`/frontend-action-registry` | 查询界面与浏览器动作协议 |
| POST | `/frontend-actions/:action_id/result` | 浏览器回传动作结果 |
| GET | `/session/:session_id/mcp` | 查询本会话 MCP 状态和实际工具 ID |
| POST | `/task-executions` | 提交后台任务执行 |
| POST | `/task/:task_id/collaboration/session` | 创建任务协作会话 |
消息请求使用 Responses 文本子集:`input` 为字符串或 user/input_text 消息数组,`conversation` 绑定已有会话(字符串或 `{ "id": "..." }`),可选 `model`、`stream` 和标准字符串 `metadata`。`stream` 默认 false。TJWater 的 `approval_mode`、`capabilities`、`task_context` 和 `trigger_context` 放在独立的 `tjwater` 扩展对象中,不接受模型自行指定身份、项目或工具权限。发送新增用户输入即可,历史从已授权会话读取,不重复上传整份 UI 历史。
运行中的普通聊天和任务协作可继续向 `/responses` 提交非流式文本,指定已有 `conversation`、`tjwater.delivery`(`steer` / `queue`)及稳定的 `tjwater.input_id`(`msg_` 加32位小写十六进制字符)。返回 HTTP 202 和 `object="tjwater.response_input"` 的收件回执,表示输入已由原生收件箱接管;`pending` 不代表模型已读取。前端默认 steer,可选择 queue;steer 在下一步引导当前工作,queue 等待当前工作完成后处理。追加输入不切换模型、审批、任务或授权上下文,也不创建第二条宿主生成循环。原生成流保持连接,`tjwater.user_input` 事件报告输入接收和处理,处理后的用户消息保存到会话历史。
未处理消息通过 `/session/:session_id/inputs` 查询,可 PATCH `{ "delivery": "queue" }` 或 `{ "delivery": "steer" }` 切换;已取走时返回409,前端刷新列表。每会话最多20条待处理消息,按次最多接收500条追加输入;停止会话会取消尚未处理的本类输入。接口校验用户、项目和会话归属,不能直接追加或修改后台 `task_execution` 调查输入,也不能借此更改代理协作消息。会话收尾或启动尚未就绪时返回409,不能自动重试为新一轮。
这里是现有 HTTP/SSE 服务的 `tjwater` 扩展,映射 OpenCode 原生用户收件箱;不等同于 OpenAI WebSocket 的 `response.steer` 事件,官方协议边界见 [Mid-turn steering](https://developers.openai.com/api/docs/guides/steering)。验证命令 `bun tests/integration/response-input-smoke.ts` 使用隔离 OpenCode 和本机确定性模型,验证模式切换及队列后续输出,不调用付费推理。
流使用 `response.created`、`response.in_progress`、`response.output_item.added/done`、`response.content_part.added/done`、`response.output_text.delta/done` 和终态 `response.completed/failed/incomplete`,带单调递增的 `sequence_number`。中止返回 incomplete;未知原因不伪造标准 incomplete reason。进度、审批、问题、凭据续期、地图和富文本展示通过 `tjwater.<事件名>` 扩展携带 `response_id`。前端使用 Responses transport 接入现有 useChat 和 UIMessage 渲染;UIMessage 不再是生成接口的网络协议。标准流语义见 [OpenAI Responses streaming](https://developers.openai.com/api/docs/guides/streaming-responses)。
当前支持文本、受权会话续接和流式/非流式生成;`previous_response_id`、客户端指定 tools、图片/文件输入及其余未实现参数均返回400,不能将此接口宣称为完整 OpenAI API 代理。模型推理仍由 OpenCode 执行,上游 provider 协议沿用 `agent-models.json` 的配置。`GET /session/:session_id/stream` 保留独立的会话状态订阅,事件包含 state、token、progress、done 等,用于历史恢复和已运行任务跟随;Server 的 `/api/v1/workbench/events` 继续承载业务事件。
发布须同步 Agent、前端与 Server Worker。旧 `/agent/chat` 路径和 POST `/stream` 已移除;原会话数据目录、会话ID及权限规则不迁移。现有 Worker 进程需加载更新后的任务执行、消息投递和凭据续期地址。
开发环境的本地上下文可通过 `AGENT_LOCAL_USER_ID`、`AGENT_LOCAL_PROJECT_ID`、`AGENT_LOCAL_NETWORK` 设置,对应请求头为 `X-Agent-Local-User`、`X-Agent-Local-Project`、`X-Agent-Local-Network`。`X-Trace-Id` 用于请求追踪。
## 部署与维护
[Dockerfile](Dockerfile) 安装匹配的 OpenCode 2 和沙箱依赖,并把统一模型配置复制到镜像。环境文件、运行数据和日志不进入镜像。容器仍依赖宿主内核支持 Landlock。
部署时通过环境文件或密钥管理器注入凭据,并将模型文件及运行数据按需要挂载。比如 Compose 服务可包含以下片段,需合并到自己的完整部署配置中:
```yaml
env_file:
- .local.env
volumes:
- ./agent-models.json:/app/agent-models.json:ro
- ./data:/app/data
- ./logs:/app/logs
- ./skills:/app/.opencode/skills
- ./opencode-data:/root/.local/share/opencode
```
配置 `AGENT_MODEL_CONFIG_PATH` 时需把相应文件挂载到容器可读路径。`docker-compose.yml` 是被 Git 忽略的本地配置,使用前需核对当前模型入口、环境变量和数据卷;不要沿用旧模型环境变量。
常用维护命令:
| 命令 | 用途 |
| --- | --- |
| `bun run check` | 检查服务和 OpenCode 插件的 TypeScript 类型 |
| `bun run check:models` | 通过实际 OpenCode 和上游目录检查模型,不请求推理 |
| `bun run start:prod` | 类型检查通过后启动服务 |
| `bun test tests/**/*.test.ts` | 全部单元测试,日常修改优先运行相关测试 |
| `bun run tests/integration/query-mcp-smoke.ts` | 验证第一方原生 MCP 查询发现、会话元数据、权限和混合工具保留;加 `--long-calculation` 额外验证90秒预算下的65秒静默调用,不运行真实模拟或推理 |
| `bun run tests/integration/mcp-smoke.ts` | 可选的本地 MCP 联调,使用自己的样例服务和临时数据,不请求推理 |
| `bun run tests/integration/workspace-smoke.ts` | 可选的原生工作区权限检查,验证本会话可读、其他业务数据不可读,不请求推理 |
[CI 工作流](.gitea/workflows/package.yml) 在推送 `v*` 标签或手动触发时调用共享容器流水线。当前镜像名为 `gitea.waternetwork.cn/orgtjwater/next-tjwater-agent`,部署服务名为 `next-agent`;仓库需设置 `REGISTRY_USERNAME`、`REGISTRY_PASSWORD`、`DEV_DEPLOY_SSH_KEY`。具体候选验证和发布步骤由共享模板定义。
代码修改约束见 [AGENTS.md](AGENTS.md),前端协议见 [Agent UI 协议](docs/agent-ui-protocol.md),任务计划规则见 [工况执行计划](docs/execution-plan.md)。
## 起始问题推荐
`POST /api/v1/suggestions` 接收可选的已配置聊天模型 `model` 和最多 12 条 `activities`。每条活动包含 `id`、`kind`(alert/task/analysis/session)、`title`、`occurred_at`、`origin`(backend/agent/demo),以及可选的 `summary`、`status`;它们是当前工作台已加载的快照,不替代 Server 的权威业务记录。
接口沿用聊天路由的登录身份与会话范围校验。OpenCode 在临时原生上下文中执行一次性文本生成,不派发聊天轮次或业务工具,也不写入用户会话历史;完成或失败后删除该上下文及其专用工作区。结果为 `suggestions`(最多三条 question/activity_id)及 `generated_at`,活动引用必须来自请求,演示活动的问题始终标记为演示。
推荐按已验证用户、项目、模型和活动快照合并并发请求并缓存 5 分钟,失败不缓存。推理超时为 20 秒;生成失败返回 503,前端保持输入可用,不展示固定问题作为生成结果。不需要增加 provider、密钥或独立模型默认值。
聚焦检查:
```bash
bun test tests/chat/promptSuggestions.test.ts tests/routes/promptSuggestions.test.ts tests/runtime/opencode.test.ts
bun run check
```
显式联调会执行一次真实模型调用,可在独立 OpenCode 实例中验证:
```bash
OPENCODE_MODE=embedded OPENCODE_PORT=0 bun run tests/integration/prompt-suggestions-smoke.ts
```
### 本地交互式聊天回答
原生插件通过 `present_answer` 发布 `answer-ui@1` 内容块,现有聊天消息流输出
`data-answer_ui`,允许文字与本地指标、表格、图表、证据、地图定位和补充资料表单交替出现。
相同 block_id 更新本轮内容;消息持久化与历史恢复保留结构化部分。最终答复包含
完整报告文字及 ui_block_ids 引用的已发布组件,其余过程默认折叠。已发布界面的
聊天在最终答复完成且无待完成工具时收尾,不代替后台任务的完成接口。后台调查执行
不发布聊天界面块,继续使用原任务报告和展示通道。
组件参数契约由供水前端 catalog 导出到 `src/chat/answer-ui-catalog.json`,
本服务按该契约校验组件参数及树结构。前端与 Agent 需同步更新,插件变化需要重启
Agent,客户端模式也需更新外部 OpenCode 的可信资产。无需新增 domain key、
UI 托管服务配置或数据库迁移。详见
[前端接入与扩展说明](../next-tjwater-frontend/docs/agent-answer-ui.md)。
原生工具发现验证:`bun run tests/integration/answer-ui-smoke.ts`,使用独立临时数据库,
不调用模型推理,不接管既有服务。
## 第一方产品 MCP 查询
新增10项受控分析:标准水力模拟、爆管侦测、爆管定位、阀门隔离、DMA漏损识别、污染扩散、关阀影响、冲洗分析、调泵影响和压力监测点选址。普通项目会话与调查主Agent可按权限调用,reader保持只读。计算不保存正式方案或下发设备指令;调查结果和输入假设沿用证据链保存。计算工具支持Agent按任务指定 `timeout_seconds`,默认120秒,上限由Server工具schema公布(默认1小时);HTTP、宿主和OpenCode同步使用相应预算,普通查询仍为30秒。修改Server上限后需重启Agent以重新发现目录并更新原生连接。长计算超时不会自动重试。能力、单位、模板隔离和发布边界见[分析工具说明](../next-tjwater-server/docs/mcp-analysis-tools.md)。
本地开发手动启动Agent,读取项目 `.local.env`,已有系统环境变量优先。MCP随Server运行。仓库保留 [tjwater-agent.service](infra/systemd/tjwater-agent.service) 作为可选部署模板;用户级Server/Agent服务已移除,不与手动进程同时启用。选择systemd部署时,私有 `~/.config/next-tjwater/agent.env` 会覆盖项目同名配置,必须与Server及前端的认证环境一致,并复用已有原生会话数据库。
安装时将两个仓库的 `infra/systemd/tjwater-{server,agent}.service` 分别复制到 `~/.config/systemd/user/`,准备上述私有覆盖文件,完成后端依赖和项目迁移后执行 `systemctl --user daemon-reload` 以及 `systemctl --user enable --now tjwater-server.service tjwater-agent.service`。后端读取自身 `.env`;生产覆盖中的 issuer 必须与后端、前端登录及 worker 取令牌地址一致。本机服务状态可用 `systemctl --user status tjwater-server tjwater-agent` 检查,Agent 就绪地址为 `http://127.0.0.1:8787/health`。
项目及宿主只读查询统一由原生连接 `mcp_tjwater` 提供,工具 ID 为 `mcp_tjwater_<name>`。本地 MCP 使用官方 Server SDK 2.3.1,固定 MCP 2026-07-28,默认提供地理编码、会话搜索、调查任务列表与详情、消息读取和待办读取六项查询,启用上游后合计41项。公开网页搜索使用 OpenCode 原生 `websearch`。`memory_manager`、`skill_manager` 保留完整读写插件;消息发送、报告发布和地图展示等也保留插件形式。
`mcp_tjwater_query_nearby_elements` 查询当前授权项目的模型管网:将 `mcp_tjwater_geocode` 返回的 EPSG:4326 `center: [经度,纬度]` 原样传入,后端负责坐标转换。`element_type` 默认 `pipe`,`radius_m` 默认200、最大10000米,`limit` 默认5、最大100,支持 `offset`。结果按点到完整几何的 WGS84 椭球最短距离排序,返回模型 `element_id`、米制 `distance_m` 和 `properties`;可用 `locate_features` 定位。原始 GIS 的 `source_fid` 不在此查询范围。空结果不自动扩大半径,空间邻近不代表连通或供水归属。普通查询预算仍为30秒,用户、项目和调查证据链沿用现有 MCP 规则。
事件业务处置 Skill 由 Server 维护并经任务 MCP 分发:`get_task_context` 摘要返回 `handling_skill` 名称、版本和内容摘要,`section="skill"` 返回按已绑定任务 workflow 选择的完整流程,不保存为事件证据。Agent 本地 Skill 维护通用执行方法,不复制或覆盖 Server 业务规则;权限、人工审批与动作执行仍由 Server 控制。报告和进度的人类可读时间统一使用 Asia/Shanghai(UTC+8),API 时间保留原始时刻。更新 Agent 指令或 Server 工具 schema 后须重启 Agent 重新加载。
`TJWATER_MCP_ENABLED` 默认 false,只控制上游 Server 项目查询。启用后,宿主通过官方 Client SDK 发现35项工具(25项查询、10项分析),合并到本地 MCP 目录,由 OpenCode 原生发现调用。旧 `query_project_data`、`query_operational_task_data` 和任务上下文查询插件及其 HTTP 查询桥已移除;未启用时项目业务查询不可用,不回退。普通项目会话和调查主 Agent 可使用;直接只读 reader 仍仅可使用25项查询;身份、项目和任务由宿主确定。调查查询保存 Server 证据,普通聊天不自动保存调查证据。
三类 Agent 均优先单对象、有过滤条件和必要短时间窗的查询。集合默认20条,按需翻页,不自动遍历全网;offset 上限10000,超过时缩小时间窗或目标。历史组合查询按序列、时间对数据点分页;读取证据优先指定 data_path 和小页。DEMO 样本超过整份摘要校验的8MiB预算时需拆分数据源,反复缩小时间窗不能解除此限制。截断或非零offset均不能证明完整范围无异常。
历史曲线展示调用从每页500个数据点起步,遇到 `RESULT_TOO_LARGE` 原位减半重试;最多读取1000条、发送16次请求,共享120秒超时(单次普通查询仍为30秒),展示最多100个有效点。空值页不等于没有历史数据,预算内仍有未读取数据时会明确说明。此预算独立于模型首次查询默认20条和时序接口单页10000条上限。
`get_task_context` 默认返回固定摘要和可读章节;完整任务、指定报告、证据、动作、进度、授权申请、计划和运行信息通过 section/data_path 分页读取。预览带 `preview_only`,不能当作完整报告。详情每页默认20项、最多500项,长文本按字符分页,数据库返回前限制8KB,超限时缩页或读取更深路径。上游大结果的证据资源链接在本地转成 `evidence_read` 工具参数,原证据编号与来源保留,不要求 OpenCode 直接读取上游资源。
宿主需配置 `TJWATER_MCP_ISSUER_URI`、`TJWATER_MCP_RESOURCE_URI`、`TJWATER_MCP_AUDIENCE`、`TJWATER_MCP_CLIENT_ID`、`TJWATER_MCP_CLIENT_SECRET`。用户及 worker 的产品令牌在宿主内交换成专用短期 MCP 令牌;凭据续期复用现有机制。MCP 401清缓存并重试一次,403不申请更大 scope;会话释放取消调用并清除缓存。OAuth 密钥和 Bearer 不发送到 OpenCode。
认证先按401 challenge 或 well-known 发现资源及 OAuth/OIDC 元数据,严格绑定配置中的资源和 issuer,再向元数据声明的 token endpoint 发送凭据;支持 Basic 和表单客户端认证。公开发现无凭据,最多64KiB/文档、10秒/轮,缓存5分钟;MCP 401 后重新发现。两种 grant 均发送标准 resource,exchange 还发送专用 audience。启用前须确认身份平台支持 resource 到 audience 的映射;若 Keycloak 拒绝 resource,需先升级或适配。不会通过移除参数重试来绕过该前提。
本地 MCP 独立监听127.0.0.1临时端口,先于原生工具发现启动;业务 HTTP 服务仍在完整预热通过后监听。公开连接描述位于元数据目录的兄弟目录 `mcp-query/connection.json`,不含密钥。embedded 自动注入 `TJWATER_AGENT_MCP_CONNECTION_PATH`;client 模式须共享该路径、同一内部密钥和同版本可信插件,Agent 重启后同步重载 MCP 连接。连接只适用于同一宿主网络空间。
OpenCode 按标准 MCP `tools/call` 调用,以 `_meta["ai.opencode/sessionID"]` 传入会话关联值。该值不是凭据:本地 MCP 先校验内部密钥、loopback 地址并拒绝浏览器请求,再解析宿主会话或已验证的直接只读子会话。真实身份、项目、任务和专用 OAuth 令牌由宿主决定。精确 MCP 权限规则对直接调用和 Code Mode 均生效;reader 只可查询父任务范围,不能发布、审批、执行 Shell 或递归委派。待办读取经宿主 RPC 访问原插件存储,保留已有清单。
配套 Server 的 `docs/mcp-query-implementation.md` 提供 Keycloak、SQL migration 和配置步骤。本次没有配置真实身份平台、迁移真实数据库或部署;开启前须完成真实项目验收。