feat(worker): 新增 AI-free StuckDetector,CLI 卡住时主动告警用户#559
Open
47seek wants to merge 12 commits into
Open
Conversation
47seek
force-pushed
the
feat/stuck-detector-clean
branch
from
July 22, 2026 13:09
2c111d8 to
87ef85b
Compare
- 新增 src/utils/stuck-detector.ts:轻量级卡住检测器,不依赖 AI,用正则匹配常见卡住态(hook review、[Y/n]、Press ... to 等) - worker.ts:flushPending 写完消息后 arm 检测器,markPromptReady 时 disarm;CLI spawn/stop 时同步启停 - worker-pool.ts:收到 stuck_warning 后在话题里发文本告警(卡住时长 + 匹配模式 + 终端快照),同 turn 去重 - types.ts / core/types.ts:新增 stuck_warning 消息类型和 stuckWarningTurnId 去重字段 - config.ts:新增 stuckDetector 配置,默认启用,超时 45s(STUCK_DETECTOR_ENABLED / STUCK_DETECTOR_TIMEOUT_MS) 现有 ScreenAnalyzer 能检测 TUI 选项卡,但默认关闭且需配置 LLM 端点。Codex PreToolUse hook review、裸 [Y/n] 等卡住态不渲染为选项列表,或 analyzer 未配置时,botmux 会傻等,用户以为消息没送达。StuckDetector 作为零配置兜底。 - 公共层改动:worker.ts / worker-pool.ts / config.ts / types.ts,影响所有 CLI 和后端 - 仅在写入消息后超时未完成 turn 时触发,正常 turn 不受影响 - 默认启用但只在确认卡住(有 inflight + 非 idle + 无 TUI 卡片)时才告警,误报率低 - npx tsc --noEmit:改动文件零错误(仅预先存在的 electron 类型报错,无关) - 待 live 验证:pnpm switch:here && pnpm daemon:restart 后,让 Codex 停在 hook review 界面观察告警
## 改了什么 - worker.ts: isActuallyStuck 增加 PTY 静默检查(15s 内有输出则不触发),避免正常长任务误报 - worker-pool.ts: 有匹配模式时用 buildTuiPromptCard 发交互式卡片(按 Enter/Esc 一键处理),无模式时只发轻量文本告警 - card-handler.ts: tui_keys isFinal 时同步清除 stuckWarningCardId - core/types.ts: 新增 stuckWarningCardId 字段 - stuck-detector.ts: 放宽 Press 正则匹配 /Press .+ to/,覆盖 'Press space or enter to' 等变体 - test/stuck-detector.test.ts: 新增 10 个单测,覆盖 arm/disarm/超时/误报门控/模式匹配/dispose/去重 ## 为什么 - 原实现只发普通文本,没有交互选择,不符合 'ask user' 目标 - 45s 正常长任务(模型流式输出、工具调用)会误报 - 新增公共路径无单测 ## 影响面 - 公共层改动:worker.ts / worker-pool.ts / card-handler.ts / types.ts,影响所有 CLI - 交互式卡片复用现有 tui_keys 通路,用户点击后 worker 直接发按键给 CLI - PTY 静默检查大幅降低误报率 ## 测试验证 - vitest run test/stuck-detector.test.ts: 10/10 passed - tsc --noEmit: 改动文件零错误 - 待 live 验证
47seek
force-pushed
the
feat/stuck-detector-clean
branch
from
July 22, 2026 13:16
87ef85b to
fe66531
Compare
## 改了什么 - stuck-detector.ts: 只保留 hook-review 模式匹配,移除通用 Y/n/permission/Press 等模式 - worker-pool.ts: hook-review 卡片提供 t=信任全部/Enter=逐项审核/Esc=关闭(与界面文档一致,不猜按键);未知模式只发文本告警,不展示终端快照(防内容泄露) - worker.ts: tui_keys 后 re-arm StuckDetector,避免进入下一层 review 后不再告警 - worker-pool.ts: 把 stuckWarning 清除逻辑移到 prompt_ready case 末尾,修复 raw-input-followup-atomicity 测试的 2000 字符窗口挤断问题 ## 为什么 - 原方案对所有模式硬编码 Enter/Esc,但 hook-review 界面 Enter=review(不是继续),点了会进入下一层仍卡住 - 通用 Y/n/permission 语义无法从正则安全推断,应拆到后续 PR - 未知模式展示原始终端快照有内容泄露风险 - prompt_ready 新增行挤断既有测试窗口 ## 影响面 - 只影响 Codex hook-review 这一个确定场景,公共路径风险大幅收窄 - 未知模式仍有轻量告警,但不泄露内容、不猜按键
…e 时序 ## 问题 原事故走 non-durable 普通 IM 路径(dispatchAttempt===undefined),但 isActuallyStuck 用 !durableTurnInFlight && inflightInputsEmpty() 做门控——non-durable turn 永远 return false。 即便 durable turn,20s submit recheck 失败会清掉 durableTurnInFlight,早于 45s detector timeout。 结果:45s 卡片很可能根本不会出现。 ## 修复 - isActuallyStuck 去掉 durableTurnInFlight / inflightInputsEmpty 门控,只保留 isPromptReady + tuiPromptBlocking + 15s PTY 静默检查。原事故时序(普通消息→submit 未确认 →hook screen 静止→20s failure notify→45s)现在能触发 hook ask。 - InflightInputTracker 新增 isEmpty() 方法(暴露真实队列长度),为后续 worker 级测试预留。 ## 影响面 - 误报风险略增(不再要求 inflight 非空),但 15s PTY 静默 + !isPromptReady 已足够过滤 正常长任务(模型流式输出、工具调用都会持续产生 PTY 活动)
## 改了什么 1. 删除未使用的 InflightInputTracker.isEmpty() 和 worker inflightInputsEmpty() 2. StuckDetector.tick(): snapshot 未匹配 hook-review 模式时静默 re-arm,不发告警 3. worker: 新增 stuckWarningActive 标记,tui_keys case 只在 stuck-warning 卡片交互时 re-arm 4. markPromptReady 时清除 stuckWarningActive 5. 更新 test/stuck-detector.test.ts:未匹配时期望 onStuck 不触发 ## 为什么 - 原实现对任意 45s+ 静默长任务发 CLI 卡住告警,误报 - tui_keys 无条件 re-arm 会污染 ScreenAnalyzer 的 TUI 卡片交互 - 未使用的 API 增加公共路径风险 ## 测试 - test/stuck-detector.test.ts: 10/10 passed - tsc --noEmit: 零错误
## 问题 - stuckWarningActive 全局布尔量不包含卡片来源,warning 后任意 ScreenAnalyzer 卡片按键仍触发 re-arm - 点 Enter 后只清 stuckWarningCardId,不清 stuckWarningTurnId,下一层 review 同 turnId 命中去重丢掉第二次 warning - t/Esc 也无条件 re-arm ## 修法 1. tui_keys 类型新增 rearmStuckDetector?: boolean 字段 2. 去掉 worker stuckWarningActive 全局布尔量 3. card-handler 只对 stuck-warning 卡片的 Enter (confirm) 动作传 rearmStuckDetector: true 4. card-handler isFinal 时清 stuckWarningTurnId(让下一层 review 能再次发卡)+ stuckWarningCardId 5. worker-pool 卡片发送失败时清 stuckWarningCardId + stuckWarningTurnId 6. worker tui_keys case 只在 rearmStuckDetector 为 true 时 re-arm ## 测试 - test/stuck-detector.test.ts: 10/10 passed - test/raw-input-followup-atomicity.test.ts: 10/10 passed - tsc --noEmit: 零错误
## 改了什么 1. card-handler: rearmStuckDetector 按实际 keys 精确等于单个 'Enter' 判断(不再按 optionType 猜,因为 t=confirm/Enter=select),同时校验 cardMessageId === stuckWarningCardId 2. worker isActuallyStuck: 添加 cliId === 'codex' 门控,其它 CLI 不会收到 Codex 专属 t/Enter/Esc 卡 3. worker-pool prompt_ready: 先把已发交互卡更新为 resolved(防止 late click 注入按键到已恢复 CLI),再清 marker 4. worker-pool stuck_warning: 去重条件改为 msg.turnId !== undefined && 相等才去重,避免 undefined===undefined 把首个告警误判为重复 ## 测试 - test/stuck-detector.test.ts: 10/10 passed - test/raw-input-followup-atomicity.test.ts: 10/10 passed - tsc --noEmit: 零错误
…urnId兜底去重 ## 改了什么 1. card-handler tui_keys 入口 fail-closed:校验 cardMessageId === tuiPromptCardId 或 stuckWarningCardId,过期卡不发任何 IPC(PATCH 只是 UI,不当授权校验) 2. card-handler isFinal 清理按来源分流:只清被点击卡的状态,stuck 卡点击不污染 ScreenAnalyzer 卡的 tuiPromptCardId/options/toggledIndices 3. worker crash-loop 恢复路径:stopStuckDetector 后加 startStuckDetector,避免检测能力永久丢失 4. worker-pool 去重:无 turnId 时以 active stuckWarningCardId 兜底,避免重复发卡 ## 验证 - test/stuck-detector.test.ts: 10/10 passed - test/raw-input-followup-atomicity.test.ts: 10/10 passed - tsc --noEmit: 无新增错误(既有 Electron 类型错误与本 PR 无关)
## 改了什么 - card-handler: stuck-warning 卡点击只发送自己的 keys,不再拼入并发 ScreenAnalyzer 多选卡的 toggled keys/options;finalText 同样按来源分流 - card-handler: 非 TUI 卡的 toggle 直接拒绝,不读写普通卡 toggle 状态 - worker-pool claude_exit: 退出/替换前将活跃 stuck 卡更新为 resolved,并清 marker,防 late click 向 replacement worker 注入按键 ## 验证 - test/stuck-detector.test.ts: 10/10 passed - test/raw-input-followup-atomicity.test.ts: 10/10 passed - tsc --noEmit: 无新增错误
## 改了什么 - classifier 从任一关键词 OR 改为同一快照同时具备:PreToolUse hooks 标题、pending review 状态、完整 t/Enter/Esc 控制提示 - 仅讨论/粘贴事故文本但缺当前控制行时不会发可注入按键的卡片 - 增加普通对话引用标题、粘贴状态缺控制行、仅控制行缺标题/状态三个负例 ## 测试 - test/stuck-detector.test.ts: 13/13 passed - tsc --noEmit: 无新增错误
Co-Authored-By: Claude <noreply@anthropic.com>
47seek
force-pushed
the
feat/stuck-detector-clean
branch
from
July 22, 2026 17:23
b08202b to
413b943
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
改了什么
新增 AI-free
StuckDetector,在 Codex PreToolUse hook-review 阻塞界面主动在飞书 ask user,并把用户选择安全回传给 CLI。最小方案,只针对 Codex hook-review 这一个确定场景:
src/utils/stuck-detector.ts:超时 + PTY 静默门控后,只匹配 hook-review 界面(hook needs review/PreToolUse hooks/Press t to trust all)src/worker.ts:flushPending写完消息 arm,markPromptReadydisarm;tui_keys后 re-arm(避免进入下一层 review 后不再告警);isActuallyStuck增加 15s PTY 静默检查,正常长任务不误报src/core/worker-pool.ts:匹配到 hook-review 时用buildTuiPromptCard发交互式卡片,按钮为 t=信任全部 / Enter=逐项审核 / Esc=关闭(与界面文档一致,不猜按键);未知 stall 只发文本告警,不展示终端快照(防内容泄露)、不猜按键src/im/lark/card-handler.ts:tui_keysisFinal 时同步清除stuckWarningCardIdsrc/core/types.ts:新增stuckWarningTurnId/stuckWarningCardId去重字段src/config.ts:stuckDetector配置(默认启用,45s)为什么
Codex 启动时检测到
~/.codex/hooks.json里的新 PreToolUse hook 会弹审核界面卡住,botmux 把消息写进 PTY 但 Codex 停在交互界面不处理,用户以为消息没送达。现有ScreenAnalyzer需配置 LLM 端点且默认关闭。影响面
tui_keys通路,用户点击后 worker 直接把按键发给 CLI测试验证
vitest run test/stuck-detector.test.ts:10/10 passed(arm/disarm/超时/误报门控/hook-review 匹配/通用模式不匹配/dispose/同窗口去重/re-arm)vitest run test/raw-input-followup-atomicity.test.ts:10/10 passed(修复了 prompt_ready 新增行挤断 2000 字符检查窗口的问题)tsc --noEmit:改动文件零错误实测
待真实 hook-review 闭环实测(需 Codex 弹审核界面时验证卡片弹出 → 点击 t/Enter/Esc → CLI 响应 → 告警清除)。