@osolmaz/pi-workflows 0.15.2 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/dist/client/activity.d.ts +2 -0
- package/dist/client/activity.js +6 -0
- package/dist/client/activity.js.map +1 -0
- package/dist/client/client.d.ts +101 -0
- package/dist/client/client.js +733 -0
- package/dist/client/client.js.map +1 -0
- package/dist/client/index.d.ts +3 -0
- package/dist/client/index.js +3 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/materialize.d.ts +7 -0
- package/dist/client/materialize.js +177 -0
- package/dist/client/materialize.js.map +1 -0
- package/dist/client/protocol.d.ts +60 -0
- package/dist/client/protocol.js +269 -0
- package/dist/client/protocol.js.map +1 -0
- package/dist/client/resolver.d.ts +23 -0
- package/dist/client/resolver.js +2 -0
- package/dist/client/resolver.js.map +1 -0
- package/dist/client/view.d.ts +118 -0
- package/dist/client/view.js +3 -0
- package/dist/client/view.js.map +1 -0
- package/dist/controllers/sqlite.d.ts +64 -0
- package/dist/controllers/sqlite.js +219 -3
- package/dist/controllers/sqlite.js.map +1 -1
- package/dist/extension/index.d.ts +1 -0
- package/dist/extension/index.js +384 -156
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/session-delivery.d.ts +6 -0
- package/dist/extension/session-delivery.js +80 -25
- package/dist/extension/session-delivery.js.map +1 -1
- package/dist/extension/session-view.d.ts +21 -0
- package/dist/extension/session-view.js +127 -0
- package/dist/extension/session-view.js.map +1 -0
- package/dist/extension/widget.d.ts +2 -1
- package/dist/extension/widget.js +15 -7
- package/dist/extension/widget.js.map +1 -1
- package/dist/host/child-worker-supervisor.js +1 -1
- package/dist/host/child-worker-supervisor.js.map +1 -1
- package/dist/host/resolver-entry.d.ts +2 -23
- package/dist/host/resolver-entry.js +1 -1
- package/dist/host/resolver-entry.js.map +1 -1
- package/dist/host/runner.d.ts +12 -0
- package/dist/host/runner.js +565 -43
- package/dist/host/runner.js.map +1 -1
- package/dist/host/state.d.ts +8 -3
- package/dist/host/state.js +59 -27
- package/dist/host/state.js.map +1 -1
- package/dist/host/view.d.ts +73 -0
- package/dist/host/view.js +871 -0
- package/dist/host/view.js.map +1 -0
- package/dist/host/worker-protocol.js +1 -1
- package/dist/host/worker-protocol.js.map +1 -1
- package/dist/state/database.d.ts +1 -0
- package/dist/state/database.js +15 -0
- package/dist/state/database.js.map +1 -1
- package/dist/state/prune.d.ts +3 -1
- package/dist/state/prune.js +6 -8
- package/dist/state/prune.js.map +1 -1
- package/dist/state/schema.js +12 -1
- package/dist/state/schema.js.map +1 -1
- package/dist/viewer/backup.d.ts +2 -0
- package/dist/viewer/backup.js +28 -0
- package/dist/viewer/backup.js.map +1 -0
- package/dist/viewer/cli.d.ts +4 -0
- package/dist/viewer/cli.js +150 -170
- package/dist/viewer/cli.js.map +1 -1
- package/dist/viewer/tui.d.ts +5 -7
- package/dist/viewer/tui.js +188 -108
- package/dist/viewer/tui.js.map +1 -1
- package/dist/workflows/store.d.ts +62 -1
- package/dist/workflows/store.js +350 -44
- package/dist/workflows/store.js.map +1 -1
- package/docs/2026-09-01-restore-session-delivery-controls-plan.md +139 -0
- package/docs/2026-09-01-unified-workflow-client-plan.md +381 -0
- package/docs/2026-09-02-installed-live-e2e-plan.md +225 -0
- package/docs/SQLITE_STATE.md +13 -11
- package/docs/WORKFLOW_HOST.md +81 -64
- package/docs/WORKFLOW_STEP_MESSAGES.md +4 -4
- package/docs/development.md +2 -1
- package/docs/live-replay-protocol.md +70 -132
- package/docs/tui-viewer.md +10 -14
- package/docs/workflows.md +56 -3
- package/herdr-plugin.toml +1 -1
- package/package.json +9 -3
- package/protocol/client.v1.schema.json +137 -0
- package/protocol/fixtures/client-v1.json +23 -0
- package/src/client/activity.ts +6 -0
- package/src/client/client.ts +935 -0
- package/src/client/index.ts +24 -0
- package/src/client/materialize.ts +228 -0
- package/src/client/protocol.ts +327 -0
- package/src/client/resolver.ts +26 -0
- package/src/client/view.ts +138 -0
- package/src/controllers/sqlite.ts +342 -3
- package/src/extension/index.ts +482 -171
- package/src/extension/session-delivery.ts +88 -25
- package/src/extension/session-view.ts +154 -0
- package/src/extension/widget.ts +18 -9
- package/src/host/child-worker-supervisor.ts +1 -1
- package/src/host/resolver-entry.ts +11 -26
- package/src/host/runner.ts +749 -75
- package/src/host/state.ts +82 -44
- package/src/host/view.ts +1084 -0
- package/src/host/worker-protocol.ts +1 -1
- package/src/state/database.ts +13 -0
- package/src/state/prune.ts +11 -11
- package/src/state/schema.ts +12 -1
- package/src/viewer/backup.ts +29 -0
- package/src/viewer/cli.ts +171 -185
- package/src/viewer/tui.ts +196 -124
- package/src/workflows/store.ts +500 -45
- package/dist/host/client.d.ts +0 -48
- package/dist/host/client.js +0 -216
- package/dist/host/client.js.map +0 -1
- package/dist/host/protocol.d.ts +0 -38
- package/dist/host/protocol.js +0 -156
- package/dist/host/protocol.js.map +0 -1
- package/dist/viewer/watch.d.ts +0 -6
- package/dist/viewer/watch.js +0 -46
- package/dist/viewer/watch.js.map +0 -1
- package/src/host/client.ts +0 -293
- package/src/host/protocol.ts +0 -196
- package/src/viewer/watch.ts +0 -51
package/src/host/state.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
CLIENT_PROTOCOL_SCHEMA,
|
|
4
|
+
clientRequestFingerprint,
|
|
5
|
+
type ClientRequest,
|
|
6
|
+
type ClientResponse,
|
|
7
|
+
} from "../client/protocol.js";
|
|
2
8
|
import { StateDatabase } from "../state/database.js";
|
|
3
9
|
import { canonicalJson, type JsonValue } from "../state/json.js";
|
|
4
10
|
import { tokenHash } from "../state/mutation.js";
|
|
5
|
-
import type { HostRequest, HostResponse } from "./protocol.js";
|
|
6
|
-
import { requestFingerprint } from "./protocol.js";
|
|
7
11
|
import type { WorkerMessage, WorkerResponse } from "./worker-protocol.js";
|
|
8
12
|
|
|
9
13
|
export type HostClaim = {
|
|
@@ -65,6 +69,8 @@ export type InteractiveRequestRecord = {
|
|
|
65
69
|
contract: JsonValue;
|
|
66
70
|
revision: number;
|
|
67
71
|
status: "pending" | "presenting" | "settled" | "cancelled";
|
|
72
|
+
presenterId: string | null;
|
|
73
|
+
presentationClaimExpiresAt: string | null;
|
|
68
74
|
presentationSessionEntryId: string | null;
|
|
69
75
|
acceptedSubmissionId: string | null;
|
|
70
76
|
createdAt: string;
|
|
@@ -199,53 +205,65 @@ export class HostStateStore {
|
|
|
199
205
|
};
|
|
200
206
|
}
|
|
201
207
|
|
|
202
|
-
readCommand(request:
|
|
208
|
+
readCommand(request: ClientRequest): ClientResponse | undefined {
|
|
203
209
|
const row = this.state.connection
|
|
204
210
|
.prepare(
|
|
205
|
-
`SELECT request_fingerprint AS
|
|
211
|
+
`SELECT request_fingerprint AS clientRequestFingerprint, outcome, accepted_revision AS revision,
|
|
206
212
|
receipt_hash AS receiptHash, error_hash AS errorHash
|
|
207
213
|
FROM host_commands WHERE request_id = ?`,
|
|
208
214
|
)
|
|
209
215
|
.get(request.requestId);
|
|
210
216
|
if (!isCommandRow(row)) return undefined;
|
|
211
|
-
if (!row.
|
|
217
|
+
if (!row.clientRequestFingerprint.equals(clientRequestFingerprint(request))) {
|
|
212
218
|
return conflictResponse(request.requestId, "Request ID was reused with another payload");
|
|
213
219
|
}
|
|
214
220
|
return this.commandResponse(request.requestId, row);
|
|
215
221
|
}
|
|
216
222
|
|
|
217
|
-
|
|
218
|
-
request: HostRequest,
|
|
219
|
-
hostEpoch: number,
|
|
220
|
-
operation: () => Omit<HostResponse, "schema" | "requestId">,
|
|
221
|
-
): HostResponse {
|
|
223
|
+
adoptCommand(request: ClientRequest): ClientResponse | undefined {
|
|
222
224
|
const existing = this.readCommand(request);
|
|
223
225
|
if (existing !== undefined) {
|
|
224
|
-
return existing.outcome === "
|
|
226
|
+
return existing.outcome === "accepted" || existing.outcome === "adopted"
|
|
227
|
+
? { ...existing, outcome: "adopted" }
|
|
228
|
+
: existing;
|
|
229
|
+
}
|
|
230
|
+
const idempotent = this.state.connection
|
|
231
|
+
.prepare(
|
|
232
|
+
`SELECT request_id AS requestId, request_fingerprint AS clientRequestFingerprint,
|
|
233
|
+
outcome, accepted_revision AS revision, receipt_hash AS receiptHash,
|
|
234
|
+
error_hash AS errorHash
|
|
235
|
+
FROM host_commands WHERE client_id = ? AND idempotency_key = ?`,
|
|
236
|
+
)
|
|
237
|
+
.get(request.clientId, request.idempotencyKey);
|
|
238
|
+
if (!isIdempotentCommandRow(idempotent)) return undefined;
|
|
239
|
+
if (!idempotent.clientRequestFingerprint.equals(clientRequestFingerprint(request))) {
|
|
240
|
+
return conflictResponse(request.requestId, "Idempotency key was reused with another payload");
|
|
225
241
|
}
|
|
242
|
+
const adopted = this.commandResponse(idempotent.requestId, idempotent);
|
|
243
|
+
return {
|
|
244
|
+
...adopted,
|
|
245
|
+
requestId: request.requestId,
|
|
246
|
+
...(adopted.outcome === "accepted" || adopted.outcome === "adopted"
|
|
247
|
+
? { outcome: "adopted" as const }
|
|
248
|
+
: {}),
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
executeCommand(
|
|
253
|
+
request: ClientRequest,
|
|
254
|
+
hostEpoch: number,
|
|
255
|
+
operation: () => Omit<ClientResponse, "schema" | "type" | "requestId">,
|
|
256
|
+
): ClientResponse {
|
|
257
|
+
const existing = this.adoptCommand(request);
|
|
258
|
+
if (existing !== undefined) return existing;
|
|
226
259
|
return this.state.transaction(() => {
|
|
227
|
-
const idempotent = this.
|
|
228
|
-
|
|
229
|
-
`SELECT request_id AS requestId, request_fingerprint AS requestFingerprint,
|
|
230
|
-
outcome, accepted_revision AS revision, receipt_hash AS receiptHash,
|
|
231
|
-
error_hash AS errorHash
|
|
232
|
-
FROM host_commands WHERE client_id = ? AND idempotency_key = ?`,
|
|
233
|
-
)
|
|
234
|
-
.get(request.clientId, request.idempotencyKey);
|
|
235
|
-
if (isIdempotentCommandRow(idempotent)) {
|
|
236
|
-
if (!idempotent.requestFingerprint.equals(requestFingerprint(request))) {
|
|
237
|
-
return conflictResponse(
|
|
238
|
-
request.requestId,
|
|
239
|
-
"Idempotency key was reused with another payload",
|
|
240
|
-
);
|
|
241
|
-
}
|
|
242
|
-
const adopted = this.commandResponse(idempotent.requestId, idempotent);
|
|
243
|
-
return { ...adopted, requestId: request.requestId, outcome: "adopted" };
|
|
244
|
-
}
|
|
260
|
+
const idempotent = this.adoptCommand(request);
|
|
261
|
+
if (idempotent !== undefined) return idempotent;
|
|
245
262
|
|
|
246
263
|
const result = operation();
|
|
247
|
-
const response:
|
|
248
|
-
schema:
|
|
264
|
+
const response: ClientResponse = {
|
|
265
|
+
schema: CLIENT_PROTOCOL_SCHEMA,
|
|
266
|
+
type: "response",
|
|
249
267
|
requestId: request.requestId,
|
|
250
268
|
...result,
|
|
251
269
|
};
|
|
@@ -267,7 +285,7 @@ export class HostStateStore {
|
|
|
267
285
|
request.clientId,
|
|
268
286
|
request.operation,
|
|
269
287
|
request.idempotencyKey,
|
|
270
|
-
|
|
288
|
+
clientRequestFingerprint(request),
|
|
271
289
|
request.runId ?? null,
|
|
272
290
|
response.revision ?? null,
|
|
273
291
|
response.outcome,
|
|
@@ -350,13 +368,13 @@ export class HostStateStore {
|
|
|
350
368
|
readWorkerMessage(message: WorkerMessage): WorkerResponse | undefined {
|
|
351
369
|
const row = this.state.connection
|
|
352
370
|
.prepare(
|
|
353
|
-
`SELECT request_fingerprint AS
|
|
371
|
+
`SELECT request_fingerprint AS clientRequestFingerprint, outcome,
|
|
354
372
|
accepted_revision AS revision, result_hash AS resultHash, error_hash AS errorHash
|
|
355
373
|
FROM worker_messages WHERE worker_epoch = ? AND message_id = ?`,
|
|
356
374
|
)
|
|
357
375
|
.get(message.workerEpoch, message.messageId);
|
|
358
376
|
if (!isWorkerMessageRow(row)) return undefined;
|
|
359
|
-
if (!row.
|
|
377
|
+
if (!row.clientRequestFingerprint.equals(workerMessageFingerprint(message))) {
|
|
360
378
|
return {
|
|
361
379
|
schema: "pi-workflows.worker-response.v1",
|
|
362
380
|
messageId: message.messageId,
|
|
@@ -726,6 +744,7 @@ export class HostStateStore {
|
|
|
726
744
|
receipt?: JsonValue;
|
|
727
745
|
}): {
|
|
728
746
|
interaction: InteractiveRequestRecord;
|
|
747
|
+
submissionId: string;
|
|
729
748
|
outcome: "accepted" | "adopted";
|
|
730
749
|
receipt: JsonValue;
|
|
731
750
|
} {
|
|
@@ -745,6 +764,7 @@ export class HostStateStore {
|
|
|
745
764
|
receipt?: JsonValue;
|
|
746
765
|
}): {
|
|
747
766
|
interaction: InteractiveRequestRecord;
|
|
767
|
+
submissionId: string;
|
|
748
768
|
outcome: "accepted" | "adopted";
|
|
749
769
|
receipt: JsonValue;
|
|
750
770
|
} {
|
|
@@ -766,6 +786,7 @@ export class HostStateStore {
|
|
|
766
786
|
receipt?: JsonValue;
|
|
767
787
|
}): {
|
|
768
788
|
interaction: InteractiveRequestRecord;
|
|
789
|
+
submissionId: string;
|
|
769
790
|
outcome: "accepted" | "adopted";
|
|
770
791
|
receipt: JsonValue;
|
|
771
792
|
} {
|
|
@@ -785,6 +806,7 @@ export class HostStateStore {
|
|
|
785
806
|
}
|
|
786
807
|
return {
|
|
787
808
|
interaction: this.requireInteractiveRequest(options.requestId),
|
|
809
|
+
submissionId: existing.submissionId,
|
|
788
810
|
outcome: "adopted",
|
|
789
811
|
receipt:
|
|
790
812
|
existing.receiptHash === null
|
|
@@ -843,6 +865,7 @@ export class HostStateStore {
|
|
|
843
865
|
}
|
|
844
866
|
return {
|
|
845
867
|
interaction: this.requireInteractiveRequest(options.requestId),
|
|
868
|
+
submissionId: options.submissionId,
|
|
846
869
|
outcome: "accepted",
|
|
847
870
|
receipt: options.receipt ?? {
|
|
848
871
|
requestId: options.requestId,
|
|
@@ -865,14 +888,15 @@ export class HostStateStore {
|
|
|
865
888
|
return row;
|
|
866
889
|
}
|
|
867
890
|
|
|
868
|
-
private commandResponse(requestId: string, row: CommandRow):
|
|
891
|
+
private commandResponse(requestId: string, row: CommandRow): ClientResponse {
|
|
869
892
|
const receipt = row.receiptHash === null ? undefined : this.state.readJson(row.receiptHash);
|
|
870
893
|
const error =
|
|
871
894
|
row.errorHash === null
|
|
872
895
|
? undefined
|
|
873
896
|
: this.state.readBlob(row.errorHash)?.content.toString("utf8");
|
|
874
897
|
return {
|
|
875
|
-
schema:
|
|
898
|
+
schema: CLIENT_PROTOCOL_SCHEMA,
|
|
899
|
+
type: "response",
|
|
876
900
|
requestId,
|
|
877
901
|
outcome: row.outcome,
|
|
878
902
|
...(row.revision === null ? {} : { revision: row.revision }),
|
|
@@ -886,7 +910,9 @@ export class HostStateStore {
|
|
|
886
910
|
.prepare(
|
|
887
911
|
`SELECT request_id AS requestId, run_id AS runId, attempt_id AS attemptId,
|
|
888
912
|
target_session_id AS targetSessionId, kind, contract_hash AS contractHash,
|
|
889
|
-
revision, status,
|
|
913
|
+
revision, status, presenter_id AS presenterId,
|
|
914
|
+
presentation_claim_expires_at AS presentationClaimExpiresAt,
|
|
915
|
+
presentation_session_entry_id AS presentationSessionEntryId,
|
|
890
916
|
accepted_submission_id AS acceptedSubmissionId, created_at AS createdAt,
|
|
891
917
|
updated_at AS updatedAt, settled_at AS settledAt, consumed_at AS consumedAt
|
|
892
918
|
FROM interactive_requests WHERE request_id = ?`,
|
|
@@ -902,6 +928,8 @@ export class HostStateStore {
|
|
|
902
928
|
contract: this.state.readJson(row.contractHash),
|
|
903
929
|
revision: row.revision,
|
|
904
930
|
status: row.status,
|
|
931
|
+
presenterId: row.presenterId,
|
|
932
|
+
presentationClaimExpiresAt: iso(row.presentationClaimExpiresAt),
|
|
905
933
|
presentationSessionEntryId: row.presentationSessionEntryId,
|
|
906
934
|
acceptedSubmissionId: row.acceptedSubmissionId,
|
|
907
935
|
createdAt: new Date(row.createdAt).toISOString(),
|
|
@@ -918,8 +946,14 @@ export class HostStateStore {
|
|
|
918
946
|
}
|
|
919
947
|
}
|
|
920
948
|
|
|
921
|
-
function conflictResponse(requestId: string, error: string):
|
|
922
|
-
return {
|
|
949
|
+
function conflictResponse(requestId: string, error: string): ClientResponse {
|
|
950
|
+
return {
|
|
951
|
+
schema: CLIENT_PROTOCOL_SCHEMA,
|
|
952
|
+
type: "response",
|
|
953
|
+
requestId,
|
|
954
|
+
outcome: "conflict",
|
|
955
|
+
error,
|
|
956
|
+
};
|
|
923
957
|
}
|
|
924
958
|
|
|
925
959
|
function requireLeaseMs(value: number): void {
|
|
@@ -943,8 +977,8 @@ type HostRow = {
|
|
|
943
977
|
};
|
|
944
978
|
|
|
945
979
|
type CommandRow = {
|
|
946
|
-
|
|
947
|
-
outcome:
|
|
980
|
+
clientRequestFingerprint: Buffer;
|
|
981
|
+
outcome: ClientResponse["outcome"];
|
|
948
982
|
revision: number | null;
|
|
949
983
|
receiptHash: Buffer | null;
|
|
950
984
|
errorHash: Buffer | null;
|
|
@@ -970,7 +1004,7 @@ type SubmissionDetailRow = {
|
|
|
970
1004
|
submittedAt: number;
|
|
971
1005
|
};
|
|
972
1006
|
type WorkerMessageRow = {
|
|
973
|
-
|
|
1007
|
+
clientRequestFingerprint: Buffer;
|
|
974
1008
|
outcome: WorkerResponse["outcome"];
|
|
975
1009
|
revision: number | null;
|
|
976
1010
|
resultHash: Buffer | null;
|
|
@@ -997,6 +1031,8 @@ type InteractiveRequestRow = {
|
|
|
997
1031
|
contractHash: Buffer;
|
|
998
1032
|
revision: number;
|
|
999
1033
|
status: InteractiveRequestRecord["status"];
|
|
1034
|
+
presenterId: string | null;
|
|
1035
|
+
presentationClaimExpiresAt: number | null;
|
|
1000
1036
|
presentationSessionEntryId: string | null;
|
|
1001
1037
|
acceptedSubmissionId: string | null;
|
|
1002
1038
|
createdAt: number;
|
|
@@ -1022,7 +1058,7 @@ function isHostRow(value: unknown): value is HostRow {
|
|
|
1022
1058
|
function isCommandRow(value: unknown): value is CommandRow {
|
|
1023
1059
|
return (
|
|
1024
1060
|
isRecord(value) &&
|
|
1025
|
-
Buffer.isBuffer(value.
|
|
1061
|
+
Buffer.isBuffer(value.clientRequestFingerprint) &&
|
|
1026
1062
|
typeof value.outcome === "string" &&
|
|
1027
1063
|
nullableNumber(value.revision) &&
|
|
1028
1064
|
(value.receiptHash === null || Buffer.isBuffer(value.receiptHash)) &&
|
|
@@ -1049,7 +1085,7 @@ function isRunIdRow(value: unknown): value is RunIdRow {
|
|
|
1049
1085
|
function isWorkerMessageRow(value: unknown): value is WorkerMessageRow {
|
|
1050
1086
|
return (
|
|
1051
1087
|
isRecord(value) &&
|
|
1052
|
-
Buffer.isBuffer(value.
|
|
1088
|
+
Buffer.isBuffer(value.clientRequestFingerprint) &&
|
|
1053
1089
|
["accepted", "adopted", "rejected", "claimLost"].includes(value.outcome as string) &&
|
|
1054
1090
|
nullableNumber(value.revision) &&
|
|
1055
1091
|
(value.resultHash === null || Buffer.isBuffer(value.resultHash)) &&
|
|
@@ -1115,6 +1151,8 @@ function isInteractiveRequestRow(value: unknown): value is InteractiveRequestRow
|
|
|
1115
1151
|
Buffer.isBuffer(value.contractHash) &&
|
|
1116
1152
|
typeof value.revision === "number" &&
|
|
1117
1153
|
["pending", "presenting", "settled", "cancelled"].includes(value.status as string) &&
|
|
1154
|
+
nullableString(value.presenterId) &&
|
|
1155
|
+
nullableNumber(value.presentationClaimExpiresAt) &&
|
|
1118
1156
|
nullableString(value.presentationSessionEntryId) &&
|
|
1119
1157
|
nullableString(value.acceptedSubmissionId) &&
|
|
1120
1158
|
typeof value.createdAt === "number" &&
|