@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
|
@@ -2,62 +2,66 @@ import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
|
|
|
2
2
|
import { once } from "node:events";
|
|
3
3
|
import { NdjsonFrameDecoder } from "../client/protocol.js";
|
|
4
4
|
import { errorMessage } from "../workflows/errors.js";
|
|
5
|
-
import {
|
|
6
|
-
import type {
|
|
5
|
+
import { ServerProcessRegistry, type ProcessIdentity } from "./processes.js";
|
|
6
|
+
import type { RunnerOutcome } from "./state.js";
|
|
7
7
|
|
|
8
8
|
const DEFAULT_STARTUP_TIMEOUT_MS = 15_000;
|
|
9
9
|
const DEFAULT_KILL_GRACE_MS = 2_000;
|
|
10
10
|
const MAX_DIAGNOSTIC_BYTES = 64 * 1024;
|
|
11
11
|
|
|
12
|
-
export type
|
|
13
|
-
outcome:
|
|
12
|
+
export type SupervisedRunnerResult = {
|
|
13
|
+
outcome: RunnerOutcome;
|
|
14
14
|
exitCode: number | null;
|
|
15
15
|
signal: NodeJS.Signals | null;
|
|
16
16
|
diagnostic?: string;
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
type
|
|
19
|
+
type ChildRunnerSupervisorOptions<Message, Response> = {
|
|
20
20
|
label: string;
|
|
21
|
-
registry:
|
|
21
|
+
registry: ServerProcessRegistry;
|
|
22
22
|
onMessage: (message: Message) => Promise<Response>;
|
|
23
23
|
parseMessage: (frame: Buffer) => Message;
|
|
24
24
|
encodeResponse: (response: Response) => Buffer;
|
|
25
25
|
isReady: (message: Message) => boolean;
|
|
26
26
|
isTerminal: (message: Message) => boolean;
|
|
27
27
|
startupTimeoutMs?: number;
|
|
28
|
+
maxMessageBytes?: number;
|
|
29
|
+
oversizedMessage?: string;
|
|
28
30
|
onSpawn?: (identity: ProcessIdentity) => void;
|
|
29
31
|
onDiagnostic?: (message: string) => void;
|
|
30
32
|
};
|
|
31
33
|
|
|
32
|
-
type
|
|
34
|
+
type ChildRunnerStartOptions = {
|
|
33
35
|
args: string[];
|
|
34
36
|
cwd: string;
|
|
35
37
|
env: NodeJS.ProcessEnv;
|
|
36
38
|
};
|
|
37
39
|
|
|
38
|
-
/** Shared supervision, framing, diagnostics, and process-group shutdown for
|
|
39
|
-
export class
|
|
40
|
+
/** Shared supervision, framing, diagnostics, and process-group shutdown for server runners. */
|
|
41
|
+
export class ChildRunnerSupervisor<Message, Response> {
|
|
40
42
|
private child: ChildProcessWithoutNullStreams | null = null;
|
|
41
43
|
private identity: ProcessIdentity | null = null;
|
|
42
|
-
private readonly decoder
|
|
44
|
+
private readonly decoder: NdjsonFrameDecoder;
|
|
43
45
|
private diagnostic = Buffer.alloc(0);
|
|
44
46
|
private terminalMessage = false;
|
|
45
|
-
private stopping:
|
|
47
|
+
private stopping: RunnerOutcome | null = null;
|
|
46
48
|
private processing = Promise.resolve();
|
|
47
49
|
private readyResolve: (() => void) | undefined;
|
|
48
50
|
private readyReject: ((error: Error) => void) | undefined;
|
|
49
|
-
private exitResolve: ((result:
|
|
51
|
+
private exitResolve: ((result: SupervisedRunnerResult) => void) | undefined;
|
|
50
52
|
private readonly readyPromise = new Promise<void>((resolve, reject) => {
|
|
51
53
|
this.readyResolve = resolve;
|
|
52
54
|
this.readyReject = reject;
|
|
53
55
|
});
|
|
54
|
-
private readonly exitPromise = new Promise<
|
|
56
|
+
private readonly exitPromise = new Promise<SupervisedRunnerResult>((resolve) => {
|
|
55
57
|
this.exitResolve = resolve;
|
|
56
58
|
});
|
|
57
59
|
|
|
58
|
-
constructor(private readonly options:
|
|
60
|
+
constructor(private readonly options: ChildRunnerSupervisorOptions<Message, Response>) {
|
|
61
|
+
this.decoder = new NdjsonFrameDecoder(options.maxMessageBytes, options.oversizedMessage);
|
|
62
|
+
}
|
|
59
63
|
|
|
60
|
-
async start(options:
|
|
64
|
+
async start(options: ChildRunnerStartOptions): Promise<void> {
|
|
61
65
|
if (this.child !== null) throw new Error(`${this.options.label} already started`);
|
|
62
66
|
const child = spawn(process.execPath, options.args, {
|
|
63
67
|
cwd: options.cwd,
|
|
@@ -86,11 +90,11 @@ export class ChildWorkerSupervisor<Message, Response> {
|
|
|
86
90
|
}
|
|
87
91
|
}
|
|
88
92
|
|
|
89
|
-
async wait(): Promise<
|
|
93
|
+
async wait(): Promise<SupervisedRunnerResult> {
|
|
90
94
|
return await this.exitPromise;
|
|
91
95
|
}
|
|
92
96
|
|
|
93
|
-
async stop(outcome:
|
|
97
|
+
async stop(outcome: RunnerOutcome = "cancelled"): Promise<void> {
|
|
94
98
|
if (this.child === null) {
|
|
95
99
|
this.exitResolve?.({ outcome, exitCode: null, signal: null });
|
|
96
100
|
return;
|
|
@@ -154,7 +158,7 @@ export class ChildWorkerSupervisor<Message, Response> {
|
|
|
154
158
|
const outcome =
|
|
155
159
|
this.stopping ?? (code === 0 && this.terminalMessage ? "exited" : ("crashed" as const));
|
|
156
160
|
const diagnostic = this.diagnostic.toString("utf8").trim();
|
|
157
|
-
const result:
|
|
161
|
+
const result: SupervisedRunnerResult = {
|
|
158
162
|
outcome,
|
|
159
163
|
exitCode: code,
|
|
160
164
|
signal,
|
|
@@ -14,9 +14,9 @@ type ProcessRegistryFile = {
|
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Tracks supervised process groups by PID and operating-system start identity.
|
|
17
|
-
* A later
|
|
17
|
+
* A later server kills a recorded process only when both values still match.
|
|
18
18
|
*/
|
|
19
|
-
export class
|
|
19
|
+
export class ServerProcessRegistry {
|
|
20
20
|
private readonly filePath: string;
|
|
21
21
|
private readonly processes = new Map<number, ProcessIdentity>();
|
|
22
22
|
|
|
@@ -69,7 +69,7 @@ export class HostProcessRegistry {
|
|
|
69
69
|
this.persist();
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
/** Reap only exact stale process identities recorded by an older
|
|
72
|
+
/** Reap only exact stale process identities recorded by an older server. */
|
|
73
73
|
reapOrphans(): number[] {
|
|
74
74
|
const recorded = this.readFile();
|
|
75
75
|
const reaped: number[] = [];
|
|
@@ -5,11 +5,11 @@ import { fileURLToPath } from "node:url";
|
|
|
5
5
|
import { builtinWorkflowCatalog } from "../builtins/catalog.js";
|
|
6
6
|
import { MAX_PROTOCOL_MESSAGE_BYTES } from "../client/protocol.js";
|
|
7
7
|
import type {
|
|
8
|
-
|
|
8
|
+
ResolvedResourceManagerInitialization,
|
|
9
9
|
ResolvedSettingsChange,
|
|
10
10
|
ResolvedWorkflowLaunch,
|
|
11
11
|
} from "../client/resolver.js";
|
|
12
|
-
import {
|
|
12
|
+
import { discoverResourceManagers, loadResourceManagerFile } from "../resource-managers/loader.js";
|
|
13
13
|
import { canonicalJson, parseJson, type JsonValue } from "../state/json.js";
|
|
14
14
|
import { compositionMetadata } from "../workflows/composition.js";
|
|
15
15
|
import { errorMessage } from "../workflows/errors.js";
|
|
@@ -19,7 +19,7 @@ import { applyWorkflowSettingsPatch } from "../workflows/settings.js";
|
|
|
19
19
|
import { createDefinitionSnapshot } from "../workflows/store.js";
|
|
20
20
|
import type { WorkflowDefinition, WorkflowSource } from "../workflows/types.js";
|
|
21
21
|
export type {
|
|
22
|
-
|
|
22
|
+
ResolvedResourceManagerInitialization,
|
|
23
23
|
ResolvedSettingsChange,
|
|
24
24
|
ResolvedWorkflowLaunch,
|
|
25
25
|
} from "../client/resolver.js";
|
|
@@ -41,18 +41,21 @@ type SettingsValidationRequest = {
|
|
|
41
41
|
actorId: string;
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
-
type
|
|
45
|
-
schema: "pi-workflows.
|
|
44
|
+
type ResourceManagerInitializationRequest = {
|
|
45
|
+
schema: "pi-workflows.resource-manager-initialization-request.v1";
|
|
46
46
|
cwd: string;
|
|
47
|
-
|
|
47
|
+
resourceManagerName: string;
|
|
48
48
|
spec: JsonValue;
|
|
49
49
|
};
|
|
50
50
|
|
|
51
|
-
type ResolverInput =
|
|
51
|
+
type ResolverInput =
|
|
52
|
+
| ResolverRequest
|
|
53
|
+
| SettingsValidationRequest
|
|
54
|
+
| ResourceManagerInitializationRequest;
|
|
52
55
|
type ResolverOutput =
|
|
53
56
|
| ResolvedWorkflowLaunch
|
|
54
57
|
| ResolvedSettingsChange
|
|
55
|
-
|
|
|
58
|
+
| ResolvedResourceManagerInitialization;
|
|
56
59
|
|
|
57
60
|
export async function resolveWorkflowLaunch(
|
|
58
61
|
request: ResolverRequest,
|
|
@@ -74,27 +77,27 @@ export async function resolveWorkflowLaunch(
|
|
|
74
77
|
};
|
|
75
78
|
}
|
|
76
79
|
|
|
77
|
-
export async function
|
|
78
|
-
request:
|
|
79
|
-
): Promise<
|
|
80
|
-
const discovered = (await
|
|
81
|
-
(candidate) => candidate.name === request.
|
|
80
|
+
export async function resolveResourceManagerInitialization(
|
|
81
|
+
request: ResourceManagerInitializationRequest,
|
|
82
|
+
): Promise<ResolvedResourceManagerInitialization> {
|
|
83
|
+
const discovered = (await discoverResourceManagers({ cwd: request.cwd })).find(
|
|
84
|
+
(candidate) => candidate.name === request.resourceManagerName,
|
|
82
85
|
);
|
|
83
86
|
if (discovered === undefined) {
|
|
84
|
-
throw new Error(`
|
|
87
|
+
throw new Error(`ResourceManager not found: ${request.resourceManagerName}`);
|
|
85
88
|
}
|
|
86
|
-
const definition = await
|
|
87
|
-
if (definition.name !== request.
|
|
88
|
-
throw new Error("
|
|
89
|
+
const definition = await loadResourceManagerFile(discovered.path);
|
|
90
|
+
if (definition.name !== request.resourceManagerName) {
|
|
91
|
+
throw new Error("ResourceManager source name does not match its discovered name");
|
|
89
92
|
}
|
|
90
93
|
const initialStatus = parseJson(canonicalJson(definition.initialStatus(request.spec)));
|
|
91
94
|
const sourceHash = createHash("sha256")
|
|
92
95
|
.update(await fs.readFile(discovered.path))
|
|
93
96
|
.digest("hex");
|
|
94
97
|
return {
|
|
95
|
-
schema: "pi-workflows.resolved-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
+
schema: "pi-workflows.resolved-resource-manager-initialization.v1",
|
|
99
|
+
resourceManagerName: definition.name,
|
|
100
|
+
resourceManagerPath: discovered.path,
|
|
98
101
|
sourceHash,
|
|
99
102
|
initialStatus,
|
|
100
103
|
};
|
|
@@ -179,11 +182,11 @@ async function readRequest(): Promise<ResolverInput> {
|
|
|
179
182
|
return value as ResolverRequest;
|
|
180
183
|
}
|
|
181
184
|
if (
|
|
182
|
-
value.schema === "pi-workflows.
|
|
183
|
-
typeof value.
|
|
185
|
+
value.schema === "pi-workflows.resource-manager-initialization-request.v1" &&
|
|
186
|
+
typeof value.resourceManagerName === "string" &&
|
|
184
187
|
Object.hasOwn(value, "spec")
|
|
185
188
|
) {
|
|
186
|
-
return value as
|
|
189
|
+
return value as ResourceManagerInitializationRequest;
|
|
187
190
|
}
|
|
188
191
|
if (
|
|
189
192
|
value.schema === "pi-workflows.settings-validation-request.v1" &&
|
|
@@ -205,8 +208,8 @@ async function main(): Promise<void> {
|
|
|
205
208
|
const result: ResolverOutput =
|
|
206
209
|
request.schema === "pi-workflows.resolve-request.v1"
|
|
207
210
|
? await resolveWorkflowLaunch(request)
|
|
208
|
-
: request.schema === "pi-workflows.
|
|
209
|
-
? await
|
|
211
|
+
: request.schema === "pi-workflows.resource-manager-initialization-request.v1"
|
|
212
|
+
? await resolveResourceManagerInitialization(request)
|
|
210
213
|
: await resolveSettingsChange(request);
|
|
211
214
|
const encoded = `${canonicalJson(result)}\n`;
|
|
212
215
|
if (Buffer.byteLength(encoded) > MAX_PROTOCOL_MESSAGE_BYTES) {
|
|
@@ -2,66 +2,66 @@
|
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { once } from "node:events";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { jsonFingerprint } from "../
|
|
6
|
-
import {
|
|
7
|
-
import { createResultHelpers } from "../
|
|
5
|
+
import { jsonFingerprint } from "../resource-managers/json.js";
|
|
6
|
+
import { loadResourceManagerFile } from "../resource-managers/loader.js";
|
|
7
|
+
import { createResultHelpers } from "../resource-managers/results.js";
|
|
8
8
|
import type {
|
|
9
9
|
ChildWorkflowRecord,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
ResourceManagerEffects,
|
|
11
|
+
ResourceManagerFollowUpResult,
|
|
12
|
+
ResourceManagerQueueFollowUpRequest,
|
|
13
|
+
ResourceManagerRemoveFollowUpRequest,
|
|
14
|
+
ResourceManagerSettingsChangeRequest,
|
|
15
|
+
ResourceManagerSettingsChangeResult,
|
|
16
|
+
ResourceManagerWorkflows,
|
|
17
17
|
EffectApplication,
|
|
18
18
|
EffectDefinition,
|
|
19
19
|
EffectObservation,
|
|
20
20
|
EffectRecord,
|
|
21
21
|
ReconcileContext,
|
|
22
22
|
ReconcileResult,
|
|
23
|
-
} from "../
|
|
23
|
+
} from "../resource-managers/types.js";
|
|
24
24
|
import { parseJson, type JsonValue } from "../state/json.js";
|
|
25
25
|
import { errorMessage } from "../workflows/errors.js";
|
|
26
26
|
import {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
type
|
|
31
|
-
type
|
|
32
|
-
type
|
|
33
|
-
type
|
|
34
|
-
} from "./
|
|
27
|
+
RESOURCE_RUNNER_LAUNCH_SCHEMA,
|
|
28
|
+
encodeResourceRunnerLine,
|
|
29
|
+
parseResourceRunnerResponse,
|
|
30
|
+
type ResourceRunnerLaunchEnvelope,
|
|
31
|
+
type ResourceRunnerMessage,
|
|
32
|
+
type ResourceRunnerOperation,
|
|
33
|
+
type ResourceRunnerResponse,
|
|
34
|
+
} from "./resource-runner-protocol.js";
|
|
35
35
|
|
|
36
|
-
const STARTUP_ENV = "
|
|
36
|
+
const STARTUP_ENV = "PI_WORKFLOWS_RESOURCE_RUNNER_LAUNCH";
|
|
37
37
|
const MAX_FRAME_BYTES = 1024 * 1024;
|
|
38
38
|
|
|
39
|
-
class
|
|
39
|
+
class ResourceRunnerTransport {
|
|
40
40
|
private readonly pending = new Map<
|
|
41
41
|
string,
|
|
42
|
-
{ resolve: (response:
|
|
42
|
+
{ resolve: (response: ResourceRunnerResponse) => void; reject: (error: Error) => void }
|
|
43
43
|
>();
|
|
44
44
|
private buffered = Buffer.alloc(0);
|
|
45
45
|
|
|
46
|
-
constructor(private readonly launch:
|
|
46
|
+
constructor(private readonly launch: ResourceRunnerLaunchEnvelope) {
|
|
47
47
|
process.stdin.on("data", (chunk: Buffer) => this.onData(chunk));
|
|
48
48
|
process.stdin.on("error", (error) => this.failAll(error));
|
|
49
49
|
process.stdin.resume();
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
async request<T>(operation:
|
|
53
|
-
const message:
|
|
52
|
+
async request<T>(operation: ResourceRunnerOperation, payload: JsonValue): Promise<T> {
|
|
53
|
+
const message: ResourceRunnerMessage = {
|
|
54
54
|
schema: "pi-workflows.controller-worker-message.v1",
|
|
55
55
|
launchSchema: this.launch.schema,
|
|
56
56
|
messageId: randomUUID(),
|
|
57
|
-
|
|
57
|
+
runnerEpoch: this.launch.runnerEpoch,
|
|
58
58
|
generation: this.launch.generation,
|
|
59
59
|
operation,
|
|
60
60
|
payload,
|
|
61
61
|
};
|
|
62
62
|
const response = await this.send(message);
|
|
63
63
|
if (response.outcome !== "accepted") {
|
|
64
|
-
throw new Error(response.error ?? `
|
|
64
|
+
throw new Error(response.error ?? `Resource runner ${response.outcome}`);
|
|
65
65
|
}
|
|
66
66
|
return response.result as T;
|
|
67
67
|
}
|
|
@@ -70,14 +70,14 @@ class ControllerWorkerTransport {
|
|
|
70
70
|
process.stdin.pause();
|
|
71
71
|
process.stdin.removeAllListeners("data");
|
|
72
72
|
process.stdin.removeAllListeners("error");
|
|
73
|
-
this.failAll(new Error("
|
|
73
|
+
this.failAll(new Error("Resource runner transport closed"));
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
private async send(message:
|
|
77
|
-
const response = new Promise<
|
|
76
|
+
private async send(message: ResourceRunnerMessage): Promise<ResourceRunnerResponse> {
|
|
77
|
+
const response = new Promise<ResourceRunnerResponse>((resolve, reject) => {
|
|
78
78
|
this.pending.set(message.messageId, { resolve, reject });
|
|
79
79
|
});
|
|
80
|
-
if (!process.stdout.write(
|
|
80
|
+
if (!process.stdout.write(encodeResourceRunnerLine(message))) {
|
|
81
81
|
await once(process.stdout, "drain");
|
|
82
82
|
}
|
|
83
83
|
return await response;
|
|
@@ -86,7 +86,7 @@ class ControllerWorkerTransport {
|
|
|
86
86
|
private onData(chunk: Buffer): void {
|
|
87
87
|
this.buffered = Buffer.concat([this.buffered, chunk]);
|
|
88
88
|
if (this.buffered.byteLength > MAX_FRAME_BYTES && !this.buffered.includes(0x0a)) {
|
|
89
|
-
this.failAll(new Error("
|
|
89
|
+
this.failAll(new Error("Resource runner response exceeds 1 MiB"));
|
|
90
90
|
return;
|
|
91
91
|
}
|
|
92
92
|
for (;;) {
|
|
@@ -96,9 +96,9 @@ class ControllerWorkerTransport {
|
|
|
96
96
|
this.buffered = this.buffered.subarray(newline + 1);
|
|
97
97
|
if (frame.byteLength === 0) continue;
|
|
98
98
|
try {
|
|
99
|
-
const response =
|
|
99
|
+
const response = parseResourceRunnerResponse(frame);
|
|
100
100
|
const pending = this.pending.get(response.messageId);
|
|
101
|
-
if (pending === undefined) throw new Error("
|
|
101
|
+
if (pending === undefined) throw new Error("Resource runner response is unexpected");
|
|
102
102
|
this.pending.delete(response.messageId);
|
|
103
103
|
pending.resolve(response);
|
|
104
104
|
} catch (error) {
|
|
@@ -113,11 +113,11 @@ class ControllerWorkerTransport {
|
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
class
|
|
116
|
+
class ServerBackedResourceManagerEffects implements ResourceManagerEffects {
|
|
117
117
|
private used = false;
|
|
118
118
|
|
|
119
119
|
constructor(
|
|
120
|
-
private readonly transport:
|
|
120
|
+
private readonly transport: ResourceRunnerTransport,
|
|
121
121
|
private readonly signal: AbortSignal,
|
|
122
122
|
) {}
|
|
123
123
|
|
|
@@ -173,8 +173,8 @@ class HostedControllerEffects implements ControllerEffects {
|
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
-
class
|
|
177
|
-
constructor(private readonly transport:
|
|
176
|
+
class ServerBackedResourceManagerWorkflows implements ResourceManagerWorkflows {
|
|
177
|
+
constructor(private readonly transport: ResourceRunnerTransport) {}
|
|
178
178
|
|
|
179
179
|
async ensure(request: {
|
|
180
180
|
requestKey: string;
|
|
@@ -188,42 +188,44 @@ class HostedControllerWorkflows implements ControllerWorkflows {
|
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
async changeSettings(
|
|
191
|
-
request:
|
|
192
|
-
): Promise<
|
|
193
|
-
return await this.transport.request<
|
|
191
|
+
request: ResourceManagerSettingsChangeRequest,
|
|
192
|
+
): Promise<ResourceManagerSettingsChangeResult> {
|
|
193
|
+
return await this.transport.request<ResourceManagerSettingsChangeResult>(
|
|
194
194
|
"workflow.changeSettings",
|
|
195
195
|
request as unknown as JsonValue,
|
|
196
196
|
);
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
-
async queueFollowUp(
|
|
200
|
-
|
|
199
|
+
async queueFollowUp(
|
|
200
|
+
request: ResourceManagerQueueFollowUpRequest,
|
|
201
|
+
): Promise<ResourceManagerFollowUpResult> {
|
|
202
|
+
return await this.transport.request<ResourceManagerFollowUpResult>(
|
|
201
203
|
"workflow.queueFollowUp",
|
|
202
204
|
request as unknown as JsonValue,
|
|
203
205
|
);
|
|
204
206
|
}
|
|
205
207
|
|
|
206
208
|
async removeFollowUp(
|
|
207
|
-
request:
|
|
208
|
-
): Promise<
|
|
209
|
-
return await this.transport.request<
|
|
209
|
+
request: ResourceManagerRemoveFollowUpRequest,
|
|
210
|
+
): Promise<ResourceManagerFollowUpResult> {
|
|
211
|
+
return await this.transport.request<ResourceManagerFollowUpResult>(
|
|
210
212
|
"workflow.removeFollowUp",
|
|
211
213
|
request as unknown as JsonValue,
|
|
212
214
|
);
|
|
213
215
|
}
|
|
214
216
|
}
|
|
215
217
|
|
|
216
|
-
export async function
|
|
218
|
+
export async function runResourceRunner(): Promise<number> {
|
|
217
219
|
const launch = readLaunchEnvelope();
|
|
218
|
-
const transport = new
|
|
220
|
+
const transport = new ResourceRunnerTransport(launch);
|
|
219
221
|
try {
|
|
220
|
-
const definition = await
|
|
221
|
-
if (definition.name !== launch.
|
|
222
|
+
const definition = await loadResourceManagerFile(launch.definitionPath);
|
|
223
|
+
if (definition.name !== launch.resourceManagerName) {
|
|
222
224
|
throw new Error(
|
|
223
|
-
`
|
|
225
|
+
`ResourceManager source name changed: expected ${launch.resourceManagerName}, got ${definition.name}`,
|
|
224
226
|
);
|
|
225
227
|
}
|
|
226
|
-
await transport.request("
|
|
228
|
+
await transport.request("runner.ready", {});
|
|
227
229
|
const abort = new AbortController();
|
|
228
230
|
const timeoutMs = definition.timeoutMs ?? launch.timeoutMs;
|
|
229
231
|
const timer = setTimeout(
|
|
@@ -233,8 +235,8 @@ export async function runControllerWorker(): Promise<number> {
|
|
|
233
235
|
try {
|
|
234
236
|
const context: ReconcileContext<unknown> = {
|
|
235
237
|
signal: abort.signal,
|
|
236
|
-
effects: new
|
|
237
|
-
workflows: new
|
|
238
|
+
effects: new ServerBackedResourceManagerEffects(transport, abort.signal),
|
|
239
|
+
workflows: new ServerBackedResourceManagerWorkflows(transport),
|
|
238
240
|
...createResultHelpers<unknown>(),
|
|
239
241
|
};
|
|
240
242
|
const result = await raceWithAbort(
|
|
@@ -242,9 +244,9 @@ export async function runControllerWorker(): Promise<number> {
|
|
|
242
244
|
abort.signal,
|
|
243
245
|
);
|
|
244
246
|
validateResult(result);
|
|
245
|
-
await transport.request("
|
|
247
|
+
await transport.request("runner.finished", result as unknown as JsonValue);
|
|
246
248
|
} catch (error) {
|
|
247
|
-
await transport.request("
|
|
249
|
+
await transport.request("runner.failed", { error: boundedError(error) });
|
|
248
250
|
} finally {
|
|
249
251
|
clearTimeout(timer);
|
|
250
252
|
}
|
|
@@ -254,34 +256,34 @@ export async function runControllerWorker(): Promise<number> {
|
|
|
254
256
|
}
|
|
255
257
|
}
|
|
256
258
|
|
|
257
|
-
function readLaunchEnvelope():
|
|
259
|
+
function readLaunchEnvelope(): ResourceRunnerLaunchEnvelope {
|
|
258
260
|
const encoded = process.env[STARTUP_ENV];
|
|
259
|
-
if (encoded === undefined) throw new Error("
|
|
261
|
+
if (encoded === undefined) throw new Error("Resource runner launch envelope is missing");
|
|
260
262
|
const value = parseJson(Buffer.from(encoded, "base64url").toString("utf8"));
|
|
261
263
|
if (
|
|
262
264
|
typeof value !== "object" ||
|
|
263
265
|
value === null ||
|
|
264
266
|
Array.isArray(value) ||
|
|
265
|
-
(value as { schema?: unknown }).schema !==
|
|
267
|
+
(value as { schema?: unknown }).schema !== RESOURCE_RUNNER_LAUNCH_SCHEMA
|
|
266
268
|
) {
|
|
267
|
-
throw new Error("
|
|
269
|
+
throw new Error("Resource runner launch envelope is invalid");
|
|
268
270
|
}
|
|
269
|
-
return value as unknown as
|
|
271
|
+
return value as unknown as ResourceRunnerLaunchEnvelope;
|
|
270
272
|
}
|
|
271
273
|
|
|
272
274
|
function validateResult(value: unknown): asserts value is ReconcileResult<unknown> {
|
|
273
275
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
274
|
-
throw new Error("
|
|
276
|
+
throw new Error("ResourceManager reconcile result must be an object");
|
|
275
277
|
}
|
|
276
278
|
const result = value as { kind?: unknown; afterMs?: unknown };
|
|
277
279
|
if (result.kind !== "settled" && result.kind !== "requeue") {
|
|
278
|
-
throw new Error("
|
|
280
|
+
throw new Error("ResourceManager reconcile result kind is invalid");
|
|
279
281
|
}
|
|
280
282
|
if (
|
|
281
283
|
result.afterMs !== undefined &&
|
|
282
284
|
(!Number.isSafeInteger(result.afterMs) || (result.afterMs as number) < 0)
|
|
283
285
|
) {
|
|
284
|
-
throw new Error("
|
|
286
|
+
throw new Error("ResourceManager reconcile afterMs must be a non-negative safe integer");
|
|
285
287
|
}
|
|
286
288
|
}
|
|
287
289
|
|
|
@@ -301,7 +303,7 @@ function boundedError(error: unknown): string {
|
|
|
301
303
|
|
|
302
304
|
async function main(): Promise<void> {
|
|
303
305
|
try {
|
|
304
|
-
process.exitCode = await
|
|
306
|
+
process.exitCode = await runResourceRunner();
|
|
305
307
|
} catch (error) {
|
|
306
308
|
process.stderr.write(`${errorMessage(error)}\n`);
|
|
307
309
|
process.exitCode = 1;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { ManagedResource } from "../resource-managers/types.js";
|
|
2
|
+
import { canonicalJson, parseJson, type JsonValue } from "../state/json.js";
|
|
3
|
+
|
|
4
|
+
export const RESOURCE_RUNNER_LAUNCH_SCHEMA = "pi-workflows.controller-worker-launch.v1" as const;
|
|
5
|
+
export const RESOURCE_RUNNER_MESSAGE_SCHEMA = "pi-workflows.controller-worker-message.v1" as const;
|
|
6
|
+
export const RESOURCE_RUNNER_RESPONSE_SCHEMA =
|
|
7
|
+
"pi-workflows.controller-worker-response.v1" as const;
|
|
8
|
+
|
|
9
|
+
export type ResourceRunnerLaunchEnvelope = {
|
|
10
|
+
schema: typeof RESOURCE_RUNNER_LAUNCH_SCHEMA;
|
|
11
|
+
runnerEpoch: string;
|
|
12
|
+
serverEpoch: number;
|
|
13
|
+
generation: number;
|
|
14
|
+
projectPath: string;
|
|
15
|
+
definitionPath: string;
|
|
16
|
+
resourceManagerName: string;
|
|
17
|
+
resource: ManagedResource;
|
|
18
|
+
timeoutMs: number;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type ResourceRunnerOperation =
|
|
22
|
+
| "runner.ready"
|
|
23
|
+
| "effect.reserve"
|
|
24
|
+
| "effect.settle"
|
|
25
|
+
| "workflow.ensure"
|
|
26
|
+
| "workflow.changeSettings"
|
|
27
|
+
| "workflow.queueFollowUp"
|
|
28
|
+
| "workflow.removeFollowUp"
|
|
29
|
+
| "runner.finished"
|
|
30
|
+
| "runner.failed";
|
|
31
|
+
|
|
32
|
+
export type ResourceRunnerMessage = {
|
|
33
|
+
schema: typeof RESOURCE_RUNNER_MESSAGE_SCHEMA;
|
|
34
|
+
launchSchema: typeof RESOURCE_RUNNER_LAUNCH_SCHEMA;
|
|
35
|
+
messageId: string;
|
|
36
|
+
runnerEpoch: string;
|
|
37
|
+
generation: number;
|
|
38
|
+
operation: ResourceRunnerOperation;
|
|
39
|
+
payload: JsonValue;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type ResourceRunnerResponse = {
|
|
43
|
+
schema: typeof RESOURCE_RUNNER_RESPONSE_SCHEMA;
|
|
44
|
+
messageId: string;
|
|
45
|
+
outcome: "accepted" | "rejected" | "claimLost";
|
|
46
|
+
result?: JsonValue;
|
|
47
|
+
error?: string;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export function encodeResourceRunnerLine(
|
|
51
|
+
value: ResourceRunnerMessage | ResourceRunnerResponse,
|
|
52
|
+
): Buffer {
|
|
53
|
+
return Buffer.from(`${canonicalJson(value)}\n`, "utf8");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function parseResourceRunnerMessage(frame: Buffer): ResourceRunnerMessage {
|
|
57
|
+
const value = parseJson(frame.toString("utf8"));
|
|
58
|
+
if (!isRecord(value) || value.schema !== RESOURCE_RUNNER_MESSAGE_SCHEMA) {
|
|
59
|
+
throw new Error("Invalid resource runner message schema");
|
|
60
|
+
}
|
|
61
|
+
if (
|
|
62
|
+
value.launchSchema !== RESOURCE_RUNNER_LAUNCH_SCHEMA ||
|
|
63
|
+
typeof value.messageId !== "string" ||
|
|
64
|
+
typeof value.runnerEpoch !== "string" ||
|
|
65
|
+
!Number.isSafeInteger(value.generation) ||
|
|
66
|
+
typeof value.operation !== "string" ||
|
|
67
|
+
!OPERATIONS.has(value.operation as ResourceRunnerOperation) ||
|
|
68
|
+
!Object.hasOwn(value, "payload")
|
|
69
|
+
) {
|
|
70
|
+
throw new Error("Invalid resource runner message");
|
|
71
|
+
}
|
|
72
|
+
return value as ResourceRunnerMessage;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function parseResourceRunnerResponse(frame: Buffer): ResourceRunnerResponse {
|
|
76
|
+
const value = parseJson(frame.toString("utf8"));
|
|
77
|
+
if (!isRecord(value) || value.schema !== RESOURCE_RUNNER_RESPONSE_SCHEMA) {
|
|
78
|
+
throw new Error("Invalid resource runner response schema");
|
|
79
|
+
}
|
|
80
|
+
if (
|
|
81
|
+
typeof value.messageId !== "string" ||
|
|
82
|
+
!["accepted", "rejected", "claimLost"].includes(String(value.outcome))
|
|
83
|
+
) {
|
|
84
|
+
throw new Error("Invalid resource runner response");
|
|
85
|
+
}
|
|
86
|
+
return value as ResourceRunnerResponse;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const OPERATIONS = new Set<ResourceRunnerOperation>([
|
|
90
|
+
"runner.ready",
|
|
91
|
+
"effect.reserve",
|
|
92
|
+
"effect.settle",
|
|
93
|
+
"workflow.ensure",
|
|
94
|
+
"workflow.changeSettings",
|
|
95
|
+
"workflow.queueFollowUp",
|
|
96
|
+
"workflow.removeFollowUp",
|
|
97
|
+
"runner.finished",
|
|
98
|
+
"runner.failed",
|
|
99
|
+
]);
|
|
100
|
+
|
|
101
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
102
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
103
|
+
}
|