gsd-pi 2.82.0-dev.3709f22a5 → 2.82.0-dev.57fd453e4
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 +2 -2
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +1 -1
- package/dist/resources/extensions/gsd/auto/loop.js +14 -1
- package/dist/resources/extensions/gsd/auto/phases.js +53 -29
- package/dist/resources/extensions/gsd/auto/session.js +4 -0
- package/dist/resources/extensions/gsd/auto/workflow-kernel.js +3 -0
- package/dist/resources/extensions/gsd/auto-direct-dispatch.js +1 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +12 -18
- package/dist/resources/extensions/gsd/auto-post-unit.js +13 -6
- package/dist/resources/extensions/gsd/auto-recovery.js +40 -13
- package/dist/resources/extensions/gsd/auto-start.js +3 -3
- package/dist/resources/extensions/gsd/auto-verification.js +17 -4
- package/dist/resources/extensions/gsd/auto-worktree.js +65 -9
- package/dist/resources/extensions/gsd/auto.js +14 -8
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +6 -1
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +1 -1
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +1 -1
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +7 -2
- package/dist/resources/extensions/gsd/crash-recovery.js +16 -4
- package/dist/resources/extensions/gsd/db/milestone-leases.js +24 -0
- package/dist/resources/extensions/gsd/forensics.js +3 -3
- package/dist/resources/extensions/gsd/git-service.js +6 -2
- package/dist/resources/extensions/gsd/gsd-db.js +20 -6
- package/dist/resources/extensions/gsd/guided-flow-queue.js +4 -3
- package/dist/resources/extensions/gsd/guided-flow.js +8 -5
- package/dist/resources/extensions/gsd/markdown-renderer.js +10 -8
- package/dist/resources/extensions/gsd/paths.js +4 -0
- package/dist/resources/extensions/gsd/queue-reorder-ui.js +30 -13
- package/dist/resources/extensions/gsd/state.js +1 -1
- package/dist/resources/extensions/gsd/status-guards.js +7 -0
- package/dist/resources/extensions/gsd/templates/plan.md +1 -0
- package/dist/resources/extensions/gsd/templates/task-plan.md +6 -0
- package/dist/resources/extensions/gsd/tools/plan-slice.js +3 -5
- package/dist/resources/extensions/gsd/workflow-mcp.js +17 -1
- package/dist/resources/extensions/gsd/worktree-manager.js +1 -1
- package/dist/resources/extensions/ttsr/ttsr-manager.js +3 -1
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +14 -14
- 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 +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +14 -14
- 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/8359.65b24fac92188a6b.js +10 -0
- package/dist/web/standalone/.next/static/chunks/9441.ff70bb53f6835771.js +1 -0
- package/dist/web/standalone/.next/static/chunks/{webpack-9a4db269f9ed63ad.js → webpack-855d616060cb6e59.js} +1 -1
- package/package.json +1 -1
- package/packages/pi-ai/dist/providers/google-gemini-cli.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/google-gemini-cli.js +5 -0
- package/packages/pi-ai/dist/providers/google-gemini-cli.js.map +1 -1
- package/packages/pi-ai/dist/providers/google-gemini-cli.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/google-gemini-cli.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/google-gemini-cli.test.js +41 -0
- package/packages/pi-ai/dist/providers/google-gemini-cli.test.js.map +1 -0
- package/packages/pi-ai/src/providers/google-gemini-cli.test.ts +49 -0
- package/packages/pi-ai/src/providers/google-gemini-cli.ts +7 -0
- package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +44 -3
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.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 +71 -97
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-ordering.test.js +12 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-ordering.test.js.map +1 -1
- 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 +19 -8
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +53 -3
- package/packages/pi-coding-agent/src/core/sdk.ts +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +75 -102
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-ordering.test.ts +14 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +23 -8
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-tui/dist/__tests__/terminal.test.d.ts +2 -0
- package/packages/pi-tui/dist/__tests__/terminal.test.d.ts.map +1 -0
- package/packages/pi-tui/dist/__tests__/terminal.test.js +103 -0
- package/packages/pi-tui/dist/__tests__/terminal.test.js.map +1 -0
- package/packages/pi-tui/dist/terminal.d.ts +2 -0
- package/packages/pi-tui/dist/terminal.d.ts.map +1 -1
- package/packages/pi-tui/dist/terminal.js +12 -0
- package/packages/pi-tui/dist/terminal.js.map +1 -1
- package/packages/pi-tui/src/__tests__/terminal.test.ts +121 -0
- package/packages/pi-tui/src/terminal.ts +11 -0
- package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +1 -1
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +9 -0
- package/src/resources/extensions/gsd/auto/loop.ts +14 -1
- package/src/resources/extensions/gsd/auto/phases.ts +60 -36
- package/src/resources/extensions/gsd/auto/session.ts +4 -0
- package/src/resources/extensions/gsd/auto/workflow-kernel.ts +5 -1
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +1 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +12 -18
- package/src/resources/extensions/gsd/auto-post-unit.ts +14 -6
- package/src/resources/extensions/gsd/auto-recovery.ts +45 -11
- package/src/resources/extensions/gsd/auto-start.ts +2 -3
- package/src/resources/extensions/gsd/auto-verification.ts +22 -2
- package/src/resources/extensions/gsd/auto-worktree.ts +74 -9
- package/src/resources/extensions/gsd/auto.ts +13 -8
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +9 -1
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +1 -1
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +1 -1
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +8 -3
- package/src/resources/extensions/gsd/crash-recovery.ts +16 -2
- package/src/resources/extensions/gsd/db/milestone-leases.ts +26 -0
- package/src/resources/extensions/gsd/forensics.ts +3 -3
- package/src/resources/extensions/gsd/git-service.ts +6 -3
- package/src/resources/extensions/gsd/gsd-db.ts +18 -6
- package/src/resources/extensions/gsd/guided-flow-queue.ts +4 -3
- package/src/resources/extensions/gsd/guided-flow.ts +8 -5
- package/src/resources/extensions/gsd/markdown-renderer.ts +10 -8
- package/src/resources/extensions/gsd/paths.ts +5 -0
- package/src/resources/extensions/gsd/queue-reorder-ui.ts +31 -13
- package/src/resources/extensions/gsd/state.ts +1 -1
- package/src/resources/extensions/gsd/status-guards.ts +8 -0
- package/src/resources/extensions/gsd/templates/plan.md +1 -0
- package/src/resources/extensions/gsd/templates/task-plan.md +6 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +139 -1
- package/src/resources/extensions/gsd/tests/auto-post-unit-step-message.test.ts +6 -5
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +76 -5
- package/src/resources/extensions/gsd/tests/checkout-branch-stash-guard.test.ts +87 -0
- package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +43 -0
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +11 -0
- package/src/resources/extensions/gsd/tests/gsdroot-worktree-detection.test.ts +5 -2
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +46 -0
- package/src/resources/extensions/gsd/tests/merge-db-cycle.test.ts +179 -0
- package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/plan-slice.test.ts +26 -1
- package/src/resources/extensions/gsd/tests/post-unit-state-rebuild.test.ts +84 -0
- package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +59 -0
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +18 -1
- package/src/resources/extensions/gsd/tests/quality-gates.test.ts +6 -0
- package/src/resources/extensions/gsd/tests/queue-reorder-ui.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/remediation-completion-guard.test.ts +43 -0
- package/src/resources/extensions/gsd/tests/run-uat-replay-cap.test.ts +2 -3
- package/src/resources/extensions/gsd/tests/status-guards.test.ts +13 -1
- package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +17 -0
- package/src/resources/extensions/gsd/tests/validate-milestone-stuck-guard.test.ts +29 -2
- package/src/resources/extensions/gsd/tests/workflow-kernel.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +18 -0
- package/src/resources/extensions/gsd/tools/plan-slice.ts +3 -4
- package/src/resources/extensions/gsd/workflow-mcp.ts +18 -1
- package/src/resources/extensions/gsd/worktree-manager.ts +1 -1
- package/src/resources/extensions/ttsr/ttsr-manager.ts +5 -1
- package/dist/web/standalone/.next/static/chunks/8359.7eb3bb8f8ecf4c01.js +0 -10
- package/dist/web/standalone/.next/static/chunks/9441.1081da1125d1764f.js +0 -1
- /package/dist/web/standalone/.next/static/{kkGf3_VaPFkiDNV_D7Dtl → ky6ieNHfZXB_oHPklwTJb}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{kkGf3_VaPFkiDNV_D7Dtl → ky6ieNHfZXB_oHPklwTJb}/_ssgManifest.js +0 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Project/App: GSD-2
|
|
2
|
+
// File Purpose: Regression tests for streamed interactive chat ordering.
|
|
1
3
|
import assert from "node:assert/strict";
|
|
2
4
|
import { test } from "node:test";
|
|
3
5
|
|
|
@@ -97,9 +99,7 @@ function createHost() {
|
|
|
97
99
|
return host;
|
|
98
100
|
}
|
|
99
101
|
|
|
100
|
-
|
|
101
|
-
// ToolExecutionComponent uses the global theme singleton.
|
|
102
|
-
// Install a minimal no-op theme implementation for this unit test.
|
|
102
|
+
function installTheme() {
|
|
103
103
|
(globalThis as any)[Symbol.for("@gsd/pi-coding-agent:theme")] = {
|
|
104
104
|
fg: (_key: string, text: string) => text,
|
|
105
105
|
bg: (_key: string, text: string) => text,
|
|
@@ -107,6 +107,12 @@ test("chat-controller renders content blocks in content[] index order (tool-firs
|
|
|
107
107
|
italic: (text: string) => text,
|
|
108
108
|
truncate: (text: string) => text,
|
|
109
109
|
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
test("chat-controller renders content blocks in content[] index order (tool-first stream)", async () => {
|
|
113
|
+
// ToolExecutionComponent uses the global theme singleton.
|
|
114
|
+
// Install a minimal no-op theme implementation for this unit test.
|
|
115
|
+
installTheme();
|
|
110
116
|
|
|
111
117
|
const host = createHost();
|
|
112
118
|
const toolId = "mcp-tool-1";
|
|
@@ -168,6 +174,50 @@ test("chat-controller renders content blocks in content[] index order (tool-firs
|
|
|
168
174
|
assert.equal(host.chatContainer.children[1]?.constructor?.name, "AssistantMessageComponent");
|
|
169
175
|
});
|
|
170
176
|
|
|
177
|
+
test("chat-controller skips empty GPT reasoning blocks before tool-only turns", async () => {
|
|
178
|
+
installTheme();
|
|
179
|
+
|
|
180
|
+
const host = createHost();
|
|
181
|
+
host.getMarkdownThemeWithSettings = () => ({});
|
|
182
|
+
const toolId = "gpt-tool-1";
|
|
183
|
+
const toolCall = {
|
|
184
|
+
type: "toolCall",
|
|
185
|
+
id: toolId,
|
|
186
|
+
name: "read",
|
|
187
|
+
arguments: { filePath: "todo.js" },
|
|
188
|
+
};
|
|
189
|
+
const content = [
|
|
190
|
+
{ type: "thinking", thinking: "", thinkingSignature: "encrypted" },
|
|
191
|
+
toolCall,
|
|
192
|
+
];
|
|
193
|
+
|
|
194
|
+
await handleAgentEvent(host, { type: "message_start", message: makeAssistant([]) } as any);
|
|
195
|
+
|
|
196
|
+
await handleAgentEvent(
|
|
197
|
+
host,
|
|
198
|
+
{
|
|
199
|
+
type: "message_update",
|
|
200
|
+
message: makeAssistant(content),
|
|
201
|
+
assistantMessageEvent: {
|
|
202
|
+
type: "toolcall_end",
|
|
203
|
+
contentIndex: 1,
|
|
204
|
+
toolCall: {
|
|
205
|
+
...toolCall,
|
|
206
|
+
externalResult: {
|
|
207
|
+
content: [{ type: "text", text: "todo contents" }],
|
|
208
|
+
details: {},
|
|
209
|
+
isError: false,
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
partial: makeAssistant(content),
|
|
213
|
+
},
|
|
214
|
+
} as any,
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
assert.equal(host.chatContainer.children.length, 1, "empty reasoning should not create a blank assistant block");
|
|
218
|
+
assert.equal(host.chatContainer.children[0]?.constructor?.name, "ToolExecutionComponent");
|
|
219
|
+
});
|
|
220
|
+
|
|
171
221
|
test("chat-controller renders serverToolUse before trailing text matching content[] index order", async () => {
|
|
172
222
|
(globalThis as any)[Symbol.for("@gsd/pi-coding-agent:theme")] = {
|
|
173
223
|
fg: (_key: string, text: string) => text,
|
|
@@ -573,7 +573,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
573
573
|
const removed = modelRegistry.authStorage.removeLegacyOAuthCredential(resolvedProvider);
|
|
574
574
|
if (removed) {
|
|
575
575
|
console.warn(
|
|
576
|
-
`[auth] Removed unsupported Anthropic OAuth credential from auth.json
|
|
576
|
+
`[auth] Removed unsupported Anthropic OAuth credential from auth.json.`,
|
|
577
577
|
);
|
|
578
578
|
}
|
|
579
579
|
if (isClaudeCodeBinaryInPath()) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
// GSD-2
|
|
1
|
+
// Project/App: GSD-2
|
|
2
|
+
// File Purpose: Interactive TUI chat stream controller.
|
|
2
3
|
import { Loader, Markdown, Spacer, Text } from "@gsd/pi-tui";
|
|
3
4
|
|
|
4
5
|
import type { InteractiveModeEvent, InteractiveModeStateHost } from "../interactive-mode-state.js";
|
|
@@ -31,18 +32,77 @@ type RenderedSegment =
|
|
|
31
32
|
| { kind: "tool"; contentIndex: number; component: ToolExecutionComponent }
|
|
32
33
|
| { kind: "tool-summary"; component: ToolPhaseSummaryComponent; phases: ToolExecutionPhase[] };
|
|
33
34
|
|
|
35
|
+
type DesiredSegment =
|
|
36
|
+
| { kind: "text-run"; startIndex: number; endIndex: number; contentType: "text" | "thinking" }
|
|
37
|
+
| { kind: "tool"; contentIndex: number; toolId: string };
|
|
38
|
+
|
|
34
39
|
let renderedSegments: RenderedSegment[] = [];
|
|
35
40
|
// When providers reuse one assistant lifecycle across internal sub-turns,
|
|
36
41
|
// a content[] shrink resets renderedSegments. Keep the displaced segments so
|
|
37
42
|
// claude-code MCP pruning can remove stale provisional text later.
|
|
38
43
|
let orphanedSegments: RenderedSegment[] = [];
|
|
39
44
|
|
|
45
|
+
function getVisibleTextLikeBlockType(block: any): "text" | "thinking" | undefined {
|
|
46
|
+
if (block?.type === "text" && typeof block.text === "string" && block.text.trim().length > 0) return "text";
|
|
47
|
+
if (block?.type === "thinking" && typeof block.thinking === "string" && block.thinking.trim().length > 0) return "thinking";
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function buildDesiredSegments(
|
|
52
|
+
blocks: Array<any>,
|
|
53
|
+
options: { shouldSkipTextBlock?: (block: any, index: number) => boolean } = {},
|
|
54
|
+
): DesiredSegment[] {
|
|
55
|
+
const desired: DesiredSegment[] = [];
|
|
56
|
+
let runStart = -1;
|
|
57
|
+
let runEnd = -1;
|
|
58
|
+
let runType: "text" | "thinking" | undefined;
|
|
59
|
+
const closeRun = () => {
|
|
60
|
+
if (runStart !== -1 && runType) {
|
|
61
|
+
desired.push({ kind: "text-run", startIndex: runStart, endIndex: runEnd, contentType: runType });
|
|
62
|
+
runStart = -1;
|
|
63
|
+
runEnd = -1;
|
|
64
|
+
runType = undefined;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
69
|
+
const block = blocks[i];
|
|
70
|
+
const blockType = getVisibleTextLikeBlockType(block);
|
|
71
|
+
const isInvisibleTextLike = blockType === undefined && (block?.type === "text" || block?.type === "thinking");
|
|
72
|
+
const isTool = block?.type === "toolCall" || block?.type === "serverToolUse";
|
|
73
|
+
|
|
74
|
+
if (blockType) {
|
|
75
|
+
if (options.shouldSkipTextBlock?.(block, i)) {
|
|
76
|
+
closeRun();
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (runStart === -1) {
|
|
80
|
+
runStart = i;
|
|
81
|
+
runEnd = i;
|
|
82
|
+
runType = blockType;
|
|
83
|
+
} else if (runType !== blockType) {
|
|
84
|
+
closeRun();
|
|
85
|
+
runStart = i;
|
|
86
|
+
runEnd = i;
|
|
87
|
+
runType = blockType;
|
|
88
|
+
} else {
|
|
89
|
+
runEnd = i;
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
if (isInvisibleTextLike) continue;
|
|
93
|
+
closeRun();
|
|
94
|
+
if (isTool) {
|
|
95
|
+
desired.push({ kind: "tool", contentIndex: i, toolId: block.id });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
closeRun();
|
|
100
|
+
|
|
101
|
+
return desired;
|
|
102
|
+
}
|
|
103
|
+
|
|
40
104
|
function hasVisibleAssistantContent(message: { content: Array<any> }): boolean {
|
|
41
|
-
return message.content.some(
|
|
42
|
-
(c) =>
|
|
43
|
-
(c.type === "text" && typeof c.text === "string" && c.text.trim().length > 0)
|
|
44
|
-
|| (c.type === "thinking" && typeof c.thinking === "string" && c.thinking.trim().length > 0),
|
|
45
|
-
);
|
|
105
|
+
return message.content.some((c) => getVisibleTextLikeBlockType(c) !== undefined);
|
|
46
106
|
}
|
|
47
107
|
|
|
48
108
|
function hasAssistantToolBlocks(message: { content: Array<any> }): boolean {
|
|
@@ -470,59 +530,14 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
|
|
|
470
530
|
// Only prune provisional pre-tool prose after post-tool prose exists,
|
|
471
531
|
// so MCP tool-only windows do not blank the assistant content.
|
|
472
532
|
const shouldDropPreToolProse = isClaudeCodeProvider && hasMcpToolBlock && hasPostToolText;
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
if (runStart !== -1 && runType) {
|
|
482
|
-
desired.push({ kind: "text-run", startIndex: runStart, endIndex: runEnd, contentType: runType });
|
|
483
|
-
runStart = -1;
|
|
484
|
-
runEnd = -1;
|
|
485
|
-
runType = undefined;
|
|
486
|
-
}
|
|
487
|
-
};
|
|
488
|
-
for (let i = 0; i < blocks.length; i++) {
|
|
489
|
-
const b = blocks[i];
|
|
490
|
-
const blockType = b.type === "text" || b.type === "thinking" ? b.type : undefined;
|
|
491
|
-
const isTextLike = blockType === "text" || blockType === "thinking";
|
|
492
|
-
const isTool = b.type === "toolCall" || b.type === "serverToolUse";
|
|
493
|
-
// For Claude Code MCP turns, prune only pre-tool prose, never thinking.
|
|
494
|
-
const textValue = blockType === "text" && typeof b?.text === "string" ? b.text : "";
|
|
495
|
-
const isLikelyQuestion = blockType === "text" && typeof textValue === "string" && /\?\s*$/.test(textValue.trim());
|
|
496
|
-
const shouldSkipProse = shouldDropPreToolProse
|
|
497
|
-
&& firstToolIdx >= 0
|
|
498
|
-
&& i < firstToolIdx
|
|
499
|
-
&& blockType === "text"
|
|
500
|
-
&& !isLikelyQuestion;
|
|
501
|
-
if (shouldSkipProse) {
|
|
502
|
-
closeRun();
|
|
503
|
-
continue;
|
|
504
|
-
}
|
|
505
|
-
if (isTextLike) {
|
|
506
|
-
if (runStart === -1) {
|
|
507
|
-
runStart = i;
|
|
508
|
-
runEnd = i;
|
|
509
|
-
runType = blockType;
|
|
510
|
-
} else if (runType !== blockType) {
|
|
511
|
-
closeRun();
|
|
512
|
-
runStart = i;
|
|
513
|
-
runEnd = i;
|
|
514
|
-
runType = blockType;
|
|
515
|
-
} else {
|
|
516
|
-
runEnd = i;
|
|
517
|
-
}
|
|
518
|
-
} else {
|
|
519
|
-
closeRun();
|
|
520
|
-
if (isTool) {
|
|
521
|
-
desired.push({ kind: "tool", contentIndex: i, toolId: b.id });
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
closeRun();
|
|
533
|
+
const desired = buildDesiredSegments(blocks, {
|
|
534
|
+
shouldSkipTextBlock: (block: any, index: number) => {
|
|
535
|
+
if (!shouldDropPreToolProse || firstToolIdx < 0 || index >= firstToolIdx) return false;
|
|
536
|
+
if (getVisibleTextLikeBlockType(block) !== "text") return false;
|
|
537
|
+
const textValue = typeof block?.text === "string" ? block.text : "";
|
|
538
|
+
return !/\?\s*$/.test(textValue.trim());
|
|
539
|
+
},
|
|
540
|
+
});
|
|
526
541
|
|
|
527
542
|
// Claude Code MCP can emit provisional pre-tool prose that gets
|
|
528
543
|
// superseded by post-tool output. Prune stale text-run segments so
|
|
@@ -742,49 +757,7 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
|
|
|
742
757
|
// ranges/components don't keep stale partial indices.
|
|
743
758
|
if (renderedSegments.length > 0) {
|
|
744
759
|
const finalBlocks = host.streamingMessage.content;
|
|
745
|
-
|
|
746
|
-
| { kind: "text-run"; startIndex: number; endIndex: number; contentType: "text" | "thinking" }
|
|
747
|
-
| { kind: "tool"; contentIndex: number; toolId: string };
|
|
748
|
-
const desired: DesiredSegment[] = [];
|
|
749
|
-
let runStart = -1;
|
|
750
|
-
let runEnd = -1;
|
|
751
|
-
let runType: "text" | "thinking" | undefined;
|
|
752
|
-
const closeRun = () => {
|
|
753
|
-
if (runStart !== -1 && runType) {
|
|
754
|
-
desired.push({ kind: "text-run", startIndex: runStart, endIndex: runEnd, contentType: runType });
|
|
755
|
-
runStart = -1;
|
|
756
|
-
runEnd = -1;
|
|
757
|
-
runType = undefined;
|
|
758
|
-
}
|
|
759
|
-
};
|
|
760
|
-
|
|
761
|
-
for (let i = 0; i < finalBlocks.length; i++) {
|
|
762
|
-
const block = finalBlocks[i] as any;
|
|
763
|
-
const blockType = block?.type === "text" || block?.type === "thinking" ? block.type : undefined;
|
|
764
|
-
const isTextLike = blockType === "text" || blockType === "thinking";
|
|
765
|
-
const isTool = block?.type === "toolCall" || block?.type === "serverToolUse";
|
|
766
|
-
|
|
767
|
-
if (isTextLike) {
|
|
768
|
-
if (runStart === -1) {
|
|
769
|
-
runStart = i;
|
|
770
|
-
runEnd = i;
|
|
771
|
-
runType = blockType;
|
|
772
|
-
} else if (runType !== blockType) {
|
|
773
|
-
closeRun();
|
|
774
|
-
runStart = i;
|
|
775
|
-
runEnd = i;
|
|
776
|
-
runType = blockType;
|
|
777
|
-
} else {
|
|
778
|
-
runEnd = i;
|
|
779
|
-
}
|
|
780
|
-
} else {
|
|
781
|
-
closeRun();
|
|
782
|
-
if (isTool) {
|
|
783
|
-
desired.push({ kind: "tool", contentIndex: i, toolId: block.id });
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
closeRun();
|
|
760
|
+
const desired = buildDesiredSegments(finalBlocks);
|
|
788
761
|
|
|
789
762
|
const toolComponentsById = new Map<string, ToolExecutionComponent>();
|
|
790
763
|
for (const [toolId, component] of host.pendingTools.entries()) {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Project/App: GSD-2
|
|
2
|
+
// File Purpose: Regression tests for interactive assistant replay ordering.
|
|
1
3
|
import assert from "node:assert/strict";
|
|
2
4
|
import { test } from "node:test";
|
|
3
5
|
|
|
@@ -42,3 +44,15 @@ test("buildAssistantReplaySegments ignores non-rendered non-tool blocks", () =>
|
|
|
42
44
|
{ kind: "assistant", startIndex: 2, endIndex: 2 },
|
|
43
45
|
]);
|
|
44
46
|
});
|
|
47
|
+
|
|
48
|
+
test("buildAssistantReplaySegments skips empty GPT reasoning blocks before tools", () => {
|
|
49
|
+
const segments = buildAssistantReplaySegments([
|
|
50
|
+
{ type: "thinking", thinking: "", thinkingSignature: "encrypted" },
|
|
51
|
+
{ type: "text", text: " " },
|
|
52
|
+
{ type: "toolCall", id: "t1", name: "read", arguments: { filePath: "todo.js" } },
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
assert.deepEqual(segments, [
|
|
56
|
+
{ kind: "tool", contentIndex: 2 },
|
|
57
|
+
]);
|
|
58
|
+
});
|
|
@@ -134,6 +134,13 @@ export type AssistantReplaySegment =
|
|
|
134
134
|
| { kind: "assistant"; startIndex: number; endIndex: number }
|
|
135
135
|
| { kind: "tool"; contentIndex: number };
|
|
136
136
|
|
|
137
|
+
function isVisibleAssistantReplayText(block: any): boolean {
|
|
138
|
+
return (
|
|
139
|
+
(block?.type === "text" && typeof block.text === "string" && block.text.trim().length > 0)
|
|
140
|
+
|| (block?.type === "thinking" && typeof block.thinking === "string" && block.thinking.trim().length > 0)
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
137
144
|
/**
|
|
138
145
|
* Build replay segments for historical assistant messages so rebuild paths
|
|
139
146
|
* preserve the original content[] ordering between assistant prose and tools.
|
|
@@ -141,30 +148,38 @@ export type AssistantReplaySegment =
|
|
|
141
148
|
export function buildAssistantReplaySegments(contentBlocks: Array<any>): AssistantReplaySegment[] {
|
|
142
149
|
const segments: AssistantReplaySegment[] = [];
|
|
143
150
|
let runStart = -1;
|
|
151
|
+
let runEnd = -1;
|
|
152
|
+
|
|
153
|
+
const closeRun = () => {
|
|
154
|
+
if (runStart !== -1) {
|
|
155
|
+
segments.push({ kind: "assistant", startIndex: runStart, endIndex: runEnd });
|
|
156
|
+
runStart = -1;
|
|
157
|
+
runEnd = -1;
|
|
158
|
+
}
|
|
159
|
+
};
|
|
144
160
|
|
|
145
161
|
for (let i = 0; i < contentBlocks.length; i++) {
|
|
146
162
|
const block = contentBlocks[i];
|
|
147
|
-
const isAssistantText = block
|
|
163
|
+
const isAssistantText = isVisibleAssistantReplayText(block);
|
|
164
|
+
const isInvisibleAssistantText = block?.type === "text" || block?.type === "thinking";
|
|
148
165
|
const isTool = block?.type === "toolCall" || block?.type === "serverToolUse";
|
|
149
166
|
|
|
150
167
|
if (isAssistantText) {
|
|
151
168
|
if (runStart === -1) runStart = i;
|
|
169
|
+
runEnd = i;
|
|
152
170
|
continue;
|
|
153
171
|
}
|
|
154
172
|
|
|
155
|
-
if (
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
173
|
+
if (isInvisibleAssistantText) continue;
|
|
174
|
+
|
|
175
|
+
closeRun();
|
|
159
176
|
|
|
160
177
|
if (isTool) {
|
|
161
178
|
segments.push({ kind: "tool", contentIndex: i });
|
|
162
179
|
}
|
|
163
180
|
}
|
|
164
181
|
|
|
165
|
-
|
|
166
|
-
segments.push({ kind: "assistant", startIndex: runStart, endIndex: contentBlocks.length - 1 });
|
|
167
|
-
}
|
|
182
|
+
closeRun();
|
|
168
183
|
|
|
169
184
|
return segments;
|
|
170
185
|
}
|