gsd-pi 2.67.0-dev.509bd95 → 2.67.0-dev.6fc2289

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 (227) hide show
  1. package/README.md +1 -1
  2. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +152 -70
  3. package/dist/resources/extensions/gsd/auto/session.js +10 -0
  4. package/dist/resources/extensions/gsd/auto-dispatch.js +1 -1
  5. package/dist/resources/extensions/gsd/auto-start.js +16 -30
  6. package/dist/resources/extensions/gsd/auto-worktree.js +62 -15
  7. package/dist/resources/extensions/gsd/auto.js +121 -59
  8. package/dist/resources/extensions/gsd/bootstrap/system-context.js +7 -2
  9. package/dist/resources/extensions/gsd/commands/catalog.js +2 -1
  10. package/dist/resources/extensions/gsd/commands/handlers/core.js +1 -1
  11. package/dist/resources/extensions/gsd/commands-mcp-status.js +43 -7
  12. package/dist/resources/extensions/gsd/doctor-git-checks.js +4 -4
  13. package/dist/resources/extensions/gsd/doctor-proactive.js +3 -3
  14. package/dist/resources/extensions/gsd/doctor.js +8 -4
  15. package/dist/resources/extensions/gsd/guided-flow.js +40 -31
  16. package/dist/resources/extensions/gsd/init-wizard.js +37 -0
  17. package/dist/resources/extensions/gsd/interrupted-session.js +146 -0
  18. package/dist/resources/extensions/gsd/mcp-project-config.js +83 -0
  19. package/dist/resources/extensions/gsd/workflow-mcp.js +90 -19
  20. package/dist/web/standalone/.next/BUILD_ID +1 -1
  21. package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
  22. package/dist/web/standalone/.next/build-manifest.json +3 -3
  23. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  24. package/dist/web/standalone/.next/react-loadable-manifest.json +2 -2
  25. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  26. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  27. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  28. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  35. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  36. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/index.html +1 -1
  44. package/dist/web/standalone/.next/server/app/index.rsc +2 -2
  45. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  46. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +2 -2
  47. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  51. package/dist/web/standalone/.next/server/app-paths-manifest.json +15 -15
  52. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  53. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  54. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  55. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  56. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  57. package/dist/web/standalone/.next/static/chunks/2826.821e01b07d92e948.js +9 -0
  58. package/dist/web/standalone/.next/static/chunks/app/{page-0c485498795110d6.js → page-f1e30ab6bb269149.js} +1 -1
  59. package/dist/web/standalone/.next/static/chunks/{webpack-b49b09f97429b5d0.js → webpack-6e4d7e9a4f57bed4.js} +1 -1
  60. package/package.json +4 -2
  61. package/packages/mcp-server/dist/cli.d.ts +9 -0
  62. package/packages/mcp-server/dist/cli.d.ts.map +1 -0
  63. package/packages/mcp-server/dist/cli.js +58 -0
  64. package/packages/mcp-server/dist/cli.js.map +1 -0
  65. package/packages/mcp-server/dist/index.d.ts +20 -0
  66. package/packages/mcp-server/dist/index.d.ts.map +1 -0
  67. package/packages/mcp-server/dist/index.js +14 -0
  68. package/packages/mcp-server/dist/index.js.map +1 -0
  69. package/packages/mcp-server/dist/readers/captures.d.ts +25 -0
  70. package/packages/mcp-server/dist/readers/captures.d.ts.map +1 -0
  71. package/packages/mcp-server/dist/readers/captures.js +67 -0
  72. package/packages/mcp-server/dist/readers/captures.js.map +1 -0
  73. package/packages/mcp-server/dist/readers/doctor-lite.d.ts +20 -0
  74. package/packages/mcp-server/dist/readers/doctor-lite.d.ts.map +1 -0
  75. package/packages/mcp-server/dist/readers/doctor-lite.js +173 -0
  76. package/packages/mcp-server/dist/readers/doctor-lite.js.map +1 -0
  77. package/packages/mcp-server/dist/readers/index.d.ts +14 -0
  78. package/packages/mcp-server/dist/readers/index.d.ts.map +1 -0
  79. package/packages/mcp-server/dist/readers/index.js +10 -0
  80. package/packages/mcp-server/dist/readers/index.js.map +1 -0
  81. package/packages/mcp-server/dist/readers/knowledge.d.ts +18 -0
  82. package/packages/mcp-server/dist/readers/knowledge.d.ts.map +1 -0
  83. package/packages/mcp-server/dist/readers/knowledge.js +82 -0
  84. package/packages/mcp-server/dist/readers/knowledge.js.map +1 -0
  85. package/packages/mcp-server/dist/readers/metrics.d.ts +32 -0
  86. package/packages/mcp-server/dist/readers/metrics.d.ts.map +1 -0
  87. package/packages/mcp-server/dist/readers/metrics.js +74 -0
  88. package/packages/mcp-server/dist/readers/metrics.js.map +1 -0
  89. package/packages/mcp-server/dist/readers/paths.d.ts +42 -0
  90. package/packages/mcp-server/dist/readers/paths.d.ts.map +1 -0
  91. package/packages/mcp-server/dist/readers/paths.js +199 -0
  92. package/packages/mcp-server/dist/readers/paths.js.map +1 -0
  93. package/packages/mcp-server/dist/readers/roadmap.d.ts +26 -0
  94. package/packages/mcp-server/dist/readers/roadmap.d.ts.map +1 -0
  95. package/packages/mcp-server/dist/readers/roadmap.js +194 -0
  96. package/packages/mcp-server/dist/readers/roadmap.js.map +1 -0
  97. package/packages/mcp-server/dist/readers/state.d.ts +43 -0
  98. package/packages/mcp-server/dist/readers/state.d.ts.map +1 -0
  99. package/packages/mcp-server/dist/readers/state.js +184 -0
  100. package/packages/mcp-server/dist/readers/state.js.map +1 -0
  101. package/packages/mcp-server/dist/server.d.ts +28 -0
  102. package/packages/mcp-server/dist/server.d.ts.map +1 -0
  103. package/packages/mcp-server/dist/server.js +319 -0
  104. package/packages/mcp-server/dist/server.js.map +1 -0
  105. package/packages/mcp-server/dist/session-manager.d.ts +54 -0
  106. package/packages/mcp-server/dist/session-manager.d.ts.map +1 -0
  107. package/packages/mcp-server/dist/session-manager.js +284 -0
  108. package/packages/mcp-server/dist/session-manager.js.map +1 -0
  109. package/packages/mcp-server/dist/types.d.ts +61 -0
  110. package/packages/mcp-server/dist/types.d.ts.map +1 -0
  111. package/packages/mcp-server/dist/types.js +11 -0
  112. package/packages/mcp-server/dist/types.js.map +1 -0
  113. package/packages/mcp-server/dist/workflow-tools.d.ts +9 -0
  114. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -0
  115. package/packages/mcp-server/dist/workflow-tools.js +532 -0
  116. package/packages/mcp-server/dist/workflow-tools.js.map +1 -0
  117. package/packages/mcp-server/src/workflow-tools.ts +13 -2
  118. package/packages/mcp-server/tsconfig.json +1 -1
  119. package/packages/pi-agent-core/dist/agent-loop.js +14 -6
  120. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  121. package/packages/pi-agent-core/src/agent-loop.test.ts +53 -0
  122. package/packages/pi-agent-core/src/agent-loop.ts +20 -6
  123. package/packages/pi-coding-agent/dist/core/contextual-tips.d.ts +43 -0
  124. package/packages/pi-coding-agent/dist/core/contextual-tips.d.ts.map +1 -0
  125. package/packages/pi-coding-agent/dist/core/contextual-tips.js +208 -0
  126. package/packages/pi-coding-agent/dist/core/contextual-tips.js.map +1 -0
  127. package/packages/pi-coding-agent/dist/core/contextual-tips.test.d.ts +2 -0
  128. package/packages/pi-coding-agent/dist/core/contextual-tips.test.d.ts.map +1 -0
  129. package/packages/pi-coding-agent/dist/core/contextual-tips.test.js +227 -0
  130. package/packages/pi-coding-agent/dist/core/contextual-tips.test.js.map +1 -0
  131. package/packages/pi-coding-agent/dist/core/index.d.ts +1 -0
  132. package/packages/pi-coding-agent/dist/core/index.d.ts.map +1 -1
  133. package/packages/pi-coding-agent/dist/core/index.js +1 -0
  134. package/packages/pi-coding-agent/dist/core/index.js.map +1 -1
  135. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.d.ts +2 -0
  136. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.d.ts.map +1 -0
  137. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +28 -0
  138. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -0
  139. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -0
  140. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  141. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +17 -12
  142. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  143. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  144. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +19 -0
  145. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  146. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts +4 -0
  147. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
  148. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +14 -0
  149. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  150. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +3 -0
  151. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  152. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +15 -12
  153. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  154. package/packages/pi-coding-agent/src/core/contextual-tips.test.ts +259 -0
  155. package/packages/pi-coding-agent/src/core/contextual-tips.ts +232 -0
  156. package/packages/pi-coding-agent/src/core/index.ts +2 -0
  157. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +54 -0
  158. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +18 -12
  159. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +21 -0
  160. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +19 -0
  161. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +19 -15
  162. package/packages/rpc-client/dist/index.d.ts +10 -0
  163. package/packages/rpc-client/dist/index.d.ts.map +1 -0
  164. package/packages/rpc-client/dist/index.js +9 -0
  165. package/packages/rpc-client/dist/index.js.map +1 -0
  166. package/packages/rpc-client/dist/jsonl.d.ts +17 -0
  167. package/packages/rpc-client/dist/jsonl.d.ts.map +1 -0
  168. package/packages/rpc-client/dist/jsonl.js +54 -0
  169. package/packages/rpc-client/dist/jsonl.js.map +1 -0
  170. package/packages/rpc-client/dist/rpc-client.d.ts +259 -0
  171. package/packages/rpc-client/dist/rpc-client.d.ts.map +1 -0
  172. package/packages/rpc-client/dist/rpc-client.js +541 -0
  173. package/packages/rpc-client/dist/rpc-client.js.map +1 -0
  174. package/packages/rpc-client/dist/rpc-client.test.d.ts +2 -0
  175. package/packages/rpc-client/dist/rpc-client.test.d.ts.map +1 -0
  176. package/packages/rpc-client/dist/rpc-client.test.js +477 -0
  177. package/packages/rpc-client/dist/rpc-client.test.js.map +1 -0
  178. package/packages/rpc-client/dist/rpc-types.d.ts +566 -0
  179. package/packages/rpc-client/dist/rpc-types.d.ts.map +1 -0
  180. package/packages/rpc-client/dist/rpc-types.js +12 -0
  181. package/packages/rpc-client/dist/rpc-types.js.map +1 -0
  182. package/scripts/ensure-workspace-builds.cjs +2 -0
  183. package/scripts/link-workspace-packages.cjs +21 -14
  184. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +190 -93
  185. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +89 -116
  186. package/src/resources/extensions/gsd/auto/session.ts +10 -0
  187. package/src/resources/extensions/gsd/auto-dispatch.ts +1 -1
  188. package/src/resources/extensions/gsd/auto-start.ts +23 -55
  189. package/src/resources/extensions/gsd/auto-worktree.ts +59 -15
  190. package/src/resources/extensions/gsd/auto.ts +133 -64
  191. package/src/resources/extensions/gsd/bootstrap/system-context.ts +8 -2
  192. package/src/resources/extensions/gsd/commands/catalog.ts +2 -1
  193. package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -1
  194. package/src/resources/extensions/gsd/commands-mcp-status.ts +53 -7
  195. package/src/resources/extensions/gsd/doctor-git-checks.ts +4 -4
  196. package/src/resources/extensions/gsd/doctor-proactive.ts +3 -3
  197. package/src/resources/extensions/gsd/doctor.ts +9 -5
  198. package/src/resources/extensions/gsd/guided-flow.ts +42 -36
  199. package/src/resources/extensions/gsd/init-wizard.ts +40 -0
  200. package/src/resources/extensions/gsd/interrupted-session.ts +224 -0
  201. package/src/resources/extensions/gsd/mcp-project-config.ts +128 -0
  202. package/src/resources/extensions/gsd/tests/auto-project-root-env.test.ts +29 -0
  203. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +668 -2
  204. package/src/resources/extensions/gsd/tests/cold-resume-db-reopen.test.ts +14 -4
  205. package/src/resources/extensions/gsd/tests/copy-planning-artifacts-samepath.test.ts +21 -0
  206. package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +380 -2
  207. package/src/resources/extensions/gsd/tests/forensics-context-persist.test.ts +30 -0
  208. package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +2 -2
  209. package/src/resources/extensions/gsd/tests/integration/doctor-fixlevel.test.ts +52 -1
  210. package/src/resources/extensions/gsd/tests/integration/doctor-git.test.ts +2 -9
  211. package/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts +0 -33
  212. package/src/resources/extensions/gsd/tests/integration/merge-cwd-restore.test.ts +169 -0
  213. package/src/resources/extensions/gsd/tests/interrupted-session-auto.test.ts +146 -0
  214. package/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts +136 -0
  215. package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +85 -0
  216. package/src/resources/extensions/gsd/tests/mcp-status.test.ts +15 -0
  217. package/src/resources/extensions/gsd/tests/verification-operational-gate.test.ts +11 -0
  218. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +212 -13
  219. package/src/resources/extensions/gsd/workflow-mcp.ts +106 -19
  220. package/dist/web/standalone/.next/static/chunks/6502.b804e48b7919f55e.js +0 -9
  221. package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.d.ts +0 -13
  222. package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.d.ts.map +0 -1
  223. package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.js +0 -27
  224. package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.js.map +0 -1
  225. package/packages/pi-coding-agent/src/modes/interactive/provider-auth-setup.ts +0 -40
  226. /package/dist/web/standalone/.next/static/{mHJZ3Z8yGRzZ32BmQs-I7 → yh2vT27L1E6PChb_C1N_F}/_buildManifest.js +0 -0
  227. /package/dist/web/standalone/.next/static/{mHJZ3Z8yGRzZ32BmQs-I7 → yh2vT27L1E6PChb_C1N_F}/_ssgManifest.js +0 -0
package/README.md CHANGED
@@ -95,7 +95,7 @@ See the full [Changelog](./CHANGELOG.md) for details on every release.
95
95
 
96
96
  ## Documentation
97
97
 
98
- Full documentation is available in the [`docs/`](./docs/) directory:
98
+ Full documentation is available at **[gsd.build](https://gsd.build)** (powered by Mintlify) and in the [`docs/`](./docs/) directory:
99
99
 
100
100
  - **[Getting Started](./docs/getting-started.md)** — install, first run, basic usage
101
101
  - **[Auto Mode](./docs/auto-mode.md)** — autonomous execution deep-dive
@@ -124,70 +124,6 @@ export function makeStreamExhaustedErrorMessage(model, lastTextContent) {
124
124
  }
125
125
  return message;
126
126
  }
127
- /**
128
- * Claude Code executes its own internal tool loop inside the SDK call. The
129
- * streamed and final assistant messages should therefore contain only
130
- * user-facing content (text/thinking), not replayable tool blocks that GSD
131
- * would render again.
132
- */
133
- function isUserFacingClaudeCodeBlock(block) {
134
- return block.type === "text" || block.type === "thinking";
135
- }
136
- function filterUserFacingClaudeCodeContent(blocks) {
137
- return blocks.filter(isUserFacingClaudeCodeBlock);
138
- }
139
- function remapClaudeCodeContentIndex(blocks, contentIndex) {
140
- let visibleCount = 0;
141
- for (let i = 0; i <= contentIndex && i < blocks.length; i++) {
142
- if (isUserFacingClaudeCodeBlock(blocks[i]))
143
- visibleCount++;
144
- }
145
- return Math.max(0, visibleCount - 1);
146
- }
147
- function sanitizeClaudeCodePartial(partial) {
148
- return {
149
- ...partial,
150
- content: filterUserFacingClaudeCodeContent(partial.content),
151
- };
152
- }
153
- export function sanitizeClaudeCodeStreamingEvent(event) {
154
- switch (event.type) {
155
- case "toolcall_start":
156
- case "toolcall_delta":
157
- case "toolcall_end":
158
- case "server_tool_use":
159
- case "web_search_result":
160
- return null;
161
- case "text_start":
162
- case "text_delta":
163
- case "text_end":
164
- case "thinking_start":
165
- case "thinking_delta":
166
- case "thinking_end":
167
- return {
168
- ...event,
169
- contentIndex: remapClaudeCodeContentIndex(event.partial.content, event.contentIndex),
170
- partial: sanitizeClaudeCodePartial(event.partial),
171
- };
172
- default:
173
- return event;
174
- }
175
- }
176
- export function buildFinalClaudeCodeContent(blocks, lastThinkingContent, lastTextContent, resultText) {
177
- const finalContent = filterUserFacingClaudeCodeContent(blocks);
178
- if (finalContent.length > 0)
179
- return finalContent;
180
- if (lastThinkingContent) {
181
- finalContent.push({ type: "thinking", thinking: lastThinkingContent });
182
- }
183
- if (lastTextContent) {
184
- finalContent.push({ type: "text", text: lastTextContent });
185
- }
186
- if (finalContent.length === 0 && resultText) {
187
- finalContent.push({ type: "text", text: resultText });
188
- }
189
- return finalContent;
190
- }
191
127
  // ---------------------------------------------------------------------------
192
128
  // SDK options builder
193
129
  // ---------------------------------------------------------------------------
@@ -213,6 +149,94 @@ export function buildSdkOptions(modelId, prompt) {
213
149
  betas: modelId.includes("sonnet") ? ["context-1m-2025-08-07"] : [],
214
150
  };
215
151
  }
152
+ function normalizeToolResultContent(content) {
153
+ if (typeof content === "string") {
154
+ return [{ type: "text", text: content }];
155
+ }
156
+ if (!Array.isArray(content)) {
157
+ if (content == null)
158
+ return [{ type: "text", text: "" }];
159
+ return [{ type: "text", text: JSON.stringify(content) }];
160
+ }
161
+ const blocks = [];
162
+ for (const item of content) {
163
+ if (typeof item === "string") {
164
+ blocks.push({ type: "text", text: item });
165
+ continue;
166
+ }
167
+ if (!item || typeof item !== "object") {
168
+ blocks.push({ type: "text", text: String(item) });
169
+ continue;
170
+ }
171
+ const block = item;
172
+ if (block.type === "text") {
173
+ blocks.push({ type: "text", text: typeof block.text === "string" ? block.text : "" });
174
+ continue;
175
+ }
176
+ if (block.type === "image"
177
+ && typeof block.data === "string"
178
+ && typeof block.mimeType === "string") {
179
+ blocks.push({ type: "image", data: block.data, mimeType: block.mimeType });
180
+ continue;
181
+ }
182
+ blocks.push({ type: "text", text: JSON.stringify(block) });
183
+ }
184
+ return blocks.length > 0 ? blocks : [{ type: "text", text: "" }];
185
+ }
186
+ export function extractToolResultsFromSdkUserMessage(message) {
187
+ const extracted = [];
188
+ const seen = new Set();
189
+ const rawMessage = message.message;
190
+ const content = Array.isArray(rawMessage?.content) ? rawMessage.content : [];
191
+ for (const item of content) {
192
+ if (!item || typeof item !== "object")
193
+ continue;
194
+ const block = item;
195
+ const type = typeof block.type === "string" ? block.type : "";
196
+ if (type !== "tool_result" && type !== "mcp_tool_result")
197
+ continue;
198
+ const toolUseId = typeof block.tool_use_id === "string" ? block.tool_use_id : "";
199
+ if (!toolUseId || seen.has(toolUseId))
200
+ continue;
201
+ seen.add(toolUseId);
202
+ extracted.push({
203
+ toolUseId,
204
+ result: {
205
+ content: normalizeToolResultContent(block.content),
206
+ details: {},
207
+ isError: block.is_error === true,
208
+ },
209
+ });
210
+ }
211
+ if (extracted.length === 0) {
212
+ const fallback = message.tool_use_result;
213
+ if (fallback && typeof fallback === "object") {
214
+ const toolResult = fallback;
215
+ const toolUseId = typeof toolResult.tool_use_id === "string" ? toolResult.tool_use_id : "";
216
+ if (toolUseId) {
217
+ extracted.push({
218
+ toolUseId,
219
+ result: {
220
+ content: normalizeToolResultContent(toolResult.content),
221
+ details: {},
222
+ isError: toolResult.is_error === true,
223
+ },
224
+ });
225
+ }
226
+ }
227
+ }
228
+ return extracted;
229
+ }
230
+ function attachExternalResultsToToolCalls(toolCalls, toolResultsById) {
231
+ for (const block of toolCalls) {
232
+ if (block.type !== "toolCall")
233
+ continue;
234
+ const externalResult = toolResultsById.get(block.id);
235
+ if (!externalResult)
236
+ continue;
237
+ block.externalResult = externalResult;
238
+ }
239
+ }
216
240
  // ---------------------------------------------------------------------------
217
241
  // streamSimple implementation
218
242
  // ---------------------------------------------------------------------------
@@ -234,6 +258,10 @@ async function pumpSdkMessages(model, context, options, stream) {
234
258
  /** Track the last text content seen across all assistant turns for the final message. */
235
259
  let lastTextContent = "";
236
260
  let lastThinkingContent = "";
261
+ /** Collect tool calls from intermediate SDK turns for tool_execution events. */
262
+ const intermediateToolCalls = [];
263
+ /** Preserve real external tool results from Claude Code's synthetic user messages. */
264
+ const toolResultsById = new Map();
237
265
  try {
238
266
  // Dynamic import — the SDK is an optional dependency.
239
267
  const sdkModule = "@anthropic-ai/claude-agent-sdk";
@@ -285,11 +313,9 @@ async function pumpSdkMessages(model, context, options, stream) {
285
313
  if (!builder)
286
314
  break;
287
315
  const assistantEvent = builder.handleEvent(event);
288
- const sanitizedEvent = assistantEvent
289
- ? sanitizeClaudeCodeStreamingEvent(assistantEvent)
290
- : null;
291
- if (sanitizedEvent)
292
- stream.push(sanitizedEvent);
316
+ if (assistantEvent) {
317
+ stream.push(assistantEvent);
318
+ }
293
319
  break;
294
320
  }
295
321
  // -- Complete assistant message (non-streaming fallback) --
@@ -317,6 +343,36 @@ async function pumpSdkMessages(model, context, options, stream) {
317
343
  else if (block.type === "thinking" && block.thinking) {
318
344
  lastThinkingContent = block.thinking;
319
345
  }
346
+ else if (block.type === "toolCall") {
347
+ // Collect tool calls for externalToolExecution rendering
348
+ intermediateToolCalls.push(block);
349
+ }
350
+ }
351
+ }
352
+ // Extract tool results from the SDK's synthetic user message
353
+ // and attach to corresponding tool call blocks immediately.
354
+ for (const { toolUseId, result } of extractToolResultsFromSdkUserMessage(msg)) {
355
+ toolResultsById.set(toolUseId, result);
356
+ }
357
+ attachExternalResultsToToolCalls(intermediateToolCalls, toolResultsById);
358
+ // Push a synthetic toolcall_end for each tool call from this turn
359
+ // so the TUI can render tool results in real-time during the SDK
360
+ // session instead of waiting until the entire session completes.
361
+ if (builder) {
362
+ for (const block of builder.message.content) {
363
+ if (block.type !== "toolCall")
364
+ continue;
365
+ const extResult = block.externalResult;
366
+ if (!extResult)
367
+ continue;
368
+ // Push a toolcall_end with result attached so the chat-controller
369
+ // can call updateResult on the pending ToolExecutionComponent.
370
+ stream.push({
371
+ type: "toolcall_end",
372
+ contentIndex: builder.message.content.indexOf(block),
373
+ toolCall: block,
374
+ partial: builder.message,
375
+ });
320
376
  }
321
377
  }
322
378
  builder = null;
@@ -325,7 +381,33 @@ async function pumpSdkMessages(model, context, options, stream) {
325
381
  // -- Result (terminal) --
326
382
  case "result": {
327
383
  const result = msg;
328
- const finalContent = buildFinalClaudeCodeContent(builder?.message.content ?? [], lastThinkingContent, lastTextContent, result.subtype === "success" ? result.result : undefined);
384
+ // Build final message. Include intermediate tool calls so the
385
+ // agent loop's externalToolExecution path emits tool_execution
386
+ // events for proper TUI rendering, followed by the text response.
387
+ const finalContent = [];
388
+ // Add tool calls from intermediate turns first (renders above text)
389
+ attachExternalResultsToToolCalls(intermediateToolCalls, toolResultsById);
390
+ finalContent.push(...intermediateToolCalls);
391
+ // Add text/thinking from the last turn
392
+ if (builder && builder.message.content.length > 0) {
393
+ for (const block of builder.message.content) {
394
+ if (block.type === "text" || block.type === "thinking") {
395
+ finalContent.push(block);
396
+ }
397
+ }
398
+ }
399
+ else {
400
+ if (lastThinkingContent) {
401
+ finalContent.push({ type: "thinking", thinking: lastThinkingContent });
402
+ }
403
+ if (lastTextContent) {
404
+ finalContent.push({ type: "text", text: lastTextContent });
405
+ }
406
+ }
407
+ // Fallback: use the SDK's result text if we have no content
408
+ if (finalContent.length === 0 && result.subtype === "success" && result.result) {
409
+ finalContent.push({ type: "text", text: result.result });
410
+ }
329
411
  const finalMessage = {
330
412
  role: "assistant",
331
413
  content: finalContent,
@@ -33,6 +33,9 @@ export class AutoSession {
33
33
  // ── Paths ────────────────────────────────────────────────────────────────
34
34
  basePath = "";
35
35
  originalBasePath = "";
36
+ previousProjectRootEnv = null;
37
+ hadProjectRootEnv = false;
38
+ projectRootEnvCaptured = false;
36
39
  gitService = null;
37
40
  // ── Dispatch counters ────────────────────────────────────────────────────
38
41
  unitDispatchCount = new Map();
@@ -60,6 +63,8 @@ export class AutoSession {
60
63
  pendingVerificationRetry = null;
61
64
  verificationRetryCount = new Map();
62
65
  pausedSessionFile = null;
66
+ pausedUnitType = null;
67
+ pausedUnitId = null;
63
68
  resourceVersionOnStart = null;
64
69
  lastStateRebuildAt = 0;
65
70
  // ── Sidecar queue ─────────────────────────────────────────────────────
@@ -132,6 +137,9 @@ export class AutoSession {
132
137
  // Paths
133
138
  this.basePath = "";
134
139
  this.originalBasePath = "";
140
+ this.previousProjectRootEnv = null;
141
+ this.hadProjectRootEnv = false;
142
+ this.projectRootEnvCaptured = false;
135
143
  this.gitService = null;
136
144
  // Dispatch
137
145
  this.unitDispatchCount.clear();
@@ -153,6 +161,8 @@ export class AutoSession {
153
161
  this.pendingVerificationRetry = null;
154
162
  this.verificationRetryCount.clear();
155
163
  this.pausedSessionFile = null;
164
+ this.pausedUnitType = null;
165
+ this.pausedUnitId = null;
156
166
  this.resourceVersionOnStart = null;
157
167
  this.lastStateRebuildAt = 0;
158
168
  // Metrics
@@ -104,7 +104,7 @@ export function isVerificationNotApplicable(value) {
104
104
  const v = (value ?? "").toLowerCase().trim().replace(/[.\s]+$/, "");
105
105
  if (!v || v === "none")
106
106
  return true;
107
- return /^(?:none[\s._-]*(?:required|needed|planned)?|n\/?a|not[\s._-]+(?:applicable|required|needed|provided)|no[\s._-]+operational[\s\S]*)$/i.test(v);
107
+ return /^(?:none(?:[\s._\u2014-]+[\s\S]*)?|n\/?a|not[\s._-]+(?:applicable|required|needed|provided)|no[\s._-]+operational[\s\S]*)$/i.test(v);
108
108
  }
109
109
  // ─── Rules ────────────────────────────────────────────────────────────────
110
110
  export const DISPATCH_RULES = [
@@ -16,8 +16,7 @@ import { migrateToExternalState, recoverFailedMigration } from "./migrate-extern
16
16
  import { collectSecretsFromManifest } from "../get-secrets-from-user.js";
17
17
  import { gsdRoot, resolveMilestoneFile } from "./paths.js";
18
18
  import { invalidateAllCaches } from "./cache.js";
19
- import { synthesizeCrashRecovery } from "./session-forensics.js";
20
- import { writeLock, clearLock, readCrashLock, formatCrashInfo, isLockProcessAlive, } from "./crash-recovery.js";
19
+ import { writeLock, clearLock } from "./crash-recovery.js";
21
20
  import { acquireSessionLock, releaseSessionLock, updateSessionLock, } from "./session-lock.js";
22
21
  import { ensureGitignore, untrackRuntimeFiles } from "./gitignore.js";
23
22
  import { nativeIsRepo, nativeInit, nativeAddAll, nativeCommit, nativeGetCurrentBranch, nativeDetectMainBranch, nativeCheckoutBranch, nativeBranchList, nativeBranchListMerged, nativeBranchDelete, nativeWorktreeRemove, } from "./native-git-bridge.js";
@@ -35,7 +34,6 @@ import { isDbAvailable, getMilestone, openDatabase } from "./gsd-db.js";
35
34
  import { hideFooter } from "./auto-dashboard.js";
36
35
  import { debugLog, enableDebug, isDebugEnabled, getDebugLogPath, } from "./debug-logger.js";
37
36
  import { logWarning, logError } from "./workflow-logger.js";
38
- import { parseUnitId } from "./unit-id.js";
39
37
  import { existsSync, mkdirSync, readdirSync, rmSync, statSync, unlinkSync, } from "node:fs";
40
38
  import { join } from "node:path";
41
39
  import { sep as pathSep } from "node:path";
@@ -174,7 +172,7 @@ export function auditOrphanedMilestoneBranches(basePath, isolationMode) {
174
172
  }
175
173
  return { recovered, warnings };
176
174
  }
177
- export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, requestedStepMode, deps) {
175
+ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, requestedStepMode, deps, interrupted) {
178
176
  const { shouldUseWorktreeIsolation, registerSigtermHandler, lockBase, buildResolver, } = deps;
179
177
  const lockResult = acquireSessionLock(base);
180
178
  if (!lockResult.acquired) {
@@ -250,35 +248,20 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
250
248
  logWarning("engine", `mkdir failed: ${err instanceof Error ? err.message : String(err)}`);
251
249
  }
252
250
  }
253
- // Initialize GitServiceImpl
254
- s.gitService = new GitServiceImpl(s.basePath, loadEffectiveGSDPreferences()?.preferences?.git ?? {});
255
- // Check for crash from previous session. Skip our own fresh bootstrap lock.
256
- const crashLock = readCrashLock(base);
257
- if (crashLock && crashLock.pid !== process.pid) {
258
- if (isLockProcessAlive(crashLock)) {
259
- ctx.ui.notify(`Another auto-mode session (PID ${crashLock.pid}) appears to be running.\nStop it with \`kill ${crashLock.pid}\` before starting a new session.`, "error");
260
- return releaseLockAndReturn();
261
- }
262
- const recoveredMid = parseUnitId(crashLock.unitId).milestone;
263
- const milestoneAlreadyComplete = recoveredMid
264
- ? !!resolveMilestoneFile(base, recoveredMid, "SUMMARY")
265
- : false;
266
- if (milestoneAlreadyComplete) {
267
- ctx.ui.notify(`Crash recovery: discarding stale context for ${crashLock.unitId} — milestone ${recoveredMid} is already complete.`, "info");
268
- }
269
- else {
270
- const activityDir = join(gsdRoot(base), "activity");
271
- const recovery = synthesizeCrashRecovery(base, crashLock.unitType, crashLock.unitId, crashLock.sessionFile, activityDir);
272
- if (recovery && recovery.trace.toolCallCount > 0) {
273
- s.pendingCrashRecovery = recovery.prompt;
274
- ctx.ui.notify(`${formatCrashInfo(crashLock)}\nRecovered ${recovery.trace.toolCallCount} tool calls from crashed session. Resuming with full context.`, "warning");
275
- }
276
- else {
277
- ctx.ui.notify(`${formatCrashInfo(crashLock)}\nNo session data recovered. Resuming from disk state.`, "warning");
251
+ if (ctx.model?.provider === "claude-code") {
252
+ try {
253
+ const { ensureProjectWorkflowMcpConfig } = await import("./mcp-project-config.js");
254
+ const result = ensureProjectWorkflowMcpConfig(base);
255
+ if (result.status !== "unchanged") {
256
+ ctx.ui.notify(`Claude Code MCP prepared at ${result.configPath}`, "info");
278
257
  }
279
258
  }
280
- clearLock(base);
259
+ catch (err) {
260
+ ctx.ui.notify(`Claude Code MCP prep failed: ${err instanceof Error ? err.message : String(err)}`, "warning");
261
+ }
281
262
  }
263
+ // Initialize GitServiceImpl
264
+ s.gitService = new GitServiceImpl(s.basePath, loadEffectiveGSDPreferences()?.preferences?.git ?? {});
282
265
  // ── Debug mode ──
283
266
  if (!isDebugEnabled() && process.env.GSD_DEBUG === "1") {
284
267
  enableDebug(base);
@@ -296,6 +279,9 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
296
279
  });
297
280
  ctx.ui.notify(`Debug logging enabled → ${getDebugLogPath()}`, "info");
298
281
  }
282
+ if (interrupted.classification !== "recoverable") {
283
+ s.pendingCrashRecovery = null;
284
+ }
299
285
  // Invalidate caches before initial state derivation
300
286
  invalidateAllCaches();
301
287
  // Clean stale runtime unit files for completed milestones (#887)
@@ -983,6 +983,8 @@ function copyPlanningArtifacts(srcBase, wtPath) {
983
983
  const dstGsd = join(wtPath, ".gsd");
984
984
  if (!existsSync(srcGsd))
985
985
  return;
986
+ if (isSamePath(srcGsd, dstGsd))
987
+ return;
986
988
  // Copy milestones/ directory (planning files, roadmaps, plans, research)
987
989
  safeCopyRecursive(join(srcGsd, "milestones"), join(dstGsd, "milestones"), {
988
990
  force: true,
@@ -1209,8 +1211,32 @@ function autoCommitDirtyState(cwd) {
1209
1211
  export function mergeMilestoneToMain(originalBasePath_, milestoneId, roadmapContent) {
1210
1212
  const worktreeCwd = process.cwd();
1211
1213
  const milestoneBranch = autoWorktreeBranch(milestoneId);
1212
- // 1. Auto-commit dirty state in worktree before leaving
1213
- autoCommitDirtyState(worktreeCwd);
1214
+ // 1. Auto-commit dirty state before leaving.
1215
+ // Guard: when we entered through an auto-worktree (originalBase is set),
1216
+ // only auto-commit when cwd is on the milestone branch. In parallel mode,
1217
+ // cwd may be on the integration branch after a prior merge's
1218
+ // MergeConflictError left cwd unrestored. Auto-committing on the
1219
+ // integration branch captures dirty files from OTHER milestones under a
1220
+ // misleading commit message, contaminating the main branch (#2929).
1221
+ //
1222
+ // When originalBase is null (branch mode, no worktree), autoCommitDirtyState
1223
+ // runs unconditionally — the caller is responsible for cwd placement.
1224
+ {
1225
+ let shouldAutoCommit = true;
1226
+ if (originalBase !== null) {
1227
+ try {
1228
+ const currentBranch = nativeGetCurrentBranch(worktreeCwd);
1229
+ shouldAutoCommit = currentBranch === milestoneBranch;
1230
+ }
1231
+ catch {
1232
+ // If we can't determine the branch, skip the auto-commit to be safe
1233
+ shouldAutoCommit = false;
1234
+ }
1235
+ }
1236
+ if (shouldAutoCommit) {
1237
+ autoCommitDirtyState(worktreeCwd);
1238
+ }
1239
+ }
1214
1240
  // Reconcile worktree DB into main DB before leaving worktree context.
1215
1241
  // Skip when both paths resolve to the same physical file (shared WAL /
1216
1242
  // symlink layout) — ATTACHing a WAL-mode file to itself corrupts the
@@ -1551,6 +1577,12 @@ export function mergeMilestoneToMain(originalBasePath_, milestoneId, roadmapCont
1551
1577
  }
1552
1578
  }
1553
1579
  restoreShelter();
1580
+ // Restore cwd so the caller is not stranded on the integration branch.
1581
+ // Without this, the next mergeMilestoneToMain call in a parallel merge
1582
+ // sequence uses process.cwd() (now the project root) as worktreeCwd,
1583
+ // causing autoCommitDirtyState to commit unrelated milestone files to
1584
+ // the integration branch (#2929).
1585
+ process.chdir(previousCwd);
1554
1586
  throw new MergeConflictError(codeConflicts, "squash", milestoneBranch, mainBranch);
1555
1587
  }
1556
1588
  }
@@ -1725,25 +1757,40 @@ export function mergeMilestoneToMain(originalBasePath_, milestoneId, roadmapCont
1725
1757
  // changes (e.g. nativeHasChanges cache returned stale false, or auto-commit
1726
1758
  // silently failed), force one final commit so code is not destroyed by
1727
1759
  // `git worktree remove --force`.
1760
+ //
1761
+ // Guard: only run when worktreeCwd is on the milestone branch (#2929).
1762
+ // In parallel mode or branch-mode merges, worktreeCwd may be the project
1763
+ // root on the integration branch. Committing dirty state there would
1764
+ // capture unrelated files from other milestones.
1728
1765
  if (existsSync(worktreeCwd)) {
1766
+ let preTeardownBranch = null;
1729
1767
  try {
1730
- const dirtyCheck = nativeWorkingTreeStatus(worktreeCwd);
1731
- if (dirtyCheck) {
1768
+ preTeardownBranch = nativeGetCurrentBranch(worktreeCwd);
1769
+ }
1770
+ catch (err) {
1771
+ debugLog("mergeMilestoneToMain", { phase: "pre-teardown-branch-detect-failed", error: String(err) });
1772
+ }
1773
+ const isOnMilestoneBranch = preTeardownBranch === milestoneBranch;
1774
+ if (isOnMilestoneBranch) {
1775
+ try {
1776
+ const dirtyCheck = nativeWorkingTreeStatus(worktreeCwd);
1777
+ if (dirtyCheck) {
1778
+ debugLog("mergeMilestoneToMain", {
1779
+ phase: "pre-teardown-dirty",
1780
+ worktreeCwd,
1781
+ status: dirtyCheck.slice(0, 200),
1782
+ });
1783
+ nativeAddAllWithExclusions(worktreeCwd, RUNTIME_EXCLUSION_PATHS);
1784
+ nativeCommit(worktreeCwd, "chore: pre-teardown auto-commit of uncommitted worktree changes");
1785
+ }
1786
+ }
1787
+ catch (e) {
1732
1788
  debugLog("mergeMilestoneToMain", {
1733
- phase: "pre-teardown-dirty",
1734
- worktreeCwd,
1735
- status: dirtyCheck.slice(0, 200),
1789
+ phase: "pre-teardown-commit-error",
1790
+ error: String(e),
1736
1791
  });
1737
- nativeAddAllWithExclusions(worktreeCwd, RUNTIME_EXCLUSION_PATHS);
1738
- nativeCommit(worktreeCwd, "chore: pre-teardown auto-commit of uncommitted worktree changes");
1739
1792
  }
1740
1793
  }
1741
- catch (e) {
1742
- debugLog("mergeMilestoneToMain", {
1743
- phase: "pre-teardown-commit-error",
1744
- error: String(e),
1745
- });
1746
- }
1747
1794
  }
1748
1795
  // 12. Remove worktree directory first (must happen before branch deletion)
1749
1796
  try {