@north-light/crouter 0.3.226 → 0.3.228

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 (288) hide show
  1. package/dist/api/client.d.ts +5 -5
  2. package/dist/api/client.js +3 -3
  3. package/dist/api/dto/broker-ops.d.ts +8 -0
  4. package/dist/api/dto/memory.d.ts +8 -7
  5. package/dist/api/dto/memory.js +2 -2
  6. package/dist/builtin-memory/05-kinds/design/00-base.md +3 -4
  7. package/dist/builtin-memory/05-kinds/design/01-orchestrator.md +4 -3
  8. package/dist/builtin-memory/05-kinds/design/design-contract.md +3 -6
  9. package/dist/builtin-memory/05-kinds/plan/00-base.md +3 -4
  10. package/dist/builtin-memory/05-kinds/plan/01-orchestrator.md +4 -5
  11. package/dist/builtin-memory/05-kinds/plan/plan-contract.md +5 -13
  12. package/dist/builtin-memory/design/guide.md +38 -16
  13. package/dist/builtin-memory/design/roadmap.md +4 -4
  14. package/dist/builtin-memory/insights/init.md +5 -5
  15. package/dist/builtin-memory/internal/INDEX.md +1 -1
  16. package/dist/builtin-memory/internal/examples/INDEX.md +1 -1
  17. package/dist/builtin-memory/internal/memory-loading.md +6 -6
  18. package/dist/builtin-memory/internal/plugins.md +1 -1
  19. package/dist/builtin-memory/plan/guide.md +53 -0
  20. package/dist/builtin-memory/plan/roadmap.md +10 -8
  21. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/memory-slash-commands.ts +50 -37
  22. package/dist/clients/attach/__tests__/pane-tag-successor.test.js +56 -0
  23. package/dist/clients/attach/__tests__/profile-files.test.js +21 -0
  24. package/dist/clients/attach/chrome/inbox-strip.js +11 -7
  25. package/dist/clients/attach/session/pane-tag.d.ts +28 -2
  26. package/dist/clients/attach/session/pane-tag.js +48 -22
  27. package/dist/clients/attach/session/profile-files.d.ts +6 -0
  28. package/dist/clients/attach/session/profile-files.js +44 -11
  29. package/dist/clients/attach/slash/dispatch.js +4 -7
  30. package/dist/clients/attach/viewer.js +588 -596
  31. package/dist/commands/__tests__/surface-reload-target.test.js +21 -0
  32. package/dist/commands/api-client.d.ts +3 -7
  33. package/dist/commands/api-client.js +7 -12
  34. package/dist/commands/memory/__tests__/command-selector-and-mutation-guards.test.js +292 -0
  35. package/dist/commands/memory/__tests__/repository-root-lint.test.js +146 -0
  36. package/dist/commands/memory/delete.js +51 -24
  37. package/dist/commands/memory/edit.js +50 -10
  38. package/dist/commands/memory/find.js +105 -56
  39. package/dist/commands/memory/history.js +51 -31
  40. package/dist/commands/memory/lint.d.ts +0 -9
  41. package/dist/commands/memory/lint.js +78 -322
  42. package/dist/commands/memory/list.d.ts +12 -4
  43. package/dist/commands/memory/list.js +64 -25
  44. package/dist/commands/memory/move.js +103 -73
  45. package/dist/commands/memory/origin.js +35 -5
  46. package/dist/commands/memory/read.d.ts +4 -0
  47. package/dist/commands/memory/read.js +132 -80
  48. package/dist/commands/memory/shared.d.ts +115 -25
  49. package/dist/commands/memory/shared.js +331 -74
  50. package/dist/commands/memory/write.js +72 -21
  51. package/dist/commands/memory.js +2 -2
  52. package/dist/commands/node/lifecycle.js +2 -2
  53. package/dist/commands/pkg/market-manage.js +25 -24
  54. package/dist/commands/pkg/plugin-manage.d.ts +22 -6
  55. package/dist/commands/pkg/plugin-manage.js +118 -25
  56. package/dist/commands/pkg/shared.d.ts +8 -0
  57. package/dist/commands/pkg/shared.js +23 -20
  58. package/dist/commands/surface-reload.d.ts +5 -0
  59. package/dist/commands/surface-reload.js +9 -1
  60. package/dist/commands/sys/__tests__/migrate.test.js +1140 -22
  61. package/dist/commands/sys/__tests__/sync-project-guidance.test.js +218 -0
  62. package/dist/commands/sys/migrate.js +126 -140
  63. package/dist/commands/sys/panels/profiles-panel.d.ts +66 -0
  64. package/dist/commands/sys/panels/profiles-panel.js +599 -0
  65. package/dist/commands/sys/settings-shell.d.ts +4 -2
  66. package/dist/commands/sys/settings-shell.js +35 -4
  67. package/dist/commands/sys/settings.js +21 -5
  68. package/dist/commands/sys/sync-deps.d.ts +2 -0
  69. package/dist/commands/sys/sync-deps.js +26 -15
  70. package/dist/commands/sys/sync-project-guidance.js +228 -145
  71. package/dist/commands/sys/sync-skills.js +28 -17
  72. package/dist/commands/sys/update.js +11 -3
  73. package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +15 -2
  74. package/dist/core/__tests__/config-change-delta.test.js +106 -0
  75. package/dist/core/__tests__/context-intro.test.js +13 -9
  76. package/dist/core/__tests__/daemon-boot.test.js +22 -16
  77. package/dist/core/__tests__/fixtures/memory-slash-live-probe.js +5 -1
  78. package/dist/core/__tests__/human-deliver.test.js +19 -0
  79. package/dist/core/__tests__/inline-memory-refs.test.js +50 -46
  80. package/dist/core/__tests__/{serial → integration}/broker-fork-seam.test.js +1 -1
  81. package/dist/core/__tests__/{serial → integration}/broker-sdk-wiring.test.js +1 -1
  82. package/dist/core/__tests__/{serial → integration}/broker-snapshot-history.test.js +1 -1
  83. package/dist/core/__tests__/{serial → integration}/command-plugins.test.js +94 -3
  84. package/dist/core/__tests__/{serial → integration}/deferred-no-wake.test.js +1 -1
  85. package/dist/core/__tests__/{serial → integration}/flagship-lifecycle.test.js +13 -27
  86. package/dist/core/__tests__/{serial → integration}/host-teardown-process-group.test.js +1 -1
  87. package/dist/core/__tests__/{serial → integration}/human-deliver-e2e.test.js +1 -1
  88. package/dist/core/__tests__/{live-mutation-verbs.test.js → integration/live-mutation-verbs.test.js} +22 -33
  89. package/dist/core/__tests__/{serial → integration}/live-mutation.test.js +21 -41
  90. package/dist/core/__tests__/integration/refresh-stall-recycle.test.d.ts +1 -0
  91. package/dist/core/__tests__/{serial → integration}/refresh-stall-recycle.test.js +1 -1
  92. package/dist/core/__tests__/integration/revive.test.d.ts +1 -0
  93. package/dist/core/__tests__/{serial → integration}/revive.test.js +38 -19
  94. package/dist/core/__tests__/integration/spawn-root.test.d.ts +1 -0
  95. package/dist/core/__tests__/{serial → integration}/spawn-root.test.js +104 -1
  96. package/dist/core/__tests__/integration/subscription-delivery.test.d.ts +1 -0
  97. package/dist/core/__tests__/{serial → integration}/subscription-delivery.test.js +1 -1
  98. package/dist/core/__tests__/integration/tmux-surface.test.d.ts +1 -0
  99. package/dist/core/__tests__/{serial → integration}/tmux-surface.test.js +1 -1
  100. package/dist/core/__tests__/integration/worktree-land.test.d.ts +1 -0
  101. package/dist/core/__tests__/integration/worktree-land.test.js +400 -0
  102. package/dist/core/__tests__/integration/worktree-reap.test.d.ts +1 -0
  103. package/dist/core/__tests__/{serial/worktree.test.js → integration/worktree-reap.test.js} +6 -338
  104. package/dist/core/__tests__/kickoff.test.js +16 -5
  105. package/dist/core/__tests__/memory-resolver-precedence.test.js +122 -91
  106. package/dist/core/__tests__/nested-store-discovery.test.js +5 -3
  107. package/dist/core/__tests__/on-read-crouter-home-fence.test.js +2 -1
  108. package/dist/core/__tests__/on-read-dedup-resume.test.js +39 -27
  109. package/dist/core/__tests__/on-read-nested-store.test.js +19 -13
  110. package/dist/core/__tests__/profile-project-memory-delivery.test.js +138 -44
  111. package/dist/core/__tests__/repository-association.test.d.ts +1 -0
  112. package/dist/core/__tests__/repository-association.test.js +153 -0
  113. package/dist/core/__tests__/repository-root-identity.test.d.ts +1 -0
  114. package/dist/core/__tests__/repository-root-identity.test.js +219 -0
  115. package/dist/core/__tests__/seam/memory-slash-node-relative-inventory.test.js +27 -11
  116. package/dist/core/__tests__/warm-claim-preference-snapshot.test.d.ts +1 -0
  117. package/dist/core/__tests__/warm-claim-preference-snapshot.test.js +40 -0
  118. package/dist/core/canvas/canvas.d.ts +3 -0
  119. package/dist/core/canvas/canvas.js +30 -10
  120. package/dist/core/canvas/db.js +28 -1
  121. package/dist/core/canvas/paths.d.ts +4 -6
  122. package/dist/core/canvas/paths.js +8 -6
  123. package/dist/core/canvas/render-source.js +2 -2
  124. package/dist/core/canvas/types.d.ts +9 -13
  125. package/dist/core/exclusive-lock.d.ts +2 -0
  126. package/dist/core/exclusive-lock.js +21 -0
  127. package/dist/core/git.d.ts +0 -1
  128. package/dist/core/git.js +0 -3
  129. package/dist/core/human/__tests__/integration/inbox-core.test.d.ts +1 -0
  130. package/dist/core/human/feedback-companion.js +3 -0
  131. package/dist/core/human/scan.js +8 -1
  132. package/dist/core/keybindings/catalog.d.ts +2 -2
  133. package/dist/core/keybindings/catalog.js +2 -1
  134. package/dist/core/memory/doc-link-grammar.js +2 -2
  135. package/dist/core/memory/history.d.ts +24 -0
  136. package/dist/core/memory/history.js +66 -1
  137. package/dist/core/memory/identity.d.ts +65 -0
  138. package/dist/core/memory/identity.js +185 -0
  139. package/dist/core/memory/inline-ref-guidance.d.ts +1 -1
  140. package/dist/core/memory/inline-ref-guidance.js +1 -1
  141. package/dist/core/memory/inline-ref-inventory.d.ts +3 -10
  142. package/dist/core/memory/inline-ref-inventory.js +30 -62
  143. package/dist/core/memory/lint.d.ts +129 -0
  144. package/dist/core/memory/lint.js +517 -0
  145. package/dist/core/memory/project-namespace.d.ts +31 -0
  146. package/dist/core/memory/project-namespace.js +85 -0
  147. package/dist/core/memory/repository-association.d.ts +31 -0
  148. package/dist/core/memory/repository-association.js +129 -0
  149. package/dist/core/memory/tree.d.ts +39 -0
  150. package/dist/core/memory/tree.js +93 -0
  151. package/dist/core/memory-resolver.d.ts +193 -90
  152. package/dist/core/memory-resolver.js +461 -375
  153. package/dist/core/nested-stores.js +6 -13
  154. package/dist/core/profiles/select.d.ts +4 -1
  155. package/dist/core/profiles/select.js +108 -48
  156. package/dist/core/review/__tests__/capture-origin.test.js +2 -2
  157. package/dist/core/review/__tests__/stage-identity.test.js +2 -2
  158. package/dist/core/review/companion.js +11 -2
  159. package/dist/core/runtime/bearings.d.ts +2 -2
  160. package/dist/core/runtime/bearings.js +3 -3
  161. package/dist/core/runtime/broker/daemon-ops.d.ts +2 -2
  162. package/dist/core/runtime/broker/rebind.js +5 -0
  163. package/dist/core/runtime/broker-extension-render.d.ts +7 -3
  164. package/dist/core/runtime/broker-extension-render.js +10 -5
  165. package/dist/core/runtime/broker-persona-guidance.d.ts +19 -5
  166. package/dist/core/runtime/broker-persona-guidance.js +122 -27
  167. package/dist/core/runtime/deliver-live.d.ts +16 -4
  168. package/dist/core/runtime/deliver-live.js +29 -15
  169. package/dist/core/runtime/kickoff.d.ts +3 -3
  170. package/dist/core/runtime/kickoff.js +8 -16
  171. package/dist/core/runtime/lifecycle.js +2 -3
  172. package/dist/core/runtime/nodes.d.ts +3 -4
  173. package/dist/core/runtime/nodes.js +3 -4
  174. package/dist/core/runtime/persona.d.ts +8 -12
  175. package/dist/core/runtime/persona.js +24 -96
  176. package/dist/core/runtime/promote.d.ts +2 -2
  177. package/dist/core/runtime/promote.js +8 -21
  178. package/dist/core/runtime/revive.js +20 -17
  179. package/dist/core/runtime/spawn.js +18 -7
  180. package/dist/core/runtime/tmux-bindings.js +2 -3
  181. package/dist/core/runtime/warm-pool.js +3 -4
  182. package/dist/core/scope.js +2 -0
  183. package/dist/core/self-update.d.ts +0 -2
  184. package/dist/core/self-update.js +2 -35
  185. package/dist/core/substrate/__tests__/surface-match-memory-read.test.d.ts +1 -0
  186. package/dist/core/substrate/__tests__/surface-match-memory-read.test.js +28 -0
  187. package/dist/core/substrate/index.d.ts +2 -2
  188. package/dist/core/substrate/index.js +1 -1
  189. package/dist/core/substrate/injected-store.d.ts +43 -27
  190. package/dist/core/substrate/injected-store.js +208 -104
  191. package/dist/core/substrate/listings.d.ts +19 -12
  192. package/dist/core/substrate/listings.js +75 -52
  193. package/dist/core/substrate/on-read-node.d.ts +4 -7
  194. package/dist/core/substrate/on-read-node.js +6 -8
  195. package/dist/core/substrate/on-read.d.ts +22 -25
  196. package/dist/core/substrate/on-read.js +103 -147
  197. package/dist/core/substrate/render-node.d.ts +4 -7
  198. package/dist/core/substrate/render-node.js +5 -7
  199. package/dist/core/substrate/render.d.ts +21 -3
  200. package/dist/core/substrate/render.js +291 -223
  201. package/dist/core/substrate/schema.d.ts +1 -13
  202. package/dist/core/substrate/schema.js +5 -40
  203. package/dist/core/substrate/session-cache.d.ts +14 -4
  204. package/dist/core/substrate/session-cache.js +40 -22
  205. package/dist/core/substrate/surface-match.d.ts +9 -7
  206. package/dist/core/substrate/surface-match.js +26 -25
  207. package/dist/daemon/__tests__/helpers/source-daemon.d.ts +30 -0
  208. package/dist/daemon/__tests__/helpers/source-daemon.js +174 -0
  209. package/dist/daemon/__tests__/integration/migration-startup.test.d.ts +1 -0
  210. package/dist/daemon/__tests__/integration/migration-startup.test.js +97 -0
  211. package/dist/daemon/api/__tests__/bridge-heartbeat.test.js +37 -9
  212. package/dist/daemon/api/bridge.d.ts +35 -4
  213. package/dist/daemon/api/bridge.js +50 -8
  214. package/dist/daemon/api/handlers/broker-ops.js +21 -16
  215. package/dist/daemon/api/handlers/memory.js +2 -0
  216. package/dist/daemon/crtrd.js +2 -0
  217. package/dist/daemon/human/finish.js +8 -1
  218. package/dist/daemon/manage.d.ts +0 -1
  219. package/dist/daemon/manage.js +7 -16
  220. package/dist/daemon/startup-policy.d.ts +1 -0
  221. package/dist/daemon/startup-policy.js +1 -0
  222. package/dist/migrations/001-surfaces-frontmatter.js +21 -109
  223. package/dist/migrations/002-profile-project-memory.js +1 -0
  224. package/dist/migrations/003-repository-root-memory-identity/front-door.d.ts +26 -0
  225. package/dist/migrations/003-repository-root-memory-identity/front-door.js +231 -0
  226. package/dist/migrations/003-repository-root-memory-identity/index.d.ts +2 -0
  227. package/dist/migrations/003-repository-root-memory-identity/index.js +513 -0
  228. package/dist/migrations/003-repository-root-memory-identity/references.d.ts +95 -0
  229. package/dist/migrations/003-repository-root-memory-identity/references.js +469 -0
  230. package/dist/migrations/003-repository-root-memory-identity/repository-facts.d.ts +39 -0
  231. package/dist/migrations/003-repository-root-memory-identity/repository-facts.js +349 -0
  232. package/dist/migrations/__tests__/activation-concurrency.test.d.ts +1 -0
  233. package/dist/migrations/__tests__/activation-concurrency.test.js +145 -0
  234. package/dist/migrations/__tests__/activation.test.d.ts +1 -0
  235. package/dist/migrations/__tests__/activation.test.js +149 -0
  236. package/dist/migrations/__tests__/deletion-and-root-declaration.test.d.ts +1 -0
  237. package/dist/migrations/__tests__/deletion-and-root-declaration.test.js +149 -0
  238. package/dist/migrations/activation.d.ts +16 -0
  239. package/dist/migrations/activation.js +78 -0
  240. package/dist/migrations/convergent.d.ts +14 -3
  241. package/dist/migrations/convergent.js +21 -10
  242. package/dist/migrations/corpus.d.ts +78 -0
  243. package/dist/migrations/corpus.js +497 -0
  244. package/dist/migrations/frontmatter-splice.d.ts +15 -0
  245. package/dist/migrations/frontmatter-splice.js +176 -0
  246. package/dist/migrations/registry.d.ts +6 -1
  247. package/dist/migrations/registry.js +7 -2
  248. package/dist/migrations/runner.d.ts +41 -0
  249. package/dist/migrations/runner.js +81 -0
  250. package/dist/migrations/types.d.ts +148 -9
  251. package/dist/migrations/types.js +9 -2
  252. package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +225 -17
  253. package/dist/pi-extensions/__tests__/canvas-goal-capture-envelope.test.js +11 -3
  254. package/dist/pi-extensions/canvas-context-intro.d.ts +3 -5
  255. package/dist/pi-extensions/canvas-context-intro.js +50 -46
  256. package/dist/pi-extensions/canvas-doc-substrate.d.ts +1 -8
  257. package/dist/pi-extensions/canvas-doc-substrate.js +55 -122
  258. package/dist/pi-extensions/canvas-stophook.js +6 -13
  259. package/dist/shared/generated-context.d.ts +0 -3
  260. package/dist/shared/generated-context.js +0 -57
  261. package/dist/shared/tool-groups.js +2 -3
  262. package/package.json +5 -4
  263. package/runtime.lock.json +2 -2
  264. /package/dist/api/__tests__/{serial → integration}/client.test.d.ts +0 -0
  265. /package/dist/api/__tests__/{serial → integration}/client.test.js +0 -0
  266. /package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/{serial → integration}/provider-rotation.test.ts +0 -0
  267. /package/dist/clients/{inbox/__tests__/serial/inbox-controller.test.d.ts → attach/__tests__/pane-tag-successor.test.d.ts} +0 -0
  268. /package/dist/clients/{inbox/__tests__/serial/mount-panel.test.d.ts → attach/__tests__/profile-files.test.d.ts} +0 -0
  269. /package/dist/{core/__tests__/live-mutation-verbs.test.d.ts → clients/inbox/__tests__/integration/inbox-controller.test.d.ts} +0 -0
  270. /package/dist/clients/inbox/__tests__/{serial → integration}/inbox-controller.test.js +0 -0
  271. /package/dist/{core/__tests__/serial/broker-fork-seam.test.d.ts → clients/inbox/__tests__/integration/mount-panel.test.d.ts} +0 -0
  272. /package/dist/clients/inbox/__tests__/{serial → integration}/mount-panel.test.js +0 -0
  273. /package/dist/{core/__tests__/serial/broker-sdk-wiring.test.d.ts → commands/__tests__/surface-reload-target.test.d.ts} +0 -0
  274. /package/dist/{core/__tests__/serial/broker-snapshot-history.test.d.ts → commands/memory/__tests__/command-selector-and-mutation-guards.test.d.ts} +0 -0
  275. /package/dist/{core/__tests__/serial/command-plugins.test.d.ts → commands/memory/__tests__/repository-root-lint.test.d.ts} +0 -0
  276. /package/dist/{core/__tests__/serial/deferred-no-wake.test.d.ts → commands/sys/__tests__/sync-project-guidance.test.d.ts} +0 -0
  277. /package/dist/core/__tests__/{serial/flagship-lifecycle.test.d.ts → config-change-delta.test.d.ts} +0 -0
  278. /package/dist/core/__tests__/{serial/host-teardown-process-group.test.d.ts → integration/broker-fork-seam.test.d.ts} +0 -0
  279. /package/dist/core/__tests__/{serial/human-deliver-e2e.test.d.ts → integration/broker-sdk-wiring.test.d.ts} +0 -0
  280. /package/dist/core/__tests__/{serial/live-mutation.test.d.ts → integration/broker-snapshot-history.test.d.ts} +0 -0
  281. /package/dist/core/__tests__/{serial/refresh-stall-recycle.test.d.ts → integration/command-plugins.test.d.ts} +0 -0
  282. /package/dist/core/__tests__/{serial/revive.test.d.ts → integration/deferred-no-wake.test.d.ts} +0 -0
  283. /package/dist/core/__tests__/{serial/spawn-root.test.d.ts → integration/flagship-lifecycle.test.d.ts} +0 -0
  284. /package/dist/core/__tests__/{serial/subscription-delivery.test.d.ts → integration/host-teardown-process-group.test.d.ts} +0 -0
  285. /package/dist/core/__tests__/{serial/tmux-surface.test.d.ts → integration/human-deliver-e2e.test.d.ts} +0 -0
  286. /package/dist/core/__tests__/{serial/worktree.test.d.ts → integration/live-mutation-verbs.test.d.ts} +0 -0
  287. /package/dist/core/{human/__tests__/serial/inbox-core.test.d.ts → __tests__/integration/live-mutation.test.d.ts} +0 -0
  288. /package/dist/core/human/__tests__/{serial → integration}/inbox-core.test.js +0 -0
@@ -1,21 +1,28 @@
1
1
  // Run with: node --import tsx/esm --test src/pi-extensions/__tests__/canvas-context-intro.test.ts
2
2
  //
3
- // A forked crouter branch receives a compact node-scoped update after its
4
- // inherited baseline, and a resume re-injects nothing — idempotency keyed on
5
- // the exact node-id stamp, independent of the bearings text.
3
+ // A forked crouter branch receives compact node bearings only with persisted
4
+ // transcript provenance. Persona drift is delivered at the provider gate.
6
5
  import { test, before, after, beforeEach, afterEach } from 'node:test';
7
6
  import assert from 'node:assert/strict';
8
- import { existsSync, mkdtempSync, mkdirSync, rmSync } from 'node:fs';
7
+ import { existsSync, mkdtempSync, mkdirSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
9
8
  import { tmpdir } from 'node:os';
10
9
  import { join } from 'node:path';
11
10
  import { registerCanvasContextIntro, CONTEXT_INTRO_CUSTOM_TYPE, } from '../canvas-context-intro.js';
11
+ import { registerCanvasDocSubstrate } from '../canvas-doc-substrate.js';
12
12
  import { buildContextBearings as buildContextIntro } from '../../core/runtime/bearings.js';
13
- import { createNode } from '../../core/canvas/canvas.js';
13
+ import { installPersonaTransitionGate } from '../../core/runtime/broker-persona-guidance.js';
14
+ import { createNode, deleteNode, getNode, updateNode } from '../../core/canvas/canvas.js';
14
15
  import { apiSocketPath } from '../../core/canvas/paths.js';
15
16
  import { createApiServer } from '../../daemon/api/server.js';
16
17
  import { closeDb } from '../../core/canvas/db.js';
18
+ import { resetScopeCache } from '../../core/scope.js';
19
+ import { clearSessionCache } from '../../core/substrate/session-cache.js';
20
+ import { emptyContextExposureState, exposureTarget, initializeForkContextExposure, loadContextExposureState, registerExposure, saveContextExposureState, } from '../../core/substrate/injected-store.js';
17
21
  let home;
22
+ let origCwd;
23
+ let origHome;
18
24
  let origNode;
25
+ let origProfile;
19
26
  let server;
20
27
  async function waitForSocket(path) {
21
28
  for (let i = 0; i < 200; i++) {
@@ -45,6 +52,7 @@ function introEntry(content) {
45
52
  }
46
53
  function makeFakePi() {
47
54
  let handler;
55
+ let branch = [];
48
56
  return {
49
57
  sent: [],
50
58
  on(event, h) {
@@ -53,41 +61,74 @@ function makeFakePi() {
53
61
  },
54
62
  sendMessage(m) {
55
63
  this.sent.push(m);
64
+ branch.push({ type: 'custom_message', ...m });
56
65
  },
57
66
  registerMessageRenderer() {
58
67
  /* display-only; irrelevant here */
59
68
  },
60
- async fireSessionStart(entries) {
61
- await handler?.(undefined, { sessionManager: { getEntries: () => entries } });
69
+ async fireSessionStart(entries, reason) {
70
+ branch = entries;
71
+ await handler?.({ reason }, {
72
+ sessionManager: {
73
+ getEntries: () => branch,
74
+ getBranch: () => branch,
75
+ },
76
+ });
62
77
  },
63
78
  };
64
79
  }
65
80
  before(() => {
81
+ origCwd = process.cwd();
82
+ origHome = process.env['HOME'];
66
83
  origNode = process.env['CRTR_NODE_ID'];
84
+ origProfile = process.env['CRTR_PROFILE_ID'];
67
85
  });
68
86
  beforeEach(async () => {
69
87
  closeDb();
70
88
  if (home)
71
89
  rmSync(home, { recursive: true, force: true });
72
90
  home = mkdtempSync(join(tmpdir(), 'crtr-context-intro-'));
91
+ const userHome = join(home, 'user');
92
+ const cwd = join(userHome, 'work');
93
+ mkdirSync(cwd, { recursive: true });
94
+ process.chdir(cwd);
95
+ process.env['HOME'] = userHome;
73
96
  process.env['CRTR_HOME'] = home;
74
97
  delete process.env['CRTR_NODE_ID'];
75
- mkdirSync(home, { recursive: true });
98
+ delete process.env['CRTR_PROFILE_ID'];
99
+ resetScopeCache();
100
+ clearSessionCache();
76
101
  server = createApiServer();
77
102
  await waitForSocket(apiSocketPath());
78
103
  });
79
104
  afterEach(async () => {
80
- await server.close();
105
+ try {
106
+ await server.close();
107
+ }
108
+ finally {
109
+ process.chdir(origCwd);
110
+ }
81
111
  });
82
112
  after(() => {
113
+ process.chdir(origCwd);
83
114
  closeDb();
84
115
  if (home)
85
116
  rmSync(home, { recursive: true, force: true });
117
+ if (origHome === undefined)
118
+ delete process.env['HOME'];
119
+ else
120
+ process.env['HOME'] = origHome;
121
+ resetScopeCache();
122
+ clearSessionCache();
86
123
  delete process.env['CRTR_HOME'];
87
124
  if (origNode === undefined)
88
125
  delete process.env['CRTR_NODE_ID'];
89
126
  else
90
127
  process.env['CRTR_NODE_ID'] = origNode;
128
+ if (origProfile === undefined)
129
+ delete process.env['CRTR_PROFILE_ID'];
130
+ else
131
+ process.env['CRTR_PROFILE_ID'] = origProfile;
91
132
  });
92
133
  test('session_start appends compact fork bearings after an inherited source baseline', async () => {
93
134
  createNode(node('srcA'));
@@ -95,6 +136,16 @@ test('session_start appends compact fork bearings after an inherited source base
95
136
  // A fork boots with the SOURCE's whole conversation copied in — including the
96
137
  // source's bearings intro entry (which names srcA, not forkB).
97
138
  const inheritedSourceBlock = introEntry(buildContextIntro('srcA'));
139
+ const sourceExposure = emptyContextExposureState();
140
+ registerExposure(exposureTarget(sourceExposure, 'system'), 'source-system-only', 'content');
141
+ registerExposure(exposureTarget(sourceExposure, 'transcript'), 'inherited-source-document', 'content');
142
+ saveContextExposureState('srcA', sourceExposure);
143
+ initializeForkContextExposure('forkB', 'srcA');
144
+ deleteNode('srcA');
145
+ initializeForkContextExposure('forkB', null);
146
+ const forkExposure = loadContextExposureState('forkB');
147
+ assert.deepEqual(forkExposure.exposures.get('inherited-source-document'), { transcript: 3 }, 'a pre-bind retry preserves the transcript exposure seed after its source disappears');
148
+ assert.equal(forkExposure.exposures.has('source-system-only'), false, 'a fork never inherits source system exposure');
98
149
  process.env['CRTR_NODE_ID'] = 'forkB';
99
150
  const pi = makeFakePi();
100
151
  registerCanvasContextIntro(pi);
@@ -104,15 +155,172 @@ test('session_start appends compact fork bearings after an inherited source base
104
155
  assert.equal(pi.sent[0].customType, CONTEXT_INTRO_CUSTOM_TYPE);
105
156
  assert.match(pi.sent[0].content, /You are node forkB/, 'injected bearings name the fork, not the source');
106
157
  assert.match(pi.sent[0].content, /new FORK of srcA/, 'injected bearings carry the fork callout');
107
- assert.doesNotMatch(pi.sent[0].content, /<project_context>/, 'the full source baseline is not repeated');
158
+ assert.doesNotMatch(pi.sent[0].content, /<project_context>/, 'inherited crouter bearings stay compact even after the source row is gone');
108
159
  assert.equal(pi.sent[0].details?.nodeId, 'forkB', 'the compact update carries the fork node stamp');
109
- // A non-crouter source has no inherited baseline, so its fork receives one.
110
- createNode(node('externalF', { fork_from: '/tmp/external-session.jsonl' }));
111
- process.env['CRTR_NODE_ID'] = 'externalF';
112
- const externalPi = makeFakePi();
113
- registerCanvasContextIntro(externalPi);
114
- await externalPi.fireSessionStart([]);
115
- assert.match(externalPi.sent[0].content, /<project_context>/, 'an external-session fork receives the full baseline');
160
+ // An inherited crouter block without a recoverable transcript seed cannot
161
+ // prove what that branch contains, so the destination starts from scratch.
162
+ createNode(node('unknownF', { fork_from: '/tmp/unknown-session.jsonl' }));
163
+ const unknownPreState = loadContextExposureState('unknownF');
164
+ assert.equal(unknownPreState.exposures.size, 0);
165
+ assert.equal(unknownPreState.preferenceSnapshot, null);
166
+ process.env['CRTR_NODE_ID'] = 'unknownF';
167
+ const unknownPi = makeFakePi();
168
+ registerCanvasContextIntro(unknownPi);
169
+ await unknownPi.fireSessionStart([inheritedSourceBlock]);
170
+ assert.match(unknownPi.sent[0].content, /<project_context>/, 'unknown inherited provenance receives the full baseline');
171
+ });
172
+ test('root /new leaves baseline assembly to the replacement broker', async () => {
173
+ const oldPersona = { kind: 'general', mode: 'base', lifecycle: 'resident' };
174
+ createNode(node('root-new', {
175
+ parent: null,
176
+ kind: 'developer',
177
+ lifecycle: 'resident',
178
+ persona_ack: oldPersona,
179
+ }));
180
+ const exposure = emptyContextExposureState();
181
+ exposure.preferenceSnapshot = 'ORIGINAL-SNAPSHOT';
182
+ registerExposure(exposureTarget(exposure, 'transcript'), 'original-transcript', 'content');
183
+ saveContextExposureState('root-new', exposure);
184
+ process.env['CRTR_NODE_ID'] = 'root-new';
185
+ const pi = makeFakePi();
186
+ registerCanvasContextIntro(pi);
187
+ await pi.fireSessionStart([], 'new');
188
+ assert.equal(pi.sent.length, 0, 'the provisional in-process branch receives no baseline');
189
+ assert.equal(loadContextExposureState('root-new').preferenceSnapshot, 'ORIGINAL-SNAPSHOT');
190
+ assert.deepEqual(getNode('root-new')?.persona_ack, oldPersona, 'the replacement launch still owns the pending transition');
191
+ });
192
+ test('child /new commits its baseline before ack and the provider gate delivers strict-resume drift', async () => {
193
+ const previousProfile = process.env['CRTR_PROFILE_ID'];
194
+ const userHome = join(home, 'user');
195
+ const cwd = join(userHome, 'work');
196
+ const userMemory = join(userHome, '.crouter', 'memory');
197
+ const projectMemory = join(cwd, '.crouter', 'memory');
198
+ mkdirSync(userMemory, { recursive: true });
199
+ mkdirSync(projectMemory, { recursive: true });
200
+ const preferencePath = join(userMemory, 'new-context-preference.md');
201
+ const knowledgePath = join(userMemory, 'new-context-knowledge.md');
202
+ const workspacePath = join(projectMemory, 'INDEX.md');
203
+ const transitionPath = join(userMemory, 'developer-transition.md');
204
+ writeFileSync(preferencePath, '---\nkind: preference\nwhen-and-why-to-read: New context preference.\nsurfaces:\n - {on: boot, at: content}\n---\nSYSTEM-PREFERENCE-NEW-CONTEXT\n');
205
+ writeFileSync(knowledgePath, '---\nkind: knowledge\nwhen-and-why-to-read: New context knowledge.\nsurfaces:\n - {on: boot, at: content}\n---\nBEARINGS-KNOWLEDGE-NEW-CONTEXT\n');
206
+ writeFileSync(workspacePath, '---\nnamespace: new-context-workspace\nkind: knowledge\nwhen-and-why-to-read: New context workspace.\nsurfaces:\n - {on: workspace-open, at: content}\n---\nBEARINGS-WORKSPACE-NEW-CONTEXT\n');
207
+ writeFileSync(transitionPath, '---\nkind: knowledge\nwhen-and-why-to-read: Developer transition knowledge.\ngate: {kind: developer}\nsurfaces:\n - {on: boot, at: content}\n---\nSTRICT-RESUME-DEVELOPER-DELTA\n');
208
+ delete process.env['CRTR_PROFILE_ID'];
209
+ resetScopeCache();
210
+ clearSessionCache();
211
+ try {
212
+ createNode(node('new-parent', { cwd, lifecycle: 'resident' }));
213
+ const oldPersona = { kind: 'general', mode: 'base', lifecycle: 'terminal' };
214
+ const currentPersona = { kind: 'review', mode: 'base', lifecycle: 'terminal' };
215
+ createNode(node('new-child', {
216
+ cwd,
217
+ parent: 'new-parent',
218
+ kind: currentPersona.kind,
219
+ persona_ack: oldPersona,
220
+ }));
221
+ mkdirSync(join(home, 'nodes', 'new-child'), { recursive: true });
222
+ process.env['CRTR_NODE_ID'] = 'new-child';
223
+ const sessionStartHandlers = [];
224
+ const sent = [];
225
+ const branch = [];
226
+ let ackWhenBearingsWereSent;
227
+ const pi = {
228
+ on(event, handler) {
229
+ if (event === 'session_start')
230
+ sessionStartHandlers.push(handler);
231
+ },
232
+ sendMessage(message) {
233
+ sent.push(message);
234
+ branch.push({ type: 'custom_message', ...message });
235
+ if (message.customType === CONTEXT_INTRO_CUSTOM_TYPE) {
236
+ ackWhenBearingsWereSent = getNode('new-child')?.persona_ack;
237
+ }
238
+ },
239
+ registerMessageRenderer() { },
240
+ };
241
+ registerCanvasContextIntro(pi);
242
+ registerCanvasDocSubstrate(pi);
243
+ const ctx = { sessionManager: { getEntries: () => branch, getBranch: () => branch } };
244
+ for (const handler of sessionStartHandlers)
245
+ await handler({ reason: 'new' }, ctx);
246
+ assert.equal(sent.length, 1, 'the fresh child branch receives one bearings message and no transition delta');
247
+ assert.match(sent[0].content, /BEARINGS-KNOWLEDGE-NEW-CONTEXT/);
248
+ assert.match(sent[0].content, /BEARINGS-WORKSPACE-NEW-CONTEXT/);
249
+ assert.deepEqual(ackWhenBearingsWereSent, oldPersona, 'the old persona remains acknowledged until the baseline is complete');
250
+ assert.deepEqual(getNode('new-child')?.persona_ack, currentPersona);
251
+ const state = loadContextExposureState('new-child');
252
+ assert.match(state.preferenceSnapshot ?? '', /SYSTEM-PREFERENCE-NEW-CONTEXT/);
253
+ assert.deepEqual(state.exposures.get(realpathSync(preferencePath)), { system: 3 });
254
+ assert.deepEqual(state.exposures.get(realpathSync(knowledgePath)), { transcript: 3 });
255
+ assert.deepEqual(state.exposures.get(realpathSync(workspacePath)), { transcript: 3 });
256
+ assert.equal(state.exposures.has(realpathSync(transitionPath)), false);
257
+ const frozenSnapshot = state.preferenceSnapshot;
258
+ updateNode('new-child', { kind: 'developer' });
259
+ for (const handler of sessionStartHandlers)
260
+ await handler({ reason: 'startup' }, ctx);
261
+ assert.equal(sent.length, 1, 'session_start preserves the branch and leaves pending drift to the provider gate');
262
+ assert.deepEqual(getNode('new-child')?.persona_ack, currentPersona);
263
+ const order = [];
264
+ const activeMessages = [];
265
+ let latestTransformed = [];
266
+ let mutateDuringAppend = false;
267
+ const providerSession = {
268
+ agent: {
269
+ state: { messages: activeMessages },
270
+ transformContext: async (messages) => {
271
+ order.push('previous-transform');
272
+ latestTransformed = [...messages];
273
+ return latestTransformed;
274
+ },
275
+ },
276
+ sessionManager: {
277
+ appendCustomMessageEntry(customType, content, display, details) {
278
+ order.push('append-transition');
279
+ branch.push({ type: 'custom_message', customType, content, display, details });
280
+ if (mutateDuringAppend)
281
+ updateNode('new-child', { kind: 'review' });
282
+ },
283
+ },
284
+ };
285
+ installPersonaTransitionGate('new-child', providerSession);
286
+ const transformed = await providerSession.agent.transformContext([{ role: 'user', content: 'continue' }]);
287
+ assert.deepEqual(order, ['previous-transform', 'append-transition'], 'Pi transforms context before crouter appends the transition');
288
+ assert.equal(branch.at(-1)?.customType, 'crtr-persona-transition');
289
+ assert.match(branch.at(-1)?.content ?? '', /STRICT-RESUME-DEVELOPER-DELTA/);
290
+ assert.equal(activeMessages.at(-1)?.['customType'], 'crtr-persona-transition');
291
+ assert.equal(transformed.at(-1)?.['customType'], 'crtr-persona-transition');
292
+ assert.deepEqual(getNode('new-child')?.persona_ack, {
293
+ kind: 'developer',
294
+ mode: 'base',
295
+ lifecycle: 'terminal',
296
+ });
297
+ const resumedState = loadContextExposureState('new-child');
298
+ assert.equal(resumedState.preferenceSnapshot, frozenSnapshot, 'strict resume preserves the frozen system snapshot byte-for-byte');
299
+ assert.deepEqual(resumedState.exposures.get(realpathSync(transitionPath)), { transcript: 3 });
300
+ updateNode('new-child', { mode: 'orchestrator' });
301
+ mutateDuringAppend = true;
302
+ order.length = 0;
303
+ const branchBeforeRace = branch.length;
304
+ const activeBeforeRace = activeMessages.length;
305
+ await providerSession.agent.transformContext([{ role: 'user', content: 'race' }]);
306
+ assert.deepEqual(order, ['previous-transform', 'append-transition', 'append-transition']);
307
+ assert.equal(branch.length, branchBeforeRace + 2, 'the stale transition is followed by current guidance');
308
+ assert.equal(activeMessages.length, activeBeforeRace + 2, 'the active agent transcript keeps both materialized cards');
309
+ assert.equal(latestTransformed.at(-1)?.['customType'], 'crtr-persona-transition', 'the provider context ends at the current persona');
310
+ assert.deepEqual(getNode('new-child')?.persona_ack, {
311
+ kind: 'review',
312
+ mode: 'orchestrator',
313
+ lifecycle: 'terminal',
314
+ }, 'the corrective transition acknowledges the current persona');
315
+ }
316
+ finally {
317
+ resetScopeCache();
318
+ clearSessionCache();
319
+ if (previousProfile === undefined)
320
+ delete process.env['CRTR_PROFILE_ID'];
321
+ else
322
+ process.env['CRTR_PROFILE_ID'] = previousProfile;
323
+ }
116
324
  });
117
325
  test('resume idempotency uses the exact details stamp independent of block text', async () => {
118
326
  createNode(node('stampD'));
@@ -62,12 +62,20 @@ async function waitForRequestMentioning(id) {
62
62
  before(async () => {
63
63
  home = mkdtempSync(join(tmpdir(), 'crtr-goal-capture-'));
64
64
  process.env['CRTR_HOME'] = home;
65
- // A stub daemon: it records the request and hangs up, so the naming path fails
66
- // fast (and silently, as it must) without any model call.
65
+ // A stub daemon: it records the request and answers a typed error, so the
66
+ // naming path fails fast (and silently, as it must) without any model call.
67
+ // It must ANSWER rather than hang up — the api client reads a mid-request
68
+ // hangup as a daemon handover and rides it out with a 10s /healthz poll,
69
+ // which kept this process alive for ten seconds after the test had passed.
67
70
  socket = createServer((connection) => {
68
71
  connection.on('data', (chunk) => {
69
72
  requests.push(chunk.toString('utf8'));
70
- connection.destroy();
73
+ const body = JSON.stringify({ error: { code: 'internal', message: 'stub daemon' } });
74
+ connection.end('HTTP/1.1 500 Internal Server Error\r\n'
75
+ + 'Content-Type: application/json\r\n'
76
+ + `Content-Length: ${Buffer.byteLength(body)}\r\n`
77
+ + 'Connection: close\r\n\r\n'
78
+ + body);
71
79
  });
72
80
  });
73
81
  await new Promise((resolve) => socket.listen(apiSocketPath(), resolve));
@@ -1,5 +1,5 @@
1
1
  import { CONTEXT_INTRO_CUSTOM_TYPE } from '../shared/generated-context.js';
2
- import { type InjectedDocs } from '../core/substrate/injected-store.js';
2
+ import { type ExposureTarget } from '../core/substrate/injected-store.js';
3
3
  /** The `customType` stamped on the injected session message. Used both to write
4
4
  * the entry and to detect it on resume (the idempotency guard). Defined beside
5
5
  * the block builder in `core/runtime/bearings.ts` because the warm-pool claim's
@@ -69,7 +69,7 @@ interface PiLike {
69
69
  }
70
70
  /** Build the full bearings baseline for a conversation without inherited
71
71
  * crouter context. Exported for testing. */
72
- export declare function buildContextIntro(nodeId: string, seen?: InjectedDocs): Promise<string>;
72
+ export declare function buildContextIntro(nodeId: string, target?: ExposureTarget): Promise<string>;
73
73
  /** Build the compact node-scoped update for a fork whose copied branch already
74
74
  * carries a full crouter baseline. Exported for testing. */
75
75
  export declare function buildForkContextIntro(nodeId: string): Promise<string>;
@@ -82,8 +82,6 @@ export declare function buildForkContextIntro(nodeId: string): Promise<string>;
82
82
  export declare function renderContextMessage(message: RenderedMessageLike, options: {
83
83
  expanded?: boolean;
84
84
  }, theme: ThemeLike): ComponentLike;
85
- /** Register the context-intro preamble. Fresh branches receive one full
86
- * baseline, forked crouter branches receive one compact update, and resumes
87
- * whose active branch already carries this node's stamp receive nothing. */
85
+ /** Register new-context baseline assembly; a strict resume changes nothing. */
88
86
  export declare function registerCanvasContextIntro(pi: PiLike): void;
89
87
  export default registerCanvasContextIntro;
@@ -3,17 +3,13 @@
3
3
  // Loaded into every canvas node's pi process via the node's launch.extensions
4
4
  // list. INERT when CRTR_NODE_ID is absent (plain pi session or legacy job agent).
5
5
  //
6
- // On `session_start` — before the node's first real user message — this injects
7
- // one persisted <crtr-context> custom message. A fresh conversation receives the
8
- // full baseline as its first entry. A fork receives a compact update after the
9
- // copied history and immediately before its new mandate. The prose lives in
10
- // core/runtime/bearings.ts.
6
+ // At session_start, before the first response, this assembles the context-owned
7
+ // memory state. A fresh branch receives full bearings, while a copied crouter
8
+ // branch with known transcript provenance receives compact node bearings.
11
9
  //
12
- // BRANCH-AWARE and IDEMPOTENT: the active branch receives one bearings message
13
- // stamped for this node. A resume finds that stamp and injects nothing. A new
14
- // cycle has no current-branch stamp and receives a fresh full baseline. A fork
15
- // whose active branch carries another node's bearings receives only a compact
16
- // node-scoped update; a fork of a non-crouter session receives the full baseline.
10
+ // The active branch owns the idempotency stamp. A new cycle has no current-node
11
+ // stamp and receives a new baseline. The preference snapshot and persona
12
+ // acknowledgment persist only after Pi materializes that baseline.
17
13
  //
18
14
  // COLLAPSED BY DEFAULT: a `registerMessageRenderer` keyed to our customType
19
15
  // renders the block as a single one-line stub; the full body only appears when
@@ -28,9 +24,11 @@
28
24
  // crouter's own tsc build without a dep on the pi packages.
29
25
  import { CONTEXT_INTRO_CUSTOM_TYPE, formatCard, parseCard } from '../shared/generated-context.js';
30
26
  import { brokerExtensionState } from '../core/runtime/broker/daemon-ops.js';
31
- import { buildContextBearingsFromState, buildForkBearingsFromState, buildProjectContextBlockForBroker } from '../core/runtime/broker-extension-render.js';
27
+ import { buildContextBearingsFromState, buildForkBearingsFromState, buildProjectContextBlockForBroker, renderPreferencesFromState, } from '../core/runtime/broker-extension-render.js';
28
+ import { acknowledgePersonaBaseline } from '../core/runtime/broker-persona-guidance.js';
32
29
  import { situationalContextEnvelope } from '../core/runtime/situational-context.js';
33
- import { saveInjectedDocs, sharedInjectedDocs } from '../core/substrate/injected-store.js';
30
+ import { clearContextExposureState, cloneTranscriptExposureState, emptyContextExposureState, exposureTarget, freezePreferenceSnapshot, hasTranscriptExposure, loadContextExposureState, replaceContextExposureState, saveContextExposureState, sharedContextExposureState, } from '../core/substrate/injected-store.js';
31
+ import { clearSessionCache } from '../core/substrate/session-cache.js';
34
32
  import { truncateToWidth } from './truncate.js';
35
33
  /** The `customType` stamped on the injected session message. Used both to write
36
34
  * the entry and to detect it on resume (the idempotency guard). Defined beside
@@ -42,9 +40,9 @@ export { CONTEXT_INTRO_CUSTOM_TYPE };
42
40
  // ---------------------------------------------------------------------------
43
41
  /** Build the full bearings baseline for a conversation without inherited
44
42
  * crouter context. Exported for testing. */
45
- export async function buildContextIntro(nodeId, seen) {
43
+ export async function buildContextIntro(nodeId, target) {
46
44
  const state = await brokerExtensionState(nodeId);
47
- return buildContextBearingsFromState(state, buildProjectContextBlockForBroker(state.node.cwd), situationalContextEnvelope(nodeId) ?? '', seen);
45
+ return buildContextBearingsFromState(state, buildProjectContextBlockForBroker(state.node.cwd), situationalContextEnvelope(nodeId) ?? '', target);
48
46
  }
49
47
  /** Build the compact node-scoped update for a fork whose copied branch already
50
48
  * carries a full crouter baseline. Exported for testing. */
@@ -79,6 +77,11 @@ function entryText(e) {
79
77
  }
80
78
  return '';
81
79
  }
80
+ function activeBranch(ctx) {
81
+ return typeof ctx.sessionManager.getBranch === 'function'
82
+ ? ctx.sessionManager.getBranch()
83
+ : ctx.sessionManager.getEntries();
84
+ }
82
85
  /** Hard-wrap a single logical line to `width` columns (content carries no ANSI).
83
86
  * Code-point aware so wide-string slicing never splits a surrogate pair; the
84
87
  * bearings prose is plain text, so code-point count == visible columns. */
@@ -127,60 +130,61 @@ export function renderContextMessage(message, options, theme) {
127
130
  // ---------------------------------------------------------------------------
128
131
  // Extension
129
132
  // ---------------------------------------------------------------------------
130
- /** Register the context-intro preamble. Fresh branches receive one full
131
- * baseline, forked crouter branches receive one compact update, and resumes
132
- * whose active branch already carries this node's stamp receive nothing. */
133
+ /** Register new-context baseline assembly; a strict resume changes nothing. */
133
134
  export function registerCanvasContextIntro(pi) {
134
135
  // Collapse the block to a one-liner until the user expands tool output (Ctrl+O).
135
136
  // Harmless to register outside TUI mode (it's only consulted while rendering).
136
137
  pi.registerMessageRenderer(CONTEXT_INTRO_CUSTOM_TYPE, renderContextMessage);
137
- pi.on('session_start', async (_event, ctx) => {
138
+ pi.on('session_start', async (event, ctx) => {
138
139
  const nodeId = process.env['CRTR_NODE_ID'];
139
140
  if (nodeId === undefined || nodeId.trim() === '')
140
141
  return;
142
+ const reason = event?.reason;
141
143
  // Warm spares have no claimant yet. Their claim path delivers the full
142
144
  // baseline after assigning the real persona, profile, and working directory.
143
- let state;
144
- try {
145
- state = await brokerExtensionState(nodeId);
146
- }
147
- catch {
148
- return;
149
- }
145
+ const state = await brokerExtensionState(nodeId);
150
146
  if (state.warm_spare)
151
147
  return;
148
+ // A root's in-process /new branch is provisional: stophook has already asked
149
+ // crtrd to relaunch it. The replacement broker owns the new baseline.
150
+ if (reason === 'new' && state.node.parent === null)
151
+ return;
152
+ if (reason === 'new') {
153
+ clearContextExposureState(nodeId);
154
+ clearSessionCache();
155
+ }
152
156
  // The active branch, rather than the whole session file, is the context the
153
157
  // next turn sees. A new cycle therefore receives its own current baseline.
154
- const scan = typeof ctx.sessionManager.getBranch === 'function'
155
- ? ctx.sessionManager.getBranch()
156
- : ctx.sessionManager.getEntries();
157
- const contextEntries = scan.filter((e) => e.type === 'custom_message' && e.customType === CONTEXT_INTRO_CUSTOM_TYPE);
158
+ const contextEntries = activeBranch(ctx).filter((e) => e.type === 'custom_message' && e.customType === CONTEXT_INTRO_CUSTOM_TYPE);
158
159
  const ours = contextEntries.some((e) => e.details?.nodeId === nodeId || entryText(e).includes(nodeId));
159
160
  if (ours)
160
161
  return;
161
- // A copied crouter branch already carries the shared baseline. Append only
162
- // this fork's new node-scoped facts. A non-crouter branch has no such entry.
163
- const inherited = contextEntries.length > 0;
164
- const forked = state.node.fork_from !== null;
165
- // The baseline branch renders workspace-open docs, so it threads the
166
- // PROCESS-SHARED dedup set: the on-read hook (canvas-doc-substrate) holds
167
- // the same Set instance, so a front door delivered here never re-delivers
168
- // on a later read beneath its store. The fork branch renders no workspace
169
- // docs and needs no set.
170
- const seen = inherited && forked ? undefined : sharedInjectedDocs(nodeId);
171
- const content = inherited && forked
162
+ const persisted = loadContextExposureState(nodeId);
163
+ const compact = contextEntries.length > 0 && hasTranscriptExposure(persisted);
164
+ const next = compact
165
+ ? cloneTranscriptExposureState(persisted)
166
+ : emptyContextExposureState();
167
+ const content = compact
172
168
  ? buildForkBearingsFromState(state, situationalContextEnvelope(nodeId) ?? '')
173
- : buildContextBearingsFromState(state, buildProjectContextBlockForBroker(state.node.cwd), situationalContextEnvelope(nodeId) ?? '', seen);
169
+ : buildContextBearingsFromState(state, buildProjectContextBlockForBroker(state.node.cwd), situationalContextEnvelope(nodeId) ?? '', exposureTarget(next, 'transcript'));
170
+ freezePreferenceSnapshot(next, renderPreferencesFromState(state, exposureTarget(next, 'system')));
171
+ const messageContent = formatCard('bearings', {}, content);
174
172
  pi.sendMessage({
175
173
  customType: CONTEXT_INTRO_CUSTOM_TYPE,
176
- content: formatCard('bearings', {}, content),
174
+ content: messageContent,
177
175
  display: true,
178
176
  details: { nodeId },
179
177
  });
180
- // Persist what the baseline delivered so the dedup survives a later
181
- // dormancy → revive(resume) into a fresh process.
182
- if (seen !== undefined)
183
- saveInjectedDocs(nodeId, seen);
178
+ const delivered = activeBranch(ctx).some((entry) => entry.type === 'custom_message'
179
+ && entry.customType === CONTEXT_INTRO_CUSTOM_TYPE
180
+ && entry.details?.nodeId === nodeId
181
+ && entryText(entry) === messageContent);
182
+ if (!delivered)
183
+ throw new Error(`Pi did not materialize the context baseline for ${nodeId}`);
184
+ const live = sharedContextExposureState(nodeId);
185
+ replaceContextExposureState(live, next);
186
+ saveContextExposureState(nodeId, live);
187
+ await acknowledgePersonaBaseline(state);
184
188
  });
185
189
  }
186
190
  export default registerCanvasContextIntro;
@@ -31,15 +31,8 @@ type ToolResultResultLike = {
31
31
  } | void;
32
32
  interface PiLike {
33
33
  on(event: 'before_agent_start', handler: (event: BeforeAgentStartEventLike) => BeforeAgentStartResultLike | void | Promise<BeforeAgentStartResultLike | void>): void;
34
- on(event: 'session_start', handler: (event: unknown, ctx: unknown) => void): void;
34
+ on(event: 'session_start', handler: (event: unknown, ctx: unknown) => void | Promise<void>): void;
35
35
  on(event: 'tool_result', handler: (event: ToolResultEventLike, ctx: ToolResultCtxLike) => ToolResultResultLike | Promise<ToolResultResultLike>): void;
36
36
  }
37
- /**
38
- * Register the document substrate's pi hooks on `pi`.
39
- *
40
- * Returns immediately (inert) when CRTR_NODE_ID is absent — a non-canvas pi
41
- * session gets no hooks. Each handler is independently wrapped so a failure
42
- * degrades to a no-op (boot proceeds, the read returns) rather than bricking.
43
- */
44
37
  export declare function registerCanvasDocSubstrate(pi: PiLike): void;
45
38
  export default registerCanvasDocSubstrate;