@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
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// The scope ancestor walk (`collectAncestorScopeRoots` → `findProjectScopeRoots`)
|
|
2
|
+
// must never climb at or above `homedir()`. When HOME is relocated and nested
|
|
3
|
+
// under a directory tree that itself contains a `.crouter/` (a mock-VM home
|
|
4
|
+
// under the real host home, or any relocated-HOME sandbox), the unfenced walk
|
|
5
|
+
// climbs ABOVE homedir() and sweeps in the above-HOME `.crouter` as a bogus
|
|
6
|
+
// "project ancestor" — leaking that host's entire user-global memory store into
|
|
7
|
+
// every nested-HOME node's assembled knowledge/preference catalog. This test
|
|
8
|
+
// pins the fence: an ancestor `.crouter/` sitting ABOVE the relocated HOME must
|
|
9
|
+
// NOT be collected as a project scope root, while a legitimate project
|
|
10
|
+
// `.crouter/` BETWEEN the cwd and HOME still is.
|
|
11
|
+
//
|
|
12
|
+
// Mirrors src/core/__tests__/on-read-crouter-home-fence.test.ts.
|
|
13
|
+
//
|
|
14
|
+
// Run: node --import tsx/esm --test src/core/__tests__/scope-crouter-home-fence.test.ts
|
|
15
|
+
import { test, before, beforeEach, after } from 'node:test';
|
|
16
|
+
import assert from 'node:assert/strict';
|
|
17
|
+
import { mkdtempSync, mkdirSync, rmSync } from 'node:fs';
|
|
18
|
+
import { tmpdir } from 'node:os';
|
|
19
|
+
import { join } from 'node:path';
|
|
20
|
+
import { findProjectScopeRoots, resetScopeCache } from '../scope.js';
|
|
21
|
+
let sandbox;
|
|
22
|
+
let prevHomeEnv;
|
|
23
|
+
before(() => {
|
|
24
|
+
prevHomeEnv = process.env['HOME'];
|
|
25
|
+
});
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
resetScopeCache();
|
|
28
|
+
// A sandbox that CONTAINS the relocated HOME, so the ancestor walk from a cwd
|
|
29
|
+
// under HOME can (without the fence) climb above HOME into the sandbox.
|
|
30
|
+
sandbox = mkdtempSync(join(tmpdir(), 'crtr-scope-fence-'));
|
|
31
|
+
});
|
|
32
|
+
after(() => {
|
|
33
|
+
resetScopeCache();
|
|
34
|
+
rmSync(sandbox, { recursive: true, force: true });
|
|
35
|
+
if (prevHomeEnv === undefined)
|
|
36
|
+
delete process.env['HOME'];
|
|
37
|
+
else
|
|
38
|
+
process.env['HOME'] = prevHomeEnv;
|
|
39
|
+
});
|
|
40
|
+
test('an ancestor .crouter ABOVE a relocated HOME is not collected as a project scope root', () => {
|
|
41
|
+
// Simulated host user-global store, sitting ABOVE the relocated home.
|
|
42
|
+
const hostUserRoot = join(sandbox, '.crouter');
|
|
43
|
+
mkdirSync(join(hostUserRoot, 'memory'), { recursive: true });
|
|
44
|
+
// The relocated HOME, nested one level under the sandbox.
|
|
45
|
+
const home = join(sandbox, 'vm-home');
|
|
46
|
+
process.env['HOME'] = home;
|
|
47
|
+
// A legitimate project `.crouter/` between the node cwd and HOME.
|
|
48
|
+
const projectDir = join(home, 'work', 'proj');
|
|
49
|
+
const projectRoot = join(projectDir, '.crouter');
|
|
50
|
+
mkdirSync(join(projectRoot, 'memory'), { recursive: true });
|
|
51
|
+
resetScopeCache();
|
|
52
|
+
const roots = findProjectScopeRoots(projectDir, null);
|
|
53
|
+
assert.ok(!roots.includes(hostUserRoot), `above-HOME .crouter must NOT be collected as a project ancestor; got: ${JSON.stringify(roots)}`);
|
|
54
|
+
assert.ok(roots.includes(projectRoot), `legitimate project .crouter between cwd and HOME must still be collected; got: ${JSON.stringify(roots)}`);
|
|
55
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Run with: node --import tsx/esm --test src/core/__tests__/stream-watchdog.test.ts
|
|
2
|
+
//
|
|
3
|
+
// Unit-level contract test for StreamWatchdog (see stream-watchdog.ts): a
|
|
4
|
+
// single-timer dead-man's switch the broker pokes while a turn awaits the
|
|
5
|
+
// provider (disarmed during tool execution) and clears at agent_end. This
|
|
6
|
+
// locks the class's arm/clear/single-fire/token contract directly — no broker
|
|
7
|
+
// or fake-engine harness needed (that integration surface is deliberately out
|
|
8
|
+
// of scope; see
|
|
9
|
+
// the roadmap's Fix 3 test-scope note).
|
|
10
|
+
import { test } from 'node:test';
|
|
11
|
+
import assert from 'node:assert/strict';
|
|
12
|
+
import { StreamWatchdog } from '../runtime/stream-watchdog.js';
|
|
13
|
+
const MS = 30;
|
|
14
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
15
|
+
test('poke() arms the watchdog, firing once after ms with no further poke', async () => {
|
|
16
|
+
let calls = 0;
|
|
17
|
+
const wd = new StreamWatchdog(() => calls++, MS);
|
|
18
|
+
wd.poke(0);
|
|
19
|
+
assert.equal(calls, 0, 'must not fire before ms elapses');
|
|
20
|
+
await sleep(MS * 2);
|
|
21
|
+
assert.equal(calls, 1, 'must fire exactly once after ms with no further poke');
|
|
22
|
+
});
|
|
23
|
+
test('repeated pokes rearm the deadline from the LAST poke, not the first', async () => {
|
|
24
|
+
// A cancel-without-reschedule bug (clear the old timer but never arm a new
|
|
25
|
+
// one) would also "never fire while poked", so proving no-fire mid-stream is
|
|
26
|
+
// not enough. Prove the deadline actually REARMS: no fire around the original
|
|
27
|
+
// deadline, then exactly one fire measured from the FINAL poke.
|
|
28
|
+
let calls = 0;
|
|
29
|
+
const wd = new StreamWatchdog(() => calls++, MS);
|
|
30
|
+
wd.poke(0);
|
|
31
|
+
// Re-poke well inside the window, several times, always resetting the clock.
|
|
32
|
+
// The last poke lands at ~t=2.5*MS, so the original t=MS deadline must pass
|
|
33
|
+
// with no fire.
|
|
34
|
+
for (let i = 0; i < 5; i++) {
|
|
35
|
+
await sleep(MS / 2);
|
|
36
|
+
wd.poke(0);
|
|
37
|
+
}
|
|
38
|
+
assert.equal(calls, 0, 'no fire around the original deadline — each poke rearmed it');
|
|
39
|
+
// From the final poke the timer must still be armed and fire exactly once.
|
|
40
|
+
await sleep(MS * 2);
|
|
41
|
+
assert.equal(calls, 1, 'exactly one fire, measured from the final poke — proves rearming');
|
|
42
|
+
});
|
|
43
|
+
test('clear() after poke prevents firing', async () => {
|
|
44
|
+
let calls = 0;
|
|
45
|
+
const wd = new StreamWatchdog(() => calls++, MS);
|
|
46
|
+
wd.poke(0);
|
|
47
|
+
wd.clear();
|
|
48
|
+
await sleep(MS * 2);
|
|
49
|
+
assert.equal(calls, 0, 'a cleared watchdog must never fire');
|
|
50
|
+
});
|
|
51
|
+
test('fires exactly once even if left armed well past ms', async () => {
|
|
52
|
+
let calls = 0;
|
|
53
|
+
const wd = new StreamWatchdog(() => calls++, MS);
|
|
54
|
+
wd.poke(0);
|
|
55
|
+
await sleep(MS * 5);
|
|
56
|
+
assert.equal(calls, 1, 'must fire only once, not repeatedly, past the deadline');
|
|
57
|
+
});
|
|
58
|
+
test('onStall receives the token of the arm that fired (generation binding)', async () => {
|
|
59
|
+
// The broker stamps each poke with the current session generation and no-ops
|
|
60
|
+
// a stall whose token is stale. This pins that the token travels with the
|
|
61
|
+
// arm: a re-poke supersedes the prior token, so a fire after a rebind carries
|
|
62
|
+
// the NEW generation — and an old timer that somehow survived would deliver
|
|
63
|
+
// the OLD one, which the broker guard rejects.
|
|
64
|
+
const tokens = [];
|
|
65
|
+
const wd = new StreamWatchdog((token) => tokens.push(token), MS);
|
|
66
|
+
wd.poke(1);
|
|
67
|
+
wd.poke(2); // supersedes generation 1 before it can fire
|
|
68
|
+
await sleep(MS * 2);
|
|
69
|
+
assert.deepEqual(tokens, [2], 'exactly one fire, carrying the most-recent arm\'s token');
|
|
70
|
+
});
|
|
@@ -18,8 +18,11 @@
|
|
|
18
18
|
import { test } from 'node:test';
|
|
19
19
|
import assert from 'node:assert/strict';
|
|
20
20
|
import { readFileSync, readdirSync, statSync } from 'node:fs';
|
|
21
|
+
import { spawnSync } from 'node:child_process';
|
|
21
22
|
import { fileURLToPath } from 'node:url';
|
|
22
23
|
import { dirname, join, basename } from 'node:path';
|
|
24
|
+
import { installTmuxBindings } from '../runtime/tmux-chrome.js';
|
|
25
|
+
import { resolveKeybindings } from '../keybindings/index.js';
|
|
23
26
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
24
27
|
const SRC_ROOT = join(__dirname, '..', '..'); // .../src
|
|
25
28
|
const TMUX_TS = join(SRC_ROOT, 'core', 'runtime', 'tmux.ts');
|
|
@@ -113,3 +116,72 @@ test('§5.1 lint: only placement.ts / tmux-chrome.ts import the tmux driver', ()
|
|
|
113
116
|
.map((f) => f.slice(SRC_ROOT.length + 1));
|
|
114
117
|
assert.deepEqual(offenders, [], `modules importing tmux.ts directly: ${offenders.join(', ')}`);
|
|
115
118
|
});
|
|
119
|
+
function isolatedTmux(socket, args) {
|
|
120
|
+
const result = spawnSync('tmux', ['-S', socket, ...args], { encoding: 'utf8' });
|
|
121
|
+
return { status: result.status, stdout: result.stdout ?? '', stderr: result.stderr ?? '' };
|
|
122
|
+
}
|
|
123
|
+
function tmuxBinding(socket, table, key) {
|
|
124
|
+
const result = isolatedTmux(socket, ['list-keys', '-T', table]);
|
|
125
|
+
if (result.status !== 0)
|
|
126
|
+
return null;
|
|
127
|
+
return result.stdout.split('\n').find((line) => {
|
|
128
|
+
const match = line.match(/^bind-key\s+(?:-r\s+)?-T\s+\S+\s+(\S+)\s+/);
|
|
129
|
+
return match?.[1] === key;
|
|
130
|
+
}) ?? null;
|
|
131
|
+
}
|
|
132
|
+
test('tmux installer remaps owned pairs and preserves occupied user bindings', (t) => {
|
|
133
|
+
if (spawnSync('tmux', ['-V']).status !== 0) {
|
|
134
|
+
t.skip('tmux is unavailable');
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const name = `crtr-keybindings-${process.pid}-${Date.now()}`;
|
|
138
|
+
const boot = spawnSync('tmux', ['-L', name, 'new-session', '-d', '-s', 'test'], { encoding: 'utf8' });
|
|
139
|
+
assert.equal(boot.status, 0, boot.stderr);
|
|
140
|
+
const socketResult = spawnSync('tmux', ['-L', name, 'display-message', '-p', '#{socket_path}'], { encoding: 'utf8' });
|
|
141
|
+
const socket = socketResult.stdout.trim();
|
|
142
|
+
assert.notEqual(socket, '');
|
|
143
|
+
const priorTmux = process.env['TMUX'];
|
|
144
|
+
process.env['TMUX'] = `${socket},1,0`;
|
|
145
|
+
try {
|
|
146
|
+
isolatedTmux(socket, ['bind-key', '-T', 'root', 'M-]', 'run-shell', `crtr node cycle --dir next --pane '#{pane_id}' >/dev/null 2>&1`]);
|
|
147
|
+
const first = installTmuxBindings(resolveKeybindings());
|
|
148
|
+
assert.equal(first.ok, true, JSON.stringify(first.diagnostics));
|
|
149
|
+
assert.ok(first.installed.some((pair) => pair.table === 'root' && pair.key === 'M-]'));
|
|
150
|
+
assert.match(tmuxBinding(socket, 'root', 'M-c') ?? '', /display-menu/);
|
|
151
|
+
assert.match(tmuxBinding(socket, 'root', 'M-v') ?? '', /switch-client/);
|
|
152
|
+
const remapped = installTmuxBindings(resolveKeybindings({
|
|
153
|
+
'crtr.tmux.menu.open': ['alt+shift+z'],
|
|
154
|
+
'crtr.tmux.menu.graph': ['shift+z'],
|
|
155
|
+
'crtr.tmux.node.next': [],
|
|
156
|
+
'crtr.tmux.view.next': ['alt+z > n'],
|
|
157
|
+
'crtr.tmux.view.previous': ['alt+z > p'],
|
|
158
|
+
}));
|
|
159
|
+
assert.equal(remapped.ok, true, JSON.stringify(remapped.diagnostics));
|
|
160
|
+
assert.equal(tmuxBinding(socket, 'root', 'M-c'), null);
|
|
161
|
+
assert.equal(tmuxBinding(socket, 'root', 'M-]'), null);
|
|
162
|
+
const menu = tmuxBinding(socket, 'root', 'M-Z') ?? '';
|
|
163
|
+
assert.match(menu, /graph view/);
|
|
164
|
+
assert.match(menu, /Z/);
|
|
165
|
+
const prefix = tmuxBinding(socket, 'root', 'M-z') ?? '';
|
|
166
|
+
assert.match(prefix, /switch-client/);
|
|
167
|
+
const again = installTmuxBindings(resolveKeybindings({
|
|
168
|
+
'crtr.tmux.menu.open': ['alt+shift+y'],
|
|
169
|
+
'crtr.tmux.node.next': [],
|
|
170
|
+
'crtr.tmux.view.next': ['alt+z > n'],
|
|
171
|
+
'crtr.tmux.view.previous': ['alt+z > p'],
|
|
172
|
+
}));
|
|
173
|
+
assert.equal(again.ok, true, JSON.stringify(again.diagnostics));
|
|
174
|
+
isolatedTmux(socket, ['bind-key', '-T', 'root', 'M-Z', 'display-message', 'user-owned']);
|
|
175
|
+
const occupied = installTmuxBindings(resolveKeybindings({ 'crtr.tmux.menu.open': ['alt+shift+z'] }));
|
|
176
|
+
assert.equal(occupied.ok, true);
|
|
177
|
+
assert.ok(occupied.diagnostics.some((diagnostic) => diagnostic.code === 'occupied' && diagnostic.bindingId === 'crtr.tmux.menu.open'));
|
|
178
|
+
assert.match(tmuxBinding(socket, 'root', 'M-Z') ?? '', /user-owned/);
|
|
179
|
+
}
|
|
180
|
+
finally {
|
|
181
|
+
if (priorTmux === undefined)
|
|
182
|
+
delete process.env['TMUX'];
|
|
183
|
+
else
|
|
184
|
+
process.env['TMUX'] = priorTmux;
|
|
185
|
+
isolatedTmux(socket, ['kill-server']);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
@@ -108,20 +108,38 @@ test('pruneNode: absent id is a no-op', () => {
|
|
|
108
108
|
assert.equal(t.nodes.size, before);
|
|
109
109
|
});
|
|
110
110
|
// ── tab predicates ───────────────────────────────────────────────────────────
|
|
111
|
-
test('tabPredicate: All / Live / Dormant /
|
|
112
|
-
assert.deepEqual([...TABS], ['All', 'Live', 'Dormant', '
|
|
111
|
+
test('tabPredicate: All / Live / Dormant / Attention / Pinned', () => {
|
|
112
|
+
assert.deepEqual([...TABS], ['All', 'Live', 'Dormant', 'Attention', 'Pinned']);
|
|
113
113
|
const active = ROWS[0]; // root1 active
|
|
114
114
|
const idle = ROWS[1]; // child-a idle
|
|
115
115
|
const done = ROWS[2]; // grand-x done
|
|
116
|
-
const
|
|
116
|
+
const attention = ROWS[3]; // child-b active asks 2
|
|
117
117
|
assert.equal(tabPredicate('All', done), true);
|
|
118
118
|
assert.equal(tabPredicate('Live', active), true);
|
|
119
119
|
assert.equal(tabPredicate('Live', idle), true);
|
|
120
120
|
assert.equal(tabPredicate('Live', done), false);
|
|
121
121
|
assert.equal(tabPredicate('Dormant', done), true);
|
|
122
122
|
assert.equal(tabPredicate('Dormant', active), false);
|
|
123
|
-
|
|
124
|
-
assert.equal(tabPredicate('
|
|
123
|
+
// Attention = pending asks OR a fault.
|
|
124
|
+
assert.equal(tabPredicate('Attention', attention), true);
|
|
125
|
+
assert.equal(tabPredicate('Attention', active), false);
|
|
126
|
+
assert.equal(tabPredicate('Attention', { ...active, asks: 0, hanging: { kind: 'provider' } }), true);
|
|
127
|
+
// Pinned reads the isPinned arg, not the row.
|
|
128
|
+
assert.equal(tabPredicate('Pinned', active), false);
|
|
129
|
+
assert.equal(tabPredicate('Pinned', active, true), true);
|
|
130
|
+
});
|
|
131
|
+
// ── flatten: pins ────────────────────────────────────────────────────────────
|
|
132
|
+
test('flatten: Pinned tab shows only pinned rows', () => {
|
|
133
|
+
const pinned = new Set(['child-c']);
|
|
134
|
+
const v = flatten(tree(), { collapsed: new Set(), tab: 'Pinned', query: '', pinned });
|
|
135
|
+
// child-c is directly matched; root2 rides along as its ancestor (context).
|
|
136
|
+
const matched = v.filter((r) => r.matched).map((r) => r.id);
|
|
137
|
+
assert.deepEqual(matched, ['child-c']);
|
|
138
|
+
});
|
|
139
|
+
test('flatten: pinned rows float to the top in a flat sort', () => {
|
|
140
|
+
const pinned = new Set(['lonely']); // an idle straggler that would otherwise sort late
|
|
141
|
+
const v = flatten(tree(), { collapsed: new Set(), tab: 'All', query: '', sort: 'recency', pinned });
|
|
142
|
+
assert.equal(v[0].id, 'lonely');
|
|
125
143
|
});
|
|
126
144
|
// ── flatten: collapse / expand ───────────────────────────────────────────────
|
|
127
145
|
const allCollapsed = () => new Set(['root1', 'child-a', 'root2']); // every node with children
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { type CanvasSource } from '../source.js';
|
|
2
2
|
import type { RemoteEventStream } from '../remote-event-stream.js';
|
|
3
3
|
import { type CoalescedRebuilder } from './rebuild-coalescer.js';
|
|
4
|
+
import { type BindingId, type BindingResolution, type RawTerminalInput } from '../../keybindings/index.js';
|
|
5
|
+
declare const BROWSE_NAV_ACTIONS: readonly ["crtr.browse.quit", "crtr.browse.down", "crtr.browse.up", "crtr.browse.first", "crtr.browse.last", "crtr.browse.expand-or-child", "crtr.browse.collapse-or-parent", "crtr.browse.toggle-fold", "crtr.browse.tab-next", "crtr.browse.tab-previous", "crtr.browse.tab.1", "crtr.browse.tab.2", "crtr.browse.tab.3", "crtr.browse.tab.4", "crtr.browse.tab.5", "crtr.browse.close-subtree", "crtr.browse.sort", "crtr.browse.scope", "crtr.browse.residents", "crtr.browse.preview", "crtr.browse.search", "crtr.browse.revive-now", "crtr.browse.focus"];
|
|
6
|
+
declare const BROWSE_SEARCH_ACTIONS: readonly ["crtr.browse.search.cancel", "crtr.browse.search.commit", "crtr.browse.search.delete", "crtr.browse.search.quit"];
|
|
7
|
+
export declare function resolveBrowseNavAction(bindings: BindingResolution<BindingId>, raw: RawTerminalInput): (typeof BROWSE_NAV_ACTIONS)[number] | null;
|
|
8
|
+
export declare function resolveBrowseSearchAction(bindings: BindingResolution<BindingId>, raw: RawTerminalInput): (typeof BROWSE_SEARCH_ACTIONS)[number] | null;
|
|
4
9
|
/** Wires a RemoteEventStream's `nodes`/`inbox` events through the coalescer
|
|
5
10
|
* (`rebuild-coalescer.ts`) so a burst of SSE events collapses to ONE rebuild
|
|
6
11
|
* round, never one N+1 remote-subprocess rebuild per event. Exported (rather
|
|
@@ -18,3 +23,4 @@ export declare function runBrowse(opts?: {
|
|
|
18
23
|
source?: CanvasSource;
|
|
19
24
|
eventStream?: RemoteEventStream;
|
|
20
25
|
}): Promise<void>;
|
|
26
|
+
export {};
|