gsd-pi 2.76.0-dev.b072ebb73 → 2.76.0-dev.fe143342a
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/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/stream-adapter.js +66 -4
- package/dist/resources/extensions/gsd/auto/phases.js +4 -1
- package/dist/resources/extensions/gsd/auto/session.js +4 -0
- package/dist/resources/extensions/gsd/auto-model-selection.js +39 -13
- package/dist/resources/extensions/gsd/auto-start.js +39 -21
- package/dist/resources/extensions/gsd/auto.js +15 -12
- package/dist/resources/extensions/gsd/blocked-models.js +68 -0
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +76 -0
- 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 +35 -0
- package/dist/resources/extensions/gsd/compaction-snapshot.js +121 -0
- package/dist/resources/extensions/gsd/complexity-classifier.js +5 -3
- package/dist/resources/extensions/gsd/error-classifier.js +31 -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 +62 -4
- 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/pre-execution-checks.js +13 -3
- 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/prompt-loader.js +22 -7
- package/dist/resources/extensions/gsd/safety/file-change-validator.js +1 -1
- 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/resources/extensions/search-the-web/command-search-provider.js +5 -4
- package/dist/resources/extensions/search-the-web/native-search.js +45 -13
- 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 +8 -8
- 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 +8 -8
- 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/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/src/workflow-tools.test.ts +146 -1
- package/packages/mcp-server/src/workflow-tools.ts +84 -43
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.js +60 -15
- package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
- package/packages/pi-ai/dist/providers/think-tag-parser.d.ts +17 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.js +75 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.js.map +1 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.test.js +41 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.test.js.map +1 -0
- package/packages/pi-ai/src/providers/openai-completions.ts +57 -16
- package/packages/pi-ai/src/providers/think-tag-parser.test.ts +44 -0
- package/packages/pi-ai/src/providers/think-tag-parser.ts +94 -0
- package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/model-discovery.d.ts +3 -1
- package/packages/pi-coding-agent/dist/core/model-discovery.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-discovery.js +92 -12
- package/packages/pi-coding-agent/dist/core/model-discovery.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-discovery.test.js +16 -1
- package/packages/pi-coding-agent/dist/core/model-discovery.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js +61 -1
- package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts +5 -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 +76 -10
- 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/provider-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +13 -7
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.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/src/core/model-discovery.test.ts +19 -0
- package/packages/pi-coding-agent/src/core/model-discovery.ts +99 -12
- package/packages/pi-coding-agent/src/core/model-registry-discovery.test.ts +75 -0
- package/packages/pi-coding-agent/src/core/model-registry.ts +86 -10
- 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/provider-manager.ts +16 -7
- package/packages/pi-coding-agent/src/modes/interactive/components/skill-invocation-message.ts +36 -22
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/scripts/link-workspace-packages.cjs +1 -0
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +67 -4
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +137 -2
- package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
- package/src/resources/extensions/gsd/auto/phases.ts +4 -0
- package/src/resources/extensions/gsd/auto/session.ts +7 -1
- package/src/resources/extensions/gsd/auto-model-selection.ts +50 -12
- package/src/resources/extensions/gsd/auto-start.ts +40 -22
- package/src/resources/extensions/gsd/auto.ts +15 -12
- package/src/resources/extensions/gsd/blocked-models.ts +98 -0
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +97 -0
- 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 +36 -0
- package/src/resources/extensions/gsd/compaction-snapshot.ts +165 -0
- package/src/resources/extensions/gsd/complexity-classifier.ts +5 -3
- package/src/resources/extensions/gsd/error-classifier.ts +36 -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 +68 -4
- package/src/resources/extensions/gsd/init-wizard.ts +15 -1
- package/src/resources/extensions/gsd/key-manager.ts +6 -0
- package/src/resources/extensions/gsd/pre-execution-checks.ts +13 -3
- 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/prompt-loader.ts +30 -7
- package/src/resources/extensions/gsd/safety/file-change-validator.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +12 -0
- package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +33 -3
- package/src/resources/extensions/gsd/tests/auto-thinking-restore.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/blocked-models.test.ts +98 -0
- package/src/resources/extensions/gsd/tests/compaction-snapshot.test.ts +123 -0
- package/src/resources/extensions/gsd/tests/complexity-classifier.test.ts +3 -3
- 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 +20 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +151 -0
- 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/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 +19 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +110 -0
- package/src/resources/extensions/gsd/tests/prompt-loader-extension-dir.test.ts +49 -0
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +91 -0
- package/src/resources/extensions/gsd/tests/save-gate-result-render.test.ts +95 -0
- package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +3 -1
- 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/src/resources/extensions/search-the-web/command-search-provider.ts +5 -4
- package/src/resources/extensions/search-the-web/native-search.ts +48 -12
- /package/dist/web/standalone/.next/static/{pBwmOoye64ZrRp-_rf0v1 → n21VtX2hZlkpdEUO_nU4z}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{pBwmOoye64ZrRp-_rf0v1 → n21VtX2hZlkpdEUO_nU4z}/_ssgManifest.js +0 -0
|
@@ -34,6 +34,7 @@ const packageMap = {
|
|
|
34
34
|
'pi-coding-agent': { scope: '@gsd', name: 'pi-coding-agent' },
|
|
35
35
|
'pi-tui': { scope: '@gsd', name: 'pi-tui' },
|
|
36
36
|
'rpc-client': { scope: '@gsd-build', name: 'rpc-client' },
|
|
37
|
+
'mcp-server': { scope: '@gsd-build', name: 'mcp-server' },
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
for (const scopeDir of Object.values(scopeDirs)) {
|
|
@@ -867,6 +867,69 @@ function normalizeToolResultContent(content: unknown): ExternalToolResultContent
|
|
|
867
867
|
return blocks.length > 0 ? blocks : [{ type: "text", text: "" }];
|
|
868
868
|
}
|
|
869
869
|
|
|
870
|
+
/**
|
|
871
|
+
* Extract a `details` payload from an MCP tool-result block.
|
|
872
|
+
*
|
|
873
|
+
* MCP's `CallToolResult` carries structured data in `structuredContent` — the
|
|
874
|
+
* protocol's supported channel for non-text payloads. Claude Code's synthetic
|
|
875
|
+
* user message may surface that field in one of two shapes depending on SDK
|
|
876
|
+
* version: as a sibling on the `mcp_tool_result` block itself, or as a
|
|
877
|
+
* dedicated content sub-block with `type: "structuredContent"`. Snake-case
|
|
878
|
+
* (`structured_content`) is accepted defensively in case a transport hop
|
|
879
|
+
* rewrites casing. All other shapes fall back to an empty object so callers
|
|
880
|
+
* can rely on `details` being present.
|
|
881
|
+
*/
|
|
882
|
+
function extractStructuredDetailsFromBlock(block: Record<string, unknown>): Record<string, unknown> | undefined {
|
|
883
|
+
const sibling = block.structuredContent ?? (block as Record<string, unknown>).structured_content;
|
|
884
|
+
if (sibling && typeof sibling === "object" && !Array.isArray(sibling)) {
|
|
885
|
+
return sibling as Record<string, unknown>;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
if (Array.isArray(block.content)) {
|
|
889
|
+
for (const item of block.content) {
|
|
890
|
+
if (!item || typeof item !== "object") continue;
|
|
891
|
+
const sub = item as Record<string, unknown>;
|
|
892
|
+
if (sub.type !== "structuredContent" && sub.type !== "structured_content") continue;
|
|
893
|
+
const payload = sub.structuredContent ?? sub.structured_content ?? sub.data ?? sub.value;
|
|
894
|
+
if (payload && typeof payload === "object" && !Array.isArray(payload)) {
|
|
895
|
+
return payload as Record<string, unknown>;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
// Return undefined (not {}) when no structured payload is present, matching
|
|
901
|
+
// the pre-#4477 contract where `details` was nullable. An empty-object
|
|
902
|
+
// sentinel is truthy and breaks downstream consumers that gate on
|
|
903
|
+
// `if (details)`. `undefined` matches the type of the field these results
|
|
904
|
+
// flow into (`Record<string, unknown> | undefined`).
|
|
905
|
+
return undefined;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
/**
|
|
909
|
+
* True for items that are MCP `structuredContent` pseudo-blocks living inside
|
|
910
|
+
* a tool-result `content[]` array. These blocks carry the structured payload
|
|
911
|
+
* (extracted separately by `extractStructuredDetailsFromBlock`) and must NOT
|
|
912
|
+
* leak into the visible content rendered to the user — otherwise the renderer
|
|
913
|
+
* stringifies the JSON pseudo-block and shows it next to the actual tool
|
|
914
|
+
* output. See PR #4477 review (CodeRabbit, post-fix-round).
|
|
915
|
+
*/
|
|
916
|
+
function isStructuredContentPseudoBlock(item: unknown): boolean {
|
|
917
|
+
if (!item || typeof item !== "object") return false;
|
|
918
|
+
const type = (item as Record<string, unknown>).type;
|
|
919
|
+
return type === "structuredContent" || type === "structured_content";
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
/**
|
|
923
|
+
* Strip `structuredContent` pseudo-blocks from a tool-result content array
|
|
924
|
+
* before normalization. The structured payload is extracted via the sibling
|
|
925
|
+
* `structuredContent` field (or a dedicated extractor pass on the raw block);
|
|
926
|
+
* the visible content path must not include the pseudo-block itself.
|
|
927
|
+
*/
|
|
928
|
+
function stripStructuredContentPseudoBlocks(content: unknown): unknown {
|
|
929
|
+
if (!Array.isArray(content)) return content;
|
|
930
|
+
return content.filter((item) => !isStructuredContentPseudoBlock(item));
|
|
931
|
+
}
|
|
932
|
+
|
|
870
933
|
/** Extract tool result payloads from an SDK synthetic user message, keyed by tool-use ID. */
|
|
871
934
|
export function extractToolResultsFromSdkUserMessage(message: SDKUserMessage): Array<{
|
|
872
935
|
toolUseId: string;
|
|
@@ -890,8 +953,8 @@ export function extractToolResultsFromSdkUserMessage(message: SDKUserMessage): A
|
|
|
890
953
|
extracted.push({
|
|
891
954
|
toolUseId,
|
|
892
955
|
result: {
|
|
893
|
-
content: normalizeToolResultContent(block.content),
|
|
894
|
-
details:
|
|
956
|
+
content: normalizeToolResultContent(stripStructuredContentPseudoBlocks(block.content)),
|
|
957
|
+
details: extractStructuredDetailsFromBlock(block),
|
|
895
958
|
isError: block.is_error === true,
|
|
896
959
|
},
|
|
897
960
|
});
|
|
@@ -906,8 +969,8 @@ export function extractToolResultsFromSdkUserMessage(message: SDKUserMessage): A
|
|
|
906
969
|
extracted.push({
|
|
907
970
|
toolUseId,
|
|
908
971
|
result: {
|
|
909
|
-
content: normalizeToolResultContent(toolResult.content),
|
|
910
|
-
details:
|
|
972
|
+
content: normalizeToolResultContent(stripStructuredContentPseudoBlocks(toolResult.content)),
|
|
973
|
+
details: extractStructuredDetailsFromBlock(toolResult),
|
|
911
974
|
isError: toolResult.is_error === true,
|
|
912
975
|
},
|
|
913
976
|
});
|
|
@@ -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
|
},
|
|
@@ -213,6 +213,7 @@ export interface LoopDeps {
|
|
|
213
213
|
retryContext?: { isRetry: boolean; previousTier?: string },
|
|
214
214
|
isAutoMode?: boolean,
|
|
215
215
|
sessionModelOverride?: { provider: string; id: string } | null,
|
|
216
|
+
autoModeStartThinkingLevel?: ReturnType<ExtensionAPI["getThinkingLevel"]> | null,
|
|
216
217
|
) => Promise<{
|
|
217
218
|
routing: { tier: string; modelDowngraded: boolean } | null;
|
|
218
219
|
appliedModel: { provider: string; id: string } | null;
|
|
@@ -1471,6 +1471,7 @@ export async function runUnitPhase(
|
|
|
1471
1471
|
sidecarItem ? undefined : { isRetry, previousTier },
|
|
1472
1472
|
undefined,
|
|
1473
1473
|
s.manualSessionModelOverride,
|
|
1474
|
+
s.autoModeStartThinkingLevel,
|
|
1474
1475
|
);
|
|
1475
1476
|
s.currentUnitRouting =
|
|
1476
1477
|
modelResult.routing as AutoSession["currentUnitRouting"];
|
|
@@ -1485,6 +1486,9 @@ export async function runUnitPhase(
|
|
|
1485
1486
|
if (match) {
|
|
1486
1487
|
const ok = await pi.setModel(match, { persist: false });
|
|
1487
1488
|
if (ok) {
|
|
1489
|
+
if (s.autoModeStartThinkingLevel) {
|
|
1490
|
+
pi.setThinkingLevel(s.autoModeStartThinkingLevel);
|
|
1491
|
+
}
|
|
1488
1492
|
s.currentUnitModel = match as AutoSession["currentUnitModel"];
|
|
1489
1493
|
ctx.ui.notify(`Hook model override: ${match.provider}/${match.id}`, "info");
|
|
1490
1494
|
} else {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import type { Api, Model } from "@gsd/pi-ai";
|
|
20
|
-
import type { ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
|
20
|
+
import type { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
|
21
21
|
import type { GitServiceImpl } from "../git-service.js";
|
|
22
22
|
import type { CaptureEntry } from "../captures.js";
|
|
23
23
|
import type { BudgetAlertLevel } from "../auto-budget.js";
|
|
@@ -40,6 +40,8 @@ export interface StartModel {
|
|
|
40
40
|
id: string;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
export type ThinkingLevelSnapshot = ReturnType<ExtensionAPI["getThinkingLevel"]>;
|
|
44
|
+
|
|
43
45
|
export interface PendingVerificationRetry {
|
|
44
46
|
unitId: string;
|
|
45
47
|
failureContext: string;
|
|
@@ -120,6 +122,8 @@ export class AutoSession {
|
|
|
120
122
|
currentDispatchedModelId: string | null = null;
|
|
121
123
|
originalModelId: string | null = null;
|
|
122
124
|
originalModelProvider: string | null = null;
|
|
125
|
+
autoModeStartThinkingLevel: ThinkingLevelSnapshot | null = null;
|
|
126
|
+
originalThinkingLevel: ThinkingLevelSnapshot | null = null;
|
|
123
127
|
lastBudgetAlertLevel: BudgetAlertLevel = 0;
|
|
124
128
|
|
|
125
129
|
// ── Recovery ─────────────────────────────────────────────────────────────
|
|
@@ -241,6 +245,8 @@ export class AutoSession {
|
|
|
241
245
|
this.currentDispatchedModelId = null;
|
|
242
246
|
this.originalModelId = null;
|
|
243
247
|
this.originalModelProvider = null;
|
|
248
|
+
this.autoModeStartThinkingLevel = null;
|
|
249
|
+
this.originalThinkingLevel = null;
|
|
244
250
|
this.lastBudgetAlertLevel = 0;
|
|
245
251
|
|
|
246
252
|
// Recovery
|
|
@@ -18,6 +18,7 @@ import { getSessionModelOverride } from "./session-model-override.js";
|
|
|
18
18
|
import { logWarning } from "./workflow-logger.js";
|
|
19
19
|
import { resolveUokFlags } from "./uok/flags.js";
|
|
20
20
|
import { applyModelPolicyFilter } from "./uok/model-policy.js";
|
|
21
|
+
import { isModelBlocked } from "./blocked-models.js";
|
|
21
22
|
|
|
22
23
|
export interface ModelSelectionResult {
|
|
23
24
|
/** Routing metadata for metrics recording */
|
|
@@ -32,6 +33,14 @@ export interface PreferredModelConfig {
|
|
|
32
33
|
source: "explicit" | "synthesized";
|
|
33
34
|
}
|
|
34
35
|
|
|
36
|
+
function reapplyThinkingLevel(
|
|
37
|
+
pi: ExtensionAPI,
|
|
38
|
+
level: ReturnType<ExtensionAPI["getThinkingLevel"]> | null | undefined,
|
|
39
|
+
): void {
|
|
40
|
+
if (!level) return;
|
|
41
|
+
pi.setThinkingLevel(level);
|
|
42
|
+
}
|
|
43
|
+
|
|
35
44
|
export function resolvePreferredModelConfig(
|
|
36
45
|
unitType: string,
|
|
37
46
|
autoModeStartModel: { provider: string; id: string; flatRateCtx?: FlatRateContext } | null,
|
|
@@ -96,6 +105,8 @@ export async function selectAndApplyModel(
|
|
|
96
105
|
isAutoMode = true,
|
|
97
106
|
/** Explicit /gsd model pin captured at bootstrap for long-running auto loops. */
|
|
98
107
|
sessionModelOverride?: { provider: string; id: string } | null,
|
|
108
|
+
/** Thinking level captured at auto-mode start and re-applied after model swaps. */
|
|
109
|
+
autoModeStartThinkingLevel?: ReturnType<ExtensionAPI["getThinkingLevel"]> | null,
|
|
99
110
|
): Promise<ModelSelectionResult> {
|
|
100
111
|
const uokFlags = resolveUokFlags(prefs);
|
|
101
112
|
const effectiveSessionModelOverride = sessionModelOverride === undefined
|
|
@@ -352,6 +363,18 @@ export async function selectAndApplyModel(
|
|
|
352
363
|
attemptedPolicyEligible = true;
|
|
353
364
|
}
|
|
354
365
|
|
|
366
|
+
// Skip models the provider has previously rejected for this account
|
|
367
|
+
// (issue #4513). The block is persisted in .gsd/runtime/blocked-models.json
|
|
368
|
+
// so it survives /gsd auto restarts — without this, the same dead model
|
|
369
|
+
// gets reselected after every restart.
|
|
370
|
+
if (isModelBlocked(basePath, model.provider, model.id)) {
|
|
371
|
+
ctx.ui.notify(
|
|
372
|
+
`Skipping blocked model ${model.provider}/${model.id} (provider rejected it for this account).`,
|
|
373
|
+
"warning",
|
|
374
|
+
);
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
|
|
355
378
|
// Warn if the ID is ambiguous across providers
|
|
356
379
|
if (!modelId.includes("/")) {
|
|
357
380
|
const providers = availableModels.filter(m => m.id === modelId).map(m => m.provider);
|
|
@@ -367,6 +390,7 @@ export async function selectAndApplyModel(
|
|
|
367
390
|
const ok = await pi.setModel(model, { persist: false });
|
|
368
391
|
if (ok) {
|
|
369
392
|
appliedModel = model;
|
|
393
|
+
reapplyThinkingLevel(pi, autoModeStartThinkingLevel);
|
|
370
394
|
|
|
371
395
|
// ADR-005: Adjust active tool set for the selected model's provider capabilities.
|
|
372
396
|
// Hard-filter incompatible tools, then let extensions override via adjust_tool_set hook.
|
|
@@ -425,19 +449,33 @@ export async function selectAndApplyModel(
|
|
|
425
449
|
// No model preference for this unit type — re-apply the model captured
|
|
426
450
|
// at auto-mode start to prevent bleed from shared global settings.json (#650).
|
|
427
451
|
const availableModels = ctx.modelRegistry.getAvailable();
|
|
428
|
-
const
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
452
|
+
const startBlocked = isModelBlocked(basePath, autoModeStartModel.provider, autoModeStartModel.id);
|
|
453
|
+
if (startBlocked) {
|
|
454
|
+
ctx.ui.notify(
|
|
455
|
+
`Auto-mode start model ${autoModeStartModel.provider}/${autoModeStartModel.id} is blocked for this account. Using current session model instead.`,
|
|
456
|
+
"warning",
|
|
457
|
+
);
|
|
458
|
+
} else {
|
|
459
|
+
const startModel = availableModels.find(
|
|
460
|
+
m => m.provider === autoModeStartModel.provider && m.id === autoModeStartModel.id,
|
|
461
|
+
);
|
|
462
|
+
if (startModel) {
|
|
463
|
+
const ok = await pi.setModel(startModel, { persist: false });
|
|
464
|
+
if (!ok) {
|
|
465
|
+
const byId = availableModels.find(
|
|
466
|
+
m => m.id === autoModeStartModel.id && !isModelBlocked(basePath, m.provider, m.id),
|
|
467
|
+
);
|
|
468
|
+
if (byId) {
|
|
469
|
+
const fallbackOk = await pi.setModel(byId, { persist: false });
|
|
470
|
+
if (fallbackOk) {
|
|
471
|
+
appliedModel = byId;
|
|
472
|
+
reapplyThinkingLevel(pi, autoModeStartThinkingLevel);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
} else {
|
|
476
|
+
appliedModel = startModel;
|
|
477
|
+
reapplyThinkingLevel(pi, autoModeStartThinkingLevel);
|
|
438
478
|
}
|
|
439
|
-
} else {
|
|
440
|
-
appliedModel = startModel;
|
|
441
479
|
}
|
|
442
480
|
}
|
|
443
481
|
}
|
|
@@ -60,7 +60,7 @@ import { initRoutingHistory } from "./routing-history.js";
|
|
|
60
60
|
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
|
-
import { isDbAvailable, getMilestone, openDatabase } from "./gsd-db.js";
|
|
63
|
+
import { isDbAvailable, getMilestone, openDatabase, getDbStatus } from "./gsd-db.js";
|
|
64
64
|
import { hideFooter } from "./auto-dashboard.js";
|
|
65
65
|
import {
|
|
66
66
|
debugLog,
|
|
@@ -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;
|
|
@@ -273,8 +273,8 @@ export async function bootstrapAutoSession(
|
|
|
273
273
|
//
|
|
274
274
|
// Precedence:
|
|
275
275
|
// 1) Explicit session override via /gsd model (this session)
|
|
276
|
-
// 2)
|
|
277
|
-
// 3)
|
|
276
|
+
// 2) Current session model from settings/session restore (if provider ready)
|
|
277
|
+
// 3) GSD model preferences from PREFERENCES.md (validated against live auth)
|
|
278
278
|
//
|
|
279
279
|
// This preserves #3517 defaults while honoring explicit runtime model
|
|
280
280
|
// selection for subsequent /gsd runs in the same session.
|
|
@@ -314,11 +314,14 @@ export async function bootstrapAutoSession(
|
|
|
314
314
|
}
|
|
315
315
|
const sessionModelReady =
|
|
316
316
|
ctx.model && ctx.modelRegistry.isProviderRequestReady(ctx.model.provider);
|
|
317
|
+
const currentSessionModel = (sessionModelReady && ctx.model)
|
|
318
|
+
? { provider: ctx.model.provider, id: ctx.model.id }
|
|
319
|
+
: null;
|
|
320
|
+
const startThinkingSnapshot = pi.getThinkingLevel();
|
|
317
321
|
const startModelSnapshot = manualSessionOverride
|
|
322
|
+
?? currentSessionModel
|
|
318
323
|
?? validatedPreferredModel
|
|
319
|
-
??
|
|
320
|
-
? { provider: ctx.model.provider, id: ctx.model.id }
|
|
321
|
-
: null);
|
|
324
|
+
?? null;
|
|
322
325
|
|
|
323
326
|
try {
|
|
324
327
|
// Validate GSD_PROJECT_ID early so the user gets immediate feedback
|
|
@@ -340,7 +343,7 @@ export async function bootstrapAutoSession(
|
|
|
340
343
|
const hasLocalGit = existsSync(join(base, ".git"));
|
|
341
344
|
if (!hasLocalGit || isInheritedRepo(base)) {
|
|
342
345
|
const mainBranch =
|
|
343
|
-
loadEffectiveGSDPreferences()?.preferences?.git?.main_branch || "main";
|
|
346
|
+
loadEffectiveGSDPreferences(base)?.preferences?.git?.main_branch || "main";
|
|
344
347
|
nativeInit(base, mainBranch);
|
|
345
348
|
}
|
|
346
349
|
|
|
@@ -358,7 +361,7 @@ export async function bootstrapAutoSession(
|
|
|
358
361
|
// Ensure .gitignore has baseline patterns.
|
|
359
362
|
// ensureGitignore checks for git-tracked .gsd/ files and skips the
|
|
360
363
|
// ".gsd" pattern if the project intentionally tracks .gsd/ in git.
|
|
361
|
-
const gitPrefs = loadEffectiveGSDPreferences()?.preferences?.git;
|
|
364
|
+
const gitPrefs = loadEffectiveGSDPreferences(base)?.preferences?.git;
|
|
362
365
|
const manageGitignore = gitPrefs?.manage_gitignore;
|
|
363
366
|
ensureGitignore(base, { manageGitignore });
|
|
364
367
|
if (manageGitignore !== false) untrackRuntimeFiles(base);
|
|
@@ -387,7 +390,7 @@ export async function bootstrapAutoSession(
|
|
|
387
390
|
// Initialize GitServiceImpl
|
|
388
391
|
s.gitService = new GitServiceImpl(
|
|
389
392
|
s.basePath,
|
|
390
|
-
loadEffectiveGSDPreferences()?.preferences?.git ?? {},
|
|
393
|
+
loadEffectiveGSDPreferences(base)?.preferences?.git ?? {},
|
|
391
394
|
);
|
|
392
395
|
|
|
393
396
|
// ── Debug mode ──
|
|
@@ -431,7 +434,7 @@ export async function bootstrapAutoSession(
|
|
|
431
434
|
// was lost due to session ending between completion and teardown.
|
|
432
435
|
// Must run after DB open and before worktree entry.
|
|
433
436
|
try {
|
|
434
|
-
const auditResult = auditOrphanedMilestoneBranches(base, getIsolationMode());
|
|
437
|
+
const auditResult = auditOrphanedMilestoneBranches(base, getIsolationMode(base));
|
|
435
438
|
for (const msg of auditResult.recovered) {
|
|
436
439
|
ctx.ui.notify(`Orphan audit: ${msg}`, "info");
|
|
437
440
|
}
|
|
@@ -451,7 +454,7 @@ export async function bootstrapAutoSession(
|
|
|
451
454
|
// Stale worktree state recovery (#654)
|
|
452
455
|
if (
|
|
453
456
|
state.activeMilestone &&
|
|
454
|
-
shouldUseWorktreeIsolation() &&
|
|
457
|
+
shouldUseWorktreeIsolation(base) &&
|
|
455
458
|
!detectWorktreeName(base)
|
|
456
459
|
) {
|
|
457
460
|
const wtPath = getAutoWorktreePath(base, state.activeMilestone.id);
|
|
@@ -469,7 +472,7 @@ export async function bootstrapAutoSession(
|
|
|
469
472
|
if (
|
|
470
473
|
state.activeMilestone &&
|
|
471
474
|
(state.phase === "pre-planning" || state.phase === "complete") &&
|
|
472
|
-
getIsolationMode() !== "none" &&
|
|
475
|
+
getIsolationMode(base) !== "none" &&
|
|
473
476
|
!detectWorktreeName(base) &&
|
|
474
477
|
!base.includes(`${pathSep}.gsd${pathSep}worktrees${pathSep}`)
|
|
475
478
|
) {
|
|
@@ -664,15 +667,16 @@ export async function bootstrapAutoSession(
|
|
|
664
667
|
s.pendingQuickTasks = [];
|
|
665
668
|
s.currentUnit = null;
|
|
666
669
|
s.currentMilestoneId = state.activeMilestone?.id ?? null;
|
|
667
|
-
s.originalModelId = ctx.model?.id ?? null;
|
|
668
|
-
s.originalModelProvider = ctx.model?.provider ?? null;
|
|
670
|
+
s.originalModelId = startModelSnapshot?.id ?? ctx.model?.id ?? null;
|
|
671
|
+
s.originalModelProvider = startModelSnapshot?.provider ?? ctx.model?.provider ?? null;
|
|
672
|
+
s.originalThinkingLevel = startThinkingSnapshot ?? null;
|
|
669
673
|
|
|
670
674
|
// Register SIGTERM handler
|
|
671
675
|
registerSigtermHandler(base);
|
|
672
676
|
|
|
673
677
|
// Capture integration branch
|
|
674
678
|
if (s.currentMilestoneId) {
|
|
675
|
-
if (getIsolationMode() !== "none") {
|
|
679
|
+
if (getIsolationMode(base) !== "none") {
|
|
676
680
|
captureIntegrationBranch(base, s.currentMilestoneId);
|
|
677
681
|
}
|
|
678
682
|
setActiveMilestoneId(base, s.currentMilestoneId);
|
|
@@ -681,7 +685,7 @@ export async function bootstrapAutoSession(
|
|
|
681
685
|
// Guard against stale milestone branch when isolation:none (#3613).
|
|
682
686
|
// A prior session with isolation:branch/worktree may have left HEAD on
|
|
683
687
|
// milestone/<MID>. Auto-checkout back to the integration branch.
|
|
684
|
-
if (getIsolationMode() === "none" && nativeIsRepo(base)) {
|
|
688
|
+
if (getIsolationMode(base) === "none" && nativeIsRepo(base)) {
|
|
685
689
|
try {
|
|
686
690
|
const currentBranch = nativeGetCurrentBranch(base);
|
|
687
691
|
if (currentBranch.startsWith("milestone/")) {
|
|
@@ -712,7 +716,7 @@ export async function bootstrapAutoSession(
|
|
|
712
716
|
|
|
713
717
|
if (
|
|
714
718
|
s.currentMilestoneId &&
|
|
715
|
-
getIsolationMode() !== "none" &&
|
|
719
|
+
getIsolationMode(base) !== "none" &&
|
|
716
720
|
!detectWorktreeName(base) &&
|
|
717
721
|
!isUnderGsdWorktrees(base)
|
|
718
722
|
) {
|
|
@@ -758,9 +762,22 @@ export async function bootstrapAutoSession(
|
|
|
758
762
|
// call returns "db_unavailable", triggering artifact-retry which
|
|
759
763
|
// re-dispatches the same task — producing an infinite loop (#2419).
|
|
760
764
|
if (existsSync(gsdDbPath) && !isDbAvailable()) {
|
|
765
|
+
const dbStatus = getDbStatus();
|
|
766
|
+
const phaseHint = dbStatus.lastPhase === "open"
|
|
767
|
+
? "The database file could not be opened"
|
|
768
|
+
: dbStatus.lastPhase === "initSchema"
|
|
769
|
+
? "The database schema could not be initialized"
|
|
770
|
+
: dbStatus.lastPhase === "vacuum-recovery"
|
|
771
|
+
? "Corruption recovery (VACUUM) failed"
|
|
772
|
+
: dbStatus.attempted
|
|
773
|
+
? "The database could not be opened (phase unknown)"
|
|
774
|
+
: "The database provider could not be loaded";
|
|
775
|
+
const errorDetail = dbStatus.lastError ? ` (${dbStatus.lastError.message})` : "";
|
|
776
|
+
const providerHint = dbStatus.provider
|
|
777
|
+
? ` Provider: ${dbStatus.provider}.`
|
|
778
|
+
: " No SQLite provider available — check Node >= 22 or install better-sqlite3.";
|
|
761
779
|
ctx.ui.notify(
|
|
762
|
-
|
|
763
|
-
"Check for corrupt gsd.db or missing native SQLite bindings.",
|
|
780
|
+
`SQLite database exists but failed to open: ${gsdDbPath}. ${phaseHint}${errorDetail}.${providerHint}`,
|
|
764
781
|
"error",
|
|
765
782
|
);
|
|
766
783
|
return releaseLockAndReturn();
|
|
@@ -779,6 +796,7 @@ export async function bootstrapAutoSession(
|
|
|
779
796
|
id: startModelSnapshot.id,
|
|
780
797
|
};
|
|
781
798
|
}
|
|
799
|
+
s.autoModeStartThinkingLevel = startThinkingSnapshot ?? null;
|
|
782
800
|
s.manualSessionModelOverride = manualSessionOverride ?? null;
|
|
783
801
|
|
|
784
802
|
// Apply worker model override from parallel orchestrator (#worker-model).
|
|
@@ -801,7 +819,7 @@ export async function bootstrapAutoSession(
|
|
|
801
819
|
}
|
|
802
820
|
|
|
803
821
|
// Snapshot installed skills
|
|
804
|
-
if (resolveSkillDiscoveryMode() !== "off") {
|
|
822
|
+
if (resolveSkillDiscoveryMode(base) !== "off") {
|
|
805
823
|
snapshotSkills();
|
|
806
824
|
}
|
|
807
825
|
|
|
@@ -835,7 +853,7 @@ export async function bootstrapAutoSession(
|
|
|
835
853
|
// FlatRateContext used by selectAndApplyModel so user-declared
|
|
836
854
|
// flat-rate providers and externalCli auto-detection are respected.
|
|
837
855
|
const { isFlatRateProvider, buildFlatRateContext } = await import("./auto-model-selection.js");
|
|
838
|
-
const bannerPrefs = loadEffectiveGSDPreferences()?.preferences;
|
|
856
|
+
const bannerPrefs = loadEffectiveGSDPreferences(base)?.preferences;
|
|
839
857
|
const effectiveProvider = s.autoModeStartModel?.provider ?? ctx.model?.provider;
|
|
840
858
|
const effectivelyEnabled = routingConfig.enabled
|
|
841
859
|
&& (routingConfig.allow_flat_rate_providers
|