Skip to content

feat(delegation): 汇报结果直发飞书群通知#2994

Open
tier2tech-tian wants to merge 375 commits into
nanocoai:mainfrom
tier2tech-tian:feat/delegation-notify
Open

feat(delegation): 汇报结果直发飞书群通知#2994
tier2tech-tian wants to merge 375 commits into
nanocoai:mainfrom
tier2tech-tian:feat/delegation-notify

Conversation

@tier2tech-tian

Copy link
Copy Markdown

delegation 子群完成后直发飞书通知,解决 agent 闷头干活不汇报用户的问题

tianjunjie and others added 30 commits April 28, 2026 17:02
sendPlainOrCard/sendMessage 返回飞书 API 的 message_id,bot 回复入库时
优先用飞书 message_id 作为 DB 主键。fetchReplyContext 先查 DB 再 fallback
飞书 API,解决引用卡片消息时只能看到 [互动卡片] 的问题。

改动:
- sendPlainOrCard: void → string | undefined(返回飞书 message_id)
- sendMessage/extractAndSendMedia: 透传 message_id
- Channel 接口 + task-scheduler/ipc 类型同步
- fetchReplyContext: DB 优先查询 + 静态 import getMessageById
- 16 个新单元测试(DB 命中/miss/异常/截断/卡片/token 失败)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
feat: Pushover 语音通知 + IPC 跨群消息 + 日志系统升级
kickoff 流程的每个阶段转换点(定位问题/写OpenSpec/评审修改/代码修复)
都必须输出结构化日志,方便未来定位问题。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- listFolder/createFolder 响应解析: resp?.files → resp?.data?.files
  (原来 --folder 参数从未真正工作过,总是上传到根目录)
- findOrCreateFolder 支持 / 分隔多级路径逐级创建
- KNOWN_FOLDER_TOKENS 消歧根目录同名 nanoclaw 文件夹
- uploadFile 返回云盘文件链接 url
- page_size 50 → 200 覆盖更多文件

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
根因:outputSentToUser 跨 query 不重置。第一轮 query 设了 true 后,
后续轮次的 cleanupProgressCard 兜底路径被 !outputSentToUser 条件跳过。
当后续 query 的回复全是 <internal> 标签时,channel.sendMessage 也不会
被调用,导致卡片永远开着。

修复:
- cleanupProgressCard 改为无条件执行(内部有空检查)
- result.status=success 处理完后重置 outputSentToUser
- 新增 everSentToUser 变量(不重置),供 error handler 判断是否推进 cursor
  (Codex review P2:重置 outputSentToUser 会破坏 error 路径的 cursor 推进逻辑)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
根因:cleanupProgressCard 先 delete pendingUsage 再 buildCompletedCard,
导致完成卡片始终没有 usage footer。commit 6616152 引入 sendDirectMessage
后,send_message MCP 路径不再经过 sendMessage(含 usage),进度卡片成为
唯一展示 usage 的地方,但它也没拿到 usage。

修复:cleanupProgressCard 先 get 再 delete,把 usage/thinking 传给
buildCompletedCard。

测试:新增 2 个用例验证有/无 pendingUsage 场景,全 46 用例通过。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
根因:commit 6616152 引入 sendDirectMessage 时为避免过早清理进度卡片,
改用裸发路径跳过了 channel.sendMessage 的 [reply] 逻辑。但同时也跳过了
pendingUsage 的读取,导致 send_message MCP 发的消息不再带 usage footer。

修复:sendDirectMessage 在发送前读取 pendingUsage/thinkingMode,传给
extractAndSendMedia,发送后清理(避免下条消息重复附加)。

测试:新增 3 个单元测试覆盖 sendDirectMessage usage 场景。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
定时任务的 onOutput 回调之前没有过滤 progress 状态,导致每次 tool_use/tool_result
都作为单独消息发到群里。增加 status === 'progress' 过滤 + <internal> 标签剥离。
同时升级 container-runner 的 agent output 日志到 info 级别以便诊断。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
账号轮换:
- 移除防抖和冷却期,触发即切换
- isRetry boolean → retryCount 数字,试完所有账号才停
- streaming 限流检测后立即 closeStdin,防止 IPC pipe 8h 挂起
- 重试 onOutput 回调加 detectRateLimit 守卫
- maxRetries=0 时不误报 allExhausted
- 重试成功后清理进度卡片和 typing 状态

/usage 命令:
- 新增 /usage delete <name> 子命令
- getUsageForSecret 加 anthropic- 前缀 fallback(OneCLI vs OAuth 名称不匹配)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
包含:
- P2-2 fake-success 防护 + P2-3 retry 进度卡片清理
- streaming rate-limit closeStdin 修复
- /usage OAuth 名称 fallback + delete 子命令
- CLAUDE.md / skill 配置更新
- feishu 频道 sendDirectMessage usage footer 恢复

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
新增 code-graph、e2e-testing、eval-conversation、html-report、implement、
nclog、nine-observability、receiving/requesting-code-review、ship、
taskboard、systematic-debugging 补充文档、test-driven-development、
verification-before-completion、writing-plans 等 skill。
同步提交 openspec changes、sync-superpowers 脚本、AGENTS.md 等。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
agent-runner 新增 runCliQuery() 路径,指定群 useCliMode: true 后
每轮 spawn claude --print --resume 替代 Agent SDK query()。
核心:清除 CLAUDE_AGENT_SDK_CLIENT_APP 环境变量,不带 x-client-app header。

改动:
- types.ts: ContainerConfig 加 useCliMode 字段
- cli-runner.ts: 纯函数(parseStreamJsonLine/buildCliArgs/buildMcpConfig/
  mapToContainerOutput/buildCliEnv)+ runCliQuery 主函数
- index.ts (agent-runner): main() 中 useCliMode 分叉
- container-runner.ts/index.ts/task-scheduler.ts: 透传 useCliMode
- cli-runner.test.ts: 38 个单元测试全部通过

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Review 发现 5 个 Important 问题,修复 4 个:
- #1: ContainerOutput 补齐 modelContextWindows 字段
- #3: system error message 加 typeof guard
- #4: mapToContainerOutput 返回数组,不丢中间输出
- #5: CLI 循环中去除重复 success output

新增 2 个测试用例(40 total):
- system error 非字符串 message 回退
- assistant 混合 text+tool_use 全部返回

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- cli-runner: assistant text block 不再映射为 progress,避免与 result 重复
- container-runner: rotateAccount 返回 oldSecretName,支持轮换通知
- index.ts: 新增轮换来源展示 + rate limit 重试消息抑制
- commands/account: 手动 switch 同步 rotateIndex/lastRotateAt 防覆盖
- agent-runner/index: 新增 archiveCliTranscript + sanitizeFilename 支持中文
- 测试用例同步更新

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
新增 interactive 模式,通过 tmux 管理 Claude CLI 交互会话,
Tap Proxy(MITM)拦截 SSE 响应流获取输出,走订阅配额替代 API 配额。

新增模块:
- sse-parser.ts: SSE 事件纯函数解析器
- tap-proxy.ts: MITM 代理 + SSE 响应拦截
- tmux-session-manager.ts: tmux 会话生命周期管理
- interactive-cli-runner.ts: 整合层,对齐 ContainerOutput 接口

Review 修复(两轮 review 共 10 项):
- P0: shell 命令注入 → shellQuote 单引号包裹
- P0: processSseData 状态丢失 → 内联调用回写闭包
- P0: CONNECT 响应未校验 → 状态码检查
- M1: stopReason 未重置 → 防死循环
- P1: 单例并发竞态 → Promise 锁
- H1/H2: 临时文件 + socket 泄漏修复
- P2: resolveCliMode 运行时校验

702 tests passing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ve-mode

feat(cli-mode): 交互式 CLI 模式 — tmux + Tap Proxy SSE 拦截
根因:限流检测在 onOutput 回调里(设 flag),但轮换代码在 runAgent() 返回后。
agent 是长驻进程不退出,runAgent 不返回,轮换永远执行不到。

修复:
- onOutput 检测到限流后立即 queue.killGroup() 杀子进程
- 子进程退出 → runContainerAgent resolve → 现有轮换逻辑自然触发
- retry 回调同样加 killGroup 防止重试死锁
- 加 early return 防止 kill 后残余 chunk 发给用户
- 新增 5 个集成测试覆盖限流→kill→轮换→重试全链路

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
合入 feat/pushover-voice 分支的 9 个 commit,包含:
- fix(account-rotate): 全面修复账号轮换 + /usage 命令
- fix(scheduler): 定时任务过滤 progress 消息
- fix(feishu): usage footer、进度卡片 IPC 模式修复
- fix(feishu-docs): 云盘上传支持嵌套目录
- feat(kickoff): 结构化日志要求

冲突解决策略:rate-limit 相关保留 main 的 killGroup 方案,
pushover-voice 的 closeStdin 方案已被替代。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- sse-parser: buildToolUseProgress() 解析完整 tool inputJson,展示命令/文件详情
- index.ts: appendInteractiveTranscript() 每轮写入磁盘,不依赖进程退出
- interactive-cli-runner: credential proxy 支持(ANTHROPIC_API_KEY 占位 + TapProxy 替换)
- interactive-cli-runner: DISABLE_TELEMETRY=1 阻断遥测发送(防 terminal:tmux 指纹泄露)
- tap-proxy: credential proxy HTTP 直转模式(不走 CONNECT 隧道)
- tmux-session-manager: waitForReady 等待 CLI 就绪、session 生命周期优化

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
SDK 有时将内部状态消息(session 重置、API 500 错误)包装成
status:"success" + result 文本返回,NanoClaw 误当正常回复发给用户。

新增过滤器拦截以下模式:
- "New session: UUID" — session 强制重置通知
- "fetch failed" — 网络层失败
- "API Error: 5xx" — 上游 API 错误

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
之前 API 500 / fetch failed 只拦截不发给用户,但 agent 也挂了没后续回复。
现在检测到 API 瞬时错误后自动重试(3s/6s 延迟),不需要轮换账号。

匹配模式:API Error 5xx、fetch failed、Internal server error、
overloaded、ECONNRESET、ETIMEDOUT

同时放宽集成测试 kill 后退出时间阈值(5s→15s),避免高负载下 flaky。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
9fad103 + 1ef031b 留下一个漏洞:SDK 把 fetch failed / API Error: 5xx
包成 status:success + result 文本通过 onOutput 流式输出,不进 output.error
字段,所以 1ef031b 的 isApiTransientError 重试链路根本接不到。结果是
用户看不到错误(9fad103 拦截了),但 agent 也不会重试,sliently 卡死。

修复:
- 主 onOutput 拦截到 fetch failed/5xx 时设 streamingApiErrorDetected
  + kill 子进程触发 runContainerAgent resolve
- runAgent 返回后新增 API error 重试 loop(3s/6s 延迟,复用 mainOnOutput)
- 限流 retry 内嵌 onOutput 也加同样拦截,覆盖 retry 期间的 API 错误
- 重试 2 次仍失败 → 发明确错误通知给用户(不再 silent)

日志补全:
- API error 拦截、重试启动、重试返回、重试抛错、重试用尽、重试成功 全链路
- 限流 retry 拦截 New session/fetch failed 补 warn 日志
- runAgent catch err 增加 message/name/stack/chatJid/retryCount/sessionId
- 1ef031b output.error 路径加重试用尽 error 日志

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
全局 mem0 MCP 导致 NanoClaw agent 读到 OpenClaw 的记忆数据,
身份/风格/回复格式全部被污染。

修复:
- settingSources 改为 ['project'](不读全局 user 配置)
- 已删除 ~/.claude.json 里的 mem0 MCP
- 已清空所有被污染的 session
- tap-proxy: sessionToken 解析从 split(':')[1] 改为 indexOf+slice,
  修复含冒号的 chatJid(如 fs:oc_xxx)被截断导致 SSE 拦截全部失效
- interactive-cli-runner: 固定 10 分钟超时改为活动超时,
  每次收到 SSE 事件重置计时器,避免多轮 tool_use 误超时
- container-runner: Agent output 日志补上 error 字段,便于排查

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- agent-runner/index.ts: SDK 模式抑制 thinking progress 输出,避免无意义 IPC
- src/index.ts: 主回调和重试回调均过滤 thinking progress + 拦截模型拒绝文本
- output-filters.ts: shouldFilterProgress / isModelRefusal 工具函数及测试

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
修复 ea21e58 引入的回归:SDK 模式下用户看不到 agent 在工具调用之间
说的话("让我看下这块代码" 等中间叙述)。

根因:ea21e58 把 flushPendingThought 在 SDK 模式整段抑制 + 用
progressType='thinking' 让主进程 shouldFilterProgress 再过滤一次。
但 pendingThought 缓存的源头是 assistant text block(不是 thinking
block),导致用户可见回复内容被双重抑制。

修复:
- agent-runner/src/index.ts: 删 isSdkMode 抑制分支,统一发
  progressType='text' + 💬 emoji
- container/agent-runner/src/sse-parser.ts:
  · 新增 buildTextProgress
  · 新增 decideTextBlockAction 纯函数(interactive 模式 stop_reason
    决断抽离,便于单测)
- container/agent-runner/src/interactive-cli-runner.ts: text block 暂存
  到 pendingTextBlocks,调用 decideTextBlockAction 决定 flush/drop。
  onError/onEnd/timeout 三路径均 flush(防 SSE 中断丢消息)
- src/channels/feishu.ts: handleProgress 把 💬 走和 💭 一样的
  "独立消息、不进进度卡片"路径;PROGRESS_JSON_PATTERN 加 💬

事件驱动去重(替代旧的 500ms 时间窗口):
- SDK 模式:tool_result 到达即 flush pending text(中间叙述确认);
  result 到达即 drop pending(已含在最终回复,避免重复 — 与
  interactive end_turn 语义对齐);30s timer 仅作 fallback;
  runQuery finally 中清 timer 防跨会话泄漏
- interactive 模式:decideTextBlockAction(stopReason, isHaikuPreheat)
  → tool_use=flush, end_turn/max_tokens/stop_sequence=drop,
  haiku 预热=drop

测试覆盖(772/772 通过,新增 10 个用例):
- buildTextProgress 8 个用例(含 progressType MUST 为 text 回归锁)
- decideTextBlockAction 7 个用例(覆盖 tool_use/end_turn/max_tokens/
  stop_sequence/空 stop_reason/haiku 优先级)
- interactive SSE 事件流端到端集成 3 个用例(完整事件序列喂给
  accumulateSseEvent,断言 acc 状态 + 决策正确)
- shouldFilterProgress('text') 不过滤(回归锁)
- feishu 💬 3 个路径(独立消息/detail 完整/progressDone 拦截)

日志全量补齐:
- SDK 模式:[text-block] received/cached/tool_result arrived/flush/
  drop on result/runQuery exiting clear pending
- interactive 模式:[interactive] buffering/flushing N (reason)/
  emit 💬/dropping N (stop=...,haiku=...)/SSE error/stream ended/
  timeout 均带 pendingTextBlocks 计数
- main:[progress] 转发到 channel (onOutput + retry 双路径)
- feishu:[progress] 中间叙述消息,独立发送
tier2tech-tian and others added 26 commits June 22, 2026 20:38
…spec

feat: allow delegation from any group
# Conflicts:
#	src/index.ts
…ills

wrapup Step 4 and wiki Ingest step 3 now require: merge into
subsections + retire stale, top status as single source, no in-page
line/section refs, and reference CONTRIBUTING.md.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
两个实战 bug 修复:
1. report 写 DB 后通过 queue.sendMessage() 实时注入活跃 agent,
   推进 lastAgentTimestamp 防 double-feed(message loop fallback
   用 ipc_ 前缀过滤已注入 report,不误丢用户消息)
2. handleReport 拒绝汇报时给 reporting agent 发错误消息,
   消除"工具返回成功但 host 拒绝"的矛盾

抽取 deliverReportToSource 统一投递函数,handleReport 和
finalizeDelegationOnTurnEnd 共用。

OpenSpec: delegation-report-injection (C1 review GO)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…injection

C1 code review found P0: lastAgentTimestamp pushed by injection gets
overwritten by old newCursor when source agent's turn ends. Extract
advanceAgentCursor(jid, ts) helper with ts > current guard, replace
all 6 bare assignments across src/index.ts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ection

feat: inject delegation report into active agent + reject feedback
Revert of 3146b26 accidentally removed two features along with
shouldCarryReply. This restores them on top of PR#32's codebase:

1. isIpcOnlyTurn + sendAgentMessage: agent replies to cross-group
   IPC messages (delegate/report) skip voice broadcast
2. shouldCarryReply + currentQueryReplies: auto-finalize only carries
   reply content when trigger messages are all IPC with matching
   single task_id, preventing cross-task content leaks
3. Error path finalize no longer passes reply content

5 new shouldCarryReply unit tests added.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix: restore IPC voice mute + auto-finalize reply attribution
delegate 注入目标群的格式是 [task_id:dlg_xxx],但 shouldCarryReply
匹配的是 (task dlg_xxx),导致自动终态永远不携带 agent 回复内容。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
进程重启/游标未推进时 DB 残留上一次派工的 ipc_ 消息,
task_id 与当前活跃任务不同导致 taskIds.size>1 误判 false,
回复内容无法携带进自动终态汇报。

改为"至少一条消息含活跃任务 task_id 即可",不再要求
所有消息的 task_id 完全一致。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
IPC pipe 模式下 agent 复用同一进程处理多次 query,但
missedMessages 只在 turn 开始时捕获一次,后续 query
的触发消息不在其中。shouldCarryReply 拿着旧消息去匹配
新的 activeTask,必然 false,回复内容丢失。

新增 hasIpcTriggerForTask 直接查 DB 确认当前活跃任务
是否有对应的 ipc_ 触发消息,作为 IPC pipe 模式的补偿。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
按消息内容分 5 类意图(action/silent/navigate/tech_status/notify),
每类走不同摘要策略:代码块/长表格直接跳过不播,需决策的消息突出
"需要你做什么",技术汇报保留关键数据。

VOICE_SUMMARY_VERSION=off 可运行时关闭 LLM 摘要。

38/38 测试通过,C1 三轮 review GO。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…patched

rate-limit 重试的 agent 进程跑完后进入 IPC pipe 等待,后续通过
pipe 投递的 query(如 delegation 派工)完成时走的是 retry 的匿名
onOutput 回调,该回调的 success 分支没有 auto-finalize 逻辑。

导致目标群跑完任务后 delegation 状态永远卡在 dispatched,源群
收不到回复,且后续派工被拒(槽位被占)。

在 retry onOutput 的 success 分支补上完整的 auto-finalize 逻辑
(shouldCarryReply + IPC pipe 补偿 + finalizeDelegationOnTurnEnd),
与 mainOnOutput 保持一致。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
按 C2 review 反馈:
1. 抽 finalizeActiveDelegationForTurn 共享函数,main/retry onOutput 统一调用
2. main/retry onOutput 的 error 分支也补 failed 终态兜底(pipe 模式下进程不退出,
   外层 error path 不会立刻执行)
3. 补 3 个测试:success 幂等、error 标记 failed、error 幂等

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ility

fix: make GitNexus available in Codex mode
原 prompt 指示 LLM "随后最多 3 个选项",导致 yes/no 确认被
编成三选一。改为忠实转述,原文有选项才列选项。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
normalizeClaudeModelName 之前会剥离 [1m] 后缀,导致 SDK 收到的模型名
不带 1M 标记,上下文窗口降到默认 200k,触发 "Prompt is too long"。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
0.3.x SDK 移除 cli.js 改用 native binary,query() 不再需要
pathToClaudeCodeExecutable 和 executable 参数,SDK 自动定位
内置 binary。版本精确 pin 0.3.201 防漂移(wiki 教训)。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude Code 新版限流提示从 "You've hit your limit" 变成
"You've hit your session limit",正则 (?:usage\s+)? 不匹配
session,导致轮换未触发。扩展为 (?:(?:usage|session)\s+)?。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
navigate 旧 prompt 指示"分几块"导航,导致每条摘要都是
"第一块...第二块..."框架占满字数没实质内容。
tech_status 的"严格 3 句话"也有类似僵化问题。
改为自然叙述直接讲结论。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
V3_COMMON 旧约束"严禁编造"太弱,LLM 经常自己添加建议、
下一步、推测性内容,导致摘要跟原文不一致甚至比原文还长。
加强为"每个信息点必须在原文中有出处"。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
delegation 子群完成后,除了写 DB + 注入 agent,同时直接往发起群
发一条飞书消息。解决 agent 收到 review 结果后闷头干活不汇报用户、
或 agent 已退出时用户看不到 delegation 结论的问题。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants