@volter/world-runtime 3.0.38 → 3.0.39

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.
Files changed (168) hide show
  1. package/dist/known-external-services.json +3 -10
  2. package/dist/src/actions-runner-cli.js +3 -3
  3. package/dist/src/actions-runner.js +2 -1
  4. package/dist/src/app-url.js +2 -1
  5. package/dist/src/backings.d.ts +10 -1
  6. package/dist/src/backings.js +66 -7
  7. package/dist/src/branch.js +85 -52
  8. package/dist/src/browser-routes.cjs +59 -0
  9. package/dist/src/ca-trust.js +4 -3
  10. package/dist/src/changeset.js +4 -3
  11. package/dist/src/cli-commands.js +25 -9
  12. package/dist/src/cli.js +2 -1
  13. package/dist/src/command-lifetime.d.mts +12 -0
  14. package/dist/src/command-lifetime.d.ts +1 -15
  15. package/dist/src/command-lifetime.js +1 -98
  16. package/dist/src/command-lifetime.mjs +90 -0
  17. package/dist/src/configs.js +1 -1
  18. package/dist/src/console-apart.js +3 -2
  19. package/dist/src/covers.d.ts +3 -2
  20. package/dist/src/covers.js +95 -18
  21. package/dist/src/error-message.d.mts +1 -0
  22. package/dist/src/error-message.d.ts +1 -0
  23. package/dist/src/error-message.js +2 -0
  24. package/dist/src/error-message.mjs +10 -0
  25. package/dist/src/guest-command.d.mts +4 -0
  26. package/dist/src/guest-command.mjs +26 -0
  27. package/dist/src/guest-control.d.mts +1 -0
  28. package/dist/src/guest-control.mjs +17 -0
  29. package/dist/src/guest-entry.mjs +146 -0
  30. package/dist/src/guest-processes.d.mts +9 -0
  31. package/dist/src/guest-processes.mjs +47 -0
  32. package/dist/src/guest-runner.d.ts +6 -0
  33. package/dist/src/guest-runner.js +567 -0
  34. package/dist/src/host.js +5 -4
  35. package/dist/src/index.d.ts +1 -0
  36. package/dist/src/index.js +1 -0
  37. package/dist/src/infra-cli.js +4 -0
  38. package/dist/src/infra.js +133 -23
  39. package/dist/src/init.js +60 -23
  40. package/dist/src/lifecycle-record.d.ts +3 -1
  41. package/dist/src/lifecycle-record.js +29 -6
  42. package/dist/src/livekit-backing.mjs +37 -0
  43. package/dist/src/local-branches.js +11 -3
  44. package/dist/src/mongodb-backing.d.ts +7 -0
  45. package/dist/src/mongodb-backing.js +58 -0
  46. package/dist/src/native-postgres-backing.d.ts +4 -0
  47. package/dist/src/native-postgres-backing.js +404 -0
  48. package/dist/src/origin.js +2 -1
  49. package/dist/src/owned-path.d.ts +4 -0
  50. package/dist/src/owned-path.js +37 -0
  51. package/dist/src/pack-facts.d.ts +1 -0
  52. package/dist/src/pglite-backing.d.ts +4 -13
  53. package/dist/src/pglite-backing.js +198 -106
  54. package/dist/src/pglite-host.mjs +47 -17
  55. package/dist/src/placeholder.d.ts +3 -2
  56. package/dist/src/placeholder.js +9 -4
  57. package/dist/src/process-groups.d.mts +2 -0
  58. package/dist/src/process-groups.d.ts +1 -4
  59. package/dist/src/process-groups.js +1 -49
  60. package/dist/src/process-groups.mjs +59 -0
  61. package/dist/src/process-receipt.d.mts +6 -0
  62. package/dist/src/process-receipt.d.ts +11 -0
  63. package/dist/src/process-receipt.js +3 -0
  64. package/dist/src/process-receipt.mjs +45 -0
  65. package/dist/src/process-signal.d.mts +9 -0
  66. package/dist/src/process-signal.mjs +92 -0
  67. package/dist/src/project-inspect.d.ts +1 -1
  68. package/dist/src/project-inspect.js +4 -2
  69. package/dist/src/proxy-daemon.js +2 -1
  70. package/dist/src/redirect-proxy.js +186 -19
  71. package/dist/src/redis-backing.js +99 -35
  72. package/dist/src/reflect.js +2 -1
  73. package/dist/src/root.js +7 -6
  74. package/dist/src/run-task-worker.js +2 -1
  75. package/dist/src/runtime.d.ts +24 -7
  76. package/dist/src/runtime.js +703 -286
  77. package/dist/src/schema.d.ts +12 -1
  78. package/dist/src/schema.js +20 -5
  79. package/dist/src/served-world.d.ts +1 -0
  80. package/dist/src/served-world.js +26 -14
  81. package/dist/src/service-recorder.js +8 -3
  82. package/dist/src/signals.d.mts +1 -0
  83. package/dist/src/signals.d.ts +1 -1
  84. package/dist/src/signals.js +1 -11
  85. package/dist/src/signals.mjs +10 -0
  86. package/dist/src/tail.d.ts +2 -0
  87. package/dist/src/tail.js +9 -2
  88. package/dist/src/tcp-port.js +27 -13
  89. package/dist/src/temporal-backing.mjs +28 -0
  90. package/dist/src/up-cli.js +8 -3
  91. package/dist/src/up-task-worker.js +2 -1
  92. package/dist/src/websocket-relay.d.ts +1 -1
  93. package/dist/src/websocket-relay.js +23 -10
  94. package/dist/src/world-view.js +2 -2
  95. package/known-external-services.json +3 -10
  96. package/package.json +12 -7
  97. package/src/actions-runner-cli.ts +3 -3
  98. package/src/actions-runner.ts +2 -1
  99. package/src/app-url.ts +2 -1
  100. package/src/backings.ts +65 -7
  101. package/src/branch.ts +73 -34
  102. package/src/browser-routes.cjs +59 -0
  103. package/src/ca-trust.ts +4 -3
  104. package/src/changeset.ts +4 -3
  105. package/src/cli-commands.ts +21 -9
  106. package/src/cli.ts +2 -1
  107. package/src/command-lifetime.d.mts +12 -0
  108. package/src/command-lifetime.mjs +90 -0
  109. package/src/command-lifetime.ts +1 -79
  110. package/src/configs.ts +1 -1
  111. package/src/console-apart.ts +2 -1
  112. package/src/covers.ts +97 -19
  113. package/src/error-message.d.mts +1 -0
  114. package/src/error-message.mjs +10 -0
  115. package/src/error-message.ts +2 -0
  116. package/src/guest-command.d.mts +4 -0
  117. package/src/guest-command.mjs +26 -0
  118. package/src/guest-control.d.mts +1 -0
  119. package/src/guest-control.mjs +17 -0
  120. package/src/guest-entry.mjs +146 -0
  121. package/src/guest-processes.d.mts +9 -0
  122. package/src/guest-processes.mjs +47 -0
  123. package/src/guest-runner.ts +414 -0
  124. package/src/host.ts +5 -4
  125. package/src/index.ts +1 -0
  126. package/src/infra-cli.ts +4 -0
  127. package/src/infra.ts +103 -22
  128. package/src/init.ts +55 -22
  129. package/src/lifecycle-record.ts +21 -6
  130. package/src/livekit-backing.mjs +37 -0
  131. package/src/local-branches.ts +6 -3
  132. package/src/mongodb-backing.ts +55 -0
  133. package/src/native-postgres-backing.ts +329 -0
  134. package/src/origin.ts +2 -1
  135. package/src/owned-path.ts +32 -0
  136. package/src/pack-facts.ts +1 -0
  137. package/src/pglite-backing.ts +155 -79
  138. package/src/pglite-host.mjs +47 -17
  139. package/src/placeholder.ts +10 -5
  140. package/src/process-groups.d.mts +2 -0
  141. package/src/process-groups.mjs +59 -0
  142. package/src/process-groups.ts +1 -33
  143. package/src/process-receipt.d.mts +6 -0
  144. package/src/process-receipt.mjs +45 -0
  145. package/src/process-receipt.ts +5 -0
  146. package/src/process-signal.d.mts +9 -0
  147. package/src/process-signal.mjs +92 -0
  148. package/src/project-inspect.ts +4 -2
  149. package/src/proxy-daemon.ts +2 -1
  150. package/src/redirect-proxy.ts +153 -18
  151. package/src/redis-backing.ts +74 -25
  152. package/src/reflect.ts +2 -1
  153. package/src/root.ts +7 -6
  154. package/src/run-task-worker.ts +2 -1
  155. package/src/runtime.ts +596 -265
  156. package/src/schema.ts +30 -6
  157. package/src/served-world.ts +21 -15
  158. package/src/service-recorder.ts +4 -2
  159. package/src/signals.d.mts +1 -0
  160. package/src/signals.mjs +10 -0
  161. package/src/signals.ts +1 -10
  162. package/src/tail.ts +10 -3
  163. package/src/tcp-port.ts +24 -13
  164. package/src/temporal-backing.mjs +28 -0
  165. package/src/up-cli.ts +6 -3
  166. package/src/up-task-worker.ts +2 -1
  167. package/src/websocket-relay.ts +18 -11
  168. package/src/world-view.ts +2 -2
@@ -8,14 +8,20 @@
8
8
  // in a container is state outside it (a branch of the World would share the container's queue and cache). The
9
9
  // twin keeps each key a subject of the World (the Redis pack's storage module). It needs nothing
10
10
  // installed: no container runtime, no redis-server binary.
11
- import { closeSync, existsSync, mkdirSync, openSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
11
+ import { closeSync, existsSync, mkdirSync, openSync, readFileSync, rmSync } from 'node:fs';
12
12
  import { connect } from 'node:net';
13
13
  import { dirname, join } from 'node:path';
14
- import { spawn } from 'node:child_process';
14
+ import { commandLifetime, COMMAND_GRACE_MS, COMMAND_CONFIRM_MS } from './command-lifetime.ts';
15
+ import { publishProcessReceipt, readProcessReceipt, readSpawnReceipt, spawnReceiptPath, signalOwnedProcess } from './process-receipt.ts';
16
+ import { spawn, type ChildProcess } from 'node:child_process';
17
+ import { survivingOwnedGroups } from './process-groups.ts';
18
+ import { errorMessage } from './error-message.ts';
15
19
  import { findInstalledPackage, packCli } from './catalog.ts';
16
20
  import { vendorPackageDir } from '@volter/world-core/runtime';
17
21
  import type { HostLaunch, InfraService } from './pglite-backing.ts';
18
22
 
23
+ interface RedisHost { child: ChildProcess; lifetime: ReturnType<typeof commandLifetime>; closed: boolean }
24
+ const hosts = new Map<string, RedisHost>();
19
25
  const PACKAGE = '@volter/twin-redis';
20
26
  const READY_TIMEOUT_MS = 60_000;
21
27
 
@@ -34,7 +40,7 @@ const pidPath = (dataDir: string, service: InfraService): string => join(dataDir
34
40
  export const redisTwinRoot = (dataDir: string): string => join(dataDir, 'redis-twin');
35
41
 
36
42
  function alive(pid: number): boolean {
37
- try { process.kill(pid, 0); return true; } catch { return false; }
43
+ try { process.kill(pid, 0); return true; } catch (error) { if ((error as NodeJS.ErrnoException).code === 'ESRCH') return false; throw error; }
38
44
  }
39
45
 
40
46
  function listening(port: number): Promise<boolean> {
@@ -53,7 +59,9 @@ const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
53
59
  export async function redisTwinUp(services: InfraService[], dataDir: string, cli: string, launch: HostLaunch = {}): Promise<void> {
54
60
  for (const service of services) {
55
61
  const pidFile = pidPath(dataDir, service);
56
- if (existsSync(pidFile) && alive(Number(readFileSync(pidFile, 'utf8').trim())) && await listening(service.hostPort)) continue;
62
+ if (hosts.has(pidFile) || existsSync(pidFile) || existsSync(spawnReceiptPath(pidFile))) {
63
+ await redisTwinDown([service], dataDir);
64
+ }
57
65
  // a port that already answers is another process's (another World's twin, a redis-server): serving there would
58
66
  // hand this World's app someone else's keys, so it is refused, never taken as ready
59
67
  if (await listening(service.hostPort)) throw new Error(`port ${service.hostPort} already serves Redis for another process`);
@@ -66,21 +74,39 @@ export async function redisTwinUp(services: InfraService[], dataDir: string, cli
66
74
  // the runtime this runs on: Bun runs the pack's TypeScript as it is; Node (volter-world-infra's shebang) needs
67
75
  // type transformation, not only stripping (the kernel declares constructor parameter properties)
68
76
  const runtime = typeof (globalThis as { Bun?: unknown }).Bun !== 'undefined' || !cli.endsWith('.ts') ? [] : ['--experimental-transform-types', '--no-warnings'];
69
- const child = spawn(process.execPath, [...runtime, cli, 'serve', '--port', String(service.hostPort), '--root', root], {
70
- env: launch.env ?? process.env,
71
- detached: true,
72
- windowsHide: true,
73
- stdio: ['ignore', logFd, logFd],
74
- });
75
- child.unref();
76
- closeSync(logFd);
77
- writeFileSync(pidFile, `${child.pid}\n`);
78
- const deadline = Date.now() + READY_TIMEOUT_MS;
79
- while (!(await listening(service.hostPort)) || !alive(child.pid!)) {
80
- if (!alive(child.pid!)) throw new Error(`redis exited before serving — see ${log}`);
77
+ let held: RedisHost | undefined;
78
+ try {
79
+ try {
80
+ const child = spawn(process.execPath, [...runtime, cli, 'serve', '--port', String(service.hostPort), '--root', root], {
81
+ env: launch.env ?? process.env, detached: true, windowsHide: true, stdio: ['ignore', logFd, logFd],
82
+ });
83
+ // Retain BEFORE descriptor close, birth capture or either publication can fail.
84
+ held = { child, lifetime: commandLifetime(child, launch.signal), closed: false };
85
+ const observed = held;
86
+ child.once('close', () => { observed.closed = true; });
87
+ child.on('error', () => {}); // lifetime joins spawn failure and close
88
+ hosts.set(pidFile, held);
89
+ } finally { closeSync(logFd); }
90
+ if (!held.child.pid) throw new Error('Redis child did not start');
91
+ const identity = await held.lifetime.identity;
92
+ if (!identity?.start) throw new Error('Redis birth capture failed; retiring held child');
93
+ publishProcessReceipt(spawnReceiptPath(pidFile), JSON.stringify(identity) + '\n');
94
+ publishProcessReceipt(pidFile, `${held.child.pid}\n`);
95
+ const deadline = Date.now() + READY_TIMEOUT_MS;
96
+ while (!(await listening(service.hostPort)) || !alive(held.child.pid)) {
97
+ if (!alive(held.child.pid)) throw new Error(`redis exited before serving — see ${log}`);
98
+ launch.signal?.throwIfAborted();
99
+ if (Date.now() > deadline) throw new Error(`redis did not serve port ${service.hostPort} within ${READY_TIMEOUT_MS / 1000}s — see ${log}`);
100
+ await sleep(200);
101
+ }
81
102
  launch.signal?.throwIfAborted();
82
- if (Date.now() > deadline) throw new Error(`redis did not serve port ${service.hostPort} within ${READY_TIMEOUT_MS / 1000}s — see ${log}`);
83
- await sleep(200);
103
+ held.child.unref();
104
+ } catch (error) {
105
+ if (held) {
106
+ try { await redisTwinDown([service], dataDir); }
107
+ catch (cleanup) { throw new AggregateError([error, cleanup], 'Redis startup and retirement failed; ownership retained'); }
108
+ }
109
+ throw error;
84
110
  }
85
111
  }
86
112
  }
@@ -98,14 +124,37 @@ export async function redisTwinStatus(services: InfraService[], dataDir: string)
98
124
  export async function redisTwinDown(services: InfraService[], dataDir: string): Promise<void> {
99
125
  for (const service of services) {
100
126
  const pidFile = pidPath(dataDir, service);
101
- if (!existsSync(pidFile)) continue;
102
- const pid = Number(readFileSync(pidFile, 'utf8').trim());
103
- if (alive(pid)) {
104
- try { process.kill(pid, 'SIGTERM'); } catch { /* already gone */ }
105
- const deadline = Date.now() + 5000;
106
- while (alive(pid) && Date.now() < deadline) await sleep(100);
107
- if (alive(pid)) { try { process.kill(pid, 'SIGKILL'); } catch { /* raced */ } }
127
+ const recorded = readProcessReceipt(pidFile, text => {
128
+ const pid = Number(text.trim());
129
+ if (!Number.isInteger(pid) || pid <= 0) throw new Error('Invalid Redis ownership PID');
130
+ return pid;
131
+ });
132
+ const identity = readSpawnReceipt(pidFile);
133
+ const held = hosts.get(pidFile);
134
+ if (recorded.error || identity.error) process.stderr.write(`${errorMessage(new AggregateError([recorded.error, identity.error].filter(Boolean), 'Redis ownership uncertain; retrying established ownership'))}\n`);
135
+ if (held) {
136
+ const retired = () => held!.closed && (!held!.child.pid || !survivingOwnedGroups([held!.child.pid]).length);
137
+ if (!retired()) {
138
+ held.child.ref(); held.lifetime.cancel('SIGTERM');
139
+ try { await held.lifetime.finish(); }
140
+ catch (error) { if (!retired()) throw error; }
141
+ }
142
+ } else {
143
+ if (!recorded.present && !identity.present) continue;
144
+ const pid = identity.value?.pid ?? recorded.value;
145
+ if (!pid) throw new AggregateError([recorded.error, identity.error].filter(Boolean), 'Redis ownership uncertain; receipts retained');
146
+ if (recorded.value && identity.value && recorded.value !== identity.value.pid) throw new Error('Redis ownership receipts disagree; retirement refused; receipts retained');
147
+ const live = () => survivingOwnedGroups([pid]).length > 0;
148
+ for (const [signal, limit] of [['SIGTERM', COMMAND_GRACE_MS], ['SIGKILL', COMMAND_CONFIRM_MS]] as const) {
149
+ if (!live()) break;
150
+ await signalOwnedProcess(identity.value, signal, true);
151
+ const deadline = Date.now() + limit;
152
+ while (live() && Date.now() < deadline) await sleep(20);
153
+ }
154
+ if (live()) throw new Error('Redis retirement unconfirmed; receipts retained');
108
155
  }
109
156
  rmSync(pidFile, { force: true });
157
+ rmSync(spawnReceiptPath(pidFile), { force: true });
158
+ hosts.delete(pidFile);
110
159
  }
111
160
  }
package/src/reflect.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { errorMessage } from './error-message.ts';
1
2
  import { serveHttp, type HttpServer } from '@volter/world-core/runtime';
2
3
  import { websocketRelay } from './websocket-relay.ts';
3
4
  import { sessionTrustEnv } from './ca-trust.ts';
@@ -178,7 +179,7 @@ export async function startReflectFront(options: ReflectFrontOptions): Promise<R
178
179
  } catch (error) {
179
180
  let world: string | undefined;
180
181
  try { world = options.envLoader().VOLTER_WORLD; } catch { /* the answer still goes out */ }
181
- return new Response(twinUnreachableMessage(target.vendor, origin.origin, error instanceof Error ? error : new Error(String(error)), world), { status: 502 });
182
+ return new Response(twinUnreachableMessage(target.vendor, origin.origin, error instanceof Error ? error : new Error(errorMessage(error)), world), { status: 502 });
182
183
  }
183
184
  },
184
185
  upgrade: websocketRelay(req => {
package/src/root.ts CHANGED
@@ -7,6 +7,7 @@
7
7
  // never in a pack, never an env var). `volter world deploy` performs landed entries against each
8
8
  // root through the pack's adapters over the kernel executor, with the world's checks in front;
9
9
  // `deploy: auto` does the same the moment an entry is appended (the host installs the hook).
10
+ import { errorMessage } from './error-message.ts';
10
11
  import { randomBytes } from 'node:crypto';
11
12
  import { packEntry } from './catalog.ts';
12
13
  import { existsSync, readFileSync } from 'node:fs';
@@ -111,7 +112,7 @@ export async function bindLegacyCredentials(worldRoot: string, current: () => Re
111
112
  // admits nothing until it opens again, and is never taken again from roots a request may have changed
112
113
  try { inventory = (await currentSealer().open(JSON.parse(heldInventory) as SealedCredential, INVENTORY_SLOT)) as unknown as Inventory; }
113
114
  catch (error) {
114
- console.error(`[credential] the inventory beside the World does not open (${(error as Error).message}): nothing is admitted until it does`);
115
+ console.error(`[credential] the inventory beside the World does not open (${errorMessage(error)}): nothing is admitted until it does`);
115
116
  return { bound: [], failed: [], refused: [], ready: false };
116
117
  }
117
118
  }
@@ -121,7 +122,7 @@ export async function bindLegacyCredentials(worldRoot: string, current: () => Re
121
122
  store.write(inventoryPath, `${JSON.stringify(sealed, null, 2)}\n`, { secret: true });
122
123
  return true;
123
124
  } catch (error) {
124
- console.error(`[credential] the inventory could not be kept (${(error as Error).message})`);
125
+ console.error(`[credential] the inventory could not be kept (${errorMessage(error)})`);
125
126
  return false;
126
127
  }
127
128
  };
@@ -149,7 +150,7 @@ export async function bindLegacyCredentials(worldRoot: string, current: () => Re
149
150
  delete inventory.pending[name]; changed = true;
150
151
  bound.push(name);
151
152
  } catch (error) {
152
- console.warn(`[credential] ${name}: not bound to its destination (${(error as Error).message})`);
153
+ console.warn(`[credential] ${name}: not bound to its destination (${errorMessage(error)})`);
153
154
  failed.push(name);
154
155
  }
155
156
  }
@@ -170,7 +171,7 @@ export async function bindLegacyCredentials(worldRoot: string, current: () => Re
170
171
  store.write(path, `${JSON.stringify({ ...moved, fingerprint: sealed.fingerprint, ...(sealed.rotatedAt ? { rotatedAt: sealed.rotatedAt } : {}) }, null, 2)}\n`, { secret: true });
171
172
  bound.push(name);
172
173
  } catch (error) {
173
- console.warn(`[credential] ${name}: not moved to the vault (${(error as Error).message})`);
174
+ console.warn(`[credential] ${name}: not moved to the vault (${errorMessage(error)})`);
174
175
  failed.push(name);
175
176
  }
176
177
  }
@@ -246,7 +247,7 @@ export async function rootCredentialInput(worldRoot: string, vendor: string, env
246
247
  try {
247
248
  response = await fetch(at, { headers: { 'x-vault-token': vault.token, ...(vault.namespace ? { 'x-vault-namespace': vault.namespace } : {}) }, signal: AbortSignal.timeout(10_000) });
248
249
  } catch (error) {
249
- notes.push(`the vault at ${vault.addr} did not answer (${(error as Error).message}; a private CA is not read from BAO_CACERT/VAULT_CACERT here)`);
250
+ notes.push(`the vault at ${vault.addr} did not answer (${errorMessage(error)}; a private CA is not read from BAO_CACERT/VAULT_CACERT here)`);
250
251
  }
251
252
  if (response) {
252
253
  if (response.status === 403) throw new Error(`the vault at ${vault.addr} refused kv/vendors/${vendor} (403): sign in again, or with a token that may read it`);
@@ -448,7 +449,7 @@ export async function refreshTwin(opts: { worldRoot: string; vendor: string; con
448
449
  // failure — its reason and how many in a row — so the status door and the twin's status say so
449
450
  const held = store.read(marker);
450
451
  const previous = held !== null ? (JSON.parse(held) as { failed?: { count?: number } }) : {};
451
- const reason = error instanceof Error ? error.message : String(error);
452
+ const reason = errorMessage(error);
452
453
  store.write(marker, `${JSON.stringify({ ...previous, at: new Date().toISOString(), failed: { at, reason, count: (previous.failed?.count ?? 0) + 1 } })}\n`);
453
454
  throw error;
454
455
  }
@@ -1,4 +1,5 @@
1
1
  import { runWorldOwned } from './runtime.ts';
2
+ import { errorMessage } from './error-message.ts';
2
3
  import type { RunTaskReply, RunTaskRequest } from './run-task.ts';
3
4
 
4
5
  // Only this process owns the IPC endpoint; child stdio explicitly excludes fd 3. A lost
@@ -20,7 +21,7 @@ process.on('message', async message => {
20
21
  started = true;
21
22
  let reply: RunTaskReply;
22
23
  try { reply = { result: await runWorldOwned(request.configId, request.command, request.options, abort.signal) }; }
23
- catch (error) { reply = { error: error instanceof Error ? error.message : String(error) }; }
24
+ catch (error) { reply = { error: errorMessage(error) }; }
24
25
  if ('error' in reply) process.stderr.write(`${reply.error}\n`);
25
26
  if (process.connected) process.send!(reply, () => process.exit('error' in reply ? 1 : 0));
26
27
  else process.exit('error' in reply ? 1 : 0);