feat: add assistant proposal worker runtime

This commit is contained in:
zenord
2026-08-18 18:48:47 +08:00
parent 947f19e3cd
commit b4121c9fd6
32 changed files with 2827 additions and 986 deletions
+46 -33
View File
@@ -7,7 +7,7 @@
`gori-agent` 是 Node.js 20+ / TypeScript 项目,通过单个 IM 平台接收请求,并通过官方 Agent Client Protocol(ACP)驱动一个 Coding Agent。
```text
用户 → Platform Adapter → Gateway → AcpSessionManager → AcpWorker → ACP Agent
用户 → Platform Adapter → Gateway → AssistantManager(Assistant / Proposal / Worker)→ ACP Agent
```
每个运行实例是一个完整且固定的 Bot:
@@ -32,6 +32,9 @@ ${GORI_AGENT_ROOT:-$HOME/.gori-agent}/instances/<bot-id>/
├── logs/gori-agent.log
└── state/
├── acp-sessions.json
├── proposals.json
├── assistant-workspaces/
├── kimi/assistant/
└── gori-agent.pid
```
@@ -42,6 +45,7 @@ ${GORI_AGENT_ROOT:-$HOME/.gori-agent}/instances/<bot-id>/
- 实例目录、`logs/`、`state/` 为 `0700`。
- `config.json`、PID、state 为 `0600`。
- 多实例必须使用不同 `gateway.server.port` 和平台 credentials。
- 旧的 `/usr/bin/flock` workspace lease 与 `owner.json` 已退役,不要重新引入。同一 `GORI_AGENT_ROOT` 下由 workspace scope 兜底:`doctor`、外层 `start` 与内部 runner 扫描其他实例目录的完整 Config v3,任一目录/配置不可读或无效即 fail closed;canonical workspace 相同或互为父子一律拒绝。该机制不覆盖终端、IDE、其他 root 或未接入 gori-agent 的进程。
- `init` / `setup` 必须交互询问并校验 server host/port;扫描其他 Config v3 的声明端口,冲突时警告,新实例建议下一个未声明端口,已有配置不得静默改端口。
- `setup <bot-id>` 只重配已有实例:校验目录/config/PID,运行中或 foreign PID 拒绝,固定 Bot ID,保留 agent/skills/permissions/policy/runtime/secrets/publicBaseUrl,确认写入后运行 doctor。
- setup 的声明冲突提示不替代实际 bind 检查;`start` 对缺配置、ID 不匹配、错误权限、占位符、运行 PID、端口冲突 fail closed。
@@ -69,31 +73,35 @@ gori-agent list
- Config v3 Zod schema、类型、交叉校验和 state 默认路径。
- 只接受 `configVersion: 3`。
- `src/server.ts`
- 组装 fixed Bot、state store、ACP manager、Gateway 和唯一 platform adapter。
- 只挂载所选平台 route。
- 组装 fixed Bot、state store、ProposalStore、AssistantManager、Gateway 和唯一 platform adapter。
- 只挂载所选平台 route;创建 AssistantManager 时传入 `runtime.acp.maxAssistantSessions`。
- `src/roles/role-registry.ts`
- 历史路径名保留,但实现是 `BotProfileResolver`,不是 role registry。
- 加载当前 Bot skills、计算 fingerprint、生成版本化 bootstrap。
- 加载当前 Bot skills、计算 fingerprint、生成版本化 assistant/worker bootstrap。
- `src/acp/client.ts`
- ACP initialize/new/resume/load/prompt/cancel 和 permission request。
- 当前 session 的每个 `session/update` 只触发活动回调,不记录或泄露 update 内容。
- assistant session 一旦出现 tool update 或 permission request 必须 fail closed。
- `src/acp/worker.ts`
- 直接使用 resolved Bot 的 agent,在 `bot.workspace` 启动。
- 记录 turn 开始/最近活动时间,并区分 `idle`、bootstrap `initializing`、普通 prompt `processing`。
- `src/acp/session-manager.ts`
- 固定 Bot 的 binding、worker pool、恢复、idle sweep、capacity、cancel/reset。
- status 暴露 `phase`、`runningSeconds`、`idleSeconds`。
- worker 在 `bot.workspace` 启动;assistant 在实例私有 `state/assistant-workspaces/<hmac>` 启动,未显式配置时 `KIMI_CODE_HOME` 指向实例私有 `state/kimi/assistant/`。
- 每个 ACP worker 使用独立进程组和随机 `GORI_AGENT_WORKER_TOKEN`;cancel、timeout、crash 或 assistant 隔离违约必须清理同进程组工具后代;bootstrap 禁止 `setsid`、`nohup`、detached/daemon/background 遗留进程,主动脱组属于无 cgroup/Bubblewrap 方案的边界。
- assistant 只接受 Kimi Code ACP,并依赖项目级 `tools: []`、`subagents: []` profile;permission deny 只是附加层。
- `src/acp/assistant-manager.ts`
- 每 chat 无工具 Assistant 会话(`GORI_ASSISTANT_ACTION_V1` envelope:create_proposal/confirm/start_next/cancel/stop,格式只修复一次)。
- 唯一活跃 Worker 执行已确认 Proposal(`GORI_WORKER_RESULT_V1`:SUCCESS/FAILED/NEEDS_CONFIRMATION);success 与 failure 都需用户确认才收尾,不自动开始下一个;执行中 dirty 是正常的 NEEDS_CONFIRMATION 确认点。
- capacity(maxAssistantSessions/maxProcesses)、idle sweep、cancel/confirm/stop、worker_lost 恢复、owner 事件通知。
- `src/core/durable-session-store.ts`
- state v2,保存 bot/platform identity 和 chat binding。
- 单 writer lock、串行持久化、临时文件、fsync、原子 rename。
- state v3 只保存 bot/platform identity 和 assistant binding(chat key、agent ID、native session ID、assistant workspace、fingerprint、时间戳);不保存消息正文。
- 单 writer lock、串行持久化、临时文件、fsync、原子 rename;version/identity 不匹配(含旧 v1/v2)拒绝并保留原文件。
- `src/core/proposal-store.ts`
- proposals.json(version 1)保存 Proposal:title/goal/steps、owner chat、状态流(proposed/queued/working/awaiting_user_confirmation/completed/failed/cancelled)、pending(step/success/failure)、worker session 与进程组 PGID/token;同样的 lock 与原子写入纪律。
- `src/core/workspace-scope.ts`
- canonical workspace + 跨实例 Config v3 扫描;相同或父子 workspace 在 doctor/start/runner fail closed。lease 已退役。
- `src/core/gateway.ts`
- 入站 allowlist、群 mention、命令、per-chat lock、队列状态和回复。
- policy 后 `/status`、`/cancel`、`/help` 必须绕过 chat lock,`/new` 必须保持串行。
- 异步平台普通消息轮到后立即 prompt,不等待提示发送;15 秒内完成只发结果,否则按 queued/running 提示,queued 转 running 时补开始提示;主动提示从入站绝对时间按 15 秒、60 秒、180 秒、480 秒触发,之后每 600 秒一次,只使用用户可感知文案并隐藏 ACP、`phase`、`idleSeconds` 等内部指标,技术状态只保留在 `/status`;同步 webhook 不提示。
- 每条异步入站使用独立串行 ReplyStream,`replySequence` 从 1 动态递增;发送失败只写安全日志且不阻止任务或后续发送,runtime/delivery 错误分离,最终消息入流后释放 chat lock。
- 每条入站只维护一个基于绝对 deadline 的可取消 timer;完成先标记 finished 并清 timer,shutdown 清理全部 timer,生产 timer 必须 `unref`,Gateway 可注入 fake clock/schedule 供测试。
- 入站 allowlist、群 mention、命令和回复;不维护普通消息队列或 per-chat task lock,也没有固定时间处理中提醒。
- 每条异步入站使用独立串行 ReplyStream,`replySequence` 从 1 动态递增;发送失败只写安全日志且不阻止任务或后续发送。
- Worker 落定事件经 `sendEvent` 作为新消息发送(QQ 同样是新消息)。
- `src/core/command-router.ts`
- `/help`、`/status`、`/cancel`、`/new`;旧 role 命令返回 retired 提示。
- `/help`、`/status`、`/list`、`/confirm`、`/stop`、`/cancel`;未识别命令按普通消息处理。
- `src/platforms/*`
- Adapter 依赖独立平台 config type,不依赖完整 AppConfig 路径。
- `src/cli/config-file.ts`
@@ -180,32 +188,37 @@ QQ 群 chat ID 为 `group:<group_openid>`。正式运维 Bot 应配置入口 all
### 4.5 Runtime ACP
- `stateFile` 为空时为 `$GORI_AGENT_HOME/state/acp-sessions.json`。
- `stateFile` 为空时为 `$GORI_AGENT_HOME/state/acp-sessions.json`;proposals 固定在同目录 `proposals.json`。
- `initializeTimeoutMs`: 默认 10000。
- `promptTimeoutMs`: 默认 7200000(2 小时)。
- `promptTimeoutMs`: 默认 14400000(4 小时)。
- `cancelGraceMs`: 默认 5000。
- `idleTimeoutMs`: 默认 1800000。
- `sweepIntervalMs`: 默认 60000。
- `maxProcesses`: 默认 8。
- `maxProcesses`: 默认 8,包含 assistant + worker。
- `maxAssistantSessions`: 默认 4。
## 5. Session 与 state v2
Kimi Code agent `args` 必须严格为 `["acp"]`,不能添加可能绕过 assistant no-tool profile 的启动参数。
Binding key 是当前实例固定的 `platform + chatId`。不再包含 role。
## 5. Assistant / Proposal / Worker 与 state v3
state v2 header 保存 `botId` 和 platform。打开时不匹配必须拒绝,不能清空、迁移或覆盖。旧 state v1 也明确拒绝并保留原文件。
运行链路是三层:每 chat 一个无工具 Assistant 会话负责对话与创建 Proposal;唯一活跃 Worker 在 `bot.workspace` 执行已确认 Proposal;Proposal 是两者之间的持久工作单。
Binding 保存 agent ID、workspace、native session ID、Bot fingerprint 和时间戳。fingerprint 包含:
Proposal 状态流:`proposed → queued → working → awaiting_user_confirmation → completed | failed | cancelled`。`proposed` 必须用户确认才进入 `queued`;没有活跃 worker、没有 working/awaiting_user_confirmation 时最早确认的 queued Proposal 才开始。
- bootstrap schema version
- Bot ID
- workspace/persona
- agent 定义
- permission policy
- skill 路径和内容 hash
Assistant 每条回复以隐藏 `GORI_ASSISTANT_ACTION_V1` envelope 结尾(`reply` + `actions`),action 仅 `create_proposal`、`confirm`、`start_next`、`cancel`、`stop`,格式错误只修复一次。Worker 每轮以隐藏 `GORI_WORKER_RESULT_V1` envelope 收尾:`SUCCESS`、`FAILED`、`NEEDS_CONFIRMATION`(可选 `question`、`nextStep`、`dirty`),同样只修复一次。
首次消息创建 native session,发送隐藏 bootstrap,成功后保存 binding。重启/idle 后优先 resume,再 fallback load。prompt 失败不重放。
确认语义:
`/new` 在 per-chat lock 内删除当前 chat binding;下一条消息创建新 session。`/status`、`/cancel`、`/help` 不等待 chat lock;status 合并 Gateway running/queued/started 时长与 ACP phase/running/idle 时长。
- `SUCCESS` 与 `FAILED` 都进入 `awaiting_user_confirmation`,都需用户确认才落定为 `completed`/`failed`;failed 用 answer `retry` 确认可重试。
- 系统不自动开始下一个 Proposal;只有新确认或 `start_next` 推进队列。
- 执行中遇到 dirty/意外目标是正常的 `NEEDS_CONFIRMATION` 确认点,不是 blocked 终态;用户回答后同一 worker 继续。
- Gateway 无固定时间提醒;worker 落定后由 Assistant 生成事件文案,作为新消息发给 owner chat。
Assistant 隔离:cwd 在实例私有 `state/assistant-workspaces/`,目录 key 使用进程随机 salt 的 HMAC,不得与项目 workspace 重叠;Kimi 项目级 agent override 必须设置 `tools: []`、`subagents: []`,ACP `mcpServers: []`,未显式配置时 `KIMI_CODE_HOME` 指向实例私有 `state/kimi/assistant/`;任何 tool update 或 permission request 都视为隔离违约,fail closed、终止进程组并删除 binding。每个 ACP worker 独立进程组 + 随机 token;runner 重启时 working Proposal 校验 token 清理旧进程组后标记 failed(worker_lost)。
state v3(`acp-sessions.json`)header 保存 `botId` 和 platform,只存 assistant binding(agent ID、assistant workspace、native session ID、Bot fingerprint、时间戳);`proposals.json`(version 1)存 Proposal 记录。两者打开时 version/identity 不匹配必须拒绝,不能清空、迁移或覆盖;旧 state v1/v2 也明确拒绝并保留原文件。fingerprint 包含 bootstrap schema version、Bot ID、workspace/persona、agent 定义、permission policy、skill 路径和内容 hash。prompt 失败不重放。
命令 `/help`、`/status`、`/list`、`/confirm`、`/stop`、`/cancel` 旁路 Assistant;`/stop` 仅 owner chat 可停止活跃 worker。
## 6. 单平台装配