@oh-my-pi/pi-coding-agent 15.9.5 → 15.10.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 +98 -1
- package/dist/types/cli/args.d.ts +1 -1
- package/dist/types/cli/gallery-cli.d.ts +43 -0
- package/dist/types/cli/gallery-fixtures/agentic.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/codeintel.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/edit.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/fs.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/index.d.ts +4 -0
- package/dist/types/cli/gallery-fixtures/interaction.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/memory.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/misc.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/search.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/shell.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/types.d.ts +44 -0
- package/dist/types/cli/gallery-fixtures/web.d.ts +2 -0
- package/dist/types/cli/gallery-screenshot.d.ts +35 -0
- package/dist/types/commands/gallery.d.ts +47 -0
- package/dist/types/config/keybindings.d.ts +10 -2
- package/dist/types/config/model-id-affixes.d.ts +2 -0
- package/dist/types/config/model-registry.d.ts +8 -1
- package/dist/types/config/settings-schema.d.ts +43 -7
- package/dist/types/edit/file-snapshot-store.d.ts +1 -1
- package/dist/types/eval/backend.d.ts +6 -6
- package/dist/types/eval/bridge-timeout.d.ts +27 -0
- package/dist/types/eval/idle-timeout.d.ts +16 -14
- package/dist/types/eval/js/executor.d.ts +3 -3
- package/dist/types/eval/py/executor.d.ts +2 -2
- package/dist/types/eval/py/spawn-options.d.ts +58 -0
- package/dist/types/extensibility/plugins/marketplace-auto-update.d.ts +8 -0
- package/dist/types/lsp/types.d.ts +10 -0
- package/dist/types/main.d.ts +3 -2
- package/dist/types/memory-backend/index.d.ts +2 -1
- package/dist/types/memory-backend/resolve.d.ts +1 -1
- package/dist/types/memory-backend/types.d.ts +1 -1
- package/dist/types/modes/components/assistant-message.d.ts +5 -0
- package/dist/types/modes/components/copy-selector.d.ts +22 -0
- package/dist/types/modes/components/custom-editor.d.ts +2 -1
- package/dist/types/modes/components/model-selector.d.ts +1 -0
- package/dist/types/modes/components/tool-execution.d.ts +18 -0
- package/dist/types/modes/controllers/command-controller.d.ts +0 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -1
- package/dist/types/modes/index.d.ts +5 -4
- package/dist/types/modes/interactive-mode.d.ts +2 -2
- package/dist/types/modes/setup-version.d.ts +11 -0
- package/dist/types/modes/setup-wizard/index.d.ts +2 -1
- package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +2 -1
- package/dist/types/modes/types.d.ts +2 -2
- package/dist/types/modes/utils/copy-targets.d.ts +53 -0
- package/dist/types/sdk.d.ts +1 -1
- package/dist/types/task/executor.d.ts +7 -0
- package/dist/types/telemetry-export.d.ts +1 -1
- package/dist/types/tools/eval-render.d.ts +1 -0
- package/dist/types/tools/fetch.d.ts +15 -7
- package/dist/types/tools/render-utils.d.ts +33 -0
- package/dist/types/tools/renderers.d.ts +16 -2
- package/dist/types/tools/search.d.ts +1 -1
- package/dist/types/tools/write.d.ts +2 -0
- package/dist/types/tui/code-cell.d.ts +6 -0
- package/dist/types/tui/output-block.d.ts +11 -0
- package/dist/types/web/scrapers/github.d.ts +22 -0
- package/dist/types/web/search/providers/perplexity.d.ts +8 -1
- package/dist/types/web/search/types.d.ts +1 -1
- package/package.json +9 -9
- package/scripts/dev-launch +42 -0
- package/scripts/dev-launch-preload.ts +19 -0
- package/src/autoresearch/dashboard.ts +11 -21
- package/src/cli/args.ts +2 -2
- package/src/cli/claude-trace-cli.ts +13 -1
- package/src/cli/gallery-cli.ts +223 -0
- package/src/cli/gallery-fixtures/agentic.ts +292 -0
- package/src/cli/gallery-fixtures/codeintel.ts +188 -0
- package/src/cli/gallery-fixtures/edit.ts +194 -0
- package/src/cli/gallery-fixtures/fs.ts +153 -0
- package/src/cli/gallery-fixtures/index.ts +40 -0
- package/src/cli/gallery-fixtures/interaction.ts +49 -0
- package/src/cli/gallery-fixtures/memory.ts +81 -0
- package/src/cli/gallery-fixtures/misc.ts +221 -0
- package/src/cli/gallery-fixtures/search.ts +213 -0
- package/src/cli/gallery-fixtures/shell.ts +167 -0
- package/src/cli/gallery-fixtures/types.ts +41 -0
- package/src/cli/gallery-fixtures/web.ts +158 -0
- package/src/cli/gallery-screenshot.ts +279 -0
- package/src/cli-commands.ts +1 -0
- package/src/commands/gallery.ts +52 -0
- package/src/commands/launch.ts +1 -1
- package/src/config/keybindings.ts +68 -2
- package/src/config/model-equivalence.ts +35 -12
- package/src/config/model-id-affixes.ts +39 -22
- package/src/config/model-registry.ts +16 -16
- package/src/config/settings-schema.ts +29 -6
- package/src/config/settings.ts +11 -0
- package/src/dap/client.ts +14 -16
- package/src/debug/raw-sse.ts +18 -4
- package/src/edit/file-snapshot-store.ts +1 -1
- package/src/edit/index.ts +1 -1
- package/src/edit/renderer.ts +43 -55
- package/src/edit/streaming.ts +1 -1
- package/src/eval/__tests__/agent-bridge.test.ts +102 -58
- package/src/eval/__tests__/bridge-timeout.test.ts +64 -0
- package/src/eval/__tests__/idle-timeout.test.ts +26 -12
- package/src/eval/__tests__/kernel-spawn.test.ts +103 -0
- package/src/eval/__tests__/llm-bridge.test.ts +10 -10
- package/src/eval/agent-bridge.ts +38 -12
- package/src/eval/backend.ts +6 -6
- package/src/eval/bridge-timeout.ts +44 -0
- package/src/eval/idle-timeout.ts +33 -15
- package/src/eval/js/executor.ts +10 -10
- package/src/eval/llm-bridge.ts +4 -5
- package/src/eval/py/executor.ts +6 -6
- package/src/eval/py/kernel.ts +11 -1
- package/src/eval/py/spawn-options.ts +126 -0
- package/src/export/ttsr.ts +9 -0
- package/src/extensibility/extensions/runner.ts +3 -0
- package/src/extensibility/plugins/doctor.ts +0 -1
- package/src/extensibility/plugins/marketplace-auto-update.ts +49 -0
- package/src/goals/tools/goal-tool.ts +2 -2
- package/src/internal-urls/docs-index.generated.ts +7 -6
- package/src/lsp/client.ts +179 -52
- package/src/lsp/index.ts +38 -4
- package/src/lsp/render.ts +3 -3
- package/src/lsp/types.ts +10 -0
- package/src/main.ts +47 -52
- package/src/memory-backend/index.ts +13 -1
- package/src/memory-backend/resolve.ts +3 -5
- package/src/memory-backend/types.ts +1 -1
- package/src/modes/components/agent-dashboard.ts +13 -4
- package/src/modes/components/assistant-message.ts +22 -1
- package/src/modes/components/copy-selector.ts +249 -0
- package/src/modes/components/custom-editor.ts +10 -1
- package/src/modes/components/extensions/extension-list.ts +17 -8
- package/src/modes/components/history-search.ts +19 -11
- package/src/modes/components/model-selector.ts +125 -29
- package/src/modes/components/oauth-selector.ts +28 -12
- package/src/modes/components/session-observer-overlay.ts +13 -15
- package/src/modes/components/session-selector.ts +24 -13
- package/src/modes/components/status-line.ts +3 -5
- package/src/modes/components/tool-execution.ts +83 -24
- package/src/modes/components/tree-selector.ts +19 -7
- package/src/modes/components/user-message-selector.ts +25 -14
- package/src/modes/controllers/command-controller.ts +13 -118
- package/src/modes/controllers/event-controller.ts +26 -10
- package/src/modes/controllers/input-controller.ts +11 -3
- package/src/modes/controllers/selector-controller.ts +40 -3
- package/src/modes/index.ts +5 -4
- package/src/modes/interactive-mode.ts +21 -7
- package/src/modes/setup-version.ts +11 -0
- package/src/modes/setup-wizard/index.ts +3 -2
- package/src/modes/setup-wizard/scenes/web-search.ts +3 -2
- package/src/modes/theme/theme.ts +46 -10
- package/src/modes/types.ts +2 -2
- package/src/modes/utils/context-usage.ts +10 -6
- package/src/modes/utils/copy-targets.ts +254 -0
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/prompts/tools/ast-edit.md +1 -1
- package/src/prompts/tools/ast-grep.md +1 -1
- package/src/prompts/tools/read.md +1 -1
- package/src/prompts/tools/search.md +1 -1
- package/src/sdk.ts +21 -23
- package/src/session/agent-session.ts +13 -9
- package/src/slash-commands/builtin-registry.ts +4 -12
- package/src/slash-commands/helpers/usage-report.ts +2 -0
- package/src/task/executor.ts +20 -2
- package/src/task/render.ts +37 -11
- package/src/telemetry-export.ts +25 -7
- package/src/tools/bash.ts +18 -8
- package/src/tools/browser/render.ts +5 -4
- package/src/tools/debug.ts +3 -3
- package/src/tools/eval-backends.ts +6 -17
- package/src/tools/eval-render.ts +28 -10
- package/src/tools/eval.ts +19 -23
- package/src/tools/fetch.ts +99 -89
- package/src/tools/read.ts +7 -7
- package/src/tools/render-utils.ts +63 -3
- package/src/tools/renderers.ts +16 -1
- package/src/tools/report-tool-issue.ts +1 -1
- package/src/tools/search.ts +173 -81
- package/src/tools/ssh.ts +21 -8
- package/src/tools/todo.ts +20 -7
- package/src/tools/write.ts +39 -9
- package/src/tui/code-cell.ts +19 -4
- package/src/tui/output-block.ts +14 -0
- package/src/web/scrapers/github.ts +255 -3
- package/src/web/scrapers/youtube.ts +3 -2
- package/src/web/search/providers/perplexity.ts +199 -51
- package/src/web/search/render.ts +42 -57
- package/src/web/search/types.ts +5 -1
- package/dist/types/eval/heartbeat.d.ts +0 -45
- package/src/eval/__tests__/heartbeat.test.ts +0 -84
- package/src/eval/__tests__/shared-executors.test.ts +0 -609
- package/src/eval/heartbeat.ts +0 -74
- /package/dist/types/eval/__tests__/{heartbeat.test.d.ts → bridge-timeout.test.d.ts} +0 -0
- /package/dist/types/eval/__tests__/{shared-executors.test.d.ts → kernel-spawn.test.d.ts} +0 -0
|
@@ -31,9 +31,9 @@ import {
|
|
|
31
31
|
renderJsonTreeLines,
|
|
32
32
|
} from "../../tools/json-tree";
|
|
33
33
|
import { formatExpandHint, replaceTabs, resolveImageOptions, truncateToWidth } from "../../tools/render-utils";
|
|
34
|
-
import { toolRenderers } from "../../tools/renderers";
|
|
34
|
+
import { type ToolRenderer, toolRenderers } from "../../tools/renderers";
|
|
35
35
|
import { TODO_STRIKE_TOTAL_FRAMES } from "../../tools/todo";
|
|
36
|
-
import { renderStatusLine } from "../../tui";
|
|
36
|
+
import { isFramedBlockComponent, renderStatusLine } from "../../tui";
|
|
37
37
|
import { sanitizeWithOptionalSixelPassthrough } from "../../utils/sixel";
|
|
38
38
|
import { renderDiff } from "./diff";
|
|
39
39
|
|
|
@@ -45,6 +45,18 @@ function ensureInvalidate(component: unknown): Component {
|
|
|
45
45
|
return c as Component;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
function addBoxChild(box: Box, component: unknown): boolean {
|
|
49
|
+
const child = ensureInvalidate(component);
|
|
50
|
+
box.addChild(child);
|
|
51
|
+
return isFramedBlockComponent(child);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function setBoxPaddingForFramedBlock(box: Box, hasFramedBlock: boolean): void {
|
|
55
|
+
const padding = hasFramedBlock ? 0 : 1;
|
|
56
|
+
box.setPaddingX(padding);
|
|
57
|
+
box.setPaddingY(padding);
|
|
58
|
+
}
|
|
59
|
+
|
|
48
60
|
/**
|
|
49
61
|
* Drop trailing removal/hunk-header lines that appear in a streaming diff
|
|
50
62
|
* before the matching `+added` lines have arrived. Without this, a partial
|
|
@@ -107,7 +119,7 @@ function rawTextInputFromPartialJson(partialJson: unknown): string | undefined {
|
|
|
107
119
|
// Function-tool arguments stream as JSON. Custom/free-form tools stream raw
|
|
108
120
|
// text in the same transport field; only the raw form is a valid fallback for
|
|
109
121
|
// the conventional `input` parameter.
|
|
110
|
-
if (first === "{" || first ===
|
|
122
|
+
if (first === "{" || first === '"') return undefined;
|
|
111
123
|
return partialJson;
|
|
112
124
|
}
|
|
113
125
|
|
|
@@ -518,6 +530,39 @@ export class ToolExecutionComponent extends Container {
|
|
|
518
530
|
return (this.#result.details as { async?: { state?: string } } | undefined)?.async?.state === "running";
|
|
519
531
|
}
|
|
520
532
|
|
|
533
|
+
/**
|
|
534
|
+
* While a tool's preview is still streaming, a block whose preview is
|
|
535
|
+
* append-only (rows only grow at the bottom, never re-layout) lets the
|
|
536
|
+
* renderer commit the scrolled-off head of an over-tall preview to native
|
|
537
|
+
* scrollback instead of dropping it — the same anti-yank path a streaming
|
|
538
|
+
* assistant reply uses (see {@link TranscriptContainer} +
|
|
539
|
+
* `NativeScrollbackLiveRegion`). Covers both phases: a pre-result call preview
|
|
540
|
+
* (a `write` whose content streams in) and a partial-result preview that
|
|
541
|
+
* streams output below fixed input (an `eval`/`bash` whose stdout grows under
|
|
542
|
+
* its code cell). Gated on {@link isTranscriptBlockFinalized} so the boundary
|
|
543
|
+
* closes the instant the block reaches a terminal state — a final result that
|
|
544
|
+
* may collapse to a compact view, a backgrounded async tool, or a seal — and
|
|
545
|
+
* the renderer decides whether its current preview shape qualifies via
|
|
546
|
+
* `isStreamingPreviewAppendOnly` (typically: only the expanded full view,
|
|
547
|
+
* which is top-anchored; the collapsed tail window re-layouts but is bounded
|
|
548
|
+
* so it never overflows anyway).
|
|
549
|
+
*/
|
|
550
|
+
isTranscriptBlockAppendOnly(): boolean {
|
|
551
|
+
// A finalized block's preview can collapse/re-layout; only a live,
|
|
552
|
+
// still-streaming block is a candidate.
|
|
553
|
+
if (this.isTranscriptBlockFinalized()) return false;
|
|
554
|
+
const predicate =
|
|
555
|
+
(this.#tool as { isStreamingPreviewAppendOnly?: ToolRenderer["isStreamingPreviewAppendOnly"] } | undefined)
|
|
556
|
+
?.isStreamingPreviewAppendOnly ?? toolRenderers[this.#toolName]?.isStreamingPreviewAppendOnly;
|
|
557
|
+
if (!predicate) return false;
|
|
558
|
+
try {
|
|
559
|
+
return predicate(this.#getCallArgsForRender(), this.#renderState, this.#result);
|
|
560
|
+
} catch (err) {
|
|
561
|
+
logger.warn("Tool append-only predicate failed", { tool: this.#toolName, error: String(err) });
|
|
562
|
+
return false;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
521
566
|
/**
|
|
522
567
|
* Mark the tool terminal even though no result arrived (the turn aborted or
|
|
523
568
|
* abandoned it) and stop animating, so it can freeze and stops pinning the
|
|
@@ -582,28 +627,35 @@ export class ToolExecutionComponent extends Container {
|
|
|
582
627
|
const inline = Boolean((tool as { inline?: boolean }).inline);
|
|
583
628
|
this.#contentBox.setBgFn(inline ? undefined : bgFn);
|
|
584
629
|
this.#contentBox.clear();
|
|
630
|
+
let contentBoxHasFramedBlock = false;
|
|
585
631
|
// Mirror the built-in renderer branch so custom renderers (notably the
|
|
586
632
|
// task tool, whose live instance routes through here) receive the same
|
|
587
633
|
// render context — e.g. the `hasResult` flag that suppresses the task
|
|
588
634
|
// call preview once result lines exist.
|
|
589
635
|
this.#renderState.renderContext = this.#buildRenderContext();
|
|
590
636
|
|
|
591
|
-
// Render call component
|
|
637
|
+
// Render call component. The fallback label only stands in for a
|
|
638
|
+
// missing `renderCall`; when the call is intentionally suppressed
|
|
639
|
+
// (mergeCallAndResult once a result exists) we render nothing here so
|
|
640
|
+
// the result component isn't preceded by a redundant tool-name line.
|
|
592
641
|
const shouldRenderCall = !this.#result || !mergeCallAndResult;
|
|
593
|
-
if (shouldRenderCall
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
642
|
+
if (shouldRenderCall) {
|
|
643
|
+
if (tool.renderCall) {
|
|
644
|
+
try {
|
|
645
|
+
const callComponent = tool.renderCall(this.#getCallArgsForRender(), this.#renderState, theme);
|
|
646
|
+
if (callComponent) {
|
|
647
|
+
contentBoxHasFramedBlock =
|
|
648
|
+
addBoxChild(this.#contentBox, callComponent) || contentBoxHasFramedBlock;
|
|
649
|
+
}
|
|
650
|
+
} catch (err) {
|
|
651
|
+
logger.warn("Tool renderer failed", { tool: this.#toolName, error: String(err) });
|
|
652
|
+
// Fall back to default on error
|
|
653
|
+
addBoxChild(this.#contentBox, new Text(theme.fg("toolTitle", theme.bold(this.#toolLabel)), 0, 0));
|
|
598
654
|
}
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
this.#contentBox.addChild(new Text(theme.fg("toolTitle", theme.bold(this.#toolLabel)), 0, 0));
|
|
655
|
+
} else {
|
|
656
|
+
// No custom renderCall, show tool name
|
|
657
|
+
addBoxChild(this.#contentBox, new Text(theme.fg("toolTitle", theme.bold(this.#toolLabel)), 0, 0));
|
|
603
658
|
}
|
|
604
|
-
} else {
|
|
605
|
-
// No custom renderCall, show tool name
|
|
606
|
-
this.#contentBox.addChild(new Text(theme.fg("toolTitle", theme.bold(this.#toolLabel)), 0, 0));
|
|
607
659
|
}
|
|
608
660
|
|
|
609
661
|
// Render result component if we have a result
|
|
@@ -626,23 +678,24 @@ export class ToolExecutionComponent extends Container {
|
|
|
626
678
|
this.#args,
|
|
627
679
|
);
|
|
628
680
|
if (resultComponent) {
|
|
629
|
-
this.#contentBox
|
|
681
|
+
contentBoxHasFramedBlock = addBoxChild(this.#contentBox, resultComponent) || contentBoxHasFramedBlock;
|
|
630
682
|
}
|
|
631
683
|
} catch (err) {
|
|
632
684
|
logger.warn("Tool renderer failed", { tool: this.#toolName, error: String(err) });
|
|
633
685
|
// Fall back to showing raw output on error
|
|
634
686
|
const output = this.#getTextOutput();
|
|
635
687
|
if (output) {
|
|
636
|
-
this.#contentBox
|
|
688
|
+
addBoxChild(this.#contentBox, new Text(theme.fg("toolOutput", replaceTabs(output)), 0, 0));
|
|
637
689
|
}
|
|
638
690
|
}
|
|
639
691
|
} else if (this.#result) {
|
|
640
692
|
// Has result but no custom renderResult
|
|
641
693
|
const output = this.#getTextOutput();
|
|
642
694
|
if (output) {
|
|
643
|
-
this.#contentBox
|
|
695
|
+
addBoxChild(this.#contentBox, new Text(theme.fg("toolOutput", replaceTabs(output)), 0, 0));
|
|
644
696
|
}
|
|
645
697
|
}
|
|
698
|
+
setBoxPaddingForFramedBlock(this.#contentBox, contentBoxHasFramedBlock);
|
|
646
699
|
} else if (this.#toolName in toolRenderers) {
|
|
647
700
|
// Built-in tools with renderers
|
|
648
701
|
const renderer = toolRenderers[this.#toolName];
|
|
@@ -661,6 +714,7 @@ export class ToolExecutionComponent extends Container {
|
|
|
661
714
|
// Multi-file: render each file as its own Box (identical to separate tool calls)
|
|
662
715
|
this.#contentBox.setBgFn(undefined);
|
|
663
716
|
this.#contentBox.clear();
|
|
717
|
+
this.#contentBox.setPaddingX(1);
|
|
664
718
|
|
|
665
719
|
const renderContext = this.#buildRenderContext();
|
|
666
720
|
this.#renderState.renderContext = renderContext;
|
|
@@ -683,7 +737,8 @@ export class ToolExecutionComponent extends Container {
|
|
|
683
737
|
theme,
|
|
684
738
|
);
|
|
685
739
|
if (resultComponent) {
|
|
686
|
-
fileBox
|
|
740
|
+
const fileBoxHasFramedBlock = addBoxChild(fileBox, resultComponent);
|
|
741
|
+
setBoxPaddingForFramedBlock(fileBox, fileBoxHasFramedBlock);
|
|
687
742
|
}
|
|
688
743
|
} catch (err) {
|
|
689
744
|
logger.warn("Tool renderer failed", { tool: this.#toolName, error: String(err) });
|
|
@@ -719,6 +774,7 @@ export class ToolExecutionComponent extends Container {
|
|
|
719
774
|
// Inline renderers skip background styling
|
|
720
775
|
this.#contentBox.setBgFn(renderer.inline ? undefined : bgFn);
|
|
721
776
|
this.#contentBox.clear();
|
|
777
|
+
let contentBoxHasFramedBlock = false;
|
|
722
778
|
|
|
723
779
|
const renderContext = this.#buildRenderContext();
|
|
724
780
|
this.#renderState.renderContext = renderContext;
|
|
@@ -729,12 +785,13 @@ export class ToolExecutionComponent extends Container {
|
|
|
729
785
|
try {
|
|
730
786
|
const callComponent = renderer.renderCall(this.#getCallArgsForRender(), this.#renderState, theme);
|
|
731
787
|
if (callComponent) {
|
|
732
|
-
|
|
788
|
+
contentBoxHasFramedBlock =
|
|
789
|
+
addBoxChild(this.#contentBox, callComponent) || contentBoxHasFramedBlock;
|
|
733
790
|
}
|
|
734
791
|
} catch (err) {
|
|
735
792
|
logger.warn("Tool renderer failed", { tool: this.#toolName, error: String(err) });
|
|
736
793
|
// Fall back to default on error
|
|
737
|
-
this.#contentBox
|
|
794
|
+
addBoxChild(this.#contentBox, new Text(theme.fg("toolTitle", theme.bold(this.#toolLabel)), 0, 0));
|
|
738
795
|
}
|
|
739
796
|
}
|
|
740
797
|
|
|
@@ -752,17 +809,19 @@ export class ToolExecutionComponent extends Container {
|
|
|
752
809
|
this.#getCallArgsForRender(),
|
|
753
810
|
);
|
|
754
811
|
if (resultComponent) {
|
|
755
|
-
|
|
812
|
+
contentBoxHasFramedBlock =
|
|
813
|
+
addBoxChild(this.#contentBox, resultComponent) || contentBoxHasFramedBlock;
|
|
756
814
|
}
|
|
757
815
|
} catch (err) {
|
|
758
816
|
logger.warn("Tool renderer failed", { tool: this.#toolName, error: String(err) });
|
|
759
817
|
// Fall back to showing raw output on error
|
|
760
818
|
const output = this.#getTextOutput();
|
|
761
819
|
if (output) {
|
|
762
|
-
this.#contentBox
|
|
820
|
+
addBoxChild(this.#contentBox, new Text(theme.fg("toolOutput", replaceTabs(output)), 0, 0));
|
|
763
821
|
}
|
|
764
822
|
}
|
|
765
823
|
}
|
|
824
|
+
setBoxPaddingForFramedBlock(this.#contentBox, contentBoxHasFramedBlock);
|
|
766
825
|
}
|
|
767
826
|
} else {
|
|
768
827
|
// Other built-in tools: use Text directly with caching
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
fuzzyMatch,
|
|
7
7
|
Input,
|
|
8
8
|
matchesKey,
|
|
9
|
+
ScrollView,
|
|
9
10
|
Spacer,
|
|
10
11
|
Text,
|
|
11
12
|
TruncatedText,
|
|
@@ -492,6 +493,10 @@ class TreeList implements Component {
|
|
|
492
493
|
const contentReserve = Math.max(MIN_CONTENT_COLS, Math.floor(width / 2));
|
|
493
494
|
const maxIndentLevels = Math.max(1, Math.floor((width - contentReserve - OVERHEAD_COLS) / 3));
|
|
494
495
|
|
|
496
|
+
const overflow = this.#filteredNodes.length > this.maxVisibleLines;
|
|
497
|
+
const rowWidth = Math.max(0, width - (overflow ? 1 : 0));
|
|
498
|
+
const rows: string[] = [];
|
|
499
|
+
|
|
495
500
|
for (let i = startIndex; i < endIndex; i++) {
|
|
496
501
|
const flatNode = this.#filteredNodes[i];
|
|
497
502
|
const entry = flatNode.node.entry;
|
|
@@ -560,15 +565,22 @@ class TreeList implements Component {
|
|
|
560
565
|
if (isSelected) {
|
|
561
566
|
line = theme.bg("selectedBg", line);
|
|
562
567
|
}
|
|
563
|
-
|
|
568
|
+
rows.push(truncateToWidth(line, rowWidth));
|
|
564
569
|
}
|
|
565
570
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
),
|
|
571
|
-
);
|
|
571
|
+
const sv = new ScrollView(rows, {
|
|
572
|
+
height: rows.length,
|
|
573
|
+
scrollbar: "auto",
|
|
574
|
+
totalRows: this.#filteredNodes.length,
|
|
575
|
+
theme: { track: t => theme.fg("muted", t), thumb: t => theme.fg("accent", t) },
|
|
576
|
+
});
|
|
577
|
+
sv.setScrollOffset(startIndex);
|
|
578
|
+
lines.push(...sv.render(width));
|
|
579
|
+
|
|
580
|
+
const filterLabel = this.#getFilterLabel();
|
|
581
|
+
if (filterLabel) {
|
|
582
|
+
lines.push(truncateToWidth(theme.fg("muted", ` ${filterLabel.trim()}`), width));
|
|
583
|
+
}
|
|
572
584
|
|
|
573
585
|
return lines;
|
|
574
586
|
}
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
extractPrintableText,
|
|
5
5
|
fuzzyFilter,
|
|
6
6
|
matchesKey,
|
|
7
|
+
ScrollView,
|
|
7
8
|
Spacer,
|
|
8
9
|
Text,
|
|
9
10
|
truncateToWidth,
|
|
@@ -48,14 +49,10 @@ class UserMessageList implements Component {
|
|
|
48
49
|
return this.#isSearchEnabled() || this.#searchQuery.length > 0;
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
#renderStatusLine(
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
? `${selectedCount}/${total} of ${this.messages.length}`
|
|
56
|
-
: `${selectedCount}/${total}`;
|
|
57
|
-
const suffix = this.#searchQuery.trim() ? ` Search: ${this.#searchQuery}` : " Type to search";
|
|
58
|
-
return theme.fg("muted", ` (${count})${suffix}`);
|
|
52
|
+
#renderStatusLine(_total: number): string {
|
|
53
|
+
const query = this.#searchQuery.trim();
|
|
54
|
+
const suffix = query ? `Search: ${this.#searchQuery}` : "Type to search";
|
|
55
|
+
return theme.fg("muted", ` ${suffix}`);
|
|
59
56
|
}
|
|
60
57
|
|
|
61
58
|
#setSearchQuery(query: string): void {
|
|
@@ -103,6 +100,9 @@ class UserMessageList implements Component {
|
|
|
103
100
|
const endIndex = Math.min(startIndex + this.#maxVisible, total);
|
|
104
101
|
|
|
105
102
|
// Render visible messages (2 lines per message + blank line)
|
|
103
|
+
const overflow = total > this.#maxVisible;
|
|
104
|
+
const rowWidth = Math.max(0, width - (overflow ? 1 : 0));
|
|
105
|
+
const messageLines: string[] = [];
|
|
106
106
|
for (let i = startIndex; i < endIndex; i++) {
|
|
107
107
|
const message = this.#filteredMessages[i];
|
|
108
108
|
if (!message) continue;
|
|
@@ -113,26 +113,37 @@ class UserMessageList implements Component {
|
|
|
113
113
|
|
|
114
114
|
// First line: cursor + message
|
|
115
115
|
const cursor = isSelected ? theme.fg("accent", "› ") : " ";
|
|
116
|
-
const maxMsgWidth =
|
|
116
|
+
const maxMsgWidth = rowWidth - 2; // Account for cursor (2 chars)
|
|
117
117
|
const truncatedMsg = truncateToWidth(normalizedMessage, maxMsgWidth);
|
|
118
118
|
const messageLine = cursor + (isSelected ? theme.bold(truncatedMsg) : truncatedMsg);
|
|
119
119
|
|
|
120
|
-
|
|
120
|
+
messageLines.push(messageLine);
|
|
121
121
|
|
|
122
122
|
// Second line: metadata (position in history)
|
|
123
123
|
const position = this.messages.indexOf(message) + 1;
|
|
124
124
|
const metadata = ` Message ${position} of ${this.messages.length}`;
|
|
125
125
|
const metadataLine = theme.fg("muted", metadata);
|
|
126
|
-
|
|
127
|
-
|
|
126
|
+
messageLines.push(metadataLine);
|
|
127
|
+
messageLines.push(""); // Blank line between messages
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
if (total === 0) {
|
|
131
131
|
lines.push(theme.fg("muted", " No matching messages"));
|
|
132
|
+
} else {
|
|
133
|
+
const visibleCount = endIndex - startIndex;
|
|
134
|
+
const linesPerItem = visibleCount > 0 ? messageLines.length / visibleCount : 1;
|
|
135
|
+
const sv = new ScrollView(messageLines, {
|
|
136
|
+
height: messageLines.length,
|
|
137
|
+
scrollbar: "auto",
|
|
138
|
+
totalRows: Math.round(total * linesPerItem),
|
|
139
|
+
theme: { track: t => theme.fg("muted", t), thumb: t => theme.fg("accent", t) },
|
|
140
|
+
});
|
|
141
|
+
sv.setScrollOffset(Math.round(startIndex * linesPerItem));
|
|
142
|
+
lines.push(...sv.render(width));
|
|
132
143
|
}
|
|
133
144
|
|
|
134
|
-
// Add
|
|
135
|
-
if (
|
|
145
|
+
// Add search indicator if needed
|
|
146
|
+
if (this.#shouldRenderSearchStatus()) {
|
|
136
147
|
lines.push(this.#renderStatusLine(total));
|
|
137
148
|
}
|
|
138
149
|
|
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
getEnvApiKey,
|
|
7
7
|
getProviderDetails,
|
|
8
8
|
type ProviderDetails,
|
|
9
|
-
type ToolCall,
|
|
10
9
|
type UsageLimit,
|
|
11
10
|
type UsageReport,
|
|
12
11
|
} from "@oh-my-pi/pi-ai";
|
|
@@ -14,7 +13,6 @@ import { Loader, Markdown, padding, Spacer, Text, visibleWidth } from "@oh-my-pi
|
|
|
14
13
|
import { formatDuration, Snowflake } from "@oh-my-pi/pi-utils";
|
|
15
14
|
import { $ } from "bun";
|
|
16
15
|
import { shouldEnableAppendOnlyContext } from "../../config/append-only-context-mode";
|
|
17
|
-
import { loadCustomShare } from "../../export/custom-share";
|
|
18
16
|
import type { CompactOptions } from "../../extensibility/extensions/types";
|
|
19
17
|
import {
|
|
20
18
|
diffMentalModelContent,
|
|
@@ -132,6 +130,7 @@ export class CommandController {
|
|
|
132
130
|
}
|
|
133
131
|
|
|
134
132
|
try {
|
|
133
|
+
const { loadCustomShare } = await import("../../export/custom-share");
|
|
135
134
|
const customShare = await loadCustomShare();
|
|
136
135
|
if (customShare) {
|
|
137
136
|
const loader = new BorderedLoader(this.ctx.ui, theme, "Sharing...");
|
|
@@ -239,121 +238,6 @@ export class CommandController {
|
|
|
239
238
|
}
|
|
240
239
|
}
|
|
241
240
|
|
|
242
|
-
handleCopyCommand(sub?: string) {
|
|
243
|
-
switch (sub) {
|
|
244
|
-
case "code":
|
|
245
|
-
return this.#copyCode();
|
|
246
|
-
case "all":
|
|
247
|
-
return this.#copyAllCode();
|
|
248
|
-
case "cmd":
|
|
249
|
-
return this.#copyLastCommand();
|
|
250
|
-
case "last":
|
|
251
|
-
case undefined:
|
|
252
|
-
return this.#copyLastMessage();
|
|
253
|
-
default:
|
|
254
|
-
this.ctx.showError(`Unknown subcommand: ${sub}. Use code, all, cmd, or last.`);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
#copyLastMessage() {
|
|
259
|
-
const assistantText = this.ctx.session.getLastAssistantText();
|
|
260
|
-
if (assistantText) {
|
|
261
|
-
this.#doCopy(assistantText, "Copied last agent message to clipboard");
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
if (!this.ctx.session.hasCopyCandidateAssistantMessage()) {
|
|
266
|
-
const handoffText = this.ctx.session.getLastVisibleHandoffText();
|
|
267
|
-
if (handoffText) {
|
|
268
|
-
this.#doCopy(handoffText, "Copied handoff context to clipboard");
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
this.ctx.showError("No agent messages to copy yet.");
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
#copyCode() {
|
|
277
|
-
const text = this.ctx.session.getLastAssistantText();
|
|
278
|
-
if (!text) {
|
|
279
|
-
this.ctx.showError("No agent messages to copy yet.");
|
|
280
|
-
return;
|
|
281
|
-
}
|
|
282
|
-
const matches = [...text.matchAll(/^```[^\n]*\n([\s\S]*?)^```/gm)];
|
|
283
|
-
const lastMatch = matches.at(-1);
|
|
284
|
-
if (!lastMatch) {
|
|
285
|
-
this.ctx.showWarning("No code block found in the last agent message.");
|
|
286
|
-
return;
|
|
287
|
-
}
|
|
288
|
-
this.#doCopy(lastMatch[1].replace(/\n$/, ""), "Copied last code block to clipboard");
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
#copyAllCode() {
|
|
292
|
-
const text = this.ctx.session.getLastAssistantText();
|
|
293
|
-
if (!text) {
|
|
294
|
-
this.ctx.showError("No agent messages to copy yet.");
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
const matches = [...text.matchAll(/^```[^\n]*\n([\s\S]*?)^```/gm)];
|
|
298
|
-
if (matches.length === 0) {
|
|
299
|
-
this.ctx.showWarning("No code blocks found in the last agent message.");
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
302
|
-
const combined = matches.map(m => m[1].replace(/\n$/, "")).join("\n\n");
|
|
303
|
-
this.#doCopy(combined, `Copied ${matches.length} code block${matches.length > 1 ? "s" : ""} to clipboard`);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
#extractEvalCode(args: unknown): string | undefined {
|
|
307
|
-
if (!args || typeof args !== "object") return undefined;
|
|
308
|
-
const cells = (args as { cells?: unknown }).cells;
|
|
309
|
-
if (!Array.isArray(cells)) return undefined;
|
|
310
|
-
|
|
311
|
-
const codeBlocks: string[] = [];
|
|
312
|
-
for (const cell of cells) {
|
|
313
|
-
if (!cell || typeof cell !== "object") continue;
|
|
314
|
-
const code = (cell as { code?: unknown }).code;
|
|
315
|
-
if (typeof code === "string" && code.length > 0) {
|
|
316
|
-
codeBlocks.push(code);
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
return codeBlocks.length > 0 ? codeBlocks.join("\n\n") : undefined;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
#copyLastCommand() {
|
|
324
|
-
const messages = this.ctx.session.messages;
|
|
325
|
-
// Walk backwards to find the last bash/eval tool call
|
|
326
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
327
|
-
const msg = messages[i];
|
|
328
|
-
if (msg.role !== "assistant") continue;
|
|
329
|
-
const toolCalls = msg.content.filter((c): c is ToolCall => c.type === "toolCall");
|
|
330
|
-
for (let j = toolCalls.length - 1; j >= 0; j--) {
|
|
331
|
-
const tc = toolCalls[j];
|
|
332
|
-
if (tc.name === "bash" && typeof tc.arguments.command === "string") {
|
|
333
|
-
this.#doCopy(tc.arguments.command, "Copied last bash command to clipboard");
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
336
|
-
if (tc.name === "eval") {
|
|
337
|
-
const code = this.#extractEvalCode(tc.arguments);
|
|
338
|
-
if (code) {
|
|
339
|
-
this.#doCopy(code, "Copied last eval code to clipboard");
|
|
340
|
-
return;
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
this.ctx.showWarning("No bash or eval command found in the conversation.");
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
#doCopy(content: string, label: string) {
|
|
349
|
-
try {
|
|
350
|
-
copyToClipboard(content);
|
|
351
|
-
this.ctx.showStatus(label);
|
|
352
|
-
} catch (error) {
|
|
353
|
-
this.ctx.showError(error instanceof Error ? error.message : String(error));
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
241
|
async handleSessionCommand(): Promise<void> {
|
|
358
242
|
const stats = this.ctx.session.getSessionStats();
|
|
359
243
|
const premiumRequests =
|
|
@@ -581,7 +465,7 @@ export class CommandController {
|
|
|
581
465
|
const argumentText = text.slice(7).trim();
|
|
582
466
|
const action = argumentText.split(/\s+/, 1)[0]?.toLowerCase() || "view";
|
|
583
467
|
const agentDir = this.ctx.settings.getAgentDir();
|
|
584
|
-
const backend = resolveMemoryBackend(this.ctx.settings);
|
|
468
|
+
const backend = await resolveMemoryBackend(this.ctx.settings);
|
|
585
469
|
|
|
586
470
|
if (action === "view") {
|
|
587
471
|
const payload = await backend.buildDeveloperInstructions(agentDir, this.ctx.settings, this.ctx.session);
|
|
@@ -1388,6 +1272,8 @@ function formatAccountLabel(limit: UsageLimit, report: UsageReport, index: numbe
|
|
|
1388
1272
|
if (email) return email;
|
|
1389
1273
|
const accountId = (report.metadata?.accountId as string | undefined) ?? limit.scope.accountId;
|
|
1390
1274
|
if (accountId) return accountId;
|
|
1275
|
+
const projectId = (report.metadata?.projectId as string | undefined) ?? limit.scope.projectId;
|
|
1276
|
+
if (projectId) return projectId;
|
|
1391
1277
|
return `account ${index + 1}`;
|
|
1392
1278
|
}
|
|
1393
1279
|
|
|
@@ -1396,6 +1282,8 @@ function formatUnlimitedReportLabel(report: UsageReport, index: number): string
|
|
|
1396
1282
|
if (email) return email;
|
|
1397
1283
|
const accountId = report.metadata?.accountId as string | undefined;
|
|
1398
1284
|
if (accountId) return accountId;
|
|
1285
|
+
const projectId = report.metadata?.projectId as string | undefined;
|
|
1286
|
+
if (projectId) return projectId;
|
|
1399
1287
|
return `account ${index + 1}`;
|
|
1400
1288
|
}
|
|
1401
1289
|
|
|
@@ -1481,6 +1369,13 @@ function formatAggregateAmount(limits: UsageLimit[]): string {
|
|
|
1481
1369
|
return `${formatNumber(remainingPct)}% free`;
|
|
1482
1370
|
}
|
|
1483
1371
|
|
|
1372
|
+
// Count unique accounts from limit scopes — not limits.length.
|
|
1373
|
+
const uniqueAccountIds = new Set(
|
|
1374
|
+
limits.map(limit => limit.scope.accountId).filter((id): id is string => typeof id === "string" && id.length > 0),
|
|
1375
|
+
);
|
|
1376
|
+
if (uniqueAccountIds.size > 0) return `${uniqueAccountIds.size} ${uniqueAccountIds.size === 1 ? "acct" : "accts"}`;
|
|
1377
|
+
// No account IDs available — keep the pre-existing fallback so providers
|
|
1378
|
+
// that don't populate scope.accountId still show a summary.
|
|
1484
1379
|
return `${limits.length} accts`;
|
|
1485
1380
|
}
|
|
1486
1381
|
|
|
@@ -49,9 +49,15 @@ export class EventController {
|
|
|
49
49
|
#lastIntent: string | undefined = undefined;
|
|
50
50
|
#backgroundToolCallIds = new Set<string>();
|
|
51
51
|
#assistantMessageStreaming = false;
|
|
52
|
+
#agentTurnActive = false;
|
|
52
53
|
#readToolCallArgs = new Map<string, Record<string, unknown>>();
|
|
53
54
|
#readToolCallAssistantComponents = new Map<string, AssistantMessageComponent>();
|
|
54
55
|
#lastAssistantComponent: AssistantMessageComponent | undefined = undefined;
|
|
56
|
+
// Assistant component whose turn-ending error is currently mirrored in the
|
|
57
|
+
// pinned banner. Its inline `Error: …` line is suppressed while pinned and
|
|
58
|
+
// restored when the banner clears at the next `agent_start` (see
|
|
59
|
+
// #handleMessageEnd / #handleAgentStart).
|
|
60
|
+
#pinnedErrorComponent: AssistantMessageComponent | undefined = undefined;
|
|
55
61
|
#idleCompactionTimer?: NodeJS.Timeout;
|
|
56
62
|
#ircExpiryTimers = new Map<string, NodeJS.Timeout>();
|
|
57
63
|
#handlers: AgentSessionEventHandlers;
|
|
@@ -172,21 +178,21 @@ export class EventController {
|
|
|
172
178
|
|
|
173
179
|
const run = this.#handlers[event.type] as (e: AgentSessionEvent) => Promise<void>;
|
|
174
180
|
await run(event);
|
|
175
|
-
// While assistant
|
|
176
|
-
//
|
|
177
|
-
// (Markdown fences, wrapping, previews). Let the
|
|
178
|
-
//
|
|
179
|
-
//
|
|
180
|
-
// Background-running tools are excluded so late async
|
|
181
|
-
//
|
|
182
|
-
//
|
|
181
|
+
// While an assistant turn is active, visible status chrome and foreground
|
|
182
|
+
// transcript blocks can re-render after rows have entered native scrollback
|
|
183
|
+
// (idle Working loader, Markdown fences, wrapping, tool previews). Let the
|
|
184
|
+
// TUI use its foreground live-region path instead of idle deferral, which
|
|
185
|
+
// can otherwise leave the loader/status frame frozen until the next input.
|
|
186
|
+
// Background-running tools after the turn ends are excluded so late async
|
|
187
|
+
// updates keep the no-yank deferral; agent_start/agent_end bracket the
|
|
188
|
+
// foreground turn.
|
|
183
189
|
if (STREAM_RENDER_MODE_EVENTS[event.type]) {
|
|
184
190
|
this.#refreshToolRenderMode();
|
|
185
191
|
}
|
|
186
192
|
}
|
|
187
193
|
|
|
188
194
|
#refreshToolRenderMode(): void {
|
|
189
|
-
let foregroundToolActive = this.#assistantMessageStreaming;
|
|
195
|
+
let foregroundToolActive = this.#agentTurnActive || this.#assistantMessageStreaming;
|
|
190
196
|
if (!foregroundToolActive) {
|
|
191
197
|
for (const toolCallId of this.ctx.pendingTools.keys()) {
|
|
192
198
|
if (!this.#backgroundToolCallIds.has(toolCallId)) {
|
|
@@ -199,11 +205,16 @@ export class EventController {
|
|
|
199
205
|
}
|
|
200
206
|
|
|
201
207
|
async #handleAgentStart(_event: Extract<AgentSessionEvent, { type: "agent_start" }>): Promise<void> {
|
|
208
|
+
this.#agentTurnActive = true;
|
|
202
209
|
this.#lastIntent = undefined;
|
|
203
210
|
this.#readToolCallArgs.clear();
|
|
204
211
|
this.#readToolCallAssistantComponents.clear();
|
|
205
212
|
this.#assistantMessageStreaming = false;
|
|
206
213
|
this.#lastAssistantComponent = undefined;
|
|
214
|
+
// Restore the previous turn's inline error in the transcript before dropping
|
|
215
|
+
// the banner, so the error stays in history once the banner is gone.
|
|
216
|
+
this.#pinnedErrorComponent?.setErrorPinned(false);
|
|
217
|
+
this.#pinnedErrorComponent = undefined;
|
|
207
218
|
this.ctx.clearPinnedError();
|
|
208
219
|
if (this.ctx.retryEscapeHandler) {
|
|
209
220
|
this.ctx.editor.onEscape = this.ctx.retryEscapeHandler;
|
|
@@ -215,6 +226,7 @@ export class EventController {
|
|
|
215
226
|
this.ctx.statusContainer.clear();
|
|
216
227
|
}
|
|
217
228
|
this.#cancelIdleCompaction();
|
|
229
|
+
this.#refreshToolRenderMode();
|
|
218
230
|
this.ctx.ensureLoadingAnimation();
|
|
219
231
|
this.ctx.ui.requestRender();
|
|
220
232
|
}
|
|
@@ -493,12 +505,15 @@ export class EventController {
|
|
|
493
505
|
this.ctx.streamingMessage = undefined;
|
|
494
506
|
// Pin a turn-ending provider error (e.g. Anthropic content-filter block)
|
|
495
507
|
// above the editor so it survives transcript scroll. Cleared at the next
|
|
496
|
-
// turn's agent_start.
|
|
508
|
+
// turn's agent_start. Suppress the transcript's inline `Error: …` line for
|
|
509
|
+
// the same message while pinned so the error isn't rendered twice.
|
|
497
510
|
if (
|
|
498
511
|
event.message.stopReason === "error" &&
|
|
499
512
|
event.message.errorMessage &&
|
|
500
513
|
!isSilentAbort(event.message.errorMessage)
|
|
501
514
|
) {
|
|
515
|
+
this.#lastAssistantComponent?.setErrorPinned(true);
|
|
516
|
+
this.#pinnedErrorComponent = this.#lastAssistantComponent;
|
|
502
517
|
this.ctx.showPinnedError(event.message.errorMessage);
|
|
503
518
|
}
|
|
504
519
|
this.ctx.statusLine.invalidate();
|
|
@@ -646,6 +661,7 @@ export class EventController {
|
|
|
646
661
|
}
|
|
647
662
|
}
|
|
648
663
|
async #handleAgentEnd(_event: Extract<AgentSessionEvent, { type: "agent_end" }>): Promise<void> {
|
|
664
|
+
this.#agentTurnActive = false;
|
|
649
665
|
this.#assistantMessageStreaming = false;
|
|
650
666
|
if (this.ctx.loadingAnimation) {
|
|
651
667
|
this.ctx.loadingAnimation.stop();
|
|
@@ -144,6 +144,8 @@ export class InputController {
|
|
|
144
144
|
this.ctx.editor.setActionKeys("app.clear", this.ctx.keybindings.getKeys("app.clear"));
|
|
145
145
|
this.ctx.editor.onClear = () => this.handleCtrlC();
|
|
146
146
|
this.ctx.editor.setActionKeys("app.exit", this.ctx.keybindings.getKeys("app.exit"));
|
|
147
|
+
this.ctx.editor.setActionKeys("app.display.reset", this.ctx.keybindings.getKeys("app.display.reset"));
|
|
148
|
+
this.ctx.editor.onDisplayReset = () => this.ctx.ui.resetDisplay();
|
|
147
149
|
this.ctx.editor.onExit = () => this.handleCtrlD();
|
|
148
150
|
this.ctx.editor.setActionKeys("app.suspend", this.ctx.keybindings.getKeys("app.suspend"));
|
|
149
151
|
this.ctx.editor.onSuspend = () => this.handleCtrlZ();
|
|
@@ -188,11 +190,9 @@ export class InputController {
|
|
|
188
190
|
this.ctx.editor.onExpandTools = () => this.toggleToolOutputExpansion();
|
|
189
191
|
this.ctx.editor.setActionKeys("app.message.dequeue", this.ctx.keybindings.getKeys("app.message.dequeue"));
|
|
190
192
|
this.ctx.editor.onDequeue = () => this.handleDequeue();
|
|
191
|
-
|
|
192
193
|
this.ctx.editor.clearCustomKeyHandlers();
|
|
193
194
|
// Wire up extension shortcuts
|
|
194
195
|
this.registerExtensionShortcuts();
|
|
195
|
-
|
|
196
196
|
const planModeKeys = this.ctx.keybindings.getKeys("app.plan.toggle");
|
|
197
197
|
for (const key of planModeKeys) {
|
|
198
198
|
this.ctx.editor.setCustomKeyHandler(key, () => void this.ctx.handlePlanModeCommand());
|
|
@@ -846,7 +846,15 @@ export class InputController {
|
|
|
846
846
|
child.setExpanded(expanded);
|
|
847
847
|
}
|
|
848
848
|
}
|
|
849
|
-
|
|
849
|
+
// Toggling expansion mutates every block, but on ED3-risk terminals the
|
|
850
|
+
// transcript freezes a snapshot of each block once it scrolls past the live
|
|
851
|
+
// region (committed native scrollback is immutable there). A plain repaint
|
|
852
|
+
// replays those stale snapshots, so the toggle appears to do nothing above
|
|
853
|
+
// the live block. resetDisplay() invalidates the snapshots and forces a
|
|
854
|
+
// full clear + replay — the keyboard-accessible resize-reset equivalent —
|
|
855
|
+
// which is the only path that re-emits the whole transcript at its new
|
|
856
|
+
// heights.
|
|
857
|
+
this.ctx.ui.resetDisplay();
|
|
850
858
|
}
|
|
851
859
|
|
|
852
860
|
toggleThinkingBlockVisibility(): void {
|