@crouton-kit/crouter 0.3.8 → 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 +25 -27
- package/dist/commands/{job.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/{agent.d.ts → daemon.d.ts} +1 -1
- 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 +15 -427
- 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 +8 -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 +12 -681
- 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 +9 -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/__tests__/resolver.test.js +69 -1
- package/dist/core/__tests__/unknown-path.test.d.ts +1 -0
- package/dist/core/__tests__/unknown-path.test.js +52 -0
- package/dist/core/bootstrap.d.ts +2 -0
- package/dist/core/bootstrap.js +66 -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 +63 -2
- package/dist/core/command.js +97 -24
- 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/frontmatter.d.ts +10 -0
- package/dist/core/frontmatter.js +24 -9
- package/dist/core/help.d.ts +39 -8
- package/dist/core/help.js +69 -35
- 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 +160 -2
- 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 -80
- package/dist/core/spawn.js +15 -219
- package/dist/daemon/crtrd-cli.d.ts +1 -0
- 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/dist/types.d.ts +21 -0
- package/dist/types.js +3 -0
- package/package.json +6 -5
- package/dist/commands/agent.js +0 -384
- package/dist/commands/debug.d.ts +0 -3
- package/dist/commands/debug.js +0 -179
- package/dist/commands/job.js +0 -344
- package/dist/commands/plan.d.ts +0 -4
- package/dist/commands/plan.js +0 -309
- package/dist/commands/spec.d.ts +0 -3
- package/dist/commands/spec.js +0 -286
- 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 -66
- package/dist/core/jobs.d.ts +0 -101
- package/dist/core/jobs.js +0 -462
- package/dist/prompts/agent.d.ts +0 -18
- package/dist/prompts/agent.js +0 -153
- 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/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';
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persona file loader.
|
|
3
|
+
*
|
|
4
|
+
* Discovers and parses persona markdown files with YAML frontmatter.
|
|
5
|
+
* Resolution order (highest → lowest precedence): project > user > builtin.
|
|
6
|
+
*
|
|
7
|
+
* Layout on disk:
|
|
8
|
+
* <root>/personas/<kind>/base.md
|
|
9
|
+
* <root>/personas/<kind>/orchestrator.md
|
|
10
|
+
* <root>/personas/orchestration-kernel.md
|
|
11
|
+
*
|
|
12
|
+
* The builtin root is src/builtin-personas (or dist/builtin-personas in the
|
|
13
|
+
* compiled build), resolved relative to this module — mirrors the pattern used
|
|
14
|
+
* for STOPHOOK_PATH in spawn.ts.
|
|
15
|
+
*/
|
|
16
|
+
export interface LoadedPersona {
|
|
17
|
+
/** Raw, uncoerced frontmatter key/value record (null when no frontmatter). */
|
|
18
|
+
frontmatter: Record<string, unknown> | null;
|
|
19
|
+
/** Body text with any @include directives inlined. */
|
|
20
|
+
body: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Load and parse a persona file for the given `kind` and `mode`.
|
|
24
|
+
*
|
|
25
|
+
* Returns `null` when no file is found in any scope (project/user/builtin).
|
|
26
|
+
* On success, `@include` directives in the body are resolved and inlined.
|
|
27
|
+
*/
|
|
28
|
+
export declare function loadPersona(kind: string, mode: 'base' | 'orchestrator'): LoadedPersona | null;
|
|
29
|
+
/**
|
|
30
|
+
* Load the raw text of the orchestration kernel (no frontmatter, body only).
|
|
31
|
+
* Returns an empty string if the kernel file cannot be found.
|
|
32
|
+
*/
|
|
33
|
+
export declare function loadKernel(): string;
|
|
34
|
+
/**
|
|
35
|
+
* Load the base runtime prompt — the node operating protocol prepended to
|
|
36
|
+
* EVERY persona (push/finish/delegate/feed/ask). Returns '' if not found.
|
|
37
|
+
*/
|
|
38
|
+
export declare function loadRuntimeBase(): string;
|
|
39
|
+
/**
|
|
40
|
+
* Enumerate the kinds with at least one persona file (base.md or
|
|
41
|
+
* orchestrator.md) across all scope roots (project/user/builtin). Used to
|
|
42
|
+
* validate a requested `--kind` and to list the valid choices.
|
|
43
|
+
*/
|
|
44
|
+
export declare function availableKinds(): string[];
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persona file loader.
|
|
3
|
+
*
|
|
4
|
+
* Discovers and parses persona markdown files with YAML frontmatter.
|
|
5
|
+
* Resolution order (highest → lowest precedence): project > user > builtin.
|
|
6
|
+
*
|
|
7
|
+
* Layout on disk:
|
|
8
|
+
* <root>/personas/<kind>/base.md
|
|
9
|
+
* <root>/personas/<kind>/orchestrator.md
|
|
10
|
+
* <root>/personas/orchestration-kernel.md
|
|
11
|
+
*
|
|
12
|
+
* The builtin root is src/builtin-personas (or dist/builtin-personas in the
|
|
13
|
+
* compiled build), resolved relative to this module — mirrors the pattern used
|
|
14
|
+
* for STOPHOOK_PATH in spawn.ts.
|
|
15
|
+
*/
|
|
16
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
17
|
+
import { dirname, join } from 'node:path';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
import { parseFrontmatterGeneric } from '../frontmatter.js';
|
|
20
|
+
import { userScopeRoot, findProjectScopeRoot } from '../scope.js';
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Builtin root resolution
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
/**
|
|
25
|
+
* Resolve the builtin-personas directory from the location of this compiled
|
|
26
|
+
* or source module. Works from both `dist/core/personas/loader.js` and
|
|
27
|
+
* `src/core/personas/loader.ts` (tsx dev runs).
|
|
28
|
+
*/
|
|
29
|
+
function builtinPersonasRoot() {
|
|
30
|
+
const here = dirname(fileURLToPath(import.meta.url)); // dist/core/personas or src/core/personas
|
|
31
|
+
const coreDir = dirname(here); // dist/core or src/core
|
|
32
|
+
const pkgDir = dirname(coreDir); // dist/ or src/
|
|
33
|
+
const distPath = join(pkgDir, 'builtin-personas');
|
|
34
|
+
const srcPath = join(dirname(pkgDir), 'src', 'builtin-personas');
|
|
35
|
+
// Prefer the path that exists; fall back to the dist-sibling (which may not
|
|
36
|
+
// exist yet if the package is being run pre-build, but that is the caller's
|
|
37
|
+
// problem).
|
|
38
|
+
if (existsSync(distPath))
|
|
39
|
+
return distPath;
|
|
40
|
+
if (existsSync(srcPath))
|
|
41
|
+
return srcPath;
|
|
42
|
+
return distPath;
|
|
43
|
+
}
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Scope roots for personas
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
/** Returns the ordered list of roots to search, highest precedence first. */
|
|
48
|
+
function personaSearchRoots() {
|
|
49
|
+
const roots = [];
|
|
50
|
+
const projectRoot = findProjectScopeRoot();
|
|
51
|
+
if (projectRoot)
|
|
52
|
+
roots.push(join(projectRoot, 'personas'));
|
|
53
|
+
roots.push(join(userScopeRoot(), 'personas'));
|
|
54
|
+
roots.push(builtinPersonasRoot());
|
|
55
|
+
return roots;
|
|
56
|
+
}
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// File resolution helpers
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
/**
|
|
61
|
+
* Find the first existing file across the scope roots.
|
|
62
|
+
* `relativePath` is relative to each root (e.g. 'general/base.md').
|
|
63
|
+
*/
|
|
64
|
+
function resolveFile(relativePath) {
|
|
65
|
+
for (const root of personaSearchRoots()) {
|
|
66
|
+
const candidate = join(root, relativePath);
|
|
67
|
+
if (existsSync(candidate))
|
|
68
|
+
return candidate;
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// @include inlining
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
const INCLUDE_RE = /^@include\s+(\S+)\s*$/m;
|
|
76
|
+
/**
|
|
77
|
+
* Inline any `@include <filename>` directive found in `body`.
|
|
78
|
+
* The included file is looked up via the same scope-resolution chain; if it
|
|
79
|
+
* cannot be found, the directive line is replaced with an empty string rather
|
|
80
|
+
* than throwing — callers that need the kernel should use `loadKernel()`
|
|
81
|
+
* directly and can assert it themselves.
|
|
82
|
+
*/
|
|
83
|
+
function inlineIncludes(body) {
|
|
84
|
+
return body.replace(INCLUDE_RE, (_match, filename) => {
|
|
85
|
+
const path = resolveFile(filename);
|
|
86
|
+
if (!path)
|
|
87
|
+
return '';
|
|
88
|
+
const src = readFileSync(path, 'utf8');
|
|
89
|
+
// The kernel file has no frontmatter, but run through the parser just in
|
|
90
|
+
// case someone added one in a user/project override.
|
|
91
|
+
const { body: kernelBody } = parseFrontmatterGeneric(src);
|
|
92
|
+
return kernelBody.trim();
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Load and parse a persona file for the given `kind` and `mode`.
|
|
97
|
+
*
|
|
98
|
+
* Returns `null` when no file is found in any scope (project/user/builtin).
|
|
99
|
+
* On success, `@include` directives in the body are resolved and inlined.
|
|
100
|
+
*/
|
|
101
|
+
export function loadPersona(kind, mode) {
|
|
102
|
+
const relativePath = `${kind}/${mode}.md`;
|
|
103
|
+
const filePath = resolveFile(relativePath);
|
|
104
|
+
if (!filePath)
|
|
105
|
+
return null;
|
|
106
|
+
const src = readFileSync(filePath, 'utf8');
|
|
107
|
+
const { data, body } = parseFrontmatterGeneric(src);
|
|
108
|
+
return {
|
|
109
|
+
frontmatter: data,
|
|
110
|
+
body: inlineIncludes(body).trim(),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Load the raw text of the orchestration kernel (no frontmatter, body only).
|
|
115
|
+
* Returns an empty string if the kernel file cannot be found.
|
|
116
|
+
*/
|
|
117
|
+
export function loadKernel() {
|
|
118
|
+
const filePath = resolveFile('orchestration-kernel.md');
|
|
119
|
+
if (!filePath)
|
|
120
|
+
return '';
|
|
121
|
+
const src = readFileSync(filePath, 'utf8');
|
|
122
|
+
const { body } = parseFrontmatterGeneric(src);
|
|
123
|
+
return body.trim();
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Load the base runtime prompt — the node operating protocol prepended to
|
|
127
|
+
* EVERY persona (push/finish/delegate/feed/ask). Returns '' if not found.
|
|
128
|
+
*/
|
|
129
|
+
export function loadRuntimeBase() {
|
|
130
|
+
const filePath = resolveFile('runtime-base.md');
|
|
131
|
+
if (!filePath)
|
|
132
|
+
return '';
|
|
133
|
+
const src = readFileSync(filePath, 'utf8');
|
|
134
|
+
const { body } = parseFrontmatterGeneric(src);
|
|
135
|
+
return body.trim();
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Enumerate the kinds with at least one persona file (base.md or
|
|
139
|
+
* orchestrator.md) across all scope roots (project/user/builtin). Used to
|
|
140
|
+
* validate a requested `--kind` and to list the valid choices.
|
|
141
|
+
*/
|
|
142
|
+
export function availableKinds() {
|
|
143
|
+
const kinds = new Set();
|
|
144
|
+
for (const root of personaSearchRoots()) {
|
|
145
|
+
if (!existsSync(root))
|
|
146
|
+
continue;
|
|
147
|
+
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
148
|
+
if (!entry.isDirectory())
|
|
149
|
+
continue;
|
|
150
|
+
const dir = join(root, entry.name);
|
|
151
|
+
if (existsSync(join(dir, 'base.md')) || existsSync(join(dir, 'orchestrator.md'))) {
|
|
152
|
+
kinds.add(entry.name);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return [...kinds].sort();
|
|
157
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persona resolver — composes base + orchestrator personas into a
|
|
3
|
+
* ResolvedPersona ready for use when spawning a canvas node.
|
|
4
|
+
*
|
|
5
|
+
* Composition rules:
|
|
6
|
+
* mode==='base'
|
|
7
|
+
* → load <kind>/base.md; if missing fall back to general defaults.
|
|
8
|
+
*
|
|
9
|
+
* mode==='orchestrator'
|
|
10
|
+
* → prefer <kind>/orchestrator.md (which must embed the kernel via
|
|
11
|
+
* @include orchestration-kernel.md — inlined by the loader).
|
|
12
|
+
* If no orchestrator.md exists for this kind, compose:
|
|
13
|
+
* <kind>/base.md body + '\n\n' + kernel body
|
|
14
|
+
* If even the base is missing, fall back to general defaults + kernel.
|
|
15
|
+
*
|
|
16
|
+
* Frontmatter from whichever file is the primary source (orchestrator.md >
|
|
17
|
+
* base.md) supplies model/lifecycle/skills/extensions/tools.
|
|
18
|
+
*
|
|
19
|
+
* Lifecycle defaults:
|
|
20
|
+
* base → 'terminal'
|
|
21
|
+
* orchestrator → 'resident'
|
|
22
|
+
*/
|
|
23
|
+
export interface ResolvedPersona {
|
|
24
|
+
systemPrompt: string;
|
|
25
|
+
extensions: string[];
|
|
26
|
+
skills: string[];
|
|
27
|
+
model?: string;
|
|
28
|
+
lifecycle: 'terminal' | 'resident';
|
|
29
|
+
tools?: string[];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Resolve a fully composed persona for the given `kind` and `mode`.
|
|
33
|
+
*
|
|
34
|
+
* Never throws for missing files — missing personas produce sensible defaults.
|
|
35
|
+
*/
|
|
36
|
+
export declare function resolve(kind: string, mode: 'base' | 'orchestrator'): ResolvedPersona;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persona resolver — composes base + orchestrator personas into a
|
|
3
|
+
* ResolvedPersona ready for use when spawning a canvas node.
|
|
4
|
+
*
|
|
5
|
+
* Composition rules:
|
|
6
|
+
* mode==='base'
|
|
7
|
+
* → load <kind>/base.md; if missing fall back to general defaults.
|
|
8
|
+
*
|
|
9
|
+
* mode==='orchestrator'
|
|
10
|
+
* → prefer <kind>/orchestrator.md (which must embed the kernel via
|
|
11
|
+
* @include orchestration-kernel.md — inlined by the loader).
|
|
12
|
+
* If no orchestrator.md exists for this kind, compose:
|
|
13
|
+
* <kind>/base.md body + '\n\n' + kernel body
|
|
14
|
+
* If even the base is missing, fall back to general defaults + kernel.
|
|
15
|
+
*
|
|
16
|
+
* Frontmatter from whichever file is the primary source (orchestrator.md >
|
|
17
|
+
* base.md) supplies model/lifecycle/skills/extensions/tools.
|
|
18
|
+
*
|
|
19
|
+
* Lifecycle defaults:
|
|
20
|
+
* base → 'terminal'
|
|
21
|
+
* orchestrator → 'resident'
|
|
22
|
+
*/
|
|
23
|
+
import { loadPersona, loadKernel, loadRuntimeBase } from './loader.js';
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Helpers
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
function toStringArray(v) {
|
|
28
|
+
if (!Array.isArray(v))
|
|
29
|
+
return [];
|
|
30
|
+
return v.filter((x) => typeof x === 'string');
|
|
31
|
+
}
|
|
32
|
+
function toOptionalString(v) {
|
|
33
|
+
return typeof v === 'string' ? v : undefined;
|
|
34
|
+
}
|
|
35
|
+
function toLifecycle(v, defaultValue) {
|
|
36
|
+
if (v === 'terminal' || v === 'resident')
|
|
37
|
+
return v;
|
|
38
|
+
return defaultValue;
|
|
39
|
+
}
|
|
40
|
+
/** The bare-minimum system prompt used when no persona file is found at all. */
|
|
41
|
+
function fallbackBasePrompt(kind) {
|
|
42
|
+
return `You are a ${kind} agent. Complete the task you have been given.`;
|
|
43
|
+
}
|
|
44
|
+
/** Prepend the base runtime protocol (push/finish/delegate/feed/ask) to a
|
|
45
|
+
* persona's prompt — every node, every kind, every mode gets it first. */
|
|
46
|
+
function withBase(personaPrompt) {
|
|
47
|
+
const base = loadRuntimeBase();
|
|
48
|
+
return base ? `${base}\n\n---\n\n${personaPrompt}` : personaPrompt;
|
|
49
|
+
}
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
// Public API
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
/**
|
|
54
|
+
* Resolve a fully composed persona for the given `kind` and `mode`.
|
|
55
|
+
*
|
|
56
|
+
* Never throws for missing files — missing personas produce sensible defaults.
|
|
57
|
+
*/
|
|
58
|
+
export function resolve(kind, mode) {
|
|
59
|
+
if (mode === 'base') {
|
|
60
|
+
const persona = loadPersona(kind, 'base');
|
|
61
|
+
if (!persona) {
|
|
62
|
+
// No persona file for this kind — use minimal defaults.
|
|
63
|
+
return {
|
|
64
|
+
systemPrompt: withBase(fallbackBasePrompt(kind)),
|
|
65
|
+
extensions: [],
|
|
66
|
+
skills: [],
|
|
67
|
+
lifecycle: 'terminal',
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const fm = persona.frontmatter ?? {};
|
|
71
|
+
return {
|
|
72
|
+
systemPrompt: withBase(persona.body || fallbackBasePrompt(kind)),
|
|
73
|
+
extensions: toStringArray(fm['extensions']),
|
|
74
|
+
skills: toStringArray(fm['skills']),
|
|
75
|
+
model: toOptionalString(fm['model']),
|
|
76
|
+
lifecycle: toLifecycle(fm['lifecycle'], 'terminal'),
|
|
77
|
+
tools: fm['tools'] !== undefined ? toStringArray(fm['tools']) : undefined,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
// mode === 'orchestrator'
|
|
81
|
+
const orchestratorPersona = loadPersona(kind, 'orchestrator');
|
|
82
|
+
if (orchestratorPersona) {
|
|
83
|
+
// Orchestrator file exists; @include was already inlined by the loader.
|
|
84
|
+
const fm = orchestratorPersona.frontmatter ?? {};
|
|
85
|
+
return {
|
|
86
|
+
systemPrompt: withBase(orchestratorPersona.body || fallbackBasePrompt(kind)),
|
|
87
|
+
extensions: toStringArray(fm['extensions']),
|
|
88
|
+
skills: toStringArray(fm['skills']),
|
|
89
|
+
model: toOptionalString(fm['model']),
|
|
90
|
+
lifecycle: toLifecycle(fm['lifecycle'], 'resident'),
|
|
91
|
+
tools: fm['tools'] !== undefined ? toStringArray(fm['tools']) : undefined,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
// No orchestrator.md for this kind — compose base + bare kernel.
|
|
95
|
+
const kernel = loadKernel();
|
|
96
|
+
const basePersona = loadPersona(kind, 'base');
|
|
97
|
+
const baseBody = basePersona?.body || fallbackBasePrompt(kind);
|
|
98
|
+
const fm = basePersona?.frontmatter ?? {};
|
|
99
|
+
// Append the kernel to the base body (with separator if kernel is non-empty).
|
|
100
|
+
const systemPrompt = kernel ? `${baseBody}\n\n${kernel}` : baseBody;
|
|
101
|
+
return {
|
|
102
|
+
systemPrompt: withBase(systemPrompt),
|
|
103
|
+
extensions: toStringArray(fm['extensions']),
|
|
104
|
+
skills: toStringArray(fm['skills']),
|
|
105
|
+
model: toOptionalString(fm['model']),
|
|
106
|
+
// Override lifecycle to 'resident' — this node is being used as an orchestrator.
|
|
107
|
+
lifecycle: 'resident',
|
|
108
|
+
tools: fm['tools'] !== undefined ? toStringArray(fm['tools']) : undefined,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { LeafHelp } from './help.js';
|
|
2
|
+
import type { ErrorPayload } from './io.js';
|
|
3
|
+
/** Schema-driven fallback: turn a result object into agent-ready XML+markdown.
|
|
4
|
+
* Field order follows the leaf's declared output schema; any extra keys append
|
|
5
|
+
* after. Scalars become attributes or a bullet list, prose and collections
|
|
6
|
+
* become their own fenced blocks. */
|
|
7
|
+
export declare function renderResult(result: Record<string, unknown>, help: LeafHelp): string;
|
|
8
|
+
/** Render a structured failure as an instruction-shaped block: what broke, what
|
|
9
|
+
* was received, and the recovery road sign — the same recovery info the JSON
|
|
10
|
+
* payload carries, shaped for the model to act on. */
|
|
11
|
+
export declare function renderError(p: ErrorPayload): string;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// Default stdout rendering: the result a leaf returns is written FOR the model
|
|
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.
|
|
5
|
+
//
|
|
6
|
+
// 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.
|
|
10
|
+
// 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.
|
|
12
|
+
const PROSE_FIELDS = new Set([
|
|
13
|
+
'follow_up', 'digest', 'message', 'next', 'result', 'content', 'body', 'guide',
|
|
14
|
+
'summary', 'note', 'reason', 'detail', 'details', 'hint', 'instruction',
|
|
15
|
+
'instructions', 'prompt', 'roadmap', 'report', 'answer', 'response', 'text',
|
|
16
|
+
]);
|
|
17
|
+
/** Minimal attribute-value sanitization: values here are controlled (ids,
|
|
18
|
+
* statuses, counts), so collapse quotes rather than entity-escape. */
|
|
19
|
+
function attrEsc(s) {
|
|
20
|
+
return String(s).replace(/"/g, "'").replace(/\n/g, ' ');
|
|
21
|
+
}
|
|
22
|
+
function scalarStr(v) {
|
|
23
|
+
if (v === null || v === undefined)
|
|
24
|
+
return '';
|
|
25
|
+
if (Array.isArray(v))
|
|
26
|
+
return v.map(scalarStr).join(', ');
|
|
27
|
+
if (typeof v === 'object')
|
|
28
|
+
return JSON.stringify(v);
|
|
29
|
+
return String(v);
|
|
30
|
+
}
|
|
31
|
+
/** A string field is prose when it is named as such, spans lines, or is long
|
|
32
|
+
* enough that it reads as a sentence rather than an identifier. */
|
|
33
|
+
function isProse(name, v) {
|
|
34
|
+
if (typeof v !== 'string')
|
|
35
|
+
return false;
|
|
36
|
+
if (PROSE_FIELDS.has(name))
|
|
37
|
+
return true;
|
|
38
|
+
return v.includes('\n') || v.length > 80;
|
|
39
|
+
}
|
|
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';
|
|
50
|
+
}
|
|
51
|
+
function renderArray(name, arr) {
|
|
52
|
+
if (arr.length === 0)
|
|
53
|
+
return `<${name} count="0"></${name}>`;
|
|
54
|
+
const allObjects = arr.every((x) => x !== null && typeof x === 'object' && !Array.isArray(x));
|
|
55
|
+
if (allObjects) {
|
|
56
|
+
const rows = arr;
|
|
57
|
+
const cols = [];
|
|
58
|
+
for (const r of rows)
|
|
59
|
+
for (const k of Object.keys(r))
|
|
60
|
+
if (!cols.includes(k))
|
|
61
|
+
cols.push(k);
|
|
62
|
+
const head = `| ${cols.join(' | ')} |`;
|
|
63
|
+
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}>`;
|
|
66
|
+
}
|
|
67
|
+
const items = arr.map((x) => `- ${scalarStr(x)}`).join('\n');
|
|
68
|
+
return `<${name} count="${arr.length}">\n${items}\n</${name}>`;
|
|
69
|
+
}
|
|
70
|
+
/** Schema-driven fallback: turn a result object into agent-ready XML+markdown.
|
|
71
|
+
* 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. */
|
|
74
|
+
export function renderResult(result, help) {
|
|
75
|
+
const order = help.output.map((f) => f.name);
|
|
76
|
+
for (const k of Object.keys(result))
|
|
77
|
+
if (!order.includes(k))
|
|
78
|
+
order.push(k);
|
|
79
|
+
const attrs = [];
|
|
80
|
+
const bullets = [];
|
|
81
|
+
const blocks = [];
|
|
82
|
+
for (const name of order) {
|
|
83
|
+
const v = result[name];
|
|
84
|
+
if (v === undefined || v === null)
|
|
85
|
+
continue;
|
|
86
|
+
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}>`);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
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)}"`);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
bullets.push(`- ${name}: ${scalarStr(v)}`);
|
|
103
|
+
}
|
|
104
|
+
const tag = rootTag(help);
|
|
105
|
+
const open = attrs.length > 0 ? `<${tag} ${attrs.join(' ')}>` : `<${tag}>`;
|
|
106
|
+
const parts = [];
|
|
107
|
+
if (bullets.length > 0)
|
|
108
|
+
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}>`;
|
|
113
|
+
}
|
|
114
|
+
/** Render a structured failure as an instruction-shaped block: what broke, what
|
|
115
|
+
* was received, and the recovery road sign — the same recovery info the JSON
|
|
116
|
+
* payload carries, shaped for the model to act on. */
|
|
117
|
+
export function renderError(p) {
|
|
118
|
+
const lines = [p.message];
|
|
119
|
+
if (p.received !== undefined && p.received !== null && p.received !== '') {
|
|
120
|
+
lines.push(`received: ${scalarStr(p.received)}`);
|
|
121
|
+
}
|
|
122
|
+
if (p.field !== undefined)
|
|
123
|
+
lines.push(`field: ${p.field}`);
|
|
124
|
+
lines.push(`Next: ${p.next}`);
|
|
125
|
+
return `<error code="${attrEsc(p.error)}">\n${lines.join('\n')}\n</error>`;
|
|
126
|
+
}
|
package/dist/core/resolver.d.ts
CHANGED
|
@@ -28,5 +28,15 @@ export declare function parseSkillQualifier(raw: string): ParsedSkillQualifier;
|
|
|
28
28
|
export declare function listInstalledMarketplaces(scope: Scope): InstalledMarketplace[];
|
|
29
29
|
export declare function listAllMarketplaces(): InstalledMarketplace[];
|
|
30
30
|
export declare function findMarketplaceByName(name: string, scope?: Scope): InstalledMarketplace | null;
|
|
31
|
+
export interface CategoryResolution {
|
|
32
|
+
id: string;
|
|
33
|
+
plugin: string | undefined;
|
|
34
|
+
scope: Scope | undefined;
|
|
35
|
+
dir: string;
|
|
36
|
+
indexPath: string | undefined;
|
|
37
|
+
skills: Skill[];
|
|
38
|
+
}
|
|
39
|
+
export declare function resolveCategory(name: string, opts?: SkillResolutionOpts): CategoryResolution | null;
|
|
40
|
+
export declare function buildCategoryIndex(cat: CategoryResolution): string;
|
|
31
41
|
export declare function scopeRootsLabel(): string;
|
|
32
42
|
export {};
|