Skip to content

fix(reaction): 修复 Grok card-off 假 DONE / 卡 GoGoGo - #633

Open
echookai wants to merge 3 commits into
deepcoldy:masterfrom
echookai:fix/grok-turn-reaction-idle
Open

fix(reaction): 修复 Grok card-off 假 DONE / 卡 GoGoGo#633
echookai wants to merge 3 commits into
deepcoldy:masterfrom
echookai:fix/grok-turn-reaction-idle

Conversation

@echookai

Copy link
Copy Markdown

改了什么

  • reliableTurnTerminal CLI(Grok/Codex 等)跳过 post-submit / queued 的 busy-absent idle probe,避免提交后几秒误判空闲、过早 DONE。
  • card-off 进度 reaction 仅在 working|analyzing → idle|limited 时 ✋→✅,避免冷启动 settle 的假 idle。
  • argv 冷启动首 prompt 第一次 ready 上报 working(非 idle),保证真实结束时有 working→idle 边,避免一直卡在 GoGoGo。

为什么

Grok 配了 busyPattern,提交后 UI busy 标记常滞后,post-submit probe 会假 idle 并提前 DONE;Codex 无 busyPattern,不走该路径。叠加上一轮「只允许 working→idle 才 DONE」后,argv 冷启动又会出现「永远 GoGoGo」(从未进入 working)。

影响面

  • CLI:有 busyPattern 且 card-off 的会话(尤其 Grok);Codex 无 busyPattern,行为基本不变。
  • 模块worker.tsworker-pool.tsadapters/cli/grok.ts(注释)。
  • 会话类型:card-off / 无流式卡片的进度 reaction;开着流式卡片的不受影响。

测试验证

pnpm vitest run test/turn-reactions.test.ts test/worker-pipe-initial-screen-order.test.ts
# 36 passed
pnpm switch:here && pnpm daemon:restart
# 飞书 Grok 实测:GoGoGo → 任务结束 → DONE,中途不再秒切 DONE

@echookai
echookai requested a review from deepcoldy as a code owner July 28, 2026 07:15
reliableTurnTerminal CLI 跳过 post-submit busy-absent 假 idle;DONE 仅
working→idle 翻转;argv 冷启动首 prompt 先报 working 以免反应卡住。
@echookai
echookai force-pushed the fix/grok-turn-reaction-idle branch from d204557 to 2296f32 Compare July 28, 2026 07:23
shouldArmSpawnArgvInitialPromptBusy 仅 Grok 类(argv+SessionStart+可靠
terminal)置位,避免 Riff/Pi 假 busy;flushPending 提交时立即上报 working
以覆盖 2s 采样漏掉的短 turn。

@deepcoldy deepcoldy left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

首审(Claude)— 1 处 P2 回归(UX,非 correctness),其余无阻塞

先用白话讲清这个 PR 在做什么,再说发现的问题。未经申晗确认不合码,findings 交 @codex 复审。

白话:这 PR 三处改动的逻辑

卡关背景:card-off(关掉流式卡片)的会话,靠给用户消息打 reaction 表示进度 —— 收到消息打 ✋(GoGoGo),干完活翻成 ✅(DONE)。Grok 出了两个毛病,这 PR 一起治:

  1. 假 DONE:Grok 提交后,它的 busy 标记(Waiting for response / Ctrl+c:cancel)常常滞后几秒才出现。老逻辑在 submit 后跑一个「busy 标记不在场 = 空闲」的探针(scheduleBusyPatternIdleProbe),于是提交后几秒就误判空闲、过早翻 DONE。改法reliableTurnTerminal 的 CLI(Grok/Codex/Claude/TRAE)改由 transcript 的 assistant_final 决定 turn 结束,不再跑这个 post-submit / queued 探针(worker.ts 两处加 reliableTurnTerminal !== true 门)。

  2. 假 DONE 的另一半:把 ✋→✅ 的翻转从「任何 settle-to-idle 边」收紧成「只有 working|analyzing → idle|limited 才翻」(worker-pool.tsprevStatus === 'working' || prevStatus === 'analyzing' 门),避免冷启动 settle 出来的假 idle 误翻。

  3. 卡 GoGoGo:叠加改动 2 后,argv 冷启动首个 prompt 从没进过 working,第一次 idle 就被门挡住,永远停在 GoGoGo。改法:给 Grok 类(argv 塞首 prompt + injectsReadyHook + reliableTurnTerminal当前只有 Grok 命中)冷启动首次 ready 报 working(非 idle),并在 flushPending 提交瞬间立即 publish 一次 working,覆盖 2s 采样漏掉的短 turn。

方向我认同,Grok 那条链路是对的。

🟠 P2:card-off + {gemini, pi, mtr, opencode} 冷启动首轮,✋ 永不翻 ✅

这个 PR 治好了 Grok 的「卡 GoGoGo」,却在同一处给另外 4 个 argv CLI 引入了同款「卡 GoGoGo」。

改动 2 的门是对所有 card-off 会话生效的,但补偿它的 working 上报只覆盖了两类:

  • 改动 3 的 arm —— 只匹配 Grok;
  • flushPending 的即时 working —— 只覆盖排队投递的 prompt。

gemini / pi / mtr / opencode 这 4 个 CLI:passesInitialPromptViaArgs=trueinjectsReadyHook=false(不被 arm),冷启动首 prompt 是塞进 argv 的(不走 flushPending,拿不到即时 working),且冷启动期 screen 采样被 awaitingFirstPrompt 早退(worker.ts:6093)静默 —— 所以 daemon 看到的第一个 screen_update 就是 idlemarkPromptReady 里的 publishScreenStatus('idle')),此刻 prevStatusds.lastScreenStatus)还是 undefined

于是门 prevStatus === 'working' || 'analyzing' = false → finishTurnReactions(全仓唯一调用点 worker-pool.ts:3138)不被调用 → 首条 ✋ 滞留。

差分 probe 实证(用 __testOnly_setupWorkerHandlers 驱动真实 handler,喂 prevStatus=undefined → idle,只 mock Lark 调用):

  • master:DONE added=true, removed=true(✋ 正确翻 ✅)
  • 本 PR:DONE added=false, removed=false, pendingLeft=[om_a](✋ 滞留)

→ 确认是本 PR 引入的回归,非既存。

可达性disableStreamingCard 是 per-bot 配置,与 CLI 选择正交 —— 任意 gemini/pi/mtr/opencode bot 都能 card-off;且常规长度首 prompt 走 argv(resolveInitialPromptDeliveryargvPrompt)就是默认冷启动路径,非边角。新建话题的三条 spawn 路径(pinned / no-projects / repo-pick)都在 accept 时先 noteTurnReceived 打 ✋、之后才 fork argv prompt,均可达。

严重度:UX-only(reaction 不翻,不涉 correctness/数据);finishTurnReactions整个 pending 列表,所以 turn 2 的 working→idle 会补翻 turn 1 的滞留 → 自愈永久滞留仅限 one-shot 会话(没有 turn 2)。但首轮(最显眼的第一问)+ one-shot 不愈,且正是 PR 想修的症状被换个 CLI 集合复现。

修法方向(不 prescribe 精确 patch,交作者/@codex 收敛):argv-baked 首 prompt 的 working 上报应覆盖所有 card-off 相关 argv CLI,而非仅 Grok arm。这些 quiescence-argv CLI 的首个 idle 是真 turn end(不是 Grok 那种假 idle),需要在 prompt 开始执行时(spawn 时 preparedInitialPrompt 非空 / ready 后)先 publish 一次 working,让真 idle 到来时 prevStatus=working —— 镜像 flushPending 对排队 prompt 的处理即可。注意别去放宽门的 allow-list({working, analyzing} 对照 ScreenStatus 枚举已完备,放宽会误翻真假 idle)。

✅ 无阻塞 / 正确的部分

  • probe-skip 安全:Codex/Claude/TRAE 三个 reliableTurnTerminal CLI 本就没有 busyPatternscheduleBusyPatternIdleProbe 对它们一直是 no-op(worker.ts:6394 早退)—— 所以 probe-skip 实际只影响 Grok,而 Grok 的 idle 由 transcript assistant_final → fireIdlegrok-transcript.ts:377worker.ts:3830)驱动,不依赖这个探针。安全。
  • Grok arm 不会永久卡住:arm 后停在 working、isPromptReady=false,随后 turn_completed → assistant_final → fireIdle → markPromptReady(flag 已清)→ publishScreenStatus('idle') 产生真 idle 边;外加 deferFirstPromptTimeoutUntilReady + first-prompt-timeout 兜底。
  • merge 干净:对当前 master fd455bcf#624 改了 worker.ts +52/-3)trial-merge 零冲突,PR 5 处 worker.ts 改动与 #624 的改动都完整保留,另 6 个文件与 PR head blob 逐字节一致。
  • build 绿;PR 三个测试文件 46/46,扩展套件(turn-reactions / worker-pipe / initial-prompt / session-lifecycle)121/121 全绿。

次要:新测试全是 source-level string pin

turn-reactions.test.ts / worker-pipe-initial-screen-order.test.ts 的新用例都是 readFileSync(源码).toContain('...') 形态 —— 锁的是改动的代码形状,从不驱动真实冷启动 status 序列过 handler,所以这个 P2 对现有测试完全不可见。建议随修法补一条 behavioral 回归:驱动真 handler 喂 prevStatus=undefined → idle,断言 card-off 首轮 ✋ 被翻。

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@deepcoldy deepcoldy left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codex 独立复审:确认首审 P2,当前 head b17a7b35 仍受影响

我独立沿 worker → daemon 的真实状态链复核,并用 __testOnly_setupWorkerHandlers 做了 master/PR 差分。结论与 Claude 首审对齐:card-off + Gemini/Pi/MTR/OpenCode 的常规 argv 冷启动首轮会残留 GoGoGo

时序证据

  • 这 4 个 adapter 都是 passesInitialPromptViaArgs=true,常规长度首 prompt 不进入 pendingMessages,因此不走 flushPending() 新增的 publishScreenStatus('working')
  • 新增的 shouldArmSpawnArgvInitialPromptBusy() 又要求 injectsReadyHook && reliableTurnTerminal;目前只有 Grok 命中,4 个 quiescence argv adapter 明确返回 false。
  • 冷启动 sampler 在 awaitingFirstPrompt 为 true 时直接 return(worker.ts:6093)。
  • 真正完成/检测到 prompt 时,markPromptReady() 在同一个同步调用里先 isPromptReady=true(5243),再清 awaitingFirstPrompt(5266-5268),最后因未 arm 而直接 publishScreenStatus('idle')(5320)。因此 sampler 不存在可观察到 awaitingFirstPrompt=false && isPromptReady=false 的窗口,daemon 在正常短首轮看不到 working。
  • daemon 的首个有效 screen_update 因而是 prevStatus=undefined → idle;新门只允许 working|analyzing → idle|limitedfinishTurnReactionsworker-pool.ts:3135-3139),首轮 pending reaction 不会翻 DONE。

差分 probe

同一个 behavioral probe:card-off ds、lastScreenStatus=undefined、预置 pendingAckReactions=[om_a],驱动真实 handler 收到 idle:

  • PR head:DONE 未添加、旧 reaction 未移除、pendingAckReactions=[om_a]
  • 临时恢复 master 的无条件 idle settle 逻辑:DONE 添加、旧 reaction 移除、pending 清空。

两边 probe 都通过各自预期,确认这是门收紧后、working 补偿覆盖不完整造成的差分,不是 handler mock 假象。

影响与建议

严重度维持 P2 / UX-only:后续 turn 的正常 working→idle 会批量补翻,永久残留主要是 one-shot 会话;但首轮是默认可达路径。修复应在 argv-baked 首 prompt 开始执行时为全部相关 adapter 建立 working 边,同时保留当前 {working, analyzing} 收口,不建议放宽 daemon 门。

验证:

  • pnpm vitest run test/turn-reactions.test.ts test/initial-prompt-arg-limit.test.ts test/worker-pipe-initial-screen-order.test.ts:46/46 通过
  • pnpm vitest run test/session-lifecycle-hooks.test.ts:13/13 通过
  • pnpm build:通过
  • 临时 probe 已删除,未改 PR 代码;未执行合并

…g→idle

quiescence argv 首 ready 为真结束:先 publish working 再 idle,避免
card-off 门闩下 undefined→idle 永远不 DONE;Grok SessionStart arm 不变。
补 handler 行为回归。
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