@crouton-kit/crouter 0.3.14 → 0.3.16
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.
- package/dist/build-root.d.ts +8 -0
- package/dist/build-root.js +30 -0
- package/dist/builtin-personas/design/base.md +3 -7
- package/dist/builtin-personas/design/orchestrator.md +4 -3
- package/dist/builtin-personas/developer/base.md +3 -7
- package/dist/builtin-personas/developer/orchestrator.md +5 -4
- package/dist/builtin-personas/explore/base.md +3 -7
- package/dist/builtin-personas/explore/orchestrator.md +1 -5
- package/dist/builtin-personas/general/base.md +2 -4
- package/dist/builtin-personas/general/orchestrator.md +2 -4
- package/dist/builtin-personas/lifecycle/resident.md +2 -0
- package/dist/builtin-personas/lifecycle/terminal.md +6 -0
- package/dist/builtin-personas/orchestration-kernel.md +42 -3
- package/dist/builtin-personas/plan/base.md +3 -5
- package/dist/builtin-personas/plan/orchestrator.md +5 -4
- package/dist/builtin-personas/plan/reviewers/architecture-fit/base.md +9 -0
- package/dist/builtin-personas/plan/reviewers/code-smells/base.md +9 -0
- package/dist/builtin-personas/plan/reviewers/pattern-consistency/base.md +9 -0
- package/dist/builtin-personas/plan/reviewers/requirements-coverage/base.md +9 -0
- package/dist/builtin-personas/plan/reviewers/security/base.md +9 -0
- package/dist/builtin-personas/review/base.md +3 -5
- package/dist/builtin-personas/review/orchestrator.md +2 -6
- package/dist/builtin-personas/runtime-base.md +3 -19
- package/dist/builtin-personas/spec/base.md +3 -5
- package/dist/builtin-personas/spec/orchestrator.md +4 -3
- package/dist/builtin-personas/spine/has-manager.md +10 -0
- package/dist/builtin-personas/spine/no-manager.md +2 -0
- package/dist/builtin-skills/skills/crouter-development/personas/SKILL.md +96 -0
- package/dist/builtin-skills/skills/crouter-development/personas/base-prompt/SKILL.md +49 -0
- package/dist/builtin-skills/skills/crouter-development/personas/orchestrator-prompt/SKILL.md +49 -0
- package/dist/builtin-skills/skills/planning/SKILL.md +1 -1
- package/dist/builtin-skills/skills/spec/SKILL.md +2 -2
- package/dist/cli.js +6 -29
- package/dist/commands/attention.js +76 -7
- package/dist/commands/canvas-prune.d.ts +2 -0
- package/dist/commands/canvas-prune.js +66 -0
- package/dist/commands/canvas.js +5 -8
- package/dist/commands/chord.d.ts +2 -0
- package/dist/commands/chord.js +143 -0
- package/dist/commands/daemon.js +8 -5
- package/dist/commands/dashboard.js +2 -0
- package/dist/commands/human/prompts.js +28 -27
- package/dist/commands/human/queue.js +30 -14
- package/dist/commands/human/shared.d.ts +26 -21
- package/dist/commands/human/shared.js +45 -67
- package/dist/commands/human.js +4 -14
- package/dist/commands/node.d.ts +11 -0
- package/dist/commands/node.js +221 -99
- package/dist/commands/pkg/market-inspect.js +6 -4
- package/dist/commands/pkg/market-manage.js +10 -6
- package/dist/commands/pkg/market.js +2 -4
- package/dist/commands/pkg/plugin-inspect.js +6 -4
- package/dist/commands/pkg/plugin-manage.js +12 -7
- package/dist/commands/pkg/plugin.js +2 -4
- package/dist/commands/pkg.js +0 -4
- package/dist/commands/push.js +178 -15
- package/dist/commands/revive.js +5 -3
- package/dist/commands/skill/author.js +6 -4
- package/dist/commands/skill/find.js +8 -5
- package/dist/commands/skill/read.js +2 -0
- package/dist/commands/skill/state.js +6 -4
- package/dist/commands/skill.js +0 -6
- package/dist/commands/sys/config.js +21 -7
- package/dist/commands/sys/doctor.js +2 -0
- package/dist/commands/sys/update.js +4 -0
- package/dist/commands/sys.js +0 -6
- package/dist/commands/tmux-spread.d.ts +2 -0
- package/dist/commands/tmux-spread.js +129 -0
- package/dist/core/__tests__/canvas-inbox-watcher.test.js +25 -0
- package/dist/core/__tests__/child-followup.test.d.ts +1 -0
- package/dist/core/__tests__/child-followup.test.js +83 -0
- package/dist/core/__tests__/close.test.d.ts +1 -0
- package/dist/core/__tests__/close.test.js +148 -0
- package/dist/core/__tests__/context-intro.test.d.ts +1 -0
- package/dist/core/__tests__/context-intro.test.js +196 -0
- package/dist/core/__tests__/daemon-boot.test.d.ts +1 -0
- package/dist/core/__tests__/daemon-boot.test.js +93 -0
- package/dist/core/__tests__/daemon-liveness.test.d.ts +1 -0
- package/dist/core/__tests__/daemon-liveness.test.js +223 -0
- package/dist/core/__tests__/focuses.test.d.ts +1 -0
- package/dist/core/__tests__/focuses.test.js +196 -0
- package/dist/core/__tests__/fork.test.d.ts +1 -0
- package/dist/core/__tests__/fork.test.js +91 -0
- package/dist/core/__tests__/home-session.test.d.ts +1 -0
- package/dist/core/__tests__/home-session.test.js +153 -0
- package/dist/core/__tests__/human-cancel-guard.test.d.ts +1 -0
- package/dist/core/__tests__/human-cancel-guard.test.js +49 -0
- package/dist/core/__tests__/keystone.test.d.ts +1 -0
- package/dist/core/__tests__/keystone.test.js +185 -0
- package/dist/core/__tests__/kickoff.test.d.ts +1 -0
- package/dist/core/__tests__/kickoff.test.js +89 -0
- package/dist/core/__tests__/lifecycle.test.d.ts +1 -0
- package/dist/core/__tests__/lifecycle.test.js +178 -0
- package/dist/core/__tests__/listing-completeness.test.d.ts +1 -0
- package/dist/core/__tests__/listing-completeness.test.js +31 -0
- package/dist/core/__tests__/memory.test.d.ts +1 -0
- package/dist/core/__tests__/memory.test.js +152 -0
- package/dist/core/__tests__/migration.test.d.ts +1 -0
- package/dist/core/__tests__/migration.test.js +238 -0
- package/dist/core/__tests__/pane-column.test.d.ts +1 -0
- package/dist/core/__tests__/pane-column.test.js +153 -0
- package/dist/core/__tests__/passive-subscription.test.js +24 -1
- package/dist/core/__tests__/persona-compose.test.d.ts +1 -0
- package/dist/core/__tests__/persona-compose.test.js +53 -0
- package/dist/core/__tests__/persona-subkind.test.d.ts +1 -0
- package/dist/core/__tests__/persona-subkind.test.js +62 -0
- package/dist/core/__tests__/persona.test.d.ts +1 -0
- package/dist/core/__tests__/persona.test.js +107 -0
- package/dist/core/__tests__/placement-focus.test.d.ts +1 -0
- package/dist/core/__tests__/placement-focus.test.js +266 -0
- package/dist/core/__tests__/placement-reconcile.test.d.ts +1 -0
- package/dist/core/__tests__/placement-reconcile.test.js +212 -0
- package/dist/core/__tests__/placement-revive.test.d.ts +1 -0
- package/dist/core/__tests__/placement-revive.test.js +238 -0
- package/dist/core/__tests__/placement-teardown.test.d.ts +1 -0
- package/dist/core/__tests__/placement-teardown.test.js +178 -0
- package/dist/core/__tests__/prune.test.d.ts +1 -0
- package/dist/core/__tests__/prune.test.js +116 -0
- package/dist/core/__tests__/push-final-guard.test.d.ts +1 -0
- package/dist/core/__tests__/push-final-guard.test.js +71 -0
- package/dist/core/__tests__/relaunch.test.d.ts +1 -0
- package/dist/core/__tests__/relaunch.test.js +334 -0
- package/dist/core/__tests__/reset.test.js +26 -7
- package/dist/core/__tests__/revive.test.d.ts +1 -0
- package/dist/core/__tests__/revive.test.js +217 -0
- package/dist/core/__tests__/spawn-root.test.d.ts +1 -0
- package/dist/core/__tests__/spawn-root.test.js +73 -0
- package/dist/core/__tests__/steer-note.test.d.ts +1 -0
- package/dist/core/__tests__/steer-note.test.js +39 -0
- package/dist/core/__tests__/stop-guard.test.d.ts +1 -0
- package/dist/core/__tests__/stop-guard.test.js +82 -0
- package/dist/core/__tests__/subcommand-tier.test.js +35 -33
- package/dist/core/__tests__/tmux-surface.test.d.ts +1 -0
- package/dist/core/__tests__/tmux-surface.test.js +105 -0
- package/dist/core/__tests__/unknown-path.test.js +8 -2
- package/dist/core/canvas/attention.d.ts +10 -0
- package/dist/core/canvas/attention.js +40 -0
- package/dist/core/canvas/canvas.d.ts +66 -7
- package/dist/core/canvas/canvas.js +209 -21
- package/dist/core/canvas/db.d.ts +8 -0
- package/dist/core/canvas/db.js +205 -4
- package/dist/core/canvas/focuses.d.ts +22 -0
- package/dist/core/canvas/focuses.js +81 -0
- package/dist/core/canvas/index.d.ts +3 -0
- package/dist/core/canvas/index.js +3 -0
- package/dist/core/canvas/labels.d.ts +27 -0
- package/dist/core/canvas/labels.js +36 -0
- package/dist/core/canvas/render.js +25 -10
- package/dist/core/canvas/telemetry.d.ts +14 -0
- package/dist/core/canvas/telemetry.js +35 -0
- package/dist/core/canvas/types.d.ts +115 -12
- package/dist/core/command.d.ts +25 -1
- package/dist/core/command.js +23 -15
- package/dist/core/config.js +36 -2
- package/dist/core/feed/feed.js +3 -3
- package/dist/core/feed/inbox.d.ts +3 -1
- package/dist/core/feed/inbox.js +45 -5
- package/dist/core/feed/passive.js +24 -11
- package/dist/core/help.d.ts +26 -13
- package/dist/core/help.js +44 -37
- package/dist/core/personas/index.d.ts +1 -1
- package/dist/core/personas/index.js +1 -1
- package/dist/core/personas/loader.d.ts +40 -1
- package/dist/core/personas/loader.js +63 -1
- package/dist/core/personas/resolve.d.ts +13 -6
- package/dist/core/personas/resolve.js +46 -34
- package/dist/core/runtime/bearings.d.ts +20 -0
- package/dist/core/runtime/bearings.js +92 -0
- package/dist/core/runtime/close.d.ts +14 -0
- package/dist/core/runtime/close.js +151 -0
- package/dist/core/runtime/demote.js +24 -12
- package/dist/core/runtime/front-door.js +1 -1
- package/dist/core/runtime/kickoff.d.ts +23 -6
- package/dist/core/runtime/kickoff.js +92 -36
- package/dist/core/runtime/launch.d.ts +26 -12
- package/dist/core/runtime/launch.js +78 -19
- package/dist/core/runtime/lifecycle.d.ts +13 -0
- package/dist/core/runtime/lifecycle.js +86 -0
- package/dist/core/runtime/memory.d.ts +43 -0
- package/dist/core/runtime/memory.js +165 -0
- package/dist/core/runtime/naming.d.ts +22 -0
- package/dist/core/runtime/naming.js +166 -0
- package/dist/core/runtime/nodes.d.ts +39 -1
- package/dist/core/runtime/nodes.js +69 -10
- package/dist/core/runtime/persona.d.ts +25 -0
- package/dist/core/runtime/persona.js +139 -0
- package/dist/core/runtime/placement.d.ts +299 -0
- package/dist/core/runtime/placement.js +688 -0
- package/dist/core/runtime/promote.d.ts +14 -7
- package/dist/core/runtime/promote.js +57 -67
- package/dist/core/runtime/reset.d.ts +47 -4
- package/dist/core/runtime/reset.js +223 -52
- package/dist/core/runtime/revive.d.ts +26 -2
- package/dist/core/runtime/revive.js +166 -39
- package/dist/core/runtime/spawn.d.ts +20 -5
- package/dist/core/runtime/spawn.js +163 -43
- package/dist/core/runtime/stop-guard.d.ts +1 -1
- package/dist/core/runtime/stop-guard.js +18 -8
- package/dist/core/runtime/tmux-chrome.d.ts +1 -0
- package/dist/core/runtime/tmux-chrome.js +4 -0
- package/dist/core/runtime/tmux.d.ts +113 -20
- package/dist/core/runtime/tmux.js +221 -39
- package/dist/core/spawn.js +15 -0
- package/dist/daemon/crtrd.d.ts +12 -1
- package/dist/daemon/crtrd.js +152 -34
- package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.d.ts +1 -0
- package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +266 -0
- package/dist/pi-extensions/canvas-commands.js +16 -13
- package/dist/pi-extensions/canvas-context-intro.d.ts +70 -0
- package/dist/pi-extensions/canvas-context-intro.js +164 -0
- package/dist/pi-extensions/canvas-goal-capture.d.ts +3 -0
- package/dist/pi-extensions/canvas-goal-capture.js +15 -1
- package/dist/pi-extensions/canvas-inbox-watcher.js +11 -0
- package/dist/pi-extensions/canvas-nav.d.ts +12 -4
- package/dist/pi-extensions/canvas-nav.js +594 -262
- package/dist/pi-extensions/canvas-resume.d.ts +22 -0
- package/dist/pi-extensions/canvas-resume.js +173 -0
- package/dist/pi-extensions/canvas-stophook.d.ts +16 -0
- package/dist/pi-extensions/canvas-stophook.js +340 -228
- package/dist/types.d.ts +28 -0
- package/dist/types.js +16 -0
- package/package.json +2 -2
- package/dist/core/runtime/presence.d.ts +0 -38
- package/dist/core/runtime/presence.js +0 -154
package/dist/core/feed/inbox.js
CHANGED
|
@@ -89,12 +89,55 @@ export function writeCursor(nodeId, iso) {
|
|
|
89
89
|
// ---------------------------------------------------------------------------
|
|
90
90
|
// Coalesce
|
|
91
91
|
// ---------------------------------------------------------------------------
|
|
92
|
+
/** Bounds for inlining a ref-less entry's body in the digest. */
|
|
93
|
+
const BODY_MAX_LINES = 12;
|
|
94
|
+
const BODY_MAX_CHARS = 1000;
|
|
95
|
+
/** Clip a body to a bounded preview, reporting whether anything was dropped. */
|
|
96
|
+
function clipBody(body) {
|
|
97
|
+
let text = body;
|
|
98
|
+
let clipped = false;
|
|
99
|
+
const lines = text.split('\n');
|
|
100
|
+
if (lines.length > BODY_MAX_LINES) {
|
|
101
|
+
text = lines.slice(0, BODY_MAX_LINES).join('\n');
|
|
102
|
+
clipped = true;
|
|
103
|
+
}
|
|
104
|
+
if (text.length > BODY_MAX_CHARS) {
|
|
105
|
+
text = text.slice(0, BODY_MAX_CHARS);
|
|
106
|
+
clipped = true;
|
|
107
|
+
}
|
|
108
|
+
return { text: text.trimEnd(), clipped };
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Render one entry's digest line(s).
|
|
112
|
+
*
|
|
113
|
+
* A push pointer (has a `ref`) stays a pointer — the body lives in the report
|
|
114
|
+
* file, dereferenced on demand by reading that path. A ref-less entry (a direct
|
|
115
|
+
* `node msg` or a system alert) has NO report to dereference; its full body
|
|
116
|
+
* lives only in `data.body`, and `label` is just the first line truncated. So
|
|
117
|
+
* for those we inline the body (bounded) — rendering only the truncated label
|
|
118
|
+
* would strand the rest with nowhere to recover it.
|
|
119
|
+
*/
|
|
120
|
+
function renderEntry(e) {
|
|
121
|
+
if (e.ref !== undefined) {
|
|
122
|
+
return ` [${e.kind}] ${e.label} (ref: ${e.ref})`;
|
|
123
|
+
}
|
|
124
|
+
const body = typeof e.data?.['body'] === 'string' ? e.data['body'].trim() : '';
|
|
125
|
+
if (body === '' || body === e.label) {
|
|
126
|
+
return ` [${e.kind}] ${e.label}`;
|
|
127
|
+
}
|
|
128
|
+
const { text, clipped } = clipBody(body);
|
|
129
|
+
const indented = text.split('\n').map((l) => ` ${l}`).join('\n');
|
|
130
|
+
const more = clipped ? '\n … (body clipped)' : '';
|
|
131
|
+
return ` [${e.kind}]\n${indented}${more}`;
|
|
132
|
+
}
|
|
92
133
|
/**
|
|
93
134
|
* Render many unread inbox pointers into one compact digest string.
|
|
94
135
|
*
|
|
95
136
|
* Format (per sender group):
|
|
96
137
|
* From <sender> — <N> update(s):
|
|
97
|
-
* [<kind>] <label> (ref: <path>)
|
|
138
|
+
* [<kind>] <label> (ref: <path>) ← push: pointer, dereference the ref
|
|
139
|
+
* [<kind>] ← ref-less msg: full body inlined
|
|
140
|
+
* <body line>
|
|
98
141
|
* …
|
|
99
142
|
*
|
|
100
143
|
* A header line announces the total count and instructs the receiver to
|
|
@@ -114,10 +157,7 @@ export function coalesce(entries) {
|
|
|
114
157
|
}
|
|
115
158
|
const sections = [];
|
|
116
159
|
for (const [sender, items] of groups) {
|
|
117
|
-
const lines = items.map(
|
|
118
|
-
const refPart = e.ref !== undefined ? ` (ref: ${e.ref})` : '';
|
|
119
|
-
return ` [${e.kind}] ${e.label}${refPart}`;
|
|
120
|
-
});
|
|
160
|
+
const lines = items.map(renderEntry);
|
|
121
161
|
sections.push(`From ${sender} — ${items.length} update${items.length === 1 ? '' : 's'}:\n${lines.join('\n')}`);
|
|
122
162
|
}
|
|
123
163
|
return header + sections.join('\n\n');
|
|
@@ -29,15 +29,32 @@ export function appendPassive(nodeId, entry) {
|
|
|
29
29
|
appendFileSync(passivePath(nodeId), line, { encoding: 'utf8', flag: 'a' });
|
|
30
30
|
return full;
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Parse jsonl text into entries, tolerating corruption: each line is parsed on
|
|
34
|
+
* its own and a single malformed line is SKIPPED, never the whole feed. The
|
|
35
|
+
* drain path renames + deletes its snapshot, so a non-tolerant parse here would
|
|
36
|
+
* silently discard every accumulated entry the moment one bad line appears.
|
|
37
|
+
*/
|
|
38
|
+
function parseEntries(text) {
|
|
39
|
+
const entries = [];
|
|
40
|
+
for (const line of text.split('\n')) {
|
|
41
|
+
if (line.trim() === '')
|
|
42
|
+
continue;
|
|
43
|
+
try {
|
|
44
|
+
entries.push(JSON.parse(line));
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// Skip only the corrupt line — the rest of the feed survives.
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return entries;
|
|
51
|
+
}
|
|
32
52
|
/** Return every accumulated passive entry (oldest first) without clearing. */
|
|
33
53
|
export function readPassive(nodeId) {
|
|
34
54
|
const p = passivePath(nodeId);
|
|
35
55
|
if (!existsSync(p))
|
|
36
56
|
return [];
|
|
37
|
-
return readFileSync(p, 'utf8')
|
|
38
|
-
.split('\n')
|
|
39
|
-
.filter((l) => l.trim() !== '')
|
|
40
|
-
.map((l) => JSON.parse(l));
|
|
57
|
+
return parseEntries(readFileSync(p, 'utf8'));
|
|
41
58
|
}
|
|
42
59
|
/**
|
|
43
60
|
* Read AND clear the accumulator in one shot — the drain-on-message primitive.
|
|
@@ -59,15 +76,11 @@ export function drainPassive(nodeId) {
|
|
|
59
76
|
// Lost the race (file vanished) — nothing to drain.
|
|
60
77
|
return [];
|
|
61
78
|
}
|
|
79
|
+
// Parse with the tolerant per-line parser BEFORE removing the snapshot, so a
|
|
80
|
+
// single corrupt line can never discard the whole accumulated feed.
|
|
62
81
|
let entries = [];
|
|
63
82
|
try {
|
|
64
|
-
entries = readFileSync(snapshot, 'utf8')
|
|
65
|
-
.split('\n')
|
|
66
|
-
.filter((l) => l.trim() !== '')
|
|
67
|
-
.map((l) => JSON.parse(l));
|
|
68
|
-
}
|
|
69
|
-
catch {
|
|
70
|
-
entries = [];
|
|
83
|
+
entries = parseEntries(readFileSync(snapshot, 'utf8'));
|
|
71
84
|
}
|
|
72
85
|
finally {
|
|
73
86
|
try {
|
package/dist/core/help.d.ts
CHANGED
|
@@ -56,18 +56,25 @@ export type InputParam = PositionalParam | FlagParam | StdinParam | ContextFileP
|
|
|
56
56
|
* - common — ALSO promoted into the parent's -h, as a bare qualified name.
|
|
57
57
|
* - important — ALSO promoted into the parent's -h, name + shortform desc. */
|
|
58
58
|
export type SubTier = 'hidden' | 'normal' | 'common' | 'important';
|
|
59
|
-
/**
|
|
60
|
-
*
|
|
61
|
-
|
|
59
|
+
/** A child's assembled parent-level listing entry — computed by defineBranch
|
|
60
|
+
* from each child def's own self-description (`description`/`whenToUse`/`tier`).
|
|
61
|
+
* renderBranch consumes this; it is never authored by hand and there is no
|
|
62
|
+
* parent-side copy of a child's description (principle 16: each node owns its
|
|
63
|
+
* representation one level up). */
|
|
64
|
+
export interface ListingChild {
|
|
62
65
|
name: string;
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
*
|
|
66
|
+
/** Short description for this child's <subcommand> row. */
|
|
67
|
+
description: string;
|
|
68
|
+
/** Selection rubric — plainly states when to reach for this command. Expansive
|
|
69
|
+
* with a variety of examples for judgment-heavy commands; concise for
|
|
70
|
+
* genuinely single-purpose ones. Rendered verbatim (no prefix). */
|
|
71
|
+
whenToUse: string;
|
|
72
|
+
/** Visibility tier in ancestor listings (see SubTier). 'hidden' children are
|
|
73
|
+
* dropped from every listing. */
|
|
74
|
+
tier: SubTier;
|
|
75
|
+
/** How many non-hidden subcommands this child itself owns — drives the
|
|
76
|
+
* `subcommands="N"` attribute when a branch child is listed without
|
|
77
|
+
* expansion. Absent for leaves and childless branches. */
|
|
71
78
|
subCount?: number;
|
|
72
79
|
}
|
|
73
80
|
/** A subtree's self-description at the parent (root) level. Each subtree owns
|
|
@@ -126,14 +133,20 @@ export interface RootCommand {
|
|
|
126
133
|
}
|
|
127
134
|
export interface BranchHelp {
|
|
128
135
|
name: string;
|
|
136
|
+
/** The command's own description — rendered as the `description` attribute of
|
|
137
|
+
* its <command> card at its own -h. */
|
|
129
138
|
summary: string;
|
|
130
|
-
/** Local
|
|
139
|
+
/** Local model prose orienting the agent to what the subtree contains and how
|
|
140
|
+
* the children differ as a group — never a per-child restatement (each
|
|
141
|
+
* child's purpose lives in its own listing row). */
|
|
131
142
|
model?: string;
|
|
132
143
|
/** Bounded runtime aggregate as a complete self-named state element (build
|
|
133
144
|
* it with stateBlock), e.g. `<skills count="42">…</skills>`. Renderer
|
|
134
145
|
* soft-fails to omission if this returns null or throws. */
|
|
135
146
|
dynamicState?: () => string | null;
|
|
136
|
-
|
|
147
|
+
/** Parent-level listing assembled by defineBranch from the actual child defs.
|
|
148
|
+
* renderBranch reads this; never author it by hand. */
|
|
149
|
+
listing?: ListingChild[];
|
|
137
150
|
}
|
|
138
151
|
export interface LeafHelp {
|
|
139
152
|
name: string;
|
package/dist/core/help.js
CHANGED
|
@@ -50,10 +50,13 @@ const IO_CONTRACT = 'I/O contract: flags and positional args on input; stdout is
|
|
|
50
50
|
'Exit 0 on success, non-zero on failure. Schemas appear at leaf -h.';
|
|
51
51
|
// Behavioral instruction (not a schema) — engrained in the appended system
|
|
52
52
|
// prompt so the model treats unfamiliar capabilities as a cue to discover the
|
|
53
|
-
// contract, never to guess
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
'
|
|
53
|
+
// contract, never to guess, AND reads a command's contract before invoking it.
|
|
54
|
+
// Lives in the root guide, outside any leaf -h.
|
|
55
|
+
const CAPABILITY_DISCOVERY = 'Before running a crtr command whose exact contract (args, flags, effects) ' +
|
|
56
|
+
"you haven't verified this session, run `-h` on it and read the schema first " +
|
|
57
|
+
'— a reliable read beats a guess that wastes a turn or triggers an unintended ' +
|
|
58
|
+
"effect. Same when the user names a capability you don't fully recognize: " +
|
|
59
|
+
'`-h` it before acting.';
|
|
57
60
|
/** Lines for a command's subcommand affordance at root: any promoted
|
|
58
61
|
* (common/important) subcommands, then a remainder line naming how many other
|
|
59
62
|
* subcommands exist behind `crtr <name> -h`. Returns [] when the command has
|
|
@@ -109,13 +112,18 @@ export function renderRoot(h) {
|
|
|
109
112
|
lines.push('</command>');
|
|
110
113
|
lines.push('');
|
|
111
114
|
}
|
|
112
|
-
// Globals block (footer)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
// Globals block (footer) — rendered only when globals exist, so an empty
|
|
116
|
+
// list never leaves a bare "Globals" header. -h itself is not a global: the
|
|
117
|
+
// capability-discovery rule below teaches -h usage with its reasoning, so no
|
|
118
|
+
// per-command CTA or standalone "-h: print help" stub is needed.
|
|
119
|
+
if (h.globals.length > 0) {
|
|
120
|
+
lines.push('Globals');
|
|
121
|
+
const gNameW = maxLen(h.globals.map((g) => g.name));
|
|
122
|
+
for (const g of h.globals) {
|
|
123
|
+
lines.push(` ${pad(g.name, gNameW)} ${g.desc}`);
|
|
124
|
+
}
|
|
125
|
+
lines.push('');
|
|
117
126
|
}
|
|
118
|
-
lines.push('');
|
|
119
127
|
lines.push(IO_CONTRACT);
|
|
120
128
|
lines.push('');
|
|
121
129
|
lines.push(CAPABILITY_DISCOVERY);
|
|
@@ -124,38 +132,37 @@ export function renderRoot(h) {
|
|
|
124
132
|
// ---------------------------------------------------------------------------
|
|
125
133
|
// renderBranch
|
|
126
134
|
// ---------------------------------------------------------------------------
|
|
135
|
+
/** Escape a value for a rendered XML attribute. Output is light XML around
|
|
136
|
+
* markdown read as prose by a model, not parsed — so we only guard the
|
|
137
|
+
* double-quote that would visually break the attribute, swapping it for a
|
|
138
|
+
* single quote rather than emitting noisy entities. */
|
|
139
|
+
function attr(s) {
|
|
140
|
+
return s.replace(/"/g, "'");
|
|
141
|
+
}
|
|
127
142
|
export function renderBranch(h) {
|
|
128
143
|
const lines = [];
|
|
129
|
-
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
//
|
|
144
|
+
// The branch renders as one <command> card: its own description in the
|
|
145
|
+
// opening attribute, then orientation prose / live state, then one
|
|
146
|
+
// self-closing <subcommand> per child. Each child's description + whenToUse
|
|
147
|
+
// are assembled by defineBranch from the child's own self-description, so the
|
|
148
|
+
// parent never restates what a child is — the child owns its representation.
|
|
149
|
+
lines.push(`<command name="${h.name}" description="${attr(h.summary)}">`);
|
|
134
150
|
const branchState = evalDynamic(h.dynamicState);
|
|
135
|
-
if (branchState !== null)
|
|
136
|
-
// dynamicState returns a complete self-named element — emit as-is.
|
|
137
|
-
lines.push('');
|
|
151
|
+
if (branchState !== null)
|
|
138
152
|
lines.push(branchState);
|
|
139
|
-
|
|
140
|
-
if (h.model !== undefined) {
|
|
141
|
-
lines.push('');
|
|
153
|
+
if (h.model !== undefined)
|
|
142
154
|
lines.push(h.model);
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
if (c.subCount !== undefined && c.subCount > 0) {
|
|
155
|
-
line += ` [+${c.subCount} subcommand${c.subCount === 1 ? '' : 's'}]`;
|
|
156
|
-
}
|
|
157
|
-
lines.push(line);
|
|
158
|
-
}
|
|
155
|
+
for (const c of h.listing ?? []) {
|
|
156
|
+
if (c.tier === 'hidden')
|
|
157
|
+
continue;
|
|
158
|
+
const subs = c.subCount !== undefined && c.subCount > 0 ? ` subcommands="${c.subCount}"` : '';
|
|
159
|
+
// whenToUse plainly states when to reach for this child, rendered verbatim —
|
|
160
|
+
// expansive with examples for judgment-heavy commands, concise for
|
|
161
|
+
// single-purpose ones. It does not restate "read my -h"; the
|
|
162
|
+
// capability-discovery rule in the root footer already teaches that.
|
|
163
|
+
lines.push(`<subcommand name="${c.name}" description="${attr(c.description)}" whenToUse="${attr(c.whenToUse)}"${subs}/>`);
|
|
164
|
+
}
|
|
165
|
+
lines.push('</command>');
|
|
159
166
|
return lines.join('\n');
|
|
160
167
|
}
|
|
161
168
|
// ---------------------------------------------------------------------------
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* - resolve (high-level composer)
|
|
7
7
|
* - ResolvedPersona (return type of resolve)
|
|
8
8
|
*/
|
|
9
|
-
export { loadPersona, loadKernel, availableKinds } from './loader.js';
|
|
9
|
+
export { loadPersona, loadKernel, availableKinds, loadLifecycleFragment, loadSpineFragment } from './loader.js';
|
|
10
10
|
export type { LoadedPersona } from './loader.js';
|
|
11
11
|
export { resolve } from './resolve.js';
|
|
12
12
|
export type { ResolvedPersona } from './resolve.js';
|
|
@@ -6,5 +6,5 @@
|
|
|
6
6
|
* - resolve (high-level composer)
|
|
7
7
|
* - ResolvedPersona (return type of resolve)
|
|
8
8
|
*/
|
|
9
|
-
export { loadPersona, loadKernel, availableKinds } from './loader.js';
|
|
9
|
+
export { loadPersona, loadKernel, availableKinds, loadLifecycleFragment, loadSpineFragment } from './loader.js';
|
|
10
10
|
export { resolve } from './resolve.js';
|
|
@@ -33,12 +33,51 @@ export declare function loadPersona(kind: string, mode: 'base' | 'orchestrator')
|
|
|
33
33
|
export declare function loadKernel(): string;
|
|
34
34
|
/**
|
|
35
35
|
* Load the base runtime prompt — the node operating protocol prepended to
|
|
36
|
-
* EVERY persona (
|
|
36
|
+
* EVERY persona (delegate/ask/promote). Returns '' if not found. The
|
|
37
|
+
* lifecycle/spine-specific sections (finish vs. dormant, report-up vs. silent)
|
|
38
|
+
* live in their own fragments, loaded below.
|
|
37
39
|
*/
|
|
38
40
|
export declare function loadRuntimeBase(): string;
|
|
41
|
+
/**
|
|
42
|
+
* Load the lifecycle fragment — the "how you end" contract, keyed on the node's
|
|
43
|
+
* lifecycle axis: `terminal` (drive to done + `push final`) or `resident`
|
|
44
|
+
* (dormant/wake, never forced to submit). Single source for both the baked-in
|
|
45
|
+
* system prompt (resolve) and the transition guidance (runtime/persona.ts).
|
|
46
|
+
* Returns '' if the fragment file cannot be found.
|
|
47
|
+
*/
|
|
48
|
+
export declare function loadLifecycleFragment(lifecycle: 'terminal' | 'resident'): string;
|
|
49
|
+
/**
|
|
50
|
+
* Load the spine fragment — the "who you report to" contract, keyed on whether
|
|
51
|
+
* the node has a manager (anyone it reports up to). `has-manager` teaches the
|
|
52
|
+
* `push update`/`push urgent`/escalate verbs; `no-manager` (a top-of-spine root)
|
|
53
|
+
* omits the push family entirely — it answers to the human directly.
|
|
54
|
+
* Returns '' if the fragment file cannot be found.
|
|
55
|
+
*/
|
|
56
|
+
export declare function loadSpineFragment(hasManager: boolean): string;
|
|
39
57
|
/**
|
|
40
58
|
* Enumerate the kinds with at least one persona file (base.md or
|
|
41
59
|
* orchestrator.md) across all scope roots (project/user/builtin). Used to
|
|
42
60
|
* validate a requested `--kind` and to list the valid choices.
|
|
43
61
|
*/
|
|
44
62
|
export declare function availableKinds(): string[];
|
|
63
|
+
export interface SubKind {
|
|
64
|
+
/** Full kind string to spawn, e.g. 'plan/reviewers/security'. */
|
|
65
|
+
kind: string;
|
|
66
|
+
/** Leaf name, e.g. 'security'. */
|
|
67
|
+
name: string;
|
|
68
|
+
/** One-line "what it reviews", from the sub-kind base.md `summary` frontmatter (or ''). */
|
|
69
|
+
summary: string;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Enumerate the reviewer sub-kinds owned by `parentKind` — the specialist
|
|
73
|
+
* personas at `<root>/<parentKind>/reviewers/<name>/base.md`, scanned across all
|
|
74
|
+
* scope roots (project > user > builtin; highest precedence wins per name).
|
|
75
|
+
*
|
|
76
|
+
* Sub-kinds are intentionally NOT global kinds: `availableKinds()` scans only the
|
|
77
|
+
* immediate children of each persona root, so `<parentKind>/reviewers/*` never
|
|
78
|
+
* leaks into the global list. A sub-kind is reachable only by its full kind
|
|
79
|
+
* string and is surfaced only in its parent kind's composed prompt (resolve.ts).
|
|
80
|
+
* Kind-parametric: any kind owns a roster simply by adding
|
|
81
|
+
* `<kind>/reviewers/<name>/base.md` — no code change.
|
|
82
|
+
*/
|
|
83
|
+
export declare function subKindsFor(parentKind: string): SubKind[];
|
|
@@ -124,7 +124,9 @@ export function loadKernel() {
|
|
|
124
124
|
}
|
|
125
125
|
/**
|
|
126
126
|
* Load the base runtime prompt — the node operating protocol prepended to
|
|
127
|
-
* EVERY persona (
|
|
127
|
+
* EVERY persona (delegate/ask/promote). Returns '' if not found. The
|
|
128
|
+
* lifecycle/spine-specific sections (finish vs. dormant, report-up vs. silent)
|
|
129
|
+
* live in their own fragments, loaded below.
|
|
128
130
|
*/
|
|
129
131
|
export function loadRuntimeBase() {
|
|
130
132
|
const filePath = resolveFile('runtime-base.md');
|
|
@@ -134,6 +136,34 @@ export function loadRuntimeBase() {
|
|
|
134
136
|
const { body } = parseFrontmatterGeneric(src);
|
|
135
137
|
return body.trim();
|
|
136
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* Load the lifecycle fragment — the "how you end" contract, keyed on the node's
|
|
141
|
+
* lifecycle axis: `terminal` (drive to done + `push final`) or `resident`
|
|
142
|
+
* (dormant/wake, never forced to submit). Single source for both the baked-in
|
|
143
|
+
* system prompt (resolve) and the transition guidance (runtime/persona.ts).
|
|
144
|
+
* Returns '' if the fragment file cannot be found.
|
|
145
|
+
*/
|
|
146
|
+
export function loadLifecycleFragment(lifecycle) {
|
|
147
|
+
const filePath = resolveFile(`lifecycle/${lifecycle}.md`);
|
|
148
|
+
if (!filePath)
|
|
149
|
+
return '';
|
|
150
|
+
const { body } = parseFrontmatterGeneric(readFileSync(filePath, 'utf8'));
|
|
151
|
+
return body.trim();
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Load the spine fragment — the "who you report to" contract, keyed on whether
|
|
155
|
+
* the node has a manager (anyone it reports up to). `has-manager` teaches the
|
|
156
|
+
* `push update`/`push urgent`/escalate verbs; `no-manager` (a top-of-spine root)
|
|
157
|
+
* omits the push family entirely — it answers to the human directly.
|
|
158
|
+
* Returns '' if the fragment file cannot be found.
|
|
159
|
+
*/
|
|
160
|
+
export function loadSpineFragment(hasManager) {
|
|
161
|
+
const filePath = resolveFile(`spine/${hasManager ? 'has-manager' : 'no-manager'}.md`);
|
|
162
|
+
if (!filePath)
|
|
163
|
+
return '';
|
|
164
|
+
const { body } = parseFrontmatterGeneric(readFileSync(filePath, 'utf8'));
|
|
165
|
+
return body.trim();
|
|
166
|
+
}
|
|
137
167
|
/**
|
|
138
168
|
* Enumerate the kinds with at least one persona file (base.md or
|
|
139
169
|
* orchestrator.md) across all scope roots (project/user/builtin). Used to
|
|
@@ -155,3 +185,35 @@ export function availableKinds() {
|
|
|
155
185
|
}
|
|
156
186
|
return [...kinds].sort();
|
|
157
187
|
}
|
|
188
|
+
const REVIEWERS_SUBDIR = 'reviewers';
|
|
189
|
+
/**
|
|
190
|
+
* Enumerate the reviewer sub-kinds owned by `parentKind` — the specialist
|
|
191
|
+
* personas at `<root>/<parentKind>/reviewers/<name>/base.md`, scanned across all
|
|
192
|
+
* scope roots (project > user > builtin; highest precedence wins per name).
|
|
193
|
+
*
|
|
194
|
+
* Sub-kinds are intentionally NOT global kinds: `availableKinds()` scans only the
|
|
195
|
+
* immediate children of each persona root, so `<parentKind>/reviewers/*` never
|
|
196
|
+
* leaks into the global list. A sub-kind is reachable only by its full kind
|
|
197
|
+
* string and is surfaced only in its parent kind's composed prompt (resolve.ts).
|
|
198
|
+
* Kind-parametric: any kind owns a roster simply by adding
|
|
199
|
+
* `<kind>/reviewers/<name>/base.md` — no code change.
|
|
200
|
+
*/
|
|
201
|
+
export function subKindsFor(parentKind) {
|
|
202
|
+
const byName = new Map();
|
|
203
|
+
for (const root of personaSearchRoots()) {
|
|
204
|
+
const dir = join(root, parentKind, REVIEWERS_SUBDIR);
|
|
205
|
+
if (!existsSync(dir))
|
|
206
|
+
continue;
|
|
207
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
208
|
+
if (!entry.isDirectory() || byName.has(entry.name))
|
|
209
|
+
continue; // higher root already won
|
|
210
|
+
const baseFile = join(dir, entry.name, 'base.md');
|
|
211
|
+
if (!existsSync(baseFile))
|
|
212
|
+
continue;
|
|
213
|
+
const { data } = parseFrontmatterGeneric(readFileSync(baseFile, 'utf8'));
|
|
214
|
+
const summary = data && typeof data['summary'] === 'string' ? data['summary'] : '';
|
|
215
|
+
byName.set(entry.name, { kind: `${parentKind}/${REVIEWERS_SUBDIR}/${entry.name}`, name: entry.name, summary });
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
219
|
+
}
|
|
@@ -14,11 +14,10 @@
|
|
|
14
14
|
* If even the base is missing, fall back to general defaults + kernel.
|
|
15
15
|
*
|
|
16
16
|
* Frontmatter from whichever file is the primary source (orchestrator.md >
|
|
17
|
-
* base.md) supplies model/
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* orchestrator → 'resident'
|
|
17
|
+
* base.md) supplies model/skills/extensions/tools. Lifecycle and spine position
|
|
18
|
+
* are INPUTS (the caller decides them — root/child, terminal/resident), not
|
|
19
|
+
* derived here; they select the lifecycle/spine protocol fragments spliced
|
|
20
|
+
* ahead of the persona body.
|
|
22
21
|
*/
|
|
23
22
|
export interface ResolvedPersona {
|
|
24
23
|
systemPrompt: string;
|
|
@@ -33,4 +32,12 @@ export interface ResolvedPersona {
|
|
|
33
32
|
*
|
|
34
33
|
* Never throws for missing files — missing personas produce sensible defaults.
|
|
35
34
|
*/
|
|
36
|
-
export
|
|
35
|
+
export interface ResolveOpts {
|
|
36
|
+
/** The node's lifecycle axis — selects the "how you end" fragment. */
|
|
37
|
+
lifecycle: 'terminal' | 'resident';
|
|
38
|
+
/** Whether the node reports up to a manager (parent !== null) — selects the
|
|
39
|
+
* spine fragment (`has-manager` teaches the push family; `no-manager` omits
|
|
40
|
+
* it entirely). */
|
|
41
|
+
hasManager: boolean;
|
|
42
|
+
}
|
|
43
|
+
export declare function resolve(kind: string, mode: 'base' | 'orchestrator', opts: ResolveOpts): ResolvedPersona;
|
|
@@ -14,13 +14,12 @@
|
|
|
14
14
|
* If even the base is missing, fall back to general defaults + kernel.
|
|
15
15
|
*
|
|
16
16
|
* Frontmatter from whichever file is the primary source (orchestrator.md >
|
|
17
|
-
* base.md) supplies model/
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* orchestrator → 'resident'
|
|
17
|
+
* base.md) supplies model/skills/extensions/tools. Lifecycle and spine position
|
|
18
|
+
* are INPUTS (the caller decides them — root/child, terminal/resident), not
|
|
19
|
+
* derived here; they select the lifecycle/spine protocol fragments spliced
|
|
20
|
+
* ahead of the persona body.
|
|
22
21
|
*/
|
|
23
|
-
import { loadPersona, loadKernel, loadRuntimeBase } from './loader.js';
|
|
22
|
+
import { loadPersona, loadKernel, loadRuntimeBase, loadSpineFragment, loadLifecycleFragment, subKindsFor } from './loader.js';
|
|
24
23
|
// ---------------------------------------------------------------------------
|
|
25
24
|
// Helpers
|
|
26
25
|
// ---------------------------------------------------------------------------
|
|
@@ -32,48 +31,62 @@ function toStringArray(v) {
|
|
|
32
31
|
function toOptionalString(v) {
|
|
33
32
|
return typeof v === 'string' ? v : undefined;
|
|
34
33
|
}
|
|
35
|
-
function toLifecycle(v, defaultValue) {
|
|
36
|
-
if (v === 'terminal' || v === 'resident')
|
|
37
|
-
return v;
|
|
38
|
-
return defaultValue;
|
|
39
|
-
}
|
|
40
34
|
/** The bare-minimum system prompt used when no persona file is found at all. */
|
|
41
35
|
function fallbackBasePrompt(kind) {
|
|
42
36
|
return `You are a ${kind} agent. Complete the task you have been given.`;
|
|
43
37
|
}
|
|
44
|
-
/**
|
|
45
|
-
*
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
38
|
+
/** Compose the runtime protocol that precedes every persona body: the
|
|
39
|
+
* lifecycle-neutral base (identity/delegate/ask/promote), then the spine
|
|
40
|
+
* fragment (report-up vs. silent, keyed on whether the node has a manager),
|
|
41
|
+
* then the lifecycle fragment (finish-with-`push final` vs. dormant/wake). The
|
|
42
|
+
* kind×mode persona body follows after a rule. Empty fragments drop out. */
|
|
43
|
+
/** Render the "sub-kinds you may spawn" menu for a kind that owns a roster.
|
|
44
|
+
* Returns '' when the kind owns none. Data-driven: one line per sub-kind, its
|
|
45
|
+
* spawn string + its `summary`. Adding a roster file makes it appear here. */
|
|
46
|
+
function renderSubKindMenu(kind) {
|
|
47
|
+
const subs = subKindsFor(kind);
|
|
48
|
+
if (subs.length === 0)
|
|
49
|
+
return '';
|
|
50
|
+
const lines = subs.map((s) => `- \`${s.kind}\` — ${s.summary}`);
|
|
51
|
+
return [
|
|
52
|
+
'## Reviewer sub-kinds you may spawn',
|
|
53
|
+
'',
|
|
54
|
+
`These specialist reviewers exist only in the ${kind} kind's world — no other kind sees them. Spawn one with \`crtr node new --kind <sub-kind> "<scope>"\`, giving it only its scope, never your suspicions: a reviewer handed a hint anchors on it instead of finding problems independently.`,
|
|
55
|
+
'',
|
|
56
|
+
...lines,
|
|
57
|
+
].join('\n');
|
|
49
58
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
function composeProtocol(personaPrompt, kind, lifecycle, hasManager) {
|
|
60
|
+
const menu = renderSubKindMenu(kind);
|
|
61
|
+
const body = menu ? `${personaPrompt}\n\n${menu}` : personaPrompt;
|
|
62
|
+
const protocol = [
|
|
63
|
+
loadRuntimeBase(),
|
|
64
|
+
loadSpineFragment(hasManager),
|
|
65
|
+
loadLifecycleFragment(lifecycle),
|
|
66
|
+
]
|
|
67
|
+
.filter((s) => s.length > 0)
|
|
68
|
+
.join('\n\n');
|
|
69
|
+
return protocol ? `${protocol}\n\n---\n\n${body}` : body;
|
|
70
|
+
}
|
|
71
|
+
export function resolve(kind, mode, opts) {
|
|
59
72
|
if (mode === 'base') {
|
|
60
73
|
const persona = loadPersona(kind, 'base');
|
|
61
74
|
if (!persona) {
|
|
62
75
|
// No persona file for this kind — use minimal defaults.
|
|
63
76
|
return {
|
|
64
|
-
systemPrompt:
|
|
77
|
+
systemPrompt: composeProtocol(fallbackBasePrompt(kind), kind, opts.lifecycle, opts.hasManager),
|
|
65
78
|
extensions: [],
|
|
66
79
|
skills: [],
|
|
67
|
-
lifecycle:
|
|
80
|
+
lifecycle: opts.lifecycle,
|
|
68
81
|
};
|
|
69
82
|
}
|
|
70
83
|
const fm = persona.frontmatter ?? {};
|
|
71
84
|
return {
|
|
72
|
-
systemPrompt:
|
|
85
|
+
systemPrompt: composeProtocol(persona.body || fallbackBasePrompt(kind), kind, opts.lifecycle, opts.hasManager),
|
|
73
86
|
extensions: toStringArray(fm['extensions']),
|
|
74
87
|
skills: toStringArray(fm['skills']),
|
|
75
88
|
model: toOptionalString(fm['model']),
|
|
76
|
-
lifecycle:
|
|
89
|
+
lifecycle: opts.lifecycle,
|
|
77
90
|
tools: fm['tools'] !== undefined ? toStringArray(fm['tools']) : undefined,
|
|
78
91
|
};
|
|
79
92
|
}
|
|
@@ -83,11 +96,11 @@ export function resolve(kind, mode) {
|
|
|
83
96
|
// Orchestrator file exists; @include was already inlined by the loader.
|
|
84
97
|
const fm = orchestratorPersona.frontmatter ?? {};
|
|
85
98
|
return {
|
|
86
|
-
systemPrompt:
|
|
99
|
+
systemPrompt: composeProtocol(orchestratorPersona.body || fallbackBasePrompt(kind), kind, opts.lifecycle, opts.hasManager),
|
|
87
100
|
extensions: toStringArray(fm['extensions']),
|
|
88
101
|
skills: toStringArray(fm['skills']),
|
|
89
102
|
model: toOptionalString(fm['model']),
|
|
90
|
-
lifecycle:
|
|
103
|
+
lifecycle: opts.lifecycle,
|
|
91
104
|
tools: fm['tools'] !== undefined ? toStringArray(fm['tools']) : undefined,
|
|
92
105
|
};
|
|
93
106
|
}
|
|
@@ -99,12 +112,11 @@ export function resolve(kind, mode) {
|
|
|
99
112
|
// Append the kernel to the base body (with separator if kernel is non-empty).
|
|
100
113
|
const systemPrompt = kernel ? `${baseBody}\n\n${kernel}` : baseBody;
|
|
101
114
|
return {
|
|
102
|
-
systemPrompt:
|
|
115
|
+
systemPrompt: composeProtocol(systemPrompt, kind, opts.lifecycle, opts.hasManager),
|
|
103
116
|
extensions: toStringArray(fm['extensions']),
|
|
104
117
|
skills: toStringArray(fm['skills']),
|
|
105
118
|
model: toOptionalString(fm['model']),
|
|
106
|
-
|
|
107
|
-
lifecycle: 'resident',
|
|
119
|
+
lifecycle: opts.lifecycle,
|
|
108
120
|
tools: fm['tools'] !== undefined ? toStringArray(fm['tools']) : undefined,
|
|
109
121
|
};
|
|
110
122
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Base framing — present for every node. No path baked in: the caller carries
|
|
2
|
+
* the dir in the <crtr-context dir="…"> attribute. */
|
|
3
|
+
export declare const BASE_CONTEXT_NOTE: string;
|
|
4
|
+
/** Orchestrator-only framing: a resident orchestrator survives refresh cycles,
|
|
5
|
+
* so its context dir is also where a future cycle of itself resumes the work.
|
|
6
|
+
* Used inside the bearings block AND in the promotion guidance dump, so a
|
|
7
|
+
* promoted node gets the same note a born-orchestrator gets. */
|
|
8
|
+
export declare function orchestratorContextNote(nodeId: string): string;
|
|
9
|
+
/** The <memory> block (orchestrators only): the scoped stores merged, each a
|
|
10
|
+
* `label · dir` header over its live index pointer lines. A memory's `type`
|
|
11
|
+
* decides which store it lands in — the mapping + the how-to live once in the
|
|
12
|
+
* orchestration kernel ("Your long-term memory"); here we carry only the live
|
|
13
|
+
* data + a one-line pointer back to it. user-global rides in when the node has
|
|
14
|
+
* a user store, project when it has a project store, node-local always (the
|
|
15
|
+
* orchestrator gate). */
|
|
16
|
+
export declare function buildMemoryBlock(nodeId: string, cwd: string): string;
|
|
17
|
+
/** The full <crtr-context> bearings block: base framing always, plus the
|
|
18
|
+
* orchestrator addendum + the merged three-store <memory> block when the node
|
|
19
|
+
* has a node-local memory store (the orchestrator gate). */
|
|
20
|
+
export declare function buildContextBearings(nodeId: string): string;
|