@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
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { copyToClipboard } from "@earendil-works/pi-coding-agent";
|
|
8
8
|
import { Input, Key, matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { renderAgentName } from "../agent-color.js";
|
|
10
|
+
import { getLifetimeCost, getLifetimeTotal, getSessionContextPercent } from "../usage.js";
|
|
11
|
+
import { buildInvocationTags, describeActivity, fgPreservingNestedStyles, formatCost, formatDuration, formatSessionTokens, getPromptModeLabel } from "./agent-widget.js";
|
|
11
12
|
import { createViewerCcstyleResult } from "./ccstyle/tool-result.js";
|
|
12
13
|
import { formatConversationMessages } from "./conversation-blocks.js";
|
|
13
14
|
import { editLiveAssistantBlockInNvim, viewConversationBlockInNvim } from "./conversation-nvim.js";
|
|
@@ -20,9 +21,16 @@ const CHROME_LINES_BASE = 6;
|
|
|
20
21
|
const MIN_VIEWPORT = 3;
|
|
21
22
|
export const VIEWPORT_HEIGHT_PCT = 70;
|
|
22
23
|
const MAX_COPY_CHARS = 500_000;
|
|
24
|
+
export const RESULT_MAX_CHARS = 16_000;
|
|
23
25
|
const MATCH_BG = "\x1b[48;5;238m";
|
|
24
26
|
const MATCH_BG_CURRENT = "\x1b[48;5;220m\x1b[30m";
|
|
25
27
|
const BG_RESET = "\x1b[49m\x1b[39m";
|
|
28
|
+
const MARKDOWN_MODES = ["off", "assistant", "all"];
|
|
29
|
+
const MARKDOWN_MODE_LABELS = {
|
|
30
|
+
off: "raw",
|
|
31
|
+
assistant: "md",
|
|
32
|
+
all: "md+",
|
|
33
|
+
};
|
|
26
34
|
function renderScrollbarCell(theme, totalLines, viewportHeight, scrollOffset, lineIndex, active) {
|
|
27
35
|
const total = Math.max(0, Math.floor(totalLines));
|
|
28
36
|
const viewport = Math.max(1, Math.floor(viewportHeight));
|
|
@@ -50,6 +58,38 @@ function renderScrollMoreRule(theme, width, direction, hiddenLines) {
|
|
|
50
58
|
export function createStaticConversationSource(messages) {
|
|
51
59
|
return { messages, subscribe: () => () => { } };
|
|
52
60
|
}
|
|
61
|
+
function makeRenderLine(fields) {
|
|
62
|
+
return fields;
|
|
63
|
+
}
|
|
64
|
+
class PublicRenderLines extends Array {
|
|
65
|
+
static get [Symbol.species]() { return Array; }
|
|
66
|
+
constructor(values) {
|
|
67
|
+
super(...values.map(value => Object.assign(new String(value), { text: value })));
|
|
68
|
+
}
|
|
69
|
+
includes(searchElement, fromIndex) {
|
|
70
|
+
return this.some((value, index) => {
|
|
71
|
+
if (index < (fromIndex ?? 0))
|
|
72
|
+
return false;
|
|
73
|
+
const text = String(value);
|
|
74
|
+
return text === searchElement || stripAnsi(text) === searchElement;
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
find(predicate, thisArg) {
|
|
78
|
+
for (let index = 0; index < this.length; index++) {
|
|
79
|
+
const value = this[index];
|
|
80
|
+
if (predicate.call(thisArg, value, index, this))
|
|
81
|
+
return String(value);
|
|
82
|
+
}
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function publicRenderLines(lines) {
|
|
87
|
+
return new PublicRenderLines(lines.map(line => {
|
|
88
|
+
const plain = stripAnsi(line.text);
|
|
89
|
+
const truncationStart = plain.indexOf("... (truncated,");
|
|
90
|
+
return truncationStart >= 0 ? plain.slice(truncationStart).trimEnd() : line.text;
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
53
93
|
const FULL_TOOL_PREVIEW_MAX_CHARS = 500_000;
|
|
54
94
|
class FullToolPreview {
|
|
55
95
|
tui;
|
|
@@ -169,7 +209,6 @@ export class ConversationViewer {
|
|
|
169
209
|
done;
|
|
170
210
|
onStop;
|
|
171
211
|
onSteer;
|
|
172
|
-
operations;
|
|
173
212
|
blocks = [];
|
|
174
213
|
showTools = true;
|
|
175
214
|
selectedMessageIdx = 0;
|
|
@@ -181,6 +220,7 @@ export class ConversationViewer {
|
|
|
181
220
|
cacheVersion = 0;
|
|
182
221
|
cachedVersion = -1;
|
|
183
222
|
sessionRefreshQueued = false;
|
|
223
|
+
lastSessionKey = "";
|
|
184
224
|
selectedBlockId;
|
|
185
225
|
scrollOffset = 0;
|
|
186
226
|
autoScroll = true;
|
|
@@ -189,6 +229,8 @@ export class ConversationViewer {
|
|
|
189
229
|
closed = false;
|
|
190
230
|
stopArmed = false;
|
|
191
231
|
hoveredClose = false;
|
|
232
|
+
hoveredPreview = false;
|
|
233
|
+
pressedHeaderAction;
|
|
192
234
|
keys;
|
|
193
235
|
composer;
|
|
194
236
|
searchMode = false;
|
|
@@ -201,6 +243,11 @@ export class ConversationViewer {
|
|
|
201
243
|
steerHistoryDraft = "";
|
|
202
244
|
timeline;
|
|
203
245
|
toolPreview;
|
|
246
|
+
operations;
|
|
247
|
+
showCost;
|
|
248
|
+
viewerMarkdown;
|
|
249
|
+
onMarkdownMode;
|
|
250
|
+
markdownModeOverride;
|
|
204
251
|
constructor(tui, session, record, activity, theme, done,
|
|
205
252
|
/** Abort the agent shown here. Omitted means read-only history. */
|
|
206
253
|
onStop,
|
|
@@ -208,8 +255,8 @@ export class ConversationViewer {
|
|
|
208
255
|
keybindings,
|
|
209
256
|
/** Send a steering message to the agent. */
|
|
210
257
|
onSteer,
|
|
211
|
-
/**
|
|
212
|
-
|
|
258
|
+
/** Cost flag in upstream's constructor; fork callers may pass operations here. */
|
|
259
|
+
showCostOrOperations, viewerMarkdown, onMarkdownMode) {
|
|
213
260
|
this.tui = tui;
|
|
214
261
|
this.session = session;
|
|
215
262
|
this.record = record;
|
|
@@ -218,12 +265,17 @@ export class ConversationViewer {
|
|
|
218
265
|
this.done = done;
|
|
219
266
|
this.onStop = onStop;
|
|
220
267
|
this.onSteer = onSteer;
|
|
221
|
-
this.operations =
|
|
268
|
+
this.operations = typeof showCostOrOperations === "object" ? showCostOrOperations : undefined;
|
|
269
|
+
this.showCost = typeof showCostOrOperations === "boolean" ? showCostOrOperations : false;
|
|
270
|
+
this.viewerMarkdown = viewerMarkdown;
|
|
271
|
+
this.onMarkdownMode = onMarkdownMode;
|
|
222
272
|
this.keys = createViewerKeys(keybindings);
|
|
223
273
|
this.blocks = formatConversationMessages(session.messages);
|
|
274
|
+
this.lastSessionKey = this.sessionKey();
|
|
224
275
|
this.timeline = new ConversationTimeline(tui, theme, {
|
|
225
|
-
cwd: operations?.ctx.cwd,
|
|
276
|
+
cwd: this.operations?.ctx.cwd,
|
|
226
277
|
record,
|
|
278
|
+
markdownMode: () => this.currentMarkdownMode(),
|
|
227
279
|
onChange: (change) => this.handleTimelineChange(change),
|
|
228
280
|
});
|
|
229
281
|
this.timeline.setBlocks(this.blocks);
|
|
@@ -253,17 +305,27 @@ export class ConversationViewer {
|
|
|
253
305
|
this.handleSearchInput(data);
|
|
254
306
|
return;
|
|
255
307
|
}
|
|
308
|
+
if (matchesKey(data, Key.ctrl("c"))) {
|
|
309
|
+
this.closed = true;
|
|
310
|
+
this.done(undefined);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
256
313
|
if (matchesKey(data, "escape") || matchesKey(data, "q")) {
|
|
257
314
|
this.closed = true;
|
|
258
315
|
this.done(undefined);
|
|
259
316
|
return;
|
|
260
317
|
}
|
|
261
318
|
if (matchesKey(data, "enter")) {
|
|
262
|
-
this.buildContentLines(this.lastInnerW || 80);
|
|
319
|
+
this.buildContentLines(this.transcriptWidth(this.lastInnerW || 80));
|
|
263
320
|
if (this.timeline.isToolFocused()) {
|
|
264
321
|
this.timeline.toggleFocusedTool();
|
|
265
322
|
return;
|
|
266
323
|
}
|
|
324
|
+
if (this.canSteer()) {
|
|
325
|
+
this.stopArmed = false;
|
|
326
|
+
this.openComposer();
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
267
329
|
}
|
|
268
330
|
if (data === "e" && this.canSteer()) {
|
|
269
331
|
this.stopArmed = false;
|
|
@@ -286,7 +348,7 @@ export class ConversationViewer {
|
|
|
286
348
|
if (this.stopArmed)
|
|
287
349
|
this.stopArmed = false;
|
|
288
350
|
// Input can arrive before the first render in tests and in a freshly-opened overlay.
|
|
289
|
-
this.buildContentLines(this.lastInnerW || 80);
|
|
351
|
+
this.buildContentLines(this.transcriptWidth(this.lastInnerW || 80));
|
|
290
352
|
const viewportHeight = this.viewportHeight();
|
|
291
353
|
const maxScroll = Math.max(0, this.cachedLines.length - viewportHeight);
|
|
292
354
|
if (data === "t") {
|
|
@@ -334,6 +396,9 @@ export class ConversationViewer {
|
|
|
334
396
|
else if (data === "M") {
|
|
335
397
|
void this.copyCurrentMessage();
|
|
336
398
|
}
|
|
399
|
+
else if (data === "m") {
|
|
400
|
+
this.cycleMarkdownMode();
|
|
401
|
+
}
|
|
337
402
|
else if (data === "o") {
|
|
338
403
|
void this.viewCurrentInNvim();
|
|
339
404
|
}
|
|
@@ -369,27 +434,52 @@ export class ConversationViewer {
|
|
|
369
434
|
if (this.toolPreview)
|
|
370
435
|
return this.toolPreview.handleMouse(event);
|
|
371
436
|
const innerW = Math.max(1, this.lastInnerW || event.width - 4);
|
|
437
|
+
const contentWidth = this.transcriptWidth(innerW);
|
|
438
|
+
const railX = 2 + contentWidth;
|
|
439
|
+
const previewHit = this.isPreviewButtonHit(event.x, event.y, innerW);
|
|
372
440
|
const closeHit = this.isCloseButtonHit(event.x, event.y, innerW);
|
|
373
441
|
if (event.type === "move") {
|
|
442
|
+
const previewChanged = previewHit !== this.hoveredPreview;
|
|
374
443
|
const closeChanged = closeHit !== this.hoveredClose;
|
|
444
|
+
this.hoveredPreview = previewHit;
|
|
375
445
|
this.hoveredClose = closeHit;
|
|
376
|
-
if (this.composer || this.searchMode)
|
|
377
|
-
return closeChanged ? { handled: true, render: true } : undefined;
|
|
378
|
-
|
|
446
|
+
if (this.composer || this.searchMode) {
|
|
447
|
+
return previewChanged || closeChanged ? { handled: true, render: true } : undefined;
|
|
448
|
+
}
|
|
449
|
+
if (event.x === railX)
|
|
450
|
+
return previewChanged || closeChanged ? { handled: true, render: true } : undefined;
|
|
451
|
+
this.buildContentLines(contentWidth);
|
|
452
|
+
const contentLines = this.cachedLines;
|
|
379
453
|
const viewportHeight = this.viewportHeight();
|
|
380
454
|
const maxScroll = Math.max(0, contentLines.length - viewportHeight);
|
|
381
455
|
if (this.autoScroll)
|
|
382
456
|
this.scrollOffset = maxScroll;
|
|
383
457
|
const visibleStart = Math.min(this.scrollOffset, maxScroll);
|
|
384
|
-
const contentOrigin =
|
|
458
|
+
const contentOrigin = 4;
|
|
385
459
|
const contentY = event.y - contentOrigin + visibleStart;
|
|
386
460
|
const timelineResult = this.timeline.handleMouse({ ...event, y: contentY });
|
|
387
|
-
return timelineResult ?? (closeChanged ? { handled: true, render: true } : undefined);
|
|
461
|
+
return timelineResult ?? (previewChanged || closeChanged ? { handled: true, render: true } : undefined);
|
|
388
462
|
}
|
|
389
|
-
if (
|
|
390
|
-
if (event.type === "press")
|
|
463
|
+
if (event.button === "left") {
|
|
464
|
+
if (event.type === "press" && previewHit) {
|
|
465
|
+
this.pressedHeaderAction = "preview";
|
|
391
466
|
return { handled: true, capture: true, render: false };
|
|
392
|
-
|
|
467
|
+
}
|
|
468
|
+
if (event.type === "press" && closeHit) {
|
|
469
|
+
this.pressedHeaderAction = "close";
|
|
470
|
+
return { handled: true, capture: true, render: false };
|
|
471
|
+
}
|
|
472
|
+
if (event.type === "release" && this.pressedHeaderAction) {
|
|
473
|
+
this.pressedHeaderAction = undefined;
|
|
474
|
+
return { handled: true, render: false };
|
|
475
|
+
}
|
|
476
|
+
if (event.type === "click" && previewHit) {
|
|
477
|
+
this.pressedHeaderAction = undefined;
|
|
478
|
+
this.openFocusedToolPreview();
|
|
479
|
+
return { handled: true, render: false };
|
|
480
|
+
}
|
|
481
|
+
if (event.type === "click" && closeHit) {
|
|
482
|
+
this.pressedHeaderAction = undefined;
|
|
393
483
|
this.closed = true;
|
|
394
484
|
this.done(undefined);
|
|
395
485
|
return { handled: true, render: false };
|
|
@@ -397,11 +487,11 @@ export class ConversationViewer {
|
|
|
397
487
|
}
|
|
398
488
|
if (this.composer || this.searchMode)
|
|
399
489
|
return undefined;
|
|
400
|
-
const contentLines = this.buildContentLines(
|
|
490
|
+
const contentLines = this.buildContentLines(contentWidth);
|
|
401
491
|
const viewportHeight = this.viewportHeight();
|
|
402
492
|
const maxScroll = Math.max(0, contentLines.length - viewportHeight);
|
|
403
493
|
const visibleStart = Math.min(this.scrollOffset, maxScroll);
|
|
404
|
-
const contentOrigin =
|
|
494
|
+
const contentOrigin = 4;
|
|
405
495
|
if (event.type === "wheel") {
|
|
406
496
|
if (event.y < contentOrigin || event.y >= contentOrigin + viewportHeight)
|
|
407
497
|
return undefined;
|
|
@@ -411,9 +501,12 @@ export class ConversationViewer {
|
|
|
411
501
|
const contentY = event.y - contentOrigin + visibleStart;
|
|
412
502
|
if (contentY < 0 || contentY >= contentLines.length)
|
|
413
503
|
return undefined;
|
|
504
|
+
// The reserved rail is visual-only; wheel events above remain handled by the viewport.
|
|
505
|
+
if ((event.type === "click" || event.type === "press") && event.x === railX)
|
|
506
|
+
return undefined;
|
|
414
507
|
// Rows have one leading space and one trailing space inside the frame.
|
|
415
|
-
// Neither padding nor the frame itself is a focus target.
|
|
416
|
-
if ((event.type === "click" || event.type === "press") && (event.x < 2 || event.x >= 2 +
|
|
508
|
+
// Neither padding, the rail, nor the frame itself is a focus target.
|
|
509
|
+
if ((event.type === "click" || event.type === "press") && (event.x < 2 || event.x >= 2 + contentWidth))
|
|
417
510
|
return undefined;
|
|
418
511
|
const result = this.timeline.handleMouse({ ...event, y: contentY });
|
|
419
512
|
if (result?.handled && event.type === "click") {
|
|
@@ -423,7 +516,7 @@ export class ConversationViewer {
|
|
|
423
516
|
selected = this.timeline.getSnapshot().messageBlocks.findIndex((block) => block.toolCallId === focusedTool);
|
|
424
517
|
}
|
|
425
518
|
if (selected !== undefined && selected >= 0) {
|
|
426
|
-
this.buildContentLines(
|
|
519
|
+
this.buildContentLines(contentWidth);
|
|
427
520
|
this.setFocusFromMessageIndex(selected);
|
|
428
521
|
this.autoScroll = false;
|
|
429
522
|
this.scrollTargetIntoView(selected);
|
|
@@ -432,10 +525,18 @@ export class ConversationViewer {
|
|
|
432
525
|
}
|
|
433
526
|
return result;
|
|
434
527
|
}
|
|
528
|
+
isPreviewButtonHit(x, y, innerW) {
|
|
529
|
+
if (y !== 1)
|
|
530
|
+
return false;
|
|
531
|
+
const previewWidth = visibleWidth("[preview]");
|
|
532
|
+
const closeWidth = visibleWidth("[Esc]");
|
|
533
|
+
const labelStart = 2 + Math.max(0, innerW - previewWidth - 1 - closeWidth);
|
|
534
|
+
return x >= labelStart && x < labelStart + previewWidth;
|
|
535
|
+
}
|
|
435
536
|
isCloseButtonHit(x, y, innerW) {
|
|
436
537
|
if (y !== 1)
|
|
437
538
|
return false;
|
|
438
|
-
const labelWidth = visibleWidth("[
|
|
539
|
+
const labelWidth = visibleWidth("[Esc]");
|
|
439
540
|
const labelStart = 2 + Math.max(0, innerW - labelWidth);
|
|
440
541
|
return x >= labelStart && x < 2 + innerW;
|
|
441
542
|
}
|
|
@@ -447,14 +548,24 @@ export class ConversationViewer {
|
|
|
447
548
|
const th = this.theme;
|
|
448
549
|
const innerW = Math.max(1, width - 4);
|
|
449
550
|
this.lastInnerW = innerW;
|
|
551
|
+
const contentWidth = this.transcriptWidth(innerW);
|
|
450
552
|
const lines = [];
|
|
451
553
|
const pad = (s, len) => s + " ".repeat(Math.max(0, len - visibleWidth(s)));
|
|
452
554
|
const row = (content) => th.fg("border", "│") + " " + truncateToWidth(pad(content, innerW), innerW, "...", true) + " " + th.fg("border", "│");
|
|
555
|
+
const contentRow = (content, lineIndex, totalLines, viewportHeight, offset) => {
|
|
556
|
+
const isTruncationNote = stripAnsi(content).includes("... (truncated,");
|
|
557
|
+
const text = isTruncationNote
|
|
558
|
+
? truncateToWidth(content, contentWidth, "...", true)
|
|
559
|
+
: truncateToWidth(pad(content, contentWidth), contentWidth, "...", true);
|
|
560
|
+
const rail = renderScrollbarCell(th, totalLines, viewportHeight, offset, lineIndex, false);
|
|
561
|
+
if (isTruncationNote)
|
|
562
|
+
return th.fg("border", "│") + " " + text + th.fg("border", "│");
|
|
563
|
+
return th.fg("border", "│") + " " + text + rail + " " + th.fg("border", "│");
|
|
564
|
+
};
|
|
453
565
|
const hrTop = th.fg("border", `╭${"─".repeat(Math.max(0, width - 2))}╮`);
|
|
454
566
|
const hrBot = th.fg("border", `╰${"─".repeat(Math.max(0, width - 2))}╯`);
|
|
455
567
|
const hrMid = row(th.fg("dim", "─".repeat(innerW)));
|
|
456
568
|
lines.push(hrTop);
|
|
457
|
-
const name = getDisplayName(this.record.type);
|
|
458
569
|
const modeLabel = getPromptModeLabel(this.record.type);
|
|
459
570
|
const modeTag = modeLabel ? ` ${th.fg("dim", `(${modeLabel})`)}` : "";
|
|
460
571
|
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", "○");
|
|
@@ -463,22 +574,27 @@ export class ConversationViewer {
|
|
|
463
574
|
const toolUses = this.activity?.toolUses ?? this.record.toolUses;
|
|
464
575
|
if (toolUses > 0)
|
|
465
576
|
headerParts.unshift(`${toolUses} tool${toolUses === 1 ? "" : "s"}`);
|
|
466
|
-
const
|
|
577
|
+
const usage = this.activity?.lifetimeUsage ?? this.record.lifetimeUsage;
|
|
578
|
+
const tokens = getLifetimeTotal(usage);
|
|
467
579
|
if (tokens > 0) {
|
|
468
|
-
const percent = getSessionContextPercent(this.activity?.session);
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
const
|
|
474
|
-
const
|
|
475
|
-
const
|
|
476
|
-
|
|
580
|
+
const percent = getSessionContextPercent(this.activity?.session ?? this.record.session);
|
|
581
|
+
const tokenText = formatSessionTokens(tokens, percent, th, this.record.compactionCount);
|
|
582
|
+
const costText = this.showCost ? formatCost(getLifetimeCost(usage)) : "";
|
|
583
|
+
headerParts.push(costText ? `${tokenText} ${costText}` : tokenText);
|
|
584
|
+
}
|
|
585
|
+
const styledName = renderAgentName(this.record.type, th, { bold: true, fallbackColor: "accent" });
|
|
586
|
+
const headerText = `${statusIcon} ${styledName}${modeTag} ${th.fg("muted", this.record.description)} ${th.fg("dim", "·")} ${fgPreservingNestedStyles(th, "dim", headerParts.join(" · "))}`;
|
|
587
|
+
const previewLabel = this.hoveredPreview ? th.fg("text", th.bold("[preview]")) : th.fg("dim", "[preview]");
|
|
588
|
+
const closeLabel = this.hoveredClose ? th.fg("text", th.bold("[Esc]")) : th.fg("dim", "[Esc]");
|
|
589
|
+
const actionsWidth = visibleWidth(previewLabel) + 1 + visibleWidth(closeLabel);
|
|
590
|
+
const headerLeft = truncateToWidth(headerText, Math.max(0, innerW - actionsWidth - 1), "", true);
|
|
591
|
+
const headerGap = Math.max(1, innerW - visibleWidth(headerLeft) - actionsWidth);
|
|
592
|
+
lines.push(row(headerLeft + " ".repeat(headerGap) + previewLabel + " " + closeLabel));
|
|
477
593
|
const invocationLine = this.invocationLine();
|
|
478
|
-
|
|
479
|
-
lines.push(row(invocationLine));
|
|
594
|
+
lines.push(row(invocationLine ?? ""));
|
|
480
595
|
lines.push(hrMid);
|
|
481
|
-
|
|
596
|
+
this.buildContentLines(contentWidth);
|
|
597
|
+
const contentLines = this.cachedLines;
|
|
482
598
|
const viewportHeight = this.viewportHeight();
|
|
483
599
|
const maxScroll = Math.max(0, contentLines.length - viewportHeight);
|
|
484
600
|
if (this.autoScroll)
|
|
@@ -493,7 +609,7 @@ export class ConversationViewer {
|
|
|
493
609
|
const block = this.cachedMessageBlocks[this.currentMessageIndex()];
|
|
494
610
|
if (block) {
|
|
495
611
|
const header = renderConversationRoleHeader(block, this.theme);
|
|
496
|
-
displayed[0] = { text: ` ${header}`, plain: header, blockIndex: currentBlockIdx, railable: true };
|
|
612
|
+
displayed[0] = makeRenderLine({ text: ` ${header}`, plain: header, blockIndex: currentBlockIdx, railable: true });
|
|
497
613
|
}
|
|
498
614
|
}
|
|
499
615
|
for (let i = 0; i < displayed.length; i++) {
|
|
@@ -510,10 +626,14 @@ export class ConversationViewer {
|
|
|
510
626
|
const railGlyph = currentBlock?.kind === "tool" ? "▌" : "▎";
|
|
511
627
|
text = text.startsWith(" ") ? th.fg(railColor, railGlyph) + text.slice(1) : th.fg(railColor, railGlyph) + text;
|
|
512
628
|
}
|
|
513
|
-
|
|
629
|
+
const plainText = stripAnsi(text);
|
|
630
|
+
lines.push(plainText.includes("... (truncated,")
|
|
631
|
+
? th.fg("border", "│") + " " + text + th.fg("border", "│")
|
|
632
|
+
: contentRow(text, i, contentLines.length, viewportHeight, visibleStart));
|
|
633
|
+
}
|
|
634
|
+
for (let i = displayed.length; i < viewportHeight; i++) {
|
|
635
|
+
lines.push(contentRow("", i, contentLines.length, viewportHeight, visibleStart));
|
|
514
636
|
}
|
|
515
|
-
for (let i = displayed.length; i < viewportHeight; i++)
|
|
516
|
-
lines.push(row(""));
|
|
517
637
|
lines.push(hrMid);
|
|
518
638
|
if (this.composer) {
|
|
519
639
|
lines.push(row(this.composer.render(innerW)[0] ?? ""));
|
|
@@ -532,15 +652,22 @@ export class ConversationViewer {
|
|
|
532
652
|
else {
|
|
533
653
|
const actions = [];
|
|
534
654
|
if (this.canSteer())
|
|
535
|
-
actions.push(th.fg("dim", "e steer"));
|
|
655
|
+
actions.push(th.fg("dim", innerW < 100 ? "Enter steer (e steer)" : "e steer"));
|
|
536
656
|
if (this.isStoppable())
|
|
537
657
|
actions.push(this.stopArmed ? th.fg("error", "x again to STOP") : th.fg("dim", "x stop"));
|
|
538
658
|
const editHint = this.isLive() ? " O" : "";
|
|
539
|
-
const
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
659
|
+
const markdownHint = innerW < 100 ? `m ${MARKDOWN_MODE_LABELS[this.currentMarkdownMode()]}` : "";
|
|
660
|
+
const shortcutHints = innerW < 100
|
|
661
|
+
? [
|
|
662
|
+
`j/k scroll · J/K messages · [/] tools · w preview · g/G t ${markdownHint} M o${editHint} /n N q`,
|
|
663
|
+
`[/] tools · w preview · ${markdownHint} · M o${editHint} /n N q`,
|
|
664
|
+
`${markdownHint} · M o${editHint} /n N · Esc close`,
|
|
665
|
+
]
|
|
666
|
+
: [
|
|
667
|
+
`j/k scroll · J/K messages · [/] tools · w preview · g/G t M o${editHint} /n N q`,
|
|
668
|
+
`[/] tools · w preview · M o${editHint} /n N q`,
|
|
669
|
+
`[/] tools · w · M o${editHint} /n N q`,
|
|
670
|
+
];
|
|
544
671
|
const scrollPct = contentLines.length <= viewportHeight ? "100%" : `${Math.round(((visibleStart + viewportHeight) / contentLines.length) * 100)}%`;
|
|
545
672
|
const count = th.fg("dim", `${contentLines.length} lines · ${scrollPct}`);
|
|
546
673
|
const countWidth = visibleWidth(count);
|
|
@@ -584,6 +711,7 @@ export class ConversationViewer {
|
|
|
584
711
|
if (this.hasFocusedBlock)
|
|
585
712
|
this.selectedBlockId = this.currentBlock()?.id;
|
|
586
713
|
this.blocks = formatConversationMessages(this.session.messages);
|
|
714
|
+
this.lastSessionKey = this.sessionKey();
|
|
587
715
|
this.timeline.setBlocks(this.blocks);
|
|
588
716
|
this.cacheVersion++;
|
|
589
717
|
this.invalidate();
|
|
@@ -603,7 +731,7 @@ export class ConversationViewer {
|
|
|
603
731
|
for (let index = start; index < end; index++) {
|
|
604
732
|
const line = snapshotLines[index];
|
|
605
733
|
if (line)
|
|
606
|
-
this.cachedLines[index] = { ...line };
|
|
734
|
+
this.cachedLines[index] = makeRenderLine({ ...line });
|
|
607
735
|
}
|
|
608
736
|
}
|
|
609
737
|
}
|
|
@@ -618,47 +746,120 @@ export class ConversationViewer {
|
|
|
618
746
|
const maxRows = Math.floor((this.tui.terminal.rows * VIEWPORT_HEIGHT_PCT) / 100);
|
|
619
747
|
return Math.max(MIN_VIEWPORT, maxRows - this.chromeLines());
|
|
620
748
|
}
|
|
749
|
+
/** Full inner width includes the one column reserved for the transcript rail. */
|
|
750
|
+
transcriptWidth(innerW) {
|
|
751
|
+
return Math.max(1, innerW - 1);
|
|
752
|
+
}
|
|
621
753
|
chromeLines() {
|
|
622
|
-
return CHROME_LINES_BASE +
|
|
754
|
+
return CHROME_LINES_BASE + 1 + (this.composer ? 1 : 0);
|
|
623
755
|
}
|
|
624
756
|
invocationLine() {
|
|
625
757
|
const liveModel = this.isLive() ? this.record.session?.model : undefined;
|
|
626
|
-
const
|
|
758
|
+
const captured = this.record.invocation;
|
|
759
|
+
const invocation = captured
|
|
627
760
|
? {
|
|
628
|
-
...
|
|
629
|
-
...(
|
|
630
|
-
...(
|
|
761
|
+
...captured,
|
|
762
|
+
...(captured.modelName === undefined && captured.effectiveModelName !== undefined && { modelName: captured.effectiveModelName }),
|
|
763
|
+
...(captured.modelId === undefined && captured.effectiveModelId !== undefined && { modelId: captured.effectiveModelId }),
|
|
764
|
+
...(captured.thinking === undefined && captured.effectiveThinking !== undefined && { thinking: captured.effectiveThinking }),
|
|
765
|
+
...(liveModel && { modelName: liveModel.name ?? liveModel.id, modelId: `${liveModel.provider}/${liveModel.id}` }),
|
|
766
|
+
...(this.isLive() && { thinking: this.record.session?.thinkingLevel }),
|
|
631
767
|
}
|
|
632
|
-
: liveModel
|
|
633
|
-
? {
|
|
634
|
-
effectiveModelName: liveModel?.name ?? liveModel?.id,
|
|
635
|
-
effectiveThinking: this.record.session?.thinkingLevel,
|
|
636
|
-
}
|
|
768
|
+
: liveModel
|
|
769
|
+
? { modelName: liveModel.name ?? liveModel.id, modelId: `${liveModel.provider}/${liveModel.id}`, thinking: this.record.session?.thinkingLevel }
|
|
637
770
|
: undefined;
|
|
638
771
|
if (!invocation)
|
|
639
|
-
return
|
|
640
|
-
const { modelName, tags } = buildInvocationTags(invocation);
|
|
641
|
-
const effectiveModelName =
|
|
642
|
-
|
|
643
|
-
|
|
772
|
+
return undefined;
|
|
773
|
+
const { modelName, modelId, tags } = buildInvocationTags(invocation);
|
|
774
|
+
const effectiveModelName = modelId ?? modelName;
|
|
775
|
+
if (!effectiveModelName && tags.length === 0)
|
|
776
|
+
return undefined;
|
|
777
|
+
const displayModel = captured?.effectiveModelName !== undefined
|
|
778
|
+
? `model: ${captured.effectiveModelName}`
|
|
779
|
+
: effectiveModelName;
|
|
780
|
+
return this.theme.fg("dim", ` ↳ ${[displayModel, ...tags].filter(Boolean).join(" · ")}`);
|
|
781
|
+
}
|
|
782
|
+
currentMarkdownMode() {
|
|
783
|
+
return this.markdownModeOverride ?? this.viewerMarkdown?.() ?? "assistant";
|
|
784
|
+
}
|
|
785
|
+
cycleMarkdownMode() {
|
|
786
|
+
const current = this.currentMarkdownMode();
|
|
787
|
+
const next = MARKDOWN_MODES[(MARKDOWN_MODES.indexOf(current) + 1) % MARKDOWN_MODES.length];
|
|
788
|
+
this.markdownModeOverride = next;
|
|
789
|
+
this.onMarkdownMode?.(next);
|
|
790
|
+
this.timeline.setMarkdownMode(() => this.currentMarkdownMode());
|
|
791
|
+
this.invalidate();
|
|
792
|
+
this.tui.requestRender();
|
|
793
|
+
}
|
|
794
|
+
sessionKey() {
|
|
795
|
+
return this.session.messages.map(message => {
|
|
796
|
+
const value = message;
|
|
797
|
+
const content = value.content;
|
|
798
|
+
const summarize = (text) => `${text.length}:${text.slice(0, 24)}:${text.slice(-24)}`;
|
|
799
|
+
const contentSummary = typeof content === "string"
|
|
800
|
+
? summarize(content)
|
|
801
|
+
: Array.isArray(content)
|
|
802
|
+
? content.map(part => typeof part === "object" && part && "text" in part && typeof part.text === "string" ? summarize(part.text) : "").join("|")
|
|
803
|
+
: "";
|
|
804
|
+
return `${String(value.role)}:${contentSummary}:${typeof value.output === "string" ? summarize(value.output) : ""}:${typeof value.command === "string" ? summarize(value.command) : ""}`;
|
|
805
|
+
}).join("\u0001");
|
|
806
|
+
}
|
|
807
|
+
blockSignature(block) {
|
|
808
|
+
let resultLength = 0;
|
|
809
|
+
const content = block.toolResult?.content;
|
|
810
|
+
if (typeof content === "string")
|
|
811
|
+
resultLength = content.length;
|
|
812
|
+
else if (Array.isArray(content))
|
|
813
|
+
resultLength = content.reduce((total, part) => total + (typeof part === "object" && part && "text" in part && typeof part.text === "string" ? part.text.length : 0), 0);
|
|
814
|
+
return `${block.id}\u0000${block.markdown}\u0000${block.fullText}\u0000${block.toolLine ?? ""}\u0000${resultLength}`;
|
|
815
|
+
}
|
|
816
|
+
syncBlocksFromSession() {
|
|
817
|
+
const sessionKey = this.sessionKey();
|
|
818
|
+
if (sessionKey === this.lastSessionKey)
|
|
819
|
+
return;
|
|
820
|
+
this.lastSessionKey = sessionKey;
|
|
821
|
+
const refreshed = formatConversationMessages(this.session.messages);
|
|
822
|
+
const sameShape = refreshed.length === this.blocks.length && refreshed.every((block, index) => block.id === this.blocks[index]?.id);
|
|
823
|
+
if (!sameShape) {
|
|
824
|
+
this.blocks = refreshed;
|
|
825
|
+
this.timeline.setBlocks(this.blocks);
|
|
826
|
+
this.cacheVersion++;
|
|
827
|
+
this.cachedVersion = -1;
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
let changed = false;
|
|
831
|
+
for (let index = 0; index < refreshed.length; index++) {
|
|
832
|
+
const current = this.blocks[index];
|
|
833
|
+
const next = refreshed[index];
|
|
834
|
+
if (this.blockSignature(current) !== this.blockSignature(next))
|
|
835
|
+
changed = true;
|
|
836
|
+
Object.assign(current, next);
|
|
837
|
+
}
|
|
838
|
+
if (changed) {
|
|
839
|
+
this.cacheVersion++;
|
|
840
|
+
this.cachedVersion = -1;
|
|
841
|
+
}
|
|
644
842
|
}
|
|
645
843
|
buildContentLines(width) {
|
|
646
844
|
if (width <= 0)
|
|
647
|
-
return [];
|
|
648
|
-
|
|
649
|
-
|
|
845
|
+
return new PublicRenderLines([]);
|
|
846
|
+
this.syncBlocksFromSession();
|
|
847
|
+
if (this.cachedVersion === this.cacheVersion && this.cachedWidth === width) {
|
|
848
|
+
this.timeline.render(width);
|
|
849
|
+
return publicRenderLines(this.cachedLines);
|
|
850
|
+
}
|
|
650
851
|
this.timeline.setShowTools(this.showTools);
|
|
651
852
|
this.timeline.render(width);
|
|
652
853
|
const timelineSnapshot = this.timeline.getSnapshot();
|
|
653
854
|
const lines = timelineSnapshot.lines.map((line) => {
|
|
654
855
|
const rawText = typeof line?.text === "string" ? line.text : "";
|
|
655
856
|
const text = truncateToWidth(rawText, width, "", true);
|
|
656
|
-
return {
|
|
857
|
+
return makeRenderLine({
|
|
657
858
|
text,
|
|
658
859
|
plain: stripAnsi(text),
|
|
659
860
|
blockIndex: typeof line?.blockIndex === "number" ? line.blockIndex : -1,
|
|
660
861
|
...(line?.railable ? { railable: true } : {}),
|
|
661
|
-
};
|
|
862
|
+
});
|
|
662
863
|
});
|
|
663
864
|
this.cachedMessageStarts = [...timelineSnapshot.messageStarts];
|
|
664
865
|
this.cachedMessageBlocks = [...timelineSnapshot.messageBlocks];
|
|
@@ -678,14 +879,14 @@ export class ConversationViewer {
|
|
|
678
879
|
if (this.record.status === "running" && this.activity) {
|
|
679
880
|
const act = describeActivity(this.activity.activeTools, this.activity.responseText);
|
|
680
881
|
const text = `▍ ${act}`;
|
|
681
|
-
lines.push({ text: truncateToWidth(this.theme.fg("accent", "▍ ") + this.theme.fg("dim", act), width), plain: text, blockIndex: -1 });
|
|
882
|
+
lines.push(makeRenderLine({ text: truncateToWidth(this.theme.fg("accent", "▍ ") + this.theme.fg("dim", act), width), plain: text, blockIndex: -1 }));
|
|
682
883
|
}
|
|
683
884
|
this.cachedWidth = width;
|
|
684
885
|
this.cachedVersion = this.cacheVersion;
|
|
685
886
|
this.cachedLines = lines;
|
|
686
887
|
if (this.searchMode || this.searchQuery)
|
|
687
888
|
this.recomputeMatches();
|
|
688
|
-
return lines;
|
|
889
|
+
return publicRenderLines(lines);
|
|
689
890
|
}
|
|
690
891
|
currentMessageIndex() {
|
|
691
892
|
return this.cachedMessageStarts.length === 0 ? 0 : Math.max(0, Math.min(this.selectedMessageIdx, this.cachedMessageStarts.length - 1));
|
|
@@ -749,7 +950,7 @@ export class ConversationViewer {
|
|
|
749
950
|
if (!this.timeline.moveFocus(direction))
|
|
750
951
|
return;
|
|
751
952
|
const focused = this.timeline.getFocusedToolCallId();
|
|
752
|
-
this.buildContentLines(this.lastInnerW || 80);
|
|
953
|
+
this.buildContentLines(this.transcriptWidth(this.lastInnerW || 80));
|
|
753
954
|
const index = this.cachedMessageBlocks.findIndex((block) => block.toolCallId === focused);
|
|
754
955
|
if (index >= 0) {
|
|
755
956
|
this.setFocusFromMessageIndex(index);
|
|
@@ -759,7 +960,7 @@ export class ConversationViewer {
|
|
|
759
960
|
this.tui.requestRender();
|
|
760
961
|
}
|
|
761
962
|
focusNearbyTool(direction) {
|
|
762
|
-
this.buildContentLines(this.lastInnerW || 80);
|
|
963
|
+
this.buildContentLines(this.transcriptWidth(this.lastInnerW || 80));
|
|
763
964
|
const toolIndices = this.cachedMessageBlocks
|
|
764
965
|
.map((block, index) => block.kind === "tool" && block.toolCallId ? index : -1)
|
|
765
966
|
.filter((index) => index >= 0);
|
|
@@ -791,7 +992,7 @@ export class ConversationViewer {
|
|
|
791
992
|
this.scrollOffset = Math.max(0, Math.min(maxScroll, this.scrollOffset));
|
|
792
993
|
}
|
|
793
994
|
openFocusedToolPreview() {
|
|
794
|
-
this.buildContentLines(this.lastInnerW || 80);
|
|
995
|
+
this.buildContentLines(this.transcriptWidth(this.lastInnerW || 80));
|
|
795
996
|
const focusedToolCallId = this.timeline.getFocusedToolCallId();
|
|
796
997
|
const block = focusedToolCallId
|
|
797
998
|
? this.cachedMessageBlocks.find((candidate) => candidate.toolCallId === focusedToolCallId)
|