@osolmaz/pi-workflows 0.15.2 → 0.16.0
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 +6 -6
- package/dist/client/activity.d.ts +2 -0
- package/dist/client/activity.js +6 -0
- package/dist/client/activity.js.map +1 -0
- package/dist/client/client.d.ts +101 -0
- package/dist/client/client.js +733 -0
- package/dist/client/client.js.map +1 -0
- package/dist/client/index.d.ts +3 -0
- package/dist/client/index.js +3 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/materialize.d.ts +7 -0
- package/dist/client/materialize.js +177 -0
- package/dist/client/materialize.js.map +1 -0
- package/dist/client/protocol.d.ts +60 -0
- package/dist/client/protocol.js +269 -0
- package/dist/client/protocol.js.map +1 -0
- package/dist/client/resolver.d.ts +23 -0
- package/dist/client/resolver.js +2 -0
- package/dist/client/resolver.js.map +1 -0
- package/dist/client/view.d.ts +118 -0
- package/dist/client/view.js +3 -0
- package/dist/client/view.js.map +1 -0
- package/dist/controllers/sqlite.d.ts +64 -0
- package/dist/controllers/sqlite.js +219 -3
- package/dist/controllers/sqlite.js.map +1 -1
- package/dist/extension/index.d.ts +1 -0
- package/dist/extension/index.js +384 -156
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/session-delivery.d.ts +6 -0
- package/dist/extension/session-delivery.js +80 -25
- package/dist/extension/session-delivery.js.map +1 -1
- package/dist/extension/session-view.d.ts +21 -0
- package/dist/extension/session-view.js +127 -0
- package/dist/extension/session-view.js.map +1 -0
- package/dist/extension/widget.d.ts +2 -1
- package/dist/extension/widget.js +15 -7
- package/dist/extension/widget.js.map +1 -1
- package/dist/host/child-worker-supervisor.js +1 -1
- package/dist/host/child-worker-supervisor.js.map +1 -1
- package/dist/host/resolver-entry.d.ts +2 -23
- package/dist/host/resolver-entry.js +1 -1
- package/dist/host/resolver-entry.js.map +1 -1
- package/dist/host/runner.d.ts +12 -0
- package/dist/host/runner.js +565 -43
- package/dist/host/runner.js.map +1 -1
- package/dist/host/state.d.ts +8 -3
- package/dist/host/state.js +59 -27
- package/dist/host/state.js.map +1 -1
- package/dist/host/view.d.ts +73 -0
- package/dist/host/view.js +871 -0
- package/dist/host/view.js.map +1 -0
- package/dist/host/worker-protocol.js +1 -1
- package/dist/host/worker-protocol.js.map +1 -1
- package/dist/state/database.d.ts +1 -0
- package/dist/state/database.js +15 -0
- package/dist/state/database.js.map +1 -1
- package/dist/state/prune.d.ts +3 -1
- package/dist/state/prune.js +6 -8
- package/dist/state/prune.js.map +1 -1
- package/dist/state/schema.js +12 -1
- package/dist/state/schema.js.map +1 -1
- package/dist/viewer/backup.d.ts +2 -0
- package/dist/viewer/backup.js +28 -0
- package/dist/viewer/backup.js.map +1 -0
- package/dist/viewer/cli.d.ts +4 -0
- package/dist/viewer/cli.js +150 -170
- package/dist/viewer/cli.js.map +1 -1
- package/dist/viewer/tui.d.ts +5 -7
- package/dist/viewer/tui.js +188 -108
- package/dist/viewer/tui.js.map +1 -1
- package/dist/workflows/store.d.ts +62 -1
- package/dist/workflows/store.js +350 -44
- package/dist/workflows/store.js.map +1 -1
- package/docs/2026-09-01-restore-session-delivery-controls-plan.md +139 -0
- package/docs/2026-09-01-unified-workflow-client-plan.md +381 -0
- package/docs/2026-09-02-installed-live-e2e-plan.md +225 -0
- package/docs/SQLITE_STATE.md +13 -11
- package/docs/WORKFLOW_HOST.md +81 -64
- package/docs/WORKFLOW_STEP_MESSAGES.md +4 -4
- package/docs/development.md +2 -1
- package/docs/live-replay-protocol.md +70 -132
- package/docs/tui-viewer.md +10 -14
- package/docs/workflows.md +56 -3
- package/herdr-plugin.toml +1 -1
- package/package.json +9 -3
- package/protocol/client.v1.schema.json +137 -0
- package/protocol/fixtures/client-v1.json +23 -0
- package/src/client/activity.ts +6 -0
- package/src/client/client.ts +935 -0
- package/src/client/index.ts +24 -0
- package/src/client/materialize.ts +228 -0
- package/src/client/protocol.ts +327 -0
- package/src/client/resolver.ts +26 -0
- package/src/client/view.ts +138 -0
- package/src/controllers/sqlite.ts +342 -3
- package/src/extension/index.ts +482 -171
- package/src/extension/session-delivery.ts +88 -25
- package/src/extension/session-view.ts +154 -0
- package/src/extension/widget.ts +18 -9
- package/src/host/child-worker-supervisor.ts +1 -1
- package/src/host/resolver-entry.ts +11 -26
- package/src/host/runner.ts +749 -75
- package/src/host/state.ts +82 -44
- package/src/host/view.ts +1084 -0
- package/src/host/worker-protocol.ts +1 -1
- package/src/state/database.ts +13 -0
- package/src/state/prune.ts +11 -11
- package/src/state/schema.ts +12 -1
- package/src/viewer/backup.ts +29 -0
- package/src/viewer/cli.ts +171 -185
- package/src/viewer/tui.ts +196 -124
- package/src/workflows/store.ts +500 -45
- package/dist/host/client.d.ts +0 -48
- package/dist/host/client.js +0 -216
- package/dist/host/client.js.map +0 -1
- package/dist/host/protocol.d.ts +0 -38
- package/dist/host/protocol.js +0 -156
- package/dist/host/protocol.js.map +0 -1
- package/dist/viewer/watch.d.ts +0 -6
- package/dist/viewer/watch.js +0 -46
- package/dist/viewer/watch.js.map +0 -1
- package/src/host/client.ts +0 -293
- package/src/host/protocol.ts +0 -196
- package/src/viewer/watch.ts +0 -51
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolver.js","sourceRoot":"","sources":["../../src/client/resolver.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import type { JsonValue } from "../state/json.js";
|
|
2
|
+
export declare const RUN_VIEW_SCHEMA: "pi-workflows.run-view.v1";
|
|
3
|
+
export declare const SESSION_VIEW_SCHEMA: "pi-workflows.session-view.v1";
|
|
4
|
+
export type WorkflowDisplayStatus = "queued" | "running" | "waiting" | "paused" | "completed" | "failed" | "timed_out" | "cancelled" | "ambiguous";
|
|
5
|
+
export type WorkflowDisplay = {
|
|
6
|
+
status: WorkflowDisplayStatus;
|
|
7
|
+
activity: "supervised_worker" | "origin_turn" | null;
|
|
8
|
+
controls: Array<"pause" | "resume" | "cancel" | "answer" | "review">;
|
|
9
|
+
reason: string | null;
|
|
10
|
+
reasonContent?: JsonValue;
|
|
11
|
+
};
|
|
12
|
+
export type WorkflowRunQueueView = {
|
|
13
|
+
runId: string;
|
|
14
|
+
workflowName: string;
|
|
15
|
+
workflowSourceRef: string;
|
|
16
|
+
initialized: boolean;
|
|
17
|
+
definitionDigest: string;
|
|
18
|
+
status: "queued" | "starting" | "running" | "parked" | "done" | "failed" | "cancelled";
|
|
19
|
+
originSessionId: string | null;
|
|
20
|
+
executionMode: "interactive" | "headless";
|
|
21
|
+
parentRunId: string | null;
|
|
22
|
+
errorCode: string | null;
|
|
23
|
+
createdAt: string;
|
|
24
|
+
updatedAt: string;
|
|
25
|
+
startedAt: string | null;
|
|
26
|
+
finishedAt: string | null;
|
|
27
|
+
};
|
|
28
|
+
export type WorkflowRunView = {
|
|
29
|
+
schema: typeof RUN_VIEW_SCHEMA;
|
|
30
|
+
runId: string;
|
|
31
|
+
revision: number;
|
|
32
|
+
display: WorkflowDisplay;
|
|
33
|
+
manifest: JsonValue;
|
|
34
|
+
state: JsonValue;
|
|
35
|
+
workflow: JsonValue;
|
|
36
|
+
queue: WorkflowRunQueueView;
|
|
37
|
+
updates: JsonValue[];
|
|
38
|
+
graphSteps: JsonValue[];
|
|
39
|
+
graphStepStart: number;
|
|
40
|
+
graphStepTotal: number;
|
|
41
|
+
takenTransitions: string[];
|
|
42
|
+
graphHistory: JsonValue;
|
|
43
|
+
takenTransitionStart: number;
|
|
44
|
+
takenTransitionTotal: number;
|
|
45
|
+
graphCursor: number;
|
|
46
|
+
stepStart: number;
|
|
47
|
+
stepTotal: number;
|
|
48
|
+
tracePage: JsonValue;
|
|
49
|
+
session: JsonValue;
|
|
50
|
+
settingsScopes: JsonValue[];
|
|
51
|
+
settingsStart: number;
|
|
52
|
+
settingsTotal: number;
|
|
53
|
+
followUpQueue: JsonValue;
|
|
54
|
+
followUpStart: number;
|
|
55
|
+
followUpTotal: number;
|
|
56
|
+
updateStart: number;
|
|
57
|
+
updateTotal: number;
|
|
58
|
+
live: boolean;
|
|
59
|
+
possiblyInterrupted: boolean;
|
|
60
|
+
};
|
|
61
|
+
export type WorkflowRunListPage = {
|
|
62
|
+
schema: "pi-workflows.run-list-page.v1";
|
|
63
|
+
revision: string;
|
|
64
|
+
start: number;
|
|
65
|
+
total: number;
|
|
66
|
+
items: WorkflowRunSummary[];
|
|
67
|
+
};
|
|
68
|
+
export type WorkflowRunSummary = {
|
|
69
|
+
runId: string;
|
|
70
|
+
workflowName: string;
|
|
71
|
+
originSessionId: string | null;
|
|
72
|
+
createdAt: string;
|
|
73
|
+
updatedAt: string;
|
|
74
|
+
display: WorkflowDisplay;
|
|
75
|
+
manifest: JsonValue;
|
|
76
|
+
live: boolean;
|
|
77
|
+
possiblyInterrupted: boolean;
|
|
78
|
+
};
|
|
79
|
+
export type ClientInteractiveRequest = {
|
|
80
|
+
requestId: string;
|
|
81
|
+
runId: string;
|
|
82
|
+
attemptId: string;
|
|
83
|
+
targetSessionId: string;
|
|
84
|
+
kind: "agent" | "assistant" | "decision";
|
|
85
|
+
contract: JsonValue;
|
|
86
|
+
revision: number;
|
|
87
|
+
status: "pending" | "presenting" | "settled" | "cancelled";
|
|
88
|
+
presenterId: string | null;
|
|
89
|
+
presentationClaimExpiresAt: string | null;
|
|
90
|
+
presentationSessionEntryId: string | null;
|
|
91
|
+
acceptedSubmissionId: string | null;
|
|
92
|
+
createdAt: string;
|
|
93
|
+
updatedAt: string;
|
|
94
|
+
settledAt: string | null;
|
|
95
|
+
consumedAt: string | null;
|
|
96
|
+
};
|
|
97
|
+
export type WorkflowDeliveryAvailability = {
|
|
98
|
+
notification: boolean;
|
|
99
|
+
turn: boolean;
|
|
100
|
+
};
|
|
101
|
+
export type WorkflowSessionView = {
|
|
102
|
+
schema: typeof SESSION_VIEW_SCHEMA;
|
|
103
|
+
sessionId: string;
|
|
104
|
+
run: WorkflowRunView | null;
|
|
105
|
+
pendingInteractions: JsonValue[];
|
|
106
|
+
pendingInteractionStart: number;
|
|
107
|
+
pendingInteractionTotal: number;
|
|
108
|
+
deliveries: WorkflowDeliveryAvailability;
|
|
109
|
+
};
|
|
110
|
+
export type OriginActivityReport = {
|
|
111
|
+
sessionId: string;
|
|
112
|
+
runId: string;
|
|
113
|
+
requestId: string;
|
|
114
|
+
deliveryId: string;
|
|
115
|
+
sessionEntryId: string;
|
|
116
|
+
sequence: number;
|
|
117
|
+
state: "started" | "refresh" | "settled";
|
|
118
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"view.js","sourceRoot":"","sources":["../../src/client/view.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,eAAe,GAAG,0BAAmC,CAAC;AACnE,MAAM,CAAC,MAAM,mBAAmB,GAAG,8BAAuC,CAAC"}
|
|
@@ -22,6 +22,26 @@ export type WorkflowRunClaimOptions = WorkflowRunReservationOptions & {
|
|
|
22
22
|
claimToken: string;
|
|
23
23
|
leaseMs: number;
|
|
24
24
|
};
|
|
25
|
+
export type WorkflowRunQueueViewRecord = {
|
|
26
|
+
runId: string;
|
|
27
|
+
workflowName: string;
|
|
28
|
+
workflowSourceRef: string;
|
|
29
|
+
workflowSource: unknown;
|
|
30
|
+
initialized: boolean;
|
|
31
|
+
definitionDigest: string;
|
|
32
|
+
runStateStatus: string;
|
|
33
|
+
paused: boolean;
|
|
34
|
+
status: WorkflowRunLaunchStatus;
|
|
35
|
+
originSessionId: string | null;
|
|
36
|
+
executionMode: "interactive" | "headless";
|
|
37
|
+
parentRunId: string | null;
|
|
38
|
+
errorCode: string | null;
|
|
39
|
+
errorMessage: string | null;
|
|
40
|
+
createdAt: string;
|
|
41
|
+
updatedAt: string;
|
|
42
|
+
startedAt: string | null;
|
|
43
|
+
finishedAt: string | null;
|
|
44
|
+
};
|
|
25
45
|
export type WorkflowRunQueueRecord = {
|
|
26
46
|
runId: string;
|
|
27
47
|
workflowName: string;
|
|
@@ -212,6 +232,19 @@ export declare class SqliteControllerStore implements ControllerStore {
|
|
|
212
232
|
prepareOrAdoptWorkflowRun(options: WorkflowRunClaimOptions): WorkflowRunPreparationResult;
|
|
213
233
|
enqueueWorkflowRun(options: WorkflowRunClaimOptions): WorkflowRunQueueRecord;
|
|
214
234
|
getWorkflowRun(runId: string): WorkflowRunQueueRecord | undefined;
|
|
235
|
+
getWorkflowRunView(runId: string): WorkflowRunQueueViewRecord | undefined;
|
|
236
|
+
workflowRunListRevision(): {
|
|
237
|
+
total: number;
|
|
238
|
+
revision: string;
|
|
239
|
+
};
|
|
240
|
+
listWorkflowRunViews(options: {
|
|
241
|
+
offset: number;
|
|
242
|
+
limit: number;
|
|
243
|
+
}): {
|
|
244
|
+
total: number;
|
|
245
|
+
revision: string;
|
|
246
|
+
runs: WorkflowRunQueueViewRecord[];
|
|
247
|
+
};
|
|
215
248
|
workflowRunProjectPath(runId: string): string | undefined;
|
|
216
249
|
listWorkflowRuns(options?: {
|
|
217
250
|
statuses?: WorkflowRunLaunchStatus[];
|
|
@@ -219,6 +252,7 @@ export declare class SqliteControllerStore implements ControllerStore {
|
|
|
219
252
|
limit?: number;
|
|
220
253
|
}): WorkflowRunQueueRecord[];
|
|
221
254
|
findSessionReservation(sessionId: string): WorkflowRunQueueRecord | undefined;
|
|
255
|
+
findSessionReservationView(sessionId: string): WorkflowRunQueueViewRecord | undefined;
|
|
222
256
|
claimWorkflowRun(options: {
|
|
223
257
|
runId: string;
|
|
224
258
|
runnerId: string;
|
|
@@ -287,6 +321,15 @@ export declare class SqliteControllerStore implements ControllerStore {
|
|
|
287
321
|
claimToken: string;
|
|
288
322
|
now?: string;
|
|
289
323
|
}): boolean;
|
|
324
|
+
pauseParkedWorkflowRun(options: {
|
|
325
|
+
runId: string;
|
|
326
|
+
now?: string;
|
|
327
|
+
}): boolean;
|
|
328
|
+
resumePausedInteraction(options: {
|
|
329
|
+
runId: string;
|
|
330
|
+
now?: string;
|
|
331
|
+
}): boolean;
|
|
332
|
+
isWorkflowRunPaused(runId: string): boolean;
|
|
290
333
|
parkWorkflowRunForSourceChange(options: {
|
|
291
334
|
runId: string;
|
|
292
335
|
claimToken: string;
|
|
@@ -357,6 +400,12 @@ export declare class SqliteControllerStore implements ControllerStore {
|
|
|
357
400
|
runId: string;
|
|
358
401
|
targetSessionId: string;
|
|
359
402
|
}): WorkflowTurnIntentRecord | undefined;
|
|
403
|
+
isWorkflowTurnIntentClaimLive(options: {
|
|
404
|
+
intentId: string;
|
|
405
|
+
targetSessionId: string;
|
|
406
|
+
claimToken: string;
|
|
407
|
+
now?: string;
|
|
408
|
+
}): boolean;
|
|
360
409
|
claimWorkflowTurnIntent(options: {
|
|
361
410
|
intentId: string;
|
|
362
411
|
targetSessionId: string;
|
|
@@ -371,6 +420,10 @@ export declare class SqliteControllerStore implements ControllerStore {
|
|
|
371
420
|
now?: string;
|
|
372
421
|
limit?: number;
|
|
373
422
|
}): WorkflowTurnIntentRecord[];
|
|
423
|
+
hasClaimableWorkflowTurnIntent(options: {
|
|
424
|
+
targetSessionId: string;
|
|
425
|
+
now?: string;
|
|
426
|
+
}): boolean;
|
|
374
427
|
makeWorkflowTurnIntentEligible(options: {
|
|
375
428
|
intentId: string;
|
|
376
429
|
fallbackFacts: WorkflowTurnIntentFacts;
|
|
@@ -406,6 +459,10 @@ export declare class SqliteControllerStore implements ControllerStore {
|
|
|
406
459
|
content: string;
|
|
407
460
|
now?: string;
|
|
408
461
|
}): WorkflowNotificationRecord;
|
|
462
|
+
hasClaimableWorkflowNotification(options: {
|
|
463
|
+
targetSessionId: string;
|
|
464
|
+
now?: string;
|
|
465
|
+
}): boolean;
|
|
409
466
|
listPendingWorkflowNotifications(options: {
|
|
410
467
|
targetSessionId: string;
|
|
411
468
|
limit?: number;
|
|
@@ -417,6 +474,12 @@ export declare class SqliteControllerStore implements ControllerStore {
|
|
|
417
474
|
now?: string;
|
|
418
475
|
limit?: number;
|
|
419
476
|
}): WorkflowNotificationRecord[];
|
|
477
|
+
isWorkflowNotificationClaimLive(options: {
|
|
478
|
+
notificationId: string;
|
|
479
|
+
targetSessionId: string;
|
|
480
|
+
claimToken: string;
|
|
481
|
+
now?: string;
|
|
482
|
+
}): boolean;
|
|
420
483
|
markWorkflowNotificationDelivered(options: {
|
|
421
484
|
notificationId: string;
|
|
422
485
|
targetSessionId: string;
|
|
@@ -457,6 +520,7 @@ export declare class SqliteControllerStore implements ControllerStore {
|
|
|
457
520
|
private requireWorkflowRunRow;
|
|
458
521
|
private requireWorkflowRun;
|
|
459
522
|
private assertWorkflowRunPreparationCompatible;
|
|
523
|
+
private workflowRunViewRows;
|
|
460
524
|
private mapWorkflowRun;
|
|
461
525
|
private claimRun;
|
|
462
526
|
private claimRunInTransaction;
|
|
@@ -640,6 +640,32 @@ export class SqliteControllerStore {
|
|
|
640
640
|
const row = this.workflowRunRow(runId);
|
|
641
641
|
return row === undefined ? undefined : this.mapWorkflowRun(row);
|
|
642
642
|
}
|
|
643
|
+
getWorkflowRunView(runId) {
|
|
644
|
+
const row = this.workflowRunViewRows("WHERE r.run_id = ?", [runId])[0];
|
|
645
|
+
return row === undefined ? undefined : workflowRunViewRecord(row);
|
|
646
|
+
}
|
|
647
|
+
workflowRunListRevision() {
|
|
648
|
+
const row = this.state.connection
|
|
649
|
+
.prepare(`SELECT count(*) AS count, COALESCE(sum(res.revision), 0) AS revisionSum,
|
|
650
|
+
COALESCE(max(q.updated_at), 0) AS updatedAt
|
|
651
|
+
FROM runs r JOIN run_queue q ON q.run_id = r.run_id
|
|
652
|
+
JOIN resources res ON res.resource_id = r.resource_id`)
|
|
653
|
+
.get();
|
|
654
|
+
if (!isRunListRevisionRow(row))
|
|
655
|
+
throw new Error("Workflow run list revision is invalid");
|
|
656
|
+
return {
|
|
657
|
+
total: row.count,
|
|
658
|
+
revision: `${row.count}:${row.revisionSum}:${row.updatedAt}`,
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
listWorkflowRunViews(options) {
|
|
662
|
+
const current = this.workflowRunListRevision();
|
|
663
|
+
const rows = this.workflowRunViewRows("ORDER BY q.created_at DESC LIMIT ? OFFSET ?", [
|
|
664
|
+
options.limit,
|
|
665
|
+
options.offset,
|
|
666
|
+
]);
|
|
667
|
+
return { ...current, runs: rows.map(workflowRunViewRecord) };
|
|
668
|
+
}
|
|
643
669
|
workflowRunProjectPath(runId) {
|
|
644
670
|
const row = this.state.connection
|
|
645
671
|
.prepare(`SELECT p.canonical_path AS canonicalPath
|
|
@@ -662,9 +688,10 @@ export class SqliteControllerStore {
|
|
|
662
688
|
clauses.push(`r.run_id NOT IN (${options.excludeRunIds.map(() => "?").join(", ")})`);
|
|
663
689
|
params.push(...options.excludeRunIds);
|
|
664
690
|
}
|
|
665
|
-
|
|
691
|
+
if (options.limit !== undefined)
|
|
692
|
+
params.push(options.limit);
|
|
666
693
|
const rows = this.state.connection
|
|
667
|
-
.prepare(workflowRunSelect(`${clauses.length === 0 ? "" : `WHERE ${clauses.join(" AND ")}`} ORDER BY q.created_at DESC LIMIT
|
|
694
|
+
.prepare(workflowRunSelect(`${clauses.length === 0 ? "" : `WHERE ${clauses.join(" AND ")}`} ORDER BY q.created_at DESC${options.limit === undefined ? "" : " LIMIT ?"}`))
|
|
668
695
|
.all(...params);
|
|
669
696
|
return rows.filter(isRunRow).map((row) => this.mapWorkflowRun(row));
|
|
670
697
|
}
|
|
@@ -674,6 +701,10 @@ export class SqliteControllerStore {
|
|
|
674
701
|
.get(sessionId);
|
|
675
702
|
return isRunRow(row) ? this.mapWorkflowRun(row) : undefined;
|
|
676
703
|
}
|
|
704
|
+
findSessionReservationView(sessionId) {
|
|
705
|
+
const row = this.workflowRunViewRows("WHERE b.origin_session_id = ? AND q.status NOT IN ('done', 'failed', 'cancelled') ORDER BY q.created_at DESC LIMIT 1", [sessionId])[0];
|
|
706
|
+
return row === undefined ? undefined : workflowRunViewRecord(row);
|
|
707
|
+
}
|
|
677
708
|
claimWorkflowRun(options) {
|
|
678
709
|
const now = epoch(validTimestamp(options.now));
|
|
679
710
|
return this.claimRun(options.runId, options.runnerId, options.claimToken, options.leaseMs, now);
|
|
@@ -832,6 +863,74 @@ export class SqliteControllerStore {
|
|
|
832
863
|
parkWorkflowRun(options) {
|
|
833
864
|
return this.releaseRunClaim(options.runId, options.claimToken, "parked", options.now);
|
|
834
865
|
}
|
|
866
|
+
pauseParkedWorkflowRun(options) {
|
|
867
|
+
const now = epoch(validTimestamp(options.now));
|
|
868
|
+
return this.state.transaction(() => {
|
|
869
|
+
const row = this.workflowRunRow(options.runId);
|
|
870
|
+
if (row === undefined || row.status !== "parked")
|
|
871
|
+
return false;
|
|
872
|
+
const lease = this.requireLease(row.resourceId);
|
|
873
|
+
if (lease.ownerId !== null && lease.expiresAt !== null && lease.expiresAt > now)
|
|
874
|
+
return false;
|
|
875
|
+
const pending = this.state.connection
|
|
876
|
+
.prepare(`SELECT 1 FROM interactive_requests
|
|
877
|
+
WHERE run_id = ? AND status IN ('pending', 'presenting') LIMIT 1`)
|
|
878
|
+
.get(options.runId);
|
|
879
|
+
if (pending === undefined)
|
|
880
|
+
return false;
|
|
881
|
+
if (row.paused === 1)
|
|
882
|
+
return true;
|
|
883
|
+
if (!["running", "waiting"].includes(row.runStatus))
|
|
884
|
+
return false;
|
|
885
|
+
const changed = this.state.connection
|
|
886
|
+
.prepare(`UPDATE runs SET paused = 1, status_detail = 'paused', updated_at = ?
|
|
887
|
+
WHERE run_id = ? AND paused = 0 AND status IN ('running', 'waiting')`)
|
|
888
|
+
.run(now, options.runId);
|
|
889
|
+
if (changed.changes !== 1)
|
|
890
|
+
return false;
|
|
891
|
+
const revision = this.resourceRevision(row.resourceId);
|
|
892
|
+
this.bumpResource(row.resourceId, revision, now);
|
|
893
|
+
this.insertEvent(row.resourceId, revision + 1, "run.paused", "control", null, { status: "parked" }, now);
|
|
894
|
+
recordViewerDeltas(this.state, options.runId, [{ targetType: "summary" }, { targetType: "replay" }], now);
|
|
895
|
+
return true;
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
resumePausedInteraction(options) {
|
|
899
|
+
const now = epoch(validTimestamp(options.now));
|
|
900
|
+
return this.state.transaction(() => {
|
|
901
|
+
const row = this.workflowRunRow(options.runId);
|
|
902
|
+
if (row === undefined ||
|
|
903
|
+
row.status !== "parked" ||
|
|
904
|
+
row.runStatus !== "waiting" ||
|
|
905
|
+
row.paused !== 1) {
|
|
906
|
+
return false;
|
|
907
|
+
}
|
|
908
|
+
const lease = this.requireLease(row.resourceId);
|
|
909
|
+
if (lease.ownerId !== null && lease.expiresAt !== null && lease.expiresAt > now)
|
|
910
|
+
return false;
|
|
911
|
+
const pending = this.state.connection
|
|
912
|
+
.prepare(`SELECT 1 FROM interactive_requests
|
|
913
|
+
WHERE run_id = ? AND status IN ('pending', 'presenting') LIMIT 1`)
|
|
914
|
+
.get(options.runId);
|
|
915
|
+
if (pending === undefined)
|
|
916
|
+
return false;
|
|
917
|
+
const changed = this.state.connection
|
|
918
|
+
.prepare(`UPDATE runs
|
|
919
|
+
SET paused = 0, status_detail = 'waiting for origin-session input', updated_at = ?
|
|
920
|
+
WHERE run_id = ? AND status = 'waiting' AND paused = 1`)
|
|
921
|
+
.run(now, options.runId);
|
|
922
|
+
if (changed.changes !== 1)
|
|
923
|
+
return false;
|
|
924
|
+
const revision = this.resourceRevision(row.resourceId);
|
|
925
|
+
this.bumpResource(row.resourceId, revision, now);
|
|
926
|
+
this.insertEvent(row.resourceId, revision + 1, "run.resumed", "control", null, { status: "waiting" }, now);
|
|
927
|
+
recordViewerDeltas(this.state, options.runId, [{ targetType: "summary" }, { targetType: "replay" }], now);
|
|
928
|
+
return true;
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
isWorkflowRunPaused(runId) {
|
|
932
|
+
return this.workflowRunRow(runId)?.paused === 1;
|
|
933
|
+
}
|
|
835
934
|
parkWorkflowRunForSourceChange(options) {
|
|
836
935
|
const now = epoch(validTimestamp(options.now));
|
|
837
936
|
return this.state.transaction(() => {
|
|
@@ -1109,6 +1208,13 @@ export class SqliteControllerStore {
|
|
|
1109
1208
|
.get(options.runId, options.targetSessionId);
|
|
1110
1209
|
return isTurnIntentRow(row) ? this.mapTurnIntent(row) : undefined;
|
|
1111
1210
|
}
|
|
1211
|
+
isWorkflowTurnIntentClaimLive(options) {
|
|
1212
|
+
const row = this.turnIntentRow(options.intentId);
|
|
1213
|
+
return (row !== undefined &&
|
|
1214
|
+
row.targetSessionId === options.targetSessionId &&
|
|
1215
|
+
row.resolvedAt === null &&
|
|
1216
|
+
this.verifyEffectToken(row.effectId, options.claimToken, options.now));
|
|
1217
|
+
}
|
|
1112
1218
|
claimWorkflowTurnIntent(options) {
|
|
1113
1219
|
const intent = this.turnIntent(options.intentId);
|
|
1114
1220
|
if (intent?.targetSessionId !== options.targetSessionId)
|
|
@@ -1131,6 +1237,21 @@ export class SqliteControllerStore {
|
|
|
1131
1237
|
}
|
|
1132
1238
|
return claimed;
|
|
1133
1239
|
}
|
|
1240
|
+
hasClaimableWorkflowTurnIntent(options) {
|
|
1241
|
+
const now = epoch(validTimestamp(options.now));
|
|
1242
|
+
const row = this.state.connection
|
|
1243
|
+
.prepare(`SELECT 1 AS present
|
|
1244
|
+
FROM turn_intents t
|
|
1245
|
+
JOIN effects e ON e.effect_id = t.effect_id
|
|
1246
|
+
JOIN leases l ON l.resource_id = e.resource_id
|
|
1247
|
+
WHERE t.target_session_id = ? AND t.resolved_at IS NULL
|
|
1248
|
+
AND t.eligible_at IS NOT NULL AND t.eligible_at <= ?
|
|
1249
|
+
AND e.status = 'pending'
|
|
1250
|
+
AND (l.owner_id IS NULL OR l.expires_at IS NULL OR l.expires_at <= ?)
|
|
1251
|
+
LIMIT 1`)
|
|
1252
|
+
.get(options.targetSessionId, now, now);
|
|
1253
|
+
return row !== undefined;
|
|
1254
|
+
}
|
|
1134
1255
|
makeWorkflowTurnIntentEligible(options) {
|
|
1135
1256
|
const eligibleAt = epoch(validTimestamp(options.eligibleAt ?? options.now));
|
|
1136
1257
|
const factsHash = this.state.putJson(options.fallbackFacts, eligibleAt);
|
|
@@ -1209,6 +1330,19 @@ export class SqliteControllerStore {
|
|
|
1209
1330
|
});
|
|
1210
1331
|
return this.requireNotification(options.runId, options.attemptId, options.notificationIndex);
|
|
1211
1332
|
}
|
|
1333
|
+
hasClaimableWorkflowNotification(options) {
|
|
1334
|
+
const now = epoch(validTimestamp(options.now));
|
|
1335
|
+
const row = this.state.connection
|
|
1336
|
+
.prepare(`SELECT 1 AS present
|
|
1337
|
+
FROM notifications n
|
|
1338
|
+
JOIN effects e ON e.effect_id = n.effect_id
|
|
1339
|
+
JOIN leases l ON l.resource_id = e.resource_id
|
|
1340
|
+
WHERE n.target_session_id = ? AND e.status = 'pending'
|
|
1341
|
+
AND (l.owner_id IS NULL OR l.expires_at IS NULL OR l.expires_at <= ?)
|
|
1342
|
+
LIMIT 1`)
|
|
1343
|
+
.get(options.targetSessionId, now);
|
|
1344
|
+
return row !== undefined;
|
|
1345
|
+
}
|
|
1212
1346
|
listPendingWorkflowNotifications(options) {
|
|
1213
1347
|
const now = Date.now();
|
|
1214
1348
|
return this.notificationRows(options.targetSessionId, options.limit ?? 20)
|
|
@@ -1226,6 +1360,12 @@ export class SqliteControllerStore {
|
|
|
1226
1360
|
}
|
|
1227
1361
|
return result;
|
|
1228
1362
|
}
|
|
1363
|
+
isWorkflowNotificationClaimLive(options) {
|
|
1364
|
+
const row = this.notificationRowById(options.notificationId);
|
|
1365
|
+
return (row !== undefined &&
|
|
1366
|
+
row.targetSessionId === options.targetSessionId &&
|
|
1367
|
+
this.verifyEffectToken(row.effectId, options.claimToken, options.now));
|
|
1368
|
+
}
|
|
1229
1369
|
markWorkflowNotificationDelivered(options) {
|
|
1230
1370
|
const now = epoch(validTimestamp(options.now));
|
|
1231
1371
|
return this.state.transaction(() => {
|
|
@@ -1597,6 +1737,12 @@ export class SqliteControllerStore {
|
|
|
1597
1737
|
throw new Error(`Workflow run preparation conflicts: ${options.runId}`);
|
|
1598
1738
|
}
|
|
1599
1739
|
}
|
|
1740
|
+
workflowRunViewRows(clause, params) {
|
|
1741
|
+
return this.state.connection
|
|
1742
|
+
.prepare(workflowRunViewSelect(clause))
|
|
1743
|
+
.all(...params)
|
|
1744
|
+
.filter(isWorkflowRunViewRow);
|
|
1745
|
+
}
|
|
1600
1746
|
/* istanbul ignore next -- pure projection covered by integration tests */
|
|
1601
1747
|
mapWorkflowRun(row) {
|
|
1602
1748
|
return {
|
|
@@ -2024,7 +2170,7 @@ function workflowSelect(clause) {
|
|
|
2024
2170
|
function workflowRunSelect(clause) {
|
|
2025
2171
|
return `SELECT r.run_id AS runId, r.resource_id AS resourceId,
|
|
2026
2172
|
d.workflow_name AS workflowName, r.workflow_ref AS workflowRef, r.status AS runStatus,
|
|
2027
|
-
r.definition_digest AS definitionDigest, d.definition_hash AS definitionHash,
|
|
2173
|
+
r.paused, r.definition_digest AS definitionDigest, d.definition_hash AS definitionHash,
|
|
2028
2174
|
r.input_hash AS inputHash,
|
|
2029
2175
|
r.launch_options_hash AS launchOptionsHash,
|
|
2030
2176
|
q.status, q.available_at AS availableAt, q.affinity_runner_id AS affinityRunnerId,
|
|
@@ -2039,6 +2185,23 @@ function workflowRunSelect(clause) {
|
|
|
2039
2185
|
LEFT JOIN run_bindings b ON b.run_id = r.run_id
|
|
2040
2186
|
JOIN leases l ON l.resource_id = r.resource_id ${clause}`;
|
|
2041
2187
|
}
|
|
2188
|
+
function workflowRunViewSelect(clause) {
|
|
2189
|
+
return `SELECT r.run_id AS runId, d.workflow_name AS workflowName,
|
|
2190
|
+
r.workflow_ref AS workflowRef, r.status AS runStatus, r.paused,
|
|
2191
|
+
r.definition_digest AS definitionDigest, q.status,
|
|
2192
|
+
b.origin_session_id AS originSessionId, b.execution_mode AS executionMode,
|
|
2193
|
+
r.parent_run_id AS parentRunId, q.error_code AS errorCode,
|
|
2194
|
+
CAST(error.content AS TEXT) AS errorMessage,
|
|
2195
|
+
q.created_at AS createdAt, q.updated_at AS updatedAt,
|
|
2196
|
+
q.started_at AS startedAt, q.finished_at AS finishedAt,
|
|
2197
|
+
source.source_type AS sourceType, source.source_ref AS sourceRef,
|
|
2198
|
+
source.source_revision AS sourceRevision
|
|
2199
|
+
FROM runs r JOIN workflow_definitions d ON d.definition_digest = r.definition_digest
|
|
2200
|
+
JOIN run_queue q ON q.run_id = r.run_id
|
|
2201
|
+
LEFT JOIN run_bindings b ON b.run_id = r.run_id
|
|
2202
|
+
LEFT JOIN run_sources source ON source.run_id = r.run_id AND source.mount_path = ''
|
|
2203
|
+
LEFT JOIN blobs error ON error.blob_hash = q.error_hash ${clause}`;
|
|
2204
|
+
}
|
|
2042
2205
|
function turnIntentSelect(clause) {
|
|
2043
2206
|
return `SELECT t.turn_intent_id AS intentId, t.source_event_id AS sourceEventId,
|
|
2044
2207
|
t.run_id AS runId, t.workflow_ref AS workflowRef,
|
|
@@ -2071,6 +2234,59 @@ function isControllerRow(value) {
|
|
|
2071
2234
|
function isLeaseRow(value) {
|
|
2072
2235
|
return isRecord(value);
|
|
2073
2236
|
}
|
|
2237
|
+
function workflowRunViewRecord(row) {
|
|
2238
|
+
const root = row.sourceType === "builtin"
|
|
2239
|
+
? { kind: "builtin", id: row.sourceRef, revision: row.sourceRevision }
|
|
2240
|
+
: { kind: "file", path: row.sourceRef, hash: row.sourceRevision };
|
|
2241
|
+
return {
|
|
2242
|
+
runId: row.runId,
|
|
2243
|
+
workflowName: row.workflowName,
|
|
2244
|
+
workflowSourceRef: row.workflowRef,
|
|
2245
|
+
workflowSource: { root, mounted: [] },
|
|
2246
|
+
initialized: row.runStatus !== "queued",
|
|
2247
|
+
definitionDigest: `sha256:${row.definitionDigest.toString("hex")}`,
|
|
2248
|
+
runStateStatus: row.runStatus,
|
|
2249
|
+
paused: row.paused === 1,
|
|
2250
|
+
status: row.status,
|
|
2251
|
+
originSessionId: row.executionMode === "headless" ? null : row.originSessionId,
|
|
2252
|
+
executionMode: row.executionMode,
|
|
2253
|
+
parentRunId: row.parentRunId,
|
|
2254
|
+
errorCode: row.errorCode,
|
|
2255
|
+
errorMessage: row.errorMessage,
|
|
2256
|
+
createdAt: new Date(row.createdAt).toISOString(),
|
|
2257
|
+
updatedAt: new Date(row.updatedAt).toISOString(),
|
|
2258
|
+
startedAt: row.startedAt === null ? null : new Date(row.startedAt).toISOString(),
|
|
2259
|
+
finishedAt: row.finishedAt === null ? null : new Date(row.finishedAt).toISOString(),
|
|
2260
|
+
};
|
|
2261
|
+
}
|
|
2262
|
+
function isWorkflowRunViewRow(value) {
|
|
2263
|
+
return (isRecord(value) &&
|
|
2264
|
+
typeof value.runId === "string" &&
|
|
2265
|
+
typeof value.workflowName === "string" &&
|
|
2266
|
+
typeof value.workflowRef === "string" &&
|
|
2267
|
+
typeof value.runStatus === "string" &&
|
|
2268
|
+
typeof value.paused === "number" &&
|
|
2269
|
+
Buffer.isBuffer(value.definitionDigest) &&
|
|
2270
|
+
typeof value.status === "string" &&
|
|
2271
|
+
(value.originSessionId === null || typeof value.originSessionId === "string") &&
|
|
2272
|
+
(value.executionMode === "interactive" || value.executionMode === "headless") &&
|
|
2273
|
+
(value.parentRunId === null || typeof value.parentRunId === "string") &&
|
|
2274
|
+
(value.errorCode === null || typeof value.errorCode === "string") &&
|
|
2275
|
+
(value.errorMessage === null || typeof value.errorMessage === "string") &&
|
|
2276
|
+
typeof value.createdAt === "number" &&
|
|
2277
|
+
typeof value.updatedAt === "number" &&
|
|
2278
|
+
(value.startedAt === null || typeof value.startedAt === "number") &&
|
|
2279
|
+
(value.finishedAt === null || typeof value.finishedAt === "number") &&
|
|
2280
|
+
(value.sourceType === "builtin" || value.sourceType === "file") &&
|
|
2281
|
+
typeof value.sourceRef === "string" &&
|
|
2282
|
+
typeof value.sourceRevision === "string");
|
|
2283
|
+
}
|
|
2284
|
+
function isRunListRevisionRow(value) {
|
|
2285
|
+
return (isRecord(value) &&
|
|
2286
|
+
typeof value.count === "number" &&
|
|
2287
|
+
typeof value.revisionSum === "number" &&
|
|
2288
|
+
typeof value.updatedAt === "number");
|
|
2289
|
+
}
|
|
2074
2290
|
function isRunRow(value) {
|
|
2075
2291
|
return isRecord(value);
|
|
2076
2292
|
}
|