@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,7 +1,9 @@
|
|
|
1
1
|
// gsd-pi + src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts - Handles provider and agent-end recovery for GSD auto-mode.
|
|
2
|
+
import { mkdirSync, readdirSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
2
4
|
import { logWarning } from "../workflow-logger.js";
|
|
3
5
|
import { checkDeepProjectSetupAfterTurn, checkAutoStartAfterDiscuss, maybeHandleReadyPhraseWithoutFiles, maybeHandleEmptyIntentTurn, resetEmptyTurnCounter, } from "../guided-flow.js";
|
|
4
|
-
import { clearPathCache } from "../paths.js";
|
|
6
|
+
import { clearPathCache, gsdRoot } from "../paths.js";
|
|
5
7
|
import { getAutoDashboardData, getAutoModeStartModel, isAutoActive, isAutoCompletionStopInProgress, pauseAuto, setCurrentDispatchedModelId, setCurrentUnitModelForRecovery, } from "../auto.js";
|
|
6
8
|
import { getNextFallbackModel, resolveModelWithFallbacksForUnit } from "../preferences.js";
|
|
7
9
|
import { pauseAutoForProviderError } from "../provider-error-pause.js";
|
|
@@ -11,7 +13,7 @@ import { resolveModelId } from "../auto-model-selection.js";
|
|
|
11
13
|
import { resolveProjectRoot } from "../worktree.js";
|
|
12
14
|
import { clearDiscussionFlowState } from "./write-gate.js";
|
|
13
15
|
import { scheduleFallbackContinuation } from "./fallback-continuation.js";
|
|
14
|
-
import { clearGuidedUnitContext } from "../guided-unit-context.js";
|
|
16
|
+
import { clearGuidedUnitContext, getGuidedUnitContext } from "../guided-unit-context.js";
|
|
15
17
|
import { resumeAutoAfterProviderDelay } from "./provider-error-resume.js";
|
|
16
18
|
import { classifyError, createRetryState, resetRetryState, isTransient, } from "../error-classifier.js";
|
|
17
19
|
import { blockModel, blockModelUntil, isModelBlocked, isModelTemporarilyUnavailable } from "../blocked-models.js";
|
|
@@ -78,10 +80,15 @@ export function isUserInitiatedAbortMessage(message) {
|
|
|
78
80
|
return false;
|
|
79
81
|
return /\b(?:claude code process aborted by user|request aborted by user|process aborted by user)\b/i.test(message);
|
|
80
82
|
}
|
|
81
|
-
export function shouldDeferTransientErrorToCoreRetry(cls, rawErrorMsg) {
|
|
83
|
+
export function shouldDeferTransientErrorToCoreRetry(cls, rawErrorMsg, deferCheckMsg = rawErrorMsg) {
|
|
82
84
|
if (!isTransient(cls) || cls.kind === "rate-limit")
|
|
83
85
|
return false;
|
|
84
|
-
|
|
86
|
+
// Empty rawErrorMsg means the SDK terminated the session without providing an
|
|
87
|
+
// error string — core is done, not mid-retry. GSD must schedule its own
|
|
88
|
+
// retry rather than silently deferring to a core that has already exited.
|
|
89
|
+
if (!rawErrorMsg)
|
|
90
|
+
return false;
|
|
91
|
+
return !/retry failed after \d+ attempts:/i.test(deferCheckMsg);
|
|
85
92
|
}
|
|
86
93
|
/** Try configured fallbacks, then the auto-mode start model. Returns true if switched. */
|
|
87
94
|
async function tryProviderModelFallback(params) {
|
|
@@ -277,6 +284,86 @@ export function suppressTerminalDeletedWorktreeMessageEnd(event) {
|
|
|
277
284
|
logWarning("bootstrap", `Suppressing stale deleted-worktree provider error during terminal completion reroot: ${displayMsg || rawErrorMsg}`);
|
|
278
285
|
return true;
|
|
279
286
|
}
|
|
287
|
+
function modelLabel(ctx) {
|
|
288
|
+
const provider = ctx.model?.provider;
|
|
289
|
+
const id = ctx.model?.id;
|
|
290
|
+
return provider && id ? `${provider}/${id}` : "unknown model";
|
|
291
|
+
}
|
|
292
|
+
function isFatalManualGuidedTerminalFailure(lastMsg) {
|
|
293
|
+
if (!isObjectRecord(lastMsg) || !("stopReason" in lastMsg))
|
|
294
|
+
return false;
|
|
295
|
+
if (lastMsg.stopReason === "error") {
|
|
296
|
+
const rawErrorMsg = ("errorMessage" in lastMsg && lastMsg.errorMessage) ? String(lastMsg.errorMessage) : "";
|
|
297
|
+
if (isUserInitiatedAbortMessage(rawErrorMsg))
|
|
298
|
+
return false;
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
if (lastMsg.stopReason !== "aborted")
|
|
302
|
+
return false;
|
|
303
|
+
const content = "content" in lastMsg ? lastMsg.content : undefined;
|
|
304
|
+
const hasErrorMessage = "errorMessage" in lastMsg && !!lastMsg.errorMessage;
|
|
305
|
+
return hasErrorMessage || !_hasEmptyAgentEndContent(content);
|
|
306
|
+
}
|
|
307
|
+
function terminalFailureDetail(lastMsg) {
|
|
308
|
+
if (!isObjectRecord(lastMsg))
|
|
309
|
+
return "Provider turn ended with an unknown terminal error.";
|
|
310
|
+
const rawErrorMsg = ("errorMessage" in lastMsg && lastMsg.errorMessage) ? String(lastMsg.errorMessage) : "";
|
|
311
|
+
const content = "content" in lastMsg ? lastMsg.content : undefined;
|
|
312
|
+
const displayMsg = resolveAgentEndErrorDisplay(rawErrorMsg, content).replace(/\s+/g, " ").trim();
|
|
313
|
+
if (displayMsg)
|
|
314
|
+
return displayMsg.length > 300 ? `${displayMsg.slice(0, 300)}...` : displayMsg;
|
|
315
|
+
return lastMsg.stopReason === "aborted"
|
|
316
|
+
? "Provider turn aborted with error context."
|
|
317
|
+
: "Provider stream ended with stopReason=error.";
|
|
318
|
+
}
|
|
319
|
+
function nextManualActivitySequence(activityDir) {
|
|
320
|
+
let maxSeq = 0;
|
|
321
|
+
try {
|
|
322
|
+
for (const file of readdirSync(activityDir)) {
|
|
323
|
+
const match = /^(\d+)-/.exec(file);
|
|
324
|
+
if (match)
|
|
325
|
+
maxSeq = Math.max(maxSeq, Number.parseInt(match[1], 10));
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
catch {
|
|
329
|
+
return "001";
|
|
330
|
+
}
|
|
331
|
+
return String(maxSeq + 1).padStart(3, "0");
|
|
332
|
+
}
|
|
333
|
+
function writeManualGuidedTerminalErrorActivity(basePath, unitType, model, detail, stopReason) {
|
|
334
|
+
const activityDir = join(gsdRoot(basePath), "activity");
|
|
335
|
+
mkdirSync(activityDir, { recursive: true });
|
|
336
|
+
const seq = nextManualActivitySequence(activityDir);
|
|
337
|
+
const safeUnitType = unitType.replace(/[^a-z0-9_.-]+/gi, "-");
|
|
338
|
+
const markerPath = join(activityDir, `${seq}-${safeUnitType}-manual-terminal-provider-error.jsonl`);
|
|
339
|
+
const message = `Manual guided ${unitType} turn ended with provider ${String(stopReason)} on ${model}: ${detail}`;
|
|
340
|
+
writeFileSync(markerPath, JSON.stringify({
|
|
341
|
+
type: "message",
|
|
342
|
+
message: {
|
|
343
|
+
role: "toolResult",
|
|
344
|
+
toolCallId: "manual-guided-terminal-provider-error",
|
|
345
|
+
toolName: "provider",
|
|
346
|
+
isError: true,
|
|
347
|
+
content: [{ type: "text", text: message }],
|
|
348
|
+
},
|
|
349
|
+
}) + "\n", "utf-8");
|
|
350
|
+
}
|
|
351
|
+
function observeManualDiscussTerminalError(ctx, lastMsg, guidedUnit) {
|
|
352
|
+
if (!guidedUnit?.unitType.startsWith("discuss-"))
|
|
353
|
+
return;
|
|
354
|
+
if (!isFatalManualGuidedTerminalFailure(lastMsg))
|
|
355
|
+
return;
|
|
356
|
+
const model = modelLabel(ctx);
|
|
357
|
+
const detail = terminalFailureDetail(lastMsg);
|
|
358
|
+
ctx.ui.notify(`Manual /gsd discuss ${guidedUnit.unitType} ended with a provider error on ${model}: ${detail}`, "warning");
|
|
359
|
+
try {
|
|
360
|
+
writeManualGuidedTerminalErrorActivity(guidedUnit.basePath, guidedUnit.unitType, model, detail, isObjectRecord(lastMsg) ? lastMsg.stopReason : "unknown");
|
|
361
|
+
}
|
|
362
|
+
catch (err) {
|
|
363
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
364
|
+
logWarning("bootstrap", `Failed to write manual guided terminal-error activity marker: ${message}`);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
280
367
|
async function pauseTransientWithBackoff(cls, pi, ctx, errorDetail, isRateLimit) {
|
|
281
368
|
retryState.consecutiveTransientCount += 1;
|
|
282
369
|
const baseRetryAfterMs = "retryAfterMs" in cls ? cls.retryAfterMs : 15_000;
|
|
@@ -315,7 +402,9 @@ export async function handleAgentEnd(pi, event, ctx) {
|
|
|
315
402
|
// rejected" loop even though the files are on disk.
|
|
316
403
|
clearPathCache();
|
|
317
404
|
const basePath = resolveAgentEndBasePath();
|
|
318
|
-
|
|
405
|
+
const lastMsg = event.messages[event.messages.length - 1];
|
|
406
|
+
const guidedUnit = basePath ? getGuidedUnitContext(basePath) ?? getGuidedUnitContext() : getGuidedUnitContext();
|
|
407
|
+
clearGuidedUnitContext(guidedUnit?.basePath ?? basePath);
|
|
319
408
|
try {
|
|
320
409
|
if (await checkDeepProjectSetupAfterTurn(event, ctx, basePath)) {
|
|
321
410
|
return;
|
|
@@ -342,12 +431,13 @@ export async function handleAgentEnd(pi, event, ctx) {
|
|
|
342
431
|
// discussions (where isAutoActive may be false) still get recovered.
|
|
343
432
|
if (maybeHandleEmptyIntentTurn(event, isAutoActive(), basePath))
|
|
344
433
|
return;
|
|
345
|
-
if (!isAutoActive())
|
|
434
|
+
if (!isAutoActive()) {
|
|
435
|
+
observeManualDiscussTerminalError(ctx, lastMsg, guidedUnit);
|
|
346
436
|
return;
|
|
437
|
+
}
|
|
347
438
|
if (shouldIgnoreAgentEndForActiveUnit(event)) {
|
|
348
439
|
return;
|
|
349
440
|
}
|
|
350
|
-
const lastMsg = event.messages[event.messages.length - 1];
|
|
351
441
|
if (isSessionSwitchInFlight()) {
|
|
352
442
|
_handleSessionSwitchAgentEnd(lastMsg, resolveAgentEndCancelled);
|
|
353
443
|
return;
|
|
@@ -435,9 +525,9 @@ export async function handleAgentEnd(pi, event, ctx) {
|
|
|
435
525
|
});
|
|
436
526
|
return;
|
|
437
527
|
}
|
|
438
|
-
// #3588: When errorMessage is uninformative, extract the real error
|
|
439
|
-
//
|
|
440
|
-
//
|
|
528
|
+
// #3588/#956: When errorMessage is uninformative, extract the real error
|
|
529
|
+
// from assistant text. Prefer rawErrorMsg for classification to avoid
|
|
530
|
+
// prose false-positives, but use display text when rawErrorMsg is empty.
|
|
441
531
|
const displayMsg = resolveAgentEndErrorDisplay(rawErrorMsg, "content" in lastMsg ? lastMsg.content : undefined);
|
|
442
532
|
if (isAutoCompletionStopInProgress() &&
|
|
443
533
|
isTerminalDeletedWorktreeProviderError(`${rawErrorMsg}\n${displayMsg}`)) {
|
|
@@ -447,8 +537,8 @@ export async function handleAgentEnd(pi, event, ctx) {
|
|
|
447
537
|
}
|
|
448
538
|
const errorDetail = displayMsg ? `: ${displayMsg}` : "";
|
|
449
539
|
const explicitRetryAfterMs = ("retryAfterMs" in lastMsg && typeof lastMsg.retryAfterMs === "number") ? lastMsg.retryAfterMs : undefined;
|
|
450
|
-
// ── 1. Classify
|
|
451
|
-
const cls = classifyError(rawErrorMsg, explicitRetryAfterMs);
|
|
540
|
+
// ── 1. Classify, preserving non-empty errorMessage precedence ──────
|
|
541
|
+
const cls = classifyError(rawErrorMsg || displayMsg, explicitRetryAfterMs);
|
|
452
542
|
// ── 1a. Unsupported-model: provider rejected this model for the current
|
|
453
543
|
// account/plan at request time (#4513). Persist a block so the
|
|
454
544
|
// same dead model isn't reselected on the next /gsd auto restart,
|
|
@@ -517,7 +607,7 @@ export async function handleAgentEnd(pi, event, ctx) {
|
|
|
517
607
|
// Core retries transient failures in-session after this handler.
|
|
518
608
|
// Keep that behavior for non-rate-limit classes to avoid pause/retry races,
|
|
519
609
|
// but let rate-limit continue into model fallback logic below (#4373).
|
|
520
|
-
if (shouldDeferTransientErrorToCoreRetry(cls, rawErrorMsg)) {
|
|
610
|
+
if (shouldDeferTransientErrorToCoreRetry(cls, rawErrorMsg, rawErrorMsg || displayMsg)) {
|
|
521
611
|
return;
|
|
522
612
|
}
|
|
523
613
|
// ── Tool-schema overload: the active model repeatedly emitted tool-call
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Project/App: gsd-pi
|
|
2
|
+
// File Purpose: Canonical core session tool surface shared by auto-mode scoping and loop guards.
|
|
3
|
+
/** Base tools available during auto-mode execution before unit-specific GSD tools are added. */
|
|
4
|
+
export const MINIMAL_AUTO_BASE_TOOL_NAMES = [
|
|
5
|
+
"ask_user_questions",
|
|
6
|
+
"bash",
|
|
7
|
+
"bg_shell",
|
|
8
|
+
"edit",
|
|
9
|
+
"find",
|
|
10
|
+
"glob",
|
|
11
|
+
"grep",
|
|
12
|
+
"fetch_page",
|
|
13
|
+
"search-the-web",
|
|
14
|
+
"ls",
|
|
15
|
+
"read",
|
|
16
|
+
"subagent",
|
|
17
|
+
"write",
|
|
18
|
+
"ToolSearch",
|
|
19
|
+
];
|
|
20
|
+
/** Tools excluded from the high per-turn loop-guard cap (strict or orchestration). */
|
|
21
|
+
const NON_REPEATABLE_FROM_MINIMAL_BASE = new Set([
|
|
22
|
+
"ask_user_questions",
|
|
23
|
+
"subagent",
|
|
24
|
+
"ToolSearch",
|
|
25
|
+
]);
|
|
26
|
+
/** Additional core tools not in MINIMAL_AUTO_BASE but routinely multi-called per turn. */
|
|
27
|
+
const EXTRA_INHERENTLY_REPEATABLE = [
|
|
28
|
+
"multi_edit",
|
|
29
|
+
"todo_write",
|
|
30
|
+
"notebook_edit",
|
|
31
|
+
"search_and_read",
|
|
32
|
+
];
|
|
33
|
+
/** Core session tools that may be invoked many times per agent turn. */
|
|
34
|
+
export const INHERENTLY_REPEATABLE_TOOL_NAMES = [
|
|
35
|
+
...MINIMAL_AUTO_BASE_TOOL_NAMES.filter((name) => !NON_REPEATABLE_FROM_MINIMAL_BASE.has(name)),
|
|
36
|
+
...EXTRA_INHERENTLY_REPEATABLE,
|
|
37
|
+
];
|
|
38
|
+
export const INHERENTLY_REPEATABLE_TOOL_SET = new Set(INHERENTLY_REPEATABLE_TOOL_NAMES);
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// File Purpose: Registers DB-backed GSD workflow tools and compatibility aliases.
|
|
3
3
|
import { Type, StringEnum } from "@gsd/pi-ai";
|
|
4
4
|
import { Text } from "@gsd/pi-tui";
|
|
5
|
+
import { SUMMARY_SAVE_CONTENT_MAX_LENGTH } from "@opengsd/contracts";
|
|
5
6
|
import { loadEffectiveGSDPreferences } from "../preferences.js";
|
|
6
7
|
import { ensureDbOpen, resolveCtxCwd, resolveWorkflowToolBasePath } from "./dynamic-tools.js";
|
|
7
8
|
import { importWorkflowExecutorsModule } from "../workflow-mcp.js";
|
|
@@ -373,13 +374,17 @@ export function registerDbTools(pi) {
|
|
|
373
374
|
"Root-level artifact paths are PROJECT.md, PROJECT-DRAFT.md, REQUIREMENTS.md, and REQUIREMENTS-DRAFT.md.",
|
|
374
375
|
"artifact_type must be one of: SUMMARY, RESEARCH, CONTEXT, ASSESSMENT, CONTEXT-DRAFT, PROJECT, PROJECT-DRAFT, REQUIREMENTS, REQUIREMENTS-DRAFT.",
|
|
375
376
|
"Use CONTEXT-DRAFT for incremental draft persistence; use CONTEXT for the final milestone context after depth verification.",
|
|
377
|
+
`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.`,
|
|
376
378
|
],
|
|
377
379
|
parameters: Type.Object({
|
|
378
380
|
milestone_id: Type.Optional(Type.String({ description: "Milestone ID (e.g. M001). Omit only for root-level PROJECT/PROJECT-DRAFT/REQUIREMENTS/REQUIREMENTS-DRAFT artifacts." })),
|
|
379
381
|
slice_id: Type.Optional(Type.String({ description: "Slice ID (e.g. S01)" })),
|
|
380
382
|
task_id: Type.Optional(Type.String({ description: "Task ID (e.g. T01)" })),
|
|
381
383
|
artifact_type: StringEnum(["SUMMARY", "RESEARCH", "CONTEXT", "ASSESSMENT", "CONTEXT-DRAFT", "PROJECT", "PROJECT-DRAFT", "REQUIREMENTS", "REQUIREMENTS-DRAFT"], { description: "Artifact type to save" }),
|
|
382
|
-
content: Type.String({
|
|
384
|
+
content: Type.String({
|
|
385
|
+
description: `The full markdown content of the artifact. Maximum ${SUMMARY_SAVE_CONTENT_MAX_LENGTH} characters per save.`,
|
|
386
|
+
maxLength: SUMMARY_SAVE_CONTENT_MAX_LENGTH,
|
|
387
|
+
}),
|
|
383
388
|
}),
|
|
384
389
|
execute: summarySaveExecute,
|
|
385
390
|
renderCall(args, theme) {
|
|
@@ -60,6 +60,7 @@ export function registerExecTools(pi) {
|
|
|
60
60
|
return executeUatExec(params, {
|
|
61
61
|
baseDir,
|
|
62
62
|
preferences: await loadContextModePreferences(baseDir),
|
|
63
|
+
signal: _signal,
|
|
63
64
|
});
|
|
64
65
|
},
|
|
65
66
|
});
|
|
@@ -99,6 +100,7 @@ export function registerExecTools(pi) {
|
|
|
99
100
|
return executeGsdExec(params, {
|
|
100
101
|
baseDir,
|
|
101
102
|
preferences: await loadContextModePreferences(baseDir),
|
|
103
|
+
signal: _signal,
|
|
102
104
|
});
|
|
103
105
|
},
|
|
104
106
|
});
|
|
@@ -7,7 +7,7 @@ import { isToolCallEventType } from "@gsd/pi-coding-agent";
|
|
|
7
7
|
import { ALWAYS_PRESERVED_SHIM_TOOL_NAMES } from "@gsd/pi-ai";
|
|
8
8
|
import { updateSnapshot } from "../ecosystem/gsd-extension-api.js";
|
|
9
9
|
import { buildMilestoneFileName, canonicalPhaseDirName, clearPathCache, milestonesDir, legacyMilestonesDir, resolveMilestonePath, resolveSliceFile, resolveSlicePath } from "../paths.js";
|
|
10
|
-
import { applyAskUserQuestionsGateResult, clearDiscussionFlowState, formatPendingAskUserQuestionsGateMessage, formatTimedOutAskUserQuestionsGateMessage, hostWriteGateAdapter, isApprovalGateVerifiedInSnapshot, isDepthConfirmationAnswer,
|
|
10
|
+
import { applyAskUserQuestionsGateResult, clearDiscussionFlowState, currentWriteGateSnapshot, formatPendingAskUserQuestionsGateMessage, formatTimedOutAskUserQuestionsGateMessage, hostWriteGateAdapter, isApprovalGateVerifiedInSnapshot, isDepthConfirmationAnswer, isMilestoneDepthVerifiedInSnapshot, isQueuePhaseActive, resetWriteGateState, shouldBlockContextWrite, shouldBlockPlanningUnit, shouldBlockQueueExecution, shouldBlockWorktreeBash, shouldBlockWorktreeWrite, isGateQuestionId, getPendingGate, shouldBlockPendingGate, shouldBlockPendingGateBash, extractDepthVerificationMilestoneId } from "./write-gate.js";
|
|
11
11
|
import { canonicalToolName } from "../engine-hook-contract.js";
|
|
12
12
|
import { resolveManifest } from "../unit-context-manifest.js";
|
|
13
13
|
import { isBlockedStateFile, isBashWriteToStateFile, BLOCKED_WRITE_ERROR } from "../write-intercept.js";
|
|
@@ -15,6 +15,7 @@ import { loadFile, saveFile, formatContinue } from "../files.js";
|
|
|
15
15
|
import { clearAutoCompletionStopInProgress, clearToolInvocationError, getAutoRuntimeSnapshot, getSourceObservationStore, isAutoActive, isAutoCompletionStopInProgress, isAutoPaused, isInteractiveElicitationInFlight, markToolEnd, markToolStart, recordAutoToolSurfaceSnapshot, recordToolInvocationError, } from "../auto-runtime-state.js";
|
|
16
16
|
import { applyProviderPayloadPolicy } from "../provider-payload-policy.js";
|
|
17
17
|
import { checkToolCallLoop, resetToolCallLoopGuard } from "./tool-call-loop-guard.js";
|
|
18
|
+
import { MINIMAL_AUTO_BASE_TOOL_NAMES } from "./core-session-tools.js";
|
|
18
19
|
import { maybePauseAutoForApprovalGate, resetPendingGatePauseGuard } from "./pending-gate-pause.js";
|
|
19
20
|
import { saveActivityLog } from "../activity-log.js";
|
|
20
21
|
import { recordToolCall as safetyRecordToolCall, recordToolResult as safetyRecordToolResult, saveEvidenceToDisk } from "../safety/evidence-collector.js";
|
|
@@ -138,22 +139,7 @@ export const MINIMAL_GSD_TOOL_NAMES = [
|
|
|
138
139
|
"capture_thought",
|
|
139
140
|
"gsd_capture_thought",
|
|
140
141
|
];
|
|
141
|
-
export
|
|
142
|
-
"ask_user_questions",
|
|
143
|
-
"bash",
|
|
144
|
-
"bg_shell",
|
|
145
|
-
"edit",
|
|
146
|
-
"find",
|
|
147
|
-
"glob",
|
|
148
|
-
"grep",
|
|
149
|
-
"fetch_page",
|
|
150
|
-
"search-the-web",
|
|
151
|
-
"ls",
|
|
152
|
-
"read",
|
|
153
|
-
"subagent",
|
|
154
|
-
"write",
|
|
155
|
-
"ToolSearch",
|
|
156
|
-
];
|
|
142
|
+
export { MINIMAL_AUTO_BASE_TOOL_NAMES } from "./core-session-tools.js";
|
|
157
143
|
function withPreservedShimTools(toolNames) {
|
|
158
144
|
return [...new Set([...toolNames, ...ALWAYS_PRESERVED_SHIM_TOOL_NAMES])];
|
|
159
145
|
}
|
|
@@ -436,6 +422,9 @@ function deferApprovalGate(gateId, basePath) {
|
|
|
436
422
|
// workflow MCP child already verified this gate, deferring would block
|
|
437
423
|
// tools for a gate that can never legitimately arm.
|
|
438
424
|
const snapshot = hostWriteGateAdapter.readState(basePath);
|
|
425
|
+
deferApprovalGateFromSnapshot(gateId, basePath, snapshot);
|
|
426
|
+
}
|
|
427
|
+
function deferApprovalGateFromSnapshot(gateId, basePath, snapshot) {
|
|
439
428
|
if (isApprovalGateVerifiedInSnapshot(snapshot, gateId))
|
|
440
429
|
return;
|
|
441
430
|
const milestoneId = extractDepthVerificationMilestoneId(gateId);
|
|
@@ -1009,14 +998,16 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
1009
998
|
return;
|
|
1010
999
|
const gateId = approvalGateIdForUnit(unitType, unitId);
|
|
1011
1000
|
if (gateId) {
|
|
1001
|
+
const basePath = contextBasePath(ctx);
|
|
1002
|
+
const gateSnapshot = currentWriteGateSnapshot(basePath);
|
|
1012
1003
|
// Skip the gate if this milestone is already depth-verified — the approval
|
|
1013
1004
|
// pattern matched again on post-verification text (a false-positive re-trigger).
|
|
1014
1005
|
// Without this guard, the second firing blocks gsd_plan_milestone in the same
|
|
1015
1006
|
// turn and leaves CONTEXT.md on disk with no DB row (#discuss-milestone-no-db).
|
|
1016
1007
|
const gateMilestoneId = extractDepthVerificationMilestoneId(gateId);
|
|
1017
|
-
if (gateMilestoneId &&
|
|
1008
|
+
if (gateMilestoneId && isMilestoneDepthVerifiedInSnapshot(gateSnapshot, gateMilestoneId))
|
|
1018
1009
|
return;
|
|
1019
|
-
|
|
1010
|
+
deferApprovalGateFromSnapshot(gateId, basePath, gateSnapshot);
|
|
1020
1011
|
}
|
|
1021
1012
|
approvalQuestionAbortInFlight = true;
|
|
1022
1013
|
ctx.ui.notify(`${unitType ?? "The discussion"}${unitId ? ` ${unitId}` : ""} is waiting for your approval - pausing before more tool calls run.`, "info");
|
|
@@ -23,9 +23,10 @@ const DEFAULT_KNOWLEDGE_MAX_CHARS = 12_000;
|
|
|
23
23
|
const DEFAULT_CODEBASE_MAX_CHARS = 8_000;
|
|
24
24
|
const MIN_CONTEXT_MESSAGE_MAX_CHARS = 1_000;
|
|
25
25
|
const MIN_KNOWLEDGE_MAX_CHARS = 1_000;
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
const
|
|
26
|
+
const CONTEXT_MAINTENANCE_KEY_SEPARATOR = "\0";
|
|
27
|
+
const contextMaintenanceCompletedForSession = new Set();
|
|
28
|
+
const contextMaintenanceInFlightBySession = new Map();
|
|
29
|
+
const deferredContextMaintenanceBySession = new Map();
|
|
29
30
|
/**
|
|
30
31
|
* Bundled skill triggers — resolved dynamically at runtime instead of
|
|
31
32
|
* hardcoding absolute paths in the system prompt template. Only skills
|
|
@@ -100,7 +101,8 @@ function warnDeprecatedAgentInstructions() {
|
|
|
100
101
|
}
|
|
101
102
|
}
|
|
102
103
|
async function runSessionStartupMaintenanceOnce(basePath, ctx) {
|
|
103
|
-
|
|
104
|
+
const maintenanceKey = getContextMaintenanceKey(basePath, ctx);
|
|
105
|
+
if (contextMaintenanceCompletedForSession.has(maintenanceKey)) {
|
|
104
106
|
// Backfills are session-once, but memory queries and other DB-backed
|
|
105
107
|
// prompt assembly still need an active adapter on every turn.
|
|
106
108
|
try {
|
|
@@ -112,16 +114,16 @@ async function runSessionStartupMaintenanceOnce(basePath, ctx) {
|
|
|
112
114
|
}
|
|
113
115
|
return false;
|
|
114
116
|
}
|
|
115
|
-
const existing =
|
|
117
|
+
const existing = contextMaintenanceInFlightBySession.get(maintenanceKey);
|
|
116
118
|
const isInitiator = !existing;
|
|
117
119
|
// Use a definite Promise<boolean> so `await inFlight` has a known return type.
|
|
118
120
|
let inFlight;
|
|
119
121
|
if (isInitiator) {
|
|
120
|
-
inFlight = performSessionStartupMaintenance(basePath, ctx);
|
|
121
|
-
|
|
122
|
+
inFlight = performSessionStartupMaintenance(basePath, ctx, maintenanceKey);
|
|
123
|
+
contextMaintenanceInFlightBySession.set(maintenanceKey, inFlight);
|
|
122
124
|
void inFlight.finally(() => {
|
|
123
|
-
if (
|
|
124
|
-
|
|
125
|
+
if (contextMaintenanceInFlightBySession.get(maintenanceKey) === inFlight) {
|
|
126
|
+
contextMaintenanceInFlightBySession.delete(maintenanceKey);
|
|
125
127
|
}
|
|
126
128
|
});
|
|
127
129
|
}
|
|
@@ -131,7 +133,7 @@ async function runSessionStartupMaintenanceOnce(basePath, ctx) {
|
|
|
131
133
|
const result = await inFlight;
|
|
132
134
|
return isInitiator ? result : false;
|
|
133
135
|
}
|
|
134
|
-
async function performSessionStartupMaintenance(basePath, ctx) {
|
|
136
|
+
async function performSessionStartupMaintenance(basePath, ctx, maintenanceKey) {
|
|
135
137
|
// DB-backed memory backfills run below. On a cold session the database file
|
|
136
138
|
// may exist without an active in-process adapter, so open the canonical
|
|
137
139
|
// project DB before those best-effort operations inspect it.
|
|
@@ -154,10 +156,33 @@ async function performSessionStartupMaintenance(basePath, ctx) {
|
|
|
154
156
|
]);
|
|
155
157
|
// Mark session complete before scheduling deferred work so any concurrent
|
|
156
158
|
// caller that observes the completed state does not re-enter maintenance.
|
|
157
|
-
|
|
158
|
-
scheduleDeferredContextMaintenance(basePath);
|
|
159
|
+
contextMaintenanceCompletedForSession.add(maintenanceKey);
|
|
160
|
+
scheduleDeferredContextMaintenance(basePath, maintenanceKey);
|
|
159
161
|
return true;
|
|
160
162
|
}
|
|
163
|
+
function getContextMaintenanceKey(basePath, ctx) {
|
|
164
|
+
return `${basePath}${CONTEXT_MAINTENANCE_KEY_SEPARATOR}${getContextSessionPart(ctx)}`;
|
|
165
|
+
}
|
|
166
|
+
function getContextSessionPart(ctx) {
|
|
167
|
+
const sessionManager = ctx.sessionManager;
|
|
168
|
+
try {
|
|
169
|
+
const sessionId = sessionManager?.getSessionId?.();
|
|
170
|
+
if (typeof sessionId === "string" && sessionId.length > 0)
|
|
171
|
+
return `id:${sessionId}`;
|
|
172
|
+
}
|
|
173
|
+
catch (e) {
|
|
174
|
+
logWarning("bootstrap", `session-id fetch failed: ${e.message}`);
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
const sessionFile = sessionManager?.getSessionFile?.();
|
|
178
|
+
if (typeof sessionFile === "string" && sessionFile.length > 0)
|
|
179
|
+
return `file:${sessionFile}`;
|
|
180
|
+
}
|
|
181
|
+
catch (e) {
|
|
182
|
+
logWarning("bootstrap", `session-file fetch failed: ${e.message}`);
|
|
183
|
+
}
|
|
184
|
+
return "process";
|
|
185
|
+
}
|
|
161
186
|
async function runDecisionsMemoryBackfill(ctx) {
|
|
162
187
|
// ADR-013 step 5: opportunistic decisions->memories backfill. Idempotent
|
|
163
188
|
// and best-effort — first run absorbs the existing decisions table into
|
|
@@ -188,18 +213,18 @@ async function runKnowledgeMemoryBackfill(basePath, ctx) {
|
|
|
188
213
|
logWarning("bootstrap", `KNOWLEDGE.md backfill failed: ${e.message}`);
|
|
189
214
|
}
|
|
190
215
|
}
|
|
191
|
-
function scheduleDeferredContextMaintenance(basePath) {
|
|
192
|
-
if (
|
|
216
|
+
function scheduleDeferredContextMaintenance(basePath, maintenanceKey) {
|
|
217
|
+
if (deferredContextMaintenanceBySession.has(maintenanceKey))
|
|
193
218
|
return;
|
|
194
219
|
const task = new Promise((resolve) => {
|
|
195
220
|
setTimeout(() => {
|
|
196
221
|
void runDeferredContextMaintenance(basePath).finally(resolve);
|
|
197
222
|
}, 0);
|
|
198
223
|
});
|
|
199
|
-
|
|
224
|
+
deferredContextMaintenanceBySession.set(maintenanceKey, task);
|
|
200
225
|
void task.finally(() => {
|
|
201
|
-
if (
|
|
202
|
-
|
|
226
|
+
if (deferredContextMaintenanceBySession.get(maintenanceKey) === task) {
|
|
227
|
+
deferredContextMaintenanceBySession.delete(maintenanceKey);
|
|
203
228
|
}
|
|
204
229
|
});
|
|
205
230
|
}
|
|
@@ -232,8 +257,10 @@ async function reportConsolidationGapsDeferred(basePath) {
|
|
|
232
257
|
}
|
|
233
258
|
export async function _flushDeferredContextMaintenanceForTest(basePath) {
|
|
234
259
|
const tasks = basePath
|
|
235
|
-
? [
|
|
236
|
-
|
|
260
|
+
? [...deferredContextMaintenanceBySession.entries()]
|
|
261
|
+
.filter(([key]) => key.startsWith(`${basePath}${CONTEXT_MAINTENANCE_KEY_SEPARATOR}`))
|
|
262
|
+
.map(([, task]) => task)
|
|
263
|
+
: [...deferredContextMaintenanceBySession.values()];
|
|
237
264
|
await Promise.allSettled(tasks);
|
|
238
265
|
}
|
|
239
266
|
export async function buildBeforeAgentStartResult(event, ctx) {
|
|
@@ -1,25 +1,49 @@
|
|
|
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
|
import { createHash } from "node:crypto";
|
|
24
|
+
import { INHERENTLY_REPEATABLE_TOOL_SET } from "./core-session-tools.js";
|
|
15
25
|
const MAX_CONSECUTIVE_IDENTICAL_CALLS = 4;
|
|
16
26
|
/** Interactive/user-facing tools where even 1 duplicate is confusing. */
|
|
17
27
|
const STRICT_LOOP_TOOLS = new Set(["ask_user_questions"]);
|
|
18
28
|
const MAX_CONSECUTIVE_STRICT = 1;
|
|
29
|
+
/**
|
|
30
|
+
* Per-turn cap on calls to the SAME tool name, regardless of args (#783).
|
|
31
|
+
*
|
|
32
|
+
* General-purpose execution tools are routinely called many times per turn
|
|
33
|
+
* (touching multiple files, running several commands), so they get a higher
|
|
34
|
+
* ceiling. Everything else — workflow one-shot tools (e.g. gsd_complete_milestone)
|
|
35
|
+
* and any non-allowlisted tool — gets the default cap. The default is generous
|
|
36
|
+
* enough to absorb legitimate retries but catches the reported improvisation
|
|
37
|
+
* loop (~51 calls) well before a cost spike.
|
|
38
|
+
*/
|
|
39
|
+
const PER_TOOL_DEFAULT_CAP = 6;
|
|
40
|
+
const PER_TOOL_REPEATABLE_CAP = 15;
|
|
19
41
|
let consecutiveCount = 0;
|
|
20
42
|
let lastSignature = "";
|
|
21
43
|
let lastToolName = "";
|
|
22
44
|
let enabled = true;
|
|
45
|
+
/** Per-tool-name call counts within the current turn (#783 Brief C). */
|
|
46
|
+
const perToolCounts = new Map();
|
|
23
47
|
/** Hash tool name + args into a compact signature for comparison. */
|
|
24
48
|
function hashToolCall(toolName, args) {
|
|
25
49
|
const h = createHash("sha256");
|
|
@@ -38,6 +62,12 @@ function hashToolCall(toolName, args) {
|
|
|
38
62
|
*
|
|
39
63
|
* Returns `{ block: false }` for allowed calls.
|
|
40
64
|
* Returns `{ block: true, reason }` when the loop threshold is exceeded.
|
|
65
|
+
*
|
|
66
|
+
* Two independent guards run; whichever trips first blocks:
|
|
67
|
+
* 1. Identical-signature streak (MAX_CONSECUTIVE_IDENTICAL_CALLS, strict for
|
|
68
|
+
* ask_user_questions).
|
|
69
|
+
* 2. Per-tool-name cap (PER_TOOL_DEFAULT_CAP / PER_TOOL_REPEATABLE_CAP),
|
|
70
|
+
* independent of args — catches improvisation loops (#783).
|
|
41
71
|
*/
|
|
42
72
|
export function checkToolCallLoop(toolName, args) {
|
|
43
73
|
if (!enabled)
|
|
@@ -51,18 +81,37 @@ export function checkToolCallLoop(toolName, args) {
|
|
|
51
81
|
lastSignature = sig;
|
|
52
82
|
lastToolName = toolName;
|
|
53
83
|
}
|
|
84
|
+
// ── Guard 1: identical-signature streak ──
|
|
54
85
|
const threshold = STRICT_LOOP_TOOLS.has(toolName)
|
|
55
86
|
? MAX_CONSECUTIVE_STRICT
|
|
56
87
|
: MAX_CONSECUTIVE_IDENTICAL_CALLS;
|
|
57
88
|
if (consecutiveCount > threshold) {
|
|
58
89
|
return {
|
|
59
90
|
block: true,
|
|
60
|
-
reason: `Tool loop detected: ${toolName} called ${consecutiveCount} times ` +
|
|
91
|
+
reason: `Tool loop detected (identical args): ${toolName} called ${consecutiveCount} times ` +
|
|
61
92
|
`with identical arguments. Blocking to prevent infinite loop. ` +
|
|
62
|
-
`
|
|
93
|
+
`Do not retry this tool or call other tools this turn — stop and respond to the user in text.`,
|
|
63
94
|
count: consecutiveCount,
|
|
64
95
|
};
|
|
65
96
|
}
|
|
97
|
+
// ── Guard 2: per-tool-name cap, independent of args (#783 Brief C) ──
|
|
98
|
+
// Catches improvisation loops where the same tool is invoked many times with
|
|
99
|
+
// varied args (e.g. retrying a missing workflow tool via bash/node -e/CLI).
|
|
100
|
+
const perToolCount = (perToolCounts.get(toolName) ?? 0) + 1;
|
|
101
|
+
perToolCounts.set(toolName, perToolCount);
|
|
102
|
+
const perToolCap = INHERENTLY_REPEATABLE_TOOL_SET.has(toolName)
|
|
103
|
+
? PER_TOOL_REPEATABLE_CAP
|
|
104
|
+
: PER_TOOL_DEFAULT_CAP;
|
|
105
|
+
if (perToolCount > perToolCap) {
|
|
106
|
+
return {
|
|
107
|
+
block: true,
|
|
108
|
+
reason: `Tool loop detected (repeated tool): ${toolName} called ${perToolCount} times ` +
|
|
109
|
+
`this turn (cap ${perToolCap}). Blocking to prevent infinite loop. ` +
|
|
110
|
+
`The tool may be unavailable or failing repeatedly. ` +
|
|
111
|
+
`Do not retry this tool or pivot to other tools this turn — stop and respond to the user in text.`,
|
|
112
|
+
count: perToolCount,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
66
115
|
return { block: false, count: consecutiveCount };
|
|
67
116
|
}
|
|
68
117
|
/** Reset the guard state. Call at agent turn boundaries. */
|
|
@@ -71,6 +120,7 @@ export function resetToolCallLoopGuard() {
|
|
|
71
120
|
lastSignature = "";
|
|
72
121
|
lastToolName = "";
|
|
73
122
|
enabled = true;
|
|
123
|
+
perToolCounts.clear();
|
|
74
124
|
}
|
|
75
125
|
/** Disable the guard (e.g. during shutdown). */
|
|
76
126
|
export function disableToolCallLoopGuard() {
|
|
@@ -78,8 +128,16 @@ export function disableToolCallLoopGuard() {
|
|
|
78
128
|
consecutiveCount = 0;
|
|
79
129
|
lastSignature = "";
|
|
80
130
|
lastToolName = "";
|
|
131
|
+
perToolCounts.clear();
|
|
81
132
|
}
|
|
82
133
|
/** Get current consecutive count for diagnostics. */
|
|
83
134
|
export function getToolCallLoopCount() {
|
|
84
135
|
return consecutiveCount;
|
|
85
136
|
}
|
|
137
|
+
/**
|
|
138
|
+
* Get the per-tool-name call count for the current turn (#783 Brief C).
|
|
139
|
+
* Returns 0 for tools not yet called. Diagnostic only.
|
|
140
|
+
*/
|
|
141
|
+
export function getToolCallCountForTool(toolName) {
|
|
142
|
+
return perToolCounts.get(toolName) ?? 0;
|
|
143
|
+
}
|
|
@@ -141,7 +141,7 @@ function ensureWriteGateSnapshotDirectory(basePath) {
|
|
|
141
141
|
}
|
|
142
142
|
mkdirSync(join(gsdPath, "runtime"), { recursive: true });
|
|
143
143
|
}
|
|
144
|
-
function currentWriteGateSnapshot(basePath = process.cwd()) {
|
|
144
|
+
export function currentWriteGateSnapshot(basePath = process.cwd()) {
|
|
145
145
|
const state = getWriteGateState(basePath);
|
|
146
146
|
return {
|
|
147
147
|
verifiedDepthMilestones: [...state.verifiedDepthMilestones].sort(),
|
|
@@ -8,6 +8,8 @@ import { formatPercent, formatTokenCount } from "./metrics.js";
|
|
|
8
8
|
import { countTokensSync } from "./token-counter.js";
|
|
9
9
|
import { writeContextChartHtml } from "./context-chart-html.js";
|
|
10
10
|
import { openInBrowser } from "./export.js";
|
|
11
|
+
import { truncateWithEllipsis } from "../shared/format-utils.js";
|
|
12
|
+
const REDACTED_TOOL_ARGUMENT_KEYS = new Set(["content", "oldText", "newText"]);
|
|
11
13
|
function resolveProvider(provider) {
|
|
12
14
|
const normalized = (provider ?? "unknown").toLowerCase();
|
|
13
15
|
if (normalized === "anthropic" || normalized === "claude-code")
|
|
@@ -167,6 +169,22 @@ export function parseSystemPromptSections(systemPrompt, provider) {
|
|
|
167
169
|
}
|
|
168
170
|
return sections;
|
|
169
171
|
}
|
|
172
|
+
function redactToolCallArguments(value) {
|
|
173
|
+
if (Array.isArray(value))
|
|
174
|
+
return value.map(redactToolCallArguments);
|
|
175
|
+
if (!value || typeof value !== "object")
|
|
176
|
+
return value;
|
|
177
|
+
const safe = {};
|
|
178
|
+
for (const [key, child] of Object.entries(value)) {
|
|
179
|
+
if (REDACTED_TOOL_ARGUMENT_KEYS.has(key)) {
|
|
180
|
+
safe[key] = typeof child === "string" ? truncateWithEllipsis(child, 101) : "[redacted]";
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
safe[key] = redactToolCallArguments(child);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return safe;
|
|
187
|
+
}
|
|
170
188
|
function messageToText(message) {
|
|
171
189
|
const role = message.role;
|
|
172
190
|
if (role === "assistant") {
|
|
@@ -183,7 +201,7 @@ function messageToText(message) {
|
|
|
183
201
|
parts.push(typed.thinking);
|
|
184
202
|
if (typed.type === "toolCall") {
|
|
185
203
|
parts.push(typed.name ?? "tool");
|
|
186
|
-
parts.push(JSON.stringify(typed.arguments ?? {}));
|
|
204
|
+
parts.push(JSON.stringify(redactToolCallArguments(typed.arguments ?? {})));
|
|
187
205
|
}
|
|
188
206
|
}
|
|
189
207
|
}
|