@dna113p/machines 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +186 -0
- package/dist/codex/machines/ui/machines.html +286 -0
- package/dist/examples/agent.d.ts +1 -0
- package/dist/examples/agent.js +19 -0
- package/dist/examples/choices.d.ts +1 -0
- package/dist/examples/choices.js +17 -0
- package/dist/examples/host.d.ts +3 -0
- package/dist/examples/host.js +32 -0
- package/dist/examples/human.d.ts +1 -0
- package/dist/examples/human.js +17 -0
- package/dist/examples/named-agents.d.ts +1 -0
- package/dist/examples/named-agents.js +38 -0
- package/dist/examples/operation.d.ts +1 -0
- package/dist/examples/operation.js +10 -0
- package/dist/examples/pi.d.ts +1 -0
- package/dist/examples/pi.js +29 -0
- package/dist/mcp/server.d.ts +17 -0
- package/dist/mcp/server.js +313 -0
- package/dist/pi-extension/index.d.ts +52 -0
- package/dist/pi-extension/index.js +390 -0
- package/dist/src/acp.d.ts +7 -0
- package/dist/src/acp.js +184 -0
- package/dist/src/agent-bindings.d.ts +15 -0
- package/dist/src/agent-bindings.js +113 -0
- package/dist/src/catalog.d.ts +31 -0
- package/dist/src/catalog.js +68 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +392 -0
- package/dist/src/discovery-worker.d.ts +3 -0
- package/dist/src/discovery-worker.js +52 -0
- package/dist/src/discovery.d.ts +8 -0
- package/dist/src/discovery.js +94 -0
- package/dist/src/host.d.ts +25 -0
- package/dist/src/host.js +357 -0
- package/dist/src/index.d.ts +210 -0
- package/dist/src/index.js +204 -0
- package/dist/src/launcher.d.ts +19 -0
- package/dist/src/launcher.js +98 -0
- package/dist/src/machine-module.d.ts +3 -0
- package/dist/src/machine-module.js +55 -0
- package/dist/src/session.d.ts +40 -0
- package/dist/src/session.js +165 -0
- package/dist/src/terminal-human.d.ts +2 -0
- package/dist/src/terminal-human.js +100 -0
- package/docs/architecture.md +111 -0
- package/docs/authoring.md +193 -0
- package/docs/integrations.md +142 -0
- package/package.json +58 -0
- package/skills/machine-builder/SKILL.md +34 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { readdir, stat } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, isAbsolute, join } from "node:path";
|
|
4
|
+
/** Select a separate global catalog without changing the process's real home. */
|
|
5
|
+
export function machinesUserHome() {
|
|
6
|
+
const override = process.env.MACHINES_USER_HOME;
|
|
7
|
+
if (override === undefined)
|
|
8
|
+
return homedir();
|
|
9
|
+
if (!isAbsolute(override))
|
|
10
|
+
throw new Error("MACHINES_USER_HOME must be an absolute path");
|
|
11
|
+
return override;
|
|
12
|
+
}
|
|
13
|
+
export async function discoverMachines(cwd, home) {
|
|
14
|
+
const globalDirectory = join(home, ".machines");
|
|
15
|
+
const projectDirectory = await nearestMachinesDirectory(cwd);
|
|
16
|
+
const found = new Map();
|
|
17
|
+
for (const machine of await machinesIn(globalDirectory)) {
|
|
18
|
+
found.set(machine.name, machine.path);
|
|
19
|
+
}
|
|
20
|
+
if (projectDirectory !== undefined) {
|
|
21
|
+
for (const machine of await machinesIn(projectDirectory)) {
|
|
22
|
+
found.set(machine.name, machine.path);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return [...found]
|
|
26
|
+
.map(([name, path]) => ({ name, path }))
|
|
27
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
28
|
+
}
|
|
29
|
+
export async function nearestMachinesDirectory(cwd) {
|
|
30
|
+
let directory = cwd;
|
|
31
|
+
while (true) {
|
|
32
|
+
const candidate = join(directory, ".machines");
|
|
33
|
+
if (await isDirectory(candidate))
|
|
34
|
+
return candidate;
|
|
35
|
+
const parent = dirname(directory);
|
|
36
|
+
if (parent === directory)
|
|
37
|
+
return undefined;
|
|
38
|
+
directory = parent;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async function machinesIn(directory) {
|
|
42
|
+
let entries;
|
|
43
|
+
try {
|
|
44
|
+
entries = await readdir(directory, { withFileTypes: true });
|
|
45
|
+
}
|
|
46
|
+
catch (cause) {
|
|
47
|
+
if (isMissing(cause))
|
|
48
|
+
return [];
|
|
49
|
+
throw cause;
|
|
50
|
+
}
|
|
51
|
+
const found = new Map();
|
|
52
|
+
for (const entry of entries) {
|
|
53
|
+
const machineDirectory = join(directory, entry.name);
|
|
54
|
+
if (!await isDirectory(machineDirectory))
|
|
55
|
+
continue;
|
|
56
|
+
const path = join(machineDirectory, "index.ts");
|
|
57
|
+
if (await isFile(path))
|
|
58
|
+
found.set(entry.name, path);
|
|
59
|
+
}
|
|
60
|
+
for (const entry of entries) {
|
|
61
|
+
if (!entry.name.endsWith(".ts")
|
|
62
|
+
|| entry.name.endsWith(".d.ts")
|
|
63
|
+
|| entry.name === "agents.ts") {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const path = join(directory, entry.name);
|
|
67
|
+
if (await isFile(path))
|
|
68
|
+
found.set(entry.name.slice(0, -3), path);
|
|
69
|
+
}
|
|
70
|
+
return [...found].map(([name, path]) => ({ name, path }));
|
|
71
|
+
}
|
|
72
|
+
async function isDirectory(path) {
|
|
73
|
+
try {
|
|
74
|
+
return (await stat(path)).isDirectory();
|
|
75
|
+
}
|
|
76
|
+
catch (cause) {
|
|
77
|
+
if (isMissing(cause))
|
|
78
|
+
return false;
|
|
79
|
+
throw cause;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async function isFile(path) {
|
|
83
|
+
try {
|
|
84
|
+
return (await stat(path)).isFile();
|
|
85
|
+
}
|
|
86
|
+
catch (cause) {
|
|
87
|
+
if (isMissing(cause))
|
|
88
|
+
return false;
|
|
89
|
+
throw cause;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function isMissing(cause) {
|
|
93
|
+
return cause instanceof Error && "code" in cause && cause.code === "ENOENT";
|
|
94
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { StateValue } from "xstate";
|
|
2
|
+
import type { AgentUpdate, HumanRequest } from "./index.ts";
|
|
3
|
+
import { type PrepareMachineRunOptions } from "./launcher.ts";
|
|
4
|
+
export interface HostedHumanRequest extends HumanRequest {
|
|
5
|
+
readonly requestId: string;
|
|
6
|
+
}
|
|
7
|
+
export interface MachineHostOptions extends PrepareMachineRunOptions {
|
|
8
|
+
readonly signal?: AbortSignal;
|
|
9
|
+
readonly onAgentUpdate?: (update: AgentUpdate) => void;
|
|
10
|
+
readonly onHumanRequest?: (request: HostedHumanRequest) => void;
|
|
11
|
+
readonly onState?: (state: StateValue) => void;
|
|
12
|
+
}
|
|
13
|
+
export interface MachineHostResult {
|
|
14
|
+
readonly state: StateValue;
|
|
15
|
+
}
|
|
16
|
+
export interface MachineHostRun {
|
|
17
|
+
readonly name: string;
|
|
18
|
+
readonly description: string;
|
|
19
|
+
readonly path: string;
|
|
20
|
+
readonly agentRoles: Readonly<Record<string, string>>;
|
|
21
|
+
readonly result: Promise<MachineHostResult>;
|
|
22
|
+
respond(response: string, requestId: string): Promise<void>;
|
|
23
|
+
terminate(): void;
|
|
24
|
+
}
|
|
25
|
+
export declare function startMachineHost(options: MachineHostOptions): Promise<MachineHostRun>;
|
package/dist/src/host.js
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import { execFileSync, fork } from "node:child_process";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import * as v from "valibot";
|
|
4
|
+
import { prepareMachineRun, } from "./launcher.js";
|
|
5
|
+
const childArgument = "--machines-child-host";
|
|
6
|
+
const humanRequestSchema = v.strictObject({
|
|
7
|
+
prompt: v.string(),
|
|
8
|
+
choices: v.optional(v.array(v.string())),
|
|
9
|
+
suggestions: v.optional(v.array(v.string())),
|
|
10
|
+
});
|
|
11
|
+
const agentUpdateSchema = v.variant("type", [
|
|
12
|
+
v.strictObject({
|
|
13
|
+
type: v.literal("identity"),
|
|
14
|
+
harness: v.string(),
|
|
15
|
+
model: v.optional(v.string()),
|
|
16
|
+
thinking: v.optional(v.string()),
|
|
17
|
+
}),
|
|
18
|
+
v.strictObject({
|
|
19
|
+
type: v.literal("output"),
|
|
20
|
+
text: v.string(),
|
|
21
|
+
}),
|
|
22
|
+
v.strictObject({
|
|
23
|
+
type: v.literal("tool"),
|
|
24
|
+
id: v.string(),
|
|
25
|
+
title: v.optional(v.string()),
|
|
26
|
+
status: v.optional(v.picklist(["pending", "in_progress", "completed", "failed"])),
|
|
27
|
+
}),
|
|
28
|
+
]);
|
|
29
|
+
const stateValueSchema = v.union([
|
|
30
|
+
v.string(),
|
|
31
|
+
v.record(v.string(), v.unknown()),
|
|
32
|
+
]);
|
|
33
|
+
const parentMessageSchema = v.variant("type", [
|
|
34
|
+
v.strictObject({
|
|
35
|
+
type: v.literal("launch"),
|
|
36
|
+
machine: v.string(),
|
|
37
|
+
input: v.optional(v.string()),
|
|
38
|
+
agents: v.optional(v.record(v.string(), v.string())),
|
|
39
|
+
cwd: v.string(),
|
|
40
|
+
home: v.optional(v.string()),
|
|
41
|
+
}),
|
|
42
|
+
v.strictObject({
|
|
43
|
+
type: v.literal("respond"),
|
|
44
|
+
requestId: v.string(),
|
|
45
|
+
response: v.string(),
|
|
46
|
+
}),
|
|
47
|
+
]);
|
|
48
|
+
const childMessageSchema = v.variant("type", [
|
|
49
|
+
v.strictObject({
|
|
50
|
+
type: v.literal("started"),
|
|
51
|
+
name: v.string(),
|
|
52
|
+
description: v.string(),
|
|
53
|
+
path: v.string(),
|
|
54
|
+
agentRoles: v.record(v.string(), v.string()),
|
|
55
|
+
}),
|
|
56
|
+
v.strictObject({ type: v.literal("state"), state: stateValueSchema }),
|
|
57
|
+
v.strictObject({ type: v.literal("agentUpdate"), update: agentUpdateSchema }),
|
|
58
|
+
v.strictObject({
|
|
59
|
+
type: v.literal("humanRequest"),
|
|
60
|
+
requestId: v.string(),
|
|
61
|
+
request: humanRequestSchema,
|
|
62
|
+
}),
|
|
63
|
+
v.strictObject({ type: v.literal("completed"), state: stateValueSchema }),
|
|
64
|
+
v.strictObject({ type: v.literal("failed"), message: v.string() }),
|
|
65
|
+
]);
|
|
66
|
+
export function startMachineHost(options) {
|
|
67
|
+
if (options.signal?.aborted)
|
|
68
|
+
return Promise.reject(new Error("Machine host was terminated"));
|
|
69
|
+
const cwd = options.cwd ?? process.cwd();
|
|
70
|
+
const child = fork(fileURLToPath(import.meta.url), [childArgument], {
|
|
71
|
+
cwd,
|
|
72
|
+
execArgv: [],
|
|
73
|
+
detached: process.platform !== "win32",
|
|
74
|
+
stdio: ["ignore", "pipe", "pipe", "ipc"],
|
|
75
|
+
});
|
|
76
|
+
child.stdout?.resume();
|
|
77
|
+
child.stderr?.resume();
|
|
78
|
+
let phase = "starting";
|
|
79
|
+
let pendingHuman;
|
|
80
|
+
let resolveResult;
|
|
81
|
+
let rejectResult;
|
|
82
|
+
const result = new Promise((resolve, reject) => {
|
|
83
|
+
resolveResult = resolve;
|
|
84
|
+
rejectResult = reject;
|
|
85
|
+
});
|
|
86
|
+
void result.catch(() => { });
|
|
87
|
+
return new Promise((resolveStarted, rejectStarted) => {
|
|
88
|
+
let observersReady = false;
|
|
89
|
+
const pendingObservations = [];
|
|
90
|
+
const observe = (observation) => {
|
|
91
|
+
if (observersReady)
|
|
92
|
+
observation();
|
|
93
|
+
else
|
|
94
|
+
pendingObservations.push(observation);
|
|
95
|
+
};
|
|
96
|
+
let disposed = false;
|
|
97
|
+
const dispose = () => {
|
|
98
|
+
if (disposed)
|
|
99
|
+
return;
|
|
100
|
+
disposed = true;
|
|
101
|
+
options.signal?.removeEventListener("abort", abort);
|
|
102
|
+
terminateProcessTree(child);
|
|
103
|
+
};
|
|
104
|
+
const abort = () => fail(new Error("Machine host was terminated"));
|
|
105
|
+
const fail = (error) => {
|
|
106
|
+
if (phase === "completed" || phase === "failed")
|
|
107
|
+
return;
|
|
108
|
+
const wasStarting = phase === "starting";
|
|
109
|
+
phase = "failed";
|
|
110
|
+
pendingHuman = undefined;
|
|
111
|
+
rejectResult(error);
|
|
112
|
+
if (wasStarting)
|
|
113
|
+
rejectStarted(error);
|
|
114
|
+
dispose();
|
|
115
|
+
};
|
|
116
|
+
options.signal?.addEventListener("abort", abort, { once: true });
|
|
117
|
+
child.on("message", (value) => {
|
|
118
|
+
if (phase === "completed" || phase === "failed")
|
|
119
|
+
return;
|
|
120
|
+
let message;
|
|
121
|
+
try {
|
|
122
|
+
message = parseMessage(childMessageSchema, value, "child");
|
|
123
|
+
}
|
|
124
|
+
catch (cause) {
|
|
125
|
+
fail(asError(cause));
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (message.type === "started") {
|
|
129
|
+
if (phase !== "starting") {
|
|
130
|
+
fail(new Error("Machine host sent an unexpected started message"));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
phase = "running";
|
|
134
|
+
resolveStarted({
|
|
135
|
+
name: message.name,
|
|
136
|
+
description: message.description,
|
|
137
|
+
path: message.path,
|
|
138
|
+
agentRoles: message.agentRoles,
|
|
139
|
+
result,
|
|
140
|
+
async respond(response, requestId) {
|
|
141
|
+
if (phase !== "running" || pendingHuman === undefined) {
|
|
142
|
+
throw new Error("Machine host is not waiting for Human input");
|
|
143
|
+
}
|
|
144
|
+
if (pendingHuman.requestId !== requestId) {
|
|
145
|
+
throw new Error("Machine Human request is stale; read the current requestId");
|
|
146
|
+
}
|
|
147
|
+
if (pendingHuman.request.choices !== undefined
|
|
148
|
+
&& !pendingHuman.request.choices.includes(response)) {
|
|
149
|
+
throw new Error(`Expected one of: ${pendingHuman.request.choices.join(", ")}`);
|
|
150
|
+
}
|
|
151
|
+
// Claim before yielding: no second caller can send this request again.
|
|
152
|
+
pendingHuman = undefined;
|
|
153
|
+
try {
|
|
154
|
+
await sendChild(child, { type: "respond", requestId, response });
|
|
155
|
+
}
|
|
156
|
+
catch (cause) {
|
|
157
|
+
fail(asError(cause));
|
|
158
|
+
throw cause;
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
terminate() {
|
|
162
|
+
fail(new Error("Machine host was terminated"));
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
queueMicrotask(() => {
|
|
166
|
+
observersReady = true;
|
|
167
|
+
pendingObservations.splice(0).forEach((observation) => observation());
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
else if (message.type === "state") {
|
|
171
|
+
observe(() => options.onState?.(message.state));
|
|
172
|
+
}
|
|
173
|
+
else if (message.type === "agentUpdate") {
|
|
174
|
+
observe(() => options.onAgentUpdate?.(message.update));
|
|
175
|
+
}
|
|
176
|
+
else if (message.type === "humanRequest") {
|
|
177
|
+
if (phase !== "running" || pendingHuman !== undefined) {
|
|
178
|
+
fail(new Error("Machine host sent an unexpected Human request"));
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
pendingHuman = {
|
|
182
|
+
requestId: message.requestId,
|
|
183
|
+
request: message.request,
|
|
184
|
+
};
|
|
185
|
+
observe(() => options.onHumanRequest?.({ ...message.request, requestId: message.requestId }));
|
|
186
|
+
}
|
|
187
|
+
else if (message.type === "completed") {
|
|
188
|
+
if (phase !== "running") {
|
|
189
|
+
fail(new Error("Machine host completed before it started"));
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
phase = "completed";
|
|
193
|
+
pendingHuman = undefined;
|
|
194
|
+
dispose();
|
|
195
|
+
resolveResult({ state: message.state });
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
fail(new Error(message.message));
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
child.once("error", (cause) => fail(asError(cause)));
|
|
202
|
+
child.once("exit", (code, signal) => {
|
|
203
|
+
if (phase === "completed" || phase === "failed")
|
|
204
|
+
return;
|
|
205
|
+
fail(new Error(`Machine host exited before completion (${signal ?? `code ${String(code)}`})`));
|
|
206
|
+
});
|
|
207
|
+
child.once("spawn", () => {
|
|
208
|
+
if (phase !== "starting")
|
|
209
|
+
return;
|
|
210
|
+
const launch = {
|
|
211
|
+
type: "launch",
|
|
212
|
+
machine: options.machine,
|
|
213
|
+
cwd,
|
|
214
|
+
...(options.input === undefined ? {} : { input: options.input }),
|
|
215
|
+
...(options.agents === undefined ? {} : { agents: options.agents }),
|
|
216
|
+
...(options.home === undefined ? {} : { home: options.home }),
|
|
217
|
+
};
|
|
218
|
+
void sendChild(child, launch).catch((cause) => fail(asError(cause)));
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
// Hosts own the subprocesses started by Operations and Agent harnesses. A separate
|
|
223
|
+
// process group lets disposal include descendants even after the host has exited.
|
|
224
|
+
// Machine-specific cleanup belongs to explicit workflow states, not termination.
|
|
225
|
+
function terminateProcessTree(child) {
|
|
226
|
+
if (child.pid === undefined)
|
|
227
|
+
return;
|
|
228
|
+
try {
|
|
229
|
+
if (process.platform === "win32") {
|
|
230
|
+
execFileSync("taskkill", ["/pid", String(child.pid), "/t", "/f"], { stdio: "ignore" });
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
process.kill(-child.pid, "SIGKILL");
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
catch (cause) {
|
|
237
|
+
if (process.platform !== "win32" && cause.code !== "ESRCH") {
|
|
238
|
+
child.kill("SIGKILL");
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
function runChildHost() {
|
|
243
|
+
let launched = false;
|
|
244
|
+
let closing = false;
|
|
245
|
+
let pendingHuman;
|
|
246
|
+
let outgoing = Promise.resolve();
|
|
247
|
+
const send = (message) => {
|
|
248
|
+
outgoing = outgoing.then(() => sendParent(message));
|
|
249
|
+
};
|
|
250
|
+
process.on("message", (value) => {
|
|
251
|
+
let message;
|
|
252
|
+
try {
|
|
253
|
+
message = parseMessage(parentMessageSchema, value, "parent");
|
|
254
|
+
}
|
|
255
|
+
catch (cause) {
|
|
256
|
+
send({ type: "failed", message: asError(cause).message });
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (message.type === "respond") {
|
|
260
|
+
if (pendingHuman === undefined || pendingHuman.requestId !== message.requestId) {
|
|
261
|
+
send({ type: "failed", message: "Machine host received an unexpected Human response" });
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
const { resolve } = pendingHuman;
|
|
265
|
+
pendingHuman = undefined;
|
|
266
|
+
resolve(message.response);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
if (launched) {
|
|
270
|
+
send({ type: "failed", message: "Machine host can launch only one Machine" });
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
launched = true;
|
|
274
|
+
void launch(message);
|
|
275
|
+
});
|
|
276
|
+
process.once("disconnect", () => {
|
|
277
|
+
if (!closing) {
|
|
278
|
+
terminateProcessTree({ pid: process.pid, kill: () => { process.exit(1); } });
|
|
279
|
+
process.exit(1);
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
async function launch(message) {
|
|
283
|
+
try {
|
|
284
|
+
const prepared = await prepareMachineRun(message);
|
|
285
|
+
send({
|
|
286
|
+
type: "started",
|
|
287
|
+
name: prepared.name,
|
|
288
|
+
description: prepared.description,
|
|
289
|
+
path: prepared.path,
|
|
290
|
+
agentRoles: prepared.agentRoles,
|
|
291
|
+
});
|
|
292
|
+
const result = await prepared.start({
|
|
293
|
+
onAgentUpdate: (update) => send({ type: "agentUpdate", update }),
|
|
294
|
+
onState: (state) => send({ type: "state", state }),
|
|
295
|
+
human: (request) => new Promise((resolve) => {
|
|
296
|
+
if (pendingHuman !== undefined) {
|
|
297
|
+
throw new Error("Machine host cannot wait for two Human responses at once");
|
|
298
|
+
}
|
|
299
|
+
const requestId = crypto.randomUUID();
|
|
300
|
+
pendingHuman = { requestId, resolve };
|
|
301
|
+
send({
|
|
302
|
+
type: "humanRequest",
|
|
303
|
+
requestId,
|
|
304
|
+
request: {
|
|
305
|
+
prompt: request.prompt,
|
|
306
|
+
...(request.choices === undefined
|
|
307
|
+
? {}
|
|
308
|
+
: { choices: [...request.choices] }),
|
|
309
|
+
...(request.suggestions === undefined
|
|
310
|
+
? {}
|
|
311
|
+
: { suggestions: [...request.suggestions] }),
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
}),
|
|
315
|
+
});
|
|
316
|
+
send({ type: "completed", state: result.value });
|
|
317
|
+
}
|
|
318
|
+
catch (cause) {
|
|
319
|
+
send({ type: "failed", message: asError(cause).message });
|
|
320
|
+
}
|
|
321
|
+
finally {
|
|
322
|
+
await outgoing;
|
|
323
|
+
closing = true;
|
|
324
|
+
if (process.connected)
|
|
325
|
+
process.disconnect();
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
function parseMessage(schema, value, sender) {
|
|
330
|
+
const result = v.safeParse(schema, value);
|
|
331
|
+
if (!result.success)
|
|
332
|
+
throw new Error(`Machine host received an invalid ${sender} message`);
|
|
333
|
+
return result.output;
|
|
334
|
+
}
|
|
335
|
+
function sendChild(child, message) {
|
|
336
|
+
return new Promise((resolve, reject) => {
|
|
337
|
+
if (!child.connected) {
|
|
338
|
+
reject(new Error("Machine host IPC is closed"));
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
child.send(message, (error) => error === null ? resolve() : reject(error));
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
function sendParent(message) {
|
|
345
|
+
return new Promise((resolve, reject) => {
|
|
346
|
+
if (process.send === undefined || !process.connected) {
|
|
347
|
+
reject(new Error("Machine host parent IPC is closed"));
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
process.send(message, (error) => error === null ? resolve() : reject(error));
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
function asError(cause) {
|
|
354
|
+
return cause instanceof Error ? cause : new Error(String(cause));
|
|
355
|
+
}
|
|
356
|
+
if (process.argv[2] === childArgument)
|
|
357
|
+
runChildHost();
|