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