@osolmaz/pi-workflows 0.16.5 → 0.16.6
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 +115 -165
- 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/workflow-message-coordinator.d.ts +12 -1
- package/dist/extension/workflow-message-coordinator.js +65 -54
- package/dist/extension/workflow-message-coordinator.js.map +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 +4 -5
- package/dist/server/view.js +51 -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 -15
- package/dist/workflows/engine.js +125 -245
- package/dist/workflows/engine.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 +174 -169
- 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/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 +72 -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 +94 -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 +117 -189
- package/src/extension/recorder.ts +2 -2
- package/src/extension/step-message.ts +4 -4
- package/src/extension/workflow-message-coordinator.ts +78 -50
- 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 +56 -60
- 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 +133 -291
- 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 +223 -231
- 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/src/workflows/engine.ts
CHANGED
|
@@ -18,8 +18,9 @@ import {
|
|
|
18
18
|
WorkflowSourceChangedError,
|
|
19
19
|
} from "./errors.js";
|
|
20
20
|
import { resolveNext, resolveNextForOutcome, validateWorkflowDefinition } from "./graph.js";
|
|
21
|
-
import { createHumanDecisionRequest
|
|
21
|
+
import { createHumanDecisionRequest } from "./human-decision.js";
|
|
22
22
|
import { extractJsonValue } from "./json.js";
|
|
23
|
+
import { workflowRequestId } from "./requests.js";
|
|
23
24
|
import {
|
|
24
25
|
parseWorkflowSettingsValue,
|
|
25
26
|
resolveInitialWorkflowSettings,
|
|
@@ -33,11 +34,12 @@ import {
|
|
|
33
34
|
createRunId,
|
|
34
35
|
type WorkflowExecutionStore,
|
|
35
36
|
} from "./store.js";
|
|
37
|
+
import { executionTransition } from "./transitions.js";
|
|
36
38
|
import type {
|
|
37
|
-
ResolvedHumanDecision,
|
|
38
39
|
AgentExpectedOutput,
|
|
39
40
|
AgentNodeDefinition,
|
|
40
41
|
AgentStepExecutor,
|
|
42
|
+
AgentStepContract,
|
|
41
43
|
AssistantMessageOutput,
|
|
42
44
|
AssistantMessageReceipt,
|
|
43
45
|
ActionNodeDefinition,
|
|
@@ -54,7 +56,6 @@ import type {
|
|
|
54
56
|
WorkflowNodeOutcome,
|
|
55
57
|
WorkflowNodeResult,
|
|
56
58
|
WorkflowNotificationSink,
|
|
57
|
-
HumanDecisionRequest,
|
|
58
59
|
WorkflowRunResult,
|
|
59
60
|
WorkflowRunState,
|
|
60
61
|
WorkflowSource,
|
|
@@ -63,7 +64,12 @@ import type {
|
|
|
63
64
|
WorkflowUpdateInput,
|
|
64
65
|
WorkflowUpdateReceipt,
|
|
65
66
|
} from "./types.js";
|
|
66
|
-
import {
|
|
67
|
+
import {
|
|
68
|
+
UpdateRateLimiter,
|
|
69
|
+
updateProjection,
|
|
70
|
+
updateReceipt,
|
|
71
|
+
validateWorkflowUpdate,
|
|
72
|
+
} from "./updates.js";
|
|
67
73
|
|
|
68
74
|
const DEFAULT_NODE_TIMEOUT_MS = 15 * 60_000;
|
|
69
75
|
const DEFAULT_MAX_STEPS = 100;
|
|
@@ -101,7 +107,8 @@ type ResumedNodeAttempt = {
|
|
|
101
107
|
nodeId: string;
|
|
102
108
|
attemptId: string;
|
|
103
109
|
startedAt: string;
|
|
104
|
-
|
|
110
|
+
timeoutMs?: number | null;
|
|
111
|
+
elapsedMs?: number;
|
|
105
112
|
settings?: WorkflowSettingsScopeRecord;
|
|
106
113
|
};
|
|
107
114
|
|
|
@@ -119,7 +126,6 @@ export class WorkflowEngine {
|
|
|
119
126
|
private readonly maxSteps: number;
|
|
120
127
|
private readonly onEvent?: WorkflowEngineOptions["onEvent"];
|
|
121
128
|
private readonly onRunStarted?: WorkflowEngineOptions["onRunStarted"];
|
|
122
|
-
private readonly onRunFinishing?: WorkflowEngineOptions["onRunFinishing"];
|
|
123
129
|
private activeAbort: AbortController | null = null;
|
|
124
130
|
private activeAttempt:
|
|
125
131
|
| {
|
|
@@ -136,7 +142,6 @@ export class WorkflowEngine {
|
|
|
136
142
|
private cancelled = false;
|
|
137
143
|
private parked = false;
|
|
138
144
|
private paused = false;
|
|
139
|
-
private presentationRequired = false;
|
|
140
145
|
private wakePause: (() => void) | null = null;
|
|
141
146
|
|
|
142
147
|
constructor(options: WorkflowEngineOptions) {
|
|
@@ -147,7 +152,6 @@ export class WorkflowEngine {
|
|
|
147
152
|
this.maxSteps = options.maxSteps ?? DEFAULT_MAX_STEPS;
|
|
148
153
|
this.onEvent = options.onEvent;
|
|
149
154
|
this.onRunStarted = options.onRunStarted;
|
|
150
|
-
this.onRunFinishing = options.onRunFinishing;
|
|
151
155
|
}
|
|
152
156
|
|
|
153
157
|
get databasePath(): string {
|
|
@@ -190,12 +194,14 @@ export class WorkflowEngine {
|
|
|
190
194
|
limiter.take();
|
|
191
195
|
const { event, record } = await this.store.publishUpdate(
|
|
192
196
|
active.runId,
|
|
193
|
-
active.state,
|
|
194
197
|
active.nodeId,
|
|
195
198
|
active.attemptId,
|
|
196
199
|
update,
|
|
197
200
|
{ signal: active.signal },
|
|
198
201
|
);
|
|
202
|
+
active.state.updates = updateProjection(active.state.updates, record);
|
|
203
|
+
active.state.traceSeq = event.seq;
|
|
204
|
+
active.state.updatedAt = event.at;
|
|
199
205
|
try {
|
|
200
206
|
this.onEvent?.(event, active.state);
|
|
201
207
|
} catch {
|
|
@@ -257,7 +263,6 @@ export class WorkflowEngine {
|
|
|
257
263
|
): Promise<WorkflowRunResult> {
|
|
258
264
|
workflow = isCompiledWorkflow(workflow) ? workflow : compileWorkflowDefinition(workflow);
|
|
259
265
|
validateWorkflowDefinition(workflow);
|
|
260
|
-
this.presentationRequired = workflow.presentationPrompt !== undefined;
|
|
261
266
|
// Fail before any run row exists so bad input cannot leave partial state.
|
|
262
267
|
const suppliedInput = input === undefined ? null : input;
|
|
263
268
|
const normalizedInput = workflow.input ? await workflow.input(suppliedInput) : suppliedInput;
|
|
@@ -330,19 +335,20 @@ export class WorkflowEngine {
|
|
|
330
335
|
): Promise<WorkflowRunResult> {
|
|
331
336
|
workflow = isCompiledWorkflow(workflow) ? workflow : compileWorkflowDefinition(workflow);
|
|
332
337
|
validateWorkflowDefinition(workflow);
|
|
333
|
-
this.presentationRequired = workflow.presentationPrompt !== undefined;
|
|
334
338
|
// Reset before any await: a park or cancel landing during preparation
|
|
335
339
|
// must survive, or a server drain would hang while the run executes.
|
|
336
340
|
this.cancelled = false;
|
|
337
341
|
this.paused = false;
|
|
338
342
|
this.parked = false;
|
|
339
|
-
const
|
|
340
|
-
|
|
343
|
+
const stored = await this.store.readRunState(runId);
|
|
344
|
+
if (stored === null) throw new Error(`Cannot resume unreadable workflow run: ${runId}`);
|
|
345
|
+
const sourceMismatch = workflowIdentityMismatch(stored, workflow, options.workflowSource);
|
|
341
346
|
if (sourceMismatch && options.force !== true) {
|
|
342
347
|
throw new WorkflowSourceChangedError(runId);
|
|
343
348
|
}
|
|
349
|
+
const state = await this.store.prepareRunResume(runId);
|
|
344
350
|
|
|
345
|
-
const point = this.resumePointFor(workflow, state
|
|
351
|
+
const point = this.resumePointFor(workflow, state);
|
|
346
352
|
const interruptedNode =
|
|
347
353
|
state.currentNode !== undefined ? workflow.nodes[state.currentNode] : undefined;
|
|
348
354
|
let resumedAttempt: ResumedNodeAttempt | undefined;
|
|
@@ -350,18 +356,19 @@ export class WorkflowEngine {
|
|
|
350
356
|
state.currentNode !== undefined &&
|
|
351
357
|
state.currentAttemptId !== undefined &&
|
|
352
358
|
state.currentNodeStartedAt !== undefined &&
|
|
353
|
-
interruptedNode?.nodeType === "
|
|
354
|
-
|
|
355
|
-
|
|
359
|
+
(interruptedNode?.nodeType === "checkpoint" ||
|
|
360
|
+
(interruptedNode?.nodeType === "agent" &&
|
|
361
|
+
(assistantMessageConfig(interruptedNode) !== undefined ||
|
|
362
|
+
options.resumeInteractionAttemptId === state.currentAttemptId)))
|
|
356
363
|
) {
|
|
357
364
|
const resumedSettings = await this.persistedSettingsBinding(state);
|
|
358
365
|
resumedAttempt = {
|
|
359
366
|
nodeId: state.currentNode,
|
|
360
367
|
attemptId: state.currentAttemptId,
|
|
361
368
|
startedAt: state.currentNodeStartedAt,
|
|
362
|
-
...(state.
|
|
369
|
+
...(state.currentNodeTimeoutMs === undefined
|
|
363
370
|
? {}
|
|
364
|
-
: {
|
|
371
|
+
: { timeoutMs: state.currentNodeTimeoutMs, elapsedMs: state.currentNodeElapsedMs ?? 0 }),
|
|
365
372
|
...(resumedSettings !== undefined ? { settings: resumedSettings } : {}),
|
|
366
373
|
};
|
|
367
374
|
}
|
|
@@ -374,7 +381,8 @@ export class WorkflowEngine {
|
|
|
374
381
|
delete state.currentNode;
|
|
375
382
|
delete state.currentAttemptId;
|
|
376
383
|
delete state.currentNodeStartedAt;
|
|
377
|
-
delete state.
|
|
384
|
+
delete state.currentNodeTimeoutMs;
|
|
385
|
+
delete state.currentNodeElapsedMs;
|
|
378
386
|
delete state.currentSettingsScopeId;
|
|
379
387
|
delete state.currentSettingsChangeNumber;
|
|
380
388
|
delete state.currentSettingsHash;
|
|
@@ -393,15 +401,8 @@ export class WorkflowEngine {
|
|
|
393
401
|
await this.onRunStarted?.(runId, state);
|
|
394
402
|
|
|
395
403
|
if (point.nodeId === null) {
|
|
396
|
-
// The last
|
|
397
|
-
|
|
398
|
-
// checkpoint restores its waiting gate rather than completing.
|
|
399
|
-
if (point.waitingOn !== undefined) {
|
|
400
|
-
await this.finishRun(runId, state, "waiting", {
|
|
401
|
-
waitingOn: point.waitingOn,
|
|
402
|
-
finalOutput: point.lastOutput,
|
|
403
|
-
});
|
|
404
|
-
} else if (point.failedResult === undefined) {
|
|
404
|
+
// The last attempt finished before the terminal event was committed.
|
|
405
|
+
if (point.failedResult === undefined) {
|
|
405
406
|
await this.finishRun(runId, state, "completed", { finalOutput: point.lastOutput });
|
|
406
407
|
} else {
|
|
407
408
|
const timedOut = point.failedResult.outcome === "timed_out";
|
|
@@ -433,161 +434,6 @@ export class WorkflowEngine {
|
|
|
433
434
|
return { runId, state };
|
|
434
435
|
}
|
|
435
436
|
|
|
436
|
-
/**
|
|
437
|
-
* Start a continuation run from a checkpointed parent. The new run gets a
|
|
438
|
-
* fresh run and event stream, carries forward the parent's outputs, results,
|
|
439
|
-
* and step accounting, and continues routing after the checkpoint.
|
|
440
|
-
*/
|
|
441
|
-
async continueRun(
|
|
442
|
-
workflow: WorkflowDefinition,
|
|
443
|
-
parentRunId: string,
|
|
444
|
-
input: unknown,
|
|
445
|
-
options: {
|
|
446
|
-
workflowSource?: WorkflowSource;
|
|
447
|
-
runId?: string;
|
|
448
|
-
force?: boolean;
|
|
449
|
-
humanDecision?: ResolvedHumanDecision;
|
|
450
|
-
} = {},
|
|
451
|
-
): Promise<WorkflowRunResult> {
|
|
452
|
-
workflow = isCompiledWorkflow(workflow) ? workflow : compileWorkflowDefinition(workflow);
|
|
453
|
-
validateWorkflowDefinition(workflow);
|
|
454
|
-
this.presentationRequired = workflow.presentationPrompt !== undefined;
|
|
455
|
-
this.cancelled = false;
|
|
456
|
-
this.paused = false;
|
|
457
|
-
this.parked = false;
|
|
458
|
-
const parentState = await this.store.readRunState(parentRunId);
|
|
459
|
-
if (parentState === null) {
|
|
460
|
-
throw new Error(`Cannot continue from unreadable workflow run: ${parentRunId}`);
|
|
461
|
-
}
|
|
462
|
-
if (parentState.status !== "waiting" || parentState.waitingOn === undefined) {
|
|
463
|
-
throw new Error(
|
|
464
|
-
`Cannot continue workflow run ${parentRunId} with status ${parentState.status}`,
|
|
465
|
-
);
|
|
466
|
-
}
|
|
467
|
-
const sourceMismatch = workflowIdentityMismatch(parentState, workflow, options.workflowSource);
|
|
468
|
-
if (sourceMismatch && options.force !== true) {
|
|
469
|
-
throw new WorkflowSourceChangedError(parentRunId);
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
const waitingNodeId = parentState.waitingOn;
|
|
473
|
-
const waitingNode = workflow.nodes[waitingNodeId];
|
|
474
|
-
const humanContract =
|
|
475
|
-
waitingNode?.nodeType === "checkpoint" ? waitingNode.humanDecision : undefined;
|
|
476
|
-
let acceptedResponse: unknown;
|
|
477
|
-
let acceptedNodeId: string | undefined;
|
|
478
|
-
let normalizedInput: unknown;
|
|
479
|
-
if (humanContract !== undefined) {
|
|
480
|
-
if (options.humanDecision === undefined) {
|
|
481
|
-
throw new Error(`Checkpoint ${waitingNodeId} requires an accepted verified human decision`);
|
|
482
|
-
}
|
|
483
|
-
const request = parentState.finalOutput as HumanDecisionRequest;
|
|
484
|
-
if (
|
|
485
|
-
request?.schema !== "pi-workflows.human-decision-request.v1" ||
|
|
486
|
-
request.decisionId !== options.humanDecision.decisionId ||
|
|
487
|
-
request.requestDigest !== options.humanDecision.requestDigest
|
|
488
|
-
) {
|
|
489
|
-
throw new Error("Accepted human decision does not match the waiting request");
|
|
490
|
-
}
|
|
491
|
-
const durableDecision = await this.store.readResolvedHumanDecision(request.decisionId);
|
|
492
|
-
if (durableDecision === null || !isDeepStrictEqual(durableDecision, options.humanDecision)) {
|
|
493
|
-
throw new Error("Accepted human decision does not match the durable decision record");
|
|
494
|
-
}
|
|
495
|
-
acceptedResponse = validateHumanDecisionResponse(request, durableDecision.response);
|
|
496
|
-
acceptedNodeId = request.nodeId;
|
|
497
|
-
normalizedInput = structuredClone(parentState.input);
|
|
498
|
-
} else {
|
|
499
|
-
const suppliedInput = input === undefined ? null : input;
|
|
500
|
-
normalizedInput = workflow.input ? await workflow.input(suppliedInput) : suppliedInput;
|
|
501
|
-
}
|
|
502
|
-
assertJsonSerializable(normalizedInput, "Workflow run input");
|
|
503
|
-
if (options.runId !== undefined && !/^[A-Za-z0-9][A-Za-z0-9._-]{0,199}$/.test(options.runId)) {
|
|
504
|
-
throw new Error(`Invalid workflow run id: ${JSON.stringify(options.runId)}`);
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
const state = await this.createRunState(
|
|
508
|
-
workflow,
|
|
509
|
-
normalizedInput,
|
|
510
|
-
options.workflowSource,
|
|
511
|
-
options.runId,
|
|
512
|
-
);
|
|
513
|
-
state.parentRunId = parentRunId;
|
|
514
|
-
state.outputs = structuredClone(parentState.outputs);
|
|
515
|
-
state.results = structuredClone(parentState.results);
|
|
516
|
-
state.steps = structuredClone(parentState.steps);
|
|
517
|
-
if (humanContract !== undefined && options.humanDecision !== undefined) {
|
|
518
|
-
const receipt = {
|
|
519
|
-
decisionId: options.humanDecision.decisionId,
|
|
520
|
-
requestDigest: options.humanDecision.requestDigest,
|
|
521
|
-
nodeId: acceptedNodeId ?? waitingNodeId,
|
|
522
|
-
response: options.humanDecision.response,
|
|
523
|
-
provenance: options.humanDecision.provenance,
|
|
524
|
-
acceptedAt: options.humanDecision.acceptedAt,
|
|
525
|
-
answerDigest: options.humanDecision.answerDigest,
|
|
526
|
-
};
|
|
527
|
-
state.humanDecision = {
|
|
528
|
-
schema: "pi-workflows.human-decision-receipt.v1",
|
|
529
|
-
...receipt,
|
|
530
|
-
subjectDigest: options.humanDecision.subjectDigest,
|
|
531
|
-
presentationDigest: options.humanDecision.presentationDigest,
|
|
532
|
-
revision: options.humanDecision.revision,
|
|
533
|
-
};
|
|
534
|
-
state.outputs[waitingNodeId] = acceptedResponse;
|
|
535
|
-
const priorResult = state.results[waitingNodeId];
|
|
536
|
-
if (priorResult === undefined) {
|
|
537
|
-
throw new Error(`Waiting human decision result is missing for ${waitingNodeId}`);
|
|
538
|
-
}
|
|
539
|
-
state.results[waitingNodeId] = { ...priorResult, output: acceptedResponse };
|
|
540
|
-
const stepIndex = state.steps.findLastIndex((step) => step.nodeId === waitingNodeId);
|
|
541
|
-
if (stepIndex < 0)
|
|
542
|
-
throw new Error(`Waiting human decision step is missing for ${waitingNodeId}`);
|
|
543
|
-
const priorStep = state.steps[stepIndex];
|
|
544
|
-
if (priorStep === undefined)
|
|
545
|
-
throw new Error("Waiting human decision step became unavailable");
|
|
546
|
-
state.steps[stepIndex] = { ...priorStep, output: acceptedResponse };
|
|
547
|
-
}
|
|
548
|
-
state.carriedStepCount = state.steps.length;
|
|
549
|
-
|
|
550
|
-
const runId = await this.store.initializeRun(workflow, state);
|
|
551
|
-
await this.persist(runId, state, {
|
|
552
|
-
scope: "run",
|
|
553
|
-
type: "run_started",
|
|
554
|
-
payload: {
|
|
555
|
-
workflowName: workflow.name,
|
|
556
|
-
...(state.runTitle ? { runTitle: state.runTitle } : {}),
|
|
557
|
-
input: state.input,
|
|
558
|
-
continuedFrom: parentRunId,
|
|
559
|
-
checkpoint: parentState.waitingOn,
|
|
560
|
-
carriedSteps: state.steps.length,
|
|
561
|
-
},
|
|
562
|
-
});
|
|
563
|
-
await this.onRunStarted?.(runId, state);
|
|
564
|
-
|
|
565
|
-
const point = this.resumePointFor(workflow, state, "continue");
|
|
566
|
-
if (point.nodeId === null) {
|
|
567
|
-
// The checkpoint was the final node; the answer completes the chain.
|
|
568
|
-
await this.finishRun(runId, state, "completed", { finalOutput: point.lastOutput });
|
|
569
|
-
return { runId, state };
|
|
570
|
-
}
|
|
571
|
-
try {
|
|
572
|
-
await this.executeGraph(
|
|
573
|
-
workflow,
|
|
574
|
-
state,
|
|
575
|
-
runId,
|
|
576
|
-
point.nodeId,
|
|
577
|
-
countExecutableSteps(workflow, state.steps),
|
|
578
|
-
point.lastOutput,
|
|
579
|
-
);
|
|
580
|
-
} catch (error) {
|
|
581
|
-
if (isClaimLostError(error)) throw error;
|
|
582
|
-
if (isRunParkedError(error) || this.parked) {
|
|
583
|
-
return { runId, state };
|
|
584
|
-
}
|
|
585
|
-
await this.finishAfterError(runId, state, error);
|
|
586
|
-
return { runId, state };
|
|
587
|
-
}
|
|
588
|
-
return { runId, state };
|
|
589
|
-
}
|
|
590
|
-
|
|
591
437
|
/**
|
|
592
438
|
* Find where a resumed run continues. An in-flight node reruns; otherwise
|
|
593
439
|
* routing continues from the last recorded step. A null nodeId means the
|
|
@@ -596,12 +442,10 @@ export class WorkflowEngine {
|
|
|
596
442
|
private resumePointFor(
|
|
597
443
|
workflow: WorkflowDefinition,
|
|
598
444
|
state: WorkflowRunState,
|
|
599
|
-
checkpointBehavior: "wait" | "continue",
|
|
600
445
|
): {
|
|
601
446
|
nodeId: string | null;
|
|
602
447
|
lastOutput?: unknown;
|
|
603
448
|
failedResult?: WorkflowNodeResult;
|
|
604
|
-
waitingOn?: string;
|
|
605
449
|
} {
|
|
606
450
|
if (state.currentNode !== undefined) {
|
|
607
451
|
if (workflow.nodes[state.currentNode] === undefined) {
|
|
@@ -618,19 +462,6 @@ export class WorkflowEngine {
|
|
|
618
462
|
return { nodeId: lastStep.nodeId };
|
|
619
463
|
}
|
|
620
464
|
if (result.outcome === "ok") {
|
|
621
|
-
// A recorded checkpoint means the run should be waiting; a crash
|
|
622
|
-
// before the run_waiting persist restores the gate instead of
|
|
623
|
-
// routing past it. The gate applies to this run's own checkpoint
|
|
624
|
-
// only: a continuation's carried steps end with the parent's
|
|
625
|
-
// already-answered checkpoint, and routing must continue from it.
|
|
626
|
-
const isCarriedStep = state.steps.length <= (state.carriedStepCount ?? 0);
|
|
627
|
-
if (
|
|
628
|
-
checkpointBehavior === "wait" &&
|
|
629
|
-
!isCarriedStep &&
|
|
630
|
-
workflow.nodes[lastStep.nodeId]?.nodeType === "checkpoint"
|
|
631
|
-
) {
|
|
632
|
-
return { nodeId: null, waitingOn: lastStep.nodeId, lastOutput: result.output };
|
|
633
|
-
}
|
|
634
465
|
const next = resolveNext(workflow.edges, lastStep.nodeId, result.output, result);
|
|
635
466
|
return next === null
|
|
636
467
|
? { nodeId: null, lastOutput: result.output }
|
|
@@ -833,6 +664,7 @@ export class WorkflowEngine {
|
|
|
833
664
|
scopeId: latest.scopeId,
|
|
834
665
|
previousChangeNumber: attempt.settings.changeNumber,
|
|
835
666
|
changeNumber: latest.changeNumber,
|
|
667
|
+
settingsHash: latest.settingsHash,
|
|
836
668
|
},
|
|
837
669
|
});
|
|
838
670
|
resumedAttempt = {
|
|
@@ -881,13 +713,6 @@ export class WorkflowEngine {
|
|
|
881
713
|
}
|
|
882
714
|
|
|
883
715
|
lastOutput = attempt.result.output;
|
|
884
|
-
if (node.nodeType === "checkpoint") {
|
|
885
|
-
await this.finishRun(runId, state, "waiting", {
|
|
886
|
-
waitingOn: attempt.result.nodeId,
|
|
887
|
-
finalOutput: lastOutput,
|
|
888
|
-
});
|
|
889
|
-
return;
|
|
890
|
-
}
|
|
891
716
|
currentNodeId = resolveNext(
|
|
892
717
|
workflow.edges,
|
|
893
718
|
attempt.result.nodeId,
|
|
@@ -999,7 +824,8 @@ export class WorkflowEngine {
|
|
|
999
824
|
delete state.currentNode;
|
|
1000
825
|
delete state.currentAttemptId;
|
|
1001
826
|
delete state.currentNodeStartedAt;
|
|
1002
|
-
delete state.
|
|
827
|
+
delete state.currentNodeTimeoutMs;
|
|
828
|
+
delete state.currentNodeElapsedMs;
|
|
1003
829
|
delete state.currentSettingsScopeId;
|
|
1004
830
|
delete state.currentSettingsChangeNumber;
|
|
1005
831
|
delete state.currentSettingsHash;
|
|
@@ -1063,7 +889,8 @@ export class WorkflowEngine {
|
|
|
1063
889
|
node,
|
|
1064
890
|
meta,
|
|
1065
891
|
settings,
|
|
1066
|
-
resumedAttempt?.
|
|
892
|
+
resumedAttempt?.timeoutMs,
|
|
893
|
+
resumedAttempt?.elapsedMs,
|
|
1067
894
|
);
|
|
1068
895
|
return {
|
|
1069
896
|
result: this.createNodeResult(nodeId, node, attemptId, startedAt, "ok", execution.output),
|
|
@@ -1131,7 +958,8 @@ export class WorkflowEngine {
|
|
|
1131
958
|
node: WorkflowNodeDefinition,
|
|
1132
959
|
meta: NodeExecutionMeta,
|
|
1133
960
|
settings?: WorkflowSettingsScopeRecord,
|
|
1134
|
-
|
|
961
|
+
persistedTimeoutMs?: number | null,
|
|
962
|
+
elapsedMs = 0,
|
|
1135
963
|
): Promise<NodeExecution> {
|
|
1136
964
|
const abort = new AbortController();
|
|
1137
965
|
const context = this.createNodeContext(state, abort.signal, settings);
|
|
@@ -1150,39 +978,40 @@ export class WorkflowEngine {
|
|
|
1150
978
|
|
|
1151
979
|
let timeoutMs: number | null;
|
|
1152
980
|
let timeoutErrorMs: number | null;
|
|
1153
|
-
if (
|
|
981
|
+
if (persistedTimeoutMs === undefined) {
|
|
1154
982
|
timeoutMs = await this.resolveNodeTimeout(node, context, abort);
|
|
1155
983
|
timeoutErrorMs = timeoutMs;
|
|
1156
|
-
if (node.nodeType === "agent" && this.executor.
|
|
1157
|
-
state.
|
|
1158
|
-
|
|
984
|
+
if (node.nodeType === "agent" && this.executor.preservesActiveTimeBudget === true) {
|
|
985
|
+
state.currentNodeTimeoutMs = timeoutMs;
|
|
986
|
+
state.currentNodeElapsedMs = 0;
|
|
1159
987
|
await this.persist(runId, state, {
|
|
1160
988
|
scope: "node",
|
|
1161
|
-
type: "
|
|
989
|
+
type: "node_timeout_set",
|
|
1162
990
|
nodeId,
|
|
1163
991
|
attemptId,
|
|
1164
|
-
payload: {
|
|
992
|
+
payload: { timeoutMs },
|
|
1165
993
|
});
|
|
1166
|
-
if (state.currentNodeDeadlineAt !== null && timeoutErrorMs !== null) {
|
|
1167
|
-
timeoutMs = Date.parse(state.currentNodeDeadlineAt) - Date.now();
|
|
1168
|
-
if (timeoutMs <= 0) throw new TimeoutError(timeoutErrorMs);
|
|
1169
|
-
}
|
|
1170
994
|
} else {
|
|
1171
|
-
delete state.
|
|
995
|
+
delete state.currentNodeTimeoutMs;
|
|
996
|
+
delete state.currentNodeElapsedMs;
|
|
1172
997
|
}
|
|
1173
|
-
} else if (
|
|
1174
|
-
state.
|
|
998
|
+
} else if (persistedTimeoutMs === null) {
|
|
999
|
+
state.currentNodeTimeoutMs = null;
|
|
1175
1000
|
timeoutMs = null;
|
|
1176
1001
|
timeoutErrorMs = null;
|
|
1177
1002
|
} else {
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1003
|
+
if (
|
|
1004
|
+
!Number.isFinite(persistedTimeoutMs) ||
|
|
1005
|
+
persistedTimeoutMs <= 0 ||
|
|
1006
|
+
!Number.isFinite(elapsedMs) ||
|
|
1007
|
+
elapsedMs < 0
|
|
1008
|
+
) {
|
|
1009
|
+
throw new Error("Persisted node timeout is invalid");
|
|
1182
1010
|
}
|
|
1183
|
-
state.
|
|
1184
|
-
|
|
1185
|
-
|
|
1011
|
+
state.currentNodeTimeoutMs = persistedTimeoutMs;
|
|
1012
|
+
state.currentNodeElapsedMs = elapsedMs;
|
|
1013
|
+
timeoutMs = persistedTimeoutMs - elapsedMs;
|
|
1014
|
+
timeoutErrorMs = persistedTimeoutMs;
|
|
1186
1015
|
if (timeoutMs <= 0) throw new TimeoutError(timeoutErrorMs);
|
|
1187
1016
|
}
|
|
1188
1017
|
if (abort.signal.aborted) {
|
|
@@ -1333,13 +1162,40 @@ export class WorkflowEngine {
|
|
|
1333
1162
|
}
|
|
1334
1163
|
case "action":
|
|
1335
1164
|
return await this.runActionNode(node, context, nodeId, attemptId, signal, meta);
|
|
1336
|
-
case "checkpoint":
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1165
|
+
case "checkpoint": {
|
|
1166
|
+
const saved = await this.store.readCheckpoint(state.runId, attemptId);
|
|
1167
|
+
if (saved !== undefined) {
|
|
1168
|
+
if (saved.request.status === "cancelled")
|
|
1169
|
+
throw new Error("Checkpoint request is cancelled");
|
|
1170
|
+
if (saved.request.status === "settled") {
|
|
1171
|
+
if (saved.humanDecision !== undefined) state.humanDecision = saved.humanDecision;
|
|
1172
|
+
return { output: saved.output, promptText: null };
|
|
1173
|
+
}
|
|
1174
|
+
} else {
|
|
1175
|
+
const request = await checkpointRequest(node, context, {
|
|
1176
|
+
workflowName: workflow.name,
|
|
1177
|
+
nodeId,
|
|
1178
|
+
attemptId,
|
|
1179
|
+
});
|
|
1180
|
+
const event = await this.store.commitTransition(state.runId, {
|
|
1181
|
+
kind: "waitForInput",
|
|
1182
|
+
...request,
|
|
1183
|
+
event: {
|
|
1184
|
+
scope: "node",
|
|
1185
|
+
type: "checkpoint_requested",
|
|
1186
|
+
nodeId,
|
|
1187
|
+
attemptId,
|
|
1188
|
+
payload: { requestId: request.requestId, requestKind: request.requestKind },
|
|
1189
|
+
},
|
|
1190
|
+
});
|
|
1191
|
+
state.traceSeq = event.seq;
|
|
1192
|
+
state.updatedAt = event.at;
|
|
1193
|
+
}
|
|
1194
|
+
state.status = "waiting";
|
|
1195
|
+
state.waitingOn = nodeId;
|
|
1196
|
+
delete state.finishedAt;
|
|
1197
|
+
throw new RunParkedError();
|
|
1198
|
+
}
|
|
1343
1199
|
}
|
|
1344
1200
|
}
|
|
1345
1201
|
|
|
@@ -1414,11 +1270,12 @@ export class WorkflowEngine {
|
|
|
1414
1270
|
signal: AbortSignal,
|
|
1415
1271
|
settings?: WorkflowSettingsScopeRecord,
|
|
1416
1272
|
): WorkflowNodeContext {
|
|
1273
|
+
const snapshot = deepFreezeJson(structuredClone(state));
|
|
1417
1274
|
return {
|
|
1418
|
-
input:
|
|
1419
|
-
outputs:
|
|
1420
|
-
results:
|
|
1421
|
-
state,
|
|
1275
|
+
input: snapshot.input,
|
|
1276
|
+
outputs: snapshot.outputs,
|
|
1277
|
+
results: snapshot.results,
|
|
1278
|
+
state: snapshot,
|
|
1422
1279
|
...(settings !== undefined
|
|
1423
1280
|
? {
|
|
1424
1281
|
settings: deepFreezeJson(structuredClone(settings.settings)),
|
|
@@ -1468,13 +1325,17 @@ export class WorkflowEngine {
|
|
|
1468
1325
|
// may already be terminal.
|
|
1469
1326
|
throw abortError(signal);
|
|
1470
1327
|
}
|
|
1471
|
-
const
|
|
1472
|
-
|
|
1473
|
-
|
|
1328
|
+
const contract: AgentStepContract = {
|
|
1329
|
+
requestId: workflowRequestId(state.runId, attemptId),
|
|
1330
|
+
runId: state.runId,
|
|
1331
|
+
workflowName: workflow.name,
|
|
1474
1332
|
nodeId,
|
|
1475
1333
|
attemptId,
|
|
1476
|
-
|
|
1477
|
-
|
|
1334
|
+
completion: assistant === undefined ? "submit" : "assistant",
|
|
1335
|
+
...(typeof node.expectedOutput === "string" ? { expectedOutput: node.expectedOutput } : {}),
|
|
1336
|
+
...(assistant?.maxChars !== undefined ? { maxOutputChars: assistant.maxChars } : {}),
|
|
1337
|
+
};
|
|
1338
|
+
const prompt = appendStepContract(basePrompt, contract);
|
|
1478
1339
|
meta.promptText = prompt;
|
|
1479
1340
|
await this.persist(runId, state, {
|
|
1480
1341
|
scope: "agent",
|
|
@@ -1486,17 +1347,7 @@ export class WorkflowEngine {
|
|
|
1486
1347
|
|
|
1487
1348
|
const submission = await this.executor.runAgentStep(
|
|
1488
1349
|
{
|
|
1489
|
-
contract
|
|
1490
|
-
runId: state.runId,
|
|
1491
|
-
workflowName: workflow.name,
|
|
1492
|
-
nodeId,
|
|
1493
|
-
attemptId,
|
|
1494
|
-
completion: assistant === undefined ? "submit" : "assistant",
|
|
1495
|
-
...(typeof node.expectedOutput === "string"
|
|
1496
|
-
? { expectedOutput: node.expectedOutput }
|
|
1497
|
-
: {}),
|
|
1498
|
-
...(assistant?.maxChars !== undefined ? { maxOutputChars: assistant.maxChars } : {}),
|
|
1499
|
-
},
|
|
1350
|
+
contract,
|
|
1500
1351
|
prompt,
|
|
1501
1352
|
...(state.runTitle !== undefined || node.statusDetail !== undefined
|
|
1502
1353
|
? {
|
|
@@ -1632,7 +1483,9 @@ export class WorkflowEngine {
|
|
|
1632
1483
|
state: WorkflowRunState,
|
|
1633
1484
|
event: WorkflowTraceEventDraft,
|
|
1634
1485
|
): Promise<void> {
|
|
1635
|
-
const traceEvent = await this.store.
|
|
1486
|
+
const traceEvent = await this.store.commitTransition(runId, executionTransition(state, event));
|
|
1487
|
+
state.traceSeq = traceEvent.seq;
|
|
1488
|
+
state.updatedAt = traceEvent.at;
|
|
1636
1489
|
try {
|
|
1637
1490
|
this.onEvent?.(traceEvent, state);
|
|
1638
1491
|
} catch {
|
|
@@ -1645,8 +1498,8 @@ export class WorkflowEngine {
|
|
|
1645
1498
|
private async finishRun(
|
|
1646
1499
|
runId: string,
|
|
1647
1500
|
state: WorkflowRunState,
|
|
1648
|
-
status:
|
|
1649
|
-
fields: { error?: string;
|
|
1501
|
+
status: "completed" | "failed" | "timed_out" | "cancelled",
|
|
1502
|
+
fields: { error?: string; finalOutput?: unknown },
|
|
1650
1503
|
): Promise<void> {
|
|
1651
1504
|
if (status === "failed" && state.status === "timed_out") {
|
|
1652
1505
|
status = "timed_out";
|
|
@@ -1656,50 +1509,38 @@ export class WorkflowEngine {
|
|
|
1656
1509
|
if (fields.error !== undefined) {
|
|
1657
1510
|
state.error = fields.error;
|
|
1658
1511
|
}
|
|
1659
|
-
if (fields.waitingOn !== undefined) {
|
|
1660
|
-
state.waitingOn = fields.waitingOn;
|
|
1661
|
-
}
|
|
1662
1512
|
if (fields.finalOutput !== undefined) {
|
|
1663
1513
|
state.finalOutput = fields.finalOutput;
|
|
1664
1514
|
}
|
|
1665
1515
|
delete state.currentNode;
|
|
1666
1516
|
delete state.currentAttemptId;
|
|
1667
1517
|
delete state.currentNodeStartedAt;
|
|
1668
|
-
delete state.
|
|
1518
|
+
delete state.currentNodeTimeoutMs;
|
|
1519
|
+
delete state.currentNodeElapsedMs;
|
|
1669
1520
|
delete state.currentSettingsScopeId;
|
|
1670
1521
|
delete state.currentSettingsChangeNumber;
|
|
1671
1522
|
delete state.currentSettingsHash;
|
|
1672
|
-
// Let observers stop, drain, and prepare terminal delivery from the final
|
|
1673
|
-
// in-memory state before the immutable terminal event is committed.
|
|
1674
|
-
try {
|
|
1675
|
-
await this.onRunFinishing?.(runId, state);
|
|
1676
|
-
} catch {
|
|
1677
|
-
// Finishing the run wins over observer failures.
|
|
1678
|
-
}
|
|
1679
1523
|
await this.persist(runId, state, {
|
|
1680
1524
|
scope: "run",
|
|
1681
1525
|
type: `run_${status}`,
|
|
1682
1526
|
payload: {
|
|
1683
1527
|
status,
|
|
1684
1528
|
...(fields.error !== undefined ? { error: fields.error } : {}),
|
|
1685
|
-
...(fields.waitingOn !== undefined ? { waitingOn: fields.waitingOn } : {}),
|
|
1686
1529
|
...(fields.finalOutput !== undefined ? { finalOutput: fields.finalOutput } : {}),
|
|
1687
|
-
presentationRequired: status === "completed" && this.presentationRequired,
|
|
1688
1530
|
},
|
|
1689
1531
|
});
|
|
1690
1532
|
}
|
|
1691
1533
|
}
|
|
1692
1534
|
|
|
1693
|
-
async function
|
|
1535
|
+
async function checkpointRequest(
|
|
1694
1536
|
node: CheckpointNodeDefinition,
|
|
1695
1537
|
context: WorkflowNodeContext,
|
|
1696
1538
|
execution: {
|
|
1697
|
-
store: WorkflowExecutionStore;
|
|
1698
1539
|
workflowName: string;
|
|
1699
1540
|
nodeId: string;
|
|
1700
1541
|
attemptId: string;
|
|
1701
1542
|
},
|
|
1702
|
-
): Promise<
|
|
1543
|
+
): Promise<{ requestId: string; requestKind: "checkpoint" | "decision"; contract: JsonValue }> {
|
|
1703
1544
|
if (node.humanDecision !== undefined) {
|
|
1704
1545
|
const prompt = await node.humanDecision.request(context);
|
|
1705
1546
|
const audience =
|
|
@@ -1719,11 +1560,18 @@ async function runCheckpointNode(
|
|
|
1719
1560
|
prompt,
|
|
1720
1561
|
...(timeout !== undefined ? { timeout } : {}),
|
|
1721
1562
|
});
|
|
1722
|
-
|
|
1723
|
-
|
|
1563
|
+
return {
|
|
1564
|
+
requestId: request.decisionId,
|
|
1565
|
+
requestKind: "decision",
|
|
1566
|
+
contract: request as unknown as JsonValue,
|
|
1567
|
+
};
|
|
1724
1568
|
}
|
|
1725
1569
|
const output = node.run ? await node.run(context) : { summary: node.summary ?? "checkpoint" };
|
|
1726
|
-
return {
|
|
1570
|
+
return {
|
|
1571
|
+
requestId: workflowRequestId(context.state.runId, execution.attemptId),
|
|
1572
|
+
requestKind: "checkpoint",
|
|
1573
|
+
contract: output as JsonValue,
|
|
1574
|
+
};
|
|
1727
1575
|
}
|
|
1728
1576
|
|
|
1729
1577
|
function shellReceipt(result: ShellActionResult): WorkflowActionReceipt {
|
|
@@ -2040,15 +1888,9 @@ function settingsDefinitionForNode(
|
|
|
2040
1888
|
* The step contract appended to every agent-node prompt. This is the
|
|
2041
1889
|
* documented standard for how the model completes a workflow step.
|
|
2042
1890
|
*/
|
|
2043
|
-
export function appendStepContract(
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
nodeId: string,
|
|
2047
|
-
attemptId: string,
|
|
2048
|
-
expectedOutput: AgentExpectedOutput | undefined,
|
|
2049
|
-
): string {
|
|
2050
|
-
const assistant = assistantMessageOutput(expectedOutput);
|
|
2051
|
-
if (assistant !== undefined) {
|
|
1891
|
+
export function appendStepContract(prompt: string, contract: AgentStepContract): string {
|
|
1892
|
+
const { requestId, workflowName, nodeId, attemptId, expectedOutput } = contract;
|
|
1893
|
+
if (contract.completion === "assistant") {
|
|
2052
1894
|
return [
|
|
2053
1895
|
prompt.trimEnd(),
|
|
2054
1896
|
"",
|
|
@@ -2058,8 +1900,8 @@ export function appendStepContract(
|
|
|
2058
1900
|
"Reply with a normal assistant message.",
|
|
2059
1901
|
"Do not call the workflow tool to complete this step.",
|
|
2060
1902
|
"Your visible reply becomes the workflow step output after the turn settles.",
|
|
2061
|
-
...(
|
|
2062
|
-
? [`Keep the visible reply within ${
|
|
1903
|
+
...(contract.maxOutputChars !== undefined
|
|
1904
|
+
? [`Keep the visible reply within ${contract.maxOutputChars} characters.`]
|
|
2063
1905
|
: []),
|
|
2064
1906
|
].join("\n");
|
|
2065
1907
|
}
|
|
@@ -2070,9 +1912,9 @@ export function appendStepContract(
|
|
|
2070
1912
|
`Workflow step contract (workflow: ${workflowName}, step: ${nodeId}, attempt: ${attemptId})`,
|
|
2071
1913
|
"",
|
|
2072
1914
|
"While this step is active, you may publish non-completing updates with:",
|
|
2073
|
-
`{"action": "update", "
|
|
1915
|
+
`{"action": "update", "requestId": ${JSON.stringify(requestId)}, "update": {"type": "...", "key": "...", "data": {...}}}`,
|
|
2074
1916
|
"Complete this step by calling the `workflow` tool exactly once with:",
|
|
2075
|
-
`{"action": "submit", "
|
|
1917
|
+
`{"action": "submit", "requestId": ${JSON.stringify(requestId)}, "output": <your result>}`,
|
|
2076
1918
|
`Expected output: ${typeof expectedOutput === "string" ? expectedOutput : "a JSON object with your result"}`,
|
|
2077
1919
|
"The step is complete only after the workflow tool accepts the output.",
|
|
2078
1920
|
"If the tool reports a validation error, correct the output and call it again.",
|