@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
@@ -60,7 +60,6 @@ interface ReceiptAgent {
60
60
  name: string;
61
61
  state: string;
62
62
  model?: string;
63
- role?: string;
64
63
  requestedModel?: string;
65
64
  tools?: readonly string[];
66
65
  /** Accepted for rendering older receipt entries; new receipts persist granted tools. */
@@ -191,7 +191,7 @@ function renderableAgent(value) {
191
191
  return false;
192
192
  if (value.model.thinking !== undefined && typeof value.model.thinking !== "string")
193
193
  return false;
194
- for (const key of ["id", "label", "parentId", "role", "requestedModel"])
194
+ for (const key of ["id", "label", "parentId", "requestedModel"])
195
195
  if (value[key] !== undefined && typeof value[key] !== "string")
196
196
  return false;
197
197
  for (const key of ["attempts", "startedAt", "durationMs", "lastEventAt"])
@@ -554,8 +554,7 @@ function renderFrame(runs, now, width, offset = 0, theme, scrolling = false, siz
554
554
  ? mark(agent.state, now, paint, run.state)
555
555
  : paint(silent >= STALL_MS ? ROLE.failed : ROLE.warn, "⚠")} ${
556
556
  // The label is what the workflow called this agent — `reviewer #2`,
557
- // `scout (api)` — where the name is the role it was built from. Two
558
- // agents of one role are otherwise indistinguishable.
557
+ // `scout (api)` — preserving an explicit label when supplied.
559
558
  agent.label ?? agent.name}${silent === undefined
560
559
  ? ""
561
560
  : ` ${paint(silent >= STALL_MS ? ROLE.failed : ROLE.warn, `${silent >= STALL_MS ? "stalled" : "quiet"} ${formatElapsed(silent)}`)}`}`,
@@ -696,18 +695,15 @@ function receiptFor(run, fallback) {
696
695
  agents: agents.map((agent) => ({
697
696
  id: agent.id,
698
697
  ...(agent.parentId === undefined ? {} : { parentId: agent.parentId }),
699
- // What the workflow called it, falling back to the role it was built
700
- // from. Two agents of one role are otherwise indistinguishable.
698
+ // Preserve explicit display labels.
701
699
  name: agent.label ?? agent.name,
702
700
  state: agent.state,
703
- // Model and thinking level together: a role picks both, and the model
704
- // name alone does not say whether it reasoned cheaply or deeply.
701
+ // Show both model and thinking level.
705
702
  ...(agent.model.model
706
703
  ? {
707
704
  model: `${agent.model.model}${agent.model.thinking ? `:${agent.model.thinking}` : ""}`,
708
705
  }
709
706
  : {}),
710
- ...(agent.role ? { role: agent.role } : {}),
711
707
  ...(agent.requestedModel ? { requestedModel: agent.requestedModel } : {}),
712
708
  tools: [...agent.tools],
713
709
  // Cache reads dwarf real input and cost almost nothing, so the split is
@@ -782,7 +778,6 @@ export function renderReceipt(data, expanded, theme) {
782
778
  lines.push(`${prefix}${lastAgent ? "╰─" : "├─"} ${glyph(agent.state)} ${agent.name} ${theme.fg("muted", detail)}`);
783
779
  const under = `${prefix}${lastAgent ? " " : "│ "} `;
784
780
  const meta = [
785
- agent.role ? `role ${agent.role}` : "",
786
781
  agent.requestedModel && agent.requestedModel !== agent.model
787
782
  ? `via ${agent.requestedModel}`
788
783
  : "",
@@ -1,4 +1,4 @@
1
- import { type AgentDefinition, type BudgetApprovalRequest, type JsonValue, type LaunchSnapshot, type RunRecord, type WorkflowBudgetUsage, type WorkflowExtensionSettings } from "./types.js";
1
+ import { type PreparedAgentConfiguration, type BudgetApprovalRequest, type JsonValue, type LaunchSnapshot, type RunRecord, type WorkflowBudgetUsage, type WorkflowExtensionSettings } from "./types.js";
2
2
  import type { OwnershipRecord } from "./agent-execution.js";
3
3
  export interface EffectiveSystemPrompt {
4
4
  sessionId: string;
@@ -13,7 +13,6 @@ export interface RunSummaryAgent {
13
13
  name: string;
14
14
  label?: string;
15
15
  state: string;
16
- role?: string;
17
16
  attempts: number;
18
17
  }
19
18
  export interface RunSummaryArtifacts {
@@ -59,7 +58,7 @@ export type Journal = {
59
58
  awaiting?: Record<string, AwaitingCheckpoint>;
60
59
  decisions?: Record<string, PendingWorkflowDecision>;
61
60
  };
62
- export declare function decodeAgentDefinition(value: unknown): AgentDefinition | undefined;
61
+ export declare function decodePreparedAgentConfiguration(value: unknown): PreparedAgentConfiguration | undefined;
63
62
  export declare function decodeWorkflowExtensions(value: unknown): WorkflowExtensionSettings | undefined;
64
63
  export declare function decodePersistedRun(value: unknown, allowLegacyAgentSessions?: boolean): PersistedRun | undefined;
65
64
  export declare function isPersistedRun(value: unknown): value is PersistedRun;
@@ -1,4 +1,4 @@
1
- import { AGENT_STATES, BUDGET_DIMENSIONS, BUDGET_EVENT_TYPES, RUN_STATES, isContextFileScope } from "./types.js";
1
+ import { AGENT_STATES, BUDGET_DIMENSIONS, BUDGET_EVENT_TYPES, RUN_STATES, isContextFileScope, LAUNCH_SNAPSHOT_IDENTITY_VERSION } from "./types.js";
2
2
  import { finiteNumber, isThinkingLevel, isWorkflowErrorCode, jsonValue, object, positiveInteger, validWorkflowExtensionNamespace } from "./utils.js";
3
3
  const INVALID_PERSISTED_VALUE = Symbol("invalid persisted value");
4
4
  function integer(value) { return finiteNumber(value) && Number.isInteger(value); }
@@ -64,28 +64,21 @@ function decodeAgentResourceSelectors(value) {
64
64
  return { ...(skills === undefined ? {} : { skills }), ...(extensions === undefined ? {} : { extensions }), ...(tools === undefined ? {} : { tools }) };
65
65
  }
66
66
  function decodeContextFileScopes(value) { return decodeArray(value, (scope) => isContextFileScope(scope) ? scope : undefined); }
67
- export function decodeAgentDefinition(value) {
68
- if (!object(value))
67
+ export function decodePreparedAgentConfiguration(value) {
68
+ if (!object(value) || !jsonValue(value) || typeof value.projectTrusted !== "boolean" || typeof value.systemPromptAppend !== "string")
69
69
  return undefined;
70
- const prompt = optionalString(value.prompt);
71
- const description = optionalString(value.description);
72
- const model = optionalString(value.model);
73
- const thinking = value.thinking;
74
- const tools = value.tools === undefined ? undefined : decodeStringArray(value.tools);
75
- const skills = value.skills === undefined ? undefined : decodeStringArray(value.skills);
76
- const extensions = value.extensions === undefined ? undefined : decodeStringArray(value.extensions);
77
- const overrideSystemPrompt = optionalBoolean(value.overrideSystemPrompt);
70
+ const model = decodeModelSpec(value.model);
71
+ const tools = decodeStringArray(value.tools);
72
+ const skills = decodeStringArray(value.skills);
73
+ const extensions = decodeStringArray(value.extensions);
74
+ const settings = decodeWorkflowExtensions(value.settings);
78
75
  const contextFiles = value.contextFiles === undefined ? undefined : decodeContextFileScopes(value.contextFiles);
79
- const extensionSettings = value.extensionSettings === undefined ? undefined : decodeWorkflowExtensions(value.extensionSettings);
80
- if (prompt === INVALID_PERSISTED_VALUE || description === INVALID_PERSISTED_VALUE || model === INVALID_PERSISTED_VALUE || overrideSystemPrompt === INVALID_PERSISTED_VALUE)
81
- return undefined;
82
- 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)
76
+ const label = optionalString(value.label);
77
+ const systemPrompt = optionalString(value.systemPrompt);
78
+ const excludeTools = value.excludeTools === undefined ? undefined : decodeStringArray(value.excludeTools);
79
+ if (!model || !tools || !skills || !extensions || !settings || label === INVALID_PERSISTED_VALUE || systemPrompt === INVALID_PERSISTED_VALUE || value.contextFiles !== undefined && !contextFiles || value.excludeTools !== undefined && !excludeTools)
83
80
  return undefined;
84
- const foldedModel = typeof model === "string" && thinking !== undefined && !model.includes(":") ? `${model}:${thinking}` : model;
85
- return {
86
- ...(prompt === undefined ? {} : { prompt }), ...(description === undefined ? {} : { description }), ...(foldedModel === undefined ? {} : { model: foldedModel }),
87
- ...(tools === undefined ? {} : { tools }), ...(skills === undefined ? {} : { skills }), ...(extensions === undefined ? {} : { extensions }), ...(overrideSystemPrompt === undefined ? {} : { overrideSystemPrompt }), ...(contextFiles === undefined ? {} : { contextFiles }), ...(extensionSettings === undefined ? {} : { extensionSettings }),
88
- };
81
+ return { model, tools, skills, extensions, settings, projectTrusted: value.projectTrusted, systemPromptAppend: value.systemPromptAppend, ...(contextFiles === undefined ? {} : { contextFiles }), ...(excludeTools === undefined ? {} : { excludeTools }), ...(label === undefined ? {} : { label }), ...(systemPrompt === undefined ? {} : { systemPrompt }) };
89
82
  }
90
83
  function decodeWorkflowMetadata(value) {
91
84
  if (!object(value) || typeof value.name !== "string")
@@ -211,17 +204,25 @@ function decodeScheduledAgentOptions(value) {
211
204
  const parentBreadcrumb = optionalString(value.parentBreadcrumb);
212
205
  const worktreeOwner = optionalString(value.worktreeOwner);
213
206
  const model = optionalString(value.model);
214
- const role = typeof value.role === "string" ? value.role : undefined;
215
207
  const contextFiles = value.contextFiles === undefined ? undefined : decodeContextFileScopes(value.contextFiles);
216
208
  const schema = value.schema === undefined ? undefined : decodeJsonObject(value.schema);
217
209
  const retries = optionalNumber(value.retries);
218
210
  const timeoutMs = value.timeoutMs;
211
+ if (value.projectTrusted !== undefined && typeof value.projectTrusted !== "boolean")
212
+ return undefined;
213
+ const rawCapabilities = value.capabilities;
214
+ const capabilities = rawCapabilities === undefined ? undefined : object(rawCapabilities) ? { tools: decodeStringArray(rawCapabilities.tools), skills: decodeStringArray(rawCapabilities.skills), extensions: decodeStringArray(rawCapabilities.extensions) } : undefined;
215
+ if (rawCapabilities !== undefined && (!capabilities?.tools || !capabilities.skills || !capabilities.extensions))
216
+ return undefined;
217
+ const configuration = value.configuration === undefined ? undefined : decodePreparedAgentConfiguration(value.configuration);
218
+ if (value.configuration !== undefined && configuration === undefined)
219
+ return undefined;
219
220
  const agentOptions = value.agentOptions === undefined ? undefined : decodeJsonObject(value.agentOptions);
220
221
  const agentIdentity = value.agentIdentity === undefined ? undefined : decodeIdentity(value.agentIdentity);
221
222
  const sessionPath = optionalString(value.sessionPath);
222
223
  const continuity = value.continuity === undefined ? undefined : isAgentContinuity(value.continuity) ? value.continuity : INVALID_PERSISTED_VALUE;
223
224
  const extensionSettings = value.extensionSettings === undefined ? undefined : decodeWorkflowExtensions(value.extensionSettings);
224
- 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))
225
+ 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))
225
226
  return undefined;
226
227
  return {
227
228
  label: value.label,
@@ -232,7 +233,6 @@ function decodeScheduledAgentOptions(value) {
232
233
  ...(skills === undefined ? {} : { skills }), ...(extensions === undefined ? {} : { extensions }),
233
234
  ...(worktreeOwner === undefined ? {} : { worktreeOwner }),
234
235
  ...(model === undefined ? {} : { model }),
235
- ...(role === undefined ? {} : { role }),
236
236
  ...(contextFiles === undefined ? {} : { contextFiles }),
237
237
  ...(schema === undefined ? {} : { schema }),
238
238
  ...(retries === undefined ? {} : { retries }),
@@ -240,6 +240,9 @@ function decodeScheduledAgentOptions(value) {
240
240
  ...(sessionPath === undefined ? {} : { sessionPath }),
241
241
  ...(continuity === undefined ? {} : { continuity }),
242
242
  ...(agentOptions === undefined ? {} : { agentOptions }),
243
+ ...(configuration === undefined ? {} : { configuration }),
244
+ ...(value.projectTrusted === undefined ? {} : { projectTrusted: value.projectTrusted }),
245
+ ...(capabilities?.tools && capabilities.skills && capabilities.extensions ? { capabilities: { tools: capabilities.tools, skills: capabilities.skills, extensions: capabilities.extensions } } : {}),
243
246
  ...(agentIdentity === undefined ? {} : { agentIdentity }),
244
247
  ...(extensionSettings === undefined ? {} : { extensionSettings }),
245
248
  };
@@ -288,12 +291,11 @@ function decodeAgentResourceInspection(value) {
288
291
  const sources = sourceRecord === undefined ? undefined : {
289
292
  global: decodeAgentResourceSelectors(sourceRecord.global),
290
293
  project: decodeAgentResourceSelectors(sourceRecord.project),
291
- ...(sourceRecord.role === undefined ? {} : { role: decodeAgentResourceSelectors(sourceRecord.role) }),
292
294
  ...(sourceRecord.call === undefined ? {} : { call: decodeAgentResourceSelectors(sourceRecord.call) }),
293
295
  };
294
- 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))
296
+ if (!selectors || !skills || !extensions || !tools || !unmatchedSkills || !unmatchedExtensions || !unmatchedTools || rawSources !== undefined && (!sources || !sources.global || !sources.project || sources.call === undefined && sourceRecord?.call !== undefined))
295
297
  return undefined;
296
- 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 }) } }) };
298
+ 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 }) } }) };
297
299
  }
298
300
  function decodeAgentSetupSummary(value) {
299
301
  if (!object(value) || typeof value.cwd !== "string")
@@ -337,7 +339,6 @@ function decodeAgent(value) {
337
339
  const worktreeOwner = optionalString(value.worktreeOwner);
338
340
  const handle = optionalString(value.handle);
339
341
  const continuity = value.continuity === undefined ? undefined : isAgentContinuity(value.continuity) ? value.continuity : INVALID_PERSISTED_VALUE;
340
- const role = optionalString(value.role);
341
342
  const requestedModel = optionalString(value.requestedModel);
342
343
  const startedAt = optionalNumber(value.startedAt);
343
344
  const durationMs = optionalNumber(value.durationMs);
@@ -346,7 +347,7 @@ function decodeAgent(value) {
346
347
  const toolCalls = value.toolCalls === undefined ? undefined : decodeArray(value.toolCalls, decodeAgentToolCall);
347
348
  const activity = value.activity === undefined ? undefined : decodeAgentActivity(value.activity);
348
349
  const lastEventAt = optionalNumber(value.lastEventAt);
349
- 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))
350
+ 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))
350
351
  return undefined;
351
352
  if (!model || !tools || value.structuralPath !== undefined && !structuralPath || value.attemptDetails !== undefined && !attemptDetails || value.accounting !== undefined && !accounting || value.toolCalls !== undefined && !toolCalls || value.activity !== undefined && !activity)
352
353
  return undefined;
@@ -365,7 +366,6 @@ function decodeAgent(value) {
365
366
  ...(worktreeOwner === undefined ? {} : { worktreeOwner }),
366
367
  ...(handle === undefined ? {} : { handle, turn: value.turn }),
367
368
  ...(continuity === undefined ? {} : { continuity }),
368
- ...(role === undefined ? {} : { role }),
369
369
  ...(requestedModel === undefined ? {} : { requestedModel }),
370
370
  model,
371
371
  tools,
@@ -539,14 +539,12 @@ export function decodeLaunchSnapshot(value) {
539
539
  const phases = value.phases === undefined ? undefined : decodeStringArray(value.phases);
540
540
  const models = decodeStringArray(value.models);
541
541
  const tools = decodeStringArray(value.tools);
542
- const agentTypes = decodeStringArray(value.agentTypes);
543
- const roles = value.roles === undefined ? undefined : decodeRecord(value.roles, decodeAgentDefinition);
544
- const projectRoles = value.projectRoles === undefined ? undefined : decodeStringArray(value.projectRoles);
542
+ const agentConfigurations = decodeRecord(value.agentConfigurations, decodePreparedAgentConfiguration);
545
543
  const schemas = decodeArray(value.schemas, decodeJsonObject);
546
- 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)
544
+ 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)
547
545
  return undefined;
548
546
  return {
549
- ...(identityVersion === undefined ? {} : { identityVersion }),
547
+ identityVersion: LAUNCH_SNAPSHOT_IDENTITY_VERSION,
550
548
  ...(launchMode === undefined ? {} : { launchMode }),
551
549
  script: value.script,
552
550
  args,
@@ -559,9 +557,7 @@ export function decodeLaunchSnapshot(value) {
559
557
  ...(phases === undefined ? {} : { phases }),
560
558
  models,
561
559
  tools,
562
- agentTypes,
563
- ...(roles === undefined ? {} : { roles }),
564
- ...(projectRoles === undefined ? {} : { projectRoles }),
560
+ agentConfigurations,
565
561
  schemas,
566
562
  };
567
563
  }
@@ -3,6 +3,8 @@ import type { AgentAttempt } from "./agent-execution.js";
3
3
  import type { AgentIdentity, JsonValue, ShellIdentity, ShellOptions, ShellResult, ToolIdentity, WorkflowBridge, WorkflowExecution } from "./types.js";
4
4
  export declare const RPC_LIMIT_BYTES: number;
5
5
  export declare const HEARTBEAT_TIMEOUT_MS = 5000;
6
+ /** Script handle names may not start with this: registered-function handles are journaled under it. */
7
+ export declare const FUNCTION_HANDLE_PREFIX = "function-handle";
6
8
  export declare function encoded(value: JsonValue): string;
7
9
  export declare function encoded(value: unknown): asserts value is JsonValue;
8
10
  /** The journal path of a script tool call; the arguments' digest keeps calls with different arguments apart whatever order they start in. */
@@ -4,7 +4,7 @@ import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
5
  import { StringDecoder } from "node:string_decoder";
6
6
  import { structuralPath as operationPath } from "./persistence.js";
7
- import { WorkflowError, roleNameOf, sumAccounting, zeroAccounting } from "./types.js";
7
+ import { WorkflowError, sumAccounting, zeroAccounting } from "./types.js";
8
8
  import { asWorkflowError, errorText, fail, isWorkflowAuthored, isWorkflowErrorCode, jsonDigest, jsonValue, markWorkflowAuthored, object, positiveInteger } from "./utils.js";
9
9
  import { instrumentWorkflow, validateAgentOptions, validateShellCommand, validateShellOptions } from "./validation.js";
10
10
  export const RPC_LIMIT_BYTES = 10 * 1024 * 1024;
@@ -35,6 +35,8 @@ function isRpcMessage(value) {
35
35
  }
36
36
  const HEARTBEAT_INTERVAL_MS = 1000;
37
37
  const OUTCOME_ERRORS = new Set(["AGENT_TIMEOUT", "AGENT_FAILED", "RESULT_INVALID"]);
38
+ /** Script handle names may not start with this: registered-function handles are journaled under it. */
39
+ export const FUNCTION_HANDLE_PREFIX = "function-handle";
38
40
  const WORK_RESULT_BRAND = "__workResult";
39
41
  const childSource = String.raw `
40
42
  "use strict";
@@ -145,15 +147,13 @@ const internalAgent = (...values) => {
145
147
  void result.then(() => agentInflight.delete(occurrenceKey), () => agentInflight.delete(occurrenceKey));
146
148
  return guardedAgentResult(result);
147
149
  };
148
- const AGENT_HANDLE_KEYS = ["name", "role", "model", "tools", "skills", "extensions", "contextFiles", "label"];
149
150
  const agentHandleNames = new Set();
150
151
  const agentHandleInflight = new Set();
151
152
  const internalAgentCreate = (...values) => {
152
153
  if (values.length !== 1 || !values[0] || typeof values[0] !== "object" || Array.isArray(values[0])) throw workError("INVALID_METADATA", "agent.create requires one options object");
153
154
  const options = values[0];
154
155
  const name = named(options.name, "agent.create");
155
- const unsupported = Object.keys(options).find(key => !AGENT_HANDLE_KEYS.includes(key));
156
- if (unsupported) throw workError("INVALID_METADATA", "agent.create option is unsupported: " + unsupported);
156
+ if (name.startsWith("${FUNCTION_HANDLE_PREFIX}/")) throw workError("INVALID_METADATA", "Agent handle names may not start with ${FUNCTION_HANDLE_PREFIX}/");
157
157
  if (agentHandleNames.has(name)) throw workError("INVALID_METADATA", "Duplicate agent handle name: " + name);
158
158
  agentHandleNames.add(name);
159
159
  const config = Object.freeze(Object.fromEntries(Object.entries(options).filter(([key]) => key !== "name")));
@@ -655,7 +655,7 @@ export function runWorkflow(script, args = null, bridge = {}, signal) {
655
655
  const opts = validateAgentOptions(values[1]);
656
656
  const identity = readAgentIdentity(values[2]);
657
657
  const path = agentIdentityPath(identity);
658
- const label = typeof opts.label === "string" ? opts.label : roleNameOf(opts.role) ?? "agent";
658
+ const label = typeof opts.label === "string" ? opts.label : "agent";
659
659
  try {
660
660
  const result = await bridge.agent(values[0], opts, controller.signal, identity);
661
661
  value = branded({ name: label, ok: true, value: result ?? null });
@@ -101,9 +101,9 @@ const WORKFLOW_ERROR_PROSE = {
101
101
  REGISTRY_FROZEN: (detail) => `Workflow extension registration is closed: ${detail}.`,
102
102
  GLOBAL_COLLISION: (detail) => `The workflow global name is already in use: ${detail}.`,
103
103
  MISSING_WORKFLOW: (detail) => `The registered workflow function is unavailable: ${detail}.`,
104
- UNKNOWN_MODEL: (detail) => `The workflow requested the unavailable model ${detail.replace(/^(?:Unknown model(?: for role [^:]+)?|Invalid model spec):\s*/, "")}.`,
104
+ UNKNOWN_MODEL: (detail) => `The workflow requested the unavailable model ${detail.replace(/^(?:Unknown model|Invalid model spec|Required model is unavailable):\s*/, "")}.`,
105
105
  UNKNOWN_TOOL: (detail) => `The workflow requested the unavailable tool ${detail.replace(/^Unknown tool:\s*/, "")}.`,
106
- UNKNOWN_AGENT_TYPE: (detail) => `The workflow requested the unavailable agent role ${detail.replace(/^Unknown agent role:\s*/, "")}.`,
106
+ UNKNOWN_AGENT: (detail) => `The workflow requested an unavailable owned agent: ${detail}.`,
107
107
  RUN_OWNED: (detail) => /already owned|active ownership/.test(detail) ? "The workflow session is already in use." : `The workflow session is already in use: ${detail}.`,
108
108
  RUN_NOT_FOUND: (detail) => /^Unknown workflow run\b/.test(detail) ? "The workflow run was not found." : `The workflow run was not found: ${detail}.`,
109
109
  RPC_LIMIT_EXCEEDED: (detail) => `The workflow communication data exceeded its size limit: ${detail}.`,
@@ -205,7 +205,6 @@ function boundedWorkflowFailureDiagnostics(value) {
205
205
  ...(value.failedAgent ? { failedAgent: {
206
206
  id: utf8Prefix(value.failedAgent.id, 128),
207
207
  ...(value.failedAgent.label ? { label: utf8Prefix(value.failedAgent.label, 128) } : {}),
208
- ...(value.failedAgent.role ? { role: utf8Prefix(value.failedAgent.role, 128) } : {}),
209
208
  structuralPath: value.failedAgent.structuralPath.slice(0, 8).map((part) => utf8Prefix(part, 128)),
210
209
  attempt: value.failedAgent.attempt,
211
210
  ...(value.failedAgent.transport ? { transport: utf8Prefix(value.failedAgent.transport, 128) } : {}),
@@ -214,7 +213,6 @@ function boundedWorkflowFailureDiagnostics(value) {
214
213
  completedSiblingAgents: (value.completedSiblingAgents ?? []).slice(0, 16).map((agent) => ({
215
214
  id: utf8Prefix(agent.id, 128),
216
215
  ...(agent.label ? { label: utf8Prefix(agent.label, 128) } : {}),
217
- ...(agent.role ? { role: utf8Prefix(agent.role, 128) } : {}),
218
216
  structuralPath: agent.structuralPath.slice(0, 8).map((part) => utf8Prefix(part, 128)),
219
217
  })),
220
218
  completedSiblingPaths: value.completedSiblingPaths.slice(0, 16).map((path) => path.slice(0, 8).map((part) => utf8Prefix(part, 128))),
@@ -306,7 +304,6 @@ export async function createWorkflowFailureDiagnostics(store, metadata, error, r
306
304
  const failedAgent = failedAgentRecord ? {
307
305
  id: failedAgentRecord.id,
308
306
  ...(failedAgentRecord.label ?? failedAgentRecord.name ? { label: failedAgentRecord.label ?? failedAgentRecord.name } : {}),
309
- ...(failedAgentRecord.role ? { role: failedAgentRecord.role } : {}),
310
307
  structuralPath: [...(failedAgentRecord.structuralPath ?? [])],
311
308
  attempt: Math.max(1, failedAttempt?.attempt ?? failedAgentRecord.attempts),
312
309
  ...(failedAttempt?.transport ? { transport: failedAttempt.transport } : {}),
@@ -319,7 +316,6 @@ export async function createWorkflowFailureDiagnostics(store, metadata, error, r
319
316
  }).map((agent) => ({
320
317
  id: agent.id,
321
318
  ...(agent.label ?? agent.name ? { label: agent.label ?? agent.name } : {}),
322
- ...(agent.role ? { role: agent.role } : {}),
323
319
  structuralPath: [...(agent.structuralPath ?? [])],
324
320
  }));
325
321
  const completedSiblingPaths = completedSiblingAgents.map((agent) => [...agent.structuralPath]);
@@ -347,9 +343,9 @@ export async function createWorkflowFailureDiagnostics(store, metadata, error, r
347
343
  });
348
344
  }
349
345
  export function formatWorkflowFailureDiagnostics(diagnostic) {
350
- 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)";
346
+ 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)";
351
347
  const siblingAgents = diagnostic.completedSiblingAgents;
352
- 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)";
348
+ 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)";
353
349
  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}`] : [];
354
350
  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");
355
351
  }
@@ -827,9 +827,10 @@ export function registerWorkflowNavigator(deps) {
827
827
  const styles = themeWorkflowProgressStyles(theme);
828
828
  const agent = selectedAgentRecord();
829
829
  const actions = actionMode ? { title: agent ? "Agent actions" : "Run actions", options: actionOptions(), index: actionIndex } : undefined;
830
- 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());
830
+ const formatPhaseLines = (scrollTop) => 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());
831
+ const phaseLines = formatPhaseLines(0);
831
832
  const statusLines = stopStatus ? truncateToVisualLines(styles.error(stopStatus), Number.MAX_SAFE_INTEGER, width, 0).visualLines.map((line) => line.trimEnd()) : [];
832
- const content = [...statusLines, ...phaseLines];
833
+ let content = [...statusLines, ...phaseLines];
833
834
  const rows = terminalRows();
834
835
  const hintRows = rows >= 3 ? 1 : 0;
835
836
  const confirmRows = inlineConfirm.rows(width, styles);
@@ -857,6 +858,8 @@ export function registerWorkflowNavigator(deps) {
857
858
  selectionNeedsScroll = false;
858
859
  }
859
860
  dashboardOffset = Math.max(0, Math.min(maxOffset, dashboardOffset));
861
+ if (!narrow && !actionMode)
862
+ content = [...statusLines, ...formatPhaseLines(Math.max(0, dashboardOffset - statusLines.length))];
860
863
  const selectedNode = selectedNodeId ? tree.byId.get(selectedNodeId) : undefined;
861
864
  const enterAction = selectedNode?.kind === "workflow" ? "run actions" : selectedNode?.kind === "agent" ? "agent actions" : selectedNode?.children.length ? "expand/collapse" : narrow ? "inspect" : "focus details";
862
865
  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] ?? "";
@@ -45,6 +45,7 @@ export interface WorkflowPhaseSelection {
45
45
  expandedNodeIds?: readonly string[] | undefined;
46
46
  treeOnly?: boolean | undefined;
47
47
  detailsOnly?: boolean | undefined;
48
+ scrollTop?: number | undefined;
48
49
  actions?: {
49
50
  title: string;
50
51
  options: readonly string[];
@@ -1,6 +1,6 @@
1
1
  import type { Api, Model } from "@earendil-works/pi-ai";
2
2
  import { type ExtensionAPI, type ExtensionToolContext } from "@earendil-works/pi-coding-agent";
3
- import { FairAgentScheduler, WorkflowAgentExecutor, type AgentDefinition, type AgentExecutionRoot, type AgentProviderFailure, type AgentProviderRecovery } from "./agent-execution.js";
3
+ import { FairAgentScheduler, WorkflowAgentExecutor, type AgentExecutionRoot, type AgentProviderFailure, type AgentProviderRecovery } from "./agent-execution.js";
4
4
  import { RunStore, type PersistedRun } from "./persistence.js";
5
5
  import { WorkflowError, type AgentIdentity, type AgentResourcePolicy, type BudgetApprovalRequest, type JsonValue, type LaunchSnapshot, type ModelSpec, type RunState, type ShellIdentity, type ShellOptions, type ShellResult, type ToolIdentity, type WorkflowMetadata, type WorkflowWorktreeReference } from "./types.js";
6
6
  import { type WorkflowRegistryApi } from "./registry.js";
@@ -24,6 +24,7 @@ export type WorkflowRecoveryContext = {
24
24
  toolContext?: ExtensionToolContext | undefined;
25
25
  signal?: AbortSignal;
26
26
  resolvedAliases?: Readonly<Record<string, string>>;
27
+ resolvedDynamicAliasNames?: readonly string[];
27
28
  blockedAliases?: ReadonlySet<string>;
28
29
  blockedAliasTargets?: Readonly<Record<string, string>>;
29
30
  };
@@ -49,6 +50,7 @@ export type WorkflowRecoveryDependencies = {
49
50
  modelRegistry?: WorkflowRecoveryContext["modelRegistry"];
50
51
  signal: AbortSignal;
51
52
  resolvedAliases?: Readonly<Record<string, string>>;
53
+ resolvedDynamicAliasNames?: readonly string[];
52
54
  blockedAliases?: ReadonlySet<string>;
53
55
  blockedAliasTargets?: Readonly<Record<string, string>>;
54
56
  withPreflight: boolean;
@@ -60,16 +62,13 @@ export type WorkflowRecoveryDependencies = {
60
62
  knownModels: ReadonlySet<string>;
61
63
  availableModels: ReadonlySet<string>;
62
64
  currentAliases: Readonly<Record<string, string>>;
65
+ dynamicAliasNames: readonly string[];
63
66
  blockedAliases: ReadonlySet<string>;
64
67
  blockedAliasTargets: Readonly<Record<string, string>>;
65
68
  snapshot: Readonly<LaunchSnapshot>;
66
69
  script: string | undefined;
67
70
  }>;
68
- resumeRoles: (store: RunStore, snapshot: Readonly<LaunchSnapshot>, cwd: string, trustedProject: boolean) => {
69
- definitions: Readonly<Record<string, AgentDefinition>>;
70
- capture: (role: string, model: ModelSpec) => Promise<void>;
71
- };
72
- 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>;
71
+ 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>;
73
72
  shellForRun: (store: RunStore, metadata: WorkflowMetadata, lifecycle: RunLifecycle, command: string, options: ShellOptions, signal: AbortSignal, identity: ShellIdentity) => Promise<ShellResult>;
74
73
  toolForRun: (store: RunStore, lifecycle: RunLifecycle, context: ExtensionToolContext | undefined, identifier: string, args: Readonly<Record<string, JsonValue>>, signal: AbortSignal, identity: ToolIdentity) => Promise<JsonValue>;
75
74
  resolveWorktree: (store: RunStore, metadata: WorkflowMetadata, owner: string) => Promise<Readonly<WorkflowWorktreeReference>>;
@@ -109,6 +108,7 @@ export declare function createWorkflowRecovery(deps: WorkflowRecoveryDependencie
109
108
  toolContext?: ExtensionToolContext | undefined;
110
109
  signal?: AbortSignal | undefined;
111
110
  resolvedAliases?: Readonly<Record<string, string>>;
111
+ resolvedDynamicAliasNames?: readonly string[];
112
112
  blockedAliases?: ReadonlySet<string>;
113
113
  blockedAliasTargets?: Readonly<Record<string, string>>;
114
114
  }, modeOverride?: boolean, waitForCompletion?: boolean) => Promise<{
@@ -28,17 +28,10 @@ function workflowRecoveryGuidance(action, state) {
28
28
  return "Interrupted workflow runs must be resumed from the interactive /workflow picker";
29
29
  return `Only failed workflow runs can be retried; source is ${state}`;
30
30
  }
31
- /** A snapshot can only be re-executed when it carries the current identity scheme and every role its script references. */
32
- function assertResumableSnapshot(snapshot, trustedProject) {
31
+ /** A snapshot can only be re-executed when it carries the current identity scheme and validated concrete agent configurations. */
32
+ function assertResumableSnapshot(snapshot) {
33
33
  if (snapshot.identityVersion !== LAUNCH_SNAPSHOT_IDENTITY_VERSION)
34
34
  throw new WorkflowError("RESUME_INCOMPATIBLE", "Workflow launch snapshot identity version is incompatible");
35
- if (snapshot.roles === undefined)
36
- throw new WorkflowError("RESUME_INCOMPATIBLE", "Workflow role definitions are missing from the launch snapshot");
37
- if ((snapshot.projectRoles?.length ?? 0) > 0 && !trustedProject)
38
- throw new WorkflowError("RESUME_INCOMPATIBLE", "Cannot restore project roles in an untrusted project");
39
- const missingRole = snapshot.agentTypes.find((role) => !snapshot.roles?.[role]);
40
- if (missingRole)
41
- throw new WorkflowError("RESUME_INCOMPATIBLE", `Role definition is missing from the launch snapshot: ${missingRole}`);
42
35
  }
43
36
  function assertExpectedWorkflowState(expectedState, actualState) {
44
37
  if (expectedState !== undefined && expectedState !== actualState)
@@ -46,7 +39,7 @@ function assertExpectedWorkflowState(expectedState, actualState) {
46
39
  }
47
40
  export function persistedFailure(run, error) { const failedAt = workflowFailedAt(error); return { ...run, error: { code: error.code, message: error.message, ...(failedAt ? { failedAt } : {}) }, ...(failedAt ? { failedAt } : {}) }; }
48
41
  export function createWorkflowRecovery(deps) {
49
- 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;
42
+ 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;
50
43
  const coordinateRunMutation = deps.coordinateRunMutation ?? ((task) => task());
51
44
  const budgetDecisionDelivery = (metadata, request) => `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}.`;
52
45
  const appendBudgetDecisionEvent = async (run, request, type) => {
@@ -70,11 +63,11 @@ export function createWorkflowRecovery(deps) {
70
63
  const loaded = await run.store.load();
71
64
  const trustedProject = context?.projectTrusted ?? run.projectTrusted();
72
65
  const rootModel = context?.model ? { ...run.model, provider: context.model.provider, model: context.model.id } : run.model;
73
- 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 });
66
+ 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 });
74
67
  await run.store.saveSnapshot(snapshot);
75
68
  scheduler.updateRunLimit(run.store.runId, snapshot.settings.concurrency);
76
69
  scheduler.setRunExtensionSettings(run.store.runId, snapshot.settings.extensionSettings);
77
- 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) });
70
+ 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) });
78
71
  run.executor.setRunContext(workflowRunContext(run.store.cwd, run.store.sessionId, run.store.runId, loaded.snapshot.metadata, loaded.snapshot.args, run.abortController.signal));
79
72
  const drift = aliasDrift(previousAliases, currentAliases);
80
73
  if (drift.length)
@@ -92,7 +85,7 @@ export function createWorkflowRecovery(deps) {
92
85
  await persistRunState(run.store, run.metadata, withoutActiveShells);
93
86
  await run.store.validateRetrySource();
94
87
  await run.store.validateBorrowedWorktrees();
95
- assertResumableSnapshot(loaded.snapshot, trustedProject);
88
+ assertResumableSnapshot(loaded.snapshot);
96
89
  const rootModel = context?.model ? { ...run.model, provider: context.model.provider, model: context.model.id } : run.model;
97
90
  const controller = new AbortController();
98
91
  if (context?.signal?.aborted)
@@ -101,7 +94,7 @@ export function createWorkflowRecovery(deps) {
101
94
  context?.signal?.addEventListener("abort", () => { controller.abort(); }, { once: true });
102
95
  }
103
96
  run.abortController = controller;
104
- 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 });
97
+ 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 });
105
98
  if (!script)
106
99
  throw new WorkflowError("INTERNAL_ERROR", "Resume preflight did not produce a launch script");
107
100
  const persistedSnapshot = modeOverride === undefined ? snapshot : createLaunchSnapshot({ ...snapshot, launchMode: foreground ? "foreground" : "background" });
@@ -110,8 +103,7 @@ export function createWorkflowRecovery(deps) {
110
103
  await persistRunState(run.store, run.metadata, (current) => ({ ...current, delivery: { ...(current.delivery ?? {}), mode: foreground ? "foreground" : "background", state: foreground ? "attached" : "pending" } }));
111
104
  scheduler.updateRunLimit(run.store.runId, snapshot.settings.concurrency);
112
105
  scheduler.setRunExtensionSettings(run.store.runId, snapshot.settings.extensionSettings);
113
- const roles = resumeRoles(run.store, persistedSnapshot, run.store.cwd, trustedProject);
114
- 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) });
106
+ 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) });
115
107
  const drift = aliasDrift(previousAliases, currentAliases);
116
108
  if (drift.length)
117
109
  await run.store.appendEvent({ type: "warning", message: `Model alias mappings changed on resume: ${drift.join("; ")}` });
@@ -119,7 +111,7 @@ export function createWorkflowRecovery(deps) {
119
111
  run.executor.setRunContext(runContext);
120
112
  await scheduler.cancelRun(run.store.runId);
121
113
  await run.lifecycle.resume();
122
- 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);
114
+ 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);
123
115
  run.execution = execution;
124
116
  const completion = execution.result.then(async (value) => {
125
117
  await scheduler.flush(run.store.runId);
@@ -292,11 +284,11 @@ export function createWorkflowRecovery(deps) {
292
284
  const trustedProject = projectTrusted(context);
293
285
  await sourceStore.validateRetrySource();
294
286
  await sourceStore.validateBorrowedWorktrees();
295
- assertResumableSnapshot(loaded.snapshot, trustedProject);
287
+ assertResumableSnapshot(loaded.snapshot);
296
288
  const modelRegistry = resumeHostContext(context).modelRegistry;
297
289
  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: "" };
298
290
  const rootModel = { provider: hostModel.provider, model: hostModel.id, thinking: pi.getThinkingLevel() };
299
- 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 });
291
+ 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 });
300
292
  await sourceStore.validateNamedWorktrees();
301
293
  for (const name of loaded.run.retry?.namedWorktrees ?? [])
302
294
  await sourceStore.resolveNamedWorktree(name);
@@ -315,10 +307,10 @@ export function createWorkflowRecovery(deps) {
315
307
  const abortController = new AbortController();
316
308
  const providerErrorRecovery = createProviderErrorRecovery(context, availableModels, () => { abortController.abort(); });
317
309
  const providerPause = async () => { deliver(`Workflow ${loaded.snapshot.metadata.name} paused: provider limit.`); await lifecycle.providerPause(); };
318
- const childRun = { 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 } : {}) };
310
+ const childRun = { 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 } : {}) };
319
311
  runs.set(childRunId, childRun);
320
312
  childCreated = true;
321
- 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 };
313
+ 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 };
322
314
  }
323
315
  finally {
324
316
  if (!childCreated)
@@ -333,7 +325,7 @@ export function createWorkflowRecovery(deps) {
333
325
  await eventPublisher.runStarted(prepared.childRun.store, prepared.childRun.metadata);
334
326
  const { hasUI, ui } = recoveryUi(context);
335
327
  const recoveryContext = resumeHostContext(context);
336
- 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);
328
+ 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);
337
329
  const completion = runs.get(prepared.childRun.store.runId)?.completion;
338
330
  if (completion) {
339
331
  childStarted = true;