@marcoscale98/pi-extensible-workflows 5.19.1-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 +77 -0
- package/README.md +2 -2
- package/dist/src/agent-execution.d.ts +43 -10
- package/dist/src/agent-execution.js +365 -127
- package/dist/src/background-widget.d.ts +0 -1
- package/dist/src/background-widget.js +4 -9
- package/dist/src/decoders.d.ts +2 -3
- package/dist/src/decoders.js +33 -37
- package/dist/src/execution.d.ts +2 -0
- package/dist/src/execution.js +5 -5
- package/dist/src/host-delivery.js +4 -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 +6 -6
- package/dist/src/host-recovery.js +14 -22
- package/dist/src/host-runtime.js +56 -5
- package/dist/src/host-view.d.ts +1 -1
- package/dist/src/host-view.js +17 -15
- package/dist/src/host.js +62 -87
- package/dist/src/index.d.ts +1 -2
- package/dist/src/index.js +1523 -1630
- package/dist/src/index.js.map +3 -3
- package/dist/src/paths.d.ts +7 -6
- package/dist/src/paths.js +24 -15
- 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 +28 -70
- package/dist/src/runtime/agent-runner.d.ts +2 -0
- package/dist/src/session-lease.d.ts +1 -1
- package/dist/src/session-lease.js +45 -11
- package/dist/src/settings.d.ts +6 -3
- package/dist/src/settings.js +36 -47
- package/dist/src/store.d.ts +3 -1
- package/dist/src/store.js +17 -1
- package/dist/src/trajectory.d.ts +0 -1
- package/dist/src/trajectory.js +2 -3
- package/dist/src/types.d.ts +100 -44
- package/dist/src/types.js +4 -7
- package/dist/src/utils.d.ts +3 -2
- package/dist/src/utils.js +20 -10
- package/dist/src/validation.d.ts +8 -2
- package/dist/src/validation.js +122 -86
- package/dist/starter/index.js +8 -9
- package/dist/starter/index.js.map +1 -1
- package/dist/starter/review-loop.js +8 -7
- package/dist/subagents/index.js +654 -814
- package/dist/subagents/index.js.map +3 -3
- package/dist/subagents/src/contracts.d.ts +4 -3
- package/dist/subagents/src/contracts.js +2 -2
- package/dist/subagents/src/index.d.ts +1 -1
- package/dist/subagents/src/index.js +1 -2
- package/dist/subagents/src/manager.js +49 -70
- package/dist/subagents/src/navigator.js +3 -14
- package/dist/subagents/src/view.d.ts +0 -1
- package/dist/subagents/src/view.js +2 -16
- package/dist/subagents/src/worktree.js +1 -1
- package/dist/trajectory/assets/index.html +8 -8
- package/dist/trajectory/index.js +487 -891
- package/dist/trajectory/index.js.map +3 -3
- package/dist/trajectory/src/assets/index.html +8 -8
- package/dist/trajectory/src/index.js +126 -86
- package/dist/trajectory/src/server.js +264 -25
- 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 +5 -5
- package/skills/pi-extensible-workflows/SKILL.md +10 -8
- package/src/agent-execution.ts +339 -127
- package/src/background-widget.ts +4 -10
- package/src/decoders.ts +31 -37
- package/src/execution.ts +5 -5
- package/src/host-delivery.ts +4 -8
- package/src/host-navigator.ts +4 -2
- package/src/host-phases.ts +1 -1
- package/src/host-recovery.ts +20 -26
- package/src/host-runtime.ts +44 -6
- package/src/host-view.ts +18 -11
- package/src/host.ts +62 -113
- package/src/index.ts +1 -2
- package/src/paths.ts +18 -12
- package/src/persistence.ts +1 -0
- package/src/pi-runtime-runner.ts +3 -1
- package/src/registry.ts +27 -63
- package/src/runtime/agent-runner.ts +2 -0
- package/src/session-lease.ts +34 -6
- package/src/settings.ts +35 -47
- package/src/store.ts +18 -2
- package/src/trajectory.ts +2 -4
- package/src/types.ts +71 -30
- package/src/utils.ts +18 -9
- package/src/validation.ts +102 -79
- package/starter/index.ts +0 -1
- package/starter/review-loop.ts +8 -7
- 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 +8 -14
- package/subagents/src/contracts.ts +3 -4
- package/subagents/src/index.ts +2 -3
- package/subagents/src/manager.ts +45 -125
- package/subagents/src/navigator.ts +4 -13
- package/subagents/src/view.ts +4 -14
- package/subagents/src/worktree.ts +1 -1
- package/trajectory/src/assets/index.html +8 -8
- package/trajectory/src/index.ts +106 -82
- package/trajectory/src/server.ts +44 -8
- package/trajectory/src/startup-mutex.ts +127 -0
- package/dist/src/roles.d.ts +0 -70
- package/dist/src/roles.js +0 -326
- 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 -327
package/dist/src/utils.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { ERROR_CODES, LAUNCH_SNAPSHOT_IDENTITY_VERSION,
|
|
2
|
+
import { ERROR_CODES, LAUNCH_SNAPSHOT_IDENTITY_VERSION, WorkflowError } from "./types.js";
|
|
3
3
|
import { Minimatch } from "minimatch";
|
|
4
|
+
import { THINKING_LEVELS } from "./types.js";
|
|
4
5
|
export class SerialLane {
|
|
5
6
|
#tail = Promise.resolve();
|
|
6
7
|
run(task) {
|
|
@@ -103,13 +104,22 @@ export const WORKFLOW_EXTENSION_NAMESPACE = /^[A-Za-z][A-Za-z0-9_-]*$/;
|
|
|
103
104
|
export function validWorkflowExtensionNamespace(value) { return WORKFLOW_EXTENSION_NAMESPACE.test(value) && value !== "__proto__" && value !== "constructor" && value !== "prototype"; }
|
|
104
105
|
export function parseThinking(value) { return isThinkingLevel(value) ? value : undefined; }
|
|
105
106
|
export function parseModelReference(value) {
|
|
106
|
-
const match = /^([^/:\s]+)\/([
|
|
107
|
+
const match = /^([^/:\s]+)\/([^\s]+)$/.exec(value);
|
|
107
108
|
if (!match?.[1] || !match[2])
|
|
108
109
|
fail("UNKNOWN_MODEL", `Invalid model spec: ${value}`);
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
110
|
+
// The model id is everything after the first slash, so namespaced ids such as
|
|
111
|
+
// `deepseek/deepseek-v4.1-flash:nitro` survive intact. Only a trailing segment
|
|
112
|
+
// that names a thinking level is one.
|
|
113
|
+
const reference = match[2];
|
|
114
|
+
const separator = reference.lastIndexOf(":");
|
|
115
|
+
const suffix = separator === -1 ? undefined : reference.slice(separator + 1);
|
|
116
|
+
if (suffix === "")
|
|
117
|
+
fail("UNKNOWN_MODEL", `Invalid model spec: ${value}`);
|
|
118
|
+
const thinking = suffix !== undefined && isThinkingLevel(suffix) ? suffix : undefined;
|
|
119
|
+
const model = thinking === undefined ? reference : reference.slice(0, separator);
|
|
120
|
+
if (model === "")
|
|
121
|
+
fail("UNKNOWN_MODEL", `Invalid model spec: ${value}`);
|
|
122
|
+
return { provider: match[1], model, ...(thinking === undefined ? {} : { thinking }) };
|
|
113
123
|
}
|
|
114
124
|
export function assertModelThinking(value, path = "model") {
|
|
115
125
|
if (value.includes("/") && parseModelReference(value).thinking === undefined)
|
|
@@ -190,6 +200,10 @@ export function resolveModelReference(value, aliases = {}, knownModels, settings
|
|
|
190
200
|
};
|
|
191
201
|
return resolveReference(value, []);
|
|
192
202
|
}
|
|
203
|
+
export function modelCapability(value, aliases, knownModels, settingsPath) {
|
|
204
|
+
const parsed = typeof value === "string" ? resolveModelReference(value, aliases, knownModels, settingsPath) : value;
|
|
205
|
+
return `${parsed.provider}/${parsed.model}`;
|
|
206
|
+
}
|
|
193
207
|
/**
|
|
194
208
|
* Tools a session can reach, and so the ceiling for its agents: the declared tools plus the ones
|
|
195
209
|
* only codemode scripts call or tool_search loads, such as MCP tools with the default exposure.
|
|
@@ -208,10 +222,6 @@ export function physicalModel(spec, aliases = {}, knownModels, settingsPath) {
|
|
|
208
222
|
const target = resolveModelReference(spec.model, aliases, knownModels, settingsPath);
|
|
209
223
|
return spec.thinking === undefined ? target : { ...target, thinking: spec.thinking };
|
|
210
224
|
}
|
|
211
|
-
export function modelCapability(value, aliases, knownModels, settingsPath) {
|
|
212
|
-
const parsed = typeof value === "string" ? resolveModelReference(value, aliases, knownModels, settingsPath) : value;
|
|
213
|
-
return `${parsed.provider}/${parsed.model}`;
|
|
214
|
-
}
|
|
215
225
|
export function aliasDrift(previous, current) {
|
|
216
226
|
return [...new Set([...Object.keys(previous), ...Object.keys(current)])].sort().flatMap((name) => previous[name] === current[name] ? [] : [`${name}: ${previous[name] ?? "(missing)"} -> ${current[name] ?? "(missing)"}`]);
|
|
217
227
|
}
|
package/dist/src/validation.d.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import type { CheckpointInput, JsonValue, PreflightCapabilities, PreflightResult, ShellOptions, StaticWorkflowCall, ValidatedWorkflowLaunch, WorkflowMetadata, WorkflowValidationContext, WorkflowValidationParameters } from "./types.js";
|
|
2
2
|
import type { WorkflowRegistryApi } from "./registry.js";
|
|
3
3
|
export { validateSchema } from "./utils.js";
|
|
4
|
-
export { loadAgentDefinitions, loadProjectAgentDefinitions, parseRoleMarkdown, workflowRoleDirectories } from "./roles.js";
|
|
5
|
-
export type { WorkflowRoleDirectoryInput } from "./roles.js";
|
|
6
4
|
export { DEFAULT_SETTINGS, loadCodemodeToolsSetting, loadSettings, loadSettingsOverrides, resolveAgentResourcePolicy, resolveWorkflowSettings, saveModelAliases, validateContextFileScopes, validateModelAliasAvailability, validateSelectorList, validateWorkflowExtensionSettings, workflowProjectSettingsPath, workflowSettingsPath, workflowToolExposure } from "./settings.js";
|
|
7
5
|
export declare function validateCheckpoint(value: unknown): CheckpointInput;
|
|
8
6
|
/**
|
|
@@ -12,10 +10,18 @@ export declare function validateCheckpoint(value: unknown): CheckpointInput;
|
|
|
12
10
|
export declare function scriptToolReferences(script: string): string[] | undefined;
|
|
13
11
|
export declare function instrumentWorkflow(script: string): string;
|
|
14
12
|
export declare function workflowPrompt(template: string, values: Readonly<Record<string, JsonValue>>): string;
|
|
13
|
+
export declare function isCoreAgentOption(key: string): boolean;
|
|
15
14
|
export declare function validateAgentOptions(value: unknown): Readonly<Record<string, JsonValue>>;
|
|
16
15
|
export declare function validateShellOptions(value: unknown): ShellOptions;
|
|
17
16
|
export declare function validateShellCommand(value: unknown): string;
|
|
18
17
|
export declare function inspectWorkflowScript(script: string): StaticWorkflowCall[];
|
|
18
|
+
/**
|
|
19
|
+
* Distinct, completely static option objects of agent(...) and agent.create(...) calls, so launch can inspect them
|
|
20
|
+
* before any effect. Calls with any dynamic option stay runtime-checked, and so do handles whose sends may add options.
|
|
21
|
+
*/
|
|
22
|
+
export declare function staticAgentPreparationOptions(script: string, registered?: Readonly<Record<string, {
|
|
23
|
+
run: (...args: never[]) => unknown;
|
|
24
|
+
}>>): Array<Readonly<Record<string, JsonValue>>>;
|
|
19
25
|
export declare function preflight(script: string, capabilities: PreflightCapabilities, schemas?: readonly unknown[], metadata?: WorkflowMetadata, compatibility?: boolean): PreflightResult;
|
|
20
26
|
export declare function validateWorkflowLaunch(params: WorkflowValidationParameters, context: WorkflowValidationContext, registry?: WorkflowRegistryApi): ValidatedWorkflowLaunch;
|
|
21
27
|
export declare function validateWorkflowLaunchWithRegistry(params: WorkflowValidationParameters, context: WorkflowValidationContext, registry?: WorkflowRegistryApi): ValidatedWorkflowLaunch;
|
package/dist/src/validation.js
CHANGED
|
@@ -2,11 +2,9 @@ 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 { assertModelThinking, validateSchema, deepFreeze, errorText, fail, jsonObject, jsonValue,
|
|
5
|
+
import { assertModelThinking, validateSchema, deepFreeze, errorText, fail, jsonObject, jsonValue, object, positiveInteger, resourcePatternHasMagic } from "./utils.js";
|
|
6
6
|
import { WORKFLOW_CALL_KINDS } from "./types.js";
|
|
7
7
|
export { validateSchema } from "./utils.js";
|
|
8
|
-
import { loadAgentDefinitions, loadProjectAgentDefinitions } from "./roles.js";
|
|
9
|
-
export { loadAgentDefinitions, loadProjectAgentDefinitions, parseRoleMarkdown, workflowRoleDirectories } from "./roles.js";
|
|
10
8
|
import { validateContextFileScopes, validateSelectorList } from "./settings.js";
|
|
11
9
|
export { DEFAULT_SETTINGS, loadCodemodeToolsSetting, loadSettings, loadSettingsOverrides, resolveAgentResourcePolicy, resolveWorkflowSettings, saveModelAliases, validateContextFileScopes, validateModelAliasAvailability, validateSelectorList, validateWorkflowExtensionSettings, workflowProjectSettingsPath, workflowSettingsPath, workflowToolExposure } from "./settings.js";
|
|
12
10
|
export function validateCheckpoint(value) {
|
|
@@ -18,23 +16,6 @@ export function validateCheckpoint(value) {
|
|
|
18
16
|
fail("INVALID_METADATA", "checkpoint context exceeds 4096 UTF-8 bytes");
|
|
19
17
|
return { name: value.name, prompt: value.prompt, context: value.context };
|
|
20
18
|
}
|
|
21
|
-
function validateRolePolicies(definitions, roles, availableModels, aliases = {}, knownModels = availableModels, settingsPath) {
|
|
22
|
-
for (const role of roles) {
|
|
23
|
-
const definition = definitions[role];
|
|
24
|
-
if (!definition)
|
|
25
|
-
continue;
|
|
26
|
-
if (definition.model !== undefined) {
|
|
27
|
-
const resolved = modelCapability(definition.model, aliases, knownModels, settingsPath);
|
|
28
|
-
if (!availableModels.has(resolved)) {
|
|
29
|
-
if (modelAliasName(definition.model, aliases))
|
|
30
|
-
unknownModel(definition.model, resolved, settingsPath);
|
|
31
|
-
fail("UNKNOWN_MODEL", `Unknown model for role ${role}: ${resolved}`);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
// Role tools absent from the session are tolerated at launch: the runtime resolve emits a
|
|
35
|
-
// warning and the agent runs without them. Doctor still reports unmatched patterns.
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
19
|
function validateWorkflowMetadata(value) {
|
|
39
20
|
if (!object(value) || typeof value.name !== "string" || value.name.trim() === "")
|
|
40
21
|
fail("INVALID_METADATA", "Workflow metadata requires a non-empty name");
|
|
@@ -348,8 +329,9 @@ export function workflowPrompt(template, values) {
|
|
|
348
329
|
return typeof value === "string" ? value : JSON.stringify(value, null, 2);
|
|
349
330
|
});
|
|
350
331
|
}
|
|
351
|
-
const AGENT_OPTION_KEYS = new Set(["label", "model", "tools", "
|
|
352
|
-
function
|
|
332
|
+
const AGENT_OPTION_KEYS = new Set(["label", "model", "tools", "excludeTools", "skills", "extensions", "contextFiles", "outputSchema", "retries", "timeoutMs"]);
|
|
333
|
+
export function isCoreAgentOption(key) { return AGENT_OPTION_KEYS.has(key); }
|
|
334
|
+
function validateAgentOption(key, value) {
|
|
353
335
|
switch (key) {
|
|
354
336
|
case "label":
|
|
355
337
|
if (typeof value !== "string" || !value.trim())
|
|
@@ -359,21 +341,21 @@ function validateAgentOption(key, value, aliases, knownModels, settingsPath) {
|
|
|
359
341
|
if (typeof value !== "string" || !value.trim())
|
|
360
342
|
fail("INVALID_METADATA", "agent model must be a non-empty string");
|
|
361
343
|
assertModelThinking(value, "agent model");
|
|
362
|
-
if (aliases !== undefined)
|
|
363
|
-
resolveModelReference(value, aliases, knownModels, settingsPath);
|
|
364
344
|
break;
|
|
365
345
|
case "tools":
|
|
366
346
|
case "skills":
|
|
367
347
|
case "extensions":
|
|
368
348
|
validateSelectorList(value, "agent options", key, "INVALID_METADATA", key !== "extensions");
|
|
369
349
|
break;
|
|
350
|
+
case "excludeTools":
|
|
351
|
+
if (!Array.isArray(value) || value.some((tool) => typeof tool !== "string" || !tool.trim() || tool !== tool.trim() || tool.startsWith("!") || resourcePatternHasMagic(tool)))
|
|
352
|
+
fail("INVALID_METADATA", "agent excludeTools must be an array of exact tool names");
|
|
353
|
+
if (value.includes("workflow_result"))
|
|
354
|
+
fail("INVALID_METADATA", "agent excludeTools cannot remove workflow_result");
|
|
355
|
+
break;
|
|
370
356
|
case "contextFiles":
|
|
371
357
|
validateContextFileScopes(value, "agent options");
|
|
372
358
|
break;
|
|
373
|
-
case "role":
|
|
374
|
-
if (typeof value !== "string" || !value.trim())
|
|
375
|
-
fail("INVALID_METADATA", "agent role must be a non-empty string");
|
|
376
|
-
break;
|
|
377
359
|
case "outputSchema":
|
|
378
360
|
validateSchema(value, "agent outputSchema");
|
|
379
361
|
break;
|
|
@@ -483,43 +465,123 @@ export function inspectWorkflowScript(script) {
|
|
|
483
465
|
knownOptionEntries.push([key, value.value]);
|
|
484
466
|
}
|
|
485
467
|
const knownOptions = Object.fromEntries(knownOptionEntries);
|
|
486
|
-
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"))
|
|
468
|
+
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")) };
|
|
487
469
|
return { ...base, ...(retries.known && typeof retries.value === "number" ? { retries: retries.value } : {}), ...(staticOutputSchema === undefined ? {} : { outputSchema: staticOutputSchema }), ...(optionKeys.length ? { options: knownOptions, optionKeys } : {}) };
|
|
488
470
|
}
|
|
489
471
|
if (kind === "checkpoint")
|
|
490
|
-
return { ...placement, kind, start: call.start, end: call.end, name: staticString(propertyNode(first, "name")), prompt: staticString(propertyNode(first, "prompt")), model: null
|
|
472
|
+
return { ...placement, kind, start: call.start, end: call.end, name: staticString(propertyNode(first, "name")), prompt: staticString(propertyNode(first, "prompt")), model: null };
|
|
491
473
|
if (kind === "shell")
|
|
492
|
-
return { ...placement, kind, start: call.start, end: call.end, name: staticString(first), prompt: null, model: null
|
|
493
|
-
return { ...placement, kind, start: call.start, end: call.end, name: staticString(first), prompt: null, model: null
|
|
474
|
+
return { ...placement, kind, start: call.start, end: call.end, name: staticString(first), prompt: null, model: null };
|
|
475
|
+
return { ...placement, kind, start: call.start, end: call.end, name: staticString(first), prompt: null, model: null };
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
/** Every option is a preparation input, because any hook may read it; one dynamic value leaves the whole call to runtime. */
|
|
479
|
+
function staticPreparationOptions(node, omitted) {
|
|
480
|
+
if (node?.type !== "ObjectExpression")
|
|
481
|
+
return undefined;
|
|
482
|
+
const options = new Map();
|
|
483
|
+
for (const property of node.properties) {
|
|
484
|
+
if (property.type === "SpreadElement" || property.computed)
|
|
485
|
+
return undefined;
|
|
486
|
+
const key = propertyKeyName(property);
|
|
487
|
+
if (key === undefined)
|
|
488
|
+
return undefined;
|
|
489
|
+
if (key === omitted)
|
|
490
|
+
continue;
|
|
491
|
+
const value = staticValue(property.value);
|
|
492
|
+
// NOTE: an output schema only shapes the result, so a computed one (a const declared elsewhere) does not hide the call.
|
|
493
|
+
if (key === "outputSchema" && !value.known)
|
|
494
|
+
continue;
|
|
495
|
+
if (!value.known || !jsonValue(value.value))
|
|
496
|
+
return undefined;
|
|
497
|
+
options.set(key, value.value);
|
|
498
|
+
}
|
|
499
|
+
return Object.fromEntries(options);
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* A handle prepares its first turn with the create options merged with that send's options, so create options are complete
|
|
503
|
+
* only when every use of the handle provably passes no send options: `agent.create(...).send(prompt)` directly, or a
|
|
504
|
+
* `const` handle whose every reference is `handle.send(prompt)`. Computed access, destructuring, aliases or any other
|
|
505
|
+
* use leave the handle to runtime.
|
|
506
|
+
*/
|
|
507
|
+
function handlesWithCompleteOptions(program) {
|
|
508
|
+
const parents = new Map();
|
|
509
|
+
const references = new Map();
|
|
510
|
+
const visit = (node) => {
|
|
511
|
+
if (node.type === "Identifier")
|
|
512
|
+
references.set(node.name, [...(references.get(node.name) ?? []), node]);
|
|
513
|
+
for (const child of astChildren(node)) {
|
|
514
|
+
parents.set(child, node);
|
|
515
|
+
visit(child);
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
visit(program);
|
|
519
|
+
const plainSend = (member) => {
|
|
520
|
+
const call = member ? parents.get(member) : undefined;
|
|
521
|
+
return member?.type === "MemberExpression" && !member.computed && member.property.type === "Identifier" && member.property.name === "send"
|
|
522
|
+
&& call?.type === "CallExpression" && call.callee === member && call.arguments.length <= 1 && call.arguments.every((argument) => argument.type !== "SpreadElement");
|
|
523
|
+
};
|
|
524
|
+
return agentCreateCalls(program).filter((create) => {
|
|
525
|
+
const parent = parents.get(create);
|
|
526
|
+
if (parent?.type === "MemberExpression" && parent.object === create)
|
|
527
|
+
return plainSend(parent);
|
|
528
|
+
const declaration = parent ? parents.get(parent) : undefined;
|
|
529
|
+
if (parent?.type !== "VariableDeclarator" || parent.init !== create || parent.id.type !== "Identifier" || declaration?.type !== "VariableDeclaration" || declaration.kind !== "const")
|
|
530
|
+
return false;
|
|
531
|
+
const name = parent.id.name;
|
|
532
|
+
return (references.get(name) ?? []).every((reference) => reference === parent.id || (parents.get(reference)?.type === "MemberExpression" && parents.get(reference).object === reference && plainSend(parents.get(reference))));
|
|
494
533
|
});
|
|
495
534
|
}
|
|
496
|
-
|
|
535
|
+
/**
|
|
536
|
+
* Distinct, completely static option objects of agent(...) and agent.create(...) calls, so launch can inspect them
|
|
537
|
+
* before any effect. Calls with any dynamic option stay runtime-checked, and so do handles whose sends may add options.
|
|
538
|
+
*/
|
|
539
|
+
export function staticAgentPreparationOptions(script, registered = {}) {
|
|
540
|
+
const program = parseWorkflow(script);
|
|
541
|
+
const sources = [program, ...registeredFunctionPrograms(program, registered)];
|
|
542
|
+
const distinct = new Map();
|
|
543
|
+
for (const source of sources)
|
|
544
|
+
for (const options of programAgentOptions(source))
|
|
545
|
+
distinct.set(JSON.stringify(options), deepFreeze(options));
|
|
546
|
+
return [...distinct.values()];
|
|
547
|
+
}
|
|
548
|
+
/** Programs of the registered functions the script calls by name, so their agents are inspected before launch like inline ones. */
|
|
549
|
+
function registeredFunctionPrograms(program, registered) {
|
|
550
|
+
const called = new Set();
|
|
551
|
+
const visit = (node) => {
|
|
552
|
+
if (node.type === "CallExpression" && node.callee.type === "Identifier" && Object.prototype.hasOwnProperty.call(registered, node.callee.name))
|
|
553
|
+
called.add(node.callee.name);
|
|
554
|
+
for (const child of astChildren(node))
|
|
555
|
+
visit(child);
|
|
556
|
+
};
|
|
557
|
+
visit(program);
|
|
558
|
+
const programs = [];
|
|
559
|
+
for (const name of called) {
|
|
560
|
+
const source = registered[name]?.run.toString() ?? "";
|
|
561
|
+
// A method prints as `async run(...) {}`, which only parses inside an object literal.
|
|
562
|
+
for (const wrapped of [`(${source})`, `({${source}})`]) {
|
|
563
|
+
try {
|
|
564
|
+
programs.push(acorn.parse(wrapped, { ecmaVersion: "latest", sourceType: "module" }));
|
|
565
|
+
break;
|
|
566
|
+
}
|
|
567
|
+
catch { /* try the next form; an unparsable function stays runtime-checked */ }
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
return programs;
|
|
571
|
+
}
|
|
572
|
+
function programAgentOptions(program) {
|
|
573
|
+
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)));
|
|
574
|
+
const handles = handlesWithCompleteOptions(program).map((call) => staticPreparationOptions(callArgument(call, 0), "name"));
|
|
575
|
+
return [...calls, ...handles].filter((options) => options !== undefined);
|
|
576
|
+
}
|
|
577
|
+
function validateStaticAgentOptions(node) {
|
|
497
578
|
if (node?.type !== "ObjectExpression")
|
|
498
579
|
return;
|
|
499
580
|
for (const key of AGENT_OPTION_KEYS) {
|
|
500
581
|
const value = staticValue(propertyNode(node, key));
|
|
501
582
|
if (value.known)
|
|
502
|
-
validateAgentOption(key, value.value
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
function hasDynamicAgentRole(node) {
|
|
506
|
-
if (!node)
|
|
507
|
-
return false;
|
|
508
|
-
if (node.type !== "ObjectExpression")
|
|
509
|
-
return true;
|
|
510
|
-
for (let index = node.properties.length - 1; index >= 0; index -= 1) {
|
|
511
|
-
const property = node.properties[index];
|
|
512
|
-
if (!property || property.type === "SpreadElement" || property.computed)
|
|
513
|
-
return true;
|
|
514
|
-
const key = propertyKeyName(property);
|
|
515
|
-
if (key === "role") {
|
|
516
|
-
const roleValue = staticValue(property.value);
|
|
517
|
-
if (roleValue.known && typeof roleValue.value === "string")
|
|
518
|
-
return false;
|
|
519
|
-
return true;
|
|
520
|
-
}
|
|
583
|
+
validateAgentOption(key, value.value);
|
|
521
584
|
}
|
|
522
|
-
return false;
|
|
523
585
|
}
|
|
524
586
|
function validateStaticShellOptions(call) {
|
|
525
587
|
if (call.arguments.some((argument) => argument.type === "SpreadElement"))
|
|
@@ -564,7 +626,7 @@ export function preflight(script, capabilities, schemas = [], metadata = { name:
|
|
|
564
626
|
for (const call of calls) {
|
|
565
627
|
const operation = call.callee.name;
|
|
566
628
|
if (operation === "agent")
|
|
567
|
-
validateStaticAgentOptions(call.arguments[1]
|
|
629
|
+
validateStaticAgentOptions(call.arguments[1]);
|
|
568
630
|
if (operation === "withWorktree")
|
|
569
631
|
validateStaticWithWorktree(call, compatibility);
|
|
570
632
|
if (operation === "shell")
|
|
@@ -582,11 +644,10 @@ export function preflight(script, capabilities, schemas = [], metadata = { name:
|
|
|
582
644
|
const options = call.arguments.length === 1 ? callArgument(call, 0) : undefined;
|
|
583
645
|
if (options?.type !== "ObjectExpression" || !literalString(propertyNode(options, "name"))?.trim())
|
|
584
646
|
fail("INVALID_METADATA", "agent.create requires an options object with a stable explicit name");
|
|
585
|
-
validateStaticAgentOptions(options
|
|
647
|
+
validateStaticAgentOptions(options);
|
|
586
648
|
return options;
|
|
587
649
|
});
|
|
588
650
|
const agentOptions = [...calls.filter((call) => call.callee.name === "agent").map((call) => callArgument(call, 1)), ...handleOptions];
|
|
589
|
-
const dynamicAgentRoles = agentOptions.some((options) => hasDynamicAgentRole(options));
|
|
590
651
|
const staticSchemas = [];
|
|
591
652
|
for (const options of agentOptions) {
|
|
592
653
|
const value = staticValue(propertyNode(options, "outputSchema"));
|
|
@@ -597,28 +658,17 @@ export function preflight(script, capabilities, schemas = [], metadata = { name:
|
|
|
597
658
|
staticSchemas.push(schema);
|
|
598
659
|
}
|
|
599
660
|
checkedSchemas.push(...staticSchemas);
|
|
600
|
-
const
|
|
601
|
-
const models = modelRefs.map(({ resolved }) => resolved);
|
|
661
|
+
const models = [];
|
|
602
662
|
const tools = agentOptions.flatMap((options) => {
|
|
603
663
|
const value = propertyNode(options, "tools");
|
|
604
664
|
return value?.type === "ArrayExpression" ? value.elements.flatMap((element) => { const tool = element && element.type !== "SpreadElement" ? literalString(element) : undefined; return tool === undefined ? [] : [tool]; }) : [];
|
|
605
665
|
});
|
|
606
|
-
const agentTypes = agentOptions.flatMap((options) => { const value = staticString(propertyNode(options, "role")); return value === null ? [] : [value]; });
|
|
607
666
|
for (const pattern of tools) {
|
|
608
667
|
const body = pattern.startsWith("!") ? pattern.slice(1) : pattern;
|
|
609
668
|
if (!pattern.startsWith("!") && !resourcePatternHasMagic(pattern) && !capabilities.tools.has(body))
|
|
610
669
|
fail("UNKNOWN_TOOL", `Unknown tool: ${body}`);
|
|
611
670
|
}
|
|
612
|
-
|
|
613
|
-
if (missingModel) {
|
|
614
|
-
if (modelAliasName(missingModel.requested, capabilities.modelAliases ?? {}))
|
|
615
|
-
unknownModel(missingModel.requested, missingModel.resolved, capabilities.settingsPath);
|
|
616
|
-
fail("UNKNOWN_MODEL", `Unknown model: ${missingModel.resolved}`);
|
|
617
|
-
}
|
|
618
|
-
const missingType = agentTypes.find((type) => !capabilities.agentTypes.has(type));
|
|
619
|
-
if (missingType)
|
|
620
|
-
fail("UNKNOWN_AGENT_TYPE", `Unknown agent type: ${missingType}`);
|
|
621
|
-
return Object.freeze({ metadata: deepFreeze(checkedMetadata), referenced: deepFreeze({ phases, models, tools, agentTypes }), schemas: deepFreeze(checkedSchemas), dynamicAgentRoles });
|
|
671
|
+
return Object.freeze({ metadata: deepFreeze(checkedMetadata), referenced: deepFreeze({ phases, models, tools }), schemas: deepFreeze(checkedSchemas) });
|
|
622
672
|
}
|
|
623
673
|
export function validateWorkflowLaunch(params, context, registry) {
|
|
624
674
|
return validateWorkflowLaunchWithRegistry(params, context, registry);
|
|
@@ -650,22 +700,8 @@ export function validateWorkflowLaunchWithRegistry(params, context, registry) {
|
|
|
650
700
|
if (!script)
|
|
651
701
|
fail("INVALID_SYNTAX", "Provide script or scriptPath");
|
|
652
702
|
const metadata = validateWorkflowMetadata({ name: explicitName, ...(typeof params.description === "string" ? { description: params.description } : {}) });
|
|
653
|
-
const
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
const aliases = context.modelAliases ?? {};
|
|
657
|
-
const knownModels = context.knownModels ?? context.availableModels;
|
|
658
|
-
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);
|
|
659
|
-
const roleNames = checked.dynamicAgentRoles ? Object.keys(agentDefinitions) : checked.referenced.agentTypes;
|
|
660
|
-
validateRolePolicies(agentDefinitions, roleNames, context.availableModels, aliases, knownModels, context.settingsPath);
|
|
661
|
-
if (registry?.validateExtensionSettings) {
|
|
662
|
-
const validate = registry.validateExtensionSettings.bind(registry);
|
|
663
|
-
const validatorContext = (source, role) => ({ source, cwd: context.cwd, projectTrusted: context.projectTrusted, ...(context.settingsPath ? { settingsPath: context.settingsPath } : {}), ...(role === undefined ? {} : { role }) });
|
|
664
|
-
validate(context.extensionSettings, validatorContext("effective"));
|
|
665
|
-
for (const role of roleNames) {
|
|
666
|
-
validate(mergeWorkflowExtensionSettings(context.extensionSettings, agentDefinitions[role]?.extensionSettings), validatorContext("role", role));
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
return { script, checked, agentDefinitions, projectAgentDefinitions, roleNames };
|
|
703
|
+
const checked = preflight(script, { models: context.availableModels, tools: context.rootTools, modelAliases: context.modelAliases ?? {}, knownModels: context.knownModels ?? context.availableModels, ...(context.settingsPath ? { settingsPath: context.settingsPath } : {}) }, [], metadata);
|
|
704
|
+
registry?.validateExtensionSettings(context.extensionSettings, { source: "effective", cwd: context.cwd, projectTrusted: context.projectTrusted, ...(context.settingsPath ? { settingsPath: context.settingsPath } : {}) });
|
|
705
|
+
return { script, checked };
|
|
670
706
|
}
|
|
671
707
|
export { createLaunchSnapshot, loadLaunchSnapshot } from "./utils.js";
|
package/dist/starter/index.js
CHANGED
|
@@ -20,23 +20,23 @@ var reviewLoop = defineWorkflowFunction({
|
|
|
20
20
|
);
|
|
21
21
|
let devResult = null;
|
|
22
22
|
let review = { pass: false, findings: [] };
|
|
23
|
+
const developer = agent.create({ name: "developer", label: "Developer", role: "developer" });
|
|
23
24
|
for (let iterations = 1; iterations <= maxIterations; iterations += 1) {
|
|
24
25
|
const devPrompt = iterations === 1 ? prompt("Implement this task:\n\n{task}", { task }) : prompt(
|
|
25
|
-
`Address the
|
|
26
|
-
<original_task>{task}</original_task>
|
|
26
|
+
`Address the latest review findings and complete the original task.
|
|
27
27
|
<last_review>{review}</last_review>`,
|
|
28
|
-
{
|
|
28
|
+
{ review: review.findings }
|
|
29
29
|
);
|
|
30
|
-
devResult = await
|
|
30
|
+
devResult = await developer.send(devPrompt);
|
|
31
31
|
review = await agent(
|
|
32
32
|
prompt(
|
|
33
|
-
`Review the implementation against the task.
|
|
33
|
+
`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.
|
|
34
34
|
<original_task>{task}</original_task>
|
|
35
|
-
<
|
|
35
|
+
<previous_review_findings>{review}</previous_review_findings>
|
|
36
36
|
<dev_summary>{devResult}</dev_summary>`,
|
|
37
37
|
{ task, devResult, review: review.findings }
|
|
38
38
|
),
|
|
39
|
-
{ role: "reviewer", outputSchema: reviewSchema }
|
|
39
|
+
{ label: `Reviewer ${String(iterations)}`, role: "reviewer", outputSchema: reviewSchema }
|
|
40
40
|
);
|
|
41
41
|
if (review.pass) return { pass: true, iterations, devResult, review };
|
|
42
42
|
}
|
|
@@ -89,8 +89,7 @@ var extension = {
|
|
|
89
89
|
"researcher-model": {
|
|
90
90
|
resolve: ({ rootModel }) => `${rootModel.provider}/${rootModel.model}`
|
|
91
91
|
}
|
|
92
|
-
}
|
|
93
|
-
roleDirectories: [new URL("./roles/", import.meta.url)]
|
|
92
|
+
}
|
|
94
93
|
};
|
|
95
94
|
function starter_default() {
|
|
96
95
|
registerWorkflowExtension(extension);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../starter/index.ts", "../../starter/review-loop.ts"],
|
|
4
|
-
"mappings": ";AAAA;AAAA,EACE;AAAA,OAEK;;;ACHP,SAAS,YAAyB;AAClC;AAAA,EACE;AAAA,OAEK;AAEA,IAAM,aAAa,uBAAuB;AAAA,EAC/C,aACE;AAAA,EACF,MAAM,IAAI,EAAE,MAAM,gBAAgB,EAAE,GAAG,EAAE,OAAO,OAAO,GAAG;AACxD,UAAM,eAAe,KAAK;AAAA,MACxB;AAAA,QACE,MAAM,KAAK,QAAQ;AAAA,QACnB,UAAU,KAAK,MAAM,KAAK,OAAO,CAAC;AAAA,MACpC;AAAA,MACA,EAAE,sBAAsB,MAAM;AAAA,IAChC;AACA,QAAI,YAAuB;AAC3B,QAAI,SAAsC,EAAE,MAAM,OAAO,UAAU,CAAC,EAAE;AAEtE,aAAS,aAAa,GAAG,cAAc,eAAe,cAAc,GAAG;AACrE,YAAM,YACJ,eAAe,IACX,OAAO,kCAAkC,EAAE,KAAK,CAAC,IACjD;AAAA,QACE;AAAA;AAAA
|
|
4
|
+
"mappings": "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",
|
|
5
5
|
"names": []
|
|
6
6
|
}
|
|
@@ -9,17 +9,18 @@ export const reviewLoop = defineWorkflowFunction({
|
|
|
9
9
|
}, { additionalProperties: false });
|
|
10
10
|
let devResult = null;
|
|
11
11
|
let review = { pass: false, findings: [] };
|
|
12
|
+
// One developer keeps its transcript across iterations; each reviewer starts fresh.
|
|
13
|
+
const developer = agent.create({ name: "developer", label: "Developer", role: "developer" });
|
|
12
14
|
for (let iterations = 1; iterations <= maxIterations; iterations += 1) {
|
|
13
15
|
const devPrompt = iterations === 1
|
|
14
16
|
? prompt("Implement this task:\n\n{task}", { task })
|
|
15
|
-
: prompt(`Address the
|
|
17
|
+
: prompt(`Address the latest review findings and complete the original task.
|
|
18
|
+
<last_review>{review}</last_review>`, { review: review.findings });
|
|
19
|
+
devResult = await developer.send(devPrompt);
|
|
20
|
+
review = await agent(prompt(`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.
|
|
16
21
|
<original_task>{task}</original_task>
|
|
17
|
-
<
|
|
18
|
-
|
|
19
|
-
review = await agent(prompt(`Review the implementation against the task. Set pass=true only when the task is complete and correct. The developer may have addressed a previous review run of yours. So its summary is related to the last round of review if present.
|
|
20
|
-
<original_task>{task}</original_task>
|
|
21
|
-
<last_review>{review}</last_review>
|
|
22
|
-
<dev_summary>{devResult}</dev_summary>`, { task, devResult, review: review.findings }), { role: "reviewer", outputSchema: reviewSchema });
|
|
22
|
+
<previous_review_findings>{review}</previous_review_findings>
|
|
23
|
+
<dev_summary>{devResult}</dev_summary>`, { task, devResult, review: review.findings }), { label: `Reviewer ${String(iterations)}`, role: "reviewer", outputSchema: reviewSchema });
|
|
23
24
|
if (review.pass)
|
|
24
25
|
return { pass: true, iterations, devResult, review };
|
|
25
26
|
}
|