@opengsd/gsd-pi 1.3.0-dev.65546769 → 1.3.0-dev.72e3af2a
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/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +11 -2
- package/dist/resources/extensions/google-cli/stream-adapter.js +82 -15
- package/dist/resources/extensions/gsd/artifact-verification.js +427 -0
- package/dist/resources/extensions/gsd/auto/orchestrator.js +12 -3
- package/dist/resources/extensions/gsd/auto/session.js +3 -0
- package/dist/resources/extensions/gsd/auto-artifact-paths.js +28 -1
- package/dist/resources/extensions/gsd/auto-dispatch.js +20 -19
- package/dist/resources/extensions/gsd/auto-prompts.js +26 -11
- package/dist/resources/extensions/gsd/auto-recovery.js +6 -507
- package/dist/resources/extensions/gsd/auto-runtime-state.js +4 -5
- package/dist/resources/extensions/gsd/auto-timeout-recovery.js +3 -3
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +103 -13
- package/dist/resources/extensions/gsd/bootstrap/core-session-tools.js +38 -0
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +6 -1
- package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +2 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +10 -19
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +46 -19
- package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +68 -10
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +1 -1
- package/dist/resources/extensions/gsd/commands-context.js +19 -1
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +16 -10
- package/dist/resources/extensions/gsd/commands-worktree.js +12 -10
- package/dist/resources/extensions/gsd/dashboard-overlay.js +32 -3
- package/dist/resources/extensions/gsd/db/queries.js +60 -0
- package/dist/resources/extensions/gsd/db-workspace.js +55 -3
- package/dist/resources/extensions/gsd/doctor-providers.js +92 -8
- package/dist/resources/extensions/gsd/exec-sandbox.js +45 -9
- package/dist/resources/extensions/gsd/forensics.js +2 -32
- package/dist/resources/extensions/gsd/git-service.js +4 -4
- package/dist/resources/extensions/gsd/guided-flow-queue.js +66 -5
- package/dist/resources/extensions/gsd/health-widget.js +55 -29
- package/dist/resources/extensions/gsd/layout-policy.js +3 -1
- package/dist/resources/extensions/gsd/markdown-renderer.js +8 -9
- package/dist/resources/extensions/gsd/memory-consolidation-scanner.js +44 -21
- package/dist/resources/extensions/gsd/migration-auto-check.js +22 -0
- package/dist/resources/extensions/gsd/milestone-ids.js +32 -2
- package/dist/resources/extensions/gsd/milestone-implementation-evidence.js +26 -20
- package/dist/resources/extensions/gsd/prompts/code-review.md +6 -4
- package/dist/resources/extensions/gsd/quick.js +45 -2
- package/dist/resources/extensions/gsd/session-forensics.js +11 -1
- package/dist/resources/extensions/gsd/skills/gsd-headless/references/commands.md +1 -1
- package/dist/resources/extensions/gsd/state/derive/cache.js +28 -0
- package/dist/resources/extensions/gsd/state/derive/db-open.js +39 -0
- package/dist/resources/extensions/gsd/state/derive/from-db.js +452 -0
- package/dist/resources/extensions/gsd/state/derive/index.js +75 -0
- package/dist/resources/extensions/gsd/state/derive/interrupted-work.js +21 -0
- package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-render.js +45 -2
- package/dist/resources/extensions/gsd/state-reconciliation/index.js +48 -23
- package/dist/resources/extensions/gsd/state-reconciliation/registry.js +32 -28
- package/dist/resources/extensions/gsd/state.js +12 -611
- package/dist/resources/extensions/gsd/tools/complete-slice.js +2 -2
- package/dist/resources/extensions/gsd/tools/complete-task.js +43 -14
- package/dist/resources/extensions/gsd/tools/exec-tool.js +7 -2
- package/dist/resources/extensions/gsd/unit-context-composer.js +23 -7
- package/dist/resources/extensions/gsd/unit-registry.js +32 -4
- package/dist/resources/extensions/gsd/unmerged-milestone-guard.js +33 -3
- package/dist/resources/extensions/gsd/validation-block-guard.js +9 -4
- package/dist/resources/extensions/gsd/workflow-projections.js +19 -14
- package/dist/resources/extensions/gsd/workspace-git-preflight.js +30 -1
- package/dist/resources/extensions/gsd/worktree-manager.js +44 -2
- 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 +10 -10
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/react-loadable-manifest.json +9 -9
- package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- 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/page_client-reference-manifest.js +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/visualizer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +2 -2
- 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/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +10 -10
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-react-loadable-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/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/{2659.b7b129ee6a769448.js → 2659.58e950899a9bb82f.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/2772.a7c1fcc69a4685ef.js +1 -0
- package/dist/web/standalone/.next/static/chunks/{3616.3c60753b8ffcbd2e.js → 3616.61a2af74bb8833c8.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/{4283.8e446784528ed9dc.js → 4283.d0d9e0a955e441cb.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/{5826.a46ecdd1cfe8dabc.js → 5826.5421d66c72b9f34e.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/{8785.481aa5869991b760.js → 8785.e29b3134cab1d153.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/8937.640dc9c2aaa1dfad.js +10 -0
- package/dist/web/standalone/.next/static/chunks/app/{page-6644fc6ee8ca1247.js → page-72a856634ad14c10.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/webpack-9c401904f87ded16.js +1 -0
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
- package/package.json +1 -1
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/dist/workflow.d.ts +1 -0
- package/packages/contracts/dist/workflow.d.ts.map +1 -1
- package/packages/contracts/dist/workflow.js +2 -0
- package/packages/contracts/dist/workflow.js.map +1 -1
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/dist/agent-session.d.ts +1 -0
- package/packages/gsd-agent-core/dist/agent-session.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/agent-session.js +3 -0
- package/packages/gsd-agent-core/dist/agent-session.js.map +1 -1
- package/packages/gsd-agent-core/dist/extension-ui-snapshot.d.ts +41 -0
- package/packages/gsd-agent-core/dist/extension-ui-snapshot.d.ts.map +1 -0
- package/packages/gsd-agent-core/dist/extension-ui-snapshot.js +62 -0
- package/packages/gsd-agent-core/dist/extension-ui-snapshot.js.map +1 -0
- package/packages/gsd-agent-core/dist/index.d.ts +2 -0
- package/packages/gsd-agent-core/dist/index.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/index.js +2 -0
- package/packages/gsd-agent-core/dist/index.js.map +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-events.js +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-events.js.map +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-host.d.ts +1 -0
- package/packages/gsd-agent-core/dist/session/agent-session-host.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-host.js.map +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-prompt.d.ts +5 -0
- package/packages/gsd-agent-core/dist/session/agent-session-prompt.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-prompt.js +60 -3
- package/packages/gsd-agent-core/dist/session/agent-session-prompt.js.map +1 -1
- package/packages/gsd-agent-core/dist/transcript-store.d.ts +58 -0
- package/packages/gsd-agent-core/dist/transcript-store.d.ts.map +1 -0
- package/packages/gsd-agent-core/dist/transcript-store.js +132 -0
- package/packages/gsd-agent-core/dist/transcript-store.js.map +1 -0
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts +2 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +25 -11
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller-latency.d.ts +4 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller-latency.d.ts.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller-latency.js +7 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller-latency.js.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts +3 -24
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +26 -829
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-handoff-filter.d.ts +58 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-handoff-filter.d.ts.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-handoff-filter.js +312 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-handoff-filter.js.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-pinned-zone.d.ts +31 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-pinned-zone.d.ts.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-pinned-zone.js +130 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-pinned-zone.js.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.d.ts +15 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.d.ts.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.js +258 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.js.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-tool-rollup.d.ts +13 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-tool-rollup.d.ts.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-tool-rollup.js +118 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-tool-rollup.js.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-state.d.ts +9 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-state.js +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-state.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-ui-state.d.ts +54 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-ui-state.d.ts.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-ui-state.js +20 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-ui-state.js.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts +4 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js +9 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/streaming-render-state.d.ts +56 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/streaming-render-state.d.ts.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/streaming-render-state.js +44 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/streaming-render-state.js.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/tui-transcript-tracker.d.ts +5 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/tui-transcript-tracker.d.ts.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/tui-transcript-tracker.js +77 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/tui-transcript-tracker.js.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.js +18 -0
- package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/README.md +1 -1
- package/packages/mcp-server/dist/server.d.ts +1 -1
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +3 -3
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts +13 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +34 -20
- package/packages/mcp-server/dist/workflow-tools.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/package.json +1 -1
- package/packages/pi-coding-agent/README.md +3 -2
- package/packages/pi-coding-agent/dist/core/session-manager-context.d.ts +9 -0
- package/packages/pi-coding-agent/dist/core/session-manager-context.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager-context.js +94 -0
- package/packages/pi-coding-agent/dist/core/session-manager-context.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager-list.d.ts +8 -0
- package/packages/pi-coding-agent/dist/core/session-manager-list.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager-list.js +244 -0
- package/packages/pi-coding-agent/dist/core/session-manager-list.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager-migration.d.ts +12 -0
- package/packages/pi-coding-agent/dist/core/session-manager-migration.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager-migration.js +84 -0
- package/packages/pi-coding-agent/dist/core/session-manager-migration.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager-types.d.ts +135 -0
- package/packages/pi-coding-agent/dist/core/session-manager-types.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager-types.js +2 -0
- package/packages/pi-coding-agent/dist/core/session-manager-types.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager.d.ts +6 -154
- package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.js +22 -459
- package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/theme/theme-schema.d.ts +75 -75
- package/packages/pi-coding-agent/dist/theme/theme-schema.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/theme/theme-schema.js +1 -1
- package/packages/pi-coding-agent/dist/theme/theme-schema.js.map +1 -1
- package/packages/pi-coding-agent/dist/theme/theme.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/theme/theme.js +11 -7
- package/packages/pi-coding-agent/dist/theme/theme.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/package.json +2 -2
- package/pkg/dist/theme/theme-schema.d.ts +75 -75
- package/pkg/dist/theme/theme-schema.d.ts.map +1 -1
- package/pkg/dist/theme/theme-schema.js +1 -1
- package/pkg/dist/theme/theme-schema.js.map +1 -1
- package/pkg/dist/theme/theme.d.ts.map +1 -1
- package/pkg/dist/theme/theme.js +11 -7
- package/pkg/dist/theme/theme.js.map +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +20 -2
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +80 -0
- package/src/resources/extensions/google-cli/stream-adapter.ts +106 -19
- package/src/resources/extensions/gsd/artifact-verification.ts +464 -0
- package/src/resources/extensions/gsd/auto/orchestrator.ts +25 -11
- package/src/resources/extensions/gsd/auto/session.ts +5 -0
- package/src/resources/extensions/gsd/auto-artifact-paths.ts +47 -1
- package/src/resources/extensions/gsd/auto-dispatch.ts +21 -23
- package/src/resources/extensions/gsd/auto-prompts.ts +38 -12
- package/src/resources/extensions/gsd/auto-recovery.ts +10 -508
- package/src/resources/extensions/gsd/auto-runtime-state.ts +4 -5
- package/src/resources/extensions/gsd/auto-timeout-recovery.ts +3 -2
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +125 -12
- package/src/resources/extensions/gsd/bootstrap/core-session-tools.ts +43 -0
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +6 -1
- package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +2 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +11 -19
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +52 -18
- package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +74 -10
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +1 -1
- package/src/resources/extensions/gsd/commands-context.ts +18 -1
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +14 -9
- package/src/resources/extensions/gsd/commands-worktree.ts +12 -10
- package/src/resources/extensions/gsd/dashboard-overlay.ts +32 -3
- package/src/resources/extensions/gsd/db/queries.ts +79 -0
- package/src/resources/extensions/gsd/db-workspace.ts +61 -3
- package/src/resources/extensions/gsd/doctor-providers.ts +103 -9
- package/src/resources/extensions/gsd/exec-sandbox.ts +49 -9
- package/src/resources/extensions/gsd/forensics.ts +2 -33
- package/src/resources/extensions/gsd/git-service.ts +5 -5
- package/src/resources/extensions/gsd/guided-flow-queue.ts +89 -4
- package/src/resources/extensions/gsd/health-widget.ts +69 -32
- package/src/resources/extensions/gsd/layout-policy.ts +2 -1
- package/src/resources/extensions/gsd/markdown-renderer.ts +8 -11
- package/src/resources/extensions/gsd/memory-consolidation-scanner.ts +51 -19
- package/src/resources/extensions/gsd/migration-auto-check.ts +23 -0
- package/src/resources/extensions/gsd/milestone-ids.ts +31 -2
- package/src/resources/extensions/gsd/milestone-implementation-evidence.ts +35 -21
- package/src/resources/extensions/gsd/prompts/code-review.md +6 -4
- package/src/resources/extensions/gsd/quick.ts +43 -2
- package/src/resources/extensions/gsd/session-forensics.ts +11 -1
- package/src/resources/extensions/gsd/skills/gsd-headless/references/commands.md +1 -1
- package/src/resources/extensions/gsd/state/derive/cache.ts +46 -0
- package/src/resources/extensions/gsd/state/derive/db-open.ts +45 -0
- package/src/resources/extensions/gsd/state/derive/from-db.ts +561 -0
- package/src/resources/extensions/gsd/state/derive/index.ts +104 -0
- package/src/resources/extensions/gsd/state/derive/interrupted-work.ts +31 -0
- package/src/resources/extensions/gsd/state-reconciliation/drift/stale-render.ts +81 -7
- package/src/resources/extensions/gsd/state-reconciliation/index.ts +50 -24
- package/src/resources/extensions/gsd/state-reconciliation/registry.ts +43 -28
- package/src/resources/extensions/gsd/state.ts +32 -732
- package/src/resources/extensions/gsd/tests/auto-artifact-paths.test.ts +98 -1
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +111 -1
- package/src/resources/extensions/gsd/tests/commands-context.test.ts +26 -0
- package/src/resources/extensions/gsd/tests/commands-gsd-core.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/commands-worktree-clean.test.ts +80 -0
- package/src/resources/extensions/gsd/tests/complete-slice.test.ts +11 -0
- package/src/resources/extensions/gsd/tests/complete-task-rollback-evidence.test.ts +48 -8
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +75 -0
- package/src/resources/extensions/gsd/tests/dashboard-overlay.test.ts +55 -2
- package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +26 -1
- package/src/resources/extensions/gsd/tests/doctor-forensics-db-open-regression.test.ts +70 -2
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +107 -0
- package/src/resources/extensions/gsd/tests/exec-graceful-kill.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/exec-tool.test.ts +45 -1
- package/src/resources/extensions/gsd/tests/forensics-error-filter.test.ts +88 -0
- package/src/resources/extensions/gsd/tests/guided-discuss-milestone-prompt-rendering.test.ts +42 -0
- package/src/resources/extensions/gsd/tests/health-widget.test.ts +268 -3
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +119 -1
- package/src/resources/extensions/gsd/tests/integration/queue-active-milestone-context-budget.test.ts +93 -0
- package/src/resources/extensions/gsd/tests/integration/quick-branch-lifecycle.test.ts +56 -9
- package/src/resources/extensions/gsd/tests/knowledge-cold-start.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/memory-consolidation-scanner.test.ts +78 -0
- package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +70 -0
- package/src/resources/extensions/gsd/tests/orchestrator-logs.test.ts +43 -1
- package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +26 -0
- package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +54 -1
- package/src/resources/extensions/gsd/tests/progressive-planning.test.ts +50 -14
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +195 -1
- package/src/resources/extensions/gsd/tests/read-uat-gate-verdict.test.ts +185 -0
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +87 -0
- package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +56 -0
- package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +191 -0
- package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +151 -0
- package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +26 -0
- package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +193 -14
- package/src/resources/extensions/gsd/tests/unmerged-milestone-guard.test.ts +25 -0
- package/src/resources/extensions/gsd/tests/validation-block-guard.test.ts +79 -0
- package/src/resources/extensions/gsd/tests/verify-artifact-tightened.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/workspace-git-preflight.test.ts +151 -2
- package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +39 -0
- package/src/resources/extensions/gsd/tools/complete-slice.ts +2 -2
- package/src/resources/extensions/gsd/tools/complete-task.ts +53 -15
- package/src/resources/extensions/gsd/tools/exec-tool.ts +7 -3
- package/src/resources/extensions/gsd/unit-context-composer.ts +33 -7
- package/src/resources/extensions/gsd/unit-registry.ts +32 -4
- package/src/resources/extensions/gsd/unmerged-milestone-guard.ts +41 -5
- package/src/resources/extensions/gsd/validation-block-guard.ts +13 -7
- package/src/resources/extensions/gsd/workflow-projections.ts +20 -14
- package/src/resources/extensions/gsd/workspace-git-preflight.ts +31 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +41 -1
- package/dist/web/standalone/.next/static/chunks/2772.bfa657f49f955239.js +0 -1
- package/dist/web/standalone/.next/static/chunks/796.e0bdc932325d7e03.js +0 -10
- package/dist/web/standalone/.next/static/chunks/webpack-f46ea08200a0227e.js +0 -1
- /package/dist/web/standalone/.next/static/{BTKtGFF1Y-hvVJEGhBRo9 → O7xDYXO0r4zFhIzY1hrWV}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{BTKtGFF1Y-hvVJEGhBRo9 → O7xDYXO0r4zFhIzY1hrWV}/_ssgManifest.js +0 -0
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// gsd-pi + src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts - Handles provider and agent-end recovery for GSD auto-mode.
|
|
2
2
|
|
|
3
3
|
import type { ExtensionAPI, ExtensionContext } from "@gsd/pi-coding-agent";
|
|
4
|
+
import { mkdirSync, readdirSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
4
6
|
|
|
5
7
|
import type { AgentEndEvent, ErrorContext } from "../auto/types.js";
|
|
6
8
|
import { logWarning } from "../workflow-logger.js";
|
|
@@ -11,7 +13,7 @@ import {
|
|
|
11
13
|
maybeHandleEmptyIntentTurn,
|
|
12
14
|
resetEmptyTurnCounter,
|
|
13
15
|
} from "../guided-flow.js";
|
|
14
|
-
import { clearPathCache } from "../paths.js";
|
|
16
|
+
import { clearPathCache, gsdRoot } from "../paths.js";
|
|
15
17
|
import {
|
|
16
18
|
getAutoDashboardData,
|
|
17
19
|
getAutoModeStartModel,
|
|
@@ -34,7 +36,7 @@ import { resolveModelId } from "../auto-model-selection.js";
|
|
|
34
36
|
import { resolveProjectRoot } from "../worktree.js";
|
|
35
37
|
import { clearDiscussionFlowState } from "./write-gate.js";
|
|
36
38
|
import { scheduleFallbackContinuation } from "./fallback-continuation.js";
|
|
37
|
-
import { clearGuidedUnitContext } from "../guided-unit-context.js";
|
|
39
|
+
import { clearGuidedUnitContext, getGuidedUnitContext, type GuidedUnitContext } from "../guided-unit-context.js";
|
|
38
40
|
import { resumeAutoAfterProviderDelay } from "./provider-error-resume.js";
|
|
39
41
|
import {
|
|
40
42
|
classifyError,
|
|
@@ -116,9 +118,14 @@ export function isUserInitiatedAbortMessage(message: string | undefined | null):
|
|
|
116
118
|
export function shouldDeferTransientErrorToCoreRetry(
|
|
117
119
|
cls: ErrorClass,
|
|
118
120
|
rawErrorMsg: string,
|
|
121
|
+
deferCheckMsg: string = rawErrorMsg,
|
|
119
122
|
): boolean {
|
|
120
123
|
if (!isTransient(cls) || cls.kind === "rate-limit") return false;
|
|
121
|
-
|
|
124
|
+
// Empty rawErrorMsg means the SDK terminated the session without providing an
|
|
125
|
+
// error string — core is done, not mid-retry. GSD must schedule its own
|
|
126
|
+
// retry rather than silently deferring to a core that has already exited.
|
|
127
|
+
if (!rawErrorMsg) return false;
|
|
128
|
+
return !/retry failed after \d+ attempts:/i.test(deferCheckMsg);
|
|
122
129
|
}
|
|
123
130
|
|
|
124
131
|
type ProviderModelFallbackParams = {
|
|
@@ -388,6 +395,108 @@ export function suppressTerminalDeletedWorktreeMessageEnd(event: MessageEndLike)
|
|
|
388
395
|
return true;
|
|
389
396
|
}
|
|
390
397
|
|
|
398
|
+
function modelLabel(ctx: ExtensionContext): string {
|
|
399
|
+
const provider = ctx.model?.provider;
|
|
400
|
+
const id = ctx.model?.id;
|
|
401
|
+
return provider && id ? `${provider}/${id}` : "unknown model";
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function isFatalManualGuidedTerminalFailure(lastMsg: unknown): boolean {
|
|
405
|
+
if (!isObjectRecord(lastMsg) || !("stopReason" in lastMsg)) return false;
|
|
406
|
+
if (lastMsg.stopReason === "error") {
|
|
407
|
+
const rawErrorMsg = ("errorMessage" in lastMsg && lastMsg.errorMessage) ? String(lastMsg.errorMessage) : "";
|
|
408
|
+
if (isUserInitiatedAbortMessage(rawErrorMsg)) return false;
|
|
409
|
+
return true;
|
|
410
|
+
}
|
|
411
|
+
if (lastMsg.stopReason !== "aborted") return false;
|
|
412
|
+
|
|
413
|
+
const content = "content" in lastMsg ? lastMsg.content : undefined;
|
|
414
|
+
const hasErrorMessage = "errorMessage" in lastMsg && !!lastMsg.errorMessage;
|
|
415
|
+
return hasErrorMessage || !_hasEmptyAgentEndContent(content);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function terminalFailureDetail(lastMsg: unknown): string {
|
|
419
|
+
if (!isObjectRecord(lastMsg)) return "Provider turn ended with an unknown terminal error.";
|
|
420
|
+
const rawErrorMsg = ("errorMessage" in lastMsg && lastMsg.errorMessage) ? String(lastMsg.errorMessage) : "";
|
|
421
|
+
const content = "content" in lastMsg ? lastMsg.content : undefined;
|
|
422
|
+
const displayMsg = resolveAgentEndErrorDisplay(rawErrorMsg, content).replace(/\s+/g, " ").trim();
|
|
423
|
+
if (displayMsg) return displayMsg.length > 300 ? `${displayMsg.slice(0, 300)}...` : displayMsg;
|
|
424
|
+
return lastMsg.stopReason === "aborted"
|
|
425
|
+
? "Provider turn aborted with error context."
|
|
426
|
+
: "Provider stream ended with stopReason=error.";
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function nextManualActivitySequence(activityDir: string): string {
|
|
430
|
+
let maxSeq = 0;
|
|
431
|
+
try {
|
|
432
|
+
for (const file of readdirSync(activityDir)) {
|
|
433
|
+
const match = /^(\d+)-/.exec(file);
|
|
434
|
+
if (match) maxSeq = Math.max(maxSeq, Number.parseInt(match[1]!, 10));
|
|
435
|
+
}
|
|
436
|
+
} catch {
|
|
437
|
+
return "001";
|
|
438
|
+
}
|
|
439
|
+
return String(maxSeq + 1).padStart(3, "0");
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function writeManualGuidedTerminalErrorActivity(
|
|
443
|
+
basePath: string,
|
|
444
|
+
unitType: string,
|
|
445
|
+
model: string,
|
|
446
|
+
detail: string,
|
|
447
|
+
stopReason: unknown,
|
|
448
|
+
): void {
|
|
449
|
+
const activityDir = join(gsdRoot(basePath), "activity");
|
|
450
|
+
mkdirSync(activityDir, { recursive: true });
|
|
451
|
+
const seq = nextManualActivitySequence(activityDir);
|
|
452
|
+
const safeUnitType = unitType.replace(/[^a-z0-9_.-]+/gi, "-");
|
|
453
|
+
const markerPath = join(activityDir, `${seq}-${safeUnitType}-manual-terminal-provider-error.jsonl`);
|
|
454
|
+
const message = `Manual guided ${unitType} turn ended with provider ${String(stopReason)} on ${model}: ${detail}`;
|
|
455
|
+
writeFileSync(
|
|
456
|
+
markerPath,
|
|
457
|
+
JSON.stringify({
|
|
458
|
+
type: "message",
|
|
459
|
+
message: {
|
|
460
|
+
role: "toolResult",
|
|
461
|
+
toolCallId: "manual-guided-terminal-provider-error",
|
|
462
|
+
toolName: "provider",
|
|
463
|
+
isError: true,
|
|
464
|
+
content: [{ type: "text", text: message }],
|
|
465
|
+
},
|
|
466
|
+
}) + "\n",
|
|
467
|
+
"utf-8",
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function observeManualDiscussTerminalError(
|
|
472
|
+
ctx: ExtensionContext,
|
|
473
|
+
lastMsg: unknown,
|
|
474
|
+
guidedUnit: GuidedUnitContext | null,
|
|
475
|
+
): void {
|
|
476
|
+
if (!guidedUnit?.unitType.startsWith("discuss-")) return;
|
|
477
|
+
if (!isFatalManualGuidedTerminalFailure(lastMsg)) return;
|
|
478
|
+
|
|
479
|
+
const model = modelLabel(ctx);
|
|
480
|
+
const detail = terminalFailureDetail(lastMsg);
|
|
481
|
+
ctx.ui.notify(
|
|
482
|
+
`Manual /gsd discuss ${guidedUnit.unitType} ended with a provider error on ${model}: ${detail}`,
|
|
483
|
+
"warning",
|
|
484
|
+
);
|
|
485
|
+
|
|
486
|
+
try {
|
|
487
|
+
writeManualGuidedTerminalErrorActivity(
|
|
488
|
+
guidedUnit.basePath,
|
|
489
|
+
guidedUnit.unitType,
|
|
490
|
+
model,
|
|
491
|
+
detail,
|
|
492
|
+
isObjectRecord(lastMsg) ? lastMsg.stopReason : "unknown",
|
|
493
|
+
);
|
|
494
|
+
} catch (err) {
|
|
495
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
496
|
+
logWarning("bootstrap", `Failed to write manual guided terminal-error activity marker: ${message}`);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
391
500
|
async function pauseTransientWithBackoff(
|
|
392
501
|
cls: ErrorClass,
|
|
393
502
|
pi: ExtensionAPI,
|
|
@@ -437,7 +546,9 @@ export async function handleAgentEnd(
|
|
|
437
546
|
// rejected" loop even though the files are on disk.
|
|
438
547
|
clearPathCache();
|
|
439
548
|
const basePath = resolveAgentEndBasePath();
|
|
440
|
-
|
|
549
|
+
const lastMsg = event.messages[event.messages.length - 1];
|
|
550
|
+
const guidedUnit = basePath ? getGuidedUnitContext(basePath) ?? getGuidedUnitContext() : getGuidedUnitContext();
|
|
551
|
+
clearGuidedUnitContext(guidedUnit?.basePath ?? basePath);
|
|
441
552
|
|
|
442
553
|
try {
|
|
443
554
|
if (await checkDeepProjectSetupAfterTurn(event, ctx, basePath)) {
|
|
@@ -466,13 +577,15 @@ export async function handleAgentEnd(
|
|
|
466
577
|
// discussions (where isAutoActive may be false) still get recovered.
|
|
467
578
|
if (maybeHandleEmptyIntentTurn(event, isAutoActive(), basePath)) return;
|
|
468
579
|
|
|
469
|
-
if (!isAutoActive())
|
|
580
|
+
if (!isAutoActive()) {
|
|
581
|
+
observeManualDiscussTerminalError(ctx, lastMsg, guidedUnit);
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
470
584
|
|
|
471
585
|
if (shouldIgnoreAgentEndForActiveUnit(event)) {
|
|
472
586
|
return;
|
|
473
587
|
}
|
|
474
588
|
|
|
475
|
-
const lastMsg = event.messages[event.messages.length - 1];
|
|
476
589
|
if (isSessionSwitchInFlight()) {
|
|
477
590
|
_handleSessionSwitchAgentEnd(lastMsg, resolveAgentEndCancelled);
|
|
478
591
|
return;
|
|
@@ -555,9 +668,9 @@ export async function handleAgentEnd(
|
|
|
555
668
|
});
|
|
556
669
|
return;
|
|
557
670
|
}
|
|
558
|
-
// #3588: When errorMessage is uninformative, extract the real error
|
|
559
|
-
//
|
|
560
|
-
//
|
|
671
|
+
// #3588/#956: When errorMessage is uninformative, extract the real error
|
|
672
|
+
// from assistant text. Prefer rawErrorMsg for classification to avoid
|
|
673
|
+
// prose false-positives, but use display text when rawErrorMsg is empty.
|
|
561
674
|
const displayMsg = resolveAgentEndErrorDisplay(
|
|
562
675
|
rawErrorMsg,
|
|
563
676
|
"content" in lastMsg ? lastMsg.content : undefined,
|
|
@@ -576,8 +689,8 @@ export async function handleAgentEnd(
|
|
|
576
689
|
const errorDetail = displayMsg ? `: ${displayMsg}` : "";
|
|
577
690
|
const explicitRetryAfterMs = ("retryAfterMs" in lastMsg && typeof lastMsg.retryAfterMs === "number") ? lastMsg.retryAfterMs : undefined;
|
|
578
691
|
|
|
579
|
-
// ── 1. Classify
|
|
580
|
-
const cls = classifyError(rawErrorMsg, explicitRetryAfterMs);
|
|
692
|
+
// ── 1. Classify, preserving non-empty errorMessage precedence ──────
|
|
693
|
+
const cls = classifyError(rawErrorMsg || displayMsg, explicitRetryAfterMs);
|
|
581
694
|
|
|
582
695
|
// ── 1a. Unsupported-model: provider rejected this model for the current
|
|
583
696
|
// account/plan at request time (#4513). Persist a block so the
|
|
@@ -655,7 +768,7 @@ export async function handleAgentEnd(
|
|
|
655
768
|
// Core retries transient failures in-session after this handler.
|
|
656
769
|
// Keep that behavior for non-rate-limit classes to avoid pause/retry races,
|
|
657
770
|
// but let rate-limit continue into model fallback logic below (#4373).
|
|
658
|
-
if (shouldDeferTransientErrorToCoreRetry(cls, rawErrorMsg)) {
|
|
771
|
+
if (shouldDeferTransientErrorToCoreRetry(cls, rawErrorMsg, rawErrorMsg || displayMsg)) {
|
|
659
772
|
return;
|
|
660
773
|
}
|
|
661
774
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Project/App: gsd-pi
|
|
2
|
+
// File Purpose: Canonical core session tool surface shared by auto-mode scoping and loop guards.
|
|
3
|
+
|
|
4
|
+
/** Base tools available during auto-mode execution before unit-specific GSD tools are added. */
|
|
5
|
+
export const MINIMAL_AUTO_BASE_TOOL_NAMES = [
|
|
6
|
+
"ask_user_questions",
|
|
7
|
+
"bash",
|
|
8
|
+
"bg_shell",
|
|
9
|
+
"edit",
|
|
10
|
+
"find",
|
|
11
|
+
"glob",
|
|
12
|
+
"grep",
|
|
13
|
+
"fetch_page",
|
|
14
|
+
"search-the-web",
|
|
15
|
+
"ls",
|
|
16
|
+
"read",
|
|
17
|
+
"subagent",
|
|
18
|
+
"write",
|
|
19
|
+
"ToolSearch",
|
|
20
|
+
] as const;
|
|
21
|
+
|
|
22
|
+
/** Tools excluded from the high per-turn loop-guard cap (strict or orchestration). */
|
|
23
|
+
const NON_REPEATABLE_FROM_MINIMAL_BASE = new Set<string>([
|
|
24
|
+
"ask_user_questions",
|
|
25
|
+
"subagent",
|
|
26
|
+
"ToolSearch",
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
/** Additional core tools not in MINIMAL_AUTO_BASE but routinely multi-called per turn. */
|
|
30
|
+
const EXTRA_INHERENTLY_REPEATABLE = [
|
|
31
|
+
"multi_edit",
|
|
32
|
+
"todo_write",
|
|
33
|
+
"notebook_edit",
|
|
34
|
+
"search_and_read",
|
|
35
|
+
] as const;
|
|
36
|
+
|
|
37
|
+
/** Core session tools that may be invoked many times per agent turn. */
|
|
38
|
+
export const INHERENTLY_REPEATABLE_TOOL_NAMES = [
|
|
39
|
+
...MINIMAL_AUTO_BASE_TOOL_NAMES.filter((name) => !NON_REPEATABLE_FROM_MINIMAL_BASE.has(name)),
|
|
40
|
+
...EXTRA_INHERENTLY_REPEATABLE,
|
|
41
|
+
] as const;
|
|
42
|
+
|
|
43
|
+
export const INHERENTLY_REPEATABLE_TOOL_SET = new Set<string>(INHERENTLY_REPEATABLE_TOOL_NAMES);
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { Type, StringEnum } from "@gsd/pi-ai";
|
|
4
4
|
import type { ExtensionAPI } from "@gsd/pi-coding-agent";
|
|
5
5
|
import { Text } from "@gsd/pi-tui";
|
|
6
|
+
import { SUMMARY_SAVE_CONTENT_MAX_LENGTH } from "@opengsd/contracts";
|
|
6
7
|
|
|
7
8
|
import { loadEffectiveGSDPreferences } from "../preferences.js";
|
|
8
9
|
import { ensureDbOpen, resolveCtxCwd, resolveWorkflowToolBasePath } from "./dynamic-tools.js";
|
|
@@ -392,13 +393,17 @@ export function registerDbTools(pi: ExtensionAPI): void {
|
|
|
392
393
|
"Root-level artifact paths are PROJECT.md, PROJECT-DRAFT.md, REQUIREMENTS.md, and REQUIREMENTS-DRAFT.md.",
|
|
393
394
|
"artifact_type must be one of: SUMMARY, RESEARCH, CONTEXT, ASSESSMENT, CONTEXT-DRAFT, PROJECT, PROJECT-DRAFT, REQUIREMENTS, REQUIREMENTS-DRAFT.",
|
|
394
395
|
"Use CONTEXT-DRAFT for incremental draft persistence; use CONTEXT for the final milestone context after depth verification.",
|
|
396
|
+
`Keep each content payload under ${SUMMARY_SAVE_CONTENT_MAX_LENGTH} characters; save large context incrementally with CONTEXT-DRAFT/PROJECT-DRAFT/REQUIREMENTS-DRAFT instead of one oversized call.`,
|
|
395
397
|
],
|
|
396
398
|
parameters: Type.Object({
|
|
397
399
|
milestone_id: Type.Optional(Type.String({ description: "Milestone ID (e.g. M001). Omit only for root-level PROJECT/PROJECT-DRAFT/REQUIREMENTS/REQUIREMENTS-DRAFT artifacts." })),
|
|
398
400
|
slice_id: Type.Optional(Type.String({ description: "Slice ID (e.g. S01)" })),
|
|
399
401
|
task_id: Type.Optional(Type.String({ description: "Task ID (e.g. T01)" })),
|
|
400
402
|
artifact_type: StringEnum(["SUMMARY", "RESEARCH", "CONTEXT", "ASSESSMENT", "CONTEXT-DRAFT", "PROJECT", "PROJECT-DRAFT", "REQUIREMENTS", "REQUIREMENTS-DRAFT"], { description: "Artifact type to save" }),
|
|
401
|
-
content: Type.String({
|
|
403
|
+
content: Type.String({
|
|
404
|
+
description: `The full markdown content of the artifact. Maximum ${SUMMARY_SAVE_CONTENT_MAX_LENGTH} characters per save.`,
|
|
405
|
+
maxLength: SUMMARY_SAVE_CONTENT_MAX_LENGTH,
|
|
406
|
+
}),
|
|
402
407
|
}),
|
|
403
408
|
execute: summarySaveExecute,
|
|
404
409
|
renderCall(args: any, theme: any) {
|
|
@@ -72,6 +72,7 @@ export function registerExecTools(pi: ExtensionAPI): void {
|
|
|
72
72
|
return executeUatExec(params as Parameters<typeof executeUatExec>[0], {
|
|
73
73
|
baseDir,
|
|
74
74
|
preferences: await loadContextModePreferences(baseDir),
|
|
75
|
+
signal: _signal,
|
|
75
76
|
});
|
|
76
77
|
},
|
|
77
78
|
});
|
|
@@ -119,6 +120,7 @@ export function registerExecTools(pi: ExtensionAPI): void {
|
|
|
119
120
|
return executeGsdExec(params as Parameters<typeof executeGsdExec>[0], {
|
|
120
121
|
baseDir,
|
|
121
122
|
preferences: await loadContextModePreferences(baseDir),
|
|
123
|
+
signal: _signal,
|
|
122
124
|
});
|
|
123
125
|
},
|
|
124
126
|
});
|
|
@@ -13,7 +13,7 @@ import type { GSDEcosystemBeforeAgentStartHandler } from "../ecosystem/gsd-exten
|
|
|
13
13
|
import { updateSnapshot } from "../ecosystem/gsd-extension-api.js";
|
|
14
14
|
|
|
15
15
|
import { buildMilestoneFileName, canonicalPhaseDirName, clearPathCache, milestonesDir, legacyMilestonesDir, resolveMilestonePath, resolveSliceFile, resolveSlicePath } from "../paths.js";
|
|
16
|
-
import { applyAskUserQuestionsGateResult, clearDiscussionFlowState, formatPendingAskUserQuestionsGateMessage, formatTimedOutAskUserQuestionsGateMessage, hostWriteGateAdapter, isApprovalGateVerifiedInSnapshot, isDepthConfirmationAnswer,
|
|
16
|
+
import { applyAskUserQuestionsGateResult, clearDiscussionFlowState, currentWriteGateSnapshot, formatPendingAskUserQuestionsGateMessage, formatTimedOutAskUserQuestionsGateMessage, hostWriteGateAdapter, isApprovalGateVerifiedInSnapshot, isDepthConfirmationAnswer, isMilestoneDepthVerifiedInSnapshot, isQueuePhaseActive, resetWriteGateState, shouldBlockContextWrite, shouldBlockPlanningUnit, shouldBlockQueueExecution, shouldBlockWorktreeBash, shouldBlockWorktreeWrite, isGateQuestionId, getPendingGate, shouldBlockPendingGate, shouldBlockPendingGateBash, extractDepthVerificationMilestoneId, type WriteGateSnapshot } from "./write-gate.js";
|
|
17
17
|
import { canonicalToolName } from "../engine-hook-contract.js";
|
|
18
18
|
import { resolveManifest } from "../unit-context-manifest.js";
|
|
19
19
|
import { isBlockedStateFile, isBashWriteToStateFile, BLOCKED_WRITE_ERROR } from "../write-intercept.js";
|
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
import { applyProviderPayloadPolicy } from "../provider-payload-policy.js";
|
|
36
36
|
|
|
37
37
|
import { checkToolCallLoop, resetToolCallLoopGuard } from "./tool-call-loop-guard.js";
|
|
38
|
+
import { MINIMAL_AUTO_BASE_TOOL_NAMES } from "./core-session-tools.js";
|
|
38
39
|
import { maybePauseAutoForApprovalGate, resetPendingGatePauseGuard } from "./pending-gate-pause.js";
|
|
39
40
|
import { saveActivityLog } from "../activity-log.js";
|
|
40
41
|
import { recordToolCall as safetyRecordToolCall, recordToolResult as safetyRecordToolResult, saveEvidenceToDisk } from "../safety/evidence-collector.js";
|
|
@@ -178,22 +179,7 @@ export const MINIMAL_GSD_TOOL_NAMES = [
|
|
|
178
179
|
"gsd_capture_thought",
|
|
179
180
|
] as const;
|
|
180
181
|
|
|
181
|
-
export
|
|
182
|
-
"ask_user_questions",
|
|
183
|
-
"bash",
|
|
184
|
-
"bg_shell",
|
|
185
|
-
"edit",
|
|
186
|
-
"find",
|
|
187
|
-
"glob",
|
|
188
|
-
"grep",
|
|
189
|
-
"fetch_page",
|
|
190
|
-
"search-the-web",
|
|
191
|
-
"ls",
|
|
192
|
-
"read",
|
|
193
|
-
"subagent",
|
|
194
|
-
"write",
|
|
195
|
-
"ToolSearch",
|
|
196
|
-
] as const;
|
|
182
|
+
export { MINIMAL_AUTO_BASE_TOOL_NAMES } from "./core-session-tools.js";
|
|
197
183
|
|
|
198
184
|
function withPreservedShimTools(toolNames: readonly string[]): string[] {
|
|
199
185
|
return [...new Set([...toolNames, ...ALWAYS_PRESERVED_SHIM_TOOL_NAMES])];
|
|
@@ -583,6 +569,10 @@ function deferApprovalGate(gateId: string, basePath: string): void {
|
|
|
583
569
|
// workflow MCP child already verified this gate, deferring would block
|
|
584
570
|
// tools for a gate that can never legitimately arm.
|
|
585
571
|
const snapshot = hostWriteGateAdapter.readState(basePath);
|
|
572
|
+
deferApprovalGateFromSnapshot(gateId, basePath, snapshot);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function deferApprovalGateFromSnapshot(gateId: string, basePath: string, snapshot: WriteGateSnapshot): void {
|
|
586
576
|
if (isApprovalGateVerifiedInSnapshot(snapshot, gateId)) return;
|
|
587
577
|
const milestoneId = extractDepthVerificationMilestoneId(gateId);
|
|
588
578
|
if (milestoneId && isMilestoneDepthVerifiedInSnapshot(snapshot, milestoneId)) return;
|
|
@@ -1233,13 +1223,15 @@ export function registerHooks(
|
|
|
1233
1223
|
|
|
1234
1224
|
const gateId = approvalGateIdForUnit(unitType, unitId);
|
|
1235
1225
|
if (gateId) {
|
|
1226
|
+
const basePath = contextBasePath(ctx);
|
|
1227
|
+
const gateSnapshot = currentWriteGateSnapshot(basePath);
|
|
1236
1228
|
// Skip the gate if this milestone is already depth-verified — the approval
|
|
1237
1229
|
// pattern matched again on post-verification text (a false-positive re-trigger).
|
|
1238
1230
|
// Without this guard, the second firing blocks gsd_plan_milestone in the same
|
|
1239
1231
|
// turn and leaves CONTEXT.md on disk with no DB row (#discuss-milestone-no-db).
|
|
1240
1232
|
const gateMilestoneId = extractDepthVerificationMilestoneId(gateId);
|
|
1241
|
-
if (gateMilestoneId &&
|
|
1242
|
-
|
|
1233
|
+
if (gateMilestoneId && isMilestoneDepthVerifiedInSnapshot(gateSnapshot, gateMilestoneId)) return;
|
|
1234
|
+
deferApprovalGateFromSnapshot(gateId, basePath, gateSnapshot);
|
|
1243
1235
|
}
|
|
1244
1236
|
|
|
1245
1237
|
approvalQuestionAbortInFlight = true;
|
|
@@ -28,9 +28,10 @@ const DEFAULT_CODEBASE_MAX_CHARS = 8_000;
|
|
|
28
28
|
const MIN_CONTEXT_MESSAGE_MAX_CHARS = 1_000;
|
|
29
29
|
const MIN_KNOWLEDGE_MAX_CHARS = 1_000;
|
|
30
30
|
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
const
|
|
31
|
+
const CONTEXT_MAINTENANCE_KEY_SEPARATOR = "\0";
|
|
32
|
+
const contextMaintenanceCompletedForSession = new Set<string>();
|
|
33
|
+
const contextMaintenanceInFlightBySession = new Map<string, Promise<boolean>>();
|
|
34
|
+
const deferredContextMaintenanceBySession = new Map<string, Promise<void>>();
|
|
34
35
|
|
|
35
36
|
/**
|
|
36
37
|
* Bundled skill triggers — resolved dynamically at runtime instead of
|
|
@@ -114,7 +115,8 @@ async function runSessionStartupMaintenanceOnce(
|
|
|
114
115
|
basePath: string,
|
|
115
116
|
ctx: ExtensionContext,
|
|
116
117
|
): Promise<boolean> {
|
|
117
|
-
|
|
118
|
+
const maintenanceKey = getContextMaintenanceKey(basePath, ctx);
|
|
119
|
+
if (contextMaintenanceCompletedForSession.has(maintenanceKey)) {
|
|
118
120
|
// Backfills are session-once, but memory queries and other DB-backed
|
|
119
121
|
// prompt assembly still need an active adapter on every turn.
|
|
120
122
|
try {
|
|
@@ -126,16 +128,16 @@ async function runSessionStartupMaintenanceOnce(
|
|
|
126
128
|
return false;
|
|
127
129
|
}
|
|
128
130
|
|
|
129
|
-
const existing =
|
|
131
|
+
const existing = contextMaintenanceInFlightBySession.get(maintenanceKey);
|
|
130
132
|
const isInitiator = !existing;
|
|
131
133
|
// Use a definite Promise<boolean> so `await inFlight` has a known return type.
|
|
132
134
|
let inFlight: Promise<boolean>;
|
|
133
135
|
if (isInitiator) {
|
|
134
|
-
inFlight = performSessionStartupMaintenance(basePath, ctx);
|
|
135
|
-
|
|
136
|
+
inFlight = performSessionStartupMaintenance(basePath, ctx, maintenanceKey);
|
|
137
|
+
contextMaintenanceInFlightBySession.set(maintenanceKey, inFlight);
|
|
136
138
|
void inFlight.finally(() => {
|
|
137
|
-
if (
|
|
138
|
-
|
|
139
|
+
if (contextMaintenanceInFlightBySession.get(maintenanceKey) === inFlight) {
|
|
140
|
+
contextMaintenanceInFlightBySession.delete(maintenanceKey);
|
|
139
141
|
}
|
|
140
142
|
});
|
|
141
143
|
} else {
|
|
@@ -149,6 +151,7 @@ async function runSessionStartupMaintenanceOnce(
|
|
|
149
151
|
async function performSessionStartupMaintenance(
|
|
150
152
|
basePath: string,
|
|
151
153
|
ctx: ExtensionContext,
|
|
154
|
+
maintenanceKey: string,
|
|
152
155
|
): Promise<boolean> {
|
|
153
156
|
// DB-backed memory backfills run below. On a cold session the database file
|
|
154
157
|
// may exist without an active in-process adapter, so open the canonical
|
|
@@ -172,11 +175,40 @@ async function performSessionStartupMaintenance(
|
|
|
172
175
|
|
|
173
176
|
// Mark session complete before scheduling deferred work so any concurrent
|
|
174
177
|
// caller that observes the completed state does not re-enter maintenance.
|
|
175
|
-
|
|
176
|
-
scheduleDeferredContextMaintenance(basePath);
|
|
178
|
+
contextMaintenanceCompletedForSession.add(maintenanceKey);
|
|
179
|
+
scheduleDeferredContextMaintenance(basePath, maintenanceKey);
|
|
177
180
|
return true;
|
|
178
181
|
}
|
|
179
182
|
|
|
183
|
+
function getContextMaintenanceKey(basePath: string, ctx: ExtensionContext): string {
|
|
184
|
+
return `${basePath}${CONTEXT_MAINTENANCE_KEY_SEPARATOR}${getContextSessionPart(ctx)}`;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function getContextSessionPart(ctx: ExtensionContext): string {
|
|
188
|
+
const sessionManager = (ctx as {
|
|
189
|
+
sessionManager?: {
|
|
190
|
+
getSessionId?: () => string | undefined;
|
|
191
|
+
getSessionFile?: () => string | undefined;
|
|
192
|
+
};
|
|
193
|
+
}).sessionManager;
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
const sessionId = sessionManager?.getSessionId?.();
|
|
197
|
+
if (typeof sessionId === "string" && sessionId.length > 0) return `id:${sessionId}`;
|
|
198
|
+
} catch (e) {
|
|
199
|
+
logWarning("bootstrap", `session-id fetch failed: ${(e as Error).message}`);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
const sessionFile = sessionManager?.getSessionFile?.();
|
|
204
|
+
if (typeof sessionFile === "string" && sessionFile.length > 0) return `file:${sessionFile}`;
|
|
205
|
+
} catch (e) {
|
|
206
|
+
logWarning("bootstrap", `session-file fetch failed: ${(e as Error).message}`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return "process";
|
|
210
|
+
}
|
|
211
|
+
|
|
180
212
|
async function runDecisionsMemoryBackfill(ctx: ExtensionContext): Promise<void> {
|
|
181
213
|
// ADR-013 step 5: opportunistic decisions->memories backfill. Idempotent
|
|
182
214
|
// and best-effort — first run absorbs the existing decisions table into
|
|
@@ -210,8 +242,8 @@ async function runKnowledgeMemoryBackfill(
|
|
|
210
242
|
}
|
|
211
243
|
}
|
|
212
244
|
|
|
213
|
-
function scheduleDeferredContextMaintenance(basePath: string): void {
|
|
214
|
-
if (
|
|
245
|
+
function scheduleDeferredContextMaintenance(basePath: string, maintenanceKey: string): void {
|
|
246
|
+
if (deferredContextMaintenanceBySession.has(maintenanceKey)) return;
|
|
215
247
|
|
|
216
248
|
const task = new Promise<void>((resolve) => {
|
|
217
249
|
setTimeout(() => {
|
|
@@ -219,10 +251,10 @@ function scheduleDeferredContextMaintenance(basePath: string): void {
|
|
|
219
251
|
}, 0);
|
|
220
252
|
});
|
|
221
253
|
|
|
222
|
-
|
|
254
|
+
deferredContextMaintenanceBySession.set(maintenanceKey, task);
|
|
223
255
|
void task.finally(() => {
|
|
224
|
-
if (
|
|
225
|
-
|
|
256
|
+
if (deferredContextMaintenanceBySession.get(maintenanceKey) === task) {
|
|
257
|
+
deferredContextMaintenanceBySession.delete(maintenanceKey);
|
|
226
258
|
}
|
|
227
259
|
});
|
|
228
260
|
}
|
|
@@ -257,8 +289,10 @@ async function reportConsolidationGapsDeferred(basePath: string): Promise<void>
|
|
|
257
289
|
|
|
258
290
|
export async function _flushDeferredContextMaintenanceForTest(basePath?: string): Promise<void> {
|
|
259
291
|
const tasks = basePath
|
|
260
|
-
? [
|
|
261
|
-
|
|
292
|
+
? [...deferredContextMaintenanceBySession.entries()]
|
|
293
|
+
.filter(([key]) => key.startsWith(`${basePath}${CONTEXT_MAINTENANCE_KEY_SEPARATOR}`))
|
|
294
|
+
.map(([, task]) => task)
|
|
295
|
+
: [...deferredContextMaintenanceBySession.values()];
|
|
262
296
|
await Promise.allSettled(tasks);
|
|
263
297
|
}
|
|
264
298
|
|
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tool-call loop guard.
|
|
3
3
|
*
|
|
4
|
-
* Detects when a model calls
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Detects when a model repeats tool calls within a single Agent Turn.
|
|
5
|
+
* Works in both auto-mode and interactive sessions by hooking into the
|
|
6
|
+
* native engine's `tool_call` event, which fires before execution and can
|
|
7
|
+
* block the call.
|
|
8
8
|
*
|
|
9
|
-
* The guard
|
|
10
|
-
* and
|
|
11
|
-
*
|
|
12
|
-
* and
|
|
9
|
+
* The guard has two independent checks: a sliding window for identical
|
|
10
|
+
* tool signatures, and a per-tool-name cap for repeated calls with varied
|
|
11
|
+
* arguments. State resets at Agent Turn boundaries (session_start,
|
|
12
|
+
* agent_end) and the identical-signature streak also resets when a
|
|
13
|
+
* different tool call breaks the streak. Block messages instruct the model
|
|
14
|
+
* to stop tooling for the rest of that turn and answer in text.
|
|
15
|
+
*
|
|
16
|
+
* The per-tool-name check (#783 Brief C) tracks call counts within a
|
|
17
|
+
* turn regardless of args. This catches improvisation
|
|
18
|
+
* loops where the model attempts the same missing workflow tool through
|
|
19
|
+
* varied surfaces (bash → `node -e` → CLI), each with a different
|
|
20
|
+
* signature, so the identical-args streak never trips. Whichever guard
|
|
21
|
+
* trips first blocks.
|
|
13
22
|
*/
|
|
14
23
|
|
|
15
24
|
import { createHash } from "node:crypto";
|
|
25
|
+
import { INHERENTLY_REPEATABLE_TOOL_SET } from "./core-session-tools.js";
|
|
16
26
|
|
|
17
27
|
const MAX_CONSECUTIVE_IDENTICAL_CALLS = 4;
|
|
18
28
|
|
|
@@ -20,11 +30,27 @@ const MAX_CONSECUTIVE_IDENTICAL_CALLS = 4;
|
|
|
20
30
|
const STRICT_LOOP_TOOLS = new Set(["ask_user_questions"]);
|
|
21
31
|
const MAX_CONSECUTIVE_STRICT = 1;
|
|
22
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Per-turn cap on calls to the SAME tool name, regardless of args (#783).
|
|
35
|
+
*
|
|
36
|
+
* General-purpose execution tools are routinely called many times per turn
|
|
37
|
+
* (touching multiple files, running several commands), so they get a higher
|
|
38
|
+
* ceiling. Everything else — workflow one-shot tools (e.g. gsd_complete_milestone)
|
|
39
|
+
* and any non-allowlisted tool — gets the default cap. The default is generous
|
|
40
|
+
* enough to absorb legitimate retries but catches the reported improvisation
|
|
41
|
+
* loop (~51 calls) well before a cost spike.
|
|
42
|
+
*/
|
|
43
|
+
const PER_TOOL_DEFAULT_CAP = 6;
|
|
44
|
+
const PER_TOOL_REPEATABLE_CAP = 15;
|
|
45
|
+
|
|
23
46
|
let consecutiveCount = 0;
|
|
24
47
|
let lastSignature = "";
|
|
25
48
|
let lastToolName = "";
|
|
26
49
|
let enabled = true;
|
|
27
50
|
|
|
51
|
+
/** Per-tool-name call counts within the current turn (#783 Brief C). */
|
|
52
|
+
const perToolCounts = new Map<string, number>();
|
|
53
|
+
|
|
28
54
|
/** Hash tool name + args into a compact signature for comparison. */
|
|
29
55
|
function hashToolCall(toolName: string, args: Record<string, unknown>): string {
|
|
30
56
|
const h = createHash("sha256");
|
|
@@ -46,6 +72,12 @@ function hashToolCall(toolName: string, args: Record<string, unknown>): string {
|
|
|
46
72
|
*
|
|
47
73
|
* Returns `{ block: false }` for allowed calls.
|
|
48
74
|
* Returns `{ block: true, reason }` when the loop threshold is exceeded.
|
|
75
|
+
*
|
|
76
|
+
* Two independent guards run; whichever trips first blocks:
|
|
77
|
+
* 1. Identical-signature streak (MAX_CONSECUTIVE_IDENTICAL_CALLS, strict for
|
|
78
|
+
* ask_user_questions).
|
|
79
|
+
* 2. Per-tool-name cap (PER_TOOL_DEFAULT_CAP / PER_TOOL_REPEATABLE_CAP),
|
|
80
|
+
* independent of args — catches improvisation loops (#783).
|
|
49
81
|
*/
|
|
50
82
|
export function checkToolCallLoop(
|
|
51
83
|
toolName: string,
|
|
@@ -63,6 +95,7 @@ export function checkToolCallLoop(
|
|
|
63
95
|
lastToolName = toolName;
|
|
64
96
|
}
|
|
65
97
|
|
|
98
|
+
// ── Guard 1: identical-signature streak ──
|
|
66
99
|
const threshold = STRICT_LOOP_TOOLS.has(toolName)
|
|
67
100
|
? MAX_CONSECUTIVE_STRICT
|
|
68
101
|
: MAX_CONSECUTIVE_IDENTICAL_CALLS;
|
|
@@ -71,13 +104,34 @@ export function checkToolCallLoop(
|
|
|
71
104
|
return {
|
|
72
105
|
block: true,
|
|
73
106
|
reason:
|
|
74
|
-
`Tool loop detected: ${toolName} called ${consecutiveCount} times ` +
|
|
107
|
+
`Tool loop detected (identical args): ${toolName} called ${consecutiveCount} times ` +
|
|
75
108
|
`with identical arguments. Blocking to prevent infinite loop. ` +
|
|
76
|
-
`
|
|
109
|
+
`Do not retry this tool or call other tools this turn — stop and respond to the user in text.`,
|
|
77
110
|
count: consecutiveCount,
|
|
78
111
|
};
|
|
79
112
|
}
|
|
80
113
|
|
|
114
|
+
// ── Guard 2: per-tool-name cap, independent of args (#783 Brief C) ──
|
|
115
|
+
// Catches improvisation loops where the same tool is invoked many times with
|
|
116
|
+
// varied args (e.g. retrying a missing workflow tool via bash/node -e/CLI).
|
|
117
|
+
const perToolCount = (perToolCounts.get(toolName) ?? 0) + 1;
|
|
118
|
+
perToolCounts.set(toolName, perToolCount);
|
|
119
|
+
const perToolCap = INHERENTLY_REPEATABLE_TOOL_SET.has(toolName)
|
|
120
|
+
? PER_TOOL_REPEATABLE_CAP
|
|
121
|
+
: PER_TOOL_DEFAULT_CAP;
|
|
122
|
+
|
|
123
|
+
if (perToolCount > perToolCap) {
|
|
124
|
+
return {
|
|
125
|
+
block: true,
|
|
126
|
+
reason:
|
|
127
|
+
`Tool loop detected (repeated tool): ${toolName} called ${perToolCount} times ` +
|
|
128
|
+
`this turn (cap ${perToolCap}). Blocking to prevent infinite loop. ` +
|
|
129
|
+
`The tool may be unavailable or failing repeatedly. ` +
|
|
130
|
+
`Do not retry this tool or pivot to other tools this turn — stop and respond to the user in text.`,
|
|
131
|
+
count: perToolCount,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
81
135
|
return { block: false, count: consecutiveCount };
|
|
82
136
|
}
|
|
83
137
|
|
|
@@ -87,6 +141,7 @@ export function resetToolCallLoopGuard(): void {
|
|
|
87
141
|
lastSignature = "";
|
|
88
142
|
lastToolName = "";
|
|
89
143
|
enabled = true;
|
|
144
|
+
perToolCounts.clear();
|
|
90
145
|
}
|
|
91
146
|
|
|
92
147
|
/** Disable the guard (e.g. during shutdown). */
|
|
@@ -95,9 +150,18 @@ export function disableToolCallLoopGuard(): void {
|
|
|
95
150
|
consecutiveCount = 0;
|
|
96
151
|
lastSignature = "";
|
|
97
152
|
lastToolName = "";
|
|
153
|
+
perToolCounts.clear();
|
|
98
154
|
}
|
|
99
155
|
|
|
100
156
|
/** Get current consecutive count for diagnostics. */
|
|
101
157
|
export function getToolCallLoopCount(): number {
|
|
102
158
|
return consecutiveCount;
|
|
103
159
|
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Get the per-tool-name call count for the current turn (#783 Brief C).
|
|
163
|
+
* Returns 0 for tools not yet called. Diagnostic only.
|
|
164
|
+
*/
|
|
165
|
+
export function getToolCallCountForTool(toolName: string): number {
|
|
166
|
+
return perToolCounts.get(toolName) ?? 0;
|
|
167
|
+
}
|
|
@@ -180,7 +180,7 @@ function ensureWriteGateSnapshotDirectory(basePath: string): void {
|
|
|
180
180
|
mkdirSync(join(gsdPath, "runtime"), { recursive: true });
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
-
function currentWriteGateSnapshot(basePath: string = process.cwd()): WriteGateSnapshot {
|
|
183
|
+
export function currentWriteGateSnapshot(basePath: string = process.cwd()): WriteGateSnapshot {
|
|
184
184
|
const state = getWriteGateState(basePath);
|
|
185
185
|
return {
|
|
186
186
|
verifiedDepthMilestones: [...state.verifiedDepthMilestones].sort(),
|