Files
TJWaterAgent/README.md
T
jiang 80cfc1f2ab
Generic Container CI/CD / test-build-publish (push) Successful in 2m44s
Agent CI/CD v2 / build-test-publish-and-deploy (push) Successful in 2m44s
feat(agent): sandbox conversation analysis
2026-08-25 16:08:33 +08:00

118 lines
7.9 KiB
Markdown
Raw Permalink 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.
# TJWaterAgent 内部智能体服务
`TJWaterAgent` 是 TJWater 内部版智能体服务,负责连接前端聊天界面、OpenCode 运行时、MCP 工具和 TJWater 后端 API。它面向内部研发与部署,保留完整的 Agent 编排、会话上下文、工具调用和运行时调试能力。
## 主要能力
- 提供 `POST /api/v1/agent/sessions/{session_id}/runs` SSE 聊天接口。
- 以内嵌模式启动并预热 OpenCode 运行时。
- 管理前端 `session_id` 与 OpenCode session 的映射。
- 在服务端保存当前会话的用户 token、项目、network 和 trace 上下文。
- 通过 `.opencode/tools` 和 MCP 工具驱动地图定位、图表、SCADA、历史数据和业务 API 调用。
- 通过 `data/` 保存运行时会话元数据、结果引用和本地状态。
## 目录结构
```text
src/ 服务端 TypeScript 源码
src/routes/ HTTP 路由
src/chat/ 聊天流和 SSE 事件适配
src/runtime/ OpenCode 运行时管理
src/session/ 会话映射和运行上下文
src/mcp/ MCP 服务与工具桥接
cli/ Agent 使用的 TypeScript 后端 API CLI
.opencode/agents/ Agent prompt 和模型行为配置
.opencode/tools/ OpenCode 自定义工具
.opencode/skills/ 可复用分析工作流
node-tests/ Node CLI 测试
data/ 本地运行时数据,禁止提交
logs/ 本地日志,禁止提交
```
仓库跟踪 `.opencode/skills/` 中经过评审的默认工作流基线;部署环境仍可通过持久化卷保留 `skill_manager` 在运行中沉淀的增量内容。默认基线不得包含真实客户数据、认证信息或本地执行产物。
## 本地开发
项目使用 Bun
```bash
bun install
bun run dev
```
`bun install` 会通过 `postinstall` 安装 `.opencode` 子目录依赖。`bun run dev` 以 watch 模式启动 `src/server.ts`,修改 `src/**``.opencode/**``opencode.json``.local.env` 后会自动重启。
`cli/tjwater-cli` 是当前唯一的 TJWater 业务 CLI 入口,由 Bun 直接执行
`cli/tjwater-cli.ts``cli/src/` 源码,并随 Agent 镜像一起交付,不需要
Python 或 PyInstaller 构建步骤。
## 常用命令
```bash
bun run check
bun run contract:generate
bun run test:api
bun run test:cli
bun run start
bun run start:prod
docker build -t tjwater-agent:local .
```
- `bun run check`:检查主项目和 `.opencode` 的 TypeScript 类型。
- `bun run contract:generate`:生成 `contracts/agent-v1.openapi.json`
- `bun run test:api`:验证公开 REST 契约和聊天路由。
- `bun run test:cli`:运行 `node-tests/cli/*.node.mjs`
- `bun run start`:直接启动服务。
- `bun run start:prod`:先类型检查,再启动服务。
## 运行模式
当前运行时使用 OpenCode 稳定版 1.x CLI,并通过稳定版 SDK 的 `@opencode-ai/sdk/v2` HTTP 客户端访问运行时;这与 `opencode2``@opencode-ai/client` 的 2.0 beta 运行时不同。Embedded 模式由服务进程拉起本机 OpenCode:
```bash
OPENCODE_MODE=embedded
TJWATER_API_BASE_URL=http://127.0.0.1:8000
```
当前仅支持 Embedded 模式,不支持连接外部 OpenCode server。
## 认证续期与学习工具
后端工具调用遇到即将过期的 access token 或首次 `401` 时,Agent 会通过当前 SSE 流发送 `credential_refresh_required`。前端使用服务端保存的 Keycloak refresh token 强制换取新 access token,再调用 `POST /api/v1/agent/sessions/{session_id}/credential-refreshes` 唤醒原工具调用。等待上限为 30 秒,同一会话的并发请求合并为一次续期,原调用最多重试一次;`403` 不触发续期。
`memory_manager``skill_manager` 在 OpenCode 侧只保留内部 HTTP 桥,读取会话上下文和持久化数据的逻辑统一在 Agent 主进程中执行。长期记忆、自动学习和显式工具写入因此共享同一组 `MemoryStore``SkillStore` 和运行时会话上下文。
本地可使用 `.local.env` 保存开发配置;系统环境变量优先级更高。
服务会在 HTTP 端口开始监听前完成 OpenCode 健康检查、临时会话创建和工具目录加载。`GET /health` 返回 `ready: true``warmed_up: true` 时,表示冷启动预热已经完成。开发环境会输出各预热阶段的耗时。
`opencode.json` 已启用 `experimental.continue_loop_on_deny`。用户拒绝权限请求后,OpenCode V1 会把拒绝结果交还给 Agent,让其尝试无需该权限的替代方案,而不是直接结束本轮执行。
前端提供三种整体权限模式:“请求批准”只执行 OpenCode 明确允许的白名单,Shell 和写操作逐次交给用户确认;“自动批准”额外自动放行低风险业务工具、skill、沙箱 Shell,以及真实路径位于当前 conversation workspace 内且通过 realpath/symlink 校验的 read/edit/glob/grep;“始终允许”自动放行当前对话中所有未被 OpenCode 明确禁止的权限请求。自动放行统一使用单次批准,切换整体模式后立即恢复对应策略,不会写入持久授权。
单次权限请求支持“允许一次”“保存授权”和“拒绝”。“保存授权”使用 OpenCode 的 `always` 回复,仅保存 OpenCode 为本次请求建议的权限范围,并只在当前 OpenCode 会话内生效。任意外部目录默认仍由静态配置禁止;`.env`、普通 `data/``logs/` 和其他对话目录保持禁止。真实聊天会话使用 `data/conversation-workspaces/<随机目录>/` 作为独立工作目录。普通 `rm <文件>``rmdir` 和非强制递归删除可在沙箱内执行,`rm -rf`/`rm -fr` 及等价的递归强制删除形式会在执行前拒绝。
OpenCode 的内置 Bash 由同名自定义工具覆盖。命令经内部鉴权路由进入独立子进程,切换到专用非 root UID 后应用 Landlock 文件规则和 seccomp 网络规则:当前 conversation workspace 可读写,系统/Python/skills 只读,其他应用文件、其他对话和全局 `tool-output` 不可见;IPv4/IPv6 TCP 与 UDP socket 均被拒绝。启动时会探测 Landlock ABI(要求 ≥4)和 libseccomp,失败时 Agent 直接启动失败,不会回退到未沙箱化 Shell。Shell 环境不包含模型 key、内部 token 或用户 access token`HOME``TMPDIR` 和 Python 缓存均位于当前对话目录。
`store_render_ref` 只会从当前对话绑定的工作目录导入包装格式 JSON;工作区根目录固定为项目内的 `./data/conversation-workspaces`,以确保 OpenCode 能继续发现项目配置和工具。文件必须包含 `metadata``location.file_path``data`,且真实路径不能越出当前对话目录;单文件默认上限为 128 MiB,成功导入后只删除这一份源包装文件。升级前已经存在的会话没有独立工作目录,需要新建对话后才能使用该导入能力。
CLI 桥接层对 stdout 设置独立的 128 MiB 硬上限(`MAX_CLI_OUTPUT_BYTES`);stderr 最多保留 256 KiB`MAX_CLI_STDERR_BYTES`),超出后截断但不会终止 CLI。`MAX_INLINE_RESULT_BYTES`(默认 12000 字节)仅决定内联还是落盘:较大结果写入当前对话的 `tool-data/` 并返回 `data_file.file_path`,不会因为超过 12000 字节杀掉 CLI;分析脚本需要文件输入时可由 `tjwater_cli(store_result=true)` 强制落盘小结果。会话暂存数据不自动删除。OpenCode 自身为其他工具生成的全局 `tool-output` 仍按 `RESULT_REF_TTL_HOURS`(默认 7 天)清理,但沙箱命令不能访问该目录。
## 配置与安全
不要提交 `.env``.local.env``data/``logs/`、会话记录、模型输出、访问令牌或 `node_modules/`。部署凭据、镜像仓库账号和 webhook 地址应放在 Gitea secrets 或部署环境变量中。
## 发布
Gitea 包工作流位于 `.gitea/workflows/package.yml`。发布前至少运行:
```bash
bun run check
```
如修改 CLI 或工具调用逻辑,同时运行:
```bash
bun run test:cli
```