pi-subagents 0.40.0 → 0.41.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/CHANGELOG.md +76 -0
- package/README.md +246 -525
- package/agents/oracle.md +1 -0
- package/package.json +12 -4
- package/prompts/parallel-context-build.md +1 -1
- package/prompts/parallel-handoff-plan.md +1 -1
- package/prompts/review-loop.md +1 -1
- package/skills/pi-subagents/SKILL.md +6 -6
- package/skills/pi-subagents/references/constraints-and-recipes.md +19 -26
- package/skills/pi-subagents/references/execution-controls.md +98 -97
- package/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
- package/skills/pi-subagents/references/prompting-and-roles.md +18 -27
- package/src/agents/agent-management.ts +155 -65
- package/src/agents/agent-serializer.ts +19 -0
- package/src/agents/agents.ts +154 -71
- package/src/agents/chain-serializer.ts +10 -7
- package/src/agents/frontmatter.ts +5 -3
- package/src/agents/identity.ts +1 -1
- package/src/agents/proactive-skills.ts +13 -10
- package/src/agents/skills.ts +23 -6
- package/src/api/control-channel.ts +4 -0
- package/src/api/delegation.ts +26 -194
- package/src/api/external-runs.ts +129 -0
- package/src/api/intercom-bridge.ts +3 -0
- package/src/api/pi-args.ts +5 -0
- package/src/api/preflight.ts +3 -3
- package/src/api/shared-types.ts +19 -0
- package/src/extension/config.ts +10 -0
- package/src/extension/control-notices.ts +5 -39
- package/src/extension/doctor.ts +10 -9
- package/src/extension/fanout-child.ts +7 -4
- package/src/extension/index.ts +232 -68
- package/src/extension/rpc.ts +18 -12
- package/src/extension/schemas.ts +48 -37
- package/src/extension/tool-description.ts +36 -86
- package/src/inspectors/herdr/actions.ts +229 -0
- package/src/inspectors/herdr/client.ts +130 -0
- package/src/inspectors/herdr/inspector-runner.ts +141 -0
- package/src/inspectors/herdr/project-panes.ts +154 -0
- package/src/integrations/herdr-status.ts +330 -0
- package/src/intercom/intercom-bridge.ts +3 -2
- package/src/intercom/result-intercom.ts +5 -1
- package/src/missions/actions.ts +372 -0
- package/src/missions/lifecycle.ts +314 -0
- package/src/missions/store.ts +442 -0
- package/src/missions/types.ts +135 -0
- package/src/policy/authority.ts +46 -0
- package/src/profiles/profiles.ts +29 -3
- package/src/runs/background/async-execution.ts +98 -49
- package/src/runs/background/async-job-tracker.ts +10 -2
- package/src/runs/background/async-resume.ts +6 -6
- package/src/runs/background/async-status.ts +29 -1
- package/src/runs/background/auto-drain.ts +3 -3
- package/src/runs/background/chain-append.ts +3 -2
- package/src/runs/background/control-channel.ts +9 -7
- package/src/runs/background/fleet-view.ts +3 -4
- package/src/runs/background/notify.ts +2 -1
- package/src/runs/background/process-terminal.ts +5 -5
- package/src/runs/background/result-watcher.ts +13 -5
- package/src/runs/background/run-id-resolver.ts +3 -3
- package/src/runs/background/run-status.ts +35 -8
- package/src/runs/background/scheduled-runs.ts +602 -375
- package/src/runs/background/stale-run-reconciler.ts +3 -3
- package/src/runs/background/subagent-runner.ts +608 -445
- package/src/runs/background/subagent-wait.ts +50 -9
- package/src/runs/background/wait-subscriptions.ts +253 -0
- package/src/runs/background/wait-tool.ts +12 -4
- package/src/runs/foreground/async-steering-action.ts +3 -3
- package/src/runs/foreground/chain-clarify.ts +8 -4
- package/src/runs/foreground/chain-execution.ts +56 -30
- package/src/runs/foreground/execution.ts +15 -2
- package/src/runs/foreground/subagent-executor.ts +1023 -273
- package/src/runs/shared/acceptance.ts +28 -6
- package/src/runs/shared/child-protocol.ts +302 -22
- package/src/runs/shared/dynamic-fanout.ts +1 -1
- package/src/runs/shared/external-cli-runner.ts +130 -0
- package/src/runs/shared/long-running-guard.ts +42 -1
- package/src/runs/shared/nested-events.ts +59 -5
- package/src/runs/shared/nested-render.ts +9 -4
- package/src/runs/shared/parallel-handoff.ts +86 -2
- package/src/runs/shared/parallel-utils.ts +11 -2
- package/src/runs/shared/permissions.ts +95 -0
- package/src/runs/shared/pi-args.ts +11 -1
- package/src/runs/shared/pi-spawn.ts +11 -1
- package/src/runs/shared/run-history.ts +1 -1
- package/src/runs/shared/subagent-prompt-runtime.ts +36 -5
- package/src/runs/shared/subagent-startup-retry.ts +5 -2
- package/src/runs/shared/turn-budget.ts +6 -6
- package/src/runs/shared/worktree.ts +122 -12
- package/src/shared/accessible-dir.ts +29 -7
- package/src/shared/artifacts.ts +18 -1
- package/src/shared/fork-context.ts +3 -2
- package/src/shared/launch-contract.ts +1 -0
- package/src/shared/settings.ts +10 -0
- package/src/shared/types.ts +156 -14
- package/src/shared/utils.ts +8 -6
- package/src/slash/delegation-adapters.ts +32 -194
- package/src/slash/delegation-request.ts +43 -126
- package/src/slash/prompt-template-bridge.ts +158 -205
- package/src/slash/prompt-workflows.ts +21 -57
- package/src/slash/slash-bridge.ts +14 -0
- package/src/slash/slash-commands.ts +31 -632
- package/src/slash/subagents-admin.ts +18 -14
- package/src/tui/fleet-status.ts +156 -21
- package/src/tui/fleet-transcript.ts +110 -5
- package/src/tui/fleet.ts +56 -24
- package/src/tui/render.ts +291 -109
- package/src/types/pi-runtime-compat.d.ts +14 -0
- package/src/watchdog/lsp-diagnostics.ts +12 -7
- package/src/watchdog/model-selection.ts +2 -2
- package/src/watchdog/permission-arbiter.ts +145 -0
- package/src/watchdog/register-child.ts +1 -1
- package/src/watchdog/register-main.ts +1 -1
- package/src/watchdog/review.ts +4 -1
- package/src/watchdog/runtime.ts +3 -2
- package/src/workflows/chat-progress.ts +140 -0
- package/src/workflows/scripted-workflow.ts +415 -0
- package/agents/advisor.md +0 -73
- package/src/extension/chain-validation.ts +0 -181
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
import { Worker } from "node:worker_threads";
|
|
2
|
+
|
|
3
|
+
const KEY_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
4
|
+
|
|
5
|
+
const WORKER_SOURCE = String.raw`
|
|
6
|
+
const { parentPort } = require("node:worker_threads");
|
|
7
|
+
const vm = require("node:vm");
|
|
8
|
+
const { inspect } = require("node:util");
|
|
9
|
+
|
|
10
|
+
let nextCallId = 0;
|
|
11
|
+
const pending = new Map();
|
|
12
|
+
const runKeyPattern = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
13
|
+
|
|
14
|
+
function stableRunJson(value) {
|
|
15
|
+
if (Array.isArray(value)) return "[" + value.map(stableRunJson).join(",") + "]";
|
|
16
|
+
if (value && typeof value === "object") return "{" + Object.keys(value).sort().map((key) => JSON.stringify(key) + ":" + stableRunJson(value[key])).join(",") + "}";
|
|
17
|
+
return JSON.stringify(value) ?? "undefined";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function hostCall(method, args) {
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
const callId = ++nextCallId;
|
|
23
|
+
pending.set(callId, { resolve, reject });
|
|
24
|
+
parentPort.postMessage({ type: "call", callId, method, args });
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function formatRef(result) {
|
|
29
|
+
if (!result || typeof result !== "object") throw new Error("runs.ref(result) requires a run result object.");
|
|
30
|
+
const parts = ["run " + (result.key || "unknown")];
|
|
31
|
+
if (result.runId) parts.push("id=" + String(result.runId).slice(0, 8));
|
|
32
|
+
return "[" + parts.join("; ") + "]";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const runFingerprints = new Map();
|
|
36
|
+
|
|
37
|
+
function validateRunCall(key, params, label, fingerprints) {
|
|
38
|
+
if (typeof key !== "string" || !runKeyPattern.test(key)) throw new Error(label + " has an invalid key.");
|
|
39
|
+
if (!params || typeof params !== "object" || Array.isArray(params)) throw new Error(label + " requires a params object.");
|
|
40
|
+
if (Object.prototype.hasOwnProperty.call(params, "action") || Object.prototype.hasOwnProperty.call(params, "workflowScript") || Object.prototype.hasOwnProperty.call(params, "tasks") || Object.prototype.hasOwnProperty.call(params, "chain") || Object.prototype.hasOwnProperty.call(params, "concurrency") || Object.prototype.hasOwnProperty.call(params, "chainDir")) {
|
|
41
|
+
const hint = label === "runs.run" ? "; use runs.all(...) and JavaScript control flow for orchestration." : ".";
|
|
42
|
+
throw new Error(label + " accepts one child via { agent, task } and execution controls only" + hint);
|
|
43
|
+
}
|
|
44
|
+
if (params.worktree !== undefined && typeof params.worktree !== "boolean") throw new Error(label + " worktree must be true or false.");
|
|
45
|
+
assertJsonValue(params, label + " params");
|
|
46
|
+
const fingerprint = stableRunJson(params);
|
|
47
|
+
const existing = fingerprints.get(key);
|
|
48
|
+
if (existing !== undefined && existing !== fingerprint) throw new Error("Duplicate workflow key '" + key + "' used with incompatible launch params.");
|
|
49
|
+
fingerprints.set(key, fingerprint);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const runs = Object.freeze({
|
|
53
|
+
run(key, params) {
|
|
54
|
+
validateRunCall(key, params, "runs.run", runFingerprints);
|
|
55
|
+
return hostCall("run", { key, params });
|
|
56
|
+
},
|
|
57
|
+
all(items) {
|
|
58
|
+
if (!Array.isArray(items)) throw new Error("runs.all(items) requires an array.");
|
|
59
|
+
const fingerprints = new Map(runFingerprints);
|
|
60
|
+
const calls = [];
|
|
61
|
+
for (let index = 0; index < items.length; index++) {
|
|
62
|
+
if (!Object.prototype.hasOwnProperty.call(items, index)) throw new Error("runs.all items must not contain sparse entries.");
|
|
63
|
+
const item = items[index];
|
|
64
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) throw new Error("runs.all item " + index + " must be an object.");
|
|
65
|
+
const { key, ...params } = item;
|
|
66
|
+
validateRunCall(key, params, "runs.all item " + index, fingerprints);
|
|
67
|
+
calls.push({ key, params });
|
|
68
|
+
}
|
|
69
|
+
for (const { key, params } of calls) runFingerprints.set(key, stableRunJson(params));
|
|
70
|
+
return Promise.all(calls.map(({ key, params }) => hostCall("run", { key, params, collectFailure: true })));
|
|
71
|
+
},
|
|
72
|
+
status(keyOrRunId) { return hostCall("status", { keyOrRunId }); },
|
|
73
|
+
ref: formatRef,
|
|
74
|
+
refs(results) {
|
|
75
|
+
if (!Array.isArray(results)) throw new Error("runs.refs(results) requires an array.");
|
|
76
|
+
return results.map(formatRef).join("\n");
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
let contextObjectPrototype;
|
|
81
|
+
|
|
82
|
+
const capturedConsole = Object.freeze(Object.fromEntries(
|
|
83
|
+
["log", "info", "warn", "error"].map((level) => [level, (...args) => {
|
|
84
|
+
parentPort.postMessage({ type: "console", level, text: args.map((value) => typeof value === "string" ? value : inspect(value, { depth: 4, breakLength: 120 })).join(" ") });
|
|
85
|
+
}]),
|
|
86
|
+
));
|
|
87
|
+
|
|
88
|
+
function assertJsonValue(value, path = "emit", seen = new Set()) {
|
|
89
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return;
|
|
90
|
+
if (typeof value === "number") {
|
|
91
|
+
if (!Number.isFinite(value)) throw new Error(path + " must contain only finite JSON numbers.");
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (typeof value !== "object") throw new Error(path + " must be a JSON value; received " + typeof value + ".");
|
|
95
|
+
if (seen.has(value)) throw new Error(path + " must not contain cycles.");
|
|
96
|
+
seen.add(value);
|
|
97
|
+
if (Array.isArray(value)) {
|
|
98
|
+
for (let index = 0; index < value.length; index++) {
|
|
99
|
+
if (!Object.prototype.hasOwnProperty.call(value, index)) throw new Error(path + " must not contain sparse array entries.");
|
|
100
|
+
assertJsonValue(value[index], path + "[" + index + "]", seen);
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
const prototype = Object.getPrototypeOf(value);
|
|
104
|
+
if (prototype !== null && prototype !== Object.prototype && prototype !== contextObjectPrototype) throw new Error(path + " must contain only plain JSON objects.");
|
|
105
|
+
if (Object.getOwnPropertySymbols(value).length > 0) throw new Error(path + " must not contain symbol keys.");
|
|
106
|
+
for (const [key, entry] of Object.entries(value)) assertJsonValue(entry, path + "." + key, seen);
|
|
107
|
+
}
|
|
108
|
+
seen.delete(value);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
parentPort.on("message", async (message) => {
|
|
112
|
+
if (message.type === "response") {
|
|
113
|
+
const entry = pending.get(message.callId);
|
|
114
|
+
if (!entry) return;
|
|
115
|
+
pending.delete(message.callId);
|
|
116
|
+
if (message.ok) entry.resolve(message.value);
|
|
117
|
+
else entry.reject(new Error(message.error));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (message.type !== "start") return;
|
|
121
|
+
try {
|
|
122
|
+
const sandbox = { runs, emit(value) { assertJsonValue(value); parentPort.postMessage({ type: "emit", value }); }, console: capturedConsole };
|
|
123
|
+
const context = vm.createContext(sandbox, { codeGeneration: { strings: false, wasm: false } });
|
|
124
|
+
contextObjectPrototype = vm.runInContext("Object.prototype", context);
|
|
125
|
+
const compiled = new vm.Script("(async () => {\n" + message.script + "\n})()", { filename: "workflow-script.js" });
|
|
126
|
+
const value = await compiled.runInContext(context);
|
|
127
|
+
const persistedValue = value === undefined ? null : value;
|
|
128
|
+
assertJsonValue(persistedValue, "return");
|
|
129
|
+
parentPort.postMessage({ type: "complete", value: persistedValue });
|
|
130
|
+
} catch (error) {
|
|
131
|
+
parentPort.postMessage({ type: "error", error: error && error.stack ? error.stack : String(error) });
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
`;
|
|
135
|
+
|
|
136
|
+
export interface WorkflowScriptChildResult {
|
|
137
|
+
key: string;
|
|
138
|
+
ok: boolean;
|
|
139
|
+
runId?: string;
|
|
140
|
+
output: string;
|
|
141
|
+
error?: string;
|
|
142
|
+
structuredOutput?: unknown;
|
|
143
|
+
artifactPaths: string[];
|
|
144
|
+
results?: unknown[];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export interface WorkflowScriptTraceEntry {
|
|
148
|
+
operation: "run" | "status";
|
|
149
|
+
key: string;
|
|
150
|
+
state: "started" | "completed" | "failed" | "reused";
|
|
151
|
+
runId?: string;
|
|
152
|
+
durationMs?: number;
|
|
153
|
+
phase?: string;
|
|
154
|
+
label?: string;
|
|
155
|
+
error?: string;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export interface WorkflowScriptResult {
|
|
159
|
+
value: unknown;
|
|
160
|
+
emits: unknown[];
|
|
161
|
+
console: Array<{ level: "log" | "info" | "warn" | "error"; text: string }>;
|
|
162
|
+
trace: WorkflowScriptTraceEntry[];
|
|
163
|
+
children: WorkflowScriptChildResult[];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export class WorkflowScriptError extends Error {
|
|
167
|
+
readonly partial: Omit<WorkflowScriptResult, "value">;
|
|
168
|
+
|
|
169
|
+
constructor(message: string, partial: Omit<WorkflowScriptResult, "value">) {
|
|
170
|
+
super(message);
|
|
171
|
+
this.name = "WorkflowScriptError";
|
|
172
|
+
this.partial = partial;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export interface RunWorkflowScriptOptions {
|
|
177
|
+
script: string;
|
|
178
|
+
timeoutMs: number;
|
|
179
|
+
signal?: AbortSignal;
|
|
180
|
+
launch: (key: string, params: Record<string, unknown>, signal: AbortSignal) => Promise<WorkflowScriptChildResult>;
|
|
181
|
+
status: (keyOrRunId: string, signal: AbortSignal) => Promise<WorkflowScriptChildResult>;
|
|
182
|
+
onTrace?: (trace: WorkflowScriptTraceEntry[]) => void;
|
|
183
|
+
onEmit?: (emits: unknown[]) => void;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
187
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function isPlainJsonObject(value: unknown): value is Record<string, unknown> {
|
|
191
|
+
if (!isRecord(value)) return false;
|
|
192
|
+
const prototype = Object.getPrototypeOf(value);
|
|
193
|
+
return prototype === null || prototype === Object.prototype;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function omitUndefinedWorkflowValues(value: unknown, seen = new Set<object>()): unknown {
|
|
197
|
+
if (value === null || typeof value !== "object") return value;
|
|
198
|
+
if (seen.has(value)) return value;
|
|
199
|
+
seen.add(value);
|
|
200
|
+
const normalized = Array.isArray(value)
|
|
201
|
+
? value.map((entry) => entry === undefined ? null : omitUndefinedWorkflowValues(entry, seen))
|
|
202
|
+
: isPlainJsonObject(value)
|
|
203
|
+
? Object.fromEntries(Object.entries(value).flatMap(([key, entry]) => entry === undefined ? [] : [[key, omitUndefinedWorkflowValues(entry, seen)]]))
|
|
204
|
+
: value;
|
|
205
|
+
seen.delete(value);
|
|
206
|
+
return normalized;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function assertWorkflowJsonValue(value: unknown, path = "value", seen = new Set<object>()): void {
|
|
210
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return;
|
|
211
|
+
if (typeof value === "number") {
|
|
212
|
+
if (!Number.isFinite(value)) throw new Error(`${path} must contain only finite JSON numbers.`);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (typeof value !== "object") throw new Error(`${path} must be a JSON value; received ${typeof value}.`);
|
|
216
|
+
if (seen.has(value)) throw new Error(`${path} must not contain cycles.`);
|
|
217
|
+
seen.add(value);
|
|
218
|
+
if (Array.isArray(value)) {
|
|
219
|
+
for (let index = 0; index < value.length; index++) {
|
|
220
|
+
if (!Object.hasOwn(value, index)) throw new Error(`${path} must not contain sparse array entries.`);
|
|
221
|
+
assertWorkflowJsonValue(value[index], `${path}[${index}]`, seen);
|
|
222
|
+
}
|
|
223
|
+
} else {
|
|
224
|
+
const prototype = Object.getPrototypeOf(value);
|
|
225
|
+
if (prototype !== null && prototype !== Object.prototype) throw new Error(`${path} must contain only plain JSON objects.`);
|
|
226
|
+
if (Object.getOwnPropertySymbols(value).length > 0) throw new Error(`${path} must not contain symbol keys.`);
|
|
227
|
+
for (const [key, entry] of Object.entries(value)) assertWorkflowJsonValue(entry, `${path}.${key}`, seen);
|
|
228
|
+
}
|
|
229
|
+
seen.delete(value);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function formatWorkflowJsonPreview(value: unknown, maxLength: number): string | undefined {
|
|
233
|
+
try {
|
|
234
|
+
assertWorkflowJsonValue(value);
|
|
235
|
+
const serialized = JSON.stringify(value);
|
|
236
|
+
return typeof serialized === "string" ? serialized.slice(0, maxLength) : undefined;
|
|
237
|
+
} catch {
|
|
238
|
+
return undefined;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function stableJson(value: unknown): string {
|
|
243
|
+
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
244
|
+
if (isRecord(value)) return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`).join(",")}}`;
|
|
245
|
+
return JSON.stringify(value) ?? "undefined";
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function validateKey(value: unknown): string {
|
|
249
|
+
if (typeof value !== "string" || !KEY_PATTERN.test(value)) {
|
|
250
|
+
throw new Error("runs.run key must be 1-128 characters using letters, numbers, '.', '_' or '-', and start with a letter or number.");
|
|
251
|
+
}
|
|
252
|
+
return value;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function workflowStringMetadata(params: Record<string, unknown>): Pick<WorkflowScriptTraceEntry, "phase" | "label"> {
|
|
256
|
+
return {
|
|
257
|
+
...(typeof params.phase === "string" && params.phase.trim() ? { phase: params.phase.trim() } : {}),
|
|
258
|
+
...(typeof params.label === "string" && params.label.trim() ? { label: params.label.trim() } : {}),
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export async function runWorkflowScript(options: RunWorkflowScriptOptions): Promise<WorkflowScriptResult> {
|
|
263
|
+
if (!options.script.trim()) throw new Error("workflowScript must not be empty.");
|
|
264
|
+
if (!Number.isInteger(options.timeoutMs) || options.timeoutMs < 1) throw new Error("workflow script timeout must be a positive integer.");
|
|
265
|
+
|
|
266
|
+
const worker = new Worker(WORKER_SOURCE, { eval: true });
|
|
267
|
+
const emits: unknown[] = [];
|
|
268
|
+
const consoleEntries: WorkflowScriptResult["console"] = [];
|
|
269
|
+
const trace: WorkflowScriptTraceEntry[] = [];
|
|
270
|
+
const children = new Map<string, WorkflowScriptChildResult>();
|
|
271
|
+
const childOrder: string[] = [];
|
|
272
|
+
const launches = new Map<string, { fingerprint: string; promise: Promise<WorkflowScriptChildResult> }>();
|
|
273
|
+
const childController = new AbortController();
|
|
274
|
+
let settled = false;
|
|
275
|
+
|
|
276
|
+
const partial = (): Omit<WorkflowScriptResult, "value"> => ({ emits, console: consoleEntries, trace, children: childOrder.flatMap((key) => {
|
|
277
|
+
const child = children.get(key);
|
|
278
|
+
return child ? [child] : [];
|
|
279
|
+
}) });
|
|
280
|
+
const traceChanged = () => options.onTrace?.([...trace]);
|
|
281
|
+
|
|
282
|
+
return await new Promise<WorkflowScriptResult>((resolve, reject) => {
|
|
283
|
+
const finish = (outcome: { value: unknown } | { error: Error }) => {
|
|
284
|
+
if (settled) return;
|
|
285
|
+
settled = true;
|
|
286
|
+
clearTimeout(timer);
|
|
287
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
288
|
+
void worker.terminate();
|
|
289
|
+
childController.abort("error" in outcome ? outcome.error : new Error("Workflow script completed; unawaited child launches are aborted."));
|
|
290
|
+
if ("error" in outcome) reject(new WorkflowScriptError(outcome.error.message, partial()));
|
|
291
|
+
else resolve({ value: outcome.value, ...partial() });
|
|
292
|
+
};
|
|
293
|
+
const onAbort = () => finish({ error: new Error("Workflow script aborted.") });
|
|
294
|
+
const timer = setTimeout(() => finish({ error: new Error(`Workflow script timed out after ${options.timeoutMs}ms.`) }), options.timeoutMs);
|
|
295
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
296
|
+
if (options.signal?.aborted) return onAbort();
|
|
297
|
+
|
|
298
|
+
worker.on("error", (error) => finish({ error: new Error(`Workflow worker failed: ${error instanceof Error ? error.message : String(error)}`) }));
|
|
299
|
+
worker.on("exit", (code) => {
|
|
300
|
+
if (!settled && code !== 0) finish({ error: new Error(`Workflow worker exited with code ${code}.`) });
|
|
301
|
+
});
|
|
302
|
+
worker.on("message", (message: Record<string, unknown>) => {
|
|
303
|
+
if (message.type === "emit") {
|
|
304
|
+
try {
|
|
305
|
+
assertWorkflowJsonValue(message.value, "emit");
|
|
306
|
+
} catch (error) {
|
|
307
|
+
finish({ error: new Error(`Workflow emit could not be persisted: ${error instanceof Error ? error.message : String(error)}`) });
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
emits.push(message.value);
|
|
311
|
+
try {
|
|
312
|
+
options.onEmit?.([...emits]);
|
|
313
|
+
} catch (error) {
|
|
314
|
+
emits.pop();
|
|
315
|
+
finish({ error: new Error(`Workflow emit could not be persisted: ${error instanceof Error ? error.message : String(error)}`) });
|
|
316
|
+
}
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
if (message.type === "console") {
|
|
320
|
+
const level = message.level;
|
|
321
|
+
if ((level === "log" || level === "info" || level === "warn" || level === "error") && typeof message.text === "string") consoleEntries.push({ level, text: message.text });
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
if (message.type === "complete") {
|
|
325
|
+
try {
|
|
326
|
+
assertWorkflowJsonValue(message.value, "return");
|
|
327
|
+
} catch (error) {
|
|
328
|
+
return finish({ error: new Error(`Workflow return could not be persisted: ${error instanceof Error ? error.message : String(error)}`) });
|
|
329
|
+
}
|
|
330
|
+
return finish({ value: message.value });
|
|
331
|
+
}
|
|
332
|
+
if (message.type === "error") return finish({ error: new Error(typeof message.error === "string" ? message.error : "Workflow script failed.") });
|
|
333
|
+
if (message.type !== "call" || typeof message.callId !== "number" || typeof message.method !== "string" || !isRecord(message.args)) return;
|
|
334
|
+
|
|
335
|
+
const respond = (promise: Promise<WorkflowScriptChildResult>) => {
|
|
336
|
+
void promise.then(
|
|
337
|
+
(value) => worker.postMessage({ type: "response", callId: message.callId, ok: true, value: omitUndefinedWorkflowValues(value) }),
|
|
338
|
+
(error: unknown) => worker.postMessage({ type: "response", callId: message.callId, ok: false, error: error instanceof Error ? error.message : String(error) }),
|
|
339
|
+
);
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
if (message.method === "status") {
|
|
343
|
+
const keyOrRunId = message.args.keyOrRunId;
|
|
344
|
+
if (typeof keyOrRunId !== "string" || !keyOrRunId.trim()) return respond(Promise.reject(new Error("runs.status(keyOrRunId) requires a non-empty string.")));
|
|
345
|
+
const known = children.get(keyOrRunId);
|
|
346
|
+
const target = known?.runId ?? keyOrRunId;
|
|
347
|
+
trace.push({ operation: "status", key: keyOrRunId, state: "started", ...(known?.runId ? { runId: known.runId } : {}) });
|
|
348
|
+
traceChanged();
|
|
349
|
+
respond(options.status(target, childController.signal).then((result) => {
|
|
350
|
+
trace.push({ operation: "status", key: keyOrRunId, state: result.ok ? "completed" : "failed", ...(result.runId ? { runId: result.runId } : {}), ...(!result.ok ? { error: result.output } : {}) });
|
|
351
|
+
traceChanged();
|
|
352
|
+
if (!result.ok) throw new Error(`Status '${keyOrRunId}' failed: ${result.output}`);
|
|
353
|
+
return result;
|
|
354
|
+
}));
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
if (message.method !== "run") return respond(Promise.reject(new Error(`Unknown runs API method '${message.method}'.`)));
|
|
358
|
+
|
|
359
|
+
let key: string;
|
|
360
|
+
try {
|
|
361
|
+
key = validateKey(message.args.key);
|
|
362
|
+
} catch (error) {
|
|
363
|
+
return respond(Promise.reject(error));
|
|
364
|
+
}
|
|
365
|
+
const params = message.args.params;
|
|
366
|
+
if (!isRecord(params)) return respond(Promise.reject(new Error(`runs.run('${key}', params) requires a params object.`)));
|
|
367
|
+
if (params.action !== undefined) return respond(Promise.reject(new Error(`runs.run('${key}') accepts execution params only; management action is not allowed.`)));
|
|
368
|
+
if (params.workflowScript !== undefined) return respond(Promise.reject(new Error(`runs.run('${key}') cannot start a nested workflow script.`)));
|
|
369
|
+
if (params.tasks !== undefined || params.chain !== undefined || params.concurrency !== undefined || params.chainDir !== undefined) {
|
|
370
|
+
return respond(Promise.reject(new Error(`runs.run('${key}') accepts one child via { agent, task }; use runs.all(...) and JavaScript control flow for orchestration.`)));
|
|
371
|
+
}
|
|
372
|
+
if (params.worktree !== undefined && typeof params.worktree !== "boolean") {
|
|
373
|
+
return respond(Promise.reject(new Error(`runs.run('${key}') worktree must be true or false.`)));
|
|
374
|
+
}
|
|
375
|
+
const collectFailure = message.args.collectFailure === true;
|
|
376
|
+
const deliver = (promise: Promise<WorkflowScriptChildResult>) => collectFailure
|
|
377
|
+
? promise
|
|
378
|
+
: promise.then((result) => {
|
|
379
|
+
if (!result.ok) throw new Error(`Run '${key}' failed: ${result.error ?? result.output}`);
|
|
380
|
+
return result;
|
|
381
|
+
});
|
|
382
|
+
const fingerprint = stableJson(params);
|
|
383
|
+
const existing = launches.get(key);
|
|
384
|
+
if (existing) {
|
|
385
|
+
if (existing.fingerprint !== fingerprint) return respond(Promise.reject(new Error(`Duplicate workflow key '${key}' used with incompatible launch params.`)));
|
|
386
|
+
trace.push({ operation: "run", key, state: "reused", ...workflowStringMetadata(params) });
|
|
387
|
+
traceChanged();
|
|
388
|
+
return respond(deliver(existing.promise));
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const startedAt = Date.now();
|
|
392
|
+
childOrder.push(key);
|
|
393
|
+
trace.push({ operation: "run", key, state: "started", ...workflowStringMetadata(params) });
|
|
394
|
+
traceChanged();
|
|
395
|
+
const promise = Promise.resolve().then(() => options.launch(key, { ...params, async: params.async ?? false }, childController.signal)).then((result) => {
|
|
396
|
+
const normalized = !result.ok && !result.error ? { ...result, error: result.output } : result;
|
|
397
|
+
children.set(key, normalized);
|
|
398
|
+
trace.push({ operation: "run", key, state: normalized.ok ? "completed" : "failed", durationMs: Date.now() - startedAt, ...workflowStringMetadata(params), ...(normalized.runId ? { runId: normalized.runId } : {}), ...(!normalized.ok ? { error: normalized.error ?? normalized.output } : {}) });
|
|
399
|
+
traceChanged();
|
|
400
|
+
return normalized;
|
|
401
|
+
}, (error: unknown) => {
|
|
402
|
+
const text = error instanceof Error ? error.message : String(error);
|
|
403
|
+
const failure: WorkflowScriptChildResult = { key, ok: false, output: text, error: text, artifactPaths: [] };
|
|
404
|
+
children.set(key, failure);
|
|
405
|
+
trace.push({ operation: "run", key, state: "failed", durationMs: Date.now() - startedAt, ...workflowStringMetadata(params), error: text });
|
|
406
|
+
traceChanged();
|
|
407
|
+
return failure;
|
|
408
|
+
});
|
|
409
|
+
launches.set(key, { fingerprint, promise });
|
|
410
|
+
respond(deliver(promise));
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
worker.postMessage({ type: "start", script: options.script });
|
|
414
|
+
});
|
|
415
|
+
}
|
package/agents/advisor.md
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: advisor
|
|
3
|
-
description: Compatibility alias for oracle; high-context decision-consistency advisor
|
|
4
|
-
tools: read, grep, find, ls, bash, intercom
|
|
5
|
-
thinking: high
|
|
6
|
-
systemPromptMode: replace
|
|
7
|
-
inheritProjectContext: true
|
|
8
|
-
inheritSkills: false
|
|
9
|
-
defaultContext: fork
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
You are the advisor, the compatibility alias for oracle: a high-context decision-consistency subagent.
|
|
13
|
-
|
|
14
|
-
Your primary job is to prevent the main agent from making hidden, conflicting, or inconsistent decisions by treating the inherited forked context as the authoritative contract. You are not the primary executor. You do not silently become a second decision-maker.
|
|
15
|
-
|
|
16
|
-
Before you do anything else, reconstruct the key inherited decisions, constraints, and open questions from the forked conversation, codebase state, and task. Those decisions form your baseline contract. Preserve them unless there is strong evidence they should be overturned.
|
|
17
|
-
|
|
18
|
-
If you need clarification from the main agent and runtime bridge instructions are present, use `contact_supervisor` with `reason: "need_decision"` and wait for the reply. Use `reason: "progress_update"` only for concise updates when blocked, explicitly asked for progress, or when a recommendation or concern would benefit from immediate discussion. Keep coordination traffic tight and purposeful. Do not narrate your whole review through `contact_supervisor`.
|
|
19
|
-
|
|
20
|
-
Do not send routine completion handoffs. If no coordination is needed, return the final oracle recommendation normally. Fall back to generic `intercom` only if `contact_supervisor` is unavailable and the runtime bridge instructions identify a safe target.
|
|
21
|
-
|
|
22
|
-
Core responsibilities:
|
|
23
|
-
- reconstruct inherited decisions, constraints, and open questions from the context
|
|
24
|
-
- identify drift between the current trajectory and those inherited decisions
|
|
25
|
-
- surface contradictions and hidden assumptions the main agent may be missing
|
|
26
|
-
- call out when a proposed move conflicts with an earlier decision or constraint
|
|
27
|
-
- protect consistency over novelty; prefer the path that honors existing decisions unless the context clearly supports a pivot
|
|
28
|
-
- when you do recommend a pivot, explain exactly which prior assumption or decision should be revised and why
|
|
29
|
-
- exploit your clean forked context to spot things the main agent may have missed due to context rot, accumulated reasoning, or errors in the original instruction
|
|
30
|
-
- look beyond the explicit question and suggest guidance based on the overall agent trajectory, even when not directly asked
|
|
31
|
-
|
|
32
|
-
What you do not do by default:
|
|
33
|
-
- do not edit files or write code
|
|
34
|
-
- do not propose additional parallel decision-makers or new subagent trees unless explicitly asked
|
|
35
|
-
- do not assume a `worker` implementation handoff is the default outcome
|
|
36
|
-
- do not propose broad pivots unless the context clearly supports them
|
|
37
|
-
- do not continue the user conversation directly
|
|
38
|
-
|
|
39
|
-
Working rules:
|
|
40
|
-
- Use `bash` only for inspection, verification, or read-only analysis.
|
|
41
|
-
- If information is missing and it matters, ask the main agent with `contact_supervisor` and `reason: "need_decision"` instead of guessing.
|
|
42
|
-
- If the answer depends on a decision the main agent has not made yet, stop and ask with `contact_supervisor` before continuing.
|
|
43
|
-
- When bridge instructions are present, send concise coordination messages only when a recommendation, concern, or question would benefit from immediate discussion instead of waiting silently until the final return.
|
|
44
|
-
- Prefer narrow, specific corrections to the current path over rewriting the whole plan.
|
|
45
|
-
|
|
46
|
-
Your output should follow this shape. If no executor handoff is warranted, say so plainly.
|
|
47
|
-
|
|
48
|
-
Inherited decisions:
|
|
49
|
-
- the key decisions, constraints, and assumptions already in play
|
|
50
|
-
|
|
51
|
-
Diagnosis:
|
|
52
|
-
- what is actually going on
|
|
53
|
-
- what the main agent may be missing
|
|
54
|
-
|
|
55
|
-
Drift / contradiction check:
|
|
56
|
-
- where the current trajectory conflicts with inherited decisions or constraints
|
|
57
|
-
- what assumptions have quietly changed
|
|
58
|
-
|
|
59
|
-
Recommendation:
|
|
60
|
-
- the best next move
|
|
61
|
-
- why it is the best move
|
|
62
|
-
- if recommending a pivot, which inherited decision is being revised and why
|
|
63
|
-
|
|
64
|
-
Risks:
|
|
65
|
-
- what could still go wrong
|
|
66
|
-
- what assumptions remain uncertain
|
|
67
|
-
|
|
68
|
-
Need from main agent:
|
|
69
|
-
- specific question or decision required before continuing, if any
|
|
70
|
-
|
|
71
|
-
Suggested execution prompt:
|
|
72
|
-
- a concrete prompt for `worker`, only if an implementation handoff is actually warranted
|
|
73
|
-
- if no handoff is warranted, say so explicitly
|
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Friendly chain parameter validation.
|
|
3
|
-
*
|
|
4
|
-
* The runtime (pi-ai) validates tool arguments against the TypeBox schema before
|
|
5
|
-
* the tool's `execute` runs. When a chain step has an extra property, the model
|
|
6
|
-
* receives a raw TypeBox message such as `chain.1: must not have additional
|
|
7
|
-
* properties` — which does not name the offending property, list the allowed
|
|
8
|
-
* ones, or show the expected shape. Agents then iteratively guess the format and
|
|
9
|
-
* waste several turns before getting it right.
|
|
10
|
-
*
|
|
11
|
-
* `validateChainInput` runs from the tool's `prepareArguments` shim (and the RPC
|
|
12
|
-
* bridge) *before* schema validation. It throws a rich, actionable error for the
|
|
13
|
-
* common chain-shape failures, so the model sees which property is disallowed,
|
|
14
|
-
* what is allowed, and a valid example — instead of a raw TypeBox diagnostic.
|
|
15
|
-
*
|
|
16
|
-
* Allowed property names and additional-property strictness are derived directly
|
|
17
|
-
* from the schema objects in `schemas.ts` so this stays aligned with TypeBox.
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import {
|
|
21
|
-
ChainItem,
|
|
22
|
-
ParallelTaskSchema,
|
|
23
|
-
DynamicParallelTemplateSchema,
|
|
24
|
-
DynamicExpandSchema,
|
|
25
|
-
DynamicCollectSchema,
|
|
26
|
-
} from "./schemas.ts";
|
|
27
|
-
|
|
28
|
-
type ObjectSchema = {
|
|
29
|
-
properties?: Record<string, unknown>;
|
|
30
|
-
additionalProperties?: unknown;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
function allowedKeysOf(schema: ObjectSchema | undefined): string[] {
|
|
34
|
-
return schema?.properties ? Object.keys(schema.properties) : [];
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const ExpandFromSchema = (DynamicExpandSchema.properties?.from ?? {}) as ObjectSchema;
|
|
38
|
-
|
|
39
|
-
export const CHAIN_STEP_KEYS = allowedKeysOf(ChainItem);
|
|
40
|
-
export const PARALLEL_TASK_KEYS = allowedKeysOf(ParallelTaskSchema);
|
|
41
|
-
export const DYNAMIC_TEMPLATE_KEYS = allowedKeysOf(DynamicParallelTemplateSchema);
|
|
42
|
-
export const EXPAND_KEYS = allowedKeysOf(DynamicExpandSchema);
|
|
43
|
-
export const EXPAND_FROM_KEYS = allowedKeysOf(ExpandFromSchema);
|
|
44
|
-
export const COLLECT_KEYS = allowedKeysOf(DynamicCollectSchema);
|
|
45
|
-
|
|
46
|
-
const CHAIN_STEP_EXAMPLE = '{"agent": "worker", "task": "do X"} or {"checkpoint": "review", "message": "Approve before implementation?"}';
|
|
47
|
-
const PARALLEL_TASK_EXAMPLE = '{"agent": "worker", "task": "do X"}';
|
|
48
|
-
const DYNAMIC_TEMPLATE_EXAMPLE = '{"agent": "worker", "task": "Review {item.path}"}';
|
|
49
|
-
const EXPAND_EXAMPLE = '{"from": {"output": "targets", "path": "/items"}, "maxItems": 4}';
|
|
50
|
-
const EXPAND_FROM_EXAMPLE = '{"output": "targets", "path": "/items"}';
|
|
51
|
-
const COLLECT_EXAMPLE = '{"as": "reviews"}';
|
|
52
|
-
|
|
53
|
-
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
54
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function disallowedKeys(value: Record<string, unknown>, allowed: readonly string[]): string[] {
|
|
58
|
-
return Object.keys(value).filter((key) => !allowed.includes(key));
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function validateCheckpointShape(step: Record<string, unknown>, path: string): void {
|
|
62
|
-
if (step.checkpoint === undefined) return;
|
|
63
|
-
const allowed = new Set(["checkpoint", "message", "phase", "label"]);
|
|
64
|
-
const mixed = Object.keys(step).filter((key) => !allowed.has(key));
|
|
65
|
-
if (mixed.length > 0) {
|
|
66
|
-
throw new Error(`subagent ${path}: checkpoint steps cannot be mixed with child-launch properties ${quoteList(mixed)}.\n\nExample: {"checkpoint": "review", "message": "Approve before implementation?"}`);
|
|
67
|
-
}
|
|
68
|
-
if (typeof step.checkpoint !== "string" || !step.checkpoint.trim()) {
|
|
69
|
-
throw new Error(`subagent ${path}.checkpoint: expected a non-empty string.`);
|
|
70
|
-
}
|
|
71
|
-
if (step.message !== undefined && typeof step.message !== "string") {
|
|
72
|
-
throw new Error(`subagent ${path}.message: expected a string.`);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function typeName(value: unknown): string {
|
|
77
|
-
if (Array.isArray(value)) return "array";
|
|
78
|
-
return typeof value;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function quoteList(keys: string[]): string {
|
|
82
|
-
return keys.map((key) => `"${key}"`).join(", ");
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function disallowedMessage(
|
|
86
|
-
path: string,
|
|
87
|
-
keys: string[],
|
|
88
|
-
allowed: readonly string[],
|
|
89
|
-
example?: string,
|
|
90
|
-
): string {
|
|
91
|
-
const subject = keys.length === 1
|
|
92
|
-
? `property "${keys[0]}" is not allowed`
|
|
93
|
-
: `properties ${quoteList(keys)} are not allowed`;
|
|
94
|
-
const examplePart = example ? `\n\nExample: ${example}` : "";
|
|
95
|
-
return `subagent ${path}: ${subject}.\nAllowed properties: ${allowed.join(", ")}.${examplePart}`;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function expectObject(value: unknown, path: string, allowed: readonly string[], example?: string): asserts value is Record<string, unknown> {
|
|
99
|
-
if (!isPlainObject(value)) {
|
|
100
|
-
const examplePart = example ? `\n\nExample: ${example}` : "";
|
|
101
|
-
throw new Error(
|
|
102
|
-
`subagent ${path}: expected an object, received ${typeName(value)}.\nAllowed properties: ${allowed.join(", ")}.${examplePart}`,
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function checkNoExtraKeys(
|
|
108
|
-
value: Record<string, unknown>,
|
|
109
|
-
path: string,
|
|
110
|
-
schema: ObjectSchema,
|
|
111
|
-
allowed: readonly string[],
|
|
112
|
-
example?: string,
|
|
113
|
-
): void {
|
|
114
|
-
if (schema.additionalProperties !== false) return;
|
|
115
|
-
const extra = disallowedKeys(value, allowed);
|
|
116
|
-
if (extra.length > 0) {
|
|
117
|
-
throw new Error(disallowedMessage(path, extra, allowed, example));
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Validates the `chain` array on a raw subagent tool-call argument object and
|
|
123
|
-
* throws a friendly, actionable error when a chain step (or a nested
|
|
124
|
-
* `parallel`/`expand`/`collect` object) has a disallowed property or wrong shape.
|
|
125
|
-
*
|
|
126
|
-
* No-op when `chain` is absent or not an array, so management/control/single/
|
|
127
|
-
* parallel calls are unaffected.
|
|
128
|
-
*/
|
|
129
|
-
export function validateChainInput(args: unknown): void {
|
|
130
|
-
if (!isPlainObject(args)) return;
|
|
131
|
-
const chain = (args as Record<string, unknown>).chain;
|
|
132
|
-
if (!Array.isArray(chain)) return;
|
|
133
|
-
|
|
134
|
-
chain.forEach((step, index) => {
|
|
135
|
-
const stepPath = `chain step validation failed at chain[${index}]`;
|
|
136
|
-
expectObject(step, stepPath, CHAIN_STEP_KEYS, CHAIN_STEP_EXAMPLE);
|
|
137
|
-
checkNoExtraKeys(step, stepPath, ChainItem, CHAIN_STEP_KEYS, CHAIN_STEP_EXAMPLE);
|
|
138
|
-
validateCheckpointShape(step, stepPath);
|
|
139
|
-
|
|
140
|
-
if (step.expand !== undefined) {
|
|
141
|
-
const expandPath = `${stepPath}.expand`;
|
|
142
|
-
expectObject(step.expand, expandPath, EXPAND_KEYS, EXPAND_EXAMPLE);
|
|
143
|
-
checkNoExtraKeys(step.expand, expandPath, DynamicExpandSchema, EXPAND_KEYS, EXPAND_EXAMPLE);
|
|
144
|
-
|
|
145
|
-
const from = (step.expand as Record<string, unknown>).from;
|
|
146
|
-
if (from !== undefined) {
|
|
147
|
-
const fromPath = `${expandPath}.from`;
|
|
148
|
-
expectObject(from, fromPath, EXPAND_FROM_KEYS, EXPAND_FROM_EXAMPLE);
|
|
149
|
-
checkNoExtraKeys(from, fromPath, ExpandFromSchema, EXPAND_FROM_KEYS, EXPAND_FROM_EXAMPLE);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
if (step.collect !== undefined) {
|
|
154
|
-
const collectPath = `${stepPath}.collect`;
|
|
155
|
-
expectObject(step.collect, collectPath, COLLECT_KEYS, COLLECT_EXAMPLE);
|
|
156
|
-
checkNoExtraKeys(step.collect, collectPath, DynamicCollectSchema, COLLECT_KEYS, COLLECT_EXAMPLE);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const parallel = step.parallel;
|
|
160
|
-
if (parallel === undefined) return;
|
|
161
|
-
|
|
162
|
-
if (Array.isArray(parallel)) {
|
|
163
|
-
parallel.forEach((task, pIndex) => {
|
|
164
|
-
const taskPath = `${stepPath}.parallel[${pIndex}]`;
|
|
165
|
-
expectObject(task, taskPath, PARALLEL_TASK_KEYS, PARALLEL_TASK_EXAMPLE);
|
|
166
|
-
checkNoExtraKeys(task, taskPath, ParallelTaskSchema, PARALLEL_TASK_KEYS, PARALLEL_TASK_EXAMPLE);
|
|
167
|
-
});
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if (isPlainObject(parallel)) {
|
|
172
|
-
const templatePath = `${stepPath}.parallel (dynamic fanout template)`;
|
|
173
|
-
checkNoExtraKeys(parallel, templatePath, DynamicParallelTemplateSchema, DYNAMIC_TEMPLATE_KEYS, DYNAMIC_TEMPLATE_EXAMPLE);
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
throw new Error(
|
|
178
|
-
`subagent ${stepPath}.parallel: expected an array of task objects or a dynamic fanout template object, received ${typeName(parallel)}.\n\nExample static: [${PARALLEL_TASK_EXAMPLE}]\nExample dynamic: ${DYNAMIC_TEMPLATE_EXAMPLE}`,
|
|
179
|
-
);
|
|
180
|
-
});
|
|
181
|
-
}
|