commandmate 0.30.0 → 0.30.1
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 +149 -149
- package/.next/app-path-routes-manifest.json +28 -28
- 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 +28 -28
- 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/server-reference-manifest.json +1 -1
- package/.next/static/chunks/{6381-28d0e95620aa018e.js → 6381-dbf04a99647f6018.js} +1 -1
- package/.next/static/chunks/app/worktrees/[id]/page-aac399392ec8c273.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 +101 -13
- package/dist/server/src/lib/hooks/sources/claude/history.js +161 -18
- package/dist/server/src/lib/hooks/sources/codex/history.js +100 -13
- package/dist/server/src/lib/hooks/stop-history-capture.js +161 -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 → pcpB4K6KreMzu2qbhvuRY}/_buildManifest.js +0 -0
- /package/.next/static/{7K0XXFu_ty4wRSkl4X21E → pcpB4K6KreMzu2qbhvuRY}/_ssgManifest.js +0 -0
|
@@ -148,8 +148,9 @@ function describe(target) {
|
|
|
148
148
|
* re-read of the log and a `/clear`.
|
|
149
149
|
* @param content - The prompt, as the agent recorded it
|
|
150
150
|
* @param timestampMs - When the agent says it was prompted, epoch ms
|
|
151
|
+
* @param options - See {@link RecordUserTurnOptions}; the defaults are #2196's
|
|
151
152
|
*/
|
|
152
|
-
async function recordUserTurn(target, key, content, timestampMs) {
|
|
153
|
+
async function recordUserTurn(target, key, content, timestampMs, options = {}) {
|
|
153
154
|
const instanceId = target.instanceId ?? target.cliToolId;
|
|
154
155
|
const normalized = normalizeUserTurnContent(content);
|
|
155
156
|
if (key.length === 0 || normalized.length === 0) {
|
|
@@ -171,7 +172,7 @@ async function recordUserTurn(target, key, content, timestampMs) {
|
|
|
171
172
|
};
|
|
172
173
|
}
|
|
173
174
|
const at = timestampMs > 0 ? timestampMs : Date.now();
|
|
174
|
-
const adopted = adoptExistingRow(chatDb, db, target, instanceId, key, normalized, at);
|
|
175
|
+
const adopted = adoptExistingRow(chatDb, db, target, instanceId, key, normalized, at, options);
|
|
175
176
|
if (adopted)
|
|
176
177
|
return adopted;
|
|
177
178
|
const message = chatDb.createMessage(db, {
|
|
@@ -217,13 +218,17 @@ async function recordUserTurn(target, key, content, timestampMs) {
|
|
|
217
218
|
*
|
|
218
219
|
* @returns The claimed row, or null when nothing here belongs to this prompt
|
|
219
220
|
*/
|
|
220
|
-
function adoptExistingRow(chatDb, db, target, instanceId, key, normalized, at) {
|
|
221
|
+
function adoptExistingRow(chatDb, db, target, instanceId, key, normalized, at, options) {
|
|
222
|
+
// `Math.min` and not a replacement: {@link RecordUserTurnOptions} may widen
|
|
223
|
+
// the search backwards and may never narrow it, so a caller that supplies a
|
|
224
|
+
// bound *inside* the standard window changes nothing.
|
|
225
|
+
const fromMs = Math.min(at - exports.USER_TURN_ADOPTION_WINDOW_MS, options.adoptionFromMs ?? Number.POSITIVE_INFINITY);
|
|
221
226
|
const candidates = chatDb
|
|
222
227
|
.findUnkeyedUserMessages(db, {
|
|
223
228
|
worktreeId: target.worktreeId,
|
|
224
229
|
cliToolId: target.cliToolId,
|
|
225
230
|
instanceId,
|
|
226
|
-
fromMs
|
|
231
|
+
fromMs,
|
|
227
232
|
toMs: at + exports.USER_TURN_ADOPTION_WINDOW_MS,
|
|
228
233
|
})
|
|
229
234
|
.filter((row) => normalizeUserTurnContent(row.content) === normalized)
|
|
@@ -30,6 +30,7 @@ const path_validator_1 = require("../../lib/security/path-validator");
|
|
|
30
30
|
const task_transition_service_1 = require("../../lib/tasks/task-transition-service");
|
|
31
31
|
const gate_runner_1 = require("../../lib/verification/gate-runner");
|
|
32
32
|
const agent_event_state_1 = require("../../lib/session/agent-event-state");
|
|
33
|
+
const stop_history_capture_1 = require("../../lib/hooks/stop-history-capture");
|
|
33
34
|
const logger_1 = require("../../lib/logger");
|
|
34
35
|
const logger = (0, logger_1.createLogger)('lib/hooks/agent-event-service');
|
|
35
36
|
/**
|
|
@@ -144,14 +145,32 @@ function safeRealpath(target) {
|
|
|
144
145
|
* Apply a `stop` event to whatever the worktree is currently doing.
|
|
145
146
|
*
|
|
146
147
|
* The no-op is the load-bearing case. Sessions with no contract, and therefore
|
|
147
|
-
* no task, are the overwhelming majority; for them this records the timestamp
|
|
148
|
-
*
|
|
148
|
+
* no task, are the overwhelming majority; for them this records the timestamp,
|
|
149
|
+
* asks the transcript reader for the turn, and returns.
|
|
150
|
+
*
|
|
151
|
+
* ## Why the history read is here (Issue #2246)
|
|
152
|
+
*
|
|
153
|
+
* Because it must happen for *every* stop event and not only for the ones that
|
|
154
|
+
* govern a task — the majority case above is exactly the session whose replies
|
|
155
|
+
* this is trying not to lose — and because both callers of this function are
|
|
156
|
+
* stop receivers (`/api/hooks/agent-event` and the compatibility
|
|
157
|
+
* `/api/hooks/claude-done`). Putting it in one of the routes would have left the
|
|
158
|
+
* other one on the old behaviour.
|
|
159
|
+
*
|
|
160
|
+
* It runs **before** the task lookup so that the two early returns below cannot
|
|
161
|
+
* skip it, and after `recordAgentStopEvent` because the reader resolves its
|
|
162
|
+
* transcript through the session pointer that call refreshes.
|
|
163
|
+
*
|
|
164
|
+
* It is awaited rather than detached. The turn has already ended, so the cost is
|
|
165
|
+
* paid by nobody who is waiting for an answer, and a detached promise here would
|
|
166
|
+
* be one whose failures nothing observes.
|
|
149
167
|
*
|
|
150
168
|
* Never throws. A hook is fire-and-forget from a CLI's stop handler, and a
|
|
151
169
|
* failure here must not become the agent's problem.
|
|
152
170
|
*/
|
|
153
171
|
async function applyAgentStopEvent(db, worktree, cliToolId, instanceId) {
|
|
154
172
|
(0, agent_event_state_1.recordAgentStopEvent)(worktree.id, cliToolId, instanceId);
|
|
173
|
+
const structuredHistoryCaptured = await (0, stop_history_capture_1.captureTranscriptTurnOnStop)(worktree, cliToolId, instanceId);
|
|
155
174
|
let task;
|
|
156
175
|
try {
|
|
157
176
|
task = (0, tasks_db_1.getActiveTaskForInstance)(db, worktree.id, cliToolId, instanceId);
|
|
@@ -161,10 +180,20 @@ async function applyAgentStopEvent(db, worktree, cliToolId, instanceId) {
|
|
|
161
180
|
worktreeId: worktree.id,
|
|
162
181
|
error: error instanceof Error ? error.message : String(error),
|
|
163
182
|
});
|
|
164
|
-
return {
|
|
183
|
+
return {
|
|
184
|
+
taskId: null,
|
|
185
|
+
taskEventApplied: false,
|
|
186
|
+
verificationRunId: null,
|
|
187
|
+
structuredHistoryCaptured,
|
|
188
|
+
};
|
|
165
189
|
}
|
|
166
190
|
if (!task) {
|
|
167
|
-
return {
|
|
191
|
+
return {
|
|
192
|
+
taskId: null,
|
|
193
|
+
taskEventApplied: false,
|
|
194
|
+
verificationRunId: null,
|
|
195
|
+
structuredHistoryCaptured,
|
|
196
|
+
};
|
|
168
197
|
}
|
|
169
198
|
let taskEventApplied = false;
|
|
170
199
|
try {
|
|
@@ -178,7 +207,12 @@ async function applyAgentStopEvent(db, worktree, cliToolId, instanceId) {
|
|
|
178
207
|
});
|
|
179
208
|
}
|
|
180
209
|
if (!task.contract.success.autoVerifyOnStop) {
|
|
181
|
-
return {
|
|
210
|
+
return {
|
|
211
|
+
taskId: task.id,
|
|
212
|
+
taskEventApplied,
|
|
213
|
+
verificationRunId: null,
|
|
214
|
+
structuredHistoryCaptured,
|
|
215
|
+
};
|
|
182
216
|
}
|
|
183
217
|
let verificationRunId = null;
|
|
184
218
|
try {
|
|
@@ -201,5 +235,5 @@ async function applyAgentStopEvent(db, worktree, cliToolId, instanceId) {
|
|
|
201
235
|
});
|
|
202
236
|
}
|
|
203
237
|
}
|
|
204
|
-
return { taskId: task.id, taskEventApplied, verificationRunId };
|
|
238
|
+
return { taskId: task.id, taskEventApplied, verificationRunId, structuredHistoryCaptured };
|
|
205
239
|
}
|
|
@@ -99,6 +99,7 @@ exports.antigravityBrainRoot = antigravityBrainRoot;
|
|
|
99
99
|
exports.antigravityTranscriptPath = antigravityTranscriptPath;
|
|
100
100
|
exports.acceptAntigravityTranscriptPath = acceptAntigravityTranscriptPath;
|
|
101
101
|
exports.captureAntigravityTranscriptTurn = captureAntigravityTranscriptTurn;
|
|
102
|
+
exports.resolveAntigravityTranscriptPath = resolveAntigravityTranscriptPath;
|
|
102
103
|
const promises_1 = require("fs/promises");
|
|
103
104
|
const os_1 = require("os");
|
|
104
105
|
const path_1 = require("path");
|
|
@@ -256,15 +257,21 @@ function acceptAntigravityTranscriptPath(agyHome, candidate) {
|
|
|
256
257
|
return resolved;
|
|
257
258
|
}
|
|
258
259
|
/**
|
|
259
|
-
* Read
|
|
260
|
+
* Read this instance's unwritten turns out of its transcript and write them.
|
|
260
261
|
*
|
|
261
|
-
* **
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
*
|
|
265
|
-
*
|
|
262
|
+
* **Every turn in the window that is not already a row**, oldest first, which is
|
|
263
|
+
* the shape Issue #2246 gave all three pull readers and the reasoning for it is
|
|
264
|
+
* written out once on `../claude/history`'s
|
|
265
|
+
* {@link captureClaudeTranscriptTurn}. The short version: this reader only runs
|
|
266
|
+
* when the poller judges a turn finished, so a missed judgement used to lose a
|
|
267
|
+
* turn permanently — by the next judgement "the newest turn" had moved on.
|
|
266
268
|
*
|
|
267
|
-
*
|
|
269
|
+
* #2121's one-row-per-turn argument is preserved by the **anchor**: the newest
|
|
270
|
+
* turn in the window this reader has already written is where the backfill
|
|
271
|
+
* starts, and a window with no anchor falls back to the newest turn alone.
|
|
272
|
+
*
|
|
273
|
+
* The return value is the poller's instruction, and it is about the **newest**
|
|
274
|
+
* turn whatever else was written on the way. **True** means History holds
|
|
268
275
|
* this turn as the agent's own Markdown and the scrape must be dropped. **False**
|
|
269
276
|
* means it does not, for any reason at all — no conversation pointer, no file, an
|
|
270
277
|
* unreadable file, a window with no prompt in it — and the scrape must be saved.
|
|
@@ -313,8 +320,7 @@ async function captureAntigravityTranscriptTurn(target, capture = {}) {
|
|
|
313
320
|
return false;
|
|
314
321
|
const parsed = (0, transcript_1.parseAntigravityTranscript)(text);
|
|
315
322
|
const built = (0, transcript_1.buildAntigravityTurns)(parsed.records, conversationId);
|
|
316
|
-
|
|
317
|
-
if (!turn) {
|
|
323
|
+
if (built.turns.length === 0) {
|
|
318
324
|
// No `USER_INPUT` in the window. Two ordinary causes, both fail-open: the
|
|
319
325
|
// 4 MiB window cut mid-conversation, and `transcript_full.jsonl` can hold
|
|
320
326
|
// less than the whole history — one of the 41 files in the corpus held a
|
|
@@ -341,8 +347,33 @@ async function captureAntigravityTranscriptTurn(target, capture = {}) {
|
|
|
341
347
|
preludeRecords: built.preludeRecords,
|
|
342
348
|
});
|
|
343
349
|
}
|
|
344
|
-
const
|
|
345
|
-
|
|
350
|
+
const pending = await selectUnwrittenAntigravityTurns(target, built.turns);
|
|
351
|
+
if (pending.turns.length === 0) {
|
|
352
|
+
logger.debug('antigravity-transcript-turns-already-saved', {
|
|
353
|
+
worktreeId: target.worktreeId,
|
|
354
|
+
instanceId,
|
|
355
|
+
turnsInWindow: built.turns.length,
|
|
356
|
+
});
|
|
357
|
+
return true;
|
|
358
|
+
}
|
|
359
|
+
if (pending.turns.length > 1) {
|
|
360
|
+
logger.info('antigravity-transcript-backfilling-turns', {
|
|
361
|
+
worktreeId: target.worktreeId,
|
|
362
|
+
instanceId,
|
|
363
|
+
path,
|
|
364
|
+
pendingTurns: pending.turns.length,
|
|
365
|
+
turnsInWindow: built.turns.length,
|
|
366
|
+
anchored: pending.anchored,
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
let captured = false;
|
|
370
|
+
for (let index = 0; index < pending.turns.length; index += 1) {
|
|
371
|
+
const turn = pending.turns[index];
|
|
372
|
+
const previousStartedAt = index === 0 ? pending.previousStartedAt : pending.turns[index - 1].startedAt;
|
|
373
|
+
const userRow = await recordAntigravityUserTurn(target, turn, previousStartedAt);
|
|
374
|
+
captured = await writeAntigravityTurn(target, turn, (0, transcript_1.renderAntigravityTurn)(turn), resolveAssistantTimestampMs(turn, userRow), path);
|
|
375
|
+
}
|
|
376
|
+
return captured;
|
|
346
377
|
}
|
|
347
378
|
catch (error) {
|
|
348
379
|
logger.error('antigravity-transcript-capture-failed', {
|
|
@@ -353,6 +384,59 @@ async function captureAntigravityTranscriptTurn(target, capture = {}) {
|
|
|
353
384
|
return false;
|
|
354
385
|
}
|
|
355
386
|
}
|
|
387
|
+
/**
|
|
388
|
+
* The transcript file this instance would be read from, or null (Issue #2246).
|
|
389
|
+
*
|
|
390
|
+
* The same two steps {@link captureAntigravityTranscriptTurn} opens with, asked
|
|
391
|
+
* without reading or writing anything; see `../claude/history`'s
|
|
392
|
+
* {@link resolveClaudeTranscriptPath} for what the Stop receiver does with it.
|
|
393
|
+
*
|
|
394
|
+
* Never throws.
|
|
395
|
+
*/
|
|
396
|
+
async function resolveAntigravityTranscriptPath(target, capture = {}) {
|
|
397
|
+
try {
|
|
398
|
+
const conversationId = await resolveAntigravityConversationId(target);
|
|
399
|
+
if (!conversationId)
|
|
400
|
+
return null;
|
|
401
|
+
return await locateAntigravityTranscript(capture.antigravityHome ?? resolveAntigravityHome(), conversationId);
|
|
402
|
+
}
|
|
403
|
+
catch {
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* The turns {@link captureAntigravityTranscriptTurn} still has to write (#2246).
|
|
409
|
+
*
|
|
410
|
+
* The same rule as `../claude/history`'s, on agy's own key: search backwards
|
|
411
|
+
* from the newest turn for one that is already a row, and take everything after
|
|
412
|
+
* it. Record order and never a timestamp — agy stamps `created_at` at
|
|
413
|
+
* second resolution, so a turn and its own reply routinely share an instant.
|
|
414
|
+
*
|
|
415
|
+
* @param turns - Every turn in the window, oldest first
|
|
416
|
+
*/
|
|
417
|
+
async function selectUnwrittenAntigravityTurns(target, turns) {
|
|
418
|
+
const [{ getDbInstance }, { findMessageByRequestId }] = await Promise.all([
|
|
419
|
+
Promise.resolve().then(() => __importStar(require('../../../../lib/db/db-instance'))),
|
|
420
|
+
Promise.resolve().then(() => __importStar(require('../../../../lib/db'))),
|
|
421
|
+
]);
|
|
422
|
+
const db = getDbInstance();
|
|
423
|
+
for (let index = turns.length - 1; index >= 0; index -= 1) {
|
|
424
|
+
const turn = turns[index];
|
|
425
|
+
const requestId = (0, agent_transcript_1.antigravityTurnRequestId)(turn.conversationId, turn.stepIndex);
|
|
426
|
+
if (!findMessageByRequestId(db, target.worktreeId, requestId))
|
|
427
|
+
continue;
|
|
428
|
+
return {
|
|
429
|
+
turns: turns.slice(index + 1),
|
|
430
|
+
previousStartedAt: turn.startedAt,
|
|
431
|
+
anchored: true,
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
return {
|
|
435
|
+
turns: turns.slice(-1),
|
|
436
|
+
previousStartedAt: turns.length > 1 ? turns[turns.length - 2].startedAt : 0,
|
|
437
|
+
anchored: false,
|
|
438
|
+
};
|
|
439
|
+
}
|
|
356
440
|
/**
|
|
357
441
|
* The transcript file for this conversation, or null.
|
|
358
442
|
*
|
|
@@ -392,12 +476,16 @@ async function isReadableFile(path) {
|
|
|
392
476
|
*
|
|
393
477
|
* Never throws; `recordUserTurn` reports its failures in the return value.
|
|
394
478
|
*/
|
|
395
|
-
async function recordAntigravityUserTurn(target, turn) {
|
|
479
|
+
async function recordAntigravityUserTurn(target, turn, previousStartedAt = 0) {
|
|
396
480
|
const skipped = { outcome: 'skipped', messageId: null, timestampMs: null };
|
|
397
481
|
const prompt = turn.prompt;
|
|
398
482
|
if (!prompt)
|
|
399
483
|
return skipped;
|
|
400
|
-
|
|
484
|
+
// Issue #2246: a turn read late is a turn whose `/send` row may be older than
|
|
485
|
+
// #2196's symmetric window reaches. The previous turn's start is the tightest
|
|
486
|
+
// honest bound, and it only ever widens the search.
|
|
487
|
+
const adoption = previousStartedAt > 0 ? { adoptionFromMs: previousStartedAt } : {};
|
|
488
|
+
const recorded = await (0, user_turn_recorder_1.recordUserTurn)(target, (0, agent_transcript_1.antigravityPromptRequestId)(turn.conversationId, prompt.stepIndex), prompt.text, prompt.timestampMs, adoption);
|
|
401
489
|
if (recorded.outcome === 'failed') {
|
|
402
490
|
logger.warn('antigravity-transcript-user-turn-failed', {
|
|
403
491
|
worktreeId: target.worktreeId,
|
|
@@ -97,6 +97,7 @@ exports.claudeProjectsRoot = claudeProjectsRoot;
|
|
|
97
97
|
exports.claudeTranscriptPath = claudeTranscriptPath;
|
|
98
98
|
exports.acceptClaudeTranscriptHint = acceptClaudeTranscriptHint;
|
|
99
99
|
exports.captureClaudeTranscriptTurn = captureClaudeTranscriptTurn;
|
|
100
|
+
exports.resolveClaudeTranscriptPath = resolveClaudeTranscriptPath;
|
|
100
101
|
exports.readClaudeTurnProgress = readClaudeTurnProgress;
|
|
101
102
|
const promises_1 = require("fs/promises");
|
|
102
103
|
const os_1 = require("os");
|
|
@@ -113,8 +114,9 @@ const logger = (0, logger_1.createLogger)('lib/hooks/sources/claude/history');
|
|
|
113
114
|
* The file grows for the life of the session and a long one is tens of
|
|
114
115
|
* megabytes — the largest on this machine on 2026-08-31 was 23 MB — so reading
|
|
115
116
|
* it whole on every finished turn would be the most expensive thing the poller
|
|
116
|
-
* does. Only
|
|
117
|
-
*
|
|
117
|
+
* does. Only turns whose prompt record is inside the window are ever written
|
|
118
|
+
* (see {@link captureClaudeTranscriptTurn}), which is also what stops a turn the
|
|
119
|
+
* window opened halfway through being written from its middle.
|
|
118
120
|
*
|
|
119
121
|
* 4 MiB is roughly two orders of magnitude above the measured size of a single
|
|
120
122
|
* turn and still small enough to read and parse in one tick. A turn that
|
|
@@ -216,15 +218,38 @@ function acceptClaudeTranscriptHint(homeDir, hint) {
|
|
|
216
218
|
return resolved;
|
|
217
219
|
}
|
|
218
220
|
/**
|
|
219
|
-
* Read
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
* `
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
221
|
+
* Read this instance's unwritten turns out of its transcript and write them.
|
|
222
|
+
*
|
|
223
|
+
* ## Why this is no longer "only the newest turn" (Issue #2246)
|
|
224
|
+
*
|
|
225
|
+
* #2121 wrote `built.turns.at(-1)` and nothing else, for a reason that was
|
|
226
|
+
* sound and is still half true: every earlier turn of this session already has
|
|
227
|
+
* a `chat_messages` row that the *scraper* wrote, and writing a Markdown row
|
|
228
|
+
* for it as well would put the same reply in History twice — once as prose and
|
|
229
|
+
* once as the pane drew it.
|
|
230
|
+
*
|
|
231
|
+
* What that argument did not cover is a turn **no writer recorded at all**. The
|
|
232
|
+
* trigger for this reader is the poller deciding a turn finished, so a poll that
|
|
233
|
+
* misjudges one completion (#2247's launch-banner heuristic, measured on
|
|
234
|
+
* 2026-09-02) does not merely delay the turn: by the time the next completion is
|
|
235
|
+
* judged, "the newest turn" is the *next* one, and the missed turn is nobody's.
|
|
236
|
+
* One dropped completion cost one turn permanently.
|
|
237
|
+
*
|
|
238
|
+
* So the unit of work is now "every turn in the window that is not already a
|
|
239
|
+
* row", written oldest first, and what preserves #2121's argument is the
|
|
240
|
+
* **anchor**: the newest turn in the window that this reader has already written
|
|
241
|
+
* is where the backfill starts. Turns after it are turns this reader was live
|
|
242
|
+
* for, so a scraper row for them exists only in the case the anchor cannot see
|
|
243
|
+
* — the reader answering false once for a transient reason (an empty body) and
|
|
244
|
+
* the scraper saving that turn's pane copy. That trade is deliberate and in the
|
|
245
|
+
* direction every Issue in this subsystem picks: two writers duplicate a reply,
|
|
246
|
+
* no writer loses one.
|
|
247
|
+
*
|
|
248
|
+
* When the window holds **no** anchor — a session the reader has never written
|
|
249
|
+
* for, and equally the first read after a `/clear` — this falls back to #2121's
|
|
250
|
+
* behaviour exactly: the newest turn, and nothing before it. That is the case
|
|
251
|
+
* where the earlier turns really are the scraper's, and there is no evidence in
|
|
252
|
+
* the window that says otherwise.
|
|
228
253
|
*
|
|
229
254
|
* The return value is the poller's instruction, so the two failure directions
|
|
230
255
|
* are worth stating plainly. **True** means this path has recorded the turn and
|
|
@@ -234,10 +259,15 @@ function acceptClaudeTranscriptHint(homeDir, hint) {
|
|
|
234
259
|
* false, which is the fail-open the acceptance criteria require: two writers
|
|
235
260
|
* duplicate a reply, no writer loses one.
|
|
236
261
|
*
|
|
262
|
+
* **It is the newest turn the answer is about**, backfill or not. A run that
|
|
263
|
+
* wrote three missed turns and then found the newest one still empty answers
|
|
264
|
+
* false, because the scrape the poller is holding is the pane's copy of *that*
|
|
265
|
+
* turn and dropping it would lose it.
|
|
266
|
+
*
|
|
237
267
|
* Never throws.
|
|
238
268
|
*
|
|
239
269
|
* @param target - The instance whose turn just ended
|
|
240
|
-
* @returns Whether History now holds this turn as
|
|
270
|
+
* @returns Whether History now holds this instance's newest turn as Markdown
|
|
241
271
|
*/
|
|
242
272
|
async function captureClaudeTranscriptTurn(target, capture) {
|
|
243
273
|
const instanceId = target.instanceId ?? target.cliToolId;
|
|
@@ -262,8 +292,7 @@ async function captureClaudeTranscriptTurn(target, capture) {
|
|
|
262
292
|
return false;
|
|
263
293
|
const parsed = (0, transcript_1.parseClaudeTranscript)(text);
|
|
264
294
|
const built = (0, transcript_1.buildClaudeTurns)(parsed.records, sessionId ?? '');
|
|
265
|
-
|
|
266
|
-
if (!turn) {
|
|
295
|
+
if (built.turns.length === 0) {
|
|
267
296
|
logger.info('claude-transcript-no-turn', {
|
|
268
297
|
worktreeId: target.worktreeId,
|
|
269
298
|
instanceId,
|
|
@@ -294,8 +323,43 @@ async function captureClaudeTranscriptTurn(target, capture) {
|
|
|
294
323
|
sidechainRecords: built.sidechainRecords,
|
|
295
324
|
});
|
|
296
325
|
}
|
|
297
|
-
const
|
|
298
|
-
|
|
326
|
+
const pending = await selectUnwrittenClaudeTurns(target, built.turns);
|
|
327
|
+
if (pending.turns.length === 0) {
|
|
328
|
+
// The anchor is the newest turn in the window, so there is nothing to
|
|
329
|
+
// write and the newest turn is a row. True, for the reason
|
|
330
|
+
// {@link writeClaudeTurn} answers true to an already-saved turn: a second
|
|
331
|
+
// poll of one finished turn must not put the pane's copy on top of it.
|
|
332
|
+
logger.debug('claude-transcript-turns-already-saved', {
|
|
333
|
+
worktreeId: target.worktreeId,
|
|
334
|
+
instanceId,
|
|
335
|
+
turnsInWindow: built.turns.length,
|
|
336
|
+
});
|
|
337
|
+
return true;
|
|
338
|
+
}
|
|
339
|
+
if (pending.turns.length > 1) {
|
|
340
|
+
// The #2246 case, and the one worth a line in the log: more than one turn
|
|
341
|
+
// was unwritten, so a completion went unnoticed at the time.
|
|
342
|
+
logger.info('claude-transcript-backfilling-turns', {
|
|
343
|
+
worktreeId: target.worktreeId,
|
|
344
|
+
instanceId,
|
|
345
|
+
path,
|
|
346
|
+
pendingTurns: pending.turns.length,
|
|
347
|
+
turnsInWindow: built.turns.length,
|
|
348
|
+
anchored: pending.anchored,
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
// Oldest first, and the ordering is load-bearing twice over: History sorts
|
|
352
|
+
// by timestamp, and each turn's `/send` row is adopted against the previous
|
|
353
|
+
// turn's start, so a later turn must not get to claim a row an earlier one
|
|
354
|
+
// is about to ask for.
|
|
355
|
+
let captured = false;
|
|
356
|
+
for (let index = 0; index < pending.turns.length; index += 1) {
|
|
357
|
+
const turn = pending.turns[index];
|
|
358
|
+
const previousStartedAt = index === 0 ? pending.previousStartedAt : pending.turns[index - 1].startedAt;
|
|
359
|
+
const userRow = await recordClaudeUserTurn(target, turn, previousStartedAt);
|
|
360
|
+
captured = await writeClaudeTurn(target, (0, transcript_1.renderClaudeTurn)(turn), resolveAssistantTimestampMs(turn, userRow), path);
|
|
361
|
+
}
|
|
362
|
+
return captured;
|
|
299
363
|
}
|
|
300
364
|
catch (error) {
|
|
301
365
|
logger.error('claude-transcript-capture-failed', {
|
|
@@ -306,6 +370,78 @@ async function captureClaudeTranscriptTurn(target, capture) {
|
|
|
306
370
|
return false;
|
|
307
371
|
}
|
|
308
372
|
}
|
|
373
|
+
/**
|
|
374
|
+
* The turns {@link captureClaudeTranscriptTurn} still has to write (Issue #2246).
|
|
375
|
+
*
|
|
376
|
+
* The search runs **backwards from the newest turn** and stops at the first one
|
|
377
|
+
* that is already a row. That turn is the *anchor*, and everything after it is
|
|
378
|
+
* pending — by construction, since the anchor is the newest written turn in the
|
|
379
|
+
* window.
|
|
380
|
+
*
|
|
381
|
+
* Two properties of that rule are worth stating because both were required:
|
|
382
|
+
*
|
|
383
|
+
* - **Order, not time.** The comparison is the transcript's own record order
|
|
384
|
+
* and never a timestamp. A turn's assistant row is dated one millisecond
|
|
385
|
+
* after its user row (`resolveAssistantTimestampMs`), so the rows do not
|
|
386
|
+
* carry an ordering that could be trusted for this.
|
|
387
|
+
* - **The window bounds it.** `buildClaudeTurns` only opens a turn on a prompt
|
|
388
|
+
* record, so a turn whose prompt fell outside
|
|
389
|
+
* {@link CLAUDE_TRANSCRIPT_TAIL_BYTES} is not in `turns` at all and cannot be
|
|
390
|
+
* backfilled from its middle. Its records are counted as
|
|
391
|
+
* `orphanedAssistantRecords` instead, which is what the caller reports.
|
|
392
|
+
*
|
|
393
|
+
* A window with no anchor answers with the newest turn alone. See
|
|
394
|
+
* {@link captureClaudeTranscriptTurn} for why that is #2121's behaviour rather
|
|
395
|
+
* than a degraded one.
|
|
396
|
+
*
|
|
397
|
+
* @param turns - Every turn in the window, oldest first
|
|
398
|
+
*/
|
|
399
|
+
async function selectUnwrittenClaudeTurns(target, turns) {
|
|
400
|
+
const [{ getDbInstance }, { findMessageByRequestId }] = await Promise.all([
|
|
401
|
+
Promise.resolve().then(() => __importStar(require('../../../../lib/db/db-instance'))),
|
|
402
|
+
Promise.resolve().then(() => __importStar(require('../../../../lib/db'))),
|
|
403
|
+
]);
|
|
404
|
+
const db = getDbInstance();
|
|
405
|
+
for (let index = turns.length - 1; index >= 0; index -= 1) {
|
|
406
|
+
const requestId = (0, agent_transcript_1.claudeTurnRequestId)(turns[index].promptUuid);
|
|
407
|
+
if (!findMessageByRequestId(db, target.worktreeId, requestId))
|
|
408
|
+
continue;
|
|
409
|
+
return {
|
|
410
|
+
turns: turns.slice(index + 1),
|
|
411
|
+
previousStartedAt: turns[index].startedAt,
|
|
412
|
+
anchored: true,
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
return {
|
|
416
|
+
turns: turns.slice(-1),
|
|
417
|
+
previousStartedAt: turns.length > 1 ? turns[turns.length - 2].startedAt : 0,
|
|
418
|
+
anchored: false,
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* The transcript file this instance would be read from, or null (Issue #2246).
|
|
423
|
+
*
|
|
424
|
+
* The same two steps {@link captureClaudeTranscriptTurn} opens with — resolve
|
|
425
|
+
* the session pointer, then check the filesystem — asked without reading or
|
|
426
|
+
* writing anything. The Stop receiver uses it to decide whether waiting half a
|
|
427
|
+
* second and asking again could possibly help: a session with no transcript to
|
|
428
|
+
* name will not have grown one by then, and a hook handler that sleeps for
|
|
429
|
+
* nothing is a hook handler that slows every turn of every tool.
|
|
430
|
+
*
|
|
431
|
+
* Never throws.
|
|
432
|
+
*/
|
|
433
|
+
async function resolveClaudeTranscriptPath(target, capture) {
|
|
434
|
+
try {
|
|
435
|
+
const homeDir = capture.homeDir ?? (0, os_1.homedir)();
|
|
436
|
+
if (typeof capture.worktreePath !== 'string' || capture.worktreePath.length === 0) {
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
439
|
+
return await locateClaudeTranscript(homeDir, capture, await resolveClaudeSessionId(target));
|
|
440
|
+
}
|
|
441
|
+
catch {
|
|
442
|
+
return null;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
309
445
|
/**
|
|
310
446
|
* The key for a body read with no prompt record in the window.
|
|
311
447
|
*
|
|
@@ -463,8 +599,14 @@ async function readClaudeTurnProgress(target, capture) {
|
|
|
463
599
|
* agent really did reply, and #2121's behaviour for those turns is unchanged.
|
|
464
600
|
*
|
|
465
601
|
* Never throws; `recordUserTurn` reports its failures in the return value.
|
|
602
|
+
*
|
|
603
|
+
* @param previousStartedAt - When the turn before this one opened, or 0. Widens
|
|
604
|
+
* `/send` adoption backwards to that instant (Issue #2246): a prompt the agent
|
|
605
|
+
* queued for eleven minutes is still the row CommandMate wrote when it sent
|
|
606
|
+
* the text, and #2196's symmetric two-minute window cannot reach it. Never
|
|
607
|
+
* narrows — see {@link RecordUserTurnOptions}.
|
|
466
608
|
*/
|
|
467
|
-
async function recordClaudeUserTurn(target, turn) {
|
|
609
|
+
async function recordClaudeUserTurn(target, turn, previousStartedAt = 0) {
|
|
468
610
|
const instanceId = target.instanceId ?? target.cliToolId;
|
|
469
611
|
if (!turn.promptIsOperatorInput) {
|
|
470
612
|
logger.debug('claude-transcript-user-turn-skipped', {
|
|
@@ -475,7 +617,8 @@ async function recordClaudeUserTurn(target, turn) {
|
|
|
475
617
|
});
|
|
476
618
|
return { outcome: 'skipped', messageId: null, timestampMs: null };
|
|
477
619
|
}
|
|
478
|
-
const
|
|
620
|
+
const adoption = previousStartedAt > 0 ? { adoptionFromMs: previousStartedAt } : {};
|
|
621
|
+
const recorded = await (0, user_turn_recorder_1.recordUserTurn)(target, (0, agent_transcript_1.claudePromptRequestId)(turn.promptUuid), turn.promptText, turn.startedAt, adoption);
|
|
479
622
|
if (recorded.outcome === 'failed') {
|
|
480
623
|
logger.warn('claude-transcript-user-turn-failed', {
|
|
481
624
|
worktreeId: target.worktreeId,
|