@zooid/core 0.7.0 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +84 -8
- package/dist/index.js +193 -21
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/acp-registry.ts +38 -2
- package/src/config.test.ts +88 -9
- package/src/config.ts +215 -21
- package/src/container-mounts.test.ts +292 -0
- package/src/index.ts +3 -0
- package/src/types.ts +57 -6
- package/src/zooid-yaml-sweep.test.ts +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -80,9 +80,25 @@ interface Transport {
|
|
|
80
80
|
listen(channel: string, onMessage: (msg: InboundMessage) => void): void;
|
|
81
81
|
reply(thread: ThreadRef, message: string): Promise<void> | void;
|
|
82
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* One bind mount. `id` is the handle used by `disable_mounts` to subtract
|
|
85
|
+
* a zooid- or preset-declared entry; user-declared entries that omit `id`
|
|
86
|
+
* are auto-assigned `user-N`. The reserved id `workspace` is rejected on
|
|
87
|
+
* user entries.
|
|
88
|
+
*/
|
|
89
|
+
interface MountConfig {
|
|
90
|
+
id?: string;
|
|
91
|
+
host: string;
|
|
92
|
+
target: string;
|
|
93
|
+
mode: 'ro' | 'rw';
|
|
94
|
+
/** mkdir -p the host path before bind-mounting. Default false. */
|
|
95
|
+
create?: boolean;
|
|
96
|
+
}
|
|
83
97
|
/**
|
|
84
98
|
* Per-agent container configuration. Holds runtime-neutral container
|
|
85
|
-
* concerns — image, env.
|
|
99
|
+
* concerns — image, env, mounts. `image` / `env` are rejected at parse time
|
|
100
|
+
* when `runtime: local`; `mounts` / `disable_mounts` are accepted under
|
|
101
|
+
* `runtime: local` but ignored at compose time.
|
|
86
102
|
*/
|
|
87
103
|
interface ContainerConfig {
|
|
88
104
|
image?: string;
|
|
@@ -93,6 +109,13 @@ interface ContainerConfig {
|
|
|
93
109
|
* `ZOOID_*` references and `ZOOID_*` keys are rejected.
|
|
94
110
|
*/
|
|
95
111
|
env?: Record<string, string>;
|
|
112
|
+
/** User-declared mounts, layered on top of workspace + preset. */
|
|
113
|
+
mounts?: MountConfig[];
|
|
114
|
+
/**
|
|
115
|
+
* Subtractive override by mount id. Built-in ids: `workspace` plus
|
|
116
|
+
* whatever each preset declares (canonical: `memory`, `history`, `config`).
|
|
117
|
+
*/
|
|
118
|
+
disable_mounts?: string[];
|
|
96
119
|
}
|
|
97
120
|
/**
|
|
98
121
|
* Workforce-level container defaults. Currently `image` only — see
|
|
@@ -101,6 +124,27 @@ interface ContainerConfig {
|
|
|
101
124
|
interface ZooidContainerConfig {
|
|
102
125
|
image?: string;
|
|
103
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* A room binding for an agent. Either a bare alias (default PL) or
|
|
129
|
+
* an alias with a declared power level applied at room creation.
|
|
130
|
+
*
|
|
131
|
+
* `alias` may be an alias (`#room:server`) or a room ID (`!id:server`).
|
|
132
|
+
* Resolved to a canonical room ID by `bot-pool` at bootstrap. The
|
|
133
|
+
* declared `powerLevel` (when set) seeds the agent's entry in
|
|
134
|
+
* `m.room.power_levels.users` at room creation; the daemon never reads
|
|
135
|
+
* or modifies power levels after that — promote/demote in the UI is
|
|
136
|
+
* canonical.
|
|
137
|
+
*/
|
|
138
|
+
interface RoomBinding {
|
|
139
|
+
/** Room alias (typical) or room ID. */
|
|
140
|
+
alias: string;
|
|
141
|
+
/**
|
|
142
|
+
* Power level the agent should hold in this room at the moment it is
|
|
143
|
+
* created. Omitted = `users_default` (effectively 0). Not reconciled
|
|
144
|
+
* after creation.
|
|
145
|
+
*/
|
|
146
|
+
powerLevel?: number;
|
|
147
|
+
}
|
|
104
148
|
/**
|
|
105
149
|
* Matrix transport binding. Lives under `agents.<name>.matrix:` in
|
|
106
150
|
* zooid.yaml. The block name (`matrix`) is the transport-kind
|
|
@@ -123,8 +167,8 @@ interface MatrixBinding {
|
|
|
123
167
|
* profile on bootstrap. Falls back to the user_id localpart when absent.
|
|
124
168
|
*/
|
|
125
169
|
display_name?: string;
|
|
126
|
-
/**
|
|
127
|
-
rooms:
|
|
170
|
+
/** Rooms this agent watches. Each entry carries the alias/ID and an optional declared PL. */
|
|
171
|
+
rooms: RoomBinding[];
|
|
128
172
|
/**
|
|
129
173
|
* Routing rule. `mention` requires the bot to be tagged; `any` triggers
|
|
130
174
|
* on every message.
|
|
@@ -181,7 +225,9 @@ interface AgentConfig {
|
|
|
181
225
|
}
|
|
182
226
|
/**
|
|
183
227
|
* Matrix application-service transport. The CLI binds the AS HTTP listener
|
|
184
|
-
* to `port` (defaults to
|
|
228
|
+
* to `port` (defaults to 9000 — the most common Matrix AS convention; see
|
|
229
|
+
* Synapse / mautrix / matrix-appservice-* projects). Must match the port in
|
|
230
|
+
* the registration YAML's `url` (read by the homeserver, not by Zooid).
|
|
185
231
|
*/
|
|
186
232
|
interface MatrixTransportConfig {
|
|
187
233
|
type: 'matrix';
|
|
@@ -207,8 +253,11 @@ interface MatrixTransportConfig {
|
|
|
207
253
|
*/
|
|
208
254
|
user_namespace: string;
|
|
209
255
|
/**
|
|
210
|
-
* AS HTTP listener port.
|
|
211
|
-
*
|
|
256
|
+
* AS HTTP listener port. Must match the registration YAML's `url` port —
|
|
257
|
+
* Zooid never reads the registration, so a mismatch silently sinks every
|
|
258
|
+
* transaction (the homeserver gets connection refused; you see no error
|
|
259
|
+
* in Zooid's logs).
|
|
260
|
+
* @default 9000
|
|
212
261
|
*/
|
|
213
262
|
port?: number;
|
|
214
263
|
/**
|
|
@@ -254,7 +303,16 @@ interface CliFlags {
|
|
|
254
303
|
image?: string;
|
|
255
304
|
}
|
|
256
305
|
|
|
257
|
-
|
|
306
|
+
interface LoadZooidConfigOptions {
|
|
307
|
+
/**
|
|
308
|
+
* Directory containing zooid.yaml. Required when any agent uses a
|
|
309
|
+
* relative `container.mounts[].host` path; resolution happens at parse
|
|
310
|
+
* time so the resulting `MountConfig` always carries an absolute host
|
|
311
|
+
* path.
|
|
312
|
+
*/
|
|
313
|
+
configDir?: string;
|
|
314
|
+
}
|
|
315
|
+
declare function loadZooidConfig(yamlText: string, opts?: LoadZooidConfigOptions): ZooidConfig;
|
|
258
316
|
declare function findTransport(cfg: ZooidConfig, name: string): TransportConfig | undefined;
|
|
259
317
|
declare function findMatrixTransport(cfg: ZooidConfig): {
|
|
260
318
|
name: string;
|
|
@@ -374,6 +432,21 @@ interface AcpAgentRegistryOptions {
|
|
|
374
432
|
* transports without a context provider (e.g. HTTP) have no entry here.
|
|
375
433
|
*/
|
|
376
434
|
contextSpawns?: Record<string, ContextSpawnFactory | undefined>;
|
|
435
|
+
/**
|
|
436
|
+
* Per-agent resolved bind-mount list. Threaded into the AcpClient's spawn
|
|
437
|
+
* spec; honoured by the docker runtime, ignored by the local runtime.
|
|
438
|
+
*/
|
|
439
|
+
mounts?: Record<string, AcpMount[]>;
|
|
440
|
+
/**
|
|
441
|
+
* Per-agent list of host directories to `mkdir -p` before the first
|
|
442
|
+
* `runtime.spawn` for that agent. Subset of mount entries with `create: true`.
|
|
443
|
+
*/
|
|
444
|
+
mkdirOnSpawn?: Record<string, string[]>;
|
|
445
|
+
/**
|
|
446
|
+
* Per-agent override for the spawn-spec `cwd`. Set to e.g. `/workspace`
|
|
447
|
+
* when the workspace mount is active; falls back to `agent.workdir`.
|
|
448
|
+
*/
|
|
449
|
+
cwd?: Record<string, string>;
|
|
377
450
|
}
|
|
378
451
|
type ContextSpawnFactory = (threadId: string, channelId?: string) => Promise<{
|
|
379
452
|
name: 'zooid-context';
|
|
@@ -394,6 +467,9 @@ declare class AcpAgentRegistry implements AcpRegistry {
|
|
|
394
467
|
hasContextSpawn(name: string): boolean;
|
|
395
468
|
resolveSpawnEnv(name: string): Record<string, string>;
|
|
396
469
|
resolveSpawnImage(name: string): string | undefined;
|
|
470
|
+
resolveSpawnMounts(name: string): AcpMount[];
|
|
471
|
+
resolveSpawnCwd(name: string): string;
|
|
472
|
+
agentNames(): string[];
|
|
397
473
|
getApprovalTimeoutMs(name: string): number;
|
|
398
474
|
ensureSession(name: string, threadId: string, channelId?: string): Promise<string>;
|
|
399
475
|
endSession(name: string, threadId: string): void;
|
|
@@ -488,4 +564,4 @@ interface TransportContextProvider {
|
|
|
488
564
|
getChannelInfo(channelId: string): Promise<ChannelInfo>;
|
|
489
565
|
}
|
|
490
566
|
|
|
491
|
-
export { AcpAgentRegistry, type AcpAgentRegistryOptions, type AcpAgentSpec, type AcpMount, type AcpRegistry, type AcpRegistryApprovalHandler, type AcpRegistryEventHandler, type AcpRuntime, type AcpSpawnSpec, type AgentConfig, ApprovalCorrelator, type ChannelInfo, type CliFlags, type ContainerConfig, type ContextSpawnFactory, type HistoryOptions, type HistoryPage, type HttpBinding, type HttpTransportConfig, type InboundMessage, type MatrixBinding, type MatrixTransportConfig, type Member, type Message, type RegisterOptions, type RegisteredApproval, type ThreadOverview, type ThreadOverviewPage, type ThreadRef, type Transport, type TransportConfig, type TransportContextProvider, type ZooidConfig, type ZooidContainerConfig, findConfigFile, findHttpTransport, findMatrixTransport, findTransport, loadZooidConfig, mergeCliFlags, resolveAcpAgentSpec };
|
|
567
|
+
export { AcpAgentRegistry, type AcpAgentRegistryOptions, type AcpAgentSpec, type AcpMount, type AcpRegistry, type AcpRegistryApprovalHandler, type AcpRegistryEventHandler, type AcpRuntime, type AcpSpawnSpec, type AgentConfig, ApprovalCorrelator, type ChannelInfo, type CliFlags, type ContainerConfig, type ContextSpawnFactory, type HistoryOptions, type HistoryPage, type HttpBinding, type HttpTransportConfig, type InboundMessage, type LoadZooidConfigOptions, type MatrixBinding, type MatrixTransportConfig, type Member, type Message, type MountConfig, type RegisterOptions, type RegisteredApproval, type RoomBinding, type ThreadOverview, type ThreadOverviewPage, type ThreadRef, type Transport, type TransportConfig, type TransportContextProvider, type ZooidConfig, type ZooidContainerConfig, findConfigFile, findHttpTransport, findMatrixTransport, findTransport, loadZooidConfig, mergeCliFlags, resolveAcpAgentSpec };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/config.ts
|
|
2
2
|
import { existsSync } from "fs";
|
|
3
|
-
import { join } from "path";
|
|
3
|
+
import { isAbsolute, join, resolve as pathResolve } from "path";
|
|
4
4
|
import { parse } from "yaml";
|
|
5
5
|
import { isPreset } from "@zooid/acp-client";
|
|
6
6
|
|
|
@@ -142,7 +142,7 @@ function parseApprovalTimeout(name, raw) {
|
|
|
142
142
|
}
|
|
143
143
|
throw new Error("unreachable");
|
|
144
144
|
}
|
|
145
|
-
function parseAgentContainer(name, raw, processEnv) {
|
|
145
|
+
function parseAgentContainer(name, raw, processEnv, configDir) {
|
|
146
146
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
147
147
|
throw new Error(`agents.${name}.container must be a mapping`);
|
|
148
148
|
}
|
|
@@ -170,6 +170,132 @@ function parseAgentContainer(name, raw, processEnv) {
|
|
|
170
170
|
}
|
|
171
171
|
out.env = interpolateEnv(stringEnv, processEnv, `agents.${name}.container.env`);
|
|
172
172
|
}
|
|
173
|
+
if (r.mounts !== void 0) {
|
|
174
|
+
out.mounts = parseMountList(name, r.mounts, processEnv, configDir);
|
|
175
|
+
}
|
|
176
|
+
if (r.disable_mounts !== void 0) {
|
|
177
|
+
out.disable_mounts = parseDisableMounts(name, r.disable_mounts);
|
|
178
|
+
}
|
|
179
|
+
return out;
|
|
180
|
+
}
|
|
181
|
+
function parseMountList(agentName, raw, processEnv, configDir) {
|
|
182
|
+
if (!Array.isArray(raw)) {
|
|
183
|
+
throw new Error(`agents.${agentName}.container.mounts must be an array`);
|
|
184
|
+
}
|
|
185
|
+
const out = [];
|
|
186
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
187
|
+
for (let i = 0; i < raw.length; i++) {
|
|
188
|
+
const entry = raw[i];
|
|
189
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
190
|
+
throw new Error(`agents.${agentName}.container.mounts[${i}] must be a mapping`);
|
|
191
|
+
}
|
|
192
|
+
const e = entry;
|
|
193
|
+
if (e.host === void 0) {
|
|
194
|
+
throw new Error(`agents.${agentName}.container.mounts[${i}].host is required`);
|
|
195
|
+
}
|
|
196
|
+
if (e.target === void 0) {
|
|
197
|
+
throw new Error(`agents.${agentName}.container.mounts[${i}].target is required`);
|
|
198
|
+
}
|
|
199
|
+
if (typeof e.host !== "string" || e.host.length === 0) {
|
|
200
|
+
throw new Error(
|
|
201
|
+
`agents.${agentName}.container.mounts[${i}].host must be a non-empty string`
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
if (typeof e.target !== "string" || e.target.length === 0) {
|
|
205
|
+
throw new Error(
|
|
206
|
+
`agents.${agentName}.container.mounts[${i}].target must be a non-empty string`
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
const mode = e.mode ?? "rw";
|
|
210
|
+
if (mode !== "ro" && mode !== "rw") {
|
|
211
|
+
throw new Error(
|
|
212
|
+
`agents.${agentName}.container.mounts[${i}].mode must be "ro" or "rw" (got ${JSON.stringify(e.mode)})`
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
let id;
|
|
216
|
+
if (e.id !== void 0) {
|
|
217
|
+
if (typeof e.id !== "string" || e.id.length === 0) {
|
|
218
|
+
throw new Error(
|
|
219
|
+
`agents.${agentName}.container.mounts[${i}].id must be a non-empty string`
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
if (e.id === "workspace") {
|
|
223
|
+
throw new Error(
|
|
224
|
+
`agents.${agentName}.container.mounts[${i}].id: "workspace" is a reserved id (set by the workspace auto-mount). Use a different id or rely on disable_mounts to subtract.`
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
if (seenIds.has(e.id)) {
|
|
228
|
+
throw new Error(
|
|
229
|
+
`agents.${agentName}.container.mounts: duplicate id "${e.id}"`
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
seenIds.add(e.id);
|
|
233
|
+
id = e.id;
|
|
234
|
+
}
|
|
235
|
+
let create;
|
|
236
|
+
if (e.create !== void 0) {
|
|
237
|
+
if (typeof e.create !== "boolean") {
|
|
238
|
+
throw new Error(
|
|
239
|
+
`agents.${agentName}.container.mounts[${i}].create must be a boolean`
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
create = e.create;
|
|
243
|
+
}
|
|
244
|
+
const host = resolveHostPath(
|
|
245
|
+
agentName,
|
|
246
|
+
i,
|
|
247
|
+
interpolateString(e.host, processEnv),
|
|
248
|
+
configDir
|
|
249
|
+
);
|
|
250
|
+
const target = interpolateString(e.target, processEnv);
|
|
251
|
+
const m = { host, target, mode };
|
|
252
|
+
if (id !== void 0) m.id = id;
|
|
253
|
+
if (create !== void 0) m.create = create;
|
|
254
|
+
out.push(m);
|
|
255
|
+
}
|
|
256
|
+
return out;
|
|
257
|
+
}
|
|
258
|
+
function resolveHostPath(agentName, index, host, configDir) {
|
|
259
|
+
if (host.startsWith("~/")) {
|
|
260
|
+
const home = process.env.HOME;
|
|
261
|
+
if (!home) {
|
|
262
|
+
throw new Error(
|
|
263
|
+
`agents.${agentName}.container.mounts[${index}].host: cannot expand ~ \u2014 $HOME is not set`
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
return `${home}/${host.slice(2)}`;
|
|
267
|
+
}
|
|
268
|
+
if (host === "~") {
|
|
269
|
+
const home = process.env.HOME;
|
|
270
|
+
if (!home) {
|
|
271
|
+
throw new Error(
|
|
272
|
+
`agents.${agentName}.container.mounts[${index}].host: cannot expand ~ \u2014 $HOME is not set`
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
return home;
|
|
276
|
+
}
|
|
277
|
+
if (isAbsolute(host)) return host;
|
|
278
|
+
if (!configDir) {
|
|
279
|
+
throw new Error(
|
|
280
|
+
`agents.${agentName}.container.mounts[${index}]: relative host path "${host}" requires configDir (zooid.yaml directory) \u2014 pass it via loadZooidConfig(yaml, { configDir })`
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
return pathResolve(configDir, host);
|
|
284
|
+
}
|
|
285
|
+
function parseDisableMounts(agentName, raw) {
|
|
286
|
+
if (!Array.isArray(raw)) {
|
|
287
|
+
throw new Error(`agents.${agentName}.container.disable_mounts must be an array of strings`);
|
|
288
|
+
}
|
|
289
|
+
const out = [];
|
|
290
|
+
for (let i = 0; i < raw.length; i++) {
|
|
291
|
+
const v = raw[i];
|
|
292
|
+
if (typeof v !== "string" || v.length === 0) {
|
|
293
|
+
throw new Error(
|
|
294
|
+
`agents.${agentName}.container.disable_mounts[${i}] must be a non-empty string`
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
out.push(v);
|
|
298
|
+
}
|
|
173
299
|
return out;
|
|
174
300
|
}
|
|
175
301
|
function parseZooidContainer(raw) {
|
|
@@ -306,6 +432,39 @@ function parseTransport(name, raw, processEnv) {
|
|
|
306
432
|
}
|
|
307
433
|
return { type: "http", port };
|
|
308
434
|
}
|
|
435
|
+
function parseRoomBinding(path, raw, serverName) {
|
|
436
|
+
function normalizeAlias(alias) {
|
|
437
|
+
if (alias.length === 0) {
|
|
438
|
+
throw new Error(`${path}: must be a non-empty alias`);
|
|
439
|
+
}
|
|
440
|
+
if (!MATRIX_ROOM_IDENT_RE.test(alias)) {
|
|
441
|
+
throw new Error(
|
|
442
|
+
`${path}: must start with '#' or '!' (got ${JSON.stringify(alias)})`
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
return alias.includes(":") ? alias : `${alias}:${serverName}`;
|
|
446
|
+
}
|
|
447
|
+
if (typeof raw === "string") {
|
|
448
|
+
return { alias: normalizeAlias(raw) };
|
|
449
|
+
}
|
|
450
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
451
|
+
throw new Error(`${path}: must be a string or { alias, power_level } object`);
|
|
452
|
+
}
|
|
453
|
+
const r = raw;
|
|
454
|
+
if (typeof r.alias !== "string" || r.alias.length === 0) {
|
|
455
|
+
throw new Error(`${path}.alias: must be a non-empty string`);
|
|
456
|
+
}
|
|
457
|
+
const out = { alias: normalizeAlias(r.alias) };
|
|
458
|
+
if (r.power_level !== void 0) {
|
|
459
|
+
if (typeof r.power_level !== "number" || !Number.isInteger(r.power_level)) {
|
|
460
|
+
throw new Error(
|
|
461
|
+
`${path}.power_level: must be an integer (got ${JSON.stringify(r.power_level)})`
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
out.powerLevel = r.power_level;
|
|
465
|
+
}
|
|
466
|
+
return out;
|
|
467
|
+
}
|
|
309
468
|
function parseTransportBinding(name, entry, transports) {
|
|
310
469
|
const present = TRANSPORT_KINDS.filter(
|
|
311
470
|
(k) => entry[k] !== void 0 && entry[k] !== null
|
|
@@ -375,16 +534,8 @@ function parseTransportBinding(name, entry, transports) {
|
|
|
375
534
|
throw new Error(`agents.${name}.matrix.rooms is required and must be a non-empty array`);
|
|
376
535
|
}
|
|
377
536
|
const rooms = [];
|
|
378
|
-
for (
|
|
379
|
-
|
|
380
|
-
throw new Error(`agents.${name}.matrix.rooms[] must be a non-empty string`);
|
|
381
|
-
}
|
|
382
|
-
if (!MATRIX_ROOM_IDENT_RE.test(r)) {
|
|
383
|
-
throw new Error(
|
|
384
|
-
`agents.${name}.matrix.rooms[] must start with '#' or '!' (got ${JSON.stringify(r)})`
|
|
385
|
-
);
|
|
386
|
-
}
|
|
387
|
-
rooms.push(r.includes(":") ? r : `${r}:${serverName}`);
|
|
537
|
+
for (let i = 0; i < block.rooms.length; i++) {
|
|
538
|
+
rooms.push(parseRoomBinding(`agents.${name}.matrix.rooms[${i}]`, block.rooms[i], serverName));
|
|
388
539
|
}
|
|
389
540
|
let displayName;
|
|
390
541
|
if (block.display_name !== void 0) {
|
|
@@ -419,7 +570,7 @@ function parseTransportBinding(name, entry, transports) {
|
|
|
419
570
|
}
|
|
420
571
|
return { http: { transport: refName } };
|
|
421
572
|
}
|
|
422
|
-
function parseAgents(raw, runtime, transports, daemonHooks, processEnv) {
|
|
573
|
+
function parseAgents(raw, runtime, transports, daemonHooks, processEnv, configDir) {
|
|
423
574
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
424
575
|
throw new Error("agents: must be a mapping");
|
|
425
576
|
}
|
|
@@ -496,11 +647,18 @@ See [ZOD043].`
|
|
|
496
647
|
let containerBlock;
|
|
497
648
|
if (entry.container !== void 0 && entry.container !== null) {
|
|
498
649
|
if (runtime === "local") {
|
|
499
|
-
|
|
500
|
-
`agents.${name}.container
|
|
501
|
-
|
|
650
|
+
if (typeof entry.container !== "object" || entry.container === null || Array.isArray(entry.container)) {
|
|
651
|
+
throw new Error(`agents.${name}.container must be a mapping`);
|
|
652
|
+
}
|
|
653
|
+
const c = entry.container;
|
|
654
|
+
const disallowed = Object.keys(c).filter((k) => k !== "mounts" && k !== "disable_mounts");
|
|
655
|
+
if (disallowed.length > 0) {
|
|
656
|
+
throw new Error(
|
|
657
|
+
`agents.${name}.container.${disallowed[0]} is only valid when runtime is 'docker' or 'podman'. runtime: local spawns agents as host child processes \u2014 there is no container, so 'image' is inert and 'env' would silently lie (the agent inherits the daemon's full process.env regardless). 'mounts' and 'disable_mounts' are accepted under runtime: local but ignored at compose time.`
|
|
658
|
+
);
|
|
659
|
+
}
|
|
502
660
|
}
|
|
503
|
-
containerBlock = parseAgentContainer(name, entry.container, processEnv);
|
|
661
|
+
containerBlock = parseAgentContainer(name, entry.container, processEnv, configDir);
|
|
504
662
|
}
|
|
505
663
|
const binding = parseTransportBinding(name, entry, transports);
|
|
506
664
|
const agentCfg = {
|
|
@@ -533,7 +691,7 @@ function zooidHooks(raw) {
|
|
|
533
691
|
}
|
|
534
692
|
return out;
|
|
535
693
|
}
|
|
536
|
-
function loadZooidConfig(yamlText) {
|
|
694
|
+
function loadZooidConfig(yamlText, opts = {}) {
|
|
537
695
|
const raw = parse(yamlText) ?? {};
|
|
538
696
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
539
697
|
throw new Error("zooid.yaml must be a YAML object");
|
|
@@ -566,7 +724,7 @@ function loadZooidConfig(yamlText) {
|
|
|
566
724
|
const processEnv = process.env;
|
|
567
725
|
const transports = parseTransports(r.transports, processEnv);
|
|
568
726
|
const hooks = zooidHooks(r);
|
|
569
|
-
const agents = parseAgents(r.agents, runtime, transports, hooks, processEnv);
|
|
727
|
+
const agents = parseAgents(r.agents, runtime, transports, hooks, processEnv, opts.configDir);
|
|
570
728
|
const cfg = {
|
|
571
729
|
runtime,
|
|
572
730
|
transports,
|
|
@@ -647,6 +805,7 @@ function mergeCliFlags(base, flags) {
|
|
|
647
805
|
}
|
|
648
806
|
|
|
649
807
|
// src/acp-registry.ts
|
|
808
|
+
import { mkdirSync } from "fs";
|
|
650
809
|
import { join as join2 } from "path";
|
|
651
810
|
import {
|
|
652
811
|
AcpClient,
|
|
@@ -689,6 +848,15 @@ var AcpAgentRegistry = class {
|
|
|
689
848
|
resolveSpawnImage(name) {
|
|
690
849
|
return this.opts.image?.[name];
|
|
691
850
|
}
|
|
851
|
+
resolveSpawnMounts(name) {
|
|
852
|
+
return this.opts.mounts?.[name] ?? [];
|
|
853
|
+
}
|
|
854
|
+
resolveSpawnCwd(name) {
|
|
855
|
+
return this.opts.cwd?.[name] ?? this.opts.agents[name]?.workdir ?? process.cwd();
|
|
856
|
+
}
|
|
857
|
+
agentNames() {
|
|
858
|
+
return Object.keys(this.opts.agents);
|
|
859
|
+
}
|
|
692
860
|
getApprovalTimeoutMs(name) {
|
|
693
861
|
return this.opts.agents[name]?.approval_timeout_ms ?? 0;
|
|
694
862
|
}
|
|
@@ -730,14 +898,18 @@ var AcpAgentRegistry = class {
|
|
|
730
898
|
const cfg = this.opts.agents[name];
|
|
731
899
|
if (!cfg.acp) throw new Error(`agents.${name}: missing acp block`);
|
|
732
900
|
const spawn = resolveAcpAgentSpec(cfg.acp);
|
|
901
|
+
for (const dir of this.opts.mkdirOnSpawn?.[name] ?? []) {
|
|
902
|
+
mkdirSync(dir, { recursive: true });
|
|
903
|
+
}
|
|
733
904
|
const client = new AcpClient({
|
|
734
905
|
agent: {
|
|
735
906
|
id: name,
|
|
736
907
|
command: spawn.command,
|
|
737
908
|
args: spawn.args,
|
|
738
909
|
env: this.opts.env?.[name],
|
|
739
|
-
cwd:
|
|
740
|
-
image: this.opts.image?.[name]
|
|
910
|
+
cwd: this.resolveSpawnCwd(name),
|
|
911
|
+
image: this.opts.image?.[name],
|
|
912
|
+
mounts: this.resolveSpawnMounts(name)
|
|
741
913
|
},
|
|
742
914
|
agentDataDir: this.opts.agentsDir ? join2(this.opts.agentsDir, name) : void 0,
|
|
743
915
|
runtime: this.opts.runtime,
|