@osolmaz/pi-workflows 0.16.2 → 0.16.4
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 +41 -41
- package/dist/builtins/monitor.workflow.d.ts +1 -1
- package/dist/builtins/monitor.workflow.js +8 -8
- package/dist/builtins/pi-agent-group.js +3 -3
- package/dist/builtins/pi-agent-group.js.map +1 -1
- package/dist/channels/adapter-entry.js +14 -14
- package/dist/channels/adapter-entry.js.map +1 -1
- package/dist/client/client.d.ts +6 -6
- package/dist/client/client.js +27 -27
- package/dist/client/client.js.map +1 -1
- package/dist/client/protocol.d.ts +4 -1
- package/dist/client/protocol.js +17 -11
- package/dist/client/protocol.js.map +1 -1
- package/dist/client/resolver.d.ts +4 -4
- package/dist/client/view.d.ts +1 -1
- package/dist/extension/index.d.ts +1 -1
- package/dist/extension/index.js +32 -32
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/recorder.d.ts +1 -1
- package/dist/extension/recorder.js +1 -1
- package/dist/extension/recorder.js.map +1 -1
- package/dist/extension/remote-recorder-store.d.ts +1 -1
- package/dist/extension/remote-recorder-store.js +2 -2
- package/dist/extension/remote-recorder-store.js.map +1 -1
- package/dist/extension/{controller-command.d.ts → resource-manager-command.d.ts} +6 -6
- package/dist/extension/{controller-command.js → resource-manager-command.js} +7 -7
- package/dist/extension/resource-manager-command.js.map +1 -0
- package/dist/extension/session-delivery.d.ts +1 -1
- package/dist/extension/session-delivery.js +2 -2
- package/dist/extension/session-delivery.js.map +1 -1
- package/dist/extension/session-view.d.ts +1 -1
- package/dist/extension/session-view.js +1 -1
- package/dist/extension/session-view.js.map +1 -1
- package/dist/extension/workflow-message-coordinator.d.ts +1 -1
- package/dist/extension/workflow-message-coordinator.js +6 -6
- package/dist/extension/workflow-message-coordinator.js.map +1 -1
- package/dist/resource-managers/conditions.d.ts +6 -0
- package/dist/{controllers → resource-managers}/conditions.js +3 -3
- package/dist/resource-managers/conditions.js.map +1 -0
- package/dist/resource-managers/definition.d.ts +6 -0
- package/dist/resource-managers/definition.js +45 -0
- package/dist/resource-managers/definition.js.map +1 -0
- package/dist/resource-managers/effects.d.ts +15 -0
- package/dist/{controllers → resource-managers}/effects.js +2 -2
- package/dist/resource-managers/effects.js.map +1 -0
- package/dist/resource-managers/errors.d.ts +12 -0
- package/dist/resource-managers/errors.js +25 -0
- package/dist/resource-managers/errors.js.map +1 -0
- package/dist/resource-managers/index.d.ts +12 -0
- package/dist/resource-managers/index.js +12 -0
- package/dist/resource-managers/index.js.map +1 -0
- package/dist/resource-managers/json.js.map +1 -0
- package/dist/resource-managers/loader.d.ts +23 -0
- package/dist/resource-managers/loader.js +75 -0
- package/dist/resource-managers/loader.js.map +1 -0
- package/dist/resource-managers/results.d.ts +5 -0
- package/dist/resource-managers/results.js.map +1 -0
- package/dist/resource-managers/runtime.d.ts +59 -0
- package/dist/{controllers/manager.js → resource-managers/runtime.js} +57 -57
- package/dist/resource-managers/runtime.js.map +1 -0
- package/dist/{controllers → resource-managers}/sqlite.d.ts +44 -44
- package/dist/{controllers → resource-managers}/sqlite.js +139 -130
- package/dist/resource-managers/sqlite.js.map +1 -0
- package/dist/{controllers → resource-managers}/store.d.ts +33 -33
- package/dist/{controllers → resource-managers}/store.js.map +1 -1
- package/dist/{controllers → resource-managers}/types.d.ts +41 -41
- package/dist/{controllers → resource-managers}/types.js.map +1 -1
- package/dist/resource-managers/workflows.d.ts +34 -0
- package/dist/{controllers → resource-managers}/workflows.js +15 -15
- package/dist/resource-managers/workflows.js.map +1 -0
- package/dist/server/channel-effects.js.map +1 -0
- package/dist/{host → server}/channel-supervisor.d.ts +6 -6
- package/dist/{host → server}/channel-supervisor.js +3 -3
- package/dist/server/channel-supervisor.js.map +1 -0
- package/dist/{host/child-worker-supervisor.d.ts → server/child-runner-supervisor.d.ts} +15 -13
- package/dist/{host/child-worker-supervisor.js → server/child-runner-supervisor.js} +6 -5
- package/dist/server/child-runner-supervisor.js.map +1 -0
- package/dist/{host → server}/processes.d.ts +3 -3
- package/dist/{host → server}/processes.js +3 -3
- package/dist/server/processes.js.map +1 -0
- package/dist/{host → server}/resolver-entry.d.ts +6 -6
- package/dist/{host → server}/resolver-entry.js +14 -14
- package/dist/server/resolver-entry.js.map +1 -0
- package/dist/server/resource-runner-entry.d.ts +2 -0
- package/dist/{host/controller-worker-entry.js → server/resource-runner-entry.js} +33 -33
- package/dist/server/resource-runner-entry.js.map +1 -0
- package/dist/server/resource-runner-protocol.d.ts +36 -0
- package/dist/server/resource-runner-protocol.js +49 -0
- package/dist/server/resource-runner-protocol.js.map +1 -0
- package/dist/server/resource-runner-supervisor.d.ts +21 -0
- package/dist/{host/worker-supervisor.js → server/resource-runner-supervisor.js} +17 -17
- package/dist/server/resource-runner-supervisor.js.map +1 -0
- package/dist/{host → server}/rpc-bridge.d.ts +3 -3
- package/dist/{host → server}/rpc-bridge.js +3 -3
- package/dist/server/rpc-bridge.js.map +1 -0
- package/dist/{host → server}/rpc-executor.d.ts +1 -1
- package/dist/{host → server}/rpc-executor.js +3 -3
- package/dist/server/rpc-executor.js.map +1 -0
- package/dist/{host/host-entry.js → server/server-entry.js} +6 -6
- package/dist/server/server-entry.js.map +1 -0
- package/dist/{host/runner.d.ts → server/server.d.ts} +39 -38
- package/dist/{host/runner.js → server/server.js} +408 -374
- package/dist/server/server.js.map +1 -0
- package/dist/{host → server}/state.d.ts +25 -25
- package/dist/{host → server}/state.js +49 -49
- package/dist/server/state.js.map +1 -0
- package/dist/{host → server}/view.d.ts +7 -7
- package/dist/{host → server}/view.js +13 -13
- package/dist/server/view.js.map +1 -0
- package/dist/server/workflow-runner-content.d.ts +6 -0
- package/dist/server/workflow-runner-content.js +96 -0
- package/dist/server/workflow-runner-content.js.map +1 -0
- package/dist/{host/worker-entry.d.ts → server/workflow-runner-entry.d.ts} +3 -3
- package/dist/{host/worker-entry.js → server/workflow-runner-entry.js} +68 -35
- package/dist/server/workflow-runner-entry.js.map +1 -0
- package/dist/server/workflow-runner-protocol.d.ts +69 -0
- package/dist/server/workflow-runner-protocol.js +176 -0
- package/dist/server/workflow-runner-protocol.js.map +1 -0
- package/dist/{host/worker-store.d.ts → server/workflow-runner-store.d.ts} +11 -11
- package/dist/{host/worker-store.js → server/workflow-runner-store.js} +8 -8
- package/dist/server/workflow-runner-store.js.map +1 -0
- package/dist/server/workflow-runner-supervisor.d.ts +22 -0
- package/dist/server/workflow-runner-supervisor.js +56 -0
- package/dist/server/workflow-runner-supervisor.js.map +1 -0
- package/dist/state/prune.d.ts +1 -1
- package/dist/state/prune.js +17 -10
- package/dist/state/prune.js.map +1 -1
- package/dist/state/schema.js +2 -2
- package/dist/state/workflow-messages.d.ts +1 -1
- package/dist/state/workflow-messages.js +1 -1
- package/dist/state/workflow-messages.js.map +1 -1
- package/dist/viewer/backup.js +1 -1
- package/dist/viewer/backup.js.map +1 -1
- package/dist/viewer/cli.d.ts +2 -2
- package/dist/viewer/cli.js +46 -32
- package/dist/viewer/cli.js.map +1 -1
- package/dist/viewer/tui.d.ts +1 -1
- package/dist/viewer/tui.js +1 -1
- package/dist/viewer/tui.js.map +1 -1
- package/dist/workflows/command-batch.js +2 -2
- package/dist/workflows/engine.js +14 -15
- package/dist/workflows/engine.js.map +1 -1
- package/dist/workflows/store.d.ts +12 -11
- package/dist/workflows/store.js +24 -18
- package/dist/workflows/store.js.map +1 -1
- package/dist/workflows/types.d.ts +2 -2
- package/docs/2026-08-20-durable-workflow-launch-plan.md +12 -12
- package/docs/2026-08-25-workflow-follow-ups.md +7 -7
- package/docs/2026-08-25-workflow-settings.md +3 -3
- 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 +5 -5
- package/docs/2026-09-01-unified-workflow-client-plan.md +6 -6
- package/docs/2026-09-02-installed-live-e2e-plan.md +3 -3
- package/docs/2026-09-02-unify-workflow-messages-plan.md +8 -8
- package/docs/2026-09-04-workflow-run-state-plan.md +6 -6
- package/docs/DEFERRED_TURNS.md +7 -7
- package/docs/DESIGN_PHILOSOPHY.md +1 -1
- package/docs/HUMAN_DECISIONS.md +11 -11
- package/docs/HUMAN_DECISION_PRESENTATIONS.md +4 -4
- package/docs/MONITOR.md +1 -1
- package/docs/RESOURCE_MANAGERS.md +221 -0
- package/docs/SQLITE_STATE.md +35 -33
- package/docs/WORKFLOW_COMPOSITION.md +1 -1
- package/docs/{WORKFLOW_HOST.md → WORKFLOW_SERVER.md} +125 -119
- package/docs/WORKFLOW_STEP_MESSAGES.md +18 -18
- package/docs/WORKFLOW_UPDATES.md +16 -16
- package/docs/development.md +15 -14
- package/docs/live-replay-protocol.md +20 -20
- package/docs/plans/2026-08-04-controller-runtime-plan.md +25 -25
- package/docs/plans/2026-08-05-always-on-workflows-plan.md +8 -8
- package/docs/plans/2026-08-10-agent-managed-monitor-workflows-plan.md +2 -2
- package/docs/plans/2026-08-13-session-addressed-workflow-notifications-plan.md +1 -1
- package/docs/plans/2026-08-16-workflow-updates-plan.md +5 -5
- package/docs/plans/2026-08-19-human-decision-gates-plan.md +1 -1
- package/docs/plans/2026-08-20-bounded-command-batches-plan.md +1 -1
- package/docs/plans/2026-08-21-deferred-turn-intents-plan.md +3 -3
- package/docs/plans/2026-08-21-sanity-check-plan.md +2 -2
- package/docs/plans/2026-08-23-assistant-agent-completion-plan.md +5 -5
- package/docs/plans/2026-08-23-sqlite-state-plan.md +25 -25
- package/docs/plans/2026-08-25-live-workflow-settings-plan.md +4 -4
- package/docs/plans/2026-08-27-workflow-terminal-restart-plan.md +3 -3
- package/docs/plans/2026-09-04-workflow-runner-resume-state-plan.md +318 -0
- package/docs/plans/piw-viewer-experience-implementation-plan.md +1 -1
- package/docs/tui-viewer.md +9 -9
- package/docs/workflows.md +72 -66
- package/examples/{controllers/pull-request.controller.ts → resource-managers/pull-request.resource-manager.ts} +12 -12
- package/herdr-plugin.toml +1 -1
- package/package.json +12 -11
- package/protocol/client.v1.schema.json +7 -7
- package/protocol/fixtures/client-v1.json +1 -1
- package/src/builtins/monitor.workflow.ts +9 -9
- package/src/builtins/pi-agent-group.ts +3 -3
- package/src/channels/adapter-entry.ts +14 -14
- package/src/client/client.ts +33 -32
- package/src/client/protocol.ts +16 -11
- package/src/client/resolver.ts +4 -4
- package/src/client/view.ts +1 -1
- package/src/extension/index.ts +44 -34
- package/src/extension/recorder.ts +1 -1
- package/src/extension/remote-recorder-store.ts +2 -2
- package/src/extension/resource-manager-command.ts +45 -0
- package/src/extension/session-delivery.ts +2 -2
- package/src/extension/session-view.ts +1 -1
- package/src/extension/workflow-message-coordinator.ts +6 -6
- package/src/{controllers → resource-managers}/conditions.ts +23 -23
- package/src/resource-managers/definition.ts +71 -0
- package/src/{controllers → resource-managers}/effects.ts +9 -9
- package/src/resource-managers/errors.ts +27 -0
- package/src/resource-managers/index.ts +88 -0
- package/src/resource-managers/loader.ts +111 -0
- package/src/{controllers → resource-managers}/results.ts +4 -4
- package/src/{controllers/manager.ts → resource-managers/runtime.ts} +95 -92
- package/src/{controllers → resource-managers}/sqlite.ts +198 -186
- package/src/{controllers → resource-managers}/store.ts +46 -38
- package/src/{controllers → resource-managers}/types.ts +50 -41
- package/src/{controllers → resource-managers}/workflows.ts +43 -41
- package/src/{host → server}/channel-supervisor.ts +8 -8
- package/src/{host/child-worker-supervisor.ts → server/child-runner-supervisor.ts} +22 -18
- package/src/{host → server}/processes.ts +3 -3
- package/src/{host → server}/resolver-entry.ts +28 -25
- package/src/{host/controller-worker-entry.ts → server/resource-runner-entry.ts} +67 -65
- package/src/server/resource-runner-protocol.ts +103 -0
- package/src/server/resource-runner-supervisor.ts +76 -0
- package/src/{host → server}/rpc-bridge.ts +3 -3
- package/src/{host → server}/rpc-executor.ts +4 -4
- package/src/{host/host-entry.ts → server/server-entry.ts} +5 -5
- package/src/{host/runner.ts → server/server.ts} +526 -461
- package/src/{host → server}/state.ts +78 -67
- package/src/{host → server}/view.ts +15 -15
- package/src/server/workflow-runner-content.ts +119 -0
- package/src/{host/worker-entry.ts → server/workflow-runner-entry.ts} +98 -57
- package/src/server/workflow-runner-protocol.ts +241 -0
- package/src/{host/worker-store.ts → server/workflow-runner-store.ts} +18 -20
- package/src/server/workflow-runner-supervisor.ts +79 -0
- package/src/state/prune.ts +27 -9
- package/src/state/schema.ts +2 -2
- package/src/state/workflow-messages.ts +1 -1
- package/src/viewer/backup.ts +1 -1
- package/src/viewer/cli.ts +50 -36
- package/src/viewer/tui.ts +1 -1
- package/src/workflows/command-batch.ts +2 -2
- package/src/workflows/engine.ts +14 -15
- package/src/workflows/store.ts +31 -28
- package/src/workflows/types.ts +2 -2
- package/dist/controllers/conditions.d.ts +0 -6
- package/dist/controllers/conditions.js.map +0 -1
- package/dist/controllers/definition.d.ts +0 -6
- package/dist/controllers/definition.js +0 -45
- package/dist/controllers/definition.js.map +0 -1
- package/dist/controllers/effects.d.ts +0 -15
- package/dist/controllers/effects.js.map +0 -1
- package/dist/controllers/errors.d.ts +0 -12
- package/dist/controllers/errors.js +0 -25
- package/dist/controllers/errors.js.map +0 -1
- package/dist/controllers/index.d.ts +0 -12
- package/dist/controllers/index.js +0 -12
- package/dist/controllers/index.js.map +0 -1
- package/dist/controllers/json.js.map +0 -1
- package/dist/controllers/loader.d.ts +0 -23
- package/dist/controllers/loader.js +0 -74
- package/dist/controllers/loader.js.map +0 -1
- package/dist/controllers/manager.d.ts +0 -59
- package/dist/controllers/manager.js.map +0 -1
- package/dist/controllers/results.d.ts +0 -5
- package/dist/controllers/results.js.map +0 -1
- package/dist/controllers/sqlite.js.map +0 -1
- package/dist/controllers/workflows.d.ts +0 -34
- package/dist/controllers/workflows.js.map +0 -1
- package/dist/extension/controller-command.js.map +0 -1
- package/dist/host/channel-effects.js.map +0 -1
- package/dist/host/channel-supervisor.js.map +0 -1
- package/dist/host/child-worker-supervisor.js.map +0 -1
- package/dist/host/controller-worker-entry.d.ts +0 -2
- package/dist/host/controller-worker-entry.js.map +0 -1
- package/dist/host/controller-worker-protocol.d.ts +0 -36
- package/dist/host/controller-worker-protocol.js +0 -49
- package/dist/host/controller-worker-protocol.js.map +0 -1
- package/dist/host/controller-worker-supervisor.d.ts +0 -21
- package/dist/host/controller-worker-supervisor.js +0 -54
- package/dist/host/controller-worker-supervisor.js.map +0 -1
- package/dist/host/host-entry.js.map +0 -1
- package/dist/host/processes.js.map +0 -1
- package/dist/host/resolver-entry.js.map +0 -1
- package/dist/host/rpc-bridge.js.map +0 -1
- package/dist/host/rpc-executor.js.map +0 -1
- package/dist/host/runner.js.map +0 -1
- package/dist/host/state.js.map +0 -1
- package/dist/host/view.js.map +0 -1
- package/dist/host/worker-entry.js.map +0 -1
- package/dist/host/worker-protocol.d.ts +0 -46
- package/dist/host/worker-protocol.js +0 -122
- package/dist/host/worker-protocol.js.map +0 -1
- package/dist/host/worker-store.js.map +0 -1
- package/dist/host/worker-supervisor.d.ts +0 -22
- package/dist/host/worker-supervisor.js.map +0 -1
- package/docs/CONTROLLERS.md +0 -217
- package/src/controllers/definition.ts +0 -65
- package/src/controllers/errors.ts +0 -27
- package/src/controllers/index.ts +0 -88
- package/src/controllers/loader.ts +0 -104
- package/src/extension/controller-command.ts +0 -45
- package/src/host/controller-worker-protocol.ts +0 -104
- package/src/host/controller-worker-supervisor.ts +0 -79
- package/src/host/worker-protocol.ts +0 -176
- package/src/host/worker-supervisor.ts +0 -74
- /package/dist/{controllers → resource-managers}/json.d.ts +0 -0
- /package/dist/{controllers → resource-managers}/json.js +0 -0
- /package/dist/{controllers → resource-managers}/results.js +0 -0
- /package/dist/{controllers → resource-managers}/store.js +0 -0
- /package/dist/{controllers → resource-managers}/types.js +0 -0
- /package/dist/{host → server}/channel-effects.d.ts +0 -0
- /package/dist/{host → server}/channel-effects.js +0 -0
- /package/dist/{host/host-entry.d.ts → server/server-entry.d.ts} +0 -0
- /package/src/{controllers → resource-managers}/json.ts +0 -0
- /package/src/{host → server}/channel-effects.ts +0 -0
|
@@ -17,10 +17,10 @@ import {
|
|
|
17
17
|
decisionWorkflowMessageContent,
|
|
18
18
|
stepWorkflowMessageContent,
|
|
19
19
|
} from "../workflows/workflow-message-content.js";
|
|
20
|
-
import type {
|
|
20
|
+
import type { WorkflowRunnerMessage, WorkflowRunnerResponse } from "./workflow-runner-protocol.js";
|
|
21
21
|
|
|
22
|
-
export type
|
|
23
|
-
|
|
22
|
+
export type ServerClaim = {
|
|
23
|
+
serverId: string;
|
|
24
24
|
token: string;
|
|
25
25
|
epoch: number;
|
|
26
26
|
pid: number;
|
|
@@ -28,9 +28,9 @@ export type HostClaim = {
|
|
|
28
28
|
expiresAt: number;
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
-
export type
|
|
31
|
+
export type WorkflowServerStatusRecord = {
|
|
32
32
|
epoch: number;
|
|
33
|
-
|
|
33
|
+
serverId: string | null;
|
|
34
34
|
pid: number | null;
|
|
35
35
|
processStartIdentity: string | null;
|
|
36
36
|
startedAt: string | null;
|
|
@@ -39,11 +39,11 @@ export type HostStatusRecord = {
|
|
|
39
39
|
live: boolean;
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
-
export type
|
|
42
|
+
export type WorkflowRunnerLaunchEnvelope = {
|
|
43
43
|
schema: "pi-workflows.worker-launch.v1";
|
|
44
44
|
runId: string;
|
|
45
45
|
generation: number;
|
|
46
|
-
|
|
46
|
+
runnerEpoch: string;
|
|
47
47
|
projectPath: string;
|
|
48
48
|
workflowSource: JsonValue;
|
|
49
49
|
definitionDigest: string;
|
|
@@ -51,7 +51,7 @@ export type WorkerLaunchEnvelope = {
|
|
|
51
51
|
protocolVersion: 1;
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
-
export type
|
|
54
|
+
export type RunnerOutcome =
|
|
55
55
|
| "exited"
|
|
56
56
|
| "cancelled"
|
|
57
57
|
| "timedOut"
|
|
@@ -86,7 +86,7 @@ export type InteractiveRequestRecord = {
|
|
|
86
86
|
consumedAt: string | null;
|
|
87
87
|
};
|
|
88
88
|
|
|
89
|
-
export class
|
|
89
|
+
export class ServerStateStore {
|
|
90
90
|
readonly state: StateDatabase;
|
|
91
91
|
private readonly ownsState: boolean;
|
|
92
92
|
readonly workflowMessages: WorkflowMessageStore;
|
|
@@ -106,19 +106,19 @@ export class HostStateStore {
|
|
|
106
106
|
if (this.ownsState) this.state.close();
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
acquireServer(options: {
|
|
110
|
+
serverId: string;
|
|
111
111
|
pid: number;
|
|
112
112
|
processStartIdentity: string;
|
|
113
113
|
leaseMs: number;
|
|
114
114
|
now?: number;
|
|
115
|
-
}):
|
|
115
|
+
}): ServerClaim {
|
|
116
116
|
const now = options.now ?? Date.now();
|
|
117
117
|
requireLeaseMs(options.leaseMs);
|
|
118
118
|
return this.state.transaction(() => {
|
|
119
|
-
const current = this.
|
|
120
|
-
if (current.
|
|
121
|
-
throw new Error(`A live Pi Workflows
|
|
119
|
+
const current = this.serverRow();
|
|
120
|
+
if (current.serverId !== null && current.expiresAt !== null && current.expiresAt > now) {
|
|
121
|
+
throw new Error(`A live Pi Workflows server already owns epoch ${current.epoch}`);
|
|
122
122
|
}
|
|
123
123
|
const token = randomBytes(32).toString("base64url");
|
|
124
124
|
const epoch = current.epoch + 1;
|
|
@@ -132,7 +132,7 @@ export class HostStateStore {
|
|
|
132
132
|
)
|
|
133
133
|
.run(
|
|
134
134
|
epoch,
|
|
135
|
-
options.
|
|
135
|
+
options.serverId,
|
|
136
136
|
tokenHash(token),
|
|
137
137
|
options.pid,
|
|
138
138
|
options.processStartIdentity,
|
|
@@ -142,9 +142,10 @@ export class HostStateStore {
|
|
|
142
142
|
current.epoch,
|
|
143
143
|
now,
|
|
144
144
|
);
|
|
145
|
-
if (changed.changes !== 1)
|
|
145
|
+
if (changed.changes !== 1)
|
|
146
|
+
throw new Error("Pi Workflows server claim changed during startup");
|
|
146
147
|
return {
|
|
147
|
-
|
|
148
|
+
serverId: options.serverId,
|
|
148
149
|
token,
|
|
149
150
|
epoch,
|
|
150
151
|
pid: options.pid,
|
|
@@ -154,7 +155,7 @@ export class HostStateStore {
|
|
|
154
155
|
});
|
|
155
156
|
}
|
|
156
157
|
|
|
157
|
-
|
|
158
|
+
renewServer(claim: ServerClaim, leaseMs: number, now: number = Date.now()): ServerClaim {
|
|
158
159
|
requireLeaseMs(leaseMs);
|
|
159
160
|
return this.state.transaction(() => {
|
|
160
161
|
const expiresAt = now + leaseMs;
|
|
@@ -168,18 +169,18 @@ export class HostStateStore {
|
|
|
168
169
|
now,
|
|
169
170
|
expiresAt,
|
|
170
171
|
claim.epoch,
|
|
171
|
-
claim.
|
|
172
|
+
claim.serverId,
|
|
172
173
|
tokenHash(claim.token),
|
|
173
174
|
claim.pid,
|
|
174
175
|
claim.processStartIdentity,
|
|
175
176
|
now,
|
|
176
177
|
);
|
|
177
|
-
if (changed.changes !== 1) throw new Error("Pi Workflows
|
|
178
|
+
if (changed.changes !== 1) throw new Error("Pi Workflows server claim lost");
|
|
178
179
|
return { ...claim, expiresAt };
|
|
179
180
|
});
|
|
180
181
|
}
|
|
181
182
|
|
|
182
|
-
|
|
183
|
+
releaseServer(claim: ServerClaim, _now: number = Date.now()): boolean {
|
|
183
184
|
return this.state.transaction(
|
|
184
185
|
() =>
|
|
185
186
|
this.state.connection
|
|
@@ -192,7 +193,7 @@ export class HostStateStore {
|
|
|
192
193
|
)
|
|
193
194
|
.run(
|
|
194
195
|
claim.epoch,
|
|
195
|
-
claim.
|
|
196
|
+
claim.serverId,
|
|
196
197
|
tokenHash(claim.token),
|
|
197
198
|
claim.pid,
|
|
198
199
|
claim.processStartIdentity,
|
|
@@ -200,17 +201,17 @@ export class HostStateStore {
|
|
|
200
201
|
);
|
|
201
202
|
}
|
|
202
203
|
|
|
203
|
-
|
|
204
|
-
const row = this.
|
|
204
|
+
serverStatus(now: number = Date.now()): WorkflowServerStatusRecord {
|
|
205
|
+
const row = this.serverRow();
|
|
205
206
|
return {
|
|
206
207
|
epoch: row.epoch,
|
|
207
|
-
|
|
208
|
+
serverId: row.serverId,
|
|
208
209
|
pid: row.pid,
|
|
209
210
|
processStartIdentity: row.processStartIdentity,
|
|
210
211
|
startedAt: iso(row.startedAt),
|
|
211
212
|
heartbeatAt: iso(row.heartbeatAt),
|
|
212
213
|
expiresAt: iso(row.expiresAt),
|
|
213
|
-
live: row.
|
|
214
|
+
live: row.serverId !== null && row.expiresAt !== null && row.expiresAt > now,
|
|
214
215
|
};
|
|
215
216
|
}
|
|
216
217
|
|
|
@@ -260,7 +261,7 @@ export class HostStateStore {
|
|
|
260
261
|
|
|
261
262
|
executeCommand(
|
|
262
263
|
request: ClientRequest,
|
|
263
|
-
|
|
264
|
+
serverEpoch: number,
|
|
264
265
|
operation: () => Omit<ClientResponse, "schema" | "type" | "requestId">,
|
|
265
266
|
): ClientResponse {
|
|
266
267
|
const existing = this.adoptCommand(request);
|
|
@@ -300,7 +301,7 @@ export class HostStateStore {
|
|
|
300
301
|
response.outcome,
|
|
301
302
|
receiptHash,
|
|
302
303
|
errorHash,
|
|
303
|
-
|
|
304
|
+
serverEpoch,
|
|
304
305
|
now,
|
|
305
306
|
now,
|
|
306
307
|
);
|
|
@@ -308,7 +309,7 @@ export class HostStateStore {
|
|
|
308
309
|
});
|
|
309
310
|
}
|
|
310
311
|
|
|
311
|
-
|
|
312
|
+
recordRunnerStart(envelope: WorkflowRunnerLaunchEnvelope, serverEpoch: number): void {
|
|
312
313
|
const now = Date.now();
|
|
313
314
|
this.state.transaction(() => {
|
|
314
315
|
const launchHash = this.state.putJson(envelope, now);
|
|
@@ -319,34 +320,41 @@ export class HostStateStore {
|
|
|
319
320
|
status, started_at
|
|
320
321
|
) VALUES (?, ?, ?, ?, ?, 'starting', ?)`,
|
|
321
322
|
)
|
|
322
|
-
.run(
|
|
323
|
+
.run(
|
|
324
|
+
envelope.runnerEpoch,
|
|
325
|
+
envelope.runId,
|
|
326
|
+
envelope.generation,
|
|
327
|
+
serverEpoch,
|
|
328
|
+
launchHash,
|
|
329
|
+
now,
|
|
330
|
+
);
|
|
323
331
|
});
|
|
324
332
|
}
|
|
325
333
|
|
|
326
|
-
|
|
334
|
+
attachRunnerProcess(runnerEpoch: string, pid: number, processStartIdentity: string): void {
|
|
327
335
|
const changed = this.state.connection
|
|
328
336
|
.prepare(
|
|
329
337
|
`UPDATE run_workers SET pid = ?, process_start_identity = ?
|
|
330
338
|
WHERE worker_epoch = ? AND status = 'starting' AND pid IS NULL`,
|
|
331
339
|
)
|
|
332
|
-
.run(pid, processStartIdentity,
|
|
333
|
-
if (changed.changes !== 1) throw new Error(`
|
|
340
|
+
.run(pid, processStartIdentity, runnerEpoch);
|
|
341
|
+
if (changed.changes !== 1) throw new Error(`Runner epoch is not starting: ${runnerEpoch}`);
|
|
334
342
|
}
|
|
335
343
|
|
|
336
|
-
|
|
344
|
+
markRunnerReady(runnerEpoch: string): void {
|
|
337
345
|
const now = Date.now();
|
|
338
346
|
const changed = this.state.connection
|
|
339
347
|
.prepare(
|
|
340
348
|
`UPDATE run_workers SET status = 'running', ready_at = ?
|
|
341
349
|
WHERE worker_epoch = ? AND status = 'starting'`,
|
|
342
350
|
)
|
|
343
|
-
.run(now,
|
|
344
|
-
if (changed.changes !== 1) throw new Error(`
|
|
351
|
+
.run(now, runnerEpoch);
|
|
352
|
+
if (changed.changes !== 1) throw new Error(`Runner epoch cannot become ready: ${runnerEpoch}`);
|
|
345
353
|
}
|
|
346
354
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
outcome:
|
|
355
|
+
finishRunner(options: {
|
|
356
|
+
runnerEpoch: string;
|
|
357
|
+
outcome: RunnerOutcome;
|
|
350
358
|
exitCode?: number | null;
|
|
351
359
|
signal?: string | null;
|
|
352
360
|
diagnostic?: string;
|
|
@@ -367,36 +375,39 @@ export class HostStateStore {
|
|
|
367
375
|
options.exitCode ?? null,
|
|
368
376
|
options.signal ?? null,
|
|
369
377
|
diagnosticHash,
|
|
370
|
-
options.
|
|
378
|
+
options.runnerEpoch,
|
|
371
379
|
);
|
|
372
380
|
if (changed.changes !== 1)
|
|
373
|
-
throw new Error(`
|
|
381
|
+
throw new Error(`Runner epoch is not active: ${options.runnerEpoch}`);
|
|
374
382
|
});
|
|
375
383
|
}
|
|
376
384
|
|
|
377
|
-
|
|
385
|
+
readRunnerMessage(message: WorkflowRunnerMessage): WorkflowRunnerResponse | undefined {
|
|
378
386
|
const row = this.state.connection
|
|
379
387
|
.prepare(
|
|
380
388
|
`SELECT request_fingerprint AS clientRequestFingerprint, outcome,
|
|
381
389
|
accepted_revision AS revision, result_hash AS resultHash, error_hash AS errorHash
|
|
382
390
|
FROM worker_messages WHERE worker_epoch = ? AND message_id = ?`,
|
|
383
391
|
)
|
|
384
|
-
.get(message.
|
|
385
|
-
if (!
|
|
386
|
-
if (!row.clientRequestFingerprint.equals(
|
|
392
|
+
.get(message.runnerEpoch, message.messageId);
|
|
393
|
+
if (!isRunnerMessageRow(row)) return undefined;
|
|
394
|
+
if (!row.clientRequestFingerprint.equals(runnerMessageFingerprint(message))) {
|
|
387
395
|
return {
|
|
388
396
|
schema: "pi-workflows.worker-response.v1",
|
|
389
397
|
messageId: message.messageId,
|
|
390
398
|
outcome: "rejected",
|
|
391
|
-
error: "
|
|
399
|
+
error: "Runner message ID was reused with another payload",
|
|
392
400
|
};
|
|
393
401
|
}
|
|
394
|
-
return
|
|
402
|
+
return runnerMessageResponse(this.state, message.messageId, row);
|
|
395
403
|
}
|
|
396
404
|
|
|
397
|
-
|
|
405
|
+
recordRunnerMessage(
|
|
406
|
+
message: WorkflowRunnerMessage,
|
|
407
|
+
response: WorkflowRunnerResponse,
|
|
408
|
+
): WorkflowRunnerResponse {
|
|
398
409
|
return this.state.transaction(() => {
|
|
399
|
-
const existing = this.
|
|
410
|
+
const existing = this.readRunnerMessage(message);
|
|
400
411
|
if (existing !== undefined) return existing;
|
|
401
412
|
const now = Date.now();
|
|
402
413
|
const resultHash =
|
|
@@ -411,9 +422,9 @@ export class HostStateStore {
|
|
|
411
422
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
412
423
|
)
|
|
413
424
|
.run(
|
|
414
|
-
message.
|
|
425
|
+
message.runnerEpoch,
|
|
415
426
|
message.messageId,
|
|
416
|
-
|
|
427
|
+
runnerMessageFingerprint(message),
|
|
417
428
|
response.outcome,
|
|
418
429
|
response.revision ?? null,
|
|
419
430
|
resultHash,
|
|
@@ -1055,16 +1066,16 @@ export class HostStateStore {
|
|
|
1055
1066
|
});
|
|
1056
1067
|
}
|
|
1057
1068
|
|
|
1058
|
-
private
|
|
1069
|
+
private serverRow(): ServerRow {
|
|
1059
1070
|
const row = this.state.connection
|
|
1060
1071
|
.prepare(
|
|
1061
|
-
`SELECT epoch, host_id AS
|
|
1072
|
+
`SELECT epoch, host_id AS serverId, token_hash AS tokenHash, pid,
|
|
1062
1073
|
process_start_identity AS processStartIdentity, started_at AS startedAt,
|
|
1063
1074
|
heartbeat_at AS heartbeatAt, expires_at AS expiresAt
|
|
1064
1075
|
FROM workflow_host_state WHERE id = 1`,
|
|
1065
1076
|
)
|
|
1066
1077
|
.get();
|
|
1067
|
-
if (!
|
|
1078
|
+
if (!isServerRow(row)) throw new Error("Pi Workflows server state is missing");
|
|
1068
1079
|
return row;
|
|
1069
1080
|
}
|
|
1070
1081
|
|
|
@@ -1151,16 +1162,16 @@ function conflictResponse(requestId: string, error: string): ClientResponse {
|
|
|
1151
1162
|
|
|
1152
1163
|
function requireLeaseMs(value: number): void {
|
|
1153
1164
|
if (!Number.isSafeInteger(value) || value <= 0)
|
|
1154
|
-
throw new Error("
|
|
1165
|
+
throw new Error("Server lease duration must be positive");
|
|
1155
1166
|
}
|
|
1156
1167
|
|
|
1157
1168
|
function iso(value: number | null): string | null {
|
|
1158
1169
|
return value === null ? null : new Date(value).toISOString();
|
|
1159
1170
|
}
|
|
1160
1171
|
|
|
1161
|
-
type
|
|
1172
|
+
type ServerRow = {
|
|
1162
1173
|
epoch: number;
|
|
1163
|
-
|
|
1174
|
+
serverId: string | null;
|
|
1164
1175
|
tokenHash: Buffer | null;
|
|
1165
1176
|
pid: number | null;
|
|
1166
1177
|
processStartIdentity: string | null;
|
|
@@ -1196,9 +1207,9 @@ type SubmissionDetailRow = {
|
|
|
1196
1207
|
receiptHash: Buffer | null;
|
|
1197
1208
|
submittedAt: number;
|
|
1198
1209
|
};
|
|
1199
|
-
type
|
|
1210
|
+
type WorkflowRunnerMessageRow = {
|
|
1200
1211
|
clientRequestFingerprint: Buffer;
|
|
1201
|
-
outcome:
|
|
1212
|
+
outcome: WorkflowRunnerResponse["outcome"];
|
|
1202
1213
|
revision: number | null;
|
|
1203
1214
|
resultHash: Buffer | null;
|
|
1204
1215
|
errorHash: Buffer | null;
|
|
@@ -1249,11 +1260,11 @@ type InteractiveRequestRow = {
|
|
|
1249
1260
|
consumedAt: number | null;
|
|
1250
1261
|
};
|
|
1251
1262
|
|
|
1252
|
-
function
|
|
1263
|
+
function isServerRow(value: unknown): value is ServerRow {
|
|
1253
1264
|
return (
|
|
1254
1265
|
isRecord(value) &&
|
|
1255
1266
|
typeof value.epoch === "number" &&
|
|
1256
|
-
nullableString(value.
|
|
1267
|
+
nullableString(value.serverId) &&
|
|
1257
1268
|
(value.tokenHash === null || Buffer.isBuffer(value.tokenHash)) &&
|
|
1258
1269
|
(value.pid === null || typeof value.pid === "number") &&
|
|
1259
1270
|
nullableString(value.processStartIdentity) &&
|
|
@@ -1290,7 +1301,7 @@ function isRunIdRow(value: unknown): value is RunIdRow {
|
|
|
1290
1301
|
return isRecord(value) && typeof value.runId === "string";
|
|
1291
1302
|
}
|
|
1292
1303
|
|
|
1293
|
-
function
|
|
1304
|
+
function isRunnerMessageRow(value: unknown): value is WorkflowRunnerMessageRow {
|
|
1294
1305
|
return (
|
|
1295
1306
|
isRecord(value) &&
|
|
1296
1307
|
Buffer.isBuffer(value.clientRequestFingerprint) &&
|
|
@@ -1401,15 +1412,15 @@ function isInteractiveRequestRow(value: unknown): value is InteractiveRequestRow
|
|
|
1401
1412
|
);
|
|
1402
1413
|
}
|
|
1403
1414
|
|
|
1404
|
-
function
|
|
1415
|
+
function runnerMessageFingerprint(message: WorkflowRunnerMessage): Buffer {
|
|
1405
1416
|
return createHash("sha256").update(canonicalJson(message)).digest();
|
|
1406
1417
|
}
|
|
1407
1418
|
|
|
1408
|
-
function
|
|
1419
|
+
function runnerMessageResponse(
|
|
1409
1420
|
state: StateDatabase,
|
|
1410
1421
|
messageId: string,
|
|
1411
|
-
row:
|
|
1412
|
-
):
|
|
1422
|
+
row: WorkflowRunnerMessageRow,
|
|
1423
|
+
): WorkflowRunnerResponse {
|
|
1413
1424
|
const result = row.resultHash === null ? undefined : state.readJson(row.resultHash);
|
|
1414
1425
|
const error =
|
|
1415
1426
|
row.errorHash === null ? undefined : state.readBlob(row.errorHash)?.content.toString("utf8");
|
|
@@ -11,10 +11,10 @@ import {
|
|
|
11
11
|
type WorkflowSessionView,
|
|
12
12
|
} from "../client/view.js";
|
|
13
13
|
import type {
|
|
14
|
-
|
|
14
|
+
SqliteResourceManagerStore,
|
|
15
15
|
WorkflowRunQueueRecord,
|
|
16
16
|
WorkflowRunQueueViewRecord,
|
|
17
|
-
} from "../
|
|
17
|
+
} from "../resource-managers/sqlite.js";
|
|
18
18
|
import type { StateDatabase } from "../state/database.js";
|
|
19
19
|
import { canonicalJson, parseJson, type JsonValue } from "../state/json.js";
|
|
20
20
|
import { WorkflowMessageStore, type WorkflowMessage } from "../state/workflow-messages.js";
|
|
@@ -27,7 +27,7 @@ import type {
|
|
|
27
27
|
WorkflowTraceEvent,
|
|
28
28
|
WorkflowUpdateRecord,
|
|
29
29
|
} from "../workflows/types.js";
|
|
30
|
-
import type {
|
|
30
|
+
import type { ServerStateStore } from "./state.js";
|
|
31
31
|
|
|
32
32
|
export const WORKFLOW_PAGE_KINDS = [
|
|
33
33
|
"steps",
|
|
@@ -63,7 +63,7 @@ const CONTENT_CACHE_BYTES = 64 * 1024 * 1024;
|
|
|
63
63
|
const VIEW_CACHE_ITEMS = 64;
|
|
64
64
|
const TERMINAL_VIEW_RETENTION_MS = 60_000;
|
|
65
65
|
|
|
66
|
-
export class
|
|
66
|
+
export class ServerViewStore {
|
|
67
67
|
private readonly contentRecords = new Map<string, ContentRecord>();
|
|
68
68
|
private readonly listCache = new Map<string, { revision: string; page: WorkflowRunListPage }>();
|
|
69
69
|
private readonly runCache = new Map<string, { version: string; view: WorkflowRunView | null }>();
|
|
@@ -74,13 +74,13 @@ export class HostViewStore {
|
|
|
74
74
|
|
|
75
75
|
constructor(
|
|
76
76
|
private readonly state: StateDatabase,
|
|
77
|
-
private readonly queue:
|
|
78
|
-
private readonly
|
|
77
|
+
private readonly queue: SqliteResourceManagerStore,
|
|
78
|
+
private readonly serverState: ServerStateStore,
|
|
79
79
|
private readonly runs: WorkflowRunStore,
|
|
80
|
-
private readonly
|
|
80
|
+
private readonly hasLiveRunner: (runId: string) => boolean,
|
|
81
81
|
private readonly hasActiveSessionTurn: (targetSessionId: string) => boolean,
|
|
82
82
|
) {
|
|
83
|
-
this.workflowMessages =
|
|
83
|
+
this.workflowMessages = serverState.workflowMessages;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
noteOriginActivityChange(): void {
|
|
@@ -324,7 +324,7 @@ export class HostViewStore {
|
|
|
324
324
|
const retainedRunId =
|
|
325
325
|
activeQueue === undefined ? this.retainedTerminalRunId(sessionId) : undefined;
|
|
326
326
|
const runId = activeQueue?.runId ?? retainedRunId;
|
|
327
|
-
const pending = this.
|
|
327
|
+
const pending = this.serverState.listPendingInteractions(sessionId);
|
|
328
328
|
const pendingInteractions = byteBoundedForwardPage(pending, (request) =>
|
|
329
329
|
this.projectRecordField(request.runId, request, "contract"),
|
|
330
330
|
);
|
|
@@ -613,7 +613,7 @@ export class HostViewStore {
|
|
|
613
613
|
}
|
|
614
614
|
|
|
615
615
|
clearConnection(_connectionId: string): void {
|
|
616
|
-
// Coordinator fencing is process-local in the
|
|
616
|
+
// Coordinator fencing is process-local in the server. Durable turn state is
|
|
617
617
|
// reconciled from the Pi branch after the next coordinator connects.
|
|
618
618
|
}
|
|
619
619
|
|
|
@@ -792,7 +792,7 @@ export class HostViewStore {
|
|
|
792
792
|
durableStatus: state?.status,
|
|
793
793
|
paused: state?.paused === true,
|
|
794
794
|
ambiguous: this.hasAmbiguousEffect(queue.runId),
|
|
795
|
-
|
|
795
|
+
runnerActive: this.hasLiveRunner(queue.runId),
|
|
796
796
|
originTurnActive: this.hasActivity(queue.runId),
|
|
797
797
|
pendingInteraction: this.hasPendingInteraction(queue.runId),
|
|
798
798
|
errorMessage: state?.error ?? queue.errorMessage,
|
|
@@ -855,7 +855,7 @@ export class HostViewStore {
|
|
|
855
855
|
row.runStatus,
|
|
856
856
|
row.paused,
|
|
857
857
|
this.workflowActivityRevision(),
|
|
858
|
-
this.
|
|
858
|
+
this.hasLiveRunner(runId),
|
|
859
859
|
this.hasActivity(runId),
|
|
860
860
|
this.hasPendingInteraction(runId),
|
|
861
861
|
this.hasAmbiguousEffect(runId),
|
|
@@ -888,7 +888,7 @@ export type WorkflowDisplayFacts = {
|
|
|
888
888
|
durableStatus: WorkflowRunState["status"] | undefined;
|
|
889
889
|
paused: boolean;
|
|
890
890
|
ambiguous: boolean;
|
|
891
|
-
|
|
891
|
+
runnerActive: boolean;
|
|
892
892
|
originTurnActive: boolean;
|
|
893
893
|
pendingInteraction: boolean;
|
|
894
894
|
errorMessage: string | null;
|
|
@@ -902,9 +902,9 @@ export function reduceWorkflowDisplay(facts: WorkflowDisplayFacts): WorkflowDisp
|
|
|
902
902
|
if (facts.ambiguous) {
|
|
903
903
|
status = "ambiguous";
|
|
904
904
|
reason = "An external effect needs explicit recovery.";
|
|
905
|
-
} else if (facts.
|
|
905
|
+
} else if (facts.runnerActive || facts.originTurnActive) {
|
|
906
906
|
status = "running";
|
|
907
|
-
activity = facts.
|
|
907
|
+
activity = facts.runnerActive ? "supervised_runner" : "origin_turn";
|
|
908
908
|
} else if (
|
|
909
909
|
facts.durableStatus === "completed" ||
|
|
910
910
|
facts.durableStatus === "failed" ||
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import type { StateDatabase } from "../state/database.js";
|
|
3
|
+
import { canonicalJson, parseJson, type JsonValue } from "../state/json.js";
|
|
4
|
+
import {
|
|
5
|
+
WORKFLOW_RUNNER_CONTENT_CHUNK_BYTES,
|
|
6
|
+
WORKFLOW_RUNNER_CONTENT_CHUNK_SCHEMA,
|
|
7
|
+
WORKFLOW_RUNNER_CONTENT_REFERENCE_SCHEMA,
|
|
8
|
+
encodeRunnerLine,
|
|
9
|
+
type WorkflowRunnerContentChunk,
|
|
10
|
+
type WorkflowRunnerContentReference,
|
|
11
|
+
type WorkflowRunnerResponse,
|
|
12
|
+
} from "./workflow-runner-protocol.js";
|
|
13
|
+
|
|
14
|
+
const CONTENT_MEDIA_TYPE = "application/json" as const;
|
|
15
|
+
|
|
16
|
+
export function boundRunnerResponse(
|
|
17
|
+
state: StateDatabase,
|
|
18
|
+
allowedDigests: Set<string>,
|
|
19
|
+
response: WorkflowRunnerResponse,
|
|
20
|
+
): WorkflowRunnerResponse {
|
|
21
|
+
try {
|
|
22
|
+
encodeRunnerLine(response);
|
|
23
|
+
return response;
|
|
24
|
+
} catch {
|
|
25
|
+
if (response.result === undefined) return responseTooLarge(response.messageId);
|
|
26
|
+
try {
|
|
27
|
+
const content = Buffer.from(canonicalJson(response.result), "utf8");
|
|
28
|
+
const sha256 = state.putBlob(content, CONTENT_MEDIA_TYPE).toString("hex");
|
|
29
|
+
allowedDigests.add(sha256);
|
|
30
|
+
const referenced: WorkflowRunnerResponse = {
|
|
31
|
+
...response,
|
|
32
|
+
result: {
|
|
33
|
+
schema: WORKFLOW_RUNNER_CONTENT_REFERENCE_SCHEMA,
|
|
34
|
+
sha256,
|
|
35
|
+
mediaType: CONTENT_MEDIA_TYPE,
|
|
36
|
+
bytes: content.byteLength,
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
encodeRunnerLine(referenced);
|
|
40
|
+
return referenced;
|
|
41
|
+
} catch {
|
|
42
|
+
return responseTooLarge(response.messageId);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function readRunnerContentChunk(
|
|
48
|
+
state: StateDatabase,
|
|
49
|
+
allowedDigests: ReadonlySet<string>,
|
|
50
|
+
sha256: string,
|
|
51
|
+
offset: number,
|
|
52
|
+
): WorkflowRunnerContentChunk {
|
|
53
|
+
if (!/^[0-9a-f]{64}$/u.test(sha256) || !allowedDigests.has(sha256)) {
|
|
54
|
+
throw new Error("Workflow runner content is unavailable");
|
|
55
|
+
}
|
|
56
|
+
if (!Number.isSafeInteger(offset) || offset < 0) {
|
|
57
|
+
throw new Error("Workflow runner content offset is invalid");
|
|
58
|
+
}
|
|
59
|
+
const stored = state.readBlob(Buffer.from(sha256, "hex"));
|
|
60
|
+
if (stored === undefined || stored.mediaType !== CONTENT_MEDIA_TYPE) {
|
|
61
|
+
throw new Error("Workflow runner content is unavailable");
|
|
62
|
+
}
|
|
63
|
+
if (offset > stored.byteLength) throw new Error("Workflow runner content offset is invalid");
|
|
64
|
+
const nextOffset = Math.min(offset + WORKFLOW_RUNNER_CONTENT_CHUNK_BYTES, stored.byteLength);
|
|
65
|
+
return {
|
|
66
|
+
schema: WORKFLOW_RUNNER_CONTENT_CHUNK_SCHEMA,
|
|
67
|
+
sha256,
|
|
68
|
+
mediaType: CONTENT_MEDIA_TYPE,
|
|
69
|
+
bytes: stored.byteLength,
|
|
70
|
+
offset,
|
|
71
|
+
nextOffset,
|
|
72
|
+
complete: nextOffset === stored.byteLength,
|
|
73
|
+
data: stored.content.subarray(offset, nextOffset).toString("base64url"),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function materializeRunnerContent(
|
|
78
|
+
reference: WorkflowRunnerContentReference,
|
|
79
|
+
readChunk: (offset: number) => Promise<WorkflowRunnerContentChunk>,
|
|
80
|
+
): Promise<JsonValue> {
|
|
81
|
+
const parts: Buffer[] = [];
|
|
82
|
+
let offset = 0;
|
|
83
|
+
for (;;) {
|
|
84
|
+
const chunk = await readChunk(offset);
|
|
85
|
+
const bytes = Buffer.from(chunk.data, "base64url");
|
|
86
|
+
if (
|
|
87
|
+
chunk.sha256 !== reference.sha256 ||
|
|
88
|
+
chunk.mediaType !== reference.mediaType ||
|
|
89
|
+
chunk.bytes !== reference.bytes ||
|
|
90
|
+
chunk.offset !== offset ||
|
|
91
|
+
chunk.nextOffset !== offset + bytes.byteLength ||
|
|
92
|
+
chunk.nextOffset > reference.bytes ||
|
|
93
|
+
chunk.complete !== (chunk.nextOffset === reference.bytes)
|
|
94
|
+
) {
|
|
95
|
+
throw new Error("Workflow runner content chunk is invalid");
|
|
96
|
+
}
|
|
97
|
+
parts.push(bytes);
|
|
98
|
+
offset = chunk.nextOffset;
|
|
99
|
+
if (chunk.complete) break;
|
|
100
|
+
if (bytes.byteLength === 0) throw new Error("Workflow runner content read made no progress");
|
|
101
|
+
}
|
|
102
|
+
const content = Buffer.concat(parts);
|
|
103
|
+
if (
|
|
104
|
+
content.byteLength !== reference.bytes ||
|
|
105
|
+
createHash("sha256").update(content).digest("hex") !== reference.sha256
|
|
106
|
+
) {
|
|
107
|
+
throw new Error("Workflow runner content does not match its reference");
|
|
108
|
+
}
|
|
109
|
+
return parseJson(content.toString("utf8"));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function responseTooLarge(messageId: string): WorkflowRunnerResponse {
|
|
113
|
+
return {
|
|
114
|
+
schema: "pi-workflows.worker-response.v1",
|
|
115
|
+
messageId,
|
|
116
|
+
outcome: "rejected",
|
|
117
|
+
error: "Workflow runner response could not be transferred within the protocol limit",
|
|
118
|
+
};
|
|
119
|
+
}
|