@north-light/crouter 0.3.197 → 0.3.199

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 (339) hide show
  1. package/dist/api/client.d.ts +19 -19
  2. package/dist/api/client.js +29 -37
  3. package/dist/api/dto/broker-ops.d.ts +3 -1
  4. package/dist/api/dto/broker.d.ts +13 -2
  5. package/dist/api/dto/broker.js +4 -4
  6. package/dist/api/dto/human.d.ts +6 -5
  7. package/dist/api/dto/inbox.d.ts +126 -33
  8. package/dist/api/dto/nodes.d.ts +8 -2
  9. package/dist/api/dto/profiles.d.ts +23 -0
  10. package/dist/api/dto/profiles.js +2 -2
  11. package/dist/api/dto/worktree.d.ts +3 -5
  12. package/dist/api/routes.d.ts +3 -5
  13. package/dist/api/routes.js +4 -6
  14. package/dist/builtin-memory/00-runtime-base.md +8 -8
  15. package/dist/builtin-memory/01-spine/01-no-manager.md +1 -1
  16. package/dist/builtin-memory/02-lifecycle/00-terminal.md +3 -3
  17. package/dist/builtin-memory/04-orchestration-kernel.md +6 -6
  18. package/dist/builtin-memory/05-kinds/design/00-base.md +1 -1
  19. package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +2 -2
  20. package/dist/builtin-memory/05-kinds/review/00-base.md +2 -2
  21. package/dist/builtin-memory/05-kinds/review/01-orchestrator.md +1 -1
  22. package/dist/builtin-memory/05-kinds/spec/01-orchestrator.md +1 -1
  23. package/dist/builtin-memory/insights/capture.md +2 -2
  24. package/dist/builtin-memory/insights/init.md +3 -3
  25. package/dist/builtin-memory/internal/INDEX.md +1 -1
  26. package/dist/builtin-memory/internal/memory-loading.md +1 -1
  27. package/dist/builtin-memory/internal/nodes-and-canvas.md +3 -3
  28. package/dist/builtin-memory/internal/plugins.md +59 -2
  29. package/dist/builtin-memory/internal/storage-tiers.md +3 -1
  30. package/dist/builtin-memory/spec/roadmap.md +2 -2
  31. package/dist/builtin-memory/testing.md +1 -1
  32. package/dist/builtin-memory/wedged-child-on-runaway-bash.md +1 -1
  33. package/dist/clients/attach/chrome/bash-jobs.js +3 -3
  34. package/dist/clients/attach/render/chat-view.d.ts +29 -9
  35. package/dist/clients/attach/render/chat-view.js +64 -35
  36. package/dist/clients/attach/render/markdown-source.js +0 -6
  37. package/dist/clients/attach/render/page-block.d.ts +34 -0
  38. package/dist/clients/attach/render/page-block.js +226 -0
  39. package/dist/clients/attach/render/tool-calls.js +16 -2
  40. package/dist/clients/attach/session/whip.d.ts +2 -0
  41. package/dist/clients/attach/session/whip.js +2 -0
  42. package/dist/clients/attach/viewer.js +1419 -982
  43. package/dist/clients/conversation/projection.js +18 -3
  44. package/dist/clients/inbox/__tests__/serial/inbox-controller.test.js +9 -6
  45. package/dist/clients/inbox/__tests__/serial/mount-panel.test.js +13 -14
  46. package/dist/clients/inbox/controller.js +10 -16
  47. package/dist/clients/inbox/page-adapter.d.ts +3 -4
  48. package/dist/clients/inbox/page-adapter.js +3 -4
  49. package/dist/clients/inbox/resolve.d.ts +1 -1
  50. package/dist/clients/inbox/resolve.js +3 -3
  51. package/dist/clients/inbox/review/document-surface.js +3 -1
  52. package/dist/clients/inbox/review/state.d.ts +2 -1
  53. package/dist/clients/inbox/review/state.js +5 -16
  54. package/dist/clients/inbox/tui/input.js +37 -19
  55. package/dist/clients/inbox/tui/panel.js +5 -31
  56. package/dist/clients/inbox/tui/render.d.ts +11 -0
  57. package/dist/clients/inbox/tui/render.js +101 -36
  58. package/dist/clients/inbox/tui/slots.d.ts +4 -4
  59. package/dist/clients/inbox/tui/slots.js +19 -44
  60. package/dist/clients/inbox/tui/types.d.ts +6 -10
  61. package/dist/clients/inbox/tui.js +2 -2
  62. package/dist/commands/__tests__/human.test.js +118 -24
  63. package/dist/commands/__tests__/node-message.test.js +14 -0
  64. package/dist/commands/attention.js +4 -4
  65. package/dist/commands/canvas-browse.js +1 -1
  66. package/dist/commands/canvas-snapshot.js +1 -1
  67. package/dist/commands/canvas.js +1 -1
  68. package/dist/commands/dashboard.js +1 -1
  69. package/dist/commands/human/{doc.d.ts → components.d.ts} +1 -1
  70. package/dist/commands/human/components.js +77 -0
  71. package/dist/commands/human/feedback.d.ts +2 -0
  72. package/dist/commands/human/feedback.js +108 -0
  73. package/dist/commands/human/prompts.d.ts +17 -8
  74. package/dist/commands/human/prompts.js +217 -226
  75. package/dist/commands/human/queue.js +57 -88
  76. package/dist/commands/human/shared.d.ts +4 -15
  77. package/dist/commands/human/shared.js +19 -61
  78. package/dist/commands/human.js +32 -19
  79. package/dist/commands/memory/origin.js +2 -2
  80. package/dist/commands/node/bash.js +4 -4
  81. package/dist/commands/node/inspect.js +3 -3
  82. package/dist/commands/node/lifecycle.js +1 -1
  83. package/dist/commands/node/message.js +2 -2
  84. package/dist/commands/node-worktree.js +8 -8
  85. package/dist/commands/pkg/browse.js +1 -1
  86. package/dist/commands/pkg/plugin-manage.js +129 -10
  87. package/dist/commands/profile/delete.js +41 -15
  88. package/dist/commands/profile.js +1 -1
  89. package/dist/commands/push.js +1 -1
  90. package/dist/commands/surface/node/focus.js +29 -14
  91. package/dist/commands/surface-edit.js +3 -3
  92. package/dist/commands/surface-inbox.d.ts +2 -0
  93. package/dist/commands/{human/inbox.js → surface-inbox.js} +14 -18
  94. package/dist/commands/surface.js +2 -1
  95. package/dist/commands/sys/doctor.js +60 -3
  96. package/dist/commands/sys/feedback.js +79 -25
  97. package/dist/commands/sys/settings.js +1 -1
  98. package/dist/core/__tests__/cron-broker-capacity.test.js +1 -1
  99. package/dist/core/__tests__/daemon-boot.test.js +7 -4
  100. package/dist/core/__tests__/dead-node-policy-table.test.js +20 -36
  101. package/dist/core/__tests__/extension-abort.test.js +5 -2
  102. package/dist/core/__tests__/fixtures/fake-engine.d.ts +13 -2
  103. package/dist/core/__tests__/fixtures/fake-engine.js +47 -3
  104. package/dist/core/__tests__/human-cancel-guard.test.js +31 -10
  105. package/dist/core/__tests__/human-deliver.test.js +41 -45
  106. package/dist/core/__tests__/human-node-not-supervised.test.js +1 -1
  107. package/dist/core/__tests__/plugin-page-components.test.js +157 -0
  108. package/dist/core/__tests__/seam/broker-crash-teardown.test.js +13 -11
  109. package/dist/core/__tests__/seam/dormancy-release.test.js +103 -4
  110. package/dist/core/__tests__/serial/flagship-lifecycle.test.js +2 -2
  111. package/dist/core/__tests__/serial/human-deliver-e2e.test.js +12 -16
  112. package/dist/core/__tests__/serial/live-mutation.test.js +1 -1
  113. package/dist/core/__tests__/serial/tmux-surface.test.js +4 -2
  114. package/dist/core/__tests__/serial/worktree.test.js +24 -198
  115. package/dist/core/__tests__/session-cycles.test.js +22 -0
  116. package/dist/core/__tests__/stop-guard.test.js +4 -4
  117. package/dist/core/__tests__/watchdog-abort-arms-retry.test.js +19 -4
  118. package/dist/core/bash-jobs.d.ts +6 -0
  119. package/dist/core/bash-jobs.js +10 -0
  120. package/dist/core/canvas/__tests__/attention.test.js +8 -5
  121. package/dist/core/canvas/__tests__/render-remote.test.js +5 -5
  122. package/dist/core/canvas/attention.js +8 -6
  123. package/dist/core/canvas/browse/app.d.ts +1 -1
  124. package/dist/core/canvas/browse/app.js +21 -3
  125. package/dist/core/canvas/browse/model.d.ts +8 -2
  126. package/dist/core/canvas/browse/model.js +11 -4
  127. package/dist/core/canvas/browse/render.d.ts +4 -0
  128. package/dist/core/canvas/browse/render.js +5 -1
  129. package/dist/core/canvas/canvas.d.ts +33 -0
  130. package/dist/core/canvas/canvas.js +92 -0
  131. package/dist/core/canvas/crons.d.ts +9 -0
  132. package/dist/core/canvas/crons.js +51 -0
  133. package/dist/core/canvas/node-order.d.ts +22 -1
  134. package/dist/core/canvas/node-order.js +26 -1
  135. package/dist/core/canvas/render-source.d.ts +5 -0
  136. package/dist/core/canvas/render-source.js +1 -0
  137. package/dist/core/canvas/types.d.ts +9 -3
  138. package/dist/core/command-plugins/bundle.d.ts +6 -1
  139. package/dist/core/command-plugins/bundle.js +26 -13
  140. package/dist/core/command.js +8 -12
  141. package/dist/core/config.d.ts +47 -1
  142. package/dist/core/config.js +138 -4
  143. package/dist/core/help.d.ts +3 -0
  144. package/dist/core/help.js +1 -1
  145. package/dist/core/human/__tests__/page-catalog.test.d.ts +1 -0
  146. package/dist/core/human/__tests__/page-catalog.test.js +20 -0
  147. package/dist/core/human/__tests__/page-tickets.test.js +68 -34
  148. package/dist/core/human/__tests__/page.test.js +60 -119
  149. package/dist/core/human/__tests__/serial/inbox-core.test.js +6 -16
  150. package/dist/core/human/answer-text.d.ts +3 -0
  151. package/dist/core/human/answer-text.js +98 -0
  152. package/dist/core/human/answer.d.ts +53 -0
  153. package/dist/core/human/answer.js +119 -0
  154. package/dist/core/human/component-docs.d.ts +18 -0
  155. package/dist/core/human/component-docs.js +434 -0
  156. package/dist/core/human/convention.d.ts +17 -3
  157. package/dist/core/human/convention.js +36 -5
  158. package/dist/core/human/feedback-companion.d.ts +20 -0
  159. package/dist/core/human/feedback-companion.js +98 -0
  160. package/dist/core/human/feedback.d.ts +65 -0
  161. package/dist/core/human/feedback.js +116 -0
  162. package/dist/core/human/page-catalog.d.ts +56 -5
  163. package/dist/core/human/page-catalog.js +151 -22
  164. package/dist/core/human/page-errors.d.ts +4 -0
  165. package/dist/core/human/page-errors.js +7 -0
  166. package/dist/core/human/page-eval.d.ts +20 -0
  167. package/dist/core/human/page-eval.js +123 -0
  168. package/dist/core/human/page-schema.d.ts +48 -116
  169. package/dist/core/human/page-schema.js +100 -72
  170. package/dist/core/human/page-synth.d.ts +2 -4
  171. package/dist/core/human/page-synth.js +20 -7
  172. package/dist/core/human/page.d.ts +13 -21
  173. package/dist/core/human/page.js +147 -391
  174. package/dist/core/human/review-schema.d.ts +1 -0
  175. package/dist/core/human/review-schema.js +1 -1
  176. package/dist/core/human/root.d.ts +6 -2
  177. package/dist/core/human/root.js +12 -4
  178. package/dist/core/human/scan.d.ts +3 -3
  179. package/dist/core/human/scan.js +21 -21
  180. package/dist/core/human/summary.d.ts +2 -6
  181. package/dist/core/human/summary.js +3 -59
  182. package/dist/core/human/tickets.d.ts +22 -15
  183. package/dist/core/human/tickets.js +90 -28
  184. package/dist/core/human/types.d.ts +14 -2
  185. package/dist/core/keybindings/catalog.d.ts +2 -2
  186. package/dist/core/keybindings/catalog.js +1 -0
  187. package/dist/core/preview-registry.js +8 -10
  188. package/dist/core/profiles/default-binding.d.ts +4 -0
  189. package/dist/core/profiles/default-binding.js +24 -0
  190. package/dist/core/profiles/deletion-reservation.d.ts +8 -0
  191. package/dist/core/profiles/deletion-reservation.js +38 -0
  192. package/dist/core/profiles/manifest.d.ts +3 -0
  193. package/dist/core/profiles/manifest.js +12 -0
  194. package/dist/core/profiles/select.js +2 -2
  195. package/dist/core/review/realize.js +12 -4
  196. package/dist/core/review/store.d.ts +6 -0
  197. package/dist/core/review/store.js +31 -0
  198. package/dist/core/runtime/bearings.d.ts +0 -4
  199. package/dist/core/runtime/bearings.js +4 -62
  200. package/dist/core/runtime/bin-contributions.d.ts +44 -0
  201. package/dist/core/runtime/bin-contributions.js +186 -0
  202. package/dist/core/runtime/boot-root.js +6 -1
  203. package/dist/core/runtime/broker/client-registry.d.ts +4 -0
  204. package/dist/core/runtime/broker/client-registry.js +13 -1
  205. package/dist/core/runtime/broker/extension-abort.d.ts +1 -1
  206. package/dist/core/runtime/broker/extension-abort.js +4 -2
  207. package/dist/core/runtime/broker/fault-retry.js +4 -0
  208. package/dist/core/runtime/broker/frame-dispatch.d.ts +3 -1
  209. package/dist/core/runtime/broker/frame-dispatch.js +5 -6
  210. package/dist/core/runtime/broker/read-ops.d.ts +1 -1
  211. package/dist/core/runtime/broker/read-ops.js +2 -2
  212. package/dist/core/runtime/broker/rebind.d.ts +1 -0
  213. package/dist/core/runtime/broker/rebind.js +6 -1
  214. package/dist/core/runtime/broker-extension-render.js +19 -0
  215. package/dist/core/runtime/broker-protocol.d.ts +14 -0
  216. package/dist/core/runtime/broker.d.ts +1 -1
  217. package/dist/core/runtime/broker.js +25 -12
  218. package/dist/core/runtime/busy.d.ts +4 -3
  219. package/dist/core/runtime/busy.js +4 -3
  220. package/dist/core/runtime/front-door-env.d.ts +5 -0
  221. package/dist/core/runtime/front-door-env.js +16 -0
  222. package/dist/core/runtime/front-door.d.ts +1 -5
  223. package/dist/core/runtime/front-door.js +2 -5
  224. package/dist/core/runtime/host.d.ts +1 -1
  225. package/dist/core/runtime/invocation.d.ts +6 -0
  226. package/dist/core/runtime/invocation.js +10 -0
  227. package/dist/core/runtime/kickoff.js +34 -0
  228. package/dist/core/runtime/launch.d.ts +2 -6
  229. package/dist/core/runtime/node-read.js +4 -1
  230. package/dist/core/runtime/nodes.js +2 -0
  231. package/dist/core/runtime/promote.d.ts +1 -0
  232. package/dist/core/runtime/promote.js +3 -1
  233. package/dist/core/runtime/session-cycles.d.ts +6 -0
  234. package/dist/core/runtime/session-cycles.js +15 -1
  235. package/dist/core/runtime/session-visibility.d.ts +17 -8
  236. package/dist/core/runtime/session-visibility.js +19 -10
  237. package/dist/core/runtime/spawn-env.d.ts +9 -1
  238. package/dist/core/runtime/spawn-env.js +62 -2
  239. package/dist/core/runtime/spawn.js +4 -4
  240. package/dist/core/runtime/stop-guard.d.ts +2 -2
  241. package/dist/core/runtime/stop-guard.js +2 -2
  242. package/dist/core/runtime/stop-signals.d.ts +1 -1
  243. package/dist/core/runtime/stop-signals.js +2 -2
  244. package/dist/core/runtime/tmux-bindings.js +1 -1
  245. package/dist/core/session-model/session-state.d.ts +4 -3
  246. package/dist/core/session-model/session-state.js +14 -1
  247. package/dist/core/user-settings.d.ts +36 -2
  248. package/dist/core/user-settings.js +56 -14
  249. package/dist/core/worktree.d.ts +15 -45
  250. package/dist/core/worktree.js +59 -261
  251. package/dist/daemon/api/__tests__/seam/profile-delete.test.d.ts +1 -0
  252. package/dist/daemon/api/__tests__/seam/profile-delete.test.js +386 -0
  253. package/dist/daemon/api/handlers/bash-jobs.js +1 -1
  254. package/dist/daemon/api/handlers/broker-ops.js +9 -5
  255. package/dist/daemon/api/handlers/feedback-comments.d.ts +2 -0
  256. package/dist/daemon/api/handlers/feedback-comments.js +207 -0
  257. package/dist/daemon/api/handlers/human.js +24 -20
  258. package/dist/daemon/api/handlers/inbox.d.ts +10 -0
  259. package/dist/daemon/api/handlers/inbox.js +48 -109
  260. package/dist/daemon/api/handlers/profiles.js +11 -29
  261. package/dist/daemon/api/map.d.ts +6 -1
  262. package/dist/daemon/api/map.js +23 -0
  263. package/dist/daemon/api/server.js +3 -1
  264. package/dist/daemon/companion-retire.d.ts +2 -0
  265. package/dist/daemon/companion-retire.js +33 -0
  266. package/dist/daemon/cron-run.js +29 -17
  267. package/dist/daemon/crtrd.js +1 -1
  268. package/dist/daemon/fleet.d.ts +22 -10
  269. package/dist/daemon/fleet.js +43 -24
  270. package/dist/daemon/human/finish.d.ts +12 -6
  271. package/dist/daemon/human/finish.js +134 -54
  272. package/dist/daemon/human/sweep.js +9 -3
  273. package/dist/daemon/profile-delete.d.ts +7 -0
  274. package/dist/daemon/profile-delete.js +306 -0
  275. package/dist/daemon/reconcilers/broker-supervision.js +14 -9
  276. package/dist/daemon/reconcilers/dormant-inbox.js +11 -6
  277. package/dist/daemon/reconcilers/live-obligation.d.ts +21 -1
  278. package/dist/daemon/reconcilers/live-obligation.js +30 -2
  279. package/dist/daemon/reconcilers/storage-maintenance.d.ts +5 -0
  280. package/dist/daemon/reconcilers/storage-maintenance.js +28 -1
  281. package/dist/daemon/review/deliver.js +3 -3
  282. package/dist/daemon/review/finish.d.ts +0 -2
  283. package/dist/daemon/review/finish.js +1 -29
  284. package/dist/daemon/review/sweep.js +12 -1
  285. package/dist/pi-extensions/__tests__/canvas-bash-valve.test.js +56 -1
  286. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +4 -4
  287. package/dist/pi-extensions/canvas-bash-valve.d.ts +9 -1
  288. package/dist/pi-extensions/canvas-bash-valve.js +31 -13
  289. package/dist/pi-extensions/canvas-inbox-watcher.js +1 -1
  290. package/dist/pi-extensions/canvas-review-boundary.d.ts +4 -0
  291. package/dist/pi-extensions/canvas-review-boundary.js +18 -9
  292. package/dist/pi-extensions/canvas-stophook.js +4 -4
  293. package/dist/shared/generated-context.js +2 -2
  294. package/dist/types.d.ts +61 -5
  295. package/dist/types.js +8 -2
  296. package/package.json +4 -4
  297. package/runtime.lock.json +5 -33
  298. package/scripts/install-runtime.mjs +19 -24
  299. package/dist/builtin-memory/init.md +0 -38
  300. package/dist/builtin-memory/plan.md +0 -19
  301. package/dist/builtin-memory/spec.md +0 -19
  302. package/dist/clients/attach/render/html-markdown.d.ts +0 -13
  303. package/dist/clients/attach/render/html-markdown.js +0 -206
  304. package/dist/commands/human/doc.js +0 -144
  305. package/dist/commands/human/inbox.d.ts +0 -2
  306. package/dist/core/human/__tests__/page-render.test.js +0 -49
  307. package/dist/core/human/markdown-html.d.ts +0 -8
  308. package/dist/core/human/markdown-html.js +0 -581
  309. package/dist/core/human/page-render.d.ts +0 -3
  310. package/dist/core/human/page-render.js +0 -131
  311. package/dist/pages/bundle.css +0 -1
  312. package/dist/pages/bundle.js +0 -1347
  313. package/dist/pages/comments.d.ts +0 -43
  314. package/dist/pages/comments.js +0 -125
  315. package/dist/pages/elements/cards.d.ts +0 -17
  316. package/dist/pages/elements/cards.js +0 -802
  317. package/dist/pages/elements/chart.js +0 -648
  318. package/dist/pages/elements/options.d.ts +0 -21
  319. package/dist/pages/elements/options.js +0 -629
  320. package/dist/pages/elements/pages.d.ts +0 -24
  321. package/dist/pages/elements/pages.js +0 -355
  322. package/dist/pages/elements/slot.d.ts +0 -21
  323. package/dist/pages/elements/slot.js +0 -105
  324. package/dist/pages/elements/table.d.ts +0 -19
  325. package/dist/pages/elements/table.js +0 -935
  326. package/dist/pages/elements/text.d.ts +0 -25
  327. package/dist/pages/elements/text.js +0 -968
  328. package/dist/pages/entry.d.ts +0 -8
  329. package/dist/pages/entry.js +0 -8
  330. package/dist/pages/host.d.ts +0 -158
  331. package/dist/pages/host.js +0 -182
  332. package/dist/pages/register.d.ts +0 -2
  333. package/dist/pages/register.js +0 -3
  334. package/dist/pages/slot-config.d.ts +0 -36
  335. package/dist/pages/slot-config.js +0 -50
  336. package/dist/pages/types.d.ts +0 -98
  337. package/dist/pages/types.js +0 -23
  338. /package/dist/{core/human/__tests__/page-render.test.d.ts → commands/__tests__/node-message.test.d.ts} +0 -0
  339. /package/dist/{pages/elements/chart.d.ts → core/__tests__/plugin-page-components.test.d.ts} +0 -0
@@ -1,21 +1,18 @@
1
- /**
2
- * Prompt-design gap: a person opens asks from background agents without the
3
- * task or conversation context. Human-ask help therefore teaches agents to
4
- * make every interruption self-contained and answerable before its evidence.
5
- */
6
1
  import { defineLeaf } from '../../core/command.js';
7
2
  import { InputError } from '../../core/io.js';
8
3
  import { cliClient } from '../api-client.js';
9
- import { extname, join } from 'node:path';
4
+ import { join } from 'node:path';
10
5
  import { randomBytes } from 'node:crypto';
11
- import { rmSync } from 'node:fs';
12
- import { readConfig } from '../../core/config.js';
6
+ import { existsSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
7
+ import { tmpdir } from 'node:os';
8
+ import { resolvePageComponents } from '../../core/config.js';
13
9
  import { markStopSignal } from '../../core/runtime/stop-signals.js';
14
- import { atomicWriteJson } from '../../core/human/convention.js';
10
+ import { bindTicketReplyRoute } from '../../core/human/convention.js';
15
11
  import { ticketDir } from '../../core/human/root.js';
16
- import { preparePage, publishPage } from '../../core/human/tickets.js';
17
- import { synthesizedPageDocument, writeSynthesizedPage } from '../../core/human/page-synth.js';
18
- import { PageAuthoringError } from '../../core/human/page.js';
12
+ import { preparePage, publishPage, readTicketResult } from '../../core/human/tickets.js';
13
+ import { PageAuthoringError, parsePage } from '../../core/human/page.js';
14
+ import { pageManifestPath } from '../../core/human/convention.js';
15
+ import { isResponseBearingSlot, validateAuthoredPageManifest } from '../../core/human/page-schema.js';
19
16
  import { display } from '../../core/termrender/display.js';
20
17
  import { pageHelpText, resolveMaxPanes } from './shared.js';
21
18
  const PAGE_HELP = pageHelpText();
@@ -26,76 +23,63 @@ function askingNode() {
26
23
  /** Provenance for crouter tickets. Ticket *identity* comes from the bridge node
27
24
  * directory, never ticket data; `nodeId` carries the originating node so a
28
25
  * consumer can relate a ticket back to whatever that node belongs to, and
29
- * `sessionName` is a presentation label only. Review tickets already stamp
30
- * `nodeId` the same way (`core/review/realize.ts`), so pages match them.
31
- * `nodeId` is stamped even when the daemon is unreachable — losing the label is
32
- * survivable, losing machine attribution is not. */
33
- export async function sourceStamp() {
26
+ * `sessionName` is a presentation label only, while `profileName` lets product
27
+ * consumers resolve the applet or worker identity behind the ticket. Review
28
+ * tickets stamp the same provenance (`core/review/realize.ts`), so pages match
29
+ * them. `nodeId` is stamped even when the daemon is unreachable — losing the
30
+ * labels is survivable, losing machine attribution is not.
31
+ *
32
+ * The same read answers who is sending: `lifecycle` decides whether the sender
33
+ * has a transcript a human is plausibly reading, which is what makes skipping
34
+ * the inbox safe. It is null for a bare shell (no node at all) and when the
35
+ * daemon cannot answer — neither is a terminal sender, so neither restricts
36
+ * delivery. */
37
+ export async function sender() {
34
38
  const nodeId = askingNode();
35
39
  if (nodeId === null || nodeId === '')
36
- return {};
40
+ return { source: {}, lifecycle: null };
37
41
  try {
38
- const sessionName = (await cliClient().getNode(nodeId)).name.trim();
39
- return sessionName === '' ? { nodeId } : { nodeId, sessionName };
42
+ const client = cliClient();
43
+ const node = await client.getNode(nodeId);
44
+ const sessionName = node.name.trim();
45
+ const profileName = node.profile_id === null
46
+ ? undefined
47
+ : (await client.getProfile(node.profile_id)).name;
48
+ return {
49
+ source: {
50
+ nodeId,
51
+ ...(sessionName === '' ? {} : { sessionName }),
52
+ ...(profileName === undefined ? {} : { profileName }),
53
+ },
54
+ lifecycle: node.lifecycle,
55
+ };
40
56
  }
41
57
  catch {
42
- return { nodeId };
58
+ return { source: { nodeId }, lifecycle: null };
43
59
  }
44
60
  }
45
- function requireAskTitle(title) {
46
- if (title !== undefined)
47
- return title;
48
- throw new InputError({
49
- error: 'missing_parameter',
50
- message: 'stdin questions require a non-empty --title',
51
- field: 'title',
52
- next: 'Retry with a short --title naming the topic being decided.',
53
- });
61
+ function pageProductKinds() {
62
+ return resolvePageComponents();
54
63
  }
55
- function requireSubtitle(subtitle, field = 'subtitle') {
56
- if (subtitle !== undefined && subtitle.trim() !== '')
57
- return subtitle.trim();
58
- throw new InputError({
59
- error: 'missing_parameter',
60
- message: 'a non-empty subtitle is required',
61
- field,
62
- next: 'Write one plain-English sentence stating the decision, recommendation or status, and stakes.',
63
- });
64
- }
65
- function pageDialect(sourceFile) {
66
- const extension = extname(sourceFile).toLowerCase();
67
- if (extension === '.md')
68
- return 'md';
69
- if (extension === '.html' || extension === '.htm')
70
- return 'html';
71
- throw new InputError({
72
- error: 'invalid_page_dialect',
73
- message: `--page must point to a .md, .html, or .htm file; received "${sourceFile}"`,
74
- field: 'page',
75
- next: 'Use a Markdown page (.md) or an HTML page (.html or .htm).',
76
- });
77
- }
78
- function pageProductKinds(dialect) {
79
- const config = readConfig('user');
80
- if (dialect === 'html' && !config.html_pages) {
81
- throw new InputError({
82
- error: 'page_dialect_disabled',
83
- message: 'html pages are not enabled in this home; md is the dialect to use',
84
- field: 'page',
85
- next: 'Use a .md page, or enable html_pages in the user scope config before submitting HTML.',
86
- });
87
- }
88
- return config.page_components;
89
- }
90
- function requireNotifyTitle(title) {
91
- if (title !== undefined && title.trim() !== '')
92
- return title.trim();
93
- throw new InputError({
94
- error: 'missing_parameter',
95
- message: 'notify shorthand requires a non-empty title',
96
- field: 'title',
97
- next: 'Retry with a positional title, --subtitle, and optional --body, or submit a page with --page PATH.',
98
- });
64
+ /** Delivery is two authored choices and one derived fact. Placement and inbox
65
+ * are the sender's to pick; `reply` is not a flag, because a page routes
66
+ * completion back exactly when it carries response-bearing components — the
67
+ * page's own shape decides it, and a sender who disagreed could only be wrong
68
+ * (inputs with no way to submit, or a bridge no response can settle).
69
+ *
70
+ * Skipping the inbox leaves the sender's own transcript as the page's only
71
+ * route to a human, so a terminal sender — a node that finishes, whose
72
+ * transcript nobody is watching — always projects to the inbox. */
73
+ function deliveryFrom(input, responseBearing, senderLifecycle) {
74
+ const inbox = senderLifecycle === 'terminal'
75
+ ? true
76
+ : input['inbox'] === true
77
+ ? true
78
+ : input['noInbox'] === true
79
+ ? false
80
+ : responseBearing;
81
+ const placement = input['placement'] ?? 'inline';
82
+ return { placement, inbox, reply: responseBearing };
99
83
  }
100
84
  function submitAuthoredPage(submit) {
101
85
  try {
@@ -108,175 +92,182 @@ function submitAuthoredPage(submit) {
108
92
  error: 'invalid_page',
109
93
  message: error.message,
110
94
  field: 'page',
111
- next: 'Fix the authored page, then pre-check it with `crtr human doc check <path>` before submitting again.',
95
+ next: 'Fix the authored page and submit again; the reported location is in the authored `.tsx` source.',
96
+ });
97
+ }
98
+ }
99
+ /** The page a `--replace` names, read for the one thing a revision inherits. */
100
+ function replacedPage(ticketId) {
101
+ const dir = ticketDir(ticketId);
102
+ if (!existsSync(pageManifestPath(dir))) {
103
+ throw new InputError({
104
+ error: 'unknown_page',
105
+ message: `no page ${ticketId} on this canvas`,
106
+ field: 'replace',
107
+ next: 'Pass the page_id a previous send returned, or omit --replace to raise a new page.',
112
108
  });
113
109
  }
110
+ return parsePage(dir);
111
+ }
112
+ function writePipedPage(document) {
113
+ const dir = mkdtempSync(join(tmpdir(), 'crtr-human-page-'));
114
+ const path = join(dir, 'page.tsx');
115
+ writeFileSync(path, document, { encoding: 'utf8', mode: 0o600 });
116
+ return { dir, path };
117
+ }
118
+ /** Resolve exactly one authored page input. Piped bytes are materialized so
119
+ * both input forms follow the same page preparation and validation path. */
120
+ function pageInput(input) {
121
+ const file = input['page'];
122
+ const documentRaw = input['document'];
123
+ // Optional stdin arrives as an empty string when a caller redirects an empty
124
+ // stream. It is not a page source and must not conflict with --page.
125
+ const document = typeof documentRaw === 'string' && documentRaw.trim() !== '' ? documentRaw : undefined;
126
+ if (file !== undefined && document !== undefined) {
127
+ throw new InputError({
128
+ error: 'bad_invocation',
129
+ message: 'received both --page and a piped page document',
130
+ field: 'page, document',
131
+ next: 'Pass exactly one page source: --page PATH or the document on stdin.',
132
+ });
133
+ }
134
+ if (file === undefined && document === undefined) {
135
+ throw new InputError({
136
+ error: 'missing_parameter',
137
+ message: 'a page source is required',
138
+ field: 'page, document',
139
+ next: 'Pass exactly one page source: --page PATH or the document on stdin.',
140
+ });
141
+ }
142
+ if (file !== undefined) {
143
+ if (typeof file !== 'string')
144
+ throw new InputError({ error: 'invalid_type', message: '--page must be a path', field: 'page', next: 'Retry with --page PATH.' });
145
+ return { sourceFile: file };
146
+ }
147
+ if (typeof document !== 'string')
148
+ throw new InputError({ error: 'invalid_type', message: 'stdin must contain a page document', field: 'document', next: 'Pipe the page document on stdin.' });
149
+ const temp = writePipedPage(document);
150
+ return { sourceFile: temp.path, tempDir: temp.dir };
114
151
  }
115
152
  /** The non-blocking status peek returned by ask/review. How the human reaches
116
- * their inbox is theirs, not the agent's — naming a shortcut here only teaches
117
- * agents to repeat a screen-specific gesture back to a reader who may be on a
118
- * different surface entirely. */
153
+ * their inbox is theirs, not the agent's — naming a shortcut here only teaches
154
+ * agents to repeat a screen-specific gesture back to a reader who may be on a
155
+ * different surface entirely. */
119
156
  function queuedFollowUp() {
120
- return 'Queued for the human. Their answer is pushed to your inbox and wakes you when they respond — never poll, verify it opened, or block.';
157
+ return 'Queued for the user. Their answer is pushed to your inbox and wakes you when they respond — never poll, verify it opened, or block.';
121
158
  }
122
159
  // ---------------------------------------------------------------------------
123
- // ask
160
+ // send
124
161
  // ---------------------------------------------------------------------------
125
- export const humanAsk = defineLeaf({
126
- name: 'ask',
127
- description: 'put a structured choice or open question to a person',
128
- whenToUse: 'you would otherwise lay a decision, a set of options, or a question out for the user as prose — reach for this instead, for anything from a quick yes/no to a judgment-heavy call: gating a handoff, merge, deploy, destructive/irreversible operation, or real budget on an explicit human sign-off (offer Yes/No options), reviewing all the requirements before building, choosing among implementation patterns, walking a list of risks and deciding what to do about each, settling a naming or scope question, picking which of several findings to act on. Works for open-ended asks too (set `allowFreetext`, offer a few `options` as starting points).',
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.',
129
166
  help: {
130
- name: 'human ask',
131
- summary: "submit a decision ticket to crouter's inbox; returns a job handle immediately.",
132
- guide: [
133
- 'Write a self-contained decision brief for a supervisor who has not followed the task: title names the topic; subtitle states the decision, your recommendation, and stakes; the body gives only the context and tradeoffs needed to decide. Put supporting evidence under `## Details`. Ask only what changes your next step, and offer genuine alternatives as `options`.',
134
- PAGE_HELP.authoring,
135
- ].join('\n\n'),
167
+ name: 'human send',
168
+ summary: 'send a durable TSX or HTML page',
169
+ guide: 'Page authoring: `crtr human -h`.',
136
170
  params: [
137
- { kind: 'flag', name: 'title', type: 'string', required: false, constraint: 'Required with stdin shorthand: a short ≤~4-word inbox topic. Mutually exclusive with --page.' },
138
- { kind: 'flag', name: 'subtitle', type: 'string', required: false, constraint: 'Required with stdin shorthand: one plain-English sentence stating the decision, recommendation, and stakes. Mutually exclusive with --page.' },
139
- { kind: 'stdin', name: 'question', required: false, constraint: "Simple open-text ask body. Pipe markdown from a single-quoted heredoc (`<<'EOF'`) to preserve literal bytes. Requires --title and --subtitle; mutually exclusive with --page." },
140
- { kind: 'flag', name: 'page', type: 'path', required: false, constraint: `${PAGE_HELP.pageHint} Mutually exclusive with shorthand.` },
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.' },
141
177
  ],
142
178
  output: [
143
- { name: 'job_id', type: 'string', required: true, constraint: 'Node id of this human interaction. Its answer is pushed to your inbox when the human responds.' },
144
- { name: 'dir', type: 'string', required: true, constraint: 'Interaction directory holding page.md or page.html, page.json, run.json, and response.json.' },
145
- { name: 'follow_up', type: 'string', required: true, constraint: 'A non-blocking status peek; the answer arrives via your inbox.' },
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.' },
146
185
  ],
147
186
  outputKind: 'object',
148
187
  effects: [
149
- 'Creates a kind:"human" node under you and enqueues a page in the human inbox.',
150
- 'Returns immediately; nothing opens on screen. The human answers from their inbox on their own time.',
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.',
151
192
  ],
152
193
  },
153
- run: async (input) => {
154
- // Optional stdin reaches the leaf as an empty string when omitted. Keep the
155
- // raw non-empty bytes untouched so shorthand callers retain their body.
156
- const titleRaw = input['title'];
157
- const title = typeof titleRaw === 'string' && titleRaw.trim() !== '' ? titleRaw.trim() : undefined;
158
- const questionRaw = input['question'];
159
- const question = typeof questionRaw === 'string' && questionRaw !== '' ? questionRaw : undefined;
160
- const subtitleRaw = input['subtitle'];
161
- const subtitle = typeof subtitleRaw === 'string' && subtitleRaw.trim() !== '' ? subtitleRaw.trim() : undefined;
162
- const pageInput = input['page'];
163
- if (pageInput !== undefined && (question !== undefined || titleRaw !== undefined || subtitleRaw !== undefined)) {
164
- throw new InputError({
165
- error: 'bad_invocation',
166
- message: 'use --title and --subtitle with stdin question text, or --page, not both',
167
- next: 'Retry with --title, --subtitle, and a piped question body, or with --page PATH alone.',
168
- });
169
- }
170
- if (question === undefined && pageInput === undefined) {
171
- throw new InputError({
172
- error: 'missing_parameter',
173
- message: 'human ask needs stdin question text or --page PATH',
174
- next: 'Pipe a question body with --title and --subtitle, or pass --page PATH for an authored page.',
175
- });
176
- }
177
- const dialect = pageDialect(typeof pageInput === 'string' ? pageInput : 'page.md');
178
- const productKinds = pageProductKinds(dialect);
179
- let tempDir;
180
- let sourceFile;
181
- if (pageInput !== undefined) {
182
- sourceFile = pageInput;
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.' });
183
200
  }
184
- else {
185
- const simpleAsk = { title: requireAskTitle(title), subtitle: requireSubtitle(subtitle), question: question };
186
- const synthesized = writeSynthesizedPage(synthesizedPageDocument(simpleAsk.title, simpleAsk.subtitle, simpleAsk.question, true));
187
- tempDir = synthesized.dir;
188
- sourceFile = synthesized.path;
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.' });
189
203
  }
190
204
  try {
191
- const source = await sourceStamp();
192
- // The page is validated before the bridge node exists: a rejected page
193
- // must create nothing, because a bridge node born for a ticket that never
194
- // got published is a live terminal node no answer can ever finish.
195
- const prepared = submitAuthoredPage(() => preparePage({ sourceFile, dialect, source, productKinds }));
196
- const cwd = process.cwd();
197
- const jobId = (await cliClient().createHumanBridge({ kind: 'human', parent: askingNode(), cwd, name: 'human-ask' })).node_id;
198
- const idir = ticketDir(jobId);
199
- const rc = { mode: 'ask', job_id: jobId };
200
- atomicWriteJson(join(idir, 'run.json'), rc);
201
- const { dir } = publishPage(ticketDir(jobId), prepared);
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) {
211
+ 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.',
216
+ });
217
+ }
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
+ });
237
+ }
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 };
262
+ }
202
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);
203
268
  if (originNodeId !== null && originNodeId !== '')
204
- markStopSignal(originNodeId, 'askedHuman');
205
- return { job_id: jobId, dir, follow_up: queuedFollowUp() };
206
- }
207
- finally {
208
- if (tempDir !== undefined)
209
- rmSync(tempDir, { recursive: true, force: true });
210
- }
211
- },
212
- });
213
- // ---------------------------------------------------------------------------
214
- // notify (no job)
215
- // ---------------------------------------------------------------------------
216
- export const humanNotify = defineLeaf({
217
- name: 'notify',
218
- description: 'fire-and-forget acknowledgement, no reply expected',
219
- whenToUse: 'informing a person without blocking or expecting an answer',
220
- help: {
221
- name: 'human notify',
222
- summary: 'enqueue a fire-and-forget acknowledgement in the human inbox; creates no job',
223
- guide: [
224
- 'State what happened in plain words. Shorthand bodies are directive-flavored Markdown; validate authored pages with `crtr human doc check`.',
225
- PAGE_HELP.authoring,
226
- ].join('\n\n'),
227
- params: [
228
- { kind: 'positional', name: 'title', type: 'string', required: false, constraint: 'Notification headline for shorthand; required with --subtitle when --page is omitted.' },
229
- { kind: 'flag', name: 'subtitle', type: 'string', required: false, constraint: 'One plain-English sentence stating the notification status and stakes; required when --page is omitted.' },
230
- { kind: 'flag', name: 'body', type: 'string', required: false, constraint: 'Optional markdown body for shorthand; mutually exclusive with --page.' },
231
- { kind: 'flag', name: 'page', type: 'path', required: false, constraint: `${PAGE_HELP.pageHint} Mutually exclusive with --body.` },
232
- ],
233
- output: [
234
- { name: 'queued', type: 'boolean', required: true, constraint: 'Always true — the notify page is durable inbox state the human acknowledges from the inbox.' },
235
- { name: 'dir', type: 'string', required: true, constraint: 'Interaction directory holding page.md or page.html, page.json, and run.json.' },
236
- ],
237
- outputKind: 'object',
238
- effects: [
239
- 'Enqueues a durable display page in the human inbox. Creates no node; nothing opens on screen.',
240
- ],
241
- },
242
- run: async (input) => {
243
- const titleRaw = input['title'];
244
- const title = typeof titleRaw === 'string' && titleRaw.trim() !== '' ? titleRaw.trim() : undefined;
245
- const subtitleRaw = input['subtitle'];
246
- const subtitle = typeof subtitleRaw === 'string' && subtitleRaw.trim() !== '' ? subtitleRaw.trim() : undefined;
247
- const body = input['body'];
248
- const pageInput = input['page'];
249
- if (pageInput !== undefined && body !== undefined) {
250
- throw new InputError({
251
- error: 'bad_invocation',
252
- message: 'use --page or --body, not both',
253
- next: 'Retry with --page PATH alone for an authored page, or use the title, --subtitle, and --body shorthand.',
254
- });
255
- }
256
- const dialect = pageDialect(typeof pageInput === 'string' ? pageInput : 'page.md');
257
- const productKinds = pageProductKinds(dialect);
258
- let tempDir;
259
- let sourceFile;
260
- if (pageInput !== undefined) {
261
- sourceFile = pageInput;
262
- }
263
- else {
264
- const shorthandTitle = requireNotifyTitle(title);
265
- const shorthandSubtitle = requireSubtitle(subtitle);
266
- const synthesized = writeSynthesizedPage(synthesizedPageDocument(shorthandTitle, shorthandSubtitle, body ?? '', false));
267
- tempDir = synthesized.dir;
268
- sourceFile = synthesized.path;
269
- }
270
- try {
271
- const source = await sourceStamp();
272
- // Validate before the ticket directory exists, so a rejected page leaves
273
- // no half-built ticket behind.
274
- const prepared = submitAuthoredPage(() => preparePage({ sourceFile, dialect, source, productKinds }));
275
- const id = `nfy-${randomBytes(4).toString('hex')}`;
276
- const rc = { mode: 'notify' };
277
- atomicWriteJson(join(ticketDir(id), 'run.json'), rc);
278
- const { dir } = publishPage(ticketDir(id), prepared);
279
- return { queued: true, dir };
269
+ markStopSignal(originNodeId, 'requestedHumanReply');
270
+ return { page_id: bridgeNodeId, dir, ...delivery, follow_up: queuedFollowUp() };
280
271
  }
281
272
  finally {
282
273
  if (tempDir !== undefined)
@@ -286,17 +277,17 @@ export const humanNotify = defineLeaf({
286
277
  });
287
278
  // ---------------------------------------------------------------------------
288
279
  // show (no job, non-blocking passthrough) — passive live document display, NOT
289
- // a ticket surface. Retained per the tmux-inbox cutover: `display()` puts a
290
- // file live in a watch pane and never enqueues anything.
280
+ // a ticket surface. `display()` puts a file live in a watch pane and never
281
+ // enqueues anything.
291
282
  // ---------------------------------------------------------------------------
292
283
  export const humanShow = defineLeaf({
293
284
  name: 'show',
294
- description: "put a file live on the human's screen",
295
- whenToUse: 'displaying a doc on screen while a human comments',
285
+ description: "put a file live on the user's screen",
286
+ whenToUse: 'displaying a doc on screen while the user comments',
296
287
  help: {
297
288
  name: 'human show',
298
289
  summary: "put a file live on screen in a tmux pane with crouter's renderer",
299
- guide: 'The pane always watches the file and live-updates on every save — a displayed doc is a live view by definition, so point it at a file something keeps rewriting (a status board, a running summary) and it stays current. The file is directive-flavored markdown rendered by termrender (panels, columns, trees, callouts, mermaid) — see `crtr human doc render` before authoring one. To put a rich document in front of the human as a durable inbox page, use `crtr human notify --page PATH`.',
290
+ guide: 'The pane always watches the file and live-updates on every save — a displayed doc is a live view by definition, so point it at a file something keeps rewriting (a status board, a running summary) and it stays current. The file is directive-flavored markdown rendered by termrender (panels, columns, trees, callouts, mermaid). To put a rich document in front of the user as a durable inbox page, use `crtr human send --page PATH`; a page with no response-bearing components is a standalone announcement.',
300
291
  params: [
301
292
  { kind: 'positional', name: 'path', type: 'path', required: true, constraint: 'Path to the file to render.' },
302
293
  { kind: 'flag', name: 'window', type: 'enum', choices: ['auto', 'split', 'new'], required: false, default: 'auto', constraint: 'Placement. Default auto.' },