@north-light/crouter 0.3.230 → 0.3.232

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 (173) hide show
  1. package/dist/api/client.d.ts +3 -1
  2. package/dist/api/client.js +4 -0
  3. package/dist/api/dto/canvas.d.ts +10 -0
  4. package/dist/api/dto/common.d.ts +1 -1
  5. package/dist/api/dto/health.d.ts +2 -1
  6. package/dist/api/dto/lifecycle.d.ts +3 -4
  7. package/dist/api/dto/messages.d.ts +5 -4
  8. package/dist/api/dto/nodes.d.ts +2 -0
  9. package/dist/api/dto/profiles.d.ts +5 -0
  10. package/dist/api/routes.d.ts +1 -0
  11. package/dist/api/routes.js +1 -0
  12. package/dist/builtin-memory/00-runtime-base/00-authoring.md +8 -0
  13. package/dist/builtin-memory/00-runtime-base/01-escalation.md +1 -1
  14. package/dist/builtin-memory/01-spine/00-has-manager.md +1 -1
  15. package/dist/builtin-memory/02-turn-lifecycle/00-ending-a-turn.md +5 -0
  16. package/dist/builtin-memory/02-turn-lifecycle/02-resident.md +5 -3
  17. package/dist/builtin-memory/04-orchestration-kernel.md +2 -2
  18. package/dist/builtin-memory/insights/capture.md +3 -2
  19. package/dist/builtin-pi-packages/pi-crtr-extensions/README.md +6 -1
  20. package/dist/clients/attach/render/diagram.js +13 -5
  21. package/dist/clients/attach/render/page-block.d.ts +0 -1
  22. package/dist/clients/attach/render/page-block.js +4 -57
  23. package/dist/clients/attach/viewer.js +570 -563
  24. package/dist/clients/inbox/__tests__/integration/inbox-controller.test.js +9 -0
  25. package/dist/clients/inbox/__tests__/integration/mount-panel.test.js +62 -1
  26. package/dist/clients/inbox/controller.d.ts +10 -0
  27. package/dist/clients/inbox/controller.js +56 -12
  28. package/dist/clients/inbox/tui/input.js +38 -10
  29. package/dist/clients/inbox/tui/page-body.d.ts +10 -0
  30. package/dist/clients/inbox/tui/page-body.js +66 -0
  31. package/dist/clients/inbox/tui/panel.js +6 -4
  32. package/dist/clients/inbox/tui/render.js +89 -17
  33. package/dist/clients/inbox/tui/types.d.ts +4 -4
  34. package/dist/commands/__tests__/human.test.js +18 -3
  35. package/dist/commands/__tests__/node-message.test.js +3 -3
  36. package/dist/commands/api-client.js +1 -7
  37. package/dist/commands/canvas-config.js +6 -14
  38. package/dist/commands/canvas-use.js +4 -6
  39. package/dist/commands/cron.js +16 -22
  40. package/dist/commands/human/prompts.d.ts +1 -1
  41. package/dist/commands/human/prompts.js +118 -112
  42. package/dist/commands/human/request.js +13 -14
  43. package/dist/commands/human/review.js +3 -4
  44. package/dist/commands/human/shared.d.ts +6 -0
  45. package/dist/commands/human/shared.js +43 -5
  46. package/dist/commands/human.js +1 -1
  47. package/dist/commands/memory/delete.js +4 -6
  48. package/dist/commands/memory/edit.js +0 -4
  49. package/dist/commands/memory/move.js +3 -5
  50. package/dist/commands/memory/shared.d.ts +1 -1
  51. package/dist/commands/memory/shared.js +9 -5
  52. package/dist/commands/memory/write.js +60 -29
  53. package/dist/commands/memory.js +1 -1
  54. package/dist/commands/node/bash.js +6 -9
  55. package/dist/commands/node/create.js +89 -22
  56. package/dist/commands/node/inspect.js +3 -3
  57. package/dist/commands/node/lifecycle.js +30 -27
  58. package/dist/commands/node/message.js +24 -45
  59. package/dist/commands/node/subscription.js +6 -15
  60. package/dist/commands/node/wait.js +2 -3
  61. package/dist/commands/node-lifecycle-revive.js +1 -12
  62. package/dist/commands/pkg/browse/actions.js +2 -3
  63. package/dist/commands/pkg/market-manage.js +2 -5
  64. package/dist/commands/pkg/plugin-manage.js +7 -8
  65. package/dist/commands/profile/default.js +5 -5
  66. package/dist/commands/profile/delete.js +1 -1
  67. package/dist/commands/profile/env.js +9 -15
  68. package/dist/commands/profile/kind.js +3 -7
  69. package/dist/commands/profile/meta.js +3 -5
  70. package/dist/commands/profile/new.js +0 -6
  71. package/dist/commands/profile/pause.js +4 -8
  72. package/dist/commands/profile/project.js +5 -9
  73. package/dist/commands/profile/rename.js +3 -7
  74. package/dist/commands/profile/show.js +3 -3
  75. package/dist/commands/profile.js +4 -3
  76. package/dist/commands/surface-tmux-spread.js +1 -3
  77. package/dist/commands/sys/config.js +3 -4
  78. package/dist/commands/sys/support/prepare.js +8 -4
  79. package/dist/commands/sys/support/submit.js +2 -3
  80. package/dist/commands/sys/sync-deps.js +1 -9
  81. package/dist/commands/sys/sync-project-guidance.js +1 -7
  82. package/dist/commands/sys/sync-skills.js +1 -11
  83. package/dist/core/__tests__/cron-node-sink-parked-root.test.d.ts +1 -0
  84. package/dist/core/__tests__/cron-node-sink-parked-root.test.js +147 -0
  85. package/dist/core/__tests__/history-inbox.test.js +11 -1
  86. package/dist/core/__tests__/human-deliver.test.js +2 -1
  87. package/dist/core/__tests__/integration/command-plugins.test.js +0 -1
  88. package/dist/core/__tests__/integration/deferred-no-wake.test.js +0 -1
  89. package/dist/core/__tests__/lifecycle.test.js +30 -2
  90. package/dist/core/__tests__/revive-parked-fresh.test.d.ts +1 -0
  91. package/dist/core/__tests__/revive-parked-fresh.test.js +109 -0
  92. package/dist/core/__tests__/seam/dormancy-release.test.js +32 -5
  93. package/dist/core/canvas/attention.d.ts +2 -0
  94. package/dist/core/canvas/attention.js +25 -18
  95. package/dist/core/canvas/extensions.d.ts +1 -1
  96. package/dist/core/canvas/extensions.js +7 -1
  97. package/dist/core/canvas/history.js +20 -2
  98. package/dist/core/canvas/types.d.ts +1 -1
  99. package/dist/core/command.js +33 -8
  100. package/dist/core/help.d.ts +28 -2
  101. package/dist/core/help.js +46 -10
  102. package/dist/core/human/__tests__/page-html-markdown.test.d.ts +1 -0
  103. package/dist/core/human/__tests__/page-html-markdown.test.js +48 -0
  104. package/dist/core/human/component-docs.js +4 -4
  105. package/dist/core/human/page-html-markdown.d.ts +8 -0
  106. package/dist/core/human/page-html-markdown.js +260 -0
  107. package/dist/core/memory/lint.d.ts +15 -0
  108. package/dist/core/memory/lint.js +150 -90
  109. package/dist/core/profiles/__tests__/fuzzy-match.test.d.ts +1 -0
  110. package/dist/core/profiles/__tests__/fuzzy-match.test.js +51 -0
  111. package/dist/core/profiles/fuzzy-match.d.ts +19 -0
  112. package/dist/core/profiles/fuzzy-match.js +92 -0
  113. package/dist/core/profiles/manifest.d.ts +14 -7
  114. package/dist/core/profiles/manifest.js +62 -12
  115. package/dist/core/profiles/select.d.ts +3 -1
  116. package/dist/core/profiles/select.js +5 -3
  117. package/dist/core/profiles/state-block.js +4 -3
  118. package/dist/core/runtime/boot-root.d.ts +3 -2
  119. package/dist/core/runtime/canvas-extensions.d.ts +7 -1
  120. package/dist/core/runtime/canvas-extensions.js +8 -1
  121. package/dist/core/runtime/lifecycle.d.ts +11 -2
  122. package/dist/core/runtime/lifecycle.js +15 -2
  123. package/dist/core/runtime/model-selection.d.ts +4 -0
  124. package/dist/core/runtime/model-selection.js +5 -0
  125. package/dist/core/runtime/nodes.js +5 -0
  126. package/dist/core/runtime/reopen.d.ts +6 -0
  127. package/dist/core/runtime/reopen.js +12 -1
  128. package/dist/core/runtime/revive.d.ts +6 -0
  129. package/dist/core/runtime/revive.js +22 -2
  130. package/dist/core/runtime/spawn.d.ts +5 -2
  131. package/dist/core/runtime/spawn.js +18 -32
  132. package/dist/core/runtime/structured-output.d.ts +6 -0
  133. package/dist/core/runtime/structured-output.js +6 -0
  134. package/dist/core/substrate/__tests__/surface-match-command.test.d.ts +1 -0
  135. package/dist/core/substrate/__tests__/surface-match-command.test.js +89 -0
  136. package/dist/core/substrate/on-read.js +2 -1
  137. package/dist/core/substrate/surface-match.js +164 -12
  138. package/dist/core/termrender/version.d.ts +1 -1
  139. package/dist/core/termrender/version.js +1 -1
  140. package/dist/core/user-settings.js +1 -1
  141. package/dist/daemon/api/__tests__/broker-settle-park.test.d.ts +1 -0
  142. package/dist/daemon/api/__tests__/broker-settle-park.test.js +102 -0
  143. package/dist/daemon/api/__tests__/canvas-snapshot-fields.test.d.ts +1 -0
  144. package/dist/daemon/api/__tests__/canvas-snapshot-fields.test.js +188 -0
  145. package/dist/daemon/api/__tests__/node-create-description.test.d.ts +1 -0
  146. package/dist/daemon/api/__tests__/node-create-description.test.js +83 -0
  147. package/dist/daemon/api/__tests__/profile-metadata-route.test.d.ts +1 -0
  148. package/dist/daemon/api/__tests__/profile-metadata-route.test.js +92 -0
  149. package/dist/daemon/api/__tests__/reopen-delivery.test.d.ts +1 -0
  150. package/dist/daemon/api/__tests__/reopen-delivery.test.js +173 -0
  151. package/dist/daemon/api/handlers/broker-ops.js +21 -0
  152. package/dist/daemon/api/handlers/canvas.js +10 -0
  153. package/dist/daemon/api/handlers/messages.js +25 -16
  154. package/dist/daemon/api/handlers/nodes.js +5 -0
  155. package/dist/daemon/api/handlers/profiles.js +22 -1
  156. package/dist/daemon/cron-run.js +19 -1
  157. package/dist/daemon/manage.d.ts +16 -1
  158. package/dist/daemon/manage.js +20 -1
  159. package/dist/daemon/park-pending.d.ts +13 -0
  160. package/dist/daemon/park-pending.js +42 -0
  161. package/dist/daemon/reconcilers/broker-supervision.d.ts +13 -0
  162. package/dist/daemon/reconcilers/broker-supervision.js +139 -21
  163. package/dist/daemon/reconcilers/live-obligation.d.ts +10 -4
  164. package/dist/daemon/reconcilers/live-obligation.js +7 -3
  165. package/dist/daemon/reconcilers/storage-maintenance.d.ts +0 -4
  166. package/dist/daemon/reconcilers/storage-maintenance.js +1 -33
  167. package/dist/pi-extensions/canvas-prompt-scrub.d.ts +13 -0
  168. package/dist/pi-extensions/canvas-prompt-scrub.js +53 -0
  169. package/dist/shared/generated-context.d.ts +7 -0
  170. package/dist/shared/generated-context.js +11 -0
  171. package/package.json +4 -4
  172. package/runtime.lock.json +2 -2
  173. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/strip-skills-docs.ts +0 -47
@@ -9,7 +9,8 @@ import { parseArgv } from '../../core/command.js';
9
9
  import { renderLeafArgv } from '../../core/help.js';
10
10
  import { componentDoc, renderComponentCatalog, renderComponentContract } from '../../core/human/component-docs.js';
11
11
  import { humanComponents } from '../human/components.js';
12
- import { humanSend } from '../human/prompts.js';
12
+ import { humanSend as buildHumanSend } from '../human/prompts.js';
13
+ const humanSend = buildHumanSend();
13
14
  import { registerHuman } from '../human.js';
14
15
  import { pageHelpText } from '../human/shared.js';
15
16
  const nlThreadRegistration = {
@@ -69,7 +70,10 @@ describe('human page input', () => {
69
70
  });
70
71
  test('send help keeps page authoring at the human branch and makes reply routing explicit', () => {
71
72
  const rendered = renderLeafArgv(humanSend.help);
72
- assert.match(rendered, /^human send: send a durable TSX or HTML page\./);
73
+ assert.match(rendered, /^human send: send a durable TSX page\./);
74
+ // HTML is named on the send leaf only where an HTML page can be read.
75
+ assert.doesNotMatch(rendered, /HTML|\.html/);
76
+ assert.match(renderLeafArgv(buildHumanSend(true).help), /^human send: send a durable TSX or HTML page\./);
73
77
  assert.match(rendered, /Page authoring: `crtr human -h`\./);
74
78
  assert.doesNotMatch(rendered, /--no-reply|--reply/);
75
79
  assert.match(rendered, /page_id/);
@@ -85,7 +89,12 @@ test('human help carries the page authoring model and follows the page-surface s
85
89
  assert.ok(human.help.model?.includes(help.authoring));
86
90
  assert.ok(human.help.model?.includes('`crtr human components`'));
87
91
  assert.doesNotMatch(human.help.model ?? '', /doc check/);
88
- assert.match(human.help.model ?? '', /\.html/);
92
+ // A terminal renders an HTML page as its title and nothing else, so the
93
+ // dialect is not offered here; what IS offered is the reason each question
94
+ // must stand alone on this host.
95
+ assert.doesNotMatch(human.help.model ?? '', /\.html/);
96
+ assert.match(human.help.model ?? '', /one question at a time/);
97
+ assert.match(human.help.model ?? '', /rather than running your module/);
89
98
  });
90
99
  withPageSurface(true, () => {
91
100
  const human = registerHuman();
@@ -93,6 +102,12 @@ test('human help carries the page authoring model and follows the page-surface s
93
102
  assert.notEqual(help.authoring, pageHelpText(false).authoring);
94
103
  assert.equal(human.rootEntry?.useWhen, help.rootUseWhen);
95
104
  assert.ok(human.help.model?.includes(help.authoring));
105
+ assert.match(human.help.model ?? '', /\.html/);
106
+ // The flattening warning is terminal-only: a page surface runs the module.
107
+ assert.doesNotMatch(human.help.model ?? '', /rather than running your module/);
108
+ // Both surfaces reach for pages by default; only the reason differs.
109
+ for (const surface of [true, false])
110
+ assert.match(pageHelpText(surface).authoring, /Use inline pages as the default way/);
96
111
  });
97
112
  });
98
113
  // The gate itself, not its prose: a page-surface host must not carry the two
@@ -2,13 +2,13 @@ import { test } from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
3
  import { renderLeafArgv } from '../../core/help.js';
4
4
  import { nodeMessage } from '../node/message.js';
5
- test('node message send help documents the finalized-node re-task gate', () => {
5
+ test('node message send help documents the reopen re-task semantics', () => {
6
6
  const send = nodeMessage.children.find((child) => child.kind === 'leaf' && child.name === 'send');
7
7
  if (send === undefined || send.kind !== 'leaf')
8
8
  assert.fail('node message send leaf is missing');
9
9
  const rendered = renderLeafArgv(send.help);
10
10
  assert.match(rendered, /--reopen/);
11
- assert.match(rendered, /Required to re-task a finalized target/);
12
- assert.match(rendered, /clears a finalized target's finalization latch before delivery/);
11
+ assert.match(rendered, /Re-task the target: commits it resident and clears any finalization latch before delivery/);
12
+ assert.match(rendered, /valid for live, parked, and finalized targets/);
13
13
  assert.match(rendered, /Without it, a finalized target rejects delivery before an inbox entry is appended or a revive is attempted/);
14
14
  });
@@ -18,7 +18,7 @@ import { closeSync, fstatSync, openSync, readSync } from 'node:fs';
18
18
  import { join } from 'node:path';
19
19
  import { CrtrClient, ApiError } from '../api/index.js';
20
20
  import { apiErrorToCliError } from '../core/io.js';
21
- import { ensureDaemon } from '../daemon/manage.js';
21
+ import { autostartDisabled, ensureDaemon } from '../daemon/manage.js';
22
22
  import { DAEMON_STARTUP_WINDOW_MS } from '../daemon/startup-policy.js';
23
23
  import { readState } from '../core/config.js';
24
24
  import { resolveRemoteCanvasTarget } from '../core/remote-canvas-target.js';
@@ -60,12 +60,6 @@ export async function getNodeOrNull(nodeId) {
60
60
  // ---------------------------------------------------------------------------
61
61
  // cliClient — the one place the CLI constructs a daemon client
62
62
  // ---------------------------------------------------------------------------
63
- /** Whether autostart is suppressed for this invocation. `--no-autostart` or
64
- * `CRTR_NO_DAEMON_AUTOSTART=1` forces fail-loud on a cold socket (spec §7.1) —
65
- * for environments that manage crtrd externally and want the diagnostic. */
66
- function autostartDisabled() {
67
- return process.env['CRTR_NO_DAEMON_AUTOSTART'] === '1' || process.argv.includes('--no-autostart');
68
- }
69
63
  /** Bounded tail of crtrd.err surfaced on a cold-start `/healthz` timeout.
70
64
  * Large enough to catch a real startup failure/stack trace,
71
65
  * small enough that a runaway daemon log can never leak unbounded into a CLI
@@ -41,13 +41,7 @@ const canvasConfigAdd = defineLeaf({
41
41
  { kind: 'flag', name: 'relay-token', type: 'string', required: true, constraint: 'Must be exactly `-` — the raw token is read from stdin, never argv (keeps it out of shell history). Any other value is rejected.' },
42
42
  { kind: 'flag', name: 'cp', type: 'string', required: false, constraint: 'Optional control-plane origin, when this target has one distinct from the preview endpoint.' },
43
43
  ],
44
- output: [
45
- { name: 'name', type: 'string', required: true, constraint: 'The target name just registered.' },
46
- { name: 'previewEndpoint', type: 'string', required: true, constraint: 'The stored preview endpoint.' },
47
- { name: 'relayTokenRef', type: 'string', required: true, constraint: 'The secrets-store key the token was written under (equals name).' },
48
- { name: 'cpOrigin', type: 'string', required: false, constraint: 'The stored control-plane origin, when --cp was given.' },
49
- { name: 'tokenStored', type: 'boolean', required: true, constraint: 'Always true on success \u2014 confirms the token was written to the 0600 store.' },
50
- ],
44
+ output: [],
51
45
  outputKind: 'object',
52
46
  effects: ['Writes remoteCanvas.targets.<name> to config.json (previewEndpoint, relayTokenRef, cpOrigin).', 'Writes the raw token to the 0600 secrets store under relayTokenRef \u2014 never to config.json or stdout.'],
53
47
  },
@@ -78,9 +72,9 @@ const canvasConfigAdd = defineLeaf({
78
72
  cfg.remoteCanvas.targets[name] = target;
79
73
  });
80
74
  setRelayToken(name, token, SCOPE);
81
- return { name, previewEndpoint, relayTokenRef: name, ...(cp ? { cpOrigin: cp } : {}), tokenStored: true };
75
+ return {};
82
76
  },
83
- render: (r) => `Registered remote canvas target '${r['name']}' → ${r['previewEndpoint']} (token stored).`,
77
+ render: () => 'Registered remote canvas target (token stored).',
84
78
  });
85
79
  // ---------------------------------------------------------------------------
86
80
  // canvas config list
@@ -169,9 +163,7 @@ const canvasConfigRemove = defineLeaf({
169
163
  params: [
170
164
  { kind: 'positional', name: 'name', required: true, constraint: 'Target name.' },
171
165
  ],
172
- output: [
173
- { name: 'removed', type: 'string', required: true, constraint: 'The target name removed.' },
174
- ],
166
+ output: [],
175
167
  outputKind: 'object',
176
168
  effects: ['Deletes remoteCanvas.targets.<name> from config.json.', 'Deletes the stored token under relayTokenRef from the secrets store.'],
177
169
  },
@@ -190,9 +182,9 @@ const canvasConfigRemove = defineLeaf({
190
182
  delete c.remoteCanvas.targets[name];
191
183
  });
192
184
  deleteRelayToken(target.relayTokenRef, SCOPE);
193
- return { removed: name };
185
+ return {};
194
186
  },
195
- render: (r) => `Removed remote canvas target '${r['removed']}'.`,
187
+ render: () => 'Removed remote canvas target.'
196
188
  });
197
189
  // ---------------------------------------------------------------------------
198
190
  // Export — mounted under `crtr canvas`
@@ -18,9 +18,7 @@ export const canvasUseLeaf = defineLeaf({
18
18
  { kind: 'positional', name: 'name', required: false, constraint: 'A remote canvas target registered via `crtr canvas config add`. Mutually exclusive with --local; exactly one of the two is required.' },
19
19
  { kind: 'flag', name: 'local', type: 'bool', required: false, constraint: 'Switch back to the local canvas. Mutually exclusive with <name>.' },
20
20
  ],
21
- output: [
22
- { name: 'active', type: 'string', required: false, constraint: 'The now-active target name, or omitted/null for local.' },
23
- ],
21
+ output: [],
24
22
  outputKind: 'object',
25
23
  effects: ['Writes state.json\u2019s activeCanvas (user scope).'],
26
24
  },
@@ -45,7 +43,7 @@ export const canvasUseLeaf = defineLeaf({
45
43
  updateState(SCOPE, (s) => {
46
44
  s.activeCanvas = null;
47
45
  });
48
- return { active: null };
46
+ return {};
49
47
  }
50
48
  if (getRemoteCanvasTarget(name, SCOPE) === undefined) {
51
49
  throw new InputError({
@@ -57,7 +55,7 @@ export const canvasUseLeaf = defineLeaf({
57
55
  updateState(SCOPE, (s) => {
58
56
  s.activeCanvas = name;
59
57
  });
60
- return { active: name };
58
+ return {};
61
59
  },
62
- render: (r) => (r['active'] ? `Active canvas: ${r['active']}.` : 'Active canvas: local.'),
60
+ render: () => 'Canvas selection updated.'
63
61
  });
@@ -18,14 +18,14 @@ import { resolve as resolvePath } from 'node:path';
18
18
  import { defineBranch, defineLeaf } from '../core/command.js';
19
19
  import { InputError, readStdinRaw } from '../core/io.js';
20
20
  import { parseWhen, parseCadence, cadenceDisplay } from '../core/wake.js';
21
- import { loadProfileManifest } from '../core/profiles/manifest.js';
21
+ import { resolveProfileOperand } from '../core/profiles/manifest.js';
22
22
  import { profilesStateBlock } from '../core/profiles/state-block.js';
23
23
  import { cliClient, rethrowAsCliError } from './api-client.js';
24
- /** Canonicalize a profile operand (id OR manifest name) to its profile id so
24
+ /** Canonicalize a profile operand (id, manifest name, or nearest match) to its profile id so
25
25
  * every cron row and scope query speaks the same identity. Resolution errors
26
26
  * name the available profiles on this host. */
27
27
  function canonicalProfileId(raw) {
28
- return loadProfileManifest(raw.trim()).profileId;
28
+ return resolveProfileOperand(raw.trim()).profileId;
29
29
  }
30
30
  /** Build ParseOpts, including `tz` only when actually provided. */
31
31
  function parseOpts(now, tz) {
@@ -147,12 +147,11 @@ const addLeaf = defineLeaf({
147
147
  { kind: 'flag', name: 'overlap', type: 'enum', choices: ['skip', 'queue', 'replace'], required: false, default: 'skip', constraint: 'A fire landing while the previous run is still going. skip (default): log the skip, do not start a second run. queue: run once more after the current finishes (depth 1; further fires collapse). replace: kill the running instance and start fresh.' },
148
148
  { kind: 'flag', name: 'cwd', type: 'path', required: false, constraint: 'Directory every run executes in. Defaults to this one, must exist now, and is snapshotted onto the row — a cron never re-resolves its context.' },
149
149
  { kind: 'flag', name: 'env', type: 'string', required: false, repeatable: true, constraint: 'KEY=VALUE added to every run\'s env (repeat per variable). Snapshotted at arm time; overlays the operational env, alongside $CRTR_CRON_ID/$CRTR_CRON_NAME/$CRTR_RUN_ID.' },
150
- { kind: 'flag', name: 'profile', type: 'string', required: false, constraint: 'Profile the runs resolve config and credentials under, and — under --scope profile — the profile this cron lists and cancels within. A profile id or unique manifest name from the <profiles> list below; anything else is rejected. Defaults to the calling node\'s ($CRTR_PROFILE_ID).' },
150
+ { kind: 'flag', name: 'profile', type: 'string', required: false, constraint: 'Profile the runs resolve config and credentials under, and — under --scope profile — the profile this cron lists and cancels within. A profile id or manifest name from the <profiles> list below; a close-enough name resolves to the nearest profile. Defaults to the calling node\'s ($CRTR_PROFILE_ID).' },
151
151
  { kind: 'flag', name: 'scope', type: 'enum', choices: ['profile', 'global'], required: false, constraint: 'Who lists and cancels this cron: profile (default when a profile is resolved) confines it to that profile; global makes it canvas-home-wide, visible from every profile.' },
152
152
  ],
153
153
  output: [
154
- { name: 'cron_id', type: 'string', required: true, constraint: 'The armed cron — inspect with `cron show <cron_id>`, cancel with `cron cancel <cron_id>`.' },
155
- { name: 'name', type: 'string', required: true, constraint: 'The label as armed.' },
154
+ { name: 'cron_id', type: 'string', required: true, constraint: 'The server-minted armed cron id — inspect with `cron show <cron_id>`, cancel with `cron cancel <cron_id>`.' },
156
155
  { name: 'fire_at', type: 'string', required: true, constraint: 'Next fire (UTC ISO).' },
157
156
  { name: 'recur', type: 'string', required: true, constraint: 'Cadence display; "none" for a one-shot.' },
158
157
  ],
@@ -267,12 +266,12 @@ const addLeaf = defineLeaf({
267
266
  overlap: input['overlap'] ?? 'skip',
268
267
  })
269
268
  .catch(rethrowAsCliError);
270
- return { cron_id: dto.cron_id, name: dto.name, fire_at: dto.fire_at, recur: cadenceDisplay(dto.recur) };
269
+ return { cron_id: dto.cron_id, fire_at: dto.fire_at, recur: cadenceDisplay(dto.recur) };
271
270
  },
272
271
  render: (r) => {
273
272
  const recur = String(r['recur']);
274
273
  const cadence = recur === 'none' ? 'one-shot (row deleted after the run)' : recur;
275
- return `Cron armed — ${String(r['name'])} (${String(r['cron_id'])}).\n\n- next fire: ${String(r['fire_at'])}\n- cadence: ${cadence}\n\nIt runs daemon-side; nothing more to do here. Inspect it with \`crtr cron show ${String(r['cron_id'])}\`; cancel any time with \`crtr cron cancel ${String(r['cron_id'])}\`.`;
274
+ return `Cron armed — ${String(r['cron_id'])}.\n\n- next fire: ${String(r['fire_at'])}\n- cadence: ${cadence}\n\nIt runs daemon-side; nothing more to do here. Inspect it with \`crtr cron show ${String(r['cron_id'])}\`; cancel any time with \`crtr cron cancel ${String(r['cron_id'])}\`.`;
276
275
  },
277
276
  });
278
277
  // ---------------------------------------------------------------------------
@@ -460,19 +459,16 @@ const pauseLeaf = defineLeaf({
460
459
  params: [
461
460
  { kind: 'positional', name: 'cron-id', required: true, constraint: 'The cron to pause (from `cron list`).' },
462
461
  ],
463
- output: [
464
- { name: 'cron_id', type: 'string', required: true, constraint: 'The paused cron.' },
465
- { name: 'state', type: 'string', required: true, constraint: 'Always "paused".' },
466
- ],
462
+ output: [],
467
463
  outputKind: 'object',
468
464
  effects: ['Stops the cron firing until `cron resume`. An in-flight run settles normally; the row just stops being due.'],
469
465
  },
470
466
  run: async (input) => {
471
467
  const id = requireCronId(input);
472
- const dto = await cliClient().pauseCron(id, scopeQuery()).catch((err) => rethrowAsCliError(err, 'Check the id against `crtr cron list`.'));
473
- return { cron_id: dto.cron_id, state: dto.state };
468
+ await cliClient().pauseCron(id, scopeQuery()).catch((err) => rethrowAsCliError(err, 'Check the id against `crtr cron list`.'));
469
+ return {};
474
470
  },
475
- render: (r) => `Cron ${String(r['cron_id'])} paused — it will not fire until \`crtr cron resume ${String(r['cron_id'])}\`.`,
471
+ render: () => 'Cron paused.'
476
472
  });
477
473
  const resumeLeaf = defineLeaf({
478
474
  name: 'resume',
@@ -485,8 +481,6 @@ const resumeLeaf = defineLeaf({
485
481
  { kind: 'positional', name: 'cron-id', required: true, constraint: 'The cron to resume (from `cron list`).' },
486
482
  ],
487
483
  output: [
488
- { name: 'cron_id', type: 'string', required: true, constraint: 'The resumed cron.' },
489
- { name: 'state', type: 'string', required: true, constraint: 'Always "active".' },
490
484
  { name: 'fire_at', type: 'string', required: true, constraint: 'Next fire — if already past, it fires on the next daemon tick.' },
491
485
  ],
492
486
  outputKind: 'object',
@@ -495,9 +489,9 @@ const resumeLeaf = defineLeaf({
495
489
  run: async (input) => {
496
490
  const id = requireCronId(input);
497
491
  const dto = await cliClient().resumeCron(id, scopeQuery()).catch((err) => rethrowAsCliError(err, 'Check the id against `crtr cron list`.'));
498
- return { cron_id: dto.cron_id, state: dto.state, fire_at: dto.fire_at };
492
+ return { fire_at: dto.fire_at };
499
493
  },
500
- render: (r) => `Cron ${String(r['cron_id'])} resumed — next fire ${String(r['fire_at'])} (if that is already past, it fires on the next tick).`,
494
+ render: (r) => `Cron resumed — next fire ${String(r['fire_at'])} (if that is already past, it fires on the next tick).`,
501
495
  });
502
496
  // ---------------------------------------------------------------------------
503
497
  // cron cancel
@@ -512,7 +506,7 @@ const cancelLeaf = defineLeaf({
512
506
  params: [
513
507
  { kind: 'positional', name: 'cron-id', required: true, constraint: 'The cron to remove (from `cron list`, or $CRTR_CRON_ID inside a run).' },
514
508
  ],
515
- output: [{ name: 'canceled', type: 'string', required: true, constraint: 'The removed (or already-absent) cron id.' }],
509
+ output: [],
516
510
  outputKind: 'object',
517
511
  effects: ['Deletes the cron row and its run history; kills any in-flight run. A no-op if already gone.'],
518
512
  },
@@ -525,9 +519,9 @@ const cancelLeaf = defineLeaf({
525
519
  ...(runId === undefined || runId === '' ? {} : { run_id: runId }),
526
520
  })
527
521
  .catch((err) => rethrowAsCliError(err, 'Check the id against `crtr cron list`.'));
528
- return { canceled: id };
522
+ return {};
529
523
  },
530
- render: (r) => `Cron ${String(r['canceled'])} canceled — it will not fire again.`,
524
+ render: () => 'Cron canceled.'
531
525
  });
532
526
  // ---------------------------------------------------------------------------
533
527
  // Registration
@@ -17,5 +17,5 @@ export declare function sender(): Promise<{
17
17
  source: PageSource;
18
18
  lifecycle: 'terminal' | 'resident' | null;
19
19
  }>;
20
- export declare const humanSend: import("../../core/command.js").LeafDef;
20
+ export declare function humanSend(pageSurface?: boolean): import("../../core/command.js").LeafDef;
21
21
  export declare const humanShow: import("../../core/command.js").LeafDef;
@@ -15,7 +15,10 @@ import { pageManifestPath } from '../../core/human/convention.js';
15
15
  import { isResponseBearingSlot, validateAuthoredPageManifest } from '../../core/human/page-schema.js';
16
16
  import { display } from '../../core/termrender/display.js';
17
17
  import { pageHelpText, resolveMaxPanes } from './shared.js';
18
- const PAGE_HELP = pageHelpText();
18
+ // Only `pageHint` is surface-independent; everything else this file reads off
19
+ // PageHelpText is chosen per host, so the send leaf is built for a surface
20
+ // rather than defined once as a const.
21
+ const PAGE_HINT = pageHelpText().pageHint;
19
22
  /** The asking node's id, or null when run from a bare shell (no parent to route to). */
20
23
  function askingNode() {
21
24
  return process.env['CRTR_NODE_ID'] ?? null;
@@ -159,122 +162,125 @@ function queuedFollowUp() {
159
162
  // ---------------------------------------------------------------------------
160
163
  // send
161
164
  // ---------------------------------------------------------------------------
162
- export const humanSend = defineLeaf({
163
- name: 'send',
164
- description: 'send a durable page with explicit delivery',
165
- whenToUse: 'you need to put structured TSX or HTML content in a conversation and optionally project it to the inbox; a page carrying response-bearing components routes completion back to this node.',
166
- help: {
167
- name: 'human send',
168
- summary: 'send a durable TSX or HTML page',
169
- guide: 'Page authoring: `crtr human -h`.',
170
- params: [
171
- { kind: 'stdin', name: 'document', required: false, allowPositional: false, constraint: 'The page module source (TSX) on stdin. Provide exactly one of stdin or --page PATH. HTML is file-authored.' },
172
- { kind: 'flag', name: 'page', type: 'path', required: false, constraint: `${PAGE_HELP.pageHint} A .tsx file is JSX; .html/.htm is complete HTML. Provide exactly one of --page or stdin.` },
173
- { kind: 'flag', name: 'placement', type: 'enum', choices: ['inline', 'panel'], required: false, default: 'inline', constraint: "Where the page is delivered. inline renders in this conversation's transcript; panel opens from the inbox beside it." },
174
- { kind: 'flag', name: 'inbox', type: 'bool', required: false, constraint: 'Project this page into the inbox. Mutually exclusive with --no-inbox.' },
175
- { kind: 'flag', name: 'no-inbox', type: 'bool', required: false, constraint: "Do not project this page into the inbox, leaving this conversation's transcript as its only route to the user. Mutually exclusive with --inbox, and refused from a terminal node, whose transcript nobody reads once it finishes." },
176
- { kind: 'flag', name: 'replace', type: 'string', required: false, constraint: 'Page id returned by an earlier send. Rewrites source in place while retaining its delivery and transcript anchor. Refused once the page is settled or canceled. Not combinable with delivery flags.' },
177
- ],
178
- output: [
179
- { name: 'page_id', type: 'string', required: true, constraint: 'Identifier for the page; pass it as --replace to revise source in place.' },
180
- { name: 'dir', type: 'string', required: true, constraint: 'Interaction directory holding page.tsx or page.html, optional page.js, page.json, and response lifecycle files when delivery needs them.' },
181
- { name: 'placement', type: 'inline | panel', required: true, constraint: 'Resolved page placement.' },
182
- { name: 'inbox', type: 'boolean', required: true, constraint: 'Whether the page is projected into the inbox.' },
183
- { name: 'reply', type: 'boolean', required: true, constraint: 'Whether completion is routed back through a human bridge. Not a flag: true exactly when the page has response-bearing components.' },
184
- { name: 'follow_up', type: 'string', required: true, constraint: 'What happens next; never instructs the caller to poll.' },
185
- ],
186
- outputKind: 'object',
187
- effects: [
188
- 'Writes one durable page record. Inbox projection follows the delivery flags, except from a terminal node, which always projects; reply routing follows the page\'s own components.',
189
- "A display-only page's inbox entry is a notice, not a task: it clears itself once the person has actually seen the page, wherever they see it.",
190
- 'A reply-bearing page creates a kind:"human" bridge under the caller and routes completion back.',
191
- '--replace rewrites source in place while retaining delivery and emitted time; open surfaces reload it without creating another page.',
192
- ],
193
- },
194
- run: async (input, context) => {
195
- const { sourceFile, tempDir } = pageInput(input);
196
- const replacePage = input['replace'];
197
- const deliveryFlags = ['placement', 'inbox', 'noInbox'];
198
- if (input['inbox'] === true && input['noInbox'] === true) {
199
- throw new InputError({ error: 'bad_invocation', message: 'received conflicting delivery flags', field: 'inbox', next: 'Pass at most one of --inbox and --no-inbox.' });
200
- }
201
- if (replacePage !== undefined && deliveryFlags.some((flag) => context?.providedParams.has(flag) === true)) {
202
- throw new InputError({ error: 'bad_invocation', message: '--replace cannot change delivery', field: 'replace', next: 'Remove delivery flags: replacement retains the page placement, inbox, reply, and emitted time.' });
203
- }
204
- try {
205
- if (replacePage !== undefined) {
206
- const prior = replacedPage(replacePage);
207
- // The human's submit wins the race with a revision: once the ticket has
208
- // any terminal outcome, replacement is refused, and that refusal is the
209
- // reviser's signal that the ticket is closed.
210
- if (readTicketResult(ticketDir(replacePage)) !== null) {
165
+ export function humanSend(pageSurface = false) {
166
+ const pageHelp = pageHelpText(pageSurface);
167
+ return defineLeaf({
168
+ name: 'send',
169
+ description: 'send a durable page with explicit delivery',
170
+ whenToUse: pageHelp.sendWhenToUse,
171
+ help: {
172
+ name: 'human send',
173
+ summary: pageHelp.sendSummary,
174
+ guide: 'Page authoring: `crtr human -h`.',
175
+ params: [
176
+ { kind: 'stdin', name: 'document', required: false, allowPositional: false, constraint: pageHelp.stdinConstraint },
177
+ { kind: 'flag', name: 'page', type: 'path', required: false, constraint: `${PAGE_HINT} ${pageHelp.pageFlagConstraint}` },
178
+ { kind: 'flag', name: 'placement', type: 'enum', choices: ['inline', 'panel'], required: false, default: 'inline', constraint: "Where the page is delivered. inline renders in this conversation's transcript; panel opens from the inbox beside it." },
179
+ { kind: 'flag', name: 'inbox', type: 'bool', required: false, constraint: 'Project this page into the inbox. Mutually exclusive with --no-inbox.' },
180
+ { kind: 'flag', name: 'no-inbox', type: 'bool', required: false, constraint: "Do not project this page into the inbox, leaving this conversation's transcript as its only route to the user. Mutually exclusive with --inbox, and refused from a terminal node, whose transcript nobody reads once it finishes." },
181
+ { kind: 'flag', name: 'replace', type: 'string', required: false, constraint: 'Page id returned by an earlier send. Rewrites source in place while retaining its delivery and transcript anchor. Refused once the page is settled or canceled. Not combinable with delivery flags.' },
182
+ ],
183
+ output: [
184
+ { name: 'page_id', type: 'string', required: true, constraint: 'Identifier for the page; pass it as --replace to revise source in place.' },
185
+ { name: 'dir', type: 'string', required: true, constraint: pageHelp.dirConstraint },
186
+ { name: 'placement', type: 'inline | panel', required: true, constraint: 'Resolved page placement.' },
187
+ { name: 'inbox', type: 'boolean', required: true, constraint: 'Whether the page is projected into the inbox.' },
188
+ { name: 'reply', type: 'boolean', required: true, constraint: 'Whether completion is routed back through a human bridge. Not a flag: true exactly when the page has response-bearing components.' },
189
+ { name: 'follow_up', type: 'string', required: true, constraint: 'What happens next; never instructs the caller to poll.' },
190
+ ],
191
+ outputKind: 'object',
192
+ effects: [
193
+ 'Writes one durable page record. Inbox projection follows the delivery flags, except from a terminal node, which always projects; reply routing follows the page\'s own components.',
194
+ "A display-only page's inbox entry is a notice, not a task: it clears itself once the person has actually seen the page, wherever they see it.",
195
+ 'A reply-bearing page creates a kind:"human" bridge under the caller and routes completion back.',
196
+ '--replace rewrites source in place while retaining delivery and emitted time; open surfaces reload it without creating another page.',
197
+ ],
198
+ },
199
+ run: async (input, context) => {
200
+ const { sourceFile, tempDir } = pageInput(input);
201
+ const replacePage = input['replace'];
202
+ const deliveryFlags = ['placement', 'inbox', 'noInbox'];
203
+ if (input['inbox'] === true && input['noInbox'] === true) {
204
+ throw new InputError({ error: 'bad_invocation', message: 'received conflicting delivery flags', field: 'inbox', next: 'Pass at most one of --inbox and --no-inbox.' });
205
+ }
206
+ if (replacePage !== undefined && deliveryFlags.some((flag) => context?.providedParams.has(flag) === true)) {
207
+ throw new InputError({ error: 'bad_invocation', message: '--replace cannot change delivery', field: 'replace', next: 'Remove delivery flags: replacement retains the page placement, inbox, reply, and emitted time.' });
208
+ }
209
+ try {
210
+ if (replacePage !== undefined) {
211
+ const prior = replacedPage(replacePage);
212
+ // The human's submit wins the race with a revision: once the ticket has
213
+ // any terminal outcome, replacement is refused, and that refusal is the
214
+ // reviser's signal that the ticket is closed.
215
+ if (readTicketResult(ticketDir(replacePage)) !== null) {
216
+ throw new InputError({
217
+ error: 'page_settled',
218
+ message: `page ${replacePage} already has a terminal outcome`,
219
+ field: 'replace',
220
+ next: 'The page was answered or canceled; nothing on it can change now. Raise a new page if more is needed.',
221
+ });
222
+ }
223
+ // A revision changes only the content. Identity, routing, and emitted
224
+ // time stay the original sender's, so the prior source is inherited
225
+ // verbatim — a fork revising the page must not repoint its provenance.
226
+ const revised = submitAuthoredPage(() => preparePage({ sourceFile, source: prior.source, productKinds: pageProductKinds(), delivery: prior.delivery }));
227
+ const { dir } = publishPage(ticketDir(replacePage), revised);
228
+ return { page_id: replacePage, dir, ...prior.delivery, follow_up: 'Replaced in place. Every open surface reloads it; delivery and emitted time are unchanged.' };
229
+ }
230
+ const { source, lifecycle } = await sender();
231
+ // A terminal node finishes and its transcript stops being read, so the
232
+ // inbox is the only surface its page can still reach a human on. Refuse
233
+ // rather than quietly overriding the flag: the caller asked for a page
234
+ // nobody would ever see, and that is worth knowing.
235
+ if (lifecycle === 'terminal' && input['noInbox'] === true) {
211
236
  throw new InputError({
212
- error: 'page_settled',
213
- message: `page ${replacePage} already has a terminal outcome`,
214
- field: 'replace',
215
- next: 'The page was answered or canceled; nothing on it can change now. Raise a new page if more is needed.',
237
+ error: 'invalid_page_delivery',
238
+ message: 'a terminal node cannot send a page --no-inbox',
239
+ field: 'inbox',
240
+ next: 'Drop --no-inbox. This node finishes, and nobody reads a finished node\'s transcript, so a page kept out of the inbox reaches no one.',
216
241
  });
217
242
  }
218
- // A revision changes only the content. Identity, routing, and emitted
219
- // time stay the original sender's, so the prior source is inherited
220
- // verbatim — a fork revising the page must not repoint its provenance.
221
- const revised = submitAuthoredPage(() => preparePage({ sourceFile, source: prior.source, productKinds: pageProductKinds(), delivery: prior.delivery }));
222
- const { dir } = publishPage(ticketDir(replacePage), revised);
223
- return { page_id: replacePage, dir, ...prior.delivery, follow_up: 'Replaced in place. Every open surface reloads it; delivery and emitted time are unchanged.' };
224
- }
225
- const { source, lifecycle } = await sender();
226
- // A terminal node finishes and its transcript stops being read, so the
227
- // inbox is the only surface its page can still reach a human on. Refuse
228
- // rather than quietly overriding the flag: the caller asked for a page
229
- // nobody would ever see, and that is worth knowing.
230
- if (lifecycle === 'terminal' && input['noInbox'] === true) {
231
- throw new InputError({
232
- error: 'invalid_page_delivery',
233
- message: 'a terminal node cannot send a page --no-inbox',
234
- field: 'inbox',
235
- next: 'Drop --no-inbox. This node finishes, and nobody reads a finished node\'s transcript, so a page kept out of the inbox reaches no one.',
236
- });
243
+ // Static extraction is an evaluation. Derive and validate delivery from this
244
+ // exact manifest so authored code runs once and cannot change between passes.
245
+ const productKinds = pageProductKinds();
246
+ const prepared = submitAuthoredPage(() => preparePage({ sourceFile, source, productKinds }));
247
+ const responseBearing = prepared.manifest.slots.some(isResponseBearingSlot);
248
+ const delivery = deliveryFrom(input, responseBearing, lifecycle);
249
+ prepared.manifest = submitAuthoredPage(() => validateAuthoredPageManifest({ ...prepared.manifest, delivery }, productKinds));
250
+ if (!delivery.reply) {
251
+ const pageId = `page-${randomBytes(4).toString('hex')}`;
252
+ const { dir } = publishPage(ticketDir(pageId), prepared);
253
+ // A page with no reply route carries no response-bearing components, so what the
254
+ // follow-up must get right is where the person will meet it and how it
255
+ // settles: an inline page is already in this conversation, and its
256
+ // inbox entry is a notice that clears itself the moment the page has
257
+ // actually been seen — an agent that points the person at their inbox
258
+ // for a page on their screen sends them to an empty list.
259
+ const followUp = delivery.placement === 'inline' && delivery.inbox
260
+ ? 'Shown in this conversation and queued in the user\'s inbox.'
261
+ : delivery.placement === 'inline'
262
+ ? 'Shown in this conversation as passive page content with no inbox entry.'
263
+ : delivery.inbox
264
+ ? 'Queued in the inbox; it opens in the panel and waits until dismissed.'
265
+ : 'Published as passive page content with no inbox or response lifecycle.';
266
+ return { page_id: pageId, dir, ...delivery, follow_up: followUp };
267
+ }
268
+ const originNodeId = askingNode();
269
+ const bridgeNodeId = (await cliClient().createHumanBridge({ kind: 'human', parent: originNodeId, cwd: process.cwd(), name: 'human-page' })).node_id;
270
+ const idir = ticketDir(bridgeNodeId);
271
+ bindTicketReplyRoute(idir, bridgeNodeId);
272
+ const { dir } = publishPage(idir, prepared);
273
+ if (originNodeId !== null && originNodeId !== '')
274
+ markStopSignal(originNodeId, 'requestedHumanReply');
275
+ return { page_id: bridgeNodeId, dir, ...delivery, follow_up: queuedFollowUp() };
237
276
  }
238
- // Static extraction is an evaluation. Derive and validate delivery from this
239
- // exact manifest so authored code runs once and cannot change between passes.
240
- const productKinds = pageProductKinds();
241
- const prepared = submitAuthoredPage(() => preparePage({ sourceFile, source, productKinds }));
242
- const responseBearing = prepared.manifest.slots.some(isResponseBearingSlot);
243
- const delivery = deliveryFrom(input, responseBearing, lifecycle);
244
- prepared.manifest = submitAuthoredPage(() => validateAuthoredPageManifest({ ...prepared.manifest, delivery }, productKinds));
245
- if (!delivery.reply) {
246
- const pageId = `page-${randomBytes(4).toString('hex')}`;
247
- const { dir } = publishPage(ticketDir(pageId), prepared);
248
- // A page with no reply route carries no response-bearing components, so what the
249
- // follow-up must get right is where the person will meet it and how it
250
- // settles: an inline page is already in this conversation, and its
251
- // inbox entry is a notice that clears itself the moment the page has
252
- // actually been seen — an agent that points the person at their inbox
253
- // for a page on their screen sends them to an empty list.
254
- const followUp = delivery.placement === 'inline' && delivery.inbox
255
- ? 'Shown in this conversation and queued in the user\'s inbox. The inbox entry clears itself the user has seen the page — someone viewing this conversation acknowledges it on sight, so do not send them to their inbox for it. They already know this, do not inform them.'
256
- : delivery.placement === 'inline'
257
- ? 'Shown in this conversation as passive page content with no inbox entry.'
258
- : delivery.inbox
259
- ? 'Queued in the inbox; it opens in the panel and waits until dismissed.'
260
- : 'Published as passive page content with no inbox or response lifecycle.';
261
- return { page_id: pageId, dir, ...delivery, follow_up: followUp };
277
+ finally {
278
+ if (tempDir !== undefined)
279
+ rmSync(tempDir, { recursive: true, force: true });
262
280
  }
263
- const originNodeId = askingNode();
264
- const bridgeNodeId = (await cliClient().createHumanBridge({ kind: 'human', parent: originNodeId, cwd: process.cwd(), name: 'human-page' })).node_id;
265
- const idir = ticketDir(bridgeNodeId);
266
- bindTicketReplyRoute(idir, bridgeNodeId);
267
- const { dir } = publishPage(idir, prepared);
268
- if (originNodeId !== null && originNodeId !== '')
269
- markStopSignal(originNodeId, 'requestedHumanReply');
270
- return { page_id: bridgeNodeId, dir, ...delivery, follow_up: queuedFollowUp() };
271
- }
272
- finally {
273
- if (tempDir !== undefined)
274
- rmSync(tempDir, { recursive: true, force: true });
275
- }
276
- },
277
- });
281
+ },
282
+ });
283
+ }
278
284
  // ---------------------------------------------------------------------------
279
285
  // show (no job, non-blocking passthrough) — passive live document display, NOT
280
286
  // a ticket surface. `display()` puts a file live in a watch pane and never