@hasna/workflows 0.1.3 → 0.1.4

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.
@@ -17,12 +17,12 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
17
17
  var __require = import.meta.require;
18
18
 
19
19
  // src/lanes/types.ts
20
- import { statSync } from "fs";
20
+ import { statSync as statSync2 } from "fs";
21
21
  function binaryOnPath(binary) {
22
22
  const pathEntries = (process.env.PATH ?? "").split(":").filter(Boolean);
23
23
  for (const entry of pathEntries) {
24
24
  try {
25
- const stat = statSync(`${entry}/${binary}`);
25
+ const stat = statSync2(`${entry}/${binary}`);
26
26
  if (stat.isFile())
27
27
  return true;
28
28
  } catch {}
@@ -297,10 +297,12 @@ function createCursorAdapter(deps = {}) {
297
297
  try {
298
298
  const run = await withTimeout(runLocalAgent(sdk, job), timeoutMs, `cursor lane (${job.prompt.slice(0, 60)})`);
299
299
  const durationMs = Date.now() - started;
300
- const status = run?.status ?? "finished";
300
+ const status = run.status;
301
301
  const ok = status === "finished";
302
- return { ok, exitCode: ok ? 0 : 1, output: run?.result ?? "", durationMs };
302
+ return { ok, exitCode: ok ? 0 : 1, output: run.result, durationMs };
303
303
  } catch (err) {
304
+ if (err instanceof LaneAdapterShapeError)
305
+ throw err;
304
306
  const durationMs = Date.now() - started;
305
307
  return { ok: false, exitCode: 1, output: "", durationMs, error: String(err instanceof Error ? err.message : err) };
306
308
  }
@@ -321,7 +323,14 @@ async function runLocalAgent(sdk, job) {
321
323
  if (!run || typeof run !== "object") {
322
324
  throw new LaneAdapterShapeError("@cursor/sdk Agent.send() returned no Run");
323
325
  }
324
- return run;
326
+ const candidate = run;
327
+ if (typeof candidate.status !== "string" || !["finished", "error", "cancelled"].includes(candidate.status)) {
328
+ throw new LaneAdapterShapeError(`@cursor/sdk Agent.send() returned a Run without a terminal status (got ${JSON.stringify(candidate.status)}; expected finished|error|cancelled)`);
329
+ }
330
+ if (typeof candidate.result !== "string") {
331
+ throw new LaneAdapterShapeError("@cursor/sdk Agent.send() returned a Run without a string result");
332
+ }
333
+ return { status: candidate.status, result: candidate.result };
325
334
  }
326
335
  function runCursorCli(job, cliPath, started, timeoutMs) {
327
336
  try {
@@ -813,6 +822,18 @@ function validateGraph(graph) {
813
822
  if (typeof graph.version !== "string" || graph.version.trim() === "") {
814
823
  issue(issues, "version", "graph requires a version string");
815
824
  }
825
+ if (graph.memoWatch !== undefined) {
826
+ if (!Array.isArray(graph.memoWatch) || graph.memoWatch.length === 0) {
827
+ issue(issues, "memoWatch", "memoWatch must be a non-empty array of file paths or globs");
828
+ } else {
829
+ for (const entry of graph.memoWatch) {
830
+ if (typeof entry !== "string" || entry.trim() === "") {
831
+ issue(issues, "memoWatch", "each memoWatch entry must be a non-empty path or glob string");
832
+ break;
833
+ }
834
+ }
835
+ }
836
+ }
816
837
  if (!Array.isArray(graph.nodes) || graph.nodes.length === 0) {
817
838
  issue(issues, "nodes", "graph requires at least one node");
818
839
  return { ok: false, issues };
@@ -971,9 +992,6 @@ function validateNode(graph, node, issues) {
971
992
  }
972
993
  }
973
994
 
974
- // src/daemon.ts
975
- import { createHash as createHash2 } from "crypto";
976
-
977
995
  // src/secrets.ts
978
996
  class SecretsGateError extends Error {
979
997
  constructor(message) {
@@ -1050,6 +1068,8 @@ function assertNoSecrets(value, what) {
1050
1068
 
1051
1069
  // src/session.ts
1052
1070
  import { createHash } from "crypto";
1071
+ import { readFileSync, statSync } from "fs";
1072
+ import { isAbsolute, join } from "path";
1053
1073
  var DEFAULT_MAX_ATTEMPTS = 3;
1054
1074
  function repairTornRuns(store, wal, opts = {}) {
1055
1075
  const maxAttempts = opts.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
@@ -1105,6 +1125,46 @@ function recordMemo(store, graphName, nodeId, input, output) {
1105
1125
  const key = memoKey(graphName, nodeId, input);
1106
1126
  store.memoPut(key, graphName, nodeId, inputHash(input), JSON.stringify(output));
1107
1127
  }
1128
+ function memoWatchFingerprint(graph, dataDir) {
1129
+ if (!graph.memoWatch || graph.memoWatch.length === 0)
1130
+ return;
1131
+ const entries = [];
1132
+ for (const pattern of graph.memoWatch) {
1133
+ if (hasGlobMagic(pattern)) {
1134
+ const matches = [...new Bun.Glob(pattern).scanSync({ cwd: dataDir, onlyFiles: true })].sort();
1135
+ for (const match of matches) {
1136
+ const path2 = isAbsolute(match) ? match : join(dataDir, match);
1137
+ entries.push(fingerprintFile(pattern, path2));
1138
+ }
1139
+ continue;
1140
+ }
1141
+ const path = isAbsolute(pattern) ? pattern : join(dataDir, pattern);
1142
+ entries.push(fingerprintFile(pattern, path));
1143
+ }
1144
+ return entries;
1145
+ }
1146
+ function hasGlobMagic(pattern) {
1147
+ return /[*?[\]{}]/.test(pattern);
1148
+ }
1149
+ function fingerprintFile(pattern, path) {
1150
+ try {
1151
+ const stat = statSync(path);
1152
+ if (!stat.isFile()) {
1153
+ return { pattern, path, missing: true, mtimeMs: null, size: null, sha256: null };
1154
+ }
1155
+ const content = readFileSync(path);
1156
+ return {
1157
+ pattern,
1158
+ path,
1159
+ missing: false,
1160
+ mtimeMs: stat.mtimeMs,
1161
+ size: content.byteLength,
1162
+ sha256: createHash("sha256").update(content).digest("hex")
1163
+ };
1164
+ } catch {
1165
+ return { pattern, path, missing: true, mtimeMs: null, size: null, sha256: null };
1166
+ }
1167
+ }
1108
1168
  function findRunByIdempotencyKey(store, key) {
1109
1169
  const runs = store.listRuns({ limit: 1000 });
1110
1170
  for (const run of runs) {
@@ -1379,10 +1439,13 @@ class WorkflowsDaemon {
1379
1439
  }
1380
1440
  async executeStep(run, graph, context, wf, node) {
1381
1441
  const nodeId = node.id;
1382
- const existing = this.store.listRunNodes(run.id).find((n) => n.nodeId === nodeId);
1383
- const nodeRow = existing ?? this.store.createRunNode({ runId: run.id, nodeId, lane: node.lane ?? "claude" });
1384
- if (node.memo) {
1385
- const memoInput = { command: node.command, prompt: node.prompt, context: stripRunState(context) };
1442
+ const latest = this.store.getLatestRunNode(run.id, nodeId);
1443
+ const isLoopBody = graph.nodes.some((candidate) => candidate.type === "while" && candidate.body.includes(nodeId));
1444
+ const nodeRow = latest === undefined || isLoopBody && latest.status === "completed" ? this.store.createRunNode({ runId: run.id, nodeId, lane: node.lane ?? "claude" }) : latest;
1445
+ const memoWatch = node.memo ? memoWatchFingerprint(graph, this.store.dataDir) : undefined;
1446
+ const memoSource = { command: node.command, prompt: node.prompt, context: stripRunState(context) };
1447
+ const memoInput = node.memo ? memoWatch !== undefined ? { ...memoSource, memoWatch } : memoSource : undefined;
1448
+ if (memoInput !== undefined) {
1386
1449
  const hit = tryMemoHit(this.store, run.graphName, nodeId, memoInput);
1387
1450
  if (hit) {
1388
1451
  const cached = JSON.parse(hit.outputJson);
@@ -1434,9 +1497,13 @@ class WorkflowsDaemon {
1434
1497
  }
1435
1498
  this.store.setRunNodeStatus(nodeRow.id, "completed", { exitCode: result.exitCode, output });
1436
1499
  this.wal.append({ op: "node_finished", runId: run.id, nodeId, status: "completed", at: nowIso() });
1437
- if (node.memo) {
1438
- recordMemo(this.store, run.graphName, nodeId, { command: node.command, prompt: node.prompt, context: stripRunState(context) }, output);
1439
- this.wal.append({ op: "memo_set", key: `${run.graphName}:${nodeId}:${createHash2("sha256").update(JSON.stringify({ command: node.command, prompt: node.prompt, context: stripRunState(context) })).digest("hex")}`, at: nowIso() });
1500
+ if (memoInput !== undefined) {
1501
+ const memoInputAfter = node.memo ? memoWatchFingerprint(graph, this.store.dataDir) : undefined;
1502
+ const stable = memoWatch === undefined && memoInputAfter === undefined || memoWatch !== undefined && memoInputAfter !== undefined && JSON.stringify(memoWatch) === JSON.stringify(memoInputAfter);
1503
+ if (stable) {
1504
+ recordMemo(this.store, run.graphName, nodeId, memoInput, output);
1505
+ this.wal.append({ op: "memo_set", key: memoKey(run.graphName, nodeId, memoInput), at: nowIso() });
1506
+ }
1440
1507
  }
1441
1508
  this.recordStepResult(context, nodeId, output);
1442
1509
  return this.afterNode(run, context, wf, node.next);
@@ -1531,6 +1598,7 @@ async function runGraphToCompletion(store, wal, graph, context, options = {}) {
1531
1598
  isolatedRunId: options.isolatedRunId
1532
1599
  });
1533
1600
  const run = daemon.startRun(graph, context);
1601
+ options.onRunCreated?.(run);
1534
1602
  daemon.isolateRun(run.id);
1535
1603
  const maxCycles = options.maxCycles ?? 500;
1536
1604
  for (let cycle = 0;cycle < maxCycles; cycle++) {
@@ -1551,17 +1619,41 @@ init_lanes();
1551
1619
  // src/store.ts
1552
1620
  import { Database } from "bun:sqlite";
1553
1621
  import { mkdirSync } from "fs";
1554
- import { join } from "path";
1622
+ import { join as join2 } from "path";
1555
1623
  import { randomUUID } from "crypto";
1556
1624
  function nowIso2() {
1557
1625
  return new Date().toISOString();
1558
1626
  }
1627
+ var BUSY_TIMEOUT_MS = 3000;
1628
+ var BUSY_RETRIES = 2;
1629
+ var BUSY_RETRY_SLEEP_MS = 50;
1630
+ function isBusyError(err) {
1631
+ if (!(err instanceof Error))
1632
+ return false;
1633
+ const code = String(err.code ?? "");
1634
+ return code.includes("BUSY") || /\bdatabase is locked\b/i.test(err.message);
1635
+ }
1636
+ function retryBusy(fn) {
1637
+ let last;
1638
+ for (let attempt = 0;attempt <= BUSY_RETRIES; attempt++) {
1639
+ try {
1640
+ return fn();
1641
+ } catch (err) {
1642
+ if (!isBusyError(err))
1643
+ throw err;
1644
+ last = err;
1645
+ Bun.sleepSync(BUSY_RETRY_SLEEP_MS);
1646
+ }
1647
+ }
1648
+ throw last;
1649
+ }
1559
1650
  function openStore(dataDir) {
1560
1651
  mkdirSync(dataDir, { recursive: true });
1561
- const db = new Database(join(dataDir, "workflows.db"));
1562
- db.exec("PRAGMA journal_mode = WAL");
1563
- db.exec("PRAGMA foreign_keys = ON");
1564
- db.exec(`
1652
+ const db = new Database(join2(dataDir, "workflows.db"));
1653
+ retryBusy(() => db.exec(`PRAGMA busy_timeout = ${BUSY_TIMEOUT_MS}`));
1654
+ retryBusy(() => db.exec("PRAGMA journal_mode = WAL"));
1655
+ retryBusy(() => db.exec("PRAGMA foreign_keys = ON"));
1656
+ retryBusy(() => db.exec(`
1565
1657
  CREATE TABLE IF NOT EXISTS runs (
1566
1658
  id TEXT PRIMARY KEY,
1567
1659
  graph_name TEXT NOT NULL,
@@ -1601,7 +1693,7 @@ function openStore(dataDir) {
1601
1693
  CREATE INDEX IF NOT EXISTS idx_runs_status ON runs(status);
1602
1694
  CREATE INDEX IF NOT EXISTS idx_runs_created ON runs(created_at);
1603
1695
  CREATE INDEX IF NOT EXISTS idx_run_nodes_run ON run_nodes(run_id);
1604
- `);
1696
+ `));
1605
1697
  const rowToRun = (r) => ({
1606
1698
  id: String(r.id),
1607
1699
  graphName: String(r.graph_name),
@@ -1639,31 +1731,33 @@ function openStore(dataDir) {
1639
1731
  createdAt: String(r.created_at)
1640
1732
  });
1641
1733
  return {
1734
+ dataDir,
1642
1735
  close() {
1643
1736
  db.close();
1644
1737
  },
1645
1738
  listTables() {
1646
- const rows = db.query("SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'").all();
1739
+ const rows = retryBusy(() => db.query("SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'").all());
1647
1740
  return rows.map((r) => r.name);
1648
1741
  },
1649
1742
  createRun(input) {
1650
1743
  const id = randomUUID();
1651
1744
  const now = nowIso2();
1652
- db.query(`INSERT INTO runs (id, graph_name, graph_version, status, attempts, context_json, created_at, updated_at)
1653
- VALUES (?, ?, ?, 'pending', 0, ?, ?, ?)`).run(id, input.graphName, input.graphVersion, JSON.stringify(input.context ?? {}), now, now);
1745
+ retryBusy(() => db.query(`INSERT INTO runs (id, graph_name, graph_version, status, attempts, context_json, created_at, updated_at)
1746
+ VALUES (?, ?, ?, 'pending', 0, ?, ?, ?)`).run(id, input.graphName, input.graphVersion, JSON.stringify(input.context ?? {}), now, now));
1654
1747
  return this.getRun(id);
1655
1748
  },
1656
1749
  getRun(id) {
1657
- const row = db.query(`SELECT * FROM runs WHERE id = ?`).get(id);
1750
+ const row = retryBusy(() => db.query(`SELECT * FROM runs WHERE id = ?`).get(id));
1658
1751
  return row ? rowToRun(row) : undefined;
1659
1752
  },
1660
1753
  listRuns(opts = {}) {
1661
1754
  const limit = Math.min(opts.limit ?? 100, 1000);
1662
1755
  if (opts.status) {
1663
- const rows2 = db.query(`SELECT * FROM runs WHERE status = ? ORDER BY created_at DESC, id DESC LIMIT ?`).all(opts.status, limit);
1756
+ const status = opts.status;
1757
+ const rows2 = retryBusy(() => db.query(`SELECT * FROM runs WHERE status = ? ORDER BY created_at DESC, id DESC LIMIT ?`).all(status, limit));
1664
1758
  return rows2.map((r) => rowToRun(r));
1665
1759
  }
1666
- const rows = db.query(`SELECT * FROM runs ORDER BY created_at DESC, id DESC LIMIT ?`).all(limit);
1760
+ const rows = retryBusy(() => db.query(`SELECT * FROM runs ORDER BY created_at DESC, id DESC LIMIT ?`).all(limit));
1667
1761
  return rows.map((r) => rowToRun(r));
1668
1762
  },
1669
1763
  setRunStatus(id, status, patch = {}) {
@@ -1674,28 +1768,32 @@ function openStore(dataDir) {
1674
1768
  const startedAt = patch.startedAt !== undefined ? patch.startedAt : status === "running" && existing?.startedAt === null ? now : existing?.startedAt ?? null;
1675
1769
  const terminal = status === "completed" || status === "failed" || status === "cancelled" || status === "interrupted";
1676
1770
  const finishedAt = patch.finishedAt !== undefined ? patch.finishedAt : terminal && existing?.finishedAt === null ? now : existing?.finishedAt ?? null;
1677
- db.query(`UPDATE runs SET status = ?, result_json = ?, error = ?, started_at = ?, finished_at = ?, updated_at = ? WHERE id = ?`).run(status, resultJson, error, startedAt, finishedAt, now, id);
1771
+ retryBusy(() => db.query(`UPDATE runs SET status = ?, result_json = ?, error = ?, started_at = ?, finished_at = ?, updated_at = ? WHERE id = ?`).run(status, resultJson, error, startedAt, finishedAt, now, id));
1678
1772
  },
1679
1773
  bumpAttempts(id) {
1680
- db.query(`UPDATE runs SET attempts = attempts + 1, updated_at = ? WHERE id = ?`).run(nowIso2(), id);
1774
+ retryBusy(() => db.query(`UPDATE runs SET attempts = attempts + 1, updated_at = ? WHERE id = ?`).run(nowIso2(), id));
1681
1775
  },
1682
1776
  setRunContext(id, context) {
1683
- db.query(`UPDATE runs SET context_json = ?, updated_at = ? WHERE id = ?`).run(JSON.stringify(context), nowIso2(), id);
1777
+ retryBusy(() => db.query(`UPDATE runs SET context_json = ?, updated_at = ? WHERE id = ?`).run(JSON.stringify(context), nowIso2(), id));
1684
1778
  },
1685
1779
  createRunNode(input) {
1686
1780
  const id = randomUUID();
1687
- db.query(`INSERT INTO run_nodes (id, run_id, node_id, status, attempts, lane)
1688
- VALUES (?, ?, ?, 'pending', 0, ?)`).run(id, input.runId, input.nodeId, input.lane ?? null);
1781
+ retryBusy(() => db.query(`INSERT INTO run_nodes (id, run_id, node_id, status, attempts, lane)
1782
+ VALUES (?, ?, ?, 'pending', 0, ?)`).run(id, input.runId, input.nodeId, input.lane ?? null));
1689
1783
  return this.getRunNode(id);
1690
1784
  },
1691
1785
  getRunNode(id) {
1692
- const row = db.query(`SELECT * FROM run_nodes WHERE id = ?`).get(id);
1786
+ const row = retryBusy(() => db.query(`SELECT * FROM run_nodes WHERE id = ?`).get(id));
1693
1787
  return row ? rowToNode(row) : undefined;
1694
1788
  },
1695
1789
  listRunNodes(runId) {
1696
- const rows = db.query(`SELECT * FROM run_nodes WHERE run_id = ? ORDER BY rowid ASC`).all(runId);
1790
+ const rows = retryBusy(() => db.query(`SELECT * FROM run_nodes WHERE run_id = ? ORDER BY rowid ASC`).all(runId));
1697
1791
  return rows.map((r) => rowToNode(r));
1698
1792
  },
1793
+ getLatestRunNode(runId, nodeId) {
1794
+ const row = retryBusy(() => db.query(`SELECT * FROM run_nodes WHERE run_id = ? AND node_id = ? ORDER BY rowid DESC LIMIT 1`).get(runId, nodeId));
1795
+ return row ? rowToNode(row) : undefined;
1796
+ },
1699
1797
  setRunNodeStatus(id, status, patch = {}) {
1700
1798
  const now = nowIso2();
1701
1799
  const existing = this.getRunNode(id);
@@ -1707,44 +1805,44 @@ function openStore(dataDir) {
1707
1805
  const startedAt = patch.startedAt !== undefined ? patch.startedAt : status === "running" && existing.startedAt === null ? now : existing.startedAt;
1708
1806
  const terminal = status === "completed" || status === "failed" || status === "skipped";
1709
1807
  const finishedAt = patch.finishedAt !== undefined ? patch.finishedAt : terminal && existing.finishedAt === null ? now : existing.finishedAt;
1710
- db.query(`UPDATE run_nodes SET status = ?, exit_code = ?, output_json = ?, error = ?, started_at = ?, finished_at = ? WHERE id = ?`).run(status, exitCode, outputJson, error, startedAt, finishedAt, id);
1808
+ retryBusy(() => db.query(`UPDATE run_nodes SET status = ?, exit_code = ?, output_json = ?, error = ?, started_at = ?, finished_at = ? WHERE id = ?`).run(status, exitCode, outputJson, error, startedAt, finishedAt, id));
1711
1809
  },
1712
1810
  bumpAttemptsNode(id) {
1713
- db.query(`UPDATE run_nodes SET attempts = attempts + 1 WHERE id = ?`).run(id);
1811
+ retryBusy(() => db.query(`UPDATE run_nodes SET attempts = attempts + 1 WHERE id = ?`).run(id));
1714
1812
  },
1715
1813
  memoPut(key, graphName, nodeId, inputHash2, outputJson) {
1716
- db.query(`INSERT INTO memos (key, graph_name, node_id, input_hash, output_json, hit_count, created_at)
1717
- VALUES (?, ?, ?, ?, ?, 0, ?)
1718
- ON CONFLICT(key) DO UPDATE SET output_json = excluded.output_json`).run(key, graphName, nodeId, inputHash2, outputJson, nowIso2());
1814
+ retryBusy(() => db.query(`INSERT INTO memos (key, graph_name, node_id, input_hash, output_json, hit_count, created_at)
1815
+ VALUES (?, ?, ?, ?, ?, 0, ?)
1816
+ ON CONFLICT(key) DO UPDATE SET output_json = excluded.output_json`).run(key, graphName, nodeId, inputHash2, outputJson, nowIso2()));
1719
1817
  },
1720
1818
  memoGet(key) {
1721
- const row = db.query(`SELECT * FROM memos WHERE key = ?`).get(key);
1819
+ const row = retryBusy(() => db.query(`SELECT * FROM memos WHERE key = ?`).get(key));
1722
1820
  return row ? rowToMemo(row) : undefined;
1723
1821
  },
1724
1822
  memoHit(key) {
1725
- db.query(`UPDATE memos SET hit_count = hit_count + 1 WHERE key = ?`).run(key);
1823
+ retryBusy(() => db.query(`UPDATE memos SET hit_count = hit_count + 1 WHERE key = ?`).run(key));
1726
1824
  },
1727
1825
  memoList() {
1728
- const rows = db.query(`SELECT * FROM memos ORDER BY created_at DESC LIMIT 1000`).all();
1826
+ const rows = retryBusy(() => db.query(`SELECT * FROM memos ORDER BY created_at DESC LIMIT 1000`).all());
1729
1827
  return rows.map((r) => rowToMemo(r));
1730
1828
  },
1731
1829
  memoClear() {
1732
- db.exec(`DELETE FROM memos`);
1830
+ retryBusy(() => db.exec(`DELETE FROM memos`));
1733
1831
  }
1734
1832
  };
1735
1833
  }
1736
1834
 
1737
1835
  // src/wal.ts
1738
- import { createHash as createHash3 } from "crypto";
1739
- import { appendFileSync, existsSync, mkdirSync as mkdirSync2, readFileSync, renameSync, truncateSync, writeFileSync } from "fs";
1740
- import { join as join2 } from "path";
1836
+ import { createHash as createHash2 } from "crypto";
1837
+ import { appendFileSync, existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync, truncateSync, writeFileSync } from "fs";
1838
+ import { join as join3 } from "path";
1741
1839
  var WAL_FILE_NAME = "session.wal";
1742
1840
  var SESSIONS_DIR_NAME = "sessions";
1743
1841
  var MAX_WAL_BYTES = 256 * 1024 * 1024;
1744
1842
  function checksumOf(op) {
1745
1843
  if (op === undefined || op === null)
1746
1844
  return "";
1747
- return createHash3("sha256").update(JSON.stringify(op)).digest("hex");
1845
+ return createHash2("sha256").update(JSON.stringify(op)).digest("hex");
1748
1846
  }
1749
1847
 
1750
1848
  class SessionWAL {
@@ -1753,9 +1851,9 @@ class SessionWAL {
1753
1851
  this.filePath = filePath;
1754
1852
  }
1755
1853
  static open(dataDir) {
1756
- mkdirSync2(join2(dataDir, SESSIONS_DIR_NAME), { recursive: true });
1757
- const canonical = join2(dataDir, SESSIONS_DIR_NAME, WAL_FILE_NAME);
1758
- const legacy = join2(dataDir, WAL_FILE_NAME);
1854
+ mkdirSync2(join3(dataDir, SESSIONS_DIR_NAME), { recursive: true });
1855
+ const canonical = join3(dataDir, SESSIONS_DIR_NAME, WAL_FILE_NAME);
1856
+ const legacy = join3(dataDir, WAL_FILE_NAME);
1759
1857
  if (existsSync(legacy) && !existsSync(canonical)) {
1760
1858
  try {
1761
1859
  renameSync(legacy, canonical);
@@ -1787,7 +1885,7 @@ class SessionWAL {
1787
1885
  let repaired = false;
1788
1886
  let content;
1789
1887
  try {
1790
- content = readFileSync(this.filePath, "utf8");
1888
+ content = readFileSync2(this.filePath, "utf8");
1791
1889
  } catch {
1792
1890
  return { entries, torn, repaired, liveClaims: () => new Map };
1793
1891
  }