codemem 0.43.1 → 0.43.2

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.js CHANGED
@@ -102,7 +102,7 @@ var BOOLEAN_TOGGLE_VALUES = new Set([
102
102
  "on",
103
103
  "no"
104
104
  ]);
105
- function expandHome$4(value) {
105
+ function expandHome$3(value) {
106
106
  const home = process.env.HOME?.trim() || homedir();
107
107
  if (value === "~") return home;
108
108
  if (value.startsWith("~/")) return join(home, value.slice(2));
@@ -111,8 +111,8 @@ function expandHome$4(value) {
111
111
  function pluginLogPath() {
112
112
  const raw = process.env.CODEMEM_PLUGIN_LOG_PATH ?? process.env.CODEMEM_PLUGIN_LOG ?? "";
113
113
  const normalized = raw.trim().toLowerCase();
114
- if (BOOLEAN_TOGGLE_VALUES.has(normalized)) return expandHome$4("~/.codemem/plugin.log");
115
- return expandHome$4(raw.trim());
114
+ if (BOOLEAN_TOGGLE_VALUES.has(normalized)) return expandHome$3("~/.codemem/plugin.log");
115
+ return expandHome$3(raw.trim());
116
116
  }
117
117
  /**
118
118
  * Append a single timestamped line to the plugin log. Best-effort: any
@@ -164,7 +164,7 @@ function envTruthy$3(value) {
164
164
  const normalized = String(value ?? "").trim().toLowerCase();
165
165
  return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on";
166
166
  }
167
- function expandHome$3(value) {
167
+ function expandHome$2(value) {
168
168
  if (value === "~") return homedir();
169
169
  if (value.startsWith("~/")) return resolve(homedir(), value.slice(2));
170
170
  return value;
@@ -360,7 +360,7 @@ async function buildClaudeFileContext(payload, opts, deps = {}) {
360
360
  return finish(continueResult$2());
361
361
  }
362
362
  const cwd = typeof payload.cwd === "string" && payload.cwd.trim() ? payload.cwd : process.cwd();
363
- const expandedPath = expandHome$3(filePath);
363
+ const expandedPath = expandHome$2(filePath);
364
364
  const absolutePath = isAbsolute(expandedPath) ? expandedPath : resolve(cwd, expandedPath);
365
365
  const relativePath = relative(cwd, absolutePath).split(sep).join("/");
366
366
  const escapesCwd = relativePath === ".." || relativePath.startsWith("../") || isAbsolute(relativePath);
@@ -519,34 +519,22 @@ var claudeHookFileContextCommand = claudeHookFileContextCmd.action(async (opts)
519
519
  emitJson$2(await buildClaudeFileContext(payload, opts));
520
520
  });
521
521
  //#endregion
522
- //#region src/commands/claude-hook-ingest-spool.ts
522
+ //#region src/commands/hook-ingest-spool.ts
523
523
  /**
524
- * Durability layer for `claude-hook-ingest`: file-based mutex to
525
- * serialize concurrent invocations, on-disk spool that captures
526
- * payloads when both HTTP and direct ingestion paths fail, and a
527
- * recovery routine that promotes stale temp files back into the queue.
524
+ * Shared durability layer for the hook ingest commands (claude, codex):
525
+ * a file-based mutex that serializes concurrent invocations, an on-disk
526
+ * spool that captures payloads when both HTTP and direct ingestion fail,
527
+ * and a recovery routine that promotes stale temp files back into the
528
+ * queue. Each client instantiates its own copy via `createHookIngestSpool`
529
+ * with its own lock/spool dirs, TTL, retry budget, log prefix, and
530
+ * client-specific `LockBusyError` name/message. Per-client flush
531
+ * predicates stay in the client files.
532
+ */
533
+ var LOCK_ACQUIRE_BACKOFF_MS = 50;
534
+ /**
535
+ * Boolean-shaped env toggle used by per-client flush predicates
536
+ * (claude's `shouldForceBoundaryFlush`).
528
537
  */
529
- var DEFAULT_LOCK_TTL_S$1 = 300;
530
- var DEFAULT_LOCK_GRACE_S$1 = 2;
531
- var LOCK_ACQUIRE_ATTEMPTS$1 = 100;
532
- var LOCK_ACQUIRE_BACKOFF_MS$1 = 50;
533
- var LockBusyError = class extends Error {
534
- constructor() {
535
- super("claude-hook-ingest lock busy");
536
- this.name = "LockBusyError";
537
- }
538
- };
539
- function expandHome$2(value) {
540
- if (value === "~") return homedir();
541
- if (value.startsWith("~/")) return join(homedir(), value.slice(2));
542
- return value;
543
- }
544
- function envInt$1(name, fallback) {
545
- const raw = process.env[name];
546
- if (raw === void 0) return fallback;
547
- const parsed = Number.parseInt(raw, 10);
548
- return Number.isFinite(parsed) ? parsed : fallback;
549
- }
550
538
  function envTruthy$2(name, fallback) {
551
539
  const raw = process.env[name];
552
540
  if (raw === void 0) return fallback;
@@ -565,336 +553,401 @@ function envTruthy$2(name, fallback) {
565
553
  ].includes(normalized)) return false;
566
554
  return fallback;
567
555
  }
568
- function lockConfig$1() {
569
- return {
570
- lockDir: expandHome$2(process.env.CODEMEM_CLAUDE_HOOK_LOCK_DIR?.trim() || "~/.codemem/claude-hook-ingest.lock"),
571
- ttlSeconds: Math.max(1, envInt$1("CODEMEM_CLAUDE_HOOK_LOCK_TTL_S", DEFAULT_LOCK_TTL_S$1)),
572
- graceSeconds: Math.max(1, envInt$1("CODEMEM_CLAUDE_HOOK_LOCK_GRACE_S", DEFAULT_LOCK_GRACE_S$1))
573
- };
556
+ function expandHome$1(value) {
557
+ if (value === "~") return homedir();
558
+ if (value.startsWith("~/")) return join(homedir(), value.slice(2));
559
+ return value;
574
560
  }
575
- function spoolDir() {
576
- return expandHome$2(process.env.CODEMEM_CLAUDE_HOOK_SPOOL_DIR?.trim() || "~/.codemem/claude-hook-spool");
561
+ function envInt(name, fallback) {
562
+ const raw = process.env[name];
563
+ if (raw === void 0) return fallback;
564
+ const parsed = Number.parseInt(raw, 10);
565
+ return Number.isFinite(parsed) ? parsed : fallback;
577
566
  }
578
- /**
579
- * Cheap pre-check used by the unlocked HTTP-success path to decide
580
- * whether it needs to acquire the ingest lock and drain queued
581
- * payloads. Returns true when the spool directory contains at least
582
- * one active entry (a `*.json` file that is neither an in-flight
583
- * `.hook-tmp-*` nor a quarantined `.bad-*` file). Any I/O failure
584
- * is treated as "no entries" so callers stay on the fast path.
585
- */
586
- function hasSpooledEntries() {
587
- const dir = spoolDir();
588
- let entries;
589
- try {
590
- entries = readdirSync(dir);
591
- } catch {
567
+ function createHookIngestSpool(cfg) {
568
+ class LockBusyError extends Error {
569
+ constructor() {
570
+ super(cfg.lockBusyErrorMessage);
571
+ this.name = cfg.lockBusyErrorName;
572
+ }
573
+ }
574
+ function lockConfig() {
575
+ return {
576
+ lockDir: expandHome$1(process.env[cfg.lockDirEnv]?.trim() || cfg.lockDirDefault),
577
+ ttlSeconds: Math.max(1, envInt(cfg.lockTtlEnv, cfg.lockTtlDefault)),
578
+ graceSeconds: Math.max(1, envInt(cfg.lockGraceEnv, cfg.lockGraceDefault))
579
+ };
580
+ }
581
+ function spoolDir() {
582
+ return expandHome$1(process.env[cfg.spoolDirEnv]?.trim() || cfg.spoolDirDefault);
583
+ }
584
+ /**
585
+ * Returns the configured lock TTL so callers (hook-ingest commands)
586
+ * can pass the same value to `recoverStaleTmpSpool` without re-reading
587
+ * the env.
588
+ */
589
+ function lockTtlSeconds() {
590
+ return lockConfig().ttlSeconds;
591
+ }
592
+ /**
593
+ * Cheap pre-check used by the unlocked HTTP-success path to decide
594
+ * whether it needs to acquire the ingest lock and drain queued
595
+ * payloads. Returns true when the spool directory contains at least
596
+ * one active entry (a `*.json` file that is neither an in-flight
597
+ * `.hook-tmp-*` nor a quarantined `.bad-*` file). Any I/O failure
598
+ * is treated as "no entries" so callers stay on the fast path.
599
+ */
600
+ function hasSpooledEntries() {
601
+ let entries;
602
+ try {
603
+ entries = readdirSync(spoolDir());
604
+ } catch {
605
+ return false;
606
+ }
607
+ for (const name of entries) {
608
+ if (!name.endsWith(".json")) continue;
609
+ if (name.startsWith(".hook-tmp-") || name.startsWith(".bad-")) continue;
610
+ return true;
611
+ }
592
612
  return false;
593
613
  }
594
- for (const name of entries) {
595
- if (!name.endsWith(".json")) continue;
596
- if (name.startsWith(".hook-tmp-") || name.startsWith(".bad-")) continue;
597
- return true;
614
+ function readFileTrimmedOrEmpty(path) {
615
+ try {
616
+ return readFileSync(path, "utf8").trim();
617
+ } catch {
618
+ return "";
619
+ }
598
620
  }
599
- return false;
600
- }
601
- function readFileTrimmedOrEmpty(path) {
602
- try {
603
- return readFileSync(path, "utf8").trim();
604
- } catch {
605
- return "";
621
+ function readLockMetadata(lockDir) {
622
+ const pid = readFileTrimmedOrEmpty(join(lockDir, "pid"));
623
+ const owner = readFileTrimmedOrEmpty(join(lockDir, "owner"));
624
+ const tsRaw = readFileTrimmedOrEmpty(join(lockDir, "ts"));
625
+ const ts = tsRaw === "" ? null : Number.parseInt(tsRaw, 10);
626
+ return {
627
+ pid,
628
+ ts: ts === null || !Number.isFinite(ts) ? null : ts,
629
+ owner
630
+ };
606
631
  }
607
- }
608
- function readLockMetadata$1(lockDir) {
609
- const pid = readFileTrimmedOrEmpty(join(lockDir, "pid"));
610
- const owner = readFileTrimmedOrEmpty(join(lockDir, "owner"));
611
- const tsRaw = readFileTrimmedOrEmpty(join(lockDir, "ts"));
612
- const ts = tsRaw === "" ? null : Number.parseInt(tsRaw, 10);
613
- return {
614
- pid,
615
- ts: ts === null || !Number.isFinite(ts) ? null : ts,
616
- owner
617
- };
618
- }
619
- function isPidAlive$1(pidText) {
620
- const pid = Number.parseInt(pidText, 10);
621
- if (!Number.isFinite(pid) || pid <= 0) return false;
622
- try {
623
- process.kill(pid, 0);
624
- return true;
625
- } catch {
626
- return false;
632
+ function isPidAlive(pidText) {
633
+ const pid = Number.parseInt(pidText, 10);
634
+ if (!Number.isFinite(pid) || pid <= 0) return false;
635
+ try {
636
+ process.kill(pid, 0);
637
+ return true;
638
+ } catch {
639
+ return false;
640
+ }
627
641
  }
628
- }
629
- function lockIsStale$1(cfg) {
630
- const snapshot = readLockMetadata$1(cfg.lockDir);
631
- const nowS = Math.floor(Date.now() / 1e3);
632
- if (snapshot.pid) {
633
- if (isPidAlive$1(snapshot.pid)) {
634
- if (snapshot.ts === null) return {
635
- stale: false,
636
- snapshot
637
- };
642
+ function lockIsStale(cfg) {
643
+ const snapshot = readLockMetadata(cfg.lockDir);
644
+ const nowS = Math.floor(Date.now() / 1e3);
645
+ if (snapshot.pid) {
646
+ if (isPidAlive(snapshot.pid)) {
647
+ if (snapshot.ts === null) return {
648
+ stale: false,
649
+ snapshot
650
+ };
651
+ return {
652
+ stale: nowS - snapshot.ts > cfg.ttlSeconds,
653
+ snapshot
654
+ };
655
+ }
638
656
  return {
639
- stale: nowS - snapshot.ts > cfg.ttlSeconds,
657
+ stale: true,
640
658
  snapshot
641
659
  };
642
660
  }
643
- return {
644
- stale: true,
661
+ if (snapshot.ts !== null) return {
662
+ stale: nowS - snapshot.ts > cfg.graceSeconds,
645
663
  snapshot
646
664
  };
647
- }
648
- if (snapshot.ts !== null) return {
649
- stale: nowS - snapshot.ts > cfg.graceSeconds,
650
- snapshot
651
- };
652
- let mtimeS;
653
- try {
654
- mtimeS = Math.floor(statSync(cfg.lockDir).mtimeMs / 1e3);
655
- } catch {
665
+ let mtimeS;
666
+ try {
667
+ mtimeS = Math.floor(statSync(cfg.lockDir).mtimeMs / 1e3);
668
+ } catch {
669
+ return {
670
+ stale: true,
671
+ snapshot
672
+ };
673
+ }
656
674
  return {
657
- stale: true,
675
+ stale: nowS - mtimeS > cfg.graceSeconds,
658
676
  snapshot
659
677
  };
660
678
  }
661
- return {
662
- stale: nowS - mtimeS > cfg.graceSeconds,
663
- snapshot
664
- };
665
- }
666
- function cleanupLockDir$1(lockDir) {
667
- for (const name of [
668
- "pid",
669
- "ts",
670
- "owner"
671
- ]) try {
672
- unlinkSync(join(lockDir, name));
673
- } catch {}
674
- try {
675
- rmdirSync(lockDir);
676
- } catch {}
677
- }
678
- function snapshotsEqual(a, b) {
679
- return a.pid === b.pid && a.ts === b.ts && a.owner === b.owner;
680
- }
681
- function cleanupLockDirIfUnchanged$1(lockDir, snapshot) {
682
- if (snapshotsEqual(readLockMetadata$1(lockDir), snapshot)) cleanupLockDir$1(lockDir);
683
- }
684
- function isErrnoException$1(err) {
685
- return typeof err === "object" && err !== null && "code" in err;
686
- }
687
- async function sleep$1(ms) {
688
- return new Promise((resolve) => setTimeout(resolve, ms));
689
- }
690
- /**
691
- * Run `fn` while holding the claude-hook-ingest lock. Throws
692
- * `LockBusyError` when the lock cannot be acquired within
693
- * `LOCK_ACQUIRE_ATTEMPTS` attempts.
694
- *
695
- * The lock is a directory at `lockDir`, with three sentinel files
696
- * (`pid`, `ts`, `owner`) recording who currently holds it. Stale locks
697
- * are detected via PID liveness, TTL, and a grace window for the
698
- * race between mkdir and writing pid/ts.
699
- */
700
- async function withClaudeHookIngestLock(fn) {
701
- const cfg = lockConfig$1();
702
- mkdirSync(dirname(cfg.lockDir), { recursive: true });
703
- const ownerToken = `${process.pid}-${Math.floor(Date.now() / 1e3)}-${randomInt(1e3, 1e4)}`;
704
- let acquired = false;
705
- for (let attempt = 0; attempt < LOCK_ACQUIRE_ATTEMPTS$1; attempt++) {
679
+ function cleanupLockDir(lockDir) {
680
+ for (const name of [
681
+ "pid",
682
+ "ts",
683
+ "owner"
684
+ ]) try {
685
+ unlinkSync(join(lockDir, name));
686
+ } catch {}
706
687
  try {
707
- mkdirSync(cfg.lockDir);
708
- } catch (err) {
709
- if (isErrnoException$1(err) && err.code === "EEXIST") {
710
- const { stale, snapshot } = lockIsStale$1(cfg);
711
- if (stale) cleanupLockDirIfUnchanged$1(cfg.lockDir, snapshot);
712
- await sleep$1(LOCK_ACQUIRE_BACKOFF_MS$1);
688
+ rmdirSync(lockDir);
689
+ } catch {}
690
+ }
691
+ function snapshotsEqual(a, b) {
692
+ return a.pid === b.pid && a.ts === b.ts && a.owner === b.owner;
693
+ }
694
+ function cleanupLockDirIfUnchanged(lockDir, snapshot) {
695
+ if (snapshotsEqual(readLockMetadata(lockDir), snapshot)) cleanupLockDir(lockDir);
696
+ }
697
+ function isErrnoException(err) {
698
+ return typeof err === "object" && err !== null && "code" in err;
699
+ }
700
+ function sleep(ms) {
701
+ return new Promise((resolve) => setTimeout(resolve, ms));
702
+ }
703
+ /**
704
+ * Run `fn` while holding the client's ingest lock. Throws the
705
+ * client-specific `LockBusyError` when the lock cannot be acquired
706
+ * within `cfg.lockAcquireAttempts` attempts.
707
+ *
708
+ * The lock is a directory at `lockDir`, with three sentinel files
709
+ * (`pid`, `ts`, `owner`) recording who currently holds it. Stale locks
710
+ * are detected via PID liveness, TTL, and a grace window for the
711
+ * race between mkdir and writing pid/ts.
712
+ */
713
+ async function withLock(fn) {
714
+ const lock = lockConfig();
715
+ mkdirSync(dirname(lock.lockDir), { recursive: true });
716
+ const ownerToken = `${process.pid}-${Math.floor(Date.now() / 1e3)}-${randomInt(1e3, 1e4)}`;
717
+ let acquired = false;
718
+ for (let attempt = 0; attempt < cfg.lockAcquireAttempts; attempt++) {
719
+ try {
720
+ mkdirSync(lock.lockDir);
721
+ } catch (err) {
722
+ if (isErrnoException(err) && err.code === "EEXIST") {
723
+ const { stale, snapshot } = lockIsStale(lock);
724
+ if (stale) cleanupLockDirIfUnchanged(lock.lockDir, snapshot);
725
+ }
726
+ await sleep(LOCK_ACQUIRE_BACKOFF_MS);
713
727
  continue;
714
728
  }
715
- await sleep$1(LOCK_ACQUIRE_BACKOFF_MS$1);
716
- continue;
729
+ try {
730
+ writeFileSync(join(lock.lockDir, "ts"), String(Math.floor(Date.now() / 1e3)), { encoding: "utf8" });
731
+ writeFileSync(join(lock.lockDir, "pid"), String(process.pid), { encoding: "utf8" });
732
+ writeFileSync(join(lock.lockDir, "owner"), ownerToken, { encoding: "utf8" });
733
+ acquired = true;
734
+ break;
735
+ } catch {
736
+ cleanupLockDir(lock.lockDir);
737
+ await sleep(LOCK_ACQUIRE_BACKOFF_MS);
738
+ }
717
739
  }
740
+ if (!acquired) throw new LockBusyError();
718
741
  try {
719
- writeFileSync(join(cfg.lockDir, "ts"), String(Math.floor(Date.now() / 1e3)), { encoding: "utf8" });
720
- writeFileSync(join(cfg.lockDir, "pid"), String(process.pid), { encoding: "utf8" });
721
- writeFileSync(join(cfg.lockDir, "owner"), ownerToken, { encoding: "utf8" });
722
- acquired = true;
723
- break;
724
- } catch {
725
- cleanupLockDir$1(cfg.lockDir);
726
- await sleep$1(LOCK_ACQUIRE_BACKOFF_MS$1);
742
+ return await fn();
743
+ } finally {
744
+ if (readFileTrimmedOrEmpty(join(lock.lockDir, "owner")) === ownerToken) cleanupLockDir(lock.lockDir);
727
745
  }
728
746
  }
729
- if (!acquired) throw new LockBusyError();
730
- try {
731
- return await fn();
732
- } finally {
733
- if (readFileTrimmedOrEmpty(join(cfg.lockDir, "owner")) === ownerToken) cleanupLockDir$1(cfg.lockDir);
734
- }
735
- }
736
- /**
737
- * Persist a payload to the spool directory using a tmp+rename so that
738
- * a partially-written file is never visible to the drainer. Returns
739
- * true on success, false on any I/O failure.
740
- */
741
- function spoolPayload(payload) {
742
- const dir = spoolDir();
743
- try {
744
- mkdirSync(dir, { recursive: true });
745
- } catch {
746
- logHookEvent("codemem claude-hook-ingest failed to create spool dir");
747
- return false;
748
- }
749
- const payloadText = JSON.stringify(payload);
750
- const tmpPath = join(dir, `.hook-tmp-${process.pid}-${Date.now()}-${randomInt(1e3, 1e4)}.json`);
751
- try {
752
- writeFileSync(tmpPath, payloadText, { encoding: "utf8" });
753
- } catch {
754
- logHookEvent("codemem claude-hook-ingest failed to allocate spool temp file");
755
- return false;
756
- }
757
- const finalPath = join(dir, `hook-${Math.floor(Date.now() / 1e3)}-${process.pid}-${randomInt(1e3, 1e4)}.json`);
758
- try {
759
- renameSync(tmpPath, finalPath);
760
- } catch {
747
+ /**
748
+ * Persist a payload to the spool directory using a tmp+rename so that
749
+ * a partially-written file is never visible to the drainer. Returns
750
+ * true on success, false on any I/O failure.
751
+ */
752
+ function spoolPayload(payload) {
753
+ const dir = spoolDir();
761
754
  try {
762
- unlinkSync(tmpPath);
763
- } catch {}
764
- logHookEvent("codemem claude-hook-ingest failed to spool payload");
765
- return false;
766
- }
767
- logHookEvent(`codemem claude-hook-ingest spooled payload: ${finalPath}`);
768
- return true;
769
- }
770
- /**
771
- * Promote any `.hook-tmp-*.json` files older than `ttlSeconds` to a
772
- * recovered name so they are picked up by the next drain. Caller is
773
- * responsible for passing the same TTL used by lock acquisition so
774
- * that an in-flight write inside an active locked region is never
775
- * mistaken for a crashed-writer leftover.
776
- */
777
- function recoverStaleTmpSpool(ttlSeconds) {
778
- const dir = spoolDir();
779
- try {
780
- mkdirSync(dir, { recursive: true });
781
- } catch {
782
- return;
783
- }
784
- let entries;
785
- try {
786
- entries = readdirSync(dir);
787
- } catch {
788
- return;
789
- }
790
- const nowS = Date.now() / 1e3;
791
- for (const name of entries) {
792
- if (!name.startsWith(".hook-tmp-") || !name.endsWith(".json")) continue;
793
- const tmpPath = join(dir, name);
794
- let mtimeS;
755
+ mkdirSync(dir, { recursive: true });
756
+ } catch {
757
+ logHookEvent(`${cfg.logPrefix} failed to create spool dir`);
758
+ return false;
759
+ }
760
+ const payloadText = JSON.stringify(payload);
761
+ const tmpPath = join(dir, `.hook-tmp-${process.pid}-${Date.now()}-${randomInt(1e3, 1e4)}.json`);
795
762
  try {
796
- mtimeS = statSync(tmpPath).mtimeMs / 1e3;
763
+ writeFileSync(tmpPath, payloadText, { encoding: "utf8" });
797
764
  } catch {
798
- continue;
765
+ logHookEvent(`${cfg.logPrefix} failed to allocate spool temp file`);
766
+ return false;
799
767
  }
800
- if (nowS - mtimeS <= ttlSeconds) continue;
801
- const recoveredPath = join(dir, `hook-recovered-${Math.floor(nowS)}-${process.pid}-${randomInt(1e3, 1e4)}.json`);
768
+ const finalPath = join(dir, `hook-${Math.floor(Date.now() / 1e3)}-${process.pid}-${randomInt(1e3, 1e4)}.json`);
802
769
  try {
803
- renameSync(tmpPath, recoveredPath);
804
- logHookEvent(`codemem claude-hook-ingest recovered stale temp spool payload: ${recoveredPath}`);
805
- } catch {}
770
+ renameSync(tmpPath, finalPath);
771
+ } catch {
772
+ try {
773
+ unlinkSync(tmpPath);
774
+ } catch {}
775
+ logHookEvent(`${cfg.logPrefix} failed to spool payload`);
776
+ return false;
777
+ }
778
+ logHookEvent(`${cfg.logPrefix} spooled payload: ${finalPath}`);
779
+ return true;
806
780
  }
807
- }
808
- /**
809
- * Move a permanently-broken spool entry out of the queue so that it
810
- * stops being picked up by future drains. The entry is renamed in
811
- * place with a `.bad-<reason>-` prefix so an operator can inspect or
812
- * delete it manually.
813
- */
814
- function quarantineSpoolEntry$1(dir, name, reason) {
815
- const sourcePath = join(dir, name);
816
- const quarantineName = `.bad-${reason}-${Date.now()}-${randomInt(1e3, 1e4)}-${name}`;
817
- try {
818
- renameSync(sourcePath, join(dir, quarantineName));
819
- logHookEvent(`codemem claude-hook-ingest quarantined corrupt spool payload (${reason}): ${quarantineName}`);
820
- } catch {
781
+ /**
782
+ * Promote any `.hook-tmp-*.json` files older than `ttlSeconds` to a
783
+ * recovered name so they are picked up by the next drain. Caller is
784
+ * responsible for passing the same TTL used by lock acquisition so
785
+ * that an in-flight write inside an active locked region is never
786
+ * mistaken for a crashed-writer leftover.
787
+ */
788
+ function recoverStaleTmpSpool(ttlSeconds) {
789
+ const dir = spoolDir();
821
790
  try {
822
- unlinkSync(sourcePath);
823
- logHookEvent(`codemem claude-hook-ingest dropped corrupt spool payload (${reason}): ${name}`);
824
- } catch {}
825
- }
826
- }
827
- /**
828
- * Process every queued payload in the spool directory in lexicographic
829
- * order (which approximates oldest-first because filenames embed the
830
- * second-precision creation timestamp). The handler returns true to
831
- * indicate the payload has been durably accepted; only then is the
832
- * spool entry deleted. Failed entries are left on disk for the next
833
- * drain attempt.
834
- */
835
- async function drainSpool(handler) {
836
- const dir = spoolDir();
837
- try {
838
- mkdirSync(dir, { recursive: true });
839
- } catch {
840
- return {
841
- processed: 0,
842
- failed: 0
843
- };
844
- }
845
- let entries;
846
- try {
847
- entries = readdirSync(dir);
848
- } catch {
849
- return {
850
- processed: 0,
851
- failed: 0
852
- };
853
- }
854
- const queued = entries.filter((name) => name.endsWith(".json") && !name.startsWith(".hook-tmp-") && !name.startsWith(".bad-")).sort();
855
- const result = {
856
- processed: 0,
857
- failed: 0
858
- };
859
- for (const name of queued) {
860
- const path = join(dir, name);
861
- let raw;
791
+ mkdirSync(dir, { recursive: true });
792
+ } catch {
793
+ return;
794
+ }
795
+ let entries;
862
796
  try {
863
- raw = readFileSync(path, "utf8");
797
+ entries = readdirSync(dir);
864
798
  } catch {
865
- logHookEvent(`codemem claude-hook-ingest failed to read spooled payload: ${path}`);
866
- result.failed++;
867
- continue;
799
+ return;
868
800
  }
869
- let parsed;
801
+ const nowS = Date.now() / 1e3;
802
+ for (const name of entries) {
803
+ if (!name.startsWith(".hook-tmp-") || !name.endsWith(".json")) continue;
804
+ const tmpPath = join(dir, name);
805
+ let mtimeS;
806
+ try {
807
+ mtimeS = statSync(tmpPath).mtimeMs / 1e3;
808
+ } catch {
809
+ continue;
810
+ }
811
+ if (nowS - mtimeS <= ttlSeconds) continue;
812
+ const recoveredPath = join(dir, `hook-recovered-${Math.floor(nowS)}-${process.pid}-${randomInt(1e3, 1e4)}.json`);
813
+ try {
814
+ renameSync(tmpPath, recoveredPath);
815
+ logHookEvent(`${cfg.logPrefix} recovered stale temp spool payload: ${recoveredPath}`);
816
+ } catch {}
817
+ }
818
+ }
819
+ /**
820
+ * Move a permanently-broken spool entry out of the queue so that it
821
+ * stops being picked up by future drains. The entry is renamed in
822
+ * place with a `.bad-<reason>-` prefix so an operator can inspect or
823
+ * delete it manually.
824
+ */
825
+ function quarantineSpoolEntry(dir, name, reason) {
826
+ const sourcePath = join(dir, name);
827
+ const quarantineName = `.bad-${reason}-${Date.now()}-${randomInt(1e3, 1e4)}-${name}`;
870
828
  try {
871
- parsed = JSON.parse(raw);
829
+ renameSync(sourcePath, join(dir, quarantineName));
830
+ logHookEvent(`${cfg.logPrefix} quarantined corrupt spool payload (${reason}): ${quarantineName}`);
872
831
  } catch {
873
- quarantineSpoolEntry$1(dir, name, "parse-error");
874
- result.failed++;
875
- continue;
832
+ try {
833
+ unlinkSync(sourcePath);
834
+ logHookEvent(`${cfg.logPrefix} dropped corrupt spool payload (${reason}): ${name}`);
835
+ } catch {}
876
836
  }
877
- if (parsed == null || typeof parsed !== "object" || Array.isArray(parsed)) {
878
- quarantineSpoolEntry$1(dir, name, "wrong-shape");
879
- continue;
837
+ }
838
+ /**
839
+ * Process every queued payload in the spool directory in lexicographic
840
+ * order (which approximates oldest-first because filenames embed the
841
+ * second-precision creation timestamp). The handler returns true to
842
+ * indicate the payload has been durably accepted; only then is the
843
+ * spool entry deleted. Failed entries are left on disk for the next
844
+ * drain attempt.
845
+ */
846
+ async function drainSpool(handler) {
847
+ const dir = spoolDir();
848
+ try {
849
+ mkdirSync(dir, { recursive: true });
850
+ } catch {
851
+ return {
852
+ processed: 0,
853
+ failed: 0
854
+ };
880
855
  }
881
- let ok = false;
856
+ let entries;
882
857
  try {
883
- ok = await handler(parsed);
858
+ entries = readdirSync(dir);
884
859
  } catch {
885
- ok = false;
860
+ return {
861
+ processed: 0,
862
+ failed: 0
863
+ };
886
864
  }
887
- if (ok) try {
888
- unlinkSync(path);
889
- result.processed++;
890
- } catch {}
891
- else {
892
- logHookEvent(`codemem claude-hook-ingest failed processing spooled payload: ${path}`);
893
- result.failed++;
865
+ const queued = entries.filter((name) => name.endsWith(".json") && !name.startsWith(".hook-tmp-") && !name.startsWith(".bad-")).sort();
866
+ const result = {
867
+ processed: 0,
868
+ failed: 0
869
+ };
870
+ for (const name of queued) {
871
+ const path = join(dir, name);
872
+ let raw;
873
+ try {
874
+ raw = readFileSync(path, "utf8");
875
+ } catch {
876
+ logHookEvent(`${cfg.logPrefix} failed to read spooled payload: ${path}`);
877
+ result.failed++;
878
+ continue;
879
+ }
880
+ let parsed;
881
+ try {
882
+ parsed = JSON.parse(raw);
883
+ } catch {
884
+ quarantineSpoolEntry(dir, name, "parse-error");
885
+ result.failed++;
886
+ continue;
887
+ }
888
+ if (parsed == null || typeof parsed !== "object" || Array.isArray(parsed)) {
889
+ quarantineSpoolEntry(dir, name, "wrong-shape");
890
+ result.failed++;
891
+ continue;
892
+ }
893
+ let ok = false;
894
+ try {
895
+ ok = await handler(parsed);
896
+ } catch {
897
+ ok = false;
898
+ }
899
+ if (ok) try {
900
+ unlinkSync(path);
901
+ result.processed++;
902
+ } catch {}
903
+ else {
904
+ logHookEvent(`${cfg.logPrefix} failed processing spooled payload: ${path}`);
905
+ result.failed++;
906
+ }
894
907
  }
908
+ return result;
895
909
  }
896
- return result;
910
+ return {
911
+ LockBusyError,
912
+ spoolDir,
913
+ lockTtlSeconds,
914
+ hasSpooledEntries,
915
+ withLock,
916
+ spoolPayload,
917
+ recoverStaleTmpSpool,
918
+ drainSpool
919
+ };
897
920
  }
921
+ //#endregion
922
+ //#region src/commands/claude-hook-ingest-spool.ts
923
+ /**
924
+ * Durability layer for `claude-hook-ingest`. The lock/spool/drain/
925
+ * quarantine machinery is shared in `hook-ingest-spool.ts`; this file
926
+ * wires the claude-specific config (dirs, TTL 300s, 100 acquire
927
+ * attempts, error name/message) and keeps the claude flush predicate.
928
+ */
929
+ var spool$1 = createHookIngestSpool({
930
+ logPrefix: "codemem claude-hook-ingest",
931
+ lockDirEnv: "CODEMEM_CLAUDE_HOOK_LOCK_DIR",
932
+ lockDirDefault: "~/.codemem/claude-hook-ingest.lock",
933
+ lockTtlEnv: "CODEMEM_CLAUDE_HOOK_LOCK_TTL_S",
934
+ lockTtlDefault: 300,
935
+ lockGraceEnv: "CODEMEM_CLAUDE_HOOK_LOCK_GRACE_S",
936
+ lockGraceDefault: 2,
937
+ lockAcquireAttempts: 100,
938
+ spoolDirEnv: "CODEMEM_CLAUDE_HOOK_SPOOL_DIR",
939
+ spoolDirDefault: "~/.codemem/claude-hook-spool",
940
+ lockBusyErrorName: "LockBusyError",
941
+ lockBusyErrorMessage: "claude-hook-ingest lock busy"
942
+ });
943
+ var LockBusyError = spool$1.LockBusyError;
944
+ var withClaudeHookIngestLock = spool$1.withLock;
945
+ var spoolPayload = spool$1.spoolPayload;
946
+ var drainSpool = spool$1.drainSpool;
947
+ var hasSpooledEntries = spool$1.hasSpooledEntries;
948
+ var recoverStaleTmpSpool = spool$1.recoverStaleTmpSpool;
949
+ var lockTtlSeconds = spool$1.lockTtlSeconds;
950
+ spool$1.spoolDir;
898
951
  /**
899
952
  * Whether the boundary-flush write-through should run for this hook
900
953
  * payload. SessionEnd defaults to forcing a flush; Stop only flushes
@@ -908,14 +961,6 @@ function shouldForceBoundaryFlush(payload) {
908
961
  if (!envTruthy$2("CODEMEM_CLAUDE_HOOK_FLUSH", false)) return false;
909
962
  return envTruthy$2("CODEMEM_CLAUDE_HOOK_FLUSH_ON_STOP", false);
910
963
  }
911
- /**
912
- * Returns the configured lock TTL so callers (`claude-hook-ingest`)
913
- * can pass the same value to `recoverStaleTmpSpool` without re-reading
914
- * the env.
915
- */
916
- function lockTtlSeconds() {
917
- return lockConfig$1().ttlSeconds;
918
- }
919
964
  //#endregion
920
965
  //#region src/commands/claude-hook-session-state.ts
921
966
  /**
@@ -945,14 +990,14 @@ function defaultSessionState() {
945
990
  updated_at: ""
946
991
  };
947
992
  }
948
- function expandHome$1(value) {
993
+ function expandHome(value) {
949
994
  if (value === "~") return homedir();
950
995
  if (value.startsWith("~/")) return join(homedir(), value.slice(2));
951
996
  return value;
952
997
  }
953
998
  function contextDir() {
954
999
  const override = process.env.CODEMEM_CLAUDE_HOOK_CONTEXT_DIR;
955
- return expandHome$1(override?.trim() ? override : "~/.codemem/claude-hook-context");
1000
+ return expandHome(override?.trim() ? override : "~/.codemem/claude-hook-context");
956
1001
  }
957
1002
  function sessionFileStem(sessionId) {
958
1003
  const trimmed = sessionId.trim();
@@ -1381,8 +1426,8 @@ async function ingestClaudeHookPayload(payload, opts, deps = {}) {
1381
1426
  };
1382
1427
  const httpResult = await httpIngest(httpPayload(payload), opts.host, port);
1383
1428
  if (httpResult.ok) {
1384
- await flushOnBoundaryIfRequested();
1385
1429
  await drainBacklogIfPresent();
1430
+ await flushOnBoundaryIfRequested();
1386
1431
  return {
1387
1432
  inserted: httpResult.inserted,
1388
1433
  skipped: httpResult.skipped,
@@ -1622,273 +1667,34 @@ var claudeHookInjectCommand = claudeHookInjectCmd.action(async (opts) => {
1622
1667
  });
1623
1668
  //#endregion
1624
1669
  //#region src/commands/codex-hook-ingest-spool.ts
1625
- var DEFAULT_LOCK_TTL_S = 120;
1626
- var DEFAULT_LOCK_GRACE_S = 2;
1627
- var LOCK_ACQUIRE_ATTEMPTS = 20;
1628
- var LOCK_ACQUIRE_BACKOFF_MS = 50;
1629
- var CodexHookLockBusyError = class extends Error {
1630
- constructor() {
1631
- super("codex-hook-ingest lock busy");
1632
- this.name = "CodexHookLockBusyError";
1633
- }
1634
- };
1635
- function expandHome(value) {
1636
- if (value === "~") return homedir();
1637
- if (value.startsWith("~/")) return join(homedir(), value.slice(2));
1638
- return value;
1639
- }
1640
- function envInt(name, fallback) {
1641
- const parsed = Number.parseInt(process.env[name] ?? "", 10);
1642
- return Number.isFinite(parsed) ? parsed : fallback;
1643
- }
1644
- function lockConfig() {
1645
- return {
1646
- lockDir: expandHome(process.env.CODEMEM_CODEX_HOOK_LOCK_DIR?.trim() || "~/.codemem/codex-hook-ingest.lock"),
1647
- ttlSeconds: Math.max(1, envInt("CODEMEM_CODEX_HOOK_LOCK_TTL_S", DEFAULT_LOCK_TTL_S)),
1648
- graceSeconds: Math.max(1, envInt("CODEMEM_CODEX_HOOK_LOCK_GRACE_S", DEFAULT_LOCK_GRACE_S))
1649
- };
1650
- }
1651
- function codexHookSpoolDir() {
1652
- return expandHome(process.env.CODEMEM_CODEX_HOOK_SPOOL_DIR?.trim() || "~/.codemem/codex-hook-spool");
1653
- }
1654
- function codexHookLockTtlSeconds() {
1655
- return lockConfig().ttlSeconds;
1656
- }
1657
- function hasCodexHookSpooledEntries() {
1658
- let entries;
1659
- try {
1660
- entries = readdirSync(codexHookSpoolDir());
1661
- } catch {
1662
- return false;
1663
- }
1664
- return entries.some((name) => name.endsWith(".json") && !name.startsWith(".hook-tmp-") && !name.startsWith(".bad-"));
1665
- }
1666
- function readTrimmed(path) {
1667
- try {
1668
- return readFileSync(path, "utf8").trim();
1669
- } catch {
1670
- return "";
1671
- }
1672
- }
1673
- function readLockMetadata(lockDir) {
1674
- const rawTs = readTrimmed(join(lockDir, "ts"));
1675
- const ts = rawTs === "" ? null : Number.parseInt(rawTs, 10);
1676
- return {
1677
- pid: readTrimmed(join(lockDir, "pid")),
1678
- ts: ts === null || !Number.isFinite(ts) ? null : ts,
1679
- owner: readTrimmed(join(lockDir, "owner"))
1680
- };
1681
- }
1682
- function isPidAlive(pidText) {
1683
- const pid = Number.parseInt(pidText, 10);
1684
- if (!Number.isFinite(pid) || pid <= 0) return false;
1685
- try {
1686
- process.kill(pid, 0);
1687
- return true;
1688
- } catch {
1689
- return false;
1690
- }
1691
- }
1692
- function lockIsStale(cfg) {
1693
- const snapshot = readLockMetadata(cfg.lockDir);
1694
- const nowS = Math.floor(Date.now() / 1e3);
1695
- if (snapshot.pid) {
1696
- if (isPidAlive(snapshot.pid)) return {
1697
- stale: snapshot.ts !== null && nowS - snapshot.ts > cfg.ttlSeconds,
1698
- snapshot
1699
- };
1700
- return {
1701
- stale: true,
1702
- snapshot
1703
- };
1704
- }
1705
- if (snapshot.ts !== null) return {
1706
- stale: nowS - snapshot.ts > cfg.graceSeconds,
1707
- snapshot
1708
- };
1709
- try {
1710
- return {
1711
- stale: nowS - Math.floor(statSync(cfg.lockDir).mtimeMs / 1e3) > cfg.graceSeconds,
1712
- snapshot
1713
- };
1714
- } catch {
1715
- return {
1716
- stale: true,
1717
- snapshot
1718
- };
1719
- }
1720
- }
1721
- function cleanupLockDir(lockDir) {
1722
- for (const name of [
1723
- "pid",
1724
- "ts",
1725
- "owner"
1726
- ]) try {
1727
- unlinkSync(join(lockDir, name));
1728
- } catch {}
1729
- try {
1730
- rmdirSync(lockDir);
1731
- } catch {}
1732
- }
1733
- function cleanupLockDirIfUnchanged(lockDir, snapshot) {
1734
- const current = readLockMetadata(lockDir);
1735
- if (current.pid === snapshot.pid && current.ts === snapshot.ts && current.owner === snapshot.owner) cleanupLockDir(lockDir);
1736
- }
1737
- function isErrnoException(err) {
1738
- return typeof err === "object" && err !== null && "code" in err;
1739
- }
1740
- function sleep(ms) {
1741
- return new Promise((resolve) => setTimeout(resolve, ms));
1742
- }
1743
- async function withCodexHookIngestLock(fn) {
1744
- const cfg = lockConfig();
1745
- mkdirSync(dirname(cfg.lockDir), { recursive: true });
1746
- const ownerToken = `${process.pid}-${Math.floor(Date.now() / 1e3)}-${randomInt(1e3, 1e4)}`;
1747
- let acquired = false;
1748
- for (let attempt = 0; attempt < LOCK_ACQUIRE_ATTEMPTS; attempt++) {
1749
- try {
1750
- mkdirSync(cfg.lockDir);
1751
- } catch (err) {
1752
- if (isErrnoException(err) && err.code === "EEXIST") {
1753
- const { stale, snapshot } = lockIsStale(cfg);
1754
- if (stale) cleanupLockDirIfUnchanged(cfg.lockDir, snapshot);
1755
- }
1756
- await sleep(LOCK_ACQUIRE_BACKOFF_MS);
1757
- continue;
1758
- }
1759
- try {
1760
- writeFileSync(join(cfg.lockDir, "ts"), String(Math.floor(Date.now() / 1e3)), "utf8");
1761
- writeFileSync(join(cfg.lockDir, "pid"), String(process.pid), "utf8");
1762
- writeFileSync(join(cfg.lockDir, "owner"), ownerToken, "utf8");
1763
- acquired = true;
1764
- break;
1765
- } catch {
1766
- cleanupLockDir(cfg.lockDir);
1767
- await sleep(LOCK_ACQUIRE_BACKOFF_MS);
1768
- }
1769
- }
1770
- if (!acquired) throw new CodexHookLockBusyError();
1771
- try {
1772
- return await fn();
1773
- } finally {
1774
- if (readTrimmed(join(cfg.lockDir, "owner")) === ownerToken) cleanupLockDir(cfg.lockDir);
1775
- }
1776
- }
1777
- function spoolCodexHookPayload(payload) {
1778
- const dir = codexHookSpoolDir();
1779
- try {
1780
- mkdirSync(dir, { recursive: true });
1781
- } catch {
1782
- logHookEvent("codemem codex-hook-ingest failed to create spool dir");
1783
- return false;
1784
- }
1785
- const tmpPath = join(dir, `.hook-tmp-${process.pid}-${Date.now()}-${randomInt(1e3, 1e4)}.json`);
1786
- try {
1787
- writeFileSync(tmpPath, JSON.stringify(payload), "utf8");
1788
- } catch {
1789
- logHookEvent("codemem codex-hook-ingest failed to allocate spool temp file");
1790
- return false;
1791
- }
1792
- const finalPath = join(dir, `hook-${Math.floor(Date.now() / 1e3)}-${process.pid}-${randomInt(1e3, 1e4)}.json`);
1793
- try {
1794
- renameSync(tmpPath, finalPath);
1795
- } catch {
1796
- try {
1797
- unlinkSync(tmpPath);
1798
- } catch {}
1799
- logHookEvent("codemem codex-hook-ingest failed to spool payload");
1800
- return false;
1801
- }
1802
- logHookEvent(`codemem codex-hook-ingest spooled payload: ${finalPath}`);
1803
- return true;
1804
- }
1805
- function recoverStaleCodexHookTmpSpool(ttlSeconds) {
1806
- const dir = codexHookSpoolDir();
1807
- try {
1808
- mkdirSync(dir, { recursive: true });
1809
- } catch {
1810
- return;
1811
- }
1812
- let entries;
1813
- try {
1814
- entries = readdirSync(dir);
1815
- } catch {
1816
- return;
1817
- }
1818
- const nowS = Date.now() / 1e3;
1819
- for (const name of entries) {
1820
- if (!name.startsWith(".hook-tmp-") || !name.endsWith(".json")) continue;
1821
- const tmpPath = join(dir, name);
1822
- try {
1823
- if (nowS - statSync(tmpPath).mtimeMs / 1e3 <= ttlSeconds) continue;
1824
- renameSync(tmpPath, join(dir, `hook-recovered-${Math.floor(nowS)}-${process.pid}-${randomInt(1e3, 1e4)}.json`));
1825
- } catch {}
1826
- }
1827
- }
1828
- function quarantineSpoolEntry(dir, name, reason) {
1829
- try {
1830
- renameSync(join(dir, name), join(dir, `.bad-${reason}-${Date.now()}-${randomInt(1e3, 1e4)}-${name}`));
1831
- } catch {
1832
- try {
1833
- unlinkSync(join(dir, name));
1834
- } catch {}
1835
- }
1836
- }
1837
- async function drainCodexHookSpool(handler) {
1838
- const dir = codexHookSpoolDir();
1839
- try {
1840
- mkdirSync(dir, { recursive: true });
1841
- } catch {
1842
- return {
1843
- processed: 0,
1844
- failed: 0
1845
- };
1846
- }
1847
- let entries;
1848
- try {
1849
- entries = readdirSync(dir);
1850
- } catch {
1851
- return {
1852
- processed: 0,
1853
- failed: 0
1854
- };
1855
- }
1856
- const result = {
1857
- processed: 0,
1858
- failed: 0
1859
- };
1860
- for (const name of entries.filter((entry) => entry.endsWith(".json") && !entry.startsWith(".hook-tmp-") && !entry.startsWith(".bad-")).sort()) {
1861
- const path = join(dir, name);
1862
- let parsed;
1863
- try {
1864
- parsed = JSON.parse(readFileSync(path, "utf8"));
1865
- } catch {
1866
- quarantineSpoolEntry(dir, name, "parse-error");
1867
- result.failed++;
1868
- continue;
1869
- }
1870
- if (parsed == null || typeof parsed !== "object" || Array.isArray(parsed)) {
1871
- quarantineSpoolEntry(dir, name, "wrong-shape");
1872
- result.failed++;
1873
- continue;
1874
- }
1875
- let ok = false;
1876
- try {
1877
- ok = await handler(parsed);
1878
- } catch {
1879
- ok = false;
1880
- }
1881
- if (!ok) {
1882
- result.failed++;
1883
- continue;
1884
- }
1885
- try {
1886
- unlinkSync(path);
1887
- result.processed++;
1888
- } catch {}
1889
- }
1890
- return result;
1891
- }
1670
+ /**
1671
+ * Durability layer for `codex-hook-ingest`. The lock/spool/drain/
1672
+ * quarantine machinery is shared in `hook-ingest-spool.ts`; this file
1673
+ * wires the codex-specific config (dirs, TTL 120s, 20 acquire attempts,
1674
+ * error name/message).
1675
+ */
1676
+ var spool = createHookIngestSpool({
1677
+ logPrefix: "codemem codex-hook-ingest",
1678
+ lockDirEnv: "CODEMEM_CODEX_HOOK_LOCK_DIR",
1679
+ lockDirDefault: "~/.codemem/codex-hook-ingest.lock",
1680
+ lockTtlEnv: "CODEMEM_CODEX_HOOK_LOCK_TTL_S",
1681
+ lockTtlDefault: 120,
1682
+ lockGraceEnv: "CODEMEM_CODEX_HOOK_LOCK_GRACE_S",
1683
+ lockGraceDefault: 2,
1684
+ lockAcquireAttempts: 20,
1685
+ spoolDirEnv: "CODEMEM_CODEX_HOOK_SPOOL_DIR",
1686
+ spoolDirDefault: "~/.codemem/codex-hook-spool",
1687
+ lockBusyErrorName: "CodexHookLockBusyError",
1688
+ lockBusyErrorMessage: "codex-hook-ingest lock busy"
1689
+ });
1690
+ var CodexHookLockBusyError = spool.LockBusyError;
1691
+ var withCodexHookIngestLock = spool.withLock;
1692
+ var spoolCodexHookPayload = spool.spoolPayload;
1693
+ var drainCodexHookSpool = spool.drainSpool;
1694
+ var hasCodexHookSpooledEntries = spool.hasSpooledEntries;
1695
+ var recoverStaleCodexHookTmpSpool = spool.recoverStaleTmpSpool;
1696
+ var codexHookLockTtlSeconds = spool.lockTtlSeconds;
1697
+ spool.spoolDir;
1892
1698
  //#endregion
1893
1699
  //#region src/commands/codex-hook-ingest.ts
1894
1700
  /**