251 lines
11 KiB
Markdown
251 lines
11 KiB
Markdown
# gori-agent
|
||
|
||
`gori-agent` 是一个 Node.js 20+ / TypeScript 网关:从一个 IM 平台接收消息,通过官方 Agent Client Protocol(ACP)驱动一个 Coding Agent。
|
||
|
||
```text
|
||
用户 → 单个平台 Adapter → Gateway → ACP Session Manager → 单个 ACP Agent
|
||
```
|
||
|
||
## Config v3 实例模型
|
||
|
||
Config v3 只支持以下边界:
|
||
|
||
- 一份运行配置对应一个固定 Bot 身份。
|
||
- 一个 Bot 只有一个 workspace、persona、ACP agent、skill 列表和 permission policy。
|
||
- 一个实例只绑定一个平台;多平台使用多个实例。
|
||
- 不再有 `roles[]`、`defaultRole`、`backends[]`、动态 `/role` 切换或 Config v1/v2 迁移。
|
||
- `configVersion` 非 `3` 会明确失败,旧 state v1 也不会自动改写。
|
||
|
||
一台机器上的实例统一位于:
|
||
|
||
```text
|
||
${GORI_AGENT_ROOT:-$HOME/.gori-agent}/instances/<bot-id>/
|
||
├── config.json
|
||
├── logs/gori-agent.log
|
||
└── state/
|
||
├── acp-sessions.json
|
||
└── gori-agent.pid
|
||
```
|
||
|
||
实例目录就是该进程的 `GORI_AGENT_HOME`。实例目录、`state/`、`logs/` 使用 `0700`,配置与状态文件使用 `0600`。
|
||
|
||
## 构建与安装
|
||
|
||
```bash
|
||
npm install
|
||
npm run build
|
||
./install.sh
|
||
```
|
||
|
||
安装器只安装一个 launcher;不会为每个 Bot 复制源码或 `dist/`。默认 launcher 位于 `$HOME/.gori-agent/bin/gori-agent`。
|
||
|
||
确认 ACP agent 可用:
|
||
|
||
```bash
|
||
kimi --version
|
||
kimi doctor
|
||
kimi acp --help
|
||
```
|
||
|
||
模型和 provider 由 ACP agent 自身配置,例如 Kimi Code 使用 `~/.kimi-code/config.toml`。gori-agent 不复制模型凭据。
|
||
|
||
## 实例管理
|
||
|
||
```bash
|
||
gori-agent init <bot-id>
|
||
gori-agent setup <bot-id>
|
||
gori-agent doctor <bot-id>
|
||
gori-agent start <bot-id>
|
||
gori-agent status <bot-id>
|
||
gori-agent logs <bot-id>
|
||
gori-agent restart <bot-id>
|
||
gori-agent stop <bot-id>
|
||
gori-agent list
|
||
```
|
||
|
||
- `init` 交互生成 Config v3,本身不启动 Bot;它会询问并校验 `gateway.server.host/port`,扫描统一实例目录中其他 Config v3 的声明端口,并在默认端口冲突时建议下一个未占用端口。
|
||
- `setup` 只重配已有实例。它校验目录、配置身份、配置/PID 权限,并拒绝运行中实例或指向其他活进程的 PID;固定 `bot.id`,保留既有 agent(含 args/env)、skills、permissions、gateway policy、runtime、平台 secrets 与 publicBaseUrl,确认写入后自动运行 doctor。
|
||
- 平台 secret/token 使用不回显输入,空 generic webhook/weixin secret 会随机生成。
|
||
- `start` 会检查配置存在、目录名匹配 `bot.id`、目录/配置权限、模板占位符、workspace、agent executable、PID identity 和端口,并等待 `/health` 返回匹配的 Bot/platform identity;任一不满足即 fail closed。
|
||
- 实例进程由内部 `dist/cli/instance-runner.js` 承载;`status` 和 lifecycle 命令精确核对 Node executable、runner 路径及唯一 config 参数,再核对 `/health` 的 Bot/platform identity。
|
||
- 每个实例必须使用不同的 `gateway.server.port` 和平台凭据;setup 的声明冲突提示不替代 `start` 的实际端口检查。
|
||
- `stop` 发送 `SIGTERM`,runner 会优雅关闭 server,并等待最多 10 秒;不会自动 `SIGKILL`。
|
||
|
||
公开 CLI 不兼容旧 `instance` 前缀,也不提供 debug 命令、`--config` 或 `--json`。
|
||
|
||
可用 `GORI_AGENT_ROOT` 改变统一根目录:
|
||
|
||
```bash
|
||
GORI_AGENT_ROOT=/srv/gori-agent gori-agent list
|
||
```
|
||
|
||
## `config.example.json` 的定位
|
||
|
||
`config.example.json` 是仓库内唯一可提交、无密钥的配置说明,不是运行配置。它可被 Config v3 schema 解析,但保留 `BOT_ID`、`QQ_APP_ID` 等明显占位符。只有 `init` 会读取 example 作为初始化种子;其他命令只按 `<bot-id>` 加载统一实例目录中的 `config.json`,缺失即失败。
|
||
|
||
`writeConfigFile()` 使用同目录临时文件、`fsync` 和原子 rename,并强制最终文件为 `0600`。
|
||
|
||
## Config v3
|
||
|
||
完整说明见 `config.example.json`。顶层只有:
|
||
|
||
```text
|
||
configVersion 固定为 3
|
||
bot Bot、workspace、persona、agent、skills、permissions
|
||
gateway HTTP server、入站 policy、唯一 platform
|
||
runtime.acp ACP state、timeout 和 worker pool
|
||
```
|
||
|
||
### Bot 与 ACP agent
|
||
|
||
```json
|
||
{
|
||
"bot": {
|
||
"id": "my-bot",
|
||
"workspace": "/absolute/workspace",
|
||
"persona": "Describe responsibilities, boundaries, and confirmation points.",
|
||
"agent": {
|
||
"id": "kimi",
|
||
"command": "/home/USER/.kimi-code/bin/kimi",
|
||
"args": ["acp"],
|
||
"env": {}
|
||
},
|
||
"skills": [],
|
||
"permissions": {
|
||
"mode": "deny",
|
||
"allowedTools": [],
|
||
"allowedCommandPatterns": []
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
- `bot.id` 只允许小写字母、数字和连字符,最长 63 字符。
|
||
- `workspace` 与每个 skill `file` 必须是绝对路径。
|
||
- `bot.skills[]` 直接声明 `{ id, file, maxBytes }`;ID 必须唯一。
|
||
- agent 使用 `shell: false` 在 `bot.workspace` 启动。
|
||
- agent env 可能包含 secret,不能进入可提交模板、日志或 diff。
|
||
|
||
### Permission policy
|
||
|
||
ACP permission request 由 `bot.permissions` 决策,persona 不是安全边界:
|
||
|
||
- `deny`:拒绝所有 permission request,默认值。
|
||
- `allowlist`:`toolCall.name` 必须与 `allowedTools` 精确匹配;bash/terminal 还必须由至少一个 `allowedCommandPatterns` 正则完整覆盖整条 raw command。名称或 command 缺失时拒绝。
|
||
- `auto`:自动允许,风险等价于 yolo;`doctor` 会告警。
|
||
|
||
`allowedTools` 只控制 ACP 审批,不会注册或创造工具。skills、agent 与 permission policy 是三个独立概念。
|
||
|
||
### 单平台配置
|
||
|
||
`gateway.platform` 是按 `type` 区分的 union,只能选择一个:
|
||
|
||
- `qq`:WebSocket 或 webhook。
|
||
- `feishu`:`/webhook/feishu`。
|
||
- `wecom`:`/webhook/wecom`;入站仍是 501 scaffold。
|
||
- `webhook`:`/webhook/generic`,同步 JSON + HMAC-SHA256。
|
||
- `weixin`:`/webhook/weixin`,外部 bridge scaffold。
|
||
|
||
对象存在即启用,不再使用 `enabled`。Server 只构造所选 adapter,也只挂载需要的 webhook route。QQ WebSocket 仅在 `type=qq` 且 `connectionMode=websocket` 时启动,此模式不挂载 `/webhook/qq`。
|
||
|
||
QQ websocket 示例:
|
||
|
||
```json
|
||
{
|
||
"type": "qq",
|
||
"connectionMode": "websocket",
|
||
"appId": "QQ_APP_ID",
|
||
"clientSecret": "QQ_CLIENT_SECRET",
|
||
"botSecret": "",
|
||
"verifySignature": true,
|
||
"botNames": ["QQ_BOT_NAME"],
|
||
"intents": 33554432,
|
||
"shard": [0, 1]
|
||
}
|
||
```
|
||
|
||
正式 Bot 应通过 `gateway.policy.allowedUsers` / `allowedChats` 限制入口。QQ 群 chat ID 为 `group:<group_openid>`。
|
||
|
||
### ACP 生命周期
|
||
|
||
默认 `runtime.acp.promptTimeoutMs` 是 `7200000`(2 小时),适合长构建/部署任务。其他默认值:
|
||
|
||
- initialize:10 秒
|
||
- cancel grace:5 秒
|
||
- idle worker:30 分钟
|
||
- sweep:60 秒
|
||
- max processes:8
|
||
|
||
`stateFile` 为空时使用当前实例的 `$GORI_AGENT_HOME/state/acp-sessions.json`。
|
||
|
||
## Session 与 state v2
|
||
|
||
每个绑定由当前实例固定的 `platform + chatId` 唯一确定。state v2 header 保存 `botId` 与 platform type;打开 state 时身份不匹配会拒绝启动,避免错误复用另一个实例目录。
|
||
|
||
Binding 保存:
|
||
|
||
- chat key
|
||
- agent ID
|
||
- native ACP session ID
|
||
- workspace
|
||
- Bot fingerprint
|
||
- 创建与更新时间
|
||
|
||
Bot fingerprint 包含 Bot ID、workspace、persona、agent、permissions、skill 路径/内容 hash 和 bootstrap schema version。任一语义变化后,下一条消息创建新 native session,不恢复旧身份上下文。
|
||
|
||
首次 prompt 会创建 native session,发送隐藏 bootstrap,bootstrap 成功后才保存 binding。worker 空闲回收后优先 `session/resume`,否则回退 `session/load`。失败 prompt 不会自动重放,因为工具操作可能已有副作用。
|
||
|
||
旧 state v1 会被原样保留并明确拒绝;使用新的实例目录开始 state v2,不要手工改写运行中的 state。
|
||
|
||
## IM 命令
|
||
|
||
```text
|
||
/help
|
||
/status
|
||
/cancel
|
||
/new
|
||
```
|
||
|
||
- `/status` 绕过 per-chat lock,显示固定 Bot、agent、workspace、队列数、Gateway 当前任务时长,以及 ACP `phase`、`runningSeconds`、`idleSeconds`。`phase` 为 `idle`、bootstrap 的 `initializing` 或普通 prompt 的 `processing`;`idleSeconds` 按当前 session 的最近一次 `session/update` 活动计算,不记录或输出 update 内容。
|
||
- `/cancel` 绕过 per-chat lock,发送 ACP cancel。
|
||
- `/help` 绕过 per-chat lock,可在长任务期间立即返回。
|
||
- `/new` 保持串行,清除当前 chat binding;下一条普通消息创建新 native session。
|
||
- `/roles`、`/role`、`/agents`、`/agent` 会返回固定 retired 提示,不会转发给 ACP。
|
||
|
||
同一 chat 的普通消息和 `/new` 串行执行,不同 chat 可并发。异步平台的普通消息轮到后立即开始 ACP prompt,不等待状态提示发送:15 秒内完成只发送真实结果;15 秒仍未完成时按 running/queued 发送口语化提示,发送过 queued 提示的消息真正开始时再补充开始提示。主动提示从入站绝对时间按 15 秒、60 秒、180 秒、480 秒触发,480 秒后每 600 秒一次(18、28、38 分钟……);它们只描述用户可感知的处理或等待状态,不暴露 ACP、`phase`、`idleSeconds` 等内部技术指标,技术状态只保留在 `/status`。延迟执行的 timer 不补发历史提醒;同步 webhook 不发送这些提示。
|
||
|
||
每条异步入站有独立、串行的回复流,`replySequence` 从 1 动态递增;因此短任务最终回复使用 `msg_seq=1`,已发送提示时后续消息使用下一序号。平台发送失败只记录不含消息正文或 provider 错误详情的安全日志,不影响 ACP 任务或回复流中的后续发送,也不会把成功的 ACP 结果误报为 `Agent error`。最终结果加入回复流后即释放 per-chat lock,平台发送延迟不会阻塞下一项任务;完成与 shutdown 都会清理状态提醒 timer。
|
||
|
||
## HTTP 端点
|
||
|
||
- `GET /health`:`configVersion`、`botId`、platform 和 aggregate ACP counters,不暴露凭据/native session ID。
|
||
- `GET /platforms`:当前单一 Bot/platform 概览。
|
||
- `POST /webhook/<selected-platform>`:只存在所选平台路由;generic 使用 `/webhook/generic`。
|
||
|
||
## Doctor 与安全纪律
|
||
|
||
`gori-agent doctor <bot-id>` 检查:
|
||
|
||
- Config v3 与 example placeholder。
|
||
- 配置权限 `0600`。
|
||
- Bot ID、workspace、skills 与 permissions。
|
||
- ACP initialize 与 session restore capability。
|
||
- state 目录可读写。
|
||
- 单平台必需字段,但不打印 credential 值。
|
||
|
||
自动测试不会启动真实 Bot、发送平台消息、pull、部署或访问远程机器。真实 `config.json`、备份、state 和日志不得提交。
|
||
|
||
## 开发验收
|
||
|
||
```bash
|
||
npm run typecheck
|
||
npm test
|
||
npm run build
|
||
git diff --check
|
||
bash -n install.sh gori-agent.sh bin/gori-agent
|
||
```
|
||
|
||
测试使用 Node `node:test` + `tsx` 和本地 fake ACP 子进程,覆盖 Config v3、permission、bootstrap、timeout/cancel、冷恢复、fingerprint mismatch、state v2 identity、原子配置/state 写入、Gateway retired commands、QQ normalization 和单平台 route。
|
||
|
||
`src/agents/*` 与 `src/core/session-store.ts` 仅为 legacy compatibility/reference,不在当前运行链路。运行时没有单轮 CLI fallback。
|