gsd-pi 2.67.0-dev.5399650 → 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.
- package/README.md +1 -1
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +152 -70
- package/dist/resources/extensions/gsd/auto/session.js +4 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +1 -1
- package/dist/resources/extensions/gsd/auto-start.js +16 -30
- package/dist/resources/extensions/gsd/auto-worktree.js +62 -15
- package/dist/resources/extensions/gsd/auto.js +94 -59
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +7 -2
- package/dist/resources/extensions/gsd/commands/catalog.js +2 -1
- package/dist/resources/extensions/gsd/commands/handlers/core.js +1 -1
- package/dist/resources/extensions/gsd/commands-mcp-status.js +43 -7
- package/dist/resources/extensions/gsd/doctor-git-checks.js +4 -4
- package/dist/resources/extensions/gsd/doctor-proactive.js +3 -3
- package/dist/resources/extensions/gsd/doctor.js +8 -4
- package/dist/resources/extensions/gsd/guided-flow.js +40 -31
- package/dist/resources/extensions/gsd/init-wizard.js +15 -12
- package/dist/resources/extensions/gsd/interrupted-session.js +146 -0
- package/dist/resources/extensions/gsd/mcp-project-config.js +83 -0
- package/dist/resources/extensions/gsd/workflow-mcp.js +64 -24
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +16 -16
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/react-loadable-manifest.json +2 -2
- package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +16 -16
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/2826.821e01b07d92e948.js +9 -0
- package/dist/web/standalone/.next/static/chunks/app/{page-0c485498795110d6.js → page-f1e30ab6bb269149.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/{webpack-b49b09f97429b5d0.js → webpack-6e4d7e9a4f57bed4.js} +1 -1
- package/package.json +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +10 -4
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/workflow-tools.ts +13 -2
- package/packages/pi-agent-core/dist/agent-loop.js +14 -6
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/src/agent-loop.test.ts +53 -0
- package/packages/pi-agent-core/src/agent-loop.ts +20 -6
- package/packages/pi-coding-agent/dist/core/contextual-tips.d.ts +43 -0
- package/packages/pi-coding-agent/dist/core/contextual-tips.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/contextual-tips.js +208 -0
- package/packages/pi-coding-agent/dist/core/contextual-tips.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/contextual-tips.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/contextual-tips.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/contextual-tips.test.js +227 -0
- package/packages/pi-coding-agent/dist/core/contextual-tips.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/index.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/index.js +1 -0
- package/packages/pi-coding-agent/dist/core/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +28 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +17 -12
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +19 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts +4 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +14 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +3 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +15 -12
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/core/contextual-tips.test.ts +259 -0
- package/packages/pi-coding-agent/src/core/contextual-tips.ts +232 -0
- package/packages/pi-coding-agent/src/core/index.ts +2 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +54 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +18 -12
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +21 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +19 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +19 -15
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +190 -93
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +89 -116
- package/src/resources/extensions/gsd/auto/session.ts +4 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +1 -1
- package/src/resources/extensions/gsd/auto-start.ts +23 -55
- package/src/resources/extensions/gsd/auto-worktree.ts +59 -15
- package/src/resources/extensions/gsd/auto.ts +104 -63
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +8 -2
- package/src/resources/extensions/gsd/commands/catalog.ts +2 -1
- package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -1
- package/src/resources/extensions/gsd/commands-mcp-status.ts +53 -7
- package/src/resources/extensions/gsd/doctor-git-checks.ts +4 -4
- package/src/resources/extensions/gsd/doctor-proactive.ts +3 -3
- package/src/resources/extensions/gsd/doctor.ts +9 -5
- package/src/resources/extensions/gsd/guided-flow.ts +42 -36
- package/src/resources/extensions/gsd/init-wizard.ts +17 -11
- package/src/resources/extensions/gsd/interrupted-session.ts +224 -0
- package/src/resources/extensions/gsd/mcp-project-config.ts +128 -0
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +668 -2
- package/src/resources/extensions/gsd/tests/cold-resume-db-reopen.test.ts +14 -4
- package/src/resources/extensions/gsd/tests/copy-planning-artifacts-samepath.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +380 -2
- package/src/resources/extensions/gsd/tests/forensics-context-persist.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/integration/doctor-fixlevel.test.ts +52 -1
- package/src/resources/extensions/gsd/tests/integration/doctor-git.test.ts +2 -9
- package/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts +0 -33
- package/src/resources/extensions/gsd/tests/integration/merge-cwd-restore.test.ts +169 -0
- package/src/resources/extensions/gsd/tests/interrupted-session-auto.test.ts +146 -0
- package/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts +136 -0
- package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +85 -0
- package/src/resources/extensions/gsd/tests/mcp-status.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/verification-operational-gate.test.ts +11 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +178 -17
- package/src/resources/extensions/gsd/workflow-mcp.ts +76 -23
- package/dist/web/standalone/.next/static/chunks/6502.b804e48b7919f55e.js +0 -9
- package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.d.ts +0 -13
- package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.d.ts.map +0 -1
- package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.js +0 -27
- package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.js.map +0 -1
- package/packages/pi-coding-agent/src/modes/interactive/provider-auth-setup.ts +0 -40
- package/src/resources/extensions/gsd/tests/init-bootstrap-completeness.test.ts +0 -121
- /package/dist/web/standalone/.next/static/{6_QPFhgX0DQnDhhquheRc → yh2vT27L1E6PChb_C1N_F}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{6_QPFhgX0DQnDhhquheRc → 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
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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
|
-
|
|
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,
|
|
@@ -63,6 +63,8 @@ export class AutoSession {
|
|
|
63
63
|
pendingVerificationRetry = null;
|
|
64
64
|
verificationRetryCount = new Map();
|
|
65
65
|
pausedSessionFile = null;
|
|
66
|
+
pausedUnitType = null;
|
|
67
|
+
pausedUnitId = null;
|
|
66
68
|
resourceVersionOnStart = null;
|
|
67
69
|
lastStateRebuildAt = 0;
|
|
68
70
|
// ── Sidecar queue ─────────────────────────────────────────────────────
|
|
@@ -159,6 +161,8 @@ export class AutoSession {
|
|
|
159
161
|
this.pendingVerificationRetry = null;
|
|
160
162
|
this.verificationRetryCount.clear();
|
|
161
163
|
this.pausedSessionFile = null;
|
|
164
|
+
this.pausedUnitType = null;
|
|
165
|
+
this.pausedUnitId = null;
|
|
162
166
|
this.resourceVersionOnStart = null;
|
|
163
167
|
this.lastStateRebuildAt = 0;
|
|
164
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._-]*
|
|
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 {
|
|
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
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
-
|
|
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
|
|
1213
|
-
|
|
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
|
-
|
|
1731
|
-
|
|
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-
|
|
1734
|
-
|
|
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 {
|