@opengsd/gsd-pi 1.4.0 → 1.5.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/README.md +11 -11
- package/dist/onboarding.js +11 -0
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/GSD-WORKFLOW.md +20 -25
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +118 -53
- package/dist/resources/extensions/cursor-cli/index.js +16 -0
- package/dist/resources/extensions/cursor-cli/models.js +63 -0
- package/dist/resources/extensions/cursor-cli/package.json +11 -0
- package/dist/resources/extensions/cursor-cli/readiness.js +128 -0
- package/dist/resources/extensions/cursor-cli/stream-adapter.js +353 -0
- package/dist/resources/extensions/gsd/activity-log.js +4 -1
- package/dist/resources/extensions/gsd/artifact-verification.js +25 -9
- package/dist/resources/extensions/gsd/auto/closeout.js +45 -42
- package/dist/resources/extensions/gsd/auto/finalize.js +9 -0
- package/dist/resources/extensions/gsd/auto/orchestrator.js +1 -1
- package/dist/resources/extensions/gsd/auto/run-unit.js +5 -2
- package/dist/resources/extensions/gsd/auto/schedule-wakeup.js +3 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +15 -5
- package/dist/resources/extensions/gsd/auto-model-selection.js +36 -18
- package/dist/resources/extensions/gsd/auto-prompts.js +76 -2
- package/dist/resources/extensions/gsd/auto-start.js +4 -3
- package/dist/resources/extensions/gsd/auto-timeout-recovery.js +4 -0
- package/dist/resources/extensions/gsd/auto-worktree.js +38 -11
- package/dist/resources/extensions/gsd/auto.js +23 -3
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +3 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +114 -5
- package/dist/resources/extensions/gsd/bootstrap/schedule-wakeup-tool.js +58 -9
- package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +10 -2
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +14 -1
- package/dist/resources/extensions/gsd/codebase-generator.js +157 -19
- package/dist/resources/extensions/gsd/commands/handlers/core.js +40 -4
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +153 -1
- package/dist/resources/extensions/gsd/commands-worktree.js +11 -2
- package/dist/resources/extensions/gsd/db/queries.js +39 -0
- package/dist/resources/extensions/gsd/discussion-handoff.js +8 -3
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +8 -2
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +206 -14
- package/dist/resources/extensions/gsd/doctor-providers.js +38 -10
- package/dist/resources/extensions/gsd/doctor.js +93 -3
- package/dist/resources/extensions/gsd/exec-sandbox.js +6 -2
- package/dist/resources/extensions/gsd/flat-phase-migration.js +74 -23
- package/dist/resources/extensions/gsd/git-service.js +14 -18
- package/dist/resources/extensions/gsd/gsd-db.js +52 -0
- package/dist/resources/extensions/gsd/guidance.js +1 -0
- package/dist/resources/extensions/gsd/guided-flow.js +40 -2
- package/dist/resources/extensions/gsd/key-manager.js +1 -0
- package/dist/resources/extensions/gsd/markdown-renderer.js +25 -8
- package/dist/resources/extensions/gsd/migrate-external.js +24 -7
- package/dist/resources/extensions/gsd/milestone-merge-transaction.js +3 -3
- package/dist/resources/extensions/gsd/preferences-diagnostics.js +24 -1
- package/dist/resources/extensions/gsd/preferences-models.js +6 -4
- package/dist/resources/extensions/gsd/preferences-validation.js +25 -2
- package/dist/resources/extensions/gsd/preferences.js +29 -2
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +4 -2
- package/dist/resources/extensions/gsd/redact-secrets.js +37 -0
- package/dist/resources/extensions/gsd/repo-identity.js +16 -0
- package/dist/resources/extensions/gsd/repository-registry.js +6 -1
- package/dist/resources/extensions/gsd/root-write-leak-guard.js +4 -2
- package/dist/resources/extensions/gsd/safe-fs.js +23 -2
- package/dist/resources/extensions/gsd/skills/gsd-headless/SKILL.md +7 -9
- package/dist/resources/extensions/gsd/skills/gsd-headless/references/commands.md +1 -0
- package/dist/resources/extensions/gsd/tools/plan-task.js +76 -8
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +40 -0
- package/dist/resources/extensions/gsd/unit-context-manifest.js +5 -4
- package/dist/resources/extensions/gsd/unit-registry.js +27 -2
- package/dist/resources/extensions/gsd/unit-runtime.js +21 -0
- package/dist/resources/extensions/gsd/workspace.js +26 -8
- package/dist/resources/extensions/gsd/worktree-lifecycle.js +67 -5
- package/dist/resources/extensions/gsd/worktree-manager.js +97 -4
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +7 -7
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- 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/update/route.js +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 +7 -7
- package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/node_modules/@gsd/native/package.json +1 -1
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
- package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
- package/dist/wizard.d.ts +8 -0
- package/dist/wizard.js +8 -1
- package/integrations/hermes/CONTEXT.md +47 -0
- package/integrations/hermes/README.md +21 -0
- package/integrations/hermes/__init__.py +31 -0
- package/integrations/hermes/docs/issue-1162-grilling.md +197 -0
- package/integrations/hermes/docs/setup.md +4 -0
- package/integrations/hermes/open_gsd_hermes/commands.py +141 -0
- package/integrations/hermes/open_gsd_hermes/config.py +2 -0
- package/integrations/hermes/open_gsd_hermes/gsd_client.py +594 -31
- package/integrations/hermes/open_gsd_hermes/notifications.py +10 -1
- package/integrations/hermes/plugin.yaml +4 -0
- package/integrations/hermes/tests/test_config.py +6 -0
- package/integrations/hermes/tests/test_directory_loader.py +37 -0
- package/integrations/hermes/tests/test_gsd_client_cache.py +3 -0
- package/integrations/hermes/tests/test_gsd_client_execute.py +35 -0
- package/integrations/hermes/tests/test_gsd_client_process_cwd.py +22 -0
- package/integrations/hermes/tests/test_gsd_client_timeout.py +98 -0
- package/integrations/hermes/tests/test_milestone_client.py +447 -0
- package/integrations/hermes/tests/test_new_milestone_command.py +313 -0
- package/integrations/hermes/tests/test_supervisor_fsm.py +40 -0
- package/package.json +8 -8
- package/packages/cloud-mcp-gateway/dist/auth-store.d.ts +1 -0
- package/packages/cloud-mcp-gateway/dist/auth-store.d.ts.map +1 -1
- package/packages/cloud-mcp-gateway/dist/auth-store.js +21 -1
- package/packages/cloud-mcp-gateway/dist/auth-store.js.map +1 -1
- package/packages/cloud-mcp-gateway/dist/auth-store.test.js +34 -0
- package/packages/cloud-mcp-gateway/dist/auth-store.test.js.map +1 -1
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/dist/cloud-runtime.d.ts +2 -0
- package/packages/daemon/dist/cloud-runtime.d.ts.map +1 -1
- package/packages/daemon/dist/cloud-runtime.js +25 -1
- package/packages/daemon/dist/cloud-runtime.js.map +1 -1
- package/packages/daemon/dist/cloud-runtime.test.js +51 -0
- package/packages/daemon/dist/cloud-runtime.test.js.map +1 -1
- package/packages/daemon/dist/message-batcher.d.ts +7 -0
- package/packages/daemon/dist/message-batcher.d.ts.map +1 -1
- package/packages/daemon/dist/message-batcher.js +13 -0
- package/packages/daemon/dist/message-batcher.js.map +1 -1
- package/packages/daemon/dist/message-batcher.test.js +48 -0
- package/packages/daemon/dist/message-batcher.test.js.map +1 -1
- package/packages/daemon/dist/session-manager.js +1 -1
- package/packages/daemon/dist/session-manager.js.map +1 -1
- package/packages/daemon/package.json +5 -5
- package/packages/gsd-agent-core/dist/compaction/compaction.d.ts +13 -1
- package/packages/gsd-agent-core/dist/compaction/compaction.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/compaction/compaction.js +15 -1
- package/packages/gsd-agent-core/dist/compaction/compaction.js.map +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-navigation.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-navigation.js +4 -1
- package/packages/gsd-agent-core/dist/session/agent-session-navigation.js.map +1 -1
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.js +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/rpc/rpc-client.js +1 -1
- package/packages/gsd-agent-modes/dist/modes/rpc/rpc-client.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/dist/server.d.ts +18 -5
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +46 -5
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/session-manager.js +1 -1
- package/packages/mcp-server/dist/session-manager.js.map +1 -1
- package/packages/mcp-server/package.json +4 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/env-api-keys.d.ts.map +1 -1
- package/packages/pi-ai/dist/env-api-keys.js +3 -0
- package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +2 -2
- 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/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -0
- package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager-types.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager-types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager-types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.js +3 -0
- package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/dist/rpc-client.js +1 -1
- package/packages/rpc-client/dist/rpc-client.js.map +1 -1
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/scripts/link-workspace-packages.cjs +14 -4
- package/src/resources/GSD-WORKFLOW.md +20 -25
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +130 -51
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +371 -39
- package/src/resources/extensions/cursor-cli/index.ts +18 -0
- package/src/resources/extensions/cursor-cli/models.ts +65 -0
- package/src/resources/extensions/cursor-cli/package.json +11 -0
- package/src/resources/extensions/cursor-cli/readiness.ts +143 -0
- package/src/resources/extensions/cursor-cli/stream-adapter.ts +440 -0
- package/src/resources/extensions/cursor-cli/tests/index.test.ts +54 -0
- package/src/resources/extensions/cursor-cli/tests/readiness.test.ts +29 -0
- package/src/resources/extensions/cursor-cli/tests/stream-adapter.test.ts +166 -0
- package/src/resources/extensions/gsd/activity-log.ts +4 -1
- package/src/resources/extensions/gsd/artifact-verification.ts +25 -9
- package/src/resources/extensions/gsd/auto/closeout.ts +54 -56
- package/src/resources/extensions/gsd/auto/finalize.ts +8 -0
- package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
- package/src/resources/extensions/gsd/auto/orchestrator.ts +1 -1
- package/src/resources/extensions/gsd/auto/run-unit.ts +5 -2
- package/src/resources/extensions/gsd/auto/schedule-wakeup.ts +8 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +17 -5
- package/src/resources/extensions/gsd/auto-model-selection.ts +49 -18
- package/src/resources/extensions/gsd/auto-prompts.ts +73 -2
- package/src/resources/extensions/gsd/auto-start.ts +4 -3
- package/src/resources/extensions/gsd/auto-timeout-recovery.ts +4 -0
- package/src/resources/extensions/gsd/auto-worktree.ts +50 -11
- package/src/resources/extensions/gsd/auto.ts +22 -3
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +3 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +128 -5
- package/src/resources/extensions/gsd/bootstrap/schedule-wakeup-tool.ts +80 -12
- package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +12 -2
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +23 -1
- package/src/resources/extensions/gsd/codebase-generator.ts +179 -18
- package/src/resources/extensions/gsd/commands/handlers/core.ts +39 -3
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +135 -1
- package/src/resources/extensions/gsd/commands-worktree.ts +13 -2
- package/src/resources/extensions/gsd/db/queries.ts +40 -0
- package/src/resources/extensions/gsd/discussion-handoff.ts +10 -2
- package/src/resources/extensions/gsd/docs/preferences-reference.md +8 -2
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +269 -18
- package/src/resources/extensions/gsd/doctor-providers.ts +39 -10
- package/src/resources/extensions/gsd/doctor-types.ts +5 -1
- package/src/resources/extensions/gsd/doctor.ts +95 -3
- package/src/resources/extensions/gsd/exec-sandbox.ts +6 -2
- package/src/resources/extensions/gsd/flat-phase-migration.ts +98 -22
- package/src/resources/extensions/gsd/git-service.ts +14 -17
- package/src/resources/extensions/gsd/gsd-db.ts +72 -0
- package/src/resources/extensions/gsd/guidance.ts +2 -0
- package/src/resources/extensions/gsd/guided-flow.ts +45 -2
- package/src/resources/extensions/gsd/key-manager.ts +1 -0
- package/src/resources/extensions/gsd/markdown-renderer.ts +25 -7
- package/src/resources/extensions/gsd/migrate-external.ts +23 -6
- package/src/resources/extensions/gsd/milestone-merge-transaction.ts +4 -4
- package/src/resources/extensions/gsd/model-router.ts +4 -5
- package/src/resources/extensions/gsd/preferences-diagnostics.ts +24 -0
- package/src/resources/extensions/gsd/preferences-models.ts +6 -3
- package/src/resources/extensions/gsd/preferences-validation.ts +28 -2
- package/src/resources/extensions/gsd/preferences.ts +38 -4
- package/src/resources/extensions/gsd/prompts/plan-slice.md +4 -2
- package/src/resources/extensions/gsd/redact-secrets.ts +40 -0
- package/src/resources/extensions/gsd/repo-identity.ts +17 -0
- package/src/resources/extensions/gsd/repository-registry.ts +7 -1
- package/src/resources/extensions/gsd/root-write-leak-guard.ts +3 -2
- package/src/resources/extensions/gsd/safe-fs.ts +22 -2
- package/src/resources/extensions/gsd/skills/gsd-headless/SKILL.md +7 -9
- package/src/resources/extensions/gsd/skills/gsd-headless/references/commands.md +1 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +131 -5
- package/src/resources/extensions/gsd/tests/auto-migrating-recovery.test.ts +18 -0
- package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +178 -0
- package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +43 -1
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/auto-remote-session-lock-cleanup.test.ts +41 -0
- package/src/resources/extensions/gsd/tests/auto-runtime-state.test.ts +41 -0
- package/src/resources/extensions/gsd/tests/auto-session-scope.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/auto-start-bootstrap-await-3420.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/auto-timeout-recovery-harness-abort.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/batch-task-query.test.ts +85 -0
- package/src/resources/extensions/gsd/tests/check-auto-start-ready-guard.test.ts +29 -0
- package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +365 -0
- package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +62 -1
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +138 -1
- package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +397 -5
- package/src/resources/extensions/gsd/tests/doctor-workspace.test.ts +207 -0
- package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +38 -1
- package/src/resources/extensions/gsd/tests/flat-rate-routing-guard.test.ts +87 -19
- package/src/resources/extensions/gsd/tests/gate-1b-orphan-discrimination.test.ts +89 -0
- package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +4 -2
- package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +122 -0
- package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/migrate-external-worktree.test.ts +116 -0
- package/src/resources/extensions/gsd/tests/milestone-merge-stash-restore.test.ts +152 -22
- package/src/resources/extensions/gsd/tests/milestone-merge-transaction.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/pending-autostart-scope.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +50 -6
- package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +17 -0
- package/src/resources/extensions/gsd/tests/plan-task.test.ts +264 -1
- package/src/resources/extensions/gsd/tests/preferences.test.ts +150 -0
- package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +142 -0
- package/src/resources/extensions/gsd/tests/recovery-verify-logs.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/redact-secrets.test.ts +56 -0
- package/src/resources/extensions/gsd/tests/register-hooks-flat-phase-migration.test.ts +73 -0
- package/src/resources/extensions/gsd/tests/register-hooks-loop-guard-auto.test.ts +275 -0
- package/src/resources/extensions/gsd/tests/repository-registry.test.ts +41 -2
- package/src/resources/extensions/gsd/tests/require-slice-discussion-dispatch.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/root-write-leak-guard.test.ts +31 -9
- package/src/resources/extensions/gsd/tests/schedule-wakeup-tool.test.ts +167 -0
- package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +49 -0
- package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/status-per-repo.test.ts +94 -0
- package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +35 -0
- package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +63 -0
- package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +66 -18
- package/src/resources/extensions/gsd/tests/unit-registry.test.ts +56 -0
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/uok-gitops-turn-action.test.ts +52 -0
- package/src/resources/extensions/gsd/tests/validator-scope-parity.test.ts +53 -1
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +208 -1
- package/src/resources/extensions/gsd/tests/workspace.test.ts +18 -4
- package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +31 -1
- package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +32 -0
- package/src/resources/extensions/gsd/tools/plan-task.ts +107 -9
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +58 -0
- package/src/resources/extensions/gsd/unit-context-manifest.ts +11 -5
- package/src/resources/extensions/gsd/unit-registry.ts +34 -2
- package/src/resources/extensions/gsd/unit-runtime.ts +42 -0
- package/src/resources/extensions/gsd/workspace.ts +42 -8
- package/src/resources/extensions/gsd/worktree-lifecycle.ts +135 -11
- package/src/resources/extensions/gsd/worktree-manager.ts +143 -5
- /package/dist/web/standalone/.next/static/{zQLcXm0Za8lexoXtgDpMG → bCg3aYrFN1DKZr2dqgE7L}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{zQLcXm0Za8lexoXtgDpMG → bCg3aYrFN1DKZr2dqgE7L}/_ssgManifest.js +0 -0
|
@@ -1199,11 +1199,11 @@ export function teardownAutoWorktree(
|
|
|
1199
1199
|
const branch = autoWorktreeBranch(milestoneId);
|
|
1200
1200
|
const { preserveBranch = false, preserveWorktree = false } = opts;
|
|
1201
1201
|
const previousCwd = safeCwd(originalBasePath);
|
|
1202
|
+
let clearActiveWorkspace = true;
|
|
1202
1203
|
|
|
1203
1204
|
// Wrap the entire teardown body in a single try/finally so activeWorkspace
|
|
1204
|
-
// is
|
|
1205
|
-
// was deleted before teardown ran).
|
|
1206
|
-
// removeWorktree, leaving the registry stale on a chdir failure (H3 fix).
|
|
1205
|
+
// is cleared on completion or irrecoverable failure — even if process.chdir
|
|
1206
|
+
// throws (e.g. originalBasePath was deleted before teardown ran).
|
|
1207
1207
|
try {
|
|
1208
1208
|
try {
|
|
1209
1209
|
process.chdir(originalBasePath);
|
|
@@ -1245,18 +1245,28 @@ export function teardownAutoWorktree(
|
|
|
1245
1245
|
// 3. Remove the worktree unless this exit path explicitly preserves it
|
|
1246
1246
|
// (slice-parallel dispatch stops the parent loop but keeps the parent
|
|
1247
1247
|
// milestone worktree for restart/re-entry).
|
|
1248
|
+
let worktreeRemoved = preserveWorktree;
|
|
1248
1249
|
if (!preserveWorktree) {
|
|
1249
|
-
removeWorktree(originalBasePath, milestoneId, {
|
|
1250
|
+
worktreeRemoved = removeWorktree(originalBasePath, milestoneId, {
|
|
1250
1251
|
branch,
|
|
1251
1252
|
deleteBranch: !preserveBranch,
|
|
1252
1253
|
});
|
|
1254
|
+
if (!worktreeRemoved) {
|
|
1255
|
+
logWarning(
|
|
1256
|
+
"worktree",
|
|
1257
|
+
`Worktree removal aborted for ${milestoneId}; uncommitted work was preserved. ` +
|
|
1258
|
+
`Manual quarantine recovery may be needed before retrying teardown.`,
|
|
1259
|
+
{ worktree: milestoneId },
|
|
1260
|
+
);
|
|
1261
|
+
clearActiveWorkspace = false;
|
|
1262
|
+
}
|
|
1253
1263
|
}
|
|
1254
1264
|
|
|
1255
1265
|
// Verify cleanup succeeded — warn if the worktree directory is still on disk.
|
|
1256
1266
|
// On Windows, bash-based cleanup can silently fail when paths contain
|
|
1257
1267
|
// backslashes (#1436), leaving ~1 GB+ orphaned directories.
|
|
1258
1268
|
const wtDir = worktreePath(originalBasePath, milestoneId);
|
|
1259
|
-
if (!preserveWorktree && existsSync(wtDir)) {
|
|
1269
|
+
if (!preserveWorktree && worktreeRemoved && existsSync(wtDir)) {
|
|
1260
1270
|
logWarning(
|
|
1261
1271
|
"reconcile",
|
|
1262
1272
|
`Worktree directory still exists after teardown: ${wtDir}. ` +
|
|
@@ -1280,10 +1290,12 @@ export function teardownAutoWorktree(
|
|
|
1280
1290
|
}
|
|
1281
1291
|
}
|
|
1282
1292
|
} finally {
|
|
1283
|
-
// Clear module state
|
|
1284
|
-
//
|
|
1285
|
-
//
|
|
1286
|
-
|
|
1293
|
+
// Clear module state when teardown completed or failed irrecoverably.
|
|
1294
|
+
// When removeWorktree returns false (quarantine failure), preserve the
|
|
1295
|
+
// registry so recovery logic still sees the active milestone worktree.
|
|
1296
|
+
if (clearActiveWorkspace) {
|
|
1297
|
+
setActiveWorkspace(null);
|
|
1298
|
+
}
|
|
1287
1299
|
}
|
|
1288
1300
|
}
|
|
1289
1301
|
|
|
@@ -1791,14 +1803,31 @@ export function mergeMilestoneToMain(
|
|
|
1791
1803
|
} catch (err) {
|
|
1792
1804
|
logWarning("worktree", `clearProjectRootStateFiles failed during already-merged cleanup: ${err instanceof Error ? err.message : String(err)}`);
|
|
1793
1805
|
}
|
|
1806
|
+
let worktreeRemoved = true;
|
|
1794
1807
|
try {
|
|
1795
|
-
removeWorktree(originalBasePath_, milestoneId, {
|
|
1808
|
+
worktreeRemoved = removeWorktree(originalBasePath_, milestoneId, {
|
|
1796
1809
|
branch: milestoneBranch,
|
|
1797
1810
|
deleteBranch: false,
|
|
1798
1811
|
});
|
|
1799
1812
|
} catch (err) {
|
|
1813
|
+
worktreeRemoved = false;
|
|
1800
1814
|
logWarning("worktree", `worktree removal failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1801
1815
|
}
|
|
1816
|
+
|
|
1817
|
+
if (!worktreeRemoved) {
|
|
1818
|
+
logWarning(
|
|
1819
|
+
"worktree",
|
|
1820
|
+
`Skipping milestone branch deletion for ${milestoneBranch}; worktree removal was aborted to preserve uncommitted milestone work.`,
|
|
1821
|
+
);
|
|
1822
|
+
nudgeGitBranchCache(previousCwd);
|
|
1823
|
+
try {
|
|
1824
|
+
process.chdir(originalBasePath_);
|
|
1825
|
+
} catch (err) {
|
|
1826
|
+
logWarning("worktree", `chdir to project root after already-merged cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1827
|
+
}
|
|
1828
|
+
return { commitMessage, pushed: false, prCreated: false, codeFilesChanged: true };
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1802
1831
|
try {
|
|
1803
1832
|
nativeBranchDelete(originalBasePath_, milestoneBranch);
|
|
1804
1833
|
} catch (err) {
|
|
@@ -2270,16 +2299,26 @@ export function mergeMilestoneToMain(
|
|
|
2270
2299
|
|
|
2271
2300
|
const finalizeMilestoneCleanup = (): void => {
|
|
2272
2301
|
// 12. Remove worktree directory first (must happen before branch deletion)
|
|
2302
|
+
let worktreeRemoved = true;
|
|
2273
2303
|
try {
|
|
2274
|
-
removeWorktree(originalBasePath_, milestoneId, {
|
|
2304
|
+
worktreeRemoved = removeWorktree(originalBasePath_, milestoneId, {
|
|
2275
2305
|
branch: milestoneBranch,
|
|
2276
2306
|
deleteBranch: false,
|
|
2277
2307
|
});
|
|
2278
2308
|
} catch (err) {
|
|
2309
|
+
worktreeRemoved = false;
|
|
2279
2310
|
// Best-effort -- worktree dir may already be gone
|
|
2280
2311
|
logWarning("worktree", `worktree removal failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2281
2312
|
}
|
|
2282
2313
|
|
|
2314
|
+
if (!worktreeRemoved) {
|
|
2315
|
+
logWarning(
|
|
2316
|
+
"worktree",
|
|
2317
|
+
`Skipping milestone branch deletion for ${milestoneBranch}; worktree removal was aborted to preserve uncommitted milestone work.`,
|
|
2318
|
+
);
|
|
2319
|
+
return;
|
|
2320
|
+
}
|
|
2321
|
+
|
|
2283
2322
|
// 13. Delete milestone branch (after worktree removal so ref is unlocked)
|
|
2284
2323
|
try {
|
|
2285
2324
|
nativeBranchDelete(originalBasePath_, milestoneBranch);
|
|
@@ -236,7 +236,7 @@ import {
|
|
|
236
236
|
getSlice,
|
|
237
237
|
getTask,
|
|
238
238
|
} from "./gsd-db.js";
|
|
239
|
-
import { closeWorkflowDatabase } from "./db-workspace.js";
|
|
239
|
+
import { checkpointWorkflowDatabase, closeWorkflowDatabase } from "./db-workspace.js";
|
|
240
240
|
import { markLatestActiveForWorkerCanceled } from "./db/unit-dispatches.js";
|
|
241
241
|
import { writeUnitRuntimeRecord } from "./unit-runtime.js";
|
|
242
242
|
import { countPendingCaptures } from "./captures.js";
|
|
@@ -1051,6 +1051,12 @@ export function stopAutoRemote(projectRoot: string): {
|
|
|
1051
1051
|
* The normal stop path stays SIGTERM-only so cooperative sessions can clean up;
|
|
1052
1052
|
* this path is only for the explicit "Force start" action.
|
|
1053
1053
|
*/
|
|
1054
|
+
// Grace window between SIGTERM and SIGKILL so the target can run its cleanup
|
|
1055
|
+
// handler (lock release, DB snapshot flush, worktree teardown). Polled so a
|
|
1056
|
+
// process that exits early is not made to wait the full window.
|
|
1057
|
+
const FORCE_STOP_GRACE_MS = 3000;
|
|
1058
|
+
const FORCE_STOP_POLL_MS = 100;
|
|
1059
|
+
|
|
1054
1060
|
export function forceStopAutoRemote(projectRoot: string): {
|
|
1055
1061
|
found: boolean;
|
|
1056
1062
|
pid?: number;
|
|
@@ -1071,6 +1077,13 @@ export function forceStopAutoRemote(projectRoot: string): {
|
|
|
1071
1077
|
|
|
1072
1078
|
try {
|
|
1073
1079
|
process.kill(lock.pid, "SIGTERM");
|
|
1080
|
+
// Poll for graceful exit before escalating. Synchronous sleep keeps this
|
|
1081
|
+
// function sync for its callers (Atomics.wait times out with no notify).
|
|
1082
|
+
const deadline = Date.now() + FORCE_STOP_GRACE_MS;
|
|
1083
|
+
while (Date.now() < deadline) {
|
|
1084
|
+
if (!isLockProcessAlive(lock)) break;
|
|
1085
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, FORCE_STOP_POLL_MS);
|
|
1086
|
+
}
|
|
1074
1087
|
if (isLockProcessAlive(lock)) {
|
|
1075
1088
|
process.kill(lock.pid, "SIGKILL");
|
|
1076
1089
|
}
|
|
@@ -1615,6 +1628,11 @@ export async function stopAuto(
|
|
|
1615
1628
|
const installTerminalCloseoutOutcome = completionStopRequested && preserveCloseoutTranscript;
|
|
1616
1629
|
const preserveCompletionSurface = completionStopRequested || preserveCloseoutTranscript;
|
|
1617
1630
|
s.completionStopInProgress = preserveCompletionSurface;
|
|
1631
|
+
try {
|
|
1632
|
+
checkpointWorkflowDatabase();
|
|
1633
|
+
} catch (e) {
|
|
1634
|
+
debugLog("stop-checkpoint-failed", { error: e instanceof Error ? e.message : String(e) });
|
|
1635
|
+
}
|
|
1618
1636
|
playNotificationBell("stop", loadedPreferences?.notifications);
|
|
1619
1637
|
|
|
1620
1638
|
// #4764 — telemetry: record the exit reason, isolation mode, whether an auto
|
|
@@ -2299,7 +2317,7 @@ export function buildWorktreeLifecycleDeps(): WorktreeLifecycleDeps {
|
|
|
2299
2317
|
// C4 (#5627) — GitServiceImpl constructor → gitServiceFactory
|
|
2300
2318
|
//
|
|
2301
2319
|
// Final WorktreeLifecycleDeps shape: 3 fields (gitServiceFactory,
|
|
2302
|
-
// worktreeProjection,
|
|
2320
|
+
// worktreeProjection, mergeMilestone transaction runner). Down from 18 at slice-7
|
|
2303
2321
|
// closure.
|
|
2304
2322
|
return {
|
|
2305
2323
|
gitServiceFactory: (basePath: string) => {
|
|
@@ -2308,7 +2326,7 @@ export function buildWorktreeLifecycleDeps(): WorktreeLifecycleDeps {
|
|
|
2308
2326
|
return new GitServiceImpl(basePath, gitConfig);
|
|
2309
2327
|
},
|
|
2310
2328
|
worktreeProjection: new WorktreeStateProjection(),
|
|
2311
|
-
|
|
2329
|
+
mergeMilestone: createMilestoneMergeTransaction(mergeMilestoneToMain),
|
|
2312
2330
|
};
|
|
2313
2331
|
}
|
|
2314
2332
|
|
|
@@ -2357,6 +2375,7 @@ function buildLoopDeps(pi: ExtensionAPI, ctx: ExtensionContext): LoopDeps {
|
|
|
2357
2375
|
stopAuto,
|
|
2358
2376
|
pauseAuto,
|
|
2359
2377
|
clearUnitTimeout,
|
|
2378
|
+
checkpointWorkflowDatabase,
|
|
2360
2379
|
updateProgressWidget,
|
|
2361
2380
|
...cmux,
|
|
2362
2381
|
handleLostSessionLock: (ctx: ExtensionContext | undefined, lockStatus: SessionLockStatus | undefined) => {
|
|
@@ -710,12 +710,14 @@ export function registerDbTools(pi: ExtensionAPI): void {
|
|
|
710
710
|
inputs: Type.Array(Type.String(), { description: "Array<string> of input files or references; pass [\"path\"] or [], never a single string" }),
|
|
711
711
|
expectedOutput: Type.Array(Type.String(), { description: "Array<string> of files this task creates or overwrites; pass [\"path\"] or [], never prose or a single string" }),
|
|
712
712
|
observabilityImpact: Type.Optional(Type.String({ description: "Task observability impact" })),
|
|
713
|
+
targetRepositories: Type.Optional(Type.Array(Type.String(), { description: "Repository id(s) this task touches (parent workspace); must match a Declared Repository. Omit for single-repo projects." })),
|
|
713
714
|
}), { description: "Optional full task replacement for the slice. Omit for incremental planning, then call gsd_plan_task once per task." })),
|
|
714
715
|
// ── Enrichment metadata (optional — defaults to empty) ────────────
|
|
715
716
|
successCriteria: Type.Optional(Type.String({ description: "Slice success criteria block" })),
|
|
716
717
|
proofLevel: Type.Optional(Type.String({ description: "Slice proof level" })),
|
|
717
718
|
integrationClosure: Type.Optional(Type.String({ description: "Slice integration closure" })),
|
|
718
719
|
observabilityImpact: Type.Optional(Type.String({ description: "Slice observability impact" })),
|
|
720
|
+
targetRepositories: Type.Optional(Type.Array(Type.String(), { description: "Slice-wide default repository id(s) (parent workspace); must match a Declared Repository. Omit for single-repo projects." })),
|
|
719
721
|
// Single-writer v3 audit trail (Stream 2): caller-provided actor identity + causation.
|
|
720
722
|
actorName: Type.Optional(Type.String({ description: "Caller-provided actor identity for the audit trail (e.g. 'executor-01', 'gsd-orchestrator')" })),
|
|
721
723
|
triggerReason: Type.Optional(Type.String({ description: "Caller-provided reason this action was triggered (e.g. 'plan-phase complete')" })),
|
|
@@ -789,6 +791,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
|
|
|
789
791
|
inputs: Type.Array(Type.String(), { description: "Array<string> of input files or references; pass [\"path\"] or [], never a single string" }),
|
|
790
792
|
expectedOutput: Type.Array(Type.String(), { description: "Array<string> of files this task creates or overwrites; pass [\"path\"] or [], never prose or a single string" }),
|
|
791
793
|
observabilityImpact: Type.Optional(Type.String({ description: "Task observability impact" })),
|
|
794
|
+
targetRepositories: Type.Optional(Type.Array(Type.String(), { description: "Repository id(s) this task touches (parent workspace); must match a Declared Repository. Omit for single-repo projects." })),
|
|
792
795
|
// Single-writer v3 audit trail (Stream 2): caller-provided actor identity + causation.
|
|
793
796
|
actorName: Type.Optional(Type.String({ description: "Caller-provided actor identity for the audit trail (e.g. 'executor-01', 'gsd-orchestrator')" })),
|
|
794
797
|
triggerReason: Type.Optional(Type.String({ description: "Caller-provided reason this action was triggered (e.g. 'plan-phase complete')" })),
|
|
@@ -32,6 +32,12 @@ import {
|
|
|
32
32
|
recordAutoToolSurfaceSnapshot,
|
|
33
33
|
recordToolInvocationError,
|
|
34
34
|
} from "../auto-runtime-state.js";
|
|
35
|
+
import {
|
|
36
|
+
isDeterministicPolicyError,
|
|
37
|
+
isQueuedUserMessageSkip,
|
|
38
|
+
isToolInvocationError,
|
|
39
|
+
isToolUnavailableError,
|
|
40
|
+
} from "../auto-tool-tracking.js";
|
|
35
41
|
import { applyProviderPayloadPolicy } from "../provider-payload-policy.js";
|
|
36
42
|
|
|
37
43
|
import { checkToolCallLoop, recordToolCallLoopMutation, resetToolCallLoopGuard } from "./tool-call-loop-guard.js";
|
|
@@ -77,9 +83,24 @@ import { clearPendingAutoStart } from "../pending-auto-start.js";
|
|
|
77
83
|
import { resolveWorkflowToolBasePath } from "./dynamic-tools.js";
|
|
78
84
|
import { getRequiredWorkflowToolsForUnit } from "../unit-tool-contracts.js";
|
|
79
85
|
import { flushAllManifests } from "../workflow-manifest.js";
|
|
86
|
+
import { recordUnitHarnessAbort, type UnitHarnessAbortRecord } from "../unit-runtime.js";
|
|
80
87
|
|
|
81
88
|
let approvalQuestionAbortInFlight = false;
|
|
82
89
|
|
|
90
|
+
function recordCurrentUnitHarnessAbort(
|
|
91
|
+
abort: Omit<UnitHarnessAbortRecord, "recordedAt"> & { recordedAt?: number },
|
|
92
|
+
): void {
|
|
93
|
+
const dash = getAutoRuntimeSnapshot();
|
|
94
|
+
if (!dash.active || !dash.basePath || !dash.currentUnit) return;
|
|
95
|
+
recordUnitHarnessAbort(
|
|
96
|
+
dash.basePath,
|
|
97
|
+
dash.currentUnit.type,
|
|
98
|
+
dash.currentUnit.id,
|
|
99
|
+
dash.currentUnit.startedAt,
|
|
100
|
+
abort,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
83
104
|
type WelcomeScreenModule = {
|
|
84
105
|
buildWelcomeScreenLines(opts: { version: string; remoteChannel?: string; width?: number }): string[];
|
|
85
106
|
};
|
|
@@ -209,10 +230,26 @@ const WORKFLOW_GSD_TOOL_NAMES = [
|
|
|
209
230
|
...Object.values(AUTO_UNIT_SCOPED_TOOLS).flat(),
|
|
210
231
|
].filter(isGsdManagedTool);
|
|
211
232
|
|
|
233
|
+
const WORKFLOW_ONLY_AUTO_BASE_TOOL_NAMES = [
|
|
234
|
+
"find",
|
|
235
|
+
"glob",
|
|
236
|
+
"grep",
|
|
237
|
+
"ls",
|
|
238
|
+
"read",
|
|
239
|
+
"subagent",
|
|
240
|
+
] as const;
|
|
241
|
+
|
|
212
242
|
function isGsdManagedTool(name: string): boolean {
|
|
213
243
|
return name.startsWith("gsd_") || name === "memory_query" || name === "capture_thought" || name === "gsd_graph";
|
|
214
244
|
}
|
|
215
245
|
|
|
246
|
+
function autoBaseToolNamesForUnit(unitType: string | undefined): readonly string[] {
|
|
247
|
+
const manifest = unitType ? resolveManifest(unitType) : null;
|
|
248
|
+
return manifest?.tools.mode === "workflow-only"
|
|
249
|
+
? WORKFLOW_ONLY_AUTO_BASE_TOOL_NAMES
|
|
250
|
+
: MINIMAL_AUTO_BASE_TOOL_NAMES;
|
|
251
|
+
}
|
|
252
|
+
|
|
216
253
|
/**
|
|
217
254
|
* Resolves requested tool names against active tools using exact and MCP-scoped matches.
|
|
218
255
|
*
|
|
@@ -275,7 +312,7 @@ export function buildMinimalAutoGsdToolSet(
|
|
|
275
312
|
return buildRunUatGsdToolSet(activeToolNames, registeredToolNames);
|
|
276
313
|
}
|
|
277
314
|
const unitTools = unitType ? AUTO_UNIT_SCOPED_TOOLS[unitType] ?? [] : [];
|
|
278
|
-
const autoBaseTools = new Set<string>(
|
|
315
|
+
const autoBaseTools = new Set<string>(autoBaseToolNamesForUnit(unitType));
|
|
279
316
|
const availableBaseTools = registeredToolNames.filter((name) => autoBaseTools.has(name));
|
|
280
317
|
const preserved = [...new Set([
|
|
281
318
|
...activeToolNames.filter((name) => autoBaseTools.has(name)),
|
|
@@ -599,6 +636,72 @@ function formatLoopGuardBlockReason(reason: string | undefined): string | undefi
|
|
|
599
636
|
);
|
|
600
637
|
}
|
|
601
638
|
|
|
639
|
+
function isGateResultPersistenceTool(toolName: string): boolean {
|
|
640
|
+
return toolName === "gsd_save_gate_result" || toolName === "gsd_uat_result_save";
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function resultDetails(result: unknown): Record<string, unknown> {
|
|
644
|
+
if (!result || typeof result !== "object") return {};
|
|
645
|
+
const details = (result as { details?: unknown }).details;
|
|
646
|
+
return details && typeof details === "object" ? details as Record<string, unknown> : {};
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
function isAbortedExecutionToolResult(toolName: string, result: unknown): boolean {
|
|
650
|
+
if (toolName !== "gsd_exec" && toolName !== "gsd_uat_exec") return false;
|
|
651
|
+
const details = resultDetails(result);
|
|
652
|
+
return details.aborted === true || details.force_resolved === true || details.timed_out === true;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function isRetryableHarnessToolError(toolName: string, result: unknown, errorText: string): boolean {
|
|
656
|
+
if (isGateResultPersistenceTool(toolName)) return false;
|
|
657
|
+
if (isDeterministicPolicyError(errorText)) return false;
|
|
658
|
+
if (toolName === "gsd_exec" || toolName === "gsd_uat_exec") {
|
|
659
|
+
if (isAbortedExecutionToolResult(toolName, result)) return true;
|
|
660
|
+
if (isToolUnavailableError(errorText)) return true;
|
|
661
|
+
if (isQueuedUserMessageSkip(errorText)) return true;
|
|
662
|
+
return isToolInvocationError(errorText);
|
|
663
|
+
}
|
|
664
|
+
if (isToolUnavailableError(errorText)) return true;
|
|
665
|
+
if (isQueuedUserMessageSkip(errorText)) return true;
|
|
666
|
+
return false;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
function recordRetryableHarnessToolError(toolName: string, result: unknown, errorText: string): void {
|
|
670
|
+
if (!isRetryableHarnessToolError(toolName, result, errorText)) return;
|
|
671
|
+
recordCurrentUnitHarnessAbort({
|
|
672
|
+
kind: "tool-error",
|
|
673
|
+
reason: errorText || "Tool execution failed before the unit could complete its gate evaluation.",
|
|
674
|
+
toolName,
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
function recordRetryableTurnAbort(event: { abortOrigin?: unknown; messages?: unknown[] }): void {
|
|
679
|
+
const origin = typeof event.abortOrigin === "string" ? event.abortOrigin : undefined;
|
|
680
|
+
if (origin === "session-transition") return;
|
|
681
|
+
|
|
682
|
+
const messages = Array.isArray(event.messages) ? event.messages : [];
|
|
683
|
+
const lastMsg = messages[messages.length - 1];
|
|
684
|
+
const stopReason = lastMsg && typeof lastMsg === "object"
|
|
685
|
+
? (lastMsg as { stopReason?: unknown }).stopReason
|
|
686
|
+
: undefined;
|
|
687
|
+
if (stopReason !== "aborted" && stopReason !== "error") return;
|
|
688
|
+
|
|
689
|
+
const errorMessage = lastMsg && typeof lastMsg === "object"
|
|
690
|
+
? (lastMsg as { errorMessage?: unknown }).errorMessage
|
|
691
|
+
: undefined;
|
|
692
|
+
const reason = [
|
|
693
|
+
"Agent turn aborted before the unit could complete its gate evaluation.",
|
|
694
|
+
origin ? `origin=${origin}` : undefined,
|
|
695
|
+
typeof stopReason === "string" ? `stopReason=${stopReason}` : undefined,
|
|
696
|
+
typeof errorMessage === "string" && errorMessage.trim() ? `error=${errorMessage.trim()}` : undefined,
|
|
697
|
+
].filter(Boolean).join(" ");
|
|
698
|
+
|
|
699
|
+
recordCurrentUnitHarnessAbort({
|
|
700
|
+
kind: "turn-abort",
|
|
701
|
+
reason,
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
|
|
602
705
|
function beginSourceObservationStoreForCurrentUnit(
|
|
603
706
|
ctx?: { cwd?: string },
|
|
604
707
|
): ReturnType<typeof getSourceObservationStore> | null {
|
|
@@ -902,8 +1005,8 @@ export function registerHooks(
|
|
|
902
1005
|
await prepareWorkflowMcpForHookContext(ctx, basePath);
|
|
903
1006
|
|
|
904
1007
|
// Migrate legacy .gsd/milestones/ to flat-phase .gsd/phases/ when detected.
|
|
905
|
-
//
|
|
906
|
-
//
|
|
1008
|
+
// Fail closed on migration errors: resolvers assume flat-phase paths after
|
|
1009
|
+
// startup, so continuing with nested disk state corrupts later checks.
|
|
907
1010
|
try {
|
|
908
1011
|
const { isInAutoWorktree } = await import("../auto-worktree.js");
|
|
909
1012
|
if (!isInAutoWorktree(basePath)) {
|
|
@@ -917,11 +1020,21 @@ export function registerHooks(
|
|
|
917
1020
|
} else {
|
|
918
1021
|
safetyLogWarning(
|
|
919
1022
|
"bootstrap",
|
|
920
|
-
"flat-phase migration
|
|
1023
|
+
"flat-phase migration required: legacy .gsd/milestones/ layout detected but the workflow database could not be opened — fix database access before starting GSD",
|
|
1024
|
+
);
|
|
1025
|
+
throw new Error(
|
|
1026
|
+
"flat-phase migration required but the workflow database could not be opened; fix database access before starting GSD",
|
|
921
1027
|
);
|
|
922
1028
|
}
|
|
923
1029
|
}
|
|
924
1030
|
}
|
|
1031
|
+
} catch (err) {
|
|
1032
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1033
|
+
safetyLogWarning("bootstrap", `flat-phase migration failed: ${message}`);
|
|
1034
|
+
throw new Error(`flat-phase migration failed: ${message}`);
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
try {
|
|
925
1038
|
const projectRoot = resolveWorktreeProjectRoot(basePath);
|
|
926
1039
|
const { pruneStaleFlatPhaseBackups } = await import("../flat-phase-migration.js");
|
|
927
1040
|
const pruned = pruneStaleFlatPhaseBackups(projectRoot);
|
|
@@ -932,7 +1045,7 @@ export function registerHooks(
|
|
|
932
1045
|
);
|
|
933
1046
|
}
|
|
934
1047
|
} catch (err) {
|
|
935
|
-
safetyLogWarning("bootstrap", `flat-phase
|
|
1048
|
+
safetyLogWarning("bootstrap", `flat-phase backup pruning: ${err instanceof Error ? err.message : String(err)}`);
|
|
936
1049
|
}
|
|
937
1050
|
|
|
938
1051
|
// Apply show_token_cost preference (#1515)
|
|
@@ -1090,6 +1203,7 @@ export function registerHooks(
|
|
|
1090
1203
|
|
|
1091
1204
|
pi.on("agent_end", async (event, ctx: ExtensionContext) => {
|
|
1092
1205
|
approvalQuestionAbortInFlight = false;
|
|
1206
|
+
recordRetryableTurnAbort(event);
|
|
1093
1207
|
resetToolCallLoopGuard();
|
|
1094
1208
|
resetPendingGatePauseGuard();
|
|
1095
1209
|
await resetAskUserQuestionsTurnCache();
|
|
@@ -1315,6 +1429,12 @@ export function registerHooks(
|
|
|
1315
1429
|
// ── Loop guard: block repeated identical tool calls ──
|
|
1316
1430
|
const loopCheck = checkToolCallLoop(toolName, event.input as Record<string, unknown>);
|
|
1317
1431
|
if (loopCheck.block) {
|
|
1432
|
+
recordCurrentUnitHarnessAbort({
|
|
1433
|
+
kind: "tool-loop-guard",
|
|
1434
|
+
reason: loopCheck.reason ?? "Tool-call loop guard blocked a repeated tool call.",
|
|
1435
|
+
toolName,
|
|
1436
|
+
count: loopCheck.count,
|
|
1437
|
+
});
|
|
1318
1438
|
return { block: true, reason: formatLoopGuardBlockReason(loopCheck.reason) };
|
|
1319
1439
|
}
|
|
1320
1440
|
|
|
@@ -1603,6 +1723,7 @@ export function registerHooks(
|
|
|
1603
1723
|
// Let recordToolInvocationError classify the failure so non-gsd_ harness
|
|
1604
1724
|
// errors and deterministic policy rejections are handled consistently.
|
|
1605
1725
|
recordToolInvocationError(event.toolName, errorText);
|
|
1726
|
+
recordRetryableHarnessToolError(toolName, resultPayload, errorText);
|
|
1606
1727
|
} else if (isAutoActive()) {
|
|
1607
1728
|
clearToolInvocationError();
|
|
1608
1729
|
}
|
|
@@ -1799,6 +1920,7 @@ export function registerHooks(
|
|
|
1799
1920
|
// for every finalized tool call on every engine, so error classification
|
|
1800
1921
|
// and evidence persistence here cover external engines that skip tool_result.
|
|
1801
1922
|
pi.on("tool_execution_end", async (event) => {
|
|
1923
|
+
const toolName = canonicalToolName(event.toolName);
|
|
1802
1924
|
markToolEnd(event.toolCallId);
|
|
1803
1925
|
// #2883/#4974: Capture deterministic invocation/policy errors
|
|
1804
1926
|
// so postUnitPreVerification can break the retry loop instead of re-dispatching.
|
|
@@ -1809,6 +1931,7 @@ export function registerHooks(
|
|
|
1809
1931
|
// Let recordToolInvocationError classify the failure so non-gsd_ harness
|
|
1810
1932
|
// errors and deterministic policy rejections are handled consistently.
|
|
1811
1933
|
recordToolInvocationError(event.toolName, errorText);
|
|
1934
|
+
recordRetryableHarnessToolError(toolName, event.result, errorText);
|
|
1812
1935
|
} else if (isAutoActive()) {
|
|
1813
1936
|
clearToolInvocationError();
|
|
1814
1937
|
}
|
|
@@ -6,22 +6,85 @@ import type { ExtensionAPI } from "@gsd/pi-coding-agent";
|
|
|
6
6
|
|
|
7
7
|
import { getAutoRuntimeSnapshot } from "../auto-runtime-state.js";
|
|
8
8
|
import { scheduleAutoWakeup } from "../auto/schedule-wakeup.js";
|
|
9
|
+
import { logWarning } from "../workflow-logger.js";
|
|
9
10
|
import { resolveCtxCwd } from "./dynamic-tools.js";
|
|
10
11
|
|
|
11
12
|
const MAX_WAKEUP_DELAY_SECONDS = 24 * 60 * 60;
|
|
13
|
+
const INTERACTIVE_WAKEUP_CUSTOM_TYPE = "gsd-schedule-wakeup";
|
|
14
|
+
|
|
15
|
+
// One pending interactive wakeup per session, keyed by base path — the same
|
|
16
|
+
// scoping auto-mode uses for its wakeup map (see `wakeupKey`). Re-arming cancels
|
|
17
|
+
// only that session's prior timer, so repeated polling never stacks overlapping
|
|
18
|
+
// wakeups, and concurrent projects in one host process don't cancel each other.
|
|
19
|
+
const pendingInteractiveWakeups = new Map<string, ReturnType<typeof setTimeout>>();
|
|
20
|
+
|
|
21
|
+
function scheduleInteractiveWakeup(
|
|
22
|
+
pi: ExtensionAPI,
|
|
23
|
+
key: string,
|
|
24
|
+
delaySeconds: number,
|
|
25
|
+
prompt: string,
|
|
26
|
+
reason: string,
|
|
27
|
+
): void {
|
|
28
|
+
const existing = pendingInteractiveWakeups.get(key);
|
|
29
|
+
if (existing) clearTimeout(existing);
|
|
30
|
+
|
|
31
|
+
const handle = setTimeout(() => {
|
|
32
|
+
if (pendingInteractiveWakeups.get(key) === handle) {
|
|
33
|
+
pendingInteractiveWakeups.delete(key);
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
void Promise.resolve(pi.sendMessage(
|
|
37
|
+
{
|
|
38
|
+
customType: INTERACTIVE_WAKEUP_CUSTOM_TYPE,
|
|
39
|
+
content: prompt,
|
|
40
|
+
display: true,
|
|
41
|
+
details: { delaySeconds, reason },
|
|
42
|
+
},
|
|
43
|
+
{ triggerTurn: true },
|
|
44
|
+
)).catch((error) => {
|
|
45
|
+
logWarning(
|
|
46
|
+
"bootstrap",
|
|
47
|
+
`ScheduleWakeup interactive dispatch failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
} catch (error) {
|
|
51
|
+
logWarning(
|
|
52
|
+
"bootstrap",
|
|
53
|
+
`ScheduleWakeup interactive dispatch failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
}, delaySeconds * 1000);
|
|
57
|
+
pendingInteractiveWakeups.set(key, handle);
|
|
58
|
+
if (
|
|
59
|
+
typeof handle === "object" &&
|
|
60
|
+
handle !== null &&
|
|
61
|
+
"unref" in handle &&
|
|
62
|
+
typeof handle.unref === "function"
|
|
63
|
+
) {
|
|
64
|
+
handle.unref();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function _resetInteractiveWakeupsForTest(): void {
|
|
69
|
+
for (const handle of pendingInteractiveWakeups.values()) {
|
|
70
|
+
clearTimeout(handle);
|
|
71
|
+
}
|
|
72
|
+
pendingInteractiveWakeups.clear();
|
|
73
|
+
}
|
|
12
74
|
|
|
13
75
|
export function registerScheduleWakeupTool(pi: ExtensionAPI): void {
|
|
14
76
|
pi.registerTool({
|
|
15
77
|
name: "ScheduleWakeup",
|
|
16
78
|
label: "Schedule Wakeup",
|
|
17
79
|
description:
|
|
18
|
-
"In GSD auto-mode,
|
|
19
|
-
"
|
|
20
|
-
promptSnippet: "Schedule a
|
|
80
|
+
"Schedule a delayed continuation turn. In GSD auto-mode, continue the current unit in the same session; " +
|
|
81
|
+
"outside auto-mode, start a new triggered turn with the supplied wakeup prompt.",
|
|
82
|
+
promptSnippet: "Schedule a wakeup prompt after a delay.",
|
|
21
83
|
promptGuidelines: [
|
|
22
84
|
"Use ScheduleWakeup at the end of an execute-task turn when waiting for a long external process.",
|
|
23
85
|
"Include a prompt that says exactly what external state to check next and what artifact to write when done.",
|
|
24
86
|
"Re-arm ScheduleWakeup on each polling turn if the external process is still running.",
|
|
87
|
+
"Outside auto-mode, use ScheduleWakeup when the user asks you to check back or poll later.",
|
|
25
88
|
],
|
|
26
89
|
parameters: Type.Object({
|
|
27
90
|
delaySeconds: Type.Number({
|
|
@@ -40,27 +103,32 @@ export function registerScheduleWakeupTool(pi: ExtensionAPI): void {
|
|
|
40
103
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
41
104
|
const dash = getAutoRuntimeSnapshot();
|
|
42
105
|
const currentUnit = dash.currentUnit;
|
|
43
|
-
const
|
|
106
|
+
const delaySeconds = Math.max(
|
|
107
|
+
1,
|
|
108
|
+
Math.min(MAX_WAKEUP_DELAY_SECONDS, Math.floor(params.delaySeconds)),
|
|
109
|
+
);
|
|
110
|
+
const reason = params.reason ?? "";
|
|
44
111
|
|
|
45
112
|
if (!dash.active || !currentUnit) {
|
|
113
|
+
const interactiveKey = dash.basePath || resolveCtxCwd(ctx);
|
|
114
|
+
scheduleInteractiveWakeup(pi, interactiveKey, delaySeconds, params.prompt, reason);
|
|
46
115
|
return {
|
|
47
|
-
content: [{
|
|
48
|
-
|
|
49
|
-
|
|
116
|
+
content: [{
|
|
117
|
+
type: "text",
|
|
118
|
+
text: `Wakeup scheduled for ${delaySeconds}s. GSD will start a new turn with the wakeup prompt.`,
|
|
119
|
+
}],
|
|
120
|
+
details: { operation: "schedule_wakeup", delaySeconds, mode: "interactive" },
|
|
50
121
|
};
|
|
51
122
|
}
|
|
52
123
|
|
|
53
|
-
const
|
|
54
|
-
1,
|
|
55
|
-
Math.min(MAX_WAKEUP_DELAY_SECONDS, Math.floor(params.delaySeconds)),
|
|
56
|
-
);
|
|
124
|
+
const basePath = dash.basePath || resolveCtxCwd(ctx);
|
|
57
125
|
scheduleAutoWakeup({
|
|
58
126
|
basePath,
|
|
59
127
|
unitType: currentUnit.type,
|
|
60
128
|
unitId: currentUnit.id,
|
|
61
129
|
delayMs: delaySeconds * 1000,
|
|
62
130
|
prompt: params.prompt,
|
|
63
|
-
reason
|
|
131
|
+
reason,
|
|
64
132
|
createdAt: Date.now(),
|
|
65
133
|
});
|
|
66
134
|
|
|
@@ -25,6 +25,8 @@
|
|
|
25
25
|
|
|
26
26
|
import { createHash } from "node:crypto";
|
|
27
27
|
import { INHERENTLY_REPEATABLE_TOOL_SET } from "./core-session-tools.js";
|
|
28
|
+
import { hasBrowserContractPrefix } from "../../shared/browser-contract.js";
|
|
29
|
+
import { canonicalToolName } from "../engine-hook-contract.js";
|
|
28
30
|
|
|
29
31
|
const MAX_CONSECUTIVE_IDENTICAL_CALLS = 4;
|
|
30
32
|
|
|
@@ -130,8 +132,16 @@ export function checkToolCallLoop(
|
|
|
130
132
|
perToolLastMutationEpoch.set(toolName, mutationEpoch);
|
|
131
133
|
|
|
132
134
|
// Read-only navigation tools are normal context gathering; Guard 1 still
|
|
133
|
-
// catches true reread loops with identical arguments.
|
|
134
|
-
|
|
135
|
+
// catches true reread loops with identical arguments. Browser Automation
|
|
136
|
+
// Contract tools (browser_*) are the same shape: a browser-backed UAT makes
|
|
137
|
+
// many distinct-arg calls (read a message, count edits, inspect a card, …),
|
|
138
|
+
// so the arg-independent per-tool cap misfires on legitimate verification;
|
|
139
|
+
// Guard 1's identical-signature streak still catches a genuinely stuck
|
|
140
|
+
// browser loop.
|
|
141
|
+
if (
|
|
142
|
+
PER_TOOL_CAP_EXEMPT_TOOLS.has(toolName) ||
|
|
143
|
+
hasBrowserContractPrefix(canonicalToolName(toolName))
|
|
144
|
+
) {
|
|
135
145
|
return { block: false, count: consecutiveCount };
|
|
136
146
|
}
|
|
137
147
|
|
|
@@ -1169,7 +1169,7 @@ function allowsControlledSubagentDispatch(
|
|
|
1169
1169
|
policy: ToolsPolicy,
|
|
1170
1170
|
): policy is ToolsPolicy & { readonly allowedSubagents: readonly string[] } {
|
|
1171
1171
|
return (
|
|
1172
|
-
(policy.mode === "planning-dispatch" || policy.mode === "verification") &&
|
|
1172
|
+
(policy.mode === "planning-dispatch" || policy.mode === "verification" || policy.mode === "workflow-only") &&
|
|
1173
1173
|
Array.isArray((policy as { readonly allowedSubagents?: unknown }).allowedSubagents)
|
|
1174
1174
|
);
|
|
1175
1175
|
}
|
|
@@ -1199,6 +1199,7 @@ function warnMissingControlledDispatchAgentClasses(unitType: string, mode: strin
|
|
|
1199
1199
|
const PLANNING_SAFE_TOOLS = new Set([
|
|
1200
1200
|
"read", "grep", "find", "ls", "glob",
|
|
1201
1201
|
"ask_user_questions",
|
|
1202
|
+
"memory_query",
|
|
1202
1203
|
"search-the-web", "resolve_library", "get_library_docs", "fetch_page",
|
|
1203
1204
|
"search_and_read",
|
|
1204
1205
|
]);
|
|
@@ -1265,6 +1266,9 @@ type PlanningUnitBlockResult = {
|
|
|
1265
1266
|
* → allows Bash for project verification commands, keeps
|
|
1266
1267
|
* writes restricted to .gsd/, and permits subagent dispatch
|
|
1267
1268
|
* only when the manifest declares allowedSubagents.
|
|
1269
|
+
* - "workflow-only"
|
|
1270
|
+
* → allows reads, GSD workflow tools, and declared read-only
|
|
1271
|
+
* subagents; blocks generic Bash and direct file writes.
|
|
1268
1272
|
*
|
|
1269
1273
|
* `pathOrCommand` is the file path for write/edit-shaped tools and the
|
|
1270
1274
|
* shell command for bash. Other tools ignore this argument.
|
|
@@ -1354,6 +1358,24 @@ export function shouldBlockPlanningUnit(
|
|
|
1354
1358
|
return planningBlock(unitType, policy.mode, "subagent dispatch is not permitted in planning units");
|
|
1355
1359
|
}
|
|
1356
1360
|
|
|
1361
|
+
if (policy.mode === "workflow-only") {
|
|
1362
|
+
if (tool === "bash") {
|
|
1363
|
+
return planningBlock(
|
|
1364
|
+
unitType,
|
|
1365
|
+
policy.mode,
|
|
1366
|
+
`bash is not permitted; use the unit's dedicated GSD workflow tools for durable validation output`,
|
|
1367
|
+
);
|
|
1368
|
+
}
|
|
1369
|
+
if (PLANNING_WRITE_TOOLS.has(tool)) {
|
|
1370
|
+
return planningBlock(
|
|
1371
|
+
unitType,
|
|
1372
|
+
policy.mode,
|
|
1373
|
+
`cannot ${tool} "${pathOrCommand}" — direct artifact writes are disabled; call the required GSD workflow tool instead`,
|
|
1374
|
+
);
|
|
1375
|
+
}
|
|
1376
|
+
return planningBlock(unitType, policy.mode, `tool "${tool}" is not on the workflow-only allowlist`);
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1357
1379
|
if (tool === "bash") {
|
|
1358
1380
|
if (policy.mode === "verification") {
|
|
1359
1381
|
if (BASH_VERIFICATION_RE.test(pathOrCommand) || BASH_READ_ONLY_RE.test(pathOrCommand)) return { block: false };
|