@north-light/crouter 0.3.276 → 0.3.278

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.
@@ -74,7 +74,7 @@ export function viewerOperationForFrame(frame) {
74
74
  export function viewerOperationForSlash(name, builtin, canvas) {
75
75
  if (canvas) {
76
76
  switch (name) {
77
- case 'resume-node': return 'canvas-resume';
77
+ case 'resume': return 'canvas-resume';
78
78
  case 'context':
79
79
  case 'node-metadata': return 'local-canvas-inspect';
80
80
  // `/plugins` configures the local machine, not this node; observers and
@@ -84,6 +84,7 @@ export function viewerOperationForSlash(name, builtin, canvas) {
84
84
  case 'clear':
85
85
  case 'promote':
86
86
  case 'rename':
87
+ case 'resume-session':
87
88
  case 'mcp': return 'engine-drive';
88
89
  default: return undefined;
89
90
  }
@@ -38,7 +38,8 @@ export const CANVAS_SLASH_COMMANDS = [
38
38
  { name: 'graph', description: 'Toggle the canvas GRAPH view (your local subscription tree)' },
39
39
  { name: 'inbox', description: 'Toggle the human inbox — pending asks and reviews for you' },
40
40
  { name: 'promote', description: 'Promote this node to an orchestrator — /promote, or /promote <kind> to specialize' },
41
- { name: 'resume-node', description: 'Open the canvas navigator and focus the chosen node; parked residents wake only on submitted input' },
41
+ { name: 'resume', description: 'Open the canvas navigator and swap THIS pane onto the chosen node; parked residents wake only on submitted input' },
42
+ { name: 'resume-session', description: 'Switch this node onto one of its saved pi sessions — picker, or /resume-session <session-path>' },
42
43
  { name: 'context', description: 'Browse this node + its subscriptions’ context dirs in a read-only nvim popup' },
43
44
  { name: 'plugins', description: 'Open the interactive plugin browser for this machine' },
44
45
  { name: 'node-metadata', description: 'Inspect this node — identity, lifecycle, runtime, graph, artifacts, paths, and agent surfaces' },
@@ -51,7 +52,11 @@ export const CANVAS_SLASH_COMMANDS = [
51
52
  { name: 'copy-transcript', description: 'Copy every copyable transcript block' },
52
53
  { name: 'fold-tools', description: 'View setting — fold every FINISHED tool call to its single call line (Alt+C → z → f)' },
53
54
  ];
54
- const CANVAS_NAMES = new Set(CANVAS_SLASH_COMMANDS.map((c) => c.name));
55
+ /** Prior names still dispatched natively but kept OUT of autocomplete, so
56
+ * muscle memory keeps working while the palette shows one canonical entry.
57
+ * Normalized to the canonical name before any gate or dispatch runs. */
58
+ const CANVAS_ALIASES = new Map([['resume-node', 'resume']]);
59
+ const CANVAS_NAMES = new Set([...CANVAS_SLASH_COMMANDS.map((c) => c.name), ...CANVAS_ALIASES.keys()]);
55
60
  /** tmux named colours accepted by `/color` (plus `colourN`/`#rrggbb`, validated
56
61
  * by pattern). Mirrors tmux's colour-name table so a typo is rejected with the
57
62
  * options rather than silently ignored by tmux. */
@@ -119,7 +124,8 @@ export function dispatchSlashCommand(text, ctx) {
119
124
  const command = parseSlashCommand(text);
120
125
  if (command === undefined)
121
126
  return false;
122
- const { name, arg } = command;
127
+ const { arg } = command;
128
+ const name = CANVAS_ALIASES.get(command.name) ?? command.name;
123
129
  if (SCOPED_OUT.has(name)) {
124
130
  ctx.notify(`/${name} is not supported in attach (Phase 4)`);
125
131
  return true;
@@ -189,11 +195,6 @@ export function dispatchSlashCommand(text, ctx) {
189
195
  return openOrHint(ctx.openModelPicker, ctx, 'model', '/model <provider/id>');
190
196
  ctx.send({ type: 'set_model', model: arg });
191
197
  return true;
192
- case 'resume':
193
- if (!arg)
194
- return openOrHint(ctx.openSessionPicker, ctx, 'session', '/resume <session-path>');
195
- ctx.send({ type: 'switch_session', path: arg });
196
- return true;
197
198
  case 'fork':
198
199
  if (!arg)
199
200
  return openOrHint(ctx.openForkPicker, ctx, 'fork point', '/fork <entryId>');
@@ -277,12 +278,24 @@ export function dispatchSlashCommand(text, ctx) {
277
278
  * omitted rather than offered-then-blocked, so autocomplete never advertises a
278
279
  * command `dispatchCanvasCommand` would refuse to run. */
279
280
  function withCanvasCommands(commands, capabilities) {
280
- const seen = new Set(commands.map((c) => c.name));
281
- const offerable = CANVAS_SLASH_COMMANDS.filter((command) => {
282
- const operation = viewerOperationForSlash(command.name, false, true);
281
+ const permitted = (name) => {
282
+ const operation = viewerOperationForSlash(name, false, true);
283
283
  return operation === undefined || capabilities.permits(operation);
284
+ };
285
+ const offerable = CANVAS_SLASH_COMMANDS.filter((command) => permitted(command.name));
286
+ // A canvas command that REPURPOSES a builtin name (`/resume`) keeps that name's
287
+ // slot in the incoming list but carries the canvas description and the canvas
288
+ // capability gate — so autocomplete never advertises pi's meaning for a command
289
+ // this viewer redefines, and never offers one it would refuse to run.
290
+ const canvasByName = new Map(CANVAS_SLASH_COMMANDS.map((command) => [command.name, command]));
291
+ const merged = commands.flatMap((command) => {
292
+ const canvas = canvasByName.get(command.name);
293
+ if (canvas === undefined)
294
+ return [command];
295
+ return permitted(command.name) ? [canvas] : [];
284
296
  });
285
- return [...commands, ...offerable.filter((command) => !seen.has(command.name))];
297
+ const seen = new Set(commands.map((c) => c.name));
298
+ return [...merged, ...offerable.filter((command) => !seen.has(command.name))];
286
299
  }
287
300
  /** Build slash-command autocomplete entries from the merged command list (the
288
301
  * broker's `get_commands` result, which T7 may inject) — defaults to the
@@ -326,10 +339,19 @@ function dispatchCanvasCommand(name, arg, ctx) {
326
339
  case 'promote':
327
340
  promoteNode(arg, ctx);
328
341
  return true;
329
- case 'resume-node':
330
- // Open the full-screen canvas navigator as a popup; on Enter it focuses the
331
- // chosen node back into THIS pane via `crtr surface node focus`.
342
+ case 'resume':
343
+ // Open the full-screen canvas navigator as a popup; on Enter it swaps THIS
344
+ // pane onto the chosen node via `crtr surface node focus --in-place`.
345
+ if (arg)
346
+ return usage(ctx, '/resume — no argument; use /resume-session <path> to switch pi sessions');
332
347
  return resumeNode(ctx);
348
+ case 'resume-session':
349
+ // The pi session picker: rebind THIS node onto a different saved session
350
+ // file (recovering a conversation the node cycled away from).
351
+ if (!arg)
352
+ return openOrHint(ctx.openSessionPicker, ctx, 'session', '/resume-session <session-path>');
353
+ ctx.send({ type: 'switch_session', path: arg });
354
+ return true;
333
355
  case 'mcp':
334
356
  // MCP server status panel — viewer-local reads of the adapter's on-disk
335
357
  // config sources/cache (see overlays/mcp.ts); the actions (connect /
@@ -556,13 +578,14 @@ function colorNode(arg, ctx) {
556
578
  ctx.notify(clear ? 'Node color cleared' : `Node colored ${value}${inTmux ? '' : ' (chip only — no tmux pane)'}`);
557
579
  return true;
558
580
  }
559
- /** `/resume-node`. Opens `crtr canvas browse` as a tmux popup, initially scoped
560
- * to this node's profile (with all dirs visible); on Enter it focuses the chosen
561
- * node back into this pane via `crtr surface node focus --pane`. */
581
+ /** `/resume`. Opens `crtr canvas browse` as a tmux popup, initially scoped to
582
+ * this node's profile (with all dirs visible); on Enter the navigator respawns
583
+ * THIS pane onto the chosen node (`crtr surface node focus --pane --in-place`),
584
+ * so resuming replaces the viewer you invoked it from. */
562
585
  function resumeNode(ctx) {
563
586
  const origPane = process.env['TMUX_PANE'];
564
587
  if (process.env['TMUX'] === undefined || origPane === undefined || origPane === '') {
565
- ctx.notify('/resume-node needs tmux');
588
+ ctx.notify('/resume needs tmux');
566
589
  return true;
567
590
  }
568
591
  const cwd = shellQuote(ctx.cwd ?? process.cwd());