@crouton-kit/crouter 0.3.68 → 0.3.78
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/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-pi-packages/pi-crtr-extensions/README.md +13 -34
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/provider-rotation.test.ts +639 -741
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +241 -58
- 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 +365 -94
- 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/cli.d.ts +1 -1
- package/dist/cli.js +1 -0
- package/dist/clients/attach/__tests__/attach-chrome-remote.test.js +11 -7
- 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 +20 -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 +1 -1
- package/dist/clients/attach/attach-cmd.js +733 -730
- package/dist/clients/attach/auth-pickers.d.ts +12 -0
- package/dist/clients/attach/auth-pickers.js +11 -0
- 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/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 +13 -17
- package/dist/clients/attach/slash-commands.d.ts +9 -0
- package/dist/clients/attach/slash-commands.js +128 -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/memory/lint.js +39 -5
- package/dist/commands/memory/write.js +1 -0
- package/dist/commands/node.js +12 -5
- package/dist/commands/profile/new.js +30 -5
- 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/config.js +18 -21
- package/dist/commands/sys/setup-core.d.ts +12 -1
- package/dist/commands/sys/setup-core.js +25 -3
- package/dist/commands/sys/setup.js +166 -20
- 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__/fault-classifier.test.js +15 -0
- package/dist/core/__tests__/full/broker-dialogs.test.js +7 -2
- package/dist/core/__tests__/full/broker-pane-resolution.test.js +9 -6
- package/dist/core/__tests__/full/cascade-close.test.js +5 -2
- package/dist/core/__tests__/full/dead-pane-regression.test.js +6 -3
- package/dist/core/__tests__/full/detach-focus.test.js +10 -5
- package/dist/core/__tests__/full/human-new-window-regression.test.js +6 -3
- package/dist/core/__tests__/full/review-render-pane-regression.test.js +2 -2
- package/dist/core/__tests__/helpers/harness.d.ts +1 -0
- package/dist/core/__tests__/helpers/harness.js +28 -4
- package/dist/core/__tests__/host-teardown-process-group.test.js +15 -4
- package/dist/core/__tests__/live-mutation-verbs.test.js +5 -4
- package/dist/core/__tests__/review-model-floor.test.js +1 -1
- 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/__tests__/remote-event-stream.test.js +11 -9
- package/dist/core/canvas/__tests__/render-remote.test.js +7 -4
- package/dist/core/canvas/browse/__tests__/model.test.js +23 -5
- package/dist/core/canvas/browse/__tests__/rebuild-coalescer.test.js +30 -13
- package/dist/core/canvas/browse/__tests__/render.test.js +1 -0
- package/dist/core/canvas/browse/app.js +164 -5
- 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/canvas/pid.d.ts +14 -10
- package/dist/core/canvas/pid.js +14 -10
- package/dist/core/config.d.ts +53 -2
- 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__/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 +256 -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/runtime/broker-protocol.d.ts +33 -5
- package/dist/core/runtime/broker.js +165 -14
- package/dist/core/runtime/launch.d.ts +39 -6
- package/dist/core/runtime/launch.js +79 -21
- 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/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 +47 -44
- package/dist/core/runtime/recycle.js +6 -1
- package/dist/core/runtime/reset.js +5 -0
- package/dist/core/runtime/revive.js +28 -18
- package/dist/core/runtime/session-list-cache.d.ts +23 -0
- package/dist/core/runtime/session-list-cache.js +270 -0
- package/dist/core/runtime/spawn.js +51 -32
- 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 +216 -110
- 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/__tests__/transport-remote.test.js +9 -6
- package/dist/core/view/contract.d.ts +17 -3
- package/dist/daemon/crtrd-cli.d.ts +1 -1
- package/dist/daemon/crtrd-cli.js +1 -0
- 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 +38 -37
- package/dist/pi-extensions/canvas-stophook.d.ts +11 -0
- package/dist/pi-extensions/canvas-stophook.js +33 -4
- package/dist/suppress-experimental-warnings.d.ts +1 -0
- package/dist/suppress-experimental-warnings.js +15 -0
- package/dist/types.d.ts +3 -13
- package/dist/types.js +12 -36
- package/dist/web-client/assets/{index-CnF5r8ky.js → index--md2ylfi.js} +18 -17
- package/dist/web-client/assets/index-BUdm9s9s.css +2 -0
- package/dist/web-client/index.html +3 -3
- package/dist/web-client/sw.js +1 -1
- package/package.json +11 -8
- package/scripts/postinstall.mjs +1 -1
- 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
- package/dist/web-client/assets/index-BnmSLNLa.css +0 -2
|
@@ -61,8 +61,8 @@ export function render(state, draw, content) {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
const header = state.edit
|
|
64
|
-
? `Replacing ${state.edit.label} —
|
|
65
|
-
: '
|
|
64
|
+
? `Replacing ${state.edit.label} — submit saves, cancel discards`
|
|
65
|
+
: 'Move · switch matrix cell · edit · refresh';
|
|
66
66
|
draw.text(content.row, content.col, 'Model ladders', { bold: true });
|
|
67
67
|
draw.text(content.row + 1, content.col, header, { dim: true });
|
|
68
68
|
if (state.edit) {
|
|
@@ -76,14 +76,14 @@ export function render(state, draw, content) {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
export const keymap = [
|
|
79
|
-
{
|
|
80
|
-
{
|
|
81
|
-
{
|
|
82
|
-
{
|
|
83
|
-
{
|
|
84
|
-
{
|
|
79
|
+
{ bindingId: 'crtr.view.settings.down', intent: 'cursorDown', when: (s) => !s.edit, hint: { label: 'move' } },
|
|
80
|
+
{ bindingId: 'crtr.view.settings.up', intent: 'cursorUp', when: (s) => !s.edit },
|
|
81
|
+
{ bindingId: 'crtr.view.settings.cell-previous', intent: 'matrixLeft', when: (s) => !s.edit, hint: { label: 'switch cell' } },
|
|
82
|
+
{ bindingId: 'crtr.view.settings.cell-next', intent: 'matrixRight', when: (s) => !s.edit },
|
|
83
|
+
{ bindingId: 'crtr.view.settings.refresh', intent: 'refresh', when: (s) => !s.edit, hint: { label: 'refresh' } },
|
|
84
|
+
{ bindingId: 'crtr.view.settings.quit', intent: 'quit', hint: { label: 'quit' } },
|
|
85
85
|
{ capture: 'setDraft', when: (s) => !!s.edit },
|
|
86
|
-
{
|
|
87
|
-
{
|
|
88
|
-
{
|
|
86
|
+
{ bindingId: 'crtr.view.settings.edit-or-submit', intent: 'submitEdit', when: (s) => !!s.edit },
|
|
87
|
+
{ bindingId: 'crtr.view.settings.edit-or-submit', intent: 'beginEdit', when: (s) => !s.edit, hint: { label: 'edit' } },
|
|
88
|
+
{ bindingId: 'crtr.view.settings.cancel-edit', intent: 'cancelEdit', when: (s) => !!s.edit },
|
|
89
89
|
];
|
|
@@ -115,7 +115,7 @@ export function render(state, draw, content) {
|
|
|
115
115
|
}
|
|
116
116
|
emptyState(draw, content, {
|
|
117
117
|
headline: 'No agents here',
|
|
118
|
-
secondary: ['Nothing started in this cwd.', '
|
|
118
|
+
secondary: ['Nothing started in this cwd.', 'Refresh to try again.'],
|
|
119
119
|
});
|
|
120
120
|
return;
|
|
121
121
|
}
|
|
@@ -128,15 +128,14 @@ export function render(state, draw, content) {
|
|
|
128
128
|
// ── keymap ───────────────────────────────────────────────────────────────
|
|
129
129
|
|
|
130
130
|
/**
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
* these bindings' `hint` fields (the single source of truth).
|
|
131
|
+
* Move over node rows, open the selected node in the chat pane, refresh, or
|
|
132
|
+
* leave the view. Footer hints use the host-resolved binding gestures.
|
|
134
133
|
* @type {import('../../core/view/contract.js').KeyBinding<SidebarState>[]}
|
|
135
134
|
*/
|
|
136
135
|
export const keymap = [
|
|
137
|
-
{
|
|
138
|
-
{
|
|
139
|
-
{
|
|
140
|
-
{
|
|
141
|
-
{
|
|
136
|
+
{ bindingId: 'crtr.view.workspace-sidebar.down', intent: 'cursorDown', hint: { label: 'move' } },
|
|
137
|
+
{ bindingId: 'crtr.view.workspace-sidebar.up', intent: 'cursorUp' },
|
|
138
|
+
{ bindingId: 'crtr.view.workspace-sidebar.open', intent: 'open', hint: { label: 'open' } },
|
|
139
|
+
{ bindingId: 'crtr.view.workspace-sidebar.refresh', intent: 'refresh', hint: { label: 'refresh' } },
|
|
140
|
+
{ bindingId: 'crtr.view.workspace-sidebar.quit', intent: 'quit', hint: { label: 'quit' } },
|
|
142
141
|
];
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
2
|
+
import './suppress-experimental-warnings.js';
|
package/dist/cli.js
CHANGED
|
@@ -152,7 +152,7 @@ function seedPoisonedLocalState() {
|
|
|
152
152
|
last_activity: 'POISON ACTIVITY — local read leaked into remote attach chrome',
|
|
153
153
|
}));
|
|
154
154
|
}
|
|
155
|
-
test('buildCanvasPanelLines suppresses every local-read adornment for a RemoteCanvasSource, even with conflicting local state for the same node id', async () => {
|
|
155
|
+
test('buildCanvasPanelLines suppresses every local-read adornment for a RemoteCanvasSource, even with conflicting local state for the same node id', async (t) => {
|
|
156
156
|
seedPoisonedLocalState();
|
|
157
157
|
const rootMeta = meta('root');
|
|
158
158
|
const aMeta = meta('a'); // the REMOTE node's meta — unrelated to the poisoned local NodeMeta above
|
|
@@ -166,6 +166,10 @@ test('buildCanvasPanelLines suppresses every local-read adornment for a RemoteCa
|
|
|
166
166
|
}
|
|
167
167
|
return { status: 500, body: {} };
|
|
168
168
|
});
|
|
169
|
+
// Register the close BEFORE any assertion runs — a listening server left
|
|
170
|
+
// open by a thrown assertion keeps this test file's process alive forever
|
|
171
|
+
// (see the CI-hang postmortem on rebuild-coalescer.test.ts).
|
|
172
|
+
t.after(() => server.close());
|
|
169
173
|
const source = new RemoteCanvasSource(server.url, 'tok');
|
|
170
174
|
// 'root' has 'a' as a live report → row('a') renders into `reports`.
|
|
171
175
|
const { reports } = await buildCanvasPanelLines('root', {}, IDENTITY_PALETTE, source);
|
|
@@ -175,9 +179,8 @@ test('buildCanvasPanelLines suppresses every local-read adornment for a RemoteCa
|
|
|
175
179
|
assert.ok(!line.includes('POISON'), `poisoned local text must never appear in a remote row: ${line}`);
|
|
176
180
|
assert.ok(!line.includes('⚠'), `a local active fault must not surface as the hanging glyph on a remote row: ${line}`);
|
|
177
181
|
assert.ok(!line.includes(BG_ATTACHED), `a local focus/attach must not paint a remote row as attached: ${line}`);
|
|
178
|
-
await server.close();
|
|
179
182
|
});
|
|
180
|
-
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 () => {
|
|
183
|
+
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) => {
|
|
181
184
|
seedPoisonedLocalState();
|
|
182
185
|
const rootMeta = meta('root');
|
|
183
186
|
const aMeta = meta('a');
|
|
@@ -191,6 +194,8 @@ test('GraphOverlay suppresses every local-read adornment for a RemoteCanvasSourc
|
|
|
191
194
|
}
|
|
192
195
|
return { status: 500, body: {} };
|
|
193
196
|
});
|
|
197
|
+
// Register the close BEFORE any assertion runs — see the note above.
|
|
198
|
+
t.after(() => server.close());
|
|
194
199
|
const source = new RemoteCanvasSource(server.url, 'tok');
|
|
195
200
|
const { tui } = fakeTui();
|
|
196
201
|
const overlay = new GraphOverlay(tui, 'root', () => ({}), IDENTITY_PALETTE, source);
|
|
@@ -219,11 +224,10 @@ test('GraphOverlay suppresses every local-read adornment for a RemoteCanvasSourc
|
|
|
219
224
|
// m must NOT close the overlay either (it would focusTarget the manager).
|
|
220
225
|
overlay.handleInput('m');
|
|
221
226
|
assert.equal(overlay.isOpen(), true, 'm must not act (close) on a remote graph row');
|
|
222
|
-
await server.close();
|
|
223
227
|
});
|
|
224
228
|
// ---------------------------------------------------------------------------
|
|
225
229
|
// Phase 3 review 2 — the ENCLOSING attach viewer (slash commands, pane tag,
|
|
226
|
-
// detach-reap, Alt+
|
|
230
|
+
// detach-reap, Alt+Shift+P mode-switch), not just the GraphOverlay/panel rendering
|
|
227
231
|
// the fix above already covers. Same adversarial shape: seed colliding LOCAL
|
|
228
232
|
// state for the remote node id `a`, then assert the remote path never touches
|
|
229
233
|
// it — and, symmetrically, that the LOCAL path is byte-for-byte unchanged
|
|
@@ -328,10 +332,10 @@ test('resolveInitialMode/requestModeSwitch never touch the local mode-switch rea
|
|
|
328
332
|
lastWrite = { cwd, nodeId, mode };
|
|
329
333
|
};
|
|
330
334
|
const remoteOk = requestModeSwitch('/tmp/cwd', 'a', true, 'spec', writeRequest);
|
|
331
|
-
assert.equal(remoteOk, false, 'remote Alt+
|
|
335
|
+
assert.equal(remoteOk, false, 'remote Alt+Shift+P must no-op (return false)');
|
|
332
336
|
assert.equal(writes, 0, 'remote must never write the local mode-switch request file');
|
|
333
337
|
const localOk = requestModeSwitch('/tmp/cwd', 'a', false, 'spec', writeRequest);
|
|
334
|
-
assert.equal(localOk, true, 'local Alt+
|
|
338
|
+
assert.equal(localOk, true, 'local Alt+Shift+P must still write through');
|
|
335
339
|
assert.equal(writes, 1, 'local must call the injected local writer exactly once');
|
|
336
340
|
assert.deepEqual(lastWrite, { cwd: '/tmp/cwd', nodeId: 'a', mode: 'spec' });
|
|
337
341
|
});
|
|
@@ -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,20 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
import { transformMermaidFences } from '../mermaid-render.js';
|
|
4
|
+
const APPLET_CONCIERGE_FLOW = `\`\`\`mermaid
|
|
5
|
+
flowchart LR
|
|
6
|
+
U[User] --> C[Applet concierge]
|
|
7
|
+
C --> B[Applet-builder developer node<br/>cwd = app repo]
|
|
8
|
+
B --> D[Platform deploy command]
|
|
9
|
+
D --> B
|
|
10
|
+
B --> C
|
|
11
|
+
C --> U
|
|
12
|
+
\`\`\``;
|
|
13
|
+
test('renders the applet concierge flowchart after provisioning termrender', () => {
|
|
14
|
+
const rendered = transformMermaidFences(APPLET_CONCIERGE_FLOW, 200);
|
|
15
|
+
assert.match(rendered, /Applet-builder/);
|
|
16
|
+
assert.match(rendered, /cwd = app repo/);
|
|
17
|
+
assert.match(rendered, /Platform deploy/);
|
|
18
|
+
assert.match(rendered, /[\u2500-\u259F\u25A0-\u25FF]/);
|
|
19
|
+
assert.doesNotMatch(rendered, /```mermaid/);
|
|
20
|
+
});
|
|
@@ -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
|
+
});
|
|
@@ -6,7 +6,7 @@ export type Mode = 'normal' | 'spec' | 'plan';
|
|
|
6
6
|
/** Resolve the mode badge's initial state. Remote never reads the local
|
|
7
7
|
* mode-switch `current` file for `nodeId` — always starts 'normal'. */
|
|
8
8
|
export declare function resolveInitialMode(cwd: string, nodeId: string, remote: boolean, readMode?: (cwd: string, nodeId: string) => Mode | undefined): Mode;
|
|
9
|
-
/** Handle Alt+
|
|
9
|
+
/** Handle Alt+Shift+P's mode-cycle request. Remote no-ops entirely (returns `false`,
|
|
10
10
|
* so the caller leaves `modeState`/the badge untouched) — there is no
|
|
11
11
|
* remote-safe mode-switch protocol, and writing the local `request.json` for a
|
|
12
12
|
* remote node id would target the wrong (or a colliding local) node. */
|