commandmate 0.47.2 → 0.48.0
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 +215 -215
- package/.next/app-path-routes-manifest.json +41 -41
- package/.next/build-manifest.json +2 -2
- package/.next/prerender-manifest.json +9 -9
- 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/release-notes/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/app/update/route.js +3 -3
- 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/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 +2 -2
- 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/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 +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/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 +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]/direct-input/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/direct-input/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/direct-input/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 +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 +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.js +1 -1
- package/.next/server/app/api/worktrees/[id]/opencode/session/route.js.nft.json +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.js +1 -1
- package/.next/server/app/api/worktrees/[id]/opencode/share/route.js.nft.json +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]/prompt-response/strict/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/prompt-response/strict/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/prompt-response/strict/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 +3 -3
- 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 +1 -103
- 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/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.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 +41 -41
- package/.next/server/chunks/1191.js +11 -4
- package/.next/server/chunks/1501.js +1 -1
- package/.next/server/chunks/1601.js +2 -2
- package/.next/server/chunks/1656.js +2 -2
- package/.next/server/chunks/1945.js +3 -3
- package/.next/server/chunks/1990.js +103 -0
- package/.next/server/chunks/2119.js +1 -1
- package/.next/server/chunks/2404.js +1 -1
- package/.next/server/chunks/2422.js +1 -1
- package/.next/server/chunks/2445.js +1 -1
- package/.next/server/chunks/2534.js +1 -1
- package/.next/server/chunks/255.js +1 -1
- package/.next/server/chunks/3241.js +1 -1
- package/.next/server/chunks/3505.js +2 -2
- package/.next/server/chunks/4248.js +1 -1
- package/.next/server/chunks/4280.js +1 -0
- package/.next/server/chunks/5209.js +1 -1
- package/.next/server/chunks/6119.js +1 -1
- package/.next/server/chunks/6130.js +1 -1
- package/.next/server/chunks/6401.js +1 -1
- package/.next/server/chunks/6486.js +9 -8
- package/.next/server/chunks/6781.js +1 -1
- package/.next/server/chunks/720.js +103 -0
- package/.next/server/chunks/7604.js +2 -0
- package/.next/server/chunks/7905.js +1 -1
- package/.next/server/chunks/8581.js +7 -7
- package/.next/server/chunks/8728.js +3 -3
- package/.next/server/chunks/8791.js +1 -1
- package/.next/server/chunks/8807.js +19 -18
- package/.next/server/chunks/8918.js +2 -104
- package/.next/server/chunks/936.js +1 -1
- package/.next/server/chunks/9390.js +1 -1
- package/.next/server/chunks/9540.js +1 -1
- package/.next/server/chunks/9627.js +3 -3
- package/.next/server/chunks/9731.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/2099-c3e0ab70a1b95dd3.js +1 -0
- package/.next/static/chunks/{2407-a6533805a86d05bd.js → 2407-26139a8a2f1adbe8.js} +1 -1
- package/.next/static/chunks/{2619-04bc32f026a0d946.js → 2619-a305ad4810f1393a.js} +1 -1
- package/.next/static/chunks/{2863-6a07ec82fb52f4dd.js → 2863-f0121af1cbbedb3b.js} +1 -1
- package/.next/static/chunks/{8165-263172c17ae8e8c3.js → 8165-c68b3dc844210e6a.js} +1 -1
- package/.next/static/chunks/8407-35bbfc84793d7004.js +1 -0
- package/.next/static/chunks/9079-406b84d3cc891a87.js +1 -0
- package/.next/static/chunks/app/{layout-538d8d2746ad1588.js → layout-46b4cbf07520d9b6.js} +1 -1
- package/.next/static/chunks/app/sessions/page-fb241bed579c67aa.js +1 -0
- package/.next/static/chunks/app/worktrees/[id]/page-cbc7749a33450cc5.js +1 -0
- package/.next/static/chunks/app/worktrees/[id]/terminal/{page-373ea37b16d930c7.js → page-c601f3b3ece6557e.js} +1 -1
- package/.next/trace +13 -13
- package/dist/cli/commands/auto-yes.d.ts +24 -0
- package/dist/cli/commands/auto-yes.d.ts.map +1 -1
- package/dist/cli/commands/auto-yes.js +73 -3
- package/dist/cli/commands/instances.d.ts.map +1 -1
- package/dist/cli/commands/instances.js +10 -1
- package/dist/cli/commands/prompt.d.ts.map +1 -1
- package/dist/cli/commands/prompt.js +5 -3
- package/dist/cli/commands/send.d.ts.map +1 -1
- package/dist/cli/commands/send.js +15 -0
- package/dist/cli/commands/wait.d.ts.map +1 -1
- package/dist/cli/commands/wait.js +101 -13
- package/dist/cli/docs/agent-operations.d.ts +2 -2
- package/dist/cli/docs/agent-operations.d.ts.map +1 -1
- package/dist/cli/docs/agent-operations.js +27 -0
- package/dist/cli/types/api-responses.d.ts +116 -2
- package/dist/cli/types/api-responses.d.ts.map +1 -1
- package/dist/cli/types/index.d.ts +4 -0
- package/dist/cli/types/index.d.ts.map +1 -1
- package/dist/cli/utils/resource-resolvers.d.ts +0 -18
- package/dist/cli/utils/resource-resolvers.d.ts.map +1 -1
- package/dist/cli/utils/resource-resolvers.js +1 -51
- package/dist/lib/polling/auto-yes-suppression-reason.d.ts +54 -2
- package/dist/lib/polling/auto-yes-suppression-reason.d.ts.map +1 -1
- package/dist/lib/polling/auto-yes-suppression-reason.js +33 -2
- package/dist/lib/prompt-observation/types.d.ts +13 -0
- package/dist/lib/prompt-observation/types.d.ts.map +1 -1
- package/dist/server/locales/en/worktree.json +2 -1
- package/dist/server/locales/ja/worktree.json +2 -1
- package/dist/server/src/lib/auto-yes-poller-answer-target.js +38 -0
- package/dist/server/src/lib/auto-yes-poller-keys.js +35 -0
- package/dist/server/src/lib/auto-yes-poller-session-presence.js +26 -0
- package/dist/server/src/lib/auto-yes-poller-tick.js +233 -0
- package/dist/server/src/lib/auto-yes-poller.js +337 -376
- package/dist/server/src/lib/auto-yes-state.js +50 -2
- package/dist/server/src/lib/cli-tools/submit-verified-sender.js +107 -14
- package/dist/server/src/lib/db/chat-db.js +5 -1
- package/dist/server/src/lib/db/verification-db.js +23 -2
- package/dist/server/src/lib/detection/option-recommendation.js +120 -0
- package/dist/server/src/lib/detection/prompt-detect-multiple-choice-approval-context.js +290 -0
- package/dist/server/src/lib/detection/prompt-detect-multiple-choice.js +396 -350
- package/dist/server/src/lib/detection/prompt-detector.js +92 -21
- package/dist/server/src/lib/detection/prompt-screen-key.js +60 -0
- package/dist/server/src/lib/detection/tools/antigravity/dialog.js +158 -35
- package/dist/server/src/lib/detection/tools/claude/detect.js +111 -10
- package/dist/server/src/lib/detection/tools/command-code/dialog.js +67 -0
- package/dist/server/src/lib/detection/tools/dialog-block.js +16 -0
- package/dist/server/src/lib/detection/waiting-screen-reading.js +98 -0
- package/dist/server/src/lib/hooks/permission-decision-service.js +7 -0
- package/dist/server/src/lib/polling/auto-yes-confirm-page.js +2 -2
- package/dist/server/src/lib/polling/auto-yes-confirmation-ledger-types.js +3 -0
- package/dist/server/src/lib/polling/auto-yes-confirmation-ledger.js +34 -3
- package/dist/server/src/lib/polling/auto-yes-decision-state.js +195 -0
- package/dist/server/src/lib/polling/auto-yes-dialog-gate.js +139 -10
- package/dist/server/src/lib/polling/auto-yes-enter-fallback.js +14 -0
- package/dist/server/src/lib/polling/auto-yes-manual-answer.js +122 -0
- package/dist/server/src/lib/polling/auto-yes-option-effect.js +157 -0
- package/dist/server/src/lib/polling/auto-yes-policy.js +11 -5
- package/dist/server/src/lib/polling/auto-yes-poller-capture.js +15 -2
- package/dist/server/src/lib/polling/auto-yes-poller-generation.js +42 -1
- package/dist/server/src/lib/polling/auto-yes-poller-receipt-log.js +42 -5
- package/dist/server/src/lib/polling/auto-yes-poller-state.js +147 -49
- package/dist/server/src/lib/polling/auto-yes-poller-steps.js +172 -2
- package/dist/server/src/lib/polling/auto-yes-prompt-suppression.js +2 -2
- package/dist/server/src/lib/polling/auto-yes-question-choice.js +57 -0
- package/dist/server/src/lib/polling/auto-yes-resolver.js +96 -21
- package/dist/server/src/lib/polling/auto-yes-suppression-reason.js +33 -2
- package/dist/server/src/lib/polling/auto-yes-suppression-state.js +25 -1
- package/dist/server/src/lib/polling/prompt-screen-reading.js +232 -0
- package/dist/server/src/lib/polling/response-checker-dialog-gate.js +39 -0
- package/dist/server/src/lib/polling/response-checker.js +133 -43
- package/dist/server/src/lib/prompt-answer-sender.js +104 -10
- package/dist/server/src/lib/push/notification-dedup.js +15 -2
- package/dist/server/src/lib/push/prompt-card-state.js +140 -19
- package/dist/server/src/lib/push/prompt-push-gate.js +158 -12
- package/dist/server/src/lib/push/push-sender.js +23 -3
- package/dist/server/src/lib/push/resolution-push-notifier.js +14 -0
- package/dist/server/src/lib/push/waiting-push-notifier.js +139 -17
- package/dist/server/src/lib/realtime/terminal-broadcast.js +16 -0
- package/dist/server/src/lib/session/agent-event-state.js +25 -5
- package/dist/server/src/lib/session/agent-event-turn.js +140 -46
- package/dist/server/src/lib/session/cli-session.js +21 -2
- package/dist/server/src/lib/session/current-output-builder.js +64 -11
- package/dist/server/src/lib/session/send-attempt-state.js +23 -7
- package/dist/server/src/lib/session/waiting-episode-binding-observation.js +289 -0
- package/dist/server/src/lib/session/waiting-episode-binding-resolve.js +38 -0
- package/dist/server/src/lib/session/waiting-episode-binding.js +276 -0
- package/dist/server/src/lib/session/waiting-episode-state.js +307 -17
- package/dist/server/src/lib/session/waiting-episode-verdict.js +174 -0
- package/dist/server/src/lib/tmux/tmux-capture-cache.js +76 -11
- package/dist/server/src/lib/tmux/tmux.js +54 -9
- package/dist/server/src/lib/verification/gate-runner.js +162 -13
- package/dist/server/src/lib/verification/machine-lock.js +5 -0
- package/dist/server/src/lib/ws-server.js +18 -3
- package/dist/server/src/types/auto-yes-answer-mode.js +38 -0
- package/dist/server/src/types/auto-yes-decision.js +61 -0
- package/dist/server/src/types/auto-yes.js +6 -0
- package/dist/server/src/types/prompt-reading.js +32 -0
- package/dist/types/auto-yes-answer-mode.d.ts +35 -0
- package/dist/types/auto-yes-answer-mode.d.ts.map +1 -0
- package/dist/types/auto-yes-answer-mode.js +38 -0
- package/docs/en/user-guide/webapp-guide.md +33 -4
- package/package.json +1 -1
- package/public/sw.js +76 -4
- package/release-notes/0.48.0.json +57 -0
- package/.next/server/chunks/1559.js +0 -2
- package/.next/server/chunks/2460.js +0 -1
- package/.next/server/chunks/5595.js +0 -1
- package/.next/server/chunks/610.js +0 -1
- package/.next/static/chunks/602-1c1488959215e140.js +0 -1
- package/.next/static/chunks/8407-155f84e5ba05f1d5.js +0 -1
- package/.next/static/chunks/9079-24f9fa549ed298f1.js +0 -1
- package/.next/static/chunks/app/sessions/page-e02f1adb1d1c1ccc.js +0 -1
- package/.next/static/chunks/app/worktrees/[id]/page-46aced20b2b1c010.js +0 -1
- /package/.next/static/{etorRtpxbQqqOzdUeXdw2 → PTlAswXLq8PAe2vUJ8dVP}/_buildManifest.js +0 -0
- /package/.next/static/{etorRtpxbQqqOzdUeXdw2 → PTlAswXLq8PAe2vUJ8dVP}/_ssgManifest.js +0 -0
|
@@ -24,6 +24,7 @@ exports.isCaptureWindowSaturated = isCaptureWindowSaturated;
|
|
|
24
24
|
exports.getCaptureGeneration = getCaptureGeneration;
|
|
25
25
|
exports.isCaptureGenerationCurrent = isCaptureGenerationCurrent;
|
|
26
26
|
exports.sliceOutput = sliceOutput;
|
|
27
|
+
exports.sliceCutsHead = sliceCutsHead;
|
|
27
28
|
exports.getCachedCapture = getCachedCapture;
|
|
28
29
|
exports.setCachedCapture = setCachedCapture;
|
|
29
30
|
exports.invalidateCache = invalidateCache;
|
|
@@ -87,6 +88,10 @@ function getCache() {
|
|
|
87
88
|
function getInflight() {
|
|
88
89
|
return (0, global_state_1.getOrInitGlobal)('__tmuxCaptureCacheInflight', () => new Map());
|
|
89
90
|
}
|
|
91
|
+
/** When each in-flight fetch started (Issue #3798), for the callers that join it. */
|
|
92
|
+
function getInflightStartedAt() {
|
|
93
|
+
return (0, global_state_1.getOrInitGlobal)('__tmuxCaptureCacheInflightStartedAt', () => new WeakMap());
|
|
94
|
+
}
|
|
90
95
|
function getGenerationState() {
|
|
91
96
|
return (0, global_state_1.getOrInitGlobal)('__tmuxCaptureCacheGeneration', () => ({ global: 0, sessions: new Map() }));
|
|
92
97
|
}
|
|
@@ -122,8 +127,24 @@ function isCaptureGenerationCurrent(sessionName, generation) {
|
|
|
122
127
|
// sliceOutput()
|
|
123
128
|
// =========================================================================
|
|
124
129
|
/**
|
|
125
|
-
*
|
|
126
|
-
*
|
|
130
|
+
* A capture's rows (Issue #3881 third review N4). tmux ends every row with a
|
|
131
|
+
* newline, so the last one terminates a row and is not a row of its own: a
|
|
132
|
+
* 1000-row pane comes back as 1000 rows and a terminator, which `split` makes
|
|
133
|
+
* 1001 elements.
|
|
134
|
+
*/
|
|
135
|
+
function rowsOf(output) {
|
|
136
|
+
const terminated = output.endsWith('\n');
|
|
137
|
+
return { rows: (terminated ? output.slice(0, -1) : output).split('\n'), terminated };
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Slice output from the end to return the last requestedLines rows.
|
|
141
|
+
* If requestedLines >= total rows, returns the full output.
|
|
142
|
+
*
|
|
143
|
+
* Issue #3881 (third review N4): counted in rows, the terminator kept. Counted
|
|
144
|
+
* in `split` elements, the terminator took one of the requested rows, so a
|
|
145
|
+
* 1000-row pane asked for 1000 rows lost its first row — on an
|
|
146
|
+
* alternate-screen pane, the row that says whether the content begins at the
|
|
147
|
+
* top of the screen.
|
|
127
148
|
*
|
|
128
149
|
* @param fullOutput - Full captured output string
|
|
129
150
|
* @param requestedLines - Number of lines requested from the end
|
|
@@ -132,10 +153,21 @@ function isCaptureGenerationCurrent(sessionName, generation) {
|
|
|
132
153
|
function sliceOutput(fullOutput, requestedLines) {
|
|
133
154
|
if (fullOutput === '')
|
|
134
155
|
return '';
|
|
135
|
-
const
|
|
136
|
-
if (requestedLines >=
|
|
156
|
+
const { rows, terminated } = rowsOf(fullOutput);
|
|
157
|
+
if (requestedLines >= rows.length)
|
|
137
158
|
return fullOutput;
|
|
138
|
-
return
|
|
159
|
+
return rows.slice(-requestedLines).join('\n') + (terminated ? '\n' : '');
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Whether {@link sliceOutput} drops rows from the top of `fullOutput` (Issue
|
|
163
|
+
* #3881 third review N4): the one fact about a capture's top that its row count
|
|
164
|
+
* cannot tell when the window is exactly the pane's height.
|
|
165
|
+
*
|
|
166
|
+
* @param fullOutput - Full captured output string
|
|
167
|
+
* @param requestedLines - Number of lines requested from the end
|
|
168
|
+
*/
|
|
169
|
+
function sliceCutsHead(fullOutput, requestedLines) {
|
|
170
|
+
return fullOutput !== '' && requestedLines < rowsOf(fullOutput).rows.length;
|
|
139
171
|
}
|
|
140
172
|
// =========================================================================
|
|
141
173
|
// getCachedCapture()
|
|
@@ -180,8 +212,10 @@ function getCachedCapture(sessionName, requestedLines) {
|
|
|
180
212
|
* @param sessionName - tmux session name (cache key)
|
|
181
213
|
* @param output - Captured output string
|
|
182
214
|
* @param capturedLines - Number of lines the output was captured with
|
|
215
|
+
* @param fetchStartedAt - When the capture began (Issue #3798); every writer
|
|
216
|
+
* in this app passes it. Defaults to now, the write time
|
|
183
217
|
*/
|
|
184
|
-
function setCachedCapture(sessionName, output, capturedLines) {
|
|
218
|
+
function setCachedCapture(sessionName, output, capturedLines, fetchStartedAt) {
|
|
185
219
|
const cache = getCache();
|
|
186
220
|
const now = Date.now();
|
|
187
221
|
// [SEC4-002] Full sweep: remove all TTL-expired entries
|
|
@@ -209,8 +243,22 @@ function setCachedCapture(sessionName, output, capturedLines) {
|
|
|
209
243
|
output,
|
|
210
244
|
capturedLines,
|
|
211
245
|
timestamp: now,
|
|
246
|
+
fetchStartedAt: fetchStartedAt ?? now,
|
|
212
247
|
});
|
|
213
248
|
}
|
|
249
|
+
/**
|
|
250
|
+
* When the cached capture of `sessionName` was started, or null when nothing
|
|
251
|
+
* live is cached (Issue #3798). Read-only: an expired entry is left for the
|
|
252
|
+
* next read to evict.
|
|
253
|
+
*
|
|
254
|
+
* @param sessionName - tmux session name (cache key)
|
|
255
|
+
*/
|
|
256
|
+
function getCachedCaptureFetchStartedAt(sessionName) {
|
|
257
|
+
const entry = getCache().get(sessionName);
|
|
258
|
+
if (!entry || Date.now() - entry.timestamp > exports.CACHE_TTL_MS)
|
|
259
|
+
return null;
|
|
260
|
+
return entry.fetchStartedAt;
|
|
261
|
+
}
|
|
214
262
|
// =========================================================================
|
|
215
263
|
// invalidateCache()
|
|
216
264
|
// =========================================================================
|
|
@@ -262,10 +310,8 @@ function resetCacheForTesting() {
|
|
|
262
310
|
globalThis.__tmuxCaptureCache = undefined;
|
|
263
311
|
globalThis.__tmuxCaptureCacheInflight = undefined;
|
|
264
312
|
globalThis.__tmuxCaptureCacheGeneration = undefined;
|
|
313
|
+
globalThis.__tmuxCaptureCacheInflightStartedAt = undefined;
|
|
265
314
|
}
|
|
266
|
-
// =========================================================================
|
|
267
|
-
// getOrFetchCapture() - singleflight pattern
|
|
268
|
-
// =========================================================================
|
|
269
315
|
/**
|
|
270
316
|
* Get capture output from cache or fetch it.
|
|
271
317
|
* Implements singleflight pattern: concurrent requests for the same session
|
|
@@ -277,33 +323,52 @@ function resetCacheForTesting() {
|
|
|
277
323
|
* @param sessionName - tmux session name
|
|
278
324
|
* @param requestedLines - Number of lines requested
|
|
279
325
|
* @param fetchFn - Function to fetch output on cache miss
|
|
326
|
+
* @param onServed - Told where the returned frame came from (Issue #3798),
|
|
327
|
+
* right before it is returned; not called when the capture throws
|
|
328
|
+
* @param onSliced - Told whether the slice to `requestedLines` dropped rows
|
|
329
|
+
* from the top of what tmux returned (Issue #3881 third review N4,
|
|
330
|
+
* {@link sliceCutsHead}), right before it is returned
|
|
280
331
|
* @returns Captured output (sliced to requestedLines)
|
|
281
332
|
*/
|
|
282
|
-
async function getOrFetchCapture(sessionName, requestedLines, fetchFn) {
|
|
333
|
+
async function getOrFetchCapture(sessionName, requestedLines, fetchFn, onServed, onSliced) {
|
|
283
334
|
// 1. Check cache first
|
|
335
|
+
const cachedStartedAt = getCachedCaptureFetchStartedAt(sessionName);
|
|
284
336
|
const cached = getCachedCapture(sessionName, requestedLines);
|
|
285
337
|
if (cached !== null) {
|
|
338
|
+
if (cachedStartedAt !== null)
|
|
339
|
+
onServed?.({ fetchStartedAt: cachedStartedAt, source: 'cache' });
|
|
340
|
+
const entry = getCache().get(sessionName);
|
|
341
|
+
if (entry)
|
|
342
|
+
onSliced?.(sliceCutsHead(entry.output, requestedLines));
|
|
286
343
|
return cached;
|
|
287
344
|
}
|
|
288
345
|
// 2. Check for inflight request (singleflight)
|
|
289
346
|
const inflight = getInflight();
|
|
290
347
|
const existingPromise = inflight.get(sessionName);
|
|
291
348
|
if (existingPromise) {
|
|
349
|
+
const joinedStartedAt = getInflightStartedAt().get(existingPromise);
|
|
292
350
|
const result = await existingPromise;
|
|
351
|
+
if (joinedStartedAt !== undefined)
|
|
352
|
+
onServed?.({ fetchStartedAt: joinedStartedAt, source: 'joined' });
|
|
353
|
+
onSliced?.(sliceCutsHead(result, requestedLines));
|
|
293
354
|
return sliceOutput(result, requestedLines);
|
|
294
355
|
}
|
|
295
356
|
// 3. Create new fetch promise
|
|
296
357
|
// Issue #3734: snapshot the generation before fetchFn() starts
|
|
297
358
|
const generation = getCaptureGeneration(sessionName);
|
|
359
|
+
const fetchStartedAt = Date.now();
|
|
298
360
|
const fetchPromise = fetchFn();
|
|
299
361
|
inflight.set(sessionName, fetchPromise);
|
|
362
|
+
getInflightStartedAt().set(fetchPromise, fetchStartedAt);
|
|
300
363
|
try {
|
|
301
364
|
const output = await fetchPromise;
|
|
302
365
|
// Cache non-empty results only [SEC4-007], and only when no invalidation
|
|
303
366
|
// happened while waiting (Issue #3734)
|
|
304
367
|
if (output.length > 0 && isCaptureGenerationCurrent(sessionName, generation)) {
|
|
305
|
-
setCachedCapture(sessionName, output, exports.CACHE_MAX_CAPTURE_LINES);
|
|
368
|
+
setCachedCapture(sessionName, output, exports.CACHE_MAX_CAPTURE_LINES, fetchStartedAt);
|
|
306
369
|
}
|
|
370
|
+
onServed?.({ fetchStartedAt, source: 'fetched' });
|
|
371
|
+
onSliced?.(sliceCutsHead(output, requestedLines));
|
|
307
372
|
return sliceOutput(output, requestedLines);
|
|
308
373
|
}
|
|
309
374
|
finally {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Provides functions to manage tmux sessions for Claude CLI integration
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.DIRECT_INPUT_TEXT_CHUNK_BYTES = exports.REPAINT_INVALIDATE_DELAY_MS = exports.NAVIGATION_KEY_VALUES = exports.SPECIAL_KEY_VALUES = exports.SpecialKeysAbortedError = void 0;
|
|
7
|
+
exports.DIRECT_INPUT_TEXT_CHUNK_BYTES = exports.REPAINT_INVALIDATE_DELAY_MS = exports.NAVIGATION_KEY_VALUES = exports.SPECIAL_KEY_VALUES = exports.SpecialKeysAbortedError = exports.TmuxSendError = void 0;
|
|
8
8
|
exports.exactTarget = exactTarget;
|
|
9
9
|
exports.reconcileSessionGeometry = reconcileSessionGeometry;
|
|
10
10
|
exports.hasSession = hasSession;
|
|
@@ -14,6 +14,7 @@ exports.getPaneCurrentCommand = getPaneCurrentCommand;
|
|
|
14
14
|
exports.listSessions = listSessions;
|
|
15
15
|
exports.createSession = createSession;
|
|
16
16
|
exports.sendKeys = sendKeys;
|
|
17
|
+
exports.deliveryOfExecFailure = deliveryOfExecFailure;
|
|
17
18
|
exports.isSendableSpecialKey = isSendableSpecialKey;
|
|
18
19
|
exports.sendSpecialKeys = sendSpecialKeys;
|
|
19
20
|
exports.capturePane = capturePane;
|
|
@@ -462,8 +463,9 @@ async function createSession(sessionNameOrOptions, cwd) {
|
|
|
462
463
|
* @param sendEnter - Whether to send Enter key after the command (default: true)
|
|
463
464
|
* @param options - {@link SendKeysOptions}; pass `{ literal: true }` for text
|
|
464
465
|
*
|
|
465
|
-
* @throws {
|
|
466
|
-
* combined with `sendEnter`
|
|
466
|
+
* @throws {TmuxSendError} If session doesn't exist, command fails, or `literal` is
|
|
467
|
+
* combined with `sendEnter`; its `delivery` says whether the keys may be on
|
|
468
|
+
* the pane (Issue #3880). A normal return is `delivered`: tmux exited 0.
|
|
467
469
|
*
|
|
468
470
|
* @example
|
|
469
471
|
* ```typescript
|
|
@@ -476,7 +478,7 @@ async function createSession(sessionNameOrOptions, cwd) {
|
|
|
476
478
|
*/
|
|
477
479
|
async function sendKeys(sessionName, keys, sendEnter = true, options) {
|
|
478
480
|
if (options?.literal && sendEnter) {
|
|
479
|
-
throw new
|
|
481
|
+
throw new TmuxSendError('sendKeys: { literal: true } cannot be combined with sendEnter — a trailing C-m would be typed as text. Send Enter as a separate key.', 'not-started', 0);
|
|
480
482
|
}
|
|
481
483
|
// execFile() passes arguments directly without shell interpretation,
|
|
482
484
|
// so no shell-level escaping is needed
|
|
@@ -496,9 +498,45 @@ async function sendKeys(sessionName, keys, sendEnter = true, options) {
|
|
|
496
498
|
}
|
|
497
499
|
catch (error) {
|
|
498
500
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
499
|
-
throw new
|
|
501
|
+
throw new TmuxSendError(`Failed to send keys to tmux session: ${errorMessage}`, deliveryOfExecFailure(error), 0);
|
|
500
502
|
}
|
|
501
503
|
}
|
|
504
|
+
/**
|
|
505
|
+
* A `sendKeys` / `sendSpecialKeys` that failed, with how far it got
|
|
506
|
+
* (Issue #3880). The message is the one these functions always threw.
|
|
507
|
+
*/
|
|
508
|
+
class TmuxSendError extends Error {
|
|
509
|
+
delivery;
|
|
510
|
+
keysDelivered;
|
|
511
|
+
constructor(message,
|
|
512
|
+
/** What the call that failed did; a failed call is never `delivered`. */
|
|
513
|
+
delivery,
|
|
514
|
+
/** How many keys before it tmux was seen to take (`sendSpecialKeys`; 0 for `sendKeys`). */
|
|
515
|
+
keysDelivered) {
|
|
516
|
+
super(message);
|
|
517
|
+
this.delivery = delivery;
|
|
518
|
+
this.keysDelivered = keysDelivered;
|
|
519
|
+
this.name = 'TmuxSendError';
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
exports.TmuxSendError = TmuxSendError;
|
|
523
|
+
/**
|
|
524
|
+
* Which {@link KeyDeliveryState} a rejected `execFile` of `tmux send-keys` is
|
|
525
|
+
* (Issue #3880). Read off the error's shape, never its wording: an exit status
|
|
526
|
+
* (`code` a number) is tmux refusing the command, an errno string (`ENOENT`)
|
|
527
|
+
* is a process that never ran, and anything else — `killed` by the timeout, a
|
|
528
|
+
* `signal`, an error of no known shape — may have left the key on the pane.
|
|
529
|
+
*/
|
|
530
|
+
function deliveryOfExecFailure(error) {
|
|
531
|
+
if (typeof error !== 'object' || error === null)
|
|
532
|
+
return 'indeterminate';
|
|
533
|
+
const { code, killed, signal } = error;
|
|
534
|
+
if (killed === true || (typeof signal === 'string' && signal !== ''))
|
|
535
|
+
return 'indeterminate';
|
|
536
|
+
if (typeof code === 'number' || typeof code === 'string')
|
|
537
|
+
return 'not-started';
|
|
538
|
+
return 'indeterminate';
|
|
539
|
+
}
|
|
502
540
|
/**
|
|
503
541
|
* Allowed tmux special key names for sendSpecialKeys() (multi-key TUI navigation).
|
|
504
542
|
* Used for cursor-based navigation sequences (e.g., ['Down', 'Down', 'Enter']).
|
|
@@ -607,7 +645,9 @@ exports.SpecialKeysAbortedError = SpecialKeysAbortedError;
|
|
|
607
645
|
* @param sessionName - Target session name
|
|
608
646
|
* @param keys - Array of tmux special key names (e.g., ['Down', 'Down', 'Space', 'Enter'])
|
|
609
647
|
* @param options - Optional `shouldContinue`, asked after each delay (Issue #3706)
|
|
610
|
-
* @throws {
|
|
648
|
+
* @throws {TmuxSendError} If any key name is not in the allowed set (`not-started`,
|
|
649
|
+
* nothing sent), or if a tmux command fails — with the keys before it that
|
|
650
|
+
* tmux took and what the failing call did (Issue #3880)
|
|
611
651
|
* @throws {SpecialKeysAbortedError} If `shouldContinue` returned false between two keys
|
|
612
652
|
*/
|
|
613
653
|
async function sendSpecialKeys(sessionName, keys, options) {
|
|
@@ -616,12 +656,15 @@ async function sendSpecialKeys(sessionName, keys, options) {
|
|
|
616
656
|
// Validate all keys are in the allowed set (command injection prevention)
|
|
617
657
|
for (const key of keys) {
|
|
618
658
|
if (!ALLOWED_SPECIAL_KEYS.has(key)) {
|
|
619
|
-
throw new
|
|
659
|
+
throw new TmuxSendError(`Invalid special key: ${key}`, 'not-started', 0);
|
|
620
660
|
}
|
|
621
661
|
}
|
|
662
|
+
// Issue #3880: the keys tmux was seen to take, for the error of a later one.
|
|
663
|
+
let delivered = 0;
|
|
622
664
|
try {
|
|
623
665
|
for (let i = 0; i < keys.length; i++) {
|
|
624
666
|
await execFileAsync('tmux', ['send-keys', '-t', exactTarget(sessionName), keys[i]], { timeout: DEFAULT_TIMEOUT });
|
|
667
|
+
delivered = i + 1;
|
|
625
668
|
// Delay between key presses (skip after the last key)
|
|
626
669
|
if (i < keys.length - 1) {
|
|
627
670
|
await new Promise(resolve => setTimeout(resolve, SPECIAL_KEY_DELAY_MS));
|
|
@@ -635,7 +678,7 @@ async function sendSpecialKeys(sessionName, keys, options) {
|
|
|
635
678
|
if (error instanceof SpecialKeysAbortedError)
|
|
636
679
|
throw error;
|
|
637
680
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
638
|
-
throw new
|
|
681
|
+
throw new TmuxSendError(`Failed to send special keys to tmux session: ${errorMessage}`, deliveryOfExecFailure(error), delivered);
|
|
639
682
|
}
|
|
640
683
|
}
|
|
641
684
|
/**
|
|
@@ -965,7 +1008,9 @@ exports.REPAINT_INVALIDATE_DELAY_MS = 250;
|
|
|
965
1008
|
* Drop the cached frame again once the TUI has had time to repaint.
|
|
966
1009
|
*
|
|
967
1010
|
* Fire-and-forget on purpose: the route must not wait 250 ms to answer, and the
|
|
968
|
-
* work is
|
|
1011
|
+
* work is synchronous and small — `invalidateCache` drops the cached frame and
|
|
1012
|
+
* the inflight capture and advances the session's generation (Issue #3734), so
|
|
1013
|
+
* a capture started before the repaint is not written back either. The timer is `unref()`ed where the runtime supports
|
|
969
1014
|
* it so a pending invalidation can never hold a process (or a test runner) open.
|
|
970
1015
|
*/
|
|
971
1016
|
function scheduleRepaintInvalidation(sessionName) {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* @module lib/verification/gate-runner
|
|
22
22
|
*/
|
|
23
23
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
-
exports.VerificationConflictError = exports.FLAKY_LOG_PREFIX = exports.MUTEX_WAIT_SKIP_REASON = exports.MUTEX_LOG_PREFIX = exports.WORKTREE_INDEX_ENV = exports.WORKTREE_ID_ENV = exports.MAX_CONCURRENT_VERIFICATIONS = exports.CONFIG_GATE_ID = exports.ENV_CLEAN_GATE_ID = exports.SCOPE_GATE_ID = exports.WORK_EVIDENCE_GATE_ID = void 0;
|
|
24
|
+
exports.VerificationConflictError = exports.TIMING_LOG_PREFIX = exports.FLAKY_LOG_PREFIX = exports.MUTEX_WAIT_SKIP_REASON = exports.MUTEX_LOG_PREFIX = exports.WORKTREE_INDEX_ENV = exports.WORKTREE_ID_ENV = exports.MAX_CONCURRENT_VERIFICATIONS = exports.CONFIG_GATE_ID = exports.ENV_CLEAN_GATE_ID = exports.SCOPE_GATE_ID = exports.WORK_EVIDENCE_GATE_ID = void 0;
|
|
25
25
|
exports.waitForVerification = waitForVerification;
|
|
26
26
|
exports.cancelVerification = cancelVerification;
|
|
27
27
|
exports.startVerification = startVerification;
|
|
@@ -87,6 +87,9 @@ const ENV_CLEAN_GATE_COMMAND = 'lsof -iTCP -sTCP:LISTEN / tmux list-sessions / $
|
|
|
87
87
|
* once turns a verification request into a fork bomb on a developer laptop.
|
|
88
88
|
* Excess runs queue rather than fail: the run row already exists and its
|
|
89
89
|
* caller has the id, so a queued run is honest about what will happen.
|
|
90
|
+
*
|
|
91
|
+
* A bound on gates *executing*, not on runs open: a run waiting for a gate's
|
|
92
|
+
* `mutex` gives its slot back for the wait (Issue #3884, see RunSlot).
|
|
90
93
|
*/
|
|
91
94
|
exports.MAX_CONCURRENT_VERIFICATIONS = 2;
|
|
92
95
|
/** Grace period between SIGTERM and SIGKILL for a gate that overran. */
|
|
@@ -144,6 +147,33 @@ exports.MUTEX_WAIT_SKIP_REASON = 'reason=mutex-wait';
|
|
|
144
147
|
* cannot import this module — `tsconfig.cli.json` compiles `src/cli/**` alone.
|
|
145
148
|
*/
|
|
146
149
|
exports.FLAKY_LOG_PREFIX = '[flaky]';
|
|
150
|
+
/**
|
|
151
|
+
* Marker line carrying a command gate's waits and its execution, apart, into
|
|
152
|
+
* `log_tail` (Issue #3884).
|
|
153
|
+
*
|
|
154
|
+
* `[timing] queue=<s> mutex=<s> run=<s>`: `queue` is the time the run waited
|
|
155
|
+
* for a {@link MAX_CONCURRENT_VERIFICATIONS} slot before and during this gate
|
|
156
|
+
* (the first command gate also carries the run's initial queue), `mutex` the
|
|
157
|
+
* time it waited for the gate's machine-wide lock, and `run` the gate's own
|
|
158
|
+
* command — `duration_ms`, which still never includes either wait (#1771).
|
|
159
|
+
* Three numbers because "this run took 40 minutes" has three different fixes:
|
|
160
|
+
* fewer runs at once, fewer heavy gates, or a faster suite.
|
|
161
|
+
*
|
|
162
|
+
* The last line, so the first line keeps meaning what it meant (the skip
|
|
163
|
+
* reason a reader of a `skipped` gate takes, the `[mutex]` / `[flaky]` marker
|
|
164
|
+
* the CLI reads). Written only for a gate that has a `mutex` or that waited for
|
|
165
|
+
* a slot: a gate that did neither has no wait to report, and its log stays
|
|
166
|
+
* byte-identical to before the marker existed. Travels on `log_tail` like
|
|
167
|
+
* `waited=` does, so it reaches `verify --json` and `verify show` without a
|
|
168
|
+
* migration; the history listing, which carries no log, reads this one line
|
|
169
|
+
* into numbers (verification-db.ts parseGateTiming, the `timing` of a gate
|
|
170
|
+
* summary — keep the two formats in step).
|
|
171
|
+
*/
|
|
172
|
+
exports.TIMING_LOG_PREFIX = '[timing]';
|
|
173
|
+
function timingMarker(waits, runMs) {
|
|
174
|
+
return (`${exports.TIMING_LOG_PREFIX} queue=${formatSeconds(waits.queueMs)} ` +
|
|
175
|
+
`mutex=${formatSeconds(waits.mutexMs)} run=${formatSeconds(runMs)}`);
|
|
176
|
+
}
|
|
147
177
|
/**
|
|
148
178
|
* Runs a gate is allowed. Not derived from `retryOnFail` at the call site: the
|
|
149
179
|
* marker states the number so a reader of stored history never has to look up
|
|
@@ -204,13 +234,43 @@ function notRun(logTail) {
|
|
|
204
234
|
// =============================================================================
|
|
205
235
|
let activeVerifications = 0;
|
|
206
236
|
const slotWaiters = [];
|
|
207
|
-
|
|
237
|
+
/**
|
|
238
|
+
* @param priority queue ahead of every run still waiting to start (Issue #3884).
|
|
239
|
+
* Used by a run that gave its slot back while it waited for a gate's
|
|
240
|
+
* `mutex` and now holds that machine-wide lock: every second it spends
|
|
241
|
+
* here is a second the lock is held with nothing running under it.
|
|
242
|
+
*/
|
|
243
|
+
function acquireSlot(priority = false, cancellation) {
|
|
208
244
|
if (activeVerifications < exports.MAX_CONCURRENT_VERIFICATIONS) {
|
|
209
245
|
activeVerifications += 1;
|
|
210
|
-
return Promise.resolve();
|
|
246
|
+
return Promise.resolve(true);
|
|
211
247
|
}
|
|
212
248
|
return new Promise((resolve) => {
|
|
213
|
-
|
|
249
|
+
let settled = false;
|
|
250
|
+
let unregister = () => { };
|
|
251
|
+
const grant = () => {
|
|
252
|
+
settled = true;
|
|
253
|
+
unregister();
|
|
254
|
+
resolve(true);
|
|
255
|
+
};
|
|
256
|
+
if (priority)
|
|
257
|
+
slotWaiters.unshift(grant);
|
|
258
|
+
else
|
|
259
|
+
slotWaiters.push(grant);
|
|
260
|
+
// Issue #3884 N3: a cancelled wait leaves the queue instead of waiting for
|
|
261
|
+
// whatever the other slots are executing. Leaving takes nothing, so there
|
|
262
|
+
// is nothing to hand back.
|
|
263
|
+
if (cancellation) {
|
|
264
|
+
unregister = cancellation.register(() => {
|
|
265
|
+
if (settled)
|
|
266
|
+
return;
|
|
267
|
+
settled = true;
|
|
268
|
+
const at = slotWaiters.indexOf(grant);
|
|
269
|
+
if (at >= 0)
|
|
270
|
+
slotWaiters.splice(at, 1);
|
|
271
|
+
resolve(false);
|
|
272
|
+
});
|
|
273
|
+
}
|
|
214
274
|
});
|
|
215
275
|
}
|
|
216
276
|
function releaseSlot() {
|
|
@@ -223,6 +283,63 @@ function releaseSlot() {
|
|
|
223
283
|
}
|
|
224
284
|
activeVerifications -= 1;
|
|
225
285
|
}
|
|
286
|
+
/**
|
|
287
|
+
* One run's hold on a {@link MAX_CONCURRENT_VERIFICATIONS} slot (Issue #3884 H-11).
|
|
288
|
+
*
|
|
289
|
+
* The slot bounds what *executes*, not what waits. A run used to take its slot
|
|
290
|
+
* up front and keep it while a gate queued for its `mutex` (`cpu.heavy`, up to
|
|
291
|
+
* the gate's whole `timeoutSec`), so one run holding the lock and one waiting
|
|
292
|
+
* for it filled both slots, and a third run could not start even its 5-second
|
|
293
|
+
* `lint`. {@link runGateAttempt} now hands the slot back while it waits for the
|
|
294
|
+
* lock and takes it again — ahead of the runs still waiting to start — once the
|
|
295
|
+
* lock is held. At most {@link MAX_CONCURRENT_VERIFICATIONS} gates still
|
|
296
|
+
* execute at once, and the heavy gates stay one per machine under their mutex,
|
|
297
|
+
* so the load a gate is judged under does not grow.
|
|
298
|
+
*
|
|
299
|
+
* No deadlock: a run waiting for a lock holds no slot, so every slot holder is
|
|
300
|
+
* executing a gate, and each gate ends within its own `timeoutSec`.
|
|
301
|
+
*
|
|
302
|
+
* Also the clock for the `queue=` of {@link TIMING_LOG_PREFIX}: the time spent
|
|
303
|
+
* waiting for a slot since the last {@link RunSlot.takeQueuedMs}.
|
|
304
|
+
*/
|
|
305
|
+
class RunSlot {
|
|
306
|
+
held = false;
|
|
307
|
+
queuedMs = 0;
|
|
308
|
+
get isHeld() {
|
|
309
|
+
return this.held;
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* @param cancellation when given, a cancel abandons the wait (Issue #3884 N3)
|
|
313
|
+
* @returns false when the wait was abandoned: no slot is held
|
|
314
|
+
*/
|
|
315
|
+
async acquire(priority = false, cancellation) {
|
|
316
|
+
if (this.held)
|
|
317
|
+
return true;
|
|
318
|
+
// Only a real queue counts: a free slot is taken without a wait, and a
|
|
319
|
+
// gate that never waited must not grow a `queue=0.0s` line (its log stays
|
|
320
|
+
// byte-identical to before #3884).
|
|
321
|
+
const queued = activeVerifications >= exports.MAX_CONCURRENT_VERIFICATIONS;
|
|
322
|
+
const startedAt = Date.now();
|
|
323
|
+
const taken = await acquireSlot(priority, cancellation);
|
|
324
|
+
this.held = taken;
|
|
325
|
+
if (queued)
|
|
326
|
+
this.queuedMs += Math.max(1, Date.now() - startedAt);
|
|
327
|
+
return taken;
|
|
328
|
+
}
|
|
329
|
+
/** Idempotent: a slot already handed back is not released twice. */
|
|
330
|
+
release() {
|
|
331
|
+
if (!this.held)
|
|
332
|
+
return;
|
|
333
|
+
this.held = false;
|
|
334
|
+
releaseSlot();
|
|
335
|
+
}
|
|
336
|
+
/** The slot wait accumulated since the previous call, and reset it. */
|
|
337
|
+
takeQueuedMs() {
|
|
338
|
+
const queued = this.queuedMs;
|
|
339
|
+
this.queuedMs = 0;
|
|
340
|
+
return queued;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
226
343
|
/**
|
|
227
344
|
* In-flight runs, so callers (tests, and the CLI in Phase 1-4) can await a run
|
|
228
345
|
* that {@link startVerification} deliberately did not wait for.
|
|
@@ -578,7 +695,7 @@ function machineLockPathFor(name) {
|
|
|
578
695
|
* across both, so a retry never keeps a machine-wide resource out of another
|
|
579
696
|
* worktree's hands for a run that has already failed once.
|
|
580
697
|
*/
|
|
581
|
-
async function runGateAttempt(gate, worktreePath, maxLogTailBytes, gateEnv, cancellation) {
|
|
698
|
+
async function runGateAttempt(gate, worktreePath, maxLogTailBytes, gateEnv, cancellation, slot, waits) {
|
|
582
699
|
const mutex = gate.mutex;
|
|
583
700
|
if (!mutex) {
|
|
584
701
|
return runCommand(gate.command, worktreePath, gate.timeoutSec, maxLogTailBytes, gateEnv, cancellation);
|
|
@@ -591,10 +708,25 @@ async function runGateAttempt(gate, worktreePath, maxLogTailBytes, gateEnv, canc
|
|
|
591
708
|
// cancelled while queued behind the lock used to stay `running` for up to
|
|
592
709
|
// half an hour — holding the worktree against the very endpoint that exists
|
|
593
710
|
// to release it.
|
|
711
|
+
//
|
|
712
|
+
// Issue #3884 H-11: the run's slot is handed back for as long as the lock is
|
|
713
|
+
// busy (never when it is free — `onWait` fires only once a claim has failed),
|
|
714
|
+
// and taken back ahead of the runs still waiting to start. See RunSlot.
|
|
594
715
|
const lock = await (0, machine_lock_1.acquireMachineLock)(mutex, {
|
|
595
716
|
timeoutMs: gate.timeoutSec * 1000,
|
|
596
717
|
shouldAbandon: () => cancellation.cancelled,
|
|
718
|
+
onWait: () => slot.release(),
|
|
597
719
|
});
|
|
720
|
+
waits.mutexMs += lock.waitedMs;
|
|
721
|
+
// Taken back even when the lock was not: the run goes on to record (or run)
|
|
722
|
+
// the gates after this one, and those are under the same bound. Not by a
|
|
723
|
+
// cancelled run (#3884 N3): it executes nothing more, and waiting for the
|
|
724
|
+
// gates the other slots are running would keep its worktree held past the
|
|
725
|
+
// cancel endpoint's budget. The wait for the slot is cancellable too — the
|
|
726
|
+
// latch is read below, and the lock released, once it returns.
|
|
727
|
+
if (!cancellation.cancelled)
|
|
728
|
+
await slot.acquire(true, cancellation);
|
|
729
|
+
waits.queueMs += slot.takeQueuedMs();
|
|
598
730
|
if (!lock.acquired) {
|
|
599
731
|
const recordedAt = Date.now();
|
|
600
732
|
return {
|
|
@@ -646,14 +778,28 @@ async function runGateAttempt(gate, worktreePath, maxLogTailBytes, gateEnv, canc
|
|
|
646
778
|
* came free) and an `error` (the command could not be spawned) never started a
|
|
647
779
|
* command, so there is no verdict to seek a second opinion on.
|
|
648
780
|
*/
|
|
649
|
-
async function runCommandGate(gate, worktreePath, maxLogTailBytes, gateEnv, cancellation) {
|
|
650
|
-
|
|
781
|
+
async function runCommandGate(gate, worktreePath, maxLogTailBytes, gateEnv, cancellation, slot) {
|
|
782
|
+
// The slot wait since the previous command gate (for the first one, since
|
|
783
|
+
// the run was opened) belongs to this gate: it is what kept it from starting.
|
|
784
|
+
const waits = { queueMs: slot.takeQueuedMs(), mutexMs: 0 };
|
|
785
|
+
const outcome = await runCommandGateAttempts(gate, worktreePath, maxLogTailBytes, gateEnv, cancellation, slot, waits);
|
|
786
|
+
if (!gate.mutex && waits.queueMs === 0)
|
|
787
|
+
return outcome;
|
|
788
|
+
return {
|
|
789
|
+
...outcome,
|
|
790
|
+
logTail: `${outcome.logTail ? `${outcome.logTail}\n` : ''}${timingMarker(waits, outcome.durationMs)}`,
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
/** {@link runCommandGate}'s attempts, with the waits they accumulate. */
|
|
794
|
+
async function runCommandGateAttempts(gate, worktreePath, maxLogTailBytes, gateEnv, cancellation, slot, waits) {
|
|
795
|
+
const attempt = () => runGateAttempt(gate, worktreePath, maxLogTailBytes, gateEnv, cancellation, slot, waits);
|
|
796
|
+
const first = await attempt();
|
|
651
797
|
// A cancelled attempt reports `skipped`, so the retry below is already ruled
|
|
652
798
|
// out by the status check: an operator's Stop must not buy a second run of
|
|
653
799
|
// the very command they stopped.
|
|
654
800
|
if (gate.retryOnFail !== 1 || first.status !== 'failed')
|
|
655
801
|
return first;
|
|
656
|
-
const second = await
|
|
802
|
+
const second = await attempt();
|
|
657
803
|
if (second.status !== 'passed' && second.status !== 'failed') {
|
|
658
804
|
// The retry reached no verdict of its own, so there is nothing to compare
|
|
659
805
|
// the first run against. The first run's FAIL stands unchanged — reporting
|
|
@@ -1024,7 +1170,9 @@ async function executeRun(db, runId, worktreeId, worktreePath,
|
|
|
1024
1170
|
/** Issue #2043: which agent instance the run is judging, for the second witness. */
|
|
1025
1171
|
instanceId, config, selection, baseRef, task, detachedContract, envClean,
|
|
1026
1172
|
/** Issue #2063: the operator's Stop switch for this run. */
|
|
1027
|
-
cancellation
|
|
1173
|
+
cancellation,
|
|
1174
|
+
/** Issue #3884: handed back while a gate waits for its mutex. */
|
|
1175
|
+
slot) {
|
|
1028
1176
|
const { maxLogTailBytes, skipInPrimaryCheckout } = config.options;
|
|
1029
1177
|
const { runWorkEvidence, gates } = selection;
|
|
1030
1178
|
// Issue #2043. A thunk rather than a number so the store is read at the
|
|
@@ -1209,7 +1357,7 @@ cancellation) {
|
|
|
1209
1357
|
statuses.push(recordNotRun(gate.id, gate.command, gate.source, run_verdict_vocabulary_1.PRIMARY_CHECKOUT_SKIP_LOG).status);
|
|
1210
1358
|
continue;
|
|
1211
1359
|
}
|
|
1212
|
-
const outcome = await runGate(gate.id, gate.command, gate.source, () => runCommandGate(gate, worktreePath, maxLogTailBytes, gateEnv(), cancellation));
|
|
1360
|
+
const outcome = await runGate(gate.id, gate.command, gate.source, () => runCommandGate(gate, worktreePath, maxLogTailBytes, gateEnv(), cancellation, slot));
|
|
1213
1361
|
statuses.push(outcome.status);
|
|
1214
1362
|
}
|
|
1215
1363
|
// The loop's own check cannot see a cancel that landed during the LAST gate:
|
|
@@ -1487,8 +1635,9 @@ async function startVerification(input) {
|
|
|
1487
1635
|
baseline: resolvedSelection.runEnvClean && taskId ? (0, env_snapshot_1.loadEnvSnapshot)(taskId) : null,
|
|
1488
1636
|
decision: envCleanDecision,
|
|
1489
1637
|
};
|
|
1638
|
+
const slot = new RunSlot();
|
|
1490
1639
|
const completion = (async () => {
|
|
1491
|
-
await
|
|
1640
|
+
await slot.acquire();
|
|
1492
1641
|
let terminalStatus = 'error';
|
|
1493
1642
|
try {
|
|
1494
1643
|
// Cancelled while queued behind MAX_CONCURRENT_VERIFICATIONS: nothing was
|
|
@@ -1499,7 +1648,7 @@ async function startVerification(input) {
|
|
|
1499
1648
|
(0, db_1.finishVerificationRun)(db, run.id, 'cancelled');
|
|
1500
1649
|
return 'cancelled';
|
|
1501
1650
|
}
|
|
1502
|
-
const status = await executeRun(db, run.id, input.worktreeId, input.worktreePath, input.instanceId, resolvedConfig, resolvedSelection, baseRef, task, detachedContract, envCleanContext, cancellation);
|
|
1651
|
+
const status = await executeRun(db, run.id, input.worktreeId, input.worktreePath, input.instanceId, resolvedConfig, resolvedSelection, baseRef, task, detachedContract, envCleanContext, cancellation, slot);
|
|
1503
1652
|
terminalStatus = status;
|
|
1504
1653
|
(0, db_1.finishVerificationRun)(db, run.id, status);
|
|
1505
1654
|
return status;
|
|
@@ -1526,7 +1675,7 @@ async function startVerification(input) {
|
|
|
1526
1675
|
// after the task transition so the run's own history is complete before
|
|
1527
1676
|
// anything is said about it.
|
|
1528
1677
|
notifyRunFailure(db, input, run.id, taskId, terminalStatus);
|
|
1529
|
-
|
|
1678
|
+
slot.release();
|
|
1530
1679
|
inFlight.delete(run.id);
|
|
1531
1680
|
cancellations.delete(run.id);
|
|
1532
1681
|
}
|
|
@@ -161,6 +161,7 @@ async function acquireMachineLock(name, options) {
|
|
|
161
161
|
// A stale record is broken at most once per token, so a directory nobody can
|
|
162
162
|
// remove degrades into ordinary waiting instead of a spin loop.
|
|
163
163
|
const broken = new Set();
|
|
164
|
+
let waiting = false;
|
|
164
165
|
for (;;) {
|
|
165
166
|
// Issue #2063. Checked before the claim as well as after the sleep, so a
|
|
166
167
|
// caller that was already cancelled when it got here never takes a lock it
|
|
@@ -210,6 +211,10 @@ async function acquireMachineLock(name, options) {
|
|
|
210
211
|
if (now >= deadline) {
|
|
211
212
|
return { acquired: false, waitedMs: now - startedAt, heldBy: describeOwner(holder) };
|
|
212
213
|
}
|
|
214
|
+
if (!waiting) {
|
|
215
|
+
waiting = true;
|
|
216
|
+
options.onWait?.();
|
|
217
|
+
}
|
|
213
218
|
await sleep(Math.min(pollIntervalMs, deadline - now));
|
|
214
219
|
}
|
|
215
220
|
}
|
|
@@ -738,7 +738,11 @@ function handleDisconnect(ws) {
|
|
|
738
738
|
// what it obtains instead of registering it on a socket that is gone.
|
|
739
739
|
clientInfo.terminalSubscriptionSeq += 1;
|
|
740
740
|
if (clientInfo.terminalSubscription) {
|
|
741
|
-
|
|
741
|
+
// Issue #3749: not awaited, so a rejection would otherwise surface as an
|
|
742
|
+
// unhandled rejection.
|
|
743
|
+
clientInfo.terminalSubscription.unsubscribe().catch(() => {
|
|
744
|
+
// Best-effort cleanup on disconnect.
|
|
745
|
+
});
|
|
742
746
|
clientInfo.terminalSubscription = null;
|
|
743
747
|
}
|
|
744
748
|
// Remove client from clients map
|
|
@@ -792,7 +796,13 @@ async function handleTerminalSubscribe(ws, message) {
|
|
|
792
796
|
// Detached before the await, so a request arriving meanwhile neither
|
|
793
797
|
// releases it a second time nor has its own registration cleared by ours.
|
|
794
798
|
clientInfo.terminalSubscription = null;
|
|
795
|
-
|
|
799
|
+
try {
|
|
800
|
+
await previous.unsubscribe();
|
|
801
|
+
}
|
|
802
|
+
catch {
|
|
803
|
+
// Issue #3749: best-effort, like the other teardowns. Letting it throw
|
|
804
|
+
// ended this handler here and the new subscribe silently never happened.
|
|
805
|
+
}
|
|
796
806
|
}
|
|
797
807
|
if (!isCurrent())
|
|
798
808
|
return;
|
|
@@ -972,7 +982,7 @@ async function handleTerminalUnsubscribe(ws) {
|
|
|
972
982
|
return;
|
|
973
983
|
// Issue #3733: also withdraws a subscribe still in flight, which would
|
|
974
984
|
// otherwise register after this and keep streaming.
|
|
975
|
-
clientInfo.terminalSubscriptionSeq
|
|
985
|
+
const seq = ++clientInfo.terminalSubscriptionSeq;
|
|
976
986
|
const subscription = clientInfo.terminalSubscription;
|
|
977
987
|
if (!subscription) {
|
|
978
988
|
return;
|
|
@@ -985,6 +995,11 @@ async function handleTerminalUnsubscribe(ws) {
|
|
|
985
995
|
catch {
|
|
986
996
|
// Best-effort cleanup during terminal unsubscribe.
|
|
987
997
|
}
|
|
998
|
+
// Issue #3749: a subscribe that started while the teardown was awaited may
|
|
999
|
+
// already have said `connected:true`; a `connected:false` sent after it
|
|
1000
|
+
// would contradict the subscription that is now live.
|
|
1001
|
+
if (clients.get(ws) !== clientInfo || clientInfo.terminalSubscriptionSeq !== seq)
|
|
1002
|
+
return;
|
|
988
1003
|
sendTerminalEvent(ws, { type: 'terminal_status', connected: false });
|
|
989
1004
|
}
|
|
990
1005
|
// ---------------------------------------------------------------------------
|