@osolmaz/pi-workflows 0.15.3 → 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 +24 -8
- 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/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 +102 -0
- package/dist/client/client.js +737 -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 +275 -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 +144 -0
- package/dist/client/view.js +3 -0
- package/dist/client/view.js.map +1 -0
- package/dist/controllers/index.d.ts +1 -1
- package/dist/controllers/index.js.map +1 -1
- package/dist/controllers/sqlite.d.ts +47 -157
- package/dist/controllers/sqlite.js +230 -406
- package/dist/controllers/sqlite.js.map +1 -1
- package/dist/extension/index.d.ts +23 -1
- package/dist/extension/index.js +734 -555
- 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 +9 -2
- package/dist/extension/session-view.js +117 -51
- 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 +3 -2
- package/dist/extension/widget.js +41 -24
- 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/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 +54 -8
- package/dist/host/runner.js +1997 -305
- package/dist/host/runner.js.map +1 -1
- package/dist/host/state.d.ts +25 -19
- package/dist/host/state.js +303 -89
- package/dist/host/state.js.map +1 -1
- package/dist/host/view.d.ts +83 -0
- package/dist/host/view.js +975 -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 +9 -33
- package/dist/state/prune.js.map +1 -1
- package/dist/state/schema.js +88 -98
- 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/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/render.js +1 -1
- package/dist/viewer/render.js.map +1 -1
- package/dist/viewer/tui.d.ts +5 -7
- package/dist/viewer/tui.js +245 -108
- 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 +67 -20
- package/dist/workflows/store.js +451 -378
- 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 +385 -0
- package/docs/2026-09-02-installed-live-e2e-plan.md +239 -0
- 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 +31 -25
- package/docs/WORKFLOW_HOST.md +155 -77
- package/docs/WORKFLOW_STEP_MESSAGES.md +166 -75
- package/docs/development.md +2 -1
- package/docs/live-replay-protocol.md +74 -132
- package/docs/tui-viewer.md +12 -14
- package/docs/workflows.md +78 -28
- package/herdr-plugin.toml +1 -1
- package/package.json +9 -3
- package/plugins/herdr/viewer.mjs +14 -5
- package/protocol/client.v1.schema.json +137 -0
- package/protocol/fixtures/client-v1.json +23 -0
- 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/activity.ts +6 -0
- package/src/client/client.ts +939 -0
- package/src/client/index.ts +25 -0
- package/src/client/materialize.ts +228 -0
- package/src/client/protocol.ts +333 -0
- package/src/client/resolver.ts +26 -0
- package/src/client/view.ts +163 -0
- package/src/controllers/index.ts +0 -1
- package/src/controllers/sqlite.ts +339 -808
- package/src/extension/index.ts +853 -665
- 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 +157 -51
- package/src/extension/step-message.ts +19 -9
- package/src/extension/widget.ts +53 -25
- 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/child-worker-supervisor.ts +1 -1
- package/src/host/resolver-entry.ts +11 -26
- package/src/host/runner.ts +2639 -450
- package/src/host/state.ts +404 -137
- package/src/host/view.ts +1212 -0
- package/src/host/worker-protocol.ts +1 -1
- package/src/state/database.ts +13 -0
- package/src/state/prune.ts +14 -42
- package/src/state/schema.ts +88 -98
- package/src/state/workflow-messages.ts +548 -0
- package/src/viewer/backup.ts +29 -0
- package/src/viewer/cli.ts +171 -185
- package/src/viewer/render.ts +1 -5
- package/src/viewer/tui.ts +261 -124
- 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 +650 -511
- 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/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/extension/decision-channels.ts +0 -1826
- package/src/host/client.ts +0 -293
- package/src/host/protocol.ts +0 -196
- package/src/viewer/watch.ts +0 -51
package/dist/host/state.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
+
import { CLIENT_PROTOCOL_SCHEMA, clientRequestFingerprint, } from "../client/protocol.js";
|
|
2
3
|
import { StateDatabase } from "../state/database.js";
|
|
3
4
|
import { canonicalJson } from "../state/json.js";
|
|
4
5
|
import { tokenHash } from "../state/mutation.js";
|
|
5
|
-
import {
|
|
6
|
+
import { WorkflowMessageStore, workflowMessageIdFor, } from "../state/workflow-messages.js";
|
|
7
|
+
import { decisionWorkflowMessageContent, stepWorkflowMessageContent, } from "../workflows/workflow-message-content.js";
|
|
6
8
|
export class HostStateStore {
|
|
7
9
|
state;
|
|
8
10
|
ownsState;
|
|
11
|
+
workflowMessages;
|
|
9
12
|
constructor(databasePath, options = {}) {
|
|
10
13
|
this.ownsState = options.state === undefined;
|
|
11
14
|
this.state =
|
|
@@ -14,6 +17,7 @@ export class HostStateStore {
|
|
|
14
17
|
filePath: databasePath,
|
|
15
18
|
mode: options.readOnly === true ? "read-only" : "read-write",
|
|
16
19
|
});
|
|
20
|
+
this.workflowMessages = new WorkflowMessageStore(this.state);
|
|
17
21
|
}
|
|
18
22
|
close() {
|
|
19
23
|
if (this.ownsState)
|
|
@@ -86,39 +90,56 @@ export class HostStateStore {
|
|
|
86
90
|
}
|
|
87
91
|
readCommand(request) {
|
|
88
92
|
const row = this.state.connection
|
|
89
|
-
.prepare(`SELECT request_fingerprint AS
|
|
93
|
+
.prepare(`SELECT request_fingerprint AS clientRequestFingerprint, outcome, accepted_revision AS revision,
|
|
90
94
|
receipt_hash AS receiptHash, error_hash AS errorHash
|
|
91
95
|
FROM host_commands WHERE request_id = ?`)
|
|
92
96
|
.get(request.requestId);
|
|
93
97
|
if (!isCommandRow(row))
|
|
94
98
|
return undefined;
|
|
95
|
-
if (!row.
|
|
99
|
+
if (!row.clientRequestFingerprint.equals(clientRequestFingerprint(request))) {
|
|
96
100
|
return conflictResponse(request.requestId, "Request ID was reused with another payload");
|
|
97
101
|
}
|
|
98
102
|
return this.commandResponse(request.requestId, row);
|
|
99
103
|
}
|
|
100
|
-
|
|
104
|
+
adoptCommand(request) {
|
|
101
105
|
const existing = this.readCommand(request);
|
|
102
106
|
if (existing !== undefined) {
|
|
103
|
-
return existing.outcome === "
|
|
107
|
+
return existing.outcome === "accepted" || existing.outcome === "adopted"
|
|
108
|
+
? { ...existing, outcome: "adopted" }
|
|
109
|
+
: existing;
|
|
110
|
+
}
|
|
111
|
+
const idempotent = this.state.connection
|
|
112
|
+
.prepare(`SELECT request_id AS requestId, request_fingerprint AS clientRequestFingerprint,
|
|
113
|
+
outcome, accepted_revision AS revision, receipt_hash AS receiptHash,
|
|
114
|
+
error_hash AS errorHash
|
|
115
|
+
FROM host_commands WHERE client_id = ? AND idempotency_key = ?`)
|
|
116
|
+
.get(request.clientId, request.idempotencyKey);
|
|
117
|
+
if (!isIdempotentCommandRow(idempotent))
|
|
118
|
+
return undefined;
|
|
119
|
+
if (!idempotent.clientRequestFingerprint.equals(clientRequestFingerprint(request))) {
|
|
120
|
+
return conflictResponse(request.requestId, "Idempotency key was reused with another payload");
|
|
104
121
|
}
|
|
122
|
+
const adopted = this.commandResponse(idempotent.requestId, idempotent);
|
|
123
|
+
return {
|
|
124
|
+
...adopted,
|
|
125
|
+
requestId: request.requestId,
|
|
126
|
+
...(adopted.outcome === "accepted" || adopted.outcome === "adopted"
|
|
127
|
+
? { outcome: "adopted" }
|
|
128
|
+
: {}),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
executeCommand(request, hostEpoch, operation) {
|
|
132
|
+
const existing = this.adoptCommand(request);
|
|
133
|
+
if (existing !== undefined)
|
|
134
|
+
return existing;
|
|
105
135
|
return this.state.transaction(() => {
|
|
106
|
-
const idempotent = this.
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
error_hash AS errorHash
|
|
110
|
-
FROM host_commands WHERE client_id = ? AND idempotency_key = ?`)
|
|
111
|
-
.get(request.clientId, request.idempotencyKey);
|
|
112
|
-
if (isIdempotentCommandRow(idempotent)) {
|
|
113
|
-
if (!idempotent.requestFingerprint.equals(requestFingerprint(request))) {
|
|
114
|
-
return conflictResponse(request.requestId, "Idempotency key was reused with another payload");
|
|
115
|
-
}
|
|
116
|
-
const adopted = this.commandResponse(idempotent.requestId, idempotent);
|
|
117
|
-
return { ...adopted, requestId: request.requestId, outcome: "adopted" };
|
|
118
|
-
}
|
|
136
|
+
const idempotent = this.adoptCommand(request);
|
|
137
|
+
if (idempotent !== undefined)
|
|
138
|
+
return idempotent;
|
|
119
139
|
const result = operation();
|
|
120
140
|
const response = {
|
|
121
|
-
schema:
|
|
141
|
+
schema: CLIENT_PROTOCOL_SCHEMA,
|
|
142
|
+
type: "response",
|
|
122
143
|
requestId: request.requestId,
|
|
123
144
|
...result,
|
|
124
145
|
};
|
|
@@ -131,7 +152,7 @@ export class HostStateStore {
|
|
|
131
152
|
run_id, accepted_revision, outcome, receipt_hash, error_hash,
|
|
132
153
|
host_epoch, created_at, completed_at
|
|
133
154
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
134
|
-
.run(request.requestId, request.clientId, request.operation, request.idempotencyKey,
|
|
155
|
+
.run(request.requestId, request.clientId, request.operation, request.idempotencyKey, clientRequestFingerprint(request), request.runId ?? null, response.revision ?? null, response.outcome, receiptHash, errorHash, hostEpoch, now, now);
|
|
135
156
|
return response;
|
|
136
157
|
});
|
|
137
158
|
}
|
|
@@ -179,13 +200,13 @@ export class HostStateStore {
|
|
|
179
200
|
}
|
|
180
201
|
readWorkerMessage(message) {
|
|
181
202
|
const row = this.state.connection
|
|
182
|
-
.prepare(`SELECT request_fingerprint AS
|
|
203
|
+
.prepare(`SELECT request_fingerprint AS clientRequestFingerprint, outcome,
|
|
183
204
|
accepted_revision AS revision, result_hash AS resultHash, error_hash AS errorHash
|
|
184
205
|
FROM worker_messages WHERE worker_epoch = ? AND message_id = ?`)
|
|
185
206
|
.get(message.workerEpoch, message.messageId);
|
|
186
207
|
if (!isWorkerMessageRow(row))
|
|
187
208
|
return undefined;
|
|
188
|
-
if (!row.
|
|
209
|
+
if (!row.clientRequestFingerprint.equals(workerMessageFingerprint(message))) {
|
|
189
210
|
return {
|
|
190
211
|
schema: "pi-workflows.worker-response.v1",
|
|
191
212
|
messageId: message.messageId,
|
|
@@ -225,14 +246,16 @@ export class HostStateStore {
|
|
|
225
246
|
if (existing.status === "settled" && existing.consumedAt === null) {
|
|
226
247
|
this.state.connection
|
|
227
248
|
.prepare(`UPDATE interactive_requests
|
|
228
|
-
SET status = 'pending',
|
|
229
|
-
|
|
230
|
-
accepted_submission_id = NULL, settled_at = NULL, consumed_at = NULL,
|
|
231
|
-
revision = revision + 1, updated_at = ?
|
|
249
|
+
SET status = 'pending', accepted_submission_id = NULL,
|
|
250
|
+
settled_at = NULL, consumed_at = NULL, revision = revision + 1, updated_at = ?
|
|
232
251
|
WHERE request_id = ? AND status = 'settled' AND consumed_at IS NULL`)
|
|
233
252
|
.run(now, options.requestId);
|
|
234
|
-
|
|
253
|
+
const reopened = this.requireInteractiveRequest(options.requestId);
|
|
254
|
+
this.ensureInteractionMessage(reopened, "resumed", now);
|
|
255
|
+
return reopened;
|
|
235
256
|
}
|
|
257
|
+
if (existing.status === "pending")
|
|
258
|
+
this.ensureInteractionMessage(existing, "initial", now);
|
|
236
259
|
return existing;
|
|
237
260
|
}
|
|
238
261
|
const contractHash = this.state.putJson(options.contract, now);
|
|
@@ -242,9 +265,172 @@ export class HostStateStore {
|
|
|
242
265
|
revision, status, created_at, updated_at
|
|
243
266
|
) VALUES (?, ?, ?, ?, ?, ?, 1, 'pending', ?, ?)`)
|
|
244
267
|
.run(options.requestId, options.runId, options.attemptId, options.targetSessionId, options.kind, contractHash, now, now);
|
|
245
|
-
|
|
268
|
+
const created = this.requireInteractiveRequest(options.requestId);
|
|
269
|
+
this.ensureInteractionMessage(created, "initial", now);
|
|
270
|
+
return created;
|
|
246
271
|
});
|
|
247
272
|
}
|
|
273
|
+
ensureInteractionMessage(request, reason, now = Date.now()) {
|
|
274
|
+
const kind = request.kind === "decision" ? "decision" : "step";
|
|
275
|
+
const idempotencyKey = `${request.revision}:${reason}`;
|
|
276
|
+
const workflowMessageId = workflowMessageIdFor(kind, request.requestId, idempotencyKey);
|
|
277
|
+
const content = kind === "decision"
|
|
278
|
+
? decisionWorkflowMessageContent({
|
|
279
|
+
workflowMessageId,
|
|
280
|
+
requestId: request.requestId,
|
|
281
|
+
runId: request.runId,
|
|
282
|
+
contract: request.contract,
|
|
283
|
+
})
|
|
284
|
+
: stepWorkflowMessageContent({
|
|
285
|
+
workflowMessageId,
|
|
286
|
+
requestId: request.requestId,
|
|
287
|
+
contract: request.contract,
|
|
288
|
+
reason,
|
|
289
|
+
});
|
|
290
|
+
return this.workflowMessages.create({
|
|
291
|
+
workflowMessageId,
|
|
292
|
+
runId: request.runId,
|
|
293
|
+
targetSessionId: request.targetSessionId,
|
|
294
|
+
kind,
|
|
295
|
+
sourceId: request.requestId,
|
|
296
|
+
idempotencyKey,
|
|
297
|
+
content,
|
|
298
|
+
now,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
resumePendingInteraction(runId, now = Date.now()) {
|
|
302
|
+
return this.state.transaction(() => {
|
|
303
|
+
const row = this.state.connection
|
|
304
|
+
.prepare(`SELECT request_id AS requestId FROM interactive_requests
|
|
305
|
+
WHERE run_id = ? AND status = 'pending' ORDER BY created_at LIMIT 1`)
|
|
306
|
+
.get(runId);
|
|
307
|
+
if (!isRequestIdRow(row))
|
|
308
|
+
throw new Error("Pending workflow interaction is missing");
|
|
309
|
+
const current = this.requireInteractiveRequest(row.requestId);
|
|
310
|
+
if (current.kind === "decision")
|
|
311
|
+
return current;
|
|
312
|
+
this.workflowMessages.cancelPendingForSource(current.requestId, "step", now);
|
|
313
|
+
const changed = this.state.connection
|
|
314
|
+
.prepare(`UPDATE interactive_requests SET revision = revision + 1, updated_at = ?
|
|
315
|
+
WHERE request_id = ? AND status = 'pending'`)
|
|
316
|
+
.run(now, row.requestId);
|
|
317
|
+
if (changed.changes !== 1)
|
|
318
|
+
throw new Error("Pending workflow interaction resume is stale");
|
|
319
|
+
const request = this.requireInteractiveRequest(row.requestId);
|
|
320
|
+
this.ensureInteractionMessage(request, "resumed", now);
|
|
321
|
+
return request;
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
beginInteractionModelTurn(requestId, now = Date.now()) {
|
|
325
|
+
const row = this.state.connection
|
|
326
|
+
.prepare(`SELECT i.attempt_id AS attemptId, i.created_at AS createdAt,
|
|
327
|
+
a.started_at AS startedAt, a.deadline_at AS deadlineAt,
|
|
328
|
+
(SELECT MAX(t.ended_at)
|
|
329
|
+
FROM workflow_messages m
|
|
330
|
+
JOIN workflow_turns t ON t.workflow_message_id = m.workflow_message_id
|
|
331
|
+
WHERE m.source_id = i.request_id AND m.kind = 'step'
|
|
332
|
+
AND t.state = 'ended') AS previousTurnEndedAt
|
|
333
|
+
FROM interactive_requests i
|
|
334
|
+
JOIN node_attempts a ON a.attempt_id = i.attempt_id
|
|
335
|
+
WHERE i.request_id = ? AND i.status = 'pending'`)
|
|
336
|
+
.get(requestId);
|
|
337
|
+
if (!isInteractionTimeoutRow(row)) {
|
|
338
|
+
throw new Error("Pending workflow interaction timeout is missing");
|
|
339
|
+
}
|
|
340
|
+
this.shiftInteractionTimeout(row, Math.max(0, now - (row.previousTurnEndedAt ?? row.startedAt ?? row.createdAt)), now);
|
|
341
|
+
}
|
|
342
|
+
interactionPauseStartedAt(runId) {
|
|
343
|
+
const row = this.state.connection
|
|
344
|
+
.prepare(`SELECT r.updated_at AS pausedAt
|
|
345
|
+
FROM runs r
|
|
346
|
+
JOIN interactive_requests i ON i.run_id = r.run_id AND i.status = 'pending'
|
|
347
|
+
WHERE r.run_id = ? AND r.paused = 1 LIMIT 1`)
|
|
348
|
+
.get(runId);
|
|
349
|
+
return isPausedAtRow(row) ? row.pausedAt : undefined;
|
|
350
|
+
}
|
|
351
|
+
resumeInteractionModelTurn(runId, pausedAt, now = Date.now()) {
|
|
352
|
+
if (pausedAt === undefined)
|
|
353
|
+
return;
|
|
354
|
+
const row = this.state.connection
|
|
355
|
+
.prepare(`SELECT i.attempt_id AS attemptId, i.created_at AS createdAt,
|
|
356
|
+
a.started_at AS startedAt, a.deadline_at AS deadlineAt,
|
|
357
|
+
t.ended_at AS previousTurnEndedAt
|
|
358
|
+
FROM interactive_requests i
|
|
359
|
+
JOIN node_attempts a ON a.attempt_id = i.attempt_id
|
|
360
|
+
JOIN workflow_messages m ON m.source_id = i.request_id AND m.kind = 'step'
|
|
361
|
+
JOIN workflow_turns t ON t.workflow_message_id = m.workflow_message_id
|
|
362
|
+
WHERE i.run_id = ? AND i.status = 'pending' AND t.started_at <= ?
|
|
363
|
+
AND (t.ended_at IS NULL OR t.ended_at > ?)
|
|
364
|
+
ORDER BY t.started_at DESC LIMIT 1`)
|
|
365
|
+
.get(runId, pausedAt, pausedAt);
|
|
366
|
+
if (!isInteractionTimeoutRow(row))
|
|
367
|
+
return;
|
|
368
|
+
const modelTurnStoppedAt = Math.min(now, row.previousTurnEndedAt ?? now);
|
|
369
|
+
this.shiftInteractionTimeout(row, Math.max(0, modelTurnStoppedAt - pausedAt), now);
|
|
370
|
+
}
|
|
371
|
+
markSessionModelTurnsInactive(targetSessionId, now = Date.now()) {
|
|
372
|
+
this.state.connection
|
|
373
|
+
.prepare(`UPDATE node_attempts SET status = 'interrupted', updated_at = ?
|
|
374
|
+
WHERE status <> 'interrupted' AND attempt_id IN (
|
|
375
|
+
SELECT DISTINCT i.attempt_id
|
|
376
|
+
FROM interactive_requests i
|
|
377
|
+
JOIN workflow_messages m ON m.source_id = i.request_id AND m.kind = 'step'
|
|
378
|
+
JOIN workflow_turns t ON t.workflow_message_id = m.workflow_message_id
|
|
379
|
+
AND t.state = 'started'
|
|
380
|
+
WHERE i.target_session_id = ? AND i.status = 'pending'
|
|
381
|
+
)`)
|
|
382
|
+
.run(now, targetSessionId);
|
|
383
|
+
}
|
|
384
|
+
recoverInactiveModelTurns(previousHeartbeatAt, now = Date.now()) {
|
|
385
|
+
const rows = this.state.connection
|
|
386
|
+
.prepare(`SELECT i.attempt_id AS attemptId, a.status, a.started_at AS startedAt,
|
|
387
|
+
a.deadline_at AS deadlineAt, a.updated_at AS updatedAt,
|
|
388
|
+
MAX(t.started_at) AS turnStartedAt
|
|
389
|
+
FROM interactive_requests i
|
|
390
|
+
JOIN node_attempts a ON a.attempt_id = i.attempt_id
|
|
391
|
+
JOIN workflow_messages m ON m.source_id = i.request_id AND m.kind = 'step'
|
|
392
|
+
JOIN workflow_turns t ON t.workflow_message_id = m.workflow_message_id
|
|
393
|
+
AND t.state = 'started'
|
|
394
|
+
WHERE i.status = 'pending'
|
|
395
|
+
GROUP BY i.attempt_id`)
|
|
396
|
+
.all()
|
|
397
|
+
.filter(isInactiveInteractionTimeoutRow);
|
|
398
|
+
for (const row of rows) {
|
|
399
|
+
const inactiveAt = row.status === "interrupted"
|
|
400
|
+
? row.updatedAt
|
|
401
|
+
: previousHeartbeatAt === undefined
|
|
402
|
+
? row.updatedAt
|
|
403
|
+
: Math.max(previousHeartbeatAt, row.turnStartedAt);
|
|
404
|
+
this.shiftInteractionTimeout(row, Math.max(0, now - inactiveAt), now);
|
|
405
|
+
this.state.connection
|
|
406
|
+
.prepare(`UPDATE node_attempts SET status = 'interrupted', updated_at = ?
|
|
407
|
+
WHERE attempt_id = ? AND status IN ('pending', 'running', 'waiting', 'interrupted')`)
|
|
408
|
+
.run(now, row.attemptId);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
resumeSessionModelTurns(targetSessionId, now = Date.now()) {
|
|
412
|
+
const rows = this.state.connection
|
|
413
|
+
.prepare(`SELECT i.attempt_id AS attemptId, a.status, a.started_at AS startedAt,
|
|
414
|
+
a.deadline_at AS deadlineAt, a.updated_at AS updatedAt,
|
|
415
|
+
MAX(t.started_at) AS turnStartedAt
|
|
416
|
+
FROM interactive_requests i
|
|
417
|
+
JOIN node_attempts a ON a.attempt_id = i.attempt_id
|
|
418
|
+
JOIN workflow_messages m ON m.source_id = i.request_id AND m.kind = 'step'
|
|
419
|
+
JOIN workflow_turns t ON t.workflow_message_id = m.workflow_message_id
|
|
420
|
+
AND t.state = 'started'
|
|
421
|
+
WHERE i.target_session_id = ? AND i.status = 'pending'
|
|
422
|
+
AND a.status = 'interrupted'
|
|
423
|
+
GROUP BY i.attempt_id`)
|
|
424
|
+
.all(targetSessionId)
|
|
425
|
+
.filter(isInactiveInteractionTimeoutRow);
|
|
426
|
+
for (const row of rows) {
|
|
427
|
+
this.shiftInteractionTimeout(row, Math.max(0, now - row.updatedAt), now);
|
|
428
|
+
this.state.connection
|
|
429
|
+
.prepare(`UPDATE node_attempts SET status = 'waiting', updated_at = ?
|
|
430
|
+
WHERE attempt_id = ? AND status = 'interrupted'`)
|
|
431
|
+
.run(now, row.attemptId);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
248
434
|
getInteraction(requestId) {
|
|
249
435
|
return this.interactiveRequest(requestId);
|
|
250
436
|
}
|
|
@@ -275,7 +461,7 @@ export class HostStateStore {
|
|
|
275
461
|
FROM interactive_requests i
|
|
276
462
|
JOIN node_attempts a ON a.attempt_id = i.attempt_id
|
|
277
463
|
JOIN interactive_submissions s ON s.request_id = i.request_id
|
|
278
|
-
WHERE i.run_id = ? AND i.status
|
|
464
|
+
WHERE i.run_id = ? AND i.status = 'pending'
|
|
279
465
|
AND s.outcome = 'validating'
|
|
280
466
|
ORDER BY s.submitted_at DESC LIMIT 1`)
|
|
281
467
|
.get(runId);
|
|
@@ -295,26 +481,32 @@ export class HostStateStore {
|
|
|
295
481
|
FROM interactive_requests i
|
|
296
482
|
JOIN interactive_submissions s ON s.request_id = i.request_id
|
|
297
483
|
JOIN run_queue q ON q.run_id = i.run_id
|
|
298
|
-
WHERE i.status
|
|
484
|
+
WHERE i.status = 'pending' AND s.outcome = 'validating'
|
|
299
485
|
AND q.status NOT IN ('done', 'failed', 'cancelled')
|
|
300
486
|
GROUP BY i.run_id
|
|
301
487
|
ORDER BY MIN(s.submitted_at), i.run_id`)
|
|
302
488
|
.all()
|
|
303
489
|
.flatMap((row) => (isRunIdRow(row) ? [row.runId] : []));
|
|
304
490
|
}
|
|
305
|
-
|
|
491
|
+
expiredInteractionRuns(now = Date.now()) {
|
|
306
492
|
return this.state.connection
|
|
307
|
-
.prepare(`SELECT i.run_id AS runId
|
|
493
|
+
.prepare(`SELECT i.run_id AS runId, i.target_session_id AS targetSessionId
|
|
308
494
|
FROM interactive_requests i
|
|
309
495
|
JOIN node_attempts a ON a.attempt_id = i.attempt_id
|
|
496
|
+
JOIN runs r ON r.run_id = i.run_id
|
|
310
497
|
JOIN run_queue q ON q.run_id = i.run_id
|
|
311
|
-
WHERE i.status
|
|
498
|
+
WHERE i.status = 'pending' AND r.paused = 0
|
|
312
499
|
AND a.deadline_at IS NOT NULL AND a.deadline_at <= ?
|
|
313
500
|
AND q.status NOT IN ('done', 'failed', 'cancelled')
|
|
314
|
-
|
|
501
|
+
AND EXISTS (
|
|
502
|
+
SELECT 1 FROM workflow_messages m
|
|
503
|
+
JOIN workflow_turns t ON t.workflow_message_id = m.workflow_message_id
|
|
504
|
+
WHERE m.source_id = i.request_id AND m.kind = 'step' AND t.state = 'started'
|
|
505
|
+
)
|
|
506
|
+
GROUP BY i.run_id, i.target_session_id
|
|
315
507
|
ORDER BY MIN(a.deadline_at), i.run_id`)
|
|
316
508
|
.all(now)
|
|
317
|
-
.
|
|
509
|
+
.filter(isExpiredInteractionRunRow);
|
|
318
510
|
}
|
|
319
511
|
timedOutInteraction(runId) {
|
|
320
512
|
const row = this.state.connection
|
|
@@ -323,8 +515,13 @@ export class HostStateStore {
|
|
|
323
515
|
JOIN node_attempts a ON a.attempt_id = i.attempt_id
|
|
324
516
|
JOIN runs r ON r.run_id = i.run_id
|
|
325
517
|
WHERE i.run_id = ? AND i.status = 'cancelled' AND r.status = 'running'
|
|
326
|
-
AND a.deadline_at IS NOT NULL AND a.deadline_at <= ?
|
|
327
|
-
AND a.status IN ('pending', 'running', 'waiting', 'interrupted')
|
|
518
|
+
AND r.paused = 0 AND a.deadline_at IS NOT NULL AND a.deadline_at <= ?
|
|
519
|
+
AND a.status IN ('pending', 'running', 'waiting', 'interrupted', 'timed_out')
|
|
520
|
+
AND EXISTS (
|
|
521
|
+
SELECT 1 FROM workflow_messages m
|
|
522
|
+
JOIN workflow_turns t ON t.workflow_message_id = m.workflow_message_id
|
|
523
|
+
WHERE m.source_id = i.request_id AND m.kind = 'step' AND t.state = 'started'
|
|
524
|
+
)
|
|
328
525
|
ORDER BY a.deadline_at, i.request_id LIMIT 1`)
|
|
329
526
|
.get(runId, Date.now());
|
|
330
527
|
return isTimedOutInteractionRow(row) ? row : undefined;
|
|
@@ -367,13 +564,14 @@ export class HostStateStore {
|
|
|
367
564
|
if (options.accepted) {
|
|
368
565
|
const changed = this.state.connection
|
|
369
566
|
.prepare(`UPDATE interactive_requests
|
|
370
|
-
SET status = 'settled',
|
|
371
|
-
presentation_claim_expires_at = NULL, accepted_submission_id = ?,
|
|
567
|
+
SET status = 'settled', accepted_submission_id = ?,
|
|
372
568
|
revision = revision + 1, updated_at = ?, settled_at = ?
|
|
373
|
-
WHERE request_id = ? AND revision = ? AND status
|
|
569
|
+
WHERE request_id = ? AND revision = ? AND status = 'pending'`)
|
|
374
570
|
.run(options.submissionId, now, now, options.requestId, request.revision);
|
|
375
571
|
if (changed.changes !== 1)
|
|
376
572
|
throw new Error("Interactive request validation is stale");
|
|
573
|
+
this.workflowMessages.cancelPendingForSource(options.requestId, "step", now);
|
|
574
|
+
this.workflowMessages.cancelPendingForSource(options.requestId, "decision", now);
|
|
377
575
|
}
|
|
378
576
|
const settled = this.interactionSubmission(options.requestId, options.submissionId);
|
|
379
577
|
if (settled === undefined)
|
|
@@ -392,40 +590,18 @@ export class HostStateStore {
|
|
|
392
590
|
listPendingInteractions(sessionId) {
|
|
393
591
|
return this.state.connection
|
|
394
592
|
.prepare(`SELECT request_id AS requestId FROM interactive_requests
|
|
395
|
-
WHERE target_session_id = ? AND status
|
|
593
|
+
WHERE target_session_id = ? AND status = 'pending'
|
|
396
594
|
ORDER BY created_at, request_id`)
|
|
397
595
|
.all(sessionId)
|
|
398
596
|
.flatMap((row) => isRequestIdRow(row) ? [this.requireInteractiveRequest(row.requestId)] : []);
|
|
399
597
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
WHERE request_id = ? AND revision = ? AND presentation_session_entry_id IS NULL
|
|
408
|
-
AND (
|
|
409
|
-
status = 'pending'
|
|
410
|
-
OR (status = 'presenting' AND presentation_claim_expires_at <= ?)
|
|
411
|
-
)`)
|
|
412
|
-
.run(options.presenterId, now + options.leaseMs, now, options.requestId, options.expectedRevision, now);
|
|
413
|
-
if (changed.changes !== 1)
|
|
414
|
-
throw new Error("Interactive request presentation claim conflict");
|
|
415
|
-
return this.requireInteractiveRequest(options.requestId);
|
|
416
|
-
}
|
|
417
|
-
markInteractionPresented(options) {
|
|
418
|
-
const now = Date.now();
|
|
419
|
-
const changed = this.state.connection
|
|
420
|
-
.prepare(`UPDATE interactive_requests
|
|
421
|
-
SET status = 'presenting', presenter_id = NULL, presentation_claim_expires_at = NULL,
|
|
422
|
-
presentation_session_entry_id = ?, revision = revision + 1, updated_at = ?
|
|
423
|
-
WHERE request_id = ? AND revision = ? AND status IN ('pending', 'presenting')
|
|
424
|
-
AND (presentation_session_entry_id IS NULL OR presentation_session_entry_id = ?)`)
|
|
425
|
-
.run(options.sessionEntryId, now, options.requestId, options.expectedRevision, options.sessionEntryId);
|
|
426
|
-
if (changed.changes !== 1)
|
|
427
|
-
throw new Error("Interactive request presentation conflict");
|
|
428
|
-
return this.requireInteractiveRequest(options.requestId);
|
|
598
|
+
listPendingDecisionInteractions() {
|
|
599
|
+
return this.state.connection
|
|
600
|
+
.prepare(`SELECT request_id AS requestId FROM interactive_requests
|
|
601
|
+
WHERE kind = 'decision' AND status = 'pending'
|
|
602
|
+
ORDER BY created_at, request_id`)
|
|
603
|
+
.all()
|
|
604
|
+
.flatMap((row) => isRequestIdRow(row) ? [this.requireInteractiveRequest(row.requestId)] : []);
|
|
429
605
|
}
|
|
430
606
|
submitInteraction(options) {
|
|
431
607
|
return this.recordInteractionSubmission({
|
|
@@ -456,6 +632,7 @@ export class HostStateStore {
|
|
|
456
632
|
}
|
|
457
633
|
return {
|
|
458
634
|
interaction: this.requireInteractiveRequest(options.requestId),
|
|
635
|
+
submissionId: existing.submissionId,
|
|
459
636
|
outcome: "adopted",
|
|
460
637
|
receipt: existing.receiptHash === null
|
|
461
638
|
? { requestId: options.requestId, submissionId: existing.submissionId }
|
|
@@ -472,8 +649,7 @@ export class HostStateStore {
|
|
|
472
649
|
}
|
|
473
650
|
}
|
|
474
651
|
const request = this.requireInteractiveRequest(options.requestId);
|
|
475
|
-
if (request.revision !== options.expectedRevision ||
|
|
476
|
-
!["pending", "presenting"].includes(request.status)) {
|
|
652
|
+
if (request.revision !== options.expectedRevision || request.status !== "pending") {
|
|
477
653
|
throw new Error("Interactive request revision conflict");
|
|
478
654
|
}
|
|
479
655
|
const savedPayloadHash = this.state.putJson(options.payload, now);
|
|
@@ -487,14 +663,16 @@ export class HostStateStore {
|
|
|
487
663
|
if (options.settle) {
|
|
488
664
|
this.state.connection
|
|
489
665
|
.prepare(`UPDATE interactive_requests
|
|
490
|
-
SET status = 'settled',
|
|
491
|
-
accepted_submission_id = ?, revision = revision + 1,
|
|
666
|
+
SET status = 'settled', accepted_submission_id = ?, revision = revision + 1,
|
|
492
667
|
updated_at = ?, settled_at = ?
|
|
493
|
-
WHERE request_id = ? AND revision = ? AND status
|
|
668
|
+
WHERE request_id = ? AND revision = ? AND status = 'pending'`)
|
|
494
669
|
.run(options.submissionId, now, now, options.requestId, options.expectedRevision);
|
|
670
|
+
this.workflowMessages.cancelPendingForSource(options.requestId, "step", now);
|
|
671
|
+
this.workflowMessages.cancelPendingForSource(options.requestId, "decision", now);
|
|
495
672
|
}
|
|
496
673
|
return {
|
|
497
674
|
interaction: this.requireInteractiveRequest(options.requestId),
|
|
675
|
+
submissionId: options.submissionId,
|
|
498
676
|
outcome: "accepted",
|
|
499
677
|
receipt: options.receipt ?? {
|
|
500
678
|
requestId: options.requestId,
|
|
@@ -520,7 +698,8 @@ export class HostStateStore {
|
|
|
520
698
|
? undefined
|
|
521
699
|
: this.state.readBlob(row.errorHash)?.content.toString("utf8");
|
|
522
700
|
return {
|
|
523
|
-
schema:
|
|
701
|
+
schema: CLIENT_PROTOCOL_SCHEMA,
|
|
702
|
+
type: "response",
|
|
524
703
|
requestId,
|
|
525
704
|
outcome: row.outcome,
|
|
526
705
|
...(row.revision === null ? {} : { revision: row.revision }),
|
|
@@ -532,9 +711,7 @@ export class HostStateStore {
|
|
|
532
711
|
const row = this.state.connection
|
|
533
712
|
.prepare(`SELECT request_id AS requestId, run_id AS runId, attempt_id AS attemptId,
|
|
534
713
|
target_session_id AS targetSessionId, kind, contract_hash AS contractHash,
|
|
535
|
-
revision, status,
|
|
536
|
-
presentation_claim_expires_at AS presentationClaimExpiresAt,
|
|
537
|
-
presentation_session_entry_id AS presentationSessionEntryId,
|
|
714
|
+
revision, status, unproductive_turn_ends AS unproductiveTurnEnds,
|
|
538
715
|
accepted_submission_id AS acceptedSubmissionId, created_at AS createdAt,
|
|
539
716
|
updated_at AS updatedAt, settled_at AS settledAt, consumed_at AS consumedAt
|
|
540
717
|
FROM interactive_requests WHERE request_id = ?`)
|
|
@@ -550,9 +727,7 @@ export class HostStateStore {
|
|
|
550
727
|
contract: this.state.readJson(row.contractHash),
|
|
551
728
|
revision: row.revision,
|
|
552
729
|
status: row.status,
|
|
553
|
-
|
|
554
|
-
presentationClaimExpiresAt: iso(row.presentationClaimExpiresAt),
|
|
555
|
-
presentationSessionEntryId: row.presentationSessionEntryId,
|
|
730
|
+
unproductiveTurnEnds: row.unproductiveTurnEnds,
|
|
556
731
|
acceptedSubmissionId: row.acceptedSubmissionId,
|
|
557
732
|
createdAt: new Date(row.createdAt).toISOString(),
|
|
558
733
|
updatedAt: new Date(row.updatedAt).toISOString(),
|
|
@@ -560,6 +735,18 @@ export class HostStateStore {
|
|
|
560
735
|
consumedAt: iso(row.consumedAt),
|
|
561
736
|
};
|
|
562
737
|
}
|
|
738
|
+
shiftInteractionTimeout(row, idleMs, now) {
|
|
739
|
+
if (idleMs === 0 || row.deadlineAt === null)
|
|
740
|
+
return;
|
|
741
|
+
const changed = this.state.connection
|
|
742
|
+
.prepare(`UPDATE node_attempts
|
|
743
|
+
SET started_at = CASE WHEN started_at IS NULL THEN NULL ELSE started_at + ? END,
|
|
744
|
+
deadline_at = deadline_at + ?, updated_at = ?
|
|
745
|
+
WHERE attempt_id = ? AND deadline_at = ?`)
|
|
746
|
+
.run(idleMs, idleMs, now, row.attemptId, row.deadlineAt);
|
|
747
|
+
if (changed.changes !== 1)
|
|
748
|
+
throw new Error("Workflow interaction timeout changed concurrently");
|
|
749
|
+
}
|
|
563
750
|
requireInteractiveRequest(requestId) {
|
|
564
751
|
const request = this.interactiveRequest(requestId);
|
|
565
752
|
if (request === undefined)
|
|
@@ -568,7 +755,13 @@ export class HostStateStore {
|
|
|
568
755
|
}
|
|
569
756
|
}
|
|
570
757
|
function conflictResponse(requestId, error) {
|
|
571
|
-
return {
|
|
758
|
+
return {
|
|
759
|
+
schema: CLIENT_PROTOCOL_SCHEMA,
|
|
760
|
+
type: "response",
|
|
761
|
+
requestId,
|
|
762
|
+
outcome: "conflict",
|
|
763
|
+
error,
|
|
764
|
+
};
|
|
572
765
|
}
|
|
573
766
|
function requireLeaseMs(value) {
|
|
574
767
|
if (!Number.isSafeInteger(value) || value <= 0)
|
|
@@ -590,7 +783,7 @@ function isHostRow(value) {
|
|
|
590
783
|
}
|
|
591
784
|
function isCommandRow(value) {
|
|
592
785
|
return (isRecord(value) &&
|
|
593
|
-
Buffer.isBuffer(value.
|
|
786
|
+
Buffer.isBuffer(value.clientRequestFingerprint) &&
|
|
594
787
|
typeof value.outcome === "string" &&
|
|
595
788
|
nullableNumber(value.revision) &&
|
|
596
789
|
(value.receiptHash === null || Buffer.isBuffer(value.receiptHash)) &&
|
|
@@ -609,7 +802,7 @@ function isRunIdRow(value) {
|
|
|
609
802
|
}
|
|
610
803
|
function isWorkerMessageRow(value) {
|
|
611
804
|
return (isRecord(value) &&
|
|
612
|
-
Buffer.isBuffer(value.
|
|
805
|
+
Buffer.isBuffer(value.clientRequestFingerprint) &&
|
|
613
806
|
["accepted", "adopted", "rejected", "claimLost"].includes(value.outcome) &&
|
|
614
807
|
nullableNumber(value.revision) &&
|
|
615
808
|
(value.resultHash === null || Buffer.isBuffer(value.resultHash)) &&
|
|
@@ -629,6 +822,29 @@ function isTimedOutInteractionRow(value) {
|
|
|
629
822
|
typeof value.attemptId === "string" &&
|
|
630
823
|
typeof value.nodeId === "string");
|
|
631
824
|
}
|
|
825
|
+
function isInteractionTimeoutRow(value) {
|
|
826
|
+
return (isRecord(value) &&
|
|
827
|
+
typeof value.attemptId === "string" &&
|
|
828
|
+
typeof value.createdAt === "number" &&
|
|
829
|
+
nullableNumber(value.startedAt) &&
|
|
830
|
+
nullableNumber(value.deadlineAt) &&
|
|
831
|
+
nullableNumber(value.previousTurnEndedAt));
|
|
832
|
+
}
|
|
833
|
+
function isInactiveInteractionTimeoutRow(value) {
|
|
834
|
+
return (isRecord(value) &&
|
|
835
|
+
typeof value.attemptId === "string" &&
|
|
836
|
+
["pending", "running", "waiting", "interrupted"].includes(value.status) &&
|
|
837
|
+
nullableNumber(value.startedAt) &&
|
|
838
|
+
nullableNumber(value.deadlineAt) &&
|
|
839
|
+
typeof value.updatedAt === "number" &&
|
|
840
|
+
typeof value.turnStartedAt === "number");
|
|
841
|
+
}
|
|
842
|
+
function isExpiredInteractionRunRow(value) {
|
|
843
|
+
return (isRecord(value) && typeof value.runId === "string" && typeof value.targetSessionId === "string");
|
|
844
|
+
}
|
|
845
|
+
function isPausedAtRow(value) {
|
|
846
|
+
return isRecord(value) && typeof value.pausedAt === "number";
|
|
847
|
+
}
|
|
632
848
|
function isSubmissionRow(value) {
|
|
633
849
|
return (isRecord(value) &&
|
|
634
850
|
typeof value.submissionId === "string" &&
|
|
@@ -658,10 +874,8 @@ function isInteractiveRequestRow(value) {
|
|
|
658
874
|
["agent", "assistant", "decision"].includes(value.kind) &&
|
|
659
875
|
Buffer.isBuffer(value.contractHash) &&
|
|
660
876
|
typeof value.revision === "number" &&
|
|
661
|
-
["pending", "
|
|
662
|
-
|
|
663
|
-
nullableNumber(value.presentationClaimExpiresAt) &&
|
|
664
|
-
nullableString(value.presentationSessionEntryId) &&
|
|
877
|
+
["pending", "settled", "cancelled"].includes(value.status) &&
|
|
878
|
+
typeof value.unproductiveTurnEnds === "number" &&
|
|
665
879
|
nullableString(value.acceptedSubmissionId) &&
|
|
666
880
|
typeof value.createdAt === "number" &&
|
|
667
881
|
typeof value.updatedAt === "number" &&
|