@north-light/crouter 0.3.198 → 0.3.200

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 (316) hide show
  1. package/dist/api/client.d.ts +18 -18
  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 +81 -34
  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 +6 -6
  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 +2 -2
  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/clients/attach/chrome/bash-jobs.js +3 -3
  33. package/dist/clients/attach/render/chat-view.d.ts +26 -1
  34. package/dist/clients/attach/render/chat-view.js +61 -11
  35. package/dist/clients/attach/render/page-block.d.ts +34 -0
  36. package/dist/clients/attach/render/page-block.js +226 -0
  37. package/dist/clients/attach/session/whip.d.ts +2 -0
  38. package/dist/clients/attach/session/whip.js +2 -0
  39. package/dist/clients/attach/viewer.js +1419 -993
  40. package/dist/clients/conversation/projection.js +18 -3
  41. package/dist/clients/inbox/__tests__/serial/inbox-controller.test.js +9 -6
  42. package/dist/clients/inbox/__tests__/serial/mount-panel.test.js +13 -21
  43. package/dist/clients/inbox/controller.js +10 -16
  44. package/dist/clients/inbox/page-adapter.d.ts +1 -3
  45. package/dist/clients/inbox/page-adapter.js +3 -4
  46. package/dist/clients/inbox/resolve.d.ts +1 -1
  47. package/dist/clients/inbox/resolve.js +3 -3
  48. package/dist/clients/inbox/review/document-surface.js +3 -1
  49. package/dist/clients/inbox/review/state.d.ts +2 -1
  50. package/dist/clients/inbox/review/state.js +5 -16
  51. package/dist/clients/inbox/tui/input.js +37 -19
  52. package/dist/clients/inbox/tui/panel.js +5 -31
  53. package/dist/clients/inbox/tui/render.d.ts +11 -0
  54. package/dist/clients/inbox/tui/render.js +71 -27
  55. package/dist/clients/inbox/tui/slots.d.ts +3 -1
  56. package/dist/clients/inbox/tui/slots.js +16 -12
  57. package/dist/clients/inbox/tui/types.d.ts +2 -7
  58. package/dist/clients/inbox/tui.js +2 -2
  59. package/dist/commands/__tests__/human.test.js +71 -114
  60. package/dist/commands/__tests__/node-message.test.js +14 -0
  61. package/dist/commands/attention.js +4 -4
  62. package/dist/commands/canvas-snapshot.js +1 -1
  63. package/dist/commands/canvas.js +1 -1
  64. package/dist/commands/dashboard.js +1 -1
  65. package/dist/commands/human/{html.d.ts → components.d.ts} +1 -1
  66. package/dist/commands/human/components.js +77 -0
  67. package/dist/commands/human/feedback.d.ts +2 -0
  68. package/dist/commands/human/feedback.js +108 -0
  69. package/dist/commands/human/prompts.d.ts +13 -5
  70. package/dist/commands/human/prompts.js +160 -97
  71. package/dist/commands/human/queue.js +57 -88
  72. package/dist/commands/human/shared.d.ts +2 -12
  73. package/dist/commands/human/shared.js +17 -11
  74. package/dist/commands/human.js +29 -21
  75. package/dist/commands/memory/origin.js +2 -2
  76. package/dist/commands/node/bash.js +4 -4
  77. package/dist/commands/node/inspect.js +3 -3
  78. package/dist/commands/node/lifecycle.js +1 -1
  79. package/dist/commands/node/message.js +2 -2
  80. package/dist/commands/node-worktree.js +8 -8
  81. package/dist/commands/pkg/browse.js +1 -1
  82. package/dist/commands/pkg/plugin-manage.js +129 -10
  83. package/dist/commands/profile/delete.js +41 -15
  84. package/dist/commands/profile.js +1 -1
  85. package/dist/commands/push.js +1 -1
  86. package/dist/commands/surface/node/focus.js +29 -14
  87. package/dist/commands/surface-edit.js +3 -3
  88. package/dist/commands/surface-inbox.d.ts +2 -0
  89. package/dist/commands/{human/inbox.js → surface-inbox.js} +14 -18
  90. package/dist/commands/surface.js +2 -1
  91. package/dist/commands/sys/doctor.js +60 -3
  92. package/dist/commands/sys/feedback.js +79 -25
  93. package/dist/commands/sys/settings.js +1 -1
  94. package/dist/core/__tests__/cron-broker-capacity.test.js +1 -1
  95. package/dist/core/__tests__/extension-abort.test.js +5 -2
  96. package/dist/core/__tests__/fixtures/fake-engine.d.ts +13 -2
  97. package/dist/core/__tests__/fixtures/fake-engine.js +47 -3
  98. package/dist/core/__tests__/helpers/harness.js +17 -55
  99. package/dist/core/__tests__/human-cancel-guard.test.js +31 -10
  100. package/dist/core/__tests__/human-deliver.test.js +31 -26
  101. package/dist/core/__tests__/human-node-not-supervised.test.js +1 -1
  102. package/dist/core/__tests__/plugin-page-components.test.js +157 -0
  103. package/dist/core/__tests__/seam/broker-crash-teardown.test.js +4 -5
  104. package/dist/core/__tests__/seam/dormancy-release.test.js +40 -0
  105. package/dist/core/__tests__/serial/flagship-lifecycle.test.js +2 -2
  106. package/dist/core/__tests__/serial/human-deliver-e2e.test.js +12 -6
  107. package/dist/core/__tests__/serial/live-mutation.test.js +1 -1
  108. package/dist/core/__tests__/serial/tmux-surface.test.js +4 -2
  109. package/dist/core/__tests__/serial/worktree.test.js +24 -198
  110. package/dist/core/__tests__/session-cycles.test.js +22 -0
  111. package/dist/core/__tests__/stop-guard.test.js +4 -4
  112. package/dist/core/__tests__/watchdog-abort-arms-retry.test.js +19 -4
  113. package/dist/core/bash-jobs.d.ts +6 -0
  114. package/dist/core/bash-jobs.js +10 -0
  115. package/dist/core/canvas/__tests__/attention.test.js +8 -5
  116. package/dist/core/canvas/__tests__/render-remote.test.js +5 -5
  117. package/dist/core/canvas/attention.js +8 -6
  118. package/dist/core/canvas/browse/model.d.ts +8 -2
  119. package/dist/core/canvas/browse/model.js +11 -4
  120. package/dist/core/canvas/canvas.d.ts +7 -0
  121. package/dist/core/canvas/canvas.js +37 -0
  122. package/dist/core/canvas/crons.d.ts +9 -0
  123. package/dist/core/canvas/crons.js +51 -0
  124. package/dist/core/canvas/node-order.d.ts +22 -1
  125. package/dist/core/canvas/node-order.js +26 -1
  126. package/dist/core/canvas/render-source.d.ts +5 -0
  127. package/dist/core/canvas/render-source.js +1 -0
  128. package/dist/core/canvas/types.d.ts +9 -3
  129. package/dist/core/command-plugins/bundle.d.ts +6 -1
  130. package/dist/core/command-plugins/bundle.js +26 -13
  131. package/dist/core/command.js +2 -1
  132. package/dist/core/config.d.ts +47 -1
  133. package/dist/core/config.js +137 -4
  134. package/dist/core/help.js +1 -1
  135. package/dist/core/human/__tests__/page-catalog.test.js +4 -0
  136. package/dist/core/human/__tests__/page-tickets.test.js +65 -52
  137. package/dist/core/human/__tests__/page.test.js +56 -98
  138. package/dist/core/human/__tests__/serial/inbox-core.test.js +6 -6
  139. package/dist/core/human/answer-text.js +3 -3
  140. package/dist/core/human/answer.d.ts +6 -4
  141. package/dist/core/human/answer.js +4 -10
  142. package/dist/core/human/component-docs.d.ts +11 -7
  143. package/dist/core/human/component-docs.js +361 -88
  144. package/dist/core/human/convention.d.ts +17 -3
  145. package/dist/core/human/convention.js +36 -5
  146. package/dist/core/human/feedback-companion.d.ts +20 -0
  147. package/dist/core/human/feedback-companion.js +98 -0
  148. package/dist/core/human/feedback.d.ts +65 -0
  149. package/dist/core/human/feedback.js +116 -0
  150. package/dist/core/human/page-catalog.d.ts +40 -5
  151. package/dist/core/human/page-catalog.js +102 -30
  152. package/dist/core/human/page-errors.d.ts +4 -0
  153. package/dist/core/human/page-errors.js +7 -0
  154. package/dist/core/human/page-eval.d.ts +20 -0
  155. package/dist/core/human/page-eval.js +123 -0
  156. package/dist/core/human/page-schema.d.ts +23 -191
  157. package/dist/core/human/page-schema.js +60 -89
  158. package/dist/core/human/page-synth.d.ts +1 -1
  159. package/dist/core/human/page-synth.js +15 -18
  160. package/dist/core/human/page.d.ts +12 -27
  161. package/dist/core/human/page.js +142 -474
  162. package/dist/core/human/root.d.ts +6 -2
  163. package/dist/core/human/root.js +12 -4
  164. package/dist/core/human/scan.d.ts +3 -4
  165. package/dist/core/human/scan.js +16 -15
  166. package/dist/core/human/summary.d.ts +1 -2
  167. package/dist/core/human/summary.js +2 -2
  168. package/dist/core/human/tickets.d.ts +20 -14
  169. package/dist/core/human/tickets.js +91 -31
  170. package/dist/core/human/types.d.ts +10 -2
  171. package/dist/core/preview-registry.js +8 -10
  172. package/dist/core/profiles/default-binding.d.ts +4 -0
  173. package/dist/core/profiles/default-binding.js +24 -0
  174. package/dist/core/profiles/deletion-reservation.d.ts +8 -0
  175. package/dist/core/profiles/deletion-reservation.js +38 -0
  176. package/dist/core/profiles/manifest.d.ts +3 -0
  177. package/dist/core/profiles/manifest.js +12 -0
  178. package/dist/core/profiles/select.js +2 -2
  179. package/dist/core/review/realize.js +3 -3
  180. package/dist/core/review/store.d.ts +6 -0
  181. package/dist/core/review/store.js +31 -0
  182. package/dist/core/runtime/bearings.d.ts +0 -4
  183. package/dist/core/runtime/bearings.js +4 -62
  184. package/dist/core/runtime/bin-contributions.d.ts +44 -0
  185. package/dist/core/runtime/bin-contributions.js +186 -0
  186. package/dist/core/runtime/broker/client-registry.d.ts +4 -0
  187. package/dist/core/runtime/broker/client-registry.js +13 -1
  188. package/dist/core/runtime/broker/extension-abort.d.ts +1 -1
  189. package/dist/core/runtime/broker/extension-abort.js +4 -2
  190. package/dist/core/runtime/broker/fault-retry.js +4 -0
  191. package/dist/core/runtime/broker/frame-dispatch.d.ts +3 -1
  192. package/dist/core/runtime/broker/frame-dispatch.js +5 -6
  193. package/dist/core/runtime/broker/read-ops.d.ts +1 -1
  194. package/dist/core/runtime/broker/read-ops.js +2 -2
  195. package/dist/core/runtime/broker/rebind.d.ts +1 -0
  196. package/dist/core/runtime/broker/rebind.js +6 -1
  197. package/dist/core/runtime/broker-extension-render.js +19 -0
  198. package/dist/core/runtime/broker-protocol.d.ts +14 -0
  199. package/dist/core/runtime/broker.d.ts +1 -1
  200. package/dist/core/runtime/broker.js +17 -6
  201. package/dist/core/runtime/front-door-env.d.ts +5 -0
  202. package/dist/core/runtime/front-door-env.js +16 -0
  203. package/dist/core/runtime/front-door.d.ts +1 -5
  204. package/dist/core/runtime/front-door.js +2 -5
  205. package/dist/core/runtime/host.d.ts +1 -1
  206. package/dist/core/runtime/invocation.d.ts +6 -0
  207. package/dist/core/runtime/invocation.js +10 -0
  208. package/dist/core/runtime/kickoff.js +34 -0
  209. package/dist/core/runtime/launch.d.ts +2 -6
  210. package/dist/core/runtime/node-read.js +4 -1
  211. package/dist/core/runtime/nodes.js +2 -0
  212. package/dist/core/runtime/promote.d.ts +1 -0
  213. package/dist/core/runtime/promote.js +3 -1
  214. package/dist/core/runtime/session-cycles.d.ts +6 -0
  215. package/dist/core/runtime/session-cycles.js +15 -1
  216. package/dist/core/runtime/session-visibility.d.ts +17 -8
  217. package/dist/core/runtime/session-visibility.js +19 -10
  218. package/dist/core/runtime/spawn-env.d.ts +9 -1
  219. package/dist/core/runtime/spawn-env.js +62 -2
  220. package/dist/core/runtime/stop-guard.d.ts +2 -2
  221. package/dist/core/runtime/stop-guard.js +2 -2
  222. package/dist/core/runtime/stop-signals.d.ts +1 -1
  223. package/dist/core/runtime/stop-signals.js +2 -2
  224. package/dist/core/runtime/tmux-bindings.js +1 -1
  225. package/dist/core/session-model/session-state.d.ts +4 -3
  226. package/dist/core/session-model/session-state.js +14 -1
  227. package/dist/core/user-settings.d.ts +21 -2
  228. package/dist/core/user-settings.js +32 -11
  229. package/dist/core/worktree.d.ts +15 -45
  230. package/dist/core/worktree.js +59 -261
  231. package/dist/daemon/api/__tests__/seam/profile-delete.test.d.ts +1 -0
  232. package/dist/daemon/api/__tests__/seam/profile-delete.test.js +386 -0
  233. package/dist/daemon/api/handlers/bash-jobs.js +1 -1
  234. package/dist/daemon/api/handlers/broker-ops.js +9 -5
  235. package/dist/daemon/api/handlers/feedback-comments.d.ts +2 -0
  236. package/dist/daemon/api/handlers/feedback-comments.js +207 -0
  237. package/dist/daemon/api/handlers/human.js +24 -20
  238. package/dist/daemon/api/handlers/inbox.d.ts +10 -0
  239. package/dist/daemon/api/handlers/inbox.js +39 -115
  240. package/dist/daemon/api/handlers/profiles.js +11 -29
  241. package/dist/daemon/api/map.d.ts +6 -1
  242. package/dist/daemon/api/map.js +23 -0
  243. package/dist/daemon/api/server.js +3 -1
  244. package/dist/daemon/companion-retire.d.ts +2 -0
  245. package/dist/daemon/companion-retire.js +33 -0
  246. package/dist/daemon/cron-run.js +14 -16
  247. package/dist/daemon/human/finish.d.ts +12 -7
  248. package/dist/daemon/human/finish.js +101 -38
  249. package/dist/daemon/human/sweep.js +5 -1
  250. package/dist/daemon/profile-delete.d.ts +7 -0
  251. package/dist/daemon/profile-delete.js +306 -0
  252. package/dist/daemon/reconcilers/broker-supervision.js +3 -1
  253. package/dist/daemon/review/deliver.js +3 -3
  254. package/dist/daemon/review/finish.d.ts +0 -2
  255. package/dist/daemon/review/finish.js +1 -29
  256. package/dist/pi-extensions/__tests__/canvas-bash-valve.test.js +56 -1
  257. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +4 -4
  258. package/dist/pi-extensions/canvas-bash-valve.js +19 -5
  259. package/dist/pi-extensions/canvas-inbox-watcher.js +1 -1
  260. package/dist/pi-extensions/canvas-review-boundary.d.ts +4 -0
  261. package/dist/pi-extensions/canvas-review-boundary.js +18 -9
  262. package/dist/pi-extensions/canvas-stophook.js +4 -4
  263. package/dist/shared/generated-context.js +2 -2
  264. package/dist/types.d.ts +44 -2
  265. package/dist/types.js +7 -2
  266. package/package.json +6 -5
  267. package/runtime.lock.json +5 -33
  268. package/dist/builtin-memory/init.md +0 -38
  269. package/dist/builtin-memory/plan.md +0 -19
  270. package/dist/builtin-memory/spec.md +0 -19
  271. package/dist/commands/human/doc.d.ts +0 -2
  272. package/dist/commands/human/doc.js +0 -91
  273. package/dist/commands/human/html.js +0 -81
  274. package/dist/commands/human/inbox.d.ts +0 -2
  275. package/dist/core/human/__tests__/html-markdown.test.js +0 -52
  276. package/dist/core/human/__tests__/page-render.test.js +0 -66
  277. package/dist/core/human/html-markdown.d.ts +0 -3
  278. package/dist/core/human/html-markdown.js +0 -288
  279. package/dist/core/human/page-render.d.ts +0 -4
  280. package/dist/core/human/page-render.js +0 -105
  281. package/dist/pages/bundle.css +0 -1
  282. package/dist/pages/bundle.js +0 -1778
  283. package/dist/pages/comments.d.ts +0 -72
  284. package/dist/pages/comments.js +0 -176
  285. package/dist/pages/controls.d.ts +0 -98
  286. package/dist/pages/controls.js +0 -261
  287. package/dist/pages/elements/cards.d.ts +0 -28
  288. package/dist/pages/elements/cards.js +0 -915
  289. package/dist/pages/elements/chart.d.ts +0 -23
  290. package/dist/pages/elements/chart.js +0 -664
  291. package/dist/pages/elements/options.d.ts +0 -30
  292. package/dist/pages/elements/options.js +0 -742
  293. package/dist/pages/elements/pages.d.ts +0 -30
  294. package/dist/pages/elements/pages.js +0 -377
  295. package/dist/pages/elements/slot.d.ts +0 -26
  296. package/dist/pages/elements/slot.js +0 -112
  297. package/dist/pages/elements/table.d.ts +0 -36
  298. package/dist/pages/elements/table.js +0 -1043
  299. package/dist/pages/elements/text.d.ts +0 -31
  300. package/dist/pages/elements/text.js +0 -1175
  301. package/dist/pages/entry.d.ts +0 -8
  302. package/dist/pages/entry.js +0 -8
  303. package/dist/pages/host.d.ts +0 -134
  304. package/dist/pages/host.js +0 -165
  305. package/dist/pages/readonly.d.ts +0 -24
  306. package/dist/pages/readonly.js +0 -31
  307. package/dist/pages/register.d.ts +0 -2
  308. package/dist/pages/register.js +0 -3
  309. package/dist/pages/responses.d.ts +0 -37
  310. package/dist/pages/responses.js +0 -56
  311. package/dist/pages/slot-config.d.ts +0 -36
  312. package/dist/pages/slot-config.js +0 -50
  313. package/dist/pages/types.d.ts +0 -98
  314. package/dist/pages/types.js +0 -23
  315. /package/dist/{core/human/__tests__/html-markdown.test.d.ts → commands/__tests__/node-message.test.d.ts} +0 -0
  316. /package/dist/core/{human/__tests__/page-render.test.d.ts → __tests__/plugin-page-components.test.d.ts} +0 -0
@@ -30,26 +30,24 @@
30
30
  // promises for graceful-teardown drain but never blocks on a slow run.
31
31
  import { spawn } from 'node:child_process';
32
32
  import { createHash, randomBytes, randomUUID } from 'node:crypto';
33
- import { join } from 'node:path';
34
33
  import { setTimeout as delay } from 'node:timers/promises';
35
34
  import { rmSync } from 'node:fs';
36
35
  import { synthesizedPageDocument, writeSynthesizedPage } from '../core/human/page-synth.js';
37
36
  import { ticketDir } from '../core/human/root.js';
38
37
  import { preparePage, publishPage } from '../core/human/tickets.js';
39
- import { readConfig } from '../core/config.js';
38
+ import { resolvePageComponents } from '../core/config.js';
40
39
  import { dueClockCrons, dueOverlapCrons, expiredIdleCrons, runningCrons, acquireCronRunLease, releaseCronRunLease, recordCronRun, listCronRuns, advanceCron, consumeCron, cancelCron, killLeasedCronRun, verifyLeasedCronRun, getCron, setCronState, setCronLastOutputHash, } from '../core/canvas/crons.js';
41
40
  import { getNode } from '../core/canvas/canvas.js';
42
41
  import { fullName } from '../core/canvas/labels.js';
43
42
  import { killProcessGroup } from '../core/canvas/pid.js';
44
43
  import { appendInbox } from '../core/feed/inbox.js';
45
- import { atomicWriteJson } from '../core/fs-utils.js';
46
44
  import { emitEvent } from '../core/events/emit.js';
47
45
  import { loadProfileManifest } from '../core/profiles/manifest.js';
48
46
  import { hasNoNaturalCycle } from '../core/runtime/revive-all.js';
49
47
  import { spawnChild } from '../core/runtime/spawn.js';
50
48
  import { cronWakeOrigin } from '../core/runtime/bearings.js';
51
49
  import { nextSlotAfter, cadenceDisplay } from '../core/wake.js';
52
- import { buildOperationalEnvBase } from '../core/runtime/spawn-env.js';
50
+ import { ADMITTED_HOST_PATH_ENV, buildOperationalEnvBase, resolveAdmittedHostCommand } from '../core/runtime/spawn-env.js';
53
51
  import { parseSink } from './cron-sink.js';
54
52
  import { readProfileEnvVars } from '../core/profiles/env-store.js';
55
53
  /** Output head caps for the run log (spec: "capped, e.g. 4KB"). */
@@ -223,9 +221,9 @@ async function deliverSpawnSink(c, kind, stdout) {
223
221
  });
224
222
  return res.node.node_id;
225
223
  }
226
- /** Deliver stdout through crouter as a durable notify page (fire-and-forget
227
- * inbox state — jobs whose output is a person's call). Mirrors `crtr human
228
- * notify` minus the CLI-env source stamp: the page names the cron instead. */
224
+ /** Deliver stdout through crouter as a standalone human page. Mirrors
225
+ * `crtr human send --inbox` for a display-only page, minus the CLI-env source
226
+ * stamp: the page names the cron instead. */
229
227
  function deliverHumanSink(c, stdout) {
230
228
  const synthesized = writeSynthesizedPage(synthesizedPageDocument(`⏰ cron ${c.name}`, `Scheduled cron "${c.name}" delivered output for your attention.`, stdout, false));
231
229
  // Cron stdout rides into the page verbatim, so it can carry invalid page
@@ -236,20 +234,18 @@ function deliverHumanSink(c, stdout) {
236
234
  const profileName = resolveLiveProfileName(c);
237
235
  prepared = preparePage({
238
236
  sourceFile: synthesized.path,
239
- dialect: 'html',
240
237
  source: {
241
238
  sessionName: `cron ${c.name} (${c.cron_id})`,
242
239
  ...(profileName === undefined ? {} : { profileName }),
243
240
  },
244
- productKinds: readConfig('user').page_components,
241
+ productKinds: resolvePageComponents(),
242
+ delivery: { placement: 'inline', inbox: true, reply: false },
245
243
  });
246
244
  }
247
245
  finally {
248
246
  rmSync(synthesized.dir, { recursive: true, force: true });
249
247
  }
250
- const id = `nfy-${randomBytes(4).toString('hex')}`;
251
- const rc = { mode: 'notify' };
252
- atomicWriteJson(join(ticketDir(id), 'run.json'), rc);
248
+ const id = `page-${randomBytes(4).toString('hex')}`;
253
249
  publishPage(ticketDir(id), prepared);
254
250
  }
255
251
  /** Route stdout to the cron's armed sink. Returns the delivered string on
@@ -267,7 +263,7 @@ async function deliverToSink(c, stdout) {
267
263
  return `delivered: spawned ${nodeId}`;
268
264
  }
269
265
  deliverHumanSink(c, stdout);
270
- return 'delivered: human notify';
266
+ return 'delivered: human send --inbox';
271
267
  }
272
268
  function failureSummary(ctx, deliveryFailure) {
273
269
  if (deliveryFailure !== null)
@@ -328,7 +324,7 @@ function buildEscalationKickoff(c, ctx, deliveryFailure) {
328
324
  `Diagnose why it failed and decide what to do:`,
329
325
  `- Transient or fixable: fix the environment/command context, verify with \`crtr cron run ${c.cron_id}\` (fires now, out of band, shows you the result), then \`crtr cron resume ${c.cron_id}\` to re-arm it.`,
330
326
  `- Obsolete: \`crtr cron cancel ${c.cron_id}\`.`,
331
- `- The user's call (or the fix needs a change you cannot make): \`crtr human ask\`.`,
327
+ `- The user's call (or the fix needs a change you cannot make): \`crtr human send\`.`,
332
328
  '',
333
329
  'The cron stays paused until someone resumes it — nothing refires while you work.',
334
330
  ].join('\n');
@@ -639,8 +635,9 @@ export function executeCron(c, opts) {
639
635
  // Layering: profile store < the arm-time `--env` snapshot (an explicit
640
636
  // choice) < crtr's own constructed CRTR_* names, which always win.
641
637
  const profileId = resolveLiveProfile(c);
638
+ const operationalEnv = buildOperationalEnvBase({ targetCwd: c.cwd, targetProfileId: profileId });
642
639
  const env = {
643
- ...buildOperationalEnvBase({ targetCwd: c.cwd, targetProfileId: profileId }),
640
+ ...operationalEnv,
644
641
  ...readProfileEnvVars(profileId),
645
642
  ...extraEnv,
646
643
  ...(profileId !== null ? { CRTR_PROFILE_ID: profileId } : {}),
@@ -660,11 +657,12 @@ export function executeCron(c, opts) {
660
657
  }
661
658
  if (c.anchor_node != null)
662
659
  env['CRTR_ANCHOR'] = c.anchor_node;
660
+ env[ADMITTED_HOST_PATH_ENV] = operationalEnv[ADMITTED_HOST_PATH_ENV];
663
661
  const startedAtMs = Date.now();
664
662
  const startedAtIso = new Date(startedAtMs).toISOString();
665
663
  let child;
666
664
  try {
667
- child = spawn('bash', ['-c', c.command], {
665
+ child = spawn(resolveAdmittedHostCommand('bash', env, c.cwd), ['-c', c.command], {
668
666
  cwd: c.cwd,
669
667
  env,
670
668
  detached: true, // own process group — killProcessGroup(-pid) reaps the whole tree
@@ -1,14 +1,19 @@
1
- import type { ProductPageComponents } from '../../types.js';
2
1
  import type { TicketResult } from '../../core/human/types.js';
2
+ /** Every ticket-ending action retires the ticket's feedback companion — done
3
+ * when active or idle, never left resident. Idempotent and replay-safe; a
4
+ * corrupt feedback file must not block result publication. Exported for the
5
+ * daemon-start sweep, which must settle terminal tickets whether or not a
6
+ * reply bridge exists. */
7
+ export declare function settleFeedbackCompanion(ticketId: string): void;
3
8
  /**
4
9
  * The one completion path for a page ticket: claim takeover, exclusive result
5
- * publication, then the asking node's wake. Every addressing surface — the
6
- * node-addressed resolve route and the ticket-id-addressed inbox respond route —
7
- * finishes a page here, so an answer always reaches the node that asked.
10
+ * publication, then optional reply delivery. Every addressing surface routes
11
+ * through this function, so the reply-route descriptor is the sole authority
12
+ * on whether completion reaches a bridge.
8
13
  */
9
- export declare function resolvePageTicket(nodeId: string, responses: unknown, productKinds?: ProductPageComponents): Promise<TicketResult>;
14
+ export declare function resolvePageTicket(ticketId: string, responses: unknown): Promise<TicketResult>;
10
15
  /** Cancel a ticket with the store's first-writer-wins publication. */
11
- export declare function cancelHumanTicket(nodeId: string, opts: {
16
+ export declare function cancelHumanTicket(ticketId: string, opts: {
12
17
  reason?: string;
13
18
  actor?: string;
14
19
  }): Promise<TicketResult>;
@@ -17,4 +22,4 @@ export declare function cancelHumanTicket(nodeId: string, opts: {
17
22
  * receipt: every action below is replay-safe, so lost delivery can be retried
18
23
  * by a later resolve/cancel attempt or the daemon-start sweep.
19
24
  */
20
- export declare function deliverTerminalResult(nodeId: string): Promise<boolean>;
25
+ export declare function deliverTerminalResult(ticketId: string): Promise<boolean>;
@@ -1,17 +1,67 @@
1
- import { join } from 'node:path';
2
1
  import { ApiError } from '../../api/index.js';
3
2
  import { getRow, subscribersOf } from '../../core/canvas/canvas.js';
4
- import { readConfig } from '../../core/config.js';
5
3
  import { ticketDir } from '../../core/human/root.js';
6
4
  import { parsePage } from '../../core/human/page.js';
7
5
  import { cancelTicket, projectReviewOutput, readTicketResult, takeoverAndCompletePage, } from '../../core/human/tickets.js';
8
6
  import { describePageAnswer } from '../../core/human/answer.js';
9
7
  import { renderAnswerText } from '../../core/human/answer-text.js';
10
- import { responsePath } from '../../core/human/convention.js';
11
- import { readJsonOrNull } from '../../core/fs-utils.js';
8
+ import { pagePath, readTicketReplyRoute, responsePath } from '../../core/human/convention.js';
9
+ import { readPageFeedback } from '../../core/human/feedback.js';
10
+ import { pageDocumentSha256 } from '../../core/human/feedback-companion.js';
12
11
  import { appendInbox } from '../../core/feed/inbox.js';
13
12
  import { FinalDeliveryError, pushFinal } from '../../core/feed/feed.js';
14
13
  import { transition } from '../../core/runtime/lifecycle.js';
14
+ import { emitEvent } from '../../core/events/emit.js';
15
+ import { retireCompanion } from '../companion-retire.js';
16
+ /** Every ticket-ending action retires the ticket's feedback companion — done
17
+ * when active or idle, never left resident. Idempotent and replay-safe; a
18
+ * corrupt feedback file must not block result publication. Exported for the
19
+ * daemon-start sweep, which must settle terminal tickets whether or not a
20
+ * reply bridge exists. */
21
+ export function settleFeedbackCompanion(ticketId) {
22
+ try {
23
+ const companionNodeId = readPageFeedback(ticketDir(ticketId)).companionNodeId;
24
+ if (companionNodeId !== undefined)
25
+ retireCompanion(companionNodeId, 'approved');
26
+ }
27
+ catch (error) {
28
+ emitEvent({ level: 'warn', event: 'human.feedback_companion.retire_failed', error });
29
+ }
30
+ }
31
+ /** The settle-time feedback addendum to a page completion — answer and
32
+ * cancellation alike: a change notice when the final page differs from the
33
+ * version first commented on, and each still-open item's quote and note.
34
+ * Silent when the page is unchanged and no feedback remains open; never a
35
+ * manufactured prose diff. */
36
+ function feedbackAddendum(dir) {
37
+ let sections;
38
+ try {
39
+ const feedback = readPageFeedback(dir);
40
+ if (feedback.companionNodeId === undefined)
41
+ return '';
42
+ sections = [];
43
+ if (feedback.firstCommentedSha256 !== undefined && pageDocumentSha256(dir) !== feedback.firstCommentedSha256) {
44
+ sections.push(`The page changed during feedback: the person commented on it and its companion conversation (node ${feedback.companionNodeId}) revised it in place. ` +
45
+ `Reread the final page at ${pagePath(dir, 'jsx')} before interpreting this outcome.`);
46
+ }
47
+ const open = feedback.comments.filter((comment) => comment.status === 'open');
48
+ if (open.length > 0) {
49
+ const items = open.map((comment) => [
50
+ ...comment.quote.split('\n').map((line) => `> ${line}`),
51
+ comment.note,
52
+ ].join('\n'));
53
+ sections.push([
54
+ `Feedback left unresolved at settlement — the companion conversation (node ${feedback.companionNodeId}) had not dealt with these items:`,
55
+ ...items,
56
+ ].join('\n\n'));
57
+ }
58
+ }
59
+ catch {
60
+ // The answer itself is the deliverable; a broken feedback record adds nothing.
61
+ return '';
62
+ }
63
+ return sections.length === 0 ? '' : `\n\n${sections.join('\n\n')}`;
64
+ }
15
65
  /** A corrupt manifest must not strand the asking node: the raw response remains its escape hatch. */
16
66
  function renderCorruptPageAnswer(result, resultPath) {
17
67
  return [
@@ -24,22 +74,21 @@ function renderCorruptPageAnswer(result, resultPath) {
24
74
  }
25
75
  function pageTitle(dir) {
26
76
  try {
27
- return parsePage(dir, readConfig('user').page_components).title;
77
+ return parsePage(dir).title;
28
78
  }
29
79
  catch {
30
80
  return undefined;
31
81
  }
32
82
  }
33
83
  function renderPageAnswer(dir, result, resultPath) {
34
- const productKinds = readConfig('user').page_components;
35
84
  let manifest;
36
85
  try {
37
- manifest = parsePage(dir, productKinds);
86
+ manifest = parsePage(dir);
38
87
  }
39
88
  catch {
40
89
  return renderCorruptPageAnswer(result, resultPath);
41
90
  }
42
- return renderAnswerText(describePageAnswer(manifest, result.responses, { completedAt: result.completedAt, productKinds }), resultPath);
91
+ return renderAnswerText(describePageAnswer(manifest, result.responses, { completedAt: result.completedAt }), resultPath) + feedbackAddendum(dir);
43
92
  }
44
93
  function deliveryFailed(error) {
45
94
  // A canonical final is committed inside its own write boundary and can never
@@ -51,9 +100,9 @@ function deliveryFailed(error) {
51
100
  const detail = error instanceof Error ? error.message : String(error);
52
101
  return new ApiError(500, 'delivery_failed', `The answer is recorded and delivery will be retried: ${detail}`);
53
102
  }
54
- async function deliverOrConflict(nodeId) {
103
+ async function deliverOrConflict(ticketId) {
55
104
  try {
56
- await deliverTerminalResult(nodeId);
105
+ await deliverTerminalResult(ticketId);
57
106
  }
58
107
  catch (error) {
59
108
  throw deliveryFailed(error);
@@ -62,16 +111,16 @@ async function deliverOrConflict(nodeId) {
62
111
  }
63
112
  /**
64
113
  * The one completion path for a page ticket: claim takeover, exclusive result
65
- * publication, then the asking node's wake. Every addressing surface — the
66
- * node-addressed resolve route and the ticket-id-addressed inbox respond route —
67
- * finishes a page here, so an answer always reaches the node that asked.
114
+ * publication, then optional reply delivery. Every addressing surface routes
115
+ * through this function, so the reply-route descriptor is the sole authority
116
+ * on whether completion reaches a bridge.
68
117
  */
69
- export async function resolvePageTicket(nodeId, responses, productKinds = []) {
70
- const completed = takeoverAndCompletePage(ticketDir(nodeId), responses, { host: 'crtrd', pid: process.pid }, { productKinds });
118
+ export async function resolvePageTicket(ticketId, responses) {
119
+ const completed = takeoverAndCompletePage(ticketDir(ticketId), responses, { host: 'crtrd', pid: process.pid });
71
120
  if (!completed.won)
72
- return deliverOrConflict(nodeId);
121
+ return deliverOrConflict(ticketId);
73
122
  try {
74
- await deliverTerminalResult(nodeId);
123
+ await deliverTerminalResult(ticketId);
75
124
  }
76
125
  catch (error) {
77
126
  throw deliveryFailed(error);
@@ -79,12 +128,12 @@ export async function resolvePageTicket(nodeId, responses, productKinds = []) {
79
128
  return completed.result;
80
129
  }
81
130
  /** Cancel a ticket with the store's first-writer-wins publication. */
82
- export async function cancelHumanTicket(nodeId, opts) {
83
- const canceled = cancelTicket(ticketDir(nodeId), opts);
131
+ export async function cancelHumanTicket(ticketId, opts) {
132
+ const canceled = cancelTicket(ticketDir(ticketId), opts);
84
133
  if (canceled.status === 'already_resolved')
85
- return deliverOrConflict(nodeId);
134
+ return deliverOrConflict(ticketId);
86
135
  try {
87
- await deliverTerminalResult(nodeId);
136
+ await deliverTerminalResult(ticketId);
88
137
  }
89
138
  catch (error) {
90
139
  throw deliveryFailed(error);
@@ -96,41 +145,55 @@ export async function cancelHumanTicket(nodeId, opts) {
96
145
  * receipt: every action below is replay-safe, so lost delivery can be retried
97
146
  * by a later resolve/cancel attempt or the daemon-start sweep.
98
147
  */
99
- export async function deliverTerminalResult(nodeId) {
100
- const dir = ticketDir(nodeId);
101
- const run = readJsonOrNull(join(dir, 'run.json'));
102
- if (run?.mode === 'notify')
103
- return false;
148
+ export async function deliverTerminalResult(ticketId) {
149
+ const dir = ticketDir(ticketId);
104
150
  const result = readTicketResult(dir);
105
151
  if (result === null)
106
152
  return false;
153
+ // Companion retirement rides the committed result, BEFORE the reply-route
154
+ // gate: every ticket-ending path (resolve, cancel, conflict replay) funnels
155
+ // through here, and an inbox-only page with no reply bridge still settles
156
+ // its companion. The daemon-start sweep settles the tickets that die before
157
+ // reaching this call.
158
+ settleFeedbackCompanion(ticketId);
159
+ const replyRoute = readTicketReplyRoute(dir);
160
+ if (replyRoute === null)
161
+ return false;
162
+ const bridgeNodeId = replyRoute.bridge_node_id;
107
163
  projectReviewOutput(dir);
108
- // The ROW, not `getNode`: a ticket directory IS the bridge's node directory,
109
- // so a bridge whose row has been pruned still answers `getNode` from its
110
- // surviving meta.json — with no status at all, which passed every guard below
111
- // and then threw out of `transition` on every sweep, re-fanning the
112
- // cancellation notice each time. No row means nothing left to deliver to.
113
- const node = getRow(nodeId);
164
+ // The ROW, not node metadata: a bridge whose row has been pruned may retain
165
+ // its ticket files, but no lifecycle target remains. No row means nothing is
166
+ // left to deliver to.
167
+ const node = getRow(bridgeNodeId);
114
168
  if (node === null || node.status === 'dead' || node.status === 'canceled' || (node.status === 'done' && node.final_report !== null)) {
115
169
  return true;
116
170
  }
117
171
  if (result.kind === 'canceled') {
118
172
  const title = pageTitle(dir);
119
173
  const reason = result.reason !== undefined && result.reason !== '' ? result.reason : 'no reason was given';
120
- const subject = title === undefined ? `human interaction ${nodeId} canceled — no answer is coming` : `human interaction ${nodeId} — “${title}” canceled, no answer is coming`;
121
- const body = title === undefined ? `The human interaction ${nodeId} was canceled — ${reason}. No response will arrive, and any partial answers the human had entered were discarded unsent.` : `The page “${title}” was canceled — ${reason}. No response will arrive, and any partial answers the human had entered were discarded unsent.`;
174
+ // A human dismissal is a rejection, not a routing event: say so plainly so
175
+ // the agent acts on the "no" instead of re-raising the same ask.
176
+ const dismissed = result.actor === 'human';
177
+ const subject = dismissed
178
+ ? (title === undefined ? `human interaction ${ticketId} dismissed without answering` : `human interaction ${ticketId} — “${title}” dismissed without answering`)
179
+ : (title === undefined ? `human interaction ${ticketId} canceled — no answer is coming` : `human interaction ${ticketId} — “${title}” canceled, no answer is coming`);
180
+ // Dismissal and withdrawal are settlement paths too: the sender still
181
+ // needs any unresolved feedback and the exceptional page-change notice.
182
+ const body = (dismissed
183
+ ? `The user dismissed ${title === undefined ? `the human interaction ${ticketId}` : `“${title}”`} without answering — a deliberate “no, I'm not answering this.” Treat it as a rejection: no response will arrive, no partial answer exists, and the same ask must not be raised again.`
184
+ : (title === undefined ? `The human interaction ${ticketId} was canceled — ${reason}. No response will arrive, and any partial answers the user had entered were discarded unsent.` : `The page “${title}” was canceled — ${reason}. No response will arrive, and any partial answers the user had entered were discarded unsent.`)) + feedbackAddendum(dir);
122
185
  // One failing subscriber must not silence the rest, and the bridge stays
123
186
  // live until every notice lands: a partial fanout leaves the ticket for the
124
187
  // next resolve/cancel attempt or the daemon-start sweep to replay. A
125
188
  // subscriber notified twice reads the same idempotent "no answer is coming";
126
189
  // a subscriber never notified waits forever.
127
190
  const failedTo = [];
128
- for (const subscriber of subscribersOf(nodeId)) {
191
+ for (const subscriber of subscribersOf(bridgeNodeId)) {
129
192
  if (result.actor !== undefined && subscriber.node_id === result.actor)
130
193
  continue;
131
194
  try {
132
195
  appendInbox(subscriber.node_id, {
133
- from: nodeId,
196
+ from: bridgeNodeId,
134
197
  tier: 'normal',
135
198
  kind: 'message',
136
199
  label: subject,
@@ -143,11 +206,11 @@ export async function deliverTerminalResult(nodeId) {
143
206
  }
144
207
  if (failedTo.length > 0)
145
208
  throw new Error(`cancellation notice failed for ${failedTo.join(', ')}`);
146
- transition(nodeId, 'finish');
209
+ transition(bridgeNodeId, 'finish');
147
210
  return true;
148
211
  }
149
212
  if (result.kind === 'page') {
150
- await pushFinal(nodeId, renderPageAnswer(dir, result, responsePath(dir)));
213
+ await pushFinal(bridgeNodeId, renderPageAnswer(dir, result, responsePath(dir)));
151
214
  }
152
215
  return true;
153
216
  }
@@ -5,7 +5,7 @@ import { getReviewByBridge } from '../../core/review/store.js';
5
5
  import { emitEvent } from '../../core/events/emit.js';
6
6
  import { ticketsRoot } from '../../core/human/root.js';
7
7
  import { readTicketResult } from '../../core/human/tickets.js';
8
- import { deliverTerminalResult } from './finish.js';
8
+ import { deliverTerminalResult, settleFeedbackCompanion } from './finish.js';
9
9
  function isTerminalBridge(status) {
10
10
  return status === 'done' || status === 'dead' || status === 'canceled';
11
11
  }
@@ -27,6 +27,10 @@ export async function reconcileUndeliveredTickets() {
27
27
  // finisher retries only when its winning operation is explicitly repeated.
28
28
  if (getReviewByBridge(entry) !== null)
29
29
  continue;
30
+ // A terminal page ticket retires its feedback companion whether or not a
31
+ // reply bridge exists: inbox-only pages have no bridge row, yet the
32
+ // daemon can die between result publication and companion retirement.
33
+ settleFeedbackCompanion(entry);
30
34
  // The ROW is what carries status; `getNode` would answer from a pruned
31
35
  // bridge's surviving meta.json with no status at all.
32
36
  const bridge = getRow(entry);
@@ -0,0 +1,7 @@
1
+ import type { DeleteProfileResultDTO } from '../api/index.js';
2
+ /** Validate and enact one forced profile deletion. A request from a node that
3
+ * belongs to the target profile is acknowledged before its broker is stopped. */
4
+ export declare function requestProfileDeletion(name: string, body: unknown): Promise<{
5
+ status: 200 | 202;
6
+ body: DeleteProfileResultDTO;
7
+ }>;