@crouton-kit/crouter 0.3.11 → 0.3.12
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/bin/crtrd +2 -0
- package/dist/builtin-personas/design/base.md +9 -0
- package/dist/builtin-personas/design/orchestrator.md +10 -0
- package/dist/builtin-personas/developer/base.md +9 -0
- package/dist/builtin-personas/developer/orchestrator.md +12 -0
- package/dist/builtin-personas/explore/base.md +9 -0
- package/dist/builtin-personas/explore/orchestrator.md +9 -0
- package/dist/builtin-personas/general/base.md +5 -0
- package/dist/builtin-personas/general/orchestrator.md +7 -0
- package/dist/builtin-personas/orchestration-kernel.md +71 -0
- package/dist/builtin-personas/plan/base.md +7 -0
- package/dist/builtin-personas/plan/orchestrator.md +12 -0
- package/dist/builtin-personas/review/base.md +7 -0
- package/dist/builtin-personas/review/orchestrator.md +9 -0
- package/dist/builtin-personas/runtime-base.md +39 -0
- package/dist/builtin-personas/spec/base.md +7 -0
- package/dist/builtin-personas/spec/orchestrator.md +10 -0
- package/dist/builtin-skills/skills/design/SKILL.md +51 -0
- package/dist/builtin-skills/skills/development/SKILL.md +109 -0
- package/dist/builtin-skills/skills/planning/SKILL.md +59 -0
- package/dist/builtin-skills/skills/spec/SKILL.md +83 -0
- package/dist/cli.js +14 -6
- package/dist/commands/{mode.d.ts → attention.d.ts} +1 -1
- package/dist/commands/attention.js +152 -0
- package/dist/commands/canvas.d.ts +2 -0
- package/dist/commands/canvas.js +35 -0
- package/dist/commands/daemon.d.ts +2 -0
- package/dist/commands/daemon.js +111 -0
- package/dist/commands/dashboard.d.ts +2 -0
- package/dist/commands/dashboard.js +65 -0
- package/dist/commands/human/prompts.d.ts +5 -0
- package/dist/commands/human/prompts.js +269 -0
- package/dist/commands/human/queue.d.ts +3 -0
- package/dist/commands/human/queue.js +133 -0
- package/dist/commands/human/shared.d.ts +43 -0
- package/dist/commands/human/shared.js +107 -0
- package/dist/commands/human.js +10 -454
- package/dist/commands/node.d.ts +2 -0
- package/dist/commands/node.js +354 -0
- package/dist/commands/pkg/market-inspect.d.ts +1 -0
- package/dist/commands/pkg/market-inspect.js +157 -0
- package/dist/commands/pkg/market-manage.d.ts +1 -0
- package/dist/commands/pkg/market-manage.js +316 -0
- package/dist/commands/pkg/market.d.ts +1 -0
- package/dist/commands/pkg/market.js +16 -0
- package/dist/commands/pkg/plugin-inspect.d.ts +1 -0
- package/dist/commands/pkg/plugin-inspect.js +142 -0
- package/dist/commands/pkg/plugin-manage.d.ts +1 -0
- package/dist/commands/pkg/plugin-manage.js +294 -0
- package/dist/commands/pkg/plugin.d.ts +1 -0
- package/dist/commands/pkg/plugin.js +16 -0
- package/dist/commands/pkg/shared.d.ts +5 -0
- package/dist/commands/pkg/shared.js +61 -0
- package/dist/commands/pkg.js +3 -1004
- package/dist/commands/push.d.ts +3 -0
- package/dist/commands/push.js +159 -0
- package/dist/commands/revive.d.ts +2 -0
- package/dist/commands/revive.js +64 -0
- package/dist/commands/skill/author.d.ts +3 -0
- package/dist/commands/skill/author.js +147 -0
- package/dist/commands/skill/find.d.ts +4 -0
- package/dist/commands/skill/find.js +254 -0
- package/dist/commands/skill/read.d.ts +1 -0
- package/dist/commands/skill/read.js +89 -0
- package/dist/commands/skill/shared.d.ts +19 -0
- package/dist/commands/skill/shared.js +207 -0
- package/dist/commands/skill/state.d.ts +3 -0
- package/dist/commands/skill/state.js +69 -0
- package/dist/commands/skill.js +6 -691
- package/dist/commands/sys/config.d.ts +1 -0
- package/dist/commands/sys/config.js +186 -0
- package/dist/commands/sys/doctor.d.ts +1 -0
- package/dist/commands/sys/doctor.js +369 -0
- package/dist/commands/sys/shared.d.ts +3 -0
- package/dist/commands/sys/shared.js +24 -0
- package/dist/commands/sys/update.d.ts +2 -0
- package/dist/commands/sys/update.js +114 -0
- package/dist/commands/sys.js +4 -694
- package/dist/core/__tests__/argv-parser.test.js +19 -1
- package/dist/core/__tests__/canvas-inbox-watcher.test.js +100 -0
- package/dist/core/__tests__/canvas.test.js +154 -0
- package/dist/core/__tests__/reset.test.js +105 -0
- package/dist/core/canvas/attention.d.ts +24 -0
- package/dist/core/canvas/attention.js +94 -0
- package/dist/core/canvas/canvas.d.ts +40 -0
- package/dist/core/canvas/canvas.js +210 -0
- package/dist/core/canvas/db.d.ts +7 -0
- package/dist/core/canvas/db.js +61 -0
- package/dist/core/canvas/index.d.ts +4 -0
- package/dist/core/canvas/index.js +6 -0
- package/dist/core/canvas/paths.d.ts +16 -0
- package/dist/core/canvas/paths.js +62 -0
- package/dist/core/canvas/render.d.ts +30 -0
- package/dist/core/canvas/render.js +186 -0
- package/dist/core/canvas/types.d.ts +87 -0
- package/dist/core/canvas/types.js +8 -0
- package/dist/core/command.d.ts +5 -0
- package/dist/core/command.js +35 -10
- package/dist/core/feed/feed.d.ts +43 -0
- package/dist/core/feed/feed.js +116 -0
- package/dist/core/feed/inbox.d.ts +50 -0
- package/dist/core/feed/inbox.js +124 -0
- package/dist/core/help.js +5 -3
- package/dist/core/io.d.ts +15 -1
- package/dist/core/io.js +56 -6
- package/dist/core/personas/index.d.ts +12 -0
- package/dist/core/personas/index.js +10 -0
- package/dist/core/personas/loader.d.ts +44 -0
- package/dist/core/personas/loader.js +157 -0
- package/dist/core/personas/resolve.d.ts +36 -0
- package/dist/core/personas/resolve.js +110 -0
- package/dist/core/render.d.ts +11 -0
- package/dist/core/render.js +126 -0
- package/dist/core/resolver.d.ts +10 -0
- package/dist/core/resolver.js +109 -1
- package/dist/core/runtime/front-door.d.ts +10 -0
- package/dist/core/runtime/front-door.js +97 -0
- package/dist/core/runtime/kickoff.d.ts +23 -0
- package/dist/core/runtime/kickoff.js +134 -0
- package/dist/core/runtime/launch.d.ts +34 -0
- package/dist/core/runtime/launch.js +85 -0
- package/dist/core/runtime/nodes.d.ts +38 -0
- package/dist/core/runtime/nodes.js +95 -0
- package/dist/core/runtime/presence.d.ts +38 -0
- package/dist/core/runtime/presence.js +152 -0
- package/dist/core/runtime/promote.d.ts +30 -0
- package/dist/core/runtime/promote.js +105 -0
- package/dist/core/runtime/reset.d.ts +13 -0
- package/dist/core/runtime/reset.js +97 -0
- package/dist/core/runtime/revive.d.ts +26 -0
- package/dist/core/runtime/revive.js +89 -0
- package/dist/core/runtime/roadmap.d.ts +12 -0
- package/dist/core/runtime/roadmap.js +52 -0
- package/dist/core/runtime/spawn.d.ts +33 -0
- package/dist/core/runtime/spawn.js +118 -0
- package/dist/core/runtime/stop-guard.d.ts +18 -0
- package/dist/core/runtime/stop-guard.js +33 -0
- package/dist/core/runtime/tmux.d.ts +88 -0
- package/dist/core/runtime/tmux.js +198 -0
- package/dist/core/spawn.d.ts +17 -197
- package/dist/core/spawn.js +16 -539
- package/dist/daemon/crtrd-cli.js +4 -0
- package/dist/daemon/crtrd.d.ts +20 -0
- package/dist/daemon/crtrd.js +200 -0
- package/dist/daemon/manage.d.ts +17 -0
- package/dist/daemon/manage.js +57 -0
- package/dist/pi-extensions/canvas-inbox-watcher.d.ts +16 -0
- package/dist/pi-extensions/canvas-inbox-watcher.js +229 -0
- package/dist/pi-extensions/canvas-nav.d.ts +32 -0
- package/dist/pi-extensions/canvas-nav.js +536 -0
- package/dist/pi-extensions/canvas-stophook.d.ts +17 -0
- package/dist/pi-extensions/canvas-stophook.js +373 -0
- package/package.json +6 -5
- package/dist/commands/agent.d.ts +0 -6
- package/dist/commands/agent.js +0 -585
- package/dist/commands/debug.d.ts +0 -3
- package/dist/commands/debug.js +0 -192
- package/dist/commands/job.d.ts +0 -11
- package/dist/commands/job.js +0 -384
- package/dist/commands/mode.js +0 -231
- package/dist/commands/plan.d.ts +0 -4
- package/dist/commands/plan.js +0 -322
- package/dist/commands/spec.d.ts +0 -3
- package/dist/commands/spec.js +0 -299
- package/dist/core/__tests__/flow-leaves.test.js +0 -248
- package/dist/core/__tests__/job.test.js +0 -310
- package/dist/core/__tests__/jobs.test.js +0 -98
- package/dist/core/__tests__/spawn.test.js +0 -138
- package/dist/core/__tests__/subagents.test.d.ts +0 -1
- package/dist/core/__tests__/subagents.test.js +0 -75
- package/dist/core/jobs.d.ts +0 -107
- package/dist/core/jobs.js +0 -565
- package/dist/core/subagents.d.ts +0 -18
- package/dist/core/subagents.js +0 -163
- package/dist/prompts/agent.d.ts +0 -27
- package/dist/prompts/agent.js +0 -184
- package/dist/prompts/debug.d.ts +0 -8
- package/dist/prompts/debug.js +0 -44
- /package/dist/core/__tests__/{flow-leaves.test.d.ts → canvas-inbox-watcher.test.d.ts} +0 -0
- /package/dist/core/__tests__/{job.test.d.ts → canvas.test.d.ts} +0 -0
- /package/dist/core/__tests__/{jobs.test.d.ts → reset.test.d.ts} +0 -0
- /package/dist/{core/__tests__/spawn.test.d.ts → daemon/crtrd-cli.d.ts} +0 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// The canvas vocabulary — the node + edge model the whole runtime hangs on.
|
|
2
|
+
//
|
|
3
|
+
// One global canvas (`~/.crtr/canvas.db`) holds the topology (nodes + edges);
|
|
4
|
+
// each node's flesh lives on disk under `~/.crtr/nodes/<id>/`. A node's
|
|
5
|
+
// `meta.json` is the source of truth for its own row; the db is a queryable
|
|
6
|
+
// index over those metas, plus the authoritative store for the mutable
|
|
7
|
+
// `subscribes_to` edges (which no single meta owns).
|
|
8
|
+
export {};
|
package/dist/core/command.d.ts
CHANGED
|
@@ -23,6 +23,10 @@ export interface LeafDef {
|
|
|
23
23
|
/** Opt into editor slash-command exposure (see SlashSpec). */
|
|
24
24
|
slash?: SlashSpec;
|
|
25
25
|
run: (input: Record<string, unknown>) => Promise<Record<string, unknown> | void>;
|
|
26
|
+
/** Optional bespoke renderer: turn the result into instruction-shaped
|
|
27
|
+
* XML+markdown the agent acts on. Omit to fall back to the schema-driven
|
|
28
|
+
* generic renderer. Ignored when `--json` is set. */
|
|
29
|
+
render?: (result: Record<string, unknown>) => string;
|
|
26
30
|
}
|
|
27
31
|
export interface BranchDef {
|
|
28
32
|
kind: 'branch';
|
|
@@ -46,6 +50,7 @@ export declare function defineLeaf(opts: {
|
|
|
46
50
|
help: LeafHelp;
|
|
47
51
|
slash?: SlashSpec;
|
|
48
52
|
run: (input: Record<string, unknown>) => Promise<Record<string, unknown> | void>;
|
|
53
|
+
render?: (result: Record<string, unknown>) => string;
|
|
49
54
|
}): LeafDef;
|
|
50
55
|
export declare function defineBranch(opts: {
|
|
51
56
|
name: string;
|
package/dist/core/command.js
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
// A plain array walk + a small flag parser is ~120 lines and has no surprising
|
|
6
6
|
// edge cases.
|
|
7
7
|
import { renderRoot, renderBranch, renderLeafArgv } from './help.js';
|
|
8
|
-
import { readStdinRaw, emit, handle } from './io.js';
|
|
8
|
+
import { readStdinRaw, emit, handle, setJsonOutput, isJsonOutput } from './io.js';
|
|
9
|
+
import { renderResult } from './render.js';
|
|
9
10
|
import { CrtrError } from './errors.js';
|
|
10
11
|
import { ExitCode } from '../types.js';
|
|
11
12
|
import { readFileSync } from 'node:fs';
|
|
@@ -19,6 +20,7 @@ export function defineLeaf(opts) {
|
|
|
19
20
|
help: opts.help,
|
|
20
21
|
slash: opts.slash,
|
|
21
22
|
run: opts.run,
|
|
23
|
+
render: opts.render,
|
|
22
24
|
};
|
|
23
25
|
}
|
|
24
26
|
export function defineBranch(opts) {
|
|
@@ -263,7 +265,10 @@ export async function parseArgv(params, tokens) {
|
|
|
263
265
|
if (positionalValue !== undefined) {
|
|
264
266
|
throw parseArgvError('bad_invocation', `unexpected extra positional argument: ${token}`, tokens.join(' '), undefined, 'Use --flag for parameters; only one positional allowed.');
|
|
265
267
|
}
|
|
266
|
-
|
|
268
|
+
// A bare positional is accepted when the leaf declares a positional param,
|
|
269
|
+
// OR when it declares a stdin param (the positional supplies the stdin
|
|
270
|
+
// value as an ergonomic alternative to piping). Otherwise it's an error.
|
|
271
|
+
if (positionalParam === undefined && stdinParam === undefined) {
|
|
267
272
|
throw parseArgvError('bad_invocation', `this leaf takes no positional arguments: ${token}`, token, undefined, 'Use --flag for parameters. Run -h for the schema.');
|
|
268
273
|
}
|
|
269
274
|
positionalValue = token;
|
|
@@ -273,13 +278,20 @@ export async function parseArgv(params, tokens) {
|
|
|
273
278
|
if (positionalValue !== undefined && positionalParam !== undefined) {
|
|
274
279
|
result[flagNameToKey(positionalParam.name)] = positionalValue;
|
|
275
280
|
}
|
|
276
|
-
//
|
|
281
|
+
// Resolve stdin if declared. A positional token (when there's no dedicated
|
|
282
|
+
// positional param to claim it) satisfies the stdin param directly, so
|
|
283
|
+
// `crtr node new "Say hi"` works as well as piping on stdin.
|
|
277
284
|
if (stdinParam !== undefined) {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
285
|
+
if (positionalValue !== undefined && positionalParam === undefined) {
|
|
286
|
+
result[flagNameToKey(stdinParam.name)] = positionalValue;
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
const raw = await readStdinRaw();
|
|
290
|
+
if (raw.trim() === '' && stdinParam.required) {
|
|
291
|
+
throw parseArgvError('missing_parameter', `stdin is required for this leaf`, '', stdinParam.name, 'Pipe the required content on stdin, or pass it as a positional argument.');
|
|
292
|
+
}
|
|
293
|
+
result[flagNameToKey(stdinParam.name)] = raw;
|
|
281
294
|
}
|
|
282
|
-
result[flagNameToKey(stdinParam.name)] = raw;
|
|
283
295
|
}
|
|
284
296
|
// Validate required params
|
|
285
297
|
for (const p of params) {
|
|
@@ -298,8 +310,15 @@ export async function parseArgv(params, tokens) {
|
|
|
298
310
|
return result;
|
|
299
311
|
}
|
|
300
312
|
export async function runCli(root, argv) {
|
|
301
|
-
// argv is process.argv — strip node binary + script path
|
|
302
|
-
|
|
313
|
+
// argv is process.argv — strip node binary + script path. `--json` is a
|
|
314
|
+
// global: pull it out anywhere it appears so the rest of argv parses against
|
|
315
|
+
// the leaf schema unchanged, and switch stdout from rendered prose to raw
|
|
316
|
+
// JSON. It is intentionally undocumented — the default prose output is the
|
|
317
|
+
// agent contract; --json exists only for programmatic/tooling consumers.
|
|
318
|
+
const rawTokens = argv.slice(2);
|
|
319
|
+
const tokens = rawTokens.filter((t) => t !== '--json');
|
|
320
|
+
if (tokens.length !== rawTokens.length)
|
|
321
|
+
setJsonOutput(true);
|
|
303
322
|
// Bare root invocation or -h at root
|
|
304
323
|
if (tokens.length === 0 || (tokens.length === 1 && (tokens[0] === '-h' || tokens[0] === '--help'))) {
|
|
305
324
|
process.stdout.write(renderRoot(root.help) + '\n');
|
|
@@ -325,7 +344,13 @@ export async function runCli(root, argv) {
|
|
|
325
344
|
const input = await parseArgv(params, remaining);
|
|
326
345
|
const result = await node.run(input);
|
|
327
346
|
if (result !== undefined && result !== null) {
|
|
328
|
-
|
|
347
|
+
if (isJsonOutput()) {
|
|
348
|
+
emit(result);
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
const text = node.render !== undefined ? node.render(result) : renderResult(result, node.help);
|
|
352
|
+
process.stdout.write(text + '\n');
|
|
353
|
+
}
|
|
329
354
|
}
|
|
330
355
|
// JSONL leaves call emitLine themselves and return void
|
|
331
356
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export type PushKind = 'update' | 'urgent' | 'final';
|
|
2
|
+
export interface PushOpts {
|
|
3
|
+
/** Semantic kind of this push. `final` also finalises the node. */
|
|
4
|
+
kind: PushKind;
|
|
5
|
+
/** Report body (markdown). Written verbatim after the YAML frontmatter. */
|
|
6
|
+
body: string;
|
|
7
|
+
/**
|
|
8
|
+
* Node id of the sender — recorded as `from` on each inbox entry.
|
|
9
|
+
* Defaults to `nodeId` (the publisher) when omitted.
|
|
10
|
+
*/
|
|
11
|
+
from?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface PushResult {
|
|
14
|
+
/** Absolute path of the written report file. */
|
|
15
|
+
reportPath: string;
|
|
16
|
+
/** Node ids that received an inbox pointer. */
|
|
17
|
+
deliveredTo: string[];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Push a report from `nodeId` and fan it out as inbox pointers to all
|
|
21
|
+
* current subscribers.
|
|
22
|
+
*
|
|
23
|
+
* Steps:
|
|
24
|
+
* (a) Write nodes/<nodeId>/reports/<ts>-<kind>.md (YAML front + body).
|
|
25
|
+
* (b) For each active/passive subscriber, append a pointer to their inbox.
|
|
26
|
+
* (c) If kind === 'final', mark the node done.
|
|
27
|
+
*/
|
|
28
|
+
export declare function push(nodeId: string, opts: PushOpts): Promise<PushResult>;
|
|
29
|
+
/** Emit a routine progress update from `nodeId`. */
|
|
30
|
+
export declare function pushUpdate(nodeId: string, body: string, opts?: {
|
|
31
|
+
from?: string;
|
|
32
|
+
}): Promise<PushResult>;
|
|
33
|
+
/** Emit an urgent alert from `nodeId` (inbox tier: urgent). */
|
|
34
|
+
export declare function pushUrgent(nodeId: string, body: string, opts?: {
|
|
35
|
+
from?: string;
|
|
36
|
+
}): Promise<PushResult>;
|
|
37
|
+
/**
|
|
38
|
+
* Emit the final report from `nodeId` (inbox tier: normal, kind: final).
|
|
39
|
+
* Also transitions the node to status=done / intent=done.
|
|
40
|
+
*/
|
|
41
|
+
export declare function pushFinal(nodeId: string, body: string, opts?: {
|
|
42
|
+
from?: string;
|
|
43
|
+
}): Promise<PushResult>;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// Push engine for the pi-native canvas runtime.
|
|
2
|
+
//
|
|
3
|
+
// `push(nodeId, opts)` writes a report to the node's reports/ directory, then
|
|
4
|
+
// fans out a lightweight inbox pointer to every subscriber. The inbox entry
|
|
5
|
+
// carries the report path (ref), not the body — subscribers dereference on
|
|
6
|
+
// demand.
|
|
7
|
+
//
|
|
8
|
+
// Compact timestamp format: 20260602T184512 (UTC, no separators) chosen for
|
|
9
|
+
// file-system friendliness and lexicographic sort alignment.
|
|
10
|
+
import { writeFileSync, renameSync, mkdirSync, existsSync } from 'node:fs';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import { reportsDir, subscribersOf, setStatus, updateNode, } from '../canvas/index.js';
|
|
13
|
+
import { appendInbox } from './inbox.js';
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Internal helpers
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
/** Format a Date as `YYYYMMDDTHHmmss` (UTC, no separators). */
|
|
18
|
+
function compactTs(d = new Date()) {
|
|
19
|
+
const pad = (n, w = 2) => String(n).padStart(w, '0');
|
|
20
|
+
return (String(d.getUTCFullYear()) +
|
|
21
|
+
pad(d.getUTCMonth() + 1) +
|
|
22
|
+
pad(d.getUTCDate()) +
|
|
23
|
+
'T' +
|
|
24
|
+
pad(d.getUTCHours()) +
|
|
25
|
+
pad(d.getUTCMinutes()) +
|
|
26
|
+
pad(d.getUTCSeconds()));
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Write a report file atomically (tmp + rename).
|
|
30
|
+
* Returns the final absolute path.
|
|
31
|
+
*/
|
|
32
|
+
function writeReport(nodeId, kind, ts, body) {
|
|
33
|
+
const dir = reportsDir(nodeId);
|
|
34
|
+
if (!existsSync(dir))
|
|
35
|
+
mkdirSync(dir, { recursive: true });
|
|
36
|
+
const fileName = `${ts}-${kind}.md`;
|
|
37
|
+
const finalPath = join(dir, fileName);
|
|
38
|
+
const tmpPath = `${finalPath}.tmp`;
|
|
39
|
+
const isoTs = new Date().toISOString();
|
|
40
|
+
// YAML frontmatter: minimal, machine-readable, no freeform content in it.
|
|
41
|
+
const frontmatter = `---\nnode: ${nodeId}\nkind: ${kind}\nts: ${isoTs}\n---\n`;
|
|
42
|
+
writeFileSync(tmpPath, frontmatter + body, 'utf8');
|
|
43
|
+
renameSync(tmpPath, finalPath);
|
|
44
|
+
return finalPath;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Extract the first line of a string and truncate to `maxLen` chars.
|
|
48
|
+
* Used to populate the inbox entry's `label` field (~80 chars).
|
|
49
|
+
*/
|
|
50
|
+
function firstLine(text, maxLen = 80) {
|
|
51
|
+
const line = text.split('\n')[0] ?? '';
|
|
52
|
+
return line.length > maxLen ? line.slice(0, maxLen - 1) + '…' : line;
|
|
53
|
+
}
|
|
54
|
+
/** Map a PushKind to the appropriate inbox delivery tier. */
|
|
55
|
+
function tierFor(kind) {
|
|
56
|
+
return kind === 'urgent' ? 'urgent' : 'normal';
|
|
57
|
+
}
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
// Core push
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
/**
|
|
62
|
+
* Push a report from `nodeId` and fan it out as inbox pointers to all
|
|
63
|
+
* current subscribers.
|
|
64
|
+
*
|
|
65
|
+
* Steps:
|
|
66
|
+
* (a) Write nodes/<nodeId>/reports/<ts>-<kind>.md (YAML front + body).
|
|
67
|
+
* (b) For each active/passive subscriber, append a pointer to their inbox.
|
|
68
|
+
* (c) If kind === 'final', mark the node done.
|
|
69
|
+
*/
|
|
70
|
+
export async function push(nodeId, opts) {
|
|
71
|
+
const { kind, body } = opts;
|
|
72
|
+
const from = opts.from ?? nodeId;
|
|
73
|
+
const now = new Date();
|
|
74
|
+
const ts = compactTs(now);
|
|
75
|
+
// (a) Write the report.
|
|
76
|
+
const reportPath = writeReport(nodeId, kind, ts, body);
|
|
77
|
+
// (b) Fan out inbox pointers to every subscriber (active and passive both
|
|
78
|
+
// receive the pointer; the daemon decides whether to wake active ones).
|
|
79
|
+
const subscribers = subscribersOf(nodeId);
|
|
80
|
+
const deliveredTo = [];
|
|
81
|
+
const label = firstLine(body);
|
|
82
|
+
for (const sub of subscribers) {
|
|
83
|
+
appendInbox(sub.node_id, {
|
|
84
|
+
from,
|
|
85
|
+
tier: tierFor(kind),
|
|
86
|
+
kind,
|
|
87
|
+
ref: reportPath,
|
|
88
|
+
label,
|
|
89
|
+
});
|
|
90
|
+
deliveredTo.push(sub.node_id);
|
|
91
|
+
}
|
|
92
|
+
// (c) Finalise node when kind === 'final'.
|
|
93
|
+
if (kind === 'final') {
|
|
94
|
+
setStatus(nodeId, 'done');
|
|
95
|
+
updateNode(nodeId, { intent: 'done' });
|
|
96
|
+
}
|
|
97
|
+
return { reportPath, deliveredTo };
|
|
98
|
+
}
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
// Convenience wrappers
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
/** Emit a routine progress update from `nodeId`. */
|
|
103
|
+
export async function pushUpdate(nodeId, body, opts) {
|
|
104
|
+
return push(nodeId, { kind: 'update', body, ...opts });
|
|
105
|
+
}
|
|
106
|
+
/** Emit an urgent alert from `nodeId` (inbox tier: urgent). */
|
|
107
|
+
export async function pushUrgent(nodeId, body, opts) {
|
|
108
|
+
return push(nodeId, { kind: 'urgent', body, ...opts });
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Emit the final report from `nodeId` (inbox tier: normal, kind: final).
|
|
112
|
+
* Also transitions the node to status=done / intent=done.
|
|
113
|
+
*/
|
|
114
|
+
export async function pushFinal(nodeId, body, opts) {
|
|
115
|
+
return push(nodeId, { kind: 'final', body, ...opts });
|
|
116
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export type InboxTier = 'critical' | 'urgent' | 'normal' | 'deferred';
|
|
2
|
+
export type InboxKind = 'update' | 'urgent' | 'final' | 'message' | 'completed';
|
|
3
|
+
/** A single inbox entry — a pointer, not a copy of the content. */
|
|
4
|
+
export interface InboxEntry {
|
|
5
|
+
/** ISO 8601 timestamp of delivery. */
|
|
6
|
+
ts: string;
|
|
7
|
+
/** Node id of the sender, or null for system-generated entries. */
|
|
8
|
+
from: string | null;
|
|
9
|
+
/** Priority band for the receiver's attention. */
|
|
10
|
+
tier: InboxTier;
|
|
11
|
+
/** Semantic kind of the push event. */
|
|
12
|
+
kind: InboxKind;
|
|
13
|
+
/** Absolute path to the report file, when this entry is a push pointer. */
|
|
14
|
+
ref?: string;
|
|
15
|
+
/** First ~80 chars of the body's first line — enough to decide if it matters. */
|
|
16
|
+
label: string;
|
|
17
|
+
/** Arbitrary structured payload for non-push message entries. */
|
|
18
|
+
data?: Record<string, unknown>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Atomically append one inbox entry to `nodes/<nodeId>/inbox.jsonl`.
|
|
22
|
+
* Fills `ts` (current ISO time). Returns the completed entry.
|
|
23
|
+
*/
|
|
24
|
+
export declare function appendInbox(nodeId: string, entry: Omit<InboxEntry, 'ts'>): InboxEntry;
|
|
25
|
+
/**
|
|
26
|
+
* Return all inbox entries strictly after `cursorIso`.
|
|
27
|
+
* When `cursorIso` is undefined, returns every entry in the file.
|
|
28
|
+
*/
|
|
29
|
+
export declare function readInboxSince(nodeId: string, cursorIso?: string): InboxEntry[];
|
|
30
|
+
/**
|
|
31
|
+
* Read the persisted cursor ISO for a node's inbox.
|
|
32
|
+
* Returns undefined if no cursor file exists yet.
|
|
33
|
+
*/
|
|
34
|
+
export declare function readCursor(nodeId: string): string | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Persist a new cursor ISO for a node's inbox (atomic tmp+rename).
|
|
37
|
+
*/
|
|
38
|
+
export declare function writeCursor(nodeId: string, iso: string): void;
|
|
39
|
+
/**
|
|
40
|
+
* Render many unread inbox pointers into one compact digest string.
|
|
41
|
+
*
|
|
42
|
+
* Format (per sender group):
|
|
43
|
+
* From <sender> — <N> update(s):
|
|
44
|
+
* [<kind>] <label> (ref: <path>)
|
|
45
|
+
* …
|
|
46
|
+
*
|
|
47
|
+
* A header line announces the total count and instructs the receiver to
|
|
48
|
+
* dereference only what matters.
|
|
49
|
+
*/
|
|
50
|
+
export declare function coalesce(entries: InboxEntry[]): string;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// Per-node inbox.jsonl primitive for the pi-native canvas runtime.
|
|
2
|
+
//
|
|
3
|
+
// An inbox entry is a lightweight POINTER (~30 tokens), never content.
|
|
4
|
+
// The report body lives in nodes/<id>/reports/; the inbox line carries only
|
|
5
|
+
// enough to find it and decide whether to dereference.
|
|
6
|
+
//
|
|
7
|
+
// Layout:
|
|
8
|
+
// nodes/<id>/inbox.jsonl — one JSON line per entry, append-only
|
|
9
|
+
// nodes/<id>/inbox.jsonl.cursor — ISO 8601 of last-read entry (sidecar)
|
|
10
|
+
import { appendFileSync, existsSync, readFileSync, writeFileSync, renameSync, mkdirSync, } from 'node:fs';
|
|
11
|
+
import { dirname } from 'node:path';
|
|
12
|
+
import { inboxPath } from '../canvas/index.js';
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Cursor sidecar path
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
function cursorPath(nodeId) {
|
|
17
|
+
return `${inboxPath(nodeId)}.cursor`;
|
|
18
|
+
}
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Append
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
/**
|
|
23
|
+
* Atomically append one inbox entry to `nodes/<nodeId>/inbox.jsonl`.
|
|
24
|
+
* Fills `ts` (current ISO time). Returns the completed entry.
|
|
25
|
+
*/
|
|
26
|
+
export function appendInbox(nodeId, entry) {
|
|
27
|
+
const full = { ts: new Date().toISOString(), ...entry };
|
|
28
|
+
const line = JSON.stringify(full) + '\n';
|
|
29
|
+
// Ensure the parent directory exists (inbox.jsonl lives directly under the
|
|
30
|
+
// node dir, which ensureNodeDirs creates — but guard anyway for callers that
|
|
31
|
+
// haven't yet scaffolded the node).
|
|
32
|
+
const dir = dirname(inboxPath(nodeId));
|
|
33
|
+
if (!existsSync(dir))
|
|
34
|
+
mkdirSync(dir, { recursive: true });
|
|
35
|
+
// appendFileSync is atomic within a single process (a single write(2) call for
|
|
36
|
+
// a short line is atomic on POSIX). For multi-process safety we rely on the
|
|
37
|
+
// OS-level append guarantee (O_APPEND) which Node honours via 'a' flag.
|
|
38
|
+
appendFileSync(inboxPath(nodeId), line, { encoding: 'utf8', flag: 'a' });
|
|
39
|
+
return full;
|
|
40
|
+
}
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Read
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
/**
|
|
45
|
+
* Return all inbox entries strictly after `cursorIso`.
|
|
46
|
+
* When `cursorIso` is undefined, returns every entry in the file.
|
|
47
|
+
*/
|
|
48
|
+
export function readInboxSince(nodeId, cursorIso) {
|
|
49
|
+
const p = inboxPath(nodeId);
|
|
50
|
+
if (!existsSync(p))
|
|
51
|
+
return [];
|
|
52
|
+
const raw = readFileSync(p, 'utf8');
|
|
53
|
+
const entries = raw
|
|
54
|
+
.split('\n')
|
|
55
|
+
.filter((l) => l.trim() !== '')
|
|
56
|
+
.map((l) => JSON.parse(l));
|
|
57
|
+
if (cursorIso === undefined)
|
|
58
|
+
return entries;
|
|
59
|
+
// Entries are appended in chronological order; filter to those strictly
|
|
60
|
+
// after the cursor. We compare ISO strings lexicographically (valid for UTC).
|
|
61
|
+
return entries.filter((e) => e.ts > cursorIso);
|
|
62
|
+
}
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// Cursor persistence
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
/**
|
|
67
|
+
* Read the persisted cursor ISO for a node's inbox.
|
|
68
|
+
* Returns undefined if no cursor file exists yet.
|
|
69
|
+
*/
|
|
70
|
+
export function readCursor(nodeId) {
|
|
71
|
+
const p = cursorPath(nodeId);
|
|
72
|
+
if (!existsSync(p))
|
|
73
|
+
return undefined;
|
|
74
|
+
const val = readFileSync(p, 'utf8').trim();
|
|
75
|
+
return val !== '' ? val : undefined;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Persist a new cursor ISO for a node's inbox (atomic tmp+rename).
|
|
79
|
+
*/
|
|
80
|
+
export function writeCursor(nodeId, iso) {
|
|
81
|
+
const p = cursorPath(nodeId);
|
|
82
|
+
const tmp = `${p}.tmp`;
|
|
83
|
+
const dir = dirname(p);
|
|
84
|
+
if (!existsSync(dir))
|
|
85
|
+
mkdirSync(dir, { recursive: true });
|
|
86
|
+
writeFileSync(tmp, iso, 'utf8');
|
|
87
|
+
renameSync(tmp, p);
|
|
88
|
+
}
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
// Coalesce
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
/**
|
|
93
|
+
* Render many unread inbox pointers into one compact digest string.
|
|
94
|
+
*
|
|
95
|
+
* Format (per sender group):
|
|
96
|
+
* From <sender> — <N> update(s):
|
|
97
|
+
* [<kind>] <label> (ref: <path>)
|
|
98
|
+
* …
|
|
99
|
+
*
|
|
100
|
+
* A header line announces the total count and instructs the receiver to
|
|
101
|
+
* dereference only what matters.
|
|
102
|
+
*/
|
|
103
|
+
export function coalesce(entries) {
|
|
104
|
+
if (entries.length === 0)
|
|
105
|
+
return '(inbox empty)';
|
|
106
|
+
const header = `${entries.length} update${entries.length === 1 ? '' : 's'} since last read — dereference what matters.\n`;
|
|
107
|
+
// Group by `from` (null → 'system').
|
|
108
|
+
const groups = new Map();
|
|
109
|
+
for (const e of entries) {
|
|
110
|
+
const key = e.from ?? 'system';
|
|
111
|
+
if (!groups.has(key))
|
|
112
|
+
groups.set(key, []);
|
|
113
|
+
groups.get(key).push(e);
|
|
114
|
+
}
|
|
115
|
+
const sections = [];
|
|
116
|
+
for (const [sender, items] of groups) {
|
|
117
|
+
const lines = items.map((e) => {
|
|
118
|
+
const refPart = e.ref !== undefined ? ` (ref: ${e.ref})` : '';
|
|
119
|
+
return ` [${e.kind}] ${e.label}${refPart}`;
|
|
120
|
+
});
|
|
121
|
+
sections.push(`From ${sender} — ${items.length} update${items.length === 1 ? '' : 's'}:\n${lines.join('\n')}`);
|
|
122
|
+
}
|
|
123
|
+
return header + sections.join('\n\n');
|
|
124
|
+
}
|
package/dist/core/help.js
CHANGED
|
@@ -45,7 +45,8 @@ function pad(s, width) {
|
|
|
45
45
|
// ---------------------------------------------------------------------------
|
|
46
46
|
// renderRoot
|
|
47
47
|
// ---------------------------------------------------------------------------
|
|
48
|
-
const IO_CONTRACT = 'I/O contract: flags and positional args on input
|
|
48
|
+
const IO_CONTRACT = 'I/O contract: flags and positional args on input; stdout is agent-ready markdown/XML you\n' +
|
|
49
|
+
'act on directly — read it as a continuation of your prompt, don\'t parse it as data.\n' +
|
|
49
50
|
'Exit 0 on success, non-zero on failure. Schemas appear at leaf -h.';
|
|
50
51
|
// Behavioral instruction (not a schema) — engrained in the appended system
|
|
51
52
|
// prompt so the model treats unfamiliar capabilities as a cue to discover the
|
|
@@ -180,8 +181,9 @@ export function renderLeafArgv(h) {
|
|
|
180
181
|
lines.push(h.inputNote !== undefined ? h.inputNote : 'No input parameters.');
|
|
181
182
|
}
|
|
182
183
|
lines.push('');
|
|
183
|
-
|
|
184
|
-
|
|
184
|
+
// The result is rendered as instruction-shaped XML+markdown; these fields are
|
|
185
|
+
// the information it carries, in order, not a literal JSON shape.
|
|
186
|
+
lines.push('Output (fields carried in the rendered result)');
|
|
185
187
|
const outNameW = maxLen(h.output.map((f) => f.name));
|
|
186
188
|
for (const f of h.output) {
|
|
187
189
|
lines.push(` ${pad(f.name, outNameW)} ${f.type}. ${f.constraint}`);
|
package/dist/core/io.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { CrtrError } from './errors.js';
|
|
2
2
|
import { type ExitCodeValue } from '../types.js';
|
|
3
|
+
/** Set by the dispatcher when `--json` is present anywhere in argv. */
|
|
4
|
+
export declare function setJsonOutput(v: boolean): void;
|
|
5
|
+
/** True when the caller asked for raw JSON instead of rendered prose. */
|
|
6
|
+
export declare function isJsonOutput(): boolean;
|
|
3
7
|
/** Structured error payload. `error` is a stable code the agent branches on;
|
|
4
8
|
* `next` is the recovery road sign. */
|
|
5
9
|
export interface ErrorPayload {
|
|
@@ -17,10 +21,20 @@ export declare class InputError extends CrtrError {
|
|
|
17
21
|
/** Read raw stdin to EOF. Returns empty string when stdin is a TTY (no pipe).
|
|
18
22
|
* Called by the argv parser for leaves declaring a `stdin` parameter. */
|
|
19
23
|
export declare function readStdinRaw(): Promise<string>;
|
|
20
|
-
/**
|
|
24
|
+
/** Raw-JSON mirror of a single-shot response (the `--json` escape hatch). The
|
|
25
|
+
* default path renders the result as prose instead — see render.ts. */
|
|
21
26
|
export declare function emit(obj: Record<string, unknown>): void;
|
|
22
27
|
/** One JSONL record. Call per event in a stream; partial reads stay parseable. */
|
|
23
28
|
export declare function emitLine(obj: Record<string, unknown>): void;
|
|
29
|
+
/**
|
|
30
|
+
* Write to stdout and resolve true ONLY once the bytes are confirmed flushed to
|
|
31
|
+
* a connected reader. Resolves false if the consumer is gone (EPIPE) or the
|
|
32
|
+
* write fails. This is the reliable "the caller actually received it" signal:
|
|
33
|
+
* use it to gate side effects that must only happen on genuine delivery (e.g.
|
|
34
|
+
* acking a collected result). A killed process never resolves at all — also
|
|
35
|
+
* safe, since the gated side effect then never runs.
|
|
36
|
+
*/
|
|
37
|
+
export declare function writeStdout(s: string): Promise<boolean>;
|
|
24
38
|
export declare function diag(message: string): void;
|
|
25
39
|
/** Terminal error handler. Command-level failures (bad input, not-found,
|
|
26
40
|
* ambiguous) surface as the JSON response on stdout so the caller parses one
|
package/dist/core/io.js
CHANGED
|
@@ -1,9 +1,23 @@
|
|
|
1
|
-
// The agent-facing I/O contract. Flags and positional args on input;
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
1
|
+
// The agent-facing I/O contract. Flags and positional args on input; stdout is
|
|
2
|
+
// agent-ready markdown/XML the caller acts on directly (the result rendered FOR
|
|
3
|
+
// the model, not data it parses); structured errors; stderr is diagnostics only
|
|
4
|
+
// and never carries the result. The raw JSON object is available behind the
|
|
5
|
+
// `--json` global for tooling. See cli-design SKILL.md / reference.md.
|
|
5
6
|
import { CrtrError } from './errors.js';
|
|
6
7
|
import { ExitCode } from '../types.js';
|
|
8
|
+
import { renderError } from './render.js';
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// output mode — prose (default) vs raw JSON (--json global, for tooling)
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
let jsonOutput = false;
|
|
13
|
+
/** Set by the dispatcher when `--json` is present anywhere in argv. */
|
|
14
|
+
export function setJsonOutput(v) {
|
|
15
|
+
jsonOutput = v;
|
|
16
|
+
}
|
|
17
|
+
/** True when the caller asked for raw JSON instead of rendered prose. */
|
|
18
|
+
export function isJsonOutput() {
|
|
19
|
+
return jsonOutput;
|
|
20
|
+
}
|
|
7
21
|
/** A command-level failure: surfaces as the JSON response on stdout. */
|
|
8
22
|
export class InputError extends CrtrError {
|
|
9
23
|
payload;
|
|
@@ -29,7 +43,8 @@ export async function readStdinRaw() {
|
|
|
29
43
|
// ---------------------------------------------------------------------------
|
|
30
44
|
// stdout — the result, nothing else
|
|
31
45
|
// ---------------------------------------------------------------------------
|
|
32
|
-
/**
|
|
46
|
+
/** Raw-JSON mirror of a single-shot response (the `--json` escape hatch). The
|
|
47
|
+
* default path renders the result as prose instead — see render.ts. */
|
|
33
48
|
export function emit(obj) {
|
|
34
49
|
process.stdout.write(JSON.stringify(obj, null, 2) + '\n');
|
|
35
50
|
}
|
|
@@ -37,6 +52,37 @@ export function emit(obj) {
|
|
|
37
52
|
export function emitLine(obj) {
|
|
38
53
|
process.stdout.write(JSON.stringify(obj) + '\n');
|
|
39
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Write to stdout and resolve true ONLY once the bytes are confirmed flushed to
|
|
57
|
+
* a connected reader. Resolves false if the consumer is gone (EPIPE) or the
|
|
58
|
+
* write fails. This is the reliable "the caller actually received it" signal:
|
|
59
|
+
* use it to gate side effects that must only happen on genuine delivery (e.g.
|
|
60
|
+
* acking a collected result). A killed process never resolves at all — also
|
|
61
|
+
* safe, since the gated side effect then never runs.
|
|
62
|
+
*/
|
|
63
|
+
export function writeStdout(s) {
|
|
64
|
+
return new Promise((resolve) => {
|
|
65
|
+
let settled = false;
|
|
66
|
+
const onErr = (e) => {
|
|
67
|
+
if (e.code === 'EPIPE')
|
|
68
|
+
finish(false);
|
|
69
|
+
};
|
|
70
|
+
const finish = (ok) => {
|
|
71
|
+
if (settled)
|
|
72
|
+
return;
|
|
73
|
+
settled = true;
|
|
74
|
+
process.stdout.off('error', onErr);
|
|
75
|
+
resolve(ok);
|
|
76
|
+
};
|
|
77
|
+
process.stdout.on('error', onErr);
|
|
78
|
+
try {
|
|
79
|
+
process.stdout.write(s, (err) => finish(err === null || err === undefined));
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
finish(false);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
40
86
|
// ---------------------------------------------------------------------------
|
|
41
87
|
// stderr — diagnostics the agent MAY capture, never the result
|
|
42
88
|
// ---------------------------------------------------------------------------
|
|
@@ -67,7 +113,11 @@ function payloadOf(e) {
|
|
|
67
113
|
* raw traces never reach the agent. Exits non-zero either way. */
|
|
68
114
|
export function handle(e) {
|
|
69
115
|
if (e instanceof CrtrError) {
|
|
70
|
-
|
|
116
|
+
const payload = payloadOf(e);
|
|
117
|
+
const out = jsonOutput
|
|
118
|
+
? JSON.stringify(payload, null, 2)
|
|
119
|
+
: renderError(payload);
|
|
120
|
+
process.stdout.write(out + '\n');
|
|
71
121
|
process.exit(e.exitCode);
|
|
72
122
|
}
|
|
73
123
|
const err = e;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persona composer public surface.
|
|
3
|
+
*
|
|
4
|
+
* Re-exports:
|
|
5
|
+
* - loadPersona / loadKernel / availableKinds (loader — raw file access)
|
|
6
|
+
* - resolve (high-level composer)
|
|
7
|
+
* - ResolvedPersona (return type of resolve)
|
|
8
|
+
*/
|
|
9
|
+
export { loadPersona, loadKernel, availableKinds } from './loader.js';
|
|
10
|
+
export type { LoadedPersona } from './loader.js';
|
|
11
|
+
export { resolve } from './resolve.js';
|
|
12
|
+
export type { ResolvedPersona } from './resolve.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persona composer public surface.
|
|
3
|
+
*
|
|
4
|
+
* Re-exports:
|
|
5
|
+
* - loadPersona / loadKernel / availableKinds (loader — raw file access)
|
|
6
|
+
* - resolve (high-level composer)
|
|
7
|
+
* - ResolvedPersona (return type of resolve)
|
|
8
|
+
*/
|
|
9
|
+
export { loadPersona, loadKernel, availableKinds } from './loader.js';
|
|
10
|
+
export { resolve } from './resolve.js';
|