@crouton-kit/crouter 0.3.78 → 0.3.80

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 (80) hide show
  1. package/dist/build-root.d.ts +12 -4
  2. package/dist/build-root.js +25 -6
  3. package/dist/builtin-memory/crouter-development/plugins.md +82 -5
  4. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/provider-rotation.test.ts +1228 -12
  5. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +3 -3
  6. package/dist/builtin-pi-packages/pi-crtr-extensions/lib/subscription-state.ts +2 -787
  7. package/dist/clients/attach/__tests__/attach-chrome-remote.test.js +8 -3
  8. package/dist/clients/attach/__tests__/attach-keybindings.test.d.ts +1 -0
  9. package/dist/clients/attach/__tests__/attach-keybindings.test.js +113 -0
  10. package/dist/clients/attach/__tests__/mermaid-render.test.js +9 -1
  11. package/dist/clients/attach/attach-cmd.d.ts +9 -1
  12. package/dist/clients/attach/attach-cmd.js +847 -801
  13. package/dist/clients/attach/auth-pickers.d.ts +0 -12
  14. package/dist/clients/attach/auth-pickers.js +64 -15
  15. package/dist/clients/attach/chat-view.d.ts +4 -0
  16. package/dist/clients/attach/chat-view.js +10 -0
  17. package/dist/clients/attach/graph-overlay.d.ts +12 -2
  18. package/dist/clients/attach/graph-overlay.js +83 -33
  19. package/dist/clients/attach/input-controller.d.ts +19 -0
  20. package/dist/clients/attach/input-controller.js +55 -11
  21. package/dist/clients/attach/titled-editor.d.ts +21 -0
  22. package/dist/clients/attach/titled-editor.js +101 -0
  23. package/dist/commands/human/queue.js +3 -4
  24. package/dist/commands/pkg/plugin-inspect.js +22 -1
  25. package/dist/commands/pkg/plugin-manage.js +31 -9
  26. package/dist/commands/sys/__tests__/setup-core.test.js +158 -1
  27. package/dist/commands/sys/doctor.js +42 -4
  28. package/dist/commands/sys/setup-core.d.ts +37 -0
  29. package/dist/commands/sys/setup-core.js +138 -1
  30. package/dist/commands/sys/setup.d.ts +88 -0
  31. package/dist/commands/sys/setup.js +915 -171
  32. package/dist/commands/view-pick.d.ts +4 -0
  33. package/dist/commands/view-pick.js +17 -7
  34. package/dist/core/__tests__/canvas-inbox-watcher.test.js +34 -9
  35. package/dist/core/__tests__/command-plugins-surfaces.test.d.ts +1 -0
  36. package/dist/core/__tests__/command-plugins-surfaces.test.js +298 -0
  37. package/dist/core/__tests__/command-plugins.test.d.ts +1 -0
  38. package/dist/core/__tests__/command-plugins.test.js +444 -0
  39. package/dist/core/__tests__/fixtures/fake-engine.d.ts +6 -0
  40. package/dist/core/__tests__/fixtures/fake-engine.js +9 -1
  41. package/dist/core/__tests__/preview-registry-sync.test.js +30 -1
  42. package/dist/core/__tests__/scope-crouter-home-fence.test.d.ts +1 -0
  43. package/dist/core/__tests__/scope-crouter-home-fence.test.js +55 -0
  44. package/dist/core/canvas/browse/app.d.ts +6 -0
  45. package/dist/core/canvas/browse/app.js +71 -41
  46. package/dist/core/command-plugins/adapter.d.ts +15 -0
  47. package/dist/core/command-plugins/adapter.js +145 -0
  48. package/dist/core/command-plugins/compose.d.ts +5 -0
  49. package/dist/core/command-plugins/compose.js +56 -0
  50. package/dist/core/command-plugins/discovery.d.ts +104 -0
  51. package/dist/core/command-plugins/discovery.js +565 -0
  52. package/dist/core/config.d.ts +2 -5
  53. package/dist/core/keybindings/__tests__/bespoke-consumers.test.d.ts +1 -0
  54. package/dist/core/keybindings/__tests__/bespoke-consumers.test.js +40 -0
  55. package/dist/core/keybindings/__tests__/resolve.test.js +2 -2
  56. package/dist/core/keybindings/catalog.d.ts +3 -3
  57. package/dist/core/keybindings/catalog.js +2 -1
  58. package/dist/core/profiles/select.d.ts +6 -0
  59. package/dist/core/profiles/select.js +90 -59
  60. package/dist/core/provider-management.d.ts +12 -0
  61. package/dist/core/provider-management.js +24 -0
  62. package/dist/core/runtime/banner.d.ts +13 -0
  63. package/dist/core/runtime/banner.js +51 -0
  64. package/dist/core/runtime/broker.js +7 -6
  65. package/dist/core/runtime/pi-vendored.d.ts +8 -0
  66. package/dist/core/runtime/pi-vendored.js +14 -0
  67. package/dist/core/runtime/recap.d.ts +1 -1
  68. package/dist/core/runtime/recap.js +50 -25
  69. package/dist/core/runtime/session-list-cache.d.ts +10 -0
  70. package/dist/core/runtime/session-list-cache.js +94 -26
  71. package/dist/core/runtime/spawn.js +5 -17
  72. package/dist/core/runtime/tmux.js +2 -1
  73. package/dist/core/scope.js +27 -4
  74. package/dist/core/subscription-state.d.ts +90 -0
  75. package/dist/core/subscription-state.js +762 -0
  76. package/dist/daemon/crtrd.js +253 -12
  77. package/dist/pi-extensions/canvas-recap.js +43 -17
  78. package/dist/types.d.ts +6 -13
  79. package/dist/types.js +0 -3
  80. package/package.json +7 -3
@@ -29,6 +29,7 @@ import { openFocusRow } from '../../../core/canvas/focuses.js';
29
29
  import { jobDir } from '../../../core/canvas/paths.js';
30
30
  import { recordFault } from '../../../core/runtime/fault.js';
31
31
  import { BG_ATTACHED } from '../../../core/canvas/nav-model.js';
32
+ import { resolveKeybindings } from '../../../core/keybindings/index.js';
32
33
  import { dispatchSlashCommand, slashCommandList } from '../slash-commands.js';
33
34
  import { resolveInitialMode, requestModeSwitch, tagAttachPane, untagAttachPane, reapOnDetach, } from '../attach-cmd.js';
34
35
  import { buildCanvasPanelLines } from '../canvas-panels.js';
@@ -198,7 +199,7 @@ test('GraphOverlay suppresses every local-read adornment for a RemoteCanvasSourc
198
199
  t.after(() => server.close());
199
200
  const source = new RemoteCanvasSource(server.url, 'tok');
200
201
  const { tui } = fakeTui();
201
- const overlay = new GraphOverlay(tui, 'root', () => ({}), IDENTITY_PALETTE, source);
202
+ const overlay = new GraphOverlay(tui, 'root', () => ({}), IDENTITY_PALETTE, resolveKeybindings(), source);
202
203
  overlay.open();
203
204
  // buildSnapshot() is async (chased through the mock server); poll for it to land.
204
205
  for (let i = 0; i < 100 && overlay.snapshot === undefined; i++) {
@@ -217,10 +218,14 @@ test('GraphOverlay suppresses every local-read adornment for a RemoteCanvasSourc
217
218
  // effect of a real `focusTarget` dispatch) — it must no-op instead.
218
219
  overlay.handleInput('\r');
219
220
  assert.equal(overlay.isOpen(), true, 'Enter must not act (close) on a remote graph row');
220
- // x must NOT arm the y/n kill confirm render() would show it in the hint.
221
+ // x must NOT arm the close confirm on a remote row. The confirmation UI never
222
+ // literally contains legacy `y/n`, so assert the real state directly:
223
+ // pendingConfirm stays unset and no `kill …?` accept/cancel prompt renders.
221
224
  overlay.handleInput('x');
225
+ assert.equal(overlay.pendingConfirm, undefined, 'x must not arm pendingConfirm on a remote graph row');
222
226
  const afterX = overlay.render(100).join('\n');
223
- assert.ok(!afterX.includes('y/n'), `x must not arm a kill confirm on a remote graph row: ${afterX}`);
227
+ assert.ok(!afterX.includes('kill'), `x must not surface a kill confirm prompt on a remote graph row: ${afterX}`);
228
+ assert.ok(!/accept|cancel/.test(afterX), `x must not surface an accept/cancel confirm on a remote graph row: ${afterX}`);
224
229
  // m must NOT close the overlay either (it would focusTarget the manager).
225
230
  overlay.handleInput('m');
226
231
  assert.equal(overlay.isOpen(), true, 'm must not act (close) on a remote graph row');
@@ -0,0 +1,113 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { resolveKeybindings } from '../../../core/keybindings/index.js';
4
+ import { detachHint, dispatchAttachBinding, matchesAttachBinding, refreshAttachBindingSnapshot, } from '../attach-cmd.js';
5
+ import { GraphOverlay } from '../graph-overlay.js';
6
+ const PALETTE = {
7
+ accent: (s) => s,
8
+ active: (s) => s,
9
+ info: (s) => s,
10
+ muted: (s) => s,
11
+ faint: (s) => s,
12
+ border: (s) => s,
13
+ bold: (s) => s,
14
+ error: (s) => s,
15
+ warning: (s) => s,
16
+ bashMode: (s) => s,
17
+ bashModeAlt: (s) => s,
18
+ surface: (s) => s,
19
+ };
20
+ function fakeTui() {
21
+ return {
22
+ showOverlay: () => ({ hide: () => { } }),
23
+ requestRender: () => { },
24
+ };
25
+ }
26
+ test('attach input dispatches only the current configured action', () => {
27
+ const bindings = resolveKeybindings({
28
+ 'crtr.attach.detach': ['ctrl+x'],
29
+ 'crtr.attach.clear-or-detach': [],
30
+ 'crtr.attach.graph.toggle': [],
31
+ 'crtr.mode.cycle': ['alt+x'],
32
+ 'crtr.attach.model-ladder.next': ['alt+z'],
33
+ });
34
+ const calls = [];
35
+ const actions = {
36
+ 'crtr.attach.detach': () => calls.push('detach'),
37
+ 'crtr.attach.clear-or-detach': () => calls.push('clear'),
38
+ 'crtr.attach.graph.toggle': () => calls.push('graph'),
39
+ 'crtr.mode.cycle': () => calls.push('mode'),
40
+ 'crtr.attach.model-ladder.next': () => calls.push('ladder'),
41
+ };
42
+ assert.equal(dispatchAttachBinding(bindings, '\x18', actions), true);
43
+ assert.equal(dispatchAttachBinding(bindings, '\x04', actions), false, 'old detach must not reach the listener action');
44
+ assert.equal(dispatchAttachBinding(bindings, '\x1bg', actions), false, 'disabled graph must not reach the listener action');
45
+ assert.equal(dispatchAttachBinding(bindings, '\x1bx', actions), true);
46
+ assert.equal(dispatchAttachBinding(bindings, '\x1bm', actions), false, 'old mode key must not reach the listener action');
47
+ assert.equal(dispatchAttachBinding(bindings, '\x1bz', actions), true);
48
+ assert.equal(dispatchAttachBinding(bindings, '\x1bM', actions), false, 'old ladder key must not reach the listener action');
49
+ assert.deepEqual(calls, ['detach', 'mode', 'ladder']);
50
+ assert.equal(detachHint(bindings), 'Ctrl+X', 'disabled clear-or-detach must fall back to enabled detach');
51
+ const noDetach = resolveKeybindings({ 'crtr.attach.detach': [], 'crtr.attach.clear-or-detach': [] });
52
+ assert.equal(detachHint(noDetach), undefined, 'a disabled pair must not render "Disabled to detach"');
53
+ });
54
+ test('attach startup and reload replace the dispatch/GRAPH snapshots and install each tmux snapshot', () => {
55
+ const first = resolveKeybindings({ 'crtr.attach.detach': ['ctrl+x'], 'crtr.graph.focus': ['f'] });
56
+ const second = resolveKeybindings({ 'crtr.attach.detach': ['ctrl+z'], 'crtr.graph.focus': ['r'] });
57
+ const snapshots = [first, second];
58
+ const graphSnapshots = [];
59
+ const installed = [];
60
+ let active = refreshAttachBindingSnapshot(() => snapshots.shift(), (bindings) => graphSnapshots.push(bindings), () => true, (bindings) => installed.push(bindings));
61
+ assert.equal(matchesAttachBinding(active, 'crtr.attach.detach', '\x18'), true, 'startup snapshot drives attach dispatch');
62
+ assert.equal(active.gestures('crtr.graph.focus')[0], 'f', 'startup snapshot reaches GRAPH');
63
+ active = refreshAttachBindingSnapshot(() => snapshots.shift(), (bindings) => graphSnapshots.push(bindings), () => true, (bindings) => installed.push(bindings));
64
+ assert.equal(matchesAttachBinding(active, 'crtr.attach.detach', '\x18'), false, 'reload replaces the old attach snapshot');
65
+ assert.equal(matchesAttachBinding(active, 'crtr.attach.detach', '\x1a'), true, 'reload dispatches through the new attach snapshot');
66
+ assert.equal(active.gestures('crtr.graph.focus')[0], 'r', 'reload replaces GRAPH bindings too');
67
+ assert.deepEqual(graphSnapshots, [first, second]);
68
+ assert.deepEqual(installed, [first, second], 'the unified tmux installer receives startup and reload snapshots');
69
+ });
70
+ test('GRAPH navigation and hints use effective bindings rather than default literals', () => {
71
+ const bindings = resolveKeybindings({
72
+ 'crtr.graph.down': ['z'],
73
+ 'crtr.graph.up': [],
74
+ 'crtr.graph.focus': ['f'],
75
+ 'crtr.graph.close-subtree': [],
76
+ 'crtr.graph.dismiss': ['q'],
77
+ 'crtr.graph.confirm.accept': ['t'],
78
+ 'crtr.graph.confirm.cancel': ['c'],
79
+ });
80
+ const overlay = new GraphOverlay(fakeTui(), 'a', () => ({}), PALETTE, bindings);
81
+ const internal = overlay;
82
+ internal.cursorId = 'a';
83
+ internal.snapshot = {
84
+ rows: [
85
+ { id: 'a', hasKids: false, isSelf: true, branch: '', cycle: false, collapsed: false },
86
+ { id: 'b', hasKids: false, isSelf: false, branch: '', cycle: false, collapsed: false },
87
+ ],
88
+ activeBelow: new Map(),
89
+ nodes: new Map([['a', null], ['b', null]]),
90
+ childIds: new Map(),
91
+ sortedChildIds: new Map(),
92
+ managerOf: new Map(),
93
+ focused: new Set(),
94
+ };
95
+ overlay.handleInput('j');
96
+ assert.equal(internal.cursorId, 'a', 'replaced default must stop matching');
97
+ overlay.handleInput('z');
98
+ assert.equal(internal.cursorId, 'b', 'replacement must drive navigation');
99
+ const rendered = overlay.render(58).join('\n');
100
+ assert.match(rendered, /F focus/, 'narrow GRAPH footer must prioritize remapped local focus');
101
+ assert.match(rendered, /Q dismiss/, 'narrow GRAPH footer must prioritize remapped dismiss');
102
+ assert.doesNotMatch(rendered, /close/);
103
+ assert.doesNotMatch(rendered, /J down/);
104
+ assert.doesNotMatch(rendered, /Disabled/);
105
+ let closed = 0;
106
+ internal.pendingConfirm = { label: 'close b?', action: () => { closed++; } };
107
+ assert.match(overlay.render(80).join('\n'), /T accept · C cancel/);
108
+ overlay.handleInput('y');
109
+ assert.equal(closed, 0, 'replaced accept default must stop confirming');
110
+ internal.pendingConfirm = { label: 'close b?', action: () => { closed++; } };
111
+ overlay.handleInput('t');
112
+ assert.equal(closed, 1, 'replacement must confirm close');
113
+ });
@@ -1,6 +1,14 @@
1
1
  import assert from 'node:assert/strict';
2
2
  import test from 'node:test';
3
+ import { ensureRenderer, isRendererReady } from '@crouton-kit/humanloop';
3
4
  import { transformMermaidFences } from '../mermaid-render.js';
5
+ // This asserts a REAL termrender render (box-drawing glyphs), so it only holds
6
+ // once the managed renderer is provisioned — true in the full tier and on a dev
7
+ // box, but the fast tier is deliberately termrender-free (no uv to provision the
8
+ // venv). Skip cleanly when the renderer is unavailable, exactly as the tmux
9
+ // tests skip when tmux is absent, rather than failing on the environment.
10
+ ensureRenderer();
11
+ const SKIP = !isRendererReady() ? 'termrender renderer unavailable' : false;
4
12
  const APPLET_CONCIERGE_FLOW = `\`\`\`mermaid
5
13
  flowchart LR
6
14
  U[User] --> C[Applet concierge]
@@ -10,7 +18,7 @@ flowchart LR
10
18
  B --> C
11
19
  C --> U
12
20
  \`\`\``;
13
- test('renders the applet concierge flowchart after provisioning termrender', () => {
21
+ test('renders the applet concierge flowchart after provisioning termrender', { skip: SKIP }, () => {
14
22
  const rendered = transformMermaidFences(APPLET_CONCIERGE_FLOW, 200);
15
23
  assert.match(rendered, /Applet-builder/);
16
24
  assert.match(rendered, /cwd = app repo/);
@@ -1,12 +1,19 @@
1
1
  import { CombinedAutocompleteProvider } from '@earendil-works/pi-tui';
2
2
  import type { BranchDef } from '../../core/command.js';
3
3
  import type { BrokerSnapshot, ClientRole } from '../../core/runtime/broker-protocol.js';
4
+ import { type BindingId, type BindingResolution } from '../../core/keybindings/index.js';
4
5
  import { type AttachPalette } from './config-load.js';
5
6
  export type Mode = 'normal' | 'spec' | 'plan';
7
+ type AttachBindingId = Extract<BindingId, `crtr.attach.${string}` | 'crtr.mode.cycle'>;
8
+ type AttachBindings = BindingResolution<BindingId>;
9
+ export declare function matchesAttachBinding(bindings: AttachBindings, id: AttachBindingId, data: string): boolean;
10
+ export declare function dispatchAttachBinding(bindings: AttachBindings, data: string, actions: Record<AttachBindingId, () => void>): boolean;
11
+ export declare function detachHint(bindings: AttachBindings): string | undefined;
12
+ export declare function refreshAttachBindingSnapshot(resolve: () => AttachBindings, setGraphBindings: (bindings: AttachBindings) => void, inTmuxNow: () => boolean, install: (bindings: AttachBindings) => void): AttachBindings;
6
13
  /** Resolve the mode badge's initial state. Remote never reads the local
7
14
  * mode-switch `current` file for `nodeId` — always starts 'normal'. */
8
15
  export declare function resolveInitialMode(cwd: string, nodeId: string, remote: boolean, readMode?: (cwd: string, nodeId: string) => Mode | undefined): Mode;
9
- /** Handle Alt+Shift+P's mode-cycle request. Remote no-ops entirely (returns `false`,
16
+ /** Handle a configured mode-cycle request. Remote no-ops entirely (returns `false`,
10
17
  * so the caller leaves `modeState`/the badge untouched) — there is no
11
18
  * remote-safe mode-switch protocol, and writing the local `request.json` for a
12
19
  * remote node id would target the wrong (or a colliding local) node. */
@@ -46,3 +53,4 @@ export declare function buildAttachAutocompleteProvider(cwd: string, fdPath: str
46
53
  description?: string;
47
54
  }>, cwd: string, fdPath: string | null) => CombinedAutocompleteProvider): CombinedAutocompleteProvider;
48
55
  export declare function registerAttach(): BranchDef;
56
+ export {};