feat: pending-finish proposals and QQ image input
Proposal semantics: worker results no longer distinguish success/fail; only an explicit finish settles a pending proposal. Pending owner input follows up by resuming the original worker session. Dirty pending blocks start_next globally; clean pending only blocks its owner. QQ adapter now downloads image attachments and passes them as ACP image content blocks; video/file attachments degrade to link text.
This commit is contained in:
@@ -87,14 +87,14 @@ gori-agent list
|
||||
- 每个 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`
|
||||
- 每 conversation(chat + user)一个无工具 Assistant 会话,同群不同用户互相隔离(`GORI_ASSISTANT_ACTION_V1` envelope:create_proposal/confirm/start_next/cancel/stop,格式只修复一次);runtime 执行 action 后在 reply 末尾追加人话纠正(如 start_next 被阻塞、confirm/stop/cancel 无 owner 匹配),Assistant 不得自行宣称 action 已生效。
|
||||
- 唯一活跃 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 事件通知。
|
||||
- 每 conversation(chat + user)一个无工具 Assistant 会话,同群不同用户互相隔离(`GORI_ASSISTANT_ACTION_V2` envelope:create_proposal/confirm/adjust_proposal/follow_up/finish/start_next/cancel/stop,格式只修复一次);runtime 执行 action 后在 reply 末尾追加人话纠正(如 start_next 被阻塞、无 owner 匹配),Assistant 不得自行宣称 action 已生效。
|
||||
- 唯一活跃 Worker 执行已确认 Proposal(`GORI_WORKER_RESULT_V2`:仅 `PENDING`,summary 必填,可带 question/workspaceDirty);pending 不区分 success/fail,只有 finish 落定为 finished(done),cancel 落定为 finished(cancelled),不自动开始下一个。
|
||||
- capacity(maxAssistantSessions/maxProcesses)、idle sweep、cancel/confirm/finish/stop、worker_lost 恢复、owner 事件通知。
|
||||
- `src/core/durable-session-store.ts`
|
||||
- state v3 只保存 bot/platform identity 和 assistant binding(conversation key,即 chat + user、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、发起用户 requesterUserId、状态流(proposed/queued/working/awaiting_user_confirmation/completed/failed/cancelled)、pending(step/success/failure)、worker session 与进程组 PGID/token;同样的 lock 与原子写入纪律。
|
||||
- proposals.json(version 2)保存 Proposal:title/goal/steps、owner chat、发起用户 requesterUserId、状态流(proposed/queued/working/pending/finished)、pending(summary/question?/workspaceDirty?/receivedAt)、finished(finishKind done|cancelled、finishNote?)、worker session 与进程组 PGID/token;同样的 lock 与原子写入纪律。打开 v1 文件时先写 `proposals.json.v1-<timestamp>.bak`(0600)备份再按固定映射迁移。
|
||||
- `src/core/workspace-scope.ts`
|
||||
- canonical workspace + 跨实例 Config v3 扫描;相同或父子 workspace 在 doctor/start/runner fail closed。lease 已退役。
|
||||
- `src/core/gateway.ts`
|
||||
@@ -102,7 +102,7 @@ gori-agent list
|
||||
- 每条异步入站使用独立串行 ReplyStream,`replySequence` 从 1 动态递增;发送失败只写安全日志且不阻止任务或后续发送。
|
||||
- Worker 落定事件经 `sendEvent` 发送:优先引用该 chat 最近入站消息走被动回复窗口(群 4.5 分钟、C2C 55 分钟保守判定,`msg_id` + 递增 `msg_seq`);`msg_seq` 按 chat+messageId 共享计数(正常回复、事件、补发同一计数器,QQ 重复推送同一 msg_id 时沿用已用序号);无新鲜窗口或发送失败时不发主动消息,记录 lastEventDelivery/lastEventError/lastEventAt,补发失败保留队列并在下次入站时重试。
|
||||
- `src/core/command-router.ts`
|
||||
- `/help`、`/status`、`/list`、`/confirm`、`/stop`、`/cancel`;未识别命令按普通消息处理。
|
||||
- `/help`、`/status`、`/list`、`/confirm`、`/finish`、`/stop`、`/cancel`;未识别命令按普通消息处理。
|
||||
- `src/platforms/*`
|
||||
- Adapter 依赖独立平台 config type,不依赖完整 AppConfig 路径。
|
||||
- `src/cli/config-file.ts`
|
||||
@@ -203,24 +203,26 @@ Kimi Code agent `args` 必须严格为 `["acp"]`,不能添加可能绕过 assi
|
||||
|
||||
## 5. Assistant / Proposal / Worker 与 state v3
|
||||
|
||||
运行链路是三层:每 conversation(chat + user)一个无工具 Assistant 会话负责对话与创建 Proposal;唯一活跃 Worker 在 `bot.workspace` 执行已确认 Proposal;Proposal 是两者之间的持久工作单,owner 是 chat + 发起用户,只有发起人本人可 confirm/stop/cancel/list,`start_next` 也只启动发起人自己的 queued Proposal。
|
||||
运行链路是三层:每 conversation(chat + user)一个无工具 Assistant 会话负责对话与创建 Proposal;唯一活跃 Worker 在 `bot.workspace` 执行已确认 Proposal;Proposal 是两者之间的持久工作单,owner 是 chat + 发起用户,只有发起人本人可 confirm/adjust/follow_up/finish/stop/cancel/list,`start_next` 也只启动发起人自己的 queued Proposal。
|
||||
|
||||
Proposal 状态流:`proposed → queued → working → awaiting_user_confirmation → completed | failed | cancelled`。`proposed` 必须用户确认才进入 `queued`;没有活跃 worker、没有 working/awaiting_user_confirmation 时最早确认的 queued Proposal 才开始。
|
||||
Proposal 状态流:`proposed → queued → working → pending → finished`。`proposed` 必须用户确认才进入 `queued`;`pending` 就是「等用户决定」,不区分 success/failure;只有 `finish` 把 pending 落定为 `finished(done)`,`cancel`(proposed/queued/pending)落定为 `finished(cancelled)`;没有 working、owner 自己没有 pending、全局没有 workspaceDirty pending 时,最早确认的 queued Proposal 才可通过 confirm 或 start_next 开始。
|
||||
|
||||
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`),同样只修复一次。
|
||||
Assistant 每条回复以隐藏 `GORI_ASSISTANT_ACTION_V2` envelope 结尾(`reply` + `actions`),action 仅 `create_proposal`、`confirm`、`adjust_proposal`(仅 proposed/queued)、`follow_up`(pending → working,优先 resume 原 worker native session,失败则带 Proposal 上下文新起 session,用户图片附件随 prompt 给 Worker)、`finish`、`start_next`、`cancel`、`stop`,格式错误只修复一次。Worker 每轮以隐藏 `GORI_WORKER_RESULT_V2` envelope 收尾:仅 `PENDING`(`summary` 必填,可选 `question`、`workspaceDirty`),同样只修复一次。
|
||||
|
||||
确认语义:
|
||||
|
||||
- `SUCCESS` 与 `FAILED` 都进入 `awaiting_user_confirmation`,都需用户确认才落定为 `completed`/`failed`;failed 用 answer `retry` 确认可重试。
|
||||
- Worker 落定即进入 `pending`(记录 summary/question?/workspaceDirty?/receivedAt);`finish`(可带 note)落定为 `finished(done)`,`cancel` 落定为 `finished(cancelled)`;finished 保留最后 summary 供面板展示。
|
||||
- 系统不自动开始下一个 Proposal;只有新确认或 `start_next` 推进队列。
|
||||
- 执行中遇到 dirty/意外目标是正常的 `NEEDS_CONFIRMATION` 确认点,不是 blocked 终态;用户回答后同一 worker 继续。
|
||||
- 阻塞规则:任何 `working` 全局拒绝 start_next/follow_up;owner 自己有 pending 时拒绝该 owner 的 start_next(提示先 finish 或 follow_up);任何 `workspaceDirty: true` 的 pending 全局拒绝 start_next(提示先处理 dirty 工作区);非 dirty 的 pending 不挡其他用户。
|
||||
- `stop` 只对 working 生效:停 worker(现有进程组清理)后 Proposal 转为 pending(summary「被用户中止」、workspaceDirty)。
|
||||
- Assistant 有 pending 时每轮 prompt 注入 pending card;回复没提到 pending 时 runtime 在 reply 末尾追加人话兜底提醒。
|
||||
- Gateway 无固定时间提醒;worker 落定后由 Assistant 生成事件文案发给 owner chat,QQ 无主动消息权限时优先使用被动回复窗口(群 5 分钟/私聊 60 分钟,按 4.5/55 分钟保守判定),过期或失败则不主动发、记录 lastEventDelivery 并在下次入站时补发;`/status` 输出当前 chat 的事件投递状态与 schedulerState/blockedReason/nextAction。
|
||||
|
||||
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)。
|
||||
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 清理旧进程组后标记为 pending(worker_lost、workspaceDirty: true)。
|
||||
|
||||
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/assistantPersona、agent 定义、permission policy、skill 路径和内容 hash。prompt 失败不重放。
|
||||
state v3(`acp-sessions.json`)header 保存 `botId` 和 platform,只存 assistant binding(agent ID、assistant workspace、native session ID、Bot fingerprint、时间戳);`proposals.json`(version 2)存 Proposal 记录。state v3 打开时 version/identity 不匹配必须拒绝,不能清空、迁移或覆盖;旧 state v1/v2 也明确拒绝并保留原文件。proposals.json 唯一例外:v1 文件打开时先在同目录写 `proposals.json.v1-<timestamp>.bak`(0600)备份,再按固定映射迁移为 v2(SUCCESS → pending{summary};FAILED → pending{summary, workspaceDirty: true};NEEDS_CONFIRMATION → pending{summary, question};completed/failed → finished(done)(failed 保留失败说明为 finishNote);cancelled → finished(cancelled);proposed/queued/working 保留)。fingerprint 包含 bootstrap schema version、Bot ID、workspace/persona/assistantPersona、agent 定义、permission policy、skill 路径和内容 hash。prompt 失败不重放。
|
||||
|
||||
命令 `/help`、`/status`、`/list`、`/confirm`、`/stop`、`/cancel` 旁路 Assistant;`/stop` 仅 Proposal 发起人(chat + user)可用,且对等待确认的 Proposal 按 pending 类型兜底落定(success→completed、failure→failed、step→cancelled),不自动开始下一个。
|
||||
命令 `/help`、`/status`、`/list`、`/confirm`、`/finish`、`/stop`、`/cancel` 旁路 Assistant;全部 owner-only(chat + user):`/confirm` 对 proposed,`/finish` 对 pending,`/stop` 仅对 working(→pending),`/cancel` 对 proposed/queued/pending;`/list` 面板按 pending → working → queued → proposed → 最近 finished 排列。QQ 入站图片附件(image/*,单张 ≤5MB、每条最多 3 张、10 秒下载超时)下载为 base64 经 `IncomingMessage.attachments` 透传;agent 声明 `promptCapabilities.image` 时作为 ACP image content block 发给 Assistant/Worker,否则降级为文本说明;视频/文件附件不下载,仅以 `[视频] <url>` / `[文件] <url>` 文本拼接。
|
||||
|
||||
## 6. 单平台装配
|
||||
|
||||
|
||||
Reference in New Issue
Block a user