pi-one-ui 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -39
- package/extensions/{shell → app/commands}/settings-previews.ts +7 -7
- package/extensions/app/config/renderer.ts +13 -63
- package/extensions/app/config/shell.ts +24 -138
- package/extensions/app/config/store.ts +290 -0
- package/extensions/app/host/pi-extension-port.ts +25 -0
- package/extensions/app/host/pi-ui-port.ts +52 -0
- package/extensions/app/host/tui-capabilities.ts +27 -0
- package/extensions/app/overlay/input-router.ts +63 -0
- package/extensions/app/overlay/overlay-manager.ts +39 -0
- package/extensions/{shell → app/overlay}/selector-border.ts +3 -3
- package/extensions/app/overlay/selector-controller.ts +104 -0
- package/extensions/app/ownership/layout-registry.ts +65 -0
- package/extensions/app/ownership.ts +21 -7
- package/extensions/app/panel.ts +261 -140
- package/extensions/app/runtime/event-coordinator.ts +84 -0
- package/extensions/app/runtime/render-scheduler.ts +56 -0
- package/extensions/app/runtime/runtime-state.ts +83 -0
- package/extensions/app/runtime/tui-runtime.ts +546 -0
- package/extensions/features/{context.ts → context-inspector/index.ts} +30 -23
- package/extensions/features/{shell/flush-docked-bash.ts → flush-docked-bash.ts} +1 -4
- package/extensions/index.ts +2 -48
- package/extensions/layouts/context/index.ts +188 -0
- package/extensions/{shell → layouts/context/message}/user-message-styles.ts +2 -2
- package/extensions/{shell → layouts/context/message}/user-message.ts +2 -2
- package/extensions/{transcript → layouts/context}/renderer/compact-mode.ts +17 -13
- package/extensions/layouts/context/renderer/context-refresh.ts +79 -0
- package/extensions/{transcript → layouts/context}/renderer/default-mode.ts +3 -3
- package/extensions/{transcript → layouts/context}/renderer/index.ts +14 -14
- package/extensions/{transcript → layouts/context}/renderer/mouse/hover.ts +2 -2
- package/extensions/{transcript → layouts/context}/renderer/mouse/interaction.ts +26 -7
- package/extensions/{transcript → layouts/context}/renderer/mouse/layout.ts +1 -1
- package/extensions/{transcript → layouts/context}/renderer/mouse/packets.ts +3 -3
- package/extensions/{transcript → layouts/context}/renderer/mouse/scroll.ts +12 -7
- package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-component.ts +2 -2
- package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-edit-render.ts +1 -1
- package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-highlight.ts +1 -1
- package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-layout.ts +1 -1
- package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-limits.ts +1 -1
- package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-palette.ts +1 -1
- package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-presentation.ts +1 -1
- package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-renderer.ts +1 -1
- package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-write-render.ts +1 -1
- package/extensions/{transcript → layouts/context}/renderer/tool/diff/index.ts +2 -2
- package/extensions/{transcript → layouts/context}/renderer/tool/grouping.ts +4 -4
- package/extensions/{transcript → layouts/context}/renderer/tool/message-display.ts +6 -4
- package/extensions/{transcript → layouts/context}/renderer/tool/names.ts +2 -2
- package/extensions/{transcript → layouts/context}/renderer/tool/result.ts +3 -3
- package/extensions/{features/agent-summary → layouts/context/summary}/core.ts +1 -1
- package/extensions/{features → layouts/context/thinking}/compact-thinking.ts +7 -7
- package/extensions/{shell → layouts/editor}/accent-rail-editor.ts +8 -8
- package/extensions/{shell → layouts/editor}/completion-menu.ts +3 -6
- package/extensions/layouts/editor/controller.ts +613 -0
- package/extensions/{shell → layouts/editor}/editor-metadata-format.ts +6 -3
- package/extensions/layouts/editor/factory.ts +159 -0
- package/extensions/layouts/editor/index.ts +16 -0
- package/extensions/{shell → layouts/editor}/minimalist-editor.ts +4 -4
- package/extensions/layouts/editor/ownership.ts +103 -0
- package/extensions/{shell → layouts/editor}/ui.ts +2 -2
- package/extensions/layouts/footer/controller.ts +392 -0
- package/extensions/layouts/footer/data.ts +41 -0
- package/extensions/{shell → layouts/footer}/extension-status.ts +2 -2
- package/extensions/{shell → layouts/footer}/footer-layout.ts +1 -1
- package/extensions/{shell → layouts/footer}/footer.ts +11 -8
- package/extensions/layouts/footer/index.ts +24 -0
- package/extensions/layouts/header/index.ts +5 -0
- package/extensions/{features/shell → layouts/header}/startup-header.ts +105 -19
- package/extensions/layouts/working-line/controller.ts +291 -0
- package/extensions/{shell → layouts/working-line}/interaction-summary.ts +3 -3
- package/extensions/{shell → layouts/working-line}/working-line-spinners.ts +1 -1
- package/extensions/{shell → layouts/working-line}/working-line.ts +4 -4
- package/extensions/{shell → services}/live-context.ts +29 -1
- package/extensions/services/project-refresh.ts +374 -0
- package/extensions/{shell → services}/project-state.ts +5 -5
- package/extensions/{shell/state.ts → services/session-state.ts} +75 -5
- package/extensions/{shell → shared}/format.ts +4 -4
- package/extensions/tools/component-tree.ts +6 -6
- package/package.json +4 -4
- package/extensions/shell/index.ts +0 -1659
- package/extensions/shell/project-refresh.ts +0 -130
- package/extensions/shell/settings-command.ts +0 -2144
- package/extensions/transcript/index.ts +0 -65
- package/extensions/transcript/renderer/transcript-refresh.ts +0 -79
- /package/extensions/{shell → app/ownership}/prototype-patch-registry.ts +0 -0
- /package/extensions/{shell → app/runtime}/session-lifecycle.ts +0 -0
- /package/extensions/features/{shell/aliases.ts → aliases.ts} +0 -0
- /package/extensions/features/{shell → legacy}/working-message.ts +0 -0
- /package/extensions/features/{reference → session-reference}/index.ts +0 -0
- /package/extensions/features/{reference → session-reference}/session.ts +0 -0
- /package/extensions/features/{reference/subagent.ts → subagent-autocomplete.ts} +0 -0
- /package/extensions/{shell → layouts/context/message}/user-message-osc.ts +0 -0
- /package/extensions/{transcript → layouts/context}/renderer/markdown-enhance.ts +0 -0
- /package/extensions/{transcript → layouts/context}/renderer/tool/diff/ATTRIBUTION.md +0 -0
- /package/extensions/{transcript → layouts/context}/renderer/tool/diff/ansi-utils.ts +0 -0
- /package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-header.ts +0 -0
- /package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-inline.ts +0 -0
- /package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-parse.ts +0 -0
- /package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-text.ts +0 -0
- /package/extensions/{transcript → layouts/context}/renderer/tool/diff/line-width-safety.ts +0 -0
- /package/extensions/{transcript → layouts/context}/renderer/tool/diff/shiki-highlight.ts +0 -0
- /package/extensions/{transcript → layouts/context}/renderer/tool/diff/write-execution.ts +0 -0
- /package/extensions/{transcript → layouts/context}/renderer/tool/show-more-hint.ts +0 -0
- /package/extensions/{features/agent-summary → layouts/context/summary}/index.ts +0 -0
- /package/extensions/{shell → layouts/editor}/accent-rail-layout-patch.ts +0 -0
- /package/extensions/{shell → layouts/editor}/editor-transfer.ts +0 -0
- /package/extensions/{shell → layouts/footer}/footer-format.ts +0 -0
- /package/extensions/{shell → layouts/working-line}/working-line-messages.ts +0 -0
- /package/extensions/{shell/git.ts → services/git-data.ts} +0 -0
- /package/extensions/{shell/package-version.ts → services/package-data.ts} +0 -0
- /package/extensions/{shell/runtime.ts → services/runtime-data.ts} +0 -0
- /package/extensions/{shell → services}/telemetry.ts +0 -0
- /package/extensions/{shell → shared}/icons.ts +0 -0
- /package/extensions/{shell → shared}/style.ts +0 -0
|
@@ -13,47 +13,59 @@ const ANSI_RESET = "\x1b[0m";
|
|
|
13
13
|
const ANSI_PATTERN =
|
|
14
14
|
/[\u001B\u009B][[\]()#;?]*(?:(?:(?:[a-zA-Z\d]*(?:;[a-zA-Z\d]*)*)?\u0007)|(?:(?:\d{1,4}(?:;\d{0,4})*)?[\dA-PR-TZcf-nq-uy=><~]))/g;
|
|
15
15
|
|
|
16
|
-
//
|
|
17
|
-
//
|
|
16
|
+
// Official install.sh static logo. Short rows are padded to eight cells,
|
|
17
|
+
// and one empty row keeps the logo as tall as the tips column.
|
|
18
18
|
const LOGO_LINES = ["██████ ", "██ ██ ", "████ ██", "██ ██", " "];
|
|
19
19
|
|
|
20
|
-
// hero
|
|
20
|
+
// Single-line hero copy replacing Pi's default startup sentence.
|
|
21
21
|
const HERO_PREFIX = "There are many agent harnesses, but this one is ";
|
|
22
22
|
const HERO_HIGHLIGHT = "yours";
|
|
23
23
|
const HERO_SUFFIX = ".";
|
|
24
24
|
|
|
25
|
-
//
|
|
25
|
+
// Two-column layout: five logo rows on the left and five tips rows on the right.
|
|
26
26
|
const TWO_COL_GAP = 2;
|
|
27
|
-
//
|
|
27
|
+
// Narrower viewports fall back to a vertical logo and hero layout.
|
|
28
28
|
const TWO_COL_MIN_WIDTH = 48;
|
|
29
29
|
|
|
30
30
|
const FALLBACK_ACCENT_RGB: Rgb = [80, 160, 255];
|
|
31
31
|
const LOGO_BLOCK_WIDTH = Math.max(
|
|
32
32
|
...LOGO_LINES.map((line) => [...line].length),
|
|
33
33
|
);
|
|
34
|
-
//
|
|
34
|
+
// The tips column starts after the fixed logo width and gap.
|
|
35
35
|
const LEFT_COLUMN_WIDTH = LOGO_BLOCK_WIDTH;
|
|
36
36
|
|
|
37
37
|
const PALETTE_STEPS = 24;
|
|
38
38
|
const PALETTE_MAX_DARKEN = 0.18;
|
|
39
39
|
const PALETTE_MAX_LIGHTEN = 0.18;
|
|
40
|
-
//
|
|
40
|
+
// The row gradient uses a monotonic quarter-wave to avoid oscillation on short rows.
|
|
41
41
|
const PALETTE_SPAN = 0.25;
|
|
42
|
-
//
|
|
42
|
+
// A small phase offset between rows creates a diagonal top-left to bottom-right gradient.
|
|
43
43
|
const LOGO_ROW_PHASE_STEP = 0.08;
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Removes terminal styling sequences before measuring visible text.
|
|
47
|
+
*/
|
|
45
48
|
function stripAnsi(text: string): string {
|
|
46
49
|
return text.replace(ANSI_PATTERN, "");
|
|
47
50
|
}
|
|
48
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Returns the number of visible cells in a terminal string.
|
|
54
|
+
*/
|
|
49
55
|
function getVisibleLength(text: string): number {
|
|
50
56
|
return [...stripAnsi(text)].length;
|
|
51
57
|
}
|
|
52
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Clamps a color channel to the byte range accepted by truecolor ANSI.
|
|
61
|
+
*/
|
|
53
62
|
function clampByte(value: number): number {
|
|
54
63
|
return Math.max(0, Math.min(255, Math.round(value)));
|
|
55
64
|
}
|
|
56
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Interpolates one color channel between two byte values.
|
|
68
|
+
*/
|
|
57
69
|
function interpolateChannel(
|
|
58
70
|
start: number,
|
|
59
71
|
end: number,
|
|
@@ -62,6 +74,9 @@ function interpolateChannel(
|
|
|
62
74
|
return Math.round(start + (end - start) * factor);
|
|
63
75
|
}
|
|
64
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Interpolates all channels between two RGB colors.
|
|
79
|
+
*/
|
|
65
80
|
function interpolateRgb(start: Rgb, end: Rgb, factor: number): Rgb {
|
|
66
81
|
return [
|
|
67
82
|
interpolateChannel(start[0], end[0], factor),
|
|
@@ -70,6 +85,9 @@ function interpolateRgb(start: Rgb, end: Rgb, factor: number): Rgb {
|
|
|
70
85
|
];
|
|
71
86
|
}
|
|
72
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Darkens an RGB color by a normalized amount.
|
|
90
|
+
*/
|
|
73
91
|
function darkenRgb(rgb: Rgb, amount: number): Rgb {
|
|
74
92
|
return [
|
|
75
93
|
clampByte(rgb[0] * (1 - amount)),
|
|
@@ -78,6 +96,9 @@ function darkenRgb(rgb: Rgb, amount: number): Rgb {
|
|
|
78
96
|
];
|
|
79
97
|
}
|
|
80
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Lightens an RGB color by a normalized amount.
|
|
101
|
+
*/
|
|
81
102
|
function lightenRgb(rgb: Rgb, amount: number): Rgb {
|
|
82
103
|
return [
|
|
83
104
|
clampByte(rgb[0] + (255 - rgb[0]) * amount),
|
|
@@ -86,11 +107,17 @@ function lightenRgb(rgb: Rgb, amount: number): Rgb {
|
|
|
86
107
|
];
|
|
87
108
|
}
|
|
88
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Wraps text in a truecolor foreground sequence and a reset.
|
|
112
|
+
*/
|
|
89
113
|
function applyTruecolor(rgb: Rgb, text: string): string {
|
|
90
114
|
const [red, green, blue] = rgb;
|
|
91
115
|
return `\x1b[38;2;${red};${green};${blue}m${text}${ANSI_RESET}`;
|
|
92
116
|
}
|
|
93
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Parses a truecolor foreground from an ANSI sequence.
|
|
120
|
+
*/
|
|
94
121
|
function parseTruecolorAnsi(ansi: string): Rgb | undefined {
|
|
95
122
|
const match = ansi.match(/38;2;(\d+);(\d+);(\d+)/);
|
|
96
123
|
if (!match) return undefined;
|
|
@@ -98,6 +125,9 @@ function parseTruecolorAnsi(ansi: string): Rgb | undefined {
|
|
|
98
125
|
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
99
126
|
}
|
|
100
127
|
|
|
128
|
+
/**
|
|
129
|
+
* Parses an ANSI 256-color foreground and converts it to RGB.
|
|
130
|
+
*/
|
|
101
131
|
function parseAnsi256Foreground(ansi: string): Rgb | undefined {
|
|
102
132
|
const match = ansi.match(/38;5;(\d+)/);
|
|
103
133
|
if (!match) return undefined;
|
|
@@ -105,6 +135,9 @@ function parseAnsi256Foreground(ansi: string): Rgb | undefined {
|
|
|
105
135
|
return ansi256ToRgb(Number(match[1]));
|
|
106
136
|
}
|
|
107
137
|
|
|
138
|
+
/**
|
|
139
|
+
* Parses a standard or bright ANSI foreground and converts it to RGB.
|
|
140
|
+
*/
|
|
108
141
|
function parseAnsi16Foreground(ansi: string): Rgb | undefined {
|
|
109
142
|
const normalMatch = ansi.match(/(?:\[|;)(3[0-7])(?:;|m)/);
|
|
110
143
|
if (normalMatch) {
|
|
@@ -119,6 +152,9 @@ function parseAnsi16Foreground(ansi: string): Rgb | undefined {
|
|
|
119
152
|
return undefined;
|
|
120
153
|
}
|
|
121
154
|
|
|
155
|
+
/**
|
|
156
|
+
* Resolves the first supported foreground color found in an ANSI sequence.
|
|
157
|
+
*/
|
|
122
158
|
function parseForegroundRgbFromAnsi(ansi: string): Rgb | undefined {
|
|
123
159
|
return (
|
|
124
160
|
parseTruecolorAnsi(ansi) ??
|
|
@@ -127,12 +163,18 @@ function parseForegroundRgbFromAnsi(ansi: string): Rgb | undefined {
|
|
|
127
163
|
);
|
|
128
164
|
}
|
|
129
165
|
|
|
166
|
+
/**
|
|
167
|
+
* Resolves the theme accent color, falling back to a stable blue.
|
|
168
|
+
*/
|
|
130
169
|
function resolveAccentRgb(theme: { getFgAnsi(name: string): string }): Rgb {
|
|
131
170
|
return (
|
|
132
171
|
parseForegroundRgbFromAnsi(theme.getFgAnsi("accent")) ?? FALLBACK_ACCENT_RGB
|
|
133
172
|
);
|
|
134
173
|
}
|
|
135
174
|
|
|
175
|
+
/**
|
|
176
|
+
* Builds the cyclic dark-to-light palette used by the logo gradient.
|
|
177
|
+
*/
|
|
136
178
|
function buildAccentPalette(accent: Rgb): Rgb[] {
|
|
137
179
|
return Array.from({ length: PALETTE_STEPS }, (_, index) => {
|
|
138
180
|
const progress = index / PALETTE_STEPS;
|
|
@@ -146,6 +188,9 @@ function buildAccentPalette(accent: Rgb): Rgb[] {
|
|
|
146
188
|
});
|
|
147
189
|
}
|
|
148
190
|
|
|
191
|
+
/**
|
|
192
|
+
* Samples one wrapped position from an RGB palette.
|
|
193
|
+
*/
|
|
149
194
|
function sampleGradientColor(palette: Rgb[], position: number): Rgb {
|
|
150
195
|
const wrappedPosition = ((position % 1) + 1) % 1;
|
|
151
196
|
const scaledPosition = wrappedPosition * palette.length;
|
|
@@ -156,6 +201,9 @@ function sampleGradientColor(palette: Rgb[], position: number): Rgb {
|
|
|
156
201
|
return interpolateRgb(palette[baseIndex]!, palette[nextIndex]!, factor);
|
|
157
202
|
}
|
|
158
203
|
|
|
204
|
+
/**
|
|
205
|
+
* Applies a diagonal accent gradient to non-space characters.
|
|
206
|
+
*/
|
|
159
207
|
function renderGradientText(
|
|
160
208
|
text: string,
|
|
161
209
|
palette: Rgb[],
|
|
@@ -167,7 +215,7 @@ function renderGradientText(
|
|
|
167
215
|
return characters
|
|
168
216
|
.map((character, index) => {
|
|
169
217
|
if (character === " ") return character;
|
|
170
|
-
//
|
|
218
|
+
// Use a monotonic quarter-wave within each row and phase-shift rows.
|
|
171
219
|
const color = sampleGradientColor(
|
|
172
220
|
palette,
|
|
173
221
|
(index / span) * PALETTE_SPAN + phase,
|
|
@@ -177,6 +225,9 @@ function renderGradientText(
|
|
|
177
225
|
.join("");
|
|
178
226
|
}
|
|
179
227
|
|
|
228
|
+
/**
|
|
229
|
+
* Centers a fixed-width block within the available viewport.
|
|
230
|
+
*/
|
|
180
231
|
function createCenteredBlockLine(
|
|
181
232
|
text: string,
|
|
182
233
|
width: number,
|
|
@@ -186,6 +237,9 @@ function createCenteredBlockLine(
|
|
|
186
237
|
return `${" ".repeat(leftPadding)}${text}`;
|
|
187
238
|
}
|
|
188
239
|
|
|
240
|
+
/**
|
|
241
|
+
* Centers styled parts while measuring their unstyled representation.
|
|
242
|
+
*/
|
|
189
243
|
function createCenteredStyledLine(parts: StyledPart[], width: number): string {
|
|
190
244
|
const rawText = parts.map((part) => part.raw).join("");
|
|
191
245
|
const leftPadding = Math.max(
|
|
@@ -196,6 +250,9 @@ function createCenteredStyledLine(parts: StyledPart[], width: number): string {
|
|
|
196
250
|
return `${" ".repeat(leftPadding)}${styledText}`;
|
|
197
251
|
}
|
|
198
252
|
|
|
253
|
+
/**
|
|
254
|
+
* Truncates a rendered line without allowing ANSI escapes to affect width.
|
|
255
|
+
*/
|
|
199
256
|
function fitLineToWidth(line: string, width: number): string {
|
|
200
257
|
if (getVisibleLength(line) <= width) {
|
|
201
258
|
return line;
|
|
@@ -204,6 +261,9 @@ function fitLineToWidth(line: string, width: number): string {
|
|
|
204
261
|
return stripAnsi(line).slice(0, width);
|
|
205
262
|
}
|
|
206
263
|
|
|
264
|
+
/**
|
|
265
|
+
* Renders all logo rows at the requested width and gradient phase.
|
|
266
|
+
*/
|
|
207
267
|
function renderLogoLines(width: number, palette: Rgb[]): string[] {
|
|
208
268
|
return LOGO_LINES.map((line, rowIndex) => {
|
|
209
269
|
const phasedLine = renderGradientText(
|
|
@@ -215,15 +275,21 @@ function renderLogoLines(width: number, palette: Rgb[]): string[] {
|
|
|
215
275
|
});
|
|
216
276
|
}
|
|
217
277
|
|
|
218
|
-
//
|
|
278
|
+
// The right column mirrors Pi's startup hints and reads active keybindings.
|
|
219
279
|
|
|
280
|
+
/**
|
|
281
|
+
* Normalizes one keybinding part for the current operating system.
|
|
282
|
+
*/
|
|
220
283
|
function formatKeyPart(part: string): string {
|
|
221
|
-
//
|
|
284
|
+
// Match Pi's keybinding hints: macOS displays alt as option.
|
|
222
285
|
return process.platform === "darwin" && part.toLowerCase() === "alt"
|
|
223
286
|
? "option"
|
|
224
287
|
: part;
|
|
225
288
|
}
|
|
226
289
|
|
|
290
|
+
/**
|
|
291
|
+
* Formats a slash- or plus-separated keybinding for display.
|
|
292
|
+
*/
|
|
227
293
|
function formatKeyText(key: string): string {
|
|
228
294
|
return key
|
|
229
295
|
.split("/")
|
|
@@ -231,16 +297,22 @@ function formatKeyText(key: string): string {
|
|
|
231
297
|
.join("/");
|
|
232
298
|
}
|
|
233
299
|
|
|
300
|
+
/**
|
|
301
|
+
* Resolves the configured display text for one application keybinding.
|
|
302
|
+
*/
|
|
234
303
|
function keyText(keybinding: AppKeybinding): string {
|
|
235
304
|
const keys = getKeybindings().getKeys(keybinding);
|
|
236
305
|
return keys.length === 0 ? "" : formatKeyText(keys.join("/"));
|
|
237
306
|
}
|
|
238
307
|
|
|
308
|
+
/**
|
|
309
|
+
* Renders the native startup hints shown beside the logo.
|
|
310
|
+
*/
|
|
239
311
|
function renderNativeLines(theme: {
|
|
240
312
|
fg(name: string, text: string): string;
|
|
241
313
|
bold(text: string): string;
|
|
242
314
|
}): string[] {
|
|
243
|
-
//
|
|
315
|
+
// Match Pi's header palette: dim keys, muted descriptions and separators.
|
|
244
316
|
const hint = (keybinding: AppKeybinding, description: string) =>
|
|
245
317
|
theme.fg("dim", keyText(keybinding)) + theme.fg("muted", ` ${description}`);
|
|
246
318
|
const rawHint = (key: string, description: string) =>
|
|
@@ -264,13 +336,16 @@ function renderNativeLines(theme: {
|
|
|
264
336
|
`Press ${keyText("app.tools.expand")} to show full startup help and loaded resources.`,
|
|
265
337
|
),
|
|
266
338
|
"",
|
|
267
|
-
//
|
|
339
|
+
// Replace the native "Pi can explain..." row with the hero sentence.
|
|
268
340
|
renderHeroParts(theme)
|
|
269
341
|
.map((part) => part.styled)
|
|
270
342
|
.join(""),
|
|
271
343
|
];
|
|
272
344
|
}
|
|
273
345
|
|
|
346
|
+
/**
|
|
347
|
+
* Builds the styled hero sentence and its raw-width counterparts.
|
|
348
|
+
*/
|
|
274
349
|
function renderHeroParts(theme: {
|
|
275
350
|
fg(name: string, text: string): string;
|
|
276
351
|
bold(text: string): string;
|
|
@@ -285,6 +360,13 @@ function renderHeroParts(theme: {
|
|
|
285
360
|
];
|
|
286
361
|
}
|
|
287
362
|
|
|
363
|
+
/**
|
|
364
|
+
* Renders the responsive startup header for one viewport width.
|
|
365
|
+
*
|
|
366
|
+
* @param width Available terminal width in cells.
|
|
367
|
+
* @param theme Theme functions used for styling and accent discovery.
|
|
368
|
+
* @returns Header rows that fit the requested layout.
|
|
369
|
+
*/
|
|
288
370
|
export function renderHeaderLines(
|
|
289
371
|
width: number,
|
|
290
372
|
theme: {
|
|
@@ -297,7 +379,7 @@ export function renderHeaderLines(
|
|
|
297
379
|
const palette = buildAccentPalette(accentRgb);
|
|
298
380
|
|
|
299
381
|
if (width < TWO_COL_MIN_WIDTH) {
|
|
300
|
-
//
|
|
382
|
+
// Narrow fallback: vertically stack the centered logo and hero.
|
|
301
383
|
const logoLines = renderLogoLines(width, palette);
|
|
302
384
|
const heroLine = createCenteredStyledLine(renderHeroParts(theme), width);
|
|
303
385
|
return ["", ...logoLines, "", heroLine, ""].map((line) =>
|
|
@@ -305,7 +387,7 @@ export function renderHeaderLines(
|
|
|
305
387
|
);
|
|
306
388
|
}
|
|
307
389
|
|
|
308
|
-
//
|
|
390
|
+
// Wide layout: place the equal-height logo and native tips columns side by side.
|
|
309
391
|
const leftLines = renderLogoLines(LEFT_COLUMN_WIDTH, palette);
|
|
310
392
|
const rightLines = renderNativeLines(theme);
|
|
311
393
|
const rightWidth = width - LEFT_COLUMN_WIDTH - TWO_COL_GAP;
|
|
@@ -326,13 +408,14 @@ export function renderHeaderLines(
|
|
|
326
408
|
}
|
|
327
409
|
|
|
328
410
|
/**
|
|
329
|
-
*
|
|
330
|
-
*
|
|
411
|
+
* Applies the configured startup header or restores Pi's native header.
|
|
412
|
+
*
|
|
413
|
+
* @param ctx Active extension context supplied by Pi.
|
|
331
414
|
*/
|
|
332
415
|
export function applyStartupHeader(ctx: any): void {
|
|
333
416
|
if (!ctx?.hasUI || typeof ctx.ui?.setHeader !== "function") return;
|
|
334
417
|
if (!config.showStartupHeader) {
|
|
335
|
-
//
|
|
418
|
+
// Restore Pi's built-in logo, key hints and onboarding content.
|
|
336
419
|
ctx.ui.setHeader(undefined);
|
|
337
420
|
return;
|
|
338
421
|
}
|
|
@@ -344,7 +427,10 @@ export function applyStartupHeader(ctx: any): void {
|
|
|
344
427
|
}));
|
|
345
428
|
}
|
|
346
429
|
|
|
347
|
-
|
|
430
|
+
/**
|
|
431
|
+
* Registers the Header layout lifecycle with the Pi extension host.
|
|
432
|
+
*/
|
|
433
|
+
export default function registerHeaderLayout(pi: ExtensionAPI) {
|
|
348
434
|
pi.on("session_start", async (_event, ctx) => {
|
|
349
435
|
applyStartupHeader(ctx);
|
|
350
436
|
});
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ExtensionAPI,
|
|
3
|
+
ExtensionContext,
|
|
4
|
+
Theme,
|
|
5
|
+
} from "@earendil-works/pi-coding-agent";
|
|
6
|
+
import {
|
|
7
|
+
InteractionMetricsTracker,
|
|
8
|
+
TURN_SUMMARY_ENTRY_TYPE,
|
|
9
|
+
type MetricsUpdateResult,
|
|
10
|
+
} from "./interaction-summary.ts";
|
|
11
|
+
import {
|
|
12
|
+
AgentDurationClock,
|
|
13
|
+
snapshotWorkingLineHighStyle,
|
|
14
|
+
WorkingLineController,
|
|
15
|
+
} from "./working-line.ts";
|
|
16
|
+
import {
|
|
17
|
+
type PolishedTuiConfig,
|
|
18
|
+
type WorkingLineComponentPatch,
|
|
19
|
+
} from "../../app/config/shell.ts";
|
|
20
|
+
import type { SessionLifecycle } from "../../app/runtime/session-lifecycle.ts";
|
|
21
|
+
|
|
22
|
+
export type WorkingLineMessage = Parameters<
|
|
23
|
+
InteractionMetricsTracker["messageUpdate"]
|
|
24
|
+
>[0];
|
|
25
|
+
export type WorkingLineMessageEnd = Parameters<
|
|
26
|
+
InteractionMetricsTracker["messageEnd"]
|
|
27
|
+
>[0];
|
|
28
|
+
|
|
29
|
+
export type WorkingLineLayoutContext = {
|
|
30
|
+
readonly pi: ExtensionAPI;
|
|
31
|
+
readonly getConfig: () => PolishedTuiConfig;
|
|
32
|
+
readonly saveComponent: (
|
|
33
|
+
patch: WorkingLineComponentPatch,
|
|
34
|
+
) => PolishedTuiConfig;
|
|
35
|
+
readonly getTheme: () => Theme;
|
|
36
|
+
readonly sessionLifecycle: SessionLifecycle;
|
|
37
|
+
readonly refresh: () => void;
|
|
38
|
+
readonly onAgentActiveChanged: (active: boolean) => void;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Owns WorkingLine lifecycle, interaction metrics, timing, and summary writes.
|
|
43
|
+
*/
|
|
44
|
+
export class WorkingLineLayoutController {
|
|
45
|
+
private readonly context: WorkingLineLayoutContext;
|
|
46
|
+
private readonly durationClock = new AgentDurationClock();
|
|
47
|
+
private readonly metrics = new InteractionMetricsTracker();
|
|
48
|
+
private readonly workingLine: WorkingLineController;
|
|
49
|
+
private agentRunActive = false;
|
|
50
|
+
private writeSummary = true;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Creates a WorkingLine controller with one timing and metrics owner.
|
|
54
|
+
*
|
|
55
|
+
* @param context Shared runtime services consumed by WorkingLine.
|
|
56
|
+
*/
|
|
57
|
+
constructor(context: WorkingLineLayoutContext) {
|
|
58
|
+
this.context = context;
|
|
59
|
+
this.workingLine = new WorkingLineController(
|
|
60
|
+
context.getConfig,
|
|
61
|
+
context.getTheme,
|
|
62
|
+
this.durationClock,
|
|
63
|
+
Math.random,
|
|
64
|
+
Date.now,
|
|
65
|
+
() => this.metrics.currentThought(),
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Starts a new WorkingLine session and clears interaction state.
|
|
71
|
+
*
|
|
72
|
+
* @param ctx Active Pi extension context.
|
|
73
|
+
*/
|
|
74
|
+
startSession(ctx: ExtensionContext): void {
|
|
75
|
+
this.metrics.shutdown();
|
|
76
|
+
this.agentRunActive = false;
|
|
77
|
+
this.durationClock.reset();
|
|
78
|
+
this.context.onAgentActiveChanged(false);
|
|
79
|
+
this.workingLine.startSession(ctx);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Starts an agent run and installs the current working indicator.
|
|
84
|
+
*
|
|
85
|
+
* @param ctx Active Pi extension context.
|
|
86
|
+
*/
|
|
87
|
+
agentStart(ctx: ExtensionContext): void {
|
|
88
|
+
const { interactionStarted } = this.metrics.agentStart();
|
|
89
|
+
this.agentRunActive = true;
|
|
90
|
+
if (interactionStarted) this.durationClock.start();
|
|
91
|
+
this.context.onAgentActiveChanged(true);
|
|
92
|
+
this.workingLine.startAgent(ctx);
|
|
93
|
+
this.context.refresh();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Starts a new turn response and selects its stable working message.
|
|
98
|
+
*
|
|
99
|
+
* @param ctx Active Pi extension context.
|
|
100
|
+
*/
|
|
101
|
+
turnStart(ctx: ExtensionContext): void {
|
|
102
|
+
this.metrics.turnStart();
|
|
103
|
+
this.workingLine.startTurn(ctx);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Records an assistant message update and refreshes visible metrics.
|
|
108
|
+
*
|
|
109
|
+
* @param message Latest assistant message snapshot.
|
|
110
|
+
* @param event Optional provider streaming event.
|
|
111
|
+
* @param ctx Active Pi extension context.
|
|
112
|
+
* @returns Metrics changes produced by the update.
|
|
113
|
+
*/
|
|
114
|
+
messageUpdate(
|
|
115
|
+
message: WorkingLineMessage,
|
|
116
|
+
event: Parameters<InteractionMetricsTracker["messageUpdate"]>[1],
|
|
117
|
+
ctx: ExtensionContext,
|
|
118
|
+
): MetricsUpdateResult {
|
|
119
|
+
const result = this.metrics.messageUpdate(message, event);
|
|
120
|
+
if (result.usageChanged || result.thoughtChanged) {
|
|
121
|
+
this.workingLine.updateMetrics(
|
|
122
|
+
result.displayTokens,
|
|
123
|
+
this.metrics.currentThought(),
|
|
124
|
+
ctx,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Finalizes one assistant message and updates WorkingLine metrics.
|
|
132
|
+
*
|
|
133
|
+
* @param message Final assistant message.
|
|
134
|
+
* @param ctx Active Pi extension context.
|
|
135
|
+
* @returns Message acceptance and display metrics.
|
|
136
|
+
*/
|
|
137
|
+
messageEnd(message: WorkingLineMessageEnd, ctx: ExtensionContext) {
|
|
138
|
+
const result = this.metrics.messageEnd(message);
|
|
139
|
+
if (result.status === "accepted") {
|
|
140
|
+
this.workingLine.updateMetrics(
|
|
141
|
+
result.displayTokens,
|
|
142
|
+
this.metrics.currentThought(),
|
|
143
|
+
ctx,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Ends an agent run while retaining settled interaction metrics.
|
|
151
|
+
*
|
|
152
|
+
* @param ctx Active Pi extension context.
|
|
153
|
+
*/
|
|
154
|
+
agentEnd(ctx: ExtensionContext): void {
|
|
155
|
+
const displayTokens = this.metrics.currentDisplayTokens();
|
|
156
|
+
this.metrics.agentEnd();
|
|
157
|
+
this.agentRunActive = false;
|
|
158
|
+
this.context.onAgentActiveChanged(false);
|
|
159
|
+
this.workingLine.finishAgent(ctx);
|
|
160
|
+
this.workingLine.updateMetrics(
|
|
161
|
+
displayTokens,
|
|
162
|
+
this.metrics.currentThought(),
|
|
163
|
+
ctx,
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Starts and finishes one tool activity in the WorkingLine.
|
|
169
|
+
*
|
|
170
|
+
* @param toolCallId Pi tool call identifier.
|
|
171
|
+
* @param toolName Display name of the tool.
|
|
172
|
+
* @param ctx Active Pi extension context.
|
|
173
|
+
*/
|
|
174
|
+
toolStart(toolCallId: string, toolName: string, ctx: ExtensionContext): void {
|
|
175
|
+
this.workingLine.startTool(toolCallId, toolName, ctx);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Finishes one tool activity in the WorkingLine.
|
|
180
|
+
*
|
|
181
|
+
* @param toolCallId Pi tool call identifier.
|
|
182
|
+
* @param ctx Active Pi extension context.
|
|
183
|
+
*/
|
|
184
|
+
toolEnd(toolCallId: string, ctx: ExtensionContext): void {
|
|
185
|
+
this.workingLine.finishTool(toolCallId, ctx);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Configures whether this controller writes the canonical turn summary.
|
|
190
|
+
*
|
|
191
|
+
* @param enabled Whether summary entries should be persisted.
|
|
192
|
+
*/
|
|
193
|
+
setSummaryWriterEnabled(enabled: boolean): void {
|
|
194
|
+
this.writeSummary = enabled;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Settles completed runs and writes one canonical summary entry when enabled.
|
|
199
|
+
*
|
|
200
|
+
* @param ctx Active Pi extension context.
|
|
201
|
+
*/
|
|
202
|
+
agentSettled(ctx: ExtensionContext): void {
|
|
203
|
+
const settled = this.metrics.settle(ctx.isIdle());
|
|
204
|
+
if (!settled) return;
|
|
205
|
+
this.agentRunActive = settled.nextStartedAt !== undefined;
|
|
206
|
+
if (settled.nextStartedAt === undefined) this.durationClock.finish();
|
|
207
|
+
else this.durationClock.start(settled.nextStartedAt);
|
|
208
|
+
this.context.onAgentActiveChanged(this.agentRunActive);
|
|
209
|
+
this.workingLine.settle(settled.nextTokens, settled.nextThought, ctx);
|
|
210
|
+
const config = this.context.getConfig().components.workingLine;
|
|
211
|
+
if (!this.writeSummary || !config.enabled || !config.turnSummary) return;
|
|
212
|
+
try {
|
|
213
|
+
this.context.pi.appendEntry(TURN_SUMMARY_ENTRY_TYPE, {
|
|
214
|
+
version: 3,
|
|
215
|
+
...settled.summary,
|
|
216
|
+
stylePrefix: snapshotWorkingLineHighStyle(
|
|
217
|
+
ctx.ui.theme,
|
|
218
|
+
config,
|
|
219
|
+
this.context.getConfig().colors,
|
|
220
|
+
),
|
|
221
|
+
});
|
|
222
|
+
} catch {
|
|
223
|
+
// Summary persistence failure must not break settlement cleanup.
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Applies a WorkingLine configuration patch and reconciles the host row.
|
|
229
|
+
*
|
|
230
|
+
* @param patch WorkingLine configuration changes to persist.
|
|
231
|
+
* @param ctx Active Pi extension context.
|
|
232
|
+
* @returns Whether the host working row accepted the change.
|
|
233
|
+
*/
|
|
234
|
+
setComponent(
|
|
235
|
+
patch: WorkingLineComponentPatch,
|
|
236
|
+
ctx: ExtensionContext,
|
|
237
|
+
): { applied: boolean; reason?: string } {
|
|
238
|
+
this.context.saveComponent(patch);
|
|
239
|
+
return this.workingLine.reconcile(ctx);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Reconciles WorkingLine configuration without changing persisted settings.
|
|
244
|
+
*
|
|
245
|
+
* @param ctx Active Pi extension context.
|
|
246
|
+
* @returns Whether the host working row accepted the change.
|
|
247
|
+
*/
|
|
248
|
+
reconcile(ctx: ExtensionContext) {
|
|
249
|
+
return this.workingLine.reconcile(ctx);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Disposes the WorkingLine and clears all interaction-local state.
|
|
254
|
+
*
|
|
255
|
+
* @param ctx Active Pi extension context.
|
|
256
|
+
*/
|
|
257
|
+
dispose(ctx: ExtensionContext): void {
|
|
258
|
+
this.workingLine.dispose(ctx);
|
|
259
|
+
this.metrics.shutdown();
|
|
260
|
+
this.durationClock.reset();
|
|
261
|
+
this.agentRunActive = false;
|
|
262
|
+
this.context.onAgentActiveChanged(false);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Returns the shared duration clock used by Editor selectors.
|
|
267
|
+
*
|
|
268
|
+
* @returns Session-local duration clock.
|
|
269
|
+
*/
|
|
270
|
+
duration(): AgentDurationClock {
|
|
271
|
+
return this.durationClock;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Reports whether the agent run is currently active.
|
|
276
|
+
*
|
|
277
|
+
* @returns Whether WorkingLine owns an active agent run.
|
|
278
|
+
*/
|
|
279
|
+
isAgentActive(): boolean {
|
|
280
|
+
return this.agentRunActive;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Returns the currently active interaction thought snapshot.
|
|
285
|
+
*
|
|
286
|
+
* @returns Current thought metrics.
|
|
287
|
+
*/
|
|
288
|
+
currentThought() {
|
|
289
|
+
return this.metrics.currentThought();
|
|
290
|
+
}
|
|
291
|
+
}
|
|
@@ -4,9 +4,9 @@ import type {
|
|
|
4
4
|
} from "@earendil-works/pi-ai";
|
|
5
5
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
6
6
|
import { Text } from "@earendil-works/pi-tui";
|
|
7
|
-
import type { ColorSource, ColorSpec } from "
|
|
8
|
-
import { formatCount, formatElapsedDuration } from "
|
|
9
|
-
import { isSafeSgrStylePrefix } from "
|
|
7
|
+
import type { ColorSource, ColorSpec } from "../../app/config/shell.ts";
|
|
8
|
+
import { formatCount, formatElapsedDuration } from "../../shared/format.ts";
|
|
9
|
+
import { isSafeSgrStylePrefix } from "../../shared/style.ts";
|
|
10
10
|
import { renderWorkingLineHigh } from "./working-line.ts";
|
|
11
11
|
|
|
12
12
|
export const TURN_SUMMARY_ENTRY_TYPE = "zentui-turn-summary";
|
|
@@ -6,15 +6,15 @@ import type {
|
|
|
6
6
|
WorkingLineComponentConfig,
|
|
7
7
|
WorkingLineTextAnimation,
|
|
8
8
|
ZentuiConfig,
|
|
9
|
-
} from "
|
|
10
|
-
import { formatCount } from "
|
|
9
|
+
} from "../../app/config/shell.ts";
|
|
10
|
+
import { formatCount } from "../../shared/format.ts";
|
|
11
11
|
import {
|
|
12
12
|
isSafeSgrStylePrefix,
|
|
13
13
|
isSupportedColorSpec,
|
|
14
14
|
renderStyleForSourceOrFallback,
|
|
15
15
|
type SourceStyleFallback,
|
|
16
16
|
type ThemeLike,
|
|
17
|
-
} from "
|
|
17
|
+
} from "../../shared/style.ts";
|
|
18
18
|
import { PI_WORKING_LINE_MESSAGES } from "./working-line-messages.ts";
|
|
19
19
|
import { WORKING_LINE_SPINNERS } from "./working-line-spinners.ts";
|
|
20
20
|
|
|
@@ -1538,7 +1538,7 @@ export class WorkingLineController {
|
|
|
1538
1538
|
if (snapshot.ownsMessage) ui.setWorkingMessage("");
|
|
1539
1539
|
} catch {
|
|
1540
1540
|
// Recovery is deliberately direct rather than recursive. If either setter cannot
|
|
1541
|
-
// restore the last successful public state, release both unkeyed
|
|
1541
|
+
// restore the last successful public state, release both unkeyed layouts.
|
|
1542
1542
|
this.releaseAfterFailure(ui);
|
|
1543
1543
|
}
|
|
1544
1544
|
}
|