@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.
Files changed (176) hide show
  1. package/README.md +1 -1
  2. package/dist/builtin-memory/00-runtime-base.md +3 -10
  3. package/dist/builtin-memory/04-base-worker.md +18 -0
  4. package/dist/builtin-memory/04-orchestration-kernel.md +1 -1
  5. package/dist/builtin-pi-packages/pi-crtr-extensions/README.md +13 -34
  6. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/provider-rotation.test.ts +639 -741
  7. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +241 -58
  8. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/statusline.ts +2 -9
  9. package/dist/builtin-pi-packages/pi-crtr-extensions/lib/subscription-state.ts +365 -94
  10. package/dist/builtin-pi-packages/pi-crtr-extensions/package-lock.json +2 -2
  11. package/dist/builtin-pi-packages/pi-mode-switch/README.md +11 -39
  12. package/dist/builtin-pi-packages/pi-mode-switch/extensions/index.ts +20 -15
  13. package/dist/builtin-pi-packages/pi-mode-switch/package.json +1 -1
  14. package/dist/builtin-views/canvas/tui.mjs +8 -9
  15. package/dist/builtin-views/chat/tui.mjs +12 -12
  16. package/dist/builtin-views/git-pr/tui.mjs +7 -8
  17. package/dist/builtin-views/inbox/tui.mjs +27 -41
  18. package/dist/builtin-views/linkedin/tui.mjs +23 -37
  19. package/dist/builtin-views/prompt-review/tui.mjs +11 -11
  20. package/dist/builtin-views/settings/tui.mjs +11 -11
  21. package/dist/builtin-views/workspace-sidebar/tui.mjs +8 -9
  22. package/dist/cli.d.ts +1 -1
  23. package/dist/cli.js +1 -0
  24. package/dist/clients/attach/__tests__/attach-chrome-remote.test.js +11 -7
  25. package/dist/clients/attach/__tests__/crtr-output-render.test.js +1 -1
  26. package/dist/clients/attach/__tests__/mermaid-render.test.d.ts +1 -0
  27. package/dist/clients/attach/__tests__/mermaid-render.test.js +20 -0
  28. package/dist/clients/attach/__tests__/oauth-dialog-lifecycle.test.d.ts +1 -0
  29. package/dist/clients/attach/__tests__/oauth-dialog-lifecycle.test.js +110 -0
  30. package/dist/clients/attach/attach-cmd.d.ts +1 -1
  31. package/dist/clients/attach/attach-cmd.js +733 -730
  32. package/dist/clients/attach/auth-pickers.d.ts +12 -0
  33. package/dist/clients/attach/auth-pickers.js +11 -0
  34. package/dist/clients/attach/canvas-panels.js +2 -3
  35. package/dist/clients/attach/chat-view.d.ts +7 -8
  36. package/dist/clients/attach/chat-view.js +119 -79
  37. package/dist/clients/attach/edit-diff-render.d.ts +6 -0
  38. package/dist/clients/attach/edit-diff-render.js +263 -0
  39. package/dist/clients/attach/input-controller.d.ts +28 -2
  40. package/dist/clients/attach/input-controller.js +38 -5
  41. package/dist/clients/attach/mermaid-render.js +5 -2
  42. package/dist/clients/attach/pickers.d.ts +8 -7
  43. package/dist/clients/attach/pickers.js +13 -17
  44. package/dist/clients/attach/slash-commands.d.ts +9 -0
  45. package/dist/clients/attach/slash-commands.js +128 -7
  46. package/dist/clients/attach/titled-editor.d.ts +12 -1
  47. package/dist/clients/attach/titled-editor.js +103 -8
  48. package/dist/commands/canvas-browse.js +2 -2
  49. package/dist/commands/memory/lint.js +39 -5
  50. package/dist/commands/memory/write.js +1 -0
  51. package/dist/commands/node.js +12 -5
  52. package/dist/commands/profile/new.js +30 -5
  53. package/dist/commands/surface-tmux-spread.js +1 -3
  54. package/dist/commands/sys/__tests__/config-keybindings.test.d.ts +1 -0
  55. package/dist/commands/sys/__tests__/config-keybindings.test.js +55 -0
  56. package/dist/commands/sys/__tests__/config-model-ladders.test.d.ts +1 -0
  57. package/dist/commands/sys/__tests__/config-model-ladders.test.js +121 -0
  58. package/dist/commands/sys/config.js +18 -21
  59. package/dist/commands/sys/setup-core.d.ts +12 -1
  60. package/dist/commands/sys/setup-core.js +25 -3
  61. package/dist/commands/sys/setup.js +166 -20
  62. package/dist/core/__tests__/base-worker-prompt.test.d.ts +1 -0
  63. package/dist/core/__tests__/base-worker-prompt.test.js +24 -0
  64. package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +232 -1
  65. package/dist/core/__tests__/fault-classifier.test.js +15 -0
  66. package/dist/core/__tests__/full/broker-dialogs.test.js +7 -2
  67. package/dist/core/__tests__/full/broker-pane-resolution.test.js +9 -6
  68. package/dist/core/__tests__/full/cascade-close.test.js +5 -2
  69. package/dist/core/__tests__/full/dead-pane-regression.test.js +6 -3
  70. package/dist/core/__tests__/full/detach-focus.test.js +10 -5
  71. package/dist/core/__tests__/full/human-new-window-regression.test.js +6 -3
  72. package/dist/core/__tests__/full/review-render-pane-regression.test.js +2 -2
  73. package/dist/core/__tests__/helpers/harness.d.ts +1 -0
  74. package/dist/core/__tests__/helpers/harness.js +28 -4
  75. package/dist/core/__tests__/host-teardown-process-group.test.js +15 -4
  76. package/dist/core/__tests__/live-mutation-verbs.test.js +5 -4
  77. package/dist/core/__tests__/review-model-floor.test.js +1 -1
  78. package/dist/core/__tests__/stream-watchdog.test.d.ts +1 -0
  79. package/dist/core/__tests__/stream-watchdog.test.js +70 -0
  80. package/dist/core/__tests__/tmux-surface.test.js +72 -0
  81. package/dist/core/canvas/__tests__/remote-event-stream.test.js +11 -9
  82. package/dist/core/canvas/__tests__/render-remote.test.js +7 -4
  83. package/dist/core/canvas/browse/__tests__/model.test.js +23 -5
  84. package/dist/core/canvas/browse/__tests__/rebuild-coalescer.test.js +30 -13
  85. package/dist/core/canvas/browse/__tests__/render.test.js +1 -0
  86. package/dist/core/canvas/browse/app.js +164 -5
  87. package/dist/core/canvas/browse/model.d.ts +10 -6
  88. package/dist/core/canvas/browse/model.js +20 -12
  89. package/dist/core/canvas/browse/pins.d.ts +4 -0
  90. package/dist/core/canvas/browse/pins.js +29 -0
  91. package/dist/core/canvas/browse/render.d.ts +13 -1
  92. package/dist/core/canvas/browse/render.js +72 -15
  93. package/dist/core/canvas/nav-model.js +5 -11
  94. package/dist/core/canvas/paths.d.ts +5 -0
  95. package/dist/core/canvas/paths.js +7 -0
  96. package/dist/core/canvas/pid.d.ts +14 -10
  97. package/dist/core/canvas/pid.js +14 -10
  98. package/dist/core/config.d.ts +53 -2
  99. package/dist/core/config.js +228 -110
  100. package/dist/core/fault-classifier.js +1 -1
  101. package/dist/core/fs-utils.d.ts +1 -0
  102. package/dist/core/fs-utils.js +15 -1
  103. package/dist/core/keybindings/__tests__/resolve.test.d.ts +1 -0
  104. package/dist/core/keybindings/__tests__/resolve.test.js +224 -0
  105. package/dist/core/keybindings/catalog.d.ts +14 -0
  106. package/dist/core/keybindings/catalog.js +256 -0
  107. package/dist/core/keybindings/index.d.ts +5 -0
  108. package/dist/core/keybindings/index.js +4 -0
  109. package/dist/core/keybindings/match.d.ts +29 -0
  110. package/dist/core/keybindings/match.js +99 -0
  111. package/dist/core/keybindings/persistence.d.ts +14 -0
  112. package/dist/core/keybindings/persistence.js +31 -0
  113. package/dist/core/keybindings/resolve.d.ts +14 -0
  114. package/dist/core/keybindings/resolve.js +305 -0
  115. package/dist/core/keybindings/types.d.ts +37 -0
  116. package/dist/core/keybindings/types.js +1 -0
  117. package/dist/core/memory-resolver.d.ts +1 -1
  118. package/dist/core/memory-resolver.js +10 -3
  119. package/dist/core/predicate.d.ts +5 -3
  120. package/dist/core/predicate.js +5 -3
  121. package/dist/core/runtime/broker-protocol.d.ts +33 -5
  122. package/dist/core/runtime/broker.js +165 -14
  123. package/dist/core/runtime/launch.d.ts +39 -6
  124. package/dist/core/runtime/launch.js +79 -21
  125. package/dist/core/runtime/naming.js +3 -3
  126. package/dist/core/runtime/pi-cli.d.ts +6 -0
  127. package/dist/core/runtime/pi-cli.js +16 -2
  128. package/dist/core/runtime/placement.d.ts +2 -2
  129. package/dist/core/runtime/placement.js +4 -1
  130. package/dist/core/runtime/promote.js +4 -0
  131. package/dist/core/runtime/recap.d.ts +3 -3
  132. package/dist/core/runtime/recap.js +47 -44
  133. package/dist/core/runtime/recycle.js +6 -1
  134. package/dist/core/runtime/reset.js +5 -0
  135. package/dist/core/runtime/revive.js +28 -18
  136. package/dist/core/runtime/session-list-cache.d.ts +23 -0
  137. package/dist/core/runtime/session-list-cache.js +270 -0
  138. package/dist/core/runtime/spawn.js +51 -32
  139. package/dist/core/runtime/stream-watchdog.d.ts +26 -0
  140. package/dist/core/runtime/stream-watchdog.js +75 -0
  141. package/dist/core/runtime/tmux-chrome.d.ts +1 -1
  142. package/dist/core/runtime/tmux-chrome.js +2 -2
  143. package/dist/core/runtime/tmux.d.ts +18 -15
  144. package/dist/core/runtime/tmux.js +216 -110
  145. package/dist/core/tui/__tests__/host-keybindings.test.d.ts +1 -0
  146. package/dist/core/tui/__tests__/host-keybindings.test.js +112 -0
  147. package/dist/core/tui/host.d.ts +30 -1
  148. package/dist/core/tui/host.js +148 -34
  149. package/dist/core/view/__tests__/transport-remote.test.js +9 -6
  150. package/dist/core/view/contract.d.ts +17 -3
  151. package/dist/daemon/crtrd-cli.d.ts +1 -1
  152. package/dist/daemon/crtrd-cli.js +1 -0
  153. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +41 -0
  154. package/dist/pi-extensions/__tests__/canvas-stophook-context-nudge.test.d.ts +1 -0
  155. package/dist/pi-extensions/__tests__/canvas-stophook-context-nudge.test.js +121 -0
  156. package/dist/pi-extensions/canvas-inbox-watcher.d.ts +1 -1
  157. package/dist/pi-extensions/canvas-inbox-watcher.js +203 -25
  158. package/dist/pi-extensions/canvas-recap.d.ts +4 -0
  159. package/dist/pi-extensions/canvas-recap.js +38 -37
  160. package/dist/pi-extensions/canvas-stophook.d.ts +11 -0
  161. package/dist/pi-extensions/canvas-stophook.js +33 -4
  162. package/dist/suppress-experimental-warnings.d.ts +1 -0
  163. package/dist/suppress-experimental-warnings.js +15 -0
  164. package/dist/types.d.ts +3 -13
  165. package/dist/types.js +12 -36
  166. package/dist/web-client/assets/{index-CnF5r8ky.js → index--md2ylfi.js} +18 -17
  167. package/dist/web-client/assets/index-BUdm9s9s.css +2 -0
  168. package/dist/web-client/index.html +3 -3
  169. package/dist/web-client/sw.js +1 -1
  170. package/package.json +11 -8
  171. package/scripts/postinstall.mjs +1 -1
  172. package/dist/pi-extensions/canvas-nav.d.ts +0 -43
  173. package/dist/pi-extensions/canvas-nav.js +0 -640
  174. package/dist/pi-extensions/widget-order-bus.d.ts +0 -6
  175. package/dist/pi-extensions/widget-order-bus.js +0 -34
  176. 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} — Enter saves, Esc cancels`
65
- : 'j/k move · h/l switch matrix cell · enter edit · g refresh';
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
- { keys: ['j', 'down'], intent: 'cursorDown', when: (s) => !s.edit, hint: { keys: 'j/k', label: 'move' } },
80
- { keys: ['k', 'up'], intent: 'cursorUp', when: (s) => !s.edit },
81
- { keys: ['h', 'left'], intent: 'matrixLeft', when: (s) => !s.edit, hint: { keys: 'h/l', label: 'switch cell' } },
82
- { keys: ['l', 'right'], intent: 'matrixRight', when: (s) => !s.edit },
83
- { keys: ['g'], intent: 'refresh', when: (s) => !s.edit, hint: { keys: 'g', label: 'refresh' } },
84
- { keys: ['q'], intent: 'quit', hint: { keys: 'q', label: 'quit' } },
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
- { keys: ['enter', 'return'], intent: 'submitEdit', when: (s) => !!s.edit },
87
- { keys: ['enter', 'return'], intent: 'beginEdit', when: (s) => !s.edit, hint: { keys: 'enter', label: 'edit' } },
88
- { keys: ['escape', 'esc'], intent: 'cancelEdit', when: (s) => !!s.edit },
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.', 'Press g to refresh.'],
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
- * j/k move the cursor over node rows, swaps the selected node into the chat
132
- * pane (the controller action), g refreshes, q quits. Footer hints come from
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
- { keys: ['j', 'down'], intent: 'cursorDown', hint: { keys: 'j/k', label: 'move' } },
138
- { keys: ['k', 'up'], intent: 'cursorUp' },
139
- { keys: ['return', 'enter'], intent: 'open', hint: { keys: '↵', label: 'open' } },
140
- { keys: ['g', 'r'], intent: 'refresh', hint: { keys: 'g', label: 'refresh' } },
141
- { keys: ['q'], intent: 'quit', hint: { keys: 'q', label: 'quit' } },
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
- export {};
2
+ import './suppress-experimental-warnings.js';
package/dist/cli.js CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import './suppress-experimental-warnings.js';
2
3
  import { enableCompileCache } from 'node:module';
3
4
  import { runCli } from './core/command.js';
4
5
  import { resolveRoot } from './build-root.js';
@@ -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+M mode-switch), not just the GraphOverlay/panel rendering
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+M must no-op (return false)');
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+M must still write through');
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+M mode cycling. (installed)',
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,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,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+M's mode-cycle request. Remote no-ops entirely (returns `false`,
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. */