@quandev104/pi-style 0.1.1 → 0.1.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 +25 -0
- package/README.md +1 -2
- package/dist/extensions/pi-style.js +5881 -4929
- package/dist/extensions/pi-style.js.map +1 -1
- package/extension-src/pi-style/app/command-service.ts +2 -2
- package/extension-src/pi-style/app/index.ts +0 -1
- package/extension-src/pi-style/domain/config-authorization.ts +1 -2
- package/extension-src/pi-style/domain/config-normalization.ts +3 -3
- package/extension-src/pi-style/domain/config-types.ts +2 -2
- package/extension-src/pi-style/domain/theme.ts +3 -0
- package/extension-src/pi-style/features/messages/boxed-block.ts +16 -20
- package/extension-src/pi-style/features/messages/index.ts +97 -40
- package/extension-src/pi-style/features/messages/special-blocks.ts +3 -3
- package/extension-src/pi-style/features/startup/index.ts +4 -4
- package/extension-src/pi-style/features/tools/boxed/bash.ts +395 -19
- package/extension-src/pi-style/features/tools/boxed/batch.ts +459 -0
- package/extension-src/pi-style/features/tools/boxed/edit.ts +39 -23
- package/extension-src/pi-style/features/tools/boxed/fallback.ts +10 -8
- package/extension-src/pi-style/features/tools/boxed/find.ts +48 -48
- package/extension-src/pi-style/features/tools/boxed/grep.ts +161 -89
- package/extension-src/pi-style/features/tools/boxed/index.ts +4 -0
- package/extension-src/pi-style/features/tools/boxed/ls.ts +39 -47
- package/extension-src/pi-style/features/tools/boxed/output-tree.ts +368 -0
- package/extension-src/pi-style/features/tools/boxed/quick-edit.ts +45 -24
- package/extension-src/pi-style/features/tools/boxed/read.ts +32 -189
- package/extension-src/pi-style/features/tools/boxed/session-config.ts +6 -0
- package/extension-src/pi-style/features/tools/boxed/write.ts +91 -49
- package/extension-src/pi-style/features/tools/index.ts +14 -0
- package/extension-src/pi-style/pi/compatibility-coordinator.ts +4 -26
- package/extension-src/pi-style/pi/compatibility-probe.ts +3 -33
- package/extension-src/pi-style/pi/compatibility-registry.ts +0 -1
- package/extension-src/pi-style/pi/config-session.ts +0 -2
- package/extension-src/pi-style/pi/index.ts +35 -1
- package/extension-src/pi-style/pi/session-coordinator.ts +48 -4
- package/extension-src/pi-style/shared/ansi.ts +3 -0
- package/extension-src/pi-style/shared/box.ts +210 -69
- package/extension-src/pi-style/shared/split-diff.ts +395 -86
- package/extension-src/pi-style/shared/theme-extras.ts +0 -2
- package/package.json +1 -1
|
@@ -45,9 +45,9 @@ const allowedPaths = new Set([
|
|
|
45
45
|
"editor.frame",
|
|
46
46
|
"editor.showMetadata",
|
|
47
47
|
"messages.enabled",
|
|
48
|
-
"messages.userPrefix",
|
|
49
48
|
"messages.assistantPrefix",
|
|
50
49
|
"messages.specialBlocks",
|
|
50
|
+
"messages.hideThinkingLabel",
|
|
51
51
|
"tools.enabled",
|
|
52
52
|
"tools.style",
|
|
53
53
|
"tools.maxCollapsedLines",
|
|
@@ -74,9 +74,9 @@ function validatePathValue(path: string, value: unknown): boolean {
|
|
|
74
74
|
"editor.enabled",
|
|
75
75
|
"editor.showMetadata",
|
|
76
76
|
"messages.enabled",
|
|
77
|
-
"messages.userPrefix",
|
|
78
77
|
"messages.assistantPrefix",
|
|
79
78
|
"messages.specialBlocks",
|
|
79
|
+
"messages.hideThinkingLabel",
|
|
80
80
|
"tools.enabled",
|
|
81
81
|
"tools.showElapsed",
|
|
82
82
|
"tools.dimOutput",
|
|
@@ -311,7 +311,6 @@ export function createPiStyleApp(
|
|
|
311
311
|
status: operational.installations?.status ?? "unknown",
|
|
312
312
|
editor: operational.installations?.editor ?? "unknown",
|
|
313
313
|
startup: operational.installations?.startup ?? "unknown",
|
|
314
|
-
userMessage: operational.compatibility?.userMessage ?? "unknown",
|
|
315
314
|
assistantMessage: operational.compatibility?.assistantMessage ?? "unknown",
|
|
316
315
|
specialBlocks: operational.compatibility?.specialBlocks ?? "unknown",
|
|
317
316
|
tools: operational.compatibility?.tools ?? "unknown",
|
|
@@ -4,7 +4,7 @@ export interface TierCAuthorizationInput {
|
|
|
4
4
|
readonly certifiedHost: boolean;
|
|
5
5
|
readonly coreFlag: boolean;
|
|
6
6
|
readonly surfaceFlag: boolean;
|
|
7
|
-
readonly surface: "messages" | "tools" | "
|
|
7
|
+
readonly surface: "messages" | "tools" | "assistantMessage" | "specialBlocks";
|
|
8
8
|
readonly config: NormalizedPiStyleConfig;
|
|
9
9
|
}
|
|
10
10
|
|
|
@@ -12,7 +12,6 @@ export interface TierCAuthorizationInput {
|
|
|
12
12
|
export function isTierCAuthorized(input: TierCAuthorizationInput): boolean {
|
|
13
13
|
if (!input.certifiedHost || !input.coreFlag || !input.surfaceFlag || !input.config.enabled) return false;
|
|
14
14
|
if (input.surface === "tools") return input.config.tools.enabled;
|
|
15
|
-
if (input.surface === "userMessage") return input.config.messages.enabled && input.config.messages.userPrefix;
|
|
16
15
|
if (input.surface === "assistantMessage")
|
|
17
16
|
return input.config.messages.enabled && input.config.messages.assistantPrefix;
|
|
18
17
|
if (input.surface === "specialBlocks") return input.config.messages.enabled && input.config.messages.specialBlocks;
|
|
@@ -28,7 +28,7 @@ export const DEFAULT_CONFIG: NormalizedPiStyleConfig = Object.freeze({
|
|
|
28
28
|
contextBarWidth: 10,
|
|
29
29
|
}),
|
|
30
30
|
editor: Object.freeze({ enabled: true, style: "compact", frame: "auto", showMetadata: false }),
|
|
31
|
-
messages: Object.freeze({ enabled: true,
|
|
31
|
+
messages: Object.freeze({ enabled: true, assistantPrefix: true, specialBlocks: true, hideThinkingLabel: true }),
|
|
32
32
|
tools: Object.freeze({
|
|
33
33
|
enabled: true,
|
|
34
34
|
style: "compact-box",
|
|
@@ -159,9 +159,9 @@ export function normalizeConfig(
|
|
|
159
159
|
}),
|
|
160
160
|
messages: Object.freeze({
|
|
161
161
|
enabled: bool(messages.enabled, defaults.messages.enabled),
|
|
162
|
-
userPrefix: bool(messages.userPrefix, defaults.messages.userPrefix),
|
|
163
162
|
assistantPrefix: bool(messages.assistantPrefix, defaults.messages.assistantPrefix),
|
|
164
163
|
specialBlocks: bool(messages.specialBlocks, defaults.messages.specialBlocks),
|
|
164
|
+
hideThinkingLabel: bool(messages.hideThinkingLabel, defaults.messages.hideThinkingLabel),
|
|
165
165
|
}),
|
|
166
166
|
tools: Object.freeze({
|
|
167
167
|
enabled: bool(tools.enabled, defaults.tools.enabled),
|
|
@@ -213,9 +213,9 @@ const BOOL_PATHS = new Set([
|
|
|
213
213
|
"editor.enabled",
|
|
214
214
|
"editor.showMetadata",
|
|
215
215
|
"messages.enabled",
|
|
216
|
-
"messages.userPrefix",
|
|
217
216
|
"messages.assistantPrefix",
|
|
218
217
|
"messages.specialBlocks",
|
|
218
|
+
"messages.hideThinkingLabel",
|
|
219
219
|
"tools.enabled",
|
|
220
220
|
"tools.showElapsed",
|
|
221
221
|
"tools.dimOutput",
|
|
@@ -32,7 +32,7 @@ export interface PiStyleConfig {
|
|
|
32
32
|
contextBarWidth?: number;
|
|
33
33
|
};
|
|
34
34
|
editor?: { enabled?: boolean; style?: string; frame?: string; showMetadata?: boolean };
|
|
35
|
-
messages?: { enabled?: boolean;
|
|
35
|
+
messages?: { enabled?: boolean; assistantPrefix?: boolean; specialBlocks?: boolean; hideThinkingLabel?: boolean };
|
|
36
36
|
tools?: {
|
|
37
37
|
enabled?: boolean;
|
|
38
38
|
style?: string;
|
|
@@ -79,7 +79,7 @@ export interface NormalizedPiStyleConfig {
|
|
|
79
79
|
contextBarWidth: number;
|
|
80
80
|
};
|
|
81
81
|
readonly editor: { enabled: boolean; style: EditorStyle; frame: EditorFrame; showMetadata: boolean };
|
|
82
|
-
readonly messages: { enabled: boolean;
|
|
82
|
+
readonly messages: { enabled: boolean; assistantPrefix: boolean; specialBlocks: boolean; hideThinkingLabel: boolean };
|
|
83
83
|
readonly tools: {
|
|
84
84
|
enabled: boolean;
|
|
85
85
|
style: string;
|
|
@@ -107,6 +107,7 @@ const GLYPHS = {
|
|
|
107
107
|
powerlineRight: "\uE0B2",
|
|
108
108
|
powerlineThinLeft: "\uE0B1",
|
|
109
109
|
powerlineThinRight: "\uE0B3",
|
|
110
|
+
batchOpen: "\u{F111}",
|
|
110
111
|
},
|
|
111
112
|
unicode: {
|
|
112
113
|
pi: "π",
|
|
@@ -118,6 +119,7 @@ const GLYPHS = {
|
|
|
118
119
|
powerlineRight: "‹",
|
|
119
120
|
powerlineThinLeft: "│",
|
|
120
121
|
powerlineThinRight: "│",
|
|
122
|
+
batchOpen: "●",
|
|
121
123
|
},
|
|
122
124
|
ascii: {
|
|
123
125
|
pi: "pi",
|
|
@@ -129,6 +131,7 @@ const GLYPHS = {
|
|
|
129
131
|
powerlineRight: "<",
|
|
130
132
|
powerlineThinLeft: "|",
|
|
131
133
|
powerlineThinRight: "|",
|
|
134
|
+
batchOpen: "v",
|
|
132
135
|
},
|
|
133
136
|
} as const;
|
|
134
137
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import type { Component } from "@earendil-works/pi-tui";
|
|
7
7
|
import type { BoxTheme } from "../../shared/box.js";
|
|
8
|
-
import { boxBorder, boxInnerWidth,
|
|
8
|
+
import { boxBlankLine, boxBorder, boxInnerWidth, boxLabeledBorder, boxLine, boxWidth } from "../../shared/box.js";
|
|
9
9
|
|
|
10
10
|
export type MessageBlockOptions = {
|
|
11
11
|
kind: string;
|
|
@@ -18,7 +18,7 @@ export type MessageBlockOptions = {
|
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
function formatMessageBlockTitle(theme: BoxTheme, kind: string, title?: string, icon = "➔"): string {
|
|
21
|
-
const rawTitle = title ? `${icon} ${kind}
|
|
21
|
+
const rawTitle = title ? `${icon} ${kind} · ${title}` : `${icon} ${kind}`;
|
|
22
22
|
const coloredTitle = theme.fg("accent", rawTitle);
|
|
23
23
|
return typeof theme?.bold === "function" ? theme.bold(coloredTitle) : coloredTitle;
|
|
24
24
|
}
|
|
@@ -26,13 +26,17 @@ function formatMessageBlockTitle(theme: BoxTheme, kind: string, title?: string,
|
|
|
26
26
|
/**
|
|
27
27
|
* Render a boxed message block.
|
|
28
28
|
*
|
|
29
|
+
* The title is embedded in the rounded top border, the body sits between
|
|
30
|
+
* blank padding rows, and the expand hint (when present) is embedded at the
|
|
31
|
+
* right end of the bottom border — no inset dividers.
|
|
32
|
+
*
|
|
29
33
|
* Returns only border/content lines with foreground styling. Background is
|
|
30
34
|
* applied by the parent Box (all patched components extend Box with a
|
|
31
35
|
* customMessageBg bgFn), so this helper must NOT apply background itself —
|
|
32
36
|
* that would create a double-background conflict.
|
|
33
37
|
*/
|
|
34
38
|
export function renderBoxedMessageBlock(theme: BoxTheme, options: MessageBlockOptions): Component {
|
|
35
|
-
const { kind, title, right, body, icon = "➔",
|
|
39
|
+
const { kind, title, right, body, icon = "➔", cache: shouldCache = true } = options;
|
|
36
40
|
let cache: { width: number; lines: string[] } | null = null;
|
|
37
41
|
|
|
38
42
|
return {
|
|
@@ -45,27 +49,19 @@ export function renderBoxedMessageBlock(theme: BoxTheme, options: MessageBlockOp
|
|
|
45
49
|
const renderedWidth = boxWidth(width);
|
|
46
50
|
const contentWidth = boxInnerWidth(renderedWidth);
|
|
47
51
|
const titleLine = formatMessageBlockTitle(theme, kind, title, icon);
|
|
52
|
+
const bodyLines = body(contentWidth);
|
|
48
53
|
|
|
49
|
-
const lines: string[] = [
|
|
50
|
-
|
|
51
|
-
|
|
54
|
+
const lines: string[] = [
|
|
55
|
+
boxLabeledBorder(theme, "╭", "╮", titleLine, undefined, renderedWidth),
|
|
56
|
+
boxBlankLine(theme, renderedWidth),
|
|
57
|
+
...bodyLines.map((line) => boxLine(theme, line, renderedWidth)),
|
|
58
|
+
];
|
|
59
|
+
if (bodyLines.length > 0) lines.push(boxBlankLine(theme, renderedWidth));
|
|
52
60
|
if (right) {
|
|
53
|
-
|
|
54
|
-
lines.push(boxLineWithRight(theme, titleLine, rightStyled, renderedWidth));
|
|
61
|
+
lines.push(boxLabeledBorder(theme, "╰", "╯", "", theme.fg("dim", right), renderedWidth));
|
|
55
62
|
} else {
|
|
56
|
-
lines.push(
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const bodyLines = body(contentWidth);
|
|
60
|
-
const showDivider = hasDivider === "auto" ? bodyLines.length > 0 : hasDivider;
|
|
61
|
-
if (showDivider) {
|
|
62
|
-
lines.push(boxInsetDivider(theme, renderedWidth));
|
|
63
|
+
lines.push(boxBorder(theme, "╰", "╯", renderedWidth));
|
|
63
64
|
}
|
|
64
|
-
for (const line of bodyLines) {
|
|
65
|
-
lines.push(boxLine(theme, line, renderedWidth));
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
lines.push(boxBorder(theme, "└", "┘", renderedWidth));
|
|
69
65
|
|
|
70
66
|
if (shouldCache) cache = { width, lines };
|
|
71
67
|
return lines;
|
|
@@ -12,22 +12,66 @@ function extractOscEnvelope(line: string): OscParts | undefined {
|
|
|
12
12
|
return { start: OSC133_ZONE_START, body: line.slice(OSC133_ZONE_START.length, bodyEnd), end: line.slice(bodyEnd) };
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
const BG_RESET = "\x1b[49m";
|
|
16
|
+
|
|
17
|
+
/** Leading zero-width OSC sequences (e.g. OSC133 markers) of a line. */
|
|
18
|
+
function splitLeadingMarkers(line: string): { head: string; rest: string } {
|
|
16
19
|
let index = 0;
|
|
20
|
+
while (line.startsWith("\x1b]", index)) {
|
|
21
|
+
const bel = line.indexOf("\x07", index + 2);
|
|
22
|
+
const st = line.indexOf("\x1b\\", index + 2);
|
|
23
|
+
const end = bel === -1 ? st : st === -1 ? bel : Math.min(bel, st);
|
|
24
|
+
if (end === -1) break;
|
|
25
|
+
index = end + 1;
|
|
26
|
+
}
|
|
27
|
+
return { head: line.slice(0, index), rest: line.slice(index) };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Leading SGR escape sequence of a line ("" when none). */
|
|
31
|
+
function leadingSgr(line: string): string {
|
|
32
|
+
if (!line.startsWith("\x1b[")) return "";
|
|
33
|
+
let index = 2;
|
|
17
34
|
while (index < line.length) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (index < line.length) index++;
|
|
22
|
-
continue;
|
|
23
|
-
}
|
|
24
|
-
if (/\s/u.test(line[index] ?? "")) {
|
|
25
|
-
index++;
|
|
26
|
-
continue;
|
|
27
|
-
}
|
|
28
|
-
break;
|
|
35
|
+
const code = line.charCodeAt(index);
|
|
36
|
+
if (code >= 64 && code <= 126) return line.slice(0, index + 1);
|
|
37
|
+
index++;
|
|
29
38
|
}
|
|
30
|
-
return
|
|
39
|
+
return "";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Whether an SGR sequence sets/resets the terminal background color. */
|
|
43
|
+
function isBackgroundSgr(sequence: string): boolean {
|
|
44
|
+
if (!sequence.startsWith("\x1b[") || !sequence.endsWith("m")) return false;
|
|
45
|
+
for (const code of sequence.slice(2, -1).split(";")) {
|
|
46
|
+
const value = Number(code);
|
|
47
|
+
if (value === 48 || value === 49) return true;
|
|
48
|
+
if (value >= 40 && value <= 47) return true;
|
|
49
|
+
if (value >= 100 && value <= 107) return true;
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Rebuild a native line so `lead` (prompt prefix / continuation indent) and the
|
|
56
|
+
* full target width are covered by the line's background.
|
|
57
|
+
*
|
|
58
|
+
* Native Box lines (user messages) are `bgAnsi + body + \x1b[49m`; prepending the
|
|
59
|
+
* prefix outside that wrap shifted the input row's background right by the
|
|
60
|
+
* prefix width while keeping the full container width, producing a staircase
|
|
61
|
+
* box (indented left, overflowing right). Rebuilding inside the wrap keeps the
|
|
62
|
+
* background flush across every row; plain (unwrapped) lines are padded to the
|
|
63
|
+
* target width instead so left/right edges stay aligned.
|
|
64
|
+
*/
|
|
65
|
+
function rebuildAtWidth(line: string, width: number, lead: string): string {
|
|
66
|
+
const { head, rest } = splitLeadingMarkers(line);
|
|
67
|
+
const bgAnsi = leadingSgr(rest);
|
|
68
|
+
if (bgAnsi && isBackgroundSgr(bgAnsi) && rest.endsWith(BG_RESET)) {
|
|
69
|
+
const body = rest.slice(bgAnsi.length, rest.length - BG_RESET.length);
|
|
70
|
+
const pad = " ".repeat(Math.max(0, width - visibleWidth(lead) - visibleWidth(body)));
|
|
71
|
+
return `${head}${bgAnsi}${lead}${body}${pad}${BG_RESET}`;
|
|
72
|
+
}
|
|
73
|
+
const padded = `${lead}${line}`;
|
|
74
|
+
return `${padded}${" ".repeat(Math.max(0, width - visibleWidth(padded)))}`;
|
|
31
75
|
}
|
|
32
76
|
|
|
33
77
|
function decorateMessageLine(
|
|
@@ -35,6 +79,7 @@ function decorateMessageLine(
|
|
|
35
79
|
index: number,
|
|
36
80
|
lastIndex: number,
|
|
37
81
|
contentIndex: number,
|
|
82
|
+
width: number,
|
|
38
83
|
options: {
|
|
39
84
|
firstEnvelope: OscParts | undefined;
|
|
40
85
|
firstHasStart: boolean;
|
|
@@ -44,14 +89,29 @@ function decorateMessageLine(
|
|
|
44
89
|
): string {
|
|
45
90
|
const { firstEnvelope, firstHasStart, multilineEnvelope, prefix } = options;
|
|
46
91
|
const prefixWidth = visibleWidth(prefix);
|
|
92
|
+
const lead = index === contentIndex ? prefix : index > contentIndex ? " ".repeat(prefixWidth) : "";
|
|
47
93
|
if (index === contentIndex && firstEnvelope)
|
|
48
|
-
return `${firstEnvelope.start}${
|
|
94
|
+
return `${firstEnvelope.start}${rebuildAtWidth(firstEnvelope.body, width, prefix)}${firstEnvelope.end}`;
|
|
49
95
|
if (index === contentIndex && firstHasStart)
|
|
50
|
-
return `${OSC133_ZONE_START}${
|
|
96
|
+
return `${OSC133_ZONE_START}${rebuildAtWidth(line.slice(OSC133_ZONE_START.length), width, prefix)}`;
|
|
51
97
|
if (index === lastIndex && multilineEnvelope && index !== contentIndex)
|
|
52
|
-
return `${OSC133_ZONE_END}${OSC133_ZONE_FINAL}${
|
|
53
|
-
|
|
54
|
-
|
|
98
|
+
return `${OSC133_ZONE_END}${OSC133_ZONE_FINAL}${rebuildAtWidth(
|
|
99
|
+
line.slice((OSC133_ZONE_END + OSC133_ZONE_FINAL).length),
|
|
100
|
+
width,
|
|
101
|
+
lead,
|
|
102
|
+
)}`;
|
|
103
|
+
if (
|
|
104
|
+
index === contentIndex &&
|
|
105
|
+
index === lastIndex &&
|
|
106
|
+
multilineEnvelope &&
|
|
107
|
+
line.startsWith(OSC133_ZONE_END + OSC133_ZONE_FINAL)
|
|
108
|
+
)
|
|
109
|
+
return `${OSC133_ZONE_END}${OSC133_ZONE_FINAL}${rebuildAtWidth(
|
|
110
|
+
line.slice((OSC133_ZONE_END + OSC133_ZONE_FINAL).length),
|
|
111
|
+
width,
|
|
112
|
+
prefix,
|
|
113
|
+
)}`;
|
|
114
|
+
return rebuildAtWidth(line, width, lead);
|
|
55
115
|
}
|
|
56
116
|
|
|
57
117
|
function contentText(line: string): string {
|
|
@@ -88,52 +148,49 @@ function prefixNative(lines: unknown, width: number, prefix: string): string[] |
|
|
|
88
148
|
const first = nativeLines[0] ?? "";
|
|
89
149
|
const last = nativeLines.at(-1) ?? "";
|
|
90
150
|
const multilineEnvelope = nativeLines.length > 1 && last.startsWith(OSC133_ZONE_END + OSC133_ZONE_FINAL);
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
151
|
+
// The last line is a content-start candidate only when the envelope is
|
|
152
|
+
// single-line, or when no earlier line carries content. Assistant messages
|
|
153
|
+
// with a single content line render as a multiline envelope whose only body
|
|
154
|
+
// sits on the final line ([OSC133_A, OSC133_END+FINAL+body]); excluding it
|
|
155
|
+
// would drop the prefix for every short assistant reply.
|
|
156
|
+
const firstContentIndex = nativeLines.findIndex((line, index) => {
|
|
157
|
+
if (index !== nativeLines.length - 1 || !multilineEnvelope) return hasContent(line);
|
|
158
|
+
return !nativeLines.slice(0, index).some((earlier) => hasContent(earlier)) && hasContent(line);
|
|
159
|
+
});
|
|
94
160
|
if (firstContentIndex < 0) return nativeLines;
|
|
95
161
|
const firstEnvelope = firstContentIndex === 0 ? extractOscEnvelope(first) : undefined;
|
|
96
162
|
const firstHasStart = firstContentIndex === 0 && first.startsWith(OSC133_ZONE_START);
|
|
97
|
-
const decorated = nativeLines.map((line, index) =>
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if (index > firstContentIndex && hasContent(line)) return `${" ".repeat(prefixWidth)}${line}`;
|
|
106
|
-
return line;
|
|
107
|
-
});
|
|
163
|
+
const decorated = nativeLines.map((line, index) =>
|
|
164
|
+
decorateMessageLine(line, index, nativeLines.length - 1, firstContentIndex, width, {
|
|
165
|
+
firstEnvelope,
|
|
166
|
+
firstHasStart,
|
|
167
|
+
multilineEnvelope,
|
|
168
|
+
prefix,
|
|
169
|
+
}),
|
|
170
|
+
);
|
|
108
171
|
if (!decorated.every((line) => visibleWidth(line) <= width)) return undefined;
|
|
109
172
|
if (!nativeLines.every((line) => visibleWidth(line) <= bodyWidth)) return undefined;
|
|
110
173
|
return decorated;
|
|
111
174
|
}
|
|
112
175
|
|
|
113
176
|
export type MessageDecorationSnapshot = Readonly<{
|
|
114
|
-
userPrefix: string;
|
|
115
177
|
assistantPrefix: string;
|
|
116
|
-
userEnabled: boolean;
|
|
117
178
|
assistantEnabled: boolean;
|
|
118
179
|
}>;
|
|
119
180
|
|
|
120
181
|
export function decorateMessageRender(
|
|
121
|
-
subtype: "native-user-message" | "native-assistant-message",
|
|
122
182
|
original: unknown,
|
|
123
183
|
instance: object,
|
|
124
184
|
args: unknown[],
|
|
125
185
|
snapshot: MessageDecorationSnapshot = {
|
|
126
|
-
userPrefix: "❯ ",
|
|
127
186
|
assistantPrefix: "│ ",
|
|
128
|
-
userEnabled: true,
|
|
129
187
|
assistantEnabled: true,
|
|
130
188
|
},
|
|
131
189
|
): unknown {
|
|
132
190
|
if (typeof original !== "function") return undefined;
|
|
133
191
|
const width = typeof args[0] === "number" ? args[0] : 0;
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
if (!enabled) return Reflect.apply(original, instance, args);
|
|
192
|
+
const prefix = snapshot.assistantPrefix;
|
|
193
|
+
if (!snapshot.assistantEnabled) return Reflect.apply(original, instance, args);
|
|
137
194
|
if (width <= visibleWidth(prefix)) return Reflect.apply(original, instance, args);
|
|
138
195
|
// Exactly one native invocation. If the reduced render cannot be certified, the
|
|
139
196
|
// already-obtained result is the only safe fallback; retrying can mutate state.
|
|
@@ -106,7 +106,7 @@ function patchCompaction(instance: MessageBlockInstance, _original: () => void,
|
|
|
106
106
|
const block = renderBoxedMessageBlock(theme, {
|
|
107
107
|
kind: "Compaction",
|
|
108
108
|
title: `${tokenStr} tokens`,
|
|
109
|
-
...(expanded ? {} : { right:
|
|
109
|
+
...(expanded ? {} : { right: expandHint() }),
|
|
110
110
|
body,
|
|
111
111
|
icon: "⊟",
|
|
112
112
|
hasDivider: expanded,
|
|
@@ -132,7 +132,7 @@ function patchSkill(instance: MessageBlockInstance, _original: () => void, theme
|
|
|
132
132
|
const block = renderBoxedMessageBlock(theme, {
|
|
133
133
|
kind: "Skill",
|
|
134
134
|
title: skillName,
|
|
135
|
-
...(expanded ? {} : { right:
|
|
135
|
+
...(expanded ? {} : { right: expandHint() }),
|
|
136
136
|
body,
|
|
137
137
|
icon: "⊟",
|
|
138
138
|
hasDivider: expanded,
|
|
@@ -156,7 +156,7 @@ function patchBranch(instance: MessageBlockInstance, _original: () => void, them
|
|
|
156
156
|
|
|
157
157
|
const block = renderBoxedMessageBlock(theme, {
|
|
158
158
|
kind: "Branch",
|
|
159
|
-
...(expanded ? {} : { right:
|
|
159
|
+
...(expanded ? {} : { right: expandHint() }),
|
|
160
160
|
body,
|
|
161
161
|
icon: "⊟",
|
|
162
162
|
hasDivider: expanded,
|
|
@@ -193,7 +193,7 @@ function renderSystemContextPanel(
|
|
|
193
193
|
"─".repeat(pathWidth),
|
|
194
194
|
)}${divider}${resolved.apply("dim", "─".repeat(metricWidth))}`;
|
|
195
195
|
const lines = [
|
|
196
|
-
renderPanelBorder(resolved, "
|
|
196
|
+
renderPanelBorder(resolved, "╭", "╮", panelWidth),
|
|
197
197
|
renderPanelLine(resolved, titleLine, panelWidth),
|
|
198
198
|
renderPanelLine(resolved, header, panelWidth),
|
|
199
199
|
renderPanelLine(resolved, separator, panelWidth),
|
|
@@ -215,7 +215,7 @@ function renderSystemContextPanel(
|
|
|
215
215
|
),
|
|
216
216
|
);
|
|
217
217
|
}
|
|
218
|
-
lines.push(renderPanelBorder(resolved, "
|
|
218
|
+
lines.push(renderPanelBorder(resolved, "╰", "╯", panelWidth));
|
|
219
219
|
return lines;
|
|
220
220
|
}
|
|
221
221
|
|
|
@@ -269,7 +269,7 @@ function renderToolsPanel(resolved: ResolvedTheme, tools: readonly StartupToolIt
|
|
|
269
269
|
"─".repeat(countWidth),
|
|
270
270
|
)}${divider}${resolved.apply("dim", "─".repeat(toolsWidth))}`;
|
|
271
271
|
const lines = [
|
|
272
|
-
renderPanelBorder(resolved, "
|
|
272
|
+
renderPanelBorder(resolved, "╭", "╮", panelWidth),
|
|
273
273
|
renderPanelLine(resolved, titleLine, panelWidth),
|
|
274
274
|
renderPanelLine(resolved, header, panelWidth),
|
|
275
275
|
renderPanelLine(resolved, separator, panelWidth),
|
|
@@ -291,7 +291,7 @@ function renderToolsPanel(resolved: ResolvedTheme, tools: readonly StartupToolIt
|
|
|
291
291
|
),
|
|
292
292
|
);
|
|
293
293
|
}
|
|
294
|
-
lines.push(renderPanelBorder(resolved, "
|
|
294
|
+
lines.push(renderPanelBorder(resolved, "╰", "╯", panelWidth));
|
|
295
295
|
return lines;
|
|
296
296
|
}
|
|
297
297
|
|