commandmate 0.30.0 → 0.30.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/BUILD_ID +1 -1
- package/.next/app-build-manifest.json +158 -158
- package/.next/app-path-routes-manifest.json +34 -34
- package/.next/build-manifest.json +2 -2
- package/.next/prerender-manifest.json +3 -3
- package/.next/required-server-files.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/api/agents/update/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/agents/versions/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/app/update/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/app/update-check/route.js +1 -1
- package/.next/server/app/api/app/update-check/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/assistant/conversation/route.js.nft.json +1 -1
- package/.next/server/app/api/assistant/conversation/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/assistant/current-output/route.js.nft.json +1 -1
- package/.next/server/app/api/assistant/current-output/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/assistant/messages/route.js +1 -1
- package/.next/server/app/api/assistant/messages/route.js.nft.json +1 -1
- package/.next/server/app/api/assistant/messages/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/assistant/session/route.js +1 -1
- package/.next/server/app/api/assistant/session/route.js.nft.json +1 -1
- package/.next/server/app/api/assistant/session/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/assistant/start/route.js +2 -2
- package/.next/server/app/api/assistant/start/route.js.nft.json +1 -1
- package/.next/server/app/api/assistant/start/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/assistant/terminal/route.js +2 -2
- package/.next/server/app/api/assistant/terminal/route.js.nft.json +1 -1
- package/.next/server/app/api/assistant/terminal/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/assistant/tools/route.js.nft.json +1 -1
- package/.next/server/app/api/assistant/tools/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/auth/login/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/auth/logout/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/auth/status/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/capabilities/route.js +1 -1
- package/.next/server/app/api/capabilities/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/daily-summary/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/daily-summary/status/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/external-apps/[id]/health/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/external-apps/[id]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/external-apps/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/fs/browse/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/fs/recent-paths/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/hooks/agent-event/route.js +1 -1
- package/.next/server/app/api/hooks/agent-event/route.js.nft.json +1 -1
- package/.next/server/app/api/hooks/agent-event/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/hooks/claude-done/route.js +2 -2
- package/.next/server/app/api/hooks/claude-done/route.js.nft.json +1 -1
- package/.next/server/app/api/hooks/claude-done/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/hooks/permission-request/route.js +1 -1
- package/.next/server/app/api/hooks/permission-request/route.js.nft.json +1 -1
- package/.next/server/app/api/hooks/permission-request/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/metrics/vibe/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/ollama/models/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/push/escalation/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/push/subscriptions/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/push/vapid/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/remote/pair/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/[id]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/[id]/todos/[todoId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/[id]/todos/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/clone/[jobId]/route.js +1 -1
- package/.next/server/app/api/repositories/clone/[jobId]/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/clone/[jobId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/clone/route.js +1 -1
- package/.next/server/app/api/repositories/clone/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/clone/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/excluded/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/restore/route.js +1 -1
- package/.next/server/app/api/repositories/restore/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/restore/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/route.js +1 -1
- package/.next/server/app/api/repositories/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/scan/route.js +1 -1
- package/.next/server/app/api/repositories/scan/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/scan/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/sync/route.js +1 -1
- package/.next/server/app/api/repositories/sync/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/sync/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/repositories/validate-path/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/settings/default-agents/route.js.nft.json +1 -1
- package/.next/server/app/api/settings/default-agents/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/settings/default-surface-mode/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/sidebar/group-order/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/skills/[id]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/skills/installations/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/skills/operations/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/skills/reindex/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/slash-commands/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/tasks/[taskId]/cancel/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/tasks/[taskId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/templates/[id]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/templates/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/todos/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/verification/runs/[runId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/verification/runs/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/auto-yes/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/auto-yes/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/capture/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/capture/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/clear-composer/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/clear-composer/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/cli-reference/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/cli-tool/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/cmate/schedules/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/current-output/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/current-output/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/env/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/execution-logs/[logId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/execution-logs/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/files/[...path]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/branch/create/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/branch/delete/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/branches/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/checkout/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/commit/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/diff/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/fetch/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/log/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/pull/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/push/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/reset/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/revert/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/show/[commitHash]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stage/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/staged/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stash/[index]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stash/apply/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stash/pop/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stash/push/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/stash/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/status/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/unstage/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/git/working-diff/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/instances/opencode/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/instances/opencode/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/interrupt/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/interrupt/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/kill-session/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/kill-session/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/logs/[filename]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/logs/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/marp-render/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/memos/[memoId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/memos/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/messages/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/opencode/diff/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/opencode/session/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/opencode/share/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/prompt-response/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/prompt-response/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/prompt-response/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/resolve-target/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/respond/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/respond/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/respond/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/schedules/[scheduleId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/schedules/active/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/schedules/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/search/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/send/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/send/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/send/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/git-workflow/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/git-workflow/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/install/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/plan/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/uninstall/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/uninstall-plan/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/update/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/[skillId]/update-plan/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/skills/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/slash-commands/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/special-keys/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/special-keys/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/tasks/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/terminal/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/terminal/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/timers/history/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/timers/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/timers/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/timers/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/todos/[todoId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/todos/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/tree/[...path]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/tree/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/upload/[...path]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/verify/config/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/verify/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/verify/runs/[runId]/cancel/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/verify/runs/[runId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/verify/runs/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/[id]/viewed/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/apply-default-agents/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/worktrees/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/route_client-reference-manifest.js +1 -1
- package/.next/server/app/chat/page_client-reference-manifest.js +1 -1
- package/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/server/app/manifest.webmanifest/route_client-reference-manifest.js +1 -1
- package/.next/server/app/more/page_client-reference-manifest.js +1 -1
- package/.next/server/app/offline/page_client-reference-manifest.js +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/proxy/[...path]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/repositories/page_client-reference-manifest.js +1 -1
- package/.next/server/app/review/page_client-reference-manifest.js +1 -1
- package/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
- package/.next/server/app/skills/[skillId]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/skills/installed/page_client-reference-manifest.js +1 -1
- package/.next/server/app/skills/page_client-reference-manifest.js +1 -1
- package/.next/server/app/worktrees/[id]/files/[...path]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/worktrees/[id]/page.js +12 -12
- package/.next/server/app/worktrees/[id]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/worktrees/[id]/terminal/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +34 -34
- package/.next/server/chunks/1058.js +1 -1
- package/.next/server/chunks/1886.js +2 -2
- package/.next/server/chunks/2119.js +1 -1
- package/.next/server/chunks/2445.js +1 -1
- package/.next/server/chunks/3099.js +5 -0
- package/.next/server/chunks/5017.js +1 -1
- package/.next/server/chunks/5043.js +1 -0
- package/.next/server/chunks/552.js +25 -0
- package/.next/server/chunks/6130.js +1 -1
- package/.next/server/chunks/6894.js +1 -1
- package/.next/server/chunks/9696.js +2 -2
- package/.next/server/middleware-manifest.json +5 -5
- package/.next/server/pages/500.html +1 -1
- package/.next/server/pages-manifest.json +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/{6381-28d0e95620aa018e.js → 6381-147dde2a967e9458.js} +1 -1
- package/.next/static/chunks/app/worktrees/[id]/page-2f73d69187936627.js +1 -0
- package/.next/static/css/23f9c40ecc458c77.css +1 -0
- package/.next/trace +13 -13
- package/dist/server/src/lib/history/user-turn-recorder.js +9 -4
- package/dist/server/src/lib/hooks/agent-event-service.js +40 -6
- package/dist/server/src/lib/hooks/sources/antigravity/history.js +125 -18
- package/dist/server/src/lib/hooks/sources/antigravity/transcript.js +39 -0
- package/dist/server/src/lib/hooks/sources/claude/history.js +312 -21
- package/dist/server/src/lib/hooks/sources/claude/transcript.js +175 -13
- package/dist/server/src/lib/hooks/sources/codex/history.js +100 -13
- package/dist/server/src/lib/hooks/stop-history-capture.js +194 -0
- package/dist/server/src/lib/polling/response-checker.js +43 -3
- package/dist/server/src/lib/polling/structured-history-gate.js +131 -6
- package/dist/server/src/lib/session/current-output-builder.js +118 -16
- package/package.json +1 -1
- package/.next/server/chunks/2827.js +0 -29
- package/.next/server/chunks/6781.js +0 -1
- package/.next/static/chunks/app/worktrees/[id]/page-d48b95ed554bcd13.js +0 -1
- package/.next/static/css/61e10d4b1b11b193.css +0 -1
- /package/.next/static/{7K0XXFu_ty4wRSkl4X21E → QXxQbXWoo7ldUyOcv7PCZ}/_buildManifest.js +0 -0
- /package/.next/static/{7K0XXFu_ty4wRSkl4X21E → QXxQbXWoo7ldUyOcv7PCZ}/_ssgManifest.js +0 -0
|
@@ -36,9 +36,12 @@
|
|
|
36
36
|
* is therefore keyed on the *prompt* record, never on a `requestId`.
|
|
37
37
|
* 3. **`type: "user"` is mostly not the user.** Of 19 user records in the
|
|
38
38
|
* sampled session, 18 were `tool_result` payloads. Tool results, `isMeta`
|
|
39
|
-
* placeholders (`[Image: original 1440x2170…]`), and the
|
|
39
|
+
* placeholders (`[Image: original 1440x2170…]`), and the built-in command
|
|
40
40
|
* bookkeeping records (`<command-name>`, `<local-command-stdout>`) all have
|
|
41
|
-
* `type: "user"` and none of them is a prompt.
|
|
41
|
+
* `type: "user"` and none of them is a prompt. The one `<command-…>` shape
|
|
42
|
+
* that *is* a prompt — a project command, recorded `<command-message>` first
|
|
43
|
+
* — is Issue #2265; see {@link CLAUDE_SLASH_COMMAND_PREFIX} and
|
|
44
|
+
* {@link isClaudePromptRecord}.
|
|
42
45
|
* 4. **`thinking` blocks can be empty.** The block arrives with a `signature`
|
|
43
46
|
* and `thinking: ""` when the text is not retained, so an empty one is
|
|
44
47
|
* skipped rather than rendered as a blank quote.
|
|
@@ -53,13 +56,16 @@
|
|
|
53
56
|
* @module lib/hooks/sources/claude/transcript
|
|
54
57
|
*/
|
|
55
58
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
56
|
-
exports.CLAUDE_HUMAN_PROMPT_SOURCES = exports.CLAUDE_HUMAN_ORIGIN_KIND = exports.CLAUDE_THINKING_LABEL = exports.MAX_CLAUDE_TOOL_DETAIL_LENGTH = exports.MAX_CLAUDE_TURN_BLOCKS = exports.CLAUDE_TURN_TRUNCATION_MARKER = exports.MAX_CLAUDE_TURN_BODY_LENGTH = exports.CLAUDE_PROJECTS_DIR_SEGMENTS = void 0;
|
|
59
|
+
exports.CLAUDE_END_TURN_STOP_REASON = exports.CLAUDE_HUMAN_PROMPT_SOURCES = exports.CLAUDE_HUMAN_ORIGIN_KIND = exports.CLAUDE_SLASH_COMMAND_PREFIX = exports.CLAUDE_THINKING_LABEL = exports.MAX_CLAUDE_TOOL_DETAIL_LENGTH = exports.MAX_CLAUDE_TURN_BLOCKS = exports.CLAUDE_TURN_TRUNCATION_MARKER = exports.MAX_CLAUDE_TURN_BODY_LENGTH = exports.CLAUDE_PROJECTS_DIR_SEGMENTS = void 0;
|
|
57
60
|
exports.claudeProjectSlug = claudeProjectSlug;
|
|
61
|
+
exports.claudeSlashCommandPrompt = claudeSlashCommandPrompt;
|
|
58
62
|
exports.readClaudeContentBlock = readClaudeContentBlock;
|
|
59
63
|
exports.readClaudeTranscriptRecord = readClaudeTranscriptRecord;
|
|
60
64
|
exports.parseClaudeTranscript = parseClaudeTranscript;
|
|
61
65
|
exports.isClaudePromptRecord = isClaudePromptRecord;
|
|
62
66
|
exports.isClaudeOperatorPromptRecord = isClaudeOperatorPromptRecord;
|
|
67
|
+
exports.isClaudeTurnClosingRecord = isClaudeTurnClosingRecord;
|
|
68
|
+
exports.isClaudeTurnWritable = isClaudeTurnWritable;
|
|
63
69
|
exports.createClaudeTurn = createClaudeTurn;
|
|
64
70
|
exports.buildClaudeTurns = buildClaudeTurns;
|
|
65
71
|
exports.renderClaudeTurn = renderClaudeTurn;
|
|
@@ -111,18 +117,97 @@ exports.CLAUDE_THINKING_LABEL = 'Thinking';
|
|
|
111
117
|
/**
|
|
112
118
|
* Prefixes that mark a `type: "user"` record as bookkeeping rather than a prompt.
|
|
113
119
|
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
120
|
+
* Three since Issue #2265, which took `<command-message>` off the list — see
|
|
121
|
+
* {@link CLAUDE_SLASH_COMMAND_PREFIX} for the census that separated the two
|
|
122
|
+
* shapes the `<command-…>` trio is written in. What is left is the bookkeeping
|
|
123
|
+
* of a **built-in** command: `<command-name>` first is how Claude records
|
|
124
|
+
* `/model`, `/compact`, `/clear`, `/login` and `/plan`, `<local-command-stdout>`
|
|
125
|
+
* is that command's output, and `<local-command-caveat>` is the preamble in
|
|
126
|
+
* front of it. Treating any of them as a prompt would open a turn that the agent
|
|
127
|
+
* never answers, and the reply to the *next* real prompt would be filed under it.
|
|
119
128
|
*/
|
|
120
129
|
const CLAUDE_SYNTHETIC_PROMPT_PREFIXES = [
|
|
121
130
|
'<local-command-caveat>',
|
|
122
131
|
'<local-command-stdout>',
|
|
123
132
|
'<command-name>',
|
|
124
|
-
'<command-message>',
|
|
125
133
|
];
|
|
134
|
+
/**
|
|
135
|
+
* The tag a slash command the operator typed opens with (Issue #2265).
|
|
136
|
+
*
|
|
137
|
+
* ## The two shapes, and why the leading tag is the discriminator
|
|
138
|
+
*
|
|
139
|
+
* Claude writes the same three tags for two quite different events, and it
|
|
140
|
+
* writes them in a different **order** for each. Measured over every
|
|
141
|
+
* `type: "user"` record under `~/.claude/projects` on 2026-09-03 whose text
|
|
142
|
+
* opens with one of the `<command-…>` tags — 222 records:
|
|
143
|
+
*
|
|
144
|
+
* | leading tag | count | `<command-name>` values |
|
|
145
|
+
* |---|---|---|
|
|
146
|
+
* | `<command-message>` | 118 | `/orchestrate`, `/release`, `/uat`, `/worktree-cleanup`, `/worktree-new`, `/multi-stage-design-review`, `/cmate-*` |
|
|
147
|
+
* | `<command-name>` | 104 | `/model`, `/compact`, `/clear`, `/login`, `/plan` |
|
|
148
|
+
*
|
|
149
|
+
* The split is exact and it is the split that matters here. The 118 are
|
|
150
|
+
* *project and user commands* — a `.claude/commands/*.md` or a skill, expanded
|
|
151
|
+
* into instructions the agent then works through, which is a turn with a reply
|
|
152
|
+
* in it. The 104 are commands Claude Code executes itself; their whole output is
|
|
153
|
+
* the `<local-command-stdout>` record that follows and the agent never answers
|
|
154
|
+
* them.
|
|
155
|
+
*
|
|
156
|
+
* Before #2265 both were excluded, on the reading that a slash command is not
|
|
157
|
+
* "the operator's text". That cost the 118 their entire turn: the reply was
|
|
158
|
+
* folded into whichever turn came before — already saved, so
|
|
159
|
+
* `selectUnwrittenClaudeTurns` answered "nothing to write", and the answer *this*
|
|
160
|
+
* reader gives is also what suppresses the scraper's copy. The measured loss was
|
|
161
|
+
* a `/release v0.30.1` turn of 7 text blocks and an `/orchestrate` turn of 8,
|
|
162
|
+
* neither of which reached History by any path at all.
|
|
163
|
+
*
|
|
164
|
+
* ## `origin`, and the versions that carry it
|
|
165
|
+
*
|
|
166
|
+
* The 118 also answer {@link isClaudeOperatorPromptRecord}, but only on recent
|
|
167
|
+
* Claude Code: of them, every record from 2.1.238 onwards (46) carries
|
|
168
|
+
* `origin: {"kind": "human"}` and every record before it (72, versions 2.1.220
|
|
169
|
+
* … 2.1.235) carries neither marker — those predate the field. On an old
|
|
170
|
+
* version the turn is still opened and its reply still written; only the `user`
|
|
171
|
+
* row is skipped, which is #2196's stated degradation and not a new one.
|
|
172
|
+
*/
|
|
173
|
+
exports.CLAUDE_SLASH_COMMAND_PREFIX = '<command-message>';
|
|
174
|
+
/** `<command-name>/release</command-name>` — the command itself. */
|
|
175
|
+
const CLAUDE_COMMAND_NAME_PATTERN = /<command-name>([\s\S]*?)<\/command-name>/;
|
|
176
|
+
/** `<command-args>v0.30.1</command-args>` — absent on a bare invocation. */
|
|
177
|
+
const CLAUDE_COMMAND_ARGS_PATTERN = /<command-args>([\s\S]*?)<\/command-args>/;
|
|
178
|
+
/**
|
|
179
|
+
* The line the operator typed, rebuilt from a slash-command record (Issue #2265).
|
|
180
|
+
*
|
|
181
|
+
* `<command-name>` and `<command-args>` back into `/release v0.30.1`, which is
|
|
182
|
+
* the string that was in the composer and — for a command CommandMate sent — the
|
|
183
|
+
* string `sendUserMessage` already wrote to `chat_messages`. That equality is
|
|
184
|
+
* what makes the `/send` row *adoptable* instead of duplicated: `recordUserTurn`
|
|
185
|
+
* compares on normalised content, and the raw record text
|
|
186
|
+
* (`<command-message>release</command-message>\n<command-name>…`) matches nothing
|
|
187
|
+
* a person ever sent.
|
|
188
|
+
*
|
|
189
|
+
* Returns null for anything that is not this shape, including a
|
|
190
|
+
* `<command-message>` record with no `<command-name>` in it. Null is what keeps
|
|
191
|
+
* {@link isClaudePromptRecord} from opening a turn it could not name: a record
|
|
192
|
+
* whose command cannot be read would otherwise reach History as its own XML,
|
|
193
|
+
* which is exactly the class of mistake #2196's positive-evidence rule exists to
|
|
194
|
+
* avoid. No such record was observed — all 118 carried both tags.
|
|
195
|
+
*
|
|
196
|
+
* @param text - The record's text, as {@link ClaudeTranscriptRecord.text} holds it
|
|
197
|
+
* @returns `/name args`, `/name` when there are no arguments, or null
|
|
198
|
+
*/
|
|
199
|
+
function claudeSlashCommandPrompt(text) {
|
|
200
|
+
const trimmed = text.trim();
|
|
201
|
+
if (!trimmed.startsWith(exports.CLAUDE_SLASH_COMMAND_PREFIX))
|
|
202
|
+
return null;
|
|
203
|
+
const name = CLAUDE_COMMAND_NAME_PATTERN.exec(trimmed)?.[1].trim() ?? '';
|
|
204
|
+
if (name.length === 0)
|
|
205
|
+
return null;
|
|
206
|
+
// Absent on a bare invocation, and empty on a command invoked with no
|
|
207
|
+
// arguments; both mean the prompt is the command name on its own.
|
|
208
|
+
const args = CLAUDE_COMMAND_ARGS_PATTERN.exec(trimmed)?.[1].trim() ?? '';
|
|
209
|
+
return args.length === 0 ? name : `${name} ${args}`;
|
|
210
|
+
}
|
|
126
211
|
/**
|
|
127
212
|
* Read one `message.content` element.
|
|
128
213
|
*
|
|
@@ -235,6 +320,7 @@ function readClaudeTranscriptRecord(value) {
|
|
|
235
320
|
isCompactSummary: value.isCompactSummary === true,
|
|
236
321
|
isInterruption: (0, event_mapper_1.readStringField)(value, 'interruptedMessageId') !== null,
|
|
237
322
|
timestampMs: Number.isFinite(parsed) ? parsed : null,
|
|
323
|
+
stopReason: message ? (0, event_mapper_1.readStringField)(message, 'stop_reason') : null,
|
|
238
324
|
blocks,
|
|
239
325
|
text,
|
|
240
326
|
};
|
|
@@ -279,11 +365,19 @@ function parseClaudeTranscript(text) {
|
|
|
279
365
|
*
|
|
280
366
|
* - it carries a `tool_result` block — 18 of the 19 user records in the sampled
|
|
281
367
|
* session were these;
|
|
282
|
-
* - `isMeta` is true — the image placeholders
|
|
283
|
-
*
|
|
284
|
-
*
|
|
368
|
+
* - `isMeta` is true — the image placeholders, the local-command caveat, and
|
|
369
|
+
* the body of the `.claude/commands/*.md` or skill a slash command expands
|
|
370
|
+
* to, which arrives on its own record right behind the one below;
|
|
371
|
+
* - its text opens with one of {@link CLAUDE_SYNTHETIC_PROMPT_PREFIXES} — a
|
|
372
|
+
* built-in command and its output;
|
|
285
373
|
* - it has no text at all, or no `uuid` to name the turn with.
|
|
286
374
|
*
|
|
375
|
+
* A record opening with {@link CLAUDE_SLASH_COMMAND_PREFIX} is the fifth case
|
|
376
|
+
* and the one Issue #2265 changed: it **is** a prompt, because the operator
|
|
377
|
+
* typed `/release v0.30.1` and the agent answers it at length. It qualifies only
|
|
378
|
+
* when {@link claudeSlashCommandPrompt} can actually read the command out of it,
|
|
379
|
+
* so a shape this reader could not name never opens a turn.
|
|
380
|
+
*
|
|
287
381
|
* Everything else is treated as a prompt, including `[Request interrupted by
|
|
288
382
|
* user]`. That is deliberate: an interruption really does end the turn, and the
|
|
289
383
|
* records after it really do belong to whatever came next.
|
|
@@ -300,6 +394,8 @@ function isClaudePromptRecord(record) {
|
|
|
300
394
|
const text = record.text.trim();
|
|
301
395
|
if (text.length === 0)
|
|
302
396
|
return false;
|
|
397
|
+
if (text.startsWith(exports.CLAUDE_SLASH_COMMAND_PREFIX))
|
|
398
|
+
return claudeSlashCommandPrompt(text) !== null;
|
|
303
399
|
return !CLAUDE_SYNTHETIC_PROMPT_PREFIXES.some((prefix) => text.startsWith(prefix));
|
|
304
400
|
}
|
|
305
401
|
/**
|
|
@@ -345,6 +441,14 @@ exports.CLAUDE_HUMAN_PROMPT_SOURCES = ['typed', 'queued'];
|
|
|
345
441
|
* is the pre-#2196 behaviour — an orphaned assistant pair, which is untidy, and
|
|
346
442
|
* not a fabricated user message, which is wrong.
|
|
347
443
|
*
|
|
444
|
+
* Issue #2265 widened what reaches here by one shape — the project-command
|
|
445
|
+
* record `isClaudePromptRecord` now accepts — and the rule holds on it without
|
|
446
|
+
* change: of the 118 such records under `~/.claude/projects` on 2026-09-03, the
|
|
447
|
+
* 46 written by Claude Code 2.1.238 and later carry `origin: {"kind": "human"}`
|
|
448
|
+
* and answer true, and the 72 written before the field existed carry no marker
|
|
449
|
+
* and answer false. The second group loses its `user` row and keeps its reply,
|
|
450
|
+
* which is this function's designed degradation rather than a new gap.
|
|
451
|
+
*
|
|
348
452
|
* `isCompactSummary` and `isInterruption` are then checked explicitly even
|
|
349
453
|
* though nothing observed carries a human marker *and* either flag. They are not
|
|
350
454
|
* redundant by construction: compaction and interruption are both things the
|
|
@@ -362,16 +466,62 @@ function isClaudeOperatorPromptRecord(record) {
|
|
|
362
466
|
return record.originKind === exports.CLAUDE_HUMAN_ORIGIN_KIND;
|
|
363
467
|
return record.promptSource !== null && exports.CLAUDE_HUMAN_PROMPT_SOURCES.includes(record.promptSource);
|
|
364
468
|
}
|
|
469
|
+
/**
|
|
470
|
+
* `message.stop_reason` on the record that ends a reply (Issue #2264).
|
|
471
|
+
*
|
|
472
|
+
* The other value this reader cares about is `tool_use`, which is not named as
|
|
473
|
+
* a constant because nothing branches on it: everything that is not `end_turn`
|
|
474
|
+
* leaves the turn open.
|
|
475
|
+
*/
|
|
476
|
+
exports.CLAUDE_END_TURN_STOP_REASON = 'end_turn';
|
|
477
|
+
/**
|
|
478
|
+
* Whether this assistant record is the one that ended the reply (Issue #2264).
|
|
479
|
+
*
|
|
480
|
+
* `end_turn` **and** prose on the same record. See
|
|
481
|
+
* {@link ClaudeTurnAccumulator.closed} for why neither half alone is the rule,
|
|
482
|
+
* and note that the emptiness test here is the one `renderClaudeTurn` applies to
|
|
483
|
+
* a text block — a block whose `text` trims to nothing is not rendered, so it
|
|
484
|
+
* cannot be what makes a turn look finished either.
|
|
485
|
+
*/
|
|
486
|
+
function isClaudeTurnClosingRecord(record) {
|
|
487
|
+
if (record.type !== 'assistant')
|
|
488
|
+
return false;
|
|
489
|
+
if (record.stopReason !== exports.CLAUDE_END_TURN_STOP_REASON)
|
|
490
|
+
return false;
|
|
491
|
+
return record.blocks.some((block) => block.type === 'text' && (block.text ?? '').trim().length > 0);
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Whether a writer may record this turn (Issue #2264).
|
|
495
|
+
*
|
|
496
|
+
* Either proof that nothing more is coming: the agent said so
|
|
497
|
+
* ({@link ClaudeTurnAccumulator.closed}), or it has moved on to another prompt
|
|
498
|
+
* ({@link ClaudeTurnAccumulator.superseded}).
|
|
499
|
+
*/
|
|
500
|
+
function isClaudeTurnWritable(turn) {
|
|
501
|
+
if (turn.closed || turn.superseded)
|
|
502
|
+
return true;
|
|
503
|
+
// No record of this turn said why it stopped, so there is no evidence to
|
|
504
|
+
// refuse it on. See {@link ClaudeTurnAccumulator.stopReasonObserved}: this is
|
|
505
|
+
// unreachable against every Claude version measured, and it is what keeps a
|
|
506
|
+
// version that stops writing the field from silently disabling the reader.
|
|
507
|
+
return !turn.stopReasonObserved;
|
|
508
|
+
}
|
|
365
509
|
/** A brand-new accumulator for one turn. */
|
|
366
510
|
function createClaudeTurn(record, sessionId) {
|
|
367
511
|
return {
|
|
368
512
|
sessionId: record.sessionId ?? sessionId,
|
|
369
513
|
promptUuid: record.uuid,
|
|
370
514
|
startedAt: record.timestampMs ?? 0,
|
|
371
|
-
|
|
515
|
+
// A slash command's record is XML and the operator typed a line; #2265 puts
|
|
516
|
+
// the line on the turn so that the `user` row reads `/release v0.30.1` and
|
|
517
|
+
// the `/send` row holding those same bytes is adopted rather than doubled.
|
|
518
|
+
promptText: claudeSlashCommandPrompt(record.text) ?? record.text,
|
|
372
519
|
promptIsOperatorInput: isClaudeOperatorPromptRecord(record),
|
|
373
520
|
blocks: [],
|
|
374
521
|
assistantRecords: 0,
|
|
522
|
+
closed: false,
|
|
523
|
+
superseded: false,
|
|
524
|
+
stopReasonObserved: false,
|
|
375
525
|
overflowed: false,
|
|
376
526
|
};
|
|
377
527
|
}
|
|
@@ -416,6 +566,13 @@ function buildClaudeTurns(records, sessionId) {
|
|
|
416
566
|
continue;
|
|
417
567
|
}
|
|
418
568
|
current.assistantRecords += 1;
|
|
569
|
+
// Assigned rather than or-ed, so the value left standing is the *last*
|
|
570
|
+
// assistant record's answer. A turn that ends on a `tool_use` record after
|
|
571
|
+
// an earlier `end_turn` — which is what a turn Claude resumed looks like —
|
|
572
|
+
// is open again, and that is the reading the Issue's census supports.
|
|
573
|
+
current.closed = isClaudeTurnClosingRecord(record);
|
|
574
|
+
if (record.stopReason !== null)
|
|
575
|
+
current.stopReasonObserved = true;
|
|
419
576
|
for (const block of record.blocks) {
|
|
420
577
|
if (current.blocks.length >= exports.MAX_CLAUDE_TURN_BLOCKS) {
|
|
421
578
|
current.overflowed = true;
|
|
@@ -424,6 +581,11 @@ function buildClaudeTurns(records, sessionId) {
|
|
|
424
581
|
current.blocks.push(block);
|
|
425
582
|
}
|
|
426
583
|
}
|
|
584
|
+
// Every turn but the last one has a later prompt behind it, and a prompt is
|
|
585
|
+
// proof the agent moved on. See {@link ClaudeTurnAccumulator.superseded}.
|
|
586
|
+
for (let index = 0; index < turns.length - 1; index += 1) {
|
|
587
|
+
turns[index].superseded = true;
|
|
588
|
+
}
|
|
427
589
|
return { turns, orphanedAssistantRecords, sidechainRecords };
|
|
428
590
|
}
|
|
429
591
|
/**
|
|
@@ -94,6 +94,7 @@ exports.acceptCodexRolloutPath = acceptCodexRolloutPath;
|
|
|
94
94
|
exports.isCodexRolloutFileFor = isCodexRolloutFileFor;
|
|
95
95
|
exports.findCodexRolloutPath = findCodexRolloutPath;
|
|
96
96
|
exports.captureCodexTranscriptTurn = captureCodexTranscriptTurn;
|
|
97
|
+
exports.resolveCodexTranscriptPath = resolveCodexTranscriptPath;
|
|
97
98
|
const promises_1 = require("fs/promises");
|
|
98
99
|
const os_1 = require("os");
|
|
99
100
|
const path_1 = require("path");
|
|
@@ -296,15 +297,21 @@ async function scanForRollout(dir, sessionId, depth) {
|
|
|
296
297
|
return null;
|
|
297
298
|
}
|
|
298
299
|
/**
|
|
299
|
-
* Read
|
|
300
|
+
* Read this instance's unwritten turns out of its rollout and write them.
|
|
300
301
|
*
|
|
301
|
-
* **
|
|
302
|
-
*
|
|
303
|
-
*
|
|
304
|
-
*
|
|
305
|
-
*
|
|
302
|
+
* **Every turn in the window that is not already a row**, oldest first, which is
|
|
303
|
+
* the shape Issue #2246 gave all three pull readers and the reasoning for it is
|
|
304
|
+
* written out once on `../claude/history`'s
|
|
305
|
+
* {@link captureClaudeTranscriptTurn}. The short version: this reader only runs
|
|
306
|
+
* when the poller judges a turn finished, so a missed judgement used to lose a
|
|
307
|
+
* turn permanently — by the next judgement "the newest turn" had moved on.
|
|
306
308
|
*
|
|
307
|
-
*
|
|
309
|
+
* #2121's one-row-per-turn argument is preserved by the **anchor**: the newest
|
|
310
|
+
* turn in the window this reader has already written is where the backfill
|
|
311
|
+
* starts, and a window with no anchor falls back to the newest turn alone.
|
|
312
|
+
*
|
|
313
|
+
* The return value is the poller's instruction, and it is about the **newest**
|
|
314
|
+
* turn whatever else was written on the way. **True** means History holds
|
|
308
315
|
* this turn as the agent's own Markdown and the scrape must be dropped. **False**
|
|
309
316
|
* means it does not, for any reason at all — no session pointer, no file, an
|
|
310
317
|
* unreadable file, a turn codex has not closed yet — and the scrape must be
|
|
@@ -350,8 +357,7 @@ async function captureCodexTranscriptTurn(target, capture = {}) {
|
|
|
350
357
|
return false;
|
|
351
358
|
const parsed = (0, transcript_1.parseCodexRollout)(text);
|
|
352
359
|
const built = (0, transcript_1.buildCodexTurns)(parsed.records, sessionId);
|
|
353
|
-
|
|
354
|
-
if (!turn) {
|
|
360
|
+
if (built.turns.length === 0) {
|
|
355
361
|
logger.info('codex-transcript-no-turn', {
|
|
356
362
|
worktreeId: target.worktreeId,
|
|
357
363
|
instanceId,
|
|
@@ -375,8 +381,33 @@ async function captureCodexTranscriptTurn(target, capture = {}) {
|
|
|
375
381
|
duplicateStreamRecords: built.duplicateStreamRecords,
|
|
376
382
|
});
|
|
377
383
|
}
|
|
378
|
-
const
|
|
379
|
-
|
|
384
|
+
const pending = await selectUnwrittenCodexTurns(target, built.turns);
|
|
385
|
+
if (pending.turns.length === 0) {
|
|
386
|
+
logger.debug('codex-transcript-turns-already-saved', {
|
|
387
|
+
worktreeId: target.worktreeId,
|
|
388
|
+
instanceId,
|
|
389
|
+
turnsInWindow: built.turns.length,
|
|
390
|
+
});
|
|
391
|
+
return true;
|
|
392
|
+
}
|
|
393
|
+
if (pending.turns.length > 1) {
|
|
394
|
+
logger.info('codex-transcript-backfilling-turns', {
|
|
395
|
+
worktreeId: target.worktreeId,
|
|
396
|
+
instanceId,
|
|
397
|
+
path,
|
|
398
|
+
pendingTurns: pending.turns.length,
|
|
399
|
+
turnsInWindow: built.turns.length,
|
|
400
|
+
anchored: pending.anchored,
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
let captured = false;
|
|
404
|
+
for (let index = 0; index < pending.turns.length; index += 1) {
|
|
405
|
+
const turn = pending.turns[index];
|
|
406
|
+
const previousStartedAt = index === 0 ? pending.previousStartedAt : pending.turns[index - 1].startedAt;
|
|
407
|
+
const lastUserRow = await recordCodexUserTurns(target, turn, previousStartedAt);
|
|
408
|
+
captured = await writeCodexTurn(target, turn, (0, transcript_1.renderCodexTurn)(turn), resolveAssistantTimestampMs(turn, lastUserRow), path);
|
|
409
|
+
}
|
|
410
|
+
return captured;
|
|
380
411
|
}
|
|
381
412
|
catch (error) {
|
|
382
413
|
logger.error('codex-transcript-capture-failed', {
|
|
@@ -387,6 +418,58 @@ async function captureCodexTranscriptTurn(target, capture = {}) {
|
|
|
387
418
|
return false;
|
|
388
419
|
}
|
|
389
420
|
}
|
|
421
|
+
/**
|
|
422
|
+
* The rollout file this instance would be read from, or null (Issue #2246).
|
|
423
|
+
*
|
|
424
|
+
* The same two steps {@link captureCodexTranscriptTurn} opens with, asked
|
|
425
|
+
* without reading or writing anything; see `../claude/history`'s
|
|
426
|
+
* {@link resolveClaudeTranscriptPath} for what the Stop receiver does with it.
|
|
427
|
+
*
|
|
428
|
+
* Never throws.
|
|
429
|
+
*/
|
|
430
|
+
async function resolveCodexTranscriptPath(target, capture = {}) {
|
|
431
|
+
try {
|
|
432
|
+
const sessionId = await resolveCodexSessionId(target);
|
|
433
|
+
if (!sessionId)
|
|
434
|
+
return null;
|
|
435
|
+
return await locateCodexRollout(capture.codexHome ?? resolveCodexHome(), sessionId);
|
|
436
|
+
}
|
|
437
|
+
catch {
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* The turns {@link captureCodexTranscriptTurn} still has to write (Issue #2246).
|
|
443
|
+
*
|
|
444
|
+
* The same rule as `../claude/history`'s, on codex's own key: search backwards
|
|
445
|
+
* from the newest turn for one that is already a row, and take everything after
|
|
446
|
+
* it. Record order and never a timestamp, and bounded by the window — a turn
|
|
447
|
+
* whose records fell outside {@link CODEX_TRANSCRIPT_TAIL_BYTES} is not in `turns`.
|
|
448
|
+
*
|
|
449
|
+
* @param turns - Every turn in the window, oldest first
|
|
450
|
+
*/
|
|
451
|
+
async function selectUnwrittenCodexTurns(target, turns) {
|
|
452
|
+
const [{ getDbInstance }, { findMessageByRequestId }] = await Promise.all([
|
|
453
|
+
Promise.resolve().then(() => __importStar(require('../../../../lib/db/db-instance'))),
|
|
454
|
+
Promise.resolve().then(() => __importStar(require('../../../../lib/db'))),
|
|
455
|
+
]);
|
|
456
|
+
const db = getDbInstance();
|
|
457
|
+
for (let index = turns.length - 1; index >= 0; index -= 1) {
|
|
458
|
+
const requestId = (0, agent_transcript_1.codexTurnRequestId)(turns[index].turnId);
|
|
459
|
+
if (!findMessageByRequestId(db, target.worktreeId, requestId))
|
|
460
|
+
continue;
|
|
461
|
+
return {
|
|
462
|
+
turns: turns.slice(index + 1),
|
|
463
|
+
previousStartedAt: turns[index].startedAt,
|
|
464
|
+
anchored: true,
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
return {
|
|
468
|
+
turns: turns.slice(-1),
|
|
469
|
+
previousStartedAt: turns.length > 1 ? turns[turns.length - 2].startedAt : 0,
|
|
470
|
+
anchored: false,
|
|
471
|
+
};
|
|
472
|
+
}
|
|
390
473
|
/**
|
|
391
474
|
* The rollout file for this session, or null.
|
|
392
475
|
*
|
|
@@ -435,11 +518,15 @@ async function isReadableFile(path) {
|
|
|
435
518
|
*
|
|
436
519
|
* @returns The last row written or found, for the timestamp
|
|
437
520
|
*/
|
|
438
|
-
async function recordCodexUserTurns(target, turn) {
|
|
521
|
+
async function recordCodexUserTurns(target, turn, previousStartedAt = 0) {
|
|
439
522
|
const instanceId = target.instanceId ?? target.cliToolId;
|
|
440
523
|
let last = { outcome: 'skipped', messageId: null, timestampMs: null };
|
|
524
|
+
// Issue #2246: a turn read late is a turn whose `/send` row may be older than
|
|
525
|
+
// #2196's symmetric window reaches. The previous turn's start is the tightest
|
|
526
|
+
// honest bound, and it only ever widens the search.
|
|
527
|
+
const adoption = previousStartedAt > 0 ? { adoptionFromMs: previousStartedAt } : {};
|
|
441
528
|
for (const prompt of turn.prompts) {
|
|
442
|
-
const recorded = await (0, user_turn_recorder_1.recordUserTurn)(target, (0, agent_transcript_1.codexPromptRequestId)(prompt.itemId), prompt.text, prompt.timestampMs);
|
|
529
|
+
const recorded = await (0, user_turn_recorder_1.recordUserTurn)(target, (0, agent_transcript_1.codexPromptRequestId)(prompt.itemId), prompt.text, prompt.timestampMs, adoption);
|
|
443
530
|
if (recorded.outcome === 'failed') {
|
|
444
531
|
logger.warn('codex-transcript-user-turn-failed', {
|
|
445
532
|
worktreeId: target.worktreeId,
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Reading the transcript when the agent says it stopped (Issue #2246).
|
|
4
|
+
*
|
|
5
|
+
* ## Why the poller was not enough
|
|
6
|
+
*
|
|
7
|
+
* `lib/polling/structured-history-gate` is asked to record a turn at exactly one
|
|
8
|
+
* moment: the poll on which the scraper decided the turn had finished and was
|
|
9
|
+
* about to save its own copy. That is the right moment — doing the handover at
|
|
10
|
+
* the point of the write is what makes it impossible for both writers to run —
|
|
11
|
+
* but it is the *only* moment, and the decision behind it is a string analysis
|
|
12
|
+
* of a terminal frame.
|
|
13
|
+
*
|
|
14
|
+
* When that analysis misses one completion the cost is not a delay. By the time
|
|
15
|
+
* the next completion is judged, the newest turn is the next one; before #2246
|
|
16
|
+
* the reader wrote only that, so the missed turn belonged to nobody and stayed
|
|
17
|
+
* missing. One misjudged frame, one turn gone (measured 2026-09-02: a reply the
|
|
18
|
+
* transcript held in full and History never showed).
|
|
19
|
+
*
|
|
20
|
+
* The agent itself knows the boundary exactly, and has been telling us: the
|
|
21
|
+
* `stop` hook. This module is that second trigger.
|
|
22
|
+
*
|
|
23
|
+
* ## Knowing the boundary is not the same as the file having reached it
|
|
24
|
+
*
|
|
25
|
+
* The `stop` post and the transcript's last append are two events with no
|
|
26
|
+
* ordering between them, and Issue #2264 is what happens when the reader is
|
|
27
|
+
* asked in the gap. The reader now refuses a turn it cannot prove is closed, so
|
|
28
|
+
* the cost of asking too early is a `false` rather than a truncated row — and
|
|
29
|
+
* this module's answer to a `false` is to ask again, up to
|
|
30
|
+
* {@link STOP_TRANSCRIPT_MAX_ATTEMPTS} times, before handing the turn back.
|
|
31
|
+
*
|
|
32
|
+
* ## What it does not do
|
|
33
|
+
*
|
|
34
|
+
* **It does not replace the poller's call.** A session with no hooks — or with
|
|
35
|
+
* hooks the operator never wired up — sends no `stop`, and for it the poller is
|
|
36
|
+
* still the only trigger there is. Two triggers for one turn cost nothing
|
|
37
|
+
* because the readers are idempotent and the gate serialises them per instance;
|
|
38
|
+
* one trigger that is sometimes absent costs a reply.
|
|
39
|
+
*
|
|
40
|
+
* **It does not decide anything about the turn.** The whole judgement stays in
|
|
41
|
+
* the gate and the readers, so the two entry points cannot drift.
|
|
42
|
+
*
|
|
43
|
+
* Never throws: a hook post is fire-and-forget from a CLI's stop handler, and a
|
|
44
|
+
* failure here must not become the agent's problem.
|
|
45
|
+
*
|
|
46
|
+
* @module lib/hooks/stop-history-capture
|
|
47
|
+
*/
|
|
48
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
49
|
+
if (k2 === undefined) k2 = k;
|
|
50
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
51
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
52
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
53
|
+
}
|
|
54
|
+
Object.defineProperty(o, k2, desc);
|
|
55
|
+
}) : (function(o, m, k, k2) {
|
|
56
|
+
if (k2 === undefined) k2 = k;
|
|
57
|
+
o[k2] = m[k];
|
|
58
|
+
}));
|
|
59
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
60
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
61
|
+
}) : function(o, v) {
|
|
62
|
+
o["default"] = v;
|
|
63
|
+
});
|
|
64
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
65
|
+
var ownKeys = function(o) {
|
|
66
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
67
|
+
var ar = [];
|
|
68
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
69
|
+
return ar;
|
|
70
|
+
};
|
|
71
|
+
return ownKeys(o);
|
|
72
|
+
};
|
|
73
|
+
return function (mod) {
|
|
74
|
+
if (mod && mod.__esModule) return mod;
|
|
75
|
+
var result = {};
|
|
76
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
77
|
+
__setModuleDefault(result, mod);
|
|
78
|
+
return result;
|
|
79
|
+
};
|
|
80
|
+
})();
|
|
81
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
82
|
+
exports.STOP_TRANSCRIPT_MAX_ATTEMPTS = exports.STOP_TRANSCRIPT_RETRY_DELAY_MS = void 0;
|
|
83
|
+
exports.captureTranscriptTurnOnStop = captureTranscriptTurnOnStop;
|
|
84
|
+
const logger_1 = require("../../lib/logger");
|
|
85
|
+
const logger = (0, logger_1.createLogger)('lib/hooks/stop-history-capture');
|
|
86
|
+
/**
|
|
87
|
+
* How long to wait before asking again.
|
|
88
|
+
*
|
|
89
|
+
* The `stop` hook fires when the agent considers the turn over, and the last
|
|
90
|
+
* assistant record is appended to the transcript around — not necessarily
|
|
91
|
+
* before — that instant. A reader that arrives in the gap sees a turn that is
|
|
92
|
+
* not closed, answers false (correctly: the row it would write is a reply with
|
|
93
|
+
* its last paragraph missing, frozen under the turn key forever) and, without a
|
|
94
|
+
* retry, hands a turn the agent has definitely finished back to the scraper.
|
|
95
|
+
*
|
|
96
|
+
* Half a second is chosen against what the gap actually is — a file append that
|
|
97
|
+
* has already been issued — rather than against a timeout.
|
|
98
|
+
*/
|
|
99
|
+
exports.STOP_TRANSCRIPT_RETRY_DELAY_MS = 500;
|
|
100
|
+
/**
|
|
101
|
+
* How many times the reader is asked in total (Issue #2264).
|
|
102
|
+
*
|
|
103
|
+
* #2246 asked twice, on the reading that the only thing a retry waits for is one
|
|
104
|
+
* append that has already been issued. The incident #2264 was reported for says
|
|
105
|
+
* that is not always one append: the measured gap between the `stop` post and
|
|
106
|
+
* the last `text` record was around 100 ms in the fast case, but the turns that
|
|
107
|
+
* were saved short had *tool* records still arriving, and a single 500 ms retry
|
|
108
|
+
* lands inside that run rather than after it.
|
|
109
|
+
*
|
|
110
|
+
* Three and not a loop, because the poller's own trigger is still behind this:
|
|
111
|
+
* if the turn is still open after the third ask, the next poll is the right
|
|
112
|
+
* place to notice — it runs when the pane returns to the composer, by which time
|
|
113
|
+
* the transcript is written — and a hook handler that waits on a file is a hook
|
|
114
|
+
* handler that can hang a turn. The worst case this bounds is 1 s of the agent's
|
|
115
|
+
* stop path, spent only on an instance that has a transcript and an open turn.
|
|
116
|
+
*/
|
|
117
|
+
exports.STOP_TRANSCRIPT_MAX_ATTEMPTS = 3;
|
|
118
|
+
function sleep(ms) {
|
|
119
|
+
return new Promise((resolve) => {
|
|
120
|
+
setTimeout(resolve, ms);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Ask the pull-mode reader to record this instance's turn, now.
|
|
125
|
+
*
|
|
126
|
+
* The tool question is asked first and without doing any work
|
|
127
|
+
* (`isPullTranscriptHistory`), so a `stop` from gemini, copilot or opencode
|
|
128
|
+
* costs one map lookup and returns — in particular it never waits out the
|
|
129
|
+
* retry delay. opencode has a second writer too, but a *push* one: its
|
|
130
|
+
* subscription has already received the reply and there is nothing to pull.
|
|
131
|
+
*
|
|
132
|
+
* The import is dynamic so that the poller's module graph — which reaches every
|
|
133
|
+
* transcript reader and the whole opencode client — does not become a static
|
|
134
|
+
* dependency of the hook receiver.
|
|
135
|
+
*
|
|
136
|
+
* @param worktree - The worktree the event resolved to
|
|
137
|
+
* @param cliToolId - The tool that sent the event
|
|
138
|
+
* @param instanceId - The already-resolved instance id
|
|
139
|
+
* @returns Whether History now holds this instance's newest turn as Markdown
|
|
140
|
+
*/
|
|
141
|
+
async function captureTranscriptTurnOnStop(worktree, cliToolId, instanceId, options = {}) {
|
|
142
|
+
try {
|
|
143
|
+
const { captureStructuredHistoryTurn, hasStructuredHistoryTranscript, isPullTranscriptHistory } = await Promise.resolve().then(() => __importStar(require('../../lib/polling/structured-history-gate')));
|
|
144
|
+
if (!isPullTranscriptHistory(cliToolId))
|
|
145
|
+
return false;
|
|
146
|
+
// No `transcriptPathHint`: that value is a line the *pane* printed, and the
|
|
147
|
+
// hook receiver has not read the pane. A tool whose transcript can only be
|
|
148
|
+
// found that way has no session pointer either, and therefore sends no stop
|
|
149
|
+
// event this path could act on.
|
|
150
|
+
const capture = { worktreePath: worktree.path, transcriptPathHint: null };
|
|
151
|
+
if (await captureStructuredHistoryTurn(worktree.id, cliToolId, instanceId, capture)) {
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
const retryDelayMs = options.retryDelayMs ?? exports.STOP_TRANSCRIPT_RETRY_DELAY_MS;
|
|
155
|
+
if (retryDelayMs <= 0)
|
|
156
|
+
return false;
|
|
157
|
+
// The retry is for one failure and one only: the turn is not closed yet
|
|
158
|
+
// because its remaining assistant records have not been appended. Every
|
|
159
|
+
// other reason the capture answers false — no session pointer, no
|
|
160
|
+
// transcript, the operator never wired hooks for this tool — is a reason
|
|
161
|
+
// waiting cannot fix, and this handler is on the agent's own stop path, so
|
|
162
|
+
// half a second spent for nothing is half a second added to every turn it is
|
|
163
|
+
// spent on. Asked once, before the first sleep: a transcript that exists
|
|
164
|
+
// does not stop existing between attempts.
|
|
165
|
+
if (!(await hasStructuredHistoryTranscript(worktree.id, cliToolId, instanceId, capture))) {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
const maxAttempts = Math.max(1, options.maxAttempts ?? exports.STOP_TRANSCRIPT_MAX_ATTEMPTS);
|
|
169
|
+
for (let attempt = 2; attempt <= maxAttempts; attempt += 1) {
|
|
170
|
+
await sleep(retryDelayMs);
|
|
171
|
+
const captured = await captureStructuredHistoryTurn(worktree.id, cliToolId, instanceId, capture);
|
|
172
|
+
logger.debug('stop-history-capture-retried', {
|
|
173
|
+
worktreeId: worktree.id,
|
|
174
|
+
cliToolId,
|
|
175
|
+
instanceId,
|
|
176
|
+
attempt,
|
|
177
|
+
maxAttempts,
|
|
178
|
+
captured,
|
|
179
|
+
});
|
|
180
|
+
if (captured)
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
logger.warn('stop-history-capture-failed', {
|
|
187
|
+
worktreeId: worktree.id,
|
|
188
|
+
cliToolId,
|
|
189
|
+
instanceId,
|
|
190
|
+
error: error instanceof Error ? error.message : String(error),
|
|
191
|
+
});
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
}
|