@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/dist/workflows/store.js
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 } from "../state/json.js";
|
|
4
5
|
import { StateMutationStore, StaleResourceError, resourceIdFor, tokenHash, } from "../state/mutation.js";
|
|
@@ -22,7 +23,6 @@ const STEP_ROW_SELECT = `
|
|
|
22
23
|
SELECT s.step_index AS stepIndex, a.attempt_id AS attemptId, a.node_id AS nodeId,
|
|
23
24
|
a.node_type AS nodeType, a.status,
|
|
24
25
|
a.prompt_hash AS promptHash, a.output_hash AS outputHash,
|
|
25
|
-
s.output_override_hash AS outputOverrideHash,
|
|
26
26
|
a.receipt_hash AS receiptHash, a.error_hash AS errorHash,
|
|
27
27
|
prompt_entry.entry_hash AS promptEntryHash,
|
|
28
28
|
response_entry.entry_hash AS responseEntryHash,
|
|
@@ -63,6 +63,9 @@ export function createRunId(workflowName, now = new Date()) {
|
|
|
63
63
|
const safeName = workflowName.replace(/[^A-Za-z0-9._-]/g, "-").slice(0, 80) || "workflow";
|
|
64
64
|
return `${now.toISOString().replace(/[-:.]/g, "").replace("T", "T").replace("Z", "Z")}-${safeName}-${randomUUID().slice(0, 8)}`;
|
|
65
65
|
}
|
|
66
|
+
/** The state boundary used by workflow code, including out-of-process runners. */
|
|
67
|
+
import { acceptedRequestOutput, ensureWorkflowRequest, requestForAttempt, } from "./requests.js";
|
|
68
|
+
import { applyExecutionTransition, executionTransition, } from "./transitions.js";
|
|
66
69
|
export class WorkflowRunStore {
|
|
67
70
|
databasePath;
|
|
68
71
|
state;
|
|
@@ -105,11 +108,23 @@ export class WorkflowRunStore {
|
|
|
105
108
|
}
|
|
106
109
|
return revision;
|
|
107
110
|
}
|
|
108
|
-
async
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
async readCheckpoint(runId, attemptId) {
|
|
112
|
+
const request = requestForAttempt(this.state, runId, attemptId);
|
|
113
|
+
if (request === undefined)
|
|
114
|
+
return undefined;
|
|
115
|
+
if (request.kind !== "checkpoint" && request.kind !== "decision") {
|
|
116
|
+
throw new Error("The attempt does not own a checkpoint request");
|
|
117
|
+
}
|
|
118
|
+
if (request.status !== "settled")
|
|
119
|
+
return { request };
|
|
120
|
+
const output = acceptedRequestOutput(this.state, request);
|
|
121
|
+
const humanDecision = request.kind === "decision"
|
|
122
|
+
? this.readHumanDecisionReceipt(runId, request.requestId)
|
|
123
|
+
: undefined;
|
|
124
|
+
if (request.kind === "decision" && humanDecision === undefined) {
|
|
125
|
+
throw new Error("The checkpoint has no verified human decision receipt");
|
|
126
|
+
}
|
|
127
|
+
return { request, output, ...(humanDecision === undefined ? {} : { humanDecision }) };
|
|
113
128
|
}
|
|
114
129
|
async reserveEffect(options) {
|
|
115
130
|
return await this.withRunLock(options.runId, async () => this.state.transaction(() => {
|
|
@@ -331,19 +346,12 @@ export class WorkflowRunStore {
|
|
|
331
346
|
}
|
|
332
347
|
this.syncNodeAttempts(state, snapshot, now);
|
|
333
348
|
recordViewerDeltas(this.state, state.runId, [...runViewerTargets(state, traceEvent), ...this.viewerInspectorTargets(state.runId)], now);
|
|
334
|
-
this.syncContinuationIdentity(state);
|
|
335
349
|
context.revision = revision;
|
|
336
350
|
return;
|
|
337
351
|
}
|
|
338
352
|
const definitionHash = this.state.putJson(snapshot, now);
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
: this.state.connection
|
|
342
|
-
.prepare(`SELECT root_run_id AS rootRunId, restart_number AS restartNumber
|
|
343
|
-
FROM runs WHERE run_id = ?`)
|
|
344
|
-
.get(state.parentRunId);
|
|
345
|
-
if (state.parentRunId !== undefined && !isRunLineageRow(parentLineage)) {
|
|
346
|
-
throw new Error(`Workflow parent run is missing: ${state.parentRunId}`);
|
|
353
|
+
if (state.parentRunId !== undefined) {
|
|
354
|
+
throw new Error("A restart must have an explicit host reservation");
|
|
347
355
|
}
|
|
348
356
|
this.state.connection
|
|
349
357
|
.prepare(`INSERT INTO workflow_definitions(
|
|
@@ -368,15 +376,14 @@ export class WorkflowRunStore {
|
|
|
368
376
|
this.state.connection
|
|
369
377
|
.prepare(`INSERT INTO runs(
|
|
370
378
|
run_id, resource_id, project_id, parent_run_id, root_run_id, lineage_kind,
|
|
371
|
-
restart_number,
|
|
379
|
+
restart_number, parent_run_revision, definition_digest,
|
|
372
380
|
workflow_ref, launch_options_hash, title, status, paused,
|
|
373
381
|
status_detail, input_hash, final_output_hash, error_hash,
|
|
374
382
|
created_at, updated_at, finished_at
|
|
375
383
|
) VALUES (?, ?, NULL, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
376
|
-
.run(state.runId, resourceId,
|
|
384
|
+
.run(state.runId, resourceId, null, state.runId, null, 0, definitionDigest, state.workflowName, launchOptionsHash, state.runTitle ?? null, state.status, state.paused === true ? 1 : 0, state.statusDetail ?? null, inputHash, finalOutputHash, errorHash, Date.parse(state.startedAt), now, state.finishedAt === undefined ? null : Date.parse(state.finishedAt));
|
|
377
385
|
initializeViewerRun(this.state, state.runId, now);
|
|
378
386
|
this.insertRunSources(state.runId, source, state.workflowSources ?? []);
|
|
379
|
-
this.syncContinuationIdentity(state);
|
|
380
387
|
insertRunEvent(this.state, resourceId, 1, at, {
|
|
381
388
|
scope: "run",
|
|
382
389
|
type: "run_created",
|
|
@@ -396,7 +403,7 @@ export class WorkflowRunStore {
|
|
|
396
403
|
const now = Date.now();
|
|
397
404
|
return this.state.transaction(() => {
|
|
398
405
|
this.requireRunAcceptsSettings(options.runId, false);
|
|
399
|
-
const originRunId =
|
|
406
|
+
const originRunId = options.runId;
|
|
400
407
|
const scopeId = workflowSettingsScopeId(originRunId, options.mountPath, options.invocation);
|
|
401
408
|
const existing = this.settingsScopeRow(scopeId);
|
|
402
409
|
if (existing !== undefined) {
|
|
@@ -462,7 +469,7 @@ export class WorkflowRunStore {
|
|
|
462
469
|
.map((row) => this.settingsScopeRecord(row));
|
|
463
470
|
}
|
|
464
471
|
settingsScopesForRunView(runId) {
|
|
465
|
-
const originRunId =
|
|
472
|
+
const originRunId = runId;
|
|
466
473
|
return this.state.connection
|
|
467
474
|
.prepare(`SELECT s.scope_id AS scopeId, s.resource_id AS resourceId,
|
|
468
475
|
s.origin_run_id AS originRunId, s.active_run_id AS activeRunId,
|
|
@@ -478,7 +485,7 @@ export class WorkflowRunStore {
|
|
|
478
485
|
.map((row) => this.settingsScopeRecord(row));
|
|
479
486
|
}
|
|
480
487
|
readSettingsScopeRange(runId, range) {
|
|
481
|
-
const originRunId =
|
|
488
|
+
const originRunId = runId;
|
|
482
489
|
return this.state.connection
|
|
483
490
|
.prepare(`SELECT s.scope_id AS scopeId, s.resource_id AS resourceId,
|
|
484
491
|
s.origin_run_id AS originRunId, s.active_run_id AS activeRunId,
|
|
@@ -712,9 +719,29 @@ export class WorkflowRunStore {
|
|
|
712
719
|
return { runId, originSessionId, followUps };
|
|
713
720
|
}
|
|
714
721
|
async prepareRunResume(runId) {
|
|
715
|
-
|
|
722
|
+
let state = this.readRunState(runId);
|
|
716
723
|
if (state === null)
|
|
717
724
|
throw new Error(`Cannot resume unreadable workflow run: ${runId}`);
|
|
725
|
+
if (state.status === "waiting" && state.currentAttemptId !== undefined) {
|
|
726
|
+
const checkpoint = await this.readCheckpoint(runId, state.currentAttemptId);
|
|
727
|
+
if (checkpoint?.request.status === "settled") {
|
|
728
|
+
if (state.waitingOn === undefined)
|
|
729
|
+
throw new Error("Accepted checkpoint has no waiting node");
|
|
730
|
+
await this.commitTransition(runId, {
|
|
731
|
+
kind: "resumeInteraction",
|
|
732
|
+
event: {
|
|
733
|
+
scope: "node",
|
|
734
|
+
type: "interaction_accepted",
|
|
735
|
+
nodeId: state.waitingOn,
|
|
736
|
+
attemptId: state.currentAttemptId,
|
|
737
|
+
payload: { requestId: checkpoint.request.requestId },
|
|
738
|
+
},
|
|
739
|
+
});
|
|
740
|
+
state = this.readRunState(runId);
|
|
741
|
+
if (state === null)
|
|
742
|
+
throw new Error(`Workflow run became unreadable: ${runId}`);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
718
745
|
if (state.status !== "running") {
|
|
719
746
|
throw new Error(`Cannot resume workflow run ${runId} with status ${state.status}`);
|
|
720
747
|
}
|
|
@@ -764,27 +791,34 @@ export class WorkflowRunStore {
|
|
|
764
791
|
delete loaded.state.currentNode;
|
|
765
792
|
delete loaded.state.currentAttemptId;
|
|
766
793
|
delete loaded.state.currentNodeStartedAt;
|
|
767
|
-
delete loaded.state.
|
|
794
|
+
delete loaded.state.currentNodeTimeoutMs;
|
|
795
|
+
delete loaded.state.currentNodeElapsedMs;
|
|
768
796
|
delete loaded.state.currentSettingsScopeId;
|
|
769
797
|
delete loaded.state.currentSettingsChangeNumber;
|
|
770
798
|
delete loaded.state.currentSettingsHash;
|
|
771
799
|
delete loaded.state.statusDetail;
|
|
772
800
|
delete loaded.state.paused;
|
|
773
|
-
await this.
|
|
801
|
+
await this.commitTransition(runId, executionTransition(loaded.state, {
|
|
774
802
|
scope: "run",
|
|
775
803
|
type: "run_interrupted",
|
|
776
804
|
payload: { error: reason },
|
|
777
|
-
});
|
|
805
|
+
}));
|
|
778
806
|
return this.readRun(runId, { includeTrace: true });
|
|
779
807
|
}
|
|
780
|
-
async publishUpdate(runId,
|
|
781
|
-
return await this.withRunLock(runId, async () => this.publishUpdateSynchronous(runId,
|
|
808
|
+
async publishUpdate(runId, nodeId, attemptId, update, options = {}) {
|
|
809
|
+
return await this.withRunLock(runId, async () => this.publishUpdateSynchronous(runId, nodeId, attemptId, update, options));
|
|
782
810
|
}
|
|
783
811
|
/** Server-only synchronous form for an already serialized command transaction. */
|
|
784
|
-
publishUpdateSynchronous(runId,
|
|
812
|
+
publishUpdateSynchronous(runId, nodeId, attemptId, update, options = {}) {
|
|
785
813
|
if (options.signal?.aborted === true) {
|
|
786
814
|
throw options.signal.reason ?? new Error("workflow update attempt is no longer active");
|
|
787
815
|
}
|
|
816
|
+
const state = this.readRunState(runId);
|
|
817
|
+
if (state === null ||
|
|
818
|
+
state.currentAttemptId !== attemptId ||
|
|
819
|
+
(state.currentNode ?? state.waitingOn) !== nodeId) {
|
|
820
|
+
throw new Error("Workflow update does not match the active attempt");
|
|
821
|
+
}
|
|
788
822
|
const exists = (state.updates ?? []).some((record) => record.type === update.type && record.key === update.key);
|
|
789
823
|
if (!exists && (state.updates?.length ?? 0) >= MAX_CURRENT_UPDATES) {
|
|
790
824
|
throw new Error(`workflow run supports at most ${MAX_CURRENT_UPDATES} current updates`);
|
|
@@ -845,7 +879,7 @@ export class WorkflowRunStore {
|
|
|
845
879
|
}
|
|
846
880
|
return { event: acceptedEvent, record: acceptedRecord };
|
|
847
881
|
}
|
|
848
|
-
async
|
|
882
|
+
async commitTransition(runId, transition) {
|
|
849
883
|
return await this.withRunLock(runId, async () => {
|
|
850
884
|
let accepted;
|
|
851
885
|
this.state.transaction(() => {
|
|
@@ -856,6 +890,62 @@ export class WorkflowRunStore {
|
|
|
856
890
|
const now = Date.now();
|
|
857
891
|
const at = new Date(now).toISOString();
|
|
858
892
|
const snapshot = this.readDefinition(run.definitionHash);
|
|
893
|
+
const current = this.materializeRunState(run, snapshot);
|
|
894
|
+
const state = applyExecutionTransition(current, snapshot, transition, at);
|
|
895
|
+
if (transition.kind === "finishAttempt" &&
|
|
896
|
+
transition.step.nodeType === "checkpoint" &&
|
|
897
|
+
transition.step.outcome === "ok") {
|
|
898
|
+
const request = requestForAttempt(this.state, runId, transition.step.attemptId);
|
|
899
|
+
if (request === undefined ||
|
|
900
|
+
request.status !== "settled" ||
|
|
901
|
+
canonicalJson(acceptedRequestOutput(this.state, request)) !==
|
|
902
|
+
canonicalJson(transition.step.output)) {
|
|
903
|
+
throw new Error("Checkpoint completion requires its exact accepted response");
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
if (transition.kind === "waitForInput") {
|
|
907
|
+
const node = snapshot.nodes[transition.event.nodeId ?? ""];
|
|
908
|
+
if ((node?.humanDecision !== undefined) !== (transition.requestKind === "decision")) {
|
|
909
|
+
throw new Error("Checkpoint response authority does not match its definition");
|
|
910
|
+
}
|
|
911
|
+
if (transition.requestKind === "decision") {
|
|
912
|
+
const decision = transition.contract;
|
|
913
|
+
if (decision.runId !== runId ||
|
|
914
|
+
decision.attemptId !== transition.event.attemptId ||
|
|
915
|
+
decision.nodeId !== transition.event.nodeId ||
|
|
916
|
+
decision.decisionId !== transition.requestId) {
|
|
917
|
+
throw new Error("Human decision does not match the active checkpoint");
|
|
918
|
+
}
|
|
919
|
+
new HumanDecisionStore(this.databasePath, { state: this.state }).createRequest(decision);
|
|
920
|
+
}
|
|
921
|
+
const binding = this.state.connection
|
|
922
|
+
.prepare("SELECT origin_session_id AS sessionId FROM run_bindings WHERE run_id = ?")
|
|
923
|
+
.get(runId);
|
|
924
|
+
ensureWorkflowRequest(this.state, {
|
|
925
|
+
requestId: transition.requestId,
|
|
926
|
+
runId,
|
|
927
|
+
attemptId: transition.event.attemptId ?? "",
|
|
928
|
+
targetSessionId: binding?.sessionId ?? "",
|
|
929
|
+
kind: transition.requestKind,
|
|
930
|
+
contract: transition.contract,
|
|
931
|
+
}, now);
|
|
932
|
+
}
|
|
933
|
+
if (transition.kind === "resume" &&
|
|
934
|
+
transition.attemptId === undefined &&
|
|
935
|
+
current.currentAttemptId !== undefined) {
|
|
936
|
+
const request = this.state.connection
|
|
937
|
+
.prepare("SELECT 1 FROM interactive_requests WHERE attempt_id = ? AND status = 'pending'")
|
|
938
|
+
.get(current.currentAttemptId);
|
|
939
|
+
if (request !== undefined)
|
|
940
|
+
throw new Error("A pending request must resume its exact attempt");
|
|
941
|
+
const retired = this.state.connection
|
|
942
|
+
.prepare(`UPDATE node_attempts SET status = 'cancelled', finished_at = ?, updated_at = ?
|
|
943
|
+
WHERE attempt_id = ? AND run_id = ? AND status = 'interrupted'`)
|
|
944
|
+
.run(now, now, current.currentAttemptId, runId);
|
|
945
|
+
if (retired.changes !== 1)
|
|
946
|
+
throw new Error("Only an interrupted attempt can be replaced");
|
|
947
|
+
}
|
|
948
|
+
const event = transition.event;
|
|
859
949
|
this.assertSettingsRouteCurrent(event, snapshot);
|
|
860
950
|
const traceEvent = { seq: revision, at, runId, ...event };
|
|
861
951
|
state.traceSeq = revision;
|
|
@@ -864,6 +954,13 @@ export class WorkflowRunStore {
|
|
|
864
954
|
this.persistRunState(run, state, revision, now);
|
|
865
955
|
this.transitionFollowUpsForRunState(state, event, now);
|
|
866
956
|
this.syncNodeAttempts(state, snapshot, now);
|
|
957
|
+
if (transition.kind === "finishAttempt") {
|
|
958
|
+
this.state.connection
|
|
959
|
+
.prepare(`UPDATE interactive_requests SET consumed_at = ?, updated_at = ?
|
|
960
|
+
WHERE run_id = ? AND attempt_id = ? AND status = 'settled'
|
|
961
|
+
AND accepted_submission_id IS NOT NULL AND consumed_at IS NULL`)
|
|
962
|
+
.run(now, now, runId, transition.step.attemptId);
|
|
963
|
+
}
|
|
867
964
|
recordViewerDeltas(this.state, runId, runViewerTargets(state, traceEvent), now);
|
|
868
965
|
this.snapshotLifecycle?.({
|
|
869
966
|
runId,
|
|
@@ -1274,15 +1371,6 @@ export class WorkflowRunStore {
|
|
|
1274
1371
|
const row = this.readRunRow(runId);
|
|
1275
1372
|
return row?.errorHash == null ? null : this.readText(row.errorHash);
|
|
1276
1373
|
}
|
|
1277
|
-
readPresentationInstructions(runId) {
|
|
1278
|
-
const row = this.state.connection
|
|
1279
|
-
.prepare("SELECT presentation_prompt_hash AS hash FROM runs WHERE run_id = ?")
|
|
1280
|
-
.get(runId);
|
|
1281
|
-
if (row?.hash == null) {
|
|
1282
|
-
return "Explain the final workflow result to the user in a normal response.";
|
|
1283
|
-
}
|
|
1284
|
-
return this.readText(row.hash);
|
|
1285
|
-
}
|
|
1286
1374
|
readTerminalData(runId) {
|
|
1287
1375
|
const row = this.state.connection
|
|
1288
1376
|
.prepare(`SELECT status, status_detail AS statusDetail, restart_number AS restartNumber
|
|
@@ -1300,7 +1388,6 @@ export class WorkflowRunStore {
|
|
|
1300
1388
|
input: this.readRunInput(runId),
|
|
1301
1389
|
finalOutput: this.readRunFinalOutput(runId),
|
|
1302
1390
|
error: this.readRunError(runId),
|
|
1303
|
-
presentationInstructions: this.readPresentationInstructions(runId),
|
|
1304
1391
|
restartNumber: row.restartNumber,
|
|
1305
1392
|
};
|
|
1306
1393
|
}
|
|
@@ -1310,6 +1397,15 @@ export class WorkflowRunStore {
|
|
|
1310
1397
|
return null;
|
|
1311
1398
|
return this.materializeRunState(row, this.readDefinition(row.definitionHash));
|
|
1312
1399
|
}
|
|
1400
|
+
runRevision(runId) {
|
|
1401
|
+
const row = this.state.connection
|
|
1402
|
+
.prepare(`SELECT resources.revision FROM resources
|
|
1403
|
+
JOIN runs ON runs.resource_id = resources.resource_id WHERE runs.run_id = ?`)
|
|
1404
|
+
.get(runId);
|
|
1405
|
+
if (row === undefined)
|
|
1406
|
+
throw new Error(`Workflow run not found: ${runId}`);
|
|
1407
|
+
return row.revision;
|
|
1408
|
+
}
|
|
1313
1409
|
readRun(runId, options = {}) {
|
|
1314
1410
|
const row = this.readRunRow(runId);
|
|
1315
1411
|
if (row === undefined)
|
|
@@ -1338,7 +1434,7 @@ export class WorkflowRunStore {
|
|
|
1338
1434
|
if (row === undefined)
|
|
1339
1435
|
return null;
|
|
1340
1436
|
const segment = this.segmentRow(segmentIdFor(runId));
|
|
1341
|
-
const settingsOrigin =
|
|
1437
|
+
const settingsOrigin = runId;
|
|
1342
1438
|
const scalar = (sql, ...params) => {
|
|
1343
1439
|
const count = this.state.connection.prepare(sql).get(...params);
|
|
1344
1440
|
return isCountRow(count) ? count.count : 0;
|
|
@@ -1637,7 +1733,10 @@ export class WorkflowRunStore {
|
|
|
1637
1733
|
.run(state.runTitle ?? null, state.status, state.paused === true ? 1 : 0, state.statusDetail ?? null, finalOutputHash, errorHash, now, state.finishedAt === undefined ? null : Date.parse(state.finishedAt), state.runId);
|
|
1638
1734
|
if (update.changes !== 1)
|
|
1639
1735
|
throw new Error(`Workflow run is missing: ${state.runId}`);
|
|
1640
|
-
if (state.
|
|
1736
|
+
if (state.paused || ["completed", "failed", "cancelled"].includes(state.status)) {
|
|
1737
|
+
closeRunTime(this.state, state.runId);
|
|
1738
|
+
}
|
|
1739
|
+
if (state.status !== "running" && state.status !== run.status) {
|
|
1641
1740
|
this.enqueueRunSettlementEffect(run.resourceId, expectedRevision, state, now);
|
|
1642
1741
|
}
|
|
1643
1742
|
}
|
|
@@ -1737,11 +1836,6 @@ export class WorkflowRunStore {
|
|
|
1737
1836
|
];
|
|
1738
1837
|
}
|
|
1739
1838
|
initializeRunSettingsAndFollowUps(state, initialSettings, now) {
|
|
1740
|
-
if (state.parentRunId !== undefined) {
|
|
1741
|
-
this.state.connection
|
|
1742
|
-
.prepare(`UPDATE workflow_settings SET active_run_id = ?, updated_at = ? WHERE active_run_id = ?`)
|
|
1743
|
-
.run(state.runId, now, state.parentRunId);
|
|
1744
|
-
}
|
|
1745
1839
|
const existing = this.state.connection
|
|
1746
1840
|
.prepare("SELECT COUNT(*) AS count FROM workflow_settings WHERE active_run_id = ?")
|
|
1747
1841
|
.get(state.runId);
|
|
@@ -1749,7 +1843,7 @@ export class WorkflowRunStore {
|
|
|
1749
1843
|
if (hasSettings)
|
|
1750
1844
|
return;
|
|
1751
1845
|
for (const scope of initialSettings) {
|
|
1752
|
-
const originRunId =
|
|
1846
|
+
const originRunId = state.runId;
|
|
1753
1847
|
const scopeId = workflowSettingsScopeId(originRunId, scope.mountPath, scope.invocation);
|
|
1754
1848
|
const resourceId = this.mutations.ensureResource("settings", scopeId, now);
|
|
1755
1849
|
const settingsHash = this.state.putJson(scope.settings, now);
|
|
@@ -1762,20 +1856,6 @@ export class WorkflowRunStore {
|
|
|
1762
1856
|
.run(scopeId, resourceId, originRunId, state.runId, scope.mountPath, scope.invocation, settingsHash, settingsHash, now, now);
|
|
1763
1857
|
}
|
|
1764
1858
|
}
|
|
1765
|
-
logicalOriginRunId(runId) {
|
|
1766
|
-
let current = runId;
|
|
1767
|
-
for (let depth = 0; depth < 100; depth += 1) {
|
|
1768
|
-
const row = this.state.connection
|
|
1769
|
-
.prepare("SELECT parent_run_id AS parentRunId FROM runs WHERE run_id = ?")
|
|
1770
|
-
.get(current);
|
|
1771
|
-
if (!isParentRunRow(row))
|
|
1772
|
-
return current;
|
|
1773
|
-
if (row.parentRunId === null)
|
|
1774
|
-
return current;
|
|
1775
|
-
current = row.parentRunId;
|
|
1776
|
-
}
|
|
1777
|
-
throw new Error(`Workflow continuation chain is too deep for run ${runId}`);
|
|
1778
|
-
}
|
|
1779
1859
|
requireRunAcceptsSettings(runId, allowWaiting) {
|
|
1780
1860
|
const row = this.state.connection
|
|
1781
1861
|
.prepare("SELECT status FROM runs WHERE run_id = ?")
|
|
@@ -1982,15 +2062,7 @@ export class WorkflowRunStore {
|
|
|
1982
2062
|
}
|
|
1983
2063
|
const rows = this.state.connection
|
|
1984
2064
|
.prepare(`${FOLLOW_UP_ROW_SELECT}
|
|
1985
|
-
WHERE f.run_id
|
|
1986
|
-
WITH RECURSIVE ancestors(run_id, parent_run_id) AS (
|
|
1987
|
-
SELECT run_id, parent_run_id FROM runs WHERE run_id = ?
|
|
1988
|
-
UNION ALL
|
|
1989
|
-
SELECT parent.run_id, parent.parent_run_id
|
|
1990
|
-
FROM runs parent JOIN ancestors ON ancestors.parent_run_id = parent.run_id
|
|
1991
|
-
)
|
|
1992
|
-
SELECT run_id FROM ancestors
|
|
1993
|
-
) AND f.status = 'queued'`)
|
|
2065
|
+
WHERE f.run_id = ? AND f.status = 'queued'`)
|
|
1994
2066
|
.all(state.runId)
|
|
1995
2067
|
.filter(isFollowUpRow);
|
|
1996
2068
|
for (const row of rows) {
|
|
@@ -2009,7 +2081,8 @@ export class WorkflowRunStore {
|
|
|
2009
2081
|
}
|
|
2010
2082
|
}
|
|
2011
2083
|
syncNodeAttempts(state, snapshot, now) {
|
|
2012
|
-
for (const step of state.steps
|
|
2084
|
+
for (const step of state.steps) {
|
|
2085
|
+
closeAttemptTime(this.state, step.attemptId);
|
|
2013
2086
|
const promptEntryId = this.findAttemptPromptEntry(state.runId, step.attemptId);
|
|
2014
2087
|
const promptHash = promptEntryId === undefined && step.prompt !== null
|
|
2015
2088
|
? this.state.putText(step.prompt, now)
|
|
@@ -2049,21 +2122,17 @@ export class WorkflowRunStore {
|
|
|
2049
2122
|
}
|
|
2050
2123
|
if (state.currentAttemptId !== undefined && state.currentNode !== undefined) {
|
|
2051
2124
|
this.ensureAttempt(state, state.currentNode, state.currentAttemptId, now, snapshot);
|
|
2052
|
-
if (state.
|
|
2053
|
-
const deadlineAt = state.currentNodeDeadlineAt === null ? null : Date.parse(state.currentNodeDeadlineAt);
|
|
2054
|
-
if (deadlineAt !== null && !Number.isFinite(deadlineAt)) {
|
|
2055
|
-
throw new Error("Workflow node deadline is invalid");
|
|
2056
|
-
}
|
|
2125
|
+
if (state.currentNodeTimeoutMs !== undefined) {
|
|
2057
2126
|
this.state.connection
|
|
2058
|
-
.prepare(`UPDATE node_attempts SET
|
|
2059
|
-
WHERE attempt_id = ? AND run_id = ?
|
|
2127
|
+
.prepare(`UPDATE node_attempts SET timeout_ms = ?, updated_at = ?
|
|
2128
|
+
WHERE attempt_id = ? AND run_id = ? AND timeout_ms IS NULL
|
|
2060
2129
|
AND status IN ('pending', 'running', 'waiting', 'interrupted')`)
|
|
2061
|
-
.run(
|
|
2130
|
+
.run(state.currentNodeTimeoutMs ?? 0, now, state.currentAttemptId, state.runId);
|
|
2062
2131
|
}
|
|
2063
2132
|
}
|
|
2064
|
-
this.syncRunSteps(state
|
|
2133
|
+
this.syncRunSteps(state);
|
|
2065
2134
|
}
|
|
2066
|
-
syncRunSteps(state
|
|
2135
|
+
syncRunSteps(state) {
|
|
2067
2136
|
const existingRows = this.state.connection
|
|
2068
2137
|
.prepare(`SELECT step_index AS stepIndex, attempt_id AS attemptId
|
|
2069
2138
|
FROM run_steps WHERE run_id = ? ORDER BY step_index`)
|
|
@@ -2082,15 +2151,15 @@ export class WorkflowRunStore {
|
|
|
2082
2151
|
/* istanbul ignore if -- array index follows a checked bound */
|
|
2083
2152
|
if (step === undefined)
|
|
2084
2153
|
throw new Error("Workflow run step became unavailable");
|
|
2085
|
-
const
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2154
|
+
const attempt = this.state.connection
|
|
2155
|
+
.prepare("SELECT run_id AS runId FROM node_attempts WHERE attempt_id = ?")
|
|
2156
|
+
.get(step.attemptId);
|
|
2157
|
+
if (attempt?.runId !== state.runId)
|
|
2158
|
+
throw new Error("A run cannot own another run's step");
|
|
2090
2159
|
this.state.connection
|
|
2091
|
-
.prepare(`INSERT INTO run_steps(run_id, step_index, attempt_id
|
|
2092
|
-
VALUES (?, ?,
|
|
2093
|
-
.run(state.runId, stepIndex, step.attemptId
|
|
2160
|
+
.prepare(`INSERT INTO run_steps(run_id, step_index, attempt_id)
|
|
2161
|
+
VALUES (?, ?, ?)`)
|
|
2162
|
+
.run(state.runId, stepIndex, step.attemptId);
|
|
2094
2163
|
}
|
|
2095
2164
|
}
|
|
2096
2165
|
syncAttemptEntries(runId, step, promptEntryId) {
|
|
@@ -2145,26 +2214,6 @@ export class WorkflowRunStore {
|
|
|
2145
2214
|
.get(runId, attemptId);
|
|
2146
2215
|
return isEntryIdentityRow(row) ? row.entryId : undefined;
|
|
2147
2216
|
}
|
|
2148
|
-
readAttemptOutput(attemptId) {
|
|
2149
|
-
const row = this.state.connection
|
|
2150
|
-
.prepare(`SELECT a.output_hash AS outputHash, a.receipt_hash AS receiptHash,
|
|
2151
|
-
e.entry_hash AS responseEntryHash
|
|
2152
|
-
FROM node_attempts a
|
|
2153
|
-
LEFT JOIN attempt_entries l ON l.attempt_id = a.attempt_id AND l.role = 'response'
|
|
2154
|
-
LEFT JOIN session_entries e
|
|
2155
|
-
ON e.segment_id = l.segment_id AND e.entry_id = l.entry_id
|
|
2156
|
-
WHERE a.attempt_id = ?`)
|
|
2157
|
-
.get(attemptId);
|
|
2158
|
-
if (!isAttemptValueRow(row)) {
|
|
2159
|
-
throw new Error(`Workflow node attempt is missing: ${attemptId}`);
|
|
2160
|
-
}
|
|
2161
|
-
if (row.outputHash !== null)
|
|
2162
|
-
return this.state.readJson(row.outputHash);
|
|
2163
|
-
if (row.responseEntryHash === null)
|
|
2164
|
-
return null;
|
|
2165
|
-
const receipt = row.receiptHash === null ? {} : this.readJsonAs(row.receiptHash);
|
|
2166
|
-
return assistantOutputFromEntry(this.state.readJson(row.responseEntryHash), receipt.assistantMessage?.maxChars);
|
|
2167
|
-
}
|
|
2168
2217
|
ensureAttempt(state, nodeId, attemptId, now, snapshot) {
|
|
2169
2218
|
const existing = this.state.connection
|
|
2170
2219
|
.prepare("SELECT attempt_id AS attemptId FROM node_attempts WHERE attempt_id = ?")
|
|
@@ -2227,28 +2276,6 @@ export class WorkflowRunStore {
|
|
|
2227
2276
|
insert.run(runId, item.mountPath.join("/"), source.type, source.ref, source.revision);
|
|
2228
2277
|
}
|
|
2229
2278
|
}
|
|
2230
|
-
syncContinuationIdentity(state) {
|
|
2231
|
-
if (state.parentRunId === undefined || state.humanDecision === undefined)
|
|
2232
|
-
return;
|
|
2233
|
-
const createdAt = Date.parse(state.humanDecision.acceptedAt);
|
|
2234
|
-
this.state.connection
|
|
2235
|
-
.prepare(`INSERT INTO continuations(
|
|
2236
|
-
decision_id, parent_run_id, continuation_run_id, created_at
|
|
2237
|
-
) VALUES (?, ?, ?, ?)
|
|
2238
|
-
ON CONFLICT(decision_id) DO NOTHING`)
|
|
2239
|
-
.run(state.humanDecision.decisionId, state.parentRunId, state.runId, createdAt);
|
|
2240
|
-
const row = this.state.connection
|
|
2241
|
-
.prepare(`SELECT parent_run_id AS parentRunId, continuation_run_id AS continuationRunId,
|
|
2242
|
-
created_at AS createdAt
|
|
2243
|
-
FROM continuations WHERE decision_id = ?`)
|
|
2244
|
-
.get(state.humanDecision.decisionId);
|
|
2245
|
-
if (!isContinuationIdentityRow(row) ||
|
|
2246
|
-
row.parentRunId !== state.parentRunId ||
|
|
2247
|
-
row.continuationRunId !== state.runId ||
|
|
2248
|
-
row.createdAt !== createdAt) {
|
|
2249
|
-
throw new Error("Immutable human decision continuation conflicts");
|
|
2250
|
-
}
|
|
2251
|
-
}
|
|
2252
2279
|
readRunRow(runId) {
|
|
2253
2280
|
const row = this.state.connection
|
|
2254
2281
|
.prepare(`SELECT r.run_id AS runId, r.resource_id AS resourceId,
|
|
@@ -2276,7 +2303,6 @@ export class WorkflowRunStore {
|
|
|
2276
2303
|
}
|
|
2277
2304
|
buildRunState(row, snapshot, projectionSteps, visibleSteps, visibleUpdates) {
|
|
2278
2305
|
const sources = this.readRunSources(row.runId, snapshot);
|
|
2279
|
-
const carriedStepCount = this.carriedStepCount(row.runId);
|
|
2280
2306
|
const activeAttempt = row.status === "running" || row.status === "waiting"
|
|
2281
2307
|
? this.readActiveAttempt(row.runId)
|
|
2282
2308
|
: undefined;
|
|
@@ -2310,7 +2336,6 @@ export class WorkflowRunStore {
|
|
|
2310
2336
|
runId: row.runId,
|
|
2311
2337
|
workflowName: row.workflowRef,
|
|
2312
2338
|
...(row.parentRunId === null ? {} : { parentRunId: row.parentRunId }),
|
|
2313
|
-
...(carriedStepCount === 0 ? {} : { carriedStepCount }),
|
|
2314
2339
|
...(row.title === null ? {} : { runTitle: row.title }),
|
|
2315
2340
|
...(sources.root === undefined ? {} : { workflowSource: sources.root }),
|
|
2316
2341
|
...(sources.mounted.length === 0 ? {} : { workflowSources: sources.mounted }),
|
|
@@ -2331,12 +2356,11 @@ export class WorkflowRunStore {
|
|
|
2331
2356
|
...(activeAttempt?.startedAt === null || activeAttempt === undefined
|
|
2332
2357
|
? {}
|
|
2333
2358
|
: { currentNodeStartedAt: new Date(activeAttempt.startedAt).toISOString() }),
|
|
2334
|
-
...(activeAttempt === undefined
|
|
2359
|
+
...(activeAttempt === undefined || activeAttempt.timeoutMs === null
|
|
2335
2360
|
? {}
|
|
2336
2361
|
: {
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
: new Date(activeAttempt.deadlineAt).toISOString(),
|
|
2362
|
+
currentNodeTimeoutMs: activeAttempt.timeoutMs === 0 ? null : activeAttempt.timeoutMs,
|
|
2363
|
+
currentNodeElapsedMs: activeAttempt.activeElapsedMs,
|
|
2340
2364
|
}),
|
|
2341
2365
|
...savedCurrentSettingsBinding(activeAttempt?.settingsScopeId ?? null, activeAttempt?.settingsChangeNumber ?? null, activeAttempt?.settingsHash ?? null),
|
|
2342
2366
|
...(row.statusDetail === null ? {} : { statusDetail: row.statusDetail }),
|
|
@@ -2402,7 +2426,7 @@ export class WorkflowRunStore {
|
|
|
2402
2426
|
: row.promptHash === null
|
|
2403
2427
|
? null
|
|
2404
2428
|
: this.readText(row.promptHash);
|
|
2405
|
-
const outputHash = row.
|
|
2429
|
+
const outputHash = row.outputHash;
|
|
2406
2430
|
const output = outputHash !== null
|
|
2407
2431
|
? this.state.readJson(outputHash)
|
|
2408
2432
|
: row.responseEntryHash === null
|
|
@@ -2453,18 +2477,13 @@ export class WorkflowRunStore {
|
|
|
2453
2477
|
}));
|
|
2454
2478
|
return { ...(root === undefined ? {} : { root }), mounted };
|
|
2455
2479
|
}
|
|
2456
|
-
carriedStepCount(runId) {
|
|
2457
|
-
const row = this.state.connection
|
|
2458
|
-
.prepare(`SELECT count(*) AS count
|
|
2459
|
-
FROM run_steps s JOIN node_attempts a ON a.attempt_id = s.attempt_id
|
|
2460
|
-
WHERE s.run_id = ? AND a.run_id <> s.run_id`)
|
|
2461
|
-
.get(runId);
|
|
2462
|
-
return isCountRow(row) ? row.count : 0;
|
|
2463
|
-
}
|
|
2464
2480
|
readActiveAttempt(runId) {
|
|
2465
2481
|
const row = this.state.connection
|
|
2466
2482
|
.prepare(`SELECT attempt_id AS attemptId, node_id AS nodeId, started_at AS startedAt,
|
|
2467
|
-
|
|
2483
|
+
timeout_ms AS timeoutMs,
|
|
2484
|
+
(SELECT COALESCE(SUM(elapsed_ms), 0) FROM attempt_active_intervals i
|
|
2485
|
+
WHERE i.attempt_id = node_attempts.attempt_id) AS activeElapsedMs,
|
|
2486
|
+
settings_scope_id AS settingsScopeId,
|
|
2468
2487
|
settings_change_number AS settingsChangeNumber,
|
|
2469
2488
|
settings_hash AS settingsHash
|
|
2470
2489
|
FROM node_attempts
|
|
@@ -2472,14 +2491,14 @@ export class WorkflowRunStore {
|
|
|
2472
2491
|
.get(runId);
|
|
2473
2492
|
return isActiveAttemptRow(row) ? row : undefined;
|
|
2474
2493
|
}
|
|
2475
|
-
readHumanDecisionReceipt(runId) {
|
|
2494
|
+
readHumanDecisionReceipt(runId, decisionId) {
|
|
2476
2495
|
const row = this.state.connection
|
|
2477
2496
|
.prepare(`SELECT d.request_hash AS requestHash, r.response_hash AS responseHash
|
|
2478
|
-
FROM
|
|
2479
|
-
JOIN
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
.get(runId);
|
|
2497
|
+
FROM human_decisions d
|
|
2498
|
+
JOIN human_decision_resolutions r ON r.decision_id = d.decision_id
|
|
2499
|
+
WHERE d.run_id = ? AND r.outcome = 'accepted' AND (? IS NULL OR d.decision_id = ?)
|
|
2500
|
+
ORDER BY r.resolved_at DESC, d.decision_id DESC LIMIT 1`)
|
|
2501
|
+
.get(runId, decisionId ?? null, decisionId ?? null);
|
|
2483
2502
|
if (!isDecisionReceiptRow(row))
|
|
2484
2503
|
return undefined;
|
|
2485
2504
|
const request = this.readJsonAs(row.requestHash);
|
|
@@ -2823,8 +2842,8 @@ function runViewerTargets(state, traceEvent) {
|
|
|
2823
2842
|
},
|
|
2824
2843
|
{
|
|
2825
2844
|
op: "add",
|
|
2826
|
-
path: "/state/
|
|
2827
|
-
value: state.
|
|
2845
|
+
path: "/state/currentNodeTimeoutMs",
|
|
2846
|
+
value: state.currentNodeTimeoutMs ?? null,
|
|
2828
2847
|
},
|
|
2829
2848
|
{
|
|
2830
2849
|
op: "add",
|
|
@@ -3241,14 +3260,6 @@ function isFollowUpRow(value) {
|
|
|
3241
3260
|
typeof value.updatedAt === "number" &&
|
|
3242
3261
|
typeof value.revision === "number");
|
|
3243
3262
|
}
|
|
3244
|
-
function isParentRunRow(value) {
|
|
3245
|
-
return isRecord(value) && (typeof value.parentRunId === "string" || value.parentRunId === null);
|
|
3246
|
-
}
|
|
3247
|
-
function isRunLineageRow(value) {
|
|
3248
|
-
return (isRecord(value) &&
|
|
3249
|
-
typeof value.rootRunId === "string" &&
|
|
3250
|
-
typeof value.restartNumber === "number");
|
|
3251
|
-
}
|
|
3252
3263
|
function isStatusRow(value) {
|
|
3253
3264
|
return isRecord(value) && typeof value.status === "string";
|
|
3254
3265
|
}
|
|
@@ -3350,18 +3361,12 @@ function isSegmentIdentityRow(value) {
|
|
|
3350
3361
|
function isEntryIdentityRow(value) {
|
|
3351
3362
|
return isRecord(value) && typeof value.entryId === "string";
|
|
3352
3363
|
}
|
|
3353
|
-
function isAttemptValueRow(value) {
|
|
3354
|
-
return isRecord(value);
|
|
3355
|
-
}
|
|
3356
3364
|
function isActiveAttemptRow(value) {
|
|
3357
3365
|
return isRecord(value);
|
|
3358
3366
|
}
|
|
3359
3367
|
function isDecisionReceiptRow(value) {
|
|
3360
3368
|
return (isRecord(value) && Buffer.isBuffer(value.requestHash) && Buffer.isBuffer(value.responseHash));
|
|
3361
3369
|
}
|
|
3362
|
-
function isContinuationIdentityRow(value) {
|
|
3363
|
-
return isRecord(value);
|
|
3364
|
-
}
|
|
3365
3370
|
function isUpdateRow(value) {
|
|
3366
3371
|
return isRecord(value);
|
|
3367
3372
|
}
|