gsd-pi 2.76.0-dev.4c866b677 → 2.76.0-dev.7218806ab
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/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-model-selection.js +1 -1
- package/dist/resources/extensions/gsd/auto-start.js +11 -15
- package/dist/resources/extensions/gsd/auto.js +13 -17
- 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 +40 -4
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +12 -1
- 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 +3 -1
- 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/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/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/workflow-mcp.js +3 -0
- 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 +11 -11
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +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 +11 -11
- 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/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.map +1 -1
- package/packages/mcp-server/dist/server.js +18 -1
- 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/remote-questions.test.ts +294 -0
- package/packages/mcp-server/src/remote-questions.ts +916 -0
- package/packages/mcp-server/src/server.ts +19 -1
- 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-shared.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.js +2 -0
- package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
- 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-shared.ts +3 -1
- 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/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/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-model-selection.ts +1 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +0 -1
- package/src/resources/extensions/gsd/auto-start.ts +13 -16
- package/src/resources/extensions/gsd/auto.ts +12 -17
- 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 +42 -4
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +13 -1
- 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 +3 -1
- 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/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 +38 -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/tests/compaction-snapshot.test.ts +123 -0
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +31 -0
- 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/gsd-db.test.ts +64 -0
- package/src/resources/extensions/gsd/tests/isolation-none-branch-guard.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/key-manager.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/pre-exec-backtick-strip.test.ts +14 -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/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/token-counter.test.ts +105 -1
- package/src/resources/extensions/gsd/tests/tool-compatibility.test.ts +107 -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/workflow-logger.ts +2 -1
- package/src/resources/extensions/gsd/workflow-mcp.ts +3 -0
- /package/dist/web/standalone/.next/static/{jDqWYbuP_CG6Kjc-uKwkN → 5qAwYhcU5Fs2VOq_R8lOc}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{jDqWYbuP_CG6Kjc-uKwkN → 5qAwYhcU5Fs2VOq_R8lOc}/_ssgManifest.js +0 -0
|
@@ -21,10 +21,11 @@ const CLAUDE_COMMAND = process.platform === "win32" ? "claude.cmd" : "claude";
|
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Windows installs vary: some environments expose `claude.cmd` (npm shim),
|
|
24
|
-
*
|
|
25
|
-
* Try
|
|
24
|
+
* `claude.exe` (direct binary install), or a bare `claude` shim on PATH
|
|
25
|
+
* (for example Git Bash wrappers). Try all three to avoid false "not
|
|
26
|
+
* installed" results in readiness checks.
|
|
26
27
|
*/
|
|
27
|
-
const CLAUDE_COMMAND_CANDIDATES = process.platform === "win32" ? [CLAUDE_COMMAND, "claude"] : [CLAUDE_COMMAND];
|
|
28
|
+
const CLAUDE_COMMAND_CANDIDATES = process.platform === "win32" ? [CLAUDE_COMMAND, "claude.exe", "claude"] : [CLAUDE_COMMAND];
|
|
28
29
|
|
|
29
30
|
function execClaude(args: string[]): Buffer {
|
|
30
31
|
let lastError: unknown;
|
|
@@ -692,18 +692,16 @@ export function makeAbortedMessage(model: string, lastTextContent: string): Assi
|
|
|
692
692
|
/**
|
|
693
693
|
* Resolve the Claude Code permission mode for the current run.
|
|
694
694
|
*
|
|
695
|
-
*
|
|
696
|
-
*
|
|
697
|
-
*
|
|
698
|
-
*
|
|
699
|
-
*
|
|
700
|
-
*
|
|
701
|
-
* users opt into a stricter mode (`acceptEdits`, `default`, `plan`).
|
|
695
|
+
* Defaults to `acceptEdits`, which auto-approves file reads/edits but
|
|
696
|
+
* surfaces a permission dialog for dangerous operations (e.g. general Bash,
|
|
697
|
+
* Agent, WebFetch). This prevents tools outside the allowlist from being
|
|
698
|
+
* silently denied — the SDK emits an `extension_ui_request` event so the
|
|
699
|
+
* user sees a prompt instead of a silent refusal that Claude Code mistakes
|
|
700
|
+
* for user rejection (#4383).
|
|
702
701
|
*
|
|
703
|
-
*
|
|
704
|
-
*
|
|
705
|
-
*
|
|
706
|
-
* (#4099) is continuous approval fatigue that blocks real work.
|
|
702
|
+
* Set `GSD_CLAUDE_CODE_PERMISSION_MODE` to `bypassPermissions` to restore
|
|
703
|
+
* the old always-approve behaviour, or to `default` / `plan` for stricter
|
|
704
|
+
* modes.
|
|
707
705
|
*/
|
|
708
706
|
export async function resolveClaudePermissionMode(
|
|
709
707
|
env: NodeJS.ProcessEnv = process.env,
|
|
@@ -712,7 +710,7 @@ export async function resolveClaudePermissionMode(
|
|
|
712
710
|
if (override === "bypassPermissions" || override === "acceptEdits" || override === "default" || override === "plan") {
|
|
713
711
|
return override;
|
|
714
712
|
}
|
|
715
|
-
return "
|
|
713
|
+
return "acceptEdits";
|
|
716
714
|
}
|
|
717
715
|
|
|
718
716
|
// NOTE: These helpers intentionally mirror @gsd/pi-ai anthropic-shared
|
|
@@ -772,7 +770,7 @@ export function buildSdkOptions(
|
|
|
772
770
|
): Record<string, unknown> {
|
|
773
771
|
const { reasoning, ...sdkExtraOptions } = extraOptions;
|
|
774
772
|
const mcpServers = buildWorkflowMcpServers();
|
|
775
|
-
const permissionMode = overrides?.permissionMode ?? "
|
|
773
|
+
const permissionMode = overrides?.permissionMode ?? "acceptEdits";
|
|
776
774
|
const disallowedTools = ["AskUserQuestion"];
|
|
777
775
|
// Pre-authorize the safe built-ins and every registered workflow MCP
|
|
778
776
|
// server's tools. `acceptEdits` mode (the interactive default) only
|
|
@@ -867,6 +865,69 @@ function normalizeToolResultContent(content: unknown): ExternalToolResultContent
|
|
|
867
865
|
return blocks.length > 0 ? blocks : [{ type: "text", text: "" }];
|
|
868
866
|
}
|
|
869
867
|
|
|
868
|
+
/**
|
|
869
|
+
* Extract a `details` payload from an MCP tool-result block.
|
|
870
|
+
*
|
|
871
|
+
* MCP's `CallToolResult` carries structured data in `structuredContent` — the
|
|
872
|
+
* protocol's supported channel for non-text payloads. Claude Code's synthetic
|
|
873
|
+
* user message may surface that field in one of two shapes depending on SDK
|
|
874
|
+
* version: as a sibling on the `mcp_tool_result` block itself, or as a
|
|
875
|
+
* dedicated content sub-block with `type: "structuredContent"`. Snake-case
|
|
876
|
+
* (`structured_content`) is accepted defensively in case a transport hop
|
|
877
|
+
* rewrites casing. All other shapes fall back to an empty object so callers
|
|
878
|
+
* can rely on `details` being present.
|
|
879
|
+
*/
|
|
880
|
+
function extractStructuredDetailsFromBlock(block: Record<string, unknown>): Record<string, unknown> | undefined {
|
|
881
|
+
const sibling = block.structuredContent ?? (block as Record<string, unknown>).structured_content;
|
|
882
|
+
if (sibling && typeof sibling === "object" && !Array.isArray(sibling)) {
|
|
883
|
+
return sibling as Record<string, unknown>;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
if (Array.isArray(block.content)) {
|
|
887
|
+
for (const item of block.content) {
|
|
888
|
+
if (!item || typeof item !== "object") continue;
|
|
889
|
+
const sub = item as Record<string, unknown>;
|
|
890
|
+
if (sub.type !== "structuredContent" && sub.type !== "structured_content") continue;
|
|
891
|
+
const payload = sub.structuredContent ?? sub.structured_content ?? sub.data ?? sub.value;
|
|
892
|
+
if (payload && typeof payload === "object" && !Array.isArray(payload)) {
|
|
893
|
+
return payload as Record<string, unknown>;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
// Return undefined (not {}) when no structured payload is present, matching
|
|
899
|
+
// the pre-#4477 contract where `details` was nullable. An empty-object
|
|
900
|
+
// sentinel is truthy and breaks downstream consumers that gate on
|
|
901
|
+
// `if (details)`. `undefined` matches the type of the field these results
|
|
902
|
+
// flow into (`Record<string, unknown> | undefined`).
|
|
903
|
+
return undefined;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
/**
|
|
907
|
+
* True for items that are MCP `structuredContent` pseudo-blocks living inside
|
|
908
|
+
* a tool-result `content[]` array. These blocks carry the structured payload
|
|
909
|
+
* (extracted separately by `extractStructuredDetailsFromBlock`) and must NOT
|
|
910
|
+
* leak into the visible content rendered to the user — otherwise the renderer
|
|
911
|
+
* stringifies the JSON pseudo-block and shows it next to the actual tool
|
|
912
|
+
* output. See PR #4477 review (CodeRabbit, post-fix-round).
|
|
913
|
+
*/
|
|
914
|
+
function isStructuredContentPseudoBlock(item: unknown): boolean {
|
|
915
|
+
if (!item || typeof item !== "object") return false;
|
|
916
|
+
const type = (item as Record<string, unknown>).type;
|
|
917
|
+
return type === "structuredContent" || type === "structured_content";
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
/**
|
|
921
|
+
* Strip `structuredContent` pseudo-blocks from a tool-result content array
|
|
922
|
+
* before normalization. The structured payload is extracted via the sibling
|
|
923
|
+
* `structuredContent` field (or a dedicated extractor pass on the raw block);
|
|
924
|
+
* the visible content path must not include the pseudo-block itself.
|
|
925
|
+
*/
|
|
926
|
+
function stripStructuredContentPseudoBlocks(content: unknown): unknown {
|
|
927
|
+
if (!Array.isArray(content)) return content;
|
|
928
|
+
return content.filter((item) => !isStructuredContentPseudoBlock(item));
|
|
929
|
+
}
|
|
930
|
+
|
|
870
931
|
/** Extract tool result payloads from an SDK synthetic user message, keyed by tool-use ID. */
|
|
871
932
|
export function extractToolResultsFromSdkUserMessage(message: SDKUserMessage): Array<{
|
|
872
933
|
toolUseId: string;
|
|
@@ -890,8 +951,8 @@ export function extractToolResultsFromSdkUserMessage(message: SDKUserMessage): A
|
|
|
890
951
|
extracted.push({
|
|
891
952
|
toolUseId,
|
|
892
953
|
result: {
|
|
893
|
-
content: normalizeToolResultContent(block.content),
|
|
894
|
-
details:
|
|
954
|
+
content: normalizeToolResultContent(stripStructuredContentPseudoBlocks(block.content)),
|
|
955
|
+
details: extractStructuredDetailsFromBlock(block),
|
|
895
956
|
isError: block.is_error === true,
|
|
896
957
|
},
|
|
897
958
|
});
|
|
@@ -906,8 +967,8 @@ export function extractToolResultsFromSdkUserMessage(message: SDKUserMessage): A
|
|
|
906
967
|
extracted.push({
|
|
907
968
|
toolUseId,
|
|
908
969
|
result: {
|
|
909
|
-
content: normalizeToolResultContent(toolResult.content),
|
|
910
|
-
details:
|
|
970
|
+
content: normalizeToolResultContent(stripStructuredContentPseudoBlocks(toolResult.content)),
|
|
971
|
+
details: extractStructuredDetailsFromBlock(toolResult),
|
|
911
972
|
isError: toolResult.is_error === true,
|
|
912
973
|
},
|
|
913
974
|
});
|
|
@@ -372,13 +372,146 @@ describe("stream-adapter — Claude Code external tool results", () => {
|
|
|
372
372
|
toolUseId: "tool-bash-1",
|
|
373
373
|
result: {
|
|
374
374
|
content: [{ type: "text", text: "line 1\nline 2" }],
|
|
375
|
-
|
|
375
|
+
// extractStructuredDetailsFromBlock returns undefined when no
|
|
376
|
+
// structured payload exists, restoring the pre-#4477 nullable
|
|
377
|
+
// contract (#4477 review feedback).
|
|
378
|
+
details: undefined,
|
|
376
379
|
isError: false,
|
|
377
380
|
},
|
|
378
381
|
},
|
|
379
382
|
]);
|
|
380
383
|
});
|
|
381
384
|
|
|
385
|
+
test("extractToolResultsFromSdkUserMessage reads structuredContent as a sibling field (#4472)", () => {
|
|
386
|
+
const message: SDKUserMessage = {
|
|
387
|
+
type: "user",
|
|
388
|
+
session_id: "sess-1",
|
|
389
|
+
parent_tool_use_id: "tool-mcp-1",
|
|
390
|
+
message: {
|
|
391
|
+
role: "user",
|
|
392
|
+
content: [
|
|
393
|
+
{
|
|
394
|
+
type: "mcp_tool_result",
|
|
395
|
+
tool_use_id: "tool-mcp-1",
|
|
396
|
+
content: [{ type: "text", text: "Gate Q3 result saved: verdict=pass" }],
|
|
397
|
+
is_error: false,
|
|
398
|
+
structuredContent: { gateId: "Q3", verdict: "pass" },
|
|
399
|
+
} as unknown as Record<string, unknown>,
|
|
400
|
+
],
|
|
401
|
+
},
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
const results = extractToolResultsFromSdkUserMessage(message);
|
|
405
|
+
assert.deepEqual(results[0].result.details, { gateId: "Q3", verdict: "pass" });
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
test("extractToolResultsFromSdkUserMessage reads structuredContent from a content sub-block (#4472)", () => {
|
|
409
|
+
const message: SDKUserMessage = {
|
|
410
|
+
type: "user",
|
|
411
|
+
session_id: "sess-1",
|
|
412
|
+
parent_tool_use_id: "tool-mcp-2",
|
|
413
|
+
message: {
|
|
414
|
+
role: "user",
|
|
415
|
+
content: [
|
|
416
|
+
{
|
|
417
|
+
type: "mcp_tool_result",
|
|
418
|
+
tool_use_id: "tool-mcp-2",
|
|
419
|
+
content: [
|
|
420
|
+
{ type: "text", text: "Gate Q4 result saved: verdict=flag" },
|
|
421
|
+
{ type: "structuredContent", structuredContent: { gateId: "Q4", verdict: "flag" } },
|
|
422
|
+
],
|
|
423
|
+
is_error: false,
|
|
424
|
+
} as unknown as Record<string, unknown>,
|
|
425
|
+
],
|
|
426
|
+
},
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
const results = extractToolResultsFromSdkUserMessage(message);
|
|
430
|
+
assert.deepEqual(results[0].result.details, { gateId: "Q4", verdict: "flag" });
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
test("#4477 extractToolResultsFromSdkUserMessage does NOT leak structuredContent pseudo-blocks into visible content", () => {
|
|
434
|
+
// Regression: when a content sub-block carries `type: "structuredContent"`,
|
|
435
|
+
// it carries the structured payload (extracted separately into `details`)
|
|
436
|
+
// and must NOT appear in the visible `content` array — otherwise the
|
|
437
|
+
// renderer stringifies the JSON pseudo-block and shows it next to the
|
|
438
|
+
// actual tool output. See PR #4477 review (CodeRabbit, post-fix-round).
|
|
439
|
+
const message: SDKUserMessage = {
|
|
440
|
+
type: "user",
|
|
441
|
+
session_id: "sess-1",
|
|
442
|
+
parent_tool_use_id: "tool-mcp-strip",
|
|
443
|
+
message: {
|
|
444
|
+
role: "user",
|
|
445
|
+
content: [
|
|
446
|
+
{
|
|
447
|
+
type: "mcp_tool_result",
|
|
448
|
+
tool_use_id: "tool-mcp-strip",
|
|
449
|
+
content: [
|
|
450
|
+
{ type: "text", text: "Gate Q5 result saved: verdict=pass" },
|
|
451
|
+
{ type: "structuredContent", structuredContent: { gateId: "Q5", verdict: "pass" } },
|
|
452
|
+
{ type: "text", text: "second visible line" },
|
|
453
|
+
// snake_case variant — also a pseudo-block; also must be stripped
|
|
454
|
+
{ type: "structured_content", structured_content: { extra: "data" } },
|
|
455
|
+
],
|
|
456
|
+
is_error: false,
|
|
457
|
+
} as unknown as Record<string, unknown>,
|
|
458
|
+
],
|
|
459
|
+
},
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
const results = extractToolResultsFromSdkUserMessage(message);
|
|
463
|
+
assert.equal(results.length, 1, "should extract one result");
|
|
464
|
+
const result = results[0].result;
|
|
465
|
+
|
|
466
|
+
// The structured payload IS extracted to `details`.
|
|
467
|
+
assert.deepEqual(result.details, { gateId: "Q5", verdict: "pass" });
|
|
468
|
+
|
|
469
|
+
// The visible content has the two text blocks but NEITHER pseudo-block.
|
|
470
|
+
const visibleTexts = result.content.map((c: any) => c.text);
|
|
471
|
+
assert.deepEqual(
|
|
472
|
+
visibleTexts,
|
|
473
|
+
["Gate Q5 result saved: verdict=pass", "second visible line"],
|
|
474
|
+
"visible content must include only the two text blocks; both structuredContent variants must be stripped",
|
|
475
|
+
);
|
|
476
|
+
|
|
477
|
+
// Belt-and-suspenders: assert no rendered text shows the JSON serialization
|
|
478
|
+
// of a pseudo-block. We don't check for bare keys like "gateId" or "verdict"
|
|
479
|
+
// because those are legitimate words in the gate-result message text. The
|
|
480
|
+
// regression signature would be a JSON-shaped substring that could only
|
|
481
|
+
// appear via stringification.
|
|
482
|
+
const allText = visibleTexts.join("\n");
|
|
483
|
+
assert.ok(
|
|
484
|
+
!allText.includes('"structuredContent"'),
|
|
485
|
+
"rendered content must not include the pseudo-block type marker as JSON text",
|
|
486
|
+
);
|
|
487
|
+
assert.ok(
|
|
488
|
+
!allText.includes('"structured_content"'),
|
|
489
|
+
"rendered content must not include the snake_case pseudo-block type marker as JSON text",
|
|
490
|
+
);
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
test("extractToolResultsFromSdkUserMessage accepts snake_case structured_content defensively (#4472)", () => {
|
|
494
|
+
const message: SDKUserMessage = {
|
|
495
|
+
type: "user",
|
|
496
|
+
session_id: "sess-1",
|
|
497
|
+
parent_tool_use_id: "tool-mcp-3",
|
|
498
|
+
message: {
|
|
499
|
+
role: "user",
|
|
500
|
+
content: [
|
|
501
|
+
{
|
|
502
|
+
type: "mcp_tool_result",
|
|
503
|
+
tool_use_id: "tool-mcp-3",
|
|
504
|
+
content: [{ type: "text", text: "ok" }],
|
|
505
|
+
structured_content: { operation: "save_gate_result" },
|
|
506
|
+
} as unknown as Record<string, unknown>,
|
|
507
|
+
],
|
|
508
|
+
},
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
const results = extractToolResultsFromSdkUserMessage(message);
|
|
512
|
+
assert.deepEqual(results[0].result.details, { operation: "save_gate_result" });
|
|
513
|
+
});
|
|
514
|
+
|
|
382
515
|
test("extractToolResultsFromSdkUserMessage falls back to tool_use_result", () => {
|
|
383
516
|
const message: SDKUserMessage = {
|
|
384
517
|
type: "user",
|
|
@@ -398,7 +531,9 @@ describe("stream-adapter — Claude Code external tool results", () => {
|
|
|
398
531
|
toolUseId: "tool-read-1",
|
|
399
532
|
result: {
|
|
400
533
|
content: [{ type: "text", text: "file contents" }],
|
|
401
|
-
|
|
534
|
+
// undefined (not {}) per the restored nullable contract — see
|
|
535
|
+
// the analogous assertion in the tool_result test above.
|
|
536
|
+
details: undefined,
|
|
402
537
|
isError: true,
|
|
403
538
|
},
|
|
404
539
|
},
|
|
@@ -1081,11 +1216,15 @@ describe("stream-adapter — permission mode (F10)", () => {
|
|
|
1081
1216
|
}
|
|
1082
1217
|
}
|
|
1083
1218
|
|
|
1084
|
-
test("buildSdkOptions defaults to
|
|
1219
|
+
test("buildSdkOptions defaults to acceptEdits (#4383)", () => {
|
|
1085
1220
|
clearWorkflowMcpEnv();
|
|
1086
1221
|
const opts = buildSdkOptions("claude-sonnet-4-6", "test");
|
|
1087
|
-
assert.equal(opts.permissionMode, "
|
|
1088
|
-
assert.equal(
|
|
1222
|
+
assert.equal(opts.permissionMode, "acceptEdits");
|
|
1223
|
+
assert.equal(
|
|
1224
|
+
opts.allowDangerouslySkipPermissions,
|
|
1225
|
+
false,
|
|
1226
|
+
"allowDangerouslySkipPermissions must be false when permissionMode is acceptEdits",
|
|
1227
|
+
);
|
|
1089
1228
|
});
|
|
1090
1229
|
|
|
1091
1230
|
test("buildSdkOptions respects explicit acceptEdits override", () => {
|
|
@@ -1099,6 +1238,11 @@ describe("stream-adapter — permission mode (F10)", () => {
|
|
|
1099
1238
|
);
|
|
1100
1239
|
});
|
|
1101
1240
|
|
|
1241
|
+
test("resolveClaudePermissionMode defaults to acceptEdits when no env var is set (#4383)", async () => {
|
|
1242
|
+
const mode = await resolveClaudePermissionMode({});
|
|
1243
|
+
assert.equal(mode, "acceptEdits");
|
|
1244
|
+
});
|
|
1245
|
+
|
|
1102
1246
|
test("resolveClaudePermissionMode honours the GSD_CLAUDE_CODE_PERMISSION_MODE env override", async () => {
|
|
1103
1247
|
const env = { GSD_CLAUDE_CODE_PERMISSION_MODE: "acceptEdits" } as NodeJS.ProcessEnv;
|
|
1104
1248
|
const mode = await resolveClaudePermissionMode(env);
|
|
@@ -395,7 +395,7 @@ export async function selectAndApplyModel(
|
|
|
395
395
|
// ADR-005: Adjust active tool set for the selected model's provider capabilities.
|
|
396
396
|
// Hard-filter incompatible tools, then let extensions override via adjust_tool_set hook.
|
|
397
397
|
const activeToolNames = pi.getActiveTools();
|
|
398
|
-
const { toolNames: compatibleTools, removedTools } = adjustToolSet(activeToolNames, model.api);
|
|
398
|
+
const { toolNames: compatibleTools, removedTools } = adjustToolSet(activeToolNames, model.api, model.provider);
|
|
399
399
|
let finalToolNames = compatibleTools;
|
|
400
400
|
|
|
401
401
|
// Fire adjust_tool_set hook — extensions can override the filtered tool set
|
|
@@ -61,7 +61,7 @@ import { restoreHookState, resetHookState } from "./post-unit-hooks.js";
|
|
|
61
61
|
import { resetProactiveHealing, setLevelChangeCallback } from "./doctor-proactive.js";
|
|
62
62
|
import { snapshotSkills } from "./skill-discovery.js";
|
|
63
63
|
import { isDbAvailable, getMilestone, openDatabase, getDbStatus } from "./gsd-db.js";
|
|
64
|
-
|
|
64
|
+
|
|
65
65
|
import {
|
|
66
66
|
debugLog,
|
|
67
67
|
enableDebug,
|
|
@@ -92,7 +92,7 @@ import type { WorktreeResolver } from "./worktree-resolver.js";
|
|
|
92
92
|
import { getSessionModelOverride } from "./session-model-override.js";
|
|
93
93
|
|
|
94
94
|
export interface BootstrapDeps {
|
|
95
|
-
shouldUseWorktreeIsolation: () => boolean;
|
|
95
|
+
shouldUseWorktreeIsolation: (basePath?: string) => boolean;
|
|
96
96
|
registerSigtermHandler: (basePath: string) => void;
|
|
97
97
|
lockBase: () => string;
|
|
98
98
|
buildResolver: () => WorktreeResolver;
|
|
@@ -343,7 +343,7 @@ export async function bootstrapAutoSession(
|
|
|
343
343
|
const hasLocalGit = existsSync(join(base, ".git"));
|
|
344
344
|
if (!hasLocalGit || isInheritedRepo(base)) {
|
|
345
345
|
const mainBranch =
|
|
346
|
-
loadEffectiveGSDPreferences()?.preferences?.git?.main_branch || "main";
|
|
346
|
+
loadEffectiveGSDPreferences(base)?.preferences?.git?.main_branch || "main";
|
|
347
347
|
nativeInit(base, mainBranch);
|
|
348
348
|
}
|
|
349
349
|
|
|
@@ -361,7 +361,7 @@ export async function bootstrapAutoSession(
|
|
|
361
361
|
// Ensure .gitignore has baseline patterns.
|
|
362
362
|
// ensureGitignore checks for git-tracked .gsd/ files and skips the
|
|
363
363
|
// ".gsd" pattern if the project intentionally tracks .gsd/ in git.
|
|
364
|
-
const gitPrefs = loadEffectiveGSDPreferences()?.preferences?.git;
|
|
364
|
+
const gitPrefs = loadEffectiveGSDPreferences(base)?.preferences?.git;
|
|
365
365
|
const manageGitignore = gitPrefs?.manage_gitignore;
|
|
366
366
|
ensureGitignore(base, { manageGitignore });
|
|
367
367
|
if (manageGitignore !== false) untrackRuntimeFiles(base);
|
|
@@ -390,7 +390,7 @@ export async function bootstrapAutoSession(
|
|
|
390
390
|
// Initialize GitServiceImpl
|
|
391
391
|
s.gitService = new GitServiceImpl(
|
|
392
392
|
s.basePath,
|
|
393
|
-
loadEffectiveGSDPreferences()?.preferences?.git ?? {},
|
|
393
|
+
loadEffectiveGSDPreferences(base)?.preferences?.git ?? {},
|
|
394
394
|
);
|
|
395
395
|
|
|
396
396
|
// ── Debug mode ──
|
|
@@ -434,7 +434,7 @@ export async function bootstrapAutoSession(
|
|
|
434
434
|
// was lost due to session ending between completion and teardown.
|
|
435
435
|
// Must run after DB open and before worktree entry.
|
|
436
436
|
try {
|
|
437
|
-
const auditResult = auditOrphanedMilestoneBranches(base, getIsolationMode());
|
|
437
|
+
const auditResult = auditOrphanedMilestoneBranches(base, getIsolationMode(base));
|
|
438
438
|
for (const msg of auditResult.recovered) {
|
|
439
439
|
ctx.ui.notify(`Orphan audit: ${msg}`, "info");
|
|
440
440
|
}
|
|
@@ -454,7 +454,7 @@ export async function bootstrapAutoSession(
|
|
|
454
454
|
// Stale worktree state recovery (#654)
|
|
455
455
|
if (
|
|
456
456
|
state.activeMilestone &&
|
|
457
|
-
shouldUseWorktreeIsolation() &&
|
|
457
|
+
shouldUseWorktreeIsolation(base) &&
|
|
458
458
|
!detectWorktreeName(base)
|
|
459
459
|
) {
|
|
460
460
|
const wtPath = getAutoWorktreePath(base, state.activeMilestone.id);
|
|
@@ -472,7 +472,7 @@ export async function bootstrapAutoSession(
|
|
|
472
472
|
if (
|
|
473
473
|
state.activeMilestone &&
|
|
474
474
|
(state.phase === "pre-planning" || state.phase === "complete") &&
|
|
475
|
-
getIsolationMode() !== "none" &&
|
|
475
|
+
getIsolationMode(base) !== "none" &&
|
|
476
476
|
!detectWorktreeName(base) &&
|
|
477
477
|
!base.includes(`${pathSep}.gsd${pathSep}worktrees${pathSep}`)
|
|
478
478
|
) {
|
|
@@ -676,7 +676,7 @@ export async function bootstrapAutoSession(
|
|
|
676
676
|
|
|
677
677
|
// Capture integration branch
|
|
678
678
|
if (s.currentMilestoneId) {
|
|
679
|
-
if (getIsolationMode() !== "none") {
|
|
679
|
+
if (getIsolationMode(base) !== "none") {
|
|
680
680
|
captureIntegrationBranch(base, s.currentMilestoneId);
|
|
681
681
|
}
|
|
682
682
|
setActiveMilestoneId(base, s.currentMilestoneId);
|
|
@@ -685,7 +685,7 @@ export async function bootstrapAutoSession(
|
|
|
685
685
|
// Guard against stale milestone branch when isolation:none (#3613).
|
|
686
686
|
// A prior session with isolation:branch/worktree may have left HEAD on
|
|
687
687
|
// milestone/<MID>. Auto-checkout back to the integration branch.
|
|
688
|
-
if (getIsolationMode() === "none" && nativeIsRepo(base)) {
|
|
688
|
+
if (getIsolationMode(base) === "none" && nativeIsRepo(base)) {
|
|
689
689
|
try {
|
|
690
690
|
const currentBranch = nativeGetCurrentBranch(base);
|
|
691
691
|
if (currentBranch.startsWith("milestone/")) {
|
|
@@ -716,7 +716,7 @@ export async function bootstrapAutoSession(
|
|
|
716
716
|
|
|
717
717
|
if (
|
|
718
718
|
s.currentMilestoneId &&
|
|
719
|
-
getIsolationMode() !== "none" &&
|
|
719
|
+
getIsolationMode(base) !== "none" &&
|
|
720
720
|
!detectWorktreeName(base) &&
|
|
721
721
|
!isUnderGsdWorktrees(base)
|
|
722
722
|
) {
|
|
@@ -819,14 +819,11 @@ export async function bootstrapAutoSession(
|
|
|
819
819
|
}
|
|
820
820
|
|
|
821
821
|
// Snapshot installed skills
|
|
822
|
-
if (resolveSkillDiscoveryMode() !== "off") {
|
|
822
|
+
if (resolveSkillDiscoveryMode(base) !== "off") {
|
|
823
823
|
snapshotSkills();
|
|
824
824
|
}
|
|
825
825
|
|
|
826
826
|
ctx.ui.setStatus("gsd-auto", s.stepMode ? "next" : "auto");
|
|
827
|
-
ctx.ui.setFooter(hideFooter);
|
|
828
|
-
// Hide gsd-health during AUTO — gsd-progress is the single source of truth
|
|
829
|
-
// for last-commit / cost / health signal while auto is running.
|
|
830
827
|
ctx.ui.setWidget("gsd-health", undefined);
|
|
831
828
|
const modeLabel = s.stepMode ? "Step-mode" : "Auto-mode";
|
|
832
829
|
const pendingCount = (state.registry ?? []).filter(
|
|
@@ -853,7 +850,7 @@ export async function bootstrapAutoSession(
|
|
|
853
850
|
// FlatRateContext used by selectAndApplyModel so user-declared
|
|
854
851
|
// flat-rate providers and externalCli auto-detection are respected.
|
|
855
852
|
const { isFlatRateProvider, buildFlatRateContext } = await import("./auto-model-selection.js");
|
|
856
|
-
const bannerPrefs = loadEffectiveGSDPreferences()?.preferences;
|
|
853
|
+
const bannerPrefs = loadEffectiveGSDPreferences(base)?.preferences;
|
|
857
854
|
const effectiveProvider = s.autoModeStartModel?.provider ?? ctx.model?.provider;
|
|
858
855
|
const effectivelyEnabled = routingConfig.enabled
|
|
859
856
|
&& (routingConfig.allow_flat_rate_providers
|
|
@@ -182,7 +182,6 @@ import {
|
|
|
182
182
|
unitVerb,
|
|
183
183
|
formatAutoElapsed as _formatAutoElapsed,
|
|
184
184
|
formatWidgetTokens,
|
|
185
|
-
hideFooter,
|
|
186
185
|
type WidgetStateAccessors,
|
|
187
186
|
} from "./auto-dashboard.js";
|
|
188
187
|
import {
|
|
@@ -330,8 +329,8 @@ export function startAutoDetached(
|
|
|
330
329
|
}
|
|
331
330
|
|
|
332
331
|
/** Returns true if the project is configured for `isolation:worktree` mode. */
|
|
333
|
-
export function shouldUseWorktreeIsolation(): boolean {
|
|
334
|
-
const prefs = loadEffectiveGSDPreferences()?.preferences?.git;
|
|
332
|
+
export function shouldUseWorktreeIsolation(basePath?: string): boolean {
|
|
333
|
+
const prefs = loadEffectiveGSDPreferences(basePath)?.preferences?.git;
|
|
335
334
|
if (prefs?.isolation === "worktree") return true;
|
|
336
335
|
// Default is false — worktree isolation requires explicit opt-in
|
|
337
336
|
return false;
|
|
@@ -424,7 +423,7 @@ export function getAutoDashboardData(): AutoDashboardData {
|
|
|
424
423
|
const rtkSavings = sessionId && s.basePath
|
|
425
424
|
? getRtkSessionSavings(s.basePath, sessionId)
|
|
426
425
|
: null;
|
|
427
|
-
const rtkEnabled = loadEffectiveGSDPreferences()?.preferences.experimental?.rtk === true;
|
|
426
|
+
const rtkEnabled = loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences.experimental?.rtk === true;
|
|
428
427
|
// Pending capture count — lazy check, non-fatal
|
|
429
428
|
let pendingCaptureCount = 0;
|
|
430
429
|
try {
|
|
@@ -648,7 +647,7 @@ function buildSnapshotOpts(
|
|
|
648
647
|
gitStatus?: "ok" | "failed";
|
|
649
648
|
gitError?: string;
|
|
650
649
|
} & Record<string, unknown> {
|
|
651
|
-
const prefs = loadEffectiveGSDPreferences()?.preferences;
|
|
650
|
+
const prefs = loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences;
|
|
652
651
|
const uokFlags = resolveUokFlags(prefs);
|
|
653
652
|
return {
|
|
654
653
|
...(s.autoStartTime > 0 ? { autoSessionKey: String(s.autoStartTime) } : {}),
|
|
@@ -686,7 +685,7 @@ function handleLostSessionLock(
|
|
|
686
685
|
restoreProjectRootEnv();
|
|
687
686
|
restoreMilestoneLockEnv();
|
|
688
687
|
deregisterSigtermHandler();
|
|
689
|
-
clearCmuxSidebar(loadEffectiveGSDPreferences()?.preferences);
|
|
688
|
+
clearCmuxSidebar(loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences);
|
|
690
689
|
const base = lockBase();
|
|
691
690
|
const lockFilePath = base ? join(gsdRoot(base), "auto.lock") : "unknown";
|
|
692
691
|
const recoverySuggestion = "\nTo recover, run: gsd doctor --fix";
|
|
@@ -706,7 +705,6 @@ function handleLostSessionLock(
|
|
|
706
705
|
);
|
|
707
706
|
ctx?.ui.setStatus("gsd-auto", undefined);
|
|
708
707
|
ctx?.ui.setWidget("gsd-progress", undefined);
|
|
709
|
-
ctx?.ui.setFooter(undefined);
|
|
710
708
|
if (ctx) initHealthWidget(ctx);
|
|
711
709
|
}
|
|
712
710
|
|
|
@@ -742,7 +740,6 @@ function cleanupAfterLoopExit(ctx: ExtensionContext): void {
|
|
|
742
740
|
if (!s.paused) {
|
|
743
741
|
ctx.ui.setStatus("gsd-auto", undefined);
|
|
744
742
|
ctx.ui.setWidget("gsd-progress", undefined);
|
|
745
|
-
ctx.ui.setFooter(undefined);
|
|
746
743
|
initHealthWidget(ctx);
|
|
747
744
|
}
|
|
748
745
|
|
|
@@ -764,7 +761,7 @@ export async function stopAuto(
|
|
|
764
761
|
reason?: string,
|
|
765
762
|
): Promise<void> {
|
|
766
763
|
if (!s.active && !s.paused) return;
|
|
767
|
-
const loadedPreferences = loadEffectiveGSDPreferences()?.preferences;
|
|
764
|
+
const loadedPreferences = loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences;
|
|
768
765
|
const reasonSuffix = reason ? ` — ${reason}` : "";
|
|
769
766
|
|
|
770
767
|
try {
|
|
@@ -1018,7 +1015,6 @@ export async function stopAuto(
|
|
|
1018
1015
|
// UI cleanup
|
|
1019
1016
|
ctx?.ui.setStatus("gsd-auto", undefined);
|
|
1020
1017
|
ctx?.ui.setWidget("gsd-progress", undefined);
|
|
1021
|
-
ctx?.ui.setFooter(undefined);
|
|
1022
1018
|
if (ctx) initHealthWidget(ctx);
|
|
1023
1019
|
restoreProjectRootEnv();
|
|
1024
1020
|
restoreMilestoneLockEnv();
|
|
@@ -1121,7 +1117,6 @@ export async function pauseAuto(
|
|
|
1121
1117
|
s.verificationRetryCount.clear();
|
|
1122
1118
|
ctx?.ui.setStatus("gsd-auto", "paused");
|
|
1123
1119
|
ctx?.ui.setWidget("gsd-progress", undefined);
|
|
1124
|
-
ctx?.ui.setFooter(undefined);
|
|
1125
1120
|
if (ctx) initHealthWidget(ctx);
|
|
1126
1121
|
const resumeCmd = s.stepMode ? "/gsd next" : "/gsd auto";
|
|
1127
1122
|
ctx?.ui.notify(
|
|
@@ -1495,7 +1490,7 @@ export async function startAuto(
|
|
|
1495
1490
|
// ── Auto-worktree / branch-mode: re-enter on resume ──
|
|
1496
1491
|
if (
|
|
1497
1492
|
s.currentMilestoneId &&
|
|
1498
|
-
getIsolationMode() !== "none" &&
|
|
1493
|
+
getIsolationMode(s.originalBasePath || s.basePath) !== "none" &&
|
|
1499
1494
|
s.originalBasePath &&
|
|
1500
1495
|
!isInAutoWorktree(s.basePath) &&
|
|
1501
1496
|
!detectWorktreeName(s.basePath) &&
|
|
@@ -1509,7 +1504,7 @@ export async function startAuto(
|
|
|
1509
1504
|
registerSigtermHandler(lockBase());
|
|
1510
1505
|
|
|
1511
1506
|
ctx.ui.setStatus("gsd-auto", s.stepMode ? "next" : "auto");
|
|
1512
|
-
ctx.ui.
|
|
1507
|
+
ctx.ui.setWidget("gsd-health", undefined);
|
|
1513
1508
|
ctx.ui.notify(
|
|
1514
1509
|
s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.",
|
|
1515
1510
|
"info",
|
|
@@ -1534,7 +1529,7 @@ export async function startAuto(
|
|
|
1534
1529
|
await openProjectDbIfPresent(s.basePath);
|
|
1535
1530
|
try {
|
|
1536
1531
|
await rebuildState(s.basePath);
|
|
1537
|
-
syncCmuxSidebar(loadEffectiveGSDPreferences()?.preferences, await deriveState(s.basePath));
|
|
1532
|
+
syncCmuxSidebar(loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences, await deriveState(s.basePath));
|
|
1538
1533
|
} catch (e) {
|
|
1539
1534
|
debugLog("resume-rebuild-state-failed", {
|
|
1540
1535
|
error: e instanceof Error ? e.message : String(e),
|
|
@@ -1584,7 +1579,7 @@ export async function startAuto(
|
|
|
1584
1579
|
"resuming",
|
|
1585
1580
|
s.currentMilestoneId ?? "unknown",
|
|
1586
1581
|
);
|
|
1587
|
-
logCmuxEvent(loadEffectiveGSDPreferences()?.preferences, s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.", "progress");
|
|
1582
|
+
logCmuxEvent(loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences, s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.", "progress");
|
|
1588
1583
|
|
|
1589
1584
|
captureProjectRootEnv(s.originalBasePath || s.basePath);
|
|
1590
1585
|
startAutoCommandPolling(s.basePath);
|
|
@@ -1622,12 +1617,12 @@ export async function startAuto(
|
|
|
1622
1617
|
|
|
1623
1618
|
captureProjectRootEnv(s.originalBasePath || s.basePath);
|
|
1624
1619
|
try {
|
|
1625
|
-
syncCmuxSidebar(loadEffectiveGSDPreferences()?.preferences, await deriveState(s.basePath));
|
|
1620
|
+
syncCmuxSidebar(loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences, await deriveState(s.basePath));
|
|
1626
1621
|
} catch (err) {
|
|
1627
1622
|
// Best-effort only — sidebar sync must never block auto-mode startup
|
|
1628
1623
|
logWarning("engine", `cmux sync failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
|
|
1629
1624
|
}
|
|
1630
|
-
logCmuxEvent(loadEffectiveGSDPreferences()?.preferences, requestedStepMode ? "Step-mode started." : "Auto-mode started.", "progress");
|
|
1625
|
+
logCmuxEvent(loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences, requestedStepMode ? "Step-mode started." : "Auto-mode started.", "progress");
|
|
1631
1626
|
|
|
1632
1627
|
startAutoCommandPolling(s.basePath);
|
|
1633
1628
|
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext } from "@gsd/pi-coding-agent";
|
|
2
2
|
|
|
3
3
|
import { logWarning } from "../workflow-logger.js";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
checkAutoStartAfterDiscuss,
|
|
6
|
+
maybeHandleReadyPhraseWithoutFiles,
|
|
7
|
+
maybeHandleEmptyIntentTurn,
|
|
8
|
+
resetEmptyTurnCounter,
|
|
9
|
+
} from "../guided-flow.js";
|
|
5
10
|
import { getAutoDashboardData, getAutoModeStartModel, isAutoActive, pauseAuto, setCurrentDispatchedModelId } from "../auto.js";
|
|
6
11
|
import { getNextFallbackModel, resolveModelWithFallbacksForUnit } from "../preferences.js";
|
|
7
12
|
import { pauseAutoForProviderError } from "../provider-error-pause.js";
|
|
@@ -75,6 +80,20 @@ export async function handleAgentEnd(
|
|
|
75
80
|
clearDiscussionFlowState();
|
|
76
81
|
return;
|
|
77
82
|
}
|
|
83
|
+
|
|
84
|
+
// #4573 — When the LLM emits "Milestone X ready." but the required files
|
|
85
|
+
// are missing, `checkAutoStartAfterDiscuss` returns false silently. Surface
|
|
86
|
+
// that and nudge the LLM to complete the writes before the user hits the
|
|
87
|
+
// downstream "All milestones complete" warning loop.
|
|
88
|
+
if (maybeHandleReadyPhraseWithoutFiles(event)) return;
|
|
89
|
+
|
|
90
|
+
// #4573 — Empty-turn recovery: if the LLM announced intent in prose but
|
|
91
|
+
// emitted no tool calls, nudge it to execute. Fires only when auto-mode is
|
|
92
|
+
// active or a discussion autostart is pending (non-auto interactive discuss
|
|
93
|
+
// is user-driven). Runs before `isAutoActive` early return so pending
|
|
94
|
+
// discussions (where isAutoActive may be false) still get recovered.
|
|
95
|
+
if (maybeHandleEmptyIntentTurn(event, isAutoActive())) return;
|
|
96
|
+
|
|
78
97
|
if (!isAutoActive()) return;
|
|
79
98
|
if (isSessionSwitchInFlight()) return;
|
|
80
99
|
|
|
@@ -336,6 +355,9 @@ export async function handleAgentEnd(
|
|
|
336
355
|
// ── Success path ─────────────────────────────────────────────────────────
|
|
337
356
|
try {
|
|
338
357
|
resetRetryState(retryState);
|
|
358
|
+
// #4573 — Reset the empty-turn counter on any successful agent turn so
|
|
359
|
+
// transient stalls don't accumulate across independent units.
|
|
360
|
+
resetEmptyTurnCounter();
|
|
339
361
|
resolveAgentEnd(event);
|
|
340
362
|
} catch (err) {
|
|
341
363
|
const message = err instanceof Error ? err.message : String(err);
|