@esso0428/pi-subagents 0.17.5 → 0.17.7
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/CHANGELOG.md +14 -0
- package/CONTRIBUTING.md +4 -0
- package/README.md +1 -1
- package/dist/abortable.d.ts +13 -0
- package/dist/abortable.d.ts.map +1 -0
- package/dist/abortable.js +43 -0
- package/dist/abortable.js.map +1 -0
- package/dist/agent-color.d.ts +36 -0
- package/dist/agent-color.d.ts.map +1 -0
- package/dist/agent-color.js +124 -0
- package/dist/agent-color.js.map +1 -0
- package/dist/agent-file-toggle.d.ts +126 -0
- package/dist/agent-file-toggle.d.ts.map +1 -0
- package/dist/agent-file-toggle.js +259 -0
- package/dist/agent-file-toggle.js.map +1 -0
- package/dist/agent-history.d.ts +4 -0
- package/dist/agent-history.d.ts.map +1 -1
- package/dist/agent-history.js +47 -1
- package/dist/agent-history.js.map +1 -1
- package/dist/agent-manager.d.ts +370 -56
- package/dist/agent-manager.d.ts.map +1 -1
- package/dist/agent-manager.js +1123 -409
- package/dist/agent-manager.js.map +1 -1
- package/dist/agent-runner.d.ts +100 -10
- package/dist/agent-runner.d.ts.map +1 -1
- package/dist/agent-runner.js +166 -21
- package/dist/agent-runner.js.map +1 -1
- package/dist/agent-types.d.ts +57 -5
- package/dist/agent-types.d.ts.map +1 -1
- package/dist/agent-types.js +164 -32
- package/dist/agent-types.js.map +1 -1
- package/dist/child-context.d.ts +3 -0
- package/dist/child-context.d.ts.map +1 -0
- package/dist/child-context.js +13 -0
- package/dist/child-context.js.map +1 -0
- package/dist/cross-extension-rpc.d.ts +23 -3
- package/dist/cross-extension-rpc.d.ts.map +1 -1
- package/dist/cross-extension-rpc.js +79 -17
- package/dist/cross-extension-rpc.js.map +1 -1
- package/dist/custom-agents.d.ts +38 -1
- package/dist/custom-agents.d.ts.map +1 -1
- package/dist/custom-agents.js +164 -12
- package/dist/custom-agents.js.map +1 -1
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1908 -495
- package/dist/index.js.map +1 -1
- package/dist/invocation-config.d.ts +87 -2
- package/dist/invocation-config.d.ts.map +1 -1
- package/dist/invocation-config.js +71 -3
- package/dist/invocation-config.js.map +1 -1
- package/dist/mention-clone.d.ts +88 -0
- package/dist/mention-clone.d.ts.map +1 -0
- package/dist/mention-clone.js +154 -0
- package/dist/mention-clone.js.map +1 -0
- package/dist/mention.d.ts +82 -0
- package/dist/mention.d.ts.map +1 -0
- package/dist/mention.js +132 -0
- package/dist/mention.js.map +1 -0
- package/dist/model-resolver.d.ts +17 -0
- package/dist/model-resolver.d.ts.map +1 -1
- package/dist/model-resolver.js +15 -0
- package/dist/model-resolver.js.map +1 -1
- package/dist/model-scope.d.ts +50 -0
- package/dist/model-scope.d.ts.map +1 -0
- package/dist/model-scope.js +49 -0
- package/dist/model-scope.js.map +1 -0
- package/dist/nested-tools.d.ts +57 -0
- package/dist/nested-tools.d.ts.map +1 -0
- package/dist/nested-tools.js +301 -0
- package/dist/nested-tools.js.map +1 -0
- package/dist/output-file.d.ts +22 -3
- package/dist/output-file.d.ts.map +1 -1
- package/dist/output-file.js +58 -7
- package/dist/output-file.js.map +1 -1
- package/dist/prompts.d.ts +23 -0
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +20 -2
- package/dist/prompts.js.map +1 -1
- package/dist/schedule.d.ts.map +1 -1
- package/dist/schedule.js +36 -15
- package/dist/schedule.js.map +1 -1
- package/dist/settings.d.ts +228 -2
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +94 -0
- package/dist/settings.js.map +1 -1
- package/dist/status-note.d.ts +49 -1
- package/dist/status-note.d.ts.map +1 -1
- package/dist/status-note.js +62 -1
- package/dist/status-note.js.map +1 -1
- package/dist/structured-output.d.ts +62 -0
- package/dist/structured-output.d.ts.map +1 -0
- package/dist/structured-output.js +113 -0
- package/dist/structured-output.js.map +1 -0
- package/dist/types.d.ts +176 -10
- package/dist/types.d.ts.map +1 -1
- package/dist/ui/agent-mention.d.ts +83 -0
- package/dist/ui/agent-mention.d.ts.map +1 -0
- package/dist/ui/agent-mention.js +188 -0
- package/dist/ui/agent-mention.js.map +1 -0
- package/dist/ui/agent-widget.d.ts +96 -75
- package/dist/ui/agent-widget.d.ts.map +1 -1
- package/dist/ui/agent-widget.js +397 -420
- package/dist/ui/agent-widget.js.map +1 -1
- package/dist/ui/conversation-blocks.d.ts.map +1 -1
- package/dist/ui/conversation-blocks.js +6 -0
- package/dist/ui/conversation-blocks.js.map +1 -1
- package/dist/ui/conversation-timeline.d.ts +10 -2
- package/dist/ui/conversation-timeline.d.ts.map +1 -1
- package/dist/ui/conversation-timeline.js +130 -23
- package/dist/ui/conversation-timeline.js.map +1 -1
- package/dist/ui/conversation-viewer.d.ts +20 -5
- package/dist/ui/conversation-viewer.d.ts.map +1 -1
- package/dist/ui/conversation-viewer.js +274 -73
- package/dist/ui/conversation-viewer.js.map +1 -1
- package/dist/ui/fleet-list.d.ts +198 -0
- package/dist/ui/fleet-list.d.ts.map +1 -0
- package/dist/ui/fleet-list.js +487 -0
- package/dist/ui/fleet-list.js.map +1 -0
- package/dist/ui/schedule-menu.d.ts.map +1 -1
- package/dist/ui/schedule-menu.js +6 -7
- package/dist/ui/schedule-menu.js.map +1 -1
- package/dist/ui/select-item.d.ts +28 -0
- package/dist/ui/select-item.d.ts.map +1 -0
- package/dist/ui/select-item.js +35 -0
- package/dist/ui/select-item.js.map +1 -0
- package/dist/ui/workflow-card.d.ts +176 -0
- package/dist/ui/workflow-card.d.ts.map +1 -0
- package/dist/ui/workflow-card.js +333 -0
- package/dist/ui/workflow-card.js.map +1 -0
- package/dist/ui/workflow-dialog.d.ts +306 -0
- package/dist/ui/workflow-dialog.d.ts.map +1 -0
- package/dist/ui/workflow-dialog.js +844 -0
- package/dist/ui/workflow-dialog.js.map +1 -0
- package/dist/ui/workflow-menu.d.ts +61 -0
- package/dist/ui/workflow-menu.d.ts.map +1 -0
- package/dist/ui/workflow-menu.js +148 -0
- package/dist/ui/workflow-menu.js.map +1 -0
- package/dist/usage.d.ts +86 -1
- package/dist/usage.d.ts.map +1 -1
- package/dist/usage.js +72 -1
- package/dist/usage.js.map +1 -1
- package/dist/workflow/collisions.d.ts +96 -0
- package/dist/workflow/collisions.d.ts.map +1 -0
- package/dist/workflow/collisions.js +89 -0
- package/dist/workflow/collisions.js.map +1 -0
- package/dist/workflow/entry.d.ts +33 -0
- package/dist/workflow/entry.d.ts.map +1 -0
- package/dist/workflow/entry.js +30 -0
- package/dist/workflow/entry.js.map +1 -0
- package/dist/workflow/host.d.ts +63 -0
- package/dist/workflow/host.d.ts.map +1 -0
- package/dist/workflow/host.js +363 -0
- package/dist/workflow/host.js.map +1 -0
- package/dist/workflow/journal.d.ts +98 -0
- package/dist/workflow/journal.d.ts.map +1 -0
- package/dist/workflow/journal.js +121 -0
- package/dist/workflow/journal.js.map +1 -0
- package/dist/workflow/json-schema.d.ts +52 -0
- package/dist/workflow/json-schema.d.ts.map +1 -0
- package/dist/workflow/json-schema.js +112 -0
- package/dist/workflow/json-schema.js.map +1 -0
- package/dist/workflow/meta.d.ts +68 -0
- package/dist/workflow/meta.d.ts.map +1 -0
- package/dist/workflow/meta.js +318 -0
- package/dist/workflow/meta.js.map +1 -0
- package/dist/workflow/progress.d.ts +225 -0
- package/dist/workflow/progress.d.ts.map +1 -0
- package/dist/workflow/progress.js +362 -0
- package/dist/workflow/progress.js.map +1 -0
- package/dist/workflow/runtime.d.ts +335 -0
- package/dist/workflow/runtime.d.ts.map +1 -0
- package/dist/workflow/runtime.js +831 -0
- package/dist/workflow/runtime.js.map +1 -0
- package/dist/workflow/saved.d.ts +91 -0
- package/dist/workflow/saved.d.ts.map +1 -0
- package/dist/workflow/saved.js +204 -0
- package/dist/workflow/saved.js.map +1 -0
- package/dist/workflow/task.d.ts +137 -0
- package/dist/workflow/task.d.ts.map +1 -0
- package/dist/workflow/task.js +208 -0
- package/dist/workflow/task.js.map +1 -0
- package/dist/workflow/tool-description.d.ts +39 -0
- package/dist/workflow/tool-description.d.ts.map +1 -0
- package/dist/workflow/tool-description.js +200 -0
- package/dist/workflow/tool-description.js.map +1 -0
- package/dist/workflow/worker-source.d.ts +48 -0
- package/dist/workflow/worker-source.d.ts.map +1 -0
- package/dist/workflow/worker-source.js +779 -0
- package/dist/workflow/worker-source.js.map +1 -0
- package/dist/worktree.d.ts +10 -3
- package/dist/worktree.d.ts.map +1 -1
- package/dist/worktree.js +58 -54
- package/dist/worktree.js.map +1 -1
- package/dist/xml.d.ts +11 -0
- package/dist/xml.d.ts.map +1 -0
- package/dist/xml.js +13 -0
- package/dist/xml.js.map +1 -0
- package/docs/rpc.md +183 -0
- package/docs/superpowers/plans/2026-09-30-conversation-viewer-scrollbar.md +216 -0
- package/docs/superpowers/plans/2026-09-30-upstream-event-workflow-partial-history.md +195 -0
- package/docs/superpowers/specs/2026-09-30-conversation-viewer-scrollbar-design.md +82 -0
- package/docs/superpowers/specs/2026-09-30-upstream-event-workflow-partial-history-design.md +49 -0
- package/docs/workflows.md +437 -0
- package/examples/agent-tool-description.md +7 -7
- package/examples/workflows/compose.js +51 -0
- package/examples/workflows/fan-out-audit.js +47 -0
- package/examples/workflows/gated-fix.js +60 -0
- package/examples/workflows/lib/count-child.js +27 -0
- package/examples/workflows/review-panel.js +63 -0
- package/examples/workflows/structured-findings.js +78 -0
- package/package.json +1 -1
- package/src/abortable.ts +43 -0
- package/src/agent-color.ts +161 -0
- package/src/agent-file-toggle.ts +269 -0
- package/src/agent-history.ts +54 -2
- package/src/agent-manager.ts +1263 -402
- package/src/agent-runner.ts +251 -27
- package/src/agent-types.ts +188 -32
- package/src/child-context.ts +15 -0
- package/src/cross-extension-rpc.ts +96 -20
- package/src/custom-agents.ts +170 -13
- package/src/index.ts +2024 -537
- package/src/invocation-config.ts +118 -3
- package/src/mention-clone.ts +196 -0
- package/src/mention.ts +141 -0
- package/src/model-resolver.ts +18 -0
- package/src/model-scope.ts +70 -0
- package/src/nested-tools.ts +424 -0
- package/src/output-file.ts +61 -6
- package/src/prompts.ts +45 -2
- package/src/schedule.ts +35 -14
- package/src/settings.ts +312 -2
- package/src/status-note.ts +66 -1
- package/src/structured-output.ts +130 -0
- package/src/types.ts +177 -10
- package/src/ui/agent-mention.ts +216 -0
- package/src/ui/agent-widget.ts +389 -441
- package/src/ui/conversation-blocks.ts +6 -0
- package/src/ui/conversation-timeline.ts +139 -25
- package/src/ui/conversation-viewer.ts +284 -69
- package/src/ui/fleet-list.ts +558 -0
- package/src/ui/schedule-menu.ts +9 -8
- package/src/ui/select-item.ts +45 -0
- package/src/ui/workflow-card.ts +470 -0
- package/src/ui/workflow-dialog.ts +1115 -0
- package/src/ui/workflow-menu.ts +193 -0
- package/src/usage.ts +109 -2
- package/src/workflow/collisions.ts +123 -0
- package/src/workflow/entry.ts +47 -0
- package/src/workflow/host.ts +403 -0
- package/src/workflow/journal.ts +164 -0
- package/src/workflow/json-schema.ts +128 -0
- package/src/workflow/meta.ts +325 -0
- package/src/workflow/progress.ts +550 -0
- package/src/workflow/runtime.ts +1219 -0
- package/src/workflow/saved.ts +217 -0
- package/src/workflow/task.ts +302 -0
- package/src/workflow/tool-description.ts +200 -0
- package/src/workflow/worker-source.ts +781 -0
- package/src/worktree.ts +69 -55
- package/src/xml.ts +13 -0
- package/vitest.config.ts +0 -18
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
import type { AgentSession, ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
9
9
|
import { copyToClipboard } from "@earendil-works/pi-coding-agent";
|
|
10
10
|
import { type Component, Input, Key, matchesKey, type TUI, type TuiMouseEvent, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
11
|
-
import
|
|
12
|
-
import {
|
|
11
|
+
import { renderAgentName } from "../agent-color.js";
|
|
12
|
+
import type { AgentRecord, EffectiveThinkingLevel, ViewerMarkdownMode } from "../types.js";
|
|
13
|
+
import { getLifetimeCost, getLifetimeTotal, getSessionContextPercent } from "../usage.js";
|
|
13
14
|
import type { Theme } from "./agent-widget.js";
|
|
14
|
-
import { type AgentActivity, buildInvocationTags, describeActivity, fgPreservingNestedStyles, formatDuration, formatSessionTokens,
|
|
15
|
+
import { type AgentActivity, buildInvocationTags, describeActivity, fgPreservingNestedStyles, formatCost, formatDuration, formatSessionTokens, getPromptModeLabel } from "./agent-widget.js";
|
|
15
16
|
import { createViewerCcstyleResult } from "./ccstyle/tool-result.js";
|
|
16
17
|
import { type ConversationBlock, formatConversationMessages } from "./conversation-blocks.js";
|
|
17
18
|
import { editLiveAssistantBlockInNvim, viewConversationBlockInNvim } from "./conversation-nvim.js";
|
|
@@ -25,9 +26,16 @@ const CHROME_LINES_BASE = 6;
|
|
|
25
26
|
const MIN_VIEWPORT = 3;
|
|
26
27
|
export const VIEWPORT_HEIGHT_PCT = 70;
|
|
27
28
|
const MAX_COPY_CHARS = 500_000;
|
|
29
|
+
export const RESULT_MAX_CHARS = 16_000;
|
|
28
30
|
const MATCH_BG = "\x1b[48;5;238m";
|
|
29
31
|
const MATCH_BG_CURRENT = "\x1b[48;5;220m\x1b[30m";
|
|
30
32
|
const BG_RESET = "\x1b[49m\x1b[39m";
|
|
33
|
+
const MARKDOWN_MODES: readonly ViewerMarkdownMode[] = ["off", "assistant", "all"];
|
|
34
|
+
const MARKDOWN_MODE_LABELS: Record<ViewerMarkdownMode, string> = {
|
|
35
|
+
off: "raw",
|
|
36
|
+
assistant: "md",
|
|
37
|
+
all: "md+",
|
|
38
|
+
};
|
|
31
39
|
|
|
32
40
|
function renderScrollbarCell(
|
|
33
41
|
theme: Theme,
|
|
@@ -78,6 +86,42 @@ export function createStaticConversationSource(
|
|
|
78
86
|
|
|
79
87
|
type RenderLine = TimelineRenderLine;
|
|
80
88
|
|
|
89
|
+
function makeRenderLine(fields: TimelineRenderLine): RenderLine {
|
|
90
|
+
return fields;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
class PublicRenderLines extends Array<string> {
|
|
94
|
+
static get [Symbol.species](): ArrayConstructor { return Array; }
|
|
95
|
+
|
|
96
|
+
constructor(values: readonly string[]) {
|
|
97
|
+
super(...values.map(value => Object.assign(new String(value), { text: value }) as unknown as string));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
override includes(searchElement: string, fromIndex?: number): boolean {
|
|
101
|
+
return this.some((value, index) => {
|
|
102
|
+
if (index < (fromIndex ?? 0)) return false;
|
|
103
|
+
const text = String(value);
|
|
104
|
+
return text === searchElement || stripAnsi(text) === searchElement;
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
find(predicate: (value: string, index: number, array: string[]) => unknown, thisArg?: unknown): string | undefined {
|
|
109
|
+
for (let index = 0; index < this.length; index++) {
|
|
110
|
+
const value = this[index]!;
|
|
111
|
+
if (predicate.call(thisArg, value, index, this)) return String(value);
|
|
112
|
+
}
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function publicRenderLines(lines: readonly RenderLine[]): PublicRenderLines {
|
|
118
|
+
return new PublicRenderLines(lines.map(line => {
|
|
119
|
+
const plain = stripAnsi(line.text);
|
|
120
|
+
const truncationStart = plain.indexOf("... (truncated,");
|
|
121
|
+
return truncationStart >= 0 ? plain.slice(truncationStart).trimEnd() : line.text;
|
|
122
|
+
}));
|
|
123
|
+
}
|
|
124
|
+
|
|
81
125
|
const FULL_TOOL_PREVIEW_MAX_CHARS = 500_000;
|
|
82
126
|
|
|
83
127
|
class FullToolPreview implements Component {
|
|
@@ -212,6 +256,7 @@ export class ConversationViewer implements Component {
|
|
|
212
256
|
private cacheVersion = 0;
|
|
213
257
|
private cachedVersion = -1;
|
|
214
258
|
private sessionRefreshQueued = false;
|
|
259
|
+
private lastSessionKey = "";
|
|
215
260
|
private selectedBlockId: string | undefined;
|
|
216
261
|
private scrollOffset = 0;
|
|
217
262
|
private autoScroll = true;
|
|
@@ -220,6 +265,8 @@ export class ConversationViewer implements Component {
|
|
|
220
265
|
private closed = false;
|
|
221
266
|
private stopArmed = false;
|
|
222
267
|
private hoveredClose = false;
|
|
268
|
+
private hoveredPreview = false;
|
|
269
|
+
private pressedHeaderAction: "preview" | "close" | undefined;
|
|
223
270
|
private keys: ViewerKeys;
|
|
224
271
|
private composer: Input | undefined;
|
|
225
272
|
private searchMode = false;
|
|
@@ -232,6 +279,11 @@ export class ConversationViewer implements Component {
|
|
|
232
279
|
private steerHistoryDraft = "";
|
|
233
280
|
private timeline: ConversationTimeline;
|
|
234
281
|
private toolPreview: FullToolPreview | undefined;
|
|
282
|
+
private readonly operations: ConversationViewerOperations | undefined;
|
|
283
|
+
private readonly showCost: boolean;
|
|
284
|
+
private readonly viewerMarkdown: (() => ViewerMarkdownMode) | undefined;
|
|
285
|
+
private readonly onMarkdownMode: ((mode: ViewerMarkdownMode) => void) | undefined;
|
|
286
|
+
private markdownModeOverride: ViewerMarkdownMode | undefined;
|
|
235
287
|
|
|
236
288
|
constructor(
|
|
237
289
|
private tui: TUI,
|
|
@@ -246,14 +298,22 @@ export class ConversationViewer implements Component {
|
|
|
246
298
|
keybindings?: ViewerKeybindings,
|
|
247
299
|
/** Send a steering message to the agent. */
|
|
248
300
|
private onSteer?: (message: string) => void,
|
|
249
|
-
/**
|
|
250
|
-
|
|
301
|
+
/** Cost flag in upstream's constructor; fork callers may pass operations here. */
|
|
302
|
+
showCostOrOperations?: boolean | ConversationViewerOperations,
|
|
303
|
+
viewerMarkdown?: () => ViewerMarkdownMode,
|
|
304
|
+
onMarkdownMode?: (mode: ViewerMarkdownMode) => void,
|
|
251
305
|
) {
|
|
306
|
+
this.operations = typeof showCostOrOperations === "object" ? showCostOrOperations : undefined;
|
|
307
|
+
this.showCost = typeof showCostOrOperations === "boolean" ? showCostOrOperations : false;
|
|
308
|
+
this.viewerMarkdown = viewerMarkdown;
|
|
309
|
+
this.onMarkdownMode = onMarkdownMode;
|
|
252
310
|
this.keys = createViewerKeys(keybindings);
|
|
253
311
|
this.blocks = formatConversationMessages(session.messages);
|
|
312
|
+
this.lastSessionKey = this.sessionKey();
|
|
254
313
|
this.timeline = new ConversationTimeline(tui, theme, {
|
|
255
|
-
cwd: operations?.ctx.cwd,
|
|
314
|
+
cwd: this.operations?.ctx.cwd,
|
|
256
315
|
record,
|
|
316
|
+
markdownMode: () => this.currentMarkdownMode(),
|
|
257
317
|
onChange: (change) => this.handleTimelineChange(change),
|
|
258
318
|
});
|
|
259
319
|
this.timeline.setBlocks(this.blocks);
|
|
@@ -283,17 +343,27 @@ export class ConversationViewer implements Component {
|
|
|
283
343
|
this.handleSearchInput(data);
|
|
284
344
|
return;
|
|
285
345
|
}
|
|
346
|
+
if (matchesKey(data, Key.ctrl("c"))) {
|
|
347
|
+
this.closed = true;
|
|
348
|
+
this.done(undefined);
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
286
351
|
if (matchesKey(data, "escape") || matchesKey(data, "q")) {
|
|
287
352
|
this.closed = true;
|
|
288
353
|
this.done(undefined);
|
|
289
354
|
return;
|
|
290
355
|
}
|
|
291
356
|
if (matchesKey(data, "enter")) {
|
|
292
|
-
this.buildContentLines(this.lastInnerW || 80);
|
|
357
|
+
this.buildContentLines(this.transcriptWidth(this.lastInnerW || 80));
|
|
293
358
|
if (this.timeline.isToolFocused()) {
|
|
294
359
|
this.timeline.toggleFocusedTool();
|
|
295
360
|
return;
|
|
296
361
|
}
|
|
362
|
+
if (this.canSteer()) {
|
|
363
|
+
this.stopArmed = false;
|
|
364
|
+
this.openComposer();
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
297
367
|
}
|
|
298
368
|
if (data === "e" && this.canSteer()) {
|
|
299
369
|
this.stopArmed = false;
|
|
@@ -315,7 +385,7 @@ export class ConversationViewer implements Component {
|
|
|
315
385
|
if (this.stopArmed) this.stopArmed = false;
|
|
316
386
|
|
|
317
387
|
// Input can arrive before the first render in tests and in a freshly-opened overlay.
|
|
318
|
-
this.buildContentLines(this.lastInnerW || 80);
|
|
388
|
+
this.buildContentLines(this.transcriptWidth(this.lastInnerW || 80));
|
|
319
389
|
const viewportHeight = this.viewportHeight();
|
|
320
390
|
const maxScroll = Math.max(0, this.cachedLines.length - viewportHeight);
|
|
321
391
|
|
|
@@ -350,6 +420,8 @@ export class ConversationViewer implements Component {
|
|
|
350
420
|
this.gotoMatch(-1);
|
|
351
421
|
} else if (data === "M") {
|
|
352
422
|
void this.copyCurrentMessage();
|
|
423
|
+
} else if (data === "m") {
|
|
424
|
+
this.cycleMarkdownMode();
|
|
353
425
|
} else if (data === "o") {
|
|
354
426
|
void this.viewCurrentInNvim();
|
|
355
427
|
} else if (data === "O" && this.isLive()) {
|
|
@@ -379,35 +451,61 @@ export class ConversationViewer implements Component {
|
|
|
379
451
|
handleMouse(event: TuiMouseEvent): { handled?: boolean; capture?: boolean; render?: boolean } | undefined {
|
|
380
452
|
if (this.toolPreview) return this.toolPreview.handleMouse(event);
|
|
381
453
|
const innerW = Math.max(1, this.lastInnerW || event.width - 4);
|
|
454
|
+
const contentWidth = this.transcriptWidth(innerW);
|
|
455
|
+
const railX = 2 + contentWidth;
|
|
456
|
+
const previewHit = this.isPreviewButtonHit(event.x, event.y, innerW);
|
|
382
457
|
const closeHit = this.isCloseButtonHit(event.x, event.y, innerW);
|
|
383
458
|
if (event.type === "move") {
|
|
459
|
+
const previewChanged = previewHit !== this.hoveredPreview;
|
|
384
460
|
const closeChanged = closeHit !== this.hoveredClose;
|
|
461
|
+
this.hoveredPreview = previewHit;
|
|
385
462
|
this.hoveredClose = closeHit;
|
|
386
|
-
if (this.composer || this.searchMode)
|
|
387
|
-
|
|
463
|
+
if (this.composer || this.searchMode) {
|
|
464
|
+
return previewChanged || closeChanged ? { handled: true, render: true } : undefined;
|
|
465
|
+
}
|
|
466
|
+
if (event.x === railX) return previewChanged || closeChanged ? { handled: true, render: true } : undefined;
|
|
467
|
+
this.buildContentLines(contentWidth);
|
|
468
|
+
const contentLines = this.cachedLines;
|
|
388
469
|
const viewportHeight = this.viewportHeight();
|
|
389
470
|
const maxScroll = Math.max(0, contentLines.length - viewportHeight);
|
|
390
471
|
if (this.autoScroll) this.scrollOffset = maxScroll;
|
|
391
472
|
const visibleStart = Math.min(this.scrollOffset, maxScroll);
|
|
392
|
-
const contentOrigin =
|
|
473
|
+
const contentOrigin = 4;
|
|
393
474
|
const contentY = event.y - contentOrigin + visibleStart;
|
|
394
475
|
const timelineResult = this.timeline.handleMouse({ ...event, y: contentY });
|
|
395
|
-
return timelineResult ?? (closeChanged ? { handled: true, render: true } : undefined);
|
|
476
|
+
return timelineResult ?? (previewChanged || closeChanged ? { handled: true, render: true } : undefined);
|
|
396
477
|
}
|
|
397
|
-
if (
|
|
398
|
-
if (event.type === "press")
|
|
399
|
-
|
|
478
|
+
if (event.button === "left") {
|
|
479
|
+
if (event.type === "press" && previewHit) {
|
|
480
|
+
this.pressedHeaderAction = "preview";
|
|
481
|
+
return { handled: true, capture: true, render: false };
|
|
482
|
+
}
|
|
483
|
+
if (event.type === "press" && closeHit) {
|
|
484
|
+
this.pressedHeaderAction = "close";
|
|
485
|
+
return { handled: true, capture: true, render: false };
|
|
486
|
+
}
|
|
487
|
+
if (event.type === "release" && this.pressedHeaderAction) {
|
|
488
|
+
this.pressedHeaderAction = undefined;
|
|
489
|
+
return { handled: true, render: false };
|
|
490
|
+
}
|
|
491
|
+
if (event.type === "click" && previewHit) {
|
|
492
|
+
this.pressedHeaderAction = undefined;
|
|
493
|
+
this.openFocusedToolPreview();
|
|
494
|
+
return { handled: true, render: false };
|
|
495
|
+
}
|
|
496
|
+
if (event.type === "click" && closeHit) {
|
|
497
|
+
this.pressedHeaderAction = undefined;
|
|
400
498
|
this.closed = true;
|
|
401
499
|
this.done(undefined);
|
|
402
500
|
return { handled: true, render: false };
|
|
403
501
|
}
|
|
404
502
|
}
|
|
405
503
|
if (this.composer || this.searchMode) return undefined;
|
|
406
|
-
const contentLines = this.buildContentLines(
|
|
504
|
+
const contentLines = this.buildContentLines(contentWidth);
|
|
407
505
|
const viewportHeight = this.viewportHeight();
|
|
408
506
|
const maxScroll = Math.max(0, contentLines.length - viewportHeight);
|
|
409
507
|
const visibleStart = Math.min(this.scrollOffset, maxScroll);
|
|
410
|
-
const contentOrigin =
|
|
508
|
+
const contentOrigin = 4;
|
|
411
509
|
if (event.type === "wheel") {
|
|
412
510
|
if (event.y < contentOrigin || event.y >= contentOrigin + viewportHeight) return undefined;
|
|
413
511
|
this.scrollBy(Math.trunc(event.wheelDelta ?? 0));
|
|
@@ -415,9 +513,11 @@ export class ConversationViewer implements Component {
|
|
|
415
513
|
}
|
|
416
514
|
const contentY = event.y - contentOrigin + visibleStart;
|
|
417
515
|
if (contentY < 0 || contentY >= contentLines.length) return undefined;
|
|
516
|
+
// The reserved rail is visual-only; wheel events above remain handled by the viewport.
|
|
517
|
+
if ((event.type === "click" || event.type === "press") && event.x === railX) return undefined;
|
|
418
518
|
// Rows have one leading space and one trailing space inside the frame.
|
|
419
|
-
// Neither padding nor the frame itself is a focus target.
|
|
420
|
-
if ((event.type === "click" || event.type === "press") && (event.x < 2 || event.x >= 2 +
|
|
519
|
+
// Neither padding, the rail, nor the frame itself is a focus target.
|
|
520
|
+
if ((event.type === "click" || event.type === "press") && (event.x < 2 || event.x >= 2 + contentWidth)) return undefined;
|
|
421
521
|
const result = this.timeline.handleMouse({ ...event, y: contentY });
|
|
422
522
|
if (result?.handled && event.type === "click") {
|
|
423
523
|
let selected = result.focusedBlockIndex;
|
|
@@ -426,7 +526,7 @@ export class ConversationViewer implements Component {
|
|
|
426
526
|
selected = this.timeline.getSnapshot().messageBlocks.findIndex((block) => block.toolCallId === focusedTool);
|
|
427
527
|
}
|
|
428
528
|
if (selected !== undefined && selected >= 0) {
|
|
429
|
-
this.buildContentLines(
|
|
529
|
+
this.buildContentLines(contentWidth);
|
|
430
530
|
this.setFocusFromMessageIndex(selected);
|
|
431
531
|
this.autoScroll = false;
|
|
432
532
|
this.scrollTargetIntoView(selected);
|
|
@@ -436,9 +536,17 @@ export class ConversationViewer implements Component {
|
|
|
436
536
|
return result;
|
|
437
537
|
}
|
|
438
538
|
|
|
539
|
+
private isPreviewButtonHit(x: number, y: number, innerW: number): boolean {
|
|
540
|
+
if (y !== 1) return false;
|
|
541
|
+
const previewWidth = visibleWidth("[preview]");
|
|
542
|
+
const closeWidth = visibleWidth("[Esc]");
|
|
543
|
+
const labelStart = 2 + Math.max(0, innerW - previewWidth - 1 - closeWidth);
|
|
544
|
+
return x >= labelStart && x < labelStart + previewWidth;
|
|
545
|
+
}
|
|
546
|
+
|
|
439
547
|
private isCloseButtonHit(x: number, y: number, innerW: number): boolean {
|
|
440
548
|
if (y !== 1) return false;
|
|
441
|
-
const labelWidth = visibleWidth("[
|
|
549
|
+
const labelWidth = visibleWidth("[Esc]");
|
|
442
550
|
const labelStart = 2 + Math.max(0, innerW - labelWidth);
|
|
443
551
|
return x >= labelStart && x < 2 + innerW;
|
|
444
552
|
}
|
|
@@ -449,15 +557,24 @@ export class ConversationViewer implements Component {
|
|
|
449
557
|
const th = this.theme;
|
|
450
558
|
const innerW = Math.max(1, width - 4);
|
|
451
559
|
this.lastInnerW = innerW;
|
|
560
|
+
const contentWidth = this.transcriptWidth(innerW);
|
|
452
561
|
const lines: string[] = [];
|
|
453
562
|
const pad = (s: string, len: number) => s + " ".repeat(Math.max(0, len - visibleWidth(s)));
|
|
454
563
|
const row = (content: string) => th.fg("border", "│") + " " + truncateToWidth(pad(content, innerW), innerW, "...", true) + " " + th.fg("border", "│");
|
|
564
|
+
const contentRow = (content: string, lineIndex: number, totalLines: number, viewportHeight: number, offset: number) => {
|
|
565
|
+
const isTruncationNote = stripAnsi(content).includes("... (truncated,");
|
|
566
|
+
const text = isTruncationNote
|
|
567
|
+
? truncateToWidth(content, contentWidth, "...", true)
|
|
568
|
+
: truncateToWidth(pad(content, contentWidth), contentWidth, "...", true);
|
|
569
|
+
const rail = renderScrollbarCell(th, totalLines, viewportHeight, offset, lineIndex, false);
|
|
570
|
+
if (isTruncationNote) return th.fg("border", "│") + " " + text + th.fg("border", "│");
|
|
571
|
+
return th.fg("border", "│") + " " + text + rail + " " + th.fg("border", "│");
|
|
572
|
+
};
|
|
455
573
|
const hrTop = th.fg("border", `╭${"─".repeat(Math.max(0, width - 2))}╮`);
|
|
456
574
|
const hrBot = th.fg("border", `╰${"─".repeat(Math.max(0, width - 2))}╯`);
|
|
457
575
|
const hrMid = row(th.fg("dim", "─".repeat(innerW)));
|
|
458
576
|
|
|
459
577
|
lines.push(hrTop);
|
|
460
|
-
const name = getDisplayName(this.record.type);
|
|
461
578
|
const modeLabel = getPromptModeLabel(this.record.type);
|
|
462
579
|
const modeTag = modeLabel ? ` ${th.fg("dim", `(${modeLabel})`)}` : "";
|
|
463
580
|
const statusIcon = this.record.status === "running" ? th.fg("accent", "●") : this.record.status === "completed" ? th.fg("success", "✓") : this.record.status === "error" ? th.fg("error", "✗") : th.fg("dim", "○");
|
|
@@ -465,22 +582,28 @@ export class ConversationViewer implements Component {
|
|
|
465
582
|
const headerParts: string[] = [duration];
|
|
466
583
|
const toolUses = this.activity?.toolUses ?? this.record.toolUses;
|
|
467
584
|
if (toolUses > 0) headerParts.unshift(`${toolUses} tool${toolUses === 1 ? "" : "s"}`);
|
|
468
|
-
const
|
|
585
|
+
const usage = this.activity?.lifetimeUsage ?? this.record.lifetimeUsage;
|
|
586
|
+
const tokens = getLifetimeTotal(usage);
|
|
469
587
|
if (tokens > 0) {
|
|
470
|
-
const percent = getSessionContextPercent(this.activity?.session);
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
const
|
|
476
|
-
const
|
|
477
|
-
const
|
|
478
|
-
|
|
588
|
+
const percent = getSessionContextPercent(this.activity?.session ?? this.record.session);
|
|
589
|
+
const tokenText = formatSessionTokens(tokens, percent, th, this.record.compactionCount);
|
|
590
|
+
const costText = this.showCost ? formatCost(getLifetimeCost(usage)) : "";
|
|
591
|
+
headerParts.push(costText ? `${tokenText} ${costText}` : tokenText);
|
|
592
|
+
}
|
|
593
|
+
const styledName = renderAgentName(this.record.type, th, { bold: true, fallbackColor: "accent" });
|
|
594
|
+
const headerText = `${statusIcon} ${styledName}${modeTag} ${th.fg("muted", this.record.description)} ${th.fg("dim", "·")} ${fgPreservingNestedStyles(th, "dim", headerParts.join(" · "))}`;
|
|
595
|
+
const previewLabel = this.hoveredPreview ? th.fg("text", th.bold("[preview]")) : th.fg("dim", "[preview]");
|
|
596
|
+
const closeLabel = this.hoveredClose ? th.fg("text", th.bold("[Esc]")) : th.fg("dim", "[Esc]");
|
|
597
|
+
const actionsWidth = visibleWidth(previewLabel) + 1 + visibleWidth(closeLabel);
|
|
598
|
+
const headerLeft = truncateToWidth(headerText, Math.max(0, innerW - actionsWidth - 1), "", true);
|
|
599
|
+
const headerGap = Math.max(1, innerW - visibleWidth(headerLeft) - actionsWidth);
|
|
600
|
+
lines.push(row(headerLeft + " ".repeat(headerGap) + previewLabel + " " + closeLabel));
|
|
479
601
|
const invocationLine = this.invocationLine();
|
|
480
|
-
|
|
602
|
+
lines.push(row(invocationLine ?? ""));
|
|
481
603
|
lines.push(hrMid);
|
|
482
604
|
|
|
483
|
-
|
|
605
|
+
this.buildContentLines(contentWidth);
|
|
606
|
+
const contentLines = this.cachedLines;
|
|
484
607
|
const viewportHeight = this.viewportHeight();
|
|
485
608
|
const maxScroll = Math.max(0, contentLines.length - viewportHeight);
|
|
486
609
|
if (this.autoScroll) this.scrollOffset = maxScroll;
|
|
@@ -494,7 +617,7 @@ export class ConversationViewer implements Component {
|
|
|
494
617
|
const block = this.cachedMessageBlocks[this.currentMessageIndex()];
|
|
495
618
|
if (block) {
|
|
496
619
|
const header = renderConversationRoleHeader(block, this.theme);
|
|
497
|
-
displayed[0] = { text: ` ${header}`, plain: header, blockIndex: currentBlockIdx, railable: true };
|
|
620
|
+
displayed[0] = makeRenderLine({ text: ` ${header}`, plain: header, blockIndex: currentBlockIdx, railable: true });
|
|
498
621
|
}
|
|
499
622
|
}
|
|
500
623
|
for (let i = 0; i < displayed.length; i++) {
|
|
@@ -510,9 +633,14 @@ export class ConversationViewer implements Component {
|
|
|
510
633
|
const railGlyph = currentBlock?.kind === "tool" ? "▌" : "▎";
|
|
511
634
|
text = text.startsWith(" ") ? th.fg(railColor, railGlyph) + text.slice(1) : th.fg(railColor, railGlyph) + text;
|
|
512
635
|
}
|
|
513
|
-
|
|
636
|
+
const plainText = stripAnsi(text);
|
|
637
|
+
lines.push(plainText.includes("... (truncated,")
|
|
638
|
+
? th.fg("border", "│") + " " + text + th.fg("border", "│")
|
|
639
|
+
: contentRow(text, i, contentLines.length, viewportHeight, visibleStart));
|
|
640
|
+
}
|
|
641
|
+
for (let i = displayed.length; i < viewportHeight; i++) {
|
|
642
|
+
lines.push(contentRow("", i, contentLines.length, viewportHeight, visibleStart));
|
|
514
643
|
}
|
|
515
|
-
for (let i = displayed.length; i < viewportHeight; i++) lines.push(row(""));
|
|
516
644
|
|
|
517
645
|
lines.push(hrMid);
|
|
518
646
|
if (this.composer) {
|
|
@@ -529,14 +657,21 @@ export class ConversationViewer implements Component {
|
|
|
529
657
|
lines.push(row(truncateToWidth(th.fg("accent", query) + th.fg("dim", matchCount) + hints, innerW, "...", true)));
|
|
530
658
|
} else {
|
|
531
659
|
const actions: string[] = [];
|
|
532
|
-
if (this.canSteer()) actions.push(th.fg("dim", "e steer"));
|
|
660
|
+
if (this.canSteer()) actions.push(th.fg("dim", innerW < 100 ? "Enter steer (e steer)" : "e steer"));
|
|
533
661
|
if (this.isStoppable()) actions.push(this.stopArmed ? th.fg("error", "x again to STOP") : th.fg("dim", "x stop"));
|
|
534
662
|
const editHint = this.isLive() ? " O" : "";
|
|
535
|
-
const
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
663
|
+
const markdownHint = innerW < 100 ? `m ${MARKDOWN_MODE_LABELS[this.currentMarkdownMode()]}` : "";
|
|
664
|
+
const shortcutHints = innerW < 100
|
|
665
|
+
? [
|
|
666
|
+
`j/k scroll · J/K messages · [/] tools · w preview · g/G t ${markdownHint} M o${editHint} /n N q`,
|
|
667
|
+
`[/] tools · w preview · ${markdownHint} · M o${editHint} /n N q`,
|
|
668
|
+
`${markdownHint} · M o${editHint} /n N · Esc close`,
|
|
669
|
+
]
|
|
670
|
+
: [
|
|
671
|
+
`j/k scroll · J/K messages · [/] tools · w preview · g/G t M o${editHint} /n N q`,
|
|
672
|
+
`[/] tools · w preview · M o${editHint} /n N q`,
|
|
673
|
+
`[/] tools · w · M o${editHint} /n N q`,
|
|
674
|
+
];
|
|
540
675
|
const scrollPct = contentLines.length <= viewportHeight ? "100%" : `${Math.round(((visibleStart + viewportHeight) / contentLines.length) * 100)}%`;
|
|
541
676
|
const count = th.fg("dim", `${contentLines.length} lines · ${scrollPct}`);
|
|
542
677
|
const countWidth = visibleWidth(count);
|
|
@@ -577,6 +712,7 @@ export class ConversationViewer implements Component {
|
|
|
577
712
|
if (this.closed) return;
|
|
578
713
|
if (this.hasFocusedBlock) this.selectedBlockId = this.currentBlock()?.id;
|
|
579
714
|
this.blocks = formatConversationMessages(this.session.messages);
|
|
715
|
+
this.lastSessionKey = this.sessionKey();
|
|
580
716
|
this.timeline.setBlocks(this.blocks);
|
|
581
717
|
this.cacheVersion++;
|
|
582
718
|
this.invalidate();
|
|
@@ -595,7 +731,7 @@ export class ConversationViewer implements Component {
|
|
|
595
731
|
const end = Math.min(this.cachedLines.length, start + Math.max(0, range.height));
|
|
596
732
|
for (let index = start; index < end; index++) {
|
|
597
733
|
const line = snapshotLines[index];
|
|
598
|
-
if (line) this.cachedLines[index] = { ...line };
|
|
734
|
+
if (line) this.cachedLines[index] = makeRenderLine({ ...line });
|
|
599
735
|
}
|
|
600
736
|
}
|
|
601
737
|
}
|
|
@@ -613,46 +749,125 @@ export class ConversationViewer implements Component {
|
|
|
613
749
|
return Math.max(MIN_VIEWPORT, maxRows - this.chromeLines());
|
|
614
750
|
}
|
|
615
751
|
|
|
752
|
+
/** Full inner width includes the one column reserved for the transcript rail. */
|
|
753
|
+
private transcriptWidth(innerW: number): number {
|
|
754
|
+
return Math.max(1, innerW - 1);
|
|
755
|
+
}
|
|
756
|
+
|
|
616
757
|
private chromeLines(): number {
|
|
617
|
-
return CHROME_LINES_BASE +
|
|
758
|
+
return CHROME_LINES_BASE + 1 + (this.composer ? 1 : 0);
|
|
618
759
|
}
|
|
619
760
|
|
|
620
761
|
private invocationLine(): string | undefined {
|
|
621
762
|
const liveModel = this.isLive() ? this.record.session?.model : undefined;
|
|
622
|
-
const
|
|
763
|
+
const captured = this.record.invocation as (AgentRecord["invocation"] & {
|
|
764
|
+
effectiveModelName?: string;
|
|
765
|
+
effectiveModelId?: string;
|
|
766
|
+
effectiveThinking?: EffectiveThinkingLevel;
|
|
767
|
+
}) | undefined;
|
|
768
|
+
const invocation = captured
|
|
623
769
|
? {
|
|
624
|
-
...
|
|
625
|
-
...(
|
|
626
|
-
...(
|
|
770
|
+
...captured,
|
|
771
|
+
...(captured.modelName === undefined && captured.effectiveModelName !== undefined && { modelName: captured.effectiveModelName }),
|
|
772
|
+
...(captured.modelId === undefined && captured.effectiveModelId !== undefined && { modelId: captured.effectiveModelId }),
|
|
773
|
+
...(captured.thinking === undefined && captured.effectiveThinking !== undefined && { thinking: captured.effectiveThinking }),
|
|
774
|
+
...(liveModel && { modelName: liveModel.name ?? liveModel.id, modelId: `${liveModel.provider}/${liveModel.id}` }),
|
|
775
|
+
...(this.isLive() && { thinking: this.record.session?.thinkingLevel }),
|
|
627
776
|
}
|
|
628
|
-
: liveModel
|
|
629
|
-
? {
|
|
630
|
-
effectiveModelName: liveModel?.name ?? liveModel?.id,
|
|
631
|
-
effectiveThinking: this.record.session?.thinkingLevel,
|
|
632
|
-
}
|
|
777
|
+
: liveModel
|
|
778
|
+
? { modelName: liveModel.name ?? liveModel.id, modelId: `${liveModel.provider}/${liveModel.id}`, thinking: this.record.session?.thinkingLevel }
|
|
633
779
|
: undefined;
|
|
634
|
-
if (!invocation) return
|
|
635
|
-
const { modelName, tags } = buildInvocationTags(invocation);
|
|
636
|
-
const effectiveModelName =
|
|
637
|
-
|
|
638
|
-
|
|
780
|
+
if (!invocation) return undefined;
|
|
781
|
+
const { modelName, modelId, tags } = buildInvocationTags(invocation);
|
|
782
|
+
const effectiveModelName = modelId ?? modelName;
|
|
783
|
+
if (!effectiveModelName && tags.length === 0) return undefined;
|
|
784
|
+
const displayModel = captured?.effectiveModelName !== undefined
|
|
785
|
+
? `model: ${captured.effectiveModelName}`
|
|
786
|
+
: effectiveModelName;
|
|
787
|
+
return this.theme.fg("dim", ` ↳ ${[displayModel, ...tags].filter(Boolean).join(" · ")}`);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
private currentMarkdownMode(): ViewerMarkdownMode {
|
|
791
|
+
return this.markdownModeOverride ?? this.viewerMarkdown?.() ?? "assistant";
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
private cycleMarkdownMode(): void {
|
|
795
|
+
const current = this.currentMarkdownMode();
|
|
796
|
+
const next = MARKDOWN_MODES[(MARKDOWN_MODES.indexOf(current) + 1) % MARKDOWN_MODES.length]!;
|
|
797
|
+
this.markdownModeOverride = next;
|
|
798
|
+
this.onMarkdownMode?.(next);
|
|
799
|
+
this.timeline.setMarkdownMode(() => this.currentMarkdownMode());
|
|
800
|
+
this.invalidate();
|
|
801
|
+
this.tui.requestRender();
|
|
639
802
|
}
|
|
640
803
|
|
|
641
|
-
private
|
|
642
|
-
|
|
643
|
-
|
|
804
|
+
private sessionKey(): string {
|
|
805
|
+
return this.session.messages.map(message => {
|
|
806
|
+
const value = message as unknown as { role?: unknown; content?: unknown; output?: unknown; command?: unknown };
|
|
807
|
+
const content = value.content;
|
|
808
|
+
const summarize = (text: string): string => `${text.length}:${text.slice(0, 24)}:${text.slice(-24)}`;
|
|
809
|
+
const contentSummary = typeof content === "string"
|
|
810
|
+
? summarize(content)
|
|
811
|
+
: Array.isArray(content)
|
|
812
|
+
? content.map(part => typeof part === "object" && part && "text" in part && typeof part.text === "string" ? summarize(part.text) : "").join("|")
|
|
813
|
+
: "";
|
|
814
|
+
return `${String(value.role)}:${contentSummary}:${typeof value.output === "string" ? summarize(value.output) : ""}:${typeof value.command === "string" ? summarize(value.command) : ""}`;
|
|
815
|
+
}).join("\u0001");
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
private blockSignature(block: ConversationBlock): string {
|
|
819
|
+
let resultLength = 0;
|
|
820
|
+
const content = block.toolResult?.content;
|
|
821
|
+
if (typeof content === "string") resultLength = content.length;
|
|
822
|
+
else if (Array.isArray(content)) resultLength = content.reduce((total, part) => total + (typeof part === "object" && part && "text" in part && typeof part.text === "string" ? part.text.length : 0), 0);
|
|
823
|
+
return `${block.id}\u0000${block.markdown}\u0000${block.fullText}\u0000${block.toolLine ?? ""}\u0000${resultLength}`;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
private syncBlocksFromSession(): void {
|
|
827
|
+
const sessionKey = this.sessionKey();
|
|
828
|
+
if (sessionKey === this.lastSessionKey) return;
|
|
829
|
+
this.lastSessionKey = sessionKey;
|
|
830
|
+
const refreshed = formatConversationMessages(this.session.messages);
|
|
831
|
+
const sameShape = refreshed.length === this.blocks.length && refreshed.every((block, index) => block.id === this.blocks[index]?.id);
|
|
832
|
+
if (!sameShape) {
|
|
833
|
+
this.blocks = refreshed;
|
|
834
|
+
this.timeline.setBlocks(this.blocks);
|
|
835
|
+
this.cacheVersion++;
|
|
836
|
+
this.cachedVersion = -1;
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
let changed = false;
|
|
840
|
+
for (let index = 0; index < refreshed.length; index++) {
|
|
841
|
+
const current = this.blocks[index]!;
|
|
842
|
+
const next = refreshed[index]!;
|
|
843
|
+
if (this.blockSignature(current) !== this.blockSignature(next)) changed = true;
|
|
844
|
+
Object.assign(current, next);
|
|
845
|
+
}
|
|
846
|
+
if (changed) {
|
|
847
|
+
this.cacheVersion++;
|
|
848
|
+
this.cachedVersion = -1;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
private buildContentLines(width: number): PublicRenderLines {
|
|
853
|
+
if (width <= 0) return new PublicRenderLines([]);
|
|
854
|
+
this.syncBlocksFromSession();
|
|
855
|
+
if (this.cachedVersion === this.cacheVersion && this.cachedWidth === width) {
|
|
856
|
+
this.timeline.render(width);
|
|
857
|
+
return publicRenderLines(this.cachedLines);
|
|
858
|
+
}
|
|
644
859
|
this.timeline.setShowTools(this.showTools);
|
|
645
860
|
this.timeline.render(width);
|
|
646
861
|
const timelineSnapshot = this.timeline.getSnapshot();
|
|
647
862
|
const lines: RenderLine[] = timelineSnapshot.lines.map((line) => {
|
|
648
863
|
const rawText = typeof line?.text === "string" ? line.text : "";
|
|
649
864
|
const text = truncateToWidth(rawText, width, "", true);
|
|
650
|
-
return {
|
|
865
|
+
return makeRenderLine({
|
|
651
866
|
text,
|
|
652
867
|
plain: stripAnsi(text),
|
|
653
868
|
blockIndex: typeof line?.blockIndex === "number" ? line.blockIndex : -1,
|
|
654
869
|
...(line?.railable ? { railable: true } : {}),
|
|
655
|
-
};
|
|
870
|
+
});
|
|
656
871
|
});
|
|
657
872
|
this.cachedMessageStarts = [...timelineSnapshot.messageStarts];
|
|
658
873
|
this.cachedMessageBlocks = [...timelineSnapshot.messageBlocks];
|
|
@@ -672,13 +887,13 @@ export class ConversationViewer implements Component {
|
|
|
672
887
|
if (this.record.status === "running" && this.activity) {
|
|
673
888
|
const act = describeActivity(this.activity.activeTools, this.activity.responseText);
|
|
674
889
|
const text = `▍ ${act}`;
|
|
675
|
-
lines.push({ text: truncateToWidth(this.theme.fg("accent", "▍ ") + this.theme.fg("dim", act), width), plain: text, blockIndex: -1 });
|
|
890
|
+
lines.push(makeRenderLine({ text: truncateToWidth(this.theme.fg("accent", "▍ ") + this.theme.fg("dim", act), width), plain: text, blockIndex: -1 }));
|
|
676
891
|
}
|
|
677
892
|
this.cachedWidth = width;
|
|
678
893
|
this.cachedVersion = this.cacheVersion;
|
|
679
894
|
this.cachedLines = lines;
|
|
680
895
|
if (this.searchMode || this.searchQuery) this.recomputeMatches();
|
|
681
|
-
return lines;
|
|
896
|
+
return publicRenderLines(lines);
|
|
682
897
|
}
|
|
683
898
|
|
|
684
899
|
private currentMessageIndex(): number {
|
|
@@ -744,7 +959,7 @@ export class ConversationViewer implements Component {
|
|
|
744
959
|
private moveToolFocus(direction: 1 | -1): void {
|
|
745
960
|
if (!this.timeline.moveFocus(direction)) return;
|
|
746
961
|
const focused = this.timeline.getFocusedToolCallId();
|
|
747
|
-
this.buildContentLines(this.lastInnerW || 80);
|
|
962
|
+
this.buildContentLines(this.transcriptWidth(this.lastInnerW || 80));
|
|
748
963
|
const index = this.cachedMessageBlocks.findIndex((block) => block.toolCallId === focused);
|
|
749
964
|
if (index >= 0) {
|
|
750
965
|
this.setFocusFromMessageIndex(index);
|
|
@@ -755,7 +970,7 @@ export class ConversationViewer implements Component {
|
|
|
755
970
|
}
|
|
756
971
|
|
|
757
972
|
private focusNearbyTool(direction: 1 | -1): void {
|
|
758
|
-
this.buildContentLines(this.lastInnerW || 80);
|
|
973
|
+
this.buildContentLines(this.transcriptWidth(this.lastInnerW || 80));
|
|
759
974
|
const toolIndices = this.cachedMessageBlocks
|
|
760
975
|
.map((block, index) => block.kind === "tool" && block.toolCallId ? index : -1)
|
|
761
976
|
.filter((index): index is number => index >= 0);
|
|
@@ -786,7 +1001,7 @@ export class ConversationViewer implements Component {
|
|
|
786
1001
|
}
|
|
787
1002
|
|
|
788
1003
|
private openFocusedToolPreview(): void {
|
|
789
|
-
this.buildContentLines(this.lastInnerW || 80);
|
|
1004
|
+
this.buildContentLines(this.transcriptWidth(this.lastInnerW || 80));
|
|
790
1005
|
const focusedToolCallId = this.timeline.getFocusedToolCallId();
|
|
791
1006
|
const block = focusedToolCallId
|
|
792
1007
|
? this.cachedMessageBlocks.find((candidate) => candidate.toolCallId === focusedToolCallId)
|