@oh-my-pi/pi-tui 18.13.2 → 18.14.0
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 +40 -54
- package/README.md +34 -34
- package/dist/types/app-keybindings.d.ts +15 -0
- package/dist/types/apps/session-picker.d.ts +5 -0
- package/dist/types/charts/chart-plan.d.ts +9 -7
- package/dist/types/chat/assistant-message.d.ts +15 -3
- package/dist/types/chat/bash-execution.d.ts +2 -0
- package/dist/types/chat/display-preferences.d.ts +2 -0
- package/dist/types/chat/extension-types.d.ts +31 -0
- package/dist/types/chat/reply-write.d.ts +48 -0
- package/dist/types/chat/table-chart.d.ts +4 -3
- package/dist/types/chat/tool-execution.d.ts +1 -2
- package/dist/types/chat/transcript-outline.d.ts +7 -0
- package/dist/types/chat/transcript-render-helpers.d.ts +2 -0
- package/dist/types/chrome/index.d.ts +1 -0
- package/dist/types/chrome/transcript-notice.d.ts +35 -0
- package/dist/types/chrome/transcript-status.d.ts +20 -8
- package/dist/types/components/editor.d.ts +8 -0
- package/dist/types/components/markdown.d.ts +7 -0
- package/dist/types/native/blobs.d.ts +2 -1
- package/dist/types/native/picker.d.ts +3 -3
- package/dist/types/overlays/annotation-overlay.d.ts +7 -1
- package/dist/types/overlays/annotation-types.d.ts +28 -0
- package/dist/types/overlays/login-dialog.d.ts +5 -1
- package/dist/types/overlays/model-browser.d.ts +10 -1
- package/dist/types/overlays/oauth-selector.d.ts +23 -0
- package/dist/types/overlays/plan-review-overlay.d.ts +2 -0
- package/dist/types/overlays/rewind-selector.d.ts +1 -1
- package/dist/types/overlays/session-selector.d.ts +22 -2
- package/dist/types/prompt/composer.d.ts +41 -4
- package/dist/types/prompt/interactive-context-helpers.d.ts +10 -3
- package/dist/types/prompt/welcome.d.ts +6 -2
- package/dist/types/render/extension-links.d.ts +65 -0
- package/dist/types/render/hyperlink.d.ts +25 -7
- package/dist/types/render/index.d.ts +1 -0
- package/dist/types/setup/scenes/model.d.ts +1 -1
- package/dist/types/setup/scenes/outro.d.ts +5 -4
- package/dist/types/setup/scenes/sign-in.d.ts +13 -8
- package/dist/types/setup/scenes/splash.d.ts +4 -4
- package/dist/types/setup/scenes/types.d.ts +16 -1
- package/dist/types/setup/startup-splash.d.ts +1 -1
- package/dist/types/setup/wizard-overlay.d.ts +11 -7
- package/dist/types/setup/wizard.d.ts +5 -1
- package/dist/types/status-line/component.d.ts +1 -1
- package/dist/types/status-line/context-usage.d.ts +28 -6
- package/dist/types/status-line/host.d.ts +6 -2
- package/dist/types/theme/theme.d.ts +0 -23
- package/dist/types/tools/bash-interactive.d.ts +6 -0
- package/dist/types/tools/bash.d.ts +2 -0
- package/dist/types/tools/default-renderer.d.ts +6 -4
- package/dist/types/tools/irc.d.ts +6 -0
- package/dist/types/tools/json-tree.d.ts +23 -4
- package/dist/types/tools/lsp.d.ts +2 -0
- package/dist/types/tools/mcp.d.ts +6 -5
- package/dist/types/tools/native-view.d.ts +1 -1
- package/dist/types/tools/renderer.d.ts +2 -0
- package/dist/types/tools/streaming-output.d.ts +11 -0
- package/dist/types/tools/task.d.ts +9 -1
- package/dist/types/tools/url-scheme-host.d.ts +17 -0
- package/dist/types/tools/yield.d.ts +1 -1
- package/dist/types/tui.d.ts +7 -2
- package/package.json +10 -15
- package/src/app-keybindings.ts +15 -0
- package/src/apps/session-picker.ts +7 -0
- package/src/charts/chart-plan.ts +20 -50
- package/src/chat/advisor-message.ts +2 -0
- package/src/chat/assistant-message.ts +137 -19
- package/src/chat/background-tan-message.ts +4 -0
- package/src/chat/bash-execution.ts +6 -0
- package/src/chat/chat-transcript-builder.ts +58 -28
- package/src/chat/compaction-summary-message.ts +2 -1
- package/src/chat/display-preferences.ts +3 -0
- package/src/chat/extension-types.ts +34 -0
- package/src/chat/reply-write.ts +127 -0
- package/src/chat/table-chart.ts +10 -4
- package/src/chat/tool-execution.ts +20 -13
- package/src/chat/transcript-outline.ts +27 -0
- package/src/chat/transcript-render-helpers.ts +66 -24
- package/src/chrome/index.ts +1 -0
- package/src/chrome/transcript-notice.ts +83 -0
- package/src/chrome/transcript-status.ts +29 -29
- package/src/components/editor.ts +10 -1
- package/src/components/markdown.ts +116 -6
- package/src/hotkeys-markdown.ts +1 -0
- package/src/native/backend.ts +5 -66
- package/src/native/blobs.ts +3 -2
- package/src/native/picker.ts +3 -3
- package/src/overlays/advisor-config.ts +1 -10
- package/src/overlays/annotation-overlay.ts +125 -37
- package/src/overlays/annotation-types.ts +24 -0
- package/src/overlays/hook-selector.ts +2 -2
- package/src/overlays/login-dialog.ts +31 -5
- package/src/overlays/model-browser.ts +37 -0
- package/src/overlays/model-hub.ts +1 -7
- package/src/overlays/model-picker.ts +1 -10
- package/src/overlays/oauth-selector.ts +44 -6
- package/src/overlays/plan-review-overlay.ts +13 -2
- package/src/overlays/rewind-selector.ts +29 -12
- package/src/overlays/session-selector.ts +305 -39
- package/src/overlays/usage-dashboard.ts +5 -12
- package/src/prompt/composer.ts +213 -5
- package/src/prompt/custom-editor.ts +6 -1
- package/src/prompt/emoji-autocomplete.ts +0 -2
- package/src/prompt/interactive-context-helpers.ts +29 -4
- package/src/prompt/welcome.ts +31 -45
- package/src/render/extension-links.ts +214 -0
- package/src/render/hyperlink.ts +65 -9
- package/src/render/index.ts +1 -0
- package/src/setup/scenes/composer.ts +2 -0
- package/src/setup/scenes/glyph.ts +2 -0
- package/src/setup/scenes/model.ts +192 -29
- package/src/setup/scenes/outro.ts +30 -17
- package/src/setup/scenes/sign-in.ts +196 -400
- package/src/setup/scenes/splash.ts +25 -21
- package/src/setup/scenes/theme.ts +2 -0
- package/src/setup/scenes/types.ts +18 -1
- package/src/setup/startup-splash.ts +8 -7
- package/src/setup/wizard-overlay.ts +164 -33
- package/src/setup/wizard.ts +11 -2
- package/src/status-line/component.ts +4 -10
- package/src/status-line/context-usage.ts +72 -21
- package/src/status-line/host.ts +6 -2
- package/src/terminal.ts +24 -2
- package/src/theme/defaults.json +1 -0
- package/src/theme/loader.ts +1 -1
- package/src/theme/theme.ts +2 -75
- package/src/tools/bash-interactive.ts +6 -1
- package/src/tools/bash.ts +2 -0
- package/src/tools/default-renderer.ts +9 -6
- package/src/tools/eval.ts +14 -4
- package/src/tools/irc.ts +6 -0
- package/src/tools/job-widget.ts +13 -1
- package/src/tools/json-tree.ts +132 -42
- package/src/tools/lsp.ts +36 -15
- package/src/tools/mcp.ts +20 -12
- package/src/tools/native-view.ts +1 -1
- package/src/tools/renderer.ts +2 -0
- package/src/tools/streaming-output.ts +41 -0
- package/src/tools/task.ts +59 -17
- package/src/tools/url-scheme-host.ts +21 -0
- package/src/tools/yield.ts +18 -9
- package/src/tui.ts +43 -15
- package/THIRD-PARTY-NOTICES.txt +0 -22901
- package/dist/types/theme/defaults/index.d.ts +0 -10289
- package/src/theme/defaults/alabaster.json +0 -103
- package/src/theme/defaults/amethyst.json +0 -106
- package/src/theme/defaults/anthracite.json +0 -103
- package/src/theme/defaults/basalt.json +0 -101
- package/src/theme/defaults/birch.json +0 -105
- package/src/theme/defaults/dark-abyss.json +0 -101
- package/src/theme/defaults/dark-arctic.json +0 -114
- package/src/theme/defaults/dark-aurora.json +0 -105
- package/src/theme/defaults/dark-catppuccin.json +0 -117
- package/src/theme/defaults/dark-cavern.json +0 -101
- package/src/theme/defaults/dark-celestial.json +0 -116
- package/src/theme/defaults/dark-copper.json +0 -105
- package/src/theme/defaults/dark-cosmos.json +0 -100
- package/src/theme/defaults/dark-cyberpunk.json +0 -112
- package/src/theme/defaults/dark-dracula.json +0 -108
- package/src/theme/defaults/dark-eclipse.json +0 -101
- package/src/theme/defaults/dark-ember.json +0 -105
- package/src/theme/defaults/dark-equinox.json +0 -100
- package/src/theme/defaults/dark-forest.json +0 -106
- package/src/theme/defaults/dark-github.json +0 -115
- package/src/theme/defaults/dark-gruvbox.json +0 -122
- package/src/theme/defaults/dark-lavender.json +0 -105
- package/src/theme/defaults/dark-lunar.json +0 -99
- package/src/theme/defaults/dark-midnight.json +0 -105
- package/src/theme/defaults/dark-monochrome.json +0 -104
- package/src/theme/defaults/dark-monokai.json +0 -108
- package/src/theme/defaults/dark-nebula.json +0 -100
- package/src/theme/defaults/dark-neon-noir.json +0 -110
- package/src/theme/defaults/dark-nord.json +0 -108
- package/src/theme/defaults/dark-ocean.json +0 -111
- package/src/theme/defaults/dark-one.json +0 -110
- package/src/theme/defaults/dark-poimandres.json +0 -125
- package/src/theme/defaults/dark-rainforest.json +0 -101
- package/src/theme/defaults/dark-reef.json +0 -101
- package/src/theme/defaults/dark-retro.json +0 -102
- package/src/theme/defaults/dark-rose-pine.json +0 -106
- package/src/theme/defaults/dark-sakura.json +0 -105
- package/src/theme/defaults/dark-slate.json +0 -105
- package/src/theme/defaults/dark-solarized.json +0 -107
- package/src/theme/defaults/dark-solstice.json +0 -100
- package/src/theme/defaults/dark-starfall.json +0 -101
- package/src/theme/defaults/dark-sunset.json +0 -109
- package/src/theme/defaults/dark-swamp.json +0 -100
- package/src/theme/defaults/dark-synthwave.json +0 -113
- package/src/theme/defaults/dark-taiga.json +0 -101
- package/src/theme/defaults/dark-terminal.json +0 -105
- package/src/theme/defaults/dark-tokyo-night.json +0 -111
- package/src/theme/defaults/dark-tundra.json +0 -101
- package/src/theme/defaults/dark-twilight.json +0 -101
- package/src/theme/defaults/dark-volcanic.json +0 -101
- package/src/theme/defaults/graphite.json +0 -102
- package/src/theme/defaults/index.ts +0 -203
- package/src/theme/defaults/light-arctic.json +0 -117
- package/src/theme/defaults/light-aurora-day.json +0 -101
- package/src/theme/defaults/light-canyon.json +0 -101
- package/src/theme/defaults/light-catppuccin.json +0 -116
- package/src/theme/defaults/light-cirrus.json +0 -100
- package/src/theme/defaults/light-coral.json +0 -105
- package/src/theme/defaults/light-cyberpunk.json +0 -106
- package/src/theme/defaults/light-dawn.json +0 -100
- package/src/theme/defaults/light-dunes.json +0 -101
- package/src/theme/defaults/light-eucalyptus.json +0 -105
- package/src/theme/defaults/light-forest.json +0 -110
- package/src/theme/defaults/light-frost.json +0 -105
- package/src/theme/defaults/light-github.json +0 -125
- package/src/theme/defaults/light-glacier.json +0 -101
- package/src/theme/defaults/light-gruvbox.json +0 -118
- package/src/theme/defaults/light-haze.json +0 -100
- package/src/theme/defaults/light-honeycomb.json +0 -105
- package/src/theme/defaults/light-lagoon.json +0 -101
- package/src/theme/defaults/light-lavender.json +0 -105
- package/src/theme/defaults/light-meadow.json +0 -101
- package/src/theme/defaults/light-mint.json +0 -105
- package/src/theme/defaults/light-monochrome.json +0 -111
- package/src/theme/defaults/light-ocean.json +0 -109
- package/src/theme/defaults/light-one.json +0 -109
- package/src/theme/defaults/light-opal.json +0 -101
- package/src/theme/defaults/light-orchard.json +0 -101
- package/src/theme/defaults/light-paper.json +0 -105
- package/src/theme/defaults/light-poimandres.json +0 -125
- package/src/theme/defaults/light-prism.json +0 -100
- package/src/theme/defaults/light-retro.json +0 -108
- package/src/theme/defaults/light-sand.json +0 -105
- package/src/theme/defaults/light-savanna.json +0 -101
- package/src/theme/defaults/light-solarized.json +0 -112
- package/src/theme/defaults/light-soleil.json +0 -100
- package/src/theme/defaults/light-sunset.json +0 -109
- package/src/theme/defaults/light-synthwave.json +0 -108
- package/src/theme/defaults/light-tokyo-night.json +0 -121
- package/src/theme/defaults/light-wetland.json +0 -101
- package/src/theme/defaults/light-zenith.json +0 -99
- package/src/theme/defaults/limestone.json +0 -104
- package/src/theme/defaults/mahogany.json +0 -107
- package/src/theme/defaults/marble.json +0 -103
- package/src/theme/defaults/obsidian.json +0 -101
- package/src/theme/defaults/onyx.json +0 -101
- package/src/theme/defaults/pearl.json +0 -103
- package/src/theme/defaults/porcelain.json +0 -101
- package/src/theme/defaults/quartz.json +0 -106
- package/src/theme/defaults/sandstone.json +0 -105
- package/src/theme/defaults/titanium.json +0 -100
|
@@ -65,8 +65,9 @@ import {
|
|
|
65
65
|
routeLateDiagnostics,
|
|
66
66
|
} from "./late-diagnostics-message";
|
|
67
67
|
import { groupedReadUsageCallIds, ReadToolGroupComponent, readArgsCollapseIntoGroup } from "./read-tool-group";
|
|
68
|
+
import { isReplyWrite, ReplyWriteComponent, replyDisplayMessage, toolResultReplies } from "./reply-write";
|
|
68
69
|
import { SkillMessageComponent } from "./skill-message";
|
|
69
|
-
import { ToolExecutionComponent } from "./tool-execution";
|
|
70
|
+
import { ToolExecutionComponent, type ToolExecutionHandle } from "./tool-execution";
|
|
70
71
|
import { TranscriptContainer } from "../chrome/transcript-container";
|
|
71
72
|
import { createUsageRowBlock, TurnUsageTally, turnElapsedMs } from "../overlays/usage-row";
|
|
72
73
|
import { CollapsedSyntheticMessageComponent, UserMessageComponent } from "./user-message";
|
|
@@ -90,7 +91,7 @@ export interface ChatTranscriptBuilderDeps {
|
|
|
90
91
|
|
|
91
92
|
export class ChatTranscriptBuilder {
|
|
92
93
|
readonly container = new TranscriptContainer();
|
|
93
|
-
#pendingTools = new Map<string,
|
|
94
|
+
#pendingTools = new Map<string, ToolExecutionHandle>();
|
|
94
95
|
#readArgs = new Map<string, Record<string, unknown>>();
|
|
95
96
|
#readGroup: ReadToolGroupComponent | null = null;
|
|
96
97
|
#pendingUsage: Usage | undefined;
|
|
@@ -203,6 +204,15 @@ export class ChatTranscriptBuilder {
|
|
|
203
204
|
this.#expandables.push(component);
|
|
204
205
|
}
|
|
205
206
|
|
|
207
|
+
/** Append assistant prose (a reply, a post-tool segment, or an `out://` reply write) with the transcript's display preferences. */
|
|
208
|
+
#mountProse(component: AssistantMessageComponent): void {
|
|
209
|
+
component.setImagesVisible(displayPreferences.showImages);
|
|
210
|
+
component.setToolResultImagesVisible(!displayPreferences.hideToolActivity);
|
|
211
|
+
component.setTableChartsVisible(this.#deps.tableCharts !== false);
|
|
212
|
+
this.#trackExpandable(component);
|
|
213
|
+
this.container.addChild(component);
|
|
214
|
+
}
|
|
215
|
+
|
|
206
216
|
/** A `wait` showing all-running is displaced by the next `wait` call. */
|
|
207
217
|
#resolveWaitingPoll(nextToolName?: string): void {
|
|
208
218
|
const previous = this.#waitingPoll;
|
|
@@ -417,12 +427,8 @@ export class ChatTranscriptBuilder {
|
|
|
417
427
|
this.#deps.linkTargets,
|
|
418
428
|
expandThinkingBlocks,
|
|
419
429
|
);
|
|
420
|
-
assistantComponent.setImagesVisible(displayPreferences.showImages);
|
|
421
|
-
assistantComponent.setToolResultImagesVisible(!displayPreferences.hideToolActivity);
|
|
422
|
-
assistantComponent.setTableChartsVisible(this.#deps.tableCharts !== false);
|
|
423
|
-
this.#trackExpandable(assistantComponent);
|
|
424
430
|
assistantComponent.pickReactionTarget(this.container.children);
|
|
425
|
-
this
|
|
431
|
+
this.#mountProse(assistantComponent);
|
|
426
432
|
let lastAssistantComponent = assistantComponent;
|
|
427
433
|
|
|
428
434
|
if (displayPreferences.cacheMissMarker) {
|
|
@@ -456,11 +462,7 @@ export class ChatTranscriptBuilder {
|
|
|
456
462
|
this.#deps.linkTargets,
|
|
457
463
|
expandThinkingBlocks,
|
|
458
464
|
);
|
|
459
|
-
component
|
|
460
|
-
component.setToolResultImagesVisible(!displayPreferences.hideToolActivity);
|
|
461
|
-
component.setTableChartsVisible(this.#deps.tableCharts !== false);
|
|
462
|
-
this.#trackExpandable(component);
|
|
463
|
-
this.container.addChild(component);
|
|
465
|
+
this.#mountProse(component);
|
|
464
466
|
lastAssistantComponent = component;
|
|
465
467
|
};
|
|
466
468
|
|
|
@@ -492,22 +494,37 @@ export class ChatTranscriptBuilder {
|
|
|
492
494
|
|
|
493
495
|
this.#readGroup?.seal();
|
|
494
496
|
this.#readGroup = null;
|
|
495
|
-
const
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
{
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
this.#
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
497
|
+
const builtIn = this.#deps.isBuiltInTool?.(content.name) ?? true;
|
|
498
|
+
let component: ToolExecutionHandle;
|
|
499
|
+
if (builtIn && isReplyWrite(content.name, content.arguments) === true) {
|
|
500
|
+
const reply = new ReplyWriteComponent({
|
|
501
|
+
requestRender: () => this.#deps.requestRender(),
|
|
502
|
+
imageBudget: this.#deps.ui.imageBudget,
|
|
503
|
+
linkTargets: this.#deps.linkTargets,
|
|
504
|
+
});
|
|
505
|
+
reply.updateArgs(content.arguments);
|
|
506
|
+
reply.setArgsComplete();
|
|
507
|
+
this.#mountProse(reply);
|
|
508
|
+
component = reply;
|
|
509
|
+
} else {
|
|
510
|
+
const card = new ToolExecutionComponent(
|
|
511
|
+
content.name,
|
|
512
|
+
content.arguments,
|
|
513
|
+
{
|
|
514
|
+
useBuiltInRenderer: builtIn,
|
|
515
|
+
// Stable ids and Kitty placeholder cells keep images anchored
|
|
516
|
+
// while the transcript viewport scrolls and reflows.
|
|
517
|
+
showImages: displayPreferences.showImages,
|
|
518
|
+
},
|
|
519
|
+
this.#deps.getTool?.(content.name),
|
|
520
|
+
this.#deps.ui,
|
|
521
|
+
this.#deps.cwd,
|
|
522
|
+
content.id,
|
|
523
|
+
);
|
|
524
|
+
this.#trackExpandable(card);
|
|
525
|
+
this.container.addChild(card);
|
|
526
|
+
component = card;
|
|
527
|
+
}
|
|
511
528
|
|
|
512
529
|
if (hasErrorStop && errorMessage) {
|
|
513
530
|
component.updateResult(
|
|
@@ -549,6 +566,19 @@ export class ChatTranscriptBuilder {
|
|
|
549
566
|
this.#readArgs.delete(message.toolCallId);
|
|
550
567
|
return;
|
|
551
568
|
}
|
|
569
|
+
for (const reply of toolResultReplies(message.details)) {
|
|
570
|
+
this.#mountProse(
|
|
571
|
+
new AssistantMessageComponent(
|
|
572
|
+
replyDisplayMessage(reply),
|
|
573
|
+
false,
|
|
574
|
+
() => this.#deps.requestRender(),
|
|
575
|
+
[],
|
|
576
|
+
this.#deps.ui.imageBudget,
|
|
577
|
+
true,
|
|
578
|
+
this.#deps.linkTargets,
|
|
579
|
+
),
|
|
580
|
+
);
|
|
581
|
+
}
|
|
552
582
|
if (!pending) return;
|
|
553
583
|
pending.updateResult(message, false, message.toolCallId);
|
|
554
584
|
this.#pendingTools.delete(message.toolCallId);
|
|
@@ -10,6 +10,7 @@ import type { TspSpan } from "@oh-my-pi/pi-wire";
|
|
|
10
10
|
import { md, node, span, text } from "../native/describe";
|
|
11
11
|
import { type NativeChild, type NativeNode, type NativeUiEvent, rootToggleExpanded } from "../native/node";
|
|
12
12
|
import { Memo } from "../native/memo";
|
|
13
|
+
import { noticeRow } from "../chrome/transcript-notice";
|
|
13
14
|
|
|
14
15
|
/** Divider labels per compaction method; unknown/legacy methods fall back to "compacted". */
|
|
15
16
|
const COMPACTION_METHOD_LABELS: Record<string, string> = {
|
|
@@ -237,7 +238,7 @@ function compactionNativeDetail(message: CompactionSummaryMessage): NativeChild[
|
|
|
237
238
|
? `Summary · ${frames} snapcompact frame${frames === 1 ? "" : "s"} attached`
|
|
238
239
|
: "Summary of the earlier conversation";
|
|
239
240
|
const detail: NativeChild[] = [text([span(caption, "dim")], { role: "omp.compaction.caption", key: "caption" })];
|
|
240
|
-
if (message.warning) detail.push(
|
|
241
|
+
if (message.warning) detail.push(noticeRow("warning", message.warning, { key: "warning" }));
|
|
241
242
|
detail.push(md(message.summary, { key: "summary" }));
|
|
242
243
|
return detail;
|
|
243
244
|
}
|
|
@@ -6,6 +6,8 @@ export interface ChatTranscriptDisplayPreferences {
|
|
|
6
6
|
cacheMissMarker: boolean;
|
|
7
7
|
showTokenUsage: boolean;
|
|
8
8
|
showTurnTime: boolean;
|
|
9
|
+
/** Max rows a collapsed advisor card shows below its header; 0 = no cap. */
|
|
10
|
+
advisorCollapsedLines: number;
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
/** Current transcript display preferences. */
|
|
@@ -16,6 +18,7 @@ export const chatTranscriptDisplayPreferences: ChatTranscriptDisplayPreferences
|
|
|
16
18
|
cacheMissMarker: false,
|
|
17
19
|
showTokenUsage: false,
|
|
18
20
|
showTurnTime: false,
|
|
21
|
+
advisorCollapsedLines: 0,
|
|
19
22
|
};
|
|
20
23
|
|
|
21
24
|
/** Apply host display preferences without pulling settings into the renderer. */
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AssistantMessage } from "@oh-my-pi/pi-ai";
|
|
1
2
|
import type { Component, TUI } from "../tui";
|
|
2
3
|
import type { Theme } from "../theme/theme";
|
|
3
4
|
import type { CustomMessage, HookMessage } from "./messages";
|
|
@@ -30,6 +31,39 @@ export type AssistantThinkingRenderer = (
|
|
|
30
31
|
theme: Theme,
|
|
31
32
|
) => Component | undefined;
|
|
32
33
|
|
|
34
|
+
export interface AssistantTextRenderContext {
|
|
35
|
+
/**
|
|
36
|
+
* The message being drawn; `message.timestamp` identifies the reply for `ctx.ui.rerenderMessage()`.
|
|
37
|
+
* After a tool call this is only the segment drawn there, so its `content` does not hold the whole reply.
|
|
38
|
+
* A deep copy: editing it, at any depth, cannot change the stored reply.
|
|
39
|
+
*/
|
|
40
|
+
message: AssistantMessage;
|
|
41
|
+
/**
|
|
42
|
+
* The text block's index in the full reply's `content`, also when the reply is drawn in segments
|
|
43
|
+
* around tool calls; not an index into `message.content` for a segment. It counts the content as
|
|
44
|
+
* currently stored: a transcript rebuild that drops tool calls left without a result shifts later
|
|
45
|
+
* blocks down, so do not persist it as a permanent key.
|
|
46
|
+
*/
|
|
47
|
+
contentIndex: number;
|
|
48
|
+
/** The block's markdown as drawn so far, after any earlier renderer. */
|
|
49
|
+
text: string;
|
|
50
|
+
/** True while the block may still grow; false for its final render, a block sealed at a tool call, and later redraws. */
|
|
51
|
+
streaming: boolean;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Display-only transform of one assistant text block: returns the markdown to draw, or `undefined`
|
|
56
|
+
* to draw `context.text` unchanged. The session, exports and model context keep the original text.
|
|
57
|
+
*/
|
|
58
|
+
export type AssistantTextRenderer = (context: AssistantTextRenderContext) => string | undefined;
|
|
59
|
+
|
|
60
|
+
/** Extension text renderers for one assistant component, and where its content starts in the full reply. */
|
|
61
|
+
export interface AssistantTextRendering {
|
|
62
|
+
renderers: readonly AssistantTextRenderer[];
|
|
63
|
+
/** Index in the full reply's `content` of this component's first block; a segment after a tool call starts past it. */
|
|
64
|
+
contentOffset: number;
|
|
65
|
+
}
|
|
66
|
+
|
|
33
67
|
export interface HookMessageRenderOptions {
|
|
34
68
|
/** Whether the view is expanded */
|
|
35
69
|
expanded: boolean;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Replies: text the agent sends to a scheme whose writes are `reply`-scoped (`out://`)
|
|
3
|
+
* is its mid-turn message to the user, drawn as assistant prose so it can answer
|
|
4
|
+
* without ending its turn.
|
|
5
|
+
* - A `write` call to a reply URL draws as {@link ReplyWriteComponent}, streaming as the
|
|
6
|
+
* call's arguments arrive, instead of a tool card.
|
|
7
|
+
* - Text a tool's commands write there (`printf … > out://` in bash) arrives in the
|
|
8
|
+
* tool's result `details.replies` ({@link toolResultReplies}); transcripts draw each
|
|
9
|
+
* one as a prose block after the call ({@link replyDisplayMessage}).
|
|
10
|
+
*/
|
|
11
|
+
import type { AssistantMessage, Usage } from "@oh-my-pi/pi-ai";
|
|
12
|
+
import { isRecord } from "@oh-my-pi/pi-utils";
|
|
13
|
+
import type { ImageBudget } from "../components/image";
|
|
14
|
+
import { internalUrlSchemeSpec, internalUrlSchemeSpecs, splitUrlScheme } from "../tools/url-scheme-host";
|
|
15
|
+
import { AssistantMessageComponent } from "./assistant-message";
|
|
16
|
+
import type { ToolExecutionHandle, ToolResultFrame } from "./tool-execution";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Whether a `toolName` call writes to a reply URL. `undefined` while streamed args cannot tell yet:
|
|
20
|
+
* no path so far, or a path that could still grow into a reply URL (`ou`, `out:/`). Callers holding
|
|
21
|
+
* final args treat `undefined` as `false`.
|
|
22
|
+
*/
|
|
23
|
+
export function isReplyWrite(toolName: string, args: unknown): boolean | undefined {
|
|
24
|
+
if (toolName !== "write") return false;
|
|
25
|
+
if (!isRecord(args)) return undefined;
|
|
26
|
+
const target = typeof args.path === "string" ? args.path : args.file_path;
|
|
27
|
+
if (typeof target !== "string") return undefined;
|
|
28
|
+
const url = splitUrlScheme(target);
|
|
29
|
+
if (url) return internalUrlSchemeSpec(url.scheme)?.write?.scope === "reply";
|
|
30
|
+
const lower = target.toLowerCase();
|
|
31
|
+
for (const [scheme, spec] of internalUrlSchemeSpecs()) {
|
|
32
|
+
if (spec.write?.scope === "reply" && `${scheme}://`.startsWith(lower)) return undefined;
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Display options of a reply block; the rest of its presentation follows the assistant prose defaults. */
|
|
38
|
+
export interface ReplyWriteOptions {
|
|
39
|
+
/** Repaint request for figures and images that finish loading after a render. */
|
|
40
|
+
readonly requestRender?: () => void;
|
|
41
|
+
readonly imageBudget?: ImageBudget;
|
|
42
|
+
/** Resolved local destinations of Markdown links, as for assistant text. */
|
|
43
|
+
readonly linkTargets?: ReadonlyMap<string, string>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const NO_USAGE: Usage = {
|
|
47
|
+
input: 0,
|
|
48
|
+
output: 0,
|
|
49
|
+
cacheRead: 0,
|
|
50
|
+
cacheWrite: 0,
|
|
51
|
+
totalTokens: 0,
|
|
52
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/** Replies a tool result carries in `details.replies` (text its commands wrote to a reply URL), oldest first. */
|
|
56
|
+
export function toolResultReplies(details: unknown): readonly string[] {
|
|
57
|
+
if (!isRecord(details) || !Array.isArray(details.replies)) return [];
|
|
58
|
+
return details.replies.filter((reply): reply is string => typeof reply === "string");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Assistant message drawing reply text. No provider turn backs it, so its identity fields stay empty. */
|
|
62
|
+
export function replyDisplayMessage(text: string, errorMessage?: string): AssistantMessage {
|
|
63
|
+
return {
|
|
64
|
+
role: "assistant",
|
|
65
|
+
content: [{ type: "text", text }],
|
|
66
|
+
api: "",
|
|
67
|
+
provider: "",
|
|
68
|
+
model: "",
|
|
69
|
+
usage: NO_USAGE,
|
|
70
|
+
stopReason: errorMessage === undefined ? "stop" : "error",
|
|
71
|
+
errorMessage,
|
|
72
|
+
timestamp: 0,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* A reply write ({@link isReplyWrite}) drawn as assistant prose. The call's lifecycle arrives
|
|
78
|
+
* through {@link ToolExecutionHandle}: streamed args grow the text, final args settle it, and
|
|
79
|
+
* the result seals the block, a failure showing its error under the text. It is a reply, not
|
|
80
|
+
* tool activity, so hiding tool activity leaves it on screen.
|
|
81
|
+
*/
|
|
82
|
+
export class ReplyWriteComponent extends AssistantMessageComponent implements ToolExecutionHandle {
|
|
83
|
+
#text = "";
|
|
84
|
+
#argsComplete = false;
|
|
85
|
+
|
|
86
|
+
constructor(options: ReplyWriteOptions = {}) {
|
|
87
|
+
super(undefined, false, options.requestRender, [], options.imageBudget, true, options.linkTargets);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
updateArgs(args: unknown): void {
|
|
91
|
+
const text = isRecord(args) && typeof args.content === "string" ? args.content : "";
|
|
92
|
+
if (text === this.#text || this.isTranscriptBlockFinalized()) return;
|
|
93
|
+
this.#text = text;
|
|
94
|
+
this.#draw();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
setArgsComplete(): void {
|
|
98
|
+
if (this.#argsComplete || this.isTranscriptBlockFinalized()) return;
|
|
99
|
+
this.#argsComplete = true;
|
|
100
|
+
this.#draw();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
setExecutionStarted(): void {}
|
|
104
|
+
|
|
105
|
+
updateResult(result: ToolResultFrame, isPartial = false): void {
|
|
106
|
+
if (isPartial || this.isTranscriptBlockFinalized()) return;
|
|
107
|
+
this.#argsComplete = true;
|
|
108
|
+
const error = result.isError
|
|
109
|
+
? result.content
|
|
110
|
+
.filter(block => block.type === "text")
|
|
111
|
+
.map(block => block.text ?? "")
|
|
112
|
+
.join("\n")
|
|
113
|
+
: undefined;
|
|
114
|
+
this.updateContent(replyDisplayMessage(this.#text, error));
|
|
115
|
+
this.markTranscriptBlockFinalized();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
parkAsBackground(): void {}
|
|
119
|
+
|
|
120
|
+
seal(): void {
|
|
121
|
+
this.markTranscriptBlockFinalized();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
#draw(): void {
|
|
125
|
+
this.updateContent(replyDisplayMessage(this.#text), { transient: !this.#argsComplete });
|
|
126
|
+
}
|
|
127
|
+
}
|
package/src/chat/table-chart.ts
CHANGED
|
@@ -37,10 +37,11 @@ export interface TableChartRequest {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
|
-
* Picks the chart for a
|
|
41
|
-
*
|
|
40
|
+
* Picks the chart for a table under `smart`: a plan, or `null` for no chart.
|
|
41
|
+
* A rejection falls back to the local guess; `undefined` (no judge to ask
|
|
42
|
+
* right now) keeps the guess at once, exactly as `always` would draw it.
|
|
42
43
|
*/
|
|
43
|
-
export type TableChartPlanner = (request: TableChartRequest) => Promise<ChartPlan | null
|
|
44
|
+
export type TableChartPlanner = (request: TableChartRequest) => Promise<ChartPlan | null> | undefined;
|
|
44
45
|
|
|
45
46
|
/**
|
|
46
47
|
* The chart under a table: its SVG, colored with figure tokens (`var(--accent)`, …)
|
|
@@ -126,7 +127,12 @@ export function lookupTableChart(table: Tokens.Table): ChartLookup {
|
|
|
126
127
|
if (entry.chart !== undefined) return entry.chart;
|
|
127
128
|
if (entry.pending) return entry.pending;
|
|
128
129
|
const { analysis, guess } = entry;
|
|
129
|
-
|
|
130
|
+
const pick = chartPlanner!({ markdown: table.raw, table: analysis, guess: guess! });
|
|
131
|
+
if (!pick) {
|
|
132
|
+
entry.chart = drawChart(analysis, guess!, true);
|
|
133
|
+
return entry.chart;
|
|
134
|
+
}
|
|
135
|
+
entry.pending = pick.then(
|
|
130
136
|
plan => {
|
|
131
137
|
entry.chart = plan ? drawChart(analysis, plan) : null;
|
|
132
138
|
},
|
|
@@ -126,6 +126,9 @@ function cardPreview(preview: NativeToolView["preview"]): TspPreview | undefined
|
|
|
126
126
|
/** The describe hooks of a registry renderer or of a custom tool object. */
|
|
127
127
|
type ToolDescriber = Pick<ToolRenderer, "describeCall" | "describeResult" | "mergeCallAndResult">;
|
|
128
128
|
|
|
129
|
+
/** Optional kinds of the host's vocabulary the describe hooks branch on. */
|
|
130
|
+
type NativeHostKinds = Required<Pick<RenderResultContextOptions, "media" | "json">>;
|
|
131
|
+
|
|
129
132
|
class SafeToolRendererComponent implements Component {
|
|
130
133
|
#toolName: string;
|
|
131
134
|
#stage: ToolRendererStage;
|
|
@@ -233,7 +236,6 @@ export interface ToolExecutionHandle extends Component {
|
|
|
233
236
|
setArgsComplete(toolCallId?: string): void;
|
|
234
237
|
setExecutionStarted(toolCallId?: string): void;
|
|
235
238
|
setExpanded(expanded: boolean): void;
|
|
236
|
-
setToolActivityVisible(visible: boolean): void;
|
|
237
239
|
/** Mark the call parked: it returned, but stays tracked for async job frames. */
|
|
238
240
|
parkAsBackground(): void;
|
|
239
241
|
/** Seal the block as final history and stop its animations. */
|
|
@@ -711,14 +713,15 @@ export class ToolExecutionComponent extends Container {
|
|
|
711
713
|
// Convert async - catch errors from processing
|
|
712
714
|
convertImageToPngShared(image)
|
|
713
715
|
.then(converted => {
|
|
716
|
+
this.#kittyConversionsAwaited.delete(key);
|
|
714
717
|
this.#kittyConverted.set(key, converted);
|
|
715
718
|
this.#displayInputVersion++;
|
|
716
719
|
this.#updateDisplay();
|
|
717
720
|
this.#ui.requestRender();
|
|
718
721
|
})
|
|
719
722
|
.catch(() => {
|
|
720
|
-
// Ignore conversion failures - display will use original image format
|
|
721
723
|
this.#kittyConversionsAwaited.delete(key);
|
|
724
|
+
this.#ui.requestRender();
|
|
722
725
|
});
|
|
723
726
|
}
|
|
724
727
|
}
|
|
@@ -859,9 +862,12 @@ export class ToolExecutionComponent extends Container {
|
|
|
859
862
|
}
|
|
860
863
|
}
|
|
861
864
|
|
|
862
|
-
/**
|
|
865
|
+
/** Hold visible results until their figures and Kitty images are ready for immutable scrollback. */
|
|
863
866
|
isTranscriptBlockPending(): boolean {
|
|
864
|
-
return
|
|
867
|
+
return (
|
|
868
|
+
this.#toolActivityVisible &&
|
|
869
|
+
((this.#showImages && this.#kittyConversionsAwaited.size > 0) || this.#figure?.pending === true)
|
|
870
|
+
);
|
|
865
871
|
}
|
|
866
872
|
|
|
867
873
|
/**
|
|
@@ -1018,7 +1024,7 @@ export class ToolExecutionComponent extends Container {
|
|
|
1018
1024
|
override describe(cx?: DescribeContext): NativeNode {
|
|
1019
1025
|
if (this.#toolName === "wait" && this.#isBenignSkip()) return EMPTY_NODE;
|
|
1020
1026
|
const dataFirst = cx?.supports("tool") === true;
|
|
1021
|
-
const
|
|
1027
|
+
const host: NativeHostKinds = { media: cx?.supports("media") === true, json: cx?.supports("json") === true };
|
|
1022
1028
|
const key = [
|
|
1023
1029
|
dataFirst,
|
|
1024
1030
|
this.#resultVersion,
|
|
@@ -1031,11 +1037,12 @@ export class ToolExecutionComponent extends Container {
|
|
|
1031
1037
|
this.#showImages,
|
|
1032
1038
|
this.#displayInputVersion,
|
|
1033
1039
|
this.#toolActivityVisible,
|
|
1034
|
-
media,
|
|
1040
|
+
host.media,
|
|
1041
|
+
host.json,
|
|
1035
1042
|
getThemeEpoch(),
|
|
1036
1043
|
];
|
|
1037
1044
|
return this.#native.get(key, () =>
|
|
1038
|
-
withHidden(dataFirst ? this.#describeTool(
|
|
1045
|
+
withHidden(dataFirst ? this.#describeTool(host) : this.#describeCard(host), !this.#toolActivityVisible),
|
|
1039
1046
|
);
|
|
1040
1047
|
}
|
|
1041
1048
|
|
|
@@ -1081,8 +1088,8 @@ export class ToolExecutionComponent extends Container {
|
|
|
1081
1088
|
* The data-first `tool` node (§7.2): the renderer's head data, status,
|
|
1082
1089
|
* timer, exit and intent as props; its body as borderless children.
|
|
1083
1090
|
*/
|
|
1084
|
-
#describeTool(
|
|
1085
|
-
const view = this.#nativeView(
|
|
1091
|
+
#describeTool(host: NativeHostKinds): NativeNode {
|
|
1092
|
+
const view = this.#nativeView(host);
|
|
1086
1093
|
const status = this.#nativeStatus();
|
|
1087
1094
|
const head = view.tool;
|
|
1088
1095
|
const late = this.#lateDiagnosticsParts();
|
|
@@ -1151,8 +1158,8 @@ export class ToolExecutionComponent extends Container {
|
|
|
1151
1158
|
return spans;
|
|
1152
1159
|
}
|
|
1153
1160
|
|
|
1154
|
-
#describeCard(
|
|
1155
|
-
const view = this.#nativeView(
|
|
1161
|
+
#describeCard(host: NativeHostKinds): NativeNode {
|
|
1162
|
+
const view = this.#nativeView(host);
|
|
1156
1163
|
const status = this.#nativeStatus();
|
|
1157
1164
|
const late = this.#lateDiagnosticsParts();
|
|
1158
1165
|
const fallbackHead = this.#fallbackHead(view);
|
|
@@ -1206,10 +1213,10 @@ export class ToolExecutionComponent extends Container {
|
|
|
1206
1213
|
* the result view (merged renderers) or the call head over both bodies.
|
|
1207
1214
|
* A renderer that throws falls back to the generic card.
|
|
1208
1215
|
*/
|
|
1209
|
-
#nativeView(
|
|
1216
|
+
#nativeView(host: NativeHostKinds): NativeToolView {
|
|
1210
1217
|
const status = this.#nativeStatus();
|
|
1211
1218
|
const options: RenderResultContextOptions = {
|
|
1212
|
-
|
|
1219
|
+
...host,
|
|
1213
1220
|
expanded: this.#expanded,
|
|
1214
1221
|
isPartial: this.#isPartial,
|
|
1215
1222
|
argsComplete: this.#argsComplete,
|
|
@@ -17,6 +17,7 @@ import { type ThemeColor, theme } from "../theme";
|
|
|
17
17
|
import type { ChatTranscriptBuilder } from "./chat-transcript-builder";
|
|
18
18
|
import { padToWidth } from "../render/utils";
|
|
19
19
|
import { isUsageRowBlock } from "../overlays/usage-row";
|
|
20
|
+
import type { DescribeContext, NativeChild } from "../native/node";
|
|
20
21
|
|
|
21
22
|
/** One selectable transcript item: a message entry plus its rendered block range. */
|
|
22
23
|
export interface OutlineTarget {
|
|
@@ -148,6 +149,32 @@ export function outlineVisibility(
|
|
|
148
149
|
});
|
|
149
150
|
}
|
|
150
151
|
|
|
152
|
+
/**
|
|
153
|
+
* Per-target "describes something that shows" flags: {@link outlineVisibility}
|
|
154
|
+
* for a TSP page, where blocks describe nodes instead of rendering rows. An
|
|
155
|
+
* empty `col`/`row` (an interrupted turn with no output) shows nothing.
|
|
156
|
+
*/
|
|
157
|
+
export function describedVisibility(
|
|
158
|
+
blocks: readonly Component[],
|
|
159
|
+
targets: readonly OutlineTarget[],
|
|
160
|
+
cx: DescribeContext,
|
|
161
|
+
): boolean[] {
|
|
162
|
+
return targets.map(target => {
|
|
163
|
+
for (let index = target.start; index < target.end; index++) {
|
|
164
|
+
if (shows(blocks[index]!, cx)) return true;
|
|
165
|
+
}
|
|
166
|
+
return false;
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Whether `child` describes a node that shows: a layout `col`/`row` only through a child that does. */
|
|
171
|
+
function shows(child: NativeChild, cx: DescribeContext): boolean {
|
|
172
|
+
const node = "k" in child ? child : child.describe?.(cx);
|
|
173
|
+
if (!node) return false;
|
|
174
|
+
if (node.k !== "col" && node.k !== "row") return true;
|
|
175
|
+
return (node.c ?? []).some(grandchild => shows(grandchild, cx));
|
|
176
|
+
}
|
|
177
|
+
|
|
151
178
|
/** Dotted horizontal rule with rounded corners, spanning the outline width. */
|
|
152
179
|
export function outlineRule(
|
|
153
180
|
left: string,
|