@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/src/workflows/store.ts
CHANGED
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
resourceIdFor,
|
|
8
8
|
tokenHash,
|
|
9
9
|
type ActorType,
|
|
10
|
-
type LeaseClaim,
|
|
11
10
|
type MutationActor,
|
|
12
11
|
type OwnerType,
|
|
13
12
|
} from "../state/mutation.js";
|
|
@@ -18,6 +17,7 @@ import {
|
|
|
18
17
|
VIEWER_PAGE_SIZE,
|
|
19
18
|
type ViewerDeltaDraft,
|
|
20
19
|
} from "../state/viewer.js";
|
|
20
|
+
import { WorkflowMessageStore, workflowMessageIdFor } from "../state/workflow-messages.js";
|
|
21
21
|
import { compositionMetadata } from "./composition.js";
|
|
22
22
|
import { ClaimLostError } from "./errors.js";
|
|
23
23
|
import { HumanDecisionStore } from "./human-decision.js";
|
|
@@ -34,7 +34,6 @@ import {
|
|
|
34
34
|
type WorkflowFollowUpQueueRecord,
|
|
35
35
|
type WorkflowFollowUpRecord,
|
|
36
36
|
type WorkflowFollowUpState,
|
|
37
|
-
type WorkflowPresentationState,
|
|
38
37
|
type WorkflowQueueFollowUpRequest,
|
|
39
38
|
type WorkflowRemoveFollowUpRequest,
|
|
40
39
|
type WorkflowSettingsChangeRecord,
|
|
@@ -70,6 +69,7 @@ import type {
|
|
|
70
69
|
WorkflowUpdateRecord,
|
|
71
70
|
} from "./types.js";
|
|
72
71
|
import { MAX_CURRENT_UPDATES, createUpdateId, updateProjection } from "./updates.js";
|
|
72
|
+
import { followUpWorkflowMessageContent } from "./workflow-message-content.js";
|
|
73
73
|
|
|
74
74
|
export const RUN_STATE_SCHEMA = "pi-workflows.run-state.v1" as const;
|
|
75
75
|
export const DEFINITION_SNAPSHOT_SCHEMA = "pi-workflows.definition-snapshot.v1" as const;
|
|
@@ -110,14 +110,11 @@ const STEP_ROW_SELECT = `
|
|
|
110
110
|
|
|
111
111
|
const FOLLOW_UP_ROW_SELECT = `
|
|
112
112
|
SELECT f.follow_up_id AS followUpId, f.resource_id AS resourceId,
|
|
113
|
-
f.
|
|
114
|
-
f.request_id AS requestId, f.order_number AS orderNumber,
|
|
113
|
+
f.run_id AS runId, f.request_id AS requestId, f.order_number AS orderNumber,
|
|
115
114
|
f.target_session_id AS targetSessionId, f.actor_type AS actorType,
|
|
116
115
|
f.actor_id AS actorId, f.source_type AS sourceType,
|
|
117
|
-
f.prompt_hash AS promptHash, f.status,
|
|
118
|
-
f.
|
|
119
|
-
f.created_at AS createdAt, f.updated_at AS updatedAt, f.sent_at AS sentAt,
|
|
120
|
-
r.revision
|
|
116
|
+
f.prompt_hash AS promptHash, f.status, f.reason_hash AS reasonHash,
|
|
117
|
+
f.created_at AS createdAt, f.updated_at AS updatedAt, r.revision
|
|
121
118
|
FROM workflow_follow_ups f
|
|
122
119
|
JOIN resources r ON r.resource_id = f.resource_id`;
|
|
123
120
|
|
|
@@ -156,6 +153,8 @@ export type WorkflowRunStoreOptions = {
|
|
|
156
153
|
database: StateDatabase;
|
|
157
154
|
now: number;
|
|
158
155
|
}) => void;
|
|
156
|
+
/** The global host may record an attested Pi session after the worker lease ends. */
|
|
157
|
+
allowHostSessionRecording?: boolean;
|
|
159
158
|
state?: StateDatabase;
|
|
160
159
|
readOnly?: boolean;
|
|
161
160
|
};
|
|
@@ -352,21 +351,9 @@ type SettingsChangeRow = {
|
|
|
352
351
|
acceptedAt: number;
|
|
353
352
|
};
|
|
354
353
|
|
|
355
|
-
type FollowUpQueueRow = {
|
|
356
|
-
runId: string;
|
|
357
|
-
resourceId: string;
|
|
358
|
-
originSessionId: string | null;
|
|
359
|
-
presentationState: string;
|
|
360
|
-
presentationEntryId: string | null;
|
|
361
|
-
presentationAssistantEntryId: string | null;
|
|
362
|
-
presentationReasonHash: Buffer | null;
|
|
363
|
-
revision: number;
|
|
364
|
-
};
|
|
365
|
-
|
|
366
354
|
type FollowUpRow = {
|
|
367
355
|
followUpId: string;
|
|
368
356
|
resourceId: string;
|
|
369
|
-
queueResourceId: string;
|
|
370
357
|
runId: string;
|
|
371
358
|
requestId: string;
|
|
372
359
|
orderNumber: number;
|
|
@@ -376,11 +363,9 @@ type FollowUpRow = {
|
|
|
376
363
|
sourceType: string;
|
|
377
364
|
promptHash: Buffer;
|
|
378
365
|
status: string;
|
|
379
|
-
sessionEntryId: string | null;
|
|
380
366
|
reasonHash: Buffer | null;
|
|
381
367
|
createdAt: number;
|
|
382
368
|
updatedAt: number;
|
|
383
|
-
sentAt: number | null;
|
|
384
369
|
revision: number;
|
|
385
370
|
};
|
|
386
371
|
|
|
@@ -503,13 +488,16 @@ export class WorkflowRunStore {
|
|
|
503
488
|
| ((runId: string) => RunWriteAuthority | undefined)
|
|
504
489
|
| undefined;
|
|
505
490
|
private readonly snapshotLifecycle: WorkflowRunStoreOptions["snapshotLifecycle"];
|
|
491
|
+
private readonly allowHostSessionRecording: boolean;
|
|
506
492
|
private readonly contexts = new Map<string, RunContext>();
|
|
507
493
|
private readonly ownsState: boolean;
|
|
508
494
|
private readonly mutations: StateMutationStore;
|
|
495
|
+
private readonly workflowMessages: WorkflowMessageStore;
|
|
509
496
|
|
|
510
497
|
constructor(databasePath: string = workflowStatePath(), options: WorkflowRunStoreOptions = {}) {
|
|
511
498
|
this.authorityProvider = options.authorityProvider;
|
|
512
499
|
this.snapshotLifecycle = options.snapshotLifecycle;
|
|
500
|
+
this.allowHostSessionRecording = options.allowHostSessionRecording === true;
|
|
513
501
|
this.ownsState = options.state === undefined;
|
|
514
502
|
this.state =
|
|
515
503
|
options.state ??
|
|
@@ -520,6 +508,7 @@ export class WorkflowRunStore {
|
|
|
520
508
|
});
|
|
521
509
|
this.databasePath = this.state.filePath;
|
|
522
510
|
this.mutations = new StateMutationStore(this.state);
|
|
511
|
+
this.workflowMessages = new WorkflowMessageStore(this.state);
|
|
523
512
|
}
|
|
524
513
|
|
|
525
514
|
close(): void {
|
|
@@ -912,6 +901,18 @@ export class WorkflowRunStore {
|
|
|
912
901
|
return;
|
|
913
902
|
}
|
|
914
903
|
const definitionHash = this.state.putJson(snapshot, now);
|
|
904
|
+
const parentLineage =
|
|
905
|
+
state.parentRunId === undefined
|
|
906
|
+
? undefined
|
|
907
|
+
: this.state.connection
|
|
908
|
+
.prepare(
|
|
909
|
+
`SELECT root_run_id AS rootRunId, restart_number AS restartNumber
|
|
910
|
+
FROM runs WHERE run_id = ?`,
|
|
911
|
+
)
|
|
912
|
+
.get(state.parentRunId);
|
|
913
|
+
if (state.parentRunId !== undefined && !isRunLineageRow(parentLineage)) {
|
|
914
|
+
throw new Error(`Workflow parent run is missing: ${state.parentRunId}`);
|
|
915
|
+
}
|
|
915
916
|
this.state.connection
|
|
916
917
|
.prepare(
|
|
917
918
|
`INSERT INTO workflow_definitions(
|
|
@@ -940,16 +941,20 @@ export class WorkflowRunStore {
|
|
|
940
941
|
this.state.connection
|
|
941
942
|
.prepare(
|
|
942
943
|
`INSERT INTO runs(
|
|
943
|
-
run_id, resource_id, project_id, parent_run_id,
|
|
944
|
+
run_id, resource_id, project_id, parent_run_id, root_run_id, lineage_kind,
|
|
945
|
+
restart_number, parent_terminal_fingerprint, definition_digest,
|
|
944
946
|
workflow_ref, launch_options_hash, title, status, paused,
|
|
945
947
|
status_detail, input_hash, final_output_hash, error_hash,
|
|
946
948
|
created_at, updated_at, finished_at
|
|
947
|
-
) VALUES (?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
949
|
+
) VALUES (?, ?, NULL, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
948
950
|
)
|
|
949
951
|
.run(
|
|
950
952
|
state.runId,
|
|
951
953
|
resourceId,
|
|
952
954
|
state.parentRunId ?? null,
|
|
955
|
+
isRunLineageRow(parentLineage) ? parentLineage.rootRunId : state.runId,
|
|
956
|
+
state.parentRunId === undefined ? null : "continuation",
|
|
957
|
+
isRunLineageRow(parentLineage) ? parentLineage.restartNumber : 0,
|
|
953
958
|
definitionDigest,
|
|
954
959
|
state.workflowName,
|
|
955
960
|
launchOptionsHash,
|
|
@@ -1287,87 +1292,78 @@ export class WorkflowRunStore {
|
|
|
1287
1292
|
if (Buffer.byteLength(request.prompt, "utf8") > 64 * 1024) {
|
|
1288
1293
|
throw new Error("Workflow follow-up prompt cannot exceed 65536 bytes");
|
|
1289
1294
|
}
|
|
1290
|
-
|
|
1295
|
+
return this.state.transaction(() => {
|
|
1291
1296
|
const prior = this.followUpRowByRequest(request.runId, request.requestId);
|
|
1292
1297
|
if (prior !== undefined) {
|
|
1293
1298
|
this.assertSameFollowUp(prior, request);
|
|
1294
1299
|
return { followUp: this.followUpRecord(prior), adopted: true };
|
|
1295
1300
|
}
|
|
1296
1301
|
this.requireRunAcceptsSettings(request.runId, true);
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
const result = this.mutations.mutate(
|
|
1300
|
-
{
|
|
1301
|
-
resourceId: queue.resourceId,
|
|
1302
|
-
operation: "follow-up.queue",
|
|
1303
|
-
actor: request.actor,
|
|
1304
|
-
expectedRevision: queue.revision,
|
|
1305
|
-
},
|
|
1306
|
-
"follow-up.queued",
|
|
1307
|
-
({ database, now }) => {
|
|
1308
|
-
this.requireRunAcceptsSettings(request.runId, true);
|
|
1309
|
-
const currentQueue = this.requireFollowUpQueueRow(request.runId);
|
|
1310
|
-
if (
|
|
1311
|
-
currentQueue.originSessionId !== null &&
|
|
1312
|
-
currentQueue.originSessionId !== request.targetSessionId
|
|
1313
|
-
) {
|
|
1314
|
-
throw new Error("Workflow follow-ups must target the run's origin Pi session");
|
|
1315
|
-
}
|
|
1316
|
-
if (currentQueue.originSessionId === null) {
|
|
1317
|
-
database.connection
|
|
1318
|
-
.prepare(
|
|
1319
|
-
`UPDATE workflow_follow_up_queues
|
|
1320
|
-
SET origin_session_id = ?, updated_at = ? WHERE run_id = ?`,
|
|
1321
|
-
)
|
|
1322
|
-
.run(request.targetSessionId, now, request.runId);
|
|
1323
|
-
}
|
|
1324
|
-
const order = this.nextFollowUpOrder(request.runId);
|
|
1325
|
-
const followUpId = followUpIdFor(request.runId, request.requestId);
|
|
1326
|
-
const resourceId = this.mutations.ensureResource("follow_up", followUpId, now);
|
|
1327
|
-
const promptHash = database.putText(request.prompt, now);
|
|
1328
|
-
database.connection
|
|
1329
|
-
.prepare(
|
|
1330
|
-
`INSERT INTO workflow_follow_ups(
|
|
1331
|
-
follow_up_id, resource_id, queue_resource_id, run_id,
|
|
1332
|
-
request_id, order_number, target_session_id,
|
|
1333
|
-
actor_type, actor_id, source_type, prompt_hash, status,
|
|
1334
|
-
created_at, updated_at
|
|
1335
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'queued', ?, ?)`,
|
|
1336
|
-
)
|
|
1337
|
-
.run(
|
|
1338
|
-
followUpId,
|
|
1339
|
-
resourceId,
|
|
1340
|
-
queue.resourceId,
|
|
1341
|
-
request.runId,
|
|
1342
|
-
request.requestId,
|
|
1343
|
-
order,
|
|
1344
|
-
request.targetSessionId,
|
|
1345
|
-
request.actor.type,
|
|
1346
|
-
request.actor.id ?? null,
|
|
1347
|
-
request.source,
|
|
1348
|
-
promptHash,
|
|
1349
|
-
now,
|
|
1350
|
-
now,
|
|
1351
|
-
);
|
|
1352
|
-
recordViewerDeltas(
|
|
1353
|
-
this.state,
|
|
1354
|
-
request.runId,
|
|
1355
|
-
this.viewerInspectorTargets(request.runId),
|
|
1356
|
-
now,
|
|
1357
|
-
);
|
|
1358
|
-
return followUpId;
|
|
1359
|
-
},
|
|
1360
|
-
{ payload: { runId: request.runId, requestId: request.requestId } },
|
|
1361
|
-
);
|
|
1362
|
-
return {
|
|
1363
|
-
followUp: this.followUpRecord(this.requireFollowUpRow(result.value)),
|
|
1364
|
-
adopted: false,
|
|
1365
|
-
};
|
|
1366
|
-
} catch (error) {
|
|
1367
|
-
if (!(error instanceof StaleResourceError)) throw error;
|
|
1302
|
+
if (this.originSessionId(request.runId) !== request.targetSessionId) {
|
|
1303
|
+
throw new Error("Workflow follow-ups must target the run's origin Pi session");
|
|
1368
1304
|
}
|
|
1369
|
-
|
|
1370
|
-
|
|
1305
|
+
const now = Date.now();
|
|
1306
|
+
const order = this.nextFollowUpOrder(request.runId);
|
|
1307
|
+
const followUpId = followUpIdFor(request.runId, request.requestId);
|
|
1308
|
+
const resourceId = this.mutations.ensureResource("follow_up", followUpId, now);
|
|
1309
|
+
const promptHash = this.state.putText(request.prompt, now);
|
|
1310
|
+
this.state.connection
|
|
1311
|
+
.prepare(
|
|
1312
|
+
`INSERT INTO workflow_follow_ups(
|
|
1313
|
+
follow_up_id, resource_id, run_id, request_id, order_number,
|
|
1314
|
+
target_session_id, actor_type, actor_id, source_type, prompt_hash,
|
|
1315
|
+
status, created_at, updated_at
|
|
1316
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'queued', ?, ?)`,
|
|
1317
|
+
)
|
|
1318
|
+
.run(
|
|
1319
|
+
followUpId,
|
|
1320
|
+
resourceId,
|
|
1321
|
+
request.runId,
|
|
1322
|
+
request.requestId,
|
|
1323
|
+
order,
|
|
1324
|
+
request.targetSessionId,
|
|
1325
|
+
request.actor.type,
|
|
1326
|
+
request.actor.id ?? null,
|
|
1327
|
+
request.source,
|
|
1328
|
+
promptHash,
|
|
1329
|
+
now,
|
|
1330
|
+
now,
|
|
1331
|
+
);
|
|
1332
|
+
const workflowMessageId = workflowMessageIdFor("followUp", followUpId, "initial");
|
|
1333
|
+
this.workflowMessages.create({
|
|
1334
|
+
workflowMessageId,
|
|
1335
|
+
runId: request.runId,
|
|
1336
|
+
targetSessionId: request.targetSessionId,
|
|
1337
|
+
kind: "followUp",
|
|
1338
|
+
sourceId: followUpId,
|
|
1339
|
+
idempotencyKey: "initial",
|
|
1340
|
+
content: followUpWorkflowMessageContent({
|
|
1341
|
+
workflowMessageId,
|
|
1342
|
+
followUpId,
|
|
1343
|
+
runId: request.runId,
|
|
1344
|
+
prompt: request.prompt,
|
|
1345
|
+
}),
|
|
1346
|
+
now,
|
|
1347
|
+
});
|
|
1348
|
+
this.bumpResource(resourceId, 0, now);
|
|
1349
|
+
insertGenericEvent(
|
|
1350
|
+
this.state,
|
|
1351
|
+
resourceId,
|
|
1352
|
+
1,
|
|
1353
|
+
"follow-up.queued",
|
|
1354
|
+
request.actor.type,
|
|
1355
|
+
request.actor.id ?? null,
|
|
1356
|
+
this.state.putJson({ runId: request.runId, requestId: request.requestId }, now),
|
|
1357
|
+
now,
|
|
1358
|
+
);
|
|
1359
|
+
recordViewerDeltas(
|
|
1360
|
+
this.state,
|
|
1361
|
+
request.runId,
|
|
1362
|
+
this.viewerInspectorTargets(request.runId),
|
|
1363
|
+
now,
|
|
1364
|
+
);
|
|
1365
|
+
return { followUp: this.followUpRecord(this.requireFollowUpRow(followUpId)), adopted: false };
|
|
1366
|
+
});
|
|
1371
1367
|
}
|
|
1372
1368
|
|
|
1373
1369
|
removeFollowUp(request: WorkflowRemoveFollowUpRequest): WorkflowFollowUpRecord {
|
|
@@ -1377,12 +1373,13 @@ export class WorkflowRunStore {
|
|
|
1377
1373
|
throw new Error(`Workflow follow-up is not part of run ${request.runId}`);
|
|
1378
1374
|
}
|
|
1379
1375
|
if (row.status === "removed") return this.followUpRecord(row);
|
|
1380
|
-
if (row.status === "
|
|
1381
|
-
throw new Error(
|
|
1376
|
+
if (row.status === "cancelled") {
|
|
1377
|
+
throw new Error("A cancelled workflow follow-up cannot be removed");
|
|
1382
1378
|
}
|
|
1383
1379
|
this.assertFollowUpRemovalAuthority(row, request.actor, request.source);
|
|
1384
|
-
|
|
1385
|
-
|
|
1380
|
+
const message = this.workflowMessages.latestForSource("followUp", request.followUpId);
|
|
1381
|
+
if (message?.status === "sent") {
|
|
1382
|
+
throw new Error("A sent workflow follow-up cannot be removed");
|
|
1386
1383
|
}
|
|
1387
1384
|
const reason = "Removed before delivery";
|
|
1388
1385
|
this.mutations.mutate(
|
|
@@ -1399,12 +1396,13 @@ export class WorkflowRunStore {
|
|
|
1399
1396
|
.prepare(
|
|
1400
1397
|
`UPDATE workflow_follow_ups
|
|
1401
1398
|
SET status = 'removed', reason_hash = ?, updated_at = ?
|
|
1402
|
-
WHERE follow_up_id = ? AND status
|
|
1399
|
+
WHERE follow_up_id = ? AND status = 'queued'`,
|
|
1403
1400
|
)
|
|
1404
1401
|
.run(reasonHash, now, request.followUpId);
|
|
1405
1402
|
if (update.changes !== 1) {
|
|
1406
1403
|
throw new StaleResourceError("Workflow follow-up changed before removal");
|
|
1407
1404
|
}
|
|
1405
|
+
this.workflowMessages.cancelPendingForSource(request.followUpId, "followUp", now);
|
|
1408
1406
|
recordViewerDeltas(
|
|
1409
1407
|
this.state,
|
|
1410
1408
|
request.runId,
|
|
@@ -1420,10 +1418,8 @@ export class WorkflowRunStore {
|
|
|
1420
1418
|
originSessionId(runId: string): string | undefined {
|
|
1421
1419
|
const row = this.state.connection
|
|
1422
1420
|
.prepare(
|
|
1423
|
-
`SELECT
|
|
1424
|
-
FROM runs r
|
|
1425
|
-
LEFT JOIN workflow_follow_up_queues q ON q.run_id = r.run_id
|
|
1426
|
-
LEFT JOIN run_bindings b ON b.run_id = r.run_id
|
|
1421
|
+
`SELECT b.origin_session_id AS originSessionId
|
|
1422
|
+
FROM runs r LEFT JOIN run_bindings b ON b.run_id = r.run_id
|
|
1427
1423
|
WHERE r.run_id = ?`,
|
|
1428
1424
|
)
|
|
1429
1425
|
.get(runId);
|
|
@@ -1433,36 +1429,22 @@ export class WorkflowRunStore {
|
|
|
1433
1429
|
}
|
|
1434
1430
|
|
|
1435
1431
|
readFollowUpQueue(runId: string): WorkflowFollowUpQueueRecord | undefined {
|
|
1436
|
-
const
|
|
1437
|
-
if (
|
|
1432
|
+
const originSessionId = this.originSessionId(runId);
|
|
1433
|
+
if (originSessionId === undefined) return undefined;
|
|
1438
1434
|
const followUps = this.state.connection
|
|
1439
1435
|
.prepare(`${FOLLOW_UP_ROW_SELECT} WHERE f.run_id = ? ORDER BY f.order_number`)
|
|
1440
1436
|
.all(runId)
|
|
1441
1437
|
.filter(isFollowUpRow)
|
|
1442
1438
|
.map((row) => this.followUpRecord(row));
|
|
1443
|
-
return {
|
|
1444
|
-
runId,
|
|
1445
|
-
...(queue.originSessionId !== null ? { originSessionId: queue.originSessionId } : {}),
|
|
1446
|
-
presentationState: assertPresentationState(queue.presentationState),
|
|
1447
|
-
...(queue.presentationEntryId !== null
|
|
1448
|
-
? { presentationEntryId: queue.presentationEntryId }
|
|
1449
|
-
: {}),
|
|
1450
|
-
...(queue.presentationAssistantEntryId !== null
|
|
1451
|
-
? { presentationAssistantEntryId: queue.presentationAssistantEntryId }
|
|
1452
|
-
: {}),
|
|
1453
|
-
...(queue.presentationReasonHash !== null
|
|
1454
|
-
? { presentationReason: this.readText(queue.presentationReasonHash) }
|
|
1455
|
-
: {}),
|
|
1456
|
-
followUps,
|
|
1457
|
-
};
|
|
1439
|
+
return { runId, originSessionId, followUps };
|
|
1458
1440
|
}
|
|
1459
1441
|
|
|
1460
1442
|
private readFollowUpQueueRange(
|
|
1461
1443
|
runId: string,
|
|
1462
1444
|
range: WorkflowRunViewRange,
|
|
1463
1445
|
): WorkflowFollowUpQueueRecord | null {
|
|
1464
|
-
const
|
|
1465
|
-
if (
|
|
1446
|
+
const originSessionId = this.originSessionId(runId);
|
|
1447
|
+
if (originSessionId === undefined) return null;
|
|
1466
1448
|
const followUps = this.state.connection
|
|
1467
1449
|
.prepare(
|
|
1468
1450
|
`${FOLLOW_UP_ROW_SELECT} WHERE f.run_id = ? ORDER BY f.order_number LIMIT ? OFFSET ?`,
|
|
@@ -1470,195 +1452,7 @@ export class WorkflowRunStore {
|
|
|
1470
1452
|
.all(runId, range.limit, range.start)
|
|
1471
1453
|
.filter(isFollowUpRow)
|
|
1472
1454
|
.map((row) => this.followUpRecord(row));
|
|
1473
|
-
return {
|
|
1474
|
-
runId,
|
|
1475
|
-
...(queue.originSessionId !== null ? { originSessionId: queue.originSessionId } : {}),
|
|
1476
|
-
presentationState: assertPresentationState(queue.presentationState),
|
|
1477
|
-
...(queue.presentationEntryId !== null
|
|
1478
|
-
? { presentationEntryId: queue.presentationEntryId }
|
|
1479
|
-
: {}),
|
|
1480
|
-
...(queue.presentationAssistantEntryId !== null
|
|
1481
|
-
? { presentationAssistantEntryId: queue.presentationAssistantEntryId }
|
|
1482
|
-
: {}),
|
|
1483
|
-
...(queue.presentationReasonHash !== null
|
|
1484
|
-
? { presentationReason: this.readText(queue.presentationReasonHash) }
|
|
1485
|
-
: {}),
|
|
1486
|
-
followUps,
|
|
1487
|
-
};
|
|
1488
|
-
}
|
|
1489
|
-
|
|
1490
|
-
listPendingPresentations(targetSessionId: string): string[] {
|
|
1491
|
-
return this.state.connection
|
|
1492
|
-
.prepare(
|
|
1493
|
-
`SELECT run_id AS runId FROM workflow_follow_up_queues
|
|
1494
|
-
WHERE origin_session_id = ? AND presentation_state = 'pending'
|
|
1495
|
-
ORDER BY created_at`,
|
|
1496
|
-
)
|
|
1497
|
-
.all(targetSessionId)
|
|
1498
|
-
.flatMap((row) => (isRecord(row) && typeof row.runId === "string" ? [row.runId] : []));
|
|
1499
|
-
}
|
|
1500
|
-
|
|
1501
|
-
settleFollowUpPresentation(options: {
|
|
1502
|
-
runId: string;
|
|
1503
|
-
state: "settled" | "unavailable";
|
|
1504
|
-
presentationEntryId?: string;
|
|
1505
|
-
assistantEntryId?: string;
|
|
1506
|
-
reason?: string;
|
|
1507
|
-
}): WorkflowFollowUpQueueRecord {
|
|
1508
|
-
const queue = this.requireFollowUpQueueRow(options.runId);
|
|
1509
|
-
if (queue.presentationState === options.state) {
|
|
1510
|
-
return this.readFollowUpQueue(options.runId) as WorkflowFollowUpQueueRecord;
|
|
1511
|
-
}
|
|
1512
|
-
if (queue.presentationState !== "pending") {
|
|
1513
|
-
throw new Error(
|
|
1514
|
-
`Workflow presentation is ${queue.presentationState}, not pending for ${options.runId}`,
|
|
1515
|
-
);
|
|
1516
|
-
}
|
|
1517
|
-
if (
|
|
1518
|
-
options.state === "settled" &&
|
|
1519
|
-
(options.presentationEntryId === undefined || options.assistantEntryId === undefined)
|
|
1520
|
-
) {
|
|
1521
|
-
throw new Error("Settled workflow presentation requires both session entry IDs");
|
|
1522
|
-
}
|
|
1523
|
-
if (options.state === "unavailable" && !options.reason?.trim()) {
|
|
1524
|
-
throw new Error("Unavailable workflow presentation requires a reason");
|
|
1525
|
-
}
|
|
1526
|
-
try {
|
|
1527
|
-
this.mutations.mutate(
|
|
1528
|
-
{
|
|
1529
|
-
resourceId: queue.resourceId,
|
|
1530
|
-
operation: "follow-up.presentation",
|
|
1531
|
-
actor: {
|
|
1532
|
-
type: "session",
|
|
1533
|
-
...(queue.originSessionId !== null ? { id: queue.originSessionId } : {}),
|
|
1534
|
-
},
|
|
1535
|
-
expectedRevision: queue.revision,
|
|
1536
|
-
},
|
|
1537
|
-
`follow-up.presentation-${options.state}`,
|
|
1538
|
-
({ database, now }) => {
|
|
1539
|
-
const reasonHash =
|
|
1540
|
-
options.state === "unavailable"
|
|
1541
|
-
? database.putText(options.reason as string, now)
|
|
1542
|
-
: null;
|
|
1543
|
-
database.connection
|
|
1544
|
-
.prepare(
|
|
1545
|
-
`UPDATE workflow_follow_up_queues
|
|
1546
|
-
SET presentation_state = ?, presentation_entry_id = ?,
|
|
1547
|
-
presentation_assistant_entry_id = ?, presentation_reason_hash = ?,
|
|
1548
|
-
presentation_updated_at = ?, updated_at = ?
|
|
1549
|
-
WHERE run_id = ? AND presentation_state = 'pending'`,
|
|
1550
|
-
)
|
|
1551
|
-
.run(
|
|
1552
|
-
options.state,
|
|
1553
|
-
options.presentationEntryId ?? null,
|
|
1554
|
-
options.assistantEntryId ?? null,
|
|
1555
|
-
reasonHash,
|
|
1556
|
-
now,
|
|
1557
|
-
now,
|
|
1558
|
-
options.runId,
|
|
1559
|
-
);
|
|
1560
|
-
database.connection
|
|
1561
|
-
.prepare(
|
|
1562
|
-
`UPDATE workflow_follow_ups SET status = 'ready', updated_at = ?
|
|
1563
|
-
WHERE run_id = ? AND status = 'pending_presentation'`,
|
|
1564
|
-
)
|
|
1565
|
-
.run(now, options.runId);
|
|
1566
|
-
recordViewerDeltas(
|
|
1567
|
-
this.state,
|
|
1568
|
-
options.runId,
|
|
1569
|
-
this.viewerInspectorTargets(options.runId),
|
|
1570
|
-
now,
|
|
1571
|
-
);
|
|
1572
|
-
},
|
|
1573
|
-
{ payload: { runId: options.runId, state: options.state } },
|
|
1574
|
-
);
|
|
1575
|
-
} catch (error) {
|
|
1576
|
-
if (error instanceof StaleResourceError) {
|
|
1577
|
-
const current = this.requireFollowUpQueueRow(options.runId);
|
|
1578
|
-
if (current.presentationState === options.state) {
|
|
1579
|
-
return this.readFollowUpQueue(options.runId) as WorkflowFollowUpQueueRecord;
|
|
1580
|
-
}
|
|
1581
|
-
}
|
|
1582
|
-
throw error;
|
|
1583
|
-
}
|
|
1584
|
-
return this.readFollowUpQueue(options.runId) as WorkflowFollowUpQueueRecord;
|
|
1585
|
-
}
|
|
1586
|
-
|
|
1587
|
-
claimNextFollowUp(
|
|
1588
|
-
targetSessionId: string,
|
|
1589
|
-
ownerId: string,
|
|
1590
|
-
leaseMs = 30_000,
|
|
1591
|
-
): { followUp: WorkflowFollowUpRecord; claim: LeaseClaim } | undefined {
|
|
1592
|
-
const rows = this.state.connection
|
|
1593
|
-
.prepare(
|
|
1594
|
-
`${FOLLOW_UP_ROW_SELECT}
|
|
1595
|
-
WHERE f.target_session_id = ? AND f.status = 'ready'
|
|
1596
|
-
ORDER BY f.created_at, f.order_number LIMIT 16`,
|
|
1597
|
-
)
|
|
1598
|
-
.all(targetSessionId)
|
|
1599
|
-
.filter(isFollowUpRow);
|
|
1600
|
-
for (const row of rows) {
|
|
1601
|
-
let claim: LeaseClaim | undefined;
|
|
1602
|
-
try {
|
|
1603
|
-
claim = this.mutations.claim({
|
|
1604
|
-
resourceId: row.resourceId,
|
|
1605
|
-
ownerType: "session",
|
|
1606
|
-
ownerId,
|
|
1607
|
-
expectedRevision: row.revision,
|
|
1608
|
-
leaseMs,
|
|
1609
|
-
});
|
|
1610
|
-
} catch (error) {
|
|
1611
|
-
if (error instanceof StaleResourceError) continue;
|
|
1612
|
-
throw error;
|
|
1613
|
-
}
|
|
1614
|
-
if (claim === undefined) continue;
|
|
1615
|
-
const current = this.requireFollowUpRow(row.followUpId);
|
|
1616
|
-
if (current.status !== "ready") {
|
|
1617
|
-
this.mutations.release(claim, claim.resourceRevision);
|
|
1618
|
-
continue;
|
|
1619
|
-
}
|
|
1620
|
-
return { followUp: this.followUpRecord(current), claim };
|
|
1621
|
-
}
|
|
1622
|
-
return undefined;
|
|
1623
|
-
}
|
|
1624
|
-
|
|
1625
|
-
markFollowUpSent(
|
|
1626
|
-
followUpId: string,
|
|
1627
|
-
claim: LeaseClaim,
|
|
1628
|
-
sessionEntryId: string,
|
|
1629
|
-
): WorkflowFollowUpRecord {
|
|
1630
|
-
const row = this.requireFollowUpRow(followUpId);
|
|
1631
|
-
if (row.status === "sent") return this.followUpRecord(row);
|
|
1632
|
-
this.mutations.mutate(
|
|
1633
|
-
{
|
|
1634
|
-
resourceId: row.resourceId,
|
|
1635
|
-
operation: "follow-up.sent",
|
|
1636
|
-
actor: { type: "session", id: claim.ownerId },
|
|
1637
|
-
expectedRevision: claim.resourceRevision,
|
|
1638
|
-
lease: claim,
|
|
1639
|
-
},
|
|
1640
|
-
"follow-up.sent",
|
|
1641
|
-
({ database, now }) => {
|
|
1642
|
-
const update = database.connection
|
|
1643
|
-
.prepare(
|
|
1644
|
-
`UPDATE workflow_follow_ups
|
|
1645
|
-
SET status = 'sent', session_entry_id = ?, sent_at = ?, updated_at = ?
|
|
1646
|
-
WHERE follow_up_id = ? AND status = 'ready'`,
|
|
1647
|
-
)
|
|
1648
|
-
.run(sessionEntryId, now, now, followUpId);
|
|
1649
|
-
if (update.changes !== 1) {
|
|
1650
|
-
throw new StaleResourceError("Workflow follow-up changed before delivery completed");
|
|
1651
|
-
}
|
|
1652
|
-
recordViewerDeltas(this.state, row.runId, this.viewerInspectorTargets(row.runId), now);
|
|
1653
|
-
},
|
|
1654
|
-
{ payload: { followUpId, sessionEntryId } },
|
|
1655
|
-
);
|
|
1656
|
-
return this.followUpRecord(this.requireFollowUpRow(followUpId));
|
|
1657
|
-
}
|
|
1658
|
-
|
|
1659
|
-
releaseFollowUpClaim(claim: LeaseClaim): void {
|
|
1660
|
-
const revision = this.requireResourceRevision(claim.resourceId);
|
|
1661
|
-
this.mutations.release(claim, revision);
|
|
1455
|
+
return { runId, originSessionId, followUps };
|
|
1662
1456
|
}
|
|
1663
1457
|
|
|
1664
1458
|
async prepareRunResume(runId: string): Promise<LoadedWorkflowRun> {
|
|
@@ -1670,7 +1464,6 @@ export class WorkflowRunStore {
|
|
|
1670
1464
|
this.verifySettingsProjections(runId);
|
|
1671
1465
|
const revision = this.resourceRevision(runId);
|
|
1672
1466
|
this.contexts.set(runId, { revision, lock: Promise.resolve() });
|
|
1673
|
-
this.finalizeRecordingCaptures(runId, "Workflow host stopped before the run finished");
|
|
1674
1467
|
this.state.transaction(() => {
|
|
1675
1468
|
const row = this.requireRunRow(runId);
|
|
1676
1469
|
const context = this.contextFor(runId);
|
|
@@ -1900,7 +1693,7 @@ export class WorkflowRunStore {
|
|
|
1900
1693
|
this.state.transaction(() => {
|
|
1901
1694
|
const run = this.requireRunRow(runId);
|
|
1902
1695
|
const context = this.contextFor(runId);
|
|
1903
|
-
this.
|
|
1696
|
+
this.assertSessionWriteAuthority(runId);
|
|
1904
1697
|
const now = Date.parse(binding.boundAt);
|
|
1905
1698
|
const resourceId = resourceIdFor("session", segmentId);
|
|
1906
1699
|
const bindingHash = this.state.putJson(binding, now);
|
|
@@ -1947,13 +1740,6 @@ export class WorkflowRunStore {
|
|
|
1947
1740
|
ON CONFLICT(run_id) DO NOTHING`,
|
|
1948
1741
|
)
|
|
1949
1742
|
.run(runId, binding.piSessionId, now);
|
|
1950
|
-
this.state.connection
|
|
1951
|
-
.prepare(
|
|
1952
|
-
`UPDATE workflow_follow_up_queues
|
|
1953
|
-
SET origin_session_id = COALESCE(origin_session_id, ?), updated_at = ?
|
|
1954
|
-
WHERE run_id = ?`,
|
|
1955
|
-
)
|
|
1956
|
-
.run(binding.piSessionId, now, runId);
|
|
1957
1743
|
const revision = context.revision + 1;
|
|
1958
1744
|
const at = new Date(now).toISOString();
|
|
1959
1745
|
const traceEvent: WorkflowTraceEvent = {
|
|
@@ -2679,6 +2465,7 @@ export class WorkflowRunStore {
|
|
|
2679
2465
|
|
|
2680
2466
|
private assertSessionWriteAuthority(runId: string): void {
|
|
2681
2467
|
const run = this.requireRunRow(runId);
|
|
2468
|
+
if (this.allowHostSessionRecording) return;
|
|
2682
2469
|
this.assertWriteAuthority(run, this.contextFor(runId).revision);
|
|
2683
2470
|
}
|
|
2684
2471
|
|
|
@@ -2902,10 +2689,8 @@ export class WorkflowRunStore {
|
|
|
2902
2689
|
order: row.orderNumber,
|
|
2903
2690
|
state: row.status,
|
|
2904
2691
|
source: row.sourceType,
|
|
2905
|
-
sessionEntryId: row.sessionEntryId,
|
|
2906
2692
|
}));
|
|
2907
2693
|
const followUpStart = Math.max(0, followUps.length - VIEWER_PAGE_SIZE);
|
|
2908
|
-
const queue = this.requireFollowUpQueueRow(runId);
|
|
2909
2694
|
return [
|
|
2910
2695
|
{
|
|
2911
2696
|
targetType: "inspector",
|
|
@@ -2927,12 +2712,7 @@ export class WorkflowRunStore {
|
|
|
2927
2712
|
{
|
|
2928
2713
|
op: "replace",
|
|
2929
2714
|
path: "/followUpQueue",
|
|
2930
|
-
value: parseJson(
|
|
2931
|
-
canonicalJson({
|
|
2932
|
-
presentationState: queue.presentationState,
|
|
2933
|
-
items: followUps.slice(followUpStart),
|
|
2934
|
-
}),
|
|
2935
|
-
),
|
|
2715
|
+
value: parseJson(canonicalJson({ items: followUps.slice(followUpStart) })),
|
|
2936
2716
|
},
|
|
2937
2717
|
{ op: "replace", path: "/followUpStart", value: followUpStart },
|
|
2938
2718
|
{ op: "replace", path: "/followUpTotal", value: followUps.length },
|
|
@@ -2946,25 +2726,7 @@ export class WorkflowRunStore {
|
|
|
2946
2726
|
initialSettings: InitialWorkflowSettingsScope[],
|
|
2947
2727
|
now: number,
|
|
2948
2728
|
): void {
|
|
2949
|
-
const childQueueResourceId = this.ensureFollowUpQueue(state.runId, now);
|
|
2950
2729
|
if (state.parentRunId !== undefined) {
|
|
2951
|
-
const parentQueue = this.followUpQueueRow(state.parentRunId);
|
|
2952
|
-
if (parentQueue !== undefined) {
|
|
2953
|
-
this.state.connection
|
|
2954
|
-
.prepare(
|
|
2955
|
-
`UPDATE workflow_follow_up_queues
|
|
2956
|
-
SET origin_session_id = COALESCE(origin_session_id, ?), updated_at = ?
|
|
2957
|
-
WHERE run_id = ?`,
|
|
2958
|
-
)
|
|
2959
|
-
.run(parentQueue.originSessionId, now, state.runId);
|
|
2960
|
-
this.state.connection
|
|
2961
|
-
.prepare(
|
|
2962
|
-
`UPDATE workflow_follow_ups
|
|
2963
|
-
SET run_id = ?, queue_resource_id = ?, updated_at = ?
|
|
2964
|
-
WHERE run_id = ? AND status = 'queued'`,
|
|
2965
|
-
)
|
|
2966
|
-
.run(state.runId, childQueueResourceId, now, state.parentRunId);
|
|
2967
|
-
}
|
|
2968
2730
|
this.state.connection
|
|
2969
2731
|
.prepare(
|
|
2970
2732
|
`UPDATE workflow_settings SET active_run_id = ?, updated_at = ? WHERE active_run_id = ?`,
|
|
@@ -3004,19 +2766,6 @@ export class WorkflowRunStore {
|
|
|
3004
2766
|
}
|
|
3005
2767
|
}
|
|
3006
2768
|
|
|
3007
|
-
private ensureFollowUpQueue(runId: string, now: number): string {
|
|
3008
|
-
const resourceId = this.mutations.ensureResource("follow_up_queue", runId, now);
|
|
3009
|
-
this.state.connection
|
|
3010
|
-
.prepare(
|
|
3011
|
-
`INSERT INTO workflow_follow_up_queues(
|
|
3012
|
-
run_id, resource_id, created_at, updated_at
|
|
3013
|
-
) VALUES (?, ?, ?, ?)
|
|
3014
|
-
ON CONFLICT(run_id) DO NOTHING`,
|
|
3015
|
-
)
|
|
3016
|
-
.run(runId, resourceId, now, now);
|
|
3017
|
-
return resourceId;
|
|
3018
|
-
}
|
|
3019
|
-
|
|
3020
2769
|
private logicalOriginRunId(runId: string): string {
|
|
3021
2770
|
let current = runId;
|
|
3022
2771
|
for (let depth = 0; depth < 100; depth += 1) {
|
|
@@ -3174,30 +2923,6 @@ export class WorkflowRunStore {
|
|
|
3174
2923
|
}
|
|
3175
2924
|
}
|
|
3176
2925
|
|
|
3177
|
-
private followUpQueueRow(runId: string): FollowUpQueueRow | undefined {
|
|
3178
|
-
const row = this.state.connection
|
|
3179
|
-
.prepare(
|
|
3180
|
-
`SELECT q.run_id AS runId, q.resource_id AS resourceId,
|
|
3181
|
-
q.origin_session_id AS originSessionId,
|
|
3182
|
-
q.presentation_state AS presentationState,
|
|
3183
|
-
q.presentation_entry_id AS presentationEntryId,
|
|
3184
|
-
q.presentation_assistant_entry_id AS presentationAssistantEntryId,
|
|
3185
|
-
q.presentation_reason_hash AS presentationReasonHash,
|
|
3186
|
-
r.revision
|
|
3187
|
-
FROM workflow_follow_up_queues q
|
|
3188
|
-
JOIN resources r ON r.resource_id = q.resource_id
|
|
3189
|
-
WHERE q.run_id = ?`,
|
|
3190
|
-
)
|
|
3191
|
-
.get(runId);
|
|
3192
|
-
return isFollowUpQueueRow(row) ? row : undefined;
|
|
3193
|
-
}
|
|
3194
|
-
|
|
3195
|
-
private requireFollowUpQueueRow(runId: string): FollowUpQueueRow {
|
|
3196
|
-
const row = this.followUpQueueRow(runId);
|
|
3197
|
-
if (row === undefined) throw new Error(`Workflow follow-up queue not found: ${runId}`);
|
|
3198
|
-
return row;
|
|
3199
|
-
}
|
|
3200
|
-
|
|
3201
2926
|
private followUpRowByRequest(runId: string, requestId: string): FollowUpRow | undefined {
|
|
3202
2927
|
const row = this.state.connection
|
|
3203
2928
|
.prepare(`${FOLLOW_UP_ROW_SELECT} WHERE f.run_id = ? AND f.request_id = ?`)
|
|
@@ -3232,11 +2957,9 @@ export class WorkflowRunStore {
|
|
|
3232
2957
|
source: row.sourceType,
|
|
3233
2958
|
prompt: this.readText(row.promptHash),
|
|
3234
2959
|
state: assertFollowUpState(row.status),
|
|
3235
|
-
...(row.sessionEntryId !== null ? { sessionEntryId: row.sessionEntryId } : {}),
|
|
3236
2960
|
...(row.reasonHash !== null ? { reason: this.readText(row.reasonHash) } : {}),
|
|
3237
2961
|
createdAt: new Date(row.createdAt).toISOString(),
|
|
3238
2962
|
updatedAt: new Date(row.updatedAt).toISOString(),
|
|
3239
|
-
...(row.sentAt !== null ? { sentAt: new Date(row.sentAt).toISOString() } : {}),
|
|
3240
2963
|
};
|
|
3241
2964
|
}
|
|
3242
2965
|
|
|
@@ -3270,13 +2993,6 @@ export class WorkflowRunStore {
|
|
|
3270
2993
|
throw new Error("Workflow follow-up can be removed only by its source or a verified human");
|
|
3271
2994
|
}
|
|
3272
2995
|
|
|
3273
|
-
private hasLiveLease(resourceId: string): boolean {
|
|
3274
|
-
const row = this.state.connection
|
|
3275
|
-
.prepare("SELECT expires_at AS expiresAt FROM leases WHERE resource_id = ?")
|
|
3276
|
-
.get(resourceId);
|
|
3277
|
-
return isLeaseExpiryRow(row) && row.expiresAt !== null && row.expiresAt > Date.now();
|
|
3278
|
-
}
|
|
3279
|
-
|
|
3280
2996
|
private nextFollowUpOrder(runId: string): number {
|
|
3281
2997
|
const row = this.state.connection
|
|
3282
2998
|
.prepare(
|
|
@@ -3309,55 +3025,51 @@ export class WorkflowRunStore {
|
|
|
3309
3025
|
|
|
3310
3026
|
private transitionFollowUpsForRunState(
|
|
3311
3027
|
state: WorkflowRunState,
|
|
3312
|
-
|
|
3028
|
+
_event: WorkflowTraceEventDraft,
|
|
3313
3029
|
now: number,
|
|
3314
3030
|
): void {
|
|
3315
|
-
if (state.status === "running" || state.status === "waiting")
|
|
3316
|
-
|
|
3317
|
-
if (queue === undefined || queue.presentationState !== "none") return;
|
|
3318
|
-
let presentationState: WorkflowPresentationState = "not-needed";
|
|
3319
|
-
let followUpState: WorkflowFollowUpState = "cancelled";
|
|
3320
|
-
let reasonHash: Buffer | null = null;
|
|
3321
|
-
if (state.status === "completed") {
|
|
3322
|
-
const required = event.payload.presentationRequired === true;
|
|
3323
|
-
presentationState = required ? "pending" : "not-needed";
|
|
3324
|
-
followUpState = required ? "pending_presentation" : "ready";
|
|
3325
|
-
} else {
|
|
3326
|
-
reasonHash = this.state.putText(
|
|
3327
|
-
state.error ?? `Workflow ended with status ${state.status}`,
|
|
3328
|
-
now,
|
|
3329
|
-
);
|
|
3031
|
+
if (state.status === "running" || state.status === "waiting" || state.status === "completed") {
|
|
3032
|
+
return;
|
|
3330
3033
|
}
|
|
3331
|
-
this.state.connection
|
|
3332
|
-
.prepare(
|
|
3333
|
-
`UPDATE workflow_follow_up_queues
|
|
3334
|
-
SET presentation_state = ?, presentation_updated_at = ?, updated_at = ?
|
|
3335
|
-
WHERE run_id = ? AND presentation_state = 'none'`,
|
|
3336
|
-
)
|
|
3337
|
-
.run(presentationState, now, now, state.runId);
|
|
3338
|
-
this.state.connection
|
|
3034
|
+
const rows = this.state.connection
|
|
3339
3035
|
.prepare(
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3036
|
+
`${FOLLOW_UP_ROW_SELECT}
|
|
3037
|
+
WHERE f.run_id IN (
|
|
3038
|
+
WITH RECURSIVE ancestors(run_id, parent_run_id) AS (
|
|
3039
|
+
SELECT run_id, parent_run_id FROM runs WHERE run_id = ?
|
|
3040
|
+
UNION ALL
|
|
3041
|
+
SELECT parent.run_id, parent.parent_run_id
|
|
3042
|
+
FROM runs parent JOIN ancestors ON ancestors.parent_run_id = parent.run_id
|
|
3043
|
+
)
|
|
3044
|
+
SELECT run_id FROM ancestors
|
|
3045
|
+
) AND f.status = 'queued'`,
|
|
3343
3046
|
)
|
|
3344
|
-
.
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3047
|
+
.all(state.runId)
|
|
3048
|
+
.filter(isFollowUpRow);
|
|
3049
|
+
for (const row of rows) {
|
|
3050
|
+
const reason = state.error ?? `Workflow ended with status ${state.status}`;
|
|
3051
|
+
const reasonHash = this.state.putText(reason, now);
|
|
3052
|
+
const update = this.state.connection
|
|
3053
|
+
.prepare(
|
|
3054
|
+
`UPDATE workflow_follow_ups
|
|
3055
|
+
SET status = 'cancelled', reason_hash = ?, updated_at = ?
|
|
3056
|
+
WHERE follow_up_id = ? AND status = 'queued'`,
|
|
3057
|
+
)
|
|
3058
|
+
.run(reasonHash, now, row.followUpId);
|
|
3059
|
+
if (update.changes !== 1) continue;
|
|
3060
|
+
this.workflowMessages.cancelPendingForSource(row.followUpId, "followUp", now);
|
|
3061
|
+
this.bumpResource(row.resourceId, row.revision, now);
|
|
3062
|
+
insertGenericEvent(
|
|
3063
|
+
this.state,
|
|
3064
|
+
row.resourceId,
|
|
3065
|
+
row.revision + 1,
|
|
3066
|
+
"follow-up.cancelled",
|
|
3067
|
+
"system",
|
|
3068
|
+
null,
|
|
3069
|
+
this.state.putJson({ runId: state.runId, status: state.status, reason }, now),
|
|
3070
|
+
now,
|
|
3071
|
+
);
|
|
3072
|
+
}
|
|
3361
3073
|
}
|
|
3362
3074
|
|
|
3363
3075
|
private syncNodeAttempts(
|
|
@@ -3554,7 +3266,7 @@ export class WorkflowRunStore {
|
|
|
3554
3266
|
JOIN session_segments s ON s.segment_id = e.segment_id
|
|
3555
3267
|
JOIN blobs b ON b.blob_hash = e.entry_hash
|
|
3556
3268
|
WHERE s.run_id = ?
|
|
3557
|
-
AND json_extract(CAST(b.content AS TEXT), '$.customType') = 'pi-workflows-
|
|
3269
|
+
AND json_extract(CAST(b.content AS TEXT), '$.customType') = 'pi-workflows-step'
|
|
3558
3270
|
AND json_extract(CAST(b.content AS TEXT), '$.details.contract.attemptId') = ?
|
|
3559
3271
|
ORDER BY e.recorded_at DESC LIMIT 1`,
|
|
3560
3272
|
)
|
|
@@ -3766,7 +3478,15 @@ export class WorkflowRunStore {
|
|
|
3766
3478
|
): WorkflowRunState {
|
|
3767
3479
|
const sources = this.readRunSources(row.runId, snapshot);
|
|
3768
3480
|
const carriedStepCount = this.carriedStepCount(row.runId);
|
|
3769
|
-
const activeAttempt =
|
|
3481
|
+
const activeAttempt =
|
|
3482
|
+
row.status === "running" || row.status === "waiting"
|
|
3483
|
+
? this.readActiveAttempt(row.runId)
|
|
3484
|
+
: undefined;
|
|
3485
|
+
const runningAttempt = row.status === "running" ? activeAttempt : undefined;
|
|
3486
|
+
const waitingNode =
|
|
3487
|
+
row.status === "waiting"
|
|
3488
|
+
? (activeAttempt?.nodeId ?? projectionSteps.at(-1)?.nodeId)
|
|
3489
|
+
: undefined;
|
|
3770
3490
|
const humanDecision = this.readHumanDecisionReceipt(row.runId);
|
|
3771
3491
|
const outputs: Record<string, unknown> = {};
|
|
3772
3492
|
const results: Record<string, WorkflowNodeResult> = {};
|
|
@@ -3808,7 +3528,7 @@ export class WorkflowRunStore {
|
|
|
3808
3528
|
results,
|
|
3809
3529
|
steps: visibleSteps,
|
|
3810
3530
|
...(visibleUpdates.length === 0 ? {} : { updates: visibleUpdates }),
|
|
3811
|
-
...(
|
|
3531
|
+
...(runningAttempt === undefined ? {} : { currentNode: runningAttempt.nodeId }),
|
|
3812
3532
|
...(activeAttempt === undefined ? {} : { currentAttemptId: activeAttempt.attemptId }),
|
|
3813
3533
|
...(activeAttempt?.startedAt === null || activeAttempt === undefined
|
|
3814
3534
|
? {}
|
|
@@ -3829,9 +3549,7 @@ export class WorkflowRunStore {
|
|
|
3829
3549
|
...(row.statusDetail === null ? {} : { statusDetail: row.statusDetail }),
|
|
3830
3550
|
...(humanDecision === undefined ? {} : { humanDecision }),
|
|
3831
3551
|
...(row.paused === 0 ? {} : { paused: true }),
|
|
3832
|
-
...(
|
|
3833
|
-
? { waitingOn: projectionSteps.at(-1)?.nodeId as string }
|
|
3834
|
-
: {}),
|
|
3552
|
+
...(waitingNode === undefined ? {} : { waitingOn: waitingNode }),
|
|
3835
3553
|
...(row.finalOutputHash === null
|
|
3836
3554
|
? {}
|
|
3837
3555
|
: { finalOutput: this.state.readJson(row.finalOutputHash) }),
|
|
@@ -4876,32 +4594,12 @@ function assertActorType(value: string): ActorType {
|
|
|
4876
4594
|
}
|
|
4877
4595
|
|
|
4878
4596
|
function assertFollowUpState(value: string): WorkflowFollowUpState {
|
|
4879
|
-
if (
|
|
4880
|
-
value !== "queued" &&
|
|
4881
|
-
value !== "pending_presentation" &&
|
|
4882
|
-
value !== "ready" &&
|
|
4883
|
-
value !== "sent" &&
|
|
4884
|
-
value !== "removed" &&
|
|
4885
|
-
value !== "cancelled"
|
|
4886
|
-
) {
|
|
4597
|
+
if (value !== "queued" && value !== "removed" && value !== "cancelled") {
|
|
4887
4598
|
throw new Error(`Unknown workflow follow-up state: ${value}`);
|
|
4888
4599
|
}
|
|
4889
4600
|
return value;
|
|
4890
4601
|
}
|
|
4891
4602
|
|
|
4892
|
-
function assertPresentationState(value: string): WorkflowPresentationState {
|
|
4893
|
-
if (
|
|
4894
|
-
value !== "none" &&
|
|
4895
|
-
value !== "not-needed" &&
|
|
4896
|
-
value !== "pending" &&
|
|
4897
|
-
value !== "settled" &&
|
|
4898
|
-
value !== "unavailable"
|
|
4899
|
-
) {
|
|
4900
|
-
throw new Error(`Unknown workflow presentation state: ${value}`);
|
|
4901
|
-
}
|
|
4902
|
-
return value;
|
|
4903
|
-
}
|
|
4904
|
-
|
|
4905
4603
|
function isSettingsScopeRow(value: unknown): value is SettingsScopeRow {
|
|
4906
4604
|
return (
|
|
4907
4605
|
isRecord(value) &&
|
|
@@ -4955,27 +4653,11 @@ function isSettingsChangeRow(value: unknown): value is SettingsChangeRow {
|
|
|
4955
4653
|
);
|
|
4956
4654
|
}
|
|
4957
4655
|
|
|
4958
|
-
function isFollowUpQueueRow(value: unknown): value is FollowUpQueueRow {
|
|
4959
|
-
return (
|
|
4960
|
-
isRecord(value) &&
|
|
4961
|
-
typeof value.runId === "string" &&
|
|
4962
|
-
typeof value.resourceId === "string" &&
|
|
4963
|
-
(typeof value.originSessionId === "string" || value.originSessionId === null) &&
|
|
4964
|
-
typeof value.presentationState === "string" &&
|
|
4965
|
-
(typeof value.presentationEntryId === "string" || value.presentationEntryId === null) &&
|
|
4966
|
-
(typeof value.presentationAssistantEntryId === "string" ||
|
|
4967
|
-
value.presentationAssistantEntryId === null) &&
|
|
4968
|
-
(Buffer.isBuffer(value.presentationReasonHash) || value.presentationReasonHash === null) &&
|
|
4969
|
-
typeof value.revision === "number"
|
|
4970
|
-
);
|
|
4971
|
-
}
|
|
4972
|
-
|
|
4973
4656
|
function isFollowUpRow(value: unknown): value is FollowUpRow {
|
|
4974
4657
|
return (
|
|
4975
4658
|
isRecord(value) &&
|
|
4976
4659
|
typeof value.followUpId === "string" &&
|
|
4977
4660
|
typeof value.resourceId === "string" &&
|
|
4978
|
-
typeof value.queueResourceId === "string" &&
|
|
4979
4661
|
typeof value.runId === "string" &&
|
|
4980
4662
|
typeof value.requestId === "string" &&
|
|
4981
4663
|
typeof value.orderNumber === "number" &&
|
|
@@ -4985,11 +4667,9 @@ function isFollowUpRow(value: unknown): value is FollowUpRow {
|
|
|
4985
4667
|
typeof value.sourceType === "string" &&
|
|
4986
4668
|
Buffer.isBuffer(value.promptHash) &&
|
|
4987
4669
|
typeof value.status === "string" &&
|
|
4988
|
-
(typeof value.sessionEntryId === "string" || value.sessionEntryId === null) &&
|
|
4989
4670
|
(Buffer.isBuffer(value.reasonHash) || value.reasonHash === null) &&
|
|
4990
4671
|
typeof value.createdAt === "number" &&
|
|
4991
4672
|
typeof value.updatedAt === "number" &&
|
|
4992
|
-
(typeof value.sentAt === "number" || value.sentAt === null) &&
|
|
4993
4673
|
typeof value.revision === "number"
|
|
4994
4674
|
);
|
|
4995
4675
|
}
|
|
@@ -4998,6 +4678,14 @@ function isParentRunRow(value: unknown): value is { parentRunId: string | null }
|
|
|
4998
4678
|
return isRecord(value) && (typeof value.parentRunId === "string" || value.parentRunId === null);
|
|
4999
4679
|
}
|
|
5000
4680
|
|
|
4681
|
+
function isRunLineageRow(value: unknown): value is { rootRunId: string; restartNumber: number } {
|
|
4682
|
+
return (
|
|
4683
|
+
isRecord(value) &&
|
|
4684
|
+
typeof value.rootRunId === "string" &&
|
|
4685
|
+
typeof value.restartNumber === "number"
|
|
4686
|
+
);
|
|
4687
|
+
}
|
|
4688
|
+
|
|
5001
4689
|
function isStatusRow(value: unknown): value is { status: string } {
|
|
5002
4690
|
return isRecord(value) && typeof value.status === "string";
|
|
5003
4691
|
}
|
|
@@ -5010,10 +4698,6 @@ function isStepIndexRow(value: unknown): value is { stepIndex: number } {
|
|
|
5010
4698
|
return isRecord(value) && typeof value.stepIndex === "number";
|
|
5011
4699
|
}
|
|
5012
4700
|
|
|
5013
|
-
function isLeaseExpiryRow(value: unknown): value is { expiresAt: number | null } {
|
|
5014
|
-
return isRecord(value) && (typeof value.expiresAt === "number" || value.expiresAt === null);
|
|
5015
|
-
}
|
|
5016
|
-
|
|
5017
4701
|
function isNextOrderRow(value: unknown): value is { nextOrder: number } {
|
|
5018
4702
|
return isRecord(value) && typeof value.nextOrder === "number";
|
|
5019
4703
|
}
|