@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
@@ -190,7 +190,6 @@ interface ReceiptAgent {
190
190
  name: string;
191
191
  state: string;
192
192
  model?: string;
193
- role?: string;
194
193
  requestedModel?: string;
195
194
  tools?: readonly string[];
196
195
  /** Accepted for rendering older receipt entries; new receipts persist granted tools. */
@@ -283,7 +282,7 @@ function optionalNumber(value: unknown): boolean {
283
282
  function renderableAgent(value: unknown): boolean {
284
283
  if (!object(value) || typeof value.name !== "string" || typeof value.state !== "string" || !object(value.model) || typeof value.model.model !== "string" || !Array.isArray(value.tools) || !value.tools.every((tool) => typeof tool === "string")) return false;
285
284
  if (value.model.thinking !== undefined && typeof value.model.thinking !== "string") return false;
286
- for (const key of ["id", "label", "parentId", "role", "requestedModel"] as const) if (value[key] !== undefined && typeof value[key] !== "string") return false;
285
+ for (const key of ["id", "label", "parentId", "requestedModel"] as const) if (value[key] !== undefined && typeof value[key] !== "string") return false;
287
286
  for (const key of ["attempts", "startedAt", "durationMs", "lastEventAt"] as const) if (!optionalNumber(value[key])) return false;
288
287
  const accounting = value.accounting;
289
288
  if (accounting !== undefined && (!object(accounting) || !["input", "output", "cacheRead", "cacheWrite", "cost"].every((key) => optionalNumber(accounting[key])))) return false;
@@ -695,8 +694,7 @@ function renderFrame(runs: readonly Run[], now: number, width: number, offset =
695
694
  : paint(silent >= STALL_MS ? ROLE.failed : ROLE.warn, "⚠")
696
695
  } ${
697
696
  // The label is what the workflow called this agent — `reviewer #2`,
698
- // `scout (api)` — where the name is the role it was built from. Two
699
- // agents of one role are otherwise indistinguishable.
697
+ // `scout (api)` — preserving an explicit label when supplied.
700
698
  agent.label ?? agent.name
701
699
  }${
702
700
  silent === undefined
@@ -848,18 +846,15 @@ function receiptFor(run: Run, fallback?: { message?: string }): Receipt {
848
846
  agents: agents.map((agent) => ({
849
847
  id: agent.id,
850
848
  ...(agent.parentId === undefined ? {} : { parentId: agent.parentId }),
851
- // What the workflow called it, falling back to the role it was built
852
- // from. Two agents of one role are otherwise indistinguishable.
849
+ // Preserve explicit display labels.
853
850
  name: agent.label ?? agent.name,
854
851
  state: agent.state,
855
- // Model and thinking level together: a role picks both, and the model
856
- // name alone does not say whether it reasoned cheaply or deeply.
852
+ // Show both model and thinking level.
857
853
  ...(agent.model.model
858
854
  ? {
859
855
  model: `${agent.model.model}${agent.model.thinking ? `:${agent.model.thinking}` : ""}`,
860
856
  }
861
857
  : {}),
862
- ...(agent.role ? { role: agent.role } : {}),
863
858
  ...(agent.requestedModel ? { requestedModel: agent.requestedModel } : {}),
864
859
  tools: [...agent.tools],
865
860
  // Cache reads dwarf real input and cost almost nothing, so the split is
@@ -947,7 +942,6 @@ export function renderReceipt(data: Receipt, expanded: boolean, theme: Theme): s
947
942
 
948
943
  const under = `${prefix}${lastAgent ? " " : "│ "} `;
949
944
  const meta = [
950
- agent.role ? `role ${agent.role}` : "",
951
945
  agent.requestedModel && agent.requestedModel !== agent.model
952
946
  ? `via ${agent.requestedModel}`
953
947
  : "",
package/src/decoders.ts CHANGED
@@ -1,10 +1,10 @@
1
- import { AGENT_STATES, BUDGET_DIMENSIONS, BUDGET_EVENT_TYPES, RUN_STATES, isContextFileScope, type AgentAccounting, type AgentActivity, type AgentAttemptSummary, type AgentContinuity, type AgentDefinition, type AgentRecord, type AgentResourceInspection, type AgentResourceSelectors, type BudgetApprovalRequest, type BudgetDimension, type BudgetEvent, type BudgetEventType, type ContextFileScope, type JsonValue, type LaunchSnapshot, type ModelSpec, type RunRecord, type WorkflowBudgetUsage, type WorkflowExtensionSettings, type WorkflowRetentionSettings, type WorkflowRunEvent } from "./types.js";
1
+ import { AGENT_STATES, BUDGET_DIMENSIONS, BUDGET_EVENT_TYPES, RUN_STATES, isContextFileScope, type AgentAccounting, type AgentActivity, type AgentAttemptSummary, type AgentContinuity, type PreparedAgentConfiguration, LAUNCH_SNAPSHOT_IDENTITY_VERSION, type AgentRecord, type AgentResourceInspection, type AgentResourceSelectors, type BudgetApprovalRequest, type BudgetDimension, type BudgetEvent, type BudgetEventType, type ContextFileScope, type JsonValue, type LaunchSnapshot, type ModelSpec, type RunRecord, type WorkflowBudgetUsage, type WorkflowExtensionSettings, type WorkflowRetentionSettings, type WorkflowRunEvent } from "./types.js";
2
2
  import type { OwnershipRecord, ScheduledAgentOptions } from "./agent-execution.js";
3
3
  import { finiteNumber, isThinkingLevel, isWorkflowErrorCode, jsonValue, object, positiveInteger, validWorkflowExtensionNamespace } from "./utils.js";
4
4
 
5
5
  export interface EffectiveSystemPrompt { sessionId: string; attempt: number; turn: number; sha256: string; prompt: string }
6
6
  export type PersistedRun = RunRecord;
7
- export interface RunSummaryAgent { id: string; name: string; label?: string; state: string; role?: string; attempts: number }
7
+ export interface RunSummaryAgent { id: string; name: string; label?: string; state: string; attempts: number }
8
8
  export interface RunSummaryArtifacts { runDirectory: string; statePath: string; journalPath: string; snapshotPath: string; workflowPath: string; resultPath: string; summaryPath: string }
9
9
  export interface RunSummary { schemaVersion: 1; runId: string; sessionId: string; workflowName: string; state: RunRecord["state"]; createdAt: string; updatedAt: string; terminalAt?: string; usage: WorkflowBudgetUsage; agents: readonly RunSummaryAgent[]; error?: RunRecord["error"]; failedAt?: string; replayablePaths: readonly string[]; incompletePaths: readonly string[]; artifacts: RunSummaryArtifacts }
10
10
  export interface CompletedOperation { path: string; value: JsonValue }
@@ -79,25 +79,19 @@ function decodeAgentResourceSelectors(value: unknown): AgentResourceSelectors |
79
79
  return { ...(skills === undefined ? {} : { skills }), ...(extensions === undefined ? {} : { extensions }), ...(tools === undefined ? {} : { tools }) };
80
80
  }
81
81
  function decodeContextFileScopes(value: unknown): ContextFileScope[] | undefined { return decodeArray(value, (scope) => isContextFileScope(scope) ? scope : undefined); }
82
- export function decodeAgentDefinition(value: unknown): AgentDefinition | undefined {
83
- if (!object(value)) return undefined;
84
- const prompt = optionalString(value.prompt);
85
- const description = optionalString(value.description);
86
- const model = optionalString(value.model);
87
- const thinking = value.thinking;
88
- const tools = value.tools === undefined ? undefined : decodeStringArray(value.tools);
89
- const skills = value.skills === undefined ? undefined : decodeStringArray(value.skills);
90
- const extensions = value.extensions === undefined ? undefined : decodeStringArray(value.extensions);
91
- const overrideSystemPrompt = optionalBoolean(value.overrideSystemPrompt);
82
+ export function decodePreparedAgentConfiguration(value: unknown): PreparedAgentConfiguration | undefined {
83
+ if (!object(value) || !jsonValue(value) || typeof value.projectTrusted !== "boolean" || typeof value.systemPromptAppend !== "string") return undefined;
84
+ const model = decodeModelSpec(value.model);
85
+ const tools = decodeStringArray(value.tools);
86
+ const skills = decodeStringArray(value.skills);
87
+ const extensions = decodeStringArray(value.extensions);
88
+ const settings = decodeWorkflowExtensions(value.settings);
92
89
  const contextFiles = value.contextFiles === undefined ? undefined : decodeContextFileScopes(value.contextFiles);
93
- const extensionSettings = value.extensionSettings === undefined ? undefined : decodeWorkflowExtensions(value.extensionSettings);
94
- if (prompt === INVALID_PERSISTED_VALUE || description === INVALID_PERSISTED_VALUE || model === INVALID_PERSISTED_VALUE || overrideSystemPrompt === INVALID_PERSISTED_VALUE) return undefined;
95
- if (thinking !== undefined && !isThinkingLevel(thinking) || value.tools !== undefined && !tools || value.skills !== undefined && !skills || value.extensions !== undefined && !extensions || value.contextFiles !== undefined && !contextFiles || value.extensionSettings !== undefined && !extensionSettings) return undefined;
96
- const foldedModel = typeof model === "string" && thinking !== undefined && !model.includes(":") ? `${model}:${thinking}` : model;
97
- return {
98
- ...(prompt === undefined ? {} : { prompt }), ...(description === undefined ? {} : { description }), ...(foldedModel === undefined ? {} : { model: foldedModel }),
99
- ...(tools === undefined ? {} : { tools }), ...(skills === undefined ? {} : { skills }), ...(extensions === undefined ? {} : { extensions }), ...(overrideSystemPrompt === undefined ? {} : { overrideSystemPrompt }), ...(contextFiles === undefined ? {} : { contextFiles }), ...(extensionSettings === undefined ? {} : { extensionSettings }),
100
- };
90
+ const label = optionalString(value.label);
91
+ const systemPrompt = optionalString(value.systemPrompt);
92
+ const excludeTools = value.excludeTools === undefined ? undefined : decodeStringArray(value.excludeTools);
93
+ if (!model || !tools || !skills || !extensions || !settings || label === INVALID_PERSISTED_VALUE || systemPrompt === INVALID_PERSISTED_VALUE || value.contextFiles !== undefined && !contextFiles || value.excludeTools !== undefined && !excludeTools) return undefined;
94
+ return { model, tools, skills, extensions, settings, projectTrusted: value.projectTrusted, systemPromptAppend: value.systemPromptAppend, ...(contextFiles === undefined ? {} : { contextFiles }), ...(excludeTools === undefined ? {} : { excludeTools }), ...(label === undefined ? {} : { label }), ...(systemPrompt === undefined ? {} : { systemPrompt }) };
101
95
  }
102
96
  function decodeWorkflowMetadata(value: unknown): LaunchSnapshot["metadata"] | undefined {
103
97
  if (!object(value) || typeof value.name !== "string") return undefined;
@@ -201,17 +195,22 @@ function decodeScheduledAgentOptions(value: unknown): PersistedOptions | undefin
201
195
  const parentBreadcrumb = optionalString(value.parentBreadcrumb);
202
196
  const worktreeOwner = optionalString(value.worktreeOwner);
203
197
  const model = optionalString(value.model);
204
- const role = typeof value.role === "string" ? value.role : undefined;
205
198
  const contextFiles = value.contextFiles === undefined ? undefined : decodeContextFileScopes(value.contextFiles);
206
199
  const schema = value.schema === undefined ? undefined : decodeJsonObject(value.schema);
207
200
  const retries = optionalNumber(value.retries);
208
201
  const timeoutMs = value.timeoutMs;
202
+ if (value.projectTrusted !== undefined && typeof value.projectTrusted !== "boolean") return undefined;
203
+ const rawCapabilities = value.capabilities;
204
+ const capabilities = rawCapabilities === undefined ? undefined : object(rawCapabilities) ? { tools: decodeStringArray(rawCapabilities.tools), skills: decodeStringArray(rawCapabilities.skills), extensions: decodeStringArray(rawCapabilities.extensions) } : undefined;
205
+ if (rawCapabilities !== undefined && (!capabilities?.tools || !capabilities.skills || !capabilities.extensions)) return undefined;
206
+ const configuration = value.configuration === undefined ? undefined : decodePreparedAgentConfiguration(value.configuration);
207
+ if (value.configuration !== undefined && configuration === undefined) return undefined;
209
208
  const agentOptions = value.agentOptions === undefined ? undefined : decodeJsonObject(value.agentOptions);
210
209
  const agentIdentity = value.agentIdentity === undefined ? undefined : decodeIdentity(value.agentIdentity);
211
210
  const sessionPath = optionalString(value.sessionPath);
212
211
  const continuity = value.continuity === undefined ? undefined : isAgentContinuity(value.continuity) ? value.continuity : INVALID_PERSISTED_VALUE;
213
212
  const extensionSettings = value.extensionSettings === undefined ? undefined : decodeWorkflowExtensions(value.extensionSettings);
214
- if (sessionPath === INVALID_PERSISTED_VALUE || continuity === INVALID_PERSISTED_VALUE || requestedLabel === INVALID_PERSISTED_VALUE || parentBreadcrumb === INVALID_PERSISTED_VALUE || worktreeOwner === INVALID_PERSISTED_VALUE || model === INVALID_PERSISTED_VALUE || value.skills !== undefined && !skills || value.extensions !== undefined && !extensions || (value.role !== undefined && typeof value.role !== "string") || (value.contextFiles !== undefined && !contextFiles) || (value.schema !== undefined && !schema) || retries === INVALID_PERSISTED_VALUE || (retries !== undefined && !integer(retries)) || (timeoutMs !== undefined && timeoutMs !== null && !finiteNumber(timeoutMs)) || (value.agentOptions !== undefined && !agentOptions) || (value.agentIdentity !== undefined && !agentIdentity) || (value.extensionSettings !== undefined && !extensionSettings)) return undefined;
213
+ if (sessionPath === INVALID_PERSISTED_VALUE || continuity === INVALID_PERSISTED_VALUE || requestedLabel === INVALID_PERSISTED_VALUE || parentBreadcrumb === INVALID_PERSISTED_VALUE || worktreeOwner === INVALID_PERSISTED_VALUE || model === INVALID_PERSISTED_VALUE || value.skills !== undefined && !skills || value.extensions !== undefined && !extensions || (value.contextFiles !== undefined && !contextFiles) || (value.schema !== undefined && !schema) || retries === INVALID_PERSISTED_VALUE || (retries !== undefined && !integer(retries)) || (timeoutMs !== undefined && timeoutMs !== null && !finiteNumber(timeoutMs)) || (value.agentOptions !== undefined && !agentOptions) || (value.agentIdentity !== undefined && !agentIdentity) || (value.extensionSettings !== undefined && !extensionSettings)) return undefined;
215
214
  return {
216
215
  label: value.label,
217
216
  ...(requestedLabel === undefined ? {} : { requestedLabel }),
@@ -221,7 +220,6 @@ function decodeScheduledAgentOptions(value: unknown): PersistedOptions | undefin
221
220
  ...(skills === undefined ? {} : { skills }), ...(extensions === undefined ? {} : { extensions }),
222
221
  ...(worktreeOwner === undefined ? {} : { worktreeOwner }),
223
222
  ...(model === undefined ? {} : { model }),
224
- ...(role === undefined ? {} : { role }),
225
223
  ...(contextFiles === undefined ? {} : { contextFiles }),
226
224
  ...(schema === undefined ? {} : { schema }),
227
225
  ...(retries === undefined ? {} : { retries }),
@@ -229,6 +227,9 @@ function decodeScheduledAgentOptions(value: unknown): PersistedOptions | undefin
229
227
  ...(sessionPath === undefined ? {} : { sessionPath }),
230
228
  ...(continuity === undefined ? {} : { continuity }),
231
229
  ...(agentOptions === undefined ? {} : { agentOptions }),
230
+ ...(configuration === undefined ? {} : { configuration }),
231
+ ...(value.projectTrusted === undefined ? {} : { projectTrusted: value.projectTrusted }),
232
+ ...(capabilities?.tools && capabilities.skills && capabilities.extensions ? { capabilities: { tools: capabilities.tools, skills: capabilities.skills, extensions: capabilities.extensions } } : {}),
232
233
  ...(agentIdentity === undefined ? {} : { agentIdentity }),
233
234
  ...(extensionSettings === undefined ? {} : { extensionSettings }),
234
235
  };
@@ -269,11 +270,10 @@ function decodeAgentResourceInspection(value: unknown): AgentResourceInspection
269
270
  const sources = sourceRecord === undefined ? undefined : {
270
271
  global: decodeAgentResourceSelectors(sourceRecord.global),
271
272
  project: decodeAgentResourceSelectors(sourceRecord.project),
272
- ...(sourceRecord.role === undefined ? {} : { role: decodeAgentResourceSelectors(sourceRecord.role) }),
273
273
  ...(sourceRecord.call === undefined ? {} : { call: decodeAgentResourceSelectors(sourceRecord.call) }),
274
274
  };
275
- if (!selectors || !skills || !extensions || !tools || !unmatchedSkills || !unmatchedExtensions || !unmatchedTools || rawSources !== undefined && (!sources || !sources.global || !sources.project || sources.role === undefined && sourceRecord?.role !== undefined || sources.call === undefined && sourceRecord?.call !== undefined)) return undefined;
276
- return { selectors: { skills: [...(selectors.skills ?? [])], extensions: [...(selectors.extensions ?? [])], tools: [...(selectors.tools ?? [])] }, skills, extensions, tools, unmatchedSkills, unmatchedExtensions, unmatchedTools, ...(sources === undefined ? {} : { selectorSources: { global: sources.global ?? {}, project: sources.project ?? {}, ...(sources.role === undefined ? {} : { role: sources.role }), ...(sources.call === undefined ? {} : { call: sources.call }) } }) };
275
+ if (!selectors || !skills || !extensions || !tools || !unmatchedSkills || !unmatchedExtensions || !unmatchedTools || rawSources !== undefined && (!sources || !sources.global || !sources.project || sources.call === undefined && sourceRecord?.call !== undefined)) return undefined;
276
+ return { selectors: { skills: [...(selectors.skills ?? [])], extensions: [...(selectors.extensions ?? [])], tools: [...(selectors.tools ?? [])] }, skills, extensions, tools, unmatchedSkills, unmatchedExtensions, unmatchedTools, ...(sources === undefined ? {} : { selectorSources: { global: sources.global ?? {}, project: sources.project ?? {}, ...(sources.call === undefined ? {} : { call: sources.call }) } }) };
277
277
  }
278
278
  function decodeAgentSetupSummary(value: unknown): NonNullable<NonNullable<AgentRecord["attemptDetails"]>[number]["setup"]> | undefined {
279
279
  if (!object(value) || typeof value.cwd !== "string") return undefined;
@@ -311,7 +311,6 @@ function decodeAgent(value: unknown): AgentRecord | undefined {
311
311
  const worktreeOwner = optionalString(value.worktreeOwner);
312
312
  const handle = optionalString(value.handle);
313
313
  const continuity = value.continuity === undefined ? undefined : isAgentContinuity(value.continuity) ? value.continuity : INVALID_PERSISTED_VALUE;
314
- const role = optionalString(value.role);
315
314
  const requestedModel = optionalString(value.requestedModel);
316
315
  const startedAt = optionalNumber(value.startedAt);
317
316
  const durationMs = optionalNumber(value.durationMs);
@@ -320,7 +319,7 @@ function decodeAgent(value: unknown): AgentRecord | undefined {
320
319
  const toolCalls = value.toolCalls === undefined ? undefined : decodeArray(value.toolCalls, decodeAgentToolCall);
321
320
  const activity = value.activity === undefined ? undefined : decodeAgentActivity(value.activity);
322
321
  const lastEventAt = optionalNumber(value.lastEventAt);
323
- if (systemPrompt === INVALID_PERSISTED_VALUE || prompt === INVALID_PERSISTED_VALUE || label === INVALID_PERSISTED_VALUE || parentId === INVALID_PERSISTED_VALUE || resultPath === INVALID_PERSISTED_VALUE || parentBreadcrumb === INVALID_PERSISTED_VALUE || worktreeOwner === INVALID_PERSISTED_VALUE || role === INVALID_PERSISTED_VALUE || requestedModel === INVALID_PERSISTED_VALUE || startedAt === INVALID_PERSISTED_VALUE || durationMs === INVALID_PERSISTED_VALUE || lastEventAt === INVALID_PERSISTED_VALUE || handle === INVALID_PERSISTED_VALUE || continuity === INVALID_PERSISTED_VALUE || handle !== undefined && !positiveInteger(value.turn)) return undefined;
322
+ if (systemPrompt === INVALID_PERSISTED_VALUE || prompt === INVALID_PERSISTED_VALUE || label === INVALID_PERSISTED_VALUE || parentId === INVALID_PERSISTED_VALUE || resultPath === INVALID_PERSISTED_VALUE || parentBreadcrumb === INVALID_PERSISTED_VALUE || worktreeOwner === INVALID_PERSISTED_VALUE || requestedModel === INVALID_PERSISTED_VALUE || startedAt === INVALID_PERSISTED_VALUE || durationMs === INVALID_PERSISTED_VALUE || lastEventAt === INVALID_PERSISTED_VALUE || handle === INVALID_PERSISTED_VALUE || continuity === INVALID_PERSISTED_VALUE || handle !== undefined && !positiveInteger(value.turn)) return undefined;
324
323
  if (!model || !tools || value.structuralPath !== undefined && !structuralPath || value.attemptDetails !== undefined && !attemptDetails || value.accounting !== undefined && !accounting || value.toolCalls !== undefined && !toolCalls || value.activity !== undefined && !activity) return undefined;
325
324
  return {
326
325
  ...(systemPrompt === undefined ? {} : { systemPrompt }),
@@ -337,7 +336,6 @@ function decodeAgent(value: unknown): AgentRecord | undefined {
337
336
  ...(worktreeOwner === undefined ? {} : { worktreeOwner }),
338
337
  ...(handle === undefined ? {} : { handle, turn: value.turn as number }),
339
338
  ...(continuity === undefined ? {} : { continuity }),
340
- ...(role === undefined ? {} : { role }),
341
339
  ...(requestedModel === undefined ? {} : { requestedModel }),
342
340
  model,
343
341
  tools,
@@ -486,13 +484,11 @@ export function decodeLaunchSnapshot(value: unknown): LaunchSnapshot | undefined
486
484
  const phases = value.phases === undefined ? undefined : decodeStringArray(value.phases);
487
485
  const models = decodeStringArray(value.models);
488
486
  const tools = decodeStringArray(value.tools);
489
- const agentTypes = decodeStringArray(value.agentTypes);
490
- const roles = value.roles === undefined ? undefined : decodeRecord(value.roles, decodeAgentDefinition);
491
- const projectRoles = value.projectRoles === undefined ? undefined : decodeStringArray(value.projectRoles);
487
+ const agentConfigurations = decodeRecord(value.agentConfigurations, decodePreparedAgentConfiguration);
492
488
  const schemas = decodeArray(value.schemas, decodeJsonObject);
493
- if (identityVersion === INVALID_PERSISTED_VALUE || value.identityVersion !== undefined && !integer(identityVersion) || launchMode !== undefined && !isLaunchMode(launchMode) || args === undefined || !metadata || !settings || value.settingsSources !== undefined && !settingsSources || value.budget !== undefined && !budget || settingsPath === INVALID_PERSISTED_VALUE || value.modelAliases !== undefined && !modelAliases || value.phases !== undefined && !phases || !models || !tools || !agentTypes || value.roles !== undefined && !roles || value.projectRoles !== undefined && !projectRoles || !schemas) return undefined;
489
+ if (identityVersion !== LAUNCH_SNAPSHOT_IDENTITY_VERSION || launchMode !== undefined && !isLaunchMode(launchMode) || args === undefined || !metadata || !settings || value.settingsSources !== undefined && !settingsSources || value.budget !== undefined && !budget || settingsPath === INVALID_PERSISTED_VALUE || value.modelAliases !== undefined && !modelAliases || value.phases !== undefined && !phases || !models || !tools || !agentConfigurations || !schemas) return undefined;
494
490
  return {
495
- ...(identityVersion === undefined ? {} : { identityVersion }),
491
+ identityVersion: LAUNCH_SNAPSHOT_IDENTITY_VERSION,
496
492
  ...(launchMode === undefined ? {} : { launchMode }),
497
493
  script: value.script,
498
494
  args,
@@ -505,9 +501,7 @@ export function decodeLaunchSnapshot(value: unknown): LaunchSnapshot | undefined
505
501
  ...(phases === undefined ? {} : { phases }),
506
502
  models,
507
503
  tools,
508
- agentTypes,
509
- ...(roles === undefined ? {} : { roles }),
510
- ...(projectRoles === undefined ? {} : { projectRoles }),
504
+ agentConfigurations,
511
505
  schemas,
512
506
  };
513
507
  }
package/src/execution.ts CHANGED
@@ -6,7 +6,7 @@ import { StringDecoder } from "node:string_decoder";
6
6
  import { RunStore, structuralPath as operationPath } from "./persistence.js";
7
7
  import type { AgentAttempt } from "./agent-execution.js";
8
8
  import type { AgentIdentity, AgentAttemptSummary, FunctionIdentity, JsonValue, ShellIdentity, ShellOptions, ShellResult, ToolIdentity, WorkflowAgentSessionReference, WorkflowBridge, WorkflowErrorCode, WorkflowExecution } from "./types.js";
9
- import { WorkflowError, roleNameOf, sumAccounting, zeroAccounting } from "./types.js";
9
+ import { WorkflowError, sumAccounting, zeroAccounting } from "./types.js";
10
10
  import { asWorkflowError, errorText, fail, isWorkflowAuthored, isWorkflowErrorCode, jsonDigest, jsonValue, markWorkflowAuthored, object, positiveInteger } from "./utils.js";
11
11
  import { instrumentWorkflow, validateAgentOptions, validateShellCommand, validateShellOptions } from "./validation.js";
12
12
 
@@ -41,6 +41,8 @@ function isRpcMessage(value: unknown): value is RpcMessage {
41
41
  const HEARTBEAT_INTERVAL_MS = 1000;
42
42
 
43
43
  const OUTCOME_ERRORS = new Set<string>(["AGENT_TIMEOUT", "AGENT_FAILED", "RESULT_INVALID"]);
44
+ /** Script handle names may not start with this: registered-function handles are journaled under it. */
45
+ export const FUNCTION_HANDLE_PREFIX = "function-handle";
44
46
  const WORK_RESULT_BRAND = "__workResult";
45
47
 
46
48
  const childSource = String.raw`
@@ -152,15 +154,13 @@ const internalAgent = (...values) => {
152
154
  void result.then(() => agentInflight.delete(occurrenceKey), () => agentInflight.delete(occurrenceKey));
153
155
  return guardedAgentResult(result);
154
156
  };
155
- const AGENT_HANDLE_KEYS = ["name", "role", "model", "tools", "skills", "extensions", "contextFiles", "label"];
156
157
  const agentHandleNames = new Set();
157
158
  const agentHandleInflight = new Set();
158
159
  const internalAgentCreate = (...values) => {
159
160
  if (values.length !== 1 || !values[0] || typeof values[0] !== "object" || Array.isArray(values[0])) throw workError("INVALID_METADATA", "agent.create requires one options object");
160
161
  const options = values[0];
161
162
  const name = named(options.name, "agent.create");
162
- const unsupported = Object.keys(options).find(key => !AGENT_HANDLE_KEYS.includes(key));
163
- if (unsupported) throw workError("INVALID_METADATA", "agent.create option is unsupported: " + unsupported);
163
+ if (name.startsWith("${FUNCTION_HANDLE_PREFIX}/")) throw workError("INVALID_METADATA", "Agent handle names may not start with ${FUNCTION_HANDLE_PREFIX}/");
164
164
  if (agentHandleNames.has(name)) throw workError("INVALID_METADATA", "Duplicate agent handle name: " + name);
165
165
  agentHandleNames.add(name);
166
166
  const config = Object.freeze(Object.fromEntries(Object.entries(options).filter(([key]) => key !== "name")));
@@ -584,7 +584,7 @@ export function runWorkflow(script: string, args: JsonValue = null, bridge: Work
584
584
  const opts = validateAgentOptions(values[1]);
585
585
  const identity = readAgentIdentity(values[2]);
586
586
  const path = agentIdentityPath(identity);
587
- const label = typeof opts.label === "string" ? opts.label : roleNameOf(opts.role) ?? "agent";
587
+ const label = typeof opts.label === "string" ? opts.label : "agent";
588
588
  try {
589
589
  const result = await bridge.agent(values[0], opts, controller.signal, identity);
590
590
  value = branded({ name: label, ok: true, value: result ?? null });
@@ -95,9 +95,9 @@ const WORKFLOW_ERROR_PROSE: Record<WorkflowErrorCode, (detail: string) => string
95
95
  REGISTRY_FROZEN: (detail) => `Workflow extension registration is closed: ${detail}.`,
96
96
  GLOBAL_COLLISION: (detail) => `The workflow global name is already in use: ${detail}.`,
97
97
  MISSING_WORKFLOW: (detail) => `The registered workflow function is unavailable: ${detail}.`,
98
- UNKNOWN_MODEL: (detail) => `The workflow requested the unavailable model ${detail.replace(/^(?:Unknown model(?: for role [^:]+)?|Invalid model spec):\s*/, "")}.`,
98
+ UNKNOWN_MODEL: (detail) => `The workflow requested the unavailable model ${detail.replace(/^(?:Unknown model|Invalid model spec|Required model is unavailable):\s*/, "")}.`,
99
99
  UNKNOWN_TOOL: (detail) => `The workflow requested the unavailable tool ${detail.replace(/^Unknown tool:\s*/, "")}.`,
100
- UNKNOWN_AGENT_TYPE: (detail) => `The workflow requested the unavailable agent role ${detail.replace(/^Unknown agent role:\s*/, "")}.`,
100
+ UNKNOWN_AGENT: (detail) => `The workflow requested an unavailable owned agent: ${detail}.`,
101
101
  RUN_OWNED: (detail) => /already owned|active ownership/.test(detail) ? "The workflow session is already in use." : `The workflow session is already in use: ${detail}.`,
102
102
  RUN_NOT_FOUND: (detail) => /^Unknown workflow run\b/.test(detail) ? "The workflow run was not found." : `The workflow run was not found: ${detail}.`,
103
103
  RPC_LIMIT_EXCEEDED: (detail) => `The workflow communication data exceeded its size limit: ${detail}.`,
@@ -213,7 +213,6 @@ function boundedWorkflowFailureDiagnostics(value: WorkflowFailureDiagnostics): W
213
213
  ...(value.failedAgent ? { failedAgent: {
214
214
  id: utf8Prefix(value.failedAgent.id, 128),
215
215
  ...(value.failedAgent.label ? { label: utf8Prefix(value.failedAgent.label, 128) } : {}),
216
- ...(value.failedAgent.role ? { role: utf8Prefix(value.failedAgent.role, 128) } : {}),
217
216
  structuralPath: value.failedAgent.structuralPath.slice(0, 8).map((part) => utf8Prefix(part, 128)),
218
217
  attempt: value.failedAgent.attempt,
219
218
  ...(value.failedAgent.transport ? { transport: utf8Prefix(value.failedAgent.transport, 128) } : {}),
@@ -222,7 +221,6 @@ function boundedWorkflowFailureDiagnostics(value: WorkflowFailureDiagnostics): W
222
221
  completedSiblingAgents: (value.completedSiblingAgents ?? []).slice(0, 16).map((agent) => ({
223
222
  id: utf8Prefix(agent.id, 128),
224
223
  ...(agent.label ? { label: utf8Prefix(agent.label, 128) } : {}),
225
- ...(agent.role ? { role: utf8Prefix(agent.role, 128) } : {}),
226
224
  structuralPath: agent.structuralPath.slice(0, 8).map((part) => utf8Prefix(part, 128)),
227
225
  })),
228
226
  completedSiblingPaths: value.completedSiblingPaths.slice(0, 16).map((path) => path.slice(0, 8).map((part) => utf8Prefix(part, 128))),
@@ -278,7 +276,6 @@ export async function createWorkflowFailureDiagnostics(store: RunStore, metadata
278
276
  const failedAgent = failedAgentRecord ? {
279
277
  id: failedAgentRecord.id,
280
278
  ...(failedAgentRecord.label ?? failedAgentRecord.name ? { label: failedAgentRecord.label ?? failedAgentRecord.name } : {}),
281
- ...(failedAgentRecord.role ? { role: failedAgentRecord.role } : {}),
282
279
  structuralPath: [...(failedAgentRecord.structuralPath ?? [])],
283
280
  attempt: Math.max(1, failedAttempt?.attempt ?? failedAgentRecord.attempts),
284
281
  ...(failedAttempt?.transport ? { transport: failedAttempt.transport } : {}),
@@ -290,7 +287,6 @@ export async function createWorkflowFailureDiagnostics(store: RunStore, metadata
290
287
  }).map((agent) => ({
291
288
  id: agent.id,
292
289
  ...(agent.label ?? agent.name ? { label: agent.label ?? agent.name } : {}),
293
- ...(agent.role ? { role: agent.role } : {}),
294
290
  structuralPath: [...(agent.structuralPath ?? [])],
295
291
  } satisfies WorkflowSiblingAgent));
296
292
  const completedSiblingPaths = completedSiblingAgents.map((agent) => [...agent.structuralPath]);
@@ -316,9 +312,9 @@ export async function createWorkflowFailureDiagnostics(store: RunStore, metadata
316
312
  }
317
313
 
318
314
  export function formatWorkflowFailureDiagnostics(diagnostic: WorkflowFailureDiagnostics): string {
319
- const failedAgent = diagnostic.failedAgent ? `${diagnostic.failedAgent.label ?? diagnostic.failedAgent.id}${diagnostic.failedAgent.role ? ` role=${diagnostic.failedAgent.role}` : ""} attempt=${String(diagnostic.failedAgent.attempt)} path=${diagnostic.failedAgent.structuralPath.join(" > ") || "(root)"}${diagnostic.failedAgent.session ? ` session=${diagnostic.failedAgent.session.transport}/${diagnostic.failedAgent.session.sessionId}` : ""}` : "(not persisted)";
315
+ const failedAgent = diagnostic.failedAgent ? `${diagnostic.failedAgent.label ?? diagnostic.failedAgent.id} attempt=${String(diagnostic.failedAgent.attempt)} path=${diagnostic.failedAgent.structuralPath.join(" > ") || "(root)"}${diagnostic.failedAgent.session ? ` session=${diagnostic.failedAgent.session.transport}/${diagnostic.failedAgent.session.sessionId}` : ""}` : "(not persisted)";
320
316
  const siblingAgents = diagnostic.completedSiblingAgents;
321
- const siblings = siblingAgents ? siblingAgents.map((agent) => `${agent.label ?? agent.id}${agent.role ? ` role=${agent.role}` : ""} path=${agent.structuralPath.join(" > ") || "(root)"}`).join(", ") || "(none)" : diagnostic.completedSiblingPaths.map((path) => path.join(" > ") || "(root)").join(", ") || "(none)";
317
+ const siblings = siblingAgents ? siblingAgents.map((agent) => `${agent.label ?? agent.id} path=${agent.structuralPath.join(" > ") || "(root)"}`).join(", ") || "(none)" : diagnostic.completedSiblingPaths.map((path) => path.join(" > ") || "(root)").join(", ") || "(none)";
322
318
  const retry = diagnostic.retry ? [` Retry: ${diagnostic.retry.action}`, ` Replayable completed paths: ${diagnostic.retry.completedPaths.join(", ") || "(none)"}`, ` Incomplete paths: ${diagnostic.retry.incompletePaths.join(", ") || "(unknown)"}`, ` Named worktrees: ${diagnostic.retry.namedWorktrees.join(", ") || "(none)"}`, ` Warning: ${diagnostic.retry.warning}`] : [];
323
319
  return [`✗ Workflow: ${diagnostic.workflowName}`, ` Run: ${diagnostic.runId}`, ` State: ${diagnostic.state}`, ` Error: ${diagnostic.error.code}: ${diagnostic.error.message}`, ` Failed at: ${diagnostic.failedAt ?? "(unknown)"}`, ` Failed agent: ${failedAgent}`, ` Completed sibling ${siblingAgents ? "agents" : "paths"}: ${siblings}`, ...retry, ` Artifacts: state=${diagnostic.artifacts.statePath} journal=${diagnostic.artifacts.journalPath}`].join("\n");
324
320
  }
@@ -700,9 +700,10 @@ export function registerWorkflowNavigator(deps: WorkflowNavigatorDependencies):
700
700
  const styles = themeWorkflowProgressStyles(theme);
701
701
  const agent = selectedAgentRecord();
702
702
  const actions = actionMode ? { title: agent ? "Agent actions" : "Run actions", options: actionOptions(), index: actionIndex } : undefined;
703
- const phaseLines = formatWorkflowPhaseDashboard(view.run, view.snapshot, width, { nodeId: selectedNodeId, expandedNodeIds: [...expandedNodeIds], ...(narrow && !detailsMode && !actionMode ? { treeOnly: true } : {}), ...(narrow && (detailsMode || actionMode) ? { detailsOnly: true } : {}), ...(actions ? { actions } : {}) }, styles, progressNow());
703
+ const formatPhaseLines = (scrollTop: number): string[] => formatWorkflowPhaseDashboard(view.run, view.snapshot, width, { scrollTop, nodeId: selectedNodeId, expandedNodeIds: [...expandedNodeIds], ...(narrow && !detailsMode && !actionMode ? { treeOnly: true } : {}), ...(narrow && (detailsMode || actionMode) ? { detailsOnly: true } : {}), ...(actions ? { actions } : {}) }, styles, progressNow());
704
+ const phaseLines = formatPhaseLines(0);
704
705
  const statusLines = stopStatus ? truncateToVisualLines(styles.error(stopStatus), Number.MAX_SAFE_INTEGER, width, 0).visualLines.map((line) => line.trimEnd()) : [];
705
- const content = [...statusLines, ...phaseLines];
706
+ let content = [...statusLines, ...phaseLines];
706
707
  const rows = terminalRows();
707
708
  const hintRows = rows >= 3 ? 1 : 0;
708
709
  const confirmRows = inlineConfirm.rows(width, styles);
@@ -725,6 +726,7 @@ export function registerWorkflowNavigator(deps: WorkflowNavigatorDependencies):
725
726
  selectionNeedsScroll = false;
726
727
  }
727
728
  dashboardOffset = Math.max(0, Math.min(maxOffset, dashboardOffset));
729
+ if (!narrow && !actionMode) content = [...statusLines, ...formatPhaseLines(Math.max(0, dashboardOffset - statusLines.length))];
728
730
  const selectedNode = selectedNodeId ? tree.byId.get(selectedNodeId) : undefined;
729
731
  const enterAction = selectedNode?.kind === "workflow" ? "run actions" : selectedNode?.kind === "agent" ? "agent actions" : selectedNode?.children.length ? "expand/collapse" : narrow ? "inspect" : "focus details";
730
732
  const hint = truncateToVisualLines(theme.fg("dim", inlineConfirm.active() ? inlineConfirm.hint(keybindings) : actionMode ? `${keyLabel("tui.select.up", "↑")}/${keyLabel("tui.select.down", "↓")} actions · ${keyLabel("tui.select.confirm", "enter")} run · ${keyLabel("tui.editor.cursorLeft", "←")} tree · ${keyLabel("tui.select.cancel", "esc")} tree` : `${keyLabel("tui.select.up", "↑")}/${keyLabel("tui.select.down", "↓")} tree · ${keyLabel("tui.editor.cursorLeft", "←")}/${keyLabel("tui.editor.cursorRight", "→")} collapse/expand · ${keyLabel("tui.select.confirm", "enter")} ${enterAction} · a actions · ${keyLabel("tui.select.cancel", "esc")} ${narrow && detailsMode ? "tree" : "back"}${content.length > viewport ? ` · ${keyLabel("tui.select.pageUp", "pgup")}/${keyLabel("tui.select.pageDown", "pgdn")} scroll` : ""} · auto-refresh 1s`), Number.MAX_SAFE_INTEGER, width, 1).visualLines[0] ?? "";
@@ -90,7 +90,7 @@ export function buildWorkflowPhaseModel(run: WorkflowPhaseRun, source?: Workflow
90
90
  if (unassignedAgents.length) result.unassignedAgents = unassignedAgents;
91
91
  return result;
92
92
  }
93
- export interface WorkflowPhaseSelection { phaseId?: string | undefined; agentId?: string | undefined; nodeId?: string | undefined; expandedNodeIds?: readonly string[] | undefined; treeOnly?: boolean | undefined; detailsOnly?: boolean | undefined; actions?: { title: string; options: readonly string[]; index: number } | undefined }
93
+ export interface WorkflowPhaseSelection { phaseId?: string | undefined; agentId?: string | undefined; nodeId?: string | undefined; expandedNodeIds?: readonly string[] | undefined; treeOnly?: boolean | undefined; detailsOnly?: boolean | undefined; scrollTop?: number | undefined; actions?: { title: string; options: readonly string[]; index: number } | undefined }
94
94
  export type WorkflowPhaseTreeNodeKind = "workflow" | "phase" | "operation" | "agent" | "shell";
95
95
  export interface WorkflowPhaseTreeNode { id: string; kind: WorkflowPhaseTreeNodeKind; label: string; depth: number; phaseId: string; operationPath: readonly string[]; parentId?: string; children: readonly string[]; state: WorkflowPhaseState | RunState | AgentRecord["state"]; agentId?: string; agent?: AgentRecord; phase?: WorkflowPhaseView; shellActivity?: WorkflowPhaseShellActivity }
96
96
  export interface WorkflowPhaseTree { roots: readonly string[]; nodes: readonly WorkflowPhaseTreeNode[]; byId: ReadonlyMap<string, WorkflowPhaseTreeNode> }
@@ -1,7 +1,7 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import type { Api, Model } from "@earendil-works/pi-ai";
3
3
  import { type ExtensionAPI, type ExtensionToolContext } from "@earendil-works/pi-coding-agent";
4
- import { FairAgentScheduler, WorkflowAgentExecutor, type AgentDefinition, type AgentExecutionRoot, type AgentProviderFailure, type AgentProviderRecovery } from "./agent-execution.js";
4
+ import { FairAgentScheduler, WorkflowAgentExecutor, type AgentExecutionRoot, type AgentProviderFailure, type AgentProviderRecovery } from "./agent-execution.js";
5
5
  import { listRunIds, RunStore, structuralPath as operationPath, type PersistedRun } from "./persistence.js";
6
6
  import { budgetUsage, budgetRelaxed, mergeBudget, resumeBudgetAllowed, validateBudget, validateBudgetPatch, WorkflowBudgetRuntime } from "./budget.js";
7
7
  import { aliasDrift, createLaunchSnapshot, errorCode, errorText, jsonValue, object } from "./utils.js";
@@ -13,7 +13,7 @@ import { createWorkflowFailureDiagnostics, formatWorkflowFailureDelivery, format
13
13
 
14
14
  /** The subset of Pi's model registry the host relies on; every member is optional because older or headless hosts expose fewer of them. */
15
15
  export type ModelRegistryCapability = { getAll?: () => readonly Model<Api>[]; getAvailable?: () => readonly Model<Api>[]; find?: (provider: string, model: string) => Model<Api> | undefined; refresh?: () => Promise<void>; getError?: () => string | undefined };
16
- export type WorkflowRecoveryContext = { model: { provider: string; id: string } | undefined; modelRegistry: ModelRegistryCapability | undefined; deliveryContext: CompletionDeliveryContext; toolContext?: ExtensionToolContext | undefined; signal?: AbortSignal; resolvedAliases?: Readonly<Record<string, string>>; blockedAliases?: ReadonlySet<string>; blockedAliasTargets?: Readonly<Record<string, string>> };
16
+ export type WorkflowRecoveryContext = { model: { provider: string; id: string } | undefined; modelRegistry: ModelRegistryCapability | undefined; deliveryContext: CompletionDeliveryContext; toolContext?: ExtensionToolContext | undefined; signal?: AbortSignal; resolvedAliases?: Readonly<Record<string, string>>; resolvedDynamicAliasNames?: readonly string[]; blockedAliases?: ReadonlySet<string>; blockedAliasTargets?: Readonly<Record<string, string>> };
17
17
  export type WorkflowRecoveryDependencies = {
18
18
  pi: Pick<ExtensionAPI, "getThinkingLevel">;
19
19
  home: string | undefined;
@@ -28,9 +28,8 @@ export type WorkflowRecoveryDependencies = {
28
28
  createAgentExecutor: (root: Omit<AgentExecutionRoot, "agentDir" | "agentSetupHooks">) => WorkflowAgentExecutor;
29
29
  activeSnapshotTools: (tools: readonly string[], active: ReadonlySet<string> | "session") => Set<string>;
30
30
  frozenResourcePolicy: (policy: AgentResourcePolicy) => () => AgentResourcePolicy;
31
- resolveLaunchPrologue: (input: { snapshot: Readonly<LaunchSnapshot>; cwd: string; trustedProject: boolean; rootModel: ModelSpec; modelRegistry?: WorkflowRecoveryContext["modelRegistry"]; signal: AbortSignal; resolvedAliases?: Readonly<Record<string, string>>; blockedAliases?: ReadonlySet<string>; blockedAliasTargets?: Readonly<Record<string, string>>; withPreflight: boolean }) => Promise<{ active: Set<string>; settingsPath: string; currentPolicy: AgentResourcePolicy; previousAliases: Readonly<Record<string, string>>; knownModels: ReadonlySet<string>; availableModels: ReadonlySet<string>; currentAliases: Readonly<Record<string, string>>; blockedAliases: ReadonlySet<string>; blockedAliasTargets: Readonly<Record<string, string>>; snapshot: Readonly<LaunchSnapshot>; script: string | undefined }>;
32
- resumeRoles: (store: RunStore, snapshot: Readonly<LaunchSnapshot>, cwd: string, trustedProject: boolean) => { definitions: Readonly<Record<string, AgentDefinition>>; capture: (role: string, model: ModelSpec) => Promise<void> };
33
- workflowAgentHandler: (store: RunStore, metadata: WorkflowMetadata, lifecycle: RunLifecycle, executor: WorkflowAgentExecutor, cwd: string, runId: string, captureRole?: (role: string, model: ModelSpec) => Promise<void>) => (prompt: string, options: Readonly<Record<string, JsonValue>>, signal: AbortSignal, identity: AgentIdentity) => Promise<JsonValue>;
31
+ resolveLaunchPrologue: (input: { snapshot: Readonly<LaunchSnapshot>; cwd: string; trustedProject: boolean; rootModel: ModelSpec; modelRegistry?: WorkflowRecoveryContext["modelRegistry"]; signal: AbortSignal; resolvedAliases?: Readonly<Record<string, string>>; resolvedDynamicAliasNames?: readonly string[]; blockedAliases?: ReadonlySet<string>; blockedAliasTargets?: Readonly<Record<string, string>>; withPreflight: boolean }) => Promise<{ active: Set<string>; settingsPath: string; currentPolicy: AgentResourcePolicy; previousAliases: Readonly<Record<string, string>>; knownModels: ReadonlySet<string>; availableModels: ReadonlySet<string>; currentAliases: Readonly<Record<string, string>>; dynamicAliasNames: readonly string[]; blockedAliases: ReadonlySet<string>; blockedAliasTargets: Readonly<Record<string, string>>; snapshot: Readonly<LaunchSnapshot>; script: string | undefined }>;
32
+ workflowAgentHandler: (store: RunStore, metadata: WorkflowMetadata, lifecycle: RunLifecycle, executor: WorkflowAgentExecutor, cwd: string, runId: string) => (prompt: string, options: Readonly<Record<string, JsonValue>>, signal: AbortSignal, identity: AgentIdentity) => Promise<JsonValue>;
34
33
  shellForRun: (store: RunStore, metadata: WorkflowMetadata, lifecycle: RunLifecycle, command: string, options: ShellOptions, signal: AbortSignal, identity: ShellIdentity) => Promise<ShellResult>;
35
34
  toolForRun: (store: RunStore, lifecycle: RunLifecycle, context: ExtensionToolContext | undefined, identifier: string, args: Readonly<Record<string, JsonValue>>, signal: AbortSignal, identity: ToolIdentity) => Promise<JsonValue>;
36
35
  resolveWorktree: (store: RunStore, metadata: WorkflowMetadata, owner: string) => Promise<Readonly<WorkflowWorktreeReference>>;
@@ -61,13 +60,9 @@ function workflowRecoveryGuidance(action: "resume" | "retry", state: RunState):
61
60
  if (state === "interrupted") return "Interrupted workflow runs must be resumed from the interactive /workflow picker";
62
61
  return `Only failed workflow runs can be retried; source is ${state}`;
63
62
  }
64
- /** A snapshot can only be re-executed when it carries the current identity scheme and every role its script references. */
65
- function assertResumableSnapshot(snapshot: Readonly<LaunchSnapshot>, trustedProject: boolean): void {
63
+ /** A snapshot can only be re-executed when it carries the current identity scheme and validated concrete agent configurations. */
64
+ function assertResumableSnapshot(snapshot: Readonly<LaunchSnapshot>): void {
66
65
  if (snapshot.identityVersion !== LAUNCH_SNAPSHOT_IDENTITY_VERSION) throw new WorkflowError("RESUME_INCOMPATIBLE", "Workflow launch snapshot identity version is incompatible");
67
- if (snapshot.roles === undefined) throw new WorkflowError("RESUME_INCOMPATIBLE", "Workflow role definitions are missing from the launch snapshot");
68
- if ((snapshot.projectRoles?.length ?? 0) > 0 && !trustedProject) throw new WorkflowError("RESUME_INCOMPATIBLE", "Cannot restore project roles in an untrusted project");
69
- const missingRole = snapshot.agentTypes.find((role) => !snapshot.roles?.[role]);
70
- if (missingRole) throw new WorkflowError("RESUME_INCOMPATIBLE", `Role definition is missing from the launch snapshot: ${missingRole}`);
71
66
  }
72
67
  function assertExpectedWorkflowState(expectedState: string | undefined, actualState: RunState): void {
73
68
  if (expectedState !== undefined && expectedState !== actualState) throw new WorkflowError("RESUME_INCOMPATIBLE", `Workflow run state changed: expected state ${expectedState}, actual state ${actualState}`);
@@ -75,7 +70,7 @@ function assertExpectedWorkflowState(expectedState: string | undefined, actualSt
75
70
  export function persistedFailure(run: PersistedRun, error: WorkflowError): PersistedRun { const failedAt = workflowFailedAt(error); return { ...run, error: { code: error.code, message: error.message, ...(failedAt ? { failedAt } : {}) }, ...(failedAt ? { failedAt } : {}) }; }
76
71
 
77
72
  export function createWorkflowRecovery(deps: WorkflowRecoveryDependencies) {
78
- const { pi, home, runs, scheduler, eventPublisher, persistRunState, projectTrusted, resumeHostContext, ensureSessionLease, createAgentExecutor, activeSnapshotTools, frozenResourcePolicy, resolveLaunchPrologue, resumeRoles, workflowAgentHandler, shellForRun, toolForRun, resolveWorktree, checkpointBridge, phaseBridge, logBridge, lifecycleFor, createProviderErrorRecovery, cleanupTerminalRun, deliver, deliverTerminal, workflowToolUpdate, registry, modelSpec } = deps;
73
+ const { pi, home, runs, scheduler, eventPublisher, persistRunState, projectTrusted, resumeHostContext, ensureSessionLease, createAgentExecutor, activeSnapshotTools, frozenResourcePolicy, resolveLaunchPrologue, workflowAgentHandler, shellForRun, toolForRun, resolveWorktree, checkpointBridge, phaseBridge, logBridge, lifecycleFor, createProviderErrorRecovery, cleanupTerminalRun, deliver, deliverTerminal, workflowToolUpdate, registry, modelSpec } = deps;
79
74
  const coordinateRunMutation = deps.coordinateRunMutation ?? (<T>(task: () => Promise<T>): Promise<T> => task());
80
75
  type BudgetDecisionResult = { state: "running" | "completed" | "budget_exhausted"; approved: boolean; value?: JsonValue; run?: PersistedRun; completion?: CompletionDeliveryResult };
81
76
  const budgetDecisionDelivery = (metadata: WorkflowMetadata, request: BudgetApprovalRequest) => `Workflow ${metadata.name} budget adjustment ${request.proposalId} for run ${request.runId} requires approval. Consumed usage: ${JSON.stringify(request.consumed)}. Previous limits: ${JSON.stringify(request.previous)}. Proposed limits: ${JSON.stringify(request.proposed)}. Respond with workflow_respond using proposalId ${request.proposalId}.`;
@@ -97,11 +92,11 @@ export function createWorkflowRecovery(deps: WorkflowRecoveryDependencies) {
97
92
  const loaded = await run.store.load();
98
93
  const trustedProject = context?.projectTrusted ?? run.projectTrusted();
99
94
  const rootModel = context?.model ? { ...run.model, provider: context.model.provider, model: context.model.id } : run.model;
100
- const { settingsPath, currentPolicy, previousAliases, knownModels, availableModels, currentAliases, blockedAliases, blockedAliasTargets, snapshot } = await resolveLaunchPrologue({ snapshot: loaded.snapshot, cwd: run.store.cwd, trustedProject, rootModel, ...(context?.modelRegistry ? { modelRegistry: context.modelRegistry } : {}), signal: run.abortController.signal, withPreflight: false });
95
+ const { settingsPath, currentPolicy, previousAliases, knownModels, availableModels, currentAliases, dynamicAliasNames, blockedAliases, blockedAliasTargets, snapshot } = await resolveLaunchPrologue({ snapshot: loaded.snapshot, cwd: run.store.cwd, trustedProject, rootModel, ...(context?.modelRegistry ? { modelRegistry: context.modelRegistry } : {}), signal: run.abortController.signal, withPreflight: false });
101
96
  await run.store.saveSnapshot(snapshot);
102
97
  scheduler.updateRunLimit(run.store.runId, snapshot.settings.concurrency);
103
98
  scheduler.setRunExtensionSettings(run.store.runId, snapshot.settings.extensionSettings);
104
- run.executor = createAgentExecutor({ cwd: run.store.cwd, model: run.model, tools: activeSnapshotTools(snapshot.tools, "session"), resourceSelectors: currentPolicy.effective, extensionSettings: snapshot.settings.extensionSettings, availableModels, knownModels, modelAliases: currentAliases, blockedAliases, blockedAliasTargets, settingsPath, agentDefinitions: resumeRoles(run.store, snapshot, run.store.cwd, trustedProject).definitions, runStore: run.store, providerPause: async () => { deliver(`Workflow ${snapshot.metadata.name} paused: provider limit.`); await run.lifecycle.providerPause(); }, agentResourcePolicy: frozenResourcePolicy(currentPolicy) });
99
+ run.executor = createAgentExecutor({ cwd: run.store.cwd, projectTrusted: currentPolicy.projectTrusted, model: run.model, tools: activeSnapshotTools(snapshot.tools, "session"), resourceSelectors: currentPolicy.effective, extensionSettings: snapshot.settings.extensionSettings, availableModels, knownModels, modelAliases: currentAliases, dynamicModelAliasNames: dynamicAliasNames, blockedAliases, blockedAliasTargets, settingsPath, runStore: run.store, providerPause: async () => { deliver(`Workflow ${snapshot.metadata.name} paused: provider limit.`); await run.lifecycle.providerPause(); }, agentResourcePolicy: frozenResourcePolicy(currentPolicy) });
105
100
  run.executor.setRunContext(workflowRunContext(run.store.cwd, run.store.sessionId, run.store.runId, loaded.snapshot.metadata, loaded.snapshot.args, run.abortController.signal));
106
101
  const drift = aliasDrift(previousAliases, currentAliases);
107
102
  if (drift.length) await run.store.appendEvent({ type: "warning", message: `Model alias mappings changed on resume: ${drift.join("; ")}` });
@@ -112,33 +107,32 @@ export function createWorkflowRecovery(deps: WorkflowRecoveryDependencies) {
112
107
  return { hasUI: host?.hasUI === true, ui };
113
108
  };
114
109
  type ColdResumeResult = { value: JsonValue; resultPath: string; resultBytes: number; completion: CompletionDeliveryResult };
115
- const coldResumeRun = async (run: WorkflowRunRecord, hasUI: boolean, ui: { select?: (prompt: string, options: string[]) => Promise<string | undefined> }, trustedProject: boolean, context?: { model: { provider: string; id: string } | undefined; modelRegistry: WorkflowRecoveryContext["modelRegistry"]; deliveryContext: CompletionDeliveryContext; toolContext?: ExtensionToolContext | undefined; signal?: AbortSignal | undefined; resolvedAliases?: Readonly<Record<string, string>>; blockedAliases?: ReadonlySet<string>; blockedAliasTargets?: Readonly<Record<string, string>> }, modeOverride?: boolean, waitForCompletion = true): Promise<ColdResumeResult | undefined> => {
110
+ const coldResumeRun = async (run: WorkflowRunRecord, hasUI: boolean, ui: { select?: (prompt: string, options: string[]) => Promise<string | undefined> }, trustedProject: boolean, context?: { model: { provider: string; id: string } | undefined; modelRegistry: WorkflowRecoveryContext["modelRegistry"]; deliveryContext: CompletionDeliveryContext; toolContext?: ExtensionToolContext | undefined; signal?: AbortSignal | undefined; resolvedAliases?: Readonly<Record<string, string>>; resolvedDynamicAliasNames?: readonly string[]; blockedAliases?: ReadonlySet<string>; blockedAliasTargets?: Readonly<Record<string, string>> }, modeOverride?: boolean, waitForCompletion = true): Promise<ColdResumeResult | undefined> => {
116
111
  const loaded = await run.store.load();
117
112
  const foreground = modeOverride ?? (loaded.run.delivery?.mode === "foreground" || (loaded.run.delivery?.mode === "background" && loaded.run.delivery.toolCallId !== undefined) || (loaded.run.delivery === undefined && loaded.snapshot.launchMode === "foreground"));
118
113
  if (loaded.run.activeShells !== undefined || loaded.run.activeShellStartedAt !== undefined || loaded.run.activeShellsByPhase !== undefined) await persistRunState(run.store, run.metadata, withoutActiveShells);
119
114
  await run.store.validateRetrySource();
120
115
  await run.store.validateBorrowedWorktrees();
121
- assertResumableSnapshot(loaded.snapshot, trustedProject);
116
+ assertResumableSnapshot(loaded.snapshot);
122
117
  const rootModel = context?.model ? { ...run.model, provider: context.model.provider, model: context.model.id } : run.model;
123
118
  const controller = new AbortController();
124
119
  if (context?.signal?.aborted) controller.abort(); else { context?.signal?.addEventListener("abort", () => { controller.abort(); }, { once: true }); }
125
120
  run.abortController = controller;
126
- const { settingsPath, currentPolicy, previousAliases, knownModels, availableModels, currentAliases, blockedAliases, blockedAliasTargets, snapshot, script } = await resolveLaunchPrologue({ snapshot: loaded.snapshot, cwd: run.store.cwd, trustedProject, rootModel, ...(context?.modelRegistry ? { modelRegistry: context.modelRegistry } : {}), signal: controller.signal, ...(context?.resolvedAliases ? { resolvedAliases: context.resolvedAliases } : {}), ...(context?.blockedAliases ? { blockedAliases: context.blockedAliases } : {}), ...(context?.blockedAliasTargets ? { blockedAliasTargets: context.blockedAliasTargets } : {}), withPreflight: true });
121
+ const { settingsPath, currentPolicy, previousAliases, knownModels, availableModels, currentAliases, dynamicAliasNames, blockedAliases, blockedAliasTargets, snapshot, script } = await resolveLaunchPrologue({ snapshot: loaded.snapshot, cwd: run.store.cwd, trustedProject, rootModel, ...(context?.modelRegistry ? { modelRegistry: context.modelRegistry } : {}), signal: controller.signal, ...(context?.resolvedAliases ? { resolvedAliases: context.resolvedAliases } : {}), ...(context?.resolvedDynamicAliasNames ? { resolvedDynamicAliasNames: context.resolvedDynamicAliasNames } : {}), ...(context?.blockedAliases ? { blockedAliases: context.blockedAliases } : {}), ...(context?.blockedAliasTargets ? { blockedAliasTargets: context.blockedAliasTargets } : {}), withPreflight: true });
127
122
  if (!script) throw new WorkflowError("INTERNAL_ERROR", "Resume preflight did not produce a launch script");
128
123
  const persistedSnapshot = modeOverride === undefined ? snapshot : createLaunchSnapshot({ ...snapshot, launchMode: foreground ? "foreground" : "background" });
129
124
  await run.store.saveSnapshot(persistedSnapshot);
130
125
  if (modeOverride !== undefined) await persistRunState(run.store, run.metadata, (current) => ({ ...current, delivery: { ...(current.delivery ?? {}), mode: foreground ? "foreground" : "background", state: foreground ? "attached" : "pending" } }));
131
126
  scheduler.updateRunLimit(run.store.runId, snapshot.settings.concurrency);
132
127
  scheduler.setRunExtensionSettings(run.store.runId, snapshot.settings.extensionSettings);
133
- const roles = resumeRoles(run.store, persistedSnapshot, run.store.cwd, trustedProject);
134
- run.executor = createAgentExecutor({ cwd: run.store.cwd, model: rootModel, tools: activeSnapshotTools(snapshot.tools, "session"), resourceSelectors: currentPolicy.effective, extensionSettings: snapshot.settings.extensionSettings, availableModels, knownModels, modelAliases: currentAliases, blockedAliases, blockedAliasTargets, settingsPath, agentDefinitions: roles.definitions, runStore: run.store, providerPause: async () => { deliver(`Workflow ${snapshot.metadata.name} paused: provider limit.`); await run.lifecycle.providerPause(); }, agentResourcePolicy: frozenResourcePolicy(currentPolicy) });
128
+ run.executor = createAgentExecutor({ cwd: run.store.cwd, projectTrusted: currentPolicy.projectTrusted, model: rootModel, tools: activeSnapshotTools(snapshot.tools, "session"), resourceSelectors: currentPolicy.effective, extensionSettings: snapshot.settings.extensionSettings, availableModels, knownModels, modelAliases: currentAliases, dynamicModelAliasNames: dynamicAliasNames, blockedAliases, blockedAliasTargets, settingsPath, runStore: run.store, providerPause: async () => { deliver(`Workflow ${snapshot.metadata.name} paused: provider limit.`); await run.lifecycle.providerPause(); }, agentResourcePolicy: frozenResourcePolicy(currentPolicy) });
135
129
  const drift = aliasDrift(previousAliases, currentAliases);
136
130
  if (drift.length) await run.store.appendEvent({ type: "warning", message: `Model alias mappings changed on resume: ${drift.join("; ")}` });
137
131
  const runContext = workflowRunContext(run.store.cwd, run.store.sessionId, run.store.runId, loaded.snapshot.metadata, loaded.snapshot.args, controller.signal);
138
132
  run.executor.setRunContext(runContext);
139
133
  await scheduler.cancelRun(run.store.runId);
140
134
  await run.lifecycle.resume();
141
- const execution = runWorkflow(script, loaded.snapshot.args, withWorkflowFunctions({ tool: (identifier, args, signal, identity) => toolForRun(run.store, run.lifecycle, context?.toolContext, identifier, args, signal, identity), shell: (command, options, signal, identity) => shellForRun(run.store, run.metadata, run.lifecycle, command, options, signal, identity), agent: workflowAgentHandler(run.store, run.metadata, run.lifecycle, run.executor, run.store.cwd, run.store.runId, roles.capture), worktree: async (owner) => resolveWorktree(run.store, run.metadata, owner), checkpoint: checkpointBridge(run.store.runId, run.store, run.metadata, foreground, hasUI ? ui : undefined), phase: phaseBridge(run.store, run.metadata, run.lifecycle), log: logBridge(run.store, run.lifecycle, run.metadata.name) }, run.store, runContext, registry, snapshot.settings.extensionSettings), controller.signal);
135
+ const execution = runWorkflow(script, loaded.snapshot.args, withWorkflowFunctions({ tool: (identifier, args, signal, identity) => toolForRun(run.store, run.lifecycle, context?.toolContext, identifier, args, signal, identity), shell: (command, options, signal, identity) => shellForRun(run.store, run.metadata, run.lifecycle, command, options, signal, identity), agent: workflowAgentHandler(run.store, run.metadata, run.lifecycle, run.executor, run.store.cwd, run.store.runId), worktree: async (owner) => resolveWorktree(run.store, run.metadata, owner), checkpoint: checkpointBridge(run.store.runId, run.store, run.metadata, foreground, hasUI ? ui : undefined), phase: phaseBridge(run.store, run.metadata, run.lifecycle), log: logBridge(run.store, run.lifecycle, run.metadata.name) }, run.store, runContext, registry, snapshot.settings.extensionSettings), controller.signal);
142
136
  run.execution = execution;
143
137
  const completion = execution.result.then(async (value) => {
144
138
  await scheduler.flush(run.store.runId);
@@ -244,7 +238,7 @@ export function createWorkflowRecovery(deps: WorkflowRecoveryDependencies) {
244
238
  return { state: "running" };
245
239
  };
246
240
  const retryReservations = new Set<string>();
247
- type PreparedRetryWorkflowRun = { childRun: WorkflowRunRecord; parentRunId: string; lineageRootRunId: string; concurrency: number; extensionSettings?: Readonly<WorkflowExtensionSettings>; hostModel: { provider: string; id: string }; modelRegistry: WorkflowRecoveryContext["modelRegistry"]; currentAliases: Readonly<Record<string, string>>; blockedAliases: ReadonlySet<string>; blockedAliasTargets: Readonly<Record<string, string>> };
241
+ type PreparedRetryWorkflowRun = { childRun: WorkflowRunRecord; parentRunId: string; lineageRootRunId: string; concurrency: number; extensionSettings?: Readonly<WorkflowExtensionSettings>; hostModel: { provider: string; id: string }; modelRegistry: WorkflowRecoveryContext["modelRegistry"]; currentAliases: Readonly<Record<string, string>>; dynamicAliasNames: readonly string[]; blockedAliases: ReadonlySet<string>; blockedAliasTargets: Readonly<Record<string, string>> };
248
242
  const retryWorkflowRunUnlocked = async (runId: string, context: unknown, signal?: AbortSignal, expectedState?: string): Promise<PreparedRetryWorkflowRun> => {
249
243
  if (typeof runId !== "string" || !runId.trim()) throw new WorkflowError("RESUME_INCOMPATIBLE", "workflow_retry requires an explicit run ID");
250
244
  const host = object(context) ? context : {};
@@ -277,11 +271,11 @@ export function createWorkflowRecovery(deps: WorkflowRecoveryDependencies) {
277
271
  const trustedProject = projectTrusted(context);
278
272
  await sourceStore.validateRetrySource();
279
273
  await sourceStore.validateBorrowedWorktrees();
280
- assertResumableSnapshot(loaded.snapshot, trustedProject);
274
+ assertResumableSnapshot(loaded.snapshot);
281
275
  const modelRegistry = resumeHostContext(context).modelRegistry;
282
276
  const hostModel = object(host.model) && typeof host.model.provider === "string" && typeof host.model.id === "string" ? { provider: host.model.provider, id: host.model.id } : { provider: "", id: "" };
283
277
  const rootModel: ModelSpec = { provider: hostModel.provider, model: hostModel.id, thinking: pi.getThinkingLevel() };
284
- const { active, settingsPath, currentPolicy, knownModels, availableModels, currentAliases, blockedAliases, blockedAliasTargets, snapshot: childBaseSnapshot } = await resolveLaunchPrologue({ snapshot: loaded.snapshot, cwd, trustedProject, rootModel, ...(modelRegistry ? { modelRegistry } : {}), signal: signal ?? new AbortController().signal, withPreflight: true });
278
+ const { active, settingsPath, currentPolicy, knownModels, availableModels, currentAliases, dynamicAliasNames, blockedAliases, blockedAliasTargets, snapshot: childBaseSnapshot } = await resolveLaunchPrologue({ snapshot: loaded.snapshot, cwd, trustedProject, rootModel, ...(modelRegistry ? { modelRegistry } : {}), signal: signal ?? new AbortController().signal, withPreflight: true });
285
279
  await sourceStore.validateNamedWorktrees();
286
280
  for (const name of loaded.run.retry?.namedWorktrees ?? []) await sourceStore.resolveNamedWorktree(name);
287
281
  const completedPaths = (await sourceStore.replayableOperations()).map(({ path }) => path);
@@ -299,10 +293,10 @@ export function createWorkflowRecovery(deps: WorkflowRecoveryDependencies) {
299
293
  const abortController = new AbortController();
300
294
  const providerErrorRecovery = createProviderErrorRecovery(context, availableModels, () => { abortController.abort(); });
301
295
  const providerPause = async () => { deliver(`Workflow ${loaded.snapshot.metadata.name} paused: provider limit.`); await lifecycle.providerPause(); };
302
- const childRun: WorkflowRunRecord = { executor: createAgentExecutor({ cwd, model, tools: activeSnapshotTools(loaded.snapshot.tools, active), resourceSelectors: currentPolicy.effective, extensionSettings: childBaseSnapshot.settings.extensionSettings, availableModels, knownModels, modelAliases: currentAliases, blockedAliases, blockedAliasTargets, settingsPath, agentDefinitions: loaded.snapshot.roles ?? {}, runStore: childStore, providerPause, agentResourcePolicy: frozenResourcePolicy(currentPolicy) }), store: childStore, metadata: loaded.snapshot.metadata, model, lifecycle, budget: childBudget, abortController, projectTrusted: () => projectTrusted(context), checkpointResolvers: new Map(), ...(providerErrorRecovery ? { providerErrorRecovery } : {}) };
296
+ const childRun: WorkflowRunRecord = { executor: createAgentExecutor({ cwd, projectTrusted: currentPolicy.projectTrusted, model, tools: activeSnapshotTools(loaded.snapshot.tools, active), resourceSelectors: currentPolicy.effective, extensionSettings: childBaseSnapshot.settings.extensionSettings, availableModels, knownModels, modelAliases: currentAliases, dynamicModelAliasNames: dynamicAliasNames, blockedAliases, blockedAliasTargets, settingsPath, runStore: childStore, providerPause, agentResourcePolicy: frozenResourcePolicy(currentPolicy) }), store: childStore, metadata: loaded.snapshot.metadata, model, lifecycle, budget: childBudget, abortController, projectTrusted: () => projectTrusted(context), checkpointResolvers: new Map(), ...(providerErrorRecovery ? { providerErrorRecovery } : {}) };
303
297
  runs.set(childRunId, childRun);
304
298
  childCreated = true;
305
- return { childRun, parentRunId: loaded.run.id, lineageRootRunId, concurrency: loaded.snapshot.settings.concurrency, ...(childBaseSnapshot.settings.extensionSettings === undefined ? {} : { extensionSettings: childBaseSnapshot.settings.extensionSettings }), hostModel, modelRegistry, currentAliases, blockedAliases, blockedAliasTargets };
299
+ return { childRun, parentRunId: loaded.run.id, lineageRootRunId, concurrency: loaded.snapshot.settings.concurrency, ...(childBaseSnapshot.settings.extensionSettings === undefined ? {} : { extensionSettings: childBaseSnapshot.settings.extensionSettings }), hostModel, modelRegistry, currentAliases, dynamicAliasNames, blockedAliases, blockedAliasTargets };
306
300
  } finally {
307
301
  if (!childCreated) retryReservations.delete(lineageRootRunId);
308
302
  }
@@ -315,7 +309,7 @@ export function createWorkflowRecovery(deps: WorkflowRecoveryDependencies) {
315
309
  await eventPublisher.runStarted(prepared.childRun.store, prepared.childRun.metadata);
316
310
  const { hasUI, ui } = recoveryUi(context);
317
311
  const recoveryContext = resumeHostContext(context);
318
- const completed = await coldResumeRun(prepared.childRun, hasUI, ui, projectTrusted(context), { model: prepared.hostModel, modelRegistry: prepared.modelRegistry, deliveryContext: recoveryContext.deliveryContext, toolContext: recoveryContext.toolContext, resolvedAliases: prepared.currentAliases, blockedAliases: prepared.blockedAliases, blockedAliasTargets: prepared.blockedAliasTargets, ...(signal ? { signal } : {}) }, modeOverride);
312
+ const completed = await coldResumeRun(prepared.childRun, hasUI, ui, projectTrusted(context), { model: prepared.hostModel, modelRegistry: prepared.modelRegistry, deliveryContext: recoveryContext.deliveryContext, toolContext: recoveryContext.toolContext, resolvedAliases: prepared.currentAliases, resolvedDynamicAliasNames: prepared.dynamicAliasNames, blockedAliases: prepared.blockedAliases, blockedAliasTargets: prepared.blockedAliasTargets, ...(signal ? { signal } : {}) }, modeOverride);
319
313
  const completion = runs.get(prepared.childRun.store.runId)?.completion;
320
314
  if (completion) {
321
315
  childStarted = true;