@crouton-kit/crouter 0.3.70 → 0.3.79
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 +1 -1
- package/dist/build-root.d.ts +12 -4
- package/dist/build-root.js +25 -6
- package/dist/builtin-memory/00-runtime-base.md +3 -10
- package/dist/builtin-memory/04-base-worker.md +18 -0
- package/dist/builtin-memory/04-orchestration-kernel.md +1 -1
- package/dist/builtin-memory/crouter-development/plugins.md +82 -5
- package/dist/builtin-pi-packages/pi-crtr-extensions/README.md +13 -34
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/provider-rotation.test.ts +1115 -1
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +234 -71
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/statusline.ts +2 -9
- package/dist/builtin-pi-packages/pi-crtr-extensions/lib/subscription-state.ts +2 -516
- package/dist/builtin-pi-packages/pi-crtr-extensions/package-lock.json +2 -2
- package/dist/builtin-pi-packages/pi-mode-switch/README.md +11 -39
- package/dist/builtin-pi-packages/pi-mode-switch/extensions/index.ts +20 -15
- package/dist/builtin-pi-packages/pi-mode-switch/package.json +1 -1
- package/dist/builtin-views/canvas/tui.mjs +8 -9
- package/dist/builtin-views/chat/tui.mjs +12 -12
- package/dist/builtin-views/git-pr/tui.mjs +7 -8
- package/dist/builtin-views/inbox/tui.mjs +27 -41
- package/dist/builtin-views/linkedin/tui.mjs +23 -37
- package/dist/builtin-views/prompt-review/tui.mjs +11 -11
- package/dist/builtin-views/settings/tui.mjs +11 -11
- package/dist/builtin-views/workspace-sidebar/tui.mjs +8 -9
- package/dist/clients/attach/__tests__/attach-chrome-remote.test.js +11 -6
- package/dist/clients/attach/__tests__/attach-keybindings.test.d.ts +1 -0
- package/dist/clients/attach/__tests__/attach-keybindings.test.js +113 -0
- package/dist/clients/attach/__tests__/crtr-output-render.test.js +1 -1
- package/dist/clients/attach/__tests__/mermaid-render.test.d.ts +1 -0
- package/dist/clients/attach/__tests__/mermaid-render.test.js +28 -0
- package/dist/clients/attach/__tests__/oauth-dialog-lifecycle.test.d.ts +1 -0
- package/dist/clients/attach/__tests__/oauth-dialog-lifecycle.test.js +110 -0
- package/dist/clients/attach/attach-cmd.d.ts +9 -1
- package/dist/clients/attach/attach-cmd.js +841 -792
- package/dist/clients/attach/auth-pickers.d.ts +0 -12
- package/dist/clients/attach/auth-pickers.js +64 -15
- package/dist/clients/attach/canvas-panels.js +2 -3
- package/dist/clients/attach/chat-view.d.ts +7 -8
- package/dist/clients/attach/chat-view.js +119 -79
- package/dist/clients/attach/edit-diff-render.d.ts +6 -0
- package/dist/clients/attach/edit-diff-render.js +263 -0
- package/dist/clients/attach/graph-overlay.d.ts +12 -2
- package/dist/clients/attach/graph-overlay.js +83 -33
- package/dist/clients/attach/input-controller.d.ts +28 -2
- package/dist/clients/attach/input-controller.js +38 -5
- package/dist/clients/attach/mermaid-render.js +5 -2
- package/dist/clients/attach/pickers.d.ts +8 -7
- package/dist/clients/attach/pickers.js +11 -17
- package/dist/clients/attach/slash-commands.d.ts +9 -0
- package/dist/clients/attach/slash-commands.js +127 -7
- package/dist/clients/attach/titled-editor.d.ts +12 -1
- package/dist/clients/attach/titled-editor.js +103 -8
- package/dist/commands/canvas-browse.js +2 -2
- package/dist/commands/human/queue.js +3 -4
- package/dist/commands/memory/lint.js +39 -5
- package/dist/commands/memory/write.js +1 -0
- package/dist/commands/node.js +9 -2
- package/dist/commands/pkg/plugin-inspect.js +22 -1
- package/dist/commands/pkg/plugin-manage.js +31 -9
- package/dist/commands/surface-tmux-spread.js +1 -3
- package/dist/commands/sys/__tests__/config-keybindings.test.d.ts +1 -0
- package/dist/commands/sys/__tests__/config-keybindings.test.js +55 -0
- package/dist/commands/sys/__tests__/config-model-ladders.test.d.ts +1 -0
- package/dist/commands/sys/__tests__/config-model-ladders.test.js +121 -0
- package/dist/commands/sys/__tests__/setup-core.test.js +158 -1
- package/dist/commands/sys/config.js +18 -21
- package/dist/commands/sys/doctor.js +42 -4
- package/dist/commands/sys/setup-core.d.ts +49 -1
- package/dist/commands/sys/setup-core.js +161 -4
- package/dist/commands/sys/setup.d.ts +88 -0
- package/dist/commands/sys/setup.js +943 -169
- package/dist/commands/view-pick.d.ts +4 -0
- package/dist/commands/view-pick.js +17 -7
- package/dist/core/__tests__/base-worker-prompt.test.d.ts +1 -0
- package/dist/core/__tests__/base-worker-prompt.test.js +24 -0
- package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +232 -1
- package/dist/core/__tests__/canvas-inbox-watcher.test.js +34 -9
- package/dist/core/__tests__/command-plugins-surfaces.test.d.ts +1 -0
- package/dist/core/__tests__/command-plugins-surfaces.test.js +298 -0
- package/dist/core/__tests__/command-plugins.test.d.ts +1 -0
- package/dist/core/__tests__/command-plugins.test.js +444 -0
- package/dist/core/__tests__/fault-classifier.test.js +15 -0
- package/dist/core/__tests__/fixtures/fake-engine.d.ts +6 -0
- package/dist/core/__tests__/fixtures/fake-engine.js +9 -1
- package/dist/core/__tests__/full/broker-dialogs.test.js +7 -2
- package/dist/core/__tests__/host-teardown-process-group.test.js +15 -4
- package/dist/core/__tests__/preview-registry-sync.test.js +30 -1
- package/dist/core/__tests__/scope-crouter-home-fence.test.d.ts +1 -0
- package/dist/core/__tests__/scope-crouter-home-fence.test.js +55 -0
- package/dist/core/__tests__/stream-watchdog.test.d.ts +1 -0
- package/dist/core/__tests__/stream-watchdog.test.js +70 -0
- package/dist/core/__tests__/tmux-surface.test.js +72 -0
- package/dist/core/canvas/browse/__tests__/model.test.js +23 -5
- package/dist/core/canvas/browse/__tests__/render.test.js +1 -0
- package/dist/core/canvas/browse/app.d.ts +6 -0
- package/dist/core/canvas/browse/app.js +230 -41
- package/dist/core/canvas/browse/model.d.ts +10 -6
- package/dist/core/canvas/browse/model.js +20 -12
- package/dist/core/canvas/browse/pins.d.ts +4 -0
- package/dist/core/canvas/browse/pins.js +29 -0
- package/dist/core/canvas/browse/render.d.ts +13 -1
- package/dist/core/canvas/browse/render.js +72 -15
- package/dist/core/canvas/nav-model.js +5 -11
- package/dist/core/canvas/paths.d.ts +5 -0
- package/dist/core/canvas/paths.js +7 -0
- package/dist/core/command-plugins/adapter.d.ts +15 -0
- package/dist/core/command-plugins/adapter.js +145 -0
- package/dist/core/command-plugins/compose.d.ts +5 -0
- package/dist/core/command-plugins/compose.js +56 -0
- package/dist/core/command-plugins/discovery.d.ts +104 -0
- package/dist/core/command-plugins/discovery.js +565 -0
- package/dist/core/config.d.ts +48 -0
- package/dist/core/config.js +228 -110
- package/dist/core/fault-classifier.js +1 -1
- package/dist/core/fs-utils.d.ts +1 -0
- package/dist/core/fs-utils.js +15 -1
- package/dist/core/keybindings/__tests__/bespoke-consumers.test.d.ts +1 -0
- package/dist/core/keybindings/__tests__/bespoke-consumers.test.js +40 -0
- package/dist/core/keybindings/__tests__/resolve.test.d.ts +1 -0
- package/dist/core/keybindings/__tests__/resolve.test.js +224 -0
- package/dist/core/keybindings/catalog.d.ts +14 -0
- package/dist/core/keybindings/catalog.js +257 -0
- package/dist/core/keybindings/index.d.ts +5 -0
- package/dist/core/keybindings/index.js +4 -0
- package/dist/core/keybindings/match.d.ts +29 -0
- package/dist/core/keybindings/match.js +99 -0
- package/dist/core/keybindings/persistence.d.ts +14 -0
- package/dist/core/keybindings/persistence.js +31 -0
- package/dist/core/keybindings/resolve.d.ts +14 -0
- package/dist/core/keybindings/resolve.js +305 -0
- package/dist/core/keybindings/types.d.ts +37 -0
- package/dist/core/keybindings/types.js +1 -0
- package/dist/core/memory-resolver.d.ts +1 -1
- package/dist/core/memory-resolver.js +10 -3
- package/dist/core/predicate.d.ts +5 -3
- package/dist/core/predicate.js +5 -3
- package/dist/core/profiles/select.d.ts +6 -0
- package/dist/core/profiles/select.js +86 -52
- package/dist/core/provider-management.d.ts +12 -0
- package/dist/core/provider-management.js +24 -0
- package/dist/core/runtime/broker-protocol.d.ts +33 -5
- package/dist/core/runtime/broker.js +164 -14
- package/dist/core/runtime/launch.d.ts +39 -6
- package/dist/core/runtime/launch.js +78 -20
- package/dist/core/runtime/naming.js +3 -3
- package/dist/core/runtime/pi-cli.d.ts +6 -0
- package/dist/core/runtime/pi-cli.js +16 -2
- package/dist/core/runtime/pi-vendored.d.ts +8 -0
- package/dist/core/runtime/pi-vendored.js +14 -0
- package/dist/core/runtime/placement.d.ts +2 -2
- package/dist/core/runtime/placement.js +4 -1
- package/dist/core/runtime/promote.js +4 -0
- package/dist/core/runtime/recap.d.ts +3 -3
- package/dist/core/runtime/recap.js +75 -47
- package/dist/core/runtime/recycle.js +6 -1
- package/dist/core/runtime/reset.js +5 -0
- package/dist/core/runtime/session-list-cache.d.ts +33 -0
- package/dist/core/runtime/session-list-cache.js +338 -0
- package/dist/core/runtime/spawn.js +52 -45
- package/dist/core/runtime/stream-watchdog.d.ts +26 -0
- package/dist/core/runtime/stream-watchdog.js +75 -0
- package/dist/core/runtime/tmux-chrome.d.ts +1 -1
- package/dist/core/runtime/tmux-chrome.js +2 -2
- package/dist/core/runtime/tmux.d.ts +18 -15
- package/dist/core/runtime/tmux.js +217 -110
- package/dist/core/scope.js +27 -4
- package/dist/core/subscription-state.d.ts +90 -0
- package/dist/core/subscription-state.js +762 -0
- package/dist/core/tui/__tests__/host-keybindings.test.d.ts +1 -0
- package/dist/core/tui/__tests__/host-keybindings.test.js +112 -0
- package/dist/core/tui/host.d.ts +30 -1
- package/dist/core/tui/host.js +148 -34
- package/dist/core/view/contract.d.ts +17 -3
- package/dist/daemon/crtrd.js +253 -12
- package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +41 -0
- package/dist/pi-extensions/__tests__/canvas-stophook-context-nudge.test.d.ts +1 -0
- package/dist/pi-extensions/__tests__/canvas-stophook-context-nudge.test.js +121 -0
- package/dist/pi-extensions/canvas-inbox-watcher.d.ts +1 -1
- package/dist/pi-extensions/canvas-inbox-watcher.js +203 -25
- package/dist/pi-extensions/canvas-recap.d.ts +4 -0
- package/dist/pi-extensions/canvas-recap.js +73 -46
- package/dist/pi-extensions/canvas-stophook.d.ts +11 -0
- package/dist/pi-extensions/canvas-stophook.js +33 -4
- package/dist/types.d.ts +9 -26
- package/dist/types.js +1 -28
- package/dist/web-client/assets/{index-DiFuLcp6.js → index--md2ylfi.js} +1 -1
- package/dist/web-client/index.html +1 -1
- package/dist/web-client/sw.js +1 -1
- package/package.json +8 -4
- package/dist/pi-extensions/canvas-nav.d.ts +0 -43
- package/dist/pi-extensions/canvas-nav.js +0 -640
- package/dist/pi-extensions/widget-order-bus.d.ts +0 -6
- package/dist/pi-extensions/widget-order-bus.js +0 -34
|
@@ -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,17 +218,21 @@ 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
|
|
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('
|
|
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');
|
|
227
232
|
});
|
|
228
233
|
// ---------------------------------------------------------------------------
|
|
229
234
|
// Phase 3 review 2 — the ENCLOSING attach viewer (slash commands, pane tag,
|
|
230
|
-
// detach-reap, Alt+
|
|
235
|
+
// detach-reap, Alt+Shift+P mode-switch), not just the GraphOverlay/panel rendering
|
|
231
236
|
// the fix above already covers. Same adversarial shape: seed colliding LOCAL
|
|
232
237
|
// state for the remote node id `a`, then assert the remote path never touches
|
|
233
238
|
// it — and, symmetrically, that the LOCAL path is byte-for-byte unchanged
|
|
@@ -332,10 +337,10 @@ test('resolveInitialMode/requestModeSwitch never touch the local mode-switch rea
|
|
|
332
337
|
lastWrite = { cwd, nodeId, mode };
|
|
333
338
|
};
|
|
334
339
|
const remoteOk = requestModeSwitch('/tmp/cwd', 'a', true, 'spec', writeRequest);
|
|
335
|
-
assert.equal(remoteOk, false, 'remote Alt+
|
|
340
|
+
assert.equal(remoteOk, false, 'remote Alt+Shift+P must no-op (return false)');
|
|
336
341
|
assert.equal(writes, 0, 'remote must never write the local mode-switch request file');
|
|
337
342
|
const localOk = requestModeSwitch('/tmp/cwd', 'a', false, 'spec', writeRequest);
|
|
338
|
-
assert.equal(localOk, true, 'local Alt+
|
|
343
|
+
assert.equal(localOk, true, 'local Alt+Shift+P must still write through');
|
|
339
344
|
assert.equal(writes, 1, 'local must call the injected local writer exactly once');
|
|
340
345
|
assert.deepEqual(lastWrite, { cwd: '/tmp/cwd', nodeId: 'a', mode: 'spec' });
|
|
341
346
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -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
|
+
});
|
|
@@ -1996,7 +1996,7 @@ const SYS_SETUP_OUTPUT = [
|
|
|
1996
1996
|
'',
|
|
1997
1997
|
'Packages:',
|
|
1998
1998
|
'- pi-crtr-extensions — crtr companion extensions. (installed)',
|
|
1999
|
-
'- pi-mode-switch — Alt+
|
|
1999
|
+
'- pi-mode-switch — Alt+Shift+P mode cycling. (installed)',
|
|
2000
2000
|
'- pi-claude-oauth-adapter — OAuth adapter. (not installed)',
|
|
2001
2001
|
'',
|
|
2002
2002
|
'System deps:',
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
import { ensureRenderer, isRendererReady } from '@crouton-kit/humanloop';
|
|
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;
|
|
12
|
+
const APPLET_CONCIERGE_FLOW = `\`\`\`mermaid
|
|
13
|
+
flowchart LR
|
|
14
|
+
U[User] --> C[Applet concierge]
|
|
15
|
+
C --> B[Applet-builder developer node<br/>cwd = app repo]
|
|
16
|
+
B --> D[Platform deploy command]
|
|
17
|
+
D --> B
|
|
18
|
+
B --> C
|
|
19
|
+
C --> U
|
|
20
|
+
\`\`\``;
|
|
21
|
+
test('renders the applet concierge flowchart after provisioning termrender', { skip: SKIP }, () => {
|
|
22
|
+
const rendered = transformMermaidFences(APPLET_CONCIERGE_FLOW, 200);
|
|
23
|
+
assert.match(rendered, /Applet-builder/);
|
|
24
|
+
assert.match(rendered, /cwd = app repo/);
|
|
25
|
+
assert.match(rendered, /Platform deploy/);
|
|
26
|
+
assert.match(rendered, /[\u2500-\u259F\u25A0-\u25FF]/);
|
|
27
|
+
assert.doesNotMatch(rendered, /```mermaid/);
|
|
28
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// oauth-dialog-lifecycle.test.ts — review mrf68z4h (OAuth dialog lifecycle).
|
|
2
|
+
//
|
|
3
|
+
// Two attach-viewer regressions, proven against a REAL InputController built from
|
|
4
|
+
// minimal fakes:
|
|
5
|
+
//
|
|
6
|
+
// A. An unrelated extension `notify` must NOT tear down a blocking dialog whose
|
|
7
|
+
// broker request is still pending. (The old code dismissed the active dialog
|
|
8
|
+
// on ANY notify — leaving the request unanswered in the broker and the user
|
|
9
|
+
// with no way to answer it.)
|
|
10
|
+
// B. A broker-driven `extension_ui_dismiss` (the correlated cleanup emitted when
|
|
11
|
+
// a dialog's request is aborted/timed out out-of-band) tears down ONLY the
|
|
12
|
+
// overlay whose id matches, never an unrelated one.
|
|
13
|
+
import { test } from 'node:test';
|
|
14
|
+
import assert from 'node:assert/strict';
|
|
15
|
+
import { initTheme } from '@earendil-works/pi-coding-agent';
|
|
16
|
+
import { InputController } from '../input-controller.js';
|
|
17
|
+
// pi's ExtensionInputComponent reads the process-global theme in its constructor
|
|
18
|
+
// (the broker calls initTheme() at boot); initialize it once so renderDialog can
|
|
19
|
+
// build a real dialog component here.
|
|
20
|
+
initTheme(undefined, false);
|
|
21
|
+
function fakeEditor() {
|
|
22
|
+
const actions = new Map();
|
|
23
|
+
const obj = {
|
|
24
|
+
onSubmit: undefined,
|
|
25
|
+
onEscape: undefined,
|
|
26
|
+
onPasteImage: undefined,
|
|
27
|
+
onAction: (name, cb) => { actions.set(name, cb); },
|
|
28
|
+
getText: () => '',
|
|
29
|
+
setText: () => { },
|
|
30
|
+
addToHistory: () => { },
|
|
31
|
+
insertTextAtCursor: () => { },
|
|
32
|
+
};
|
|
33
|
+
return { obj };
|
|
34
|
+
}
|
|
35
|
+
/** A TUI fake whose `showOverlay` returns a handle whose `hide()` is observable,
|
|
36
|
+
* so a test can tell whether a dialog was actually torn down. */
|
|
37
|
+
function fakeTui() {
|
|
38
|
+
const hides = [];
|
|
39
|
+
let seq = 0;
|
|
40
|
+
const tui = {
|
|
41
|
+
requestRender: () => { },
|
|
42
|
+
setFocus: () => { },
|
|
43
|
+
showOverlay: () => {
|
|
44
|
+
const label = `overlay-${seq++}`;
|
|
45
|
+
return { hide: () => hides.push(label), label };
|
|
46
|
+
},
|
|
47
|
+
start: () => { },
|
|
48
|
+
stop: () => { },
|
|
49
|
+
};
|
|
50
|
+
return { tui, hides };
|
|
51
|
+
}
|
|
52
|
+
function buildController() {
|
|
53
|
+
const notices = [];
|
|
54
|
+
const editor = fakeEditor();
|
|
55
|
+
const { tui, hides } = fakeTui();
|
|
56
|
+
const ic = new InputController(tui, editor.obj, {}, {
|
|
57
|
+
onCommand: () => { },
|
|
58
|
+
onDialogResponse: () => { },
|
|
59
|
+
onNotice: (m) => notices.push(m),
|
|
60
|
+
});
|
|
61
|
+
return { ic, notices, hides };
|
|
62
|
+
}
|
|
63
|
+
const inputDialog = (id) => ({
|
|
64
|
+
type: 'extension_ui_request',
|
|
65
|
+
id,
|
|
66
|
+
method: 'input',
|
|
67
|
+
title: 'Paste the redirect URL or code',
|
|
68
|
+
placeholder: 'http://localhost/callback?...',
|
|
69
|
+
});
|
|
70
|
+
const notify = (message) => ({
|
|
71
|
+
type: 'extension_ui_request',
|
|
72
|
+
id: 'notify-1',
|
|
73
|
+
method: 'notify',
|
|
74
|
+
message,
|
|
75
|
+
notifyType: 'info',
|
|
76
|
+
});
|
|
77
|
+
test('an unrelated notify does NOT dismiss an active blocking dialog (its broker request stays live)', () => {
|
|
78
|
+
const { ic, notices, hides } = buildController();
|
|
79
|
+
ic.attachDialog(inputDialog('oauth-manual-1'));
|
|
80
|
+
assert.equal(hides.length, 0, 'the dialog overlay is up, not torn down');
|
|
81
|
+
// An unrelated extension fires a notify while the blocking dialog is showing.
|
|
82
|
+
ic.attachDialog(notify('Some unrelated background progress'));
|
|
83
|
+
assert.equal(hides.length, 0, 'the blocking dialog must remain on screen — a notify is NOT a dismissal signal');
|
|
84
|
+
assert.ok(notices.some((n) => /unrelated background progress/i.test(n)), `the notify still shows as a notice line, got: ${JSON.stringify(notices)}`);
|
|
85
|
+
});
|
|
86
|
+
test('extension_ui_dismiss tears down ONLY the overlay whose request id matches', () => {
|
|
87
|
+
const { ic, hides } = buildController();
|
|
88
|
+
ic.attachDialog(inputDialog('oauth-manual-1'));
|
|
89
|
+
assert.equal(hides.length, 0);
|
|
90
|
+
// A dismiss for a DIFFERENT request id must be a no-op.
|
|
91
|
+
ic.dismissDialog('some-other-request');
|
|
92
|
+
assert.equal(hides.length, 0, 'a non-matching dismiss must not tear down the current dialog');
|
|
93
|
+
// The correlated dismiss for THIS dialog's id tears it down.
|
|
94
|
+
ic.dismissDialog('oauth-manual-1');
|
|
95
|
+
assert.equal(hides.length, 1, 'the matching correlated dismiss tears down exactly the current overlay');
|
|
96
|
+
// Idempotent: a repeat dismiss (or a late one after teardown) is inert.
|
|
97
|
+
ic.dismissDialog('oauth-manual-1');
|
|
98
|
+
assert.equal(hides.length, 1, 'a repeat dismiss must not double-tear-down');
|
|
99
|
+
});
|
|
100
|
+
test('a new blocking dialog still supersedes the previous one (control-handoff re-route unaffected)', () => {
|
|
101
|
+
const { ic, hides } = buildController();
|
|
102
|
+
ic.attachDialog(inputDialog('dialog-a'));
|
|
103
|
+
ic.attachDialog(inputDialog('dialog-b'));
|
|
104
|
+
assert.equal(hides.length, 1, 'the first dialog is superseded (dismissed) when a second blocking dialog arrives');
|
|
105
|
+
// Now a correlated dismiss for the OLD id must not touch the current one.
|
|
106
|
+
ic.dismissDialog('dialog-a');
|
|
107
|
+
assert.equal(hides.length, 1, 'a stale dismiss for the superseded dialog does not tear down the current one');
|
|
108
|
+
ic.dismissDialog('dialog-b');
|
|
109
|
+
assert.equal(hides.length, 2, 'the current dialog is torn down by its own correlated dismiss');
|
|
110
|
+
});
|
|
@@ -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
|
|
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 {};
|