@osolmaz/pi-workflows 0.16.0 → 0.16.2
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 +48 -16
- package/dist/client/view.js +1 -0
- 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 +20 -165
- package/dist/controllers/sqlite.js +178 -436
- package/dist/controllers/sqlite.js.map +1 -1
- package/dist/extension/index.d.ts +23 -2
- package/dist/extension/index.js +637 -534
- 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 +28 -0
- package/dist/extension/workflow-message-coordinator.js +346 -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 +50 -8
- package/dist/host/runner.js +1632 -341
- 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 +227 -119
- package/dist/host/view.js.map +1 -1
- package/dist/host/worker-entry.d.ts +4 -1
- package/dist/host/worker-entry.js +23 -24
- package/dist/host/worker-entry.js.map +1 -1
- package/dist/host/worker-protocol.d.ts +15 -0
- package/dist/host/worker-protocol.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 +98 -0
- package/dist/state/workflow-messages.js +366 -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/composition.js +0 -4
- package/dist/workflows/composition.js.map +1 -1
- package/dist/workflows/definition.js +0 -8
- package/dist/workflows/definition.js.map +1 -1
- package/dist/workflows/engine.js +4 -5
- package/dist/workflows/engine.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/schema.js +0 -4
- package/dist/workflows/schema.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 +20 -19
- package/dist/workflows/store.js +163 -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 +6 -8
- 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/2026-09-04-workflow-run-state-plan.md +363 -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 +25 -17
- package/docs/WORKFLOW_HOST.md +105 -31
- 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 +44 -28
- 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 +51 -18
- package/src/controllers/index.ts +0 -1
- package/src/controllers/sqlite.ts +247 -845
- package/src/extension/index.ts +712 -647
- 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 +398 -0
- package/src/host/channel-effects.ts +465 -0
- package/src/host/channel-supervisor.ts +73 -0
- package/src/host/runner.ts +2163 -525
- package/src/host/state.ts +333 -94
- package/src/host/view.ts +254 -122
- package/src/host/worker-entry.ts +38 -30
- package/src/host/worker-protocol.ts +11 -0
- package/src/state/prune.ts +3 -31
- package/src/state/schema.ts +76 -97
- package/src/state/workflow-messages.ts +599 -0
- package/src/viewer/render.ts +1 -5
- package/src/viewer/tui.ts +71 -6
- package/src/workflows/composition.ts +0 -5
- package/src/workflows/definition.ts +0 -8
- package/src/workflows/engine.ts +4 -6
- package/src/workflows/human-decision.ts +18 -2
- package/src/workflows/index.ts +0 -1
- package/src/workflows/schema.ts +0 -6
- package/src/workflows/settings.ts +1 -20
- package/src/workflows/store.ts +237 -489
- package/src/workflows/tool-input.ts +36 -1
- package/src/workflows/types.ts +6 -8
- 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
|
-
};
|
|
608
|
+
if (this.originSessionId(request.runId) !== request.targetSessionId) {
|
|
609
|
+
throw new Error("Workflow follow-ups must target the run's origin Pi session");
|
|
634
610
|
}
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
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 = {
|
|
@@ -1415,6 +1262,48 @@ export class WorkflowRunStore {
|
|
|
1415
1262
|
lastEventSeq: segment?.eventCount ?? 0,
|
|
1416
1263
|
};
|
|
1417
1264
|
}
|
|
1265
|
+
readRunInput(runId) {
|
|
1266
|
+
const row = this.readRunRow(runId);
|
|
1267
|
+
return row === undefined ? null : this.state.readJson(row.inputHash);
|
|
1268
|
+
}
|
|
1269
|
+
readRunFinalOutput(runId) {
|
|
1270
|
+
const row = this.readRunRow(runId);
|
|
1271
|
+
return row?.finalOutputHash == null ? null : this.state.readJson(row.finalOutputHash);
|
|
1272
|
+
}
|
|
1273
|
+
readRunError(runId) {
|
|
1274
|
+
const row = this.readRunRow(runId);
|
|
1275
|
+
return row?.errorHash == null ? null : this.readText(row.errorHash);
|
|
1276
|
+
}
|
|
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
|
+
readTerminalData(runId) {
|
|
1287
|
+
const row = this.state.connection
|
|
1288
|
+
.prepare(`SELECT status, status_detail AS statusDetail, restart_number AS restartNumber
|
|
1289
|
+
FROM runs WHERE run_id = ?`)
|
|
1290
|
+
.get(runId);
|
|
1291
|
+
if (row === undefined ||
|
|
1292
|
+
!["completed", "failed", "timed_out", "cancelled"].includes(String(row.status)) ||
|
|
1293
|
+
typeof row.restartNumber !== "number") {
|
|
1294
|
+
return null;
|
|
1295
|
+
}
|
|
1296
|
+
return {
|
|
1297
|
+
runId,
|
|
1298
|
+
status: row.status,
|
|
1299
|
+
statusDetail: typeof row.statusDetail === "string" ? row.statusDetail : null,
|
|
1300
|
+
input: this.readRunInput(runId),
|
|
1301
|
+
finalOutput: this.readRunFinalOutput(runId),
|
|
1302
|
+
error: this.readRunError(runId),
|
|
1303
|
+
presentationInstructions: this.readPresentationInstructions(runId),
|
|
1304
|
+
restartNumber: row.restartNumber,
|
|
1305
|
+
};
|
|
1306
|
+
}
|
|
1418
1307
|
readRun(runId, options = {}) {
|
|
1419
1308
|
const row = this.readRunRow(runId);
|
|
1420
1309
|
if (row === undefined)
|
|
@@ -1659,6 +1548,8 @@ export class WorkflowRunStore {
|
|
|
1659
1548
|
}
|
|
1660
1549
|
assertSessionWriteAuthority(runId) {
|
|
1661
1550
|
const run = this.requireRunRow(runId);
|
|
1551
|
+
if (this.allowHostSessionRecording)
|
|
1552
|
+
return;
|
|
1662
1553
|
this.assertWriteAuthority(run, this.contextFor(runId).revision);
|
|
1663
1554
|
}
|
|
1664
1555
|
resourceRevision(runId) {
|
|
@@ -1808,10 +1699,8 @@ export class WorkflowRunStore {
|
|
|
1808
1699
|
order: row.orderNumber,
|
|
1809
1700
|
state: row.status,
|
|
1810
1701
|
source: row.sourceType,
|
|
1811
|
-
sessionEntryId: row.sessionEntryId,
|
|
1812
1702
|
}));
|
|
1813
1703
|
const followUpStart = Math.max(0, followUps.length - VIEWER_PAGE_SIZE);
|
|
1814
|
-
const queue = this.requireFollowUpQueueRow(runId);
|
|
1815
1704
|
return [
|
|
1816
1705
|
{
|
|
1817
1706
|
targetType: "inspector",
|
|
@@ -1833,10 +1722,7 @@ export class WorkflowRunStore {
|
|
|
1833
1722
|
{
|
|
1834
1723
|
op: "replace",
|
|
1835
1724
|
path: "/followUpQueue",
|
|
1836
|
-
value: parseJson(canonicalJson({
|
|
1837
|
-
presentationState: queue.presentationState,
|
|
1838
|
-
items: followUps.slice(followUpStart),
|
|
1839
|
-
})),
|
|
1725
|
+
value: parseJson(canonicalJson({ items: followUps.slice(followUpStart) })),
|
|
1840
1726
|
},
|
|
1841
1727
|
{ op: "replace", path: "/followUpStart", value: followUpStart },
|
|
1842
1728
|
{ op: "replace", path: "/followUpTotal", value: followUps.length },
|
|
@@ -1845,21 +1731,7 @@ export class WorkflowRunStore {
|
|
|
1845
1731
|
];
|
|
1846
1732
|
}
|
|
1847
1733
|
initializeRunSettingsAndFollowUps(state, initialSettings, now) {
|
|
1848
|
-
const childQueueResourceId = this.ensureFollowUpQueue(state.runId, now);
|
|
1849
1734
|
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
1735
|
this.state.connection
|
|
1864
1736
|
.prepare(`UPDATE workflow_settings SET active_run_id = ?, updated_at = ? WHERE active_run_id = ?`)
|
|
1865
1737
|
.run(state.runId, now, state.parentRunId);
|
|
@@ -1884,16 +1756,6 @@ export class WorkflowRunStore {
|
|
|
1884
1756
|
.run(scopeId, resourceId, originRunId, state.runId, scope.mountPath, scope.invocation, settingsHash, settingsHash, now, now);
|
|
1885
1757
|
}
|
|
1886
1758
|
}
|
|
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
1759
|
logicalOriginRunId(runId) {
|
|
1898
1760
|
let current = runId;
|
|
1899
1761
|
for (let depth = 0; depth < 100; depth += 1) {
|
|
@@ -2026,27 +1888,6 @@ export class WorkflowRunStore {
|
|
|
2026
1888
|
throw new Error(`Workflow settings request ID was reused with different content`);
|
|
2027
1889
|
}
|
|
2028
1890
|
}
|
|
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
1891
|
followUpRowByRequest(runId, requestId) {
|
|
2051
1892
|
const row = this.state.connection
|
|
2052
1893
|
.prepare(`${FOLLOW_UP_ROW_SELECT} WHERE f.run_id = ? AND f.request_id = ?`)
|
|
@@ -2079,11 +1920,9 @@ export class WorkflowRunStore {
|
|
|
2079
1920
|
source: row.sourceType,
|
|
2080
1921
|
prompt: this.readText(row.promptHash),
|
|
2081
1922
|
state: assertFollowUpState(row.status),
|
|
2082
|
-
...(row.sessionEntryId !== null ? { sessionEntryId: row.sessionEntryId } : {}),
|
|
2083
1923
|
...(row.reasonHash !== null ? { reason: this.readText(row.reasonHash) } : {}),
|
|
2084
1924
|
createdAt: new Date(row.createdAt).toISOString(),
|
|
2085
1925
|
updatedAt: new Date(row.updatedAt).toISOString(),
|
|
2086
|
-
...(row.sentAt !== null ? { sentAt: new Date(row.sentAt).toISOString() } : {}),
|
|
2087
1926
|
};
|
|
2088
1927
|
}
|
|
2089
1928
|
assertSameFollowUp(row, request) {
|
|
@@ -2107,12 +1946,6 @@ export class WorkflowRunStore {
|
|
|
2107
1946
|
}
|
|
2108
1947
|
throw new Error("Workflow follow-up can be removed only by its source or a verified human");
|
|
2109
1948
|
}
|
|
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
1949
|
nextFollowUpOrder(runId) {
|
|
2117
1950
|
const row = this.state.connection
|
|
2118
1951
|
.prepare(`SELECT COALESCE(MAX(order_number), 0) + 1 AS nextOrder
|
|
@@ -2137,37 +1970,37 @@ export class WorkflowRunStore {
|
|
|
2137
1970
|
throw new StaleResourceError(`Settings route ${event.nodeId} used change ${changeNumber}, current change is ${scope.revision}`);
|
|
2138
1971
|
}
|
|
2139
1972
|
}
|
|
2140
|
-
transitionFollowUpsForRunState(state,
|
|
2141
|
-
if (state.status === "running" || state.status === "waiting")
|
|
1973
|
+
transitionFollowUpsForRunState(state, _event, now) {
|
|
1974
|
+
if (state.status === "running" || state.status === "waiting" || state.status === "completed") {
|
|
2142
1975
|
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
1976
|
}
|
|
2154
|
-
|
|
2155
|
-
|
|
1977
|
+
const rows = this.state.connection
|
|
1978
|
+
.prepare(`${FOLLOW_UP_ROW_SELECT}
|
|
1979
|
+
WHERE f.run_id IN (
|
|
1980
|
+
WITH RECURSIVE ancestors(run_id, parent_run_id) AS (
|
|
1981
|
+
SELECT run_id, parent_run_id FROM runs WHERE run_id = ?
|
|
1982
|
+
UNION ALL
|
|
1983
|
+
SELECT parent.run_id, parent.parent_run_id
|
|
1984
|
+
FROM runs parent JOIN ancestors ON ancestors.parent_run_id = parent.run_id
|
|
1985
|
+
)
|
|
1986
|
+
SELECT run_id FROM ancestors
|
|
1987
|
+
) AND f.status = 'queued'`)
|
|
1988
|
+
.all(state.runId)
|
|
1989
|
+
.filter(isFollowUpRow);
|
|
1990
|
+
for (const row of rows) {
|
|
1991
|
+
const reason = state.error ?? `Workflow ended with status ${state.status}`;
|
|
1992
|
+
const reasonHash = this.state.putText(reason, now);
|
|
1993
|
+
const update = this.state.connection
|
|
1994
|
+
.prepare(`UPDATE workflow_follow_ups
|
|
1995
|
+
SET status = 'cancelled', reason_hash = ?, updated_at = ?
|
|
1996
|
+
WHERE follow_up_id = ? AND status = 'queued'`)
|
|
1997
|
+
.run(reasonHash, now, row.followUpId);
|
|
1998
|
+
if (update.changes !== 1)
|
|
1999
|
+
continue;
|
|
2000
|
+
this.workflowMessages.cancelPendingForSource(row.followUpId, "followUp", now);
|
|
2001
|
+
this.bumpResource(row.resourceId, row.revision, now);
|
|
2002
|
+
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
2003
|
}
|
|
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
2004
|
}
|
|
2172
2005
|
syncNodeAttempts(state, snapshot, now) {
|
|
2173
2006
|
for (const step of state.steps.slice(state.carriedStepCount ?? 0)) {
|
|
@@ -2300,7 +2133,7 @@ export class WorkflowRunStore {
|
|
|
2300
2133
|
JOIN session_segments s ON s.segment_id = e.segment_id
|
|
2301
2134
|
JOIN blobs b ON b.blob_hash = e.entry_hash
|
|
2302
2135
|
WHERE s.run_id = ?
|
|
2303
|
-
AND json_extract(CAST(b.content AS TEXT), '$.customType') = 'pi-workflows-
|
|
2136
|
+
AND json_extract(CAST(b.content AS TEXT), '$.customType') = 'pi-workflows-step'
|
|
2304
2137
|
AND json_extract(CAST(b.content AS TEXT), '$.details.contract.attemptId') = ?
|
|
2305
2138
|
ORDER BY e.recorded_at DESC LIMIT 1`)
|
|
2306
2139
|
.get(runId, attemptId);
|
|
@@ -2438,7 +2271,13 @@ export class WorkflowRunStore {
|
|
|
2438
2271
|
buildRunState(row, snapshot, projectionSteps, visibleSteps, visibleUpdates) {
|
|
2439
2272
|
const sources = this.readRunSources(row.runId, snapshot);
|
|
2440
2273
|
const carriedStepCount = this.carriedStepCount(row.runId);
|
|
2441
|
-
const activeAttempt = row.status === "running"
|
|
2274
|
+
const activeAttempt = row.status === "running" || row.status === "waiting"
|
|
2275
|
+
? this.readActiveAttempt(row.runId)
|
|
2276
|
+
: undefined;
|
|
2277
|
+
const runningAttempt = row.status === "running" ? activeAttempt : undefined;
|
|
2278
|
+
const waitingNode = row.status === "waiting"
|
|
2279
|
+
? (activeAttempt?.nodeId ?? projectionSteps.at(-1)?.nodeId)
|
|
2280
|
+
: undefined;
|
|
2442
2281
|
const humanDecision = this.readHumanDecisionReceipt(row.runId);
|
|
2443
2282
|
const outputs = {};
|
|
2444
2283
|
const results = {};
|
|
@@ -2481,7 +2320,7 @@ export class WorkflowRunStore {
|
|
|
2481
2320
|
results,
|
|
2482
2321
|
steps: visibleSteps,
|
|
2483
2322
|
...(visibleUpdates.length === 0 ? {} : { updates: visibleUpdates }),
|
|
2484
|
-
...(
|
|
2323
|
+
...(runningAttempt === undefined ? {} : { currentNode: runningAttempt.nodeId }),
|
|
2485
2324
|
...(activeAttempt === undefined ? {} : { currentAttemptId: activeAttempt.attemptId }),
|
|
2486
2325
|
...(activeAttempt?.startedAt === null || activeAttempt === undefined
|
|
2487
2326
|
? {}
|
|
@@ -2497,9 +2336,7 @@ export class WorkflowRunStore {
|
|
|
2497
2336
|
...(row.statusDetail === null ? {} : { statusDetail: row.statusDetail }),
|
|
2498
2337
|
...(humanDecision === undefined ? {} : { humanDecision }),
|
|
2499
2338
|
...(row.paused === 0 ? {} : { paused: true }),
|
|
2500
|
-
...(
|
|
2501
|
-
? { waitingOn: projectionSteps.at(-1)?.nodeId }
|
|
2502
|
-
: {}),
|
|
2339
|
+
...(waitingNode === undefined ? {} : { waitingOn: waitingNode }),
|
|
2503
2340
|
...(row.finalOutputHash === null
|
|
2504
2341
|
? {}
|
|
2505
2342
|
: { finalOutput: this.state.readJson(row.finalOutputHash) }),
|
|
@@ -3334,26 +3171,11 @@ function assertActorType(value) {
|
|
|
3334
3171
|
return value;
|
|
3335
3172
|
}
|
|
3336
3173
|
function assertFollowUpState(value) {
|
|
3337
|
-
if (value !== "queued" &&
|
|
3338
|
-
value !== "pending_presentation" &&
|
|
3339
|
-
value !== "ready" &&
|
|
3340
|
-
value !== "sent" &&
|
|
3341
|
-
value !== "removed" &&
|
|
3342
|
-
value !== "cancelled") {
|
|
3174
|
+
if (value !== "queued" && value !== "removed" && value !== "cancelled") {
|
|
3343
3175
|
throw new Error(`Unknown workflow follow-up state: ${value}`);
|
|
3344
3176
|
}
|
|
3345
3177
|
return value;
|
|
3346
3178
|
}
|
|
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
3179
|
function isSettingsScopeRow(value) {
|
|
3358
3180
|
return (isRecord(value) &&
|
|
3359
3181
|
typeof value.scopeId === "string" &&
|
|
@@ -3395,23 +3217,10 @@ function isSettingsChangeRow(value) {
|
|
|
3395
3217
|
Buffer.isBuffer(value.afterHash) &&
|
|
3396
3218
|
typeof value.acceptedAt === "number");
|
|
3397
3219
|
}
|
|
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
3220
|
function isFollowUpRow(value) {
|
|
3411
3221
|
return (isRecord(value) &&
|
|
3412
3222
|
typeof value.followUpId === "string" &&
|
|
3413
3223
|
typeof value.resourceId === "string" &&
|
|
3414
|
-
typeof value.queueResourceId === "string" &&
|
|
3415
3224
|
typeof value.runId === "string" &&
|
|
3416
3225
|
typeof value.requestId === "string" &&
|
|
3417
3226
|
typeof value.orderNumber === "number" &&
|
|
@@ -3421,16 +3230,19 @@ function isFollowUpRow(value) {
|
|
|
3421
3230
|
typeof value.sourceType === "string" &&
|
|
3422
3231
|
Buffer.isBuffer(value.promptHash) &&
|
|
3423
3232
|
typeof value.status === "string" &&
|
|
3424
|
-
(typeof value.sessionEntryId === "string" || value.sessionEntryId === null) &&
|
|
3425
3233
|
(Buffer.isBuffer(value.reasonHash) || value.reasonHash === null) &&
|
|
3426
3234
|
typeof value.createdAt === "number" &&
|
|
3427
3235
|
typeof value.updatedAt === "number" &&
|
|
3428
|
-
(typeof value.sentAt === "number" || value.sentAt === null) &&
|
|
3429
3236
|
typeof value.revision === "number");
|
|
3430
3237
|
}
|
|
3431
3238
|
function isParentRunRow(value) {
|
|
3432
3239
|
return isRecord(value) && (typeof value.parentRunId === "string" || value.parentRunId === null);
|
|
3433
3240
|
}
|
|
3241
|
+
function isRunLineageRow(value) {
|
|
3242
|
+
return (isRecord(value) &&
|
|
3243
|
+
typeof value.rootRunId === "string" &&
|
|
3244
|
+
typeof value.restartNumber === "number");
|
|
3245
|
+
}
|
|
3434
3246
|
function isStatusRow(value) {
|
|
3435
3247
|
return isRecord(value) && typeof value.status === "string";
|
|
3436
3248
|
}
|
|
@@ -3440,9 +3252,6 @@ function isCountRow(value) {
|
|
|
3440
3252
|
function isStepIndexRow(value) {
|
|
3441
3253
|
return isRecord(value) && typeof value.stepIndex === "number";
|
|
3442
3254
|
}
|
|
3443
|
-
function isLeaseExpiryRow(value) {
|
|
3444
|
-
return isRecord(value) && (typeof value.expiresAt === "number" || value.expiresAt === null);
|
|
3445
|
-
}
|
|
3446
3255
|
function isNextOrderRow(value) {
|
|
3447
3256
|
return isRecord(value) && typeof value.nextOrder === "number";
|
|
3448
3257
|
}
|