@cotal-ai/cli 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/dist/commands/down-manifest.d.ts +6 -0
  2. package/dist/commands/down-manifest.d.ts.map +1 -0
  3. package/dist/commands/down-manifest.js +282 -0
  4. package/dist/commands/down-manifest.js.map +1 -0
  5. package/dist/commands/down.d.ts +5 -3
  6. package/dist/commands/down.d.ts.map +1 -1
  7. package/dist/commands/down.js +24 -4
  8. package/dist/commands/down.js.map +1 -1
  9. package/dist/commands/meshes.js +1 -1
  10. package/dist/commands/meshes.js.map +1 -1
  11. package/dist/commands/mint.d.ts.map +1 -1
  12. package/dist/commands/mint.js +2 -1
  13. package/dist/commands/mint.js.map +1 -1
  14. package/dist/commands/setup.d.ts.map +1 -1
  15. package/dist/commands/setup.js +66 -16
  16. package/dist/commands/setup.js.map +1 -1
  17. package/dist/commands/spawn-manifest.d.ts +10 -0
  18. package/dist/commands/spawn-manifest.d.ts.map +1 -0
  19. package/dist/commands/spawn-manifest.js +197 -0
  20. package/dist/commands/spawn-manifest.js.map +1 -0
  21. package/dist/commands/spawn.d.ts.map +1 -1
  22. package/dist/commands/spawn.js +31 -4
  23. package/dist/commands/spawn.js.map +1 -1
  24. package/dist/commands/topology.d.ts +10 -0
  25. package/dist/commands/topology.d.ts.map +1 -0
  26. package/dist/commands/topology.js +46 -0
  27. package/dist/commands/topology.js.map +1 -0
  28. package/dist/commands/up.d.ts +4 -0
  29. package/dist/commands/up.d.ts.map +1 -1
  30. package/dist/commands/up.js +112 -2
  31. package/dist/commands/up.js.map +1 -1
  32. package/dist/commands/use.d.ts +1 -1
  33. package/dist/commands/use.d.ts.map +1 -1
  34. package/dist/commands/use.js +1 -1
  35. package/dist/commands/use.js.map +1 -1
  36. package/dist/commands/web.d.ts.map +1 -1
  37. package/dist/commands/web.js +3 -1
  38. package/dist/commands/web.js.map +1 -1
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +10 -2
  41. package/dist/index.js.map +1 -1
  42. package/dist/lib/connect.d.ts +18 -16
  43. package/dist/lib/connect.d.ts.map +1 -1
  44. package/dist/lib/connect.js +27 -51
  45. package/dist/lib/connect.js.map +1 -1
  46. package/dist/lib/delivery-proc.d.ts.map +1 -1
  47. package/dist/lib/delivery-proc.js +2 -1
  48. package/dist/lib/delivery-proc.js.map +1 -1
  49. package/dist/lib/manager-proc.d.ts +4 -0
  50. package/dist/lib/manager-proc.d.ts.map +1 -1
  51. package/dist/lib/manager-proc.js +17 -0
  52. package/dist/lib/manager-proc.js.map +1 -1
  53. package/dist/lib/manifest/apply.d.ts +29 -0
  54. package/dist/lib/manifest/apply.d.ts.map +1 -0
  55. package/dist/lib/manifest/apply.js +138 -0
  56. package/dist/lib/manifest/apply.js.map +1 -0
  57. package/dist/lib/manifest/errors.d.ts +21 -0
  58. package/dist/lib/manifest/errors.d.ts.map +1 -0
  59. package/dist/lib/manifest/errors.js +19 -0
  60. package/dist/lib/manifest/errors.js.map +1 -0
  61. package/dist/lib/manifest/index.d.ts +13 -0
  62. package/dist/lib/manifest/index.d.ts.map +1 -0
  63. package/dist/lib/manifest/index.js +21 -0
  64. package/dist/lib/manifest/index.js.map +1 -0
  65. package/dist/lib/manifest/ledger.d.ts +81 -0
  66. package/dist/lib/manifest/ledger.d.ts.map +1 -0
  67. package/dist/lib/manifest/ledger.js +213 -0
  68. package/dist/lib/manifest/ledger.js.map +1 -0
  69. package/dist/lib/manifest/live.d.ts +25 -0
  70. package/dist/lib/manifest/live.d.ts.map +1 -0
  71. package/dist/lib/manifest/live.js +61 -0
  72. package/dist/lib/manifest/live.js.map +1 -0
  73. package/dist/lib/manifest/model.d.ts +71 -0
  74. package/dist/lib/manifest/model.d.ts.map +1 -0
  75. package/dist/lib/manifest/model.js +2 -0
  76. package/dist/lib/manifest/model.js.map +1 -0
  77. package/dist/lib/manifest/preflight.d.ts +12 -0
  78. package/dist/lib/manifest/preflight.d.ts.map +1 -0
  79. package/dist/lib/manifest/preflight.js +43 -0
  80. package/dist/lib/manifest/preflight.js.map +1 -0
  81. package/dist/lib/manifest/prepare.d.ts +57 -0
  82. package/dist/lib/manifest/prepare.d.ts.map +1 -0
  83. package/dist/lib/manifest/prepare.js +95 -0
  84. package/dist/lib/manifest/prepare.js.map +1 -0
  85. package/dist/lib/manifest/render.d.ts +41 -0
  86. package/dist/lib/manifest/render.d.ts.map +1 -0
  87. package/dist/lib/manifest/render.js +177 -0
  88. package/dist/lib/manifest/render.js.map +1 -0
  89. package/dist/lib/manifest/resolve.d.ts +5 -0
  90. package/dist/lib/manifest/resolve.d.ts.map +1 -0
  91. package/dist/lib/manifest/resolve.js +185 -0
  92. package/dist/lib/manifest/resolve.js.map +1 -0
  93. package/dist/lib/manifest/schema.d.ts +103 -0
  94. package/dist/lib/manifest/schema.d.ts.map +1 -0
  95. package/dist/lib/manifest/schema.js +77 -0
  96. package/dist/lib/manifest/schema.js.map +1 -0
  97. package/dist/lib/manifest/spawn-plan.d.ts +87 -0
  98. package/dist/lib/manifest/spawn-plan.d.ts.map +1 -0
  99. package/dist/lib/manifest/spawn-plan.js +75 -0
  100. package/dist/lib/manifest/spawn-plan.js.map +1 -0
  101. package/dist/lib/meshes.d.ts +1 -7
  102. package/dist/lib/meshes.d.ts.map +1 -1
  103. package/dist/lib/meshes.js +5 -14
  104. package/dist/lib/meshes.js.map +1 -1
  105. package/dist/lib/onboard.js +1 -1
  106. package/dist/lib/onboard.js.map +1 -1
  107. package/dist/lib/paths.js +1 -1
  108. package/dist/lib/paths.js.map +1 -1
  109. package/dist/lib/status.d.ts.map +1 -1
  110. package/dist/lib/status.js +2 -1
  111. package/dist/lib/status.js.map +1 -1
  112. package/dist/web/graph.html +3 -0
  113. package/dist/web/graph.js +76 -21
  114. package/package.json +6 -2
@@ -0,0 +1,213 @@
1
+ /**
2
+ * The `spawn -f` ownership ledger (`cotal-ledger/v1`, one file per run at
3
+ * `.cotal/manifests/<runId>.json`): the durable, creation-only record of exactly what a `spawn -f`
4
+ * deploy added to a shared mesh — the registry keys it created and the agents it spawned (by the
5
+ * manager-reply **spawned** name + nkey id). `down -f` reads it to tear down *only* those resources.
6
+ *
7
+ * Two hard rules, because a tampered ledger could otherwise become arbitrary process/credential/file
8
+ * deletion (the round-8/9 security review):
9
+ * 1. **Store IDs, never arbitrary paths.** Cred paths are *derived* from the known auth root at
10
+ * teardown ({@link ownedCredPath}); the run dir is `.cotal/run/<runId>`. The ledger never names
11
+ * a path to delete.
12
+ * 2. **Treat the file as untrusted on read.** {@link loadLedger} strictly validates the whole
13
+ * ledger (schema, token-safe ids/names, concrete channels, duplicate detection) BEFORE the
14
+ * caller takes any destructive action — fail closed, globally.
15
+ */
16
+ import { createHash, randomBytes } from "node:crypto";
17
+ import { readFileSync, writeFileSync, readdirSync, renameSync, lstatSync } from "node:fs";
18
+ import { join, resolve, dirname } from "node:path";
19
+ import { z } from "zod";
20
+ import { assertValidChannel, assertValidName, ensureDirNoSymlink, isConcreteChannel, realDirNoSymlink } from "@cotal-ai/core";
21
+ import { authDir } from "@cotal-ai/workspace";
22
+ export const LEDGER_VERSION = "cotal-ledger/v1";
23
+ // Path-/route-safe token (run id, agent name) and an alphanumeric content hash — same shapes the
24
+ // launch spec uses, so the ledger can't smuggle a traversal/injection string into a derived path.
25
+ const TOKEN = /^[A-Za-z0-9_-]+$/;
26
+ const HASH = /^[A-Za-z0-9]+$/;
27
+ const LedgerAgentSchema = z.strictObject({
28
+ /** The manifest agent key (`agents:` name) — display + the stable key a re-apply matches a declared
29
+ * agent against (the spawned name may collision-number, so it can't be that key). */
30
+ requested: z.string().regex(TOKEN, "requested name must be a path-safe token ([A-Za-z0-9_-])"),
31
+ /** The manager-reply SPAWNED name (collision-numbered, e.g. `socrates-2`) — what creds are filed
32
+ * under; the cred path derives from THIS, never the manifest key. */
33
+ name: z.string().regex(TOKEN, "agent name must be a path-safe token ([A-Za-z0-9_-])"),
34
+ /** The spawned agent's nkey id — the immutable identity `down -f` matches before stopping. */
35
+ id: z.string().min(1),
36
+ /** The resolved hash at spawn (drift/stale detection on re-apply). */
37
+ hash: z.string().regex(HASH, "hash must be alphanumeric"),
38
+ });
39
+ const LedgerSchema = z.strictObject({
40
+ apiVersion: z.literal(LEDGER_VERSION),
41
+ kind: z.literal("MeshLedger"),
42
+ runId: z.string().regex(TOKEN, "runId must be a path-safe token ([A-Za-z0-9_-])"),
43
+ space: z.string().min(1),
44
+ server: z.string().min(1),
45
+ /** Content hash of the source manifest — an INDEX for `down -f cotal.yaml` only, never authority. */
46
+ manifestHash: z.string().regex(HASH, "manifestHash must be alphanumeric"),
47
+ /** The source manifest path — operator/display context only; never derived from or deleted. */
48
+ manifestPath: z.string().min(1),
49
+ teardownMode: z.literal("ledger-scoped"),
50
+ created: z.strictObject({
51
+ /** Registry keys this run CREATED (brand-new only — never adopted/pre-existing). */
52
+ channels: z.array(z.string()),
53
+ agents: z.array(LedgerAgentSchema),
54
+ }),
55
+ });
56
+ /** Assemble a ledger from a `spawn -f` apply result. `agents` carry the manager-reply SPAWNED name
57
+ * (cred-keying) + manifest `requested` key + nkey id + resolved hash; `channels` are the brand-new
58
+ * registry keys this run created. */
59
+ export function buildLedger(opts) {
60
+ return {
61
+ apiVersion: LEDGER_VERSION,
62
+ kind: "MeshLedger",
63
+ runId: opts.runId,
64
+ space: opts.space,
65
+ server: opts.server,
66
+ manifestHash: opts.manifestHash,
67
+ manifestPath: opts.manifestPath,
68
+ teardownMode: "ledger-scoped",
69
+ created: { channels: opts.channels, agents: opts.agents },
70
+ };
71
+ }
72
+ /** Stable index hash of the manifest source text — ties a `down -f cotal.yaml` back to its ledger
73
+ * (an edited file no longer matches, so `down -f` fails rather than guessing; `--run` is the
74
+ * escape hatch). NOT an integrity check and NOT ownership authority — only a lookup key. */
75
+ export function hashManifestSource(src) {
76
+ return createHash("sha256").update(src).digest("hex").slice(0, 16);
77
+ }
78
+ /** Write the ledger to `<root>/.cotal/manifests/<runId>.json`, `0600`, atomically and refusing a
79
+ * symlinked parent. A brand-new ledger is `wx` (exclusive create); an additive update (re-apply)
80
+ * goes temp-then-rename so `down -f` never reads a half-written file. */
81
+ export function writeLedger(root, ledger, opts = {}) {
82
+ const dir = ensureDirNoSymlink(root, ".cotal", "manifests");
83
+ const path = join(dir, `${ledger.runId}.json`);
84
+ const body = JSON.stringify(ledger, null, 2);
85
+ if (opts.update) {
86
+ // Atomic replace: write a fresh temp (exclusive — never follow a pre-planted symlink), then
87
+ // rename over the target. `rename` replaces the destination name itself, not a symlink target.
88
+ const tmp = join(dir, `.${ledger.runId}.${randomBytes(4).toString("hex")}.tmp`);
89
+ writeFileSync(tmp, body, { mode: 0o600, flag: "wx" });
90
+ renameSync(tmp, path);
91
+ }
92
+ else {
93
+ writeFileSync(path, body, { mode: 0o600, flag: "wx" });
94
+ }
95
+ return path;
96
+ }
97
+ /** Parse + strictly validate a ledger file as **untrusted input** — schema, unknown-key rejection,
98
+ * token-safe run id / agent names, concrete channel keys, and duplicate detection — so the WHOLE
99
+ * ledger is proven before the caller deletes anything (no partial "validated the class I'm about to
100
+ * delete" flow). Throws on any deviation. */
101
+ export function loadLedger(path) {
102
+ // No-follow: the ledger drives destructive teardown, so refuse a symlinked ledger file (a symlink
103
+ // could redirect `down -f` to attacker-chosen content). Callers also prove the parent dir chain.
104
+ let st;
105
+ try {
106
+ st = lstatSync(path);
107
+ }
108
+ catch (e) {
109
+ throw new Error(`ledger ${path}: ${e.message}`);
110
+ }
111
+ if (st.isSymbolicLink())
112
+ throw new Error(`refusing to read ledger "${path}": it is a symlink`);
113
+ if (!st.isFile())
114
+ throw new Error(`refusing to read ledger "${path}": not a regular file`);
115
+ let json;
116
+ try {
117
+ json = JSON.parse(readFileSync(path, "utf8"));
118
+ }
119
+ catch (e) {
120
+ throw new Error(`ledger ${path}: ${e.message}`);
121
+ }
122
+ const r = LedgerSchema.safeParse(json);
123
+ if (!r.success)
124
+ throw new Error(`ledger ${path}: ${r.error.issues.map((i) => `${i.path.join(".") || "<root>"}: ${i.message}`).join("; ")}`);
125
+ const led = r.data;
126
+ const agents = new Set();
127
+ for (const a of led.created.agents) {
128
+ assertValidName(a.name); // belt-and-suspenders past the regex — it names a derived cred path
129
+ if (agents.has(a.name))
130
+ throw new Error(`ledger ${path}: duplicate owned agent "${a.name}"`);
131
+ agents.add(a.name);
132
+ }
133
+ const channels = new Set();
134
+ for (const ch of led.created.channels) {
135
+ assertValidChannel(ch);
136
+ if (!isConcreteChannel(ch))
137
+ throw new Error(`ledger ${path}: owned channel "${ch}" is not concrete`);
138
+ if (channels.has(ch))
139
+ throw new Error(`ledger ${path}: duplicate owned channel "${ch}"`);
140
+ channels.add(ch);
141
+ }
142
+ return led;
143
+ }
144
+ /** Every valid ledger under `<root>/.cotal/manifests/`. Unparseable/foreign files are skipped (a
145
+ * targeted `down -f --run <id>` names one directly); used to resolve a `down -f cotal.yaml` to its
146
+ * run by `manifestHash`. */
147
+ export function listLedgers(root) {
148
+ // Prove `.cotal/manifests` is a real (non-symlink) directory chain before reading under it — a
149
+ // symlinked parent could redirect `down -f` to attacker-chosen ledgers.
150
+ const dir = realDirNoSymlink(root, ".cotal", "manifests");
151
+ if (!dir)
152
+ return [];
153
+ let names;
154
+ try {
155
+ names = readdirSync(dir);
156
+ }
157
+ catch {
158
+ return [];
159
+ }
160
+ const out = [];
161
+ for (const n of names) {
162
+ if (!n.endsWith(".json") || n.startsWith("."))
163
+ continue;
164
+ const p = join(dir, n);
165
+ try {
166
+ const ledger = loadLedger(p);
167
+ if (`${ledger.runId}.json` !== n)
168
+ continue; // filename must match the declared runId (no spoofed authority)
169
+ out.push({ path: p, ledger });
170
+ }
171
+ catch {
172
+ /* skip — a foreign/corrupt file isn't this run's ledger; `--run` targets a known one */
173
+ }
174
+ }
175
+ return out;
176
+ }
177
+ /** Resolve a `down -f cotal.yaml` to its ledger by the manifest's content hash. Fails (never
178
+ * guesses) when nothing matches (edited file) or more than one does — `--run <id>` is the escape. */
179
+ export function findLedgerByHash(root, manifestHash) {
180
+ const matches = listLedgers(root).filter((l) => l.ledger.manifestHash === manifestHash);
181
+ if (matches.length === 0)
182
+ throw new Error(`no ledger matches this manifest's current contents (was it edited since \`spawn -f\`?) — tear down by run id: \`cotal down -f <file> --run <id>\``);
183
+ if (matches.length > 1)
184
+ throw new Error(`${matches.length} runs share this manifest — name one: ${matches.map((m) => m.ledger.runId).join(", ")} (\`--run <id>\`)`);
185
+ return matches[0];
186
+ }
187
+ /** Resolve a ledger by explicit run id (the `--run <id>` path). Validates the id is a safe token
188
+ * before deriving the path. */
189
+ export function findLedgerByRun(root, runId) {
190
+ assertValidName(runId);
191
+ const dir = realDirNoSymlink(root, ".cotal", "manifests"); // refuse a symlinked manifests parent
192
+ if (!dir)
193
+ throw new Error(`no ledger for run ${runId} (.cotal/manifests is absent)`);
194
+ const path = join(dir, `${runId}.json`);
195
+ const ledger = loadLedger(path); // loadLedger refuses a symlinked ledger file
196
+ // Bind the filename to the contents: a tampered `<runId>.json` whose body declares a DIFFERENT
197
+ // runId must not redirect teardown to that other run's derived run dir / resources.
198
+ if (ledger.runId !== runId)
199
+ throw new Error(`ledger ${path}: declares runId "${ledger.runId}", not "${runId}" — refusing`);
200
+ return { path, ledger };
201
+ }
202
+ /** Derive an owned agent's cred path under the known auth root — `<root>/.cotal/auth/creds/<name>.creds`
203
+ * — from the SPAWNED name, rejecting any name that escapes the creds dir. The ledger never stores a
204
+ * cred path; teardown derives it here and the caller deletes it no-follow ({@link unlinkFileNoFollow}). */
205
+ export function ownedCredPath(root, spawnedName) {
206
+ assertValidName(spawnedName);
207
+ const dir = resolve(authDir(root), "creds");
208
+ const path = resolve(dir, `${spawnedName}.creds`);
209
+ if (dirname(path) !== dir)
210
+ throw new Error(`unsafe owned agent name "${spawnedName}" — cred path escapes ${dir}`);
211
+ return path;
212
+ }
213
+ //# sourceMappingURL=ledger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ledger.js","sourceRoot":"","sources":["../../../src/lib/manifest/ledger.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Live-mesh helpers for `cotal spawn -f` — the network half: a transient, invisible probe endpoint
3
+ * for reading the roster + membership feed, and the admin-tier control calls that drive the running
4
+ * manager's `launch` op. (Channel-registry reads use `readChannelRegistry`, which connects itself.)
5
+ */
6
+ import { CotalEndpoint, type ControlReply, type Presence } from "@cotal-ai/core";
7
+ export interface MeshConn {
8
+ space: string;
9
+ server: string;
10
+ creds?: string;
11
+ }
12
+ /** Connect a transient, presence-invisible endpoint (it watches the roster but doesn't register
13
+ * itself) used to read live state + drive control. The caller stops it. */
14
+ export declare function connectProbe(conn: MeshConn): Promise<CotalEndpoint>;
15
+ /** Let the presence KV replay settle (roster count steady across two polls, ≤1s), then snapshot the
16
+ * live peers — mirrors the dedup probe in `cotal spawn`. */
17
+ export declare function settleRoster(ep: CotalEndpoint): Promise<Presence[]>;
18
+ /** Poll the manager's control plane until it answers `ps` — it may have just been started detached,
19
+ * so it needs a moment to connect + come up. Returns false on timeout. */
20
+ export declare function waitManagerReady(ep: CotalEndpoint, timeoutMs?: number): Promise<boolean>;
21
+ /** Ask the running manager to launch one resolved agent from the run spec, on the ADMIN tier (the
22
+ * `launch` op is operator-only — a spawn-capable agent must not reach it). The manager derives
23
+ * `.cotal/run/<runId>.json` itself; we pass the runId, never a path. */
24
+ export declare function launchAgent(ep: CotalEndpoint, runId: string, name: string): Promise<ControlReply>;
25
+ //# sourceMappingURL=live.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"live.d.ts","sourceRoot":"","sources":["../../../src/lib/manifest/live.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,aAAa,EAAiB,KAAK,YAAY,EAAE,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAEhG,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAID;4EAC4E;AAC5E,wBAAsB,YAAY,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,aAAa,CAAC,CAczE;AAED;6DAC6D;AAC7D,wBAAsB,YAAY,CAAC,EAAE,EAAE,aAAa,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CASzE;AAED;2EAC2E;AAC3E,wBAAsB,gBAAgB,CAAC,EAAE,EAAE,aAAa,EAAE,SAAS,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,CAY9F;AAED;;yEAEyE;AACzE,wBAAsB,WAAW,CAAC,EAAE,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAEvG"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Live-mesh helpers for `cotal spawn -f` — the network half: a transient, invisible probe endpoint
3
+ * for reading the roster + membership feed, and the admin-tier control calls that drive the running
4
+ * manager's `launch` op. (Channel-registry reads use `readChannelRegistry`, which connects itself.)
5
+ */
6
+ import { CotalEndpoint, CONTROL_ADMIN } from "@cotal-ai/core";
7
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
8
+ /** Connect a transient, presence-invisible endpoint (it watches the roster but doesn't register
9
+ * itself) used to read live state + drive control. The caller stops it. */
10
+ export async function connectProbe(conn) {
11
+ const ep = new CotalEndpoint({
12
+ space: conn.space,
13
+ servers: conn.server,
14
+ creds: conn.creds,
15
+ channels: [],
16
+ consume: false,
17
+ registerPresence: false, // an invisible probe — don't add ourselves to the roster we read
18
+ watchPresence: true,
19
+ card: { name: "spawn-f", kind: "endpoint" },
20
+ });
21
+ ep.on("error", () => { }); // a presence/control hiccup must never crash the deploy
22
+ await ep.start();
23
+ return ep;
24
+ }
25
+ /** Let the presence KV replay settle (roster count steady across two polls, ≤1s), then snapshot the
26
+ * live peers — mirrors the dedup probe in `cotal spawn`. */
27
+ export async function settleRoster(ep) {
28
+ let prev = -1;
29
+ for (let i = 0; i < 10; i++) {
30
+ await sleep(100);
31
+ const n = ep.getRoster().length;
32
+ if (n === prev)
33
+ break;
34
+ prev = n;
35
+ }
36
+ return ep.getRoster();
37
+ }
38
+ /** Poll the manager's control plane until it answers `ps` — it may have just been started detached,
39
+ * so it needs a moment to connect + come up. Returns false on timeout. */
40
+ export async function waitManagerReady(ep, timeoutMs = 20_000) {
41
+ const deadline = Date.now() + timeoutMs;
42
+ while (Date.now() < deadline) {
43
+ try {
44
+ const r = await ep.requestControl(CONTROL_ADMIN, { op: "ps" });
45
+ if (r.ok)
46
+ return true;
47
+ }
48
+ catch {
49
+ /* manager not answering yet */
50
+ }
51
+ await sleep(500);
52
+ }
53
+ return false;
54
+ }
55
+ /** Ask the running manager to launch one resolved agent from the run spec, on the ADMIN tier (the
56
+ * `launch` op is operator-only — a spawn-capable agent must not reach it). The manager derives
57
+ * `.cotal/run/<runId>.json` itself; we pass the runId, never a path. */
58
+ export async function launchAgent(ep, runId, name) {
59
+ return ep.requestControl(CONTROL_ADMIN, { op: "launch", args: { runId, name } });
60
+ }
61
+ //# sourceMappingURL=live.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"live.js","sourceRoot":"","sources":["../../../src/lib/manifest/live.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,aAAa,EAAE,aAAa,EAAoC,MAAM,gBAAgB,CAAC;AAQhG,MAAM,KAAK,GAAG,CAAC,EAAU,EAAiB,EAAE,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;AAEnF;4EAC4E;AAC5E,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAc;IAC/C,MAAM,EAAE,GAAG,IAAI,aAAa,CAAC;QAC3B,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,OAAO,EAAE,IAAI,CAAC,MAAM;QACpB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,QAAQ,EAAE,EAAE;QACZ,OAAO,EAAE,KAAK;QACd,gBAAgB,EAAE,KAAK,EAAE,iEAAiE;QAC1F,aAAa,EAAE,IAAI;QACnB,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,EAAE;KAC5C,CAAC,CAAC;IACH,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC,CAAC,wDAAwD;IAClF,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;IACjB,OAAO,EAAE,CAAC;AACZ,CAAC;AAED;6DAC6D;AAC7D,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,EAAiB;IAClD,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC;IACd,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC;QAC5B,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;QACjB,MAAM,CAAC,GAAG,EAAE,CAAC,SAAS,EAAE,CAAC,MAAM,CAAC;QAChC,IAAI,CAAC,KAAK,IAAI;YAAE,MAAM;QACtB,IAAI,GAAG,CAAC,CAAC;IACX,CAAC;IACD,OAAO,EAAE,CAAC,SAAS,EAAE,CAAC;AACxB,CAAC;AAED;2EAC2E;AAC3E,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,EAAiB,EAAE,SAAS,GAAG,MAAM;IAC1E,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;IACxC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;QAC7B,IAAI,CAAC;YACH,MAAM,CAAC,GAAG,MAAM,EAAE,CAAC,cAAc,CAAC,aAAa,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;YAC/D,IAAI,CAAC,CAAC,EAAE;gBAAE,OAAO,IAAI,CAAC;QACxB,CAAC;QAAC,MAAM,CAAC;YACP,+BAA+B;QACjC,CAAC;QACD,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;IACnB,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;yEAEyE;AACzE,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,EAAiB,EAAE,KAAa,EAAE,IAAY;IAC9E,OAAO,EAAE,CAAC,cAAc,CAAC,aAAa,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;AACnF,CAAC"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * The resolved manifest model — the typed output of the pure pipeline (parse → schema →
3
+ * normalize/invert → semantic), with channel-centric membership inverted into per-agent ACLs.
4
+ * Preflight (persona reads + the `include` merge) and the plan/apply stages consume this.
5
+ */
6
+ import type { ChannelDefaults, DeliveryClass } from "@cotal-ai/core";
7
+ export type PersonaPermissions = "reject" | "include";
8
+ /** A channel after normalization: `allowSubscribe` defaulted to `subscribe`, dedup'd. The
9
+ * registry-card fields (description/instructions/replay…) seed the channel registry verbatim. */
10
+ export interface ResolvedChannel {
11
+ name: string;
12
+ description?: string;
13
+ instructions?: string;
14
+ /** Active read set (boot subscription). */
15
+ subscribe: string[];
16
+ /** Read ACL — may read/join. Defaults to {@link subscribe}; always a superset of it. */
17
+ allowSubscribe: string[];
18
+ /** Post ACL (default-deny: empty ⇒ nobody posts). */
19
+ allowPublish: string[];
20
+ replay?: boolean;
21
+ replayWindow?: string;
22
+ deliveryClass?: DeliveryClass;
23
+ }
24
+ /** Per-agent ACLs inverted from the channel membership lists — the manifest-declared access only
25
+ * (a persona's own grants, under `include`, are merged on top in preflight). 1:1 with AgentDef. */
26
+ export interface AgentPolicy {
27
+ subscribe: string[];
28
+ allowSubscribe: string[];
29
+ allowPublish: string[];
30
+ }
31
+ /** A resolved agent: its identity/behavior source (a persona file or fully inline) plus the
32
+ * manifest-declared policy. Behavior fields here are the manifest's overrides; the persona
33
+ * default is filled in during preflight (which reads the file). */
34
+ export interface ResolvedAgent {
35
+ /** The `agents:` key — also the requested spawn name in v1. */
36
+ name: string;
37
+ /** Connector type to spawn with (agent-entry `agent:` ?? top-level `agent:`). */
38
+ agentType: string;
39
+ /** Resolved persona path (absolute), or undefined for an inline agent. */
40
+ persona?: string;
41
+ /** Manifest override of the persona's model (or the inline model). */
42
+ model?: string;
43
+ role?: string;
44
+ /** Manifest card blurb (override / inline). */
45
+ description?: string;
46
+ /** Manifest persona body (REPLACES the file body when set; the sole body for inline). */
47
+ instructions?: string;
48
+ /** Manifest capabilities — win over the persona's when present. */
49
+ capabilities?: string[];
50
+ /** Effective policy for THIS agent: per-agent override ?? top-level ?? "reject". */
51
+ personaPermissions: PersonaPermissions;
52
+ /** ACLs inverted from the channels this agent appears in (pre persona-merge). */
53
+ policy: AgentPolicy;
54
+ }
55
+ /** The fully resolved, validated manifest — channel-centric on disk, per-agent here. */
56
+ export interface ResolvedManifest {
57
+ space: string;
58
+ broker?: {
59
+ servers?: string;
60
+ host?: string;
61
+ auth?: boolean;
62
+ };
63
+ runtime?: "pty" | "tmux" | "cmux";
64
+ personaPermissions: PersonaPermissions;
65
+ defaults?: ChannelDefaults;
66
+ agents: ResolvedAgent[];
67
+ channels: ResolvedChannel[];
68
+ /** Absolute path the manifest was loaded from — anchors relative persona refs + error output. */
69
+ sourcePath: string;
70
+ }
71
+ //# sourceMappingURL=model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../../src/lib/manifest/model.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAErE,MAAM,MAAM,kBAAkB,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEtD;kGACkG;AAClG,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,2CAA2C;IAC3C,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,wFAAwF;IACxF,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,qDAAqD;IACrD,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED;oGACoG;AACpG,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED;;oEAEoE;AACpE,MAAM,WAAW,aAAa;IAC5B,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAC;IACb,iFAAiF;IACjF,SAAS,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sEAAsE;IACtE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,+CAA+C;IAC/C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yFAAyF;IACzF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mEAAmE;IACnE,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,oFAAoF;IACpF,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,iFAAiF;IACjF,MAAM,EAAE,WAAW,CAAC;CACrB;AAED,wFAAwF;AACxF,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAC7D,OAAO,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;IAClC,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,iGAAiG;IACjG,UAAU,EAAE,MAAM,CAAC;CACpB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.js","sourceRoot":"","sources":["../../../src/lib/manifest/model.ts"],"names":[],"mappings":""}
@@ -0,0 +1,12 @@
1
+ import type { ResolvedManifest } from "./model.js";
2
+ import { type AgentWarning, type PreparedAgent } from "./prepare.js";
3
+ export interface PreparedManifest {
4
+ manifest: ResolvedManifest;
5
+ agents: PreparedAgent[];
6
+ /** Non-fatal diagnostics to render in dry-run / topology view. */
7
+ warnings: AgentWarning[];
8
+ }
9
+ /** Read personas + merge. Behavior defaults always come from the file (under both policies); only
10
+ * the persona's *permissions* are gated by `personaPermissions`. */
11
+ export declare function preparePersonas(manifest: ResolvedManifest): PreparedManifest;
12
+ //# sourceMappingURL=preflight.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preflight.d.ts","sourceRoot":"","sources":["../../../src/lib/manifest/preflight.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAgB,KAAK,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAGnF,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,kEAAkE;IAClE,QAAQ,EAAE,YAAY,EAAE,CAAC;CAC1B;AAED;qEACqE;AACrE,wBAAgB,eAAe,CAAC,QAAQ,EAAE,gBAAgB,GAAG,gBAAgB,CA4B5E"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Preflight — the I/O half of resolution: read each agent's persona file and merge it with the
3
+ * manifest (via the pure {@link prepareAgent}). Returns the launch-ready {@link PreparedManifest}
4
+ * (agents + diagnostics) or throws {@link ManifestError} with every persona problem located.
5
+ *
6
+ * Live checks that need the network (broker reachability for `up -f` vs `spawn -f`) or the connector
7
+ * registry stay in the command wiring; this is the fail-fast, file-level half.
8
+ */
9
+ import { loadAgentFile } from "@cotal-ai/core";
10
+ import { prepareAgent } from "./prepare.js";
11
+ import { ManifestError } from "./errors.js";
12
+ /** Read personas + merge. Behavior defaults always come from the file (under both policies); only
13
+ * the persona's *permissions* are gated by `personaPermissions`. */
14
+ export function preparePersonas(manifest) {
15
+ const declared = new Set(manifest.channels.map((c) => c.name));
16
+ const issues = [];
17
+ const warnings = [];
18
+ const agents = [];
19
+ for (const a of manifest.agents) {
20
+ let persona;
21
+ if (a.persona) {
22
+ try {
23
+ persona = loadAgentFile(a.persona);
24
+ }
25
+ catch (e) {
26
+ const code = e.code;
27
+ issues.push({
28
+ message: code === "ENOENT" ? `persona file not found: ${a.persona}` : e.message,
29
+ path: ["agents", a.name],
30
+ });
31
+ continue;
32
+ }
33
+ }
34
+ const r = prepareAgent(a, persona, declared);
35
+ issues.push(...r.issues);
36
+ warnings.push(...r.warnings);
37
+ agents.push(r.prepared);
38
+ }
39
+ if (issues.length)
40
+ throw new ManifestError(manifest.sourcePath, issues);
41
+ return { manifest, agents, warnings };
42
+ }
43
+ //# sourceMappingURL=preflight.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preflight.js","sourceRoot":"","sources":["../../../src/lib/manifest/preflight.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,aAAa,EAAiB,MAAM,gBAAgB,CAAC;AAE9D,OAAO,EAAE,YAAY,EAAyC,MAAM,cAAc,CAAC;AACnF,OAAO,EAAE,aAAa,EAAsB,MAAM,aAAa,CAAC;AAShE;qEACqE;AACrE,MAAM,UAAU,eAAe,CAAC,QAA0B;IACxD,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAC/D,MAAM,MAAM,GAAoB,EAAE,CAAC;IACnC,MAAM,QAAQ,GAAmB,EAAE,CAAC;IACpC,MAAM,MAAM,GAAoB,EAAE,CAAC;IAEnC,KAAK,MAAM,CAAC,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;QAChC,IAAI,OAA6B,CAAC;QAClC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;YACd,IAAI,CAAC;gBACH,OAAO,GAAG,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;YACrC,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,MAAM,IAAI,GAAI,CAA2B,CAAC,IAAI,CAAC;gBAC/C,MAAM,CAAC,IAAI,CAAC;oBACV,OAAO,EAAE,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,2BAA2B,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAE,CAAW,CAAC,OAAO;oBAC1F,IAAI,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC;iBACzB,CAAC,CAAC;gBACH,SAAS;YACX,CAAC;QACH,CAAC;QACD,MAAM,CAAC,GAAG,YAAY,CAAC,CAAC,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;QAC7C,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC;QACzB,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC;QAC7B,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;IAC1B,CAAC;IAED,IAAI,MAAM,CAAC,MAAM;QAAE,MAAM,IAAI,aAAa,CAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IACxE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;AACxC,CAAC"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Prepare stage — resolve each agent's effective launch identity + ACLs by merging its persona
3
+ * (read from disk) with the manifest. Pure: it takes already-loaded {@link AgentDef}s so it stays
4
+ * unit-testable; the fs/live I/O lives in {@link ./preflight.js}.
5
+ *
6
+ * Behavior fields (model/role/description/body) are persona-default + manifest-override. Access is
7
+ * governed by {@link PersonaPermissions}: under `reject` the manifest is the sole source; under
8
+ * `include` a persona's own grants are inherited **only for channels the manifest does not declare**
9
+ * (one authority per channel), concrete-only (wildcards rejected), and surfaced loudly.
10
+ */
11
+ import { type AgentDef } from "@cotal-ai/core";
12
+ import type { AgentPolicy, ResolvedAgent } from "./model.js";
13
+ import type { ManifestIssue } from "./errors.js";
14
+ /** Persona grants that fall outside the manifest's channels (unmanaged credential scopes) + the
15
+ * capabilities inherited from the persona — the data the loud dry-run section renders. */
16
+ export interface InheritedScopes {
17
+ subscribe: string[];
18
+ allowSubscribe: string[];
19
+ allowPublish: string[];
20
+ capabilities: string[];
21
+ }
22
+ /** A non-fatal diagnostic to surface in dry-run / topology view. `loud` = call it out prominently
23
+ * (e.g. an agent with capabilities but no channel access). */
24
+ export interface AgentWarning {
25
+ agent: string;
26
+ message: string;
27
+ loud?: boolean;
28
+ }
29
+ /** The fully resolved launch form for one agent — what the spawn path needs. */
30
+ export interface PreparedAgent {
31
+ name: string;
32
+ agentType: string;
33
+ /** Persona file path (or undefined for an inline agent). */
34
+ persona?: string;
35
+ /** Effective values (manifest override ?? persona default). */
36
+ model?: string;
37
+ role?: string;
38
+ description?: string;
39
+ /** Effective persona body (manifest `instructions` REPLACES the file body; sole body for inline). */
40
+ body?: string;
41
+ /** Effective merged capabilities. */
42
+ capabilities: string[];
43
+ capabilitySource: "manifest" | "persona" | "none";
44
+ /** Effective merged per-channel ACLs (manifest + persona-undeclared under `include`). */
45
+ policy: AgentPolicy;
46
+ /** Persona grants outside manifest channels + inherited caps (empty under `reject`/inline). */
47
+ inherited: InheritedScopes;
48
+ }
49
+ export interface PreparedResult {
50
+ prepared: PreparedAgent;
51
+ issues: ManifestIssue[];
52
+ warnings: AgentWarning[];
53
+ }
54
+ /** Merge one resolved agent with its (optional) loaded persona, given the set of channel names the
55
+ * manifest declares. Returns the launch form plus any errors/warnings. */
56
+ export declare function prepareAgent(agent: ResolvedAgent, persona: AgentDef | undefined, declared: Set<string>): PreparedResult;
57
+ //# sourceMappingURL=prepare.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prepare.d.ts","sourceRoot":"","sources":["../../../src/lib/manifest/prepare.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAqB,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAClE,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD;2FAC2F;AAC3F,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED;+DAC+D;AAC/D,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,gFAAgF;AAChF,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+DAA+D;IAC/D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qGAAqG;IACrG,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qCAAqC;IACrC,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,gBAAgB,EAAE,UAAU,GAAG,SAAS,GAAG,MAAM,CAAC;IAClD,yFAAyF;IACzF,MAAM,EAAE,WAAW,CAAC;IACpB,+FAA+F;IAC/F,SAAS,EAAE,eAAe,CAAC;CAC5B;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,aAAa,CAAC;IACxB,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,QAAQ,EAAE,YAAY,EAAE,CAAC;CAC1B;AAED;2EAC2E;AAC3E,wBAAgB,YAAY,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,QAAQ,GAAG,SAAS,EAAE,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,cAAc,CAqFvH"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Prepare stage — resolve each agent's effective launch identity + ACLs by merging its persona
3
+ * (read from disk) with the manifest. Pure: it takes already-loaded {@link AgentDef}s so it stays
4
+ * unit-testable; the fs/live I/O lives in {@link ./preflight.js}.
5
+ *
6
+ * Behavior fields (model/role/description/body) are persona-default + manifest-override. Access is
7
+ * governed by {@link PersonaPermissions}: under `reject` the manifest is the sole source; under
8
+ * `include` a persona's own grants are inherited **only for channels the manifest does not declare**
9
+ * (one authority per channel), concrete-only (wildcards rejected), and surfaced loudly.
10
+ */
11
+ import { isConcreteChannel } from "@cotal-ai/core";
12
+ /** Merge one resolved agent with its (optional) loaded persona, given the set of channel names the
13
+ * manifest declares. Returns the launch form plus any errors/warnings. */
14
+ export function prepareAgent(agent, persona, declared) {
15
+ const issues = [];
16
+ const warnings = [];
17
+ const at = ["agents", agent.name];
18
+ // Behavior: persona default, manifest override wins. Inline `instructions` REPLACE the body.
19
+ const model = agent.model ?? persona?.model;
20
+ const role = agent.role ?? persona?.role;
21
+ const description = agent.description ?? persona?.description;
22
+ const body = agent.instructions ?? persona?.persona;
23
+ // Capabilities: manifest wins; else inherited from the persona only under `include`.
24
+ let capabilities = [];
25
+ let capabilitySource = "none";
26
+ if (agent.capabilities?.length) {
27
+ capabilities = [...agent.capabilities];
28
+ capabilitySource = "manifest";
29
+ }
30
+ else if (agent.personaPermissions === "include" && persona?.capabilities?.length) {
31
+ capabilities = [...persona.capabilities];
32
+ capabilitySource = "persona";
33
+ }
34
+ // Access. Start from the manifest-inverted policy (all its channels are declared by construction).
35
+ const policy = {
36
+ subscribe: [...agent.policy.subscribe],
37
+ allowSubscribe: [...agent.policy.allowSubscribe],
38
+ allowPublish: [...agent.policy.allowPublish],
39
+ };
40
+ const inherited = { subscribe: [], allowSubscribe: [], allowPublish: [], capabilities: [] };
41
+ if (agent.personaPermissions === "include" && persona) {
42
+ const personaAllowSub = persona.allowSubscribe ?? persona.subscribe ?? [];
43
+ // Reject persona WILDCARD grants in v1 (they'd re-introduce wildcards via the persona file and
44
+ // break the per-channel partition).
45
+ for (const [field, list] of [["subscribe", persona.subscribe], ["allowSubscribe", personaAllowSub], ["allowPublish", persona.allowPublish]])
46
+ for (const ch of list ?? [])
47
+ if (!isConcreteChannel(ch))
48
+ issues.push({ message: `persona ${field} "${ch}" is a wildcard — not supported in v1 (declare concrete channels)`, path: at });
49
+ // Persona grants apply ONLY to channels the manifest does not declare (manifest owns its own).
50
+ const undeclared = (list) => (list ?? []).filter((c) => isConcreteChannel(c) && !declared.has(c));
51
+ inherited.subscribe = undeclared(persona.subscribe);
52
+ inherited.allowSubscribe = undeclared(personaAllowSub);
53
+ inherited.allowPublish = undeclared(persona.allowPublish);
54
+ inherited.capabilities = capabilitySource === "persona" ? capabilities : [];
55
+ policy.subscribe = dedupe([...policy.subscribe, ...inherited.subscribe]);
56
+ policy.allowSubscribe = dedupe([...policy.allowSubscribe, ...inherited.allowSubscribe]);
57
+ policy.allowPublish = dedupe([...policy.allowPublish, ...inherited.allowPublish]);
58
+ }
59
+ // Defensive: the merged read set must stay within the merged read ACL.
60
+ const missing = policy.subscribe.filter((c) => !policy.allowSubscribe.includes(c));
61
+ if (missing.length)
62
+ issues.push({ message: `merged subscribe [${missing.join(", ")}] not within allowSubscribe`, path: at });
63
+ // Warn on an agent the manifest declares but never grants channel access — likely a typo, but
64
+ // valid (a DM/control-only peer). Loud when it nonetheless carries capabilities.
65
+ const noAccess = !policy.subscribe.length && !policy.allowSubscribe.length && !policy.allowPublish.length;
66
+ if (noAccess)
67
+ warnings.push({
68
+ agent: agent.name,
69
+ loud: capabilities.length > 0,
70
+ message: capabilities.length
71
+ ? `declared with capabilities [${capabilities.join(", ")}] but NO channel access (DM/control-only — a powerful non-channel grant)`
72
+ : `declared but has no channel access from the manifest (DM/control-only unless a persona under \`include\` grants scopes)`,
73
+ });
74
+ return {
75
+ prepared: {
76
+ name: agent.name,
77
+ agentType: agent.agentType,
78
+ persona: agent.persona,
79
+ model,
80
+ role,
81
+ description,
82
+ body,
83
+ capabilities,
84
+ capabilitySource,
85
+ policy,
86
+ inherited,
87
+ },
88
+ issues,
89
+ warnings,
90
+ };
91
+ }
92
+ function dedupe(xs) {
93
+ return [...new Set(xs)];
94
+ }
95
+ //# sourceMappingURL=prepare.js.map