@north-light/crouter 0.3.157 → 0.3.158

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 (76) hide show
  1. package/dist/api/client.d.ts +4 -1
  2. package/dist/api/client.js +5 -0
  3. package/dist/api/dto/broker.d.ts +1 -1
  4. package/dist/api/dto/nodes.d.ts +15 -0
  5. package/dist/api/routes.d.ts +1 -0
  6. package/dist/api/routes.js +1 -0
  7. package/dist/builtin-views/chat/core.mjs +6 -51
  8. package/dist/builtin-views/chat/tui.mjs +6 -14
  9. package/dist/builtin-views/chat/web.jsx +2 -7
  10. package/dist/clients/attach/__tests__/oauth-dialog-lifecycle.test.js +1 -1
  11. package/dist/clients/attach/chrome/roster.d.ts +1 -1
  12. package/dist/clients/attach/chrome/roster.js +9 -1
  13. package/dist/clients/attach/command.js +5 -5
  14. package/dist/clients/attach/input/controller.d.ts +23 -8
  15. package/dist/clients/attach/input/controller.js +59 -29
  16. package/dist/clients/attach/overlays/dialogs.d.ts +1 -2
  17. package/dist/clients/attach/overlays/graph.d.ts +4 -1
  18. package/dist/clients/attach/overlays/graph.js +25 -7
  19. package/dist/clients/attach/session/context.d.ts +7 -0
  20. package/dist/clients/attach/session/frames.js +1 -8
  21. package/dist/clients/attach/session/input-wiring.d.ts +1 -1
  22. package/dist/clients/attach/session/input-wiring.js +21 -11
  23. package/dist/clients/attach/session/mode.d.ts +4 -3
  24. package/dist/clients/attach/session/mode.js +6 -1
  25. package/dist/clients/attach/session/reconnect.d.ts +3 -3
  26. package/dist/clients/attach/session/reconnect.js +6 -7
  27. package/dist/clients/attach/session/state-sync.d.ts +1 -1
  28. package/dist/clients/attach/session/state-sync.js +2 -2
  29. package/dist/clients/attach/slash/dispatch.d.ts +13 -1
  30. package/dist/clients/attach/slash/dispatch.js +65 -17
  31. package/dist/clients/attach/viewer.js +523 -523
  32. package/dist/clients/web/web-client/shared/protocol.d.ts +5 -3
  33. package/dist/commands/node.js +1 -1
  34. package/dist/core/__tests__/broker-sdk-wiring.test.js +18 -18
  35. package/dist/core/__tests__/chat-view-reconnect.test.js +44 -23
  36. package/dist/core/__tests__/full/broker-attach-limits.test.js +60 -36
  37. package/dist/core/__tests__/full/broker-attach-stream.test.js +4 -4
  38. package/dist/core/__tests__/full/broker-dialogs.test.js +121 -62
  39. package/dist/core/__tests__/helpers/broker-clients.js +2 -2
  40. package/dist/core/__tests__/session-model.test.js +15 -26
  41. package/dist/core/keybindings/__tests__/resolve.test.js +1 -1
  42. package/dist/core/keybindings/catalog.d.ts +2 -2
  43. package/dist/core/keybindings/catalog.js +0 -2
  44. package/dist/core/runtime/auth-reload.d.ts +4 -4
  45. package/dist/core/runtime/auth-reload.js +9 -13
  46. package/dist/core/runtime/boot-root.d.ts +2 -2
  47. package/dist/core/runtime/boot-root.js +7 -7
  48. package/dist/core/runtime/broker-protocol.d.ts +23 -27
  49. package/dist/core/runtime/broker-protocol.js +1 -1
  50. package/dist/core/runtime/broker-request.js +5 -11
  51. package/dist/core/runtime/broker.d.ts +21 -13
  52. package/dist/core/runtime/broker.js +151 -186
  53. package/dist/core/runtime/interactive-deliver.js +4 -5
  54. package/dist/core/runtime/model-swap.d.ts +2 -3
  55. package/dist/core/runtime/model-swap.js +3 -4
  56. package/dist/core/runtime/node-read.d.ts +20 -0
  57. package/dist/core/runtime/node-read.js +34 -1
  58. package/dist/core/runtime/resume-root.d.ts +1 -1
  59. package/dist/core/runtime/resume-root.js +6 -6
  60. package/dist/core/runtime/spawn.js +3 -3
  61. package/dist/core/session-model/session-state.d.ts +8 -6
  62. package/dist/core/session-model/session-state.js +6 -16
  63. package/dist/daemon/api/handlers/nodes.js +18 -1
  64. package/dist/daemon/manage.js +2 -2
  65. package/dist/index.d.ts +1 -1
  66. package/dist/pi-extensions/canvas-bash-valve.js +4 -3
  67. package/dist/web-client/assets/{index-DJhQZoAj.css → index-CpEl9LTS.css} +1 -1
  68. package/dist/web-client/assets/{index--SsQYcKu.js → index-CsuwzlcQ.js} +19 -19
  69. package/dist/web-client/index.html +2 -2
  70. package/dist/web-client/sw.js +1 -1
  71. package/docs/compat/hearth-crtr-v5.md +175 -0
  72. package/docs/public-api.md +2 -2
  73. package/package.json +2 -2
  74. package/runtime.lock.json +6 -6
  75. package/dist/core/__tests__/full/broker-control-preempt.test.d.ts +0 -1
  76. package/dist/core/__tests__/full/broker-control-preempt.test.js +0 -61
@@ -52,6 +52,17 @@ const CANVAS_NAMES = new Set(CANVAS_SLASH_COMMANDS.map((c) => c.name));
52
52
  * (the in-process GRAPH overlay, already remote-safe — see overlays/graph.ts)
53
53
  * and `/view` (a self-contained popup unrelated to `nodeId`) stay available. */
54
54
  const REMOTE_UNSAFE_CANVAS_NAMES = new Set(['promote', 'resume-node', 'context', 'node-metadata', 'rename', 'clear', 'mcp']);
55
+ /** Native canvas commands an OBSERVER may not run — the ones that drive the
56
+ * engine or mutate the node, whether by frame or by shelling the crtr CLI:
57
+ * `/clear` sends `new_session`; `/promote` shells `crtr node promote` and then
58
+ * triggers a turn; `/rename` sends `set_session_name` and shells `crtr node
59
+ * config --name`; `/resume-node` opens the navigator whose Enter path focuses
60
+ * (and so revives) a node; `/mcp`'s panel actions ride a `prompt` frame into
61
+ * the engine. Deliberately NOT here — genuinely read-only or viewer-local:
62
+ * `/graph` (in-process overlay), `/context` and `/node-metadata` (read-only
63
+ * inspection popups), `/color` (this pane's tmux border + this viewer's name
64
+ * chip). */
65
+ const OBSERVER_UNSAFE_CANVAS_NAMES = new Set(['clear', 'promote', 'rename', 'resume-node', 'view', 'mcp']);
55
66
  /** tmux named colours accepted by `/color` (plus `colourN`/`#rrggbb`, validated
56
67
  * by pattern). Mirrors tmux's colour-name table so a typo is rejected with the
57
68
  * options rather than silently ignored by tmux. */
@@ -78,17 +89,53 @@ const THINKING_LEVELS = new Set(Object.keys({
78
89
  max: true,
79
90
  }));
80
91
  const BUILTIN_NAMES = new Set(BUILTIN_SLASH_COMMANDS.map((c) => c.name));
81
- /** Builtin slash commands safe to run against a REMOTE attach (`--canvas`) —
92
+ /** Builtin slash commands safe to run from a READ-ONLY viewer — a REMOTE
93
+ * attach (`--canvas`) or an `--observer` one —
82
94
  * everything else in `BUILTIN_SLASH_COMMANDS` either drives the engine
83
95
  * (`/model`, `/new`, `/compact`, `/name`, `/resume`, `/fork`, `/tree`,
84
96
  * `/settings`, `/share`, `/clone`, `/import`, `/reload`, `/scoped-models`
85
97
  * selection) or is viewer-local but operates on the WRONG host for a remote
86
98
  * node (`/login`, `/logout` — LOCAL `auth.json` via `defaultAgentDir()`, not
87
- * the remote broker's). Blocked here BEFORE the switch so a remote attach
88
- * never even opens the picker only to have the eventual selection frame
89
- * swallowed by InputController's `emitCommand` gate (defense-in-depth #2,
90
- * not the only defense — see input/controller.ts DRIVE_FRAME_TYPES). */
91
- const REMOTE_SAFE_BUILTIN_NAMES = new Set(['copy', 'session', 'changelog', 'hotkeys', 'quit']);
99
+ * the remote broker's; for an observer, credentials it may not swap under a
100
+ * running engine). Blocked BEFORE the switch so a read-only viewer never even
101
+ * opens the picker only to have the eventual selection frame swallowed by
102
+ * InputController's `emitCommand` gate (defense-in-depth #2, not the only
103
+ * defense — see input/controller.ts DRIVE_FRAME_TYPES). */
104
+ const READ_ONLY_SAFE_BUILTIN_NAMES = new Set(['copy', 'session', 'changelog', 'hotkeys', 'quit']);
105
+ /** THE capability gate for a read-only viewer, keyed on the two fixed facts a
106
+ * session resolves once (`remote`, `role`) — so the answer for a given command
107
+ * cannot change mid-session. Returns the refusal notice, or `undefined` when
108
+ * this viewer may run the command. Both `dispatchSlashCommand` (so a direct
109
+ * caller is safe) and the InputController's submit path (so a refusal is
110
+ * reported before command dispatch) consult this one function; there is no
111
+ * second policy anywhere. */
112
+ function readOnlyRefusal(name, ctx) {
113
+ const canvas = CANVAS_NAMES.has(name);
114
+ const builtinUnsafe = BUILTIN_NAMES.has(name) && !READ_ONLY_SAFE_BUILTIN_NAMES.has(name);
115
+ if (ctx.remote === true) {
116
+ if (canvas && REMOTE_UNSAFE_CANVAS_NAMES.has(name)) {
117
+ return `/${name} is not available for a remote attach (--canvas) — it acts on this machine's local canvas`;
118
+ }
119
+ if (builtinUnsafe)
120
+ return `/${name} is not available for a remote attach (--canvas) — view-only`;
121
+ }
122
+ if (ctx.remote !== true && (ctx.role ?? 'controller') !== 'controller') {
123
+ if ((canvas && OBSERVER_UNSAFE_CANVAS_NAMES.has(name)) || builtinUnsafe) {
124
+ return `/${name} is not available to a read-only observer attach (--observer) — it would drive this node`;
125
+ }
126
+ }
127
+ return undefined;
128
+ }
129
+ /** The same gate, entered from raw editor text: the InputController asks BEFORE
130
+ * dispatching so a refused command is reported without reaching command
131
+ * dispatch. Unrecognized (extension) commands answer `undefined` here — they
132
+ * ride the normal `prompt` path, which the controller's own drive gate covers. */
133
+ export function readOnlySlashRefusal(text, ctx) {
134
+ const m = /^\/(\S+)\s*([\s\S]*)$/.exec(text.trim());
135
+ if (!m)
136
+ return undefined;
137
+ return readOnlyRefusal(m[1], ctx);
138
+ }
92
139
  /** Every leading-command name this viewer consumes ENTIRELY locally — the
93
140
  * message never reaches the engine as prose, whether dispatched or blocked
94
141
  * with a notice (a remote-unsafe canvas/builtin command still returns
@@ -125,13 +172,20 @@ export function dispatchSlashCommand(text, ctx) {
125
172
  }
126
173
  // Canvas commands (reimplemented native in the viewer) — handled before the
127
174
  // builtin gate since they are neither pi builtins nor engine-extension commands.
128
- if (CANVAS_NAMES.has(name))
175
+ if (CANVAS_NAMES.has(name)) {
176
+ const refusal = readOnlyRefusal(name, ctx);
177
+ if (refusal !== undefined) {
178
+ ctx.notify(refusal);
179
+ return true;
180
+ }
129
181
  return dispatchCanvasCommand(name, arg, ctx);
182
+ }
130
183
  // Unknown to us AND not a builtin → let the engine's extension runner have it.
131
184
  if (!BUILTIN_NAMES.has(name))
132
185
  return false;
133
- if (ctx.remote === true && !REMOTE_SAFE_BUILTIN_NAMES.has(name)) {
134
- ctx.notify(`/${name} is not available for a remote attach (--canvas) — view-only`);
186
+ const refusal = readOnlyRefusal(name, ctx);
187
+ if (refusal !== undefined) {
188
+ ctx.notify(refusal);
135
189
  return true;
136
190
  }
137
191
  switch (name) {
@@ -286,14 +340,8 @@ export function commandAutocompleteItems(commands = BUILTIN_SLASH_COMMANDS, remo
286
340
  /** Dispatch one of the native canvas commands. Always returns `true` (the
287
341
  * command is viewer-owned — never forwarded to the engine as a prompt). */
288
342
  function dispatchCanvasCommand(name, arg, ctx) {
289
- // Structural omission (withCanvasCommands) keeps these out of remote-mode
290
- // autocomplete; this gate is the belt-and-suspenders that also makes them
291
- // NOT EXECUTABLE if typed by hand — a remote node id has no relationship to
292
- // this machine's local canvas.db (or, worse, collides with one that does).
293
- if (ctx.remote && REMOTE_UNSAFE_CANVAS_NAMES.has(name)) {
294
- ctx.notify(`/${name} is not available for a remote attach (--canvas) — it acts on this machine's local canvas`);
295
- return true;
296
- }
343
+ // The capability gate (`readOnlyRefusal`) already ran in dispatchSlashCommand,
344
+ // so everything reaching here is a command this viewer may run.
297
345
  switch (name) {
298
346
  case 'clear':
299
347
  // Claude-Code ergonomics: `/clear` == `/new` — drop the conversation and