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.
Files changed (200) hide show
  1. package/dist/mcp-server.d.ts +7 -0
  2. package/dist/mcp-server.js +35 -1
  3. package/dist/resource-loader.d.ts +1 -1
  4. package/dist/resource-loader.js +2 -8
  5. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +66 -4
  6. package/dist/resources/extensions/gsd/auto/phases.js +4 -1
  7. package/dist/resources/extensions/gsd/auto/session.js +4 -0
  8. package/dist/resources/extensions/gsd/auto-model-selection.js +39 -13
  9. package/dist/resources/extensions/gsd/auto-start.js +39 -21
  10. package/dist/resources/extensions/gsd/auto.js +15 -12
  11. package/dist/resources/extensions/gsd/blocked-models.js +68 -0
  12. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +76 -0
  13. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +39 -9
  14. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +93 -0
  15. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +2 -0
  16. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +35 -0
  17. package/dist/resources/extensions/gsd/compaction-snapshot.js +121 -0
  18. package/dist/resources/extensions/gsd/complexity-classifier.js +5 -3
  19. package/dist/resources/extensions/gsd/error-classifier.js +31 -3
  20. package/dist/resources/extensions/gsd/exec-history.js +120 -0
  21. package/dist/resources/extensions/gsd/exec-sandbox.js +258 -0
  22. package/dist/resources/extensions/gsd/gsd-db.js +62 -4
  23. package/dist/resources/extensions/gsd/init-wizard.js +15 -1
  24. package/dist/resources/extensions/gsd/key-manager.js +6 -0
  25. package/dist/resources/extensions/gsd/pre-execution-checks.js +13 -3
  26. package/dist/resources/extensions/gsd/preferences-types.js +9 -0
  27. package/dist/resources/extensions/gsd/preferences-validation.js +83 -0
  28. package/dist/resources/extensions/gsd/preferences.js +17 -17
  29. package/dist/resources/extensions/gsd/prompt-loader.js +22 -7
  30. package/dist/resources/extensions/gsd/safety/file-change-validator.js +1 -1
  31. package/dist/resources/extensions/gsd/tools/exec-search-tool.js +59 -0
  32. package/dist/resources/extensions/gsd/tools/exec-tool.js +126 -0
  33. package/dist/resources/extensions/gsd/tools/resume-tool.js +23 -0
  34. package/dist/resources/extensions/gsd/workflow-mcp.js +3 -0
  35. package/dist/resources/extensions/search-the-web/command-search-provider.js +5 -4
  36. package/dist/resources/extensions/search-the-web/native-search.js +45 -13
  37. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  38. package/dist/web/standalone/.next/BUILD_ID +1 -1
  39. package/dist/web/standalone/.next/app-path-routes-manifest.json +8 -8
  40. package/dist/web/standalone/.next/build-manifest.json +2 -2
  41. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  42. package/dist/web/standalone/.next/required-server-files.json +1 -1
  43. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  44. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  47. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  52. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  56. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/index.html +1 -1
  60. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app-paths-manifest.json +8 -8
  67. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  68. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  69. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  70. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  71. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  72. package/dist/web/standalone/server.js +1 -1
  73. package/package.json +1 -1
  74. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  75. package/packages/mcp-server/dist/workflow-tools.js +64 -25
  76. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  77. package/packages/mcp-server/src/workflow-tools.test.ts +146 -1
  78. package/packages/mcp-server/src/workflow-tools.ts +84 -43
  79. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  80. package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  81. package/packages/pi-ai/dist/providers/openai-completions.js +60 -15
  82. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  83. package/packages/pi-ai/dist/providers/think-tag-parser.d.ts +17 -0
  84. package/packages/pi-ai/dist/providers/think-tag-parser.d.ts.map +1 -0
  85. package/packages/pi-ai/dist/providers/think-tag-parser.js +75 -0
  86. package/packages/pi-ai/dist/providers/think-tag-parser.js.map +1 -0
  87. package/packages/pi-ai/dist/providers/think-tag-parser.test.d.ts +2 -0
  88. package/packages/pi-ai/dist/providers/think-tag-parser.test.d.ts.map +1 -0
  89. package/packages/pi-ai/dist/providers/think-tag-parser.test.js +41 -0
  90. package/packages/pi-ai/dist/providers/think-tag-parser.test.js.map +1 -0
  91. package/packages/pi-ai/src/providers/openai-completions.ts +57 -16
  92. package/packages/pi-ai/src/providers/think-tag-parser.test.ts +44 -0
  93. package/packages/pi-ai/src/providers/think-tag-parser.ts +94 -0
  94. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  95. package/packages/pi-coding-agent/dist/core/model-discovery.d.ts +3 -1
  96. package/packages/pi-coding-agent/dist/core/model-discovery.d.ts.map +1 -1
  97. package/packages/pi-coding-agent/dist/core/model-discovery.js +92 -12
  98. package/packages/pi-coding-agent/dist/core/model-discovery.js.map +1 -1
  99. package/packages/pi-coding-agent/dist/core/model-discovery.test.js +16 -1
  100. package/packages/pi-coding-agent/dist/core/model-discovery.test.js.map +1 -1
  101. package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js +61 -1
  102. package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js.map +1 -1
  103. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +5 -0
  104. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  105. package/packages/pi-coding-agent/dist/core/model-registry.js +76 -10
  106. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  107. package/packages/pi-coding-agent/dist/core/redact-secrets.d.ts +2 -0
  108. package/packages/pi-coding-agent/dist/core/redact-secrets.d.ts.map +1 -0
  109. package/packages/pi-coding-agent/dist/core/redact-secrets.js +49 -0
  110. package/packages/pi-coding-agent/dist/core/redact-secrets.js.map +1 -0
  111. package/packages/pi-coding-agent/dist/core/redact-secrets.test.d.ts +2 -0
  112. package/packages/pi-coding-agent/dist/core/redact-secrets.test.d.ts.map +1 -0
  113. package/packages/pi-coding-agent/dist/core/redact-secrets.test.js +67 -0
  114. package/packages/pi-coding-agent/dist/core/redact-secrets.test.js.map +1 -0
  115. package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
  116. package/packages/pi-coding-agent/dist/core/session-manager.js +9 -5
  117. package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
  118. package/packages/pi-coding-agent/dist/core/session-manager.test.js +25 -1
  119. package/packages/pi-coding-agent/dist/core/session-manager.test.js.map +1 -1
  120. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts +1 -1
  121. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts.map +1 -1
  122. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js +5 -4
  123. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js.map +1 -1
  124. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -1
  125. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +13 -7
  126. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -1
  127. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.d.ts +7 -6
  128. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  129. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js +29 -21
  130. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  131. package/packages/pi-coding-agent/src/core/model-discovery.test.ts +19 -0
  132. package/packages/pi-coding-agent/src/core/model-discovery.ts +99 -12
  133. package/packages/pi-coding-agent/src/core/model-registry-discovery.test.ts +75 -0
  134. package/packages/pi-coding-agent/src/core/model-registry.ts +86 -10
  135. package/packages/pi-coding-agent/src/core/redact-secrets.test.ts +86 -0
  136. package/packages/pi-coding-agent/src/core/redact-secrets.ts +58 -0
  137. package/packages/pi-coding-agent/src/core/session-manager.test.ts +36 -1
  138. package/packages/pi-coding-agent/src/core/session-manager.ts +9 -5
  139. package/packages/pi-coding-agent/src/modes/interactive/components/chat-frame.ts +6 -6
  140. package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +16 -7
  141. package/packages/pi-coding-agent/src/modes/interactive/components/skill-invocation-message.ts +36 -22
  142. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  143. package/scripts/link-workspace-packages.cjs +1 -0
  144. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +67 -4
  145. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +137 -2
  146. package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
  147. package/src/resources/extensions/gsd/auto/phases.ts +4 -0
  148. package/src/resources/extensions/gsd/auto/session.ts +7 -1
  149. package/src/resources/extensions/gsd/auto-model-selection.ts +50 -12
  150. package/src/resources/extensions/gsd/auto-start.ts +40 -22
  151. package/src/resources/extensions/gsd/auto.ts +15 -12
  152. package/src/resources/extensions/gsd/blocked-models.ts +98 -0
  153. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +97 -0
  154. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +40 -9
  155. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +109 -0
  156. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +2 -0
  157. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +36 -0
  158. package/src/resources/extensions/gsd/compaction-snapshot.ts +165 -0
  159. package/src/resources/extensions/gsd/complexity-classifier.ts +5 -3
  160. package/src/resources/extensions/gsd/error-classifier.ts +36 -3
  161. package/src/resources/extensions/gsd/exec-history.ts +153 -0
  162. package/src/resources/extensions/gsd/exec-sandbox.ts +326 -0
  163. package/src/resources/extensions/gsd/gsd-db.ts +68 -4
  164. package/src/resources/extensions/gsd/init-wizard.ts +15 -1
  165. package/src/resources/extensions/gsd/key-manager.ts +6 -0
  166. package/src/resources/extensions/gsd/pre-execution-checks.ts +13 -3
  167. package/src/resources/extensions/gsd/preferences-types.ts +38 -0
  168. package/src/resources/extensions/gsd/preferences-validation.ts +79 -0
  169. package/src/resources/extensions/gsd/preferences.ts +17 -17
  170. package/src/resources/extensions/gsd/prompt-loader.ts +30 -7
  171. package/src/resources/extensions/gsd/safety/file-change-validator.ts +1 -1
  172. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +12 -0
  173. package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +33 -3
  174. package/src/resources/extensions/gsd/tests/auto-thinking-restore.test.ts +38 -0
  175. package/src/resources/extensions/gsd/tests/blocked-models.test.ts +98 -0
  176. package/src/resources/extensions/gsd/tests/compaction-snapshot.test.ts +123 -0
  177. package/src/resources/extensions/gsd/tests/complexity-classifier.test.ts +3 -3
  178. package/src/resources/extensions/gsd/tests/exec-history.test.ts +124 -0
  179. package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +210 -0
  180. package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +20 -0
  181. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +151 -0
  182. package/src/resources/extensions/gsd/tests/init-wizard.test.ts +27 -0
  183. package/src/resources/extensions/gsd/tests/isolation-none-branch-guard.test.ts +1 -1
  184. package/src/resources/extensions/gsd/tests/key-manager.test.ts +7 -0
  185. package/src/resources/extensions/gsd/tests/pre-exec-backtick-strip.test.ts +14 -0
  186. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +19 -0
  187. package/src/resources/extensions/gsd/tests/preferences.test.ts +110 -0
  188. package/src/resources/extensions/gsd/tests/prompt-loader-extension-dir.test.ts +49 -0
  189. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +91 -0
  190. package/src/resources/extensions/gsd/tests/save-gate-result-render.test.ts +95 -0
  191. package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +3 -1
  192. package/src/resources/extensions/gsd/tools/exec-search-tool.ts +81 -0
  193. package/src/resources/extensions/gsd/tools/exec-tool.ts +183 -0
  194. package/src/resources/extensions/gsd/tools/resume-tool.ts +40 -0
  195. package/src/resources/extensions/gsd/workflow-logger.ts +2 -1
  196. package/src/resources/extensions/gsd/workflow-mcp.ts +3 -0
  197. package/src/resources/extensions/search-the-web/command-search-provider.ts +5 -4
  198. package/src/resources/extensions/search-the-web/native-search.ts +48 -12
  199. /package/dist/web/standalone/.next/static/{pBwmOoye64ZrRp-_rf0v1 → n21VtX2hZlkpdEUO_nU4z}/_buildManifest.js +0 -0
  200. /package/dist/web/standalone/.next/static/{pBwmOoye64ZrRp-_rf0v1 → n21VtX2hZlkpdEUO_nU4z}/_ssgManifest.js +0 -0
@@ -1,6 +1,11 @@
1
1
  /**
2
2
  * Minimal tool interface matching GSD's AgentTool shape.
3
3
  * Avoids a direct dependency on @gsd/pi-agent-core from this compiled module.
4
+ *
5
+ * `details` and `isError` are optional fields that runtime tool implementations
6
+ * may populate. The MCP transport drops non-standard fields, so the wrapper at
7
+ * the call site mirrors `details` into `structuredContent` and forwards
8
+ * `isError` directly. See #4472.
4
9
  */
5
10
  export interface McpToolDef {
6
11
  name: string;
@@ -13,6 +18,8 @@ export interface McpToolDef {
13
18
  data?: string;
14
19
  mimeType?: string;
15
20
  }>;
21
+ details?: Record<string, unknown>;
22
+ isError?: boolean;
16
23
  }>;
17
24
  }
18
25
  /**
@@ -1,3 +1,21 @@
1
+ /**
2
+ * Strict plain-object guard. True only for object literals and
3
+ * `Object.create(null)` — not for `Date`, `URL`, `Map`, `Set`, class instances,
4
+ * or arrays. Used to gate `structuredContent` forwarding so the MCP transport
5
+ * receives only true JSON objects (the protocol contract). See #4477 review.
6
+ *
7
+ * Mirrored in `packages/mcp-server/src/workflow-tools.ts` for the
8
+ * `adaptExecutorResult` adapter on the workflow path. Keep both copies in
9
+ * sync if the contract definition needs to evolve.
10
+ */
11
+ function isPlainObject(value) {
12
+ if (value === null || typeof value !== 'object')
13
+ return false;
14
+ if (Array.isArray(value))
15
+ return false;
16
+ const proto = Object.getPrototypeOf(value);
17
+ return proto === null || proto === Object.prototype;
18
+ }
1
19
  // MCP SDK subpath imports use wildcard exports (./*) in @modelcontextprotocol/sdk's
2
20
  // package.json export map. The wildcard maps "./foo" → "./dist/cjs/foo" (no .js
3
21
  // suffix), so bare subpath specifiers like `${MCP_PKG}/server/stdio` resolve to
@@ -83,7 +101,23 @@ export async function startMcpServer(options) {
83
101
  // by stringifying into a text block so clients see the payload.
84
102
  return { type: 'text', text: JSON.stringify(block) };
85
103
  });
86
- return { content };
104
+ // Forward a tool's runtime `details` field to MCP's `structuredContent`
105
+ // channel. The protocol drops non-standard fields on the wire, so tools
106
+ // that populate `details` for client-side renderers (e.g. save_gate_result)
107
+ // would otherwise arrive empty on the other side. See #4472.
108
+ //
109
+ // Use a strict plain-object guard (prototype-chain check) rather than just
110
+ // `typeof === 'object' && !Array.isArray()` — Date, URL, Map, Set, and
111
+ // class instances would otherwise pass through and end up as
112
+ // `structuredContent`, violating the protocol's JSON-object contract.
113
+ // The mirror discipline applies in `workflow-tools.ts adaptExecutorResult`.
114
+ const base = { content };
115
+ if (isPlainObject(result.details)) {
116
+ base.structuredContent = result.details;
117
+ }
118
+ if (result.isError === true)
119
+ base.isError = true;
120
+ return base;
87
121
  }
88
122
  catch (err) {
89
123
  // AbortError from a cancelled tool surfaces as a normal error — MCP
@@ -21,6 +21,6 @@ export declare function getNewerManagedResourceVersion(agentDir: string, current
21
21
  *
22
22
  * Inspectable: `ls ~/.gsd/agent/extensions/`
23
23
  */
24
- export declare function initResources(agentDir: string): void;
24
+ export declare function initResources(agentDir: string, skillsDir?: string): void;
25
25
  export declare function hasStaleCompiledExtensionSiblings(extensionsDir: string, sourceDir?: string): boolean;
26
26
  export declare function buildResourceLoader(agentDir: string): DefaultResourceLoader;
@@ -506,7 +506,7 @@ function pruneRemovedBundledExtensions(manifest, agentDir) {
506
506
  *
507
507
  * Inspectable: `ls ~/.gsd/agent/extensions/`
508
508
  */
509
- export function initResources(agentDir) {
509
+ export function initResources(agentDir, skillsDir = join(homedir(), '.agents', 'skills')) {
510
510
  mkdirSync(agentDir, { recursive: true });
511
511
  const currentVersion = getBundledGsdVersion();
512
512
  const manifest = readManagedResourceManifest(agentDir);
@@ -538,13 +538,7 @@ export function initResources(agentDir) {
538
538
  // Sync bundled resources — overwrite so updates land on next launch.
539
539
  syncResourceDir(bundledExtensionsDir, join(agentDir, 'extensions'));
540
540
  syncResourceDir(join(resourcesDir, 'agents'), join(agentDir, 'agents'));
541
- // Skills are no longer force-synced here. Users install skills via the
542
- // skills.sh CLI (`npx skills add <repo>`) into ~/.agents/skills/ which
543
- // is the industry-standard Agent Skills ecosystem directory.
544
- //
545
- // Migration from the legacy ~/.gsd/agent/skills/ directory is handled
546
- // above the manifest check so it runs on every launch (including retries
547
- // after partial copy failures).
541
+ syncResourceDir(join(resourcesDir, 'skills'), skillsDir);
548
542
  // Sync GSD-WORKFLOW.md to agentDir as a fallback for when GSD_WORKFLOW_PATH
549
543
  // env var is not set (e.g. fork/dev builds, alternative entry points).
550
544
  const workflowSrc = join(resourcesDir, 'GSD-WORKFLOW.md');
@@ -637,6 +637,68 @@ function normalizeToolResultContent(content) {
637
637
  }
638
638
  return blocks.length > 0 ? blocks : [{ type: "text", text: "" }];
639
639
  }
640
+ /**
641
+ * Extract a `details` payload from an MCP tool-result block.
642
+ *
643
+ * MCP's `CallToolResult` carries structured data in `structuredContent` — the
644
+ * protocol's supported channel for non-text payloads. Claude Code's synthetic
645
+ * user message may surface that field in one of two shapes depending on SDK
646
+ * version: as a sibling on the `mcp_tool_result` block itself, or as a
647
+ * dedicated content sub-block with `type: "structuredContent"`. Snake-case
648
+ * (`structured_content`) is accepted defensively in case a transport hop
649
+ * rewrites casing. All other shapes fall back to an empty object so callers
650
+ * can rely on `details` being present.
651
+ */
652
+ function extractStructuredDetailsFromBlock(block) {
653
+ const sibling = block.structuredContent ?? block.structured_content;
654
+ if (sibling && typeof sibling === "object" && !Array.isArray(sibling)) {
655
+ return sibling;
656
+ }
657
+ if (Array.isArray(block.content)) {
658
+ for (const item of block.content) {
659
+ if (!item || typeof item !== "object")
660
+ continue;
661
+ const sub = item;
662
+ if (sub.type !== "structuredContent" && sub.type !== "structured_content")
663
+ continue;
664
+ const payload = sub.structuredContent ?? sub.structured_content ?? sub.data ?? sub.value;
665
+ if (payload && typeof payload === "object" && !Array.isArray(payload)) {
666
+ return payload;
667
+ }
668
+ }
669
+ }
670
+ // Return undefined (not {}) when no structured payload is present, matching
671
+ // the pre-#4477 contract where `details` was nullable. An empty-object
672
+ // sentinel is truthy and breaks downstream consumers that gate on
673
+ // `if (details)`. `undefined` matches the type of the field these results
674
+ // flow into (`Record<string, unknown> | undefined`).
675
+ return undefined;
676
+ }
677
+ /**
678
+ * True for items that are MCP `structuredContent` pseudo-blocks living inside
679
+ * a tool-result `content[]` array. These blocks carry the structured payload
680
+ * (extracted separately by `extractStructuredDetailsFromBlock`) and must NOT
681
+ * leak into the visible content rendered to the user — otherwise the renderer
682
+ * stringifies the JSON pseudo-block and shows it next to the actual tool
683
+ * output. See PR #4477 review (CodeRabbit, post-fix-round).
684
+ */
685
+ function isStructuredContentPseudoBlock(item) {
686
+ if (!item || typeof item !== "object")
687
+ return false;
688
+ const type = item.type;
689
+ return type === "structuredContent" || type === "structured_content";
690
+ }
691
+ /**
692
+ * Strip `structuredContent` pseudo-blocks from a tool-result content array
693
+ * before normalization. The structured payload is extracted via the sibling
694
+ * `structuredContent` field (or a dedicated extractor pass on the raw block);
695
+ * the visible content path must not include the pseudo-block itself.
696
+ */
697
+ function stripStructuredContentPseudoBlocks(content) {
698
+ if (!Array.isArray(content))
699
+ return content;
700
+ return content.filter((item) => !isStructuredContentPseudoBlock(item));
701
+ }
640
702
  /** Extract tool result payloads from an SDK synthetic user message, keyed by tool-use ID. */
641
703
  export function extractToolResultsFromSdkUserMessage(message) {
642
704
  const extracted = [];
@@ -657,8 +719,8 @@ export function extractToolResultsFromSdkUserMessage(message) {
657
719
  extracted.push({
658
720
  toolUseId,
659
721
  result: {
660
- content: normalizeToolResultContent(block.content),
661
- details: {},
722
+ content: normalizeToolResultContent(stripStructuredContentPseudoBlocks(block.content)),
723
+ details: extractStructuredDetailsFromBlock(block),
662
724
  isError: block.is_error === true,
663
725
  },
664
726
  });
@@ -672,8 +734,8 @@ export function extractToolResultsFromSdkUserMessage(message) {
672
734
  extracted.push({
673
735
  toolUseId,
674
736
  result: {
675
- content: normalizeToolResultContent(toolResult.content),
676
- details: {},
737
+ content: normalizeToolResultContent(stripStructuredContentPseudoBlocks(toolResult.content)),
738
+ details: extractStructuredDetailsFromBlock(toolResult),
677
739
  isError: toolResult.is_error === true,
678
740
  },
679
741
  });
@@ -1094,7 +1094,7 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
1094
1094
  logWarning("engine", "Prompt reorder failed", { error: msg });
1095
1095
  }
1096
1096
  // Select and apply model (with tier escalation on retry — normal units only)
1097
- const modelResult = await deps.selectAndApplyModel(ctx, pi, unitType, unitId, s.basePath, prefs, s.verbose, s.autoModeStartModel, sidecarItem ? undefined : { isRetry, previousTier }, undefined, s.manualSessionModelOverride);
1097
+ const modelResult = await deps.selectAndApplyModel(ctx, pi, unitType, unitId, s.basePath, prefs, s.verbose, s.autoModeStartModel, sidecarItem ? undefined : { isRetry, previousTier }, undefined, s.manualSessionModelOverride, s.autoModeStartThinkingLevel);
1098
1098
  s.currentUnitRouting =
1099
1099
  modelResult.routing;
1100
1100
  s.currentUnitModel =
@@ -1107,6 +1107,9 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
1107
1107
  if (match) {
1108
1108
  const ok = await pi.setModel(match, { persist: false });
1109
1109
  if (ok) {
1110
+ if (s.autoModeStartThinkingLevel) {
1111
+ pi.setThinkingLevel(s.autoModeStartThinkingLevel);
1112
+ }
1110
1113
  s.currentUnitModel = match;
1111
1114
  ctx.ui.notify(`Hook model override: ${match.provider}/${match.id}`, "info");
1112
1115
  }
@@ -65,6 +65,8 @@ export class AutoSession {
65
65
  currentDispatchedModelId = null;
66
66
  originalModelId = null;
67
67
  originalModelProvider = null;
68
+ autoModeStartThinkingLevel = null;
69
+ originalThinkingLevel = null;
68
70
  lastBudgetAlertLevel = 0;
69
71
  // ── Recovery ─────────────────────────────────────────────────────────────
70
72
  pendingCrashRecovery = null;
@@ -177,6 +179,8 @@ export class AutoSession {
177
179
  this.currentDispatchedModelId = null;
178
180
  this.originalModelId = null;
179
181
  this.originalModelProvider = null;
182
+ this.autoModeStartThinkingLevel = null;
183
+ this.originalThinkingLevel = null;
180
184
  this.lastBudgetAlertLevel = 0;
181
185
  // Recovery
182
186
  this.pendingCrashRecovery = null;
@@ -12,6 +12,12 @@ import { getSessionModelOverride } from "./session-model-override.js";
12
12
  import { logWarning } from "./workflow-logger.js";
13
13
  import { resolveUokFlags } from "./uok/flags.js";
14
14
  import { applyModelPolicyFilter } from "./uok/model-policy.js";
15
+ import { isModelBlocked } from "./blocked-models.js";
16
+ function reapplyThinkingLevel(pi, level) {
17
+ if (!level)
18
+ return;
19
+ pi.setThinkingLevel(level);
20
+ }
15
21
  export function resolvePreferredModelConfig(unitType, autoModeStartModel, isAutoMode = true) {
16
22
  const explicitConfig = resolveModelWithFallbacksForUnit(unitType);
17
23
  if (explicitConfig) {
@@ -57,7 +63,9 @@ export async function selectAndApplyModel(ctx, pi, unitType, unitId, basePath, p
57
63
  * Dynamic routing only applies in auto-mode where cost optimization is expected. (#3962) */
58
64
  isAutoMode = true,
59
65
  /** Explicit /gsd model pin captured at bootstrap for long-running auto loops. */
60
- sessionModelOverride) {
66
+ sessionModelOverride,
67
+ /** Thinking level captured at auto-mode start and re-applied after model swaps. */
68
+ autoModeStartThinkingLevel) {
61
69
  const uokFlags = resolveUokFlags(prefs);
62
70
  const effectiveSessionModelOverride = sessionModelOverride === undefined
63
71
  ? getSessionModelOverride(ctx.sessionManager.getSessionId())
@@ -260,6 +268,14 @@ sessionModelOverride) {
260
268
  }
261
269
  attemptedPolicyEligible = true;
262
270
  }
271
+ // Skip models the provider has previously rejected for this account
272
+ // (issue #4513). The block is persisted in .gsd/runtime/blocked-models.json
273
+ // so it survives /gsd auto restarts — without this, the same dead model
274
+ // gets reselected after every restart.
275
+ if (isModelBlocked(basePath, model.provider, model.id)) {
276
+ ctx.ui.notify(`Skipping blocked model ${model.provider}/${model.id} (provider rejected it for this account).`, "warning");
277
+ continue;
278
+ }
263
279
  // Warn if the ID is ambiguous across providers
264
280
  if (!modelId.includes("/")) {
265
281
  const providers = availableModels.filter(m => m.id === modelId).map(m => m.provider);
@@ -271,6 +287,7 @@ sessionModelOverride) {
271
287
  const ok = await pi.setModel(model, { persist: false });
272
288
  if (ok) {
273
289
  appliedModel = model;
290
+ reapplyThinkingLevel(pi, autoModeStartThinkingLevel);
274
291
  // ADR-005: Adjust active tool set for the selected model's provider capabilities.
275
292
  // Hard-filter incompatible tools, then let extensions override via adjust_tool_set hook.
276
293
  const activeToolNames = pi.getActiveTools();
@@ -325,19 +342,28 @@ sessionModelOverride) {
325
342
  // No model preference for this unit type — re-apply the model captured
326
343
  // at auto-mode start to prevent bleed from shared global settings.json (#650).
327
344
  const availableModels = ctx.modelRegistry.getAvailable();
328
- const startModel = availableModels.find(m => m.provider === autoModeStartModel.provider && m.id === autoModeStartModel.id);
329
- if (startModel) {
330
- const ok = await pi.setModel(startModel, { persist: false });
331
- if (!ok) {
332
- const byId = availableModels.find(m => m.id === autoModeStartModel.id);
333
- if (byId) {
334
- const fallbackOk = await pi.setModel(byId, { persist: false });
335
- if (fallbackOk)
336
- appliedModel = byId;
345
+ const startBlocked = isModelBlocked(basePath, autoModeStartModel.provider, autoModeStartModel.id);
346
+ if (startBlocked) {
347
+ ctx.ui.notify(`Auto-mode start model ${autoModeStartModel.provider}/${autoModeStartModel.id} is blocked for this account. Using current session model instead.`, "warning");
348
+ }
349
+ else {
350
+ const startModel = availableModels.find(m => m.provider === autoModeStartModel.provider && m.id === autoModeStartModel.id);
351
+ if (startModel) {
352
+ const ok = await pi.setModel(startModel, { persist: false });
353
+ if (!ok) {
354
+ const byId = availableModels.find(m => m.id === autoModeStartModel.id && !isModelBlocked(basePath, m.provider, m.id));
355
+ if (byId) {
356
+ const fallbackOk = await pi.setModel(byId, { persist: false });
357
+ if (fallbackOk) {
358
+ appliedModel = byId;
359
+ reapplyThinkingLevel(pi, autoModeStartThinkingLevel);
360
+ }
361
+ }
362
+ }
363
+ else {
364
+ appliedModel = startModel;
365
+ reapplyThinkingLevel(pi, autoModeStartThinkingLevel);
337
366
  }
338
- }
339
- else {
340
- appliedModel = startModel;
341
367
  }
342
368
  }
343
369
  }
@@ -30,7 +30,7 @@ import { initRoutingHistory } from "./routing-history.js";
30
30
  import { restoreHookState, resetHookState } from "./post-unit-hooks.js";
31
31
  import { resetProactiveHealing, setLevelChangeCallback } from "./doctor-proactive.js";
32
32
  import { snapshotSkills } from "./skill-discovery.js";
33
- import { isDbAvailable, getMilestone, openDatabase } from "./gsd-db.js";
33
+ import { isDbAvailable, getMilestone, openDatabase, getDbStatus } from "./gsd-db.js";
34
34
  import { hideFooter } from "./auto-dashboard.js";
35
35
  import { debugLog, enableDebug, isDebugEnabled, getDebugLogPath, } from "./debug-logger.js";
36
36
  import { logWarning, logError } from "./workflow-logger.js";
@@ -190,8 +190,8 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
190
190
  //
191
191
  // Precedence:
192
192
  // 1) Explicit session override via /gsd model (this session)
193
- // 2) GSD model preferences from PREFERENCES.md (validated against live auth)
194
- // 3) Current session model from settings/session restore (if provider ready)
193
+ // 2) Current session model from settings/session restore (if provider ready)
194
+ // 3) GSD model preferences from PREFERENCES.md (validated against live auth)
195
195
  //
196
196
  // This preserves #3517 defaults while honoring explicit runtime model
197
197
  // selection for subsequent /gsd runs in the same session.
@@ -224,11 +224,14 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
224
224
  }
225
225
  }
226
226
  const sessionModelReady = ctx.model && ctx.modelRegistry.isProviderRequestReady(ctx.model.provider);
227
+ const currentSessionModel = (sessionModelReady && ctx.model)
228
+ ? { provider: ctx.model.provider, id: ctx.model.id }
229
+ : null;
230
+ const startThinkingSnapshot = pi.getThinkingLevel();
227
231
  const startModelSnapshot = manualSessionOverride
232
+ ?? currentSessionModel
228
233
  ?? validatedPreferredModel
229
- ?? (sessionModelReady && ctx.model
230
- ? { provider: ctx.model.provider, id: ctx.model.id }
231
- : null);
234
+ ?? null;
232
235
  try {
233
236
  // Validate GSD_PROJECT_ID early so the user gets immediate feedback
234
237
  const customProjectId = process.env.GSD_PROJECT_ID;
@@ -244,7 +247,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
244
247
  // the parent git root). See #2393 and related issue.
245
248
  const hasLocalGit = existsSync(join(base, ".git"));
246
249
  if (!hasLocalGit || isInheritedRepo(base)) {
247
- const mainBranch = loadEffectiveGSDPreferences()?.preferences?.git?.main_branch || "main";
250
+ const mainBranch = loadEffectiveGSDPreferences(base)?.preferences?.git?.main_branch || "main";
248
251
  nativeInit(base, mainBranch);
249
252
  }
250
253
  // Migrate legacy in-project .gsd/ to external state directory.
@@ -260,7 +263,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
260
263
  // Ensure .gitignore has baseline patterns.
261
264
  // ensureGitignore checks for git-tracked .gsd/ files and skips the
262
265
  // ".gsd" pattern if the project intentionally tracks .gsd/ in git.
263
- const gitPrefs = loadEffectiveGSDPreferences()?.preferences?.git;
266
+ const gitPrefs = loadEffectiveGSDPreferences(base)?.preferences?.git;
264
267
  const manageGitignore = gitPrefs?.manage_gitignore;
265
268
  ensureGitignore(base, { manageGitignore });
266
269
  if (manageGitignore !== false)
@@ -286,7 +289,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
286
289
  prepareWorkflowMcpForProject(ctx, base);
287
290
  }
288
291
  // Initialize GitServiceImpl
289
- s.gitService = new GitServiceImpl(s.basePath, loadEffectiveGSDPreferences()?.preferences?.git ?? {});
292
+ s.gitService = new GitServiceImpl(s.basePath, loadEffectiveGSDPreferences(base)?.preferences?.git ?? {});
290
293
  // ── Debug mode ──
291
294
  if (!isDebugEnabled() && process.env.GSD_DEBUG === "1") {
292
295
  enableDebug(base);
@@ -319,7 +322,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
319
322
  // was lost due to session ending between completion and teardown.
320
323
  // Must run after DB open and before worktree entry.
321
324
  try {
322
- const auditResult = auditOrphanedMilestoneBranches(base, getIsolationMode());
325
+ const auditResult = auditOrphanedMilestoneBranches(base, getIsolationMode(base));
323
326
  for (const msg of auditResult.recovered) {
324
327
  ctx.ui.notify(`Orphan audit: ${msg}`, "info");
325
328
  }
@@ -337,7 +340,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
337
340
  let state = await deriveState(base);
338
341
  // Stale worktree state recovery (#654)
339
342
  if (state.activeMilestone &&
340
- shouldUseWorktreeIsolation() &&
343
+ shouldUseWorktreeIsolation(base) &&
341
344
  !detectWorktreeName(base)) {
342
345
  const wtPath = getAutoWorktreePath(base, state.activeMilestone.id);
343
346
  if (wtPath) {
@@ -352,7 +355,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
352
355
  let hasSurvivorBranch = false;
353
356
  if (state.activeMilestone &&
354
357
  (state.phase === "pre-planning" || state.phase === "complete") &&
355
- getIsolationMode() !== "none" &&
358
+ getIsolationMode(base) !== "none" &&
356
359
  !detectWorktreeName(base) &&
357
360
  !base.includes(`${pathSep}.gsd${pathSep}worktrees${pathSep}`)) {
358
361
  const milestoneBranch = `milestone/${state.activeMilestone.id}`;
@@ -506,13 +509,14 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
506
509
  s.pendingQuickTasks = [];
507
510
  s.currentUnit = null;
508
511
  s.currentMilestoneId = state.activeMilestone?.id ?? null;
509
- s.originalModelId = ctx.model?.id ?? null;
510
- s.originalModelProvider = ctx.model?.provider ?? null;
512
+ s.originalModelId = startModelSnapshot?.id ?? ctx.model?.id ?? null;
513
+ s.originalModelProvider = startModelSnapshot?.provider ?? ctx.model?.provider ?? null;
514
+ s.originalThinkingLevel = startThinkingSnapshot ?? null;
511
515
  // Register SIGTERM handler
512
516
  registerSigtermHandler(base);
513
517
  // Capture integration branch
514
518
  if (s.currentMilestoneId) {
515
- if (getIsolationMode() !== "none") {
519
+ if (getIsolationMode(base) !== "none") {
516
520
  captureIntegrationBranch(base, s.currentMilestoneId);
517
521
  }
518
522
  setActiveMilestoneId(base, s.currentMilestoneId);
@@ -520,7 +524,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
520
524
  // Guard against stale milestone branch when isolation:none (#3613).
521
525
  // A prior session with isolation:branch/worktree may have left HEAD on
522
526
  // milestone/<MID>. Auto-checkout back to the integration branch.
523
- if (getIsolationMode() === "none" && nativeIsRepo(base)) {
527
+ if (getIsolationMode(base) === "none" && nativeIsRepo(base)) {
524
528
  try {
525
529
  const currentBranch = nativeGetCurrentBranch(base);
526
530
  if (currentBranch.startsWith("milestone/")) {
@@ -548,7 +552,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
548
552
  return symlinkRe.test(p);
549
553
  };
550
554
  if (s.currentMilestoneId &&
551
- getIsolationMode() !== "none" &&
555
+ getIsolationMode(base) !== "none" &&
552
556
  !detectWorktreeName(base) &&
553
557
  !isUnderGsdWorktrees(base)) {
554
558
  buildResolver().enterMilestone(s.currentMilestoneId, {
@@ -593,8 +597,21 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
593
597
  // call returns "db_unavailable", triggering artifact-retry which
594
598
  // re-dispatches the same task — producing an infinite loop (#2419).
595
599
  if (existsSync(gsdDbPath) && !isDbAvailable()) {
596
- ctx.ui.notify("SQLite database exists but failed to open. Auto-mode cannot proceed without a working database provider. " +
597
- "Check for corrupt gsd.db or missing native SQLite bindings.", "error");
600
+ const dbStatus = getDbStatus();
601
+ const phaseHint = dbStatus.lastPhase === "open"
602
+ ? "The database file could not be opened"
603
+ : dbStatus.lastPhase === "initSchema"
604
+ ? "The database schema could not be initialized"
605
+ : dbStatus.lastPhase === "vacuum-recovery"
606
+ ? "Corruption recovery (VACUUM) failed"
607
+ : dbStatus.attempted
608
+ ? "The database could not be opened (phase unknown)"
609
+ : "The database provider could not be loaded";
610
+ const errorDetail = dbStatus.lastError ? ` (${dbStatus.lastError.message})` : "";
611
+ const providerHint = dbStatus.provider
612
+ ? ` Provider: ${dbStatus.provider}.`
613
+ : " No SQLite provider available — check Node >= 22 or install better-sqlite3.";
614
+ ctx.ui.notify(`SQLite database exists but failed to open: ${gsdDbPath}. ${phaseHint}${errorDetail}.${providerHint}`, "error");
598
615
  return releaseLockAndReturn();
599
616
  }
600
617
  // Initialize metrics
@@ -608,6 +625,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
608
625
  id: startModelSnapshot.id,
609
626
  };
610
627
  }
628
+ s.autoModeStartThinkingLevel = startThinkingSnapshot ?? null;
611
629
  s.manualSessionModelOverride = manualSessionOverride ?? null;
612
630
  // Apply worker model override from parallel orchestrator (#worker-model).
613
631
  // GSD_WORKER_MODEL is injected by the coordinator when parallel.worker_model
@@ -628,7 +646,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
628
646
  }
629
647
  }
630
648
  // Snapshot installed skills
631
- if (resolveSkillDiscoveryMode() !== "off") {
649
+ if (resolveSkillDiscoveryMode(base) !== "off") {
632
650
  snapshotSkills();
633
651
  }
634
652
  ctx.ui.setStatus("gsd-auto", s.stepMode ? "next" : "auto");
@@ -656,7 +674,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
656
674
  // FlatRateContext used by selectAndApplyModel so user-declared
657
675
  // flat-rate providers and externalCli auto-detection are respected.
658
676
  const { isFlatRateProvider, buildFlatRateContext } = await import("./auto-model-selection.js");
659
- const bannerPrefs = loadEffectiveGSDPreferences()?.preferences;
677
+ const bannerPrefs = loadEffectiveGSDPreferences(base)?.preferences;
660
678
  const effectiveProvider = s.autoModeStartModel?.provider ?? ctx.model?.provider;
661
679
  const effectivelyEnabled = routingConfig.enabled
662
680
  && (routingConfig.allow_flat_rate_providers
@@ -145,8 +145,8 @@ export function startAutoDetached(ctx, pi, base, verboseMode, options) {
145
145
  });
146
146
  }
147
147
  /** Returns true if the project is configured for `isolation:worktree` mode. */
148
- export function shouldUseWorktreeIsolation() {
149
- const prefs = loadEffectiveGSDPreferences()?.preferences?.git;
148
+ export function shouldUseWorktreeIsolation(basePath) {
149
+ const prefs = loadEffectiveGSDPreferences(basePath)?.preferences?.git;
150
150
  if (prefs?.isolation === "worktree")
151
151
  return true;
152
152
  // Default is false — worktree isolation requires explicit opt-in
@@ -215,7 +215,7 @@ export function getAutoDashboardData() {
215
215
  const rtkSavings = sessionId && s.basePath
216
216
  ? getRtkSessionSavings(s.basePath, sessionId)
217
217
  : null;
218
- const rtkEnabled = loadEffectiveGSDPreferences()?.preferences.experimental?.rtk === true;
218
+ const rtkEnabled = loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences.experimental?.rtk === true;
219
219
  // Pending capture count — lazy check, non-fatal
220
220
  let pendingCaptureCount = 0;
221
221
  try {
@@ -393,7 +393,7 @@ function clearUnitTimeout() {
393
393
  }
394
394
  /** Build snapshot metric opts. */
395
395
  function buildSnapshotOpts(_unitType, _unitId) {
396
- const prefs = loadEffectiveGSDPreferences()?.preferences;
396
+ const prefs = loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences;
397
397
  const uokFlags = resolveUokFlags(prefs);
398
398
  return {
399
399
  ...(s.autoStartTime > 0 ? { autoSessionKey: String(s.autoStartTime) } : {}),
@@ -427,7 +427,7 @@ function handleLostSessionLock(ctx, lockStatus) {
427
427
  restoreProjectRootEnv();
428
428
  restoreMilestoneLockEnv();
429
429
  deregisterSigtermHandler();
430
- clearCmuxSidebar(loadEffectiveGSDPreferences()?.preferences);
430
+ clearCmuxSidebar(loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences);
431
431
  const base = lockBase();
432
432
  const lockFilePath = base ? join(gsdRoot(base), "auto.lock") : "unknown";
433
433
  const recoverySuggestion = "\nTo recover, run: gsd doctor --fix";
@@ -498,7 +498,7 @@ function cleanupAfterLoopExit(ctx) {
498
498
  export async function stopAuto(ctx, pi, reason) {
499
499
  if (!s.active && !s.paused)
500
500
  return;
501
- const loadedPreferences = loadEffectiveGSDPreferences()?.preferences;
501
+ const loadedPreferences = loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences;
502
502
  const reasonSuffix = reason ? ` — ${reason}` : "";
503
503
  try {
504
504
  // ── Step 1: Timers and locks ──
@@ -695,13 +695,16 @@ export async function stopAuto(ctx, pi, reason) {
695
695
  catch (err) { /* non-fatal */
696
696
  logWarning("engine", `file unlink failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
697
697
  }
698
- // ── Step 13: Restore original model (before reset clears IDs) ──
698
+ // ── Step 13: Restore original model + thinking (before reset clears IDs) ──
699
699
  try {
700
700
  if (pi && ctx && s.originalModelId && s.originalModelProvider) {
701
701
  const original = ctx.modelRegistry.find(s.originalModelProvider, s.originalModelId);
702
702
  if (original)
703
703
  await pi.setModel(original);
704
704
  }
705
+ if (pi && s.originalThinkingLevel) {
706
+ pi.setThinkingLevel(s.originalThinkingLevel);
707
+ }
705
708
  }
706
709
  catch (e) {
707
710
  debugLog("stop-cleanup-model", { error: e instanceof Error ? e.message : String(e) });
@@ -1161,7 +1164,7 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
1161
1164
  });
1162
1165
  // ── Auto-worktree / branch-mode: re-enter on resume ──
1163
1166
  if (s.currentMilestoneId &&
1164
- getIsolationMode() !== "none" &&
1167
+ getIsolationMode(s.originalBasePath || s.basePath) !== "none" &&
1165
1168
  s.originalBasePath &&
1166
1169
  !isInAutoWorktree(s.basePath) &&
1167
1170
  !detectWorktreeName(s.basePath) &&
@@ -1194,7 +1197,7 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
1194
1197
  await openProjectDbIfPresent(s.basePath);
1195
1198
  try {
1196
1199
  await rebuildState(s.basePath);
1197
- syncCmuxSidebar(loadEffectiveGSDPreferences()?.preferences, await deriveState(s.basePath));
1200
+ syncCmuxSidebar(loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences, await deriveState(s.basePath));
1198
1201
  }
1199
1202
  catch (e) {
1200
1203
  debugLog("resume-rebuild-state-failed", {
@@ -1224,7 +1227,7 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
1224
1227
  }
1225
1228
  updateSessionLock(lockBase(), "resuming", s.currentMilestoneId ?? "unknown");
1226
1229
  writeLock(lockBase(), "resuming", s.currentMilestoneId ?? "unknown");
1227
- logCmuxEvent(loadEffectiveGSDPreferences()?.preferences, s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.", "progress");
1230
+ logCmuxEvent(loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences, s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.", "progress");
1228
1231
  captureProjectRootEnv(s.originalBasePath || s.basePath);
1229
1232
  startAutoCommandPolling(s.basePath);
1230
1233
  await runAutoLoopWithUok({
@@ -1250,13 +1253,13 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
1250
1253
  return;
1251
1254
  captureProjectRootEnv(s.originalBasePath || s.basePath);
1252
1255
  try {
1253
- syncCmuxSidebar(loadEffectiveGSDPreferences()?.preferences, await deriveState(s.basePath));
1256
+ syncCmuxSidebar(loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences, await deriveState(s.basePath));
1254
1257
  }
1255
1258
  catch (err) {
1256
1259
  // Best-effort only — sidebar sync must never block auto-mode startup
1257
1260
  logWarning("engine", `cmux sync failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
1258
1261
  }
1259
- logCmuxEvent(loadEffectiveGSDPreferences()?.preferences, requestedStepMode ? "Step-mode started." : "Auto-mode started.", "progress");
1262
+ logCmuxEvent(loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences, requestedStepMode ? "Step-mode started." : "Auto-mode started.", "progress");
1260
1263
  startAutoCommandPolling(s.basePath);
1261
1264
  // Dispatch the first unit
1262
1265
  await runAutoLoopWithUok({