pi-extensible-workflows 3.3.0 → 3.4.0
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 +250 -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 +1 -1
- package/skills/pi-extensible-workflows/SKILL.md +13 -5
- package/src/agent-execution.ts +213 -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,8 +12,7 @@ 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("");
|
|
@@ -21,19 +20,14 @@ function text(messages) {
|
|
|
21
20
|
function hasToolCall(message) {
|
|
22
21
|
return typeof message === "object" && message !== null && Array.isArray(message.content) && message.content.some((part) => typeof part === "object" && part !== null && part.type === "toolCall");
|
|
23
22
|
}
|
|
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");
|
|
23
|
+
function latestAssistantHasToolCall(message) { return hasToolCall(message); }
|
|
24
|
+
function throwIfTerminalAssistantError(session, message) {
|
|
30
25
|
if (message?.stopReason !== "error")
|
|
31
26
|
return;
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
const error = message.errorMessage ?? "Native Pi assistant ended with a terminal provider error";
|
|
27
|
+
const state = session.getState();
|
|
28
|
+
const error = message.errorMessage ?? "Workflow agent session ended with a terminal provider error";
|
|
35
29
|
const failure = new WorkflowError("AGENT_FAILED", error);
|
|
36
|
-
Object.defineProperty(failure, "terminalProviderError", { value: { provider, model, error }, configurable: true });
|
|
30
|
+
Object.defineProperty(failure, "terminalProviderError", { value: { provider: state.model.provider, model: state.model.model, error }, configurable: true });
|
|
37
31
|
throw failure;
|
|
38
32
|
}
|
|
39
33
|
function terminalProviderError(error) {
|
|
@@ -44,11 +38,11 @@ function terminalProviderError(error) {
|
|
|
44
38
|
return typeof candidate.provider === "string" && typeof candidate.model === "string" && typeof candidate.error === "string" ? { provider: candidate.provider, model: candidate.model, error: candidate.error } : undefined;
|
|
45
39
|
}
|
|
46
40
|
const providerContinuationPrompt = "The provider error was transient. Continue the task from your current state.";
|
|
47
|
-
async function recoverTerminalProviderError(session,
|
|
41
|
+
async function recoverTerminalProviderError(session, label, recovery, continuePrompt, getAssistant) {
|
|
48
42
|
let continued = false;
|
|
49
43
|
for (;;) {
|
|
50
44
|
try {
|
|
51
|
-
throwIfTerminalAssistantError(session,
|
|
45
|
+
throwIfTerminalAssistantError(session, getAssistant());
|
|
52
46
|
return continued;
|
|
53
47
|
}
|
|
54
48
|
catch (error) {
|
|
@@ -91,7 +85,7 @@ function workflowSystemPromptPath(cwd, agentDir, projectTrusted) {
|
|
|
91
85
|
const globalPaths = [join(homedir(), ".pi", WORKFLOW_DIRECTORY, "SYSTEM.md"), join(agentDir, WORKFLOW_DIRECTORY, "SYSTEM.md")];
|
|
92
86
|
return globalPaths.find((path) => existsSync(path));
|
|
93
87
|
}
|
|
94
|
-
export async function
|
|
88
|
+
export async function createLocalPiSession(input) {
|
|
95
89
|
const agentDir = input.agentDir ?? getAgentDir();
|
|
96
90
|
const systemPromptSource = workflowSystemPromptPath(input.cwd, agentDir, input.resourcePolicy?.projectTrusted ?? true);
|
|
97
91
|
const systemPromptOptions = input.systemPrompt !== undefined ? { systemPromptOverride: () => input.systemPrompt } : systemPromptSource !== undefined ? { systemPrompt: systemPromptSource } : {};
|
|
@@ -150,6 +144,72 @@ export async function createNativeAgentSession(input) {
|
|
|
150
144
|
getToolDefinitions: () => session.getAllTools().map(({ name, description, parameters, promptGuidelines }) => ({ name, description, parameters, ...(promptGuidelines ? { promptGuidelines } : {}) })),
|
|
151
145
|
});
|
|
152
146
|
}
|
|
147
|
+
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; }
|
|
148
|
+
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 }; }
|
|
149
|
+
function workflowAgentState(native, prepared) {
|
|
150
|
+
const tools = native.agent?.state.tools.map(({ name }) => name) ?? prepared.tools;
|
|
151
|
+
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 };
|
|
152
|
+
return { model, ...(model.thinking ? { thinking: model.thinking } : {}), tools: [...tools], ...(native.systemPrompt === undefined ? {} : { systemPrompt: native.systemPrompt }) };
|
|
153
|
+
}
|
|
154
|
+
function localSessionEvent(event) { return event; }
|
|
155
|
+
export async function createLocalWorkflowAgentSession(prepared, context) {
|
|
156
|
+
void context;
|
|
157
|
+
const input = {
|
|
158
|
+
cwd: prepared.cwd, model: { ...prepared.model }, tools: [...prepared.tools], sessionLabel: prepared.sessionLabel,
|
|
159
|
+
...(prepared.agentDir ? { agentDir: prepared.agentDir } : {}), ...(prepared.customTools?.length ? { customTools: [...prepared.customTools] } : {}),
|
|
160
|
+
...(prepared.resultTool ? { resultTool: prepared.resultTool } : {}), ...(prepared.systemPrompt === undefined ? {} : { systemPrompt: prepared.systemPrompt }),
|
|
161
|
+
...(prepared.systemPromptAppend ? { systemPromptAppend: prepared.systemPromptAppend } : {}), ...(prepared.extensionFactories?.length ? { extensionFactories: [...prepared.extensionFactories] } : {}),
|
|
162
|
+
...(prepared.additionalSkillPaths?.length ? { additionalSkillPaths: [...prepared.additionalSkillPaths] } : {}), ...(prepared.resourcePolicy ? { resourcePolicy: structuredClone(prepared.resourcePolicy) } : {}), ...(prepared.options ? { options: { ...prepared.options } } : {}),
|
|
163
|
+
};
|
|
164
|
+
const native = await createLocalPiSession(input);
|
|
165
|
+
let disposal;
|
|
166
|
+
let aborting;
|
|
167
|
+
let prompting;
|
|
168
|
+
let disposed = false;
|
|
169
|
+
const startAbort = () => aborting ??= Promise.resolve().then(() => native.abort?.()).then(() => undefined).finally(() => { aborting = undefined; });
|
|
170
|
+
const reference = { transport: "local", sessionId: native.sessionId, ...(native.sessionFile ? { locator: { sessionFile: native.sessionFile } } : {}) };
|
|
171
|
+
const session = {
|
|
172
|
+
reference,
|
|
173
|
+
getState: () => Object.freeze(workflowAgentState(native, prepared)),
|
|
174
|
+
getSessionStats: () => workflowAgentStats(native.getSessionStats()),
|
|
175
|
+
subscribe(listener) { listener({ type: "state_changed", state: workflowAgentState(native, prepared) }); return native.subscribe?.((event) => { listener(localSessionEvent(event)); }) ?? (() => undefined); },
|
|
176
|
+
getLastAssistant: () => workflowAgentMessage([...native.messages].reverse().find((message) => message.role === "assistant")),
|
|
177
|
+
async prompt(text) {
|
|
178
|
+
if (disposed)
|
|
179
|
+
throw new WorkflowError("INTERNAL_ERROR", "Local workflow session is disposed");
|
|
180
|
+
const prompt = Promise.resolve().then(async () => { await native.prompt(text); const assistant = workflowAgentMessage([...native.messages].reverse().find((message) => message.role === "assistant")); return assistant ? { assistant } : {}; });
|
|
181
|
+
prompting = prompt;
|
|
182
|
+
try {
|
|
183
|
+
return await prompt;
|
|
184
|
+
}
|
|
185
|
+
finally {
|
|
186
|
+
if (prompting === prompt)
|
|
187
|
+
prompting = undefined;
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
async steer(text) { if (!native.steer)
|
|
191
|
+
throw new WorkflowError("INTERNAL_ERROR", "Local workflow session does not support steering"); await native.steer(text); },
|
|
192
|
+
async abort() { if (disposed)
|
|
193
|
+
return; await startAbort(); },
|
|
194
|
+
async dispose() {
|
|
195
|
+
disposal ??= (async () => {
|
|
196
|
+
disposed = true;
|
|
197
|
+
try {
|
|
198
|
+
await startAbort();
|
|
199
|
+
}
|
|
200
|
+
catch { /* Abort failure must not prevent the prompt from settling. */ }
|
|
201
|
+
try {
|
|
202
|
+
await prompting;
|
|
203
|
+
}
|
|
204
|
+
catch { /* Prompt rejection is expected after abort during teardown. */ }
|
|
205
|
+
native.dispose();
|
|
206
|
+
})();
|
|
207
|
+
await disposal;
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
return session;
|
|
211
|
+
}
|
|
212
|
+
export const localAgentTransport = Object.freeze({ id: "local", createSession: createLocalWorkflowAgentSession });
|
|
153
213
|
function changedOption(options, baseline, key) { return JSON.stringify(options[key]) !== JSON.stringify(baseline[key]); }
|
|
154
214
|
function validThinking(value) { return typeof value === "string" && ["off", "minimal", "low", "medium", "high", "xhigh", "max"].includes(value); }
|
|
155
215
|
function isChildAgentToolParams(value) {
|
|
@@ -179,51 +239,94 @@ function fallbackSetupContext(root, options, signal) {
|
|
|
179
239
|
function resourcePolicySummary(policy) {
|
|
180
240
|
return { skills: [...policy.effective.skills], extensions: [...policy.effective.extensions], excludedSkills: [...(policy.excludedSkills ?? [])], excludedExtensions: [...(policy.excludedExtensions ?? [])], unmatchedSkills: [...policy.unmatchedSkills], unmatchedExtensions: [...policy.unmatchedExtensions] };
|
|
181
241
|
}
|
|
182
|
-
|
|
242
|
+
function resourcePolicyWidened(ceiling, candidate) {
|
|
243
|
+
if (!ceiling)
|
|
244
|
+
return false;
|
|
245
|
+
if (!candidate)
|
|
246
|
+
return true;
|
|
247
|
+
if (!ceiling.projectTrusted && candidate.projectTrusted)
|
|
248
|
+
return true;
|
|
249
|
+
return ceiling.effective.skills.some((pattern) => !candidate.effective.skills.includes(pattern)) || ceiling.effective.extensions.some((pattern) => !candidate.effective.extensions.includes(pattern));
|
|
250
|
+
}
|
|
251
|
+
function preparedAgentSession(input) {
|
|
252
|
+
const prepared = {
|
|
253
|
+
cwd: input.cwd, model: Object.freeze({ ...input.model }), tools: Object.freeze([...input.tools]), sessionLabel: input.sessionLabel,
|
|
254
|
+
...(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)) } : {}),
|
|
255
|
+
...(input.systemPrompt === undefined ? {} : { systemPrompt: input.systemPrompt }), ...(input.systemPromptAppend ? { systemPromptAppend: input.systemPromptAppend } : {}),
|
|
256
|
+
...(input.extensionFactories?.length ? { extensionFactories: Object.freeze([...input.extensionFactories]) } : {}), ...(input.additionalSkillPaths?.length ? { additionalSkillPaths: Object.freeze([...input.additionalSkillPaths]) } : {}),
|
|
257
|
+
...(input.resourcePolicy ? { resourcePolicy: Object.freeze(structuredClone(input.resourcePolicy)) } : {}),
|
|
258
|
+
};
|
|
259
|
+
return deepFreeze(prepared);
|
|
260
|
+
}
|
|
261
|
+
function agentSetupSummary(setup, hookNames) {
|
|
262
|
+
const model = setup.sessionInput.model;
|
|
263
|
+
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) } : {}) };
|
|
264
|
+
}
|
|
265
|
+
async function prepareAgentSetup(root, transport, task, options, resolved, cwd, attempt, signal, customTools, resultTool) {
|
|
183
266
|
const setupSignal = signal ?? root.runContext?.signal ?? new AbortController().signal;
|
|
184
267
|
const baselineOptions = structuredClone(options.agentOptions ?? {});
|
|
185
268
|
const baseResourcePolicy = await root.agentResourcePolicy?.();
|
|
186
269
|
const roleExclusions = options.role ? root.agentDefinitions?.[options.role]?.disabledAgentResources : undefined;
|
|
187
270
|
const resourcePolicy = baseResourcePolicy && roleExclusions ? { ...baseResourcePolicy, effective: mergeAgentResourceExclusions(baseResourcePolicy.effective, roleExclusions) } : baseResourcePolicy;
|
|
271
|
+
const resourcePolicyCeiling = resourcePolicy ? structuredClone(resourcePolicy) : undefined;
|
|
188
272
|
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,
|
|
273
|
+
const setup = { prompt: task, options: sessionInput.options ?? {}, sessionInput, prepared: preparedAgentSession(sessionInput), transport };
|
|
190
274
|
const base = fallbackSetupContext(root, options, setupSignal);
|
|
191
275
|
const context = Object.freeze({ run: base.run, identity: base.identity, attempt, signal: setupSignal });
|
|
192
276
|
const hookNames = [];
|
|
193
277
|
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
278
|
if (setupSignal.aborted)
|
|
195
|
-
|
|
279
|
+
return { setup, summary: agentSetupSummary(setup, hookNames), failure: { error: new WorkflowError("CANCELLED", "Agent cancelled") } };
|
|
196
280
|
try {
|
|
197
281
|
await hook.setup(setup, context);
|
|
198
282
|
}
|
|
199
283
|
catch (error) {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
throw error;
|
|
284
|
+
setup.prepared = preparedAgentSession(setup.sessionInput);
|
|
285
|
+
return { setup, summary: agentSetupSummary(setup, hookNames), failure: { error: setupSignal.reason !== undefined ? new WorkflowError("CANCELLED", "Agent cancelled") : error } };
|
|
203
286
|
}
|
|
287
|
+
setup.prepared = preparedAgentSession(setup.sessionInput);
|
|
204
288
|
hookNames.push(hook.name);
|
|
205
289
|
if (setupSignal.reason !== undefined)
|
|
206
|
-
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
setup.
|
|
213
|
-
|
|
214
|
-
setup.
|
|
215
|
-
|
|
216
|
-
setup.
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
290
|
+
return { setup, summary: agentSetupSummary(setup, hookNames), failure: { error: new WorkflowError("CANCELLED", "Agent cancelled") } };
|
|
291
|
+
}
|
|
292
|
+
try {
|
|
293
|
+
if (resourcePolicyWidened(resourcePolicyCeiling, setup.sessionInput.resourcePolicy))
|
|
294
|
+
throw new WorkflowError("INVALID_METADATA", "Agent setup widened the prepared resource policy");
|
|
295
|
+
setup.sessionInput.options = setup.options;
|
|
296
|
+
if (changedOption(setup.options, baselineOptions, "model") && typeof setup.options.model === "string")
|
|
297
|
+
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);
|
|
298
|
+
if (changedOption(setup.options, baselineOptions, "thinking") && validThinking(setup.options.thinking))
|
|
299
|
+
setup.sessionInput.model = { ...setup.sessionInput.model, thinking: setup.options.thinking };
|
|
300
|
+
if (changedOption(setup.options, baselineOptions, "tools") && Array.isArray(setup.options.tools) && setup.options.tools.every((tool) => typeof tool === "string"))
|
|
301
|
+
setup.sessionInput.tools = [...setup.options.tools];
|
|
302
|
+
if (changedOption(setup.options, baselineOptions, "cwd") && typeof setup.options.cwd === "string")
|
|
303
|
+
setup.sessionInput.cwd = setup.options.cwd;
|
|
304
|
+
const customToolNames = new Set([...(setup.sessionInput.customTools ?? []).map(({ name }) => name), ...(setup.sessionInput.resultTool ? [setup.sessionInput.resultTool.name] : [])]);
|
|
305
|
+
const widened = setup.sessionInput.tools.find((tool) => !resolved.tools.includes(tool) && !customToolNames.has(tool));
|
|
306
|
+
const outsideTool = widened ?? setup.sessionInput.tools.find((tool) => !root.tools.has(tool) && !customToolNames.has(tool));
|
|
307
|
+
if (outsideTool)
|
|
308
|
+
throw new WorkflowError("UNKNOWN_TOOL", `Tool is outside the prepared agent policy: ${outsideTool}`);
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
setup.prepared = preparedAgentSession(setup.sessionInput);
|
|
312
|
+
return { setup, summary: agentSetupSummary(setup, hookNames), failure: { error } };
|
|
313
|
+
}
|
|
314
|
+
setup.prepared = preparedAgentSession(setup.sessionInput);
|
|
315
|
+
return { setup, summary: agentSetupSummary(setup, hookNames) };
|
|
316
|
+
}
|
|
317
|
+
function attemptRecord(transport, attempt, session, setup, stats, result, error) {
|
|
318
|
+
return { attempt, transport, session: session.reference, ...(result === undefined ? {} : { result }), ...(error ? { error } : {}), accounting: stats, setup };
|
|
319
|
+
}
|
|
320
|
+
function errorWithAttempts(error, attempts) {
|
|
321
|
+
const typed = error instanceof Error ? error : new Error(typeof error === "string" ? error : String(error));
|
|
322
|
+
return Object.assign(typed, { attempts });
|
|
220
323
|
}
|
|
221
324
|
export class WorkflowAgentExecutor {
|
|
222
325
|
root;
|
|
223
|
-
|
|
224
|
-
constructor(root,
|
|
326
|
+
transport;
|
|
327
|
+
constructor(root, transport = localAgentTransport) {
|
|
225
328
|
this.root = root;
|
|
226
|
-
this.
|
|
329
|
+
this.transport = transport;
|
|
227
330
|
}
|
|
228
331
|
setRunContext(runContext) { this.root.runContext = runContext; }
|
|
229
332
|
resolve(options, inheritedTools) {
|
|
@@ -291,16 +394,16 @@ export class WorkflowAgentExecutor {
|
|
|
291
394
|
const attempts = [];
|
|
292
395
|
let maxAttempts = (options.retries ?? 0) + 1;
|
|
293
396
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
397
|
+
const attemptSignal = executionSignal ?? new AbortController().signal;
|
|
294
398
|
if (recoveryModel)
|
|
295
399
|
resolved = this.resolve({ ...options, modelOverride: recoveryModel });
|
|
296
400
|
options.budget?.beforeAttempt();
|
|
297
401
|
let schemaResult;
|
|
298
402
|
let session;
|
|
299
403
|
let setup;
|
|
300
|
-
let setupSummary;
|
|
404
|
+
let setupSummary = { hookNames: [], model: { ...resolved.model }, tools: [...resolved.tools], cwd };
|
|
301
405
|
let setupFailed = false;
|
|
302
406
|
let budgetError;
|
|
303
|
-
let turnStarted = false;
|
|
304
407
|
const hasSchemaResult = () => schemaResult !== undefined;
|
|
305
408
|
const resultTool = options.schema ? {
|
|
306
409
|
name: "workflow_result", label: "Workflow Result", description: "Submit the terminal structured workflow result", parameters: Type.Unsafe(options.schema),
|
|
@@ -310,7 +413,9 @@ export class WorkflowAgentExecutor {
|
|
|
310
413
|
if (schemaResult !== undefined)
|
|
311
414
|
return { content: [{ type: "text", text: "Result has already been accepted." }], details: {}, isError: true };
|
|
312
415
|
schemaResult = structuredClone(value);
|
|
313
|
-
|
|
416
|
+
const currentSession = session;
|
|
417
|
+
if (currentSession)
|
|
418
|
+
void currentSession.abort();
|
|
314
419
|
return { content: [{ type: "text", text: "Result accepted." }], details: {} };
|
|
315
420
|
},
|
|
316
421
|
} : undefined;
|
|
@@ -323,6 +428,8 @@ export class WorkflowAgentExecutor {
|
|
|
323
428
|
let systemPromptTurn = 0;
|
|
324
429
|
let systemPromptWrite = Promise.resolve();
|
|
325
430
|
let systemPromptWriteError;
|
|
431
|
+
let turnStarted = false;
|
|
432
|
+
let completedAttempt;
|
|
326
433
|
const flushSystemPrompts = async () => {
|
|
327
434
|
await systemPromptWrite;
|
|
328
435
|
if (systemPromptWriteError)
|
|
@@ -331,7 +438,7 @@ export class WorkflowAgentExecutor {
|
|
|
331
438
|
const report = (persist) => {
|
|
332
439
|
if (!session || !options.onProgress)
|
|
333
440
|
return;
|
|
334
|
-
const update = { accounting: accounting(session.getSessionStats()), toolCalls: [...toolCalls.values()], ...(activity ? { activity } : {}), ...(lastEventAt === undefined ? {} : { lastEventAt }), persist };
|
|
441
|
+
const update = { accounting: accounting(session.getSessionStats()), toolCalls: [...toolCalls.values()], state: session.getState(), ...(activity ? { activity } : {}), ...(lastEventAt === undefined ? {} : { lastEventAt }), persist };
|
|
335
442
|
if (lastEventAt !== undefined)
|
|
336
443
|
lastReportedEventAt = lastEventAt;
|
|
337
444
|
progress = progress.then(() => options.onProgress?.(update)).then(() => undefined);
|
|
@@ -344,34 +451,48 @@ export class WorkflowAgentExecutor {
|
|
|
344
451
|
const activityChanged = (previous) => previous?.kind !== activity?.kind || previous?.text !== activity?.text;
|
|
345
452
|
try {
|
|
346
453
|
setupFailed = true;
|
|
347
|
-
const prepared = await prepareAgentSetup(this.root, this.
|
|
454
|
+
const prepared = await prepareAgentSetup(this.root, this.transport, task, options, resolved, cwd, attempt, attemptSignal, customTools, resultTool);
|
|
348
455
|
setup = prepared.setup;
|
|
349
456
|
setupSummary = prepared.summary;
|
|
457
|
+
if (prepared.failure)
|
|
458
|
+
throw prepared.failure.error;
|
|
350
459
|
setupFailed = false;
|
|
351
|
-
if (
|
|
460
|
+
if (attemptSignal.aborted)
|
|
352
461
|
throw new WorkflowError("CANCELLED", "Agent cancelled");
|
|
353
462
|
const started = Date.now();
|
|
354
|
-
|
|
463
|
+
const transportSignal = attemptSignal;
|
|
464
|
+
await options.onAttempt?.({ attempt, transport: setup.transport.id, accounting: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 }, setup: setupSummary });
|
|
465
|
+
const transportBase = fallbackSetupContext(this.root, options, transportSignal);
|
|
466
|
+
const createdSession = await setup.transport.createSession(setup.prepared, Object.freeze({ run: transportBase.run, identity: transportBase.identity, attempt, signal: transportSignal }));
|
|
467
|
+
if (createdSession.reference.transport !== setup.transport.id) {
|
|
468
|
+
await createdSession.dispose();
|
|
469
|
+
throw new WorkflowError("INTERNAL_ERROR", `Agent transport ${setup.transport.id} created a session for ${createdSession.reference.transport}`);
|
|
470
|
+
}
|
|
471
|
+
session = createdSession;
|
|
472
|
+
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 });
|
|
473
|
+
const preparedTools = new Set([...setup.prepared.tools, ...(setup.prepared.customTools ?? []).map(({ name }) => name), ...(setup.prepared.resultTool ? [setup.prepared.resultTool.name] : [])]);
|
|
474
|
+
if (session.getState().tools.some((tool) => !preparedTools.has(tool)))
|
|
475
|
+
throw new WorkflowError("INTERNAL_ERROR", `Agent transport ${setup.transport.id} widened the prepared tool policy`);
|
|
355
476
|
if (setup.sessionInput.resourcePolicy)
|
|
356
477
|
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
478
|
const activeSession = session;
|
|
360
|
-
|
|
361
|
-
const recoverTerminal = () => recoverTerminalProviderError(activeSession,
|
|
362
|
-
await promptWithProviderPause(activeSession, providerContinuationPrompt, remaining(options.timeoutMs, started),
|
|
479
|
+
let lastAssistant;
|
|
480
|
+
const recoverTerminal = () => recoverTerminalProviderError(activeSession, options.label, options.providerErrorRecovery, async () => { try {
|
|
481
|
+
lastAssistant = (await promptWithProviderPause(activeSession, providerContinuationPrompt, remaining(options.timeoutMs, started), attemptSignal, this.root.providerPause)).assistant;
|
|
363
482
|
}
|
|
364
483
|
catch (error) {
|
|
484
|
+
lastAssistant = activeSession.getLastAssistant?.() ?? lastAssistant;
|
|
365
485
|
if (!hasSchemaResult())
|
|
366
486
|
throw error;
|
|
367
|
-
} });
|
|
487
|
+
} }, () => lastAssistant);
|
|
368
488
|
const promptAndRecover = async (prompt) => {
|
|
369
489
|
let promptFailed = false;
|
|
370
490
|
let promptError;
|
|
371
491
|
try {
|
|
372
|
-
await promptWithProviderPause(activeSession, prompt, remaining(options.timeoutMs, started),
|
|
492
|
+
lastAssistant = (await promptWithProviderPause(activeSession, prompt, remaining(options.timeoutMs, started), attemptSignal, this.root.providerPause)).assistant;
|
|
373
493
|
}
|
|
374
494
|
catch (error) {
|
|
495
|
+
lastAssistant = activeSession.getLastAssistant?.() ?? lastAssistant;
|
|
375
496
|
promptFailed = true;
|
|
376
497
|
promptError = error;
|
|
377
498
|
}
|
|
@@ -379,19 +500,35 @@ export class WorkflowAgentExecutor {
|
|
|
379
500
|
if (promptFailed && !hasSchemaResult() && !recovered)
|
|
380
501
|
throw promptError;
|
|
381
502
|
};
|
|
382
|
-
unsubscribe = activeSession.subscribe
|
|
503
|
+
unsubscribe = activeSession.subscribe((event) => {
|
|
383
504
|
lastEventAt = Date.now();
|
|
384
505
|
let persist = false;
|
|
385
506
|
let shouldReport = false;
|
|
386
507
|
let removeToolCallId;
|
|
387
|
-
if (event.type === "agent_start" && session?.systemPrompt !== undefined) {
|
|
508
|
+
if (event.type === "agent_start" && session?.getState().systemPrompt !== undefined) {
|
|
388
509
|
if (this.root.runStore) {
|
|
389
510
|
systemPromptTurn += 1;
|
|
390
|
-
const entry = { sessionId: session.sessionId, attempt, turn: systemPromptTurn, prompt: session.systemPrompt };
|
|
511
|
+
const entry = { sessionId: session.reference.sessionId, attempt, turn: systemPromptTurn, prompt: session.getState().systemPrompt ?? "" };
|
|
391
512
|
systemPromptWrite = systemPromptWrite.then(() => this.root.runStore?.recordSystemPrompt(entry)).then(() => undefined).catch((error) => { systemPromptWriteError ??= error; });
|
|
392
513
|
}
|
|
393
514
|
}
|
|
394
|
-
if (event.type === "
|
|
515
|
+
if (event.type === "state_changed") {
|
|
516
|
+
shouldReport = true;
|
|
517
|
+
persist = true;
|
|
518
|
+
}
|
|
519
|
+
if (event.type === "turnStarted" || event.type === "turn_started") {
|
|
520
|
+
if (!turnStarted) {
|
|
521
|
+
try {
|
|
522
|
+
options.budget?.beforeTurn();
|
|
523
|
+
turnStarted = true;
|
|
524
|
+
}
|
|
525
|
+
catch (error) {
|
|
526
|
+
budgetError ??= error instanceof WorkflowError ? error : new WorkflowError("BUDGET_EXHAUSTED", error instanceof Error ? error.message : String(error));
|
|
527
|
+
void activeSession.abort();
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
if (event.type === "message_start" && event.message?.role === "assistant") {
|
|
395
532
|
if (!turnStarted) {
|
|
396
533
|
try {
|
|
397
534
|
options.budget?.beforeTurn();
|
|
@@ -399,7 +536,7 @@ export class WorkflowAgentExecutor {
|
|
|
399
536
|
}
|
|
400
537
|
catch (error) {
|
|
401
538
|
budgetError ??= error instanceof WorkflowError ? error : new WorkflowError("BUDGET_EXHAUSTED", error instanceof Error ? error.message : String(error));
|
|
402
|
-
void
|
|
539
|
+
void activeSession.abort();
|
|
403
540
|
}
|
|
404
541
|
}
|
|
405
542
|
activity = { kind: "text", text: "responding" };
|
|
@@ -407,9 +544,10 @@ export class WorkflowAgentExecutor {
|
|
|
407
544
|
}
|
|
408
545
|
if (event.type === "message_update") {
|
|
409
546
|
const previousActivity = activity;
|
|
410
|
-
|
|
547
|
+
const updateType = event.assistantMessageEvent?.type;
|
|
548
|
+
if (updateType && ["thinking_start", "thinking_delta", "thinking_end"].includes(updateType))
|
|
411
549
|
activity = { kind: "reasoning", text: "reasoning" };
|
|
412
|
-
else if (["text_start", "text_delta", "text_end", "toolcall_start", "toolcall_delta", "toolcall_end"].includes(
|
|
550
|
+
else if (updateType && ["text_start", "text_delta", "text_end", "toolcall_start", "toolcall_delta", "toolcall_end"].includes(updateType))
|
|
413
551
|
activity = { kind: "text", text: "responding" };
|
|
414
552
|
shouldReport = activityChanged(previousActivity);
|
|
415
553
|
}
|
|
@@ -417,7 +555,8 @@ export class WorkflowAgentExecutor {
|
|
|
417
555
|
const previousActivity = activity;
|
|
418
556
|
activity = undefined;
|
|
419
557
|
shouldReport = activityChanged(previousActivity);
|
|
420
|
-
if (event.message
|
|
558
|
+
if (event.message?.role === "assistant") {
|
|
559
|
+
lastAssistant = event.message;
|
|
421
560
|
const needsMoreWork = hasToolCall(event.message);
|
|
422
561
|
const final = !needsMoreWork || (options.schema !== undefined && hasSchemaResult());
|
|
423
562
|
if (!budgetError) {
|
|
@@ -426,29 +565,29 @@ export class WorkflowAgentExecutor {
|
|
|
426
565
|
if (!final) {
|
|
427
566
|
const instruction = options.budget?.instruction();
|
|
428
567
|
if (instruction)
|
|
429
|
-
void
|
|
568
|
+
void activeSession.steer(instruction);
|
|
430
569
|
}
|
|
431
570
|
}
|
|
432
571
|
catch (error) {
|
|
433
572
|
budgetError ??= error instanceof WorkflowError ? error : new WorkflowError("BUDGET_EXHAUSTED", error instanceof Error ? error.message : String(error));
|
|
434
|
-
void
|
|
573
|
+
void activeSession.abort();
|
|
435
574
|
}
|
|
436
575
|
}
|
|
437
576
|
turnStarted = false;
|
|
438
577
|
persist = true;
|
|
439
578
|
}
|
|
440
579
|
}
|
|
441
|
-
if (event.type === "tool_execution_start") {
|
|
580
|
+
if (event.type === "tool_execution_start" && event.toolCallId && event.toolName) {
|
|
442
581
|
toolCalls.set(event.toolCallId, { id: event.toolCallId, name: event.toolName, state: "running" });
|
|
443
582
|
activity = { kind: "tool", text: event.toolName };
|
|
444
583
|
shouldReport = true;
|
|
445
584
|
}
|
|
446
|
-
if (event.type === "tool_execution_update") {
|
|
585
|
+
if (event.type === "tool_execution_update" && event.toolName) {
|
|
447
586
|
const previousActivity = activity;
|
|
448
587
|
activity = { kind: "tool", text: event.toolName };
|
|
449
588
|
shouldReport = activityChanged(previousActivity);
|
|
450
589
|
}
|
|
451
|
-
if (event.type === "tool_execution_end") {
|
|
590
|
+
if (event.type === "tool_execution_end" && event.toolCallId && event.toolName) {
|
|
452
591
|
toolCalls.set(event.toolCallId, { id: event.toolCallId, name: event.toolName, state: event.isError ? "failed" : "completed" });
|
|
453
592
|
if (activity?.kind === "tool" && activity.text === event.toolName)
|
|
454
593
|
activity = undefined;
|
|
@@ -464,9 +603,7 @@ export class WorkflowAgentExecutor {
|
|
|
464
603
|
});
|
|
465
604
|
report(false);
|
|
466
605
|
if (setSteer) {
|
|
467
|
-
|
|
468
|
-
throw new WorkflowError("INTERNAL_ERROR", "Native Pi session does not support steering");
|
|
469
|
-
setSteer((message) => session?.steer?.(message));
|
|
606
|
+
setSteer((message) => activeSession.steer(message));
|
|
470
607
|
}
|
|
471
608
|
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
609
|
const instruction = options.budget?.instruction();
|
|
@@ -476,7 +613,7 @@ export class WorkflowAgentExecutor {
|
|
|
476
613
|
await promptAndRecover(promptText);
|
|
477
614
|
{
|
|
478
615
|
const completedAccounting = accounting(session.getSessionStats());
|
|
479
|
-
options.budget?.afterTurn(completedAccounting, options.schema !== undefined ? hasSchemaResult() : !latestAssistantHasToolCall(
|
|
616
|
+
options.budget?.afterTurn(completedAccounting, options.schema !== undefined ? hasSchemaResult() : !latestAssistantHasToolCall(lastAssistant));
|
|
480
617
|
turnStarted = false;
|
|
481
618
|
}
|
|
482
619
|
if (budgetError)
|
|
@@ -505,24 +642,41 @@ export class WorkflowAgentExecutor {
|
|
|
505
642
|
if (schemaResult === undefined)
|
|
506
643
|
throw new WorkflowError("RESULT_INVALID", "Agent did not submit a valid workflow_result after one repair");
|
|
507
644
|
}
|
|
508
|
-
const value = options.schema ? schemaResult : text(
|
|
645
|
+
const value = options.schema ? schemaResult : text(lastAssistant);
|
|
509
646
|
if (options.worktreeOwner)
|
|
510
647
|
await this.root.runStore?.snapshotWorktree(options.worktreeOwner);
|
|
511
648
|
report(true);
|
|
512
649
|
await progress;
|
|
513
650
|
await flushSystemPrompts();
|
|
514
|
-
unsubscribe
|
|
651
|
+
unsubscribe();
|
|
515
652
|
const attemptAccounting = accounting(session.getSessionStats());
|
|
516
|
-
|
|
517
|
-
attempts.push(
|
|
518
|
-
|
|
653
|
+
completedAttempt = attemptRecord(setup.transport.id, attempt, session, setupSummary, attemptAccounting, value);
|
|
654
|
+
attempts.push(completedAttempt);
|
|
655
|
+
try {
|
|
656
|
+
await options.onAttempt?.(completedAttempt);
|
|
657
|
+
}
|
|
658
|
+
finally {
|
|
659
|
+
await session.dispose();
|
|
660
|
+
}
|
|
519
661
|
return { value, attempts, cwd: setupSummary.cwd };
|
|
520
662
|
}
|
|
521
663
|
catch (error) {
|
|
522
|
-
|
|
664
|
+
if (completedAttempt)
|
|
665
|
+
throw errorWithAttempts(error, attempts);
|
|
666
|
+
const typed = budgetError ?? (error instanceof WorkflowError ? error : new WorkflowError(attemptSignal.aborted && setupFailed ? "CANCELLED" : "AGENT_FAILED", error instanceof Error ? error.message : String(error)));
|
|
667
|
+
if (!session) {
|
|
668
|
+
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 };
|
|
669
|
+
attempts.push(failedAttempt);
|
|
670
|
+
try {
|
|
671
|
+
await options.onAttempt?.(failedAttempt);
|
|
672
|
+
}
|
|
673
|
+
catch (persistenceError) {
|
|
674
|
+
throw errorWithAttempts(persistenceError, attempts);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
523
677
|
if (session) {
|
|
524
678
|
report(true);
|
|
525
|
-
await progress;
|
|
679
|
+
await progress.catch(() => undefined);
|
|
526
680
|
try {
|
|
527
681
|
await flushSystemPrompts();
|
|
528
682
|
}
|
|
@@ -537,9 +691,19 @@ export class WorkflowAgentExecutor {
|
|
|
537
691
|
budgetError ??= budgetFailure instanceof WorkflowError ? budgetFailure : new WorkflowError("BUDGET_EXHAUSTED", budgetFailure instanceof Error ? budgetFailure.message : String(budgetFailure));
|
|
538
692
|
}
|
|
539
693
|
}
|
|
540
|
-
const
|
|
541
|
-
attempts.push(
|
|
542
|
-
|
|
694
|
+
const failedAttempt = attemptRecord(setup?.transport.id ?? this.transport.id, attempt, session, setupSummary, attemptAccounting, undefined, { code: typed.code, message: typed.message });
|
|
695
|
+
attempts.push(failedAttempt);
|
|
696
|
+
try {
|
|
697
|
+
try {
|
|
698
|
+
await options.onAttempt?.(failedAttempt);
|
|
699
|
+
}
|
|
700
|
+
finally {
|
|
701
|
+
await session.dispose();
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
catch (persistenceError) {
|
|
705
|
+
throw errorWithAttempts(persistenceError, attempts);
|
|
706
|
+
}
|
|
543
707
|
}
|
|
544
708
|
if (options.worktreeOwner && typed.code !== "WORKTREE_FAILED")
|
|
545
709
|
await this.root.runStore?.snapshotWorktree(options.worktreeOwner).catch(() => undefined);
|
|
@@ -627,7 +791,7 @@ export class FairAgentScheduler {
|
|
|
627
791
|
const id = `${runId}:${String(++this.#nextId)}`;
|
|
628
792
|
let resolveResult = () => undefined;
|
|
629
793
|
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 };
|
|
794
|
+
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
795
|
node.task = async () => {
|
|
632
796
|
if (node.controller.signal.aborted) {
|
|
633
797
|
this.#release(node.runId);
|
|
@@ -766,7 +930,7 @@ export class FairAgentScheduler {
|
|
|
766
930
|
return [agentTool, resultTool, steerTool];
|
|
767
931
|
}
|
|
768
932
|
snapshot() {
|
|
769
|
-
return [...this.#nodes.values()].map(({ id, parentId, options, state }) => ({ id, ...(parentId ? { parentId } : {}), label: options.label, state, options }));
|
|
933
|
+
return [...this.#nodes.values()].map(({ id, parentId, prompt, options, state }) => ({ id, ...(parentId ? { parentId } : {}), ...(prompt === undefined ? {} : { prompt }), label: options.label, state, options }));
|
|
770
934
|
}
|
|
771
935
|
restoreRun(runId, limit, ownership, beforeLaunch) {
|
|
772
936
|
this.addRun(runId, limit, beforeLaunch);
|
|
@@ -776,7 +940,7 @@ export class FairAgentScheduler {
|
|
|
776
940
|
throw new WorkflowError("RESUME_INCOMPATIBLE", `Persisted agent belongs to another run: ${record.id}`);
|
|
777
941
|
let resolveResult = () => undefined;
|
|
778
942
|
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 };
|
|
943
|
+
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
944
|
this.#nodes.set(node.id, node);
|
|
781
945
|
run.logical += 1;
|
|
782
946
|
this.#nextId = Math.max(this.#nextId, Number(node.id.slice(node.id.lastIndexOf(":") + 1)) || 0);
|
|
@@ -887,11 +1051,6 @@ export class FairAgentScheduler {
|
|
|
887
1051
|
}
|
|
888
1052
|
}
|
|
889
1053
|
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
1054
|
function remaining(timeoutMs, started) {
|
|
896
1055
|
return timeoutMs === null || timeoutMs === undefined ? timeoutMs : Math.max(1, timeoutMs - (Date.now() - started));
|
|
897
1056
|
}
|
|
@@ -904,8 +1063,7 @@ function providerLimited(error) {
|
|
|
904
1063
|
async function promptWithProviderPause(session, text, timeoutMs, signal, pause) {
|
|
905
1064
|
for (;;) {
|
|
906
1065
|
try {
|
|
907
|
-
await withTimeout(session.prompt(text), timeoutMs, signal, session);
|
|
908
|
-
return;
|
|
1066
|
+
return await withTimeout(session.prompt(text), timeoutMs, signal, session);
|
|
909
1067
|
}
|
|
910
1068
|
catch (error) {
|
|
911
1069
|
if (!pause || !providerLimited(error))
|
|
@@ -923,7 +1081,7 @@ async function withTimeout(work, timeoutMs, signal, session) {
|
|
|
923
1081
|
const timeout = timeoutMs ? new Promise((_, reject) => { timer = setTimeout(() => { state.interrupted = true; reject(new WorkflowError("AGENT_TIMEOUT", "Agent attempt timed out")); }, timeoutMs); }) : new Promise(() => { });
|
|
924
1082
|
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
1083
|
try {
|
|
926
|
-
await Promise.race([work, timeout, cancelled]);
|
|
1084
|
+
return await Promise.race([work, timeout, cancelled]);
|
|
927
1085
|
}
|
|
928
1086
|
finally {
|
|
929
1087
|
if (timer)
|
|
@@ -931,6 +1089,6 @@ async function withTimeout(work, timeoutMs, signal, session) {
|
|
|
931
1089
|
if (abort)
|
|
932
1090
|
signal?.removeEventListener("abort", abort);
|
|
933
1091
|
if (state.interrupted)
|
|
934
|
-
await session.abort
|
|
1092
|
+
await session.abort();
|
|
935
1093
|
}
|
|
936
1094
|
}
|