@north-light/crouter 0.3.198 → 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 (314) 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__/human-cancel-guard.test.js +31 -10
  99. package/dist/core/__tests__/human-deliver.test.js +31 -26
  100. package/dist/core/__tests__/human-node-not-supervised.test.js +1 -1
  101. package/dist/core/__tests__/plugin-page-components.test.js +157 -0
  102. package/dist/core/__tests__/seam/dormancy-release.test.js +40 -0
  103. package/dist/core/__tests__/serial/flagship-lifecycle.test.js +2 -2
  104. package/dist/core/__tests__/serial/human-deliver-e2e.test.js +12 -6
  105. package/dist/core/__tests__/serial/live-mutation.test.js +1 -1
  106. package/dist/core/__tests__/serial/tmux-surface.test.js +4 -2
  107. package/dist/core/__tests__/serial/worktree.test.js +24 -198
  108. package/dist/core/__tests__/session-cycles.test.js +22 -0
  109. package/dist/core/__tests__/stop-guard.test.js +4 -4
  110. package/dist/core/__tests__/watchdog-abort-arms-retry.test.js +19 -4
  111. package/dist/core/bash-jobs.d.ts +6 -0
  112. package/dist/core/bash-jobs.js +10 -0
  113. package/dist/core/canvas/__tests__/attention.test.js +8 -5
  114. package/dist/core/canvas/__tests__/render-remote.test.js +5 -5
  115. package/dist/core/canvas/attention.js +8 -6
  116. package/dist/core/canvas/browse/model.d.ts +8 -2
  117. package/dist/core/canvas/browse/model.js +11 -4
  118. package/dist/core/canvas/canvas.d.ts +7 -0
  119. package/dist/core/canvas/canvas.js +37 -0
  120. package/dist/core/canvas/crons.d.ts +9 -0
  121. package/dist/core/canvas/crons.js +51 -0
  122. package/dist/core/canvas/node-order.d.ts +22 -1
  123. package/dist/core/canvas/node-order.js +26 -1
  124. package/dist/core/canvas/render-source.d.ts +5 -0
  125. package/dist/core/canvas/render-source.js +1 -0
  126. package/dist/core/canvas/types.d.ts +9 -3
  127. package/dist/core/command-plugins/bundle.d.ts +6 -1
  128. package/dist/core/command-plugins/bundle.js +26 -13
  129. package/dist/core/command.js +2 -1
  130. package/dist/core/config.d.ts +47 -1
  131. package/dist/core/config.js +137 -4
  132. package/dist/core/help.js +1 -1
  133. package/dist/core/human/__tests__/page-catalog.test.js +4 -0
  134. package/dist/core/human/__tests__/page-tickets.test.js +65 -52
  135. package/dist/core/human/__tests__/page.test.js +56 -98
  136. package/dist/core/human/__tests__/serial/inbox-core.test.js +6 -6
  137. package/dist/core/human/answer-text.js +3 -3
  138. package/dist/core/human/answer.d.ts +6 -4
  139. package/dist/core/human/answer.js +4 -10
  140. package/dist/core/human/component-docs.d.ts +11 -7
  141. package/dist/core/human/component-docs.js +361 -88
  142. package/dist/core/human/convention.d.ts +17 -3
  143. package/dist/core/human/convention.js +36 -5
  144. package/dist/core/human/feedback-companion.d.ts +20 -0
  145. package/dist/core/human/feedback-companion.js +98 -0
  146. package/dist/core/human/feedback.d.ts +65 -0
  147. package/dist/core/human/feedback.js +116 -0
  148. package/dist/core/human/page-catalog.d.ts +40 -5
  149. package/dist/core/human/page-catalog.js +102 -30
  150. package/dist/core/human/page-errors.d.ts +4 -0
  151. package/dist/core/human/page-errors.js +7 -0
  152. package/dist/core/human/page-eval.d.ts +20 -0
  153. package/dist/core/human/page-eval.js +123 -0
  154. package/dist/core/human/page-schema.d.ts +23 -191
  155. package/dist/core/human/page-schema.js +60 -89
  156. package/dist/core/human/page-synth.d.ts +1 -1
  157. package/dist/core/human/page-synth.js +15 -18
  158. package/dist/core/human/page.d.ts +12 -27
  159. package/dist/core/human/page.js +142 -474
  160. package/dist/core/human/root.d.ts +6 -2
  161. package/dist/core/human/root.js +12 -4
  162. package/dist/core/human/scan.d.ts +3 -4
  163. package/dist/core/human/scan.js +16 -15
  164. package/dist/core/human/summary.d.ts +1 -2
  165. package/dist/core/human/summary.js +2 -2
  166. package/dist/core/human/tickets.d.ts +20 -14
  167. package/dist/core/human/tickets.js +91 -31
  168. package/dist/core/human/types.d.ts +10 -2
  169. package/dist/core/preview-registry.js +8 -10
  170. package/dist/core/profiles/default-binding.d.ts +4 -0
  171. package/dist/core/profiles/default-binding.js +24 -0
  172. package/dist/core/profiles/deletion-reservation.d.ts +8 -0
  173. package/dist/core/profiles/deletion-reservation.js +38 -0
  174. package/dist/core/profiles/manifest.d.ts +3 -0
  175. package/dist/core/profiles/manifest.js +12 -0
  176. package/dist/core/profiles/select.js +2 -2
  177. package/dist/core/review/realize.js +3 -3
  178. package/dist/core/review/store.d.ts +6 -0
  179. package/dist/core/review/store.js +31 -0
  180. package/dist/core/runtime/bearings.d.ts +0 -4
  181. package/dist/core/runtime/bearings.js +4 -62
  182. package/dist/core/runtime/bin-contributions.d.ts +44 -0
  183. package/dist/core/runtime/bin-contributions.js +186 -0
  184. package/dist/core/runtime/broker/client-registry.d.ts +4 -0
  185. package/dist/core/runtime/broker/client-registry.js +13 -1
  186. package/dist/core/runtime/broker/extension-abort.d.ts +1 -1
  187. package/dist/core/runtime/broker/extension-abort.js +4 -2
  188. package/dist/core/runtime/broker/fault-retry.js +4 -0
  189. package/dist/core/runtime/broker/frame-dispatch.d.ts +3 -1
  190. package/dist/core/runtime/broker/frame-dispatch.js +5 -6
  191. package/dist/core/runtime/broker/read-ops.d.ts +1 -1
  192. package/dist/core/runtime/broker/read-ops.js +2 -2
  193. package/dist/core/runtime/broker/rebind.d.ts +1 -0
  194. package/dist/core/runtime/broker/rebind.js +6 -1
  195. package/dist/core/runtime/broker-extension-render.js +19 -0
  196. package/dist/core/runtime/broker-protocol.d.ts +14 -0
  197. package/dist/core/runtime/broker.d.ts +1 -1
  198. package/dist/core/runtime/broker.js +17 -6
  199. package/dist/core/runtime/front-door-env.d.ts +5 -0
  200. package/dist/core/runtime/front-door-env.js +16 -0
  201. package/dist/core/runtime/front-door.d.ts +1 -5
  202. package/dist/core/runtime/front-door.js +2 -5
  203. package/dist/core/runtime/host.d.ts +1 -1
  204. package/dist/core/runtime/invocation.d.ts +6 -0
  205. package/dist/core/runtime/invocation.js +10 -0
  206. package/dist/core/runtime/kickoff.js +34 -0
  207. package/dist/core/runtime/launch.d.ts +2 -6
  208. package/dist/core/runtime/node-read.js +4 -1
  209. package/dist/core/runtime/nodes.js +2 -0
  210. package/dist/core/runtime/promote.d.ts +1 -0
  211. package/dist/core/runtime/promote.js +3 -1
  212. package/dist/core/runtime/session-cycles.d.ts +6 -0
  213. package/dist/core/runtime/session-cycles.js +15 -1
  214. package/dist/core/runtime/session-visibility.d.ts +17 -8
  215. package/dist/core/runtime/session-visibility.js +19 -10
  216. package/dist/core/runtime/spawn-env.d.ts +9 -1
  217. package/dist/core/runtime/spawn-env.js +62 -2
  218. package/dist/core/runtime/stop-guard.d.ts +2 -2
  219. package/dist/core/runtime/stop-guard.js +2 -2
  220. package/dist/core/runtime/stop-signals.d.ts +1 -1
  221. package/dist/core/runtime/stop-signals.js +2 -2
  222. package/dist/core/runtime/tmux-bindings.js +1 -1
  223. package/dist/core/session-model/session-state.d.ts +4 -3
  224. package/dist/core/session-model/session-state.js +14 -1
  225. package/dist/core/user-settings.d.ts +21 -2
  226. package/dist/core/user-settings.js +32 -11
  227. package/dist/core/worktree.d.ts +15 -45
  228. package/dist/core/worktree.js +59 -261
  229. package/dist/daemon/api/__tests__/seam/profile-delete.test.d.ts +1 -0
  230. package/dist/daemon/api/__tests__/seam/profile-delete.test.js +386 -0
  231. package/dist/daemon/api/handlers/bash-jobs.js +1 -1
  232. package/dist/daemon/api/handlers/broker-ops.js +9 -5
  233. package/dist/daemon/api/handlers/feedback-comments.d.ts +2 -0
  234. package/dist/daemon/api/handlers/feedback-comments.js +207 -0
  235. package/dist/daemon/api/handlers/human.js +24 -20
  236. package/dist/daemon/api/handlers/inbox.d.ts +10 -0
  237. package/dist/daemon/api/handlers/inbox.js +39 -115
  238. package/dist/daemon/api/handlers/profiles.js +11 -29
  239. package/dist/daemon/api/map.d.ts +6 -1
  240. package/dist/daemon/api/map.js +23 -0
  241. package/dist/daemon/api/server.js +3 -1
  242. package/dist/daemon/companion-retire.d.ts +2 -0
  243. package/dist/daemon/companion-retire.js +33 -0
  244. package/dist/daemon/cron-run.js +14 -16
  245. package/dist/daemon/human/finish.d.ts +12 -7
  246. package/dist/daemon/human/finish.js +101 -38
  247. package/dist/daemon/human/sweep.js +5 -1
  248. package/dist/daemon/profile-delete.d.ts +7 -0
  249. package/dist/daemon/profile-delete.js +306 -0
  250. package/dist/daemon/reconcilers/broker-supervision.js +3 -1
  251. package/dist/daemon/review/deliver.js +3 -3
  252. package/dist/daemon/review/finish.d.ts +0 -2
  253. package/dist/daemon/review/finish.js +1 -29
  254. package/dist/pi-extensions/__tests__/canvas-bash-valve.test.js +56 -1
  255. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +4 -4
  256. package/dist/pi-extensions/canvas-bash-valve.js +19 -5
  257. package/dist/pi-extensions/canvas-inbox-watcher.js +1 -1
  258. package/dist/pi-extensions/canvas-review-boundary.d.ts +4 -0
  259. package/dist/pi-extensions/canvas-review-boundary.js +18 -9
  260. package/dist/pi-extensions/canvas-stophook.js +4 -4
  261. package/dist/shared/generated-context.js +2 -2
  262. package/dist/types.d.ts +44 -2
  263. package/dist/types.js +7 -2
  264. package/package.json +4 -4
  265. package/runtime.lock.json +5 -33
  266. package/dist/builtin-memory/init.md +0 -38
  267. package/dist/builtin-memory/plan.md +0 -19
  268. package/dist/builtin-memory/spec.md +0 -19
  269. package/dist/commands/human/doc.d.ts +0 -2
  270. package/dist/commands/human/doc.js +0 -91
  271. package/dist/commands/human/html.js +0 -81
  272. package/dist/commands/human/inbox.d.ts +0 -2
  273. package/dist/core/human/__tests__/html-markdown.test.js +0 -52
  274. package/dist/core/human/__tests__/page-render.test.js +0 -66
  275. package/dist/core/human/html-markdown.d.ts +0 -3
  276. package/dist/core/human/html-markdown.js +0 -288
  277. package/dist/core/human/page-render.d.ts +0 -4
  278. package/dist/core/human/page-render.js +0 -105
  279. package/dist/pages/bundle.css +0 -1
  280. package/dist/pages/bundle.js +0 -1778
  281. package/dist/pages/comments.d.ts +0 -72
  282. package/dist/pages/comments.js +0 -176
  283. package/dist/pages/controls.d.ts +0 -98
  284. package/dist/pages/controls.js +0 -261
  285. package/dist/pages/elements/cards.d.ts +0 -28
  286. package/dist/pages/elements/cards.js +0 -915
  287. package/dist/pages/elements/chart.d.ts +0 -23
  288. package/dist/pages/elements/chart.js +0 -664
  289. package/dist/pages/elements/options.d.ts +0 -30
  290. package/dist/pages/elements/options.js +0 -742
  291. package/dist/pages/elements/pages.d.ts +0 -30
  292. package/dist/pages/elements/pages.js +0 -377
  293. package/dist/pages/elements/slot.d.ts +0 -26
  294. package/dist/pages/elements/slot.js +0 -112
  295. package/dist/pages/elements/table.d.ts +0 -36
  296. package/dist/pages/elements/table.js +0 -1043
  297. package/dist/pages/elements/text.d.ts +0 -31
  298. package/dist/pages/elements/text.js +0 -1175
  299. package/dist/pages/entry.d.ts +0 -8
  300. package/dist/pages/entry.js +0 -8
  301. package/dist/pages/host.d.ts +0 -134
  302. package/dist/pages/host.js +0 -165
  303. package/dist/pages/readonly.d.ts +0 -24
  304. package/dist/pages/readonly.js +0 -31
  305. package/dist/pages/register.d.ts +0 -2
  306. package/dist/pages/register.js +0 -3
  307. package/dist/pages/responses.d.ts +0 -37
  308. package/dist/pages/responses.js +0 -56
  309. package/dist/pages/slot-config.d.ts +0 -36
  310. package/dist/pages/slot-config.js +0 -50
  311. package/dist/pages/types.d.ts +0 -98
  312. package/dist/pages/types.js +0 -23
  313. /package/dist/{core/human/__tests__/html-markdown.test.d.ts → commands/__tests__/node-message.test.d.ts} +0 -0
  314. /package/dist/core/{human/__tests__/page-render.test.d.ts → __tests__/plugin-page-components.test.d.ts} +0 -0
@@ -1,6 +1,10 @@
1
1
  export declare function ticketsRoot(): string;
2
- /** A ticket directory is the bridge node's canvas directory, the same path by construction. */
3
- export declare function ticketDir(nodeId: string): string;
2
+ /** The opaque id the `/v1/human/inbox` routes address a ticket by — a hash of
3
+ * the canonical tickets root and the ticket directory basename. The daemon
4
+ * derives the same value when it resolves a route id back to a directory. */
5
+ export declare function opaqueInboxTicketId(ticketId: string): string;
6
+ /** Ticket directories share the canvas node root. Reply-bearing tickets may use their bridge id; standalone pages use an independent ticket id. */
7
+ export declare function ticketDir(ticketId: string): string;
4
8
  export declare function inboxActivityPath(): string;
5
9
  /** Signal one durable ticket mutation to every open inbox without watching every ticket directory. */
6
10
  export declare function signalInboxActivity(): void;
@@ -1,12 +1,20 @@
1
- import { mkdirSync, watch, writeFileSync } from 'node:fs';
1
+ import { mkdirSync, realpathSync, watch, writeFileSync } from 'node:fs';
2
+ import { createHash } from 'node:crypto';
2
3
  import { basename, dirname, join } from 'node:path';
3
4
  import { crtrHome, nodeDir, nodesRoot } from '../canvas/paths.js';
4
5
  export function ticketsRoot() {
5
6
  return nodesRoot();
6
7
  }
7
- /** A ticket directory is the bridge node's canvas directory, the same path by construction. */
8
- export function ticketDir(nodeId) {
9
- return nodeDir(nodeId);
8
+ /** The opaque id the `/v1/human/inbox` routes address a ticket by — a hash of
9
+ * the canonical tickets root and the ticket directory basename. The daemon
10
+ * derives the same value when it resolves a route id back to a directory. */
11
+ export function opaqueInboxTicketId(ticketId) {
12
+ const canonicalRoot = realpathSync(ticketsRoot());
13
+ return createHash('sha256').update(`${canonicalRoot}\0${ticketId}`, 'utf8').digest('hex');
14
+ }
15
+ /** Ticket directories share the canvas node root. Reply-bearing tickets may use their bridge id; standalone pages use an independent ticket id. */
16
+ export function ticketDir(ticketId) {
17
+ return nodeDir(ticketId);
10
18
  }
11
19
  export function inboxActivityPath() {
12
20
  return join(crtrHome(), 'signals', 'inbox-activity');
@@ -1,13 +1,12 @@
1
- import type { ProductPageComponents } from '../../types.js';
2
1
  import type { PageTicketSummary, TicketSummary } from './types.js';
3
2
  /** Read all unresolved page/review tickets, newest first. Progress never affects visibility. */
4
- export declare function scanInbox(productKinds?: ProductPageComponents): TicketSummary[];
3
+ export declare function scanInbox(): TicketSummary[];
5
4
  export interface PageHistoryEntry extends PageTicketSummary {
6
- state: 'pending' | 'resolved' | 'canceled';
5
+ state: 'pending' | 'resolved' | 'canceled' | 'passive';
7
6
  }
8
7
  /**
9
8
  * Every page ticket raised by one node — pending, resolved or canceled — oldest
10
9
  * first. This is the durable discovery seam for transcript placement, so it is a
11
10
  * separate walk from the pending-only inbox scan and never filters on a terminal file.
12
11
  */
13
- export declare function scanPageHistory(nodeId: string, productKinds?: ProductPageComponents): PageHistoryEntry[];
12
+ export declare function scanPageHistory(nodeId: string): PageHistoryEntry[];
@@ -25,18 +25,18 @@ function reviewSummary(dir, id) {
25
25
  }
26
26
  return { dir, id, kind: 'review', title: review.title, subtitle: review.subtitle, file: review.file, output: review.output, source: review.source, blockedSince: review.blockedSince, claim: claimSummary(dir) };
27
27
  }
28
- function pageSummary(dir, id, productKinds) {
28
+ function pageSummary(dir, id) {
29
29
  let manifest;
30
30
  try {
31
- manifest = parsePage(dir, productKinds);
31
+ manifest = parsePage(dir);
32
32
  }
33
33
  catch {
34
34
  return null;
35
35
  }
36
- let blockedSince = manifest.source?.blockedSince;
37
- if (blockedSince === undefined) {
36
+ let emittedAt = manifest.source?.emittedAt;
37
+ if (emittedAt === undefined) {
38
38
  try {
39
- blockedSince = statSync(pageManifestPath(dir)).mtime.toISOString();
39
+ emittedAt = statSync(pageManifestPath(dir)).mtime.toISOString();
40
40
  }
41
41
  catch {
42
42
  return null;
@@ -49,17 +49,18 @@ function pageSummary(dir, id, productKinds) {
49
49
  title: manifest.title,
50
50
  subtitle: manifest.subtitle ?? '',
51
51
  dialect: manifest.dialect,
52
- placement: manifest.placement,
52
+ placement: manifest.delivery.placement,
53
+ inbox: manifest.delivery.inbox,
53
54
  steps: manifest.steps,
54
55
  slotKinds: manifest.slots.map((slot) => slot.kind),
55
- awaitsResponse: readJsonOrNull(join(dir, 'run.json'))?.mode === 'ask',
56
+ awaitsResponse: manifest.delivery.reply,
56
57
  source: manifest.source ?? {},
57
- blockedSince,
58
+ emittedAt,
58
59
  claim: claimSummary(dir),
59
60
  };
60
61
  }
61
62
  /** Read all unresolved page/review tickets, newest first. Progress never affects visibility. */
62
- export function scanInbox(productKinds = []) {
63
+ export function scanInbox() {
63
64
  let entries;
64
65
  try {
65
66
  entries = readdirSync(ticketsRoot());
@@ -79,18 +80,18 @@ export function scanInbox(productKinds = []) {
79
80
  }
80
81
  if (isResolved(dir))
81
82
  continue;
82
- const item = reviewSummary(dir, basename(dir)) ?? pageSummary(dir, basename(dir), productKinds);
83
- if (item !== null)
83
+ const item = reviewSummary(dir, basename(dir)) ?? pageSummary(dir, basename(dir));
84
+ if (item !== null && (item.kind !== 'page' || item.inbox))
84
85
  items.push(item);
85
86
  }
86
- return items.sort((a, b) => b.blockedSince.localeCompare(a.blockedSince) || a.id.localeCompare(b.id));
87
+ return items.sort((a, b) => (b.kind === 'page' ? b.emittedAt : b.blockedSince).localeCompare(a.kind === 'page' ? a.emittedAt : a.blockedSince) || a.id.localeCompare(b.id));
87
88
  }
88
89
  /**
89
90
  * Every page ticket raised by one node — pending, resolved or canceled — oldest
90
91
  * first. This is the durable discovery seam for transcript placement, so it is a
91
92
  * separate walk from the pending-only inbox scan and never filters on a terminal file.
92
93
  */
93
- export function scanPageHistory(nodeId, productKinds = []) {
94
+ export function scanPageHistory(nodeId) {
94
95
  let entries;
95
96
  try {
96
97
  entries = readdirSync(ticketsRoot());
@@ -108,12 +109,12 @@ export function scanPageHistory(nodeId, productKinds = []) {
108
109
  catch {
109
110
  continue;
110
111
  }
111
- const summary = pageSummary(dir, basename(dir), productKinds);
112
+ const summary = pageSummary(dir, basename(dir));
112
113
  if (summary === null || summary.source.nodeId !== nodeId)
113
114
  continue;
114
115
  const state = pageTicketState(dir);
115
116
  const result = state === 'resolved' ? readTicketResult(dir) : null;
116
117
  items.push({ ...summary, state, ...(result?.kind === 'page' ? { answerDigest: result.summary.replace(/\s+/g, ' ').trim() } : {}) });
117
118
  }
118
- return items.sort((a, b) => Date.parse(a.blockedSince) - Date.parse(b.blockedSince) || a.id.localeCompare(b.id));
119
+ return items.sort((a, b) => Date.parse(a.emittedAt) - Date.parse(b.emittedAt) || a.id.localeCompare(b.id));
119
120
  }
@@ -1,4 +1,3 @@
1
- import type { ProductPageComponents } from '../../types.js';
2
1
  import type { PageManifest, PageResponses } from './page-schema.js';
3
2
  /** The stable one-line digest stored in response.json. */
4
- export declare function buildPageSummary(manifest: PageManifest, responses: PageResponses, productKinds?: ProductPageComponents): string;
3
+ export declare function buildPageSummary(manifest: PageManifest, responses: PageResponses): string;
@@ -1,5 +1,5 @@
1
1
  import { describePageAnswer } from './answer.js';
2
2
  /** The stable one-line digest stored in response.json. */
3
- export function buildPageSummary(manifest, responses, productKinds) {
4
- return describePageAnswer(manifest, responses, { completedAt: '', productKinds }).digest;
3
+ export function buildPageSummary(manifest, responses) {
4
+ return describePageAnswer(manifest, responses, { completedAt: '' }).digest;
5
5
  }
@@ -1,22 +1,22 @@
1
1
  import type { ReviewDescriptor, TicketResult } from './types.js';
2
2
  import type { ProductPageComponents } from '../../types.js';
3
- import { type AuthoredPageDialect } from './page.js';
4
- import { type PageManifest, type PageResponses, type PageSource } from './page-schema.js';
3
+ import { type PageDelivery, type PageManifest, type PageResponses, type PageSource } from './page-schema.js';
5
4
  /** Strict decoder for the only canonical final marker. */
6
5
  export declare function readTicketResult(dir: string): TicketResult | null;
7
6
  export interface SubmitPageOptions {
8
7
  dir: string;
9
8
  /** The authored page source; copied into the ticket directory as its `document`. */
10
9
  sourceFile: string;
11
- dialect: AuthoredPageDialect;
12
10
  source?: PageSource;
13
11
  productKinds?: ProductPageComponents;
12
+ delivery?: PageDelivery;
14
13
  }
15
14
  /** One validated page, ready to publish into a ticket directory. */
16
15
  export interface PreparedPage {
17
- dialect: AuthoredPageDialect;
18
16
  /** The authored source, verbatim, as the ticket's `document`. */
19
17
  document: string;
18
+ /** The compiled CommonJS module the Gateway evaluates for JSX pages. */
19
+ module?: string;
20
20
  manifest: PageManifest;
21
21
  }
22
22
  /** Validate one authored page source into publishable form. Reads only: a page
@@ -25,7 +25,12 @@ export interface PreparedPage {
25
25
  * The source is read exactly once, so the published document is the same bytes
26
26
  * the manifest was derived from even if the file changes underneath. */
27
27
  export declare function preparePage(opts: Omit<SubmitPageOptions, 'dir'>): PreparedPage;
28
- /** Publish one prepared page ticket: the authored source plus its manifest, exclusively. */
28
+ /** Publish one prepared page ticket: the authored source plus its manifest.
29
+ * These writes are overwrites. A ticket's REQUEST is whatever page the agent
30
+ * currently wants on screen, and `crtr human send --replace` revises it in
31
+ * place while the human watches; only the ANSWER is write-once, which
32
+ * `publishJsonExclusive` still guarantees for response.json alone. The manifest
33
+ * switches after all new generation files exist, then stale media is removed. */
29
34
  export declare function publishPage(dir: string, prepared: PreparedPage): {
30
35
  id: string;
31
36
  dir: string;
@@ -54,26 +59,27 @@ export declare function submitReview(opts: SubmitReviewOptions): {
54
59
  kind: 'review';
55
60
  };
56
61
  /** Validate a page resolution against its canonical ticket before claim takeover. */
57
- export declare function validatePageTicketResponses(dir: string, responses: unknown, productKinds?: ProductPageComponents): PageResponses;
62
+ export declare function validatePageTicketResponses(dir: string, responses: unknown): PageResponses;
58
63
  /** The three terminal states of a page ticket, derived from its published result. */
59
- export declare function pageTicketState(dir: string): 'pending' | 'resolved' | 'canceled';
60
- /** Autosave partial page work. Every supplied response is validated; omitted slots are permitted. */
61
- export declare function writePageProgress(dir: string, responses: unknown, productKinds?: ProductPageComponents): PageResponses;
64
+ export declare function pageTicketState(dir: string): 'pending' | 'resolved' | 'canceled' | 'passive';
65
+ /** Autosave partial page work. Every supplied response is validated; omitted components are permitted. */
66
+ export declare function writePageProgress(dir: string, responses: unknown): PageResponses;
62
67
  /** Saved partial work, or null when none exists. Malformed progress is an error, never silently discarded. */
63
- export declare function readPageProgress(dir: string, productKinds?: ProductPageComponents): PageResponses | null;
68
+ export declare function readPageProgress(dir: string): PageResponses | null;
64
69
  export interface PageTicket {
65
70
  dir: string;
66
71
  id: string;
67
72
  manifest: PageManifest;
68
- /** The authored source document, verbatim. */
73
+ /** The authored TSX source document, verbatim. */
69
74
  document: string;
70
- state: 'pending' | 'resolved' | 'canceled';
75
+ /** The compiled module the Gateway evaluates, for JSX pages. */
76
+ module?: string;
77
+ state: 'pending' | 'resolved' | 'canceled' | 'passive';
71
78
  progress: PageResponses | null;
72
79
  }
73
80
  /** Read one addressable page ticket — pending, resolved or canceled alike. */
74
- export declare function readPageTicket(dir: string, productKinds?: ProductPageComponents): PageTicket;
81
+ export declare function readPageTicket(dir: string): PageTicket;
75
82
  export interface CompletePageOptions {
76
- productKinds?: ProductPageComponents;
77
83
  completedAt?: string;
78
84
  }
79
85
  export declare function completePage(dir: string, responses: unknown, claimToken: string, opts?: CompletePageOptions): {
@@ -3,10 +3,10 @@ import { basename, isAbsolute, resolve } from 'node:path';
3
3
  import { randomUUID } from 'node:crypto';
4
4
  import { z } from 'zod';
5
5
  import { buildPageSummary } from './summary.js';
6
- import { atomicWriteJson, clearProgress, claimPath, pageManifestPath, pagePath, progressPath, publishJsonExclusive, requireTicket, responsePath, reviewPath } from './convention.js';
6
+ import { atomicWriteJson, atomicWriteText, clearProgress, claimPath, pageManifestPath, pageModulePath, pagePath, progressPath, publishJsonExclusive, requireTicket, responsePath, reviewPath } from './convention.js';
7
7
  import { validateReviewDescriptor, validateReviewProjection } from './review-schema.js';
8
- import { derivePageManifestFromDocument, parsePage, PageAuthoringError, snapshotPageSources } from './page.js';
9
- import { validatePageResponses, validatePartialPageResponses } from './page-schema.js';
8
+ import { compilePage, deriveHtmlPageManifest, derivePageManifestFromDocument, parsePage, PageAuthoringError } from './page.js';
9
+ import { isResponseBearingSlot, validatePageResponses, validatePartialPageResponses } from './page-schema.js';
10
10
  import { signalInboxActivity } from './root.js';
11
11
  import { readTicketClaim, releaseClaimLocked, withTicketLock } from './claim.js';
12
12
  import { checkReviewableFile } from './visible-paths.js';
@@ -46,7 +46,7 @@ export function readTicketResult(dir) {
46
46
  return canceled.success ? canceled.data : null;
47
47
  }
48
48
  function hasTicketProtocolState(dir) {
49
- return [pageManifestPath(dir), pagePath(dir, 'md'), pagePath(dir, 'html'), reviewPath(dir), responsePath(dir), progressPath(dir), claimPath(dir)].some(existsSync);
49
+ return [pageManifestPath(dir), pagePath(dir), pageModulePath(dir), reviewPath(dir), responsePath(dir), progressPath(dir), claimPath(dir)].some(existsSync);
50
50
  }
51
51
  function publishRequest(path, value) {
52
52
  if (!publishJsonExclusive(path, value))
@@ -58,28 +58,58 @@ function publishRequest(path, value) {
58
58
  * The source is read exactly once, so the published document is the same bytes
59
59
  * the manifest was derived from even if the file changes underneath. */
60
60
  export function preparePage(opts) {
61
+ const html = /\.html?$/i.test(opts.sourceFile);
62
+ if (!html && !opts.sourceFile.endsWith('.tsx'))
63
+ throw new PageAuthoringError(`page source file must be .tsx, .html, or .htm: ${opts.sourceFile}`);
61
64
  if (!existsSync(opts.sourceFile))
62
65
  throw new PageAuthoringError(`page source file does not exist: ${opts.sourceFile}`);
63
66
  const document = readFileSync(opts.sourceFile, 'utf8');
64
- const stamped = { ...(opts.source ?? {}), blockedSince: opts.source?.blockedSince ?? new Date().toISOString() };
65
- const authored = derivePageManifestFromDocument(document, opts.dialect, opts.productKinds ?? [], stamped);
66
- const manifest = snapshotPageSources(authored, opts.productKinds ?? []);
67
- return { dialect: opts.dialect, document, manifest };
67
+ const stamped = { ...(opts.source ?? {}), emittedAt: opts.source?.emittedAt ?? new Date().toISOString() };
68
+ const delivery = opts.delivery ?? { placement: 'inline', inbox: false, reply: false };
69
+ if (html)
70
+ return { document, manifest: deriveHtmlPageManifest(document, stamped, delivery) };
71
+ return { document, module: compilePage(document), manifest: derivePageManifestFromDocument(document, opts.productKinds ?? [], stamped, delivery) };
68
72
  }
69
- /** Publish one prepared page ticket: the authored source plus its manifest, exclusively. */
73
+ /** Remove a stale generation file. The manifest always switches first, so a
74
+ * failed cleanup leaves one valid current record plus harmless old source. */
75
+ function unlinkIfPresent(path) {
76
+ try {
77
+ unlinkSync(path);
78
+ }
79
+ catch (error) {
80
+ if (error.code !== 'ENOENT')
81
+ throw error;
82
+ }
83
+ }
84
+ /** Publish one prepared page ticket: the authored source plus its manifest.
85
+ * These writes are overwrites. A ticket's REQUEST is whatever page the agent
86
+ * currently wants on screen, and `crtr human send --replace` revises it in
87
+ * place while the human watches; only the ANSWER is write-once, which
88
+ * `publishJsonExclusive` still guarantees for response.json alone. The manifest
89
+ * switches after all new generation files exist, then stale media is removed. */
70
90
  export function publishPage(dir, prepared) {
71
91
  mkdirSync(dir, { recursive: true, mode: 0o700 });
72
92
  const ticket = realpathSync(dir);
73
- if (hasTicketProtocolState(ticket))
74
- throw new Error(`ticket protocol state already exists: ${ticket}`);
75
- writeFileSync(pagePath(ticket, prepared.dialect), prepared.document, { flag: 'wx', mode: 0o600 });
76
- publishRequest(pageManifestPath(ticket), prepared.manifest);
93
+ atomicWriteText(pagePath(ticket, prepared.manifest.dialect), prepared.document);
94
+ if (prepared.manifest.dialect === 'jsx')
95
+ atomicWriteText(pageModulePath(ticket), prepared.module);
96
+ atomicWriteJson(pageManifestPath(ticket), prepared.manifest);
97
+ if (prepared.manifest.dialect === 'jsx')
98
+ unlinkIfPresent(pagePath(ticket, 'html'));
99
+ else {
100
+ unlinkIfPresent(pagePath(ticket, 'jsx'));
101
+ unlinkIfPresent(pageModulePath(ticket));
102
+ }
103
+ // Unconditional: a no-inbox page still lives in an attached viewer's transcript, and
104
+ // --replace promises every open surface reloads it.
77
105
  signalInboxActivity();
78
106
  return { id: basename(ticket), dir: ticket, kind: 'page', manifest: prepared.manifest };
79
107
  }
80
108
  /** Validate and publish one page ticket in a single step, for callers that own
81
109
  * the ticket directory before they have a page. */
82
110
  export function submitPage(opts) {
111
+ if (existsSync(opts.dir) && hasTicketProtocolState(opts.dir) && !existsSync(pageManifestPath(opts.dir)))
112
+ throw new Error(`ticket protocol state already exists: ${opts.dir}`);
83
113
  return publishPage(opts.dir, preparePage(opts));
84
114
  }
85
115
  export function submitReview(opts) {
@@ -104,17 +134,40 @@ function exclusiveResult(dir, result) {
104
134
  return publishJsonExclusive(responsePath(dir), result);
105
135
  }
106
136
  function requireReview(dir) { return validateReviewDescriptor(JSON.parse(readFileSync(reviewPath(dir), 'utf8'))); }
107
- function requirePage(dir, productKinds) {
137
+ function requirePage(dir) {
108
138
  if (!existsSync(pageManifestPath(dir)))
109
139
  throw new Error(`not a page ticket: ${dir}`);
110
- return parsePage(dir, productKinds);
140
+ return parsePage(dir);
141
+ }
142
+ function requireSettleablePage(dir) {
143
+ const manifest = requirePage(dir);
144
+ if (!manifest.delivery.inbox && !manifest.delivery.reply)
145
+ throw new Error('page_has_no_response_lifecycle');
146
+ return manifest;
147
+ }
148
+ function isEmptyResponseMap(responses) {
149
+ return typeof responses === 'object' && responses !== null && !Array.isArray(responses) && Object.keys(responses).length === 0;
150
+ }
151
+ /** Validate a final page response against its ticket's reply contract. */
152
+ function validateFinalPageTicketResponses(dir, responses) {
153
+ const manifest = requireSettleablePage(dir);
154
+ // An inbox-only page accepts an empty acknowledgement without requiring an answer
155
+ // from every response-bearing component.
156
+ if (!manifest.delivery.reply && isEmptyResponseMap(responses)) {
157
+ return validatePartialPageResponses(manifest, responses);
158
+ }
159
+ return validatePageResponses(manifest, responses);
111
160
  }
112
161
  /** Validate a page resolution against its canonical ticket before claim takeover. */
113
- export function validatePageTicketResponses(dir, responses, productKinds = []) {
114
- return validatePageResponses(requirePage(requireTicket(dir), productKinds), responses);
162
+ export function validatePageTicketResponses(dir, responses) {
163
+ const ticket = requireTicket(dir);
164
+ return validateFinalPageTicketResponses(ticket, responses);
115
165
  }
116
166
  /** The three terminal states of a page ticket, derived from its published result. */
117
167
  export function pageTicketState(dir) {
168
+ const manifest = requirePage(dir);
169
+ if (!manifest.delivery.inbox && !manifest.delivery.reply)
170
+ return 'passive';
118
171
  const result = readTicketResult(dir);
119
172
  if (result === null)
120
173
  return 'pending';
@@ -133,33 +186,38 @@ function readPageProgressFor(dir, manifest) {
133
186
  const parsed = pageProgressSchema.safeParse(raw);
134
187
  if (!parsed.success)
135
188
  throw new Error(`page progress is malformed: ${progressPath(dir)}`);
136
- // Saved work is validated on read as well as on write: a page whose manifest
137
- // changed under it must fail loudly rather than hydrate an answer it no longer has.
138
- return validatePartialPageResponses(manifest, parsed.data.responses);
189
+ // A pending page's request can be replaced under saved work, so progress may
190
+ // name a component the current page no longer declares. Those entries are dropped
191
+ // and everything still declared hydrates; what remains is validated, so a
192
+ // saved answer that no longer fits its own component is still an error.
193
+ const declared = new Set(manifest.slots.filter(isResponseBearingSlot).map((slot) => slot.id));
194
+ const current = Object.fromEntries(Object.entries(parsed.data.responses).filter(([id]) => declared.has(id)));
195
+ return validatePartialPageResponses(manifest, current);
139
196
  }
140
- /** Autosave partial page work. Every supplied response is validated; omitted slots are permitted. */
141
- export function writePageProgress(dir, responses, productKinds = []) {
197
+ /** Autosave partial page work. Every supplied response is validated; omitted components are permitted. */
198
+ export function writePageProgress(dir, responses) {
142
199
  const ticket = requireTicket(dir);
143
- const validated = validatePartialPageResponses(requirePage(ticket, productKinds), responses);
200
+ const validated = validatePartialPageResponses(requireSettleablePage(ticket), responses);
144
201
  atomicWriteJson(progressPath(ticket), { kind: 'page', responses: validated, savedAt: new Date().toISOString() });
145
202
  return validated;
146
203
  }
147
204
  /** Saved partial work, or null when none exists. Malformed progress is an error, never silently discarded. */
148
- export function readPageProgress(dir, productKinds = []) {
205
+ export function readPageProgress(dir) {
149
206
  const ticket = requireTicket(dir);
150
- return readPageProgressFor(ticket, requirePage(ticket, productKinds));
207
+ return readPageProgressFor(ticket, requireSettleablePage(ticket));
151
208
  }
152
209
  /** Read one addressable page ticket — pending, resolved or canceled alike. */
153
- export function readPageTicket(dir, productKinds = []) {
210
+ export function readPageTicket(dir) {
154
211
  const ticket = requireTicket(dir);
155
- const manifest = requirePage(ticket, productKinds);
212
+ const manifest = requirePage(ticket);
156
213
  return {
157
214
  dir: ticket,
158
215
  id: basename(ticket),
159
216
  manifest,
160
217
  document: readFileSync(pagePath(ticket, manifest.dialect), 'utf8'),
218
+ ...(manifest.dialect === 'jsx' ? { module: readFileSync(pageModulePath(ticket), 'utf8') } : { module: undefined }),
161
219
  state: pageTicketState(ticket),
162
- progress: readPageProgressFor(ticket, manifest),
220
+ progress: manifest.delivery.inbox || manifest.delivery.reply ? readPageProgressFor(ticket, manifest) : null,
163
221
  };
164
222
  }
165
223
  function requireClaimOwnership(dir, token) {
@@ -170,9 +228,9 @@ function clearOwnedWork(dir, claimToken) { clearProgress(dir); releaseClaimLocke
170
228
  /** Caller holds `.ticket-lock`. */
171
229
  function completePageLocked(dir, responses, claimToken, opts) {
172
230
  requireClaimOwnership(dir, claimToken);
173
- const manifest = requirePage(dir, opts.productKinds ?? []);
174
- const parsedResponses = validatePageResponses(manifest, responses);
175
- const result = { schema: 'crtr.page-response/v1', kind: 'page', responses: parsedResponses, summary: buildPageSummary(manifest, parsedResponses, opts.productKinds), completedAt: opts.completedAt ?? new Date().toISOString() };
231
+ const manifest = requirePage(dir);
232
+ const parsedResponses = validateFinalPageTicketResponses(dir, responses);
233
+ const result = { schema: 'crtr.page-response/v1', kind: 'page', responses: parsedResponses, summary: buildPageSummary(manifest, parsedResponses), completedAt: opts.completedAt ?? new Date().toISOString() };
176
234
  const won = exclusiveResult(dir, result);
177
235
  clearOwnedWork(dir, claimToken);
178
236
  return { won, result: won ? result : readTicketResult(dir) ?? result };
@@ -213,6 +271,8 @@ export function takeoverAndCompletePage(dir, responses, owner, opts = {}) {
213
271
  export function cancelTicket(dir, opts = {}) {
214
272
  const ticket = requireTicket(dir);
215
273
  const canceled = withTicketLock(ticket, () => {
274
+ if (existsSync(pageManifestPath(ticket)))
275
+ requireSettleablePage(ticket);
216
276
  const result = { schema: 'humanloop.cancel/v1', kind: 'canceled', canceledAt: new Date().toISOString(), ...(opts.reason === undefined ? {} : { reason: opts.reason }), ...(opts.actor === undefined ? {} : { actor: opts.actor }) };
217
277
  const won = exclusiveResult(ticket, result);
218
278
  return { status: won ? 'canceled' : 'already_resolved', result: won ? result : readTicketResult(ticket) ?? result };
@@ -48,12 +48,20 @@ export interface ReviewTicketSummary extends TicketSummaryBase {
48
48
  output: string;
49
49
  }
50
50
  /** Page triage fields: a client picks a transcript rendering without a second fetch. */
51
- export interface PageTicketSummary extends TicketSummaryBase {
51
+ export interface PageTicketSummary {
52
+ dir: string;
53
+ id: string;
52
54
  kind: 'page';
55
+ title: string;
56
+ subtitle: string;
57
+ source: import('./page-schema.js').PageSource;
58
+ emittedAt: string;
59
+ claim?: ClaimSummary;
53
60
  /** Present on resolved history entries; the stored one-line response digest. */
54
61
  answerDigest?: string;
55
- dialect: 'md' | 'html';
62
+ dialect: 'jsx' | 'html';
56
63
  placement: 'inline' | 'panel';
64
+ inbox: boolean;
57
65
  steps: number;
58
66
  slotKinds: string[];
59
67
  awaitsResponse: boolean;
@@ -104,9 +104,8 @@ const LEAF_ICON_CP = {
104
104
  'canvas issue new': 0xf188,
105
105
  'canvas prune': 0xf0c4,
106
106
  'canvas rebuild-index': 0xf0ad,
107
- 'human ask': 0xf059,
107
+ 'human send': 0xf059,
108
108
  'human review': 0xf044,
109
- 'human notify': 0xf0f3,
110
109
  'human show': 0xf108,
111
110
  'human cancel': 0xf00d,
112
111
  'human list': 0xf03a,
@@ -304,15 +303,14 @@ function summarizeDaemon(field, active, inactive) {
304
303
  return [{ text: state ? `${active}${pid === undefined ? '' : ` · pid ${pid}`}` : inactive, tone: state ? 'success' : 'muted' }];
305
304
  };
306
305
  }
307
- function summarizeHumanAsk(input, tokens) {
306
+ function summarizeHumanSend(input) {
308
307
  const value = record(input);
309
- if (value === undefined || stringField(value, 'job_id') === undefined)
308
+ if (value === undefined || stringField(value, 'page_id') === undefined)
310
309
  return null;
311
- const question = tokens.slice(2).find((token) => !token.startsWith('--'));
312
- return [
313
- { text: 'ask posted → waiting on human', tone: 'accent' },
314
- ...(question === undefined ? [] : [{ text: truncateLine(question.split('\n')[0].trim(), 160), tone: 'muted' }]),
315
- ];
310
+ const reply = booleanField(value, 'reply');
311
+ if (reply === undefined)
312
+ return null;
313
+ return [{ text: reply ? 'page sent → waiting on human' : 'announcement sent', tone: reply ? 'accent' : 'success' }];
316
314
  }
317
315
  function summarizeHumanResolve(input) {
318
316
  const value = record(input);
@@ -352,7 +350,7 @@ const SUMMARIZERS = [
352
350
  { path: 'canvas attention count', run: summarizeCount('pending asks', true) },
353
351
  { path: 'canvas attention list', run: summarizeArray('pending asks') },
354
352
  { path: 'canvas attention map', run: summarizeAttentionMap },
355
- { path: 'human ask', run: summarizeHumanAsk },
353
+ { path: 'human send', run: summarizeHumanSend },
356
354
  { path: 'human list', run: summarizeArray('pending') },
357
355
  { path: 'human resolve', run: summarizeHumanResolve },
358
356
  { path: 'pkg plugin list', run: summarizeArray('plugins') },
@@ -4,3 +4,7 @@ export declare function resolveBindingCwd(cwd: string): string;
4
4
  export declare function getDefaultProfileId(cwd: string): string | null;
5
5
  export declare function setDefaultProfileId(cwd: string, profileId: string): void;
6
6
  export declare function clearDefaultProfile(cwd: string): void;
7
+ /** Every directory currently pinned to `profileId`, in deterministic order. */
8
+ export declare function defaultProfileDirs(profileId: string): string[];
9
+ /** Clear every per-directory default that names `profileId`. */
10
+ export declare function clearDefaultProfileId(profileId: string): string[];
@@ -44,3 +44,27 @@ export function clearDefaultProfile(cwd) {
44
44
  else
45
45
  writeJson(bindingsPath(), value);
46
46
  }
47
+ /** Every directory currently pinned to `profileId`, in deterministic order. */
48
+ export function defaultProfileDirs(profileId) {
49
+ return Object.entries(bindings())
50
+ .filter(([, pinnedProfileId]) => pinnedProfileId === profileId)
51
+ .map(([dir]) => dir)
52
+ .sort();
53
+ }
54
+ /** Clear every per-directory default that names `profileId`. */
55
+ export function clearDefaultProfileId(profileId) {
56
+ const value = bindings();
57
+ const cleared = Object.entries(value)
58
+ .filter(([, pinnedProfileId]) => pinnedProfileId === profileId)
59
+ .map(([dir]) => dir)
60
+ .sort();
61
+ if (cleared.length === 0)
62
+ return [];
63
+ for (const dir of cleared)
64
+ delete value[dir];
65
+ if (Object.keys(value).length === 0)
66
+ removePath(bindingsPath());
67
+ else
68
+ writeJson(bindingsPath(), value);
69
+ return cleared;
70
+ }
@@ -0,0 +1,8 @@
1
+ /** Reserve one profile identity for its complete daemon-owned deletion lifecycle.
2
+ * The caller must release the returned lease after completion or failure. */
3
+ export declare function reserveProfileDeletion(profileId: string): () => void;
4
+ /** The synchronous birth gate. spawnNode calls this immediately before the
5
+ * canvas row is persisted. A reservation rejects a birth that reaches the gate
6
+ * during deletion; the exact manifest read rejects one whose async preflight
7
+ * started before a fast hard deletion and resumed after its lease was released. */
8
+ export declare function assertProfileAvailableForBirth(profileId: string | null): void;
@@ -0,0 +1,38 @@
1
+ import { usage } from '../errors.js';
2
+ import { loadExactProfileManifest } from './manifest.js';
3
+ const deletingProfileIds = new Set();
4
+ /** Reserve one profile identity for its complete daemon-owned deletion lifecycle.
5
+ * The caller must release the returned lease after completion or failure. */
6
+ export function reserveProfileDeletion(profileId) {
7
+ if (deletingProfileIds.has(profileId)) {
8
+ throw usage(`profile deletion is already in progress: ${profileId}`, {
9
+ error: 'profile_deletion_in_progress',
10
+ profile_id: profileId,
11
+ next: 'Wait for the accepted deletion to finish before using this profile again.',
12
+ });
13
+ }
14
+ deletingProfileIds.add(profileId);
15
+ let released = false;
16
+ return () => {
17
+ if (released)
18
+ return;
19
+ released = true;
20
+ deletingProfileIds.delete(profileId);
21
+ };
22
+ }
23
+ /** The synchronous birth gate. spawnNode calls this immediately before the
24
+ * canvas row is persisted. A reservation rejects a birth that reaches the gate
25
+ * during deletion; the exact manifest read rejects one whose async preflight
26
+ * started before a fast hard deletion and resumed after its lease was released. */
27
+ export function assertProfileAvailableForBirth(profileId) {
28
+ if (profileId === null)
29
+ return;
30
+ if (deletingProfileIds.has(profileId)) {
31
+ throw usage(`cannot create a node while its profile is being deleted: ${profileId}`, {
32
+ error: 'profile_deletion_in_progress',
33
+ profile_id: profileId,
34
+ next: 'Choose another profile or wait for this deletion to finish.',
35
+ });
36
+ }
37
+ loadExactProfileManifest(profileId);
38
+ }
@@ -30,6 +30,9 @@ export declare function listProfiles(): ProfileEntry[];
30
30
  * function every command leaf and runtime consumer should
31
31
  * call to turn a `<profile>` operand or `CRTR_PROFILE_ID` into a concrete,
32
32
  * safe profile id. */
33
+ /** Read one exact profile id without falling back to manifest-name matching.
34
+ * Used at persistence gates that already hold a resolved durable identity. */
35
+ export declare function loadExactProfileManifest(profileId: string): ProfileEntry;
33
36
  export declare function loadProfileManifest(profileIdOrName: string): ProfileEntry;
34
37
  /** Hold the per-profile manifest lock for the duration of `fn`. ALL manifest
35
38
  * mutations (create/rename/add-project/remove-project/delete/last-used)
@@ -150,6 +150,18 @@ export function listProfiles() {
150
150
  * function every command leaf and runtime consumer should
151
151
  * call to turn a `<profile>` operand or `CRTR_PROFILE_ID` into a concrete,
152
152
  * safe profile id. */
153
+ /** Read one exact profile id without falling back to manifest-name matching.
154
+ * Used at persistence gates that already hold a resolved durable identity. */
155
+ export function loadExactProfileManifest(profileId) {
156
+ assertSafeProfileId(profileId);
157
+ const manifest = readManifestFile(profileManifestPath(profileId), profileId);
158
+ if (manifest !== null)
159
+ return { profileId, manifest };
160
+ throw notFound(`profile not found: ${profileId}`, {
161
+ received: profileId,
162
+ next: 'Run `crtr profile list` to see available profiles.',
163
+ });
164
+ }
153
165
  export function loadProfileManifest(profileIdOrName) {
154
166
  const all = listProfiles();
155
167
  if (ID_SHAPE.test(profileIdOrName)) {