commandmate 0.33.1 → 0.33.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 +290 -290
- package/.next/app-path-routes-manifest.json +29 -29
- package/.next/build-manifest.json +5 -5
- package/.next/diagnostics/framework.json +1 -1
- package/.next/next-server.js.nft.json +1 -1
- package/.next/prerender-manifest.json +3 -3
- package/.next/required-server-files.json +1 -2
- 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_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 +32 -1
- 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 +33 -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_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.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.js +1 -1
- package/.next/server/app/api/push/vapid/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/relays/[relayId]/cancel/route.js +1 -1
- package/.next/server/app/api/relays/[relayId]/cancel/route.js.nft.json +1 -1
- package/.next/server/app/api/relays/[relayId]/cancel/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/relays/route.js +1 -1
- package/.next/server/app/api/relays/route.js.nft.json +1 -1
- package/.next/server/app/api/relays/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 +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 +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 +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/notes/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 +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 +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 +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 +103 -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.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.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 +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 +8 -8
- 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 +29 -29
- package/.next/server/chunks/1068.js +1 -0
- package/.next/server/chunks/1210.js +1 -1
- package/.next/server/chunks/1331.js +2 -2
- package/.next/server/chunks/1366.js +5 -3
- package/.next/server/chunks/1886.js +2 -2
- package/.next/server/chunks/2839.js +1 -0
- package/.next/server/chunks/3241.js +1 -1
- package/.next/server/chunks/3323.js +1 -0
- package/.next/server/chunks/3707.js +1 -1
- package/.next/server/chunks/3708.js +1 -1
- package/.next/server/chunks/4443.js +1 -1
- package/.next/server/chunks/5220.js +1 -1
- package/.next/server/chunks/595.js +2 -2
- package/.next/server/chunks/650.js +1 -0
- package/.next/server/chunks/6781.js +1 -0
- package/.next/server/chunks/6808.js +1 -0
- package/.next/server/chunks/6838.js +1 -1
- package/.next/server/chunks/7875.js +3 -3
- package/.next/server/chunks/8918.js +107 -5
- package/.next/server/middleware-build-manifest.js +1 -1
- 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/server/src/middleware.js.map +1 -1
- package/.next/static/chunks/1255-7e5873f06b25e129.js +1 -0
- package/.next/static/chunks/{4718-2bc5cdf06573bf54.js → 4718-441a2af91f0374e9.js} +1 -1
- package/.next/static/chunks/app/worktrees/[id]/page-38edcd53e51f53b0.js +1 -0
- package/.next/static/chunks/{main-6092074a0dc78280.js → main-ac91ed6e71380841.js} +1 -1
- package/.next/trace +13 -14
- package/dist/server/src/lib/assistant-response-saver.js +168 -12
- package/dist/server/src/lib/hooks/sources/antigravity/history.js +204 -9
- package/dist/server/src/lib/hooks/sources/claude/history.js +29 -3
- package/dist/server/src/lib/hooks/sources/codex/history.js +29 -3
- package/dist/server/src/lib/hooks/sources/command-code/history.js +29 -3
- package/dist/server/src/lib/hooks/stop-history-capture.js +30 -4
- package/dist/server/src/lib/polling/response-checker.js +258 -9
- package/dist/server/src/lib/polling/response-poller-core.js +20 -0
- package/dist/server/src/lib/polling/structured-history-gate.js +26 -6
- package/dist/server/src/lib/response-cleaner.js +84 -0
- package/package.json +2 -2
- package/.next/server/chunks/21.js +0 -1
- package/.next/server/chunks/3086.js +0 -1
- package/.next/server/chunks/3270.js +0 -1
- package/.next/server/chunks/3925.js +0 -1
- package/.next/server/chunks/5043.js +0 -1
- package/.next/server/chunks/6306.js +0 -1
- package/.next/server/chunks/6363.js +0 -1
- package/.next/server/chunks/6491.js +0 -103
- package/.next/server/chunks/7890.js +0 -1
- package/.next/server/chunks/8063.js +0 -32
- package/.next/server/chunks/8687.js +0 -1
- package/.next/static/chunks/1255-8cb587d9170b45ed.js +0 -1
- package/.next/static/chunks/app/worktrees/[id]/page-13ca0ba551fe5cfb.js +0 -1
- /package/.next/static/{9lgw9hA727UMyFhXryGWG → 0a4qrU8WdUwQrlO4mfLMu}/_buildManifest.js +0 -0
- /package/.next/static/{9lgw9hA727UMyFhXryGWG → 0a4qrU8WdUwQrlO4mfLMu}/_ssgManifest.js +0 -0
|
@@ -135,6 +135,7 @@ const path_1 = require("path");
|
|
|
135
135
|
const auto_yes_state_1 = require("../../../../lib/auto-yes-state");
|
|
136
136
|
const transcript_tail_1 = require("../../../../lib/history/transcript-tail");
|
|
137
137
|
const user_turn_recorder_1 = require("../../../../lib/history/user-turn-recorder");
|
|
138
|
+
const assistant_response_saver_1 = require("../../../../lib/assistant-response-saver");
|
|
138
139
|
const logger_1 = require("../../../../lib/logger");
|
|
139
140
|
const agent_transcript_1 = require("../../../../types/agent-transcript");
|
|
140
141
|
const transcript_1 = require("./transcript");
|
|
@@ -325,10 +326,18 @@ async function findCommandCodeTranscriptPath(homeDir, sessionId) {
|
|
|
325
326
|
*
|
|
326
327
|
* Never throws.
|
|
327
328
|
*
|
|
329
|
+
* Since Issue #2436 the false can explain itself: pass a
|
|
330
|
+
* `StructuredHistoryCaptureReport` and `outcome` is set to `'not_yet_closed'`
|
|
331
|
+
* when the newest turn is one the agent has not finished writing — the case the
|
|
332
|
+
* `-turn-open` line below reports, and the one where the caller's scraped copy
|
|
333
|
+
* is worth holding rather than saving. Every other false leaves it unset, which
|
|
334
|
+
* the gate reads as `'unavailable'`.
|
|
335
|
+
*
|
|
328
336
|
* @param target - The instance whose turn just ended
|
|
337
|
+
* @param report - Optional out-parameter; see `StructuredHistoryCaptureReport`
|
|
329
338
|
* @returns Whether History now holds this instance's newest turn as Markdown
|
|
330
339
|
*/
|
|
331
|
-
async function captureCommandCodeTranscriptTurn(target, capture) {
|
|
340
|
+
async function captureCommandCodeTranscriptTurn(target, capture, report) {
|
|
332
341
|
const instanceId = target.instanceId ?? target.cliToolId;
|
|
333
342
|
try {
|
|
334
343
|
const homeDir = capture.commandCodeHome ?? (0, os_1.homedir)();
|
|
@@ -394,6 +403,9 @@ async function captureCommandCodeTranscriptTurn(target, capture) {
|
|
|
394
403
|
instanceId,
|
|
395
404
|
turnsInWindow: built.turns.length,
|
|
396
405
|
});
|
|
406
|
+
// Issue #2437: this turn is already History's Markdown, so the pane rows
|
|
407
|
+
// behind it must stop being "unsaved output" the pre-send flush can pick up.
|
|
408
|
+
await (0, assistant_response_saver_1.advanceCapturedLineForTranscriptTurn)(target);
|
|
397
409
|
return true;
|
|
398
410
|
}
|
|
399
411
|
if (pending.turns.length > 1) {
|
|
@@ -426,7 +438,14 @@ async function captureCommandCodeTranscriptTurn(target, capture) {
|
|
|
426
438
|
let captured = false;
|
|
427
439
|
for (let index = 0; index < pending.turns.length; index += 1) {
|
|
428
440
|
const turn = pending.turns[index];
|
|
429
|
-
captured = await writeCommandCodeTurn(target, turn, (0, transcript_1.renderCommandCodeTurn)(turn), resolveAssistantTimestampMs(turn, userRows[index], lastRecordAt.get(turn.promptId) ?? 0, nextTurnOpensAt(pending.turns, userRows, index)), path);
|
|
441
|
+
captured = await writeCommandCodeTurn(target, turn, (0, transcript_1.renderCommandCodeTurn)(turn), resolveAssistantTimestampMs(turn, userRows[index], lastRecordAt.get(turn.promptId) ?? 0, nextTurnOpensAt(pending.turns, userRows, index)), path, report);
|
|
442
|
+
}
|
|
443
|
+
if (captured) {
|
|
444
|
+
// Issue #2437: History now holds this turn as the agent's own Markdown.
|
|
445
|
+
// Park the pre-send flush's cursor past the pane rows it covers, or a
|
|
446
|
+
// `/send` arriving before the next poll tick saves the whole finished turn
|
|
447
|
+
// a second time.
|
|
448
|
+
await (0, assistant_response_saver_1.advanceCapturedLineForTranscriptTurn)(target);
|
|
430
449
|
}
|
|
431
450
|
return captured;
|
|
432
451
|
}
|
|
@@ -773,8 +792,13 @@ async function refreshCommandCodeTurnRows(target, candidates, path) {
|
|
|
773
792
|
*
|
|
774
793
|
* @returns Whether History holds this turn as the agent's own Markdown
|
|
775
794
|
*/
|
|
776
|
-
async function writeCommandCodeTurn(target, turn, rendered, timestampMs, path) {
|
|
795
|
+
async function writeCommandCodeTurn(target, turn, rendered, timestampMs, path, report) {
|
|
777
796
|
const instanceId = target.instanceId ?? target.cliToolId;
|
|
797
|
+
// Issue #2436: the caller's report is about the turn THIS call is deciding.
|
|
798
|
+
// The loop above walks oldest-first, so a verdict left by an earlier turn has
|
|
799
|
+
// to be cleared before this one's is written.
|
|
800
|
+
if (report)
|
|
801
|
+
report.outcome = undefined;
|
|
778
802
|
if (!(0, transcript_1.isCommandCodeTurnWritable)(turn)) {
|
|
779
803
|
// The agent's last word in this turn reached for a tool and no later prompt
|
|
780
804
|
// has taken over, so what is in the file is a turn in progress. Writing it
|
|
@@ -789,6 +813,8 @@ async function writeCommandCodeTurn(target, turn, rendered, timestampMs, path) {
|
|
|
789
813
|
// Handing it back to the scraper costs the Markdown rendering for this turn
|
|
790
814
|
// and nothing else: the Stop receiver asks again after a short delay, and
|
|
791
815
|
// the poller asks again when the pane returns to the composer.
|
|
816
|
+
if (report)
|
|
817
|
+
report.outcome = 'not_yet_closed';
|
|
792
818
|
logger.info('command-code-transcript-turn-open', {
|
|
793
819
|
worktreeId: target.worktreeId,
|
|
794
820
|
instanceId,
|
|
@@ -248,7 +248,12 @@ async function readAfterResponding(gate, worktree, cliToolId, instanceId, captur
|
|
|
248
248
|
try {
|
|
249
249
|
for (const [index, delayMs] of delaysMs.entries()) {
|
|
250
250
|
await sleep(delayMs);
|
|
251
|
-
|
|
251
|
+
// Issue #2436: the boolean is the verdict, the report says why. Read
|
|
252
|
+
// explicitly rather than as truthiness — an open turn answers `false`
|
|
253
|
+
// here and must keep the loop going, which is exactly the misreading the
|
|
254
|
+
// Issue's requirement C is about.
|
|
255
|
+
const report = {};
|
|
256
|
+
const captured = await gate.captureStructuredHistoryTurn(worktree.id, cliToolId, instanceId, capture, report);
|
|
252
257
|
if (captured) {
|
|
253
258
|
// The line an operator greps when a codex reply did reach the chat
|
|
254
259
|
// surface: the receiver's own `agent-event-stop-applied` cannot carry
|
|
@@ -269,6 +274,10 @@ async function readAfterResponding(gate, worktree, cliToolId, instanceId, captur
|
|
|
269
274
|
instanceId,
|
|
270
275
|
attempt: index + 1,
|
|
271
276
|
attempts: delaysMs.length,
|
|
277
|
+
// Issue #2436: `not_yet_closed` here means the next read is worth
|
|
278
|
+
// taking; anything else means these delays are being spent on a turn
|
|
279
|
+
// that will never be written by this reader.
|
|
280
|
+
outcome: report.outcome ?? 'unavailable',
|
|
272
281
|
elapsedMs: Date.now() - startedAt,
|
|
273
282
|
});
|
|
274
283
|
}
|
|
@@ -324,7 +333,15 @@ async function resolveStopTranscriptCapture(worktree, cliToolId, instanceId, opt
|
|
|
324
333
|
// found that way has no session pointer either, and therefore sends no stop
|
|
325
334
|
// event this path could act on.
|
|
326
335
|
const capture = { worktreePath: worktree.path, transcriptPathHint: null };
|
|
327
|
-
|
|
336
|
+
// Issue #2436: `captureStructuredHistoryTurn` still answers a boolean, and
|
|
337
|
+
// `true` still means — and only means — "the row is written NOW". That is
|
|
338
|
+
// load-bearing here: the three statuses below are decided by reading this
|
|
339
|
+
// value as a boolean, and a tri-state RETURN would have made every one of
|
|
340
|
+
// them truthy, turning an open turn into `captured` and cancelling the very
|
|
341
|
+
// reads that go on to write it. The third value arrives beside the boolean
|
|
342
|
+
// instead, in `firstReport`, and is used explicitly.
|
|
343
|
+
const firstReport = {};
|
|
344
|
+
if (await gate.captureStructuredHistoryTurn(worktree.id, cliToolId, instanceId, capture, firstReport)) {
|
|
328
345
|
return { status: 'captured' };
|
|
329
346
|
}
|
|
330
347
|
// Asked once, before any waiting, on either path: every failure other than
|
|
@@ -333,7 +350,14 @@ async function resolveStopTranscriptCapture(worktree, cliToolId, instanceId, opt
|
|
|
333
350
|
// nor deferring can fix, and an instance with no transcript will not have
|
|
334
351
|
// one in 150 ms either. A transcript that exists does not stop existing
|
|
335
352
|
// between attempts, so this is not re-asked.
|
|
336
|
-
|
|
353
|
+
//
|
|
354
|
+
// Issue #2436: a reader that answered `not_yet_closed` HAS just read the
|
|
355
|
+
// file — that is what "the turn in it is still open" is a statement about —
|
|
356
|
+
// so the probe is skipped and the answer taken from the report. The
|
|
357
|
+
// fallback is the pre-#2436 round-trip, for a reader that reported nothing.
|
|
358
|
+
const hasTranscript = () => firstReport.outcome === 'not_yet_closed'
|
|
359
|
+
? Promise.resolve(true)
|
|
360
|
+
: gate.hasStructuredHistoryTranscript(worktree.id, cliToolId, instanceId, capture);
|
|
337
361
|
if (exports.STOP_HOOK_BLOCKS_TRANSCRIPT_CLOSE.has(cliToolId)) {
|
|
338
362
|
const delaysMs = options.deferredDelaysMs ?? exports.STOP_TRANSCRIPT_DEFERRED_DELAYS_MS;
|
|
339
363
|
if (delaysMs.length === 0)
|
|
@@ -357,7 +381,8 @@ async function resolveStopTranscriptCapture(worktree, cliToolId, instanceId, opt
|
|
|
357
381
|
const maxAttempts = Math.max(1, options.maxAttempts ?? exports.STOP_TRANSCRIPT_MAX_ATTEMPTS);
|
|
358
382
|
for (let attempt = 2; attempt <= maxAttempts; attempt += 1) {
|
|
359
383
|
await sleep(retryDelayMs);
|
|
360
|
-
const
|
|
384
|
+
const report = {};
|
|
385
|
+
const captured = await gate.captureStructuredHistoryTurn(worktree.id, cliToolId, instanceId, capture, report);
|
|
361
386
|
logger.debug('stop-history-capture-retried', {
|
|
362
387
|
worktreeId: worktree.id,
|
|
363
388
|
cliToolId,
|
|
@@ -365,6 +390,7 @@ async function resolveStopTranscriptCapture(worktree, cliToolId, instanceId, opt
|
|
|
365
390
|
attempt,
|
|
366
391
|
maxAttempts,
|
|
367
392
|
captured,
|
|
393
|
+
outcome: report.outcome ?? 'unavailable',
|
|
368
394
|
});
|
|
369
395
|
if (captured)
|
|
370
396
|
return { status: 'captured' };
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/** Response checking and extraction logic for CLI tool polling (Issue #575 split from response-poller.ts). */
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.PENDING_SCRAPE_HOLD_MS = void 0;
|
|
4
5
|
exports.incompleteResult = incompleteResult;
|
|
5
6
|
exports.buildPromptExtractionResult = buildPromptExtractionResult;
|
|
6
7
|
exports.detectPromptOnCleanFrame = detectPromptOnCleanFrame;
|
|
7
8
|
exports.detectPromptWithOptions = detectPromptWithOptions;
|
|
8
9
|
exports.extractResponse = extractResponse;
|
|
10
|
+
exports.resetPendingScrapedResponses = resetPendingScrapedResponses;
|
|
11
|
+
exports.hasPendingScrapedResponse = hasPendingScrapedResponse;
|
|
12
|
+
exports.flushPendingScrapedResponse = flushPendingScrapedResponse;
|
|
9
13
|
exports.checkForResponse = checkForResponse;
|
|
10
14
|
const cli_session_1 = require("../../lib/session/cli-session");
|
|
11
15
|
const db_instance_1 = require("../../lib/db/db-instance");
|
|
@@ -30,6 +34,7 @@ const prompt_dedup_1 = require("./prompt-dedup");
|
|
|
30
34
|
const prompt_dedup_state_1 = require("./prompt-dedup-state");
|
|
31
35
|
const response_dedup_1 = require("./response-dedup");
|
|
32
36
|
const structured_history_gate_1 = require("./structured-history-gate");
|
|
37
|
+
const stop_history_capture_1 = require("../../lib/hooks/stop-history-capture");
|
|
33
38
|
const relay_triggers_1 = require("../../lib/relay/relay-triggers");
|
|
34
39
|
// Issue #2317 Phase D: while a human holds the pane's geometry, the frame is
|
|
35
40
|
// their terminal (44 rows), not the 1000-row canvas every rule below was
|
|
@@ -735,6 +740,176 @@ function extractResponse(output, lastCapturedLine, cliToolId, captureWindowLines
|
|
|
735
740
|
// ============================================================================
|
|
736
741
|
// checkForResponse (exported for response-poller-core.ts)
|
|
737
742
|
// ============================================================================
|
|
743
|
+
// ============================================================================
|
|
744
|
+
// The held scrape (Issue #2436)
|
|
745
|
+
// ============================================================================
|
|
746
|
+
/**
|
|
747
|
+
* How long a scraped reply is held while its transcript finishes closing.
|
|
748
|
+
*
|
|
749
|
+
* Derived from `STOP_TRANSCRIPT_DEFERRED_DELAYS_MS`, not spelled again: those
|
|
750
|
+
* are the instants the Stop receiver re-reads the transcript at after it has
|
|
751
|
+
* answered the agent (#2398), so their SUM is the moment after which nobody is
|
|
752
|
+
* still trying. Holding past it would be holding for a row that has no producer
|
|
753
|
+
* left; stopping short of it would race the producer that is still running.
|
|
754
|
+
*
|
|
755
|
+
* The measured gap this covers is under a second — codex 2026-09-08 appended
|
|
756
|
+
* `task_complete` ~700 ms after the frame went quiet — so the budget is roughly
|
|
757
|
+
* ten times the case it exists for, spent only on turns that ask for it.
|
|
758
|
+
*/
|
|
759
|
+
exports.PENDING_SCRAPE_HOLD_MS = stop_history_capture_1.STOP_TRANSCRIPT_DEFERRED_DELAYS_MS.reduce((total, delay) => total + delay, 0);
|
|
760
|
+
/**
|
|
761
|
+
* Held scrapes, by poller key.
|
|
762
|
+
*
|
|
763
|
+
* **Deliberately not in `./response-dedup`.** That module's two caches are
|
|
764
|
+
* cleared by `stopPollingByKey` — `clearResponseHashCache` is called from
|
|
765
|
+
* inside it — so a held reply parked beside them would be dropped by the very
|
|
766
|
+
* event that has to write it (Issue #2436, requirement B). It lives here, next
|
|
767
|
+
* to the code that fills it and the code that writes it out, and
|
|
768
|
+
* `flushPendingScrapedResponse` is what the poller calls before it clears
|
|
769
|
+
* anything.
|
|
770
|
+
*
|
|
771
|
+
* On `globalThis` for the reason every shared map in this subsystem is (#1736):
|
|
772
|
+
* under `next dev` the poller's bundle and each route's bundle would otherwise
|
|
773
|
+
* hold a private copy, and a held reply only one bundle can see is a lost one.
|
|
774
|
+
*/
|
|
775
|
+
const pendingScrapedResponses = (globalThis.__pendingScrapedResponses ??= new Map());
|
|
776
|
+
/** Forget every held scrape. Test seam. */
|
|
777
|
+
function resetPendingScrapedResponses() {
|
|
778
|
+
pendingScrapedResponses.clear();
|
|
779
|
+
}
|
|
780
|
+
/** Whether a scrape is being held for this poller key. Test seam / diagnostics. */
|
|
781
|
+
function hasPendingScrapedResponse(pollerKey) {
|
|
782
|
+
return pendingScrapedResponses.has(pollerKey);
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Hold this tick's scraped reply instead of writing it (Issue #2436).
|
|
786
|
+
*
|
|
787
|
+
* Called when the transcript reader has said `not_yet_closed`: the agent's own
|
|
788
|
+
* Markdown for this turn is coming, and writing the pane's copy now is what put
|
|
789
|
+
* 234,323 characters of prompt echo, intermediate output and footer into
|
|
790
|
+
* History beside the real answer.
|
|
791
|
+
*
|
|
792
|
+
* A second hold for the same key REPLACES the content and keeps the original
|
|
793
|
+
* deadline. Replaces, because a frame that moved on is a better copy of the
|
|
794
|
+
* same turn; keeps, because a pane that redraws every tick would otherwise push
|
|
795
|
+
* its own deadline forward forever and the hold would stop being bounded.
|
|
796
|
+
*/
|
|
797
|
+
function holdScrapedResponse(pollerKey, pending) {
|
|
798
|
+
const existing = pendingScrapedResponses.get(pollerKey);
|
|
799
|
+
if (existing) {
|
|
800
|
+
existing.content = pending.content;
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
pendingScrapedResponses.set(pollerKey, pending);
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* Drop a held scrape without writing it (Issue #2436).
|
|
807
|
+
*
|
|
808
|
+
* The one thing that justifies dropping it: the transcript reader has since
|
|
809
|
+
* written the turn as the agent's own Markdown, so the pane's copy is the
|
|
810
|
+
* duplicate this Issue exists to stop.
|
|
811
|
+
*/
|
|
812
|
+
function discardPendingScrapedResponse(pollerKey) {
|
|
813
|
+
pendingScrapedResponses.delete(pollerKey);
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
816
|
+
* Write a held scrape now, whatever the clock says (Issue #2436).
|
|
817
|
+
*
|
|
818
|
+
* Exported because `response-poller-core` calls it from `stopPollingByKey`,
|
|
819
|
+
* which is every way a polling cycle ends: an explicit stop, the session going
|
|
820
|
+
* away, `MAX_POLLING_DURATION`, and the restart that opens the NEXT turn. A
|
|
821
|
+
* held reply must not be able to outlive the cycle that holds it — the caches
|
|
822
|
+
* that key it are cleared in that same function, and a reply nobody writes is
|
|
823
|
+
* strictly worse than the duplicate row this Issue is trading against.
|
|
824
|
+
*
|
|
825
|
+
* **Deliberately bypasses `isDuplicateResponse`.** The hash for this content was
|
|
826
|
+
* registered by the tick that decided to hold it (the dedup guard's check is
|
|
827
|
+
* also its write), so a re-check here would answer "duplicate" for the reply
|
|
828
|
+
* that has never been saved. That is requirement A of the Issue in one line.
|
|
829
|
+
*
|
|
830
|
+
* Synchronous through the row: `better-sqlite3` is, and this runs on shutdown
|
|
831
|
+
* paths where an awaited continuation may never be reached. The Markdown
|
|
832
|
+
* conversation log is fired afterwards and not waited for, because it is a
|
|
833
|
+
* secondary record and the row is the one History reads.
|
|
834
|
+
*
|
|
835
|
+
* @param pollerKey - Poller key ("worktreeId:instanceId")
|
|
836
|
+
* @param reason - What ended the hold; logged, for the operator reading back
|
|
837
|
+
* @returns Whether a held reply was written
|
|
838
|
+
*/
|
|
839
|
+
function flushPendingScrapedResponse(pollerKey, reason) {
|
|
840
|
+
const pending = pendingScrapedResponses.get(pollerKey);
|
|
841
|
+
if (!pending)
|
|
842
|
+
return false;
|
|
843
|
+
pendingScrapedResponses.delete(pollerKey);
|
|
844
|
+
try {
|
|
845
|
+
const db = (0, db_instance_1.getDbInstance)();
|
|
846
|
+
const message = (0, db_1.createMessage)(db, {
|
|
847
|
+
worktreeId: pending.worktreeId,
|
|
848
|
+
role: 'assistant',
|
|
849
|
+
content: pending.content,
|
|
850
|
+
messageType: 'normal',
|
|
851
|
+
timestamp: pending.timestamp,
|
|
852
|
+
cliToolId: pending.cliToolId,
|
|
853
|
+
instanceId: pending.instanceId,
|
|
854
|
+
summary: pending.summary,
|
|
855
|
+
logFileName: pending.logFileName,
|
|
856
|
+
requestId: pending.requestId,
|
|
857
|
+
});
|
|
858
|
+
(0, ws_server_1.broadcastMessage)('message', { worktreeId: pending.worktreeId, message });
|
|
859
|
+
logger.info('pending-scrape-flushed', {
|
|
860
|
+
worktreeId: pending.worktreeId,
|
|
861
|
+
cliToolId: pending.cliToolId,
|
|
862
|
+
instanceId: pending.instanceId,
|
|
863
|
+
reason,
|
|
864
|
+
scrapedLength: pending.content.length,
|
|
865
|
+
heldForMs: Date.now() - pending.timestamp.getTime(),
|
|
866
|
+
});
|
|
867
|
+
void (0, conversation_logger_1.recordClaudeConversation)(db, pending.worktreeId, pending.content, pending.cliToolId).catch(() => { });
|
|
868
|
+
return true;
|
|
869
|
+
}
|
|
870
|
+
catch (error) {
|
|
871
|
+
logger.warn('pending-scrape-flush-failed', {
|
|
872
|
+
worktreeId: pending.worktreeId,
|
|
873
|
+
cliToolId: pending.cliToolId,
|
|
874
|
+
instanceId: pending.instanceId,
|
|
875
|
+
reason,
|
|
876
|
+
error: error instanceof Error ? error.message : String(error),
|
|
877
|
+
});
|
|
878
|
+
return false;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
/**
|
|
882
|
+
* End a hold whose deadline has passed, one way or the other (Issue #2436).
|
|
883
|
+
*
|
|
884
|
+
* Run at the top of every tick, because the tick that has to notice an expiry
|
|
885
|
+
* is very often one that returns early — a static frame yields no new lines for
|
|
886
|
+
* a scrollback tool and is a duplicate for an alternate-screen one, and neither
|
|
887
|
+
* of those paths reaches the save block at the bottom of `checkForResponse`.
|
|
888
|
+
*
|
|
889
|
+
* The reader is asked ONE more time before the row is written. By the deadline
|
|
890
|
+
* the throttled recheck (#2399) has usually already captured the turn and
|
|
891
|
+
* dropped the hold, but the two are not in step — the recheck is every third
|
|
892
|
+
* duplicate tick and this is a wall clock — and a last read costs one tail
|
|
893
|
+
* parse against writing a pane dump that is about to be superseded.
|
|
894
|
+
*/
|
|
895
|
+
async function settleExpiredPendingScrapedResponse(pollerKey) {
|
|
896
|
+
const pending = pendingScrapedResponses.get(pollerKey);
|
|
897
|
+
if (!pending || Date.now() < pending.expiresAt)
|
|
898
|
+
return;
|
|
899
|
+
const captured = await (0, structured_history_gate_1.captureStructuredHistoryTurn)(pending.worktreeId, pending.cliToolId, pending.instanceId, { worktreePath: pending.worktreePath, transcriptPathHint: pending.transcriptPathHint });
|
|
900
|
+
if (captured) {
|
|
901
|
+
discardPendingScrapedResponse(pollerKey);
|
|
902
|
+
(0, response_dedup_1.settleStructuredHistoryRecheck)(pollerKey);
|
|
903
|
+
logger.info('pending-scrape-superseded', {
|
|
904
|
+
worktreeId: pending.worktreeId,
|
|
905
|
+
cliToolId: pending.cliToolId,
|
|
906
|
+
instanceId: pending.instanceId,
|
|
907
|
+
heldForMs: Date.now() - pending.timestamp.getTime(),
|
|
908
|
+
});
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
flushPendingScrapedResponse(pollerKey, 'hold-expired');
|
|
912
|
+
}
|
|
738
913
|
/**
|
|
739
914
|
* Record what the structured writers said about this turn, for the ticks that
|
|
740
915
|
* will not get to ask (Issue #2399).
|
|
@@ -784,9 +959,20 @@ async function checkForResponse(worktreeId, cliToolId, instanceId) {
|
|
|
784
959
|
const running = await (0, cli_session_1.isSessionRunning)(worktreeId, cliToolId, instanceId);
|
|
785
960
|
if (!running) {
|
|
786
961
|
logger.info('session-not-running');
|
|
962
|
+
// `stopPolling` is what confirms a held scrape here; see
|
|
963
|
+
// `flushPendingScrapedResponse` and `stopPollingByKey`. Requirement B of
|
|
964
|
+
// Issue #2436: this return is ~300 lines above the save path, so a hold
|
|
965
|
+
// released only down there would never be released at all.
|
|
787
966
|
(0, response_poller_core_1.stopPolling)(worktreeId, cliToolId, instanceId);
|
|
788
967
|
return false;
|
|
789
968
|
}
|
|
969
|
+
const pollerKey = (0, response_poller_core_1.getPollerKey)(worktreeId, cliToolId, instanceId);
|
|
970
|
+
// Issue #2436: a scrape held by an earlier tick, whose transcript has now
|
|
971
|
+
// had its whole budget to close. Ahead of everything below because most
|
|
972
|
+
// ticks of a finished turn never reach the save path — a static frame is a
|
|
973
|
+
// duplicate for an alternate-screen tool and yields no new lines for a
|
|
974
|
+
// scrollback one, and both of those return early.
|
|
975
|
+
await settleExpiredPendingScrapedResponse(pollerKey);
|
|
790
976
|
// Issue #2317 Phase D: is a human reading this pane at their own terminal
|
|
791
977
|
// size right now, and did they just stop?
|
|
792
978
|
//
|
|
@@ -831,7 +1017,6 @@ async function checkForResponse(worktreeId, cliToolId, instanceId) {
|
|
|
831
1017
|
// Layer 2: Accumulate TUI content for full-screen TUI tools, so a turn that
|
|
832
1018
|
// outgrows the alternate-screen pane keeps the head that has scrolled away.
|
|
833
1019
|
if (cliToolId === 'opencode' || cliToolId === 'copilot') {
|
|
834
|
-
const pollerKey = (0, response_poller_core_1.getPollerKey)(worktreeId, cliToolId, instanceId);
|
|
835
1020
|
// Issue #1911: opencode is accumulated from the CURRENT TURN'S REGION
|
|
836
1021
|
// rather than the whole frame. Feeding the raw pane seeded the accumulator
|
|
837
1022
|
// with the previous turn's transcript, the echoed prompt and the bottom
|
|
@@ -885,7 +1070,6 @@ async function checkForResponse(worktreeId, cliToolId, instanceId) {
|
|
|
885
1070
|
if (promptDetection.isPrompt) {
|
|
886
1071
|
// Issue #565: Content hash-based duplicate prompt prevention
|
|
887
1072
|
const promptContent = promptDetection.rawContent || promptDetection.cleanContent;
|
|
888
|
-
const pollerKey = (0, response_poller_core_1.getPollerKey)(worktreeId, cliToolId, instanceId);
|
|
889
1073
|
const normalizedForDedup = (0, prompt_dedup_1.normalizePromptForDedup)(promptContent, cliToolId);
|
|
890
1074
|
if ((0, prompt_dedup_1.isDuplicatePrompt)(pollerKey, normalizedForDedup)) {
|
|
891
1075
|
// Issue #1695: the log line below is invisible to `commandmate capture
|
|
@@ -995,7 +1179,6 @@ async function checkForResponse(worktreeId, cliToolId, instanceId) {
|
|
|
995
1179
|
cleanedResponse = (0, response_cleaner_1.cleanClaudeResponse)(result.response);
|
|
996
1180
|
}
|
|
997
1181
|
else if (cliToolId === 'copilot') {
|
|
998
|
-
const pollerKey = (0, response_poller_core_1.getPollerKey)(worktreeId, cliToolId, instanceId);
|
|
999
1182
|
const accumulatedContent = (0, tui_accumulator_1.getAccumulatedContent)(pollerKey);
|
|
1000
1183
|
const sourceContent = accumulatedContent || result.response;
|
|
1001
1184
|
cleanedResponse = (0, response_cleaner_1.cleanCopilotResponse)(sourceContent);
|
|
@@ -1003,7 +1186,6 @@ async function checkForResponse(worktreeId, cliToolId, instanceId) {
|
|
|
1003
1186
|
(0, tui_accumulator_1.clearTuiAccumulator)(pollerKey);
|
|
1004
1187
|
}
|
|
1005
1188
|
else if (cliToolId === 'opencode') {
|
|
1006
|
-
const pollerKey = (0, response_poller_core_1.getPollerKey)(worktreeId, cliToolId, instanceId);
|
|
1007
1189
|
// Issue #1911 defect 3: opencode wrote to the Layer-2 accumulator but never
|
|
1008
1190
|
// read it, so any turn longer than the pane was saved without its head.
|
|
1009
1191
|
//
|
|
@@ -1042,7 +1224,6 @@ async function checkForResponse(worktreeId, cliToolId, instanceId) {
|
|
|
1042
1224
|
// Without this the disabled cursor would leave nothing suppressing re-saves and
|
|
1043
1225
|
// the poller would append the same finished reply every 2 s.
|
|
1044
1226
|
if (!lineCountIsCursor) {
|
|
1045
|
-
const pollerKey = (0, response_poller_core_1.getPollerKey)(worktreeId, cliToolId, instanceId);
|
|
1046
1227
|
if ((0, response_dedup_1.isDuplicateResponse)(pollerKey, cleanedResponse)) {
|
|
1047
1228
|
// Issue #1695: this branch used to drop the response silently — the
|
|
1048
1229
|
// prompt-side guard above has logged its skip since #565, this one
|
|
@@ -1092,6 +1273,16 @@ async function checkForResponse(worktreeId, cliToolId, instanceId) {
|
|
|
1092
1273
|
// is neither. Two rows for one turn is the failure this trades for, and
|
|
1093
1274
|
// #2401 has already stopped the junk one being picked as a relay's
|
|
1094
1275
|
// answer.
|
|
1276
|
+
//
|
|
1277
|
+
// Issue #2436 narrowed what that trade costs, without changing the
|
|
1278
|
+
// decision above. The row this skip cannot retract is now only ever one
|
|
1279
|
+
// the poller had no reason to hold: a turn whose reader said
|
|
1280
|
+
// `not_yet_closed` is held at the save path below rather than written,
|
|
1281
|
+
// so on the ordinary codex turn there is no earlier row here to regret.
|
|
1282
|
+
// What remains is the case the three reasons above are actually about —
|
|
1283
|
+
// a scrape written when the reader could tell us nothing, and a
|
|
1284
|
+
// transcript that closed later anyway — and for that the row stays,
|
|
1285
|
+
// folded rather than deleted on the chat surface (`ChatMessageBubble`).
|
|
1095
1286
|
if ((0, response_dedup_1.claimStructuredHistoryRecheck)(pollerKey)) {
|
|
1096
1287
|
const recaptured = await (0, structured_history_gate_1.captureStructuredHistoryTurn)(worktreeId, cliToolId, instanceId, {
|
|
1097
1288
|
worktreePath: worktree.path,
|
|
@@ -1099,6 +1290,11 @@ async function checkForResponse(worktreeId, cliToolId, instanceId) {
|
|
|
1099
1290
|
});
|
|
1100
1291
|
if (recaptured) {
|
|
1101
1292
|
(0, response_dedup_1.settleStructuredHistoryRecheck)(pollerKey);
|
|
1293
|
+
// Issue #2436: the turn is now the agent's own Markdown, so a
|
|
1294
|
+
// scrape held for it is exactly the second row this Issue exists
|
|
1295
|
+
// to stop. Dropped, not written — the only case where dropping a
|
|
1296
|
+
// held reply loses nothing.
|
|
1297
|
+
discardPendingScrapedResponse(pollerKey);
|
|
1102
1298
|
logger.info('structured-history-recheck-captured', {
|
|
1103
1299
|
worktreeId,
|
|
1104
1300
|
cliToolId,
|
|
@@ -1132,18 +1328,24 @@ async function checkForResponse(worktreeId, cliToolId, instanceId) {
|
|
|
1132
1328
|
// Markdown and answers true, or answers false and leaves the scrape below to
|
|
1133
1329
|
// be the only record. `||` and not `&&`: the two are different tools'
|
|
1134
1330
|
// answers to the same question, and each one is false for the other's tool.
|
|
1331
|
+
//
|
|
1332
|
+
// Issue #2436 adds the third answer. `captureReport.outcome` distinguishes
|
|
1333
|
+
// "the agent has not closed this turn yet" from "there is nothing to read
|
|
1334
|
+
// here", which the boolean could not: both arrived as `false`, and `false`
|
|
1335
|
+
// meant "save the pane's copy". See {@link StructuredHistoryCaptureOutcome}.
|
|
1336
|
+
const captureReport = {};
|
|
1135
1337
|
const structuredHistoryLive = (0, structured_history_gate_1.isStructuredHistoryWriterLive)(worktreeId, cliToolId, instanceId) ||
|
|
1136
1338
|
(await (0, structured_history_gate_1.captureStructuredHistoryTurn)(worktreeId, cliToolId, instanceId, {
|
|
1137
1339
|
worktreePath: worktree.path,
|
|
1138
1340
|
transcriptPathHint: claudeMetadata?.logFilePath ?? null,
|
|
1139
|
-
}));
|
|
1341
|
+
}, captureReport));
|
|
1140
1342
|
// Issue #2399: remember which way that went, because the next tick may not
|
|
1141
1343
|
// get here. A `false` is the reader saying "not yet, or not mine", and the
|
|
1142
1344
|
// dedup guard above turns every following poll of the same static frame into
|
|
1143
1345
|
// a return — so unless the fact is written down now, the ask never happens
|
|
1144
1346
|
// again. A `true` settles it: the turn is recorded and there is nothing left
|
|
1145
1347
|
// to re-ask about.
|
|
1146
|
-
markOrSettleStructuredHistoryRecheck(
|
|
1348
|
+
markOrSettleStructuredHistoryRecheck(pollerKey, structuredHistoryLive);
|
|
1147
1349
|
// Issue #2317 Phase D: the scrape is dropped while the geometry is
|
|
1148
1350
|
// delegated, and the transcript capture above is what makes that safe.
|
|
1149
1351
|
//
|
|
@@ -1161,8 +1363,20 @@ async function checkForResponse(worktreeId, cliToolId, instanceId) {
|
|
|
1161
1363
|
// deliberate trade: a missing row is recoverable, a truncated reply saved as
|
|
1162
1364
|
// the agent's answer is not.
|
|
1163
1365
|
const suppressScrapedHistory = structuredHistoryLive || delegation.delegated;
|
|
1366
|
+
// Issue #2436: not suppressed — HELD. The reader has read the transcript,
|
|
1367
|
+
// found the newest turn still open and said so, which means the agent's own
|
|
1368
|
+
// Markdown for this turn is on its way. Writing the pane's copy now is what
|
|
1369
|
+
// put a 234,323-character dump of prompt echo, intermediate output and
|
|
1370
|
+
// footer into History beside the real answer, and #2399 explicitly accepted
|
|
1371
|
+
// that trade because the boolean it had could not tell "not yet" from
|
|
1372
|
+
// "never". It can now.
|
|
1373
|
+
//
|
|
1374
|
+
// Everything else this tick does still happens: the cursor advances, the
|
|
1375
|
+
// prompts are marked answered, the waiting episode closes, the push goes
|
|
1376
|
+
// out. Only the two writes that RECORD THE REPLY wait.
|
|
1377
|
+
const holdScrapedHistory = !suppressScrapedHistory && captureReport.outcome === 'not_yet_closed';
|
|
1164
1378
|
// Create Markdown log file for the conversation pair
|
|
1165
|
-
if (cleanedResponse && !suppressScrapedHistory) {
|
|
1379
|
+
if (cleanedResponse && !suppressScrapedHistory && !holdScrapedHistory) {
|
|
1166
1380
|
await (0, conversation_logger_1.recordClaudeConversation)(db, worktreeId, cleanedResponse, cliToolId);
|
|
1167
1381
|
}
|
|
1168
1382
|
// Mark any pending prompts as answered
|
|
@@ -1183,7 +1397,38 @@ async function checkForResponse(worktreeId, cliToolId, instanceId) {
|
|
|
1183
1397
|
// are not byte-comparable — the pane's copy is hard-wrapped at the pane
|
|
1184
1398
|
// width and gutter-prefixed, so no content check could ever recognise them
|
|
1185
1399
|
// as the same reply.
|
|
1186
|
-
if (
|
|
1400
|
+
if (holdScrapedHistory) {
|
|
1401
|
+
holdScrapedResponse(pollerKey, {
|
|
1402
|
+
worktreeId,
|
|
1403
|
+
cliToolId,
|
|
1404
|
+
instanceId: resolvedInstanceId,
|
|
1405
|
+
content: cleanedResponse,
|
|
1406
|
+
// The instant the turn was JUDGED finished, not the instant the row is
|
|
1407
|
+
// written. History sorts on this, and a row dated at the end of the
|
|
1408
|
+
// hold would sort under the NEXT turn's prompt.
|
|
1409
|
+
timestamp: new Date(),
|
|
1410
|
+
summary: claudeMetadata?.summary,
|
|
1411
|
+
logFileName: claudeMetadata?.logFileName,
|
|
1412
|
+
requestId: claudeMetadata?.requestId,
|
|
1413
|
+
worktreePath: worktree.path,
|
|
1414
|
+
transcriptPathHint: claudeMetadata?.logFilePath ?? null,
|
|
1415
|
+
expiresAt: Date.now() + exports.PENDING_SCRAPE_HOLD_MS,
|
|
1416
|
+
});
|
|
1417
|
+
logger.info('structured-history-scrape-held', {
|
|
1418
|
+
worktreeId,
|
|
1419
|
+
cliToolId,
|
|
1420
|
+
instanceId: resolvedInstanceId,
|
|
1421
|
+
scrapedLength: cleanedResponse.length,
|
|
1422
|
+
holdMs: exports.PENDING_SCRAPE_HOLD_MS,
|
|
1423
|
+
});
|
|
1424
|
+
// The completion edge is announced HERE and not at the flush, because the
|
|
1425
|
+
// turn finished now. A relay waiting on this session gets #2401's grace
|
|
1426
|
+
// window to find a turn-keyed row, which is exactly the row the hold is
|
|
1427
|
+
// waiting for; delaying the announcement by the hold would delay every
|
|
1428
|
+
// delivery by it too.
|
|
1429
|
+
(0, relay_triggers_1.onRelayTurnCompleted)(worktreeId, cliToolId, resolvedInstanceId, false);
|
|
1430
|
+
}
|
|
1431
|
+
else if (!suppressScrapedHistory) {
|
|
1187
1432
|
// Create new CLI tool message in database
|
|
1188
1433
|
const message = (0, db_1.createMessage)(db, {
|
|
1189
1434
|
worktreeId,
|
|
@@ -1209,6 +1454,10 @@ async function checkForResponse(worktreeId, cliToolId, instanceId) {
|
|
|
1209
1454
|
(0, relay_triggers_1.onRelayTurnCompleted)(worktreeId, cliToolId, resolvedInstanceId, false);
|
|
1210
1455
|
}
|
|
1211
1456
|
else {
|
|
1457
|
+
// Issue #2436: a hold from an earlier tick of this same turn is now moot
|
|
1458
|
+
// — the row it was waiting for exists.
|
|
1459
|
+
if (structuredHistoryLive)
|
|
1460
|
+
discardPendingScrapedResponse(pollerKey);
|
|
1212
1461
|
logger.info('structured-history-scrape-suppressed', {
|
|
1213
1462
|
worktreeId,
|
|
1214
1463
|
cliToolId,
|
|
@@ -365,6 +365,26 @@ function stopPollingByKey(pollerKey, options = {}) {
|
|
|
365
365
|
});
|
|
366
366
|
return;
|
|
367
367
|
}
|
|
368
|
+
// Issue #2436: a scraped reply this cycle decided to HOLD — because the
|
|
369
|
+
// agent's transcript had not closed the turn yet — is confirmed here, before
|
|
370
|
+
// anything that keys it is dropped. Every way a cycle ends comes through this
|
|
371
|
+
// function: an explicit stop, `checkForResponse` finding the session gone,
|
|
372
|
+
// `MAX_POLLING_DURATION`, and the restart that opens the next turn. A hold
|
|
373
|
+
// that outlived its cycle would be a reply nobody ever writes, which is worse
|
|
374
|
+
// than the duplicate row the hold is trading against.
|
|
375
|
+
//
|
|
376
|
+
// Ahead of `clearResponseHashCache` below, and that ordering is the whole
|
|
377
|
+
// reason the held reply does not live in `./response-dedup` beside the hash:
|
|
378
|
+
// it would be cleared by the same call that has to write it.
|
|
379
|
+
//
|
|
380
|
+
// `resume` is the one exception, and for the same reason the response hash
|
|
381
|
+
// survives it: the cycle is not ending, it is being picked up where a prompt
|
|
382
|
+
// paused it, and the turn the hold belongs to is still the current one. The
|
|
383
|
+
// hold stays bounded regardless — the first tick of the resumed chain runs
|
|
384
|
+
// `settleExpiredPendingScrapedResponse`, and by then the deadline has almost
|
|
385
|
+
// certainly passed.
|
|
386
|
+
if (!options.resume)
|
|
387
|
+
(0, response_checker_1.flushPendingScrapedResponse)(pollerKey, 'poller-stopped');
|
|
368
388
|
const owner = coordinator.owners.get(pollerKey);
|
|
369
389
|
// The live owner stopping itself from inside its own tick. Identity, not key
|
|
370
390
|
// equality: a stale generation with the same key was turned away above.
|
|
@@ -161,19 +161,19 @@ function serializePerInstance(key, work) {
|
|
|
161
161
|
*/
|
|
162
162
|
const PULL_TRANSCRIPT_READERS = {
|
|
163
163
|
[tool_id_1.CLAUDE_CLI_TOOL_ID]: {
|
|
164
|
-
capture: (target, capture) => (0, history_1.captureClaudeTranscriptTurn)(target, capture),
|
|
164
|
+
capture: (target, capture, report) => (0, history_1.captureClaudeTranscriptTurn)(target, capture, report),
|
|
165
165
|
locate: (target, capture) => (0, history_1.resolveClaudeTranscriptPath)(target, capture),
|
|
166
166
|
},
|
|
167
167
|
[tool_id_2.CODEX_CLI_TOOL_ID]: {
|
|
168
|
-
capture: (target, capture) => (0, history_2.captureCodexTranscriptTurn)(target, capture),
|
|
168
|
+
capture: (target, capture, report) => (0, history_2.captureCodexTranscriptTurn)(target, capture, report),
|
|
169
169
|
locate: (target, capture) => (0, history_2.resolveCodexTranscriptPath)(target, capture),
|
|
170
170
|
},
|
|
171
171
|
[tool_id_3.ANTIGRAVITY_CLI_TOOL_ID]: {
|
|
172
|
-
capture: (target, capture) => (0, history_3.captureAntigravityTranscriptTurn)(target, capture),
|
|
172
|
+
capture: (target, capture, report) => (0, history_3.captureAntigravityTranscriptTurn)(target, capture, report),
|
|
173
173
|
locate: (target, capture) => (0, history_3.resolveAntigravityTranscriptPath)(target, capture),
|
|
174
174
|
},
|
|
175
175
|
[tool_id_4.COMMAND_CODE_CLI_TOOL_ID]: {
|
|
176
|
-
capture: (target, capture) => (0, history_4.captureCommandCodeTranscriptTurn)(target, capture),
|
|
176
|
+
capture: (target, capture, report) => (0, history_4.captureCommandCodeTranscriptTurn)(target, capture, report),
|
|
177
177
|
locate: (target, capture) => (0, history_4.resolveCommandCodeTranscriptPath)(target, capture),
|
|
178
178
|
},
|
|
179
179
|
};
|
|
@@ -267,12 +267,23 @@ function isStructuredHistoryWriterLive(worktreeId, cliToolId, instanceId) {
|
|
|
267
267
|
* not: this runs inside the poller's save path, and an exception here would cost
|
|
268
268
|
* the scraped reply as well as the structured one.
|
|
269
269
|
*
|
|
270
|
+
* Since Issue #2436 the `false` can be asked to explain itself: pass a
|
|
271
|
+
* {@link StructuredHistoryCaptureReport} and `outcome` says whether the turn is
|
|
272
|
+
* merely still open — the row is coming, and a caller holding the pane's copy
|
|
273
|
+
* should wait — or whether nothing will ever be written for it. The boolean is
|
|
274
|
+
* unchanged in meaning and in value; see
|
|
275
|
+
* {@link StructuredHistoryCaptureOutcome} for why the third value is reported
|
|
276
|
+
* beside it rather than in it.
|
|
277
|
+
*
|
|
270
278
|
* @param worktreeId - The worktree
|
|
271
279
|
* @param cliToolId - The tool driving the pane
|
|
272
280
|
* @param instanceId - The agent instance; defaults to the primary
|
|
273
281
|
* @param capture - Where to look; see {@link StructuredHistoryCapture}
|
|
282
|
+
* @param report - Optional out-parameter; see {@link StructuredHistoryCaptureReport}
|
|
274
283
|
*/
|
|
275
|
-
async function captureStructuredHistoryTurn(worktreeId, cliToolId, instanceId, capture) {
|
|
284
|
+
async function captureStructuredHistoryTurn(worktreeId, cliToolId, instanceId, capture, report) {
|
|
285
|
+
if (report)
|
|
286
|
+
report.outcome = 'unavailable';
|
|
276
287
|
if (!isPullTranscriptHistory(cliToolId))
|
|
277
288
|
return false;
|
|
278
289
|
const reader = PULL_TRANSCRIPT_READERS[cliToolId];
|
|
@@ -286,8 +297,17 @@ async function captureStructuredHistoryTurn(worktreeId, cliToolId, instanceId, c
|
|
|
286
297
|
}
|
|
287
298
|
const resolvedInstanceId = instanceId ?? cliToolId;
|
|
288
299
|
return serializePerInstance(captureKeyOf(worktreeId, cliToolId, resolvedInstanceId), async () => {
|
|
300
|
+
// The reader's own report, kept local and copied out below: `report` is
|
|
301
|
+
// the caller's object and must end up holding one verdict, not whatever
|
|
302
|
+
// the last reader to touch it happened to leave there.
|
|
303
|
+
const readerReport = {};
|
|
289
304
|
try {
|
|
290
|
-
const captured = await reader.capture({ worktreeId, cliToolId, instanceId: resolvedInstanceId }, capture);
|
|
305
|
+
const captured = await reader.capture({ worktreeId, cliToolId, instanceId: resolvedInstanceId }, capture, readerReport);
|
|
306
|
+
if (report) {
|
|
307
|
+
// A reader that says nothing is read as `unavailable` — the pre-#2436
|
|
308
|
+
// behaviour, and the safe direction: the scraper keeps the turn.
|
|
309
|
+
report.outcome = captured ? 'captured' : readerReport.outcome ?? 'unavailable';
|
|
310
|
+
}
|
|
291
311
|
// Issue #2377: the moment a relay is waiting for. This is the single
|
|
292
312
|
// point BOTH triggers of a pull capture pass through — the poller's save
|
|
293
313
|
// path and the Stop hook receiver — which is why the relay needs one
|