gsd-pi 2.63.0-dev.026d309 → 2.63.0-dev.351157b
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/README.md +46 -134
- package/dist/cli.js +44 -6
- package/dist/help-text.js +4 -1
- package/dist/onboarding.js +15 -8
- package/dist/resource-loader.js +18 -3
- package/dist/resources/extensions/cmux/index.js +21 -12
- package/dist/resources/extensions/gsd/auto/finalize-timeout.js +40 -0
- package/dist/resources/extensions/gsd/auto/loop.js +4 -0
- package/dist/resources/extensions/gsd/auto/phases.js +123 -22
- package/dist/resources/extensions/gsd/auto/session.js +8 -0
- package/dist/resources/extensions/gsd/auto-dashboard.js +9 -3
- package/dist/resources/extensions/gsd/auto-post-unit.js +45 -10
- package/dist/resources/extensions/gsd/auto-prompts.js +25 -0
- package/dist/resources/extensions/gsd/auto-recovery.js +15 -7
- package/dist/resources/extensions/gsd/auto-start.js +10 -21
- package/dist/resources/extensions/gsd/auto-tool-tracking.js +17 -0
- package/dist/resources/extensions/gsd/auto-worktree.js +13 -7
- package/dist/resources/extensions/gsd/auto.js +19 -2
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +73 -60
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +13 -0
- package/dist/resources/extensions/gsd/bootstrap/query-tools.js +85 -0
- package/dist/resources/extensions/gsd/bootstrap/register-extension.js +3 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +9 -1
- package/dist/resources/extensions/gsd/bootstrap/sanitize-complete-milestone.js +54 -0
- package/dist/resources/extensions/gsd/commands-handlers.js +9 -4
- package/dist/resources/extensions/gsd/constants.js +42 -0
- package/dist/resources/extensions/gsd/db-writer.js +72 -4
- package/dist/resources/extensions/gsd/forensics.js +20 -4
- package/dist/resources/extensions/gsd/gsd-db.js +64 -17
- package/dist/resources/extensions/gsd/guided-flow.js +19 -0
- package/dist/resources/extensions/gsd/metrics.js +27 -1
- package/dist/resources/extensions/gsd/native-git-bridge.js +5 -3
- package/dist/resources/extensions/gsd/preferences-types.js +1 -0
- package/dist/resources/extensions/gsd/preferences.js +7 -2
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +2 -0
- package/dist/resources/extensions/gsd/prompts/doctor-heal.md +1 -0
- package/dist/resources/extensions/gsd/prompts/forensics.md +2 -0
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +2 -0
- package/dist/resources/extensions/gsd/prompts/system.md +1 -0
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
- package/dist/resources/extensions/gsd/roadmap-mutations.js +1 -1
- package/dist/resources/extensions/gsd/roadmap-slices.js +9 -5
- package/dist/resources/extensions/gsd/slice-parallel-conflict.js +67 -0
- package/dist/resources/extensions/gsd/slice-parallel-eligibility.js +51 -0
- package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +378 -0
- package/dist/resources/extensions/gsd/state.js +74 -14
- package/dist/resources/extensions/gsd/status-guards.js +11 -0
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +17 -12
- package/dist/resources/extensions/gsd/tools/complete-slice.js +40 -26
- package/dist/resources/extensions/gsd/tools/complete-task.js +12 -12
- package/dist/resources/extensions/gsd/tools/plan-milestone.js +33 -25
- package/dist/resources/extensions/gsd/tools/plan-slice.js +5 -8
- package/dist/resources/extensions/gsd/workflow-projections.js +21 -5
- package/dist/resources/extensions/gsd/worktree-manager.js +82 -29
- package/dist/resources/extensions/gsd/worktree-resolver.js +4 -3
- package/dist/resources/extensions/mcp-client/auth.js +101 -0
- package/dist/resources/extensions/mcp-client/index.js +10 -1
- package/dist/resources/extensions/ollama/index.js +6 -12
- package/dist/resources/extensions/ollama/model-capabilities.js +37 -34
- package/dist/resources/extensions/ollama/ndjson-stream.js +54 -0
- package/dist/resources/extensions/ollama/ollama-chat-provider.js +380 -0
- package/dist/resources/extensions/ollama/ollama-client.js +23 -32
- package/dist/resources/extensions/ollama/ollama-discovery.js +2 -7
- package/dist/resources/extensions/ollama/ollama-tool.js +62 -0
- package/dist/resources/extensions/ollama/thinking-parser.js +104 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +14 -14
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +14 -14
- package/dist/web/standalone/.next/server/chunks/6897.js +12 -0
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/pi-agent-core/dist/agent-loop.d.ts +8 -0
- package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +50 -0
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/src/agent-loop.test.ts +221 -5
- package/packages/pi-agent-core/src/agent-loop.ts +53 -0
- package/packages/pi-ai/dist/types.d.ts +16 -1
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/src/types.ts +18 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +9 -0
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js +50 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js +41 -0
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts +7 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +31 -4
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.test.js +28 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js +3 -3
- package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +23 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.js +80 -56
- package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +10 -0
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/src/core/auth-storage.test.ts +53 -0
- package/packages/pi-coding-agent/src/core/auth-storage.ts +66 -1
- package/packages/pi-coding-agent/src/core/extensions/loader.test.ts +39 -1
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +34 -4
- package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -0
- package/packages/pi-coding-agent/src/core/model-registry.ts +2 -0
- package/packages/pi-coding-agent/src/core/model-resolver.ts +3 -3
- package/packages/pi-coding-agent/src/core/resource-loader.ts +89 -56
- package/packages/pi-coding-agent/src/core/sdk.ts +11 -0
- package/src/resources/extensions/cmux/index.ts +18 -12
- package/src/resources/extensions/gsd/auto/finalize-timeout.ts +46 -0
- package/src/resources/extensions/gsd/auto/loop.ts +5 -0
- package/src/resources/extensions/gsd/auto/phases.ts +156 -34
- package/src/resources/extensions/gsd/auto/session.ts +9 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +11 -3
- package/src/resources/extensions/gsd/auto-post-unit.ts +53 -12
- package/src/resources/extensions/gsd/auto-prompts.ts +21 -0
- package/src/resources/extensions/gsd/auto-recovery.ts +9 -8
- package/src/resources/extensions/gsd/auto-start.ts +11 -20
- package/src/resources/extensions/gsd/auto-tool-tracking.ts +19 -0
- package/src/resources/extensions/gsd/auto-worktree.ts +14 -6
- package/src/resources/extensions/gsd/auto.ts +22 -1
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +74 -60
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +15 -0
- package/src/resources/extensions/gsd/bootstrap/query-tools.ts +98 -0
- package/src/resources/extensions/gsd/bootstrap/register-extension.ts +4 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +9 -1
- package/src/resources/extensions/gsd/bootstrap/sanitize-complete-milestone.ts +57 -0
- package/src/resources/extensions/gsd/commands-handlers.ts +10 -4
- package/src/resources/extensions/gsd/constants.ts +44 -0
- package/src/resources/extensions/gsd/db-writer.ts +78 -4
- package/src/resources/extensions/gsd/forensics.ts +21 -5
- package/src/resources/extensions/gsd/gsd-db.ts +64 -17
- package/src/resources/extensions/gsd/guided-flow.ts +22 -0
- package/src/resources/extensions/gsd/metrics.ts +28 -1
- package/src/resources/extensions/gsd/native-git-bridge.ts +5 -3
- package/src/resources/extensions/gsd/preferences-types.ts +3 -0
- package/src/resources/extensions/gsd/preferences.ts +9 -2
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
- package/src/resources/extensions/gsd/prompts/complete-slice.md +2 -0
- package/src/resources/extensions/gsd/prompts/doctor-heal.md +1 -0
- package/src/resources/extensions/gsd/prompts/forensics.md +2 -0
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +2 -0
- package/src/resources/extensions/gsd/prompts/system.md +1 -0
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
- package/src/resources/extensions/gsd/roadmap-mutations.ts +1 -1
- package/src/resources/extensions/gsd/roadmap-slices.ts +10 -5
- package/src/resources/extensions/gsd/slice-parallel-conflict.ts +86 -0
- package/src/resources/extensions/gsd/slice-parallel-eligibility.ts +73 -0
- package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +477 -0
- package/src/resources/extensions/gsd/state.ts +67 -12
- package/src/resources/extensions/gsd/status-guards.ts +13 -0
- package/src/resources/extensions/gsd/tests/artifact-corruption-2630.test.ts +288 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +34 -13
- package/src/resources/extensions/gsd/tests/cmux.test.ts +58 -0
- package/src/resources/extensions/gsd/tests/cold-resume-db-reopen.test.ts +51 -0
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +140 -0
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/dashboard-model-label-ordering.test.ts +107 -0
- package/src/resources/extensions/gsd/tests/db-access-guardrails.test.ts +109 -0
- package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +13 -9
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +134 -0
- package/src/resources/extensions/gsd/tests/deferred-slice-dispatch.test.ts +203 -0
- package/src/resources/extensions/gsd/tests/discuss-tool-scoping.test.ts +130 -0
- package/src/resources/extensions/gsd/tests/doctor-fix-flag.test.ts +92 -0
- package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +116 -0
- package/src/resources/extensions/gsd/tests/forensics-stuck-loops.test.ts +103 -0
- package/src/resources/extensions/gsd/tests/insert-slice-no-wipe.test.ts +88 -0
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +27 -7
- package/src/resources/extensions/gsd/tests/integration/idle-recovery.test.ts +34 -0
- package/src/resources/extensions/gsd/tests/metrics.test.ts +116 -1
- package/src/resources/extensions/gsd/tests/milestone-status-tool.test.ts +201 -0
- package/src/resources/extensions/gsd/tests/plan-milestone-title.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +82 -18
- package/src/resources/extensions/gsd/tests/preferences.test.ts +10 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +25 -0
- package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +69 -0
- package/src/resources/extensions/gsd/tests/shared-wal.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/slice-context-injection.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/slice-parallel-conflict.test.ts +92 -0
- package/src/resources/extensions/gsd/tests/slice-parallel-eligibility.test.ts +95 -0
- package/src/resources/extensions/gsd/tests/slice-parallel-orchestrator.test.ts +83 -0
- package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +103 -0
- package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +349 -0
- package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +35 -2
- package/src/resources/extensions/gsd/tests/worktree-health-monorepo.test.ts +73 -0
- package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +34 -0
- package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +148 -0
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +34 -20
- package/src/resources/extensions/gsd/tools/complete-slice.ts +41 -26
- package/src/resources/extensions/gsd/tools/complete-task.ts +12 -12
- package/src/resources/extensions/gsd/tools/plan-milestone.ts +55 -30
- package/src/resources/extensions/gsd/tools/plan-slice.ts +13 -8
- package/src/resources/extensions/gsd/types.ts +44 -22
- package/src/resources/extensions/gsd/workflow-projections.ts +23 -5
- package/src/resources/extensions/gsd/worktree-manager.ts +76 -28
- package/src/resources/extensions/gsd/worktree-resolver.ts +4 -3
- package/src/resources/extensions/mcp-client/auth.ts +149 -0
- package/src/resources/extensions/mcp-client/index.ts +16 -1
- package/src/resources/extensions/ollama/index.ts +6 -14
- package/src/resources/extensions/ollama/model-capabilities.ts +41 -34
- package/src/resources/extensions/ollama/ndjson-stream.ts +63 -0
- package/src/resources/extensions/ollama/ollama-chat-provider.ts +459 -0
- package/src/resources/extensions/ollama/ollama-client.ts +30 -30
- package/src/resources/extensions/ollama/ollama-discovery.ts +5 -8
- package/src/resources/extensions/ollama/ollama-tool.ts +69 -0
- package/src/resources/extensions/ollama/tests/ollama-discovery.test.ts +0 -27
- package/src/resources/extensions/ollama/thinking-parser.ts +116 -0
- package/src/resources/extensions/ollama/types.ts +23 -0
- package/dist/web/standalone/.next/server/chunks/2229.js +0 -12
- /package/dist/web/standalone/.next/static/{TTlAguZQ5vR9EOv6G8cel → QmuF-eAbuU_2MQ03t38qr}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{TTlAguZQ5vR9EOv6G8cel → QmuF-eAbuU_2MQ03t38qr}/_ssgManifest.js +0 -0
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFileSync, spawn } from "node:child_process";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
-
import { promisify } from "node:util";
|
|
6
5
|
import type { GSDPreferences } from "../gsd/preferences.js";
|
|
7
6
|
import type { GSDState, Phase } from "../gsd/types.js";
|
|
8
|
-
|
|
9
|
-
const execFileAsync = promisify(execFile);
|
|
10
7
|
const DEFAULT_SOCKET_PATH = "/tmp/cmux.sock";
|
|
11
8
|
const STATUS_KEY = "gsd";
|
|
12
9
|
const lastSidebarSnapshots = new Map<string, string>();
|
|
@@ -200,6 +197,7 @@ export class CmuxClient {
|
|
|
200
197
|
return execFileSync("cmux", args, {
|
|
201
198
|
encoding: "utf-8",
|
|
202
199
|
timeout: 3000,
|
|
200
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
203
201
|
env: process.env,
|
|
204
202
|
});
|
|
205
203
|
} catch {
|
|
@@ -209,16 +207,24 @@ export class CmuxClient {
|
|
|
209
207
|
|
|
210
208
|
private async runAsync(args: string[]): Promise<string | null> {
|
|
211
209
|
if (!this.canRun()) return null;
|
|
212
|
-
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
timeout: 5000,
|
|
210
|
+
return new Promise<string | null>((resolve) => {
|
|
211
|
+
const child = spawn("cmux", args, {
|
|
212
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
216
213
|
env: process.env,
|
|
217
214
|
});
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
215
|
+
const chunks: Buffer[] = [];
|
|
216
|
+
let settled = false;
|
|
217
|
+
const done = (result: string | null) => {
|
|
218
|
+
if (!settled) { settled = true; resolve(result); }
|
|
219
|
+
};
|
|
220
|
+
const timer = setTimeout(() => { child.kill(); done(null); }, 5000);
|
|
221
|
+
child.stdout!.on("data", (chunk: Buffer) => chunks.push(chunk));
|
|
222
|
+
child.on("close", (code) => {
|
|
223
|
+
clearTimeout(timer);
|
|
224
|
+
done(code === 0 ? Buffer.concat(chunks).toString("utf-8") : null);
|
|
225
|
+
});
|
|
226
|
+
child.on("error", () => { clearTimeout(timer); done(null); });
|
|
227
|
+
});
|
|
222
228
|
}
|
|
223
229
|
|
|
224
230
|
getCapabilities(): unknown | null {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* auto/finalize-timeout.ts — Timeout guard for post-unit finalization.
|
|
3
|
+
*
|
|
4
|
+
* Prevents the auto-loop from hanging indefinitely when
|
|
5
|
+
* postUnitPostVerification() never resolves (#2344).
|
|
6
|
+
*
|
|
7
|
+
* Leaf module — no imports from auto/ to avoid circular dependencies.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** Timeout for postUnitPostVerification in runFinalize (ms). */
|
|
11
|
+
export const FINALIZE_POST_TIMEOUT_MS = 60_000;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Race a promise against a timeout. Returns an object indicating whether
|
|
15
|
+
* the timeout fired and the resolved value (if any).
|
|
16
|
+
*
|
|
17
|
+
* Unlike Promise.race with a rejection, this returns a discriminated
|
|
18
|
+
* result so callers can handle timeouts as a recoverable condition
|
|
19
|
+
* rather than an exception.
|
|
20
|
+
*
|
|
21
|
+
* The timeout timer is always cleaned up, whether the promise resolves
|
|
22
|
+
* or the timeout fires.
|
|
23
|
+
*/
|
|
24
|
+
export async function withTimeout<T>(
|
|
25
|
+
promise: Promise<T>,
|
|
26
|
+
timeoutMs: number,
|
|
27
|
+
label: string,
|
|
28
|
+
): Promise<{ value: T; timedOut: false } | { value: undefined; timedOut: true }> {
|
|
29
|
+
let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
|
|
30
|
+
|
|
31
|
+
const timeoutPromise = new Promise<{ value: undefined; timedOut: true }>((resolve) => {
|
|
32
|
+
timeoutHandle = setTimeout(() => {
|
|
33
|
+
resolve({ value: undefined, timedOut: true });
|
|
34
|
+
}, timeoutMs);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const result = await Promise.race([
|
|
39
|
+
promise.then((value) => ({ value, timedOut: false as const })),
|
|
40
|
+
timeoutPromise,
|
|
41
|
+
]);
|
|
42
|
+
return result;
|
|
43
|
+
} finally {
|
|
44
|
+
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -259,6 +259,11 @@ export async function autoLoop(
|
|
|
259
259
|
// ── Blanket catch: absorb unexpected exceptions, apply graduated recovery ──
|
|
260
260
|
const msg = loopErr instanceof Error ? loopErr.message : String(loopErr);
|
|
261
261
|
|
|
262
|
+
// Always emit iteration-end on error so the journal records iteration
|
|
263
|
+
// completion even on failure (#2344). Without this, errors in
|
|
264
|
+
// runFinalize leave the journal incomplete, making diagnosis harder.
|
|
265
|
+
deps.emitJournalEvent({ ts: new Date().toISOString(), flowId, seq: nextSeq(), eventType: "iteration-end", data: { iteration, error: msg } });
|
|
266
|
+
|
|
262
267
|
// ── Infrastructure errors: immediate stop, no retry ──
|
|
263
268
|
// These are unrecoverable (disk full, OOM, etc.). Retrying just burns
|
|
264
269
|
// LLM budget on guaranteed failures.
|
|
@@ -26,13 +26,17 @@ import { runUnit } from "./run-unit.js";
|
|
|
26
26
|
import { debugLog } from "../debug-logger.js";
|
|
27
27
|
import { PROJECT_FILES } from "../detection.js";
|
|
28
28
|
import { MergeConflictError } from "../git-service.js";
|
|
29
|
-
import { join, basename } from "node:path";
|
|
30
|
-
import { existsSync, cpSync } from "node:fs";
|
|
29
|
+
import { join, basename, dirname, parse as parsePath } from "node:path";
|
|
30
|
+
import { existsSync, cpSync, readdirSync } from "node:fs";
|
|
31
31
|
import { logWarning, logError } from "../workflow-logger.js";
|
|
32
32
|
import { gsdRoot } from "../paths.js";
|
|
33
33
|
import { atomicWriteSync } from "../atomic-write.js";
|
|
34
34
|
import { verifyExpectedArtifact, diagnoseExpectedArtifact, buildLoopRemediationSteps } from "../auto-recovery.js";
|
|
35
35
|
import { writeUnitRuntimeRecord } from "../unit-runtime.js";
|
|
36
|
+
import { withTimeout, FINALIZE_POST_TIMEOUT_MS } from "./finalize-timeout.js";
|
|
37
|
+
import { getEligibleSlices } from "../slice-parallel-eligibility.js";
|
|
38
|
+
import { startSliceParallel } from "../slice-parallel-orchestrator.js";
|
|
39
|
+
import { isDbAvailable, getMilestoneSlices } from "../gsd-db.js";
|
|
36
40
|
|
|
37
41
|
// ─── generateMilestoneReport ──────────────────────────────────────────────────
|
|
38
42
|
|
|
@@ -218,6 +222,63 @@ export async function runPreDispatch(
|
|
|
218
222
|
statePhase: state.phase,
|
|
219
223
|
});
|
|
220
224
|
|
|
225
|
+
// ── Slice-level parallelism gate (#2340) ─────────────────────────────
|
|
226
|
+
// When slice_parallel is enabled, check if multiple slices are eligible
|
|
227
|
+
// for parallel execution. If so, dispatch them in parallel and stop the
|
|
228
|
+
// sequential loop. Workers are spawned via slice-parallel-orchestrator.ts.
|
|
229
|
+
if (
|
|
230
|
+
prefs?.slice_parallel?.enabled &&
|
|
231
|
+
mid &&
|
|
232
|
+
!process.env.GSD_PARALLEL_WORKER &&
|
|
233
|
+
isDbAvailable()
|
|
234
|
+
) {
|
|
235
|
+
try {
|
|
236
|
+
const dbSlices = getMilestoneSlices(mid);
|
|
237
|
+
if (dbSlices.length > 0) {
|
|
238
|
+
const doneIds = new Set(dbSlices.filter(sl => sl.status === "complete" || sl.status === "done").map(sl => sl.id));
|
|
239
|
+
const sliceInputs = dbSlices.map(sl => ({
|
|
240
|
+
id: sl.id,
|
|
241
|
+
done: doneIds.has(sl.id),
|
|
242
|
+
depends: sl.depends ?? [],
|
|
243
|
+
}));
|
|
244
|
+
const eligible = getEligibleSlices(sliceInputs, doneIds);
|
|
245
|
+
if (eligible.length > 1) {
|
|
246
|
+
debugLog("autoLoop", {
|
|
247
|
+
phase: "slice-parallel-dispatch",
|
|
248
|
+
iteration: ic.iteration,
|
|
249
|
+
mid,
|
|
250
|
+
eligibleSlices: eligible.map(e => e.id),
|
|
251
|
+
});
|
|
252
|
+
ctx.ui.notify(
|
|
253
|
+
`Slice-parallel: dispatching ${eligible.length} eligible slices for ${mid}.`,
|
|
254
|
+
"info",
|
|
255
|
+
);
|
|
256
|
+
const result = await startSliceParallel(
|
|
257
|
+
s.basePath,
|
|
258
|
+
mid,
|
|
259
|
+
eligible,
|
|
260
|
+
{ maxWorkers: prefs.slice_parallel.max_workers ?? 2 },
|
|
261
|
+
);
|
|
262
|
+
if (result.started.length > 0) {
|
|
263
|
+
ctx.ui.notify(
|
|
264
|
+
`Slice-parallel: started ${result.started.length} worker(s): ${result.started.join(", ")}.`,
|
|
265
|
+
"info",
|
|
266
|
+
);
|
|
267
|
+
await deps.stopAuto(ctx, pi, `Slice-parallel dispatched for ${mid}`);
|
|
268
|
+
return { action: "break", reason: "slice-parallel-dispatched" };
|
|
269
|
+
}
|
|
270
|
+
// Fall through to sequential if no workers started
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
} catch (err) {
|
|
274
|
+
debugLog("autoLoop", {
|
|
275
|
+
phase: "slice-parallel-check-error",
|
|
276
|
+
error: err instanceof Error ? err.message : String(err),
|
|
277
|
+
});
|
|
278
|
+
// Non-fatal — fall through to sequential dispatch
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
221
282
|
// ── Milestone transition ────────────────────────────────────────────
|
|
222
283
|
if (mid && s.currentMilestoneId && mid !== s.currentMilestoneId) {
|
|
223
284
|
deps.emitJournalEvent({ ts: new Date().toISOString(), flowId: ic.flowId, seq: ic.nextSeq(), eventType: "milestone-transition", data: { from: s.currentMilestoneId, to: mid } });
|
|
@@ -948,11 +1009,38 @@ export async function runUnitPhase(
|
|
|
948
1009
|
}
|
|
949
1010
|
const hasProjectFile = PROJECT_FILES.some((f) => deps.existsSync(join(s.basePath, f)));
|
|
950
1011
|
const hasSrcDir = deps.existsSync(join(s.basePath, "src"));
|
|
951
|
-
|
|
1012
|
+
// Xcode bundles have project-specific names (*.xcodeproj, *.xcworkspace)
|
|
1013
|
+
// that cannot be matched by exact filename — scan the directory by suffix.
|
|
1014
|
+
let hasXcodeBundle = false;
|
|
1015
|
+
try {
|
|
1016
|
+
const entries = deps.existsSync(s.basePath) ? readdirSync(s.basePath) : [];
|
|
1017
|
+
hasXcodeBundle = entries.some((e: string) => e.endsWith(".xcodeproj") || e.endsWith(".xcworkspace"));
|
|
1018
|
+
} catch (err) {
|
|
1019
|
+
debugLog("runUnitPhase", { phase: "xcode-bundle-scan-failed", basePath: s.basePath, error: String(err) });
|
|
1020
|
+
}
|
|
1021
|
+
// Monorepo support (#2347): if no project files in the worktree directory,
|
|
1022
|
+
// walk parent directories up to the filesystem root. In monorepos,
|
|
1023
|
+
// package.json / Cargo.toml etc. live in a parent directory.
|
|
1024
|
+
let hasProjectFileInParent = false;
|
|
1025
|
+
if (!hasProjectFile && !hasSrcDir && !hasXcodeBundle) {
|
|
1026
|
+
let checkDir = dirname(s.basePath);
|
|
1027
|
+
const { root } = parsePath(checkDir);
|
|
1028
|
+
while (checkDir !== root) {
|
|
1029
|
+
// Stop at git repository boundary — ancestors above the repo root
|
|
1030
|
+
// (e.g. ~ or /usr/local) may contain unrelated project files.
|
|
1031
|
+
if (deps.existsSync(join(checkDir, ".git"))) break;
|
|
1032
|
+
if (PROJECT_FILES.some((f) => deps.existsSync(join(checkDir, f)))) {
|
|
1033
|
+
hasProjectFileInParent = true;
|
|
1034
|
+
break;
|
|
1035
|
+
}
|
|
1036
|
+
checkDir = dirname(checkDir);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
if (!hasProjectFile && !hasSrcDir && !hasXcodeBundle && !hasProjectFileInParent) {
|
|
952
1040
|
// Greenfield projects won't have project files yet — the first task creates them.
|
|
953
1041
|
// Log a warning but allow execution to proceed. The .git check above is sufficient
|
|
954
1042
|
// to ensure we're in a valid working directory.
|
|
955
|
-
debugLog("runUnitPhase", { phase: "worktree-health-warn-greenfield", basePath: s.basePath, hasProjectFile, hasSrcDir });
|
|
1043
|
+
debugLog("runUnitPhase", { phase: "worktree-health-warn-greenfield", basePath: s.basePath, hasProjectFile, hasSrcDir, hasXcodeBundle });
|
|
956
1044
|
ctx.ui.notify(`Warning: ${s.basePath} has no recognized project files — proceeding as greenfield project`, "warning");
|
|
957
1045
|
}
|
|
958
1046
|
}
|
|
@@ -966,6 +1054,7 @@ export async function runUnitPhase(
|
|
|
966
1054
|
const previousTier = s.currentUnitRouting?.tier;
|
|
967
1055
|
|
|
968
1056
|
s.currentUnit = { type: unitType, id: unitId, startedAt: Date.now() };
|
|
1057
|
+
s.lastToolInvocationError = null; // #2883: clear stale error from previous unit
|
|
969
1058
|
const unitStartSeq = ic.nextSeq();
|
|
970
1059
|
deps.emitJournalEvent({ ts: new Date().toISOString(), flowId: ic.flowId, seq: unitStartSeq, eventType: "unit-start", data: { unitType, unitId } });
|
|
971
1060
|
deps.captureAvailableSkills();
|
|
@@ -985,30 +1074,10 @@ export async function runUnitPhase(
|
|
|
985
1074
|
},
|
|
986
1075
|
);
|
|
987
1076
|
|
|
988
|
-
//
|
|
989
|
-
const modelResult = await deps.selectAndApplyModel(
|
|
990
|
-
ctx,
|
|
991
|
-
pi,
|
|
992
|
-
unitType,
|
|
993
|
-
unitId,
|
|
994
|
-
s.basePath,
|
|
995
|
-
prefs,
|
|
996
|
-
s.verbose,
|
|
997
|
-
s.autoModeStartModel,
|
|
998
|
-
sidecarItem ? undefined : { isRetry, previousTier },
|
|
999
|
-
);
|
|
1000
|
-
s.currentUnitRouting =
|
|
1001
|
-
modelResult.routing as AutoSession["currentUnitRouting"];
|
|
1002
|
-
s.currentUnitModel =
|
|
1003
|
-
modelResult.appliedModel as AutoSession["currentUnitModel"];
|
|
1004
|
-
|
|
1005
|
-
// Status bar + progress widget
|
|
1077
|
+
// Status bar (widget + preconditions deferred until after model selection — see #2899)
|
|
1006
1078
|
ctx.ui.setStatus("gsd-auto", "auto");
|
|
1007
1079
|
if (mid)
|
|
1008
1080
|
deps.updateSliceProgressCache(s.basePath, mid, state.activeSlice?.id);
|
|
1009
|
-
deps.updateProgressWidget(ctx, unitType, unitId, state);
|
|
1010
|
-
|
|
1011
|
-
deps.ensurePreconditions(unitType, unitId, s.basePath, state);
|
|
1012
1081
|
|
|
1013
1082
|
// Prompt injection
|
|
1014
1083
|
let finalPrompt = prompt;
|
|
@@ -1074,6 +1143,23 @@ export async function runUnitPhase(
|
|
|
1074
1143
|
logWarning("engine", "Prompt reorder failed", { error: msg });
|
|
1075
1144
|
}
|
|
1076
1145
|
|
|
1146
|
+
// Select and apply model (with tier escalation on retry — normal units only)
|
|
1147
|
+
const modelResult = await deps.selectAndApplyModel(
|
|
1148
|
+
ctx,
|
|
1149
|
+
pi,
|
|
1150
|
+
unitType,
|
|
1151
|
+
unitId,
|
|
1152
|
+
s.basePath,
|
|
1153
|
+
prefs,
|
|
1154
|
+
s.verbose,
|
|
1155
|
+
s.autoModeStartModel,
|
|
1156
|
+
sidecarItem ? undefined : { isRetry, previousTier },
|
|
1157
|
+
);
|
|
1158
|
+
s.currentUnitRouting =
|
|
1159
|
+
modelResult.routing as AutoSession["currentUnitRouting"];
|
|
1160
|
+
s.currentUnitModel =
|
|
1161
|
+
modelResult.appliedModel as AutoSession["currentUnitModel"];
|
|
1162
|
+
|
|
1077
1163
|
// Apply sidecar/pre-dispatch hook model override (takes priority over standard model selection)
|
|
1078
1164
|
const hookModelOverride = sidecarItem?.model ?? iterData.hookModelOverride;
|
|
1079
1165
|
if (hookModelOverride) {
|
|
@@ -1099,6 +1185,17 @@ export async function runUnitPhase(
|
|
|
1099
1185
|
}
|
|
1100
1186
|
}
|
|
1101
1187
|
|
|
1188
|
+
// Store the final dispatched model ID so the dashboard can read it (#2899).
|
|
1189
|
+
// This accounts for hook model overrides applied after selectAndApplyModel.
|
|
1190
|
+
s.currentDispatchedModelId = s.currentUnitModel
|
|
1191
|
+
? `${(s.currentUnitModel as any).provider ?? ""}/${(s.currentUnitModel as any).id ?? ""}`
|
|
1192
|
+
: null;
|
|
1193
|
+
|
|
1194
|
+
// Progress widget + preconditions — deferred to after model selection so the
|
|
1195
|
+
// widget's first render tick shows the correct model (#2899).
|
|
1196
|
+
deps.updateProgressWidget(ctx, unitType, unitId, state);
|
|
1197
|
+
deps.ensurePreconditions(unitType, unitId, s.basePath, state);
|
|
1198
|
+
|
|
1102
1199
|
// Start unit supervision
|
|
1103
1200
|
deps.clearUnitTimeout();
|
|
1104
1201
|
deps.startUnitSupervision({
|
|
@@ -1211,11 +1308,13 @@ export async function runUnitPhase(
|
|
|
1211
1308
|
);
|
|
1212
1309
|
}
|
|
1213
1310
|
|
|
1214
|
-
// ── Zero tool-call guard (#1833)
|
|
1215
|
-
//
|
|
1216
|
-
//
|
|
1217
|
-
// the
|
|
1218
|
-
|
|
1311
|
+
// ── Zero tool-call guard (#1833, #2653) ──────────────────────────
|
|
1312
|
+
// Any unit that completes with 0 tool calls made no real progress —
|
|
1313
|
+
// likely context exhaustion where all tool calls errored out. Treat
|
|
1314
|
+
// as failed so the unit is retried in a fresh context instead of
|
|
1315
|
+
// silently passing through to artifact verification (which loops
|
|
1316
|
+
// forever when the unit never produced its artifact).
|
|
1317
|
+
{
|
|
1219
1318
|
const currentLedger = deps.getLedger() as { units: Array<{ type: string; id: string; startedAt: number; toolCalls: number }> } | null;
|
|
1220
1319
|
if (currentLedger?.units) {
|
|
1221
1320
|
const lastUnit = [...currentLedger.units].reverse().find(
|
|
@@ -1226,14 +1325,14 @@ export async function runUnitPhase(
|
|
|
1226
1325
|
phase: "zero-tool-calls",
|
|
1227
1326
|
unitType,
|
|
1228
1327
|
unitId,
|
|
1229
|
-
warning: "
|
|
1328
|
+
warning: "Unit completed with 0 tool calls — likely context exhaustion, marking as failed",
|
|
1230
1329
|
});
|
|
1231
1330
|
ctx.ui.notify(
|
|
1232
|
-
`${unitType} ${unitId} completed with 0 tool calls —
|
|
1331
|
+
`${unitType} ${unitId} completed with 0 tool calls — context exhaustion, will retry`,
|
|
1233
1332
|
"warning",
|
|
1234
1333
|
);
|
|
1235
1334
|
// Fall through to next iteration where dispatch will re-derive
|
|
1236
|
-
// and re-dispatch this
|
|
1335
|
+
// and re-dispatch this unit.
|
|
1237
1336
|
return { action: "next", data: { unitStartedAt: s.currentUnit?.startedAt } };
|
|
1238
1337
|
}
|
|
1239
1338
|
}
|
|
@@ -1377,7 +1476,30 @@ export async function runFinalize(
|
|
|
1377
1476
|
}
|
|
1378
1477
|
|
|
1379
1478
|
// Post-verification processing (DB dual-write, hooks, triage, quick-tasks)
|
|
1380
|
-
|
|
1479
|
+
// Timeout guard: if postUnitPostVerification hangs (e.g., module import
|
|
1480
|
+
// deadlock, SQLite transaction hang), force-continue after timeout so the
|
|
1481
|
+
// auto-loop is not permanently frozen (#2344).
|
|
1482
|
+
const postResultGuard = await withTimeout(
|
|
1483
|
+
deps.postUnitPostVerification(postUnitCtx),
|
|
1484
|
+
FINALIZE_POST_TIMEOUT_MS,
|
|
1485
|
+
"postUnitPostVerification",
|
|
1486
|
+
);
|
|
1487
|
+
|
|
1488
|
+
if (postResultGuard.timedOut) {
|
|
1489
|
+
debugLog("autoLoop", {
|
|
1490
|
+
phase: "post-verification-timeout",
|
|
1491
|
+
iteration: ic.iteration,
|
|
1492
|
+
unitType: iterData.unitType,
|
|
1493
|
+
unitId: iterData.unitId,
|
|
1494
|
+
});
|
|
1495
|
+
ctx.ui.notify(
|
|
1496
|
+
`postUnitPostVerification timed out after ${FINALIZE_POST_TIMEOUT_MS / 1000}s for ${iterData.unitType} ${iterData.unitId} — continuing to next iteration`,
|
|
1497
|
+
"warning",
|
|
1498
|
+
);
|
|
1499
|
+
return { action: "next", data: undefined as void };
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
const postResult = postResultGuard.value;
|
|
1381
1503
|
|
|
1382
1504
|
if (postResult === "stopped") {
|
|
1383
1505
|
debugLog("autoLoop", {
|
|
@@ -105,6 +105,8 @@ export class AutoSession {
|
|
|
105
105
|
// ── Model state ──────────────────────────────────────────────────────────
|
|
106
106
|
autoModeStartModel: StartModel | null = null;
|
|
107
107
|
currentUnitModel: Model<Api> | null = null;
|
|
108
|
+
/** Fully-qualified model ID (provider/id) set after selectAndApplyModel + hook overrides (#2899). */
|
|
109
|
+
currentDispatchedModelId: string | null = null;
|
|
108
110
|
originalModelId: string | null = null;
|
|
109
111
|
originalModelProvider: string | null = null;
|
|
110
112
|
lastBudgetAlertLevel: BudgetAlertLevel = 0;
|
|
@@ -120,6 +122,11 @@ export class AutoSession {
|
|
|
120
122
|
// ── Sidecar queue ─────────────────────────────────────────────────────
|
|
121
123
|
sidecarQueue: SidecarItem[] = [];
|
|
122
124
|
|
|
125
|
+
// ── Tool invocation errors (#2883) ──────────────────────────────────
|
|
126
|
+
/** Set when a GSD tool execution ends with isError due to malformed/truncated
|
|
127
|
+
* JSON arguments. Checked by postUnitPreVerification to break retry loops. */
|
|
128
|
+
lastToolInvocationError: string | null = null;
|
|
129
|
+
|
|
123
130
|
// ── Isolation degradation ────────────────────────────────────────────
|
|
124
131
|
/** Set to true when worktree creation fails; prevents merge of nonexistent branch. */
|
|
125
132
|
isolationDegraded = false;
|
|
@@ -193,6 +200,7 @@ export class AutoSession {
|
|
|
193
200
|
// Model
|
|
194
201
|
this.autoModeStartModel = null;
|
|
195
202
|
this.currentUnitModel = null;
|
|
203
|
+
this.currentDispatchedModelId = null;
|
|
196
204
|
this.originalModelId = null;
|
|
197
205
|
this.originalModelProvider = null;
|
|
198
206
|
this.lastBudgetAlertLevel = 0;
|
|
@@ -212,6 +220,7 @@ export class AutoSession {
|
|
|
212
220
|
this.pendingQuickTasks = [];
|
|
213
221
|
this.sidecarQueue = [];
|
|
214
222
|
this.rewriteAttemptCount = 0;
|
|
223
|
+
this.lastToolInvocationError = null;
|
|
215
224
|
this.isolationDegraded = false;
|
|
216
225
|
this.milestoneMergedInPhases = false;
|
|
217
226
|
|
|
@@ -438,6 +438,8 @@ export interface WidgetStateAccessors {
|
|
|
438
438
|
isVerbose(): boolean;
|
|
439
439
|
/** True while newSession() is in-flight — render must not access session state. */
|
|
440
440
|
isSessionSwitching(): boolean;
|
|
441
|
+
/** Fully-qualified dispatched model ID (provider/id) set after model selection + hook overrides (#2899). */
|
|
442
|
+
getCurrentDispatchedModelId(): string | null;
|
|
441
443
|
}
|
|
442
444
|
|
|
443
445
|
export function updateProgressWidget(
|
|
@@ -629,9 +631,15 @@ export function updateProgressWidget(
|
|
|
629
631
|
const cxPctVal = cxUsage?.percent ?? 0;
|
|
630
632
|
const cxPct = cxUsage?.percent !== null ? cxPctVal.toFixed(1) : "?";
|
|
631
633
|
|
|
632
|
-
// Model display —
|
|
633
|
-
|
|
634
|
-
const
|
|
634
|
+
// Model display — prefer dispatched model ID (set after selectAndApplyModel
|
|
635
|
+
// + hook overrides) over cmdCtx?.model which can be stale (#2899).
|
|
636
|
+
const dispatchedModelId = accessors.getCurrentDispatchedModelId();
|
|
637
|
+
const modelId = dispatchedModelId
|
|
638
|
+
? dispatchedModelId.split("/").slice(1).join("/") || dispatchedModelId
|
|
639
|
+
: (cmdCtx?.model?.id ?? "");
|
|
640
|
+
const modelProvider = dispatchedModelId
|
|
641
|
+
? dispatchedModelId.split("/")[0] || ""
|
|
642
|
+
: (cmdCtx?.model?.provider ?? "");
|
|
635
643
|
const tierIcon = resolveServiceTierIcon(effectiveServiceTier, modelId);
|
|
636
644
|
const modelDisplay = (modelProvider && modelId
|
|
637
645
|
? `${modelProvider}/${modelId}`
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
import {
|
|
34
34
|
verifyExpectedArtifact,
|
|
35
35
|
resolveExpectedArtifactPath,
|
|
36
|
+
writeBlockerPlaceholder,
|
|
36
37
|
diagnoseExpectedArtifact,
|
|
37
38
|
} from "./auto-recovery.js";
|
|
38
39
|
import { regenerateIfMissing } from "./workflow-projections.js";
|
|
@@ -52,6 +53,9 @@ import { debugLog } from "./debug-logger.js";
|
|
|
52
53
|
import { runSafely } from "./auto-utils.js";
|
|
53
54
|
import type { AutoSession, SidecarItem } from "./auto/session.js";
|
|
54
55
|
|
|
56
|
+
/** Maximum verification retry attempts before escalating to blocker placeholder (#2653). */
|
|
57
|
+
const MAX_VERIFICATION_RETRIES = 3;
|
|
58
|
+
|
|
55
59
|
|
|
56
60
|
/** Enqueue a sidecar item (hook, triage, or quick-task) for the main loop to
|
|
57
61
|
* drain via runUnit. Logs the enqueue event and notifies the UI. */
|
|
@@ -468,6 +472,8 @@ export async function postUnitPreVerification(pctx: PostUnitContext, opts?: PreV
|
|
|
468
472
|
// When artifact verification fails for a unit type that has a known expected
|
|
469
473
|
// artifact, return "retry" so the caller re-dispatches with failure context
|
|
470
474
|
// instead of blindly re-dispatching the same unit (#1571).
|
|
475
|
+
// After MAX_VERIFICATION_RETRIES, escalate to writeBlockerPlaceholder so the
|
|
476
|
+
// pipeline can advance instead of looping forever (#2653).
|
|
471
477
|
//
|
|
472
478
|
// HOWEVER, if the DB is unavailable (db_unavailable), the artifact was never
|
|
473
479
|
// written because the completion tool failed at the infra level. Retrying
|
|
@@ -483,23 +489,58 @@ export async function postUnitPreVerification(pctx: PostUnitContext, opts?: PreV
|
|
|
483
489
|
"error",
|
|
484
490
|
);
|
|
485
491
|
} else if (!triggerArtifactVerified) {
|
|
492
|
+
// #2883: If the artifact is missing because the tool invocation itself
|
|
493
|
+
// failed (malformed/truncated JSON arguments), retrying will produce the
|
|
494
|
+
// same failure. Pause auto-mode instead of entering a stuck retry loop.
|
|
495
|
+
if (s.lastToolInvocationError) {
|
|
496
|
+
const errMsg = `Tool invocation failed for ${s.currentUnit.type}: ${s.lastToolInvocationError}. Structured argument generation failed — pausing auto-mode.`;
|
|
497
|
+
debugLog("postUnit", { phase: "tool-invocation-error-pause", unitType: s.currentUnit.type, unitId: s.currentUnit.id, error: s.lastToolInvocationError });
|
|
498
|
+
ctx.ui.notify(errMsg, "error");
|
|
499
|
+
s.lastToolInvocationError = null;
|
|
500
|
+
await pauseAuto(ctx, pi);
|
|
501
|
+
return "dispatched";
|
|
502
|
+
}
|
|
503
|
+
|
|
486
504
|
const hasExpectedArtifact = resolveExpectedArtifactPath(s.currentUnit.type, s.currentUnit.id, s.basePath) !== null;
|
|
487
505
|
if (hasExpectedArtifact) {
|
|
488
506
|
const retryKey = `${s.currentUnit.type}:${s.currentUnit.id}`;
|
|
489
507
|
const attempt = (s.verificationRetryCount.get(retryKey) ?? 0) + 1;
|
|
490
508
|
s.verificationRetryCount.set(retryKey, attempt);
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
509
|
+
|
|
510
|
+
if (attempt > MAX_VERIFICATION_RETRIES) {
|
|
511
|
+
// Retries exhausted — write a blocker placeholder so the pipeline
|
|
512
|
+
// can advance past this stuck unit (#2653).
|
|
513
|
+
debugLog("postUnit", {
|
|
514
|
+
phase: "artifact-verify-escalate",
|
|
515
|
+
unitType: s.currentUnit.type,
|
|
516
|
+
unitId: s.currentUnit.id,
|
|
517
|
+
attempt,
|
|
518
|
+
maxRetries: MAX_VERIFICATION_RETRIES,
|
|
519
|
+
});
|
|
520
|
+
const reason = `Artifact verification failed after ${MAX_VERIFICATION_RETRIES} retries for ${s.currentUnit.type} "${s.currentUnit.id}".`;
|
|
521
|
+
writeBlockerPlaceholder(s.currentUnit.type, s.currentUnit.id, s.basePath, reason);
|
|
522
|
+
ctx.ui.notify(
|
|
523
|
+
`${s.currentUnit.type} ${s.currentUnit.id} — verification retries exhausted (${MAX_VERIFICATION_RETRIES}), wrote blocker placeholder to advance pipeline`,
|
|
524
|
+
"warning",
|
|
525
|
+
);
|
|
526
|
+
// Reset retry count and fall through to "continue" so the loop
|
|
527
|
+
// re-derives state with the placeholder in place.
|
|
528
|
+
s.verificationRetryCount.delete(retryKey);
|
|
529
|
+
s.pendingVerificationRetry = null;
|
|
530
|
+
// Do NOT return "retry" — fall through to "continue" below.
|
|
531
|
+
} else {
|
|
532
|
+
s.pendingVerificationRetry = {
|
|
533
|
+
unitId: s.currentUnit.id,
|
|
534
|
+
failureContext: `Artifact verification failed: expected artifact for ${s.currentUnit.type} "${s.currentUnit.id}" was not found on disk after unit execution (attempt ${attempt}).`,
|
|
535
|
+
attempt,
|
|
536
|
+
};
|
|
537
|
+
debugLog("postUnit", { phase: "artifact-verify-retry", unitType: s.currentUnit.type, unitId: s.currentUnit.id, attempt });
|
|
538
|
+
ctx.ui.notify(
|
|
539
|
+
`Artifact missing for ${s.currentUnit.type} ${s.currentUnit.id} — retrying (attempt ${attempt})`,
|
|
540
|
+
"warning",
|
|
541
|
+
);
|
|
542
|
+
return "retry";
|
|
543
|
+
}
|
|
503
544
|
}
|
|
504
545
|
}
|
|
505
546
|
} else {
|
|
@@ -994,10 +994,15 @@ export async function buildResearchSlicePrompt(
|
|
|
994
994
|
const milestoneResearchPath = resolveMilestoneFile(base, mid, "RESEARCH");
|
|
995
995
|
const milestoneResearchRel = relMilestoneFile(base, mid, "RESEARCH");
|
|
996
996
|
|
|
997
|
+
const sliceContextPath = resolveSliceFile(base, mid, sid, "CONTEXT");
|
|
998
|
+
const sliceContextRel = relSliceFile(base, mid, sid, "CONTEXT");
|
|
999
|
+
|
|
997
1000
|
const inlined: string[] = [];
|
|
998
1001
|
inlined.push(await inlineFile(roadmapPath, roadmapRel, "Milestone Roadmap"));
|
|
999
1002
|
const contextInline = await inlineFileOptional(contextPath, contextRel, "Milestone Context");
|
|
1000
1003
|
if (contextInline) inlined.push(contextInline);
|
|
1004
|
+
const sliceCtxInline = await inlineFileOptional(sliceContextPath, sliceContextRel, "Slice Context (from discussion)");
|
|
1005
|
+
if (sliceCtxInline) inlined.push(sliceCtxInline);
|
|
1001
1006
|
const researchInline = await inlineFileOptional(milestoneResearchPath, milestoneResearchRel, "Milestone Research");
|
|
1002
1007
|
if (researchInline) inlined.push(researchInline);
|
|
1003
1008
|
const decisionsInline = await inlineDecisionsFromDb(base, mid);
|
|
@@ -1045,6 +1050,8 @@ export async function buildPlanSlicePrompt(
|
|
|
1045
1050
|
const roadmapRel = relMilestoneFile(base, mid, "ROADMAP");
|
|
1046
1051
|
const researchPath = resolveSliceFile(base, mid, sid, "RESEARCH");
|
|
1047
1052
|
const researchRel = relSliceFile(base, mid, sid, "RESEARCH");
|
|
1053
|
+
const sliceContextPath = resolveSliceFile(base, mid, sid, "CONTEXT");
|
|
1054
|
+
const sliceContextRel = relSliceFile(base, mid, sid, "CONTEXT");
|
|
1048
1055
|
|
|
1049
1056
|
const inlined: string[] = [];
|
|
1050
1057
|
|
|
@@ -1053,6 +1060,8 @@ export async function buildPlanSlicePrompt(
|
|
|
1053
1060
|
if (researchSliceAnchor) inlined.push(formatAnchorForPrompt(researchSliceAnchor));
|
|
1054
1061
|
|
|
1055
1062
|
inlined.push(await inlineFile(roadmapPath, roadmapRel, "Milestone Roadmap"));
|
|
1063
|
+
const sliceCtxInline = await inlineFileOptional(sliceContextPath, sliceContextRel, "Slice Context (from discussion)");
|
|
1064
|
+
if (sliceCtxInline) inlined.push(sliceCtxInline);
|
|
1056
1065
|
const researchInline = await inlineFileOptional(researchPath, researchRel, "Slice Research");
|
|
1057
1066
|
if (researchInline) inlined.push(researchInline);
|
|
1058
1067
|
if (inlineLevel !== "minimal") {
|
|
@@ -1253,9 +1262,13 @@ export async function buildCompleteSlicePrompt(
|
|
|
1253
1262
|
const roadmapRel = relMilestoneFile(base, mid, "ROADMAP");
|
|
1254
1263
|
const slicePlanPath = resolveSliceFile(base, mid, sid, "PLAN");
|
|
1255
1264
|
const slicePlanRel = relSliceFile(base, mid, sid, "PLAN");
|
|
1265
|
+
const sliceContextPath = resolveSliceFile(base, mid, sid, "CONTEXT");
|
|
1266
|
+
const sliceContextRel = relSliceFile(base, mid, sid, "CONTEXT");
|
|
1256
1267
|
|
|
1257
1268
|
const inlined: string[] = [];
|
|
1258
1269
|
inlined.push(await inlineFile(roadmapPath, roadmapRel, "Milestone Roadmap"));
|
|
1270
|
+
const sliceCtxInline = await inlineFileOptional(sliceContextPath, sliceContextRel, "Slice Context (from discussion)");
|
|
1271
|
+
if (sliceCtxInline) inlined.push(sliceCtxInline);
|
|
1259
1272
|
inlined.push(await inlineFile(slicePlanPath, slicePlanRel, "Slice Plan"));
|
|
1260
1273
|
if (inlineLevel !== "minimal") {
|
|
1261
1274
|
const requirementsInline = await inlineRequirementsFromDb(base, sid, inlineLevel);
|
|
@@ -1510,9 +1523,13 @@ export async function buildReplanSlicePrompt(
|
|
|
1510
1523
|
const roadmapRel = relMilestoneFile(base, mid, "ROADMAP");
|
|
1511
1524
|
const slicePlanPath = resolveSliceFile(base, mid, sid, "PLAN");
|
|
1512
1525
|
const slicePlanRel = relSliceFile(base, mid, sid, "PLAN");
|
|
1526
|
+
const sliceContextPath = resolveSliceFile(base, mid, sid, "CONTEXT");
|
|
1527
|
+
const sliceContextRel = relSliceFile(base, mid, sid, "CONTEXT");
|
|
1513
1528
|
|
|
1514
1529
|
const inlined: string[] = [];
|
|
1515
1530
|
inlined.push(await inlineFile(roadmapPath, roadmapRel, "Milestone Roadmap"));
|
|
1531
|
+
const sliceCtxInline = await inlineFileOptional(sliceContextPath, sliceContextRel, "Slice Context (from discussion)");
|
|
1532
|
+
if (sliceCtxInline) inlined.push(sliceCtxInline);
|
|
1516
1533
|
inlined.push(await inlineFile(slicePlanPath, slicePlanRel, "Current Slice Plan"));
|
|
1517
1534
|
|
|
1518
1535
|
// Find the blocker task summary — the completed task with blocker_discovered: true
|
|
@@ -1627,9 +1644,13 @@ export async function buildReassessRoadmapPrompt(
|
|
|
1627
1644
|
const roadmapRel = relMilestoneFile(base, mid, "ROADMAP");
|
|
1628
1645
|
const summaryPath = resolveSliceFile(base, mid, completedSliceId, "SUMMARY");
|
|
1629
1646
|
const summaryRel = relSliceFile(base, mid, completedSliceId, "SUMMARY");
|
|
1647
|
+
const sliceContextPath = resolveSliceFile(base, mid, completedSliceId, "CONTEXT");
|
|
1648
|
+
const sliceContextRel = relSliceFile(base, mid, completedSliceId, "CONTEXT");
|
|
1630
1649
|
|
|
1631
1650
|
const inlined: string[] = [];
|
|
1632
1651
|
inlined.push(await inlineFile(roadmapPath, roadmapRel, "Current Roadmap"));
|
|
1652
|
+
const sliceCtxInline = await inlineFileOptional(sliceContextPath, sliceContextRel, "Slice Context (from discussion)");
|
|
1653
|
+
if (sliceCtxInline) inlined.push(sliceCtxInline);
|
|
1633
1654
|
inlined.push(await inlineFile(summaryPath, summaryRel, `${completedSliceId} Summary`));
|
|
1634
1655
|
if (inlineLevel !== "minimal") {
|
|
1635
1656
|
const projectInline = await inlineProjectFromDb(base);
|