@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/src/workflows/store.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { closeAttemptTime, closeRunTime } from "../state/attempt-time.js";
|
|
2
3
|
import { StateDatabase, workflowStatePath } from "../state/database.js";
|
|
3
4
|
import { canonicalJson, parseJson, type JsonValue } from "../state/json.js";
|
|
4
5
|
import {
|
|
@@ -18,7 +19,7 @@ import {
|
|
|
18
19
|
type ViewerDeltaDraft,
|
|
19
20
|
} from "../state/viewer.js";
|
|
20
21
|
import { WorkflowMessageStore, workflowMessageIdFor } from "../state/workflow-messages.js";
|
|
21
|
-
import { compositionMetadata } from "./composition.js";
|
|
22
|
+
import { compileWorkflowDefinition, compositionMetadata } from "./composition.js";
|
|
22
23
|
import { ClaimLostError } from "./errors.js";
|
|
23
24
|
import { HumanDecisionStore } from "./human-decision.js";
|
|
24
25
|
import { applyJsonPatch, validateJsonPatch } from "./json-patch.js";
|
|
@@ -49,7 +50,6 @@ import type {
|
|
|
49
50
|
WorkflowEffectReservation,
|
|
50
51
|
HumanDecisionReceipt,
|
|
51
52
|
HumanDecisionRequest,
|
|
52
|
-
ResolvedHumanDecision,
|
|
53
53
|
WorkflowNodeDefinition,
|
|
54
54
|
WorkflowNodeResult,
|
|
55
55
|
WorkflowNodeSnapshot,
|
|
@@ -82,7 +82,6 @@ const STEP_ROW_SELECT = `
|
|
|
82
82
|
SELECT s.step_index AS stepIndex, a.attempt_id AS attemptId, a.node_id AS nodeId,
|
|
83
83
|
a.node_type AS nodeType, a.status,
|
|
84
84
|
a.prompt_hash AS promptHash, a.output_hash AS outputHash,
|
|
85
|
-
s.output_override_hash AS outputOverrideHash,
|
|
86
85
|
a.receipt_hash AS receiptHash, a.error_hash AS errorHash,
|
|
87
86
|
prompt_entry.entry_hash AS promptEntryHash,
|
|
88
87
|
response_entry.entry_hash AS responseEntryHash,
|
|
@@ -150,7 +149,6 @@ export type WorkflowRunTerminalData = {
|
|
|
150
149
|
input: JsonValue;
|
|
151
150
|
finalOutput: JsonValue | null;
|
|
152
151
|
error: string | null;
|
|
153
|
-
presentationInstructions: string;
|
|
154
152
|
restartNumber: number;
|
|
155
153
|
};
|
|
156
154
|
|
|
@@ -171,6 +169,18 @@ export type WorkflowRunStoreOptions = {
|
|
|
171
169
|
};
|
|
172
170
|
|
|
173
171
|
/** The state boundary used by workflow code, including out-of-process runners. */
|
|
172
|
+
import {
|
|
173
|
+
acceptedRequestOutput,
|
|
174
|
+
ensureWorkflowRequest,
|
|
175
|
+
requestForAttempt,
|
|
176
|
+
type WorkflowCheckpointState,
|
|
177
|
+
} from "./requests.js";
|
|
178
|
+
import {
|
|
179
|
+
applyExecutionTransition,
|
|
180
|
+
executionTransition,
|
|
181
|
+
type WorkflowTransition,
|
|
182
|
+
} from "./transitions.js";
|
|
183
|
+
|
|
174
184
|
export interface WorkflowExecutionStore {
|
|
175
185
|
readonly databasePath: string;
|
|
176
186
|
initializeRun(
|
|
@@ -180,14 +190,9 @@ export interface WorkflowExecutionStore {
|
|
|
180
190
|
): Promise<string>;
|
|
181
191
|
prepareRunResume(runId: string): Promise<WorkflowRunState>;
|
|
182
192
|
readRunState(runId: string): WorkflowRunState | null | Promise<WorkflowRunState | null>;
|
|
183
|
-
|
|
184
|
-
runId: string,
|
|
185
|
-
state: WorkflowRunState,
|
|
186
|
-
event: WorkflowTraceEventDraft,
|
|
187
|
-
): Promise<WorkflowTraceEvent>;
|
|
193
|
+
commitTransition(runId: string, transition: WorkflowTransition): Promise<WorkflowTraceEvent>;
|
|
188
194
|
publishUpdate(
|
|
189
195
|
runId: string,
|
|
190
|
-
state: WorkflowRunState,
|
|
191
196
|
nodeId: string,
|
|
192
197
|
attemptId: string,
|
|
193
198
|
update: WorkflowUpdateInput,
|
|
@@ -208,8 +213,7 @@ export interface WorkflowExecutionStore {
|
|
|
208
213
|
scopeId: string,
|
|
209
214
|
changeNumber: number,
|
|
210
215
|
): WorkflowSettingsScopeRecord | undefined | Promise<WorkflowSettingsScopeRecord | undefined>;
|
|
211
|
-
|
|
212
|
-
readResolvedHumanDecision(decisionId: string): Promise<ResolvedHumanDecision | null>;
|
|
216
|
+
readCheckpoint(runId: string, attemptId: string): Promise<WorkflowCheckpointState | undefined>;
|
|
213
217
|
reserveEffect(options: {
|
|
214
218
|
runId: string;
|
|
215
219
|
attemptId: string;
|
|
@@ -269,7 +273,6 @@ type StepRow = {
|
|
|
269
273
|
status: string;
|
|
270
274
|
promptHash: Buffer | null;
|
|
271
275
|
outputHash: Buffer | null;
|
|
272
|
-
outputOverrideHash: Buffer | null;
|
|
273
276
|
receiptHash: Buffer | null;
|
|
274
277
|
promptEntryHash: Buffer | null;
|
|
275
278
|
responseEntryHash: Buffer | null;
|
|
@@ -535,16 +538,25 @@ export class WorkflowRunStore {
|
|
|
535
538
|
return revision;
|
|
536
539
|
}
|
|
537
540
|
|
|
538
|
-
async
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
);
|
|
541
|
+
async readCheckpoint(
|
|
542
|
+
runId: string,
|
|
543
|
+
attemptId: string,
|
|
544
|
+
): Promise<WorkflowCheckpointState | undefined> {
|
|
545
|
+
const request = requestForAttempt(this.state, runId, attemptId);
|
|
546
|
+
if (request === undefined) return undefined;
|
|
547
|
+
if (request.kind !== "checkpoint" && request.kind !== "decision") {
|
|
548
|
+
throw new Error("The attempt does not own a checkpoint request");
|
|
549
|
+
}
|
|
550
|
+
if (request.status !== "settled") return { request };
|
|
551
|
+
const output = acceptedRequestOutput(this.state, request);
|
|
552
|
+
const humanDecision =
|
|
553
|
+
request.kind === "decision"
|
|
554
|
+
? this.readHumanDecisionReceipt(runId, request.requestId)
|
|
555
|
+
: undefined;
|
|
556
|
+
if (request.kind === "decision" && humanDecision === undefined) {
|
|
557
|
+
throw new Error("The checkpoint has no verified human decision receipt");
|
|
558
|
+
}
|
|
559
|
+
return { request, output, ...(humanDecision === undefined ? {} : { humanDecision }) };
|
|
548
560
|
}
|
|
549
561
|
|
|
550
562
|
async reserveEffect(options: {
|
|
@@ -832,7 +844,7 @@ export class WorkflowRunStore {
|
|
|
832
844
|
options: InitializeWorkflowRunOptions = {},
|
|
833
845
|
): Promise<string> {
|
|
834
846
|
return await this.initializeRunFromSnapshot(
|
|
835
|
-
createDefinitionSnapshot(workflow),
|
|
847
|
+
createDefinitionSnapshot(compileWorkflowDefinition(workflow)),
|
|
836
848
|
workflow.name,
|
|
837
849
|
state,
|
|
838
850
|
options,
|
|
@@ -904,22 +916,12 @@ export class WorkflowRunStore {
|
|
|
904
916
|
[...runViewerTargets(state, traceEvent), ...this.viewerInspectorTargets(state.runId)],
|
|
905
917
|
now,
|
|
906
918
|
);
|
|
907
|
-
this.syncContinuationIdentity(state);
|
|
908
919
|
context.revision = revision;
|
|
909
920
|
return;
|
|
910
921
|
}
|
|
911
922
|
const definitionHash = this.state.putJson(snapshot, now);
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
? undefined
|
|
915
|
-
: this.state.connection
|
|
916
|
-
.prepare(
|
|
917
|
-
`SELECT root_run_id AS rootRunId, restart_number AS restartNumber
|
|
918
|
-
FROM runs WHERE run_id = ?`,
|
|
919
|
-
)
|
|
920
|
-
.get(state.parentRunId);
|
|
921
|
-
if (state.parentRunId !== undefined && !isRunLineageRow(parentLineage)) {
|
|
922
|
-
throw new Error(`Workflow parent run is missing: ${state.parentRunId}`);
|
|
923
|
+
if (state.parentRunId !== undefined) {
|
|
924
|
+
throw new Error("A restart must have an explicit host reservation");
|
|
923
925
|
}
|
|
924
926
|
this.state.connection
|
|
925
927
|
.prepare(
|
|
@@ -950,7 +952,7 @@ export class WorkflowRunStore {
|
|
|
950
952
|
.prepare(
|
|
951
953
|
`INSERT INTO runs(
|
|
952
954
|
run_id, resource_id, project_id, parent_run_id, root_run_id, lineage_kind,
|
|
953
|
-
restart_number,
|
|
955
|
+
restart_number, parent_run_revision, definition_digest,
|
|
954
956
|
workflow_ref, launch_options_hash, title, status, paused,
|
|
955
957
|
status_detail, input_hash, final_output_hash, error_hash,
|
|
956
958
|
created_at, updated_at, finished_at
|
|
@@ -959,10 +961,10 @@ export class WorkflowRunStore {
|
|
|
959
961
|
.run(
|
|
960
962
|
state.runId,
|
|
961
963
|
resourceId,
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
964
|
+
null,
|
|
965
|
+
state.runId,
|
|
966
|
+
null,
|
|
967
|
+
0,
|
|
966
968
|
definitionDigest,
|
|
967
969
|
state.workflowName,
|
|
968
970
|
launchOptionsHash,
|
|
@@ -979,7 +981,6 @@ export class WorkflowRunStore {
|
|
|
979
981
|
);
|
|
980
982
|
initializeViewerRun(this.state, state.runId, now);
|
|
981
983
|
this.insertRunSources(state.runId, source, state.workflowSources ?? []);
|
|
982
|
-
this.syncContinuationIdentity(state);
|
|
983
984
|
insertRunEvent(this.state, resourceId, 1, at, {
|
|
984
985
|
scope: "run",
|
|
985
986
|
type: "run_created",
|
|
@@ -1010,7 +1011,7 @@ export class WorkflowRunStore {
|
|
|
1010
1011
|
const now = Date.now();
|
|
1011
1012
|
return this.state.transaction(() => {
|
|
1012
1013
|
this.requireRunAcceptsSettings(options.runId, false);
|
|
1013
|
-
const originRunId =
|
|
1014
|
+
const originRunId = options.runId;
|
|
1014
1015
|
const scopeId = workflowSettingsScopeId(originRunId, options.mountPath, options.invocation);
|
|
1015
1016
|
const existing = this.settingsScopeRow(scopeId);
|
|
1016
1017
|
if (existing !== undefined) {
|
|
@@ -1106,7 +1107,7 @@ export class WorkflowRunStore {
|
|
|
1106
1107
|
}
|
|
1107
1108
|
|
|
1108
1109
|
private settingsScopesForRunView(runId: string): WorkflowSettingsScopeRecord[] {
|
|
1109
|
-
const originRunId =
|
|
1110
|
+
const originRunId = runId;
|
|
1110
1111
|
return this.state.connection
|
|
1111
1112
|
.prepare(
|
|
1112
1113
|
`SELECT s.scope_id AS scopeId, s.resource_id AS resourceId,
|
|
@@ -1128,7 +1129,7 @@ export class WorkflowRunStore {
|
|
|
1128
1129
|
runId: string,
|
|
1129
1130
|
range: WorkflowRunViewRange,
|
|
1130
1131
|
): WorkflowSettingsScopeRecord[] {
|
|
1131
|
-
const originRunId =
|
|
1132
|
+
const originRunId = runId;
|
|
1132
1133
|
return this.state.connection
|
|
1133
1134
|
.prepare(
|
|
1134
1135
|
`SELECT s.scope_id AS scopeId, s.resource_id AS resourceId,
|
|
@@ -1464,8 +1465,27 @@ export class WorkflowRunStore {
|
|
|
1464
1465
|
}
|
|
1465
1466
|
|
|
1466
1467
|
async prepareRunResume(runId: string): Promise<WorkflowRunState> {
|
|
1467
|
-
|
|
1468
|
+
let state = this.readRunState(runId);
|
|
1468
1469
|
if (state === null) throw new Error(`Cannot resume unreadable workflow run: ${runId}`);
|
|
1470
|
+
if (state.status === "waiting" && state.currentAttemptId !== undefined) {
|
|
1471
|
+
const checkpoint = await this.readCheckpoint(runId, state.currentAttemptId);
|
|
1472
|
+
if (checkpoint?.request.status === "settled") {
|
|
1473
|
+
if (state.waitingOn === undefined)
|
|
1474
|
+
throw new Error("Accepted checkpoint has no waiting node");
|
|
1475
|
+
await this.commitTransition(runId, {
|
|
1476
|
+
kind: "resumeInteraction",
|
|
1477
|
+
event: {
|
|
1478
|
+
scope: "node",
|
|
1479
|
+
type: "interaction_accepted",
|
|
1480
|
+
nodeId: state.waitingOn,
|
|
1481
|
+
attemptId: state.currentAttemptId,
|
|
1482
|
+
payload: { requestId: checkpoint.request.requestId },
|
|
1483
|
+
},
|
|
1484
|
+
});
|
|
1485
|
+
state = this.readRunState(runId);
|
|
1486
|
+
if (state === null) throw new Error(`Workflow run became unreadable: ${runId}`);
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1469
1489
|
if (state.status !== "running") {
|
|
1470
1490
|
throw new Error(`Cannot resume workflow run ${runId} with status ${state.status}`);
|
|
1471
1491
|
}
|
|
@@ -1519,37 +1539,39 @@ export class WorkflowRunStore {
|
|
|
1519
1539
|
delete loaded.state.currentNode;
|
|
1520
1540
|
delete loaded.state.currentAttemptId;
|
|
1521
1541
|
delete loaded.state.currentNodeStartedAt;
|
|
1522
|
-
delete loaded.state.
|
|
1542
|
+
delete loaded.state.currentNodeTimeoutMs;
|
|
1543
|
+
delete loaded.state.currentNodeElapsedMs;
|
|
1523
1544
|
delete loaded.state.currentSettingsScopeId;
|
|
1524
1545
|
delete loaded.state.currentSettingsChangeNumber;
|
|
1525
1546
|
delete loaded.state.currentSettingsHash;
|
|
1526
1547
|
delete loaded.state.statusDetail;
|
|
1527
1548
|
delete loaded.state.paused;
|
|
1528
|
-
await this.
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1549
|
+
await this.commitTransition(
|
|
1550
|
+
runId,
|
|
1551
|
+
executionTransition(loaded.state, {
|
|
1552
|
+
scope: "run",
|
|
1553
|
+
type: "run_interrupted",
|
|
1554
|
+
payload: { error: reason },
|
|
1555
|
+
}),
|
|
1556
|
+
);
|
|
1533
1557
|
return this.readRun(runId, { includeTrace: true });
|
|
1534
1558
|
}
|
|
1535
1559
|
|
|
1536
1560
|
async publishUpdate(
|
|
1537
1561
|
runId: string,
|
|
1538
|
-
state: WorkflowRunState,
|
|
1539
1562
|
nodeId: string,
|
|
1540
1563
|
attemptId: string,
|
|
1541
1564
|
update: WorkflowUpdateInput,
|
|
1542
1565
|
options: { signal?: AbortSignal } = {},
|
|
1543
1566
|
): Promise<{ event: WorkflowTraceEvent; record: WorkflowUpdateRecord }> {
|
|
1544
1567
|
return await this.withRunLock(runId, async () =>
|
|
1545
|
-
this.publishUpdateSynchronous(runId,
|
|
1568
|
+
this.publishUpdateSynchronous(runId, nodeId, attemptId, update, options),
|
|
1546
1569
|
);
|
|
1547
1570
|
}
|
|
1548
1571
|
|
|
1549
1572
|
/** Server-only synchronous form for an already serialized command transaction. */
|
|
1550
1573
|
publishUpdateSynchronous(
|
|
1551
1574
|
runId: string,
|
|
1552
|
-
state: WorkflowRunState,
|
|
1553
1575
|
nodeId: string,
|
|
1554
1576
|
attemptId: string,
|
|
1555
1577
|
update: WorkflowUpdateInput,
|
|
@@ -1558,6 +1580,14 @@ export class WorkflowRunStore {
|
|
|
1558
1580
|
if (options.signal?.aborted === true) {
|
|
1559
1581
|
throw options.signal.reason ?? new Error("workflow update attempt is no longer active");
|
|
1560
1582
|
}
|
|
1583
|
+
const state = this.readRunState(runId);
|
|
1584
|
+
if (
|
|
1585
|
+
state === null ||
|
|
1586
|
+
state.currentAttemptId !== attemptId ||
|
|
1587
|
+
(state.currentNode ?? state.waitingOn) !== nodeId
|
|
1588
|
+
) {
|
|
1589
|
+
throw new Error("Workflow update does not match the active attempt");
|
|
1590
|
+
}
|
|
1561
1591
|
const exists = (state.updates ?? []).some(
|
|
1562
1592
|
(record) => record.type === update.type && record.key === update.key,
|
|
1563
1593
|
);
|
|
@@ -1632,10 +1662,9 @@ export class WorkflowRunStore {
|
|
|
1632
1662
|
return { event: acceptedEvent, record: acceptedRecord };
|
|
1633
1663
|
}
|
|
1634
1664
|
|
|
1635
|
-
async
|
|
1665
|
+
async commitTransition(
|
|
1636
1666
|
runId: string,
|
|
1637
|
-
|
|
1638
|
-
event: WorkflowTraceEventDraft,
|
|
1667
|
+
transition: WorkflowTransition,
|
|
1639
1668
|
): Promise<WorkflowTraceEvent> {
|
|
1640
1669
|
return await this.withRunLock(runId, async () => {
|
|
1641
1670
|
let accepted: WorkflowTraceEvent | undefined;
|
|
@@ -1647,6 +1676,79 @@ export class WorkflowRunStore {
|
|
|
1647
1676
|
const now = Date.now();
|
|
1648
1677
|
const at = new Date(now).toISOString();
|
|
1649
1678
|
const snapshot = this.readDefinition(run.definitionHash);
|
|
1679
|
+
const current = this.materializeRunState(run, snapshot);
|
|
1680
|
+
const state = applyExecutionTransition(current, snapshot, transition, at);
|
|
1681
|
+
if (
|
|
1682
|
+
transition.kind === "finishAttempt" &&
|
|
1683
|
+
transition.step.nodeType === "checkpoint" &&
|
|
1684
|
+
transition.step.outcome === "ok"
|
|
1685
|
+
) {
|
|
1686
|
+
const request = requestForAttempt(this.state, runId, transition.step.attemptId);
|
|
1687
|
+
if (
|
|
1688
|
+
request === undefined ||
|
|
1689
|
+
request.status !== "settled" ||
|
|
1690
|
+
canonicalJson(acceptedRequestOutput(this.state, request)) !==
|
|
1691
|
+
canonicalJson(transition.step.output)
|
|
1692
|
+
) {
|
|
1693
|
+
throw new Error("Checkpoint completion requires its exact accepted response");
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
if (transition.kind === "waitForInput") {
|
|
1697
|
+
const node = snapshot.nodes[transition.event.nodeId ?? ""];
|
|
1698
|
+
if ((node?.humanDecision !== undefined) !== (transition.requestKind === "decision")) {
|
|
1699
|
+
throw new Error("Checkpoint response authority does not match its definition");
|
|
1700
|
+
}
|
|
1701
|
+
if (transition.requestKind === "decision") {
|
|
1702
|
+
const decision = transition.contract as unknown as HumanDecisionRequest;
|
|
1703
|
+
if (
|
|
1704
|
+
decision.runId !== runId ||
|
|
1705
|
+
decision.attemptId !== transition.event.attemptId ||
|
|
1706
|
+
decision.nodeId !== transition.event.nodeId ||
|
|
1707
|
+
decision.decisionId !== transition.requestId
|
|
1708
|
+
) {
|
|
1709
|
+
throw new Error("Human decision does not match the active checkpoint");
|
|
1710
|
+
}
|
|
1711
|
+
new HumanDecisionStore(this.databasePath, { state: this.state }).createRequest(
|
|
1712
|
+
decision,
|
|
1713
|
+
);
|
|
1714
|
+
}
|
|
1715
|
+
const binding = this.state.connection
|
|
1716
|
+
.prepare("SELECT origin_session_id AS sessionId FROM run_bindings WHERE run_id = ?")
|
|
1717
|
+
.get(runId) as { sessionId: string | null } | undefined;
|
|
1718
|
+
ensureWorkflowRequest(
|
|
1719
|
+
this.state,
|
|
1720
|
+
{
|
|
1721
|
+
requestId: transition.requestId,
|
|
1722
|
+
runId,
|
|
1723
|
+
attemptId: transition.event.attemptId ?? "",
|
|
1724
|
+
targetSessionId: binding?.sessionId ?? "",
|
|
1725
|
+
kind: transition.requestKind,
|
|
1726
|
+
contract: transition.contract,
|
|
1727
|
+
},
|
|
1728
|
+
now,
|
|
1729
|
+
);
|
|
1730
|
+
}
|
|
1731
|
+
if (
|
|
1732
|
+
transition.kind === "resume" &&
|
|
1733
|
+
transition.attemptId === undefined &&
|
|
1734
|
+
current.currentAttemptId !== undefined
|
|
1735
|
+
) {
|
|
1736
|
+
const request = this.state.connection
|
|
1737
|
+
.prepare(
|
|
1738
|
+
"SELECT 1 FROM interactive_requests WHERE attempt_id = ? AND status = 'pending'",
|
|
1739
|
+
)
|
|
1740
|
+
.get(current.currentAttemptId);
|
|
1741
|
+
if (request !== undefined)
|
|
1742
|
+
throw new Error("A pending request must resume its exact attempt");
|
|
1743
|
+
const retired = this.state.connection
|
|
1744
|
+
.prepare(
|
|
1745
|
+
`UPDATE node_attempts SET status = 'cancelled', finished_at = ?, updated_at = ?
|
|
1746
|
+
WHERE attempt_id = ? AND run_id = ? AND status = 'interrupted'`,
|
|
1747
|
+
)
|
|
1748
|
+
.run(now, now, current.currentAttemptId, runId);
|
|
1749
|
+
if (retired.changes !== 1) throw new Error("Only an interrupted attempt can be replaced");
|
|
1750
|
+
}
|
|
1751
|
+
const event = transition.event;
|
|
1650
1752
|
this.assertSettingsRouteCurrent(event, snapshot);
|
|
1651
1753
|
const traceEvent: WorkflowTraceEvent = { seq: revision, at, runId, ...event };
|
|
1652
1754
|
state.traceSeq = revision;
|
|
@@ -1655,6 +1757,15 @@ export class WorkflowRunStore {
|
|
|
1655
1757
|
this.persistRunState(run, state, revision, now);
|
|
1656
1758
|
this.transitionFollowUpsForRunState(state, event, now);
|
|
1657
1759
|
this.syncNodeAttempts(state, snapshot, now);
|
|
1760
|
+
if (transition.kind === "finishAttempt") {
|
|
1761
|
+
this.state.connection
|
|
1762
|
+
.prepare(
|
|
1763
|
+
`UPDATE interactive_requests SET consumed_at = ?, updated_at = ?
|
|
1764
|
+
WHERE run_id = ? AND attempt_id = ? AND status = 'settled'
|
|
1765
|
+
AND accepted_submission_id IS NOT NULL AND consumed_at IS NULL`,
|
|
1766
|
+
)
|
|
1767
|
+
.run(now, now, runId, transition.step.attemptId);
|
|
1768
|
+
}
|
|
1658
1769
|
recordViewerDeltas(this.state, runId, runViewerTargets(state, traceEvent), now);
|
|
1659
1770
|
this.snapshotLifecycle?.({
|
|
1660
1771
|
runId,
|
|
@@ -2205,16 +2316,6 @@ export class WorkflowRunStore {
|
|
|
2205
2316
|
return row?.errorHash == null ? null : this.readText(row.errorHash);
|
|
2206
2317
|
}
|
|
2207
2318
|
|
|
2208
|
-
readPresentationInstructions(runId: string): string {
|
|
2209
|
-
const row = this.state.connection
|
|
2210
|
-
.prepare("SELECT presentation_prompt_hash AS hash FROM runs WHERE run_id = ?")
|
|
2211
|
-
.get(runId) as { hash?: Buffer | null } | undefined;
|
|
2212
|
-
if (row?.hash == null) {
|
|
2213
|
-
return "Explain the final workflow result to the user in a normal response.";
|
|
2214
|
-
}
|
|
2215
|
-
return this.readText(row.hash);
|
|
2216
|
-
}
|
|
2217
|
-
|
|
2218
2319
|
readTerminalData(runId: string): WorkflowRunTerminalData | null {
|
|
2219
2320
|
const row = this.state.connection
|
|
2220
2321
|
.prepare(
|
|
@@ -2238,7 +2339,6 @@ export class WorkflowRunStore {
|
|
|
2238
2339
|
input: this.readRunInput(runId) as JsonValue,
|
|
2239
2340
|
finalOutput: this.readRunFinalOutput(runId),
|
|
2240
2341
|
error: this.readRunError(runId),
|
|
2241
|
-
presentationInstructions: this.readPresentationInstructions(runId),
|
|
2242
2342
|
restartNumber: row.restartNumber,
|
|
2243
2343
|
};
|
|
2244
2344
|
}
|
|
@@ -2249,6 +2349,17 @@ export class WorkflowRunStore {
|
|
|
2249
2349
|
return this.materializeRunState(row, this.readDefinition(row.definitionHash));
|
|
2250
2350
|
}
|
|
2251
2351
|
|
|
2352
|
+
runRevision(runId: string): number {
|
|
2353
|
+
const row = this.state.connection
|
|
2354
|
+
.prepare(
|
|
2355
|
+
`SELECT resources.revision FROM resources
|
|
2356
|
+
JOIN runs ON runs.resource_id = resources.resource_id WHERE runs.run_id = ?`,
|
|
2357
|
+
)
|
|
2358
|
+
.get(runId) as { revision: number } | undefined;
|
|
2359
|
+
if (row === undefined) throw new Error(`Workflow run not found: ${runId}`);
|
|
2360
|
+
return row.revision;
|
|
2361
|
+
}
|
|
2362
|
+
|
|
2252
2363
|
readRun(runId: string, options: ReadWorkflowRunOptions = {}): LoadedWorkflowRun | null {
|
|
2253
2364
|
const row = this.readRunRow(runId);
|
|
2254
2365
|
if (row === undefined) return null;
|
|
@@ -2276,7 +2387,7 @@ export class WorkflowRunStore {
|
|
|
2276
2387
|
const row = this.readRunRow(runId);
|
|
2277
2388
|
if (row === undefined) return null;
|
|
2278
2389
|
const segment = this.segmentRow(segmentIdFor(runId));
|
|
2279
|
-
const settingsOrigin =
|
|
2390
|
+
const settingsOrigin = runId;
|
|
2280
2391
|
const scalar = (sql: string, ...params: unknown[]): number => {
|
|
2281
2392
|
const count = this.state.connection.prepare(sql).get(...params);
|
|
2282
2393
|
return isCountRow(count) ? count.count : 0;
|
|
@@ -2652,7 +2763,10 @@ export class WorkflowRunStore {
|
|
|
2652
2763
|
state.runId,
|
|
2653
2764
|
);
|
|
2654
2765
|
if (update.changes !== 1) throw new Error(`Workflow run is missing: ${state.runId}`);
|
|
2655
|
-
if (state.
|
|
2766
|
+
if (state.paused || ["completed", "failed", "cancelled"].includes(state.status)) {
|
|
2767
|
+
closeRunTime(this.state, state.runId);
|
|
2768
|
+
}
|
|
2769
|
+
if (state.status !== "running" && state.status !== run.status) {
|
|
2656
2770
|
this.enqueueRunSettlementEffect(run.resourceId, expectedRevision, state, now);
|
|
2657
2771
|
}
|
|
2658
2772
|
}
|
|
@@ -2793,20 +2907,13 @@ export class WorkflowRunStore {
|
|
|
2793
2907
|
initialSettings: InitialWorkflowSettingsScope[],
|
|
2794
2908
|
now: number,
|
|
2795
2909
|
): void {
|
|
2796
|
-
if (state.parentRunId !== undefined) {
|
|
2797
|
-
this.state.connection
|
|
2798
|
-
.prepare(
|
|
2799
|
-
`UPDATE workflow_settings SET active_run_id = ?, updated_at = ? WHERE active_run_id = ?`,
|
|
2800
|
-
)
|
|
2801
|
-
.run(state.runId, now, state.parentRunId);
|
|
2802
|
-
}
|
|
2803
2910
|
const existing = this.state.connection
|
|
2804
2911
|
.prepare("SELECT COUNT(*) AS count FROM workflow_settings WHERE active_run_id = ?")
|
|
2805
2912
|
.get(state.runId);
|
|
2806
2913
|
const hasSettings = isCountRow(existing) && existing.count > 0;
|
|
2807
2914
|
if (hasSettings) return;
|
|
2808
2915
|
for (const scope of initialSettings) {
|
|
2809
|
-
const originRunId =
|
|
2916
|
+
const originRunId = state.runId;
|
|
2810
2917
|
const scopeId = workflowSettingsScopeId(originRunId, scope.mountPath, scope.invocation);
|
|
2811
2918
|
const resourceId = this.mutations.ensureResource("settings", scopeId, now);
|
|
2812
2919
|
const settingsHash = this.state.putJson(scope.settings, now);
|
|
@@ -2833,19 +2940,6 @@ export class WorkflowRunStore {
|
|
|
2833
2940
|
}
|
|
2834
2941
|
}
|
|
2835
2942
|
|
|
2836
|
-
private logicalOriginRunId(runId: string): string {
|
|
2837
|
-
let current = runId;
|
|
2838
|
-
for (let depth = 0; depth < 100; depth += 1) {
|
|
2839
|
-
const row = this.state.connection
|
|
2840
|
-
.prepare("SELECT parent_run_id AS parentRunId FROM runs WHERE run_id = ?")
|
|
2841
|
-
.get(current);
|
|
2842
|
-
if (!isParentRunRow(row)) return current;
|
|
2843
|
-
if (row.parentRunId === null) return current;
|
|
2844
|
-
current = row.parentRunId;
|
|
2845
|
-
}
|
|
2846
|
-
throw new Error(`Workflow continuation chain is too deep for run ${runId}`);
|
|
2847
|
-
}
|
|
2848
|
-
|
|
2849
2943
|
private requireRunAcceptsSettings(runId: string, allowWaiting: boolean): void {
|
|
2850
2944
|
const row = this.state.connection
|
|
2851
2945
|
.prepare("SELECT status FROM runs WHERE run_id = ?")
|
|
@@ -3101,15 +3195,7 @@ export class WorkflowRunStore {
|
|
|
3101
3195
|
const rows = this.state.connection
|
|
3102
3196
|
.prepare(
|
|
3103
3197
|
`${FOLLOW_UP_ROW_SELECT}
|
|
3104
|
-
WHERE f.run_id
|
|
3105
|
-
WITH RECURSIVE ancestors(run_id, parent_run_id) AS (
|
|
3106
|
-
SELECT run_id, parent_run_id FROM runs WHERE run_id = ?
|
|
3107
|
-
UNION ALL
|
|
3108
|
-
SELECT parent.run_id, parent.parent_run_id
|
|
3109
|
-
FROM runs parent JOIN ancestors ON ancestors.parent_run_id = parent.run_id
|
|
3110
|
-
)
|
|
3111
|
-
SELECT run_id FROM ancestors
|
|
3112
|
-
) AND f.status = 'queued'`,
|
|
3198
|
+
WHERE f.run_id = ? AND f.status = 'queued'`,
|
|
3113
3199
|
)
|
|
3114
3200
|
.all(state.runId)
|
|
3115
3201
|
.filter(isFollowUpRow);
|
|
@@ -3144,7 +3230,8 @@ export class WorkflowRunStore {
|
|
|
3144
3230
|
snapshot: WorkflowDefinitionSnapshot,
|
|
3145
3231
|
now: number,
|
|
3146
3232
|
): void {
|
|
3147
|
-
for (const step of state.steps
|
|
3233
|
+
for (const step of state.steps) {
|
|
3234
|
+
closeAttemptTime(this.state, step.attemptId);
|
|
3148
3235
|
const promptEntryId = this.findAttemptPromptEntry(state.runId, step.attemptId);
|
|
3149
3236
|
const promptHash =
|
|
3150
3237
|
promptEntryId === undefined && step.prompt !== null
|
|
@@ -3219,25 +3306,20 @@ export class WorkflowRunStore {
|
|
|
3219
3306
|
}
|
|
3220
3307
|
if (state.currentAttemptId !== undefined && state.currentNode !== undefined) {
|
|
3221
3308
|
this.ensureAttempt(state, state.currentNode, state.currentAttemptId, now, snapshot);
|
|
3222
|
-
if (state.
|
|
3223
|
-
const deadlineAt =
|
|
3224
|
-
state.currentNodeDeadlineAt === null ? null : Date.parse(state.currentNodeDeadlineAt);
|
|
3225
|
-
if (deadlineAt !== null && !Number.isFinite(deadlineAt)) {
|
|
3226
|
-
throw new Error("Workflow node deadline is invalid");
|
|
3227
|
-
}
|
|
3309
|
+
if (state.currentNodeTimeoutMs !== undefined) {
|
|
3228
3310
|
this.state.connection
|
|
3229
3311
|
.prepare(
|
|
3230
|
-
`UPDATE node_attempts SET
|
|
3231
|
-
WHERE attempt_id = ? AND run_id = ?
|
|
3312
|
+
`UPDATE node_attempts SET timeout_ms = ?, updated_at = ?
|
|
3313
|
+
WHERE attempt_id = ? AND run_id = ? AND timeout_ms IS NULL
|
|
3232
3314
|
AND status IN ('pending', 'running', 'waiting', 'interrupted')`,
|
|
3233
3315
|
)
|
|
3234
|
-
.run(
|
|
3316
|
+
.run(state.currentNodeTimeoutMs ?? 0, now, state.currentAttemptId, state.runId);
|
|
3235
3317
|
}
|
|
3236
3318
|
}
|
|
3237
|
-
this.syncRunSteps(state
|
|
3319
|
+
this.syncRunSteps(state);
|
|
3238
3320
|
}
|
|
3239
3321
|
|
|
3240
|
-
private syncRunSteps(state: WorkflowRunState
|
|
3322
|
+
private syncRunSteps(state: WorkflowRunState): void {
|
|
3241
3323
|
const existingRows = this.state.connection
|
|
3242
3324
|
.prepare(
|
|
3243
3325
|
`SELECT step_index AS stepIndex, attempt_id AS attemptId
|
|
@@ -3257,18 +3339,16 @@ export class WorkflowRunStore {
|
|
|
3257
3339
|
const step = state.steps[stepIndex];
|
|
3258
3340
|
/* istanbul ignore if -- array index follows a checked bound */
|
|
3259
3341
|
if (step === undefined) throw new Error("Workflow run step became unavailable");
|
|
3260
|
-
const
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
? this.state.putJson(step.output, now)
|
|
3265
|
-
: null;
|
|
3342
|
+
const attempt = this.state.connection
|
|
3343
|
+
.prepare("SELECT run_id AS runId FROM node_attempts WHERE attempt_id = ?")
|
|
3344
|
+
.get(step.attemptId) as { runId: string } | undefined;
|
|
3345
|
+
if (attempt?.runId !== state.runId) throw new Error("A run cannot own another run's step");
|
|
3266
3346
|
this.state.connection
|
|
3267
3347
|
.prepare(
|
|
3268
|
-
`INSERT INTO run_steps(run_id, step_index, attempt_id
|
|
3269
|
-
VALUES (?, ?,
|
|
3348
|
+
`INSERT INTO run_steps(run_id, step_index, attempt_id)
|
|
3349
|
+
VALUES (?, ?, ?)`,
|
|
3270
3350
|
)
|
|
3271
|
-
.run(state.runId, stepIndex, step.attemptId
|
|
3351
|
+
.run(state.runId, stepIndex, step.attemptId);
|
|
3272
3352
|
}
|
|
3273
3353
|
}
|
|
3274
3354
|
|
|
@@ -3341,31 +3421,6 @@ export class WorkflowRunStore {
|
|
|
3341
3421
|
return isEntryIdentityRow(row) ? row.entryId : undefined;
|
|
3342
3422
|
}
|
|
3343
3423
|
|
|
3344
|
-
private readAttemptOutput(attemptId: string): unknown {
|
|
3345
|
-
const row = this.state.connection
|
|
3346
|
-
.prepare(
|
|
3347
|
-
`SELECT a.output_hash AS outputHash, a.receipt_hash AS receiptHash,
|
|
3348
|
-
e.entry_hash AS responseEntryHash
|
|
3349
|
-
FROM node_attempts a
|
|
3350
|
-
LEFT JOIN attempt_entries l ON l.attempt_id = a.attempt_id AND l.role = 'response'
|
|
3351
|
-
LEFT JOIN session_entries e
|
|
3352
|
-
ON e.segment_id = l.segment_id AND e.entry_id = l.entry_id
|
|
3353
|
-
WHERE a.attempt_id = ?`,
|
|
3354
|
-
)
|
|
3355
|
-
.get(attemptId);
|
|
3356
|
-
if (!isAttemptValueRow(row)) {
|
|
3357
|
-
throw new Error(`Workflow node attempt is missing: ${attemptId}`);
|
|
3358
|
-
}
|
|
3359
|
-
if (row.outputHash !== null) return this.state.readJson(row.outputHash);
|
|
3360
|
-
if (row.responseEntryHash === null) return null;
|
|
3361
|
-
const receipt =
|
|
3362
|
-
row.receiptHash === null ? {} : this.readJsonAs<StoredAttemptReceipt>(row.receiptHash);
|
|
3363
|
-
return assistantOutputFromEntry(
|
|
3364
|
-
this.state.readJson(row.responseEntryHash),
|
|
3365
|
-
receipt.assistantMessage?.maxChars,
|
|
3366
|
-
);
|
|
3367
|
-
}
|
|
3368
|
-
|
|
3369
3424
|
private ensureAttempt(
|
|
3370
3425
|
state: WorkflowRunState,
|
|
3371
3426
|
nodeId: string,
|
|
@@ -3473,34 +3528,6 @@ export class WorkflowRunStore {
|
|
|
3473
3528
|
}
|
|
3474
3529
|
}
|
|
3475
3530
|
|
|
3476
|
-
private syncContinuationIdentity(state: WorkflowRunState): void {
|
|
3477
|
-
if (state.parentRunId === undefined || state.humanDecision === undefined) return;
|
|
3478
|
-
const createdAt = Date.parse(state.humanDecision.acceptedAt);
|
|
3479
|
-
this.state.connection
|
|
3480
|
-
.prepare(
|
|
3481
|
-
`INSERT INTO continuations(
|
|
3482
|
-
decision_id, parent_run_id, continuation_run_id, created_at
|
|
3483
|
-
) VALUES (?, ?, ?, ?)
|
|
3484
|
-
ON CONFLICT(decision_id) DO NOTHING`,
|
|
3485
|
-
)
|
|
3486
|
-
.run(state.humanDecision.decisionId, state.parentRunId, state.runId, createdAt);
|
|
3487
|
-
const row = this.state.connection
|
|
3488
|
-
.prepare(
|
|
3489
|
-
`SELECT parent_run_id AS parentRunId, continuation_run_id AS continuationRunId,
|
|
3490
|
-
created_at AS createdAt
|
|
3491
|
-
FROM continuations WHERE decision_id = ?`,
|
|
3492
|
-
)
|
|
3493
|
-
.get(state.humanDecision.decisionId);
|
|
3494
|
-
if (
|
|
3495
|
-
!isContinuationIdentityRow(row) ||
|
|
3496
|
-
row.parentRunId !== state.parentRunId ||
|
|
3497
|
-
row.continuationRunId !== state.runId ||
|
|
3498
|
-
row.createdAt !== createdAt
|
|
3499
|
-
) {
|
|
3500
|
-
throw new Error("Immutable human decision continuation conflicts");
|
|
3501
|
-
}
|
|
3502
|
-
}
|
|
3503
|
-
|
|
3504
3531
|
private readRunRow(runId: string): RunRow | undefined {
|
|
3505
3532
|
const row = this.state.connection
|
|
3506
3533
|
.prepare(
|
|
@@ -3544,7 +3571,6 @@ export class WorkflowRunStore {
|
|
|
3544
3571
|
visibleUpdates: WorkflowUpdateRecord[],
|
|
3545
3572
|
): WorkflowRunState {
|
|
3546
3573
|
const sources = this.readRunSources(row.runId, snapshot);
|
|
3547
|
-
const carriedStepCount = this.carriedStepCount(row.runId);
|
|
3548
3574
|
const activeAttempt =
|
|
3549
3575
|
row.status === "running" || row.status === "waiting"
|
|
3550
3576
|
? this.readActiveAttempt(row.runId)
|
|
@@ -3579,13 +3605,10 @@ export class WorkflowRunStore {
|
|
|
3579
3605
|
runId: row.runId,
|
|
3580
3606
|
workflowName: row.workflowRef,
|
|
3581
3607
|
...(row.parentRunId === null ? {} : { parentRunId: row.parentRunId }),
|
|
3582
|
-
...(carriedStepCount === 0 ? {} : { carriedStepCount }),
|
|
3583
3608
|
...(row.title === null ? {} : { runTitle: row.title }),
|
|
3584
3609
|
...(sources.root === undefined ? {} : { workflowSource: sources.root }),
|
|
3585
3610
|
...(sources.mounted.length === 0 ? {} : { workflowSources: sources.mounted }),
|
|
3586
|
-
|
|
3587
|
-
? { definitionDigest: `sha256:${row.definitionDigest.toString("hex")}` }
|
|
3588
|
-
: {}),
|
|
3611
|
+
definitionDigest: `sha256:${row.definitionDigest.toString("hex")}`,
|
|
3589
3612
|
startedAt: new Date(row.createdAt).toISOString(),
|
|
3590
3613
|
...(row.finishedAt === null ? {} : { finishedAt: new Date(row.finishedAt).toISOString() }),
|
|
3591
3614
|
updatedAt: new Date(row.updatedAt).toISOString(),
|
|
@@ -3600,13 +3623,11 @@ export class WorkflowRunStore {
|
|
|
3600
3623
|
...(activeAttempt?.startedAt === null || activeAttempt === undefined
|
|
3601
3624
|
? {}
|
|
3602
3625
|
: { currentNodeStartedAt: new Date(activeAttempt.startedAt).toISOString() }),
|
|
3603
|
-
...(activeAttempt === undefined
|
|
3626
|
+
...(activeAttempt === undefined || activeAttempt.timeoutMs === null
|
|
3604
3627
|
? {}
|
|
3605
3628
|
: {
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
? null
|
|
3609
|
-
: new Date(activeAttempt.deadlineAt).toISOString(),
|
|
3629
|
+
currentNodeTimeoutMs: activeAttempt.timeoutMs === 0 ? null : activeAttempt.timeoutMs,
|
|
3630
|
+
currentNodeElapsedMs: activeAttempt.activeElapsedMs,
|
|
3610
3631
|
}),
|
|
3611
3632
|
...savedCurrentSettingsBinding(
|
|
3612
3633
|
activeAttempt?.settingsScopeId ?? null,
|
|
@@ -3693,7 +3714,7 @@ export class WorkflowRunStore {
|
|
|
3693
3714
|
: row.promptHash === null
|
|
3694
3715
|
? null
|
|
3695
3716
|
: this.readText(row.promptHash);
|
|
3696
|
-
const outputHash = row.
|
|
3717
|
+
const outputHash = row.outputHash;
|
|
3697
3718
|
const output =
|
|
3698
3719
|
outputHash !== null
|
|
3699
3720
|
? this.state.readJson(outputHash)
|
|
@@ -3763,23 +3784,13 @@ export class WorkflowRunStore {
|
|
|
3763
3784
|
return { ...(root === undefined ? {} : { root }), mounted };
|
|
3764
3785
|
}
|
|
3765
3786
|
|
|
3766
|
-
private carriedStepCount(runId: string): number {
|
|
3767
|
-
const row = this.state.connection
|
|
3768
|
-
.prepare(
|
|
3769
|
-
`SELECT count(*) AS count
|
|
3770
|
-
FROM run_steps s JOIN node_attempts a ON a.attempt_id = s.attempt_id
|
|
3771
|
-
WHERE s.run_id = ? AND a.run_id <> s.run_id`,
|
|
3772
|
-
)
|
|
3773
|
-
.get(runId);
|
|
3774
|
-
return isCountRow(row) ? row.count : 0;
|
|
3775
|
-
}
|
|
3776
|
-
|
|
3777
3787
|
private readActiveAttempt(runId: string):
|
|
3778
3788
|
| {
|
|
3779
3789
|
attemptId: string;
|
|
3780
3790
|
nodeId: string;
|
|
3781
3791
|
startedAt: number | null;
|
|
3782
|
-
|
|
3792
|
+
timeoutMs: number | null;
|
|
3793
|
+
activeElapsedMs: number;
|
|
3783
3794
|
settingsScopeId: string | null;
|
|
3784
3795
|
settingsChangeNumber: number | null;
|
|
3785
3796
|
settingsHash: Buffer | null;
|
|
@@ -3788,7 +3799,10 @@ export class WorkflowRunStore {
|
|
|
3788
3799
|
const row = this.state.connection
|
|
3789
3800
|
.prepare(
|
|
3790
3801
|
`SELECT attempt_id AS attemptId, node_id AS nodeId, started_at AS startedAt,
|
|
3791
|
-
|
|
3802
|
+
timeout_ms AS timeoutMs,
|
|
3803
|
+
(SELECT COALESCE(SUM(elapsed_ms), 0) FROM attempt_active_intervals i
|
|
3804
|
+
WHERE i.attempt_id = node_attempts.attempt_id) AS activeElapsedMs,
|
|
3805
|
+
settings_scope_id AS settingsScopeId,
|
|
3792
3806
|
settings_change_number AS settingsChangeNumber,
|
|
3793
3807
|
settings_hash AS settingsHash
|
|
3794
3808
|
FROM node_attempts
|
|
@@ -3798,16 +3812,19 @@ export class WorkflowRunStore {
|
|
|
3798
3812
|
return isActiveAttemptRow(row) ? row : undefined;
|
|
3799
3813
|
}
|
|
3800
3814
|
|
|
3801
|
-
private readHumanDecisionReceipt(
|
|
3815
|
+
private readHumanDecisionReceipt(
|
|
3816
|
+
runId: string,
|
|
3817
|
+
decisionId?: string,
|
|
3818
|
+
): HumanDecisionReceipt | undefined {
|
|
3802
3819
|
const row = this.state.connection
|
|
3803
3820
|
.prepare(
|
|
3804
3821
|
`SELECT d.request_hash AS requestHash, r.response_hash AS responseHash
|
|
3805
|
-
FROM
|
|
3806
|
-
JOIN
|
|
3807
|
-
|
|
3808
|
-
|
|
3822
|
+
FROM human_decisions d
|
|
3823
|
+
JOIN human_decision_resolutions r ON r.decision_id = d.decision_id
|
|
3824
|
+
WHERE d.run_id = ? AND r.outcome = 'accepted' AND (? IS NULL OR d.decision_id = ?)
|
|
3825
|
+
ORDER BY r.resolved_at DESC, d.decision_id DESC LIMIT 1`,
|
|
3809
3826
|
)
|
|
3810
|
-
.get(runId);
|
|
3827
|
+
.get(runId, decisionId ?? null, decisionId ?? null);
|
|
3811
3828
|
if (!isDecisionReceiptRow(row)) return undefined;
|
|
3812
3829
|
const request = this.readJsonAs<Record<string, unknown>>(row.requestHash);
|
|
3813
3830
|
const decision = this.readJsonAs<Record<string, unknown>>(row.responseHash);
|
|
@@ -4210,8 +4227,8 @@ function runViewerTargets(
|
|
|
4210
4227
|
},
|
|
4211
4228
|
{
|
|
4212
4229
|
op: "add",
|
|
4213
|
-
path: "/state/
|
|
4214
|
-
value: state.
|
|
4230
|
+
path: "/state/currentNodeTimeoutMs",
|
|
4231
|
+
value: state.currentNodeTimeoutMs ?? null,
|
|
4215
4232
|
},
|
|
4216
4233
|
{
|
|
4217
4234
|
op: "add",
|
|
@@ -4741,18 +4758,6 @@ function isFollowUpRow(value: unknown): value is FollowUpRow {
|
|
|
4741
4758
|
);
|
|
4742
4759
|
}
|
|
4743
4760
|
|
|
4744
|
-
function isParentRunRow(value: unknown): value is { parentRunId: string | null } {
|
|
4745
|
-
return isRecord(value) && (typeof value.parentRunId === "string" || value.parentRunId === null);
|
|
4746
|
-
}
|
|
4747
|
-
|
|
4748
|
-
function isRunLineageRow(value: unknown): value is { rootRunId: string; restartNumber: number } {
|
|
4749
|
-
return (
|
|
4750
|
-
isRecord(value) &&
|
|
4751
|
-
typeof value.rootRunId === "string" &&
|
|
4752
|
-
typeof value.restartNumber === "number"
|
|
4753
|
-
);
|
|
4754
|
-
}
|
|
4755
|
-
|
|
4756
4761
|
function isStatusRow(value: unknown): value is { status: string } {
|
|
4757
4762
|
return isRecord(value) && typeof value.status === "string";
|
|
4758
4763
|
}
|
|
@@ -4891,19 +4896,12 @@ function isEntryIdentityRow(value: unknown): value is { entryId: string } {
|
|
|
4891
4896
|
return isRecord(value) && typeof value.entryId === "string";
|
|
4892
4897
|
}
|
|
4893
4898
|
|
|
4894
|
-
function isAttemptValueRow(value: unknown): value is {
|
|
4895
|
-
outputHash: Buffer | null;
|
|
4896
|
-
receiptHash: Buffer | null;
|
|
4897
|
-
responseEntryHash: Buffer | null;
|
|
4898
|
-
} {
|
|
4899
|
-
return isRecord(value);
|
|
4900
|
-
}
|
|
4901
|
-
|
|
4902
4899
|
function isActiveAttemptRow(value: unknown): value is {
|
|
4903
4900
|
attemptId: string;
|
|
4904
4901
|
nodeId: string;
|
|
4905
4902
|
startedAt: number | null;
|
|
4906
|
-
|
|
4903
|
+
timeoutMs: number | null;
|
|
4904
|
+
activeElapsedMs: number;
|
|
4907
4905
|
settingsScopeId: string | null;
|
|
4908
4906
|
settingsChangeNumber: number | null;
|
|
4909
4907
|
settingsHash: Buffer | null;
|
|
@@ -4920,14 +4918,6 @@ function isDecisionReceiptRow(value: unknown): value is {
|
|
|
4920
4918
|
);
|
|
4921
4919
|
}
|
|
4922
4920
|
|
|
4923
|
-
function isContinuationIdentityRow(value: unknown): value is {
|
|
4924
|
-
parentRunId: string;
|
|
4925
|
-
continuationRunId: string;
|
|
4926
|
-
createdAt: number;
|
|
4927
|
-
} {
|
|
4928
|
-
return isRecord(value);
|
|
4929
|
-
}
|
|
4930
|
-
|
|
4931
4921
|
function isUpdateRow(value: unknown): value is UpdateRow {
|
|
4932
4922
|
return isRecord(value);
|
|
4933
4923
|
}
|