@marcoscale98/pi-extensible-workflows 5.19.1-fork.1 → 6.2.1-fork.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +77 -0
- package/README.md +2 -2
- package/dist/src/agent-execution.d.ts +43 -10
- package/dist/src/agent-execution.js +365 -127
- package/dist/src/background-widget.d.ts +0 -1
- package/dist/src/background-widget.js +4 -9
- package/dist/src/decoders.d.ts +2 -3
- package/dist/src/decoders.js +33 -37
- package/dist/src/execution.d.ts +2 -0
- package/dist/src/execution.js +5 -5
- package/dist/src/host-delivery.js +4 -8
- package/dist/src/host-navigator.js +5 -2
- package/dist/src/host-phases.d.ts +1 -0
- package/dist/src/host-recovery.d.ts +6 -6
- package/dist/src/host-recovery.js +14 -22
- package/dist/src/host-runtime.js +56 -5
- package/dist/src/host-view.d.ts +1 -1
- package/dist/src/host-view.js +17 -15
- package/dist/src/host.js +62 -87
- package/dist/src/index.d.ts +1 -2
- package/dist/src/index.js +1523 -1630
- package/dist/src/index.js.map +3 -3
- package/dist/src/paths.d.ts +7 -6
- package/dist/src/paths.js +24 -15
- package/dist/src/persistence.d.ts +1 -0
- package/dist/src/persistence.js +1 -0
- package/dist/src/pi-runtime-runner.js +4 -1
- package/dist/src/registry.d.ts +9 -7
- package/dist/src/registry.js +28 -70
- package/dist/src/runtime/agent-runner.d.ts +2 -0
- package/dist/src/session-lease.d.ts +1 -1
- package/dist/src/session-lease.js +45 -11
- package/dist/src/settings.d.ts +6 -3
- package/dist/src/settings.js +36 -47
- package/dist/src/store.d.ts +3 -1
- package/dist/src/store.js +17 -1
- package/dist/src/trajectory.d.ts +0 -1
- package/dist/src/trajectory.js +2 -3
- package/dist/src/types.d.ts +100 -44
- package/dist/src/types.js +4 -7
- package/dist/src/utils.d.ts +3 -2
- package/dist/src/utils.js +20 -10
- package/dist/src/validation.d.ts +8 -2
- package/dist/src/validation.js +122 -86
- package/dist/starter/index.js +8 -9
- package/dist/starter/index.js.map +1 -1
- package/dist/starter/review-loop.js +8 -7
- package/dist/subagents/index.js +654 -814
- package/dist/subagents/index.js.map +3 -3
- package/dist/subagents/src/contracts.d.ts +4 -3
- package/dist/subagents/src/contracts.js +2 -2
- package/dist/subagents/src/index.d.ts +1 -1
- package/dist/subagents/src/index.js +1 -2
- package/dist/subagents/src/manager.js +49 -70
- package/dist/subagents/src/navigator.js +3 -14
- package/dist/subagents/src/view.d.ts +0 -1
- package/dist/subagents/src/view.js +2 -16
- package/dist/subagents/src/worktree.js +1 -1
- package/dist/trajectory/assets/index.html +8 -8
- package/dist/trajectory/index.js +487 -891
- package/dist/trajectory/index.js.map +3 -3
- package/dist/trajectory/src/assets/index.html +8 -8
- package/dist/trajectory/src/index.js +126 -86
- package/dist/trajectory/src/server.js +264 -25
- package/dist/trajectory/src/server.js.map +3 -3
- package/dist/trajectory/src/startup-mutex.d.ts +17 -0
- package/dist/trajectory/src/startup-mutex.js +172 -0
- package/examples/workflow-extension-template/README.md +4 -7
- package/examples/workflow-extension-template/extension.test.mjs +4 -8
- package/examples/workflow-extension-template/index.js +0 -3
- package/package.json +5 -5
- package/skills/pi-extensible-workflows/SKILL.md +10 -8
- package/src/agent-execution.ts +339 -127
- package/src/background-widget.ts +4 -10
- package/src/decoders.ts +31 -37
- package/src/execution.ts +5 -5
- package/src/host-delivery.ts +4 -8
- package/src/host-navigator.ts +4 -2
- package/src/host-phases.ts +1 -1
- package/src/host-recovery.ts +20 -26
- package/src/host-runtime.ts +44 -6
- package/src/host-view.ts +18 -11
- package/src/host.ts +62 -113
- package/src/index.ts +1 -2
- package/src/paths.ts +18 -12
- package/src/persistence.ts +1 -0
- package/src/pi-runtime-runner.ts +3 -1
- package/src/registry.ts +27 -63
- package/src/runtime/agent-runner.ts +2 -0
- package/src/session-lease.ts +34 -6
- package/src/settings.ts +35 -47
- package/src/store.ts +18 -2
- package/src/trajectory.ts +2 -4
- package/src/types.ts +71 -30
- package/src/utils.ts +18 -9
- package/src/validation.ts +102 -79
- package/starter/index.ts +0 -1
- package/starter/review-loop.ts +8 -7
- package/starter/roles/developer.md +0 -1
- package/starter/roles/oracle.md +0 -1
- package/starter/roles/researcher.md +0 -1
- package/starter/roles/reviewer.md +0 -1
- package/starter/roles/scout.md +0 -1
- package/subagents/README.md +8 -14
- package/subagents/src/contracts.ts +3 -4
- package/subagents/src/index.ts +2 -3
- package/subagents/src/manager.ts +45 -125
- package/subagents/src/navigator.ts +4 -13
- package/subagents/src/view.ts +4 -14
- package/subagents/src/worktree.ts +1 -1
- package/trajectory/src/assets/index.html +8 -8
- package/trajectory/src/index.ts +106 -82
- package/trajectory/src/server.ts +44 -8
- package/trajectory/src/startup-mutex.ts +127 -0
- package/dist/src/roles.d.ts +0 -70
- package/dist/src/roles.js +0 -326
- package/dist/starter/roles/developer.md +0 -6
- package/dist/starter/roles/oracle.md +0 -18
- package/dist/starter/roles/researcher.md +0 -20
- package/dist/starter/roles/reviewer.md +0 -19
- package/dist/starter/roles/scout.md +0 -18
- package/examples/workflow-extension-template/roles/reviewer.md +0 -4
- package/src/roles.ts +0 -327
package/src/background-widget.ts
CHANGED
|
@@ -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", "
|
|
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)` —
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
|
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;
|
|
7
|
+
export interface RunSummaryAgent { id: string; name: string; label?: string; state: string; attempts: number }
|
|
8
8
|
export interface RunSummaryArtifacts { runDirectory: string; statePath: string; journalPath: string; snapshotPath: string; workflowPath: string; resultPath: string; summaryPath: string }
|
|
9
9
|
export interface RunSummary { schemaVersion: 1; runId: string; sessionId: string; workflowName: string; state: RunRecord["state"]; createdAt: string; updatedAt: string; terminalAt?: string; usage: WorkflowBudgetUsage; agents: readonly RunSummaryAgent[]; error?: RunRecord["error"]; failedAt?: string; replayablePaths: readonly string[]; incompletePaths: readonly string[]; artifacts: RunSummaryArtifacts }
|
|
10
10
|
export interface CompletedOperation { path: string; value: JsonValue }
|
|
@@ -79,25 +79,19 @@ function decodeAgentResourceSelectors(value: unknown): AgentResourceSelectors |
|
|
|
79
79
|
return { ...(skills === undefined ? {} : { skills }), ...(extensions === undefined ? {} : { extensions }), ...(tools === undefined ? {} : { tools }) };
|
|
80
80
|
}
|
|
81
81
|
function decodeContextFileScopes(value: unknown): ContextFileScope[] | undefined { return decodeArray(value, (scope) => isContextFileScope(scope) ? scope : undefined); }
|
|
82
|
-
export function
|
|
83
|
-
if (!object(value)) return undefined;
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
-
const
|
|
88
|
-
const
|
|
89
|
-
const skills = value.skills === undefined ? undefined : decodeStringArray(value.skills);
|
|
90
|
-
const extensions = value.extensions === undefined ? undefined : decodeStringArray(value.extensions);
|
|
91
|
-
const overrideSystemPrompt = optionalBoolean(value.overrideSystemPrompt);
|
|
82
|
+
export function decodePreparedAgentConfiguration(value: unknown): PreparedAgentConfiguration | undefined {
|
|
83
|
+
if (!object(value) || !jsonValue(value) || typeof value.projectTrusted !== "boolean" || typeof value.systemPromptAppend !== "string") return undefined;
|
|
84
|
+
const model = decodeModelSpec(value.model);
|
|
85
|
+
const tools = decodeStringArray(value.tools);
|
|
86
|
+
const skills = decodeStringArray(value.skills);
|
|
87
|
+
const extensions = decodeStringArray(value.extensions);
|
|
88
|
+
const settings = decodeWorkflowExtensions(value.settings);
|
|
92
89
|
const contextFiles = value.contextFiles === undefined ? undefined : decodeContextFileScopes(value.contextFiles);
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
return {
|
|
98
|
-
...(prompt === undefined ? {} : { prompt }), ...(description === undefined ? {} : { description }), ...(foldedModel === undefined ? {} : { model: foldedModel }),
|
|
99
|
-
...(tools === undefined ? {} : { tools }), ...(skills === undefined ? {} : { skills }), ...(extensions === undefined ? {} : { extensions }), ...(overrideSystemPrompt === undefined ? {} : { overrideSystemPrompt }), ...(contextFiles === undefined ? {} : { contextFiles }), ...(extensionSettings === undefined ? {} : { extensionSettings }),
|
|
100
|
-
};
|
|
90
|
+
const label = optionalString(value.label);
|
|
91
|
+
const systemPrompt = optionalString(value.systemPrompt);
|
|
92
|
+
const excludeTools = value.excludeTools === undefined ? undefined : decodeStringArray(value.excludeTools);
|
|
93
|
+
if (!model || !tools || !skills || !extensions || !settings || label === INVALID_PERSISTED_VALUE || systemPrompt === INVALID_PERSISTED_VALUE || value.contextFiles !== undefined && !contextFiles || value.excludeTools !== undefined && !excludeTools) return undefined;
|
|
94
|
+
return { model, tools, skills, extensions, settings, projectTrusted: value.projectTrusted, systemPromptAppend: value.systemPromptAppend, ...(contextFiles === undefined ? {} : { contextFiles }), ...(excludeTools === undefined ? {} : { excludeTools }), ...(label === undefined ? {} : { label }), ...(systemPrompt === undefined ? {} : { systemPrompt }) };
|
|
101
95
|
}
|
|
102
96
|
function decodeWorkflowMetadata(value: unknown): LaunchSnapshot["metadata"] | undefined {
|
|
103
97
|
if (!object(value) || typeof value.name !== "string") return undefined;
|
|
@@ -201,17 +195,22 @@ function decodeScheduledAgentOptions(value: unknown): PersistedOptions | undefin
|
|
|
201
195
|
const parentBreadcrumb = optionalString(value.parentBreadcrumb);
|
|
202
196
|
const worktreeOwner = optionalString(value.worktreeOwner);
|
|
203
197
|
const model = optionalString(value.model);
|
|
204
|
-
const role = typeof value.role === "string" ? value.role : undefined;
|
|
205
198
|
const contextFiles = value.contextFiles === undefined ? undefined : decodeContextFileScopes(value.contextFiles);
|
|
206
199
|
const schema = value.schema === undefined ? undefined : decodeJsonObject(value.schema);
|
|
207
200
|
const retries = optionalNumber(value.retries);
|
|
208
201
|
const timeoutMs = value.timeoutMs;
|
|
202
|
+
if (value.projectTrusted !== undefined && typeof value.projectTrusted !== "boolean") return undefined;
|
|
203
|
+
const rawCapabilities = value.capabilities;
|
|
204
|
+
const capabilities = rawCapabilities === undefined ? undefined : object(rawCapabilities) ? { tools: decodeStringArray(rawCapabilities.tools), skills: decodeStringArray(rawCapabilities.skills), extensions: decodeStringArray(rawCapabilities.extensions) } : undefined;
|
|
205
|
+
if (rawCapabilities !== undefined && (!capabilities?.tools || !capabilities.skills || !capabilities.extensions)) return undefined;
|
|
206
|
+
const configuration = value.configuration === undefined ? undefined : decodePreparedAgentConfiguration(value.configuration);
|
|
207
|
+
if (value.configuration !== undefined && configuration === undefined) return undefined;
|
|
209
208
|
const agentOptions = value.agentOptions === undefined ? undefined : decodeJsonObject(value.agentOptions);
|
|
210
209
|
const agentIdentity = value.agentIdentity === undefined ? undefined : decodeIdentity(value.agentIdentity);
|
|
211
210
|
const sessionPath = optionalString(value.sessionPath);
|
|
212
211
|
const continuity = value.continuity === undefined ? undefined : isAgentContinuity(value.continuity) ? value.continuity : INVALID_PERSISTED_VALUE;
|
|
213
212
|
const extensionSettings = value.extensionSettings === undefined ? undefined : decodeWorkflowExtensions(value.extensionSettings);
|
|
214
|
-
if (sessionPath === INVALID_PERSISTED_VALUE || continuity === INVALID_PERSISTED_VALUE || requestedLabel === INVALID_PERSISTED_VALUE || parentBreadcrumb === INVALID_PERSISTED_VALUE || worktreeOwner === INVALID_PERSISTED_VALUE || model === INVALID_PERSISTED_VALUE || value.skills !== undefined && !skills || value.extensions !== undefined && !extensions || (value.
|
|
213
|
+
if (sessionPath === INVALID_PERSISTED_VALUE || continuity === INVALID_PERSISTED_VALUE || requestedLabel === INVALID_PERSISTED_VALUE || parentBreadcrumb === INVALID_PERSISTED_VALUE || worktreeOwner === INVALID_PERSISTED_VALUE || model === INVALID_PERSISTED_VALUE || value.skills !== undefined && !skills || value.extensions !== undefined && !extensions || (value.contextFiles !== undefined && !contextFiles) || (value.schema !== undefined && !schema) || retries === INVALID_PERSISTED_VALUE || (retries !== undefined && !integer(retries)) || (timeoutMs !== undefined && timeoutMs !== null && !finiteNumber(timeoutMs)) || (value.agentOptions !== undefined && !agentOptions) || (value.agentIdentity !== undefined && !agentIdentity) || (value.extensionSettings !== undefined && !extensionSettings)) return undefined;
|
|
215
214
|
return {
|
|
216
215
|
label: value.label,
|
|
217
216
|
...(requestedLabel === undefined ? {} : { requestedLabel }),
|
|
@@ -221,7 +220,6 @@ function decodeScheduledAgentOptions(value: unknown): PersistedOptions | undefin
|
|
|
221
220
|
...(skills === undefined ? {} : { skills }), ...(extensions === undefined ? {} : { extensions }),
|
|
222
221
|
...(worktreeOwner === undefined ? {} : { worktreeOwner }),
|
|
223
222
|
...(model === undefined ? {} : { model }),
|
|
224
|
-
...(role === undefined ? {} : { role }),
|
|
225
223
|
...(contextFiles === undefined ? {} : { contextFiles }),
|
|
226
224
|
...(schema === undefined ? {} : { schema }),
|
|
227
225
|
...(retries === undefined ? {} : { retries }),
|
|
@@ -229,6 +227,9 @@ function decodeScheduledAgentOptions(value: unknown): PersistedOptions | undefin
|
|
|
229
227
|
...(sessionPath === undefined ? {} : { sessionPath }),
|
|
230
228
|
...(continuity === undefined ? {} : { continuity }),
|
|
231
229
|
...(agentOptions === undefined ? {} : { agentOptions }),
|
|
230
|
+
...(configuration === undefined ? {} : { configuration }),
|
|
231
|
+
...(value.projectTrusted === undefined ? {} : { projectTrusted: value.projectTrusted }),
|
|
232
|
+
...(capabilities?.tools && capabilities.skills && capabilities.extensions ? { capabilities: { tools: capabilities.tools, skills: capabilities.skills, extensions: capabilities.extensions } } : {}),
|
|
232
233
|
...(agentIdentity === undefined ? {} : { agentIdentity }),
|
|
233
234
|
...(extensionSettings === undefined ? {} : { extensionSettings }),
|
|
234
235
|
};
|
|
@@ -269,11 +270,10 @@ function decodeAgentResourceInspection(value: unknown): AgentResourceInspection
|
|
|
269
270
|
const sources = sourceRecord === undefined ? undefined : {
|
|
270
271
|
global: decodeAgentResourceSelectors(sourceRecord.global),
|
|
271
272
|
project: decodeAgentResourceSelectors(sourceRecord.project),
|
|
272
|
-
...(sourceRecord.role === undefined ? {} : { role: decodeAgentResourceSelectors(sourceRecord.role) }),
|
|
273
273
|
...(sourceRecord.call === undefined ? {} : { call: decodeAgentResourceSelectors(sourceRecord.call) }),
|
|
274
274
|
};
|
|
275
|
-
if (!selectors || !skills || !extensions || !tools || !unmatchedSkills || !unmatchedExtensions || !unmatchedTools || rawSources !== undefined && (!sources || !sources.global || !sources.project || sources.
|
|
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.
|
|
275
|
+
if (!selectors || !skills || !extensions || !tools || !unmatchedSkills || !unmatchedExtensions || !unmatchedTools || rawSources !== undefined && (!sources || !sources.global || !sources.project || sources.call === undefined && sourceRecord?.call !== undefined)) return undefined;
|
|
276
|
+
return { selectors: { skills: [...(selectors.skills ?? [])], extensions: [...(selectors.extensions ?? [])], tools: [...(selectors.tools ?? [])] }, skills, extensions, tools, unmatchedSkills, unmatchedExtensions, unmatchedTools, ...(sources === undefined ? {} : { selectorSources: { global: sources.global ?? {}, project: sources.project ?? {}, ...(sources.call === undefined ? {} : { call: sources.call }) } }) };
|
|
277
277
|
}
|
|
278
278
|
function decodeAgentSetupSummary(value: unknown): NonNullable<NonNullable<AgentRecord["attemptDetails"]>[number]["setup"]> | undefined {
|
|
279
279
|
if (!object(value) || typeof value.cwd !== "string") return undefined;
|
|
@@ -311,7 +311,6 @@ function decodeAgent(value: unknown): AgentRecord | undefined {
|
|
|
311
311
|
const worktreeOwner = optionalString(value.worktreeOwner);
|
|
312
312
|
const handle = optionalString(value.handle);
|
|
313
313
|
const continuity = value.continuity === undefined ? undefined : isAgentContinuity(value.continuity) ? value.continuity : INVALID_PERSISTED_VALUE;
|
|
314
|
-
const role = optionalString(value.role);
|
|
315
314
|
const requestedModel = optionalString(value.requestedModel);
|
|
316
315
|
const startedAt = optionalNumber(value.startedAt);
|
|
317
316
|
const durationMs = optionalNumber(value.durationMs);
|
|
@@ -320,7 +319,7 @@ function decodeAgent(value: unknown): AgentRecord | undefined {
|
|
|
320
319
|
const toolCalls = value.toolCalls === undefined ? undefined : decodeArray(value.toolCalls, decodeAgentToolCall);
|
|
321
320
|
const activity = value.activity === undefined ? undefined : decodeAgentActivity(value.activity);
|
|
322
321
|
const lastEventAt = optionalNumber(value.lastEventAt);
|
|
323
|
-
if (systemPrompt === INVALID_PERSISTED_VALUE || prompt === INVALID_PERSISTED_VALUE || label === INVALID_PERSISTED_VALUE || parentId === INVALID_PERSISTED_VALUE || resultPath === INVALID_PERSISTED_VALUE || parentBreadcrumb === INVALID_PERSISTED_VALUE || worktreeOwner === INVALID_PERSISTED_VALUE ||
|
|
322
|
+
if (systemPrompt === INVALID_PERSISTED_VALUE || prompt === INVALID_PERSISTED_VALUE || label === INVALID_PERSISTED_VALUE || parentId === INVALID_PERSISTED_VALUE || resultPath === INVALID_PERSISTED_VALUE || parentBreadcrumb === INVALID_PERSISTED_VALUE || worktreeOwner === INVALID_PERSISTED_VALUE || requestedModel === INVALID_PERSISTED_VALUE || startedAt === INVALID_PERSISTED_VALUE || durationMs === INVALID_PERSISTED_VALUE || lastEventAt === INVALID_PERSISTED_VALUE || handle === INVALID_PERSISTED_VALUE || continuity === INVALID_PERSISTED_VALUE || handle !== undefined && !positiveInteger(value.turn)) return undefined;
|
|
324
323
|
if (!model || !tools || value.structuralPath !== undefined && !structuralPath || value.attemptDetails !== undefined && !attemptDetails || value.accounting !== undefined && !accounting || value.toolCalls !== undefined && !toolCalls || value.activity !== undefined && !activity) return undefined;
|
|
325
324
|
return {
|
|
326
325
|
...(systemPrompt === undefined ? {} : { systemPrompt }),
|
|
@@ -337,7 +336,6 @@ function decodeAgent(value: unknown): AgentRecord | undefined {
|
|
|
337
336
|
...(worktreeOwner === undefined ? {} : { worktreeOwner }),
|
|
338
337
|
...(handle === undefined ? {} : { handle, turn: value.turn as number }),
|
|
339
338
|
...(continuity === undefined ? {} : { continuity }),
|
|
340
|
-
...(role === undefined ? {} : { role }),
|
|
341
339
|
...(requestedModel === undefined ? {} : { requestedModel }),
|
|
342
340
|
model,
|
|
343
341
|
tools,
|
|
@@ -486,13 +484,11 @@ export function decodeLaunchSnapshot(value: unknown): LaunchSnapshot | undefined
|
|
|
486
484
|
const phases = value.phases === undefined ? undefined : decodeStringArray(value.phases);
|
|
487
485
|
const models = decodeStringArray(value.models);
|
|
488
486
|
const tools = decodeStringArray(value.tools);
|
|
489
|
-
const
|
|
490
|
-
const roles = value.roles === undefined ? undefined : decodeRecord(value.roles, decodeAgentDefinition);
|
|
491
|
-
const projectRoles = value.projectRoles === undefined ? undefined : decodeStringArray(value.projectRoles);
|
|
487
|
+
const agentConfigurations = decodeRecord(value.agentConfigurations, decodePreparedAgentConfiguration);
|
|
492
488
|
const schemas = decodeArray(value.schemas, decodeJsonObject);
|
|
493
|
-
if (identityVersion
|
|
489
|
+
if (identityVersion !== LAUNCH_SNAPSHOT_IDENTITY_VERSION || launchMode !== undefined && !isLaunchMode(launchMode) || args === undefined || !metadata || !settings || value.settingsSources !== undefined && !settingsSources || value.budget !== undefined && !budget || settingsPath === INVALID_PERSISTED_VALUE || value.modelAliases !== undefined && !modelAliases || value.phases !== undefined && !phases || !models || !tools || !agentConfigurations || !schemas) return undefined;
|
|
494
490
|
return {
|
|
495
|
-
|
|
491
|
+
identityVersion: LAUNCH_SNAPSHOT_IDENTITY_VERSION,
|
|
496
492
|
...(launchMode === undefined ? {} : { launchMode }),
|
|
497
493
|
script: value.script,
|
|
498
494
|
args,
|
|
@@ -505,9 +501,7 @@ export function decodeLaunchSnapshot(value: unknown): LaunchSnapshot | undefined
|
|
|
505
501
|
...(phases === undefined ? {} : { phases }),
|
|
506
502
|
models,
|
|
507
503
|
tools,
|
|
508
|
-
|
|
509
|
-
...(roles === undefined ? {} : { roles }),
|
|
510
|
-
...(projectRoles === undefined ? {} : { projectRoles }),
|
|
504
|
+
agentConfigurations,
|
|
511
505
|
schemas,
|
|
512
506
|
};
|
|
513
507
|
}
|
package/src/execution.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { StringDecoder } from "node:string_decoder";
|
|
|
6
6
|
import { RunStore, structuralPath as operationPath } from "./persistence.js";
|
|
7
7
|
import type { AgentAttempt } from "./agent-execution.js";
|
|
8
8
|
import type { AgentIdentity, AgentAttemptSummary, FunctionIdentity, JsonValue, ShellIdentity, ShellOptions, ShellResult, ToolIdentity, WorkflowAgentSessionReference, WorkflowBridge, WorkflowErrorCode, WorkflowExecution } from "./types.js";
|
|
9
|
-
import { WorkflowError,
|
|
9
|
+
import { WorkflowError, sumAccounting, zeroAccounting } from "./types.js";
|
|
10
10
|
import { asWorkflowError, errorText, fail, isWorkflowAuthored, isWorkflowErrorCode, jsonDigest, jsonValue, markWorkflowAuthored, object, positiveInteger } from "./utils.js";
|
|
11
11
|
import { instrumentWorkflow, validateAgentOptions, validateShellCommand, validateShellOptions } from "./validation.js";
|
|
12
12
|
|
|
@@ -41,6 +41,8 @@ function isRpcMessage(value: unknown): value is RpcMessage {
|
|
|
41
41
|
const HEARTBEAT_INTERVAL_MS = 1000;
|
|
42
42
|
|
|
43
43
|
const OUTCOME_ERRORS = new Set<string>(["AGENT_TIMEOUT", "AGENT_FAILED", "RESULT_INVALID"]);
|
|
44
|
+
/** Script handle names may not start with this: registered-function handles are journaled under it. */
|
|
45
|
+
export const FUNCTION_HANDLE_PREFIX = "function-handle";
|
|
44
46
|
const WORK_RESULT_BRAND = "__workResult";
|
|
45
47
|
|
|
46
48
|
const childSource = String.raw`
|
|
@@ -152,15 +154,13 @@ const internalAgent = (...values) => {
|
|
|
152
154
|
void result.then(() => agentInflight.delete(occurrenceKey), () => agentInflight.delete(occurrenceKey));
|
|
153
155
|
return guardedAgentResult(result);
|
|
154
156
|
};
|
|
155
|
-
const AGENT_HANDLE_KEYS = ["name", "role", "model", "tools", "skills", "extensions", "contextFiles", "label"];
|
|
156
157
|
const agentHandleNames = new Set();
|
|
157
158
|
const agentHandleInflight = new Set();
|
|
158
159
|
const internalAgentCreate = (...values) => {
|
|
159
160
|
if (values.length !== 1 || !values[0] || typeof values[0] !== "object" || Array.isArray(values[0])) throw workError("INVALID_METADATA", "agent.create requires one options object");
|
|
160
161
|
const options = values[0];
|
|
161
162
|
const name = named(options.name, "agent.create");
|
|
162
|
-
|
|
163
|
-
if (unsupported) throw workError("INVALID_METADATA", "agent.create option is unsupported: " + unsupported);
|
|
163
|
+
if (name.startsWith("${FUNCTION_HANDLE_PREFIX}/")) throw workError("INVALID_METADATA", "Agent handle names may not start with ${FUNCTION_HANDLE_PREFIX}/");
|
|
164
164
|
if (agentHandleNames.has(name)) throw workError("INVALID_METADATA", "Duplicate agent handle name: " + name);
|
|
165
165
|
agentHandleNames.add(name);
|
|
166
166
|
const config = Object.freeze(Object.fromEntries(Object.entries(options).filter(([key]) => key !== "name")));
|
|
@@ -584,7 +584,7 @@ export function runWorkflow(script: string, args: JsonValue = null, bridge: Work
|
|
|
584
584
|
const opts = validateAgentOptions(values[1]);
|
|
585
585
|
const identity = readAgentIdentity(values[2]);
|
|
586
586
|
const path = agentIdentityPath(identity);
|
|
587
|
-
const label = typeof opts.label === "string" ? opts.label :
|
|
587
|
+
const label = typeof opts.label === "string" ? opts.label : "agent";
|
|
588
588
|
try {
|
|
589
589
|
const result = await bridge.agent(values[0], opts, controller.signal, identity);
|
|
590
590
|
value = branded({ name: label, ok: true, value: result ?? null });
|
package/src/host-delivery.ts
CHANGED
|
@@ -95,9 +95,9 @@ const WORKFLOW_ERROR_PROSE: Record<WorkflowErrorCode, (detail: string) => string
|
|
|
95
95
|
REGISTRY_FROZEN: (detail) => `Workflow extension registration is closed: ${detail}.`,
|
|
96
96
|
GLOBAL_COLLISION: (detail) => `The workflow global name is already in use: ${detail}.`,
|
|
97
97
|
MISSING_WORKFLOW: (detail) => `The registered workflow function is unavailable: ${detail}.`,
|
|
98
|
-
UNKNOWN_MODEL: (detail) => `The workflow requested the unavailable model ${detail.replace(/^(?:Unknown model
|
|
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
|
-
|
|
100
|
+
UNKNOWN_AGENT: (detail) => `The workflow requested an unavailable owned agent: ${detail}.`,
|
|
101
101
|
RUN_OWNED: (detail) => /already owned|active ownership/.test(detail) ? "The workflow session is already in use." : `The workflow session is already in use: ${detail}.`,
|
|
102
102
|
RUN_NOT_FOUND: (detail) => /^Unknown workflow run\b/.test(detail) ? "The workflow run was not found." : `The workflow run was not found: ${detail}.`,
|
|
103
103
|
RPC_LIMIT_EXCEEDED: (detail) => `The workflow communication data exceeded its size limit: ${detail}.`,
|
|
@@ -213,7 +213,6 @@ function boundedWorkflowFailureDiagnostics(value: WorkflowFailureDiagnostics): W
|
|
|
213
213
|
...(value.failedAgent ? { failedAgent: {
|
|
214
214
|
id: utf8Prefix(value.failedAgent.id, 128),
|
|
215
215
|
...(value.failedAgent.label ? { label: utf8Prefix(value.failedAgent.label, 128) } : {}),
|
|
216
|
-
...(value.failedAgent.role ? { role: utf8Prefix(value.failedAgent.role, 128) } : {}),
|
|
217
216
|
structuralPath: value.failedAgent.structuralPath.slice(0, 8).map((part) => utf8Prefix(part, 128)),
|
|
218
217
|
attempt: value.failedAgent.attempt,
|
|
219
218
|
...(value.failedAgent.transport ? { transport: utf8Prefix(value.failedAgent.transport, 128) } : {}),
|
|
@@ -222,7 +221,6 @@ function boundedWorkflowFailureDiagnostics(value: WorkflowFailureDiagnostics): W
|
|
|
222
221
|
completedSiblingAgents: (value.completedSiblingAgents ?? []).slice(0, 16).map((agent) => ({
|
|
223
222
|
id: utf8Prefix(agent.id, 128),
|
|
224
223
|
...(agent.label ? { label: utf8Prefix(agent.label, 128) } : {}),
|
|
225
|
-
...(agent.role ? { role: utf8Prefix(agent.role, 128) } : {}),
|
|
226
224
|
structuralPath: agent.structuralPath.slice(0, 8).map((part) => utf8Prefix(part, 128)),
|
|
227
225
|
})),
|
|
228
226
|
completedSiblingPaths: value.completedSiblingPaths.slice(0, 16).map((path) => path.slice(0, 8).map((part) => utf8Prefix(part, 128))),
|
|
@@ -278,7 +276,6 @@ export async function createWorkflowFailureDiagnostics(store: RunStore, metadata
|
|
|
278
276
|
const failedAgent = failedAgentRecord ? {
|
|
279
277
|
id: failedAgentRecord.id,
|
|
280
278
|
...(failedAgentRecord.label ?? failedAgentRecord.name ? { label: failedAgentRecord.label ?? failedAgentRecord.name } : {}),
|
|
281
|
-
...(failedAgentRecord.role ? { role: failedAgentRecord.role } : {}),
|
|
282
279
|
structuralPath: [...(failedAgentRecord.structuralPath ?? [])],
|
|
283
280
|
attempt: Math.max(1, failedAttempt?.attempt ?? failedAgentRecord.attempts),
|
|
284
281
|
...(failedAttempt?.transport ? { transport: failedAttempt.transport } : {}),
|
|
@@ -290,7 +287,6 @@ export async function createWorkflowFailureDiagnostics(store: RunStore, metadata
|
|
|
290
287
|
}).map((agent) => ({
|
|
291
288
|
id: agent.id,
|
|
292
289
|
...(agent.label ?? agent.name ? { label: agent.label ?? agent.name } : {}),
|
|
293
|
-
...(agent.role ? { role: agent.role } : {}),
|
|
294
290
|
structuralPath: [...(agent.structuralPath ?? [])],
|
|
295
291
|
} satisfies WorkflowSiblingAgent));
|
|
296
292
|
const completedSiblingPaths = completedSiblingAgents.map((agent) => [...agent.structuralPath]);
|
|
@@ -316,9 +312,9 @@ export async function createWorkflowFailureDiagnostics(store: RunStore, metadata
|
|
|
316
312
|
}
|
|
317
313
|
|
|
318
314
|
export function formatWorkflowFailureDiagnostics(diagnostic: WorkflowFailureDiagnostics): string {
|
|
319
|
-
const failedAgent = diagnostic.failedAgent ? `${diagnostic.failedAgent.label ?? diagnostic.failedAgent.id}
|
|
315
|
+
const failedAgent = diagnostic.failedAgent ? `${diagnostic.failedAgent.label ?? diagnostic.failedAgent.id} attempt=${String(diagnostic.failedAgent.attempt)} path=${diagnostic.failedAgent.structuralPath.join(" > ") || "(root)"}${diagnostic.failedAgent.session ? ` session=${diagnostic.failedAgent.session.transport}/${diagnostic.failedAgent.session.sessionId}` : ""}` : "(not persisted)";
|
|
320
316
|
const siblingAgents = diagnostic.completedSiblingAgents;
|
|
321
|
-
const siblings = siblingAgents ? siblingAgents.map((agent) => `${agent.label ?? agent.id}
|
|
317
|
+
const siblings = siblingAgents ? siblingAgents.map((agent) => `${agent.label ?? agent.id} path=${agent.structuralPath.join(" > ") || "(root)"}`).join(", ") || "(none)" : diagnostic.completedSiblingPaths.map((path) => path.join(" > ") || "(root)").join(", ") || "(none)";
|
|
322
318
|
const retry = diagnostic.retry ? [` Retry: ${diagnostic.retry.action}`, ` Replayable completed paths: ${diagnostic.retry.completedPaths.join(", ") || "(none)"}`, ` Incomplete paths: ${diagnostic.retry.incompletePaths.join(", ") || "(unknown)"}`, ` Named worktrees: ${diagnostic.retry.namedWorktrees.join(", ") || "(none)"}`, ` Warning: ${diagnostic.retry.warning}`] : [];
|
|
323
319
|
return [`✗ Workflow: ${diagnostic.workflowName}`, ` Run: ${diagnostic.runId}`, ` State: ${diagnostic.state}`, ` Error: ${diagnostic.error.code}: ${diagnostic.error.message}`, ` Failed at: ${diagnostic.failedAt ?? "(unknown)"}`, ` Failed agent: ${failedAgent}`, ` Completed sibling ${siblingAgents ? "agents" : "paths"}: ${siblings}`, ...retry, ` Artifacts: state=${diagnostic.artifacts.statePath} journal=${diagnostic.artifacts.journalPath}`].join("\n");
|
|
324
320
|
}
|
package/src/host-navigator.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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] ?? "";
|
package/src/host-phases.ts
CHANGED
|
@@ -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> }
|
package/src/host-recovery.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
3
3
|
import { type ExtensionAPI, type ExtensionToolContext } from "@earendil-works/pi-coding-agent";
|
|
4
|
-
import { FairAgentScheduler, WorkflowAgentExecutor, type
|
|
4
|
+
import { FairAgentScheduler, WorkflowAgentExecutor, type AgentExecutionRoot, type AgentProviderFailure, type AgentProviderRecovery } from "./agent-execution.js";
|
|
5
5
|
import { listRunIds, RunStore, structuralPath as operationPath, type PersistedRun } from "./persistence.js";
|
|
6
6
|
import { budgetUsage, budgetRelaxed, mergeBudget, resumeBudgetAllowed, validateBudget, validateBudgetPatch, WorkflowBudgetRuntime } from "./budget.js";
|
|
7
7
|
import { aliasDrift, createLaunchSnapshot, errorCode, errorText, jsonValue, object } from "./utils.js";
|
|
@@ -13,7 +13,7 @@ import { createWorkflowFailureDiagnostics, formatWorkflowFailureDelivery, format
|
|
|
13
13
|
|
|
14
14
|
/** The subset of Pi's model registry the host relies on; every member is optional because older or headless hosts expose fewer of them. */
|
|
15
15
|
export type ModelRegistryCapability = { getAll?: () => readonly Model<Api>[]; getAvailable?: () => readonly Model<Api>[]; find?: (provider: string, model: string) => Model<Api> | undefined; refresh?: () => Promise<void>; getError?: () => string | undefined };
|
|
16
|
-
export type WorkflowRecoveryContext = { model: { provider: string; id: string } | undefined; modelRegistry: ModelRegistryCapability | undefined; deliveryContext: CompletionDeliveryContext; toolContext?: ExtensionToolContext | undefined; signal?: AbortSignal; resolvedAliases?: Readonly<Record<string, string>>; blockedAliases?: ReadonlySet<string>; blockedAliasTargets?: Readonly<Record<string, string>> };
|
|
16
|
+
export type WorkflowRecoveryContext = { model: { provider: string; id: string } | undefined; modelRegistry: ModelRegistryCapability | undefined; deliveryContext: CompletionDeliveryContext; toolContext?: ExtensionToolContext | undefined; signal?: AbortSignal; resolvedAliases?: Readonly<Record<string, string>>; resolvedDynamicAliasNames?: readonly string[]; blockedAliases?: ReadonlySet<string>; blockedAliasTargets?: Readonly<Record<string, string>> };
|
|
17
17
|
export type WorkflowRecoveryDependencies = {
|
|
18
18
|
pi: Pick<ExtensionAPI, "getThinkingLevel">;
|
|
19
19
|
home: string | undefined;
|
|
@@ -28,9 +28,8 @@ export type WorkflowRecoveryDependencies = {
|
|
|
28
28
|
createAgentExecutor: (root: Omit<AgentExecutionRoot, "agentDir" | "agentSetupHooks">) => WorkflowAgentExecutor;
|
|
29
29
|
activeSnapshotTools: (tools: readonly string[], active: ReadonlySet<string> | "session") => Set<string>;
|
|
30
30
|
frozenResourcePolicy: (policy: AgentResourcePolicy) => () => AgentResourcePolicy;
|
|
31
|
-
resolveLaunchPrologue: (input: { snapshot: Readonly<LaunchSnapshot>; cwd: string; trustedProject: boolean; rootModel: ModelSpec; modelRegistry?: WorkflowRecoveryContext["modelRegistry"]; signal: AbortSignal; resolvedAliases?: Readonly<Record<string, string>>; blockedAliases?: ReadonlySet<string>; blockedAliasTargets?: Readonly<Record<string, string>>; withPreflight: boolean }) => Promise<{ active: Set<string>; settingsPath: string; currentPolicy: AgentResourcePolicy; previousAliases: Readonly<Record<string, string>>; knownModels: ReadonlySet<string>; availableModels: ReadonlySet<string>; currentAliases: Readonly<Record<string, string>>; blockedAliases: ReadonlySet<string>; blockedAliasTargets: Readonly<Record<string, string>>; snapshot: Readonly<LaunchSnapshot>; script: string | undefined }>;
|
|
32
|
-
|
|
33
|
-
workflowAgentHandler: (store: RunStore, metadata: WorkflowMetadata, lifecycle: RunLifecycle, executor: WorkflowAgentExecutor, cwd: string, runId: string, captureRole?: (role: string, model: ModelSpec) => Promise<void>) => (prompt: string, options: Readonly<Record<string, JsonValue>>, signal: AbortSignal, identity: AgentIdentity) => Promise<JsonValue>;
|
|
31
|
+
resolveLaunchPrologue: (input: { snapshot: Readonly<LaunchSnapshot>; cwd: string; trustedProject: boolean; rootModel: ModelSpec; modelRegistry?: WorkflowRecoveryContext["modelRegistry"]; signal: AbortSignal; resolvedAliases?: Readonly<Record<string, string>>; resolvedDynamicAliasNames?: readonly string[]; blockedAliases?: ReadonlySet<string>; blockedAliasTargets?: Readonly<Record<string, string>>; withPreflight: boolean }) => Promise<{ active: Set<string>; settingsPath: string; currentPolicy: AgentResourcePolicy; previousAliases: Readonly<Record<string, string>>; knownModels: ReadonlySet<string>; availableModels: ReadonlySet<string>; currentAliases: Readonly<Record<string, string>>; dynamicAliasNames: readonly string[]; blockedAliases: ReadonlySet<string>; blockedAliasTargets: Readonly<Record<string, string>>; snapshot: Readonly<LaunchSnapshot>; script: string | undefined }>;
|
|
32
|
+
workflowAgentHandler: (store: RunStore, metadata: WorkflowMetadata, lifecycle: RunLifecycle, executor: WorkflowAgentExecutor, cwd: string, runId: string) => (prompt: string, options: Readonly<Record<string, JsonValue>>, signal: AbortSignal, identity: AgentIdentity) => Promise<JsonValue>;
|
|
34
33
|
shellForRun: (store: RunStore, metadata: WorkflowMetadata, lifecycle: RunLifecycle, command: string, options: ShellOptions, signal: AbortSignal, identity: ShellIdentity) => Promise<ShellResult>;
|
|
35
34
|
toolForRun: (store: RunStore, lifecycle: RunLifecycle, context: ExtensionToolContext | undefined, identifier: string, args: Readonly<Record<string, JsonValue>>, signal: AbortSignal, identity: ToolIdentity) => Promise<JsonValue>;
|
|
36
35
|
resolveWorktree: (store: RunStore, metadata: WorkflowMetadata, owner: string) => Promise<Readonly<WorkflowWorktreeReference>>;
|
|
@@ -61,13 +60,9 @@ function workflowRecoveryGuidance(action: "resume" | "retry", state: RunState):
|
|
|
61
60
|
if (state === "interrupted") return "Interrupted workflow runs must be resumed from the interactive /workflow picker";
|
|
62
61
|
return `Only failed workflow runs can be retried; source is ${state}`;
|
|
63
62
|
}
|
|
64
|
-
/** A snapshot can only be re-executed when it carries the current identity scheme and
|
|
65
|
-
function assertResumableSnapshot(snapshot: Readonly<LaunchSnapshot
|
|
63
|
+
/** A snapshot can only be re-executed when it carries the current identity scheme and validated concrete agent configurations. */
|
|
64
|
+
function assertResumableSnapshot(snapshot: Readonly<LaunchSnapshot>): void {
|
|
66
65
|
if (snapshot.identityVersion !== LAUNCH_SNAPSHOT_IDENTITY_VERSION) throw new WorkflowError("RESUME_INCOMPATIBLE", "Workflow launch snapshot identity version is incompatible");
|
|
67
|
-
if (snapshot.roles === undefined) throw new WorkflowError("RESUME_INCOMPATIBLE", "Workflow role definitions are missing from the launch snapshot");
|
|
68
|
-
if ((snapshot.projectRoles?.length ?? 0) > 0 && !trustedProject) throw new WorkflowError("RESUME_INCOMPATIBLE", "Cannot restore project roles in an untrusted project");
|
|
69
|
-
const missingRole = snapshot.agentTypes.find((role) => !snapshot.roles?.[role]);
|
|
70
|
-
if (missingRole) throw new WorkflowError("RESUME_INCOMPATIBLE", `Role definition is missing from the launch snapshot: ${missingRole}`);
|
|
71
66
|
}
|
|
72
67
|
function assertExpectedWorkflowState(expectedState: string | undefined, actualState: RunState): void {
|
|
73
68
|
if (expectedState !== undefined && expectedState !== actualState) throw new WorkflowError("RESUME_INCOMPATIBLE", `Workflow run state changed: expected state ${expectedState}, actual state ${actualState}`);
|
|
@@ -75,7 +70,7 @@ function assertExpectedWorkflowState(expectedState: string | undefined, actualSt
|
|
|
75
70
|
export function persistedFailure(run: PersistedRun, error: WorkflowError): PersistedRun { const failedAt = workflowFailedAt(error); return { ...run, error: { code: error.code, message: error.message, ...(failedAt ? { failedAt } : {}) }, ...(failedAt ? { failedAt } : {}) }; }
|
|
76
71
|
|
|
77
72
|
export function createWorkflowRecovery(deps: WorkflowRecoveryDependencies) {
|
|
78
|
-
const { pi, home, runs, scheduler, eventPublisher, persistRunState, projectTrusted, resumeHostContext, ensureSessionLease, createAgentExecutor, activeSnapshotTools, frozenResourcePolicy, resolveLaunchPrologue,
|
|
73
|
+
const { pi, home, runs, scheduler, eventPublisher, persistRunState, projectTrusted, resumeHostContext, ensureSessionLease, createAgentExecutor, activeSnapshotTools, frozenResourcePolicy, resolveLaunchPrologue, workflowAgentHandler, shellForRun, toolForRun, resolveWorktree, checkpointBridge, phaseBridge, logBridge, lifecycleFor, createProviderErrorRecovery, cleanupTerminalRun, deliver, deliverTerminal, workflowToolUpdate, registry, modelSpec } = deps;
|
|
79
74
|
const coordinateRunMutation = deps.coordinateRunMutation ?? (<T>(task: () => Promise<T>): Promise<T> => task());
|
|
80
75
|
type BudgetDecisionResult = { state: "running" | "completed" | "budget_exhausted"; approved: boolean; value?: JsonValue; run?: PersistedRun; completion?: CompletionDeliveryResult };
|
|
81
76
|
const budgetDecisionDelivery = (metadata: WorkflowMetadata, request: BudgetApprovalRequest) => `Workflow ${metadata.name} budget adjustment ${request.proposalId} for run ${request.runId} requires approval. Consumed usage: ${JSON.stringify(request.consumed)}. Previous limits: ${JSON.stringify(request.previous)}. Proposed limits: ${JSON.stringify(request.proposed)}. Respond with workflow_respond using proposalId ${request.proposalId}.`;
|
|
@@ -97,11 +92,11 @@ export function createWorkflowRecovery(deps: WorkflowRecoveryDependencies) {
|
|
|
97
92
|
const loaded = await run.store.load();
|
|
98
93
|
const trustedProject = context?.projectTrusted ?? run.projectTrusted();
|
|
99
94
|
const rootModel = context?.model ? { ...run.model, provider: context.model.provider, model: context.model.id } : run.model;
|
|
100
|
-
const { settingsPath, currentPolicy, previousAliases, knownModels, availableModels, currentAliases, blockedAliases, blockedAliasTargets, snapshot } = await resolveLaunchPrologue({ snapshot: loaded.snapshot, cwd: run.store.cwd, trustedProject, rootModel, ...(context?.modelRegistry ? { modelRegistry: context.modelRegistry } : {}), signal: run.abortController.signal, withPreflight: false });
|
|
95
|
+
const { settingsPath, currentPolicy, previousAliases, knownModels, availableModels, currentAliases, dynamicAliasNames, blockedAliases, blockedAliasTargets, snapshot } = await resolveLaunchPrologue({ snapshot: loaded.snapshot, cwd: run.store.cwd, trustedProject, rootModel, ...(context?.modelRegistry ? { modelRegistry: context.modelRegistry } : {}), signal: run.abortController.signal, withPreflight: false });
|
|
101
96
|
await run.store.saveSnapshot(snapshot);
|
|
102
97
|
scheduler.updateRunLimit(run.store.runId, snapshot.settings.concurrency);
|
|
103
98
|
scheduler.setRunExtensionSettings(run.store.runId, snapshot.settings.extensionSettings);
|
|
104
|
-
run.executor = createAgentExecutor({ cwd: run.store.cwd, model: run.model, tools: activeSnapshotTools(snapshot.tools, "session"), resourceSelectors: currentPolicy.effective, extensionSettings: snapshot.settings.extensionSettings, availableModels, knownModels, modelAliases: currentAliases,
|
|
99
|
+
run.executor = createAgentExecutor({ cwd: run.store.cwd, projectTrusted: currentPolicy.projectTrusted, model: run.model, tools: activeSnapshotTools(snapshot.tools, "session"), resourceSelectors: currentPolicy.effective, extensionSettings: snapshot.settings.extensionSettings, availableModels, knownModels, modelAliases: currentAliases, dynamicModelAliasNames: dynamicAliasNames, blockedAliases, blockedAliasTargets, settingsPath, runStore: run.store, providerPause: async () => { deliver(`Workflow ${snapshot.metadata.name} paused: provider limit.`); await run.lifecycle.providerPause(); }, agentResourcePolicy: frozenResourcePolicy(currentPolicy) });
|
|
105
100
|
run.executor.setRunContext(workflowRunContext(run.store.cwd, run.store.sessionId, run.store.runId, loaded.snapshot.metadata, loaded.snapshot.args, run.abortController.signal));
|
|
106
101
|
const drift = aliasDrift(previousAliases, currentAliases);
|
|
107
102
|
if (drift.length) await run.store.appendEvent({ type: "warning", message: `Model alias mappings changed on resume: ${drift.join("; ")}` });
|
|
@@ -112,33 +107,32 @@ export function createWorkflowRecovery(deps: WorkflowRecoveryDependencies) {
|
|
|
112
107
|
return { hasUI: host?.hasUI === true, ui };
|
|
113
108
|
};
|
|
114
109
|
type ColdResumeResult = { value: JsonValue; resultPath: string; resultBytes: number; completion: CompletionDeliveryResult };
|
|
115
|
-
const coldResumeRun = async (run: WorkflowRunRecord, hasUI: boolean, ui: { select?: (prompt: string, options: string[]) => Promise<string | undefined> }, trustedProject: boolean, context?: { model: { provider: string; id: string } | undefined; modelRegistry: WorkflowRecoveryContext["modelRegistry"]; deliveryContext: CompletionDeliveryContext; toolContext?: ExtensionToolContext | undefined; signal?: AbortSignal | undefined; resolvedAliases?: Readonly<Record<string, string>>; blockedAliases?: ReadonlySet<string>; blockedAliasTargets?: Readonly<Record<string, string>> }, modeOverride?: boolean, waitForCompletion = true): Promise<ColdResumeResult | undefined> => {
|
|
110
|
+
const coldResumeRun = async (run: WorkflowRunRecord, hasUI: boolean, ui: { select?: (prompt: string, options: string[]) => Promise<string | undefined> }, trustedProject: boolean, context?: { model: { provider: string; id: string } | undefined; modelRegistry: WorkflowRecoveryContext["modelRegistry"]; deliveryContext: CompletionDeliveryContext; toolContext?: ExtensionToolContext | undefined; signal?: AbortSignal | undefined; resolvedAliases?: Readonly<Record<string, string>>; resolvedDynamicAliasNames?: readonly string[]; blockedAliases?: ReadonlySet<string>; blockedAliasTargets?: Readonly<Record<string, string>> }, modeOverride?: boolean, waitForCompletion = true): Promise<ColdResumeResult | undefined> => {
|
|
116
111
|
const loaded = await run.store.load();
|
|
117
112
|
const foreground = modeOverride ?? (loaded.run.delivery?.mode === "foreground" || (loaded.run.delivery?.mode === "background" && loaded.run.delivery.toolCallId !== undefined) || (loaded.run.delivery === undefined && loaded.snapshot.launchMode === "foreground"));
|
|
118
113
|
if (loaded.run.activeShells !== undefined || loaded.run.activeShellStartedAt !== undefined || loaded.run.activeShellsByPhase !== undefined) await persistRunState(run.store, run.metadata, withoutActiveShells);
|
|
119
114
|
await run.store.validateRetrySource();
|
|
120
115
|
await run.store.validateBorrowedWorktrees();
|
|
121
|
-
assertResumableSnapshot(loaded.snapshot
|
|
116
|
+
assertResumableSnapshot(loaded.snapshot);
|
|
122
117
|
const rootModel = context?.model ? { ...run.model, provider: context.model.provider, model: context.model.id } : run.model;
|
|
123
118
|
const controller = new AbortController();
|
|
124
119
|
if (context?.signal?.aborted) controller.abort(); else { context?.signal?.addEventListener("abort", () => { controller.abort(); }, { once: true }); }
|
|
125
120
|
run.abortController = controller;
|
|
126
|
-
const { settingsPath, currentPolicy, previousAliases, knownModels, availableModels, currentAliases, blockedAliases, blockedAliasTargets, snapshot, script } = await resolveLaunchPrologue({ snapshot: loaded.snapshot, cwd: run.store.cwd, trustedProject, rootModel, ...(context?.modelRegistry ? { modelRegistry: context.modelRegistry } : {}), signal: controller.signal, ...(context?.resolvedAliases ? { resolvedAliases: context.resolvedAliases } : {}), ...(context?.blockedAliases ? { blockedAliases: context.blockedAliases } : {}), ...(context?.blockedAliasTargets ? { blockedAliasTargets: context.blockedAliasTargets } : {}), withPreflight: true });
|
|
121
|
+
const { settingsPath, currentPolicy, previousAliases, knownModels, availableModels, currentAliases, dynamicAliasNames, blockedAliases, blockedAliasTargets, snapshot, script } = await resolveLaunchPrologue({ snapshot: loaded.snapshot, cwd: run.store.cwd, trustedProject, rootModel, ...(context?.modelRegistry ? { modelRegistry: context.modelRegistry } : {}), signal: controller.signal, ...(context?.resolvedAliases ? { resolvedAliases: context.resolvedAliases } : {}), ...(context?.resolvedDynamicAliasNames ? { resolvedDynamicAliasNames: context.resolvedDynamicAliasNames } : {}), ...(context?.blockedAliases ? { blockedAliases: context.blockedAliases } : {}), ...(context?.blockedAliasTargets ? { blockedAliasTargets: context.blockedAliasTargets } : {}), withPreflight: true });
|
|
127
122
|
if (!script) throw new WorkflowError("INTERNAL_ERROR", "Resume preflight did not produce a launch script");
|
|
128
123
|
const persistedSnapshot = modeOverride === undefined ? snapshot : createLaunchSnapshot({ ...snapshot, launchMode: foreground ? "foreground" : "background" });
|
|
129
124
|
await run.store.saveSnapshot(persistedSnapshot);
|
|
130
125
|
if (modeOverride !== undefined) await persistRunState(run.store, run.metadata, (current) => ({ ...current, delivery: { ...(current.delivery ?? {}), mode: foreground ? "foreground" : "background", state: foreground ? "attached" : "pending" } }));
|
|
131
126
|
scheduler.updateRunLimit(run.store.runId, snapshot.settings.concurrency);
|
|
132
127
|
scheduler.setRunExtensionSettings(run.store.runId, snapshot.settings.extensionSettings);
|
|
133
|
-
|
|
134
|
-
run.executor = createAgentExecutor({ cwd: run.store.cwd, model: rootModel, tools: activeSnapshotTools(snapshot.tools, "session"), resourceSelectors: currentPolicy.effective, extensionSettings: snapshot.settings.extensionSettings, availableModels, knownModels, modelAliases: currentAliases, blockedAliases, blockedAliasTargets, settingsPath, agentDefinitions: roles.definitions, runStore: run.store, providerPause: async () => { deliver(`Workflow ${snapshot.metadata.name} paused: provider limit.`); await run.lifecycle.providerPause(); }, agentResourcePolicy: frozenResourcePolicy(currentPolicy) });
|
|
128
|
+
run.executor = createAgentExecutor({ cwd: run.store.cwd, projectTrusted: currentPolicy.projectTrusted, model: rootModel, tools: activeSnapshotTools(snapshot.tools, "session"), resourceSelectors: currentPolicy.effective, extensionSettings: snapshot.settings.extensionSettings, availableModels, knownModels, modelAliases: currentAliases, dynamicModelAliasNames: dynamicAliasNames, blockedAliases, blockedAliasTargets, settingsPath, runStore: run.store, providerPause: async () => { deliver(`Workflow ${snapshot.metadata.name} paused: provider limit.`); await run.lifecycle.providerPause(); }, agentResourcePolicy: frozenResourcePolicy(currentPolicy) });
|
|
135
129
|
const drift = aliasDrift(previousAliases, currentAliases);
|
|
136
130
|
if (drift.length) await run.store.appendEvent({ type: "warning", message: `Model alias mappings changed on resume: ${drift.join("; ")}` });
|
|
137
131
|
const runContext = workflowRunContext(run.store.cwd, run.store.sessionId, run.store.runId, loaded.snapshot.metadata, loaded.snapshot.args, controller.signal);
|
|
138
132
|
run.executor.setRunContext(runContext);
|
|
139
133
|
await scheduler.cancelRun(run.store.runId);
|
|
140
134
|
await run.lifecycle.resume();
|
|
141
|
-
const execution = runWorkflow(script, loaded.snapshot.args, withWorkflowFunctions({ tool: (identifier, args, signal, identity) => toolForRun(run.store, run.lifecycle, context?.toolContext, identifier, args, signal, identity), shell: (command, options, signal, identity) => shellForRun(run.store, run.metadata, run.lifecycle, command, options, signal, identity), agent: workflowAgentHandler(run.store, run.metadata, run.lifecycle, run.executor, run.store.cwd, run.store.runId
|
|
135
|
+
const execution = runWorkflow(script, loaded.snapshot.args, withWorkflowFunctions({ tool: (identifier, args, signal, identity) => toolForRun(run.store, run.lifecycle, context?.toolContext, identifier, args, signal, identity), shell: (command, options, signal, identity) => shellForRun(run.store, run.metadata, run.lifecycle, command, options, signal, identity), agent: workflowAgentHandler(run.store, run.metadata, run.lifecycle, run.executor, run.store.cwd, run.store.runId), worktree: async (owner) => resolveWorktree(run.store, run.metadata, owner), checkpoint: checkpointBridge(run.store.runId, run.store, run.metadata, foreground, hasUI ? ui : undefined), phase: phaseBridge(run.store, run.metadata, run.lifecycle), log: logBridge(run.store, run.lifecycle, run.metadata.name) }, run.store, runContext, registry, snapshot.settings.extensionSettings), controller.signal);
|
|
142
136
|
run.execution = execution;
|
|
143
137
|
const completion = execution.result.then(async (value) => {
|
|
144
138
|
await scheduler.flush(run.store.runId);
|
|
@@ -244,7 +238,7 @@ export function createWorkflowRecovery(deps: WorkflowRecoveryDependencies) {
|
|
|
244
238
|
return { state: "running" };
|
|
245
239
|
};
|
|
246
240
|
const retryReservations = new Set<string>();
|
|
247
|
-
type PreparedRetryWorkflowRun = { childRun: WorkflowRunRecord; parentRunId: string; lineageRootRunId: string; concurrency: number; extensionSettings?: Readonly<WorkflowExtensionSettings>; hostModel: { provider: string; id: string }; modelRegistry: WorkflowRecoveryContext["modelRegistry"]; currentAliases: Readonly<Record<string, string>>; blockedAliases: ReadonlySet<string>; blockedAliasTargets: Readonly<Record<string, string>> };
|
|
241
|
+
type PreparedRetryWorkflowRun = { childRun: WorkflowRunRecord; parentRunId: string; lineageRootRunId: string; concurrency: number; extensionSettings?: Readonly<WorkflowExtensionSettings>; hostModel: { provider: string; id: string }; modelRegistry: WorkflowRecoveryContext["modelRegistry"]; currentAliases: Readonly<Record<string, string>>; dynamicAliasNames: readonly string[]; blockedAliases: ReadonlySet<string>; blockedAliasTargets: Readonly<Record<string, string>> };
|
|
248
242
|
const retryWorkflowRunUnlocked = async (runId: string, context: unknown, signal?: AbortSignal, expectedState?: string): Promise<PreparedRetryWorkflowRun> => {
|
|
249
243
|
if (typeof runId !== "string" || !runId.trim()) throw new WorkflowError("RESUME_INCOMPATIBLE", "workflow_retry requires an explicit run ID");
|
|
250
244
|
const host = object(context) ? context : {};
|
|
@@ -277,11 +271,11 @@ export function createWorkflowRecovery(deps: WorkflowRecoveryDependencies) {
|
|
|
277
271
|
const trustedProject = projectTrusted(context);
|
|
278
272
|
await sourceStore.validateRetrySource();
|
|
279
273
|
await sourceStore.validateBorrowedWorktrees();
|
|
280
|
-
assertResumableSnapshot(loaded.snapshot
|
|
274
|
+
assertResumableSnapshot(loaded.snapshot);
|
|
281
275
|
const modelRegistry = resumeHostContext(context).modelRegistry;
|
|
282
276
|
const hostModel = object(host.model) && typeof host.model.provider === "string" && typeof host.model.id === "string" ? { provider: host.model.provider, id: host.model.id } : { provider: "", id: "" };
|
|
283
277
|
const rootModel: ModelSpec = { provider: hostModel.provider, model: hostModel.id, thinking: pi.getThinkingLevel() };
|
|
284
|
-
const { active, settingsPath, currentPolicy, knownModels, availableModels, currentAliases, blockedAliases, blockedAliasTargets, snapshot: childBaseSnapshot } = await resolveLaunchPrologue({ snapshot: loaded.snapshot, cwd, trustedProject, rootModel, ...(modelRegistry ? { modelRegistry } : {}), signal: signal ?? new AbortController().signal, withPreflight: true });
|
|
278
|
+
const { active, settingsPath, currentPolicy, knownModels, availableModels, currentAliases, dynamicAliasNames, blockedAliases, blockedAliasTargets, snapshot: childBaseSnapshot } = await resolveLaunchPrologue({ snapshot: loaded.snapshot, cwd, trustedProject, rootModel, ...(modelRegistry ? { modelRegistry } : {}), signal: signal ?? new AbortController().signal, withPreflight: true });
|
|
285
279
|
await sourceStore.validateNamedWorktrees();
|
|
286
280
|
for (const name of loaded.run.retry?.namedWorktrees ?? []) await sourceStore.resolveNamedWorktree(name);
|
|
287
281
|
const completedPaths = (await sourceStore.replayableOperations()).map(({ path }) => path);
|
|
@@ -299,10 +293,10 @@ export function createWorkflowRecovery(deps: WorkflowRecoveryDependencies) {
|
|
|
299
293
|
const abortController = new AbortController();
|
|
300
294
|
const providerErrorRecovery = createProviderErrorRecovery(context, availableModels, () => { abortController.abort(); });
|
|
301
295
|
const providerPause = async () => { deliver(`Workflow ${loaded.snapshot.metadata.name} paused: provider limit.`); await lifecycle.providerPause(); };
|
|
302
|
-
const childRun: WorkflowRunRecord = { executor: createAgentExecutor({ cwd, model, tools: activeSnapshotTools(loaded.snapshot.tools, active), resourceSelectors: currentPolicy.effective, extensionSettings: childBaseSnapshot.settings.extensionSettings, availableModels, knownModels, modelAliases: currentAliases, blockedAliases, blockedAliasTargets, settingsPath,
|
|
296
|
+
const childRun: WorkflowRunRecord = { executor: createAgentExecutor({ cwd, projectTrusted: currentPolicy.projectTrusted, model, tools: activeSnapshotTools(loaded.snapshot.tools, active), resourceSelectors: currentPolicy.effective, extensionSettings: childBaseSnapshot.settings.extensionSettings, availableModels, knownModels, modelAliases: currentAliases, dynamicModelAliasNames: dynamicAliasNames, blockedAliases, blockedAliasTargets, settingsPath, runStore: childStore, providerPause, agentResourcePolicy: frozenResourcePolicy(currentPolicy) }), store: childStore, metadata: loaded.snapshot.metadata, model, lifecycle, budget: childBudget, abortController, projectTrusted: () => projectTrusted(context), checkpointResolvers: new Map(), ...(providerErrorRecovery ? { providerErrorRecovery } : {}) };
|
|
303
297
|
runs.set(childRunId, childRun);
|
|
304
298
|
childCreated = true;
|
|
305
|
-
return { childRun, parentRunId: loaded.run.id, lineageRootRunId, concurrency: loaded.snapshot.settings.concurrency, ...(childBaseSnapshot.settings.extensionSettings === undefined ? {} : { extensionSettings: childBaseSnapshot.settings.extensionSettings }), hostModel, modelRegistry, currentAliases, blockedAliases, blockedAliasTargets };
|
|
299
|
+
return { childRun, parentRunId: loaded.run.id, lineageRootRunId, concurrency: loaded.snapshot.settings.concurrency, ...(childBaseSnapshot.settings.extensionSettings === undefined ? {} : { extensionSettings: childBaseSnapshot.settings.extensionSettings }), hostModel, modelRegistry, currentAliases, dynamicAliasNames, blockedAliases, blockedAliasTargets };
|
|
306
300
|
} finally {
|
|
307
301
|
if (!childCreated) retryReservations.delete(lineageRootRunId);
|
|
308
302
|
}
|
|
@@ -315,7 +309,7 @@ export function createWorkflowRecovery(deps: WorkflowRecoveryDependencies) {
|
|
|
315
309
|
await eventPublisher.runStarted(prepared.childRun.store, prepared.childRun.metadata);
|
|
316
310
|
const { hasUI, ui } = recoveryUi(context);
|
|
317
311
|
const recoveryContext = resumeHostContext(context);
|
|
318
|
-
const completed = await coldResumeRun(prepared.childRun, hasUI, ui, projectTrusted(context), { model: prepared.hostModel, modelRegistry: prepared.modelRegistry, deliveryContext: recoveryContext.deliveryContext, toolContext: recoveryContext.toolContext, resolvedAliases: prepared.currentAliases, blockedAliases: prepared.blockedAliases, blockedAliasTargets: prepared.blockedAliasTargets, ...(signal ? { signal } : {}) }, modeOverride);
|
|
312
|
+
const completed = await coldResumeRun(prepared.childRun, hasUI, ui, projectTrusted(context), { model: prepared.hostModel, modelRegistry: prepared.modelRegistry, deliveryContext: recoveryContext.deliveryContext, toolContext: recoveryContext.toolContext, resolvedAliases: prepared.currentAliases, resolvedDynamicAliasNames: prepared.dynamicAliasNames, blockedAliases: prepared.blockedAliases, blockedAliasTargets: prepared.blockedAliasTargets, ...(signal ? { signal } : {}) }, modeOverride);
|
|
319
313
|
const completion = runs.get(prepared.childRun.store.runId)?.completion;
|
|
320
314
|
if (completion) {
|
|
321
315
|
childStarted = true;
|