@oh-my-pi/pi-coding-agent 16.3.0 → 16.3.3
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 +91 -0
- package/dist/cli.js +3855 -3798
- package/dist/types/cli/update-cli.d.ts +1 -0
- package/dist/types/cli/update-cli.test.d.ts +1 -0
- package/dist/types/commands/update.d.ts +5 -0
- package/dist/types/config/config-file.d.ts +1 -1
- package/dist/types/config/settings-schema.d.ts +20 -0
- package/dist/types/dap/client.d.ts +9 -1
- package/dist/types/discovery/helpers.d.ts +2 -0
- package/dist/types/edit/file-snapshot-store.d.ts +9 -1
- package/dist/types/edit/hashline/execute.d.ts +1 -1
- package/dist/types/edit/hashline/params.d.ts +3 -6
- package/dist/types/edit/renderer.d.ts +1 -0
- package/dist/types/extensibility/plugins/parser.d.ts +2 -1
- package/dist/types/hindsight/client.d.ts +15 -11
- package/dist/types/hindsight/client.test.d.ts +1 -0
- package/dist/types/irc/bus.d.ts +5 -4
- package/dist/types/mcp/smithery-registry.d.ts +1 -0
- package/dist/types/mcp/smithery-registry.test.d.ts +1 -0
- package/dist/types/modes/components/advisor-message.d.ts +4 -2
- package/dist/types/modes/components/tool-execution.d.ts +9 -6
- package/dist/types/modes/components/transcript-container.d.ts +1 -0
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +13 -13
- package/dist/types/session/indexed-session-storage.d.ts +2 -2
- package/dist/types/session/session-storage.d.ts +31 -3
- package/dist/types/ssh/__tests__/connection-manager-timeout.test.d.ts +1 -0
- package/dist/types/ssh/__tests__/sshfs-mount.test.d.ts +1 -0
- package/dist/types/ssh/connection-manager.d.ts +19 -0
- package/dist/types/ssh/sshfs-mount.d.ts +10 -1
- package/dist/types/subprocess/worker-client.d.ts +20 -6
- package/dist/types/task/discovery.d.ts +5 -2
- package/dist/types/task/renderer.d.ts +1 -0
- package/dist/types/tools/ast-grep.d.ts +4 -2
- package/dist/types/tools/bash.d.ts +27 -0
- package/dist/types/tools/browser/render.d.ts +2 -0
- package/dist/types/tools/debug.d.ts +1 -0
- package/dist/types/tools/eval-render.d.ts +2 -0
- package/dist/types/tools/glob.d.ts +4 -2
- package/dist/types/tools/grep.d.ts +4 -3
- package/dist/types/tools/path-utils.d.ts +7 -0
- package/dist/types/tools/renderers.d.ts +24 -0
- package/dist/types/tools/ssh.d.ts +4 -1
- package/dist/types/tts/index.d.ts +2 -0
- package/dist/types/tts/speakable.d.ts +47 -0
- package/dist/types/tts/speech-enhancer.d.ts +46 -0
- package/dist/types/tts/streaming-player.d.ts +1 -2
- package/dist/types/tts/tts-client.d.ts +11 -10
- package/dist/types/tts/tts-protocol.d.ts +7 -0
- package/dist/types/tts/vocalizer.d.ts +15 -8
- package/dist/types/utils/fetch-timeout.d.ts +4 -0
- package/dist/types/utils/git.d.ts +2 -0
- package/dist/types/web/search/providers/base.d.ts +1 -0
- package/dist/types/web/search/providers/gemini.d.ts +1 -0
- package/package.json +12 -12
- package/scripts/generate-legacy-pi-bundled-registry.ts +8 -2
- package/src/advisor/__tests__/advisor.test.ts +1 -1
- package/src/cli/gallery-fixtures/fs.ts +2 -2
- package/src/cli/gallery-fixtures/search.ts +2 -2
- package/src/cli/models-cli.ts +19 -0
- package/src/cli/update-cli.test.ts +28 -0
- package/src/cli/update-cli.ts +35 -8
- package/src/cli.ts +27 -5
- package/src/commands/update.ts +8 -2
- package/src/config/config-file.ts +6 -6
- package/src/config/model-resolver.ts +31 -10
- package/src/config/settings-schema.ts +21 -0
- package/src/cursor.ts +1 -1
- package/src/dap/client.ts +134 -36
- package/src/discovery/helpers.ts +8 -0
- package/src/edit/file-snapshot-store.ts +12 -1
- package/src/edit/hashline/diff.ts +4 -13
- package/src/edit/hashline/execute.ts +1 -1
- package/src/edit/hashline/params.ts +5 -12
- package/src/edit/renderer.ts +4 -2
- package/src/edit/streaming.ts +15 -5
- package/src/export/html/tool-views.generated.js +34 -34
- package/src/extensibility/custom-tools/loader.ts +3 -3
- package/src/extensibility/extensions/loader.ts +10 -3
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +2 -2
- package/src/extensibility/plugins/installer.ts +12 -3
- package/src/extensibility/plugins/legacy-pi-compat.ts +87 -11
- package/src/extensibility/plugins/loader.ts +30 -1
- package/src/extensibility/plugins/manager.ts +32 -8
- package/src/extensibility/plugins/parser.ts +7 -5
- package/src/extensibility/tool-event-input.ts +1 -1
- package/src/hindsight/client.test.ts +33 -0
- package/src/hindsight/client.ts +42 -22
- package/src/internal-urls/docs-index.generated.txt +1 -1
- package/src/irc/bus.ts +5 -4
- package/src/main.ts +7 -1
- package/src/mcp/oauth-flow.ts +93 -4
- package/src/mcp/smithery-auth.ts +3 -0
- package/src/mcp/smithery-connect.ts +9 -0
- package/src/mcp/smithery-registry.test.ts +51 -0
- package/src/mcp/smithery-registry.ts +27 -4
- package/src/modes/components/__tests__/move-overlay.test.ts +72 -1
- package/src/modes/components/advisor-message.ts +13 -10
- package/src/modes/components/assistant-message.ts +1 -1
- package/src/modes/components/move-overlay.ts +35 -23
- package/src/modes/components/status-line/component.ts +11 -4
- package/src/modes/components/tool-execution.ts +119 -20
- package/src/modes/components/transcript-container.ts +26 -0
- package/src/modes/components/tree-selector.ts +10 -3
- package/src/modes/controllers/event-controller.ts +23 -3
- package/src/modes/controllers/tool-args-reveal.ts +1 -1
- package/src/modes/interactive-mode.ts +1 -0
- package/src/modes/rpc/rpc-client.ts +29 -16
- package/src/modes/theme/shimmer.ts +49 -15
- package/src/modes/theme/theme.ts +7 -0
- package/src/prompts/goals/goal-mode-context.md +4 -0
- package/src/prompts/goals/goal-todo-context.md +10 -2
- package/src/prompts/system/speech-rewrite.md +15 -0
- package/src/prompts/system/tiny-title-system.md +1 -1
- package/src/prompts/system/title-system-marker.md +2 -1
- package/src/prompts/system/title-system.md +2 -1
- package/src/prompts/tools/ast-grep.md +3 -3
- package/src/prompts/tools/glob.md +1 -1
- package/src/prompts/tools/grep.md +1 -1
- package/src/sdk.ts +8 -5
- package/src/session/agent-session.ts +329 -79
- package/src/session/indexed-session-storage.ts +40 -3
- package/src/session/session-history-format.ts +6 -2
- package/src/session/session-manager.ts +83 -14
- package/src/session/session-storage.ts +112 -26
- package/src/slash-commands/helpers/usage-report.ts +6 -1
- package/src/ssh/__tests__/connection-manager-timeout.test.ts +61 -0
- package/src/ssh/__tests__/sshfs-mount.test.ts +13 -0
- package/src/ssh/connection-manager.ts +44 -11
- package/src/ssh/sshfs-mount.ts +27 -4
- package/src/subprocess/worker-client.ts +161 -10
- package/src/task/discovery.ts +25 -2
- package/src/task/executor.ts +24 -4
- package/src/task/render.ts +26 -12
- package/src/task/renderer.ts +1 -0
- package/src/task/worktree.ts +144 -16
- package/src/tiny/text.ts +109 -17
- package/src/tools/ast-grep.ts +20 -17
- package/src/tools/bash.ts +46 -9
- package/src/tools/browser/render.ts +2 -0
- package/src/tools/debug.ts +1 -0
- package/src/tools/eval-render.ts +5 -2
- package/src/tools/gh-renderer.ts +3 -0
- package/src/tools/glob.ts +24 -20
- package/src/tools/grep.ts +39 -37
- package/src/tools/path-utils.ts +55 -10
- package/src/tools/read.ts +23 -6
- package/src/tools/renderers.ts +24 -0
- package/src/tools/ssh.ts +25 -7
- package/src/tts/index.ts +2 -0
- package/src/tts/speakable.ts +382 -0
- package/src/tts/speech-enhancer.ts +204 -0
- package/src/tts/streaming-player.ts +71 -16
- package/src/tts/tts-client.ts +11 -10
- package/src/tts/tts-protocol.ts +14 -5
- package/src/tts/tts-worker.ts +52 -49
- package/src/tts/vocalizer.ts +277 -46
- package/src/utils/clipboard.ts +49 -12
- package/src/utils/fetch-timeout.ts +10 -0
- package/src/utils/git.ts +8 -0
- package/src/web/search/index.ts +8 -0
- package/src/web/search/providers/base.ts +1 -0
- package/src/web/search/providers/gemini.ts +23 -6
|
@@ -4,7 +4,6 @@ import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
|
4
4
|
import type { AssistantMessage, UsageLimit, UsageReport } from "@oh-my-pi/pi-ai";
|
|
5
5
|
import { type Component, truncateToWidth, visibleWidth } from "@oh-my-pi/pi-tui";
|
|
6
6
|
import { getProjectDir } from "@oh-my-pi/pi-utils";
|
|
7
|
-
import { $ } from "bun";
|
|
8
7
|
import { settings } from "../../../config/settings";
|
|
9
8
|
import type { AgentSession } from "../../../session/agent-session";
|
|
10
9
|
import type { OAuthAccountIdentity } from "../../../session/auth-storage";
|
|
@@ -707,14 +706,22 @@ export class StatusLineComponent implements Component {
|
|
|
707
706
|
}
|
|
708
707
|
};
|
|
709
708
|
try {
|
|
710
|
-
//
|
|
711
|
-
|
|
709
|
+
// Route through the shared `gh` helper so the child inherits
|
|
710
|
+
// `GH_NON_INTERACTIVE_ENV` (disables terminal/keychain prompts) and
|
|
711
|
+
// hard-terminates on the git command deadline instead of stalling
|
|
712
|
+
// the status-line indefinitely (#4234). Requires `gh repo set-default`;
|
|
713
|
+
// non-zero exit still falls through to the null cache below.
|
|
714
|
+
const result = await git.github.run(
|
|
715
|
+
lookupCwd,
|
|
716
|
+
["pr", "view", "--json", "number,url"],
|
|
717
|
+
AbortSignal.timeout(git.GIT_COMMAND_TIMEOUT_MS),
|
|
718
|
+
);
|
|
712
719
|
if (this.#disposed) return;
|
|
713
720
|
if (result.exitCode !== 0) {
|
|
714
721
|
setCachedPr(null);
|
|
715
722
|
return;
|
|
716
723
|
}
|
|
717
|
-
const pr = JSON.parse(result.stdout
|
|
724
|
+
const pr = JSON.parse(result.stdout) as { number: number; url: string };
|
|
718
725
|
if (typeof pr.number === "number") {
|
|
719
726
|
setCachedPr({ number: pr.number, url: pr.url });
|
|
720
727
|
} else {
|
|
@@ -182,12 +182,14 @@ export interface ToolExecutionHandle {
|
|
|
182
182
|
setExpanded(expanded: boolean): void;
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
-
/**
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
*
|
|
185
|
+
/** Redraw live tool blocks at the spinner's glyph-advance rate. Rendering more
|
|
186
|
+
* often produced identical frames — the previous 30fps cadence emitted ~2.4
|
|
187
|
+
* paints per glyph step, and although the terminal I/O layer dedupes those, the
|
|
188
|
+
* compose pipeline still ran end-to-end per frame (issue #4353). Matching the
|
|
189
|
+
* render tick to the glyph tick halves the paints during tool execution with no
|
|
190
|
+
* visible change. */
|
|
191
|
+
export const SPINNER_RENDER_INTERVAL_MS = 80;
|
|
192
|
+
/** Advance the spinner glyph at its classic ~12.5fps step (mirrors `Loader`). */
|
|
191
193
|
export const SPINNER_GLYPH_ADVANCE_MS = 80;
|
|
192
194
|
|
|
193
195
|
/** Phase-locked spinner glyph index shared by every live tool block so parallel
|
|
@@ -281,6 +283,14 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
|
|
|
281
283
|
// history, so progress renders static gray and further partial snapshots are
|
|
282
284
|
// dropped (see #maybeFreezeBackgroundTask).
|
|
283
285
|
#backgroundTaskFrozen = false;
|
|
286
|
+
// Set on each `render()` when the last painted shape carried the streamed
|
|
287
|
+
// SSH-style placeholder / partial-result chrome. Reset gates key off these
|
|
288
|
+
// so a topology-changing update that lands before the shape reaches the
|
|
289
|
+
// terminal never triggers a full-viewport replay (which on direct terminals
|
|
290
|
+
// wipes native scrollback and flashes the user's history — reviewer note on
|
|
291
|
+
// PR #4315).
|
|
292
|
+
#placeholderShapePainted = false;
|
|
293
|
+
#partialResultShapePainted = false;
|
|
284
294
|
#renderState: {
|
|
285
295
|
spinnerFrame?: number;
|
|
286
296
|
expanded: boolean;
|
|
@@ -334,6 +344,7 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
|
|
|
334
344
|
// so keep their horizontal padding even when the user enables tight layout.
|
|
335
345
|
this.setIgnoreTight(true);
|
|
336
346
|
|
|
347
|
+
this.#updateSpinnerAnimation();
|
|
337
348
|
this.#updateDisplay();
|
|
338
349
|
this.#schedulePreviewDiff();
|
|
339
350
|
}
|
|
@@ -484,6 +495,12 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
|
|
|
484
495
|
if (isPartial && this.#toolName === "task" && this.#maybeFreezeBackgroundTask()) {
|
|
485
496
|
return;
|
|
486
497
|
}
|
|
498
|
+
const hadNoResult = this.#result === undefined;
|
|
499
|
+
const wasPartialResult = this.#result !== undefined && this.#isPartial;
|
|
500
|
+
const placeholderPainted = this.#placeholderShapePainted;
|
|
501
|
+
const partialResultPainted = this.#partialResultShapePainted;
|
|
502
|
+
this.#placeholderShapePainted = false;
|
|
503
|
+
this.#partialResultShapePainted = false;
|
|
487
504
|
this.#result = result;
|
|
488
505
|
this.#resultVersion++;
|
|
489
506
|
this.#isPartial = isPartial;
|
|
@@ -495,6 +512,11 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
|
|
|
495
512
|
this.#updateSpinnerAnimation();
|
|
496
513
|
this.#updateTodoStrikeAnimation();
|
|
497
514
|
this.#updateDisplay();
|
|
515
|
+
this.#resetDisplayForResultTopologyChange(
|
|
516
|
+
hadNoResult && placeholderPainted,
|
|
517
|
+
wasPartialResult && partialResultPainted,
|
|
518
|
+
isPartial,
|
|
519
|
+
);
|
|
498
520
|
// Convert non-PNG images to PNG for Kitty protocol (async)
|
|
499
521
|
this.#maybeConvertImagesForKitty();
|
|
500
522
|
}
|
|
@@ -546,21 +568,47 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
|
|
|
546
568
|
}
|
|
547
569
|
|
|
548
570
|
/**
|
|
549
|
-
* Start or stop spinner animation for
|
|
571
|
+
* Start or stop spinner animation for live states that visibly tick.
|
|
550
572
|
*/
|
|
551
573
|
#updateSpinnerAnimation(): void {
|
|
552
|
-
//
|
|
553
|
-
//
|
|
554
|
-
//
|
|
574
|
+
// Live partial tool blocks stay repaintable until a terminal result seals
|
|
575
|
+
// them. Todo snapshots and detached background tool progress are deliberate
|
|
576
|
+
// static exceptions because their rows can be superseded or committed to
|
|
577
|
+
// scrollback while later updates continue elsewhere.
|
|
555
578
|
const isStreamingArgs = !this.#argsComplete && (isEditLikeToolName(this.#toolName) || this.#toolName === "write");
|
|
556
579
|
const isBackgroundAsyncRunning =
|
|
557
580
|
(this.#result?.details as { async?: { state?: string } } | undefined)?.async?.state === "running";
|
|
558
|
-
const
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
581
|
+
const renderer = toolRenderers[this.#toolName] as
|
|
582
|
+
| {
|
|
583
|
+
animatedPendingPreview?: boolean | ((args: unknown) => boolean);
|
|
584
|
+
animatedPartialResult?: boolean | ((args: unknown) => boolean);
|
|
585
|
+
}
|
|
586
|
+
| undefined;
|
|
587
|
+
const pendingAnimation = renderer?.animatedPendingPreview;
|
|
588
|
+
const partialAnimation = renderer?.animatedPartialResult;
|
|
589
|
+
const pendingCallConsumesSpinner =
|
|
590
|
+
this.#result === undefined &&
|
|
591
|
+
(renderer === undefined
|
|
592
|
+
? // Only the generic #formatToolExecution fallback consumes the frame;
|
|
593
|
+
// a custom renderCall/renderResult pair routes through the custom
|
|
594
|
+
// branch whose pending label is a static tool-name Text.
|
|
595
|
+
!this.#tool?.renderCall && !this.#tool?.renderResult
|
|
596
|
+
: typeof pendingAnimation === "function"
|
|
597
|
+
? pendingAnimation(this.#args)
|
|
598
|
+
: pendingAnimation === true);
|
|
599
|
+
const partialResultConsumesSpinner =
|
|
600
|
+
this.#result !== undefined &&
|
|
601
|
+
(renderer === undefined
|
|
602
|
+
? !this.#tool?.renderCall && !this.#tool?.renderResult
|
|
603
|
+
: typeof partialAnimation === "function"
|
|
604
|
+
? partialAnimation(this.#args)
|
|
605
|
+
: partialAnimation === true);
|
|
606
|
+
const isLivePartialTool =
|
|
607
|
+
this.#isPartial &&
|
|
608
|
+
this.#toolName !== "todo" &&
|
|
609
|
+
!isBackgroundAsyncRunning &&
|
|
610
|
+
(pendingCallConsumesSpinner || partialResultConsumesSpinner);
|
|
611
|
+
const needsSpinner = isStreamingArgs || isLivePartialTool || this.#displaceableByToolName === "job";
|
|
564
612
|
if (needsSpinner && !this.#spinnerInterval) {
|
|
565
613
|
const frameCount = theme.spinnerFrames.length;
|
|
566
614
|
const frame = sharedSpinnerFrame(frameCount);
|
|
@@ -803,6 +851,49 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
|
|
|
803
851
|
this.#displayBuilt = true;
|
|
804
852
|
}
|
|
805
853
|
|
|
854
|
+
#rendererFlag(name: "forceFirstResultViewportRepaint" | "forceResultViewportRepaintOnSettle"): boolean {
|
|
855
|
+
const toolValue = (this.#tool as Record<string, unknown> | undefined)?.[name];
|
|
856
|
+
const rendererValue = toolRenderers[this.#toolName]?.[name];
|
|
857
|
+
return toolValue === true || (toolValue === undefined && rendererValue === true);
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
/**
|
|
861
|
+
* True while the last painted shape uses the streamed placeholder path
|
|
862
|
+
* (`⏳ SSH: […]` / `$ …`) — the render call ran with `__partialJson` args
|
|
863
|
+
* and no result. Kept as a per-paint fact so a topology-changing update
|
|
864
|
+
* that lands before the placeholder reaches the terminal skips the reset.
|
|
865
|
+
*/
|
|
866
|
+
#isPlaceholderShapeAtRender(): boolean {
|
|
867
|
+
if (this.#result !== undefined) return false;
|
|
868
|
+
if (!this.#rendererFlag("forceFirstResultViewportRepaint")) return false;
|
|
869
|
+
return partialJsonOf(this.#args) !== undefined;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
#resetDisplayForResultTopologyChange(
|
|
873
|
+
firstResultAfterPlaceholderPaint: boolean,
|
|
874
|
+
partialResultPaintedBeforeSettle: boolean,
|
|
875
|
+
isPartial: boolean,
|
|
876
|
+
): void {
|
|
877
|
+
const firstResultReplacesStreamedPlaceholder =
|
|
878
|
+
firstResultAfterPlaceholderPaint && this.#rendererFlag("forceFirstResultViewportRepaint");
|
|
879
|
+
const provisionalResultSettled =
|
|
880
|
+
partialResultPaintedBeforeSettle && !isPartial && this.#rendererFlag("forceResultViewportRepaintOnSettle");
|
|
881
|
+
if (firstResultReplacesStreamedPlaceholder || provisionalResultSettled) {
|
|
882
|
+
this.#ui.resetDisplay();
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
override render(width: number): readonly string[] {
|
|
887
|
+
const lines = super.render(width);
|
|
888
|
+
// Update the paint-tracking flags after `super.render(width)` — the
|
|
889
|
+
// override runs on every compose the parent Container performs, so a
|
|
890
|
+
// frame that never gets composed leaves the flags false and prevents a
|
|
891
|
+
// spurious `resetDisplay()`.
|
|
892
|
+
this.#placeholderShapePainted = this.#isPlaceholderShapeAtRender();
|
|
893
|
+
this.#partialResultShapePainted = this.#result !== undefined && this.#isPartial;
|
|
894
|
+
return lines;
|
|
895
|
+
}
|
|
896
|
+
|
|
806
897
|
// Viewport-/settings-dependent image sizing folded into the memo key only when
|
|
807
898
|
// the last rebuild actually emitted images, so a terminal resize re-shapes an
|
|
808
899
|
// image-bearing result (to rescale it) without re-shaping every image-free
|
|
@@ -846,7 +937,8 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
|
|
|
846
937
|
if (shouldRenderCall) {
|
|
847
938
|
if (tool.renderCall) {
|
|
848
939
|
try {
|
|
849
|
-
const
|
|
940
|
+
const callArgs = this.#getCallArgsForRender();
|
|
941
|
+
const callComponent = tool.renderCall(callArgs, this.#renderState, theme);
|
|
850
942
|
if (callComponent) this.#contentBox.addChild(callComponent as Component);
|
|
851
943
|
} catch (err) {
|
|
852
944
|
logger.warn("Tool renderer failed", { tool: this.#toolName, error: String(err) });
|
|
@@ -980,7 +1072,8 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
|
|
|
980
1072
|
if (shouldRenderCall) {
|
|
981
1073
|
// Render call component
|
|
982
1074
|
try {
|
|
983
|
-
const
|
|
1075
|
+
const callArgs = this.#getCallArgsForRender();
|
|
1076
|
+
const callComponent = renderer.renderCall(callArgs, this.#renderState, theme);
|
|
984
1077
|
if (callComponent) this.#contentBox.addChild(callComponent);
|
|
985
1078
|
} catch (err) {
|
|
986
1079
|
logger.warn("Tool renderer failed", { tool: this.#toolName, error: String(err) });
|
|
@@ -1172,8 +1265,14 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
|
|
|
1172
1265
|
*/
|
|
1173
1266
|
#formatToolExecution(contentWidth: number): string {
|
|
1174
1267
|
const lines: string[] = [];
|
|
1175
|
-
const icon = this.#isPartial
|
|
1176
|
-
|
|
1268
|
+
const icon = this.#isPartial
|
|
1269
|
+
? this.#spinnerFrame !== undefined
|
|
1270
|
+
? "running"
|
|
1271
|
+
: "pending"
|
|
1272
|
+
: this.#result?.isError
|
|
1273
|
+
? "error"
|
|
1274
|
+
: "done";
|
|
1275
|
+
lines.push(renderStatusLine({ icon, spinnerFrame: this.#spinnerFrame, title: this.#toolLabel }, theme));
|
|
1177
1276
|
|
|
1178
1277
|
const argsObject = this.#args && typeof this.#args === "object" ? (this.#args as Record<string, unknown>) : null;
|
|
1179
1278
|
if (!this.#expanded && argsObject && Object.keys(argsObject).length > 0) {
|
|
@@ -443,6 +443,10 @@ export class TranscriptContainer
|
|
|
443
443
|
// drift after commit; the engine commits them audit-exempt. Provisional
|
|
444
444
|
// (commit-unstable) blocks never extend it.
|
|
445
445
|
#nativeScrollbackSnapshotSafeEnd: number | undefined;
|
|
446
|
+
// Local line index through which lower finalized siblings are safe to OFFER to
|
|
447
|
+
// native scrollback while still audited. Unlike snapshotSafeEnd, rows below a
|
|
448
|
+
// live block are not durable: growth above them must repair stale history.
|
|
449
|
+
#nativeScrollbackOfferSafeEnd: number | undefined;
|
|
446
450
|
// Persistent assembled transcript rows. Rows before the stable floor are
|
|
447
451
|
// byte-identical to the previous render; rows at/after it were re-pushed.
|
|
448
452
|
#lines: string[] = [];
|
|
@@ -491,6 +495,10 @@ export class TranscriptContainer
|
|
|
491
495
|
return this.#nativeScrollbackSnapshotSafeEnd;
|
|
492
496
|
}
|
|
493
497
|
|
|
498
|
+
getNativeScrollbackOfferSafeEnd(): number | undefined {
|
|
499
|
+
return this.#nativeScrollbackOfferSafeEnd;
|
|
500
|
+
}
|
|
501
|
+
|
|
494
502
|
/**
|
|
495
503
|
* Whether `component` sits below a still-mutating block — i.e. inside the
|
|
496
504
|
* live region, where its rows cannot have been committed to native
|
|
@@ -583,6 +591,7 @@ export class TranscriptContainer
|
|
|
583
591
|
this.#nativeScrollbackLiveRegionStart = undefined;
|
|
584
592
|
this.#nativeScrollbackCommitSafeEnd = undefined;
|
|
585
593
|
this.#nativeScrollbackSnapshotSafeEnd = undefined;
|
|
594
|
+
this.#nativeScrollbackOfferSafeEnd = undefined;
|
|
586
595
|
|
|
587
596
|
const count = this.children.length;
|
|
588
597
|
|
|
@@ -629,6 +638,14 @@ export class TranscriptContainer
|
|
|
629
638
|
// liveStartIndex; empty leading blocks (or a separator) must not claim it
|
|
630
639
|
// early.
|
|
631
640
|
let liveRecorded = false;
|
|
641
|
+
// Prefix boundary for finalized siblings rendered below the first live
|
|
642
|
+
// block. These rows may be offered to native scrollback, but they cannot
|
|
643
|
+
// extend snapshotSafeEnd because a live block above can still move them.
|
|
644
|
+
let offerSafeEnd: number | undefined;
|
|
645
|
+
// Offer rows must be a contiguous finalized run below the first live
|
|
646
|
+
// block. A later live/provisional block can still push rows below it, so
|
|
647
|
+
// finalized siblings after that barrier must stay forced-overflow.
|
|
648
|
+
let offerSafeOpen = true;
|
|
632
649
|
// Frame row cursor: rows emitted (reused or pushed) so far.
|
|
633
650
|
let row = 0;
|
|
634
651
|
let stableRows = 0;
|
|
@@ -701,6 +718,7 @@ export class TranscriptContainer
|
|
|
701
718
|
// everything below it.
|
|
702
719
|
if (contribution.length === 0) {
|
|
703
720
|
if (i >= liveStartIndex && commitSafeOpen && !finalized) commitSafeOpen = false;
|
|
721
|
+
if (i > liveStartIndex && !finalized) offerSafeOpen = false;
|
|
704
722
|
if (chainStable && !(reusable && previous.rowCount === 0 && previous.startRow === row)) {
|
|
705
723
|
chainStable = false;
|
|
706
724
|
lines.length = row;
|
|
@@ -770,6 +788,13 @@ export class TranscriptContainer
|
|
|
770
788
|
// rows around as it grows, so the run closes there.
|
|
771
789
|
if (!(finalized && safeLength >= contribution.length)) commitSafeOpen = false;
|
|
772
790
|
}
|
|
791
|
+
if (i > liveStartIndex) {
|
|
792
|
+
if (offerSafeOpen && finalized) {
|
|
793
|
+
offerSafeEnd = blockStart + contribution.length;
|
|
794
|
+
} else if (!finalized) {
|
|
795
|
+
offerSafeOpen = false;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
773
798
|
|
|
774
799
|
segments[i] = {
|
|
775
800
|
component: child,
|
|
@@ -788,6 +813,7 @@ export class TranscriptContainer
|
|
|
788
813
|
// Trailing shrink: blocks removed from the tail leave stale rows behind
|
|
789
814
|
// when every surviving segment was reused.
|
|
790
815
|
if (lines.length !== row) lines.length = row;
|
|
816
|
+
this.#nativeScrollbackOfferSafeEnd = offerSafeEnd;
|
|
791
817
|
this.#segments = segments;
|
|
792
818
|
this.#stableRowsFloor = Math.min(stableFloorBefore, stableRows, row);
|
|
793
819
|
return lines;
|
|
@@ -15,7 +15,7 @@ import type { TreeFilterMode } from "../../config/settings-schema";
|
|
|
15
15
|
import { theme } from "../../modes/theme/theme";
|
|
16
16
|
import { matchesAppInterrupt, matchesSelectDown, matchesSelectUp } from "../../modes/utils/keybinding-matchers";
|
|
17
17
|
import type { SessionTreeNode } from "../../session/session-entries";
|
|
18
|
-
import { toPathList } from "../../tools/
|
|
18
|
+
import { toPathList } from "../../tools/path-utils";
|
|
19
19
|
import { shortenPath } from "../../tools/render-utils";
|
|
20
20
|
import { canonicalizeMessage } from "../../utils/thinking-display";
|
|
21
21
|
import { DynamicBorder } from "./dynamic-border";
|
|
@@ -753,8 +753,15 @@ class TreeList implements Component {
|
|
|
753
753
|
return `[grep: /${pattern}/ in ${shortenPath(scope)}]`;
|
|
754
754
|
}
|
|
755
755
|
case "glob": {
|
|
756
|
-
const
|
|
757
|
-
|
|
756
|
+
const globInput =
|
|
757
|
+
typeof args.path === "string"
|
|
758
|
+
? args.path
|
|
759
|
+
: typeof args.paths === "string" || Array.isArray(args.paths)
|
|
760
|
+
? args.paths
|
|
761
|
+
: undefined;
|
|
762
|
+
const paths = toPathList(globInput);
|
|
763
|
+
const scope = paths.length > 0 ? paths.join(", ") : ".";
|
|
764
|
+
return `[glob: ${shortenPath(scope)}]`;
|
|
758
765
|
}
|
|
759
766
|
case "ls": {
|
|
760
767
|
const path = shortenPath(String(args.path || "."));
|
|
@@ -27,6 +27,7 @@ import { isSilentAbort, readQueueChipText, resolveAbortLabel } from "../../sessi
|
|
|
27
27
|
import { previewLine, TRUNCATE_LENGTHS } from "../../tools/render-utils";
|
|
28
28
|
import type { ResolveToolDetails } from "../../tools/resolve";
|
|
29
29
|
import { nextActionableTask } from "../../tools/todo";
|
|
30
|
+
import { SpeechEnhancer } from "../../tts/speech-enhancer";
|
|
30
31
|
import { vocalizer } from "../../tts/vocalizer";
|
|
31
32
|
import { canonicalizeMessage } from "../../utils/thinking-display";
|
|
32
33
|
import { interruptHint } from "../shared";
|
|
@@ -111,6 +112,21 @@ export class EventController {
|
|
|
111
112
|
#terminalProgressActive = false;
|
|
112
113
|
|
|
113
114
|
constructor(private ctx: InteractiveModeContext) {
|
|
115
|
+
// Enhanced speech (`speech.enhanced`) rewrites blocks through the
|
|
116
|
+
// tiny/smol role with this session's registry and credentials; the
|
|
117
|
+
// vocalizer falls back to mechanical cleanup when unset. Tolerates
|
|
118
|
+
// partial contexts (tests, minimal embeddings) by wiring null.
|
|
119
|
+
const session = ctx.session;
|
|
120
|
+
vocalizer.setEnhancer(
|
|
121
|
+
session?.modelRegistry && session.agent && session.settings
|
|
122
|
+
? new SpeechEnhancer({
|
|
123
|
+
settings: session.settings,
|
|
124
|
+
registry: session.modelRegistry,
|
|
125
|
+
sessionId: session.sessionId,
|
|
126
|
+
metadataResolver: provider => session.agent.metadataForProvider(provider),
|
|
127
|
+
})
|
|
128
|
+
: null,
|
|
129
|
+
);
|
|
114
130
|
this.#streamingReveal = new StreamingRevealController({
|
|
115
131
|
getSmoothStreaming: () => this.ctx.settings.get("display.smoothStreaming"),
|
|
116
132
|
getHideThinkingBlock: () => this.ctx.effectiveHideThinkingBlock,
|
|
@@ -286,9 +302,13 @@ export class EventController {
|
|
|
286
302
|
await this.ctx.init();
|
|
287
303
|
}
|
|
288
304
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
305
|
+
// Each handler explicitly requests a render (or leaves it out, when it
|
|
306
|
+
// changed nothing visible). A blanket pre-render fired on every event —
|
|
307
|
+
// including the ~hundreds of `message_update` deltas per streaming turn —
|
|
308
|
+
// doubled the paint rate: the pre-render's frame fires while the handler
|
|
309
|
+
// is awaiting, then the handler's own final requestRender schedules a
|
|
310
|
+
// second identical frame. Removing it lets the render cadence follow real
|
|
311
|
+
// state changes rather than event volume (issue #4353).
|
|
292
312
|
const run = this.#handlers[event.type] as (e: AgentSessionEvent) => Promise<void>;
|
|
293
313
|
await run(event);
|
|
294
314
|
}
|
|
@@ -13,7 +13,7 @@ type ToolArgsRevealComponent = {
|
|
|
13
13
|
// patch/replace `edits[].diff`) still fall through to the throttled parse.
|
|
14
14
|
const STREAMING_STRING_KEYS_BY_TOOL: Record<string, readonly string[]> = {
|
|
15
15
|
write: ["content"],
|
|
16
|
-
edit: ["input"],
|
|
16
|
+
edit: ["input", "_input"],
|
|
17
17
|
eval: ["code"],
|
|
18
18
|
};
|
|
19
19
|
|
|
@@ -3584,6 +3584,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3584
3584
|
} else if (!this.statusContainer.children.includes(this.loadingAnimation)) {
|
|
3585
3585
|
this.statusContainer.clear();
|
|
3586
3586
|
this.statusContainer.addChild(this.loadingAnimation);
|
|
3587
|
+
this.ui.requestRender();
|
|
3587
3588
|
}
|
|
3588
3589
|
this.applyPendingWorkingMessage();
|
|
3589
3590
|
}
|
|
@@ -256,18 +256,19 @@ export class RpcClient {
|
|
|
256
256
|
args.push(...this.options.args);
|
|
257
257
|
}
|
|
258
258
|
|
|
259
|
-
|
|
259
|
+
const child = ptree.spawn(["bun", cliPath, ...args], {
|
|
260
260
|
cwd: this.options.cwd,
|
|
261
261
|
env: { ...Bun.env, ...this.options.env },
|
|
262
262
|
stdin: "pipe",
|
|
263
263
|
});
|
|
264
|
+
this.#process = child;
|
|
264
265
|
|
|
265
266
|
// Wait for the "ready" signal or process exit
|
|
266
267
|
const { promise: readyPromise, resolve: readyResolve, reject: readyReject } = Promise.withResolvers<void>();
|
|
267
268
|
let readySettled = false;
|
|
268
269
|
|
|
269
270
|
// Process lines in background, intercepting the ready signal
|
|
270
|
-
const lines = readJsonl(
|
|
271
|
+
const lines = readJsonl(child.stdout, this.#abortController.signal);
|
|
271
272
|
void (async () => {
|
|
272
273
|
for await (const line of lines) {
|
|
273
274
|
if (!readySettled && isRecord(line) && line.type === "ready") {
|
|
@@ -277,10 +278,16 @@ export class RpcClient {
|
|
|
277
278
|
}
|
|
278
279
|
this.#handleLine(line);
|
|
279
280
|
}
|
|
280
|
-
// Stream ended without ready signal —
|
|
281
|
+
// Stream ended without the ready signal — the child exited or is
|
|
282
|
+
// exiting. Defer to the exit handler below: ptree resolves
|
|
283
|
+
// `exited` only after stderr is fully drained (nonzero exits), so
|
|
284
|
+
// rejecting here would snapshot a partial stderr tail and lose
|
|
285
|
+
// the actual startup error.
|
|
286
|
+
if (readySettled) return;
|
|
287
|
+
await child.exited.catch(() => {});
|
|
281
288
|
if (!readySettled) {
|
|
282
289
|
readySettled = true;
|
|
283
|
-
readyReject(new Error(`Agent process exited before ready. Stderr: ${
|
|
290
|
+
readyReject(new Error(`Agent process exited before ready. Stderr: ${child.peekStderr()}`));
|
|
284
291
|
}
|
|
285
292
|
})().catch((err: Error) => {
|
|
286
293
|
if (!readySettled) {
|
|
@@ -290,22 +297,26 @@ export class RpcClient {
|
|
|
290
297
|
});
|
|
291
298
|
|
|
292
299
|
// Also race against process exit (in case stdout closes before we read it)
|
|
293
|
-
void
|
|
294
|
-
|
|
300
|
+
void child.exited.then(
|
|
301
|
+
(exitCode: number) => {
|
|
302
|
+
if (readySettled) return;
|
|
295
303
|
readySettled = true;
|
|
296
|
-
readyReject(
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
304
|
+
readyReject(new Error(`Agent process exited with code ${exitCode}. Stderr: ${child.peekStderr()}`));
|
|
305
|
+
},
|
|
306
|
+
(err: Error) => {
|
|
307
|
+
// Killed or reaped without an exit code (e.g. stop() during
|
|
308
|
+
// startup); surface it instead of leaking an unhandled rejection.
|
|
309
|
+
if (readySettled) return;
|
|
310
|
+
readySettled = true;
|
|
311
|
+
readyReject(new Error(`Agent process exited before ready. Stderr: ${child.peekStderr()}`, { cause: err }));
|
|
312
|
+
},
|
|
313
|
+
);
|
|
301
314
|
|
|
302
315
|
// Timeout to prevent hanging forever
|
|
303
316
|
const readyTimeout = this.#startTimeout(30000, () => {
|
|
304
317
|
if (readySettled) return;
|
|
305
318
|
readySettled = true;
|
|
306
|
-
readyReject(
|
|
307
|
-
new Error(`Timeout waiting for agent to become ready. Stderr: ${this.#process?.peekStderr() ?? ""}`),
|
|
308
|
-
);
|
|
319
|
+
readyReject(new Error(`Timeout waiting for agent to become ready. Stderr: ${child.peekStderr()}`));
|
|
309
320
|
});
|
|
310
321
|
|
|
311
322
|
try {
|
|
@@ -318,12 +329,14 @@ export class RpcClient {
|
|
|
318
329
|
// state so the caller (or a retry via start() again) does not
|
|
319
330
|
// leak the abandoned process (issue #4079).
|
|
320
331
|
try {
|
|
321
|
-
|
|
332
|
+
child.kill();
|
|
322
333
|
} catch {
|
|
323
334
|
// best-effort cleanup
|
|
324
335
|
}
|
|
325
336
|
this.#abortController.abort();
|
|
326
|
-
this.#process
|
|
337
|
+
if (this.#process === child) {
|
|
338
|
+
this.#process = null;
|
|
339
|
+
}
|
|
327
340
|
throw err;
|
|
328
341
|
} finally {
|
|
329
342
|
clearTimeout(readyTimeout);
|
|
@@ -180,12 +180,16 @@ export function shimmerSegments(segments: readonly ShimmerSegment[], theme: Shim
|
|
|
180
180
|
const mode = resolveMode();
|
|
181
181
|
|
|
182
182
|
// Pre-scan: total code-point count (positions the band) and resolved palette.
|
|
183
|
+
// The per-segment string is kept verbatim — iterating UTF-16 units with a
|
|
184
|
+
// surrogate-pair guard produces the same code points as `Array.from(text)`
|
|
185
|
+
// at zero per-frame allocation (previously the #1 hotspot at ~10% of profiled
|
|
186
|
+
// CPU during streaming — the working message is shimmered every animation
|
|
187
|
+
// frame at 30fps and `Array.from` reallocated the code-point array each tick).
|
|
183
188
|
let total = 0;
|
|
184
|
-
const perSeg: {
|
|
189
|
+
const perSeg: { text: string; palette: ShimmerPalette }[] = [];
|
|
185
190
|
for (const seg of segments) {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
perSeg.push({ chars, palette: seg.palette ?? DEFAULT_SHIMMER_PALETTE });
|
|
191
|
+
total += countCodePoints(seg.text);
|
|
192
|
+
perSeg.push({ text: seg.text, palette: seg.palette ?? DEFAULT_SHIMMER_PALETTE });
|
|
189
193
|
}
|
|
190
194
|
if (total === 0) return "";
|
|
191
195
|
|
|
@@ -193,9 +197,9 @@ export function shimmerSegments(segments: readonly ShimmerSegment[], theme: Shim
|
|
|
193
197
|
// tier so the working line stays legible without movement.
|
|
194
198
|
if (mode === "disabled") {
|
|
195
199
|
let out = "";
|
|
196
|
-
for (const {
|
|
200
|
+
for (const { text, palette } of perSeg) {
|
|
197
201
|
const seq = compile(theme, palette).mid;
|
|
198
|
-
out += `${seq.open}${
|
|
202
|
+
out += `${seq.open}${text}${seq.close}`;
|
|
199
203
|
}
|
|
200
204
|
return out;
|
|
201
205
|
}
|
|
@@ -205,31 +209,61 @@ export function shimmerSegments(segments: readonly ShimmerSegment[], theme: Shim
|
|
|
205
209
|
|
|
206
210
|
let out = "";
|
|
207
211
|
let index = 0;
|
|
208
|
-
for (const {
|
|
212
|
+
for (const { text, palette } of perSeg) {
|
|
209
213
|
const compiled = compile(theme, palette);
|
|
210
214
|
let runTier: Tier | null = null;
|
|
211
|
-
let
|
|
212
|
-
|
|
215
|
+
let runStart = 0;
|
|
216
|
+
let runEnd = 0;
|
|
217
|
+
let i = 0;
|
|
218
|
+
while (i < text.length) {
|
|
219
|
+
// Detect a surrogate pair so a single code point (e.g. an emoji) stays
|
|
220
|
+
// atomic; the band position is measured in code points, not UTF-16 units.
|
|
221
|
+
const c = text.charCodeAt(i);
|
|
222
|
+
let step = 1;
|
|
223
|
+
if (c >= 0xd800 && c <= 0xdbff && i + 1 < text.length) {
|
|
224
|
+
const c2 = text.charCodeAt(i + 1);
|
|
225
|
+
if (c2 >= 0xdc00 && c2 <= 0xdfff) step = 2;
|
|
226
|
+
}
|
|
213
227
|
const tier = tierFor(intensityFn(time, index, total));
|
|
214
228
|
if (tier !== runTier) {
|
|
215
|
-
if (runTier !== null) {
|
|
229
|
+
if (runTier !== null && runEnd > runStart) {
|
|
216
230
|
const seq = compiled[runTier];
|
|
217
|
-
out += `${seq.open}${
|
|
218
|
-
runBuf = "";
|
|
231
|
+
out += `${seq.open}${text.slice(runStart, runEnd)}${seq.close}`;
|
|
219
232
|
}
|
|
220
233
|
runTier = tier;
|
|
234
|
+
runStart = i;
|
|
221
235
|
}
|
|
222
|
-
|
|
236
|
+
runEnd = i + step;
|
|
223
237
|
index++;
|
|
238
|
+
i += step;
|
|
224
239
|
}
|
|
225
|
-
if (runTier !== null &&
|
|
240
|
+
if (runTier !== null && runEnd > runStart) {
|
|
226
241
|
const seq = compiled[runTier];
|
|
227
|
-
out += `${seq.open}${
|
|
242
|
+
out += `${seq.open}${text.slice(runStart, runEnd)}${seq.close}`;
|
|
228
243
|
}
|
|
229
244
|
}
|
|
230
245
|
return out;
|
|
231
246
|
}
|
|
232
247
|
|
|
248
|
+
function countCodePoints(text: string): number {
|
|
249
|
+
let n = 0;
|
|
250
|
+
let i = 0;
|
|
251
|
+
while (i < text.length) {
|
|
252
|
+
const c = text.charCodeAt(i);
|
|
253
|
+
if (c >= 0xd800 && c <= 0xdbff && i + 1 < text.length) {
|
|
254
|
+
const c2 = text.charCodeAt(i + 1);
|
|
255
|
+
if (c2 >= 0xdc00 && c2 <= 0xdfff) {
|
|
256
|
+
i += 2;
|
|
257
|
+
n++;
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
i++;
|
|
262
|
+
n++;
|
|
263
|
+
}
|
|
264
|
+
return n;
|
|
265
|
+
}
|
|
266
|
+
|
|
233
267
|
export function shimmerText(text: string, theme: ShimmerTheme, palette?: ShimmerPalette): string {
|
|
234
268
|
return shimmerSegments([{ text, palette }], theme);
|
|
235
269
|
}
|
package/src/modes/theme/theme.ts
CHANGED
|
@@ -159,6 +159,8 @@ export type SymbolKey =
|
|
|
159
159
|
| "md.hrChar"
|
|
160
160
|
| "md.bullet"
|
|
161
161
|
| "md.colorSwatch"
|
|
162
|
+
// Advisor note rail
|
|
163
|
+
| "advisor.rail"
|
|
162
164
|
// Language/file type icons
|
|
163
165
|
| "lang.default"
|
|
164
166
|
| "lang.typescript"
|
|
@@ -364,6 +366,8 @@ const UNICODE_SYMBOLS: SymbolMap = {
|
|
|
364
366
|
"md.hrChar": "─",
|
|
365
367
|
"md.bullet": "•",
|
|
366
368
|
"md.colorSwatch": "■",
|
|
369
|
+
// Advisor note rail (heavier than md.quoteBorder so notes read as a distinct voice)
|
|
370
|
+
"advisor.rail": "▎",
|
|
367
371
|
// Language/file icons (emoji-centric, no Nerd Font required)
|
|
368
372
|
"lang.default": "⌘",
|
|
369
373
|
"lang.typescript": "🟦",
|
|
@@ -675,6 +679,8 @@ const NERD_SYMBOLS: SymbolMap = {
|
|
|
675
679
|
"md.bullet": "\uf111",
|
|
676
680
|
// pick: ■ | alt: (U+F096)
|
|
677
681
|
"md.colorSwatch": "■",
|
|
682
|
+
// pick: ▎ | alt: ┃ │
|
|
683
|
+
"advisor.rail": "▎",
|
|
678
684
|
// Language icons (nerd font devicons)
|
|
679
685
|
"lang.default": "",
|
|
680
686
|
"lang.typescript": "\u{E628}",
|
|
@@ -877,6 +883,7 @@ const ASCII_SYMBOLS: SymbolMap = {
|
|
|
877
883
|
"md.hrChar": "-",
|
|
878
884
|
"md.bullet": "*",
|
|
879
885
|
"md.colorSwatch": "[]",
|
|
886
|
+
"advisor.rail": "|",
|
|
880
887
|
// Language icons (ASCII uses abbreviations)
|
|
881
888
|
"lang.default": "code",
|
|
882
889
|
"lang.typescript": "ts",
|