@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.
Files changed (123) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/README.md +2 -2
  3. package/dist/src/agent-execution.d.ts +43 -10
  4. package/dist/src/agent-execution.js +365 -127
  5. package/dist/src/background-widget.d.ts +0 -1
  6. package/dist/src/background-widget.js +4 -9
  7. package/dist/src/decoders.d.ts +2 -3
  8. package/dist/src/decoders.js +33 -37
  9. package/dist/src/execution.d.ts +2 -0
  10. package/dist/src/execution.js +5 -5
  11. package/dist/src/host-delivery.js +4 -8
  12. package/dist/src/host-navigator.js +5 -2
  13. package/dist/src/host-phases.d.ts +1 -0
  14. package/dist/src/host-recovery.d.ts +6 -6
  15. package/dist/src/host-recovery.js +14 -22
  16. package/dist/src/host-runtime.js +56 -5
  17. package/dist/src/host-view.d.ts +1 -1
  18. package/dist/src/host-view.js +17 -15
  19. package/dist/src/host.js +62 -87
  20. package/dist/src/index.d.ts +1 -2
  21. package/dist/src/index.js +1523 -1630
  22. package/dist/src/index.js.map +3 -3
  23. package/dist/src/paths.d.ts +7 -6
  24. package/dist/src/paths.js +24 -15
  25. package/dist/src/persistence.d.ts +1 -0
  26. package/dist/src/persistence.js +1 -0
  27. package/dist/src/pi-runtime-runner.js +4 -1
  28. package/dist/src/registry.d.ts +9 -7
  29. package/dist/src/registry.js +28 -70
  30. package/dist/src/runtime/agent-runner.d.ts +2 -0
  31. package/dist/src/session-lease.d.ts +1 -1
  32. package/dist/src/session-lease.js +45 -11
  33. package/dist/src/settings.d.ts +6 -3
  34. package/dist/src/settings.js +36 -47
  35. package/dist/src/store.d.ts +3 -1
  36. package/dist/src/store.js +17 -1
  37. package/dist/src/trajectory.d.ts +0 -1
  38. package/dist/src/trajectory.js +2 -3
  39. package/dist/src/types.d.ts +100 -44
  40. package/dist/src/types.js +4 -7
  41. package/dist/src/utils.d.ts +3 -2
  42. package/dist/src/utils.js +20 -10
  43. package/dist/src/validation.d.ts +8 -2
  44. package/dist/src/validation.js +122 -86
  45. package/dist/starter/index.js +8 -9
  46. package/dist/starter/index.js.map +1 -1
  47. package/dist/starter/review-loop.js +8 -7
  48. package/dist/subagents/index.js +654 -814
  49. package/dist/subagents/index.js.map +3 -3
  50. package/dist/subagents/src/contracts.d.ts +4 -3
  51. package/dist/subagents/src/contracts.js +2 -2
  52. package/dist/subagents/src/index.d.ts +1 -1
  53. package/dist/subagents/src/index.js +1 -2
  54. package/dist/subagents/src/manager.js +49 -70
  55. package/dist/subagents/src/navigator.js +3 -14
  56. package/dist/subagents/src/view.d.ts +0 -1
  57. package/dist/subagents/src/view.js +2 -16
  58. package/dist/subagents/src/worktree.js +1 -1
  59. package/dist/trajectory/assets/index.html +8 -8
  60. package/dist/trajectory/index.js +487 -891
  61. package/dist/trajectory/index.js.map +3 -3
  62. package/dist/trajectory/src/assets/index.html +8 -8
  63. package/dist/trajectory/src/index.js +126 -86
  64. package/dist/trajectory/src/server.js +264 -25
  65. package/dist/trajectory/src/server.js.map +3 -3
  66. package/dist/trajectory/src/startup-mutex.d.ts +17 -0
  67. package/dist/trajectory/src/startup-mutex.js +172 -0
  68. package/examples/workflow-extension-template/README.md +4 -7
  69. package/examples/workflow-extension-template/extension.test.mjs +4 -8
  70. package/examples/workflow-extension-template/index.js +0 -3
  71. package/package.json +5 -5
  72. package/skills/pi-extensible-workflows/SKILL.md +10 -8
  73. package/src/agent-execution.ts +339 -127
  74. package/src/background-widget.ts +4 -10
  75. package/src/decoders.ts +31 -37
  76. package/src/execution.ts +5 -5
  77. package/src/host-delivery.ts +4 -8
  78. package/src/host-navigator.ts +4 -2
  79. package/src/host-phases.ts +1 -1
  80. package/src/host-recovery.ts +20 -26
  81. package/src/host-runtime.ts +44 -6
  82. package/src/host-view.ts +18 -11
  83. package/src/host.ts +62 -113
  84. package/src/index.ts +1 -2
  85. package/src/paths.ts +18 -12
  86. package/src/persistence.ts +1 -0
  87. package/src/pi-runtime-runner.ts +3 -1
  88. package/src/registry.ts +27 -63
  89. package/src/runtime/agent-runner.ts +2 -0
  90. package/src/session-lease.ts +34 -6
  91. package/src/settings.ts +35 -47
  92. package/src/store.ts +18 -2
  93. package/src/trajectory.ts +2 -4
  94. package/src/types.ts +71 -30
  95. package/src/utils.ts +18 -9
  96. package/src/validation.ts +102 -79
  97. package/starter/index.ts +0 -1
  98. package/starter/review-loop.ts +8 -7
  99. package/starter/roles/developer.md +0 -1
  100. package/starter/roles/oracle.md +0 -1
  101. package/starter/roles/researcher.md +0 -1
  102. package/starter/roles/reviewer.md +0 -1
  103. package/starter/roles/scout.md +0 -1
  104. package/subagents/README.md +8 -14
  105. package/subagents/src/contracts.ts +3 -4
  106. package/subagents/src/index.ts +2 -3
  107. package/subagents/src/manager.ts +45 -125
  108. package/subagents/src/navigator.ts +4 -13
  109. package/subagents/src/view.ts +4 -14
  110. package/subagents/src/worktree.ts +1 -1
  111. package/trajectory/src/assets/index.html +8 -8
  112. package/trajectory/src/index.ts +106 -82
  113. package/trajectory/src/server.ts +44 -8
  114. package/trajectory/src/startup-mutex.ts +127 -0
  115. package/dist/src/roles.d.ts +0 -70
  116. package/dist/src/roles.js +0 -326
  117. package/dist/starter/roles/developer.md +0 -6
  118. package/dist/starter/roles/oracle.md +0 -18
  119. package/dist/starter/roles/researcher.md +0 -20
  120. package/dist/starter/roles/reviewer.md +0 -19
  121. package/dist/starter/roles/scout.md +0 -18
  122. package/examples/workflow-extension-template/roles/reviewer.md +0 -4
  123. 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, THINKING_LEVELS, WorkflowError } from "./types.js";
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]+)\/([^:\s]+)(?::([^:\s]+))?$/.exec(value);
107
+ const match = /^([^/:\s]+)\/([^\s]+)$/.exec(value);
107
108
  if (!match?.[1] || !match[2])
108
109
  fail("UNKNOWN_MODEL", `Invalid model spec: ${value}`);
109
- const thinking = match[3];
110
- if (thinking !== undefined && !isThinkingLevel(thinking))
111
- fail("UNKNOWN_MODEL", `Invalid thinking level: ${thinking}`);
112
- return { provider: match[1], model: match[2], ...(thinking !== undefined ? { thinking } : {}) };
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
  }
@@ -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;
@@ -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, mergeWorkflowExtensionSettings, modelAliasName, modelCapability, object, positiveInteger, resolveModelReference, resourcePatternHasMagic, unknownModel } from "./utils.js";
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", "skills", "extensions", "contextFiles", "role", "outputSchema", "retries", "timeoutMs"]);
352
- function validateAgentOption(key, value, aliases, knownModels, settingsPath) {
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")), role: staticString(propertyNode(options, "role")) };
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, role: 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, role: null };
493
- return { ...placement, kind, start: call.start, end: call.end, name: staticString(first), prompt: null, model: null, role: 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
- function validateStaticAgentOptions(node, aliases = {}, knownModels, settingsPath) {
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, aliases, knownModels, settingsPath);
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], capabilities.modelAliases ?? {}, capabilities.knownModels ?? capabilities.models, capabilities.settingsPath);
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, capabilities.modelAliases ?? {}, capabilities.knownModels ?? capabilities.models, capabilities.settingsPath);
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 modelRefs = agentOptions.flatMap((options) => { const requested = literalString(propertyNode(options, "model")); return requested === undefined ? [] : [{ requested, resolved: modelCapability(requested, capabilities.modelAliases, capabilities.knownModels ?? capabilities.models, capabilities.settingsPath) }]; });
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
- const missingModel = capabilities.skipModelAvailability ? undefined : modelRefs.find(({ resolved }) => !capabilities.models.has(resolved));
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 globalAgentDefinitions = loadAgentDefinitions(context.cwd, context.agentDir, false, registry && typeof registry.roleDirectoryRegistrations === "function" ? registry.roleDirectoryRegistrations() : registry && typeof registry.roleDirectories === "function" ? registry.roleDirectories() : undefined);
654
- const projectAgentDefinitions = context.projectTrusted ? loadProjectAgentDefinitions(context.cwd) : {};
655
- const agentDefinitions = deepFreeze({ ...globalAgentDefinitions, ...projectAgentDefinitions });
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";
@@ -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 previous review findings and complete the task.
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
- { task, review: review.findings }
28
+ { review: review.findings }
29
29
  );
30
- devResult = await agent(devPrompt, { role: "developer" });
30
+ devResult = await developer.send(devPrompt);
31
31
  review = await agent(
32
32
  prompt(
33
- `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.
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
- <last_review>{review}</last_review>
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": "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",
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 previous review findings and complete the task.
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
- <last_review>{review}</last_review>`, { task, review: review.findings });
18
- devResult = await agent(devPrompt, { role: "developer" });
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
  }