@osolmaz/pi-workflows 0.16.1 → 0.16.3
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 +7 -1
- package/dist/client/view.js +1 -0
- package/dist/client/view.js.map +1 -1
- package/dist/extension/index.d.ts +1 -1
- package/dist/extension/index.js +66 -47
- 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 +4 -3
- package/dist/extension/workflow-message-coordinator.js +81 -29
- 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 +51 -43
- package/dist/{controllers → resource-managers}/sqlite.js +187 -125
- 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} +47 -39
- package/dist/{host/runner.js → server/server.js} +624 -535
- 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 +19 -15
- 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/server/workflow-runner-entry.d.ts +13 -0
- package/dist/{host/worker-entry.js → server/workflow-runner-entry.js} +89 -57
- 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 +3 -1
- package/dist/state/workflow-messages.js +32 -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/composition.js +0 -4
- package/dist/workflows/composition.js.map +1 -1
- package/dist/workflows/definition.js +0 -8
- package/dist/workflows/definition.js.map +1 -1
- package/dist/workflows/engine.js +18 -20
- package/dist/workflows/engine.js.map +1 -1
- package/dist/workflows/schema.js +0 -4
- package/dist/workflows/schema.js.map +1 -1
- package/dist/workflows/store.d.ts +27 -11
- package/dist/workflows/store.js +66 -18
- package/dist/workflows/store.js.map +1 -1
- package/dist/workflows/types.d.ts +3 -5
- 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 +363 -0
- 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 +40 -34
- package/docs/WORKFLOW_COMPOSITION.md +1 -1
- package/docs/{WORKFLOW_HOST.md → WORKFLOW_SERVER.md} +131 -116
- 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 +78 -63
- 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 +5 -5
- 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 +9 -1
- package/src/extension/index.ts +81 -53
- 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 +94 -30
- 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 +275 -181
- 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} +786 -672
- package/src/{host → server}/state.ts +78 -67
- package/src/{host → server}/view.ts +21 -17
- package/src/server/workflow-runner-content.ts +119 -0
- package/src/{host/worker-entry.ts → server/workflow-runner-entry.ts} +131 -82
- 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 +52 -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/composition.ts +0 -5
- package/src/workflows/definition.ts +0 -8
- package/src/workflows/engine.ts +18 -21
- package/src/workflows/schema.ts +0 -6
- package/src/workflows/store.ts +95 -28
- package/src/workflows/types.ts +3 -5
- 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.d.ts +0 -10
- package/dist/host/worker-entry.js.map +0 -1
- package/dist/host/worker-protocol.d.ts +0 -31
- 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 -165
- 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,27 +25,31 @@ 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
|
-
|
|
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";
|
|
36
44
|
|
|
37
|
-
const STARTUP_ENV = "
|
|
45
|
+
const STARTUP_ENV = "PI_WORKFLOWS_WORKFLOW_RUNNER_LAUNCH";
|
|
38
46
|
const PRESENTATION_TIMEOUT_MS = 30_000;
|
|
39
47
|
|
|
40
|
-
type
|
|
41
|
-
|
|
42
|
-
input: JsonValue;
|
|
43
|
-
launchOptions: JsonValue;
|
|
44
|
-
parentRunId: string | null;
|
|
48
|
+
type WorkflowRunnerBootstrap = {
|
|
49
|
+
command: WorkflowRunnerCommand;
|
|
45
50
|
originSessionId: string | null;
|
|
46
51
|
stateDirectory: string;
|
|
47
52
|
piArgs?: string[];
|
|
48
|
-
resumeInteractionAttemptId?: string;
|
|
49
53
|
candidateInteraction?: {
|
|
50
54
|
requestId: string;
|
|
51
55
|
attemptId: string;
|
|
@@ -55,21 +59,21 @@ type WorkerBootstrap = {
|
|
|
55
59
|
};
|
|
56
60
|
};
|
|
57
61
|
|
|
58
|
-
class
|
|
62
|
+
class StdioRunnerTransport implements WorkflowRunnerStoreTransport {
|
|
59
63
|
private readonly pending = new Map<
|
|
60
64
|
string,
|
|
61
|
-
{ resolve: (response:
|
|
65
|
+
{ resolve: (response: WorkflowRunnerResponse) => void; reject: (error: Error) => void }
|
|
62
66
|
>();
|
|
63
67
|
private buffered = Buffer.alloc(0);
|
|
64
68
|
|
|
65
|
-
constructor(private readonly launch:
|
|
69
|
+
constructor(private readonly launch: WorkflowRunnerLaunchEnvelope) {
|
|
66
70
|
process.stdin.on("data", (chunk: Buffer) => this.onData(chunk));
|
|
67
71
|
process.stdin.on("error", (error) => this.failAll(error));
|
|
68
72
|
process.stdin.resume();
|
|
69
73
|
}
|
|
70
74
|
|
|
71
|
-
async request(options: Parameters<
|
|
72
|
-
const message:
|
|
75
|
+
async request(options: Parameters<WorkflowRunnerStoreTransport["request"]>[0]) {
|
|
76
|
+
const message: WorkflowRunnerMessage = {
|
|
73
77
|
schema: "pi-workflows.worker-message.v1",
|
|
74
78
|
launchSchema: this.launch.schema,
|
|
75
79
|
messageId: options.messageId,
|
|
@@ -77,17 +81,17 @@ class StdioWorkerTransport implements WorkerStoreTransport {
|
|
|
77
81
|
operation: options.operation,
|
|
78
82
|
runId: this.launch.runId,
|
|
79
83
|
generation: this.launch.generation,
|
|
80
|
-
|
|
84
|
+
runnerEpoch: this.launch.runnerEpoch,
|
|
81
85
|
expectedRevision: options.expectedRevision,
|
|
82
86
|
...(options.attemptId === undefined ? {} : { attemptId: options.attemptId }),
|
|
83
87
|
payload: options.payload,
|
|
84
88
|
};
|
|
85
|
-
const response = await this.
|
|
89
|
+
const response = await this.sendResolved(message);
|
|
86
90
|
if (response.outcome === "claimLost") {
|
|
87
91
|
throw new ClaimLostError(this.launch.runId, "ownerChanged");
|
|
88
92
|
}
|
|
89
93
|
if (response.outcome === "rejected") {
|
|
90
|
-
throw new Error(response.error ?? "Workflow
|
|
94
|
+
throw new Error(response.error ?? "Workflow runner message was rejected");
|
|
91
95
|
}
|
|
92
96
|
return {
|
|
93
97
|
...(response.result === undefined ? {} : { result: response.result }),
|
|
@@ -96,10 +100,10 @@ class StdioWorkerTransport implements WorkerStoreTransport {
|
|
|
96
100
|
}
|
|
97
101
|
|
|
98
102
|
async control(
|
|
99
|
-
operation: "
|
|
103
|
+
operation: "runner.ready" | "runner.exiting",
|
|
100
104
|
payload: JsonValue,
|
|
101
|
-
): Promise<
|
|
102
|
-
return await this.
|
|
105
|
+
): Promise<WorkflowRunnerResponse> {
|
|
106
|
+
return await this.sendResolved({
|
|
103
107
|
schema: "pi-workflows.worker-message.v1",
|
|
104
108
|
launchSchema: this.launch.schema,
|
|
105
109
|
messageId: randomUUID(),
|
|
@@ -107,7 +111,7 @@ class StdioWorkerTransport implements WorkerStoreTransport {
|
|
|
107
111
|
operation,
|
|
108
112
|
runId: this.launch.runId,
|
|
109
113
|
generation: this.launch.generation,
|
|
110
|
-
|
|
114
|
+
runnerEpoch: this.launch.runnerEpoch,
|
|
111
115
|
expectedRevision: 0,
|
|
112
116
|
payload,
|
|
113
117
|
});
|
|
@@ -117,21 +121,55 @@ class StdioWorkerTransport implements WorkerStoreTransport {
|
|
|
117
121
|
process.stdin.pause();
|
|
118
122
|
process.stdin.removeAllListeners("data");
|
|
119
123
|
process.stdin.removeAllListeners("error");
|
|
120
|
-
this.failAll(new Error("Workflow
|
|
124
|
+
this.failAll(new Error("Workflow runner transport closed"));
|
|
125
|
+
}
|
|
126
|
+
|
|
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
|
+
};
|
|
121
156
|
}
|
|
122
157
|
|
|
123
|
-
private async send(message:
|
|
124
|
-
const response = new Promise<
|
|
158
|
+
private async send(message: WorkflowRunnerMessage): Promise<WorkflowRunnerResponse> {
|
|
159
|
+
const response = new Promise<WorkflowRunnerResponse>((resolve, reject) => {
|
|
125
160
|
this.pending.set(message.messageId, { resolve, reject });
|
|
126
161
|
});
|
|
127
|
-
if (!process.stdout.write(
|
|
162
|
+
if (!process.stdout.write(encodeRunnerLine(message))) await once(process.stdout, "drain");
|
|
128
163
|
return await response;
|
|
129
164
|
}
|
|
130
165
|
|
|
131
166
|
private onData(chunk: Buffer): void {
|
|
132
167
|
this.buffered = Buffer.concat([this.buffered, chunk]);
|
|
133
|
-
if (
|
|
134
|
-
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"));
|
|
135
173
|
return;
|
|
136
174
|
}
|
|
137
175
|
for (;;) {
|
|
@@ -141,9 +179,9 @@ class StdioWorkerTransport implements WorkerStoreTransport {
|
|
|
141
179
|
this.buffered = this.buffered.subarray(newline + 1);
|
|
142
180
|
if (frame.byteLength === 0) continue;
|
|
143
181
|
try {
|
|
144
|
-
const response =
|
|
182
|
+
const response = parseRunnerResponse(frame);
|
|
145
183
|
const pending = this.pending.get(response.messageId);
|
|
146
|
-
if (pending === undefined) throw new Error("
|
|
184
|
+
if (pending === undefined) throw new Error("Runner response has no pending request");
|
|
147
185
|
this.pending.delete(response.messageId);
|
|
148
186
|
pending.resolve(response);
|
|
149
187
|
} catch (error) {
|
|
@@ -163,7 +201,7 @@ class InteractiveExecutor implements AgentStepExecutor {
|
|
|
163
201
|
readonly preservesDeadlineWhileParked = true;
|
|
164
202
|
|
|
165
203
|
constructor(
|
|
166
|
-
private readonly store:
|
|
204
|
+
private readonly store: ServerBackedWorkflowStore,
|
|
167
205
|
private readonly candidate:
|
|
168
206
|
| {
|
|
169
207
|
requestId: string;
|
|
@@ -278,30 +316,59 @@ export function validateAcceptedAssistantSubmission(
|
|
|
278
316
|
};
|
|
279
317
|
}
|
|
280
318
|
|
|
281
|
-
export async function
|
|
319
|
+
export async function executeRunnerRunCommand(
|
|
320
|
+
engine: Pick<WorkflowEngine, "run" | "resumeRun" | "continueRun">,
|
|
321
|
+
workflow: WorkflowDefinition,
|
|
322
|
+
runId: string,
|
|
323
|
+
workflowSource: WorkflowSource,
|
|
324
|
+
command: WorkflowRunnerCommand,
|
|
325
|
+
) {
|
|
326
|
+
switch (command.kind) {
|
|
327
|
+
case "start":
|
|
328
|
+
case "restart":
|
|
329
|
+
return await engine.run(workflow, command.input, { runId, workflowSource });
|
|
330
|
+
case "resume":
|
|
331
|
+
return await engine.resumeRun(workflow, runId, {
|
|
332
|
+
workflowSource,
|
|
333
|
+
...(command.resumeInteractionAttemptId === undefined
|
|
334
|
+
? {}
|
|
335
|
+
: { resumeInteractionAttemptId: command.resumeInteractionAttemptId }),
|
|
336
|
+
});
|
|
337
|
+
case "continue":
|
|
338
|
+
return await engine.continueRun(workflow, command.parentRunId, command.input, {
|
|
339
|
+
runId,
|
|
340
|
+
workflowSource,
|
|
341
|
+
...(command.humanDecision === undefined
|
|
342
|
+
? {}
|
|
343
|
+
: { humanDecision: command.humanDecision as ResolvedHumanDecision }),
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export async function runWorkflowRunner(): Promise<number> {
|
|
282
349
|
const launch = readLaunchEnvelope();
|
|
283
|
-
const transport = new
|
|
350
|
+
const transport = new StdioRunnerTransport(launch);
|
|
284
351
|
try {
|
|
285
|
-
const ready = await transport.control("
|
|
352
|
+
const ready = await transport.control("runner.ready", {});
|
|
286
353
|
if (ready.outcome !== "accepted" || ready.result === undefined) {
|
|
287
|
-
throw new Error(ready.error ?? "Workflow
|
|
354
|
+
throw new Error(ready.error ?? "Workflow server rejected runner startup");
|
|
288
355
|
}
|
|
289
|
-
const bootstrap = ready.result as unknown as
|
|
290
|
-
const workflowSource =
|
|
356
|
+
const bootstrap = ready.result as unknown as WorkflowRunnerBootstrap;
|
|
357
|
+
const workflowSource = parseRunnerWorkflowSources(launch.workflowSource);
|
|
291
358
|
let workflow: WorkflowDefinition;
|
|
292
359
|
try {
|
|
293
360
|
workflow = await resolveVerifiedWorkflow(launch.runId, workflowSource);
|
|
294
361
|
} catch (error) {
|
|
295
|
-
const response = await transport.control("
|
|
362
|
+
const response = await transport.control("runner.exiting", {
|
|
296
363
|
status: "workflowLoadFailed",
|
|
297
364
|
error: errorMessage(error),
|
|
298
365
|
});
|
|
299
366
|
if (response.outcome !== "accepted") {
|
|
300
|
-
throw new Error(response.error ?? "Workflow
|
|
367
|
+
throw new Error(response.error ?? "Workflow server rejected the load-failure report");
|
|
301
368
|
}
|
|
302
369
|
return 1;
|
|
303
370
|
}
|
|
304
|
-
const store = new
|
|
371
|
+
const store = new ServerBackedWorkflowStore(launch.runId, transport, ready.revision ?? 0);
|
|
305
372
|
let executor: AgentStepExecutor;
|
|
306
373
|
let closeExecutor: (() => Promise<void>) | undefined;
|
|
307
374
|
if (bootstrap.originSessionId !== null) {
|
|
@@ -316,7 +383,7 @@ export async function runWorkflowWorker(): Promise<number> {
|
|
|
316
383
|
await transport.request({
|
|
317
384
|
messageId: randomUUID(),
|
|
318
385
|
operation: "process.register",
|
|
319
|
-
kind: "
|
|
386
|
+
kind: "runner.progress",
|
|
320
387
|
expectedRevision: 0,
|
|
321
388
|
payload: { pid },
|
|
322
389
|
});
|
|
@@ -325,7 +392,7 @@ export async function runWorkflowWorker(): Promise<number> {
|
|
|
325
392
|
await transport.request({
|
|
326
393
|
messageId: randomUUID(),
|
|
327
394
|
operation: "process.unregister",
|
|
328
|
-
kind: "
|
|
395
|
+
kind: "runner.progress",
|
|
329
396
|
expectedRevision: 0,
|
|
330
397
|
payload: { pid },
|
|
331
398
|
});
|
|
@@ -360,33 +427,15 @@ export async function runWorkflowWorker(): Promise<number> {
|
|
|
360
427
|
await store.requestPresentation(instructions);
|
|
361
428
|
},
|
|
362
429
|
});
|
|
363
|
-
const launchOptions =
|
|
364
|
-
typeof bootstrap.launchOptions === "object" &&
|
|
365
|
-
bootstrap.launchOptions !== null &&
|
|
366
|
-
!Array.isArray(bootstrap.launchOptions)
|
|
367
|
-
? (bootstrap.launchOptions as Record<string, unknown>)
|
|
368
|
-
: {};
|
|
369
430
|
try {
|
|
370
|
-
const result =
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
? await engine.run(workflow, bootstrap.input, {
|
|
379
|
-
runId: launch.runId,
|
|
380
|
-
workflowSource: workflowSource.root,
|
|
381
|
-
})
|
|
382
|
-
: await engine.continueRun(workflow, bootstrap.parentRunId, bootstrap.input, {
|
|
383
|
-
runId: launch.runId,
|
|
384
|
-
workflowSource: workflowSource.root,
|
|
385
|
-
...(launchOptions.humanDecision === undefined
|
|
386
|
-
? {}
|
|
387
|
-
: { humanDecision: launchOptions.humanDecision as ResolvedHumanDecision }),
|
|
388
|
-
});
|
|
389
|
-
await transport.control("worker.exiting", {
|
|
431
|
+
const result = await executeRunnerRunCommand(
|
|
432
|
+
engine,
|
|
433
|
+
workflow,
|
|
434
|
+
launch.runId,
|
|
435
|
+
workflowSource.root,
|
|
436
|
+
bootstrap.command,
|
|
437
|
+
);
|
|
438
|
+
await transport.control("runner.exiting", {
|
|
390
439
|
status: result.state.status,
|
|
391
440
|
traceSeq: result.state.traceSeq,
|
|
392
441
|
});
|
|
@@ -436,25 +485,25 @@ async function resolvePresentationInstructions(
|
|
|
436
485
|
}
|
|
437
486
|
}
|
|
438
487
|
|
|
439
|
-
type
|
|
488
|
+
type WorkflowRunnerSources = {
|
|
440
489
|
root: WorkflowSource;
|
|
441
490
|
mounted: WorkflowMountedSource[];
|
|
442
491
|
};
|
|
443
492
|
|
|
444
|
-
function
|
|
493
|
+
function parseRunnerWorkflowSources(value: JsonValue): WorkflowRunnerSources {
|
|
445
494
|
if (!isRecord(value) || !isWorkflowSource(value.root) || !Array.isArray(value.mounted)) {
|
|
446
|
-
throw new Error("Workflow
|
|
495
|
+
throw new Error("Workflow runner source identity is invalid");
|
|
447
496
|
}
|
|
448
497
|
const mounted = value.mounted;
|
|
449
498
|
if (!mounted.every(isMountedWorkflowSource)) {
|
|
450
|
-
throw new Error("Workflow
|
|
499
|
+
throw new Error("Workflow runner mounted source identity is invalid");
|
|
451
500
|
}
|
|
452
501
|
return { root: value.root, mounted };
|
|
453
502
|
}
|
|
454
503
|
|
|
455
504
|
async function resolveVerifiedWorkflow(
|
|
456
505
|
runId: string,
|
|
457
|
-
sources:
|
|
506
|
+
sources: WorkflowRunnerSources,
|
|
458
507
|
): Promise<WorkflowDefinition> {
|
|
459
508
|
for (const source of [sources.root, ...sources.mounted.map((mounted) => mounted.source)]) {
|
|
460
509
|
if (source.kind === "file") {
|
|
@@ -506,9 +555,9 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
506
555
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
507
556
|
}
|
|
508
557
|
|
|
509
|
-
function readLaunchEnvelope():
|
|
558
|
+
function readLaunchEnvelope(): WorkflowRunnerLaunchEnvelope {
|
|
510
559
|
const encoded = process.env[STARTUP_ENV];
|
|
511
|
-
if (encoded === undefined) throw new Error("Workflow
|
|
560
|
+
if (encoded === undefined) throw new Error("Workflow runner launch envelope is missing");
|
|
512
561
|
const value = parseJson(Buffer.from(encoded, "base64url").toString("utf8"));
|
|
513
562
|
if (
|
|
514
563
|
typeof value !== "object" ||
|
|
@@ -516,14 +565,14 @@ function readLaunchEnvelope(): WorkerLaunchEnvelope {
|
|
|
516
565
|
Array.isArray(value) ||
|
|
517
566
|
(value as { schema?: unknown }).schema !== "pi-workflows.worker-launch.v1"
|
|
518
567
|
) {
|
|
519
|
-
throw new Error("Workflow
|
|
568
|
+
throw new Error("Workflow runner launch envelope is invalid");
|
|
520
569
|
}
|
|
521
|
-
return value as unknown as
|
|
570
|
+
return value as unknown as WorkflowRunnerLaunchEnvelope;
|
|
522
571
|
}
|
|
523
572
|
|
|
524
573
|
async function main(): Promise<void> {
|
|
525
574
|
try {
|
|
526
|
-
process.exitCode = await
|
|
575
|
+
process.exitCode = await runWorkflowRunner();
|
|
527
576
|
} catch (error) {
|
|
528
577
|
process.stderr.write(`${errorMessage(error)}\n`);
|
|
529
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
|
+
}
|