@north-light/api 0.0.0-stage → 0.3.414

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 (247) hide show
  1. package/LICENSE +18 -0
  2. package/README.md +66 -2
  3. package/dist/api/__tests__/error-codes.test.d.ts +1 -0
  4. package/dist/api/__tests__/error-codes.test.js +79 -0
  5. package/dist/api/__tests__/integration/client.test.d.ts +1 -0
  6. package/dist/api/__tests__/integration/client.test.js +276 -0
  7. package/dist/api/client.d.ts +501 -0
  8. package/dist/api/client.js +1281 -0
  9. package/dist/api/command-manifest/index.d.ts +3 -0
  10. package/dist/api/command-manifest/index.js +3 -0
  11. package/dist/api/command-manifest/manifest.d.ts +51 -0
  12. package/dist/api/command-manifest/manifest.js +332 -0
  13. package/dist/api/command-manifest/result.d.ts +25 -0
  14. package/dist/api/command-manifest/result.js +97 -0
  15. package/dist/api/command-manifest/schema.d.ts +28 -0
  16. package/dist/api/command-manifest/schema.js +856 -0
  17. package/dist/api/dto/analytics.d.ts +184 -0
  18. package/dist/api/dto/analytics.js +3 -0
  19. package/dist/api/dto/attach.d.ts +22 -0
  20. package/dist/api/dto/attach.js +13 -0
  21. package/dist/api/dto/bash-jobs.d.ts +24 -0
  22. package/dist/api/dto/bash-jobs.js +9 -0
  23. package/dist/api/dto/bash.d.ts +17 -0
  24. package/dist/api/dto/bash.js +1 -0
  25. package/dist/api/dto/broker-ops.d.ts +187 -0
  26. package/dist/api/dto/broker-ops.js +6 -0
  27. package/dist/api/dto/broker-signals.d.ts +25 -0
  28. package/dist/api/dto/broker-signals.js +1 -0
  29. package/dist/api/dto/broker.d.ts +86 -0
  30. package/dist/api/dto/broker.js +20 -0
  31. package/dist/api/dto/canvas.d.ts +359 -0
  32. package/dist/api/dto/canvas.js +2 -0
  33. package/dist/api/dto/chat-inventory.d.ts +56 -0
  34. package/dist/api/dto/chat-inventory.js +11 -0
  35. package/dist/api/dto/common.d.ts +29 -0
  36. package/dist/api/dto/common.js +15 -0
  37. package/dist/api/dto/config.d.ts +46 -0
  38. package/dist/api/dto/config.js +3 -0
  39. package/dist/api/dto/crons.d.ts +158 -0
  40. package/dist/api/dto/crons.js +10 -0
  41. package/dist/api/dto/custom-objects.d.ts +66 -0
  42. package/dist/api/dto/custom-objects.js +1 -0
  43. package/dist/api/dto/delivery.d.ts +71 -0
  44. package/dist/api/dto/delivery.js +7 -0
  45. package/dist/api/dto/docs.d.ts +135 -0
  46. package/dist/api/dto/docs.js +8 -0
  47. package/dist/api/dto/files.d.ts +21 -0
  48. package/dist/api/dto/files.js +1 -0
  49. package/dist/api/dto/focus.d.ts +24 -0
  50. package/dist/api/dto/focus.js +10 -0
  51. package/dist/api/dto/grants.d.ts +28 -0
  52. package/dist/api/dto/grants.js +1 -0
  53. package/dist/api/dto/health.d.ts +116 -0
  54. package/dist/api/dto/health.js +2 -0
  55. package/dist/api/dto/human-requests.d.ts +113 -0
  56. package/dist/api/dto/human-requests.js +4 -0
  57. package/dist/api/dto/human.d.ts +28 -0
  58. package/dist/api/dto/human.js +4 -0
  59. package/dist/api/dto/inbox.d.ts +273 -0
  60. package/dist/api/dto/inbox.js +4 -0
  61. package/dist/api/dto/lifecycle.d.ts +88 -0
  62. package/dist/api/dto/lifecycle.js +3 -0
  63. package/dist/api/dto/limits.d.ts +29 -0
  64. package/dist/api/dto/limits.js +2 -0
  65. package/dist/api/dto/mail.d.ts +44 -0
  66. package/dist/api/dto/mail.js +1 -0
  67. package/dist/api/dto/messages.d.ts +104 -0
  68. package/dist/api/dto/messages.js +2 -0
  69. package/dist/api/dto/model-config.d.ts +25 -0
  70. package/dist/api/dto/model-config.js +1 -0
  71. package/dist/api/dto/modelauth.d.ts +132 -0
  72. package/dist/api/dto/modelauth.js +4 -0
  73. package/dist/api/dto/node-events.d.ts +65 -0
  74. package/dist/api/dto/node-events.js +4 -0
  75. package/dist/api/dto/node-outcomes.d.ts +88 -0
  76. package/dist/api/dto/node-outcomes.js +2 -0
  77. package/dist/api/dto/node-records.d.ts +35 -0
  78. package/dist/api/dto/node-records.js +5 -0
  79. package/dist/api/dto/nodes.d.ts +371 -0
  80. package/dist/api/dto/nodes.js +3 -0
  81. package/dist/api/dto/objects.d.ts +172 -0
  82. package/dist/api/dto/objects.js +5 -0
  83. package/dist/api/dto/profiles.d.ts +117 -0
  84. package/dist/api/dto/profiles.js +4 -0
  85. package/dist/api/dto/recovery.d.ts +104 -0
  86. package/dist/api/dto/recovery.js +1 -0
  87. package/dist/api/dto/reports.d.ts +95 -0
  88. package/dist/api/dto/reports.js +2 -0
  89. package/dist/api/dto/review-comments.d.ts +146 -0
  90. package/dist/api/dto/review-comments.js +5 -0
  91. package/dist/api/dto/reviews.d.ts +113 -0
  92. package/dist/api/dto/reviews.js +5 -0
  93. package/dist/api/dto/run-events.d.ts +295 -0
  94. package/dist/api/dto/run-events.js +6 -0
  95. package/dist/api/dto/structured-requests.d.ts +64 -0
  96. package/dist/api/dto/structured-requests.js +5 -0
  97. package/dist/api/dto/subscriptions.d.ts +14 -0
  98. package/dist/api/dto/subscriptions.js +2 -0
  99. package/dist/api/dto/worktree.d.ts +55 -0
  100. package/dist/api/dto/worktree.js +6 -0
  101. package/dist/api/error-codes.d.ts +259 -0
  102. package/dist/api/error-codes.js +56 -0
  103. package/dist/api/errors.d.ts +47 -0
  104. package/dist/api/errors.js +66 -0
  105. package/dist/api/index.d.ts +48 -0
  106. package/dist/api/index.js +45 -0
  107. package/dist/api/long-poll.d.ts +59 -0
  108. package/dist/api/long-poll.js +67 -0
  109. package/dist/api/node-transport.d.ts +18 -0
  110. package/dist/api/node-transport.js +105 -0
  111. package/dist/api/plugin-manifest-schema.d.ts +233 -0
  112. package/dist/api/plugin-manifest-schema.js +23 -0
  113. package/dist/api/routes.d.ts +164 -0
  114. package/dist/api/routes.js +197 -0
  115. package/dist/api/runtime-version.d.ts +31 -0
  116. package/dist/api/runtime-version.js +50 -0
  117. package/dist/core/asset-root.d.ts +7 -0
  118. package/dist/core/asset-root.js +18 -0
  119. package/dist/core/canvas/boot-id.d.ts +6 -0
  120. package/dist/core/canvas/boot-id.js +26 -0
  121. package/dist/core/canvas/paths.d.ts +72 -0
  122. package/dist/core/canvas/paths.js +163 -0
  123. package/dist/core/canvas/pid.d.ts +391 -0
  124. package/dist/core/canvas/pid.js +948 -0
  125. package/dist/core/command-plugins/bundle.d.ts +149 -0
  126. package/dist/core/command-plugins/bundle.js +588 -0
  127. package/dist/core/command-plugins/endpoint.d.ts +24 -0
  128. package/dist/core/command-plugins/endpoint.js +51 -0
  129. package/dist/core/config.d.ts +233 -0
  130. package/dist/core/config.js +1120 -0
  131. package/dist/core/env-name.d.ts +6 -0
  132. package/dist/core/env-name.js +9 -0
  133. package/dist/core/errors.d.ts +38 -0
  134. package/dist/core/errors.js +90 -0
  135. package/dist/core/events/emit.d.ts +6 -0
  136. package/dist/core/events/emit.js +42 -0
  137. package/dist/core/events/envelope.d.ts +2 -0
  138. package/dist/core/events/envelope.js +84 -0
  139. package/dist/core/events/errors.d.ts +4 -0
  140. package/dist/core/events/errors.js +69 -0
  141. package/dist/core/events/operation-id.d.ts +4 -0
  142. package/dist/core/events/operation-id.js +24 -0
  143. package/dist/core/events/serialize.d.ts +4 -0
  144. package/dist/core/events/serialize.js +199 -0
  145. package/dist/core/events/source.d.ts +16 -0
  146. package/dist/core/events/source.js +31 -0
  147. package/dist/core/events/types.d.ts +68 -0
  148. package/dist/core/events/types.js +11 -0
  149. package/dist/core/exclusive-lock.d.ts +34 -0
  150. package/dist/core/exclusive-lock.js +197 -0
  151. package/dist/core/fs-utils.d.ts +44 -0
  152. package/dist/core/fs-utils.js +208 -0
  153. package/dist/core/help.d.ts +309 -0
  154. package/dist/core/help.js +406 -0
  155. package/dist/core/human/page-catalog.d.ts +57 -0
  156. package/dist/core/human/page-catalog.js +172 -0
  157. package/dist/core/installed-plugins.d.ts +2 -0
  158. package/dist/core/installed-plugins.js +79 -0
  159. package/dist/core/io.d.ts +122 -0
  160. package/dist/core/io.js +373 -0
  161. package/dist/core/keybindings/attach-control.d.ts +49 -0
  162. package/dist/core/keybindings/attach-control.js +42 -0
  163. package/dist/core/keybindings/catalog.d.ts +18 -0
  164. package/dist/core/keybindings/catalog.js +257 -0
  165. package/dist/core/keybindings/types.d.ts +42 -0
  166. package/dist/core/keybindings/types.js +1 -0
  167. package/dist/core/layout.d.ts +34 -0
  168. package/dist/core/layout.js +106 -0
  169. package/dist/core/locked-file.d.ts +27 -0
  170. package/dist/core/locked-file.js +118 -0
  171. package/dist/core/log.d.ts +9 -0
  172. package/dist/core/log.js +89 -0
  173. package/dist/core/manifest.d.ts +5 -0
  174. package/dist/core/manifest.js +15 -0
  175. package/dist/core/plugin-env.d.ts +8 -0
  176. package/dist/core/plugin-env.js +31 -0
  177. package/dist/core/plugin-extensions.d.ts +29 -0
  178. package/dist/core/plugin-extensions.js +191 -0
  179. package/dist/core/plugin-swap-lock.d.ts +9 -0
  180. package/dist/core/plugin-swap-lock.js +31 -0
  181. package/dist/core/preview-result-path.d.ts +4 -0
  182. package/dist/core/preview-result-path.js +26 -0
  183. package/dist/core/profiles/env-store.d.ts +22 -0
  184. package/dist/core/profiles/env-store.js +163 -0
  185. package/dist/core/profiles/fuzzy-match.d.ts +19 -0
  186. package/dist/core/profiles/fuzzy-match.js +92 -0
  187. package/dist/core/profiles/manifest.d.ts +120 -0
  188. package/dist/core/profiles/manifest.js +529 -0
  189. package/dist/core/rate-limit-scope.d.ts +25 -0
  190. package/dist/core/rate-limit-scope.js +64 -0
  191. package/dist/core/render.d.ts +12 -0
  192. package/dist/core/render.js +138 -0
  193. package/dist/core/resolver.d.ts +14 -0
  194. package/dist/core/resolver.js +111 -0
  195. package/dist/core/runtime/branded-host.d.ts +55 -0
  196. package/dist/core/runtime/branded-host.js +485 -0
  197. package/dist/core/runtime/broker/daemon-ops.d.ts +65 -0
  198. package/dist/core/runtime/broker/daemon-ops.js +177 -0
  199. package/dist/core/runtime/broker/signal-stream.d.ts +30 -0
  200. package/dist/core/runtime/broker/signal-stream.js +149 -0
  201. package/dist/core/scope.d.ts +32 -0
  202. package/dist/core/scope.js +184 -0
  203. package/dist/core/scoped-state/db.d.ts +17 -0
  204. package/dist/core/scoped-state/db.js +247 -0
  205. package/dist/core/scoped-state/migrate.d.ts +8 -0
  206. package/dist/core/scoped-state/migrate.js +187 -0
  207. package/dist/core/scoped-state/paths.d.ts +9 -0
  208. package/dist/core/scoped-state/paths.js +27 -0
  209. package/dist/core/scoped-state/profiles.d.ts +27 -0
  210. package/dist/core/scoped-state/profiles.js +93 -0
  211. package/dist/core/scoped-state/providers.d.ts +24 -0
  212. package/dist/core/scoped-state/providers.js +19 -0
  213. package/dist/core/scoped-state/schema.d.ts +6 -0
  214. package/dist/core/scoped-state/schema.js +43 -0
  215. package/dist/core/scoped-state/settings.d.ts +28 -0
  216. package/dist/core/scoped-state/settings.js +83 -0
  217. package/dist/core/spaces/open-beneath.d.ts +71 -0
  218. package/dist/core/spaces/open-beneath.js +581 -0
  219. package/dist/core/sqlite-statements.d.ts +4 -0
  220. package/dist/core/sqlite-statements.js +17 -0
  221. package/dist/core/subscription-state.d.ts +121 -0
  222. package/dist/core/subscription-state.js +287 -0
  223. package/dist/core/user-settings.d.ts +377 -0
  224. package/dist/core/user-settings.js +458 -0
  225. package/dist/daemon/broker-signals/bus.d.ts +30 -0
  226. package/dist/daemon/broker-signals/bus.js +87 -0
  227. package/dist/daemon/manage.d.ts +176 -0
  228. package/dist/daemon/manage.js +664 -0
  229. package/dist/daemon/pidfile.d.ts +8 -0
  230. package/dist/daemon/pidfile.js +37 -0
  231. package/dist/daemon/startup-policy.d.ts +1 -0
  232. package/dist/daemon/startup-policy.js +1 -0
  233. package/dist/native/linux.d.ts +29 -0
  234. package/dist/native/linux.js +20 -0
  235. package/dist/shared/env.d.ts +116 -0
  236. package/dist/shared/env.js +271 -0
  237. package/dist/shared/generated-context.d.ts +83 -0
  238. package/dist/shared/generated-context.js +235 -0
  239. package/dist/shared/inbox-entry-body.d.ts +22 -0
  240. package/dist/shared/inbox-entry-body.js +116 -0
  241. package/dist/shared/predicates.d.ts +2 -0
  242. package/dist/shared/predicates.js +4 -0
  243. package/dist/shared/working-activity.d.ts +9 -0
  244. package/dist/shared/working-activity.js +27 -0
  245. package/dist/types.d.ts +562 -0
  246. package/dist/types.js +186 -0
  247. package/package.json +47 -4
@@ -0,0 +1,197 @@
1
+ // API version + route path builders (spec §4.1). Every route is prefixed `/v1`
2
+ // EXCEPT `GET /healthz`, which stays unversioned so any-version probes (incl.
3
+ // the P2 provisioning ladder) can reach it. A version mismatch surfaces as a
4
+ // 404 — there is no negotiation, no Accept-header versioning, no compat shim.
5
+ //
6
+ // These builders carry NO logic — they are pure string constructors. Every
7
+ // interpolated param (node id, target, cron id, profile name, provider) MUST
8
+ // be a pre-validated single path segment (e.g. `isSafeNodeId`-gated ids;
9
+ // upstream-constrained names) — nothing here parses, encodes, or branches, so a
10
+ // param carrying `/` or `..` would corrupt the path.
11
+ //
12
+ // PURITY (spec §3.1): Node built-ins + `src/api/*` only.
13
+ /** The single API version constant; every versioned path prepends it. */
14
+ export const API_VERSION = 'v1';
15
+ const V = `/${API_VERSION}`;
16
+ /** Route path builders. Keys mirror the spec §6 route table. */
17
+ export const routes = {
18
+ // Health / status
19
+ healthz: () => '/healthz',
20
+ status: () => `${V}/status`,
21
+ due: () => `${V}/due`,
22
+ daemonRestart: () => `${V}/daemon/restart`,
23
+ daemonMigrate: () => `${V}/daemon/migrate`,
24
+ daemonAdmit: () => `${V}/daemon/admit`,
25
+ // Nodes — collection + item
26
+ nodes: () => `${V}/nodes`,
27
+ reviveAll: () => `${V}/nodes/revive-all`,
28
+ node: (id) => `${V}/nodes/${id}`,
29
+ nodeMemoryReads: (id) => `${V}/nodes/${id}/memory-reads`,
30
+ nodeOutcome: (id) => `${V}/nodes/${id}/outcome`,
31
+ nodeOutcomeDelivery: (id) => `${V}/nodes/${id}/outcome-delivery`,
32
+ nodeEvents: (id) => `${V}/nodes/${id}/events`,
33
+ // Node reads
34
+ nodeSnapshot: (id) => `${V}/nodes/${id}/snapshot`,
35
+ nodeSubject: (id) => `${V}/nodes/${id}/subject`,
36
+ nodeSession: (id) => `${V}/nodes/${id}/session`,
37
+ nodeChatInventory: (id) => `${V}/nodes/${id}/chat-inventory`,
38
+ prospectiveChatInventory: () => `${V}/prospective-chat-inventory`,
39
+ nodeTranscript: (id) => `${V}/nodes/${id}/transcript`,
40
+ nodeContext: (id) => `${V}/nodes/${id}/context`,
41
+ nodeReports: (id) => `${V}/nodes/${id}/reports`,
42
+ nodeResult: (id) => `${V}/nodes/${id}/result`,
43
+ nodeJobs: (id) => `${V}/nodes/${id}/jobs`,
44
+ nodeJob: (id, jobId) => `${V}/nodes/${id}/jobs/${jobId}`,
45
+ nodeJobBackground: (id, jobId) => `${V}/nodes/${id}/jobs/${jobId}/background`,
46
+ nodeDelivery: (id) => `${V}/nodes/${id}/delivery`,
47
+ deliveryDryRun: () => `${V}/delivery/dry-run`,
48
+ deliveryRetry: (id) => `${V}/deliveries/${encodeURIComponent(id)}/retry`,
49
+ // Node messages / feed
50
+ nodeMessages: (id) => `${V}/nodes/${id}/messages`,
51
+ nodeInterrupt: (id) => `${V}/nodes/${id}/interrupt`,
52
+ // Node lifecycle actions
53
+ nodeFork: (id) => `${V}/nodes/${id}/fork`,
54
+ nodeRevive: (id) => `${V}/nodes/${id}/revive`,
55
+ nodeRelaunchRoot: (id) => `${V}/nodes/${id}/relaunch-root`,
56
+ nodeBrokerSessionBound: (id) => `${V}/nodes/${id}/broker/session-bound`,
57
+ nodeBrokerSettle: (id) => `${V}/nodes/${id}/broker/settle`,
58
+ nodeBrokerParkComplete: (id) => `${V}/nodes/${id}/broker/park-complete`,
59
+ nodeBrokerParkActivity: (id) => `${V}/nodes/${id}/broker/park-activity`,
60
+ nodeBrokerTelemetry: (id) => `${V}/nodes/${id}/broker/telemetry`,
61
+ nodeLog: (id) => `${V}/nodes/${id}/records/log`,
62
+ nodeTelemetry: (id) => `${V}/nodes/${id}/records/telemetry`,
63
+ nodeRecap: (id) => `${V}/nodes/${id}/records/recap`,
64
+ nodeInboxReport: (id) => `${V}/nodes/${id}/records/inbox`,
65
+ nodePassiveMessage: (id) => `${V}/nodes/${id}/records/passive`,
66
+ nodePushedFinal: (id) => `${V}/nodes/${id}/records/pushed-final`,
67
+ nodeMailClaim: (id) => `${V}/nodes/${id}/mail/claim`,
68
+ nodeMailAcknowledge: (id) => `${V}/nodes/${id}/mail/acknowledge`,
69
+ nodeBrokerModel: (id) => `${V}/nodes/${id}/broker/model`,
70
+ nodeBrokerExtensionState: (id) => `${V}/nodes/${id}/broker/extension-state`,
71
+ nodeBrokerSignals: (id) => `${V}/nodes/${id}/broker/signals`,
72
+ nodeBrokerGeneratedName: (id) => `${V}/nodes/${id}/broker/generated-name`,
73
+ nodeBrokerPersonaAck: (id) => `${V}/nodes/${id}/broker/persona-ack`,
74
+ brokerTurn: (id) => `${V}/broker/${id}/turn`,
75
+ brokerProviderRetry: (id) => `${V}/broker/${id}/provider-retry`,
76
+ brokerFault: (id) => `${V}/broker/${id}/fault`,
77
+ brokerRecovery: (id) => `${V}/broker/${id}/recovery`,
78
+ nodeFault: (id) => `${V}/nodes/${id}/fault`,
79
+ nodeClose: (id) => `${V}/nodes/${id}/close`,
80
+ nodeRecycle: (id) => `${V}/nodes/${id}/recycle`,
81
+ nodeDemote: (id) => `${V}/nodes/${id}/demote`,
82
+ nodePromote: (id) => `${V}/nodes/${id}/promote`,
83
+ nodeYield: (id) => `${V}/nodes/${id}/yield`,
84
+ nodeWait: (id) => `${V}/nodes/${id}/wait`,
85
+ nodeConfig: (id) => `${V}/nodes/${id}/config`,
86
+ kinds: () => `${V}/kinds`,
87
+ nodeWorktreeClose: (id) => `${V}/nodes/${id}/worktree/close`,
88
+ nodeWorktreeAbandon: (id) => `${V}/nodes/${id}/worktree/abandon`,
89
+ quarantinedWorktrees: () => `${V}/worktrees/quarantined`,
90
+ spaces: () => `${V}/spaces`,
91
+ limits: () => `${V}/limits`,
92
+ limitsApp: (app) => `${V}/limits/apps/${encodeURIComponent(app)}`,
93
+ nodeAttach: (id) => `${V}/nodes/${id}/attach`,
94
+ // Focuses (viewer registry — the canvas.db `focuses` table)
95
+ focuses: () => `${V}/focuses`,
96
+ focusByNode: () => `${V}/focuses/by-node`,
97
+ focusByPane: () => `${V}/focuses/by-pane`,
98
+ focus: (focusId) => `${V}/focuses/${focusId}`,
99
+ // Subscriptions
100
+ nodeSubscriptions: (id) => `${V}/nodes/${id}/subscriptions`,
101
+ nodeSubscription: (id, target) => `${V}/nodes/${id}/subscriptions/${target}`,
102
+ // Crons (the cron-spec scheduler)
103
+ crons: () => `${V}/crons`,
104
+ cron: (cronId) => `${V}/crons/${cronId}`,
105
+ cronPause: (cronId) => `${V}/crons/${cronId}/pause`,
106
+ cronResume: (cronId) => `${V}/crons/${cronId}/resume`,
107
+ cronRun: (cronId) => `${V}/crons/${cronId}/run`,
108
+ cronsPoke: () => `${V}/crons/poke`,
109
+ // Canvas maintenance / reads
110
+ canvasAttention: () => `${V}/canvas/attention`,
111
+ canvasAttentionCounts: () => `${V}/canvas/attention/counts`,
112
+ canvasHistorySearch: () => `${V}/canvas/history/search`,
113
+ canvasHistoryGrep: () => `${V}/canvas/history/grep`,
114
+ canvasHistoryRead: () => `${V}/canvas/history/read`,
115
+ canvasHistoryStats: () => `${V}/canvas/history/stats`,
116
+ canvasSnapshot: () => `${V}/canvas/snapshot`,
117
+ canvasRoster: () => `${V}/canvas/roster`,
118
+ canvasBrowse: () => `${V}/canvas/browse`,
119
+ canvasAnalytics: () => `${V}/canvas/analytics`,
120
+ canvasGraph: () => `${V}/canvas/graph`,
121
+ canvasPrune: () => `${V}/canvas/prune`,
122
+ // Daemon-owned document reviews and comments. All interpolated ids are
123
+ // guarded by `CrtrClient` before they reach these pure builders.
124
+ humanReviews: () => `${V}/human/reviews`,
125
+ humanReview: (reviewId) => `${V}/human/reviews/${reviewId}`,
126
+ humanReviewSubmit: (reviewId) => `${V}/human/reviews/${reviewId}/submit`,
127
+ humanReviewCancel: (reviewId) => `${V}/human/reviews/${reviewId}/cancel`,
128
+ humanReviewDocument: (reviewId) => `${V}/human/reviews/${reviewId}/document`,
129
+ humanReviewComments: (reviewId) => `${V}/human/reviews/${reviewId}/comments`,
130
+ humanReviewCommentEvents: (reviewId) => `${V}/human/reviews/${reviewId}/comment-events`,
131
+ humanReviewCommentRanges: (reviewId) => `${V}/human/reviews/${reviewId}/comment-ranges`,
132
+ humanComment: (commentId) => `${V}/human/comments/${commentId}`,
133
+ humanCommentEdit: (commentId) => `${V}/human/comments/${commentId}/edit`,
134
+ humanCommentResolve: (commentId) => `${V}/human/comments/${commentId}/resolve`,
135
+ humanCommentReopen: (commentId) => `${V}/human/comments/${commentId}/reopen`,
136
+ humanCommentDelete: (commentId) => `${V}/human/comments/${commentId}/delete`,
137
+ humanCommentFork: (commentId) => `${V}/human/comments/${commentId}/fork`,
138
+ // Humanloop inbox (crouter cloud crouter-inbox v1, inbox-contract.md §A)
139
+ humanInbox: () => `${V}/human/inbox`,
140
+ humanInboxTicket: (ticketId) => `${V}/human/inbox/${ticketId}`,
141
+ humanInboxRespond: (ticketId) => `${V}/human/inbox/${ticketId}/respond`,
142
+ humanInboxProgress: (ticketId) => `${V}/human/inbox/${ticketId}/progress`,
143
+ humanInboxResponse: (ticketId) => `${V}/human/inbox/${ticketId}/response`,
144
+ humanInboxCancel: (ticketId) => `${V}/human/inbox/${ticketId}/cancel`,
145
+ humanInboxFeedbackResolve: (ticketId, commentId) => `${V}/human/inbox/${ticketId}/feedback-comments/${commentId}/resolve`,
146
+ // Durable programmatic human requests. A request id is separate from the
147
+ // opaque inbox ticket id used by `/v1/human/inbox`.
148
+ humanRequests: () => `${V}/human/requests`,
149
+ humanComponents: () => `${V}/human/components`,
150
+ humanRequest: (requestId) => `${V}/human/requests/${requestId}`,
151
+ humanRequestReplace: (requestId) => `${V}/human/requests/${requestId}/replace`,
152
+ humanRequestRespond: (requestId) => `${V}/human/requests/${requestId}/respond`,
153
+ humanRequestDismiss: (requestId) => `${V}/human/requests/${requestId}/dismiss`,
154
+ humanRequestCancel: (requestId) => `${V}/human/requests/${requestId}/cancel`,
155
+ // Profiles (deletion is daemon-owned because it crosses canvas state)
156
+ profiles: () => `${V}/profiles`,
157
+ profile: (name) => `${V}/profiles/${name}`,
158
+ profilePause: (name) => `${V}/profiles/${name}/pause`,
159
+ profileResume: (name) => `${V}/profiles/${name}/resume`,
160
+ profileMetadata: (name) => `${V}/profiles/${name}/metadata`,
161
+ appEnv: () => `${V}/apps/self/env`,
162
+ appEnvVar: (variable) => `${V}/apps/self/env/${encodeURIComponent(variable)}`,
163
+ profileEnv: (name) => `${V}/profiles/${name}/env`,
164
+ profileEnvVar: (name, variable) => `${V}/profiles/${name}/env/${variable}`,
165
+ // Model auth
166
+ modelAuths: () => `${V}/model-auth`,
167
+ modelAuthReadiness: () => `${V}/model-auth/readiness`,
168
+ modelAuth: (provider) => `${V}/model-auth/${provider}`,
169
+ modelAuthFlows: () => `${V}/model-auth/flows`,
170
+ modelAuthFlow: (flowId) => `${V}/model-auth/flows/${encodeURIComponent(flowId)}`,
171
+ modelAuthFlowComplete: (flowId) => `${V}/model-auth/flows/${encodeURIComponent(flowId)}/complete`,
172
+ modelAuthFlowCancel: (flowId) => `${V}/model-auth/flows/${encodeURIComponent(flowId)}/cancel`,
173
+ // Host file read (browser file-peek panel). The absolute path rides as a
174
+ // `path` query param, not a path segment — it is not a single safe segment.
175
+ filePeek: () => `${V}/files/peek`,
176
+ fileWrite: () => `${V}/files/write`,
177
+ fileList: () => `${V}/files/list`,
178
+ bash: () => `${V}/bash`,
179
+ // Canvas objects and documents. A ref (name or id) may contain `/`, so it is
180
+ // always one percent-encoded path segment.
181
+ objects: () => `${V}/objects`,
182
+ object: (ref) => `${V}/objects/${encodeURIComponent(ref)}`,
183
+ objectsSearch: () => `${V}/objects/search`,
184
+ objectWatch: (ref) => `${V}/objects/${encodeURIComponent(ref)}/watch`,
185
+ customObjects: () => `${V}/custom-objects`,
186
+ customObject: (ref) => `${V}/custom-objects/${encodeURIComponent(ref)}`,
187
+ customObjectEvents: (ref) => `${V}/custom-objects/${encodeURIComponent(ref)}/events`,
188
+ customObjectDeliveries: (ref) => `${V}/custom-objects/${encodeURIComponent(ref)}/deliveries`,
189
+ customObjectAck: (ref) => `${V}/custom-objects/${encodeURIComponent(ref)}/deliveries/ack`,
190
+ objectEdges: (ref) => `${V}/objects/${encodeURIComponent(ref)}/edges`,
191
+ docs: () => `${V}/docs`,
192
+ doc: (ref) => `${V}/docs/${encodeURIComponent(ref)}`,
193
+ docMove: (ref) => `${V}/docs/${encodeURIComponent(ref)}/move`,
194
+ docHistory: (ref) => `${V}/docs/${encodeURIComponent(ref)}/history`,
195
+ docsLint: () => `${V}/docs/lint`,
196
+ documentsReconcilePackages: () => `${V}/documents/reconcile-packages`,
197
+ };
@@ -0,0 +1,31 @@
1
+ /** Response header carrying the runtime's release version, e.g. `0.3.389`. */
2
+ export declare const RUNTIME_VERSION_HEADER = "Crouter-Runtime-Version";
3
+ /** Compares two `major.minor.patch` versions numerically (a prerelease or build
4
+ * suffix is ignored). Negative when `a` is older, zero when equal, positive when
5
+ * newer. A version that does not parse sorts below every version that does. */
6
+ export declare function compareReleaseVersions(a: string, b: string): number;
7
+ /** Request header naming the oldest runtime release the caller accepts, e.g. `0.3.396`. The SDK sends its own
8
+ * version on every request except `/healthz`. Optional: absent means no check. The router in front of a hosted
9
+ * runtime and the runtime itself each refuse a request that names a newer release than the runtime's, before any
10
+ * handler runs (so nothing is accepted and no idempotency key is recorded). */
11
+ export declare const MINIMUM_RUNTIME_VERSION_HEADER = "Crouter-Minimum-Runtime-Version";
12
+ /** The refusal a runtime answers instead of handling a request, or `null` when the request may be handled.
13
+ * - `required` absent: handled.
14
+ * - `required` not `major.minor.patch` (optional `v`, optional suffix): 400 `invalid_request`.
15
+ * - `runtimeVersion` older than `required`, or not a release version: 426 `runtime_version_unsupported`
16
+ * (details `{sdk_version, runtime_version}`, the SDK's `RuntimeVersionDetails`). */
17
+ export type MinimumRuntimeRefusal = {
18
+ status: 400;
19
+ code: 'invalid_request';
20
+ message: string;
21
+ param: string;
22
+ } | {
23
+ status: 426;
24
+ code: 'runtime_version_unsupported';
25
+ message: string;
26
+ details: {
27
+ sdk_version: string;
28
+ runtime_version: string | null;
29
+ };
30
+ };
31
+ export declare function minimumRuntimeRefusal(required: string | undefined, runtimeVersion: string | null): MinimumRuntimeRefusal | null;
@@ -0,0 +1,50 @@
1
+ // The runtime's release version on every response (P2: latest only).
2
+ //
3
+ // The runtime sends its package version (`crouter` `package.json`) in this
4
+ // header on every `/v1` answer of the owner API and the app listener. A client
5
+ // built from the same release set compares it with its own version and refuses
6
+ // an older runtime: there is no capability negotiation, so a client's types are
7
+ // true only for a runtime at or above its own release.
8
+ //
9
+ // PURITY (spec §3.1): Node built-ins + `src/api/*` only.
10
+ /** Response header carrying the runtime's release version, e.g. `0.3.389`. */
11
+ export const RUNTIME_VERSION_HEADER = 'Crouter-Runtime-Version';
12
+ /** Compares two `major.minor.patch` versions numerically (a prerelease or build
13
+ * suffix is ignored). Negative when `a` is older, zero when equal, positive when
14
+ * newer. A version that does not parse sorts below every version that does. */
15
+ export function compareReleaseVersions(a, b) {
16
+ const left = parseRelease(a);
17
+ const right = parseRelease(b);
18
+ if (left === null || right === null)
19
+ return left === right ? 0 : left === null ? -1 : 1;
20
+ for (let i = 0; i < 3; i++)
21
+ if (left[i] !== right[i])
22
+ return left[i] - right[i];
23
+ return 0;
24
+ }
25
+ function parseRelease(version) {
26
+ const match = /^v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/.exec(version.trim());
27
+ return match === null ? null : [Number(match[1]), Number(match[2]), Number(match[3])];
28
+ }
29
+ /** Request header naming the oldest runtime release the caller accepts, e.g. `0.3.396`. The SDK sends its own
30
+ * version on every request except `/healthz`. Optional: absent means no check. The router in front of a hosted
31
+ * runtime and the runtime itself each refuse a request that names a newer release than the runtime's, before any
32
+ * handler runs (so nothing is accepted and no idempotency key is recorded). */
33
+ export const MINIMUM_RUNTIME_VERSION_HEADER = 'Crouter-Minimum-Runtime-Version';
34
+ export function minimumRuntimeRefusal(required, runtimeVersion) {
35
+ if (required === undefined)
36
+ return null;
37
+ if (parseRelease(required) === null) {
38
+ return { status: 400, code: 'invalid_request', param: MINIMUM_RUNTIME_VERSION_HEADER, message: `${MINIMUM_RUNTIME_VERSION_HEADER} must be a release version like 0.3.396.` };
39
+ }
40
+ if (runtimeVersion !== null && parseRelease(runtimeVersion) !== null && compareReleaseVersions(runtimeVersion, required) >= 0)
41
+ return null;
42
+ const sdkVersion = required.trim();
43
+ return {
44
+ status: 426, code: 'runtime_version_unsupported',
45
+ message: runtimeVersion === null
46
+ ? `The runtime did not name its version; the caller requires a runtime at ${sdkVersion} or later. Roll the runtime forward.`
47
+ : `The runtime is ${runtimeVersion}; the caller requires a runtime at ${sdkVersion} or later. Roll the runtime forward.`,
48
+ details: { sdk_version: sdkVersion, runtime_version: runtimeVersion },
49
+ };
50
+ }
@@ -0,0 +1,7 @@
1
+ /** The source or runtime directory containing every shipped asset tree.
2
+ *
3
+ * `import.meta.url` names the module when loaded directly, but names the
4
+ * attach viewer bundle when esbuild inlines this module. Walking to the
5
+ * directory marked by the built-in assets preserves one resolution rule for
6
+ * both loading contexts. */
7
+ export declare function assetRoot(): string;
@@ -0,0 +1,18 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ /** The source or runtime directory containing every shipped asset tree.
5
+ *
6
+ * `import.meta.url` names the module when loaded directly, but names the
7
+ * attach viewer bundle when esbuild inlines this module. Walking to the
8
+ * directory marked by the built-in assets preserves one resolution rule for
9
+ * both loading contexts. */
10
+ export function assetRoot() {
11
+ for (let current = dirname(fileURLToPath(import.meta.url));; current = dirname(current)) {
12
+ if (existsSync(join(current, 'builtin-memory')) && existsSync(join(current, 'builtin-pi-packages')))
13
+ return current;
14
+ const parent = dirname(current);
15
+ if (parent === current)
16
+ throw new Error(`cannot locate crouter assets from ${import.meta.url}`);
17
+ }
18
+ }
@@ -0,0 +1,6 @@
1
+ /** The Linux kernel's per-boot identity at `/proc/sys/kernel/random/boot_id` —
2
+ * a fresh random UUID minted on every kernel boot, or null when unreadable
3
+ * (non-Linux dev, or the file is absent). Exact when present: a microVM
4
+ * recreate boots a new kernel (new id); a plain daemon restart within the same
5
+ * boot leaves it identical. */
6
+ export declare function readKernelBootId(): string | null;
@@ -0,0 +1,26 @@
1
+ // boot-id.ts — the PURE kernel-boot-identity reader, split out of boot.ts so the
2
+ // low-level liveness probe (`canvas/pid.ts`) can import it WITHOUT dragging in
3
+ // boot.ts's `openDb` persistence machinery. boot.ts's boot-change reconciler
4
+ // persists the last-seen identity in canvas.db (hence its top-level `openDb`
5
+ // import); `readKernelBootId` itself is a plain `/proc` read with zero db reach,
6
+ // and `pid.ts` needs only that. Keeping it here means `pid.ts` (and everything
7
+ // that imports it down — including the CLI's daemon-liveness front door) stays
8
+ // canvas.db-free.
9
+ //
10
+ // PURITY: Node built-ins only. Never `core/canvas/db.js` or any db-touching
11
+ // module.
12
+ import { readFileSync } from 'node:fs';
13
+ /** The Linux kernel's per-boot identity at `/proc/sys/kernel/random/boot_id` —
14
+ * a fresh random UUID minted on every kernel boot, or null when unreadable
15
+ * (non-Linux dev, or the file is absent). Exact when present: a microVM
16
+ * recreate boots a new kernel (new id); a plain daemon restart within the same
17
+ * boot leaves it identical. */
18
+ export function readKernelBootId() {
19
+ try {
20
+ const id = readFileSync('/proc/sys/kernel/random/boot_id', 'utf8').trim();
21
+ return id.length > 0 ? id : null;
22
+ }
23
+ catch {
24
+ return null; // non-Linux, or unreadable — fall back to the wall-clock epoch
25
+ }
26
+ }
@@ -0,0 +1,72 @@
1
+ /** Runtime-owned records; never a bind source for an app sandbox. */
2
+ export declare function crtrHome(): string;
3
+ /** The canvas home requires durable storage with SQLite WAL locking support. */
4
+ export declare function canvasDbPath(): string;
5
+ export declare function onDiskMigrationLockPath(): string;
6
+ export declare function onDiskMigrationMarkerPath(): string;
7
+ /** The viewer socket belongs to the per-node run folder, not the durable records. */
8
+ export declare function viewSocketPath(nodeId: string): string;
9
+ export declare function nodesRoot(): string;
10
+ export declare function reviewsRoot(): string;
11
+ export declare function reviewDir(reviewId: string): string;
12
+ export declare function reviewResultPath(reviewId: string): string;
13
+ export declare function ensureReviewDir(reviewId: string): void;
14
+ /** Absolute path to crtrd's API listener unix socket, a sibling of
15
+ * `canvasDbPath()` under the canvas home (`~/.crouter/canvas/crtrd.sock` unless
16
+ * `CRTR_HOME` is set). Pure path builder — no db reach — so both the daemon
17
+ * (which binds it) and a CLI-side resolver can import it freely. `src/api`'s
18
+ * `defaultSocketPath()` (in `api/node-transport.ts`) deliberately re-derives
19
+ * this same path from a Node-builtin `join(crtrHome-equivalent, 'crtrd.sock')`
20
+ * rather than importing here, to keep the exported `/api` contract
21
+ * dependency-light; the two must agree. */
22
+ export declare function apiSocketPath(): string;
23
+ /** Global, cwd-agnostic broker cache of per-session picker metadata, keyed by
24
+ * absolute `.jsonl` path. Shared across every node's broker (paths are absolute)
25
+ * and survives broker restarts so the first `/resume` open after a restart is
26
+ * also fast. Machine-local performance state — safe to delete; rebuilt on demand. */
27
+ export declare function sessionListCachePath(): string;
28
+ /** Whether a persisted node ID is safe as a single filesystem path segment. */
29
+ export declare function isSafeNodeId(nodeId: string): boolean;
30
+ export declare function nodeRecordDir(nodeId: string): string;
31
+ /** Existing callers use nodeDir for the daemon's records only. */
32
+ export declare const nodeDir: typeof nodeRecordDir;
33
+ /** Pure path builder. A broker may resolve its own app from its launch identity;
34
+ * every other caller must supply the app from the daemon's node row. */
35
+ export declare function nodeAgentDir(nodeId: string, grantee?: string): string;
36
+ /** The home folder of one node of an isolated run, `/apps/<A>/nodes/<id>/home`, used
37
+ * in place of the app's shared `home` so two isolated runs share no writable folder. */
38
+ export declare function isolatedNodeHome(nodeId: string, grantee: string): string;
39
+ /** The HOME a node's processes get: its own home in an isolated run, else the app's. */
40
+ export declare function nodeHome(nodeId: string, grantee: string, isolated: boolean): string;
41
+ export declare function nodeRunDir(nodeId: string): string;
42
+ export declare function contextDir(nodeId: string, grantee?: string): string;
43
+ /** Replace the literal env-var tokens `$CRTR_CONTEXT_DIR` / `$CRTR_NODE_ID` in
44
+ * agent-facing doc text with the node's real values, so a doc surfaced INTO a
45
+ * node hands it a concrete absolute path instead of a shell-only string. The
46
+ * env vars only expand in bash; an agent that pastes `$CRTR_CONTEXT_DIR/x.md`
47
+ * into the (non-expanding) Write tool creates a literal `$CRTR_CONTEXT_DIR/`
48
+ * dir under the project repo. The resolved absolute path works in both bash and
49
+ * Write, so this substitution is strictly safer. Callers must only apply it
50
+ * when they have a concrete node to render for (never on generic, node-less
51
+ * output). Pure string work — no I/O. */
52
+ export declare function interpolateNodePaths(text: string, nodeId: string, grantee?: string): string;
53
+ export declare function jobDir(nodeId: string): string;
54
+ export declare function nodeRunJobDir(nodeId: string): string;
55
+ /** Overflow store for inbox entries whose inline body exceeds the digest's
56
+ * bounded preview (a direct `node message send` or a close/cancel doctrine
57
+ * wake) — the full body spills here, mirroring how a
58
+ * push's body always lives in a file, never inline; the inbox entry then
59
+ * carries `ref` at this path, same as a push pointer. */
60
+ export declare function messagesDir(nodeId: string): string;
61
+ export declare function nodeMetaPath(nodeId: string): string;
62
+ export declare function inboxPath(nodeId: string): string;
63
+ /** Rendered ambient context lives outside the user-authored context directory. */
64
+ export declare function situationalContextPath(nodeId: string): string;
65
+ export declare function passivePath(nodeId: string): string;
66
+ export declare function transcriptPath(nodeId: string): string;
67
+ export declare function sessionPtrPath(nodeId: string): string;
68
+ /** Create the full directory skeleton for a node. Idempotent. The agent and
69
+ * run folders are app-writable, so their subfolders are made beneath them. */
70
+ export declare function ensureNodeDirs(nodeId: string, grantee: string): void;
71
+ /** Ensure the canvas home exists. Idempotent. */
72
+ export declare function ensureHome(): void;
@@ -0,0 +1,163 @@
1
+ import { join } from 'node:path';
2
+ import { mkdirSync } from 'node:fs';
3
+ import { appSpace, runtimeRoot, socketDir } from '../layout.js';
4
+ import { envGrantee, envNodeId } from '../../shared/env.js';
5
+ import { withDirBeneath } from '../spaces/open-beneath.js';
6
+ /** Runtime-owned records; never a bind source for an app sandbox. */
7
+ export function crtrHome() { return runtimeRoot(); }
8
+ /** The canvas home requires durable storage with SQLite WAL locking support. */
9
+ export function canvasDbPath() {
10
+ return join(crtrHome(), 'canvas.db');
11
+ }
12
+ export function onDiskMigrationLockPath() {
13
+ return join(crtrHome(), 'on-disk-migration.lock');
14
+ }
15
+ export function onDiskMigrationMarkerPath() {
16
+ return join(crtrHome(), 'on-disk-migrations.json');
17
+ }
18
+ /** The viewer socket belongs to the per-node run folder, not the durable records. */
19
+ export function viewSocketPath(nodeId) {
20
+ return join(nodeRunDir(nodeId), 'view.sock');
21
+ }
22
+ export function nodesRoot() {
23
+ return join(crtrHome(), 'nodes');
24
+ }
25
+ export function reviewsRoot() {
26
+ return join(crtrHome(), 'reviews');
27
+ }
28
+ export function reviewDir(reviewId) {
29
+ assertSafeNodeId(reviewId);
30
+ return join(reviewsRoot(), reviewId);
31
+ }
32
+ export function reviewResultPath(reviewId) {
33
+ return join(reviewDir(reviewId), 'result.json');
34
+ }
35
+ export function ensureReviewDir(reviewId) {
36
+ mkdirSync(reviewDir(reviewId), { recursive: true, mode: 0o700 });
37
+ }
38
+ /** Absolute path to crtrd's API listener unix socket, a sibling of
39
+ * `canvasDbPath()` under the canvas home (`~/.crouter/canvas/crtrd.sock` unless
40
+ * `CRTR_HOME` is set). Pure path builder — no db reach — so both the daemon
41
+ * (which binds it) and a CLI-side resolver can import it freely. `src/api`'s
42
+ * `defaultSocketPath()` (in `api/node-transport.ts`) deliberately re-derives
43
+ * this same path from a Node-builtin `join(crtrHome-equivalent, 'crtrd.sock')`
44
+ * rather than importing here, to keep the exported `/api` contract
45
+ * dependency-light; the two must agree. */
46
+ export function apiSocketPath() {
47
+ return join(socketDir(), 'api.sock');
48
+ }
49
+ /** Global, cwd-agnostic broker cache of per-session picker metadata, keyed by
50
+ * absolute `.jsonl` path. Shared across every node's broker (paths are absolute)
51
+ * and survives broker restarts so the first `/resume` open after a restart is
52
+ * also fast. Machine-local performance state — safe to delete; rebuilt on demand. */
53
+ export function sessionListCachePath() {
54
+ return join(crtrHome(), 'session-list-cache.json');
55
+ }
56
+ const MAX_NODE_ID_BYTES = 128;
57
+ /** Whether a persisted node ID is safe as a single filesystem path segment. */
58
+ export function isSafeNodeId(nodeId) {
59
+ return typeof nodeId === 'string'
60
+ && nodeId !== ''
61
+ && nodeId !== '.'
62
+ && nodeId !== '..'
63
+ && !/[\\/\0]/u.test(nodeId)
64
+ && Buffer.byteLength(nodeId, 'utf8') <= MAX_NODE_ID_BYTES;
65
+ }
66
+ function assertSafeNodeId(nodeId) {
67
+ if (!isSafeNodeId(nodeId)) {
68
+ throw new TypeError('node_id must be a non-empty path segment of at most 128 UTF-8 bytes');
69
+ }
70
+ }
71
+ export function nodeRecordDir(nodeId) {
72
+ assertSafeNodeId(nodeId);
73
+ return join(nodesRoot(), nodeId);
74
+ }
75
+ /** Existing callers use nodeDir for the daemon's records only. */
76
+ export const nodeDir = nodeRecordDir;
77
+ /** Pure path builder. A broker may resolve its own app from its launch identity;
78
+ * every other caller must supply the app from the daemon's node row. */
79
+ export function nodeAgentDir(nodeId, grantee) {
80
+ assertSafeNodeId(nodeId);
81
+ const app = grantee ?? (envNodeId() === nodeId ? envGrantee() : undefined);
82
+ if (!app)
83
+ throw new Error(`node ${nodeId} has no app assignment`);
84
+ return join(appSpace(app).nodes, nodeId);
85
+ }
86
+ /** The home folder of one node of an isolated run, `/apps/<A>/nodes/<id>/home`, used
87
+ * in place of the app's shared `home` so two isolated runs share no writable folder. */
88
+ export function isolatedNodeHome(nodeId, grantee) {
89
+ return join(nodeAgentDir(nodeId, grantee), 'home');
90
+ }
91
+ /** The HOME a node's processes get: its own home in an isolated run, else the app's. */
92
+ export function nodeHome(nodeId, grantee, isolated) {
93
+ return isolated ? isolatedNodeHome(nodeId, grantee) : appSpace(grantee).home;
94
+ }
95
+ export function nodeRunDir(nodeId) {
96
+ assertSafeNodeId(nodeId);
97
+ return process.platform === 'linux' ? join(socketDir(), 'nodes', nodeId) : join(nodeRecordDir(nodeId), 'run');
98
+ }
99
+ export function contextDir(nodeId, grantee) {
100
+ return join(nodeAgentDir(nodeId, grantee), 'context');
101
+ }
102
+ /** Replace the literal env-var tokens `$CRTR_CONTEXT_DIR` / `$CRTR_NODE_ID` in
103
+ * agent-facing doc text with the node's real values, so a doc surfaced INTO a
104
+ * node hands it a concrete absolute path instead of a shell-only string. The
105
+ * env vars only expand in bash; an agent that pastes `$CRTR_CONTEXT_DIR/x.md`
106
+ * into the (non-expanding) Write tool creates a literal `$CRTR_CONTEXT_DIR/`
107
+ * dir under the project repo. The resolved absolute path works in both bash and
108
+ * Write, so this substitution is strictly safer. Callers must only apply it
109
+ * when they have a concrete node to render for (never on generic, node-less
110
+ * output). Pure string work — no I/O. */
111
+ export function interpolateNodePaths(text, nodeId, grantee) {
112
+ return text
113
+ .replaceAll('$CRTR_CONTEXT_DIR', contextDir(nodeId, grantee))
114
+ .replaceAll('$CRTR_NODE_ID', nodeId);
115
+ }
116
+ export function jobDir(nodeId) {
117
+ return join(nodeRecordDir(nodeId), 'job');
118
+ }
119
+ export function nodeRunJobDir(nodeId) {
120
+ return join(nodeRunDir(nodeId), 'job');
121
+ }
122
+ /** Overflow store for inbox entries whose inline body exceeds the digest's
123
+ * bounded preview (a direct `node message send` or a close/cancel doctrine
124
+ * wake) — the full body spills here, mirroring how a
125
+ * push's body always lives in a file, never inline; the inbox entry then
126
+ * carries `ref` at this path, same as a push pointer. */
127
+ export function messagesDir(nodeId) {
128
+ return join(nodeDir(nodeId), 'messages');
129
+ }
130
+ export function nodeMetaPath(nodeId) {
131
+ return join(nodeDir(nodeId), 'meta.json');
132
+ }
133
+ export function inboxPath(nodeId) {
134
+ return join(nodeDir(nodeId), 'inbox.jsonl');
135
+ }
136
+ /** Rendered ambient context lives outside the user-authored context directory. */
137
+ export function situationalContextPath(nodeId) {
138
+ return join(nodeRunDir(nodeId), 'situational-context.md');
139
+ }
140
+ export function passivePath(nodeId) {
141
+ return join(nodeDir(nodeId), 'passive.jsonl');
142
+ }
143
+ export function transcriptPath(nodeId) {
144
+ return join(nodeDir(nodeId), 'transcript.jsonl');
145
+ }
146
+ export function sessionPtrPath(nodeId) {
147
+ return join(nodeDir(nodeId), 'session.ptr');
148
+ }
149
+ /** Create the full directory skeleton for a node. Idempotent. The agent and
150
+ * run folders are app-writable, so their subfolders are made beneath them. */
151
+ export function ensureNodeDirs(nodeId, grantee) {
152
+ const agent = nodeAgentDir(nodeId, grantee);
153
+ mkdirSync(nodeRecordDir(nodeId), { recursive: true });
154
+ mkdirSync(jobDir(nodeId), { recursive: true });
155
+ for (const d of ['context', 'reports', 'attachments', 'worktree']) {
156
+ withDirBeneath(appSpace(grantee).nodes, join(agent, d), () => undefined, true);
157
+ }
158
+ withDirBeneath(nodeRunDir(nodeId), nodeRunJobDir(nodeId), () => undefined, true);
159
+ }
160
+ /** Ensure the canvas home exists. Idempotent. */
161
+ export function ensureHome() {
162
+ mkdirSync(nodesRoot(), { recursive: true });
163
+ }