pi-extensible-workflows 1.0.1 → 3.0.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 +9 -2
- package/dist/src/agent-execution.d.ts +13 -14
- package/dist/src/agent-execution.js +110 -197
- package/dist/src/budget.d.ts +38 -0
- package/dist/src/budget.js +160 -0
- package/dist/src/cli.d.ts +9 -0
- package/dist/src/cli.js +536 -6
- package/dist/src/doctor.d.ts +4 -4
- package/dist/src/doctor.js +9 -29
- package/dist/src/execution.d.ts +17 -0
- package/dist/src/execution.js +630 -0
- package/dist/src/herdr.d.ts +12 -0
- package/dist/src/herdr.js +74 -0
- package/dist/src/host.d.ts +62 -0
- package/dist/src/host.js +2696 -0
- package/dist/src/index.d.ts +11 -557
- package/dist/src/index.js +8 -3974
- package/dist/src/persistence.d.ts +32 -19
- package/dist/src/persistence.js +310 -70
- package/dist/src/registry.d.ts +31 -0
- package/dist/src/registry.js +169 -0
- package/dist/src/session-inspector.d.ts +1 -0
- package/dist/src/session-inspector.js +4 -1
- package/dist/src/types.d.ts +565 -0
- package/dist/src/types.js +27 -0
- package/dist/src/utils.d.ts +26 -0
- package/dist/src/utils.js +128 -0
- package/dist/src/validation.d.ts +24 -0
- package/dist/src/validation.js +740 -0
- package/dist/src/workflow-evals.js +2 -1
- package/package.json +4 -3
- package/skills/pi-extensible-workflows/SKILL.md +52 -67
- package/src/agent-execution.ts +84 -147
- package/src/budget.ts +75 -0
- package/src/cli.ts +427 -6
- package/src/doctor.ts +13 -32
- package/src/execution.ts +540 -0
- package/src/herdr.ts +73 -0
- package/src/host.ts +2265 -0
- package/src/index.ts +11 -3453
- package/src/persistence.ts +255 -47
- package/src/registry.ts +157 -0
- package/src/session-inspector.ts +5 -1
- package/src/types.ts +113 -0
- package/src/utils.ts +108 -0
- package/src/validation.ts +616 -0
- package/src/workflow-evals.ts +2 -1
package/README.md
CHANGED
|
@@ -20,7 +20,9 @@ For source installs and local development, see the [installation guide](https://
|
|
|
20
20
|
|
|
21
21
|
## Capabilities
|
|
22
22
|
|
|
23
|
-
The main Pi agent acts as the orchestrator: it writes workflow scripts on the fly for each task. Pi extensions can add reusable functions and variables to those scripts
|
|
23
|
+
The main Pi agent acts as the orchestrator: it writes workflow scripts on the fly for each task. Pi extensions can add reusable functions and variables to those scripts; every registered function is also directly runnable as a top-level workflow.
|
|
24
|
+
|
|
25
|
+
Inline workflow launches require a non-empty `name`; registered function launches reject `name` and use their registered function name as the run name. Workflow worktree scopes always use the explicit `withWorktree(name, callback)` form.
|
|
24
26
|
|
|
25
27
|
A workflow can fan out across specialized agents, combine their results, and resume without rerunning completed work.
|
|
26
28
|
|
|
@@ -61,9 +63,14 @@ Global workflow settings live at `~/.pi/agent/pi-extensible-workflows/settings.j
|
|
|
61
63
|
```sh
|
|
62
64
|
npx pi-extensible-workflows doctor
|
|
63
65
|
npx pi-extensible-workflows inspect [session-id]
|
|
66
|
+
npx pi-extensible-workflows transcript <session-file>
|
|
67
|
+
npx pi-extensible-workflows run <workflow-name> [workflow arguments]
|
|
68
|
+
npx pi-extensible-workflows export <workflow-name> [--name <command>] [--output <path>] [--force]
|
|
64
69
|
```
|
|
65
70
|
|
|
66
|
-
`doctor` validates the installation and active Pi resources. `inspect` opens a read-only terminal view of persisted workflow runs.
|
|
71
|
+
`doctor` validates the installation and active Pi resources. `inspect` opens a read-only terminal view of persisted workflow runs. `transcript` renders a session transcript to stdout. `run` derives flat CLI arguments and help from a registered function's input schema. Use `--input '<json>'` for nested or otherwise complex inputs. It executes in the current working directory, writes the final JSON result to stdout, and writes progress and errors to stderr. `export` creates an executable POSIX launcher in `~/.local/bin` by default.
|
|
72
|
+
`run` and `export` accept the trust overrides `--approve` and `--no-approve`; the generated launcher forwards its arguments to `run`. `--` ends launcher option parsing, and later tokens are passed to workflow input instead of being interpreted as launcher options.
|
|
73
|
+
Launch snapshots use identity version 5. Cold resume rejects older snapshots, including v4 snapshots created with the previous worktree or registered-function naming contracts, with `RESUME_INCOMPATIBLE`; relaunch the workflow instead.
|
|
67
74
|
|
|
68
75
|
## Development
|
|
69
76
|
|
|
@@ -15,7 +15,7 @@ type AgentMessage = {
|
|
|
15
15
|
};
|
|
16
16
|
};
|
|
17
17
|
};
|
|
18
|
-
import type { AgentIdentity, AgentResourceExclusions, AgentResourcePolicy, AgentSetupSummary, JsonSchema, JsonValue, ModelSpec, WorkflowRunContext } from "./
|
|
18
|
+
import type { AgentIdentity, AgentResourceExclusions, AgentResourcePolicy, AgentSetupSummary, JsonSchema, JsonValue, ModelSpec, WorkflowRunContext } from "./types.js";
|
|
19
19
|
import type { RunStore } from "./persistence.js";
|
|
20
20
|
export interface AgentBudgetHooks {
|
|
21
21
|
beforeAttempt(): void;
|
|
@@ -31,6 +31,15 @@ export interface AgentDefinition {
|
|
|
31
31
|
tools?: readonly string[];
|
|
32
32
|
disabledAgentResources?: AgentResourceExclusions;
|
|
33
33
|
}
|
|
34
|
+
export interface AgentProviderFailure {
|
|
35
|
+
label: string;
|
|
36
|
+
provider: string;
|
|
37
|
+
model: string;
|
|
38
|
+
error: string;
|
|
39
|
+
}
|
|
40
|
+
export type AgentProviderRecovery = "retry" | "abort" | {
|
|
41
|
+
model: string;
|
|
42
|
+
};
|
|
34
43
|
export interface AgentExecutionOptions {
|
|
35
44
|
label: string;
|
|
36
45
|
workflowName: string;
|
|
@@ -40,6 +49,8 @@ export interface AgentExecutionOptions {
|
|
|
40
49
|
thinking?: ThinkingLevel;
|
|
41
50
|
onProgress?: (progress: AgentProgress) => void | Promise<void>;
|
|
42
51
|
onAttempt?: (attempt: Pick<AgentAttempt, "attempt" | "sessionId" | "sessionFile" | "setup">) => void | Promise<void>;
|
|
52
|
+
providerErrorRecovery?: (failure: AgentProviderFailure) => Promise<AgentProviderRecovery>;
|
|
53
|
+
modelOverride?: ModelSpec;
|
|
43
54
|
tools?: readonly string[];
|
|
44
55
|
effectiveTools?: readonly string[];
|
|
45
56
|
role?: string;
|
|
@@ -52,10 +63,6 @@ export interface AgentExecutionOptions {
|
|
|
52
63
|
budget?: AgentBudgetHooks;
|
|
53
64
|
agentOptions?: Readonly<Record<string, JsonValue>>;
|
|
54
65
|
agentIdentity?: AgentIdentity;
|
|
55
|
-
conversation?: {
|
|
56
|
-
id: string;
|
|
57
|
-
turn: number;
|
|
58
|
-
};
|
|
59
66
|
}
|
|
60
67
|
export interface AgentExecutionRoot {
|
|
61
68
|
cwd: string;
|
|
@@ -174,11 +181,6 @@ export interface SessionInput {
|
|
|
174
181
|
extensionFactories?: InlineExtension[];
|
|
175
182
|
resourcePolicy?: AgentResourcePolicy;
|
|
176
183
|
options?: Record<string, JsonValue>;
|
|
177
|
-
continuation?: {
|
|
178
|
-
sessionId: string;
|
|
179
|
-
sessionFile: string;
|
|
180
|
-
leafId: string;
|
|
181
|
-
};
|
|
182
184
|
}
|
|
183
185
|
export type SessionFactory = (input: SessionInput) => Promise<NativeSession>;
|
|
184
186
|
export declare function createNativeAgentSession(input: SessionInput): Promise<NativeSession>;
|
|
@@ -210,10 +212,6 @@ export interface ScheduledAgentOptions {
|
|
|
210
212
|
timeoutMs?: number | null;
|
|
211
213
|
agentOptions?: Readonly<Record<string, JsonValue>>;
|
|
212
214
|
agentIdentity?: AgentIdentity;
|
|
213
|
-
conversation?: {
|
|
214
|
-
id: string;
|
|
215
|
-
turn: number;
|
|
216
|
-
};
|
|
217
215
|
}
|
|
218
216
|
export type ScheduledAgentResult = {
|
|
219
217
|
id: string;
|
|
@@ -278,6 +276,7 @@ export declare class FairAgentScheduler {
|
|
|
278
276
|
retry(id: string): void;
|
|
279
277
|
attemptStarted(id: string): void;
|
|
280
278
|
cancelRun(runId: string): Promise<void>;
|
|
279
|
+
removeRun(runId: string): void;
|
|
281
280
|
toolsFor(parentId: string, resolveTools?: (role: string | undefined, tools: readonly string[] | undefined, model: string | undefined, inheritedTools: readonly string[], thinking: ThinkingLevel | undefined) => readonly string[]): ToolDefinition[];
|
|
282
281
|
snapshot(): readonly OwnershipRecord[];
|
|
283
282
|
restoreRun(runId: string, limit: number, ownership: readonly OwnershipRecord[], beforeLaunch?: () => void): void;
|
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
1
|
import { realpathSync } from "node:fs";
|
|
3
2
|
import { join, resolve } from "node:path";
|
|
4
3
|
import { Type } from "@earendil-works/pi-ai";
|
|
5
4
|
import { Value } from "typebox/value";
|
|
6
5
|
import { createAgentSession, DefaultPackageManager, DefaultResourceLoader, getAgentDir, ModelRuntime, SessionManager, SettingsManager } from "@earendil-works/pi-coding-agent";
|
|
7
|
-
import { mergeAgentResourceExclusions,
|
|
6
|
+
import { jsonObject, mergeAgentResourceExclusions, modelAliasName, modelCapability, resolveModelReference } from "./utils.js";
|
|
7
|
+
import { WorkflowError } from "./types.js";
|
|
8
8
|
function parseModel(value, fallback, thinking, aliases = {}, knownModels, settingsPath) {
|
|
9
9
|
if (!value)
|
|
10
10
|
return { ...fallback, ...(thinking ? { thinking } : {}) };
|
|
11
11
|
const parsed = resolveModelReference(value, aliases, knownModels, settingsPath);
|
|
12
12
|
return { ...parsed, ...(thinking ? { thinking } : !parsed.thinking && fallback.thinking ? { thinking: fallback.thinking } : {}) };
|
|
13
13
|
}
|
|
14
|
-
function modelCapability(model) { return `${model.provider}/${model.model}`; }
|
|
15
14
|
function text(messages) {
|
|
16
15
|
const message = [...messages].reverse().find((item) => item.role === "assistant");
|
|
17
16
|
if (!message || !Array.isArray(message.content))
|
|
@@ -25,10 +24,23 @@ function latestAssistantHasToolCall(messages) {
|
|
|
25
24
|
const message = [...messages].reverse().find((item) => item.role === "assistant");
|
|
26
25
|
return hasToolCall(message);
|
|
27
26
|
}
|
|
28
|
-
function throwIfTerminalAssistantError(session) {
|
|
27
|
+
function throwIfTerminalAssistantError(session, fallbackModel) {
|
|
29
28
|
const message = [...session.messages].reverse().find((item) => item.role === "assistant");
|
|
30
|
-
if (message?.stopReason
|
|
31
|
-
|
|
29
|
+
if (message?.stopReason !== "error")
|
|
30
|
+
return;
|
|
31
|
+
const provider = session.model?.provider ?? fallbackModel.provider;
|
|
32
|
+
const model = session.model?.model ?? session.model?.id ?? fallbackModel.model;
|
|
33
|
+
const error = message.errorMessage ?? "Native Pi assistant ended with a terminal provider error";
|
|
34
|
+
const failure = new WorkflowError("AGENT_FAILED", error);
|
|
35
|
+
Object.defineProperty(failure, "terminalProviderError", { value: { provider, model, error }, configurable: true });
|
|
36
|
+
throw failure;
|
|
37
|
+
}
|
|
38
|
+
function terminalProviderError(error) {
|
|
39
|
+
const value = error.terminalProviderError;
|
|
40
|
+
if (!value || typeof value !== "object")
|
|
41
|
+
return undefined;
|
|
42
|
+
const candidate = value;
|
|
43
|
+
return typeof candidate.provider === "string" && typeof candidate.model === "string" && typeof candidate.error === "string" ? { provider: candidate.provider, model: candidate.model, error: candidate.error } : undefined;
|
|
32
44
|
}
|
|
33
45
|
function accounting(stats) {
|
|
34
46
|
return { input: stats.tokens.input, output: stats.tokens.output, cacheRead: stats.tokens.cacheRead, cacheWrite: stats.tokens.cacheWrite, cost: stats.cost };
|
|
@@ -41,30 +53,8 @@ catch {
|
|
|
41
53
|
} }
|
|
42
54
|
export async function createNativeAgentSession(input) {
|
|
43
55
|
const agentDir = input.agentDir ?? getAgentDir();
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
try {
|
|
47
|
-
manager = SessionManager.open(input.continuation.sessionFile, input.agentDir ? join(agentDir, "sessions") : undefined, input.cwd);
|
|
48
|
-
const header = manager.getHeader();
|
|
49
|
-
if (!header || canonicalSourcePath(header.cwd) !== canonicalSourcePath(input.cwd) || manager.getSessionId() !== input.continuation.sessionId || !manager.getEntry(input.continuation.leafId))
|
|
50
|
-
throw new Error("Persisted transcript identity does not match the conversation head");
|
|
51
|
-
manager.branch(input.continuation.leafId);
|
|
52
|
-
const context = manager.buildSessionContext();
|
|
53
|
-
if (context.model && (context.model.provider !== input.model.provider || context.model.modelId !== input.model.model))
|
|
54
|
-
throw new Error("Persisted transcript model does not match the conversation execution policy");
|
|
55
|
-
if (input.model.thinking && context.thinkingLevel !== input.model.thinking)
|
|
56
|
-
throw new Error("Persisted transcript thinking level does not match the conversation execution policy");
|
|
57
|
-
}
|
|
58
|
-
catch (error) {
|
|
59
|
-
if (error instanceof WorkflowError && error.code === "RESUME_INCOMPATIBLE")
|
|
60
|
-
throw error;
|
|
61
|
-
throw new WorkflowError("RESUME_INCOMPATIBLE", `Cannot reopen conversation transcript: ${error instanceof Error ? error.message : String(error)}`);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
else {
|
|
65
|
-
manager = input.agentDir ? SessionManager.create(input.cwd, join(agentDir, "sessions")) : SessionManager.create(input.cwd);
|
|
66
|
-
manager.appendSessionInfo(input.sessionLabel);
|
|
67
|
-
}
|
|
56
|
+
const manager = input.agentDir ? SessionManager.create(input.cwd, join(agentDir, "sessions")) : SessionManager.create(input.cwd);
|
|
57
|
+
manager.appendSessionInfo(input.sessionLabel);
|
|
68
58
|
const modelRuntime = await ModelRuntime.create({ authPath: join(agentDir, "auth.json"), modelsPath: join(agentDir, "models.json") });
|
|
69
59
|
const model = modelRuntime.getModel(input.model.provider, input.model.model);
|
|
70
60
|
if (!model)
|
|
@@ -110,9 +100,7 @@ export async function createNativeAgentSession(input) {
|
|
|
110
100
|
resourceLoader = new DefaultResourceLoader({ cwd: input.cwd, agentDir, ...(input.extensionFactories?.length ? { extensionFactories: input.extensionFactories } : {}), ...(input.systemPromptAppend ? { appendSystemPromptOverride: (base) => [...base, input.systemPromptAppend ?? ""] } : {}) });
|
|
111
101
|
await resourceLoader.reload();
|
|
112
102
|
}
|
|
113
|
-
const { session
|
|
114
|
-
if (input.continuation && modelFallbackMessage)
|
|
115
|
-
throw new WorkflowError("RESUME_INCOMPATIBLE", modelFallbackMessage);
|
|
103
|
+
const { session } = await createAgentSession({ ...(input.options ?? {}), cwd: input.cwd, agentDir, modelRuntime, model, ...(settingsManager ? { settingsManager } : {}), ...(input.model.thinking ? { thinkingLevel: input.model.thinking } : {}), tools, ...(customTools.length ? { customTools } : {}), ...(input.extensionFactories?.length ? { extensionFactories: input.extensionFactories } : {}), ...(resourceLoader ? { resourceLoader } : {}), sessionManager: manager });
|
|
116
104
|
return Object.assign(session, {
|
|
117
105
|
getLeafId: () => manager.getLeafId(),
|
|
118
106
|
getToolDefinitions: () => session.getAllTools().map(({ name, description, parameters, promptGuidelines }) => ({ name, description, parameters, ...(promptGuidelines ? { promptGuidelines } : {}) })),
|
|
@@ -120,24 +108,6 @@ export async function createNativeAgentSession(input) {
|
|
|
120
108
|
}
|
|
121
109
|
function changedOption(options, baseline, key) { return JSON.stringify(options[key]) !== JSON.stringify(baseline[key]); }
|
|
122
110
|
function validThinking(value) { return typeof value === "string" && ["off", "minimal", "low", "medium", "high", "xhigh", "max"].includes(value); }
|
|
123
|
-
function jsonValue(value, seen = new Set()) {
|
|
124
|
-
if (value === null || typeof value === "boolean" || typeof value === "string")
|
|
125
|
-
return true;
|
|
126
|
-
if (typeof value === "number")
|
|
127
|
-
return Number.isFinite(value);
|
|
128
|
-
if (typeof value !== "object" || seen.has(value))
|
|
129
|
-
return false;
|
|
130
|
-
if (!Array.isArray(value) && Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null)
|
|
131
|
-
return false;
|
|
132
|
-
const keys = Reflect.ownKeys(value);
|
|
133
|
-
if (keys.some((key) => typeof key !== "string"))
|
|
134
|
-
return false;
|
|
135
|
-
seen.add(value);
|
|
136
|
-
const valid = (Array.isArray(value) ? Array.from(value) : keys.map((key) => value[key])).every((item) => jsonValue(item, seen));
|
|
137
|
-
seen.delete(value);
|
|
138
|
-
return valid;
|
|
139
|
-
}
|
|
140
|
-
function jsonObject(value) { return jsonValue(value) && typeof value === "object" && value !== null && !Array.isArray(value); }
|
|
141
111
|
function isChildAgentToolParams(value) {
|
|
142
112
|
if (!jsonObject(value) || typeof value.prompt !== "string" || typeof value.label !== "string")
|
|
143
113
|
return false;
|
|
@@ -165,36 +135,7 @@ function fallbackSetupContext(root, options, signal) {
|
|
|
165
135
|
function resourcePolicySummary(policy) {
|
|
166
136
|
return { skills: [...policy.effective.skills], extensions: [...policy.effective.extensions], unmatchedSkills: [...policy.unmatchedSkills], unmatchedExtensions: [...policy.unmatchedExtensions] };
|
|
167
137
|
}
|
|
168
|
-
function
|
|
169
|
-
if (Array.isArray(value))
|
|
170
|
-
return value.map((item) => canonicalJson(item));
|
|
171
|
-
if (value && typeof value === "object")
|
|
172
|
-
return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => [key, canonicalJson(item)]));
|
|
173
|
-
return value;
|
|
174
|
-
}
|
|
175
|
-
function fingerprint(value) { return createHash("sha256").update(JSON.stringify(canonicalJson(value))).digest("hex"); }
|
|
176
|
-
function promptFingerprint(value) { return createHash("sha256").update(value).digest("hex"); }
|
|
177
|
-
function fixedConversationOptions(options) {
|
|
178
|
-
const fixedOptions = structuredClone(options);
|
|
179
|
-
delete fixedOptions.timeoutMs;
|
|
180
|
-
delete fixedOptions.retries;
|
|
181
|
-
return fixedOptions;
|
|
182
|
-
}
|
|
183
|
-
function conversationExecutionPolicy(options, setup) {
|
|
184
|
-
return structuredClone({
|
|
185
|
-
model: setup.sessionInput.model,
|
|
186
|
-
tools: [...setup.sessionInput.tools],
|
|
187
|
-
cwd: setup.sessionInput.cwd,
|
|
188
|
-
role: options.role ?? null,
|
|
189
|
-
worktreeOwner: options.worktreeOwner ?? null,
|
|
190
|
-
parent: options.parent ?? null,
|
|
191
|
-
systemPromptAppend: setup.sessionInput.systemPromptAppend ?? "",
|
|
192
|
-
options: fixedConversationOptions(setup.options),
|
|
193
|
-
resourcePolicy: setup.sessionInput.resourcePolicy ? resourcePolicySummary(setup.sessionInput.resourcePolicy) : null,
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
function conversationFailure(message) { return new WorkflowError("RESUME_INCOMPATIBLE", message); }
|
|
197
|
-
async function prepareAgentSetup(root, createSession, task, options, resolved, cwd, attempt, signal, customTools, resultTool, continuation) {
|
|
138
|
+
async function prepareAgentSetup(root, createSession, task, options, resolved, cwd, attempt, signal, customTools, resultTool) {
|
|
198
139
|
const setupSignal = signal ?? root.runContext?.signal ?? new AbortController().signal;
|
|
199
140
|
const baselineOptions = structuredClone(options.agentOptions ?? {});
|
|
200
141
|
const baseResourcePolicy = await root.agentResourcePolicy?.();
|
|
@@ -229,8 +170,6 @@ async function prepareAgentSetup(root, createSession, task, options, resolved, c
|
|
|
229
170
|
setup.sessionInput.tools = [...setup.options.tools];
|
|
230
171
|
if (changedOption(setup.options, baselineOptions, "cwd") && typeof setup.options.cwd === "string")
|
|
231
172
|
setup.sessionInput.cwd = setup.options.cwd;
|
|
232
|
-
if (continuation)
|
|
233
|
-
setup.sessionInput.continuation = { sessionId: continuation.sessionId, sessionFile: continuation.sessionFile, leafId: continuation.leafId };
|
|
234
173
|
const model = setup.sessionInput.model;
|
|
235
174
|
const summary = { 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) } : {}) };
|
|
236
175
|
return { setup, summary };
|
|
@@ -255,17 +194,18 @@ export class WorkflowAgentExecutor {
|
|
|
255
194
|
if (forbidden)
|
|
256
195
|
throw new WorkflowError("UNKNOWN_TOOL", `Tool is outside the launching session boundary: ${forbidden}`);
|
|
257
196
|
const requestedModel = options.model ?? definition?.model;
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
197
|
+
const alias = requestedModel === undefined ? undefined : modelAliasName(requestedModel, this.root.modelAliases ?? {});
|
|
198
|
+
const blockedAlias = requestedModel?.split(":", 1)[0];
|
|
199
|
+
if (requestedModel !== undefined && blockedAlias && this.root.blockedAliases?.has(blockedAlias) && !alias) {
|
|
200
|
+
const target = this.root.blockedAliasTargets?.[blockedAlias];
|
|
261
201
|
throw new WorkflowError("UNKNOWN_MODEL", `Unknown model alias ${requestedModel}${target ? ` resolved to ${target}` : ""}${this.root.settingsPath ? ` (settings: ${this.root.settingsPath})` : ""}`);
|
|
262
202
|
}
|
|
263
|
-
const aliasThinking = requestedModel !== undefined &&
|
|
264
|
-
const model = parseModel(requestedModel, this.root.model, options.thinking ?? (aliasThinking === undefined ? definition?.thinking : undefined), this.root.modelAliases, this.root.knownModels ?? this.root.availableModels, this.root.settingsPath);
|
|
203
|
+
const aliasThinking = requestedModel !== undefined && alias ? resolveModelReference(requestedModel, this.root.modelAliases, this.root.knownModels ?? this.root.availableModels, this.root.settingsPath).thinking : undefined;
|
|
204
|
+
const model = options.modelOverride ?? parseModel(requestedModel, this.root.model, options.thinking ?? (aliasThinking === undefined ? definition?.thinking : undefined), this.root.modelAliases, this.root.knownModels ?? this.root.availableModels, this.root.settingsPath);
|
|
265
205
|
const availableModels = this.root.knownModels ?? this.root.availableModels ?? new Set([modelCapability(this.root.model)]);
|
|
266
206
|
if (!availableModels.has(modelCapability(model)))
|
|
267
207
|
throw new WorkflowError("UNKNOWN_MODEL", `Unknown model${requestedModel ? ` ${requestedModel} resolved to ${modelCapability(model)}` : ""}${this.root.settingsPath ? ` (settings: ${this.root.settingsPath})` : ""}`);
|
|
268
|
-
return { model, ...(
|
|
208
|
+
return { model, ...(alias && requestedModel ? { requestedModel } : {}), tools: [...requested], systemPromptAppend: definition?.prompt ?? "" };
|
|
269
209
|
}
|
|
270
210
|
async execute(task, options, signal, customTools = [], setSteer, beforeRetry) {
|
|
271
211
|
const executionSignal = signal ?? this.root.runContext?.signal;
|
|
@@ -273,7 +213,8 @@ export class WorkflowAgentExecutor {
|
|
|
273
213
|
throw new WorkflowError("INVALID_METADATA", "retries must be a non-negative integer");
|
|
274
214
|
if (options.timeoutMs !== undefined && options.timeoutMs !== null && (!Number.isInteger(options.timeoutMs) || options.timeoutMs <= 0))
|
|
275
215
|
throw new WorkflowError("INVALID_METADATA", "timeoutMs must be null or a positive integer");
|
|
276
|
-
|
|
216
|
+
let resolved = this.resolve(options);
|
|
217
|
+
let recoveryModel;
|
|
277
218
|
let cwd;
|
|
278
219
|
if (options.parent) {
|
|
279
220
|
if (!options.cwd)
|
|
@@ -302,28 +243,12 @@ export class WorkflowAgentExecutor {
|
|
|
302
243
|
throw new WorkflowError("INVALID_METADATA", "Only child agents or worktree scopes may provide a cwd");
|
|
303
244
|
cwd = this.root.cwd;
|
|
304
245
|
}
|
|
305
|
-
let conversationRecord;
|
|
306
|
-
if (options.conversation) {
|
|
307
|
-
const store = this.root.runStore;
|
|
308
|
-
if (!store)
|
|
309
|
-
throw conversationFailure("Conversation persistence is unavailable");
|
|
310
|
-
try {
|
|
311
|
-
conversationRecord = await store.conversation(options.conversation.id);
|
|
312
|
-
}
|
|
313
|
-
catch (error) {
|
|
314
|
-
throw conversationFailure(`Cannot load conversation state: ${error instanceof Error ? error.message : String(error)}`);
|
|
315
|
-
}
|
|
316
|
-
if (!Number.isInteger(options.conversation.turn) || options.conversation.turn < 1)
|
|
317
|
-
throw conversationFailure("Conversation turn must be a positive integer");
|
|
318
|
-
if (conversationRecord ? conversationRecord.head.turn + 1 !== options.conversation.turn : options.conversation.turn !== 1)
|
|
319
|
-
throw conversationFailure(`Conversation turn ${String(options.conversation.turn)} does not continue its persisted head`);
|
|
320
|
-
}
|
|
321
246
|
const attempts = [];
|
|
322
|
-
let
|
|
323
|
-
const maxAttempts = (options.retries ?? 0) + 1;
|
|
247
|
+
let maxAttempts = (options.retries ?? 0) + 1;
|
|
324
248
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
249
|
+
if (recoveryModel)
|
|
250
|
+
resolved = this.resolve({ ...options, modelOverride: recoveryModel });
|
|
325
251
|
options.budget?.beforeAttempt();
|
|
326
|
-
let accepted = false;
|
|
327
252
|
let schemaResult;
|
|
328
253
|
let session;
|
|
329
254
|
let setup;
|
|
@@ -331,18 +256,14 @@ export class WorkflowAgentExecutor {
|
|
|
331
256
|
let setupFailed = false;
|
|
332
257
|
let budgetError;
|
|
333
258
|
let turnStarted = false;
|
|
334
|
-
let conversationSystemPrompt = "";
|
|
335
|
-
let conversationToolDefinitionsSha256 = "";
|
|
336
|
-
let conversationMismatch;
|
|
337
|
-
const conversationMismatchError = () => conversationMismatch ? new WorkflowError("RESUME_INCOMPATIBLE", conversationMismatch.message) : undefined;
|
|
338
259
|
const hasSchemaResult = () => schemaResult !== undefined;
|
|
339
260
|
const resultTool = options.schema ? {
|
|
340
261
|
name: "workflow_result", label: "Workflow Result", description: "Submit the terminal structured workflow result", parameters: Type.Unsafe(options.schema),
|
|
341
262
|
async execute(_id, value) {
|
|
342
|
-
if (!accepted)
|
|
343
|
-
return { content: [{ type: "text", text: "Result acceptance is not enabled yet." }], details: {}, isError: true };
|
|
344
263
|
if (!Value.Check(options.schema, value))
|
|
345
264
|
return { content: [{ type: "text", text: "Result does not match the required schema." }], details: {}, isError: true };
|
|
265
|
+
if (schemaResult !== undefined)
|
|
266
|
+
return { content: [{ type: "text", text: "Result has already been accepted." }], details: {}, isError: true };
|
|
346
267
|
schemaResult = structuredClone(value);
|
|
347
268
|
void session?.abort?.();
|
|
348
269
|
return { content: [{ type: "text", text: "Result accepted." }], details: {} };
|
|
@@ -368,7 +289,7 @@ export class WorkflowAgentExecutor {
|
|
|
368
289
|
};
|
|
369
290
|
try {
|
|
370
291
|
setupFailed = true;
|
|
371
|
-
const prepared = await prepareAgentSetup(this.root, this.createSession, task, options, resolved, cwd, attempt, executionSignal, customTools, resultTool
|
|
292
|
+
const prepared = await prepareAgentSetup(this.root, this.createSession, task, options, resolved, cwd, attempt, executionSignal, customTools, resultTool);
|
|
372
293
|
setup = prepared.setup;
|
|
373
294
|
setupSummary = prepared.summary;
|
|
374
295
|
setupFailed = false;
|
|
@@ -378,58 +299,11 @@ export class WorkflowAgentExecutor {
|
|
|
378
299
|
session = await setup.createSession(setup.sessionInput);
|
|
379
300
|
if (setup.sessionInput.resourcePolicy)
|
|
380
301
|
setupSummary = { ...setupSummary, disabledAgentResources: resourcePolicySummary(setup.sessionInput.resourcePolicy) };
|
|
381
|
-
if (options.conversation) {
|
|
382
|
-
conversationSystemPrompt = session.systemPrompt ?? "";
|
|
383
|
-
conversationToolDefinitionsSha256 = fingerprint(session.getToolDefinitions?.() ?? session.agent?.state.tools ?? []);
|
|
384
|
-
const currentExecutionPolicy = conversationExecutionPolicy(options, setup);
|
|
385
|
-
if (conversationRecord) {
|
|
386
|
-
if (session.sessionId !== conversationRecord.head.sessionId || requiredFile(session.sessionFile) !== conversationRecord.head.sessionFile)
|
|
387
|
-
throw conversationFailure("Conversation transcript identity changed");
|
|
388
|
-
if (!session.getLeafId || session.getLeafId() !== conversationRecord.head.leafId)
|
|
389
|
-
throw conversationFailure("Conversation transcript leaf identity changed");
|
|
390
|
-
if (fingerprint(currentExecutionPolicy) !== fingerprint(conversationRecord.policy))
|
|
391
|
-
throw conversationFailure("Conversation execution policy changed");
|
|
392
|
-
if (!session.subscribe && (promptFingerprint(conversationSystemPrompt) !== conversationRecord.head.systemPromptSha256 || conversationSystemPrompt !== conversationRecord.head.systemPrompt))
|
|
393
|
-
throw conversationFailure("Conversation system prompt changed");
|
|
394
|
-
if (conversationToolDefinitionsSha256 !== conversationRecord.head.toolDefinitionsSha256)
|
|
395
|
-
throw conversationFailure("Conversation tool definitions changed");
|
|
396
|
-
}
|
|
397
|
-
else if (conversationBaseline) {
|
|
398
|
-
if (fingerprint(currentExecutionPolicy) !== fingerprint(conversationBaseline.executionPolicy))
|
|
399
|
-
throw conversationFailure("Conversation execution policy changed");
|
|
400
|
-
if (conversationToolDefinitionsSha256 !== conversationBaseline.toolDefinitionsSha256)
|
|
401
|
-
throw conversationFailure("Conversation tool definitions changed");
|
|
402
|
-
}
|
|
403
|
-
else {
|
|
404
|
-
conversationBaseline = { executionPolicy: currentExecutionPolicy, toolDefinitionsSha256: conversationToolDefinitionsSha256 };
|
|
405
|
-
}
|
|
406
|
-
if (!session.subscribe) {
|
|
407
|
-
const expectedPrompt = conversationRecord?.head.systemPrompt ?? conversationBaseline?.systemPrompt;
|
|
408
|
-
const expectedDigest = conversationRecord?.head.systemPromptSha256 ?? conversationBaseline?.systemPromptSha256;
|
|
409
|
-
if (expectedPrompt !== undefined && expectedDigest !== undefined && (promptFingerprint(conversationSystemPrompt) !== expectedDigest || expectedPrompt !== conversationSystemPrompt))
|
|
410
|
-
throw conversationFailure("Conversation system prompt changed");
|
|
411
|
-
if (!conversationRecord && conversationBaseline && conversationBaseline.systemPrompt === undefined)
|
|
412
|
-
conversationBaseline = { ...conversationBaseline, systemPrompt: conversationSystemPrompt, systemPromptSha256: promptFingerprint(conversationSystemPrompt) };
|
|
413
|
-
}
|
|
414
|
-
if (conversationRecord && (!session.model || session.model.provider !== setup.sessionInput.model.provider || (session.model.model ?? session.model.id) !== setup.sessionInput.model.model))
|
|
415
|
-
throw conversationFailure("Conversation model changed");
|
|
416
|
-
}
|
|
417
302
|
const includeAttemptSetup = Boolean(this.root.agentSetupHooks?.length || setup.sessionInput.resourcePolicy);
|
|
418
303
|
await options.onAttempt?.({ attempt, sessionId: session.sessionId, sessionFile: requiredFile(session.sessionFile), ...(includeAttemptSetup ? { setup: setupSummary } : {}) });
|
|
419
304
|
const activeSession = session;
|
|
420
305
|
unsubscribe = activeSession.subscribe?.((event) => {
|
|
421
306
|
if (event.type === "agent_start" && session?.systemPrompt !== undefined) {
|
|
422
|
-
if (options.conversation) {
|
|
423
|
-
conversationSystemPrompt = session.systemPrompt;
|
|
424
|
-
const expectedPrompt = conversationRecord?.head.systemPrompt ?? conversationBaseline?.systemPrompt;
|
|
425
|
-
const expectedDigest = conversationRecord?.head.systemPromptSha256 ?? conversationBaseline?.systemPromptSha256;
|
|
426
|
-
if (expectedPrompt !== undefined && expectedDigest !== undefined && (promptFingerprint(conversationSystemPrompt) !== expectedDigest || expectedPrompt !== conversationSystemPrompt)) {
|
|
427
|
-
conversationMismatch = conversationFailure("Conversation system prompt changed");
|
|
428
|
-
void session.abort?.();
|
|
429
|
-
}
|
|
430
|
-
if (!conversationRecord && conversationBaseline && conversationBaseline.systemPrompt === undefined)
|
|
431
|
-
conversationBaseline = { ...conversationBaseline, systemPrompt: conversationSystemPrompt, systemPromptSha256: promptFingerprint(conversationSystemPrompt) };
|
|
432
|
-
}
|
|
433
307
|
if (this.root.runStore) {
|
|
434
308
|
systemPromptTurn += 1;
|
|
435
309
|
const entry = { sessionId: session.sessionId, attempt, turn: systemPromptTurn, prompt: session.systemPrompt };
|
|
@@ -454,7 +328,7 @@ export class WorkflowAgentExecutor {
|
|
|
454
328
|
activity = undefined;
|
|
455
329
|
if (event.message.role === "assistant") {
|
|
456
330
|
const needsMoreWork = hasToolCall(event.message);
|
|
457
|
-
const final = !needsMoreWork || (options.schema !== undefined &&
|
|
331
|
+
const final = !needsMoreWork || (options.schema !== undefined && hasSchemaResult());
|
|
458
332
|
if (!budgetError) {
|
|
459
333
|
try {
|
|
460
334
|
options.budget?.afterTurn(accounting(activeSession.getSessionStats()), final);
|
|
@@ -483,6 +357,7 @@ export class WorkflowAgentExecutor {
|
|
|
483
357
|
if (activity?.kind === "tool" && activity.text === event.toolName)
|
|
484
358
|
activity = undefined;
|
|
485
359
|
report(false);
|
|
360
|
+
toolCalls.delete(event.toolCallId);
|
|
486
361
|
}
|
|
487
362
|
});
|
|
488
363
|
report(false);
|
|
@@ -496,34 +371,39 @@ export class WorkflowAgentExecutor {
|
|
|
496
371
|
const promptText = `${context}\n\nTask:\n${setup.prompt}${instruction ? `\n\n${instruction}` : ""}`;
|
|
497
372
|
options.budget?.beforeTurn();
|
|
498
373
|
turnStarted = true;
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
374
|
+
try {
|
|
375
|
+
await promptWithProviderPause(session, promptText, remaining(options.timeoutMs, started), executionSignal, this.root.providerPause);
|
|
376
|
+
}
|
|
377
|
+
catch (error) {
|
|
378
|
+
if (!hasSchemaResult())
|
|
379
|
+
throw error;
|
|
380
|
+
}
|
|
381
|
+
throwIfTerminalAssistantError(session, setup.sessionInput.model);
|
|
503
382
|
{
|
|
504
383
|
const completedAccounting = accounting(session.getSessionStats());
|
|
505
|
-
options.budget?.afterTurn(completedAccounting, options.schema !== undefined ?
|
|
384
|
+
options.budget?.afterTurn(completedAccounting, options.schema !== undefined ? hasSchemaResult() : !latestAssistantHasToolCall(session.messages));
|
|
506
385
|
turnStarted = false;
|
|
507
386
|
}
|
|
508
387
|
if (budgetError)
|
|
509
388
|
throw budgetError;
|
|
510
389
|
if (options.schema) {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
390
|
+
if (!hasSchemaResult()) {
|
|
391
|
+
try {
|
|
392
|
+
options.budget?.beforeTurn();
|
|
393
|
+
turnStarted = true;
|
|
394
|
+
await promptWithProviderPause(session, "Submit the final result now by calling workflow_result exactly once. Do not return prose.", remaining(options.timeoutMs, started), executionSignal, this.root.providerPause);
|
|
395
|
+
{
|
|
396
|
+
const completedAccounting = accounting(session.getSessionStats());
|
|
397
|
+
options.budget?.afterTurn(completedAccounting, true);
|
|
398
|
+
turnStarted = false;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
catch (error) {
|
|
402
|
+
if (!hasSchemaResult())
|
|
403
|
+
throw error;
|
|
520
404
|
}
|
|
521
405
|
}
|
|
522
|
-
|
|
523
|
-
if (!hasSchemaResult())
|
|
524
|
-
throw error;
|
|
525
|
-
}
|
|
526
|
-
throwIfTerminalAssistantError(session);
|
|
406
|
+
throwIfTerminalAssistantError(session, setup.sessionInput.model);
|
|
527
407
|
if (!hasSchemaResult()) {
|
|
528
408
|
try {
|
|
529
409
|
options.budget?.beforeTurn();
|
|
@@ -539,14 +419,11 @@ export class WorkflowAgentExecutor {
|
|
|
539
419
|
if (!hasSchemaResult())
|
|
540
420
|
throw error;
|
|
541
421
|
}
|
|
542
|
-
throwIfTerminalAssistantError(session);
|
|
422
|
+
throwIfTerminalAssistantError(session, setup.sessionInput.model);
|
|
543
423
|
}
|
|
544
424
|
if (schemaResult === undefined)
|
|
545
425
|
throw new WorkflowError("RESULT_INVALID", "Agent did not submit a valid workflow_result after one repair");
|
|
546
426
|
}
|
|
547
|
-
const mismatch = conversationMismatchError();
|
|
548
|
-
if (mismatch)
|
|
549
|
-
throw mismatch;
|
|
550
427
|
const value = options.schema ? schemaResult : text(session.messages);
|
|
551
428
|
if (options.worktreeOwner)
|
|
552
429
|
await this.root.runStore?.snapshotWorktree(options.worktreeOwner);
|
|
@@ -555,22 +432,13 @@ export class WorkflowAgentExecutor {
|
|
|
555
432
|
await flushSystemPrompts();
|
|
556
433
|
unsubscribe?.();
|
|
557
434
|
const attemptAccounting = accounting(session.getSessionStats());
|
|
558
|
-
const leafId = session.getLeafId?.() ?? undefined;
|
|
559
|
-
if (options.conversation) {
|
|
560
|
-
if (!leafId)
|
|
561
|
-
throw conversationFailure("Conversation transcript has no persisted leaf");
|
|
562
|
-
const store = this.root.runStore;
|
|
563
|
-
if (!store)
|
|
564
|
-
throw conversationFailure("Conversation persistence is unavailable");
|
|
565
|
-
await store.saveConversation({ id: options.conversation.id, policy: conversationExecutionPolicy(options, setup), head: { turn: options.conversation.turn, sessionId: session.sessionId, sessionFile: requiredFile(session.sessionFile), leafId, systemPrompt: conversationSystemPrompt, systemPromptSha256: promptFingerprint(conversationSystemPrompt), toolDefinitionsSha256: conversationToolDefinitionsSha256 } });
|
|
566
|
-
}
|
|
567
435
|
const includeCompletedSetup = Boolean(this.root.agentSetupHooks?.length || setup.sessionInput.resourcePolicy);
|
|
568
436
|
attempts.push({ attempt, sessionId: session.sessionId, sessionFile: requiredFile(session.sessionFile), result: value, accounting: attemptAccounting, ...(includeCompletedSetup ? { setup: setupSummary } : {}) });
|
|
569
437
|
session.dispose();
|
|
570
438
|
return { value, attempts, cwd: setupSummary.cwd };
|
|
571
439
|
}
|
|
572
440
|
catch (error) {
|
|
573
|
-
const typed = budgetError ??
|
|
441
|
+
const typed = budgetError ?? (error instanceof WorkflowError ? error : new WorkflowError(executionSignal?.aborted && setupFailed ? "CANCELLED" : "AGENT_FAILED", error instanceof Error ? error.message : String(error)));
|
|
574
442
|
if (session) {
|
|
575
443
|
report(true);
|
|
576
444
|
await progress;
|
|
@@ -594,6 +462,30 @@ export class WorkflowAgentExecutor {
|
|
|
594
462
|
}
|
|
595
463
|
if (options.worktreeOwner && typed.code !== "WORKTREE_FAILED")
|
|
596
464
|
await this.root.runStore?.snapshotWorktree(options.worktreeOwner).catch(() => undefined);
|
|
465
|
+
const terminal = terminalProviderError(typed);
|
|
466
|
+
if (terminal && options.providerErrorRecovery) {
|
|
467
|
+
let recovery;
|
|
468
|
+
try {
|
|
469
|
+
recovery = await options.providerErrorRecovery({ label: options.label, ...terminal });
|
|
470
|
+
}
|
|
471
|
+
catch {
|
|
472
|
+
throw Object.assign(typed, { attempts });
|
|
473
|
+
}
|
|
474
|
+
if (recovery === "retry" || typeof recovery === "object" && typeof recovery.model === "string") {
|
|
475
|
+
if (typeof recovery === "object") {
|
|
476
|
+
try {
|
|
477
|
+
const selected = resolveModelReference(recovery.model, this.root.modelAliases, this.root.knownModels ?? this.root.availableModels, this.root.settingsPath);
|
|
478
|
+
recoveryModel = selected.thinking === undefined && resolved.model.thinking ? { ...selected, thinking: resolved.model.thinking } : selected;
|
|
479
|
+
}
|
|
480
|
+
catch {
|
|
481
|
+
throw Object.assign(typed, { attempts });
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
maxAttempts += 1;
|
|
485
|
+
beforeRetry?.();
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
597
489
|
if (attempt === maxAttempts || setupFailed || typed.code === "CANCELLED" || typed.code === "WORKTREE_FAILED" || typed.code === "RESUME_INCOMPATIBLE")
|
|
598
490
|
throw Object.assign(typed, { attempts });
|
|
599
491
|
beforeRetry?.();
|
|
@@ -723,6 +615,26 @@ export class FairAgentScheduler {
|
|
|
723
615
|
if (nodes.every(({ restored }) => restored))
|
|
724
616
|
run.logical = 0;
|
|
725
617
|
}
|
|
618
|
+
removeRun(runId) {
|
|
619
|
+
const run = this.#runs.get(runId);
|
|
620
|
+
if (!run)
|
|
621
|
+
return;
|
|
622
|
+
const nodes = [...this.#nodes.values()].filter((node) => node.runId === runId);
|
|
623
|
+
if (run.active > 0 || nodes.some(({ state }) => !["completed", "failed", "cancelled"].includes(state)))
|
|
624
|
+
throw new WorkflowError("INTERNAL_ERROR", `Cannot remove active scheduler run: ${runId}`);
|
|
625
|
+
for (const { id } of nodes)
|
|
626
|
+
this.#nodes.delete(id);
|
|
627
|
+
this.#runs.delete(runId);
|
|
628
|
+
const index = this.#runOrder.indexOf(runId);
|
|
629
|
+
if (index >= 0) {
|
|
630
|
+
this.#runOrder.splice(index, 1);
|
|
631
|
+
if (index < this.#cursor)
|
|
632
|
+
this.#cursor -= 1;
|
|
633
|
+
if (this.#cursor >= this.#runOrder.length)
|
|
634
|
+
this.#cursor = 0;
|
|
635
|
+
}
|
|
636
|
+
this.#dispatch();
|
|
637
|
+
}
|
|
726
638
|
toolsFor(parentId, resolveTools) {
|
|
727
639
|
const parent = this.#node(parentId);
|
|
728
640
|
if (!parent.options.tools.includes("agent"))
|
|
@@ -842,6 +754,7 @@ export class FairAgentScheduler {
|
|
|
842
754
|
return;
|
|
843
755
|
const heldPermit = node.state === "running" || node.state === "retrying";
|
|
844
756
|
node.state = result.ok ? "completed" : result.error.code === "CANCELLED" ? "cancelled" : "failed";
|
|
757
|
+
Reflect.deleteProperty(node, "steer");
|
|
845
758
|
this.#persist(node.runId);
|
|
846
759
|
if (heldPermit)
|
|
847
760
|
this.#release(node.runId);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type BudgetDimension, type BudgetEvent, type WorkflowBudget, type WorkflowBudgetPatch, type WorkflowBudgetUsage, type AgentAccounting, type RunState } from "./types.js";
|
|
2
|
+
export declare function validateBudget(value: unknown): WorkflowBudget | undefined;
|
|
3
|
+
export declare function validateBudgetPatch(value: unknown): WorkflowBudgetPatch;
|
|
4
|
+
export declare function budgetUsage(value?: Partial<WorkflowBudgetUsage>): WorkflowBudgetUsage;
|
|
5
|
+
export declare class WorkflowBudgetRuntime {
|
|
6
|
+
#private;
|
|
7
|
+
readonly budget: WorkflowBudget | undefined;
|
|
8
|
+
readonly version: number;
|
|
9
|
+
constructor(budget: WorkflowBudget | undefined, version?: number, usage?: Partial<WorkflowBudgetUsage>, events?: readonly BudgetEvent[], options?: {
|
|
10
|
+
now?: () => number;
|
|
11
|
+
onChange?: () => void;
|
|
12
|
+
active?: boolean;
|
|
13
|
+
});
|
|
14
|
+
get usage(): WorkflowBudgetUsage;
|
|
15
|
+
get events(): readonly BudgetEvent[];
|
|
16
|
+
get hardExhausted(): boolean;
|
|
17
|
+
checkAgentLaunch(): void;
|
|
18
|
+
beforeAttempt(): void;
|
|
19
|
+
beforeTurn(): void;
|
|
20
|
+
afterTurn(accounting: AgentAccounting, final: boolean): void;
|
|
21
|
+
instruction(agentId?: string): string | undefined;
|
|
22
|
+
forAgent(agentId: string): {
|
|
23
|
+
beforeAttempt: () => void;
|
|
24
|
+
beforeTurn: () => void;
|
|
25
|
+
afterTurn: (accounting: AgentAccounting, final: boolean) => void;
|
|
26
|
+
instruction: () => string | undefined;
|
|
27
|
+
};
|
|
28
|
+
transition(state: RunState): void;
|
|
29
|
+
recordEvent(event: BudgetEvent): void;
|
|
30
|
+
snapshot(): {
|
|
31
|
+
usage: WorkflowBudgetUsage;
|
|
32
|
+
budgetEvents: readonly BudgetEvent[];
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export declare function mergeBudget(budget: WorkflowBudget | undefined, patch: WorkflowBudgetPatch): WorkflowBudget | undefined;
|
|
36
|
+
export declare function budgetRelaxed(previous: WorkflowBudget | undefined, next: WorkflowBudget | undefined): boolean;
|
|
37
|
+
export declare function exhaustedBudgetDimensions(budget: WorkflowBudget | undefined, usage: WorkflowBudgetUsage): BudgetDimension[];
|
|
38
|
+
export declare function resumeBudgetAllowed(budget: WorkflowBudget | undefined, usage: WorkflowBudgetUsage): boolean;
|