@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/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
|
|
|
@@ -146,6 +143,17 @@ export type WorkflowRunDisplayState = {
|
|
|
146
143
|
error: string | null;
|
|
147
144
|
};
|
|
148
145
|
|
|
146
|
+
export type WorkflowRunTerminalData = {
|
|
147
|
+
runId: string;
|
|
148
|
+
status: "completed" | "failed" | "timed_out" | "cancelled";
|
|
149
|
+
statusDetail: string | null;
|
|
150
|
+
input: JsonValue;
|
|
151
|
+
finalOutput: JsonValue | null;
|
|
152
|
+
error: string | null;
|
|
153
|
+
presentationInstructions: string;
|
|
154
|
+
restartNumber: number;
|
|
155
|
+
};
|
|
156
|
+
|
|
149
157
|
export type WorkflowRunStoreOptions = {
|
|
150
158
|
authorityProvider?: (runId: string) => RunWriteAuthority | undefined;
|
|
151
159
|
/** Host-owned projection updates that must commit with each run snapshot. */
|
|
@@ -156,6 +164,8 @@ export type WorkflowRunStoreOptions = {
|
|
|
156
164
|
database: StateDatabase;
|
|
157
165
|
now: number;
|
|
158
166
|
}) => void;
|
|
167
|
+
/** The global host may record an attested Pi session after the worker lease ends. */
|
|
168
|
+
allowHostSessionRecording?: boolean;
|
|
159
169
|
state?: StateDatabase;
|
|
160
170
|
readOnly?: boolean;
|
|
161
171
|
};
|
|
@@ -352,21 +362,9 @@ type SettingsChangeRow = {
|
|
|
352
362
|
acceptedAt: number;
|
|
353
363
|
};
|
|
354
364
|
|
|
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
365
|
type FollowUpRow = {
|
|
367
366
|
followUpId: string;
|
|
368
367
|
resourceId: string;
|
|
369
|
-
queueResourceId: string;
|
|
370
368
|
runId: string;
|
|
371
369
|
requestId: string;
|
|
372
370
|
orderNumber: number;
|
|
@@ -376,11 +374,9 @@ type FollowUpRow = {
|
|
|
376
374
|
sourceType: string;
|
|
377
375
|
promptHash: Buffer;
|
|
378
376
|
status: string;
|
|
379
|
-
sessionEntryId: string | null;
|
|
380
377
|
reasonHash: Buffer | null;
|
|
381
378
|
createdAt: number;
|
|
382
379
|
updatedAt: number;
|
|
383
|
-
sentAt: number | null;
|
|
384
380
|
revision: number;
|
|
385
381
|
};
|
|
386
382
|
|
|
@@ -503,13 +499,16 @@ export class WorkflowRunStore {
|
|
|
503
499
|
| ((runId: string) => RunWriteAuthority | undefined)
|
|
504
500
|
| undefined;
|
|
505
501
|
private readonly snapshotLifecycle: WorkflowRunStoreOptions["snapshotLifecycle"];
|
|
502
|
+
private readonly allowHostSessionRecording: boolean;
|
|
506
503
|
private readonly contexts = new Map<string, RunContext>();
|
|
507
504
|
private readonly ownsState: boolean;
|
|
508
505
|
private readonly mutations: StateMutationStore;
|
|
506
|
+
private readonly workflowMessages: WorkflowMessageStore;
|
|
509
507
|
|
|
510
508
|
constructor(databasePath: string = workflowStatePath(), options: WorkflowRunStoreOptions = {}) {
|
|
511
509
|
this.authorityProvider = options.authorityProvider;
|
|
512
510
|
this.snapshotLifecycle = options.snapshotLifecycle;
|
|
511
|
+
this.allowHostSessionRecording = options.allowHostSessionRecording === true;
|
|
513
512
|
this.ownsState = options.state === undefined;
|
|
514
513
|
this.state =
|
|
515
514
|
options.state ??
|
|
@@ -520,6 +519,7 @@ export class WorkflowRunStore {
|
|
|
520
519
|
});
|
|
521
520
|
this.databasePath = this.state.filePath;
|
|
522
521
|
this.mutations = new StateMutationStore(this.state);
|
|
522
|
+
this.workflowMessages = new WorkflowMessageStore(this.state);
|
|
523
523
|
}
|
|
524
524
|
|
|
525
525
|
close(): void {
|
|
@@ -912,6 +912,18 @@ export class WorkflowRunStore {
|
|
|
912
912
|
return;
|
|
913
913
|
}
|
|
914
914
|
const definitionHash = this.state.putJson(snapshot, now);
|
|
915
|
+
const parentLineage =
|
|
916
|
+
state.parentRunId === undefined
|
|
917
|
+
? undefined
|
|
918
|
+
: this.state.connection
|
|
919
|
+
.prepare(
|
|
920
|
+
`SELECT root_run_id AS rootRunId, restart_number AS restartNumber
|
|
921
|
+
FROM runs WHERE run_id = ?`,
|
|
922
|
+
)
|
|
923
|
+
.get(state.parentRunId);
|
|
924
|
+
if (state.parentRunId !== undefined && !isRunLineageRow(parentLineage)) {
|
|
925
|
+
throw new Error(`Workflow parent run is missing: ${state.parentRunId}`);
|
|
926
|
+
}
|
|
915
927
|
this.state.connection
|
|
916
928
|
.prepare(
|
|
917
929
|
`INSERT INTO workflow_definitions(
|
|
@@ -940,16 +952,20 @@ export class WorkflowRunStore {
|
|
|
940
952
|
this.state.connection
|
|
941
953
|
.prepare(
|
|
942
954
|
`INSERT INTO runs(
|
|
943
|
-
run_id, resource_id, project_id, parent_run_id,
|
|
955
|
+
run_id, resource_id, project_id, parent_run_id, root_run_id, lineage_kind,
|
|
956
|
+
restart_number, parent_terminal_fingerprint, definition_digest,
|
|
944
957
|
workflow_ref, launch_options_hash, title, status, paused,
|
|
945
958
|
status_detail, input_hash, final_output_hash, error_hash,
|
|
946
959
|
created_at, updated_at, finished_at
|
|
947
|
-
) VALUES (?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
960
|
+
) VALUES (?, ?, NULL, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
948
961
|
)
|
|
949
962
|
.run(
|
|
950
963
|
state.runId,
|
|
951
964
|
resourceId,
|
|
952
965
|
state.parentRunId ?? null,
|
|
966
|
+
isRunLineageRow(parentLineage) ? parentLineage.rootRunId : state.runId,
|
|
967
|
+
state.parentRunId === undefined ? null : "continuation",
|
|
968
|
+
isRunLineageRow(parentLineage) ? parentLineage.restartNumber : 0,
|
|
953
969
|
definitionDigest,
|
|
954
970
|
state.workflowName,
|
|
955
971
|
launchOptionsHash,
|
|
@@ -1287,87 +1303,78 @@ export class WorkflowRunStore {
|
|
|
1287
1303
|
if (Buffer.byteLength(request.prompt, "utf8") > 64 * 1024) {
|
|
1288
1304
|
throw new Error("Workflow follow-up prompt cannot exceed 65536 bytes");
|
|
1289
1305
|
}
|
|
1290
|
-
|
|
1306
|
+
return this.state.transaction(() => {
|
|
1291
1307
|
const prior = this.followUpRowByRequest(request.runId, request.requestId);
|
|
1292
1308
|
if (prior !== undefined) {
|
|
1293
1309
|
this.assertSameFollowUp(prior, request);
|
|
1294
1310
|
return { followUp: this.followUpRecord(prior), adopted: true };
|
|
1295
1311
|
}
|
|
1296
1312
|
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;
|
|
1313
|
+
if (this.originSessionId(request.runId) !== request.targetSessionId) {
|
|
1314
|
+
throw new Error("Workflow follow-ups must target the run's origin Pi session");
|
|
1368
1315
|
}
|
|
1369
|
-
|
|
1370
|
-
|
|
1316
|
+
const now = Date.now();
|
|
1317
|
+
const order = this.nextFollowUpOrder(request.runId);
|
|
1318
|
+
const followUpId = followUpIdFor(request.runId, request.requestId);
|
|
1319
|
+
const resourceId = this.mutations.ensureResource("follow_up", followUpId, now);
|
|
1320
|
+
const promptHash = this.state.putText(request.prompt, now);
|
|
1321
|
+
this.state.connection
|
|
1322
|
+
.prepare(
|
|
1323
|
+
`INSERT INTO workflow_follow_ups(
|
|
1324
|
+
follow_up_id, resource_id, run_id, request_id, order_number,
|
|
1325
|
+
target_session_id, actor_type, actor_id, source_type, prompt_hash,
|
|
1326
|
+
status, created_at, updated_at
|
|
1327
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'queued', ?, ?)`,
|
|
1328
|
+
)
|
|
1329
|
+
.run(
|
|
1330
|
+
followUpId,
|
|
1331
|
+
resourceId,
|
|
1332
|
+
request.runId,
|
|
1333
|
+
request.requestId,
|
|
1334
|
+
order,
|
|
1335
|
+
request.targetSessionId,
|
|
1336
|
+
request.actor.type,
|
|
1337
|
+
request.actor.id ?? null,
|
|
1338
|
+
request.source,
|
|
1339
|
+
promptHash,
|
|
1340
|
+
now,
|
|
1341
|
+
now,
|
|
1342
|
+
);
|
|
1343
|
+
const workflowMessageId = workflowMessageIdFor("followUp", followUpId, "initial");
|
|
1344
|
+
this.workflowMessages.create({
|
|
1345
|
+
workflowMessageId,
|
|
1346
|
+
runId: request.runId,
|
|
1347
|
+
targetSessionId: request.targetSessionId,
|
|
1348
|
+
kind: "followUp",
|
|
1349
|
+
sourceId: followUpId,
|
|
1350
|
+
idempotencyKey: "initial",
|
|
1351
|
+
content: followUpWorkflowMessageContent({
|
|
1352
|
+
workflowMessageId,
|
|
1353
|
+
followUpId,
|
|
1354
|
+
runId: request.runId,
|
|
1355
|
+
prompt: request.prompt,
|
|
1356
|
+
}),
|
|
1357
|
+
now,
|
|
1358
|
+
});
|
|
1359
|
+
this.bumpResource(resourceId, 0, now);
|
|
1360
|
+
insertGenericEvent(
|
|
1361
|
+
this.state,
|
|
1362
|
+
resourceId,
|
|
1363
|
+
1,
|
|
1364
|
+
"follow-up.queued",
|
|
1365
|
+
request.actor.type,
|
|
1366
|
+
request.actor.id ?? null,
|
|
1367
|
+
this.state.putJson({ runId: request.runId, requestId: request.requestId }, now),
|
|
1368
|
+
now,
|
|
1369
|
+
);
|
|
1370
|
+
recordViewerDeltas(
|
|
1371
|
+
this.state,
|
|
1372
|
+
request.runId,
|
|
1373
|
+
this.viewerInspectorTargets(request.runId),
|
|
1374
|
+
now,
|
|
1375
|
+
);
|
|
1376
|
+
return { followUp: this.followUpRecord(this.requireFollowUpRow(followUpId)), adopted: false };
|
|
1377
|
+
});
|
|
1371
1378
|
}
|
|
1372
1379
|
|
|
1373
1380
|
removeFollowUp(request: WorkflowRemoveFollowUpRequest): WorkflowFollowUpRecord {
|
|
@@ -1377,12 +1384,13 @@ export class WorkflowRunStore {
|
|
|
1377
1384
|
throw new Error(`Workflow follow-up is not part of run ${request.runId}`);
|
|
1378
1385
|
}
|
|
1379
1386
|
if (row.status === "removed") return this.followUpRecord(row);
|
|
1380
|
-
if (row.status === "
|
|
1381
|
-
throw new Error(
|
|
1387
|
+
if (row.status === "cancelled") {
|
|
1388
|
+
throw new Error("A cancelled workflow follow-up cannot be removed");
|
|
1382
1389
|
}
|
|
1383
1390
|
this.assertFollowUpRemovalAuthority(row, request.actor, request.source);
|
|
1384
|
-
|
|
1385
|
-
|
|
1391
|
+
const message = this.workflowMessages.latestForSource("followUp", request.followUpId);
|
|
1392
|
+
if (message?.status === "sent") {
|
|
1393
|
+
throw new Error("A sent workflow follow-up cannot be removed");
|
|
1386
1394
|
}
|
|
1387
1395
|
const reason = "Removed before delivery";
|
|
1388
1396
|
this.mutations.mutate(
|
|
@@ -1399,12 +1407,13 @@ export class WorkflowRunStore {
|
|
|
1399
1407
|
.prepare(
|
|
1400
1408
|
`UPDATE workflow_follow_ups
|
|
1401
1409
|
SET status = 'removed', reason_hash = ?, updated_at = ?
|
|
1402
|
-
WHERE follow_up_id = ? AND status
|
|
1410
|
+
WHERE follow_up_id = ? AND status = 'queued'`,
|
|
1403
1411
|
)
|
|
1404
1412
|
.run(reasonHash, now, request.followUpId);
|
|
1405
1413
|
if (update.changes !== 1) {
|
|
1406
1414
|
throw new StaleResourceError("Workflow follow-up changed before removal");
|
|
1407
1415
|
}
|
|
1416
|
+
this.workflowMessages.cancelPendingForSource(request.followUpId, "followUp", now);
|
|
1408
1417
|
recordViewerDeltas(
|
|
1409
1418
|
this.state,
|
|
1410
1419
|
request.runId,
|
|
@@ -1420,10 +1429,8 @@ export class WorkflowRunStore {
|
|
|
1420
1429
|
originSessionId(runId: string): string | undefined {
|
|
1421
1430
|
const row = this.state.connection
|
|
1422
1431
|
.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
|
|
1432
|
+
`SELECT b.origin_session_id AS originSessionId
|
|
1433
|
+
FROM runs r LEFT JOIN run_bindings b ON b.run_id = r.run_id
|
|
1427
1434
|
WHERE r.run_id = ?`,
|
|
1428
1435
|
)
|
|
1429
1436
|
.get(runId);
|
|
@@ -1433,36 +1440,22 @@ export class WorkflowRunStore {
|
|
|
1433
1440
|
}
|
|
1434
1441
|
|
|
1435
1442
|
readFollowUpQueue(runId: string): WorkflowFollowUpQueueRecord | undefined {
|
|
1436
|
-
const
|
|
1437
|
-
if (
|
|
1443
|
+
const originSessionId = this.originSessionId(runId);
|
|
1444
|
+
if (originSessionId === undefined) return undefined;
|
|
1438
1445
|
const followUps = this.state.connection
|
|
1439
1446
|
.prepare(`${FOLLOW_UP_ROW_SELECT} WHERE f.run_id = ? ORDER BY f.order_number`)
|
|
1440
1447
|
.all(runId)
|
|
1441
1448
|
.filter(isFollowUpRow)
|
|
1442
1449
|
.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
|
-
};
|
|
1450
|
+
return { runId, originSessionId, followUps };
|
|
1458
1451
|
}
|
|
1459
1452
|
|
|
1460
1453
|
private readFollowUpQueueRange(
|
|
1461
1454
|
runId: string,
|
|
1462
1455
|
range: WorkflowRunViewRange,
|
|
1463
1456
|
): WorkflowFollowUpQueueRecord | null {
|
|
1464
|
-
const
|
|
1465
|
-
if (
|
|
1457
|
+
const originSessionId = this.originSessionId(runId);
|
|
1458
|
+
if (originSessionId === undefined) return null;
|
|
1466
1459
|
const followUps = this.state.connection
|
|
1467
1460
|
.prepare(
|
|
1468
1461
|
`${FOLLOW_UP_ROW_SELECT} WHERE f.run_id = ? ORDER BY f.order_number LIMIT ? OFFSET ?`,
|
|
@@ -1470,195 +1463,7 @@ export class WorkflowRunStore {
|
|
|
1470
1463
|
.all(runId, range.limit, range.start)
|
|
1471
1464
|
.filter(isFollowUpRow)
|
|
1472
1465
|
.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);
|
|
1466
|
+
return { runId, originSessionId, followUps };
|
|
1662
1467
|
}
|
|
1663
1468
|
|
|
1664
1469
|
async prepareRunResume(runId: string): Promise<LoadedWorkflowRun> {
|
|
@@ -1670,7 +1475,6 @@ export class WorkflowRunStore {
|
|
|
1670
1475
|
this.verifySettingsProjections(runId);
|
|
1671
1476
|
const revision = this.resourceRevision(runId);
|
|
1672
1477
|
this.contexts.set(runId, { revision, lock: Promise.resolve() });
|
|
1673
|
-
this.finalizeRecordingCaptures(runId, "Workflow host stopped before the run finished");
|
|
1674
1478
|
this.state.transaction(() => {
|
|
1675
1479
|
const row = this.requireRunRow(runId);
|
|
1676
1480
|
const context = this.contextFor(runId);
|
|
@@ -1900,7 +1704,7 @@ export class WorkflowRunStore {
|
|
|
1900
1704
|
this.state.transaction(() => {
|
|
1901
1705
|
const run = this.requireRunRow(runId);
|
|
1902
1706
|
const context = this.contextFor(runId);
|
|
1903
|
-
this.
|
|
1707
|
+
this.assertSessionWriteAuthority(runId);
|
|
1904
1708
|
const now = Date.parse(binding.boundAt);
|
|
1905
1709
|
const resourceId = resourceIdFor("session", segmentId);
|
|
1906
1710
|
const bindingHash = this.state.putJson(binding, now);
|
|
@@ -1947,13 +1751,6 @@ export class WorkflowRunStore {
|
|
|
1947
1751
|
ON CONFLICT(run_id) DO NOTHING`,
|
|
1948
1752
|
)
|
|
1949
1753
|
.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
1754
|
const revision = context.revision + 1;
|
|
1958
1755
|
const at = new Date(now).toISOString();
|
|
1959
1756
|
const traceEvent: WorkflowTraceEvent = {
|
|
@@ -2396,6 +2193,59 @@ export class WorkflowRunStore {
|
|
|
2396
2193
|
};
|
|
2397
2194
|
}
|
|
2398
2195
|
|
|
2196
|
+
readRunInput(runId: string): JsonValue | null {
|
|
2197
|
+
const row = this.readRunRow(runId);
|
|
2198
|
+
return row === undefined ? null : this.state.readJson(row.inputHash);
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2201
|
+
readRunFinalOutput(runId: string): JsonValue | null {
|
|
2202
|
+
const row = this.readRunRow(runId);
|
|
2203
|
+
return row?.finalOutputHash == null ? null : this.state.readJson(row.finalOutputHash);
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
readRunError(runId: string): string | null {
|
|
2207
|
+
const row = this.readRunRow(runId);
|
|
2208
|
+
return row?.errorHash == null ? null : this.readText(row.errorHash);
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2211
|
+
readPresentationInstructions(runId: string): string {
|
|
2212
|
+
const row = this.state.connection
|
|
2213
|
+
.prepare("SELECT presentation_prompt_hash AS hash FROM runs WHERE run_id = ?")
|
|
2214
|
+
.get(runId) as { hash?: Buffer | null } | undefined;
|
|
2215
|
+
if (row?.hash == null) {
|
|
2216
|
+
return "Explain the final workflow result to the user in a normal response.";
|
|
2217
|
+
}
|
|
2218
|
+
return this.readText(row.hash);
|
|
2219
|
+
}
|
|
2220
|
+
|
|
2221
|
+
readTerminalData(runId: string): WorkflowRunTerminalData | null {
|
|
2222
|
+
const row = this.state.connection
|
|
2223
|
+
.prepare(
|
|
2224
|
+
`SELECT status, status_detail AS statusDetail, restart_number AS restartNumber
|
|
2225
|
+
FROM runs WHERE run_id = ?`,
|
|
2226
|
+
)
|
|
2227
|
+
.get(runId) as
|
|
2228
|
+
| { status?: unknown; statusDetail?: unknown; restartNumber?: unknown }
|
|
2229
|
+
| undefined;
|
|
2230
|
+
if (
|
|
2231
|
+
row === undefined ||
|
|
2232
|
+
!["completed", "failed", "timed_out", "cancelled"].includes(String(row.status)) ||
|
|
2233
|
+
typeof row.restartNumber !== "number"
|
|
2234
|
+
) {
|
|
2235
|
+
return null;
|
|
2236
|
+
}
|
|
2237
|
+
return {
|
|
2238
|
+
runId,
|
|
2239
|
+
status: row.status as WorkflowRunTerminalData["status"],
|
|
2240
|
+
statusDetail: typeof row.statusDetail === "string" ? row.statusDetail : null,
|
|
2241
|
+
input: this.readRunInput(runId) as JsonValue,
|
|
2242
|
+
finalOutput: this.readRunFinalOutput(runId),
|
|
2243
|
+
error: this.readRunError(runId),
|
|
2244
|
+
presentationInstructions: this.readPresentationInstructions(runId),
|
|
2245
|
+
restartNumber: row.restartNumber,
|
|
2246
|
+
};
|
|
2247
|
+
}
|
|
2248
|
+
|
|
2399
2249
|
readRun(runId: string, options: ReadWorkflowRunOptions = {}): LoadedWorkflowRun | null {
|
|
2400
2250
|
const row = this.readRunRow(runId);
|
|
2401
2251
|
if (row === undefined) return null;
|
|
@@ -2679,6 +2529,7 @@ export class WorkflowRunStore {
|
|
|
2679
2529
|
|
|
2680
2530
|
private assertSessionWriteAuthority(runId: string): void {
|
|
2681
2531
|
const run = this.requireRunRow(runId);
|
|
2532
|
+
if (this.allowHostSessionRecording) return;
|
|
2682
2533
|
this.assertWriteAuthority(run, this.contextFor(runId).revision);
|
|
2683
2534
|
}
|
|
2684
2535
|
|
|
@@ -2902,10 +2753,8 @@ export class WorkflowRunStore {
|
|
|
2902
2753
|
order: row.orderNumber,
|
|
2903
2754
|
state: row.status,
|
|
2904
2755
|
source: row.sourceType,
|
|
2905
|
-
sessionEntryId: row.sessionEntryId,
|
|
2906
2756
|
}));
|
|
2907
2757
|
const followUpStart = Math.max(0, followUps.length - VIEWER_PAGE_SIZE);
|
|
2908
|
-
const queue = this.requireFollowUpQueueRow(runId);
|
|
2909
2758
|
return [
|
|
2910
2759
|
{
|
|
2911
2760
|
targetType: "inspector",
|
|
@@ -2927,12 +2776,7 @@ export class WorkflowRunStore {
|
|
|
2927
2776
|
{
|
|
2928
2777
|
op: "replace",
|
|
2929
2778
|
path: "/followUpQueue",
|
|
2930
|
-
value: parseJson(
|
|
2931
|
-
canonicalJson({
|
|
2932
|
-
presentationState: queue.presentationState,
|
|
2933
|
-
items: followUps.slice(followUpStart),
|
|
2934
|
-
}),
|
|
2935
|
-
),
|
|
2779
|
+
value: parseJson(canonicalJson({ items: followUps.slice(followUpStart) })),
|
|
2936
2780
|
},
|
|
2937
2781
|
{ op: "replace", path: "/followUpStart", value: followUpStart },
|
|
2938
2782
|
{ op: "replace", path: "/followUpTotal", value: followUps.length },
|
|
@@ -2946,25 +2790,7 @@ export class WorkflowRunStore {
|
|
|
2946
2790
|
initialSettings: InitialWorkflowSettingsScope[],
|
|
2947
2791
|
now: number,
|
|
2948
2792
|
): void {
|
|
2949
|
-
const childQueueResourceId = this.ensureFollowUpQueue(state.runId, now);
|
|
2950
2793
|
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
2794
|
this.state.connection
|
|
2969
2795
|
.prepare(
|
|
2970
2796
|
`UPDATE workflow_settings SET active_run_id = ?, updated_at = ? WHERE active_run_id = ?`,
|
|
@@ -3004,19 +2830,6 @@ export class WorkflowRunStore {
|
|
|
3004
2830
|
}
|
|
3005
2831
|
}
|
|
3006
2832
|
|
|
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
2833
|
private logicalOriginRunId(runId: string): string {
|
|
3021
2834
|
let current = runId;
|
|
3022
2835
|
for (let depth = 0; depth < 100; depth += 1) {
|
|
@@ -3174,30 +2987,6 @@ export class WorkflowRunStore {
|
|
|
3174
2987
|
}
|
|
3175
2988
|
}
|
|
3176
2989
|
|
|
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
2990
|
private followUpRowByRequest(runId: string, requestId: string): FollowUpRow | undefined {
|
|
3202
2991
|
const row = this.state.connection
|
|
3203
2992
|
.prepare(`${FOLLOW_UP_ROW_SELECT} WHERE f.run_id = ? AND f.request_id = ?`)
|
|
@@ -3232,11 +3021,9 @@ export class WorkflowRunStore {
|
|
|
3232
3021
|
source: row.sourceType,
|
|
3233
3022
|
prompt: this.readText(row.promptHash),
|
|
3234
3023
|
state: assertFollowUpState(row.status),
|
|
3235
|
-
...(row.sessionEntryId !== null ? { sessionEntryId: row.sessionEntryId } : {}),
|
|
3236
3024
|
...(row.reasonHash !== null ? { reason: this.readText(row.reasonHash) } : {}),
|
|
3237
3025
|
createdAt: new Date(row.createdAt).toISOString(),
|
|
3238
3026
|
updatedAt: new Date(row.updatedAt).toISOString(),
|
|
3239
|
-
...(row.sentAt !== null ? { sentAt: new Date(row.sentAt).toISOString() } : {}),
|
|
3240
3027
|
};
|
|
3241
3028
|
}
|
|
3242
3029
|
|
|
@@ -3270,13 +3057,6 @@ export class WorkflowRunStore {
|
|
|
3270
3057
|
throw new Error("Workflow follow-up can be removed only by its source or a verified human");
|
|
3271
3058
|
}
|
|
3272
3059
|
|
|
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
3060
|
private nextFollowUpOrder(runId: string): number {
|
|
3281
3061
|
const row = this.state.connection
|
|
3282
3062
|
.prepare(
|
|
@@ -3309,55 +3089,51 @@ export class WorkflowRunStore {
|
|
|
3309
3089
|
|
|
3310
3090
|
private transitionFollowUpsForRunState(
|
|
3311
3091
|
state: WorkflowRunState,
|
|
3312
|
-
|
|
3092
|
+
_event: WorkflowTraceEventDraft,
|
|
3313
3093
|
now: number,
|
|
3314
3094
|
): 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
|
-
);
|
|
3095
|
+
if (state.status === "running" || state.status === "waiting" || state.status === "completed") {
|
|
3096
|
+
return;
|
|
3330
3097
|
}
|
|
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
|
|
3098
|
+
const rows = this.state.connection
|
|
3339
3099
|
.prepare(
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3100
|
+
`${FOLLOW_UP_ROW_SELECT}
|
|
3101
|
+
WHERE f.run_id IN (
|
|
3102
|
+
WITH RECURSIVE ancestors(run_id, parent_run_id) AS (
|
|
3103
|
+
SELECT run_id, parent_run_id FROM runs WHERE run_id = ?
|
|
3104
|
+
UNION ALL
|
|
3105
|
+
SELECT parent.run_id, parent.parent_run_id
|
|
3106
|
+
FROM runs parent JOIN ancestors ON ancestors.parent_run_id = parent.run_id
|
|
3107
|
+
)
|
|
3108
|
+
SELECT run_id FROM ancestors
|
|
3109
|
+
) AND f.status = 'queued'`,
|
|
3343
3110
|
)
|
|
3344
|
-
.
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3111
|
+
.all(state.runId)
|
|
3112
|
+
.filter(isFollowUpRow);
|
|
3113
|
+
for (const row of rows) {
|
|
3114
|
+
const reason = state.error ?? `Workflow ended with status ${state.status}`;
|
|
3115
|
+
const reasonHash = this.state.putText(reason, now);
|
|
3116
|
+
const update = this.state.connection
|
|
3117
|
+
.prepare(
|
|
3118
|
+
`UPDATE workflow_follow_ups
|
|
3119
|
+
SET status = 'cancelled', reason_hash = ?, updated_at = ?
|
|
3120
|
+
WHERE follow_up_id = ? AND status = 'queued'`,
|
|
3121
|
+
)
|
|
3122
|
+
.run(reasonHash, now, row.followUpId);
|
|
3123
|
+
if (update.changes !== 1) continue;
|
|
3124
|
+
this.workflowMessages.cancelPendingForSource(row.followUpId, "followUp", now);
|
|
3125
|
+
this.bumpResource(row.resourceId, row.revision, now);
|
|
3126
|
+
insertGenericEvent(
|
|
3127
|
+
this.state,
|
|
3128
|
+
row.resourceId,
|
|
3129
|
+
row.revision + 1,
|
|
3130
|
+
"follow-up.cancelled",
|
|
3131
|
+
"system",
|
|
3132
|
+
null,
|
|
3133
|
+
this.state.putJson({ runId: state.runId, status: state.status, reason }, now),
|
|
3134
|
+
now,
|
|
3135
|
+
);
|
|
3136
|
+
}
|
|
3361
3137
|
}
|
|
3362
3138
|
|
|
3363
3139
|
private syncNodeAttempts(
|
|
@@ -3554,7 +3330,7 @@ export class WorkflowRunStore {
|
|
|
3554
3330
|
JOIN session_segments s ON s.segment_id = e.segment_id
|
|
3555
3331
|
JOIN blobs b ON b.blob_hash = e.entry_hash
|
|
3556
3332
|
WHERE s.run_id = ?
|
|
3557
|
-
AND json_extract(CAST(b.content AS TEXT), '$.customType') = 'pi-workflows-
|
|
3333
|
+
AND json_extract(CAST(b.content AS TEXT), '$.customType') = 'pi-workflows-step'
|
|
3558
3334
|
AND json_extract(CAST(b.content AS TEXT), '$.details.contract.attemptId') = ?
|
|
3559
3335
|
ORDER BY e.recorded_at DESC LIMIT 1`,
|
|
3560
3336
|
)
|
|
@@ -3766,7 +3542,15 @@ export class WorkflowRunStore {
|
|
|
3766
3542
|
): WorkflowRunState {
|
|
3767
3543
|
const sources = this.readRunSources(row.runId, snapshot);
|
|
3768
3544
|
const carriedStepCount = this.carriedStepCount(row.runId);
|
|
3769
|
-
const activeAttempt =
|
|
3545
|
+
const activeAttempt =
|
|
3546
|
+
row.status === "running" || row.status === "waiting"
|
|
3547
|
+
? this.readActiveAttempt(row.runId)
|
|
3548
|
+
: undefined;
|
|
3549
|
+
const runningAttempt = row.status === "running" ? activeAttempt : undefined;
|
|
3550
|
+
const waitingNode =
|
|
3551
|
+
row.status === "waiting"
|
|
3552
|
+
? (activeAttempt?.nodeId ?? projectionSteps.at(-1)?.nodeId)
|
|
3553
|
+
: undefined;
|
|
3770
3554
|
const humanDecision = this.readHumanDecisionReceipt(row.runId);
|
|
3771
3555
|
const outputs: Record<string, unknown> = {};
|
|
3772
3556
|
const results: Record<string, WorkflowNodeResult> = {};
|
|
@@ -3808,7 +3592,7 @@ export class WorkflowRunStore {
|
|
|
3808
3592
|
results,
|
|
3809
3593
|
steps: visibleSteps,
|
|
3810
3594
|
...(visibleUpdates.length === 0 ? {} : { updates: visibleUpdates }),
|
|
3811
|
-
...(
|
|
3595
|
+
...(runningAttempt === undefined ? {} : { currentNode: runningAttempt.nodeId }),
|
|
3812
3596
|
...(activeAttempt === undefined ? {} : { currentAttemptId: activeAttempt.attemptId }),
|
|
3813
3597
|
...(activeAttempt?.startedAt === null || activeAttempt === undefined
|
|
3814
3598
|
? {}
|
|
@@ -3829,9 +3613,7 @@ export class WorkflowRunStore {
|
|
|
3829
3613
|
...(row.statusDetail === null ? {} : { statusDetail: row.statusDetail }),
|
|
3830
3614
|
...(humanDecision === undefined ? {} : { humanDecision }),
|
|
3831
3615
|
...(row.paused === 0 ? {} : { paused: true }),
|
|
3832
|
-
...(
|
|
3833
|
-
? { waitingOn: projectionSteps.at(-1)?.nodeId as string }
|
|
3834
|
-
: {}),
|
|
3616
|
+
...(waitingNode === undefined ? {} : { waitingOn: waitingNode }),
|
|
3835
3617
|
...(row.finalOutputHash === null
|
|
3836
3618
|
? {}
|
|
3837
3619
|
: { finalOutput: this.state.readJson(row.finalOutputHash) }),
|
|
@@ -4876,32 +4658,12 @@ function assertActorType(value: string): ActorType {
|
|
|
4876
4658
|
}
|
|
4877
4659
|
|
|
4878
4660
|
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
|
-
) {
|
|
4661
|
+
if (value !== "queued" && value !== "removed" && value !== "cancelled") {
|
|
4887
4662
|
throw new Error(`Unknown workflow follow-up state: ${value}`);
|
|
4888
4663
|
}
|
|
4889
4664
|
return value;
|
|
4890
4665
|
}
|
|
4891
4666
|
|
|
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
4667
|
function isSettingsScopeRow(value: unknown): value is SettingsScopeRow {
|
|
4906
4668
|
return (
|
|
4907
4669
|
isRecord(value) &&
|
|
@@ -4955,27 +4717,11 @@ function isSettingsChangeRow(value: unknown): value is SettingsChangeRow {
|
|
|
4955
4717
|
);
|
|
4956
4718
|
}
|
|
4957
4719
|
|
|
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
4720
|
function isFollowUpRow(value: unknown): value is FollowUpRow {
|
|
4974
4721
|
return (
|
|
4975
4722
|
isRecord(value) &&
|
|
4976
4723
|
typeof value.followUpId === "string" &&
|
|
4977
4724
|
typeof value.resourceId === "string" &&
|
|
4978
|
-
typeof value.queueResourceId === "string" &&
|
|
4979
4725
|
typeof value.runId === "string" &&
|
|
4980
4726
|
typeof value.requestId === "string" &&
|
|
4981
4727
|
typeof value.orderNumber === "number" &&
|
|
@@ -4985,11 +4731,9 @@ function isFollowUpRow(value: unknown): value is FollowUpRow {
|
|
|
4985
4731
|
typeof value.sourceType === "string" &&
|
|
4986
4732
|
Buffer.isBuffer(value.promptHash) &&
|
|
4987
4733
|
typeof value.status === "string" &&
|
|
4988
|
-
(typeof value.sessionEntryId === "string" || value.sessionEntryId === null) &&
|
|
4989
4734
|
(Buffer.isBuffer(value.reasonHash) || value.reasonHash === null) &&
|
|
4990
4735
|
typeof value.createdAt === "number" &&
|
|
4991
4736
|
typeof value.updatedAt === "number" &&
|
|
4992
|
-
(typeof value.sentAt === "number" || value.sentAt === null) &&
|
|
4993
4737
|
typeof value.revision === "number"
|
|
4994
4738
|
);
|
|
4995
4739
|
}
|
|
@@ -4998,6 +4742,14 @@ function isParentRunRow(value: unknown): value is { parentRunId: string | null }
|
|
|
4998
4742
|
return isRecord(value) && (typeof value.parentRunId === "string" || value.parentRunId === null);
|
|
4999
4743
|
}
|
|
5000
4744
|
|
|
4745
|
+
function isRunLineageRow(value: unknown): value is { rootRunId: string; restartNumber: number } {
|
|
4746
|
+
return (
|
|
4747
|
+
isRecord(value) &&
|
|
4748
|
+
typeof value.rootRunId === "string" &&
|
|
4749
|
+
typeof value.restartNumber === "number"
|
|
4750
|
+
);
|
|
4751
|
+
}
|
|
4752
|
+
|
|
5001
4753
|
function isStatusRow(value: unknown): value is { status: string } {
|
|
5002
4754
|
return isRecord(value) && typeof value.status === "string";
|
|
5003
4755
|
}
|
|
@@ -5010,10 +4762,6 @@ function isStepIndexRow(value: unknown): value is { stepIndex: number } {
|
|
|
5010
4762
|
return isRecord(value) && typeof value.stepIndex === "number";
|
|
5011
4763
|
}
|
|
5012
4764
|
|
|
5013
|
-
function isLeaseExpiryRow(value: unknown): value is { expiresAt: number | null } {
|
|
5014
|
-
return isRecord(value) && (typeof value.expiresAt === "number" || value.expiresAt === null);
|
|
5015
|
-
}
|
|
5016
|
-
|
|
5017
4765
|
function isNextOrderRow(value: unknown): value is { nextOrder: number } {
|
|
5018
4766
|
return isRecord(value) && typeof value.nextOrder === "number";
|
|
5019
4767
|
}
|