@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.
Files changed (67) hide show
  1. package/README.md +205 -68
  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 +342 -38
  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 +82 -29
  27. package/lib/live-widgets.js +222 -92
  28. package/lib/live-widgets.js.map +1 -1
  29. package/lib/messages.d.ts +4 -2
  30. package/lib/messages.js +8 -3
  31. package/lib/messages.js.map +1 -1
  32. package/lib/panels.d.ts +177 -0
  33. package/lib/panels.js +331 -0
  34. package/lib/panels.js.map +1 -0
  35. package/lib/provider-catalog.d.ts +31 -8
  36. package/lib/provider-catalog.js +63 -8
  37. package/lib/provider-catalog.js.map +1 -1
  38. package/lib/selectors.d.ts +12 -1
  39. package/lib/selectors.js +5 -2
  40. package/lib/selectors.js.map +1 -1
  41. package/lib/session.d.ts +36 -0
  42. package/lib/session.js +171 -10
  43. package/lib/session.js.map +1 -1
  44. package/lib/settings.d.ts +1 -2
  45. package/lib/settings.js +29 -2
  46. package/lib/settings.js.map +1 -1
  47. package/lib/subagent-policy.d.ts +58 -0
  48. package/lib/subagent-policy.js +137 -0
  49. package/lib/subagent-policy.js.map +1 -0
  50. package/lib/subagent-viewer.d.ts +49 -0
  51. package/lib/subagent-viewer.js +523 -0
  52. package/lib/subagent-viewer.js.map +1 -0
  53. package/lib/theme/index.js +4 -0
  54. package/lib/theme/index.js.map +1 -1
  55. package/lib/theme/palette.d.ts +21 -3
  56. package/lib/theme/palette.js +27 -6
  57. package/lib/theme/palette.js.map +1 -1
  58. package/lib/theme-settings.d.ts +39 -4
  59. package/lib/theme-settings.js +69 -12
  60. package/lib/theme-settings.js.map +1 -1
  61. package/lib/tui.d.ts +32 -12
  62. package/lib/tui.js +109 -36
  63. package/lib/tui.js.map +1 -1
  64. package/package.json +3 -4
  65. package/patches/@earendil-works__pi-tui.patch +206 -2
  66. package/scripts/dev-install.mjs +87 -0
  67. package/scripts/link-dsh-closure.mjs +85 -0
@@ -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 never paints the main canvas (the terminal background shows
14
- * through), so `canvas` is the semantic base used by the dark muted blends
15
- * and by contrast design; `canvasSubtle` is the visible raised surface.
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;
@@ -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 never paints the main canvas (the terminal background shows
14
- * through), so `canvas` is the semantic base used by the dark muted blends
15
- * and by contrast design; `canvasSubtle` is the visible raised surface.
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. The TUI never paints it — the terminal
21
- // background (#f6f8fa) shows through; kept as the semantic base for the
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
@@ -1 +1 @@
1
- {"version":3,"file":"palette.js","sourceRoot":"","sources":["../../src/theme/palette.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAmCH,MAAM,CAAC,MAAM,WAAW,GAAY;IAClC,IAAI,EAAE,cAAc;IACpB,sEAAsE;IACtE,iEAAiE;IACjE,wEAAwE;IACxE,yCAAyC;IACzC,MAAM,EAAE,SAAS;IACjB,qEAAqE;IACrE,8DAA8D;IAC9D,YAAY,EAAE,SAAS;IACvB,uEAAuE;IACvE,WAAW,EAAE,SAAS;IACtB,uEAAuE;IACvE,mBAAmB;IACnB,SAAS,EAAE,SAAS;IACpB,gEAAgE;IAChE,OAAO,EAAE,SAAS;IAClB,wEAAwE;IACxE,qEAAqE;IACrE,QAAQ,EAAE,SAAS;IACnB,uEAAuE;IACvE,uBAAuB;IACvB,aAAa,EAAE,SAAS;IACxB,mCAAmC;IACnC,WAAW,EAAE,SAAS;IACtB,wEAAwE;IACxE,+BAA+B;IAC/B,MAAM,EAAE,SAAS;IACjB,+CAA+C;IAC/C,WAAW,EAAE,SAAS;IACtB,wEAAwE;IACxE,yBAAyB;IACzB,OAAO,EAAE,SAAS;IAClB,yDAAyD;IACzD,YAAY,EAAE,SAAS;IACvB,uEAAuE;IACvE,wBAAwB;IACxB,MAAM,EAAE,SAAS;IACjB,mDAAmD;IACnD,WAAW,EAAE,SAAS;IACtB,2EAA2E;IAC3E,SAAS,EAAE,SAAS;IACpB,mCAAmC;IACnC,cAAc,EAAE,SAAS;IACzB,2EAA2E;IAC3E,QAAQ,EAAE,SAAS;IACnB,4EAA4E;IAC5E,gDAAgD;IAChD,eAAe,EAAE,SAAS;IAC1B,2EAA2E;IAC3E,4CAA4C;IAC5C,WAAW,EAAE,SAAS;IACtB,0EAA0E;IAC1E,0DAA0D;IAC1D,WAAW,EAAE,SAAS;CACvB,CAAA;AAED,MAAM,IAAI,GAAG,mBAAmB,CAAA;AAEhC,sFAAsF;AACtF,SAAS,KAAK,CAAC,IAAY,EAAE,IAAY,EAAE,KAAa;IACtD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACzC,MAAM,IAAI,SAAS,CAAC,8CAA8C,IAAI,UAAU,IAAI,GAAG,CAAC,CAAA;IAC1F,CAAC;IACD,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAA;IAChE,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAA;IAChE,MAAM,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAE,GAAG,KAAK,CAAC,CAAC,CAAA;IAC1E,OAAO,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAA;AACvE,CAAC;AAED,MAAM,CAAC,MAAM,UAAU,GAAY;IACjC,IAAI,EAAE,aAAa;IACnB,wDAAwD;IACxD,MAAM,EAAE,SAAS;IACjB,0EAA0E;IAC1E,YAAY,EAAE,SAAS;IACvB,+CAA+C;IAC/C,WAAW,EAAE,SAAS;IACtB,6CAA6C;IAC7C,SAAS,EAAE,SAAS;IACpB,mCAAmC;IACnC,OAAO,EAAE,SAAS;IAClB,qEAAqE;IACrE,+CAA+C;IAC/C,QAAQ,EAAE,SAAS;IACnB,6BAA6B;IAC7B,aAAa,EAAE,SAAS;IACxB,iCAAiC;IACjC,WAAW,EAAE,SAAS;IACtB,uEAAuE;IACvE,yBAAyB;IACzB,MAAM,EAAE,SAAS;IACjB,iEAAiE;IACjE,+BAA+B;IAC/B,WAAW,EAAE,KAAK,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,CAAC;IAC9C,wEAAwE;IACxE,gBAAgB;IAChB,OAAO,EAAE,SAAS;IAClB,oEAAoE;IACpE,YAAY,EAAE,KAAK,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,CAAC;IAC/C,oEAAoE;IACpE,eAAe;IACf,MAAM,EAAE,SAAS;IACjB,6DAA6D;IAC7D,WAAW,EAAE,KAAK,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,CAAC;IAC9C,yEAAyE;IACzE,qBAAqB;IACrB,SAAS,EAAE,SAAS;IACpB,8CAA8C;IAC9C,cAAc,EAAE,KAAK,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,CAAC;IACjD,uEAAuE;IACvE,yBAAyB;IACzB,QAAQ,EAAE,SAAS;IACnB,0EAA0E;IAC1E,kCAAkC;IAClC,eAAe,EAAE,KAAK,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,CAAC;IAClD,yEAAyE;IACzE,wEAAwE;IACxE,WAAW,EAAE,KAAK,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,CAAC;IAC9C,0EAA0E;IAC1E,+DAA+D;IAC/D,WAAW,EAAE,SAAS;CACvB,CAAA;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAyB,OAAO,CAAC,GAAG;IACpE,6EAA6E;IAC7E,4EAA4E;IAC5E,MAAM,SAAS,GAAG,GAAG,CAAC,SAAS,CAAA;IAC/B,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5B,MAAM,EAAE,GAAG,QAAQ,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAA;QACtD,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;YAAE,OAAO,CAAC,CAAC,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IACxD,CAAC;IACD,6EAA6E;IAC7E,OAAO,IAAI,CAAA;AACb,CAAC"}
1
+ {"version":3,"file":"palette.js","sourceRoot":"","sources":["../../src/theme/palette.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,10 @@
1
1
  /**
2
- * Theme settings: persists the user's theme preference and the think/tool
3
- * panel height under the `dsh-tui` settings namespace, surfaced by the
4
- * /settings browser and the /theme command. The preferences are read once at
5
- * TUI startup (`readThemePreference` / `readPanelHeightPreference`), and the
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>;
@@ -1,8 +1,10 @@
1
1
  /**
2
- * Theme settings: persists the user's theme preference and the think/tool
3
- * panel height under the `dsh-tui` settings namespace, surfaced by the
4
- * /settings browser and the /theme command. The preferences are read once at
5
- * TUI startup (`readThemePreference` / `readPanelHeightPreference`), and the
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 panelHeight) to the settings
182
- * namespace. The namespace is `applies: 'live'`, so the commit (observed
183
- * through the registration's watch hook) hot-applies the change to the
184
- * running TUI. Best-effort: a deployment without a settings provider reports
185
- * the failure; a failed write returns its error message for the caller to
186
- * surface. A concurrent writer moving the namespace rejects with
187
- * `SettingsConflictError` — retried once against a fresh revision; a second
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":"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"}
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/Agents) │ │ dock — basis auto / grow 0,
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
- /** Ctrl+C handler. Defaults to a plain exit (smoke-test mode). */
26
- onInterrupt?: () => void;
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/Agents widgets. */
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 editor border, the last-request
54
- * line and (when empty) the placeholder, and updates every subsequent
55
- * `theme` read. Transcript repainting is the TranscriptRenderer's job
56
- * (`setTheme`), owned by the caller; call it before `applyTheme` so the
57
- * one throttled render frame paints everything at once. No-op when the
58
- * bundle is unchanged (themes are module singletons).
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
- /** Show/hide the "last request" widget below the editor. */
68
- setLastRequest(text: string | undefined): void;
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/Agents) │ │ dock — basis auto / grow 0,
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, matchesKey, ProcessTerminal, ScrollView, Spacer, Text, TuiAltScreen, VStack, } from '@earendil-works/pi-tui';
21
+ import { Container, ProcessTerminal, ScrollView, Spacer, Text, TuiAltScreen, VStack, } from '@earendil-works/pi-tui';
20
22
  import { CwdBorderEditor } from "./editor.js";
21
- import { ansiFg, RESET, resolveTheme } from "./theme/index.js";
22
- import { clipToWidth } from "./text.js";
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/Agents widgets, pinned ABOVE the chat input: a plain Container
31
- // with auto height — it renders zero rows while empty and grows to its
32
- // (bordered-panel) content while the model has todos or subagents running.
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
- setLastRequest(text) {
158
- if (text === undefined || text.trim() === '') {
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
- if (matchesKey(data, 'ctrl+c')) {
181
- if (options.onInterrupt !== undefined) {
182
- options.onInterrupt();
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 {