@deeplake/hivemind 0.7.31 → 0.7.32

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.
@@ -54,13 +54,13 @@ var init_index_marker_store = __esm({
54
54
 
55
55
  // dist/src/utils/stdin.js
56
56
  function readStdin() {
57
- return new Promise((resolve, reject) => {
57
+ return new Promise((resolve2, reject) => {
58
58
  let data = "";
59
59
  process.stdin.setEncoding("utf-8");
60
60
  process.stdin.on("data", (chunk) => data += chunk);
61
61
  process.stdin.on("end", () => {
62
62
  try {
63
- resolve(JSON.parse(data));
63
+ resolve2(JSON.parse(data));
64
64
  } catch (err) {
65
65
  reject(new Error(`Failed to parse hook input: ${err}`));
66
66
  }
@@ -176,7 +176,7 @@ function getQueryTimeoutMs() {
176
176
  return Number(process.env.HIVEMIND_QUERY_TIMEOUT_MS ?? 1e4);
177
177
  }
178
178
  function sleep(ms) {
179
- return new Promise((resolve) => setTimeout(resolve, ms));
179
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
180
180
  }
181
181
  function isTimeoutError(error) {
182
182
  const name = error instanceof Error ? error.name.toLowerCase() : "";
@@ -206,7 +206,7 @@ var Semaphore = class {
206
206
  this.active++;
207
207
  return;
208
208
  }
209
- await new Promise((resolve) => this.waiting.push(resolve));
209
+ await new Promise((resolve2) => this.waiting.push(resolve2));
210
210
  }
211
211
  release() {
212
212
  this.active--;
@@ -570,9 +570,9 @@ function buildSessionPath(config, sessionId) {
570
570
  // dist/src/embeddings/client.js
571
571
  import { connect } from "node:net";
572
572
  import { spawn } from "node:child_process";
573
- import { openSync, closeSync, writeSync, unlinkSync, existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
574
- import { homedir as homedir3 } from "node:os";
575
- import { join as join4 } from "node:path";
573
+ import { openSync as openSync2, closeSync as closeSync2, writeSync, unlinkSync as unlinkSync2, existsSync as existsSync4, readFileSync as readFileSync5 } from "node:fs";
574
+ import { homedir as homedir6 } from "node:os";
575
+ import { join as join7 } from "node:path";
576
576
 
577
577
  // dist/src/embeddings/protocol.js
578
578
  var DEFAULT_SOCKET_DIR = "/tmp";
@@ -585,13 +585,234 @@ function pidPathFor(uid, dir = DEFAULT_SOCKET_DIR) {
585
585
  return `${dir}/hivemind-embed-${uid}.pid`;
586
586
  }
587
587
 
588
+ // dist/src/notifications/queue.js
589
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, renameSync, mkdirSync as mkdirSync2, openSync, closeSync, unlinkSync, statSync } from "node:fs";
590
+ import { join as join4, resolve } from "node:path";
591
+ import { homedir as homedir3 } from "node:os";
592
+ import { setTimeout as sleep2 } from "node:timers/promises";
593
+ var log3 = (msg) => log("notifications-queue", msg);
594
+ var LOCK_RETRY_MAX = 50;
595
+ var LOCK_RETRY_BASE_MS = 5;
596
+ var LOCK_STALE_MS = 5e3;
597
+ function queuePath() {
598
+ return join4(homedir3(), ".deeplake", "notifications-queue.json");
599
+ }
600
+ function lockPath() {
601
+ return `${queuePath()}.lock`;
602
+ }
603
+ function readQueue() {
604
+ try {
605
+ const raw = readFileSync3(queuePath(), "utf-8");
606
+ const parsed = JSON.parse(raw);
607
+ if (!parsed || !Array.isArray(parsed.queue)) {
608
+ log3(`queue malformed \u2192 treating as empty`);
609
+ return { queue: [] };
610
+ }
611
+ return { queue: parsed.queue };
612
+ } catch {
613
+ return { queue: [] };
614
+ }
615
+ }
616
+ function _isQueuePathInsideHome(path, home) {
617
+ const r = resolve(path);
618
+ const h = resolve(home);
619
+ return r.startsWith(h + "/") || r === h;
620
+ }
621
+ function writeQueue(q) {
622
+ const path = queuePath();
623
+ const home = resolve(homedir3());
624
+ if (!_isQueuePathInsideHome(path, home)) {
625
+ throw new Error(`notifications-queue write blocked: ${path} is outside ${home}`);
626
+ }
627
+ mkdirSync2(join4(home, ".deeplake"), { recursive: true, mode: 448 });
628
+ const tmp = `${path}.${process.pid}.tmp`;
629
+ writeFileSync2(tmp, JSON.stringify(q, null, 2), { mode: 384 });
630
+ renameSync(tmp, path);
631
+ }
632
+ async function withQueueLock(fn) {
633
+ const path = lockPath();
634
+ mkdirSync2(join4(homedir3(), ".deeplake"), { recursive: true, mode: 448 });
635
+ let fd = null;
636
+ for (let attempt = 0; attempt < LOCK_RETRY_MAX; attempt++) {
637
+ try {
638
+ fd = openSync(path, "wx", 384);
639
+ break;
640
+ } catch (e) {
641
+ const code = e.code;
642
+ if (code !== "EEXIST")
643
+ throw e;
644
+ try {
645
+ const age = Date.now() - statSync(path).mtimeMs;
646
+ if (age > LOCK_STALE_MS) {
647
+ unlinkSync(path);
648
+ continue;
649
+ }
650
+ } catch {
651
+ }
652
+ const delay = LOCK_RETRY_BASE_MS * (attempt + 1);
653
+ await sleep2(delay);
654
+ }
655
+ }
656
+ if (fd === null) {
657
+ log3(`lock acquisition gave up after ${LOCK_RETRY_MAX} attempts \u2014 proceeding unlocked (last-writer-wins)`);
658
+ return fn();
659
+ }
660
+ try {
661
+ return fn();
662
+ } finally {
663
+ try {
664
+ closeSync(fd);
665
+ } catch {
666
+ }
667
+ try {
668
+ unlinkSync(path);
669
+ } catch {
670
+ }
671
+ }
672
+ }
673
+ function sameDedupKey(a, b) {
674
+ if (a.id !== b.id)
675
+ return false;
676
+ return JSON.stringify(a.dedupKey) === JSON.stringify(b.dedupKey);
677
+ }
678
+ async function enqueueNotification(n) {
679
+ await withQueueLock(() => {
680
+ const q = readQueue();
681
+ if (q.queue.some((existing) => sameDedupKey(existing, n))) {
682
+ return;
683
+ }
684
+ q.queue.push(n);
685
+ writeQueue(q);
686
+ });
687
+ }
688
+
689
+ // dist/src/embeddings/disable.js
690
+ import { createRequire } from "node:module";
691
+ import { homedir as homedir5 } from "node:os";
692
+ import { join as join6 } from "node:path";
693
+ import { pathToFileURL } from "node:url";
694
+
695
+ // dist/src/user-config.js
696
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync4, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "node:fs";
697
+ import { homedir as homedir4 } from "node:os";
698
+ import { dirname, join as join5 } from "node:path";
699
+ var _configPath = () => process.env.HIVEMIND_CONFIG_PATH ?? join5(homedir4(), ".deeplake", "config.json");
700
+ var _cache = null;
701
+ var _migrated = false;
702
+ function readUserConfig() {
703
+ if (_cache !== null)
704
+ return _cache;
705
+ const path = _configPath();
706
+ if (!existsSync3(path)) {
707
+ _cache = {};
708
+ return _cache;
709
+ }
710
+ try {
711
+ const raw = readFileSync4(path, "utf-8");
712
+ const parsed = JSON.parse(raw);
713
+ _cache = isPlainObject(parsed) ? parsed : {};
714
+ } catch {
715
+ _cache = {};
716
+ }
717
+ return _cache;
718
+ }
719
+ function writeUserConfig(patch) {
720
+ const current = readUserConfig();
721
+ const merged = deepMerge(current, patch);
722
+ const path = _configPath();
723
+ const dir = dirname(path);
724
+ if (!existsSync3(dir))
725
+ mkdirSync3(dir, { recursive: true });
726
+ const tmp = `${path}.tmp.${process.pid}`;
727
+ writeFileSync3(tmp, JSON.stringify(merged, null, 2) + "\n", "utf-8");
728
+ renameSync2(tmp, path);
729
+ _cache = merged;
730
+ return merged;
731
+ }
732
+ function getEmbeddingsEnabled() {
733
+ const cfg = readUserConfig();
734
+ if (cfg.embeddings && typeof cfg.embeddings.enabled === "boolean") {
735
+ return cfg.embeddings.enabled;
736
+ }
737
+ if (_migrated) {
738
+ return migrationValueFromEnv();
739
+ }
740
+ _migrated = true;
741
+ const enabled = migrationValueFromEnv();
742
+ try {
743
+ writeUserConfig({ embeddings: { enabled } });
744
+ } catch {
745
+ _cache = { ...cfg ?? {}, embeddings: { ...cfg?.embeddings ?? {}, enabled } };
746
+ }
747
+ return enabled;
748
+ }
749
+ function migrationValueFromEnv() {
750
+ const raw = process.env.HIVEMIND_EMBEDDINGS;
751
+ if (raw === void 0)
752
+ return false;
753
+ if (raw === "false")
754
+ return false;
755
+ return true;
756
+ }
757
+ function isPlainObject(value) {
758
+ return typeof value === "object" && value !== null && !Array.isArray(value);
759
+ }
760
+ function deepMerge(base, patch) {
761
+ const out = { ...base };
762
+ for (const key of Object.keys(patch)) {
763
+ const patchVal = patch[key];
764
+ const baseVal = base[key];
765
+ if (isPlainObject(patchVal) && isPlainObject(baseVal)) {
766
+ out[key] = { ...baseVal, ...patchVal };
767
+ } else if (patchVal !== void 0) {
768
+ out[key] = patchVal;
769
+ }
770
+ }
771
+ return out;
772
+ }
773
+
774
+ // dist/src/embeddings/disable.js
775
+ var cachedStatus = null;
776
+ function defaultResolveTransformers() {
777
+ const sharedDir = join6(homedir5(), ".hivemind", "embed-deps");
778
+ try {
779
+ createRequire(pathToFileURL(`${sharedDir}/`).href).resolve("@huggingface/transformers");
780
+ return;
781
+ } catch {
782
+ }
783
+ createRequire(import.meta.url).resolve("@huggingface/transformers");
784
+ }
785
+ var _resolve = defaultResolveTransformers;
786
+ var _readEnabled = getEmbeddingsEnabled;
787
+ function detectStatus() {
788
+ if (!_readEnabled())
789
+ return "user-disabled";
790
+ try {
791
+ _resolve();
792
+ return "enabled";
793
+ } catch {
794
+ return "no-transformers";
795
+ }
796
+ }
797
+ function embeddingsStatus() {
798
+ if (cachedStatus !== null)
799
+ return cachedStatus;
800
+ cachedStatus = detectStatus();
801
+ return cachedStatus;
802
+ }
803
+ function embeddingsDisabled() {
804
+ return embeddingsStatus() !== "enabled";
805
+ }
806
+
588
807
  // dist/src/embeddings/client.js
589
- var SHARED_DAEMON_PATH = join4(homedir3(), ".hivemind", "embed-deps", "embed-daemon.js");
590
- var log3 = (m) => log("embed-client", m);
808
+ var SHARED_DAEMON_PATH = join7(homedir6(), ".hivemind", "embed-deps", "embed-daemon.js");
809
+ var log4 = (m) => log("embed-client", m);
591
810
  function getUid() {
592
811
  const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
593
812
  return uid !== void 0 ? String(uid) : process.env.USER ?? "default";
594
813
  }
814
+ var _signalledMissingDeps = false;
815
+ var _recycledStuckDaemon = false;
595
816
  var EmbedClient = class {
596
817
  socketPath;
597
818
  pidPath;
@@ -600,13 +821,14 @@ var EmbedClient = class {
600
821
  autoSpawn;
601
822
  spawnWaitMs;
602
823
  nextId = 0;
824
+ helloVerified = false;
603
825
  constructor(opts = {}) {
604
826
  const uid = getUid();
605
827
  const dir = opts.socketDir ?? "/tmp";
606
828
  this.socketPath = socketPathFor(uid, dir);
607
829
  this.pidPath = pidPathFor(uid, dir);
608
830
  this.timeoutMs = opts.timeoutMs ?? DEFAULT_CLIENT_TIMEOUT_MS;
609
- this.daemonEntry = opts.daemonEntry ?? process.env.HIVEMIND_EMBED_DAEMON ?? (existsSync3(SHARED_DAEMON_PATH) ? SHARED_DAEMON_PATH : void 0);
831
+ this.daemonEntry = opts.daemonEntry ?? process.env.HIVEMIND_EMBED_DAEMON ?? (existsSync4(SHARED_DAEMON_PATH) ? SHARED_DAEMON_PATH : void 0);
610
832
  this.autoSpawn = opts.autoSpawn ?? true;
611
833
  this.spawnWaitMs = opts.spawnWaitMs ?? 5e3;
612
834
  }
@@ -616,8 +838,33 @@ var EmbedClient = class {
616
838
  *
617
839
  * Fire-and-forget spawn on miss: if the daemon isn't up, this call returns
618
840
  * null AND kicks off a background spawn. The next call finds a ready daemon.
841
+ *
842
+ * Stuck-daemon recycle: if the daemon returns a transformers-missing
843
+ * error (typical after a marketplace upgrade left an older daemon process
844
+ * alive but with no node_modules accessible from its bundle path), we
845
+ * SIGTERM it and clear its sock/pid so the very next call spawns a fresh
846
+ * daemon from the current bundle. Without this, the stuck daemon would
847
+ * keep poisoning every session until its 10-minute idle-out fires.
619
848
  */
620
849
  async embed(text, kind = "document") {
850
+ const v = await this.embedAttempt(text, kind);
851
+ if (v !== "recycled")
852
+ return v;
853
+ if (!this.autoSpawn)
854
+ return null;
855
+ this.trySpawnDaemon();
856
+ await this.waitForDaemonReady();
857
+ const retry = await this.embedAttempt(text, kind);
858
+ return retry === "recycled" ? null : retry;
859
+ }
860
+ /**
861
+ * One round-trip: connect → verify → embed. Returns:
862
+ * - number[] : embedding vector (happy path)
863
+ * - null : timeout / daemon error / transformers-missing
864
+ * - "recycled": verifyDaemonOnce killed the daemon mid-call;
865
+ * caller should respawn and retry once.
866
+ */
867
+ async embedAttempt(text, kind) {
621
868
  let sock;
622
869
  try {
623
870
  sock = await this.connectOnce();
@@ -627,17 +874,25 @@ var EmbedClient = class {
627
874
  return null;
628
875
  }
629
876
  try {
877
+ const recycled = await this.verifyDaemonOnce(sock);
878
+ if (recycled) {
879
+ return "recycled";
880
+ }
630
881
  const id = String(++this.nextId);
631
882
  const req = { op: "embed", id, kind, text };
632
883
  const resp = await this.sendAndWait(sock, req);
633
884
  if (resp.error || !("embedding" in resp) || !resp.embedding) {
634
- log3(`embed err: ${resp.error ?? "no embedding"}`);
885
+ const err = resp.error ?? "no embedding";
886
+ log4(`embed err: ${err}`);
887
+ if (isTransformersMissingError(err)) {
888
+ this.handleTransformersMissing(err);
889
+ }
635
890
  return null;
636
891
  }
637
892
  return resp.embedding;
638
893
  } catch (e) {
639
894
  const err = e instanceof Error ? e.message : String(e);
640
- log3(`embed failed: ${err}`);
895
+ log4(`embed failed: ${err}`);
641
896
  return null;
642
897
  } finally {
643
898
  try {
@@ -646,6 +901,139 @@ var EmbedClient = class {
646
901
  }
647
902
  }
648
903
  }
904
+ /**
905
+ * Poll for the sock file to come back after `trySpawnDaemon` — used by
906
+ * the recycle retry path. Best-effort: caps at `spawnWaitMs` and
907
+ * returns regardless so the retry attempt can run.
908
+ */
909
+ async waitForDaemonReady() {
910
+ const deadline = Date.now() + this.spawnWaitMs;
911
+ while (Date.now() < deadline) {
912
+ if (existsSync4(this.socketPath))
913
+ return;
914
+ await new Promise((r) => setTimeout(r, 50));
915
+ }
916
+ }
917
+ /**
918
+ * Send a `hello` on first successful connect per EmbedClient instance.
919
+ * If the daemon answers with a path that doesn't match our configured
920
+ * daemonEntry — typical after a marketplace upgrade replaced the bundle
921
+ * — SIGTERM the daemon + clear sock/pid so the next call spawns from the
922
+ * current bundle.
923
+ *
924
+ * `helloVerified` is set ONLY after we've seen a compatible response,
925
+ * so a transient probe failure or a recycle-triggering mismatch leaves
926
+ * the flag false; the next reconnect re-runs verification against
927
+ * whatever daemon is then live (typically the fresh spawn).
928
+ */
929
+ async verifyDaemonOnce(sock) {
930
+ if (this.helloVerified)
931
+ return false;
932
+ if (!this.daemonEntry) {
933
+ this.helloVerified = true;
934
+ return false;
935
+ }
936
+ const id = String(++this.nextId);
937
+ const req = { op: "hello", id };
938
+ let resp;
939
+ try {
940
+ resp = await this.sendAndWait(sock, req);
941
+ } catch (e) {
942
+ log4(`hello probe failed (inconclusive, will retry next connect): ${e instanceof Error ? e.message : String(e)}`);
943
+ return false;
944
+ }
945
+ const hello = resp;
946
+ if (_recycledStuckDaemon) {
947
+ return false;
948
+ }
949
+ if (!hello.daemonPath) {
950
+ _recycledStuckDaemon = true;
951
+ log4(`daemon does not implement hello (older protocol); recycling`);
952
+ this.recycleDaemon(hello.pid);
953
+ return true;
954
+ }
955
+ if (hello.daemonPath !== this.daemonEntry && !existsSync4(hello.daemonPath)) {
956
+ _recycledStuckDaemon = true;
957
+ log4(`daemon path no longer on disk \u2014 running=${hello.daemonPath} (gone) expected=${this.daemonEntry}; recycling`);
958
+ this.recycleDaemon(hello.pid);
959
+ return true;
960
+ }
961
+ this.helloVerified = true;
962
+ return false;
963
+ }
964
+ /**
965
+ * On a transformers-missing error from the daemon, SIGTERM the stuck
966
+ * daemon (the bundle daemon that can't find its deps) and clear
967
+ * sock/pid so the next call spawns fresh. Also enqueue a one-time
968
+ * notification telling the user to run `hivemind embeddings install`
969
+ * — but only when the user has opted in. Suppressed when
970
+ * embeddingsStatus() === "user-disabled" so we don't nag users who
971
+ * explicitly chose to turn embeddings off.
972
+ */
973
+ handleTransformersMissing(detail) {
974
+ if (!_recycledStuckDaemon) {
975
+ _recycledStuckDaemon = true;
976
+ this.recycleDaemon(null);
977
+ }
978
+ if (_signalledMissingDeps)
979
+ return;
980
+ _signalledMissingDeps = true;
981
+ let status;
982
+ try {
983
+ status = embeddingsStatus();
984
+ } catch {
985
+ status = "enabled";
986
+ }
987
+ if (status === "user-disabled")
988
+ return;
989
+ enqueueNotification({
990
+ id: "embed-deps-missing",
991
+ severity: "warn",
992
+ title: "Hivemind embeddings disabled \u2014 deps missing",
993
+ body: `Semantic memory search is off because @huggingface/transformers is not installed where the daemon can find it. Run \`hivemind embeddings install\` to enable.`,
994
+ dedupKey: { reason: "transformers-missing", detail: detail.slice(0, 200) }
995
+ }).catch((e) => {
996
+ log4(`enqueue embed-deps-missing failed: ${e instanceof Error ? e.message : String(e)}`);
997
+ });
998
+ }
999
+ /**
1000
+ * Best-effort SIGTERM + sock/pid cleanup. Tolerant of every missing-file
1001
+ * combination and dead-PID cases.
1002
+ *
1003
+ * Identity check: gate the SIGTERM on the daemon's socket file still
1004
+ * existing. We know the daemon was alive moments ago (we either just
1005
+ * got a hello response or the caller saw a transformers-missing error
1006
+ * the daemon emitted), but if the socket file is gone by the time we
1007
+ * try to kill, the daemon process is also gone and the PID we
1008
+ * captured may already have been recycled by the OS to an unrelated
1009
+ * user process. Mirrors the gate added to `killEmbedDaemon` in the
1010
+ * CLI — same failure mode, rarer trigger.
1011
+ */
1012
+ recycleDaemon(reportedPid) {
1013
+ let pid = reportedPid;
1014
+ if (pid === null) {
1015
+ try {
1016
+ pid = Number.parseInt(readFileSync5(this.pidPath, "utf-8").trim(), 10);
1017
+ } catch {
1018
+ }
1019
+ }
1020
+ if (Number.isFinite(pid) && pid !== null && pid > 0 && existsSync4(this.socketPath)) {
1021
+ try {
1022
+ process.kill(pid, "SIGTERM");
1023
+ } catch {
1024
+ }
1025
+ } else if (pid !== null) {
1026
+ log4(`recycle: socket gone, skipping SIGTERM on possibly-stale pid ${pid}`);
1027
+ }
1028
+ try {
1029
+ unlinkSync2(this.socketPath);
1030
+ } catch {
1031
+ }
1032
+ try {
1033
+ unlinkSync2(this.pidPath);
1034
+ } catch {
1035
+ }
1036
+ }
649
1037
  /**
650
1038
  * Wait up to spawnWaitMs for the daemon to accept connections, spawning if
651
1039
  * necessary. Meant for SessionStart / long-running batches — not the hot path.
@@ -669,7 +1057,7 @@ var EmbedClient = class {
669
1057
  }
670
1058
  }
671
1059
  connectOnce() {
672
- return new Promise((resolve, reject) => {
1060
+ return new Promise((resolve2, reject) => {
673
1061
  const sock = connect(this.socketPath);
674
1062
  const to = setTimeout(() => {
675
1063
  sock.destroy();
@@ -677,7 +1065,7 @@ var EmbedClient = class {
677
1065
  }, this.timeoutMs);
678
1066
  sock.once("connect", () => {
679
1067
  clearTimeout(to);
680
- resolve(sock);
1068
+ resolve2(sock);
681
1069
  });
682
1070
  sock.once("error", (e) => {
683
1071
  clearTimeout(to);
@@ -688,16 +1076,16 @@ var EmbedClient = class {
688
1076
  trySpawnDaemon() {
689
1077
  let fd;
690
1078
  try {
691
- fd = openSync(this.pidPath, "wx", 384);
1079
+ fd = openSync2(this.pidPath, "wx", 384);
692
1080
  writeSync(fd, String(process.pid));
693
1081
  } catch (e) {
694
1082
  if (this.isPidFileStale()) {
695
1083
  try {
696
- unlinkSync(this.pidPath);
1084
+ unlinkSync2(this.pidPath);
697
1085
  } catch {
698
1086
  }
699
1087
  try {
700
- fd = openSync(this.pidPath, "wx", 384);
1088
+ fd = openSync2(this.pidPath, "wx", 384);
701
1089
  writeSync(fd, String(process.pid));
702
1090
  } catch {
703
1091
  return;
@@ -706,11 +1094,11 @@ var EmbedClient = class {
706
1094
  return;
707
1095
  }
708
1096
  }
709
- if (!this.daemonEntry || !existsSync3(this.daemonEntry)) {
710
- log3(`daemonEntry not configured or missing: ${this.daemonEntry}`);
1097
+ if (!this.daemonEntry || !existsSync4(this.daemonEntry)) {
1098
+ log4(`daemonEntry not configured or missing: ${this.daemonEntry}`);
711
1099
  try {
712
- closeSync(fd);
713
- unlinkSync(this.pidPath);
1100
+ closeSync2(fd);
1101
+ unlinkSync2(this.pidPath);
714
1102
  } catch {
715
1103
  }
716
1104
  return;
@@ -722,14 +1110,14 @@ var EmbedClient = class {
722
1110
  env: process.env
723
1111
  });
724
1112
  child.unref();
725
- log3(`spawned daemon pid=${child.pid}`);
1113
+ log4(`spawned daemon pid=${child.pid}`);
726
1114
  } finally {
727
- closeSync(fd);
1115
+ closeSync2(fd);
728
1116
  }
729
1117
  }
730
1118
  isPidFileStale() {
731
1119
  try {
732
- const raw = readFileSync3(this.pidPath, "utf-8").trim();
1120
+ const raw = readFileSync5(this.pidPath, "utf-8").trim();
733
1121
  const pid = Number(raw);
734
1122
  if (!pid || Number.isNaN(pid))
735
1123
  return true;
@@ -747,9 +1135,9 @@ var EmbedClient = class {
747
1135
  const deadline = Date.now() + this.spawnWaitMs;
748
1136
  let delay = 30;
749
1137
  while (Date.now() < deadline) {
750
- await sleep2(delay);
1138
+ await sleep3(delay);
751
1139
  delay = Math.min(delay * 1.5, 300);
752
- if (!existsSync3(this.socketPath))
1140
+ if (!existsSync4(this.socketPath))
753
1141
  continue;
754
1142
  try {
755
1143
  return await this.connectOnce();
@@ -759,7 +1147,7 @@ var EmbedClient = class {
759
1147
  throw new Error("daemon did not become ready within spawnWaitMs");
760
1148
  }
761
1149
  sendAndWait(sock, req) {
762
- return new Promise((resolve, reject) => {
1150
+ return new Promise((resolve2, reject) => {
763
1151
  let buf = "";
764
1152
  const to = setTimeout(() => {
765
1153
  sock.destroy();
@@ -774,7 +1162,7 @@ var EmbedClient = class {
774
1162
  const line = buf.slice(0, nl);
775
1163
  clearTimeout(to);
776
1164
  try {
777
- resolve(JSON.parse(line));
1165
+ resolve2(JSON.parse(line));
778
1166
  } catch (e) {
779
1167
  reject(e);
780
1168
  }
@@ -791,9 +1179,14 @@ var EmbedClient = class {
791
1179
  });
792
1180
  }
793
1181
  };
794
- function sleep2(ms) {
1182
+ function sleep3(ms) {
795
1183
  return new Promise((r) => setTimeout(r, ms));
796
1184
  }
1185
+ function isTransformersMissingError(err) {
1186
+ if (/hivemind embeddings install/i.test(err))
1187
+ return true;
1188
+ return /@huggingface\/transformers/i.test(err);
1189
+ }
797
1190
 
798
1191
  // dist/src/embeddings/sql.js
799
1192
  function embeddingSqlLiteral(vec) {
@@ -808,91 +1201,120 @@ function embeddingSqlLiteral(vec) {
808
1201
  return `ARRAY[${parts.join(",")}]::float4[]`;
809
1202
  }
810
1203
 
811
- // dist/src/embeddings/disable.js
812
- import { createRequire } from "node:module";
813
- import { homedir as homedir4 } from "node:os";
814
- import { join as join5 } from "node:path";
815
- import { pathToFileURL } from "node:url";
816
- var cachedStatus = null;
817
- function defaultResolveTransformers() {
1204
+ // dist/src/embeddings/self-heal.js
1205
+ import { existsSync as existsSync5, lstatSync, mkdirSync as mkdirSync4, readlinkSync, renameSync as renameSync3, rmSync, symlinkSync, statSync as statSync2 } from "node:fs";
1206
+ import { homedir as homedir7 } from "node:os";
1207
+ import { basename, dirname as dirname2, join as join8 } from "node:path";
1208
+ function ensurePluginNodeModulesLink(opts) {
1209
+ if (basename(opts.bundleDir) !== "bundle") {
1210
+ return { kind: "not-bundle-layout", bundleDir: opts.bundleDir };
1211
+ }
1212
+ const target = opts.sharedNodeModules ?? join8(homedir7(), ".hivemind", "embed-deps", "node_modules");
1213
+ const pluginDir = dirname2(opts.bundleDir);
1214
+ const link = join8(pluginDir, "node_modules");
1215
+ if (!existsSync5(target)) {
1216
+ return { kind: "shared-deps-missing", target };
1217
+ }
1218
+ let linkStat;
818
1219
  try {
819
- createRequire(import.meta.url).resolve("@huggingface/transformers");
820
- return;
1220
+ linkStat = lstatSync(link);
821
1221
  } catch {
1222
+ return createSymlinkAtomic(target, link);
822
1223
  }
823
- const sharedDir = join5(homedir4(), ".hivemind", "embed-deps");
824
- createRequire(pathToFileURL(`${sharedDir}/`).href).resolve("@huggingface/transformers");
1224
+ if (linkStat.isSymbolicLink()) {
1225
+ let existingTarget;
1226
+ try {
1227
+ existingTarget = readlinkSync(link);
1228
+ } catch (e) {
1229
+ return { kind: "error", detail: `readlink failed: ${e instanceof Error ? e.message : String(e)}` };
1230
+ }
1231
+ if (existingTarget === target) {
1232
+ return { kind: "already-linked", target, link };
1233
+ }
1234
+ try {
1235
+ statSync2(link);
1236
+ return { kind: "linked-elsewhere", link, existingTarget };
1237
+ } catch {
1238
+ try {
1239
+ rmSync(link);
1240
+ } catch {
1241
+ }
1242
+ const recreated = createSymlinkAtomic(target, link);
1243
+ if (recreated.kind === "linked") {
1244
+ return { kind: "stale-link-removed", link, danglingTarget: existingTarget };
1245
+ }
1246
+ return recreated;
1247
+ }
1248
+ }
1249
+ return { kind: "plugin-owns-node-modules", link };
825
1250
  }
826
- var _resolve = defaultResolveTransformers;
827
- function detectStatus() {
828
- if (process.env.HIVEMIND_EMBEDDINGS === "false")
829
- return "env-disabled";
1251
+ function createSymlinkAtomic(target, link) {
830
1252
  try {
831
- _resolve();
832
- return "enabled";
833
- } catch {
834
- return "no-transformers";
1253
+ const parent = dirname2(link);
1254
+ if (!existsSync5(parent))
1255
+ mkdirSync4(parent, { recursive: true });
1256
+ const tmp = `${link}.tmp.${process.pid}`;
1257
+ try {
1258
+ rmSync(tmp, { force: true });
1259
+ } catch {
1260
+ }
1261
+ symlinkSync(target, tmp);
1262
+ renameSync3(tmp, link);
1263
+ return { kind: "linked", target, link };
1264
+ } catch (e) {
1265
+ return { kind: "error", detail: e instanceof Error ? e.message : String(e) };
835
1266
  }
836
1267
  }
837
- function embeddingsStatus() {
838
- if (cachedStatus !== null)
839
- return cachedStatus;
840
- cachedStatus = detectStatus();
841
- return cachedStatus;
842
- }
843
- function embeddingsDisabled() {
844
- return embeddingsStatus() !== "enabled";
845
- }
846
1268
 
847
1269
  // dist/src/hooks/cursor/capture.js
848
1270
  import { fileURLToPath as fileURLToPath3 } from "node:url";
849
- import { dirname as dirname4, join as join15 } from "node:path";
1271
+ import { dirname as dirname6, join as join18 } from "node:path";
850
1272
 
851
1273
  // dist/src/hooks/summary-state.js
852
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, writeSync as writeSync2, mkdirSync as mkdirSync2, renameSync, existsSync as existsSync4, unlinkSync as unlinkSync2, openSync as openSync2, closeSync as closeSync2 } from "node:fs";
853
- import { homedir as homedir5 } from "node:os";
854
- import { join as join6 } from "node:path";
1274
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, writeSync as writeSync2, mkdirSync as mkdirSync5, renameSync as renameSync4, existsSync as existsSync6, unlinkSync as unlinkSync3, openSync as openSync3, closeSync as closeSync3 } from "node:fs";
1275
+ import { homedir as homedir8 } from "node:os";
1276
+ import { join as join9 } from "node:path";
855
1277
  var dlog = (msg) => log("summary-state", msg);
856
- var STATE_DIR = join6(homedir5(), ".claude", "hooks", "summary-state");
1278
+ var STATE_DIR = join9(homedir8(), ".claude", "hooks", "summary-state");
857
1279
  var YIELD_BUF = new Int32Array(new SharedArrayBuffer(4));
858
1280
  function statePath(sessionId) {
859
- return join6(STATE_DIR, `${sessionId}.json`);
1281
+ return join9(STATE_DIR, `${sessionId}.json`);
860
1282
  }
861
- function lockPath(sessionId) {
862
- return join6(STATE_DIR, `${sessionId}.lock`);
1283
+ function lockPath2(sessionId) {
1284
+ return join9(STATE_DIR, `${sessionId}.lock`);
863
1285
  }
864
1286
  function readState(sessionId) {
865
1287
  const p = statePath(sessionId);
866
- if (!existsSync4(p))
1288
+ if (!existsSync6(p))
867
1289
  return null;
868
1290
  try {
869
- return JSON.parse(readFileSync4(p, "utf-8"));
1291
+ return JSON.parse(readFileSync6(p, "utf-8"));
870
1292
  } catch {
871
1293
  return null;
872
1294
  }
873
1295
  }
874
1296
  function writeState(sessionId, state) {
875
- mkdirSync2(STATE_DIR, { recursive: true });
1297
+ mkdirSync5(STATE_DIR, { recursive: true });
876
1298
  const p = statePath(sessionId);
877
1299
  const tmp = `${p}.${process.pid}.${Date.now()}.tmp`;
878
- writeFileSync2(tmp, JSON.stringify(state));
879
- renameSync(tmp, p);
1300
+ writeFileSync4(tmp, JSON.stringify(state));
1301
+ renameSync4(tmp, p);
880
1302
  }
881
1303
  function withRmwLock(sessionId, fn) {
882
- mkdirSync2(STATE_DIR, { recursive: true });
1304
+ mkdirSync5(STATE_DIR, { recursive: true });
883
1305
  const rmwLock = statePath(sessionId) + ".rmw";
884
1306
  const deadline = Date.now() + 2e3;
885
1307
  let fd = null;
886
1308
  while (fd === null) {
887
1309
  try {
888
- fd = openSync2(rmwLock, "wx");
1310
+ fd = openSync3(rmwLock, "wx");
889
1311
  } catch (e) {
890
1312
  if (e.code !== "EEXIST")
891
1313
  throw e;
892
1314
  if (Date.now() > deadline) {
893
1315
  dlog(`rmw lock deadline exceeded for ${sessionId}, reclaiming stale lock`);
894
1316
  try {
895
- unlinkSync2(rmwLock);
1317
+ unlinkSync3(rmwLock);
896
1318
  } catch (unlinkErr) {
897
1319
  dlog(`stale rmw lock unlink failed for ${sessionId}: ${unlinkErr.message}`);
898
1320
  }
@@ -904,9 +1326,9 @@ function withRmwLock(sessionId, fn) {
904
1326
  try {
905
1327
  return fn();
906
1328
  } finally {
907
- closeSync2(fd);
1329
+ closeSync3(fd);
908
1330
  try {
909
- unlinkSync2(rmwLock);
1331
+ unlinkSync3(rmwLock);
910
1332
  } catch (unlinkErr) {
911
1333
  dlog(`rmw lock cleanup failed for ${sessionId}: ${unlinkErr.message}`);
912
1334
  }
@@ -941,29 +1363,29 @@ function shouldTrigger(state, cfg, now = Date.now()) {
941
1363
  return false;
942
1364
  }
943
1365
  function tryAcquireLock(sessionId, maxAgeMs = 10 * 60 * 1e3) {
944
- mkdirSync2(STATE_DIR, { recursive: true });
945
- const p = lockPath(sessionId);
946
- if (existsSync4(p)) {
1366
+ mkdirSync5(STATE_DIR, { recursive: true });
1367
+ const p = lockPath2(sessionId);
1368
+ if (existsSync6(p)) {
947
1369
  try {
948
- const ageMs = Date.now() - parseInt(readFileSync4(p, "utf-8"), 10);
1370
+ const ageMs = Date.now() - parseInt(readFileSync6(p, "utf-8"), 10);
949
1371
  if (Number.isFinite(ageMs) && ageMs < maxAgeMs)
950
1372
  return false;
951
1373
  } catch (readErr) {
952
1374
  dlog(`lock file unreadable for ${sessionId}, treating as stale: ${readErr.message}`);
953
1375
  }
954
1376
  try {
955
- unlinkSync2(p);
1377
+ unlinkSync3(p);
956
1378
  } catch (unlinkErr) {
957
1379
  dlog(`could not unlink stale lock for ${sessionId}: ${unlinkErr.message}`);
958
1380
  return false;
959
1381
  }
960
1382
  }
961
1383
  try {
962
- const fd = openSync2(p, "wx");
1384
+ const fd = openSync3(p, "wx");
963
1385
  try {
964
1386
  writeSync2(fd, String(Date.now()));
965
1387
  } finally {
966
- closeSync2(fd);
1388
+ closeSync3(fd);
967
1389
  }
968
1390
  return true;
969
1391
  } catch (e) {
@@ -974,7 +1396,7 @@ function tryAcquireLock(sessionId, maxAgeMs = 10 * 60 * 1e3) {
974
1396
  }
975
1397
  function releaseLock(sessionId) {
976
1398
  try {
977
- unlinkSync2(lockPath(sessionId));
1399
+ unlinkSync3(lockPath2(sessionId));
978
1400
  } catch (e) {
979
1401
  if (e?.code !== "ENOENT") {
980
1402
  dlog(`releaseLock unlink failed for ${sessionId}: ${e.message}`);
@@ -985,20 +1407,20 @@ function releaseLock(sessionId) {
985
1407
  // dist/src/hooks/cursor/spawn-wiki-worker.js
986
1408
  import { spawn as spawn2, execSync } from "node:child_process";
987
1409
  import { fileURLToPath } from "node:url";
988
- import { dirname as dirname2, join as join9 } from "node:path";
989
- import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync4 } from "node:fs";
990
- import { homedir as homedir6, tmpdir as tmpdir2 } from "node:os";
1410
+ import { dirname as dirname4, join as join12 } from "node:path";
1411
+ import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync7 } from "node:fs";
1412
+ import { homedir as homedir9, tmpdir as tmpdir2 } from "node:os";
991
1413
 
992
1414
  // dist/src/utils/wiki-log.js
993
- import { mkdirSync as mkdirSync3, appendFileSync as appendFileSync2 } from "node:fs";
994
- import { join as join7 } from "node:path";
1415
+ import { mkdirSync as mkdirSync6, appendFileSync as appendFileSync2 } from "node:fs";
1416
+ import { join as join10 } from "node:path";
995
1417
  function makeWikiLogger(hooksDir, filename = "deeplake-wiki.log") {
996
- const path = join7(hooksDir, filename);
1418
+ const path = join10(hooksDir, filename);
997
1419
  return {
998
1420
  path,
999
1421
  log(msg) {
1000
1422
  try {
1001
- mkdirSync3(hooksDir, { recursive: true });
1423
+ mkdirSync6(hooksDir, { recursive: true });
1002
1424
  appendFileSync2(path, `[${utcTimestamp()}] ${msg}
1003
1425
  `);
1004
1426
  } catch {
@@ -1008,18 +1430,18 @@ function makeWikiLogger(hooksDir, filename = "deeplake-wiki.log") {
1008
1430
  }
1009
1431
 
1010
1432
  // dist/src/utils/version-check.js
1011
- import { readFileSync as readFileSync5 } from "node:fs";
1012
- import { dirname, join as join8 } from "node:path";
1433
+ import { readFileSync as readFileSync7 } from "node:fs";
1434
+ import { dirname as dirname3, join as join11 } from "node:path";
1013
1435
  function getInstalledVersion(bundleDir, pluginManifestDir) {
1014
1436
  try {
1015
- const pluginJson = join8(bundleDir, "..", pluginManifestDir, "plugin.json");
1016
- const plugin = JSON.parse(readFileSync5(pluginJson, "utf-8"));
1437
+ const pluginJson = join11(bundleDir, "..", pluginManifestDir, "plugin.json");
1438
+ const plugin = JSON.parse(readFileSync7(pluginJson, "utf-8"));
1017
1439
  if (plugin.version)
1018
1440
  return plugin.version;
1019
1441
  } catch {
1020
1442
  }
1021
1443
  try {
1022
- const stamp = readFileSync5(join8(bundleDir, "..", ".hivemind_version"), "utf-8").trim();
1444
+ const stamp = readFileSync7(join11(bundleDir, "..", ".hivemind_version"), "utf-8").trim();
1023
1445
  if (stamp)
1024
1446
  return stamp;
1025
1447
  } catch {
@@ -1034,14 +1456,14 @@ function getInstalledVersion(bundleDir, pluginManifestDir) {
1034
1456
  ]);
1035
1457
  let dir = bundleDir;
1036
1458
  for (let i = 0; i < 5; i++) {
1037
- const candidate = join8(dir, "package.json");
1459
+ const candidate = join11(dir, "package.json");
1038
1460
  try {
1039
- const pkg = JSON.parse(readFileSync5(candidate, "utf-8"));
1461
+ const pkg = JSON.parse(readFileSync7(candidate, "utf-8"));
1040
1462
  if (HIVEMIND_PKG_NAMES.has(pkg.name) && pkg.version)
1041
1463
  return pkg.version;
1042
1464
  } catch {
1043
1465
  }
1044
- const parent = dirname(dir);
1466
+ const parent = dirname3(dir);
1045
1467
  if (parent === dir)
1046
1468
  break;
1047
1469
  dir = parent;
@@ -1050,8 +1472,8 @@ function getInstalledVersion(bundleDir, pluginManifestDir) {
1050
1472
  }
1051
1473
 
1052
1474
  // dist/src/hooks/cursor/spawn-wiki-worker.js
1053
- var HOME = homedir6();
1054
- var wikiLogger = makeWikiLogger(join9(HOME, ".cursor", "hooks"));
1475
+ var HOME = homedir9();
1476
+ var wikiLogger = makeWikiLogger(join12(HOME, ".cursor", "hooks"));
1055
1477
  var WIKI_LOG = wikiLogger.path;
1056
1478
  var WIKI_PROMPT_TEMPLATE = `You are building a personal wiki from a coding session. Your goal is to extract every piece of knowledge \u2014 entities, decisions, relationships, and facts \u2014 into a structured, searchable wiki entry.
1057
1479
 
@@ -1113,11 +1535,11 @@ function findCursorBin() {
1113
1535
  function spawnCursorWikiWorker(opts) {
1114
1536
  const { config, sessionId, cwd, bundleDir, reason } = opts;
1115
1537
  const projectName = cwd.split("/").pop() || "unknown";
1116
- const tmpDir = join9(tmpdir2(), `deeplake-wiki-${sessionId}-${Date.now()}`);
1117
- mkdirSync4(tmpDir, { recursive: true });
1538
+ const tmpDir = join12(tmpdir2(), `deeplake-wiki-${sessionId}-${Date.now()}`);
1539
+ mkdirSync7(tmpDir, { recursive: true });
1118
1540
  const pluginVersion = getInstalledVersion(bundleDir, ".claude-plugin") ?? "";
1119
- const configFile = join9(tmpDir, "config.json");
1120
- writeFileSync3(configFile, JSON.stringify({
1541
+ const configFile = join12(tmpDir, "config.json");
1542
+ writeFileSync5(configFile, JSON.stringify({
1121
1543
  apiUrl: config.apiUrl,
1122
1544
  token: config.token,
1123
1545
  orgId: config.orgId,
@@ -1132,11 +1554,11 @@ function spawnCursorWikiWorker(opts) {
1132
1554
  cursorBin: findCursorBin(),
1133
1555
  cursorModel: process.env.HIVEMIND_CURSOR_MODEL ?? "auto",
1134
1556
  wikiLog: WIKI_LOG,
1135
- hooksDir: join9(HOME, ".cursor", "hooks"),
1557
+ hooksDir: join12(HOME, ".cursor", "hooks"),
1136
1558
  promptTemplate: WIKI_PROMPT_TEMPLATE
1137
1559
  }));
1138
1560
  wikiLog(`${reason}: spawning summary worker for ${sessionId}`);
1139
- const workerPath = join9(bundleDir, "wiki-worker.js");
1561
+ const workerPath = join12(bundleDir, "wiki-worker.js");
1140
1562
  spawn2("nohup", ["node", workerPath, configFile], {
1141
1563
  detached: true,
1142
1564
  stdio: ["ignore", "ignore", "ignore"]
@@ -1144,33 +1566,33 @@ function spawnCursorWikiWorker(opts) {
1144
1566
  wikiLog(`${reason}: spawned summary worker for ${sessionId}`);
1145
1567
  }
1146
1568
  function bundleDirFromImportMeta(importMetaUrl) {
1147
- return dirname2(fileURLToPath(importMetaUrl));
1569
+ return dirname4(fileURLToPath(importMetaUrl));
1148
1570
  }
1149
1571
 
1150
1572
  // dist/src/skillify/spawn-skillify-worker.js
1151
1573
  import { spawn as spawn3 } from "node:child_process";
1152
1574
  import { fileURLToPath as fileURLToPath2 } from "node:url";
1153
- import { dirname as dirname3, join as join11 } from "node:path";
1154
- import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, appendFileSync as appendFileSync3, chmodSync } from "node:fs";
1155
- import { homedir as homedir8, tmpdir as tmpdir3 } from "node:os";
1575
+ import { dirname as dirname5, join as join14 } from "node:path";
1576
+ import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync8, appendFileSync as appendFileSync3, chmodSync } from "node:fs";
1577
+ import { homedir as homedir11, tmpdir as tmpdir3 } from "node:os";
1156
1578
 
1157
1579
  // dist/src/skillify/gate-runner.js
1158
- import { existsSync as existsSync5 } from "node:fs";
1580
+ import { existsSync as existsSync7 } from "node:fs";
1159
1581
  import { createRequire as createRequire2 } from "node:module";
1160
- import { homedir as homedir7 } from "node:os";
1161
- import { join as join10 } from "node:path";
1582
+ import { homedir as homedir10 } from "node:os";
1583
+ import { join as join13 } from "node:path";
1162
1584
  var requireForCp = createRequire2(import.meta.url);
1163
1585
  var { execFileSync: runChildProcess } = requireForCp("node:child_process");
1164
1586
  var inheritedEnv = process;
1165
1587
  function firstExistingPath(candidates) {
1166
1588
  for (const c of candidates) {
1167
- if (existsSync5(c))
1589
+ if (existsSync7(c))
1168
1590
  return c;
1169
1591
  }
1170
1592
  return null;
1171
1593
  }
1172
1594
  function findAgentBin(agent) {
1173
- const home = homedir7();
1595
+ const home = homedir10();
1174
1596
  switch (agent) {
1175
1597
  // /usr/bin/<name> is included in every candidate list — that's the
1176
1598
  // common Linux package-manager install path (apt, dnf, pacman). Old
@@ -1179,54 +1601,54 @@ function findAgentBin(agent) {
1179
1601
  // #170 caught the gap.
1180
1602
  case "claude_code":
1181
1603
  return firstExistingPath([
1182
- join10(home, ".claude", "local", "claude"),
1604
+ join13(home, ".claude", "local", "claude"),
1183
1605
  "/usr/local/bin/claude",
1184
1606
  "/usr/bin/claude",
1185
- join10(home, ".npm-global", "bin", "claude"),
1186
- join10(home, ".local", "bin", "claude"),
1607
+ join13(home, ".npm-global", "bin", "claude"),
1608
+ join13(home, ".local", "bin", "claude"),
1187
1609
  "/opt/homebrew/bin/claude"
1188
- ]) ?? join10(home, ".claude", "local", "claude");
1610
+ ]) ?? join13(home, ".claude", "local", "claude");
1189
1611
  case "codex":
1190
1612
  return firstExistingPath([
1191
1613
  "/usr/local/bin/codex",
1192
1614
  "/usr/bin/codex",
1193
- join10(home, ".npm-global", "bin", "codex"),
1194
- join10(home, ".local", "bin", "codex"),
1615
+ join13(home, ".npm-global", "bin", "codex"),
1616
+ join13(home, ".local", "bin", "codex"),
1195
1617
  "/opt/homebrew/bin/codex"
1196
1618
  ]) ?? "/usr/local/bin/codex";
1197
1619
  case "cursor":
1198
1620
  return firstExistingPath([
1199
1621
  "/usr/local/bin/cursor-agent",
1200
1622
  "/usr/bin/cursor-agent",
1201
- join10(home, ".npm-global", "bin", "cursor-agent"),
1202
- join10(home, ".local", "bin", "cursor-agent"),
1623
+ join13(home, ".npm-global", "bin", "cursor-agent"),
1624
+ join13(home, ".local", "bin", "cursor-agent"),
1203
1625
  "/opt/homebrew/bin/cursor-agent"
1204
1626
  ]) ?? "/usr/local/bin/cursor-agent";
1205
1627
  case "hermes":
1206
1628
  return firstExistingPath([
1207
- join10(home, ".local", "bin", "hermes"),
1629
+ join13(home, ".local", "bin", "hermes"),
1208
1630
  "/usr/local/bin/hermes",
1209
1631
  "/usr/bin/hermes",
1210
- join10(home, ".npm-global", "bin", "hermes"),
1632
+ join13(home, ".npm-global", "bin", "hermes"),
1211
1633
  "/opt/homebrew/bin/hermes"
1212
- ]) ?? join10(home, ".local", "bin", "hermes");
1634
+ ]) ?? join13(home, ".local", "bin", "hermes");
1213
1635
  case "pi":
1214
1636
  return firstExistingPath([
1215
- join10(home, ".local", "bin", "pi"),
1637
+ join13(home, ".local", "bin", "pi"),
1216
1638
  "/usr/local/bin/pi",
1217
1639
  "/usr/bin/pi",
1218
- join10(home, ".npm-global", "bin", "pi"),
1640
+ join13(home, ".npm-global", "bin", "pi"),
1219
1641
  "/opt/homebrew/bin/pi"
1220
- ]) ?? join10(home, ".local", "bin", "pi");
1642
+ ]) ?? join13(home, ".local", "bin", "pi");
1221
1643
  }
1222
1644
  }
1223
1645
 
1224
1646
  // dist/src/skillify/spawn-skillify-worker.js
1225
- var HOME2 = homedir8();
1226
- var SKILLIFY_LOG = join11(HOME2, ".claude", "hooks", "skillify.log");
1647
+ var HOME2 = homedir11();
1648
+ var SKILLIFY_LOG = join14(HOME2, ".claude", "hooks", "skillify.log");
1227
1649
  function skillifyLog(msg) {
1228
1650
  try {
1229
- mkdirSync5(dirname3(SKILLIFY_LOG), { recursive: true });
1651
+ mkdirSync8(dirname5(SKILLIFY_LOG), { recursive: true });
1230
1652
  appendFileSync3(SKILLIFY_LOG, `[${utcTimestamp()}] ${msg}
1231
1653
  `);
1232
1654
  } catch {
@@ -1234,11 +1656,11 @@ function skillifyLog(msg) {
1234
1656
  }
1235
1657
  function spawnSkillifyWorker(opts) {
1236
1658
  const { config, cwd, projectKey, project, bundleDir, agent, scopeConfig, currentSessionId, reason } = opts;
1237
- const tmpDir = join11(tmpdir3(), `deeplake-skillify-${projectKey}-${Date.now()}`);
1238
- mkdirSync5(tmpDir, { recursive: true, mode: 448 });
1659
+ const tmpDir = join14(tmpdir3(), `deeplake-skillify-${projectKey}-${Date.now()}`);
1660
+ mkdirSync8(tmpDir, { recursive: true, mode: 448 });
1239
1661
  const gateBin = findAgentBin(agent);
1240
- const configFile = join11(tmpDir, "config.json");
1241
- writeFileSync4(configFile, JSON.stringify({
1662
+ const configFile = join14(tmpDir, "config.json");
1663
+ writeFileSync6(configFile, JSON.stringify({
1242
1664
  apiUrl: config.apiUrl,
1243
1665
  token: config.token,
1244
1666
  orgId: config.orgId,
@@ -1268,7 +1690,7 @@ function spawnSkillifyWorker(opts) {
1268
1690
  } catch {
1269
1691
  }
1270
1692
  skillifyLog(`${reason}: spawning skillify worker for project=${project} key=${projectKey}`);
1271
- const workerPath = join11(bundleDir, "skillify-worker.js");
1693
+ const workerPath = join14(bundleDir, "skillify-worker.js");
1272
1694
  spawn3("nohup", ["node", workerPath, configFile], {
1273
1695
  detached: true,
1274
1696
  stdio: ["ignore", "ignore", "ignore"]
@@ -1277,31 +1699,31 @@ function spawnSkillifyWorker(opts) {
1277
1699
  }
1278
1700
 
1279
1701
  // dist/src/skillify/state.js
1280
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, writeSync as writeSync3, mkdirSync as mkdirSync6, renameSync as renameSync3, existsSync as existsSync7, unlinkSync as unlinkSync3, openSync as openSync3, closeSync as closeSync3 } from "node:fs";
1702
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync7, writeSync as writeSync3, mkdirSync as mkdirSync9, renameSync as renameSync6, existsSync as existsSync9, unlinkSync as unlinkSync4, openSync as openSync4, closeSync as closeSync4 } from "node:fs";
1281
1703
  import { execSync as execSync2 } from "node:child_process";
1282
- import { homedir as homedir10 } from "node:os";
1704
+ import { homedir as homedir13 } from "node:os";
1283
1705
  import { createHash } from "node:crypto";
1284
- import { join as join13, basename } from "node:path";
1706
+ import { join as join16, basename as basename2 } from "node:path";
1285
1707
 
1286
1708
  // dist/src/skillify/legacy-migration.js
1287
- import { existsSync as existsSync6, renameSync as renameSync2 } from "node:fs";
1288
- import { homedir as homedir9 } from "node:os";
1289
- import { join as join12 } from "node:path";
1709
+ import { existsSync as existsSync8, renameSync as renameSync5 } from "node:fs";
1710
+ import { homedir as homedir12 } from "node:os";
1711
+ import { join as join15 } from "node:path";
1290
1712
  var dlog2 = (msg) => log("skillify-migrate", msg);
1291
1713
  var attempted = false;
1292
1714
  function migrateLegacyStateDir() {
1293
1715
  if (attempted)
1294
1716
  return;
1295
1717
  attempted = true;
1296
- const root = join12(homedir9(), ".deeplake", "state");
1297
- const legacy = join12(root, "skilify");
1298
- const current = join12(root, "skillify");
1299
- if (!existsSync6(legacy))
1718
+ const root = join15(homedir12(), ".deeplake", "state");
1719
+ const legacy = join15(root, "skilify");
1720
+ const current = join15(root, "skillify");
1721
+ if (!existsSync8(legacy))
1300
1722
  return;
1301
- if (existsSync6(current))
1723
+ if (existsSync8(current))
1302
1724
  return;
1303
1725
  try {
1304
- renameSync2(legacy, current);
1726
+ renameSync5(legacy, current);
1305
1727
  dlog2(`migrated ${legacy} -> ${current}`);
1306
1728
  } catch (err) {
1307
1729
  const code = err.code;
@@ -1315,17 +1737,17 @@ function migrateLegacyStateDir() {
1315
1737
 
1316
1738
  // dist/src/skillify/state.js
1317
1739
  var dlog3 = (msg) => log("skillify-state", msg);
1318
- var STATE_DIR2 = join13(homedir10(), ".deeplake", "state", "skillify");
1740
+ var STATE_DIR2 = join16(homedir13(), ".deeplake", "state", "skillify");
1319
1741
  var YIELD_BUF2 = new Int32Array(new SharedArrayBuffer(4));
1320
1742
  var TRIGGER_THRESHOLD = (() => {
1321
1743
  const n = Number(process.env.HIVEMIND_SKILLIFY_EVERY_N_TURNS ?? "");
1322
1744
  return Number.isInteger(n) && n > 0 ? n : 20;
1323
1745
  })();
1324
1746
  function statePath2(projectKey) {
1325
- return join13(STATE_DIR2, `${projectKey}.json`);
1747
+ return join16(STATE_DIR2, `${projectKey}.json`);
1326
1748
  }
1327
- function lockPath2(projectKey) {
1328
- return join13(STATE_DIR2, `${projectKey}.lock`);
1749
+ function lockPath3(projectKey) {
1750
+ return join16(STATE_DIR2, `${projectKey}.lock`);
1329
1751
  }
1330
1752
  var DEFAULT_PORTS = {
1331
1753
  http: "80",
@@ -1353,7 +1775,7 @@ function normalizeGitRemoteUrl(url) {
1353
1775
  return s.toLowerCase();
1354
1776
  }
1355
1777
  function deriveProjectKey(cwd) {
1356
- const project = basename(cwd) || "unknown";
1778
+ const project = basename2(cwd) || "unknown";
1357
1779
  let signature = null;
1358
1780
  try {
1359
1781
  const raw = execSync2("git config --get remote.origin.url", {
@@ -1371,38 +1793,38 @@ function deriveProjectKey(cwd) {
1371
1793
  function readState2(projectKey) {
1372
1794
  migrateLegacyStateDir();
1373
1795
  const p = statePath2(projectKey);
1374
- if (!existsSync7(p))
1796
+ if (!existsSync9(p))
1375
1797
  return null;
1376
1798
  try {
1377
- return JSON.parse(readFileSync6(p, "utf-8"));
1799
+ return JSON.parse(readFileSync8(p, "utf-8"));
1378
1800
  } catch {
1379
1801
  return null;
1380
1802
  }
1381
1803
  }
1382
1804
  function writeState2(projectKey, state) {
1383
1805
  migrateLegacyStateDir();
1384
- mkdirSync6(STATE_DIR2, { recursive: true });
1806
+ mkdirSync9(STATE_DIR2, { recursive: true });
1385
1807
  const p = statePath2(projectKey);
1386
1808
  const tmp = `${p}.${process.pid}.${Date.now()}.tmp`;
1387
- writeFileSync5(tmp, JSON.stringify(state, null, 2));
1388
- renameSync3(tmp, p);
1809
+ writeFileSync7(tmp, JSON.stringify(state, null, 2));
1810
+ renameSync6(tmp, p);
1389
1811
  }
1390
1812
  function withRmwLock2(projectKey, fn) {
1391
1813
  migrateLegacyStateDir();
1392
- mkdirSync6(STATE_DIR2, { recursive: true });
1393
- const rmw = lockPath2(projectKey) + ".rmw";
1814
+ mkdirSync9(STATE_DIR2, { recursive: true });
1815
+ const rmw = lockPath3(projectKey) + ".rmw";
1394
1816
  const deadline = Date.now() + 2e3;
1395
1817
  let fd = null;
1396
1818
  while (fd === null) {
1397
1819
  try {
1398
- fd = openSync3(rmw, "wx");
1820
+ fd = openSync4(rmw, "wx");
1399
1821
  } catch (e) {
1400
1822
  if (e.code !== "EEXIST")
1401
1823
  throw e;
1402
1824
  if (Date.now() > deadline) {
1403
1825
  dlog3(`rmw lock deadline exceeded for ${projectKey}, reclaiming stale lock`);
1404
1826
  try {
1405
- unlinkSync3(rmw);
1827
+ unlinkSync4(rmw);
1406
1828
  } catch (unlinkErr) {
1407
1829
  dlog3(`stale rmw lock unlink failed for ${projectKey}: ${unlinkErr.message}`);
1408
1830
  }
@@ -1414,9 +1836,9 @@ function withRmwLock2(projectKey, fn) {
1414
1836
  try {
1415
1837
  return fn();
1416
1838
  } finally {
1417
- closeSync3(fd);
1839
+ closeSync4(fd);
1418
1840
  try {
1419
- unlinkSync3(rmw);
1841
+ unlinkSync4(rmw);
1420
1842
  } catch (unlinkErr) {
1421
1843
  dlog3(`rmw lock cleanup failed for ${projectKey}: ${unlinkErr.message}`);
1422
1844
  }
@@ -1449,29 +1871,29 @@ function resetCounter(projectKey) {
1449
1871
  }
1450
1872
  function tryAcquireWorkerLock(projectKey, maxAgeMs = 10 * 60 * 1e3) {
1451
1873
  migrateLegacyStateDir();
1452
- mkdirSync6(STATE_DIR2, { recursive: true });
1453
- const p = lockPath2(projectKey);
1454
- if (existsSync7(p)) {
1874
+ mkdirSync9(STATE_DIR2, { recursive: true });
1875
+ const p = lockPath3(projectKey);
1876
+ if (existsSync9(p)) {
1455
1877
  try {
1456
- const ageMs = Date.now() - parseInt(readFileSync6(p, "utf-8"), 10);
1878
+ const ageMs = Date.now() - parseInt(readFileSync8(p, "utf-8"), 10);
1457
1879
  if (Number.isFinite(ageMs) && ageMs < maxAgeMs)
1458
1880
  return false;
1459
1881
  } catch (readErr) {
1460
1882
  dlog3(`worker lock unreadable for ${projectKey}, treating as stale: ${readErr.message}`);
1461
1883
  }
1462
1884
  try {
1463
- unlinkSync3(p);
1885
+ unlinkSync4(p);
1464
1886
  } catch (unlinkErr) {
1465
1887
  dlog3(`could not unlink stale worker lock for ${projectKey}: ${unlinkErr.message}`);
1466
1888
  return false;
1467
1889
  }
1468
1890
  }
1469
1891
  try {
1470
- const fd = openSync3(p, "wx");
1892
+ const fd = openSync4(p, "wx");
1471
1893
  try {
1472
1894
  writeSync3(fd, String(Date.now()));
1473
1895
  } finally {
1474
- closeSync3(fd);
1896
+ closeSync4(fd);
1475
1897
  }
1476
1898
  return true;
1477
1899
  } catch {
@@ -1479,26 +1901,26 @@ function tryAcquireWorkerLock(projectKey, maxAgeMs = 10 * 60 * 1e3) {
1479
1901
  }
1480
1902
  }
1481
1903
  function releaseWorkerLock(projectKey) {
1482
- const p = lockPath2(projectKey);
1904
+ const p = lockPath3(projectKey);
1483
1905
  try {
1484
- unlinkSync3(p);
1906
+ unlinkSync4(p);
1485
1907
  } catch {
1486
1908
  }
1487
1909
  }
1488
1910
 
1489
1911
  // dist/src/skillify/scope-config.js
1490
- import { existsSync as existsSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "node:fs";
1491
- import { homedir as homedir11 } from "node:os";
1492
- import { join as join14 } from "node:path";
1493
- var STATE_DIR3 = join14(homedir11(), ".deeplake", "state", "skillify");
1494
- var CONFIG_PATH = join14(STATE_DIR3, "config.json");
1912
+ import { existsSync as existsSync10, mkdirSync as mkdirSync10, readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "node:fs";
1913
+ import { homedir as homedir14 } from "node:os";
1914
+ import { join as join17 } from "node:path";
1915
+ var STATE_DIR3 = join17(homedir14(), ".deeplake", "state", "skillify");
1916
+ var CONFIG_PATH = join17(STATE_DIR3, "config.json");
1495
1917
  var DEFAULT = { scope: "me", team: [], install: "project" };
1496
1918
  function loadScopeConfig() {
1497
1919
  migrateLegacyStateDir();
1498
- if (!existsSync8(CONFIG_PATH))
1920
+ if (!existsSync10(CONFIG_PATH))
1499
1921
  return DEFAULT;
1500
1922
  try {
1501
- const raw = JSON.parse(readFileSync7(CONFIG_PATH, "utf-8"));
1923
+ const raw = JSON.parse(readFileSync9(CONFIG_PATH, "utf-8"));
1502
1924
  const scope = raw.scope === "team" ? "team" : raw.scope === "org" ? "team" : "me";
1503
1925
  const team = Array.isArray(raw.team) ? raw.team.filter((s) => typeof s === "string") : [];
1504
1926
  const install = raw.install === "global" ? "global" : "project";
@@ -1549,12 +1971,18 @@ function tryStopCounterTrigger(opts) {
1549
1971
  }
1550
1972
 
1551
1973
  // dist/src/hooks/cursor/capture.js
1552
- var log4 = (msg) => log("cursor-capture", msg);
1974
+ var log5 = (msg) => log("cursor-capture", msg);
1553
1975
  function resolveEmbedDaemonPath() {
1554
- return join15(dirname4(fileURLToPath3(import.meta.url)), "embeddings", "embed-daemon.js");
1976
+ return join18(dirname6(fileURLToPath3(import.meta.url)), "embeddings", "embed-daemon.js");
1555
1977
  }
1556
- var __bundleDir = dirname4(fileURLToPath3(import.meta.url));
1978
+ var __bundleDir = dirname6(fileURLToPath3(import.meta.url));
1557
1979
  var PLUGIN_VERSION = getInstalledVersion(__bundleDir, ".claude-plugin") ?? "";
1980
+ if (!embeddingsDisabled()) {
1981
+ try {
1982
+ ensurePluginNodeModulesLink({ bundleDir: __bundleDir });
1983
+ } catch {
1984
+ }
1985
+ }
1558
1986
  var CAPTURE = process.env.HIVEMIND_CAPTURE !== "false";
1559
1987
  function resolveCwd(input) {
1560
1988
  if (typeof input.cwd === "string" && input.cwd)
@@ -1570,7 +1998,7 @@ async function main() {
1570
1998
  const input = await readStdin();
1571
1999
  const config = loadConfig();
1572
2000
  if (!config) {
1573
- log4("no config");
2001
+ log5("no config");
1574
2002
  return;
1575
2003
  }
1576
2004
  const sessionId = input.conversation_id ?? `cursor-${Date.now()}`;
@@ -1589,10 +2017,10 @@ async function main() {
1589
2017
  };
1590
2018
  let entry = null;
1591
2019
  if (event === "beforeSubmitPrompt" && typeof input.prompt === "string") {
1592
- log4(`user session=${sessionId}`);
2020
+ log5(`user session=${sessionId}`);
1593
2021
  entry = { id: crypto.randomUUID(), ...meta, type: "user_message", content: input.prompt };
1594
2022
  } else if (event === "postToolUse" && typeof input.tool_name === "string") {
1595
- log4(`tool=${input.tool_name} session=${sessionId}`);
2023
+ log5(`tool=${input.tool_name} session=${sessionId}`);
1596
2024
  entry = {
1597
2025
  id: crypto.randomUUID(),
1598
2026
  ...meta,
@@ -1604,10 +2032,10 @@ async function main() {
1604
2032
  tool_response: typeof input.tool_output === "string" ? input.tool_output : JSON.stringify(input.tool_output)
1605
2033
  };
1606
2034
  } else if (event === "afterAgentResponse" && typeof input.text === "string") {
1607
- log4(`assistant session=${sessionId}`);
2035
+ log5(`assistant session=${sessionId}`);
1608
2036
  entry = { id: crypto.randomUUID(), ...meta, type: "assistant_message", content: input.text };
1609
2037
  } else if (event === "stop") {
1610
- log4(`stop session=${sessionId} status=${input.status ?? "unknown"}`);
2038
+ log5(`stop session=${sessionId} status=${input.status ?? "unknown"}`);
1611
2039
  entry = {
1612
2040
  id: crypto.randomUUID(),
1613
2041
  ...meta,
@@ -1616,12 +2044,12 @@ async function main() {
1616
2044
  loop_count: input.loop_count
1617
2045
  };
1618
2046
  } else {
1619
- log4(`unknown event: ${event}, skipping`);
2047
+ log5(`unknown event: ${event}, skipping`);
1620
2048
  return;
1621
2049
  }
1622
2050
  const sessionPath = buildSessionPath(config, sessionId);
1623
2051
  const line = JSON.stringify(entry);
1624
- log4(`writing to ${sessionPath}`);
2052
+ log5(`writing to ${sessionPath}`);
1625
2053
  const projectName = cwd.split("/").pop() || "unknown";
1626
2054
  const filename = sessionPath.split("/").pop() ?? "";
1627
2055
  const jsonForSql = line.replace(/'/g, "''");
@@ -1632,14 +2060,14 @@ async function main() {
1632
2060
  await api.query(insertSql);
1633
2061
  } catch (e) {
1634
2062
  if (e.message?.includes("permission denied") || e.message?.includes("does not exist")) {
1635
- log4("table missing, creating and retrying");
2063
+ log5("table missing, creating and retrying");
1636
2064
  await api.ensureSessionsTable(sessionsTable);
1637
2065
  await api.query(insertSql);
1638
2066
  } else {
1639
2067
  throw e;
1640
2068
  }
1641
2069
  }
1642
- log4("capture ok \u2192 cloud");
2070
+ log5("capture ok \u2192 cloud");
1643
2071
  maybeTriggerPeriodicSummary(sessionId, cwd, config);
1644
2072
  if (event === "afterAgentResponse" && process.env.HIVEMIND_WIKI_WORKER !== "1" && process.env.HIVEMIND_SKILLIFY_WORKER !== "1") {
1645
2073
  tryStopCounterTrigger({
@@ -1660,7 +2088,7 @@ function maybeTriggerPeriodicSummary(sessionId, cwd, config) {
1660
2088
  if (!shouldTrigger(state, cfg))
1661
2089
  return;
1662
2090
  if (!tryAcquireLock(sessionId)) {
1663
- log4(`periodic trigger suppressed (lock held) session=${sessionId}`);
2091
+ log5(`periodic trigger suppressed (lock held) session=${sessionId}`);
1664
2092
  return;
1665
2093
  }
1666
2094
  wikiLog(`Periodic: threshold hit (total=${state.totalCount}, since=${state.totalCount - state.lastSummaryCount}, N=${cfg.everyNMessages}, hours=${cfg.everyHours})`);
@@ -1673,17 +2101,17 @@ function maybeTriggerPeriodicSummary(sessionId, cwd, config) {
1673
2101
  reason: "Periodic"
1674
2102
  });
1675
2103
  } catch (e) {
1676
- log4(`periodic spawn failed: ${e.message}`);
2104
+ log5(`periodic spawn failed: ${e.message}`);
1677
2105
  try {
1678
2106
  releaseLock(sessionId);
1679
2107
  } catch {
1680
2108
  }
1681
2109
  }
1682
2110
  } catch (e) {
1683
- log4(`periodic trigger error: ${e.message}`);
2111
+ log5(`periodic trigger error: ${e.message}`);
1684
2112
  }
1685
2113
  }
1686
2114
  main().catch((e) => {
1687
- log4(`fatal: ${e.message}`);
2115
+ log5(`fatal: ${e.message}`);
1688
2116
  process.exit(0);
1689
2117
  });