@osolmaz/pi-workflows 0.16.0 → 0.16.1
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 +18 -2
- package/dist/channels/adapter-entry.d.ts +3 -0
- package/dist/channels/adapter-entry.js +203 -0
- package/dist/channels/adapter-entry.js.map +1 -0
- package/dist/channels/config.d.ts +43 -0
- package/dist/channels/config.js +234 -0
- package/dist/channels/config.js.map +1 -0
- package/dist/channels/protocol.d.ts +94 -0
- package/dist/channels/protocol.js +219 -0
- package/dist/channels/protocol.js.map +1 -0
- package/dist/channels/telegram.d.ts +46 -0
- package/dist/channels/telegram.js +285 -0
- package/dist/channels/telegram.js.map +1 -0
- package/dist/client/client.d.ts +1 -0
- package/dist/client/client.js +5 -1
- package/dist/client/client.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/protocol.d.ts +1 -1
- package/dist/client/protocol.js +11 -5
- package/dist/client/protocol.js.map +1 -1
- package/dist/client/view.d.ts +42 -16
- package/dist/client/view.js.map +1 -1
- package/dist/controllers/index.d.ts +1 -1
- package/dist/controllers/index.js.map +1 -1
- package/dist/controllers/sqlite.d.ts +12 -165
- package/dist/controllers/sqlite.js +124 -435
- package/dist/controllers/sqlite.js.map +1 -1
- package/dist/extension/index.d.ts +23 -2
- package/dist/extension/index.js +619 -535
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/recorder.d.ts +95 -0
- package/dist/extension/recorder.js +530 -0
- package/dist/extension/recorder.js.map +1 -0
- package/dist/extension/remote-recorder-store.d.ts +25 -0
- package/dist/extension/remote-recorder-store.js +81 -0
- package/dist/extension/remote-recorder-store.js.map +1 -0
- package/dist/extension/session-events.d.ts +134 -0
- package/dist/extension/session-events.js +60 -0
- package/dist/extension/session-events.js.map +1 -0
- package/dist/extension/session-view.d.ts +2 -0
- package/dist/extension/session-view.js +60 -2
- package/dist/extension/session-view.js.map +1 -1
- package/dist/extension/step-message.d.ts +6 -4
- package/dist/extension/step-message.js +12 -6
- package/dist/extension/step-message.js.map +1 -1
- package/dist/extension/widget.d.ts +2 -2
- package/dist/extension/widget.js +28 -18
- package/dist/extension/widget.js.map +1 -1
- package/dist/extension/workflow-message-coordinator.d.ts +27 -0
- package/dist/extension/workflow-message-coordinator.js +294 -0
- package/dist/extension/workflow-message-coordinator.js.map +1 -0
- package/dist/host/channel-effects.d.ts +72 -0
- package/dist/host/channel-effects.js +271 -0
- package/dist/host/channel-effects.js.map +1 -0
- package/dist/host/channel-supervisor.d.ts +21 -0
- package/dist/host/channel-supervisor.js +54 -0
- package/dist/host/channel-supervisor.js.map +1 -0
- package/dist/host/runner.d.ts +43 -8
- package/dist/host/runner.js +1567 -331
- package/dist/host/runner.js.map +1 -1
- package/dist/host/state.d.ts +19 -16
- package/dist/host/state.js +251 -63
- package/dist/host/state.js.map +1 -1
- package/dist/host/view.d.ts +19 -9
- package/dist/host/view.js +222 -118
- package/dist/host/view.js.map +1 -1
- package/dist/state/prune.js +3 -25
- package/dist/state/prune.js.map +1 -1
- package/dist/state/schema.js +76 -97
- package/dist/state/schema.js.map +1 -1
- package/dist/state/workflow-messages.d.ts +96 -0
- package/dist/state/workflow-messages.js +335 -0
- package/dist/state/workflow-messages.js.map +1 -0
- package/dist/viewer/render.js +1 -1
- package/dist/viewer/render.js.map +1 -1
- package/dist/viewer/tui.d.ts +1 -1
- package/dist/viewer/tui.js +65 -8
- package/dist/viewer/tui.js.map +1 -1
- package/dist/workflows/human-decision.d.ts +2 -0
- package/dist/workflows/human-decision.js +8 -2
- package/dist/workflows/human-decision.js.map +1 -1
- package/dist/workflows/index.d.ts +1 -1
- package/dist/workflows/index.js.map +1 -1
- package/dist/workflows/settings.d.ts +1 -8
- package/dist/workflows/settings.js.map +1 -1
- package/dist/workflows/store.d.ts +5 -19
- package/dist/workflows/store.js +121 -354
- package/dist/workflows/store.js.map +1 -1
- package/dist/workflows/tool-input.d.ts +21 -0
- package/dist/workflows/tool-input.js +23 -1
- package/dist/workflows/tool-input.js.map +1 -1
- package/dist/workflows/types.d.ts +5 -5
- package/dist/workflows/workflow-message-content.d.ts +38 -0
- package/dist/workflows/workflow-message-content.js +157 -0
- package/dist/workflows/workflow-message-content.js.map +1 -0
- package/docs/2026-08-18-herdr-piw-plan.md +2 -1
- package/docs/2026-08-20-durable-workflow-launch-plan.md +1 -1
- package/docs/2026-08-25-workflow-follow-ups.md +26 -50
- package/docs/2026-08-30-out-of-process-workflow-host-plan.md +3 -3
- package/docs/2026-09-01-restore-session-delivery-controls-plan.md +4 -0
- package/docs/2026-09-01-unified-workflow-client-plan.md +5 -1
- package/docs/2026-09-02-installed-live-e2e-plan.md +15 -1
- package/docs/2026-09-02-unify-workflow-messages-plan.md +562 -0
- package/docs/DEFERRED_TURNS.md +66 -294
- package/docs/HUMAN_DECISIONS.md +29 -30
- package/docs/HUMAN_DECISION_PRESENTATIONS.md +4 -11
- package/docs/SQLITE_STATE.md +19 -15
- package/docs/WORKFLOW_HOST.md +93 -28
- package/docs/WORKFLOW_STEP_MESSAGES.md +166 -75
- package/docs/live-replay-protocol.md +8 -4
- package/docs/tui-viewer.md +3 -1
- package/docs/workflows.md +34 -27
- package/herdr-plugin.toml +1 -1
- package/package.json +1 -1
- package/plugins/herdr/viewer.mjs +14 -5
- package/src/channels/adapter-entry.ts +220 -0
- package/src/channels/config.ts +296 -0
- package/src/channels/protocol.ts +333 -0
- package/src/channels/telegram.ts +335 -0
- package/src/client/client.ts +6 -2
- package/src/client/index.ts +2 -1
- package/src/client/protocol.ts +11 -5
- package/src/client/view.ts +43 -18
- package/src/controllers/index.ts +0 -1
- package/src/controllers/sqlite.ts +164 -844
- package/src/extension/index.ts +693 -646
- package/src/extension/recorder.ts +687 -0
- package/src/extension/remote-recorder-store.ts +126 -0
- package/src/extension/session-events.ts +119 -0
- package/src/extension/session-view.ts +86 -3
- package/src/extension/step-message.ts +19 -9
- package/src/extension/widget.ts +37 -17
- package/src/extension/workflow-message-coordinator.ts +334 -0
- package/src/host/channel-effects.ts +465 -0
- package/src/host/channel-supervisor.ts +73 -0
- package/src/host/runner.ts +2120 -531
- package/src/host/state.ts +333 -94
- package/src/host/view.ts +249 -121
- package/src/state/prune.ts +3 -31
- package/src/state/schema.ts +76 -97
- package/src/state/workflow-messages.ts +548 -0
- package/src/viewer/render.ts +1 -5
- package/src/viewer/tui.ts +71 -6
- package/src/workflows/human-decision.ts +18 -2
- package/src/workflows/index.ts +0 -1
- package/src/workflows/settings.ts +1 -20
- package/src/workflows/store.ts +173 -489
- package/src/workflows/tool-input.ts +36 -1
- package/src/workflows/types.ts +5 -5
- package/src/workflows/workflow-message-content.ts +197 -0
- package/dist/extension/decision-channels.d.ts +0 -134
- package/dist/extension/decision-channels.js +0 -1307
- package/dist/extension/decision-channels.js.map +0 -1
- package/src/extension/decision-channels.ts +0 -1826
package/dist/workflows/store.js
CHANGED
|
@@ -3,6 +3,7 @@ import { StateDatabase, workflowStatePath } from "../state/database.js";
|
|
|
3
3
|
import { canonicalJson, parseJson } from "../state/json.js";
|
|
4
4
|
import { StateMutationStore, StaleResourceError, resourceIdFor, tokenHash, } from "../state/mutation.js";
|
|
5
5
|
import { initializeViewerRun, recordViewerDeltas, viewerTailPatch, VIEWER_PAGE_SIZE, } from "../state/viewer.js";
|
|
6
|
+
import { WorkflowMessageStore, workflowMessageIdFor } from "../state/workflow-messages.js";
|
|
6
7
|
import { compositionMetadata } from "./composition.js";
|
|
7
8
|
import { ClaimLostError } from "./errors.js";
|
|
8
9
|
import { HumanDecisionStore } from "./human-decision.js";
|
|
@@ -10,6 +11,7 @@ import { applyJsonPatch, validateJsonPatch } from "./json-patch.js";
|
|
|
10
11
|
import { reduceSessionEvents, reduceSessionEventsFromCheckpoint, } from "./session-reducer.js";
|
|
11
12
|
import { applyWorkflowSettingsPatch, workflowSettingsScopeId, } from "./settings.js";
|
|
12
13
|
import { MAX_CURRENT_UPDATES, createUpdateId, updateProjection } from "./updates.js";
|
|
14
|
+
import { followUpWorkflowMessageContent } from "./workflow-message-content.js";
|
|
13
15
|
export const RUN_STATE_SCHEMA = "pi-workflows.run-state.v1";
|
|
14
16
|
export const DEFINITION_SNAPSHOT_SCHEMA = "pi-workflows.definition-snapshot.v1";
|
|
15
17
|
export const SESSION_BINDING_SCHEMA = "pi-workflows.session-binding.v1";
|
|
@@ -47,14 +49,11 @@ const STEP_ROW_SELECT = `
|
|
|
47
49
|
ON last_link.attempt_id = a.attempt_id AND last_link.role = 'last'`;
|
|
48
50
|
const FOLLOW_UP_ROW_SELECT = `
|
|
49
51
|
SELECT f.follow_up_id AS followUpId, f.resource_id AS resourceId,
|
|
50
|
-
f.
|
|
51
|
-
f.request_id AS requestId, f.order_number AS orderNumber,
|
|
52
|
+
f.run_id AS runId, f.request_id AS requestId, f.order_number AS orderNumber,
|
|
52
53
|
f.target_session_id AS targetSessionId, f.actor_type AS actorType,
|
|
53
54
|
f.actor_id AS actorId, f.source_type AS sourceType,
|
|
54
|
-
f.prompt_hash AS promptHash, f.status,
|
|
55
|
-
f.
|
|
56
|
-
f.created_at AS createdAt, f.updated_at AS updatedAt, f.sent_at AS sentAt,
|
|
57
|
-
r.revision
|
|
55
|
+
f.prompt_hash AS promptHash, f.status, f.reason_hash AS reasonHash,
|
|
56
|
+
f.created_at AS createdAt, f.updated_at AS updatedAt, r.revision
|
|
58
57
|
FROM workflow_follow_ups f
|
|
59
58
|
JOIN resources r ON r.resource_id = f.resource_id`;
|
|
60
59
|
export function workflowStateDatabasePath(homeDir) {
|
|
@@ -69,12 +68,15 @@ export class WorkflowRunStore {
|
|
|
69
68
|
state;
|
|
70
69
|
authorityProvider;
|
|
71
70
|
snapshotLifecycle;
|
|
71
|
+
allowHostSessionRecording;
|
|
72
72
|
contexts = new Map();
|
|
73
73
|
ownsState;
|
|
74
74
|
mutations;
|
|
75
|
+
workflowMessages;
|
|
75
76
|
constructor(databasePath = workflowStatePath(), options = {}) {
|
|
76
77
|
this.authorityProvider = options.authorityProvider;
|
|
77
78
|
this.snapshotLifecycle = options.snapshotLifecycle;
|
|
79
|
+
this.allowHostSessionRecording = options.allowHostSessionRecording === true;
|
|
78
80
|
this.ownsState = options.state === undefined;
|
|
79
81
|
this.state =
|
|
80
82
|
options.state ??
|
|
@@ -85,6 +87,7 @@ export class WorkflowRunStore {
|
|
|
85
87
|
});
|
|
86
88
|
this.databasePath = this.state.filePath;
|
|
87
89
|
this.mutations = new StateMutationStore(this.state);
|
|
90
|
+
this.workflowMessages = new WorkflowMessageStore(this.state);
|
|
88
91
|
}
|
|
89
92
|
close() {
|
|
90
93
|
if (this.ownsState)
|
|
@@ -333,6 +336,15 @@ export class WorkflowRunStore {
|
|
|
333
336
|
return;
|
|
334
337
|
}
|
|
335
338
|
const definitionHash = this.state.putJson(snapshot, now);
|
|
339
|
+
const parentLineage = state.parentRunId === undefined
|
|
340
|
+
? undefined
|
|
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}`);
|
|
347
|
+
}
|
|
336
348
|
this.state.connection
|
|
337
349
|
.prepare(`INSERT INTO workflow_definitions(
|
|
338
350
|
definition_digest, workflow_name, definition_hash, created_at
|
|
@@ -355,12 +367,13 @@ export class WorkflowRunStore {
|
|
|
355
367
|
const errorHash = state.error === undefined ? null : this.state.putText(state.error, now);
|
|
356
368
|
this.state.connection
|
|
357
369
|
.prepare(`INSERT INTO runs(
|
|
358
|
-
run_id, resource_id, project_id, parent_run_id,
|
|
370
|
+
run_id, resource_id, project_id, parent_run_id, root_run_id, lineage_kind,
|
|
371
|
+
restart_number, parent_terminal_fingerprint, definition_digest,
|
|
359
372
|
workflow_ref, launch_options_hash, title, status, paused,
|
|
360
373
|
status_detail, input_hash, final_output_hash, error_hash,
|
|
361
374
|
created_at, updated_at, finished_at
|
|
362
|
-
) VALUES (?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
363
|
-
.run(state.runId, resourceId, state.parentRunId ?? null, 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));
|
|
375
|
+
) VALUES (?, ?, NULL, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
376
|
+
.run(state.runId, resourceId, state.parentRunId ?? null, isRunLineageRow(parentLineage) ? parentLineage.rootRunId : state.runId, state.parentRunId === undefined ? null : "continuation", isRunLineageRow(parentLineage) ? parentLineage.restartNumber : 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));
|
|
364
377
|
initializeViewerRun(this.state, state.runId, now);
|
|
365
378
|
this.insertRunSources(state.runId, source, state.workflowSources ?? []);
|
|
366
379
|
this.syncContinuationIdentity(state);
|
|
@@ -585,59 +598,49 @@ export class WorkflowRunStore {
|
|
|
585
598
|
if (Buffer.byteLength(request.prompt, "utf8") > 64 * 1024) {
|
|
586
599
|
throw new Error("Workflow follow-up prompt cannot exceed 65536 bytes");
|
|
587
600
|
}
|
|
588
|
-
|
|
601
|
+
return this.state.transaction(() => {
|
|
589
602
|
const prior = this.followUpRowByRequest(request.runId, request.requestId);
|
|
590
603
|
if (prior !== undefined) {
|
|
591
604
|
this.assertSameFollowUp(prior, request);
|
|
592
605
|
return { followUp: this.followUpRecord(prior), adopted: true };
|
|
593
606
|
}
|
|
594
607
|
this.requireRunAcceptsSettings(request.runId, true);
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
const result = this.mutations.mutate({
|
|
598
|
-
resourceId: queue.resourceId,
|
|
599
|
-
operation: "follow-up.queue",
|
|
600
|
-
actor: request.actor,
|
|
601
|
-
expectedRevision: queue.revision,
|
|
602
|
-
}, "follow-up.queued", ({ database, now }) => {
|
|
603
|
-
this.requireRunAcceptsSettings(request.runId, true);
|
|
604
|
-
const currentQueue = this.requireFollowUpQueueRow(request.runId);
|
|
605
|
-
if (currentQueue.originSessionId !== null &&
|
|
606
|
-
currentQueue.originSessionId !== request.targetSessionId) {
|
|
607
|
-
throw new Error("Workflow follow-ups must target the run's origin Pi session");
|
|
608
|
-
}
|
|
609
|
-
if (currentQueue.originSessionId === null) {
|
|
610
|
-
database.connection
|
|
611
|
-
.prepare(`UPDATE workflow_follow_up_queues
|
|
612
|
-
SET origin_session_id = ?, updated_at = ? WHERE run_id = ?`)
|
|
613
|
-
.run(request.targetSessionId, now, request.runId);
|
|
614
|
-
}
|
|
615
|
-
const order = this.nextFollowUpOrder(request.runId);
|
|
616
|
-
const followUpId = followUpIdFor(request.runId, request.requestId);
|
|
617
|
-
const resourceId = this.mutations.ensureResource("follow_up", followUpId, now);
|
|
618
|
-
const promptHash = database.putText(request.prompt, now);
|
|
619
|
-
database.connection
|
|
620
|
-
.prepare(`INSERT INTO workflow_follow_ups(
|
|
621
|
-
follow_up_id, resource_id, queue_resource_id, run_id,
|
|
622
|
-
request_id, order_number, target_session_id,
|
|
623
|
-
actor_type, actor_id, source_type, prompt_hash, status,
|
|
624
|
-
created_at, updated_at
|
|
625
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'queued', ?, ?)`)
|
|
626
|
-
.run(followUpId, resourceId, queue.resourceId, request.runId, request.requestId, order, request.targetSessionId, request.actor.type, request.actor.id ?? null, request.source, promptHash, now, now);
|
|
627
|
-
recordViewerDeltas(this.state, request.runId, this.viewerInspectorTargets(request.runId), now);
|
|
628
|
-
return followUpId;
|
|
629
|
-
}, { payload: { runId: request.runId, requestId: request.requestId } });
|
|
630
|
-
return {
|
|
631
|
-
followUp: this.followUpRecord(this.requireFollowUpRow(result.value)),
|
|
632
|
-
adopted: false,
|
|
633
|
-
};
|
|
634
|
-
}
|
|
635
|
-
catch (error) {
|
|
636
|
-
if (!(error instanceof StaleResourceError))
|
|
637
|
-
throw error;
|
|
608
|
+
if (this.originSessionId(request.runId) !== request.targetSessionId) {
|
|
609
|
+
throw new Error("Workflow follow-ups must target the run's origin Pi session");
|
|
638
610
|
}
|
|
639
|
-
|
|
640
|
-
|
|
611
|
+
const now = Date.now();
|
|
612
|
+
const order = this.nextFollowUpOrder(request.runId);
|
|
613
|
+
const followUpId = followUpIdFor(request.runId, request.requestId);
|
|
614
|
+
const resourceId = this.mutations.ensureResource("follow_up", followUpId, now);
|
|
615
|
+
const promptHash = this.state.putText(request.prompt, now);
|
|
616
|
+
this.state.connection
|
|
617
|
+
.prepare(`INSERT INTO workflow_follow_ups(
|
|
618
|
+
follow_up_id, resource_id, run_id, request_id, order_number,
|
|
619
|
+
target_session_id, actor_type, actor_id, source_type, prompt_hash,
|
|
620
|
+
status, created_at, updated_at
|
|
621
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'queued', ?, ?)`)
|
|
622
|
+
.run(followUpId, resourceId, request.runId, request.requestId, order, request.targetSessionId, request.actor.type, request.actor.id ?? null, request.source, promptHash, now, now);
|
|
623
|
+
const workflowMessageId = workflowMessageIdFor("followUp", followUpId, "initial");
|
|
624
|
+
this.workflowMessages.create({
|
|
625
|
+
workflowMessageId,
|
|
626
|
+
runId: request.runId,
|
|
627
|
+
targetSessionId: request.targetSessionId,
|
|
628
|
+
kind: "followUp",
|
|
629
|
+
sourceId: followUpId,
|
|
630
|
+
idempotencyKey: "initial",
|
|
631
|
+
content: followUpWorkflowMessageContent({
|
|
632
|
+
workflowMessageId,
|
|
633
|
+
followUpId,
|
|
634
|
+
runId: request.runId,
|
|
635
|
+
prompt: request.prompt,
|
|
636
|
+
}),
|
|
637
|
+
now,
|
|
638
|
+
});
|
|
639
|
+
this.bumpResource(resourceId, 0, now);
|
|
640
|
+
insertGenericEvent(this.state, resourceId, 1, "follow-up.queued", request.actor.type, request.actor.id ?? null, this.state.putJson({ runId: request.runId, requestId: request.requestId }, now), now);
|
|
641
|
+
recordViewerDeltas(this.state, request.runId, this.viewerInspectorTargets(request.runId), now);
|
|
642
|
+
return { followUp: this.followUpRecord(this.requireFollowUpRow(followUpId)), adopted: false };
|
|
643
|
+
});
|
|
641
644
|
}
|
|
642
645
|
removeFollowUp(request) {
|
|
643
646
|
assertSourceType(request.source);
|
|
@@ -647,12 +650,13 @@ export class WorkflowRunStore {
|
|
|
647
650
|
}
|
|
648
651
|
if (row.status === "removed")
|
|
649
652
|
return this.followUpRecord(row);
|
|
650
|
-
if (row.status === "
|
|
651
|
-
throw new Error(
|
|
653
|
+
if (row.status === "cancelled") {
|
|
654
|
+
throw new Error("A cancelled workflow follow-up cannot be removed");
|
|
652
655
|
}
|
|
653
656
|
this.assertFollowUpRemovalAuthority(row, request.actor, request.source);
|
|
654
|
-
|
|
655
|
-
|
|
657
|
+
const message = this.workflowMessages.latestForSource("followUp", request.followUpId);
|
|
658
|
+
if (message?.status === "sent") {
|
|
659
|
+
throw new Error("A sent workflow follow-up cannot be removed");
|
|
656
660
|
}
|
|
657
661
|
const reason = "Removed before delivery";
|
|
658
662
|
this.mutations.mutate({
|
|
@@ -665,21 +669,20 @@ export class WorkflowRunStore {
|
|
|
665
669
|
const update = database.connection
|
|
666
670
|
.prepare(`UPDATE workflow_follow_ups
|
|
667
671
|
SET status = 'removed', reason_hash = ?, updated_at = ?
|
|
668
|
-
WHERE follow_up_id = ? AND status
|
|
672
|
+
WHERE follow_up_id = ? AND status = 'queued'`)
|
|
669
673
|
.run(reasonHash, now, request.followUpId);
|
|
670
674
|
if (update.changes !== 1) {
|
|
671
675
|
throw new StaleResourceError("Workflow follow-up changed before removal");
|
|
672
676
|
}
|
|
677
|
+
this.workflowMessages.cancelPendingForSource(request.followUpId, "followUp", now);
|
|
673
678
|
recordViewerDeltas(this.state, request.runId, this.viewerInspectorTargets(request.runId), now);
|
|
674
679
|
}, { payload: { followUpId: request.followUpId, source: request.source } });
|
|
675
680
|
return this.followUpRecord(this.requireFollowUpRow(request.followUpId));
|
|
676
681
|
}
|
|
677
682
|
originSessionId(runId) {
|
|
678
683
|
const row = this.state.connection
|
|
679
|
-
.prepare(`SELECT
|
|
680
|
-
FROM runs r
|
|
681
|
-
LEFT JOIN workflow_follow_up_queues q ON q.run_id = r.run_id
|
|
682
|
-
LEFT JOIN run_bindings b ON b.run_id = r.run_id
|
|
684
|
+
.prepare(`SELECT b.origin_session_id AS originSessionId
|
|
685
|
+
FROM runs r LEFT JOIN run_bindings b ON b.run_id = r.run_id
|
|
683
686
|
WHERE r.run_id = ?`)
|
|
684
687
|
.get(runId);
|
|
685
688
|
return isRecord(row) && typeof row.originSessionId === "string"
|
|
@@ -687,176 +690,26 @@ export class WorkflowRunStore {
|
|
|
687
690
|
: undefined;
|
|
688
691
|
}
|
|
689
692
|
readFollowUpQueue(runId) {
|
|
690
|
-
const
|
|
691
|
-
if (
|
|
693
|
+
const originSessionId = this.originSessionId(runId);
|
|
694
|
+
if (originSessionId === undefined)
|
|
692
695
|
return undefined;
|
|
693
696
|
const followUps = this.state.connection
|
|
694
697
|
.prepare(`${FOLLOW_UP_ROW_SELECT} WHERE f.run_id = ? ORDER BY f.order_number`)
|
|
695
698
|
.all(runId)
|
|
696
699
|
.filter(isFollowUpRow)
|
|
697
700
|
.map((row) => this.followUpRecord(row));
|
|
698
|
-
return {
|
|
699
|
-
runId,
|
|
700
|
-
...(queue.originSessionId !== null ? { originSessionId: queue.originSessionId } : {}),
|
|
701
|
-
presentationState: assertPresentationState(queue.presentationState),
|
|
702
|
-
...(queue.presentationEntryId !== null
|
|
703
|
-
? { presentationEntryId: queue.presentationEntryId }
|
|
704
|
-
: {}),
|
|
705
|
-
...(queue.presentationAssistantEntryId !== null
|
|
706
|
-
? { presentationAssistantEntryId: queue.presentationAssistantEntryId }
|
|
707
|
-
: {}),
|
|
708
|
-
...(queue.presentationReasonHash !== null
|
|
709
|
-
? { presentationReason: this.readText(queue.presentationReasonHash) }
|
|
710
|
-
: {}),
|
|
711
|
-
followUps,
|
|
712
|
-
};
|
|
701
|
+
return { runId, originSessionId, followUps };
|
|
713
702
|
}
|
|
714
703
|
readFollowUpQueueRange(runId, range) {
|
|
715
|
-
const
|
|
716
|
-
if (
|
|
704
|
+
const originSessionId = this.originSessionId(runId);
|
|
705
|
+
if (originSessionId === undefined)
|
|
717
706
|
return null;
|
|
718
707
|
const followUps = this.state.connection
|
|
719
708
|
.prepare(`${FOLLOW_UP_ROW_SELECT} WHERE f.run_id = ? ORDER BY f.order_number LIMIT ? OFFSET ?`)
|
|
720
709
|
.all(runId, range.limit, range.start)
|
|
721
710
|
.filter(isFollowUpRow)
|
|
722
711
|
.map((row) => this.followUpRecord(row));
|
|
723
|
-
return {
|
|
724
|
-
runId,
|
|
725
|
-
...(queue.originSessionId !== null ? { originSessionId: queue.originSessionId } : {}),
|
|
726
|
-
presentationState: assertPresentationState(queue.presentationState),
|
|
727
|
-
...(queue.presentationEntryId !== null
|
|
728
|
-
? { presentationEntryId: queue.presentationEntryId }
|
|
729
|
-
: {}),
|
|
730
|
-
...(queue.presentationAssistantEntryId !== null
|
|
731
|
-
? { presentationAssistantEntryId: queue.presentationAssistantEntryId }
|
|
732
|
-
: {}),
|
|
733
|
-
...(queue.presentationReasonHash !== null
|
|
734
|
-
? { presentationReason: this.readText(queue.presentationReasonHash) }
|
|
735
|
-
: {}),
|
|
736
|
-
followUps,
|
|
737
|
-
};
|
|
738
|
-
}
|
|
739
|
-
listPendingPresentations(targetSessionId) {
|
|
740
|
-
return this.state.connection
|
|
741
|
-
.prepare(`SELECT run_id AS runId FROM workflow_follow_up_queues
|
|
742
|
-
WHERE origin_session_id = ? AND presentation_state = 'pending'
|
|
743
|
-
ORDER BY created_at`)
|
|
744
|
-
.all(targetSessionId)
|
|
745
|
-
.flatMap((row) => (isRecord(row) && typeof row.runId === "string" ? [row.runId] : []));
|
|
746
|
-
}
|
|
747
|
-
settleFollowUpPresentation(options) {
|
|
748
|
-
const queue = this.requireFollowUpQueueRow(options.runId);
|
|
749
|
-
if (queue.presentationState === options.state) {
|
|
750
|
-
return this.readFollowUpQueue(options.runId);
|
|
751
|
-
}
|
|
752
|
-
if (queue.presentationState !== "pending") {
|
|
753
|
-
throw new Error(`Workflow presentation is ${queue.presentationState}, not pending for ${options.runId}`);
|
|
754
|
-
}
|
|
755
|
-
if (options.state === "settled" &&
|
|
756
|
-
(options.presentationEntryId === undefined || options.assistantEntryId === undefined)) {
|
|
757
|
-
throw new Error("Settled workflow presentation requires both session entry IDs");
|
|
758
|
-
}
|
|
759
|
-
if (options.state === "unavailable" && !options.reason?.trim()) {
|
|
760
|
-
throw new Error("Unavailable workflow presentation requires a reason");
|
|
761
|
-
}
|
|
762
|
-
try {
|
|
763
|
-
this.mutations.mutate({
|
|
764
|
-
resourceId: queue.resourceId,
|
|
765
|
-
operation: "follow-up.presentation",
|
|
766
|
-
actor: {
|
|
767
|
-
type: "session",
|
|
768
|
-
...(queue.originSessionId !== null ? { id: queue.originSessionId } : {}),
|
|
769
|
-
},
|
|
770
|
-
expectedRevision: queue.revision,
|
|
771
|
-
}, `follow-up.presentation-${options.state}`, ({ database, now }) => {
|
|
772
|
-
const reasonHash = options.state === "unavailable"
|
|
773
|
-
? database.putText(options.reason, now)
|
|
774
|
-
: null;
|
|
775
|
-
database.connection
|
|
776
|
-
.prepare(`UPDATE workflow_follow_up_queues
|
|
777
|
-
SET presentation_state = ?, presentation_entry_id = ?,
|
|
778
|
-
presentation_assistant_entry_id = ?, presentation_reason_hash = ?,
|
|
779
|
-
presentation_updated_at = ?, updated_at = ?
|
|
780
|
-
WHERE run_id = ? AND presentation_state = 'pending'`)
|
|
781
|
-
.run(options.state, options.presentationEntryId ?? null, options.assistantEntryId ?? null, reasonHash, now, now, options.runId);
|
|
782
|
-
database.connection
|
|
783
|
-
.prepare(`UPDATE workflow_follow_ups SET status = 'ready', updated_at = ?
|
|
784
|
-
WHERE run_id = ? AND status = 'pending_presentation'`)
|
|
785
|
-
.run(now, options.runId);
|
|
786
|
-
recordViewerDeltas(this.state, options.runId, this.viewerInspectorTargets(options.runId), now);
|
|
787
|
-
}, { payload: { runId: options.runId, state: options.state } });
|
|
788
|
-
}
|
|
789
|
-
catch (error) {
|
|
790
|
-
if (error instanceof StaleResourceError) {
|
|
791
|
-
const current = this.requireFollowUpQueueRow(options.runId);
|
|
792
|
-
if (current.presentationState === options.state) {
|
|
793
|
-
return this.readFollowUpQueue(options.runId);
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
throw error;
|
|
797
|
-
}
|
|
798
|
-
return this.readFollowUpQueue(options.runId);
|
|
799
|
-
}
|
|
800
|
-
claimNextFollowUp(targetSessionId, ownerId, leaseMs = 30_000) {
|
|
801
|
-
const rows = this.state.connection
|
|
802
|
-
.prepare(`${FOLLOW_UP_ROW_SELECT}
|
|
803
|
-
WHERE f.target_session_id = ? AND f.status = 'ready'
|
|
804
|
-
ORDER BY f.created_at, f.order_number LIMIT 16`)
|
|
805
|
-
.all(targetSessionId)
|
|
806
|
-
.filter(isFollowUpRow);
|
|
807
|
-
for (const row of rows) {
|
|
808
|
-
let claim;
|
|
809
|
-
try {
|
|
810
|
-
claim = this.mutations.claim({
|
|
811
|
-
resourceId: row.resourceId,
|
|
812
|
-
ownerType: "session",
|
|
813
|
-
ownerId,
|
|
814
|
-
expectedRevision: row.revision,
|
|
815
|
-
leaseMs,
|
|
816
|
-
});
|
|
817
|
-
}
|
|
818
|
-
catch (error) {
|
|
819
|
-
if (error instanceof StaleResourceError)
|
|
820
|
-
continue;
|
|
821
|
-
throw error;
|
|
822
|
-
}
|
|
823
|
-
if (claim === undefined)
|
|
824
|
-
continue;
|
|
825
|
-
const current = this.requireFollowUpRow(row.followUpId);
|
|
826
|
-
if (current.status !== "ready") {
|
|
827
|
-
this.mutations.release(claim, claim.resourceRevision);
|
|
828
|
-
continue;
|
|
829
|
-
}
|
|
830
|
-
return { followUp: this.followUpRecord(current), claim };
|
|
831
|
-
}
|
|
832
|
-
return undefined;
|
|
833
|
-
}
|
|
834
|
-
markFollowUpSent(followUpId, claim, sessionEntryId) {
|
|
835
|
-
const row = this.requireFollowUpRow(followUpId);
|
|
836
|
-
if (row.status === "sent")
|
|
837
|
-
return this.followUpRecord(row);
|
|
838
|
-
this.mutations.mutate({
|
|
839
|
-
resourceId: row.resourceId,
|
|
840
|
-
operation: "follow-up.sent",
|
|
841
|
-
actor: { type: "session", id: claim.ownerId },
|
|
842
|
-
expectedRevision: claim.resourceRevision,
|
|
843
|
-
lease: claim,
|
|
844
|
-
}, "follow-up.sent", ({ database, now }) => {
|
|
845
|
-
const update = database.connection
|
|
846
|
-
.prepare(`UPDATE workflow_follow_ups
|
|
847
|
-
SET status = 'sent', session_entry_id = ?, sent_at = ?, updated_at = ?
|
|
848
|
-
WHERE follow_up_id = ? AND status = 'ready'`)
|
|
849
|
-
.run(sessionEntryId, now, now, followUpId);
|
|
850
|
-
if (update.changes !== 1) {
|
|
851
|
-
throw new StaleResourceError("Workflow follow-up changed before delivery completed");
|
|
852
|
-
}
|
|
853
|
-
recordViewerDeltas(this.state, row.runId, this.viewerInspectorTargets(row.runId), now);
|
|
854
|
-
}, { payload: { followUpId, sessionEntryId } });
|
|
855
|
-
return this.followUpRecord(this.requireFollowUpRow(followUpId));
|
|
856
|
-
}
|
|
857
|
-
releaseFollowUpClaim(claim) {
|
|
858
|
-
const revision = this.requireResourceRevision(claim.resourceId);
|
|
859
|
-
this.mutations.release(claim, revision);
|
|
712
|
+
return { runId, originSessionId, followUps };
|
|
860
713
|
}
|
|
861
714
|
async prepareRunResume(runId) {
|
|
862
715
|
const loaded = this.readRun(runId, { includeTrace: true });
|
|
@@ -868,7 +721,6 @@ export class WorkflowRunStore {
|
|
|
868
721
|
this.verifySettingsProjections(runId);
|
|
869
722
|
const revision = this.resourceRevision(runId);
|
|
870
723
|
this.contexts.set(runId, { revision, lock: Promise.resolve() });
|
|
871
|
-
this.finalizeRecordingCaptures(runId, "Workflow host stopped before the run finished");
|
|
872
724
|
this.state.transaction(() => {
|
|
873
725
|
const row = this.requireRunRow(runId);
|
|
874
726
|
const context = this.contextFor(runId);
|
|
@@ -1051,7 +903,7 @@ export class WorkflowRunStore {
|
|
|
1051
903
|
this.state.transaction(() => {
|
|
1052
904
|
const run = this.requireRunRow(runId);
|
|
1053
905
|
const context = this.contextFor(runId);
|
|
1054
|
-
this.
|
|
906
|
+
this.assertSessionWriteAuthority(runId);
|
|
1055
907
|
const now = Date.parse(binding.boundAt);
|
|
1056
908
|
const resourceId = resourceIdFor("session", segmentId);
|
|
1057
909
|
const bindingHash = this.state.putJson(binding, now);
|
|
@@ -1075,11 +927,6 @@ export class WorkflowRunStore {
|
|
|
1075
927
|
VALUES (?, ?, 'interactive', ?)
|
|
1076
928
|
ON CONFLICT(run_id) DO NOTHING`)
|
|
1077
929
|
.run(runId, binding.piSessionId, now);
|
|
1078
|
-
this.state.connection
|
|
1079
|
-
.prepare(`UPDATE workflow_follow_up_queues
|
|
1080
|
-
SET origin_session_id = COALESCE(origin_session_id, ?), updated_at = ?
|
|
1081
|
-
WHERE run_id = ?`)
|
|
1082
|
-
.run(binding.piSessionId, now, runId);
|
|
1083
930
|
const revision = context.revision + 1;
|
|
1084
931
|
const at = new Date(now).toISOString();
|
|
1085
932
|
const traceEvent = {
|
|
@@ -1659,6 +1506,8 @@ export class WorkflowRunStore {
|
|
|
1659
1506
|
}
|
|
1660
1507
|
assertSessionWriteAuthority(runId) {
|
|
1661
1508
|
const run = this.requireRunRow(runId);
|
|
1509
|
+
if (this.allowHostSessionRecording)
|
|
1510
|
+
return;
|
|
1662
1511
|
this.assertWriteAuthority(run, this.contextFor(runId).revision);
|
|
1663
1512
|
}
|
|
1664
1513
|
resourceRevision(runId) {
|
|
@@ -1808,10 +1657,8 @@ export class WorkflowRunStore {
|
|
|
1808
1657
|
order: row.orderNumber,
|
|
1809
1658
|
state: row.status,
|
|
1810
1659
|
source: row.sourceType,
|
|
1811
|
-
sessionEntryId: row.sessionEntryId,
|
|
1812
1660
|
}));
|
|
1813
1661
|
const followUpStart = Math.max(0, followUps.length - VIEWER_PAGE_SIZE);
|
|
1814
|
-
const queue = this.requireFollowUpQueueRow(runId);
|
|
1815
1662
|
return [
|
|
1816
1663
|
{
|
|
1817
1664
|
targetType: "inspector",
|
|
@@ -1833,10 +1680,7 @@ export class WorkflowRunStore {
|
|
|
1833
1680
|
{
|
|
1834
1681
|
op: "replace",
|
|
1835
1682
|
path: "/followUpQueue",
|
|
1836
|
-
value: parseJson(canonicalJson({
|
|
1837
|
-
presentationState: queue.presentationState,
|
|
1838
|
-
items: followUps.slice(followUpStart),
|
|
1839
|
-
})),
|
|
1683
|
+
value: parseJson(canonicalJson({ items: followUps.slice(followUpStart) })),
|
|
1840
1684
|
},
|
|
1841
1685
|
{ op: "replace", path: "/followUpStart", value: followUpStart },
|
|
1842
1686
|
{ op: "replace", path: "/followUpTotal", value: followUps.length },
|
|
@@ -1845,21 +1689,7 @@ export class WorkflowRunStore {
|
|
|
1845
1689
|
];
|
|
1846
1690
|
}
|
|
1847
1691
|
initializeRunSettingsAndFollowUps(state, initialSettings, now) {
|
|
1848
|
-
const childQueueResourceId = this.ensureFollowUpQueue(state.runId, now);
|
|
1849
1692
|
if (state.parentRunId !== undefined) {
|
|
1850
|
-
const parentQueue = this.followUpQueueRow(state.parentRunId);
|
|
1851
|
-
if (parentQueue !== undefined) {
|
|
1852
|
-
this.state.connection
|
|
1853
|
-
.prepare(`UPDATE workflow_follow_up_queues
|
|
1854
|
-
SET origin_session_id = COALESCE(origin_session_id, ?), updated_at = ?
|
|
1855
|
-
WHERE run_id = ?`)
|
|
1856
|
-
.run(parentQueue.originSessionId, now, state.runId);
|
|
1857
|
-
this.state.connection
|
|
1858
|
-
.prepare(`UPDATE workflow_follow_ups
|
|
1859
|
-
SET run_id = ?, queue_resource_id = ?, updated_at = ?
|
|
1860
|
-
WHERE run_id = ? AND status = 'queued'`)
|
|
1861
|
-
.run(state.runId, childQueueResourceId, now, state.parentRunId);
|
|
1862
|
-
}
|
|
1863
1693
|
this.state.connection
|
|
1864
1694
|
.prepare(`UPDATE workflow_settings SET active_run_id = ?, updated_at = ? WHERE active_run_id = ?`)
|
|
1865
1695
|
.run(state.runId, now, state.parentRunId);
|
|
@@ -1884,16 +1714,6 @@ export class WorkflowRunStore {
|
|
|
1884
1714
|
.run(scopeId, resourceId, originRunId, state.runId, scope.mountPath, scope.invocation, settingsHash, settingsHash, now, now);
|
|
1885
1715
|
}
|
|
1886
1716
|
}
|
|
1887
|
-
ensureFollowUpQueue(runId, now) {
|
|
1888
|
-
const resourceId = this.mutations.ensureResource("follow_up_queue", runId, now);
|
|
1889
|
-
this.state.connection
|
|
1890
|
-
.prepare(`INSERT INTO workflow_follow_up_queues(
|
|
1891
|
-
run_id, resource_id, created_at, updated_at
|
|
1892
|
-
) VALUES (?, ?, ?, ?)
|
|
1893
|
-
ON CONFLICT(run_id) DO NOTHING`)
|
|
1894
|
-
.run(runId, resourceId, now, now);
|
|
1895
|
-
return resourceId;
|
|
1896
|
-
}
|
|
1897
1717
|
logicalOriginRunId(runId) {
|
|
1898
1718
|
let current = runId;
|
|
1899
1719
|
for (let depth = 0; depth < 100; depth += 1) {
|
|
@@ -2026,27 +1846,6 @@ export class WorkflowRunStore {
|
|
|
2026
1846
|
throw new Error(`Workflow settings request ID was reused with different content`);
|
|
2027
1847
|
}
|
|
2028
1848
|
}
|
|
2029
|
-
followUpQueueRow(runId) {
|
|
2030
|
-
const row = this.state.connection
|
|
2031
|
-
.prepare(`SELECT q.run_id AS runId, q.resource_id AS resourceId,
|
|
2032
|
-
q.origin_session_id AS originSessionId,
|
|
2033
|
-
q.presentation_state AS presentationState,
|
|
2034
|
-
q.presentation_entry_id AS presentationEntryId,
|
|
2035
|
-
q.presentation_assistant_entry_id AS presentationAssistantEntryId,
|
|
2036
|
-
q.presentation_reason_hash AS presentationReasonHash,
|
|
2037
|
-
r.revision
|
|
2038
|
-
FROM workflow_follow_up_queues q
|
|
2039
|
-
JOIN resources r ON r.resource_id = q.resource_id
|
|
2040
|
-
WHERE q.run_id = ?`)
|
|
2041
|
-
.get(runId);
|
|
2042
|
-
return isFollowUpQueueRow(row) ? row : undefined;
|
|
2043
|
-
}
|
|
2044
|
-
requireFollowUpQueueRow(runId) {
|
|
2045
|
-
const row = this.followUpQueueRow(runId);
|
|
2046
|
-
if (row === undefined)
|
|
2047
|
-
throw new Error(`Workflow follow-up queue not found: ${runId}`);
|
|
2048
|
-
return row;
|
|
2049
|
-
}
|
|
2050
1849
|
followUpRowByRequest(runId, requestId) {
|
|
2051
1850
|
const row = this.state.connection
|
|
2052
1851
|
.prepare(`${FOLLOW_UP_ROW_SELECT} WHERE f.run_id = ? AND f.request_id = ?`)
|
|
@@ -2079,11 +1878,9 @@ export class WorkflowRunStore {
|
|
|
2079
1878
|
source: row.sourceType,
|
|
2080
1879
|
prompt: this.readText(row.promptHash),
|
|
2081
1880
|
state: assertFollowUpState(row.status),
|
|
2082
|
-
...(row.sessionEntryId !== null ? { sessionEntryId: row.sessionEntryId } : {}),
|
|
2083
1881
|
...(row.reasonHash !== null ? { reason: this.readText(row.reasonHash) } : {}),
|
|
2084
1882
|
createdAt: new Date(row.createdAt).toISOString(),
|
|
2085
1883
|
updatedAt: new Date(row.updatedAt).toISOString(),
|
|
2086
|
-
...(row.sentAt !== null ? { sentAt: new Date(row.sentAt).toISOString() } : {}),
|
|
2087
1884
|
};
|
|
2088
1885
|
}
|
|
2089
1886
|
assertSameFollowUp(row, request) {
|
|
@@ -2107,12 +1904,6 @@ export class WorkflowRunStore {
|
|
|
2107
1904
|
}
|
|
2108
1905
|
throw new Error("Workflow follow-up can be removed only by its source or a verified human");
|
|
2109
1906
|
}
|
|
2110
|
-
hasLiveLease(resourceId) {
|
|
2111
|
-
const row = this.state.connection
|
|
2112
|
-
.prepare("SELECT expires_at AS expiresAt FROM leases WHERE resource_id = ?")
|
|
2113
|
-
.get(resourceId);
|
|
2114
|
-
return isLeaseExpiryRow(row) && row.expiresAt !== null && row.expiresAt > Date.now();
|
|
2115
|
-
}
|
|
2116
1907
|
nextFollowUpOrder(runId) {
|
|
2117
1908
|
const row = this.state.connection
|
|
2118
1909
|
.prepare(`SELECT COALESCE(MAX(order_number), 0) + 1 AS nextOrder
|
|
@@ -2137,37 +1928,37 @@ export class WorkflowRunStore {
|
|
|
2137
1928
|
throw new StaleResourceError(`Settings route ${event.nodeId} used change ${changeNumber}, current change is ${scope.revision}`);
|
|
2138
1929
|
}
|
|
2139
1930
|
}
|
|
2140
|
-
transitionFollowUpsForRunState(state,
|
|
2141
|
-
if (state.status === "running" || state.status === "waiting")
|
|
1931
|
+
transitionFollowUpsForRunState(state, _event, now) {
|
|
1932
|
+
if (state.status === "running" || state.status === "waiting" || state.status === "completed") {
|
|
2142
1933
|
return;
|
|
2143
|
-
const queue = this.followUpQueueRow(state.runId);
|
|
2144
|
-
if (queue === undefined || queue.presentationState !== "none")
|
|
2145
|
-
return;
|
|
2146
|
-
let presentationState = "not-needed";
|
|
2147
|
-
let followUpState = "cancelled";
|
|
2148
|
-
let reasonHash = null;
|
|
2149
|
-
if (state.status === "completed") {
|
|
2150
|
-
const required = event.payload.presentationRequired === true;
|
|
2151
|
-
presentationState = required ? "pending" : "not-needed";
|
|
2152
|
-
followUpState = required ? "pending_presentation" : "ready";
|
|
2153
1934
|
}
|
|
2154
|
-
|
|
2155
|
-
|
|
1935
|
+
const rows = this.state.connection
|
|
1936
|
+
.prepare(`${FOLLOW_UP_ROW_SELECT}
|
|
1937
|
+
WHERE f.run_id IN (
|
|
1938
|
+
WITH RECURSIVE ancestors(run_id, parent_run_id) AS (
|
|
1939
|
+
SELECT run_id, parent_run_id FROM runs WHERE run_id = ?
|
|
1940
|
+
UNION ALL
|
|
1941
|
+
SELECT parent.run_id, parent.parent_run_id
|
|
1942
|
+
FROM runs parent JOIN ancestors ON ancestors.parent_run_id = parent.run_id
|
|
1943
|
+
)
|
|
1944
|
+
SELECT run_id FROM ancestors
|
|
1945
|
+
) AND f.status = 'queued'`)
|
|
1946
|
+
.all(state.runId)
|
|
1947
|
+
.filter(isFollowUpRow);
|
|
1948
|
+
for (const row of rows) {
|
|
1949
|
+
const reason = state.error ?? `Workflow ended with status ${state.status}`;
|
|
1950
|
+
const reasonHash = this.state.putText(reason, now);
|
|
1951
|
+
const update = this.state.connection
|
|
1952
|
+
.prepare(`UPDATE workflow_follow_ups
|
|
1953
|
+
SET status = 'cancelled', reason_hash = ?, updated_at = ?
|
|
1954
|
+
WHERE follow_up_id = ? AND status = 'queued'`)
|
|
1955
|
+
.run(reasonHash, now, row.followUpId);
|
|
1956
|
+
if (update.changes !== 1)
|
|
1957
|
+
continue;
|
|
1958
|
+
this.workflowMessages.cancelPendingForSource(row.followUpId, "followUp", now);
|
|
1959
|
+
this.bumpResource(row.resourceId, row.revision, now);
|
|
1960
|
+
insertGenericEvent(this.state, row.resourceId, row.revision + 1, "follow-up.cancelled", "system", null, this.state.putJson({ runId: state.runId, status: state.status, reason }, now), now);
|
|
2156
1961
|
}
|
|
2157
|
-
this.state.connection
|
|
2158
|
-
.prepare(`UPDATE workflow_follow_up_queues
|
|
2159
|
-
SET presentation_state = ?, presentation_updated_at = ?, updated_at = ?
|
|
2160
|
-
WHERE run_id = ? AND presentation_state = 'none'`)
|
|
2161
|
-
.run(presentationState, now, now, state.runId);
|
|
2162
|
-
this.state.connection
|
|
2163
|
-
.prepare(`UPDATE workflow_follow_ups
|
|
2164
|
-
SET status = ?, reason_hash = ?, updated_at = ?
|
|
2165
|
-
WHERE run_id = ? AND status IN ('queued', 'pending_presentation', 'ready')`)
|
|
2166
|
-
.run(followUpState, reasonHash, now, state.runId);
|
|
2167
|
-
const revision = this.requireResourceRevision(queue.resourceId);
|
|
2168
|
-
this.bumpResource(queue.resourceId, revision, now);
|
|
2169
|
-
const payloadHash = this.state.putJson({ runId: state.runId, state: presentationState, followUps: followUpState }, now);
|
|
2170
|
-
insertGenericEvent(this.state, queue.resourceId, revision + 1, "follow-up.run-finished", "system", null, payloadHash, now);
|
|
2171
1962
|
}
|
|
2172
1963
|
syncNodeAttempts(state, snapshot, now) {
|
|
2173
1964
|
for (const step of state.steps.slice(state.carriedStepCount ?? 0)) {
|
|
@@ -2300,7 +2091,7 @@ export class WorkflowRunStore {
|
|
|
2300
2091
|
JOIN session_segments s ON s.segment_id = e.segment_id
|
|
2301
2092
|
JOIN blobs b ON b.blob_hash = e.entry_hash
|
|
2302
2093
|
WHERE s.run_id = ?
|
|
2303
|
-
AND json_extract(CAST(b.content AS TEXT), '$.customType') = 'pi-workflows-
|
|
2094
|
+
AND json_extract(CAST(b.content AS TEXT), '$.customType') = 'pi-workflows-step'
|
|
2304
2095
|
AND json_extract(CAST(b.content AS TEXT), '$.details.contract.attemptId') = ?
|
|
2305
2096
|
ORDER BY e.recorded_at DESC LIMIT 1`)
|
|
2306
2097
|
.get(runId, attemptId);
|
|
@@ -2438,7 +2229,13 @@ export class WorkflowRunStore {
|
|
|
2438
2229
|
buildRunState(row, snapshot, projectionSteps, visibleSteps, visibleUpdates) {
|
|
2439
2230
|
const sources = this.readRunSources(row.runId, snapshot);
|
|
2440
2231
|
const carriedStepCount = this.carriedStepCount(row.runId);
|
|
2441
|
-
const activeAttempt = row.status === "running"
|
|
2232
|
+
const activeAttempt = row.status === "running" || row.status === "waiting"
|
|
2233
|
+
? this.readActiveAttempt(row.runId)
|
|
2234
|
+
: undefined;
|
|
2235
|
+
const runningAttempt = row.status === "running" ? activeAttempt : undefined;
|
|
2236
|
+
const waitingNode = row.status === "waiting"
|
|
2237
|
+
? (activeAttempt?.nodeId ?? projectionSteps.at(-1)?.nodeId)
|
|
2238
|
+
: undefined;
|
|
2442
2239
|
const humanDecision = this.readHumanDecisionReceipt(row.runId);
|
|
2443
2240
|
const outputs = {};
|
|
2444
2241
|
const results = {};
|
|
@@ -2481,7 +2278,7 @@ export class WorkflowRunStore {
|
|
|
2481
2278
|
results,
|
|
2482
2279
|
steps: visibleSteps,
|
|
2483
2280
|
...(visibleUpdates.length === 0 ? {} : { updates: visibleUpdates }),
|
|
2484
|
-
...(
|
|
2281
|
+
...(runningAttempt === undefined ? {} : { currentNode: runningAttempt.nodeId }),
|
|
2485
2282
|
...(activeAttempt === undefined ? {} : { currentAttemptId: activeAttempt.attemptId }),
|
|
2486
2283
|
...(activeAttempt?.startedAt === null || activeAttempt === undefined
|
|
2487
2284
|
? {}
|
|
@@ -2497,9 +2294,7 @@ export class WorkflowRunStore {
|
|
|
2497
2294
|
...(row.statusDetail === null ? {} : { statusDetail: row.statusDetail }),
|
|
2498
2295
|
...(humanDecision === undefined ? {} : { humanDecision }),
|
|
2499
2296
|
...(row.paused === 0 ? {} : { paused: true }),
|
|
2500
|
-
...(
|
|
2501
|
-
? { waitingOn: projectionSteps.at(-1)?.nodeId }
|
|
2502
|
-
: {}),
|
|
2297
|
+
...(waitingNode === undefined ? {} : { waitingOn: waitingNode }),
|
|
2503
2298
|
...(row.finalOutputHash === null
|
|
2504
2299
|
? {}
|
|
2505
2300
|
: { finalOutput: this.state.readJson(row.finalOutputHash) }),
|
|
@@ -3334,26 +3129,11 @@ function assertActorType(value) {
|
|
|
3334
3129
|
return value;
|
|
3335
3130
|
}
|
|
3336
3131
|
function assertFollowUpState(value) {
|
|
3337
|
-
if (value !== "queued" &&
|
|
3338
|
-
value !== "pending_presentation" &&
|
|
3339
|
-
value !== "ready" &&
|
|
3340
|
-
value !== "sent" &&
|
|
3341
|
-
value !== "removed" &&
|
|
3342
|
-
value !== "cancelled") {
|
|
3132
|
+
if (value !== "queued" && value !== "removed" && value !== "cancelled") {
|
|
3343
3133
|
throw new Error(`Unknown workflow follow-up state: ${value}`);
|
|
3344
3134
|
}
|
|
3345
3135
|
return value;
|
|
3346
3136
|
}
|
|
3347
|
-
function assertPresentationState(value) {
|
|
3348
|
-
if (value !== "none" &&
|
|
3349
|
-
value !== "not-needed" &&
|
|
3350
|
-
value !== "pending" &&
|
|
3351
|
-
value !== "settled" &&
|
|
3352
|
-
value !== "unavailable") {
|
|
3353
|
-
throw new Error(`Unknown workflow presentation state: ${value}`);
|
|
3354
|
-
}
|
|
3355
|
-
return value;
|
|
3356
|
-
}
|
|
3357
3137
|
function isSettingsScopeRow(value) {
|
|
3358
3138
|
return (isRecord(value) &&
|
|
3359
3139
|
typeof value.scopeId === "string" &&
|
|
@@ -3395,23 +3175,10 @@ function isSettingsChangeRow(value) {
|
|
|
3395
3175
|
Buffer.isBuffer(value.afterHash) &&
|
|
3396
3176
|
typeof value.acceptedAt === "number");
|
|
3397
3177
|
}
|
|
3398
|
-
function isFollowUpQueueRow(value) {
|
|
3399
|
-
return (isRecord(value) &&
|
|
3400
|
-
typeof value.runId === "string" &&
|
|
3401
|
-
typeof value.resourceId === "string" &&
|
|
3402
|
-
(typeof value.originSessionId === "string" || value.originSessionId === null) &&
|
|
3403
|
-
typeof value.presentationState === "string" &&
|
|
3404
|
-
(typeof value.presentationEntryId === "string" || value.presentationEntryId === null) &&
|
|
3405
|
-
(typeof value.presentationAssistantEntryId === "string" ||
|
|
3406
|
-
value.presentationAssistantEntryId === null) &&
|
|
3407
|
-
(Buffer.isBuffer(value.presentationReasonHash) || value.presentationReasonHash === null) &&
|
|
3408
|
-
typeof value.revision === "number");
|
|
3409
|
-
}
|
|
3410
3178
|
function isFollowUpRow(value) {
|
|
3411
3179
|
return (isRecord(value) &&
|
|
3412
3180
|
typeof value.followUpId === "string" &&
|
|
3413
3181
|
typeof value.resourceId === "string" &&
|
|
3414
|
-
typeof value.queueResourceId === "string" &&
|
|
3415
3182
|
typeof value.runId === "string" &&
|
|
3416
3183
|
typeof value.requestId === "string" &&
|
|
3417
3184
|
typeof value.orderNumber === "number" &&
|
|
@@ -3421,16 +3188,19 @@ function isFollowUpRow(value) {
|
|
|
3421
3188
|
typeof value.sourceType === "string" &&
|
|
3422
3189
|
Buffer.isBuffer(value.promptHash) &&
|
|
3423
3190
|
typeof value.status === "string" &&
|
|
3424
|
-
(typeof value.sessionEntryId === "string" || value.sessionEntryId === null) &&
|
|
3425
3191
|
(Buffer.isBuffer(value.reasonHash) || value.reasonHash === null) &&
|
|
3426
3192
|
typeof value.createdAt === "number" &&
|
|
3427
3193
|
typeof value.updatedAt === "number" &&
|
|
3428
|
-
(typeof value.sentAt === "number" || value.sentAt === null) &&
|
|
3429
3194
|
typeof value.revision === "number");
|
|
3430
3195
|
}
|
|
3431
3196
|
function isParentRunRow(value) {
|
|
3432
3197
|
return isRecord(value) && (typeof value.parentRunId === "string" || value.parentRunId === null);
|
|
3433
3198
|
}
|
|
3199
|
+
function isRunLineageRow(value) {
|
|
3200
|
+
return (isRecord(value) &&
|
|
3201
|
+
typeof value.rootRunId === "string" &&
|
|
3202
|
+
typeof value.restartNumber === "number");
|
|
3203
|
+
}
|
|
3434
3204
|
function isStatusRow(value) {
|
|
3435
3205
|
return isRecord(value) && typeof value.status === "string";
|
|
3436
3206
|
}
|
|
@@ -3440,9 +3210,6 @@ function isCountRow(value) {
|
|
|
3440
3210
|
function isStepIndexRow(value) {
|
|
3441
3211
|
return isRecord(value) && typeof value.stepIndex === "number";
|
|
3442
3212
|
}
|
|
3443
|
-
function isLeaseExpiryRow(value) {
|
|
3444
|
-
return isRecord(value) && (typeof value.expiresAt === "number" || value.expiresAt === null);
|
|
3445
|
-
}
|
|
3446
3213
|
function isNextOrderRow(value) {
|
|
3447
3214
|
return isRecord(value) && typeof value.nextOrder === "number";
|
|
3448
3215
|
}
|