@aiwayds/dsh-tui-pi 0.1.1 → 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.
Files changed (59) hide show
  1. package/README.md +175 -41
  2. package/lib/agent-manager.d.ts +125 -0
  3. package/lib/agent-manager.js +298 -0
  4. package/lib/agent-manager.js.map +1 -0
  5. package/lib/agents.d.ts +30 -0
  6. package/lib/agents.js +468 -0
  7. package/lib/agents.js.map +1 -0
  8. package/lib/append-system.d.ts +2 -0
  9. package/lib/append-system.js +1 -1
  10. package/lib/append-system.js.map +1 -1
  11. package/lib/dsh-events.d.ts +12 -0
  12. package/lib/editor.d.ts +58 -0
  13. package/lib/editor.js +92 -0
  14. package/lib/editor.js.map +1 -1
  15. package/lib/footer.d.ts +8 -0
  16. package/lib/footer.js +8 -0
  17. package/lib/footer.js.map +1 -1
  18. package/lib/hotkeys.d.ts +93 -0
  19. package/lib/hotkeys.js +305 -0
  20. package/lib/hotkeys.js.map +1 -0
  21. package/lib/index.js +301 -29
  22. package/lib/index.js.map +1 -1
  23. package/lib/keymap.d.ts +177 -0
  24. package/lib/keymap.js +189 -0
  25. package/lib/keymap.js.map +1 -0
  26. package/lib/live-widgets.d.ts +29 -24
  27. package/lib/live-widgets.js +78 -45
  28. package/lib/live-widgets.js.map +1 -1
  29. package/lib/panels.d.ts +177 -0
  30. package/lib/panels.js +331 -0
  31. package/lib/panels.js.map +1 -0
  32. package/lib/selectors.d.ts +12 -1
  33. package/lib/selectors.js +1 -1
  34. package/lib/selectors.js.map +1 -1
  35. package/lib/session.d.ts +25 -0
  36. package/lib/session.js +137 -10
  37. package/lib/session.js.map +1 -1
  38. package/lib/settings.d.ts +1 -2
  39. package/lib/subagent-policy.d.ts +58 -0
  40. package/lib/subagent-policy.js +137 -0
  41. package/lib/subagent-policy.js.map +1 -0
  42. package/lib/subagent-viewer.d.ts +49 -0
  43. package/lib/subagent-viewer.js +523 -0
  44. package/lib/subagent-viewer.js.map +1 -0
  45. package/lib/theme/index.js +4 -0
  46. package/lib/theme/index.js.map +1 -1
  47. package/lib/theme/palette.d.ts +21 -3
  48. package/lib/theme/palette.js +27 -6
  49. package/lib/theme/palette.js.map +1 -1
  50. package/lib/theme-settings.d.ts +39 -4
  51. package/lib/theme-settings.js +69 -12
  52. package/lib/theme-settings.js.map +1 -1
  53. package/lib/tui.d.ts +23 -9
  54. package/lib/tui.js +99 -8
  55. package/lib/tui.js.map +1 -1
  56. package/package.json +3 -4
  57. package/patches/@earendil-works__pi-tui.patch +206 -2
  58. package/scripts/dev-install.mjs +87 -0
  59. package/scripts/link-dsh-closure.mjs +85 -0
@@ -0,0 +1,177 @@
1
+ /**
2
+ * App-level keybinding decisions — the pi interrupt chain, kept pure.
3
+ *
4
+ * pi (badlogic/pi-mono) routes terminal key presses through an app-level
5
+ * handler before the focused component sees them. This module is the
6
+ * dsh-tui-pi equivalent: `tui.ts` composes the live state and calls
7
+ * `resolveKeyAction`, `index.ts` executes the returned action. Keeping the
8
+ * decision PURE makes the whole chain unit-testable without a terminal.
9
+ *
10
+ * Esc chain (mirrors pi's `app.interrupt`, verified on pi 0.84.2):
11
+ * 1. popup/selector overlay open → let the popup's own Esc handle it —
12
+ * Esc NEVER touches the running task
13
+ * from inside a popup (the subagent
14
+ * viewer, pickers and dialogs close
15
+ * themselves and leave the turn alone)
16
+ * 2. editor autocomplete showing → let the editor close the list only
17
+ * 3. agent mid-turn (running) → 1st press arms the stop window, 2nd
18
+ * press within `DOUBLE_PRESS_MS` cancels
19
+ * the whole task (parent + subagents) —
20
+ * stopping is a deliberate double-press
21
+ * 4. editor non-empty (idle) → NO-OP — pi's anti-misfire core
22
+ * 5. editor empty (idle) → 1st press arms, 2nd press within
23
+ * `DOUBLE_PRESS_MS` triggers the mapped
24
+ * action (pi defaults to /tree; dsh has
25
+ * no /tree, so we map to /session — the
26
+ * closest non-invasive analogue). The
27
+ * running-stop and idle windows own
28
+ * SEPARATE clocks (`lastRunningEscPress`
29
+ * vs `lastEscPress`) so an Esc that armed
30
+ * a stop never pops /session after the
31
+ * turn settles, and vice versa.
32
+ *
33
+ * Ctrl+C chain (dsh convention, windowed): first press cancels a running
34
+ * turn — or clears the editor while idle — and a second press within
35
+ * `DOUBLE_PRESS_MS` quits. This keeps dsh's "first cancels / second quits"
36
+ * while requiring a deliberate double-press before an idle exit. Held-key
37
+ * auto-repeat never counts: repeats inside `MIN_DOUBLE_PRESS_GAP_MS` resolve
38
+ * to `key-repeat` (consumed, inert — and the executor aborts a pending quit
39
+ * confirmation on one, since a repeat betrays a held key; see index.ts).
40
+ *
41
+ * Ctrl+D chain (pi `app.exit`): only quits on an EMPTY editor; with text it
42
+ * is left alone so the pi-tui Editor's existing `tui.editor.deleteCharForward`
43
+ * binding (delete, ctrl+d) deletes a character forward — exactly pi's
44
+ * context distinction.
45
+ *
46
+ * Ctrl+L chain (pi `app.model.select`): opens the model/think picker — the
47
+ * dsh analogue of pi's model selector. Like every app key here it yields to
48
+ * an open overlay first, so a popup that is on screen keeps its own keys.
49
+ *
50
+ * Ctrl+G chain (dsh's subagent viewer): opens the subagent picker only while
51
+ * a subagent runs (`runningAgents > 0`) — idle it falls through untouched.
52
+ * Same overlay-yields-first rule as the other app keys.
53
+ */
54
+ import { type KeyId } from '@earendil-works/pi-tui';
55
+ /** Double-press window (ms) for ctrl+c quit and empty-editor double-Esc. */
56
+ export declare const DOUBLE_PRESS_MS = 500;
57
+ /**
58
+ * Minimum gap (ms) between two presses of the same key for the second to
59
+ * count as a deliberate press rather than terminal auto-repeat. A held key is
60
+ * re-sent every ~30-50ms after the OS repeat delay (183ms-2s) and terminals
61
+ * report no key-up, so gap alone is the discriminator: anything closer than
62
+ * this floor is auto-repeat (see `key-repeat`). Human double-presses are
63
+ * rarely faster than ~100ms; common repeat rates (20-33/s) sit well under it.
64
+ */
65
+ export declare const MIN_DOUBLE_PRESS_GAP_MS = 80;
66
+ /**
67
+ * The key identifiers resolved for each app-level binding. Injectable so a
68
+ * future `~/.dsh/keybindings.json` override can remap the app keys without
69
+ * touching the decision logic (see README "Keyboard shortcuts").
70
+ */
71
+ export interface KeyBindings {
72
+ /** Stop the current task (pi app.interrupt). */
73
+ escape: KeyId;
74
+ /** Cancels the running turn / clears the editor; double-press quits. */
75
+ ctrlC: KeyId;
76
+ /** Quit, but only when the editor is empty (pi app.exit). */
77
+ ctrlD: KeyId;
78
+ /** Open the model picker (pi app.model.select). */
79
+ modelPicker: KeyId;
80
+ /** Open the subagent picker / viewer (running children). */
81
+ subagentViewer: KeyId;
82
+ }
83
+ export declare const DEFAULT_KEYBINDINGS: KeyBindings;
84
+ /** Live snapshot of everything the decision needs — composed by tui.ts. */
85
+ export interface KeyPressState {
86
+ /** Agent mid-turn (`bridge.isRunning()`). */
87
+ running: boolean;
88
+ /** An overlay/popup is open and focused (`tui.hasOverlay()`). */
89
+ overlayOpen: boolean;
90
+ /** Editor buffer is non-empty. */
91
+ editorHasText: boolean;
92
+ /** Editor autocomplete dropdown is currently showing. */
93
+ autocompleteOpen: boolean;
94
+ /** Live (not settled) subagent count; 0 = nothing for ctrl+g to open. */
95
+ runningAgents: number;
96
+ /** Timestamp (ms) of the last handled escape press; 0 = none. */
97
+ lastEscPress: number;
98
+ /** Timestamp (ms) of the last handled escape press that armed the
99
+ * running-stop window (branch 3 above); 0 = none. Own clock: independent
100
+ * of `lastEscPress` so the two Esc windows never bleed into each other. */
101
+ lastRunningEscPress: number;
102
+ /** Timestamp (ms) of the last handled ctrl+c press; 0 = none. */
103
+ lastCtrlCPress: number;
104
+ }
105
+ /**
106
+ * One resolved app-level key action. `consumes` mirrors pi-tui's
107
+ * `TuiInputListenerResult.consume`: true stops the key from reaching the
108
+ * focused component, false lets it through (the popup / the editor handle
109
+ * it — e.g. Esc-close, ctrl+d delete-char, drop of an unbound key).
110
+ */
111
+ export type KeyAction = {
112
+ kind: 'overlay';
113
+ consumes: false;
114
+ } | {
115
+ kind: 'autocomplete-close';
116
+ consumes: false;
117
+ } | {
118
+ kind: 'noop';
119
+ consumes: false;
120
+ } | {
121
+ kind: 'interrupt-arm-stop';
122
+ consumes: true;
123
+ } | {
124
+ kind: 'interrupt-cancel';
125
+ consumes: true;
126
+ } | {
127
+ kind: 'interrupt-arm';
128
+ consumes: true;
129
+ } | {
130
+ kind: 'interrupt-double';
131
+ consumes: true;
132
+ } | {
133
+ kind: 'ctrl-c-cancel';
134
+ consumes: true;
135
+ } | {
136
+ kind: 'ctrl-c-clear';
137
+ consumes: true;
138
+ } | {
139
+ kind: 'ctrl-c-quit';
140
+ consumes: true;
141
+ } | {
142
+ kind: 'ctrl-d-quit';
143
+ consumes: true;
144
+ } | {
145
+ kind: 'model-picker';
146
+ consumes: true;
147
+ } | {
148
+ kind: 'subagent-viewer';
149
+ consumes: true;
150
+ }
151
+ /**
152
+ * Auto-repeat of a windowed key (Ctrl+C / armed Esc) — the key is being
153
+ * held. Consumed so the editor never sees it, but no action fires and the
154
+ * double-press clock does NOT advance (a repeat must never arm or complete
155
+ * a double press). The executor can use the `key` to cancel a pending
156
+ * quit confirmation (a repeat betrays a held key).
157
+ */
158
+ | {
159
+ kind: 'key-repeat';
160
+ key: 'ctrl-c' | 'escape';
161
+ consumes: true;
162
+ };
163
+ /**
164
+ * Resolve one raw terminal input sequence into an app-level action.
165
+ * `data` is the single decoded key sequence `addInputListener` receives
166
+ * (e.g. "\x1b" for Escape, "\x03" for Ctrl+C); see keys.js for the
167
+ * normalization: Escape is `\x1b`, and legacy Ctrl+[ collapses to the same
168
+ * byte, so both are recognized as `escape`.
169
+ *
170
+ * `keys` may be a PARTIAL override — every missing binding falls back to the
171
+ * default. That is the shape a future `~/.dsh/keybindings.json` loader will
172
+ * feed in: remap one key, keep the rest.
173
+ *
174
+ * Any key outside the app bindings yields `noop` (not consumed — the focused
175
+ * component, normally the editor, processes it).
176
+ */
177
+ export declare function resolveKeyAction(data: string, state: KeyPressState, now: number, keys?: Partial<KeyBindings>): KeyAction;
package/lib/keymap.js ADDED
@@ -0,0 +1,189 @@
1
+ /**
2
+ * App-level keybinding decisions — the pi interrupt chain, kept pure.
3
+ *
4
+ * pi (badlogic/pi-mono) routes terminal key presses through an app-level
5
+ * handler before the focused component sees them. This module is the
6
+ * dsh-tui-pi equivalent: `tui.ts` composes the live state and calls
7
+ * `resolveKeyAction`, `index.ts` executes the returned action. Keeping the
8
+ * decision PURE makes the whole chain unit-testable without a terminal.
9
+ *
10
+ * Esc chain (mirrors pi's `app.interrupt`, verified on pi 0.84.2):
11
+ * 1. popup/selector overlay open → let the popup's own Esc handle it —
12
+ * Esc NEVER touches the running task
13
+ * from inside a popup (the subagent
14
+ * viewer, pickers and dialogs close
15
+ * themselves and leave the turn alone)
16
+ * 2. editor autocomplete showing → let the editor close the list only
17
+ * 3. agent mid-turn (running) → 1st press arms the stop window, 2nd
18
+ * press within `DOUBLE_PRESS_MS` cancels
19
+ * the whole task (parent + subagents) —
20
+ * stopping is a deliberate double-press
21
+ * 4. editor non-empty (idle) → NO-OP — pi's anti-misfire core
22
+ * 5. editor empty (idle) → 1st press arms, 2nd press within
23
+ * `DOUBLE_PRESS_MS` triggers the mapped
24
+ * action (pi defaults to /tree; dsh has
25
+ * no /tree, so we map to /session — the
26
+ * closest non-invasive analogue). The
27
+ * running-stop and idle windows own
28
+ * SEPARATE clocks (`lastRunningEscPress`
29
+ * vs `lastEscPress`) so an Esc that armed
30
+ * a stop never pops /session after the
31
+ * turn settles, and vice versa.
32
+ *
33
+ * Ctrl+C chain (dsh convention, windowed): first press cancels a running
34
+ * turn — or clears the editor while idle — and a second press within
35
+ * `DOUBLE_PRESS_MS` quits. This keeps dsh's "first cancels / second quits"
36
+ * while requiring a deliberate double-press before an idle exit. Held-key
37
+ * auto-repeat never counts: repeats inside `MIN_DOUBLE_PRESS_GAP_MS` resolve
38
+ * to `key-repeat` (consumed, inert — and the executor aborts a pending quit
39
+ * confirmation on one, since a repeat betrays a held key; see index.ts).
40
+ *
41
+ * Ctrl+D chain (pi `app.exit`): only quits on an EMPTY editor; with text it
42
+ * is left alone so the pi-tui Editor's existing `tui.editor.deleteCharForward`
43
+ * binding (delete, ctrl+d) deletes a character forward — exactly pi's
44
+ * context distinction.
45
+ *
46
+ * Ctrl+L chain (pi `app.model.select`): opens the model/think picker — the
47
+ * dsh analogue of pi's model selector. Like every app key here it yields to
48
+ * an open overlay first, so a popup that is on screen keeps its own keys.
49
+ *
50
+ * Ctrl+G chain (dsh's subagent viewer): opens the subagent picker only while
51
+ * a subagent runs (`runningAgents > 0`) — idle it falls through untouched.
52
+ * Same overlay-yields-first rule as the other app keys.
53
+ */
54
+ import { matchesKey } from '@earendil-works/pi-tui';
55
+ /** Double-press window (ms) for ctrl+c quit and empty-editor double-Esc. */
56
+ export const DOUBLE_PRESS_MS = 500;
57
+ /**
58
+ * Minimum gap (ms) between two presses of the same key for the second to
59
+ * count as a deliberate press rather than terminal auto-repeat. A held key is
60
+ * re-sent every ~30-50ms after the OS repeat delay (183ms-2s) and terminals
61
+ * report no key-up, so gap alone is the discriminator: anything closer than
62
+ * this floor is auto-repeat (see `key-repeat`). Human double-presses are
63
+ * rarely faster than ~100ms; common repeat rates (20-33/s) sit well under it.
64
+ */
65
+ export const MIN_DOUBLE_PRESS_GAP_MS = 80;
66
+ export const DEFAULT_KEYBINDINGS = {
67
+ escape: 'escape',
68
+ ctrlC: 'ctrl+c',
69
+ ctrlD: 'ctrl+d',
70
+ modelPicker: 'ctrl+l',
71
+ subagentViewer: 'ctrl+g',
72
+ };
73
+ /** True when a second press within the window follows `lastPress`. */
74
+ function isDoublePress(lastPress, now) {
75
+ return lastPress > 0 && now - lastPress <= DOUBLE_PRESS_MS;
76
+ }
77
+ /** True when this press is auto-repeat of a held key (gap under the floor). */
78
+ function isKeyRepeat(lastPress, now) {
79
+ return lastPress > 0 && now - lastPress < MIN_DOUBLE_PRESS_GAP_MS;
80
+ }
81
+ /** The Esc chain — pi app.interrupt, popup and autocomplete first. */
82
+ function resolveEscape(state, now) {
83
+ // 1. An open overlay owns Esc: the popup (subagent viewer, pickers,
84
+ // dialogs) closes itself and the running task is NEVER touched by that
85
+ // press — Esc is a per-popup cancel, not the global stop.
86
+ if (state.overlayOpen)
87
+ return { kind: 'overlay', consumes: false };
88
+ if (state.autocompleteOpen)
89
+ return { kind: 'autocomplete-close', consumes: false };
90
+ // 2. Agent mid-turn: stopping the whole task (parent + subagents) is a
91
+ // deliberate double-press — the first Esc only arms the stop window, so
92
+ // a stray Esc (e.g. one aimed at a just-closed popup) can never kill a
93
+ // running turn. Its clock is the separate `lastRunningEscPress`; held
94
+ // auto-repeat never arms or fires it.
95
+ if (state.running) {
96
+ if (isKeyRepeat(state.lastRunningEscPress, now))
97
+ return { kind: 'key-repeat', key: 'escape', consumes: true };
98
+ return isDoublePress(state.lastRunningEscPress, now)
99
+ ? { kind: 'interrupt-cancel', consumes: true }
100
+ : { kind: 'interrupt-arm-stop', consumes: true };
101
+ }
102
+ // 3. Idle: a non-empty editor is untouched (pi's anti-misfire core).
103
+ if (state.editorHasText)
104
+ return { kind: 'noop', consumes: false };
105
+ // 4. Empty editor, idle: first press arms, second within the window fires
106
+ // the double action (/session — dsh has no pi /tree). Auto-repeat of the
107
+ // armed key never fires the double action.
108
+ if (isKeyRepeat(state.lastEscPress, now))
109
+ return { kind: 'key-repeat', key: 'escape', consumes: true };
110
+ return isDoublePress(state.lastEscPress, now)
111
+ ? { kind: 'interrupt-double', consumes: true }
112
+ : { kind: 'interrupt-arm', consumes: true };
113
+ }
114
+ /**
115
+ * Ctrl+C — dsh's "first cancels / second quits", windowed against misfires.
116
+ * Auto-repeat (a held key) is swallowed: it neither cancels again nor ever
117
+ * completes the quit double press.
118
+ */
119
+ function resolveCtrlC(state, now) {
120
+ if (state.overlayOpen)
121
+ return { kind: 'overlay', consumes: false };
122
+ if (isKeyRepeat(state.lastCtrlCPress, now))
123
+ return { kind: 'key-repeat', key: 'ctrl-c', consumes: true };
124
+ if (state.running) {
125
+ if (isDoublePress(state.lastCtrlCPress, now))
126
+ return { kind: 'ctrl-c-quit', consumes: true };
127
+ return { kind: 'ctrl-c-cancel', consumes: true };
128
+ }
129
+ if (isDoublePress(state.lastCtrlCPress, now))
130
+ return { kind: 'ctrl-c-quit', consumes: true };
131
+ return { kind: 'ctrl-c-clear', consumes: true };
132
+ }
133
+ /** Ctrl+D — pi app.exit: quit only on an empty editor, else let it delete. */
134
+ function resolveCtrlD(state) {
135
+ if (state.overlayOpen)
136
+ return { kind: 'overlay', consumes: false };
137
+ if (state.editorHasText)
138
+ return { kind: 'noop', consumes: false };
139
+ return { kind: 'ctrl-d-quit', consumes: true };
140
+ }
141
+ /** Ctrl+L — pi app.model.select: open the model picker (overlay yields). */
142
+ function resolveModelPicker(state) {
143
+ if (state.overlayOpen)
144
+ return { kind: 'overlay', consumes: false };
145
+ return { kind: 'model-picker', consumes: true };
146
+ }
147
+ /** Ctrl+G — open the subagent picker only while children run (overlay yields). */
148
+ function resolveSubagentViewer(state) {
149
+ if (state.overlayOpen)
150
+ return { kind: 'overlay', consumes: false };
151
+ if (state.runningAgents > 0)
152
+ return { kind: 'subagent-viewer', consumes: true };
153
+ // No subagents to watch: let the key fall through to the focused component.
154
+ return { kind: 'noop', consumes: false };
155
+ }
156
+ /**
157
+ * Resolve one raw terminal input sequence into an app-level action.
158
+ * `data` is the single decoded key sequence `addInputListener` receives
159
+ * (e.g. "\x1b" for Escape, "\x03" for Ctrl+C); see keys.js for the
160
+ * normalization: Escape is `\x1b`, and legacy Ctrl+[ collapses to the same
161
+ * byte, so both are recognized as `escape`.
162
+ *
163
+ * `keys` may be a PARTIAL override — every missing binding falls back to the
164
+ * default. That is the shape a future `~/.dsh/keybindings.json` loader will
165
+ * feed in: remap one key, keep the rest.
166
+ *
167
+ * Any key outside the app bindings yields `noop` (not consumed — the focused
168
+ * component, normally the editor, processes it).
169
+ */
170
+ export function resolveKeyAction(data, state, now, keys = DEFAULT_KEYBINDINGS) {
171
+ const bindings = { ...DEFAULT_KEYBINDINGS };
172
+ for (const id of Object.keys(keys)) {
173
+ const value = keys[id];
174
+ if (value !== undefined)
175
+ bindings[id] = value;
176
+ }
177
+ if (matchesKey(data, bindings.escape))
178
+ return resolveEscape(state, now);
179
+ if (matchesKey(data, bindings.ctrlC))
180
+ return resolveCtrlC(state, now);
181
+ if (matchesKey(data, bindings.ctrlD))
182
+ return resolveCtrlD(state);
183
+ if (matchesKey(data, bindings.modelPicker))
184
+ return resolveModelPicker(state);
185
+ if (matchesKey(data, bindings.subagentViewer))
186
+ return resolveSubagentViewer(state);
187
+ return { kind: 'noop', consumes: false };
188
+ }
189
+ //# sourceMappingURL=keymap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keymap.js","sourceRoot":"","sources":["../src/keymap.ts"],"names":[],"mappings":"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"}
@@ -9,13 +9,12 @@
9
9
  * renders zero rows while empty and grows to its content while the model
10
10
  * has todos.
11
11
  * - `activityDoc` — the lastRequest container below the editor
12
- * (`ui.lastRequest`): the ` ↳ <last request>` line (persisting across
12
+ * (`ui.lastRequest`): the ` ● <last request>` line (persisting across
13
13
  * agent churn) followed by one compact line PER RUNNING agent. NO box
14
- * chrome, NO `● Agents` header, NO connector and NO provider — just
15
- * ` ↳ ⠋ <name> · ↻N≤M · 562 token (4%) · 13.6s`, the ` ↳ ` prefix
16
- * nesting one column right of the request line's ` ↳ ` (a tree level
17
- * under the last request) so the agent NAMES align under that level.
18
- * Auto height — it
14
+ * chrome, NO `● Agents` header, NO provider — just
15
+ * `├─ ⠋ <name> · ↻N≤M · 21k/1m · 13.6s · <latest output>`, the `├─ ` /
16
+ * `└─ ` prefix in the same column as the todo rows and the request ` ● `
17
+ * (the last running agent closes the list with `└─ `). Auto height — it
19
18
  * collapses to zero rows when both the last-request line is cleared and no
20
19
  * agent runs.
21
20
  *
@@ -37,6 +36,11 @@ import { type TuiTheme } from './theme/index.ts';
37
36
  export declare const AGENT_TICK_MS = 100;
38
37
  /** Braille spinner cycle; the frame index advances once per tick. */
39
38
  export declare const AGENT_SPINNER_FRAMES: readonly ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
39
+ /**
40
+ * Compact human-readable size: 1_500_000 → `1.5m`, 20_965 → `20k`, 999 → `999`.
41
+ * Millions keep one decimal (a trailing `.0` dropped), thousands floor to `k`.
42
+ */
43
+ export declare function fmtCompact(n: number): string;
40
44
  export declare class LiveWidgets {
41
45
  private readonly todosDoc;
42
46
  private readonly activityDoc;
@@ -50,7 +54,7 @@ export declare class LiveWidgets {
50
54
  private todosText;
51
55
  /** The merged running-agent Text in `activityDoc`, replaced on rebuild. */
52
56
  private agentsText;
53
- /** The ` ↳ <last request>` Text in `activityDoc` (persists across churn). */
57
+ /** The ` ● <last request>` Text in `activityDoc` (persists across churn). */
54
58
  private requestText;
55
59
  /** Plain clipped last-request text backing `requestText` (for setTheme). */
56
60
  private requestDisplay;
@@ -59,12 +63,12 @@ export declare class LiveWidgets {
59
63
  /**
60
64
  * @param todosDoc The dock slot ABOVE the chat input — the Todos boxed
61
65
  * panel lives here.
62
- * @param activityDoc The lastRequest container BELOW the editor — the ` ↳ `
66
+ * @param activityDoc The lastRequest container BELOW the editor — the ` ● `
63
67
  * last-request line plus the compact running-agent lines live here.
64
68
  */
65
69
  constructor(todosDoc: Container, activityDoc: Container, theme: TuiTheme, requestRender: () => void);
66
70
  /**
67
- * Render the ` ↳ <text>` last-request line in `activityDoc`, UNDER which the
71
+ * Render the ` ● <text>` last-request line in `activityDoc`, UNDER which the
68
72
  * running-agent lines appear. `undefined` (or blank) removes the line. Same
69
73
  * styling as before the merge: `fgMuted` prefix, but the text is clipped to
70
74
  * the terminal width (`columns - 5`) so it ALWAYS renders on one row and
@@ -84,21 +88,21 @@ export declare class LiveWidgets {
84
88
  * — the activity area then holds its final (empty) state.
85
89
  */
86
90
  tickLive(): void;
87
- /** Recolor the widget (Todos panel, ↳ line and agent lines) under a theme
91
+ /** Recolor the widget (Todos panel, ● line and agent lines) under a theme
88
92
  * hot-switch (no-op on the same bundle). */
89
93
  setTheme(theme: TuiTheme): void;
90
94
  /**
91
95
  * Drop everything except the last-request echo (`/new`): clears the Todos
92
- * panel and the running-agent lines, keeps the ` ↳ ` line. The bridge also
96
+ * panel and the running-agent lines, keeps the ` ● ` line. The bridge also
93
97
  * fires `renderAgents([])`.
94
98
  */
95
99
  clear(): void;
96
100
  /**
97
101
  * Rebuild the two live surfaces:
98
102
  * - `todosDoc`: one bordered Todos panel (or nothing when all completed).
99
- * - `activityDoc`: the ` ↳ ` line (managed separately, persists) followed
103
+ * - `activityDoc`: the ` ● ` line (managed separately, persists) followed
100
104
  * by ONE Text holding the compact running-agent lines joined by '\n'
101
- * (or nothing when no agent runs — the slot collapses to the ↳ line).
105
+ * (or nothing when no agent runs — the slot collapses to the ● line).
102
106
  * Clear-when-done: the Todos panel hides once every todo is completed (the
103
107
  * model writes the whole-list snapshot and rarely clears it — an
104
108
  * all-completed list is the end-of-work signal); the agent lines hide once
@@ -117,17 +121,18 @@ export declare class LiveWidgets {
117
121
  */
118
122
  private todoLines;
119
123
  /**
120
- * One compact running-agent line for the last-request area: ` ↳ ` prefix
121
- * (nesting one column right of the ` ↳ ` request text, like a tree level
122
- * under the last request) + spinner + the agent NAME
123
- * (`view.label`, matching the boxed panel's main line) + the exact meta that
124
- * main line showed (`↻retries≤max`, `N token (%)`, elapsed). NO box chrome,
125
- * NO connector, NO provider — the name is the prominent element. The label
126
- * is the only unbounded field; it is clipped against a budget measured from
127
- * the terminal width so the whole plain line (prefix included) fits, then
128
- * the assembled plain line is clipped to the terminal width BEFORE any ANSI
129
- * is applied (clipToWidth counts SGR fragments as visible columns — style
130
- * last). The provider (`view.provider`) is dropped from this line.
124
+ * One compact running-agent line for the last-request area, todo-style:
125
+ * `├─ `/`└─ ` connector (same column as the todo rows and the request
126
+ * ` ● `) + spinner + the agent NAME (`view.label`, matching the boxed
127
+ * panel's main line) + the exact meta that main line showed (`↻retries≤max`,
128
+ * compact `tokens[/contextWindow]`, elapsed) + the child's latest output
129
+ * line when one exists (` · <tail>`, so the user sees it is alive). NO box
130
+ * chrome, NO provider — the name is the prominent element; the last running
131
+ * agent closes the list with `└─ `. The label is the only unbounded field;
132
+ * it is clipped against a budget measured from the terminal width so the
133
+ * whole plain line (prefix included) fits, then the assembled plain line is
134
+ * clipped to the terminal width BEFORE any ANSI is applied (clipToWidth
135
+ * counts SGR fragments as visible columns — style last).
131
136
  */
132
137
  private compactAgentLine;
133
138
  }