@osolmaz/pi-workflows 0.16.5 → 0.16.7
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 +5 -5
- package/dist/builtins/autoimplement.workflow.d.ts +5 -1
- package/dist/builtins/autoimplement.workflow.js +29 -10
- package/dist/builtins/autoimplement.workflow.js.map +1 -1
- package/dist/client/client.js +3 -1
- package/dist/client/client.js.map +1 -1
- package/dist/client/protocol.d.ts +1 -1
- package/dist/client/protocol.js +18 -1
- package/dist/client/protocol.js.map +1 -1
- package/dist/client/view.d.ts +7 -5
- package/dist/extension/index.d.ts +2 -1
- package/dist/extension/index.js +116 -166
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/recorder.d.ts +1 -1
- package/dist/extension/recorder.js +2 -2
- package/dist/extension/recorder.js.map +1 -1
- package/dist/extension/step-message.d.ts +1 -1
- package/dist/extension/step-message.js +4 -4
- package/dist/extension/step-message.js.map +1 -1
- package/dist/extension/widget.js +1 -1
- package/dist/extension/widget.js.map +1 -1
- package/dist/extension/workflow-message-coordinator.d.ts +12 -3
- package/dist/extension/workflow-message-coordinator.js +108 -94
- package/dist/extension/workflow-message-coordinator.js.map +1 -1
- package/dist/render/graph-render.js +1 -1
- package/dist/resource-managers/index.d.ts +1 -2
- package/dist/resource-managers/index.js +1 -2
- package/dist/resource-managers/index.js.map +1 -1
- package/dist/resource-managers/sqlite.d.ts +2 -295
- package/dist/resource-managers/sqlite.js +71 -1528
- package/dist/resource-managers/sqlite.js.map +1 -1
- package/dist/resource-managers/types.d.ts +2 -7
- package/dist/server/resource-runner-entry.js +2 -6
- package/dist/server/resource-runner-entry.js.map +1 -1
- package/dist/server/rpc-bridge.js +1 -1
- package/dist/server/rpc-bridge.js.map +1 -1
- package/dist/server/rpc-executor.js +2 -4
- package/dist/server/rpc-executor.js.map +1 -1
- package/dist/server/server-entry.js +3 -0
- package/dist/server/server-entry.js.map +1 -1
- package/dist/server/server.d.ts +9 -8
- package/dist/server/server.js +364 -541
- package/dist/server/server.js.map +1 -1
- package/dist/server/state.d.ts +16 -29
- package/dist/server/state.js +105 -319
- package/dist/server/state.js.map +1 -1
- package/dist/server/view.d.ts +6 -5
- package/dist/server/view.js +68 -51
- package/dist/server/view.js.map +1 -1
- package/dist/server/workflow-runner-entry.d.ts +1 -1
- package/dist/server/workflow-runner-entry.js +1 -49
- package/dist/server/workflow-runner-entry.js.map +1 -1
- package/dist/server/workflow-runner-protocol.d.ts +2 -7
- package/dist/server/workflow-runner-protocol.js +5 -10
- package/dist/server/workflow-runner-protocol.js.map +1 -1
- package/dist/server/workflow-runner-store.d.ts +5 -6
- package/dist/server/workflow-runner-store.js +7 -14
- package/dist/server/workflow-runner-store.js.map +1 -1
- package/dist/state/attempt-time.d.ts +21 -0
- package/dist/state/attempt-time.js +95 -0
- package/dist/state/attempt-time.js.map +1 -0
- package/dist/state/json.d.ts +2 -1
- package/dist/state/json.js.map +1 -1
- package/dist/state/project-store.d.ts +39 -0
- package/dist/state/project-store.js +154 -0
- package/dist/state/project-store.js.map +1 -0
- package/dist/state/prune.js +0 -7
- package/dist/state/prune.js.map +1 -1
- package/dist/state/schema.js +23 -23
- package/dist/state/workflow-messages.d.ts +1 -1
- package/dist/state/workflow-messages.js +3 -1
- package/dist/state/workflow-messages.js.map +1 -1
- package/dist/viewer/backup.js +5 -0
- package/dist/viewer/backup.js.map +1 -1
- package/dist/workflows/diagnostics.d.ts +8 -0
- package/dist/workflows/diagnostics.js +61 -0
- package/dist/workflows/diagnostics.js.map +1 -0
- package/dist/workflows/engine.d.ts +2 -16
- package/dist/workflows/engine.js +128 -252
- package/dist/workflows/engine.js.map +1 -1
- package/dist/workflows/errors.d.ts +1 -1
- package/dist/workflows/errors.js +2 -2
- package/dist/workflows/errors.js.map +1 -1
- package/dist/workflows/human-decision.d.ts +3 -7
- package/dist/workflows/human-decision.js +2 -56
- package/dist/workflows/human-decision.js.map +1 -1
- package/dist/workflows/index.d.ts +2 -1
- package/dist/workflows/index.js +1 -0
- package/dist/workflows/index.js.map +1 -1
- package/dist/workflows/queue.d.ts +267 -0
- package/dist/workflows/queue.js +1248 -0
- package/dist/workflows/queue.js.map +1 -0
- package/dist/workflows/requests.d.ts +47 -0
- package/dist/workflows/requests.js +158 -0
- package/dist/workflows/requests.js.map +1 -0
- package/dist/workflows/schema.js +2 -4
- package/dist/workflows/schema.js.map +1 -1
- package/dist/workflows/store.d.ts +11 -16
- package/dist/workflows/store.js +177 -174
- package/dist/workflows/store.js.map +1 -1
- package/dist/workflows/tool-input.d.ts +6 -9
- package/dist/workflows/tool-input.js +15 -12
- package/dist/workflows/tool-input.js.map +1 -1
- package/dist/workflows/transitions.d.ts +38 -0
- package/dist/workflows/transitions.js +276 -0
- package/dist/workflows/transitions.js.map +1 -0
- package/dist/workflows/types.d.ts +9 -42
- package/dist/workflows/workflow-message-content.d.ts +7 -1
- package/dist/workflows/workflow-message-content.js +22 -4
- package/dist/workflows/workflow-message-content.js.map +1 -1
- package/docs/2026-08-25-workflow-follow-ups.md +5 -5
- package/docs/2026-09-02-unify-workflow-messages-plan.md +2 -0
- package/docs/2026-09-04-workflow-run-state-plan.md +2 -0
- package/docs/2026-09-06-durable-execution-plan.md +176 -0
- package/docs/2026-09-07-workflow-handoff-plan.md +84 -0
- package/docs/DEFERRED_TURNS.md +84 -105
- package/docs/HUMAN_DECISIONS.md +17 -19
- package/docs/HUMAN_DECISION_PRESENTATIONS.md +1 -1
- package/docs/SQLITE_STATE.md +68 -29
- package/docs/WORKFLOW_SERVER.md +21 -10
- package/docs/WORKFLOW_STEP_MESSAGES.md +83 -54
- package/docs/plans/2026-08-04-controller-runtime-plan.md +2 -0
- package/docs/plans/2026-08-21-deferred-turn-intents-plan.md +2 -0
- package/docs/plans/2026-08-27-workflow-terminal-restart-plan.md +2 -0
- package/docs/workflows.md +111 -45
- package/examples/workflows/autoresearch.workflow.ts +0 -2
- package/examples/workflows/branch.workflow.ts +0 -4
- package/examples/workflows/echo.workflow.ts +0 -2
- package/examples/workflows/live-settings.workflow.ts +0 -2
- package/examples/workflows/two-turn.workflow.ts +0 -2
- package/herdr-plugin.toml +1 -1
- package/package.json +1 -1
- package/protocol/fixtures/client-v1.json +1 -1
- package/skills/pi-workflows/SKILL.md +8 -7
- package/src/builtins/autoimplement.workflow.ts +31 -10
- package/src/client/client.ts +3 -1
- package/src/client/protocol.ts +21 -1
- package/src/client/view.ts +9 -5
- package/src/extension/index.ts +118 -190
- package/src/extension/recorder.ts +2 -2
- package/src/extension/step-message.ts +4 -4
- package/src/extension/widget.ts +1 -1
- package/src/extension/workflow-message-coordinator.ts +133 -95
- package/src/render/graph-render.ts +1 -1
- package/src/resource-managers/index.ts +1 -9
- package/src/resource-managers/sqlite.ts +330 -2655
- package/src/resource-managers/types.ts +2 -3
- package/src/server/resource-runner-entry.ts +2 -7
- package/src/server/rpc-bridge.ts +1 -1
- package/src/server/rpc-executor.ts +4 -9
- package/src/server/server-entry.ts +3 -0
- package/src/server/server.ts +416 -641
- package/src/server/state.ts +122 -466
- package/src/server/view.ts +73 -58
- package/src/server/workflow-runner-entry.ts +2 -55
- package/src/server/workflow-runner-protocol.ts +5 -15
- package/src/server/workflow-runner-store.ts +12 -27
- package/src/state/attempt-time.ts +122 -0
- package/src/state/json.ts +2 -1
- package/src/state/project-store.ts +218 -0
- package/src/state/prune.ts +0 -13
- package/src/state/schema.ts +23 -23
- package/src/state/workflow-messages.ts +4 -2
- package/src/viewer/backup.ts +7 -0
- package/src/workflows/diagnostics.ts +78 -0
- package/src/workflows/engine.ts +136 -299
- package/src/workflows/errors.ts +4 -2
- package/src/workflows/human-decision.ts +3 -81
- package/src/workflows/index.ts +8 -2
- package/src/workflows/queue.ts +2091 -0
- package/src/workflows/requests.ts +283 -0
- package/src/workflows/schema.ts +2 -6
- package/src/workflows/store.ts +226 -236
- package/src/workflows/tool-input.ts +20 -12
- package/src/workflows/transitions.ts +330 -0
- package/src/workflows/types.ts +9 -46
- package/src/workflows/workflow-message-content.ts +28 -4
- package/dist/resource-managers/runtime.d.ts +0 -59
- package/dist/resource-managers/runtime.js +0 -423
- package/dist/resource-managers/runtime.js.map +0 -1
- package/src/resource-managers/runtime.ts +0 -576
package/dist/workflows/engine.js
CHANGED
|
@@ -5,12 +5,14 @@ import { StaleResourceError } from "../state/mutation.js";
|
|
|
5
5
|
import { compileWorkflowDefinition, compositionMetadata, isCompiledWorkflow, } from "./composition.js";
|
|
6
6
|
import { CancelledError, errorMessage, isAbortLikeError, isClaimLostError, isRunParkedError, RunParkedError, TimeoutError, WorkflowSourceChangedError, } from "./errors.js";
|
|
7
7
|
import { resolveNext, resolveNextForOutcome, validateWorkflowDefinition } from "./graph.js";
|
|
8
|
-
import { createHumanDecisionRequest
|
|
8
|
+
import { createHumanDecisionRequest } from "./human-decision.js";
|
|
9
9
|
import { extractJsonValue } from "./json.js";
|
|
10
|
+
import { workflowRequestId } from "./requests.js";
|
|
10
11
|
import { parseWorkflowSettingsValue, resolveInitialWorkflowSettings, } from "./settings.js";
|
|
11
12
|
import { runShellAction, shellResultFromError } from "./shell.js";
|
|
12
13
|
import { RUN_STATE_SCHEMA, WorkflowRunStore, createDefinitionSnapshot, createRunId, } from "./store.js";
|
|
13
|
-
import {
|
|
14
|
+
import { executionTransition } from "./transitions.js";
|
|
15
|
+
import { UpdateRateLimiter, updateProjection, updateReceipt, validateWorkflowUpdate, } from "./updates.js";
|
|
14
16
|
const DEFAULT_NODE_TIMEOUT_MS = 15 * 60_000;
|
|
15
17
|
const DEFAULT_MAX_STEPS = 100;
|
|
16
18
|
const TITLE_TIMEOUT_MS = 30_000;
|
|
@@ -32,7 +34,6 @@ export class WorkflowEngine {
|
|
|
32
34
|
maxSteps;
|
|
33
35
|
onEvent;
|
|
34
36
|
onRunStarted;
|
|
35
|
-
onRunFinishing;
|
|
36
37
|
activeAbort = null;
|
|
37
38
|
activeAttempt;
|
|
38
39
|
updateLimiters = new Map();
|
|
@@ -40,7 +41,6 @@ export class WorkflowEngine {
|
|
|
40
41
|
cancelled = false;
|
|
41
42
|
parked = false;
|
|
42
43
|
paused = false;
|
|
43
|
-
presentationRequired = false;
|
|
44
44
|
wakePause = null;
|
|
45
45
|
constructor(options) {
|
|
46
46
|
this.executor = options.executor;
|
|
@@ -50,7 +50,6 @@ export class WorkflowEngine {
|
|
|
50
50
|
this.maxSteps = options.maxSteps ?? DEFAULT_MAX_STEPS;
|
|
51
51
|
this.onEvent = options.onEvent;
|
|
52
52
|
this.onRunStarted = options.onRunStarted;
|
|
53
|
-
this.onRunFinishing = options.onRunFinishing;
|
|
54
53
|
}
|
|
55
54
|
get databasePath() {
|
|
56
55
|
return this.store.databasePath;
|
|
@@ -80,7 +79,10 @@ export class WorkflowEngine {
|
|
|
80
79
|
this.updateLimiters.set(active.state.runId, limiter);
|
|
81
80
|
}
|
|
82
81
|
limiter.take();
|
|
83
|
-
const { event, record } = await this.store.publishUpdate(active.runId, active.
|
|
82
|
+
const { event, record } = await this.store.publishUpdate(active.runId, active.nodeId, active.attemptId, update, { signal: active.signal });
|
|
83
|
+
active.state.updates = updateProjection(active.state.updates, record);
|
|
84
|
+
active.state.traceSeq = event.seq;
|
|
85
|
+
active.state.updatedAt = event.at;
|
|
84
86
|
try {
|
|
85
87
|
this.onEvent?.(event, active.state);
|
|
86
88
|
}
|
|
@@ -133,7 +135,6 @@ export class WorkflowEngine {
|
|
|
133
135
|
async run(workflow, input, options = {}) {
|
|
134
136
|
workflow = isCompiledWorkflow(workflow) ? workflow : compileWorkflowDefinition(workflow);
|
|
135
137
|
validateWorkflowDefinition(workflow);
|
|
136
|
-
this.presentationRequired = workflow.presentationPrompt !== undefined;
|
|
137
138
|
// Fail before any run row exists so bad input cannot leave partial state.
|
|
138
139
|
const suppliedInput = input === undefined ? null : input;
|
|
139
140
|
const normalizedInput = workflow.input ? await workflow.input(suppliedInput) : suppliedInput;
|
|
@@ -191,34 +192,37 @@ export class WorkflowEngine {
|
|
|
191
192
|
async resumeRun(workflow, runId, options = {}) {
|
|
192
193
|
workflow = isCompiledWorkflow(workflow) ? workflow : compileWorkflowDefinition(workflow);
|
|
193
194
|
validateWorkflowDefinition(workflow);
|
|
194
|
-
this.presentationRequired = workflow.presentationPrompt !== undefined;
|
|
195
195
|
// Reset before any await: a park or cancel landing during preparation
|
|
196
196
|
// must survive, or a server drain would hang while the run executes.
|
|
197
197
|
this.cancelled = false;
|
|
198
198
|
this.paused = false;
|
|
199
199
|
this.parked = false;
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
200
|
+
const stored = await this.store.readRunState(runId);
|
|
201
|
+
if (stored === null)
|
|
202
|
+
throw new Error(`Cannot resume unreadable workflow run: ${runId}`);
|
|
203
|
+
const sourceMismatch = workflowIdentityMismatch(stored, workflow, options.workflowSource);
|
|
204
|
+
if (sourceMismatch) {
|
|
203
205
|
throw new WorkflowSourceChangedError(runId);
|
|
204
206
|
}
|
|
205
|
-
const
|
|
207
|
+
const state = await this.store.prepareRunResume(runId);
|
|
208
|
+
const point = this.resumePointFor(workflow, state);
|
|
206
209
|
const interruptedNode = state.currentNode !== undefined ? workflow.nodes[state.currentNode] : undefined;
|
|
207
210
|
let resumedAttempt;
|
|
208
211
|
if (state.currentNode !== undefined &&
|
|
209
212
|
state.currentAttemptId !== undefined &&
|
|
210
213
|
state.currentNodeStartedAt !== undefined &&
|
|
211
|
-
interruptedNode?.nodeType === "
|
|
212
|
-
|
|
213
|
-
|
|
214
|
+
(interruptedNode?.nodeType === "checkpoint" ||
|
|
215
|
+
(interruptedNode?.nodeType === "agent" &&
|
|
216
|
+
(assistantMessageConfig(interruptedNode) !== undefined ||
|
|
217
|
+
options.resumeInteractionAttemptId === state.currentAttemptId)))) {
|
|
214
218
|
const resumedSettings = await this.persistedSettingsBinding(state);
|
|
215
219
|
resumedAttempt = {
|
|
216
220
|
nodeId: state.currentNode,
|
|
217
221
|
attemptId: state.currentAttemptId,
|
|
218
222
|
startedAt: state.currentNodeStartedAt,
|
|
219
|
-
...(state.
|
|
223
|
+
...(state.currentNodeTimeoutMs === undefined
|
|
220
224
|
? {}
|
|
221
|
-
: {
|
|
225
|
+
: { timeoutMs: state.currentNodeTimeoutMs, elapsedMs: state.currentNodeElapsedMs ?? 0 }),
|
|
222
226
|
...(resumedSettings !== undefined ? { settings: resumedSettings } : {}),
|
|
223
227
|
};
|
|
224
228
|
}
|
|
@@ -231,7 +235,8 @@ export class WorkflowEngine {
|
|
|
231
235
|
delete state.currentNode;
|
|
232
236
|
delete state.currentAttemptId;
|
|
233
237
|
delete state.currentNodeStartedAt;
|
|
234
|
-
delete state.
|
|
238
|
+
delete state.currentNodeTimeoutMs;
|
|
239
|
+
delete state.currentNodeElapsedMs;
|
|
235
240
|
delete state.currentSettingsScopeId;
|
|
236
241
|
delete state.currentSettingsChangeNumber;
|
|
237
242
|
delete state.currentSettingsHash;
|
|
@@ -244,21 +249,12 @@ export class WorkflowEngine {
|
|
|
244
249
|
...(point.nodeId !== null ? { resumeAt: point.nodeId } : {}),
|
|
245
250
|
...(resumedAttempt !== undefined ? { resumedAttemptId: resumedAttempt.attemptId } : {}),
|
|
246
251
|
replayedSteps: state.steps.length,
|
|
247
|
-
...(sourceMismatch ? { workflowSourceMismatch: true, forced: true } : {}),
|
|
248
252
|
},
|
|
249
253
|
});
|
|
250
254
|
await this.onRunStarted?.(runId, state);
|
|
251
255
|
if (point.nodeId === null) {
|
|
252
|
-
// The last
|
|
253
|
-
|
|
254
|
-
// checkpoint restores its waiting gate rather than completing.
|
|
255
|
-
if (point.waitingOn !== undefined) {
|
|
256
|
-
await this.finishRun(runId, state, "waiting", {
|
|
257
|
-
waitingOn: point.waitingOn,
|
|
258
|
-
finalOutput: point.lastOutput,
|
|
259
|
-
});
|
|
260
|
-
}
|
|
261
|
-
else if (point.failedResult === undefined) {
|
|
256
|
+
// The last attempt finished before the terminal event was committed.
|
|
257
|
+
if (point.failedResult === undefined) {
|
|
262
258
|
await this.finishRun(runId, state, "completed", { finalOutput: point.lastOutput });
|
|
263
259
|
}
|
|
264
260
|
else {
|
|
@@ -283,138 +279,12 @@ export class WorkflowEngine {
|
|
|
283
279
|
}
|
|
284
280
|
return { runId, state };
|
|
285
281
|
}
|
|
286
|
-
/**
|
|
287
|
-
* Start a continuation run from a checkpointed parent. The new run gets a
|
|
288
|
-
* fresh run and event stream, carries forward the parent's outputs, results,
|
|
289
|
-
* and step accounting, and continues routing after the checkpoint.
|
|
290
|
-
*/
|
|
291
|
-
async continueRun(workflow, parentRunId, input, options = {}) {
|
|
292
|
-
workflow = isCompiledWorkflow(workflow) ? workflow : compileWorkflowDefinition(workflow);
|
|
293
|
-
validateWorkflowDefinition(workflow);
|
|
294
|
-
this.presentationRequired = workflow.presentationPrompt !== undefined;
|
|
295
|
-
this.cancelled = false;
|
|
296
|
-
this.paused = false;
|
|
297
|
-
this.parked = false;
|
|
298
|
-
const parentState = await this.store.readRunState(parentRunId);
|
|
299
|
-
if (parentState === null) {
|
|
300
|
-
throw new Error(`Cannot continue from unreadable workflow run: ${parentRunId}`);
|
|
301
|
-
}
|
|
302
|
-
if (parentState.status !== "waiting" || parentState.waitingOn === undefined) {
|
|
303
|
-
throw new Error(`Cannot continue workflow run ${parentRunId} with status ${parentState.status}`);
|
|
304
|
-
}
|
|
305
|
-
const sourceMismatch = workflowIdentityMismatch(parentState, workflow, options.workflowSource);
|
|
306
|
-
if (sourceMismatch && options.force !== true) {
|
|
307
|
-
throw new WorkflowSourceChangedError(parentRunId);
|
|
308
|
-
}
|
|
309
|
-
const waitingNodeId = parentState.waitingOn;
|
|
310
|
-
const waitingNode = workflow.nodes[waitingNodeId];
|
|
311
|
-
const humanContract = waitingNode?.nodeType === "checkpoint" ? waitingNode.humanDecision : undefined;
|
|
312
|
-
let acceptedResponse;
|
|
313
|
-
let acceptedNodeId;
|
|
314
|
-
let normalizedInput;
|
|
315
|
-
if (humanContract !== undefined) {
|
|
316
|
-
if (options.humanDecision === undefined) {
|
|
317
|
-
throw new Error(`Checkpoint ${waitingNodeId} requires an accepted verified human decision`);
|
|
318
|
-
}
|
|
319
|
-
const request = parentState.finalOutput;
|
|
320
|
-
if (request?.schema !== "pi-workflows.human-decision-request.v1" ||
|
|
321
|
-
request.decisionId !== options.humanDecision.decisionId ||
|
|
322
|
-
request.requestDigest !== options.humanDecision.requestDigest) {
|
|
323
|
-
throw new Error("Accepted human decision does not match the waiting request");
|
|
324
|
-
}
|
|
325
|
-
const durableDecision = await this.store.readResolvedHumanDecision(request.decisionId);
|
|
326
|
-
if (durableDecision === null || !isDeepStrictEqual(durableDecision, options.humanDecision)) {
|
|
327
|
-
throw new Error("Accepted human decision does not match the durable decision record");
|
|
328
|
-
}
|
|
329
|
-
acceptedResponse = validateHumanDecisionResponse(request, durableDecision.response);
|
|
330
|
-
acceptedNodeId = request.nodeId;
|
|
331
|
-
normalizedInput = structuredClone(parentState.input);
|
|
332
|
-
}
|
|
333
|
-
else {
|
|
334
|
-
const suppliedInput = input === undefined ? null : input;
|
|
335
|
-
normalizedInput = workflow.input ? await workflow.input(suppliedInput) : suppliedInput;
|
|
336
|
-
}
|
|
337
|
-
assertJsonSerializable(normalizedInput, "Workflow run input");
|
|
338
|
-
if (options.runId !== undefined && !/^[A-Za-z0-9][A-Za-z0-9._-]{0,199}$/.test(options.runId)) {
|
|
339
|
-
throw new Error(`Invalid workflow run id: ${JSON.stringify(options.runId)}`);
|
|
340
|
-
}
|
|
341
|
-
const state = await this.createRunState(workflow, normalizedInput, options.workflowSource, options.runId);
|
|
342
|
-
state.parentRunId = parentRunId;
|
|
343
|
-
state.outputs = structuredClone(parentState.outputs);
|
|
344
|
-
state.results = structuredClone(parentState.results);
|
|
345
|
-
state.steps = structuredClone(parentState.steps);
|
|
346
|
-
if (humanContract !== undefined && options.humanDecision !== undefined) {
|
|
347
|
-
const receipt = {
|
|
348
|
-
decisionId: options.humanDecision.decisionId,
|
|
349
|
-
requestDigest: options.humanDecision.requestDigest,
|
|
350
|
-
nodeId: acceptedNodeId ?? waitingNodeId,
|
|
351
|
-
response: options.humanDecision.response,
|
|
352
|
-
provenance: options.humanDecision.provenance,
|
|
353
|
-
acceptedAt: options.humanDecision.acceptedAt,
|
|
354
|
-
answerDigest: options.humanDecision.answerDigest,
|
|
355
|
-
};
|
|
356
|
-
state.humanDecision = {
|
|
357
|
-
schema: "pi-workflows.human-decision-receipt.v1",
|
|
358
|
-
...receipt,
|
|
359
|
-
subjectDigest: options.humanDecision.subjectDigest,
|
|
360
|
-
presentationDigest: options.humanDecision.presentationDigest,
|
|
361
|
-
revision: options.humanDecision.revision,
|
|
362
|
-
};
|
|
363
|
-
state.outputs[waitingNodeId] = acceptedResponse;
|
|
364
|
-
const priorResult = state.results[waitingNodeId];
|
|
365
|
-
if (priorResult === undefined) {
|
|
366
|
-
throw new Error(`Waiting human decision result is missing for ${waitingNodeId}`);
|
|
367
|
-
}
|
|
368
|
-
state.results[waitingNodeId] = { ...priorResult, output: acceptedResponse };
|
|
369
|
-
const stepIndex = state.steps.findLastIndex((step) => step.nodeId === waitingNodeId);
|
|
370
|
-
if (stepIndex < 0)
|
|
371
|
-
throw new Error(`Waiting human decision step is missing for ${waitingNodeId}`);
|
|
372
|
-
const priorStep = state.steps[stepIndex];
|
|
373
|
-
if (priorStep === undefined)
|
|
374
|
-
throw new Error("Waiting human decision step became unavailable");
|
|
375
|
-
state.steps[stepIndex] = { ...priorStep, output: acceptedResponse };
|
|
376
|
-
}
|
|
377
|
-
state.carriedStepCount = state.steps.length;
|
|
378
|
-
const runId = await this.store.initializeRun(workflow, state);
|
|
379
|
-
await this.persist(runId, state, {
|
|
380
|
-
scope: "run",
|
|
381
|
-
type: "run_started",
|
|
382
|
-
payload: {
|
|
383
|
-
workflowName: workflow.name,
|
|
384
|
-
...(state.runTitle ? { runTitle: state.runTitle } : {}),
|
|
385
|
-
input: state.input,
|
|
386
|
-
continuedFrom: parentRunId,
|
|
387
|
-
checkpoint: parentState.waitingOn,
|
|
388
|
-
carriedSteps: state.steps.length,
|
|
389
|
-
},
|
|
390
|
-
});
|
|
391
|
-
await this.onRunStarted?.(runId, state);
|
|
392
|
-
const point = this.resumePointFor(workflow, state, "continue");
|
|
393
|
-
if (point.nodeId === null) {
|
|
394
|
-
// The checkpoint was the final node; the answer completes the chain.
|
|
395
|
-
await this.finishRun(runId, state, "completed", { finalOutput: point.lastOutput });
|
|
396
|
-
return { runId, state };
|
|
397
|
-
}
|
|
398
|
-
try {
|
|
399
|
-
await this.executeGraph(workflow, state, runId, point.nodeId, countExecutableSteps(workflow, state.steps), point.lastOutput);
|
|
400
|
-
}
|
|
401
|
-
catch (error) {
|
|
402
|
-
if (isClaimLostError(error))
|
|
403
|
-
throw error;
|
|
404
|
-
if (isRunParkedError(error) || this.parked) {
|
|
405
|
-
return { runId, state };
|
|
406
|
-
}
|
|
407
|
-
await this.finishAfterError(runId, state, error);
|
|
408
|
-
return { runId, state };
|
|
409
|
-
}
|
|
410
|
-
return { runId, state };
|
|
411
|
-
}
|
|
412
282
|
/**
|
|
413
283
|
* Find where a resumed run continues. An in-flight node reruns; otherwise
|
|
414
284
|
* routing continues from the last recorded step. A null nodeId means the
|
|
415
285
|
* graph was already done when the crash hit.
|
|
416
286
|
*/
|
|
417
|
-
resumePointFor(workflow, state
|
|
287
|
+
resumePointFor(workflow, state) {
|
|
418
288
|
if (state.currentNode !== undefined) {
|
|
419
289
|
if (workflow.nodes[state.currentNode] === undefined) {
|
|
420
290
|
throw new Error(`Resume node is missing from the workflow: ${state.currentNode}`);
|
|
@@ -430,17 +300,6 @@ export class WorkflowEngine {
|
|
|
430
300
|
return { nodeId: lastStep.nodeId };
|
|
431
301
|
}
|
|
432
302
|
if (result.outcome === "ok") {
|
|
433
|
-
// A recorded checkpoint means the run should be waiting; a crash
|
|
434
|
-
// before the run_waiting persist restores the gate instead of
|
|
435
|
-
// routing past it. The gate applies to this run's own checkpoint
|
|
436
|
-
// only: a continuation's carried steps end with the parent's
|
|
437
|
-
// already-answered checkpoint, and routing must continue from it.
|
|
438
|
-
const isCarriedStep = state.steps.length <= (state.carriedStepCount ?? 0);
|
|
439
|
-
if (checkpointBehavior === "wait" &&
|
|
440
|
-
!isCarriedStep &&
|
|
441
|
-
workflow.nodes[lastStep.nodeId]?.nodeType === "checkpoint") {
|
|
442
|
-
return { nodeId: null, waitingOn: lastStep.nodeId, lastOutput: result.output };
|
|
443
|
-
}
|
|
444
303
|
const next = resolveNext(workflow.edges, lastStep.nodeId, result.output, result);
|
|
445
304
|
return next === null
|
|
446
305
|
? { nodeId: null, lastOutput: result.output }
|
|
@@ -496,9 +355,7 @@ export class WorkflowEngine {
|
|
|
496
355
|
...(await this.resolveTitleBounded(workflow, input)),
|
|
497
356
|
...(workflowSource !== undefined ? { workflowSource } : {}),
|
|
498
357
|
...(composition?.sources.length ? { workflowSources: composition.sources } : {}),
|
|
499
|
-
|
|
500
|
-
? { definitionDigest: definitionDigest(workflow) }
|
|
501
|
-
: {}),
|
|
358
|
+
definitionDigest: definitionDigest(workflow),
|
|
502
359
|
startedAt: now,
|
|
503
360
|
updatedAt: now,
|
|
504
361
|
status: "running",
|
|
@@ -602,6 +459,7 @@ export class WorkflowEngine {
|
|
|
602
459
|
scopeId: latest.scopeId,
|
|
603
460
|
previousChangeNumber: attempt.settings.changeNumber,
|
|
604
461
|
changeNumber: latest.changeNumber,
|
|
462
|
+
settingsHash: latest.settingsHash,
|
|
605
463
|
},
|
|
606
464
|
});
|
|
607
465
|
resumedAttempt = {
|
|
@@ -647,13 +505,6 @@ export class WorkflowEngine {
|
|
|
647
505
|
});
|
|
648
506
|
}
|
|
649
507
|
lastOutput = attempt.result.output;
|
|
650
|
-
if (node.nodeType === "checkpoint") {
|
|
651
|
-
await this.finishRun(runId, state, "waiting", {
|
|
652
|
-
waitingOn: attempt.result.nodeId,
|
|
653
|
-
finalOutput: lastOutput,
|
|
654
|
-
});
|
|
655
|
-
return;
|
|
656
|
-
}
|
|
657
508
|
currentNodeId = resolveNext(workflow.edges, attempt.result.nodeId, attempt.result.output, attempt.result);
|
|
658
509
|
}
|
|
659
510
|
await this.finishRun(runId, state, "completed", { finalOutput: lastOutput });
|
|
@@ -749,7 +600,8 @@ export class WorkflowEngine {
|
|
|
749
600
|
delete state.currentNode;
|
|
750
601
|
delete state.currentAttemptId;
|
|
751
602
|
delete state.currentNodeStartedAt;
|
|
752
|
-
delete state.
|
|
603
|
+
delete state.currentNodeTimeoutMs;
|
|
604
|
+
delete state.currentNodeElapsedMs;
|
|
753
605
|
delete state.currentSettingsScopeId;
|
|
754
606
|
delete state.currentSettingsChangeNumber;
|
|
755
607
|
delete state.currentSettingsHash;
|
|
@@ -795,7 +647,7 @@ export class WorkflowEngine {
|
|
|
795
647
|
}
|
|
796
648
|
const meta = { promptText: null };
|
|
797
649
|
try {
|
|
798
|
-
const execution = await this.runNodeWithTimeout(workflow, state, runId, nodeId, attemptId, node, meta, settings, resumedAttempt?.
|
|
650
|
+
const execution = await this.runNodeWithTimeout(workflow, state, runId, nodeId, attemptId, node, meta, settings, resumedAttempt?.timeoutMs, resumedAttempt?.elapsedMs);
|
|
799
651
|
return {
|
|
800
652
|
result: this.createNodeResult(nodeId, node, attemptId, startedAt, "ok", execution.output),
|
|
801
653
|
execution,
|
|
@@ -845,7 +697,7 @@ export class WorkflowEngine {
|
|
|
845
697
|
...(output !== undefined ? { output } : {}),
|
|
846
698
|
};
|
|
847
699
|
}
|
|
848
|
-
async runNodeWithTimeout(workflow, state, runId, nodeId, attemptId, node, meta, settings,
|
|
700
|
+
async runNodeWithTimeout(workflow, state, runId, nodeId, attemptId, node, meta, settings, persistedTimeoutMs, elapsedMs = 0) {
|
|
849
701
|
const abort = new AbortController();
|
|
850
702
|
const context = this.createNodeContext(state, abort.signal, settings);
|
|
851
703
|
let timer;
|
|
@@ -862,43 +714,41 @@ export class WorkflowEngine {
|
|
|
862
714
|
}
|
|
863
715
|
let timeoutMs;
|
|
864
716
|
let timeoutErrorMs;
|
|
865
|
-
if (
|
|
717
|
+
if (persistedTimeoutMs === undefined) {
|
|
866
718
|
timeoutMs = await this.resolveNodeTimeout(node, context, abort);
|
|
867
719
|
timeoutErrorMs = timeoutMs;
|
|
868
|
-
if (node.nodeType === "agent" && this.executor.
|
|
869
|
-
state.
|
|
870
|
-
|
|
720
|
+
if (node.nodeType === "agent" && this.executor.preservesActiveTimeBudget === true) {
|
|
721
|
+
state.currentNodeTimeoutMs = timeoutMs;
|
|
722
|
+
state.currentNodeElapsedMs = 0;
|
|
871
723
|
await this.persist(runId, state, {
|
|
872
724
|
scope: "node",
|
|
873
|
-
type: "
|
|
725
|
+
type: "node_timeout_set",
|
|
874
726
|
nodeId,
|
|
875
727
|
attemptId,
|
|
876
|
-
payload: {
|
|
728
|
+
payload: { timeoutMs },
|
|
877
729
|
});
|
|
878
|
-
if (state.currentNodeDeadlineAt !== null && timeoutErrorMs !== null) {
|
|
879
|
-
timeoutMs = Date.parse(state.currentNodeDeadlineAt) - Date.now();
|
|
880
|
-
if (timeoutMs <= 0)
|
|
881
|
-
throw new TimeoutError(timeoutErrorMs);
|
|
882
|
-
}
|
|
883
730
|
}
|
|
884
731
|
else {
|
|
885
|
-
delete state.
|
|
732
|
+
delete state.currentNodeTimeoutMs;
|
|
733
|
+
delete state.currentNodeElapsedMs;
|
|
886
734
|
}
|
|
887
735
|
}
|
|
888
|
-
else if (
|
|
889
|
-
state.
|
|
736
|
+
else if (persistedTimeoutMs === null) {
|
|
737
|
+
state.currentNodeTimeoutMs = null;
|
|
890
738
|
timeoutMs = null;
|
|
891
739
|
timeoutErrorMs = null;
|
|
892
740
|
}
|
|
893
741
|
else {
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
742
|
+
if (!Number.isFinite(persistedTimeoutMs) ||
|
|
743
|
+
persistedTimeoutMs <= 0 ||
|
|
744
|
+
!Number.isFinite(elapsedMs) ||
|
|
745
|
+
elapsedMs < 0) {
|
|
746
|
+
throw new Error("Persisted node timeout is invalid");
|
|
898
747
|
}
|
|
899
|
-
state.
|
|
900
|
-
|
|
901
|
-
|
|
748
|
+
state.currentNodeTimeoutMs = persistedTimeoutMs;
|
|
749
|
+
state.currentNodeElapsedMs = elapsedMs;
|
|
750
|
+
timeoutMs = persistedTimeoutMs - elapsedMs;
|
|
751
|
+
timeoutErrorMs = persistedTimeoutMs;
|
|
902
752
|
if (timeoutMs <= 0)
|
|
903
753
|
throw new TimeoutError(timeoutErrorMs);
|
|
904
754
|
}
|
|
@@ -1010,13 +860,42 @@ export class WorkflowEngine {
|
|
|
1010
860
|
}
|
|
1011
861
|
case "action":
|
|
1012
862
|
return await this.runActionNode(node, context, nodeId, attemptId, signal, meta);
|
|
1013
|
-
case "checkpoint":
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
863
|
+
case "checkpoint": {
|
|
864
|
+
const saved = await this.store.readCheckpoint(state.runId, attemptId);
|
|
865
|
+
if (saved !== undefined) {
|
|
866
|
+
if (saved.request.status === "cancelled")
|
|
867
|
+
throw new Error("Checkpoint request is cancelled");
|
|
868
|
+
if (saved.request.status === "settled") {
|
|
869
|
+
if (saved.humanDecision !== undefined)
|
|
870
|
+
state.humanDecision = saved.humanDecision;
|
|
871
|
+
return { output: saved.output, promptText: null };
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
else {
|
|
875
|
+
const request = await checkpointRequest(node, context, {
|
|
876
|
+
workflowName: workflow.name,
|
|
877
|
+
nodeId,
|
|
878
|
+
attemptId,
|
|
879
|
+
});
|
|
880
|
+
const event = await this.store.commitTransition(state.runId, {
|
|
881
|
+
kind: "waitForInput",
|
|
882
|
+
...request,
|
|
883
|
+
event: {
|
|
884
|
+
scope: "node",
|
|
885
|
+
type: "checkpoint_requested",
|
|
886
|
+
nodeId,
|
|
887
|
+
attemptId,
|
|
888
|
+
payload: { requestId: request.requestId, requestKind: request.requestKind },
|
|
889
|
+
},
|
|
890
|
+
});
|
|
891
|
+
state.traceSeq = event.seq;
|
|
892
|
+
state.updatedAt = event.at;
|
|
893
|
+
}
|
|
894
|
+
state.status = "waiting";
|
|
895
|
+
state.waitingOn = nodeId;
|
|
896
|
+
delete state.finishedAt;
|
|
897
|
+
throw new RunParkedError();
|
|
898
|
+
}
|
|
1020
899
|
}
|
|
1021
900
|
}
|
|
1022
901
|
async captureSettingsBinding(workflow, state, nodeId) {
|
|
@@ -1072,11 +951,12 @@ export class WorkflowEngine {
|
|
|
1072
951
|
return scope;
|
|
1073
952
|
}
|
|
1074
953
|
createNodeContext(state, signal, settings) {
|
|
954
|
+
const snapshot = deepFreezeJson(structuredClone(state));
|
|
1075
955
|
return {
|
|
1076
|
-
input:
|
|
1077
|
-
outputs:
|
|
1078
|
-
results:
|
|
1079
|
-
state,
|
|
956
|
+
input: snapshot.input,
|
|
957
|
+
outputs: snapshot.outputs,
|
|
958
|
+
results: snapshot.results,
|
|
959
|
+
state: snapshot,
|
|
1080
960
|
...(settings !== undefined
|
|
1081
961
|
? {
|
|
1082
962
|
settings: deepFreezeJson(structuredClone(settings.settings)),
|
|
@@ -1114,7 +994,17 @@ export class WorkflowEngine {
|
|
|
1114
994
|
// may already be terminal.
|
|
1115
995
|
throw abortError(signal);
|
|
1116
996
|
}
|
|
1117
|
-
const
|
|
997
|
+
const contract = {
|
|
998
|
+
requestId: workflowRequestId(state.runId, attemptId),
|
|
999
|
+
runId: state.runId,
|
|
1000
|
+
workflowName: workflow.name,
|
|
1001
|
+
nodeId,
|
|
1002
|
+
attemptId,
|
|
1003
|
+
completion: assistant === undefined ? "submit" : "assistant",
|
|
1004
|
+
...(typeof node.expectedOutput === "string" ? { expectedOutput: node.expectedOutput } : {}),
|
|
1005
|
+
...(assistant?.maxChars !== undefined ? { maxOutputChars: assistant.maxChars } : {}),
|
|
1006
|
+
};
|
|
1007
|
+
const prompt = appendStepContract(basePrompt, contract);
|
|
1118
1008
|
meta.promptText = prompt;
|
|
1119
1009
|
await this.persist(runId, state, {
|
|
1120
1010
|
scope: "agent",
|
|
@@ -1124,17 +1014,7 @@ export class WorkflowEngine {
|
|
|
1124
1014
|
payload: { prompt, completion: assistant === undefined ? "submit" : "assistant" },
|
|
1125
1015
|
});
|
|
1126
1016
|
const submission = await this.executor.runAgentStep({
|
|
1127
|
-
contract
|
|
1128
|
-
runId: state.runId,
|
|
1129
|
-
workflowName: workflow.name,
|
|
1130
|
-
nodeId,
|
|
1131
|
-
attemptId,
|
|
1132
|
-
completion: assistant === undefined ? "submit" : "assistant",
|
|
1133
|
-
...(typeof node.expectedOutput === "string"
|
|
1134
|
-
? { expectedOutput: node.expectedOutput }
|
|
1135
|
-
: {}),
|
|
1136
|
-
...(assistant?.maxChars !== undefined ? { maxOutputChars: assistant.maxChars } : {}),
|
|
1137
|
-
},
|
|
1017
|
+
contract,
|
|
1138
1018
|
prompt,
|
|
1139
1019
|
...(state.runTitle !== undefined || node.statusDetail !== undefined
|
|
1140
1020
|
? {
|
|
@@ -1251,7 +1131,9 @@ export class WorkflowEngine {
|
|
|
1251
1131
|
}
|
|
1252
1132
|
}
|
|
1253
1133
|
async persist(runId, state, event) {
|
|
1254
|
-
const traceEvent = await this.store.
|
|
1134
|
+
const traceEvent = await this.store.commitTransition(runId, executionTransition(state, event));
|
|
1135
|
+
state.traceSeq = traceEvent.seq;
|
|
1136
|
+
state.updatedAt = traceEvent.at;
|
|
1255
1137
|
try {
|
|
1256
1138
|
this.onEvent?.(traceEvent, state);
|
|
1257
1139
|
}
|
|
@@ -1270,41 +1152,29 @@ export class WorkflowEngine {
|
|
|
1270
1152
|
if (fields.error !== undefined) {
|
|
1271
1153
|
state.error = fields.error;
|
|
1272
1154
|
}
|
|
1273
|
-
if (fields.waitingOn !== undefined) {
|
|
1274
|
-
state.waitingOn = fields.waitingOn;
|
|
1275
|
-
}
|
|
1276
1155
|
if (fields.finalOutput !== undefined) {
|
|
1277
1156
|
state.finalOutput = fields.finalOutput;
|
|
1278
1157
|
}
|
|
1279
1158
|
delete state.currentNode;
|
|
1280
1159
|
delete state.currentAttemptId;
|
|
1281
1160
|
delete state.currentNodeStartedAt;
|
|
1282
|
-
delete state.
|
|
1161
|
+
delete state.currentNodeTimeoutMs;
|
|
1162
|
+
delete state.currentNodeElapsedMs;
|
|
1283
1163
|
delete state.currentSettingsScopeId;
|
|
1284
1164
|
delete state.currentSettingsChangeNumber;
|
|
1285
1165
|
delete state.currentSettingsHash;
|
|
1286
|
-
// Let observers stop, drain, and prepare terminal delivery from the final
|
|
1287
|
-
// in-memory state before the immutable terminal event is committed.
|
|
1288
|
-
try {
|
|
1289
|
-
await this.onRunFinishing?.(runId, state);
|
|
1290
|
-
}
|
|
1291
|
-
catch {
|
|
1292
|
-
// Finishing the run wins over observer failures.
|
|
1293
|
-
}
|
|
1294
1166
|
await this.persist(runId, state, {
|
|
1295
1167
|
scope: "run",
|
|
1296
1168
|
type: `run_${status}`,
|
|
1297
1169
|
payload: {
|
|
1298
1170
|
status,
|
|
1299
1171
|
...(fields.error !== undefined ? { error: fields.error } : {}),
|
|
1300
|
-
...(fields.waitingOn !== undefined ? { waitingOn: fields.waitingOn } : {}),
|
|
1301
1172
|
...(fields.finalOutput !== undefined ? { finalOutput: fields.finalOutput } : {}),
|
|
1302
|
-
presentationRequired: status === "completed" && this.presentationRequired,
|
|
1303
1173
|
},
|
|
1304
1174
|
});
|
|
1305
1175
|
}
|
|
1306
1176
|
}
|
|
1307
|
-
async function
|
|
1177
|
+
async function checkpointRequest(node, context, execution) {
|
|
1308
1178
|
if (node.humanDecision !== undefined) {
|
|
1309
1179
|
const prompt = await node.humanDecision.request(context);
|
|
1310
1180
|
const audience = typeof node.humanDecision.audience === "function"
|
|
@@ -1322,11 +1192,18 @@ async function runCheckpointNode(node, context, execution) {
|
|
|
1322
1192
|
prompt,
|
|
1323
1193
|
...(timeout !== undefined ? { timeout } : {}),
|
|
1324
1194
|
});
|
|
1325
|
-
|
|
1326
|
-
|
|
1195
|
+
return {
|
|
1196
|
+
requestId: request.decisionId,
|
|
1197
|
+
requestKind: "decision",
|
|
1198
|
+
contract: request,
|
|
1199
|
+
};
|
|
1327
1200
|
}
|
|
1328
1201
|
const output = node.run ? await node.run(context) : { summary: node.summary ?? "checkpoint" };
|
|
1329
|
-
return {
|
|
1202
|
+
return {
|
|
1203
|
+
requestId: workflowRequestId(context.state.runId, execution.attemptId),
|
|
1204
|
+
requestKind: "checkpoint",
|
|
1205
|
+
contract: output,
|
|
1206
|
+
};
|
|
1330
1207
|
}
|
|
1331
1208
|
function shellReceipt(result) {
|
|
1332
1209
|
return {
|
|
@@ -1496,8 +1373,7 @@ export function workflowIdentityMismatch(state, workflow, source) {
|
|
|
1496
1373
|
const currentSources = metadata?.sources ?? [];
|
|
1497
1374
|
if (!isDeepStrictEqual(state.workflowSources ?? [], currentSources))
|
|
1498
1375
|
return true;
|
|
1499
|
-
|
|
1500
|
-
return state.definitionDigest !== currentDigest;
|
|
1376
|
+
return state.definitionDigest !== definitionDigest(workflow);
|
|
1501
1377
|
}
|
|
1502
1378
|
function definitionDigest(workflow) {
|
|
1503
1379
|
return `sha256:${createHash("sha256")
|
|
@@ -1578,9 +1454,9 @@ function settingsDefinitionForNode(workflow, nodeId) {
|
|
|
1578
1454
|
* The step contract appended to every agent-node prompt. This is the
|
|
1579
1455
|
* documented standard for how the model completes a workflow step.
|
|
1580
1456
|
*/
|
|
1581
|
-
export function appendStepContract(prompt,
|
|
1582
|
-
const
|
|
1583
|
-
if (
|
|
1457
|
+
export function appendStepContract(prompt, contract) {
|
|
1458
|
+
const { requestId, workflowName, nodeId, attemptId, expectedOutput } = contract;
|
|
1459
|
+
if (contract.completion === "assistant") {
|
|
1584
1460
|
return [
|
|
1585
1461
|
prompt.trimEnd(),
|
|
1586
1462
|
"",
|
|
@@ -1590,8 +1466,8 @@ export function appendStepContract(prompt, workflowName, nodeId, attemptId, expe
|
|
|
1590
1466
|
"Reply with a normal assistant message.",
|
|
1591
1467
|
"Do not call the workflow tool to complete this step.",
|
|
1592
1468
|
"Your visible reply becomes the workflow step output after the turn settles.",
|
|
1593
|
-
...(
|
|
1594
|
-
? [`Keep the visible reply within ${
|
|
1469
|
+
...(contract.maxOutputChars !== undefined
|
|
1470
|
+
? [`Keep the visible reply within ${contract.maxOutputChars} characters.`]
|
|
1595
1471
|
: []),
|
|
1596
1472
|
].join("\n");
|
|
1597
1473
|
}
|
|
@@ -1602,9 +1478,9 @@ export function appendStepContract(prompt, workflowName, nodeId, attemptId, expe
|
|
|
1602
1478
|
`Workflow step contract (workflow: ${workflowName}, step: ${nodeId}, attempt: ${attemptId})`,
|
|
1603
1479
|
"",
|
|
1604
1480
|
"While this step is active, you may publish non-completing updates with:",
|
|
1605
|
-
`{"action": "update", "
|
|
1481
|
+
`{"action": "update", "requestId": ${JSON.stringify(requestId)}, "update": {"type": "...", "key": "...", "data": {...}}}`,
|
|
1606
1482
|
"Complete this step by calling the `workflow` tool exactly once with:",
|
|
1607
|
-
`{"action": "submit", "
|
|
1483
|
+
`{"action": "submit", "requestId": ${JSON.stringify(requestId)}, "output": <your result>}`,
|
|
1608
1484
|
`Expected output: ${typeof expectedOutput === "string" ? expectedOutput : "a JSON object with your result"}`,
|
|
1609
1485
|
"The step is complete only after the workflow tool accepts the output.",
|
|
1610
1486
|
"If the tool reports a validation error, correct the output and call it again.",
|