@osolmaz/pi-workflows 0.15.3 → 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 +43 -0
- package/dist/controllers/sqlite.js +137 -2
- package/dist/controllers/sqlite.js.map +1 -1
- package/dist/extension/index.d.ts +1 -0
- package/dist/extension/index.js +278 -183
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/session-view.d.ts +7 -2
- package/dist/extension/session-view.js +60 -52
- package/dist/extension/session-view.js.map +1 -1
- package/dist/extension/widget.d.ts +2 -1
- package/dist/extension/widget.js +14 -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 +11 -0
- package/dist/host/runner.js +473 -17
- package/dist/host/runner.js.map +1 -1
- package/dist/host/state.d.ts +6 -3
- package/dist/host/state.js +52 -26
- 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-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 +74 -61
- 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 +44 -1
- 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 +213 -2
- package/src/extension/index.ts +349 -208
- package/src/extension/session-view.ts +73 -50
- package/src/extension/widget.ts +16 -8
- package/src/host/child-worker-supervisor.ts +1 -1
- package/src/host/resolver-entry.ts +11 -26
- package/src/host/runner.ts +648 -48
- package/src/host/state.ts +71 -43
- 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 = {
|
|
@@ -201,53 +205,65 @@ export class HostStateStore {
|
|
|
201
205
|
};
|
|
202
206
|
}
|
|
203
207
|
|
|
204
|
-
readCommand(request:
|
|
208
|
+
readCommand(request: ClientRequest): ClientResponse | undefined {
|
|
205
209
|
const row = this.state.connection
|
|
206
210
|
.prepare(
|
|
207
|
-
`SELECT request_fingerprint AS
|
|
211
|
+
`SELECT request_fingerprint AS clientRequestFingerprint, outcome, accepted_revision AS revision,
|
|
208
212
|
receipt_hash AS receiptHash, error_hash AS errorHash
|
|
209
213
|
FROM host_commands WHERE request_id = ?`,
|
|
210
214
|
)
|
|
211
215
|
.get(request.requestId);
|
|
212
216
|
if (!isCommandRow(row)) return undefined;
|
|
213
|
-
if (!row.
|
|
217
|
+
if (!row.clientRequestFingerprint.equals(clientRequestFingerprint(request))) {
|
|
214
218
|
return conflictResponse(request.requestId, "Request ID was reused with another payload");
|
|
215
219
|
}
|
|
216
220
|
return this.commandResponse(request.requestId, row);
|
|
217
221
|
}
|
|
218
222
|
|
|
219
|
-
|
|
220
|
-
request: HostRequest,
|
|
221
|
-
hostEpoch: number,
|
|
222
|
-
operation: () => Omit<HostResponse, "schema" | "requestId">,
|
|
223
|
-
): HostResponse {
|
|
223
|
+
adoptCommand(request: ClientRequest): ClientResponse | undefined {
|
|
224
224
|
const existing = this.readCommand(request);
|
|
225
225
|
if (existing !== undefined) {
|
|
226
|
-
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");
|
|
227
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;
|
|
228
259
|
return this.state.transaction(() => {
|
|
229
|
-
const idempotent = this.
|
|
230
|
-
|
|
231
|
-
`SELECT request_id AS requestId, request_fingerprint AS requestFingerprint,
|
|
232
|
-
outcome, accepted_revision AS revision, receipt_hash AS receiptHash,
|
|
233
|
-
error_hash AS errorHash
|
|
234
|
-
FROM host_commands WHERE client_id = ? AND idempotency_key = ?`,
|
|
235
|
-
)
|
|
236
|
-
.get(request.clientId, request.idempotencyKey);
|
|
237
|
-
if (isIdempotentCommandRow(idempotent)) {
|
|
238
|
-
if (!idempotent.requestFingerprint.equals(requestFingerprint(request))) {
|
|
239
|
-
return conflictResponse(
|
|
240
|
-
request.requestId,
|
|
241
|
-
"Idempotency key was reused with another payload",
|
|
242
|
-
);
|
|
243
|
-
}
|
|
244
|
-
const adopted = this.commandResponse(idempotent.requestId, idempotent);
|
|
245
|
-
return { ...adopted, requestId: request.requestId, outcome: "adopted" };
|
|
246
|
-
}
|
|
260
|
+
const idempotent = this.adoptCommand(request);
|
|
261
|
+
if (idempotent !== undefined) return idempotent;
|
|
247
262
|
|
|
248
263
|
const result = operation();
|
|
249
|
-
const response:
|
|
250
|
-
schema:
|
|
264
|
+
const response: ClientResponse = {
|
|
265
|
+
schema: CLIENT_PROTOCOL_SCHEMA,
|
|
266
|
+
type: "response",
|
|
251
267
|
requestId: request.requestId,
|
|
252
268
|
...result,
|
|
253
269
|
};
|
|
@@ -269,7 +285,7 @@ export class HostStateStore {
|
|
|
269
285
|
request.clientId,
|
|
270
286
|
request.operation,
|
|
271
287
|
request.idempotencyKey,
|
|
272
|
-
|
|
288
|
+
clientRequestFingerprint(request),
|
|
273
289
|
request.runId ?? null,
|
|
274
290
|
response.revision ?? null,
|
|
275
291
|
response.outcome,
|
|
@@ -352,13 +368,13 @@ export class HostStateStore {
|
|
|
352
368
|
readWorkerMessage(message: WorkerMessage): WorkerResponse | undefined {
|
|
353
369
|
const row = this.state.connection
|
|
354
370
|
.prepare(
|
|
355
|
-
`SELECT request_fingerprint AS
|
|
371
|
+
`SELECT request_fingerprint AS clientRequestFingerprint, outcome,
|
|
356
372
|
accepted_revision AS revision, result_hash AS resultHash, error_hash AS errorHash
|
|
357
373
|
FROM worker_messages WHERE worker_epoch = ? AND message_id = ?`,
|
|
358
374
|
)
|
|
359
375
|
.get(message.workerEpoch, message.messageId);
|
|
360
376
|
if (!isWorkerMessageRow(row)) return undefined;
|
|
361
|
-
if (!row.
|
|
377
|
+
if (!row.clientRequestFingerprint.equals(workerMessageFingerprint(message))) {
|
|
362
378
|
return {
|
|
363
379
|
schema: "pi-workflows.worker-response.v1",
|
|
364
380
|
messageId: message.messageId,
|
|
@@ -728,6 +744,7 @@ export class HostStateStore {
|
|
|
728
744
|
receipt?: JsonValue;
|
|
729
745
|
}): {
|
|
730
746
|
interaction: InteractiveRequestRecord;
|
|
747
|
+
submissionId: string;
|
|
731
748
|
outcome: "accepted" | "adopted";
|
|
732
749
|
receipt: JsonValue;
|
|
733
750
|
} {
|
|
@@ -747,6 +764,7 @@ export class HostStateStore {
|
|
|
747
764
|
receipt?: JsonValue;
|
|
748
765
|
}): {
|
|
749
766
|
interaction: InteractiveRequestRecord;
|
|
767
|
+
submissionId: string;
|
|
750
768
|
outcome: "accepted" | "adopted";
|
|
751
769
|
receipt: JsonValue;
|
|
752
770
|
} {
|
|
@@ -768,6 +786,7 @@ export class HostStateStore {
|
|
|
768
786
|
receipt?: JsonValue;
|
|
769
787
|
}): {
|
|
770
788
|
interaction: InteractiveRequestRecord;
|
|
789
|
+
submissionId: string;
|
|
771
790
|
outcome: "accepted" | "adopted";
|
|
772
791
|
receipt: JsonValue;
|
|
773
792
|
} {
|
|
@@ -787,6 +806,7 @@ export class HostStateStore {
|
|
|
787
806
|
}
|
|
788
807
|
return {
|
|
789
808
|
interaction: this.requireInteractiveRequest(options.requestId),
|
|
809
|
+
submissionId: existing.submissionId,
|
|
790
810
|
outcome: "adopted",
|
|
791
811
|
receipt:
|
|
792
812
|
existing.receiptHash === null
|
|
@@ -845,6 +865,7 @@ export class HostStateStore {
|
|
|
845
865
|
}
|
|
846
866
|
return {
|
|
847
867
|
interaction: this.requireInteractiveRequest(options.requestId),
|
|
868
|
+
submissionId: options.submissionId,
|
|
848
869
|
outcome: "accepted",
|
|
849
870
|
receipt: options.receipt ?? {
|
|
850
871
|
requestId: options.requestId,
|
|
@@ -867,14 +888,15 @@ export class HostStateStore {
|
|
|
867
888
|
return row;
|
|
868
889
|
}
|
|
869
890
|
|
|
870
|
-
private commandResponse(requestId: string, row: CommandRow):
|
|
891
|
+
private commandResponse(requestId: string, row: CommandRow): ClientResponse {
|
|
871
892
|
const receipt = row.receiptHash === null ? undefined : this.state.readJson(row.receiptHash);
|
|
872
893
|
const error =
|
|
873
894
|
row.errorHash === null
|
|
874
895
|
? undefined
|
|
875
896
|
: this.state.readBlob(row.errorHash)?.content.toString("utf8");
|
|
876
897
|
return {
|
|
877
|
-
schema:
|
|
898
|
+
schema: CLIENT_PROTOCOL_SCHEMA,
|
|
899
|
+
type: "response",
|
|
878
900
|
requestId,
|
|
879
901
|
outcome: row.outcome,
|
|
880
902
|
...(row.revision === null ? {} : { revision: row.revision }),
|
|
@@ -924,8 +946,14 @@ export class HostStateStore {
|
|
|
924
946
|
}
|
|
925
947
|
}
|
|
926
948
|
|
|
927
|
-
function conflictResponse(requestId: string, error: string):
|
|
928
|
-
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
|
+
};
|
|
929
957
|
}
|
|
930
958
|
|
|
931
959
|
function requireLeaseMs(value: number): void {
|
|
@@ -949,8 +977,8 @@ type HostRow = {
|
|
|
949
977
|
};
|
|
950
978
|
|
|
951
979
|
type CommandRow = {
|
|
952
|
-
|
|
953
|
-
outcome:
|
|
980
|
+
clientRequestFingerprint: Buffer;
|
|
981
|
+
outcome: ClientResponse["outcome"];
|
|
954
982
|
revision: number | null;
|
|
955
983
|
receiptHash: Buffer | null;
|
|
956
984
|
errorHash: Buffer | null;
|
|
@@ -976,7 +1004,7 @@ type SubmissionDetailRow = {
|
|
|
976
1004
|
submittedAt: number;
|
|
977
1005
|
};
|
|
978
1006
|
type WorkerMessageRow = {
|
|
979
|
-
|
|
1007
|
+
clientRequestFingerprint: Buffer;
|
|
980
1008
|
outcome: WorkerResponse["outcome"];
|
|
981
1009
|
revision: number | null;
|
|
982
1010
|
resultHash: Buffer | null;
|
|
@@ -1030,7 +1058,7 @@ function isHostRow(value: unknown): value is HostRow {
|
|
|
1030
1058
|
function isCommandRow(value: unknown): value is CommandRow {
|
|
1031
1059
|
return (
|
|
1032
1060
|
isRecord(value) &&
|
|
1033
|
-
Buffer.isBuffer(value.
|
|
1061
|
+
Buffer.isBuffer(value.clientRequestFingerprint) &&
|
|
1034
1062
|
typeof value.outcome === "string" &&
|
|
1035
1063
|
nullableNumber(value.revision) &&
|
|
1036
1064
|
(value.receiptHash === null || Buffer.isBuffer(value.receiptHash)) &&
|
|
@@ -1057,7 +1085,7 @@ function isRunIdRow(value: unknown): value is RunIdRow {
|
|
|
1057
1085
|
function isWorkerMessageRow(value: unknown): value is WorkerMessageRow {
|
|
1058
1086
|
return (
|
|
1059
1087
|
isRecord(value) &&
|
|
1060
|
-
Buffer.isBuffer(value.
|
|
1088
|
+
Buffer.isBuffer(value.clientRequestFingerprint) &&
|
|
1061
1089
|
["accepted", "adopted", "rejected", "claimLost"].includes(value.outcome as string) &&
|
|
1062
1090
|
nullableNumber(value.revision) &&
|
|
1063
1091
|
(value.resultHash === null || Buffer.isBuffer(value.resultHash)) &&
|