@mcmcjs/julia 0.4.0 → 0.6.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/dist/index.cjs CHANGED
@@ -29,6 +29,7 @@ __export(index_exports, {
29
29
  juliaEngine: () => juliaEngine,
30
30
  juliaupInstallCommand: () => juliaupInstallCommand,
31
31
  listVersions: () => listVersions,
32
+ listWorkers: () => listWorkers,
32
33
  managedProjectDir: () => managedProjectDir,
33
34
  managedProjectReady: () => managedProjectReady,
34
35
  planSetup: () => planSetup,
@@ -37,11 +38,16 @@ __export(index_exports, {
37
38
  resolveVersion: () => resolveVersion,
38
39
  runDoctor: () => runDoctor,
39
40
  runFit: () => runFit,
41
+ runFitAuto: () => runFitAuto,
40
42
  runMatrix: () => runMatrix,
41
43
  runPredict: () => runPredict,
42
44
  runSetup: () => runSetup,
43
45
  setDefaultVersion: () => setDefaultVersion,
44
- updateVersion: () => updateVersion
46
+ sharedTmpParent: () => sharedTmpParent,
47
+ stopWorker: () => stopWorker,
48
+ updateVersion: () => updateVersion,
49
+ validatePins: () => validatePins,
50
+ validateVersionString: () => validateVersionString
45
51
  });
46
52
  module.exports = __toCommonJS(index_exports);
47
53
 
@@ -56,7 +62,8 @@ var import_engine = require("@mcmcjs/engine");
56
62
  var import_engine2 = require("@mcmcjs/engine");
57
63
  var defaultRunner = (0, import_engine.createRunner)();
58
64
  function candidates(binary) {
59
- return [(0, import_node_path.join)((0, import_node_os.homedir)(), ".juliaup", "bin", binary), binary];
65
+ const home = process.env.HOME || (0, import_node_os.homedir)();
66
+ return [(0, import_node_path.join)(home, ".juliaup", "bin", binary), binary];
60
67
  }
61
68
  async function detect(binary, parseVersion, runner) {
62
69
  for (const path of candidates(binary)) {
@@ -104,17 +111,24 @@ var juliaEngine = {
104
111
 
105
112
  // src/fit.ts
106
113
  var import_node_fs = require("fs");
107
- var import_node_os2 = require("os");
108
114
  var import_node_path3 = require("path");
109
115
  var import_core = require("@mcmcjs/core");
110
116
 
111
117
  // src/runner-common.ts
112
118
  var import_node_crypto = require("crypto");
119
+ var import_node_os2 = require("os");
113
120
  var import_node_path2 = require("path");
114
121
  var import_node_url = require("url");
122
+ function sharedTmpParent() {
123
+ const uid = typeof process.getuid === "function" ? `-${process.getuid()}` : "";
124
+ return (0, import_node_path2.join)((0, import_node_os2.tmpdir)(), `mcmcjs${uid}`);
125
+ }
115
126
  function driverPath() {
116
127
  return (0, import_node_path2.join)((0, import_node_path2.dirname)((0, import_node_url.fileURLToPath)(importMetaUrl)), "driver.jl");
117
128
  }
129
+ function workerPath() {
130
+ return (0, import_node_path2.join)((0, import_node_path2.dirname)((0, import_node_url.fileURLToPath)(importMetaUrl)), "worker.jl");
131
+ }
118
132
  function sha256(text) {
119
133
  return (0, import_node_crypto.createHash)("sha256").update(text).digest("hex");
120
134
  }
@@ -136,45 +150,40 @@ var STAGES = /* @__PURE__ */ new Set([
136
150
  "load_samples",
137
151
  "predict",
138
152
  "write",
139
- "spawn"
153
+ "spawn",
154
+ "worker"
140
155
  ]);
141
156
  function toStage(value) {
142
157
  return typeof value === "string" && STAGES.has(value) ? value : void 0;
143
158
  }
144
159
 
145
160
  // src/fit.ts
146
- async function runFit(spec, resolved, io) {
147
- const startedAt = (/* @__PURE__ */ new Date()).toISOString();
148
- const start = performance.now();
161
+ function makeRequestDir(kind) {
162
+ const parent = sharedTmpParent();
163
+ (0, import_node_fs.mkdirSync)(parent, { recursive: true });
164
+ return (0, import_node_fs.mkdtempSync)((0, import_node_path3.join)(parent, `${kind}-`));
165
+ }
166
+ function fitRequest(spec, outPath) {
167
+ return {
168
+ schema_version: spec.schema_version,
169
+ backend: { id: spec.backend.id },
170
+ model: { file: spec.modelPath, entry: spec.model.entry },
171
+ data: spec.data,
172
+ sampler: spec.sampler,
173
+ seed: spec.seed,
174
+ out: outPath
175
+ };
176
+ }
177
+ function finalizeOkFit(spec, resolved, io, provenance, startedAt, elapsedMs) {
149
178
  const runtimeRequested = spec.backend.version;
150
- const tmp = io.tmpDir ?? (0, import_node_fs.mkdtempSync)((0, import_node_path3.join)((0, import_node_os2.tmpdir)(), "mcmcjs-fit-"));
151
- const requestPath = (0, import_node_path3.join)(tmp, "request.json");
152
- (0, import_node_fs.writeFileSync)(
153
- requestPath,
154
- JSON.stringify({
155
- schema_version: spec.schema_version,
156
- backend: { id: spec.backend.id },
157
- model: { file: spec.modelPath, entry: spec.model.entry },
158
- data: spec.data,
159
- sampler: spec.sampler,
160
- seed: spec.seed,
161
- out: io.outPath
162
- })
163
- );
164
- const args = [
165
- ...resolved.args,
166
- "--startup-file=no",
167
- `--project=${io.projectDir}`,
168
- driverPath(),
169
- requestPath
170
- ];
171
- const { stdout, stderr, code } = await io.spawn(resolved.command, args);
172
- const elapsedMs = Math.round(performance.now() - start);
173
- if (code !== 0 || !(0, import_node_fs.existsSync)(io.outPath)) {
174
- const failure = lastJsonLine(stderr);
175
- const stage = toStage(failure?.stage);
176
- const error = failure?.error ?? stderr.trim().split("\n").pop() ?? `julia exited with code ${code}`;
177
- return { status: "error", runtimeRequested, elapsedMs, stage, error };
179
+ if (!(0, import_node_fs.existsSync)(io.outPath)) {
180
+ return {
181
+ status: "error",
182
+ runtimeRequested,
183
+ elapsedMs,
184
+ stage: "write",
185
+ error: "the runtime reported success but wrote no samples file"
186
+ };
178
187
  }
179
188
  try {
180
189
  (0, import_core.parseSamples)((0, import_node_fs.readFileSync)(io.outPath, "utf8"));
@@ -187,7 +196,6 @@ async function runFit(spec, resolved, io) {
187
196
  error: `samples file did not parse: ${error.message}`
188
197
  };
189
198
  }
190
- const provenance = lastJsonLine(stdout);
191
199
  const julia = provenance?.julia_version;
192
200
  const record = {
193
201
  schema_version: import_core.RUN_RECORD_SCHEMA_VERSION,
@@ -198,12 +206,13 @@ async function runFit(spec, resolved, io) {
198
206
  manifest_sha256: provenance?.manifest_sha256,
199
207
  packages: provenance?.packages,
200
208
  model_sha256: (0, import_node_fs.existsSync)(spec.modelPath) ? sha256((0, import_node_fs.readFileSync)(spec.modelPath, "utf8")) : void 0,
201
- data_sha256: sha256((0, import_core.canonicalJson)(spec.data)),
209
+ data_sha256: io.dataSha256 ?? sha256((0, import_core.canonicalJson)(spec.data)),
210
+ ...io.dataFile ? { data_file: io.dataFile } : {},
202
211
  samples_file: io.outPath,
203
212
  started_at: startedAt,
204
213
  elapsed_ms: elapsedMs
205
214
  };
206
- (0, import_node_fs.writeFileSync)(`${io.outPath}.run.json`, `${JSON.stringify(record, null, 2)}
215
+ (0, import_node_fs.writeFileSync)(io.recordPath ?? `${io.outPath}.run.json`, `${JSON.stringify(record, null, 2)}
207
216
  `);
208
217
  return {
209
218
  status: "ok",
@@ -213,10 +222,43 @@ async function runFit(spec, resolved, io) {
213
222
  elapsedMs
214
223
  };
215
224
  }
225
+ async function runFit(spec, resolved, io) {
226
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
227
+ const start = performance.now();
228
+ const runtimeRequested = spec.backend.version;
229
+ const ownTmp = io.tmpDir === void 0;
230
+ const tmp = io.tmpDir ?? makeRequestDir("fit");
231
+ const requestPath = (0, import_node_path3.join)(tmp, "request.json");
232
+ (0, import_node_fs.writeFileSync)(requestPath, JSON.stringify(fitRequest(spec, io.outPath)));
233
+ const args = [
234
+ ...resolved.args,
235
+ "--startup-file=no",
236
+ `--project=${io.projectDir}`,
237
+ driverPath(),
238
+ requestPath
239
+ ];
240
+ let stdout;
241
+ let stderr;
242
+ let code;
243
+ try {
244
+ ({ stdout, stderr, code } = await io.spawn(resolved.command, args, {
245
+ onProgress: io.onProgress
246
+ }));
247
+ } finally {
248
+ if (ownTmp) (0, import_node_fs.rmSync)(tmp, { recursive: true, force: true });
249
+ }
250
+ const elapsedMs = Math.round(performance.now() - start);
251
+ if (code !== 0 || !(0, import_node_fs.existsSync)(io.outPath)) {
252
+ const failure = lastJsonLine(stderr);
253
+ const stage = toStage(failure?.stage);
254
+ const error = failure?.error ?? stderr.trim().split("\n").pop() ?? `julia exited with code ${code}`;
255
+ return { status: "error", runtimeRequested, elapsedMs, stage, error };
256
+ }
257
+ return finalizeOkFit(spec, resolved, io, lastJsonLine(stdout), startedAt, elapsedMs);
258
+ }
216
259
 
217
260
  // src/predict.ts
218
261
  var import_node_fs2 = require("fs");
219
- var import_node_os3 = require("os");
220
262
  var import_node_path4 = require("path");
221
263
  var import_core2 = require("@mcmcjs/core");
222
264
  function predictData(spec) {
@@ -234,7 +276,8 @@ async function runPredict(spec, resolved, io) {
234
276
  const start = performance.now();
235
277
  const runtimeRequested = spec.backend.version;
236
278
  const data = predictData(spec);
237
- const tmp = io.tmpDir ?? (0, import_node_fs2.mkdtempSync)((0, import_node_path4.join)((0, import_node_os3.tmpdir)(), "mcmcjs-predict-"));
279
+ const ownTmp = io.tmpDir === void 0;
280
+ const tmp = io.tmpDir ?? makeRequestDir("predict");
238
281
  const requestPath = (0, import_node_path4.join)(tmp, "request.json");
239
282
  (0, import_node_fs2.writeFileSync)(
240
283
  requestPath,
@@ -257,7 +300,14 @@ async function runPredict(spec, resolved, io) {
257
300
  driverPath(),
258
301
  requestPath
259
302
  ];
260
- const { stdout, stderr, code } = await io.spawn(resolved.command, args);
303
+ let stdout;
304
+ let stderr;
305
+ let code;
306
+ try {
307
+ ({ stdout, stderr, code } = await io.spawn(resolved.command, args));
308
+ } finally {
309
+ if (ownTmp) (0, import_node_fs2.rmSync)(tmp, { recursive: true, force: true });
310
+ }
261
311
  const elapsedMs = Math.round(performance.now() - start);
262
312
  if (code !== 0 || !(0, import_node_fs2.existsSync)(io.outPath)) {
263
313
  const failure = lastJsonLine(stderr);
@@ -306,8 +356,9 @@ async function runPredict(spec, resolved, io) {
306
356
 
307
357
  // src/project.ts
308
358
  var import_node_fs3 = require("fs");
309
- var import_node_os4 = require("os");
359
+ var import_node_os3 = require("os");
310
360
  var import_node_path5 = require("path");
361
+ var import_core3 = require("@mcmcjs/core");
311
362
  var import_engine3 = require("@mcmcjs/engine");
312
363
  var PACKAGES = [
313
364
  "Turing",
@@ -320,29 +371,65 @@ var PACKAGES = [
320
371
  "JSON",
321
372
  "StableRNGs"
322
373
  ];
323
- function managedProjectDir() {
324
- const base = process.env.XDG_DATA_HOME || (0, import_node_path5.join)((0, import_node_os4.homedir)(), ".local", "share");
325
- return (0, import_node_path5.join)(base, "mcmcjs", "julia", "env");
374
+ var VERSION_RE = /^[0-9A-Za-z.+~^=<>,\s-]+$/;
375
+ function validateVersionString(name, version) {
376
+ if (!VERSION_RE.test(version)) {
377
+ throw new Error(
378
+ `invalid version "${version}" for package ${name}; a version may contain only digits, letters, and . + ~ ^ = < > , - and spaces`
379
+ );
380
+ }
381
+ }
382
+ function validatePins(pins) {
383
+ for (const [name, version] of Object.entries(pins ?? {})) {
384
+ if (!PACKAGES.includes(name)) {
385
+ throw new Error(
386
+ `cannot pin unknown package "${name}"; managed packages are: ${PACKAGES.join(", ")}`
387
+ );
388
+ }
389
+ validateVersionString(name, version);
390
+ }
391
+ }
392
+ function hasPins(pins) {
393
+ return pins !== void 0 && Object.keys(pins).length > 0;
394
+ }
395
+ function managedProjectDir(version, pins) {
396
+ const base = process.env.XDG_DATA_HOME || (0, import_node_path5.join)((0, import_node_os3.homedir)(), ".local", "share");
397
+ const root = (0, import_node_path5.join)(base, "mcmcjs", "julia", "env");
398
+ if (!version && !hasPins(pins)) return root;
399
+ let leaf = (version ?? "default").replace(/[^A-Za-z0-9._-]/g, "_");
400
+ if (hasPins(pins)) leaf += `-${sha256((0, import_core3.canonicalJson)(pins)).slice(0, 8)}`;
401
+ return (0, import_node_path5.join)(root, leaf);
326
402
  }
327
403
  function sentinelPath(dir) {
328
404
  return (0, import_node_path5.join)(dir, ".mcmcjs-packages.json");
329
405
  }
330
- function provisioned(dir) {
406
+ function expectedSentinel(pins) {
407
+ return (0, import_core3.canonicalJson)({ packages: [...PACKAGES].sort(), pins: pins ?? {} });
408
+ }
409
+ function provisioned(dir, pins) {
331
410
  try {
332
- return (0, import_node_fs3.readFileSync)(sentinelPath(dir), "utf8").trim() === JSON.stringify([...PACKAGES].sort());
411
+ return (0, import_node_fs3.readFileSync)(sentinelPath(dir), "utf8").trim() === expectedSentinel(pins);
333
412
  } catch {
334
413
  return false;
335
414
  }
336
415
  }
337
- function managedProjectReady(dir = managedProjectDir()) {
338
- return (0, import_node_fs3.existsSync)((0, import_node_path5.join)(dir, "Project.toml")) && provisioned(dir);
416
+ function managedProjectReady(dir = managedProjectDir(), pins) {
417
+ return (0, import_node_fs3.existsSync)((0, import_node_path5.join)(dir, "Project.toml")) && provisioned(dir, pins);
339
418
  }
340
- async function ensureProject(juliaBin, run = (0, import_engine3.createRunner)(30 * 6e4), dir = managedProjectDir()) {
341
- if (managedProjectReady(dir)) return dir;
419
+ function packageSpecsCode(pins) {
420
+ const specs = PACKAGES.map((name) => {
421
+ const version = pins?.[name];
422
+ return version ? `Pkg.PackageSpec(name=${JSON.stringify(name)}, version=${JSON.stringify(version)})` : `Pkg.PackageSpec(name=${JSON.stringify(name)})`;
423
+ });
424
+ return `[${specs.join(", ")}]`;
425
+ }
426
+ async function ensureProject(juliaBin, run = (0, import_engine3.createRunner)(30 * 6e4), dir = managedProjectDir(), pins) {
427
+ validatePins(pins);
428
+ if (managedProjectReady(dir, pins)) return dir;
342
429
  (0, import_node_fs3.mkdirSync)(dir, { recursive: true });
343
- const code = `using Pkg; Pkg.add(${JSON.stringify(PACKAGES)}); Pkg.precompile()`;
430
+ const code = `using Pkg; Pkg.add(${packageSpecsCode(pins)}); Pkg.precompile()`;
344
431
  await run(juliaBin, ["--startup-file=no", `--project=${dir}`, "-e", code]);
345
- (0, import_node_fs3.writeFileSync)(sentinelPath(dir), JSON.stringify([...PACKAGES].sort()));
432
+ (0, import_node_fs3.writeFileSync)(sentinelPath(dir), expectedSentinel(pins));
346
433
  return dir;
347
434
  }
348
435
 
@@ -354,11 +441,14 @@ async function runMatrix(spec, versions, io) {
354
441
  let entry;
355
442
  try {
356
443
  const resolved = await io.resolve(version);
444
+ const projectDir = await io.ensure(resolved);
357
445
  const versioned = { ...spec, backend: { ...spec.backend, version } };
358
446
  const result = await runFit(versioned, resolved, {
359
447
  spawn: io.spawn,
360
- projectDir: io.projectDir,
361
- outPath: (0, import_node_path6.join)(io.outDir, `${version}.samples.json`)
448
+ projectDir,
449
+ outPath: (0, import_node_path6.join)(io.outDir, `${version}.samples.json`),
450
+ dataFile: io.dataFile,
451
+ dataSha256: io.dataSha256
362
452
  });
363
453
  entry = {
364
454
  version,
@@ -507,7 +597,7 @@ async function resolveVersion(bin, channel, run) {
507
597
  `Julia version "${channel}" is not installed. Add it with: mcmc julia version add ${channel}`
508
598
  );
509
599
  }
510
- return { command: match.File, args: match.Args ?? [] };
600
+ return { command: match.File, args: match.Args ?? [], version: match.Version };
511
601
  }
512
602
  function assertVersionsInstalled(channels, installed) {
513
603
  const have = new Set(installed.map((v) => v.id));
@@ -519,6 +609,256 @@ function assertVersionsInstalled(channels, installed) {
519
609
  );
520
610
  }
521
611
  }
612
+
613
+ // src/worker.ts
614
+ var import_node_child_process = require("child_process");
615
+ var import_node_fs4 = require("fs");
616
+ var import_node_net = require("net");
617
+ var import_node_path7 = require("path");
618
+ var import_engine4 = require("@mcmcjs/engine");
619
+ var START_WAIT_MS = 18e4;
620
+ var CONNECT_TIMEOUT_MS = 1e3;
621
+ var STOP_ACK_TIMEOUT_MS = 3e3;
622
+ function fitInactivityTimeoutMs() {
623
+ const fromEnv = Number(process.env.MCMC_WORKER_TIMEOUT_MS);
624
+ return Number.isFinite(fromEnv) && fromEnv > 0 ? fromEnv : 30 * 6e4;
625
+ }
626
+ var WorkerTimeoutError = class extends Error {
627
+ };
628
+ function workersDir() {
629
+ const runtime = process.env.XDG_RUNTIME_DIR;
630
+ const dir = runtime ? (0, import_node_path7.join)(runtime, "mcmcjs") : (0, import_node_path7.join)(sharedTmpParent(), "workers");
631
+ (0, import_node_fs4.mkdirSync)(dir, { recursive: true, mode: 448 });
632
+ return dir;
633
+ }
634
+ function workerSocketPath(command, projectDir) {
635
+ return (0, import_node_path7.join)(workersDir(), `worker-${sha256(`${command}
636
+ ${projectDir}`).slice(0, 12)}.sock`);
637
+ }
638
+ function connectOnce(path) {
639
+ return new Promise((resolve, reject) => {
640
+ const sock = (0, import_node_net.createConnection)(path);
641
+ const timer = setTimeout(() => {
642
+ sock.destroy();
643
+ reject(new Error("connect timeout"));
644
+ }, CONNECT_TIMEOUT_MS);
645
+ sock.once("connect", () => {
646
+ clearTimeout(timer);
647
+ resolve(sock);
648
+ });
649
+ sock.once("error", (error) => {
650
+ clearTimeout(timer);
651
+ reject(error);
652
+ });
653
+ });
654
+ }
655
+ var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
656
+ function acquireStartLock(socketPath) {
657
+ const lock = `${socketPath}.start`;
658
+ try {
659
+ (0, import_node_fs4.mkdirSync)(lock);
660
+ return true;
661
+ } catch {
662
+ try {
663
+ if (Date.now() - (0, import_node_fs4.statSync)(lock).mtimeMs > START_WAIT_MS) {
664
+ (0, import_node_fs4.rmdirSync)(lock);
665
+ (0, import_node_fs4.mkdirSync)(lock);
666
+ return true;
667
+ }
668
+ } catch {
669
+ }
670
+ return false;
671
+ }
672
+ }
673
+ function releaseStartLock(socketPath) {
674
+ try {
675
+ (0, import_node_fs4.rmdirSync)(`${socketPath}.start`);
676
+ } catch {
677
+ }
678
+ }
679
+ async function ensureWorker(resolved, projectDir, notify) {
680
+ const path = workerSocketPath(resolved.command, projectDir);
681
+ try {
682
+ return await connectOnce(path);
683
+ } catch {
684
+ }
685
+ let spawnFailure;
686
+ const startedHere = acquireStartLock(path);
687
+ if (startedHere) {
688
+ try {
689
+ (0, import_node_fs4.rmSync)(path, { force: true });
690
+ notify?.("starting the Julia worker (first use loads Turing, can take a minute)...");
691
+ const child = (0, import_node_child_process.spawn)(
692
+ resolved.command,
693
+ [...resolved.args, "--startup-file=no", `--project=${projectDir}`, workerPath(), path],
694
+ { detached: true, stdio: "ignore" }
695
+ );
696
+ child.on("error", (error) => {
697
+ spawnFailure = error;
698
+ });
699
+ child.on("exit", (code) => {
700
+ if (code !== 0 && code !== null) {
701
+ spawnFailure = new Error(`the worker exited with code ${code} before serving`);
702
+ }
703
+ });
704
+ child.unref();
705
+ } catch (error) {
706
+ releaseStartLock(path);
707
+ throw error;
708
+ }
709
+ }
710
+ try {
711
+ const deadline = Date.now() + START_WAIT_MS;
712
+ for (; ; ) {
713
+ if (spawnFailure) throw spawnFailure;
714
+ try {
715
+ return await connectOnce(path);
716
+ } catch {
717
+ }
718
+ if (Date.now() > deadline) throw new Error("the Julia worker did not start in time");
719
+ await delay(250);
720
+ }
721
+ } finally {
722
+ if (startedHere) releaseStartLock(path);
723
+ }
724
+ }
725
+ function readWorkerResponse(sock, onProgress, inactivityMs = fitInactivityTimeoutMs()) {
726
+ return new Promise((resolve, reject) => {
727
+ let pending = "";
728
+ let settled = false;
729
+ const settle = (fn) => {
730
+ if (settled) return;
731
+ settled = true;
732
+ sock.setTimeout(0);
733
+ fn();
734
+ };
735
+ sock.setTimeout(inactivityMs, () => {
736
+ const error = new WorkerTimeoutError(
737
+ `the worker sent nothing for ${Math.round(inactivityMs / 6e4)} minutes`
738
+ );
739
+ settle(() => {
740
+ sock.destroy();
741
+ reject(error);
742
+ });
743
+ });
744
+ sock.on("data", (chunk) => {
745
+ pending += chunk.toString("utf8");
746
+ for (; ; ) {
747
+ const at = pending.indexOf("\n");
748
+ if (at === -1) break;
749
+ const line = pending.slice(0, at).trim();
750
+ pending = pending.slice(at + 1);
751
+ if (!line) continue;
752
+ const progress = (0, import_engine4.parseProgressLine)(line);
753
+ if (progress) {
754
+ onProgress?.(progress);
755
+ continue;
756
+ }
757
+ try {
758
+ const doc = JSON.parse(line);
759
+ settle(() => {
760
+ sock.end();
761
+ resolve(doc);
762
+ });
763
+ return;
764
+ } catch {
765
+ }
766
+ }
767
+ });
768
+ sock.on("error", (error) => settle(() => reject(error)));
769
+ sock.on("close", () => settle(() => reject(new Error("worker closed the connection"))));
770
+ });
771
+ }
772
+ async function runFitViaWorker(spec, resolved, io) {
773
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
774
+ const start = performance.now();
775
+ const sock = await ensureWorker(resolved, io.projectDir, io.notify);
776
+ sock.write(`${JSON.stringify(fitRequest(spec, io.outPath))}
777
+ `);
778
+ const final = await readWorkerResponse(sock, io.onProgress);
779
+ const elapsedMs = Math.round(performance.now() - start);
780
+ if (final.ok !== true) {
781
+ if (final.stage === "worker") throw new Error(String(final.error ?? "worker error"));
782
+ return {
783
+ status: "error",
784
+ runtimeRequested: spec.backend.version,
785
+ elapsedMs,
786
+ stage: toStage(final.stage),
787
+ error: String(final.error ?? "worker error")
788
+ };
789
+ }
790
+ return finalizeOkFit(
791
+ spec,
792
+ resolved,
793
+ io,
794
+ final.provenance,
795
+ startedAt,
796
+ elapsedMs
797
+ );
798
+ }
799
+ async function runFitAuto(spec, resolved, io) {
800
+ if (io.daemon && process.platform !== "win32") {
801
+ const start = performance.now();
802
+ try {
803
+ return await runFitViaWorker(spec, resolved, io);
804
+ } catch (error) {
805
+ if (error instanceof WorkerTimeoutError) {
806
+ return {
807
+ status: "error",
808
+ runtimeRequested: spec.backend.version,
809
+ elapsedMs: Math.round(performance.now() - start),
810
+ stage: "worker",
811
+ error: `${error.message}; the worker may be wedged, try mcmc daemon stop`
812
+ };
813
+ }
814
+ io.notify?.(
815
+ `worker unavailable (${error.message}); falling back to a one-shot run`
816
+ );
817
+ }
818
+ }
819
+ return runFit(spec, resolved, io);
820
+ }
821
+ async function listWorkers() {
822
+ const dir = workersDir();
823
+ const sockets = (0, import_node_fs4.readdirSync)(dir).filter((f) => f.startsWith("worker-") && f.endsWith(".sock"));
824
+ const statuses = [];
825
+ for (const name of sockets) {
826
+ const socket = (0, import_node_path7.join)(dir, name);
827
+ let alive = false;
828
+ try {
829
+ const sock = await connectOnce(socket);
830
+ sock.destroy();
831
+ alive = true;
832
+ } catch {
833
+ }
834
+ statuses.push({ socket, alive });
835
+ }
836
+ return statuses;
837
+ }
838
+ async function stopWorker(socket) {
839
+ let sock;
840
+ try {
841
+ sock = await connectOnce(socket);
842
+ } catch {
843
+ (0, import_node_fs4.rmSync)(socket, { force: true });
844
+ return "stale";
845
+ }
846
+ const acked = await new Promise((resolve) => {
847
+ const timer = setTimeout(() => resolve(false), STOP_ACK_TIMEOUT_MS);
848
+ const finish = (ok) => {
849
+ clearTimeout(timer);
850
+ resolve(ok);
851
+ };
852
+ sock.resume();
853
+ sock.on("close", () => finish(true));
854
+ sock.on("error", () => finish(false));
855
+ sock.write('{"mcmcjs":"stop"}\n');
856
+ });
857
+ sock.destroy();
858
+ if (!acked) return "pending";
859
+ (0, import_node_fs4.rmSync)(socket, { force: true });
860
+ return "stopped";
861
+ }
522
862
  // Annotate the CommonJS export names for ESM import in node:
523
863
  0 && (module.exports = {
524
864
  addVersion,
@@ -530,6 +870,7 @@ function assertVersionsInstalled(channels, installed) {
530
870
  juliaEngine,
531
871
  juliaupInstallCommand,
532
872
  listVersions,
873
+ listWorkers,
533
874
  managedProjectDir,
534
875
  managedProjectReady,
535
876
  planSetup,
@@ -538,10 +879,15 @@ function assertVersionsInstalled(channels, installed) {
538
879
  resolveVersion,
539
880
  runDoctor,
540
881
  runFit,
882
+ runFitAuto,
541
883
  runMatrix,
542
884
  runPredict,
543
885
  runSetup,
544
886
  setDefaultVersion,
545
- updateVersion
887
+ sharedTmpParent,
888
+ stopWorker,
889
+ updateVersion,
890
+ validatePins,
891
+ validateVersionString
546
892
  });
547
893
  //# sourceMappingURL=index.cjs.map