@bastani/atomic 0.8.24-alpha.2 → 0.8.24-alpha.3
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/CHANGELOG.md +6 -0
- package/README.md +2 -1
- package/dist/builtin/intercom/CHANGELOG.md +6 -0
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/CHANGELOG.md +6 -0
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/CHANGELOG.md +10 -0
- package/dist/builtin/subagents/README.md +132 -21
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/subagents/prompts/parallel-context-build.md +4 -2
- package/dist/builtin/subagents/prompts/parallel-handoff-plan.md +3 -1
- package/dist/builtin/subagents/skills/subagent/SKILL.md +49 -11
- package/dist/builtin/subagents/src/agents/agent-management.ts +79 -16
- package/dist/builtin/subagents/src/agents/agents.ts +47 -16
- package/dist/builtin/subagents/src/agents/chain-serializer.ts +114 -0
- package/dist/builtin/subagents/src/extension/schemas.ts +139 -3
- package/dist/builtin/subagents/src/runs/background/async-execution.ts +92 -6
- package/dist/builtin/subagents/src/runs/background/async-status.ts +11 -1
- package/dist/builtin/subagents/src/runs/background/run-status.ts +4 -1
- package/dist/builtin/subagents/src/runs/background/subagent-runner.ts +529 -32
- package/dist/builtin/subagents/src/runs/foreground/chain-execution.ts +361 -118
- package/dist/builtin/subagents/src/runs/foreground/execution.ts +75 -7
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor.ts +33 -0
- package/dist/builtin/subagents/src/runs/shared/acceptance.ts +611 -0
- package/dist/builtin/subagents/src/runs/shared/chain-outputs.ts +101 -0
- package/dist/builtin/subagents/src/runs/shared/dynamic-fanout.ts +293 -0
- package/dist/builtin/subagents/src/runs/shared/parallel-utils.ts +29 -1
- package/dist/builtin/subagents/src/runs/shared/pi-args.ts +11 -0
- package/dist/builtin/subagents/src/runs/shared/structured-output.ts +79 -0
- package/dist/builtin/subagents/src/runs/shared/subagent-prompt-runtime.ts +52 -2
- package/dist/builtin/subagents/src/runs/shared/workflow-graph.ts +206 -0
- package/dist/builtin/subagents/src/shared/formatters.ts +2 -2
- package/dist/builtin/subagents/src/shared/settings.ts +53 -4
- package/dist/builtin/subagents/src/shared/types.ts +226 -0
- package/dist/builtin/subagents/src/shared/utils.ts +2 -1
- package/dist/builtin/subagents/src/slash/slash-commands.ts +41 -3
- package/dist/builtin/subagents/src/tui/render.ts +152 -34
- package/dist/builtin/web-access/CHANGELOG.md +6 -0
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +6 -0
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/skills/create-spec/SKILL.md +1 -1
- package/dist/builtin/workflows/src/tui/stage-chat-view.ts +0 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +1 -0
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +4 -3
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +1 -1
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/docs/usage.md +1 -0
- package/docs/workflows.md +173 -0
- package/package.json +1 -1
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
parsePackageName,
|
|
18
18
|
} from "./agents.ts";
|
|
19
19
|
import { serializeAgent } from "./agent-serializer.ts";
|
|
20
|
-
import { serializeChain } from "./chain-serializer.ts";
|
|
20
|
+
import { serializeChain, serializeJsonChain } from "./chain-serializer.ts";
|
|
21
21
|
import { discoverAvailableSkills } from "./skills.ts";
|
|
22
22
|
import type { SubagentToolResult } from "../shared/types.ts";
|
|
23
23
|
|
|
@@ -116,10 +116,31 @@ function nameExistsInScope(cwd: string, scope: ManagementScope, name: string, ex
|
|
|
116
116
|
return false;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
function chainStepAgentNames(step: ChainStepConfig): string[] {
|
|
120
|
+
const names: string[] = [];
|
|
121
|
+
if (typeof step.agent === "string") names.push(step.agent);
|
|
122
|
+
const parallel = step.parallel;
|
|
123
|
+
if (Array.isArray(parallel)) {
|
|
124
|
+
for (const item of parallel) {
|
|
125
|
+
if (item && typeof item === "object") {
|
|
126
|
+
const agent = (item as { agent?: unknown }).agent;
|
|
127
|
+
if (typeof agent === "string") names.push(agent);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
} else if (parallel && typeof parallel === "object") {
|
|
131
|
+
const agent = (parallel as { agent?: unknown }).agent;
|
|
132
|
+
if (typeof agent === "string") names.push(agent);
|
|
133
|
+
}
|
|
134
|
+
return names;
|
|
135
|
+
}
|
|
136
|
+
|
|
119
137
|
function unknownChainAgents(cwd: string, steps: ChainStepConfig[]): string[] {
|
|
120
138
|
const d = discoverAgentsAll(cwd);
|
|
121
139
|
const known = new Set(allAgents(d).map((a) => a.name));
|
|
122
|
-
|
|
140
|
+
const unknown = steps
|
|
141
|
+
.flatMap((step) => chainStepAgentNames(step))
|
|
142
|
+
.filter((agent) => !known.has(agent));
|
|
143
|
+
return [...new Set(unknown)].sort((a, b) => a.localeCompare(b));
|
|
123
144
|
}
|
|
124
145
|
|
|
125
146
|
function chainStepWarnings(ctx: ManagementContext, steps: ChainStepConfig[]): string[] {
|
|
@@ -169,6 +190,22 @@ function parseStepList(raw: unknown): { steps?: ChainStepConfig[]; error?: strin
|
|
|
169
190
|
const s = item as Record<string, unknown>;
|
|
170
191
|
if (typeof s.agent !== "string" || !s.agent.trim()) return { error: `config.steps[${i}].agent must be a non-empty string.` };
|
|
171
192
|
const step: ChainStepConfig = { agent: s.agent.trim(), task: typeof s.task === "string" ? s.task : "" };
|
|
193
|
+
if (hasKey(s, "phase")) {
|
|
194
|
+
if (typeof s.phase === "string") step.phase = s.phase;
|
|
195
|
+
else return { error: `config.steps[${i}].phase must be a string.` };
|
|
196
|
+
}
|
|
197
|
+
if (hasKey(s, "label")) {
|
|
198
|
+
if (typeof s.label === "string") step.label = s.label;
|
|
199
|
+
else return { error: `config.steps[${i}].label must be a string.` };
|
|
200
|
+
}
|
|
201
|
+
if (hasKey(s, "as")) {
|
|
202
|
+
if (typeof s.as === "string") step.as = s.as;
|
|
203
|
+
else return { error: `config.steps[${i}].as must be a string.` };
|
|
204
|
+
}
|
|
205
|
+
if (hasKey(s, "outputSchema")) {
|
|
206
|
+
if (typeof s.outputSchema === "string") step.outputSchema = s.outputSchema;
|
|
207
|
+
else return { error: `config.steps[${i}].outputSchema must be a schema file path string for saved chains.` };
|
|
208
|
+
}
|
|
172
209
|
if (hasKey(s, "output")) {
|
|
173
210
|
if (s.output === false) step.output = false;
|
|
174
211
|
else if (typeof s.output === "string") step.output = s.output;
|
|
@@ -345,7 +382,7 @@ function renamePath(
|
|
|
345
382
|
cwd: string,
|
|
346
383
|
): { filePath?: string; error?: string } {
|
|
347
384
|
if (nameExistsInScope(cwd, scope, newName, currentPath)) return { error: `Name '${newName}' already exists in ${scope} scope.` };
|
|
348
|
-
const ext = kind === "agent" ? ".md" : ".chain.md";
|
|
385
|
+
const ext = kind === "agent" ? ".md" : currentPath.endsWith(".chain.json") ? ".chain.json" : ".chain.md";
|
|
349
386
|
const filePath = path.join(path.dirname(currentPath), `${newName}${ext}`);
|
|
350
387
|
if (fs.existsSync(filePath) && filePath !== currentPath) {
|
|
351
388
|
return { error: `File already exists at ${filePath} but is not a valid ${kind} definition. Remove or rename it first.` };
|
|
@@ -381,6 +418,41 @@ function formatAgentDetail(agent: AgentConfig): string {
|
|
|
381
418
|
return lines.join("\n");
|
|
382
419
|
}
|
|
383
420
|
|
|
421
|
+
function formatChainStepDetail(step: ChainStepConfig, index: number): string[] {
|
|
422
|
+
const lines: string[] = [];
|
|
423
|
+
if (step.expand || step.collect) {
|
|
424
|
+
const parallel = step.parallel && !Array.isArray(step.parallel) && typeof step.parallel === "object" ? step.parallel as { agent?: unknown; task?: unknown; label?: unknown; outputSchema?: unknown } : undefined;
|
|
425
|
+
const expand = step.expand && typeof step.expand === "object" ? step.expand as { from?: { output?: unknown; path?: unknown }; item?: unknown; key?: unknown; maxItems?: unknown; onEmpty?: unknown } : undefined;
|
|
426
|
+
const collect = step.collect && typeof step.collect === "object" ? step.collect as { as?: unknown; outputSchema?: unknown } : undefined;
|
|
427
|
+
lines.push(`${index + 1}. Dynamic fanout${typeof collect?.as === "string" ? ` -> ${collect.as}` : ""}`);
|
|
428
|
+
if (expand?.from) lines.push(` Expand: ${String(expand.from.output ?? "?")}${String(expand.from.path ?? "")}`);
|
|
429
|
+
if (typeof expand?.item === "string") lines.push(` Item variable: ${expand.item}`);
|
|
430
|
+
if (typeof expand?.key === "string") lines.push(` Key: ${expand.key}`);
|
|
431
|
+
if (typeof expand?.maxItems === "number") lines.push(` Max items: ${expand.maxItems}`);
|
|
432
|
+
if (typeof expand?.onEmpty === "string") lines.push(` On empty: ${expand.onEmpty}`);
|
|
433
|
+
if (parallel?.agent) lines.push(` Agent: ${String(parallel.agent)}`);
|
|
434
|
+
if (typeof parallel?.label === "string") lines.push(` Label: ${parallel.label}`);
|
|
435
|
+
if (typeof parallel?.task === "string" && parallel.task.trim()) lines.push(` Task: ${parallel.task}`);
|
|
436
|
+
if (parallel?.outputSchema) lines.push(" Structured output: true");
|
|
437
|
+
if (collect?.outputSchema) lines.push(" Collect schema: true");
|
|
438
|
+
if (step.concurrency !== undefined) lines.push(` Concurrency: ${step.concurrency}`);
|
|
439
|
+
if (step.failFast !== undefined) lines.push(` Fail fast: ${step.failFast ? "true" : "false"}`);
|
|
440
|
+
return lines;
|
|
441
|
+
}
|
|
442
|
+
lines.push(`${index + 1}. ${step.agent}`);
|
|
443
|
+
if (step.task?.trim()) lines.push(` Task: ${step.task}`);
|
|
444
|
+
if (step.output === false) lines.push(" Output: false");
|
|
445
|
+
else if (step.output) lines.push(` Output: ${step.output}`);
|
|
446
|
+
if (step.outputMode) lines.push(` Output mode: ${step.outputMode}`);
|
|
447
|
+
if (step.reads === false) lines.push(" Reads: false");
|
|
448
|
+
else if (Array.isArray(step.reads) && step.reads.length > 0) lines.push(` Reads: ${step.reads.join(", ")}`);
|
|
449
|
+
if (step.model) lines.push(` Model: ${step.model}`);
|
|
450
|
+
if (step.skills === false) lines.push(" Skills: false");
|
|
451
|
+
else if (Array.isArray(step.skills) && step.skills.length > 0) lines.push(` Skills: ${step.skills.join(", ")}`);
|
|
452
|
+
if (step.progress !== undefined) lines.push(` Progress: ${step.progress ? "true" : "false"}`);
|
|
453
|
+
return lines;
|
|
454
|
+
}
|
|
455
|
+
|
|
384
456
|
function formatChainDetail(chain: ChainConfig): string {
|
|
385
457
|
const lines: string[] = [`Chain: ${chain.name} (${chain.source})`, `Path: ${chain.filePath}`, `Description: ${chain.description}`];
|
|
386
458
|
if (chain.packageName) {
|
|
@@ -389,18 +461,7 @@ function formatChainDetail(chain: ChainConfig): string {
|
|
|
389
461
|
}
|
|
390
462
|
lines.push("", "Steps:");
|
|
391
463
|
for (let i = 0; i < chain.steps.length; i++) {
|
|
392
|
-
|
|
393
|
-
lines.push(`${i + 1}. ${s.agent}`);
|
|
394
|
-
if (s.task.trim()) lines.push(` Task: ${s.task}`);
|
|
395
|
-
if (s.output === false) lines.push(" Output: false");
|
|
396
|
-
else if (s.output) lines.push(` Output: ${s.output}`);
|
|
397
|
-
if (s.outputMode) lines.push(` Output mode: ${s.outputMode}`);
|
|
398
|
-
if (s.reads === false) lines.push(" Reads: false");
|
|
399
|
-
else if (Array.isArray(s.reads) && s.reads.length > 0) lines.push(` Reads: ${s.reads.join(", ")}`);
|
|
400
|
-
if (s.model) lines.push(` Model: ${s.model}`);
|
|
401
|
-
if (s.skills === false) lines.push(" Skills: false");
|
|
402
|
-
else if (Array.isArray(s.skills) && s.skills.length > 0) lines.push(` Skills: ${s.skills.join(", ")}`);
|
|
403
|
-
if (s.progress !== undefined) lines.push(` Progress: ${s.progress ? "true" : "false"}`);
|
|
464
|
+
lines.push(...formatChainStepDetail(chain.steps[i]!, i));
|
|
404
465
|
}
|
|
405
466
|
return lines.join("\n");
|
|
406
467
|
}
|
|
@@ -411,6 +472,7 @@ export function handleList(params: ManagementParams, ctx: ManagementContext): Su
|
|
|
411
472
|
const scopedAgents = allAgents(d).filter((a) => scope === "both" || a.source === "builtin" || a.source === scope).sort((a, b) => a.name.localeCompare(b.name));
|
|
412
473
|
const agents = scopedAgents.filter((a) => !a.disabled);
|
|
413
474
|
const chains = d.chains.filter((c) => scope === "both" || c.source === scope).sort((a, b) => a.name.localeCompare(b.name));
|
|
475
|
+
const diagnostics = d.chainDiagnostics.filter((entry) => scope === "both" || entry.source === scope);
|
|
414
476
|
const lines = [
|
|
415
477
|
"Executable agents:",
|
|
416
478
|
...(agents.length
|
|
@@ -419,6 +481,7 @@ export function handleList(params: ManagementParams, ctx: ManagementContext): Su
|
|
|
419
481
|
"",
|
|
420
482
|
"Chains:",
|
|
421
483
|
...(chains.length ? chains.map((c) => `- ${c.name} (${c.source}): ${c.description}`) : ["- (none)"]),
|
|
484
|
+
...(diagnostics.length ? ["", "Chain diagnostics:", ...diagnostics.map((entry) => `- ${entry.filePath}: ${entry.error}`)] : []),
|
|
422
485
|
];
|
|
423
486
|
return result(lines.join("\n"));
|
|
424
487
|
}
|
|
@@ -614,7 +677,7 @@ export function handleUpdate(params: ManagementParams, ctx: ManagementContext):
|
|
|
614
677
|
if (renamed.error) return result(renamed.error, true);
|
|
615
678
|
updated.filePath = renamed.filePath!;
|
|
616
679
|
}
|
|
617
|
-
fs.writeFileSync(updated.filePath, serializeChain(updated), "utf-8");
|
|
680
|
+
fs.writeFileSync(updated.filePath, updated.filePath.endsWith(".chain.json") ? serializeJsonChain(updated) : serializeChain(updated), "utf-8");
|
|
618
681
|
const headline = updated.name === oldName
|
|
619
682
|
? `Updated chain '${updated.name}' at ${updated.filePath}.`
|
|
620
683
|
: `Updated chain '${oldName}' to '${updated.name}' at ${updated.filePath}.`;
|
|
@@ -7,9 +7,9 @@ import * as os from "node:os";
|
|
|
7
7
|
import * as path from "node:path";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
9
|
import { CONFIG_DIR_NAME, getAgentConfigPaths, getEnvValue, getProjectConfigDirs } from "@bastani/atomic";
|
|
10
|
-
import type { OutputMode } from "../shared/types.ts";
|
|
10
|
+
import type { AcceptanceInput, OutputMode } from "../shared/types.ts";
|
|
11
11
|
import { KNOWN_FIELDS } from "./agent-serializer.ts";
|
|
12
|
-
import { parseChain } from "./chain-serializer.ts";
|
|
12
|
+
import { parseChain, parseJsonChain } from "./chain-serializer.ts";
|
|
13
13
|
import { mergeAgentsForScope } from "./agent-selection.ts";
|
|
14
14
|
import { parseFrontmatter } from "./frontmatter.ts";
|
|
15
15
|
import { buildRuntimeName, parsePackageName } from "./identity.ts";
|
|
@@ -111,14 +111,25 @@ interface SubagentSettings {
|
|
|
111
111
|
const EMPTY_SUBAGENT_SETTINGS: SubagentSettings = { overrides: {} };
|
|
112
112
|
|
|
113
113
|
export interface ChainStepConfig {
|
|
114
|
-
agent
|
|
115
|
-
task
|
|
114
|
+
agent?: string;
|
|
115
|
+
task?: string;
|
|
116
|
+
phase?: string;
|
|
117
|
+
label?: string;
|
|
118
|
+
as?: string;
|
|
119
|
+
outputSchema?: string | Record<string, unknown>;
|
|
116
120
|
output?: string | false;
|
|
117
121
|
outputMode?: OutputMode;
|
|
118
122
|
reads?: string[] | false;
|
|
119
123
|
model?: string;
|
|
120
124
|
skills?: string[] | false;
|
|
121
125
|
progress?: boolean;
|
|
126
|
+
parallel?: unknown;
|
|
127
|
+
expand?: unknown;
|
|
128
|
+
collect?: unknown;
|
|
129
|
+
concurrency?: number;
|
|
130
|
+
failFast?: boolean;
|
|
131
|
+
worktree?: boolean;
|
|
132
|
+
acceptance?: AcceptanceInput;
|
|
122
133
|
}
|
|
123
134
|
|
|
124
135
|
export interface ChainConfig {
|
|
@@ -132,6 +143,12 @@ export interface ChainConfig {
|
|
|
132
143
|
extraFields?: Record<string, string>;
|
|
133
144
|
}
|
|
134
145
|
|
|
146
|
+
export interface ChainDiscoveryDiagnostic {
|
|
147
|
+
source: "user" | "project";
|
|
148
|
+
filePath: string;
|
|
149
|
+
error: string;
|
|
150
|
+
}
|
|
151
|
+
|
|
135
152
|
interface AgentDiscoveryResult {
|
|
136
153
|
agents: AgentConfig[];
|
|
137
154
|
projectAgentsDir: string | null;
|
|
@@ -582,7 +599,7 @@ export function removeBuiltinAgentOverride(cwd: string, name: string, scope: "us
|
|
|
582
599
|
return filePath;
|
|
583
600
|
}
|
|
584
601
|
|
|
585
|
-
function
|
|
602
|
+
function listFilesRecursive(dir: string, predicate: (fileName: string) => boolean): string[] {
|
|
586
603
|
const files: string[] = [];
|
|
587
604
|
if (!fs.existsSync(dir)) return files;
|
|
588
605
|
|
|
@@ -596,7 +613,7 @@ function listMarkdownFilesRecursive(dir: string, predicate: (fileName: string) =
|
|
|
596
613
|
for (const entry of entries) {
|
|
597
614
|
const filePath = path.join(dir, entry.name);
|
|
598
615
|
if (entry.isDirectory()) {
|
|
599
|
-
files.push(...
|
|
616
|
+
files.push(...listFilesRecursive(filePath, predicate));
|
|
600
617
|
continue;
|
|
601
618
|
}
|
|
602
619
|
if (!entry.isFile() && !entry.isSymbolicLink()) continue;
|
|
@@ -609,7 +626,7 @@ function listMarkdownFilesRecursive(dir: string, predicate: (fileName: string) =
|
|
|
609
626
|
function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
|
|
610
627
|
const agents: AgentConfig[] = [];
|
|
611
628
|
|
|
612
|
-
for (const filePath of
|
|
629
|
+
for (const filePath of listFilesRecursive(dir, (fileName) => fileName.endsWith(".md") && !fileName.endsWith(".chain.md"))) {
|
|
613
630
|
let content: string;
|
|
614
631
|
try {
|
|
615
632
|
content = fs.readFileSync(filePath, "utf-8");
|
|
@@ -741,10 +758,11 @@ function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
|
|
|
741
758
|
return agents;
|
|
742
759
|
}
|
|
743
760
|
|
|
744
|
-
function loadChainsFromDir(dir: string, source: "user" | "project"): ChainConfig[] {
|
|
745
|
-
const chains
|
|
761
|
+
function loadChainsFromDir(dir: string, source: "user" | "project"): { chains: ChainConfig[]; diagnostics: ChainDiscoveryDiagnostic[] } {
|
|
762
|
+
const chains = new Map<string, ChainConfig>();
|
|
763
|
+
const diagnostics: ChainDiscoveryDiagnostic[] = [];
|
|
746
764
|
|
|
747
|
-
for (const filePath of
|
|
765
|
+
for (const filePath of listFilesRecursive(dir, (fileName) => fileName.endsWith(".chain.md") || fileName.endsWith(".chain.json"))) {
|
|
748
766
|
let content: string;
|
|
749
767
|
try {
|
|
750
768
|
content = fs.readFileSync(filePath, "utf-8");
|
|
@@ -753,13 +771,17 @@ function loadChainsFromDir(dir: string, source: "user" | "project"): ChainConfig
|
|
|
753
771
|
}
|
|
754
772
|
|
|
755
773
|
try {
|
|
756
|
-
|
|
757
|
-
|
|
774
|
+
const chain = filePath.endsWith(".chain.json") ? parseJsonChain(content, source, filePath) : parseChain(content, source, filePath);
|
|
775
|
+
const existing = chains.get(chain.name);
|
|
776
|
+
if (existing && existing.filePath.endsWith(".chain.json") && filePath.endsWith(".chain.md")) continue;
|
|
777
|
+
chains.set(chain.name, chain);
|
|
778
|
+
} catch (error) {
|
|
779
|
+
diagnostics.push({ source, filePath, error: error instanceof Error ? error.message : String(error) });
|
|
758
780
|
continue;
|
|
759
781
|
}
|
|
760
782
|
}
|
|
761
783
|
|
|
762
|
-
return chains;
|
|
784
|
+
return { chains: Array.from(chains.values()), diagnostics };
|
|
763
785
|
}
|
|
764
786
|
|
|
765
787
|
function isDirectory(p: string): boolean {
|
|
@@ -836,6 +858,7 @@ export function discoverAgentsAll(cwd: string): {
|
|
|
836
858
|
user: AgentConfig[];
|
|
837
859
|
project: AgentConfig[];
|
|
838
860
|
chains: ChainConfig[];
|
|
861
|
+
chainDiagnostics: ChainDiscoveryDiagnostic[];
|
|
839
862
|
userDir: string;
|
|
840
863
|
projectDir: string | null;
|
|
841
864
|
userChainDir: string;
|
|
@@ -875,19 +898,27 @@ export function discoverAgentsAll(cwd: string): {
|
|
|
875
898
|
const project = Array.from(projectMap.values());
|
|
876
899
|
|
|
877
900
|
const chainMap = new Map<string, ChainConfig>();
|
|
901
|
+
const projectChainDiagnostics: ChainDiscoveryDiagnostic[] = [];
|
|
878
902
|
for (const dir of projectChainDirs) {
|
|
879
|
-
|
|
903
|
+
const loaded = loadChainsFromDir(dir, "project");
|
|
904
|
+
projectChainDiagnostics.push(...loaded.diagnostics);
|
|
905
|
+
for (const chain of loaded.chains) {
|
|
880
906
|
chainMap.set(chain.name, chain);
|
|
881
907
|
}
|
|
882
908
|
}
|
|
909
|
+
const userChainLoads = getUserChainDirs().map((dir) => loadChainsFromDir(dir, "user"));
|
|
883
910
|
const chains = [
|
|
884
|
-
...
|
|
911
|
+
...userChainLoads.flatMap((loaded) => loaded.chains),
|
|
885
912
|
...Array.from(chainMap.values()),
|
|
886
913
|
];
|
|
914
|
+
const chainDiagnostics = [
|
|
915
|
+
...userChainLoads.flatMap((loaded) => loaded.diagnostics),
|
|
916
|
+
...projectChainDiagnostics,
|
|
917
|
+
];
|
|
887
918
|
|
|
888
919
|
const legacyUserAgentDir = userDirOld[0]!;
|
|
889
920
|
// ATOMIC_CODING_AGENT_DIR is already applied by getUserAgentDirs(); prefer that resolved path over ~/.agents.
|
|
890
921
|
const userDir = getEnvValue("ATOMIC_CODING_AGENT_DIR") ? legacyUserAgentDir : fs.existsSync(userDirNew) ? userDirNew : legacyUserAgentDir;
|
|
891
922
|
|
|
892
|
-
return { builtin, user, project, chains, userDir, projectDir, userChainDir, projectChainDir, userSettingsPath, projectSettingsPath };
|
|
923
|
+
return { builtin, user, project, chains, chainDiagnostics, userDir, projectDir, userChainDir, projectChainDir, userSettingsPath, projectSettingsPath };
|
|
893
924
|
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type { ChainConfig, ChainStepConfig } from "./agents.ts";
|
|
2
2
|
import { buildRuntimeName, frontmatterNameForConfig, parsePackageName } from "./identity.ts";
|
|
3
3
|
import { parseFrontmatter } from "./frontmatter.ts";
|
|
4
|
+
import { ChainOutputValidationError, validateChainOutputBindings } from "../runs/shared/chain-outputs.ts";
|
|
5
|
+
import { validateAcceptanceInput } from "../runs/shared/acceptance.ts";
|
|
6
|
+
import type { ChainStep } from "../shared/settings.ts";
|
|
4
7
|
|
|
5
8
|
function parseStepBody(agent: string, sectionBody: string): ChainStepConfig {
|
|
6
9
|
const lines = sectionBody.split("\n");
|
|
@@ -20,6 +23,25 @@ function parseStepBody(agent: string, sectionBody: string): ChainStepConfig {
|
|
|
20
23
|
else if (rawValue) step.output = rawValue;
|
|
21
24
|
continue;
|
|
22
25
|
}
|
|
26
|
+
if (key === "phase") {
|
|
27
|
+
if (rawValue) step.phase = rawValue;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (key === "label") {
|
|
31
|
+
if (rawValue) step.label = rawValue;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (key === "as") {
|
|
35
|
+
if (rawValue) step.as = rawValue;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (key === "outputschema") {
|
|
39
|
+
if (rawValue.startsWith("{") || rawValue.startsWith("[")) {
|
|
40
|
+
throw new Error("Inline outputSchema values are not supported in .chain.md files; use a schema file path.");
|
|
41
|
+
}
|
|
42
|
+
if (rawValue) step.outputSchema = rawValue;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
23
45
|
if (key === "outputmode") {
|
|
24
46
|
if (rawValue === "inline" || rawValue === "file-only") step.outputMode = rawValue;
|
|
25
47
|
continue;
|
|
@@ -102,6 +124,94 @@ export function parseChain(content: string, source: "user" | "project", filePath
|
|
|
102
124
|
};
|
|
103
125
|
}
|
|
104
126
|
|
|
127
|
+
export function parseJsonChain(content: string, source: "user" | "project", filePath: string): ChainConfig {
|
|
128
|
+
let parsed: unknown;
|
|
129
|
+
try {
|
|
130
|
+
parsed = JSON.parse(content);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
133
|
+
throw new Error(`Invalid JSON chain '${filePath}': ${message}`);
|
|
134
|
+
}
|
|
135
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
136
|
+
throw new Error(`JSON chain '${filePath}' must contain an object root.`);
|
|
137
|
+
}
|
|
138
|
+
const input = parsed as Record<string, unknown>;
|
|
139
|
+
if (typeof input.name !== "string" || !input.name.trim()) {
|
|
140
|
+
throw new Error(`JSON chain '${filePath}' must include string name.`);
|
|
141
|
+
}
|
|
142
|
+
if (typeof input.description !== "string" || !input.description.trim()) {
|
|
143
|
+
throw new Error(`JSON chain '${filePath}' must include string description.`);
|
|
144
|
+
}
|
|
145
|
+
if (!Array.isArray(input.chain)) {
|
|
146
|
+
throw new Error(`JSON chain '${filePath}' must include array chain.`);
|
|
147
|
+
}
|
|
148
|
+
for (let i = 0; i < input.chain.length; i++) {
|
|
149
|
+
const step = input.chain[i];
|
|
150
|
+
if (!step || typeof step !== "object" || Array.isArray(step)) {
|
|
151
|
+
throw new Error(`JSON chain '${filePath}' step ${i + 1} must be an object.`);
|
|
152
|
+
}
|
|
153
|
+
const stepRecord = step as Record<string, unknown>;
|
|
154
|
+
const acceptanceErrors = validateAcceptanceInput(stepRecord.acceptance, `step ${i + 1} acceptance`);
|
|
155
|
+
if (acceptanceErrors.length > 0) {
|
|
156
|
+
throw new Error(`Invalid JSON chain '${filePath}': ${acceptanceErrors.join(" ")}`);
|
|
157
|
+
}
|
|
158
|
+
const parallel = stepRecord.parallel;
|
|
159
|
+
if (Array.isArray(parallel)) {
|
|
160
|
+
for (let taskIndex = 0; taskIndex < parallel.length; taskIndex++) {
|
|
161
|
+
const task = parallel[taskIndex];
|
|
162
|
+
if (!task || typeof task !== "object" || Array.isArray(task)) continue;
|
|
163
|
+
const taskErrors = validateAcceptanceInput((task as Record<string, unknown>).acceptance, `step ${i + 1} parallel task ${taskIndex + 1} acceptance`);
|
|
164
|
+
if (taskErrors.length > 0) {
|
|
165
|
+
throw new Error(`Invalid JSON chain '${filePath}': ${taskErrors.join(" ")}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
} else if (parallel && typeof parallel === "object") {
|
|
169
|
+
const templateErrors = validateAcceptanceInput((parallel as Record<string, unknown>).acceptance, `step ${i + 1} dynamic template acceptance`);
|
|
170
|
+
if (templateErrors.length > 0) {
|
|
171
|
+
throw new Error(`Invalid JSON chain '${filePath}': ${templateErrors.join(" ")}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
try {
|
|
176
|
+
validateChainOutputBindings(input.chain as ChainStep[], { maxItems: Number.MAX_SAFE_INTEGER });
|
|
177
|
+
} catch (error) {
|
|
178
|
+
if (error instanceof ChainOutputValidationError) throw new Error(`Invalid JSON chain '${filePath}': ${error.message}`);
|
|
179
|
+
throw error;
|
|
180
|
+
}
|
|
181
|
+
const parsedPackage = parsePackageName(typeof input.package === "string" ? input.package : undefined, `Chain '${input.name}' package`);
|
|
182
|
+
if (parsedPackage.error) throw new Error(parsedPackage.error);
|
|
183
|
+
const extraFields: Record<string, string> = {};
|
|
184
|
+
for (const [key, value] of Object.entries(input)) {
|
|
185
|
+
if (key === "name" || key === "package" || key === "description" || key === "chain") continue;
|
|
186
|
+
if (typeof value === "string") extraFields[key] = value;
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
name: buildRuntimeName(input.name.trim(), parsedPackage.packageName),
|
|
190
|
+
localName: input.name.trim(),
|
|
191
|
+
packageName: parsedPackage.packageName,
|
|
192
|
+
description: input.description.trim(),
|
|
193
|
+
source,
|
|
194
|
+
filePath,
|
|
195
|
+
steps: input.chain as ChainStepConfig[],
|
|
196
|
+
extraFields: Object.keys(extraFields).length > 0 ? extraFields : undefined,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function serializeJsonChain(config: ChainConfig): string {
|
|
201
|
+
const root: Record<string, unknown> = {
|
|
202
|
+
name: frontmatterNameForConfig(config),
|
|
203
|
+
description: config.description,
|
|
204
|
+
chain: config.steps,
|
|
205
|
+
};
|
|
206
|
+
if (config.packageName) root.package = config.packageName;
|
|
207
|
+
if (config.extraFields) {
|
|
208
|
+
for (const [key, value] of Object.entries(config.extraFields)) {
|
|
209
|
+
if (key !== "name" && key !== "description" && key !== "package" && key !== "chain") root[key] = value;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return `${JSON.stringify(root, null, 2)}\n`;
|
|
213
|
+
}
|
|
214
|
+
|
|
105
215
|
export function serializeChain(config: ChainConfig): string {
|
|
106
216
|
const lines: string[] = [];
|
|
107
217
|
lines.push("---");
|
|
@@ -121,6 +231,10 @@ export function serializeChain(config: ChainConfig): string {
|
|
|
121
231
|
lines.push(`## ${step.agent}`);
|
|
122
232
|
if (step.output === false) lines.push("output: false");
|
|
123
233
|
else if (step.output) lines.push(`output: ${step.output}`);
|
|
234
|
+
if (step.phase) lines.push(`phase: ${step.phase}`);
|
|
235
|
+
if (step.label) lines.push(`label: ${step.label}`);
|
|
236
|
+
if (step.as) lines.push(`as: ${step.as}`);
|
|
237
|
+
if (step.outputSchema) lines.push(`outputSchema: ${step.outputSchema}`);
|
|
124
238
|
if (step.outputMode) lines.push(`outputMode: ${step.outputMode}`);
|
|
125
239
|
if (step.reads === false) lines.push("reads: false");
|
|
126
240
|
else if (Array.isArray(step.reads) && step.reads.length > 0) lines.push(`reads: ${step.reads.join(", ")}`);
|
|
@@ -35,6 +35,82 @@ const ReadsOverride = Type.Unsafe({
|
|
|
35
35
|
description: "Files to read before running (array of filenames), or false to disable",
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
+
const JsonSchemaObject = Type.Unsafe({
|
|
39
|
+
type: "object",
|
|
40
|
+
additionalProperties: true,
|
|
41
|
+
description: "JSON Schema object for strict structured output. Non-object roots are rejected.",
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const AcceptanceEvidenceKind = Type.String({
|
|
45
|
+
enum: [
|
|
46
|
+
"changed-files",
|
|
47
|
+
"tests-added",
|
|
48
|
+
"commands-run",
|
|
49
|
+
"validation-output",
|
|
50
|
+
"residual-risks",
|
|
51
|
+
"no-staged-files",
|
|
52
|
+
"diff-summary",
|
|
53
|
+
"review-findings",
|
|
54
|
+
"manual-notes",
|
|
55
|
+
],
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const AcceptanceGateSchema = Type.Object({
|
|
59
|
+
id: Type.String(),
|
|
60
|
+
must: Type.String(),
|
|
61
|
+
evidence: Type.Optional(Type.Array(AcceptanceEvidenceKind)),
|
|
62
|
+
severity: Type.Optional(Type.String({ enum: ["required", "recommended"] })),
|
|
63
|
+
}, { additionalProperties: false });
|
|
64
|
+
|
|
65
|
+
const AcceptanceVerifyCommandSchema = Type.Object({
|
|
66
|
+
id: Type.String(),
|
|
67
|
+
command: Type.String(),
|
|
68
|
+
timeoutMs: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
69
|
+
cwd: Type.Optional(Type.String()),
|
|
70
|
+
env: Type.Optional(Type.Unsafe({ type: "object", additionalProperties: { type: "string" } })),
|
|
71
|
+
allowFailure: Type.Optional(Type.Boolean()),
|
|
72
|
+
}, { additionalProperties: false });
|
|
73
|
+
|
|
74
|
+
const AcceptanceReviewGateSchema = Type.Object({
|
|
75
|
+
agent: Type.Optional(Type.String()),
|
|
76
|
+
focus: Type.Optional(Type.String()),
|
|
77
|
+
required: Type.Optional(Type.Boolean()),
|
|
78
|
+
}, { additionalProperties: false });
|
|
79
|
+
|
|
80
|
+
const AcceptanceOverride = Type.Unsafe({
|
|
81
|
+
anyOf: [
|
|
82
|
+
{ type: "string", enum: ["auto", "none", "attested", "checked", "verified", "reviewed"] },
|
|
83
|
+
{ const: false },
|
|
84
|
+
{
|
|
85
|
+
type: "object",
|
|
86
|
+
properties: {
|
|
87
|
+
level: { type: "string", enum: ["auto", "none", "attested", "checked", "verified", "reviewed"] },
|
|
88
|
+
criteria: {
|
|
89
|
+
type: "array",
|
|
90
|
+
items: {
|
|
91
|
+
anyOf: [
|
|
92
|
+
{ type: "string" },
|
|
93
|
+
AcceptanceGateSchema,
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
evidence: { type: "array", items: AcceptanceEvidenceKind },
|
|
98
|
+
verify: { type: "array", items: AcceptanceVerifyCommandSchema },
|
|
99
|
+
review: {
|
|
100
|
+
anyOf: [
|
|
101
|
+
{ const: false },
|
|
102
|
+
AcceptanceReviewGateSchema,
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
stopRules: { type: "array", items: { type: "string" } },
|
|
106
|
+
reason: { type: "string" },
|
|
107
|
+
},
|
|
108
|
+
additionalProperties: false,
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
description: "Optional acceptance policy. Omitted means auto-inferred; verified requires configured runtime commands.",
|
|
112
|
+
});
|
|
113
|
+
|
|
38
114
|
const TaskItem = Type.Object({
|
|
39
115
|
agent: Type.String(),
|
|
40
116
|
task: Type.String(),
|
|
@@ -46,12 +122,17 @@ const TaskItem = Type.Object({
|
|
|
46
122
|
progress: Type.Optional(Type.Boolean({ description: "Enable progress.md tracking for this task" })),
|
|
47
123
|
model: Type.Optional(Type.String({ description: "Override model for this task (e.g. 'google/gemini-3-pro')" })),
|
|
48
124
|
skill: Type.Optional(SkillOverride),
|
|
125
|
+
acceptance: Type.Optional(AcceptanceOverride),
|
|
49
126
|
});
|
|
50
127
|
|
|
51
128
|
// Parallel task item (within a parallel step)
|
|
52
129
|
const ParallelTaskSchema = Type.Object({
|
|
53
130
|
agent: Type.String(),
|
|
54
131
|
task: Type.Optional(Type.String({ description: "Task template with {task}, {previous}, {chain_dir} variables. Defaults to {previous}." })),
|
|
132
|
+
phase: Type.Optional(Type.String({ description: "Optional phase/group label for status and graph rendering." })),
|
|
133
|
+
label: Type.Optional(Type.String({ description: "Optional user-facing label for this parallel task." })),
|
|
134
|
+
as: Type.Optional(Type.String({ description: "Optional safe identifier used as {outputs.name} in later chain steps." })),
|
|
135
|
+
outputSchema: Type.Optional(JsonSchemaObject),
|
|
55
136
|
cwd: Type.Optional(Type.String()),
|
|
56
137
|
count: Type.Optional(Type.Integer({ minimum: 1, description: "Repeat this parallel task N times with the same settings." })),
|
|
57
138
|
output: Type.Optional(OutputOverride),
|
|
@@ -60,14 +141,51 @@ const ParallelTaskSchema = Type.Object({
|
|
|
60
141
|
progress: Type.Optional(Type.Boolean({ description: "Enable progress.md tracking in {chain_dir}" })),
|
|
61
142
|
skill: Type.Optional(SkillOverride),
|
|
62
143
|
model: Type.Optional(Type.String({ description: "Override model for this task" })),
|
|
144
|
+
acceptance: Type.Optional(AcceptanceOverride),
|
|
63
145
|
});
|
|
64
146
|
|
|
147
|
+
const DynamicExpandSchema = Type.Object({
|
|
148
|
+
from: Type.Object({
|
|
149
|
+
output: Type.String({ description: "Prior named structured output to expand from." }),
|
|
150
|
+
path: Type.String({ description: "JSON Pointer into the structured output, e.g. /items." }),
|
|
151
|
+
}, { additionalProperties: false }),
|
|
152
|
+
item: Type.Optional(Type.String({ description: "Template variable name for each item. Defaults to item." })),
|
|
153
|
+
key: Type.Optional(Type.String({ description: "JSON Pointer relative to each item for stable child ids." })),
|
|
154
|
+
maxItems: Type.Optional(Type.Integer({ minimum: 0, description: "Required fanout bound unless configured globally." })),
|
|
155
|
+
onEmpty: Type.Optional(Type.String({ enum: ["skip", "fail"], description: "Empty input behavior. Defaults to skip." })),
|
|
156
|
+
}, { additionalProperties: false });
|
|
157
|
+
|
|
158
|
+
const DynamicParallelTemplateSchema = Type.Object({
|
|
159
|
+
agent: Type.String(),
|
|
160
|
+
task: Type.Optional(Type.String({ description: "Task template with {item}, {item.path}, {task}, {previous}, {chain_dir}, and {outputs.name} variables." })),
|
|
161
|
+
phase: Type.Optional(Type.String({ description: "Optional phase/group label for status and graph rendering." })),
|
|
162
|
+
label: Type.Optional(Type.String({ description: "Optional user-facing label; item templates are supported." })),
|
|
163
|
+
outputSchema: Type.Optional(JsonSchemaObject),
|
|
164
|
+
cwd: Type.Optional(Type.String()),
|
|
165
|
+
output: Type.Optional(OutputOverride),
|
|
166
|
+
outputMode: Type.Optional(OutputModeOverride),
|
|
167
|
+
reads: Type.Optional(ReadsOverride),
|
|
168
|
+
progress: Type.Optional(Type.Boolean({ description: "Enable progress.md tracking in {chain_dir}" })),
|
|
169
|
+
skill: Type.Optional(SkillOverride),
|
|
170
|
+
model: Type.Optional(Type.String({ description: "Override model for this task" })),
|
|
171
|
+
acceptance: Type.Optional(AcceptanceOverride),
|
|
172
|
+
}, { additionalProperties: false });
|
|
173
|
+
|
|
174
|
+
const DynamicCollectSchema = Type.Object({
|
|
175
|
+
as: Type.String({ description: "Safe output name for the ordered collected result array." }),
|
|
176
|
+
outputSchema: Type.Optional(JsonSchemaObject),
|
|
177
|
+
}, { additionalProperties: false });
|
|
178
|
+
|
|
65
179
|
// Flattened so chain steps do not need an object-shape anyOf/oneOf union.
|
|
66
180
|
const ChainItem = Type.Object({
|
|
67
181
|
agent: Type.Optional(Type.String({ description: "Sequential step agent name" })),
|
|
68
182
|
task: Type.Optional(Type.String({
|
|
69
|
-
description: "Task template with variables: {task}=original request, {previous}=prior step's text response, {chain_dir}=shared folder. Required for first step, defaults to '{previous}' for subsequent steps."
|
|
183
|
+
description: "Task template with variables: {task}=original request, {previous}=prior step's text response, {chain_dir}=shared folder, {outputs.name}=prior named output. Required for first step, defaults to '{previous}' for subsequent steps."
|
|
70
184
|
})),
|
|
185
|
+
phase: Type.Optional(Type.String({ description: "Optional phase/group label for status and graph rendering." })),
|
|
186
|
+
label: Type.Optional(Type.String({ description: "Optional user-facing label for this chain step." })),
|
|
187
|
+
as: Type.Optional(Type.String({ description: "Optional safe identifier used as {outputs.name} in later chain steps." })),
|
|
188
|
+
outputSchema: Type.Optional(JsonSchemaObject),
|
|
71
189
|
cwd: Type.Optional(Type.String()),
|
|
72
190
|
output: Type.Optional(OutputOverride),
|
|
73
191
|
outputMode: Type.Optional(OutputModeOverride),
|
|
@@ -75,13 +193,30 @@ const ChainItem = Type.Object({
|
|
|
75
193
|
progress: Type.Optional(Type.Boolean({ description: "Enable progress.md tracking in {chain_dir}" })),
|
|
76
194
|
skill: Type.Optional(SkillOverride),
|
|
77
195
|
model: Type.Optional(Type.String({ description: "Override model for this step" })),
|
|
78
|
-
|
|
196
|
+
acceptance: Type.Optional(AcceptanceOverride),
|
|
197
|
+
parallel: Type.Optional(Type.Unsafe({
|
|
198
|
+
anyOf: [
|
|
199
|
+
Type.Array(ParallelTaskSchema, { minItems: 1, description: "Tasks to run in parallel" }),
|
|
200
|
+
DynamicParallelTemplateSchema,
|
|
201
|
+
],
|
|
202
|
+
description: "Static parallel tasks array, or a single dynamic fanout child template when expand/collect are present.",
|
|
203
|
+
})),
|
|
204
|
+
expand: Type.Optional(DynamicExpandSchema),
|
|
205
|
+
collect: Type.Optional(DynamicCollectSchema),
|
|
79
206
|
concurrency: Type.Optional(Type.Number({ description: "Max concurrent tasks (default: 4)" })),
|
|
80
207
|
failFast: Type.Optional(Type.Boolean({ description: "Stop on first failure (default: false)" })),
|
|
81
208
|
worktree: Type.Optional(Type.Boolean({
|
|
82
209
|
description: "Create isolated git worktrees for each parallel task."
|
|
83
210
|
})),
|
|
84
|
-
}, {
|
|
211
|
+
}, {
|
|
212
|
+
description: "Chain step: use {agent, task?, ...} for sequential, {parallel: [...]} for static concurrent execution, or {expand, parallel: {...}, collect} for dynamic fanout.",
|
|
213
|
+
additionalProperties: false,
|
|
214
|
+
allOf: [
|
|
215
|
+
{ if: { required: ["expand"] }, then: { required: ["parallel", "collect"], properties: { parallel: { type: "object" } } } },
|
|
216
|
+
{ if: { required: ["collect"] }, then: { required: ["expand", "parallel"], properties: { parallel: { type: "object" } } } },
|
|
217
|
+
{ not: { required: ["expand"], properties: { parallel: { type: "array", items: {} } } } },
|
|
218
|
+
],
|
|
219
|
+
});
|
|
85
220
|
|
|
86
221
|
const ControlOverrides = Type.Object({
|
|
87
222
|
enabled: Type.Optional(Type.Boolean({ description: "Enable/disable subagent control attention tracking for this run" })),
|
|
@@ -165,4 +300,5 @@ export const SubagentParams = Type.Object({
|
|
|
165
300
|
outputMode: Type.Optional(OutputModeOverride),
|
|
166
301
|
skill: Type.Optional(SkillOverride),
|
|
167
302
|
model: Type.Optional(Type.String({ description: "Override model for single agent (e.g. 'anthropic/claude-sonnet-4')" })),
|
|
303
|
+
acceptance: Type.Optional(AcceptanceOverride),
|
|
168
304
|
});
|