@crouton-kit/crouter 0.3.81 → 0.3.83
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/dist/builtin-pi-packages/pi-mode-switch/README.md +3 -3
- package/dist/clients/attach/__tests__/attach-chrome-remote.test.js +12 -4
- package/dist/clients/attach/__tests__/attach-keybindings.test.js +7 -3
- package/dist/clients/attach/__tests__/chat-view-snapshot-ordering.test.js +104 -0
- package/dist/clients/attach/__tests__/crtr-output-render.test.js +16 -44
- package/dist/clients/attach/attach-cmd.d.ts +1 -2
- package/dist/clients/attach/attach-cmd.js +960 -961
- package/dist/clients/attach/attach-def.d.ts +2 -0
- package/dist/clients/attach/attach-def.js +93 -0
- package/dist/clients/attach/canvas-panels.d.ts +10 -3
- package/dist/clients/attach/canvas-panels.js +42 -16
- package/dist/clients/attach/chat-view.d.ts +20 -1
- package/dist/clients/attach/chat-view.js +85 -2
- package/dist/commands/__tests__/human.test.js +7 -22
- package/dist/commands/attention.js +31 -30
- package/dist/commands/human/prompts.js +51 -38
- package/dist/commands/human/queue.d.ts +1 -28
- package/dist/commands/human/queue.js +156 -520
- package/dist/commands/human/shared.d.ts +28 -68
- package/dist/commands/human/shared.js +37 -120
- package/dist/commands/human.js +13 -17
- package/dist/commands/node.js +4 -2
- package/dist/commands/surface.js +1 -1
- package/dist/commands/sys/__tests__/setup-core.test.js +1 -1
- package/dist/commands/sys/__tests__/setup-front-door.test.js +98 -0
- package/dist/commands/sys/setup-core.d.ts +22 -0
- package/dist/commands/sys/setup-core.js +62 -0
- package/dist/commands/sys/setup.js +8 -1
- package/dist/core/__tests__/human-deliver-e2e.test.js +95 -0
- package/dist/core/__tests__/human-deliver.test.js +332 -0
- package/dist/core/__tests__/human-node-not-supervised.test.js +5 -4
- package/dist/core/__tests__/pid-identity-match.test.js +83 -0
- package/dist/core/canvas/__tests__/attention.test.d.ts +1 -0
- package/dist/core/canvas/__tests__/attention.test.js +126 -0
- package/dist/core/canvas/__tests__/remote-canvas-source.test.js +4 -4
- package/dist/core/canvas/__tests__/render-remote.test.js +1 -1
- package/dist/core/canvas/attention.d.ts +16 -14
- package/dist/core/canvas/attention.js +34 -29
- package/dist/core/canvas/boot.d.ts +1 -0
- package/dist/core/canvas/boot.js +1 -1
- package/dist/core/canvas/browse/app.js +3 -3
- package/dist/core/canvas/pid.d.ts +43 -17
- package/dist/core/canvas/pid.js +104 -35
- package/dist/core/canvas/remote-canvas-source.d.ts +1 -1
- package/dist/core/canvas/remote-canvas-source.js +4 -2
- package/dist/core/canvas/render.d.ts +5 -5
- package/dist/core/canvas/render.js +14 -14
- package/dist/core/canvas/source.d.ts +2 -2
- package/dist/core/canvas/source.js +3 -3
- package/dist/core/keybindings/__tests__/resolve.test.js +1 -1
- package/dist/core/keybindings/catalog.d.ts +2 -2
- package/dist/core/keybindings/catalog.js +3 -2
- package/dist/core/preview-registry.js +22 -29
- package/dist/core/profiles/select.js +4 -4
- package/dist/core/runtime/front-door.js +19 -6
- package/dist/core/runtime/package-health.d.ts +13 -0
- package/dist/core/runtime/package-health.js +18 -0
- package/dist/core/runtime/pi-vendored.js +15 -1
- package/dist/core/runtime/placement.d.ts +1 -10
- package/dist/core/runtime/placement.js +43 -26
- package/dist/core/runtime/resume.js +72 -4
- package/dist/core/runtime/session-list-cache.d.ts +22 -2
- package/dist/core/runtime/session-list-cache.js +92 -24
- package/dist/core/runtime/spawn.js +9 -0
- package/dist/core/spawn.d.ts +0 -59
- package/dist/core/spawn.js +5 -163
- package/dist/daemon/crtrd.js +7 -6
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/web-client/assets/index-DZdKS-FB.js +78 -0
- package/dist/web-client/index.html +1 -1
- package/dist/web-client/sw.js +1 -1
- package/package.json +2 -2
- package/dist/core/__tests__/full/dead-pane-regression.test.js +0 -154
- package/dist/core/__tests__/full/human-new-window-regression.test.js +0 -104
- package/dist/core/__tests__/full/review-render-pane-regression.test.js +0 -133
- package/dist/core/__tests__/human-stranded-deliver.test.js +0 -145
- package/dist/core/__tests__/human-surface-target.test.js +0 -98
- package/dist/web-client/assets/index--md2ylfi.js +0 -78
- /package/dist/{core/__tests__/full/dead-pane-regression.test.d.ts → clients/attach/__tests__/chat-view-snapshot-ordering.test.d.ts} +0 -0
- /package/dist/{core/__tests__/full/human-new-window-regression.test.d.ts → commands/sys/__tests__/setup-front-door.test.d.ts} +0 -0
- /package/dist/core/__tests__/{full/review-render-pane-regression.test.d.ts → human-deliver-e2e.test.d.ts} +0 -0
- /package/dist/core/__tests__/{human-stranded-deliver.test.d.ts → human-deliver.test.d.ts} +0 -0
- /package/dist/core/__tests__/{human-surface-target.test.d.ts → pid-identity-match.test.d.ts} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# pi-mode-switch
|
|
2
2
|
|
|
3
|
-
Cycle between operating modes with crouter's configurable **Cycle mode** binding (default **Alt+
|
|
3
|
+
Cycle between operating modes with crouter's configurable **Cycle mode** binding (default **Alt+Shift+P**) — the way Claude's Shift+Tab cycles plan / accept-edits. Whenever the mode changes, the new mode's operating instructions are injected invisibly before your next message.
|
|
4
4
|
|
|
5
5
|
## Modes
|
|
6
6
|
|
|
@@ -12,7 +12,7 @@ Cycle between operating modes with crouter's configurable **Cycle mode** binding
|
|
|
12
12
|
|
|
13
13
|
## Usage
|
|
14
14
|
|
|
15
|
-
- the configured `crtr.mode.cycle` binding (default `Alt+
|
|
15
|
+
- the configured `crtr.mode.cycle` binding (default `Alt+Shift+P`) — cycle `normal → spec → plan → normal`
|
|
16
16
|
- `/mode` — cycle without using the shortcut
|
|
17
17
|
- `/mode <normal\|spec\|plan>` — jump to a specific mode
|
|
18
18
|
- the agent can switch its own mode by running the bundled `bin/mode` CLI through its bash tool — e.g. `bash <plugin>/bin/mode normal` after a spec/plan is approved. The command prints that mode's guidance, so the text only enters context at the moment of the switch (no always-on tool)
|
|
@@ -33,4 +33,4 @@ The input badge is drawn by wrapping whatever editor is already installed (for e
|
|
|
33
33
|
|
|
34
34
|
This package ships inside crouter. `crtr sys setup` registers its built package path with pi; there is no separate repository or package to install.
|
|
35
35
|
|
|
36
|
-
Run `/reload` in pi after setup. The extension resolves `crtr.mode.cycle` from the sparse `keybindings` object in `~/.crouter/config.json`; missing overrides use Alt+
|
|
36
|
+
Run `/reload` in pi after setup. The extension resolves `crtr.mode.cycle` from the sparse `keybindings` object in `~/.crouter/config.json`; missing overrides use Alt+Shift+P, multiple gestures all register, and an empty list disables the shortcut. `/mode` remains available when the shortcut is disabled. Pi's stock actions and `~/.pi/agent/keybindings.json` remain untouched.
|
|
@@ -172,13 +172,21 @@ test('buildCanvasPanelLines suppresses every local-read adornment for a RemoteCa
|
|
|
172
172
|
// (see the CI-hang postmortem on rebuild-coalescer.test.ts).
|
|
173
173
|
t.after(() => server.close());
|
|
174
174
|
const source = new RemoteCanvasSource(server.url, 'tok');
|
|
175
|
-
//
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
175
|
+
// The stable family roster shown when attached to 'root' is root-first:
|
|
176
|
+
// the root row followed by its one live child 'a' (root has no non-human
|
|
177
|
+
// manager, so it is its own roster root). The poisoned local node id is 'a',
|
|
178
|
+
// so the load-bearing assertions must target the 'a' ROW specifically —
|
|
179
|
+
// `reportIds` maps each rendered row to its node id in render order.
|
|
180
|
+
const { reports, reportIds } = await buildCanvasPanelLines('root', {}, IDENTITY_PALETTE, source);
|
|
181
|
+
assert.deepEqual(reportIds, ['root', 'a'], 'roster is root-first: the attached root then its live child');
|
|
182
|
+
assert.equal(reports.length, 2);
|
|
183
|
+
const line = reports[reportIds.indexOf('a')];
|
|
179
184
|
assert.ok(!line.includes('1000k'), `local telemetry (999999 tokens) must not leak into a remote row: ${line}`);
|
|
180
185
|
assert.ok(!line.includes('POISON'), `poisoned local text must never appear in a remote row: ${line}`);
|
|
181
186
|
assert.ok(!line.includes('⚠'), `a local active fault must not surface as the hanging glyph on a remote row: ${line}`);
|
|
187
|
+
// The 'a' row is NOT the attached node ('root' is), so a legitimate
|
|
188
|
+
// you-are-here bar never applies to it — any BG_ATTACHED here could only
|
|
189
|
+
// come from the poisoned LOCAL focus/attach on id 'a', which must be ignored.
|
|
182
190
|
assert.ok(!line.includes(BG_ATTACHED), `a local focus/attach must not paint a remote row as attached: ${line}`);
|
|
183
191
|
});
|
|
184
192
|
test('GraphOverlay suppresses every local-read adornment for a RemoteCanvasSource AND makes Enter/m/x no-ops, even with conflicting local state for the same node id', async (t) => {
|
|
@@ -30,6 +30,7 @@ test('attach input dispatches only the current configured action', () => {
|
|
|
30
30
|
'crtr.attach.graph.toggle': [],
|
|
31
31
|
'crtr.mode.cycle': ['alt+x'],
|
|
32
32
|
'crtr.attach.model-ladder.next': ['alt+z'],
|
|
33
|
+
'crtr.attach.model-ladder.previous': ['alt+y'],
|
|
33
34
|
});
|
|
34
35
|
const calls = [];
|
|
35
36
|
const actions = {
|
|
@@ -38,15 +39,18 @@ test('attach input dispatches only the current configured action', () => {
|
|
|
38
39
|
'crtr.attach.graph.toggle': () => calls.push('graph'),
|
|
39
40
|
'crtr.mode.cycle': () => calls.push('mode'),
|
|
40
41
|
'crtr.attach.model-ladder.next': () => calls.push('ladder'),
|
|
42
|
+
'crtr.attach.model-ladder.previous': () => calls.push('ladder-back'),
|
|
41
43
|
};
|
|
42
44
|
assert.equal(dispatchAttachBinding(bindings, '\x18', actions), true);
|
|
43
45
|
assert.equal(dispatchAttachBinding(bindings, '\x04', actions), false, 'old detach must not reach the listener action');
|
|
44
46
|
assert.equal(dispatchAttachBinding(bindings, '\x1bg', actions), false, 'disabled graph must not reach the listener action');
|
|
45
47
|
assert.equal(dispatchAttachBinding(bindings, '\x1bx', actions), true);
|
|
46
|
-
assert.equal(dispatchAttachBinding(bindings, '\
|
|
48
|
+
assert.equal(dispatchAttachBinding(bindings, '\x1bP', actions), false, 'old mode key must not reach the listener action');
|
|
47
49
|
assert.equal(dispatchAttachBinding(bindings, '\x1bz', actions), true);
|
|
48
|
-
assert.equal(dispatchAttachBinding(bindings, '\
|
|
49
|
-
assert.
|
|
50
|
+
assert.equal(dispatchAttachBinding(bindings, '\x1bm', actions), false, 'old ladder key must not reach the listener action');
|
|
51
|
+
assert.equal(dispatchAttachBinding(bindings, '\x1by', actions), true);
|
|
52
|
+
assert.equal(dispatchAttachBinding(bindings, '\x1bM', actions), false, 'old ladder-back key must not reach the listener action');
|
|
53
|
+
assert.deepEqual(calls, ['detach', 'mode', 'ladder', 'ladder-back']);
|
|
50
54
|
assert.equal(detachHint(bindings), 'Ctrl+X', 'disabled clear-or-detach must fall back to enabled detach');
|
|
51
55
|
const noDetach = resolveKeybindings({ 'crtr.attach.detach': [], 'crtr.attach.clear-or-detach': [] });
|
|
52
56
|
assert.equal(detachHint(noDetach), undefined, 'a disabled pair must not render "Disabled to detach"');
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// Regression coverage for the single-writer snapshot ordering in ChatView
|
|
2
|
+
// (chat-view.ts). A large NON-streaming catch-up yields to the event loop
|
|
3
|
+
// mid-build so queued stdin drains; that yield window used to let later broker
|
|
4
|
+
// frames mutate a half-rebuilt transcript. Two races are pinned here:
|
|
5
|
+
//
|
|
6
|
+
// 1. Live turn frames arriving during a yielded welcome must be replayed
|
|
7
|
+
// AFTER the build finishes (not interleaved), and their streaming/volatility
|
|
8
|
+
// state must survive — the resuming build's stale non-streaming completion
|
|
9
|
+
// must not overwrite it.
|
|
10
|
+
// 2. A second welcome delivered while the first build is suspended must
|
|
11
|
+
// supersede it outright: the newest snapshot wins and the stale build
|
|
12
|
+
// abandons itself instead of appending into the replaced transcript.
|
|
13
|
+
import assert from 'node:assert/strict';
|
|
14
|
+
import test from 'node:test';
|
|
15
|
+
import { Container, TUI } from '@earendil-works/pi-tui';
|
|
16
|
+
import { AssistantMessageComponent, UserMessageComponent, } from '@earendil-works/pi-coding-agent';
|
|
17
|
+
import { ChatView } from '../chat-view.js';
|
|
18
|
+
/** A headless terminal so TUI.requestRender's scheduled writes are no-ops (the
|
|
19
|
+
* test only inspects component state, never real output). */
|
|
20
|
+
function fakeTerminal() {
|
|
21
|
+
return {
|
|
22
|
+
start() { },
|
|
23
|
+
stop() { },
|
|
24
|
+
async drainInput() { },
|
|
25
|
+
write() { },
|
|
26
|
+
get columns() { return 80; },
|
|
27
|
+
get rows() { return 24; },
|
|
28
|
+
get kittyProtocolActive() { return false; },
|
|
29
|
+
moveBy() { },
|
|
30
|
+
hideCursor() { },
|
|
31
|
+
showCursor() { },
|
|
32
|
+
clearLine() { },
|
|
33
|
+
clearFromCursor() { },
|
|
34
|
+
clearScreen() { },
|
|
35
|
+
setTitle() { },
|
|
36
|
+
setProgress() { },
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function makeChatView() {
|
|
40
|
+
const tui = new TUI(fakeTerminal());
|
|
41
|
+
const container = new Container();
|
|
42
|
+
const view = new ChatView(tui, container);
|
|
43
|
+
const internals = view;
|
|
44
|
+
return { view, historyChildren: () => internals.historyContainer.children, internals };
|
|
45
|
+
}
|
|
46
|
+
/** A non-streaming snapshot of `count` assistant text messages. Enough messages
|
|
47
|
+
* (>2× SNAPSHOT_YIELD_EVERY) to guarantee the build suspends at least once. */
|
|
48
|
+
function assistantSnapshot(count, tag) {
|
|
49
|
+
const messages = Array.from({ length: count }, (_, i) => ({
|
|
50
|
+
role: 'assistant',
|
|
51
|
+
content: [{ type: 'text', text: `${tag}-${i}` }],
|
|
52
|
+
stopReason: 'endTurn',
|
|
53
|
+
}));
|
|
54
|
+
return {
|
|
55
|
+
messages,
|
|
56
|
+
stats: {},
|
|
57
|
+
state: { isStreaming: false },
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const countOf = (children, ctor) => children.filter((c) => c instanceof ctor).length;
|
|
61
|
+
test('live turn frames arriving during a yielded welcome replay after the build and keep streaming state', async () => {
|
|
62
|
+
const { view, historyChildren, internals } = makeChatView();
|
|
63
|
+
// Start a large non-streaming catch-up: it appends the first chunk of
|
|
64
|
+
// components synchronously, then suspends at the first event-loop yield.
|
|
65
|
+
const build = view.applySnapshot(assistantSnapshot(120, 'snap'));
|
|
66
|
+
assert.equal(internals.applyingSnapshot, true, 'build is in flight after the first synchronous chunk');
|
|
67
|
+
// Live turn frames arrive while the build is suspended — these must be
|
|
68
|
+
// deferred, not applied to the half-rebuilt view.
|
|
69
|
+
view.handleEvent({ type: 'agent_start' });
|
|
70
|
+
view.handleEvent({ type: 'message_start', message: { role: 'user', content: 'LIVE' } });
|
|
71
|
+
await build;
|
|
72
|
+
const children = historyChildren();
|
|
73
|
+
// All 120 snapshot assistant components are present and precede the one live
|
|
74
|
+
// user message that was deferred and replayed at the tail.
|
|
75
|
+
assert.equal(countOf(children, AssistantMessageComponent), 120, 'every snapshot message survived the build');
|
|
76
|
+
assert.equal(countOf(children, UserMessageComponent), 1, 'exactly the one deferred live user message landed');
|
|
77
|
+
const lastReal = [...children].reverse().find((c) => c instanceof UserMessageComponent || c instanceof AssistantMessageComponent);
|
|
78
|
+
assert.ok(lastReal instanceof UserMessageComponent, 'the live message is appended AFTER the whole snapshot, not interleaved');
|
|
79
|
+
// The replayed agent_start owns the final volatility/activity state — the
|
|
80
|
+
// resuming build's stale non-streaming completion did NOT overwrite it.
|
|
81
|
+
assert.equal(internals.applyingSnapshot, false, 'gate released after the build');
|
|
82
|
+
assert.equal(internals.historyContainer.volatile, true, 'live turn keeps history volatile (cache bypassed)');
|
|
83
|
+
assert.notEqual(internals.activityLoader, undefined, 'the working loader from the live agent_start is present');
|
|
84
|
+
// Cleanup: the live loader runs a spinner interval that would otherwise hold
|
|
85
|
+
// the event loop (and the test process) open forever.
|
|
86
|
+
internals.setActivity(undefined);
|
|
87
|
+
});
|
|
88
|
+
test('a second welcome delivered mid-build supersedes the first and the stale build abandons itself', async () => {
|
|
89
|
+
const { view, historyChildren, internals } = makeChatView();
|
|
90
|
+
// First large build suspends at its first yield.
|
|
91
|
+
const first = view.applySnapshot(assistantSnapshot(120, 'first'));
|
|
92
|
+
assert.equal(internals.snapshotGeneration, 1);
|
|
93
|
+
// A newer welcome arrives while the first is suspended — resets and rebuilds
|
|
94
|
+
// synchronously (small, no yield), bumping the generation.
|
|
95
|
+
const second = view.applySnapshot(assistantSnapshot(3, 'second'));
|
|
96
|
+
assert.equal(internals.snapshotGeneration, 2, 'the newer welcome opened a new generation');
|
|
97
|
+
await Promise.all([first, second]);
|
|
98
|
+
// Only the second snapshot's transcript remains: the stale first build saw the
|
|
99
|
+
// generation change after resuming and returned without appending its tail.
|
|
100
|
+
const children = historyChildren();
|
|
101
|
+
assert.equal(countOf(children, AssistantMessageComponent), 3, 'newest welcome wins; no interleaved stale tail');
|
|
102
|
+
assert.equal(internals.applyingSnapshot, false, 'gate released');
|
|
103
|
+
assert.equal(internals.historyContainer.volatile, false, 'second (non-streaming) snapshot set the final state');
|
|
104
|
+
});
|
|
@@ -1318,84 +1318,56 @@ test('crtr push urgent/final cards collapse to a one-line outcome + report basen
|
|
|
1318
1318
|
// ---------------------------------------------------------------------------
|
|
1319
1319
|
// Batch C — human/memory/pkg summarizers (preview-registry.ts "Batch C")
|
|
1320
1320
|
// ---------------------------------------------------------------------------
|
|
1321
|
-
|
|
1321
|
+
// Ask and review now share the single QUEUED_FOLLOW_UP road-sign (prompts.ts):
|
|
1322
|
+
// arrival is always just durable queue state, so there is no drained/delivered
|
|
1323
|
+
// fork — the card collapses to one posted line.
|
|
1324
|
+
const QUEUED_FOLLOW_UP = "Queued for the human. Their answer is pushed to your inbox and wakes you when they respond — never poll, verify it opened, or block. Press ⌥I (or run `hl inbox open` at a terminal) to see the queue.";
|
|
1325
|
+
const HUMAN_ASK_QUEUED_OUTPUT = [
|
|
1322
1326
|
'- job_id: mrASK0001',
|
|
1323
1327
|
'',
|
|
1324
1328
|
'**dir:** /Users/silas/Code/cli/crouter',
|
|
1325
1329
|
'',
|
|
1326
|
-
|
|
1330
|
+
`**follow_up:** ${QUEUED_FOLLOW_UP}`,
|
|
1327
1331
|
].join('\n');
|
|
1328
|
-
|
|
1329
|
-
'- job_id: mrASK0002',
|
|
1330
|
-
'',
|
|
1331
|
-
'**dir:** /Users/silas/Code/cli/crouter',
|
|
1332
|
-
'',
|
|
1333
|
-
'**follow_up:** Not in tmux: a human must drain it — run `crtr human inbox` (or re-run inside tmux). The answer then arrives in your inbox.',
|
|
1334
|
-
].join('\n');
|
|
1335
|
-
test('a crtr human ask card collapses to a posted/drained status + question teaser, not the raw fields', () => {
|
|
1332
|
+
test('a crtr human ask card collapses to a posted status + question teaser, not the raw fields', () => {
|
|
1336
1333
|
const def = createCrtrBashToolDefinition({ command: 'crtr human ask "Should we ship the preview rollout now?"' });
|
|
1337
1334
|
const collapsed = def
|
|
1338
|
-
.renderResult({ content: [{ type: 'text', text:
|
|
1335
|
+
.renderResult({ content: [{ type: 'text', text: HUMAN_ASK_QUEUED_OUTPUT }] }, { expanded: false }, theme, {})
|
|
1339
1336
|
.render(140)
|
|
1340
1337
|
.join('\n');
|
|
1341
1338
|
assert.match(collapsed, /<accent>ask posted → waiting on human<\/accent>/);
|
|
1342
1339
|
assert.match(collapsed, /<muted>Should we ship the preview rollout now\?<\/muted>/);
|
|
1343
1340
|
assert.doesNotMatch(collapsed, /- job_id:/);
|
|
1344
1341
|
const expanded = def
|
|
1345
|
-
.renderResult({ content: [{ type: 'text', text:
|
|
1342
|
+
.renderResult({ content: [{ type: 'text', text: HUMAN_ASK_QUEUED_OUTPUT }] }, { expanded: true }, theme, {})
|
|
1346
1343
|
.render(140)
|
|
1347
1344
|
.join('\n');
|
|
1348
1345
|
assert.match(expanded, /job_id: mrASK0001/);
|
|
1349
|
-
assert.match(expanded, /
|
|
1350
|
-
const drained = def
|
|
1351
|
-
.renderResult({ content: [{ type: 'text', text: HUMAN_ASK_DRAINED_OUTPUT }] }, { expanded: false }, theme, {})
|
|
1352
|
-
.render(140)
|
|
1353
|
-
.join('\n');
|
|
1354
|
-
assert.match(drained, /<warning>ask posted — drain via `human inbox`<\/warning>/);
|
|
1355
|
-
assert.match(drained, /<muted>Should we ship the preview rollout now\?<\/muted>/);
|
|
1356
|
-
const drainedExpanded = def
|
|
1357
|
-
.renderResult({ content: [{ type: 'text', text: HUMAN_ASK_DRAINED_OUTPUT }] }, { expanded: true }, theme, {})
|
|
1358
|
-
.render(140)
|
|
1359
|
-
.join('\n');
|
|
1360
|
-
assert.match(drainedExpanded, /Not in tmux: a human must drain it/);
|
|
1346
|
+
assert.match(expanded, /wakes you when they respond/);
|
|
1361
1347
|
});
|
|
1362
|
-
const
|
|
1348
|
+
const HUMAN_REVIEW_QUEUED_OUTPUT = [
|
|
1363
1349
|
'- job_id: mrREV0001',
|
|
1364
1350
|
'',
|
|
1365
1351
|
'**dir:** /Users/silas/Code/cli/crouter',
|
|
1366
1352
|
'',
|
|
1367
1353
|
'**output:** /Users/silas/Code/cli/crouter/spec.review.md',
|
|
1368
1354
|
'',
|
|
1369
|
-
|
|
1355
|
+
`**follow_up:** ${QUEUED_FOLLOW_UP}`,
|
|
1370
1356
|
].join('\n');
|
|
1371
|
-
|
|
1372
|
-
'- job_id: mrREV0002',
|
|
1373
|
-
'',
|
|
1374
|
-
'**dir:** /Users/silas/Code/cli/crouter',
|
|
1375
|
-
'',
|
|
1376
|
-
'**output:** /Users/silas/Code/cli/crouter/spec.review.md',
|
|
1377
|
-
'',
|
|
1378
|
-
'**follow_up:** Not in tmux: a human must drain it — run `crtr human inbox` (or re-run inside tmux). The answer then arrives in your inbox.',
|
|
1379
|
-
].join('\n');
|
|
1380
|
-
test('a crtr human review card collapses to a posted/drained status + file basename, not the raw fields', () => {
|
|
1357
|
+
test('a crtr human review card collapses to a posted status + file basename, not the raw fields', () => {
|
|
1381
1358
|
const def = createCrtrBashToolDefinition({ command: 'crtr human review /Users/silas/Code/cli/crouter/spec.md' });
|
|
1382
1359
|
const collapsed = def
|
|
1383
|
-
.renderResult({ content: [{ type: 'text', text:
|
|
1360
|
+
.renderResult({ content: [{ type: 'text', text: HUMAN_REVIEW_QUEUED_OUTPUT }] }, { expanded: false }, theme, {})
|
|
1384
1361
|
.render(140)
|
|
1385
1362
|
.join('\n');
|
|
1386
1363
|
assert.match(collapsed, /<accent>review posted: spec\.md → waiting on human<\/accent>/);
|
|
1387
1364
|
assert.doesNotMatch(collapsed, /- job_id:/);
|
|
1388
1365
|
const expanded = def
|
|
1389
|
-
.renderResult({ content: [{ type: 'text', text:
|
|
1366
|
+
.renderResult({ content: [{ type: 'text', text: HUMAN_REVIEW_QUEUED_OUTPUT }] }, { expanded: true }, theme, {})
|
|
1390
1367
|
.render(140)
|
|
1391
1368
|
.join('\n');
|
|
1392
1369
|
assert.match(expanded, /job_id: mrREV0001/);
|
|
1393
|
-
assert.match(expanded, /
|
|
1394
|
-
const drained = def
|
|
1395
|
-
.renderResult({ content: [{ type: 'text', text: HUMAN_REVIEW_DRAINED_OUTPUT }] }, { expanded: false }, theme, {})
|
|
1396
|
-
.render(140)
|
|
1397
|
-
.join('\n');
|
|
1398
|
-
assert.match(drained, /<warning>review posted: spec\.md — drain via `human inbox`<\/warning>/);
|
|
1370
|
+
assert.match(expanded, /wakes you when they respond/);
|
|
1399
1371
|
});
|
|
1400
1372
|
const HUMAN_LIST_OUTPUT = [
|
|
1401
1373
|
'- total: 2',
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { CombinedAutocompleteProvider } from '@earendil-works/pi-tui';
|
|
2
|
-
import type { BranchDef } from '../../core/command.js';
|
|
3
2
|
import type { BrokerSnapshot, ClientRole } from '../../core/runtime/broker-protocol.js';
|
|
4
3
|
import { type BindingId, type BindingResolution } from '../../core/keybindings/index.js';
|
|
5
4
|
import { type AttachPalette } from './config-load.js';
|
|
@@ -52,5 +51,5 @@ export declare function buildAttachAutocompleteProvider(cwd: string, fdPath: str
|
|
|
52
51
|
name: string;
|
|
53
52
|
description?: string;
|
|
54
53
|
}>, cwd: string, fdPath: string | null) => CombinedAutocompleteProvider): CombinedAutocompleteProvider;
|
|
55
|
-
export declare function
|
|
54
|
+
export declare function runAttach(nodeId: string, observer: boolean, canvasName?: string): Promise<void>;
|
|
56
55
|
export {};
|