@osolmaz/pi-workflows 0.13.4 → 0.15.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 +146 -160
- package/dist/builtins/autoimplement.workflow.js +3 -1
- package/dist/builtins/autoimplement.workflow.js.map +1 -1
- package/dist/builtins/change-verification.workflow.js +7 -2
- package/dist/builtins/change-verification.workflow.js.map +1 -1
- package/dist/builtins/metadata.d.ts +22 -0
- package/dist/builtins/metadata.js +10 -0
- package/dist/builtins/metadata.js.map +1 -0
- package/dist/builtins/monitor.workflow.js +4 -1
- package/dist/builtins/monitor.workflow.js.map +1 -1
- package/dist/builtins/sanity-check.workflow.js +4 -1
- package/dist/builtins/sanity-check.workflow.js.map +1 -1
- package/dist/builtins/workspace-preparation.workflow.js +3 -1
- package/dist/builtins/workspace-preparation.workflow.js.map +1 -1
- package/dist/controllers/index.d.ts +1 -2
- package/dist/controllers/index.js +0 -1
- package/dist/controllers/index.js.map +1 -1
- package/dist/controllers/sqlite.d.ts +85 -31
- package/dist/controllers/sqlite.js +541 -126
- package/dist/controllers/sqlite.js.map +1 -1
- package/dist/controllers/store.d.ts +1 -0
- package/dist/extension/controller-command.d.ts +22 -0
- package/dist/extension/controller-command.js +32 -0
- package/dist/extension/controller-command.js.map +1 -0
- package/dist/extension/index.d.ts +2 -9
- package/dist/extension/index.js +876 -2928
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/message-card.d.ts +13 -0
- package/dist/extension/message-card.js +46 -0
- package/dist/extension/message-card.js.map +1 -0
- package/dist/extension/step-message.d.ts +5 -6
- package/dist/extension/step-message.js +41 -52
- package/dist/extension/step-message.js.map +1 -1
- package/dist/host/child-worker-supervisor.d.ts +50 -0
- package/dist/host/child-worker-supervisor.js +159 -0
- package/dist/host/child-worker-supervisor.js.map +1 -0
- package/dist/host/client.d.ts +48 -0
- package/dist/host/client.js +216 -0
- package/dist/host/client.js.map +1 -0
- package/dist/host/controller-worker-entry.d.ts +2 -0
- package/dist/host/controller-worker-entry.js +244 -0
- package/dist/host/controller-worker-entry.js.map +1 -0
- package/dist/host/controller-worker-protocol.d.ts +36 -0
- package/dist/host/controller-worker-protocol.js +49 -0
- package/dist/host/controller-worker-protocol.js.map +1 -0
- package/dist/host/controller-worker-supervisor.d.ts +21 -0
- package/dist/host/controller-worker-supervisor.js +54 -0
- package/dist/host/controller-worker-supervisor.js.map +1 -0
- package/dist/host/host-entry.d.ts +2 -0
- package/dist/host/host-entry.js +23 -0
- package/dist/host/host-entry.js.map +1 -0
- package/dist/host/processes.d.ts +17 -12
- package/dist/host/processes.js +154 -50
- package/dist/host/processes.js.map +1 -1
- package/dist/host/protocol.d.ts +38 -0
- package/dist/host/protocol.js +156 -0
- package/dist/host/protocol.js.map +1 -0
- package/dist/host/resolver-entry.d.ts +49 -0
- package/dist/host/resolver-entry.js +149 -0
- package/dist/host/resolver-entry.js.map +1 -0
- package/dist/host/rpc-executor.d.ts +14 -3
- package/dist/host/rpc-executor.js +63 -30
- package/dist/host/rpc-executor.js.map +1 -1
- package/dist/host/runner.d.ts +95 -29
- package/dist/host/runner.js +2311 -348
- package/dist/host/runner.js.map +1 -1
- package/dist/host/state.d.ts +174 -0
- package/dist/host/state.js +689 -0
- package/dist/host/state.js.map +1 -0
- package/dist/host/worker-entry.d.ts +10 -0
- package/dist/host/worker-entry.js +446 -0
- package/dist/host/worker-entry.js.map +1 -0
- package/dist/host/worker-protocol.d.ts +31 -0
- package/dist/host/worker-protocol.js +122 -0
- package/dist/host/worker-protocol.js.map +1 -0
- package/dist/host/worker-store.d.ts +80 -0
- package/dist/host/worker-store.js +91 -0
- package/dist/host/worker-store.js.map +1 -0
- package/dist/host/worker-supervisor.d.ts +22 -0
- package/dist/host/worker-supervisor.js +54 -0
- package/dist/host/worker-supervisor.js.map +1 -0
- package/dist/render/canvas.d.ts +2 -1
- package/dist/render/canvas.js +40 -15
- package/dist/render/canvas.js.map +1 -1
- package/dist/render/graph-render.d.ts +7 -3
- package/dist/render/graph-render.js +125 -74
- package/dist/render/graph-render.js.map +1 -1
- package/dist/state/database.js +3 -1
- package/dist/state/database.js.map +1 -1
- package/dist/state/index.d.ts +1 -0
- package/dist/state/index.js +1 -0
- package/dist/state/index.js.map +1 -1
- package/dist/state/prune.js +36 -10
- package/dist/state/prune.js.map +1 -1
- package/dist/state/schema.d.ts +1 -1
- package/dist/state/schema.js +171 -3
- package/dist/state/schema.js.map +1 -1
- package/dist/state/viewer.d.ts +46 -0
- package/dist/state/viewer.js +249 -0
- package/dist/state/viewer.js.map +1 -0
- package/dist/viewer/cli.d.ts +1 -1
- package/dist/viewer/cli.js +47 -18
- package/dist/viewer/cli.js.map +1 -1
- package/dist/workflows/composition.js +25 -2
- package/dist/workflows/composition.js.map +1 -1
- package/dist/workflows/definition.d.ts +3 -1
- package/dist/workflows/definition.js +25 -0
- package/dist/workflows/definition.js.map +1 -1
- package/dist/workflows/engine.d.ts +1 -0
- package/dist/workflows/engine.js +157 -42
- package/dist/workflows/engine.js.map +1 -1
- package/dist/workflows/errors.d.ts +3 -1
- package/dist/workflows/errors.js +4 -7
- package/dist/workflows/errors.js.map +1 -1
- package/dist/workflows/human-decision.d.ts +3 -0
- package/dist/workflows/human-decision.js +31 -0
- package/dist/workflows/human-decision.js.map +1 -1
- package/dist/workflows/index.d.ts +1 -1
- package/dist/workflows/index.js +1 -1
- package/dist/workflows/index.js.map +1 -1
- package/dist/workflows/schema.js +17 -6
- package/dist/workflows/schema.js.map +1 -1
- package/dist/{viewer → workflows}/session-reducer.d.ts +3 -1
- package/dist/{viewer → workflows}/session-reducer.js +4 -0
- package/dist/workflows/session-reducer.js.map +1 -0
- package/dist/workflows/store.d.ts +87 -1
- package/dist/workflows/store.js +892 -93
- package/dist/workflows/store.js.map +1 -1
- package/dist/workflows/tool-input.d.ts +0 -22
- package/dist/workflows/tool-input.js +1 -44
- package/dist/workflows/tool-input.js.map +1 -1
- package/dist/workflows/types.d.ts +36 -4
- package/docs/2026-08-25-workflow-follow-ups.md +8 -6
- package/docs/2026-08-30-out-of-process-workflow-host-plan.md +353 -0
- package/docs/CONTROLLERS.md +11 -11
- package/docs/DEFERRED_TURNS.md +61 -27
- package/docs/HUMAN_DECISIONS.md +12 -4
- package/docs/SQLITE_STATE.md +42 -8
- package/docs/WORKFLOW_HOST.md +452 -0
- package/docs/development.md +46 -30
- package/docs/live-replay-protocol.md +129 -100
- package/docs/plans/2026-08-19-human-decision-gates-plan.md +34 -8
- package/docs/plans/2026-08-27-workflow-terminal-restart-plan.md +399 -0
- package/docs/plans/2026-08-28-piw-incremental-viewer-plan.md +293 -0
- package/docs/plans/piw-viewer-experience-implementation-plan.md +11 -1
- package/docs/plans/tui-viewer-implementation-plan.md +5 -0
- package/docs/tui-viewer.md +18 -3
- package/docs/workflows.md +164 -156
- package/examples/workflows/command-batch.workflow.ts +2 -0
- package/examples/workflows/shell.workflow.ts +2 -1
- package/herdr-plugin.toml +1 -1
- package/package.json +1 -1
- package/skills/autodoc/SKILL.md +1 -1
- package/skills/autoimplement/SKILL.md +1 -1
- package/skills/autoplan/SKILL.md +1 -1
- package/skills/pi-workflows/SKILL.md +2 -0
- package/src/builtins/autoimplement.workflow.ts +3 -0
- package/src/builtins/change-verification.workflow.ts +7 -2
- package/src/builtins/metadata.ts +9 -0
- package/src/builtins/monitor.workflow.ts +4 -0
- package/src/builtins/sanity-check.workflow.ts +4 -0
- package/src/builtins/workspace-preparation.workflow.ts +3 -1
- package/src/controllers/index.ts +3 -5
- package/src/controllers/sqlite.ts +929 -225
- package/src/controllers/store.ts +1 -0
- package/src/extension/controller-command.ts +45 -0
- package/src/extension/index.ts +948 -3506
- package/src/extension/message-card.ts +61 -0
- package/src/extension/step-message.ts +58 -63
- package/src/host/child-worker-supervisor.ts +183 -0
- package/src/host/client.ts +293 -0
- package/src/host/controller-worker-entry.ts +311 -0
- package/src/host/controller-worker-protocol.ts +104 -0
- package/src/host/controller-worker-supervisor.ts +79 -0
- package/src/host/host-entry.ts +23 -0
- package/src/host/processes.ts +171 -54
- package/src/host/protocol.ts +196 -0
- package/src/host/resolver-entry.ts +241 -0
- package/src/host/rpc-executor.ts +76 -34
- package/src/host/runner.ts +2813 -422
- package/src/host/state.ts +1160 -0
- package/src/host/worker-entry.ts +533 -0
- package/src/host/worker-protocol.ts +165 -0
- package/src/host/worker-store.ts +229 -0
- package/src/host/worker-supervisor.ts +74 -0
- package/src/render/canvas.ts +44 -10
- package/src/render/graph-render.ts +145 -90
- package/src/state/database.ts +2 -1
- package/src/state/index.ts +14 -0
- package/src/state/prune.ts +35 -9
- package/src/state/schema.ts +171 -3
- package/src/state/viewer.ts +356 -0
- package/src/viewer/cli.ts +49 -17
- package/src/workflows/composition.ts +36 -2
- package/src/workflows/definition.ts +32 -0
- package/src/workflows/engine.ts +157 -54
- package/src/workflows/errors.ts +11 -2
- package/src/workflows/human-decision.ts +49 -0
- package/src/workflows/index.ts +2 -0
- package/src/workflows/schema.ts +19 -6
- package/src/{viewer → workflows}/session-reducer.ts +11 -1
- package/src/workflows/store.ts +1316 -108
- package/src/workflows/tool-input.ts +2 -60
- package/src/workflows/types.ts +32 -4
- package/dist/controllers/workflow-engine-scheduler.d.ts +0 -29
- package/dist/controllers/workflow-engine-scheduler.js +0 -175
- package/dist/controllers/workflow-engine-scheduler.js.map +0 -1
- package/dist/extension/controller-host.d.ts +0 -48
- package/dist/extension/controller-host.js +0 -110
- package/dist/extension/controller-host.js.map +0 -1
- package/dist/extension/deferred-turn-coordinator.d.ts +0 -32
- package/dist/extension/deferred-turn-coordinator.js +0 -143
- package/dist/extension/deferred-turn-coordinator.js.map +0 -1
- package/dist/extension/deferred-turn.d.ts +0 -44
- package/dist/extension/deferred-turn.js +0 -110
- package/dist/extension/deferred-turn.js.map +0 -1
- package/dist/extension/executor.d.ts +0 -86
- package/dist/extension/executor.js +0 -311
- package/dist/extension/executor.js.map +0 -1
- package/dist/extension/follow-up-coordinator.d.ts +0 -27
- package/dist/extension/follow-up-coordinator.js +0 -131
- package/dist/extension/follow-up-coordinator.js.map +0 -1
- package/dist/extension/recorder.d.ts +0 -84
- package/dist/extension/recorder.js +0 -528
- package/dist/extension/recorder.js.map +0 -1
- package/dist/extension/session-events.d.ts +0 -133
- package/dist/extension/session-events.js +0 -61
- package/dist/extension/session-events.js.map +0 -1
- package/dist/viewer/session-reducer.js.map +0 -1
- package/src/controllers/workflow-engine-scheduler.ts +0 -246
- package/src/extension/controller-host.ts +0 -167
- package/src/extension/deferred-turn-coordinator.ts +0 -171
- package/src/extension/deferred-turn.ts +0 -166
- package/src/extension/executor.ts +0 -411
- package/src/extension/follow-up-coordinator.ts +0 -151
- package/src/extension/recorder.ts +0 -655
- package/src/extension/session-events.ts +0 -121
package/src/state/schema.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { createHash } from "node:crypto";
|
|
|
3
3
|
export const STATE_APPLICATION_ID = 0x50495746;
|
|
4
4
|
export const STATE_SCHEMA_NAME = "pi-workflows-state";
|
|
5
5
|
export const STATE_SCHEMA_VERSION = 1;
|
|
6
|
-
export const STATE_APP_VERSION = "0.
|
|
6
|
+
export const STATE_APP_VERSION = "0.14.0";
|
|
7
7
|
|
|
8
8
|
export const STATE_SCHEMA_SQL = String.raw`
|
|
9
9
|
CREATE TABLE schema_meta (
|
|
@@ -22,6 +22,28 @@ CREATE TABLE projects (
|
|
|
22
22
|
created_at INTEGER NOT NULL
|
|
23
23
|
) STRICT;
|
|
24
24
|
|
|
25
|
+
CREATE TABLE workflow_host_state (
|
|
26
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
27
|
+
epoch INTEGER NOT NULL DEFAULT 0 CHECK (epoch >= 0),
|
|
28
|
+
host_id TEXT,
|
|
29
|
+
token_hash BLOB CHECK (token_hash IS NULL OR length(token_hash) = 32),
|
|
30
|
+
pid INTEGER CHECK (pid IS NULL OR pid > 0),
|
|
31
|
+
process_start_identity TEXT,
|
|
32
|
+
started_at INTEGER,
|
|
33
|
+
heartbeat_at INTEGER,
|
|
34
|
+
expires_at INTEGER,
|
|
35
|
+
CHECK (
|
|
36
|
+
(host_id IS NULL AND token_hash IS NULL AND pid IS NULL AND process_start_identity IS NULL
|
|
37
|
+
AND started_at IS NULL AND heartbeat_at IS NULL AND expires_at IS NULL)
|
|
38
|
+
OR
|
|
39
|
+
(host_id IS NOT NULL AND token_hash IS NOT NULL AND pid IS NOT NULL
|
|
40
|
+
AND process_start_identity IS NOT NULL AND started_at IS NOT NULL
|
|
41
|
+
AND heartbeat_at IS NOT NULL AND expires_at IS NOT NULL)
|
|
42
|
+
)
|
|
43
|
+
) STRICT;
|
|
44
|
+
|
|
45
|
+
INSERT INTO workflow_host_state(id, epoch) VALUES (1, 0);
|
|
46
|
+
|
|
25
47
|
CREATE TABLE blobs (
|
|
26
48
|
blob_hash BLOB PRIMARY KEY CHECK (length(blob_hash) = 32),
|
|
27
49
|
media_type TEXT NOT NULL,
|
|
@@ -115,6 +137,39 @@ CREATE INDEX runs_project_idx ON runs(project_id, created_at DESC);
|
|
|
115
137
|
CREATE INDEX runs_status_idx ON runs(status, updated_at DESC);
|
|
116
138
|
CREATE INDEX runs_parent_idx ON runs(parent_run_id);
|
|
117
139
|
|
|
140
|
+
CREATE TABLE viewer_runs (
|
|
141
|
+
run_id TEXT PRIMARY KEY REFERENCES runs(run_id) ON DELETE CASCADE,
|
|
142
|
+
presentation_revision INTEGER NOT NULL CHECK (presentation_revision >= 1),
|
|
143
|
+
retained_from_revision INTEGER NOT NULL CHECK (
|
|
144
|
+
retained_from_revision >= 1 AND retained_from_revision <= presentation_revision
|
|
145
|
+
),
|
|
146
|
+
updated_at INTEGER NOT NULL
|
|
147
|
+
) STRICT;
|
|
148
|
+
|
|
149
|
+
CREATE TABLE viewer_deltas (
|
|
150
|
+
run_id TEXT NOT NULL REFERENCES viewer_runs(run_id) ON DELETE CASCADE,
|
|
151
|
+
presentation_revision INTEGER NOT NULL CHECK (presentation_revision >= 1),
|
|
152
|
+
delta_index INTEGER NOT NULL CHECK (delta_index >= 0),
|
|
153
|
+
target_type TEXT NOT NULL CHECK (target_type IN (
|
|
154
|
+
'summary', 'graph', 'replay', 'timeline', 'conversation', 'inspector'
|
|
155
|
+
)),
|
|
156
|
+
target_key TEXT NOT NULL,
|
|
157
|
+
patch_hash BLOB NOT NULL REFERENCES blobs(blob_hash),
|
|
158
|
+
recorded_at INTEGER NOT NULL,
|
|
159
|
+
PRIMARY KEY (run_id, presentation_revision, delta_index)
|
|
160
|
+
) STRICT;
|
|
161
|
+
|
|
162
|
+
CREATE INDEX viewer_deltas_resume_idx
|
|
163
|
+
ON viewer_deltas(run_id, presentation_revision, delta_index);
|
|
164
|
+
|
|
165
|
+
CREATE TABLE viewer_session_checkpoints (
|
|
166
|
+
run_id TEXT NOT NULL REFERENCES viewer_runs(run_id) ON DELETE CASCADE,
|
|
167
|
+
event_seq INTEGER NOT NULL CHECK (event_seq > 0),
|
|
168
|
+
state_hash BLOB NOT NULL REFERENCES blobs(blob_hash),
|
|
169
|
+
recorded_at INTEGER NOT NULL,
|
|
170
|
+
PRIMARY KEY (run_id, event_seq)
|
|
171
|
+
) STRICT;
|
|
172
|
+
|
|
118
173
|
CREATE TABLE run_sources (
|
|
119
174
|
run_id TEXT NOT NULL REFERENCES runs(run_id) ON DELETE CASCADE,
|
|
120
175
|
mount_path TEXT NOT NULL,
|
|
@@ -240,6 +295,70 @@ CREATE INDEX run_queue_claim_idx ON run_queue(status, available_at, created_at);
|
|
|
240
295
|
CREATE UNIQUE INDEX run_queue_active_session_idx ON run_queue(origin_session_id)
|
|
241
296
|
WHERE status IN ('queued', 'starting', 'running');
|
|
242
297
|
|
|
298
|
+
CREATE TABLE host_commands (
|
|
299
|
+
request_id TEXT PRIMARY KEY,
|
|
300
|
+
client_id TEXT NOT NULL,
|
|
301
|
+
operation TEXT NOT NULL CHECK (operation IN (
|
|
302
|
+
'run.start', 'run.pause', 'run.resume', 'run.cancel', 'run.status', 'run.list',
|
|
303
|
+
'checkpoint.answer', 'decision.answer', 'interaction.submit', 'interaction.update',
|
|
304
|
+
'notification.claim', 'notification.deliver', 'turn.claim', 'turn.resolve',
|
|
305
|
+
'controller.list', 'controller.get', 'controller.apply', 'controller.reconcile', 'controller.delete',
|
|
306
|
+
'host.status', 'host.stop'
|
|
307
|
+
)),
|
|
308
|
+
idempotency_key TEXT NOT NULL,
|
|
309
|
+
request_fingerprint BLOB NOT NULL CHECK (length(request_fingerprint) = 32),
|
|
310
|
+
run_id TEXT REFERENCES runs(run_id) ON DELETE SET NULL,
|
|
311
|
+
accepted_revision INTEGER CHECK (accepted_revision IS NULL OR accepted_revision >= 0),
|
|
312
|
+
outcome TEXT NOT NULL CHECK (outcome IN (
|
|
313
|
+
'accepted', 'adopted', 'rejected', 'conflict', 'notFound', 'claimLost', 'unavailable'
|
|
314
|
+
)),
|
|
315
|
+
receipt_hash BLOB REFERENCES blobs(blob_hash),
|
|
316
|
+
error_hash BLOB REFERENCES blobs(blob_hash),
|
|
317
|
+
host_epoch INTEGER NOT NULL CHECK (host_epoch > 0),
|
|
318
|
+
created_at INTEGER NOT NULL,
|
|
319
|
+
completed_at INTEGER NOT NULL,
|
|
320
|
+
UNIQUE (client_id, idempotency_key)
|
|
321
|
+
) STRICT;
|
|
322
|
+
|
|
323
|
+
CREATE INDEX host_commands_run_idx ON host_commands(run_id, created_at);
|
|
324
|
+
|
|
325
|
+
CREATE TABLE run_workers (
|
|
326
|
+
worker_epoch TEXT PRIMARY KEY,
|
|
327
|
+
run_id TEXT NOT NULL REFERENCES runs(run_id) ON DELETE CASCADE,
|
|
328
|
+
generation INTEGER NOT NULL CHECK (generation > 0),
|
|
329
|
+
host_epoch INTEGER NOT NULL CHECK (host_epoch > 0),
|
|
330
|
+
launch_envelope_hash BLOB NOT NULL REFERENCES blobs(blob_hash),
|
|
331
|
+
pid INTEGER,
|
|
332
|
+
process_start_identity TEXT,
|
|
333
|
+
status TEXT NOT NULL CHECK (status IN (
|
|
334
|
+
'starting', 'ready', 'running', 'exited', 'cancelled', 'timedOut',
|
|
335
|
+
'crashed', 'claimLost', 'orphaned'
|
|
336
|
+
)),
|
|
337
|
+
started_at INTEGER NOT NULL,
|
|
338
|
+
ready_at INTEGER,
|
|
339
|
+
finished_at INTEGER,
|
|
340
|
+
exit_code INTEGER,
|
|
341
|
+
signal TEXT,
|
|
342
|
+
diagnostic_hash BLOB REFERENCES blobs(blob_hash),
|
|
343
|
+
CHECK ((pid IS NULL) = (process_start_identity IS NULL))
|
|
344
|
+
) STRICT;
|
|
345
|
+
|
|
346
|
+
CREATE UNIQUE INDEX run_workers_active_idx ON run_workers(run_id)
|
|
347
|
+
WHERE status IN ('starting', 'ready', 'running');
|
|
348
|
+
CREATE INDEX run_workers_generation_idx ON run_workers(run_id, generation, started_at);
|
|
349
|
+
|
|
350
|
+
CREATE TABLE worker_messages (
|
|
351
|
+
worker_epoch TEXT NOT NULL REFERENCES run_workers(worker_epoch) ON DELETE CASCADE,
|
|
352
|
+
message_id TEXT NOT NULL,
|
|
353
|
+
request_fingerprint BLOB NOT NULL CHECK (length(request_fingerprint) = 32),
|
|
354
|
+
outcome TEXT NOT NULL CHECK (outcome IN ('accepted', 'adopted', 'rejected', 'claimLost')),
|
|
355
|
+
accepted_revision INTEGER CHECK (accepted_revision IS NULL OR accepted_revision >= 0),
|
|
356
|
+
result_hash BLOB REFERENCES blobs(blob_hash),
|
|
357
|
+
error_hash BLOB REFERENCES blobs(blob_hash),
|
|
358
|
+
completed_at INTEGER NOT NULL,
|
|
359
|
+
PRIMARY KEY (worker_epoch, message_id)
|
|
360
|
+
) STRICT;
|
|
361
|
+
|
|
243
362
|
CREATE TABLE node_attempts (
|
|
244
363
|
attempt_id TEXT PRIMARY KEY,
|
|
245
364
|
run_id TEXT NOT NULL REFERENCES runs(run_id) ON DELETE CASCADE,
|
|
@@ -268,6 +387,7 @@ CREATE TABLE node_attempts (
|
|
|
268
387
|
CREATE UNIQUE INDEX node_attempts_active_idx ON node_attempts(run_id)
|
|
269
388
|
WHERE status IN ('pending', 'running', 'waiting');
|
|
270
389
|
CREATE INDEX node_attempts_run_idx ON node_attempts(run_id, created_at);
|
|
390
|
+
CREATE INDEX node_attempts_deadline_idx ON node_attempts(deadline_at) WHERE deadline_at IS NOT NULL;
|
|
271
391
|
|
|
272
392
|
CREATE TABLE run_steps (
|
|
273
393
|
run_id TEXT NOT NULL REFERENCES runs(run_id) ON DELETE CASCADE,
|
|
@@ -314,14 +434,19 @@ CREATE INDEX session_segments_run_idx ON session_segments(run_id, created_at);
|
|
|
314
434
|
|
|
315
435
|
CREATE TABLE session_entries (
|
|
316
436
|
segment_id TEXT NOT NULL REFERENCES session_segments(segment_id) ON DELETE CASCADE,
|
|
437
|
+
run_id TEXT NOT NULL REFERENCES runs(run_id) ON DELETE CASCADE,
|
|
317
438
|
entry_seq INTEGER NOT NULL CHECK (entry_seq > 0),
|
|
439
|
+
run_seq INTEGER NOT NULL CHECK (run_seq > 0),
|
|
318
440
|
entry_id TEXT NOT NULL,
|
|
319
441
|
entry_hash BLOB NOT NULL REFERENCES blobs(blob_hash),
|
|
320
442
|
recorded_at INTEGER NOT NULL,
|
|
321
443
|
PRIMARY KEY (segment_id, entry_seq),
|
|
322
|
-
UNIQUE (segment_id, entry_id)
|
|
444
|
+
UNIQUE (segment_id, entry_id),
|
|
445
|
+
UNIQUE (run_id, run_seq)
|
|
323
446
|
) STRICT;
|
|
324
447
|
|
|
448
|
+
CREATE INDEX session_entries_run_idx ON session_entries(run_id, run_seq);
|
|
449
|
+
|
|
325
450
|
CREATE TABLE attempt_entries (
|
|
326
451
|
attempt_id TEXT NOT NULL REFERENCES node_attempts(attempt_id) ON DELETE CASCADE,
|
|
327
452
|
role TEXT NOT NULL CHECK (role IN ('prompt', 'response', 'first', 'last')),
|
|
@@ -336,7 +461,9 @@ CREATE INDEX attempt_entries_entry_idx ON attempt_entries(segment_id, entry_id);
|
|
|
336
461
|
|
|
337
462
|
CREATE TABLE session_events (
|
|
338
463
|
segment_id TEXT NOT NULL REFERENCES session_segments(segment_id) ON DELETE CASCADE,
|
|
464
|
+
run_id TEXT NOT NULL REFERENCES runs(run_id) ON DELETE CASCADE,
|
|
339
465
|
event_seq INTEGER NOT NULL CHECK (event_seq > 0),
|
|
466
|
+
run_seq INTEGER NOT NULL CHECK (run_seq > 0),
|
|
340
467
|
event_type TEXT NOT NULL,
|
|
341
468
|
node_id TEXT,
|
|
342
469
|
attempt_id TEXT,
|
|
@@ -345,9 +472,12 @@ CREATE TABLE session_events (
|
|
|
345
472
|
tool_call_id TEXT,
|
|
346
473
|
payload_hash BLOB NOT NULL REFERENCES blobs(blob_hash),
|
|
347
474
|
recorded_at INTEGER NOT NULL,
|
|
348
|
-
PRIMARY KEY (segment_id, event_seq)
|
|
475
|
+
PRIMARY KEY (segment_id, event_seq),
|
|
476
|
+
UNIQUE (run_id, run_seq)
|
|
349
477
|
) STRICT;
|
|
350
478
|
|
|
479
|
+
CREATE INDEX session_events_run_idx ON session_events(run_id, run_seq);
|
|
480
|
+
|
|
351
481
|
CREATE TABLE human_decisions (
|
|
352
482
|
decision_id TEXT PRIMARY KEY,
|
|
353
483
|
resource_id TEXT NOT NULL UNIQUE REFERENCES resources(resource_id),
|
|
@@ -369,6 +499,44 @@ CREATE TABLE human_decisions (
|
|
|
369
499
|
CREATE INDEX human_decisions_run_idx ON human_decisions(run_id, created_at);
|
|
370
500
|
CREATE INDEX human_decisions_deadline_idx ON human_decisions(deadline_at) WHERE deadline_at IS NOT NULL;
|
|
371
501
|
|
|
502
|
+
CREATE TABLE interactive_requests (
|
|
503
|
+
request_id TEXT PRIMARY KEY,
|
|
504
|
+
run_id TEXT NOT NULL REFERENCES runs(run_id) ON DELETE CASCADE,
|
|
505
|
+
attempt_id TEXT NOT NULL UNIQUE REFERENCES node_attempts(attempt_id) ON DELETE CASCADE,
|
|
506
|
+
target_session_id TEXT NOT NULL,
|
|
507
|
+
kind TEXT NOT NULL CHECK (kind IN ('agent', 'assistant', 'decision')),
|
|
508
|
+
contract_hash BLOB NOT NULL REFERENCES blobs(blob_hash),
|
|
509
|
+
revision INTEGER NOT NULL DEFAULT 1 CHECK (revision > 0),
|
|
510
|
+
status TEXT NOT NULL CHECK (status IN ('pending', 'presenting', 'settled', 'cancelled')),
|
|
511
|
+
presenter_id TEXT,
|
|
512
|
+
presentation_claim_expires_at INTEGER,
|
|
513
|
+
presentation_session_entry_id TEXT,
|
|
514
|
+
accepted_submission_id TEXT,
|
|
515
|
+
created_at INTEGER NOT NULL,
|
|
516
|
+
updated_at INTEGER NOT NULL,
|
|
517
|
+
settled_at INTEGER,
|
|
518
|
+
consumed_at INTEGER,
|
|
519
|
+
CHECK ((status = 'settled') = (accepted_submission_id IS NOT NULL AND settled_at IS NOT NULL)),
|
|
520
|
+
CHECK ((presenter_id IS NULL) = (presentation_claim_expires_at IS NULL)),
|
|
521
|
+
CHECK (presenter_id IS NULL OR status = 'presenting'),
|
|
522
|
+
CHECK (consumed_at IS NULL OR status = 'settled')
|
|
523
|
+
) STRICT;
|
|
524
|
+
|
|
525
|
+
CREATE INDEX interactive_requests_session_idx
|
|
526
|
+
ON interactive_requests(target_session_id, status, created_at);
|
|
527
|
+
|
|
528
|
+
CREATE TABLE interactive_submissions (
|
|
529
|
+
submission_id TEXT PRIMARY KEY,
|
|
530
|
+
request_id TEXT NOT NULL REFERENCES interactive_requests(request_id) ON DELETE CASCADE,
|
|
531
|
+
idempotency_key TEXT NOT NULL,
|
|
532
|
+
request_revision INTEGER NOT NULL CHECK (request_revision > 0),
|
|
533
|
+
payload_hash BLOB NOT NULL REFERENCES blobs(blob_hash),
|
|
534
|
+
outcome TEXT NOT NULL CHECK (outcome IN ('validating', 'accepted', 'rejected', 'adopted')),
|
|
535
|
+
receipt_hash BLOB REFERENCES blobs(blob_hash),
|
|
536
|
+
submitted_at INTEGER NOT NULL,
|
|
537
|
+
UNIQUE (request_id, idempotency_key)
|
|
538
|
+
) STRICT;
|
|
539
|
+
|
|
372
540
|
CREATE TABLE human_decision_resolutions (
|
|
373
541
|
decision_id TEXT PRIMARY KEY REFERENCES human_decisions(decision_id) ON DELETE CASCADE,
|
|
374
542
|
outcome TEXT NOT NULL CHECK (outcome IN ('accepted', 'cancelled')),
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import { StateDatabase } from "./database.js";
|
|
2
|
+
import { canonicalJson, parseJson, type JsonValue } from "./json.js";
|
|
3
|
+
|
|
4
|
+
export const VIEWER_DELTA_RETENTION = 256;
|
|
5
|
+
export const VIEWER_PAGE_SIZE = 256;
|
|
6
|
+
|
|
7
|
+
export type ViewerTargetType =
|
|
8
|
+
| "summary"
|
|
9
|
+
| "graph"
|
|
10
|
+
| "replay"
|
|
11
|
+
| "timeline"
|
|
12
|
+
| "conversation"
|
|
13
|
+
| "inspector";
|
|
14
|
+
|
|
15
|
+
export type ViewerPatchOperation =
|
|
16
|
+
| { op: "add" | "replace"; path: string; value: JsonValue }
|
|
17
|
+
| { op: "remove"; path: string }
|
|
18
|
+
| { op: "append"; path: string; value: JsonValue[] };
|
|
19
|
+
|
|
20
|
+
export type ViewerDeltaDraft = {
|
|
21
|
+
targetType: ViewerTargetType;
|
|
22
|
+
targetKey?: string;
|
|
23
|
+
patch?: ViewerPatchOperation[];
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type ViewerTailItem = JsonValue;
|
|
27
|
+
|
|
28
|
+
export type ViewerDeltaRecord = {
|
|
29
|
+
runId: string;
|
|
30
|
+
presentationRevision: number;
|
|
31
|
+
deltaIndex: number;
|
|
32
|
+
targetType: ViewerTargetType;
|
|
33
|
+
targetKey: string;
|
|
34
|
+
patch: ViewerPatchOperation[];
|
|
35
|
+
recordedAt: number;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type ViewerCursorResult =
|
|
39
|
+
| {
|
|
40
|
+
kind: "deltas";
|
|
41
|
+
currentRevision: number;
|
|
42
|
+
retainedFromRevision: number;
|
|
43
|
+
deltas: ViewerDeltaRecord[];
|
|
44
|
+
}
|
|
45
|
+
| {
|
|
46
|
+
kind: "snapshot_required";
|
|
47
|
+
currentRevision: number;
|
|
48
|
+
retainedFromRevision: number;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
type ViewerRunRow = {
|
|
52
|
+
presentationRevision: number;
|
|
53
|
+
retainedFromRevision: number;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
type ViewerDeltaRow = {
|
|
57
|
+
runId: string;
|
|
58
|
+
presentationRevision: number;
|
|
59
|
+
deltaIndex: number;
|
|
60
|
+
targetType: string;
|
|
61
|
+
targetKey: string;
|
|
62
|
+
patchHash: Buffer;
|
|
63
|
+
recordedAt: number;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export function initializeViewerRun(state: StateDatabase, runId: string, now: number): number {
|
|
67
|
+
state.connection
|
|
68
|
+
.prepare(
|
|
69
|
+
`INSERT INTO viewer_runs(
|
|
70
|
+
run_id, presentation_revision, retained_from_revision, updated_at
|
|
71
|
+
) VALUES (?, 1, 1, ?)
|
|
72
|
+
ON CONFLICT(run_id) DO NOTHING`,
|
|
73
|
+
)
|
|
74
|
+
.run(runId, now);
|
|
75
|
+
const row = state.connection
|
|
76
|
+
.prepare(
|
|
77
|
+
`SELECT presentation_revision AS presentationRevision,
|
|
78
|
+
retained_from_revision AS retainedFromRevision
|
|
79
|
+
FROM viewer_runs WHERE run_id = ?`,
|
|
80
|
+
)
|
|
81
|
+
.get(runId);
|
|
82
|
+
if (!isViewerRunRow(row)) {
|
|
83
|
+
throw new Error(`Viewer projection could not be initialized for workflow run: ${runId}`);
|
|
84
|
+
}
|
|
85
|
+
return row.presentationRevision;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function recordViewerDeltas(
|
|
89
|
+
state: StateDatabase,
|
|
90
|
+
runId: string,
|
|
91
|
+
drafts: readonly ViewerDeltaDraft[],
|
|
92
|
+
now: number = Date.now(),
|
|
93
|
+
): number {
|
|
94
|
+
if (!state.connection.inTransaction) {
|
|
95
|
+
throw new Error("Viewer changes must be recorded inside the durable state transaction");
|
|
96
|
+
}
|
|
97
|
+
if (drafts.length === 0) {
|
|
98
|
+
throw new Error("Viewer change must name at least one bounded target");
|
|
99
|
+
}
|
|
100
|
+
const row = state.connection
|
|
101
|
+
.prepare(
|
|
102
|
+
`SELECT presentation_revision AS presentationRevision,
|
|
103
|
+
retained_from_revision AS retainedFromRevision
|
|
104
|
+
FROM viewer_runs WHERE run_id = ?`,
|
|
105
|
+
)
|
|
106
|
+
.get(runId);
|
|
107
|
+
if (!isViewerRunRow(row)) {
|
|
108
|
+
throw new Error(`Viewer projection does not exist for workflow run: ${runId}`);
|
|
109
|
+
}
|
|
110
|
+
const revision = row.presentationRevision + 1;
|
|
111
|
+
for (const [index, draft] of drafts.entries()) {
|
|
112
|
+
const patch: ViewerPatchOperation[] = [
|
|
113
|
+
{
|
|
114
|
+
op: "replace",
|
|
115
|
+
path: "/presentationRevision",
|
|
116
|
+
value: revision,
|
|
117
|
+
},
|
|
118
|
+
...(draft.targetType === "graph"
|
|
119
|
+
? [
|
|
120
|
+
{
|
|
121
|
+
op: "replace" as const,
|
|
122
|
+
path: "/graphRevision",
|
|
123
|
+
value: revision,
|
|
124
|
+
},
|
|
125
|
+
]
|
|
126
|
+
: []),
|
|
127
|
+
...(draft.patch ?? []),
|
|
128
|
+
];
|
|
129
|
+
const patchHash = state.putJson(patch, now);
|
|
130
|
+
state.connection
|
|
131
|
+
.prepare(
|
|
132
|
+
`INSERT INTO viewer_deltas(
|
|
133
|
+
run_id, presentation_revision, delta_index, target_type,
|
|
134
|
+
target_key, patch_hash, recorded_at
|
|
135
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
136
|
+
)
|
|
137
|
+
.run(runId, revision, index, draft.targetType, draft.targetKey ?? "", patchHash, now);
|
|
138
|
+
}
|
|
139
|
+
const retainedFromRevision = Math.max(1, revision - VIEWER_DELTA_RETENTION + 1);
|
|
140
|
+
const update = state.connection
|
|
141
|
+
.prepare(
|
|
142
|
+
`UPDATE viewer_runs
|
|
143
|
+
SET presentation_revision = ?, retained_from_revision = ?, updated_at = ?
|
|
144
|
+
WHERE run_id = ? AND presentation_revision = ?`,
|
|
145
|
+
)
|
|
146
|
+
.run(revision, retainedFromRevision, now, runId, row.presentationRevision);
|
|
147
|
+
if (update.changes !== 1) {
|
|
148
|
+
throw new Error(`Viewer projection revision conflict for workflow run: ${runId}`);
|
|
149
|
+
}
|
|
150
|
+
const stalePatchHashes = state.connection
|
|
151
|
+
.prepare(
|
|
152
|
+
`SELECT DISTINCT patch_hash AS patchHash FROM viewer_deltas
|
|
153
|
+
WHERE run_id = ? AND presentation_revision < ?`,
|
|
154
|
+
)
|
|
155
|
+
.all(runId, retainedFromRevision)
|
|
156
|
+
.filter(isPatchHashRow)
|
|
157
|
+
.map((value) => value.patchHash);
|
|
158
|
+
state.connection
|
|
159
|
+
.prepare(
|
|
160
|
+
`DELETE FROM viewer_deltas
|
|
161
|
+
WHERE run_id = ? AND presentation_revision < ?`,
|
|
162
|
+
)
|
|
163
|
+
.run(runId, retainedFromRevision);
|
|
164
|
+
const deleteBlob = state.connection.prepare(
|
|
165
|
+
`DELETE FROM blobs WHERE blob_hash = ?
|
|
166
|
+
AND NOT EXISTS (SELECT 1 FROM viewer_deltas WHERE patch_hash = blobs.blob_hash)`,
|
|
167
|
+
);
|
|
168
|
+
for (const hash of stalePatchHashes) {
|
|
169
|
+
try {
|
|
170
|
+
deleteBlob.run(hash);
|
|
171
|
+
} catch (error) {
|
|
172
|
+
if (
|
|
173
|
+
typeof error !== "object" ||
|
|
174
|
+
error === null ||
|
|
175
|
+
!("code" in error) ||
|
|
176
|
+
error.code !== "SQLITE_CONSTRAINT_FOREIGNKEY"
|
|
177
|
+
) {
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return revision;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function viewerTailPatch(
|
|
186
|
+
oldTotal: number,
|
|
187
|
+
items: readonly ViewerTailItem[],
|
|
188
|
+
): ViewerPatchOperation[] {
|
|
189
|
+
if (!Number.isInteger(oldTotal) || oldTotal < 0) {
|
|
190
|
+
throw new Error("Viewer page total must be a non-negative integer");
|
|
191
|
+
}
|
|
192
|
+
if (items.length === 0) return [];
|
|
193
|
+
const nextTotal = oldTotal + items.length;
|
|
194
|
+
const previousStart = Math.max(0, oldTotal - VIEWER_PAGE_SIZE);
|
|
195
|
+
const nextStart = Math.max(0, nextTotal - VIEWER_PAGE_SIZE);
|
|
196
|
+
const patch: ViewerPatchOperation[] = [];
|
|
197
|
+
if (items.length >= VIEWER_PAGE_SIZE) {
|
|
198
|
+
patch.push({
|
|
199
|
+
op: "replace",
|
|
200
|
+
path: "/items",
|
|
201
|
+
value: items.slice(-VIEWER_PAGE_SIZE),
|
|
202
|
+
});
|
|
203
|
+
} else {
|
|
204
|
+
const retainedBefore = oldTotal - previousStart;
|
|
205
|
+
const removed = Math.min(nextStart - previousStart, retainedBefore);
|
|
206
|
+
for (let index = 0; index < removed; index += 1) {
|
|
207
|
+
patch.push({ op: "remove", path: "/items/0" });
|
|
208
|
+
}
|
|
209
|
+
patch.push({ op: "append", path: "/items", value: [...items] });
|
|
210
|
+
}
|
|
211
|
+
if (nextStart !== previousStart) {
|
|
212
|
+
patch.push({ op: "replace", path: "/start", value: nextStart });
|
|
213
|
+
}
|
|
214
|
+
patch.push({ op: "replace", path: "/total", value: nextTotal });
|
|
215
|
+
return patch;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function readViewerDeltas(
|
|
219
|
+
state: StateDatabase,
|
|
220
|
+
runId: string,
|
|
221
|
+
afterRevision: number,
|
|
222
|
+
): ViewerCursorResult {
|
|
223
|
+
if (!Number.isInteger(afterRevision) || afterRevision < 0) {
|
|
224
|
+
throw new Error("Viewer cursor must be a non-negative integer");
|
|
225
|
+
}
|
|
226
|
+
const row = state.connection
|
|
227
|
+
.prepare(
|
|
228
|
+
`SELECT presentation_revision AS presentationRevision,
|
|
229
|
+
retained_from_revision AS retainedFromRevision
|
|
230
|
+
FROM viewer_runs WHERE run_id = ?`,
|
|
231
|
+
)
|
|
232
|
+
.get(runId);
|
|
233
|
+
if (!isViewerRunRow(row)) {
|
|
234
|
+
throw new Error(`Viewer projection does not exist for workflow run: ${runId}`);
|
|
235
|
+
}
|
|
236
|
+
if (
|
|
237
|
+
afterRevision === 0 ||
|
|
238
|
+
afterRevision > row.presentationRevision ||
|
|
239
|
+
afterRevision < row.retainedFromRevision - 1
|
|
240
|
+
) {
|
|
241
|
+
return {
|
|
242
|
+
kind: "snapshot_required",
|
|
243
|
+
currentRevision: row.presentationRevision,
|
|
244
|
+
retainedFromRevision: row.retainedFromRevision,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
const rows = state.connection
|
|
248
|
+
.prepare(
|
|
249
|
+
`SELECT run_id AS runId, presentation_revision AS presentationRevision,
|
|
250
|
+
delta_index AS deltaIndex, target_type AS targetType,
|
|
251
|
+
target_key AS targetKey, patch_hash AS patchHash,
|
|
252
|
+
recorded_at AS recordedAt
|
|
253
|
+
FROM viewer_deltas
|
|
254
|
+
WHERE run_id = ? AND presentation_revision > ?
|
|
255
|
+
ORDER BY presentation_revision, delta_index`,
|
|
256
|
+
)
|
|
257
|
+
.all(runId, afterRevision);
|
|
258
|
+
const deltas: ViewerDeltaRecord[] = [];
|
|
259
|
+
for (const value of rows) {
|
|
260
|
+
if (!isViewerDeltaRow(value) || !isViewerTargetType(value.targetType)) {
|
|
261
|
+
throw new Error("Viewer delta row is invalid");
|
|
262
|
+
}
|
|
263
|
+
deltas.push({
|
|
264
|
+
runId: value.runId,
|
|
265
|
+
presentationRevision: value.presentationRevision,
|
|
266
|
+
deltaIndex: value.deltaIndex,
|
|
267
|
+
targetType: value.targetType,
|
|
268
|
+
targetKey: value.targetKey,
|
|
269
|
+
patch: parsePatch(state.readJson(value.patchHash)),
|
|
270
|
+
recordedAt: value.recordedAt,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
const revisions = [...new Set(deltas.map((delta) => delta.presentationRevision))];
|
|
274
|
+
const expectedCount = row.presentationRevision - afterRevision;
|
|
275
|
+
if (
|
|
276
|
+
revisions.length !== expectedCount ||
|
|
277
|
+
revisions.some((revision, index) => revision !== afterRevision + index + 1)
|
|
278
|
+
) {
|
|
279
|
+
return {
|
|
280
|
+
kind: "snapshot_required",
|
|
281
|
+
currentRevision: row.presentationRevision,
|
|
282
|
+
retainedFromRevision: row.retainedFromRevision,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
return {
|
|
286
|
+
kind: "deltas",
|
|
287
|
+
currentRevision: row.presentationRevision,
|
|
288
|
+
retainedFromRevision: row.retainedFromRevision,
|
|
289
|
+
deltas,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function parsePatch(value: JsonValue): ViewerPatchOperation[] {
|
|
294
|
+
if (!Array.isArray(value)) throw new Error("Viewer delta patch is not an array");
|
|
295
|
+
const operations: ViewerPatchOperation[] = [];
|
|
296
|
+
for (const item of value) {
|
|
297
|
+
if (!isRecord(item) || typeof item.op !== "string" || typeof item.path !== "string") {
|
|
298
|
+
throw new Error("Viewer delta patch operation is invalid");
|
|
299
|
+
}
|
|
300
|
+
if (item.op === "remove") {
|
|
301
|
+
operations.push({ op: "remove", path: item.path });
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
if ((item.op === "add" || item.op === "replace") && "value" in item) {
|
|
305
|
+
operations.push({
|
|
306
|
+
op: item.op,
|
|
307
|
+
path: item.path,
|
|
308
|
+
value: parseJson(canonicalJson(item.value)),
|
|
309
|
+
});
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
if (item.op === "append" && Array.isArray(item.value)) {
|
|
313
|
+
operations.push({
|
|
314
|
+
op: "append",
|
|
315
|
+
path: item.path,
|
|
316
|
+
value: item.value.map((entry) => parseJson(canonicalJson(entry))),
|
|
317
|
+
});
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
throw new Error("Viewer delta patch operation is unsupported");
|
|
321
|
+
}
|
|
322
|
+
return operations;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function isPatchHashRow(value: unknown): value is { patchHash: Buffer } {
|
|
326
|
+
return isRecord(value) && Buffer.isBuffer(value.patchHash);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function isViewerRunRow(value: unknown): value is ViewerRunRow {
|
|
330
|
+
return (
|
|
331
|
+
isRecord(value) &&
|
|
332
|
+
typeof value.presentationRevision === "number" &&
|
|
333
|
+
typeof value.retainedFromRevision === "number"
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function isViewerDeltaRow(value: unknown): value is ViewerDeltaRow {
|
|
338
|
+
return (
|
|
339
|
+
isRecord(value) &&
|
|
340
|
+
typeof value.runId === "string" &&
|
|
341
|
+
typeof value.presentationRevision === "number" &&
|
|
342
|
+
typeof value.deltaIndex === "number" &&
|
|
343
|
+
typeof value.targetType === "string" &&
|
|
344
|
+
typeof value.targetKey === "string" &&
|
|
345
|
+
Buffer.isBuffer(value.patchHash) &&
|
|
346
|
+
typeof value.recordedAt === "number"
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function isViewerTargetType(value: string): value is ViewerTargetType {
|
|
351
|
+
return ["summary", "graph", "replay", "timeline", "conversation", "inspector"].includes(value);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
355
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
356
|
+
}
|