pi-extensible-workflows 3.3.0 → 3.4.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/README.md +2 -2
- package/dist/src/agent-execution.d.ts +63 -9
- package/dist/src/agent-execution.js +265 -92
- package/dist/src/cli.js +11 -2
- package/dist/src/doctor-cleanup.js +66 -31
- package/dist/src/execution.d.ts +1 -1
- package/dist/src/execution.js +29 -13
- package/dist/src/host.d.ts +5 -5
- package/dist/src/host.js +160 -76
- package/dist/src/index.d.ts +3 -3
- package/dist/src/index.js +1 -1
- package/dist/src/persistence.d.ts +4 -8
- package/dist/src/persistence.js +3 -0
- package/dist/src/registry.d.ts +3 -2
- package/dist/src/registry.js +16 -4
- package/dist/src/session-inspector.js +14 -22
- package/dist/src/types.d.ts +123 -60
- package/dist/src/validation.js +20 -5
- package/dist/src/workflow-artifacts.d.ts +1 -0
- package/dist/src/workflow-artifacts.js +1 -0
- package/package.json +2 -2
- package/skills/pi-extensible-workflows/SKILL.md +13 -5
- package/src/agent-execution.ts +221 -90
- package/src/cli.ts +14 -3
- package/src/doctor-cleanup.ts +36 -3
- package/src/execution.ts +27 -15
- package/src/host.ts +136 -62
- package/src/index.ts +3 -3
- package/src/persistence.ts +5 -3
- package/src/registry.ts +14 -5
- package/src/session-inspector.ts +13 -22
- package/src/types.ts +45 -27
- package/src/validation.ts +13 -4
- package/src/workflow-artifacts.ts +1 -0
|
@@ -4,7 +4,7 @@ import { join, resolve } from "node:path";
|
|
|
4
4
|
import { Type } from "@earendil-works/pi-ai";
|
|
5
5
|
import { Value } from "typebox/value";
|
|
6
6
|
import { createAgentSession, DefaultPackageManager, DefaultResourceLoader, getAgentDir, ModelRuntime, SessionManager, SettingsManager } from "@earendil-works/pi-coding-agent";
|
|
7
|
-
import { jsonObject, disabledResources, mergeAgentResourceExclusions, modelAliasName, modelCapability, resolveModelReference, unmatchedResourcePatterns } from "./utils.js";
|
|
7
|
+
import { deepFreeze, jsonObject, disabledResources, mergeAgentResourceExclusions, modelAliasName, modelCapability, resolveModelReference, unmatchedResourcePatterns } from "./utils.js";
|
|
8
8
|
import { WorkflowError } from "./types.js";
|
|
9
9
|
function parseModel(value, fallback, thinking, aliases = {}, knownModels, settingsPath) {
|
|
10
10
|
if (!value)
|
|
@@ -12,28 +12,23 @@ function parseModel(value, fallback, thinking, aliases = {}, knownModels, settin
|
|
|
12
12
|
const parsed = resolveModelReference(value, aliases, knownModels, settingsPath);
|
|
13
13
|
return { ...parsed, ...(thinking ? { thinking } : !parsed.thinking && fallback.thinking ? { thinking: fallback.thinking } : {}) };
|
|
14
14
|
}
|
|
15
|
-
function text(
|
|
16
|
-
const message = [...messages].reverse().find((item) => item.role === "assistant");
|
|
15
|
+
function text(message) {
|
|
17
16
|
if (!message || !Array.isArray(message.content))
|
|
18
17
|
return "";
|
|
19
18
|
return message.content.filter((part) => typeof part === "object" && part !== null && "type" in part && part.type === "text" && "text" in part && typeof part.text === "string").map((part) => part.text).join("");
|
|
20
19
|
}
|
|
20
|
+
function isEmptyAbortedAssistant(message) { return message?.stopReason === "aborted" && Array.isArray(message.content) && message.content.length === 0; }
|
|
21
21
|
function hasToolCall(message) {
|
|
22
22
|
return typeof message === "object" && message !== null && Array.isArray(message.content) && message.content.some((part) => typeof part === "object" && part !== null && part.type === "toolCall");
|
|
23
23
|
}
|
|
24
|
-
function latestAssistantHasToolCall(
|
|
25
|
-
|
|
26
|
-
return hasToolCall(message);
|
|
27
|
-
}
|
|
28
|
-
function throwIfTerminalAssistantError(session, fallbackModel) {
|
|
29
|
-
const message = [...session.messages].reverse().find((item) => item.role === "assistant");
|
|
24
|
+
function latestAssistantHasToolCall(message) { return hasToolCall(message); }
|
|
25
|
+
function throwIfTerminalAssistantError(session, message) {
|
|
30
26
|
if (message?.stopReason !== "error")
|
|
31
27
|
return;
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
const error = message.errorMessage ?? "Native Pi assistant ended with a terminal provider error";
|
|
28
|
+
const state = session.getState();
|
|
29
|
+
const error = message.errorMessage ?? "Workflow agent session ended with a terminal provider error";
|
|
35
30
|
const failure = new WorkflowError("AGENT_FAILED", error);
|
|
36
|
-
Object.defineProperty(failure, "terminalProviderError", { value: { provider, model, error }, configurable: true });
|
|
31
|
+
Object.defineProperty(failure, "terminalProviderError", { value: { provider: state.model.provider, model: state.model.model, error }, configurable: true });
|
|
37
32
|
throw failure;
|
|
38
33
|
}
|
|
39
34
|
function terminalProviderError(error) {
|
|
@@ -44,11 +39,11 @@ function terminalProviderError(error) {
|
|
|
44
39
|
return typeof candidate.provider === "string" && typeof candidate.model === "string" && typeof candidate.error === "string" ? { provider: candidate.provider, model: candidate.model, error: candidate.error } : undefined;
|
|
45
40
|
}
|
|
46
41
|
const providerContinuationPrompt = "The provider error was transient. Continue the task from your current state.";
|
|
47
|
-
async function recoverTerminalProviderError(session,
|
|
42
|
+
async function recoverTerminalProviderError(session, label, recovery, continuePrompt, getAssistant) {
|
|
48
43
|
let continued = false;
|
|
49
44
|
for (;;) {
|
|
50
45
|
try {
|
|
51
|
-
throwIfTerminalAssistantError(session,
|
|
46
|
+
throwIfTerminalAssistantError(session, getAssistant());
|
|
52
47
|
return continued;
|
|
53
48
|
}
|
|
54
49
|
catch (error) {
|
|
@@ -91,7 +86,7 @@ function workflowSystemPromptPath(cwd, agentDir, projectTrusted) {
|
|
|
91
86
|
const globalPaths = [join(homedir(), ".pi", WORKFLOW_DIRECTORY, "SYSTEM.md"), join(agentDir, WORKFLOW_DIRECTORY, "SYSTEM.md")];
|
|
92
87
|
return globalPaths.find((path) => existsSync(path));
|
|
93
88
|
}
|
|
94
|
-
export async function
|
|
89
|
+
export async function createLocalPiSession(input) {
|
|
95
90
|
const agentDir = input.agentDir ?? getAgentDir();
|
|
96
91
|
const systemPromptSource = workflowSystemPromptPath(input.cwd, agentDir, input.resourcePolicy?.projectTrusted ?? true);
|
|
97
92
|
const systemPromptOptions = input.systemPrompt !== undefined ? { systemPromptOverride: () => input.systemPrompt } : systemPromptSource !== undefined ? { systemPrompt: systemPromptSource } : {};
|
|
@@ -150,6 +145,77 @@ export async function createNativeAgentSession(input) {
|
|
|
150
145
|
getToolDefinitions: () => session.getAllTools().map(({ name, description, parameters, promptGuidelines }) => ({ name, description, parameters, ...(promptGuidelines ? { promptGuidelines } : {}) })),
|
|
151
146
|
});
|
|
152
147
|
}
|
|
148
|
+
function workflowAgentMessage(message) { return message ? { role: message.role, ...(message.content === undefined ? {} : { content: message.content }), ...(message.stopReason === undefined ? {} : { stopReason: message.stopReason }), ...(message.errorMessage === undefined ? {} : { errorMessage: message.errorMessage }), ...(message.usage === undefined ? {} : { usage: message.usage }) } : undefined; }
|
|
149
|
+
function latestUsableAssistant(messages) { for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
150
|
+
const candidate = workflowAgentMessage(messages[index]);
|
|
151
|
+
if (candidate?.role === "assistant" && !isEmptyAbortedAssistant(candidate))
|
|
152
|
+
return candidate;
|
|
153
|
+
} return undefined; }
|
|
154
|
+
function workflowAgentStats(stats) { return { tokens: { input: stats.tokens.input, output: stats.tokens.output, cacheRead: stats.tokens.cacheRead, cacheWrite: stats.tokens.cacheWrite, total: stats.tokens.total }, cost: stats.cost }; }
|
|
155
|
+
function workflowAgentState(native, prepared) {
|
|
156
|
+
const tools = native.agent?.state.tools.map(({ name }) => name) ?? prepared.tools;
|
|
157
|
+
const model = native.model?.provider && (native.model.model ?? native.model.id) ? { provider: native.model.provider, model: native.model.model ?? native.model.id ?? prepared.model.model, ...(prepared.model.thinking ? { thinking: prepared.model.thinking } : {}) } : { ...prepared.model };
|
|
158
|
+
return { model, ...(model.thinking ? { thinking: model.thinking } : {}), tools: [...tools], ...(native.systemPrompt === undefined ? {} : { systemPrompt: native.systemPrompt }) };
|
|
159
|
+
}
|
|
160
|
+
function localSessionEvent(event) { return event; }
|
|
161
|
+
export async function createLocalWorkflowAgentSession(prepared, context) {
|
|
162
|
+
void context;
|
|
163
|
+
const input = {
|
|
164
|
+
cwd: prepared.cwd, model: { ...prepared.model }, tools: [...prepared.tools], sessionLabel: prepared.sessionLabel,
|
|
165
|
+
...(prepared.agentDir ? { agentDir: prepared.agentDir } : {}), ...(prepared.customTools?.length ? { customTools: [...prepared.customTools] } : {}),
|
|
166
|
+
...(prepared.resultTool ? { resultTool: prepared.resultTool } : {}), ...(prepared.systemPrompt === undefined ? {} : { systemPrompt: prepared.systemPrompt }),
|
|
167
|
+
...(prepared.systemPromptAppend ? { systemPromptAppend: prepared.systemPromptAppend } : {}), ...(prepared.extensionFactories?.length ? { extensionFactories: [...prepared.extensionFactories] } : {}),
|
|
168
|
+
...(prepared.additionalSkillPaths?.length ? { additionalSkillPaths: [...prepared.additionalSkillPaths] } : {}), ...(prepared.resourcePolicy ? { resourcePolicy: structuredClone(prepared.resourcePolicy) } : {}), ...(prepared.options ? { options: { ...prepared.options } } : {}),
|
|
169
|
+
};
|
|
170
|
+
const native = await createLocalPiSession(input);
|
|
171
|
+
let disposal;
|
|
172
|
+
let aborting;
|
|
173
|
+
let prompting;
|
|
174
|
+
let disposed = false;
|
|
175
|
+
const startAbort = () => aborting ??= Promise.resolve().then(() => native.abort?.()).then(() => undefined).finally(() => { aborting = undefined; });
|
|
176
|
+
const reference = { transport: "local", sessionId: native.sessionId, ...(native.sessionFile ? { locator: { sessionFile: native.sessionFile } } : {}) };
|
|
177
|
+
const session = {
|
|
178
|
+
reference,
|
|
179
|
+
getState: () => Object.freeze(workflowAgentState(native, prepared)),
|
|
180
|
+
getSessionStats: () => workflowAgentStats(native.getSessionStats()),
|
|
181
|
+
subscribe(listener) { listener({ type: "state_changed", state: workflowAgentState(native, prepared) }); return native.subscribe?.((event) => { listener(localSessionEvent(event)); }) ?? (() => undefined); },
|
|
182
|
+
getLastAssistant: () => latestUsableAssistant(native.messages),
|
|
183
|
+
async prompt(text) {
|
|
184
|
+
if (disposed)
|
|
185
|
+
throw new WorkflowError("INTERNAL_ERROR", "Local workflow session is disposed");
|
|
186
|
+
const prompt = Promise.resolve().then(async () => { await native.prompt(text); const assistant = latestUsableAssistant(native.messages); return assistant ? { assistant } : {}; });
|
|
187
|
+
prompting = prompt;
|
|
188
|
+
try {
|
|
189
|
+
return await prompt;
|
|
190
|
+
}
|
|
191
|
+
finally {
|
|
192
|
+
if (prompting === prompt)
|
|
193
|
+
prompting = undefined;
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
async steer(text) { if (!native.steer)
|
|
197
|
+
throw new WorkflowError("INTERNAL_ERROR", "Local workflow session does not support steering"); await native.steer(text); },
|
|
198
|
+
async abort() { if (disposed)
|
|
199
|
+
return; await startAbort(); },
|
|
200
|
+
async dispose() {
|
|
201
|
+
disposal ??= (async () => {
|
|
202
|
+
disposed = true;
|
|
203
|
+
try {
|
|
204
|
+
await startAbort();
|
|
205
|
+
}
|
|
206
|
+
catch { /* Abort failure must not prevent the prompt from settling. */ }
|
|
207
|
+
try {
|
|
208
|
+
await prompting;
|
|
209
|
+
}
|
|
210
|
+
catch { /* Prompt rejection is expected after abort during teardown. */ }
|
|
211
|
+
native.dispose();
|
|
212
|
+
})();
|
|
213
|
+
await disposal;
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
return session;
|
|
217
|
+
}
|
|
218
|
+
export const localAgentTransport = Object.freeze({ id: "local", createSession: createLocalWorkflowAgentSession });
|
|
153
219
|
function changedOption(options, baseline, key) { return JSON.stringify(options[key]) !== JSON.stringify(baseline[key]); }
|
|
154
220
|
function validThinking(value) { return typeof value === "string" && ["off", "minimal", "low", "medium", "high", "xhigh", "max"].includes(value); }
|
|
155
221
|
function isChildAgentToolParams(value) {
|
|
@@ -179,51 +245,94 @@ function fallbackSetupContext(root, options, signal) {
|
|
|
179
245
|
function resourcePolicySummary(policy) {
|
|
180
246
|
return { skills: [...policy.effective.skills], extensions: [...policy.effective.extensions], excludedSkills: [...(policy.excludedSkills ?? [])], excludedExtensions: [...(policy.excludedExtensions ?? [])], unmatchedSkills: [...policy.unmatchedSkills], unmatchedExtensions: [...policy.unmatchedExtensions] };
|
|
181
247
|
}
|
|
182
|
-
|
|
248
|
+
function resourcePolicyWidened(ceiling, candidate) {
|
|
249
|
+
if (!ceiling)
|
|
250
|
+
return false;
|
|
251
|
+
if (!candidate)
|
|
252
|
+
return true;
|
|
253
|
+
if (!ceiling.projectTrusted && candidate.projectTrusted)
|
|
254
|
+
return true;
|
|
255
|
+
return ceiling.effective.skills.some((pattern) => !candidate.effective.skills.includes(pattern)) || ceiling.effective.extensions.some((pattern) => !candidate.effective.extensions.includes(pattern));
|
|
256
|
+
}
|
|
257
|
+
function preparedAgentSession(input) {
|
|
258
|
+
const prepared = {
|
|
259
|
+
cwd: input.cwd, model: Object.freeze({ ...input.model }), tools: Object.freeze([...input.tools]), sessionLabel: input.sessionLabel,
|
|
260
|
+
...(input.agentDir ? { agentDir: input.agentDir } : {}), ...(input.customTools?.length ? { customTools: Object.freeze([...input.customTools]) } : {}), ...(input.resultTool ? { resultTool: input.resultTool } : {}), ...(input.options ? { options: Object.freeze(structuredClone(input.options)) } : {}),
|
|
261
|
+
...(input.systemPrompt === undefined ? {} : { systemPrompt: input.systemPrompt }), ...(input.systemPromptAppend ? { systemPromptAppend: input.systemPromptAppend } : {}),
|
|
262
|
+
...(input.extensionFactories?.length ? { extensionFactories: Object.freeze([...input.extensionFactories]) } : {}), ...(input.additionalSkillPaths?.length ? { additionalSkillPaths: Object.freeze([...input.additionalSkillPaths]) } : {}),
|
|
263
|
+
...(input.resourcePolicy ? { resourcePolicy: Object.freeze(structuredClone(input.resourcePolicy)) } : {}),
|
|
264
|
+
};
|
|
265
|
+
return deepFreeze(prepared);
|
|
266
|
+
}
|
|
267
|
+
function agentSetupSummary(setup, hookNames) {
|
|
268
|
+
const model = setup.sessionInput.model;
|
|
269
|
+
return { hookNames: [...hookNames], model: { provider: model.provider, model: model.model, ...(model.thinking ? { thinking: model.thinking } : {}) }, tools: [...setup.sessionInput.tools], cwd: setup.sessionInput.cwd, ...(setup.sessionInput.resourcePolicy ? { disabledAgentResources: resourcePolicySummary(setup.sessionInput.resourcePolicy) } : {}) };
|
|
270
|
+
}
|
|
271
|
+
async function prepareAgentSetup(root, transport, task, options, resolved, cwd, attempt, signal, customTools, resultTool) {
|
|
183
272
|
const setupSignal = signal ?? root.runContext?.signal ?? new AbortController().signal;
|
|
184
273
|
const baselineOptions = structuredClone(options.agentOptions ?? {});
|
|
185
274
|
const baseResourcePolicy = await root.agentResourcePolicy?.();
|
|
186
275
|
const roleExclusions = options.role ? root.agentDefinitions?.[options.role]?.disabledAgentResources : undefined;
|
|
187
276
|
const resourcePolicy = baseResourcePolicy && roleExclusions ? { ...baseResourcePolicy, effective: mergeAgentResourceExclusions(baseResourcePolicy.effective, roleExclusions) } : baseResourcePolicy;
|
|
277
|
+
const resourcePolicyCeiling = resourcePolicy ? structuredClone(resourcePolicy) : undefined;
|
|
188
278
|
const sessionInput = { cwd, model: { ...resolved.model }, tools: [...resolved.tools], sessionLabel: `${options.workflowName}:${options.label}:attempt-${String(attempt)}`, ...(root.agentDir ? { agentDir: root.agentDir } : {}), ...(root.additionalSkillPaths?.length ? { additionalSkillPaths: [...root.additionalSkillPaths] } : {}), ...(customTools.length ? { customTools: [...customTools] } : {}), ...(resultTool ? { resultTool } : {}), ...(resolved.systemPrompt !== undefined ? { systemPrompt: resolved.systemPrompt } : {}), systemPromptAppend: resolved.systemPromptAppend, ...(resourcePolicy ? { resourcePolicy } : {}), options: structuredClone(baselineOptions) };
|
|
189
|
-
const setup = { prompt: task, options: sessionInput.options ?? {}, sessionInput,
|
|
279
|
+
const setup = { prompt: task, options: sessionInput.options ?? {}, sessionInput, prepared: preparedAgentSession(sessionInput), transport };
|
|
190
280
|
const base = fallbackSetupContext(root, options, setupSignal);
|
|
191
281
|
const context = Object.freeze({ run: base.run, identity: base.identity, attempt, signal: setupSignal });
|
|
192
282
|
const hookNames = [];
|
|
193
283
|
for (const hook of [...(root.agentSetupHooks ?? [])].sort((left, right) => left.priority - right.priority || (left.name < right.name ? -1 : left.name > right.name ? 1 : 0))) {
|
|
194
284
|
if (setupSignal.aborted)
|
|
195
|
-
|
|
285
|
+
return { setup, summary: agentSetupSummary(setup, hookNames), failure: { error: new WorkflowError("CANCELLED", "Agent cancelled") } };
|
|
196
286
|
try {
|
|
197
287
|
await hook.setup(setup, context);
|
|
198
288
|
}
|
|
199
289
|
catch (error) {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
throw error;
|
|
290
|
+
setup.prepared = preparedAgentSession(setup.sessionInput);
|
|
291
|
+
return { setup, summary: agentSetupSummary(setup, hookNames), failure: { error: setupSignal.reason !== undefined ? new WorkflowError("CANCELLED", "Agent cancelled") : error } };
|
|
203
292
|
}
|
|
293
|
+
setup.prepared = preparedAgentSession(setup.sessionInput);
|
|
204
294
|
hookNames.push(hook.name);
|
|
205
295
|
if (setupSignal.reason !== undefined)
|
|
206
|
-
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
setup.
|
|
213
|
-
|
|
214
|
-
setup.
|
|
215
|
-
|
|
216
|
-
setup.
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
296
|
+
return { setup, summary: agentSetupSummary(setup, hookNames), failure: { error: new WorkflowError("CANCELLED", "Agent cancelled") } };
|
|
297
|
+
}
|
|
298
|
+
try {
|
|
299
|
+
if (resourcePolicyWidened(resourcePolicyCeiling, setup.sessionInput.resourcePolicy))
|
|
300
|
+
throw new WorkflowError("INVALID_METADATA", "Agent setup widened the prepared resource policy");
|
|
301
|
+
setup.sessionInput.options = setup.options;
|
|
302
|
+
if (changedOption(setup.options, baselineOptions, "model") && typeof setup.options.model === "string")
|
|
303
|
+
setup.sessionInput.model = parseModel(setup.options.model, setup.sessionInput.model, changedOption(setup.options, baselineOptions, "thinking") && validThinking(setup.options.thinking) ? setup.options.thinking : undefined, root.modelAliases, root.knownModels ?? root.availableModels, root.settingsPath);
|
|
304
|
+
if (changedOption(setup.options, baselineOptions, "thinking") && validThinking(setup.options.thinking))
|
|
305
|
+
setup.sessionInput.model = { ...setup.sessionInput.model, thinking: setup.options.thinking };
|
|
306
|
+
if (changedOption(setup.options, baselineOptions, "tools") && Array.isArray(setup.options.tools) && setup.options.tools.every((tool) => typeof tool === "string"))
|
|
307
|
+
setup.sessionInput.tools = [...setup.options.tools];
|
|
308
|
+
if (changedOption(setup.options, baselineOptions, "cwd") && typeof setup.options.cwd === "string")
|
|
309
|
+
setup.sessionInput.cwd = setup.options.cwd;
|
|
310
|
+
const customToolNames = new Set([...(setup.sessionInput.customTools ?? []).map(({ name }) => name), ...(setup.sessionInput.resultTool ? [setup.sessionInput.resultTool.name] : [])]);
|
|
311
|
+
const widened = setup.sessionInput.tools.find((tool) => !resolved.tools.includes(tool) && !customToolNames.has(tool));
|
|
312
|
+
const outsideTool = widened ?? setup.sessionInput.tools.find((tool) => !root.tools.has(tool) && !customToolNames.has(tool));
|
|
313
|
+
if (outsideTool)
|
|
314
|
+
throw new WorkflowError("UNKNOWN_TOOL", `Tool is outside the prepared agent policy: ${outsideTool}`);
|
|
315
|
+
}
|
|
316
|
+
catch (error) {
|
|
317
|
+
setup.prepared = preparedAgentSession(setup.sessionInput);
|
|
318
|
+
return { setup, summary: agentSetupSummary(setup, hookNames), failure: { error } };
|
|
319
|
+
}
|
|
320
|
+
setup.prepared = preparedAgentSession(setup.sessionInput);
|
|
321
|
+
return { setup, summary: agentSetupSummary(setup, hookNames) };
|
|
322
|
+
}
|
|
323
|
+
function attemptRecord(transport, attempt, session, setup, stats, result, error) {
|
|
324
|
+
return { attempt, transport, session: session.reference, ...(result === undefined ? {} : { result }), ...(error ? { error } : {}), accounting: stats, setup };
|
|
325
|
+
}
|
|
326
|
+
function errorWithAttempts(error, attempts) {
|
|
327
|
+
const typed = error instanceof Error ? error : new Error(typeof error === "string" ? error : String(error));
|
|
328
|
+
return Object.assign(typed, { attempts });
|
|
220
329
|
}
|
|
221
330
|
export class WorkflowAgentExecutor {
|
|
222
331
|
root;
|
|
223
|
-
|
|
224
|
-
constructor(root,
|
|
332
|
+
transport;
|
|
333
|
+
constructor(root, transport = localAgentTransport) {
|
|
225
334
|
this.root = root;
|
|
226
|
-
this.
|
|
335
|
+
this.transport = transport;
|
|
227
336
|
}
|
|
228
337
|
setRunContext(runContext) { this.root.runContext = runContext; }
|
|
229
338
|
resolve(options, inheritedTools) {
|
|
@@ -291,16 +400,16 @@ export class WorkflowAgentExecutor {
|
|
|
291
400
|
const attempts = [];
|
|
292
401
|
let maxAttempts = (options.retries ?? 0) + 1;
|
|
293
402
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
403
|
+
const attemptSignal = executionSignal ?? new AbortController().signal;
|
|
294
404
|
if (recoveryModel)
|
|
295
405
|
resolved = this.resolve({ ...options, modelOverride: recoveryModel });
|
|
296
406
|
options.budget?.beforeAttempt();
|
|
297
407
|
let schemaResult;
|
|
298
408
|
let session;
|
|
299
409
|
let setup;
|
|
300
|
-
let setupSummary;
|
|
410
|
+
let setupSummary = { hookNames: [], model: { ...resolved.model }, tools: [...resolved.tools], cwd };
|
|
301
411
|
let setupFailed = false;
|
|
302
412
|
let budgetError;
|
|
303
|
-
let turnStarted = false;
|
|
304
413
|
const hasSchemaResult = () => schemaResult !== undefined;
|
|
305
414
|
const resultTool = options.schema ? {
|
|
306
415
|
name: "workflow_result", label: "Workflow Result", description: "Submit the terminal structured workflow result", parameters: Type.Unsafe(options.schema),
|
|
@@ -310,7 +419,9 @@ export class WorkflowAgentExecutor {
|
|
|
310
419
|
if (schemaResult !== undefined)
|
|
311
420
|
return { content: [{ type: "text", text: "Result has already been accepted." }], details: {}, isError: true };
|
|
312
421
|
schemaResult = structuredClone(value);
|
|
313
|
-
|
|
422
|
+
const currentSession = session;
|
|
423
|
+
if (currentSession)
|
|
424
|
+
void currentSession.abort();
|
|
314
425
|
return { content: [{ type: "text", text: "Result accepted." }], details: {} };
|
|
315
426
|
},
|
|
316
427
|
} : undefined;
|
|
@@ -323,6 +434,8 @@ export class WorkflowAgentExecutor {
|
|
|
323
434
|
let systemPromptTurn = 0;
|
|
324
435
|
let systemPromptWrite = Promise.resolve();
|
|
325
436
|
let systemPromptWriteError;
|
|
437
|
+
let turnStarted = false;
|
|
438
|
+
let completedAttempt;
|
|
326
439
|
const flushSystemPrompts = async () => {
|
|
327
440
|
await systemPromptWrite;
|
|
328
441
|
if (systemPromptWriteError)
|
|
@@ -331,7 +444,7 @@ export class WorkflowAgentExecutor {
|
|
|
331
444
|
const report = (persist) => {
|
|
332
445
|
if (!session || !options.onProgress)
|
|
333
446
|
return;
|
|
334
|
-
const update = { accounting: accounting(session.getSessionStats()), toolCalls: [...toolCalls.values()], ...(activity ? { activity } : {}), ...(lastEventAt === undefined ? {} : { lastEventAt }), persist };
|
|
447
|
+
const update = { accounting: accounting(session.getSessionStats()), toolCalls: [...toolCalls.values()], state: session.getState(), ...(activity ? { activity } : {}), ...(lastEventAt === undefined ? {} : { lastEventAt }), persist };
|
|
335
448
|
if (lastEventAt !== undefined)
|
|
336
449
|
lastReportedEventAt = lastEventAt;
|
|
337
450
|
progress = progress.then(() => options.onProgress?.(update)).then(() => undefined);
|
|
@@ -344,34 +457,57 @@ export class WorkflowAgentExecutor {
|
|
|
344
457
|
const activityChanged = (previous) => previous?.kind !== activity?.kind || previous?.text !== activity?.text;
|
|
345
458
|
try {
|
|
346
459
|
setupFailed = true;
|
|
347
|
-
const prepared = await prepareAgentSetup(this.root, this.
|
|
460
|
+
const prepared = await prepareAgentSetup(this.root, this.transport, task, options, resolved, cwd, attempt, attemptSignal, customTools, resultTool);
|
|
348
461
|
setup = prepared.setup;
|
|
349
462
|
setupSummary = prepared.summary;
|
|
463
|
+
if (prepared.failure)
|
|
464
|
+
throw prepared.failure.error;
|
|
350
465
|
setupFailed = false;
|
|
351
|
-
if (
|
|
466
|
+
if (attemptSignal.aborted)
|
|
352
467
|
throw new WorkflowError("CANCELLED", "Agent cancelled");
|
|
353
468
|
const started = Date.now();
|
|
354
|
-
|
|
469
|
+
const transportSignal = attemptSignal;
|
|
470
|
+
await options.onAttempt?.({ attempt, transport: setup.transport.id, accounting: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 }, setup: setupSummary });
|
|
471
|
+
const transportBase = fallbackSetupContext(this.root, options, transportSignal);
|
|
472
|
+
const createdSession = await setup.transport.createSession(setup.prepared, Object.freeze({ run: transportBase.run, identity: transportBase.identity, attempt, signal: transportSignal }));
|
|
473
|
+
if (createdSession.reference.transport !== setup.transport.id) {
|
|
474
|
+
await createdSession.dispose();
|
|
475
|
+
throw new WorkflowError("INTERNAL_ERROR", `Agent transport ${setup.transport.id} created a session for ${createdSession.reference.transport}`);
|
|
476
|
+
}
|
|
477
|
+
session = createdSession;
|
|
478
|
+
await options.onAttempt?.({ attempt, transport: setup.transport.id, session: session.reference, liveSession: session, accounting: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 }, setup: setupSummary });
|
|
479
|
+
const preparedTools = new Set([...setup.prepared.tools, ...(setup.prepared.customTools ?? []).map(({ name }) => name), ...(setup.prepared.resultTool ? [setup.prepared.resultTool.name] : [])]);
|
|
480
|
+
if (session.getState().tools.some((tool) => !preparedTools.has(tool)))
|
|
481
|
+
throw new WorkflowError("INTERNAL_ERROR", `Agent transport ${setup.transport.id} widened the prepared tool policy`);
|
|
355
482
|
if (setup.sessionInput.resourcePolicy)
|
|
356
483
|
setupSummary = { ...setupSummary, disabledAgentResources: resourcePolicySummary(setup.sessionInput.resourcePolicy) };
|
|
357
|
-
const includeAttemptSetup = Boolean(this.root.agentSetupHooks?.length || setup.sessionInput.resourcePolicy);
|
|
358
|
-
await options.onAttempt?.({ attempt, sessionId: session.sessionId, sessionFile: requiredFile(session.sessionFile), ...(includeAttemptSetup ? { setup: setupSummary } : {}) });
|
|
359
484
|
const activeSession = session;
|
|
360
|
-
|
|
361
|
-
const
|
|
362
|
-
|
|
485
|
+
let lastAssistant;
|
|
486
|
+
const acceptAssistant = (candidate) => {
|
|
487
|
+
if (isEmptyAbortedAssistant(candidate)) {
|
|
488
|
+
const previous = activeSession.getLastAssistant?.();
|
|
489
|
+
if (previous && !isEmptyAbortedAssistant(previous))
|
|
490
|
+
lastAssistant = previous;
|
|
491
|
+
}
|
|
492
|
+
else if (candidate)
|
|
493
|
+
lastAssistant = candidate;
|
|
494
|
+
};
|
|
495
|
+
const recoverTerminal = () => recoverTerminalProviderError(activeSession, options.label, options.providerErrorRecovery, async () => { try {
|
|
496
|
+
acceptAssistant((await promptWithProviderPause(activeSession, providerContinuationPrompt, remaining(options.timeoutMs, started), attemptSignal, this.root.providerPause)).assistant);
|
|
363
497
|
}
|
|
364
498
|
catch (error) {
|
|
499
|
+
acceptAssistant(activeSession.getLastAssistant?.() ?? lastAssistant);
|
|
365
500
|
if (!hasSchemaResult())
|
|
366
501
|
throw error;
|
|
367
|
-
} });
|
|
502
|
+
} }, () => lastAssistant);
|
|
368
503
|
const promptAndRecover = async (prompt) => {
|
|
369
504
|
let promptFailed = false;
|
|
370
505
|
let promptError;
|
|
371
506
|
try {
|
|
372
|
-
await promptWithProviderPause(activeSession, prompt, remaining(options.timeoutMs, started),
|
|
507
|
+
acceptAssistant((await promptWithProviderPause(activeSession, prompt, remaining(options.timeoutMs, started), attemptSignal, this.root.providerPause)).assistant);
|
|
373
508
|
}
|
|
374
509
|
catch (error) {
|
|
510
|
+
acceptAssistant(activeSession.getLastAssistant?.() ?? lastAssistant);
|
|
375
511
|
promptFailed = true;
|
|
376
512
|
promptError = error;
|
|
377
513
|
}
|
|
@@ -379,19 +515,35 @@ export class WorkflowAgentExecutor {
|
|
|
379
515
|
if (promptFailed && !hasSchemaResult() && !recovered)
|
|
380
516
|
throw promptError;
|
|
381
517
|
};
|
|
382
|
-
unsubscribe = activeSession.subscribe
|
|
518
|
+
unsubscribe = activeSession.subscribe((event) => {
|
|
383
519
|
lastEventAt = Date.now();
|
|
384
520
|
let persist = false;
|
|
385
521
|
let shouldReport = false;
|
|
386
522
|
let removeToolCallId;
|
|
387
|
-
if (event.type === "agent_start" && session?.systemPrompt !== undefined) {
|
|
523
|
+
if (event.type === "agent_start" && session?.getState().systemPrompt !== undefined) {
|
|
388
524
|
if (this.root.runStore) {
|
|
389
525
|
systemPromptTurn += 1;
|
|
390
|
-
const entry = { sessionId: session.sessionId, attempt, turn: systemPromptTurn, prompt: session.systemPrompt };
|
|
526
|
+
const entry = { sessionId: session.reference.sessionId, attempt, turn: systemPromptTurn, prompt: session.getState().systemPrompt ?? "" };
|
|
391
527
|
systemPromptWrite = systemPromptWrite.then(() => this.root.runStore?.recordSystemPrompt(entry)).then(() => undefined).catch((error) => { systemPromptWriteError ??= error; });
|
|
392
528
|
}
|
|
393
529
|
}
|
|
394
|
-
if (event.type === "
|
|
530
|
+
if (event.type === "state_changed") {
|
|
531
|
+
shouldReport = true;
|
|
532
|
+
persist = true;
|
|
533
|
+
}
|
|
534
|
+
if (event.type === "turnStarted" || event.type === "turn_started") {
|
|
535
|
+
if (!turnStarted) {
|
|
536
|
+
try {
|
|
537
|
+
options.budget?.beforeTurn();
|
|
538
|
+
turnStarted = true;
|
|
539
|
+
}
|
|
540
|
+
catch (error) {
|
|
541
|
+
budgetError ??= error instanceof WorkflowError ? error : new WorkflowError("BUDGET_EXHAUSTED", error instanceof Error ? error.message : String(error));
|
|
542
|
+
void activeSession.abort();
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
if (event.type === "message_start" && event.message?.role === "assistant") {
|
|
395
547
|
if (!turnStarted) {
|
|
396
548
|
try {
|
|
397
549
|
options.budget?.beforeTurn();
|
|
@@ -399,7 +551,7 @@ export class WorkflowAgentExecutor {
|
|
|
399
551
|
}
|
|
400
552
|
catch (error) {
|
|
401
553
|
budgetError ??= error instanceof WorkflowError ? error : new WorkflowError("BUDGET_EXHAUSTED", error instanceof Error ? error.message : String(error));
|
|
402
|
-
void
|
|
554
|
+
void activeSession.abort();
|
|
403
555
|
}
|
|
404
556
|
}
|
|
405
557
|
activity = { kind: "text", text: "responding" };
|
|
@@ -407,9 +559,10 @@ export class WorkflowAgentExecutor {
|
|
|
407
559
|
}
|
|
408
560
|
if (event.type === "message_update") {
|
|
409
561
|
const previousActivity = activity;
|
|
410
|
-
|
|
562
|
+
const updateType = event.assistantMessageEvent?.type;
|
|
563
|
+
if (updateType && ["thinking_start", "thinking_delta", "thinking_end"].includes(updateType))
|
|
411
564
|
activity = { kind: "reasoning", text: "reasoning" };
|
|
412
|
-
else if (["text_start", "text_delta", "text_end", "toolcall_start", "toolcall_delta", "toolcall_end"].includes(
|
|
565
|
+
else if (updateType && ["text_start", "text_delta", "text_end", "toolcall_start", "toolcall_delta", "toolcall_end"].includes(updateType))
|
|
413
566
|
activity = { kind: "text", text: "responding" };
|
|
414
567
|
shouldReport = activityChanged(previousActivity);
|
|
415
568
|
}
|
|
@@ -417,7 +570,8 @@ export class WorkflowAgentExecutor {
|
|
|
417
570
|
const previousActivity = activity;
|
|
418
571
|
activity = undefined;
|
|
419
572
|
shouldReport = activityChanged(previousActivity);
|
|
420
|
-
if (event.message
|
|
573
|
+
if (event.message?.role === "assistant") {
|
|
574
|
+
acceptAssistant(event.message);
|
|
421
575
|
const needsMoreWork = hasToolCall(event.message);
|
|
422
576
|
const final = !needsMoreWork || (options.schema !== undefined && hasSchemaResult());
|
|
423
577
|
if (!budgetError) {
|
|
@@ -426,29 +580,29 @@ export class WorkflowAgentExecutor {
|
|
|
426
580
|
if (!final) {
|
|
427
581
|
const instruction = options.budget?.instruction();
|
|
428
582
|
if (instruction)
|
|
429
|
-
void
|
|
583
|
+
void activeSession.steer(instruction);
|
|
430
584
|
}
|
|
431
585
|
}
|
|
432
586
|
catch (error) {
|
|
433
587
|
budgetError ??= error instanceof WorkflowError ? error : new WorkflowError("BUDGET_EXHAUSTED", error instanceof Error ? error.message : String(error));
|
|
434
|
-
void
|
|
588
|
+
void activeSession.abort();
|
|
435
589
|
}
|
|
436
590
|
}
|
|
437
591
|
turnStarted = false;
|
|
438
592
|
persist = true;
|
|
439
593
|
}
|
|
440
594
|
}
|
|
441
|
-
if (event.type === "tool_execution_start") {
|
|
595
|
+
if (event.type === "tool_execution_start" && event.toolCallId && event.toolName) {
|
|
442
596
|
toolCalls.set(event.toolCallId, { id: event.toolCallId, name: event.toolName, state: "running" });
|
|
443
597
|
activity = { kind: "tool", text: event.toolName };
|
|
444
598
|
shouldReport = true;
|
|
445
599
|
}
|
|
446
|
-
if (event.type === "tool_execution_update") {
|
|
600
|
+
if (event.type === "tool_execution_update" && event.toolName) {
|
|
447
601
|
const previousActivity = activity;
|
|
448
602
|
activity = { kind: "tool", text: event.toolName };
|
|
449
603
|
shouldReport = activityChanged(previousActivity);
|
|
450
604
|
}
|
|
451
|
-
if (event.type === "tool_execution_end") {
|
|
605
|
+
if (event.type === "tool_execution_end" && event.toolCallId && event.toolName) {
|
|
452
606
|
toolCalls.set(event.toolCallId, { id: event.toolCallId, name: event.toolName, state: event.isError ? "failed" : "completed" });
|
|
453
607
|
if (activity?.kind === "tool" && activity.text === event.toolName)
|
|
454
608
|
activity = undefined;
|
|
@@ -464,9 +618,7 @@ export class WorkflowAgentExecutor {
|
|
|
464
618
|
});
|
|
465
619
|
report(false);
|
|
466
620
|
if (setSteer) {
|
|
467
|
-
|
|
468
|
-
throw new WorkflowError("INTERNAL_ERROR", "Native Pi session does not support steering");
|
|
469
|
-
setSteer((message) => session?.steer?.(message));
|
|
621
|
+
setSteer((message) => activeSession.steer(message));
|
|
470
622
|
}
|
|
471
623
|
const context = [`Workflow: ${options.workflowName}`, `Agent: ${options.label}`, options.phase ? `Phase: ${options.phase}` : "", options.parent ? `Parent: ${options.parent}` : "", "You own this task and any direct child agents you create. Return child results to your parent; do not leave descendants running.", attempt > 1 ? `Retry attempt ${String(attempt)}. Previous state: ${options.retryState ?? attempts.at(-1)?.error?.message ?? "failed attempt"}` : ""].filter(Boolean).join("\n");
|
|
472
624
|
const instruction = options.budget?.instruction();
|
|
@@ -476,7 +628,7 @@ export class WorkflowAgentExecutor {
|
|
|
476
628
|
await promptAndRecover(promptText);
|
|
477
629
|
{
|
|
478
630
|
const completedAccounting = accounting(session.getSessionStats());
|
|
479
|
-
options.budget?.afterTurn(completedAccounting, options.schema !== undefined ? hasSchemaResult() : !latestAssistantHasToolCall(
|
|
631
|
+
options.budget?.afterTurn(completedAccounting, options.schema !== undefined ? hasSchemaResult() : !latestAssistantHasToolCall(lastAssistant));
|
|
480
632
|
turnStarted = false;
|
|
481
633
|
}
|
|
482
634
|
if (budgetError)
|
|
@@ -505,24 +657,41 @@ export class WorkflowAgentExecutor {
|
|
|
505
657
|
if (schemaResult === undefined)
|
|
506
658
|
throw new WorkflowError("RESULT_INVALID", "Agent did not submit a valid workflow_result after one repair");
|
|
507
659
|
}
|
|
508
|
-
const value = options.schema ? schemaResult : text(
|
|
660
|
+
const value = options.schema ? schemaResult : text(lastAssistant);
|
|
509
661
|
if (options.worktreeOwner)
|
|
510
662
|
await this.root.runStore?.snapshotWorktree(options.worktreeOwner);
|
|
511
663
|
report(true);
|
|
512
664
|
await progress;
|
|
513
665
|
await flushSystemPrompts();
|
|
514
|
-
unsubscribe
|
|
666
|
+
unsubscribe();
|
|
515
667
|
const attemptAccounting = accounting(session.getSessionStats());
|
|
516
|
-
|
|
517
|
-
attempts.push(
|
|
518
|
-
|
|
668
|
+
completedAttempt = attemptRecord(setup.transport.id, attempt, session, setupSummary, attemptAccounting, value);
|
|
669
|
+
attempts.push(completedAttempt);
|
|
670
|
+
try {
|
|
671
|
+
await options.onAttempt?.(completedAttempt);
|
|
672
|
+
}
|
|
673
|
+
finally {
|
|
674
|
+
await session.dispose();
|
|
675
|
+
}
|
|
519
676
|
return { value, attempts, cwd: setupSummary.cwd };
|
|
520
677
|
}
|
|
521
678
|
catch (error) {
|
|
522
|
-
|
|
679
|
+
if (completedAttempt)
|
|
680
|
+
throw errorWithAttempts(error, attempts);
|
|
681
|
+
const typed = budgetError ?? (error instanceof WorkflowError ? error : new WorkflowError(attemptSignal.aborted && setupFailed ? "CANCELLED" : "AGENT_FAILED", error instanceof Error ? error.message : String(error)));
|
|
682
|
+
if (!session) {
|
|
683
|
+
const failedAttempt = { attempt, transport: setup?.transport.id ?? this.transport.id, accounting: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 }, error: { code: typed.code, message: typed.message }, setup: setupSummary };
|
|
684
|
+
attempts.push(failedAttempt);
|
|
685
|
+
try {
|
|
686
|
+
await options.onAttempt?.(failedAttempt);
|
|
687
|
+
}
|
|
688
|
+
catch (persistenceError) {
|
|
689
|
+
throw errorWithAttempts(persistenceError, attempts);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
523
692
|
if (session) {
|
|
524
693
|
report(true);
|
|
525
|
-
await progress;
|
|
694
|
+
await progress.catch(() => undefined);
|
|
526
695
|
try {
|
|
527
696
|
await flushSystemPrompts();
|
|
528
697
|
}
|
|
@@ -537,9 +706,19 @@ export class WorkflowAgentExecutor {
|
|
|
537
706
|
budgetError ??= budgetFailure instanceof WorkflowError ? budgetFailure : new WorkflowError("BUDGET_EXHAUSTED", budgetFailure instanceof Error ? budgetFailure.message : String(budgetFailure));
|
|
538
707
|
}
|
|
539
708
|
}
|
|
540
|
-
const
|
|
541
|
-
attempts.push(
|
|
542
|
-
|
|
709
|
+
const failedAttempt = attemptRecord(setup?.transport.id ?? this.transport.id, attempt, session, setupSummary, attemptAccounting, undefined, { code: typed.code, message: typed.message });
|
|
710
|
+
attempts.push(failedAttempt);
|
|
711
|
+
try {
|
|
712
|
+
try {
|
|
713
|
+
await options.onAttempt?.(failedAttempt);
|
|
714
|
+
}
|
|
715
|
+
finally {
|
|
716
|
+
await session.dispose();
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
catch (persistenceError) {
|
|
720
|
+
throw errorWithAttempts(persistenceError, attempts);
|
|
721
|
+
}
|
|
543
722
|
}
|
|
544
723
|
if (options.worktreeOwner && typed.code !== "WORKTREE_FAILED")
|
|
545
724
|
await this.root.runStore?.snapshotWorktree(options.worktreeOwner).catch(() => undefined);
|
|
@@ -627,7 +806,7 @@ export class FairAgentScheduler {
|
|
|
627
806
|
const id = `${runId}:${String(++this.#nextId)}`;
|
|
628
807
|
let resolveResult = () => undefined;
|
|
629
808
|
const promise = new Promise((resolve) => { resolveResult = resolve; });
|
|
630
|
-
const node = { id, runId, ...(parentId ? { parentId } : {}), options: effective, children: new Set(), collected: false, state: "queued", controller: new AbortController(), promise, resolve: resolveResult, task: async () => undefined, restored: false };
|
|
809
|
+
const node = { id, runId, ...(parentId ? { parentId } : {}), prompt, options: effective, children: new Set(), collected: false, state: "queued", controller: new AbortController(), promise, resolve: resolveResult, task: async () => undefined, restored: false };
|
|
631
810
|
node.task = async () => {
|
|
632
811
|
if (node.controller.signal.aborted) {
|
|
633
812
|
this.#release(node.runId);
|
|
@@ -766,7 +945,7 @@ export class FairAgentScheduler {
|
|
|
766
945
|
return [agentTool, resultTool, steerTool];
|
|
767
946
|
}
|
|
768
947
|
snapshot() {
|
|
769
|
-
return [...this.#nodes.values()].map(({ id, parentId, options, state }) => ({ id, ...(parentId ? { parentId } : {}), label: options.label, state, options }));
|
|
948
|
+
return [...this.#nodes.values()].map(({ id, parentId, prompt, options, state }) => ({ id, ...(parentId ? { parentId } : {}), ...(prompt === undefined ? {} : { prompt }), label: options.label, state, options }));
|
|
770
949
|
}
|
|
771
950
|
restoreRun(runId, limit, ownership, beforeLaunch) {
|
|
772
951
|
this.addRun(runId, limit, beforeLaunch);
|
|
@@ -776,7 +955,7 @@ export class FairAgentScheduler {
|
|
|
776
955
|
throw new WorkflowError("RESUME_INCOMPATIBLE", `Persisted agent belongs to another run: ${record.id}`);
|
|
777
956
|
let resolveResult = () => undefined;
|
|
778
957
|
const promise = new Promise((resolve) => { resolveResult = resolve; });
|
|
779
|
-
const node = { id: record.id, runId, ...(record.parentId ? { parentId: record.parentId } : {}), options: this.#inherit(undefined, record.options), children: new Set(), collected: false, state: record.state, controller: new AbortController(), promise, resolve: resolveResult, task: async () => undefined, restored: true };
|
|
958
|
+
const node = { id: record.id, runId, ...(record.parentId ? { parentId: record.parentId } : {}), ...(record.prompt === undefined ? {} : { prompt: record.prompt }), options: this.#inherit(undefined, record.options), children: new Set(), collected: false, state: record.state, controller: new AbortController(), promise, resolve: resolveResult, task: async () => undefined, restored: true };
|
|
780
959
|
this.#nodes.set(node.id, node);
|
|
781
960
|
run.logical += 1;
|
|
782
961
|
this.#nextId = Math.max(this.#nextId, Number(node.id.slice(node.id.lastIndexOf(":") + 1)) || 0);
|
|
@@ -887,11 +1066,6 @@ export class FairAgentScheduler {
|
|
|
887
1066
|
}
|
|
888
1067
|
}
|
|
889
1068
|
function resolvePath(path) { return path.replace(/[\\/]+$/, ""); }
|
|
890
|
-
function requiredFile(file) {
|
|
891
|
-
if (!file)
|
|
892
|
-
throw new WorkflowError("INTERNAL_ERROR", "Workflow agents require persisted native Pi sessions");
|
|
893
|
-
return file;
|
|
894
|
-
}
|
|
895
1069
|
function remaining(timeoutMs, started) {
|
|
896
1070
|
return timeoutMs === null || timeoutMs === undefined ? timeoutMs : Math.max(1, timeoutMs - (Date.now() - started));
|
|
897
1071
|
}
|
|
@@ -904,8 +1078,7 @@ function providerLimited(error) {
|
|
|
904
1078
|
async function promptWithProviderPause(session, text, timeoutMs, signal, pause) {
|
|
905
1079
|
for (;;) {
|
|
906
1080
|
try {
|
|
907
|
-
await withTimeout(session.prompt(text), timeoutMs, signal, session);
|
|
908
|
-
return;
|
|
1081
|
+
return await withTimeout(session.prompt(text), timeoutMs, signal, session);
|
|
909
1082
|
}
|
|
910
1083
|
catch (error) {
|
|
911
1084
|
if (!pause || !providerLimited(error))
|
|
@@ -923,7 +1096,7 @@ async function withTimeout(work, timeoutMs, signal, session) {
|
|
|
923
1096
|
const timeout = timeoutMs ? new Promise((_, reject) => { timer = setTimeout(() => { state.interrupted = true; reject(new WorkflowError("AGENT_TIMEOUT", "Agent attempt timed out")); }, timeoutMs); }) : new Promise(() => { });
|
|
924
1097
|
const cancelled = signal ? new Promise((_, reject) => { abort = () => { state.interrupted = true; reject(new WorkflowError("CANCELLED", "Agent cancelled")); }; signal.addEventListener("abort", abort, { once: true }); }) : new Promise(() => { });
|
|
925
1098
|
try {
|
|
926
|
-
await Promise.race([work, timeout, cancelled]);
|
|
1099
|
+
return await Promise.race([work, timeout, cancelled]);
|
|
927
1100
|
}
|
|
928
1101
|
finally {
|
|
929
1102
|
if (timer)
|
|
@@ -931,6 +1104,6 @@ async function withTimeout(work, timeoutMs, signal, session) {
|
|
|
931
1104
|
if (abort)
|
|
932
1105
|
signal?.removeEventListener("abort", abort);
|
|
933
1106
|
if (state.interrupted)
|
|
934
|
-
await session.abort
|
|
1107
|
+
await session.abort();
|
|
935
1108
|
}
|
|
936
1109
|
}
|