@zooid/core 0.9.0 → 0.10.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.
- package/dist/index.d.ts +34 -12
- package/dist/index.js +55 -12
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/config.test.ts +103 -1
- package/src/config.ts +74 -12
- package/src/types.ts +34 -12
package/dist/index.d.ts
CHANGED
|
@@ -118,8 +118,8 @@ interface ContainerConfig {
|
|
|
118
118
|
disable_mounts?: string[];
|
|
119
119
|
}
|
|
120
120
|
/**
|
|
121
|
-
* Workforce-level container defaults. Currently `image` only —
|
|
122
|
-
*
|
|
121
|
+
* Workforce-level container defaults. Currently `image` only — workforce-level
|
|
122
|
+
* env is intentionally unsupported (set env per-agent under `container.env`).
|
|
123
123
|
*/
|
|
124
124
|
interface ZooidContainerConfig {
|
|
125
125
|
image?: string;
|
|
@@ -225,9 +225,8 @@ interface AgentConfig {
|
|
|
225
225
|
}
|
|
226
226
|
/**
|
|
227
227
|
* Matrix application-service transport. The CLI binds the AS HTTP listener
|
|
228
|
-
* to `port` (defaults to
|
|
229
|
-
*
|
|
230
|
-
* the registration YAML's `url` (read by the homeserver, not by Zooid).
|
|
228
|
+
* to `port` (defaults to 9099 for co-located/community-box mode). Must match
|
|
229
|
+
* the port in the registration YAML's `url` (read by the homeserver, not by Zooid).
|
|
231
230
|
*/
|
|
232
231
|
interface MatrixTransportConfig {
|
|
233
232
|
type: 'matrix';
|
|
@@ -244,22 +243,38 @@ interface MatrixTransportConfig {
|
|
|
244
243
|
hs_token: string;
|
|
245
244
|
/**
|
|
246
245
|
* Localpart of the AS sender user (the bridge bot).
|
|
247
|
-
* @default 'zooid'
|
|
246
|
+
* @default 'zooid', or the workstation name when `workstation` is set
|
|
248
247
|
*/
|
|
249
248
|
sender_localpart: string;
|
|
250
249
|
/**
|
|
251
250
|
* Regex covering all bot users, e.g. `@.*:example.com`.
|
|
252
|
-
* @default `@.*:<host
|
|
251
|
+
* @default `@.*:<host>`, or workstation-scoped `@<workstation>\..*:<host>` when `workstation` is set
|
|
253
252
|
*/
|
|
254
253
|
user_namespace: string;
|
|
255
254
|
/**
|
|
256
|
-
* AS HTTP listener port.
|
|
257
|
-
*
|
|
258
|
-
*
|
|
259
|
-
* in Zooid's logs).
|
|
260
|
-
* @default 9000
|
|
255
|
+
* AS HTTP listener port. Mutually exclusive with `advertise_url`.
|
|
256
|
+
* When set, the registration `url` is `http://host.docker.internal:<port>`.
|
|
257
|
+
* @default 9099 (co-located mode) when neither port nor advertise_url is set
|
|
261
258
|
*/
|
|
262
259
|
port?: number;
|
|
260
|
+
/**
|
|
261
|
+
* Explicit registration URL advertised to the homeserver. Mutually exclusive
|
|
262
|
+
* with `port`. Use for multi-workstation setups where the homeserver reaches
|
|
263
|
+
* the daemon via a stable external address.
|
|
264
|
+
*/
|
|
265
|
+
advertise_url?: string;
|
|
266
|
+
/**
|
|
267
|
+
* AS mode. `appservice` = push transport (homeserver calls the daemon).
|
|
268
|
+
* `client` = pull transport (daemon polls /_matrix/client/v3/sync).
|
|
269
|
+
* @default 'appservice'
|
|
270
|
+
*/
|
|
271
|
+
mode?: 'appservice' | 'client';
|
|
272
|
+
/**
|
|
273
|
+
* Workstation identity derived from the top-level `workstation:` in
|
|
274
|
+
* zooid.yaml. Present only when a workstation is declared; absent for the
|
|
275
|
+
* shared-namespace `zooid dev` profile.
|
|
276
|
+
*/
|
|
277
|
+
workstation?: string;
|
|
263
278
|
/**
|
|
264
279
|
* Workforce space localpart. Resolves to alias `#<space>:<server>`.
|
|
265
280
|
* @default 'dev'
|
|
@@ -285,6 +300,13 @@ type TransportConfig = MatrixTransportConfig | HttpTransportConfig;
|
|
|
285
300
|
*/
|
|
286
301
|
interface ZooidConfig {
|
|
287
302
|
runtime: 'local' | 'docker' | 'podman';
|
|
303
|
+
/**
|
|
304
|
+
* Workstation identity. A short, lowercase, hyphen-separated name that
|
|
305
|
+
* uniquely identifies this daemon instance (e.g. `laptop`, `ec2-prod`).
|
|
306
|
+
* When set, the matrix transport derives `sender_localpart` and
|
|
307
|
+
* `user_namespace` from it, enabling exclusive per-workstation AS namespaces.
|
|
308
|
+
*/
|
|
309
|
+
workstation?: string;
|
|
288
310
|
/** Workforce-wide container defaults. Image only — no workforce-level env. Rejected when runtime: local. */
|
|
289
311
|
container?: ZooidContainerConfig;
|
|
290
312
|
/** Required. Map of operator-chosen names → transport config. At least one entry. */
|
package/dist/index.js
CHANGED
|
@@ -55,6 +55,7 @@ function interpolateString(value, processEnv) {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
// src/config.ts
|
|
58
|
+
var SLUG_RE = /^[a-z0-9-]+$/;
|
|
58
59
|
var AGENT_NAME_RE = /^[a-z][a-z0-9-]{0,31}$/;
|
|
59
60
|
var MATRIX_USER_ID_RE = /^@[A-Za-z0-9._\-=/+]+:[A-Za-z0-9.\-]+$/;
|
|
60
61
|
var MATRIX_USER_LOCALPART_RE = /^@[a-z0-9._=/+\-]+$/;
|
|
@@ -317,7 +318,7 @@ function parseZooidContainer(raw) {
|
|
|
317
318
|
}
|
|
318
319
|
return out;
|
|
319
320
|
}
|
|
320
|
-
function parseTransports(raw, processEnv) {
|
|
321
|
+
function parseTransports(raw, processEnv, workstation) {
|
|
321
322
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
322
323
|
throw new Error("transports: must be a mapping with at least one entry");
|
|
323
324
|
}
|
|
@@ -328,7 +329,7 @@ function parseTransports(raw, processEnv) {
|
|
|
328
329
|
}
|
|
329
330
|
const out = {};
|
|
330
331
|
for (const name of names) {
|
|
331
|
-
out[name] = parseTransport(name, r[name], processEnv);
|
|
332
|
+
out[name] = parseTransport(name, r[name], processEnv, workstation);
|
|
332
333
|
}
|
|
333
334
|
const matrixEntries = Object.entries(out).filter(
|
|
334
335
|
(e) => e[1].type === "matrix"
|
|
@@ -364,7 +365,7 @@ function parseTransports(raw, processEnv) {
|
|
|
364
365
|
}
|
|
365
366
|
return out;
|
|
366
367
|
}
|
|
367
|
-
function parseTransport(name, raw, processEnv) {
|
|
368
|
+
function parseTransport(name, raw, processEnv, workstation) {
|
|
368
369
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
369
370
|
throw new Error(`transports.${name}: must be a mapping`);
|
|
370
371
|
}
|
|
@@ -376,16 +377,38 @@ function parseTransport(name, raw, processEnv) {
|
|
|
376
377
|
);
|
|
377
378
|
}
|
|
378
379
|
if (inferredType === "matrix") {
|
|
379
|
-
|
|
380
|
-
if (
|
|
380
|
+
const mode = r.mode ?? "appservice";
|
|
381
|
+
if (mode !== "appservice" && mode !== "client") {
|
|
382
|
+
throw new Error(
|
|
383
|
+
`transports.${name}.mode must be "appservice" or "client" (got ${JSON.stringify(r.mode)})`
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
if (r.port !== void 0 && r.advertise_url !== void 0) {
|
|
387
|
+
throw new Error(
|
|
388
|
+
`transports.${name}: set 'port' or 'advertise_url', not both`
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
let resolvedHost;
|
|
392
|
+
if (typeof r.homeserver === "string") {
|
|
381
393
|
try {
|
|
382
|
-
|
|
383
|
-
interpolateString(r.homeserver, processEnv)
|
|
384
|
-
).hostname;
|
|
385
|
-
if (host) r.user_namespace = `@.*:${host}`;
|
|
394
|
+
resolvedHost = new URL(interpolateString(r.homeserver, processEnv)).hostname;
|
|
386
395
|
} catch {
|
|
387
396
|
}
|
|
388
397
|
}
|
|
398
|
+
if (workstation !== void 0) {
|
|
399
|
+
if (!SLUG_RE.test(workstation)) {
|
|
400
|
+
throw new Error(`workstation must match /^[a-z0-9-]+$/ (got ${JSON.stringify(workstation)})`);
|
|
401
|
+
}
|
|
402
|
+
if (r.sender_localpart === void 0) r.sender_localpart = workstation;
|
|
403
|
+
if (r.user_namespace === void 0 && resolvedHost) {
|
|
404
|
+
r.user_namespace = `@${workstation}\\..*:${resolvedHost}`;
|
|
405
|
+
}
|
|
406
|
+
} else {
|
|
407
|
+
if (r.sender_localpart === void 0) r.sender_localpart = "zooid";
|
|
408
|
+
if (r.user_namespace === void 0 && resolvedHost) {
|
|
409
|
+
r.user_namespace = `@.*:${resolvedHost}`;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
389
412
|
if (r.as_token === void 0) r.as_token = "__INFER__";
|
|
390
413
|
if (r.hs_token === void 0) r.hs_token = "__INFER__";
|
|
391
414
|
const fields = [
|
|
@@ -406,8 +429,10 @@ function parseTransport(name, raw, processEnv) {
|
|
|
406
429
|
as_token: interpolateString(r.as_token, processEnv),
|
|
407
430
|
hs_token: interpolateString(r.hs_token, processEnv),
|
|
408
431
|
sender_localpart: r.sender_localpart,
|
|
409
|
-
user_namespace: r.user_namespace
|
|
432
|
+
user_namespace: r.user_namespace,
|
|
433
|
+
mode
|
|
410
434
|
};
|
|
435
|
+
if (workstation !== void 0) out.workstation = workstation;
|
|
411
436
|
if (r.port !== void 0) {
|
|
412
437
|
if (!Number.isInteger(r.port)) {
|
|
413
438
|
throw new Error(
|
|
@@ -415,6 +440,13 @@ function parseTransport(name, raw, processEnv) {
|
|
|
415
440
|
);
|
|
416
441
|
}
|
|
417
442
|
out.port = r.port;
|
|
443
|
+
} else if (r.advertise_url !== void 0) {
|
|
444
|
+
if (typeof r.advertise_url !== "string" || r.advertise_url.length === 0) {
|
|
445
|
+
throw new Error(`transports.${name}.advertise_url must be a non-empty string`);
|
|
446
|
+
}
|
|
447
|
+
out.advertise_url = r.advertise_url;
|
|
448
|
+
} else {
|
|
449
|
+
out.port = 9099;
|
|
418
450
|
}
|
|
419
451
|
if (r.space !== void 0) {
|
|
420
452
|
if (typeof r.space !== "string" || r.space.length === 0) {
|
|
@@ -520,7 +552,7 @@ function parseTransportBinding(name, entry, transports) {
|
|
|
520
552
|
throw new Error(`agents.${name}.matrix: transport must be matrix`);
|
|
521
553
|
}
|
|
522
554
|
const serverName = deriveServerName(refTransport.user_namespace);
|
|
523
|
-
const rawUserId = typeof block.user_id === "string" && block.user_id.length > 0 ? block.user_id : `@${name}`;
|
|
555
|
+
const rawUserId = typeof block.user_id === "string" && block.user_id.length > 0 ? block.user_id : refTransport.workstation ? `@${refTransport.workstation}.${name}` : `@${name}`;
|
|
524
556
|
let userId = rawUserId;
|
|
525
557
|
if (!userId.includes(":") && MATRIX_USER_LOCALPART_RE.test(userId)) {
|
|
526
558
|
userId = `${userId}:${serverName}`;
|
|
@@ -722,7 +754,17 @@ function loadZooidConfig(yamlText, opts = {}) {
|
|
|
722
754
|
}
|
|
723
755
|
const runtime = parseRuntime(r.runtime);
|
|
724
756
|
const processEnv = process.env;
|
|
725
|
-
|
|
757
|
+
let workstation;
|
|
758
|
+
if (r.workstation !== void 0) {
|
|
759
|
+
if (typeof r.workstation !== "string" || r.workstation.length === 0) {
|
|
760
|
+
throw new Error("workstation must be a non-empty string");
|
|
761
|
+
}
|
|
762
|
+
if (!SLUG_RE.test(r.workstation)) {
|
|
763
|
+
throw new Error(`workstation must match /^[a-z0-9-]+$/ (got ${JSON.stringify(r.workstation)})`);
|
|
764
|
+
}
|
|
765
|
+
workstation = r.workstation;
|
|
766
|
+
}
|
|
767
|
+
const transports = parseTransports(r.transports, processEnv, workstation);
|
|
726
768
|
const hooks = zooidHooks(r);
|
|
727
769
|
const agents = parseAgents(r.agents, runtime, transports, hooks, processEnv, opts.configDir);
|
|
728
770
|
const cfg = {
|
|
@@ -731,6 +773,7 @@ function loadZooidConfig(yamlText, opts = {}) {
|
|
|
731
773
|
agents,
|
|
732
774
|
hooks
|
|
733
775
|
};
|
|
776
|
+
if (workstation !== void 0) cfg.workstation = workstation;
|
|
734
777
|
if (r.container !== void 0 && r.container !== null) {
|
|
735
778
|
if (runtime === "local") {
|
|
736
779
|
throw new Error(
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config.ts","../src/env-interpolation.ts","../src/acp-registry.ts","../src/approval-correlator.ts"],"sourcesContent":["import { existsSync } from 'node:fs'\nimport { isAbsolute, join, resolve as pathResolve } from 'node:path'\nimport { parse } from 'yaml'\nimport type { AcpAgentSpec } from './acp-types.js'\nimport { isPreset } from '@zooid/acp-client'\nimport { interpolateEnv, interpolateString } from './env-interpolation.js'\nimport type {\n AgentConfig,\n CliFlags,\n ContainerConfig,\n HttpBinding,\n HttpTransportConfig,\n MatrixBinding,\n MatrixTransportConfig,\n MountConfig,\n RoomBinding,\n TransportConfig,\n ZooidConfig,\n ZooidContainerConfig,\n} from './types.js'\n\nexport interface LoadZooidConfigOptions {\n /**\n * Directory containing zooid.yaml. Required when any agent uses a\n * relative `container.mounts[].host` path; resolution happens at parse\n * time so the resulting `MountConfig` always carries an absolute host\n * path.\n */\n configDir?: string\n}\n\nconst AGENT_NAME_RE = /^[a-z][a-z0-9-]{0,31}$/\nconst MATRIX_USER_ID_RE = /^@[A-Za-z0-9._\\-=/+]+:[A-Za-z0-9.\\-]+$/\nconst MATRIX_USER_LOCALPART_RE = /^@[a-z0-9._=/+\\-]+$/\nconst MATRIX_ROOM_IDENT_RE = /^[#!]/\n\nfunction deriveServerName(userNamespace: string): string {\n // user_namespace is a regex like `@.*:localhost`. The part after the first\n // `:` is the server_name (strip a trailing `)` left over from a wrapped\n // group like `@(.*):localhost)`).\n const tail = userNamespace.split(':').slice(1).join(':').replace(/\\\\?\\)?$/, '')\n if (!tail) throw new Error(`user_namespace missing server_name: ${userNamespace}`)\n return tail\n}\n\nconst TRANSPORT_KINDS = ['matrix', 'http'] as const\ntype TransportKind = (typeof TRANSPORT_KINDS)[number]\n\nfunction parseAcpBlock(name: string, raw: unknown): AcpAgentSpec {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {\n throw new Error(`agents.${name}.acp: must be a mapping with either preset or command`)\n }\n const a = raw as Record<string, unknown>\n const hasPreset = a.preset !== undefined\n const hasCommand = a.command !== undefined\n if (hasPreset && hasCommand) {\n throw new Error(\n `agents.${name}.acp: specify either preset or command, not both`,\n )\n }\n if (!hasPreset && !hasCommand) {\n throw new Error(\n `agents.${name}.acp: must specify either preset or command`,\n )\n }\n if (hasPreset) {\n if (typeof a.preset !== 'string' || a.preset.length === 0) {\n throw new Error(`agents.${name}.acp.preset: must be a non-empty string`)\n }\n if (!isPreset(a.preset)) {\n throw new Error(\n `agents.${name}.acp.preset: unknown preset \"${a.preset}\"`,\n )\n }\n const out: { preset: string; model?: string } = { preset: a.preset }\n if (a.model !== undefined) {\n if (typeof a.model !== 'string' || a.model.trim().length === 0) {\n throw new Error(`agents.${name}.acp.model: must be a non-empty string`)\n }\n out.model = a.model.trim()\n }\n return out as AcpAgentSpec\n }\n if (typeof a.command !== 'string' || a.command.length === 0) {\n throw new Error(`agents.${name}.acp.command: must be a non-empty string`)\n }\n const args: string[] = []\n if (a.args !== undefined) {\n if (!Array.isArray(a.args)) {\n throw new Error(`agents.${name}.acp.args: must be an array of strings`)\n }\n for (const v of a.args) {\n if (typeof v !== 'string') {\n throw new Error(`agents.${name}.acp.args[]: must be a string`)\n }\n args.push(v)\n }\n }\n return { command: a.command, args } as AcpAgentSpec\n}\n\nfunction parseApprovalTimeout(name: string, raw: unknown): number {\n if (raw === undefined) return 0\n if (raw === 0 || raw === '0') return 0\n if (typeof raw !== 'string') {\n throw new Error(\n `agents.${name}.approval_timeout: must be a duration like \"1h\", \"15m\", \"30s\", or 0 to disable (got ${JSON.stringify(raw)})`,\n )\n }\n const m = /^(\\d+)(s|m|h)$/.exec(raw)\n if (!m) {\n throw new Error(\n `agents.${name}.approval_timeout: \"${raw}\" is not a valid duration (use \"<n>s\", \"<n>m\", or \"<n>h\")`,\n )\n }\n const n = Number(m[1])\n switch (m[2]) {\n case 's':\n return n * 1000\n case 'm':\n return n * 60_000\n case 'h':\n return n * 60 * 60_000\n }\n throw new Error('unreachable')\n}\n\nfunction parseAgentContainer(\n name: string,\n raw: unknown,\n processEnv: NodeJS.ProcessEnv,\n configDir: string | undefined,\n): ContainerConfig {\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {\n throw new Error(`agents.${name}.container must be a mapping`)\n }\n const r = raw as Record<string, unknown>\n const out: ContainerConfig = {}\n if (r.image !== undefined) {\n if (typeof r.image !== 'string' || r.image.length === 0) {\n throw new Error(`agents.${name}.container.image must be a non-empty string`)\n }\n out.image = r.image\n }\n if (r.env !== undefined && r.env !== null) {\n if (typeof r.env !== 'object' || Array.isArray(r.env)) {\n throw new Error(`agents.${name}.container.env must be a mapping`)\n }\n const rawEnv = r.env as Record<string, unknown>\n const stringEnv: Record<string, string> = {}\n for (const [k, v] of Object.entries(rawEnv)) {\n if (typeof v !== 'string') {\n throw new Error(\n `agents.${name}.container.env.${k}: must be a string (got ${typeof v})`,\n )\n }\n stringEnv[k] = v\n }\n out.env = interpolateEnv(stringEnv, processEnv, `agents.${name}.container.env`)\n }\n if (r.mounts !== undefined) {\n out.mounts = parseMountList(name, r.mounts, processEnv, configDir)\n }\n if (r.disable_mounts !== undefined) {\n out.disable_mounts = parseDisableMounts(name, r.disable_mounts)\n }\n return out\n}\n\nfunction parseMountList(\n agentName: string,\n raw: unknown,\n processEnv: NodeJS.ProcessEnv,\n configDir: string | undefined,\n): MountConfig[] {\n if (!Array.isArray(raw)) {\n throw new Error(`agents.${agentName}.container.mounts must be an array`)\n }\n const out: MountConfig[] = []\n const seenIds = new Set<string>()\n for (let i = 0; i < raw.length; i++) {\n const entry = raw[i]\n if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {\n throw new Error(`agents.${agentName}.container.mounts[${i}] must be a mapping`)\n }\n const e = entry as Record<string, unknown>\n if (e.host === undefined) {\n throw new Error(`agents.${agentName}.container.mounts[${i}].host is required`)\n }\n if (e.target === undefined) {\n throw new Error(`agents.${agentName}.container.mounts[${i}].target is required`)\n }\n if (typeof e.host !== 'string' || e.host.length === 0) {\n throw new Error(\n `agents.${agentName}.container.mounts[${i}].host must be a non-empty string`,\n )\n }\n if (typeof e.target !== 'string' || e.target.length === 0) {\n throw new Error(\n `agents.${agentName}.container.mounts[${i}].target must be a non-empty string`,\n )\n }\n const mode = e.mode ?? 'rw'\n if (mode !== 'ro' && mode !== 'rw') {\n throw new Error(\n `agents.${agentName}.container.mounts[${i}].mode must be \"ro\" or \"rw\" (got ${JSON.stringify(e.mode)})`,\n )\n }\n let id: string | undefined\n if (e.id !== undefined) {\n if (typeof e.id !== 'string' || e.id.length === 0) {\n throw new Error(\n `agents.${agentName}.container.mounts[${i}].id must be a non-empty string`,\n )\n }\n if (e.id === 'workspace') {\n throw new Error(\n `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.`,\n )\n }\n if (seenIds.has(e.id)) {\n throw new Error(\n `agents.${agentName}.container.mounts: duplicate id \"${e.id}\"`,\n )\n }\n seenIds.add(e.id)\n id = e.id\n }\n let create: boolean | undefined\n if (e.create !== undefined) {\n if (typeof e.create !== 'boolean') {\n throw new Error(\n `agents.${agentName}.container.mounts[${i}].create must be a boolean`,\n )\n }\n create = e.create\n }\n const host = resolveHostPath(\n agentName,\n i,\n interpolateString(e.host, processEnv),\n configDir,\n )\n const target = interpolateString(e.target, processEnv)\n const m: MountConfig = { host, target, mode }\n if (id !== undefined) m.id = id\n if (create !== undefined) m.create = create\n out.push(m)\n }\n return out\n}\n\nfunction resolveHostPath(\n agentName: string,\n index: number,\n host: string,\n configDir: string | undefined,\n): string {\n if (host.startsWith('~/')) {\n const home = process.env.HOME\n if (!home) {\n throw new Error(\n `agents.${agentName}.container.mounts[${index}].host: cannot expand ~ — $HOME is not set`,\n )\n }\n return `${home}/${host.slice(2)}`\n }\n if (host === '~') {\n const home = process.env.HOME\n if (!home) {\n throw new Error(\n `agents.${agentName}.container.mounts[${index}].host: cannot expand ~ — $HOME is not set`,\n )\n }\n return home\n }\n if (isAbsolute(host)) return host\n if (!configDir) {\n throw new Error(\n `agents.${agentName}.container.mounts[${index}]: relative host path \"${host}\" requires configDir (zooid.yaml directory) — pass it via loadZooidConfig(yaml, { configDir })`,\n )\n }\n return pathResolve(configDir, host)\n}\n\nfunction parseDisableMounts(agentName: string, raw: unknown): string[] {\n if (!Array.isArray(raw)) {\n throw new Error(`agents.${agentName}.container.disable_mounts must be an array of strings`)\n }\n const out: string[] = []\n for (let i = 0; i < raw.length; i++) {\n const v = raw[i]\n if (typeof v !== 'string' || v.length === 0) {\n throw new Error(\n `agents.${agentName}.container.disable_mounts[${i}] must be a non-empty string`,\n )\n }\n out.push(v)\n }\n return out\n}\n\nfunction parseZooidContainer(raw: unknown): ZooidContainerConfig {\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {\n throw new Error('container must be a mapping')\n }\n const r = raw as Record<string, unknown>\n const out: ZooidContainerConfig = {}\n if (r.env !== undefined) {\n throw new Error(\n \"Top-level 'container.env' is not supported (workforce-level env defaults are out of scope; see [ZOD043]). \" +\n 'Move env entries to per-agent container.env.',\n )\n }\n if (r.image !== undefined) {\n if (typeof r.image !== 'string' || r.image.length === 0) {\n throw new Error('container.image must be a non-empty string')\n }\n out.image = r.image\n }\n return out\n}\n\nfunction parseTransports(\n raw: unknown,\n processEnv: NodeJS.ProcessEnv,\n): Record<string, TransportConfig> {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {\n throw new Error('transports: must be a mapping with at least one entry')\n }\n const r = raw as Record<string, unknown>\n const names = Object.keys(r)\n if (names.length === 0) {\n throw new Error('transports: at least one transport must be declared')\n }\n const out: Record<string, TransportConfig> = {}\n for (const name of names) {\n out[name] = parseTransport(name, r[name], processEnv)\n }\n const matrixEntries = Object.entries(out).filter(\n (e): e is [string, MatrixTransportConfig] => e[1].type === 'matrix',\n )\n if (matrixEntries.length === 1) {\n const [, mt] = matrixEntries[0]!\n if (mt.as_token === '__INFER__') {\n const v = interpolateString('${MATRIX_AS_TOKEN}', processEnv)\n if (v.length === 0) {\n throw new Error(\n 'transports.matrix.as_token: env var MATRIX_AS_TOKEN is not set ' +\n '(set it in your shell or .env, or declare as_token explicitly in zooid.yaml)',\n )\n }\n mt.as_token = v\n }\n if (mt.hs_token === '__INFER__') {\n const v = interpolateString('${MATRIX_HS_TOKEN}', processEnv)\n if (v.length === 0) {\n throw new Error(\n 'transports.matrix.hs_token: env var MATRIX_HS_TOKEN is not set ' +\n '(set it in your shell or .env, or declare hs_token explicitly in zooid.yaml)',\n )\n }\n mt.hs_token = v\n }\n } else if (matrixEntries.length > 1) {\n for (const [tname, mt] of matrixEntries) {\n if (mt.as_token === '__INFER__' || mt.hs_token === '__INFER__') {\n throw new Error(\n `transports.${tname}: as_token / hs_token must be set explicitly when more than one matrix transport is declared ` +\n `(no sensible default env var across multiple transports)`,\n )\n }\n }\n }\n return out\n}\n\nfunction parseTransport(\n name: string,\n raw: unknown,\n processEnv: NodeJS.ProcessEnv,\n): TransportConfig {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {\n throw new Error(`transports.${name}: must be a mapping`)\n }\n const r = raw as Record<string, unknown>\n const inferredType =\n r.type ?? (name === 'matrix' || name === 'http' ? name : undefined)\n if (inferredType !== 'matrix' && inferredType !== 'http') {\n throw new Error(\n `transports.${name}.type must be \"matrix\" or \"http\" (got ${JSON.stringify(r.type)})`,\n )\n }\n if (inferredType === 'matrix') {\n if (r.sender_localpart === undefined) r.sender_localpart = 'zooid'\n if (r.user_namespace === undefined && typeof r.homeserver === 'string') {\n try {\n const host = new URL(\n interpolateString(r.homeserver as string, processEnv),\n ).hostname\n if (host) r.user_namespace = `@.*:${host}`\n } catch {\n // Fall through: the required-fields loop will fire its \"must be a\n // non-empty string\" error, which is the same message today's parser\n // would emit for a bad homeserver URL.\n }\n }\n if (r.as_token === undefined) r.as_token = '__INFER__'\n if (r.hs_token === undefined) r.hs_token = '__INFER__'\n const fields = [\n 'homeserver',\n 'as_token',\n 'hs_token',\n 'sender_localpart',\n 'user_namespace',\n ] as const\n for (const f of fields) {\n if (typeof r[f] !== 'string' || (r[f] as string).length === 0) {\n throw new Error(`transports.${name}.${f} must be a non-empty string`)\n }\n }\n const out: MatrixTransportConfig = {\n type: 'matrix',\n homeserver: interpolateString(r.homeserver as string, processEnv),\n as_token: interpolateString(r.as_token as string, processEnv),\n hs_token: interpolateString(r.hs_token as string, processEnv),\n sender_localpart: r.sender_localpart as string,\n user_namespace: r.user_namespace as string,\n }\n if (r.port !== undefined) {\n if (!Number.isInteger(r.port)) {\n throw new Error(\n `transports.${name}.port must be an integer (got ${JSON.stringify(r.port)})`,\n )\n }\n out.port = r.port as number\n }\n if (r.space !== undefined) {\n if (typeof r.space !== 'string' || r.space.length === 0) {\n throw new Error(\n `transports.${name}.space must be a non-empty string (got ${JSON.stringify(r.space)})`,\n )\n }\n out.space = r.space\n }\n return out\n }\n // type: 'http'\n const port = (r.port ?? 8080) as number\n if (!Number.isInteger(port)) {\n throw new Error(`transports.${name}.port must be an integer (got ${JSON.stringify(port)})`)\n }\n return { type: 'http', port }\n}\n\nfunction parseRoomBinding(path: string, raw: unknown, serverName: string): RoomBinding {\n function normalizeAlias(alias: string): string {\n if (alias.length === 0) {\n throw new Error(`${path}: must be a non-empty alias`)\n }\n if (!MATRIX_ROOM_IDENT_RE.test(alias)) {\n throw new Error(\n `${path}: must start with '#' or '!' (got ${JSON.stringify(alias)})`,\n )\n }\n return alias.includes(':') ? alias : `${alias}:${serverName}`\n }\n if (typeof raw === 'string') {\n return { alias: normalizeAlias(raw) }\n }\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {\n throw new Error(`${path}: must be a string or { alias, power_level } object`)\n }\n const r = raw as Record<string, unknown>\n if (typeof r.alias !== 'string' || r.alias.length === 0) {\n throw new Error(`${path}.alias: must be a non-empty string`)\n }\n const out: RoomBinding = { alias: normalizeAlias(r.alias) }\n if (r.power_level !== undefined) {\n if (typeof r.power_level !== 'number' || !Number.isInteger(r.power_level)) {\n throw new Error(\n `${path}.power_level: must be an integer (got ${JSON.stringify(r.power_level)})`,\n )\n }\n out.powerLevel = r.power_level\n }\n return out\n}\n\nfunction parseTransportBinding(\n name: string,\n entry: Record<string, unknown>,\n transports: Record<string, TransportConfig>,\n): { matrix?: MatrixBinding; http?: HttpBinding } {\n const present = TRANSPORT_KINDS.filter(\n (k) => entry[k] !== undefined && entry[k] !== null,\n )\n if (present.length === 0) {\n throw new Error(\n `agents.${name}: must declare exactly one transport-kind block ` +\n `(e.g. 'matrix:' or 'http:'). Saw none.`,\n )\n }\n if (present.length > 1) {\n throw new Error(\n `agents.${name}: must declare exactly one transport-kind block. ` +\n `Saw: ${present.join(', ')}. To run \"the same agent\" on two transports, ` +\n `declare two agents (e.g. ${name}-matrix and ${name}-http).`,\n )\n }\n const kind = present[0] as TransportKind\n const blockRaw = entry[kind]\n if (typeof blockRaw !== 'object' || blockRaw === null || Array.isArray(blockRaw)) {\n throw new Error(`agents.${name}.${kind}: must be a mapping`)\n }\n const block = blockRaw as Record<string, unknown>\n let refName: string\n if (typeof block.transport === 'string' && block.transport.length > 0) {\n refName = block.transport\n } else {\n const matches = Object.entries(transports).filter(\n ([, t]) => t.type === kind,\n )\n if (matches.length === 0) {\n throw new Error(\n `agents.${name}.${kind}: no transport of type ${kind} declared (add one under transports:)`,\n )\n }\n if (matches.length > 1) {\n throw new Error(\n `agents.${name}.${kind}.transport is required when more than one ${kind} transport is declared ` +\n `(saw: ${matches.map(([n]) => n).join(', ')})`,\n )\n }\n refName = matches[0]![0]\n }\n const refTransport = transports[refName]\n if (!refTransport) {\n throw new Error(\n `agents.${name}.${kind}.transport \"${refName}\" is not declared in transports`,\n )\n }\n if (refTransport.type !== kind) {\n throw new Error(\n `agents.${name}.${kind} references transport \"${refName}\" of type: ${refTransport.type}. ` +\n `Block name and referenced transport's type must match.`,\n )\n }\n\n if (kind === 'matrix') {\n if (refTransport.type !== 'matrix') {\n throw new Error(`agents.${name}.matrix: transport must be matrix`)\n }\n const serverName = deriveServerName(refTransport.user_namespace)\n\n const rawUserId =\n typeof block.user_id === 'string' && block.user_id.length > 0\n ? block.user_id\n : `@${name}`\n let userId = rawUserId\n if (!userId.includes(':') && MATRIX_USER_LOCALPART_RE.test(userId)) {\n userId = `${userId}:${serverName}`\n }\n if (!MATRIX_USER_ID_RE.test(userId)) {\n throw new Error(\n `agents.${name}.matrix.user_id must look like @localpart:server (got ${JSON.stringify(block.user_id)})`,\n )\n }\n\n if (!Array.isArray(block.rooms) || block.rooms.length === 0) {\n throw new Error(`agents.${name}.matrix.rooms is required and must be a non-empty array`)\n }\n const rooms: RoomBinding[] = []\n for (let i = 0; i < block.rooms.length; i++) {\n rooms.push(parseRoomBinding(`agents.${name}.matrix.rooms[${i}]`, block.rooms[i], serverName))\n }\n\n let displayName: string | undefined\n if (block.display_name !== undefined) {\n if (typeof block.display_name !== 'string') {\n throw new Error(\n `agents.${name}.matrix.display_name must be a string (got ${JSON.stringify(block.display_name)})`,\n )\n }\n const trimmed = block.display_name.trim()\n if (trimmed.length === 0) {\n throw new Error(`agents.${name}.matrix.display_name must be non-empty after trim`)\n }\n if (trimmed.length > 256) {\n throw new Error(`agents.${name}.matrix.display_name must be 256 characters or fewer`)\n }\n displayName = trimmed\n }\n\n const tr = block.trigger ?? 'mention'\n if (tr !== 'mention' && tr !== 'any') {\n throw new Error(\n `agents.${name}.matrix.trigger must be \"mention\" or \"any\" (got ${JSON.stringify(tr)})`,\n )\n }\n const matrix: MatrixBinding = {\n transport: refName,\n user_id: userId,\n rooms,\n trigger: tr,\n }\n if (displayName !== undefined) matrix.display_name = displayName\n return { matrix }\n }\n // kind === 'http'\n return { http: { transport: refName } }\n}\n\nfunction parseAgents(\n raw: unknown,\n runtime: 'local' | 'docker' | 'podman',\n transports: Record<string, TransportConfig>,\n daemonHooks: { pre_turn?: string; post_turn?: string },\n processEnv: NodeJS.ProcessEnv,\n configDir: string | undefined,\n): Record<string, AgentConfig> {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {\n throw new Error('agents: must be a mapping')\n }\n const entries = Object.entries(raw as Record<string, unknown>)\n if (entries.length === 0) {\n throw new Error('agents: must have at least one entry')\n }\n const result: Record<string, AgentConfig> = {}\n for (const [name, val] of entries) {\n if (!AGENT_NAME_RE.test(name)) {\n throw new Error(`agents.${name}: name must match /^[a-z][a-z0-9-]{0,31}$/`)\n }\n if (!val || typeof val !== 'object' || Array.isArray(val)) {\n throw new Error(`agents.${name} must be a mapping`)\n }\n const entry = val as Record<string, unknown>\n let workdir: string\n if (entry.workdir === undefined) {\n workdir = `./agents/${name}`\n } else if (typeof entry.workdir !== 'string' || entry.workdir.length === 0) {\n throw new Error(`agents.${name}.workdir must be a non-empty string`)\n } else {\n workdir = entry.workdir\n }\n\n if (entry.adapter !== undefined) {\n throw new Error(\n `agents.${name}: \"adapter\" is no longer supported; use \"acp\" — see epics/003-ZOD025-acp-migration/SPEC.md`,\n )\n }\n\n if (entry.acp === undefined) {\n throw new Error(`agents.${name}: missing required \"acp\" block`)\n }\n const acp = parseAcpBlock(name, entry.acp)\n const approval_timeout_ms = parseApprovalTimeout(name, entry.approval_timeout)\n\n // Reject legacy fields up front with pointers to [ZOD043].\n if (entry.docker !== undefined) {\n throw new Error(\n `agents.${name}.docker is no longer supported. ` +\n `Move 'image' to agents.${name}.container.image, and 'forward_env' entries to ` +\n `agents.${name}.container.env with \\${VAR} interpolation. See [ZOD043].`,\n )\n }\n if (typeof entry.transport === 'string') {\n throw new Error(\n `agents.${name}.transport (string) is no longer supported at the agent level. ` +\n `Move it inside a transport-kind block, e.g.:\\n` +\n ` matrix:\\n transport: <name>\\n user_id: \"@...\"\\n rooms: [...]\\n` +\n `See [ZOD043].`,\n )\n }\n for (const k of ['matrix_user_id', 'rooms', 'trigger'] as const) {\n if (entry[k] !== undefined) {\n throw new Error(\n `agents.${name}.${k} is no longer supported as a flat field. ` +\n `Move it inside a 'matrix:' block on the agent. See [ZOD043].`,\n )\n }\n }\n\n const agentHooks: AgentConfig['hooks'] = {}\n if (daemonHooks.pre_turn !== undefined) agentHooks.pre_turn = daemonHooks.pre_turn\n if (daemonHooks.post_turn !== undefined) agentHooks.post_turn = daemonHooks.post_turn\n if (entry.hooks !== undefined && entry.hooks !== null) {\n if (typeof entry.hooks !== 'object' || Array.isArray(entry.hooks)) {\n throw new Error(`agents.${name}.hooks must be a mapping`)\n }\n const h = entry.hooks as Record<string, unknown>\n if (Object.prototype.hasOwnProperty.call(h, 'pre_turn')) {\n if (typeof h.pre_turn === 'string') agentHooks.pre_turn = h.pre_turn\n else delete agentHooks.pre_turn\n }\n if (Object.prototype.hasOwnProperty.call(h, 'post_turn')) {\n if (typeof h.post_turn === 'string') agentHooks.post_turn = h.post_turn\n else delete agentHooks.post_turn\n }\n }\n\n let containerBlock: ContainerConfig | undefined\n if (entry.container !== undefined && entry.container !== null) {\n if (runtime === 'local') {\n // Under runtime: local, the parser only accepts mounts/disable_mounts\n // (which the compose layer ignores). image/env stay rejected because\n // they would silently lie: there's no container and the host inherits\n // the daemon's full process.env regardless.\n if (typeof entry.container !== 'object' || entry.container === null || Array.isArray(entry.container)) {\n throw new Error(`agents.${name}.container must be a mapping`)\n }\n const c = entry.container as Record<string, unknown>\n const disallowed = Object.keys(c).filter((k) => k !== 'mounts' && k !== 'disable_mounts')\n if (disallowed.length > 0) {\n throw new Error(\n `agents.${name}.container.${disallowed[0]} is only valid when runtime is 'docker' or 'podman'. ` +\n `runtime: local spawns agents as host child processes — there is no container, ` +\n `so 'image' is inert and 'env' would silently lie (the agent inherits the daemon's ` +\n `full process.env regardless). 'mounts' and 'disable_mounts' are accepted under ` +\n `runtime: local but ignored at compose time.`,\n )\n }\n }\n containerBlock = parseAgentContainer(name, entry.container, processEnv, configDir)\n }\n\n const binding = parseTransportBinding(name, entry, transports)\n\n const agentCfg: AgentConfig = {\n name,\n workdir,\n hooks: agentHooks,\n acp,\n approval_timeout_ms,\n }\n if (containerBlock) agentCfg.container = containerBlock\n if (binding.matrix) agentCfg.matrix = binding.matrix\n if (binding.http) agentCfg.http = binding.http\n result[name] = agentCfg\n }\n return result\n}\n\nfunction parseRuntime(raw: unknown): 'local' | 'docker' | 'podman' {\n const runtime = raw ?? 'docker'\n if (runtime !== 'local' && runtime !== 'docker' && runtime !== 'podman') {\n throw new Error(`runtime must be \"local\", \"docker\", or \"podman\" (got \"${runtime}\")`)\n }\n return runtime\n}\n\nfunction zooidHooks(raw: Record<string, unknown>): { pre_turn?: string; post_turn?: string } {\n const out: { pre_turn?: string; post_turn?: string } = {}\n if (raw.hooks && typeof raw.hooks === 'object') {\n const h = raw.hooks as Record<string, unknown>\n if (typeof h.pre_turn === 'string') out.pre_turn = h.pre_turn\n if (typeof h.post_turn === 'string') out.post_turn = h.post_turn\n }\n return out\n}\n\nexport function loadZooidConfig(\n yamlText: string,\n opts: LoadZooidConfigOptions = {},\n): ZooidConfig {\n const raw = parse(yamlText) ?? {}\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {\n throw new Error('zooid.yaml must be a YAML object')\n }\n const r = raw as Record<string, unknown>\n\n if (r.transport !== undefined) {\n throw new Error(\n 'zooid.yaml: top-level \"transport:\" is no longer supported; declare entries under \"transports:\" instead',\n )\n }\n if (r.matrix !== undefined) {\n throw new Error(\n 'zooid.yaml: top-level \"matrix:\" is no longer supported; move it under \"transports.<name>: { type: matrix, ... }\"',\n )\n }\n if (r.workdir !== undefined) {\n throw new Error(\n 'top-level workdir is not supported; define agents: { <name>: { workdir: ... } } instead',\n )\n }\n if (r.docker !== undefined) {\n throw new Error(\n \"Top-level 'docker' block is no longer supported. \" +\n \"Move 'image' to top-level 'container.image'. See [ZOD043].\",\n )\n }\n if (r.agents === undefined) {\n throw new Error('agents: is required — zooid.yaml must define at least one agent')\n }\n\n const runtime = parseRuntime(r.runtime)\n const processEnv = process.env\n const transports = parseTransports(r.transports, processEnv)\n const hooks = zooidHooks(r)\n const agents = parseAgents(r.agents, runtime, transports, hooks, processEnv, opts.configDir)\n\n const cfg: ZooidConfig = {\n runtime,\n transports,\n agents,\n hooks,\n }\n if (r.container !== undefined && r.container !== null) {\n if (runtime === 'local') {\n throw new Error(\n \"container is only valid when runtime is 'docker' or 'podman'. \" +\n 'runtime: local does not run agents in containers; image is ignored. See [ZOD043].',\n )\n }\n cfg.container = parseZooidContainer(r.container)\n }\n return cfg\n}\n\nexport function findTransport(\n cfg: ZooidConfig,\n name: string,\n): TransportConfig | undefined {\n return cfg.transports[name]\n}\n\nexport function findMatrixTransport(\n cfg: ZooidConfig,\n): { name: string; transport: MatrixTransportConfig } | null {\n const matrices = Object.entries(cfg.transports).filter(\n (e): e is [string, MatrixTransportConfig] => e[1].type === 'matrix',\n )\n if (matrices.length === 0) return null\n if (matrices.length > 1) {\n throw new Error(\n `findMatrixTransport: multiple matrix transports declared (${matrices\n .map((m) => m[0])\n .join(', ')}). Per-agent matrix routing is not supported yet.`,\n )\n }\n const [name, transport] = matrices[0]!\n return { name, transport }\n}\n\nexport function findHttpTransport(\n cfg: ZooidConfig,\n): { name: string; transport: HttpTransportConfig } | null {\n const https = Object.entries(cfg.transports).filter(\n (e): e is [string, HttpTransportConfig] => e[1].type === 'http',\n )\n if (https.length === 0) return null\n if (https.length > 1) {\n throw new Error(\n `findHttpTransport: multiple http transports declared (${https\n .map((h) => h[0])\n .join(', ')}). Per-agent http routing is not supported yet.`,\n )\n }\n const [name, transport] = https[0]!\n return { name, transport }\n}\n\nexport interface FoundConfigFile {\n path: string\n}\n\nexport function findConfigFile(cwd: string): FoundConfigFile | null {\n const z = join(cwd, 'zooid.yaml')\n if (existsSync(z)) return { path: z }\n const legacy = join(cwd, 'workforce.yaml')\n if (existsSync(legacy)) {\n throw new Error(\n `workforce.yaml is no longer supported. Rename it to zooid.yaml. See [ZOD045].`,\n )\n }\n return null\n}\n\nexport function mergeCliFlags(base: ZooidConfig, flags: CliFlags): ZooidConfig {\n const runtimeFlag = flags.runtime as 'local' | 'docker' | 'podman' | undefined\n if (\n runtimeFlag !== undefined &&\n runtimeFlag !== 'local' &&\n runtimeFlag !== 'docker' &&\n runtimeFlag !== 'podman'\n ) {\n throw new Error(`runtime must be \"local\", \"docker\", or \"podman\" (got \"${flags.runtime}\")`)\n }\n const runtime = runtimeFlag ?? base.runtime\n const merged: ZooidConfig = {\n runtime,\n transports: base.transports,\n agents: base.agents,\n hooks: { ...base.hooks },\n }\n if (runtime === 'docker' || runtime === 'podman') {\n const image = flags.image ?? base.container?.image\n if (image !== undefined) {\n merged.container = { image }\n } else if (base.container !== undefined) {\n merged.container = { ...base.container }\n }\n }\n return merged\n}\n","import dotenvExpand from 'dotenv-expand'\n\n/**\n * Matches compose-style env references inside a value string:\n * `${NAME}`, `$NAME`, `${NAME:-default}`, `${NAME-default}`,\n * `${NAME:+alt}`, `${NAME+alt}`.\n *\n * The captured group is always the referenced variable name.\n */\nconst REF_RE =\n /\\$\\{([A-Za-z_][A-Za-z0-9_]*)(?::?[-+][^}]*)?\\}|\\$([A-Za-z_][A-Za-z0-9_]*)/g\n\nexport class EnvInterpolationError extends Error {}\n\nconst isDenied = (name: string): boolean =>\n name === 'ZOOID_TOKEN' || name.startsWith('ZOOID_')\n\n/**\n * Run compose-style interpolation over a `{ KEY: literal-or-${ref} }` map.\n *\n * - Rejects keys in the `ZOOID_*` namespace.\n * - Rejects any `${ZOOID_*}` reference, including inside composed values\n * (e.g. `\"prefix-${ZOOID_INTERNAL}\"`).\n * - Otherwise delegates to `dotenv-expand` for the actual substitution,\n * preserving compose semantics (missing → empty string,\n * `${VAR:-default}`, `${VAR-default}`, `$$` escape).\n */\nexport function interpolateEnv(\n parsed: Record<string, string>,\n processEnv: NodeJS.ProcessEnv,\n scope: string,\n): Record<string, string> {\n for (const [key, val] of Object.entries(parsed)) {\n if (isDenied(key)) {\n throw new EnvInterpolationError(\n `${scope}.${key}: keys in the ZOOID_* namespace are not allowed`,\n )\n }\n if (typeof val !== 'string') {\n throw new EnvInterpolationError(\n `${scope}.${key}: env values must be strings (got ${typeof val})`,\n )\n }\n REF_RE.lastIndex = 0\n let m: RegExpExecArray | null\n while ((m = REF_RE.exec(val)) !== null) {\n const ref = (m[1] ?? m[2]) as string\n if (isDenied(ref)) {\n throw new EnvInterpolationError(\n `${scope}.${key}: references to ZOOID_* vars are not allowed (saw \\${${ref}})`,\n )\n }\n }\n }\n // Process each value through interpolateString. Per-key processing avoids\n // dotenv-expand's \"processEnv shadows parsed\" behaviour, which would let the\n // daemon's `LOG_LEVEL=debug` overwrite a literal `LOG_LEVEL: info` declared in\n // zooid.yaml. References still resolve against the full processEnv.\n const out: Record<string, string> = {}\n for (const [key, val] of Object.entries(parsed)) {\n out[key] = interpolateString(val, processEnv)\n }\n return out\n}\n\n/**\n * Run compose-style interpolation over a single string value (e.g. a\n * transport's `as_token`). No denylist — transports legitimately reference\n * `ZOOID_*` tokens for the daemon itself (see [ZOD043] §Denylist note).\n */\nexport function interpolateString(\n value: string,\n processEnv: NodeJS.ProcessEnv,\n): string {\n // Fast path: literals with no `$` need no expansion. Skipping the dotenv-expand\n // call also avoids the library's \"processEnv shadows parsed\" merge behaviour,\n // which can leak unrelated env vars through a sentinel key.\n if (!value.includes('$')) return value\n const sentinel = '__zooid_interp_v1__'\n const env: Record<string, string> = {}\n for (const [k, v] of Object.entries(processEnv)) {\n if (typeof v === 'string') env[k] = v\n }\n delete env[sentinel]\n const result = dotenvExpand.expand({\n parsed: { [sentinel]: value },\n processEnv: env,\n })\n return result.parsed?.[sentinel] ?? ''\n}\n","import { mkdirSync } from 'node:fs'\nimport { join } from 'node:path'\nimport {\n AcpClient,\n resolvePreset,\n type AgentEvent,\n type ApprovalDecision,\n type ApprovalRequest,\n type PromptInput,\n type PromptResult,\n type TapEvent,\n} from '@zooid/acp-client'\nimport type { AcpAgentSpec, AcpMount, AcpRuntime } from './acp-types.js'\nimport type { AgentConfig } from './types.js'\nimport type {\n ApprovalCorrelator,\n RegisteredApproval,\n} from './approval-correlator.js'\n\nexport type AcpRegistryEventHandler = (\n agentName: string,\n event: AgentEvent,\n) => void\nexport type AcpRegistryApprovalHandler = (\n agentName: string,\n req: ApprovalRequest,\n) => Promise<ApprovalDecision>\n\n/**\n * Daemon-side surface of the ACP agent fleet. The transport (HTTP) consumes\n * this; the CLI builds it via `buildAcpRegistry`. Long-lived: one\n * `AcpClient` per agent, kept alive across prompts.\n */\nexport interface AcpRegistry {\n hasAgent(name: string): boolean\n /** Whether an agent has a transport-context provider attached. */\n hasContextSpawn(name: string): boolean\n /** Per-agent approval timeout from zooid.yaml. 0 means no timeout. */\n getApprovalTimeoutMs(name: string): number\n ensureSession(name: string, threadId: string, channelId?: string): Promise<string>\n /** Drop the in-memory session for (agent, threadId). Next prompt re-creates one. */\n endSession(name: string, threadId: string): void\n prompt(name: string, input: PromptInput): Promise<PromptResult>\n /**\n * Cancel an in-flight prompt for (agent, sessionId). Sends `session/cancel`\n * via the underlying AcpClient and resolves any pending approvals with\n * `decision: 'cancel'`. Idempotent.\n */\n cancelSession(name: string, sessionId: string): Promise<void>\n stopAll(): Promise<void>\n /** Set by the transport. Receives every ACP event from any agent. */\n onEvent: AcpRegistryEventHandler\n /** Set by the transport. Resolves permission requests. */\n onApprovalRequest: AcpRegistryApprovalHandler\n}\n\nexport interface AcpAgentRegistryOptions {\n runtime: AcpRuntime\n agents: Record<string, AgentConfig>\n /** Per-agent env passed to each `AcpClient`'s spawn spec. */\n env?: Record<string, Record<string, string>>\n /** Per-agent container image. Used by DockerAcpRuntime; ignored by LocalAcpRuntime. */\n image?: Record<string, string | undefined>\n /** Initial event handler (the transport may overwrite at app creation). */\n onEvent?: AcpRegistryEventHandler\n /** Initial approval handler (the transport may overwrite at app creation). */\n onApprovalRequest?: AcpRegistryApprovalHandler\n /**\n * Optional correlator: when set, the registry's default\n * `onApprovalRequest` registers each request on the correlator (with the\n * agent's `approval_timeout_ms`) and returns the registered handle's\n * `decisionPromise`. Transports listen on the correlator's `'registered'`\n * + `'timeout'` events to drive the SSE wire and accept HTTP decisions.\n */\n approvals?: ApprovalCorrelator\n /** Called whenever the correlator-backed handler registers an approval. */\n onApprovalRegistered?: (approval: RegisteredApproval) => void\n /**\n * Optional observability tap. Forwarded to each AcpClient so the\n * unfiltered ACP protocol stream + turn-boundary events are visible to\n * the host (e.g. the dev CLI capturing them to disk).\n */\n onTap?: (agentName: string, event: TapEvent) => void\n /**\n * Root directory under which each agent gets a per-agent state dir\n * (`<agentsDir>/<agentName>/`). Used by the AcpClient session store to\n * persist ACP `sessionId`s across daemon restarts. Optional: when unset,\n * session continuity across restarts is disabled.\n */\n agentsDir?: string\n /**\n * Per-agent factory that returns a `mcpServers[]` entry for the\n * `zooid-context` MCP server. Forwarded to each AcpClient. Agents bound to\n * transports without a context provider (e.g. HTTP) have no entry here.\n */\n contextSpawns?: Record<string, ContextSpawnFactory | undefined>\n /**\n * Per-agent resolved bind-mount list. Threaded into the AcpClient's spawn\n * spec; honoured by the docker runtime, ignored by the local runtime.\n */\n mounts?: Record<string, AcpMount[]>\n /**\n * Per-agent list of host directories to `mkdir -p` before the first\n * `runtime.spawn` for that agent. Subset of mount entries with `create: true`.\n */\n mkdirOnSpawn?: Record<string, string[]>\n /**\n * Per-agent override for the spawn-spec `cwd`. Set to e.g. `/workspace`\n * when the workspace mount is active; falls back to `agent.workdir`.\n */\n cwd?: Record<string, string>\n}\n\nexport type ContextSpawnFactory = (\n threadId: string,\n channelId?: string,\n) => Promise<{\n name: 'zooid-context'\n command: string\n args: string[]\n env: Array<{ name: string; value: string }>\n}>\n\nexport class AcpAgentRegistry implements AcpRegistry {\n readonly opts: AcpAgentRegistryOptions\n private readonly clients = new Map<string, AcpClient>()\n\n onEvent: AcpRegistryEventHandler\n onApprovalRequest: AcpRegistryApprovalHandler\n\n constructor(opts: AcpAgentRegistryOptions) {\n this.opts = opts\n this.onEvent = opts.onEvent ?? (() => {})\n if (opts.onApprovalRequest) {\n this.onApprovalRequest = opts.onApprovalRequest\n } else if (opts.approvals) {\n const correlator = opts.approvals\n this.onApprovalRequest = async (name, req) => {\n const cfg = this.opts.agents[name]\n const handle = correlator.register(name, req.sessionId, req, {\n timeoutMs: cfg?.approval_timeout_ms ?? 0,\n })\n this.opts.onApprovalRegistered?.(handle)\n return handle.decisionPromise\n }\n } else {\n this.onApprovalRequest = async () => ({ decision: 'cancel' })\n }\n }\n\n hasAgent(name: string): boolean {\n return Object.prototype.hasOwnProperty.call(this.opts.agents, name)\n }\n\n hasContextSpawn(name: string): boolean {\n return Boolean(this.opts.contextSpawns?.[name])\n }\n\n resolveSpawnEnv(name: string): Record<string, string> {\n return this.opts.env?.[name] ?? {}\n }\n\n resolveSpawnImage(name: string): string | undefined {\n return this.opts.image?.[name]\n }\n\n resolveSpawnMounts(name: string): AcpMount[] {\n return this.opts.mounts?.[name] ?? []\n }\n\n resolveSpawnCwd(name: string): string {\n return (\n this.opts.cwd?.[name] ??\n this.opts.agents[name]?.workdir ??\n process.cwd()\n )\n }\n\n agentNames(): string[] {\n return Object.keys(this.opts.agents)\n }\n\n getApprovalTimeoutMs(name: string): number {\n return this.opts.agents[name]?.approval_timeout_ms ?? 0\n }\n\n async ensureSession(name: string, threadId: string, channelId?: string): Promise<string> {\n if (!this.hasAgent(name)) throw new Error(`unknown agent: ${name}`)\n const client = await this.ensureClient(name)\n return client.ensureSession(threadId, channelId)\n }\n\n endSession(name: string, threadId: string): void {\n if (!this.hasAgent(name)) return\n const client = this.clients.get(name)\n client?.endSession(threadId)\n }\n\n async cancelSession(name: string, sessionId: string): Promise<void> {\n if (!this.hasAgent(name)) return\n const client = this.clients.get(name)\n // Always nudge the correlator first so any pending approvals resolve with\n // 'cancel' regardless of whether the client is alive or already stopped.\n this.opts.approvals?.cancelSession(sessionId)\n if (!client) return\n try {\n await client.cancel(sessionId)\n } catch (err) {\n // ACP cancel is a notification; failures here are typically transport\n // errors after the agent has already exited.\n console.warn(`[acp:${name}] cancel(${sessionId}) failed:`, err)\n }\n }\n\n async prompt(name: string, input: PromptInput): Promise<PromptResult> {\n if (!this.hasAgent(name)) throw new Error(`unknown agent: ${name}`)\n const client = await this.ensureClient(name)\n return client.prompt(input)\n }\n\n async stopAll(): Promise<void> {\n await Promise.allSettled(\n [...this.clients.values()].map((c) => c.stop()),\n )\n this.clients.clear()\n }\n\n private async ensureClient(name: string): Promise<AcpClient> {\n const existing = this.clients.get(name)\n if (existing) return existing\n const cfg = this.opts.agents[name]\n if (!cfg.acp) throw new Error(`agents.${name}: missing acp block`)\n const spawn = resolveAcpAgentSpec(cfg.acp)\n for (const dir of this.opts.mkdirOnSpawn?.[name] ?? []) {\n mkdirSync(dir, { recursive: true })\n }\n const client = new AcpClient({\n agent: {\n id: name,\n command: spawn.command,\n args: spawn.args,\n env: this.opts.env?.[name],\n cwd: this.resolveSpawnCwd(name),\n image: this.opts.image?.[name],\n mounts: this.resolveSpawnMounts(name),\n },\n agentDataDir: this.opts.agentsDir ? join(this.opts.agentsDir, name) : undefined,\n runtime: this.opts.runtime,\n onEvent: (e) => this.onEvent(name, e),\n onApprovalRequest: (req) => this.onApprovalRequest(name, req),\n onTap: this.opts.onTap ? (e) => this.opts.onTap!(name, e) : undefined,\n contextSpawn: this.opts.contextSpawns?.[name],\n })\n await client.start()\n this.clients.set(name, client)\n return client\n }\n}\n\nexport function resolveAcpAgentSpec(spec: AcpAgentSpec): {\n command: string\n args: string[]\n} {\n if ('preset' in spec && spec.preset) {\n return resolvePreset(spec.preset, { model: spec.model })\n }\n if ('command' in spec && spec.command) {\n return { command: spec.command, args: spec.args ?? [] }\n }\n throw new Error('AcpAgentSpec: must specify either preset or command')\n}\n","import { EventEmitter } from 'node:events'\nimport { randomUUID } from 'node:crypto'\nimport type {\n ApprovalDecision,\n ApprovalRequest,\n} from '@zooid/acp-client'\n\nexport interface RegisteredApproval {\n approvalId: string\n agentName: string\n sessionId: string\n toolCallId: string\n toolKind?: string\n toolTitle?: string\n toolInput?: unknown\n options: ApprovalRequest['options']\n decisionPromise: Promise<ApprovalDecision>\n}\n\nexport interface RegisterOptions {\n /** Wall-clock timeout in ms. 0 = no timeout. */\n timeoutMs?: number\n}\n\ninterface PendingEntry extends RegisteredApproval {\n resolve(decision: ApprovalDecision): void\n timer?: ReturnType<typeof setTimeout>\n}\n\n/**\n * Correlates ACP approval requests (mid-prompt, originating in `AcpClient`)\n * with HTTP/transport-side decisions. The transport listens to:\n *\n * - `'registered'` — fires after `register()` so the transport can emit\n * `approval.request` on the right SSE stream.\n * - `'timeout'` — fires when an entry is auto-cancelled by the timer\n * so the transport can emit `approval.timeout`.\n */\nexport class ApprovalCorrelator extends EventEmitter {\n private readonly pending = new Map<string, PendingEntry>()\n private readonly bySession = new Map<string, Set<string>>()\n\n register(\n agentName: string,\n sessionId: string,\n req: ApprovalRequest,\n opts: RegisterOptions = {},\n ): RegisteredApproval {\n const approvalId = randomUUID()\n let resolve!: (d: ApprovalDecision) => void\n const decisionPromise = new Promise<ApprovalDecision>((r) => {\n resolve = r\n })\n const entry: PendingEntry = {\n approvalId,\n agentName,\n sessionId,\n toolCallId: req.toolCallId,\n toolKind: req.toolKind,\n toolTitle: req.toolTitle,\n toolInput: req.toolInput,\n options: req.options,\n decisionPromise,\n resolve,\n }\n if (opts.timeoutMs && opts.timeoutMs > 0) {\n entry.timer = setTimeout(() => {\n if (this.pending.get(approvalId) !== entry) return\n entry.resolve({ decision: 'cancel' })\n this.pending.delete(approvalId)\n this.bySession.get(sessionId)?.delete(approvalId)\n this.emit('timeout', { approvalId, sessionId, agentName })\n }, opts.timeoutMs)\n entry.timer.unref?.()\n }\n this.pending.set(approvalId, entry)\n let set = this.bySession.get(sessionId)\n if (!set) {\n set = new Set()\n this.bySession.set(sessionId, set)\n }\n set.add(approvalId)\n this.emit('registered', this.toPublic(entry))\n return this.toPublic(entry)\n }\n\n resolve(\n sessionId: string,\n approvalId: string,\n decision: ApprovalDecision,\n ): boolean {\n const entry = this.pending.get(approvalId)\n if (!entry || entry.sessionId !== sessionId) return false\n if (entry.timer) clearTimeout(entry.timer)\n entry.resolve(decision)\n this.pending.delete(approvalId)\n this.bySession.get(sessionId)?.delete(approvalId)\n return true\n }\n\n cancelSession(sessionId: string): void {\n const ids = this.bySession.get(sessionId)\n if (!ids) return\n for (const id of [...ids]) {\n const entry = this.pending.get(id)\n if (entry) {\n if (entry.timer) clearTimeout(entry.timer)\n entry.resolve({ decision: 'cancel' })\n this.pending.delete(id)\n }\n }\n this.bySession.delete(sessionId)\n }\n\n listPending(sessionId: string): RegisteredApproval[] {\n const ids = this.bySession.get(sessionId)\n if (!ids) return []\n const out: RegisteredApproval[] = []\n for (const id of ids) {\n const entry = this.pending.get(id)\n if (entry) out.push(this.toPublic(entry))\n }\n return out\n }\n\n size(): number {\n return this.pending.size\n }\n\n private toPublic(entry: PendingEntry): RegisteredApproval {\n return {\n approvalId: entry.approvalId,\n agentName: entry.agentName,\n sessionId: entry.sessionId,\n toolCallId: entry.toolCallId,\n toolKind: entry.toolKind,\n toolTitle: entry.toolTitle,\n toolInput: entry.toolInput,\n options: entry.options,\n decisionPromise: entry.decisionPromise,\n }\n }\n}\n"],"mappings":"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","names":["join"]}
|
|
1
|
+
{"version":3,"sources":["../src/config.ts","../src/env-interpolation.ts","../src/acp-registry.ts","../src/approval-correlator.ts"],"sourcesContent":["import { existsSync } from 'node:fs'\nimport { isAbsolute, join, resolve as pathResolve } from 'node:path'\nimport { parse } from 'yaml'\nimport type { AcpAgentSpec } from './acp-types.js'\nimport { isPreset } from '@zooid/acp-client'\nimport { interpolateEnv, interpolateString } from './env-interpolation.js'\nimport type {\n AgentConfig,\n CliFlags,\n ContainerConfig,\n HttpBinding,\n HttpTransportConfig,\n MatrixBinding,\n MatrixTransportConfig,\n MountConfig,\n RoomBinding,\n TransportConfig,\n ZooidConfig,\n ZooidContainerConfig,\n} from './types.js'\n\nconst SLUG_RE = /^[a-z0-9-]+$/\n\nexport interface LoadZooidConfigOptions {\n /**\n * Directory containing zooid.yaml. Required when any agent uses a\n * relative `container.mounts[].host` path; resolution happens at parse\n * time so the resulting `MountConfig` always carries an absolute host\n * path.\n */\n configDir?: string\n}\n\nconst AGENT_NAME_RE = /^[a-z][a-z0-9-]{0,31}$/\nconst MATRIX_USER_ID_RE = /^@[A-Za-z0-9._\\-=/+]+:[A-Za-z0-9.\\-]+$/\nconst MATRIX_USER_LOCALPART_RE = /^@[a-z0-9._=/+\\-]+$/\nconst MATRIX_ROOM_IDENT_RE = /^[#!]/\n\nfunction deriveServerName(userNamespace: string): string {\n // user_namespace is a regex like `@.*:localhost`. The part after the first\n // `:` is the server_name (strip a trailing `)` left over from a wrapped\n // group like `@(.*):localhost)`).\n const tail = userNamespace.split(':').slice(1).join(':').replace(/\\\\?\\)?$/, '')\n if (!tail) throw new Error(`user_namespace missing server_name: ${userNamespace}`)\n return tail\n}\n\nconst TRANSPORT_KINDS = ['matrix', 'http'] as const\ntype TransportKind = (typeof TRANSPORT_KINDS)[number]\n\nfunction parseAcpBlock(name: string, raw: unknown): AcpAgentSpec {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {\n throw new Error(`agents.${name}.acp: must be a mapping with either preset or command`)\n }\n const a = raw as Record<string, unknown>\n const hasPreset = a.preset !== undefined\n const hasCommand = a.command !== undefined\n if (hasPreset && hasCommand) {\n throw new Error(\n `agents.${name}.acp: specify either preset or command, not both`,\n )\n }\n if (!hasPreset && !hasCommand) {\n throw new Error(\n `agents.${name}.acp: must specify either preset or command`,\n )\n }\n if (hasPreset) {\n if (typeof a.preset !== 'string' || a.preset.length === 0) {\n throw new Error(`agents.${name}.acp.preset: must be a non-empty string`)\n }\n if (!isPreset(a.preset)) {\n throw new Error(\n `agents.${name}.acp.preset: unknown preset \"${a.preset}\"`,\n )\n }\n const out: { preset: string; model?: string } = { preset: a.preset }\n if (a.model !== undefined) {\n if (typeof a.model !== 'string' || a.model.trim().length === 0) {\n throw new Error(`agents.${name}.acp.model: must be a non-empty string`)\n }\n out.model = a.model.trim()\n }\n return out as AcpAgentSpec\n }\n if (typeof a.command !== 'string' || a.command.length === 0) {\n throw new Error(`agents.${name}.acp.command: must be a non-empty string`)\n }\n const args: string[] = []\n if (a.args !== undefined) {\n if (!Array.isArray(a.args)) {\n throw new Error(`agents.${name}.acp.args: must be an array of strings`)\n }\n for (const v of a.args) {\n if (typeof v !== 'string') {\n throw new Error(`agents.${name}.acp.args[]: must be a string`)\n }\n args.push(v)\n }\n }\n return { command: a.command, args } as AcpAgentSpec\n}\n\nfunction parseApprovalTimeout(name: string, raw: unknown): number {\n if (raw === undefined) return 0\n if (raw === 0 || raw === '0') return 0\n if (typeof raw !== 'string') {\n throw new Error(\n `agents.${name}.approval_timeout: must be a duration like \"1h\", \"15m\", \"30s\", or 0 to disable (got ${JSON.stringify(raw)})`,\n )\n }\n const m = /^(\\d+)(s|m|h)$/.exec(raw)\n if (!m) {\n throw new Error(\n `agents.${name}.approval_timeout: \"${raw}\" is not a valid duration (use \"<n>s\", \"<n>m\", or \"<n>h\")`,\n )\n }\n const n = Number(m[1])\n switch (m[2]) {\n case 's':\n return n * 1000\n case 'm':\n return n * 60_000\n case 'h':\n return n * 60 * 60_000\n }\n throw new Error('unreachable')\n}\n\nfunction parseAgentContainer(\n name: string,\n raw: unknown,\n processEnv: NodeJS.ProcessEnv,\n configDir: string | undefined,\n): ContainerConfig {\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {\n throw new Error(`agents.${name}.container must be a mapping`)\n }\n const r = raw as Record<string, unknown>\n const out: ContainerConfig = {}\n if (r.image !== undefined) {\n if (typeof r.image !== 'string' || r.image.length === 0) {\n throw new Error(`agents.${name}.container.image must be a non-empty string`)\n }\n out.image = r.image\n }\n if (r.env !== undefined && r.env !== null) {\n if (typeof r.env !== 'object' || Array.isArray(r.env)) {\n throw new Error(`agents.${name}.container.env must be a mapping`)\n }\n const rawEnv = r.env as Record<string, unknown>\n const stringEnv: Record<string, string> = {}\n for (const [k, v] of Object.entries(rawEnv)) {\n if (typeof v !== 'string') {\n throw new Error(\n `agents.${name}.container.env.${k}: must be a string (got ${typeof v})`,\n )\n }\n stringEnv[k] = v\n }\n out.env = interpolateEnv(stringEnv, processEnv, `agents.${name}.container.env`)\n }\n if (r.mounts !== undefined) {\n out.mounts = parseMountList(name, r.mounts, processEnv, configDir)\n }\n if (r.disable_mounts !== undefined) {\n out.disable_mounts = parseDisableMounts(name, r.disable_mounts)\n }\n return out\n}\n\nfunction parseMountList(\n agentName: string,\n raw: unknown,\n processEnv: NodeJS.ProcessEnv,\n configDir: string | undefined,\n): MountConfig[] {\n if (!Array.isArray(raw)) {\n throw new Error(`agents.${agentName}.container.mounts must be an array`)\n }\n const out: MountConfig[] = []\n const seenIds = new Set<string>()\n for (let i = 0; i < raw.length; i++) {\n const entry = raw[i]\n if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {\n throw new Error(`agents.${agentName}.container.mounts[${i}] must be a mapping`)\n }\n const e = entry as Record<string, unknown>\n if (e.host === undefined) {\n throw new Error(`agents.${agentName}.container.mounts[${i}].host is required`)\n }\n if (e.target === undefined) {\n throw new Error(`agents.${agentName}.container.mounts[${i}].target is required`)\n }\n if (typeof e.host !== 'string' || e.host.length === 0) {\n throw new Error(\n `agents.${agentName}.container.mounts[${i}].host must be a non-empty string`,\n )\n }\n if (typeof e.target !== 'string' || e.target.length === 0) {\n throw new Error(\n `agents.${agentName}.container.mounts[${i}].target must be a non-empty string`,\n )\n }\n const mode = e.mode ?? 'rw'\n if (mode !== 'ro' && mode !== 'rw') {\n throw new Error(\n `agents.${agentName}.container.mounts[${i}].mode must be \"ro\" or \"rw\" (got ${JSON.stringify(e.mode)})`,\n )\n }\n let id: string | undefined\n if (e.id !== undefined) {\n if (typeof e.id !== 'string' || e.id.length === 0) {\n throw new Error(\n `agents.${agentName}.container.mounts[${i}].id must be a non-empty string`,\n )\n }\n if (e.id === 'workspace') {\n throw new Error(\n `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.`,\n )\n }\n if (seenIds.has(e.id)) {\n throw new Error(\n `agents.${agentName}.container.mounts: duplicate id \"${e.id}\"`,\n )\n }\n seenIds.add(e.id)\n id = e.id\n }\n let create: boolean | undefined\n if (e.create !== undefined) {\n if (typeof e.create !== 'boolean') {\n throw new Error(\n `agents.${agentName}.container.mounts[${i}].create must be a boolean`,\n )\n }\n create = e.create\n }\n const host = resolveHostPath(\n agentName,\n i,\n interpolateString(e.host, processEnv),\n configDir,\n )\n const target = interpolateString(e.target, processEnv)\n const m: MountConfig = { host, target, mode }\n if (id !== undefined) m.id = id\n if (create !== undefined) m.create = create\n out.push(m)\n }\n return out\n}\n\nfunction resolveHostPath(\n agentName: string,\n index: number,\n host: string,\n configDir: string | undefined,\n): string {\n if (host.startsWith('~/')) {\n const home = process.env.HOME\n if (!home) {\n throw new Error(\n `agents.${agentName}.container.mounts[${index}].host: cannot expand ~ — $HOME is not set`,\n )\n }\n return `${home}/${host.slice(2)}`\n }\n if (host === '~') {\n const home = process.env.HOME\n if (!home) {\n throw new Error(\n `agents.${agentName}.container.mounts[${index}].host: cannot expand ~ — $HOME is not set`,\n )\n }\n return home\n }\n if (isAbsolute(host)) return host\n if (!configDir) {\n throw new Error(\n `agents.${agentName}.container.mounts[${index}]: relative host path \"${host}\" requires configDir (zooid.yaml directory) — pass it via loadZooidConfig(yaml, { configDir })`,\n )\n }\n return pathResolve(configDir, host)\n}\n\nfunction parseDisableMounts(agentName: string, raw: unknown): string[] {\n if (!Array.isArray(raw)) {\n throw new Error(`agents.${agentName}.container.disable_mounts must be an array of strings`)\n }\n const out: string[] = []\n for (let i = 0; i < raw.length; i++) {\n const v = raw[i]\n if (typeof v !== 'string' || v.length === 0) {\n throw new Error(\n `agents.${agentName}.container.disable_mounts[${i}] must be a non-empty string`,\n )\n }\n out.push(v)\n }\n return out\n}\n\nfunction parseZooidContainer(raw: unknown): ZooidContainerConfig {\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {\n throw new Error('container must be a mapping')\n }\n const r = raw as Record<string, unknown>\n const out: ZooidContainerConfig = {}\n if (r.env !== undefined) {\n throw new Error(\n \"Top-level 'container.env' is not supported (workforce-level env defaults are out of scope; see [ZOD043]). \" +\n 'Move env entries to per-agent container.env.',\n )\n }\n if (r.image !== undefined) {\n if (typeof r.image !== 'string' || r.image.length === 0) {\n throw new Error('container.image must be a non-empty string')\n }\n out.image = r.image\n }\n return out\n}\n\nfunction parseTransports(\n raw: unknown,\n processEnv: NodeJS.ProcessEnv,\n workstation?: string,\n): Record<string, TransportConfig> {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {\n throw new Error('transports: must be a mapping with at least one entry')\n }\n const r = raw as Record<string, unknown>\n const names = Object.keys(r)\n if (names.length === 0) {\n throw new Error('transports: at least one transport must be declared')\n }\n const out: Record<string, TransportConfig> = {}\n for (const name of names) {\n out[name] = parseTransport(name, r[name], processEnv, workstation)\n }\n const matrixEntries = Object.entries(out).filter(\n (e): e is [string, MatrixTransportConfig] => e[1].type === 'matrix',\n )\n if (matrixEntries.length === 1) {\n const [, mt] = matrixEntries[0]!\n if (mt.as_token === '__INFER__') {\n const v = interpolateString('${MATRIX_AS_TOKEN}', processEnv)\n if (v.length === 0) {\n throw new Error(\n 'transports.matrix.as_token: env var MATRIX_AS_TOKEN is not set ' +\n '(set it in your shell or .env, or declare as_token explicitly in zooid.yaml)',\n )\n }\n mt.as_token = v\n }\n if (mt.hs_token === '__INFER__') {\n const v = interpolateString('${MATRIX_HS_TOKEN}', processEnv)\n if (v.length === 0) {\n throw new Error(\n 'transports.matrix.hs_token: env var MATRIX_HS_TOKEN is not set ' +\n '(set it in your shell or .env, or declare hs_token explicitly in zooid.yaml)',\n )\n }\n mt.hs_token = v\n }\n } else if (matrixEntries.length > 1) {\n for (const [tname, mt] of matrixEntries) {\n if (mt.as_token === '__INFER__' || mt.hs_token === '__INFER__') {\n throw new Error(\n `transports.${tname}: as_token / hs_token must be set explicitly when more than one matrix transport is declared ` +\n `(no sensible default env var across multiple transports)`,\n )\n }\n }\n }\n return out\n}\n\nfunction parseTransport(\n name: string,\n raw: unknown,\n processEnv: NodeJS.ProcessEnv,\n workstation?: string,\n): TransportConfig {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {\n throw new Error(`transports.${name}: must be a mapping`)\n }\n const r = raw as Record<string, unknown>\n const inferredType =\n r.type ?? (name === 'matrix' || name === 'http' ? name : undefined)\n if (inferredType !== 'matrix' && inferredType !== 'http') {\n throw new Error(\n `transports.${name}.type must be \"matrix\" or \"http\" (got ${JSON.stringify(r.type)})`,\n )\n }\n if (inferredType === 'matrix') {\n // Validate mode first so we can give a clear error.\n const mode = r.mode ?? 'appservice'\n if (mode !== 'appservice' && mode !== 'client') {\n throw new Error(\n `transports.${name}.mode must be \"appservice\" or \"client\" (got ${JSON.stringify(r.mode)})`,\n )\n }\n\n // port xor advertise_url\n if (r.port !== undefined && r.advertise_url !== undefined) {\n throw new Error(\n `transports.${name}: set 'port' or 'advertise_url', not both`,\n )\n }\n\n // Workstation-derived defaults: sender_localpart and user_namespace\n let resolvedHost: string | undefined\n if (typeof r.homeserver === 'string') {\n try {\n resolvedHost = new URL(interpolateString(r.homeserver as string, processEnv)).hostname\n } catch {\n // fall through — required-field check below fires the error\n }\n }\n\n if (workstation !== undefined) {\n if (!SLUG_RE.test(workstation)) {\n throw new Error(`workstation must match /^[a-z0-9-]+$/ (got ${JSON.stringify(workstation)})`)\n }\n if (r.sender_localpart === undefined) r.sender_localpart = workstation\n if (r.user_namespace === undefined && resolvedHost) {\n r.user_namespace = `@${workstation}\\\\..*:${resolvedHost}`\n }\n } else {\n if (r.sender_localpart === undefined) r.sender_localpart = 'zooid'\n if (r.user_namespace === undefined && resolvedHost) {\n r.user_namespace = `@.*:${resolvedHost}`\n }\n }\n\n if (r.as_token === undefined) r.as_token = '__INFER__'\n if (r.hs_token === undefined) r.hs_token = '__INFER__'\n const fields = [\n 'homeserver',\n 'as_token',\n 'hs_token',\n 'sender_localpart',\n 'user_namespace',\n ] as const\n for (const f of fields) {\n if (typeof r[f] !== 'string' || (r[f] as string).length === 0) {\n throw new Error(`transports.${name}.${f} must be a non-empty string`)\n }\n }\n const out: MatrixTransportConfig = {\n type: 'matrix',\n homeserver: interpolateString(r.homeserver as string, processEnv),\n as_token: interpolateString(r.as_token as string, processEnv),\n hs_token: interpolateString(r.hs_token as string, processEnv),\n sender_localpart: r.sender_localpart as string,\n user_namespace: r.user_namespace as string,\n mode: mode as 'appservice' | 'client',\n }\n if (workstation !== undefined) out.workstation = workstation\n\n if (r.port !== undefined) {\n if (!Number.isInteger(r.port)) {\n throw new Error(\n `transports.${name}.port must be an integer (got ${JSON.stringify(r.port)})`,\n )\n }\n out.port = r.port as number\n } else if (r.advertise_url !== undefined) {\n if (typeof r.advertise_url !== 'string' || r.advertise_url.length === 0) {\n throw new Error(`transports.${name}.advertise_url must be a non-empty string`)\n }\n out.advertise_url = r.advertise_url as string\n } else {\n // Neither set — default to co-located port\n out.port = 9099\n }\n\n if (r.space !== undefined) {\n if (typeof r.space !== 'string' || r.space.length === 0) {\n throw new Error(\n `transports.${name}.space must be a non-empty string (got ${JSON.stringify(r.space)})`,\n )\n }\n out.space = r.space\n }\n return out\n }\n // type: 'http'\n const port = (r.port ?? 8080) as number\n if (!Number.isInteger(port)) {\n throw new Error(`transports.${name}.port must be an integer (got ${JSON.stringify(port)})`)\n }\n return { type: 'http', port }\n}\n\nfunction parseRoomBinding(path: string, raw: unknown, serverName: string): RoomBinding {\n function normalizeAlias(alias: string): string {\n if (alias.length === 0) {\n throw new Error(`${path}: must be a non-empty alias`)\n }\n if (!MATRIX_ROOM_IDENT_RE.test(alias)) {\n throw new Error(\n `${path}: must start with '#' or '!' (got ${JSON.stringify(alias)})`,\n )\n }\n return alias.includes(':') ? alias : `${alias}:${serverName}`\n }\n if (typeof raw === 'string') {\n return { alias: normalizeAlias(raw) }\n }\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {\n throw new Error(`${path}: must be a string or { alias, power_level } object`)\n }\n const r = raw as Record<string, unknown>\n if (typeof r.alias !== 'string' || r.alias.length === 0) {\n throw new Error(`${path}.alias: must be a non-empty string`)\n }\n const out: RoomBinding = { alias: normalizeAlias(r.alias) }\n if (r.power_level !== undefined) {\n if (typeof r.power_level !== 'number' || !Number.isInteger(r.power_level)) {\n throw new Error(\n `${path}.power_level: must be an integer (got ${JSON.stringify(r.power_level)})`,\n )\n }\n out.powerLevel = r.power_level\n }\n return out\n}\n\nfunction parseTransportBinding(\n name: string,\n entry: Record<string, unknown>,\n transports: Record<string, TransportConfig>,\n): { matrix?: MatrixBinding; http?: HttpBinding } {\n const present = TRANSPORT_KINDS.filter(\n (k) => entry[k] !== undefined && entry[k] !== null,\n )\n if (present.length === 0) {\n throw new Error(\n `agents.${name}: must declare exactly one transport-kind block ` +\n `(e.g. 'matrix:' or 'http:'). Saw none.`,\n )\n }\n if (present.length > 1) {\n throw new Error(\n `agents.${name}: must declare exactly one transport-kind block. ` +\n `Saw: ${present.join(', ')}. To run \"the same agent\" on two transports, ` +\n `declare two agents (e.g. ${name}-matrix and ${name}-http).`,\n )\n }\n const kind = present[0] as TransportKind\n const blockRaw = entry[kind]\n if (typeof blockRaw !== 'object' || blockRaw === null || Array.isArray(blockRaw)) {\n throw new Error(`agents.${name}.${kind}: must be a mapping`)\n }\n const block = blockRaw as Record<string, unknown>\n let refName: string\n if (typeof block.transport === 'string' && block.transport.length > 0) {\n refName = block.transport\n } else {\n const matches = Object.entries(transports).filter(\n ([, t]) => t.type === kind,\n )\n if (matches.length === 0) {\n throw new Error(\n `agents.${name}.${kind}: no transport of type ${kind} declared (add one under transports:)`,\n )\n }\n if (matches.length > 1) {\n throw new Error(\n `agents.${name}.${kind}.transport is required when more than one ${kind} transport is declared ` +\n `(saw: ${matches.map(([n]) => n).join(', ')})`,\n )\n }\n refName = matches[0]![0]\n }\n const refTransport = transports[refName]\n if (!refTransport) {\n throw new Error(\n `agents.${name}.${kind}.transport \"${refName}\" is not declared in transports`,\n )\n }\n if (refTransport.type !== kind) {\n throw new Error(\n `agents.${name}.${kind} references transport \"${refName}\" of type: ${refTransport.type}. ` +\n `Block name and referenced transport's type must match.`,\n )\n }\n\n if (kind === 'matrix') {\n if (refTransport.type !== 'matrix') {\n throw new Error(`agents.${name}.matrix: transport must be matrix`)\n }\n const serverName = deriveServerName(refTransport.user_namespace)\n\n // When the referenced transport is workstationed (ZOD063), an agent with no\n // explicit user_id gets the workstation-scoped MXID @{workstation}.{name},\n // so it lands inside the AS's exclusive @{workstation}\\..* namespace.\n // Without this it would default to @{name} and be rejected (M_EXCLUSIVE).\n const rawUserId =\n typeof block.user_id === 'string' && block.user_id.length > 0\n ? block.user_id\n : refTransport.workstation\n ? `@${refTransport.workstation}.${name}`\n : `@${name}`\n let userId = rawUserId\n if (!userId.includes(':') && MATRIX_USER_LOCALPART_RE.test(userId)) {\n userId = `${userId}:${serverName}`\n }\n if (!MATRIX_USER_ID_RE.test(userId)) {\n throw new Error(\n `agents.${name}.matrix.user_id must look like @localpart:server (got ${JSON.stringify(block.user_id)})`,\n )\n }\n\n if (!Array.isArray(block.rooms) || block.rooms.length === 0) {\n throw new Error(`agents.${name}.matrix.rooms is required and must be a non-empty array`)\n }\n const rooms: RoomBinding[] = []\n for (let i = 0; i < block.rooms.length; i++) {\n rooms.push(parseRoomBinding(`agents.${name}.matrix.rooms[${i}]`, block.rooms[i], serverName))\n }\n\n let displayName: string | undefined\n if (block.display_name !== undefined) {\n if (typeof block.display_name !== 'string') {\n throw new Error(\n `agents.${name}.matrix.display_name must be a string (got ${JSON.stringify(block.display_name)})`,\n )\n }\n const trimmed = block.display_name.trim()\n if (trimmed.length === 0) {\n throw new Error(`agents.${name}.matrix.display_name must be non-empty after trim`)\n }\n if (trimmed.length > 256) {\n throw new Error(`agents.${name}.matrix.display_name must be 256 characters or fewer`)\n }\n displayName = trimmed\n }\n\n const tr = block.trigger ?? 'mention'\n if (tr !== 'mention' && tr !== 'any') {\n throw new Error(\n `agents.${name}.matrix.trigger must be \"mention\" or \"any\" (got ${JSON.stringify(tr)})`,\n )\n }\n const matrix: MatrixBinding = {\n transport: refName,\n user_id: userId,\n rooms,\n trigger: tr,\n }\n if (displayName !== undefined) matrix.display_name = displayName\n return { matrix }\n }\n // kind === 'http'\n return { http: { transport: refName } }\n}\n\nfunction parseAgents(\n raw: unknown,\n runtime: 'local' | 'docker' | 'podman',\n transports: Record<string, TransportConfig>,\n daemonHooks: { pre_turn?: string; post_turn?: string },\n processEnv: NodeJS.ProcessEnv,\n configDir: string | undefined,\n): Record<string, AgentConfig> {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {\n throw new Error('agents: must be a mapping')\n }\n const entries = Object.entries(raw as Record<string, unknown>)\n if (entries.length === 0) {\n throw new Error('agents: must have at least one entry')\n }\n const result: Record<string, AgentConfig> = {}\n for (const [name, val] of entries) {\n if (!AGENT_NAME_RE.test(name)) {\n throw new Error(`agents.${name}: name must match /^[a-z][a-z0-9-]{0,31}$/`)\n }\n if (!val || typeof val !== 'object' || Array.isArray(val)) {\n throw new Error(`agents.${name} must be a mapping`)\n }\n const entry = val as Record<string, unknown>\n let workdir: string\n if (entry.workdir === undefined) {\n workdir = `./agents/${name}`\n } else if (typeof entry.workdir !== 'string' || entry.workdir.length === 0) {\n throw new Error(`agents.${name}.workdir must be a non-empty string`)\n } else {\n workdir = entry.workdir\n }\n\n if (entry.adapter !== undefined) {\n throw new Error(\n `agents.${name}: \"adapter\" is no longer supported; use \"acp\" — see epics/003-ZOD025-acp-migration/SPEC.md`,\n )\n }\n\n if (entry.acp === undefined) {\n throw new Error(`agents.${name}: missing required \"acp\" block`)\n }\n const acp = parseAcpBlock(name, entry.acp)\n const approval_timeout_ms = parseApprovalTimeout(name, entry.approval_timeout)\n\n // Reject legacy fields up front with pointers to [ZOD043].\n if (entry.docker !== undefined) {\n throw new Error(\n `agents.${name}.docker is no longer supported. ` +\n `Move 'image' to agents.${name}.container.image, and 'forward_env' entries to ` +\n `agents.${name}.container.env with \\${VAR} interpolation. See [ZOD043].`,\n )\n }\n if (typeof entry.transport === 'string') {\n throw new Error(\n `agents.${name}.transport (string) is no longer supported at the agent level. ` +\n `Move it inside a transport-kind block, e.g.:\\n` +\n ` matrix:\\n transport: <name>\\n user_id: \"@...\"\\n rooms: [...]\\n` +\n `See [ZOD043].`,\n )\n }\n for (const k of ['matrix_user_id', 'rooms', 'trigger'] as const) {\n if (entry[k] !== undefined) {\n throw new Error(\n `agents.${name}.${k} is no longer supported as a flat field. ` +\n `Move it inside a 'matrix:' block on the agent. See [ZOD043].`,\n )\n }\n }\n\n const agentHooks: AgentConfig['hooks'] = {}\n if (daemonHooks.pre_turn !== undefined) agentHooks.pre_turn = daemonHooks.pre_turn\n if (daemonHooks.post_turn !== undefined) agentHooks.post_turn = daemonHooks.post_turn\n if (entry.hooks !== undefined && entry.hooks !== null) {\n if (typeof entry.hooks !== 'object' || Array.isArray(entry.hooks)) {\n throw new Error(`agents.${name}.hooks must be a mapping`)\n }\n const h = entry.hooks as Record<string, unknown>\n if (Object.prototype.hasOwnProperty.call(h, 'pre_turn')) {\n if (typeof h.pre_turn === 'string') agentHooks.pre_turn = h.pre_turn\n else delete agentHooks.pre_turn\n }\n if (Object.prototype.hasOwnProperty.call(h, 'post_turn')) {\n if (typeof h.post_turn === 'string') agentHooks.post_turn = h.post_turn\n else delete agentHooks.post_turn\n }\n }\n\n let containerBlock: ContainerConfig | undefined\n if (entry.container !== undefined && entry.container !== null) {\n if (runtime === 'local') {\n // Under runtime: local, the parser only accepts mounts/disable_mounts\n // (which the compose layer ignores). image/env stay rejected because\n // they would silently lie: there's no container and the host inherits\n // the daemon's full process.env regardless.\n if (typeof entry.container !== 'object' || entry.container === null || Array.isArray(entry.container)) {\n throw new Error(`agents.${name}.container must be a mapping`)\n }\n const c = entry.container as Record<string, unknown>\n const disallowed = Object.keys(c).filter((k) => k !== 'mounts' && k !== 'disable_mounts')\n if (disallowed.length > 0) {\n throw new Error(\n `agents.${name}.container.${disallowed[0]} is only valid when runtime is 'docker' or 'podman'. ` +\n `runtime: local spawns agents as host child processes — there is no container, ` +\n `so 'image' is inert and 'env' would silently lie (the agent inherits the daemon's ` +\n `full process.env regardless). 'mounts' and 'disable_mounts' are accepted under ` +\n `runtime: local but ignored at compose time.`,\n )\n }\n }\n containerBlock = parseAgentContainer(name, entry.container, processEnv, configDir)\n }\n\n const binding = parseTransportBinding(name, entry, transports)\n\n const agentCfg: AgentConfig = {\n name,\n workdir,\n hooks: agentHooks,\n acp,\n approval_timeout_ms,\n }\n if (containerBlock) agentCfg.container = containerBlock\n if (binding.matrix) agentCfg.matrix = binding.matrix\n if (binding.http) agentCfg.http = binding.http\n result[name] = agentCfg\n }\n return result\n}\n\nfunction parseRuntime(raw: unknown): 'local' | 'docker' | 'podman' {\n const runtime = raw ?? 'docker'\n if (runtime !== 'local' && runtime !== 'docker' && runtime !== 'podman') {\n throw new Error(`runtime must be \"local\", \"docker\", or \"podman\" (got \"${runtime}\")`)\n }\n return runtime\n}\n\nfunction zooidHooks(raw: Record<string, unknown>): { pre_turn?: string; post_turn?: string } {\n const out: { pre_turn?: string; post_turn?: string } = {}\n if (raw.hooks && typeof raw.hooks === 'object') {\n const h = raw.hooks as Record<string, unknown>\n if (typeof h.pre_turn === 'string') out.pre_turn = h.pre_turn\n if (typeof h.post_turn === 'string') out.post_turn = h.post_turn\n }\n return out\n}\n\nexport function loadZooidConfig(\n yamlText: string,\n opts: LoadZooidConfigOptions = {},\n): ZooidConfig {\n const raw = parse(yamlText) ?? {}\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {\n throw new Error('zooid.yaml must be a YAML object')\n }\n const r = raw as Record<string, unknown>\n\n if (r.transport !== undefined) {\n throw new Error(\n 'zooid.yaml: top-level \"transport:\" is no longer supported; declare entries under \"transports:\" instead',\n )\n }\n if (r.matrix !== undefined) {\n throw new Error(\n 'zooid.yaml: top-level \"matrix:\" is no longer supported; move it under \"transports.<name>: { type: matrix, ... }\"',\n )\n }\n if (r.workdir !== undefined) {\n throw new Error(\n 'top-level workdir is not supported; define agents: { <name>: { workdir: ... } } instead',\n )\n }\n if (r.docker !== undefined) {\n throw new Error(\n \"Top-level 'docker' block is no longer supported. \" +\n \"Move 'image' to top-level 'container.image'. See [ZOD043].\",\n )\n }\n if (r.agents === undefined) {\n throw new Error('agents: is required — zooid.yaml must define at least one agent')\n }\n\n const runtime = parseRuntime(r.runtime)\n const processEnv = process.env\n\n let workstation: string | undefined\n if (r.workstation !== undefined) {\n if (typeof r.workstation !== 'string' || r.workstation.length === 0) {\n throw new Error('workstation must be a non-empty string')\n }\n if (!SLUG_RE.test(r.workstation)) {\n throw new Error(`workstation must match /^[a-z0-9-]+$/ (got ${JSON.stringify(r.workstation)})`)\n }\n workstation = r.workstation\n }\n\n const transports = parseTransports(r.transports, processEnv, workstation)\n const hooks = zooidHooks(r)\n const agents = parseAgents(r.agents, runtime, transports, hooks, processEnv, opts.configDir)\n\n const cfg: ZooidConfig = {\n runtime,\n transports,\n agents,\n hooks,\n }\n if (workstation !== undefined) cfg.workstation = workstation\n if (r.container !== undefined && r.container !== null) {\n if (runtime === 'local') {\n throw new Error(\n \"container is only valid when runtime is 'docker' or 'podman'. \" +\n 'runtime: local does not run agents in containers; image is ignored. See [ZOD043].',\n )\n }\n cfg.container = parseZooidContainer(r.container)\n }\n return cfg\n}\n\nexport function findTransport(\n cfg: ZooidConfig,\n name: string,\n): TransportConfig | undefined {\n return cfg.transports[name]\n}\n\nexport function findMatrixTransport(\n cfg: ZooidConfig,\n): { name: string; transport: MatrixTransportConfig } | null {\n const matrices = Object.entries(cfg.transports).filter(\n (e): e is [string, MatrixTransportConfig] => e[1].type === 'matrix',\n )\n if (matrices.length === 0) return null\n if (matrices.length > 1) {\n throw new Error(\n `findMatrixTransport: multiple matrix transports declared (${matrices\n .map((m) => m[0])\n .join(', ')}). Per-agent matrix routing is not supported yet.`,\n )\n }\n const [name, transport] = matrices[0]!\n return { name, transport }\n}\n\nexport function findHttpTransport(\n cfg: ZooidConfig,\n): { name: string; transport: HttpTransportConfig } | null {\n const https = Object.entries(cfg.transports).filter(\n (e): e is [string, HttpTransportConfig] => e[1].type === 'http',\n )\n if (https.length === 0) return null\n if (https.length > 1) {\n throw new Error(\n `findHttpTransport: multiple http transports declared (${https\n .map((h) => h[0])\n .join(', ')}). Per-agent http routing is not supported yet.`,\n )\n }\n const [name, transport] = https[0]!\n return { name, transport }\n}\n\nexport interface FoundConfigFile {\n path: string\n}\n\nexport function findConfigFile(cwd: string): FoundConfigFile | null {\n const z = join(cwd, 'zooid.yaml')\n if (existsSync(z)) return { path: z }\n const legacy = join(cwd, 'workforce.yaml')\n if (existsSync(legacy)) {\n throw new Error(\n `workforce.yaml is no longer supported. Rename it to zooid.yaml. See [ZOD045].`,\n )\n }\n return null\n}\n\nexport function mergeCliFlags(base: ZooidConfig, flags: CliFlags): ZooidConfig {\n const runtimeFlag = flags.runtime as 'local' | 'docker' | 'podman' | undefined\n if (\n runtimeFlag !== undefined &&\n runtimeFlag !== 'local' &&\n runtimeFlag !== 'docker' &&\n runtimeFlag !== 'podman'\n ) {\n throw new Error(`runtime must be \"local\", \"docker\", or \"podman\" (got \"${flags.runtime}\")`)\n }\n const runtime = runtimeFlag ?? base.runtime\n const merged: ZooidConfig = {\n runtime,\n transports: base.transports,\n agents: base.agents,\n hooks: { ...base.hooks },\n }\n if (runtime === 'docker' || runtime === 'podman') {\n const image = flags.image ?? base.container?.image\n if (image !== undefined) {\n merged.container = { image }\n } else if (base.container !== undefined) {\n merged.container = { ...base.container }\n }\n }\n return merged\n}\n","import dotenvExpand from 'dotenv-expand'\n\n/**\n * Matches compose-style env references inside a value string:\n * `${NAME}`, `$NAME`, `${NAME:-default}`, `${NAME-default}`,\n * `${NAME:+alt}`, `${NAME+alt}`.\n *\n * The captured group is always the referenced variable name.\n */\nconst REF_RE =\n /\\$\\{([A-Za-z_][A-Za-z0-9_]*)(?::?[-+][^}]*)?\\}|\\$([A-Za-z_][A-Za-z0-9_]*)/g\n\nexport class EnvInterpolationError extends Error {}\n\nconst isDenied = (name: string): boolean =>\n name === 'ZOOID_TOKEN' || name.startsWith('ZOOID_')\n\n/**\n * Run compose-style interpolation over a `{ KEY: literal-or-${ref} }` map.\n *\n * - Rejects keys in the `ZOOID_*` namespace.\n * - Rejects any `${ZOOID_*}` reference, including inside composed values\n * (e.g. `\"prefix-${ZOOID_INTERNAL}\"`).\n * - Otherwise delegates to `dotenv-expand` for the actual substitution,\n * preserving compose semantics (missing → empty string,\n * `${VAR:-default}`, `${VAR-default}`, `$$` escape).\n */\nexport function interpolateEnv(\n parsed: Record<string, string>,\n processEnv: NodeJS.ProcessEnv,\n scope: string,\n): Record<string, string> {\n for (const [key, val] of Object.entries(parsed)) {\n if (isDenied(key)) {\n throw new EnvInterpolationError(\n `${scope}.${key}: keys in the ZOOID_* namespace are not allowed`,\n )\n }\n if (typeof val !== 'string') {\n throw new EnvInterpolationError(\n `${scope}.${key}: env values must be strings (got ${typeof val})`,\n )\n }\n REF_RE.lastIndex = 0\n let m: RegExpExecArray | null\n while ((m = REF_RE.exec(val)) !== null) {\n const ref = (m[1] ?? m[2]) as string\n if (isDenied(ref)) {\n throw new EnvInterpolationError(\n `${scope}.${key}: references to ZOOID_* vars are not allowed (saw \\${${ref}})`,\n )\n }\n }\n }\n // Process each value through interpolateString. Per-key processing avoids\n // dotenv-expand's \"processEnv shadows parsed\" behaviour, which would let the\n // daemon's `LOG_LEVEL=debug` overwrite a literal `LOG_LEVEL: info` declared in\n // zooid.yaml. References still resolve against the full processEnv.\n const out: Record<string, string> = {}\n for (const [key, val] of Object.entries(parsed)) {\n out[key] = interpolateString(val, processEnv)\n }\n return out\n}\n\n/**\n * Run compose-style interpolation over a single string value (e.g. a\n * transport's `as_token`). No denylist — transports legitimately reference\n * `ZOOID_*` tokens for the daemon itself (see [ZOD043] §Denylist note).\n */\nexport function interpolateString(\n value: string,\n processEnv: NodeJS.ProcessEnv,\n): string {\n // Fast path: literals with no `$` need no expansion. Skipping the dotenv-expand\n // call also avoids the library's \"processEnv shadows parsed\" merge behaviour,\n // which can leak unrelated env vars through a sentinel key.\n if (!value.includes('$')) return value\n const sentinel = '__zooid_interp_v1__'\n const env: Record<string, string> = {}\n for (const [k, v] of Object.entries(processEnv)) {\n if (typeof v === 'string') env[k] = v\n }\n delete env[sentinel]\n const result = dotenvExpand.expand({\n parsed: { [sentinel]: value },\n processEnv: env,\n })\n return result.parsed?.[sentinel] ?? ''\n}\n","import { mkdirSync } from 'node:fs'\nimport { join } from 'node:path'\nimport {\n AcpClient,\n resolvePreset,\n type AgentEvent,\n type ApprovalDecision,\n type ApprovalRequest,\n type PromptInput,\n type PromptResult,\n type TapEvent,\n} from '@zooid/acp-client'\nimport type { AcpAgentSpec, AcpMount, AcpRuntime } from './acp-types.js'\nimport type { AgentConfig } from './types.js'\nimport type {\n ApprovalCorrelator,\n RegisteredApproval,\n} from './approval-correlator.js'\n\nexport type AcpRegistryEventHandler = (\n agentName: string,\n event: AgentEvent,\n) => void\nexport type AcpRegistryApprovalHandler = (\n agentName: string,\n req: ApprovalRequest,\n) => Promise<ApprovalDecision>\n\n/**\n * Daemon-side surface of the ACP agent fleet. The transport (HTTP) consumes\n * this; the CLI builds it via `buildAcpRegistry`. Long-lived: one\n * `AcpClient` per agent, kept alive across prompts.\n */\nexport interface AcpRegistry {\n hasAgent(name: string): boolean\n /** Whether an agent has a transport-context provider attached. */\n hasContextSpawn(name: string): boolean\n /** Per-agent approval timeout from zooid.yaml. 0 means no timeout. */\n getApprovalTimeoutMs(name: string): number\n ensureSession(name: string, threadId: string, channelId?: string): Promise<string>\n /** Drop the in-memory session for (agent, threadId). Next prompt re-creates one. */\n endSession(name: string, threadId: string): void\n prompt(name: string, input: PromptInput): Promise<PromptResult>\n /**\n * Cancel an in-flight prompt for (agent, sessionId). Sends `session/cancel`\n * via the underlying AcpClient and resolves any pending approvals with\n * `decision: 'cancel'`. Idempotent.\n */\n cancelSession(name: string, sessionId: string): Promise<void>\n stopAll(): Promise<void>\n /** Set by the transport. Receives every ACP event from any agent. */\n onEvent: AcpRegistryEventHandler\n /** Set by the transport. Resolves permission requests. */\n onApprovalRequest: AcpRegistryApprovalHandler\n}\n\nexport interface AcpAgentRegistryOptions {\n runtime: AcpRuntime\n agents: Record<string, AgentConfig>\n /** Per-agent env passed to each `AcpClient`'s spawn spec. */\n env?: Record<string, Record<string, string>>\n /** Per-agent container image. Used by DockerAcpRuntime; ignored by LocalAcpRuntime. */\n image?: Record<string, string | undefined>\n /** Initial event handler (the transport may overwrite at app creation). */\n onEvent?: AcpRegistryEventHandler\n /** Initial approval handler (the transport may overwrite at app creation). */\n onApprovalRequest?: AcpRegistryApprovalHandler\n /**\n * Optional correlator: when set, the registry's default\n * `onApprovalRequest` registers each request on the correlator (with the\n * agent's `approval_timeout_ms`) and returns the registered handle's\n * `decisionPromise`. Transports listen on the correlator's `'registered'`\n * + `'timeout'` events to drive the SSE wire and accept HTTP decisions.\n */\n approvals?: ApprovalCorrelator\n /** Called whenever the correlator-backed handler registers an approval. */\n onApprovalRegistered?: (approval: RegisteredApproval) => void\n /**\n * Optional observability tap. Forwarded to each AcpClient so the\n * unfiltered ACP protocol stream + turn-boundary events are visible to\n * the host (e.g. the dev CLI capturing them to disk).\n */\n onTap?: (agentName: string, event: TapEvent) => void\n /**\n * Root directory under which each agent gets a per-agent state dir\n * (`<agentsDir>/<agentName>/`). Used by the AcpClient session store to\n * persist ACP `sessionId`s across daemon restarts. Optional: when unset,\n * session continuity across restarts is disabled.\n */\n agentsDir?: string\n /**\n * Per-agent factory that returns a `mcpServers[]` entry for the\n * `zooid-context` MCP server. Forwarded to each AcpClient. Agents bound to\n * transports without a context provider (e.g. HTTP) have no entry here.\n */\n contextSpawns?: Record<string, ContextSpawnFactory | undefined>\n /**\n * Per-agent resolved bind-mount list. Threaded into the AcpClient's spawn\n * spec; honoured by the docker runtime, ignored by the local runtime.\n */\n mounts?: Record<string, AcpMount[]>\n /**\n * Per-agent list of host directories to `mkdir -p` before the first\n * `runtime.spawn` for that agent. Subset of mount entries with `create: true`.\n */\n mkdirOnSpawn?: Record<string, string[]>\n /**\n * Per-agent override for the spawn-spec `cwd`. Set to e.g. `/workspace`\n * when the workspace mount is active; falls back to `agent.workdir`.\n */\n cwd?: Record<string, string>\n}\n\nexport type ContextSpawnFactory = (\n threadId: string,\n channelId?: string,\n) => Promise<{\n name: 'zooid-context'\n command: string\n args: string[]\n env: Array<{ name: string; value: string }>\n}>\n\nexport class AcpAgentRegistry implements AcpRegistry {\n readonly opts: AcpAgentRegistryOptions\n private readonly clients = new Map<string, AcpClient>()\n\n onEvent: AcpRegistryEventHandler\n onApprovalRequest: AcpRegistryApprovalHandler\n\n constructor(opts: AcpAgentRegistryOptions) {\n this.opts = opts\n this.onEvent = opts.onEvent ?? (() => {})\n if (opts.onApprovalRequest) {\n this.onApprovalRequest = opts.onApprovalRequest\n } else if (opts.approvals) {\n const correlator = opts.approvals\n this.onApprovalRequest = async (name, req) => {\n const cfg = this.opts.agents[name]\n const handle = correlator.register(name, req.sessionId, req, {\n timeoutMs: cfg?.approval_timeout_ms ?? 0,\n })\n this.opts.onApprovalRegistered?.(handle)\n return handle.decisionPromise\n }\n } else {\n this.onApprovalRequest = async () => ({ decision: 'cancel' })\n }\n }\n\n hasAgent(name: string): boolean {\n return Object.prototype.hasOwnProperty.call(this.opts.agents, name)\n }\n\n hasContextSpawn(name: string): boolean {\n return Boolean(this.opts.contextSpawns?.[name])\n }\n\n resolveSpawnEnv(name: string): Record<string, string> {\n return this.opts.env?.[name] ?? {}\n }\n\n resolveSpawnImage(name: string): string | undefined {\n return this.opts.image?.[name]\n }\n\n resolveSpawnMounts(name: string): AcpMount[] {\n return this.opts.mounts?.[name] ?? []\n }\n\n resolveSpawnCwd(name: string): string {\n return (\n this.opts.cwd?.[name] ??\n this.opts.agents[name]?.workdir ??\n process.cwd()\n )\n }\n\n agentNames(): string[] {\n return Object.keys(this.opts.agents)\n }\n\n getApprovalTimeoutMs(name: string): number {\n return this.opts.agents[name]?.approval_timeout_ms ?? 0\n }\n\n async ensureSession(name: string, threadId: string, channelId?: string): Promise<string> {\n if (!this.hasAgent(name)) throw new Error(`unknown agent: ${name}`)\n const client = await this.ensureClient(name)\n return client.ensureSession(threadId, channelId)\n }\n\n endSession(name: string, threadId: string): void {\n if (!this.hasAgent(name)) return\n const client = this.clients.get(name)\n client?.endSession(threadId)\n }\n\n async cancelSession(name: string, sessionId: string): Promise<void> {\n if (!this.hasAgent(name)) return\n const client = this.clients.get(name)\n // Always nudge the correlator first so any pending approvals resolve with\n // 'cancel' regardless of whether the client is alive or already stopped.\n this.opts.approvals?.cancelSession(sessionId)\n if (!client) return\n try {\n await client.cancel(sessionId)\n } catch (err) {\n // ACP cancel is a notification; failures here are typically transport\n // errors after the agent has already exited.\n console.warn(`[acp:${name}] cancel(${sessionId}) failed:`, err)\n }\n }\n\n async prompt(name: string, input: PromptInput): Promise<PromptResult> {\n if (!this.hasAgent(name)) throw new Error(`unknown agent: ${name}`)\n const client = await this.ensureClient(name)\n return client.prompt(input)\n }\n\n async stopAll(): Promise<void> {\n await Promise.allSettled(\n [...this.clients.values()].map((c) => c.stop()),\n )\n this.clients.clear()\n }\n\n private async ensureClient(name: string): Promise<AcpClient> {\n const existing = this.clients.get(name)\n if (existing) return existing\n const cfg = this.opts.agents[name]\n if (!cfg.acp) throw new Error(`agents.${name}: missing acp block`)\n const spawn = resolveAcpAgentSpec(cfg.acp)\n for (const dir of this.opts.mkdirOnSpawn?.[name] ?? []) {\n mkdirSync(dir, { recursive: true })\n }\n const client = new AcpClient({\n agent: {\n id: name,\n command: spawn.command,\n args: spawn.args,\n env: this.opts.env?.[name],\n cwd: this.resolveSpawnCwd(name),\n image: this.opts.image?.[name],\n mounts: this.resolveSpawnMounts(name),\n },\n agentDataDir: this.opts.agentsDir ? join(this.opts.agentsDir, name) : undefined,\n runtime: this.opts.runtime,\n onEvent: (e) => this.onEvent(name, e),\n onApprovalRequest: (req) => this.onApprovalRequest(name, req),\n onTap: this.opts.onTap ? (e) => this.opts.onTap!(name, e) : undefined,\n contextSpawn: this.opts.contextSpawns?.[name],\n })\n await client.start()\n this.clients.set(name, client)\n return client\n }\n}\n\nexport function resolveAcpAgentSpec(spec: AcpAgentSpec): {\n command: string\n args: string[]\n} {\n if ('preset' in spec && spec.preset) {\n return resolvePreset(spec.preset, { model: spec.model })\n }\n if ('command' in spec && spec.command) {\n return { command: spec.command, args: spec.args ?? [] }\n }\n throw new Error('AcpAgentSpec: must specify either preset or command')\n}\n","import { EventEmitter } from 'node:events'\nimport { randomUUID } from 'node:crypto'\nimport type {\n ApprovalDecision,\n ApprovalRequest,\n} from '@zooid/acp-client'\n\nexport interface RegisteredApproval {\n approvalId: string\n agentName: string\n sessionId: string\n toolCallId: string\n toolKind?: string\n toolTitle?: string\n toolInput?: unknown\n options: ApprovalRequest['options']\n decisionPromise: Promise<ApprovalDecision>\n}\n\nexport interface RegisterOptions {\n /** Wall-clock timeout in ms. 0 = no timeout. */\n timeoutMs?: number\n}\n\ninterface PendingEntry extends RegisteredApproval {\n resolve(decision: ApprovalDecision): void\n timer?: ReturnType<typeof setTimeout>\n}\n\n/**\n * Correlates ACP approval requests (mid-prompt, originating in `AcpClient`)\n * with HTTP/transport-side decisions. The transport listens to:\n *\n * - `'registered'` — fires after `register()` so the transport can emit\n * `approval.request` on the right SSE stream.\n * - `'timeout'` — fires when an entry is auto-cancelled by the timer\n * so the transport can emit `approval.timeout`.\n */\nexport class ApprovalCorrelator extends EventEmitter {\n private readonly pending = new Map<string, PendingEntry>()\n private readonly bySession = new Map<string, Set<string>>()\n\n register(\n agentName: string,\n sessionId: string,\n req: ApprovalRequest,\n opts: RegisterOptions = {},\n ): RegisteredApproval {\n const approvalId = randomUUID()\n let resolve!: (d: ApprovalDecision) => void\n const decisionPromise = new Promise<ApprovalDecision>((r) => {\n resolve = r\n })\n const entry: PendingEntry = {\n approvalId,\n agentName,\n sessionId,\n toolCallId: req.toolCallId,\n toolKind: req.toolKind,\n toolTitle: req.toolTitle,\n toolInput: req.toolInput,\n options: req.options,\n decisionPromise,\n resolve,\n }\n if (opts.timeoutMs && opts.timeoutMs > 0) {\n entry.timer = setTimeout(() => {\n if (this.pending.get(approvalId) !== entry) return\n entry.resolve({ decision: 'cancel' })\n this.pending.delete(approvalId)\n this.bySession.get(sessionId)?.delete(approvalId)\n this.emit('timeout', { approvalId, sessionId, agentName })\n }, opts.timeoutMs)\n entry.timer.unref?.()\n }\n this.pending.set(approvalId, entry)\n let set = this.bySession.get(sessionId)\n if (!set) {\n set = new Set()\n this.bySession.set(sessionId, set)\n }\n set.add(approvalId)\n this.emit('registered', this.toPublic(entry))\n return this.toPublic(entry)\n }\n\n resolve(\n sessionId: string,\n approvalId: string,\n decision: ApprovalDecision,\n ): boolean {\n const entry = this.pending.get(approvalId)\n if (!entry || entry.sessionId !== sessionId) return false\n if (entry.timer) clearTimeout(entry.timer)\n entry.resolve(decision)\n this.pending.delete(approvalId)\n this.bySession.get(sessionId)?.delete(approvalId)\n return true\n }\n\n cancelSession(sessionId: string): void {\n const ids = this.bySession.get(sessionId)\n if (!ids) return\n for (const id of [...ids]) {\n const entry = this.pending.get(id)\n if (entry) {\n if (entry.timer) clearTimeout(entry.timer)\n entry.resolve({ decision: 'cancel' })\n this.pending.delete(id)\n }\n }\n this.bySession.delete(sessionId)\n }\n\n listPending(sessionId: string): RegisteredApproval[] {\n const ids = this.bySession.get(sessionId)\n if (!ids) return []\n const out: RegisteredApproval[] = []\n for (const id of ids) {\n const entry = this.pending.get(id)\n if (entry) out.push(this.toPublic(entry))\n }\n return out\n }\n\n size(): number {\n return this.pending.size\n }\n\n private toPublic(entry: PendingEntry): RegisteredApproval {\n return {\n approvalId: entry.approvalId,\n agentName: entry.agentName,\n sessionId: entry.sessionId,\n toolCallId: entry.toolCallId,\n toolKind: entry.toolKind,\n toolTitle: entry.toolTitle,\n toolInput: entry.toolInput,\n options: entry.options,\n decisionPromise: entry.decisionPromise,\n }\n }\n}\n"],"mappings":";AAAA,SAAS,kBAAkB;AAC3B,SAAS,YAAY,MAAM,WAAW,mBAAmB;AACzD,SAAS,aAAa;AAEtB,SAAS,gBAAgB;;;ACJzB,OAAO,kBAAkB;AASzB,IAAM,SACJ;AAEK,IAAM,wBAAN,cAAoC,MAAM;AAAC;AAElD,IAAM,WAAW,CAAC,SAChB,SAAS,iBAAiB,KAAK,WAAW,QAAQ;AAY7C,SAAS,eACd,QACA,YACA,OACwB;AACxB,aAAW,CAAC,KAAK,GAAG,KAAK,OAAO,QAAQ,MAAM,GAAG;AAC/C,QAAI,SAAS,GAAG,GAAG;AACjB,YAAM,IAAI;AAAA,QACR,GAAG,KAAK,IAAI,GAAG;AAAA,MACjB;AAAA,IACF;AACA,QAAI,OAAO,QAAQ,UAAU;AAC3B,YAAM,IAAI;AAAA,QACR,GAAG,KAAK,IAAI,GAAG,qCAAqC,OAAO,GAAG;AAAA,MAChE;AAAA,IACF;AACA,WAAO,YAAY;AACnB,QAAI;AACJ,YAAQ,IAAI,OAAO,KAAK,GAAG,OAAO,MAAM;AACtC,YAAM,MAAO,EAAE,CAAC,KAAK,EAAE,CAAC;AACxB,UAAI,SAAS,GAAG,GAAG;AACjB,cAAM,IAAI;AAAA,UACR,GAAG,KAAK,IAAI,GAAG,wDAAwD,GAAG;AAAA,QAC5E;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAKA,QAAM,MAA8B,CAAC;AACrC,aAAW,CAAC,KAAK,GAAG,KAAK,OAAO,QAAQ,MAAM,GAAG;AAC/C,QAAI,GAAG,IAAI,kBAAkB,KAAK,UAAU;AAAA,EAC9C;AACA,SAAO;AACT;AAOO,SAAS,kBACd,OACA,YACQ;AAIR,MAAI,CAAC,MAAM,SAAS,GAAG,EAAG,QAAO;AACjC,QAAM,WAAW;AACjB,QAAM,MAA8B,CAAC;AACrC,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,UAAU,GAAG;AAC/C,QAAI,OAAO,MAAM,SAAU,KAAI,CAAC,IAAI;AAAA,EACtC;AACA,SAAO,IAAI,QAAQ;AACnB,QAAM,SAAS,aAAa,OAAO;AAAA,IACjC,QAAQ,EAAE,CAAC,QAAQ,GAAG,MAAM;AAAA,IAC5B,YAAY;AAAA,EACd,CAAC;AACD,SAAO,OAAO,SAAS,QAAQ,KAAK;AACtC;;;ADpEA,IAAM,UAAU;AAYhB,IAAM,gBAAgB;AACtB,IAAM,oBAAoB;AAC1B,IAAM,2BAA2B;AACjC,IAAM,uBAAuB;AAE7B,SAAS,iBAAiB,eAA+B;AAIvD,QAAM,OAAO,cAAc,MAAM,GAAG,EAAE,MAAM,CAAC,EAAE,KAAK,GAAG,EAAE,QAAQ,WAAW,EAAE;AAC9E,MAAI,CAAC,KAAM,OAAM,IAAI,MAAM,uCAAuC,aAAa,EAAE;AACjF,SAAO;AACT;AAEA,IAAM,kBAAkB,CAAC,UAAU,MAAM;AAGzC,SAAS,cAAc,MAAc,KAA4B;AAC/D,MAAI,CAAC,OAAO,OAAO,QAAQ,YAAY,MAAM,QAAQ,GAAG,GAAG;AACzD,UAAM,IAAI,MAAM,UAAU,IAAI,uDAAuD;AAAA,EACvF;AACA,QAAM,IAAI;AACV,QAAM,YAAY,EAAE,WAAW;AAC/B,QAAM,aAAa,EAAE,YAAY;AACjC,MAAI,aAAa,YAAY;AAC3B,UAAM,IAAI;AAAA,MACR,UAAU,IAAI;AAAA,IAChB;AAAA,EACF;AACA,MAAI,CAAC,aAAa,CAAC,YAAY;AAC7B,UAAM,IAAI;AAAA,MACR,UAAU,IAAI;AAAA,IAChB;AAAA,EACF;AACA,MAAI,WAAW;AACb,QAAI,OAAO,EAAE,WAAW,YAAY,EAAE,OAAO,WAAW,GAAG;AACzD,YAAM,IAAI,MAAM,UAAU,IAAI,yCAAyC;AAAA,IACzE;AACA,QAAI,CAAC,SAAS,EAAE,MAAM,GAAG;AACvB,YAAM,IAAI;AAAA,QACR,UAAU,IAAI,gCAAgC,EAAE,MAAM;AAAA,MACxD;AAAA,IACF;AACA,UAAM,MAA0C,EAAE,QAAQ,EAAE,OAAO;AACnE,QAAI,EAAE,UAAU,QAAW;AACzB,UAAI,OAAO,EAAE,UAAU,YAAY,EAAE,MAAM,KAAK,EAAE,WAAW,GAAG;AAC9D,cAAM,IAAI,MAAM,UAAU,IAAI,wCAAwC;AAAA,MACxE;AACA,UAAI,QAAQ,EAAE,MAAM,KAAK;AAAA,IAC3B;AACA,WAAO;AAAA,EACT;AACA,MAAI,OAAO,EAAE,YAAY,YAAY,EAAE,QAAQ,WAAW,GAAG;AAC3D,UAAM,IAAI,MAAM,UAAU,IAAI,0CAA0C;AAAA,EAC1E;AACA,QAAM,OAAiB,CAAC;AACxB,MAAI,EAAE,SAAS,QAAW;AACxB,QAAI,CAAC,MAAM,QAAQ,EAAE,IAAI,GAAG;AAC1B,YAAM,IAAI,MAAM,UAAU,IAAI,wCAAwC;AAAA,IACxE;AACA,eAAW,KAAK,EAAE,MAAM;AACtB,UAAI,OAAO,MAAM,UAAU;AACzB,cAAM,IAAI,MAAM,UAAU,IAAI,+BAA+B;AAAA,MAC/D;AACA,WAAK,KAAK,CAAC;AAAA,IACb;AAAA,EACF;AACA,SAAO,EAAE,SAAS,EAAE,SAAS,KAAK;AACpC;AAEA,SAAS,qBAAqB,MAAc,KAAsB;AAChE,MAAI,QAAQ,OAAW,QAAO;AAC9B,MAAI,QAAQ,KAAK,QAAQ,IAAK,QAAO;AACrC,MAAI,OAAO,QAAQ,UAAU;AAC3B,UAAM,IAAI;AAAA,MACR,UAAU,IAAI,uFAAuF,KAAK,UAAU,GAAG,CAAC;AAAA,IAC1H;AAAA,EACF;AACA,QAAM,IAAI,iBAAiB,KAAK,GAAG;AACnC,MAAI,CAAC,GAAG;AACN,UAAM,IAAI;AAAA,MACR,UAAU,IAAI,uBAAuB,GAAG;AAAA,IAC1C;AAAA,EACF;AACA,QAAM,IAAI,OAAO,EAAE,CAAC,CAAC;AACrB,UAAQ,EAAE,CAAC,GAAG;AAAA,IACZ,KAAK;AACH,aAAO,IAAI;AAAA,IACb,KAAK;AACH,aAAO,IAAI;AAAA,IACb,KAAK;AACH,aAAO,IAAI,KAAK;AAAA,EACpB;AACA,QAAM,IAAI,MAAM,aAAa;AAC/B;AAEA,SAAS,oBACP,MACA,KACA,YACA,WACiB;AACjB,MAAI,OAAO,QAAQ,YAAY,QAAQ,QAAQ,MAAM,QAAQ,GAAG,GAAG;AACjE,UAAM,IAAI,MAAM,UAAU,IAAI,8BAA8B;AAAA,EAC9D;AACA,QAAM,IAAI;AACV,QAAM,MAAuB,CAAC;AAC9B,MAAI,EAAE,UAAU,QAAW;AACzB,QAAI,OAAO,EAAE,UAAU,YAAY,EAAE,MAAM,WAAW,GAAG;AACvD,YAAM,IAAI,MAAM,UAAU,IAAI,6CAA6C;AAAA,IAC7E;AACA,QAAI,QAAQ,EAAE;AAAA,EAChB;AACA,MAAI,EAAE,QAAQ,UAAa,EAAE,QAAQ,MAAM;AACzC,QAAI,OAAO,EAAE,QAAQ,YAAY,MAAM,QAAQ,EAAE,GAAG,GAAG;AACrD,YAAM,IAAI,MAAM,UAAU,IAAI,kCAAkC;AAAA,IAClE;AACA,UAAM,SAAS,EAAE;AACjB,UAAM,YAAoC,CAAC;AAC3C,eAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,MAAM,GAAG;AAC3C,UAAI,OAAO,MAAM,UAAU;AACzB,cAAM,IAAI;AAAA,UACR,UAAU,IAAI,kBAAkB,CAAC,2BAA2B,OAAO,CAAC;AAAA,QACtE;AAAA,MACF;AACA,gBAAU,CAAC,IAAI;AAAA,IACjB;AACA,QAAI,MAAM,eAAe,WAAW,YAAY,UAAU,IAAI,gBAAgB;AAAA,EAChF;AACA,MAAI,EAAE,WAAW,QAAW;AAC1B,QAAI,SAAS,eAAe,MAAM,EAAE,QAAQ,YAAY,SAAS;AAAA,EACnE;AACA,MAAI,EAAE,mBAAmB,QAAW;AAClC,QAAI,iBAAiB,mBAAmB,MAAM,EAAE,cAAc;AAAA,EAChE;AACA,SAAO;AACT;AAEA,SAAS,eACP,WACA,KACA,YACA,WACe;AACf,MAAI,CAAC,MAAM,QAAQ,GAAG,GAAG;AACvB,UAAM,IAAI,MAAM,UAAU,SAAS,oCAAoC;AAAA,EACzE;AACA,QAAM,MAAqB,CAAC;AAC5B,QAAM,UAAU,oBAAI,IAAY;AAChC,WAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,UAAM,QAAQ,IAAI,CAAC;AACnB,QAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,MAAM,QAAQ,KAAK,GAAG;AACvE,YAAM,IAAI,MAAM,UAAU,SAAS,qBAAqB,CAAC,qBAAqB;AAAA,IAChF;AACA,UAAM,IAAI;AACV,QAAI,EAAE,SAAS,QAAW;AACxB,YAAM,IAAI,MAAM,UAAU,SAAS,qBAAqB,CAAC,oBAAoB;AAAA,IAC/E;AACA,QAAI,EAAE,WAAW,QAAW;AAC1B,YAAM,IAAI,MAAM,UAAU,SAAS,qBAAqB,CAAC,sBAAsB;AAAA,IACjF;AACA,QAAI,OAAO,EAAE,SAAS,YAAY,EAAE,KAAK,WAAW,GAAG;AACrD,YAAM,IAAI;AAAA,QACR,UAAU,SAAS,qBAAqB,CAAC;AAAA,MAC3C;AAAA,IACF;AACA,QAAI,OAAO,EAAE,WAAW,YAAY,EAAE,OAAO,WAAW,GAAG;AACzD,YAAM,IAAI;AAAA,QACR,UAAU,SAAS,qBAAqB,CAAC;AAAA,MAC3C;AAAA,IACF;AACA,UAAM,OAAO,EAAE,QAAQ;AACvB,QAAI,SAAS,QAAQ,SAAS,MAAM;AAClC,YAAM,IAAI;AAAA,QACR,UAAU,SAAS,qBAAqB,CAAC,oCAAoC,KAAK,UAAU,EAAE,IAAI,CAAC;AAAA,MACrG;AAAA,IACF;AACA,QAAI;AACJ,QAAI,EAAE,OAAO,QAAW;AACtB,UAAI,OAAO,EAAE,OAAO,YAAY,EAAE,GAAG,WAAW,GAAG;AACjD,cAAM,IAAI;AAAA,UACR,UAAU,SAAS,qBAAqB,CAAC;AAAA,QAC3C;AAAA,MACF;AACA,UAAI,EAAE,OAAO,aAAa;AACxB,cAAM,IAAI;AAAA,UACR,UAAU,SAAS,qBAAqB,CAAC;AAAA,QAC3C;AAAA,MACF;AACA,UAAI,QAAQ,IAAI,EAAE,EAAE,GAAG;AACrB,cAAM,IAAI;AAAA,UACR,UAAU,SAAS,oCAAoC,EAAE,EAAE;AAAA,QAC7D;AAAA,MACF;AACA,cAAQ,IAAI,EAAE,EAAE;AAChB,WAAK,EAAE;AAAA,IACT;AACA,QAAI;AACJ,QAAI,EAAE,WAAW,QAAW;AAC1B,UAAI,OAAO,EAAE,WAAW,WAAW;AACjC,cAAM,IAAI;AAAA,UACR,UAAU,SAAS,qBAAqB,CAAC;AAAA,QAC3C;AAAA,MACF;AACA,eAAS,EAAE;AAAA,IACb;AACA,UAAM,OAAO;AAAA,MACX;AAAA,MACA;AAAA,MACA,kBAAkB,EAAE,MAAM,UAAU;AAAA,MACpC;AAAA,IACF;AACA,UAAM,SAAS,kBAAkB,EAAE,QAAQ,UAAU;AACrD,UAAM,IAAiB,EAAE,MAAM,QAAQ,KAAK;AAC5C,QAAI,OAAO,OAAW,GAAE,KAAK;AAC7B,QAAI,WAAW,OAAW,GAAE,SAAS;AACrC,QAAI,KAAK,CAAC;AAAA,EACZ;AACA,SAAO;AACT;AAEA,SAAS,gBACP,WACA,OACA,MACA,WACQ;AACR,MAAI,KAAK,WAAW,IAAI,GAAG;AACzB,UAAM,OAAO,QAAQ,IAAI;AACzB,QAAI,CAAC,MAAM;AACT,YAAM,IAAI;AAAA,QACR,UAAU,SAAS,qBAAqB,KAAK;AAAA,MAC/C;AAAA,IACF;AACA,WAAO,GAAG,IAAI,IAAI,KAAK,MAAM,CAAC,CAAC;AAAA,EACjC;AACA,MAAI,SAAS,KAAK;AAChB,UAAM,OAAO,QAAQ,IAAI;AACzB,QAAI,CAAC,MAAM;AACT,YAAM,IAAI;AAAA,QACR,UAAU,SAAS,qBAAqB,KAAK;AAAA,MAC/C;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACA,MAAI,WAAW,IAAI,EAAG,QAAO;AAC7B,MAAI,CAAC,WAAW;AACd,UAAM,IAAI;AAAA,MACR,UAAU,SAAS,qBAAqB,KAAK,0BAA0B,IAAI;AAAA,IAC7E;AAAA,EACF;AACA,SAAO,YAAY,WAAW,IAAI;AACpC;AAEA,SAAS,mBAAmB,WAAmB,KAAwB;AACrE,MAAI,CAAC,MAAM,QAAQ,GAAG,GAAG;AACvB,UAAM,IAAI,MAAM,UAAU,SAAS,uDAAuD;AAAA,EAC5F;AACA,QAAM,MAAgB,CAAC;AACvB,WAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,UAAM,IAAI,IAAI,CAAC;AACf,QAAI,OAAO,MAAM,YAAY,EAAE,WAAW,GAAG;AAC3C,YAAM,IAAI;AAAA,QACR,UAAU,SAAS,6BAA6B,CAAC;AAAA,MACnD;AAAA,IACF;AACA,QAAI,KAAK,CAAC;AAAA,EACZ;AACA,SAAO;AACT;AAEA,SAAS,oBAAoB,KAAoC;AAC/D,MAAI,OAAO,QAAQ,YAAY,QAAQ,QAAQ,MAAM,QAAQ,GAAG,GAAG;AACjE,UAAM,IAAI,MAAM,6BAA6B;AAAA,EAC/C;AACA,QAAM,IAAI;AACV,QAAM,MAA4B,CAAC;AACnC,MAAI,EAAE,QAAQ,QAAW;AACvB,UAAM,IAAI;AAAA,MACR;AAAA,IAEF;AAAA,EACF;AACA,MAAI,EAAE,UAAU,QAAW;AACzB,QAAI,OAAO,EAAE,UAAU,YAAY,EAAE,MAAM,WAAW,GAAG;AACvD,YAAM,IAAI,MAAM,4CAA4C;AAAA,IAC9D;AACA,QAAI,QAAQ,EAAE;AAAA,EAChB;AACA,SAAO;AACT;AAEA,SAAS,gBACP,KACA,YACA,aACiC;AACjC,MAAI,CAAC,OAAO,OAAO,QAAQ,YAAY,MAAM,QAAQ,GAAG,GAAG;AACzD,UAAM,IAAI,MAAM,uDAAuD;AAAA,EACzE;AACA,QAAM,IAAI;AACV,QAAM,QAAQ,OAAO,KAAK,CAAC;AAC3B,MAAI,MAAM,WAAW,GAAG;AACtB,UAAM,IAAI,MAAM,qDAAqD;AAAA,EACvE;AACA,QAAM,MAAuC,CAAC;AAC9C,aAAW,QAAQ,OAAO;AACxB,QAAI,IAAI,IAAI,eAAe,MAAM,EAAE,IAAI,GAAG,YAAY,WAAW;AAAA,EACnE;AACA,QAAM,gBAAgB,OAAO,QAAQ,GAAG,EAAE;AAAA,IACxC,CAAC,MAA4C,EAAE,CAAC,EAAE,SAAS;AAAA,EAC7D;AACA,MAAI,cAAc,WAAW,GAAG;AAC9B,UAAM,CAAC,EAAE,EAAE,IAAI,cAAc,CAAC;AAC9B,QAAI,GAAG,aAAa,aAAa;AAC/B,YAAM,IAAI,kBAAkB,sBAAsB,UAAU;AAC5D,UAAI,EAAE,WAAW,GAAG;AAClB,cAAM,IAAI;AAAA,UACR;AAAA,QAEF;AAAA,MACF;AACA,SAAG,WAAW;AAAA,IAChB;AACA,QAAI,GAAG,aAAa,aAAa;AAC/B,YAAM,IAAI,kBAAkB,sBAAsB,UAAU;AAC5D,UAAI,EAAE,WAAW,GAAG;AAClB,cAAM,IAAI;AAAA,UACR;AAAA,QAEF;AAAA,MACF;AACA,SAAG,WAAW;AAAA,IAChB;AAAA,EACF,WAAW,cAAc,SAAS,GAAG;AACnC,eAAW,CAAC,OAAO,EAAE,KAAK,eAAe;AACvC,UAAI,GAAG,aAAa,eAAe,GAAG,aAAa,aAAa;AAC9D,cAAM,IAAI;AAAA,UACR,cAAc,KAAK;AAAA,QAErB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,eACP,MACA,KACA,YACA,aACiB;AACjB,MAAI,CAAC,OAAO,OAAO,QAAQ,YAAY,MAAM,QAAQ,GAAG,GAAG;AACzD,UAAM,IAAI,MAAM,cAAc,IAAI,qBAAqB;AAAA,EACzD;AACA,QAAM,IAAI;AACV,QAAM,eACJ,EAAE,SAAS,SAAS,YAAY,SAAS,SAAS,OAAO;AAC3D,MAAI,iBAAiB,YAAY,iBAAiB,QAAQ;AACxD,UAAM,IAAI;AAAA,MACR,cAAc,IAAI,yCAAyC,KAAK,UAAU,EAAE,IAAI,CAAC;AAAA,IACnF;AAAA,EACF;AACA,MAAI,iBAAiB,UAAU;AAE7B,UAAM,OAAO,EAAE,QAAQ;AACvB,QAAI,SAAS,gBAAgB,SAAS,UAAU;AAC9C,YAAM,IAAI;AAAA,QACR,cAAc,IAAI,+CAA+C,KAAK,UAAU,EAAE,IAAI,CAAC;AAAA,MACzF;AAAA,IACF;AAGA,QAAI,EAAE,SAAS,UAAa,EAAE,kBAAkB,QAAW;AACzD,YAAM,IAAI;AAAA,QACR,cAAc,IAAI;AAAA,MACpB;AAAA,IACF;AAGA,QAAI;AACJ,QAAI,OAAO,EAAE,eAAe,UAAU;AACpC,UAAI;AACF,uBAAe,IAAI,IAAI,kBAAkB,EAAE,YAAsB,UAAU,CAAC,EAAE;AAAA,MAChF,QAAQ;AAAA,MAER;AAAA,IACF;AAEA,QAAI,gBAAgB,QAAW;AAC7B,UAAI,CAAC,QAAQ,KAAK,WAAW,GAAG;AAC9B,cAAM,IAAI,MAAM,8CAA8C,KAAK,UAAU,WAAW,CAAC,GAAG;AAAA,MAC9F;AACA,UAAI,EAAE,qBAAqB,OAAW,GAAE,mBAAmB;AAC3D,UAAI,EAAE,mBAAmB,UAAa,cAAc;AAClD,UAAE,iBAAiB,IAAI,WAAW,SAAS,YAAY;AAAA,MACzD;AAAA,IACF,OAAO;AACL,UAAI,EAAE,qBAAqB,OAAW,GAAE,mBAAmB;AAC3D,UAAI,EAAE,mBAAmB,UAAa,cAAc;AAClD,UAAE,iBAAiB,OAAO,YAAY;AAAA,MACxC;AAAA,IACF;AAEA,QAAI,EAAE,aAAa,OAAW,GAAE,WAAW;AAC3C,QAAI,EAAE,aAAa,OAAW,GAAE,WAAW;AAC3C,UAAM,SAAS;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,eAAW,KAAK,QAAQ;AACtB,UAAI,OAAO,EAAE,CAAC,MAAM,YAAa,EAAE,CAAC,EAAa,WAAW,GAAG;AAC7D,cAAM,IAAI,MAAM,cAAc,IAAI,IAAI,CAAC,6BAA6B;AAAA,MACtE;AAAA,IACF;AACA,UAAM,MAA6B;AAAA,MACjC,MAAM;AAAA,MACN,YAAY,kBAAkB,EAAE,YAAsB,UAAU;AAAA,MAChE,UAAU,kBAAkB,EAAE,UAAoB,UAAU;AAAA,MAC5D,UAAU,kBAAkB,EAAE,UAAoB,UAAU;AAAA,MAC5D,kBAAkB,EAAE;AAAA,MACpB,gBAAgB,EAAE;AAAA,MAClB;AAAA,IACF;AACA,QAAI,gBAAgB,OAAW,KAAI,cAAc;AAEjD,QAAI,EAAE,SAAS,QAAW;AACxB,UAAI,CAAC,OAAO,UAAU,EAAE,IAAI,GAAG;AAC7B,cAAM,IAAI;AAAA,UACR,cAAc,IAAI,iCAAiC,KAAK,UAAU,EAAE,IAAI,CAAC;AAAA,QAC3E;AAAA,MACF;AACA,UAAI,OAAO,EAAE;AAAA,IACf,WAAW,EAAE,kBAAkB,QAAW;AACxC,UAAI,OAAO,EAAE,kBAAkB,YAAY,EAAE,cAAc,WAAW,GAAG;AACvE,cAAM,IAAI,MAAM,cAAc,IAAI,2CAA2C;AAAA,MAC/E;AACA,UAAI,gBAAgB,EAAE;AAAA,IACxB,OAAO;AAEL,UAAI,OAAO;AAAA,IACb;AAEA,QAAI,EAAE,UAAU,QAAW;AACzB,UAAI,OAAO,EAAE,UAAU,YAAY,EAAE,MAAM,WAAW,GAAG;AACvD,cAAM,IAAI;AAAA,UACR,cAAc,IAAI,0CAA0C,KAAK,UAAU,EAAE,KAAK,CAAC;AAAA,QACrF;AAAA,MACF;AACA,UAAI,QAAQ,EAAE;AAAA,IAChB;AACA,WAAO;AAAA,EACT;AAEA,QAAM,OAAQ,EAAE,QAAQ;AACxB,MAAI,CAAC,OAAO,UAAU,IAAI,GAAG;AAC3B,UAAM,IAAI,MAAM,cAAc,IAAI,iCAAiC,KAAK,UAAU,IAAI,CAAC,GAAG;AAAA,EAC5F;AACA,SAAO,EAAE,MAAM,QAAQ,KAAK;AAC9B;AAEA,SAAS,iBAAiB,MAAc,KAAc,YAAiC;AACrF,WAAS,eAAe,OAAuB;AAC7C,QAAI,MAAM,WAAW,GAAG;AACtB,YAAM,IAAI,MAAM,GAAG,IAAI,6BAA6B;AAAA,IACtD;AACA,QAAI,CAAC,qBAAqB,KAAK,KAAK,GAAG;AACrC,YAAM,IAAI;AAAA,QACR,GAAG,IAAI,qCAAqC,KAAK,UAAU,KAAK,CAAC;AAAA,MACnE;AAAA,IACF;AACA,WAAO,MAAM,SAAS,GAAG,IAAI,QAAQ,GAAG,KAAK,IAAI,UAAU;AAAA,EAC7D;AACA,MAAI,OAAO,QAAQ,UAAU;AAC3B,WAAO,EAAE,OAAO,eAAe,GAAG,EAAE;AAAA,EACtC;AACA,MAAI,CAAC,OAAO,OAAO,QAAQ,YAAY,MAAM,QAAQ,GAAG,GAAG;AACzD,UAAM,IAAI,MAAM,GAAG,IAAI,qDAAqD;AAAA,EAC9E;AACA,QAAM,IAAI;AACV,MAAI,OAAO,EAAE,UAAU,YAAY,EAAE,MAAM,WAAW,GAAG;AACvD,UAAM,IAAI,MAAM,GAAG,IAAI,oCAAoC;AAAA,EAC7D;AACA,QAAM,MAAmB,EAAE,OAAO,eAAe,EAAE,KAAK,EAAE;AAC1D,MAAI,EAAE,gBAAgB,QAAW;AAC/B,QAAI,OAAO,EAAE,gBAAgB,YAAY,CAAC,OAAO,UAAU,EAAE,WAAW,GAAG;AACzE,YAAM,IAAI;AAAA,QACR,GAAG,IAAI,yCAAyC,KAAK,UAAU,EAAE,WAAW,CAAC;AAAA,MAC/E;AAAA,IACF;AACA,QAAI,aAAa,EAAE;AAAA,EACrB;AACA,SAAO;AACT;AAEA,SAAS,sBACP,MACA,OACA,YACgD;AAChD,QAAM,UAAU,gBAAgB;AAAA,IAC9B,CAAC,MAAM,MAAM,CAAC,MAAM,UAAa,MAAM,CAAC,MAAM;AAAA,EAChD;AACA,MAAI,QAAQ,WAAW,GAAG;AACxB,UAAM,IAAI;AAAA,MACR,UAAU,IAAI;AAAA,IAEhB;AAAA,EACF;AACA,MAAI,QAAQ,SAAS,GAAG;AACtB,UAAM,IAAI;AAAA,MACR,UAAU,IAAI,yDACJ,QAAQ,KAAK,IAAI,CAAC,yEACE,IAAI,eAAe,IAAI;AAAA,IACvD;AAAA,EACF;AACA,QAAM,OAAO,QAAQ,CAAC;AACtB,QAAM,WAAW,MAAM,IAAI;AAC3B,MAAI,OAAO,aAAa,YAAY,aAAa,QAAQ,MAAM,QAAQ,QAAQ,GAAG;AAChF,UAAM,IAAI,MAAM,UAAU,IAAI,IAAI,IAAI,qBAAqB;AAAA,EAC7D;AACA,QAAM,QAAQ;AACd,MAAI;AACJ,MAAI,OAAO,MAAM,cAAc,YAAY,MAAM,UAAU,SAAS,GAAG;AACrE,cAAU,MAAM;AAAA,EAClB,OAAO;AACL,UAAM,UAAU,OAAO,QAAQ,UAAU,EAAE;AAAA,MACzC,CAAC,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS;AAAA,IACxB;AACA,QAAI,QAAQ,WAAW,GAAG;AACxB,YAAM,IAAI;AAAA,QACR,UAAU,IAAI,IAAI,IAAI,0BAA0B,IAAI;AAAA,MACtD;AAAA,IACF;AACA,QAAI,QAAQ,SAAS,GAAG;AACtB,YAAM,IAAI;AAAA,QACR,UAAU,IAAI,IAAI,IAAI,6CAA6C,IAAI,gCAC5D,QAAQ,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,KAAK,IAAI,CAAC;AAAA,MAC/C;AAAA,IACF;AACA,cAAU,QAAQ,CAAC,EAAG,CAAC;AAAA,EACzB;AACA,QAAM,eAAe,WAAW,OAAO;AACvC,MAAI,CAAC,cAAc;AACjB,UAAM,IAAI;AAAA,MACR,UAAU,IAAI,IAAI,IAAI,eAAe,OAAO;AAAA,IAC9C;AAAA,EACF;AACA,MAAI,aAAa,SAAS,MAAM;AAC9B,UAAM,IAAI;AAAA,MACR,UAAU,IAAI,IAAI,IAAI,0BAA0B,OAAO,cAAc,aAAa,IAAI;AAAA,IAExF;AAAA,EACF;AAEA,MAAI,SAAS,UAAU;AACrB,QAAI,aAAa,SAAS,UAAU;AAClC,YAAM,IAAI,MAAM,UAAU,IAAI,mCAAmC;AAAA,IACnE;AACA,UAAM,aAAa,iBAAiB,aAAa,cAAc;AAM/D,UAAM,YACJ,OAAO,MAAM,YAAY,YAAY,MAAM,QAAQ,SAAS,IACxD,MAAM,UACN,aAAa,cACX,IAAI,aAAa,WAAW,IAAI,IAAI,KACpC,IAAI,IAAI;AAChB,QAAI,SAAS;AACb,QAAI,CAAC,OAAO,SAAS,GAAG,KAAK,yBAAyB,KAAK,MAAM,GAAG;AAClE,eAAS,GAAG,MAAM,IAAI,UAAU;AAAA,IAClC;AACA,QAAI,CAAC,kBAAkB,KAAK,MAAM,GAAG;AACnC,YAAM,IAAI;AAAA,QACR,UAAU,IAAI,yDAAyD,KAAK,UAAU,MAAM,OAAO,CAAC;AAAA,MACtG;AAAA,IACF;AAEA,QAAI,CAAC,MAAM,QAAQ,MAAM,KAAK,KAAK,MAAM,MAAM,WAAW,GAAG;AAC3D,YAAM,IAAI,MAAM,UAAU,IAAI,yDAAyD;AAAA,IACzF;AACA,UAAM,QAAuB,CAAC;AAC9B,aAAS,IAAI,GAAG,IAAI,MAAM,MAAM,QAAQ,KAAK;AAC3C,YAAM,KAAK,iBAAiB,UAAU,IAAI,iBAAiB,CAAC,KAAK,MAAM,MAAM,CAAC,GAAG,UAAU,CAAC;AAAA,IAC9F;AAEA,QAAI;AACJ,QAAI,MAAM,iBAAiB,QAAW;AACpC,UAAI,OAAO,MAAM,iBAAiB,UAAU;AAC1C,cAAM,IAAI;AAAA,UACR,UAAU,IAAI,8CAA8C,KAAK,UAAU,MAAM,YAAY,CAAC;AAAA,QAChG;AAAA,MACF;AACA,YAAM,UAAU,MAAM,aAAa,KAAK;AACxC,UAAI,QAAQ,WAAW,GAAG;AACxB,cAAM,IAAI,MAAM,UAAU,IAAI,mDAAmD;AAAA,MACnF;AACA,UAAI,QAAQ,SAAS,KAAK;AACxB,cAAM,IAAI,MAAM,UAAU,IAAI,sDAAsD;AAAA,MACtF;AACA,oBAAc;AAAA,IAChB;AAEA,UAAM,KAAK,MAAM,WAAW;AAC5B,QAAI,OAAO,aAAa,OAAO,OAAO;AACpC,YAAM,IAAI;AAAA,QACR,UAAU,IAAI,mDAAmD,KAAK,UAAU,EAAE,CAAC;AAAA,MACrF;AAAA,IACF;AACA,UAAM,SAAwB;AAAA,MAC5B,WAAW;AAAA,MACX,SAAS;AAAA,MACT;AAAA,MACA,SAAS;AAAA,IACX;AACA,QAAI,gBAAgB,OAAW,QAAO,eAAe;AACrD,WAAO,EAAE,OAAO;AAAA,EAClB;AAEA,SAAO,EAAE,MAAM,EAAE,WAAW,QAAQ,EAAE;AACxC;AAEA,SAAS,YACP,KACA,SACA,YACA,aACA,YACA,WAC6B;AAC7B,MAAI,CAAC,OAAO,OAAO,QAAQ,YAAY,MAAM,QAAQ,GAAG,GAAG;AACzD,UAAM,IAAI,MAAM,2BAA2B;AAAA,EAC7C;AACA,QAAM,UAAU,OAAO,QAAQ,GAA8B;AAC7D,MAAI,QAAQ,WAAW,GAAG;AACxB,UAAM,IAAI,MAAM,sCAAsC;AAAA,EACxD;AACA,QAAM,SAAsC,CAAC;AAC7C,aAAW,CAAC,MAAM,GAAG,KAAK,SAAS;AACjC,QAAI,CAAC,cAAc,KAAK,IAAI,GAAG;AAC7B,YAAM,IAAI,MAAM,UAAU,IAAI,4CAA4C;AAAA,IAC5E;AACA,QAAI,CAAC,OAAO,OAAO,QAAQ,YAAY,MAAM,QAAQ,GAAG,GAAG;AACzD,YAAM,IAAI,MAAM,UAAU,IAAI,oBAAoB;AAAA,IACpD;AACA,UAAM,QAAQ;AACd,QAAI;AACJ,QAAI,MAAM,YAAY,QAAW;AAC/B,gBAAU,YAAY,IAAI;AAAA,IAC5B,WAAW,OAAO,MAAM,YAAY,YAAY,MAAM,QAAQ,WAAW,GAAG;AAC1E,YAAM,IAAI,MAAM,UAAU,IAAI,qCAAqC;AAAA,IACrE,OAAO;AACL,gBAAU,MAAM;AAAA,IAClB;AAEA,QAAI,MAAM,YAAY,QAAW;AAC/B,YAAM,IAAI;AAAA,QACR,UAAU,IAAI;AAAA,MAChB;AAAA,IACF;AAEA,QAAI,MAAM,QAAQ,QAAW;AAC3B,YAAM,IAAI,MAAM,UAAU,IAAI,gCAAgC;AAAA,IAChE;AACA,UAAM,MAAM,cAAc,MAAM,MAAM,GAAG;AACzC,UAAM,sBAAsB,qBAAqB,MAAM,MAAM,gBAAgB;AAG7E,QAAI,MAAM,WAAW,QAAW;AAC9B,YAAM,IAAI;AAAA,QACR,UAAU,IAAI,0DACc,IAAI,yDACpB,IAAI;AAAA,MAClB;AAAA,IACF;AACA,QAAI,OAAO,MAAM,cAAc,UAAU;AACvC,YAAM,IAAI;AAAA,QACR,UAAU,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAIhB;AAAA,IACF;AACA,eAAW,KAAK,CAAC,kBAAkB,SAAS,SAAS,GAAY;AAC/D,UAAI,MAAM,CAAC,MAAM,QAAW;AAC1B,cAAM,IAAI;AAAA,UACR,UAAU,IAAI,IAAI,CAAC;AAAA,QAErB;AAAA,MACF;AAAA,IACF;AAEA,UAAM,aAAmC,CAAC;AAC1C,QAAI,YAAY,aAAa,OAAW,YAAW,WAAW,YAAY;AAC1E,QAAI,YAAY,cAAc,OAAW,YAAW,YAAY,YAAY;AAC5E,QAAI,MAAM,UAAU,UAAa,MAAM,UAAU,MAAM;AACrD,UAAI,OAAO,MAAM,UAAU,YAAY,MAAM,QAAQ,MAAM,KAAK,GAAG;AACjE,cAAM,IAAI,MAAM,UAAU,IAAI,0BAA0B;AAAA,MAC1D;AACA,YAAM,IAAI,MAAM;AAChB,UAAI,OAAO,UAAU,eAAe,KAAK,GAAG,UAAU,GAAG;AACvD,YAAI,OAAO,EAAE,aAAa,SAAU,YAAW,WAAW,EAAE;AAAA,YACvD,QAAO,WAAW;AAAA,MACzB;AACA,UAAI,OAAO,UAAU,eAAe,KAAK,GAAG,WAAW,GAAG;AACxD,YAAI,OAAO,EAAE,cAAc,SAAU,YAAW,YAAY,EAAE;AAAA,YACzD,QAAO,WAAW;AAAA,MACzB;AAAA,IACF;AAEA,QAAI;AACJ,QAAI,MAAM,cAAc,UAAa,MAAM,cAAc,MAAM;AAC7D,UAAI,YAAY,SAAS;AAKvB,YAAI,OAAO,MAAM,cAAc,YAAY,MAAM,cAAc,QAAQ,MAAM,QAAQ,MAAM,SAAS,GAAG;AACrG,gBAAM,IAAI,MAAM,UAAU,IAAI,8BAA8B;AAAA,QAC9D;AACA,cAAM,IAAI,MAAM;AAChB,cAAM,aAAa,OAAO,KAAK,CAAC,EAAE,OAAO,CAAC,MAAM,MAAM,YAAY,MAAM,gBAAgB;AACxF,YAAI,WAAW,SAAS,GAAG;AACzB,gBAAM,IAAI;AAAA,YACR,UAAU,IAAI,cAAc,WAAW,CAAC,CAAC;AAAA,UAK3C;AAAA,QACF;AAAA,MACF;AACA,uBAAiB,oBAAoB,MAAM,MAAM,WAAW,YAAY,SAAS;AAAA,IACnF;AAEA,UAAM,UAAU,sBAAsB,MAAM,OAAO,UAAU;AAE7D,UAAM,WAAwB;AAAA,MAC5B;AAAA,MACA;AAAA,MACA,OAAO;AAAA,MACP;AAAA,MACA;AAAA,IACF;AACA,QAAI,eAAgB,UAAS,YAAY;AACzC,QAAI,QAAQ,OAAQ,UAAS,SAAS,QAAQ;AAC9C,QAAI,QAAQ,KAAM,UAAS,OAAO,QAAQ;AAC1C,WAAO,IAAI,IAAI;AAAA,EACjB;AACA,SAAO;AACT;AAEA,SAAS,aAAa,KAA6C;AACjE,QAAM,UAAU,OAAO;AACvB,MAAI,YAAY,WAAW,YAAY,YAAY,YAAY,UAAU;AACvE,UAAM,IAAI,MAAM,wDAAwD,OAAO,IAAI;AAAA,EACrF;AACA,SAAO;AACT;AAEA,SAAS,WAAW,KAAyE;AAC3F,QAAM,MAAiD,CAAC;AACxD,MAAI,IAAI,SAAS,OAAO,IAAI,UAAU,UAAU;AAC9C,UAAM,IAAI,IAAI;AACd,QAAI,OAAO,EAAE,aAAa,SAAU,KAAI,WAAW,EAAE;AACrD,QAAI,OAAO,EAAE,cAAc,SAAU,KAAI,YAAY,EAAE;AAAA,EACzD;AACA,SAAO;AACT;AAEO,SAAS,gBACd,UACA,OAA+B,CAAC,GACnB;AACb,QAAM,MAAM,MAAM,QAAQ,KAAK,CAAC;AAChC,MAAI,OAAO,QAAQ,YAAY,QAAQ,QAAQ,MAAM,QAAQ,GAAG,GAAG;AACjE,UAAM,IAAI,MAAM,kCAAkC;AAAA,EACpD;AACA,QAAM,IAAI;AAEV,MAAI,EAAE,cAAc,QAAW;AAC7B,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,MAAI,EAAE,WAAW,QAAW;AAC1B,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,MAAI,EAAE,YAAY,QAAW;AAC3B,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,MAAI,EAAE,WAAW,QAAW;AAC1B,UAAM,IAAI;AAAA,MACR;AAAA,IAEF;AAAA,EACF;AACA,MAAI,EAAE,WAAW,QAAW;AAC1B,UAAM,IAAI,MAAM,sEAAiE;AAAA,EACnF;AAEA,QAAM,UAAU,aAAa,EAAE,OAAO;AACtC,QAAM,aAAa,QAAQ;AAE3B,MAAI;AACJ,MAAI,EAAE,gBAAgB,QAAW;AAC/B,QAAI,OAAO,EAAE,gBAAgB,YAAY,EAAE,YAAY,WAAW,GAAG;AACnE,YAAM,IAAI,MAAM,wCAAwC;AAAA,IAC1D;AACA,QAAI,CAAC,QAAQ,KAAK,EAAE,WAAW,GAAG;AAChC,YAAM,IAAI,MAAM,8CAA8C,KAAK,UAAU,EAAE,WAAW,CAAC,GAAG;AAAA,IAChG;AACA,kBAAc,EAAE;AAAA,EAClB;AAEA,QAAM,aAAa,gBAAgB,EAAE,YAAY,YAAY,WAAW;AACxE,QAAM,QAAQ,WAAW,CAAC;AAC1B,QAAM,SAAS,YAAY,EAAE,QAAQ,SAAS,YAAY,OAAO,YAAY,KAAK,SAAS;AAE3F,QAAM,MAAmB;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,MAAI,gBAAgB,OAAW,KAAI,cAAc;AACjD,MAAI,EAAE,cAAc,UAAa,EAAE,cAAc,MAAM;AACrD,QAAI,YAAY,SAAS;AACvB,YAAM,IAAI;AAAA,QACR;AAAA,MAEF;AAAA,IACF;AACA,QAAI,YAAY,oBAAoB,EAAE,SAAS;AAAA,EACjD;AACA,SAAO;AACT;AAEO,SAAS,cACd,KACA,MAC6B;AAC7B,SAAO,IAAI,WAAW,IAAI;AAC5B;AAEO,SAAS,oBACd,KAC2D;AAC3D,QAAM,WAAW,OAAO,QAAQ,IAAI,UAAU,EAAE;AAAA,IAC9C,CAAC,MAA4C,EAAE,CAAC,EAAE,SAAS;AAAA,EAC7D;AACA,MAAI,SAAS,WAAW,EAAG,QAAO;AAClC,MAAI,SAAS,SAAS,GAAG;AACvB,UAAM,IAAI;AAAA,MACR,6DAA6D,SAC1D,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,EACf,KAAK,IAAI,CAAC;AAAA,IACf;AAAA,EACF;AACA,QAAM,CAAC,MAAM,SAAS,IAAI,SAAS,CAAC;AACpC,SAAO,EAAE,MAAM,UAAU;AAC3B;AAEO,SAAS,kBACd,KACyD;AACzD,QAAM,QAAQ,OAAO,QAAQ,IAAI,UAAU,EAAE;AAAA,IAC3C,CAAC,MAA0C,EAAE,CAAC,EAAE,SAAS;AAAA,EAC3D;AACA,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,MAAI,MAAM,SAAS,GAAG;AACpB,UAAM,IAAI;AAAA,MACR,yDAAyD,MACtD,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,EACf,KAAK,IAAI,CAAC;AAAA,IACf;AAAA,EACF;AACA,QAAM,CAAC,MAAM,SAAS,IAAI,MAAM,CAAC;AACjC,SAAO,EAAE,MAAM,UAAU;AAC3B;AAMO,SAAS,eAAe,KAAqC;AAClE,QAAM,IAAI,KAAK,KAAK,YAAY;AAChC,MAAI,WAAW,CAAC,EAAG,QAAO,EAAE,MAAM,EAAE;AACpC,QAAM,SAAS,KAAK,KAAK,gBAAgB;AACzC,MAAI,WAAW,MAAM,GAAG;AACtB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEO,SAAS,cAAc,MAAmB,OAA8B;AAC7E,QAAM,cAAc,MAAM;AAC1B,MACE,gBAAgB,UAChB,gBAAgB,WAChB,gBAAgB,YAChB,gBAAgB,UAChB;AACA,UAAM,IAAI,MAAM,wDAAwD,MAAM,OAAO,IAAI;AAAA,EAC3F;AACA,QAAM,UAAU,eAAe,KAAK;AACpC,QAAM,SAAsB;AAAA,IAC1B;AAAA,IACA,YAAY,KAAK;AAAA,IACjB,QAAQ,KAAK;AAAA,IACb,OAAO,EAAE,GAAG,KAAK,MAAM;AAAA,EACzB;AACA,MAAI,YAAY,YAAY,YAAY,UAAU;AAChD,UAAM,QAAQ,MAAM,SAAS,KAAK,WAAW;AAC7C,QAAI,UAAU,QAAW;AACvB,aAAO,YAAY,EAAE,MAAM;AAAA,IAC7B,WAAW,KAAK,cAAc,QAAW;AACvC,aAAO,YAAY,EAAE,GAAG,KAAK,UAAU;AAAA,IACzC;AAAA,EACF;AACA,SAAO;AACT;;;AEv8BA,SAAS,iBAAiB;AAC1B,SAAS,QAAAA,aAAY;AACrB;AAAA,EACE;AAAA,EACA;AAAA,OAOK;AAgHA,IAAM,mBAAN,MAA8C;AAAA,EAC1C;AAAA,EACQ,UAAU,oBAAI,IAAuB;AAAA,EAEtD;AAAA,EACA;AAAA,EAEA,YAAY,MAA+B;AACzC,SAAK,OAAO;AACZ,SAAK,UAAU,KAAK,YAAY,MAAM;AAAA,IAAC;AACvC,QAAI,KAAK,mBAAmB;AAC1B,WAAK,oBAAoB,KAAK;AAAA,IAChC,WAAW,KAAK,WAAW;AACzB,YAAM,aAAa,KAAK;AACxB,WAAK,oBAAoB,OAAO,MAAM,QAAQ;AAC5C,cAAM,MAAM,KAAK,KAAK,OAAO,IAAI;AACjC,cAAM,SAAS,WAAW,SAAS,MAAM,IAAI,WAAW,KAAK;AAAA,UAC3D,WAAW,KAAK,uBAAuB;AAAA,QACzC,CAAC;AACD,aAAK,KAAK,uBAAuB,MAAM;AACvC,eAAO,OAAO;AAAA,MAChB;AAAA,IACF,OAAO;AACL,WAAK,oBAAoB,aAAa,EAAE,UAAU,SAAS;AAAA,IAC7D;AAAA,EACF;AAAA,EAEA,SAAS,MAAuB;AAC9B,WAAO,OAAO,UAAU,eAAe,KAAK,KAAK,KAAK,QAAQ,IAAI;AAAA,EACpE;AAAA,EAEA,gBAAgB,MAAuB;AACrC,WAAO,QAAQ,KAAK,KAAK,gBAAgB,IAAI,CAAC;AAAA,EAChD;AAAA,EAEA,gBAAgB,MAAsC;AACpD,WAAO,KAAK,KAAK,MAAM,IAAI,KAAK,CAAC;AAAA,EACnC;AAAA,EAEA,kBAAkB,MAAkC;AAClD,WAAO,KAAK,KAAK,QAAQ,IAAI;AAAA,EAC/B;AAAA,EAEA,mBAAmB,MAA0B;AAC3C,WAAO,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC;AAAA,EACtC;AAAA,EAEA,gBAAgB,MAAsB;AACpC,WACE,KAAK,KAAK,MAAM,IAAI,KACpB,KAAK,KAAK,OAAO,IAAI,GAAG,WACxB,QAAQ,IAAI;AAAA,EAEhB;AAAA,EAEA,aAAuB;AACrB,WAAO,OAAO,KAAK,KAAK,KAAK,MAAM;AAAA,EACrC;AAAA,EAEA,qBAAqB,MAAsB;AACzC,WAAO,KAAK,KAAK,OAAO,IAAI,GAAG,uBAAuB;AAAA,EACxD;AAAA,EAEA,MAAM,cAAc,MAAc,UAAkB,WAAqC;AACvF,QAAI,CAAC,KAAK,SAAS,IAAI,EAAG,OAAM,IAAI,MAAM,kBAAkB,IAAI,EAAE;AAClE,UAAM,SAAS,MAAM,KAAK,aAAa,IAAI;AAC3C,WAAO,OAAO,cAAc,UAAU,SAAS;AAAA,EACjD;AAAA,EAEA,WAAW,MAAc,UAAwB;AAC/C,QAAI,CAAC,KAAK,SAAS,IAAI,EAAG;AAC1B,UAAM,SAAS,KAAK,QAAQ,IAAI,IAAI;AACpC,YAAQ,WAAW,QAAQ;AAAA,EAC7B;AAAA,EAEA,MAAM,cAAc,MAAc,WAAkC;AAClE,QAAI,CAAC,KAAK,SAAS,IAAI,EAAG;AAC1B,UAAM,SAAS,KAAK,QAAQ,IAAI,IAAI;AAGpC,SAAK,KAAK,WAAW,cAAc,SAAS;AAC5C,QAAI,CAAC,OAAQ;AACb,QAAI;AACF,YAAM,OAAO,OAAO,SAAS;AAAA,IAC/B,SAAS,KAAK;AAGZ,cAAQ,KAAK,QAAQ,IAAI,YAAY,SAAS,aAAa,GAAG;AAAA,IAChE;AAAA,EACF;AAAA,EAEA,MAAM,OAAO,MAAc,OAA2C;AACpE,QAAI,CAAC,KAAK,SAAS,IAAI,EAAG,OAAM,IAAI,MAAM,kBAAkB,IAAI,EAAE;AAClE,UAAM,SAAS,MAAM,KAAK,aAAa,IAAI;AAC3C,WAAO,OAAO,OAAO,KAAK;AAAA,EAC5B;AAAA,EAEA,MAAM,UAAyB;AAC7B,UAAM,QAAQ;AAAA,MACZ,CAAC,GAAG,KAAK,QAAQ,OAAO,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC;AAAA,IAChD;AACA,SAAK,QAAQ,MAAM;AAAA,EACrB;AAAA,EAEA,MAAc,aAAa,MAAkC;AAC3D,UAAM,WAAW,KAAK,QAAQ,IAAI,IAAI;AACtC,QAAI,SAAU,QAAO;AACrB,UAAM,MAAM,KAAK,KAAK,OAAO,IAAI;AACjC,QAAI,CAAC,IAAI,IAAK,OAAM,IAAI,MAAM,UAAU,IAAI,qBAAqB;AACjE,UAAM,QAAQ,oBAAoB,IAAI,GAAG;AACzC,eAAW,OAAO,KAAK,KAAK,eAAe,IAAI,KAAK,CAAC,GAAG;AACtD,gBAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA,IACpC;AACA,UAAM,SAAS,IAAI,UAAU;AAAA,MAC3B,OAAO;AAAA,QACL,IAAI;AAAA,QACJ,SAAS,MAAM;AAAA,QACf,MAAM,MAAM;AAAA,QACZ,KAAK,KAAK,KAAK,MAAM,IAAI;AAAA,QACzB,KAAK,KAAK,gBAAgB,IAAI;AAAA,QAC9B,OAAO,KAAK,KAAK,QAAQ,IAAI;AAAA,QAC7B,QAAQ,KAAK,mBAAmB,IAAI;AAAA,MACtC;AAAA,MACA,cAAc,KAAK,KAAK,YAAYA,MAAK,KAAK,KAAK,WAAW,IAAI,IAAI;AAAA,MACtE,SAAS,KAAK,KAAK;AAAA,MACnB,SAAS,CAAC,MAAM,KAAK,QAAQ,MAAM,CAAC;AAAA,MACpC,mBAAmB,CAAC,QAAQ,KAAK,kBAAkB,MAAM,GAAG;AAAA,MAC5D,OAAO,KAAK,KAAK,QAAQ,CAAC,MAAM,KAAK,KAAK,MAAO,MAAM,CAAC,IAAI;AAAA,MAC5D,cAAc,KAAK,KAAK,gBAAgB,IAAI;AAAA,IAC9C,CAAC;AACD,UAAM,OAAO,MAAM;AACnB,SAAK,QAAQ,IAAI,MAAM,MAAM;AAC7B,WAAO;AAAA,EACT;AACF;AAEO,SAAS,oBAAoB,MAGlC;AACA,MAAI,YAAY,QAAQ,KAAK,QAAQ;AACnC,WAAO,cAAc,KAAK,QAAQ,EAAE,OAAO,KAAK,MAAM,CAAC;AAAA,EACzD;AACA,MAAI,aAAa,QAAQ,KAAK,SAAS;AACrC,WAAO,EAAE,SAAS,KAAK,SAAS,MAAM,KAAK,QAAQ,CAAC,EAAE;AAAA,EACxD;AACA,QAAM,IAAI,MAAM,qDAAqD;AACvE;;;AC9QA,SAAS,oBAAoB;AAC7B,SAAS,kBAAkB;AAqCpB,IAAM,qBAAN,cAAiC,aAAa;AAAA,EAClC,UAAU,oBAAI,IAA0B;AAAA,EACxC,YAAY,oBAAI,IAAyB;AAAA,EAE1D,SACE,WACA,WACA,KACA,OAAwB,CAAC,GACL;AACpB,UAAM,aAAa,WAAW;AAC9B,QAAI;AACJ,UAAM,kBAAkB,IAAI,QAA0B,CAAC,MAAM;AAC3D,gBAAU;AAAA,IACZ,CAAC;AACD,UAAM,QAAsB;AAAA,MAC1B;AAAA,MACA;AAAA,MACA;AAAA,MACA,YAAY,IAAI;AAAA,MAChB,UAAU,IAAI;AAAA,MACd,WAAW,IAAI;AAAA,MACf,WAAW,IAAI;AAAA,MACf,SAAS,IAAI;AAAA,MACb;AAAA,MACA;AAAA,IACF;AACA,QAAI,KAAK,aAAa,KAAK,YAAY,GAAG;AACxC,YAAM,QAAQ,WAAW,MAAM;AAC7B,YAAI,KAAK,QAAQ,IAAI,UAAU,MAAM,MAAO;AAC5C,cAAM,QAAQ,EAAE,UAAU,SAAS,CAAC;AACpC,aAAK,QAAQ,OAAO,UAAU;AAC9B,aAAK,UAAU,IAAI,SAAS,GAAG,OAAO,UAAU;AAChD,aAAK,KAAK,WAAW,EAAE,YAAY,WAAW,UAAU,CAAC;AAAA,MAC3D,GAAG,KAAK,SAAS;AACjB,YAAM,MAAM,QAAQ;AAAA,IACtB;AACA,SAAK,QAAQ,IAAI,YAAY,KAAK;AAClC,QAAI,MAAM,KAAK,UAAU,IAAI,SAAS;AACtC,QAAI,CAAC,KAAK;AACR,YAAM,oBAAI,IAAI;AACd,WAAK,UAAU,IAAI,WAAW,GAAG;AAAA,IACnC;AACA,QAAI,IAAI,UAAU;AAClB,SAAK,KAAK,cAAc,KAAK,SAAS,KAAK,CAAC;AAC5C,WAAO,KAAK,SAAS,KAAK;AAAA,EAC5B;AAAA,EAEA,QACE,WACA,YACA,UACS;AACT,UAAM,QAAQ,KAAK,QAAQ,IAAI,UAAU;AACzC,QAAI,CAAC,SAAS,MAAM,cAAc,UAAW,QAAO;AACpD,QAAI,MAAM,MAAO,cAAa,MAAM,KAAK;AACzC,UAAM,QAAQ,QAAQ;AACtB,SAAK,QAAQ,OAAO,UAAU;AAC9B,SAAK,UAAU,IAAI,SAAS,GAAG,OAAO,UAAU;AAChD,WAAO;AAAA,EACT;AAAA,EAEA,cAAc,WAAyB;AACrC,UAAM,MAAM,KAAK,UAAU,IAAI,SAAS;AACxC,QAAI,CAAC,IAAK;AACV,eAAW,MAAM,CAAC,GAAG,GAAG,GAAG;AACzB,YAAM,QAAQ,KAAK,QAAQ,IAAI,EAAE;AACjC,UAAI,OAAO;AACT,YAAI,MAAM,MAAO,cAAa,MAAM,KAAK;AACzC,cAAM,QAAQ,EAAE,UAAU,SAAS,CAAC;AACpC,aAAK,QAAQ,OAAO,EAAE;AAAA,MACxB;AAAA,IACF;AACA,SAAK,UAAU,OAAO,SAAS;AAAA,EACjC;AAAA,EAEA,YAAY,WAAyC;AACnD,UAAM,MAAM,KAAK,UAAU,IAAI,SAAS;AACxC,QAAI,CAAC,IAAK,QAAO,CAAC;AAClB,UAAM,MAA4B,CAAC;AACnC,eAAW,MAAM,KAAK;AACpB,YAAM,QAAQ,KAAK,QAAQ,IAAI,EAAE;AACjC,UAAI,MAAO,KAAI,KAAK,KAAK,SAAS,KAAK,CAAC;AAAA,IAC1C;AACA,WAAO;AAAA,EACT;AAAA,EAEA,OAAe;AACb,WAAO,KAAK,QAAQ;AAAA,EACtB;AAAA,EAEQ,SAAS,OAAyC;AACxD,WAAO;AAAA,MACL,YAAY,MAAM;AAAA,MAClB,WAAW,MAAM;AAAA,MACjB,WAAW,MAAM;AAAA,MACjB,YAAY,MAAM;AAAA,MAClB,UAAU,MAAM;AAAA,MAChB,WAAW,MAAM;AAAA,MACjB,WAAW,MAAM;AAAA,MACjB,SAAS,MAAM;AAAA,MACf,iBAAiB,MAAM;AAAA,IACzB;AAAA,EACF;AACF;","names":["join"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zooid/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "zooid core: SessionRunner, Chunker, hooks, config parsing, and the Runtime/Adapter/Transport interfaces.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"dotenv-expand": "^13.0.0",
|
|
28
28
|
"yaml": "^2.5.0",
|
|
29
|
-
"@zooid/acp-client": "^0.
|
|
29
|
+
"@zooid/acp-client": "^0.10.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@types/node": "^22.0.0",
|
package/src/config.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import { loadZooidConfig, mergeCliFlags } from './config.js'
|
|
2
|
+
import { loadZooidConfig, mergeCliFlags, findMatrixTransport } from './config.js'
|
|
3
3
|
import type { ZooidConfig } from './types.js'
|
|
4
4
|
|
|
5
5
|
const HTTP_TRANSPORT = `
|
|
@@ -1394,3 +1394,105 @@ agents:
|
|
|
1394
1394
|
expect(cfg.agents.docs.matrix?.user_id).toBe('@docs-agent:localhost')
|
|
1395
1395
|
})
|
|
1396
1396
|
})
|
|
1397
|
+
|
|
1398
|
+
// --- ZOD063: workstation, mode, port xor advertise_url ---
|
|
1399
|
+
|
|
1400
|
+
const workstationBase = (extra: string) => `
|
|
1401
|
+
runtime: podman
|
|
1402
|
+
workstation: laptop
|
|
1403
|
+
transports:
|
|
1404
|
+
matrix:
|
|
1405
|
+
homeserver: https://zoon.eco
|
|
1406
|
+
as_token: as-tok
|
|
1407
|
+
hs_token: hs-tok
|
|
1408
|
+
${extra}
|
|
1409
|
+
agents:
|
|
1410
|
+
docs:
|
|
1411
|
+
acp: { preset: opencode }
|
|
1412
|
+
matrix: { rooms: ['#docs'] }
|
|
1413
|
+
`
|
|
1414
|
+
|
|
1415
|
+
describe('ZOD063: transport mode', () => {
|
|
1416
|
+
it("defaults mode to 'appservice'", () => {
|
|
1417
|
+
const cfg = loadZooidConfig(workstationBase(' port: 9099'))
|
|
1418
|
+
expect(findMatrixTransport(cfg)!.transport.mode).toBe('appservice')
|
|
1419
|
+
})
|
|
1420
|
+
it('rejects an unknown mode', () => {
|
|
1421
|
+
expect(() => loadZooidConfig(workstationBase(' mode: peer\n port: 9099')))
|
|
1422
|
+
.toThrow(/mode.*appservice.*client/i)
|
|
1423
|
+
})
|
|
1424
|
+
})
|
|
1425
|
+
|
|
1426
|
+
describe('ZOD063: port xor advertise_url', () => {
|
|
1427
|
+
it('errors when BOTH are set', () => {
|
|
1428
|
+
expect(() =>
|
|
1429
|
+
loadZooidConfig(workstationBase(' port: 9099\n advertise_url: http://10.0.1.5:9099')),
|
|
1430
|
+
).toThrow(/port.*advertise_url|advertise_url.*port/i)
|
|
1431
|
+
})
|
|
1432
|
+
it('defaults to port 9099 when NEITHER is set (co-located mode)', () => {
|
|
1433
|
+
const m = findMatrixTransport(loadZooidConfig(workstationBase('')))!.transport
|
|
1434
|
+
expect(m.port).toBe(9099)
|
|
1435
|
+
expect(m.advertise_url).toBeUndefined()
|
|
1436
|
+
})
|
|
1437
|
+
it('accepts advertise_url alone', () => {
|
|
1438
|
+
const m = findMatrixTransport(
|
|
1439
|
+
loadZooidConfig(workstationBase(' advertise_url: http://10.0.1.5:9099')),
|
|
1440
|
+
)!.transport
|
|
1441
|
+
expect(m.advertise_url).toBe('http://10.0.1.5:9099')
|
|
1442
|
+
expect(m.port).toBeUndefined()
|
|
1443
|
+
})
|
|
1444
|
+
})
|
|
1445
|
+
|
|
1446
|
+
describe('ZOD063: workstation-derived identity', () => {
|
|
1447
|
+
it('derives sender_localpart and an exclusive workstation namespace from top-level workstation', () => {
|
|
1448
|
+
const m = findMatrixTransport(loadZooidConfig(workstationBase(' port: 9099')))!.transport
|
|
1449
|
+
expect(m.sender_localpart).toBe('laptop')
|
|
1450
|
+
expect(m.user_namespace).toBe('@laptop\\..*:zoon.eco')
|
|
1451
|
+
})
|
|
1452
|
+
it('rejects an invalid workstation', () => {
|
|
1453
|
+
expect(() =>
|
|
1454
|
+
loadZooidConfig(
|
|
1455
|
+
workstationBase(' port: 9099').replace('workstation: laptop', 'workstation: Lap.Top'),
|
|
1456
|
+
),
|
|
1457
|
+
).toThrow(/workstation/i)
|
|
1458
|
+
})
|
|
1459
|
+
})
|
|
1460
|
+
|
|
1461
|
+
describe('ZOD067: workstation-derived agent MXID default', () => {
|
|
1462
|
+
// workstationBase declares `workstation: laptop`, homeserver zoon.eco, and an
|
|
1463
|
+
// agent `docs` with no explicit user_id — exactly the derivation case.
|
|
1464
|
+
it('derives @{workstation}.{name} when a workstation is set and user_id is omitted', () => {
|
|
1465
|
+
const cfg = loadZooidConfig(workstationBase(' port: 9099'))
|
|
1466
|
+
expect(cfg.agents.docs!.matrix?.user_id).toBe('@laptop.docs:zoon.eco')
|
|
1467
|
+
})
|
|
1468
|
+
|
|
1469
|
+
it('derives an in-namespace MXID — it matches the transport user_namespace', () => {
|
|
1470
|
+
const cfg = loadZooidConfig(workstationBase(' port: 9099'))
|
|
1471
|
+
// user_namespace is @laptop\..*:zoon.eco — the derived MXID must satisfy it,
|
|
1472
|
+
// which is exactly what avoids M_EXCLUSIVE at impersonation time.
|
|
1473
|
+
const ns = findMatrixTransport(cfg)!.transport.user_namespace
|
|
1474
|
+
expect(new RegExp(`^${ns}$`).test(cfg.agents.docs!.matrix!.user_id)).toBe(true)
|
|
1475
|
+
})
|
|
1476
|
+
|
|
1477
|
+
it('still honors an explicit user_id over the derived default', () => {
|
|
1478
|
+
const yaml = workstationBase(' port: 9099').replace(
|
|
1479
|
+
"matrix: { rooms: ['#docs'] }",
|
|
1480
|
+
"matrix: { user_id: '@laptop.custom:zoon.eco', rooms: ['#docs'] }",
|
|
1481
|
+
)
|
|
1482
|
+
expect(loadZooidConfig(yaml).agents.docs!.matrix?.user_id).toBe('@laptop.custom:zoon.eco')
|
|
1483
|
+
})
|
|
1484
|
+
|
|
1485
|
+
it('leaves the flat @{name} default unchanged when no workstation is declared', () => {
|
|
1486
|
+
const cfg = loadZooidConfig(`
|
|
1487
|
+
runtime: local
|
|
1488
|
+
${MATRIX_TRANSPORT.trimStart()}
|
|
1489
|
+
agents:
|
|
1490
|
+
zooid-assistant:
|
|
1491
|
+
acp: { preset: claude }
|
|
1492
|
+
matrix:
|
|
1493
|
+
transport: matrix-local
|
|
1494
|
+
rooms: ['#zooid']
|
|
1495
|
+
`)
|
|
1496
|
+
expect(cfg.agents['zooid-assistant']!.matrix?.user_id).toBe('@zooid-assistant:localhost')
|
|
1497
|
+
})
|
|
1498
|
+
})
|
package/src/config.ts
CHANGED
|
@@ -19,6 +19,8 @@ import type {
|
|
|
19
19
|
ZooidContainerConfig,
|
|
20
20
|
} from './types.js'
|
|
21
21
|
|
|
22
|
+
const SLUG_RE = /^[a-z0-9-]+$/
|
|
23
|
+
|
|
22
24
|
export interface LoadZooidConfigOptions {
|
|
23
25
|
/**
|
|
24
26
|
* Directory containing zooid.yaml. Required when any agent uses a
|
|
@@ -324,6 +326,7 @@ function parseZooidContainer(raw: unknown): ZooidContainerConfig {
|
|
|
324
326
|
function parseTransports(
|
|
325
327
|
raw: unknown,
|
|
326
328
|
processEnv: NodeJS.ProcessEnv,
|
|
329
|
+
workstation?: string,
|
|
327
330
|
): Record<string, TransportConfig> {
|
|
328
331
|
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
329
332
|
throw new Error('transports: must be a mapping with at least one entry')
|
|
@@ -335,7 +338,7 @@ function parseTransports(
|
|
|
335
338
|
}
|
|
336
339
|
const out: Record<string, TransportConfig> = {}
|
|
337
340
|
for (const name of names) {
|
|
338
|
-
out[name] = parseTransport(name, r[name], processEnv)
|
|
341
|
+
out[name] = parseTransport(name, r[name], processEnv, workstation)
|
|
339
342
|
}
|
|
340
343
|
const matrixEntries = Object.entries(out).filter(
|
|
341
344
|
(e): e is [string, MatrixTransportConfig] => e[1].type === 'matrix',
|
|
@@ -379,6 +382,7 @@ function parseTransport(
|
|
|
379
382
|
name: string,
|
|
380
383
|
raw: unknown,
|
|
381
384
|
processEnv: NodeJS.ProcessEnv,
|
|
385
|
+
workstation?: string,
|
|
382
386
|
): TransportConfig {
|
|
383
387
|
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
384
388
|
throw new Error(`transports.${name}: must be a mapping`)
|
|
@@ -392,19 +396,46 @@ function parseTransport(
|
|
|
392
396
|
)
|
|
393
397
|
}
|
|
394
398
|
if (inferredType === 'matrix') {
|
|
395
|
-
|
|
396
|
-
|
|
399
|
+
// Validate mode first so we can give a clear error.
|
|
400
|
+
const mode = r.mode ?? 'appservice'
|
|
401
|
+
if (mode !== 'appservice' && mode !== 'client') {
|
|
402
|
+
throw new Error(
|
|
403
|
+
`transports.${name}.mode must be "appservice" or "client" (got ${JSON.stringify(r.mode)})`,
|
|
404
|
+
)
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// port xor advertise_url
|
|
408
|
+
if (r.port !== undefined && r.advertise_url !== undefined) {
|
|
409
|
+
throw new Error(
|
|
410
|
+
`transports.${name}: set 'port' or 'advertise_url', not both`,
|
|
411
|
+
)
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Workstation-derived defaults: sender_localpart and user_namespace
|
|
415
|
+
let resolvedHost: string | undefined
|
|
416
|
+
if (typeof r.homeserver === 'string') {
|
|
397
417
|
try {
|
|
398
|
-
|
|
399
|
-
interpolateString(r.homeserver as string, processEnv),
|
|
400
|
-
).hostname
|
|
401
|
-
if (host) r.user_namespace = `@.*:${host}`
|
|
418
|
+
resolvedHost = new URL(interpolateString(r.homeserver as string, processEnv)).hostname
|
|
402
419
|
} catch {
|
|
403
|
-
//
|
|
404
|
-
// non-empty string" error, which is the same message today's parser
|
|
405
|
-
// would emit for a bad homeserver URL.
|
|
420
|
+
// fall through — required-field check below fires the error
|
|
406
421
|
}
|
|
407
422
|
}
|
|
423
|
+
|
|
424
|
+
if (workstation !== undefined) {
|
|
425
|
+
if (!SLUG_RE.test(workstation)) {
|
|
426
|
+
throw new Error(`workstation must match /^[a-z0-9-]+$/ (got ${JSON.stringify(workstation)})`)
|
|
427
|
+
}
|
|
428
|
+
if (r.sender_localpart === undefined) r.sender_localpart = workstation
|
|
429
|
+
if (r.user_namespace === undefined && resolvedHost) {
|
|
430
|
+
r.user_namespace = `@${workstation}\\..*:${resolvedHost}`
|
|
431
|
+
}
|
|
432
|
+
} else {
|
|
433
|
+
if (r.sender_localpart === undefined) r.sender_localpart = 'zooid'
|
|
434
|
+
if (r.user_namespace === undefined && resolvedHost) {
|
|
435
|
+
r.user_namespace = `@.*:${resolvedHost}`
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
408
439
|
if (r.as_token === undefined) r.as_token = '__INFER__'
|
|
409
440
|
if (r.hs_token === undefined) r.hs_token = '__INFER__'
|
|
410
441
|
const fields = [
|
|
@@ -426,7 +457,10 @@ function parseTransport(
|
|
|
426
457
|
hs_token: interpolateString(r.hs_token as string, processEnv),
|
|
427
458
|
sender_localpart: r.sender_localpart as string,
|
|
428
459
|
user_namespace: r.user_namespace as string,
|
|
460
|
+
mode: mode as 'appservice' | 'client',
|
|
429
461
|
}
|
|
462
|
+
if (workstation !== undefined) out.workstation = workstation
|
|
463
|
+
|
|
430
464
|
if (r.port !== undefined) {
|
|
431
465
|
if (!Number.isInteger(r.port)) {
|
|
432
466
|
throw new Error(
|
|
@@ -434,7 +468,16 @@ function parseTransport(
|
|
|
434
468
|
)
|
|
435
469
|
}
|
|
436
470
|
out.port = r.port as number
|
|
471
|
+
} else if (r.advertise_url !== undefined) {
|
|
472
|
+
if (typeof r.advertise_url !== 'string' || r.advertise_url.length === 0) {
|
|
473
|
+
throw new Error(`transports.${name}.advertise_url must be a non-empty string`)
|
|
474
|
+
}
|
|
475
|
+
out.advertise_url = r.advertise_url as string
|
|
476
|
+
} else {
|
|
477
|
+
// Neither set — default to co-located port
|
|
478
|
+
out.port = 9099
|
|
437
479
|
}
|
|
480
|
+
|
|
438
481
|
if (r.space !== undefined) {
|
|
439
482
|
if (typeof r.space !== 'string' || r.space.length === 0) {
|
|
440
483
|
throw new Error(
|
|
@@ -553,10 +596,16 @@ function parseTransportBinding(
|
|
|
553
596
|
}
|
|
554
597
|
const serverName = deriveServerName(refTransport.user_namespace)
|
|
555
598
|
|
|
599
|
+
// When the referenced transport is workstationed (ZOD063), an agent with no
|
|
600
|
+
// explicit user_id gets the workstation-scoped MXID @{workstation}.{name},
|
|
601
|
+
// so it lands inside the AS's exclusive @{workstation}\..* namespace.
|
|
602
|
+
// Without this it would default to @{name} and be rejected (M_EXCLUSIVE).
|
|
556
603
|
const rawUserId =
|
|
557
604
|
typeof block.user_id === 'string' && block.user_id.length > 0
|
|
558
605
|
? block.user_id
|
|
559
|
-
:
|
|
606
|
+
: refTransport.workstation
|
|
607
|
+
? `@${refTransport.workstation}.${name}`
|
|
608
|
+
: `@${name}`
|
|
560
609
|
let userId = rawUserId
|
|
561
610
|
if (!userId.includes(':') && MATRIX_USER_LOCALPART_RE.test(userId)) {
|
|
562
611
|
userId = `${userId}:${serverName}`
|
|
@@ -796,7 +845,19 @@ export function loadZooidConfig(
|
|
|
796
845
|
|
|
797
846
|
const runtime = parseRuntime(r.runtime)
|
|
798
847
|
const processEnv = process.env
|
|
799
|
-
|
|
848
|
+
|
|
849
|
+
let workstation: string | undefined
|
|
850
|
+
if (r.workstation !== undefined) {
|
|
851
|
+
if (typeof r.workstation !== 'string' || r.workstation.length === 0) {
|
|
852
|
+
throw new Error('workstation must be a non-empty string')
|
|
853
|
+
}
|
|
854
|
+
if (!SLUG_RE.test(r.workstation)) {
|
|
855
|
+
throw new Error(`workstation must match /^[a-z0-9-]+$/ (got ${JSON.stringify(r.workstation)})`)
|
|
856
|
+
}
|
|
857
|
+
workstation = r.workstation
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
const transports = parseTransports(r.transports, processEnv, workstation)
|
|
800
861
|
const hooks = zooidHooks(r)
|
|
801
862
|
const agents = parseAgents(r.agents, runtime, transports, hooks, processEnv, opts.configDir)
|
|
802
863
|
|
|
@@ -806,6 +867,7 @@ export function loadZooidConfig(
|
|
|
806
867
|
agents,
|
|
807
868
|
hooks,
|
|
808
869
|
}
|
|
870
|
+
if (workstation !== undefined) cfg.workstation = workstation
|
|
809
871
|
if (r.container !== undefined && r.container !== null) {
|
|
810
872
|
if (runtime === 'local') {
|
|
811
873
|
throw new Error(
|
package/src/types.ts
CHANGED
|
@@ -69,8 +69,8 @@ export interface ContainerConfig {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
|
-
* Workforce-level container defaults. Currently `image` only —
|
|
73
|
-
*
|
|
72
|
+
* Workforce-level container defaults. Currently `image` only — workforce-level
|
|
73
|
+
* env is intentionally unsupported (set env per-agent under `container.env`).
|
|
74
74
|
*/
|
|
75
75
|
export interface ZooidContainerConfig {
|
|
76
76
|
image?: string
|
|
@@ -181,9 +181,8 @@ export interface AgentConfig {
|
|
|
181
181
|
|
|
182
182
|
/**
|
|
183
183
|
* Matrix application-service transport. The CLI binds the AS HTTP listener
|
|
184
|
-
* to `port` (defaults to
|
|
185
|
-
*
|
|
186
|
-
* the registration YAML's `url` (read by the homeserver, not by Zooid).
|
|
184
|
+
* to `port` (defaults to 9099 for co-located/community-box mode). Must match
|
|
185
|
+
* the port in the registration YAML's `url` (read by the homeserver, not by Zooid).
|
|
187
186
|
*/
|
|
188
187
|
export interface MatrixTransportConfig {
|
|
189
188
|
type: 'matrix'
|
|
@@ -200,22 +199,38 @@ export interface MatrixTransportConfig {
|
|
|
200
199
|
hs_token: string
|
|
201
200
|
/**
|
|
202
201
|
* Localpart of the AS sender user (the bridge bot).
|
|
203
|
-
* @default 'zooid'
|
|
202
|
+
* @default 'zooid', or the workstation name when `workstation` is set
|
|
204
203
|
*/
|
|
205
204
|
sender_localpart: string
|
|
206
205
|
/**
|
|
207
206
|
* Regex covering all bot users, e.g. `@.*:example.com`.
|
|
208
|
-
* @default `@.*:<host
|
|
207
|
+
* @default `@.*:<host>`, or workstation-scoped `@<workstation>\..*:<host>` when `workstation` is set
|
|
209
208
|
*/
|
|
210
209
|
user_namespace: string
|
|
211
210
|
/**
|
|
212
|
-
* AS HTTP listener port.
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
* in Zooid's logs).
|
|
216
|
-
* @default 9000
|
|
211
|
+
* AS HTTP listener port. Mutually exclusive with `advertise_url`.
|
|
212
|
+
* When set, the registration `url` is `http://host.docker.internal:<port>`.
|
|
213
|
+
* @default 9099 (co-located mode) when neither port nor advertise_url is set
|
|
217
214
|
*/
|
|
218
215
|
port?: number
|
|
216
|
+
/**
|
|
217
|
+
* Explicit registration URL advertised to the homeserver. Mutually exclusive
|
|
218
|
+
* with `port`. Use for multi-workstation setups where the homeserver reaches
|
|
219
|
+
* the daemon via a stable external address.
|
|
220
|
+
*/
|
|
221
|
+
advertise_url?: string
|
|
222
|
+
/**
|
|
223
|
+
* AS mode. `appservice` = push transport (homeserver calls the daemon).
|
|
224
|
+
* `client` = pull transport (daemon polls /_matrix/client/v3/sync).
|
|
225
|
+
* @default 'appservice'
|
|
226
|
+
*/
|
|
227
|
+
mode?: 'appservice' | 'client'
|
|
228
|
+
/**
|
|
229
|
+
* Workstation identity derived from the top-level `workstation:` in
|
|
230
|
+
* zooid.yaml. Present only when a workstation is declared; absent for the
|
|
231
|
+
* shared-namespace `zooid dev` profile.
|
|
232
|
+
*/
|
|
233
|
+
workstation?: string
|
|
219
234
|
/**
|
|
220
235
|
* Workforce space localpart. Resolves to alias `#<space>:<server>`.
|
|
221
236
|
* @default 'dev'
|
|
@@ -244,6 +259,13 @@ export type TransportConfig = MatrixTransportConfig | HttpTransportConfig
|
|
|
244
259
|
*/
|
|
245
260
|
export interface ZooidConfig {
|
|
246
261
|
runtime: 'local' | 'docker' | 'podman'
|
|
262
|
+
/**
|
|
263
|
+
* Workstation identity. A short, lowercase, hyphen-separated name that
|
|
264
|
+
* uniquely identifies this daemon instance (e.g. `laptop`, `ec2-prod`).
|
|
265
|
+
* When set, the matrix transport derives `sender_localpart` and
|
|
266
|
+
* `user_namespace` from it, enabling exclusive per-workstation AS namespaces.
|
|
267
|
+
*/
|
|
268
|
+
workstation?: string
|
|
247
269
|
/** Workforce-wide container defaults. Image only — no workforce-level env. Rejected when runtime: local. */
|
|
248
270
|
container?: ZooidContainerConfig
|
|
249
271
|
/** Required. Map of operator-chosen names → transport config. At least one entry. */
|