@marcoscale98/pi-extensible-workflows 5.17.0-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 (128) hide show
  1. package/CHANGELOG.md +145 -0
  2. package/README.md +2 -2
  3. package/dist/src/agent-execution.d.ts +43 -10
  4. package/dist/src/agent-execution.js +442 -162
  5. package/dist/src/background-widget.d.ts +5 -1
  6. package/dist/src/background-widget.js +5 -10
  7. package/dist/src/decoders.d.ts +2 -3
  8. package/dist/src/decoders.js +59 -37
  9. package/dist/src/execution.d.ts +6 -2
  10. package/dist/src/execution.js +48 -7
  11. package/dist/src/host-delivery.js +5 -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 +11 -8
  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 +6 -1
  18. package/dist/src/host-view.js +46 -20
  19. package/dist/src/host.d.ts +2 -2
  20. package/dist/src/host.js +246 -118
  21. package/dist/src/index.d.ts +1 -2
  22. package/dist/src/index.js +1985 -1690
  23. package/dist/src/index.js.map +3 -3
  24. package/dist/src/paths.d.ts +8 -5
  25. package/dist/src/paths.js +24 -13
  26. package/dist/src/persistence.d.ts +1 -0
  27. package/dist/src/persistence.js +1 -0
  28. package/dist/src/pi-runtime-runner.js +4 -1
  29. package/dist/src/registry.d.ts +9 -7
  30. package/dist/src/registry.js +29 -71
  31. package/dist/src/runtime/agent-runner.d.ts +2 -0
  32. package/dist/src/script-tools.d.ts +27 -0
  33. package/dist/src/script-tools.js +87 -0
  34. package/dist/src/session-lease.d.ts +1 -1
  35. package/dist/src/session-lease.js +45 -11
  36. package/dist/src/settings.d.ts +19 -3
  37. package/dist/src/settings.js +63 -50
  38. package/dist/src/store.d.ts +3 -1
  39. package/dist/src/store.js +17 -1
  40. package/dist/src/trajectory.d.ts +12 -3
  41. package/dist/src/trajectory.js +32 -11
  42. package/dist/src/types.d.ts +129 -45
  43. package/dist/src/types.js +5 -8
  44. package/dist/src/utils.d.ts +21 -1
  45. package/dist/src/utils.js +46 -6
  46. package/dist/src/validation.d.ts +14 -3
  47. package/dist/src/validation.js +143 -87
  48. package/dist/starter/index.js +8 -11
  49. package/dist/starter/index.js.map +1 -1
  50. package/dist/starter/review-loop.d.ts +0 -13
  51. package/dist/starter/review-loop.js +8 -9
  52. package/dist/subagents/index.js +904 -956
  53. package/dist/subagents/index.js.map +3 -3
  54. package/dist/subagents/src/contracts.d.ts +48 -31
  55. package/dist/subagents/src/contracts.js +20 -9
  56. package/dist/subagents/src/index.d.ts +1 -1
  57. package/dist/subagents/src/index.js +14 -8
  58. package/dist/subagents/src/manager.js +52 -73
  59. package/dist/subagents/src/navigator.js +3 -14
  60. package/dist/subagents/src/view.d.ts +1 -1
  61. package/dist/subagents/src/view.js +15 -68
  62. package/dist/subagents/src/worktree.js +1 -1
  63. package/dist/trajectory/assets/index.html +35 -13
  64. package/dist/trajectory/index.js +576 -932
  65. package/dist/trajectory/index.js.map +3 -3
  66. package/dist/trajectory/src/assets/index.html +35 -13
  67. package/dist/trajectory/src/index.js +134 -104
  68. package/dist/trajectory/src/server.js +268 -27
  69. package/dist/trajectory/src/server.js.map +3 -3
  70. package/dist/trajectory/src/startup-mutex.d.ts +17 -0
  71. package/dist/trajectory/src/startup-mutex.js +172 -0
  72. package/examples/workflow-extension-template/README.md +4 -7
  73. package/examples/workflow-extension-template/extension.test.mjs +4 -8
  74. package/examples/workflow-extension-template/index.js +0 -3
  75. package/package.json +10 -10
  76. package/skills/pi-extensible-workflows/SKILL.md +12 -8
  77. package/src/agent-execution.ts +412 -164
  78. package/src/background-widget.ts +5 -11
  79. package/src/decoders.ts +51 -37
  80. package/src/execution.ts +46 -9
  81. package/src/host-delivery.ts +5 -8
  82. package/src/host-navigator.ts +4 -2
  83. package/src/host-phases.ts +1 -1
  84. package/src/host-recovery.ts +23 -28
  85. package/src/host-runtime.ts +44 -6
  86. package/src/host-view.ts +41 -16
  87. package/src/host.ts +229 -142
  88. package/src/index.ts +1 -2
  89. package/src/paths.ts +18 -9
  90. package/src/persistence.ts +1 -0
  91. package/src/pi-runtime-runner.ts +3 -1
  92. package/src/registry.ts +28 -64
  93. package/src/runtime/agent-runner.ts +2 -0
  94. package/src/script-tools.ts +76 -0
  95. package/src/session-lease.ts +34 -6
  96. package/src/settings.ts +56 -50
  97. package/src/store.ts +18 -2
  98. package/src/trajectory.ts +32 -15
  99. package/src/types.ts +82 -34
  100. package/src/utils.ts +42 -5
  101. package/src/validation.ts +121 -80
  102. package/starter/index.ts +0 -1
  103. package/starter/review-loop.ts +8 -9
  104. package/starter/roles/developer.md +0 -1
  105. package/starter/roles/oracle.md +0 -1
  106. package/starter/roles/researcher.md +0 -1
  107. package/starter/roles/reviewer.md +0 -1
  108. package/starter/roles/scout.md +0 -1
  109. package/subagents/README.md +10 -16
  110. package/subagents/src/contracts.ts +21 -11
  111. package/subagents/src/index.ts +17 -8
  112. package/subagents/src/manager.ts +48 -128
  113. package/subagents/src/navigator.ts +4 -13
  114. package/subagents/src/view.ts +16 -63
  115. package/subagents/src/worktree.ts +1 -1
  116. package/trajectory/src/assets/index.html +35 -13
  117. package/trajectory/src/index.ts +112 -91
  118. package/trajectory/src/server.ts +46 -10
  119. package/trajectory/src/startup-mutex.ts +127 -0
  120. package/dist/src/roles.d.ts +0 -70
  121. package/dist/src/roles.js +0 -325
  122. package/dist/starter/roles/developer.md +0 -6
  123. package/dist/starter/roles/oracle.md +0 -18
  124. package/dist/starter/roles/researcher.md +0 -20
  125. package/dist/starter/roles/reviewer.md +0 -19
  126. package/dist/starter/roles/scout.md +0 -18
  127. package/examples/workflow-extension-template/roles/reviewer.md +0 -4
  128. package/src/roles.ts +0 -326
@@ -49,7 +49,7 @@ const ENTRY_TYPE = "piewf-run-receipt";
49
49
  * Repaint interval. Fast enough that the spinner reads as motion rather than a
50
50
  * stutter; the underlying data is only re-read when an event says it changed.
51
51
  */
52
- const REPAINT_MS = 125;
52
+ export const REPAINT_MS = 125;
53
53
 
54
54
  /**
55
55
  * Filesystem rescan interval. Shell-only activity has no event, but it does not
@@ -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 }
@@ -17,6 +17,7 @@ type PersistedAgent = RunRecord["agents"][number];
17
17
  type PersistedPhaseRecord = NonNullable<RunRecord["phaseHistory"]>[number];
18
18
  type PersistedShellActivity = NonNullable<RunRecord["activeShellsByPhase"]>[number];
19
19
  type PersistedDelivery = NonNullable<RunRecord["delivery"]>;
20
+ type PersistedScriptCall = NonNullable<RunRecord["scriptCalls"]>[number];
20
21
  type PersistedIdentity = NonNullable<ScheduledAgentOptions["agentIdentity"]>;
21
22
  type PersistedOptions = ScheduledAgentOptions;
22
23
 
@@ -78,25 +79,19 @@ function decodeAgentResourceSelectors(value: unknown): AgentResourceSelectors |
78
79
  return { ...(skills === undefined ? {} : { skills }), ...(extensions === undefined ? {} : { extensions }), ...(tools === undefined ? {} : { tools }) };
79
80
  }
80
81
  function decodeContextFileScopes(value: unknown): ContextFileScope[] | undefined { return decodeArray(value, (scope) => isContextFileScope(scope) ? scope : undefined); }
81
- export function decodeAgentDefinition(value: unknown): AgentDefinition | undefined {
82
- if (!object(value)) return undefined;
83
- const prompt = optionalString(value.prompt);
84
- const description = optionalString(value.description);
85
- const model = optionalString(value.model);
86
- const thinking = value.thinking;
87
- const tools = value.tools === undefined ? undefined : decodeStringArray(value.tools);
88
- const skills = value.skills === undefined ? undefined : decodeStringArray(value.skills);
89
- const extensions = value.extensions === undefined ? undefined : decodeStringArray(value.extensions);
90
- 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);
91
89
  const contextFiles = value.contextFiles === undefined ? undefined : decodeContextFileScopes(value.contextFiles);
92
- const extensionSettings = value.extensionSettings === undefined ? undefined : decodeWorkflowExtensions(value.extensionSettings);
93
- if (prompt === INVALID_PERSISTED_VALUE || description === INVALID_PERSISTED_VALUE || model === INVALID_PERSISTED_VALUE || overrideSystemPrompt === INVALID_PERSISTED_VALUE) return undefined;
94
- 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;
95
- const foldedModel = typeof model === "string" && thinking !== undefined && !model.includes(":") ? `${model}:${thinking}` : model;
96
- return {
97
- ...(prompt === undefined ? {} : { prompt }), ...(description === undefined ? {} : { description }), ...(foldedModel === undefined ? {} : { model: foldedModel }),
98
- ...(tools === undefined ? {} : { tools }), ...(skills === undefined ? {} : { skills }), ...(extensions === undefined ? {} : { extensions }), ...(overrideSystemPrompt === undefined ? {} : { overrideSystemPrompt }), ...(contextFiles === undefined ? {} : { contextFiles }), ...(extensionSettings === undefined ? {} : { extensionSettings }),
99
- };
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 }) };
100
95
  }
101
96
  function decodeWorkflowMetadata(value: unknown): LaunchSnapshot["metadata"] | undefined {
102
97
  if (!object(value) || typeof value.name !== "string") return undefined;
@@ -200,17 +195,22 @@ function decodeScheduledAgentOptions(value: unknown): PersistedOptions | undefin
200
195
  const parentBreadcrumb = optionalString(value.parentBreadcrumb);
201
196
  const worktreeOwner = optionalString(value.worktreeOwner);
202
197
  const model = optionalString(value.model);
203
- const role = typeof value.role === "string" ? value.role : undefined;
204
198
  const contextFiles = value.contextFiles === undefined ? undefined : decodeContextFileScopes(value.contextFiles);
205
199
  const schema = value.schema === undefined ? undefined : decodeJsonObject(value.schema);
206
200
  const retries = optionalNumber(value.retries);
207
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;
208
208
  const agentOptions = value.agentOptions === undefined ? undefined : decodeJsonObject(value.agentOptions);
209
209
  const agentIdentity = value.agentIdentity === undefined ? undefined : decodeIdentity(value.agentIdentity);
210
210
  const sessionPath = optionalString(value.sessionPath);
211
211
  const continuity = value.continuity === undefined ? undefined : isAgentContinuity(value.continuity) ? value.continuity : INVALID_PERSISTED_VALUE;
212
212
  const extensionSettings = value.extensionSettings === undefined ? undefined : decodeWorkflowExtensions(value.extensionSettings);
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.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;
214
214
  return {
215
215
  label: value.label,
216
216
  ...(requestedLabel === undefined ? {} : { requestedLabel }),
@@ -220,7 +220,6 @@ function decodeScheduledAgentOptions(value: unknown): PersistedOptions | undefin
220
220
  ...(skills === undefined ? {} : { skills }), ...(extensions === undefined ? {} : { extensions }),
221
221
  ...(worktreeOwner === undefined ? {} : { worktreeOwner }),
222
222
  ...(model === undefined ? {} : { model }),
223
- ...(role === undefined ? {} : { role }),
224
223
  ...(contextFiles === undefined ? {} : { contextFiles }),
225
224
  ...(schema === undefined ? {} : { schema }),
226
225
  ...(retries === undefined ? {} : { retries }),
@@ -228,6 +227,9 @@ function decodeScheduledAgentOptions(value: unknown): PersistedOptions | undefin
228
227
  ...(sessionPath === undefined ? {} : { sessionPath }),
229
228
  ...(continuity === undefined ? {} : { continuity }),
230
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 } } : {}),
231
233
  ...(agentIdentity === undefined ? {} : { agentIdentity }),
232
234
  ...(extensionSettings === undefined ? {} : { extensionSettings }),
233
235
  };
@@ -268,11 +270,10 @@ function decodeAgentResourceInspection(value: unknown): AgentResourceInspection
268
270
  const sources = sourceRecord === undefined ? undefined : {
269
271
  global: decodeAgentResourceSelectors(sourceRecord.global),
270
272
  project: decodeAgentResourceSelectors(sourceRecord.project),
271
- ...(sourceRecord.role === undefined ? {} : { role: decodeAgentResourceSelectors(sourceRecord.role) }),
272
273
  ...(sourceRecord.call === undefined ? {} : { call: decodeAgentResourceSelectors(sourceRecord.call) }),
273
274
  };
274
- 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;
275
- 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 }) } }) };
276
277
  }
277
278
  function decodeAgentSetupSummary(value: unknown): NonNullable<NonNullable<AgentRecord["attemptDetails"]>[number]["setup"]> | undefined {
278
279
  if (!object(value) || typeof value.cwd !== "string") return undefined;
@@ -310,7 +311,6 @@ function decodeAgent(value: unknown): AgentRecord | undefined {
310
311
  const worktreeOwner = optionalString(value.worktreeOwner);
311
312
  const handle = optionalString(value.handle);
312
313
  const continuity = value.continuity === undefined ? undefined : isAgentContinuity(value.continuity) ? value.continuity : INVALID_PERSISTED_VALUE;
313
- const role = optionalString(value.role);
314
314
  const requestedModel = optionalString(value.requestedModel);
315
315
  const startedAt = optionalNumber(value.startedAt);
316
316
  const durationMs = optionalNumber(value.durationMs);
@@ -319,7 +319,7 @@ function decodeAgent(value: unknown): AgentRecord | undefined {
319
319
  const toolCalls = value.toolCalls === undefined ? undefined : decodeArray(value.toolCalls, decodeAgentToolCall);
320
320
  const activity = value.activity === undefined ? undefined : decodeAgentActivity(value.activity);
321
321
  const lastEventAt = optionalNumber(value.lastEventAt);
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 || 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;
323
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;
324
324
  return {
325
325
  ...(systemPrompt === undefined ? {} : { systemPrompt }),
@@ -336,7 +336,6 @@ function decodeAgent(value: unknown): AgentRecord | undefined {
336
336
  ...(worktreeOwner === undefined ? {} : { worktreeOwner }),
337
337
  ...(handle === undefined ? {} : { handle, turn: value.turn as number }),
338
338
  ...(continuity === undefined ? {} : { continuity }),
339
- ...(role === undefined ? {} : { role }),
340
339
  ...(requestedModel === undefined ? {} : { requestedModel }),
341
340
  model,
342
341
  tools,
@@ -394,6 +393,22 @@ function decodeRunEvent(value: unknown): WorkflowRunEvent | undefined {
394
393
  if (timestamp === INVALID_PERSISTED_VALUE) return undefined;
395
394
  return { type: value.type, message: value.message, ...(timestamp === undefined ? {} : { timestamp }) };
396
395
  }
396
+ function decodeScriptCall(value: unknown): PersistedScriptCall | undefined {
397
+ if (!object(value) || value.kind !== "shell" && value.kind !== "tool" || typeof value.name !== "string" || typeof value.path !== "string" || !finiteNumber(value.startedAt)) return undefined;
398
+ const shell = value.kind === "shell";
399
+ if (shell && !jsonValue(value.input)) return undefined;
400
+ const inputBytes = shell ? optionalNumber(value.inputBytes) : undefined;
401
+ const phase = optionalString(value.phase);
402
+ const finishedAt = optionalNumber(value.finishedAt);
403
+ let error: PersistedScriptCall["error"];
404
+ if (value.error !== undefined) {
405
+ if (shell) error = decodeWorkflowError(value.error);
406
+ else if (object(value.error) && typeof value.error.code === "string") error = { code: isWorkflowErrorCode(value.error.code) ? value.error.code : "INTERNAL_ERROR" };
407
+ if (!error) return undefined;
408
+ }
409
+ if (inputBytes === INVALID_PERSISTED_VALUE || phase === INVALID_PERSISTED_VALUE || finishedAt === INVALID_PERSISTED_VALUE) return undefined;
410
+ return { kind: value.kind, name: value.name, path: value.path, ...(shell && jsonValue(value.input) ? { input: value.input } : {}), ...(inputBytes === undefined ? {} : { inputBytes }), ...(phase === undefined ? {} : { phase }), startedAt: value.startedAt, ...(finishedAt === undefined ? {} : { finishedAt }), ...(error === undefined ? {} : { error }) };
411
+ }
397
412
  function decodeDelivery(value: unknown): PersistedDelivery | undefined {
398
413
  if (!object(value)) return undefined;
399
414
  const mode = value.mode;
@@ -423,6 +438,8 @@ export function decodePersistedRun(value: unknown, allowLegacyAgentSessions = fa
423
438
  const budgetEvents = value.budgetEvents === undefined ? undefined : decodeArray(value.budgetEvents, decodeBudgetEvent);
424
439
  const events = value.events === undefined ? undefined : decodeArray(value.events, decodeRunEvent);
425
440
  const delivery = value.delivery === undefined ? undefined : decodeDelivery(value.delivery);
441
+ const scriptCalls = value.scriptCalls === undefined ? undefined : decodeArray(value.scriptCalls, decodeScriptCall);
442
+ if (value.scriptCalls !== undefined && !scriptCalls) return undefined;
426
443
  if (!agentSessions || !agents || parentRunId === INVALID_PERSISTED_VALUE || value.retry !== undefined && !retry || phase === INVALID_PERSISTED_VALUE || value.phaseHistory !== undefined && !phaseHistory || phaseHistoryIndex === INVALID_PERSISTED_VALUE || activeShells === INVALID_PERSISTED_VALUE || activeShellStartedAt === INVALID_PERSISTED_VALUE || value.activeShellsByPhase !== undefined && !activeShellsByPhase || value.error !== undefined && !error || failedAt === INVALID_PERSISTED_VALUE || value.budget !== undefined && !budget || budgetVersion === INVALID_PERSISTED_VALUE || value.budgetVersion !== undefined && !integer(budgetVersion) || value.usage !== undefined && !usage || value.budgetEvents !== undefined && !budgetEvents || value.events !== undefined && !events || value.delivery !== undefined && !delivery) return undefined;
427
444
  if (value.agentSessions === undefined && !allowLegacyAgentSessions) return undefined;
428
445
  return {
@@ -441,6 +458,7 @@ export function decodePersistedRun(value: unknown, allowLegacyAgentSessions = fa
441
458
  ...(activeShells === undefined ? {} : { activeShells }),
442
459
  ...(activeShellStartedAt === undefined ? {} : { activeShellStartedAt }),
443
460
  ...(activeShellsByPhase === undefined ? {} : { activeShellsByPhase }),
461
+ ...(scriptCalls === undefined ? {} : { scriptCalls }),
444
462
  ...(error === undefined ? {} : { error }),
445
463
  ...(failedAt === undefined ? {} : { failedAt }),
446
464
  ...(budget === undefined ? {} : { budget }),
@@ -466,13 +484,11 @@ export function decodeLaunchSnapshot(value: unknown): LaunchSnapshot | undefined
466
484
  const phases = value.phases === undefined ? undefined : decodeStringArray(value.phases);
467
485
  const models = decodeStringArray(value.models);
468
486
  const tools = decodeStringArray(value.tools);
469
- const agentTypes = decodeStringArray(value.agentTypes);
470
- const roles = value.roles === undefined ? undefined : decodeRecord(value.roles, decodeAgentDefinition);
471
- const projectRoles = value.projectRoles === undefined ? undefined : decodeStringArray(value.projectRoles);
487
+ const agentConfigurations = decodeRecord(value.agentConfigurations, decodePreparedAgentConfiguration);
472
488
  const schemas = decodeArray(value.schemas, decodeJsonObject);
473
- 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;
474
490
  return {
475
- ...(identityVersion === undefined ? {} : { identityVersion }),
491
+ identityVersion: LAUNCH_SNAPSHOT_IDENTITY_VERSION,
476
492
  ...(launchMode === undefined ? {} : { launchMode }),
477
493
  script: value.script,
478
494
  args,
@@ -485,9 +501,7 @@ export function decodeLaunchSnapshot(value: unknown): LaunchSnapshot | undefined
485
501
  ...(phases === undefined ? {} : { phases }),
486
502
  models,
487
503
  tools,
488
- agentTypes,
489
- ...(roles === undefined ? {} : { roles }),
490
- ...(projectRoles === undefined ? {} : { projectRoles }),
504
+ agentConfigurations,
491
505
  schemas,
492
506
  };
493
507
  }
package/src/execution.ts CHANGED
@@ -5,9 +5,9 @@ import { join } from "node:path";
5
5
  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
- import type { AgentIdentity, AgentAttemptSummary, FunctionIdentity, JsonValue, ShellIdentity, ShellOptions, ShellResult, WorkflowAgentSessionReference, WorkflowBridge, WorkflowErrorCode, WorkflowExecution } from "./types.js";
9
- import { WorkflowError, roleNameOf, sumAccounting, zeroAccounting } from "./types.js";
10
- import { asWorkflowError, errorText, fail, isWorkflowAuthored, isWorkflowErrorCode, jsonValue, markWorkflowAuthored, object, positiveInteger } from "./utils.js";
8
+ import type { AgentIdentity, AgentAttemptSummary, FunctionIdentity, JsonValue, ShellIdentity, ShellOptions, ShellResult, ToolIdentity, WorkflowAgentSessionReference, WorkflowBridge, WorkflowErrorCode, WorkflowExecution } from "./types.js";
9
+ import { WorkflowError, sumAccounting, zeroAccounting } from "./types.js";
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
 
13
13
  export const RPC_LIMIT_BYTES = 10 * 1024 * 1024;
@@ -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")));
@@ -275,6 +275,30 @@ const functions = Object.freeze(Object.fromEntries(Object.entries(config.functio
275
275
  Object.defineProperty(result, "toJSON", { value() { throw workError("INVALID_METADATA", "Workflow function result is a Promise; await it before serialization"); } });
276
276
  return result;
277
277
  }])));
278
+ // tools.<id>(args) runs a Pi session tool through the host. Any identifier resolves, so a resumed run
279
+ // replays journaled calls even when the tool is gone; the host rejects identifiers it cannot call.
280
+ // A call is the n-th call with the same arguments in its scope, so replay does not depend on the
281
+ // order in which concurrent calls start.
282
+ const NOT_TOOLS = new Set(["then", "toJSON", "toString", "valueOf", "constructor"]);
283
+ const sortedJson = value => Array.isArray(value) ? value.map(sortedJson) : value && typeof value === "object" ? Object.fromEntries(Object.keys(value).sort().map(key => [key, sortedJson(value[key])])) : value;
284
+ const toolOccurrences = new Map();
285
+ const toolCall = id => (...values) => {
286
+ if (worktreeOwners.getStore()) throw workError("INVALID_METADATA", "tools." + id + " is unavailable inside withWorktree: session tools resolve paths against the session working directory");
287
+ if (values.length > 1 || values[0] !== undefined && (!values[0] || typeof values[0] !== "object" || Array.isArray(values[0]))) throw workError("INVALID_METADATA", "tools." + id + " takes one arguments object");
288
+ const args = values[0] === undefined ? {} : values[0];
289
+ const structuralPath = inheritedAgentPath.getStore() || [];
290
+ const key = JSON.stringify([structuralPath, id, sortedJson(args)]);
291
+ const occurrence = (toolOccurrences.get(key) || 0) + 1;
292
+ toolOccurrences.set(key, occurrence);
293
+ return guardedResult(rpc("tool", [id, args, { structuralPath: [...structuralPath], occurrence }]), "tool");
294
+ };
295
+ const tools = new Proxy(Object.create(null), {
296
+ get: (_target, key) => typeof key === "string" && !NOT_TOOLS.has(key) ? toolCall(key) : undefined,
297
+ // Probing would branch on the session that resumed the run; the launch already checks named tools.
298
+ has: () => { throw workError("INVALID_METADATA", "Workflow scripts cannot probe tools with 'in'; call tools.<name>(...) directly, and the launch checks that the tool exists"); },
299
+ set: () => false,
300
+ defineProperty: () => false,
301
+ });
278
302
  const recordEntries = (value, kind) => {
279
303
  if (!value || typeof value !== "object" || Array.isArray(value)) throw workError("INVALID_METADATA", kind + " must be a record");
280
304
  return Object.entries(value);
@@ -335,7 +359,7 @@ const pipeline = async (operationName, items, stages) => {
335
359
  return Object.fromEntries(results.map(result => [result.name, result.value]));
336
360
  };
337
361
  const safeMath = Object.fromEntries(Object.getOwnPropertyNames(Math).filter(name => name !== "random").map(name => [name, Math[name]]));
338
- const sandbox = { agent, shell, withWorktree: rejectWorktree, prompt, checkpoint, parallel, pipeline, phase, log, args: config.args, Promise, JSON, Math: Object.freeze(safeMath) };
362
+ const sandbox = { agent, shell, withWorktree: rejectWorktree, prompt, checkpoint, parallel, pipeline, phase, log, tools, args: config.args, Promise, JSON, Math: Object.freeze(safeMath) };
339
363
  for (const [name, fn] of Object.entries(functions)) Object.defineProperty(sandbox, name, { value: fn, writable: false, configurable: false });
340
364
  for (const name of ["Date","eval","Function","WebAssembly","process","require","module","exports","console","fetch","XMLHttpRequest","WebSocket","performance","crypto","setTimeout","setInterval","setImmediate","queueMicrotask","Intl","SharedArrayBuffer","Atomics"]) sandbox[name] = undefined;
341
365
  const context = vm.createContext(sandbox, { codeGeneration: { strings: false, wasm: false } });
@@ -384,6 +408,14 @@ function readShellIdentity(value: unknown): ShellIdentity {
384
408
  const identity = readAgentIdentity(value);
385
409
  return { structuralPath: identity.structuralPath, callSite: identity.callSite, occurrence: identity.occurrence, ...(identity.worktreeOwner ? { worktreeOwner: identity.worktreeOwner } : {}) };
386
410
  }
411
+ function readToolIdentity(value: unknown): ToolIdentity {
412
+ if (!object(value) || !Array.isArray(value.structuralPath) || !value.structuralPath.every((part): part is string => typeof part === "string" && Boolean(part.trim())) || !positiveInteger(value.occurrence)) fail("INTERNAL_ERROR", "Invalid workflow tool identity");
413
+ return { structuralPath: [...value.structuralPath], occurrence: value.occurrence };
414
+ }
415
+ /** The journal path of a script tool call; the arguments' digest keeps calls with different arguments apart whatever order they start in. */
416
+ export function toolIdentityPath(identifier: string, args: JsonValue, identity: ToolIdentity): string {
417
+ return operationPath("tool", ...identity.structuralPath, identifier, `args:${jsonDigest(args)}`, String(identity.occurrence));
418
+ }
387
419
  export function agentHandleTurnPath(handle: string, turn: number): string {
388
420
  return operationPath("agent", "handle", handle, `turn:${String(turn)}`);
389
421
  }
@@ -552,7 +584,7 @@ export function runWorkflow(script: string, args: JsonValue = null, bridge: Work
552
584
  const opts = validateAgentOptions(values[1]);
553
585
  const identity = readAgentIdentity(values[2]);
554
586
  const path = agentIdentityPath(identity);
555
- const label = typeof opts.label === "string" ? opts.label : roleNameOf(opts.role) ?? "agent";
587
+ const label = typeof opts.label === "string" ? opts.label : "agent";
556
588
  try {
557
589
  const result = await bridge.agent(values[0], opts, controller.signal, identity);
558
590
  value = branded({ name: label, ok: true, value: result ?? null });
@@ -595,6 +627,11 @@ export function runWorkflow(script: string, args: JsonValue = null, bridge: Work
595
627
  if (!OUTCOME_ERRORS.has(typed.code)) throw typed;
596
628
  value = branded({ name, ok: false, failedAt: name, error: workerErrorShape(typed) });
597
629
  }
630
+ } else if (method === "tool") {
631
+ if (typeof values[0] !== "string" || !object(values[1])) fail("INTERNAL_ERROR", "tool requires an identifier and object arguments");
632
+ const identity = readToolIdentity(values[2]);
633
+ if (!bridge.tool) fail("UNKNOWN_TOOL", `tools.${values[0]} is unavailable: this run has no session tools`);
634
+ value = await bridge.tool(values[0], values[1], controller.signal, identity);
598
635
  } else if (method === "worktree") {
599
636
  if (!bridge.worktree || typeof values[0] !== "string" || !values[0]) fail("INTERNAL_ERROR", "worktree requires an active host bridge and scope");
600
637
  value = await bridge.worktree(values[0], controller.signal);
@@ -649,4 +686,4 @@ export async function persistAgentAttempts(store: RunStore, id: string, attempts
649
686
  return { ...run, agents: run.agents.map((candidate) => candidate.id === id ? { ...candidate, attempts: attempts.length, attemptDetails, accounting: total } : candidate), agentSessions: [...run.agentSessions.filter(({ transport, sessionId }) => !sessionKeys.has(`${transport}:${sessionId}`)), ...sessions] };
650
687
  });
651
688
  }
652
- export type { AgentIdentity, FunctionIdentity, ShellIdentity, WorkflowBridge, WorkflowExecution } from "./types.js";
689
+ export type { AgentIdentity, FunctionIdentity, ShellIdentity, ToolIdentity, WorkflowBridge, WorkflowExecution } from "./types.js";
@@ -95,13 +95,14 @@ 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}.`,
104
104
  SHELL_FAILED: (detail) => `The workflow shell command failed: ${detail}.`,
105
+ TOOL_FAILED: (detail) => `The workflow tool call failed: ${detail}.`,
105
106
  AGENT_TIMEOUT: (detail) => `The workflow agent timed out: ${detail}.`,
106
107
  AGENT_FAILED: (detail) => `The workflow agent failed: ${detail}.`,
107
108
  AGENT_RESULT_COLLECTED: (detail) => `The nested agent result was already collected: ${detail}.`,
@@ -212,7 +213,6 @@ function boundedWorkflowFailureDiagnostics(value: WorkflowFailureDiagnostics): W
212
213
  ...(value.failedAgent ? { failedAgent: {
213
214
  id: utf8Prefix(value.failedAgent.id, 128),
214
215
  ...(value.failedAgent.label ? { label: utf8Prefix(value.failedAgent.label, 128) } : {}),
215
- ...(value.failedAgent.role ? { role: utf8Prefix(value.failedAgent.role, 128) } : {}),
216
216
  structuralPath: value.failedAgent.structuralPath.slice(0, 8).map((part) => utf8Prefix(part, 128)),
217
217
  attempt: value.failedAgent.attempt,
218
218
  ...(value.failedAgent.transport ? { transport: utf8Prefix(value.failedAgent.transport, 128) } : {}),
@@ -221,7 +221,6 @@ function boundedWorkflowFailureDiagnostics(value: WorkflowFailureDiagnostics): W
221
221
  completedSiblingAgents: (value.completedSiblingAgents ?? []).slice(0, 16).map((agent) => ({
222
222
  id: utf8Prefix(agent.id, 128),
223
223
  ...(agent.label ? { label: utf8Prefix(agent.label, 128) } : {}),
224
- ...(agent.role ? { role: utf8Prefix(agent.role, 128) } : {}),
225
224
  structuralPath: agent.structuralPath.slice(0, 8).map((part) => utf8Prefix(part, 128)),
226
225
  })),
227
226
  completedSiblingPaths: value.completedSiblingPaths.slice(0, 16).map((path) => path.slice(0, 8).map((part) => utf8Prefix(part, 128))),
@@ -277,7 +276,6 @@ export async function createWorkflowFailureDiagnostics(store: RunStore, metadata
277
276
  const failedAgent = failedAgentRecord ? {
278
277
  id: failedAgentRecord.id,
279
278
  ...(failedAgentRecord.label ?? failedAgentRecord.name ? { label: failedAgentRecord.label ?? failedAgentRecord.name } : {}),
280
- ...(failedAgentRecord.role ? { role: failedAgentRecord.role } : {}),
281
279
  structuralPath: [...(failedAgentRecord.structuralPath ?? [])],
282
280
  attempt: Math.max(1, failedAttempt?.attempt ?? failedAgentRecord.attempts),
283
281
  ...(failedAttempt?.transport ? { transport: failedAttempt.transport } : {}),
@@ -289,7 +287,6 @@ export async function createWorkflowFailureDiagnostics(store: RunStore, metadata
289
287
  }).map((agent) => ({
290
288
  id: agent.id,
291
289
  ...(agent.label ?? agent.name ? { label: agent.label ?? agent.name } : {}),
292
- ...(agent.role ? { role: agent.role } : {}),
293
290
  structuralPath: [...(agent.structuralPath ?? [])],
294
291
  } satisfies WorkflowSiblingAgent));
295
292
  const completedSiblingPaths = completedSiblingAgents.map((agent) => [...agent.structuralPath]);
@@ -315,9 +312,9 @@ export async function createWorkflowFailureDiagnostics(store: RunStore, metadata
315
312
  }
316
313
 
317
314
  export function formatWorkflowFailureDiagnostics(diagnostic: WorkflowFailureDiagnostics): string {
318
- 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)";
319
316
  const siblingAgents = diagnostic.completedSiblingAgents;
320
- 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)";
321
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}`] : [];
322
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");
323
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> }