@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
@@ -1,5 +1,6 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks";
2
- import { deepFreeze, errorCode, errorText, fail, isWorkflowErrorCode, jsonValue, object } from "./utils.js";
2
+ import { FUNCTION_HANDLE_PREFIX } from "./execution.js";
3
+ import { deepFreeze, errorCode, errorText, fail, isWorkflowErrorCode, jsonDigest, jsonValue, object } from "./utils.js";
3
4
  import { validateAgentOptions, validateShellOptions, workflowPrompt } from "./validation.js";
4
5
  import { HARD_TERMINAL_RUN_STATES, WORKFLOW_AGENT_STATE_CHANGED_EVENT, WORKFLOW_BUDGET_EVENT, WORKFLOW_CHECKPOINT_STATE_CHANGED_EVENT, WORKFLOW_PHASE_CHANGED_EVENT, WORKFLOW_RUN_COMPLETED_EVENT, WORKFLOW_RUN_FAILED_EVENT, WORKFLOW_RUN_RESUMED_EVENT, WORKFLOW_RUN_STARTED_EVENT, WORKFLOW_RUN_STATE_CHANGED_EVENT, WORKFLOW_WORKTREE_CREATED_EVENT, WorkflowError } from "./types.js";
5
6
  import { structuralPath as operationPath } from "./persistence.js";
@@ -130,7 +131,7 @@ export class WorkflowEventPublisher {
130
131
  await this.#publish(store, metadata, WORKFLOW_RUN_FAILED_EVENT, { error: safeEventError(error) });
131
132
  }
132
133
  async agentState(store, metadata, previous, agent) {
133
- await this.#publish(store, metadata, WORKFLOW_AGENT_STATE_CHANGED_EVENT, { agentId: agent.id, displayLabel: agent.label ?? agent.name, ...(agent.role ? { role: agent.role } : {}), structuralPath: [...(agent.structuralPath ?? [])], ...(agent.parentId ? { parentId: agent.parentId } : {}), ...(agent.parentBreadcrumb ? { parentBreadcrumb: agent.parentBreadcrumb } : {}), ...(agent.worktreeOwner ? { worktreeOwner: agent.worktreeOwner } : {}), ...(previous ? { previousState: previous.state } : {}), state: agent.state, attempt: agent.attempts });
134
+ await this.#publish(store, metadata, WORKFLOW_AGENT_STATE_CHANGED_EVENT, { agentId: agent.id, displayLabel: agent.label ?? agent.name, structuralPath: [...(agent.structuralPath ?? [])], ...(agent.parentId ? { parentId: agent.parentId } : {}), ...(agent.parentBreadcrumb ? { parentBreadcrumb: agent.parentBreadcrumb } : {}), ...(agent.worktreeOwner ? { worktreeOwner: agent.worktreeOwner } : {}), ...(previous ? { previousState: previous.state } : {}), state: agent.state, attempt: agent.attempts });
134
135
  }
135
136
  async agentStates(store, metadata, previous, current) {
136
137
  const previousById = new Map(previous.map((agent) => [agent.id, agent]));
@@ -285,6 +286,7 @@ export function nextNamedOccurrence(counters, label) {
285
286
  function functionBreadcrumb(name, occurrence) { return occurrence === 1 ? name : `${name} #${String(occurrence)}`; }
286
287
  export function withWorkflowFunctions(bridge, store, runContext, registry, settings = Object.freeze({})) {
287
288
  const functionAgentOccurrences = new Map();
289
+ const functionHandles = new Set();
288
290
  const functionShellOccurrences = new Map();
289
291
  const functionInvokeOccurrences = new Map();
290
292
  const functionInvokeBreadcrumbOccurrences = new Map();
@@ -307,6 +309,7 @@ export function withWorkflowFunctions(bridge, store, runContext, registry, setti
307
309
  // Own rejection immediately; Promise.all below still propagates it after the function completes.
308
310
  void sideEffect.catch(() => undefined);
309
311
  };
312
+ let closed = false;
310
313
  const parentBreadcrumb = breadcrumb ?? functionBreadcrumb(name, identity.occurrence);
311
314
  const context = {
312
315
  settings: deepFreeze(structuredClone(settings)),
@@ -327,7 +330,7 @@ export function withWorkflowFunctions(bridge, store, runContext, registry, setti
327
330
  const nestedIdentity = { path: nestedPath, structuralPath: [...inherited], occurrence, ...(scopedWorktreeOwner ? { worktreeOwner: scopedWorktreeOwner } : {}) };
328
331
  return invokeFunction(targetName, targetInput, signal, nestedIdentity, `${parentBreadcrumb} > ${functionBreadcrumb(breadcrumbName, breadcrumbOccurrence)}`);
329
332
  },
330
- agent: async (prompt, options) => {
333
+ agent: Object.assign(async (prompt, options) => {
331
334
  if (!bridge.agent || typeof prompt !== "string")
332
335
  fail("AGENT_FAILED", "No agent bridge is available");
333
336
  const validatedOptions = validateAgentOptions(options === undefined ? {} : options);
@@ -337,7 +340,55 @@ export function withWorkflowFunctions(bridge, store, runContext, registry, setti
337
340
  const occurrence = (functionAgentOccurrences.get(key) ?? 0) + 1;
338
341
  functionAgentOccurrences.set(key, occurrence);
339
342
  return bridge.agent(prompt, validatedOptions, signal, { structuralPath: [...inherited], callSite: `function:${path}`, occurrence, parentBreadcrumb, ...(scopedWorktreeOwner ? { worktreeOwner: scopedWorktreeOwner } : {}) });
340
- },
343
+ }, {
344
+ create: (...created) => {
345
+ if (created.length !== 1 || !object(created[0]))
346
+ fail("INVALID_METADATA", "agent.create requires one options object");
347
+ const { name, ...rest } = created[0];
348
+ if (typeof name !== "string" || !name.trim())
349
+ fail("INVALID_METADATA", "agent.create requires a stable explicit name");
350
+ const config = deepFreeze(structuredClone(validateAgentOptions(rest)));
351
+ const scopedStructuralPath = [...(inheritedHostAgentPath.getStore() ?? [])];
352
+ const scopedWorktreeOwner = inheritedHostWorktreeOwner.getStore() ?? worktreeOwner;
353
+ const scope = JSON.stringify([scopedStructuralPath, scopedWorktreeOwner ?? null]);
354
+ // The registered-function path makes repeated and nested invocations distinct; scope separates branches and worktrees. The digest bounds the journal and turn-copy file names.
355
+ const handle = operationPath(FUNCTION_HANDLE_PREFIX, jsonDigest([path, scopedStructuralPath, scopedWorktreeOwner ?? null]), name);
356
+ if (functionHandles.has(handle))
357
+ fail("INVALID_METADATA", `Duplicate agent handle name: ${name}`);
358
+ functionHandles.add(handle);
359
+ let turn = 0;
360
+ let inflight = false;
361
+ // Not a nested promise chain: the caller owns the one returned promise, and inflight clears in finally on every outcome.
362
+ const send = async (...sent) => {
363
+ if (!bridge.agent)
364
+ fail("AGENT_FAILED", "No agent bridge is available");
365
+ if (sent.length < 1 || sent.length > 2)
366
+ fail("INVALID_METADATA", "send requires a prompt and optional options");
367
+ if (closed)
368
+ fail("INVALID_METADATA", `Agent handle outlived the function invocation that created it: ${name}`);
369
+ if (typeof sent[0] !== "string")
370
+ fail("INVALID_METADATA", "send prompt must be a string");
371
+ const sendOptions = sent[1] === undefined ? {} : sent[1];
372
+ if (!object(sendOptions) || Object.keys(sendOptions).some((key) => key !== "outputSchema" && key !== "timeoutMs"))
373
+ fail("INVALID_METADATA", "send options must contain only outputSchema and timeoutMs");
374
+ const sendStructuralPath = [...(inheritedHostAgentPath.getStore() ?? [])];
375
+ if (JSON.stringify([sendStructuralPath, inheritedHostWorktreeOwner.getStore() ?? worktreeOwner ?? null]) !== scope)
376
+ fail("INVALID_METADATA", `Agent handle sends must run in the scope that created the handle: ${name}`);
377
+ if (inflight)
378
+ fail("INVALID_METADATA", "Concurrent send(...) calls on one agent handle are unsupported; await each turn");
379
+ const validatedOptions = validateAgentOptions({ ...config, ...sendOptions });
380
+ inflight = true;
381
+ turn += 1;
382
+ try {
383
+ return await bridge.agent(sent[0], validatedOptions, signal, { structuralPath: sendStructuralPath, callSite: `function:${path}:handle:${name}`, occurrence: turn, handle, turn, parentBreadcrumb, ...(scopedWorktreeOwner ? { worktreeOwner: scopedWorktreeOwner } : {}) });
384
+ }
385
+ finally {
386
+ inflight = false;
387
+ }
388
+ };
389
+ return Object.freeze({ name, send });
390
+ },
391
+ }),
341
392
  shell: async (...args) => {
342
393
  if (!bridge.shell)
343
394
  fail("SHELL_FAILED", "No shell bridge is available");
@@ -363,7 +414,7 @@ export function withWorkflowFunctions(bridge, store, runContext, registry, setti
363
414
  phase: (name) => { ownSideEffect(() => bridge.phase?.(name)); },
364
415
  log: (message) => { ownSideEffect(() => bridge.log?.(message)); },
365
416
  };
366
- const result = await inheritedHostAgentPath.run([...structuralPath], async () => registry.invokeFunction(name, input, context, path, { get: () => replayed?.value, put: (_path, value) => { stored = value; } }));
417
+ const result = await inheritedHostAgentPath.run([...structuralPath], async () => registry.invokeFunction(name, input, context, path, { get: () => replayed?.value, put: (_path, value) => { stored = value; } })).finally(() => { closed = true; });
367
418
  await Promise.all(sideEffects);
368
419
  if (!replayed)
369
420
  await store.complete(path, stored ?? result);
@@ -95,7 +95,6 @@ export interface AgentDetailPresentation {
95
95
  readonly lastEventAt?: number;
96
96
  readonly structuralPath?: readonly string[];
97
97
  readonly model?: AgentRecord["model"];
98
- readonly role?: string;
99
98
  readonly tools?: readonly string[];
100
99
  readonly attempts?: number;
101
100
  readonly startedAt?: number;
@@ -132,5 +131,6 @@ export declare function formatWorkflowPhaseDashboard(run: PersistedRun, snapshot
132
131
  export declare function formatNavigatorColumns(sidebar: readonly string[], detail: readonly string[], width: number, layout?: Readonly<{
133
132
  treeOnly?: boolean | undefined;
134
133
  detailsOnly?: boolean | undefined;
134
+ detailOffset?: number | undefined;
135
135
  }>): string[];
136
136
  export declare function formatCheckpointReview(checkpoint: AwaitingCheckpoint): string;
@@ -686,7 +686,6 @@ export function formatAgentDetail(agent, styles = PLAIN_WORKFLOW_PROGRESS_STYLES
686
686
  `State: ${state(agent.state)}`,
687
687
  ...(agent.structuralPath?.length ? [`Structural path: ${agent.structuralPath.join(" > ")}`] : []),
688
688
  ...(model === undefined ? [] : [`Model: ${model}`]),
689
- `Role: ${agent.role ?? "(none)"}`,
690
689
  `Tools: ${tools}`,
691
690
  ...(agent.attempts !== undefined && Number.isFinite(agent.attempts) && agent.attempts > 1 ? [`Attempts: ${String(agent.attempts)}`] : []),
692
691
  ...(duration === undefined ? [] : [`Duration: ${formatWorkflowRuntime(duration)}`]),
@@ -812,11 +811,10 @@ export function formatNavigatorRun(loaded, checkpoints, worktrees, now = Date.no
812
811
  const byId = new Map(run.agents.map((agent) => [agent.id, agent]));
813
812
  lines.push(...renderGroupedAgents(run.agents, ({ agent, index, depth }, grouped) => {
814
813
  const model = `${agent.model.provider}/${agent.model.model}${agent.model.thinking ? `:${agent.model.thinking}` : ""}`;
815
- const role = agent.role ? ` role=${agent.role}` : "";
816
814
  const tools = ` tools=${agent.tools.join(",") || "(none)"}`;
817
815
  const accounting = agent.accounting ? ` input=${String(agent.accounting.input)} output=${String(agent.accounting.output)} cache-read=${String(agent.accounting.cacheRead)} cache-write=${String(agent.accounting.cacheWrite)} cost=${formatCost(agent.accounting.cost) || "$0.00"}` : "";
818
816
  const indent = " ".repeat((grouped ? 2 : 1) + depth);
819
- const result = [`${indent}#${String(index + 1)} ${grouped ? agent.label ?? agent.name : agentBreadcrumb(agent, byId)} state=${agent.state} model=${model}${agent.requestedModel ? ` requested=${agent.requestedModel}` : ""}${role}${tools} attempts=${String(agent.attempts)} retries=${String(Math.max(0, agent.attempts - 1))}${accounting}`];
817
+ const result = [`${indent}#${String(index + 1)} ${grouped ? agent.label ?? agent.name : agentBreadcrumb(agent, byId)} state=${agent.state} model=${model}${agent.requestedModel ? ` requested=${agent.requestedModel}` : ""}${tools} attempts=${String(agent.attempts)} retries=${String(Math.max(0, agent.attempts - 1))}${accounting}`];
820
818
  for (const attempt of agent.attemptDetails ?? [])
821
819
  result.push(`${indent} attempt ${String(attempt.attempt)}${attempt.error ? ` error=${attempt.error.code}: ${attempt.error.message}` : ""}`);
822
820
  for (const call of agent.toolCalls ?? [])
@@ -856,17 +854,17 @@ export function formatWorkflowPhaseDashboard(run, snapshot, width, selection = {
856
854
  return;
857
855
  if (child.agent)
858
856
  agents.push(child.agent);
859
- else
860
- for (const childId of child.children)
861
- visit(childId);
862
- };
863
- if (node.agent)
864
- agents.push(node.agent);
865
- else
866
- for (const childId of node.children)
857
+ for (const childId of child.children)
867
858
  visit(childId);
859
+ };
860
+ visit(node.id);
868
861
  return agents;
869
862
  };
863
+ const usageLines = (agents) => {
864
+ const spans = agents.flatMap((agent) => agent.startedAt === undefined ? [] : [[agent.startedAt, agent.startedAt + (elapsedDurationMs(agent, now) ?? 0)]]);
865
+ const elapsed = spans.length ? formatWorkflowRuntime(Math.max(...spans.map(([, end]) => end)) - Math.min(...spans.map(([start]) => start))) : "";
866
+ return [...formatAgentAccounting(sumAccounting(agents.map((agent) => agent.accounting ?? zeroAccounting()))), ...(elapsed ? [`Elapsed: ${elapsed}`] : [])];
867
+ };
870
868
  const nodeStatus = (node) => phaseStyle(node.state)(node.state);
871
869
  const nodeIcon = (node) => node.children.length ? expanded.has(node.id) ? "▾" : "▸" : node.kind === "agent" ? "•" : node.kind === "shell" ? "◇" : " ";
872
870
  const treeLine = (node) => {
@@ -887,12 +885,12 @@ export function formatWorkflowPhaseDashboard(run, snapshot, width, selection = {
887
885
  const selected = node.phase;
888
886
  const counts = selected?.counts ?? phaseAgentCounts(agents);
889
887
  const hint = node.children.length ? "enter expand/collapse" : "enter phase details";
890
- return [styles.bold(`Selected phase: ${node.label}`), `Status: ${nodeStatus(node)}`, `agents completed=${String(counts.completed)} running=${String(counts.running)} failed=${String(counts.failed)} cancelled=${String(counts.cancelled)} pending=${String(counts.pending)}`, `Agents: ${String(agents.length)}`, ...(selection.actions ? [] : [styles.muted(hint)])];
888
+ return [styles.bold(`Selected phase: ${node.label}`), `Status: ${nodeStatus(node)}`, `agents completed=${String(counts.completed)} running=${String(counts.running)} failed=${String(counts.failed)} cancelled=${String(counts.cancelled)} pending=${String(counts.pending)}`, `Agents: ${String(agents.length)}`, ...usageLines(agents), ...(selection.actions ? [] : [styles.muted(hint)])];
891
889
  }
892
890
  if (node.kind === "operation") {
893
891
  const states = phaseAgentCounts(agents);
894
892
  const hint = node.children.length ? "enter expand/collapse" : "enter phase details";
895
- return [styles.bold(`Selected operation: ${node.operationPath.join(" > ")}`), `Phase: ${node.phase?.name ?? node.phaseId}`, `Status: ${nodeStatus(node)}`, `agents completed=${String(states.completed)} running=${String(states.running)} failed=${String(states.failed)} cancelled=${String(states.cancelled)} pending=${String(states.pending)}`, `Agents: ${String(agents.length)}`, ...(selection.actions ? [] : [styles.muted(hint)])];
893
+ return [styles.bold(`Selected operation: ${node.operationPath.join(" > ")}`), `Phase: ${node.phase?.name ?? node.phaseId}`, `Status: ${nodeStatus(node)}`, `agents completed=${String(states.completed)} running=${String(states.running)} failed=${String(states.failed)} cancelled=${String(states.cancelled)} pending=${String(states.pending)}`, `Agents: ${String(agents.length)}`, ...usageLines(agents), ...(selection.actions ? [] : [styles.muted(hint)])];
896
894
  }
897
895
  if (node.kind === "shell") {
898
896
  return [styles.bold("Selected shell"), formatShellActivity(node.shellActivity?.active, node.shellActivity?.startedAt, "⠦", styles, now) ?? "shell [running]"];
@@ -928,7 +926,8 @@ export function formatWorkflowPhaseDashboard(run, snapshot, width, selection = {
928
926
  return ["", styles.bold(actions.title), ...actions.options.map((option, index) => `${index === actions.index ? "→ " : " "}${index === actions.index ? styles.accent(option) : option}`)];
929
927
  };
930
928
  const treeRows = [styles.bold("Tree"), ...(visibleNodes.length ? visibleNodes.map(treeLine) : [styles.muted("(empty)")])];
931
- lines.push(...formatNavigatorColumns(treeRows, [...details(selectedNode), ...actionRows()], safeWidth, selection));
929
+ const detailOffset = Math.max(0, (selection.scrollTop ?? 0) - lines.flatMap(wrap).length);
930
+ lines.push(...formatNavigatorColumns(treeRows, [...details(selectedNode), ...actionRows()], safeWidth, { ...selection, detailOffset }));
932
931
  if (model.unassignedAgents?.length && !tree.nodes.some((node) => node.phaseId === "unassigned"))
933
932
  lines.push(...wrap(styles.muted(`Unassigned agents: ${String(model.unassignedAgents.length)}`)));
934
933
  return lines.flatMap((line) => wrap(line));
@@ -942,7 +941,10 @@ export function formatNavigatorColumns(sidebar, detail, width, layout = {}) {
942
941
  if (safeWidth >= 80) {
943
942
  const sidebarWidth = Math.min(42, Math.max(24, Math.floor((safeWidth - 3) * 0.38)));
944
943
  const left = sidebar.flatMap((line) => wrapNavigatorLine(line, sidebarWidth));
945
- const right = detail.flatMap((line) => wrapNavigatorLine(line, safeWidth - sidebarWidth - 3));
944
+ const wrapped = detail.flatMap((line) => wrapNavigatorLine(line, safeWidth - sidebarWidth - 3));
945
+ // Keep the details in view when the tree is scrolled, without making the block taller than the tree.
946
+ const shift = Math.min(Math.max(0, layout.detailOffset ?? 0), Math.max(0, left.length - wrapped.length));
947
+ const right = [...Array(shift).fill(""), ...wrapped];
946
948
  return Array.from({ length: Math.max(left.length, right.length) }, (_, index) => {
947
949
  const cell = left[index] ?? "";
948
950
  return `${cell}${" ".repeat(Math.max(0, sidebarWidth - visibleWidth(cell)))} | ${right[index] ?? ""}`;
package/dist/src/host.js CHANGED
@@ -12,9 +12,8 @@ import { registerWorkflowNavigator, uiHostCapabilities } from "./host-navigator.
12
12
  import { acquireSessionLease, isPersistedRun, listPersistedSessionIds, listRunIds, RunStore, structuralPath as operationPath } from "./persistence.js";
13
13
  import { retainTerminalRuns } from "./retention.js";
14
14
  import { validateBudget, WorkflowBudgetRuntime } from "./budget.js";
15
- import { SerialLane, asWorkflowError, createLaunchSnapshot, errorCode, errorText, fail, isNodeError, jsonValue, mergeWorkflowExtensionSettings, modelAliasErrorName, modelCapability, object, parseModelReference, positiveInteger, reachableTools, resolveModelReference, VIRTUAL_MODEL_PROVIDER, sanitizeDisplayText, validateModelAliases } from "./utils.js";
16
- import { loadCodemodeToolsSetting, loadSettings, preflight, resolveAgentResourcePolicy, resolveWorkflowSettings, validateCheckpoint, validateModelAliasAvailability, validateWorkflowLaunchWithRegistry, workflowProjectSettingsPath, workflowSettingsPath, workflowToolExposure } from "./validation.js";
17
- import { loadAgentDefinitions, loadProjectAgentDefinitions } from "./roles.js";
15
+ import { SerialLane, asWorkflowError, createLaunchSnapshot, errorCode, errorText, fail, isNodeError, jsonValue, modelAliasErrorName, object, parseModelReference, positiveInteger, reachableTools, resolveModelReference, VIRTUAL_MODEL_PROVIDER, sanitizeDisplayText, validateModelAliases } from "./utils.js";
16
+ import { loadCodemodeToolsSetting, preflight, resolveAgentResourcePolicy, staticAgentPreparationOptions, resolveWorkflowSettings, validateCheckpoint, validateModelAliasAvailability, validateWorkflowLaunchWithRegistry, workflowProjectSettingsPath, workflowSettingsPath, workflowToolExposure } from "./validation.js";
18
17
  import { beginWorkflowExtensionLoading, loadingRegistry, resetWorkflowRegistryIfIdle, retainWorkflowRegistry } from "./registry.js";
19
18
  import { agentHandleTurnPath, agentIdentityPath, agentWorktree, encoded, executeShellCommand, persistActiveAgentAttempt, persistAgentAttempts, readShellResult, runWorkflow, shellIdentityPath, toolIdentityPath } from "./execution.js";
20
19
  import { prepareScriptToolLoadout, scriptTool, scriptToolContext, scriptToolValue, validateScriptToolReferences } from "./script-tools.js";
@@ -23,9 +22,9 @@ import { showChangelogNotice } from "./changelog.js";
23
22
  import { createTrajectoryRunLoader, createTrajectoryRunMetadataLoader, createTrajectorySubagentLoader, createTrajectorySubagentMetadataLoader, createTrajectoryTranscriptLoader } from "./trajectory.js";
24
23
  import { getTrajectoryHost } from "./trajectory-host-handle.js";
25
24
  import { getSubagentManager } from "./subagent-manager-handle.js";
26
- import { HARD_TERMINAL_RUN_STATES, LAUNCH_SNAPSHOT_IDENTITY_VERSION, THINKING_LEVELS, WORKFLOW_BLOCKED_EVENT, WorkflowError, isContextFileScope, isExternallyEndedRunState, isHardTerminalRunState, roleNameOf } from "./types.js";
27
- import { SETTLED_AGENT_STATES, catalogResultValue, formatWorkflowCatalog, styledTextBlock, textBlock, workflowCatalogBlock, workflowControlCall, workflowControlResult, workflowProgressBlock, } from "./host-view.js";
28
- import { DELIVERY_LIMIT_BYTES, ForegroundDeliveryController, markWorkflowFailureDiagnostics, WORKFLOW_LOG_ENTRY, completionDescriptor, completionDeliveryFromStore, createWorkflowFailureDiagnostics, failureDiagnosticsFrom, formatWorkflowFailure, formatWorkflowFailureDelivery, formatWorkflowFailureDeliveryFallback, formatWorkflowFailureDiagnostics, isWorkflowFailureDiagnostics, serializeWorkflowFailureDiagnostics, utf8Prefix, } from "./host-delivery.js";
25
+ import { HARD_TERMINAL_RUN_STATES, LAUNCH_SNAPSHOT_IDENTITY_VERSION, THINKING_LEVELS, WORKFLOW_BLOCKED_EVENT, WorkflowError, isContextFileScope, isExternallyEndedRunState, isHardTerminalRunState } from "./types.js";
26
+ import { SETTLED_AGENT_STATES, catalogResultValue, formatWorkflowCatalog, styledTextBlock, textBlock, workflowCatalogBlock, workflowControlCall, workflowControlResult, workflowProgressBlock } from "./host-view.js";
27
+ import { DELIVERY_LIMIT_BYTES, ForegroundDeliveryController, markWorkflowFailureDiagnostics, WORKFLOW_LOG_ENTRY, completionDescriptor, completionDeliveryFromStore, createWorkflowFailureDiagnostics, failureDiagnosticsFrom, formatWorkflowFailure, formatWorkflowFailureDelivery, formatWorkflowFailureDeliveryFallback, formatWorkflowFailureDiagnostics, isWorkflowFailureDiagnostics, serializeWorkflowFailureDiagnostics, utf8Prefix } from "./host-delivery.js";
29
28
  export { agentBreadcrumb, agentBreadcrumbParts, formatBudgetStatus, formatNavigatorDashboard, formatNavigatorRun, formatStalledDuration, formatWorkflowPhaseDashboard, formatWorkflowProgress, navigatorAttentionSort, truncateWorkflowProgress, } from "./host-view.js";
30
29
  export { buildWorkflowPhaseModel, buildWorkflowPhaseTree, navigateWorkflowPhaseTree, preserveWorkflowPhaseSelection, preserveWorkflowPhaseTreeSelection, workflowPhaseTreeInitialExpanded, workflowPhaseTreeVisibleNodes } from "./host-phases.js";
31
30
  export { formatWorkflowFailure, formatWorkflowFailureDelivery, formatWorkflowFailureDiagnostics } from "./host-delivery.js";
@@ -84,27 +83,6 @@ function completionControlContent(result, controlRunId) {
84
83
  delete record.run;
85
84
  return JSON.stringify({ ...record, value });
86
85
  }
87
- /** Persists a role definition (and its model) into the launch snapshot the first time an agent uses it, so later replays and retries see the definition the run actually ran with. */
88
- function roleCapture(store, initial, definitions, projectDefinitions) {
89
- let persisted = initial;
90
- return {
91
- get snapshot() { return persisted; },
92
- async capture(role, model) {
93
- const definition = definitions[role];
94
- if (!definition)
95
- return;
96
- const modelName = `${model.provider}/${model.model}`;
97
- const hasProjectRole = projectDefinitions[role] !== undefined;
98
- if (persisted.roles?.[role] !== undefined && (!hasProjectRole || persisted.projectRoles?.includes(role)) && persisted.models.includes(modelName))
99
- return;
100
- const roles = { ...(persisted.roles ?? {}), [role]: definition };
101
- const projectRoles = hasProjectRole ? [...new Set([...(persisted.projectRoles ?? []), role])] : persisted.projectRoles ?? [];
102
- const models = [...new Set([...persisted.models, modelName])];
103
- persisted = createLaunchSnapshot({ ...persisted, models, roles, projectRoles });
104
- await store.saveSnapshot(persisted);
105
- },
106
- };
107
- }
108
86
  export function formatWorkflowPreview(args) {
109
87
  const name = typeof args.name === "string" && args.name.trim() ? args.name.trim() : "workflow";
110
88
  if (typeof args.script !== "string" || !args.script.trim())
@@ -283,6 +261,21 @@ async function resolveLaunchAliases(registry, staticAliases, context, availableM
283
261
  throw error;
284
262
  }
285
263
  }
264
+ /**
265
+ * Prepares each completely static agent configuration in inspection mode, so preparation plugins resolve their own aliases
266
+ * and options without persisting a configuration. Any preparation failure rejects launch before effects; cancellation stops
267
+ * the remaining inspections.
268
+ */
269
+ async function inspectStaticAgentConfigurations(executor, script, workflowName, signal, functions) {
270
+ // NOTE: inspection uses the launch cwd; agents inside withWorktree(...) prepare against their checkout at runtime.
271
+ for (const agentOptions of staticAgentPreparationOptions(script, functions)) {
272
+ if (signal.aborted)
273
+ throw new WorkflowError("CANCELLED", "Workflow launch cancelled");
274
+ await executor.prepare({ label: "agent", workflowName, agentOptions }, undefined, signal, "inspection");
275
+ }
276
+ if (signal.aborted)
277
+ throw new WorkflowError("CANCELLED", "Workflow launch cancelled");
278
+ }
286
279
  export default function workflowExtension(pi, home, clipboard = copyToClipboard, transport = localAgentTransport, agentDir, additionalSkillPaths = []) {
287
280
  beginWorkflowExtensionLoading();
288
281
  const registry = loadingRegistry();
@@ -298,7 +291,7 @@ export default function workflowExtension(pi, home, clipboard = copyToClipboard,
298
291
  });
299
292
  let backgroundWidgetEnabled = true;
300
293
  try {
301
- const globalSettings = loadSettings(workflowSettingsPath(extensionAgentDir));
294
+ const globalSettings = resolveWorkflowSettings(process.cwd(), false, workflowSettingsPath(extensionAgentDir)).effective;
302
295
  backgroundWidgetEnabled = globalSettings.backgroundWidget ?? true;
303
296
  // Registered at load, before Pi resolves --model and restores the session model. Project aliases need project trust, which is unknown here.
304
297
  registerAliasVirtualModels(pi, Object.keys(globalSettings.modelAliases ?? {}), workflowSettingsPath(extensionAgentDir));
@@ -338,6 +331,9 @@ export default function workflowExtension(pi, home, clipboard = copyToClipboard,
338
331
  return skillPath ? { skillPaths: [skillPath] } : undefined;
339
332
  });
340
333
  const runs = new Map();
334
+ // Extension load failures a headless launcher reported for this host. They hold for the whole process, so every executor,
335
+ // including those that paused resume, cold resume and retry create, keeps failing ownerless options closed.
336
+ let extensionLoad;
341
337
  const deliveryController = new ForegroundDeliveryController({ runs, ...(typeof pi.sendMessage === "function" ? { deliver: (content) => { deliver(pi, content); } } : {}) });
342
338
  let releaseWorkflowRegistry;
343
339
  const providerRecoveryLane = new SerialLane();
@@ -817,7 +813,7 @@ export default function workflowExtension(pi, home, clipboard = copyToClipboard,
817
813
  const persisted = await persistRunState(run.store, run.metadata, (current) => ({ ...current, ...run.budget.snapshot(), agents: current.agents.map((agent) => agent.id === id ? { ...agent, lastEventAt } : agent) }));
818
814
  run.update?.(workflowToolUpdate(withLiveActivities(persisted)));
819
815
  };
820
- const result = await run.executor.execute(prompt, { label: options.label, workflowName: run.metadata.name, tuiIndex, tuiLabel: options.requestedLabel ?? options.label, agentNodeId: id, ...(options.extensionSettings === undefined ? {} : { inheritedExtensionSettings: options.extensionSettings }), onProgress, onAttempt, budget, ...(run.providerErrorRecovery ? { providerErrorRecovery: run.providerErrorRecovery } : {}), ...(parentId ? { parent: parentId, cwd: options.cwd, ...(options.worktreeOwner ? { worktreeOwner: options.worktreeOwner } : {}) } : options.worktreeOwner ? { worktreeOwner: options.worktreeOwner } : {}), ...(options.model ? { model: options.model } : {}), ...(options.role ? { role: options.role } : {}), ...(options.contextFiles ? { contextFiles: options.contextFiles } : {}), tools: options.tools, ...(options.skills ? { skills: options.skills } : {}), ...(options.extensions ? { extensions: options.extensions } : {}), effectiveTools: options.tools, ...(options.schema ? { schema: options.schema } : {}), ...(options.retries === undefined ? {} : { retries: options.retries }), ...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }), ...(options.sessionPath ? { sessionPath: options.sessionPath } : {}), ...(options.agentOptions ? { agentOptions: options.agentOptions } : {}), ...(options.agentIdentity ? { agentIdentity: options.agentIdentity } : {}) }, signal, scheduler.toolsFor(id, (role, tools, model, inheritedTools, skills, extensions) => run.executor.resolve({ label: "child", workflowName: run.metadata.name, ...(model ? { model } : {}), ...(role ? { role } : {}), ...(tools !== undefined ? { tools } : {}), ...(skills !== undefined ? { skills } : {}), ...(extensions !== undefined ? { extensions } : {}) }, inheritedTools).tools), setSteer, () => { scheduler.cancelChildren(id); scheduler.retry(id); });
816
+ const result = await run.executor.execute(prompt, { label: options.label, workflowName: run.metadata.name, tuiIndex, tuiLabel: options.requestedLabel ?? options.label, agentNodeId: id, ...(options.extensionSettings === undefined ? {} : { inheritedExtensionSettings: options.extensionSettings }), onProgress, onAttempt, budget, ...(run.providerErrorRecovery ? { providerErrorRecovery: run.providerErrorRecovery } : {}), ...(parentId ? { parent: parentId, cwd: options.cwd, ...(options.worktreeOwner ? { worktreeOwner: options.worktreeOwner } : {}) } : options.worktreeOwner ? { worktreeOwner: options.worktreeOwner } : {}), ...(options.model ? { model: options.model } : {}), ...(options.contextFiles ? { contextFiles: options.contextFiles } : {}), tools: options.tools, ...(options.skills ? { skills: options.skills } : {}), ...(options.extensions ? { extensions: options.extensions } : {}), effectiveTools: options.tools, ...(options.schema ? { schema: options.schema } : {}), ...(options.retries === undefined ? {} : { retries: options.retries }), ...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }), ...(options.sessionPath ? { sessionPath: options.sessionPath } : {}), ...(options.agentOptions ? { agentOptions: options.agentOptions } : {}), ...(options.agentIdentity ? { agentIdentity: options.agentIdentity } : {}), ...(options.configuration ? { configuration: options.configuration } : {}), ...(options.capabilities ? { capabilities: options.capabilities } : {}), ...(options.projectTrusted === undefined ? {} : { projectTrusted: options.projectTrusted }), ...(options.inheritedExcludeTools ? { inheritedExcludeTools: options.inheritedExcludeTools } : {}), onConfiguration: (configuration) => scheduler.setConfiguration(id, configuration) }, signal, scheduler.toolsFor(id), setSteer, () => { scheduler.cancelChildren(id); scheduler.retry(id); });
821
817
  cancelDeferredProgress();
822
818
  const before = (await run.store.load()).run;
823
819
  await persistAgentAttempts(run.store, id, result.attempts);
@@ -855,7 +851,7 @@ export default function workflowExtension(pi, home, clipboard = copyToClipboard,
855
851
  const existing = new Map(current.agents.map((agent) => [agent.id, agent]));
856
852
  const agents = ownership.map((node) => {
857
853
  const previous = existing.get(node.id);
858
- const requested = { label: node.options.label, workflowName: run.metadata.name, ...(node.options.skills ? { skills: node.options.skills } : {}), ...(node.options.extensions ? { extensions: node.options.extensions } : {}), ...(node.options.model ? { model: node.options.model } : {}), ...(node.options.role ? { role: node.options.role } : {}), ...(node.options.contextFiles ? { contextFiles: node.options.contextFiles } : {}) };
854
+ const requested = { label: node.options.label, workflowName: run.metadata.name, ...(node.options.skills ? { skills: node.options.skills } : {}), ...(node.options.extensions ? { extensions: node.options.extensions } : {}), ...(node.options.model ? { model: node.options.model } : {}), ...(node.options.contextFiles ? { contextFiles: node.options.contextFiles } : {}), ...(node.options.configuration ? { configuration: node.options.configuration } : {}) };
859
855
  let effective;
860
856
  try {
861
857
  effective = { ...run.executor.resolve({ ...requested, effectiveTools: node.options.tools }), tools: node.options.tools };
@@ -864,12 +860,11 @@ export default function workflowExtension(pi, home, clipboard = copyToClipboard,
864
860
  effective = previous ? { model: previous.model, ...(previous.requestedModel ? { requestedModel: previous.requestedModel } : {}), tools: previous.tools } : { model: node.options.model ? modelSpec(node.options.model, run.model) : run.model, ...(node.options.model ? { requestedModel: node.options.model } : {}), tools: node.options.tools };
865
861
  }
866
862
  const resultPath = !node.parentId && node.options.agentIdentity ? agentIdentityPath(node.options.agentIdentity) : undefined;
867
- const nodeRole = roleNameOf(node.options.role);
868
863
  const now = Date.now();
869
864
  const lastEventAt = node.state === "running" ? previous?.state === "running" && previous.lastEventAt !== undefined ? previous.lastEventAt : now : previous?.lastEventAt;
870
865
  const startedAt = previous?.startedAt ?? (node.state === "running" ? now : undefined);
871
866
  const durationMs = previous?.durationMs ?? (SETTLED_AGENT_STATES.has(node.state) && startedAt !== undefined ? Math.max(0, now - startedAt) : undefined);
872
- return { ...(previous?.systemPrompt === undefined ? {} : { systemPrompt: previous.systemPrompt }), ...(node.prompt !== undefined ? { prompt: node.prompt } : previous?.prompt !== undefined ? { prompt: previous.prompt } : {}), id: node.id, name: node.label, ...(node.options.requestedLabel ? { label: node.options.requestedLabel } : {}), path: node.id, state: node.state, ...(node.parentId ? { parentId: node.parentId } : {}), structuralPath: [...(node.options.agentIdentity?.structuralPath ?? [])], ...(resultPath ? { resultPath } : {}), ...(node.options.parentBreadcrumb ? { parentBreadcrumb: node.options.parentBreadcrumb } : {}), ...(node.options.worktreeOwner ? { worktreeOwner: node.options.worktreeOwner } : {}), ...(node.options.agentIdentity?.handle === undefined ? {} : { handle: node.options.agentIdentity.handle, ...(node.options.agentIdentity.turn === undefined ? {} : { turn: node.options.agentIdentity.turn }), ...(node.options.continuity ? { continuity: node.options.continuity } : {}) }), ...(nodeRole ? { role: nodeRole } : {}), ...(effective.requestedModel ? { requestedModel: effective.requestedModel } : {}), model: effective.model, tools: effective.tools, attempts: previous?.attempts ?? 0, ...(startedAt === undefined ? {} : { startedAt }), ...(durationMs === undefined ? {} : { durationMs }), ...(previous?.attemptDetails ? { attemptDetails: previous.attemptDetails } : {}), ...(previous?.accounting ? { accounting: previous.accounting } : {}), ...(previous?.toolCalls ? { toolCalls: previous.toolCalls } : {}), ...(previous?.activity ? { activity: previous.activity } : {}), ...(lastEventAt === undefined ? {} : { lastEventAt }) };
867
+ return { ...(previous?.systemPrompt === undefined ? {} : { systemPrompt: previous.systemPrompt }), ...(node.prompt !== undefined ? { prompt: node.prompt } : previous?.prompt !== undefined ? { prompt: previous.prompt } : {}), id: node.id, name: node.label, ...(node.options.requestedLabel ? { label: node.options.requestedLabel } : {}), path: node.id, state: node.state, ...(node.parentId ? { parentId: node.parentId } : {}), structuralPath: [...(node.options.agentIdentity?.structuralPath ?? [])], ...(resultPath ? { resultPath } : {}), ...(node.options.parentBreadcrumb ? { parentBreadcrumb: node.options.parentBreadcrumb } : {}), ...(node.options.worktreeOwner ? { worktreeOwner: node.options.worktreeOwner } : {}), ...(node.options.agentIdentity?.handle === undefined ? {} : { handle: node.options.agentIdentity.handle, ...(node.options.agentIdentity.turn === undefined ? {} : { turn: node.options.agentIdentity.turn }), ...(node.options.continuity ? { continuity: node.options.continuity } : {}) }), ...(effective.requestedModel ? { requestedModel: effective.requestedModel } : {}), model: effective.model, tools: effective.tools, attempts: previous?.attempts ?? 0, ...(startedAt === undefined ? {} : { startedAt }), ...(durationMs === undefined ? {} : { durationMs }), ...(previous?.attemptDetails ? { attemptDetails: previous.attemptDetails } : {}), ...(previous?.accounting ? { accounting: previous.accounting } : {}), ...(previous?.toolCalls ? { toolCalls: previous.toolCalls } : {}), ...(previous?.activity ? { activity: previous.activity } : {}), ...(lastEventAt === undefined ? {} : { lastEventAt }) };
873
868
  });
874
869
  return { ...current, agents };
875
870
  });
@@ -1093,21 +1088,7 @@ export default function workflowExtension(pi, home, clipboard = copyToClipboard,
1093
1088
  });
1094
1089
  catalogRegistered = true;
1095
1090
  };
1096
- const createAgentExecutor = (root) => new WorkflowAgentExecutor({ ...root, agentDir: extensionAgentDir, ...(additionalSkillPaths.length ? { additionalSkillPaths } : {}), agentSetupHooks: registry.agentSetupHooks(), validateExtensionSettings: registry.validateExtensionSettings, onResourceWarning: (message) => { deliverWarning(pi, message); }, onAgentSettings: (agentId, settings) => { scheduler.setExtensionSettings(agentId, settings); } }, transport);
1097
- // Resumed runs keep the role definitions frozen in their snapshot and fall back to the current definitions for roles a
1098
- // registered function reaches for the first time after the restart; those are captured into the snapshot like at launch.
1099
- const resumeRoles = (store, snapshot, cwd, trustedProject) => {
1100
- let current = {};
1101
- let project = {};
1102
- try {
1103
- current = loadAgentDefinitions(cwd, extensionAgentDir, trustedProject, registry.roleDirectoryRegistrations());
1104
- project = trustedProject ? loadProjectAgentDefinitions(cwd) : {};
1105
- }
1106
- catch { /* Unreadable current roles leave the resume with the snapshot roles, as before. */ }
1107
- const definitions = { ...current, ...(snapshot.roles ?? {}) };
1108
- const captured = roleCapture(store, snapshot, definitions, project);
1109
- return { definitions, capture: (role, model) => captured.capture(role, model) };
1110
- };
1091
+ const createAgentExecutor = (root) => new WorkflowAgentExecutor({ ...root, ...(extensionLoad ? { extensionLoad } : {}), agentDir: extensionAgentDir, ...(additionalSkillPaths.length ? { additionalSkillPaths } : {}), agentPreparationHooks: registry.agentPreparationHooks(), agentSetupHooks: registry.agentSetupHooks(), validateExtensionSettings: registry.validateExtensionSettings, onResourceWarning: root.onResourceWarning ?? ((message) => { deliverWarning(pi, message); }), onAgentSettings: (agentId, settings) => { scheduler.setExtensionSettings(agentId, settings); }, onAgentCapabilities: (agentId, capabilities, projectTrusted) => { scheduler.setAttemptCapabilities(agentId, capabilities, projectTrusted); } }, transport);
1111
1092
  const activeSnapshotTools = (tools, active) => active === "session"
1112
1093
  ? new Set(tools.filter((tool) => reachableTools(pi).includes(tool) && tool !== "workflow_catalog"))
1113
1094
  : new Set(tools.filter((tool) => active.has(tool) || tool === "workflow_catalog"));
@@ -1121,27 +1102,27 @@ export default function workflowExtension(pi, home, clipboard = copyToClipboard,
1121
1102
  const extensionSettings = input.snapshot.settings.extensionSettings;
1122
1103
  const extensionSettingsPath = input.snapshot.settingsSources?.extensionSettings ?? input.snapshot.settingsPath ?? settingsPath;
1123
1104
  registry.validateExtensionSettings(extensionSettings, { source: "effective", cwd: input.cwd, projectTrusted: input.trustedProject, settingsPath: extensionSettingsPath });
1124
- for (const [role, definition] of Object.entries(input.snapshot.roles ?? {}))
1125
- registry.validateExtensionSettings(mergeWorkflowExtensionSettings(extensionSettings, definition.extensionSettings), { source: "role", cwd: input.cwd, projectTrusted: input.trustedProject, settingsPath: extensionSettingsPath, role });
1126
1105
  const currentPolicy = resolveAgentResourcePolicy(input.cwd, input.trustedProject, settingsPath);
1127
1106
  const staticAliases = resolution.effective.modelAliases ?? {};
1128
1107
  const previousAliases = input.snapshot.modelAliases ?? input.snapshot.settings.modelAliases ?? {};
1129
1108
  const inventory = modelInventory(input.rootModel, input.modelRegistry);
1130
1109
  const knownModels = input.modelRegistry ? inventory.knownModels : new Set([...input.snapshot.models, ...inventory.knownModels]);
1131
1110
  const availableModels = input.modelRegistry ? inventory.availableModels : new Set([...input.snapshot.models, ...inventory.availableModels]);
1132
- const currentAliases = input.resolvedAliases ?? (await resolveLaunchAliases(registry, staticAliases, { cwd: input.cwd, projectTrusted: input.trustedProject, rootModel: input.rootModel, knownModels, availableModels, signal: input.signal }, availableModels, knownModels, settingsPath)).aliases;
1111
+ const resolved = input.resolvedAliases ? { aliases: input.resolvedAliases, dynamicNames: input.resolvedDynamicAliasNames ?? [] } : await resolveLaunchAliases(registry, staticAliases, { cwd: input.cwd, projectTrusted: input.trustedProject, rootModel: input.rootModel, knownModels, availableModels, signal: input.signal }, availableModels, knownModels, settingsPath);
1112
+ const currentAliases = resolved.aliases;
1113
+ const dynamicAliasNames = resolved.dynamicNames;
1133
1114
  const blockedAliases = input.blockedAliases ?? new Set(Object.keys(previousAliases).filter((name) => !Object.prototype.hasOwnProperty.call(currentAliases, name)));
1134
1115
  const blockedAliasTargets = input.blockedAliasTargets ?? Object.fromEntries(Object.entries(previousAliases).filter(([name]) => !Object.prototype.hasOwnProperty.call(currentAliases, name)));
1135
1116
  const script = input.withPreflight ? input.snapshot.script : undefined;
1136
1117
  if (script !== undefined) {
1137
1118
  const resumeAliases = { ...previousAliases, ...currentAliases };
1138
- preflight(script, { models: availableModels, tools: active, agentTypes: new Set(input.snapshot.agentTypes), modelAliases: resumeAliases, knownModels, settingsPath, skipModelAvailability: true }, input.snapshot.schemas, input.snapshot.metadata, true);
1119
+ preflight(script, { models: availableModels, tools: active, modelAliases: resumeAliases, knownModels, settingsPath, skipModelAvailability: true }, input.snapshot.schemas, input.snapshot.metadata, true);
1139
1120
  }
1140
1121
  const refreshed = resumedSnapshotSettings(input.snapshot, resolution, currentAliases);
1141
1122
  const snapshot = createLaunchSnapshot({ ...input.snapshot, settingsPath, ...refreshed, modelAliases: currentAliases });
1142
- return { active, settingsPath, resolution, currentPolicy, previousAliases, knownModels, availableModels, currentAliases, blockedAliases, blockedAliasTargets, snapshot, script };
1123
+ return { active, settingsPath, resolution, currentPolicy, previousAliases, knownModels, availableModels, currentAliases, dynamicAliasNames, blockedAliases, blockedAliasTargets, snapshot, script };
1143
1124
  };
1144
- const workflowAgentHandler = (store, metadata, lifecycle, executor, cwd, runId, captureRole) => {
1125
+ const workflowAgentHandler = (store, metadata, lifecycle, executor, cwd, runId) => {
1145
1126
  // Replay lookup and spawn are serialised per run so concurrent agent(...) calls reach the scheduler in call order.
1146
1127
  const admission = new SerialLane();
1147
1128
  return async (prompt, options, agentSignal, identity) => {
@@ -1154,21 +1135,21 @@ export default function workflowExtension(pi, home, clipboard = copyToClipboard,
1154
1135
  return { replayed };
1155
1136
  const worktree = agentWorktree(identity);
1156
1137
  const agentCwd = worktree.worktreeOwner ? (await persistWorktree(store, metadata, worktree.worktreeOwner)).cwd : cwd;
1157
- const role = typeof options.role === "string" ? options.role : undefined;
1158
1138
  const model = typeof options.model === "string" ? options.model : undefined;
1159
1139
  const requestedLabel = typeof options.label === "string" ? options.label : undefined;
1160
1140
  const skills = Array.isArray(options.skills) ? options.skills : undefined;
1161
1141
  const extensions = Array.isArray(options.extensions) ? options.extensions : undefined;
1162
1142
  const contextFiles = Array.isArray(options.contextFiles) && options.contextFiles.every(isContextFileScope) ? options.contextFiles : undefined;
1163
- const resolved = executor.resolve({ label: requestedLabel ?? role ?? "agent", workflowName: metadata.name, ...(model ? { model } : {}), ...(role ? { role } : {}), ...(contextFiles ? { contextFiles } : {}), ...(Array.isArray(options.tools) ? { tools: options.tools } : {}), ...(skills ? { skills } : {}), ...(extensions ? { extensions } : {}) });
1164
- if (role)
1165
- await captureRole?.(role, resolved.model);
1166
- const label = displayAgentName(requestedLabel, role, resolved.model);
1143
+ // A paused resume replaces the run executor with refreshed aliases; new identities must prepare against it.
1144
+ const current = runs.get(runId)?.executor ?? executor;
1145
+ const configuration = await current.prepare({ label: requestedLabel ?? "agent", workflowName: metadata.name, agentOptions: options, agentIdentity: identity, ...(worktree.worktreeOwner ? { worktreeOwner: worktree.worktreeOwner } : {}) }, agentCwd, agentSignal);
1146
+ const resolved = configuration;
1147
+ const label = requestedLabel ?? configuration.label ?? resolved.model.model;
1167
1148
  const tools = resolved.tools;
1168
1149
  const schema = object(options.outputSchema) ? options.outputSchema : undefined;
1169
1150
  const sessionPath = identity.handle !== undefined && identity.turn !== undefined ? await handleTurnInput(store, identity.handle, identity.turn) : undefined;
1170
1151
  const continuity = identity.handle === undefined ? undefined : sessionPath ? "continued" : "fresh";
1171
- return { spawned: scheduler.spawn(runId, prompt, { label, ...(requestedLabel ? { requestedLabel } : {}), ...(identity.parentBreadcrumb ? { parentBreadcrumb: identity.parentBreadcrumb } : {}), cwd: agentCwd, tools, ...(skills ? { skills } : {}), ...(extensions ? { extensions } : {}), ...worktree, ...(model ? { model } : {}), ...(role ? { role } : {}), ...(contextFiles ? { contextFiles } : {}), ...(schema ? { schema } : {}), ...(typeof options.retries === "number" ? { retries: options.retries } : {}), ...(positiveInteger(options.timeoutMs) || options.timeoutMs === null ? { timeoutMs: options.timeoutMs } : {}), ...(sessionPath ? { sessionPath } : {}), ...(continuity ? { continuity } : {}), agentOptions: options, agentIdentity: identity }) };
1152
+ return { spawned: scheduler.spawn(runId, prompt, { label, ...(requestedLabel ? { requestedLabel } : {}), ...(identity.parentBreadcrumb ? { parentBreadcrumb: identity.parentBreadcrumb } : {}), cwd: agentCwd, tools, ...(skills ? { skills } : {}), ...(extensions ? { extensions } : {}), ...worktree, ...(model ? { model } : {}), ...(contextFiles ? { contextFiles } : {}), ...(schema ? { schema } : {}), ...(typeof options.retries === "number" ? { retries: options.retries } : {}), ...(positiveInteger(options.timeoutMs) || options.timeoutMs === null ? { timeoutMs: options.timeoutMs } : {}), ...(sessionPath ? { sessionPath } : {}), ...(continuity ? { continuity } : {}), agentOptions: options, agentIdentity: identity, configuration }) };
1172
1153
  });
1173
1154
  if ("replayed" in admitted)
1174
1155
  return admitted.replayed.value;
@@ -1191,7 +1172,7 @@ export default function workflowExtension(pi, home, clipboard = copyToClipboard,
1191
1172
  };
1192
1173
  };
1193
1174
  const recovery = createWorkflowRecovery({
1194
- pi, home, runs, scheduler, eventPublisher, persistRunState, projectTrusted, resumeHostContext, ensureSessionLease, coordinateRunMutation, createAgentExecutor, activeSnapshotTools, frozenResourcePolicy, resolveLaunchPrologue: resumeLaunchPrologue, resumeRoles, workflowAgentHandler, shellForRun, toolForRun, resolveWorktree, checkpointBridge, phaseBridge, logBridge, lifecycleFor, createProviderErrorRecovery, cleanupTerminalRun, deliver: (content) => { deliver(pi, content); }, deliverTerminal: deliveryController.deliverTerminal, workflowToolUpdate, registry, modelSpec,
1175
+ pi, home, runs, scheduler, eventPublisher, persistRunState, projectTrusted, resumeHostContext, ensureSessionLease, coordinateRunMutation, createAgentExecutor, activeSnapshotTools, frozenResourcePolicy, resolveLaunchPrologue: resumeLaunchPrologue, workflowAgentHandler, shellForRun, toolForRun, resolveWorktree, checkpointBridge, phaseBridge, logBridge, lifecycleFor, createProviderErrorRecovery, cleanupTerminalRun, deliver: (content) => { deliver(pi, content); }, deliverTerminal: deliveryController.deliverTerminal, workflowToolUpdate, registry, modelSpec,
1195
1176
  });
1196
1177
  const resumeSelectedWorkflow = async (runId, foreground, context, budgetPatch) => {
1197
1178
  const run = runs.get(runId);
@@ -1212,9 +1193,8 @@ export default function workflowExtension(pi, home, clipboard = copyToClipboard,
1212
1193
  await deliveryController.moveForegroundToBackground(runId);
1213
1194
  if (foreground && !wasAttached) {
1214
1195
  run.foreground = true;
1215
- const loaded = await run.store.load();
1216
1196
  await persistRunState(run.store, run.metadata, (current) => ({ ...current, delivery: { ...(current.delivery ?? {}), mode: "foreground", state: "attached" } }));
1217
- await run.store.saveSnapshot(createLaunchSnapshot({ ...loaded.snapshot, launchMode: "foreground" }));
1197
+ await run.store.setLaunchMode("foreground");
1218
1198
  }
1219
1199
  else if (!foreground)
1220
1200
  run.foreground = false;
@@ -1349,10 +1329,9 @@ export default function workflowExtension(pi, home, clipboard = copyToClipboard,
1349
1329
  const budgetRuntime = new WorkflowBudgetRuntime(budget, loaded.run.budgetVersion ?? 1, loaded.run.usage, loaded.run.budgetEvents, { active: loaded.run.state === "running" });
1350
1330
  const lifecycle = lifecycleFor(store, loaded.run.state, loaded.snapshot.metadata);
1351
1331
  const providerPause = async () => { deliver(pi, `Workflow ${loaded.snapshot.metadata.name} paused: provider limit.`); await lifecycle.providerPause(); };
1352
- const roleDefinitions = loaded.snapshot.roles ?? {};
1353
1332
  const abortController = new AbortController();
1354
1333
  const providerErrorRecovery = createProviderErrorRecovery(ctx, new Set(loaded.snapshot.models), () => { abortController.abort(); });
1355
- runs.set(runId, { executor: createAgentExecutor({ cwd: ctx.cwd, model, tools: activeSnapshotTools(loaded.snapshot.tools, "session"), resourceSelectors: snapshotResourcePolicy(loaded.snapshot, store.cwd, projectTrusted(ctx), workflowSettingsPath(extensionAgentDir)).effective, extensionSettings: loaded.snapshot.settings.extensionSettings, availableModels: new Set(loaded.snapshot.models), knownModels: new Set(loaded.snapshot.models), ...(loaded.snapshot.modelAliases ?? loaded.snapshot.settings.modelAliases ? { modelAliases: loaded.snapshot.modelAliases ?? loaded.snapshot.settings.modelAliases } : {}), ...(loaded.snapshot.settingsSources?.modelAliases ? { settingsPath: loaded.snapshot.settingsSources.modelAliases } : loaded.snapshot.settingsPath ? { settingsPath: loaded.snapshot.settingsPath } : {}), agentDefinitions: roleDefinitions, runStore: store, providerPause, agentResourcePolicy: frozenResourcePolicy(snapshotResourcePolicy(loaded.snapshot, store.cwd, projectTrusted(ctx), workflowSettingsPath(extensionAgentDir))) }), store, metadata: loaded.snapshot.metadata, model, lifecycle, budget: budgetRuntime, abortController, projectTrusted: () => projectTrusted(ctx), checkpointResolvers: new Map(), ...(providerErrorRecovery ? { providerErrorRecovery } : {}) });
1334
+ runs.set(runId, { executor: createAgentExecutor({ cwd: ctx.cwd, projectTrusted: projectTrusted(ctx), model, tools: activeSnapshotTools(loaded.snapshot.tools, "session"), resourceSelectors: snapshotResourcePolicy(loaded.snapshot, store.cwd, projectTrusted(ctx), workflowSettingsPath(extensionAgentDir)).effective, extensionSettings: loaded.snapshot.settings.extensionSettings, availableModels: new Set(loaded.snapshot.models), knownModels: new Set(loaded.snapshot.models), ...(loaded.snapshot.modelAliases ?? loaded.snapshot.settings.modelAliases ? { modelAliases: loaded.snapshot.modelAliases ?? loaded.snapshot.settings.modelAliases } : {}), ...(loaded.snapshot.settingsSources?.modelAliases ? { settingsPath: loaded.snapshot.settingsSources.modelAliases } : loaded.snapshot.settingsPath ? { settingsPath: loaded.snapshot.settingsPath } : {}), runStore: store, providerPause, agentResourcePolicy: frozenResourcePolicy(snapshotResourcePolicy(loaded.snapshot, store.cwd, projectTrusted(ctx), workflowSettingsPath(extensionAgentDir))) }), store, metadata: loaded.snapshot.metadata, model, lifecycle, budget: budgetRuntime, abortController, projectTrusted: () => projectTrusted(ctx), checkpointResolvers: new Map(), ...(providerErrorRecovery ? { providerErrorRecovery } : {}) });
1356
1335
  for (const checkpoint of await store.awaitingCheckpoints())
1357
1336
  deliver(pi, `Workflow ${loaded.snapshot.metadata.name} checkpoint ${checkpoint.name}: ${checkpoint.prompt}\nContext: ${JSON.stringify(checkpoint.context)}\nRespond with workflow_respond.`);
1358
1337
  for (const decision of await store.pendingWorkflowDecisions())
@@ -1391,15 +1370,6 @@ export default function workflowExtension(pi, home, clipboard = copyToClipboard,
1391
1370
  throw error;
1392
1371
  }
1393
1372
  });
1394
- pi.on("before_agent_start", (event, ctx) => {
1395
- if (!pi.getActiveTools().includes("workflow"))
1396
- return;
1397
- const roles = Object.entries(loadAgentDefinitions(ctx.cwd, extensionAgentDir, projectTrusted(ctx), typeof registry.roleDirectoryRegistrations === "function" ? registry.roleDirectoryRegistrations() : undefined)).filter(([, definition]) => definition.description);
1398
- if (!roles.length)
1399
- return;
1400
- const content = `Workflow role descriptions:\n${roles.map(([name, definition]) => `- \`${name}\`: ${String(definition.description)}`).join("\n")}`;
1401
- return { systemPrompt: `${event.systemPrompt}\n\n${content}` };
1402
- });
1403
1373
  const workflowTool = {
1404
1374
  name: "workflow",
1405
1375
  label: WORKFLOW_TOOL_LABEL,
@@ -1448,25 +1418,33 @@ export default function workflowExtension(pi, home, clipboard = copyToClipboard,
1448
1418
  if (trustedProject && launch.resolution.project.extensionSettings !== undefined)
1449
1419
  registry.validateExtensionSettings(launch.resolution.project.extensionSettings, { source: "project", cwd: ctx.cwd, projectTrusted: true, settingsPath: launch.resolution.projectSettingsPath });
1450
1420
  const validated = validateWorkflowLaunchWithRegistry(params, { cwd: ctx.cwd, agentDir: extensionAgentDir, projectTrusted: trustedProject, availableModels, rootTools: new Set(rootTools), modelAliases, knownModels, settingsPath, ...(settings.extensionSettings === undefined ? {} : { extensionSettings: settings.extensionSettings }) }, registry);
1451
- const { script, checked, agentDefinitions, projectAgentDefinitions, roleNames } = validated;
1421
+ const { script, checked } = validated;
1452
1422
  const toolContext = scriptToolContext(ctx);
1453
1423
  validateScriptToolReferences(script, toolContext);
1424
+ // Headless hosts that tolerated extension load failures report them with the extensions they loaded.
1425
+ const strings = (value) => Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
1426
+ const loadErrors = object(ctx) ? strings(ctx.extensionLoadErrors) : [];
1427
+ if (loadErrors.length)
1428
+ extensionLoad = Object.freeze({ errors: loadErrors, loaded: object(ctx) ? strings(ctx.loadedExtensionPaths) : [] });
1429
+ const executorRoot = { cwd: ctx.cwd, projectTrusted: launch.resourcePolicy.projectTrusted, model: rootModel, tools: new Set(rootTools), resourceSelectors: launch.resourcePolicy.effective, extensionSettings: settings.extensionSettings, availableModels, knownModels, modelAliases, dynamicModelAliasNames: resolvedAliases.dynamicNames, settingsPath, agentResourcePolicy: frozenResourcePolicy(launch.resourcePolicy) };
1430
+ // Runtime emits the same resource warnings, so inspection stays silent.
1431
+ await inspectStaticAgentConfigurations(createAgentExecutor({ ...executorRoot, onResourceWarning: () => undefined }), script, checked.metadata.name, runController.signal, registry.functions());
1454
1432
  await ensureSessionLease(ctx.cwd, ctx.sessionManager.getSessionId());
1433
+ // Cancellation can arrive while the lease or a queued parent validation is pending; no run may be created after it.
1434
+ const assertLaunchActive = () => { if (runController.signal.aborted)
1435
+ throw new WorkflowError("CANCELLED", "Workflow launch cancelled"); };
1436
+ assertLaunchActive();
1455
1437
  const runId = randomUUID();
1456
1438
  const args = params.args ?? null;
1457
1439
  encoded(args);
1458
1440
  const runContext = workflowRunContext(ctx.cwd, ctx.sessionManager.getSessionId(), runId, checked.metadata, args, runController.signal);
1459
1441
  const store = new RunStore(ctx.cwd, ctx.sessionManager.getSessionId(), runId, home);
1460
1442
  const parentRunId = params.parentRunId;
1461
- const roles = Object.fromEntries(roleNames.map((role) => [role, agentDefinitions[role]]));
1462
- const projectRoles = roleNames.filter((role) => projectAgentDefinitions[role] !== undefined);
1463
- const roleModels = roleNames.flatMap((role) => { const model = agentDefinitions[role]?.model; return model ? [modelCapability(model, modelAliases, knownModels, settingsPath)] : []; });
1464
- const snapshotModels = [...new Set([rootModelName, ...checked.referenced.models, ...roleModels])];
1465
- const snapshot = createLaunchSnapshot({ script, args, metadata: checked.metadata, launchMode: params.foreground ? "foreground" : "background", settings, settingsPath, settingsSources: { ...launch.resolution.sources, concurrency: params.concurrency === undefined ? launch.resolution.sources.concurrency : "per-run options" }, ...(Object.keys(modelAliases).length ? { modelAliases } : {}), ...(budget ? { budget } : {}), ...(checked.referenced.phases.length ? { phases: checked.referenced.phases } : {}), models: snapshotModels, tools: rootTools, agentTypes: checked.referenced.agentTypes, roles, projectRoles, schemas: checked.schemas });
1466
- const capturedRoles = roleCapture(store, snapshot, agentDefinitions, projectAgentDefinitions);
1443
+ const snapshotModels = [...new Set([rootModelName, ...checked.referenced.models])];
1444
+ const snapshot = createLaunchSnapshot({ script, args, metadata: checked.metadata, launchMode: params.foreground ? "foreground" : "background", settings, settingsPath, settingsSources: { ...launch.resolution.sources, concurrency: params.concurrency === undefined ? launch.resolution.sources.concurrency : "per-run options" }, ...(Object.keys(modelAliases).length ? { modelAliases } : {}), ...(budget ? { budget } : {}), ...(checked.referenced.phases.length ? { phases: checked.referenced.phases } : {}), models: snapshotModels, tools: rootTools, agentConfigurations: {}, schemas: checked.schemas });
1467
1445
  const budgetRuntime = new WorkflowBudgetRuntime(budget);
1468
1446
  const initialBudget = budgetRuntime.snapshot();
1469
- const createRun = () => store.create({ id: runId, workflowName: checked.metadata.name, cwd: ctx.cwd, sessionId: ctx.sessionManager.getSessionId(), state: "running", ...(parentRunId !== undefined ? { parentRunId } : {}), agents: [], agentSessions: [], delivery: params.foreground ? { mode: "foreground", state: "attached", toolCallId } : { mode: "background", state: "pending" }, ...(budget ? { budget } : {}), budgetVersion: 1, ...initialBudget }, snapshot);
1447
+ const createRun = () => { assertLaunchActive(); return store.create({ id: runId, workflowName: checked.metadata.name, cwd: ctx.cwd, sessionId: ctx.sessionManager.getSessionId(), state: "running", ...(parentRunId !== undefined ? { parentRunId } : {}), agents: [], agentSessions: [], delivery: params.foreground ? { mode: "foreground", state: "attached", toolCallId } : { mode: "background", state: "pending" }, ...(budget ? { budget } : {}), budgetVersion: 1, ...initialBudget }, snapshot); };
1470
1448
  // Parent validation and persistence share the mutation lane so a manual deletion cannot remove the parent between the two.
1471
1449
  if (parentRunId !== undefined)
1472
1450
  await coordinateRunMutation(async () => { await store.validateParentRun(parentRunId); await createRun(); });
@@ -1494,7 +1472,7 @@ export default function workflowExtension(pi, home, clipboard = copyToClipboard,
1494
1472
  activeRun.foreground = false;
1495
1473
  delete activeRun.update;
1496
1474
  }
1497
- await store.saveSnapshot(createLaunchSnapshot({ ...capturedRoles.snapshot, launchMode: "background" }));
1475
+ await store.setLaunchMode("background");
1498
1476
  for (const checkpoint of await store.awaitingCheckpoints())
1499
1477
  deliverBackgroundCheckpoint(checked.metadata.name, runId, checkpoint);
1500
1478
  signal?.removeEventListener("abort", onForegroundAbort);
@@ -1511,13 +1489,13 @@ export default function workflowExtension(pi, home, clipboard = copyToClipboard,
1511
1489
  const providerPause = async () => { if (!foregroundAttached)
1512
1490
  deliver(pi, `Workflow ${checked.metadata.name} paused: provider limit.`); await lifecycle.providerPause(); };
1513
1491
  const providerErrorRecovery = createProviderErrorRecovery(ctx, availableModels, () => { runController.abort(); });
1514
- const executor = createAgentExecutor({ cwd: ctx.cwd, model: rootModel, tools: new Set(rootTools), resourceSelectors: launch.resourcePolicy.effective, extensionSettings: settings.extensionSettings, availableModels, knownModels, modelAliases, settingsPath, agentDefinitions, runStore: store, providerPause, agentResourcePolicy: frozenResourcePolicy(launch.resourcePolicy), runContext });
1492
+ const executor = createAgentExecutor({ ...executorRoot, runStore: store, providerPause, runContext });
1515
1493
  const runRecord = { executor, store, metadata: checked.metadata, model: rootModel, lifecycle, budget: budgetRuntime, abortController: runController, foreground: foregroundAttached, projectTrusted: () => projectTrusted(ctx), checkpointResolvers: new Map(), ...(providerErrorRecovery ? { providerErrorRecovery } : {}), ...(params.foreground && onUpdate ? { update: onUpdate } : {}) };
1516
1494
  runs.set(runId, runRecord);
1517
1495
  if (params.foreground && onUpdate)
1518
1496
  onUpdate(workflowToolUpdate((await store.load()).run));
1519
1497
  scheduler.addRun(runId, settings.concurrency, () => runs.get(runId)?.budget.checkAgentLaunch(), settings.extensionSettings);
1520
- const execution = runWorkflow(script, args, withWorkflowFunctions({ tool: (identifier, toolArgs, signal, identity) => toolForRun(store, lifecycle, toolContext, identifier, toolArgs, signal, identity), shell: (command, options, signal, identity) => shellForRun(store, checked.metadata, lifecycle, command, options, signal, identity), agent: workflowAgentHandler(store, checked.metadata, lifecycle, executor, ctx.cwd, runId, (role, model) => capturedRoles.capture(role, model)), worktree: async (owner) => resolveWorktree(store, checked.metadata, owner), checkpoint: checkpointBridge(runId, store, checked.metadata, () => runs.get(runId)?.foreground ?? foregroundAttached, ctx.hasUI ? ctx.ui : undefined, headless), phase: phaseBridge(store, checked.metadata, lifecycle), log: logBridge(store, lifecycle, checked.metadata.name) }, store, runContext, registry, settings.extensionSettings), runController.signal);
1498
+ const execution = runWorkflow(script, args, withWorkflowFunctions({ tool: (identifier, toolArgs, signal, identity) => toolForRun(store, lifecycle, toolContext, identifier, toolArgs, signal, identity), shell: (command, options, signal, identity) => shellForRun(store, checked.metadata, lifecycle, command, options, signal, identity), agent: workflowAgentHandler(store, checked.metadata, lifecycle, executor, ctx.cwd, runId), worktree: async (owner) => resolveWorktree(store, checked.metadata, owner), checkpoint: checkpointBridge(runId, store, checked.metadata, () => runs.get(runId)?.foreground ?? foregroundAttached, ctx.hasUI ? ctx.ui : undefined, headless), phase: phaseBridge(store, checked.metadata, lifecycle), log: logBridge(store, lifecycle, checked.metadata.name) }, store, runContext, registry, settings.extensionSettings), runController.signal);
1521
1499
  runRecord.execution = execution;
1522
1500
  await eventPublisher.runStarted(store, checked.metadata);
1523
1501
  const finish = execution.result.then(async (value) => {
@@ -1813,9 +1791,6 @@ async function handleTurnInput(store, handle, turn) {
1813
1791
  }
1814
1792
  return target;
1815
1793
  }
1816
- function displayAgentName(label, role, model) {
1817
- return label ?? role ?? model.model;
1818
- }
1819
1794
  function modelSpec(value, fallback) {
1820
1795
  try {
1821
1796
  const parsed = parseModelReference(value);
@@ -3,10 +3,9 @@ export * from "./package-identity.js";
3
3
  export { createToolTimingExtension, TOOL_TIMING_ENTRY_TYPE } from "./tool-timing.js";
4
4
  export type { ToolTimingEntry } from "./tool-timing.js";
5
5
  export * from "./utils.js";
6
- export { canonicalPath, sameFilesystemPath } from "./paths.js";
6
+ export { canonicalPath, extensionIdentity, sameFilesystemPath } from "./paths.js";
7
7
  export * from "./budget.js";
8
8
  export * from "./validation.js";
9
- export * from "./roles.js";
10
9
  export * from "./registry.js";
11
10
  export * from "./execution.js";
12
11
  export * from "./host.js";