@aiwayds/dsh-tui-pi 0.1.0 → 0.3.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 +205 -68
- package/lib/agent-manager.d.ts +125 -0
- package/lib/agent-manager.js +298 -0
- package/lib/agent-manager.js.map +1 -0
- package/lib/agents.d.ts +30 -0
- package/lib/agents.js +468 -0
- package/lib/agents.js.map +1 -0
- package/lib/append-system.d.ts +2 -0
- package/lib/append-system.js +1 -1
- package/lib/append-system.js.map +1 -1
- package/lib/dsh-events.d.ts +12 -0
- package/lib/editor.d.ts +58 -0
- package/lib/editor.js +92 -0
- package/lib/editor.js.map +1 -1
- package/lib/footer.d.ts +8 -0
- package/lib/footer.js +8 -0
- package/lib/footer.js.map +1 -1
- package/lib/hotkeys.d.ts +93 -0
- package/lib/hotkeys.js +305 -0
- package/lib/hotkeys.js.map +1 -0
- package/lib/index.js +342 -38
- package/lib/index.js.map +1 -1
- package/lib/keymap.d.ts +177 -0
- package/lib/keymap.js +189 -0
- package/lib/keymap.js.map +1 -0
- package/lib/live-widgets.d.ts +82 -29
- package/lib/live-widgets.js +222 -92
- package/lib/live-widgets.js.map +1 -1
- package/lib/messages.d.ts +4 -2
- package/lib/messages.js +8 -3
- package/lib/messages.js.map +1 -1
- package/lib/panels.d.ts +177 -0
- package/lib/panels.js +331 -0
- package/lib/panels.js.map +1 -0
- package/lib/provider-catalog.d.ts +31 -8
- package/lib/provider-catalog.js +63 -8
- package/lib/provider-catalog.js.map +1 -1
- package/lib/selectors.d.ts +12 -1
- package/lib/selectors.js +5 -2
- package/lib/selectors.js.map +1 -1
- package/lib/session.d.ts +36 -0
- package/lib/session.js +171 -10
- package/lib/session.js.map +1 -1
- package/lib/settings.d.ts +1 -2
- package/lib/settings.js +29 -2
- package/lib/settings.js.map +1 -1
- package/lib/subagent-policy.d.ts +58 -0
- package/lib/subagent-policy.js +137 -0
- package/lib/subagent-policy.js.map +1 -0
- package/lib/subagent-viewer.d.ts +49 -0
- package/lib/subagent-viewer.js +523 -0
- package/lib/subagent-viewer.js.map +1 -0
- package/lib/theme/index.js +4 -0
- package/lib/theme/index.js.map +1 -1
- package/lib/theme/palette.d.ts +21 -3
- package/lib/theme/palette.js +27 -6
- package/lib/theme/palette.js.map +1 -1
- package/lib/theme-settings.d.ts +39 -4
- package/lib/theme-settings.js +69 -12
- package/lib/theme-settings.js.map +1 -1
- package/lib/tui.d.ts +32 -12
- package/lib/tui.js +109 -36
- package/lib/tui.js.map +1 -1
- package/package.json +3 -4
- package/patches/@earendil-works__pi-tui.patch +206 -2
- package/scripts/dev-install.mjs +87 -0
- package/scripts/link-dsh-closure.mjs +85 -0
package/lib/theme/palette.d.ts
CHANGED
|
@@ -10,13 +10,19 @@
|
|
|
10
10
|
* fills are solid approximations of alpha tints blended over `canvas`
|
|
11
11
|
* (the terminal can't carry alpha) — same values as `blend()` below.
|
|
12
12
|
*
|
|
13
|
-
* The TUI
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* The TUI paints every rendered row with `canvas` (patched pi-tui
|
|
14
|
+
* `setCanvasBackground`, see src/tui.ts) — the app owns its background, so
|
|
15
|
+
* a theme switch recolors the whole screen and the terminal/multiplexer
|
|
16
|
+
* background never shows through. `canvasSubtle` stays the visible raised
|
|
17
|
+
* surface on top of it. DSH_TUI_TRANSPARENT=1 reverts to the old
|
|
18
|
+
* see-through canvas, where `canvas` is only the semantic base for the dark
|
|
19
|
+
* muted blends and contrast design.
|
|
16
20
|
*/
|
|
17
21
|
export interface Palette {
|
|
18
22
|
/** Theme id, used by the theme switcher. */
|
|
19
23
|
readonly name: string;
|
|
24
|
+
/** True for dark themes — components pick bright/light color variants on it. */
|
|
25
|
+
readonly dark: boolean;
|
|
20
26
|
/** Main background (chat canvas). */
|
|
21
27
|
readonly canvas: string;
|
|
22
28
|
/** Slightly offset surface (message bubbles, code blocks, tool cards). */
|
|
@@ -54,3 +60,15 @@ export declare const githubDark: Palette;
|
|
|
54
60
|
* this, so this only inspects terminal environment signals.
|
|
55
61
|
*/
|
|
56
62
|
export declare function detectDarkPalette(env?: NodeJS.ProcessEnv): boolean;
|
|
63
|
+
/** RGB triple as reported by pi-tui's OSC 11 background query. */
|
|
64
|
+
export interface Rgb {
|
|
65
|
+
r: number;
|
|
66
|
+
g: number;
|
|
67
|
+
b: number;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* True when an OSC 11 background color reads as a light terminal (the same
|
|
71
|
+
* 0.5 luminance threshold pi uses for its terminal detection). Structural
|
|
72
|
+
* match for pi-tui's `RgbColor`, so the query result passes through as-is.
|
|
73
|
+
*/
|
|
74
|
+
export declare function rgbIsLight(rgb: Rgb): boolean;
|
package/lib/theme/palette.js
CHANGED
|
@@ -10,16 +10,20 @@
|
|
|
10
10
|
* fills are solid approximations of alpha tints blended over `canvas`
|
|
11
11
|
* (the terminal can't carry alpha) — same values as `blend()` below.
|
|
12
12
|
*
|
|
13
|
-
* The TUI
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* The TUI paints every rendered row with `canvas` (patched pi-tui
|
|
14
|
+
* `setCanvasBackground`, see src/tui.ts) — the app owns its background, so
|
|
15
|
+
* a theme switch recolors the whole screen and the terminal/multiplexer
|
|
16
|
+
* background never shows through. `canvasSubtle` stays the visible raised
|
|
17
|
+
* surface on top of it. DSH_TUI_TRANSPARENT=1 reverts to the old
|
|
18
|
+
* see-through canvas, where `canvas` is only the semantic base for the dark
|
|
19
|
+
* muted blends and contrast design.
|
|
16
20
|
*/
|
|
17
21
|
export const githubLight = {
|
|
18
22
|
name: 'github-light',
|
|
23
|
+
dark: false,
|
|
19
24
|
// Main canvas: near-white with a faint cool-green cast (not the harsh
|
|
20
|
-
// pure #fff); paper feel.
|
|
21
|
-
//
|
|
22
|
-
// dark muted blends and contrast design.
|
|
25
|
+
// pure #fff); paper feel. Painted on every rendered row (see the module
|
|
26
|
+
// header) — the whole screen carries it.
|
|
23
27
|
canvas: '#fcfdfc',
|
|
24
28
|
// Raised surface (message bubbles, think/tool panels, overlays, code
|
|
25
29
|
// blocks): one step darker than the canvas, clear gray-green.
|
|
@@ -83,6 +87,7 @@ function blend(base, over, alpha) {
|
|
|
83
87
|
}
|
|
84
88
|
export const githubDark = {
|
|
85
89
|
name: 'github-dark',
|
|
90
|
+
dark: true,
|
|
86
91
|
// Main canvas: deep gray-blue (the GitHub dark family).
|
|
87
92
|
canvas: '#0d1117',
|
|
88
93
|
// Raised surface (bubbles, panels, overlays): one step lighter gray-blue.
|
|
@@ -151,4 +156,20 @@ export function detectDarkPalette(env = process.env) {
|
|
|
151
156
|
// Default to dark — GitHub's terminal default and the safer contrast choice.
|
|
152
157
|
return true;
|
|
153
158
|
}
|
|
159
|
+
/** WCAG relative luminance of an RGB triple (0 = black, 1 = white). */
|
|
160
|
+
function rgbLuminance({ r, g, b }) {
|
|
161
|
+
const toLinear = (channel) => {
|
|
162
|
+
const value = channel / 255;
|
|
163
|
+
return value <= 0.03928 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
|
|
164
|
+
};
|
|
165
|
+
return 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* True when an OSC 11 background color reads as a light terminal (the same
|
|
169
|
+
* 0.5 luminance threshold pi uses for its terminal detection). Structural
|
|
170
|
+
* match for pi-tui's `RgbColor`, so the query result passes through as-is.
|
|
171
|
+
*/
|
|
172
|
+
export function rgbIsLight(rgb) {
|
|
173
|
+
return rgbLuminance(rgb) >= 0.5;
|
|
174
|
+
}
|
|
154
175
|
//# sourceMappingURL=palette.js.map
|
package/lib/theme/palette.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"palette.js","sourceRoot":"","sources":["../../src/theme/palette.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"palette.js","sourceRoot":"","sources":["../../src/theme/palette.ts"],"names":[],"mappings":"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"}
|
package/lib/theme-settings.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Theme settings: persists the user's theme preference
|
|
3
|
-
*
|
|
4
|
-
* /settings browser and the /theme
|
|
5
|
-
*
|
|
2
|
+
* Theme settings: persists the user's theme preference, the think/tool panel
|
|
3
|
+
* height, and the subagent concurrency/rounds limits under the `dsh-tui`
|
|
4
|
+
* settings namespace, surfaced by the /settings browser and the /theme
|
|
5
|
+
* command. The theme and panel-height preferences are read once at TUI
|
|
6
|
+
* startup (`readThemePreference` / `readPanelHeightPreference`); the subagent
|
|
7
|
+
* limits are read live at every policy decision (`readSubagentLimits`). The
|
|
6
8
|
* namespace is marked `applies: 'live'`: a committed change (the /theme
|
|
7
9
|
* picker, the /settings browser, an external edit) is pushed through the
|
|
8
10
|
* watch hook, so the running TUI repaints without a restart.
|
|
@@ -13,6 +15,19 @@ import { type PanelHeight } from './messages.ts';
|
|
|
13
15
|
import type { ThemePreference } from './theme/index.ts';
|
|
14
16
|
/** Settings namespace carrying the persisted dsh-tui preferences. */
|
|
15
17
|
export declare const THEME_SETTINGS_NAMESPACE: SettingsNamespace;
|
|
18
|
+
/** Subagent concurrency/rounds knobs read by the subagent policy. */
|
|
19
|
+
export interface SubagentLimits {
|
|
20
|
+
/** Concurrent live children allowed; 0 lifts the cap (the guard stays off). */
|
|
21
|
+
maxAgents: number;
|
|
22
|
+
/** Completed turns per child before a summary request is injected; 0 disables. */
|
|
23
|
+
maxRounds: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Default subagent limits, applied whenever the settings service, namespace,
|
|
27
|
+
* or a field cannot be read. 4 concurrent children and 50 rounds per child are
|
|
28
|
+
* the documented out-of-the-box behavior.
|
|
29
|
+
*/
|
|
30
|
+
export declare const DEFAULT_SUBAGENT_LIMITS: SubagentLimits;
|
|
16
31
|
/**
|
|
17
32
|
* Register the `dsh-tui` settings namespace with the settings provider.
|
|
18
33
|
*
|
|
@@ -66,3 +81,23 @@ export declare function currentThemePreference(ctx: Context): ThemePreference;
|
|
|
66
81
|
* @returns undefined on success, the failure message otherwise.
|
|
67
82
|
*/
|
|
68
83
|
export declare function writeThemePreference(ctx: Context, pref: ThemePreference): Promise<string | undefined>;
|
|
84
|
+
/**
|
|
85
|
+
* Read the currently resolved subagent limits, synchronously. Unlike the
|
|
86
|
+
* startup-snapshot readers, this does not wait for the namespace registration
|
|
87
|
+
* — it describes whatever the settings service exposes right now, so every
|
|
88
|
+
* policy decision (the guard at each spawn, `onTurnCount` at each child turn)
|
|
89
|
+
* reflects the latest committed value without a watcher. Missing settings
|
|
90
|
+
* service or namespace, or a non-integer/negative field, degrades to the
|
|
91
|
+
* defaults — a settings-less deployment keeps the documented caps.
|
|
92
|
+
*/
|
|
93
|
+
export declare function readSubagentLimits(ctx: Context): SubagentLimits;
|
|
94
|
+
/**
|
|
95
|
+
* Persist one subagent limit (maxAgents or maxRounds) to the `dsh-tui`
|
|
96
|
+
* settings namespace. The namespace is `applies: 'live'`, so the commit
|
|
97
|
+
* hot-applies without a restart — the policy reads `readSubagentLimits` at
|
|
98
|
+
* the next decision point. Never throws: a deployment without the settings
|
|
99
|
+
* provider, or an unregistered namespace, surfaces a failure message for the
|
|
100
|
+
* caller.
|
|
101
|
+
* @returns undefined on success, the failure message otherwise.
|
|
102
|
+
*/
|
|
103
|
+
export declare function writeSubagentLimit(ctx: Context, key: 'maxAgents' | 'maxRounds', value: number): Promise<string | undefined>;
|
package/lib/theme-settings.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Theme settings: persists the user's theme preference
|
|
3
|
-
*
|
|
4
|
-
* /settings browser and the /theme
|
|
5
|
-
*
|
|
2
|
+
* Theme settings: persists the user's theme preference, the think/tool panel
|
|
3
|
+
* height, and the subagent concurrency/rounds limits under the `dsh-tui`
|
|
4
|
+
* settings namespace, surfaced by the /settings browser and the /theme
|
|
5
|
+
* command. The theme and panel-height preferences are read once at TUI
|
|
6
|
+
* startup (`readThemePreference` / `readPanelHeightPreference`); the subagent
|
|
7
|
+
* limits are read live at every policy decision (`readSubagentLimits`). The
|
|
6
8
|
* namespace is marked `applies: 'live'`: a committed change (the /theme
|
|
7
9
|
* picker, the /settings browser, an external edit) is pushed through the
|
|
8
10
|
* watch hook, so the running TUI repaints without a restart.
|
|
@@ -12,6 +14,12 @@ import z from '@deepseek-ai/schemastery';
|
|
|
12
14
|
import { DEFAULT_PANEL_HEIGHT } from "./messages.js";
|
|
13
15
|
/** Settings namespace carrying the persisted dsh-tui preferences. */
|
|
14
16
|
export const THEME_SETTINGS_NAMESPACE = settingsNamespace('dsh-tui');
|
|
17
|
+
/**
|
|
18
|
+
* Default subagent limits, applied whenever the settings service, namespace,
|
|
19
|
+
* or a field cannot be read. 4 concurrent children and 50 rounds per child are
|
|
20
|
+
* the documented out-of-the-box behavior.
|
|
21
|
+
*/
|
|
22
|
+
export const DEFAULT_SUBAGENT_LIMITS = Object.freeze({ maxAgents: 4, maxRounds: 50 });
|
|
15
23
|
/** Schema of the `dsh-tui` settings section. */
|
|
16
24
|
const THEME_SETTINGS_SCHEMA = z.object({
|
|
17
25
|
theme: z
|
|
@@ -24,11 +32,23 @@ const THEME_SETTINGS_SCHEMA = z.object({
|
|
|
24
32
|
.description("Think/tool panel height in displayed rows ('5'/'7'/'10' — header + content lines; "
|
|
25
33
|
+ "box borders add 2 more) or 'all' to print the full content — a streaming "
|
|
26
34
|
+ 'reasoning panel shows a 200-line live tail and tool results cap at 2000 lines'),
|
|
35
|
+
// `z.natural()` is schemastery's constraint for a non-negative integer
|
|
36
|
+
// (the `z.number().int().min(0)` intent — no `.int()` chain exists here).
|
|
37
|
+
maxAgents: z
|
|
38
|
+
.natural()
|
|
39
|
+
.default(DEFAULT_SUBAGENT_LIMITS.maxAgents)
|
|
40
|
+
.description('Max concurrently running subagents (0 = unlimited)'),
|
|
41
|
+
maxRounds: z
|
|
42
|
+
.natural()
|
|
43
|
+
.default(DEFAULT_SUBAGENT_LIMITS.maxRounds)
|
|
44
|
+
.description('Max rounds per subagent before the TUI sends a summary request (0 = unlimited)'),
|
|
27
45
|
});
|
|
28
46
|
/** Composition entry below the user layer: fall back to the defaults. */
|
|
29
47
|
const THEME_SETTINGS_ENTRY = {
|
|
30
48
|
theme: 'auto',
|
|
31
49
|
panelHeight: DEFAULT_PANEL_HEIGHT,
|
|
50
|
+
maxAgents: DEFAULT_SUBAGENT_LIMITS.maxAgents,
|
|
51
|
+
maxRounds: DEFAULT_SUBAGENT_LIMITS.maxRounds,
|
|
32
52
|
};
|
|
33
53
|
/**
|
|
34
54
|
* In-flight namespace registration. The registration rides the settings
|
|
@@ -178,14 +198,14 @@ export function currentThemePreference(ctx) {
|
|
|
178
198
|
return 'auto';
|
|
179
199
|
}
|
|
180
200
|
/**
|
|
181
|
-
* Persist one `dsh-tui` preference (theme or
|
|
182
|
-
* namespace. The namespace is `applies: 'live'`, so the
|
|
183
|
-
* through the registration's watch hook) hot-applies the
|
|
184
|
-
* running TUI. Best-effort: a deployment without a settings
|
|
185
|
-
* the failure; a failed write returns its error message for
|
|
186
|
-
* surface. A concurrent writer moving the namespace rejects
|
|
187
|
-
* `SettingsConflictError` — retried once against a fresh revision; a
|
|
188
|
-
* conflict surfaces a friendly message instead of the raw error.
|
|
201
|
+
* Persist one `dsh-tui` preference (theme, panelHeight, or a subagent limit)
|
|
202
|
+
* to the settings namespace. The namespace is `applies: 'live'`, so the
|
|
203
|
+
* commit (observed through the registration's watch hook) hot-applies the
|
|
204
|
+
* change to the running TUI. Best-effort: a deployment without a settings
|
|
205
|
+
* provider reports the failure; a failed write returns its error message for
|
|
206
|
+
* the caller to surface. A concurrent writer moving the namespace rejects
|
|
207
|
+
* with `SettingsConflictError` — retried once against a fresh revision; a
|
|
208
|
+
* second conflict surfaces a friendly message instead of the raw error.
|
|
189
209
|
* @returns undefined on success, the failure message otherwise.
|
|
190
210
|
*/
|
|
191
211
|
async function writeDshTuiPreference(ctx, key, value) {
|
|
@@ -220,4 +240,41 @@ async function writeDshTuiPreference(ctx, key, value) {
|
|
|
220
240
|
export async function writeThemePreference(ctx, pref) {
|
|
221
241
|
return writeDshTuiPreference(ctx, 'theme', pref);
|
|
222
242
|
}
|
|
243
|
+
/**
|
|
244
|
+
* Read the currently resolved subagent limits, synchronously. Unlike the
|
|
245
|
+
* startup-snapshot readers, this does not wait for the namespace registration
|
|
246
|
+
* — it describes whatever the settings service exposes right now, so every
|
|
247
|
+
* policy decision (the guard at each spawn, `onTurnCount` at each child turn)
|
|
248
|
+
* reflects the latest committed value without a watcher. Missing settings
|
|
249
|
+
* service or namespace, or a non-integer/negative field, degrades to the
|
|
250
|
+
* defaults — a settings-less deployment keeps the documented caps.
|
|
251
|
+
*/
|
|
252
|
+
export function readSubagentLimits(ctx) {
|
|
253
|
+
const settings = ctx.get('settings');
|
|
254
|
+
if (settings === undefined)
|
|
255
|
+
return { ...DEFAULT_SUBAGENT_LIMITS };
|
|
256
|
+
// The descriptor's `value` is the whole resolved section
|
|
257
|
+
// (`{ theme: ..., panelHeight: ..., maxAgents: ..., maxRounds: ... }`) — narrow
|
|
258
|
+
// the unknown to the two observed numeric fields.
|
|
259
|
+
const section = settings
|
|
260
|
+
.describe()
|
|
261
|
+
.find((descriptor) => descriptor.ns === THEME_SETTINGS_NAMESPACE)?.value;
|
|
262
|
+
const natural = (value, fallback) => typeof value === 'number' && Number.isInteger(value) && value >= 0 ? value : fallback;
|
|
263
|
+
return {
|
|
264
|
+
maxAgents: natural(section?.maxAgents, DEFAULT_SUBAGENT_LIMITS.maxAgents),
|
|
265
|
+
maxRounds: natural(section?.maxRounds, DEFAULT_SUBAGENT_LIMITS.maxRounds),
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Persist one subagent limit (maxAgents or maxRounds) to the `dsh-tui`
|
|
270
|
+
* settings namespace. The namespace is `applies: 'live'`, so the commit
|
|
271
|
+
* hot-applies without a restart — the policy reads `readSubagentLimits` at
|
|
272
|
+
* the next decision point. Never throws: a deployment without the settings
|
|
273
|
+
* provider, or an unregistered namespace, surfaces a failure message for the
|
|
274
|
+
* caller.
|
|
275
|
+
* @returns undefined on success, the failure message otherwise.
|
|
276
|
+
*/
|
|
277
|
+
export async function writeSubagentLimit(ctx, key, value) {
|
|
278
|
+
return writeDshTuiPreference(ctx, key, value);
|
|
279
|
+
}
|
|
223
280
|
//# sourceMappingURL=theme-settings.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"theme-settings.js","sourceRoot":"","sources":["../src/theme-settings.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"theme-settings.js","sourceRoot":"","sources":["../src/theme-settings.ts"],"names":[],"mappings":"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"}
|
package/lib/tui.d.ts
CHANGED
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
* │ scrollable transcript (ScrollView) │ basis 0 / grow 1 — fills the rest
|
|
8
8
|
* ├──────────────────────────────────────┤
|
|
9
9
|
* │ statusContainer │ ┐
|
|
10
|
-
* │ widgetsContainer (Todos
|
|
10
|
+
* │ widgetsContainer (Todos) │ │ dock — basis auto / grow 0,
|
|
11
11
|
* │ editor │ │ sized to its content
|
|
12
|
+
* │ lastRequestContainer │ │ ← also hosts the merged
|
|
13
|
+
* │ (last-request + activity lines) │ │ running-agent activity
|
|
12
14
|
* │ footerContainer │ ┘
|
|
13
15
|
* └──────────────────────────────────────┘
|
|
14
16
|
*
|
|
@@ -18,12 +20,24 @@
|
|
|
18
20
|
*/
|
|
19
21
|
import { Container, type AutocompleteProvider, type TUI } from '@earendil-works/pi-tui';
|
|
20
22
|
import { CwdBorderEditor } from './editor.ts';
|
|
23
|
+
import { type KeyAction, type KeyBindings } from './keymap.ts';
|
|
21
24
|
import { type ThemePreference, type TuiTheme } from './theme/index.ts';
|
|
22
25
|
export interface StartTuiOptions {
|
|
23
26
|
/** Submit handler for the editor. Defaults to a local echo (smoke-test mode). */
|
|
24
27
|
onSubmit?: (text: string) => void;
|
|
25
|
-
/**
|
|
26
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Executes an app-level key action (see keymap.ts for the pi-aligned
|
|
30
|
+
* interrupt chain: double-Esc stop, windowed Ctrl+C, empty-editor Ctrl+D
|
|
31
|
+
* quit). Absent (smoke-test mode): Ctrl+C family and Ctrl+D quit fall back
|
|
32
|
+
* to a plain exit(130); interrupt-arm/arm-stop/double are no-ops.
|
|
33
|
+
*/
|
|
34
|
+
onKeyAction?: (action: KeyAction) => void;
|
|
35
|
+
/** Whether the agent is mid-turn; feeds the Esc/Ctrl+C decision. Defaults to false. */
|
|
36
|
+
isRunning?: () => boolean;
|
|
37
|
+
/** Live running-subagent count; feeds the Ctrl+G viewer decision. Defaults to 0. */
|
|
38
|
+
getRunningAgents?: () => number;
|
|
39
|
+
/** User keybindings overrides (`~/.dsh/keybindings.json`); partial merge. */
|
|
40
|
+
keyBindings?: Partial<KeyBindings>;
|
|
27
41
|
/** Persisted theme preference; 'auto' falls back to terminal detection. */
|
|
28
42
|
themePreference?: ThemePreference;
|
|
29
43
|
}
|
|
@@ -34,8 +48,13 @@ export interface TuiHandle {
|
|
|
34
48
|
readonly tui: TUI;
|
|
35
49
|
/** The scrollable transcript document container. */
|
|
36
50
|
readonly transcript: Container;
|
|
37
|
-
/** Fixed slot pinned above the chat window — live Todos
|
|
51
|
+
/** Fixed slot pinned above the chat window — the live Todos widget. */
|
|
38
52
|
readonly widgets: Container;
|
|
53
|
+
/**
|
|
54
|
+
* Fixed dock slot below the editor — the last-request line + merged
|
|
55
|
+
* running-agent activity (LiveWidgets owns both).
|
|
56
|
+
*/
|
|
57
|
+
readonly lastRequest: Container;
|
|
39
58
|
/** Fixed dock slot rendered between transcript and editor. */
|
|
40
59
|
readonly status: Container;
|
|
41
60
|
/** The input editor. */
|
|
@@ -50,12 +69,13 @@ export interface TuiHandle {
|
|
|
50
69
|
*/
|
|
51
70
|
readonly theme: TuiTheme;
|
|
52
71
|
/**
|
|
53
|
-
* Hot-swap the theme bundle: repaints the
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
72
|
+
* Hot-swap the theme bundle: repaints the canvas background, the editor
|
|
73
|
+
* border and the placeholder, and updates every subsequent `theme` read.
|
|
74
|
+
* Transcript repainting is the TranscriptRenderer's job (`setTheme`), and
|
|
75
|
+
* the last-request / running-agent lines are the LiveWidgets' job
|
|
76
|
+
* (`setTheme`) — both owned by the caller; call them before `applyTheme`
|
|
77
|
+
* so the one throttled render frame paints everything at once. No-op when
|
|
78
|
+
* the bundle is unchanged (themes are module singletons).
|
|
59
79
|
*/
|
|
60
80
|
applyTheme(theme: TuiTheme): void;
|
|
61
81
|
/** Autocomplete provider for the editor; re-applied across editor rebuilds. */
|
|
@@ -64,7 +84,7 @@ export interface TuiHandle {
|
|
|
64
84
|
setEditorBranchProvider(provider: () => string | undefined): void;
|
|
65
85
|
/** Live permission-preset display-name source for the editor; re-applied across editor rebuilds. */
|
|
66
86
|
setEditorPermissionProvider(provider: () => string | undefined): void;
|
|
67
|
-
/**
|
|
68
|
-
|
|
87
|
+
/** Swap the user keybindings live (`/hotkeys` writes → apply immediately). */
|
|
88
|
+
setKeyBindings(bindings: Partial<KeyBindings>): void;
|
|
69
89
|
}
|
|
70
90
|
export declare function startTui(options?: StartTuiOptions): TuiHandle;
|
package/lib/tui.js
CHANGED
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
* │ scrollable transcript (ScrollView) │ basis 0 / grow 1 — fills the rest
|
|
8
8
|
* ├──────────────────────────────────────┤
|
|
9
9
|
* │ statusContainer │ ┐
|
|
10
|
-
* │ widgetsContainer (Todos
|
|
10
|
+
* │ widgetsContainer (Todos) │ │ dock — basis auto / grow 0,
|
|
11
11
|
* │ editor │ │ sized to its content
|
|
12
|
+
* │ lastRequestContainer │ │ ← also hosts the merged
|
|
13
|
+
* │ (last-request + activity lines) │ │ running-agent activity
|
|
12
14
|
* │ footerContainer │ ┘
|
|
13
15
|
* └──────────────────────────────────────┘
|
|
14
16
|
*
|
|
@@ -16,20 +18,31 @@
|
|
|
16
18
|
* session log during render; all session-derived state (messages, usage,
|
|
17
19
|
* counts) is maintained incrementally by event listeners and read O(1) here.
|
|
18
20
|
*/
|
|
19
|
-
import { Container,
|
|
21
|
+
import { Container, ProcessTerminal, ScrollView, Spacer, Text, TuiAltScreen, VStack, } from '@earendil-works/pi-tui';
|
|
20
22
|
import { CwdBorderEditor } from "./editor.js";
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
+
import { resolveKeyAction } from "./keymap.js";
|
|
24
|
+
import { ansiBg, ansiFg, RESET, resolveTheme } from "./theme/index.js";
|
|
23
25
|
export function startTui(options = {}) {
|
|
24
26
|
const terminal = new ProcessTerminal();
|
|
25
27
|
const tui = new TuiAltScreen(terminal, true);
|
|
26
28
|
// Mutable theme ref: `applyTheme` swaps it and every later read (handle
|
|
27
29
|
// getter, baked closures below) observes the new bundle on the next call.
|
|
28
30
|
let themeRef = resolveTheme(process.env, options.themePreference ?? 'auto');
|
|
31
|
+
// App-owned canvas: every rendered row is painted with the palette's
|
|
32
|
+
// canvas background (patched pi-tui — see setCanvasBackground), so a theme
|
|
33
|
+
// switch recolors the WHOLE screen, not only the surfaces that paint their
|
|
34
|
+
// own bg. Without it the terminal default background shows through the
|
|
35
|
+
// unpainted rows and "freezes" on switch — most visible inside cmux/gostty,
|
|
36
|
+
// where the pane background belongs to the multiplexer. DSH_TUI_TRANSPARENT=1
|
|
37
|
+
// opts back into the old see-through canvas for users who want their
|
|
38
|
+
// terminal theme to stay visible.
|
|
39
|
+
const paintCanvas = process.env.DSH_TUI_TRANSPARENT !== '1';
|
|
40
|
+
if (paintCanvas)
|
|
41
|
+
tui.setCanvasBackground(ansiBg(themeRef.palette.canvas));
|
|
29
42
|
// ------------------------------------------------------------- component tree --
|
|
30
|
-
// Live Todos
|
|
31
|
-
//
|
|
32
|
-
// (bordered-panel) content while the model has todos
|
|
43
|
+
// Live Todos widget, pinned ABOVE the chat input: a plain Container with
|
|
44
|
+
// auto height — it renders zero rows while empty and grows to its
|
|
45
|
+
// (bordered-panel) content while the model has todos.
|
|
33
46
|
const widgets = new Container();
|
|
34
47
|
const transcript = new Container();
|
|
35
48
|
const transcriptView = new ScrollView(transcript, {
|
|
@@ -44,14 +57,19 @@ export function startTui(options = {}) {
|
|
|
44
57
|
let editor = new CwdBorderEditor(tui, themeRef.editor, process.cwd(), {
|
|
45
58
|
infoColor: text => ansiFg(themeRef.palette.fgMuted) + text + RESET,
|
|
46
59
|
});
|
|
60
|
+
// User keybindings overrides — mutable so `/hotkeys` can live-apply a
|
|
61
|
+
// write without restarting the TUI (see `setKeyBindings`).
|
|
62
|
+
let keyBindingsRef = options.keyBindings;
|
|
47
63
|
// External wiring that must survive editor rebuilds.
|
|
48
64
|
let autocompleteProvider;
|
|
49
65
|
let branchProvider;
|
|
50
66
|
let permissionProvider;
|
|
67
|
+
// Last-request + merged running-agent activity, pinned BELOW the editor: a
|
|
68
|
+
// plain Container with auto height; LiveWidgets owns the ` ↳ ` line and the
|
|
69
|
+
// compact agent lines it hosts. It collapses to zero rows when both the
|
|
70
|
+
// last-request line is cleared and no subagent runs.
|
|
51
71
|
const lastRequest = new Container();
|
|
52
72
|
const footer = new Container();
|
|
53
|
-
let lastRequestText;
|
|
54
|
-
let lastRequestDisplay;
|
|
55
73
|
const dock = new VStack([
|
|
56
74
|
{ component: status, shrink: 1, minSize: 0 },
|
|
57
75
|
{ component: widgets, shrink: 1, minSize: 0 },
|
|
@@ -71,6 +89,10 @@ export function startTui(options = {}) {
|
|
|
71
89
|
editor.onSubmit = (text) => {
|
|
72
90
|
if (text.trim() === '')
|
|
73
91
|
return;
|
|
92
|
+
// Record the submitted message for Up/Down browse before dispatching —
|
|
93
|
+
// covers both the real path and the smoke-test fallback echo path. The
|
|
94
|
+
// editor's own `submitValue()` never calls `addToHistory` in pi-tui 0.84.2.
|
|
95
|
+
editor.addToHistory(text);
|
|
74
96
|
if (options.onSubmit !== undefined) {
|
|
75
97
|
options.onSubmit(text);
|
|
76
98
|
return;
|
|
@@ -91,12 +113,23 @@ export function startTui(options = {}) {
|
|
|
91
113
|
*/
|
|
92
114
|
function rebuildEditor() {
|
|
93
115
|
const text = editor.getText();
|
|
116
|
+
// Preserve the Up/Down browse history AND the mid-browse cursor/draft
|
|
117
|
+
// across a theme-swap rebuild: the rebuild fires asynchronously (settings
|
|
118
|
+
// watch, CSI 997 terminal-follow) — precisely while the user may be sitting
|
|
119
|
+
// mid-browse with a pre-browse draft saved in the base. Copies `history`
|
|
120
|
+
// and `browse` so the swap restores exactly where the user was.
|
|
121
|
+
const history = editor.getHistory();
|
|
122
|
+
const browse = editor.getBrowseState();
|
|
94
123
|
const hadFocus = editor.focused;
|
|
95
124
|
const next = new CwdBorderEditor(tui, themeRef.editor, process.cwd(), {
|
|
96
125
|
infoColor: text => ansiFg(themeRef.palette.fgMuted) + text + RESET,
|
|
97
126
|
});
|
|
98
127
|
next.setText(text);
|
|
99
128
|
next.onSubmit = editor.onSubmit;
|
|
129
|
+
// `addToHistory` unshifts, so reseed oldest→newest to land in the same
|
|
130
|
+
// order on the new instance; then restore the browse cursor/draft.
|
|
131
|
+
next.reseedHistory(history);
|
|
132
|
+
next.restoreBrowseState(browse);
|
|
100
133
|
if (autocompleteProvider !== undefined)
|
|
101
134
|
next.setAutocompleteProvider(autocompleteProvider);
|
|
102
135
|
if (branchProvider !== undefined)
|
|
@@ -121,6 +154,7 @@ export function startTui(options = {}) {
|
|
|
121
154
|
tui,
|
|
122
155
|
transcript,
|
|
123
156
|
widgets,
|
|
157
|
+
lastRequest,
|
|
124
158
|
status,
|
|
125
159
|
get editor() {
|
|
126
160
|
return editor;
|
|
@@ -136,10 +170,9 @@ export function startTui(options = {}) {
|
|
|
136
170
|
if (theme === themeRef)
|
|
137
171
|
return;
|
|
138
172
|
themeRef = theme;
|
|
173
|
+
if (paintCanvas)
|
|
174
|
+
tui.setCanvasBackground(ansiBg(theme.palette.canvas));
|
|
139
175
|
rebuildEditor();
|
|
140
|
-
if (lastRequestDisplay !== undefined && lastRequestText !== undefined) {
|
|
141
|
-
lastRequestText.setText(ansiFg(themeRef.palette.fgMuted) + ` ↳ ${lastRequestDisplay}` + RESET);
|
|
142
|
-
}
|
|
143
176
|
tui.requestRender();
|
|
144
177
|
},
|
|
145
178
|
setEditorAutocompleteProvider(provider) {
|
|
@@ -154,40 +187,80 @@ export function startTui(options = {}) {
|
|
|
154
187
|
permissionProvider = provider;
|
|
155
188
|
editor.setPermissionProvider(provider);
|
|
156
189
|
},
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
if (lastRequestText !== undefined) {
|
|
160
|
-
lastRequest.removeChild(lastRequestText);
|
|
161
|
-
lastRequestText = undefined;
|
|
162
|
-
lastRequestDisplay = undefined;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
else {
|
|
166
|
-
const display = clipToWidth(text, 200);
|
|
167
|
-
if (lastRequestText === undefined) {
|
|
168
|
-
lastRequestText = new Text('', 1, 0);
|
|
169
|
-
lastRequest.addChild(lastRequestText);
|
|
170
|
-
}
|
|
171
|
-
lastRequestDisplay = display;
|
|
172
|
-
lastRequestText.setText(ansiFg(themeRef.palette.fgMuted) + ` ↳ ${display}` + RESET);
|
|
173
|
-
}
|
|
174
|
-
tui.requestRender();
|
|
190
|
+
setKeyBindings(bindings) {
|
|
191
|
+
keyBindingsRef = bindings;
|
|
175
192
|
},
|
|
176
193
|
};
|
|
177
194
|
// ------------------------------------------------------------- input & focus --
|
|
178
195
|
tui.setFocus(editor);
|
|
196
|
+
/**
|
|
197
|
+
* App-level key dispatch — the pi interrupt chain (see keymap.ts):
|
|
198
|
+
* popup/overlay first (a popup closes itself and never stops the running
|
|
199
|
+
* task), then the editor's autocomplete, then the running agent — where a
|
|
200
|
+
* DOUBLE Esc within `DOUBLE_PRESS_MS` stops the whole task (parent +
|
|
201
|
+
* subagents; the first press only arms the stop window) — and finally the
|
|
202
|
+
* empty-editor double-press windows. Every decision is delegated to the
|
|
203
|
+
* pure `resolveKeyAction`; the listener only composes live state, advances
|
|
204
|
+
* the double-press timestamps, and reports whether the key was consumed.
|
|
205
|
+
* `matchesKey('escape')` is true for the lone ESC byte `\x1b` — and,
|
|
206
|
+
* because legacy terminal Ctrl+[ sends the same byte, for Ctrl+[ too
|
|
207
|
+
* (pi-tui keys.js normalizes both to `escape`).
|
|
208
|
+
*/
|
|
209
|
+
let lastEscPress = 0;
|
|
210
|
+
let lastRunningEscPress = 0;
|
|
211
|
+
let lastCtrlCPress = 0;
|
|
212
|
+
const isRunning = options.isRunning ?? (() => false);
|
|
213
|
+
const getRunningAgents = options.getRunningAgents ?? (() => 0);
|
|
179
214
|
tui.addInputListener((data) => {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
215
|
+
const action = resolveKeyAction(data, {
|
|
216
|
+
running: isRunning(),
|
|
217
|
+
overlayOpen: tui.hasOverlay(),
|
|
218
|
+
editorHasText: editor.getText() !== '',
|
|
219
|
+
autocompleteOpen: editor.isShowingAutocomplete(),
|
|
220
|
+
runningAgents: getRunningAgents(),
|
|
221
|
+
lastEscPress,
|
|
222
|
+
lastRunningEscPress,
|
|
223
|
+
lastCtrlCPress,
|
|
224
|
+
}, Date.now(), keyBindingsRef);
|
|
225
|
+
// Advance the double-press windows for the keys this chain owns. Two
|
|
226
|
+
// separate Esc windows keep their own clocks: the running-stop arm
|
|
227
|
+
// (`interrupt-arm-stop` → `interrupt-cancel`) and the idle double-Esc
|
|
228
|
+
// (`interrupt-arm` → `interrupt-double`), so an Esc that armed a stop
|
|
229
|
+
// never pops /session once the turn settles inside the window, and vice
|
|
230
|
+
// versa. A fired stop re-arms the running clock at the cancel timestamp
|
|
231
|
+
// (Date.now() on `interrupt-cancel`): lashes of held-key Esc after the
|
|
232
|
+
// cancel land <80ms later and resolve as swallowable `key-repeat`
|
|
233
|
+
// instead of re-noticing another arm. `key-repeat` never advances a
|
|
234
|
+
// window — a held key must not arm or complete a double press
|
|
235
|
+
// (see keymap.ts).
|
|
236
|
+
if (action.kind === 'interrupt-arm' || action.kind === 'interrupt-double') {
|
|
237
|
+
lastEscPress = Date.now();
|
|
238
|
+
}
|
|
239
|
+
else if (action.kind === 'interrupt-arm-stop' || action.kind === 'interrupt-cancel') {
|
|
240
|
+
lastRunningEscPress = Date.now();
|
|
241
|
+
}
|
|
242
|
+
else if (action.kind === 'ctrl-c-cancel' || action.kind === 'ctrl-c-clear' || action.kind === 'ctrl-c-quit') {
|
|
243
|
+
lastCtrlCPress = Date.now();
|
|
244
|
+
}
|
|
245
|
+
// Pass-through outcomes (popup, autocomplete, unbound keys) let the
|
|
246
|
+
// focused component handle the key; actionable ones (key-repeat included,
|
|
247
|
+
// so the executor can abort a pending quit confirmation on it) go to the
|
|
248
|
+
// caller.
|
|
249
|
+
if (action.kind !== 'overlay' && action.kind !== 'autocomplete-close' && action.kind !== 'noop') {
|
|
250
|
+
if (options.onKeyAction !== undefined) {
|
|
251
|
+
options.onKeyAction(action);
|
|
183
252
|
}
|
|
184
|
-
else
|
|
253
|
+
else if (action.kind === 'ctrl-c-cancel' || action.kind === 'ctrl-c-clear'
|
|
254
|
+
|| action.kind === 'ctrl-c-quit' || action.kind === 'ctrl-d-quit') {
|
|
255
|
+
// Smoke-test fallback: any Ctrl+C family press, or an empty-editor
|
|
256
|
+
// Ctrl+D, exits — Ctrl+C behaves like the pre-keymap build. (key-repeat
|
|
257
|
+
// deliberately excluded: no terminal is attached in smoke mode, but a
|
|
258
|
+
// repeat must never be an exit path by itself.)
|
|
185
259
|
handle.dispose();
|
|
186
260
|
process.exit(130);
|
|
187
261
|
}
|
|
188
|
-
return { consume: true };
|
|
189
262
|
}
|
|
190
|
-
return undefined;
|
|
263
|
+
return action.consumes ? { consume: true } : undefined;
|
|
191
264
|
});
|
|
192
265
|
tui.setLayoutRoot(root);
|
|
193
266
|
try {
|