@marcoscale98/pi-extensible-workflows 5.17.0-fork.1 → 6.2.1-fork.1
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 +145 -0
- package/README.md +2 -2
- package/dist/src/agent-execution.d.ts +43 -10
- package/dist/src/agent-execution.js +442 -162
- package/dist/src/background-widget.d.ts +5 -1
- package/dist/src/background-widget.js +5 -10
- package/dist/src/decoders.d.ts +2 -3
- package/dist/src/decoders.js +59 -37
- package/dist/src/execution.d.ts +6 -2
- package/dist/src/execution.js +48 -7
- package/dist/src/host-delivery.js +5 -8
- package/dist/src/host-navigator.js +5 -2
- package/dist/src/host-phases.d.ts +1 -0
- package/dist/src/host-recovery.d.ts +11 -8
- package/dist/src/host-recovery.js +14 -22
- package/dist/src/host-runtime.js +56 -5
- package/dist/src/host-view.d.ts +6 -1
- package/dist/src/host-view.js +46 -20
- package/dist/src/host.d.ts +2 -2
- package/dist/src/host.js +246 -118
- package/dist/src/index.d.ts +1 -2
- package/dist/src/index.js +1985 -1690
- package/dist/src/index.js.map +3 -3
- package/dist/src/paths.d.ts +8 -5
- package/dist/src/paths.js +24 -13
- package/dist/src/persistence.d.ts +1 -0
- package/dist/src/persistence.js +1 -0
- package/dist/src/pi-runtime-runner.js +4 -1
- package/dist/src/registry.d.ts +9 -7
- package/dist/src/registry.js +29 -71
- package/dist/src/runtime/agent-runner.d.ts +2 -0
- package/dist/src/script-tools.d.ts +27 -0
- package/dist/src/script-tools.js +87 -0
- package/dist/src/session-lease.d.ts +1 -1
- package/dist/src/session-lease.js +45 -11
- package/dist/src/settings.d.ts +19 -3
- package/dist/src/settings.js +63 -50
- package/dist/src/store.d.ts +3 -1
- package/dist/src/store.js +17 -1
- package/dist/src/trajectory.d.ts +12 -3
- package/dist/src/trajectory.js +32 -11
- package/dist/src/types.d.ts +129 -45
- package/dist/src/types.js +5 -8
- package/dist/src/utils.d.ts +21 -1
- package/dist/src/utils.js +46 -6
- package/dist/src/validation.d.ts +14 -3
- package/dist/src/validation.js +143 -87
- package/dist/starter/index.js +8 -11
- package/dist/starter/index.js.map +1 -1
- package/dist/starter/review-loop.d.ts +0 -13
- package/dist/starter/review-loop.js +8 -9
- package/dist/subagents/index.js +904 -956
- package/dist/subagents/index.js.map +3 -3
- package/dist/subagents/src/contracts.d.ts +48 -31
- package/dist/subagents/src/contracts.js +20 -9
- package/dist/subagents/src/index.d.ts +1 -1
- package/dist/subagents/src/index.js +14 -8
- package/dist/subagents/src/manager.js +52 -73
- package/dist/subagents/src/navigator.js +3 -14
- package/dist/subagents/src/view.d.ts +1 -1
- package/dist/subagents/src/view.js +15 -68
- package/dist/subagents/src/worktree.js +1 -1
- package/dist/trajectory/assets/index.html +35 -13
- package/dist/trajectory/index.js +576 -932
- package/dist/trajectory/index.js.map +3 -3
- package/dist/trajectory/src/assets/index.html +35 -13
- package/dist/trajectory/src/index.js +134 -104
- package/dist/trajectory/src/server.js +268 -27
- package/dist/trajectory/src/server.js.map +3 -3
- package/dist/trajectory/src/startup-mutex.d.ts +17 -0
- package/dist/trajectory/src/startup-mutex.js +172 -0
- package/examples/workflow-extension-template/README.md +4 -7
- package/examples/workflow-extension-template/extension.test.mjs +4 -8
- package/examples/workflow-extension-template/index.js +0 -3
- package/package.json +10 -10
- package/skills/pi-extensible-workflows/SKILL.md +12 -8
- package/src/agent-execution.ts +412 -164
- package/src/background-widget.ts +5 -11
- package/src/decoders.ts +51 -37
- package/src/execution.ts +46 -9
- package/src/host-delivery.ts +5 -8
- package/src/host-navigator.ts +4 -2
- package/src/host-phases.ts +1 -1
- package/src/host-recovery.ts +23 -28
- package/src/host-runtime.ts +44 -6
- package/src/host-view.ts +41 -16
- package/src/host.ts +229 -142
- package/src/index.ts +1 -2
- package/src/paths.ts +18 -9
- package/src/persistence.ts +1 -0
- package/src/pi-runtime-runner.ts +3 -1
- package/src/registry.ts +28 -64
- package/src/runtime/agent-runner.ts +2 -0
- package/src/script-tools.ts +76 -0
- package/src/session-lease.ts +34 -6
- package/src/settings.ts +56 -50
- package/src/store.ts +18 -2
- package/src/trajectory.ts +32 -15
- package/src/types.ts +82 -34
- package/src/utils.ts +42 -5
- package/src/validation.ts +121 -80
- package/starter/index.ts +0 -1
- package/starter/review-loop.ts +8 -9
- package/starter/roles/developer.md +0 -1
- package/starter/roles/oracle.md +0 -1
- package/starter/roles/researcher.md +0 -1
- package/starter/roles/reviewer.md +0 -1
- package/starter/roles/scout.md +0 -1
- package/subagents/README.md +10 -16
- package/subagents/src/contracts.ts +21 -11
- package/subagents/src/index.ts +17 -8
- package/subagents/src/manager.ts +48 -128
- package/subagents/src/navigator.ts +4 -13
- package/subagents/src/view.ts +16 -63
- package/subagents/src/worktree.ts +1 -1
- package/trajectory/src/assets/index.html +35 -13
- package/trajectory/src/index.ts +112 -91
- package/trajectory/src/server.ts +46 -10
- package/trajectory/src/startup-mutex.ts +127 -0
- package/dist/src/roles.d.ts +0 -70
- package/dist/src/roles.js +0 -325
- package/dist/starter/roles/developer.md +0 -6
- package/dist/starter/roles/oracle.md +0 -18
- package/dist/starter/roles/researcher.md +0 -20
- package/dist/starter/roles/reviewer.md +0 -19
- package/dist/starter/roles/scout.md +0 -18
- package/examples/workflow-extension-template/roles/reviewer.md +0 -4
- package/src/roles.ts +0 -326
package/src/validation.ts
CHANGED
|
@@ -2,17 +2,14 @@ import { readFileSync } from "node:fs";
|
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import * as acorn from "acorn";
|
|
4
4
|
import { Script } from "node:vm";
|
|
5
|
-
import type {
|
|
5
|
+
import type { CheckpointInput, JsonSchema, JsonValue, PreflightCapabilities, PreflightResult, ShellOptions, StaticWorkflowCall, StaticWorkflowExecution, StaticWorkflowScope, ValidatedWorkflowLaunch, WorkflowCallKind, WorkflowErrorCode, WorkflowMetadata, WorkflowValidationContext, WorkflowValidationParameters } from "./types.js";
|
|
6
6
|
import type { WorkflowRegistryApi } from "./registry.js";
|
|
7
|
-
import { assertModelThinking, validateSchema, deepFreeze, errorText, fail, jsonObject, jsonValue,
|
|
7
|
+
import { assertModelThinking, validateSchema, deepFreeze, errorText, fail, jsonObject, jsonValue, object, positiveInteger, resourcePatternHasMagic } from "./utils.js";
|
|
8
8
|
import { WORKFLOW_CALL_KINDS } from "./types.js";
|
|
9
9
|
export { validateSchema } from "./utils.js";
|
|
10
|
-
import { loadAgentDefinitions, loadProjectAgentDefinitions } from "./roles.js";
|
|
11
|
-
export { loadAgentDefinitions, loadProjectAgentDefinitions, parseRoleMarkdown, workflowRoleDirectories } from "./roles.js";
|
|
12
|
-
export type { WorkflowRoleDirectoryInput } from "./roles.js";
|
|
13
10
|
|
|
14
11
|
import { validateContextFileScopes, validateSelectorList } from "./settings.js";
|
|
15
|
-
export { DEFAULT_SETTINGS, loadSettings, loadSettingsOverrides, resolveAgentResourcePolicy, resolveWorkflowSettings, saveModelAliases, validateContextFileScopes, validateModelAliasAvailability, validateSelectorList, validateWorkflowExtensionSettings, workflowProjectSettingsPath, workflowSettingsPath } from "./settings.js";
|
|
12
|
+
export { DEFAULT_SETTINGS, loadCodemodeToolsSetting, loadSettings, loadSettingsOverrides, resolveAgentResourcePolicy, resolveWorkflowSettings, saveModelAliases, validateContextFileScopes, validateModelAliasAvailability, validateSelectorList, validateWorkflowExtensionSettings, workflowProjectSettingsPath, workflowSettingsPath, workflowToolExposure } from "./settings.js";
|
|
16
13
|
|
|
17
14
|
export function validateCheckpoint(value: unknown): CheckpointInput {
|
|
18
15
|
if (!object(value) || Object.keys(value).some((key) => !["name", "prompt", "context"].includes(key)) || typeof value.name !== "string" || value.name.trim() === "" || typeof value.prompt !== "string" || !jsonValue(value.context)) fail("INVALID_METADATA", "checkpoint requires only name, prompt, and JSON context");
|
|
@@ -21,22 +18,6 @@ export function validateCheckpoint(value: unknown): CheckpointInput {
|
|
|
21
18
|
return { name: value.name, prompt: value.prompt, context: value.context };
|
|
22
19
|
}
|
|
23
20
|
|
|
24
|
-
function validateRolePolicies(definitions: Readonly<Record<string, AgentDefinition>>, roles: readonly string[], availableModels: ReadonlySet<string>, aliases: Readonly<Record<string, string>> = {}, knownModels = availableModels, settingsPath?: string): void {
|
|
25
|
-
for (const role of roles) {
|
|
26
|
-
const definition = definitions[role];
|
|
27
|
-
if (!definition) continue;
|
|
28
|
-
if (definition.model !== undefined) {
|
|
29
|
-
const resolved = modelCapability(definition.model, aliases, knownModels, settingsPath);
|
|
30
|
-
if (!availableModels.has(resolved)) {
|
|
31
|
-
if (modelAliasName(definition.model, aliases)) unknownModel(definition.model, resolved, settingsPath);
|
|
32
|
-
fail("UNKNOWN_MODEL", `Unknown model for role ${role}: ${resolved}`);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
// Role tools absent from the session are tolerated at launch: the runtime resolve emits a
|
|
36
|
-
// warning and the agent runs without them. Doctor still reports unmatched patterns.
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
21
|
function validateWorkflowMetadata(value: unknown): WorkflowMetadata {
|
|
41
22
|
if (!object(value) || typeof value.name !== "string" || value.name.trim() === "") fail("INVALID_METADATA", "Workflow metadata requires a non-empty name");
|
|
42
23
|
if (value.description !== undefined && (typeof value.description !== "string" || value.description.trim() === "")) fail("INVALID_METADATA", "Workflow description must be a non-empty string when provided");
|
|
@@ -219,6 +200,24 @@ function callHasTrailingComma(source: string, call: WorkflowCall): boolean {
|
|
|
219
200
|
return current?.type.label === ")" && previous?.type.label === ",";
|
|
220
201
|
}
|
|
221
202
|
|
|
203
|
+
/**
|
|
204
|
+
* The `tools.<id>` members a workflow script names statically. Any other use of `tools`, such as a
|
|
205
|
+
* local declaration that shadows the global or passing it around, makes the set unknowable: undefined.
|
|
206
|
+
*/
|
|
207
|
+
export function scriptToolReferences(script: string): string[] | undefined {
|
|
208
|
+
const body = workflowBody(script);
|
|
209
|
+
if (!body.trim()) return [];
|
|
210
|
+
const names = new Set<string>();
|
|
211
|
+
const opaque = (node: acorn.AnyNode, parent?: acorn.AnyNode): boolean => {
|
|
212
|
+
if (node.type === "Identifier" && node.name === "tools") {
|
|
213
|
+
if (parent?.type === "MemberExpression" && parent.object === node && !parent.computed && parent.property.type === "Identifier") names.add(parent.property.name);
|
|
214
|
+
else if (!(parent?.type === "Property" && parent.key === node && !parent.computed && !parent.shorthand || parent?.type === "MemberExpression" && parent.property === node && !parent.computed)) return true;
|
|
215
|
+
}
|
|
216
|
+
return astChildren(node).some((child) => opaque(child, node));
|
|
217
|
+
};
|
|
218
|
+
return opaque(parseWorkflow(body)) ? undefined : [...names];
|
|
219
|
+
}
|
|
220
|
+
|
|
222
221
|
export function instrumentWorkflow(script: string): string {
|
|
223
222
|
const body = workflowBody(script);
|
|
224
223
|
if (!body.trim()) return body;
|
|
@@ -291,8 +290,9 @@ export function workflowPrompt(template: string, values: Readonly<Record<string,
|
|
|
291
290
|
});
|
|
292
291
|
}
|
|
293
292
|
|
|
294
|
-
const AGENT_OPTION_KEYS = new Set(["label", "model", "tools", "
|
|
295
|
-
function
|
|
293
|
+
const AGENT_OPTION_KEYS = new Set(["label", "model", "tools", "excludeTools", "skills", "extensions", "contextFiles", "outputSchema", "retries", "timeoutMs"]);
|
|
294
|
+
export function isCoreAgentOption(key: string): boolean { return AGENT_OPTION_KEYS.has(key); }
|
|
295
|
+
function validateAgentOption(key: string, value: unknown): void {
|
|
296
296
|
switch (key) {
|
|
297
297
|
case "label":
|
|
298
298
|
if (typeof value !== "string" || !value.trim()) fail("INVALID_METADATA", "agent label must be a non-empty string");
|
|
@@ -300,19 +300,19 @@ function validateAgentOption(key: string, value: unknown, aliases?: Readonly<Rec
|
|
|
300
300
|
case "model":
|
|
301
301
|
if (typeof value !== "string" || !value.trim()) fail("INVALID_METADATA", "agent model must be a non-empty string");
|
|
302
302
|
assertModelThinking(value, "agent model");
|
|
303
|
-
if (aliases !== undefined) resolveModelReference(value, aliases, knownModels, settingsPath);
|
|
304
303
|
break;
|
|
305
304
|
case "tools":
|
|
306
305
|
case "skills":
|
|
307
306
|
case "extensions":
|
|
308
307
|
validateSelectorList(value, "agent options", key, "INVALID_METADATA", key !== "extensions");
|
|
309
308
|
break;
|
|
309
|
+
case "excludeTools":
|
|
310
|
+
if (!Array.isArray(value) || value.some((tool) => typeof tool !== "string" || !tool.trim() || tool !== tool.trim() || tool.startsWith("!") || resourcePatternHasMagic(tool))) fail("INVALID_METADATA", "agent excludeTools must be an array of exact tool names");
|
|
311
|
+
if (value.includes("workflow_result")) fail("INVALID_METADATA", "agent excludeTools cannot remove workflow_result");
|
|
312
|
+
break;
|
|
310
313
|
case "contextFiles":
|
|
311
314
|
validateContextFileScopes(value, "agent options");
|
|
312
315
|
break;
|
|
313
|
-
case "role":
|
|
314
|
-
if (typeof value !== "string" || !value.trim()) fail("INVALID_METADATA", "agent role must be a non-empty string");
|
|
315
|
-
break;
|
|
316
316
|
case "outputSchema":
|
|
317
317
|
validateSchema(value, "agent outputSchema");
|
|
318
318
|
break;
|
|
@@ -411,36 +411,101 @@ export function inspectWorkflowScript(script: string): StaticWorkflowCall[] {
|
|
|
411
411
|
if (value.known && jsonValue(value.value)) knownOptionEntries.push([key, value.value]);
|
|
412
412
|
}
|
|
413
413
|
const knownOptions: Record<string, JsonValue> = Object.fromEntries(knownOptionEntries);
|
|
414
|
-
const base = { ...placement, kind, start: call.start, end: call.end, name: null, prompt: staticString(first), model: staticString(propertyNode(options, "model")), label: staticString(propertyNode(options, "label"))
|
|
414
|
+
const base = { ...placement, kind, start: call.start, end: call.end, name: null, prompt: staticString(first), model: staticString(propertyNode(options, "model")), label: staticString(propertyNode(options, "label")) };
|
|
415
415
|
return { ...base, ...(retries.known && typeof retries.value === "number" ? { retries: retries.value } : {}), ...(staticOutputSchema === undefined ? {} : { outputSchema: staticOutputSchema }), ...(optionKeys.length ? { options: knownOptions, optionKeys } : {}) };
|
|
416
416
|
}
|
|
417
|
-
if (kind === "checkpoint") return { ...placement, kind, start: call.start, end: call.end, name: staticString(propertyNode(first, "name")), prompt: staticString(propertyNode(first, "prompt")), model: null
|
|
418
|
-
if (kind === "shell") return { ...placement, kind, start: call.start, end: call.end, name: staticString(first), prompt: null, model: null
|
|
419
|
-
return { ...placement, kind, start: call.start, end: call.end, name: staticString(first), prompt: null, model: null
|
|
417
|
+
if (kind === "checkpoint") return { ...placement, kind, start: call.start, end: call.end, name: staticString(propertyNode(first, "name")), prompt: staticString(propertyNode(first, "prompt")), model: null };
|
|
418
|
+
if (kind === "shell") return { ...placement, kind, start: call.start, end: call.end, name: staticString(first), prompt: null, model: null };
|
|
419
|
+
return { ...placement, kind, start: call.start, end: call.end, name: staticString(first), prompt: null, model: null };
|
|
420
420
|
});
|
|
421
421
|
}
|
|
422
422
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
423
|
+
/** Every option is a preparation input, because any hook may read it; one dynamic value leaves the whole call to runtime. */
|
|
424
|
+
function staticPreparationOptions(node: acorn.AnyNode | undefined, omitted?: string): Record<string, JsonValue> | undefined {
|
|
425
|
+
if (node?.type !== "ObjectExpression") return undefined;
|
|
426
|
+
const options = new Map<string, JsonValue>();
|
|
427
|
+
for (const property of node.properties) {
|
|
428
|
+
if (property.type === "SpreadElement" || property.computed) return undefined;
|
|
429
|
+
const key = propertyKeyName(property);
|
|
430
|
+
if (key === undefined) return undefined;
|
|
431
|
+
if (key === omitted) continue;
|
|
432
|
+
const value = staticValue(property.value);
|
|
433
|
+
// NOTE: an output schema only shapes the result, so a computed one (a const declared elsewhere) does not hide the call.
|
|
434
|
+
if (key === "outputSchema" && !value.known) continue;
|
|
435
|
+
if (!value.known || !jsonValue(value.value)) return undefined;
|
|
436
|
+
options.set(key, value.value);
|
|
428
437
|
}
|
|
438
|
+
return Object.fromEntries(options);
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* A handle prepares its first turn with the create options merged with that send's options, so create options are complete
|
|
442
|
+
* only when every use of the handle provably passes no send options: `agent.create(...).send(prompt)` directly, or a
|
|
443
|
+
* `const` handle whose every reference is `handle.send(prompt)`. Computed access, destructuring, aliases or any other
|
|
444
|
+
* use leave the handle to runtime.
|
|
445
|
+
*/
|
|
446
|
+
function handlesWithCompleteOptions(program: acorn.Program): acorn.CallExpression[] {
|
|
447
|
+
const parents = new Map<acorn.AnyNode, acorn.AnyNode>();
|
|
448
|
+
const references = new Map<string, acorn.AnyNode[]>();
|
|
449
|
+
const visit = (node: acorn.AnyNode): void => {
|
|
450
|
+
if (node.type === "Identifier") references.set(node.name, [...(references.get(node.name) ?? []), node]);
|
|
451
|
+
for (const child of astChildren(node)) { parents.set(child, node); visit(child); }
|
|
452
|
+
};
|
|
453
|
+
visit(program);
|
|
454
|
+
const plainSend = (member: acorn.AnyNode | undefined): boolean => {
|
|
455
|
+
const call = member ? parents.get(member) : undefined;
|
|
456
|
+
return member?.type === "MemberExpression" && !member.computed && member.property.type === "Identifier" && member.property.name === "send"
|
|
457
|
+
&& call?.type === "CallExpression" && call.callee === member && call.arguments.length <= 1 && call.arguments.every((argument) => argument.type !== "SpreadElement");
|
|
458
|
+
};
|
|
459
|
+
return agentCreateCalls(program).filter((create) => {
|
|
460
|
+
const parent = parents.get(create);
|
|
461
|
+
if (parent?.type === "MemberExpression" && parent.object === create) return plainSend(parent);
|
|
462
|
+
const declaration = parent ? parents.get(parent) : undefined;
|
|
463
|
+
if (parent?.type !== "VariableDeclarator" || parent.init !== create || parent.id.type !== "Identifier" || declaration?.type !== "VariableDeclaration" || declaration.kind !== "const") return false;
|
|
464
|
+
const name = parent.id.name;
|
|
465
|
+
return (references.get(name) ?? []).every((reference) => reference === parent.id || (parents.get(reference)?.type === "MemberExpression" && (parents.get(reference) as acorn.MemberExpression).object === reference && plainSend(parents.get(reference))));
|
|
466
|
+
});
|
|
429
467
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
468
|
+
/**
|
|
469
|
+
* Distinct, completely static option objects of agent(...) and agent.create(...) calls, so launch can inspect them
|
|
470
|
+
* before any effect. Calls with any dynamic option stay runtime-checked, and so do handles whose sends may add options.
|
|
471
|
+
*/
|
|
472
|
+
export function staticAgentPreparationOptions(script: string, registered: Readonly<Record<string, { run: (...args: never[]) => unknown }>> = {}): Array<Readonly<Record<string, JsonValue>>> {
|
|
473
|
+
const program = parseWorkflow(script);
|
|
474
|
+
const sources = [program, ...registeredFunctionPrograms(program, registered)];
|
|
475
|
+
const distinct = new Map<string, Readonly<Record<string, JsonValue>>>();
|
|
476
|
+
for (const source of sources) for (const options of programAgentOptions(source)) distinct.set(JSON.stringify(options), deepFreeze(options));
|
|
477
|
+
return [...distinct.values()];
|
|
478
|
+
}
|
|
479
|
+
/** Programs of the registered functions the script calls by name, so their agents are inspected before launch like inline ones. */
|
|
480
|
+
function registeredFunctionPrograms(program: acorn.Program, registered: Readonly<Record<string, { run: (...args: never[]) => unknown }>>): acorn.Program[] {
|
|
481
|
+
const called = new Set<string>();
|
|
482
|
+
const visit = (node: acorn.AnyNode): void => {
|
|
483
|
+
if (node.type === "CallExpression" && node.callee.type === "Identifier" && Object.prototype.hasOwnProperty.call(registered, node.callee.name)) called.add(node.callee.name);
|
|
484
|
+
for (const child of astChildren(node)) visit(child);
|
|
485
|
+
};
|
|
486
|
+
visit(program);
|
|
487
|
+
const programs: acorn.Program[] = [];
|
|
488
|
+
for (const name of called) {
|
|
489
|
+
const source = registered[name]?.run.toString() ?? "";
|
|
490
|
+
// A method prints as `async run(...) {}`, which only parses inside an object literal.
|
|
491
|
+
for (const wrapped of [`(${source})`, `({${source}})`]) {
|
|
492
|
+
try { programs.push(acorn.parse(wrapped, { ecmaVersion: "latest", sourceType: "module" })); break; } catch { /* try the next form; an unparsable function stays runtime-checked */ }
|
|
441
493
|
}
|
|
442
494
|
}
|
|
443
|
-
return
|
|
495
|
+
return programs;
|
|
496
|
+
}
|
|
497
|
+
function programAgentOptions(program: acorn.Program): Array<Readonly<Record<string, JsonValue>>> {
|
|
498
|
+
const calls = workflowCalls(program).filter((call) => call.callee.name === "agent").map((call) => call.arguments.some((argument) => argument.type === "SpreadElement") ? undefined : call.arguments.length < 2 ? {} : staticPreparationOptions(callArgument(call, 1)));
|
|
499
|
+
const handles = handlesWithCompleteOptions(program).map((call) => staticPreparationOptions(callArgument(call, 0), "name"));
|
|
500
|
+
return [...calls, ...handles].filter((options): options is Record<string, JsonValue> => options !== undefined);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function validateStaticAgentOptions(node: acorn.AnyNode | undefined): void {
|
|
504
|
+
if (node?.type !== "ObjectExpression") return;
|
|
505
|
+
for (const key of AGENT_OPTION_KEYS) {
|
|
506
|
+
const value = staticValue(propertyNode(node, key));
|
|
507
|
+
if (value.known) validateAgentOption(key, value.value);
|
|
508
|
+
}
|
|
444
509
|
}
|
|
445
510
|
function validateStaticShellOptions(call: WorkflowCall): void {
|
|
446
511
|
if (call.arguments.some((argument) => argument.type === "SpreadElement")) return;
|
|
@@ -477,7 +542,7 @@ export function preflight(script: string, capabilities: PreflightCapabilities, s
|
|
|
477
542
|
const phases = calls.filter((call) => call.callee.name === "phase").map((call) => literalString(call.arguments[0])).filter((phase): phase is string => phase !== undefined);
|
|
478
543
|
for (const call of calls) {
|
|
479
544
|
const operation = call.callee.name;
|
|
480
|
-
if (operation === "agent") validateStaticAgentOptions(call.arguments[1]
|
|
545
|
+
if (operation === "agent") validateStaticAgentOptions(call.arguments[1]);
|
|
481
546
|
if (operation === "withWorktree") validateStaticWithWorktree(call, compatibility);
|
|
482
547
|
if (operation === "shell") validateStaticShellOptions(call);
|
|
483
548
|
if ((operation === "parallel" || operation === "pipeline") && call.arguments.some((argument) => argument.type === "SpreadElement")) continue;
|
|
@@ -488,11 +553,10 @@ export function preflight(script: string, capabilities: PreflightCapabilities, s
|
|
|
488
553
|
const handleOptions = agentCreateCalls(program).map((call) => {
|
|
489
554
|
const options = call.arguments.length === 1 ? callArgument(call, 0) : undefined;
|
|
490
555
|
if (options?.type !== "ObjectExpression" || !literalString(propertyNode(options, "name"))?.trim()) fail("INVALID_METADATA", "agent.create requires an options object with a stable explicit name");
|
|
491
|
-
validateStaticAgentOptions(options
|
|
556
|
+
validateStaticAgentOptions(options);
|
|
492
557
|
return options;
|
|
493
558
|
});
|
|
494
559
|
const agentOptions = [...calls.filter((call) => call.callee.name === "agent").map((call) => callArgument(call, 1)), ...handleOptions];
|
|
495
|
-
const dynamicAgentRoles = agentOptions.some((options) => hasDynamicAgentRole(options));
|
|
496
560
|
const staticSchemas: JsonSchema[] = [];
|
|
497
561
|
for (const options of agentOptions) {
|
|
498
562
|
const value = staticValue(propertyNode(options, "outputSchema"));
|
|
@@ -502,25 +566,16 @@ export function preflight(script: string, capabilities: PreflightCapabilities, s
|
|
|
502
566
|
staticSchemas.push(schema);
|
|
503
567
|
}
|
|
504
568
|
checkedSchemas.push(...staticSchemas);
|
|
505
|
-
const
|
|
506
|
-
const models = modelRefs.map(({ resolved }) => resolved);
|
|
569
|
+
const models: string[] = [];
|
|
507
570
|
const tools = agentOptions.flatMap((options) => {
|
|
508
571
|
const value = propertyNode(options, "tools");
|
|
509
572
|
return value?.type === "ArrayExpression" ? value.elements.flatMap((element) => { const tool = element && element.type !== "SpreadElement" ? literalString(element) : undefined; return tool === undefined ? [] : [tool]; }) : [];
|
|
510
573
|
});
|
|
511
|
-
const agentTypes = agentOptions.flatMap((options) => { const value = staticString(propertyNode(options, "role")); return value === null ? [] : [value]; });
|
|
512
574
|
for (const pattern of tools) {
|
|
513
575
|
const body = pattern.startsWith("!") ? pattern.slice(1) : pattern;
|
|
514
576
|
if (!pattern.startsWith("!") && !resourcePatternHasMagic(pattern) && !capabilities.tools.has(body)) fail("UNKNOWN_TOOL", `Unknown tool: ${body}`);
|
|
515
577
|
}
|
|
516
|
-
|
|
517
|
-
if (missingModel) {
|
|
518
|
-
if (modelAliasName(missingModel.requested, capabilities.modelAliases ?? {})) unknownModel(missingModel.requested, missingModel.resolved, capabilities.settingsPath);
|
|
519
|
-
fail("UNKNOWN_MODEL", `Unknown model: ${missingModel.resolved}`);
|
|
520
|
-
}
|
|
521
|
-
const missingType = agentTypes.find((type) => !capabilities.agentTypes.has(type));
|
|
522
|
-
if (missingType) fail("UNKNOWN_AGENT_TYPE", `Unknown agent type: ${missingType}`);
|
|
523
|
-
return Object.freeze({ metadata: deepFreeze(checkedMetadata), referenced: deepFreeze({ phases, models, tools, agentTypes }), schemas: deepFreeze(checkedSchemas), dynamicAgentRoles });
|
|
578
|
+
return Object.freeze({ metadata: deepFreeze(checkedMetadata), referenced: deepFreeze({ phases, models, tools }), schemas: deepFreeze(checkedSchemas) });
|
|
524
579
|
}
|
|
525
580
|
|
|
526
581
|
export function validateWorkflowLaunch(params: WorkflowValidationParameters, context: WorkflowValidationContext, registry?: WorkflowRegistryApi): ValidatedWorkflowLaunch {
|
|
@@ -544,23 +599,9 @@ export function validateWorkflowLaunchWithRegistry(params: WorkflowValidationPar
|
|
|
544
599
|
const script = typeof params.script === "string" && params.script.trim() ? params.script : fileScript ?? "";
|
|
545
600
|
if (!script) fail("INVALID_SYNTAX", "Provide script or scriptPath");
|
|
546
601
|
const metadata = validateWorkflowMetadata({ name: explicitName, ...(typeof params.description === "string" ? { description: params.description } : {}) });
|
|
547
|
-
const
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
const aliases = context.modelAliases ?? {};
|
|
551
|
-
const knownModels = context.knownModels ?? context.availableModels;
|
|
552
|
-
const checked = preflight(script, { models: context.availableModels, tools: context.rootTools, agentTypes: new Set(Object.keys(agentDefinitions)), modelAliases: aliases, knownModels, ...(context.settingsPath ? { settingsPath: context.settingsPath } : {}) }, [], metadata);
|
|
553
|
-
const roleNames = checked.dynamicAgentRoles ? Object.keys(agentDefinitions) : checked.referenced.agentTypes;
|
|
554
|
-
validateRolePolicies(agentDefinitions, roleNames, context.availableModels, aliases, knownModels, context.settingsPath);
|
|
555
|
-
if (registry?.validateExtensionSettings) {
|
|
556
|
-
const validate = registry.validateExtensionSettings.bind(registry);
|
|
557
|
-
const validatorContext = (source: "effective" | "role", role?: string) => ({ source, cwd: context.cwd, projectTrusted: context.projectTrusted, ...(context.settingsPath ? { settingsPath: context.settingsPath } : {}), ...(role === undefined ? {} : { role }) });
|
|
558
|
-
validate(context.extensionSettings, validatorContext("effective"));
|
|
559
|
-
for (const role of roleNames) {
|
|
560
|
-
validate(mergeWorkflowExtensionSettings(context.extensionSettings, agentDefinitions[role]?.extensionSettings), validatorContext("role", role));
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
return { script, checked, agentDefinitions, projectAgentDefinitions, roleNames };
|
|
602
|
+
const checked = preflight(script, { models: context.availableModels, tools: context.rootTools, modelAliases: context.modelAliases ?? {}, knownModels: context.knownModels ?? context.availableModels, ...(context.settingsPath ? { settingsPath: context.settingsPath } : {}) }, [], metadata);
|
|
603
|
+
registry?.validateExtensionSettings(context.extensionSettings, { source: "effective", cwd: context.cwd, projectTrusted: context.projectTrusted, ...(context.settingsPath ? { settingsPath: context.settingsPath } : {}) });
|
|
604
|
+
return { script, checked };
|
|
564
605
|
}
|
|
565
606
|
|
|
566
607
|
export { createLaunchSnapshot, loadLaunchSnapshot } from "./utils.js";
|
package/starter/index.ts
CHANGED
package/starter/review-loop.ts
CHANGED
|
@@ -17,27 +17,28 @@ export const reviewLoop = defineWorkflowFunction({
|
|
|
17
17
|
);
|
|
18
18
|
let devResult: JsonValue = null;
|
|
19
19
|
let review: Static<typeof reviewSchema> = { pass: false, findings: [] };
|
|
20
|
+
// One developer keeps its transcript across iterations; each reviewer starts fresh.
|
|
21
|
+
const developer = agent.create({ name: "developer", label: "Developer", role: "developer" });
|
|
20
22
|
|
|
21
23
|
for (let iterations = 1; iterations <= maxIterations; iterations += 1) {
|
|
22
24
|
const devPrompt =
|
|
23
25
|
iterations === 1
|
|
24
26
|
? prompt("Implement this task:\n\n{task}", { task })
|
|
25
27
|
: prompt(
|
|
26
|
-
`Address the
|
|
27
|
-
<original_task>{task}</original_task>
|
|
28
|
+
`Address the latest review findings and complete the original task.
|
|
28
29
|
<last_review>{review}</last_review>`,
|
|
29
|
-
{
|
|
30
|
+
{ review: review.findings },
|
|
30
31
|
);
|
|
31
|
-
devResult = await
|
|
32
|
+
devResult = await developer.send(devPrompt);
|
|
32
33
|
review = await agent(
|
|
33
34
|
prompt(
|
|
34
|
-
`Review the implementation against the task.
|
|
35
|
+
`Review the actual current implementation against the entire original task. Inspect the code and run relevant checks yourself. Do not rely on the developer summary or on previous findings; they may be incomplete or wrong. Set pass=true only when the whole task is complete and correct.
|
|
35
36
|
<original_task>{task}</original_task>
|
|
36
|
-
<
|
|
37
|
+
<previous_review_findings>{review}</previous_review_findings>
|
|
37
38
|
<dev_summary>{devResult}</dev_summary>`,
|
|
38
39
|
{ task, devResult, review: review.findings },
|
|
39
40
|
),
|
|
40
|
-
{ role: "reviewer", outputSchema: reviewSchema },
|
|
41
|
+
{ label: `Reviewer ${String(iterations)}`, role: "reviewer", outputSchema: reviewSchema },
|
|
41
42
|
);
|
|
42
43
|
|
|
43
44
|
if (review.pass) return { pass: true, iterations, devResult, review };
|
|
@@ -69,5 +70,3 @@ export const reviewLoop = defineWorkflowFunction({
|
|
|
69
70
|
),
|
|
70
71
|
});
|
|
71
72
|
|
|
72
|
-
export const reviewLoopInputSchema = reviewLoop.input;
|
|
73
|
-
export const reviewLoopOutputSchema = reviewLoop.output;
|
package/starter/roles/oracle.md
CHANGED
package/starter/roles/scout.md
CHANGED
package/subagents/README.md
CHANGED
|
@@ -18,7 +18,7 @@ Use the built-in subagent tools for focused, independent tasks. Use `pi-extensib
|
|
|
18
18
|
|
|
19
19
|
- Five focused tools: run, inspect, steer, stop, and retry.
|
|
20
20
|
- Background fan-out with one durable ID per run, or foreground execution with an inline terminal result.
|
|
21
|
-
- Reuses workflow
|
|
21
|
+
- Reuses generic workflow preparation hooks, model aliases, settings including extension-defined namespaces, and agent options: `label`, `model`, `tools`, `skills`, `extensions`, `contextFiles`, `worktree`, `outputSchema`, `retries` (0 through 255), and `timeoutMs`.
|
|
22
22
|
- Repeatable inspection of progress, token accounting, tool calls, results, failures, and worktrees.
|
|
23
23
|
|
|
24
24
|
Built-in subagent tools are available when the core extension is installed. Use the core package's Pi extension manifest to enable them.
|
|
@@ -44,23 +44,23 @@ subagents_run({ prompt: "Summarize README.md.", mode: "foreground" })
|
|
|
44
44
|
|
|
45
45
|
## Interactive inspection
|
|
46
46
|
|
|
47
|
-
In Pi's TUI, `/subagents` opens the `/workflow` dashboard layout for the durable standalone runs of the current session: the run list sits beside the selected run's details at 80 columns or wider, and narrower terminals drill down from the list to the details. The details show the run ID and the same activity, stall warning, state, model,
|
|
47
|
+
In Pi's TUI, `/subagents` opens the `/workflow` dashboard layout for the durable standalone runs of the current session: the run list sits beside the selected run's details at 80 columns or wider, and narrower terminals drill down from the list to the details. The details show the run ID and the same activity, stall warning, state, model, tools, attempts, duration, cumulative token accounting, cost, and error fields as a `/workflow` agent; a running run shows its live activity, which `subagents_inspect` leaves out. While a run is active the view refreshes the active runs and the selection every second; runs launched while it is open appear after an action or when it reopens. `a` or `enter` opens registered standalone agent actions, editor controls for the prompt, the live system prompt, and the result, `Steer` and `Stop` while running, `Retry` for failed or stopped runs, and the `/workflow` run actions that apply: `Delete`, `Delete all completed`, `Delete all failed`, `Copy run path`, and `Copy agent ID`. Stop and every deletion ask for confirmation, and a run whose worktree still awaits cleanup cannot be deleted. Outside the TUI, `/subagents` opens a picker with the same run summary and bulk deletions as the `/workflow` picker. Background runs appear below the editor in the workflow widget's frame, one row per run with its model, tokens, cost, elapsed time, and quiet or stalled warning, and each leaves a receipt in the transcript when it settles. Inspection does not launch a new LLM call; lifecycle actions may steer, stop, retry, or delete a run.
|
|
48
48
|
|
|
49
49
|
## Tools
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
`subagents_run` accepts additional JSON agent options for loaded preparation hooks; unknown options are ignored when no plugin handles them. The other control-tool schemas are closed objects. The model-visible standalone surface contains exactly these five names:
|
|
52
52
|
|
|
53
53
|
| Tool | Input schema |
|
|
54
54
|
| --- | --- |
|
|
55
|
-
| `subagents_run` | `{ prompt: string, mode?: "background" \| "foreground", label?: string, model?: string, tools?: string[], skills?: string[], extensions?: string[], contextFiles?: string[],
|
|
56
|
-
| `subagents_inspect` | `{ id?: string }` |
|
|
55
|
+
| `subagents_run` | `{ prompt: string, mode?: "background" \| "foreground", label?: string, model?: string, tools?: string[], skills?: string[], extensions?: string[], contextFiles?: string[], worktree?: string, outputSchema?: object, retries?: integer 0..255, timeoutMs?: positive integer \| null }` |
|
|
56
|
+
| `subagents_inspect` | `{ id?: string, scope?: "session" \| "all" }` |
|
|
57
57
|
| `subagents_steer` | `{ id: string, message: string }` |
|
|
58
58
|
| `subagents_stop` | `{ id: string }` |
|
|
59
59
|
| `subagents_retry` | `{ id: string }` |
|
|
60
60
|
|
|
61
|
-
`prompt` is the only required `subagents_run` property. `mode` defaults to "background". Resource candidates start enabled, so plain positive top-level selector lists are additive and top-level `[]` adds no matches. Use `["!*", "read", "grep"]` to restrict a selector to those resources, or `["!*"]` to select none.
|
|
61
|
+
`prompt` is the only required `subagents_run` property. `mode` defaults to "background". Resource candidates start enabled, so plain positive top-level selector lists are additive and top-level `[]` adds no matches. Use `["!*", "read", "grep"]` to restrict a selector to those resources, or `["!*"]` to select none. Unknown JSON options are forwarded to enabled plugins. `role` has no core meaning. Concrete models are `provider/model:thinking`. Capability selectors cannot exceed root and parent ceilings. `excludeTools` takes exact tool names removed after preparation hooks, as Pi's `excludeTools` does; patterns and `workflow_result` are rejected with `INVALID_METADATA`, names also remove same-named custom tools, are frozen with the configuration that retries reuse, and names matching no root or custom tool only warn.
|
|
62
62
|
|
|
63
|
-
`subagents_inspect({})` returns
|
|
63
|
+
`subagents_inspect({})` returns the current Pi session's run summaries ordered by start time; `subagents_inspect({ scope: "all" })` lists every stored run. `subagents_inspect({ id })` returns the detailed lifecycle record, including state, start and finish timestamps, and the live snapshot under `progress`: `state`, cumulative `accounting`, `toolCalls`, `activity`, and `lastEventAt`. The snapshot state never includes the effective system prompt, and inspection has no `usage` field; token totals are derived from accounting. Materialized worktree path and branch are included when available. For completed runs it also includes `value`; for failed runs it includes `error`. A running run has no terminal value yet. Unknown IDs fail with `RUN_NOT_FOUND`.
|
|
64
64
|
|
|
65
65
|
## Launching and concurrency
|
|
66
66
|
|
|
@@ -82,7 +82,7 @@ Each call owns an independent run, so calls can execute concurrently and settle
|
|
|
82
82
|
|
|
83
83
|
## IDs, inspection, and terminal values
|
|
84
84
|
|
|
85
|
-
Run records are stored below the agent directory's private `subagents/<id>/` directory, normally `~/.pi/agent/subagents/<id>/`. The record includes the normalized request, its frozen
|
|
85
|
+
Run records are stored below the agent directory's private `subagents/<id>/` directory, normally `~/.pi/agent/subagents/<id>/`. The record includes the normalized request, its frozen concrete prepared configuration for deterministic retries, and status. A shared storage owner marker uses the process ID, process start, session ID, and token; every running record carries the same manager identity so a live manager does not reconcile another manager's active run.
|
|
86
86
|
|
|
87
87
|
Inspection is repeatable. Use the list form for ordered summaries and the ID form for one detailed status plus its terminal value or failure information. Progress is retained in memory and is persisted when the executor marks a progress update for persistence. The result and failure files remain available after manager restart.
|
|
88
88
|
|
|
@@ -102,13 +102,7 @@ Set `worktree` on `subagents_run` to create a named isolated Git worktree for th
|
|
|
102
102
|
|
|
103
103
|
## Agent options, roles, and settings
|
|
104
104
|
|
|
105
|
-
`subagents_run` accepts the same execution options as
|
|
106
|
-
|
|
107
|
-
- global roles: `<agentDir>/pi-extensible-workflows/roles/<name>.md`, normally `~/.pi/agent/pi-extensible-workflows/roles/`;
|
|
108
|
-
- trusted project roles: `<cwd>/.pi/pi-extensible-workflows/roles/<name>.md`;
|
|
109
|
-
- global and trusted project settings: the normal workflow settings path under the agent directory and `<cwd>/.pi/pi-extensible-workflows/settings.json`.
|
|
110
|
-
|
|
111
|
-
The current Pi model, thinking level, active tools, project trust, session ID, role definitions, aliases, and resource policy are captured from the extension context for each run. Internal workflow and subagent control tools are not exposed to the child agent.
|
|
105
|
+
`subagents_run` accepts the same execution options as workflow agents, plus JSON extension options. Enable the optional `@piewf/pi-ext-roles` extension in the parent Pi session when role behavior is required; core does not discover role files or read shared role settings. [Generic preparation hooks](https://vekexasia.github.io/pi-extensible-workflows/extensions.html#preparation) receive authorized inventories, effective cwd/agentDir/trust, root defaults, immutable original options, and cancellation. Prepared configurations are persisted before execution and reused on retry without resolving external policies again. Lost capabilities or trust fail closed. Internal workflow and subagent control tools remain excluded from the child agent.
|
|
112
106
|
|
|
113
107
|
## Programmatic host integration
|
|
114
108
|
|
|
@@ -142,4 +136,4 @@ The v5.4 release removes the seven-tool surface's redundant `subagents_list`, `s
|
|
|
142
136
|
| `subagents_stop({ id })` | unchanged |
|
|
143
137
|
| `subagents_retry({ id })` | unchanged; the original launch mode is preserved |
|
|
144
138
|
|
|
145
|
-
The standalone manager deliberately keeps durable run IDs, repeatable inspection, bounded steering, named worktree cleanup, and workflow
|
|
139
|
+
The standalone manager deliberately keeps durable run IDs, repeatable inspection, bounded steering, named worktree cleanup, and generic workflow preparation and settings reuse.
|
|
@@ -29,18 +29,19 @@ export const SUBAGENTS_RUN_PARAMETERS = Type.Object({
|
|
|
29
29
|
label: Type.Optional(Type.String({ description: "Optional display label for the subagent" })),
|
|
30
30
|
model: Type.Optional(Type.String({ description: "Optional model as provider/model:thinking or alias[:thinking]" })),
|
|
31
31
|
tools: Type.Optional(Type.Array(Type.String(), { description: "Optional ordered tool selectors; candidates start enabled and !* restricts the set" })),
|
|
32
|
+
excludeTools: Type.Optional(Type.Array(Type.String(), { description: "Optional exact tool names removed after preparation; no patterns, cannot remove workflow_result" })),
|
|
32
33
|
skills: Type.Optional(Type.Array(Type.String(), { description: "Optional ordered skill selectors; candidates start enabled and !* restricts the set" })),
|
|
33
34
|
extensions: Type.Optional(Type.Array(Type.String(), { description: "Optional ordered extension selectors; candidates start enabled and !* restricts the set" })),
|
|
34
35
|
contextFiles: Type.Optional(Type.Array(Type.String(), { description: "Optional context-file scopes: global, project, cwd" })),
|
|
35
|
-
role: Type.Optional(Type.String({ description: "Workflow role name" })),
|
|
36
36
|
worktree: Type.Optional(Type.String({ description: "Optional named worktree; requires a clean working tree" })),
|
|
37
37
|
outputSchema: Type.Optional(Type.Record(Type.String(), Type.Unknown(), { description: "Optional JSON schema for the result" })),
|
|
38
38
|
retries: Type.Optional(Type.Integer({ minimum: 0, maximum: SUBAGENT_MAX_RETRIES, description: "Optional retry count; at most 255 retries" })),
|
|
39
39
|
timeoutMs: Type.Optional(Type.Union([Type.Integer({ minimum: 1, description: "Optional execution timeout in milliseconds" }), Type.Null()])),
|
|
40
|
-
}, { additionalProperties:
|
|
40
|
+
}, { additionalProperties: true });
|
|
41
41
|
|
|
42
42
|
export const SUBAGENTS_INSPECT_PARAMETERS = Type.Object({
|
|
43
43
|
id: Type.Optional(Type.String({ description: "Subagent ID; omit to list ordered run summaries" })),
|
|
44
|
+
scope: Type.Optional(Type.Union([Type.Literal("session"), Type.Literal("all")], { description: "List scope when id is omitted: session (default) lists runs from the current Pi session; all lists every stored run" })),
|
|
44
45
|
}, { additionalProperties: false });
|
|
45
46
|
|
|
46
47
|
export const SUBAGENTS_ID_PARAMETERS = Type.Object({
|
|
@@ -60,15 +61,25 @@ export const SUBAGENTS_RETRY_PARAMETERS = Type.Object({
|
|
|
60
61
|
id: Type.String({ description: "Failed or stopped subagent ID to retry" }),
|
|
61
62
|
}, { additionalProperties: false });
|
|
62
63
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
64
|
+
// Structured results codemode scripts receive instead of the JSON text; nested records stay loose so they cannot drift from persistence.
|
|
65
|
+
export const SUBAGENT_STATUS_OUTPUT = Type.Object({
|
|
66
|
+
id: Type.String(),
|
|
67
|
+
state: Type.String(),
|
|
68
|
+
sessionId: Type.Optional(Type.String()),
|
|
69
|
+
startedAt: Type.Optional(Type.Number()),
|
|
70
|
+
finishedAt: Type.Optional(Type.Number()),
|
|
71
|
+
attempts: Type.Optional(Type.Number()),
|
|
72
|
+
attemptDetails: Type.Optional(Type.Array(Type.Unknown())),
|
|
73
|
+
worktree: Type.Optional(Type.Object({ path: Type.String(), branch: Type.String() })),
|
|
74
|
+
error: Type.Optional(Type.Object({ code: Type.String(), message: Type.String() })),
|
|
75
|
+
progress: Type.Optional(Type.Unknown()),
|
|
76
|
+
value: Type.Optional(Type.Unknown()),
|
|
77
|
+
});
|
|
78
|
+
// One run when id is given, otherwise the ordered run summaries; the summaries stay short in the codemode description.
|
|
79
|
+
export const SUBAGENTS_INSPECT_OUTPUT = Type.Union([SUBAGENT_STATUS_OUTPUT, Type.Array(Type.Object({ id: Type.String(), state: Type.String() }))]);
|
|
80
|
+
export const SUBAGENTS_STEER_OUTPUT = Type.Object({ id: Type.String(), accepted: Type.Boolean() });
|
|
70
81
|
|
|
71
|
-
export type SubagentRunRequest = Static<typeof SUBAGENTS_RUN_PARAMETERS
|
|
82
|
+
export type SubagentRunRequest = Static<typeof SUBAGENTS_RUN_PARAMETERS> & { readonly [key: string]: unknown };
|
|
72
83
|
export function normalizeSubagentRunRequest(value: unknown): SubagentRunRequest {
|
|
73
84
|
if (!Value.Check(SUBAGENTS_RUN_PARAMETERS, value)) throw new WorkflowError("INVALID_METADATA", "Invalid subagents_run parameters");
|
|
74
85
|
if (typeof value.worktree === "string" && !value.worktree.trim()) throw new WorkflowError("INVALID_METADATA", "worktree name must be a non-empty string");
|
|
@@ -103,7 +114,6 @@ export interface SubagentStatus {
|
|
|
103
114
|
export interface SubagentNotification {
|
|
104
115
|
readonly id: string;
|
|
105
116
|
readonly label?: string;
|
|
106
|
-
readonly role?: string;
|
|
107
117
|
readonly state: "completed" | "failed";
|
|
108
118
|
readonly error?: { readonly code: string; readonly message: string };
|
|
109
119
|
}
|