@crouton-kit/crouter 0.3.19 → 0.3.21

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 (90) hide show
  1. package/dist/builtin-memory/crouter-development/marketplaces.md +2 -4
  2. package/dist/builtin-memory/crouter-development/personas/base-prompt.md +3 -5
  3. package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +3 -6
  4. package/dist/builtin-memory/crouter-development/personas.md +2 -6
  5. package/dist/builtin-memory/crouter-development/plugins.md +2 -4
  6. package/dist/builtin-memory/design.md +2 -4
  7. package/dist/builtin-memory/development.md +3 -3
  8. package/dist/builtin-memory/planning.md +3 -4
  9. package/dist/builtin-memory/spec.md +3 -10
  10. package/dist/builtin-personas/design/PERSONA.md +1 -0
  11. package/dist/builtin-personas/design/orchestrator.md +1 -0
  12. package/dist/builtin-personas/developer/PERSONA.md +1 -0
  13. package/dist/builtin-personas/developer/orchestrator.md +1 -0
  14. package/dist/builtin-personas/explore/PERSONA.md +1 -0
  15. package/dist/builtin-personas/explore/orchestrator.md +4 -0
  16. package/dist/builtin-personas/general/PERSONA.md +1 -0
  17. package/dist/builtin-personas/general/orchestrator.md +4 -0
  18. package/dist/builtin-personas/orchestration-kernel.md +1 -1
  19. package/dist/builtin-personas/plan/PERSONA.md +1 -0
  20. package/dist/builtin-personas/plan/orchestrator.md +1 -0
  21. package/dist/builtin-personas/plan/reviewers/architecture-fit/PERSONA.md +1 -0
  22. package/dist/builtin-personas/plan/reviewers/code-smells/PERSONA.md +1 -0
  23. package/dist/builtin-personas/plan/reviewers/pattern-consistency/PERSONA.md +1 -0
  24. package/dist/builtin-personas/plan/reviewers/requirements-coverage/PERSONA.md +1 -0
  25. package/dist/builtin-personas/plan/reviewers/security/PERSONA.md +1 -0
  26. package/dist/builtin-personas/review/PERSONA.md +1 -0
  27. package/dist/builtin-personas/review/orchestrator.md +4 -0
  28. package/dist/builtin-personas/spec/PERSONA.md +1 -0
  29. package/dist/builtin-personas/spec/orchestrator.md +1 -0
  30. package/dist/commands/human/queue.js +13 -2
  31. package/dist/commands/memory/__tests__/lint-schema.test.d.ts +1 -0
  32. package/dist/commands/memory/__tests__/lint-schema.test.js +29 -0
  33. package/dist/commands/memory/find.js +10 -15
  34. package/dist/commands/memory/lint.d.ts +7 -0
  35. package/dist/commands/memory/lint.js +10 -1
  36. package/dist/commands/memory/shared.js +1 -2
  37. package/dist/commands/memory/write.js +6 -9
  38. package/dist/commands/node.js +51 -26
  39. package/dist/commands/push.js +10 -15
  40. package/dist/commands/tmux-spread.js +16 -1
  41. package/dist/commands/view-cycle.js +9 -5
  42. package/dist/commands/view-run.js +3 -2
  43. package/dist/core/__tests__/broker-attach-limits.test.d.ts +1 -0
  44. package/dist/core/__tests__/broker-attach-limits.test.js +157 -0
  45. package/dist/core/__tests__/broker-attach-stream.test.d.ts +1 -0
  46. package/dist/core/__tests__/broker-attach-stream.test.js +125 -0
  47. package/dist/core/__tests__/broker-crash-teardown.test.d.ts +1 -0
  48. package/dist/core/__tests__/broker-crash-teardown.test.js +116 -0
  49. package/dist/core/__tests__/broker-dialogs.test.d.ts +1 -0
  50. package/dist/core/__tests__/broker-dialogs.test.js +126 -0
  51. package/dist/core/__tests__/broker-dormant-wake.test.d.ts +1 -0
  52. package/dist/core/__tests__/broker-dormant-wake.test.js +51 -0
  53. package/dist/core/__tests__/broker-lifecycle.test.js +14 -604
  54. package/dist/core/__tests__/canvas-inbox-watcher-hold.test.d.ts +1 -0
  55. package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +102 -0
  56. package/dist/core/__tests__/canvas-inbox-watcher.test.js +2 -36
  57. package/dist/core/__tests__/cascade-close.test.js +3 -2
  58. package/dist/core/__tests__/context-intro.test.js +23 -27
  59. package/dist/core/__tests__/detach-focus.test.js +2 -2
  60. package/dist/core/__tests__/helpers/broker-clients.d.ts +43 -0
  61. package/dist/core/__tests__/helpers/broker-clients.js +178 -0
  62. package/dist/core/__tests__/live-mutation-verbs.test.d.ts +1 -0
  63. package/dist/core/__tests__/live-mutation-verbs.test.js +175 -0
  64. package/dist/core/__tests__/live-mutation.test.js +4 -147
  65. package/dist/core/__tests__/memory.test.js +4 -4
  66. package/dist/core/__tests__/subscription-delivery.test.js +3 -3
  67. package/dist/core/bootstrap.js +18 -14
  68. package/dist/core/canvas/canvas.js +1 -1
  69. package/dist/core/canvas/types.d.ts +6 -0
  70. package/dist/core/help.d.ts +2 -2
  71. package/dist/core/help.js +1 -1
  72. package/dist/core/render.d.ts +4 -3
  73. package/dist/core/render.js +38 -41
  74. package/dist/core/resolver.js +1 -1
  75. package/dist/core/runtime/bearings.d.ts +2 -2
  76. package/dist/core/runtime/bearings.js +2 -2
  77. package/dist/core/runtime/launch.d.ts +9 -2
  78. package/dist/core/runtime/launch.js +20 -3
  79. package/dist/core/runtime/nodes.d.ts +4 -0
  80. package/dist/core/runtime/nodes.js +1 -0
  81. package/dist/core/runtime/promote.js +3 -0
  82. package/dist/core/runtime/reset.js +3 -2
  83. package/dist/core/runtime/spawn.d.ts +4 -0
  84. package/dist/core/runtime/spawn.js +2 -1
  85. package/dist/core/substrate/render.d.ts +1 -2
  86. package/dist/core/substrate/render.js +9 -12
  87. package/dist/core/substrate/schema.d.ts +18 -13
  88. package/dist/core/substrate/schema.js +12 -11
  89. package/dist/pi-extensions/canvas-context-intro.js +5 -3
  90. package/package.json +1 -1
@@ -1,14 +1,18 @@
1
1
  // Default stdout rendering: the result a leaf returns is written FOR the model
2
2
  // to act on, not as data to parse. Output is a continuation of the agent's
3
- // prompt — light XML fences around markdown, prose where prose belongs. The
4
- // raw JSON object is still available behind the `--json` global for tooling.
3
+ // prompt — PLAIN MARKDOWN, no XML wrapper. Scalars list as `- name: value`
4
+ // bullets in output-schema order, prose fields render as paragraphs, and arrays
5
+ // become a count lead-in plus a markdown table (objects) or bullets (scalars).
6
+ // The raw JSON object is still available behind the `--json` global for tooling.
5
7
  //
6
8
  // A leaf may hand `defineLeaf` a bespoke `render(result)` for instruction-shaped
7
- // output (see `node new`, `push`, `feed read`). Everything else falls through to
8
- // the schema-driven generic renderer here, so every command obeys the paradigm
9
- // without a hand-written renderer.
9
+ // output that leads with the outcome (see `node new`, `push`, `feed read`).
10
+ // Everything else falls through to the schema-driven generic renderer here, so
11
+ // every command obeys the markdown paradigm without a hand-written renderer.
12
+ // `renderError` is the one sanctioned exception: a failure is a different domain
13
+ // from a result, so it keeps its `<error>` block.
10
14
  // Field names whose value is prose the agent reads, not a scalar it keys on —
11
- // these always render as their own fenced block, never as a tag attribute.
15
+ // these always render as their own paragraph, never as a `- name: value` bullet.
12
16
  const PROSE_FIELDS = new Set([
13
17
  'follow_up', 'digest', 'message', 'next', 'result', 'content', 'body', 'guide',
14
18
  'summary', 'note', 'reason', 'detail', 'details', 'hint', 'instruction',
@@ -37,20 +41,17 @@ function isProse(name, v) {
37
41
  return true;
38
42
  return v.includes('\n') || v.length > 80;
39
43
  }
40
- /** Short, space-free scalars ride as attributes on the open tag (ids, statuses,
41
- * counts); everything else that isn't prose lists as a markdown bullet. */
42
- function isAttr(v) {
43
- if (typeof v === 'number' || typeof v === 'boolean')
44
- return true;
45
- return typeof v === 'string' && v.length <= 40 && !v.includes(' ') && !v.includes('\n');
46
- }
47
- /** Root element name = the command path with spaces hyphenated. */
48
- function rootTag(help) {
49
- return help.name.trim().split(/\s+/).join('-').replace(/[^a-zA-Z0-9_-]/g, '') || 'result';
44
+ /** Keep a value safe inside a markdown table cell: pipes would split columns,
45
+ * newlines would break the row. */
46
+ function cellEsc(v) {
47
+ return scalarStr(v).replace(/\|/g, '\\|').replace(/\n/g, ' ');
50
48
  }
49
+ /** An array renders as a count lead-in followed by the collection: a markdown
50
+ * table when every element is an object, plain bullets otherwise. */
51
51
  function renderArray(name, arr) {
52
52
  if (arr.length === 0)
53
- return `<${name} count="0"></${name}>`;
53
+ return `0 ${name}.`;
54
+ const lead = `${arr.length} ${name}:`;
54
55
  const allObjects = arr.every((x) => x !== null && typeof x === 'object' && !Array.isArray(x));
55
56
  if (allObjects) {
56
57
  const rows = arr;
@@ -61,55 +62,51 @@ function renderArray(name, arr) {
61
62
  cols.push(k);
62
63
  const head = `| ${cols.join(' | ')} |`;
63
64
  const sep = `| ${cols.map(() => '---').join(' | ')} |`;
64
- const body = rows.map((r) => `| ${cols.map((c) => scalarStr(r[c])).join(' | ')} |`).join('\n');
65
- return `<${name} count="${arr.length}">\n${head}\n${sep}\n${body}\n</${name}>`;
65
+ const body = rows.map((r) => `| ${cols.map((c) => cellEsc(r[c])).join(' | ')} |`).join('\n');
66
+ return `${lead}\n\n${head}\n${sep}\n${body}`;
66
67
  }
67
68
  const items = arr.map((x) => `- ${scalarStr(x)}`).join('\n');
68
- return `<${name} count="${arr.length}">\n${items}\n</${name}>`;
69
+ return `${lead}\n${items}`;
69
70
  }
70
- /** Schema-driven fallback: turn a result object into agent-ready XML+markdown.
71
+ /** Schema-driven fallback: turn a result object into agent-ready plain markdown.
71
72
  * Field order follows the leaf's declared output schema; any extra keys append
72
- * after. Scalars become attributes or a bullet list, prose and collections
73
- * become their own fenced blocks. */
73
+ * after. Scalars (and nested objects) list as `- name: value` bullets, prose
74
+ * fields render as paragraphs, and arrays become a count lead-in plus a table
75
+ * or bullets. No root tag — the result is read as a continuation of the prompt. */
74
76
  export function renderResult(result, help) {
75
77
  const order = help.output.map((f) => f.name);
76
78
  for (const k of Object.keys(result))
77
79
  if (!order.includes(k))
78
80
  order.push(k);
79
- const attrs = [];
80
81
  const bullets = [];
81
- const blocks = [];
82
+ const proseFields = [];
83
+ const arrays = [];
82
84
  for (const name of order) {
83
85
  const v = result[name];
84
86
  if (v === undefined || v === null)
85
87
  continue;
86
88
  if (Array.isArray(v)) {
87
- blocks.push(renderArray(name, v));
88
- continue;
89
- }
90
- if (typeof v === 'object') {
91
- blocks.push(`<${name}>\n${JSON.stringify(v, null, 2)}\n</${name}>`);
89
+ arrays.push(renderArray(name, v));
92
90
  continue;
93
91
  }
94
92
  if (isProse(name, v)) {
95
- blocks.push(`<${name}>\n${v}\n</${name}>`);
96
- continue;
97
- }
98
- if (isAttr(v)) {
99
- attrs.push(`${name}="${attrEsc(v)}"`);
93
+ proseFields.push({ name, text: v });
100
94
  continue;
101
95
  }
96
+ // Scalars and nested objects alike list as a bullet (objects via compact JSON).
102
97
  bullets.push(`- ${name}: ${scalarStr(v)}`);
103
98
  }
104
- const tag = rootTag(help);
105
- const open = attrs.length > 0 ? `<${tag} ${attrs.join(' ')}>` : `<${tag}>`;
106
99
  const parts = [];
107
100
  if (bullets.length > 0)
108
101
  parts.push(bullets.join('\n'));
109
- if (blocks.length > 0)
110
- parts.push(blocks.join('\n\n'));
111
- const inner = parts.join('\n\n');
112
- return inner !== '' ? `${open}\n${inner}\n</${tag}>` : `${open}</${tag}>`;
102
+ // Label prose only when more than one block would otherwise blur together.
103
+ const labelProse = proseFields.length > 1;
104
+ for (const { name, text } of proseFields) {
105
+ parts.push(labelProse ? `**${name}:** ${text}` : text);
106
+ }
107
+ for (const block of arrays)
108
+ parts.push(block);
109
+ return parts.join('\n\n');
113
110
  }
114
111
  /** Render a structured failure as an instruction-shaped block: what broke, what
115
112
  * was received, and the recovery road sign — the same recovery info the JSON
@@ -403,7 +403,7 @@ function formatNotFoundMessage(rawName, skillName, pluginQualifier) {
403
403
  lines.push(` did you mean: ${formatted.join(', ')}`);
404
404
  }
405
405
  else {
406
- lines.push(' run `crtr skill find list` or `crtr skill find search <query>` to discover skills');
406
+ lines.push(' run `crtr memory list` or `crtr memory find "<query>"` to discover skills');
407
407
  }
408
408
  return lines.join('\n');
409
409
  }
@@ -56,6 +56,6 @@ export declare function buildWakeBearings(origin: WakeOrigin): string;
56
56
  * any copied-in persona) followed by the `<crtr-context>` bearings block. Base
57
57
  * framing rides for EVERY node; the across-cycles context-dir note is added
58
58
  * ONLY for an orchestrator (by mode) — the one node whose dir a future cycle
59
- * resumes from. The `## References` block (substrate reference docs +
60
- * node-local docs) replaces the old `<memory>` block. */
59
+ * resumes from. The `## References` block carries the substrate reference docs
60
+ * + node-local docs. */
61
61
  export declare function buildContextBearings(nodeId: string): string;
@@ -145,8 +145,8 @@ export function buildWakeBearings(origin) {
145
145
  * any copied-in persona) followed by the `<crtr-context>` bearings block. Base
146
146
  * framing rides for EVERY node; the across-cycles context-dir note is added
147
147
  * ONLY for an orchestrator (by mode) — the one node whose dir a future cycle
148
- * resumes from. The `## References` block (substrate reference docs +
149
- * node-local docs) replaces the old `<memory>` block. */
148
+ * resumes from. The `## References` block carries the substrate reference docs
149
+ * + node-local docs. */
150
150
  export function buildContextBearings(nodeId) {
151
151
  const identity = buildIdentityAssertion(nodeId);
152
152
  const dir = contextDir(nodeId);
@@ -22,8 +22,14 @@ export declare const CANVAS_VIEW_PATH: string;
22
22
  * All self-gate on CRTR_NODE_ID. goal-capture precedes passive-context so it
23
23
  * reads the raw user text. */
24
24
  export declare const CANVAS_EXTENSIONS: string[];
25
- /** Bare model aliases resolve to the anthropic provider under pi (avoids the
26
- * bedrock default). Anything with a `/` or an unknown name passes through. */
25
+ /** The named capability tiers a caller picks with `--model`, in descending
26
+ * strength. Each maps to a concrete pi model spec. `strong`/`medium`/`light`
27
+ * resolve to the latest opus/sonnet/haiku; `ultra` to the frontier model. */
28
+ export declare const MODEL_TIERS: Record<string, string>;
29
+ /** Resolve a model token to the spec pi gets via `--model`. A named tier
30
+ * (ultra/strong/medium/light) maps to its concrete spec; a bare alias
31
+ * (sonnet/opus/haiku) resolves to the anthropic provider under pi (avoids the
32
+ * bedrock default); anything with a `/` or an unknown name passes through. */
27
33
  export declare function normalizeModel(model: string): string;
28
34
  /** Compose a node's full pi launch recipe from its persona. The system prompt
29
35
  * is composed from FOUR inputs: kind×mode (the persona body) plus lifecycle
@@ -36,6 +42,7 @@ export declare function buildLaunchSpec(kind: string, mode: Mode, opts: {
36
42
  lifecycle: Lifecycle;
37
43
  hasManager: boolean;
38
44
  extraEnv?: Record<string, string>;
45
+ model?: string;
39
46
  }): {
40
47
  launch: LaunchSpec;
41
48
  lifecycle: 'terminal' | 'resident';
@@ -60,9 +60,22 @@ export const CANVAS_EXTENSIONS = [
60
60
  CANVAS_RESUME_PATH,
61
61
  CANVAS_VIEW_PATH,
62
62
  ];
63
- /** Bare model aliases resolve to the anthropic provider under pi (avoids the
64
- * bedrock default). Anything with a `/` or an unknown name passes through. */
63
+ /** The named capability tiers a caller picks with `--model`, in descending
64
+ * strength. Each maps to a concrete pi model spec. `strong`/`medium`/`light`
65
+ * resolve to the latest opus/sonnet/haiku; `ultra` to the frontier model. */
66
+ export const MODEL_TIERS = {
67
+ ultra: 'anthropic/claude-fable-5',
68
+ strong: 'anthropic/opus',
69
+ medium: 'anthropic/sonnet',
70
+ light: 'anthropic/haiku',
71
+ };
72
+ /** Resolve a model token to the spec pi gets via `--model`. A named tier
73
+ * (ultra/strong/medium/light) maps to its concrete spec; a bare alias
74
+ * (sonnet/opus/haiku) resolves to the anthropic provider under pi (avoids the
75
+ * bedrock default); anything with a `/` or an unknown name passes through. */
65
76
  export function normalizeModel(model) {
77
+ if (model in MODEL_TIERS)
78
+ return MODEL_TIERS[model];
66
79
  const bare = new Set(['sonnet', 'opus', 'haiku']);
67
80
  if (bare.has(model))
68
81
  return `anthropic/${model}`;
@@ -80,8 +93,12 @@ export function normalizeModel(model) {
80
93
  * declared extensions follow. */
81
94
  export function buildLaunchSpec(kind, mode, opts) {
82
95
  const p = resolvePersona(kind, mode, { lifecycle: opts.lifecycle, hasManager: opts.hasManager });
96
+ // A caller-supplied override (durable on `meta.model_override`, re-passed on
97
+ // every polymorph) wins over the persona's declared default; absent both, the
98
+ // model is left unset and the node inherits pi's default.
99
+ const chosenModel = opts.model ?? p.model;
83
100
  const launch = {
84
- model: p.model !== undefined ? normalizeModel(p.model) : undefined,
101
+ model: chosenModel !== undefined ? normalizeModel(chosenModel) : undefined,
85
102
  tools: p.tools,
86
103
  extensions: [...CANVAS_EXTENSIONS, ...p.extensions],
87
104
  systemPrompt: p.systemPrompt,
@@ -98,6 +98,10 @@ export interface SpawnNodeOpts {
98
98
  passiveDefault?: boolean;
99
99
  /** Resolved pi launch recipe (from resolve(kind,mode)). */
100
100
  launch?: LaunchSpec;
101
+ /** Caller-pinned model tier (ultra/strong/medium/light) that overrides the
102
+ * persona default. Persisted to `meta.model_override` so polymorphs preserve
103
+ * it. Omit to use the persona default. */
104
+ modelOverride?: string | null;
101
105
  /** Override the generated id (e.g. when a caller pre-allocates one). */
102
106
  nodeId?: string;
103
107
  }
@@ -179,6 +179,7 @@ export function spawnNode(opts) {
179
179
  parent,
180
180
  spawned_by: spawnedBy,
181
181
  fork_from: opts.forkFrom,
182
+ model_override: opts.modelOverride ?? null,
182
183
  passive_default: opts.passiveDefault ?? false,
183
184
  intent: null,
184
185
  pi_session_id: null,
@@ -46,6 +46,9 @@ export function promote(nodeId, opts = {}) {
46
46
  const { launch } = buildLaunchSpec(targetKind, 'orchestrator', {
47
47
  lifecycle: opts.resident === true ? 'resident' : node.lifecycle,
48
48
  hasManager: node.parent !== null,
49
+ // Preserve a caller-pinned model tier across the polymorph (the persona
50
+ // default is recomputed fresh for targetKind).
51
+ model: node.model_override ?? undefined,
49
52
  });
50
53
  // Seed a barebones roadmap scaffold if absent so the file exists for a
51
54
  // refresh. Pre-fill its Goal from the node's goal doc when present (set at
@@ -112,7 +112,7 @@ export function resetRoot(nodeId, newSessionId, newSessionFile) {
112
112
  // Re-seed persona_ack to the fresh persona so the pristine `/new`
113
113
  // conversation never gets a spurious mode/lifecycle transition steer (the
114
114
  // persona injector compares against this ack).
115
- const { launch } = buildLaunchSpec(meta.kind, 'base', { lifecycle: 'resident', hasManager: false });
115
+ const { launch } = buildLaunchSpec(meta.kind, 'base', { lifecycle: 'resident', hasManager: false, model: meta.model_override ?? undefined });
116
116
  updateNode(nodeId, {
117
117
  mode: 'base',
118
118
  lifecycle: 'resident',
@@ -180,7 +180,7 @@ export function relaunchRoot(oldId, pane, deps = {}) {
180
180
  window: oldMeta.window ?? null,
181
181
  };
182
182
  const newId = newNodeId();
183
- const { launch } = buildLaunchSpec(oldMeta.kind, 'base', { lifecycle: 'resident', hasManager: false });
183
+ const { launch } = buildLaunchSpec(oldMeta.kind, 'base', { lifecycle: 'resident', hasManager: false, model: oldMeta.model_override ?? undefined });
184
184
  // Park-old + mint-new is the single most fragile spot in the runtime, so it is
185
185
  // ONE atomic unit: every ROW write below runs inside a sqlite transaction. A
186
186
  // failure anywhere — including the respawn DISPATCH — rolls the whole thing
@@ -206,6 +206,7 @@ export function relaunchRoot(oldId, pane, deps = {}) {
206
206
  parent: null,
207
207
  spawnedBy: oldId, // audit-only successor link; does NOT touch the spine
208
208
  nodeId: newId,
209
+ modelOverride: oldMeta.model_override,
209
210
  launch,
210
211
  });
211
212
  transition(newId, 'yield'); // active (from spawn) + intent=refresh safety net
@@ -39,6 +39,10 @@ export interface SpawnChildOpts {
39
39
  * headless broker. Persisted as `host_kind` at birth (resolved from
40
40
  * `--headless` / the `headless` config default by the caller). */
41
41
  hostKind?: 'tmux' | 'broker';
42
+ /** Pin the node to a model TIER (ultra/strong/medium/light), overriding the
43
+ * persona's declared default. Persisted to `meta.model_override` so it
44
+ * survives polymorphs. Omit to use the persona default. */
45
+ model?: string;
42
46
  }
43
47
  /** Resolve a `--fork-from` value to the source pi gets as `--fork <path|id>`.
44
48
  * A live node id resolves to its captured session FILE (absolute, cwd-immune),
@@ -161,7 +161,7 @@ export function spawnChild(opts) {
161
161
  // Spine: a managed child reports up to its spawner (has a manager); an
162
162
  // independent root sits top-of-spine with nobody to push to. Mirrors the
163
163
  // `parent` set below (root ? null : spawner), so hasManager === parent!==null.
164
- const { launch } = buildLaunchSpec(opts.kind, mode, { lifecycle, hasManager: !root });
164
+ const { launch } = buildLaunchSpec(opts.kind, mode, { lifecycle, hasManager: !root, model: opts.model });
165
165
  // Name the worker from its task now, so its first editor label carries it.
166
166
  const meta = spawnNode({
167
167
  kind: opts.kind,
@@ -180,6 +180,7 @@ export function spawnChild(opts) {
180
180
  // identity over the source's copied-in conversation.
181
181
  forkFrom: opts.forkFrom,
182
182
  hostKind: opts.hostKind,
183
+ modelOverride: opts.model,
183
184
  launch,
184
185
  });
185
186
  // Persist the task as the child's goal for a fresh revive to re-read.
@@ -14,8 +14,7 @@ export declare function renderPreferencesSection(nodeId: string): string;
14
14
  * drops it when ''). Holds every eligible `kind: reference` resolver doc at its
15
15
  * `system-prompt-visibility` (reference boot default is `none`, so only
16
16
  * author-promoted references show) PLUS the node-local memory docs (any kind),
17
- * each a `###` sub-section. Replaces the old `<memory>` block. Returns '' when
18
- * nothing is eligible.
17
+ * each a `###` sub-section. Returns '' when nothing is eligible.
19
18
  *
20
19
  * DEFENSIVE: each doc is rendered in its own try/catch so a single malformed
21
20
  * doc drops only itself (with a loud stderr warning naming the offending path),
@@ -5,8 +5,7 @@
5
5
  // (renderPreferencesSection) — strings the D2 `before_agent_start` extension
6
6
  // splices into the system prompt (built by a sibling AFTER this module);
7
7
  // • the `<crtr-context>` half — `## References` (renderReferencesBlock) — the
8
- // string wired into bearings.ts's session_start message, REPLACING the old
9
- // `<memory>` block.
8
+ // string wired into bearings.ts's session_start message.
10
9
  //
11
10
  // Every doc flows through the same pipeline (design §4/§6/§9):
12
11
  // MemoryDoc → parseSubstrateDoc → (null-filter non-substrate) →
@@ -54,10 +53,9 @@ function resolverDocs(subject, kind) {
54
53
  * canvas home (`nodes/<id>/context/memory/`) and is OUTSIDE the scope resolver,
55
54
  * so it is loaded directly here, its raw frontmatter run through
56
55
  * parseSubstrateFrontmatter (mirroring the resolver pipeline), then gate-passed.
57
- * Old-format / non-substrate files (incl. the MEMORY.md index, which has no
58
- * `kind`) parse to null and drop out. Returned across ALL kinds — node-local is
59
- * the catch-all this-node store and rides into the references block as the
60
- * replacement for the old node-local `<memory>` stanza.
56
+ * Non-substrate files (those with no `kind`) parse to null and drop out.
57
+ * Returned across ALL kinds — node-local is the catch-all this-node store and
58
+ * rides into the references block.
61
59
  *
62
60
  * IMPORTANT: node-local docs are NOT filtered by `systemPromptVisibility` rung.
63
61
  * A migrated node-local reference defaults to rung `none`, which would make it
@@ -122,8 +120,8 @@ function renderSubSection(d) {
122
120
  * resolver-provided, NOT migrated — named-plugin skills at user/project scope;
123
121
  * the SCOPE_SKILL_PLUGIN sentinel and builtin scope are EXCLUDED because they
124
122
  * become substrate docs). Reuses skill/shared.ts's renderCatalogSection
125
- * group-collapse so the boot catalog matches today's `<skills count=N>` shape.
126
- * Returns '' when nothing is in the catalog. */
123
+ * group-collapse for a compact, source-grouped catalog. Returns '' when nothing
124
+ * is in the catalog. */
127
125
  function renderSkillCatalog(nameRungSkillDocs) {
128
126
  let pluginSkills;
129
127
  try {
@@ -138,8 +136,8 @@ function renderSkillCatalog(nameRungSkillDocs) {
138
136
  ];
139
137
  if (entries.length === 0)
140
138
  return '';
141
- // Group by scope+plugin → forest-root names (drop nested children), mirroring
142
- // buildSkillCatalog's source construction.
139
+ // Group by scope+plugin → forest-root names (drop nested children) so each
140
+ // source contributes only its top-level skills.
143
141
  const bySource = new Map();
144
142
  for (const e of entries) {
145
143
  const key = `${e.scope}\t${e.plugin}`;
@@ -225,8 +223,7 @@ export function renderPreferencesSection(nodeId) {
225
223
  * drops it when ''). Holds every eligible `kind: reference` resolver doc at its
226
224
  * `system-prompt-visibility` (reference boot default is `none`, so only
227
225
  * author-promoted references show) PLUS the node-local memory docs (any kind),
228
- * each a `###` sub-section. Replaces the old `<memory>` block. Returns '' when
229
- * nothing is eligible.
226
+ * each a `###` sub-section. Returns '' when nothing is eligible.
230
227
  *
231
228
  * DEFENSIVE: each doc is rendered in its own try/catch so a single malformed
232
229
  * doc drops only itself (with a loud stderr warning naming the offending path),
@@ -21,15 +21,17 @@ export declare const KIND_DEFAULT_RUNGS: Record<DocKind, {
21
21
  * `all`/`any`/`not` combinators (design §4). */
22
22
  export type GatePredicate = Record<string, unknown>;
23
23
  /** The frontmatter-derived schema of a substrate document, with kind-aware
24
- * defaults applied. Required fields (`kind`/`when`/`why`) and optionals all
25
- * resolved to concrete typed values. */
24
+ * defaults applied. Required fields (`kind`/`when-and-why-to-read`) and
25
+ * optionals all resolved to concrete typed values. */
26
26
  export interface SubstrateSchema {
27
27
  /** Which of the three semantic kinds. */
28
28
  kind: DocKind;
29
- /** Routing condition"When you are in a situation like X…" (design §4). */
30
- when: string;
31
- /** Payoff "…because Z." Half of the generated preview line. */
32
- why: string;
29
+ /** The read-routing line a single sentence answering WHEN to read this doc
30
+ * and WHY it is worth the read: "When <circumstance>, this <kind> should be
31
+ * read <because <payoff>>." (design §4). This is read-routing why an agent
32
+ * should spend the read — NEVER justification of why the content should be
33
+ * obeyed. It IS the preview verbatim. Frontmatter key `when-and-why-to-read`. */
34
+ whenAndWhyToRead: string;
33
35
  /** Human-facing abbreviation for `crtr memory list`. NEVER loaded into an
34
36
  * agent's context (design §3). Empty string when absent. */
35
37
  shortForm: string;
@@ -62,15 +64,18 @@ export interface SubstrateDoc extends SubstrateSchema {
62
64
  * resolver) into a typed schema with defaults applied. Returns `null` when the
63
65
  * record is absent or carries no valid `kind` — i.e. it is not a substrate
64
66
  * document and cannot be classified or defaulted. Tolerant of every other
65
- * imperfection (missing `when`/`why` default to '', a bad rung falls back to
66
- * the kind default), so a renderer mapping over many docs never throws. */
67
+ * imperfection (a missing `when-and-why-to-read` defaults to '', a bad rung
68
+ * falls back to the kind default), so a renderer mapping over many docs never
69
+ * throws. Authoring-time enforcement of the field lives in `crtr memory lint`. */
67
70
  export declare function parseSubstrateFrontmatter(fm: Record<string, unknown> | null): SubstrateSchema | null;
68
71
  /** Parse a resolved MemoryDoc into a fully-typed SubstrateDoc (schema + the
69
72
  * resolver's name/scope/path/body). Returns `null` for a non-substrate doc
70
73
  * (no valid `kind`), so callers can `docs.map(parseSubstrateDoc).filter(...)`. */
71
74
  export declare function parseSubstrateDoc(doc: MemoryDoc): SubstrateDoc | null;
72
- /** The generated `preview`-rung routing line (design §4), composed from the two
73
- * required prose fields and the kind: `"{when}, read this {kind}. {why}."`.
74
- * Both boot and on-read render render this identical line, so it lives once
75
- * here to prevent drift. Light cleanup avoids doubled punctuation. */
76
- export declare function previewLine(doc: Pick<SubstrateSchema, 'kind' | 'when' | 'why'>): string;
75
+ /** The `preview`-rung routing line (design §4): the `when-and-why-to-read`
76
+ * field rendered essentially verbatim it is already authored as the complete
77
+ * routing sentence ("When …, this <kind> should be read …"), so there is no
78
+ * template to compose. Both boot and on-read render this identical line, so it
79
+ * lives once here to prevent drift. Light cleanup normalizes the trailing
80
+ * period. */
81
+ export declare function previewLine(doc: Pick<SubstrateSchema, 'whenAndWhyToRead'>): string;
@@ -50,8 +50,9 @@ export const KIND_DEFAULT_RUNGS = {
50
50
  * resolver) into a typed schema with defaults applied. Returns `null` when the
51
51
  * record is absent or carries no valid `kind` — i.e. it is not a substrate
52
52
  * document and cannot be classified or defaulted. Tolerant of every other
53
- * imperfection (missing `when`/`why` default to '', a bad rung falls back to
54
- * the kind default), so a renderer mapping over many docs never throws. */
53
+ * imperfection (a missing `when-and-why-to-read` defaults to '', a bad rung
54
+ * falls back to the kind default), so a renderer mapping over many docs never
55
+ * throws. Authoring-time enforcement of the field lives in `crtr memory lint`. */
55
56
  export function parseSubstrateFrontmatter(fm) {
56
57
  if (fm === null)
57
58
  return null;
@@ -61,8 +62,7 @@ export function parseSubstrateFrontmatter(fm) {
61
62
  const defaults = KIND_DEFAULT_RUNGS[kind];
62
63
  return {
63
64
  kind,
64
- when: strField(fm.when),
65
- why: strField(fm.why),
65
+ whenAndWhyToRead: strField(fm['when-and-why-to-read']),
66
66
  shortForm: strField(fm['short-form']),
67
67
  systemPromptVisibility: parseRung(fm['system-prompt-visibility'], defaults.systemPrompt),
68
68
  fileReadVisibility: parseRung(fm['file-read-visibility'], defaults.fileRead),
@@ -79,14 +79,15 @@ export function parseSubstrateDoc(doc) {
79
79
  return null;
80
80
  return { ...schema, name: doc.name, scope: doc.scope, path: doc.path, body: doc.body };
81
81
  }
82
- /** The generated `preview`-rung routing line (design §4), composed from the two
83
- * required prose fields and the kind: `"{when}, read this {kind}. {why}."`.
84
- * Both boot and on-read render render this identical line, so it lives once
85
- * here to prevent drift. Light cleanup avoids doubled punctuation. */
82
+ /** The `preview`-rung routing line (design §4): the `when-and-why-to-read`
83
+ * field rendered essentially verbatim it is already authored as the complete
84
+ * routing sentence ("When …, this <kind> should be read …"), so there is no
85
+ * template to compose. Both boot and on-read render this identical line, so it
86
+ * lives once here to prevent drift. Light cleanup normalizes the trailing
87
+ * period. */
86
88
  export function previewLine(doc) {
87
- const when = doc.when.trim().replace(/[.,]+$/, '');
88
- const why = doc.why.trim().replace(/\.+$/, '');
89
- return `${when}, read this ${doc.kind}. ${why}.`;
89
+ const line = doc.whenAndWhyToRead.trim().replace(/\.+$/, '');
90
+ return line === '' ? '' : `${line}.`;
90
91
  }
91
92
  // ---------------------------------------------------------------------------
92
93
  // Field coercion helpers (private).
@@ -15,9 +15,11 @@
15
15
  //
16
16
  // The block carries: the path to the node's own context dir and the framing for
17
17
  // what belongs there (a shared document store for the other nodes). EVERY node
18
- // also gets a <memory> block merging the indexes of its three scoped memory
19
- // stores (user-global, project, node-local), each labeled with its dir — one
20
- // consistent surface, since every node is born with all three stores. An
18
+ // also gets a `## References` block rendered from the document substrate
19
+ // eligible `reference` docs at their system-prompt visibility rung, plus the
20
+ // node's own node-local substrate docs so the bearings name what to read on
21
+ // demand. (Skills and preferences surface as their own `## Skills` /
22
+ // `## Preferences` sections of the system prompt, not in this block.) An
21
23
  // orchestrator additionally gets the across-refresh-cycles framing (the one
22
24
  // thing a terminal worker's bearings drop). The prose lives in
23
25
  // core/runtime/bearings.ts (shared with the promotion guidance dump).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crouton-kit/crouter",
3
- "version": "0.3.19",
3
+ "version": "0.3.21",
4
4
  "description": "crtr — fast access to skills, plugins, and marketplaces",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",