@oh-my-pi/pi-coding-agent 15.11.0 → 15.11.2
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 +57 -2
- package/dist/cli.js +678 -657
- package/dist/types/capability/mcp.d.ts +1 -0
- package/dist/types/config/settings-schema.d.ts +49 -4
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/extensibility/custom-commands/types.d.ts +6 -3
- package/dist/types/extensibility/custom-tools/loader.d.ts +2 -1
- package/dist/types/extensibility/custom-tools/types.d.ts +8 -4
- package/dist/types/extensibility/extensions/types.d.ts +2 -2
- package/dist/types/extensibility/hooks/types.d.ts +8 -4
- package/dist/types/irc/bus.d.ts +15 -2
- package/dist/types/mcp/oauth-discovery.d.ts +2 -0
- package/dist/types/mcp/oauth-flow.d.ts +6 -1
- package/dist/types/mcp/transports/stdio.d.ts +1 -0
- package/dist/types/mcp/types.d.ts +2 -0
- package/dist/types/modes/components/assistant-message.d.ts +1 -0
- package/dist/types/modes/components/mcp-add-wizard.d.ts +2 -1
- package/dist/types/modes/components/plan-review-overlay.d.ts +2 -0
- package/dist/types/modes/components/settings-selector.d.ts +1 -0
- package/dist/types/modes/components/status-line/types.d.ts +3 -0
- package/dist/types/modes/components/transcript-container.d.ts +3 -2
- package/dist/types/modes/controllers/tool-args-reveal.d.ts +43 -0
- package/dist/types/modes/rpc/rpc-client.d.ts +10 -1
- package/dist/types/modes/rpc/rpc-mode.d.ts +2 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +30 -0
- package/dist/types/modes/theme/theme.d.ts +3 -2
- package/dist/types/session/agent-session.d.ts +17 -3
- package/dist/types/slash-commands/available-commands.d.ts +34 -0
- package/dist/types/task/index.d.ts +3 -3
- package/dist/types/tools/bash.d.ts +1 -1
- package/dist/types/tools/browser/attach.d.ts +4 -4
- package/dist/types/tools/browser/registry.d.ts +1 -0
- package/dist/types/tools/irc.d.ts +3 -2
- package/dist/types/tools/path-utils.d.ts +0 -4
- package/dist/types/tools/render-utils.d.ts +22 -0
- package/package.json +11 -11
- package/src/capability/mcp.ts +1 -0
- package/src/cli/gallery-cli.ts +5 -4
- package/src/config/mcp-schema.json +4 -0
- package/src/config/settings-schema.ts +55 -4
- package/src/edit/renderer.ts +96 -46
- package/src/exec/bash-executor.ts +21 -6
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +6 -1
- package/src/extensibility/custom-commands/loader.ts +3 -1
- package/src/extensibility/custom-commands/types.ts +6 -3
- package/src/extensibility/custom-tools/loader.ts +4 -7
- package/src/extensibility/custom-tools/types.ts +8 -4
- package/src/extensibility/extensions/loader.ts +2 -1
- package/src/extensibility/extensions/types.ts +2 -2
- package/src/extensibility/hooks/loader.ts +3 -1
- package/src/extensibility/hooks/types.ts +8 -4
- package/src/internal-urls/docs-index.generated.ts +8 -8
- package/src/irc/bus.ts +14 -3
- package/src/lsp/defaults.json +6 -0
- package/src/lsp/render.ts +2 -28
- package/src/mcp/manager.ts +3 -0
- package/src/mcp/oauth-discovery.ts +27 -2
- package/src/mcp/oauth-flow.ts +47 -1
- package/src/mcp/transports/stdio.ts +3 -0
- package/src/mcp/types.ts +2 -0
- package/src/memories/index.ts +2 -0
- package/src/modes/acp/acp-agent.ts +4 -67
- package/src/modes/components/assistant-message.ts +15 -0
- package/src/modes/components/btw-panel.ts +5 -1
- package/src/modes/components/mcp-add-wizard.ts +13 -0
- package/src/modes/components/plan-review-overlay.ts +32 -3
- package/src/modes/components/settings-selector.ts +2 -0
- package/src/modes/components/status-line/component.ts +22 -12
- package/src/modes/components/status-line/types.ts +3 -0
- package/src/modes/components/transcript-container.ts +99 -18
- package/src/modes/components/tree-selector.ts +6 -1
- package/src/modes/controllers/event-controller.ts +28 -4
- package/src/modes/controllers/mcp-command-controller.ts +34 -2
- package/src/modes/controllers/selector-controller.ts +4 -0
- package/src/modes/controllers/streaming-reveal.ts +16 -8
- package/src/modes/controllers/tool-args-reveal.ts +174 -0
- package/src/modes/interactive-mode.ts +41 -2
- package/src/modes/rpc/rpc-client.ts +32 -0
- package/src/modes/rpc/rpc-mode.ts +82 -7
- package/src/modes/rpc/rpc-types.ts +23 -0
- package/src/modes/theme/theme.ts +13 -7
- package/src/modes/utils/ui-helpers.ts +13 -4
- package/src/prompts/memories/consolidation_system.md +4 -0
- package/src/prompts/system/irc-autoreply.md +6 -0
- package/src/prompts/system/irc-incoming.md +1 -1
- package/src/prompts/tools/bash.md +1 -0
- package/src/prompts/tools/irc.md +1 -1
- package/src/prompts/tools/task.md +7 -2
- package/src/session/agent-session.ts +120 -10
- package/src/slash-commands/available-commands.ts +105 -0
- package/src/task/index.ts +15 -10
- package/src/task/render.ts +10 -4
- package/src/tools/bash.ts +5 -1
- package/src/tools/browser/attach.ts +26 -7
- package/src/tools/browser/registry.ts +11 -1
- package/src/tools/irc.ts +16 -4
- package/src/tools/job.ts +7 -3
- package/src/tools/path-utils.ts +22 -15
- package/src/tools/render-utils.ts +56 -0
- package/src/tools/write.ts +65 -47
- package/src/web/search/providers/anthropic.ts +29 -4
|
@@ -83,6 +83,8 @@ export interface PlanReviewOverlayCallbacks {
|
|
|
83
83
|
onCancel: () => void;
|
|
84
84
|
/** Invoked when the external-editor key is pressed (overlay stays open). */
|
|
85
85
|
onExternalEditor?: () => void;
|
|
86
|
+
/** Invoked when the external-editor key edits the active annotation draft. */
|
|
87
|
+
onAnnotationExternalEditor?: (draft: string, commit: (text: string | null) => void) => void;
|
|
86
88
|
/** Invoked with the new full plan text after an in-overlay delete/undo. */
|
|
87
89
|
onPlanEdited?: (content: string) => void;
|
|
88
90
|
/** Invoked with the Refine feedback markdown whenever annotations change. */
|
|
@@ -282,6 +284,12 @@ export class PlanReviewOverlay implements Component {
|
|
|
282
284
|
handleInput(keyData: string): void {
|
|
283
285
|
if (keyData.startsWith("\x1b[<") && this.#handleMouse(keyData)) return;
|
|
284
286
|
if (this.#annotating) {
|
|
287
|
+
if (this.callbacks.onAnnotationExternalEditor && matchesAppExternalEditor(keyData)) {
|
|
288
|
+
this.callbacks.onAnnotationExternalEditor(this.#input.getValue(), text => {
|
|
289
|
+
if (text !== null) this.#submitAnnotation(text);
|
|
290
|
+
});
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
285
293
|
this.#input.handleInput(keyData);
|
|
286
294
|
return;
|
|
287
295
|
}
|
|
@@ -603,11 +611,23 @@ export class PlanReviewOverlay implements Component {
|
|
|
603
611
|
}
|
|
604
612
|
for (const section of annotated) {
|
|
605
613
|
feedback += `\n## ${section.title}\n`;
|
|
606
|
-
for (const note of section.annotations) feedback +=
|
|
614
|
+
for (const note of section.annotations) feedback += this.#formatAnnotationFeedback(note);
|
|
607
615
|
}
|
|
608
616
|
this.callbacks.onFeedbackChange?.(feedback);
|
|
609
617
|
}
|
|
610
618
|
|
|
619
|
+
#formatAnnotationFeedback(note: string): string {
|
|
620
|
+
if (!note.includes("\n")) return `- ${note}\n`;
|
|
621
|
+
const fence = this.#markdownFenceFor(note);
|
|
622
|
+
return `${fence}md\n${note}\n${fence}\n`;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
#markdownFenceFor(text: string): string {
|
|
626
|
+
let fence = "```";
|
|
627
|
+
while (text.includes(fence)) fence += "`";
|
|
628
|
+
return fence;
|
|
629
|
+
}
|
|
630
|
+
|
|
611
631
|
#renderSliderLines(): string[] {
|
|
612
632
|
const slider = this.#slider;
|
|
613
633
|
if (!slider) return [];
|
|
@@ -676,7 +696,14 @@ export class PlanReviewOverlay implements Component {
|
|
|
676
696
|
if (section.level >= 1 && section.annotations.length > 0 && rendered.length > 0) {
|
|
677
697
|
lines.push(rendered[0]!);
|
|
678
698
|
for (const note of section.annotations) {
|
|
679
|
-
|
|
699
|
+
const noteLines = note.split(/\r?\n/);
|
|
700
|
+
for (let j = 0; j < noteLines.length; j++) {
|
|
701
|
+
const prefix =
|
|
702
|
+
j === 0
|
|
703
|
+
? `${theme.fg("warning", "▎ ")}${theme.fg("dim", "note: ")}`
|
|
704
|
+
: `${theme.fg("warning", "▎ ")}${theme.fg("dim", " ")}`;
|
|
705
|
+
lines.push(`${prefix}${theme.fg("accent", noteLines[j] ?? "")}`);
|
|
706
|
+
}
|
|
680
707
|
}
|
|
681
708
|
for (let k = 1; k < rendered.length; k++) lines.push(rendered[k]!);
|
|
682
709
|
} else {
|
|
@@ -749,7 +776,9 @@ export class PlanReviewOverlay implements Component {
|
|
|
749
776
|
const section = this.#sections[this.#toc[this.#tocCursor]!];
|
|
750
777
|
const title = section?.title ?? "";
|
|
751
778
|
const caption = `${theme.fg("dim", "Annotate")} ${theme.fg("accent", `‹${title}›`)}`;
|
|
752
|
-
|
|
779
|
+
const hintParts = ["enter save", "esc cancel"];
|
|
780
|
+
if (this.#externalEditorLabel) hintParts.push(`${this.#externalEditorLabel} editor`);
|
|
781
|
+
return [caption, this.#input.render(innerWidth)[0] ?? "", theme.fg("dim", hintParts.join(" · "))];
|
|
753
782
|
}
|
|
754
783
|
return [theme.fg("dim", this.#buildHelp())];
|
|
755
784
|
}
|
|
@@ -196,6 +196,7 @@ export interface StatusLinePreviewSettings {
|
|
|
196
196
|
rightSegments?: StatusLineSegmentId[];
|
|
197
197
|
separator?: StatusLineSeparatorStyle;
|
|
198
198
|
sessionAccent?: boolean;
|
|
199
|
+
transparent?: boolean;
|
|
199
200
|
}
|
|
200
201
|
|
|
201
202
|
export interface SettingsCallbacks {
|
|
@@ -590,6 +591,7 @@ export class SettingsSelectorComponent extends Container {
|
|
|
590
591
|
rightSegments: settings.get("statusLine.rightSegments"),
|
|
591
592
|
separator: settings.get("statusLine.separator"),
|
|
592
593
|
sessionAccent: settings.get("statusLine.sessionAccent"),
|
|
594
|
+
transparent: settings.get("statusLine.transparent"),
|
|
593
595
|
};
|
|
594
596
|
this.callbacks.onStatusLinePreview?.(statusLineSettings);
|
|
595
597
|
this.#updateStatusPreview();
|
|
@@ -3,7 +3,7 @@ import * as path from "node:path";
|
|
|
3
3
|
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
4
4
|
import { estimateTokens } from "@oh-my-pi/pi-agent-core/compaction";
|
|
5
5
|
import { type Component, truncateToWidth, visibleWidth } from "@oh-my-pi/pi-tui";
|
|
6
|
-
import {
|
|
6
|
+
import { getProjectDir } from "@oh-my-pi/pi-utils";
|
|
7
7
|
import { $ } from "bun";
|
|
8
8
|
import { settings } from "../../../config/settings";
|
|
9
9
|
import type { AgentSession } from "../../../session/agent-session";
|
|
@@ -196,6 +196,7 @@ export class StatusLineComponent implements Component {
|
|
|
196
196
|
showHookStatus: settings.get("statusLine.showHookStatus"),
|
|
197
197
|
segmentOptions: settings.getGroup("statusLine").segmentOptions,
|
|
198
198
|
sessionAccent: settings.get("statusLine.sessionAccent"),
|
|
199
|
+
transparent: settings.get("statusLine.transparent"),
|
|
199
200
|
};
|
|
200
201
|
}
|
|
201
202
|
|
|
@@ -713,7 +714,15 @@ export class StatusLineComponent implements Component {
|
|
|
713
714
|
const ctx = this.#buildSegmentContext(width, effectiveSettings.segmentOptions, includeContext);
|
|
714
715
|
const separatorDef = getSeparator(effectiveSettings.separator ?? "powerline-thin", theme);
|
|
715
716
|
|
|
716
|
-
|
|
717
|
+
// `transparent` reuses the empty-string sentinel (`\x1b[49m`) so the bar
|
|
718
|
+
// inherits the terminal's default background, matching custom themes that
|
|
719
|
+
// set `statusLineBg: ""`. Powerline end caps need a contrasting fill to
|
|
720
|
+
// bridge the bar into the surrounding terminal; without one they read as
|
|
721
|
+
// stray glyphs, so the cap renderer drops them when the fill is empty.
|
|
722
|
+
const TRANSPARENT_BG_ANSI = "\x1b[49m";
|
|
723
|
+
const themeBgAnsi = theme.getBgAnsi("statusLineBg");
|
|
724
|
+
const bgAnsi = effectiveSettings.transparent ? TRANSPARENT_BG_ANSI : themeBgAnsi;
|
|
725
|
+
const transparentBg = bgAnsi === TRANSPARENT_BG_ANSI;
|
|
717
726
|
const fgAnsi = theme.getFgAnsi("text");
|
|
718
727
|
const sepAnsi = theme.getFgAnsi("statusLineSep");
|
|
719
728
|
|
|
@@ -738,9 +747,7 @@ export class StatusLineComponent implements Component {
|
|
|
738
747
|
|
|
739
748
|
const runningBackgroundJobs = this.session.getAsyncJobSnapshot()?.running.length ?? 0;
|
|
740
749
|
if (runningBackgroundJobs > 0) {
|
|
741
|
-
|
|
742
|
-
const label = `${formatCount("job", runningBackgroundJobs)} running`;
|
|
743
|
-
rightParts.push(theme.fg("statusLineSubagents", `${icon}${label}`));
|
|
750
|
+
rightParts.unshift(theme.fg("statusLineSubagents", `${theme.icon.job} ${runningBackgroundJobs}`));
|
|
744
751
|
}
|
|
745
752
|
const topFillWidth = Math.max(0, width);
|
|
746
753
|
const left = [...leftParts];
|
|
@@ -748,8 +755,10 @@ export class StatusLineComponent implements Component {
|
|
|
748
755
|
|
|
749
756
|
const leftSepWidth = visibleWidth(separatorDef.left);
|
|
750
757
|
const rightSepWidth = visibleWidth(separatorDef.right);
|
|
751
|
-
|
|
752
|
-
|
|
758
|
+
// Transparent mode drops powerline caps (they need a bg fill to bridge),
|
|
759
|
+
// so the width budget excludes them too.
|
|
760
|
+
const leftCapWidth = separatorDef.endCaps && !transparentBg ? visibleWidth(separatorDef.endCaps.right) : 0;
|
|
761
|
+
const rightCapWidth = separatorDef.endCaps && !transparentBg ? visibleWidth(separatorDef.endCaps.left) : 0;
|
|
753
762
|
|
|
754
763
|
const groupWidth = (parts: string[], capWidth: number, sepWidth: number): number => {
|
|
755
764
|
if (parts.length === 0) return 0;
|
|
@@ -810,11 +819,12 @@ export class StatusLineComponent implements Component {
|
|
|
810
819
|
const renderGroup = (parts: string[], direction: "left" | "right"): string => {
|
|
811
820
|
if (parts.length === 0) return "";
|
|
812
821
|
const sep = direction === "left" ? separatorDef.left : separatorDef.right;
|
|
813
|
-
const cap =
|
|
814
|
-
|
|
815
|
-
?
|
|
816
|
-
|
|
817
|
-
|
|
822
|
+
const cap =
|
|
823
|
+
separatorDef.endCaps && !transparentBg
|
|
824
|
+
? direction === "left"
|
|
825
|
+
? separatorDef.endCaps.right
|
|
826
|
+
: separatorDef.endCaps.left
|
|
827
|
+
: "";
|
|
818
828
|
const capPrefix = separatorDef.endCaps?.useBgAsFg ? bgAnsi.replace("\x1b[48;", "\x1b[38;") : bgAnsi + sepAnsi;
|
|
819
829
|
const capText = cap ? `${capPrefix}${cap}\x1b[0m` : "";
|
|
820
830
|
|
|
@@ -18,6 +18,9 @@ export interface StatusLineSettings {
|
|
|
18
18
|
segmentOptions?: StatusLineSegmentOptions;
|
|
19
19
|
showHookStatus?: boolean;
|
|
20
20
|
sessionAccent?: boolean;
|
|
21
|
+
/** Drop the theme's `statusLineBg` fill and powerline caps so the bar
|
|
22
|
+
* inherits the terminal's default background. */
|
|
23
|
+
transparent?: boolean;
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
export type EffectiveStatusLineSettings = Required<
|
|
@@ -1,12 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type Component,
|
|
3
|
+
Container,
|
|
4
|
+
type NativeScrollbackCommittedRows,
|
|
5
|
+
type NativeScrollbackLiveRegion,
|
|
6
|
+
type RenderStablePrefix,
|
|
7
|
+
} from "@oh-my-pi/pi-tui";
|
|
2
8
|
|
|
3
9
|
const kSnapshot = Symbol("transcript.liveDiffSnapshot");
|
|
4
10
|
|
|
5
11
|
/**
|
|
6
|
-
* Per-block
|
|
7
|
-
* derived append-only state.
|
|
8
|
-
*
|
|
9
|
-
*
|
|
12
|
+
* Per-block render cache: the block's previous stripped contribution plus the
|
|
13
|
+
* derived append-only state. Still-live blocks use it as input to
|
|
14
|
+
* {@link deriveLiveCommitState}; finalized blocks wholly inside already
|
|
15
|
+
* committed native scrollback can replay it without calling render().
|
|
10
16
|
*/
|
|
11
17
|
interface LiveDiffSnapshot {
|
|
12
18
|
width: number;
|
|
@@ -47,6 +53,16 @@ interface SnapshotCarrier {
|
|
|
47
53
|
*/
|
|
48
54
|
interface FinalizableBlock {
|
|
49
55
|
isTranscriptBlockFinalized?(): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Monotonic content version for blocks that can still mutate *after*
|
|
58
|
+
* reporting finalized (e.g. `AssistantMessageComponent`: the inline error
|
|
59
|
+
* restored at the next turn's `agent_start`, late tool-result images). The
|
|
60
|
+
* committed-scrollback render bypass only replays a block's previous rows
|
|
61
|
+
* when the version is unchanged; without this signal a post-finalize
|
|
62
|
+
* mutation would stay invisible until a global invalidation. Blocks that
|
|
63
|
+
* never mutate post-finalize simply omit the method.
|
|
64
|
+
*/
|
|
65
|
+
getTranscriptBlockVersion?(): number;
|
|
50
66
|
}
|
|
51
67
|
|
|
52
68
|
function isBlockFinalized(child: Component): boolean {
|
|
@@ -54,6 +70,11 @@ function isBlockFinalized(child: Component): boolean {
|
|
|
54
70
|
return fn ? fn.call(child) : true;
|
|
55
71
|
}
|
|
56
72
|
|
|
73
|
+
function getBlockVersion(child: Component): number | undefined {
|
|
74
|
+
const fn = (child as Component & FinalizableBlock).getTranscriptBlockVersion;
|
|
75
|
+
return fn ? fn.call(child) : undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
57
78
|
// A "plain blank" row is empty or whitespace-only with no ANSI bytes. It marks
|
|
58
79
|
// separation padding (a `Spacer`, or a no-background `paddingY` row) as opposed
|
|
59
80
|
// to a background-colored padding row, whose escape sequences contain `\S` and
|
|
@@ -87,11 +108,16 @@ interface BlockSegment {
|
|
|
87
108
|
rawRef: readonly string[];
|
|
88
109
|
contribution: readonly string[];
|
|
89
110
|
width: number;
|
|
111
|
+
generation: number;
|
|
90
112
|
/** Frame row of this block's first emitted row (the separator when present). */
|
|
91
113
|
startRow: number;
|
|
92
114
|
/** Rows emitted: separator + contribution (0 for empty contributions). */
|
|
93
115
|
rowCount: number;
|
|
94
116
|
sep: number;
|
|
117
|
+
/** Whether the block reported finalized when this segment was rendered. */
|
|
118
|
+
finalized: boolean;
|
|
119
|
+
/** Block version observed when this segment was rendered (see {@link FinalizableBlock}). */
|
|
120
|
+
version: number | undefined;
|
|
95
121
|
}
|
|
96
122
|
|
|
97
123
|
const EMPTY_SEGMENTS: BlockSegment[] = [];
|
|
@@ -369,7 +395,10 @@ function deriveLiveCommitState(
|
|
|
369
395
|
* through {@link RenderStablePrefix} so the engine can skip marker scanning,
|
|
370
396
|
* line preparation, and the committed-prefix audit for those rows.
|
|
371
397
|
*/
|
|
372
|
-
export class TranscriptContainer
|
|
398
|
+
export class TranscriptContainer
|
|
399
|
+
extends Container
|
|
400
|
+
implements NativeScrollbackLiveRegion, NativeScrollbackCommittedRows, RenderStablePrefix
|
|
401
|
+
{
|
|
373
402
|
// Bumped to retire every block's diff snapshot at once (theme change /
|
|
374
403
|
// clear); a snapshot is only honored when its stored generation matches.
|
|
375
404
|
#generation = 0;
|
|
@@ -390,6 +419,10 @@ export class TranscriptContainer extends Container implements NativeScrollbackLi
|
|
|
390
419
|
#lines: string[] = [];
|
|
391
420
|
#segments: BlockSegment[] = EMPTY_SEGMENTS;
|
|
392
421
|
#renderWidth = -1;
|
|
422
|
+
// Local rows already committed to native scrollback by the previous frame.
|
|
423
|
+
// Finalized blocks wholly before this boundary are immutable on-screen history;
|
|
424
|
+
// their previous contribution can be replayed without calling render().
|
|
425
|
+
#committedRows = 0;
|
|
393
426
|
// Stable-prefix floor accumulated across renders since the last
|
|
394
427
|
// getRenderStablePrefixRows() read (see RenderStablePrefix: reading
|
|
395
428
|
// consumes the report and re-bases the baseline). Out-of-band renders
|
|
@@ -407,6 +440,10 @@ export class TranscriptContainer extends Container implements NativeScrollbackLi
|
|
|
407
440
|
super.clear();
|
|
408
441
|
}
|
|
409
442
|
|
|
443
|
+
setNativeScrollbackCommittedRows(rows: number): void {
|
|
444
|
+
this.#committedRows = Number.isFinite(rows) ? Math.max(0, Math.trunc(rows)) : 0;
|
|
445
|
+
}
|
|
446
|
+
|
|
410
447
|
getRenderStablePrefixRows(): number {
|
|
411
448
|
const value = Math.min(this.#stableRowsFloor, this.#lines.length);
|
|
412
449
|
this.#stableRowsFloor = this.#lines.length;
|
|
@@ -497,21 +534,43 @@ export class TranscriptContainer extends Container implements NativeScrollbackLi
|
|
|
497
534
|
|
|
498
535
|
// This child's contribution: its current render with plain-blank
|
|
499
536
|
// top/bottom edges stripped (the container owns inter-block gaps).
|
|
500
|
-
//
|
|
501
|
-
//
|
|
502
|
-
//
|
|
503
|
-
//
|
|
504
|
-
//
|
|
537
|
+
// Finalized blocks wholly inside committed native scrollback can reuse
|
|
538
|
+
// their previous contribution without calling render(): those rows are
|
|
539
|
+
// immutable terminal history for the current width/generation. Blocks
|
|
540
|
+
// outside committed history still render normally so late results,
|
|
541
|
+
// post-finalize re-layouts, and expand toggles remain visible.
|
|
505
542
|
const previousSnapshot = child[kSnapshot];
|
|
506
|
-
const raw = child.render(width);
|
|
507
543
|
const previous = previousSegments[i];
|
|
508
|
-
const
|
|
544
|
+
const finalized = isBlockFinalized(child);
|
|
545
|
+
const version = getBlockVersion(child);
|
|
546
|
+
const committedReusable =
|
|
509
547
|
previous !== undefined &&
|
|
510
548
|
previous.component === child &&
|
|
511
|
-
previous.
|
|
512
|
-
previous.
|
|
549
|
+
previous.width === width &&
|
|
550
|
+
previous.generation === this.#generation &&
|
|
551
|
+
previous.startRow === row &&
|
|
552
|
+
previous.startRow + previous.rowCount <= this.#committedRows &&
|
|
553
|
+
finalized &&
|
|
554
|
+
// Only replay bytes that were themselves produced by a finalized
|
|
555
|
+
// render: a block finalizing between frames may have changed content
|
|
556
|
+
// while its rows were already committed via the append-only live
|
|
557
|
+
// path, so the first post-transition frame must render. Defense in
|
|
558
|
+
// depth on the transcript side — the TUI commit policy should keep
|
|
559
|
+
// that window closed, but the safety must not live there alone.
|
|
560
|
+
previous.finalized &&
|
|
561
|
+
// Post-finalize mutations (inline error restore, late tool images)
|
|
562
|
+
// bump the block version; a mismatch forces a real render so the
|
|
563
|
+
// committed-prefix audit can observe and re-anchor the change.
|
|
564
|
+
previous.version === version;
|
|
565
|
+
const raw = committedReusable ? previous.rawRef : child.render(width);
|
|
566
|
+
const reusable =
|
|
567
|
+
committedReusable ||
|
|
568
|
+
(previous !== undefined &&
|
|
569
|
+
previous.component === child &&
|
|
570
|
+
previous.rawRef === raw &&
|
|
571
|
+
previous.width === width &&
|
|
572
|
+
previous.generation === this.#generation);
|
|
513
573
|
const contribution = reusable ? previous.contribution : stripPlainBlankEdges(raw);
|
|
514
|
-
const finalized = isBlockFinalized(child);
|
|
515
574
|
let liveCommitState: LiveCommitState | undefined;
|
|
516
575
|
if (i >= liveStartIndex && !finalized) {
|
|
517
576
|
liveCommitState = deriveLiveCommitState(previousSnapshot, contribution, width, this.#generation);
|
|
@@ -540,7 +599,18 @@ export class TranscriptContainer extends Container implements NativeScrollbackLi
|
|
|
540
599
|
lines.length = row;
|
|
541
600
|
}
|
|
542
601
|
if (chainStable) stableRows = row;
|
|
543
|
-
segments[i] = {
|
|
602
|
+
segments[i] = {
|
|
603
|
+
component: child,
|
|
604
|
+
rawRef: raw,
|
|
605
|
+
contribution,
|
|
606
|
+
width,
|
|
607
|
+
generation: this.#generation,
|
|
608
|
+
startRow: row,
|
|
609
|
+
rowCount: 0,
|
|
610
|
+
sep: 0,
|
|
611
|
+
finalized,
|
|
612
|
+
version,
|
|
613
|
+
};
|
|
544
614
|
continue;
|
|
545
615
|
}
|
|
546
616
|
|
|
@@ -584,7 +654,18 @@ export class TranscriptContainer extends Container implements NativeScrollbackLi
|
|
|
584
654
|
if (!(finalized && safeLength >= contribution.length)) commitSafeOpen = false;
|
|
585
655
|
}
|
|
586
656
|
|
|
587
|
-
segments[i] = {
|
|
657
|
+
segments[i] = {
|
|
658
|
+
component: child,
|
|
659
|
+
rawRef: raw,
|
|
660
|
+
contribution,
|
|
661
|
+
width,
|
|
662
|
+
generation: this.#generation,
|
|
663
|
+
startRow: row,
|
|
664
|
+
rowCount,
|
|
665
|
+
sep,
|
|
666
|
+
finalized,
|
|
667
|
+
version,
|
|
668
|
+
};
|
|
588
669
|
row += rowCount;
|
|
589
670
|
}
|
|
590
671
|
// Trailing shrink: blocks removed from the tail leave stale rows behind
|
|
@@ -518,6 +518,7 @@ class TreeList implements Component {
|
|
|
518
518
|
const renderedIndent = Math.min(displayIndent, maxIndentLevels);
|
|
519
519
|
const scrollOffset = displayIndent - renderedIndent;
|
|
520
520
|
const connectorPositionDisplay = hasConnector ? renderedIndent - 1 : -1;
|
|
521
|
+
const chainGutter = !hasConnector ? flatNode.gutters[flatNode.gutters.length - 1] : undefined;
|
|
521
522
|
|
|
522
523
|
// Build prefix char by char, placing gutters and connector at their positions
|
|
523
524
|
const totalChars = renderedIndent * 3;
|
|
@@ -530,8 +531,12 @@ class TreeList implements Component {
|
|
|
530
531
|
// Check if there's a gutter at this level (translated to original tree depth)
|
|
531
532
|
const gutter = flatNode.gutters.find(g => g.position === originalLevel);
|
|
532
533
|
if (gutter) {
|
|
534
|
+
// Chain rows (no connector of their own) extend only their
|
|
535
|
+
// nearest connector gutter so the flattened conversation flow
|
|
536
|
+
// stays anchored without reviving unrelated `└─` ancestors (#2298).
|
|
537
|
+
const showVertical = gutter.show || gutter === chainGutter;
|
|
533
538
|
if (posInLevel === 0) {
|
|
534
|
-
prefixChars.push(
|
|
539
|
+
prefixChars.push(showVertical ? theme.tree.vertical : " ");
|
|
535
540
|
} else {
|
|
536
541
|
prefixChars.push(" ");
|
|
537
542
|
}
|
|
@@ -21,6 +21,7 @@ import { isSilentAbort, readPendingDisplayTag, resolveAbortLabel } from "../../s
|
|
|
21
21
|
import type { ResolveToolDetails } from "../../tools/resolve";
|
|
22
22
|
import { interruptHint } from "../shared";
|
|
23
23
|
import { StreamingRevealController } from "./streaming-reveal";
|
|
24
|
+
import { ToolArgsRevealController } from "./tool-args-reveal";
|
|
24
25
|
|
|
25
26
|
type AgentSessionEventKind = AgentSessionEvent["type"];
|
|
26
27
|
|
|
@@ -86,6 +87,7 @@ export class EventController {
|
|
|
86
87
|
// rules into this block while it is still the (live-region) transcript tail.
|
|
87
88
|
#lastTtsrNotification: TtsrNotificationComponent | undefined = undefined;
|
|
88
89
|
#streamingReveal: StreamingRevealController;
|
|
90
|
+
#toolArgsReveal: ToolArgsRevealController;
|
|
89
91
|
#handlers: AgentSessionEventHandlers;
|
|
90
92
|
|
|
91
93
|
constructor(private ctx: InteractiveModeContext) {
|
|
@@ -94,6 +96,10 @@ export class EventController {
|
|
|
94
96
|
getHideThinkingBlock: () => this.ctx.hideThinkingBlock,
|
|
95
97
|
requestRender: () => this.ctx.ui.requestRender(),
|
|
96
98
|
});
|
|
99
|
+
this.#toolArgsReveal = new ToolArgsRevealController({
|
|
100
|
+
getSmoothStreaming: () => this.ctx.settings.get("display.smoothStreaming"),
|
|
101
|
+
requestRender: () => this.ctx.ui.requestRender(),
|
|
102
|
+
});
|
|
97
103
|
this.#handlers = {
|
|
98
104
|
agent_start: e => this.#handleAgentStart(e),
|
|
99
105
|
agent_end: e => this.#handleAgentEnd(e),
|
|
@@ -127,6 +133,7 @@ export class EventController {
|
|
|
127
133
|
|
|
128
134
|
dispose(): void {
|
|
129
135
|
this.#streamingReveal.stop();
|
|
136
|
+
this.#toolArgsReveal.stop();
|
|
130
137
|
this.#cancelIdleCompaction();
|
|
131
138
|
for (const timer of this.#ircExpiryTimers.values()) {
|
|
132
139
|
clearTimeout(timer);
|
|
@@ -492,10 +499,23 @@ export class EventController {
|
|
|
492
499
|
|
|
493
500
|
// Preserve the raw partial JSON for renderers that need to surface fields before the JSON object closes.
|
|
494
501
|
// Bash uses this to show inline env assignments during streaming instead of popping them in at completion.
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
502
|
+
// While the JSON is still open, ToolArgsRevealController paces the
|
|
503
|
+
// reveal (write/edit/bash previews grow smoothly when a slow provider
|
|
504
|
+
// delivers large batches); once it closes, the final args render
|
|
505
|
+
// as-is — mirroring how assistant text snaps at message_end.
|
|
506
|
+
const partialJson = "partialJson" in content ? content.partialJson : undefined;
|
|
507
|
+
let renderArgs: Record<string, unknown>;
|
|
508
|
+
if (typeof partialJson === "string") {
|
|
509
|
+
renderArgs = this.#toolArgsReveal.setTarget(
|
|
510
|
+
content.id,
|
|
511
|
+
partialJson,
|
|
512
|
+
content.customWireName !== undefined,
|
|
513
|
+
content.arguments,
|
|
514
|
+
);
|
|
515
|
+
} else {
|
|
516
|
+
this.#toolArgsReveal.finish(content.id);
|
|
517
|
+
renderArgs = content.arguments;
|
|
518
|
+
}
|
|
499
519
|
if (!this.ctx.pendingTools.has(content.id)) {
|
|
500
520
|
this.#resolveDisplaceablePoll(content.name);
|
|
501
521
|
this.#resetReadGroup();
|
|
@@ -517,10 +537,12 @@ export class EventController {
|
|
|
517
537
|
component.setExpanded(this.ctx.toolOutputExpanded);
|
|
518
538
|
this.ctx.chatContainer.addChild(component);
|
|
519
539
|
this.ctx.pendingTools.set(content.id, component);
|
|
540
|
+
this.#toolArgsReveal.bind(content.id, component);
|
|
520
541
|
} else {
|
|
521
542
|
const component = this.ctx.pendingTools.get(content.id);
|
|
522
543
|
if (component) {
|
|
523
544
|
component.updateArgs(renderArgs, content.id);
|
|
545
|
+
this.#toolArgsReveal.bind(content.id, component);
|
|
524
546
|
}
|
|
525
547
|
}
|
|
526
548
|
}
|
|
@@ -555,6 +577,7 @@ export class EventController {
|
|
|
555
577
|
if (this.ctx.streamingComponent && event.message.role === "assistant") {
|
|
556
578
|
this.ctx.streamingMessage = event.message;
|
|
557
579
|
this.#streamingReveal.stop();
|
|
580
|
+
this.#toolArgsReveal.flushAll();
|
|
558
581
|
let errorMessage: string | undefined;
|
|
559
582
|
const aborted = this.ctx.streamingMessage.stopReason === "aborted";
|
|
560
583
|
const silentlyAborted = aborted && isSilentAbort(this.ctx.streamingMessage.errorMessage);
|
|
@@ -772,6 +795,7 @@ export class EventController {
|
|
|
772
795
|
async #handleAgentEnd(_event: Extract<AgentSessionEvent, { type: "agent_end" }>): Promise<void> {
|
|
773
796
|
this.#agentTurnActive = false;
|
|
774
797
|
this.#streamingReveal.stop();
|
|
798
|
+
this.#toolArgsReveal.flushAll();
|
|
775
799
|
if (this.ctx.loadingAnimation) {
|
|
776
800
|
this.ctx.loadingAnimation.stop();
|
|
777
801
|
this.ctx.loadingAnimation = undefined;
|
|
@@ -127,6 +127,7 @@ interface OAuthFlowResult {
|
|
|
127
127
|
credentialId: string;
|
|
128
128
|
clientId?: string;
|
|
129
129
|
clientSecret?: string;
|
|
130
|
+
resource?: string;
|
|
130
131
|
}
|
|
131
132
|
|
|
132
133
|
type MCPAddScope = "user" | "project";
|
|
@@ -490,6 +491,7 @@ export class MCPCommandController {
|
|
|
490
491
|
|
|
491
492
|
try {
|
|
492
493
|
const oauthClientSecret = finalConfig.oauth?.clientSecret ?? "";
|
|
494
|
+
const oauthResource = oauth.resource ?? finalConfig.url;
|
|
493
495
|
const oauthResult = await this.#handleOAuthFlow(
|
|
494
496
|
oauth.authorizationUrl,
|
|
495
497
|
oauth.tokenUrl,
|
|
@@ -499,15 +501,18 @@ export class MCPCommandController {
|
|
|
499
501
|
finalConfig.oauth?.callbackPort,
|
|
500
502
|
finalConfig.oauth?.callbackPath,
|
|
501
503
|
finalConfig.oauth?.redirectUri,
|
|
504
|
+
oauthResource,
|
|
502
505
|
);
|
|
503
506
|
const persistedClientId = oauthResult.clientId ?? oauth.clientId ?? finalConfig.oauth?.clientId;
|
|
504
507
|
const persistedClientSecret = oauthResult.clientSecret ?? finalConfig.oauth?.clientSecret;
|
|
508
|
+
const persistedResource = oauthResult.resource ?? oauthResource;
|
|
505
509
|
finalConfig = {
|
|
506
510
|
...finalConfig,
|
|
507
511
|
auth: {
|
|
508
512
|
type: "oauth",
|
|
509
513
|
credentialId: oauthResult.credentialId,
|
|
510
514
|
tokenUrl: oauth.tokenUrl,
|
|
515
|
+
resource: persistedResource,
|
|
511
516
|
clientId: persistedClientId,
|
|
512
517
|
clientSecret: persistedClientSecret,
|
|
513
518
|
},
|
|
@@ -548,8 +553,25 @@ export class MCPCommandController {
|
|
|
548
553
|
done();
|
|
549
554
|
this.#handleWizardCancel();
|
|
550
555
|
},
|
|
551
|
-
async (
|
|
552
|
-
|
|
556
|
+
async (
|
|
557
|
+
authUrl: string,
|
|
558
|
+
tokenUrl: string,
|
|
559
|
+
clientId: string,
|
|
560
|
+
clientSecret: string,
|
|
561
|
+
scopes: string,
|
|
562
|
+
resource?: string,
|
|
563
|
+
) => {
|
|
564
|
+
return await this.#handleOAuthFlow(
|
|
565
|
+
authUrl,
|
|
566
|
+
tokenUrl,
|
|
567
|
+
clientId,
|
|
568
|
+
clientSecret,
|
|
569
|
+
scopes,
|
|
570
|
+
undefined,
|
|
571
|
+
undefined,
|
|
572
|
+
undefined,
|
|
573
|
+
resource,
|
|
574
|
+
);
|
|
553
575
|
},
|
|
554
576
|
async (config: MCPServerConfig) => {
|
|
555
577
|
return await this.#handleTestConnection(config);
|
|
@@ -579,6 +601,7 @@ export class MCPCommandController {
|
|
|
579
601
|
callbackPort?: number,
|
|
580
602
|
callbackPath?: string,
|
|
581
603
|
redirectUri?: string,
|
|
604
|
+
resource?: string,
|
|
582
605
|
): Promise<OAuthFlowResult> {
|
|
583
606
|
const authStorage = this.ctx.session.modelRegistry.authStorage;
|
|
584
607
|
let parsedAuthUrl: URL;
|
|
@@ -617,6 +640,7 @@ export class MCPCommandController {
|
|
|
617
640
|
redirectUri,
|
|
618
641
|
callbackPort,
|
|
619
642
|
callbackPath,
|
|
643
|
+
resource,
|
|
620
644
|
},
|
|
621
645
|
{
|
|
622
646
|
onAuth: (info: { url: string; instructions?: string }) => {
|
|
@@ -704,6 +728,7 @@ export class MCPCommandController {
|
|
|
704
728
|
credentialId,
|
|
705
729
|
clientId: flow.resolvedClientId,
|
|
706
730
|
clientSecret: flow.registeredClientSecret,
|
|
731
|
+
resource: flow.resource,
|
|
707
732
|
};
|
|
708
733
|
} catch (error) {
|
|
709
734
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
@@ -804,6 +829,7 @@ export class MCPCommandController {
|
|
|
804
829
|
tokenUrl: string;
|
|
805
830
|
clientId?: string;
|
|
806
831
|
scopes?: string;
|
|
832
|
+
resource?: string;
|
|
807
833
|
}> {
|
|
808
834
|
// First test if server actually needs auth by connecting without OAuth
|
|
809
835
|
let connectionSucceeded = false;
|
|
@@ -1415,6 +1441,9 @@ export class MCPCommandController {
|
|
|
1415
1441
|
|
|
1416
1442
|
this.#showMessage(["", theme.fg("muted", `Reauthorizing "${name}"...`), ""].join("\n"));
|
|
1417
1443
|
|
|
1444
|
+
const oauthResource =
|
|
1445
|
+
oauth.resource ?? currentAuth?.resource ?? ("url" in baseConfig ? baseConfig.url : undefined);
|
|
1446
|
+
|
|
1418
1447
|
const oauthResult = await this.#handleOAuthFlow(
|
|
1419
1448
|
oauth.authorizationUrl,
|
|
1420
1449
|
oauth.tokenUrl,
|
|
@@ -1424,10 +1453,12 @@ export class MCPCommandController {
|
|
|
1424
1453
|
found.config.oauth?.callbackPort,
|
|
1425
1454
|
found.config.oauth?.callbackPath,
|
|
1426
1455
|
found.config.oauth?.redirectUri,
|
|
1456
|
+
oauthResource,
|
|
1427
1457
|
);
|
|
1428
1458
|
|
|
1429
1459
|
const persistedClientId = oauthResult.clientId ?? oauth.clientId ?? found.config.oauth?.clientId;
|
|
1430
1460
|
const persistedClientSecret = oauthResult.clientSecret ?? (oauthClientSecret || undefined);
|
|
1461
|
+
const persistedResource = oauthResult.resource ?? oauthResource;
|
|
1431
1462
|
|
|
1432
1463
|
const updated: MCPServerConfig = {
|
|
1433
1464
|
...baseConfig,
|
|
@@ -1435,6 +1466,7 @@ export class MCPCommandController {
|
|
|
1435
1466
|
type: "oauth",
|
|
1436
1467
|
credentialId: oauthResult.credentialId,
|
|
1437
1468
|
tokenUrl: oauth.tokenUrl,
|
|
1469
|
+
resource: persistedResource,
|
|
1438
1470
|
clientId: persistedClientId,
|
|
1439
1471
|
clientSecret: persistedClientSecret,
|
|
1440
1472
|
},
|
|
@@ -120,6 +120,7 @@ export class SelectorController {
|
|
|
120
120
|
separator: settings.get("statusLine.separator"),
|
|
121
121
|
showHookStatus: settings.get("statusLine.showHookStatus"),
|
|
122
122
|
sessionAccent: settings.get("statusLine.sessionAccent"),
|
|
123
|
+
transparent: settings.get("statusLine.transparent"),
|
|
123
124
|
...previewSettings,
|
|
124
125
|
});
|
|
125
126
|
this.ctx.updateEditorTopBorder();
|
|
@@ -147,6 +148,7 @@ export class SelectorController {
|
|
|
147
148
|
separator: settings.get("statusLine.separator"),
|
|
148
149
|
showHookStatus: settings.get("statusLine.showHookStatus"),
|
|
149
150
|
sessionAccent: settings.get("statusLine.sessionAccent"),
|
|
151
|
+
transparent: settings.get("statusLine.transparent"),
|
|
150
152
|
});
|
|
151
153
|
this.ctx.updateEditorTopBorder();
|
|
152
154
|
this.ctx.ui.requestRender();
|
|
@@ -351,6 +353,7 @@ export class SelectorController {
|
|
|
351
353
|
case "statusLineShowHooks":
|
|
352
354
|
case "statusLine.showHookStatus":
|
|
353
355
|
case "statusLine.sessionAccent":
|
|
356
|
+
case "statusLine.transparent":
|
|
354
357
|
case "statusLineSegments":
|
|
355
358
|
case "statusLineModelThinking":
|
|
356
359
|
case "statusLinePathAbbreviate":
|
|
@@ -369,6 +372,7 @@ export class SelectorController {
|
|
|
369
372
|
separator: settings.get("statusLine.separator"),
|
|
370
373
|
showHookStatus: settings.get("statusLine.showHookStatus"),
|
|
371
374
|
sessionAccent: settings.get("statusLine.sessionAccent"),
|
|
375
|
+
transparent: settings.get("statusLine.transparent"),
|
|
372
376
|
segmentOptions: settings.get("statusLine.segmentOptions"),
|
|
373
377
|
};
|
|
374
378
|
this.ctx.statusLine.updateSettings(statusLineSettings);
|