From 914579a94ca26eea9e5bc7c0f806d3d1f6a2eef4 Mon Sep 17 00:00:00 2001 From: zenord Date: Sun, 16 Aug 2026 11:07:33 +0800 Subject: [PATCH] Add agent configuration guide --- .gitignore | 1 + AGENTS.md | 353 ++++++++++++++++++++++++++++++++++++++++++++ README.md | 18 +++ config.example.json | 64 +++----- 4 files changed, 394 insertions(+), 42 deletions(-) create mode 100644 AGENTS.md diff --git a/.gitignore b/.gitignore index e4d15fe..7f4ac56 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ node_modules/ dist/ .env config.json +config.json.*.bak *.log .DS_Store coverage/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..dc054c7 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,353 @@ +# gori-agent 项目施工指南 + +本文件供 Kimi Code 等 Coding Agent 在本仓库中施工时使用。目标是让后续 Agent 先理解架构和配置契约,再做最小、可验证的修改。 + +## 1. 项目定位 + +`gori-agent` 是一个 Node.js 20+ / TypeScript 项目,通过 IM 接收用户请求,并通过官方 Agent Client Protocol(ACP)驱动 Coding Agent。 + +当前主链路: + +```text +用户 + → QQ / 其他 IM + → Platform Adapter + → Gateway + → AcpSessionManager + → AcpWorker + → ACP Coding Agent(默认 Kimi Code) + → 工具、脚本和工作目录 + → 原路返回用户 +``` + +当前运行时只使用 ACP。不要重新引入“一句话启动一次 CLI”的单轮调用方式,也不要把 legacy `CliAgent` 当作运行时 fallback。 + +模型和 provider 属于 ACP 后端自身的配置。例如 Kimi Code 使用其自己的 `config.toml`;`gori-agent/config.json` 只配置如何启动 ACP backend,不负责复制或管理模型凭据。 + +## 2. 核心设计原则 + +- **Role 是业务身份**:定义职责、workspace、skills 和权限策略。 +- **Backend 是执行后端**:定义 ACP 子进程的 command、args 和 env。 +- **Skill 是可注入知识**:顶层声明 SKILL.md,role 按 ID 引用。 +- **Session 是连续对话**:同一 `platform + chat + role` 复用同一 native ACP session。 +- **权限由 ACP policy 约束**:persona 不是安全边界,不能替代 permission policy。 +- **默认 fail closed**:不确定时拒绝权限,不自动扩大工具或命令范围。 +- **一个进程只登录一个 QQ Bot**:多 Bot 使用多个进程和独立配置、端口、状态目录。 + +## 3. 代码结构 + +- `src/server.ts` + - 组装 store、role registry、ACP session manager、Gateway 和平台 adapter。 + - 挂载 health/platform/webhook 路由,按配置启动 QQ WebSocket。 +- `src/config.ts` + - config v2 的 Zod schema、默认值、交叉引用校验和 v1 Kimi 配置迁移。 + - 新增配置字段时,必须先更新 schema,再更新模板、setup/doctor、测试和文档。 +- `src/core/gateway.ts` + - 入站 allowlist、群聊 mention 规则、命令分发、per-chat 串行锁和回复发送。 + - `/cancel` 必须能绕过 chat lock,避免无法取消长任务。 +- `src/core/command-router.ts` + - `/help`、`/roles`、`/role`、`/status`、`/cancel`、`/new`。 +- `src/core/durable-session-store.ts` + - 持久化 role 选择和 ACP session binding。 + - 使用单 writer lock 和原子写入;不要绕过或手改运行中的 state。 +- `src/roles/role-registry.ts` + - 读取 role、加载 skill、生成 fingerprint 和隐藏 bootstrap prompt。 +- `src/roles/skill-loader.ts` + - 校验 skill 文件、大小限制并计算内容 hash。 +- `src/acp/client.ts` + - ACP initialize、session new/resume/load、prompt、cancel 和 permission request。 +- `src/acp/worker.ts` + - 在 `role.workspace` 中启动 ACP 子进程,处理超时、取消和异常退出。 +- `src/acp/session-manager.ts` + - 管理 chat/role binding、worker 池、冷恢复、idle sweep、容量淘汰和 reset。 +- `src/platforms/qq/` + - QQ WebSocket、webhook、验签、消息标准化和发送。 +- `src/cli.ts`、`src/cli/` + - setup、doctor、status 和 backend discovery。 +- `src/agents/`、`src/core/session-store.ts` + - legacy compatibility/reference,不是当前运行链路。除非需求明确,不要在这里扩展新能力。 +- `dist/` + - TypeScript 构建产物。只修改 `src/`,不要手改 `dist/`。 + +## 4. Config v2 契约 + +唯一可提交的配置模板是 `config.example.json`。真实部署配置复制为本地 `config.json`: + +```bash +cp config.example.json config.json +chmod 600 config.json +``` + +`config.json` 和备份包含 IM credentials,不得提交。 + +### 4.1 顶层字段 + +```text +configVersion 必须为 2 +server HTTP 服务配置 +policy IM 入站访问策略 +acp ACP 生命周期、状态和进程池配置 +backends[] ACP 后端启动定义,至少一个 +skills[] 可选 SKILL.md 定义 +defaultRole 默认 role ID +roles[] 业务角色定义,至少一个 +platforms QQ、Feishu、WeCom、Webhook、Weixin 配置 +``` + +Zod 会剥离 schema 未声明的字段。因此不能只在 JSON 中增加字段而不修改 `src/config.ts`。 + +### 4.2 `server` + +- `host`:默认 `0.0.0.0`。 +- `port`:1–65535;多实例必须使用不同端口。 +- `publicBaseUrl`:反向代理或公开 webhook 基础地址;不需要时为空。 + +### 4.3 顶层 `policy` + +- `allowedUsers: string[]`:空数组表示不按用户限制;非空时精确匹配。 +- `allowedChats: string[]`:空数组表示不按会话限制;非空时精确匹配。 +- `requireMentionInGroup: boolean`:群聊是否必须 @bot。 +- QQ 群 chat ID 形式为 `group:`。 +- 正式运维 Bot 应配置 allowlist,不要长期保持完全开放。 + +### 4.4 `acp` + +- `stateFile`:空字符串时使用 `$GORI_AGENT_HOME/state/acp-sessions.json`。 +- `initializeTimeoutMs`:ACP 初始化超时。 +- `promptTimeoutMs`:单轮 prompt 超时。 +- `cancelGraceMs`:取消后等待时间,超时才终止 worker。 +- `idleTimeoutMs`:空闲 worker 回收时间;session binding 仍可持久化恢复。 +- `sweepIntervalMs`:空闲扫描周期。 +- `maxProcesses`:最大 ACP 子进程数。 + +不同实例不得共享 `stateFile` 或 `GORI_AGENT_HOME`。持久 store 有单 writer lock,共享会使第二个实例启动失败。 + +### 4.5 `backends[]` + +每项字段: + +```json +{ + "id": "kimi", + "command": "/home/USER/.kimi-code/bin/kimi", + "args": ["acp"], + "env": {} +} +``` + +约束: + +- `id` 必须唯一。 +- `command` 必须是可执行程序。 +- Kimi Code backend 应使用 `args: ["acp"]`。 +- 子进程以 `shell: false`、`cwd: role.workspace` 启动。 +- `env` 会覆盖同名进程环境变量;不得把 token 或 API key 写入可提交模板。 +- 新增其他 Coding Agent 前,必须确认它提供兼容 ACP,或提供独立且有测试的 ACP adapter;不要假设普通 CLI 等同 ACP。 + +### 4.6 `skills[]` + +每项字段: + +```json +{ + "id": "SKILL_ID", + "file": "/absolute/path/to/SKILL.md", + "maxBytes": 256000 +} +``` + +约束: + +- `id` 必须唯一。 +- 建议使用绝对路径,避免不同启动目录导致解析变化。 +- 文件必须存在、可读、是普通文件且不超过 `maxBytes`。 +- Role 只能引用已在顶层声明的 skill ID。 +- Skill 内容会发送给 ACP backend,也参与 role fingerprint;不要在 skill 中放 secret。 + +### 4.7 `roles[]` + +推荐一个 Bot 实例只保留它自己的一个 role: + +```json +{ + "id": "ROLE_ID", + "backend": "kimi", + "workspace": "/absolute/path/to/workspace", + "persona": "明确描述职责、边界和何时停止请求确认。", + "skills": [], + "policy": { + "permissionMode": "deny", + "allowedTools": [], + "allowedCommandPatterns": [] + } +} +``` + +约束: + +- Role `id` 必须唯一,`defaultRole` 必须引用存在的 role。 +- `workspace` 必须是绝对路径,并应在 `doctor` 时真实存在。 +- `backend` 和 `skills` 引用必须存在。 +- 修改 role ID、backend、workspace、persona、policy 或 skill 内容会改变 fingerprint;下一条消息会创建新 native session,避免沿用旧身份上下文。 + +权限模式: + +- `deny`:拒绝所有 ACP permission request;新角色默认使用此模式。 +- `allowlist`:只允许 `allowedTools`;bash/terminal 还必须匹配至少一个 `allowedCommandPatterns` 正则。 +- `auto`:自动允许,等价于高风险/yolo;只有用户明确要求、workspace 与 Bot 访问范围都可控时才能配置。 + +配置 `allowlist` 时: + +- 使用最小工具集合。 +- 命令正则应锚定开头并限制参数,不要使用 `.*` 放行所有命令。 +- 删除、部署、推送、发送消息等不可逆或外部操作仍应在 persona 中要求确认。 +- permission request 信息不足时必须拒绝,不能猜测。 + +### 4.8 `platforms.qq` + +```json +{ + "enabled": true, + "connectionMode": "websocket", + "appId": "QQ_APP_ID", + "clientSecret": "QQ_CLIENT_SECRET", + "botSecret": "", + "verifySignature": true, + "botNames": ["QQ_BOT_NAME"], + "intents": 33554432, + "shard": [0, 1] +} +``` + +- WebSocket 登录需要有效的 `appId` 和 `clientSecret`。 +- Webhook 验签使用 `botSecret || clientSecret`。 +- `botNames` 用于 mention 识别,应与平台上的机器人名称一致。 +- 一个 config 只有一个 QQ 对象,因此一个进程只能登录一个 QQ Bot。 +- 不要把 QQ 注销、群注销、无权限等平台 4xx 错误误判成 ACP session 错误;先检查错误码、目标 chat/user openid 和消息发送 endpoint。 + +其他平台字段以 `src/config.ts` schema 为准。平台 `enabled` 不一定会统一禁用所有 HTTP 路由;公开 webhook 时仍需检查路由和验签实现。 + +## 5. Role 与 Session 行为 + +- Binding key 是 `platform + chatId + roleId`。 +- 同一 chat 的不同 role 拥有不同 native ACP session。 +- 首次 prompt 会创建 session、发送隐藏 bootstrap,成功后保存 binding,再发送用户请求。 +- Gateway 或 ACP worker 重启后,使用持久 binding 尝试 `session/resume`,必要时回退 `session/load`。 +- `/new` 删除当前 chat/role binding;下一条普通消息再创建新 session。 +- `/cancel` 取消当前 turn,不应等待正在执行的 chat lock。 +- Prompt 失败不能擅自重放,因为工具操作可能已经产生副作用。 +- Session 上下文由 native ACP Agent 维护;gori-agent 维护角色选择、binding、生命周期和恢复信息,不应每轮把完整历史重新拼给 CLI。 + +## 6. 多机器、多 Bot 部署规范 + +每个 Bot 使用一份位于部署机器上的本地 `config.json`,不要在仓库中创建 `config.ops.json`、`config.developer.json` 等带真实凭据的文件。 + +每个实例至少要唯一: + +- QQ `appId`、`clientSecret`、`botNames` +- `server.port` +- `GORI_AGENT_HOME`,或显式且独立的 `acp.stateFile` +- role ID、workspace、persona 和 policy + +示例: + +```bash +GORI_AGENT_HOME=/home/USER/.gori-agent-ROLE_ID \ + ./gori-agent.sh start-daemon --config ./config.json +``` + +`GORI_AGENT_HOME` 隔离: + +- PID +- 日志 +- 默认 ACP session state +- 单实例锁 + +同一机器启动多个实例时,每个实例应从各自部署目录运行,或明确指定独立配置路径和 `GORI_AGENT_HOME`。 + +## 7. 配置施工流程 + +当用户让 Agent 配置一个新角色或新 Bot 时,按以下顺序执行: + +1. 读取 `AGENTS.md`、`config.example.json` 和相关 schema;不要先读取或展示真实 secrets。 +2. 明确 role 的职责、workspace、所需 skills、允许的工具和命令。 +3. 确认 backend 可执行路径以及 `kimi acp` 可用。 +4. 从唯一模板复制本地 `config.json`,不另建可提交的具体 Bot 模板。 +5. 填写本机 QQ credentials;不要在回复、日志、diff 或测试输出中回显。 +6. 默认先用 `deny`;需要施工能力时配置最小 `allowlist`;只有明确授权才使用 `auto`。 +7. 设置文件权限为 `0600`。 +8. 运行配置和 backend 检查: + + ```bash + ./gori-agent.sh discover-backends --config ./config.json + ./gori-agent.sh doctor --config ./config.json + ``` + +9. 前台启动,确认 QQ WebSocket ready,再从 IM 执行只读验收。 +10. 前台通过后,使用唯一 `GORI_AGENT_HOME` 后台启动。 +11. 任何包含 secret 的配置、备份和日志都不得加入 Git。 + +若缺少真实凭据,可以完成无密钥模板和规范,但不得虚构 credentials,也不得声称真实 QQ 登录已验证。 + +## 8. 常用命令 + +```bash +npm install +npm run typecheck +npm test +npm run build + +gori-agent setup --config ./config.json +gori-agent discover-backends --config ./config.json +gori-agent doctor --config ./config.json +gori-agent start --config ./config.json + +./gori-agent.sh start --config ./config.json +./gori-agent.sh start-daemon --config ./config.json +./gori-agent.sh status --config ./config.json +./gori-agent.sh logs +./gori-agent.sh stop +``` + +`gori-agent.sh` 不会检测源码是否比 `dist/` 新。修改 TypeScript 后必须显式运行 `npm run build`。 + +## 9. 修改与验收规则 + +- 做最小、局部、可审查的修改,不做无关重构。 +- 先修改 `src/`,不要直接修改 `dist/`。 +- 行为变化应补对应测试;不要通过削弱测试来适配实现。 +- 配置 schema 变化必须同步: + - `src/config.ts` + - `config.example.json` + - setup/doctor(如适用) + - README 和本文件 + - 配置测试 +- 平台错误必须保留足够的错误码和 trace ID 用于诊断,但不得输出 secret。 +- 修改后运行: + + ```bash + npm run typecheck + npm test + npm run build + git diff --check + ``` + +- 测试未通过时不能宣称完成。 +- 不执行真实 QQ 消息、pull、部署、push、创建 PR 等外部操作,除非用户明确要求并授权。 +- 不自动执行 Git commit/push;需要时按用户当次指令处理。 + +## 10. 敏感信息与禁止提交内容 + +不得提交或粘贴: + +- `config.json` +- `config.json.*.bak` +- 任意包含真实 Bot/App credentials 的自定义配置 +- `.env` +- `backends[].env` 中的 token/API key +- ACP state、PID、lock 和 session metadata +- 日志中的真实 chat ID、user ID、message ID、消息正文或 ACP stderr 敏感内容 + +唯一可提交配置模板:`config.example.json`。其中只能使用明显占位符。 + +当前 `.gitignore` 已覆盖 `config.json`、备份、日志、`dist/`、`node_modules/` 和 coverage。新增其他具体配置文件名时,不要依赖命名约定判断安全;只要包含真实凭据,就必须留在仓库外或明确忽略。 diff --git a/README.md b/README.md index 31b1169..9855239 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,24 @@ export GORI_AGENT_HOME=/home/ubuntu/.gori-agent chmod 600 config.json ``` +## 多机器与多 QQ Bot + +仓库只维护一个无密钥模板:`config.example.json`。在每台机器、每个 Bot 实例中复制后单独修改: + +```bash +cp config.example.json config.json +chmod 600 config.json +``` + +每个进程只能登录一个 QQ Bot。为每个实例配置唯一的 QQ `appId`、`clientSecret`、`botNames` 和 `server.port`,并使用不同的 `GORI_AGENT_HOME`,避免日志、PID 和 ACP session 状态相互覆盖: + +```bash +GORI_AGENT_HOME=/home/USER/.gori-agent-ROLE_ID \ + ./gori-agent.sh start-daemon --config ./config.json +``` + +建议每个 Bot 的 `roles[]` 只保留它自己的一个 role,同时修改 `ROLE_ID`、`workspace`、`persona` 和 `policy`。需要 skill 时,再向顶层 `skills[]` 添加对应 SKILL.md,并在 role 的 `skills` 中引用。各机器的 `config.json` 不要提交到仓库。 + ## 配置角色 角色定义在 `config.json` 的 `roles[]` 中: diff --git a/config.example.json b/config.example.json index 606b594..f2e70f8 100644 --- a/config.example.json +++ b/config.example.json @@ -2,7 +2,7 @@ "configVersion": 2, "server": { "host": "0.0.0.0", - "port": 3000, + "port": 8787, "publicBaseUrl": "" }, "policy": { @@ -22,80 +22,60 @@ "backends": [ { "id": "kimi", - "command": "/home/ubuntu/.kimi-code/bin/kimi", + "command": "/home/USER/.kimi-code/bin/kimi", "args": ["acp"], "env": {} } ], - "skills": [ - { - "id": "gori-update", - "file": "/home/ubuntu/gori-space/gori-deploy/.kimi-code/skills/gori-update/SKILL.md", - "maxBytes": 256000 - } - ], - "defaultRole": "assistant", + "skills": [], + "defaultRole": "ROLE_ID", "roles": [ { - "id": "assistant", + "id": "ROLE_ID", "backend": "kimi", - "workspace": "/home/ubuntu/gori-space/gori-agent", - "persona": "", + "workspace": "/absolute/path/to/workspace", + "persona": "Describe this agent's responsibilities and boundaries.", "skills": [], "policy": { "permissionMode": "deny", "allowedTools": [], "allowedCommandPatterns": [] } - }, - { - "id": "ops", - "backend": "kimi", - "workspace": "/home/ubuntu/gori-space", - "persona": "你是 Gori 团队运维角色。严格遵循 gori-update skill;有风险或需要外部确认时停止并报告。", - "skills": ["gori-update"], - "policy": { - "permissionMode": "allowlist", - "allowedTools": ["read", "grep", "glob", "bash"], - "allowedCommandPatterns": [ - "^(?:.*\\\"command\\\":\\\")?(?:git (?:status|log|diff|pull --ff-only)|bash gori-deploy/(?:build\\.sh|dist/deploy-[a-z-]+\\.sh)|docker (?:ps|logs))" - ] - } } ], "platforms": { "feishu": { "enabled": false, - "appId": "cli_xxx", - "appSecret": "replace-me", - "verificationToken": "replace-me", - "botNames": ["Gori Agent"] + "appId": "", + "appSecret": "", + "verificationToken": "", + "botNames": [] }, "wecom": { "enabled": false, - "corpId": "wwxxxx", - "agentId": "1000001", - "secret": "replace-me" + "corpId": "", + "agentId": "", + "secret": "" }, "qq": { - "enabled": false, + "enabled": true, "connectionMode": "websocket", - "appId": "1020xxxx", - "clientSecret": "replace-me", - "botSecret": "replace-me", + "appId": "QQ_APP_ID", + "clientSecret": "QQ_CLIENT_SECRET", + "botSecret": "", "verifySignature": true, - "botNames": ["Gori Agent"], + "botNames": ["QQ_BOT_NAME"], "intents": 33554432, "shard": [0, 1] }, "webhook": { - "enabled": true, - "secret": "replace-me" + "enabled": false, + "secret": "" }, "weixin": { "enabled": false, "mode": "external-webhook", - "secret": "replace-me" + "secret": "" } } }