@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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/bundle/cli.js +427 -233
- package/codex/bundle/capture.js +550 -122
- package/codex/bundle/embeddings/embed-daemon.js +55 -4
- package/codex/bundle/pre-tool-use.js +447 -90
- package/codex/bundle/shell/deeplake-shell.js +431 -74
- package/codex/bundle/stop.js +437 -80
- package/codex/bundle/wiki-worker.js +429 -72
- package/cursor/bundle/capture.js +625 -197
- package/cursor/bundle/embeddings/embed-daemon.js +55 -4
- package/cursor/bundle/pre-tool-use.js +432 -75
- package/cursor/bundle/session-start.js +8 -1
- package/cursor/bundle/shell/deeplake-shell.js +431 -74
- package/cursor/bundle/wiki-worker.js +429 -72
- package/hermes/bundle/capture.js +626 -198
- package/hermes/bundle/embeddings/embed-daemon.js +55 -4
- package/hermes/bundle/pre-tool-use.js +431 -74
- package/hermes/bundle/session-start.js +8 -1
- package/hermes/bundle/shell/deeplake-shell.js +431 -74
- package/hermes/bundle/wiki-worker.js +429 -72
- package/openclaw/dist/index.js +1 -1
- package/openclaw/openclaw.plugin.json +1 -1
- package/openclaw/package.json +1 -1
- package/package.json +1 -1
package/codex/bundle/capture.js
CHANGED
|
@@ -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((
|
|
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
|
-
|
|
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((
|
|
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((
|
|
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
|
|
574
|
-
import { homedir as
|
|
575
|
-
import { join as
|
|
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 =
|
|
590
|
-
var
|
|
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 ?? (
|
|
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
|
-
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
1084
|
+
unlinkSync2(this.pidPath);
|
|
697
1085
|
} catch {
|
|
698
1086
|
}
|
|
699
1087
|
try {
|
|
700
|
-
fd =
|
|
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 || !
|
|
710
|
-
|
|
1097
|
+
if (!this.daemonEntry || !existsSync4(this.daemonEntry)) {
|
|
1098
|
+
log4(`daemonEntry not configured or missing: ${this.daemonEntry}`);
|
|
711
1099
|
try {
|
|
712
|
-
|
|
713
|
-
|
|
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
|
-
|
|
1113
|
+
log4(`spawned daemon pid=${child.pid}`);
|
|
726
1114
|
} finally {
|
|
727
|
-
|
|
1115
|
+
closeSync2(fd);
|
|
728
1116
|
}
|
|
729
1117
|
}
|
|
730
1118
|
isPidFileStale() {
|
|
731
1119
|
try {
|
|
732
|
-
const raw =
|
|
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
|
|
1138
|
+
await sleep3(delay);
|
|
751
1139
|
delay = Math.min(delay * 1.5, 300);
|
|
752
|
-
if (!
|
|
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((
|
|
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
|
-
|
|
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
|
|
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/
|
|
812
|
-
import {
|
|
813
|
-
import { homedir as
|
|
814
|
-
import { join as
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
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
|
-
|
|
820
|
-
return;
|
|
1220
|
+
linkStat = lstatSync(link);
|
|
821
1221
|
} catch {
|
|
1222
|
+
return createSymlinkAtomic(target, link);
|
|
822
1223
|
}
|
|
823
|
-
|
|
824
|
-
|
|
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
|
-
|
|
827
|
-
function detectStatus() {
|
|
828
|
-
if (process.env.HIVEMIND_EMBEDDINGS === "false")
|
|
829
|
-
return "env-disabled";
|
|
1251
|
+
function createSymlinkAtomic(target, link) {
|
|
830
1252
|
try {
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
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/codex/capture.js
|
|
848
1270
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
849
|
-
import { dirname as
|
|
1271
|
+
import { dirname as dirname5, join as join13 } from "node:path";
|
|
850
1272
|
|
|
851
1273
|
// dist/src/hooks/summary-state.js
|
|
852
|
-
import { readFileSync as
|
|
853
|
-
import { homedir as
|
|
854
|
-
import { join as
|
|
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 =
|
|
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
|
|
1281
|
+
return join9(STATE_DIR, `${sessionId}.json`);
|
|
860
1282
|
}
|
|
861
|
-
function
|
|
862
|
-
return
|
|
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 (!
|
|
1288
|
+
if (!existsSync6(p))
|
|
867
1289
|
return null;
|
|
868
1290
|
try {
|
|
869
|
-
return JSON.parse(
|
|
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
|
-
|
|
1297
|
+
mkdirSync5(STATE_DIR, { recursive: true });
|
|
876
1298
|
const p = statePath(sessionId);
|
|
877
1299
|
const tmp = `${p}.${process.pid}.${Date.now()}.tmp`;
|
|
878
|
-
|
|
879
|
-
|
|
1300
|
+
writeFileSync4(tmp, JSON.stringify(state));
|
|
1301
|
+
renameSync4(tmp, p);
|
|
880
1302
|
}
|
|
881
1303
|
function withRmwLock(sessionId, fn) {
|
|
882
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
1329
|
+
closeSync3(fd);
|
|
908
1330
|
try {
|
|
909
|
-
|
|
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
|
-
|
|
945
|
-
const p =
|
|
946
|
-
if (
|
|
1366
|
+
mkdirSync5(STATE_DIR, { recursive: true });
|
|
1367
|
+
const p = lockPath2(sessionId);
|
|
1368
|
+
if (existsSync6(p)) {
|
|
947
1369
|
try {
|
|
948
|
-
const ageMs = Date.now() - parseInt(
|
|
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
|
-
|
|
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 =
|
|
1384
|
+
const fd = openSync3(p, "wx");
|
|
963
1385
|
try {
|
|
964
1386
|
writeSync2(fd, String(Date.now()));
|
|
965
1387
|
} finally {
|
|
966
|
-
|
|
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
|
-
|
|
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/codex/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
|
|
989
|
-
import { writeFileSync as
|
|
990
|
-
import { homedir as
|
|
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
|
|
994
|
-
import { join as
|
|
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 =
|
|
1418
|
+
const path = join10(hooksDir, filename);
|
|
997
1419
|
return {
|
|
998
1420
|
path,
|
|
999
1421
|
log(msg) {
|
|
1000
1422
|
try {
|
|
1001
|
-
|
|
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
|
|
1012
|
-
import { dirname, join as
|
|
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 =
|
|
1016
|
-
const plugin = JSON.parse(
|
|
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 =
|
|
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 =
|
|
1459
|
+
const candidate = join11(dir, "package.json");
|
|
1038
1460
|
try {
|
|
1039
|
-
const pkg = JSON.parse(
|
|
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 =
|
|
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/codex/spawn-wiki-worker.js
|
|
1053
|
-
var HOME =
|
|
1054
|
-
var wikiLogger = makeWikiLogger(
|
|
1475
|
+
var HOME = homedir9();
|
|
1476
|
+
var wikiLogger = makeWikiLogger(join12(HOME, ".codex", "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 findCodexBin() {
|
|
|
1113
1535
|
function spawnCodexWikiWorker(opts) {
|
|
1114
1536
|
const { config, sessionId, cwd, bundleDir, reason } = opts;
|
|
1115
1537
|
const projectName = cwd.split("/").pop() || "unknown";
|
|
1116
|
-
const tmpDir =
|
|
1117
|
-
|
|
1538
|
+
const tmpDir = join12(tmpdir2(), `deeplake-wiki-${sessionId}-${Date.now()}`);
|
|
1539
|
+
mkdirSync7(tmpDir, { recursive: true });
|
|
1118
1540
|
const pluginVersion = getInstalledVersion(bundleDir, ".codex-plugin") ?? "";
|
|
1119
|
-
const configFile =
|
|
1120
|
-
|
|
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,
|
|
@@ -1131,11 +1553,11 @@ function spawnCodexWikiWorker(opts) {
|
|
|
1131
1553
|
tmpDir,
|
|
1132
1554
|
codexBin: findCodexBin(),
|
|
1133
1555
|
wikiLog: WIKI_LOG,
|
|
1134
|
-
hooksDir:
|
|
1556
|
+
hooksDir: join12(HOME, ".codex", "hooks"),
|
|
1135
1557
|
promptTemplate: WIKI_PROMPT_TEMPLATE
|
|
1136
1558
|
}));
|
|
1137
1559
|
wikiLog(`${reason}: spawning summary worker for ${sessionId}`);
|
|
1138
|
-
const workerPath =
|
|
1560
|
+
const workerPath = join12(bundleDir, "wiki-worker.js");
|
|
1139
1561
|
spawn2("nohup", ["node", workerPath, configFile], {
|
|
1140
1562
|
detached: true,
|
|
1141
1563
|
stdio: ["ignore", "ignore", "ignore"]
|
|
@@ -1143,16 +1565,22 @@ function spawnCodexWikiWorker(opts) {
|
|
|
1143
1565
|
wikiLog(`${reason}: spawned summary worker for ${sessionId}`);
|
|
1144
1566
|
}
|
|
1145
1567
|
function bundleDirFromImportMeta(importMetaUrl) {
|
|
1146
|
-
return
|
|
1568
|
+
return dirname4(fileURLToPath(importMetaUrl));
|
|
1147
1569
|
}
|
|
1148
1570
|
|
|
1149
1571
|
// dist/src/hooks/codex/capture.js
|
|
1150
|
-
var
|
|
1572
|
+
var log5 = (msg) => log("codex-capture", msg);
|
|
1151
1573
|
function resolveEmbedDaemonPath() {
|
|
1152
|
-
return
|
|
1574
|
+
return join13(dirname5(fileURLToPath2(import.meta.url)), "embeddings", "embed-daemon.js");
|
|
1153
1575
|
}
|
|
1154
|
-
var __bundleDir =
|
|
1576
|
+
var __bundleDir = dirname5(fileURLToPath2(import.meta.url));
|
|
1155
1577
|
var PLUGIN_VERSION = getInstalledVersion(__bundleDir, ".codex-plugin") ?? "";
|
|
1578
|
+
if (!embeddingsDisabled()) {
|
|
1579
|
+
try {
|
|
1580
|
+
ensurePluginNodeModulesLink({ bundleDir: __bundleDir });
|
|
1581
|
+
} catch {
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1156
1584
|
var CAPTURE = process.env.HIVEMIND_CAPTURE !== "false";
|
|
1157
1585
|
async function main() {
|
|
1158
1586
|
if (!CAPTURE)
|
|
@@ -1160,7 +1588,7 @@ async function main() {
|
|
|
1160
1588
|
const input = await readStdin();
|
|
1161
1589
|
const config = loadConfig();
|
|
1162
1590
|
if (!config) {
|
|
1163
|
-
|
|
1591
|
+
log5("no config");
|
|
1164
1592
|
return;
|
|
1165
1593
|
}
|
|
1166
1594
|
const sessionsTable = config.sessionsTableName;
|
|
@@ -1177,7 +1605,7 @@ async function main() {
|
|
|
1177
1605
|
};
|
|
1178
1606
|
let entry;
|
|
1179
1607
|
if (input.hook_event_name === "UserPromptSubmit" && input.prompt !== void 0) {
|
|
1180
|
-
|
|
1608
|
+
log5(`user session=${input.session_id}`);
|
|
1181
1609
|
entry = {
|
|
1182
1610
|
id: crypto.randomUUID(),
|
|
1183
1611
|
...meta,
|
|
@@ -1185,7 +1613,7 @@ async function main() {
|
|
|
1185
1613
|
content: input.prompt
|
|
1186
1614
|
};
|
|
1187
1615
|
} else if (input.hook_event_name === "PostToolUse" && input.tool_name !== void 0) {
|
|
1188
|
-
|
|
1616
|
+
log5(`tool=${input.tool_name} session=${input.session_id}`);
|
|
1189
1617
|
entry = {
|
|
1190
1618
|
id: crypto.randomUUID(),
|
|
1191
1619
|
...meta,
|
|
@@ -1196,12 +1624,12 @@ async function main() {
|
|
|
1196
1624
|
tool_response: JSON.stringify(input.tool_response)
|
|
1197
1625
|
};
|
|
1198
1626
|
} else {
|
|
1199
|
-
|
|
1627
|
+
log5(`unknown event: ${input.hook_event_name}, skipping`);
|
|
1200
1628
|
return;
|
|
1201
1629
|
}
|
|
1202
1630
|
const sessionPath = buildSessionPath(config, input.session_id);
|
|
1203
1631
|
const line = JSON.stringify(entry);
|
|
1204
|
-
|
|
1632
|
+
log5(`writing to ${sessionPath}`);
|
|
1205
1633
|
const projectName = (input.cwd ?? "").split("/").pop() || "unknown";
|
|
1206
1634
|
const filename = sessionPath.split("/").pop() ?? "";
|
|
1207
1635
|
const jsonForSql = line.replace(/'/g, "''");
|
|
@@ -1212,14 +1640,14 @@ async function main() {
|
|
|
1212
1640
|
await api.query(insertSql);
|
|
1213
1641
|
} catch (e) {
|
|
1214
1642
|
if (e.message?.includes("permission denied") || e.message?.includes("does not exist")) {
|
|
1215
|
-
|
|
1643
|
+
log5("table missing, creating and retrying");
|
|
1216
1644
|
await api.ensureSessionsTable(sessionsTable);
|
|
1217
1645
|
await api.query(insertSql);
|
|
1218
1646
|
} else {
|
|
1219
1647
|
throw e;
|
|
1220
1648
|
}
|
|
1221
1649
|
}
|
|
1222
|
-
|
|
1650
|
+
log5("capture ok");
|
|
1223
1651
|
maybeTriggerPeriodicSummary(input.session_id, input.cwd ?? "", config);
|
|
1224
1652
|
}
|
|
1225
1653
|
function maybeTriggerPeriodicSummary(sessionId, cwd, config) {
|
|
@@ -1231,7 +1659,7 @@ function maybeTriggerPeriodicSummary(sessionId, cwd, config) {
|
|
|
1231
1659
|
if (!shouldTrigger(state, cfg))
|
|
1232
1660
|
return;
|
|
1233
1661
|
if (!tryAcquireLock(sessionId)) {
|
|
1234
|
-
|
|
1662
|
+
log5(`periodic trigger suppressed (lock held) session=${sessionId}`);
|
|
1235
1663
|
return;
|
|
1236
1664
|
}
|
|
1237
1665
|
wikiLog(`Periodic: threshold hit (total=${state.totalCount}, since=${state.totalCount - state.lastSummaryCount}, N=${cfg.everyNMessages}, hours=${cfg.everyHours})`);
|
|
@@ -1244,19 +1672,19 @@ function maybeTriggerPeriodicSummary(sessionId, cwd, config) {
|
|
|
1244
1672
|
reason: "Periodic"
|
|
1245
1673
|
});
|
|
1246
1674
|
} catch (e) {
|
|
1247
|
-
|
|
1675
|
+
log5(`periodic spawn failed: ${e.message}`);
|
|
1248
1676
|
try {
|
|
1249
1677
|
releaseLock(sessionId);
|
|
1250
1678
|
} catch (releaseErr) {
|
|
1251
|
-
|
|
1679
|
+
log5(`releaseLock after periodic spawn failure also failed: ${releaseErr.message}`);
|
|
1252
1680
|
}
|
|
1253
1681
|
throw e;
|
|
1254
1682
|
}
|
|
1255
1683
|
} catch (e) {
|
|
1256
|
-
|
|
1684
|
+
log5(`periodic trigger error: ${e.message}`);
|
|
1257
1685
|
}
|
|
1258
1686
|
}
|
|
1259
1687
|
main().catch((e) => {
|
|
1260
|
-
|
|
1688
|
+
log5(`fatal: ${e.message}`);
|
|
1261
1689
|
process.exit(0);
|
|
1262
1690
|
});
|