dahrk-node 0.1.17 → 0.1.19

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 (2) hide show
  1. package/dist/main.js +188 -25
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -2740,12 +2740,30 @@ function tokenise(cmd) {
2740
2740
  let quoted = false;
2741
2741
  let started = false;
2742
2742
  let quote = null;
2743
+ const pendingHeredocs = [];
2743
2744
  const push = () => {
2744
2745
  if (started) tokens.push({ value: cur, quoted });
2745
2746
  cur = "";
2746
2747
  quoted = false;
2747
2748
  started = false;
2748
2749
  };
2750
+ const consumeHeredocs = (s) => {
2751
+ let p = s;
2752
+ for (; ; ) {
2753
+ let lineEnd = p;
2754
+ while (lineEnd < cmd.length && cmd[lineEnd] !== "\n") lineEnd++;
2755
+ const line = cmd.slice(p, lineEnd);
2756
+ const { delim, dash } = pendingHeredocs[0];
2757
+ const terminator = dash ? line.replace(/^\t+/, "") : line;
2758
+ const atEnd = lineEnd >= cmd.length;
2759
+ if (terminator === delim) {
2760
+ pendingHeredocs.shift();
2761
+ if (pendingHeredocs.length === 0) return atEnd ? lineEnd : lineEnd + 1;
2762
+ }
2763
+ if (atEnd) return -1;
2764
+ p = lineEnd + 1;
2765
+ }
2766
+ };
2749
2767
  for (let i = 0; i < cmd.length; i++) {
2750
2768
  const c = cmd[i];
2751
2769
  if (quote) {
@@ -2793,6 +2811,13 @@ function tokenise(cmd) {
2793
2811
  i = j;
2794
2812
  continue;
2795
2813
  }
2814
+ if (c === "\n" && pendingHeredocs.length > 0) {
2815
+ push();
2816
+ const resume = consumeHeredocs(i + 1);
2817
+ if (resume < 0) return null;
2818
+ i = resume - 1;
2819
+ continue;
2820
+ }
2796
2821
  if (/\s/.test(c)) {
2797
2822
  push();
2798
2823
  continue;
@@ -2805,6 +2830,38 @@ function tokenise(cmd) {
2805
2830
  i += 2;
2806
2831
  continue;
2807
2832
  }
2833
+ if (two === "<<" && cmd[i + 2] !== "<") {
2834
+ push();
2835
+ let k = i + 2;
2836
+ const dash = cmd[k] === "-";
2837
+ if (dash) k++;
2838
+ while (k < cmd.length && (cmd[k] === " " || cmd[k] === " ")) k++;
2839
+ let delim = "";
2840
+ let dq = null;
2841
+ for (; k < cmd.length; k++) {
2842
+ const d = cmd[k];
2843
+ if (dq) {
2844
+ if (d === dq) dq = null;
2845
+ else delim += d;
2846
+ continue;
2847
+ }
2848
+ if (d === '"' || d === "'") {
2849
+ dq = d;
2850
+ continue;
2851
+ }
2852
+ if (d === "\\" && k + 1 < cmd.length) {
2853
+ delim += cmd[++k];
2854
+ continue;
2855
+ }
2856
+ if (/[\s;&|<>]/.test(d)) break;
2857
+ delim += d;
2858
+ }
2859
+ if (dq) return null;
2860
+ if (!delim) return null;
2861
+ pendingHeredocs.push({ delim, dash });
2862
+ i = k - 1;
2863
+ continue;
2864
+ }
2808
2865
  if (["&&", "||", ">>", "2>", "&>"].includes(two)) {
2809
2866
  push();
2810
2867
  tokens.push({ value: two, quoted: false, operator: two });
@@ -2820,6 +2877,7 @@ function tokenise(cmd) {
2820
2877
  started = true;
2821
2878
  }
2822
2879
  if (quote) return null;
2880
+ if (pendingHeredocs.length > 0) return null;
2823
2881
  push();
2824
2882
  return { tokens, subshells };
2825
2883
  }
@@ -2949,7 +3007,7 @@ function scanCommand(cmd, roots, cwd = roots.cwd) {
2949
3007
  }
2950
3008
  if (out2.kind !== "ok") return out2;
2951
3009
  }
2952
- return { kind: "ok" };
3010
+ return { kind: "ok", cwd: cwdNow };
2953
3011
  }
2954
3012
 
2955
3013
  // ../../packages/edge/src/builtins.ts
@@ -3028,6 +3086,7 @@ function pathsIn(input) {
3028
3086
  return out2;
3029
3087
  }
3030
3088
  function fsConfineRule(roots) {
3089
+ let sessionCwd = roots.cwd;
3031
3090
  return {
3032
3091
  name: "fs_confine",
3033
3092
  evaluate(event) {
@@ -3038,8 +3097,12 @@ function fsConfineRule(roots) {
3038
3097
  reason: `${need2 === "write" ? "write to" : "read of"} "${path}" is outside the run's worktree`
3039
3098
  });
3040
3099
  if (SHELL_TOOLS.has(event.tool)) {
3041
- const result = scanCommand(commandOf(event.input), roots);
3100
+ const result = scanCommand(commandOf(event.input), roots, sessionCwd);
3042
3101
  if (result.kind === "escape") return deny(result.path, result.need);
3102
+ if (result.kind === "ok") {
3103
+ if (result.cwd) sessionCwd = result.cwd;
3104
+ return null;
3105
+ }
3043
3106
  if (result.kind === "unparseable") {
3044
3107
  return {
3045
3108
  verdict: "deny",
@@ -3884,6 +3947,9 @@ function createStageRunner(deps) {
3884
3947
 
3885
3948
  // ../../packages/edge/src/ws-client.ts
3886
3949
  var ENROLMENT_REJECTED_EXIT_CODE = 78;
3950
+ var RECONNECT_BASE_MS = 500;
3951
+ var RECONNECT_MAX_MS = 3e4;
3952
+ var PARK_POLL_MS = 6e4;
3887
3953
  function classifyError(e) {
3888
3954
  const msg = e instanceof Error ? e.message : String(e);
3889
3955
  if (/\bgit\b|worktree|clone|fetch|branch|checkout|remote/i.test(msg)) return "git";
@@ -3919,7 +3985,11 @@ async function startEdgeNode(opts) {
3919
3985
  let lastPongAt = 0;
3920
3986
  let shuttingDown = false;
3921
3987
  let connectCount = 0;
3988
+ let enrolToken = opts.enrolToken;
3989
+ let reconnectAttempts = 0;
3990
+ let parkTimer;
3922
3991
  let onFatal;
3992
+ let onParked;
3923
3993
  const send = (msg) => {
3924
3994
  if (ws && ws.readyState === WebSocket.OPEN) ws.send(encode(msg));
3925
3995
  };
@@ -4042,7 +4112,7 @@ async function startEdgeNode(opts) {
4042
4112
  const sendHello = () => {
4043
4113
  send({
4044
4114
  type: "hello",
4045
- enrolToken: opts.enrolToken ?? "",
4115
+ enrolToken: enrolToken ?? "",
4046
4116
  detectedRuntimes: currentRuntimes,
4047
4117
  servesRepoIds: opts.servesRepoIds ?? [],
4048
4118
  ...opts.credentialModeExplicit && opts.credentialMode ? { credentialMode: opts.credentialMode } : {},
@@ -4102,6 +4172,7 @@ async function startEdgeNode(opts) {
4102
4172
  const msg = decode(raw);
4103
4173
  if (msg.type === "welcome") {
4104
4174
  stageDeps.tenantId = msg.tenantId;
4175
+ reconnectAttempts = 0;
4105
4176
  if (opts.retention === void 0 && msg.retention) stageDeps.retention = msg.retention;
4106
4177
  if (opts.heartbeatMs === void 0 && msg.heartbeatMs > 0 && ws) {
4107
4178
  startHeartbeat(ws, msg.heartbeatMs);
@@ -4286,19 +4357,59 @@ async function startEdgeNode(opts) {
4286
4357
  if (heartbeat) clearInterval(heartbeat);
4287
4358
  heartbeat = void 0;
4288
4359
  if (isEnrolmentRejection(code)) {
4289
- shuttingDown = true;
4290
4360
  const detail = reason.toString() || "enrolment rejected";
4291
- log.error({ closeCode: code, detail, fatal: true }, `EDGE_REJECTED:${code} ${detail}`);
4292
- process.exitCode = ENROLMENT_REJECTED_EXIT_CODE;
4293
- onFatal?.(new Error(`hub rejected edge enrolment (${code}): ${detail}`));
4361
+ const refresh = opts.refreshEnrolToken;
4362
+ log.error({ closeCode: code, detail, fatal: !refresh }, `EDGE_REJECTED:${code} ${detail}`);
4363
+ if (!refresh) {
4364
+ shuttingDown = true;
4365
+ process.exitCode = ENROLMENT_REJECTED_EXIT_CODE;
4366
+ onFatal?.(new Error(`hub rejected edge enrolment (${code}): ${detail}`));
4367
+ return;
4368
+ }
4369
+ park(refresh);
4294
4370
  return;
4295
4371
  }
4296
4372
  if (!shuttingDown) {
4297
- log.warn({ closeCode: code, reason: reason.toString(), connectCount }, `EDGE_DISCONNECTED:${code} reconnecting in 500ms`);
4298
- reconnectTimer = setTimeout(connect, 500);
4373
+ const delay = reconnectDelay();
4374
+ log.warn(
4375
+ { closeCode: code, reason: reason.toString(), connectCount, delayMs: delay, attempt: reconnectAttempts },
4376
+ `EDGE_DISCONNECTED:${code} reconnecting in ${delay}ms`
4377
+ );
4378
+ reconnectTimer = setTimeout(connect, delay);
4299
4379
  }
4300
4380
  });
4301
4381
  };
4382
+ const reconnectDelay = () => {
4383
+ const ceiling = Math.min(RECONNECT_BASE_MS * 2 ** reconnectAttempts, RECONNECT_MAX_MS);
4384
+ reconnectAttempts++;
4385
+ return Math.round(ceiling / 2 + Math.random() * (ceiling / 2));
4386
+ };
4387
+ const park = (refresh) => {
4388
+ const rejected = enrolToken;
4389
+ if (parkTimer) return;
4390
+ const pollMs = opts.parkPollMs ?? PARK_POLL_MS;
4391
+ log.error(
4392
+ { pollMs },
4393
+ "EDGE_PARKED:enrolment rejected - the node is up but serving nothing. Re-enrol with `dahrk start --token <token>` (get one at https://app.dahrk.ai); it will reconnect on its own."
4394
+ );
4395
+ parkTimer = setInterval(() => {
4396
+ let fresh;
4397
+ try {
4398
+ fresh = refresh();
4399
+ } catch (e) {
4400
+ log.warn({ err: e }, `EDGE_PARK_REFRESH_FAILED ${e.message}`);
4401
+ return;
4402
+ }
4403
+ if (!fresh || fresh === rejected) return;
4404
+ if (parkTimer) clearInterval(parkTimer);
4405
+ parkTimer = void 0;
4406
+ enrolToken = fresh;
4407
+ reconnectAttempts = 0;
4408
+ log.info({}, "EDGE_UNPARKED:a new enrolment token was found, reconnecting");
4409
+ connect();
4410
+ }, pollMs);
4411
+ onParked?.();
4412
+ };
4302
4413
  opts.signal?.addEventListener(
4303
4414
  "abort",
4304
4415
  () => {
@@ -4309,6 +4420,8 @@ async function startEdgeNode(opts) {
4309
4420
  reconnectTimer = void 0;
4310
4421
  if (reprobeTimer) clearInterval(reprobeTimer);
4311
4422
  reprobeTimer = void 0;
4423
+ if (parkTimer) clearInterval(parkTimer);
4424
+ parkTimer = void 0;
4312
4425
  ws?.close(1e3, "shutting down");
4313
4426
  },
4314
4427
  { once: true }
@@ -4325,6 +4438,10 @@ async function startEdgeNode(opts) {
4325
4438
  clearInterval(t);
4326
4439
  reject(err);
4327
4440
  };
4441
+ onParked = () => {
4442
+ clearInterval(t);
4443
+ resolve3();
4444
+ };
4328
4445
  });
4329
4446
  }
4330
4447
 
@@ -5737,6 +5854,7 @@ function persistEnrolment(env, enrolment) {
5737
5854
  });
5738
5855
  }
5739
5856
  function resolveEnrolToken(env, opts = {}) {
5857
+ if (opts.supervised && !opts.ephemeral) return readPersistedToken(env) ?? env.DAHRK_ENROL_TOKEN;
5740
5858
  if (env.DAHRK_ENROL_TOKEN) return env.DAHRK_ENROL_TOKEN;
5741
5859
  if (opts.ephemeral) return void 0;
5742
5860
  return readPersistedToken(env);
@@ -5756,7 +5874,6 @@ function serviceArgv(inputs) {
5756
5874
  }
5757
5875
  function serviceEnv(inputs) {
5758
5876
  return {
5759
- DAHRK_ENROL_TOKEN: inputs.token,
5760
5877
  DAHRK_SUPERVISED: "1",
5761
5878
  ...inputs.hubUrl ? { DAHRK_HUB_URL: inputs.hubUrl } : {},
5762
5879
  ...inputs.name ? { DAHRK_NODE_NAME: inputs.name } : {},
@@ -5928,7 +6045,6 @@ async function runServiceInstall(inputs, deps = {}) {
5928
6045
  manager,
5929
6046
  nodeBin: d.nodeBin,
5930
6047
  scriptPath: d.scriptPath,
5931
- token: inputs.token,
5932
6048
  ...inputs.name ? { name: inputs.name } : {},
5933
6049
  ...inputs.hubUrl ? { hubUrl: inputs.hubUrl } : {},
5934
6050
  ...d.pathEnv ? { pathEnv: d.pathEnv } : {},
@@ -5996,21 +6112,19 @@ async function runNodeStart(inputs, deps = {}, opts = {}) {
5996
6112
  manager,
5997
6113
  nodeBin: d.nodeBin,
5998
6114
  scriptPath: d.scriptPath,
5999
- token: inputs.token ?? "-",
6000
6115
  ...inputs.name ? { name: inputs.name } : {},
6001
6116
  ...inputs.hubUrl ? { hubUrl: inputs.hubUrl } : {},
6002
6117
  ...d.pathEnv ? { pathEnv: d.pathEnv } : {},
6003
6118
  homeDir: d.homeDir,
6004
6119
  logDir: d.logDir
6005
6120
  });
6006
- const canRender = inputs.token !== void 0;
6007
- const current = canRender && unitExists && unitIsCurrent(plan, d.readFile(filePath));
6121
+ const current = unitExists && unitIsCurrent(plan, d.readFile(filePath));
6008
6122
  if (!opts.alwaysLoad) {
6009
6123
  const probe2 = unitExists ? d.capture(statusCommand(manager)) : { code: 1, stdout: "" };
6010
6124
  const status = parseServiceStatus(manager, unitExists, probe2);
6011
6125
  if (current && status.running) return { kind: "running", code: 0, already: true };
6012
6126
  }
6013
- if (canRender && !current) {
6127
+ if (!current) {
6014
6128
  try {
6015
6129
  d.mkdirp(d.logDir);
6016
6130
  d.mkdirp(dirOf(plan.filePath));
@@ -6040,7 +6154,6 @@ async function runNodeRestart(inputs, deps = {}) {
6040
6154
  manager,
6041
6155
  nodeBin: d.nodeBin,
6042
6156
  scriptPath: d.scriptPath,
6043
- token: "-",
6044
6157
  homeDir: d.homeDir,
6045
6158
  logDir: d.logDir
6046
6159
  });
@@ -6082,7 +6195,6 @@ async function runNodeStop(inputs = {}, deps = {}) {
6082
6195
  manager,
6083
6196
  nodeBin: d.nodeBin,
6084
6197
  scriptPath: d.scriptPath,
6085
- token: "-",
6086
6198
  homeDir: d.homeDir,
6087
6199
  logDir: d.logDir
6088
6200
  });
@@ -6119,7 +6231,6 @@ async function runServiceUninstall(deps = {}) {
6119
6231
  manager,
6120
6232
  nodeBin: d.nodeBin,
6121
6233
  scriptPath: d.scriptPath,
6122
- token: "-",
6123
6234
  homeDir: d.homeDir,
6124
6235
  logDir: d.logDir
6125
6236
  });
@@ -6183,9 +6294,9 @@ var defaultDeps3 = () => ({
6183
6294
  // npm-global bins) that a supervisor's minimal PATH would otherwise hide.
6184
6295
  pathEnv: process.env.PATH,
6185
6296
  mkdirp: (dir) => void mkdirSync12(dir, { recursive: true }),
6186
- // The unit's environment block carries the enrolment token, so the file is a secret: write it
6187
- // owner-only. `writeFileSync`'s `mode` applies only when it CREATES the file, so chmod explicitly
6188
- // too - re-installing over a unit an older client left at 0644 must tighten it, not keep it.
6297
+ // The unit holds no secret any more, but it stays owner-only: it names paths on this host, and there is
6298
+ // no reason for it to be world-readable. `writeFileSync`'s `mode` applies only when it CREATES the file,
6299
+ // so chmod explicitly too - re-installing over a unit an older client left at 0644 must tighten it.
6189
6300
  writeFile: (path, content) => {
6190
6301
  writeFileSync11(path, content, { mode: UNIT_FILE_MODE });
6191
6302
  chmodSync3(path, UNIT_FILE_MODE);
@@ -6527,6 +6638,12 @@ function renderStatus(f) {
6527
6638
  lines.push(kv("Work", jobLine(first, f.now)));
6528
6639
  for (const j of rest) lines.push(kv("", jobLine(j, f.now)));
6529
6640
  }
6641
+ if (isEnrolmentBlocked(f.connection)) {
6642
+ lines.push("");
6643
+ lines.push(verdict("fail", "The hub rejected this node's enrolment token; it is serving no Jobs."));
6644
+ lines.push("");
6645
+ lines.push(hint("Re-enrol with `dahrk start --token <token>`. Get one at https://app.dahrk.ai."));
6646
+ }
6530
6647
  lines.push(...presenceHints(f).flatMap((h) => ["", h]));
6531
6648
  lines.push("", dim(` State file: ${f.stateFile}`));
6532
6649
  return lines;
@@ -6549,7 +6666,7 @@ function jobLine(j, now) {
6549
6666
  return `${where} ${dim(humanDuration(now - j.startedAt))}`;
6550
6667
  }
6551
6668
  function isUnhealthy(f) {
6552
- return f.presence.kind === "crashed";
6669
+ return f.presence.kind === "crashed" || isEnrolmentBlocked(f.connection);
6553
6670
  }
6554
6671
  function resolvePresence(service, lockedPid, desired) {
6555
6672
  if (service?.running) return { kind: "running", ...service.pid ? { pid: service.pid } : {} };
@@ -6592,10 +6709,21 @@ async function gatherFacts(inputs, deps) {
6592
6709
  }
6593
6710
  var CONNECTION_MARKERS = [
6594
6711
  { prefix: "EDGE_WELCOMED", event: "welcomed", detail: false },
6712
+ // The two markers whose absence here was itself the bug. `EDGE_CONNECTED` is written when the SOCKET
6713
+ // opens, which is before the hub has looked at our token - so a node whose enrolment was rejected has
6714
+ // `EDGE_CONNECTED` as its last marker, and `status` used to read that and cheerfully report a connected
6715
+ // node while it served nothing. The rejection is the newer marker, and it is the true one.
6716
+ { prefix: "EDGE_REJECTED", event: "rejected", detail: true },
6717
+ { prefix: "EDGE_PARKED", event: "parked", detail: false },
6718
+ { prefix: "EDGE_UNPARKED", event: "reconnecting", detail: false },
6595
6719
  { prefix: "EDGE_CONNECTED", event: "connected", detail: false },
6596
6720
  { prefix: "EDGE_DISCONNECTED", event: "disconnected", detail: true },
6597
6721
  { prefix: "EDGE_STALE", event: "went stale", detail: false }
6598
6722
  ];
6723
+ var BLOCKED_EVENTS = /* @__PURE__ */ new Set(["rejected", "parked"]);
6724
+ function isEnrolmentBlocked(connection) {
6725
+ return connection !== void 0 && BLOCKED_EVENTS.has(connection.event);
6726
+ }
6599
6727
  function lastConnection(records) {
6600
6728
  for (let i = records.length - 1; i >= 0; i--) {
6601
6729
  const r = records[i];
@@ -6621,7 +6749,7 @@ async function runStatus(inputs, deps) {
6621
6749
  }
6622
6750
 
6623
6751
  // src/main.ts
6624
- var CLIENT_VERSION = "0.1.17";
6752
+ var CLIENT_VERSION = "0.1.19";
6625
6753
  var DEFAULT_HUB_URL = "wss://api.dahrk.ai";
6626
6754
  var list = (v) => (v ?? "").split(",").map((s) => s.trim()).filter(Boolean);
6627
6755
  var RUNTIMES2 = ["claude-code", "codex", "pi"];
@@ -6706,6 +6834,8 @@ function wantsForeground(env, flags) {
6706
6834
  async function start(flags) {
6707
6835
  const env = envWithFlags(process.env, flags);
6708
6836
  if (wantsForeground(env, flags)) return startForeground(env, flags);
6837
+ const enrolled = await enrolToDisk(env);
6838
+ if (enrolled !== 0) return enrolled;
6709
6839
  await offerUpdate(env);
6710
6840
  const outcome = await runNodeStart(serviceInputs(env));
6711
6841
  if (outcome.kind === "error") return outcome.code;
@@ -6725,6 +6855,28 @@ var serviceInputs = (env) => ({
6725
6855
  ...env.DAHRK_NODE_NAME ? { name: env.DAHRK_NODE_NAME } : {},
6726
6856
  ...env.DAHRK_HUB_URL ? { hubUrl: env.DAHRK_HUB_URL } : {}
6727
6857
  });
6858
+ async function enrolToDisk(env) {
6859
+ const token = resolveEnrolToken(env);
6860
+ if (!token || token === readPersistedToken(env)) return 0;
6861
+ const hubUrl = env.DAHRK_HUB_URL ?? DEFAULT_HUB_URL;
6862
+ const probe2 = await probeHub({ hubUrl, enrolToken: token, clientVersion: CLIENT_VERSION });
6863
+ if (!probe2.ok && probe2.reason === "rejected") {
6864
+ out("");
6865
+ out(verdict("fail", `The hub rejected that enrolment token: ${probe2.detail}`));
6866
+ out("");
6867
+ out(hint("Get a fresh token at https://app.dahrk.ai, then run `dahrk start --token <token>` again."));
6868
+ return 2;
6869
+ }
6870
+ if (!probe2.ok) {
6871
+ out("");
6872
+ out(verdict("warn", `Could not reach ${hubUrl} to check the token (${probe2.detail}); using it anyway.`));
6873
+ }
6874
+ persistEnrolment(env, {
6875
+ token,
6876
+ ...probe2.ok ? { name: probe2.name, tenantId: probe2.tenantId } : {}
6877
+ });
6878
+ return 0;
6879
+ }
6728
6880
  async function restart(flags, force) {
6729
6881
  const env = envWithFlags(process.env, flags);
6730
6882
  out("");
@@ -6763,7 +6915,10 @@ async function startForeground(env, flags) {
6763
6915
  clientVersion: CLIENT_VERSION,
6764
6916
  ...env.DAHRK_CRASH_EXIT === "1" ? { exitOnCrash: true } : {}
6765
6917
  });
6766
- const token = resolveEnrolToken(env, { ephemeral: flags.ephemeral });
6918
+ const token = resolveEnrolToken(env, {
6919
+ ephemeral: flags.ephemeral,
6920
+ supervised: env.DAHRK_SUPERVISED === "1"
6921
+ });
6767
6922
  if (token) env.DAHRK_ENROL_TOKEN = token;
6768
6923
  const runtimes = await resolveRuntimes(env);
6769
6924
  if (!flags.ephemeral) {
@@ -6802,7 +6957,15 @@ async function startForeground(env, flags) {
6802
6957
  ...flags.ephemeral ? {} : { jobLedger: fileJobLedger(jobLedgerFile(stateDir(env))) },
6803
6958
  ...persist ? {
6804
6959
  onEnrolled: (welcome) => persistEnrolment(env, { token, name: welcome.name, tenantId: welcome.tenantId })
6805
- } : {}
6960
+ } : {},
6961
+ // How a rejected node heals. Reads `node.json` DIRECTLY rather than going through
6962
+ // `resolveEnrolToken`, and that is the whole point: the disk is where re-enrolment writes, so it is
6963
+ // the only place a *newer* token can appear. (Deliberately narrower than boot-time resolution, which
6964
+ // still lets an explicit `DAHRK_ENROL_TOKEN` win - but a stale env var must never be able to shadow
6965
+ // the fresh token on disk forever, which is precisely how a node used to wedge.)
6966
+ //
6967
+ // Only for a persistent node: an ephemeral one has no disk to heal from and must still fail fast.
6968
+ ...flags.ephemeral ? {} : { refreshEnrolToken: () => readPersistedToken(env) }
6806
6969
  });
6807
6970
  return 0;
6808
6971
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dahrk-node",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
4
4
  "private": false,
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",