gsd-pi 2.74.0-dev.6e23363 → 2.74.0-dev.703eabc
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/dist/resources/extensions/gsd/auto-post-unit.js +7 -3
- package/dist/resources/extensions/gsd/bootstrap/register-extension.js +10 -1
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +45 -4
- package/dist/resources/extensions/gsd/commands/handlers/ops.js +5 -0
- package/dist/resources/extensions/gsd/commands-extract-learnings.js +225 -0
- package/dist/resources/extensions/gsd/ecosystem/gsd-extension-api.js +144 -0
- package/dist/resources/extensions/gsd/ecosystem/loader.js +145 -0
- 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 +16 -16
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-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/onboarding/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/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 +16 -16
- package/dist/web/standalone/.next/server/chunks/6897.js +3 -3
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
- 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/package.json +1 -1
- package/packages/mcp-server/dist/readers/graph.d.ts +1 -1
- package/packages/mcp-server/dist/readers/graph.d.ts.map +1 -1
- package/packages/mcp-server/dist/readers/graph.js +107 -0
- package/packages/mcp-server/dist/readers/graph.js.map +1 -1
- package/packages/mcp-server/src/readers/graph.test.ts +178 -0
- package/packages/mcp-server/src/readers/graph.ts +148 -1
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-ai/dist/index.d.ts +1 -9
- package/packages/pi-ai/dist/index.d.ts.map +1 -1
- package/packages/pi-ai/dist/index.js +1 -9
- package/packages/pi-ai/dist/index.js.map +1 -1
- package/packages/pi-ai/dist/models/capability-patches.d.ts +19 -0
- package/packages/pi-ai/dist/models/capability-patches.d.ts.map +1 -0
- package/packages/pi-ai/dist/models/capability-patches.js +36 -0
- package/packages/pi-ai/dist/models/capability-patches.js.map +1 -0
- package/packages/pi-ai/dist/{models.custom.d.ts → models/custom.d.ts} +1 -1
- package/packages/pi-ai/dist/models/custom.d.ts.map +1 -0
- package/packages/pi-ai/dist/{models.custom.js → models/custom.js} +4 -4
- package/packages/pi-ai/dist/models/custom.js.map +1 -0
- package/packages/pi-ai/dist/models/generated/amazon-bedrock.d.ts +1482 -0
- package/packages/pi-ai/dist/models/generated/amazon-bedrock.d.ts.map +1 -0
- package/packages/pi-ai/dist/models/generated/amazon-bedrock.js +1484 -0
- package/packages/pi-ai/dist/models/generated/amazon-bedrock.js.map +1 -0
- package/packages/pi-ai/dist/models/generated/anthropic.d.ts +377 -0
- package/packages/pi-ai/dist/models/generated/anthropic.d.ts.map +1 -0
- package/packages/pi-ai/dist/models/generated/anthropic.js +379 -0
- package/packages/pi-ai/dist/models/generated/anthropic.js.map +1 -0
- package/packages/pi-ai/dist/models/generated/azure-openai-responses.d.ts +700 -0
- package/packages/pi-ai/dist/models/generated/azure-openai-responses.d.ts.map +1 -0
- package/packages/pi-ai/dist/models/generated/azure-openai-responses.js +702 -0
- package/packages/pi-ai/dist/models/generated/azure-openai-responses.js.map +1 -0
- package/packages/pi-ai/dist/models/generated/cerebras.d.ts +71 -0
- package/packages/pi-ai/dist/models/generated/cerebras.d.ts.map +1 -0
- package/packages/pi-ai/dist/models/generated/cerebras.js +73 -0
- package/packages/pi-ai/dist/models/generated/cerebras.js.map +1 -0
- package/packages/pi-ai/dist/models/generated/github-copilot.d.ts +590 -0
- package/packages/pi-ai/dist/models/generated/github-copilot.d.ts.map +1 -0
- package/packages/pi-ai/dist/models/generated/github-copilot.js +444 -0
- package/packages/pi-ai/dist/models/generated/github-copilot.js.map +1 -0
- package/packages/pi-ai/dist/models/generated/google-antigravity.d.ts +156 -0
- package/packages/pi-ai/dist/models/generated/google-antigravity.d.ts.map +1 -0
- package/packages/pi-ai/dist/models/generated/google-antigravity.js +158 -0
- package/packages/pi-ai/dist/models/generated/google-antigravity.js.map +1 -0
- package/packages/pi-ai/dist/models/generated/google-gemini-cli.d.ts +105 -0
- package/packages/pi-ai/dist/models/generated/google-gemini-cli.d.ts.map +1 -0
- package/packages/pi-ai/dist/models/generated/google-gemini-cli.js +107 -0
- package/packages/pi-ai/dist/models/generated/google-gemini-cli.js.map +1 -0
- package/packages/pi-ai/dist/models/generated/google-vertex.d.ts +207 -0
- package/packages/pi-ai/dist/models/generated/google-vertex.d.ts.map +1 -0
- package/packages/pi-ai/dist/models/generated/google-vertex.js +209 -0
- package/packages/pi-ai/dist/models/generated/google-vertex.js.map +1 -0
- package/packages/pi-ai/dist/models/generated/google.d.ts +462 -0
- package/packages/pi-ai/dist/models/generated/google.d.ts.map +1 -0
- package/packages/pi-ai/dist/models/generated/google.js +464 -0
- package/packages/pi-ai/dist/models/generated/google.js.map +1 -0
- package/packages/pi-ai/dist/models/generated/groq.d.ts +309 -0
- package/packages/pi-ai/dist/models/generated/groq.d.ts.map +1 -0
- package/packages/pi-ai/dist/models/generated/groq.js +311 -0
- package/packages/pi-ai/dist/models/generated/groq.js.map +1 -0
- package/packages/pi-ai/dist/models/generated/huggingface.d.ts +383 -0
- package/packages/pi-ai/dist/models/generated/huggingface.d.ts.map +1 -0
- package/packages/pi-ai/dist/models/generated/huggingface.js +347 -0
- package/packages/pi-ai/dist/models/generated/huggingface.js.map +1 -0
- package/packages/pi-ai/dist/{models.generated.d.ts → models/generated/index.d.ts} +1 -1
- package/packages/pi-ai/dist/{models.generated.d.ts.map → models/generated/index.d.ts.map} +1 -1
- package/packages/pi-ai/dist/models/generated/index.js +51 -0
- package/packages/pi-ai/dist/models/generated/index.js.map +1 -0
- package/packages/pi-ai/dist/models/generated/kimi-coding.d.ts +37 -0
- package/packages/pi-ai/dist/models/generated/kimi-coding.d.ts.map +1 -0
- package/packages/pi-ai/dist/models/generated/kimi-coding.js +39 -0
- package/packages/pi-ai/dist/models/generated/kimi-coding.js.map +1 -0
- package/packages/pi-ai/dist/models/generated/minimax-cn.d.ts +105 -0
- package/packages/pi-ai/dist/models/generated/minimax-cn.d.ts.map +1 -0
- package/packages/pi-ai/dist/models/generated/minimax-cn.js +107 -0
- package/packages/pi-ai/dist/models/generated/minimax-cn.js.map +1 -0
- package/packages/pi-ai/dist/models/generated/minimax.d.ts +105 -0
- package/packages/pi-ai/dist/models/generated/minimax.d.ts.map +1 -0
- package/packages/pi-ai/dist/models/generated/minimax.js +107 -0
- package/packages/pi-ai/dist/models/generated/minimax.js.map +1 -0
- package/packages/pi-ai/dist/models/generated/mistral.d.ts +445 -0
- package/packages/pi-ai/dist/models/generated/mistral.d.ts.map +1 -0
- package/packages/pi-ai/dist/models/generated/mistral.js +447 -0
- package/packages/pi-ai/dist/models/generated/mistral.js.map +1 -0
- package/packages/pi-ai/dist/models/generated/openai-codex.d.ts +139 -0
- package/packages/pi-ai/dist/models/generated/openai-codex.d.ts.map +1 -0
- package/packages/pi-ai/dist/models/generated/openai-codex.js +141 -0
- package/packages/pi-ai/dist/models/generated/openai-codex.js.map +1 -0
- package/packages/pi-ai/dist/models/generated/openai.d.ts +700 -0
- package/packages/pi-ai/dist/models/generated/openai.d.ts.map +1 -0
- package/packages/pi-ai/dist/models/generated/openai.js +702 -0
- package/packages/pi-ai/dist/models/generated/openai.js.map +1 -0
- package/packages/pi-ai/dist/models/generated/opencode-go.d.ts +122 -0
- package/packages/pi-ai/dist/models/generated/opencode-go.d.ts.map +1 -0
- package/packages/pi-ai/dist/models/generated/opencode-go.js +124 -0
- package/packages/pi-ai/dist/models/generated/opencode-go.js.map +1 -0
- package/packages/pi-ai/dist/models/generated/opencode.d.ts +530 -0
- package/packages/pi-ai/dist/models/generated/opencode.d.ts.map +1 -0
- package/packages/pi-ai/dist/models/generated/opencode.js +532 -0
- package/packages/pi-ai/dist/models/generated/opencode.js.map +1 -0
- package/packages/pi-ai/dist/models/generated/openrouter.d.ts +4270 -0
- package/packages/pi-ai/dist/models/generated/openrouter.d.ts.map +1 -0
- package/packages/pi-ai/dist/models/generated/openrouter.js +4272 -0
- package/packages/pi-ai/dist/models/generated/openrouter.js.map +1 -0
- package/packages/pi-ai/dist/models/generated/vercel-ai-gateway.d.ts +2604 -0
- package/packages/pi-ai/dist/models/generated/vercel-ai-gateway.d.ts.map +1 -0
- package/packages/pi-ai/dist/models/generated/vercel-ai-gateway.js +2606 -0
- package/packages/pi-ai/dist/models/generated/vercel-ai-gateway.js.map +1 -0
- package/packages/pi-ai/dist/models/generated/xai.d.ts +411 -0
- package/packages/pi-ai/dist/models/generated/xai.d.ts.map +1 -0
- package/packages/pi-ai/dist/models/generated/xai.js +413 -0
- package/packages/pi-ai/dist/models/generated/xai.js.map +1 -0
- package/packages/pi-ai/dist/models/generated/zai.d.ts +276 -0
- package/packages/pi-ai/dist/models/generated/zai.d.ts.map +1 -0
- package/packages/pi-ai/dist/models/generated/zai.js +239 -0
- package/packages/pi-ai/dist/models/generated/zai.js.map +1 -0
- package/packages/pi-ai/dist/models/index.d.ts +27 -0
- package/packages/pi-ai/dist/models/index.d.ts.map +1 -0
- package/packages/pi-ai/dist/models/index.js +80 -0
- package/packages/pi-ai/dist/models/index.js.map +1 -0
- package/packages/pi-ai/dist/models.d.ts +1 -36
- package/packages/pi-ai/dist/models.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.test.js +1 -2
- package/packages/pi-ai/dist/models.generated.test.js.map +1 -1
- package/packages/pi-ai/dist/models.js +3 -112
- package/packages/pi-ai/dist/models.js.map +1 -1
- package/packages/pi-ai/dist/models.test.js +6 -5
- package/packages/pi-ai/dist/models.test.js.map +1 -1
- package/packages/pi-ai/scripts/generate-models.ts +74 -40
- package/packages/pi-ai/src/index.ts +1 -9
- package/packages/pi-ai/src/models/capability-patches.ts +40 -0
- package/packages/pi-ai/src/{models.custom.ts → models/custom.ts} +4 -4
- package/packages/pi-ai/src/models/generated/amazon-bedrock.ts +1486 -0
- package/packages/pi-ai/src/models/generated/anthropic.ts +381 -0
- package/packages/pi-ai/src/models/generated/azure-openai-responses.ts +704 -0
- package/packages/pi-ai/src/models/generated/cerebras.ts +75 -0
- package/packages/pi-ai/src/models/generated/github-copilot.ts +446 -0
- package/packages/pi-ai/src/models/generated/google-antigravity.ts +160 -0
- package/packages/pi-ai/src/models/generated/google-gemini-cli.ts +109 -0
- package/packages/pi-ai/src/models/generated/google-vertex.ts +211 -0
- package/packages/pi-ai/src/models/generated/google.ts +466 -0
- package/packages/pi-ai/src/models/generated/groq.ts +313 -0
- package/packages/pi-ai/src/models/generated/huggingface.ts +349 -0
- package/packages/pi-ai/src/models/generated/index.ts +52 -0
- package/packages/pi-ai/src/models/generated/kimi-coding.ts +41 -0
- package/packages/pi-ai/src/models/generated/minimax-cn.ts +109 -0
- package/packages/pi-ai/src/models/generated/minimax.ts +109 -0
- package/packages/pi-ai/src/models/generated/mistral.ts +449 -0
- package/packages/pi-ai/src/models/generated/openai-codex.ts +143 -0
- package/packages/pi-ai/src/models/generated/openai.ts +704 -0
- package/packages/pi-ai/src/models/generated/opencode-go.ts +126 -0
- package/packages/pi-ai/src/models/generated/opencode.ts +534 -0
- package/packages/pi-ai/src/models/generated/openrouter.ts +4274 -0
- package/packages/pi-ai/src/models/generated/vercel-ai-gateway.ts +2608 -0
- package/packages/pi-ai/src/models/generated/xai.ts +415 -0
- package/packages/pi-ai/src/models/generated/zai.ts +241 -0
- package/packages/pi-ai/src/models/index.ts +106 -0
- package/packages/pi-ai/src/models.generated.test.ts +1 -2
- package/packages/pi-ai/src/models.test.ts +6 -5
- package/packages/pi-ai/src/models.ts +3 -153
- package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +8 -2
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +359 -7
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +11 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js +23 -9
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts +11 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js +47 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js.map +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 +51 -8
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js +22 -22
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.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 +192 -22
- 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.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-ordering.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-ordering.test.js +38 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-ordering.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +13 -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 +53 -6
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +12 -6
- package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +453 -7
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +19 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +25 -10
- package/packages/pi-coding-agent/src/modes/interactive/components/chat-frame.ts +67 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +66 -7
- package/packages/pi-coding-agent/src/modes/interactive/components/user-message.ts +23 -26
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +253 -45
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-ordering.test.ts +44 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +73 -6
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +7 -3
- package/src/resources/extensions/gsd/bootstrap/register-extension.ts +15 -1
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +56 -3
- package/src/resources/extensions/gsd/commands/handlers/ops.ts +5 -0
- package/src/resources/extensions/gsd/commands-extract-learnings.ts +304 -0
- package/src/resources/extensions/gsd/ecosystem/gsd-extension-api.ts +228 -0
- package/src/resources/extensions/gsd/ecosystem/loader.ts +201 -0
- package/src/resources/extensions/gsd/tests/commands-extract-learnings.test.ts +340 -0
- package/src/resources/extensions/gsd/tests/health-widget.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +1 -1
- package/src/resources/extensions/gsd/types.ts +13 -0
- package/src/resources/extensions/gsd/workflow-logger.ts +2 -1
- package/packages/pi-ai/dist/models.custom.d.ts.map +0 -1
- package/packages/pi-ai/dist/models.custom.js.map +0 -1
- package/packages/pi-ai/dist/models.generated.js +0 -14343
- package/packages/pi-ai/dist/models.generated.js.map +0 -1
- package/packages/pi-ai/src/models.generated.ts +0 -14345
- /package/dist/web/standalone/.next/static/{bc2gRVFTgD7j--BsJE7vP → 3U-oZ5FT59BM7sm2GInic}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{bc2gRVFTgD7j--BsJE7vP → 3U-oZ5FT59BM7sm2GInic}/_ssgManifest.js +0 -0
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
Text,
|
|
10
10
|
type TUI,
|
|
11
11
|
truncateToWidth,
|
|
12
|
+
visibleWidth,
|
|
12
13
|
} from "@gsd/pi-tui";
|
|
13
14
|
import stripAnsi from "strip-ansi";
|
|
14
15
|
import type { ToolDefinition } from "../../../core/extensions/types.js";
|
|
@@ -65,6 +66,53 @@ function parseMcpToolName(name: string): { server: string; tool: string } | null
|
|
|
65
66
|
return { server: rest.slice(0, delim), tool: rest.slice(delim + 2) };
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
type ToolFrameTone = "pending" | "success" | "error";
|
|
70
|
+
|
|
71
|
+
function trimOuterBlankLines(lines: string[]): string[] {
|
|
72
|
+
let start = 0;
|
|
73
|
+
let end = lines.length;
|
|
74
|
+
while (start < end && lines[start].trim().length === 0) start++;
|
|
75
|
+
while (end > start && lines[end - 1].trim().length === 0) end--;
|
|
76
|
+
return lines.slice(start, end);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function renderToolFrame(
|
|
80
|
+
contentLines: string[],
|
|
81
|
+
width: number,
|
|
82
|
+
opts: {
|
|
83
|
+
label: string;
|
|
84
|
+
status: string;
|
|
85
|
+
tone: ToolFrameTone;
|
|
86
|
+
},
|
|
87
|
+
): string[] {
|
|
88
|
+
const outerWidth = Math.max(20, width);
|
|
89
|
+
const contentWidth = Math.max(1, outerWidth - 2); // "│ " + content
|
|
90
|
+
|
|
91
|
+
const borderColor = opts.tone === "error" ? "error" : "toolTitle";
|
|
92
|
+
const topColor = opts.tone === "error" ? "error" : "toolTitle";
|
|
93
|
+
const labelColor = opts.tone === "error" ? "error" : "toolTitle";
|
|
94
|
+
const statusColor = opts.tone === "error" ? "error" : opts.tone === "pending" ? "warning" : "success";
|
|
95
|
+
const border = (s: string) => theme.fg(borderColor, s);
|
|
96
|
+
|
|
97
|
+
const leftStyled = theme.fg(labelColor, theme.bold(`• ${opts.label}`));
|
|
98
|
+
const rightStyled = theme.fg(statusColor, opts.status);
|
|
99
|
+
const gap = Math.max(1, outerWidth - visibleWidth(leftStyled) - visibleWidth(rightStyled));
|
|
100
|
+
const headerRow = `${leftStyled}${" ".repeat(gap)}${rightStyled}`;
|
|
101
|
+
const headerPad = Math.max(0, outerWidth - visibleWidth(headerRow));
|
|
102
|
+
|
|
103
|
+
const sourceLines = trimOuterBlankLines(contentLines);
|
|
104
|
+
const bodyLines = (sourceLines.length > 0 ? sourceLines : [""]).map((line) => {
|
|
105
|
+
const clipped = truncateToWidth(line, contentWidth, "");
|
|
106
|
+
return border("│ ") + clipped;
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
return [
|
|
110
|
+
theme.fg(topColor, "─".repeat(outerWidth)),
|
|
111
|
+
headerRow + " ".repeat(headerPad),
|
|
112
|
+
...bodyLines,
|
|
113
|
+
];
|
|
114
|
+
}
|
|
115
|
+
|
|
68
116
|
const COMPACT_ARG_VALUE_LIMIT = 60;
|
|
69
117
|
const GENERIC_OUTPUT_PREVIEW_LINES = 10;
|
|
70
118
|
const GENERIC_ARGS_JSON_PREVIEW_LINES = 10;
|
|
@@ -452,16 +500,27 @@ export class ToolExecutionComponent extends Container {
|
|
|
452
500
|
if (this.hideComponent) {
|
|
453
501
|
return [];
|
|
454
502
|
}
|
|
455
|
-
|
|
503
|
+
const frameWidth = Math.max(20, width);
|
|
504
|
+
const contentWidth = Math.max(1, frameWidth - 4);
|
|
505
|
+
const lines = super.render(contentWidth);
|
|
506
|
+
const frameTone: ToolFrameTone =
|
|
507
|
+
this.result?.isError ? "error" : this.isPartial || !this.result ? "pending" : "success";
|
|
508
|
+
const frameStatus = this.isPartial || !this.result ? "Running" : this.result.isError ? "Error" : "Done";
|
|
509
|
+
const parsed = parseMcpToolName(this.toolName);
|
|
510
|
+
const frameLabel = parsed
|
|
511
|
+
? `Tool ${parsed.server}·${parsed.tool}`
|
|
512
|
+
: `Tool ${this.normalizedToolName || this.toolName || "unknown"}`;
|
|
513
|
+
const framed = renderToolFrame(lines, frameWidth, {
|
|
514
|
+
label: frameLabel,
|
|
515
|
+
status: frameStatus,
|
|
516
|
+
tone: frameTone,
|
|
517
|
+
});
|
|
518
|
+
return framed.length > 0 ? ["", ...framed] : framed;
|
|
456
519
|
}
|
|
457
520
|
|
|
458
521
|
private updateDisplay(): void {
|
|
459
|
-
//
|
|
460
|
-
const bgFn =
|
|
461
|
-
? (text: string) => theme.bg("toolPendingBg", text)
|
|
462
|
-
: this.result?.isError
|
|
463
|
-
? (text: string) => theme.bg("toolErrorBg", text)
|
|
464
|
-
: (text: string) => theme.bg("toolSuccessBg", text);
|
|
522
|
+
// Tool body now uses transparent background; status is conveyed in the frame header.
|
|
523
|
+
const bgFn = (text: string) => text;
|
|
465
524
|
|
|
466
525
|
const useBuiltInRenderer = this.shouldUseBuiltInRenderer();
|
|
467
526
|
let customRendererHasContent = false;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { Container, Markdown, type MarkdownTheme
|
|
2
|
-
import { getMarkdownTheme
|
|
3
|
-
import {
|
|
1
|
+
import { Container, Markdown, type MarkdownTheme } from "@gsd/pi-tui";
|
|
2
|
+
import { getMarkdownTheme } from "../theme/theme.js";
|
|
3
|
+
import { type TimestampFormat } from "./timestamp.js";
|
|
4
|
+
import { renderChatFrame } from "./chat-frame.js";
|
|
4
5
|
|
|
5
6
|
const OSC133_ZONE_START = "\x1b]133;A\x07";
|
|
6
7
|
const OSC133_ZONE_END = "\x1b]133;B\x07";
|
|
@@ -16,32 +17,28 @@ export class UserMessageComponent extends Container {
|
|
|
16
17
|
super();
|
|
17
18
|
this.timestamp = timestamp;
|
|
18
19
|
this.timestampFormat = timestampFormat;
|
|
19
|
-
this.addChild(new
|
|
20
|
-
this.addChild(
|
|
21
|
-
new Markdown(text, 1, 1, markdownTheme, {
|
|
22
|
-
bgColor: (text: string) => theme.bg("userMessageBg", text),
|
|
23
|
-
color: (text: string) => theme.fg("userMessageText", text),
|
|
24
|
-
}),
|
|
25
|
-
);
|
|
20
|
+
this.addChild(new Markdown(text, 0, 0, markdownTheme));
|
|
26
21
|
}
|
|
27
22
|
|
|
28
23
|
override render(width: number): string[] {
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
24
|
+
const frameWidth = Math.max(20, width);
|
|
25
|
+
const contentWidth = Math.max(1, frameWidth - 4);
|
|
26
|
+
const lines = super.render(contentWidth);
|
|
27
|
+
const framed = renderChatFrame(lines, frameWidth, {
|
|
28
|
+
label: "You",
|
|
29
|
+
tone: "user",
|
|
30
|
+
timestamp: this.timestamp,
|
|
31
|
+
timestampFormat: this.timestampFormat,
|
|
32
|
+
showTimestamp: true,
|
|
33
|
+
});
|
|
34
|
+
if (framed.length === 0) {
|
|
35
|
+
return framed;
|
|
32
36
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const timestampLine = " ".repeat(padding) + label;
|
|
40
|
-
lines.splice(0, 0, timestampLine);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
lines[0] = OSC133_ZONE_START + lines[0];
|
|
44
|
-
lines[lines.length - 1] = lines[lines.length - 1] + OSC133_ZONE_END;
|
|
45
|
-
return lines;
|
|
37
|
+
const out = ["", ...framed];
|
|
38
|
+
const firstFrameLine = 1;
|
|
39
|
+
const lastFrameLine = out.length - 1;
|
|
40
|
+
out[firstFrameLine] = OSC133_ZONE_START + out[firstFrameLine];
|
|
41
|
+
out[lastFrameLine] = out[lastFrameLine] + OSC133_ZONE_END;
|
|
42
|
+
return out;
|
|
46
43
|
}
|
|
47
44
|
}
|
|
@@ -16,10 +16,20 @@ let lastContentLength = 0;
|
|
|
16
16
|
|
|
17
17
|
// --- Segment walker state (per streaming assistant turn) ---
|
|
18
18
|
type RenderedSegment =
|
|
19
|
-
| {
|
|
19
|
+
| {
|
|
20
|
+
kind: "text-run";
|
|
21
|
+
startIndex: number;
|
|
22
|
+
endIndex: number;
|
|
23
|
+
contentType: "text" | "thinking";
|
|
24
|
+
component: AssistantMessageComponent;
|
|
25
|
+
}
|
|
20
26
|
| { kind: "tool"; contentIndex: number; component: ToolExecutionComponent };
|
|
21
27
|
|
|
22
28
|
let renderedSegments: RenderedSegment[] = [];
|
|
29
|
+
// When providers reuse one assistant lifecycle across internal sub-turns,
|
|
30
|
+
// a content[] shrink resets renderedSegments. Keep the displaced segments so
|
|
31
|
+
// claude-code MCP pruning can remove stale provisional text later.
|
|
32
|
+
let orphanedSegments: RenderedSegment[] = [];
|
|
23
33
|
|
|
24
34
|
function hasVisibleAssistantContent(message: { content: Array<any> }): boolean {
|
|
25
35
|
return message.content.some(
|
|
@@ -85,6 +95,7 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
|
|
|
85
95
|
}
|
|
86
96
|
|
|
87
97
|
host.footer.invalidate();
|
|
98
|
+
const timestampFormat = host.settingsManager.getTimestampFormat();
|
|
88
99
|
|
|
89
100
|
// Reset content index tracker and pinned state when a new assistant message starts
|
|
90
101
|
if (event.type === "message_start" && event.message.role === "assistant") {
|
|
@@ -93,6 +104,7 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
|
|
|
93
104
|
lastPinnedText = "";
|
|
94
105
|
hasToolsInTurn = false;
|
|
95
106
|
renderedSegments = [];
|
|
107
|
+
orphanedSegments = [];
|
|
96
108
|
if (pinnedBorder) pinnedBorder.stopSpinner();
|
|
97
109
|
pinnedBorder = undefined;
|
|
98
110
|
pinnedTextComponent = undefined;
|
|
@@ -113,6 +125,7 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
|
|
|
113
125
|
lastPinnedText = "";
|
|
114
126
|
hasToolsInTurn = false;
|
|
115
127
|
renderedSegments = [];
|
|
128
|
+
orphanedSegments = [];
|
|
116
129
|
lastContentLength = 0;
|
|
117
130
|
if (pinnedBorder) pinnedBorder.stopSpinner();
|
|
118
131
|
pinnedBorder = undefined;
|
|
@@ -226,6 +239,10 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
|
|
|
226
239
|
// content (#4144 regression). Prior sub-turn children stay in
|
|
227
240
|
// chatContainer as frozen history; new segments append after them.
|
|
228
241
|
if (contentBlocks.length < lastContentLength) {
|
|
242
|
+
// Accumulate across successive shrinks — overwriting would drop
|
|
243
|
+
// segments displaced by an earlier shrink, leaving them stranded
|
|
244
|
+
// in chatContainer once the prune pass finally runs.
|
|
245
|
+
orphanedSegments = [...orphanedSegments, ...renderedSegments];
|
|
229
246
|
renderedSegments = [];
|
|
230
247
|
lastPinnedText = "";
|
|
231
248
|
lastProcessedContentIndex = 0;
|
|
@@ -312,44 +329,95 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
|
|
|
312
329
|
}
|
|
313
330
|
return false;
|
|
314
331
|
});
|
|
315
|
-
const shouldDropPreToolText = isClaudeCodeProvider && hasMcpToolBlock;
|
|
316
332
|
const firstToolIdx = blocks.findIndex((b: any) => b.type === "toolCall" || b.type === "serverToolUse");
|
|
333
|
+
const hasPostToolText = firstToolIdx >= 0
|
|
334
|
+
&& blocks.some(
|
|
335
|
+
(b: any, idx: number) => (
|
|
336
|
+
idx > firstToolIdx
|
|
337
|
+
&& b?.type === "text"
|
|
338
|
+
&& typeof b?.text === "string"
|
|
339
|
+
&& b.text.trim().length > 0
|
|
340
|
+
),
|
|
341
|
+
);
|
|
342
|
+
// Only prune provisional pre-tool prose after post-tool prose exists,
|
|
343
|
+
// so MCP tool-only windows do not blank the assistant content.
|
|
344
|
+
const shouldDropPreToolProse = isClaudeCodeProvider && hasMcpToolBlock && hasPostToolText;
|
|
317
345
|
type DesiredSegment =
|
|
318
|
-
| { kind: "text-run"; startIndex: number; endIndex: number }
|
|
346
|
+
| { kind: "text-run"; startIndex: number; endIndex: number; contentType: "text" | "thinking" }
|
|
319
347
|
| { kind: "tool"; contentIndex: number; toolId: string };
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
348
|
+
const desired: DesiredSegment[] = [];
|
|
349
|
+
let runStart = -1;
|
|
350
|
+
let runEnd = -1;
|
|
351
|
+
let runType: "text" | "thinking" | undefined;
|
|
352
|
+
const closeRun = () => {
|
|
353
|
+
if (runStart !== -1 && runType) {
|
|
354
|
+
desired.push({ kind: "text-run", startIndex: runStart, endIndex: runEnd, contentType: runType });
|
|
355
|
+
runStart = -1;
|
|
356
|
+
runEnd = -1;
|
|
357
|
+
runType = undefined;
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
361
|
+
const b = blocks[i];
|
|
362
|
+
const blockType = b.type === "text" || b.type === "thinking" ? b.type : undefined;
|
|
363
|
+
const isTextLike = blockType === "text" || blockType === "thinking";
|
|
364
|
+
const isTool = b.type === "toolCall" || b.type === "serverToolUse";
|
|
365
|
+
// For Claude Code MCP turns, prune only pre-tool prose, never thinking.
|
|
366
|
+
const textValue = blockType === "text" && typeof b?.text === "string" ? b.text : "";
|
|
367
|
+
const isLikelyQuestion = blockType === "text" && typeof textValue === "string" && /\?\s*$/.test(textValue.trim());
|
|
368
|
+
const shouldSkipProse = shouldDropPreToolProse
|
|
369
|
+
&& firstToolIdx >= 0
|
|
370
|
+
&& i < firstToolIdx
|
|
371
|
+
&& blockType === "text"
|
|
372
|
+
&& !isLikelyQuestion;
|
|
373
|
+
if (shouldSkipProse) {
|
|
374
|
+
closeRun();
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
if (isTextLike) {
|
|
378
|
+
if (runStart === -1) {
|
|
379
|
+
runStart = i;
|
|
380
|
+
runEnd = i;
|
|
381
|
+
runType = blockType;
|
|
382
|
+
} else if (runType !== blockType) {
|
|
383
|
+
closeRun();
|
|
384
|
+
runStart = i;
|
|
385
|
+
runEnd = i;
|
|
386
|
+
runType = blockType;
|
|
387
|
+
} else {
|
|
388
|
+
runEnd = i;
|
|
329
389
|
}
|
|
330
|
-
if (runStart === -1) runStart = i;
|
|
331
390
|
} else {
|
|
332
|
-
|
|
333
|
-
desired.push({ kind: "text-run", startIndex: runStart, endIndex: i - 1 });
|
|
334
|
-
runStart = -1;
|
|
335
|
-
}
|
|
391
|
+
closeRun();
|
|
336
392
|
if (isTool) {
|
|
337
393
|
desired.push({ kind: "tool", contentIndex: i, toolId: b.id });
|
|
338
394
|
}
|
|
339
395
|
}
|
|
340
396
|
}
|
|
341
|
-
|
|
342
|
-
desired.push({ kind: "text-run", startIndex: runStart, endIndex: blocks.length - 1 });
|
|
343
|
-
}
|
|
397
|
+
closeRun();
|
|
344
398
|
|
|
345
399
|
// Claude Code MCP can emit provisional pre-tool prose that gets
|
|
346
400
|
// superseded by post-tool output. Prune stale text-run segments so
|
|
347
401
|
// the final assistant output remains below tool output.
|
|
348
|
-
if (
|
|
349
|
-
|
|
402
|
+
if (shouldDropPreToolProse && firstToolIdx >= 0) {
|
|
403
|
+
if (orphanedSegments.length > 0) {
|
|
404
|
+
const remainingOrphans: RenderedSegment[] = [];
|
|
405
|
+
for (const orphan of orphanedSegments) {
|
|
406
|
+
if (orphan.kind === "text-run" && orphan.contentType === "text") {
|
|
407
|
+
host.chatContainer.removeChild(orphan.component);
|
|
408
|
+
if (host.streamingComponent === orphan.component) {
|
|
409
|
+
host.streamingComponent = undefined;
|
|
410
|
+
}
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
remainingOrphans.push(orphan);
|
|
414
|
+
}
|
|
415
|
+
orphanedSegments = remainingOrphans;
|
|
416
|
+
}
|
|
417
|
+
const desiredTextKeys = new Set(
|
|
350
418
|
desired
|
|
351
419
|
.filter((seg): seg is Extract<DesiredSegment, { kind: "text-run" }> => seg.kind === "text-run")
|
|
352
|
-
.map((seg) => seg.startIndex),
|
|
420
|
+
.map((seg) => `${seg.contentType}:${seg.startIndex}`),
|
|
353
421
|
);
|
|
354
422
|
const desiredToolIndices = new Set(
|
|
355
423
|
desired
|
|
@@ -358,7 +426,11 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
|
|
|
358
426
|
);
|
|
359
427
|
const nextRendered: RenderedSegment[] = [];
|
|
360
428
|
for (const seg of renderedSegments) {
|
|
361
|
-
if (
|
|
429
|
+
if (
|
|
430
|
+
seg.kind === "text-run"
|
|
431
|
+
&& seg.contentType === "text"
|
|
432
|
+
&& !desiredTextKeys.has(`${seg.contentType}:${seg.startIndex}`)
|
|
433
|
+
) {
|
|
362
434
|
host.chatContainer.removeChild(seg.component);
|
|
363
435
|
if (host.streamingComponent === seg.component) {
|
|
364
436
|
host.streamingComponent = undefined;
|
|
@@ -390,18 +462,24 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
|
|
|
390
462
|
} else {
|
|
391
463
|
// text-run segment
|
|
392
464
|
const existing = renderedSegments.find(
|
|
393
|
-
(s) => s.kind === "text-run" && s.startIndex === seg.startIndex,
|
|
465
|
+
(s) => s.kind === "text-run" && s.startIndex === seg.startIndex && s.contentType === seg.contentType,
|
|
394
466
|
);
|
|
395
467
|
if (!existing) {
|
|
396
468
|
const comp = new AssistantMessageComponent(
|
|
397
469
|
undefined,
|
|
398
470
|
host.hideThinkingBlock,
|
|
399
471
|
host.getMarkdownThemeWithSettings(),
|
|
400
|
-
|
|
472
|
+
timestampFormat,
|
|
401
473
|
{ startIndex: seg.startIndex, endIndex: seg.endIndex },
|
|
402
474
|
);
|
|
403
475
|
host.chatContainer.addChild(comp);
|
|
404
|
-
renderedSegments.push({
|
|
476
|
+
renderedSegments.push({
|
|
477
|
+
kind: "text-run",
|
|
478
|
+
startIndex: seg.startIndex,
|
|
479
|
+
endIndex: seg.endIndex,
|
|
480
|
+
contentType: seg.contentType,
|
|
481
|
+
component: comp,
|
|
482
|
+
});
|
|
405
483
|
host.streamingComponent = comp;
|
|
406
484
|
}
|
|
407
485
|
}
|
|
@@ -412,7 +490,9 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
|
|
|
412
490
|
for (const seg of renderedSegments) {
|
|
413
491
|
if (seg.kind === "text-run") {
|
|
414
492
|
// Find corresponding desired segment to get current endIndex
|
|
415
|
-
const d = desired.find(
|
|
493
|
+
const d = desired.find(
|
|
494
|
+
(ds) => ds.kind === "text-run" && ds.startIndex === seg.startIndex && ds.contentType === seg.contentType,
|
|
495
|
+
);
|
|
416
496
|
if (d && d.kind === "text-run" && d.endIndex !== seg.endIndex) {
|
|
417
497
|
seg.endIndex = d.endIndex;
|
|
418
498
|
seg.component.setRange({ startIndex: seg.startIndex, endIndex: seg.endIndex });
|
|
@@ -483,11 +563,11 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
|
|
|
483
563
|
}
|
|
484
564
|
break;
|
|
485
565
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
566
|
+
case "message_end":
|
|
567
|
+
if (event.message.role === "user") break;
|
|
568
|
+
if (event.message.role === "assistant") {
|
|
569
|
+
host.streamingMessage = event.message;
|
|
570
|
+
let errorMessage: string | undefined;
|
|
491
571
|
if (host.streamingMessage.stopReason === "aborted") {
|
|
492
572
|
const retryAttempt = host.session.retryAttempt;
|
|
493
573
|
errorMessage = retryAttempt > 0
|
|
@@ -496,18 +576,144 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
|
|
|
496
576
|
host.streamingMessage.errorMessage = errorMessage;
|
|
497
577
|
}
|
|
498
578
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
579
|
+
const shouldRenderAssistant = hasVisibleAssistantContent(host.streamingMessage)
|
|
580
|
+
|| (
|
|
581
|
+
(host.streamingMessage.stopReason === "aborted" || host.streamingMessage.stopReason === "error")
|
|
582
|
+
&& !hasAssistantToolBlocks(host.streamingMessage)
|
|
583
|
+
);
|
|
584
|
+
|
|
585
|
+
// The final message_end payload can contain additional text/thinking
|
|
586
|
+
// blocks that never arrived via message_update (e.g. SDK result
|
|
587
|
+
// aggregation). Rebuild this in-flight turn from final content so
|
|
588
|
+
// ranges/components don't keep stale partial indices.
|
|
589
|
+
if (renderedSegments.length > 0) {
|
|
590
|
+
const finalBlocks = host.streamingMessage.content;
|
|
591
|
+
type DesiredSegment =
|
|
592
|
+
| { kind: "text-run"; startIndex: number; endIndex: number; contentType: "text" | "thinking" }
|
|
593
|
+
| { kind: "tool"; contentIndex: number; toolId: string };
|
|
594
|
+
const desired: DesiredSegment[] = [];
|
|
595
|
+
let runStart = -1;
|
|
596
|
+
let runEnd = -1;
|
|
597
|
+
let runType: "text" | "thinking" | undefined;
|
|
598
|
+
const closeRun = () => {
|
|
599
|
+
if (runStart !== -1 && runType) {
|
|
600
|
+
desired.push({ kind: "text-run", startIndex: runStart, endIndex: runEnd, contentType: runType });
|
|
601
|
+
runStart = -1;
|
|
602
|
+
runEnd = -1;
|
|
603
|
+
runType = undefined;
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
|
|
607
|
+
for (let i = 0; i < finalBlocks.length; i++) {
|
|
608
|
+
const block = finalBlocks[i] as any;
|
|
609
|
+
const blockType = block?.type === "text" || block?.type === "thinking" ? block.type : undefined;
|
|
610
|
+
const isTextLike = blockType === "text" || blockType === "thinking";
|
|
611
|
+
const isTool = block?.type === "toolCall" || block?.type === "serverToolUse";
|
|
612
|
+
|
|
613
|
+
if (isTextLike) {
|
|
614
|
+
if (runStart === -1) {
|
|
615
|
+
runStart = i;
|
|
616
|
+
runEnd = i;
|
|
617
|
+
runType = blockType;
|
|
618
|
+
} else if (runType !== blockType) {
|
|
619
|
+
closeRun();
|
|
620
|
+
runStart = i;
|
|
621
|
+
runEnd = i;
|
|
622
|
+
runType = blockType;
|
|
623
|
+
} else {
|
|
624
|
+
runEnd = i;
|
|
625
|
+
}
|
|
626
|
+
} else {
|
|
627
|
+
closeRun();
|
|
628
|
+
if (isTool) {
|
|
629
|
+
desired.push({ kind: "tool", contentIndex: i, toolId: block.id });
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
closeRun();
|
|
634
|
+
|
|
635
|
+
const toolComponentsById = new Map<string, ToolExecutionComponent>();
|
|
636
|
+
for (const [toolId, component] of host.pendingTools.entries()) {
|
|
637
|
+
toolComponentsById.set(toolId, component);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
for (const seg of renderedSegments) {
|
|
641
|
+
host.chatContainer.removeChild(seg.component);
|
|
642
|
+
if (seg.kind === "tool") {
|
|
643
|
+
const priorBlocks = host.streamingMessage.content;
|
|
644
|
+
const priorBlock = priorBlocks[seg.contentIndex] as any;
|
|
645
|
+
if (priorBlock?.id && !toolComponentsById.has(priorBlock.id)) {
|
|
646
|
+
toolComponentsById.set(priorBlock.id, seg.component);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
renderedSegments = [];
|
|
651
|
+
host.streamingComponent = undefined;
|
|
652
|
+
|
|
653
|
+
for (const seg of desired) {
|
|
654
|
+
if (seg.kind === "tool") {
|
|
655
|
+
const finalBlock = finalBlocks[seg.contentIndex] as any;
|
|
656
|
+
let component = toolComponentsById.get(seg.toolId);
|
|
657
|
+
if (!component && finalBlock?.id) {
|
|
658
|
+
component = host.pendingTools.get(finalBlock.id);
|
|
659
|
+
}
|
|
660
|
+
if (!component && finalBlock?.type === "toolCall") {
|
|
661
|
+
component = new ToolExecutionComponent(
|
|
662
|
+
finalBlock.name,
|
|
663
|
+
finalBlock.arguments,
|
|
664
|
+
{ showImages: host.settingsManager.getShowImages() },
|
|
665
|
+
host.getRegisteredToolDefinition(finalBlock.name),
|
|
666
|
+
host.ui,
|
|
667
|
+
);
|
|
668
|
+
component.setExpanded(host.toolOutputExpanded);
|
|
669
|
+
host.pendingTools.set(finalBlock.id, component);
|
|
670
|
+
toolComponentsById.set(finalBlock.id, component);
|
|
671
|
+
} else if (!component && finalBlock?.type === "serverToolUse") {
|
|
672
|
+
component = new ToolExecutionComponent(
|
|
673
|
+
finalBlock.name,
|
|
674
|
+
finalBlock.input ?? {},
|
|
675
|
+
{ showImages: host.settingsManager.getShowImages() },
|
|
676
|
+
undefined,
|
|
677
|
+
host.ui,
|
|
678
|
+
);
|
|
679
|
+
component.setExpanded(host.toolOutputExpanded);
|
|
680
|
+
host.pendingTools.set(finalBlock.id, component);
|
|
681
|
+
toolComponentsById.set(finalBlock.id, component);
|
|
682
|
+
}
|
|
683
|
+
if (component) {
|
|
684
|
+
host.chatContainer.addChild(component);
|
|
685
|
+
renderedSegments.push({ kind: "tool", contentIndex: seg.contentIndex, component });
|
|
686
|
+
}
|
|
687
|
+
continue;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
const comp = new AssistantMessageComponent(
|
|
691
|
+
undefined,
|
|
692
|
+
host.hideThinkingBlock,
|
|
693
|
+
host.getMarkdownThemeWithSettings(),
|
|
694
|
+
timestampFormat,
|
|
695
|
+
{ startIndex: seg.startIndex, endIndex: seg.endIndex },
|
|
696
|
+
);
|
|
697
|
+
comp.updateContent(host.streamingMessage);
|
|
698
|
+
host.chatContainer.addChild(comp);
|
|
699
|
+
renderedSegments.push({
|
|
700
|
+
kind: "text-run",
|
|
701
|
+
startIndex: seg.startIndex,
|
|
702
|
+
endIndex: seg.endIndex,
|
|
703
|
+
contentType: seg.contentType,
|
|
704
|
+
component: comp,
|
|
705
|
+
});
|
|
706
|
+
host.streamingComponent = comp;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
if (!host.streamingComponent && shouldRenderAssistant) {
|
|
711
|
+
host.streamingComponent = new AssistantMessageComponent(
|
|
712
|
+
undefined,
|
|
713
|
+
host.hideThinkingBlock,
|
|
714
|
+
host.getMarkdownThemeWithSettings(),
|
|
715
|
+
timestampFormat,
|
|
716
|
+
);
|
|
511
717
|
host.chatContainer.addChild(host.streamingComponent);
|
|
512
718
|
}
|
|
513
719
|
if (host.streamingComponent) {
|
|
@@ -536,6 +742,7 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
|
|
|
536
742
|
host.streamingComponent = undefined;
|
|
537
743
|
host.streamingMessage = undefined;
|
|
538
744
|
renderedSegments = [];
|
|
745
|
+
orphanedSegments = [];
|
|
539
746
|
lastContentLength = 0;
|
|
540
747
|
// Clear pinned output once the message is finalized in the chat
|
|
541
748
|
// container — prevents duplicate display when the agent continues
|
|
@@ -599,6 +806,7 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
|
|
|
599
806
|
host.streamingComponent = undefined;
|
|
600
807
|
host.streamingMessage = undefined;
|
|
601
808
|
renderedSegments = [];
|
|
809
|
+
orphanedSegments = [];
|
|
602
810
|
lastContentLength = 0;
|
|
603
811
|
host.pendingTools.clear();
|
|
604
812
|
// Pinned output is only useful while work is actively streaming.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { test } from "node:test";
|
|
3
|
+
|
|
4
|
+
import { buildAssistantReplaySegments } from "./interactive-mode.js";
|
|
5
|
+
|
|
6
|
+
test("buildAssistantReplaySegments preserves tool-first ordering", () => {
|
|
7
|
+
const segments = buildAssistantReplaySegments([
|
|
8
|
+
{ type: "toolCall", id: "t1", name: "read", arguments: {} },
|
|
9
|
+
{ type: "text", text: "Done." },
|
|
10
|
+
]);
|
|
11
|
+
|
|
12
|
+
assert.deepEqual(segments, [
|
|
13
|
+
{ kind: "tool", contentIndex: 0 },
|
|
14
|
+
{ kind: "assistant", startIndex: 1, endIndex: 1 },
|
|
15
|
+
]);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("buildAssistantReplaySegments preserves interleaved assistant-tool-assistant runs", () => {
|
|
19
|
+
const segments = buildAssistantReplaySegments([
|
|
20
|
+
{ type: "text", text: "Let me check." },
|
|
21
|
+
{ type: "serverToolUse", id: "s1", name: "mcp__fs__glob", input: {} },
|
|
22
|
+
{ type: "thinking", thinking: "Tool result looks good." },
|
|
23
|
+
{ type: "text", text: "Here is the answer." },
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
assert.deepEqual(segments, [
|
|
27
|
+
{ kind: "assistant", startIndex: 0, endIndex: 0 },
|
|
28
|
+
{ kind: "tool", contentIndex: 1 },
|
|
29
|
+
{ kind: "assistant", startIndex: 2, endIndex: 3 },
|
|
30
|
+
]);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("buildAssistantReplaySegments ignores non-rendered non-tool blocks", () => {
|
|
34
|
+
const segments = buildAssistantReplaySegments([
|
|
35
|
+
{ type: "text", text: "before" },
|
|
36
|
+
{ type: "webSearchResult", toolUseId: "s1", content: {} },
|
|
37
|
+
{ type: "text", text: "after" },
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
assert.deepEqual(segments, [
|
|
41
|
+
{ kind: "assistant", startIndex: 0, endIndex: 0 },
|
|
42
|
+
{ kind: "assistant", startIndex: 2, endIndex: 2 },
|
|
43
|
+
]);
|
|
44
|
+
});
|