@bluewombat/runtime 0.5.0 → 0.7.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 (63) hide show
  1. package/README.md +142 -57
  2. package/SPECS.md +35 -18
  3. package/dist/cli.js +18 -0
  4. package/dist/cli.js.map +1 -1
  5. package/dist/config/agent-shorthand.d.ts +45 -0
  6. package/dist/config/agent-shorthand.d.ts.map +1 -0
  7. package/dist/config/agent-shorthand.js +256 -0
  8. package/dist/config/agent-shorthand.js.map +1 -0
  9. package/dist/config/load-config.d.ts.map +1 -1
  10. package/dist/config/load-config.js +121 -24
  11. package/dist/config/load-config.js.map +1 -1
  12. package/dist/config/parse-args.d.ts.map +1 -1
  13. package/dist/config/parse-args.js +84 -24
  14. package/dist/config/parse-args.js.map +1 -1
  15. package/dist/config/types.d.ts +32 -12
  16. package/dist/config/types.d.ts.map +1 -1
  17. package/dist/index.d.ts +2 -0
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +1 -0
  20. package/dist/index.js.map +1 -1
  21. package/dist/loop/deliveries.d.ts +16 -0
  22. package/dist/loop/deliveries.d.ts.map +1 -1
  23. package/dist/loop/deliveries.js +25 -0
  24. package/dist/loop/deliveries.js.map +1 -1
  25. package/dist/loop/drive.d.ts.map +1 -1
  26. package/dist/loop/drive.js +15 -1
  27. package/dist/loop/drive.js.map +1 -1
  28. package/dist/loop/open-host.d.ts +1 -1
  29. package/dist/loop/open-host.d.ts.map +1 -1
  30. package/dist/loop/open-host.js +13 -1
  31. package/dist/loop/open-host.js.map +1 -1
  32. package/dist/loop/report.d.ts.map +1 -1
  33. package/dist/loop/report.js +20 -0
  34. package/dist/loop/report.js.map +1 -1
  35. package/dist/loop/tick.d.ts +3 -1
  36. package/dist/loop/tick.d.ts.map +1 -1
  37. package/dist/loop/tick.js.map +1 -1
  38. package/dist/loop/transformers.d.ts +9 -2
  39. package/dist/loop/transformers.d.ts.map +1 -1
  40. package/dist/loop/transformers.js +31 -29
  41. package/dist/loop/transformers.js.map +1 -1
  42. package/dist/loop/warm.d.ts +21 -0
  43. package/dist/loop/warm.d.ts.map +1 -0
  44. package/dist/loop/warm.js +59 -0
  45. package/dist/loop/warm.js.map +1 -0
  46. package/dist/operator/agent-plugins.d.ts +14 -0
  47. package/dist/operator/agent-plugins.d.ts.map +1 -0
  48. package/dist/operator/agent-plugins.js +149 -0
  49. package/dist/operator/agent-plugins.js.map +1 -0
  50. package/dist/operator/doctor.d.ts.map +1 -1
  51. package/dist/operator/doctor.js +27 -20
  52. package/dist/operator/doctor.js.map +1 -1
  53. package/dist/operator/film.js +5 -0
  54. package/dist/operator/film.js.map +1 -1
  55. package/dist/operator/init.js +14 -5
  56. package/dist/operator/init.js.map +1 -1
  57. package/dist/operator/live.js +9 -7
  58. package/dist/operator/live.js.map +1 -1
  59. package/dist/operator/release.d.ts +18 -0
  60. package/dist/operator/release.d.ts.map +1 -0
  61. package/dist/operator/release.js +65 -0
  62. package/dist/operator/release.js.map +1 -0
  63. package/package.json +15 -15
@@ -0,0 +1,256 @@
1
+ import { resolve } from "node:path";
2
+ /**
3
+ * The short way to write an agent pass: which agent, which prompt, which
4
+ * plugins — and Host writes the argv a pass has always been.
5
+ *
6
+ * agents:
7
+ * cursor: { adapter: cursor, home: ./.mason/agent-home, apiKeyEnv: CURSOR_API_KEY,
8
+ * transcripts: ./.mason/transcripts, pluginsOut: ./.mason/plugins }
9
+ * builder:
10
+ * producer: { agent: cursor, prompt: ./prompts/builder.md,
11
+ * plugins: [./ai-plugins/core/builder], profile: builder, timeoutMs: … }
12
+ *
13
+ * expands to
14
+ *
15
+ * node <slots>/builders/producer.mjs --prompt-file <prompt> --
16
+ * node <slots>/agents/cursor.mjs --transcript-dir … --agent-home … --api-key-env …
17
+ * --plugin-dir <core> --profile builder --plugins-out …
18
+ *
19
+ * The expansion is the whole feature: nothing downstream knows a pass was
20
+ * written short, so `cmd` stays the one shape the rest of Host reads, and a
21
+ * pass that needs something the short form cannot say still writes `cmd`.
22
+ */
23
+ /** Where the shipped slots live, next to the config: what `npm install` in a Mason home makes. */
24
+ const SLOTS_PACKAGE = "node_modules/@bluewombat/slots";
25
+ /** The shipped role slot behind each pass that drives an agent. */
26
+ export const ROLE_SCRIPTS = {
27
+ planner: "planners/producer.mjs",
28
+ "builder.producer": "builders/producer.mjs",
29
+ "builder.repair": "builders/repair.mjs",
30
+ "assembly.fix": "assembly/fix.mjs",
31
+ "assembly.validate": "assembly/validate.mjs",
32
+ "authority.describe": "messages/git-producer.mjs",
33
+ };
34
+ const ADAPTERS = new Set(["cursor", "claude"]);
35
+ const AGENT_KEYS = new Set([
36
+ "adapter",
37
+ "bin",
38
+ "model",
39
+ "home",
40
+ "apiKeyEnv",
41
+ "transcripts",
42
+ "pluginsOut",
43
+ "args",
44
+ ]);
45
+ /** The keys a pass written short may carry, besides its own (`timeoutMs`, `gates`). */
46
+ export const SHORT_PASS_KEYS = new Set(["agent", "prompt", "rules", "plugins", "profile", "args"]);
47
+ export function readAgents(value, configDir) {
48
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
49
+ return { ok: false, reason: 'Config "agents" must be a YAML mapping of agent names.' };
50
+ }
51
+ const agents = new Map();
52
+ for (const [name, raw] of Object.entries(value)) {
53
+ const key = `agents.${name}`;
54
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
55
+ return { ok: false, reason: `Config "${key}" must be a YAML mapping.` };
56
+ }
57
+ const object = raw;
58
+ for (const field of Object.keys(object)) {
59
+ if (!AGENT_KEYS.has(field)) {
60
+ return { ok: false, reason: `Config "${key}" has unknown key "${field}".` };
61
+ }
62
+ }
63
+ const fields = {};
64
+ for (const field of [
65
+ "adapter",
66
+ "bin",
67
+ "model",
68
+ "home",
69
+ "apiKeyEnv",
70
+ "transcripts",
71
+ "pluginsOut",
72
+ ]) {
73
+ const text = object[field];
74
+ if (text !== undefined && (typeof text !== "string" || text.trim().length === 0)) {
75
+ return { ok: false, reason: `Config "${key}.${field}" must be a non-empty string.` };
76
+ }
77
+ fields[field] = text;
78
+ }
79
+ const adapter = fields.adapter;
80
+ if (adapter === undefined) {
81
+ return {
82
+ ok: false,
83
+ reason: `Config "${key}" needs an "adapter": cursor, claude, or the path of an agent wrapper.`,
84
+ };
85
+ }
86
+ if ((fields.home === undefined) !== (fields.apiKeyEnv === undefined)) {
87
+ return {
88
+ ok: false,
89
+ reason: `Config "${key}" isolates an agent with "home" and "apiKeyEnv" together, or neither.`,
90
+ };
91
+ }
92
+ const args = stringList(object.args, `${key}.args`);
93
+ if (args.ok === false) {
94
+ return args;
95
+ }
96
+ const at = (path) => path === undefined ? undefined : resolve(configDir, path);
97
+ const decl = {
98
+ adapter: ADAPTERS.has(adapter)
99
+ ? resolve(configDir, SLOTS_PACKAGE, "agents", `${adapter}.mjs`)
100
+ : resolve(configDir, adapter),
101
+ args: args.value,
102
+ };
103
+ // A binary named bare is looked up on PATH; one written as a path is next to the config.
104
+ const bin = fields.bin;
105
+ if (bin !== undefined) {
106
+ decl.bin = bin.includes("/") ? resolve(configDir, bin) : bin;
107
+ }
108
+ if (fields.model !== undefined) {
109
+ decl.model = fields.model;
110
+ }
111
+ const home = at(fields.home);
112
+ if (home !== undefined) {
113
+ decl.home = home;
114
+ }
115
+ if (fields.apiKeyEnv !== undefined) {
116
+ decl.apiKeyEnv = fields.apiKeyEnv;
117
+ }
118
+ const transcripts = at(fields.transcripts);
119
+ if (transcripts !== undefined) {
120
+ decl.transcripts = transcripts;
121
+ }
122
+ const pluginsOut = at(fields.pluginsOut);
123
+ if (pluginsOut !== undefined) {
124
+ decl.pluginsOut = pluginsOut;
125
+ }
126
+ agents.set(name, decl);
127
+ }
128
+ return { ok: true, value: agents };
129
+ }
130
+ /**
131
+ * The argv a pass written short stands for. `object` is the pass's own
132
+ * mapping; its keys were checked by the caller.
133
+ */
134
+ export function expandAgentPass(object, role, agents, configDir, key) {
135
+ if (object.cmd !== undefined) {
136
+ return { ok: false, reason: `Config "${key}" names "cmd" and "agent": write one of the two.` };
137
+ }
138
+ const name = object.agent;
139
+ if (typeof name !== "string" || name.trim().length === 0) {
140
+ return { ok: false, reason: `Config "${key}.agent" must name an agent of "agents".` };
141
+ }
142
+ const agent = agents.get(name);
143
+ if (agent === undefined) {
144
+ return {
145
+ ok: false,
146
+ reason: `Config "${key}.agent" is "${name}", and "agents" declares no such agent.`,
147
+ };
148
+ }
149
+ const prompt = pathField(object, "prompt", key, configDir);
150
+ if (prompt.ok === false) {
151
+ return prompt;
152
+ }
153
+ const rules = pathField(object, "rules", key, configDir);
154
+ if (rules.ok === false) {
155
+ return rules;
156
+ }
157
+ const read = pathField(object, "read", key, configDir);
158
+ if (read.ok === false) {
159
+ return read;
160
+ }
161
+ const plugins = stringList(object.plugins, `${key}.plugins`);
162
+ if (plugins.ok === false) {
163
+ return plugins;
164
+ }
165
+ const profile = object.profile;
166
+ if (profile !== undefined && (typeof profile !== "string" || profile.trim().length === 0)) {
167
+ return { ok: false, reason: `Config "${key}.profile" must be a deck profile name.` };
168
+ }
169
+ if (profile !== undefined && agent.pluginsOut === undefined) {
170
+ return {
171
+ ok: false,
172
+ reason: `Config "${key}" names a deck profile, so "agents.${name}.pluginsOut" must say where it is built.`,
173
+ };
174
+ }
175
+ const args = stringList(object.args, `${key}.args`);
176
+ if (args.ok === false) {
177
+ return args;
178
+ }
179
+ const argv = ["node", resolve(configDir, SLOTS_PACKAGE, ROLE_SCRIPTS[role])];
180
+ if (prompt.value !== undefined) {
181
+ argv.push("--prompt-file", prompt.value);
182
+ }
183
+ if (rules.value !== undefined) {
184
+ argv.push("--rules-file", rules.value);
185
+ }
186
+ if (read.value !== undefined) {
187
+ argv.push("--read", read.value);
188
+ }
189
+ argv.push("--", "node", agent.adapter);
190
+ if (agent.bin !== undefined) {
191
+ argv.push("--bin", agent.bin);
192
+ }
193
+ if (agent.model !== undefined) {
194
+ argv.push("--model", agent.model);
195
+ }
196
+ if (agent.transcripts !== undefined) {
197
+ argv.push("--transcript-dir", agent.transcripts);
198
+ }
199
+ if (agent.home !== undefined && agent.apiKeyEnv !== undefined) {
200
+ argv.push("--agent-home", agent.home, "--api-key-env", agent.apiKeyEnv);
201
+ }
202
+ for (const dir of plugins.value) {
203
+ argv.push("--plugin-dir", resolve(configDir, dir));
204
+ }
205
+ if (profile !== undefined && agent.pluginsOut !== undefined) {
206
+ argv.push("--profile", profile, "--plugins-out", agent.pluginsOut);
207
+ }
208
+ for (const arg of [...agent.args, ...args.value]) {
209
+ argv.push("--agent-arg", arg);
210
+ }
211
+ return { ok: true, value: argv };
212
+ }
213
+ /** The agent and profile a pass's argv names, written short or not; for `doctor`. */
214
+ export function agentPartOf(argv) {
215
+ const split = argv.indexOf("--");
216
+ const agent = split === -1 ? [] : argv.slice(split + 1);
217
+ const found = {
218
+ pluginDirs: [],
219
+ };
220
+ agent.forEach((token, at) => {
221
+ const value = agent[at + 1];
222
+ if (value === undefined) {
223
+ return;
224
+ }
225
+ if (token === "--profile") {
226
+ found.profile = value;
227
+ }
228
+ else if (token === "--plugins-out") {
229
+ found.pluginsOut = value;
230
+ }
231
+ else if (token === "--plugin-dir") {
232
+ found.pluginDirs.push(value);
233
+ }
234
+ });
235
+ return found;
236
+ }
237
+ function pathField(object, field, key, configDir) {
238
+ const value = object[field];
239
+ if (value === undefined) {
240
+ return { ok: true, value: undefined };
241
+ }
242
+ if (typeof value !== "string" || value.trim().length === 0) {
243
+ return { ok: false, reason: `Config "${key}.${field}" must be a non-empty path.` };
244
+ }
245
+ return { ok: true, value: resolve(configDir, value) };
246
+ }
247
+ function stringList(value, key) {
248
+ if (value === undefined) {
249
+ return { ok: true, value: [] };
250
+ }
251
+ if (!Array.isArray(value) || value.some((token) => typeof token !== "string")) {
252
+ return { ok: false, reason: `Config "${key}" must be an array of strings.` };
253
+ }
254
+ return { ok: true, value: value };
255
+ }
256
+ //# sourceMappingURL=agent-shorthand.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-shorthand.js","sourceRoot":"","sources":["../../src/config/agent-shorthand.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"load-config.d.ts","sourceRoot":"","sources":["../../src/config/load-config.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAGV,cAAc,EAIf,MAAM,YAAY,CAAC;AA8BpB,MAAM,MAAM,YAAY,GACpB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,UAAU,EAAE,OAAO,CAAC,cAAc,CAAC,CAAA;CAAE,GACjD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAElC;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAmKrD"}
1
+ {"version":3,"file":"load-config.d.ts","sourceRoot":"","sources":["../../src/config/load-config.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAIV,cAAc,EAIf,MAAM,YAAY,CAAC;AAgCpB,MAAM,MAAM,YAAY,GACpB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,UAAU,EAAE,OAAO,CAAC,cAAc,CAAC,CAAA;CAAE,GACjD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAElC;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CA+KrD"}
@@ -1,6 +1,7 @@
1
1
  import { existsSync, readFileSync, statSync } from "node:fs";
2
2
  import { dirname, resolve } from "node:path";
3
3
  import { parse as parseYaml } from "yaml";
4
+ import { expandAgentPass, readAgents, SHORT_PASS_KEYS, } from "./agent-shorthand.js";
4
5
  const KNOWN_KEYS = new Set([
5
6
  "manager",
6
7
  "managerOptions",
@@ -9,6 +10,7 @@ const KNOWN_KEYS = new Set([
9
10
  "workspaceRoot",
10
11
  "ledger",
11
12
  "persist",
13
+ "agents",
12
14
  "planner",
13
15
  "builder",
14
16
  "assembly",
@@ -18,9 +20,10 @@ const KNOWN_KEYS = new Set([
18
20
  "maxRefusals",
19
21
  "pollIntervalMs",
20
22
  ]);
21
- const WORK_LINE_KEYS = new Set(["stable", "branch", "isolation", "isolationOptions"]);
23
+ const WORK_LINE_KEYS = new Set(["stable", "branch", "isolation", "isolationOptions", "warm"]);
22
24
  const PASS_KEYS = new Set(["cmd", "timeoutMs"]);
23
- const STAGE_KEYS = new Set(["producer", "repair", "maxAttempts", "gates"]);
25
+ const GATED_PASS_KEYS = new Set(["cmd", "timeoutMs", "gates"]);
26
+ const STAGE_KEYS = new Set(["producer", "repair", "maxAttempts"]);
24
27
  const ASSEMBLY_KEYS = new Set(["fix", "validate", "maxAttempts", "gates"]);
25
28
  const GATES_KEYS = new Set(["defaultTimeoutMs", "gates"]);
26
29
  const AUTHORITY_KEYS = new Set(["enabled", "publish", "refresh", "describe"]);
@@ -97,6 +100,9 @@ export function loadConfig(path) {
97
100
  if (workLine.isolationOptions !== undefined) {
98
101
  invocation.workLineIsolationOptions = workLine.isolationOptions;
99
102
  }
103
+ if (workLine.warm !== undefined) {
104
+ invocation.workLineWarm = workLine.warm;
105
+ }
100
106
  }
101
107
  const workspaceRoot = stringField(object, "workspaceRoot");
102
108
  if (workspaceRoot.ok === false) {
@@ -119,29 +125,37 @@ export function loadConfig(path) {
119
125
  if (persist.value !== undefined) {
120
126
  invocation.persist = persist.value;
121
127
  }
128
+ let agents = new Map();
129
+ if (object.agents !== undefined) {
130
+ const read = readAgents(object.agents, configDir);
131
+ if (read.ok === false) {
132
+ return read;
133
+ }
134
+ agents = read.value;
135
+ }
122
136
  if (object.planner !== undefined) {
123
- const planner = readPass(object.planner, configDir, "planner");
137
+ const planner = readGatedPass(object.planner, configDir, "planner", agents);
124
138
  if (planner.ok === false) {
125
139
  return planner;
126
140
  }
127
141
  invocation.planner = planner.value;
128
142
  }
129
143
  if (object.builder !== undefined) {
130
- const builder = readBuilder(object.builder, configDir);
144
+ const builder = readBuilder(object.builder, configDir, agents);
131
145
  if (builder.ok === false) {
132
146
  return builder;
133
147
  }
134
148
  invocation.builder = builder.value;
135
149
  }
136
150
  if (object.assembly !== undefined) {
137
- const assembly = readAssembly(object.assembly, configDir);
151
+ const assembly = readAssembly(object.assembly, configDir, agents);
138
152
  if (assembly.ok === false) {
139
153
  return assembly;
140
154
  }
141
155
  invocation.assembly = assembly.value;
142
156
  }
143
157
  if (object.authority !== undefined) {
144
- const authority = readAuthority(object.authority, configDir);
158
+ const authority = readAuthority(object.authority, configDir, agents);
145
159
  if (authority.ok === false) {
146
160
  return authority;
147
161
  }
@@ -243,6 +257,14 @@ function readWorkLine(value, configDir) {
243
257
  Array.isArray(isolationOptions))) {
244
258
  return { ok: false, reason: 'Config "workLine.isolationOptions" must be a YAML mapping.' };
245
259
  }
260
+ let warm;
261
+ if (object.warm !== undefined) {
262
+ const read = readPass(object.warm, configDir, "workLine.warm");
263
+ if (read.ok === false) {
264
+ return read;
265
+ }
266
+ warm = read.value;
267
+ }
246
268
  return {
247
269
  ok: true,
248
270
  ...(stable.value === undefined ? {} : { stable: resolve(configDir, stable.value) }),
@@ -251,6 +273,7 @@ function readWorkLine(value, configDir) {
251
273
  ...(isolationOptions === undefined
252
274
  ? {}
253
275
  : { isolationOptions: isolationOptions }),
276
+ ...(warm === undefined ? {} : { warm }),
254
277
  };
255
278
  }
256
279
  /**
@@ -269,7 +292,7 @@ function readWorkLine(value, configDir) {
269
292
  * shared work line and one that keeps every fold local are different products,
270
293
  * and neither is the obvious one to assume.
271
294
  */
272
- function readAuthority(value, configDir) {
295
+ function readAuthority(value, configDir, agents) {
273
296
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
274
297
  return { ok: false, reason: 'Config "authority" must be a YAML mapping.' };
275
298
  }
@@ -315,13 +338,26 @@ function readAuthority(value, configDir) {
315
338
  };
316
339
  // Optional: the Project's own words for what it submits.
317
340
  const describe = object.describe;
318
- if (describe !== undefined) {
341
+ if (describe !== null && typeof describe === "object" && !Array.isArray(describe)) {
342
+ const short = describe;
343
+ for (const name of Object.keys(short)) {
344
+ if (!SHORT_PASS_KEYS.has(name)) {
345
+ return { ok: false, reason: `Config "authority.describe" has unknown key "${name}".` };
346
+ }
347
+ }
348
+ const argv = expandAgentPass(short, "authority.describe", agents, configDir, "authority.describe");
349
+ if (argv.ok === false) {
350
+ return argv;
351
+ }
352
+ spec.describeArgv = argv.value;
353
+ }
354
+ else if (describe !== undefined) {
319
355
  if (!Array.isArray(describe) ||
320
356
  describe.length === 0 ||
321
357
  describe.some((token) => typeof token !== "string")) {
322
358
  return {
323
359
  ok: false,
324
- reason: 'Config "authority.describe" must be a non-empty array of strings.',
360
+ reason: 'Config "authority.describe" must be a non-empty array of strings, or an agent pass ({ agent: … }).',
325
361
  };
326
362
  }
327
363
  spec.describeArgv = resolveArgv(describe, configDir);
@@ -483,10 +519,67 @@ function readPass(value, configDir, key) {
483
519
  value: { cmd: resolveArgv(cmd.value, configDir), timeoutMs: timeoutMs.value },
484
520
  };
485
521
  }
522
+ /**
523
+ * A command, and its own Gate sequence — `planner`, `builder.producer`,
524
+ * `builder.repair`, `assembly.fix`, `assembly.validate`. Deliberately its own
525
+ * ~10 lines rather than a parameterized `readPass`: each reader's error
526
+ * messages and key set stay independently legible.
527
+ */
528
+ function readGatedPass(value, configDir, key, agents) {
529
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
530
+ return {
531
+ ok: false,
532
+ reason: `Config "${key}" must be a YAML mapping with "cmd" (or "agent") and "timeoutMs".`,
533
+ };
534
+ }
535
+ const object = value;
536
+ const short = object.agent !== undefined;
537
+ for (const name of Object.keys(object)) {
538
+ const known = GATED_PASS_KEYS.has(name) ||
539
+ (short && (SHORT_PASS_KEYS.has(name) || (name === "read" && key === "planner")));
540
+ if (!known) {
541
+ return { ok: false, reason: `Config "${key}" has unknown key "${name}".` };
542
+ }
543
+ }
544
+ let cmd;
545
+ if (short) {
546
+ const expanded = expandAgentPass(object, key, agents, configDir, key);
547
+ if (expanded.ok === false) {
548
+ return expanded;
549
+ }
550
+ cmd = expanded.value;
551
+ }
552
+ else {
553
+ const written = stringArrayField(object, "cmd");
554
+ if (written.ok === false || written.value === undefined) {
555
+ return {
556
+ ok: false,
557
+ reason: `Config "${key}.cmd" must be a non-empty array of strings, or "${key}.agent" an agent of "agents".`,
558
+ };
559
+ }
560
+ cmd = resolveArgv(written.value, configDir);
561
+ }
562
+ const timeoutMs = positiveIntField(object, "timeoutMs");
563
+ if (timeoutMs.ok === false || timeoutMs.value === undefined) {
564
+ return {
565
+ ok: false,
566
+ reason: `Config "${key}" needs a positive "timeoutMs": nothing else bounds a command.`,
567
+ };
568
+ }
569
+ let gates = [];
570
+ if (object.gates !== undefined) {
571
+ const read = readGates(object.gates, configDir, `${key}.gates`);
572
+ if (read.ok === false) {
573
+ return read;
574
+ }
575
+ gates = read.value;
576
+ }
577
+ return { ok: true, value: { cmd, timeoutMs: timeoutMs.value, gates } };
578
+ }
486
579
  /**
487
580
  * Making a Subtask: both passes named, neither borrowing the other's command.
488
581
  */
489
- function readBuilder(value, configDir) {
582
+ function readBuilder(value, configDir, agents) {
490
583
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
491
584
  return { ok: false, reason: 'Config "builder" must be a YAML mapping.' };
492
585
  }
@@ -502,18 +595,17 @@ function readBuilder(value, configDir) {
502
595
  if (object.repair === undefined) {
503
596
  return { ok: false, reason: 'Config "builder" needs a "repair".' };
504
597
  }
505
- const readProducer = readPass(object.producer, configDir, "builder.producer");
598
+ const readProducer = readGatedPass(object.producer, configDir, "builder.producer", agents);
506
599
  if (readProducer.ok === false) {
507
600
  return readProducer;
508
601
  }
509
- const readRepair = readPass(object.repair, configDir, "builder.repair");
602
+ const readRepair = readGatedPass(object.repair, configDir, "builder.repair", agents);
510
603
  if (readRepair.ok === false) {
511
604
  return readRepair;
512
605
  }
513
606
  const spec = {
514
607
  producer: readProducer.value,
515
608
  repair: readRepair.value,
516
- gates: [],
517
609
  };
518
610
  const maxAttempts = positiveIntField(object, "maxAttempts");
519
611
  if (maxAttempts.ok === false) {
@@ -522,20 +614,13 @@ function readBuilder(value, configDir) {
522
614
  if (maxAttempts.value !== undefined) {
523
615
  spec.maxAttempts = maxAttempts.value;
524
616
  }
525
- if (object.gates !== undefined) {
526
- const gates = readGates(object.gates, configDir, "builder.gates");
527
- if (gates.ok === false) {
528
- return gates;
529
- }
530
- spec.gates = gates.value;
531
- }
532
617
  return { ok: true, value: spec };
533
618
  }
534
619
  /**
535
620
  * Judging the assembled feature. A command here is a fix of what a judgement
536
621
  * refused, not a second first-pass of the request.
537
622
  */
538
- function readAssembly(value, configDir) {
623
+ function readAssembly(value, configDir, agents) {
539
624
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
540
625
  return { ok: false, reason: 'Config "assembly" must be a YAML mapping.' };
541
626
  }
@@ -554,14 +639,14 @@ function readAssembly(value, configDir) {
554
639
  }
555
640
  const spec = { gates: [] };
556
641
  if (object.fix !== undefined) {
557
- const read = readPass(object.fix, configDir, "assembly.fix");
642
+ const read = readGatedPass(object.fix, configDir, "assembly.fix", agents);
558
643
  if (read.ok === false) {
559
644
  return read;
560
645
  }
561
646
  spec.fix = read.value;
562
647
  }
563
648
  if (object.validate !== undefined) {
564
- const read = readPass(object.validate, configDir, "assembly.validate");
649
+ const read = readGatedPass(object.validate, configDir, "assembly.validate", agents);
565
650
  if (read.ok === false) {
566
651
  return read;
567
652
  }
@@ -592,8 +677,20 @@ function readAssembly(value, configDir) {
592
677
  * resolved against the slot's working directory, which is the Task workspace.
593
678
  * A workspace is a git worktree that is published and then destroyed: output
594
679
  * left there is committed into the work first, and lost after.
680
+ *
681
+ * `--transcript-dir` and `--agent-home` are Host-owned write paths (films, and
682
+ * the vendor's isolated HOME). Both must resolve against the config directory
683
+ * even before the directory exists. So must `--plugins-out`, where a deck
684
+ * profile is built. `--plugin-dir` is read, not written, but a relative one
685
+ * left as a word would be looked up in the Task workspace — never where the
686
+ * operator's plugin is.
595
687
  */
596
- const WRITTEN_PATH_FLAGS = new Set(["--transcript-dir"]);
688
+ const WRITTEN_PATH_FLAGS = new Set([
689
+ "--transcript-dir",
690
+ "--agent-home",
691
+ "--plugins-out",
692
+ "--plugin-dir",
693
+ ]);
597
694
  function resolveArgv(argv, configDir) {
598
695
  return argv.map((token, at) => WRITTEN_PATH_FLAGS.has(argv[at - 1] ?? "")
599
696
  ? resolve(configDir, token)