gsd-pi 2.76.0-dev.82e249f7b → 2.76.0-dev.97f5583d9
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/claude-cli-check.js +32 -3
- package/dist/mcp-server.d.ts +7 -0
- package/dist/mcp-server.js +35 -1
- package/dist/resource-loader.d.ts +1 -1
- package/dist/resource-loader.js +2 -8
- package/dist/resources/extensions/claude-code-cli/readiness.js +4 -3
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +77 -17
- package/dist/resources/extensions/gsd/auto/phases.js +42 -1
- package/dist/resources/extensions/gsd/auto/run-unit.js +27 -0
- package/dist/resources/extensions/gsd/auto/session.js +12 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +16 -3
- package/dist/resources/extensions/gsd/auto-model-selection.js +1 -1
- package/dist/resources/extensions/gsd/auto-post-unit.js +25 -2
- package/dist/resources/extensions/gsd/auto-prompts.js +14 -0
- package/dist/resources/extensions/gsd/auto-recovery.js +13 -0
- package/dist/resources/extensions/gsd/auto-start.js +27 -18
- package/dist/resources/extensions/gsd/auto-worktree.js +51 -53
- package/dist/resources/extensions/gsd/auto.js +55 -27
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +17 -1
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +39 -9
- package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +93 -0
- package/dist/resources/extensions/gsd/bootstrap/register-extension.js +2 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +51 -5
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +34 -2
- package/dist/resources/extensions/gsd/clean-root-preflight.js +93 -0
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +968 -23
- package/dist/resources/extensions/gsd/compaction-snapshot.js +121 -0
- package/dist/resources/extensions/gsd/error-classifier.js +10 -3
- package/dist/resources/extensions/gsd/exec-history.js +120 -0
- package/dist/resources/extensions/gsd/exec-sandbox.js +258 -0
- package/dist/resources/extensions/gsd/gsd-db.js +115 -7
- package/dist/resources/extensions/gsd/guided-flow.js +189 -0
- package/dist/resources/extensions/gsd/health-widget.js +4 -1
- package/dist/resources/extensions/gsd/init-wizard.js +15 -1
- package/dist/resources/extensions/gsd/key-manager.js +6 -0
- package/dist/resources/extensions/gsd/model-router.js +36 -3
- package/dist/resources/extensions/gsd/pre-execution-checks.js +35 -9
- package/dist/resources/extensions/gsd/preferences-types.js +9 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +83 -0
- package/dist/resources/extensions/gsd/preferences.js +17 -17
- package/dist/resources/extensions/gsd/prompts/discuss-headless.md +8 -0
- package/dist/resources/extensions/gsd/prompts/discuss.md +29 -2
- package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +5 -2
- package/dist/resources/extensions/gsd/safety/evidence-collector.js +96 -0
- package/dist/resources/extensions/gsd/safety/file-change-validator.js +13 -5
- package/dist/resources/extensions/gsd/safety/safety-harness.js +5 -1
- package/dist/resources/extensions/gsd/token-counter.js +22 -5
- package/dist/resources/extensions/gsd/tools/exec-search-tool.js +59 -0
- package/dist/resources/extensions/gsd/tools/exec-tool.js +126 -0
- package/dist/resources/extensions/gsd/tools/resume-tool.js +23 -0
- package/dist/resources/extensions/gsd/uok/plan-v2.js +20 -3
- package/dist/resources/extensions/gsd/workflow-mcp.js +3 -0
- package/dist/resources/skills/verify-before-complete/SKILL.md +2 -1
- package/dist/resources/skills/write-docs/SKILL.md +2 -1
- 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 +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/required-server-files.json +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.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/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +10 -10
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
- 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/server.js +1 -1
- package/package.json +1 -1
- package/packages/mcp-server/dist/remote-questions.d.ts +45 -0
- package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -0
- package/packages/mcp-server/dist/remote-questions.js +732 -0
- package/packages/mcp-server/dist/remote-questions.js.map +1 -0
- package/packages/mcp-server/dist/server.d.ts +7 -0
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +41 -4
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +64 -25
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +2 -1
- package/packages/mcp-server/src/mcp-server.test.ts +30 -0
- package/packages/mcp-server/src/remote-questions.test.ts +294 -0
- package/packages/mcp-server/src/remote-questions.ts +916 -0
- package/packages/mcp-server/src/server.ts +62 -10
- package/packages/mcp-server/src/workflow-tools.test.ts +146 -1
- package/packages/mcp-server/src/workflow-tools.ts +84 -43
- package/packages/mcp-server/tsconfig.test.json +19 -0
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-ai/dist/providers/anthropic-auth.test.js +1 -1
- package/packages/pi-ai/dist/providers/anthropic-auth.test.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.js +27 -4
- package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +8 -3
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/providers/minimax-tool-name.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/minimax-tool-name.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/minimax-tool-name.test.js +80 -0
- package/packages/pi-ai/dist/providers/minimax-tool-name.test.js.map +1 -0
- package/packages/pi-ai/dist/providers/simple-options.d.ts +10 -0
- package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/simple-options.js +16 -1
- package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
- package/packages/pi-ai/src/providers/anthropic-auth.test.ts +1 -1
- package/packages/pi-ai/src/providers/anthropic-shared.ts +26 -5
- package/packages/pi-ai/src/providers/anthropic.ts +9 -3
- package/packages/pi-ai/src/providers/minimax-tool-name.test.ts +98 -0
- package/packages/pi-ai/src/providers/simple-options.ts +17 -1
- package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.js +203 -0
- package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +14 -0
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/redact-secrets.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/redact-secrets.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/redact-secrets.js +49 -0
- package/packages/pi-coding-agent/dist/core/redact-secrets.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/redact-secrets.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/redact-secrets.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/redact-secrets.test.js +67 -0
- package/packages/pi-coding-agent/dist/core/redact-secrets.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.js +9 -5
- package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.test.js +25 -1
- package/packages/pi-coding-agent/dist/core/session-manager.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js +5 -4
- package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.d.ts +7 -6
- package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js +29 -21
- package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +13 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/core/model-registry-custom-caps.test.ts +245 -0
- package/packages/pi-coding-agent/src/core/model-registry.ts +16 -0
- package/packages/pi-coding-agent/src/core/redact-secrets.test.ts +86 -0
- package/packages/pi-coding-agent/src/core/redact-secrets.ts +58 -0
- package/packages/pi-coding-agent/src/core/session-manager.test.ts +36 -1
- package/packages/pi-coding-agent/src/core/session-manager.ts +9 -5
- package/packages/pi-coding-agent/src/modes/interactive/components/chat-frame.ts +6 -6
- package/packages/pi-coding-agent/src/modes/interactive/components/skill-invocation-message.ts +36 -22
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +13 -1
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/claude-code-cli/readiness.ts +4 -3
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +78 -17
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +149 -5
- package/src/resources/extensions/gsd/auto/loop-deps.ts +13 -0
- package/src/resources/extensions/gsd/auto/phases.ts +66 -1
- package/src/resources/extensions/gsd/auto/run-unit.ts +29 -0
- package/src/resources/extensions/gsd/auto/session.ts +22 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +16 -3
- package/src/resources/extensions/gsd/auto-model-selection.ts +1 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +29 -3
- package/src/resources/extensions/gsd/auto-prompts.ts +28 -1
- package/src/resources/extensions/gsd/auto-recovery.ts +15 -0
- package/src/resources/extensions/gsd/auto-start.ts +29 -19
- package/src/resources/extensions/gsd/auto-worktree.ts +62 -63
- package/src/resources/extensions/gsd/auto.ts +58 -27
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +23 -1
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +40 -9
- package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +109 -0
- package/src/resources/extensions/gsd/bootstrap/register-extension.ts +2 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +53 -5
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +35 -2
- package/src/resources/extensions/gsd/clean-root-preflight.ts +111 -0
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +898 -32
- package/src/resources/extensions/gsd/compaction-snapshot.ts +165 -0
- package/src/resources/extensions/gsd/error-classifier.ts +10 -3
- package/src/resources/extensions/gsd/exec-history.ts +153 -0
- package/src/resources/extensions/gsd/exec-sandbox.ts +326 -0
- package/src/resources/extensions/gsd/gsd-db.ts +122 -7
- package/src/resources/extensions/gsd/guided-flow.ts +221 -0
- package/src/resources/extensions/gsd/health-widget.ts +3 -1
- package/src/resources/extensions/gsd/init-wizard.ts +15 -1
- package/src/resources/extensions/gsd/journal.ts +2 -1
- package/src/resources/extensions/gsd/key-manager.ts +6 -0
- package/src/resources/extensions/gsd/model-router.ts +42 -1
- package/src/resources/extensions/gsd/pre-execution-checks.ts +36 -10
- package/src/resources/extensions/gsd/preferences-types.ts +46 -0
- package/src/resources/extensions/gsd/preferences-validation.ts +79 -0
- package/src/resources/extensions/gsd/preferences.ts +17 -17
- package/src/resources/extensions/gsd/prompts/discuss-headless.md +8 -0
- package/src/resources/extensions/gsd/prompts/discuss.md +29 -2
- package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +5 -2
- package/src/resources/extensions/gsd/safety/evidence-collector.ts +119 -0
- package/src/resources/extensions/gsd/safety/file-change-validator.ts +17 -4
- package/src/resources/extensions/gsd/safety/safety-harness.ts +9 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +119 -1
- package/src/resources/extensions/gsd/tests/auto-paused-session-validation.test.ts +12 -0
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +49 -0
- package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +186 -0
- package/src/resources/extensions/gsd/tests/compaction-snapshot.test.ts +123 -0
- package/src/resources/extensions/gsd/tests/complete-slice.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +31 -0
- package/src/resources/extensions/gsd/tests/double-merge-guard.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/escalation.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/exec-history.test.ts +124 -0
- package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +210 -0
- package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +58 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +152 -1
- package/src/resources/extensions/gsd/tests/init-wizard.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/isolation-none-branch-guard.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/issue-4540-regressions.test.ts +288 -0
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/key-manager.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/pre-exec-backtick-strip.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/pre-exec-gate-loop.test.ts +272 -0
- package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +234 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +110 -0
- package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +388 -0
- package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +9 -3
- package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +205 -0
- package/src/resources/extensions/gsd/tests/save-gate-result-render.test.ts +95 -0
- package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +32 -40
- package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +56 -0
- package/src/resources/extensions/gsd/tests/token-counter.test.ts +105 -1
- package/src/resources/extensions/gsd/tests/tool-compatibility.test.ts +107 -0
- package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +23 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +65 -2
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +64 -0
- package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +3 -1
- package/src/resources/extensions/gsd/token-counter.ts +22 -5
- package/src/resources/extensions/gsd/tools/exec-search-tool.ts +81 -0
- package/src/resources/extensions/gsd/tools/exec-tool.ts +183 -0
- package/src/resources/extensions/gsd/tools/resume-tool.ts +40 -0
- package/src/resources/extensions/gsd/uok/plan-v2.ts +26 -3
- package/src/resources/extensions/gsd/workflow-logger.ts +3 -1
- package/src/resources/extensions/gsd/workflow-mcp.ts +3 -0
- package/src/resources/skills/verify-before-complete/SKILL.md +2 -1
- package/src/resources/skills/write-docs/SKILL.md +2 -1
- /package/dist/web/standalone/.next/static/{ecSsu49rxxcpbNmVP4mLD → lLdDRDspgYzfz0bJAmUSz}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{ecSsu49rxxcpbNmVP4mLD → lLdDRDspgYzfz0bJAmUSz}/_ssgManifest.js +0 -0
package/dist/claude-cli-check.js
CHANGED
|
@@ -12,12 +12,41 @@ import { execFileSync } from 'node:child_process';
|
|
|
12
12
|
* `src/resources/extensions/gsd/pre-execution-checks.ts`.
|
|
13
13
|
*/
|
|
14
14
|
export const CLAUDE_COMMAND = process.platform === 'win32' ? 'claude.cmd' : 'claude';
|
|
15
|
+
/**
|
|
16
|
+
* Ordered list of binary names to probe for the Claude Code CLI.
|
|
17
|
+
*
|
|
18
|
+
* Windows installs vary: npm-global installs produce a `claude.cmd` shim,
|
|
19
|
+
* direct binary installs produce `claude.exe`, and Git Bash wrappers may
|
|
20
|
+
* expose a bare `claude` shim. Try all three so no valid install is missed.
|
|
21
|
+
*/
|
|
22
|
+
const CLAUDE_COMMAND_CANDIDATES = process.platform === 'win32' ? [CLAUDE_COMMAND, 'claude.exe', 'claude'] : [CLAUDE_COMMAND];
|
|
23
|
+
/**
|
|
24
|
+
* Try to run `args` against each candidate binary.
|
|
25
|
+
* Returns the output buffer on first success, throws the last error if all fail.
|
|
26
|
+
*/
|
|
27
|
+
function execClaudeCheck(args) {
|
|
28
|
+
let lastError;
|
|
29
|
+
for (const command of CLAUDE_COMMAND_CANDIDATES) {
|
|
30
|
+
try {
|
|
31
|
+
return execFileSync(command, args, { timeout: 5_000, stdio: 'pipe' });
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
lastError = error;
|
|
35
|
+
const code = error?.code;
|
|
36
|
+
// EINVAL can surface on Windows Git Bash for .cmd spawn failures.
|
|
37
|
+
if (code === 'ENOENT' || code === 'EINVAL')
|
|
38
|
+
continue;
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
throw lastError ?? new Error(`Claude CLI not found (tried: ${CLAUDE_COMMAND_CANDIDATES.join(', ')})`);
|
|
43
|
+
}
|
|
15
44
|
/**
|
|
16
45
|
* Check if the `claude` binary is installed (regardless of auth state).
|
|
17
46
|
*/
|
|
18
47
|
export function isClaudeBinaryInstalled() {
|
|
19
48
|
try {
|
|
20
|
-
|
|
49
|
+
execClaudeCheck(['--version']);
|
|
21
50
|
return true;
|
|
22
51
|
}
|
|
23
52
|
catch {
|
|
@@ -29,13 +58,13 @@ export function isClaudeBinaryInstalled() {
|
|
|
29
58
|
*/
|
|
30
59
|
export function isClaudeCliReady() {
|
|
31
60
|
try {
|
|
32
|
-
|
|
61
|
+
execClaudeCheck(['--version']);
|
|
33
62
|
}
|
|
34
63
|
catch {
|
|
35
64
|
return false;
|
|
36
65
|
}
|
|
37
66
|
try {
|
|
38
|
-
const output =
|
|
67
|
+
const output = execClaudeCheck(['auth', 'status'])
|
|
39
68
|
.toString()
|
|
40
69
|
.toLowerCase();
|
|
41
70
|
return !(/not logged in|no credentials|unauthenticated|not authenticated/i.test(output));
|
package/dist/mcp-server.d.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Minimal tool interface matching GSD's AgentTool shape.
|
|
3
3
|
* Avoids a direct dependency on @gsd/pi-agent-core from this compiled module.
|
|
4
|
+
*
|
|
5
|
+
* `details` and `isError` are optional fields that runtime tool implementations
|
|
6
|
+
* may populate. The MCP transport drops non-standard fields, so the wrapper at
|
|
7
|
+
* the call site mirrors `details` into `structuredContent` and forwards
|
|
8
|
+
* `isError` directly. See #4472.
|
|
4
9
|
*/
|
|
5
10
|
export interface McpToolDef {
|
|
6
11
|
name: string;
|
|
@@ -13,6 +18,8 @@ export interface McpToolDef {
|
|
|
13
18
|
data?: string;
|
|
14
19
|
mimeType?: string;
|
|
15
20
|
}>;
|
|
21
|
+
details?: Record<string, unknown>;
|
|
22
|
+
isError?: boolean;
|
|
16
23
|
}>;
|
|
17
24
|
}
|
|
18
25
|
/**
|
package/dist/mcp-server.js
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strict plain-object guard. True only for object literals and
|
|
3
|
+
* `Object.create(null)` — not for `Date`, `URL`, `Map`, `Set`, class instances,
|
|
4
|
+
* or arrays. Used to gate `structuredContent` forwarding so the MCP transport
|
|
5
|
+
* receives only true JSON objects (the protocol contract). See #4477 review.
|
|
6
|
+
*
|
|
7
|
+
* Mirrored in `packages/mcp-server/src/workflow-tools.ts` for the
|
|
8
|
+
* `adaptExecutorResult` adapter on the workflow path. Keep both copies in
|
|
9
|
+
* sync if the contract definition needs to evolve.
|
|
10
|
+
*/
|
|
11
|
+
function isPlainObject(value) {
|
|
12
|
+
if (value === null || typeof value !== 'object')
|
|
13
|
+
return false;
|
|
14
|
+
if (Array.isArray(value))
|
|
15
|
+
return false;
|
|
16
|
+
const proto = Object.getPrototypeOf(value);
|
|
17
|
+
return proto === null || proto === Object.prototype;
|
|
18
|
+
}
|
|
1
19
|
// MCP SDK subpath imports use wildcard exports (./*) in @modelcontextprotocol/sdk's
|
|
2
20
|
// package.json export map. The wildcard maps "./foo" → "./dist/cjs/foo" (no .js
|
|
3
21
|
// suffix), so bare subpath specifiers like `${MCP_PKG}/server/stdio` resolve to
|
|
@@ -83,7 +101,23 @@ export async function startMcpServer(options) {
|
|
|
83
101
|
// by stringifying into a text block so clients see the payload.
|
|
84
102
|
return { type: 'text', text: JSON.stringify(block) };
|
|
85
103
|
});
|
|
86
|
-
|
|
104
|
+
// Forward a tool's runtime `details` field to MCP's `structuredContent`
|
|
105
|
+
// channel. The protocol drops non-standard fields on the wire, so tools
|
|
106
|
+
// that populate `details` for client-side renderers (e.g. save_gate_result)
|
|
107
|
+
// would otherwise arrive empty on the other side. See #4472.
|
|
108
|
+
//
|
|
109
|
+
// Use a strict plain-object guard (prototype-chain check) rather than just
|
|
110
|
+
// `typeof === 'object' && !Array.isArray()` — Date, URL, Map, Set, and
|
|
111
|
+
// class instances would otherwise pass through and end up as
|
|
112
|
+
// `structuredContent`, violating the protocol's JSON-object contract.
|
|
113
|
+
// The mirror discipline applies in `workflow-tools.ts adaptExecutorResult`.
|
|
114
|
+
const base = { content };
|
|
115
|
+
if (isPlainObject(result.details)) {
|
|
116
|
+
base.structuredContent = result.details;
|
|
117
|
+
}
|
|
118
|
+
if (result.isError === true)
|
|
119
|
+
base.isError = true;
|
|
120
|
+
return base;
|
|
87
121
|
}
|
|
88
122
|
catch (err) {
|
|
89
123
|
// AbortError from a cancelled tool surfaces as a normal error — MCP
|
|
@@ -21,6 +21,6 @@ export declare function getNewerManagedResourceVersion(agentDir: string, current
|
|
|
21
21
|
*
|
|
22
22
|
* Inspectable: `ls ~/.gsd/agent/extensions/`
|
|
23
23
|
*/
|
|
24
|
-
export declare function initResources(agentDir: string): void;
|
|
24
|
+
export declare function initResources(agentDir: string, skillsDir?: string): void;
|
|
25
25
|
export declare function hasStaleCompiledExtensionSiblings(extensionsDir: string, sourceDir?: string): boolean;
|
|
26
26
|
export declare function buildResourceLoader(agentDir: string): DefaultResourceLoader;
|
package/dist/resource-loader.js
CHANGED
|
@@ -506,7 +506,7 @@ function pruneRemovedBundledExtensions(manifest, agentDir) {
|
|
|
506
506
|
*
|
|
507
507
|
* Inspectable: `ls ~/.gsd/agent/extensions/`
|
|
508
508
|
*/
|
|
509
|
-
export function initResources(agentDir) {
|
|
509
|
+
export function initResources(agentDir, skillsDir = join(homedir(), '.agents', 'skills')) {
|
|
510
510
|
mkdirSync(agentDir, { recursive: true });
|
|
511
511
|
const currentVersion = getBundledGsdVersion();
|
|
512
512
|
const manifest = readManagedResourceManifest(agentDir);
|
|
@@ -538,13 +538,7 @@ export function initResources(agentDir) {
|
|
|
538
538
|
// Sync bundled resources — overwrite so updates land on next launch.
|
|
539
539
|
syncResourceDir(bundledExtensionsDir, join(agentDir, 'extensions'));
|
|
540
540
|
syncResourceDir(join(resourcesDir, 'agents'), join(agentDir, 'agents'));
|
|
541
|
-
|
|
542
|
-
// skills.sh CLI (`npx skills add <repo>`) into ~/.agents/skills/ which
|
|
543
|
-
// is the industry-standard Agent Skills ecosystem directory.
|
|
544
|
-
//
|
|
545
|
-
// Migration from the legacy ~/.gsd/agent/skills/ directory is handled
|
|
546
|
-
// above the manifest check so it runs on every launch (including retries
|
|
547
|
-
// after partial copy failures).
|
|
541
|
+
syncResourceDir(join(resourcesDir, 'skills'), skillsDir);
|
|
548
542
|
// Sync GSD-WORKFLOW.md to agentDir as a fallback for when GSD_WORKFLOW_PATH
|
|
549
543
|
// env var is not set (e.g. fork/dev builds, alternative entry points).
|
|
550
544
|
const workflowSrc = join(resourcesDir, 'GSD-WORKFLOW.md');
|
|
@@ -18,10 +18,11 @@ import { execFileSync } from "node:child_process";
|
|
|
18
18
|
const CLAUDE_COMMAND = process.platform === "win32" ? "claude.cmd" : "claude";
|
|
19
19
|
/**
|
|
20
20
|
* Windows installs vary: some environments expose `claude.cmd` (npm shim),
|
|
21
|
-
*
|
|
22
|
-
* Try
|
|
21
|
+
* `claude.exe` (direct binary install), or a bare `claude` shim on PATH
|
|
22
|
+
* (for example Git Bash wrappers). Try all three to avoid false "not
|
|
23
|
+
* installed" results in readiness checks.
|
|
23
24
|
*/
|
|
24
|
-
const CLAUDE_COMMAND_CANDIDATES = process.platform === "win32" ? [CLAUDE_COMMAND, "claude"] : [CLAUDE_COMMAND];
|
|
25
|
+
const CLAUDE_COMMAND_CANDIDATES = process.platform === "win32" ? [CLAUDE_COMMAND, "claude.exe", "claude"] : [CLAUDE_COMMAND];
|
|
25
26
|
function execClaude(args) {
|
|
26
27
|
let lastError;
|
|
27
28
|
for (const command of CLAUDE_COMMAND_CANDIDATES) {
|
|
@@ -484,25 +484,23 @@ export function makeAbortedMessage(model, lastTextContent) {
|
|
|
484
484
|
/**
|
|
485
485
|
* Resolve the Claude Code permission mode for the current run.
|
|
486
486
|
*
|
|
487
|
-
*
|
|
488
|
-
*
|
|
489
|
-
*
|
|
490
|
-
*
|
|
491
|
-
*
|
|
492
|
-
*
|
|
493
|
-
* users opt into a stricter mode (`acceptEdits`, `default`, `plan`).
|
|
487
|
+
* Defaults to `acceptEdits`, which auto-approves file reads/edits but
|
|
488
|
+
* surfaces a permission dialog for dangerous operations (e.g. general Bash,
|
|
489
|
+
* Agent, WebFetch). This prevents tools outside the allowlist from being
|
|
490
|
+
* silently denied — the SDK emits an `extension_ui_request` event so the
|
|
491
|
+
* user sees a prompt instead of a silent refusal that Claude Code mistakes
|
|
492
|
+
* for user rejection (#4383).
|
|
494
493
|
*
|
|
495
|
-
*
|
|
496
|
-
*
|
|
497
|
-
*
|
|
498
|
-
* (#4099) is continuous approval fatigue that blocks real work.
|
|
494
|
+
* Set `GSD_CLAUDE_CODE_PERMISSION_MODE` to `bypassPermissions` to restore
|
|
495
|
+
* the old always-approve behaviour, or to `default` / `plan` for stricter
|
|
496
|
+
* modes.
|
|
499
497
|
*/
|
|
500
498
|
export async function resolveClaudePermissionMode(env = process.env) {
|
|
501
499
|
const override = env.GSD_CLAUDE_CODE_PERMISSION_MODE?.trim();
|
|
502
500
|
if (override === "bypassPermissions" || override === "acceptEdits" || override === "default" || override === "plan") {
|
|
503
501
|
return override;
|
|
504
502
|
}
|
|
505
|
-
return "
|
|
503
|
+
return "acceptEdits";
|
|
506
504
|
}
|
|
507
505
|
// NOTE: These helpers intentionally mirror @gsd/pi-ai anthropic-shared
|
|
508
506
|
// behavior so this extension remains typecheck-stable even when the published
|
|
@@ -554,7 +552,7 @@ function mapThinkingLevelToAnthropicEffort(level, modelId) {
|
|
|
554
552
|
export function buildSdkOptions(modelId, prompt, overrides, extraOptions = {}) {
|
|
555
553
|
const { reasoning, ...sdkExtraOptions } = extraOptions;
|
|
556
554
|
const mcpServers = buildWorkflowMcpServers();
|
|
557
|
-
const permissionMode = overrides?.permissionMode ?? "
|
|
555
|
+
const permissionMode = overrides?.permissionMode ?? "acceptEdits";
|
|
558
556
|
const disallowedTools = ["AskUserQuestion"];
|
|
559
557
|
// Pre-authorize the safe built-ins and every registered workflow MCP
|
|
560
558
|
// server's tools. `acceptEdits` mode (the interactive default) only
|
|
@@ -637,6 +635,68 @@ function normalizeToolResultContent(content) {
|
|
|
637
635
|
}
|
|
638
636
|
return blocks.length > 0 ? blocks : [{ type: "text", text: "" }];
|
|
639
637
|
}
|
|
638
|
+
/**
|
|
639
|
+
* Extract a `details` payload from an MCP tool-result block.
|
|
640
|
+
*
|
|
641
|
+
* MCP's `CallToolResult` carries structured data in `structuredContent` — the
|
|
642
|
+
* protocol's supported channel for non-text payloads. Claude Code's synthetic
|
|
643
|
+
* user message may surface that field in one of two shapes depending on SDK
|
|
644
|
+
* version: as a sibling on the `mcp_tool_result` block itself, or as a
|
|
645
|
+
* dedicated content sub-block with `type: "structuredContent"`. Snake-case
|
|
646
|
+
* (`structured_content`) is accepted defensively in case a transport hop
|
|
647
|
+
* rewrites casing. All other shapes fall back to an empty object so callers
|
|
648
|
+
* can rely on `details` being present.
|
|
649
|
+
*/
|
|
650
|
+
function extractStructuredDetailsFromBlock(block) {
|
|
651
|
+
const sibling = block.structuredContent ?? block.structured_content;
|
|
652
|
+
if (sibling && typeof sibling === "object" && !Array.isArray(sibling)) {
|
|
653
|
+
return sibling;
|
|
654
|
+
}
|
|
655
|
+
if (Array.isArray(block.content)) {
|
|
656
|
+
for (const item of block.content) {
|
|
657
|
+
if (!item || typeof item !== "object")
|
|
658
|
+
continue;
|
|
659
|
+
const sub = item;
|
|
660
|
+
if (sub.type !== "structuredContent" && sub.type !== "structured_content")
|
|
661
|
+
continue;
|
|
662
|
+
const payload = sub.structuredContent ?? sub.structured_content ?? sub.data ?? sub.value;
|
|
663
|
+
if (payload && typeof payload === "object" && !Array.isArray(payload)) {
|
|
664
|
+
return payload;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
// Return undefined (not {}) when no structured payload is present, matching
|
|
669
|
+
// the pre-#4477 contract where `details` was nullable. An empty-object
|
|
670
|
+
// sentinel is truthy and breaks downstream consumers that gate on
|
|
671
|
+
// `if (details)`. `undefined` matches the type of the field these results
|
|
672
|
+
// flow into (`Record<string, unknown> | undefined`).
|
|
673
|
+
return undefined;
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* True for items that are MCP `structuredContent` pseudo-blocks living inside
|
|
677
|
+
* a tool-result `content[]` array. These blocks carry the structured payload
|
|
678
|
+
* (extracted separately by `extractStructuredDetailsFromBlock`) and must NOT
|
|
679
|
+
* leak into the visible content rendered to the user — otherwise the renderer
|
|
680
|
+
* stringifies the JSON pseudo-block and shows it next to the actual tool
|
|
681
|
+
* output. See PR #4477 review (CodeRabbit, post-fix-round).
|
|
682
|
+
*/
|
|
683
|
+
function isStructuredContentPseudoBlock(item) {
|
|
684
|
+
if (!item || typeof item !== "object")
|
|
685
|
+
return false;
|
|
686
|
+
const type = item.type;
|
|
687
|
+
return type === "structuredContent" || type === "structured_content";
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Strip `structuredContent` pseudo-blocks from a tool-result content array
|
|
691
|
+
* before normalization. The structured payload is extracted via the sibling
|
|
692
|
+
* `structuredContent` field (or a dedicated extractor pass on the raw block);
|
|
693
|
+
* the visible content path must not include the pseudo-block itself.
|
|
694
|
+
*/
|
|
695
|
+
function stripStructuredContentPseudoBlocks(content) {
|
|
696
|
+
if (!Array.isArray(content))
|
|
697
|
+
return content;
|
|
698
|
+
return content.filter((item) => !isStructuredContentPseudoBlock(item));
|
|
699
|
+
}
|
|
640
700
|
/** Extract tool result payloads from an SDK synthetic user message, keyed by tool-use ID. */
|
|
641
701
|
export function extractToolResultsFromSdkUserMessage(message) {
|
|
642
702
|
const extracted = [];
|
|
@@ -657,8 +717,8 @@ export function extractToolResultsFromSdkUserMessage(message) {
|
|
|
657
717
|
extracted.push({
|
|
658
718
|
toolUseId,
|
|
659
719
|
result: {
|
|
660
|
-
content: normalizeToolResultContent(block.content),
|
|
661
|
-
details:
|
|
720
|
+
content: normalizeToolResultContent(stripStructuredContentPseudoBlocks(block.content)),
|
|
721
|
+
details: extractStructuredDetailsFromBlock(block),
|
|
662
722
|
isError: block.is_error === true,
|
|
663
723
|
},
|
|
664
724
|
});
|
|
@@ -672,8 +732,8 @@ export function extractToolResultsFromSdkUserMessage(message) {
|
|
|
672
732
|
extracted.push({
|
|
673
733
|
toolUseId,
|
|
674
734
|
result: {
|
|
675
|
-
content: normalizeToolResultContent(toolResult.content),
|
|
676
|
-
details:
|
|
735
|
+
content: normalizeToolResultContent(stripStructuredContentPseudoBlocks(toolResult.content)),
|
|
736
|
+
details: extractStructuredDetailsFromBlock(toolResult),
|
|
677
737
|
isError: toolResult.is_error === true,
|
|
678
738
|
},
|
|
679
739
|
});
|
|
@@ -30,7 +30,8 @@ import { isDbAvailable, getMilestoneSlices } from "../gsd-db.js";
|
|
|
30
30
|
import { ensurePlanV2Graph } from "../uok/plan-v2.js";
|
|
31
31
|
import { resolveUokFlags } from "../uok/flags.js";
|
|
32
32
|
import { UokGateRunner } from "../uok/gate-runner.js";
|
|
33
|
-
import { resetEvidence } from "../safety/evidence-collector.js";
|
|
33
|
+
import { resetEvidence, loadEvidenceFromDisk } from "../safety/evidence-collector.js";
|
|
34
|
+
import { parseUnitId } from "../unit-id.js";
|
|
34
35
|
import { createCheckpoint, cleanupCheckpoint, rollbackToCheckpoint } from "../safety/git-checkpoint.js";
|
|
35
36
|
import { resolveSafetyHarnessConfig } from "../safety/safety-harness.js";
|
|
36
37
|
import { getWorkflowTransportSupportError, getRequiredWorkflowToolsForAutoUnit, supportsStructuredQuestions, } from "../workflow-mcp.js";
|
|
@@ -389,6 +390,8 @@ export async function runPreDispatch(ic, loopState) {
|
|
|
389
390
|
loopState.recentUnits.length = 0;
|
|
390
391
|
loopState.stuckRecoveryAttempts = 0;
|
|
391
392
|
// Worktree lifecycle on milestone transition — merge current, enter next
|
|
393
|
+
// #2909: preflight — warn + stash dirty working tree before merge
|
|
394
|
+
const preflightTransition = deps.preflightCleanRoot(s.originalBasePath || s.basePath, s.currentMilestoneId, ctx.ui.notify.bind(ctx.ui));
|
|
392
395
|
try {
|
|
393
396
|
deps.resolver.mergeAndExit(s.currentMilestoneId, ctx.ui);
|
|
394
397
|
}
|
|
@@ -405,6 +408,10 @@ export async function runPreDispatch(ic, loopState) {
|
|
|
405
408
|
await deps.stopAuto(ctx, pi, `Merge error on milestone ${s.currentMilestoneId}: ${String(mergeErr)}`);
|
|
406
409
|
return { action: "break", reason: "merge-failed" };
|
|
407
410
|
}
|
|
411
|
+
// #2909: postflight — restore stashed changes after successful merge
|
|
412
|
+
if (preflightTransition.stashPushed) {
|
|
413
|
+
deps.postflightPopStash(s.originalBasePath || s.basePath, s.currentMilestoneId, ctx.ui.notify.bind(ctx.ui));
|
|
414
|
+
}
|
|
408
415
|
// PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
|
|
409
416
|
deps.invalidateAllCaches();
|
|
410
417
|
state = await deps.deriveState(s.basePath);
|
|
@@ -459,6 +466,8 @@ export async function runPreDispatch(ic, loopState) {
|
|
|
459
466
|
if (incomplete.length === 0 && state.registry.length > 0) {
|
|
460
467
|
// All milestones complete — merge milestone branch before stopping
|
|
461
468
|
if (s.currentMilestoneId) {
|
|
469
|
+
// #2909: preflight — warn + stash dirty working tree before merge
|
|
470
|
+
const preflightAllComplete = deps.preflightCleanRoot(s.originalBasePath || s.basePath, s.currentMilestoneId, ctx.ui.notify.bind(ctx.ui));
|
|
462
471
|
try {
|
|
463
472
|
deps.resolver.mergeAndExit(s.currentMilestoneId, ctx.ui);
|
|
464
473
|
// Prevent stopAuto from attempting the same merge (#2645)
|
|
@@ -475,6 +484,10 @@ export async function runPreDispatch(ic, loopState) {
|
|
|
475
484
|
await deps.stopAuto(ctx, pi, `Merge error on milestone ${s.currentMilestoneId}: ${String(mergeErr)}`);
|
|
476
485
|
return { action: "break", reason: "merge-failed" };
|
|
477
486
|
}
|
|
487
|
+
// #2909: postflight — restore stashed changes after successful merge
|
|
488
|
+
if (preflightAllComplete.stashPushed) {
|
|
489
|
+
deps.postflightPopStash(s.originalBasePath || s.basePath, s.currentMilestoneId, ctx.ui.notify.bind(ctx.ui));
|
|
490
|
+
}
|
|
478
491
|
// PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
|
|
479
492
|
}
|
|
480
493
|
deps.sendDesktopNotification("GSD", "All milestones complete!", "success", "milestone", basename(s.originalBasePath || s.basePath));
|
|
@@ -539,6 +552,8 @@ export async function runPreDispatch(ic, loopState) {
|
|
|
539
552
|
if (state.phase === "complete") {
|
|
540
553
|
// Milestone merge on complete (before closeout so branch state is clean)
|
|
541
554
|
if (s.currentMilestoneId) {
|
|
555
|
+
// #2909: preflight — warn + stash dirty working tree before merge
|
|
556
|
+
const preflightComplete = deps.preflightCleanRoot(s.originalBasePath || s.basePath, s.currentMilestoneId, ctx.ui.notify.bind(ctx.ui));
|
|
542
557
|
try {
|
|
543
558
|
deps.resolver.mergeAndExit(s.currentMilestoneId, ctx.ui);
|
|
544
559
|
// Prevent stopAuto from attempting the same merge (#2645)
|
|
@@ -555,6 +570,10 @@ export async function runPreDispatch(ic, loopState) {
|
|
|
555
570
|
await deps.stopAuto(ctx, pi, `Merge error on milestone ${s.currentMilestoneId}: ${String(mergeErr)}`);
|
|
556
571
|
return { action: "break", reason: "merge-failed" };
|
|
557
572
|
}
|
|
573
|
+
// #2909: postflight — restore stashed changes after successful merge
|
|
574
|
+
if (preflightComplete.stashPushed) {
|
|
575
|
+
deps.postflightPopStash(s.originalBasePath || s.basePath, s.currentMilestoneId, ctx.ui.notify.bind(ctx.ui));
|
|
576
|
+
}
|
|
558
577
|
// PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
|
|
559
578
|
}
|
|
560
579
|
deps.sendDesktopNotification("GSD", `Milestone ${mid} complete!`, "success", "milestone", basename(s.originalBasePath || s.basePath));
|
|
@@ -1028,6 +1047,14 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
1028
1047
|
const safetyConfig = resolveSafetyHarnessConfig(prefs?.safety_harness);
|
|
1029
1048
|
if (safetyConfig.enabled && safetyConfig.evidence_collection) {
|
|
1030
1049
|
resetEvidence();
|
|
1050
|
+
// Restore persisted evidence so session-restart resumes don't produce
|
|
1051
|
+
// false-positive "no bash calls" warnings (Bug #4385).
|
|
1052
|
+
if (s.basePath && unitType === "execute-task") {
|
|
1053
|
+
const { milestone: eMid, slice: eSid, task: eTid } = parseUnitId(unitId);
|
|
1054
|
+
if (eMid && eSid && eTid) {
|
|
1055
|
+
loadEvidenceFromDisk(s.basePath, eMid, eSid, eTid);
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1031
1058
|
}
|
|
1032
1059
|
// Only checkpoint code-executing units (not lifecycle/planning units)
|
|
1033
1060
|
if (safetyConfig.enabled && safetyConfig.checkpoints && unitType === "execute-task") {
|
|
@@ -1430,6 +1457,20 @@ export async function runFinalize(ic, iterData, loopState, sidecarItem) {
|
|
|
1430
1457
|
}
|
|
1431
1458
|
else {
|
|
1432
1459
|
// s.pendingVerificationRetry was set by postUnitPreVerification.
|
|
1460
|
+
// Emit a dedicated journal event so forensics can distinguish bounded
|
|
1461
|
+
// verification retries from genuine stuck-loop dispatch repetitions (#4540).
|
|
1462
|
+
const retryInfo = s.pendingVerificationRetry;
|
|
1463
|
+
deps.emitJournalEvent({
|
|
1464
|
+
ts: new Date().toISOString(),
|
|
1465
|
+
flowId: ic.flowId,
|
|
1466
|
+
seq: ic.nextSeq(),
|
|
1467
|
+
eventType: "artifact-verification-retry",
|
|
1468
|
+
data: {
|
|
1469
|
+
unitType: preUnitSnapshot?.type,
|
|
1470
|
+
unitId: retryInfo?.unitId,
|
|
1471
|
+
attempt: retryInfo?.attempt,
|
|
1472
|
+
},
|
|
1473
|
+
});
|
|
1433
1474
|
// Continue the loop — next iteration will inject the retry context into the prompt.
|
|
1434
1475
|
debugLog("autoLoop", { phase: "artifact-verification-retry", iteration: ic.iteration });
|
|
1435
1476
|
return { action: "continue" };
|
|
@@ -92,6 +92,33 @@ export async function runUnit(ctx, pi, s, unitType, unitId, prompt) {
|
|
|
92
92
|
catch (e) {
|
|
93
93
|
logWarning("engine", "Failed to chdir to basePath before dispatch", { basePath: s.basePath, error: String(e) });
|
|
94
94
|
}
|
|
95
|
+
// ── Provider request-readiness pre-check (#4555) ──
|
|
96
|
+
// Verify the provider can accept requests before dispatching. If the token
|
|
97
|
+
// has expired since bootstrap, return cancelled immediately so the unit is
|
|
98
|
+
// not wasted on a guaranteed 401.
|
|
99
|
+
{
|
|
100
|
+
const provider = s.currentUnitModel?.provider ?? ctx.model?.provider;
|
|
101
|
+
const registry = ctx.modelRegistry;
|
|
102
|
+
if (provider && registry != null && typeof registry.isProviderRequestReady === "function") {
|
|
103
|
+
let ready = false;
|
|
104
|
+
try {
|
|
105
|
+
ready = registry.isProviderRequestReady(provider);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
ready = false;
|
|
109
|
+
}
|
|
110
|
+
if (!ready) {
|
|
111
|
+
return {
|
|
112
|
+
status: "cancelled",
|
|
113
|
+
errorContext: {
|
|
114
|
+
message: `Provider ${provider} is not request-ready (login/token expired)`,
|
|
115
|
+
category: "provider",
|
|
116
|
+
isTransient: false,
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
95
122
|
// ── Send the prompt ──
|
|
96
123
|
debugLog("runUnit", { phase: "send-message", unitType, unitId });
|
|
97
124
|
pi.sendMessage({ customType: "gsd-auto", content: prompt, display: s.verbose }, { triggerTurn: true });
|
|
@@ -79,6 +79,17 @@ export class AutoSession {
|
|
|
79
79
|
lastStateRebuildAt = 0;
|
|
80
80
|
// ── Sidecar queue ─────────────────────────────────────────────────────
|
|
81
81
|
sidecarQueue = [];
|
|
82
|
+
// ── Pre-exec gate failure context (#4551) ───────────────────────────
|
|
83
|
+
/**
|
|
84
|
+
* Persisted when a pre-execution gate fails on a plan-slice or refine-slice
|
|
85
|
+
* unit. The planning → plan-slice dispatch rule reads this field and injects
|
|
86
|
+
* the failure details into the next re-dispatch prompt so the LLM can fix the
|
|
87
|
+
* specific issues instead of producing an identical plan.
|
|
88
|
+
*
|
|
89
|
+
* Cleared after it has been consumed (injected into the prompt) to avoid
|
|
90
|
+
* stale context bleeding into unrelated slices.
|
|
91
|
+
*/
|
|
92
|
+
lastPreExecFailure = null;
|
|
82
93
|
// ── Tool invocation errors (#2883) ──────────────────────────────────
|
|
83
94
|
/** Set when a GSD tool execution ends with isError due to malformed/truncated
|
|
84
95
|
* JSON arguments. Checked by postUnitPreVerification to break retry loops. */
|
|
@@ -199,6 +210,7 @@ export class AutoSession {
|
|
|
199
210
|
this.sidecarQueue = [];
|
|
200
211
|
this.rewriteAttemptCount = 0;
|
|
201
212
|
this.consecutiveCompleteBootstraps = 0;
|
|
213
|
+
this.lastPreExecFailure = null;
|
|
202
214
|
this.lastToolInvocationError = null;
|
|
203
215
|
this.lastGitActionFailure = null;
|
|
204
216
|
this.lastGitActionStatus = null;
|
|
@@ -451,18 +451,31 @@ export const DISPATCH_RULES = [
|
|
|
451
451
|
},
|
|
452
452
|
{
|
|
453
453
|
name: "planning → plan-slice",
|
|
454
|
-
match: async ({ state, mid, midTitle, basePath, sessionContextWindow, modelRegistry }) => {
|
|
454
|
+
match: async ({ state, mid, midTitle, basePath, sessionContextWindow, modelRegistry, session }) => {
|
|
455
455
|
if (state.phase !== "planning")
|
|
456
456
|
return null;
|
|
457
457
|
if (!state.activeSlice)
|
|
458
458
|
return missingSliceStop(mid, state.phase);
|
|
459
459
|
const sid = state.activeSlice.id;
|
|
460
460
|
const sTitle = state.activeSlice.title;
|
|
461
|
+
// #4551: Consume any persisted pre-exec failure for this slice so the
|
|
462
|
+
// re-dispatched prompt includes the exact blocked references. Clear the
|
|
463
|
+
// field immediately after reading to prevent stale context leaking into
|
|
464
|
+
// a later, unrelated plan-slice run.
|
|
465
|
+
const unitId = `${mid}/${sid}`;
|
|
466
|
+
let priorPreExecFailure;
|
|
467
|
+
if (session?.lastPreExecFailure?.unitId === unitId) {
|
|
468
|
+
priorPreExecFailure = {
|
|
469
|
+
blockingFindings: session.lastPreExecFailure.blockingFindings,
|
|
470
|
+
verdictExcerpt: session.lastPreExecFailure.verdictExcerpt,
|
|
471
|
+
};
|
|
472
|
+
session.lastPreExecFailure = null;
|
|
473
|
+
}
|
|
461
474
|
return {
|
|
462
475
|
action: "dispatch",
|
|
463
476
|
unitType: "plan-slice",
|
|
464
|
-
unitId
|
|
465
|
-
prompt: await buildPlanSlicePrompt(mid, midTitle, sid, sTitle, basePath, undefined, { sessionContextWindow, modelRegistry }),
|
|
477
|
+
unitId,
|
|
478
|
+
prompt: await buildPlanSlicePrompt(mid, midTitle, sid, sTitle, basePath, undefined, { sessionContextWindow, modelRegistry, priorPreExecFailure }),
|
|
466
479
|
};
|
|
467
480
|
},
|
|
468
481
|
},
|
|
@@ -291,7 +291,7 @@ autoModeStartThinkingLevel) {
|
|
|
291
291
|
// ADR-005: Adjust active tool set for the selected model's provider capabilities.
|
|
292
292
|
// Hard-filter incompatible tools, then let extensions override via adjust_tool_set hook.
|
|
293
293
|
const activeToolNames = pi.getActiveTools();
|
|
294
|
-
const { toolNames: compatibleTools, removedTools } = adjustToolSet(activeToolNames, model.api);
|
|
294
|
+
const { toolNames: compatibleTools, removedTools } = adjustToolSet(activeToolNames, model.api, model.provider);
|
|
295
295
|
let finalToolNames = compatibleTools;
|
|
296
296
|
// Fire adjust_tool_set hook — extensions can override the filtered tool set
|
|
297
297
|
if (routingConfig.hooks !== false) {
|
|
@@ -30,7 +30,7 @@ import { checkPostUnitHooks, isRetryPending, consumeRetryTrigger, persistHookSta
|
|
|
30
30
|
import { hasPendingCaptures, loadPendingCaptures, revertExecutorResolvedCaptures } from "./captures.js";
|
|
31
31
|
import { debugLog } from "./debug-logger.js";
|
|
32
32
|
import { runSafely } from "./auto-utils.js";
|
|
33
|
-
import { getEvidence } from "./safety/evidence-collector.js";
|
|
33
|
+
import { getEvidence, clearEvidenceFromDisk } from "./safety/evidence-collector.js";
|
|
34
34
|
import { validateFileChanges } from "./safety/file-change-validator.js";
|
|
35
35
|
// crossReferenceEvidence available for future use when verification_evidence is stored in DB
|
|
36
36
|
// import { crossReferenceEvidence, type ClaimedEvidence } from "./safety/evidence-cross-ref.js";
|
|
@@ -535,7 +535,7 @@ export async function postUnitPreVerification(pctx, opts) {
|
|
|
535
535
|
if (taskRow) {
|
|
536
536
|
const expectedOutput = taskRow.expected_output ?? [];
|
|
537
537
|
const plannedFiles = taskRow.files ?? [];
|
|
538
|
-
const audit = validateFileChanges(s.basePath, expectedOutput, plannedFiles);
|
|
538
|
+
const audit = validateFileChanges(s.basePath, expectedOutput, plannedFiles, safetyConfig.file_change_allowlist);
|
|
539
539
|
if (audit && audit.violations.length > 0) {
|
|
540
540
|
const warnings = audit.violations.filter(v => v.severity === "warning");
|
|
541
541
|
for (const v of warnings) {
|
|
@@ -588,6 +588,16 @@ export async function postUnitPreVerification(pctx, opts) {
|
|
|
588
588
|
debugLog("postUnit", { phase: "safety-content-validation", error: String(e) });
|
|
589
589
|
}
|
|
590
590
|
}
|
|
591
|
+
// Clear persisted evidence file now that post-unit processing is complete
|
|
592
|
+
// (Bug #4385 — prevents stale evidence from affecting retries of same unit ID).
|
|
593
|
+
if (safetyConfig.evidence_collection && s.currentUnit.type === "execute-task" && sMid && sSid && sTid) {
|
|
594
|
+
try {
|
|
595
|
+
clearEvidenceFromDisk(s.basePath, sMid, sSid, sTid);
|
|
596
|
+
}
|
|
597
|
+
catch (e) {
|
|
598
|
+
debugLog("postUnit", { phase: "safety-evidence-clear", error: String(e) });
|
|
599
|
+
}
|
|
600
|
+
}
|
|
591
601
|
}
|
|
592
602
|
}
|
|
593
603
|
catch (e) {
|
|
@@ -950,12 +960,25 @@ export async function postUnitPostVerification(pctx) {
|
|
|
950
960
|
const suffix = blockingChecks.length > 3 ? `\n \u2022 ...and ${blockingChecks.length - 3} more` : "";
|
|
951
961
|
const evidenceNote = `\nSee ${sid}-PRE-EXEC-VERIFY.json for full details.`;
|
|
952
962
|
ctx.ui.notify(`Pre-execution checks failed: ${blockingCount} blocking issue${blockingCount === 1 ? "" : "s"} found\n${details}${suffix}${evidenceNote}`, "error");
|
|
963
|
+
// Persist failure context so the next plan-slice re-dispatch can inject
|
|
964
|
+
// it into the prompt and break the infinite loop (#4551).
|
|
965
|
+
s.lastPreExecFailure = {
|
|
966
|
+
unitId: currentUnit.id,
|
|
967
|
+
blockingFindings: blockingChecks.map(c => `[${c.category}] ${c.target}: ${c.message}`),
|
|
968
|
+
verdictExcerpt: `status=${result.status}; ${blockingCount} blocking issue${blockingCount === 1 ? "" : "s"} detected`,
|
|
969
|
+
};
|
|
953
970
|
preExecPauseNeeded = true;
|
|
954
971
|
}
|
|
955
972
|
else if (result.status === "warn") {
|
|
956
973
|
ctx.ui.notify(`Pre-execution checks passed with warnings`, "warning");
|
|
957
974
|
// Strict mode: treat warnings as blocking
|
|
958
975
|
if (prefs?.enhanced_verification_strict === true) {
|
|
976
|
+
const warnChecks = result.checks.filter(c => !c.passed);
|
|
977
|
+
s.lastPreExecFailure = {
|
|
978
|
+
unitId: currentUnit.id,
|
|
979
|
+
blockingFindings: warnChecks.map(c => `[${c.category}] ${c.target}: ${c.message}`),
|
|
980
|
+
verdictExcerpt: `status=${result.status} (strict mode); ${warnChecks.length} warning${warnChecks.length === 1 ? "" : "s"} treated as blocking`,
|
|
981
|
+
};
|
|
959
982
|
preExecPauseNeeded = true;
|
|
960
983
|
}
|
|
961
984
|
}
|
|
@@ -1223,6 +1223,20 @@ export async function buildPlanSlicePrompt(mid, _midTitle, sid, sTitle, base, le
|
|
|
1223
1223
|
prependBlocks.push(`## Prior Sketch Scope (soft hint — non-binding)\n\n${options.softScopeHint.trim()}\n\n` +
|
|
1224
1224
|
`This scope was captured during an earlier progressive-planning pass that was later disabled. Treat it as context only — you may plan beyond it if the work genuinely requires more scope. Do NOT treat this as a hard boundary.`);
|
|
1225
1225
|
}
|
|
1226
|
+
// #4551: inject pre-exec failure context so the re-dispatched plan-slice
|
|
1227
|
+
// addresses the exact blocked references rather than reproducing the same plan.
|
|
1228
|
+
if (options?.priorPreExecFailure) {
|
|
1229
|
+
const { blockingFindings, verdictExcerpt } = options.priorPreExecFailure;
|
|
1230
|
+
const findingsList = blockingFindings.length > 0
|
|
1231
|
+
? blockingFindings.map(f => `- ${f}`).join("\n")
|
|
1232
|
+
: "- (no specific findings recorded)";
|
|
1233
|
+
prependBlocks.push(`## Fix these specific issues from the prior pre-exec check\n\n` +
|
|
1234
|
+
`The previous plan-slice attempt was blocked by pre-execution validation.\n` +
|
|
1235
|
+
`Gate verdict: ${verdictExcerpt}\n\n` +
|
|
1236
|
+
`Blocked references that must be resolved in this plan:\n${findingsList}\n\n` +
|
|
1237
|
+
`Revise the plan so that every reference listed above is satisfied before execution begins. ` +
|
|
1238
|
+
`Do not reproduce the same file paths, package names, or task ordering that caused these failures.`);
|
|
1239
|
+
}
|
|
1226
1240
|
return renderSlicePrompt({
|
|
1227
1241
|
mid, sid, sTitle, base,
|
|
1228
1242
|
level: level ?? resolveInlineLevel(),
|
|
@@ -220,12 +220,25 @@ export function verifyExpectedArtifact(unitType, unitId, base) {
|
|
|
220
220
|
// RESEARCH file. Without this, resolveExpectedArtifactPath returns null and
|
|
221
221
|
// the retry/escalation machinery silently re-dispatches forever.
|
|
222
222
|
//
|
|
223
|
+
// #4068: Also treat a PARALLEL-BLOCKER placeholder as a terminal completion
|
|
224
|
+
// so that timeout-recovery can write the blocker, have verifyExpectedArtifact
|
|
225
|
+
// return true, and let the dispatch loop advance past this unit. Without
|
|
226
|
+
// this, the blocker is written but verification still returns false, the unit
|
|
227
|
+
// is never cleared from unitDispatchCount, and on the next iteration the
|
|
228
|
+
// dispatch rule (which correctly skips parallel-research when PARALLEL-BLOCKER
|
|
229
|
+
// exists) returns null — leaving the loop stuck re-deriving indefinitely.
|
|
230
|
+
//
|
|
223
231
|
// NOTE: this predicate mirrors the dispatch rule at
|
|
224
232
|
// auto-dispatch.ts parallel-research-slices — keep the two in sync.
|
|
225
233
|
if (unitType === "research-slice" && unitId.endsWith("/parallel-research")) {
|
|
226
234
|
const { milestone: mid } = parseUnitId(unitId);
|
|
227
235
|
if (!mid)
|
|
228
236
|
return false;
|
|
237
|
+
// #4068: PARALLEL-BLOCKER written by timeout-recovery is a terminal state.
|
|
238
|
+
const blockerPath = resolveExpectedArtifactPath(unitType, unitId, base);
|
|
239
|
+
if (blockerPath && existsSync(blockerPath)) {
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
229
242
|
const roadmapFile = resolveMilestoneFile(base, mid, "ROADMAP");
|
|
230
243
|
if (!roadmapFile || !existsSync(roadmapFile)) {
|
|
231
244
|
logWarning("recovery", `verify-fail ${unitType} ${unitId}: roadmap missing`);
|