@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
|
@@ -25,21 +25,28 @@ import type {
|
|
|
25
25
|
WorkflowSource,
|
|
26
26
|
} from "../workflows/types.js";
|
|
27
27
|
import { RpcStepExecutor } from "./rpc-executor.js";
|
|
28
|
-
import type {
|
|
28
|
+
import type { WorkflowRunnerLaunchEnvelope } from "./state.js";
|
|
29
|
+
import { materializeRunnerContent } from "./workflow-runner-content.js";
|
|
29
30
|
import {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
31
|
+
MAX_WORKFLOW_RUNNER_PROTOCOL_MESSAGE_BYTES,
|
|
32
|
+
encodeRunnerLine,
|
|
33
|
+
isRunnerContentChunk,
|
|
34
|
+
isRunnerContentReference,
|
|
35
|
+
parseRunnerResponse,
|
|
36
|
+
type WorkflowRunnerMessage,
|
|
37
|
+
type WorkflowRunnerResponse,
|
|
38
|
+
type WorkflowRunnerCommand,
|
|
39
|
+
} from "./workflow-runner-protocol.js";
|
|
40
|
+
import {
|
|
41
|
+
ServerBackedWorkflowStore,
|
|
42
|
+
type WorkflowRunnerStoreTransport,
|
|
43
|
+
} from "./workflow-runner-store.js";
|
|
37
44
|
|
|
38
|
-
const STARTUP_ENV = "
|
|
45
|
+
const STARTUP_ENV = "PI_WORKFLOWS_WORKFLOW_RUNNER_LAUNCH";
|
|
39
46
|
const PRESENTATION_TIMEOUT_MS = 30_000;
|
|
40
47
|
|
|
41
|
-
type
|
|
42
|
-
command:
|
|
48
|
+
type WorkflowRunnerBootstrap = {
|
|
49
|
+
command: WorkflowRunnerCommand;
|
|
43
50
|
originSessionId: string | null;
|
|
44
51
|
stateDirectory: string;
|
|
45
52
|
piArgs?: string[];
|
|
@@ -52,21 +59,21 @@ type WorkerBootstrap = {
|
|
|
52
59
|
};
|
|
53
60
|
};
|
|
54
61
|
|
|
55
|
-
class
|
|
62
|
+
class StdioRunnerTransport implements WorkflowRunnerStoreTransport {
|
|
56
63
|
private readonly pending = new Map<
|
|
57
64
|
string,
|
|
58
|
-
{ resolve: (response:
|
|
65
|
+
{ resolve: (response: WorkflowRunnerResponse) => void; reject: (error: Error) => void }
|
|
59
66
|
>();
|
|
60
67
|
private buffered = Buffer.alloc(0);
|
|
61
68
|
|
|
62
|
-
constructor(private readonly launch:
|
|
69
|
+
constructor(private readonly launch: WorkflowRunnerLaunchEnvelope) {
|
|
63
70
|
process.stdin.on("data", (chunk: Buffer) => this.onData(chunk));
|
|
64
71
|
process.stdin.on("error", (error) => this.failAll(error));
|
|
65
72
|
process.stdin.resume();
|
|
66
73
|
}
|
|
67
74
|
|
|
68
|
-
async request(options: Parameters<
|
|
69
|
-
const message:
|
|
75
|
+
async request(options: Parameters<WorkflowRunnerStoreTransport["request"]>[0]) {
|
|
76
|
+
const message: WorkflowRunnerMessage = {
|
|
70
77
|
schema: "pi-workflows.worker-message.v1",
|
|
71
78
|
launchSchema: this.launch.schema,
|
|
72
79
|
messageId: options.messageId,
|
|
@@ -74,17 +81,17 @@ class StdioWorkerTransport implements WorkerStoreTransport {
|
|
|
74
81
|
operation: options.operation,
|
|
75
82
|
runId: this.launch.runId,
|
|
76
83
|
generation: this.launch.generation,
|
|
77
|
-
|
|
84
|
+
runnerEpoch: this.launch.runnerEpoch,
|
|
78
85
|
expectedRevision: options.expectedRevision,
|
|
79
86
|
...(options.attemptId === undefined ? {} : { attemptId: options.attemptId }),
|
|
80
87
|
payload: options.payload,
|
|
81
88
|
};
|
|
82
|
-
const response = await this.
|
|
89
|
+
const response = await this.sendResolved(message);
|
|
83
90
|
if (response.outcome === "claimLost") {
|
|
84
91
|
throw new ClaimLostError(this.launch.runId, "ownerChanged");
|
|
85
92
|
}
|
|
86
93
|
if (response.outcome === "rejected") {
|
|
87
|
-
throw new Error(response.error ?? "Workflow
|
|
94
|
+
throw new Error(response.error ?? "Workflow runner message was rejected");
|
|
88
95
|
}
|
|
89
96
|
return {
|
|
90
97
|
...(response.result === undefined ? {} : { result: response.result }),
|
|
@@ -93,10 +100,10 @@ class StdioWorkerTransport implements WorkerStoreTransport {
|
|
|
93
100
|
}
|
|
94
101
|
|
|
95
102
|
async control(
|
|
96
|
-
operation: "
|
|
103
|
+
operation: "runner.ready" | "runner.exiting",
|
|
97
104
|
payload: JsonValue,
|
|
98
|
-
): Promise<
|
|
99
|
-
return await this.
|
|
105
|
+
): Promise<WorkflowRunnerResponse> {
|
|
106
|
+
return await this.sendResolved({
|
|
100
107
|
schema: "pi-workflows.worker-message.v1",
|
|
101
108
|
launchSchema: this.launch.schema,
|
|
102
109
|
messageId: randomUUID(),
|
|
@@ -104,7 +111,7 @@ class StdioWorkerTransport implements WorkerStoreTransport {
|
|
|
104
111
|
operation,
|
|
105
112
|
runId: this.launch.runId,
|
|
106
113
|
generation: this.launch.generation,
|
|
107
|
-
|
|
114
|
+
runnerEpoch: this.launch.runnerEpoch,
|
|
108
115
|
expectedRevision: 0,
|
|
109
116
|
payload,
|
|
110
117
|
});
|
|
@@ -114,21 +121,55 @@ class StdioWorkerTransport implements WorkerStoreTransport {
|
|
|
114
121
|
process.stdin.pause();
|
|
115
122
|
process.stdin.removeAllListeners("data");
|
|
116
123
|
process.stdin.removeAllListeners("error");
|
|
117
|
-
this.failAll(new Error("Workflow
|
|
124
|
+
this.failAll(new Error("Workflow runner transport closed"));
|
|
118
125
|
}
|
|
119
126
|
|
|
120
|
-
private async
|
|
121
|
-
const response =
|
|
127
|
+
private async sendResolved(message: WorkflowRunnerMessage): Promise<WorkflowRunnerResponse> {
|
|
128
|
+
const response = await this.send(message);
|
|
129
|
+
if (!isRunnerContentReference(response.result)) return response;
|
|
130
|
+
const reference = response.result;
|
|
131
|
+
const expectedRevision = response.revision ?? message.expectedRevision;
|
|
132
|
+
return {
|
|
133
|
+
...response,
|
|
134
|
+
result: await materializeRunnerContent(reference, async (offset) => {
|
|
135
|
+
const chunkResponse = await this.send({
|
|
136
|
+
schema: "pi-workflows.worker-message.v1",
|
|
137
|
+
launchSchema: this.launch.schema,
|
|
138
|
+
messageId: randomUUID(),
|
|
139
|
+
kind: "runner.progress",
|
|
140
|
+
operation: "content.read",
|
|
141
|
+
runId: this.launch.runId,
|
|
142
|
+
generation: this.launch.generation,
|
|
143
|
+
runnerEpoch: this.launch.runnerEpoch,
|
|
144
|
+
expectedRevision,
|
|
145
|
+
payload: { sha256: reference.sha256, offset },
|
|
146
|
+
});
|
|
147
|
+
if (chunkResponse.outcome === "claimLost") {
|
|
148
|
+
throw new ClaimLostError(this.launch.runId, "ownerChanged");
|
|
149
|
+
}
|
|
150
|
+
if (chunkResponse.outcome !== "accepted" || !isRunnerContentChunk(chunkResponse.result)) {
|
|
151
|
+
throw new Error(chunkResponse.error ?? "Workflow runner content read was rejected");
|
|
152
|
+
}
|
|
153
|
+
return chunkResponse.result;
|
|
154
|
+
}),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private async send(message: WorkflowRunnerMessage): Promise<WorkflowRunnerResponse> {
|
|
159
|
+
const response = new Promise<WorkflowRunnerResponse>((resolve, reject) => {
|
|
122
160
|
this.pending.set(message.messageId, { resolve, reject });
|
|
123
161
|
});
|
|
124
|
-
if (!process.stdout.write(
|
|
162
|
+
if (!process.stdout.write(encodeRunnerLine(message))) await once(process.stdout, "drain");
|
|
125
163
|
return await response;
|
|
126
164
|
}
|
|
127
165
|
|
|
128
166
|
private onData(chunk: Buffer): void {
|
|
129
167
|
this.buffered = Buffer.concat([this.buffered, chunk]);
|
|
130
|
-
if (
|
|
131
|
-
this.
|
|
168
|
+
if (
|
|
169
|
+
this.buffered.byteLength > MAX_WORKFLOW_RUNNER_PROTOCOL_MESSAGE_BYTES &&
|
|
170
|
+
!this.buffered.includes(0x0a)
|
|
171
|
+
) {
|
|
172
|
+
this.failAll(new Error("Runner response exceeds 1 MiB"));
|
|
132
173
|
return;
|
|
133
174
|
}
|
|
134
175
|
for (;;) {
|
|
@@ -138,9 +179,9 @@ class StdioWorkerTransport implements WorkerStoreTransport {
|
|
|
138
179
|
this.buffered = this.buffered.subarray(newline + 1);
|
|
139
180
|
if (frame.byteLength === 0) continue;
|
|
140
181
|
try {
|
|
141
|
-
const response =
|
|
182
|
+
const response = parseRunnerResponse(frame);
|
|
142
183
|
const pending = this.pending.get(response.messageId);
|
|
143
|
-
if (pending === undefined) throw new Error("
|
|
184
|
+
if (pending === undefined) throw new Error("Runner response has no pending request");
|
|
144
185
|
this.pending.delete(response.messageId);
|
|
145
186
|
pending.resolve(response);
|
|
146
187
|
} catch (error) {
|
|
@@ -160,7 +201,7 @@ class InteractiveExecutor implements AgentStepExecutor {
|
|
|
160
201
|
readonly preservesDeadlineWhileParked = true;
|
|
161
202
|
|
|
162
203
|
constructor(
|
|
163
|
-
private readonly store:
|
|
204
|
+
private readonly store: ServerBackedWorkflowStore,
|
|
164
205
|
private readonly candidate:
|
|
165
206
|
| {
|
|
166
207
|
requestId: string;
|
|
@@ -275,12 +316,12 @@ export function validateAcceptedAssistantSubmission(
|
|
|
275
316
|
};
|
|
276
317
|
}
|
|
277
318
|
|
|
278
|
-
export async function
|
|
319
|
+
export async function executeRunnerRunCommand(
|
|
279
320
|
engine: Pick<WorkflowEngine, "run" | "resumeRun" | "continueRun">,
|
|
280
321
|
workflow: WorkflowDefinition,
|
|
281
322
|
runId: string,
|
|
282
323
|
workflowSource: WorkflowSource,
|
|
283
|
-
command:
|
|
324
|
+
command: WorkflowRunnerCommand,
|
|
284
325
|
) {
|
|
285
326
|
switch (command.kind) {
|
|
286
327
|
case "start":
|
|
@@ -304,30 +345,30 @@ export async function executeWorkerRunCommand(
|
|
|
304
345
|
}
|
|
305
346
|
}
|
|
306
347
|
|
|
307
|
-
export async function
|
|
348
|
+
export async function runWorkflowRunner(): Promise<number> {
|
|
308
349
|
const launch = readLaunchEnvelope();
|
|
309
|
-
const transport = new
|
|
350
|
+
const transport = new StdioRunnerTransport(launch);
|
|
310
351
|
try {
|
|
311
|
-
const ready = await transport.control("
|
|
352
|
+
const ready = await transport.control("runner.ready", {});
|
|
312
353
|
if (ready.outcome !== "accepted" || ready.result === undefined) {
|
|
313
|
-
throw new Error(ready.error ?? "Workflow
|
|
354
|
+
throw new Error(ready.error ?? "Workflow server rejected runner startup");
|
|
314
355
|
}
|
|
315
|
-
const bootstrap = ready.result as unknown as
|
|
316
|
-
const workflowSource =
|
|
356
|
+
const bootstrap = ready.result as unknown as WorkflowRunnerBootstrap;
|
|
357
|
+
const workflowSource = parseRunnerWorkflowSources(launch.workflowSource);
|
|
317
358
|
let workflow: WorkflowDefinition;
|
|
318
359
|
try {
|
|
319
360
|
workflow = await resolveVerifiedWorkflow(launch.runId, workflowSource);
|
|
320
361
|
} catch (error) {
|
|
321
|
-
const response = await transport.control("
|
|
362
|
+
const response = await transport.control("runner.exiting", {
|
|
322
363
|
status: "workflowLoadFailed",
|
|
323
364
|
error: errorMessage(error),
|
|
324
365
|
});
|
|
325
366
|
if (response.outcome !== "accepted") {
|
|
326
|
-
throw new Error(response.error ?? "Workflow
|
|
367
|
+
throw new Error(response.error ?? "Workflow server rejected the load-failure report");
|
|
327
368
|
}
|
|
328
369
|
return 1;
|
|
329
370
|
}
|
|
330
|
-
const store = new
|
|
371
|
+
const store = new ServerBackedWorkflowStore(launch.runId, transport, ready.revision ?? 0);
|
|
331
372
|
let executor: AgentStepExecutor;
|
|
332
373
|
let closeExecutor: (() => Promise<void>) | undefined;
|
|
333
374
|
if (bootstrap.originSessionId !== null) {
|
|
@@ -342,7 +383,7 @@ export async function runWorkflowWorker(): Promise<number> {
|
|
|
342
383
|
await transport.request({
|
|
343
384
|
messageId: randomUUID(),
|
|
344
385
|
operation: "process.register",
|
|
345
|
-
kind: "
|
|
386
|
+
kind: "runner.progress",
|
|
346
387
|
expectedRevision: 0,
|
|
347
388
|
payload: { pid },
|
|
348
389
|
});
|
|
@@ -351,7 +392,7 @@ export async function runWorkflowWorker(): Promise<number> {
|
|
|
351
392
|
await transport.request({
|
|
352
393
|
messageId: randomUUID(),
|
|
353
394
|
operation: "process.unregister",
|
|
354
|
-
kind: "
|
|
395
|
+
kind: "runner.progress",
|
|
355
396
|
expectedRevision: 0,
|
|
356
397
|
payload: { pid },
|
|
357
398
|
});
|
|
@@ -387,14 +428,14 @@ export async function runWorkflowWorker(): Promise<number> {
|
|
|
387
428
|
},
|
|
388
429
|
});
|
|
389
430
|
try {
|
|
390
|
-
const result = await
|
|
431
|
+
const result = await executeRunnerRunCommand(
|
|
391
432
|
engine,
|
|
392
433
|
workflow,
|
|
393
434
|
launch.runId,
|
|
394
435
|
workflowSource.root,
|
|
395
436
|
bootstrap.command,
|
|
396
437
|
);
|
|
397
|
-
await transport.control("
|
|
438
|
+
await transport.control("runner.exiting", {
|
|
398
439
|
status: result.state.status,
|
|
399
440
|
traceSeq: result.state.traceSeq,
|
|
400
441
|
});
|
|
@@ -444,25 +485,25 @@ async function resolvePresentationInstructions(
|
|
|
444
485
|
}
|
|
445
486
|
}
|
|
446
487
|
|
|
447
|
-
type
|
|
488
|
+
type WorkflowRunnerSources = {
|
|
448
489
|
root: WorkflowSource;
|
|
449
490
|
mounted: WorkflowMountedSource[];
|
|
450
491
|
};
|
|
451
492
|
|
|
452
|
-
function
|
|
493
|
+
function parseRunnerWorkflowSources(value: JsonValue): WorkflowRunnerSources {
|
|
453
494
|
if (!isRecord(value) || !isWorkflowSource(value.root) || !Array.isArray(value.mounted)) {
|
|
454
|
-
throw new Error("Workflow
|
|
495
|
+
throw new Error("Workflow runner source identity is invalid");
|
|
455
496
|
}
|
|
456
497
|
const mounted = value.mounted;
|
|
457
498
|
if (!mounted.every(isMountedWorkflowSource)) {
|
|
458
|
-
throw new Error("Workflow
|
|
499
|
+
throw new Error("Workflow runner mounted source identity is invalid");
|
|
459
500
|
}
|
|
460
501
|
return { root: value.root, mounted };
|
|
461
502
|
}
|
|
462
503
|
|
|
463
504
|
async function resolveVerifiedWorkflow(
|
|
464
505
|
runId: string,
|
|
465
|
-
sources:
|
|
506
|
+
sources: WorkflowRunnerSources,
|
|
466
507
|
): Promise<WorkflowDefinition> {
|
|
467
508
|
for (const source of [sources.root, ...sources.mounted.map((mounted) => mounted.source)]) {
|
|
468
509
|
if (source.kind === "file") {
|
|
@@ -514,9 +555,9 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
514
555
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
515
556
|
}
|
|
516
557
|
|
|
517
|
-
function readLaunchEnvelope():
|
|
558
|
+
function readLaunchEnvelope(): WorkflowRunnerLaunchEnvelope {
|
|
518
559
|
const encoded = process.env[STARTUP_ENV];
|
|
519
|
-
if (encoded === undefined) throw new Error("Workflow
|
|
560
|
+
if (encoded === undefined) throw new Error("Workflow runner launch envelope is missing");
|
|
520
561
|
const value = parseJson(Buffer.from(encoded, "base64url").toString("utf8"));
|
|
521
562
|
if (
|
|
522
563
|
typeof value !== "object" ||
|
|
@@ -524,14 +565,14 @@ function readLaunchEnvelope(): WorkerLaunchEnvelope {
|
|
|
524
565
|
Array.isArray(value) ||
|
|
525
566
|
(value as { schema?: unknown }).schema !== "pi-workflows.worker-launch.v1"
|
|
526
567
|
) {
|
|
527
|
-
throw new Error("Workflow
|
|
568
|
+
throw new Error("Workflow runner launch envelope is invalid");
|
|
528
569
|
}
|
|
529
|
-
return value as unknown as
|
|
570
|
+
return value as unknown as WorkflowRunnerLaunchEnvelope;
|
|
530
571
|
}
|
|
531
572
|
|
|
532
573
|
async function main(): Promise<void> {
|
|
533
574
|
try {
|
|
534
|
-
process.exitCode = await
|
|
575
|
+
process.exitCode = await runWorkflowRunner();
|
|
535
576
|
} catch (error) {
|
|
536
577
|
process.stderr.write(`${errorMessage(error)}\n`);
|
|
537
578
|
process.exitCode = 1;
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { canonicalJson, parseJson, type JsonValue } from "../state/json.js";
|
|
2
|
+
|
|
3
|
+
export const MAX_WORKFLOW_RUNNER_PROTOCOL_MESSAGE_BYTES = 1024 * 1024;
|
|
4
|
+
export const WORKFLOW_RUNNER_CONTENT_CHUNK_BYTES = 512 * 1024;
|
|
5
|
+
export const WORKFLOW_RUNNER_MESSAGE_SCHEMA = "pi-workflows.worker-message.v1" as const;
|
|
6
|
+
export const WORKFLOW_RUNNER_RESPONSE_SCHEMA = "pi-workflows.worker-response.v1" as const;
|
|
7
|
+
export const WORKFLOW_RUNNER_CONTENT_REFERENCE_SCHEMA =
|
|
8
|
+
"pi-workflows.worker-content-reference.v1" as const;
|
|
9
|
+
export const WORKFLOW_RUNNER_CONTENT_CHUNK_SCHEMA = "pi-workflows.worker-content-chunk.v1" as const;
|
|
10
|
+
|
|
11
|
+
export type WorkflowRunnerCommand =
|
|
12
|
+
| { kind: "start"; input: JsonValue }
|
|
13
|
+
| { kind: "resume"; resumeInteractionAttemptId?: string }
|
|
14
|
+
| {
|
|
15
|
+
kind: "continue";
|
|
16
|
+
parentRunId: string;
|
|
17
|
+
input: JsonValue;
|
|
18
|
+
humanDecision?: JsonValue;
|
|
19
|
+
}
|
|
20
|
+
| { kind: "restart"; input: JsonValue };
|
|
21
|
+
|
|
22
|
+
export const WORKFLOW_RUNNER_MESSAGE_KINDS = [
|
|
23
|
+
"runner.ready",
|
|
24
|
+
"node.started",
|
|
25
|
+
"node.update",
|
|
26
|
+
"node.finished",
|
|
27
|
+
"node.failed",
|
|
28
|
+
"run.parked",
|
|
29
|
+
"run.finished",
|
|
30
|
+
"interaction.requested",
|
|
31
|
+
"interaction.accepted",
|
|
32
|
+
"interaction.rejected",
|
|
33
|
+
"notification.requested",
|
|
34
|
+
"presentation.requested",
|
|
35
|
+
"effect.reserve",
|
|
36
|
+
"effect.settle",
|
|
37
|
+
"runner.progress",
|
|
38
|
+
"runner.exiting",
|
|
39
|
+
] as const;
|
|
40
|
+
|
|
41
|
+
export type WorkflowRunnerMessageKind = (typeof WORKFLOW_RUNNER_MESSAGE_KINDS)[number];
|
|
42
|
+
export const WORKFLOW_RUNNER_STORE_OPERATIONS = [
|
|
43
|
+
"store.initializeRun",
|
|
44
|
+
"store.prepareRunResume",
|
|
45
|
+
"store.readRunState",
|
|
46
|
+
"store.writeSnapshot",
|
|
47
|
+
"store.publishUpdate",
|
|
48
|
+
"store.findSettingsScope",
|
|
49
|
+
"store.ensureSettingsScope",
|
|
50
|
+
"store.getSettingsScopeAtChange",
|
|
51
|
+
"store.createHumanDecisionRequest",
|
|
52
|
+
"store.readResolvedHumanDecision",
|
|
53
|
+
"store.reserveEffect",
|
|
54
|
+
"store.settleEffect",
|
|
55
|
+
"content.read",
|
|
56
|
+
"process.register",
|
|
57
|
+
"process.unregister",
|
|
58
|
+
"interaction.request",
|
|
59
|
+
"interaction.accept",
|
|
60
|
+
"interaction.reject",
|
|
61
|
+
"notification.request",
|
|
62
|
+
"presentation.request",
|
|
63
|
+
] as const;
|
|
64
|
+
export type WorkflowRunnerStoreOperation = (typeof WORKFLOW_RUNNER_STORE_OPERATIONS)[number];
|
|
65
|
+
const WORKFLOW_RUNNER_CONTROL_OPERATIONS = ["runner.ready", "runner.exiting"] as const;
|
|
66
|
+
|
|
67
|
+
export type WorkflowRunnerMessage = {
|
|
68
|
+
schema: typeof WORKFLOW_RUNNER_MESSAGE_SCHEMA;
|
|
69
|
+
launchSchema: "pi-workflows.worker-launch.v1";
|
|
70
|
+
messageId: string;
|
|
71
|
+
kind: WorkflowRunnerMessageKind;
|
|
72
|
+
operation: WorkflowRunnerStoreOperation | "runner.ready" | "runner.exiting";
|
|
73
|
+
runId: string;
|
|
74
|
+
generation: number;
|
|
75
|
+
runnerEpoch: string;
|
|
76
|
+
expectedRevision: number;
|
|
77
|
+
attemptId?: string;
|
|
78
|
+
payload: JsonValue;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export type WorkflowRunnerResponse = {
|
|
82
|
+
schema: typeof WORKFLOW_RUNNER_RESPONSE_SCHEMA;
|
|
83
|
+
messageId: string;
|
|
84
|
+
outcome: "accepted" | "adopted" | "rejected" | "claimLost";
|
|
85
|
+
revision?: number;
|
|
86
|
+
result?: JsonValue;
|
|
87
|
+
error?: string;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export type WorkflowRunnerContentReference = {
|
|
91
|
+
schema: typeof WORKFLOW_RUNNER_CONTENT_REFERENCE_SCHEMA;
|
|
92
|
+
sha256: string;
|
|
93
|
+
mediaType: "application/json";
|
|
94
|
+
bytes: number;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export type WorkflowRunnerContentChunk = {
|
|
98
|
+
schema: typeof WORKFLOW_RUNNER_CONTENT_CHUNK_SCHEMA;
|
|
99
|
+
sha256: string;
|
|
100
|
+
mediaType: "application/json";
|
|
101
|
+
bytes: number;
|
|
102
|
+
offset: number;
|
|
103
|
+
nextOffset: number;
|
|
104
|
+
complete: boolean;
|
|
105
|
+
data: string;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export function isRunnerContentReference(value: unknown): value is WorkflowRunnerContentReference {
|
|
109
|
+
return (
|
|
110
|
+
isRecord(value) &&
|
|
111
|
+
value.schema === WORKFLOW_RUNNER_CONTENT_REFERENCE_SCHEMA &&
|
|
112
|
+
typeof value.sha256 === "string" &&
|
|
113
|
+
/^[0-9a-f]{64}$/u.test(value.sha256) &&
|
|
114
|
+
value.mediaType === "application/json" &&
|
|
115
|
+
Number.isSafeInteger(value.bytes) &&
|
|
116
|
+
(value.bytes as number) >= 0
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function isRunnerContentChunk(value: unknown): value is WorkflowRunnerContentChunk {
|
|
121
|
+
return (
|
|
122
|
+
isRecord(value) &&
|
|
123
|
+
value.schema === WORKFLOW_RUNNER_CONTENT_CHUNK_SCHEMA &&
|
|
124
|
+
typeof value.sha256 === "string" &&
|
|
125
|
+
/^[0-9a-f]{64}$/u.test(value.sha256) &&
|
|
126
|
+
value.mediaType === "application/json" &&
|
|
127
|
+
Number.isSafeInteger(value.bytes) &&
|
|
128
|
+
(value.bytes as number) >= 0 &&
|
|
129
|
+
Number.isSafeInteger(value.offset) &&
|
|
130
|
+
(value.offset as number) >= 0 &&
|
|
131
|
+
Number.isSafeInteger(value.nextOffset) &&
|
|
132
|
+
(value.nextOffset as number) >= (value.offset as number) &&
|
|
133
|
+
typeof value.complete === "boolean" &&
|
|
134
|
+
typeof value.data === "string"
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function runnerKindForOperation(
|
|
139
|
+
operation: WorkflowRunnerStoreOperation,
|
|
140
|
+
payload: Record<string, unknown>,
|
|
141
|
+
): WorkflowRunnerMessageKind {
|
|
142
|
+
if (operation === "store.publishUpdate") return "node.update";
|
|
143
|
+
if (operation === "store.reserveEffect") return "effect.reserve";
|
|
144
|
+
if (operation === "store.settleEffect") return "effect.settle";
|
|
145
|
+
if (operation === "store.createHumanDecisionRequest" || operation === "interaction.request") {
|
|
146
|
+
return "interaction.requested";
|
|
147
|
+
}
|
|
148
|
+
if (operation === "interaction.accept") return "interaction.accepted";
|
|
149
|
+
if (operation === "interaction.reject") return "interaction.rejected";
|
|
150
|
+
if (operation === "notification.request") return "notification.requested";
|
|
151
|
+
if (operation === "presentation.request") return "presentation.requested";
|
|
152
|
+
if (operation === "store.writeSnapshot") {
|
|
153
|
+
const event = payload.event;
|
|
154
|
+
const eventType = isRecord(event) && typeof event.type === "string" ? event.type : "";
|
|
155
|
+
if (eventType === "node_started") return "node.started";
|
|
156
|
+
if (eventType === "node_finished") return "node.finished";
|
|
157
|
+
if (eventType === "node_failed" || eventType === "node_timed_out") return "node.failed";
|
|
158
|
+
if (eventType === "run_waiting" || eventType.includes("park")) return "run.parked";
|
|
159
|
+
if (eventType.startsWith("run_") && eventType !== "run_started") return "run.finished";
|
|
160
|
+
}
|
|
161
|
+
return "runner.progress";
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function encodeRunnerLine(message: WorkflowRunnerMessage | WorkflowRunnerResponse): Buffer {
|
|
165
|
+
const encoded = Buffer.from(`${canonicalJson(message)}\n`, "utf8");
|
|
166
|
+
if (encoded.byteLength > MAX_WORKFLOW_RUNNER_PROTOCOL_MESSAGE_BYTES) {
|
|
167
|
+
throw new Error("Runner protocol message exceeds 1 MiB");
|
|
168
|
+
}
|
|
169
|
+
return encoded;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function parseRunnerMessage(line: Buffer | string): WorkflowRunnerMessage {
|
|
173
|
+
const value = parseRunnerValue(line);
|
|
174
|
+
if (
|
|
175
|
+
value.schema !== WORKFLOW_RUNNER_MESSAGE_SCHEMA ||
|
|
176
|
+
value.launchSchema !== "pi-workflows.worker-launch.v1"
|
|
177
|
+
) {
|
|
178
|
+
throw new Error("Invalid workflow runner message schema");
|
|
179
|
+
}
|
|
180
|
+
requireText(value.messageId, "messageId");
|
|
181
|
+
requireText(value.runId, "runId");
|
|
182
|
+
requireText(value.runnerEpoch, "runnerEpoch");
|
|
183
|
+
if (!WORKFLOW_RUNNER_MESSAGE_KINDS.includes(value.kind as WorkflowRunnerMessageKind)) {
|
|
184
|
+
throw new Error("Invalid workflow runner message kind");
|
|
185
|
+
}
|
|
186
|
+
if (!Number.isSafeInteger(value.generation) || (value.generation as number) <= 0) {
|
|
187
|
+
throw new Error("Runner generation must be positive");
|
|
188
|
+
}
|
|
189
|
+
if (!Number.isSafeInteger(value.expectedRevision) || (value.expectedRevision as number) < 0) {
|
|
190
|
+
throw new Error("Runner expectedRevision must be non-negative");
|
|
191
|
+
}
|
|
192
|
+
requireText(value.operation, "operation");
|
|
193
|
+
if (
|
|
194
|
+
!WORKFLOW_RUNNER_STORE_OPERATIONS.includes(value.operation as WorkflowRunnerStoreOperation) &&
|
|
195
|
+
!WORKFLOW_RUNNER_CONTROL_OPERATIONS.includes(
|
|
196
|
+
value.operation as (typeof WORKFLOW_RUNNER_CONTROL_OPERATIONS)[number],
|
|
197
|
+
)
|
|
198
|
+
) {
|
|
199
|
+
throw new Error("Invalid workflow runner operation");
|
|
200
|
+
}
|
|
201
|
+
if (value.attemptId !== undefined) requireText(value.attemptId, "attemptId");
|
|
202
|
+
if (!Object.hasOwn(value, "payload")) throw new Error("Runner payload is required");
|
|
203
|
+
return value as WorkflowRunnerMessage;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function parseRunnerResponse(line: Buffer | string): WorkflowRunnerResponse {
|
|
207
|
+
const value = parseRunnerValue(line);
|
|
208
|
+
if (value.schema !== WORKFLOW_RUNNER_RESPONSE_SCHEMA)
|
|
209
|
+
throw new Error("Invalid workflow runner response schema");
|
|
210
|
+
requireText(value.messageId, "messageId");
|
|
211
|
+
if (!["accepted", "adopted", "rejected", "claimLost"].includes(value.outcome as string)) {
|
|
212
|
+
throw new Error("Invalid workflow runner response outcome");
|
|
213
|
+
}
|
|
214
|
+
if (value.error !== undefined && typeof value.error !== "string") {
|
|
215
|
+
throw new Error("Runner response error must be text");
|
|
216
|
+
}
|
|
217
|
+
return value as WorkflowRunnerResponse;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function parseRunnerValue(line: Buffer | string): Record<string, unknown> {
|
|
221
|
+
const buffer = Buffer.isBuffer(line) ? line : Buffer.from(line, "utf8");
|
|
222
|
+
if (buffer.byteLength + 1 > MAX_WORKFLOW_RUNNER_PROTOCOL_MESSAGE_BYTES) {
|
|
223
|
+
throw new Error("Runner protocol message exceeds 1 MiB");
|
|
224
|
+
}
|
|
225
|
+
const text = buffer.toString("utf8");
|
|
226
|
+
const value = parseJson(text);
|
|
227
|
+
if (!isRecord(value) || canonicalJson(value) !== text) {
|
|
228
|
+
throw new Error("Runner protocol message must be a canonical JSON object");
|
|
229
|
+
}
|
|
230
|
+
return value;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function requireText(value: unknown, field: string): asserts value is string {
|
|
234
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 256) {
|
|
235
|
+
throw new Error(`Runner ${field} must be nonempty bounded text`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
240
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
241
|
+
}
|
|
@@ -4,8 +4,6 @@ import type { WorkflowSettingsScopeRecord } from "../workflows/settings.js";
|
|
|
4
4
|
import {
|
|
5
5
|
createDefinitionSnapshot,
|
|
6
6
|
type InitializeWorkflowRunOptions,
|
|
7
|
-
type LoadedWorkflowRun,
|
|
8
|
-
type ReadWorkflowRunOptions,
|
|
9
7
|
type WorkflowExecutionStore,
|
|
10
8
|
} from "../workflows/store.js";
|
|
11
9
|
import type {
|
|
@@ -22,28 +20,31 @@ import type {
|
|
|
22
20
|
WorkflowUpdateInput,
|
|
23
21
|
WorkflowUpdateRecord,
|
|
24
22
|
} from "../workflows/types.js";
|
|
25
|
-
import type {
|
|
26
|
-
|
|
23
|
+
import type {
|
|
24
|
+
WorkflowRunnerMessageKind,
|
|
25
|
+
WorkflowRunnerStoreOperation,
|
|
26
|
+
} from "./workflow-runner-protocol.js";
|
|
27
|
+
import { runnerKindForOperation } from "./workflow-runner-protocol.js";
|
|
27
28
|
|
|
28
|
-
export interface
|
|
29
|
+
export interface WorkflowRunnerStoreTransport {
|
|
29
30
|
request(options: {
|
|
30
31
|
messageId: string;
|
|
31
|
-
operation:
|
|
32
|
-
kind:
|
|
32
|
+
operation: WorkflowRunnerStoreOperation;
|
|
33
|
+
kind: WorkflowRunnerMessageKind;
|
|
33
34
|
expectedRevision: number;
|
|
34
35
|
attemptId?: string;
|
|
35
36
|
payload: JsonValue;
|
|
36
37
|
}): Promise<{ result?: JsonValue; revision?: number }>;
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
/** A
|
|
40
|
-
export class
|
|
41
|
-
readonly databasePath = "
|
|
40
|
+
/** A runner-side store that can only propose state changes to its server. */
|
|
41
|
+
export class ServerBackedWorkflowStore implements WorkflowExecutionStore {
|
|
42
|
+
readonly databasePath = "server://pi-workflows-state";
|
|
42
43
|
private revision: number;
|
|
43
44
|
|
|
44
45
|
constructor(
|
|
45
46
|
private readonly runId: string,
|
|
46
|
-
private readonly transport:
|
|
47
|
+
private readonly transport: WorkflowRunnerStoreTransport,
|
|
47
48
|
initialRevision = 0,
|
|
48
49
|
) {
|
|
49
50
|
this.revision = initialRevision;
|
|
@@ -62,15 +63,12 @@ export class HostBackedWorkflowStore implements WorkflowExecutionStore {
|
|
|
62
63
|
});
|
|
63
64
|
}
|
|
64
65
|
|
|
65
|
-
async prepareRunResume(runId: string): Promise<
|
|
66
|
-
return await this.call<
|
|
66
|
+
async prepareRunResume(runId: string): Promise<WorkflowRunState> {
|
|
67
|
+
return await this.call<WorkflowRunState>("store.prepareRunResume", { runId });
|
|
67
68
|
}
|
|
68
69
|
|
|
69
|
-
async
|
|
70
|
-
runId
|
|
71
|
-
options: ReadWorkflowRunOptions = {},
|
|
72
|
-
): Promise<LoadedWorkflowRun | null> {
|
|
73
|
-
return await this.call<LoadedWorkflowRun | null>("store.readRun", { runId, options });
|
|
70
|
+
async readRunState(runId: string): Promise<WorkflowRunState | null> {
|
|
71
|
+
return await this.call<WorkflowRunState | null>("store.readRunState", { runId });
|
|
74
72
|
}
|
|
75
73
|
|
|
76
74
|
async writeSnapshot(
|
|
@@ -211,14 +209,14 @@ export class HostBackedWorkflowStore implements WorkflowExecutionStore {
|
|
|
211
209
|
}
|
|
212
210
|
|
|
213
211
|
private async call<T>(
|
|
214
|
-
operation:
|
|
212
|
+
operation: WorkflowRunnerStoreOperation,
|
|
215
213
|
payload: Record<string, unknown>,
|
|
216
214
|
attemptId?: string,
|
|
217
215
|
): Promise<T> {
|
|
218
216
|
const response = await this.transport.request({
|
|
219
217
|
messageId: randomUUID(),
|
|
220
218
|
operation,
|
|
221
|
-
kind:
|
|
219
|
+
kind: runnerKindForOperation(operation, payload),
|
|
222
220
|
expectedRevision: this.revision,
|
|
223
221
|
...(attemptId === undefined ? {} : { attemptId }),
|
|
224
222
|
payload: payload as JsonValue,
|