gsd-pi 2.67.0-dev.a5b1d8f → 2.67.0-dev.fe39184

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 (191) hide show
  1. package/README.md +41 -31
  2. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +121 -8
  3. package/dist/resources/extensions/gsd/auto/phases.js +17 -0
  4. package/dist/resources/extensions/gsd/auto/session.js +6 -0
  5. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +12 -0
  6. package/dist/resources/extensions/gsd/auto-start.js +12 -0
  7. package/dist/resources/extensions/gsd/auto.js +27 -0
  8. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +11 -435
  9. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +1 -4
  10. package/dist/resources/extensions/gsd/bootstrap/query-tools.js +7 -64
  11. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +88 -8
  12. package/dist/resources/extensions/gsd/commands/catalog.js +2 -1
  13. package/dist/resources/extensions/gsd/commands/handlers/core.js +39 -25
  14. package/dist/resources/extensions/gsd/commands/index.js +8 -1
  15. package/dist/resources/extensions/gsd/commands-mcp-status.js +43 -7
  16. package/dist/resources/extensions/gsd/guided-flow.js +16 -0
  17. package/dist/resources/extensions/gsd/init-wizard.js +37 -0
  18. package/dist/resources/extensions/gsd/mcp-project-config.js +83 -0
  19. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +508 -0
  20. package/dist/resources/extensions/gsd/workflow-logger.js +18 -3
  21. package/dist/resources/extensions/gsd/workflow-mcp.js +261 -0
  22. package/dist/web/standalone/.next/BUILD_ID +1 -1
  23. package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
  24. package/dist/web/standalone/.next/build-manifest.json +3 -3
  25. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  26. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  27. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  28. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +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 +1 -1
  45. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  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-paths-manifest.json +15 -15
  51. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  52. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  53. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  54. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  55. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  56. package/dist/web/standalone/.next/static/chunks/6502.5dcdcf1e1432e20d.js +9 -0
  57. package/dist/web/standalone/.next/static/chunks/{webpack-b49b09f97429b5d0.js → webpack-42a66876b763aa26.js} +1 -1
  58. package/package.json +4 -2
  59. package/packages/mcp-server/README.md +38 -0
  60. package/packages/mcp-server/dist/cli.d.ts +9 -0
  61. package/packages/mcp-server/dist/cli.d.ts.map +1 -0
  62. package/packages/mcp-server/dist/cli.js +58 -0
  63. package/packages/mcp-server/dist/cli.js.map +1 -0
  64. package/packages/mcp-server/dist/index.d.ts +20 -0
  65. package/packages/mcp-server/dist/index.d.ts.map +1 -0
  66. package/packages/mcp-server/dist/index.js +14 -0
  67. package/packages/mcp-server/dist/index.js.map +1 -0
  68. package/packages/mcp-server/dist/readers/captures.d.ts +25 -0
  69. package/packages/mcp-server/dist/readers/captures.d.ts.map +1 -0
  70. package/packages/mcp-server/dist/readers/captures.js +67 -0
  71. package/packages/mcp-server/dist/readers/captures.js.map +1 -0
  72. package/packages/mcp-server/dist/readers/doctor-lite.d.ts +20 -0
  73. package/packages/mcp-server/dist/readers/doctor-lite.d.ts.map +1 -0
  74. package/packages/mcp-server/dist/readers/doctor-lite.js +173 -0
  75. package/packages/mcp-server/dist/readers/doctor-lite.js.map +1 -0
  76. package/packages/mcp-server/dist/readers/index.d.ts +14 -0
  77. package/packages/mcp-server/dist/readers/index.d.ts.map +1 -0
  78. package/packages/mcp-server/dist/readers/index.js +10 -0
  79. package/packages/mcp-server/dist/readers/index.js.map +1 -0
  80. package/packages/mcp-server/dist/readers/knowledge.d.ts +18 -0
  81. package/packages/mcp-server/dist/readers/knowledge.d.ts.map +1 -0
  82. package/packages/mcp-server/dist/readers/knowledge.js +82 -0
  83. package/packages/mcp-server/dist/readers/knowledge.js.map +1 -0
  84. package/packages/mcp-server/dist/readers/metrics.d.ts +32 -0
  85. package/packages/mcp-server/dist/readers/metrics.d.ts.map +1 -0
  86. package/packages/mcp-server/dist/readers/metrics.js +74 -0
  87. package/packages/mcp-server/dist/readers/metrics.js.map +1 -0
  88. package/packages/mcp-server/dist/readers/paths.d.ts +42 -0
  89. package/packages/mcp-server/dist/readers/paths.d.ts.map +1 -0
  90. package/packages/mcp-server/dist/readers/paths.js +199 -0
  91. package/packages/mcp-server/dist/readers/paths.js.map +1 -0
  92. package/packages/mcp-server/dist/readers/roadmap.d.ts +26 -0
  93. package/packages/mcp-server/dist/readers/roadmap.d.ts.map +1 -0
  94. package/packages/mcp-server/dist/readers/roadmap.js +194 -0
  95. package/packages/mcp-server/dist/readers/roadmap.js.map +1 -0
  96. package/packages/mcp-server/dist/readers/state.d.ts +43 -0
  97. package/packages/mcp-server/dist/readers/state.d.ts.map +1 -0
  98. package/packages/mcp-server/dist/readers/state.js +184 -0
  99. package/packages/mcp-server/dist/readers/state.js.map +1 -0
  100. package/packages/mcp-server/dist/server.d.ts +28 -0
  101. package/packages/mcp-server/dist/server.d.ts.map +1 -0
  102. package/packages/mcp-server/dist/server.js +319 -0
  103. package/packages/mcp-server/dist/server.js.map +1 -0
  104. package/packages/mcp-server/dist/session-manager.d.ts +54 -0
  105. package/packages/mcp-server/dist/session-manager.d.ts.map +1 -0
  106. package/packages/mcp-server/dist/session-manager.js +284 -0
  107. package/packages/mcp-server/dist/session-manager.js.map +1 -0
  108. package/packages/mcp-server/dist/types.d.ts +61 -0
  109. package/packages/mcp-server/dist/types.d.ts.map +1 -0
  110. package/packages/mcp-server/dist/types.js +11 -0
  111. package/packages/mcp-server/dist/types.js.map +1 -0
  112. package/packages/mcp-server/dist/workflow-tools.d.ts +9 -0
  113. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -0
  114. package/packages/mcp-server/dist/workflow-tools.js +532 -0
  115. package/packages/mcp-server/dist/workflow-tools.js.map +1 -0
  116. package/packages/mcp-server/src/server.ts +6 -2
  117. package/packages/mcp-server/src/workflow-tools.test.ts +976 -0
  118. package/packages/mcp-server/src/workflow-tools.ts +997 -0
  119. package/packages/mcp-server/tsconfig.json +1 -1
  120. package/packages/pi-agent-core/dist/agent-loop.js +14 -6
  121. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  122. package/packages/pi-agent-core/src/agent-loop.test.ts +53 -0
  123. package/packages/pi-agent-core/src/agent-loop.ts +20 -6
  124. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.d.ts +2 -0
  125. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.d.ts.map +1 -0
  126. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +28 -0
  127. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -0
  128. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -0
  129. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  130. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +17 -12
  131. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  132. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  133. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +19 -0
  134. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  135. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +54 -0
  136. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +18 -12
  137. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +21 -0
  138. package/packages/rpc-client/dist/index.d.ts +10 -0
  139. package/packages/rpc-client/dist/index.d.ts.map +1 -0
  140. package/packages/rpc-client/dist/index.js +9 -0
  141. package/packages/rpc-client/dist/index.js.map +1 -0
  142. package/packages/rpc-client/dist/jsonl.d.ts +17 -0
  143. package/packages/rpc-client/dist/jsonl.d.ts.map +1 -0
  144. package/packages/rpc-client/dist/jsonl.js +54 -0
  145. package/packages/rpc-client/dist/jsonl.js.map +1 -0
  146. package/packages/rpc-client/dist/rpc-client.d.ts +259 -0
  147. package/packages/rpc-client/dist/rpc-client.d.ts.map +1 -0
  148. package/packages/rpc-client/dist/rpc-client.js +541 -0
  149. package/packages/rpc-client/dist/rpc-client.js.map +1 -0
  150. package/packages/rpc-client/dist/rpc-client.test.d.ts +2 -0
  151. package/packages/rpc-client/dist/rpc-client.test.d.ts.map +1 -0
  152. package/packages/rpc-client/dist/rpc-client.test.js +477 -0
  153. package/packages/rpc-client/dist/rpc-client.test.js.map +1 -0
  154. package/packages/rpc-client/dist/rpc-types.d.ts +566 -0
  155. package/packages/rpc-client/dist/rpc-types.d.ts.map +1 -0
  156. package/packages/rpc-client/dist/rpc-types.js +12 -0
  157. package/packages/rpc-client/dist/rpc-types.js.map +1 -0
  158. package/scripts/ensure-workspace-builds.cjs +2 -0
  159. package/scripts/link-workspace-packages.cjs +21 -14
  160. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +157 -8
  161. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +182 -0
  162. package/src/resources/extensions/gsd/auto/phases.ts +25 -0
  163. package/src/resources/extensions/gsd/auto/session.ts +6 -0
  164. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +20 -0
  165. package/src/resources/extensions/gsd/auto-start.ts +15 -1
  166. package/src/resources/extensions/gsd/auto.ts +29 -1
  167. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +22 -435
  168. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +1 -5
  169. package/src/resources/extensions/gsd/bootstrap/query-tools.ts +7 -72
  170. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +122 -6
  171. package/src/resources/extensions/gsd/commands/catalog.ts +2 -1
  172. package/src/resources/extensions/gsd/commands/handlers/core.ts +53 -26
  173. package/src/resources/extensions/gsd/commands/index.ts +7 -1
  174. package/src/resources/extensions/gsd/commands-mcp-status.ts +53 -7
  175. package/src/resources/extensions/gsd/guided-flow.ts +24 -0
  176. package/src/resources/extensions/gsd/init-wizard.ts +40 -0
  177. package/src/resources/extensions/gsd/mcp-project-config.ts +128 -0
  178. package/src/resources/extensions/gsd/tests/auto-project-root-env.test.ts +29 -0
  179. package/src/resources/extensions/gsd/tests/core-overlay-fallback.test.ts +101 -0
  180. package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +66 -0
  181. package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +85 -0
  182. package/src/resources/extensions/gsd/tests/mcp-status.test.ts +15 -0
  183. package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +16 -0
  184. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +500 -0
  185. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +625 -0
  186. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +629 -0
  187. package/src/resources/extensions/gsd/workflow-logger.ts +19 -3
  188. package/src/resources/extensions/gsd/workflow-mcp.ts +320 -0
  189. package/dist/web/standalone/.next/static/chunks/6502.b804e48b7919f55e.js +0 -9
  190. /package/dist/web/standalone/.next/static/{NllX5BEOLdTXS9ypf1i3i → gbSATDX4Jt2ufxzUr5nYm}/_buildManifest.js +0 -0
  191. /package/dist/web/standalone/.next/static/{NllX5BEOLdTXS9ypf1i3i → gbSATDX4Jt2ufxzUr5nYm}/_ssgManifest.js +0 -0
@@ -2,7 +2,8 @@
2
2
  /**
3
3
  * link-workspace-packages.cjs
4
4
  *
5
- * Creates node_modules/@gsd/* symlinks pointing to packages/* directories.
5
+ * Creates node_modules/@gsd/* and node_modules/@gsd-build/* symlinks pointing
6
+ * to shipped packages/* directories.
6
7
  *
7
8
  * During development, npm workspaces creates these automatically. But in the
8
9
  * published tarball, workspace packages are shipped under packages/ (via the
@@ -20,27 +21,33 @@ const { resolve, join } = require('path')
20
21
 
21
22
  const root = resolve(__dirname, '..')
22
23
  const packagesDir = join(root, 'packages')
23
- const nodeModulesGsd = join(root, 'node_modules', '@gsd')
24
+ const scopeDirs = {
25
+ '@gsd': join(root, 'node_modules', '@gsd'),
26
+ '@gsd-build': join(root, 'node_modules', '@gsd-build'),
27
+ }
24
28
 
25
- // Map directory names to package names
29
+ // Map directory names to scoped package names
26
30
  const packageMap = {
27
- 'native': 'native',
28
- 'pi-agent-core': 'pi-agent-core',
29
- 'pi-ai': 'pi-ai',
30
- 'pi-coding-agent': 'pi-coding-agent',
31
- 'pi-tui': 'pi-tui',
31
+ 'native': { scope: '@gsd', name: 'native' },
32
+ 'pi-agent-core': { scope: '@gsd', name: 'pi-agent-core' },
33
+ 'pi-ai': { scope: '@gsd', name: 'pi-ai' },
34
+ 'pi-coding-agent': { scope: '@gsd', name: 'pi-coding-agent' },
35
+ 'pi-tui': { scope: '@gsd', name: 'pi-tui' },
36
+ 'rpc-client': { scope: '@gsd-build', name: 'rpc-client' },
32
37
  }
33
38
 
34
- // Ensure @gsd scope directory exists
35
- if (!existsSync(nodeModulesGsd)) {
36
- mkdirSync(nodeModulesGsd, { recursive: true })
39
+ for (const scopeDir of Object.values(scopeDirs)) {
40
+ if (!existsSync(scopeDir)) {
41
+ mkdirSync(scopeDir, { recursive: true })
42
+ }
37
43
  }
38
44
 
39
45
  let linked = 0
40
46
  let copied = 0
41
- for (const [dir, name] of Object.entries(packageMap)) {
47
+ for (const [dir, pkg] of Object.entries(packageMap)) {
42
48
  const source = join(packagesDir, dir)
43
- const target = join(nodeModulesGsd, name)
49
+ const scopeDir = scopeDirs[pkg.scope]
50
+ const target = join(scopeDir, pkg.name)
44
51
 
45
52
  if (!existsSync(source)) continue
46
53
 
@@ -50,7 +57,7 @@ for (const [dir, name] of Object.entries(packageMap)) {
50
57
  const stat = lstatSync(target)
51
58
  if (stat.isSymbolicLink()) {
52
59
  const linkTarget = readlinkSync(target)
53
- if (resolve(join(nodeModulesGsd, linkTarget)) === source || linkTarget === source) {
60
+ if (resolve(join(scopeDir, linkTarget)) === source || linkTarget === source) {
54
61
  continue // Already correct
55
62
  }
56
63
  unlinkSync(target) // Wrong target, relink
@@ -14,17 +14,37 @@ import type {
14
14
  Context,
15
15
  Model,
16
16
  SimpleStreamOptions,
17
+ ToolCall,
17
18
  } from "@gsd/pi-ai";
18
19
  import { EventStream } from "@gsd/pi-ai";
19
20
  import { execSync } from "node:child_process";
20
21
  import { PartialMessageBuilder, ZERO_USAGE, mapUsage } from "./partial-builder.js";
22
+ import { buildWorkflowMcpServers } from "../gsd/workflow-mcp.js";
21
23
  import type {
22
24
  SDKAssistantMessage,
23
25
  SDKMessage,
24
26
  SDKPartialAssistantMessage,
25
27
  SDKResultMessage,
28
+ SDKUserMessage,
26
29
  } from "./sdk-types.js";
27
30
 
31
+ export interface ExternalToolResultContentBlock {
32
+ type: string;
33
+ text?: string;
34
+ data?: string;
35
+ mimeType?: string;
36
+ }
37
+
38
+ export interface ExternalToolResultPayload {
39
+ content: ExternalToolResultContentBlock[];
40
+ details?: Record<string, unknown>;
41
+ isError: boolean;
42
+ }
43
+
44
+ type ToolCallWithExternalResult = ToolCall & {
45
+ externalResult?: ExternalToolResultPayload;
46
+ };
47
+
28
48
  // ---------------------------------------------------------------------------
29
49
  // Stream factory
30
50
  // ---------------------------------------------------------------------------
@@ -163,6 +183,7 @@ export function makeStreamExhaustedErrorMessage(model: string, lastTextContent:
163
183
  * beta flags, and other configuration without mocking the full SDK.
164
184
  */
165
185
  export function buildSdkOptions(modelId: string, prompt: string): Record<string, unknown> {
186
+ const mcpServers = buildWorkflowMcpServers();
166
187
  return {
167
188
  pathToClaudeCodeExecutable: getClaudePath(),
168
189
  model: modelId,
@@ -173,10 +194,115 @@ export function buildSdkOptions(modelId: string, prompt: string): Record<string,
173
194
  allowDangerouslySkipPermissions: true,
174
195
  settingSources: ["project"],
175
196
  systemPrompt: { type: "preset", preset: "claude_code" },
197
+ ...(mcpServers ? { mcpServers } : {}),
176
198
  betas: modelId.includes("sonnet") ? ["context-1m-2025-08-07"] : [],
177
199
  };
178
200
  }
179
201
 
202
+ function normalizeToolResultContent(content: unknown): ExternalToolResultContentBlock[] {
203
+ if (typeof content === "string") {
204
+ return [{ type: "text", text: content }];
205
+ }
206
+
207
+ if (!Array.isArray(content)) {
208
+ if (content == null) return [{ type: "text", text: "" }];
209
+ return [{ type: "text", text: JSON.stringify(content) }];
210
+ }
211
+
212
+ const blocks: ExternalToolResultContentBlock[] = [];
213
+
214
+ for (const item of content) {
215
+ if (typeof item === "string") {
216
+ blocks.push({ type: "text", text: item });
217
+ continue;
218
+ }
219
+ if (!item || typeof item !== "object") {
220
+ blocks.push({ type: "text", text: String(item) });
221
+ continue;
222
+ }
223
+
224
+ const block = item as Record<string, unknown>;
225
+ if (block.type === "text") {
226
+ blocks.push({ type: "text", text: typeof block.text === "string" ? block.text : "" });
227
+ continue;
228
+ }
229
+ if (
230
+ block.type === "image"
231
+ && typeof block.data === "string"
232
+ && typeof block.mimeType === "string"
233
+ ) {
234
+ blocks.push({ type: "image", data: block.data, mimeType: block.mimeType });
235
+ continue;
236
+ }
237
+
238
+ blocks.push({ type: "text", text: JSON.stringify(block) });
239
+ }
240
+
241
+ return blocks.length > 0 ? blocks : [{ type: "text", text: "" }];
242
+ }
243
+
244
+ export function extractToolResultsFromSdkUserMessage(message: SDKUserMessage): Array<{
245
+ toolUseId: string;
246
+ result: ExternalToolResultPayload;
247
+ }> {
248
+ const extracted: Array<{ toolUseId: string; result: ExternalToolResultPayload }> = [];
249
+ const seen = new Set<string>();
250
+ const rawMessage = message.message as Record<string, unknown> | null | undefined;
251
+ const content = Array.isArray(rawMessage?.content) ? rawMessage.content : [];
252
+
253
+ for (const item of content) {
254
+ if (!item || typeof item !== "object") continue;
255
+ const block = item as Record<string, unknown>;
256
+ const type = typeof block.type === "string" ? block.type : "";
257
+ if (type !== "tool_result" && type !== "mcp_tool_result") continue;
258
+
259
+ const toolUseId = typeof block.tool_use_id === "string" ? block.tool_use_id : "";
260
+ if (!toolUseId || seen.has(toolUseId)) continue;
261
+ seen.add(toolUseId);
262
+
263
+ extracted.push({
264
+ toolUseId,
265
+ result: {
266
+ content: normalizeToolResultContent(block.content),
267
+ details: {},
268
+ isError: block.is_error === true,
269
+ },
270
+ });
271
+ }
272
+
273
+ if (extracted.length === 0) {
274
+ const fallback = message.tool_use_result;
275
+ if (fallback && typeof fallback === "object") {
276
+ const toolResult = fallback as Record<string, unknown>;
277
+ const toolUseId = typeof toolResult.tool_use_id === "string" ? toolResult.tool_use_id : "";
278
+ if (toolUseId) {
279
+ extracted.push({
280
+ toolUseId,
281
+ result: {
282
+ content: normalizeToolResultContent(toolResult.content),
283
+ details: {},
284
+ isError: toolResult.is_error === true,
285
+ },
286
+ });
287
+ }
288
+ }
289
+ }
290
+
291
+ return extracted;
292
+ }
293
+
294
+ function attachExternalResultsToToolCalls(
295
+ toolCalls: AssistantMessage["content"],
296
+ toolResultsById: ReadonlyMap<string, ExternalToolResultPayload>,
297
+ ): void {
298
+ for (const block of toolCalls) {
299
+ if (block.type !== "toolCall") continue;
300
+ const externalResult = toolResultsById.get(block.id);
301
+ if (!externalResult) continue;
302
+ (block as ToolCallWithExternalResult).externalResult = externalResult;
303
+ }
304
+ }
305
+
180
306
  // ---------------------------------------------------------------------------
181
307
  // streamSimple implementation
182
308
  // ---------------------------------------------------------------------------
@@ -213,6 +339,8 @@ async function pumpSdkMessages(
213
339
  let lastThinkingContent = "";
214
340
  /** Collect tool calls from intermediate SDK turns for tool_execution events. */
215
341
  const intermediateToolCalls: AssistantMessage["content"] = [];
342
+ /** Preserve real external tool results from Claude Code's synthetic user messages. */
343
+ const toolResultsById = new Map<string, ExternalToolResultPayload>();
216
344
 
217
345
  try {
218
346
  // Dynamic import — the SDK is an optional dependency.
@@ -282,14 +410,7 @@ async function pumpSdkMessages(
282
410
 
283
411
  const assistantEvent = builder.handleEvent(event);
284
412
  if (assistantEvent) {
285
- // Skip toolcall events — the agent loop's externalToolExecution
286
- // path emits tool_execution_start/end events after streamSimple
287
- // returns. Streaming toolcall events would render tool calls
288
- // out of order in the TUI's accumulated message content.
289
- const t = assistantEvent.type;
290
- if (t !== "toolcall_start" && t !== "toolcall_delta" && t !== "toolcall_end") {
291
- stream.push(assistantEvent);
292
- }
413
+ stream.push(assistantEvent);
293
414
  }
294
415
  break;
295
416
  }
@@ -324,6 +445,33 @@ async function pumpSdkMessages(
324
445
  }
325
446
  }
326
447
  }
448
+
449
+ // Extract tool results from the SDK's synthetic user message
450
+ // and attach to corresponding tool call blocks immediately.
451
+ for (const { toolUseId, result } of extractToolResultsFromSdkUserMessage(msg as SDKUserMessage)) {
452
+ toolResultsById.set(toolUseId, result);
453
+ }
454
+ attachExternalResultsToToolCalls(intermediateToolCalls, toolResultsById);
455
+
456
+ // Push a synthetic toolcall_end for each tool call from this turn
457
+ // so the TUI can render tool results in real-time during the SDK
458
+ // session instead of waiting until the entire session completes.
459
+ if (builder) {
460
+ for (const block of builder.message.content) {
461
+ if (block.type !== "toolCall") continue;
462
+ const extResult = (block as ToolCallWithExternalResult).externalResult;
463
+ if (!extResult) continue;
464
+ // Push a toolcall_end with result attached so the chat-controller
465
+ // can call updateResult on the pending ToolExecutionComponent.
466
+ stream.push({
467
+ type: "toolcall_end",
468
+ contentIndex: builder.message.content.indexOf(block),
469
+ toolCall: block,
470
+ partial: builder.message,
471
+ });
472
+ }
473
+ }
474
+
327
475
  builder = null;
328
476
  break;
329
477
  }
@@ -338,6 +486,7 @@ async function pumpSdkMessages(
338
486
  const finalContent: AssistantMessage["content"] = [];
339
487
 
340
488
  // Add tool calls from intermediate turns first (renders above text)
489
+ attachExternalResultsToToolCalls(intermediateToolCalls, toolResultsById);
341
490
  finalContent.push(...intermediateToolCalls);
342
491
 
343
492
  // Add text/thinking from the last turn
@@ -1,13 +1,18 @@
1
1
  import { describe, test } from "node:test";
2
2
  import assert from "node:assert/strict";
3
+ import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
4
+ import { join, resolve } from "node:path";
5
+ import { tmpdir } from "node:os";
3
6
  import {
4
7
  makeStreamExhaustedErrorMessage,
5
8
  buildPromptFromContext,
6
9
  buildSdkOptions,
10
+ extractToolResultsFromSdkUserMessage,
7
11
  getClaudeLookupCommand,
8
12
  parseClaudeLookupOutput,
9
13
  } from "../stream-adapter.ts";
10
14
  import type { Context, Message } from "@gsd/pi-ai";
15
+ import type { SDKUserMessage } from "../sdk-types.ts";
11
16
 
12
17
  // ---------------------------------------------------------------------------
13
18
  // Existing tests — exhausted stream fallback (#2575)
@@ -103,6 +108,65 @@ describe("stream-adapter — full context prompt (#2859)", () => {
103
108
  });
104
109
  });
105
110
 
111
+ describe("stream-adapter — Claude Code external tool results", () => {
112
+ test("extractToolResultsFromSdkUserMessage maps tool_result content to tool payloads", () => {
113
+ const message: SDKUserMessage = {
114
+ type: "user",
115
+ session_id: "sess-1",
116
+ parent_tool_use_id: "tool-bash-1",
117
+ message: {
118
+ role: "user",
119
+ content: [
120
+ {
121
+ type: "tool_result",
122
+ tool_use_id: "tool-bash-1",
123
+ content: "line 1\nline 2",
124
+ is_error: false,
125
+ },
126
+ ],
127
+ },
128
+ };
129
+
130
+ const results = extractToolResultsFromSdkUserMessage(message);
131
+ assert.deepEqual(results, [
132
+ {
133
+ toolUseId: "tool-bash-1",
134
+ result: {
135
+ content: [{ type: "text", text: "line 1\nline 2" }],
136
+ details: {},
137
+ isError: false,
138
+ },
139
+ },
140
+ ]);
141
+ });
142
+
143
+ test("extractToolResultsFromSdkUserMessage falls back to tool_use_result", () => {
144
+ const message: SDKUserMessage = {
145
+ type: "user",
146
+ session_id: "sess-1",
147
+ parent_tool_use_id: "tool-read-1",
148
+ message: { role: "user", content: [] },
149
+ tool_use_result: {
150
+ tool_use_id: "tool-read-1",
151
+ content: "file contents",
152
+ is_error: true,
153
+ },
154
+ };
155
+
156
+ const results = extractToolResultsFromSdkUserMessage(message);
157
+ assert.deepEqual(results, [
158
+ {
159
+ toolUseId: "tool-read-1",
160
+ result: {
161
+ content: [{ type: "text", text: "file contents" }],
162
+ details: {},
163
+ isError: true,
164
+ },
165
+ },
166
+ ]);
167
+ });
168
+ });
169
+
106
170
  describe("stream-adapter — session persistence (#2859)", () => {
107
171
  test("buildSdkOptions enables persistSession by default", () => {
108
172
  const options = buildSdkOptions("claude-sonnet-4-20250514", "test prompt");
@@ -127,6 +191,124 @@ describe("stream-adapter — session persistence (#2859)", () => {
127
191
  "non-sonnet models should have empty betas",
128
192
  );
129
193
  });
194
+
195
+ test("buildSdkOptions includes workflow MCP server config when env is set", () => {
196
+ const prev = {
197
+ GSD_WORKFLOW_MCP_COMMAND: process.env.GSD_WORKFLOW_MCP_COMMAND,
198
+ GSD_WORKFLOW_MCP_NAME: process.env.GSD_WORKFLOW_MCP_NAME,
199
+ GSD_WORKFLOW_MCP_ARGS: process.env.GSD_WORKFLOW_MCP_ARGS,
200
+ GSD_WORKFLOW_MCP_ENV: process.env.GSD_WORKFLOW_MCP_ENV,
201
+ GSD_WORKFLOW_MCP_CWD: process.env.GSD_WORKFLOW_MCP_CWD,
202
+ };
203
+ try {
204
+ process.env.GSD_WORKFLOW_MCP_COMMAND = "node";
205
+ process.env.GSD_WORKFLOW_MCP_NAME = "gsd-workflow";
206
+ process.env.GSD_WORKFLOW_MCP_ARGS = JSON.stringify(["packages/mcp-server/dist/cli.js"]);
207
+ process.env.GSD_WORKFLOW_MCP_ENV = JSON.stringify({ GSD_CLI_PATH: "/tmp/gsd" });
208
+ process.env.GSD_WORKFLOW_MCP_CWD = "/tmp/project";
209
+
210
+ const options = buildSdkOptions("claude-sonnet-4-20250514", "test");
211
+ const mcpServers = options.mcpServers as Record<string, any>;
212
+ assert.ok(mcpServers?.["gsd-workflow"], "expected gsd-workflow server config");
213
+ const srv = mcpServers["gsd-workflow"];
214
+ assert.equal(srv.command, "node");
215
+ assert.deepEqual(srv.args, ["packages/mcp-server/dist/cli.js"]);
216
+ assert.equal(srv.cwd, "/tmp/project");
217
+ assert.equal(srv.env.GSD_CLI_PATH, "/tmp/gsd");
218
+ assert.equal(srv.env.GSD_PERSIST_WRITE_GATE_STATE, "1");
219
+ assert.equal(srv.env.GSD_WORKFLOW_PROJECT_ROOT, "/tmp/project");
220
+ } finally {
221
+ process.env.GSD_WORKFLOW_MCP_COMMAND = prev.GSD_WORKFLOW_MCP_COMMAND;
222
+ process.env.GSD_WORKFLOW_MCP_NAME = prev.GSD_WORKFLOW_MCP_NAME;
223
+ process.env.GSD_WORKFLOW_MCP_ARGS = prev.GSD_WORKFLOW_MCP_ARGS;
224
+ process.env.GSD_WORKFLOW_MCP_ENV = prev.GSD_WORKFLOW_MCP_ENV;
225
+ process.env.GSD_WORKFLOW_MCP_CWD = prev.GSD_WORKFLOW_MCP_CWD;
226
+ }
227
+ });
228
+
229
+ test("buildSdkOptions auto-discovers bundled MCP server even without env hints", () => {
230
+ const prev = {
231
+ GSD_WORKFLOW_MCP_COMMAND: process.env.GSD_WORKFLOW_MCP_COMMAND,
232
+ GSD_WORKFLOW_MCP_NAME: process.env.GSD_WORKFLOW_MCP_NAME,
233
+ GSD_WORKFLOW_MCP_ARGS: process.env.GSD_WORKFLOW_MCP_ARGS,
234
+ GSD_WORKFLOW_MCP_ENV: process.env.GSD_WORKFLOW_MCP_ENV,
235
+ GSD_WORKFLOW_MCP_CWD: process.env.GSD_WORKFLOW_MCP_CWD,
236
+ };
237
+ try {
238
+ delete process.env.GSD_WORKFLOW_MCP_COMMAND;
239
+ delete process.env.GSD_WORKFLOW_MCP_NAME;
240
+ delete process.env.GSD_WORKFLOW_MCP_ARGS;
241
+ delete process.env.GSD_WORKFLOW_MCP_ENV;
242
+ delete process.env.GSD_WORKFLOW_MCP_CWD;
243
+
244
+ const originalCwd = process.cwd();
245
+ const emptyDir = mkdtempSync(join(tmpdir(), "claude-mcp-none-"));
246
+ process.chdir(emptyDir);
247
+ const options = buildSdkOptions("claude-sonnet-4-20250514", "test");
248
+ process.chdir(originalCwd);
249
+ // The bundled CLI may or may not be discoverable depending on
250
+ // whether the build output exists relative to import.meta.url.
251
+ // Either outcome is valid — the key invariant is no crash.
252
+ const mcpServers = (options as any).mcpServers;
253
+ if (mcpServers) {
254
+ assert.ok(mcpServers["gsd-workflow"], "if present, must be gsd-workflow");
255
+ }
256
+ rmSync(emptyDir, { recursive: true, force: true });
257
+ } finally {
258
+ process.env.GSD_WORKFLOW_MCP_COMMAND = prev.GSD_WORKFLOW_MCP_COMMAND;
259
+ process.env.GSD_WORKFLOW_MCP_NAME = prev.GSD_WORKFLOW_MCP_NAME;
260
+ process.env.GSD_WORKFLOW_MCP_ARGS = prev.GSD_WORKFLOW_MCP_ARGS;
261
+ process.env.GSD_WORKFLOW_MCP_ENV = prev.GSD_WORKFLOW_MCP_ENV;
262
+ process.env.GSD_WORKFLOW_MCP_CWD = prev.GSD_WORKFLOW_MCP_CWD;
263
+ }
264
+ });
265
+
266
+ test("buildSdkOptions auto-detects local workflow MCP dist CLI when present", () => {
267
+ const prev = {
268
+ GSD_WORKFLOW_MCP_COMMAND: process.env.GSD_WORKFLOW_MCP_COMMAND,
269
+ GSD_WORKFLOW_MCP_NAME: process.env.GSD_WORKFLOW_MCP_NAME,
270
+ GSD_WORKFLOW_MCP_ARGS: process.env.GSD_WORKFLOW_MCP_ARGS,
271
+ GSD_WORKFLOW_MCP_ENV: process.env.GSD_WORKFLOW_MCP_ENV,
272
+ GSD_WORKFLOW_MCP_CWD: process.env.GSD_WORKFLOW_MCP_CWD,
273
+ GSD_CLI_PATH: process.env.GSD_CLI_PATH,
274
+ };
275
+ const originalCwd = process.cwd();
276
+ const repoDir = mkdtempSync(join(tmpdir(), "claude-mcp-detect-"));
277
+ try {
278
+ delete process.env.GSD_WORKFLOW_MCP_COMMAND;
279
+ delete process.env.GSD_WORKFLOW_MCP_NAME;
280
+ delete process.env.GSD_WORKFLOW_MCP_ARGS;
281
+ delete process.env.GSD_WORKFLOW_MCP_ENV;
282
+ delete process.env.GSD_WORKFLOW_MCP_CWD;
283
+ process.env.GSD_CLI_PATH = "/tmp/gsd";
284
+
285
+ const distDir = join(repoDir, "packages", "mcp-server", "dist");
286
+ mkdirSync(distDir, { recursive: true });
287
+ writeFileSync(join(distDir, "cli.js"), "#!/usr/bin/env node\n");
288
+ process.chdir(repoDir);
289
+ const resolvedRepoDir = realpathSync(repoDir);
290
+
291
+ const options = buildSdkOptions("claude-sonnet-4-20250514", "test");
292
+ const mcpServers = options.mcpServers as Record<string, any>;
293
+ assert.ok(mcpServers?.["gsd-workflow"], "expected gsd-workflow server config");
294
+ const srv = mcpServers["gsd-workflow"];
295
+ assert.equal(srv.command, process.execPath);
296
+ assert.deepEqual(srv.args, [realpathSync(resolve(repoDir, "packages", "mcp-server", "dist", "cli.js"))]);
297
+ assert.equal(srv.cwd, resolvedRepoDir);
298
+ assert.equal(srv.env.GSD_CLI_PATH, "/tmp/gsd");
299
+ assert.equal(srv.env.GSD_PERSIST_WRITE_GATE_STATE, "1");
300
+ assert.equal(srv.env.GSD_WORKFLOW_PROJECT_ROOT, resolvedRepoDir);
301
+ } finally {
302
+ process.chdir(originalCwd);
303
+ rmSync(repoDir, { recursive: true, force: true });
304
+ process.env.GSD_WORKFLOW_MCP_COMMAND = prev.GSD_WORKFLOW_MCP_COMMAND;
305
+ process.env.GSD_WORKFLOW_MCP_NAME = prev.GSD_WORKFLOW_MCP_NAME;
306
+ process.env.GSD_WORKFLOW_MCP_ARGS = prev.GSD_WORKFLOW_MCP_ARGS;
307
+ process.env.GSD_WORKFLOW_MCP_ENV = prev.GSD_WORKFLOW_MCP_ENV;
308
+ process.env.GSD_WORKFLOW_MCP_CWD = prev.GSD_WORKFLOW_MCP_CWD;
309
+ process.env.GSD_CLI_PATH = prev.GSD_CLI_PATH;
310
+ }
311
+ });
130
312
  });
131
313
 
132
314
  describe("stream-adapter — Windows Claude path lookup (#3770)", () => {
@@ -41,6 +41,10 @@ import { isDbAvailable, getMilestoneSlices } from "../gsd-db.js";
41
41
  import { resetEvidence } from "../safety/evidence-collector.js";
42
42
  import { createCheckpoint, cleanupCheckpoint, rollbackToCheckpoint } from "../safety/git-checkpoint.js";
43
43
  import { resolveSafetyHarnessConfig } from "../safety/safety-harness.js";
44
+ import {
45
+ getWorkflowTransportSupportError,
46
+ getRequiredWorkflowToolsForAutoUnit,
47
+ } from "../workflow-mcp.js";
44
48
 
45
49
  // ─── generateMilestoneReport ──────────────────────────────────────────────────
46
50
 
@@ -1216,6 +1220,27 @@ export async function runUnitPhase(
1216
1220
  ? `${(s.currentUnitModel as any).provider ?? ""}/${(s.currentUnitModel as any).id ?? ""}`
1217
1221
  : null;
1218
1222
 
1223
+ const compatibilityError = getWorkflowTransportSupportError(
1224
+ s.currentUnitModel?.provider ?? ctx.model?.provider,
1225
+ getRequiredWorkflowToolsForAutoUnit(unitType),
1226
+ {
1227
+ projectRoot: s.basePath,
1228
+ surface: "auto-mode",
1229
+ unitType,
1230
+ authMode: s.currentUnitModel?.provider
1231
+ ? ctx.modelRegistry.getProviderAuthMode(s.currentUnitModel.provider)
1232
+ : ctx.model?.provider
1233
+ ? ctx.modelRegistry.getProviderAuthMode(ctx.model.provider)
1234
+ : undefined,
1235
+ baseUrl: (s.currentUnitModel as any)?.baseUrl ?? ctx.model?.baseUrl,
1236
+ },
1237
+ );
1238
+ if (compatibilityError) {
1239
+ ctx.ui.notify(compatibilityError, "error");
1240
+ await deps.stopAuto(ctx, pi, compatibilityError);
1241
+ return { action: "break", reason: "workflow-capability" };
1242
+ }
1243
+
1219
1244
  // Progress widget + preconditions — deferred to after model selection so the
1220
1245
  // widget's first render tick shows the correct model (#2899).
1221
1246
  deps.updateProgressWidget(ctx, unitType, unitId, state);
@@ -84,6 +84,9 @@ export class AutoSession {
84
84
  // ── Paths ────────────────────────────────────────────────────────────────
85
85
  basePath = "";
86
86
  originalBasePath = "";
87
+ previousProjectRootEnv: string | null = null;
88
+ hadProjectRootEnv = false;
89
+ projectRootEnvCaptured = false;
87
90
  gitService: GitServiceImpl | null = null;
88
91
 
89
92
  // ── Dispatch counters ────────────────────────────────────────────────────
@@ -192,6 +195,9 @@ export class AutoSession {
192
195
  // Paths
193
196
  this.basePath = "";
194
197
  this.originalBasePath = "";
198
+ this.previousProjectRootEnv = null;
199
+ this.hadProjectRootEnv = false;
200
+ this.projectRootEnvCaptured = false;
195
201
  this.gitService = null;
196
202
 
197
203
  // Dispatch
@@ -29,6 +29,10 @@ import {
29
29
  } from "./auto-prompts.js";
30
30
  import { loadEffectiveGSDPreferences } from "./preferences.js";
31
31
  import { pauseAuto } from "./auto.js";
32
+ import {
33
+ getWorkflowTransportSupportError,
34
+ getRequiredWorkflowToolsForAutoUnit,
35
+ } from "./workflow-mcp.js";
32
36
 
33
37
  export async function dispatchDirectPhase(
34
38
  ctx: ExtensionCommandContext,
@@ -243,6 +247,22 @@ export async function dispatchDirectPhase(
243
247
  return;
244
248
  }
245
249
 
250
+ const compatibilityError = getWorkflowTransportSupportError(
251
+ ctx.model?.provider,
252
+ getRequiredWorkflowToolsForAutoUnit(unitType),
253
+ {
254
+ projectRoot: base,
255
+ surface: "direct phase dispatch",
256
+ unitType,
257
+ authMode: ctx.model?.provider ? ctx.modelRegistry.getProviderAuthMode(ctx.model.provider) : undefined,
258
+ baseUrl: ctx.model?.baseUrl,
259
+ },
260
+ );
261
+ if (compatibilityError) {
262
+ ctx.ui.notify(compatibilityError, "error");
263
+ return;
264
+ }
265
+
246
266
  ctx.ui.notify(`Dispatching ${unitType} for ${unitId}...`, "info");
247
267
  const result = await ctx.newSession();
248
268
  if (result.cancelled) {
@@ -340,6 +340,21 @@ export async function bootstrapAutoSession(
340
340
  }
341
341
  }
342
342
 
343
+ if (ctx.model?.provider === "claude-code") {
344
+ try {
345
+ const { ensureProjectWorkflowMcpConfig } = await import("./mcp-project-config.js");
346
+ const result = ensureProjectWorkflowMcpConfig(base);
347
+ if (result.status !== "unchanged") {
348
+ ctx.ui.notify(`Claude Code MCP prepared at ${result.configPath}`, "info");
349
+ }
350
+ } catch (err) {
351
+ ctx.ui.notify(
352
+ `Claude Code MCP prep failed: ${err instanceof Error ? err.message : String(err)}`,
353
+ "warning",
354
+ );
355
+ }
356
+ }
357
+
343
358
  // Initialize GitServiceImpl
344
359
  s.gitService = new GitServiceImpl(
345
360
  s.basePath,
@@ -909,4 +924,3 @@ export async function bootstrapAutoSession(
909
924
  throw err;
910
925
  }
911
926
  }
912
-