agent-worker 0.17.0 → 0.19.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.
@@ -1,744 +0,0 @@
1
- import "./backends-D7DT0uox.mjs";
2
- import "./create-tool-gcUuI1FD.mjs";
3
- import { A as resolveContextDir, C as retrySection, S as projectSection, T as LOOP_DEFAULTS, _ as documentSection, a as getBackendByType, b as inboxSection, c as createAgentLoop, d as generateWorkflowMCPConfig, f as writeBackendMcpConfig, g as buildAgentPrompt, h as assemblePrompt, i as createSilentLogger, l as runSdkAgent, m as activitySection, n as createWiredLoop, o as getBackendForModel, p as DEFAULT_SECTIONS, r as createChannelLogger, s as checkWorkflowIdle, t as createMinimalRuntime, u as runMockAgent, v as exitSection, w as workflowSection, x as instructionsSection, y as formatInbox, z as CONTEXT_DEFAULTS } from "./cli/index.mjs";
4
- import "./memory-provider-Z9D8NdwS.mjs";
5
- import { createWorkflowProvider, initWorkflow, n as interpolate, runWorkflowWithLoops, shutdownLoops, t as createContext } from "./runner-BmT0Y8MD.mjs";
6
- import { existsSync, mkdirSync, readFileSync, rmSync } from "node:fs";
7
- import { basename, dirname, join, resolve } from "node:path";
8
- import { parse } from "yaml";
9
- import { homedir } from "node:os";
10
- import { execFileSync } from "node:child_process";
11
- import { parseArgs } from "node:util";
12
-
13
- //#region src/workflow/types.ts
14
- /** Type guard: is this agent entry a reference to a global agent? */
15
- function isRefAgentEntry(entry) {
16
- return "ref" in entry && typeof entry.ref === "string";
17
- }
18
-
19
- //#endregion
20
- //#region src/workflow/source.ts
21
- /**
22
- * Workflow source resolver — supports local files and remote GitHub references.
23
- *
24
- * Formats:
25
- * Local: ./review.yml, /path/to/review.yml, review.yml
26
- *
27
- * Remote (full path):
28
- * github:owner/repo@ref/path/file.yml (pinned to ref)
29
- * github:owner/repo/path/file.yml (default branch: main)
30
- *
31
- * Remote (shorthand — resolves to workflows/<name>.yml):
32
- * github:owner/repo@ref#name
33
- * github:owner/repo#name
34
- *
35
- * The @ref is always on the repo segment (format D), keeping repo+version
36
- * as a single semantic unit.
37
- *
38
- * Remote sources are cloned (shallow) to a local cache directory:
39
- * ~/.cache/agent-worker/sources/{owner}/{repo}/{ref}/
40
- *
41
- * The `sourceDir` field exposes the repo root, accessible in workflows
42
- * as ${{ source.dir }}.
43
- */
44
- const GITHUB_PREFIX = "github:";
45
- const DEFAULT_REF = "main";
46
- const CACHE_BASE = join(homedir(), ".cache", "agent-worker", "sources");
47
- /** Check if the input is a remote source reference */
48
- function isRemoteSource(input) {
49
- return input.startsWith(GITHUB_PREFIX);
50
- }
51
- /**
52
- * Resolve a workflow source — local file or remote GitHub reference.
53
- * Returns a WorkflowSource that can read the YAML and resolve relative files.
54
- */
55
- async function resolveSource(input) {
56
- if (isRemoteSource(input)) return resolveGitHubSource(input);
57
- return resolveLocalSource(input);
58
- }
59
- function resolveLocalSource(filePath) {
60
- const absolutePath = resolve(filePath);
61
- if (!existsSync(absolutePath)) throw new Error(`Workflow file not found: ${absolutePath}`);
62
- const content = readFileSync(absolutePath, "utf-8");
63
- const workflowDir = dirname(absolutePath);
64
- return {
65
- content,
66
- displayPath: absolutePath,
67
- inferredName: basename(absolutePath, ".yml").replace(".yaml", ""),
68
- sourceDir: workflowDir,
69
- readRelativeFile: async (relativePath) => {
70
- const fullPath = relativePath.startsWith("/") ? relativePath : join(workflowDir, relativePath);
71
- if (existsSync(fullPath)) return readFileSync(fullPath, "utf-8");
72
- return null;
73
- }
74
- };
75
- }
76
- /**
77
- * Parse a github: reference string into its components.
78
- *
79
- * Supports two formats:
80
- * Full path: github:owner/repo[@ref]/path/to/file.yml
81
- * Shorthand: github:owner/repo[@ref]#name -> workflows/name.yml
82
- */
83
- function parseGitHubRef(input) {
84
- if (!input.startsWith(GITHUB_PREFIX)) throw new Error(`Not a GitHub reference: "${input}"`);
85
- const rest = input.slice(7);
86
- const hashIdx = rest.indexOf("#");
87
- if (hashIdx !== -1) {
88
- const repoStr = rest.slice(0, hashIdx);
89
- const name = rest.slice(hashIdx + 1);
90
- if (!name) throw new Error(`Missing workflow name after '#' in: "${input}"`);
91
- const { owner, repo, ref } = parseRepoSegment(repoStr);
92
- return {
93
- owner,
94
- repo,
95
- ref,
96
- path: `workflows/${name}.yml`
97
- };
98
- }
99
- const firstSlash = rest.indexOf("/");
100
- const secondSlash = firstSlash === -1 ? -1 : rest.indexOf("/", firstSlash + 1);
101
- if (firstSlash === -1 || secondSlash === -1) throw new Error(`Invalid GitHub reference: "${input}". Expected: github:owner/repo/path or github:owner/repo#name`);
102
- const repoStr = rest.slice(0, secondSlash);
103
- const path = rest.slice(secondSlash + 1);
104
- if (!path) throw new Error(`Missing file path in: "${input}"`);
105
- const { owner, repo, ref } = parseRepoSegment(repoStr);
106
- return {
107
- owner,
108
- repo,
109
- ref,
110
- path
111
- };
112
- }
113
- /**
114
- * Validate a git ref (branch, tag, or SHA) to prevent injection.
115
- * Allows alphanumeric, hyphens, dots, underscores, slashes — the standard git ref charset.
116
- */
117
- function validateGitRef(ref) {
118
- if (!/^[a-zA-Z0-9._\-/]+$/.test(ref)) throw new Error(`Invalid git ref: "${ref}". Only alphanumeric, hyphens, dots, underscores, and slashes are allowed.`);
119
- }
120
- /** Parse "owner/repo" or "owner/repo@ref" */
121
- function parseRepoSegment(repoStr) {
122
- let ref = DEFAULT_REF;
123
- const atIdx = repoStr.indexOf("@");
124
- let cleanStr = repoStr;
125
- if (atIdx !== -1) {
126
- ref = repoStr.slice(atIdx + 1);
127
- cleanStr = repoStr.slice(0, atIdx);
128
- if (!ref) throw new Error(`Empty ref after '@' in: "${repoStr}"`);
129
- }
130
- const parts = cleanStr.split("/");
131
- if (parts.length !== 2 || !parts[0] || !parts[1]) throw new Error(`Invalid repository format: "${repoStr}". Expected "owner/repo"`);
132
- validateGitRef(ref);
133
- return {
134
- owner: parts[0],
135
- repo: parts[1],
136
- ref
137
- };
138
- }
139
- /**
140
- * Get cache directory for a repo+ref combination.
141
- */
142
- function getCacheDir(ref) {
143
- return join(CACHE_BASE, ref.owner, ref.repo, ref.ref);
144
- }
145
- /**
146
- * Build the clone URL. Uses HTTPS; GITHUB_TOKEN auth via git credential helper
147
- * or the GIT_ASKPASS/GIT_AUTH mechanism handled by git itself.
148
- */
149
- function getCloneUrl(ref) {
150
- const token = process.env.GITHUB_TOKEN;
151
- if (token) return `https://${token}@github.com/${ref.owner}/${ref.repo}.git`;
152
- return `https://github.com/${ref.owner}/${ref.repo}.git`;
153
- }
154
- /**
155
- * Check if a ref looks like a branch/tag name (mutable) vs a commit SHA (immutable).
156
- * Commit SHAs are 7-40 hex chars.
157
- */
158
- function isImmutableRef(ref) {
159
- return /^[0-9a-f]{7,40}$/.test(ref);
160
- }
161
- /**
162
- * Clone or update a remote repo to the cache directory.
163
- *
164
- * Strategy:
165
- * - If cache exists and ref is immutable (SHA): skip, use cached
166
- * - If cache exists and ref is mutable (branch/tag): git fetch + reset
167
- * - If cache doesn't exist: shallow clone
168
- *
169
- * @returns Absolute path to the cache directory (repo root)
170
- */
171
- function ensureClone(ref) {
172
- const cacheDir = getCacheDir(ref);
173
- if (existsSync(join(cacheDir, ".git"))) {
174
- if (isImmutableRef(ref.ref)) return cacheDir;
175
- try {
176
- execFileSync("git", [
177
- "fetch",
178
- "origin",
179
- ref.ref,
180
- "--depth",
181
- "1"
182
- ], {
183
- cwd: cacheDir,
184
- stdio: "pipe",
185
- timeout: 3e4
186
- });
187
- execFileSync("git", [
188
- "reset",
189
- "--hard",
190
- "FETCH_HEAD"
191
- ], {
192
- cwd: cacheDir,
193
- stdio: "pipe",
194
- timeout: 1e4
195
- });
196
- return cacheDir;
197
- } catch {}
198
- }
199
- mkdirSync(dirname(cacheDir), { recursive: true });
200
- if (existsSync(cacheDir)) rmSync(cacheDir, {
201
- recursive: true,
202
- force: true
203
- });
204
- const url = getCloneUrl(ref);
205
- execFileSync("git", [
206
- "clone",
207
- "--depth",
208
- "1",
209
- "--single-branch",
210
- "--branch",
211
- ref.ref,
212
- url,
213
- cacheDir
214
- ], {
215
- stdio: "pipe",
216
- timeout: 6e4
217
- });
218
- return cacheDir;
219
- }
220
- /** Resolve a github: reference to a WorkflowSource */
221
- async function resolveGitHubSource(input) {
222
- const ref = parseGitHubRef(input);
223
- const repoDir = ensureClone(ref);
224
- const workflowPath = join(repoDir, ref.path);
225
- if (!existsSync(workflowPath)) throw new Error(`Remote workflow not found: ${ref.path}\n Source: ${input}\n Parsed: ${ref.owner}/${ref.repo}@${ref.ref} -> ${ref.path}\n Clone: ${repoDir}`);
226
- const content = readFileSync(workflowPath, "utf-8");
227
- const workflowDir = dirname(workflowPath);
228
- const inferredName = basename(ref.path, ".yml").replace(".yaml", "");
229
- return {
230
- content,
231
- displayPath: `${GITHUB_PREFIX}${ref.owner}/${ref.repo}@${ref.ref}/${ref.path}`,
232
- inferredName,
233
- sourceDir: repoDir,
234
- readRelativeFile: async (relativePath) => {
235
- const fullPath = relativePath.startsWith("/") ? relativePath : join(workflowDir, relativePath);
236
- if (existsSync(fullPath)) return readFileSync(fullPath, "utf-8");
237
- return null;
238
- }
239
- };
240
- }
241
-
242
- //#endregion
243
- //#region src/workflow/parser.ts
244
- /**
245
- * Workflow file parser
246
- */
247
- /**
248
- * Parse a workflow file (local or remote).
249
- *
250
- * Supports:
251
- * Local: review.yml, ./path/to/review.yml
252
- * Remote: github:owner/repo@ref/path/file.yml
253
- * github:owner/repo#name[@ref]
254
- */
255
- async function parseWorkflowFile(filePath, options) {
256
- const workflow = options?.workflow ?? "global";
257
- const tag = options?.tag ?? "main";
258
- const source = await resolveSource(filePath);
259
- const contextBaseDir = isRemoteSource(filePath) ? process.cwd() : dirname(resolve(filePath));
260
- let raw;
261
- try {
262
- raw = parse(source.content);
263
- } catch (error) {
264
- throw new Error(`Failed to parse YAML: ${error instanceof Error ? error.message : String(error)}`);
265
- }
266
- const validation = validateWorkflow(raw);
267
- if (!validation.valid) {
268
- const messages = validation.errors.map((e) => ` - ${e.path}: ${e.message}`).join("\n");
269
- throw new Error(`Invalid workflow file:\n${messages}`);
270
- }
271
- const name = raw.name || source.inferredName;
272
- const agents = {};
273
- for (const [agentName, entry] of Object.entries(raw.agents)) if (isRefAgentEntry(entry)) agents[agentName] = resolveRefAgent(entry, options?.agentRegistry);
274
- else agents[agentName] = await resolveInlineAgent(entry, source.readRelativeFile);
275
- const context = resolveContext(raw.context, contextBaseDir, name, workflow, tag);
276
- return {
277
- name,
278
- filePath: source.displayPath,
279
- sourceDir: source.sourceDir,
280
- agents,
281
- context,
282
- params: raw.params,
283
- setup: raw.setup || [],
284
- kickoff: raw.kickoff
285
- };
286
- }
287
- /**
288
- * Resolve context configuration
289
- *
290
- * - undefined (not set): default file provider enabled
291
- * - null: default file provider enabled (YAML `context:` syntax)
292
- * - false: explicitly disabled
293
- * - { provider: 'file', config?: { dir | bind } }: file provider (ephemeral or persistent)
294
- * - { provider: 'memory' }: memory provider (for testing)
295
- */
296
- function resolveContext(config, workflowDir, workflowName, workflow, tag) {
297
- const resolve = (template) => resolveContextDir(template, {
298
- workflowName,
299
- workflow,
300
- tag,
301
- baseDir: workflowDir
302
- });
303
- if (config === false) return;
304
- if (config === void 0 || config === null) return {
305
- provider: "file",
306
- dir: resolve(CONTEXT_DEFAULTS.dir)
307
- };
308
- if (config.provider === "memory") return {
309
- provider: "memory",
310
- documentOwner: config.documentOwner
311
- };
312
- const bindPath = config.config?.bind;
313
- if (bindPath) return {
314
- provider: "file",
315
- dir: resolve(bindPath),
316
- persistent: true,
317
- documentOwner: config.documentOwner
318
- };
319
- return {
320
- provider: "file",
321
- dir: resolve(config.config?.dir || CONTEXT_DEFAULTS.dir),
322
- documentOwner: config.documentOwner
323
- };
324
- }
325
- /**
326
- * Resolve an inline agent definition (load system prompt from file if needed).
327
- *
328
- * Uses a `readRelativeFile` function to abstract local vs remote file access.
329
- * Also transforms `wakeup` and `wakeup_prompt` fields into a `ScheduleConfig`
330
- * object, which is the format expected by the daemon and loop layers
331
- * for setting up periodic wakeup timers.
332
- */
333
- async function resolveInlineAgent(agent, readRelativeFile) {
334
- let resolvedSystemPrompt = agent.system_prompt;
335
- if (resolvedSystemPrompt?.endsWith(".txt") || resolvedSystemPrompt?.endsWith(".md")) {
336
- const content = await readRelativeFile(resolvedSystemPrompt);
337
- if (content !== null) resolvedSystemPrompt = content;
338
- }
339
- let schedule;
340
- if (agent.wakeup !== void 0) {
341
- schedule = { wakeup: agent.wakeup };
342
- if (agent.wakeup_prompt) schedule.prompt = agent.wakeup_prompt;
343
- }
344
- return {
345
- ...agent,
346
- resolvedSystemPrompt,
347
- schedule,
348
- isRef: false
349
- };
350
- }
351
- /**
352
- * Resolve a ref agent entry — load from AgentRegistry, map to WorkflowAgentDef,
353
- * apply workflow overrides (prompt.append, max_tokens, max_steps).
354
- */
355
- function resolveRefAgent(entry, registry) {
356
- if (!registry) throw new Error(`Agent ref "${entry.ref}" requires an AgentRegistry. Pass agentRegistry in ParseOptions.`);
357
- const handle = registry.get(entry.ref);
358
- if (!handle) throw new Error(`Agent ref "${entry.ref}" not found in registry. Available agents: ${registry.list().map((h) => h.definition.name).join(", ") || "(none)"}`);
359
- const def = handle.definition;
360
- const basePrompt = def.prompt.system ?? "";
361
- let resolvedSystemPrompt = basePrompt;
362
- if (entry.prompt?.append) resolvedSystemPrompt = basePrompt ? `${basePrompt}\n\n${entry.prompt.append}` : entry.prompt.append;
363
- const backend = def.backend === "sdk" ? "default" : def.backend;
364
- return {
365
- model: def.model,
366
- backend,
367
- provider: def.provider,
368
- system_prompt: basePrompt,
369
- max_tokens: entry.max_tokens ?? def.max_tokens,
370
- max_steps: entry.max_steps ?? def.max_steps,
371
- schedule: def.schedule,
372
- resolvedSystemPrompt,
373
- handle,
374
- isRef: true
375
- };
376
- }
377
- /**
378
- * Validate workflow structure
379
- */
380
- function validateWorkflow(workflow) {
381
- const errors = [];
382
- if (!workflow || typeof workflow !== "object") {
383
- errors.push({
384
- path: "",
385
- message: "Workflow must be an object"
386
- });
387
- return {
388
- valid: false,
389
- errors
390
- };
391
- }
392
- const w = workflow;
393
- if (!w.agents || typeof w.agents !== "object") errors.push({
394
- path: "agents",
395
- message: "Required field \"agents\" must be an object"
396
- });
397
- else {
398
- const agents = w.agents;
399
- for (const [name, agent] of Object.entries(agents)) validateAgent(name, agent, errors);
400
- }
401
- if (w.context !== void 0 && w.context !== null && w.context !== false) validateContext(w.context, errors);
402
- if (w.params !== void 0) if (!Array.isArray(w.params)) errors.push({
403
- path: "params",
404
- message: "Params must be an array"
405
- });
406
- else {
407
- const names = /* @__PURE__ */ new Set();
408
- const shorts = /* @__PURE__ */ new Set();
409
- for (let i = 0; i < w.params.length; i++) validateParam(`params[${i}]`, w.params[i], errors, names, shorts);
410
- }
411
- if (w.setup !== void 0) if (!Array.isArray(w.setup)) errors.push({
412
- path: "setup",
413
- message: "Setup must be an array"
414
- });
415
- else for (let i = 0; i < w.setup.length; i++) validateSetupTask(`setup[${i}]`, w.setup[i], errors);
416
- if (w.kickoff !== void 0 && typeof w.kickoff !== "string") errors.push({
417
- path: "kickoff",
418
- message: "Kickoff must be a string"
419
- });
420
- return {
421
- valid: errors.length === 0,
422
- errors
423
- };
424
- }
425
- function validateContext(context, errors) {
426
- if (typeof context !== "object" || context === null) {
427
- errors.push({
428
- path: "context",
429
- message: "Context must be an object or false"
430
- });
431
- return;
432
- }
433
- const c = context;
434
- if (!c.provider || typeof c.provider !== "string") {
435
- errors.push({
436
- path: "context.provider",
437
- message: "Context requires \"provider\" field (file or memory)"
438
- });
439
- return;
440
- }
441
- if (c.provider !== "file" && c.provider !== "memory") {
442
- errors.push({
443
- path: "context.provider",
444
- message: "Context provider must be \"file\" or \"memory\""
445
- });
446
- return;
447
- }
448
- if (c.documentOwner !== void 0 && typeof c.documentOwner !== "string") errors.push({
449
- path: "context.documentOwner",
450
- message: "Context documentOwner must be a string"
451
- });
452
- if (c.provider === "file" && c.config !== void 0) {
453
- if (typeof c.config !== "object" || c.config === null) {
454
- errors.push({
455
- path: "context.config",
456
- message: "Context config must be an object"
457
- });
458
- return;
459
- }
460
- const cfg = c.config;
461
- if (cfg.dir !== void 0 && cfg.bind !== void 0) {
462
- errors.push({
463
- path: "context.config",
464
- message: "\"dir\" and \"bind\" are mutually exclusive — use one or the other"
465
- });
466
- return;
467
- }
468
- if (cfg.dir !== void 0 && typeof cfg.dir !== "string") errors.push({
469
- path: "context.config.dir",
470
- message: "Context config dir must be a string"
471
- });
472
- if (cfg.bind !== void 0 && typeof cfg.bind !== "string") errors.push({
473
- path: "context.config.bind",
474
- message: "Context config bind must be a string path"
475
- });
476
- }
477
- }
478
- const RESERVED_NAMESPACES = [
479
- "env",
480
- "workflow",
481
- "params",
482
- "source"
483
- ];
484
- const VALID_PARAM_TYPES = [
485
- "string",
486
- "number",
487
- "boolean"
488
- ];
489
- function validateSetupTask(path, task, errors) {
490
- if (!task || typeof task !== "object") {
491
- errors.push({
492
- path,
493
- message: "Setup task must be an object"
494
- });
495
- return;
496
- }
497
- const t = task;
498
- if (!t.shell || typeof t.shell !== "string") errors.push({
499
- path: `${path}.shell`,
500
- message: "Setup task requires \"shell\" field as string"
501
- });
502
- if (t.as !== void 0 && typeof t.as !== "string") errors.push({
503
- path: `${path}.as`,
504
- message: "Setup task \"as\" field must be a string"
505
- });
506
- if (typeof t.as === "string" && RESERVED_NAMESPACES.includes(t.as)) errors.push({
507
- path: `${path}.as`,
508
- message: `"${t.as}" is a reserved namespace and cannot be used as a variable name`
509
- });
510
- }
511
- function validateParam(path, param, errors, names, shorts) {
512
- if (!param || typeof param !== "object") {
513
- errors.push({
514
- path,
515
- message: "Param must be an object"
516
- });
517
- return;
518
- }
519
- const p = param;
520
- if (!p.name || typeof p.name !== "string") {
521
- errors.push({
522
- path: `${path}.name`,
523
- message: "Param requires \"name\" field as string"
524
- });
525
- return;
526
- }
527
- if (names.has(p.name)) errors.push({
528
- path: `${path}.name`,
529
- message: `Duplicate param name: "${p.name}"`
530
- });
531
- names.add(p.name);
532
- if (p.description !== void 0 && typeof p.description !== "string") errors.push({
533
- path: `${path}.description`,
534
- message: "Param description must be a string"
535
- });
536
- if (p.type !== void 0) {
537
- if (typeof p.type !== "string" || !VALID_PARAM_TYPES.includes(p.type)) errors.push({
538
- path: `${path}.type`,
539
- message: `Param type must be one of: ${VALID_PARAM_TYPES.join(", ")}`
540
- });
541
- }
542
- if (p.short !== void 0) if (typeof p.short !== "string" || p.short.length !== 1) errors.push({
543
- path: `${path}.short`,
544
- message: "Param short must be a single character"
545
- });
546
- else {
547
- if (shorts.has(p.short)) errors.push({
548
- path: `${path}.short`,
549
- message: `Duplicate param short flag: "-${p.short}"`
550
- });
551
- shorts.add(p.short);
552
- }
553
- if (p.required !== void 0 && typeof p.required !== "boolean") errors.push({
554
- path: `${path}.required`,
555
- message: "Param required must be a boolean"
556
- });
557
- }
558
- /** Backends that don't require an explicit model field */
559
- const CLI_BACKENDS = [
560
- "claude",
561
- "cursor",
562
- "codex",
563
- "opencode",
564
- "mock"
565
- ];
566
- function validateAgent(name, agent, errors) {
567
- const path = `agents.${name}`;
568
- if (!agent || typeof agent !== "object") {
569
- errors.push({
570
- path,
571
- message: "Agent must be an object"
572
- });
573
- return;
574
- }
575
- const a = agent;
576
- if (typeof a.ref === "string") validateRefAgent(path, a, errors);
577
- else validateInlineAgent(path, a, errors);
578
- }
579
- function validateRefAgent(path, a, errors) {
580
- if (!a.ref.length) errors.push({
581
- path: `${path}.ref`,
582
- message: "Field \"ref\" must be a non-empty string"
583
- });
584
- if (a.prompt !== void 0) if (typeof a.prompt !== "object" || a.prompt === null) errors.push({
585
- path: `${path}.prompt`,
586
- message: "Field \"prompt\" for ref agents must be an object with optional \"append\""
587
- });
588
- else {
589
- const p = a.prompt;
590
- if (p.append !== void 0 && typeof p.append !== "string") errors.push({
591
- path: `${path}.prompt.append`,
592
- message: "Field \"prompt.append\" must be a string"
593
- });
594
- }
595
- if (a.system_prompt !== void 0) errors.push({
596
- path: `${path}.system_prompt`,
597
- message: "Field \"system_prompt\" cannot be used with ref agents — use \"prompt.append\" instead"
598
- });
599
- for (const field of [
600
- "model",
601
- "backend",
602
- "provider",
603
- "tools",
604
- "wakeup",
605
- "wakeup_prompt",
606
- "timeout"
607
- ]) if (a[field] !== void 0) errors.push({
608
- path: `${path}.${field}`,
609
- message: `Field "${field}" cannot be used with ref agents — it comes from the agent definition`
610
- });
611
- if (a.max_tokens !== void 0 && typeof a.max_tokens !== "number") errors.push({
612
- path: `${path}.max_tokens`,
613
- message: "Field \"max_tokens\" must be a number"
614
- });
615
- if (a.max_steps !== void 0 && typeof a.max_steps !== "number") errors.push({
616
- path: `${path}.max_steps`,
617
- message: "Field \"max_steps\" must be a number"
618
- });
619
- }
620
- function validateInlineAgent(path, a, errors) {
621
- const backend = typeof a.backend === "string" ? a.backend : "default";
622
- if (a.model !== void 0 && typeof a.model !== "string") errors.push({
623
- path: `${path}.model`,
624
- message: "Field \"model\" must be a string"
625
- });
626
- else if (!a.model && !CLI_BACKENDS.includes(backend)) errors.push({
627
- path: `${path}.model`,
628
- message: "Required field \"model\" must be a string (required for default backend)"
629
- });
630
- if (a.system_prompt !== void 0 && typeof a.system_prompt !== "string") errors.push({
631
- path: `${path}.system_prompt`,
632
- message: "Optional field \"system_prompt\" must be a string"
633
- });
634
- if (a.tools !== void 0 && !Array.isArray(a.tools)) errors.push({
635
- path: `${path}.tools`,
636
- message: "Optional field \"tools\" must be an array"
637
- });
638
- if (a.wakeup !== void 0) {
639
- if (typeof a.wakeup !== "string" && typeof a.wakeup !== "number") errors.push({
640
- path: `${path}.wakeup`,
641
- message: "Field \"wakeup\" must be a string (duration or cron) or number (ms)"
642
- });
643
- else if (typeof a.wakeup === "number" && a.wakeup <= 0) errors.push({
644
- path: `${path}.wakeup`,
645
- message: "Field \"wakeup\" must be a positive number when specified as ms"
646
- });
647
- }
648
- if (a.wakeup_prompt !== void 0) {
649
- if (typeof a.wakeup_prompt !== "string") errors.push({
650
- path: `${path}.wakeup_prompt`,
651
- message: "Field \"wakeup_prompt\" must be a string"
652
- });
653
- if (a.wakeup === void 0) errors.push({
654
- path: `${path}.wakeup_prompt`,
655
- message: "Field \"wakeup_prompt\" can only be used when \"wakeup\" is also specified"
656
- });
657
- }
658
- if (a.provider !== void 0) {
659
- if (typeof a.provider === "string") {} else if (typeof a.provider === "object" && a.provider !== null && !Array.isArray(a.provider)) {
660
- const p = a.provider;
661
- if (!p.name || typeof p.name !== "string") errors.push({
662
- path: `${path}.provider.name`,
663
- message: "Field \"provider.name\" is required and must be a string"
664
- });
665
- if (p.base_url !== void 0 && typeof p.base_url !== "string") errors.push({
666
- path: `${path}.provider.base_url`,
667
- message: "Field \"provider.base_url\" must be a string"
668
- });
669
- if (p.api_key !== void 0 && typeof p.api_key !== "string") errors.push({
670
- path: `${path}.provider.api_key`,
671
- message: "Field \"provider.api_key\" must be a string"
672
- });
673
- } else errors.push({
674
- path: `${path}.provider`,
675
- message: "Field \"provider\" must be a string or object with { name, base_url?, api_key? }"
676
- });
677
- if (CLI_BACKENDS.includes(backend) && backend !== "mock") errors.push({
678
- path: `${path}.provider`,
679
- message: `Field "provider" is ignored for CLI backend "${backend}" (only works with default backend)`
680
- });
681
- }
682
- }
683
- /**
684
- * Parse CLI arguments against workflow param definitions.
685
- * Uses Node's built-in util.parseArgs().
686
- *
687
- * @param defs Param definitions from workflow YAML
688
- * @param argv Raw CLI arguments (everything after the workflow file)
689
- * @returns Resolved param values as string map
690
- * @throws Error if required params are missing or types are invalid
691
- */
692
- function parseWorkflowParams(defs, argv) {
693
- if (defs.length === 0) return {};
694
- const options = {};
695
- for (const def of defs) {
696
- const opt = { type: def.type === "boolean" ? "boolean" : "string" };
697
- if (def.short) opt.short = def.short;
698
- options[def.name] = opt;
699
- }
700
- const { values } = parseArgs({
701
- args: argv,
702
- options,
703
- strict: true
704
- });
705
- const result = {};
706
- const missing = [];
707
- for (const def of defs) {
708
- let raw = values[def.name];
709
- if (raw === void 0 && def.default !== void 0) raw = String(def.default);
710
- if (raw === void 0) {
711
- if (def.required) {
712
- const flag = def.short ? `-${def.short}/--${def.name}` : `--${def.name}`;
713
- missing.push(flag);
714
- }
715
- continue;
716
- }
717
- if (def.type === "number") {
718
- const num = Number(raw);
719
- if (isNaN(num)) throw new Error(`Parameter --${def.name} must be a number, got: "${raw}"`);
720
- result[def.name] = String(num);
721
- } else result[def.name] = String(raw);
722
- }
723
- if (missing.length > 0) throw new Error(`Missing required parameter(s): ${missing.join(", ")}`);
724
- return result;
725
- }
726
- /**
727
- * Format parameter help text for workflow params
728
- */
729
- function formatParamHelp(defs) {
730
- if (defs.length === 0) return "";
731
- const lines = ["", "Workflow parameters:"];
732
- for (const def of defs) {
733
- const flags = def.short ? `-${def.short}, --${def.name}` : ` --${def.name}`;
734
- const type = def.type || "string";
735
- const req = def.required ? " (required)" : "";
736
- const dflt = def.default !== void 0 ? ` [default: ${def.default}]` : "";
737
- const desc = def.description || "";
738
- lines.push(` ${flags} <${type}> ${desc}${req}${dflt}`);
739
- }
740
- return lines.join("\n");
741
- }
742
-
743
- //#endregion
744
- export { formatParamHelp, parseWorkflowFile, parseWorkflowParams, runWorkflowWithLoops, shutdownLoops };