@crouton-kit/crouter 0.3.17 → 0.3.19

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 (283) hide show
  1. package/dist/build-root.js +6 -0
  2. package/dist/builtin-memory/crouter-development/marketplaces.md +164 -0
  3. package/dist/builtin-memory/crouter-development/personas/base-prompt.md +58 -0
  4. package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +60 -0
  5. package/dist/builtin-memory/crouter-development/personas.md +117 -0
  6. package/dist/builtin-memory/crouter-development/plugins.md +163 -0
  7. package/dist/builtin-memory/design.md +60 -0
  8. package/dist/builtin-memory/development.md +116 -0
  9. package/dist/builtin-memory/planning.md +66 -0
  10. package/dist/builtin-memory/spec.md +99 -0
  11. package/dist/builtin-personas/design/{base.md → PERSONA.md} +4 -0
  12. package/dist/builtin-personas/design/orchestrator.md +1 -1
  13. package/dist/builtin-personas/developer/{base.md → PERSONA.md} +4 -0
  14. package/dist/builtin-personas/developer/orchestrator.md +1 -1
  15. package/dist/builtin-personas/explore/{base.md → PERSONA.md} +4 -0
  16. package/dist/builtin-personas/general/{base.md → PERSONA.md} +4 -0
  17. package/dist/builtin-personas/lifecycle/resident.md +1 -1
  18. package/dist/builtin-personas/lifecycle/terminal.md +5 -2
  19. package/dist/builtin-personas/orchestration-kernel.md +15 -24
  20. package/dist/builtin-personas/plan/{base.md → PERSONA.md} +4 -0
  21. package/dist/builtin-personas/plan/orchestrator.md +1 -1
  22. package/dist/builtin-personas/plan/reviewers/architecture-fit/{base.md → PERSONA.md} +1 -1
  23. package/dist/builtin-personas/plan/reviewers/code-smells/{base.md → PERSONA.md} +1 -1
  24. package/dist/builtin-personas/plan/reviewers/pattern-consistency/{base.md → PERSONA.md} +1 -1
  25. package/dist/builtin-personas/plan/reviewers/requirements-coverage/{base.md → PERSONA.md} +1 -1
  26. package/dist/builtin-personas/plan/reviewers/security/{base.md → PERSONA.md} +1 -1
  27. package/dist/builtin-personas/review/{base.md → PERSONA.md} +4 -0
  28. package/dist/builtin-personas/runtime-base.md +6 -3
  29. package/dist/builtin-personas/spec/{base.md → PERSONA.md} +4 -0
  30. package/dist/builtin-personas/spec/orchestrator.md +1 -1
  31. package/dist/builtin-personas/waiting.md +8 -0
  32. package/dist/builtin-skills/skills/crouter-development/personas/SKILL.md +24 -14
  33. package/dist/builtin-skills/skills/crouter-development/personas/base-prompt/SKILL.md +4 -4
  34. package/dist/builtin-skills/skills/crouter-development/personas/orchestrator-prompt/SKILL.md +1 -1
  35. package/dist/builtin-skills/skills/crouter-development/plugins/SKILL.md +2 -2
  36. package/dist/builtin-views/_lib/states.mjs +161 -0
  37. package/dist/builtin-views/canvas/client.mjs +303 -0
  38. package/dist/builtin-views/canvas/view.mjs +576 -0
  39. package/dist/builtin-views/git-pr/client.mjs +440 -0
  40. package/dist/builtin-views/git-pr/view.mjs +675 -0
  41. package/dist/builtin-views/inbox/_lib/render.mjs +177 -0
  42. package/dist/builtin-views/inbox/sources/gmail.mjs +965 -0
  43. package/dist/builtin-views/inbox/sources/linkedin.mjs +427 -0
  44. package/dist/builtin-views/inbox/view.mjs +889 -0
  45. package/dist/builtin-views/linkedin/client.mjs +610 -0
  46. package/dist/builtin-views/linkedin/view.mjs +1171 -0
  47. package/dist/clients/attach/attach-cmd.d.ts +2 -0
  48. package/dist/clients/attach/attach-cmd.js +354 -0
  49. package/dist/clients/attach/chat-view.d.ts +77 -0
  50. package/dist/clients/attach/chat-view.js +450 -0
  51. package/dist/clients/attach/clipboard-image.d.ts +16 -0
  52. package/dist/clients/attach/clipboard-image.js +113 -0
  53. package/dist/clients/attach/config-load.d.ts +31 -0
  54. package/dist/clients/attach/config-load.js +113 -0
  55. package/dist/clients/attach/extension-dialogs.d.ts +29 -0
  56. package/dist/clients/attach/extension-dialogs.js +101 -0
  57. package/dist/clients/attach/input-controller.d.ts +54 -0
  58. package/dist/clients/attach/input-controller.js +204 -0
  59. package/dist/clients/attach/slash-commands.d.ts +36 -0
  60. package/dist/clients/attach/slash-commands.js +200 -0
  61. package/dist/clients/attach/view-socket.d.ts +48 -0
  62. package/dist/clients/attach/view-socket.js +126 -0
  63. package/dist/commands/attention.js +3 -3
  64. package/dist/commands/canvas-prune.js +1 -1
  65. package/dist/commands/daemon.js +4 -3
  66. package/dist/commands/human/prompts.js +4 -10
  67. package/dist/commands/human/queue.js +43 -8
  68. package/dist/commands/human/shared.d.ts +28 -1
  69. package/dist/commands/human/shared.js +48 -10
  70. package/dist/commands/memory/find.d.ts +1 -0
  71. package/dist/commands/memory/find.js +180 -0
  72. package/dist/commands/memory/lint.d.ts +1 -0
  73. package/dist/commands/memory/lint.js +140 -0
  74. package/dist/commands/memory/list.d.ts +1 -0
  75. package/dist/commands/memory/list.js +79 -0
  76. package/dist/commands/memory/read.js +103 -0
  77. package/dist/commands/memory/shared.d.ts +30 -0
  78. package/dist/commands/memory/shared.js +122 -0
  79. package/dist/commands/memory/write.d.ts +1 -0
  80. package/dist/commands/memory/write.js +85 -0
  81. package/dist/commands/memory.d.ts +2 -0
  82. package/dist/commands/memory.js +27 -0
  83. package/dist/commands/node.d.ts +3 -2
  84. package/dist/commands/node.js +660 -65
  85. package/dist/commands/pkg/market-manage.js +1 -1
  86. package/dist/commands/push.js +6 -6
  87. package/dist/commands/revive.js +1 -1
  88. package/dist/commands/skill/author.js +1 -1
  89. package/dist/commands/skill/shared.d.ts +1 -8
  90. package/dist/commands/skill/shared.js +2 -55
  91. package/dist/commands/skill.js +9 -14
  92. package/dist/commands/sys/doctor.js +1 -1
  93. package/dist/commands/sys/update.js +1 -1
  94. package/dist/commands/view-cycle.d.ts +2 -0
  95. package/dist/commands/view-cycle.js +125 -0
  96. package/dist/commands/view-list.d.ts +2 -0
  97. package/dist/commands/view-list.js +66 -0
  98. package/dist/commands/view-new.d.ts +2 -0
  99. package/dist/commands/view-new.js +70 -0
  100. package/dist/commands/view-pick.d.ts +2 -0
  101. package/dist/commands/view-pick.js +119 -0
  102. package/dist/commands/view-run.d.ts +2 -0
  103. package/dist/commands/view-run.js +191 -0
  104. package/dist/commands/view.d.ts +2 -0
  105. package/dist/commands/view.js +29 -0
  106. package/dist/core/__tests__/broker-lifecycle.test.d.ts +1 -0
  107. package/dist/core/__tests__/broker-lifecycle.test.js +677 -0
  108. package/dist/core/__tests__/broker-sdk-wiring.test.d.ts +1 -0
  109. package/dist/core/__tests__/broker-sdk-wiring.test.js +166 -0
  110. package/dist/core/__tests__/cascade-close.test.js +12 -2
  111. package/dist/core/__tests__/child-death-wake.test.d.ts +1 -0
  112. package/dist/core/__tests__/child-death-wake.test.js +245 -0
  113. package/dist/core/__tests__/context-intro.test.js +76 -62
  114. package/dist/core/__tests__/daemon-boot.test.js +14 -5
  115. package/dist/core/__tests__/daemon-liveness.test.js +34 -9
  116. package/dist/core/__tests__/detach-focus.test.d.ts +1 -0
  117. package/dist/core/__tests__/detach-focus.test.js +206 -0
  118. package/dist/core/__tests__/draw-style.test.d.ts +1 -0
  119. package/dist/core/__tests__/draw-style.test.js +258 -0
  120. package/dist/core/__tests__/fixtures/c3-custom-provider-ext.d.ts +2 -0
  121. package/dist/core/__tests__/fixtures/c3-custom-provider-ext.js +18 -0
  122. package/dist/core/__tests__/fixtures/fake-engine.d.ts +138 -0
  123. package/dist/core/__tests__/fixtures/fake-engine.js +614 -0
  124. package/dist/core/__tests__/fixtures/fake-pi-host.js +1 -0
  125. package/dist/core/__tests__/flagship-lifecycle.test.js +7 -1
  126. package/dist/core/__tests__/frame-decoder-perf.test.d.ts +1 -0
  127. package/dist/core/__tests__/frame-decoder-perf.test.js +198 -0
  128. package/dist/core/__tests__/helpers/harness.d.ts +9 -0
  129. package/dist/core/__tests__/helpers/harness.js +111 -1
  130. package/dist/core/__tests__/home-session.test.js +41 -8
  131. package/dist/core/__tests__/human-new-window-regression.test.d.ts +1 -0
  132. package/dist/core/__tests__/human-new-window-regression.test.js +101 -0
  133. package/dist/core/__tests__/human-surface-target.test.d.ts +1 -0
  134. package/dist/core/__tests__/human-surface-target.test.js +98 -0
  135. package/dist/core/__tests__/kickoff.test.js +37 -3
  136. package/dist/core/__tests__/live-mutation.test.js +50 -33
  137. package/dist/core/__tests__/memory.test.js +23 -115
  138. package/dist/core/__tests__/persona-subkind.test.js +18 -15
  139. package/dist/core/__tests__/placement-focus.test.js +5 -0
  140. package/dist/core/__tests__/placement-teardown.test.js +54 -11
  141. package/dist/core/__tests__/relaunch.test.js +4 -3
  142. package/dist/core/__tests__/review-render-pane-regression.test.d.ts +1 -0
  143. package/dist/core/__tests__/review-render-pane-regression.test.js +133 -0
  144. package/dist/core/__tests__/spawn-root.test.js +10 -0
  145. package/dist/core/__tests__/spike-harness.test.js +1 -0
  146. package/dist/core/__tests__/wake-bearings.test.d.ts +1 -0
  147. package/dist/core/__tests__/wake-bearings.test.js +156 -0
  148. package/dist/core/__tests__/wake-origin.test.d.ts +1 -0
  149. package/dist/core/__tests__/wake-origin.test.js +110 -0
  150. package/dist/core/bootstrap.js +1 -1
  151. package/dist/core/canvas/browse/__tests__/render.test.js +1 -0
  152. package/dist/core/canvas/browse/app.js +24 -2
  153. package/dist/core/canvas/browse/model.d.ts +38 -2
  154. package/dist/core/canvas/browse/model.js +134 -10
  155. package/dist/core/canvas/browse/render.d.ts +6 -12
  156. package/dist/core/canvas/browse/render.js +72 -104
  157. package/dist/core/canvas/canvas.js +15 -22
  158. package/dist/core/canvas/db.js +46 -0
  159. package/dist/core/canvas/index.d.ts +1 -0
  160. package/dist/core/canvas/index.js +1 -0
  161. package/dist/core/canvas/paths.d.ts +4 -1
  162. package/dist/core/canvas/paths.js +10 -4
  163. package/dist/core/canvas/pid.d.ts +4 -0
  164. package/dist/core/canvas/pid.js +23 -0
  165. package/dist/core/canvas/render.d.ts +11 -2
  166. package/dist/core/canvas/render.js +69 -0
  167. package/dist/core/canvas/types.d.ts +85 -3
  168. package/dist/core/canvas/types.js +2 -2
  169. package/dist/core/canvas/wakeups.d.ts +76 -0
  170. package/dist/core/canvas/wakeups.js +185 -0
  171. package/dist/core/config.js +4 -1
  172. package/dist/core/frontmatter.js +37 -124
  173. package/dist/core/help.d.ts +6 -0
  174. package/dist/core/help.js +7 -0
  175. package/dist/core/memory-resolver.d.ts +49 -0
  176. package/dist/core/memory-resolver.js +141 -0
  177. package/dist/core/personas/index.d.ts +4 -3
  178. package/dist/core/personas/index.js +3 -2
  179. package/dist/core/personas/loader.d.ts +41 -16
  180. package/dist/core/personas/loader.js +133 -29
  181. package/dist/core/personas/resolve.d.ts +4 -4
  182. package/dist/core/personas/resolve.js +28 -16
  183. package/dist/core/predicate.d.ts +63 -0
  184. package/dist/core/predicate.js +189 -0
  185. package/dist/core/resolver.js +26 -5
  186. package/dist/core/runtime/bearings.d.ts +53 -12
  187. package/dist/core/runtime/bearings.js +132 -59
  188. package/dist/core/runtime/broker-cli.d.ts +1 -0
  189. package/dist/core/runtime/broker-cli.js +46 -0
  190. package/dist/core/runtime/broker-protocol.d.ts +332 -0
  191. package/dist/core/runtime/broker-protocol.js +153 -0
  192. package/dist/core/runtime/broker-sdk.d.ts +48 -0
  193. package/dist/core/runtime/broker-sdk.js +72 -0
  194. package/dist/core/runtime/broker.d.ts +55 -0
  195. package/dist/core/runtime/broker.js +1128 -0
  196. package/dist/core/runtime/close.js +35 -6
  197. package/dist/core/runtime/host.d.ts +53 -0
  198. package/dist/core/runtime/host.js +186 -0
  199. package/dist/core/runtime/kickoff.d.ts +2 -1
  200. package/dist/core/runtime/kickoff.js +91 -5
  201. package/dist/core/runtime/launch.d.ts +45 -2
  202. package/dist/core/runtime/launch.js +65 -2
  203. package/dist/core/runtime/lifecycle.js +23 -6
  204. package/dist/core/runtime/memory.d.ts +2 -42
  205. package/dist/core/runtime/memory.js +11 -162
  206. package/dist/core/runtime/nodes.d.ts +33 -0
  207. package/dist/core/runtime/nodes.js +59 -1
  208. package/dist/core/runtime/persona.js +21 -11
  209. package/dist/core/runtime/pi-vendored.d.ts +18 -0
  210. package/dist/core/runtime/pi-vendored.js +49 -0
  211. package/dist/core/runtime/placement.d.ts +42 -14
  212. package/dist/core/runtime/placement.js +228 -38
  213. package/dist/core/runtime/promote.d.ts +0 -6
  214. package/dist/core/runtime/promote.js +1 -12
  215. package/dist/core/runtime/{demote.d.ts → recycle.d.ts} +5 -5
  216. package/dist/core/runtime/{demote.js → recycle.js} +27 -11
  217. package/dist/core/runtime/reset.js +8 -6
  218. package/dist/core/runtime/revive.d.ts +2 -0
  219. package/dist/core/runtime/revive.js +34 -34
  220. package/dist/core/runtime/spawn.d.ts +19 -0
  221. package/dist/core/runtime/spawn.js +75 -22
  222. package/dist/core/runtime/stop-guard.d.ts +1 -1
  223. package/dist/core/runtime/stop-guard.js +6 -1
  224. package/dist/core/runtime/tmux-chrome.d.ts +1 -1
  225. package/dist/core/runtime/tmux-chrome.js +1 -1
  226. package/dist/core/runtime/tmux.d.ts +28 -0
  227. package/dist/core/runtime/tmux.js +80 -6
  228. package/dist/core/scope.d.ts +11 -0
  229. package/dist/core/scope.js +39 -0
  230. package/dist/core/spawn.d.ts +25 -1
  231. package/dist/core/spawn.js +72 -7
  232. package/dist/core/substrate/gate.d.ts +13 -0
  233. package/dist/core/substrate/gate.js +21 -0
  234. package/dist/core/substrate/index.d.ts +7 -0
  235. package/dist/core/substrate/index.js +18 -0
  236. package/dist/core/substrate/on-read.d.ts +14 -0
  237. package/dist/core/substrate/on-read.js +292 -0
  238. package/dist/core/substrate/render.d.ts +25 -0
  239. package/dist/core/substrate/render.js +256 -0
  240. package/dist/core/substrate/schema.d.ts +76 -0
  241. package/dist/core/substrate/schema.js +124 -0
  242. package/dist/core/substrate/session-cache.d.ts +30 -0
  243. package/dist/core/substrate/session-cache.js +77 -0
  244. package/dist/core/substrate/subject.d.ts +41 -0
  245. package/dist/core/substrate/subject.js +54 -0
  246. package/dist/core/tui/contract.d.ts +83 -0
  247. package/dist/core/tui/contract.js +8 -0
  248. package/dist/core/tui/draw.d.ts +96 -0
  249. package/dist/core/tui/draw.js +339 -0
  250. package/dist/core/tui/host.d.ts +29 -0
  251. package/dist/core/tui/host.js +379 -0
  252. package/dist/core/tui/loader.d.ts +16 -0
  253. package/dist/core/tui/loader.js +94 -0
  254. package/dist/core/{canvas/browse → tui}/terminal.js +7 -6
  255. package/dist/core/wake.d.ts +86 -0
  256. package/dist/core/wake.js +308 -0
  257. package/dist/daemon/crtrd.d.ts +29 -4
  258. package/dist/daemon/crtrd.js +662 -46
  259. package/dist/pi-extensions/__tests__/canvas-context-intro.test.d.ts +1 -0
  260. package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +171 -0
  261. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +19 -12
  262. package/dist/pi-extensions/canvas-commands.d.ts +3 -0
  263. package/dist/pi-extensions/canvas-commands.js +10 -0
  264. package/dist/pi-extensions/canvas-context-intro.d.ts +17 -0
  265. package/dist/pi-extensions/canvas-context-intro.js +55 -15
  266. package/dist/pi-extensions/canvas-doc-substrate.d.ts +44 -0
  267. package/dist/pi-extensions/canvas-doc-substrate.js +112 -0
  268. package/dist/pi-extensions/canvas-nav.d.ts +3 -0
  269. package/dist/pi-extensions/canvas-nav.js +145 -34
  270. package/dist/pi-extensions/canvas-stophook.js +17 -8
  271. package/dist/pi-extensions/canvas-view.d.ts +21 -0
  272. package/dist/pi-extensions/canvas-view.js +75 -0
  273. package/dist/prompts/skill.js +19 -26
  274. package/dist/prompts/view.d.ts +7 -0
  275. package/dist/prompts/view.js +101 -0
  276. package/dist/types.d.ts +4 -0
  277. package/dist/types.js +1 -0
  278. package/package.json +9 -4
  279. package/dist/commands/skill/find.d.ts +0 -4
  280. package/dist/commands/skill/find.js +0 -257
  281. package/dist/commands/skill/read.js +0 -91
  282. /package/dist/commands/{skill → memory}/read.d.ts +0 -0
  283. /package/dist/core/{canvas/browse → tui}/terminal.d.ts +0 -0
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Predicate matcher engine — vendored verbatim from pi's frontmatter-rules
3
+ * extension.
4
+ *
5
+ * Source: pi-personal-extensions/extensions/frontmatter-rules/index.ts
6
+ * (the "Condition language" block, functions `asArray` … `evalCondition`).
7
+ *
8
+ * The unified document substrate uses this to evaluate a document's `gate`
9
+ * predicate against a node-config subject (e.g. `orchestration.depth: { gte: 2 }`).
10
+ *
11
+ * This is intentionally a COPY, not an import: the source lives in a separate,
12
+ * private, unpublished repo that crtr cannot depend on. The functions are pure
13
+ * TS over `unknown` / `Record<string, unknown>` with zero external imports, so
14
+ * vendoring them keeps this module standalone and dependency-free. Keep it in
15
+ * sync with the source by hand if the matcher language ever changes.
16
+ *
17
+ * Condition language (evaluated against the subject object):
18
+ * A condition is a map of <field> -> <matcher>, AND-ed across fields.
19
+ * `field` may be dotted to reach nested values (e.g. `orchestration.depth`).
20
+ *
21
+ * Matcher forms:
22
+ * scalar (string/number/bool/null)
23
+ * field === value, OR (if the field is an array) the array includes value
24
+ * array [a, b, c]
25
+ * membership/intersection: field is one of these, OR the field array
26
+ * shares any element with these
27
+ * object { <op>: <arg>, ... } (multiple ops AND together)
28
+ * eq equals (scalar or array-includes)
29
+ * ne not eq
30
+ * in [..] — field (or any of its array elements) is in the list
31
+ * nin not in
32
+ * exists true|false — field is present / absent
33
+ * contains field array includes this value
34
+ * containsAll [..] — field array includes all of these
35
+ * containsAny [..] — field array includes any of these
36
+ * matches regex string, case-sensitive (tests String(field); arrays: any)
37
+ * imatches regex string, case-insensitive
38
+ * gt gte lt lte numeric comparison
39
+ *
40
+ * Combinators (reserved keys at any condition level):
41
+ * all: [ {..}, {..} ] every sub-condition true (AND)
42
+ * any: [ {..}, {..} ] at least one true (OR)
43
+ * not: { .. } sub-condition false
44
+ * Sibling field matchers next to combinators are AND-ed in.
45
+ *
46
+ * Two load-bearing edge cases preserved from the source:
47
+ * - An empty condition (`{}`) is INERT: it returns false, NOT match-all.
48
+ * - An unknown op never matches.
49
+ */
50
+ export function asArray(v) {
51
+ if (Array.isArray(v))
52
+ return v;
53
+ if (v === undefined)
54
+ return [];
55
+ return [v];
56
+ }
57
+ // Scalar equality with light, predictable coercion: exact match, or
58
+ // string/number cross-compare so `version: 2` matches a subject "2".
59
+ export function scalarEq(a, b) {
60
+ if (a === b)
61
+ return true;
62
+ if ((typeof a === 'number' || typeof a === 'string') &&
63
+ (typeof b === 'number' || typeof b === 'string')) {
64
+ return String(a) === String(b);
65
+ }
66
+ return false;
67
+ }
68
+ // Resolve a (possibly dotted) field path against the subject object.
69
+ export function getField(subject, key) {
70
+ if (subject && typeof subject === 'object' && key in subject) {
71
+ return subject[key];
72
+ }
73
+ if (!key.includes('.'))
74
+ return undefined;
75
+ let cur = subject;
76
+ for (const part of key.split('.')) {
77
+ if (cur == null || typeof cur !== 'object')
78
+ return undefined;
79
+ cur = cur[part];
80
+ }
81
+ return cur;
82
+ }
83
+ export function toRegExp(arg, flags) {
84
+ if (typeof arg !== 'string')
85
+ return null;
86
+ try {
87
+ return new RegExp(arg, flags);
88
+ }
89
+ catch {
90
+ return null;
91
+ }
92
+ }
93
+ export function applyOp(op, value, arg) {
94
+ const vals = asArray(value);
95
+ switch (op) {
96
+ case 'eq':
97
+ return Array.isArray(value) ? value.some((v) => scalarEq(v, arg)) : scalarEq(value, arg);
98
+ case 'ne':
99
+ return !applyOp('eq', value, arg);
100
+ case 'in':
101
+ return asArray(arg).some((a) => vals.some((v) => scalarEq(v, a)));
102
+ case 'nin':
103
+ return !applyOp('in', value, arg);
104
+ case 'exists':
105
+ return (value !== undefined) === (arg !== false);
106
+ case 'contains':
107
+ return vals.some((v) => scalarEq(v, arg));
108
+ case 'containsAll':
109
+ return asArray(arg).every((a) => vals.some((v) => scalarEq(v, a)));
110
+ case 'containsAny':
111
+ return asArray(arg).some((a) => vals.some((v) => scalarEq(v, a)));
112
+ case 'matches':
113
+ case 'imatches': {
114
+ const re = toRegExp(arg, op === 'imatches' ? 'i' : '');
115
+ if (!re)
116
+ return false;
117
+ return vals.some((v) => (typeof v === 'string' || typeof v === 'number' ? re.test(String(v)) : false));
118
+ }
119
+ case 'gt':
120
+ case 'gte':
121
+ case 'lt':
122
+ case 'lte': {
123
+ const n = Number(value);
124
+ const t = Number(arg);
125
+ if (Number.isNaN(n) || Number.isNaN(t))
126
+ return false;
127
+ if (op === 'gt')
128
+ return n > t;
129
+ if (op === 'gte')
130
+ return n >= t;
131
+ if (op === 'lt')
132
+ return n < t;
133
+ return n <= t;
134
+ }
135
+ default:
136
+ return false; // unknown op never matches
137
+ }
138
+ }
139
+ export function matchField(value, matcher) {
140
+ if (matcher === null || ['string', 'number', 'boolean'].includes(typeof matcher)) {
141
+ if (Array.isArray(value))
142
+ return value.some((v) => scalarEq(v, matcher));
143
+ return scalarEq(value, matcher);
144
+ }
145
+ if (Array.isArray(matcher)) {
146
+ const vals = asArray(value);
147
+ return matcher.some((m) => vals.some((v) => scalarEq(v, m)));
148
+ }
149
+ if (matcher && typeof matcher === 'object') {
150
+ return Object.entries(matcher).every(([op, arg]) => applyOp(op, value, arg));
151
+ }
152
+ return false;
153
+ }
154
+ /**
155
+ * Evaluate a `gate` predicate against a subject object.
156
+ *
157
+ * This is the one function consumers call. Returns true iff `condition`
158
+ * matches `subject`. A null/non-object condition and an empty object both
159
+ * return false (inert), never match-all.
160
+ */
161
+ export function evalCondition(condition, subject) {
162
+ if (condition == null)
163
+ return false;
164
+ if (Array.isArray(condition))
165
+ return condition.every((sub) => evalCondition(sub, subject));
166
+ if (typeof condition !== 'object')
167
+ return false;
168
+ const c = condition;
169
+ const hasCombinator = 'all' in c || 'any' in c || 'not' in c;
170
+ if (hasCombinator) {
171
+ let ok = true;
172
+ if ('all' in c)
173
+ ok = ok && asArray(c.all).every((s) => evalCondition(s, subject));
174
+ if ('any' in c)
175
+ ok = ok && asArray(c.any).some((s) => evalCondition(s, subject));
176
+ if ('not' in c)
177
+ ok = ok && !evalCondition(c.not, subject);
178
+ for (const k of Object.keys(c)) {
179
+ if (k === 'all' || k === 'any' || k === 'not')
180
+ continue;
181
+ ok = ok && matchField(getField(subject, k), c[k]);
182
+ }
183
+ return ok;
184
+ }
185
+ const keys = Object.keys(c);
186
+ if (keys.length === 0)
187
+ return false; // empty condition is inert, not match-all
188
+ return keys.every((k) => matchField(getField(subject, k), c[k]));
189
+ }
@@ -5,6 +5,7 @@ import { listDirs, pathExists, readText, readTextIfExists, walkFiles, } from './
5
5
  import { readMarketplaceManifest, readPluginManifest } from './manifest.js';
6
6
  import { parseFrontmatter } from './frontmatter.js';
7
7
  import { ambiguous, notFound, usage } from './errors.js';
8
+ import { warn } from './output.js';
8
9
  import { InputError } from './io.js';
9
10
  import { builtinSkillsRoot, marketplacesDir, pluginsDir, projectScopeRoot, scopeSkillsDir, userScopeRoot, } from './scope.js';
10
11
  function getBuiltinPlugin() {
@@ -94,6 +95,22 @@ export function effectiveSkillEnabled(pluginName, skillName, cfgs) {
94
95
  }
95
96
  return { enabled: true };
96
97
  }
98
+ /** Parse one skill file's frontmatter at the COLLECTION layer: the strict
99
+ * parser THROWS on invalid YAML (the frontmatter contract is "valid YAML"),
100
+ * so a single malformed SKILL.md must not brick a whole `skill find`/catalog
101
+ * scan across the corpus. On a parse error, emit a clear scoped notice naming
102
+ * the file and return null so the iterator SKIPS it and continues. A doc with
103
+ * no frontmatter block parses fine (data === null) and is kept. */
104
+ function readSkillFrontmatterSafe(file) {
105
+ try {
106
+ return parseFrontmatter(readText(file));
107
+ }
108
+ catch (e) {
109
+ const msg = (e instanceof Error ? e.message : String(e)).split('\n')[0];
110
+ warn(`invalid frontmatter in ${file}: ${msg}`);
111
+ return null;
112
+ }
113
+ }
97
114
  export function listSkillsInPlugin(plugin, cfgs) {
98
115
  const skillsRoot = join(plugin.root, SKILLS_DIR);
99
116
  if (!pathExists(skillsRoot))
@@ -106,8 +123,10 @@ export function listSkillsInPlugin(plugin, cfgs) {
106
123
  const name = rel.split(sep).join('/');
107
124
  if (!name)
108
125
  continue;
109
- const source = readText(file);
110
- const { data } = parseFrontmatter(source);
126
+ const parsed = readSkillFrontmatterSafe(file);
127
+ if (parsed === null)
128
+ continue;
129
+ const { data } = parsed;
111
130
  const { enabled, disabledIn } = effectiveSkillEnabled(plugin.name, name, configs);
112
131
  skills.push({
113
132
  name,
@@ -136,8 +155,10 @@ export function listScopeRootSkills(scope, cfgs) {
136
155
  const name = rel.split(sep).join('/');
137
156
  if (!name)
138
157
  continue;
139
- const source = readText(file);
140
- const { data } = parseFrontmatter(source);
158
+ const parsed = readSkillFrontmatterSafe(file);
159
+ if (parsed === null)
160
+ continue;
161
+ const { data } = parsed;
141
162
  const { enabled, disabledIn } = effectiveSkillEnabled(SCOPE_SKILL_PLUGIN, name, configs);
142
163
  skills.push({
143
164
  name,
@@ -639,7 +660,7 @@ export function buildCategoryIndex(cat) {
639
660
  lines.push(`- \`${fullId}\` — ${desc}`);
640
661
  }
641
662
  lines.push('');
642
- lines.push(`Read one with \`crtr skill read <full-id>\`. Narrow with \`crtr skill find list --plugin ${cat.plugin ?? cat.id}\`.`);
663
+ lines.push('Read one with `crtr memory read <full-id>`. Search with `crtr memory find "<topic>"`.');
643
664
  return lines.join('\n');
644
665
  }
645
666
  export function scopeRootsLabel() {
@@ -1,20 +1,61 @@
1
+ import { type WakeKind, type Wakeup } from '../canvas/index.js';
1
2
  /** Base framing — present for every node. No path baked in: the caller carries
2
- * the dir in the <crtr-context dir="…"> attribute. */
3
+ * the dir in the `<crtr-context dir="…">` attribute. */
3
4
  export declare const BASE_CONTEXT_NOTE: string;
4
5
  /** Orchestrator-only framing: a resident orchestrator survives refresh cycles,
5
6
  * so its context dir is also where a future cycle of itself resumes the work.
6
7
  * Used inside the bearings block AND in the promotion guidance dump, so a
7
8
  * promoted node gets the same note a born-orchestrator gets. */
8
9
  export declare function orchestratorContextNote(nodeId: string): string;
9
- /** The <memory> block (orchestrators only): the scoped stores merged, each a
10
- * `label · dir` header over its live index pointer lines. A memory's `type`
11
- * decides which store it lands in the mapping + the how-to live once in the
12
- * orchestration kernel ("Your long-term memory"); here we carry only the live
13
- * data + a one-line pointer back to it. user-global rides in when the node has
14
- * a user store, project when it has a project store, node-local always (the
15
- * orchestrator gate). */
16
- export declare function buildMemoryBlock(nodeId: string, cwd: string): string;
17
- /** The full <crtr-context> bearings block: base framing always, plus the
18
- * orchestrator addendum + the merged three-store <memory> block when the node
19
- * has a node-local memory store (the orchestrator gate). */
10
+ /** The IDENTITY assertion that opens every boot intro the load-bearing fix
11
+ * for the `--fork-from` impersonation bug. A fork copies the SOURCE node's
12
+ * entire first-person conversation into its own session, so without an explicit
13
+ * re-assertion the forked agent reads that copied narrative as its own and
14
+ * impersonates the source (it kept "monitoring itself" as a phantom child).
15
+ * This block names the node unambiguously. ONLY when the node IS a fork does it
16
+ * additionally name the source AND disown the copied first-person narrative as
17
+ * inherited reference material a non-fork node's bearings are its first session
18
+ * entry, so there is no earlier narrative to disown (emitting a disown line there
19
+ * is dead weight that reads as a contradiction with no referent). Always the FIRST
20
+ * thing the node reads. Exported for testing. */
21
+ export declare function buildIdentityAssertion(nodeId: string): string;
22
+ /** Why a node woke or was born, as carried from the fired wakeup row to the
23
+ * injection seam. `ownerName` is the armer's resolved display name when it
24
+ * still exists (a reaped cron's armer renders as a bare id, never crashes). */
25
+ export interface WakeOrigin {
26
+ /** The firing kind. The <crtr-wake> block is rendered for 'spawn' (birth) and
27
+ * 'bare' (revive); 'noted'/'deadline' self-mark at the message seam instead. */
28
+ kind: WakeKind;
29
+ /** The armer node id (wakeups.owner_id), or null if somehow absent. */
30
+ ownerId: string | null;
31
+ /** The armer's display name, resolved if it still exists. */
32
+ ownerName?: string;
33
+ /** wakeups.created — when the wake was armed (ISO). */
34
+ armedAt: string;
35
+ /** wakeups.recur JSON when recurring, else null/undefined for a one-shot. */
36
+ recur?: string | null;
37
+ }
38
+ /** Build a WakeOrigin from a fired wakeup row, resolving the armer's display
39
+ * name if the node still exists. The daemon calls this at fire time for the
40
+ * bare-revive and spawn-birth seams. */
41
+ export declare function wakeOriginFrom(w: Wakeup): WakeOrigin;
42
+ /** The <crtr-wake> provenance block — load-bearing agent-facing prose read by
43
+ * every wake-born or wake-woken node. Decision-first: it leads with the fact a
44
+ * TIMER (not a message/event) caused this turn, names the wake kind, surfaces
45
+ * the cadence for a recurrence (so the agent knows it is one run of a standing
46
+ * job, not a one-off), and ends with the directive. The spawn (birth) variant
47
+ * names the ARMER explicitly ("armed by node X") so the newborn never reads that
48
+ * id as its own; the bare (revive) variant drops armer attribution entirely — a
49
+ * bare wake can be armed for a node by ANOTHER (`--node`), and who armed it is
50
+ * not decision-relevant to a timed re-check. No timestamp is rendered ("to fire
51
+ * now" / the cadence already carry the signal; a raw ISO instant is noise an
52
+ * agent cannot cheaply turn into an elapsed delta). Rendered for 'spawn' and
53
+ * 'bare'; other kinds self-mark elsewhere. */
54
+ export declare function buildWakeBearings(origin: WakeOrigin): string;
55
+ /** The full boot intro: the IDENTITY assertion (always first, so it overrides
56
+ * any copied-in persona) followed by the `<crtr-context>` bearings block. Base
57
+ * framing rides for EVERY node; the across-cycles context-dir note is added
58
+ * ONLY for an orchestrator (by mode) — the one node whose dir a future cycle
59
+ * resumes from. The `## References` block (substrate reference docs +
60
+ * node-local docs) replaces the old `<memory>` block. */
20
61
  export declare function buildContextBearings(nodeId: string): string;
@@ -1,8 +1,15 @@
1
- // bearings.ts — the <crtr-context> framing prose, shared by the two paths that
2
- // deliver it so they can never drift:
1
+ // bearings.ts — the boot-intro prose (the <crtr-identity> assertion + the
2
+ // <crtr-context> framing), shared by the paths that deliver it so they can
3
+ // never drift:
3
4
  //
4
5
  // • the context-intro pi-extension injects buildContextBearings() as the
5
- // node's first session message in every brand-new chat;
6
+ // node's first session message in every brand-new chat. It opens with a
7
+ // <crtr-identity> block (buildIdentityAssertion) that names the node and,
8
+ // ONLY for a fork, disowns the source's copied first-person narrative as
9
+ // inherited context — the fix for the `--fork-from` bug where a fork copies
10
+ // the source's whole conversation and then impersonates it. A non-fork
11
+ // node's bearings are its FIRST entry, so there is nothing earlier to
12
+ // disown; it gets only the declarative identity line;
6
13
  // • promote.ts folds orchestratorContextNote() into the promotion guidance
7
14
  // dump, so a node that becomes an orchestrator MID-LIFE gets the
8
15
  // orchestrator framing it never received at spawn — it spawned as a base
@@ -12,17 +19,17 @@
12
19
  // one place other nodes on the canvas can read from, so it is for documents
13
20
  // worth a shared reference, NOT a task tracker, and NOT a "future memory-wiped
14
21
  // you" stash (a terminal worker has no future cycle — that framing only makes
15
- // sense once a node is a resident orchestrator).
22
+ // sense once a node is a resident orchestrator). The `## References` block
23
+ // (substrate reference docs + node-local docs) rides into the context message.
16
24
  //
17
- // Orchestrator addendum (resident orchestrators i.e. nodes that have a
18
- // node-local memory store): the dir ALSO survives refresh cycles, so it is where
19
- // a future cycle of the orchestrator resumes; durable cross-goal lessons live in
20
- // the three scoped memory stores, whose index pointer lines are inlined into
21
- // <memory> (the how-to lives once in the kernel, not here).
22
- import { contextDir, getNode } from '../canvas/index.js';
23
- import { hasMemory, memoryDir, readMemory, hasUserMemory, userMemoryDir, readUserMemory, hasProjectMemory, projectMemoryDir, readProjectMemory, } from './memory.js';
25
+ // Orchestrator addendum (gated on orchestrator MODE): the dir ALSO survives
26
+ // refresh cycles, so it is where a future cycle of the orchestrator resumes.
27
+ // This across-cycles note is the ONE thing a terminal worker's bearings drop.
28
+ import { contextDir, getNode, fullName } from '../canvas/index.js';
29
+ import { cadenceDisplay } from '../wake.js';
30
+ import { renderReferencesBlock } from '../substrate/index.js';
24
31
  /** Base framing — present for every node. No path baked in: the caller carries
25
- * the dir in the <crtr-context dir="…"> attribute. */
32
+ * the dir in the `<crtr-context dir="…">` attribute. */
26
33
  export const BASE_CONTEXT_NOTE = 'This is your context directory — durable scratch space on disk, and the one place the other ' +
27
34
  'nodes on the canvas can read from. Put documents here that you want to share by reference ' +
28
35
  'instead of re-explaining them in a prompt: specs, designs, findings, notes worth pointing a ' +
@@ -36,57 +43,123 @@ export function orchestratorContextNote(nodeId) {
36
43
  `also where a future cycle of you resumes the work — keep the working notes and decisions a ` +
37
44
  `refreshed you would need there, alongside the docs you share with the nodes you spawn.`);
38
45
  }
39
- /** One labeled store stanza inside <memory>: a compact `label · dir` header (the
40
- * scope name + where to WRITE this kind of memory), then the LIVE pointer lines
41
- * extracted fresh from the store's index only lines matching `- [...` — with
42
- * the index's how-to boilerplate dropped (it lives once in the kernel) and
43
- * detail files loaded on demand. Falls back to `(empty)` when the index carries
44
- * no pointers, which also covers the not-seeded / template-only case. */
45
- function memoryStanza(label, dir, index) {
46
- const pointers = (index ?? '')
47
- .split('\n')
48
- .filter((line) => /^\s*-\s*\[/.test(line))
49
- .map((line) => line.trim());
50
- const body = pointers.length > 0 ? pointers.join('\n') : '(empty)';
51
- return `${label} · ${dir}\n${body}`;
46
+ /** The IDENTITY assertion that opens every boot intro the load-bearing fix
47
+ * for the `--fork-from` impersonation bug. A fork copies the SOURCE node's
48
+ * entire first-person conversation into its own session, so without an explicit
49
+ * re-assertion the forked agent reads that copied narrative as its own and
50
+ * impersonates the source (it kept "monitoring itself" as a phantom child).
51
+ * This block names the node unambiguously. ONLY when the node IS a fork does it
52
+ * additionally name the source AND disown the copied first-person narrative as
53
+ * inherited reference material a non-fork node's bearings are its first session
54
+ * entry, so there is no earlier narrative to disown (emitting a disown line there
55
+ * is dead weight that reads as a contradiction with no referent). Always the FIRST
56
+ * thing the node reads. Exported for testing. */
57
+ export function buildIdentityAssertion(nodeId) {
58
+ const meta = getNode(nodeId);
59
+ const name = meta?.name ?? nodeId;
60
+ const kind = meta?.kind ?? 'general';
61
+ const mode = meta?.mode ?? 'base';
62
+ const lines = [
63
+ '<crtr-identity>',
64
+ `You are node ${nodeId} — name "${name}", kind ${kind}, mode ${mode}.`,
65
+ ];
66
+ const forkFrom = meta?.fork_from;
67
+ if (forkFrom !== undefined && forkFrom !== null && forkFrom !== '') {
68
+ // Name the source: a known node id gets its human label; a raw path/uuid
69
+ // passes through as-is.
70
+ const src = getNode(forkFrom);
71
+ const sourceLabel = src !== null ? `${forkFrom} ("${fullName(src)}")` : forkFrom;
72
+ lines.push(`You are a FORK of ${sourceLabel}: at spawn pi COPIED that node's conversation into your ` +
73
+ `session as a starting point. You are NOT ${sourceLabel}. Everything earlier in this ` +
74
+ 'conversation is THEIR first-person history — inherited reference material, not your own ' +
75
+ 'past. Do not speak or act as them, do not continue their task as if it were yours, and do ' +
76
+ 'not "monitor yourself" as though you were a child they spawned.');
77
+ }
78
+ lines.push('</crtr-identity>');
79
+ return lines.join('\n');
80
+ }
81
+ /** Build a WakeOrigin from a fired wakeup row, resolving the armer's display
82
+ * name if the node still exists. The daemon calls this at fire time for the
83
+ * bare-revive and spawn-birth seams. */
84
+ export function wakeOriginFrom(w) {
85
+ const owner = w.owner_id != null ? getNode(w.owner_id) : null;
86
+ return {
87
+ kind: w.kind,
88
+ ownerId: w.owner_id ?? null,
89
+ ownerName: owner !== null ? fullName(owner) : undefined,
90
+ armedAt: w.created,
91
+ recur: w.recur,
92
+ };
52
93
  }
53
- /** The <memory> block (orchestrators only): the scoped stores merged, each a
54
- * `label · dir` header over its live index pointer lines. A memory's `type`
55
- * decides which store it lands in the mapping + the how-to live once in the
56
- * orchestration kernel ("Your long-term memory"); here we carry only the live
57
- * data + a one-line pointer back to it. user-global rides in when the node has
58
- * a user store, project when it has a project store, node-local always (the
59
- * orchestrator gate). */
60
- export function buildMemoryBlock(nodeId, cwd) {
61
- const stanzas = [];
62
- if (hasUserMemory()) {
63
- stanzas.push(memoryStanza('user-global', userMemoryDir(), readUserMemory()));
94
+ /** The armer rendered for prose, ROLE-explicit so a newborn never mistakes the
95
+ * id for its own: `node <id> ("<name>")` when the name resolved, `node <id>
96
+ * (now gone)` when the armer was reaped, `an unknown node` only if the owner id
97
+ * is somehow absent (owner_id is NOT NULL on every real row — defensive). */
98
+ function armerPhrase(origin) {
99
+ if (origin.ownerId === null || origin.ownerId === '')
100
+ return 'an unknown node';
101
+ if (origin.ownerName !== undefined)
102
+ return `node ${origin.ownerId} ("${origin.ownerName}")`;
103
+ return `node ${origin.ownerId} (now gone)`;
104
+ }
105
+ /** The <crtr-wake> provenance block — load-bearing agent-facing prose read by
106
+ * every wake-born or wake-woken node. Decision-first: it leads with the fact a
107
+ * TIMER (not a message/event) caused this turn, names the wake kind, surfaces
108
+ * the cadence for a recurrence (so the agent knows it is one run of a standing
109
+ * job, not a one-off), and ends with the directive. The spawn (birth) variant
110
+ * names the ARMER explicitly ("armed by node X") so the newborn never reads that
111
+ * id as its own; the bare (revive) variant drops armer attribution entirely — a
112
+ * bare wake can be armed for a node by ANOTHER (`--node`), and who armed it is
113
+ * not decision-relevant to a timed re-check. No timestamp is rendered ("to fire
114
+ * now" / the cadence already carry the signal; a raw ISO instant is noise an
115
+ * agent cannot cheaply turn into an elapsed delta). Rendered for 'spawn' and
116
+ * 'bare'; other kinds self-mark elsewhere. */
117
+ export function buildWakeBearings(origin) {
118
+ const recurring = origin.recur !== null && origin.recur !== undefined && origin.recur !== '';
119
+ const cadence = recurring ? cadenceDisplay(origin.recur) : null;
120
+ let body;
121
+ if (origin.kind === 'spawn') {
122
+ const armer = armerPhrase(origin);
123
+ body = recurring
124
+ ? `You were BORN by a scheduled wake, not spawned on demand — a recurring spawn-cron armed by ` +
125
+ `${armer}, firing ${cadence}. The runtime re-births a fresh node like you on this cadence whether ` +
126
+ `or not earlier runs survived: you are one run of a standing job, not a one-off, and you inherit ` +
127
+ `nothing from prior runs but this task. Your task follows.`
128
+ : `You were BORN by a scheduled wake, not spawned on demand — a one-shot deferred birth armed by ` +
129
+ `${armer} to fire now. You are its only run, not a recurring job. Your task follows.`;
64
130
  }
65
- if (hasProjectMemory(cwd)) {
66
- stanzas.push(memoryStanza('project', projectMemoryDir(cwd), readProjectMemory(cwd)));
131
+ else {
132
+ // bare scheduled alarm (the only kind delivered through this block at the
133
+ // revive seam; noted/deadline self-mark via their inbox label instead).
134
+ body = recurring
135
+ ? `You woke because a recurring scheduled alarm fired (${cadence}) — a timer, NOT a new message or ` +
136
+ `request. This is one tick of a standing re-check: re-read your roadmap and the disk bearings below ` +
137
+ `and decide what this moment calls for.`
138
+ : `You woke because a scheduled alarm fired — a timer, NOT a new message or request. This is a timed ` +
139
+ `re-check, not a new task: re-read your roadmap and the disk bearings below and decide what this ` +
140
+ `moment calls for.`;
67
141
  }
68
- stanzas.push(memoryStanza('node-local', memoryDir(nodeId), readMemory(nodeId)));
69
- const n = stanzas.length;
70
- return ('<memory>\n' +
71
- `Long-term memory, ${n} scope${n === 1 ? '' : 's'}. Each line ` +
72
- '`- [Title](slug.md) — hook`; load a detail file by slug from the scope dir on demand. ' +
73
- 'Write a new fact to the scope matching its `type` (see "Your long-term memory").\n\n' +
74
- stanzas.join('\n\n') +
75
- '\n</memory>');
142
+ return `<crtr-wake>\n${body}\n</crtr-wake>`;
76
143
  }
77
- /** The full <crtr-context> bearings block: base framing always, plus the
78
- * orchestrator addendum + the merged three-store <memory> block when the node
79
- * has a node-local memory store (the orchestrator gate). */
144
+ /** The full boot intro: the IDENTITY assertion (always first, so it overrides
145
+ * any copied-in persona) followed by the `<crtr-context>` bearings block. Base
146
+ * framing rides for EVERY node; the across-cycles context-dir note is added
147
+ * ONLY for an orchestrator (by mode) — the one node whose dir a future cycle
148
+ * resumes from. The `## References` block (substrate reference docs +
149
+ * node-local docs) replaces the old `<memory>` block. */
80
150
  export function buildContextBearings(nodeId) {
151
+ const identity = buildIdentityAssertion(nodeId);
81
152
  const dir = contextDir(nodeId);
82
- if (!hasMemory(nodeId)) {
83
- // A terminal worker (no memory store): base framing only, no memory block.
84
- return `<crtr-context dir="${dir}">\n${BASE_CONTEXT_NOTE}\n</crtr-context>`;
85
- }
86
- // An orchestrator: across-cycles framing + the merged three-store memory. The
87
- // project store is keyed off the node's cwd (its working dir on disk).
88
- const cwd = getNode(nodeId)?.cwd ?? process.cwd();
89
- return (`<crtr-context dir="${dir}">\n` +
90
- `${BASE_CONTEXT_NOTE}\n${orchestratorContextNote(nodeId)}\n${buildMemoryBlock(nodeId, cwd)}\n` +
91
- '</crtr-context>');
153
+ const node = getNode(nodeId);
154
+ const parts = [BASE_CONTEXT_NOTE];
155
+ // Orchestrator-only: the across-cycles framing (a terminal has no future cycle).
156
+ if (node?.mode === 'orchestrator')
157
+ parts.push(orchestratorContextNote(nodeId));
158
+ // The substrate references block: eligible `reference` docs at their
159
+ // system-prompt rung + node-local memory docs; dropped (returns '') when
160
+ // nothing is eligible.
161
+ const references = renderReferencesBlock(nodeId);
162
+ if (references !== '')
163
+ parts.push(references);
164
+ return `${identity}\n<crtr-context dir="${dir}">\n${parts.join('\n')}\n</crtr-context>`;
92
165
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,46 @@
1
+ // broker-cli.ts — the headless broker's dedicated detached entry (plan T4 /
2
+ // decision §1.13). Spawned directly via `spawn(execPath, [thisFile, nodeId], …)`
3
+ // by HeadlessBrokerHost.launch (T6) — NEVER routed through src/cli.ts, which
4
+ // would run the whole bootstrap chain (auto-update / scope-init / slash-template
5
+ // rewrite) on every broker boot. Mirrors src/daemon/crtrd-cli.ts: parse the one
6
+ // positional arg and hand off; keep this file a thin shim.
7
+ import { runBroker, disposeActiveSession } from './broker.js';
8
+ const nodeId = process.argv[2];
9
+ if (nodeId === undefined || nodeId.trim() === '') {
10
+ process.stderr.write('[broker] usage: broker-cli <nodeId>\n');
11
+ process.exit(1);
12
+ }
13
+ // M3 (scout mq5thyli): dispose the live engine before a FATAL exit. The bash tool
14
+ // spawns children `detached` (own pgid); only session.dispose() (→ abortBash /
15
+ // agent.abort → killProcessTree) reaps them. The graceful path (shutdownHandler /
16
+ // SIGTERM → disposeAndExit) already disposes; this routes the crash path —
17
+ // uncaughtException / unhandledRejection / a runBroker reject — through dispose
18
+ // too, so a fatal error never ORPHANS in-flight bash subprocesses. dispose is
19
+ // idempotent + a no-op once the graceful path has run.
20
+ function disposeAndExit(code) {
21
+ try {
22
+ disposeActiveSession();
23
+ }
24
+ catch {
25
+ /* dispose must not block the fatal exit */
26
+ }
27
+ process.exit(code);
28
+ }
29
+ // Last-resort safety net (review N-4): a stray throw/rejection from the SDK event
30
+ // stream or an extension's async hook would otherwise crash the broker on Node's
31
+ // default handler. Now that the host redirects the broker's stderr to the node's
32
+ // job/broker.log (M-2), log it there and exit non-zero — pre-session_start this
33
+ // folds into the daemon's boot-failure detection (M-1: pid stays null past the
34
+ // boot grace → surfaceBootFailure); post-session_start it is a clean grace-revive.
35
+ process.on('uncaughtException', (err) => {
36
+ process.stderr.write(`[broker] uncaughtException: ${err instanceof Error ? err.stack ?? err.message : String(err)}\n`);
37
+ disposeAndExit(1);
38
+ });
39
+ process.on('unhandledRejection', (reason) => {
40
+ process.stderr.write(`[broker] unhandledRejection: ${reason instanceof Error ? reason.stack ?? reason.message : String(reason)}\n`);
41
+ disposeAndExit(1);
42
+ });
43
+ runBroker(nodeId).catch((err) => {
44
+ process.stderr.write(`[broker] fatal: ${err instanceof Error ? err.stack ?? err.message : String(err)}\n`);
45
+ disposeAndExit(1);
46
+ });