@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
@@ -22,6 +22,11 @@ export type BackgroundWidgetAPI = Pick<ExtensionAPI, "appendEntry" | "on"> & {
22
22
  registerEntryRenderer?: ExtensionAPI["registerEntryRenderer"];
23
23
  registerShortcut?: ExtensionAPI["registerShortcut"];
24
24
  };
25
+ /**
26
+ * Repaint interval. Fast enough that the spinner reads as motion rather than a
27
+ * stutter; the underlying data is only re-read when an event says it changed.
28
+ */
29
+ export declare const REPAINT_MS = 125;
25
30
  /**
26
31
  * How long an agent may go without an event before the widget says so.
27
32
  *
@@ -55,7 +60,6 @@ interface ReceiptAgent {
55
60
  name: string;
56
61
  state: string;
57
62
  model?: string;
58
- role?: string;
59
63
  requestedModel?: string;
60
64
  tools?: readonly string[];
61
65
  /** Accepted for rendering older receipt entries; new receipts persist granted tools. */
@@ -25,7 +25,7 @@ const ENTRY_TYPE = "piewf-run-receipt";
25
25
  * Repaint interval. Fast enough that the spinner reads as motion rather than a
26
26
  * stutter; the underlying data is only re-read when an event says it changed.
27
27
  */
28
- const REPAINT_MS = 125;
28
+ export const REPAINT_MS = 125;
29
29
  /**
30
30
  * Filesystem rescan interval. Shell-only activity has no event, but it does not
31
31
  * need to be polled at the animation cadence.
@@ -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,
@@ -434,6 +434,28 @@ function decodeRunEvent(value) {
434
434
  return undefined;
435
435
  return { type: value.type, message: value.message, ...(timestamp === undefined ? {} : { timestamp }) };
436
436
  }
437
+ function decodeScriptCall(value) {
438
+ if (!object(value) || value.kind !== "shell" && value.kind !== "tool" || typeof value.name !== "string" || typeof value.path !== "string" || !finiteNumber(value.startedAt))
439
+ return undefined;
440
+ const shell = value.kind === "shell";
441
+ if (shell && !jsonValue(value.input))
442
+ return undefined;
443
+ const inputBytes = shell ? optionalNumber(value.inputBytes) : undefined;
444
+ const phase = optionalString(value.phase);
445
+ const finishedAt = optionalNumber(value.finishedAt);
446
+ let error;
447
+ if (value.error !== undefined) {
448
+ if (shell)
449
+ error = decodeWorkflowError(value.error);
450
+ else if (object(value.error) && typeof value.error.code === "string")
451
+ error = { code: isWorkflowErrorCode(value.error.code) ? value.error.code : "INTERNAL_ERROR" };
452
+ if (!error)
453
+ return undefined;
454
+ }
455
+ if (inputBytes === INVALID_PERSISTED_VALUE || phase === INVALID_PERSISTED_VALUE || finishedAt === INVALID_PERSISTED_VALUE)
456
+ return undefined;
457
+ 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 }) };
458
+ }
437
459
  function decodeDelivery(value) {
438
460
  if (!object(value))
439
461
  return undefined;
@@ -467,6 +489,9 @@ export function decodePersistedRun(value, allowLegacyAgentSessions = false) {
467
489
  const budgetEvents = value.budgetEvents === undefined ? undefined : decodeArray(value.budgetEvents, decodeBudgetEvent);
468
490
  const events = value.events === undefined ? undefined : decodeArray(value.events, decodeRunEvent);
469
491
  const delivery = value.delivery === undefined ? undefined : decodeDelivery(value.delivery);
492
+ const scriptCalls = value.scriptCalls === undefined ? undefined : decodeArray(value.scriptCalls, decodeScriptCall);
493
+ if (value.scriptCalls !== undefined && !scriptCalls)
494
+ return undefined;
470
495
  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)
471
496
  return undefined;
472
497
  if (value.agentSessions === undefined && !allowLegacyAgentSessions)
@@ -487,6 +512,7 @@ export function decodePersistedRun(value, allowLegacyAgentSessions = false) {
487
512
  ...(activeShells === undefined ? {} : { activeShells }),
488
513
  ...(activeShellStartedAt === undefined ? {} : { activeShellStartedAt }),
489
514
  ...(activeShellsByPhase === undefined ? {} : { activeShellsByPhase }),
515
+ ...(scriptCalls === undefined ? {} : { scriptCalls }),
490
516
  ...(error === undefined ? {} : { error }),
491
517
  ...(failedAt === undefined ? {} : { failedAt }),
492
518
  ...(budget === undefined ? {} : { budget }),
@@ -513,14 +539,12 @@ export function decodeLaunchSnapshot(value) {
513
539
  const phases = value.phases === undefined ? undefined : decodeStringArray(value.phases);
514
540
  const models = decodeStringArray(value.models);
515
541
  const tools = decodeStringArray(value.tools);
516
- const agentTypes = decodeStringArray(value.agentTypes);
517
- const roles = value.roles === undefined ? undefined : decodeRecord(value.roles, decodeAgentDefinition);
518
- const projectRoles = value.projectRoles === undefined ? undefined : decodeStringArray(value.projectRoles);
542
+ const agentConfigurations = decodeRecord(value.agentConfigurations, decodePreparedAgentConfiguration);
519
543
  const schemas = decodeArray(value.schemas, decodeJsonObject);
520
- 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)
521
545
  return undefined;
522
546
  return {
523
- ...(identityVersion === undefined ? {} : { identityVersion }),
547
+ identityVersion: LAUNCH_SNAPSHOT_IDENTITY_VERSION,
524
548
  ...(launchMode === undefined ? {} : { launchMode }),
525
549
  script: value.script,
526
550
  args,
@@ -533,9 +557,7 @@ export function decodeLaunchSnapshot(value) {
533
557
  ...(phases === undefined ? {} : { phases }),
534
558
  models,
535
559
  tools,
536
- agentTypes,
537
- ...(roles === undefined ? {} : { roles }),
538
- ...(projectRoles === undefined ? {} : { projectRoles }),
560
+ agentConfigurations,
539
561
  schemas,
540
562
  };
541
563
  }
@@ -1,10 +1,14 @@
1
1
  import { RunStore } from "./persistence.js";
2
2
  import type { AgentAttempt } from "./agent-execution.js";
3
- import type { AgentIdentity, JsonValue, ShellIdentity, ShellOptions, ShellResult, WorkflowBridge, WorkflowExecution } from "./types.js";
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;
10
+ /** The journal path of a script tool call; the arguments' digest keeps calls with different arguments apart whatever order they start in. */
11
+ export declare function toolIdentityPath(identifier: string, args: JsonValue, identity: ToolIdentity): string;
8
12
  export declare function agentHandleTurnPath(handle: string, turn: number): string;
9
13
  export declare function agentIdentityPath(identity: AgentIdentity): string;
10
14
  export declare function shellIdentityPath(identity: ShellIdentity): string;
@@ -16,4 +20,4 @@ export declare function executeShellCommand(command: string, options: ShellOptio
16
20
  export declare function runWorkflow(script: string, args?: JsonValue, bridge?: WorkflowBridge, signal?: AbortSignal): WorkflowExecution;
17
21
  export declare function persistActiveAgentAttempt(store: RunStore, id: string, active: AgentAttempt): Promise<void>;
18
22
  export declare function persistAgentAttempts(store: RunStore, id: string, attempts: readonly AgentAttempt[]): Promise<void>;
19
- export type { AgentIdentity, FunctionIdentity, ShellIdentity, WorkflowBridge, WorkflowExecution } from "./types.js";
23
+ export type { AgentIdentity, FunctionIdentity, ShellIdentity, ToolIdentity, WorkflowBridge, WorkflowExecution } from "./types.js";
@@ -4,8 +4,8 @@ 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";
8
- import { asWorkflowError, errorText, fail, isWorkflowAuthored, isWorkflowErrorCode, jsonValue, markWorkflowAuthored, object, positiveInteger } from "./utils.js";
7
+ import { WorkflowError, sumAccounting, zeroAccounting } from "./types.js";
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;
11
11
  export const HEARTBEAT_TIMEOUT_MS = 5000;
@@ -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")));
@@ -268,6 +268,30 @@ const functions = Object.freeze(Object.fromEntries(Object.entries(config.functio
268
268
  Object.defineProperty(result, "toJSON", { value() { throw workError("INVALID_METADATA", "Workflow function result is a Promise; await it before serialization"); } });
269
269
  return result;
270
270
  }])));
271
+ // tools.<id>(args) runs a Pi session tool through the host. Any identifier resolves, so a resumed run
272
+ // replays journaled calls even when the tool is gone; the host rejects identifiers it cannot call.
273
+ // A call is the n-th call with the same arguments in its scope, so replay does not depend on the
274
+ // order in which concurrent calls start.
275
+ const NOT_TOOLS = new Set(["then", "toJSON", "toString", "valueOf", "constructor"]);
276
+ 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;
277
+ const toolOccurrences = new Map();
278
+ const toolCall = id => (...values) => {
279
+ if (worktreeOwners.getStore()) throw workError("INVALID_METADATA", "tools." + id + " is unavailable inside withWorktree: session tools resolve paths against the session working directory");
280
+ 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");
281
+ const args = values[0] === undefined ? {} : values[0];
282
+ const structuralPath = inheritedAgentPath.getStore() || [];
283
+ const key = JSON.stringify([structuralPath, id, sortedJson(args)]);
284
+ const occurrence = (toolOccurrences.get(key) || 0) + 1;
285
+ toolOccurrences.set(key, occurrence);
286
+ return guardedResult(rpc("tool", [id, args, { structuralPath: [...structuralPath], occurrence }]), "tool");
287
+ };
288
+ const tools = new Proxy(Object.create(null), {
289
+ get: (_target, key) => typeof key === "string" && !NOT_TOOLS.has(key) ? toolCall(key) : undefined,
290
+ // Probing would branch on the session that resumed the run; the launch already checks named tools.
291
+ has: () => { throw workError("INVALID_METADATA", "Workflow scripts cannot probe tools with 'in'; call tools.<name>(...) directly, and the launch checks that the tool exists"); },
292
+ set: () => false,
293
+ defineProperty: () => false,
294
+ });
271
295
  const recordEntries = (value, kind) => {
272
296
  if (!value || typeof value !== "object" || Array.isArray(value)) throw workError("INVALID_METADATA", kind + " must be a record");
273
297
  return Object.entries(value);
@@ -328,7 +352,7 @@ const pipeline = async (operationName, items, stages) => {
328
352
  return Object.fromEntries(results.map(result => [result.name, result.value]));
329
353
  };
330
354
  const safeMath = Object.fromEntries(Object.getOwnPropertyNames(Math).filter(name => name !== "random").map(name => [name, Math[name]]));
331
- const sandbox = { agent, shell, withWorktree: rejectWorktree, prompt, checkpoint, parallel, pipeline, phase, log, args: config.args, Promise, JSON, Math: Object.freeze(safeMath) };
355
+ const sandbox = { agent, shell, withWorktree: rejectWorktree, prompt, checkpoint, parallel, pipeline, phase, log, tools, args: config.args, Promise, JSON, Math: Object.freeze(safeMath) };
332
356
  for (const [name, fn] of Object.entries(functions)) Object.defineProperty(sandbox, name, { value: fn, writable: false, configurable: false });
333
357
  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;
334
358
  const context = vm.createContext(sandbox, { codeGeneration: { strings: false, wasm: false } });
@@ -378,6 +402,15 @@ function readShellIdentity(value) {
378
402
  const identity = readAgentIdentity(value);
379
403
  return { structuralPath: identity.structuralPath, callSite: identity.callSite, occurrence: identity.occurrence, ...(identity.worktreeOwner ? { worktreeOwner: identity.worktreeOwner } : {}) };
380
404
  }
405
+ function readToolIdentity(value) {
406
+ if (!object(value) || !Array.isArray(value.structuralPath) || !value.structuralPath.every((part) => typeof part === "string" && Boolean(part.trim())) || !positiveInteger(value.occurrence))
407
+ fail("INTERNAL_ERROR", "Invalid workflow tool identity");
408
+ return { structuralPath: [...value.structuralPath], occurrence: value.occurrence };
409
+ }
410
+ /** The journal path of a script tool call; the arguments' digest keeps calls with different arguments apart whatever order they start in. */
411
+ export function toolIdentityPath(identifier, args, identity) {
412
+ return operationPath("tool", ...identity.structuralPath, identifier, `args:${jsonDigest(args)}`, String(identity.occurrence));
413
+ }
381
414
  export function agentHandleTurnPath(handle, turn) {
382
415
  return operationPath("agent", "handle", handle, `turn:${String(turn)}`);
383
416
  }
@@ -622,7 +655,7 @@ export function runWorkflow(script, args = null, bridge = {}, signal) {
622
655
  const opts = validateAgentOptions(values[1]);
623
656
  const identity = readAgentIdentity(values[2]);
624
657
  const path = agentIdentityPath(identity);
625
- const label = typeof opts.label === "string" ? opts.label : roleNameOf(opts.role) ?? "agent";
658
+ const label = typeof opts.label === "string" ? opts.label : "agent";
626
659
  try {
627
660
  const result = await bridge.agent(values[0], opts, controller.signal, identity);
628
661
  value = branded({ name: label, ok: true, value: result ?? null });
@@ -681,6 +714,14 @@ export function runWorkflow(script, args = null, bridge = {}, signal) {
681
714
  value = branded({ name, ok: false, failedAt: name, error: workerErrorShape(typed) });
682
715
  }
683
716
  }
717
+ else if (method === "tool") {
718
+ if (typeof values[0] !== "string" || !object(values[1]))
719
+ fail("INTERNAL_ERROR", "tool requires an identifier and object arguments");
720
+ const identity = readToolIdentity(values[2]);
721
+ if (!bridge.tool)
722
+ fail("UNKNOWN_TOOL", `tools.${values[0]} is unavailable: this run has no session tools`);
723
+ value = await bridge.tool(values[0], values[1], controller.signal, identity);
724
+ }
684
725
  else if (method === "worktree") {
685
726
  if (!bridge.worktree || typeof values[0] !== "string" || !values[0])
686
727
  fail("INTERNAL_ERROR", "worktree requires an active host bridge and scope");
@@ -101,13 +101,14 @@ 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}.`,
110
110
  SHELL_FAILED: (detail) => `The workflow shell command failed: ${detail}.`,
111
+ TOOL_FAILED: (detail) => `The workflow tool call failed: ${detail}.`,
111
112
  AGENT_TIMEOUT: (detail) => `The workflow agent timed out: ${detail}.`,
112
113
  AGENT_FAILED: (detail) => `The workflow agent failed: ${detail}.`,
113
114
  AGENT_RESULT_COLLECTED: (detail) => `The nested agent result was already collected: ${detail}.`,
@@ -204,7 +205,6 @@ function boundedWorkflowFailureDiagnostics(value) {
204
205
  ...(value.failedAgent ? { failedAgent: {
205
206
  id: utf8Prefix(value.failedAgent.id, 128),
206
207
  ...(value.failedAgent.label ? { label: utf8Prefix(value.failedAgent.label, 128) } : {}),
207
- ...(value.failedAgent.role ? { role: utf8Prefix(value.failedAgent.role, 128) } : {}),
208
208
  structuralPath: value.failedAgent.structuralPath.slice(0, 8).map((part) => utf8Prefix(part, 128)),
209
209
  attempt: value.failedAgent.attempt,
210
210
  ...(value.failedAgent.transport ? { transport: utf8Prefix(value.failedAgent.transport, 128) } : {}),
@@ -213,7 +213,6 @@ function boundedWorkflowFailureDiagnostics(value) {
213
213
  completedSiblingAgents: (value.completedSiblingAgents ?? []).slice(0, 16).map((agent) => ({
214
214
  id: utf8Prefix(agent.id, 128),
215
215
  ...(agent.label ? { label: utf8Prefix(agent.label, 128) } : {}),
216
- ...(agent.role ? { role: utf8Prefix(agent.role, 128) } : {}),
217
216
  structuralPath: agent.structuralPath.slice(0, 8).map((part) => utf8Prefix(part, 128)),
218
217
  })),
219
218
  completedSiblingPaths: value.completedSiblingPaths.slice(0, 16).map((path) => path.slice(0, 8).map((part) => utf8Prefix(part, 128))),
@@ -305,7 +304,6 @@ export async function createWorkflowFailureDiagnostics(store, metadata, error, r
305
304
  const failedAgent = failedAgentRecord ? {
306
305
  id: failedAgentRecord.id,
307
306
  ...(failedAgentRecord.label ?? failedAgentRecord.name ? { label: failedAgentRecord.label ?? failedAgentRecord.name } : {}),
308
- ...(failedAgentRecord.role ? { role: failedAgentRecord.role } : {}),
309
307
  structuralPath: [...(failedAgentRecord.structuralPath ?? [])],
310
308
  attempt: Math.max(1, failedAttempt?.attempt ?? failedAgentRecord.attempts),
311
309
  ...(failedAttempt?.transport ? { transport: failedAttempt.transport } : {}),
@@ -318,7 +316,6 @@ export async function createWorkflowFailureDiagnostics(store, metadata, error, r
318
316
  }).map((agent) => ({
319
317
  id: agent.id,
320
318
  ...(agent.label ?? agent.name ? { label: agent.label ?? agent.name } : {}),
321
- ...(agent.role ? { role: agent.role } : {}),
322
319
  structuralPath: [...(agent.structuralPath ?? [])],
323
320
  }));
324
321
  const completedSiblingPaths = completedSiblingAgents.map((agent) => [...agent.structuralPath]);
@@ -346,9 +343,9 @@ export async function createWorkflowFailureDiagnostics(store, metadata, error, r
346
343
  });
347
344
  }
348
345
  export function formatWorkflowFailureDiagnostics(diagnostic) {
349
- 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)";
350
347
  const siblingAgents = diagnostic.completedSiblingAgents;
351
- 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)";
352
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}`] : [];
353
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");
354
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[];