@north-light/crouter 0.3.207 → 0.3.209

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 (172) hide show
  1. package/dist/api/client.d.ts +3 -1
  2. package/dist/api/client.js +4 -0
  3. package/dist/api/dto/broker-ops.d.ts +2 -12
  4. package/dist/api/dto/nodes.d.ts +16 -0
  5. package/dist/api/routes.d.ts +1 -0
  6. package/dist/api/routes.js +1 -0
  7. package/dist/builtin-memory/00-runtime-base.md +3 -2
  8. package/dist/builtin-memory/01-spine/00-has-manager.md +3 -2
  9. package/dist/builtin-memory/01-spine/01-no-manager.md +3 -2
  10. package/dist/builtin-memory/02-lifecycle/00-terminal.md +3 -2
  11. package/dist/builtin-memory/02-lifecycle/01-resident.md +3 -2
  12. package/dist/builtin-memory/04-base-worker.md +3 -2
  13. package/dist/builtin-memory/04-orchestration-kernel.md +3 -2
  14. package/dist/builtin-memory/05-kinds/advisor/00-base.md +3 -2
  15. package/dist/builtin-memory/05-kinds/advisor/01-orchestrator.md +3 -2
  16. package/dist/builtin-memory/05-kinds/design/00-base.md +3 -2
  17. package/dist/builtin-memory/05-kinds/design/01-orchestrator.md +3 -2
  18. package/dist/builtin-memory/05-kinds/developer/00-base.md +3 -2
  19. package/dist/builtin-memory/05-kinds/developer/01-orchestrator.md +3 -2
  20. package/dist/builtin-memory/05-kinds/explore/00-base.md +3 -2
  21. package/dist/builtin-memory/05-kinds/explore/01-orchestrator.md +3 -2
  22. package/dist/builtin-memory/05-kinds/general/00-base.md +3 -2
  23. package/dist/builtin-memory/05-kinds/general/01-orchestrator.md +3 -2
  24. package/dist/builtin-memory/05-kinds/plan/00-base.md +3 -2
  25. package/dist/builtin-memory/05-kinds/plan/01-orchestrator.md +3 -2
  26. package/dist/builtin-memory/05-kinds/plan/reviewers/00-base.md +3 -2
  27. package/dist/builtin-memory/05-kinds/plan/reviewers/architecture-fit.md +3 -2
  28. package/dist/builtin-memory/05-kinds/plan/reviewers/code-smells.md +3 -2
  29. package/dist/builtin-memory/05-kinds/plan/reviewers/pattern-consistency.md +3 -2
  30. package/dist/builtin-memory/05-kinds/plan/reviewers/requirements-coverage.md +3 -2
  31. package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +3 -2
  32. package/dist/builtin-memory/05-kinds/review/00-base.md +3 -2
  33. package/dist/builtin-memory/05-kinds/review/01-orchestrator.md +3 -2
  34. package/dist/builtin-memory/05-kinds/review/companion/00-base.md +3 -2
  35. package/dist/builtin-memory/05-kinds/spec/00-base.md +3 -2
  36. package/dist/builtin-memory/05-kinds/spec/01-orchestrator.md +3 -2
  37. package/dist/builtin-memory/05-kinds/spec/requirements.md +3 -2
  38. package/dist/builtin-memory/advisor/council.md +0 -2
  39. package/dist/builtin-memory/design.md +3 -2
  40. package/dist/builtin-memory/development.md +3 -2
  41. package/dist/builtin-memory/insights/capture.md +1 -3
  42. package/dist/builtin-memory/insights/init.md +1 -3
  43. package/dist/builtin-memory/insights/listen.md +3 -2
  44. package/dist/builtin-memory/internal/INDEX.md +5 -4
  45. package/dist/builtin-memory/internal/agent-shaping.md +5 -4
  46. package/dist/builtin-memory/internal/examples/INDEX.md +3 -2
  47. package/dist/builtin-memory/internal/examples/imessage-assistant.md +3 -2
  48. package/dist/builtin-memory/internal/marketplaces.md +3 -2
  49. package/dist/builtin-memory/internal/memory-loading.md +31 -20
  50. package/dist/builtin-memory/internal/nodes-and-canvas.md +3 -2
  51. package/dist/builtin-memory/internal/plugins.md +7 -6
  52. package/dist/builtin-memory/internal/storage-tiers.md +3 -2
  53. package/dist/builtin-memory/plan/roadmap.md +3 -2
  54. package/dist/builtin-memory/spec/guide.md +0 -2
  55. package/dist/builtin-memory/spec/requirements.md +0 -2
  56. package/dist/builtin-memory/spec/roadmap.md +3 -2
  57. package/dist/builtin-memory/testing.md +1 -3
  58. package/dist/builtin-memory/wedged-child-on-runaway-bash.md +3 -2
  59. package/dist/builtin-pi-packages/pi-crtr-extensions/README.md +1 -1
  60. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/frontmatter-rules/index.ts +3 -3
  61. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/memory-slash-commands.ts +1 -4
  62. package/dist/clients/attach/__tests__/group-activity.test.js +8 -1
  63. package/dist/clients/attach/render/card-presentation.d.ts +31 -0
  64. package/dist/clients/attach/render/card-presentation.js +163 -0
  65. package/dist/clients/attach/render/chat-view.d.ts +0 -2
  66. package/dist/clients/attach/render/chat-view.js +12 -23
  67. package/dist/clients/attach/render/context-message.d.ts +5 -9
  68. package/dist/clients/attach/render/context-message.js +20 -43
  69. package/dist/clients/attach/render/group-activity.d.ts +28 -4
  70. package/dist/clients/attach/render/group-activity.js +65 -11
  71. package/dist/clients/attach/render/group-recap.d.ts +3 -0
  72. package/dist/clients/attach/render/group-recap.js +17 -0
  73. package/dist/clients/attach/viewer.js +516 -516
  74. package/dist/commands/memory/delete.js +3 -18
  75. package/dist/commands/memory/edit.js +23 -30
  76. package/dist/commands/memory/history.js +1 -1
  77. package/dist/commands/memory/lint.d.ts +7 -8
  78. package/dist/commands/memory/lint.js +178 -132
  79. package/dist/commands/memory/list.d.ts +0 -1
  80. package/dist/commands/memory/list.js +2 -12
  81. package/dist/commands/memory/move.d.ts +1 -0
  82. package/dist/commands/memory/move.js +195 -0
  83. package/dist/commands/memory/read.js +135 -141
  84. package/dist/commands/memory/shared.d.ts +18 -17
  85. package/dist/commands/memory/shared.js +93 -39
  86. package/dist/commands/memory/write.js +23 -33
  87. package/dist/commands/memory.js +5 -4
  88. package/dist/commands/pkg/browse/catalog.js +2 -4
  89. package/dist/commands/pkg/browse/doc-view.js +17 -11
  90. package/dist/commands/pkg/browse/model.d.ts +7 -9
  91. package/dist/commands/sys/migrate.d.ts +1 -0
  92. package/dist/commands/sys/migrate.js +106 -0
  93. package/dist/commands/sys/sync-deps.js +5 -10
  94. package/dist/commands/sys/sync-project-guidance.js +36 -16
  95. package/dist/commands/sys/sync-skills.js +8 -4
  96. package/dist/commands/sys.js +3 -2
  97. package/dist/core/__tests__/canvas-inbox-watcher.test.js +1 -1
  98. package/dist/core/__tests__/helpers/broker-clients.d.ts +3 -1
  99. package/dist/core/__tests__/helpers/broker-clients.js +7 -2
  100. package/dist/core/__tests__/inline-memory-refs.test.js +8 -5
  101. package/dist/core/__tests__/memory-resolver-precedence.test.js +5 -4
  102. package/dist/core/__tests__/nested-store-discovery.test.js +1 -3
  103. package/dist/core/__tests__/on-read-crouter-home-fence.test.js +3 -3
  104. package/dist/core/__tests__/on-read-dedup-resume.test.js +12 -12
  105. package/dist/core/__tests__/on-read-nested-store.test.js +23 -20
  106. package/dist/core/__tests__/seam/broker-attach-multiclient.test.js +9 -0
  107. package/dist/core/__tests__/serial/broker-snapshot-history.test.js +52 -1
  108. package/dist/core/__tests__/serial/flagship-lifecycle.test.js +1 -1
  109. package/dist/core/canvas/db.d.ts +3 -1
  110. package/dist/core/canvas/db.js +12 -2
  111. package/dist/core/memory/history.d.ts +4 -1
  112. package/dist/core/memory/history.js +1 -0
  113. package/dist/core/memory/inline-ref-inventory.d.ts +5 -4
  114. package/dist/core/memory/inline-ref-inventory.js +23 -19
  115. package/dist/core/memory-resolver.d.ts +4 -4
  116. package/dist/core/memory-resolver.js +16 -43
  117. package/dist/core/runtime/bearings.d.ts +4 -3
  118. package/dist/core/runtime/bearings.js +4 -4
  119. package/dist/core/runtime/broker/fault-retry.js +2 -7
  120. package/dist/core/runtime/broker/frame-dispatch.js +1 -1
  121. package/dist/core/runtime/broker/tool-groups.js +7 -6
  122. package/dist/core/runtime/broker-extension-render.d.ts +3 -2
  123. package/dist/core/runtime/broker-extension-render.js +3 -3
  124. package/dist/core/runtime/broker.js +4 -4
  125. package/dist/core/runtime/kickoff.js +4 -10
  126. package/dist/core/runtime/memory.js +2 -3
  127. package/dist/core/runtime/node-read.js +7 -1
  128. package/dist/core/runtime/stop-guard.js +3 -3
  129. package/dist/core/substrate/index.d.ts +7 -4
  130. package/dist/core/substrate/index.js +6 -4
  131. package/dist/core/substrate/injected-store.d.ts +24 -12
  132. package/dist/core/substrate/injected-store.js +80 -33
  133. package/dist/core/substrate/listings.d.ts +21 -0
  134. package/dist/core/substrate/listings.js +88 -0
  135. package/dist/core/substrate/on-read-node.d.ts +5 -5
  136. package/dist/core/substrate/on-read-node.js +4 -5
  137. package/dist/core/substrate/on-read.d.ts +25 -4
  138. package/dist/core/substrate/on-read.js +81 -102
  139. package/dist/core/substrate/render-node.d.ts +5 -2
  140. package/dist/core/substrate/render-node.js +5 -3
  141. package/dist/core/substrate/render.d.ts +9 -8
  142. package/dist/core/substrate/render.js +104 -96
  143. package/dist/core/substrate/schema.d.ts +34 -18
  144. package/dist/core/substrate/schema.js +75 -32
  145. package/dist/core/substrate/surface-match.d.ts +32 -0
  146. package/dist/core/substrate/surface-match.js +179 -0
  147. package/dist/daemon/api/handlers/nodes.js +9 -0
  148. package/dist/daemon/review/comment-notify.js +1 -8
  149. package/dist/daemon/review/deliver.js +1 -1
  150. package/dist/daemon/review/finish.js +1 -1
  151. package/dist/migrations/001-surfaces-frontmatter.d.ts +2 -0
  152. package/dist/migrations/001-surfaces-frontmatter.js +276 -0
  153. package/dist/migrations/convergent.d.ts +31 -0
  154. package/dist/migrations/convergent.js +71 -0
  155. package/dist/migrations/registry.d.ts +2 -0
  156. package/dist/migrations/registry.js +19 -0
  157. package/dist/migrations/types.d.ts +40 -0
  158. package/dist/migrations/types.js +11 -0
  159. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +1 -1
  160. package/dist/pi-extensions/canvas-context-intro.d.ts +2 -1
  161. package/dist/pi-extensions/canvas-doc-substrate.d.ts +2 -1
  162. package/dist/pi-extensions/canvas-doc-substrate.js +57 -34
  163. package/dist/pi-extensions/canvas-review-boundary.js +1 -1
  164. package/dist/shared/__tests__/generated-context-grammar.test.js +19 -22
  165. package/dist/shared/generated-context.d.ts +0 -18
  166. package/dist/shared/generated-context.js +16 -120
  167. package/dist/shared/tool-groups.d.ts +1 -0
  168. package/dist/shared/tool-groups.js +7 -0
  169. package/package.json +1 -1
  170. package/runtime.lock.json +2 -2
  171. package/dist/core/substrate/ceiling.d.ts +0 -17
  172. package/dist/core/substrate/ceiling.js +0 -67
@@ -4,13 +4,15 @@
4
4
  * rule of its own. */
5
5
  export declare const HISTORY_DIR = ".history";
6
6
  /** File operations only — deliberately not a review lifecycle. */
7
- export type HistoryOp = 'create' | 'edit' | 'delete';
7
+ export type HistoryOp = 'create' | 'edit' | 'delete' | 'move';
8
8
  export interface HistoryRecord {
9
9
  op: HistoryOp;
10
10
  at: string;
11
11
  /** Authoring node id; absent for a human at a bare CLI. */
12
12
  node?: string;
13
13
  cwd: string;
14
+ /** The canonical name this document answered to before a `move`; absent otherwise. */
15
+ from?: string;
14
16
  /** Why this revision happened. Required on `edit`, absent otherwise. */
15
17
  rationale?: string;
16
18
  /** True when the body was declared a verbatim payload from the principal. */
@@ -29,6 +31,7 @@ export declare function historyLogPathFor(memoryRoot: string, docPath: string):
29
31
  * serialized key order — the on-disk shape consumers read. */
30
32
  export declare function buildHistoryRecord(input: {
31
33
  op: HistoryOp;
34
+ from?: string;
32
35
  rationale?: string;
33
36
  verbatim?: boolean;
34
37
  before: string;
@@ -40,6 +40,7 @@ export function buildHistoryRecord(input) {
40
40
  at: new Date().toISOString(),
41
41
  ...(env['CRTR_NODE_ID'] ? { node: env['CRTR_NODE_ID'] } : {}),
42
42
  cwd: env['CRTR_NODE_CWD'] ?? process.cwd(),
43
+ ...(input.from !== undefined ? { from: input.from } : {}),
43
44
  ...(input.rationale !== undefined ? { rationale: input.rationale } : {}),
44
45
  ...(input.verbatim === true ? { verbatim: true } : {}),
45
46
  before: input.before,
@@ -2,10 +2,11 @@ import type { RefMeta } from '../runtime/broker-protocol.js';
2
2
  /**
3
3
  * Build the inline memory-reference inventory: every referenceable document
4
4
  * in this node's corpus, precedence-deduped first-wins by canonical name,
5
- * both kinds, no gate/rung/`slash` filtering, INDEX docs folded to their bare
6
- * directory name. Returns display metadata (`refs`, stably sorted by winning
7
- * scope then name) plus the resolution set (`names`) that both the broker's
8
- * per-submission resolver and `buildGuidance` test tokens against.
5
+ * both kinds, no gate/rung/`slash` filtering. Returns display metadata
6
+ * (`refs`, stably sorted by winning scope then name) plus the resolution set
7
+ * (`names` — doc names plus every proper directory prefix, because a bare-dir
8
+ * ref is a legal listing link) that both the broker's per-submission resolver
9
+ * and `buildGuidance` test tokens against.
9
10
  */
10
11
  export declare function buildRefInventory(): {
11
12
  refs: RefMeta[];
@@ -18,8 +18,10 @@
18
18
  // automatic boot/on-read surfacing only (spec §7.1), and `slash` governs
19
19
  // command registration only (spec §4.3). An explicit human reference
20
20
  // outranks both.
21
- // • A directory's INDEX doc folds to its bare directory name (`taste`, not
22
- // `taste/INDEX`), matching what `crtr memory read taste` accepts.
21
+ // • Directory names are referenceable too: a bare-dir `[[ref]]` opens the
22
+ // directory's listing through `crtr memory read <dir>`, so every proper
23
+ // name-prefix of a winning doc joins the resolution set — resolution
24
+ // only, never a display row.
23
25
  // • A malformed doc (no valid `kind`) is already dropped by
24
26
  // `parseSubstrateDoc` returning null, so it never reaches the winners map.
25
27
  //
@@ -29,7 +31,6 @@
29
31
  // cache it reads through.
30
32
  import { listAllMemoryDocs } from '../memory-resolver.js';
31
33
  import { parseSubstrateDoc } from '../substrate/schema.js';
32
- import { displayName } from '../substrate/ceiling.js';
33
34
  import { cachedSubstrateDocsInclusive } from '../substrate/session-cache.js';
34
35
  // Display-sort weight matching resolution precedence (node > project >
35
36
  // profile > user > builtin) — display-only; resolution itself is
@@ -44,26 +45,21 @@ const SCOPE_SORT_RANK = {
44
45
  /**
45
46
  * Build the inline memory-reference inventory: every referenceable document
46
47
  * in this node's corpus, precedence-deduped first-wins by canonical name,
47
- * both kinds, no gate/rung/`slash` filtering, INDEX docs folded to their bare
48
- * directory name. Returns display metadata (`refs`, stably sorted by winning
49
- * scope then name) plus the resolution set (`names`) that both the broker's
50
- * per-submission resolver and `buildGuidance` test tokens against.
48
+ * both kinds, no gate/rung/`slash` filtering. Returns display metadata
49
+ * (`refs`, stably sorted by winning scope then name) plus the resolution set
50
+ * (`names` — doc names plus every proper directory prefix, because a bare-dir
51
+ * ref is a legal listing link) that both the broker's per-submission resolver
52
+ * and `buildGuidance` test tokens against.
51
53
  */
52
54
  export function buildRefInventory() {
53
55
  const docs = cachedSubstrateDocsInclusive(listAllMemoryDocs, parseSubstrateDoc);
54
- // First-wins by canonical (INDEX-folded) name: docs arrive already in
55
- // precedence order (listAllMemoryDocs' nearest-first source ordering), so
56
- // the first doc seen for a name is the winner.
56
+ // First-wins by canonical name: docs arrive already in precedence order
57
+ // (listAllMemoryDocs' nearest-first source ordering), so the first doc seen
58
+ // for a name is the winner.
57
59
  const winners = new Map();
58
60
  for (const doc of docs) {
59
- const name = displayName(doc.name);
60
- // A root INDEX folds to '' (no bare-directory token exists for it) and
61
- // has no other nonempty canonical identity in this corpus, so it has
62
- // no inline token to offer — omit it rather than expose `name: ''`.
63
- if (name === '')
64
- continue;
65
- if (!winners.has(name))
66
- winners.set(name, doc);
61
+ if (!winners.has(doc.name))
62
+ winners.set(doc.name, doc);
67
63
  }
68
64
  const refs = [];
69
65
  for (const [name, doc] of winners) {
@@ -75,5 +71,13 @@ export function buildRefInventory() {
75
71
  return scopeDelta;
76
72
  return a.name.localeCompare(b.name);
77
73
  });
78
- return { refs, names: new Set(refs.map((r) => r.name)) };
74
+ const names = new Set(refs.map((r) => r.name));
75
+ // Directory names resolve as listing links, so they join the set — but not
76
+ // the display rows: a dir has no kind/short-form to show.
77
+ for (const name of winners.keys()) {
78
+ const segs = name.split('/');
79
+ for (let i = 1; i < segs.length; i++)
80
+ names.add(segs.slice(0, i).join('/'));
81
+ }
82
+ return { refs, names };
79
83
  }
@@ -5,8 +5,8 @@ import { type InstalledPlugin, type Scope } from '../types.js';
5
5
  * stack nearest > ... > profile > user > builtin) for strong matches: (1) exact
6
6
  * substrate identity (`doc.name === query` — the explicit frontmatter `name`,
7
7
  * or its path-derived fallback), then (2) direct `memory/<name>.md` physical
8
- * path (plus the bare-dir/bare-plugin-name → `INDEX.md` convenience neither
9
- * identity nor a literal path expresses). Only when no source has a strong
8
+ * path. A directory name resolves to NO doc — the read leaf answers it with
9
+ * the directory's listing. Only when no source has a strong
10
10
  * match does a second nearest-first pass try bare leaf-name fallback (final
11
11
  * path segment only). Thus scope precedence resolves competing strong matches,
12
12
  * while a nearer nested doc's coincidental leaf can never shadow a farther
@@ -123,8 +123,8 @@ export declare function listAllMemoryDocs(scope?: MemoryScope, quiet?: boolean,
123
123
  * tree segment for segment with `.jsonl` in place of `.md`. Reuses the doc
124
124
  * resolution rules so a log outlives its doc under the same name the doc had:
125
125
  * numeric prefixes stay prefix-blind (`00-topic.md` logged at
126
- * `.history/00-topic.jsonl` still answers to `topic`) and a bare directory
127
- * name falls back to its INDEX log. Returns null when nothing resolves. */
126
+ * `.history/00-topic.jsonl` still answers to `topic`). Returns null when
127
+ * nothing resolves. */
128
128
  export declare function resolveHistoryLogPath(historyDir: string, segments: string[]): string | null;
129
129
  export interface MemoryDocSnapshot {
130
130
  /** Every document in default scope precedence order, loaded once. */
@@ -305,8 +305,7 @@ function matchNormalizedChild(dir, segment, want, ext = '.md') {
305
305
  /** Resolve a NORMALIZED (numeric-prefix-stripped) segment path against a
306
306
  * physical memory dir: walk intermediate segments matching each as a
307
307
  * directory by normalized name, then resolve the final segment as either a
308
- * `.md` file or a directory (the bare-dir -> INDEX.md fallback needs the dir
309
- * match even when a same-named file also exists). Either half is null when
308
+ * `.md` file or a directory. Either half is null when
310
309
  * the segment path does not resolve that way. This is what makes
311
310
  * `00-runtime-base.md` findable as `runtime-base` and `01-spine/00-has-manager`
312
311
  * findable as `spine/has-manager` — the physical path keeps its pins, only
@@ -329,28 +328,16 @@ function resolveNormalizedPath(baseDir, segments, ext = '.md') {
329
328
  * tree segment for segment with `.jsonl` in place of `.md`. Reuses the doc
330
329
  * resolution rules so a log outlives its doc under the same name the doc had:
331
330
  * numeric prefixes stay prefix-blind (`00-topic.md` logged at
332
- * `.history/00-topic.jsonl` still answers to `topic`) and a bare directory
333
- * name falls back to its INDEX log. Returns null when nothing resolves. */
331
+ * `.history/00-topic.jsonl` still answers to `topic`). Returns null when
332
+ * nothing resolves. */
334
333
  export function resolveHistoryLogPath(historyDir, segments) {
335
- const { filePath, dirPath } = resolveNormalizedPath(historyDir, segments, '.jsonl');
336
- if (filePath)
337
- return filePath;
338
- if (dirPath) {
339
- const index = matchNormalizedChild(dirPath, 'INDEX', 'file', '.jsonl');
340
- if (index)
341
- return index;
342
- }
343
- return null;
334
+ return resolveNormalizedPath(historyDir, segments, '.jsonl').filePath;
344
335
  }
345
336
  /** Direct full-path lookup of memory/<name>.md within ONE source. Returns that
346
337
  * source's single hit, or undefined — a source can produce at most one direct
347
338
  * match (native wins over plugin within the source, and at most one plugin's
348
- * name can match).
349
- *
350
- * A directory INDEX is the cleaner contract: when `<name>.md` is absent but
351
- * `<name>/INDEX.md` exists, the bare dir name (`taste`) resolves to the dir's
352
- * INDEX doc — carrying the dir name as its identity. (`taste/INDEX` still
353
- * resolves directly as the file path.)
339
+ * name can match). A directory name matches NO doc here: the read leaf
340
+ * answers a directory with its listing, never a stand-in document.
354
341
  *
355
342
  * `name`'s segments are already normalized (a caller-supplied identifier is
356
343
  * never authored with a numeric prefix); resolution against the physical tree
@@ -362,40 +349,26 @@ function findMemoryMatchInSource(name, segments, source) {
362
349
  // precedence), then its enabled plugins.
363
350
  const dir = source.memoryDir;
364
351
  if (dir) {
365
- const { filePath, dirPath } = resolveNormalizedPath(dir, segments);
352
+ const { filePath } = resolveNormalizedPath(dir, segments);
366
353
  if (!isLegacySkillDoc && filePath !== null)
367
354
  return loadMemoryDoc(source.scope, dir, filePath, name);
368
- if (dirPath !== null) {
369
- const indexPath = join(dirPath, 'INDEX.md');
370
- if (pathExists(indexPath))
371
- return loadMemoryDoc(source.scope, dir, indexPath, name);
372
- }
373
355
  }
374
356
  // Plugin memory dir: a `<plugin>/<rest>` name resolves against that enabled
375
357
  // plugin's memory/ tree (the `<pluginName>/` mount that listAllMemoryDocs
376
- // enumerates — `read` must resolve what `list` shows). A BARE plugin name
377
- // (no slash) resolves the plugin-root INDEX.md, mirroring the native
378
- // bare-dir-name -> INDEX.md contract for the plugin mount root.
358
+ // enumerates — `read` must resolve what `list` shows).
379
359
  const slash = name.indexOf('/');
380
- const pluginName = slash > 0 ? name.slice(0, slash) : name;
381
- const rest = slash > 0 ? name.slice(slash + 1) : '';
360
+ if (slash <= 0)
361
+ return undefined;
362
+ const pluginName = name.slice(0, slash);
363
+ const rest = name.slice(slash + 1);
382
364
  for (const p of source.plugins) {
383
365
  if (p.name !== pluginName)
384
366
  continue;
385
367
  const pdir = pluginMemoryDir(p);
386
- const restSegments = rest ? rest.split('/') : [];
387
- if (rest) {
388
- const { filePath } = resolveNormalizedPath(pdir, restSegments);
389
- if (restSegments.at(-1) !== 'SKILL' && filePath !== null)
390
- return loadMemoryDoc(source.scope, pdir, filePath, name);
391
- }
392
- // Bare name -> <plugin>/memory/INDEX.md; slashed name -> dir INDEX.
393
- const pIndexDir = rest ? resolveNormalizedPath(pdir, restSegments).dirPath : pdir;
394
- if (pIndexDir !== null) {
395
- const pindex = join(pIndexDir, 'INDEX.md');
396
- if (pathExists(pindex))
397
- return loadMemoryDoc(source.scope, pdir, pindex, name);
398
- }
368
+ const restSegments = rest.split('/');
369
+ const { filePath } = resolveNormalizedPath(pdir, restSegments);
370
+ if (restSegments.at(-1) !== 'SKILL' && filePath !== null)
371
+ return loadMemoryDoc(source.scope, pdir, filePath, name);
399
372
  }
400
373
  return undefined;
401
374
  }
@@ -1,4 +1,5 @@
1
1
  import { type Cron } from '../canvas/index.js';
2
+ import type { InjectedDocs } from '../substrate/injected-store.js';
2
3
  /** The bearings custom type is shared with every attached renderer. */
3
4
  export { CONTEXT_INTRO_CUSTOM_TYPE } from '../../shared/generated-context.js';
4
5
  /** The `<project_context>` block — the `<environment>` snapshot only: a
@@ -71,10 +72,10 @@ export declare function buildWakeBearings(origin: WakeOrigin): string;
71
72
  * from.
72
73
  * 2. `<memory kind="knowledge">` — the consultable boot catalog, a SIBLING of
73
74
  * the bearings (never nested under the context dir). Dropped when empty.
74
- * 3. `<auto-loaded-context>` — memories whose `applies-to: "."` fires because
75
- * cwd/profile mounted their owning project store. Dropped when empty.
75
+ * 3. `<auto-loaded-context>` — memories whose `on: workspace-open` entry fires
76
+ * because cwd/profile mounted their owning project store. Dropped when empty.
76
77
  * 4. `<project_context>` — the cwd/git environment snapshot. pi's own
77
78
  * AGENTS.md/CLAUDE.md injection stays suppressed (`noContextFiles` in
78
79
  * broker.ts); `crtr sys sync project-guidance` imports that guidance into
79
80
  * the directory's `.crouter/memory/INDEX.md` front door. */
80
- export declare function buildContextBearings(nodeId: string, seen?: Set<string>): string;
81
+ export declare function buildContextBearings(nodeId: string, seen?: InjectedDocs): string;
@@ -340,8 +340,8 @@ function managedWorktreeNote(node) {
340
340
  * from.
341
341
  * 2. `<memory kind="knowledge">` — the consultable boot catalog, a SIBLING of
342
342
  * the bearings (never nested under the context dir). Dropped when empty.
343
- * 3. `<auto-loaded-context>` — memories whose `applies-to: "."` fires because
344
- * cwd/profile mounted their owning project store. Dropped when empty.
343
+ * 3. `<auto-loaded-context>` — memories whose `on: workspace-open` entry fires
344
+ * because cwd/profile mounted their owning project store. Dropped when empty.
345
345
  * 4. `<project_context>` — the cwd/git environment snapshot. pi's own
346
346
  * AGENTS.md/CLAUDE.md injection stays suppressed (`noContextFiles` in
347
347
  * broker.ts); `crtr sys sync project-guidance` imports that guidance into
@@ -365,8 +365,8 @@ export function buildContextBearings(nodeId, seen) {
365
365
  bearings.push('', orchestratorContextNote(nodeId));
366
366
  bearings.push('</crtr-bearings>');
367
367
  // The consultable catalog rides BESIDE the bearings, not inside them.
368
- const knowledge = renderKnowledgeBlock(nodeId);
369
- // A workspace mount is the initial read of its root (`applies-to: "."`).
368
+ const knowledge = renderKnowledgeBlock(nodeId, seen);
369
+ // A workspace mount is the initial read of its root (`on: workspace-open`).
370
370
  const workspaceContext = renderWorkspaceOpenDocs(nodeId, seen);
371
371
  // Hidden applet/situation-origin ambient context (see situational-context.ts)
372
372
  // — the stored `situational` card, placed in the bearings BODY, so it is a
@@ -208,12 +208,7 @@ export class FaultRetry {
208
208
  return;
209
209
  if (unconfigured)
210
210
  clearFault(this.deps.nodeId, { link: 'pi→provider' });
211
- await operationIdContext.fresh(() => candidateSession.prompt(formatCard('recovery', {
212
- reason: 'model-fallback',
213
- from: currentSpec,
214
- to: resolved,
215
- cause: unconfigured ? 'credential' : 'not-found',
216
- }, formatModelFallbackRecovery(currentSpec, resolved, unconfigured ? 'credential' : 'not-found'))));
211
+ await operationIdContext.fresh(() => candidateSession.prompt(formatCard('recovery', {}, formatModelFallbackRecovery(currentSpec, resolved, unconfigured ? 'credential' : 'not-found'))));
217
212
  })().catch((err) => {
218
213
  emitEvent({ level: 'debug', event: 'broker.runtime.diagnostic', fields: { message: `${unconfigured ? 'unconfigured-provider' : 'not_found'} fallback failed: ${err instanceof Error ? err.message : String(err)}` } });
219
214
  }).finally(() => {
@@ -300,6 +295,6 @@ export class FaultRetry {
300
295
  if (this.deps.installedGeneration() !== generation || this.deps.currentSession() !== candidateSession)
301
296
  return;
302
297
  recordFault(this.deps.nodeId, plan.fault);
303
- await operationIdContext.fresh(() => candidateSession.prompt(formatCard('recovery', { reason: fault.kind === 'connection' ? 'connection' : 'provider' }, fault.kind === 'connection' ? CONNECTION_FAULT_RECOVERY_BODY : PROVIDER_FAULT_RECOVERY_BODY)));
298
+ await operationIdContext.fresh(() => candidateSession.prompt(formatCard('recovery', {}, fault.kind === 'connection' ? CONNECTION_FAULT_RECOVERY_BODY : PROVIDER_FAULT_RECOVERY_BODY)));
304
299
  }
305
300
  }
@@ -1442,7 +1442,7 @@ export function createFrameDispatchContext(deps) {
1442
1442
  fresh.since !== authFault.since)
1443
1443
  return;
1444
1444
  clearFault(nodeId, { link: 'pi→provider' });
1445
- await authFaultSession.prompt(formatCard('recovery', { reason: 'auth' }, AUTH_FAULT_RECOVERY_BODY));
1445
+ await authFaultSession.prompt(formatCard('recovery', {}, AUTH_FAULT_RECOVERY_BODY));
1446
1446
  }
1447
1447
  catch (err) {
1448
1448
  emitEvent({
@@ -4,7 +4,7 @@ import { jobDir } from '../../canvas/paths.js';
4
4
  import { readConfig } from '../../config.js';
5
5
  import { emitEvent } from '../../events/emit.js';
6
6
  import { atomicWriteJson, readJsonIfExists } from '../../fs-utils.js';
7
- import { assistantVisibleText, isTrueUserMessage, normalizeToolGroupSummaryRecord, } from '../../../shared/tool-groups.js';
7
+ import { assistantVisibleText, isGroupBoundaryKind, isTrueUserMessage, normalizeToolGroupSummaryRecord, } from '../../../shared/tool-groups.js';
8
8
  import { generatedContextText, parseCard } from '../../../shared/generated-context.js';
9
9
  import { headlessToolGroupSummary, serializeToolGroup } from '../tool-group-summary.js';
10
10
  const PENDING_TOOL_SUMMARY_KEY_MAX_CHARS = 1_000;
@@ -140,11 +140,12 @@ export class ToolGroupTracker {
140
140
  if (item.display === false)
141
141
  return;
142
142
  const text = generatedContextText(item);
143
- const kind = parseCard(item)?.kind;
144
- const isBearingsOrRevive = kind === 'bearings' || kind === 'revive';
145
- if (isBearingsOrRevive) {
146
- if (this.openToolGroup !== undefined && text !== '')
147
- this.openToolGroup.pieces.push({ type: 'text', text });
143
+ const card = parseCard(item);
144
+ // A boundary card ends the preceding run and becomes the next group's
145
+ // context, exactly as a person's message does; the viewer reads the same
146
+ // set, so both sides fold at the same seams.
147
+ if (card !== null && isGroupBoundaryKind(card.kind)) {
148
+ this.visibleBoundary(text);
148
149
  return;
149
150
  }
150
151
  const group = this.ensureOpenToolGroup();
@@ -1,3 +1,4 @@
1
+ import type { InjectedDocs } from '../substrate/injected-store.js';
1
2
  import type { BrokerExtensionNodeDTO, BrokerExtensionStateDTO } from '../../api/dto/broker-ops.js';
2
3
  export declare function editorLabelForBrokerNode(node: BrokerExtensionNodeDTO): string;
3
4
  /** Same environment-only project context rendered at normal boot, without a
@@ -6,9 +7,9 @@ export declare function buildProjectContextBlockForBroker(cwd: string): string;
6
7
  /** `situational` is the node's stored situational card, placed in the bearings
7
8
  * BODY — the one place in the system where a runtime card nests inside
8
9
  * another (see situational-context.ts). '' when none is set. */
9
- export declare function buildContextBearingsFromState(state: BrokerExtensionStateDTO, projectContext: string, situational?: string, seen?: Set<string>): string;
10
+ export declare function buildContextBearingsFromState(state: BrokerExtensionStateDTO, projectContext: string, situational?: string, seen?: InjectedDocs): string;
10
11
  export declare function buildForkBearingsFromState(state: BrokerExtensionStateDTO, situational?: string): string;
11
- export declare function renderPreferencesFromState(state: BrokerExtensionStateDTO): string;
12
+ export declare function renderPreferencesFromState(state: BrokerExtensionStateDTO, seen?: InjectedDocs): string;
12
13
  /** A canonical report ref has already been validated by crtrd in state. The
13
14
  * broker reads only its local filesystem content; no canvas accessor is needed. */
14
15
  export declare function reportPathFromRef(ref: string, reportNodes: ReadonlyMap<string, unknown>): string | null;
@@ -158,7 +158,7 @@ export function buildContextBearingsFromState(state, projectContext, situational
158
158
  if (node.mode === 'orchestrator')
159
159
  bearings.push('', `Because you persist across refresh cycles, your context directory (${contextDir(node.node_id)}) is also where a future cycle of you resumes the work — keep the working notes and decisions a refreshed you would need there, alongside the docs you share with the nodes you spawn.`);
160
160
  bearings.push('</crtr-bearings>');
161
- const knowledge = renderKnowledgeForSubject(state.subject, node.node_id);
161
+ const knowledge = renderKnowledgeForSubject(state.subject, node.node_id, seen);
162
162
  const workspace = renderWorkspaceOpenDocsForSubject(state.subject, node.cwd, node.profile_id, seen);
163
163
  return [bearings.join('\n'), knowledge, workspace, situational, projectContext].filter((part) => part !== '').join('\n');
164
164
  }
@@ -186,8 +186,8 @@ export function buildForkBearingsFromState(state, situational = '') {
186
186
  bearings.push('', 'The first real user message after these bearings is your mandate.', '</crtr-bearings>');
187
187
  return [bearings.join('\n'), situational].filter((part) => part !== '').join('\n');
188
188
  }
189
- export function renderPreferencesFromState(state) {
190
- return interpolateNodePaths(renderPreferencesForSubject(state.subject).block, state.node.node_id);
189
+ export function renderPreferencesFromState(state, seen) {
190
+ return interpolateNodePaths(renderPreferencesForSubject(state.subject, seen).block, state.node.node_id);
191
191
  }
192
192
  /** A canonical report ref has already been validated by crtrd in state. The
193
193
  * broker reads only its local filesystem content; no canvas accessor is needed. */
@@ -359,13 +359,13 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
359
359
  const liveSession = rebind.session();
360
360
  const identified = snapshotMessages(liveSession, boundaryReviewId);
361
361
  const tail = normalizeSnapshotTail(client.snapshotTail);
362
+ // A tail is only usable with stable ids, which let its recipient page back
363
+ // to the omitted history.
362
364
  const retained = identified === undefined || tail === undefined ? identified : identified.slice(-tail);
363
365
  const messages = retained?.map(({ message }) => message)
364
- ?? (tail === undefined
365
- ? liveSession.sessionManager.buildSessionContext().messages
366
- : liveSession.sessionManager.buildSessionContext().messages.slice(-tail));
366
+ ?? liveSession.sessionManager.buildSessionContext().messages;
367
367
  const fullToolGroupSummaries = toolGroups.summaries();
368
- const retainedToolCallIds = tail === undefined ? undefined : toolCallIds(messages);
368
+ const retainedToolCallIds = retained !== undefined && tail !== undefined ? toolCallIds(messages) : undefined;
369
369
  const toolGroupSummaries = retainedToolCallIds === undefined
370
370
  ? fullToolGroupSummaries
371
371
  : Object.fromEntries(Object.entries(fullToolGroupSummaries).filter(([id]) => retainedToolCallIds.has(id)));
@@ -318,14 +318,8 @@ export function buildReviveKickoff(meta, bearings, wakeReason) {
318
318
  if (bearings.driftGuidance !== null) {
319
319
  parts.push(`<persona-transition>\nYour role was changed while you were away. ${bearings.driftGuidance}\n</persona-transition>`);
320
320
  }
321
- // The whole kickoff is one runtime card. Its facts are what this producer
322
- // knows about the revive, and they are the ONLY thing a reader may classify or
323
- // summarize on — the prose above stays free to be reworded. The envelope is
324
- // also what keeps a fresh revive out of canvas-goal-capture's first-real-input
325
- // path, which would otherwise overwrite a bare root's mandate and rename it.
326
- return formatCard('revive', {
327
- mode: meta.mode,
328
- ...(meta.cycle_pending === true ? { cycle: true } : {}),
329
- ...(wakeReason === undefined ? {} : { wake: wakeReason.kind }),
330
- }, `\n${parts.join('\n\n')}\n`);
321
+ // The envelope keeps a fresh revive out of canvas-goal-capture's
322
+ // first-real-input path, which would otherwise overwrite a bare root's
323
+ // mandate and rename it.
324
+ return formatCard('revive', {}, `\n${parts.join('\n\n')}\n`);
331
325
  }
@@ -1,9 +1,8 @@
1
1
  // memory.ts — the node-local substrate document store.
2
2
  //
3
3
  // One scope lives here: node-local memory, a `memory/` directory of substrate
4
- // documents (.md files with typed frontmatter — kind, when, why,
5
- // system-prompt-visibility, gate) inside a node's context dir; it dies with the
6
- // node. User-global and project memory are scope-resolved stores — see
4
+ // documents (.md files with typed frontmatter — kind, when-and-why-to-read,
5
+ // surfaces, gate) inside a node's context dir; it dies with the node. User-global and project memory are scope-resolved stores — see
7
6
  // `scopeMemoryDir` in core/scope.ts.
8
7
  import { join } from 'node:path';
9
8
  // Import the pure path helper from paths.ts directly, NOT the canvas/index.js
@@ -263,7 +263,13 @@ export async function readNodeMessagesPage(nodeId, { cursor, limit = 200 }) {
263
263
  }
264
264
  const { messages, identified } = await reconstructVisibleMessages(nodeId);
265
265
  const before = cursor === undefined ? messages.length : decodeMessagesCursor(cursor, messages.length);
266
- const start = Math.max(0, before - limit);
266
+ let start = Math.max(0, before - limit);
267
+ while (start > 0
268
+ && typeof messages[start] === 'object'
269
+ && messages[start] !== null
270
+ && messages[start].role === 'toolResult') {
271
+ start--;
272
+ }
267
273
  const page = identified?.slice(start, before);
268
274
  return {
269
275
  messages: page?.map(({ message }) => message) ?? messages.slice(start, before),
@@ -28,11 +28,11 @@ import { readOutputRequest } from './structured-output.js';
28
28
  export { STALL_REPROMPT } from '../../shared/generated-context.js';
29
29
  /** Format the stop guard's reprompt for a valid structured-output request. */
30
30
  function formatValidStructuredOutputReprompt(schema) {
31
- return formatCard('stop-guard', { reason: 'structured-output' }, formatStructuredOutputReprompt(JSON.stringify(schema, null, 2)));
31
+ return formatCard('stop-guard', {}, formatStructuredOutputReprompt(JSON.stringify(schema, null, 2)));
32
32
  }
33
33
  /** Format the stop guard's reprompt for an invalid structured-output request file. */
34
34
  function formatInvalidStructuredOutputReprompt(error) {
35
- return formatCard('stop-guard', { reason: 'structured-output' }, `Structured-output request file is invalid and cannot be used: ${error}\n\n` +
35
+ return formatCard('stop-guard', {}, `Structured-output request file is invalid and cannot be used: ${error}\n\n` +
36
36
  `You have two options:\n` +
37
37
  `1. Remove the invalid file (the output-schema.json in your node directory has been corrupted)\n` +
38
38
  `2. Fix the corruption if you know the original request schema\n\n` +
@@ -99,6 +99,6 @@ export function evaluateStop(nodeId, signals, backgroundJobsRunning = activeBack
99
99
  return {
100
100
  action: 'reprompt',
101
101
  reason: 'stalled',
102
- message: formatCard('stop-guard', { reason: 'stalled' }, STALL_REPROMPT),
102
+ message: formatCard('stop-guard', {}, STALL_REPROMPT),
103
103
  };
104
104
  }
@@ -1,11 +1,14 @@
1
- export { KINDS, isDocKind, RUNGS, rungRank, rungAtLeast, FALLBACK_RUNG, parseSubstrateFrontmatter, parseSubstrateDoc, previewLine, normalizeNameSegment, normalizeDocName, resolveDocName, } from './schema.js';
2
- export type { DocKind, Rung, GatePredicate, SubstrateSchema, SubstrateDoc } from './schema.js';
1
+ export { KINDS, isDocKind, RUNGS, rungRank, rungAtLeast, SURFACE_EVENTS, SURFACE_RUNGS, bootRung, parseSubstrateFrontmatter, parseSubstrateDoc, previewLine, normalizeNameSegment, normalizeDocName, resolveDocName, } from './schema.js';
2
+ export type { DocKind, Rung, GatePredicate, SubstrateSchema, SubstrateDoc, SurfaceEntry, SurfaceEvent, SurfaceRung } from './schema.js';
3
+ export { matchesReadEntry, matchesMemoryReadEntry, matchesCommandEntry, readDeliveryRung, workspaceOpenRung, memoryReadDeliveryRung, commandDeliveryRung, owningRootOf, underOwningRoot } from './surface-match.js';
4
+ export { dirDedupKey, docsByName, isDirName, ancestorDirsOf, renderDirListing } from './listings.js';
5
+ export { deliveredAtOrAbove, recordDelivery, mergeInjectedDocs } from './injected-store.js';
6
+ export type { InjectedDocs } from './injected-store.js';
3
7
  export { scopeForCwd, profileNameFor } from './subject-fields.js';
4
8
  export type { NodeConfigSubject } from './subject-fields.js';
5
9
  export { spineDepth, assembleNodeSubject } from './subject.js';
6
10
  export { gatePasses } from './gate.js';
7
- export { INDEX_NAME, isIndexName, indexDirOf, displayName, buildCeilingIndex, effectiveSystemPromptRung, } from './ceiling.js';
8
11
  export { renderPreferencesForSubject, renderKnowledgeForSubject } from './render.js';
9
12
  export { renderPreferencesSection, renderKnowledgeBlock } from './render-node.js';
10
- export { renderOnReadDocsForSubject, renderWorkspaceOpenDocsForSubject } from './on-read.js';
13
+ export { renderOnReadDocsForSubject, renderWorkspaceOpenDocsForSubject, renderOnCommandDocsForSubject, renderCandidateBlocks, memoryReadDocBlocks } from './on-read.js';
11
14
  export { renderOnReadDocs, renderWorkspaceOpenDocs } from './on-read-node.js';
@@ -8,19 +8,21 @@ export {
8
8
  KINDS, isDocKind,
9
9
  // ladder
10
10
  RUNGS, rungRank, rungAtLeast,
11
- // fallback floor
12
- FALLBACK_RUNG,
11
+ // surfaces (event routing)
12
+ SURFACE_EVENTS, SURFACE_RUNGS, bootRung,
13
13
  // parse + render-shared helpers
14
14
  parseSubstrateFrontmatter, parseSubstrateDoc, previewLine,
15
15
  // display-name normalization
16
16
  normalizeNameSegment, normalizeDocName,
17
17
  // substrate identity (explicit-name-then-path-fallback)
18
18
  resolveDocName, } from './schema.js';
19
+ export { matchesReadEntry, matchesMemoryReadEntry, matchesCommandEntry, readDeliveryRung, workspaceOpenRung, memoryReadDeliveryRung, commandDeliveryRung, owningRootOf, underOwningRoot } from './surface-match.js';
20
+ export { dirDedupKey, docsByName, isDirName, ancestorDirsOf, renderDirListing } from './listings.js';
21
+ export { deliveredAtOrAbove, recordDelivery, mergeInjectedDocs } from './injected-store.js';
19
22
  export { scopeForCwd, profileNameFor } from './subject-fields.js';
20
23
  export { spineDepth, assembleNodeSubject } from './subject.js';
21
24
  export { gatePasses } from './gate.js';
22
- export { INDEX_NAME, isIndexName, indexDirOf, displayName, buildCeilingIndex, effectiveSystemPromptRung, } from './ceiling.js';
23
25
  export { renderPreferencesForSubject, renderKnowledgeForSubject } from './render.js';
24
26
  export { renderPreferencesSection, renderKnowledgeBlock } from './render-node.js';
25
- export { renderOnReadDocsForSubject, renderWorkspaceOpenDocsForSubject } from './on-read.js';
27
+ export { renderOnReadDocsForSubject, renderWorkspaceOpenDocsForSubject, renderOnCommandDocsForSubject, renderCandidateBlocks, memoryReadDocBlocks } from './on-read.js';
26
28
  export { renderOnReadDocs, renderWorkspaceOpenDocs } from './on-read-node.js';
@@ -1,16 +1,28 @@
1
- /** The process-shared dedup set for a node: loaded from disk once, then the
2
- * SAME Set instance on every call, so all hooks in this pi process grow and
3
- * consult one set. Daemon-side deliveries (a warm-spare claim) write the disk
1
+ import { type Rung } from './schema.js';
2
+ /** realpath → highest rung rank delivered this transcript (1=name, 2=preview,
3
+ * 3=content — `rungRank` of the delivered rung). */
4
+ export type InjectedDocs = Map<string, number>;
5
+ /** Already delivered at `rung` or above? — the skip test every channel runs
6
+ * before rendering a doc (or a listing line, at preview) again. */
7
+ export declare function deliveredAtOrAbove(seen: InjectedDocs, realpath: string, rung: Rung): boolean;
8
+ /** Record a delivery at `rung`, keeping the highest rank seen. */
9
+ export declare function recordDelivery(seen: InjectedDocs, realpath: string, rung: Rung): void;
10
+ /** Max-merge `source` into `target` — used to fold an out-of-process delivery
11
+ * (the daemon's warm-spare bearings push) into a live process's shared map. */
12
+ export declare function mergeInjectedDocs(target: InjectedDocs, source: InjectedDocs): void;
13
+ /** The process-shared dedup map for a node: loaded from disk once, then the
14
+ * SAME Map instance on every call, so all hooks in this pi process grow and
15
+ * consult one map. Daemon-side deliveries (a warm-spare claim) write the disk
4
16
  * file from another process; consumers that must see those merge the plain
5
17
  * `loadInjectedDocs` result in before rendering. */
6
- export declare function sharedInjectedDocs(nodeId: string): Set<string>;
7
- /** Rehydrate a node's on-read dedup set from disk. Returns an empty set when the
8
- * file is absent, unreadable, or malformed (a fresh transcript, or a node that
9
- * has not yet surfaced any on-read doc). */
10
- export declare function loadInjectedDocs(nodeId: string): Set<string>;
11
- /** Persist a node's on-read dedup set. Called after each read that surfaced a
12
- * new doc, so the grown set survives a later dormancy. */
13
- export declare function saveInjectedDocs(nodeId: string, seen: Set<string>): void;
14
- /** Drop a node's persisted dedup set. Called by the launch paths that start a
18
+ export declare function sharedInjectedDocs(nodeId: string): InjectedDocs;
19
+ /** Rehydrate a node's dedup map from disk. Returns an empty map when the file
20
+ * is absent, unreadable, or malformed (a fresh transcript, or a node that has
21
+ * not yet surfaced any doc). */
22
+ export declare function loadInjectedDocs(nodeId: string): InjectedDocs;
23
+ /** Persist a node's dedup map. Called after each delivery that grew it, so
24
+ * the grown map survives a later dormancy. */
25
+ export declare function saveInjectedDocs(nodeId: string, seen: InjectedDocs): void;
26
+ /** Drop a node's persisted dedup map. Called by the launch paths that start a
15
27
  * FRESH transcript, so the new conversation surfaces docs from scratch. */
16
28
  export declare function clearInjectedDocs(nodeId: string): void;