@deeplake/hivemind 0.7.31 → 0.7.33
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 +594 -267
- package/codex/bundle/capture.js +585 -124
- package/codex/bundle/commands/auth-login.js +163 -30
- package/codex/bundle/embeddings/embed-daemon.js +55 -4
- package/codex/bundle/pre-tool-use.js +475 -85
- package/codex/bundle/session-start-setup.js +193 -60
- package/codex/bundle/session-start.js +221 -88
- package/codex/bundle/shell/deeplake-shell.js +458 -68
- package/codex/bundle/stop.js +565 -175
- package/codex/bundle/wiki-worker.js +258 -19
- package/cursor/bundle/capture.js +660 -199
- package/cursor/bundle/commands/auth-login.js +163 -30
- package/cursor/bundle/embeddings/embed-daemon.js +55 -4
- package/cursor/bundle/pre-tool-use.js +460 -70
- package/cursor/bundle/session-start.js +271 -131
- package/cursor/bundle/shell/deeplake-shell.js +458 -68
- package/cursor/bundle/wiki-worker.js +258 -19
- package/hermes/bundle/capture.js +661 -200
- package/hermes/bundle/commands/auth-login.js +163 -30
- package/hermes/bundle/embeddings/embed-daemon.js +55 -4
- package/hermes/bundle/pre-tool-use.js +459 -69
- package/hermes/bundle/session-start.js +268 -128
- package/hermes/bundle/shell/deeplake-shell.js +458 -68
- package/hermes/bundle/wiki-worker.js +258 -19
- package/mcp/bundle/server.js +190 -57
- package/openclaw/dist/index.js +160 -32
- package/openclaw/openclaw.plugin.json +1 -1
- package/openclaw/package.json +1 -1
- package/package.json +1 -1
package/hermes/bundle/capture.js
CHANGED
|
@@ -16,21 +16,21 @@ __export(index_marker_store_exports, {
|
|
|
16
16
|
hasFreshIndexMarker: () => hasFreshIndexMarker,
|
|
17
17
|
writeIndexMarker: () => writeIndexMarker
|
|
18
18
|
});
|
|
19
|
-
import { existsSync as existsSync2, mkdirSync, readFileSync as
|
|
20
|
-
import { join as
|
|
19
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
20
|
+
import { join as join5 } from "node:path";
|
|
21
21
|
import { tmpdir } from "node:os";
|
|
22
22
|
function getIndexMarkerDir() {
|
|
23
|
-
return process.env.HIVEMIND_INDEX_MARKER_DIR ??
|
|
23
|
+
return process.env.HIVEMIND_INDEX_MARKER_DIR ?? join5(tmpdir(), "hivemind-deeplake-indexes");
|
|
24
24
|
}
|
|
25
25
|
function buildIndexMarkerPath(workspaceId, orgId, table, suffix) {
|
|
26
26
|
const markerKey = [workspaceId, orgId, table, suffix].join("__").replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
27
|
-
return
|
|
27
|
+
return join5(getIndexMarkerDir(), `${markerKey}.json`);
|
|
28
28
|
}
|
|
29
29
|
function hasFreshIndexMarker(markerPath) {
|
|
30
30
|
if (!existsSync2(markerPath))
|
|
31
31
|
return false;
|
|
32
32
|
try {
|
|
33
|
-
const raw = JSON.parse(
|
|
33
|
+
const raw = JSON.parse(readFileSync4(markerPath, "utf-8"));
|
|
34
34
|
const updatedAt = raw.updatedAt ? new Date(raw.updatedAt).getTime() : NaN;
|
|
35
35
|
if (!Number.isFinite(updatedAt) || Date.now() - updatedAt > INDEX_MARKER_TTL_MS)
|
|
36
36
|
return false;
|
|
@@ -40,8 +40,8 @@ function hasFreshIndexMarker(markerPath) {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
function writeIndexMarker(markerPath) {
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
mkdirSync3(getIndexMarkerDir(), { recursive: true });
|
|
44
|
+
writeFileSync3(markerPath, JSON.stringify({ updatedAt: (/* @__PURE__ */ new Date()).toISOString() }), "utf-8");
|
|
45
45
|
}
|
|
46
46
|
var INDEX_MARKER_TTL_MS;
|
|
47
47
|
var init_index_marker_store = __esm({
|
|
@@ -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
|
}
|
|
@@ -146,6 +146,125 @@ function deeplakeClientHeader() {
|
|
|
146
146
|
return { [DEEPLAKE_CLIENT_HEADER]: deeplakeClientValue() };
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
+
// dist/src/notifications/queue.js
|
|
150
|
+
import { readFileSync as readFileSync2, writeFileSync, renameSync, mkdirSync, openSync, closeSync, unlinkSync, statSync } from "node:fs";
|
|
151
|
+
import { join as join3, resolve } from "node:path";
|
|
152
|
+
import { homedir as homedir3 } from "node:os";
|
|
153
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
154
|
+
var log2 = (msg) => log("notifications-queue", msg);
|
|
155
|
+
var LOCK_RETRY_MAX = 50;
|
|
156
|
+
var LOCK_RETRY_BASE_MS = 5;
|
|
157
|
+
var LOCK_STALE_MS = 5e3;
|
|
158
|
+
function queuePath() {
|
|
159
|
+
return join3(homedir3(), ".deeplake", "notifications-queue.json");
|
|
160
|
+
}
|
|
161
|
+
function lockPath() {
|
|
162
|
+
return `${queuePath()}.lock`;
|
|
163
|
+
}
|
|
164
|
+
function readQueue() {
|
|
165
|
+
try {
|
|
166
|
+
const raw = readFileSync2(queuePath(), "utf-8");
|
|
167
|
+
const parsed = JSON.parse(raw);
|
|
168
|
+
if (!parsed || !Array.isArray(parsed.queue)) {
|
|
169
|
+
log2(`queue malformed \u2192 treating as empty`);
|
|
170
|
+
return { queue: [] };
|
|
171
|
+
}
|
|
172
|
+
return { queue: parsed.queue };
|
|
173
|
+
} catch {
|
|
174
|
+
return { queue: [] };
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function _isQueuePathInsideHome(path, home) {
|
|
178
|
+
const r = resolve(path);
|
|
179
|
+
const h = resolve(home);
|
|
180
|
+
return r.startsWith(h + "/") || r === h;
|
|
181
|
+
}
|
|
182
|
+
function writeQueue(q) {
|
|
183
|
+
const path = queuePath();
|
|
184
|
+
const home = resolve(homedir3());
|
|
185
|
+
if (!_isQueuePathInsideHome(path, home)) {
|
|
186
|
+
throw new Error(`notifications-queue write blocked: ${path} is outside ${home}`);
|
|
187
|
+
}
|
|
188
|
+
mkdirSync(join3(home, ".deeplake"), { recursive: true, mode: 448 });
|
|
189
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
190
|
+
writeFileSync(tmp, JSON.stringify(q, null, 2), { mode: 384 });
|
|
191
|
+
renameSync(tmp, path);
|
|
192
|
+
}
|
|
193
|
+
async function withQueueLock(fn) {
|
|
194
|
+
const path = lockPath();
|
|
195
|
+
mkdirSync(join3(homedir3(), ".deeplake"), { recursive: true, mode: 448 });
|
|
196
|
+
let fd = null;
|
|
197
|
+
for (let attempt = 0; attempt < LOCK_RETRY_MAX; attempt++) {
|
|
198
|
+
try {
|
|
199
|
+
fd = openSync(path, "wx", 384);
|
|
200
|
+
break;
|
|
201
|
+
} catch (e) {
|
|
202
|
+
const code = e.code;
|
|
203
|
+
if (code !== "EEXIST")
|
|
204
|
+
throw e;
|
|
205
|
+
try {
|
|
206
|
+
const age = Date.now() - statSync(path).mtimeMs;
|
|
207
|
+
if (age > LOCK_STALE_MS) {
|
|
208
|
+
unlinkSync(path);
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
} catch {
|
|
212
|
+
}
|
|
213
|
+
const delay = LOCK_RETRY_BASE_MS * (attempt + 1);
|
|
214
|
+
await sleep(delay);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (fd === null) {
|
|
218
|
+
log2(`lock acquisition gave up after ${LOCK_RETRY_MAX} attempts \u2014 proceeding unlocked (last-writer-wins)`);
|
|
219
|
+
return fn();
|
|
220
|
+
}
|
|
221
|
+
try {
|
|
222
|
+
return fn();
|
|
223
|
+
} finally {
|
|
224
|
+
try {
|
|
225
|
+
closeSync(fd);
|
|
226
|
+
} catch {
|
|
227
|
+
}
|
|
228
|
+
try {
|
|
229
|
+
unlinkSync(path);
|
|
230
|
+
} catch {
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
function sameDedupKey(a, b) {
|
|
235
|
+
if (a.id !== b.id)
|
|
236
|
+
return false;
|
|
237
|
+
return JSON.stringify(a.dedupKey) === JSON.stringify(b.dedupKey);
|
|
238
|
+
}
|
|
239
|
+
async function enqueueNotification(n) {
|
|
240
|
+
await withQueueLock(() => {
|
|
241
|
+
const q = readQueue();
|
|
242
|
+
if (q.queue.some((existing) => sameDedupKey(existing, n))) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
q.queue.push(n);
|
|
246
|
+
writeQueue(q);
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// dist/src/commands/auth-creds.js
|
|
251
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2 } from "node:fs";
|
|
252
|
+
import { join as join4 } from "node:path";
|
|
253
|
+
import { homedir as homedir4 } from "node:os";
|
|
254
|
+
function configDir() {
|
|
255
|
+
return join4(homedir4(), ".deeplake");
|
|
256
|
+
}
|
|
257
|
+
function credsPath() {
|
|
258
|
+
return join4(configDir(), "credentials.json");
|
|
259
|
+
}
|
|
260
|
+
function loadCredentials() {
|
|
261
|
+
try {
|
|
262
|
+
return JSON.parse(readFileSync3(credsPath(), "utf-8"));
|
|
263
|
+
} catch {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
149
268
|
// dist/src/deeplake-api.js
|
|
150
269
|
var indexMarkerStorePromise = null;
|
|
151
270
|
function getIndexMarkerStore() {
|
|
@@ -153,7 +272,7 @@ function getIndexMarkerStore() {
|
|
|
153
272
|
indexMarkerStorePromise = Promise.resolve().then(() => (init_index_marker_store(), index_marker_store_exports));
|
|
154
273
|
return indexMarkerStorePromise;
|
|
155
274
|
}
|
|
156
|
-
var
|
|
275
|
+
var log3 = (msg) => log("sdk", msg);
|
|
157
276
|
function summarizeSql(sql, maxLen = 220) {
|
|
158
277
|
const compact = sql.replace(/\s+/g, " ").trim();
|
|
159
278
|
return compact.length > maxLen ? `${compact.slice(0, maxLen)}...` : compact;
|
|
@@ -165,7 +284,38 @@ function traceSql(msg) {
|
|
|
165
284
|
process.stderr.write(`[deeplake-sql] ${msg}
|
|
166
285
|
`);
|
|
167
286
|
if (process.env.HIVEMIND_DEBUG === "1")
|
|
168
|
-
|
|
287
|
+
log3(msg);
|
|
288
|
+
}
|
|
289
|
+
var _signalledBalanceExhausted = false;
|
|
290
|
+
function maybeSignalBalanceExhausted(status, bodyText) {
|
|
291
|
+
if (status !== 402)
|
|
292
|
+
return;
|
|
293
|
+
if (!bodyText.includes("balance_cents"))
|
|
294
|
+
return;
|
|
295
|
+
if (_signalledBalanceExhausted)
|
|
296
|
+
return;
|
|
297
|
+
_signalledBalanceExhausted = true;
|
|
298
|
+
log3(`balance exhausted \u2014 enqueuing session-start banner (body=${bodyText.slice(0, 120)})`);
|
|
299
|
+
enqueueNotification({
|
|
300
|
+
id: "balance-exhausted",
|
|
301
|
+
severity: "warn",
|
|
302
|
+
transient: true,
|
|
303
|
+
title: "Hivemind credits exhausted \u2014 top up to keep capturing",
|
|
304
|
+
body: `Sessions are not being saved and memory recall is returning empty. Top up at ${billingUrl()} to restore capture and recall.`,
|
|
305
|
+
dedupKey: { reason: "balance-zero" }
|
|
306
|
+
}).catch((e) => {
|
|
307
|
+
log3(`enqueue balance-exhausted failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
function billingUrl() {
|
|
311
|
+
try {
|
|
312
|
+
const c = loadCredentials();
|
|
313
|
+
if (c?.orgName && c?.workspaceId) {
|
|
314
|
+
return `https://deeplake.ai/${encodeURIComponent(c.orgName)}/workspace/${encodeURIComponent(c.workspaceId)}/billing`;
|
|
315
|
+
}
|
|
316
|
+
} catch {
|
|
317
|
+
}
|
|
318
|
+
return "https://deeplake.ai";
|
|
169
319
|
}
|
|
170
320
|
var RETRYABLE_CODES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
|
|
171
321
|
var MAX_RETRIES = 3;
|
|
@@ -174,8 +324,8 @@ var MAX_CONCURRENCY = 5;
|
|
|
174
324
|
function getQueryTimeoutMs() {
|
|
175
325
|
return Number(process.env.HIVEMIND_QUERY_TIMEOUT_MS ?? 1e4);
|
|
176
326
|
}
|
|
177
|
-
function
|
|
178
|
-
return new Promise((
|
|
327
|
+
function sleep2(ms) {
|
|
328
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
179
329
|
}
|
|
180
330
|
function isTimeoutError(error) {
|
|
181
331
|
const name = error instanceof Error ? error.name.toLowerCase() : "";
|
|
@@ -205,7 +355,7 @@ var Semaphore = class {
|
|
|
205
355
|
this.active++;
|
|
206
356
|
return;
|
|
207
357
|
}
|
|
208
|
-
await new Promise((
|
|
358
|
+
await new Promise((resolve2) => this.waiting.push(resolve2));
|
|
209
359
|
}
|
|
210
360
|
release() {
|
|
211
361
|
this.active--;
|
|
@@ -276,8 +426,8 @@ var DeeplakeApi = class {
|
|
|
276
426
|
lastError = e instanceof Error ? e : new Error(String(e));
|
|
277
427
|
if (attempt < MAX_RETRIES) {
|
|
278
428
|
const delay = BASE_DELAY_MS * Math.pow(2, attempt) + Math.random() * 200;
|
|
279
|
-
|
|
280
|
-
await
|
|
429
|
+
log3(`query retry ${attempt + 1}/${MAX_RETRIES} (fetch error: ${lastError.message}) in ${delay.toFixed(0)}ms`);
|
|
430
|
+
await sleep2(delay);
|
|
281
431
|
continue;
|
|
282
432
|
}
|
|
283
433
|
throw lastError;
|
|
@@ -293,10 +443,11 @@ var DeeplakeApi = class {
|
|
|
293
443
|
const alreadyExists = resp.status === 500 && isDuplicateIndexError(text);
|
|
294
444
|
if (!alreadyExists && attempt < MAX_RETRIES && (RETRYABLE_CODES.has(resp.status) || retryable403)) {
|
|
295
445
|
const delay = BASE_DELAY_MS * Math.pow(2, attempt) + Math.random() * 200;
|
|
296
|
-
|
|
297
|
-
await
|
|
446
|
+
log3(`query retry ${attempt + 1}/${MAX_RETRIES} (${resp.status}) in ${delay.toFixed(0)}ms`);
|
|
447
|
+
await sleep2(delay);
|
|
298
448
|
continue;
|
|
299
449
|
}
|
|
450
|
+
maybeSignalBalanceExhausted(resp.status, text);
|
|
300
451
|
throw new Error(`Query failed: ${resp.status}: ${text.slice(0, 200)}`);
|
|
301
452
|
}
|
|
302
453
|
throw lastError ?? new Error("Query failed: max retries exceeded");
|
|
@@ -317,7 +468,7 @@ var DeeplakeApi = class {
|
|
|
317
468
|
const chunk = rows.slice(i, i + CONCURRENCY);
|
|
318
469
|
await Promise.allSettled(chunk.map((r) => this.upsertRowSql(r)));
|
|
319
470
|
}
|
|
320
|
-
|
|
471
|
+
log3(`commit: ${rows.length} rows`);
|
|
321
472
|
}
|
|
322
473
|
async upsertRowSql(row) {
|
|
323
474
|
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -373,7 +524,7 @@ var DeeplakeApi = class {
|
|
|
373
524
|
markers.writeIndexMarker(markerPath);
|
|
374
525
|
return;
|
|
375
526
|
}
|
|
376
|
-
|
|
527
|
+
log3(`index "${indexName}" skipped: ${e.message}`);
|
|
377
528
|
}
|
|
378
529
|
}
|
|
379
530
|
/**
|
|
@@ -463,13 +614,13 @@ var DeeplakeApi = class {
|
|
|
463
614
|
};
|
|
464
615
|
}
|
|
465
616
|
if (attempt < MAX_RETRIES && RETRYABLE_CODES.has(resp.status)) {
|
|
466
|
-
await
|
|
617
|
+
await sleep2(BASE_DELAY_MS * Math.pow(2, attempt) + Math.random() * 200);
|
|
467
618
|
continue;
|
|
468
619
|
}
|
|
469
620
|
return { tables: [], cacheable: false };
|
|
470
621
|
} catch {
|
|
471
622
|
if (attempt < MAX_RETRIES) {
|
|
472
|
-
await
|
|
623
|
+
await sleep2(BASE_DELAY_MS * Math.pow(2, attempt));
|
|
473
624
|
continue;
|
|
474
625
|
}
|
|
475
626
|
return { tables: [], cacheable: false };
|
|
@@ -497,9 +648,9 @@ var DeeplakeApi = class {
|
|
|
497
648
|
} catch (err) {
|
|
498
649
|
lastErr = err;
|
|
499
650
|
const msg = err instanceof Error ? err.message : String(err);
|
|
500
|
-
|
|
651
|
+
log3(`CREATE TABLE "${label}" attempt ${attempt + 1}/${OUTER_BACKOFFS_MS.length + 1} failed: ${msg}`);
|
|
501
652
|
if (attempt < OUTER_BACKOFFS_MS.length) {
|
|
502
|
-
await
|
|
653
|
+
await sleep2(OUTER_BACKOFFS_MS[attempt]);
|
|
503
654
|
}
|
|
504
655
|
}
|
|
505
656
|
}
|
|
@@ -510,9 +661,9 @@ var DeeplakeApi = class {
|
|
|
510
661
|
const tbl = sqlIdent(name ?? this.tableName);
|
|
511
662
|
const tables = await this.listTables();
|
|
512
663
|
if (!tables.includes(tbl)) {
|
|
513
|
-
|
|
664
|
+
log3(`table "${tbl}" not found, creating`);
|
|
514
665
|
await this.createTableWithRetry(`CREATE TABLE IF NOT EXISTS "${tbl}" (id TEXT NOT NULL DEFAULT '', path TEXT NOT NULL DEFAULT '', filename TEXT NOT NULL DEFAULT '', summary TEXT NOT NULL DEFAULT '', summary_embedding FLOAT4[], author TEXT NOT NULL DEFAULT '', mime_type TEXT NOT NULL DEFAULT 'text/plain', size_bytes BIGINT NOT NULL DEFAULT 0, project TEXT NOT NULL DEFAULT '', description TEXT NOT NULL DEFAULT '', agent TEXT NOT NULL DEFAULT '', plugin_version TEXT NOT NULL DEFAULT '', creation_date TEXT NOT NULL DEFAULT '', last_update_date TEXT NOT NULL DEFAULT '') USING deeplake`, tbl);
|
|
515
|
-
|
|
666
|
+
log3(`table "${tbl}" created`);
|
|
516
667
|
if (!tables.includes(tbl))
|
|
517
668
|
this._tablesCache = [...tables, tbl];
|
|
518
669
|
}
|
|
@@ -525,9 +676,9 @@ var DeeplakeApi = class {
|
|
|
525
676
|
const safe = sqlIdent(name);
|
|
526
677
|
const tables = await this.listTables();
|
|
527
678
|
if (!tables.includes(safe)) {
|
|
528
|
-
|
|
679
|
+
log3(`table "${safe}" not found, creating`);
|
|
529
680
|
await this.createTableWithRetry(`CREATE TABLE IF NOT EXISTS "${safe}" (id TEXT NOT NULL DEFAULT '', path TEXT NOT NULL DEFAULT '', filename TEXT NOT NULL DEFAULT '', message JSONB, message_embedding FLOAT4[], author TEXT NOT NULL DEFAULT '', mime_type TEXT NOT NULL DEFAULT 'application/json', size_bytes BIGINT NOT NULL DEFAULT 0, project TEXT NOT NULL DEFAULT '', description TEXT NOT NULL DEFAULT '', agent TEXT NOT NULL DEFAULT '', plugin_version TEXT NOT NULL DEFAULT '', creation_date TEXT NOT NULL DEFAULT '', last_update_date TEXT NOT NULL DEFAULT '') USING deeplake`, safe);
|
|
530
|
-
|
|
681
|
+
log3(`table "${safe}" created`);
|
|
531
682
|
if (!tables.includes(safe))
|
|
532
683
|
this._tablesCache = [...tables, safe];
|
|
533
684
|
}
|
|
@@ -550,9 +701,9 @@ var DeeplakeApi = class {
|
|
|
550
701
|
const safe = sqlIdent(name);
|
|
551
702
|
const tables = await this.listTables();
|
|
552
703
|
if (!tables.includes(safe)) {
|
|
553
|
-
|
|
704
|
+
log3(`table "${safe}" not found, creating`);
|
|
554
705
|
await this.createTableWithRetry(`CREATE TABLE IF NOT EXISTS "${safe}" (id TEXT NOT NULL DEFAULT '', name TEXT NOT NULL DEFAULT '', project TEXT NOT NULL DEFAULT '', project_key TEXT NOT NULL DEFAULT '', local_path TEXT NOT NULL DEFAULT '', install TEXT NOT NULL DEFAULT 'project', source_sessions TEXT NOT NULL DEFAULT '[]', source_agent TEXT NOT NULL DEFAULT '', scope TEXT NOT NULL DEFAULT 'me', author TEXT NOT NULL DEFAULT '', description TEXT NOT NULL DEFAULT '', trigger_text TEXT NOT NULL DEFAULT '', body TEXT NOT NULL DEFAULT '', version BIGINT NOT NULL DEFAULT 1, created_at TEXT NOT NULL DEFAULT '', updated_at TEXT NOT NULL DEFAULT '') USING deeplake`, safe);
|
|
555
|
-
|
|
706
|
+
log3(`table "${safe}" created`);
|
|
556
707
|
if (!tables.includes(safe))
|
|
557
708
|
this._tablesCache = [...tables, safe];
|
|
558
709
|
}
|
|
@@ -569,9 +720,9 @@ function buildSessionPath(config, sessionId) {
|
|
|
569
720
|
// dist/src/embeddings/client.js
|
|
570
721
|
import { connect } from "node:net";
|
|
571
722
|
import { spawn } from "node:child_process";
|
|
572
|
-
import { openSync, closeSync, writeSync, unlinkSync, existsSync as existsSync3, readFileSync as
|
|
573
|
-
import { homedir as
|
|
574
|
-
import { join as
|
|
723
|
+
import { openSync as openSync2, closeSync as closeSync2, writeSync, unlinkSync as unlinkSync3, existsSync as existsSync3, readFileSync as readFileSync5 } from "node:fs";
|
|
724
|
+
import { homedir as homedir5 } from "node:os";
|
|
725
|
+
import { join as join6 } from "node:path";
|
|
575
726
|
|
|
576
727
|
// dist/src/embeddings/protocol.js
|
|
577
728
|
var DEFAULT_SOCKET_DIR = "/tmp";
|
|
@@ -585,12 +736,13 @@ function pidPathFor(uid, dir = DEFAULT_SOCKET_DIR) {
|
|
|
585
736
|
}
|
|
586
737
|
|
|
587
738
|
// dist/src/embeddings/client.js
|
|
588
|
-
var SHARED_DAEMON_PATH =
|
|
589
|
-
var
|
|
739
|
+
var SHARED_DAEMON_PATH = join6(homedir5(), ".hivemind", "embed-deps", "embed-daemon.js");
|
|
740
|
+
var log4 = (m) => log("embed-client", m);
|
|
590
741
|
function getUid() {
|
|
591
742
|
const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
|
|
592
743
|
return uid !== void 0 ? String(uid) : process.env.USER ?? "default";
|
|
593
744
|
}
|
|
745
|
+
var _recycledStuckDaemon = false;
|
|
594
746
|
var EmbedClient = class {
|
|
595
747
|
socketPath;
|
|
596
748
|
pidPath;
|
|
@@ -599,6 +751,7 @@ var EmbedClient = class {
|
|
|
599
751
|
autoSpawn;
|
|
600
752
|
spawnWaitMs;
|
|
601
753
|
nextId = 0;
|
|
754
|
+
helloVerified = false;
|
|
602
755
|
constructor(opts = {}) {
|
|
603
756
|
const uid = getUid();
|
|
604
757
|
const dir = opts.socketDir ?? "/tmp";
|
|
@@ -615,8 +768,33 @@ var EmbedClient = class {
|
|
|
615
768
|
*
|
|
616
769
|
* Fire-and-forget spawn on miss: if the daemon isn't up, this call returns
|
|
617
770
|
* null AND kicks off a background spawn. The next call finds a ready daemon.
|
|
771
|
+
*
|
|
772
|
+
* Stuck-daemon recycle: if the daemon returns a transformers-missing
|
|
773
|
+
* error (typical after a marketplace upgrade left an older daemon process
|
|
774
|
+
* alive but with no node_modules accessible from its bundle path), we
|
|
775
|
+
* SIGTERM it and clear its sock/pid so the very next call spawns a fresh
|
|
776
|
+
* daemon from the current bundle. Without this, the stuck daemon would
|
|
777
|
+
* keep poisoning every session until its 10-minute idle-out fires.
|
|
618
778
|
*/
|
|
619
779
|
async embed(text, kind = "document") {
|
|
780
|
+
const v = await this.embedAttempt(text, kind);
|
|
781
|
+
if (v !== "recycled")
|
|
782
|
+
return v;
|
|
783
|
+
if (!this.autoSpawn)
|
|
784
|
+
return null;
|
|
785
|
+
this.trySpawnDaemon();
|
|
786
|
+
await this.waitForDaemonReady();
|
|
787
|
+
const retry = await this.embedAttempt(text, kind);
|
|
788
|
+
return retry === "recycled" ? null : retry;
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* One round-trip: connect → verify → embed. Returns:
|
|
792
|
+
* - number[] : embedding vector (happy path)
|
|
793
|
+
* - null : timeout / daemon error / transformers-missing
|
|
794
|
+
* - "recycled": verifyDaemonOnce killed the daemon mid-call;
|
|
795
|
+
* caller should respawn and retry once.
|
|
796
|
+
*/
|
|
797
|
+
async embedAttempt(text, kind) {
|
|
620
798
|
let sock;
|
|
621
799
|
try {
|
|
622
800
|
sock = await this.connectOnce();
|
|
@@ -626,17 +804,25 @@ var EmbedClient = class {
|
|
|
626
804
|
return null;
|
|
627
805
|
}
|
|
628
806
|
try {
|
|
807
|
+
const recycled = await this.verifyDaemonOnce(sock);
|
|
808
|
+
if (recycled) {
|
|
809
|
+
return "recycled";
|
|
810
|
+
}
|
|
629
811
|
const id = String(++this.nextId);
|
|
630
812
|
const req = { op: "embed", id, kind, text };
|
|
631
813
|
const resp = await this.sendAndWait(sock, req);
|
|
632
814
|
if (resp.error || !("embedding" in resp) || !resp.embedding) {
|
|
633
|
-
|
|
815
|
+
const err = resp.error ?? "no embedding";
|
|
816
|
+
log4(`embed err: ${err}`);
|
|
817
|
+
if (isTransformersMissingError(err)) {
|
|
818
|
+
this.handleTransformersMissing(err);
|
|
819
|
+
}
|
|
634
820
|
return null;
|
|
635
821
|
}
|
|
636
822
|
return resp.embedding;
|
|
637
823
|
} catch (e) {
|
|
638
824
|
const err = e instanceof Error ? e.message : String(e);
|
|
639
|
-
|
|
825
|
+
log4(`embed failed: ${err}`);
|
|
640
826
|
return null;
|
|
641
827
|
} finally {
|
|
642
828
|
try {
|
|
@@ -645,6 +831,123 @@ var EmbedClient = class {
|
|
|
645
831
|
}
|
|
646
832
|
}
|
|
647
833
|
}
|
|
834
|
+
/**
|
|
835
|
+
* Poll for the sock file to come back after `trySpawnDaemon` — used by
|
|
836
|
+
* the recycle retry path. Best-effort: caps at `spawnWaitMs` and
|
|
837
|
+
* returns regardless so the retry attempt can run.
|
|
838
|
+
*/
|
|
839
|
+
async waitForDaemonReady() {
|
|
840
|
+
const deadline = Date.now() + this.spawnWaitMs;
|
|
841
|
+
while (Date.now() < deadline) {
|
|
842
|
+
if (existsSync3(this.socketPath))
|
|
843
|
+
return;
|
|
844
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* Send a `hello` on first successful connect per EmbedClient instance.
|
|
849
|
+
* If the daemon answers with a path that doesn't match our configured
|
|
850
|
+
* daemonEntry — typical after a marketplace upgrade replaced the bundle
|
|
851
|
+
* — SIGTERM the daemon + clear sock/pid so the next call spawns from the
|
|
852
|
+
* current bundle.
|
|
853
|
+
*
|
|
854
|
+
* `helloVerified` is set ONLY after we've seen a compatible response,
|
|
855
|
+
* so a transient probe failure or a recycle-triggering mismatch leaves
|
|
856
|
+
* the flag false; the next reconnect re-runs verification against
|
|
857
|
+
* whatever daemon is then live (typically the fresh spawn).
|
|
858
|
+
*/
|
|
859
|
+
async verifyDaemonOnce(sock) {
|
|
860
|
+
if (this.helloVerified)
|
|
861
|
+
return false;
|
|
862
|
+
if (!this.daemonEntry) {
|
|
863
|
+
this.helloVerified = true;
|
|
864
|
+
return false;
|
|
865
|
+
}
|
|
866
|
+
const id = String(++this.nextId);
|
|
867
|
+
const req = { op: "hello", id };
|
|
868
|
+
let resp;
|
|
869
|
+
try {
|
|
870
|
+
resp = await this.sendAndWait(sock, req);
|
|
871
|
+
} catch (e) {
|
|
872
|
+
log4(`hello probe failed (inconclusive, will retry next connect): ${e instanceof Error ? e.message : String(e)}`);
|
|
873
|
+
return false;
|
|
874
|
+
}
|
|
875
|
+
const hello = resp;
|
|
876
|
+
if (_recycledStuckDaemon) {
|
|
877
|
+
return false;
|
|
878
|
+
}
|
|
879
|
+
if (!hello.daemonPath) {
|
|
880
|
+
_recycledStuckDaemon = true;
|
|
881
|
+
log4(`daemon does not implement hello (older protocol); recycling`);
|
|
882
|
+
this.recycleDaemon(hello.pid);
|
|
883
|
+
return true;
|
|
884
|
+
}
|
|
885
|
+
if (hello.daemonPath !== this.daemonEntry && !existsSync3(hello.daemonPath)) {
|
|
886
|
+
_recycledStuckDaemon = true;
|
|
887
|
+
log4(`daemon path no longer on disk \u2014 running=${hello.daemonPath} (gone) expected=${this.daemonEntry}; recycling`);
|
|
888
|
+
this.recycleDaemon(hello.pid);
|
|
889
|
+
return true;
|
|
890
|
+
}
|
|
891
|
+
this.helloVerified = true;
|
|
892
|
+
return false;
|
|
893
|
+
}
|
|
894
|
+
/**
|
|
895
|
+
* On a transformers-missing error from the daemon, SIGTERM the stuck
|
|
896
|
+
* daemon (the bundle daemon that can't find its deps) and clear
|
|
897
|
+
* sock/pid so the next call spawns fresh.
|
|
898
|
+
*
|
|
899
|
+
* Previously this also enqueued a user-visible "Hivemind embeddings
|
|
900
|
+
* disabled — deps missing" notification telling the user to run
|
|
901
|
+
* `hivemind embeddings install`. The notification was removed because
|
|
902
|
+
* (a) the recycle alone often fixes the issue silently, and (b) the
|
|
903
|
+
* warning kept stacking on top of the primary session-start banner
|
|
904
|
+
* which clashed with the single-slot priority model. The `detail`
|
|
905
|
+
* argument is retained for future telemetry / debug logging.
|
|
906
|
+
*/
|
|
907
|
+
handleTransformersMissing(_detail) {
|
|
908
|
+
if (!_recycledStuckDaemon) {
|
|
909
|
+
_recycledStuckDaemon = true;
|
|
910
|
+
this.recycleDaemon(null);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
/**
|
|
914
|
+
* Best-effort SIGTERM + sock/pid cleanup. Tolerant of every missing-file
|
|
915
|
+
* combination and dead-PID cases.
|
|
916
|
+
*
|
|
917
|
+
* Identity check: gate the SIGTERM on the daemon's socket file still
|
|
918
|
+
* existing. We know the daemon was alive moments ago (we either just
|
|
919
|
+
* got a hello response or the caller saw a transformers-missing error
|
|
920
|
+
* the daemon emitted), but if the socket file is gone by the time we
|
|
921
|
+
* try to kill, the daemon process is also gone and the PID we
|
|
922
|
+
* captured may already have been recycled by the OS to an unrelated
|
|
923
|
+
* user process. Mirrors the gate added to `killEmbedDaemon` in the
|
|
924
|
+
* CLI — same failure mode, rarer trigger.
|
|
925
|
+
*/
|
|
926
|
+
recycleDaemon(reportedPid) {
|
|
927
|
+
let pid = reportedPid;
|
|
928
|
+
if (pid === null) {
|
|
929
|
+
try {
|
|
930
|
+
pid = Number.parseInt(readFileSync5(this.pidPath, "utf-8").trim(), 10);
|
|
931
|
+
} catch {
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
if (Number.isFinite(pid) && pid !== null && pid > 0 && existsSync3(this.socketPath)) {
|
|
935
|
+
try {
|
|
936
|
+
process.kill(pid, "SIGTERM");
|
|
937
|
+
} catch {
|
|
938
|
+
}
|
|
939
|
+
} else if (pid !== null) {
|
|
940
|
+
log4(`recycle: socket gone, skipping SIGTERM on possibly-stale pid ${pid}`);
|
|
941
|
+
}
|
|
942
|
+
try {
|
|
943
|
+
unlinkSync3(this.socketPath);
|
|
944
|
+
} catch {
|
|
945
|
+
}
|
|
946
|
+
try {
|
|
947
|
+
unlinkSync3(this.pidPath);
|
|
948
|
+
} catch {
|
|
949
|
+
}
|
|
950
|
+
}
|
|
648
951
|
/**
|
|
649
952
|
* Wait up to spawnWaitMs for the daemon to accept connections, spawning if
|
|
650
953
|
* necessary. Meant for SessionStart / long-running batches — not the hot path.
|
|
@@ -668,7 +971,7 @@ var EmbedClient = class {
|
|
|
668
971
|
}
|
|
669
972
|
}
|
|
670
973
|
connectOnce() {
|
|
671
|
-
return new Promise((
|
|
974
|
+
return new Promise((resolve2, reject) => {
|
|
672
975
|
const sock = connect(this.socketPath);
|
|
673
976
|
const to = setTimeout(() => {
|
|
674
977
|
sock.destroy();
|
|
@@ -676,7 +979,7 @@ var EmbedClient = class {
|
|
|
676
979
|
}, this.timeoutMs);
|
|
677
980
|
sock.once("connect", () => {
|
|
678
981
|
clearTimeout(to);
|
|
679
|
-
|
|
982
|
+
resolve2(sock);
|
|
680
983
|
});
|
|
681
984
|
sock.once("error", (e) => {
|
|
682
985
|
clearTimeout(to);
|
|
@@ -687,16 +990,16 @@ var EmbedClient = class {
|
|
|
687
990
|
trySpawnDaemon() {
|
|
688
991
|
let fd;
|
|
689
992
|
try {
|
|
690
|
-
fd =
|
|
993
|
+
fd = openSync2(this.pidPath, "wx", 384);
|
|
691
994
|
writeSync(fd, String(process.pid));
|
|
692
995
|
} catch (e) {
|
|
693
996
|
if (this.isPidFileStale()) {
|
|
694
997
|
try {
|
|
695
|
-
|
|
998
|
+
unlinkSync3(this.pidPath);
|
|
696
999
|
} catch {
|
|
697
1000
|
}
|
|
698
1001
|
try {
|
|
699
|
-
fd =
|
|
1002
|
+
fd = openSync2(this.pidPath, "wx", 384);
|
|
700
1003
|
writeSync(fd, String(process.pid));
|
|
701
1004
|
} catch {
|
|
702
1005
|
return;
|
|
@@ -706,10 +1009,10 @@ var EmbedClient = class {
|
|
|
706
1009
|
}
|
|
707
1010
|
}
|
|
708
1011
|
if (!this.daemonEntry || !existsSync3(this.daemonEntry)) {
|
|
709
|
-
|
|
1012
|
+
log4(`daemonEntry not configured or missing: ${this.daemonEntry}`);
|
|
710
1013
|
try {
|
|
711
|
-
|
|
712
|
-
|
|
1014
|
+
closeSync2(fd);
|
|
1015
|
+
unlinkSync3(this.pidPath);
|
|
713
1016
|
} catch {
|
|
714
1017
|
}
|
|
715
1018
|
return;
|
|
@@ -721,14 +1024,14 @@ var EmbedClient = class {
|
|
|
721
1024
|
env: process.env
|
|
722
1025
|
});
|
|
723
1026
|
child.unref();
|
|
724
|
-
|
|
1027
|
+
log4(`spawned daemon pid=${child.pid}`);
|
|
725
1028
|
} finally {
|
|
726
|
-
|
|
1029
|
+
closeSync2(fd);
|
|
727
1030
|
}
|
|
728
1031
|
}
|
|
729
1032
|
isPidFileStale() {
|
|
730
1033
|
try {
|
|
731
|
-
const raw =
|
|
1034
|
+
const raw = readFileSync5(this.pidPath, "utf-8").trim();
|
|
732
1035
|
const pid = Number(raw);
|
|
733
1036
|
if (!pid || Number.isNaN(pid))
|
|
734
1037
|
return true;
|
|
@@ -746,7 +1049,7 @@ var EmbedClient = class {
|
|
|
746
1049
|
const deadline = Date.now() + this.spawnWaitMs;
|
|
747
1050
|
let delay = 30;
|
|
748
1051
|
while (Date.now() < deadline) {
|
|
749
|
-
await
|
|
1052
|
+
await sleep3(delay);
|
|
750
1053
|
delay = Math.min(delay * 1.5, 300);
|
|
751
1054
|
if (!existsSync3(this.socketPath))
|
|
752
1055
|
continue;
|
|
@@ -758,7 +1061,7 @@ var EmbedClient = class {
|
|
|
758
1061
|
throw new Error("daemon did not become ready within spawnWaitMs");
|
|
759
1062
|
}
|
|
760
1063
|
sendAndWait(sock, req) {
|
|
761
|
-
return new Promise((
|
|
1064
|
+
return new Promise((resolve2, reject) => {
|
|
762
1065
|
let buf = "";
|
|
763
1066
|
const to = setTimeout(() => {
|
|
764
1067
|
sock.destroy();
|
|
@@ -773,7 +1076,7 @@ var EmbedClient = class {
|
|
|
773
1076
|
const line = buf.slice(0, nl);
|
|
774
1077
|
clearTimeout(to);
|
|
775
1078
|
try {
|
|
776
|
-
|
|
1079
|
+
resolve2(JSON.parse(line));
|
|
777
1080
|
} catch (e) {
|
|
778
1081
|
reject(e);
|
|
779
1082
|
}
|
|
@@ -790,9 +1093,14 @@ var EmbedClient = class {
|
|
|
790
1093
|
});
|
|
791
1094
|
}
|
|
792
1095
|
};
|
|
793
|
-
function
|
|
1096
|
+
function sleep3(ms) {
|
|
794
1097
|
return new Promise((r) => setTimeout(r, ms));
|
|
795
1098
|
}
|
|
1099
|
+
function isTransformersMissingError(err) {
|
|
1100
|
+
if (/hivemind embeddings install/i.test(err))
|
|
1101
|
+
return true;
|
|
1102
|
+
return /@huggingface\/transformers/i.test(err);
|
|
1103
|
+
}
|
|
796
1104
|
|
|
797
1105
|
// dist/src/embeddings/sql.js
|
|
798
1106
|
function embeddingSqlLiteral(vec) {
|
|
@@ -809,23 +1117,105 @@ function embeddingSqlLiteral(vec) {
|
|
|
809
1117
|
|
|
810
1118
|
// dist/src/embeddings/disable.js
|
|
811
1119
|
import { createRequire } from "node:module";
|
|
812
|
-
import { homedir as
|
|
813
|
-
import { join as
|
|
1120
|
+
import { homedir as homedir7 } from "node:os";
|
|
1121
|
+
import { join as join8 } from "node:path";
|
|
814
1122
|
import { pathToFileURL } from "node:url";
|
|
1123
|
+
|
|
1124
|
+
// dist/src/user-config.js
|
|
1125
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync6, renameSync as renameSync2, writeFileSync as writeFileSync4 } from "node:fs";
|
|
1126
|
+
import { homedir as homedir6 } from "node:os";
|
|
1127
|
+
import { dirname, join as join7 } from "node:path";
|
|
1128
|
+
var _configPath = () => process.env.HIVEMIND_CONFIG_PATH ?? join7(homedir6(), ".deeplake", "config.json");
|
|
1129
|
+
var _cache = null;
|
|
1130
|
+
var _migrated = false;
|
|
1131
|
+
function readUserConfig() {
|
|
1132
|
+
if (_cache !== null)
|
|
1133
|
+
return _cache;
|
|
1134
|
+
const path = _configPath();
|
|
1135
|
+
if (!existsSync4(path)) {
|
|
1136
|
+
_cache = {};
|
|
1137
|
+
return _cache;
|
|
1138
|
+
}
|
|
1139
|
+
try {
|
|
1140
|
+
const raw = readFileSync6(path, "utf-8");
|
|
1141
|
+
const parsed = JSON.parse(raw);
|
|
1142
|
+
_cache = isPlainObject(parsed) ? parsed : {};
|
|
1143
|
+
} catch {
|
|
1144
|
+
_cache = {};
|
|
1145
|
+
}
|
|
1146
|
+
return _cache;
|
|
1147
|
+
}
|
|
1148
|
+
function writeUserConfig(patch) {
|
|
1149
|
+
const current = readUserConfig();
|
|
1150
|
+
const merged = deepMerge(current, patch);
|
|
1151
|
+
const path = _configPath();
|
|
1152
|
+
const dir = dirname(path);
|
|
1153
|
+
if (!existsSync4(dir))
|
|
1154
|
+
mkdirSync4(dir, { recursive: true });
|
|
1155
|
+
const tmp = `${path}.tmp.${process.pid}`;
|
|
1156
|
+
writeFileSync4(tmp, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
1157
|
+
renameSync2(tmp, path);
|
|
1158
|
+
_cache = merged;
|
|
1159
|
+
return merged;
|
|
1160
|
+
}
|
|
1161
|
+
function getEmbeddingsEnabled() {
|
|
1162
|
+
const cfg = readUserConfig();
|
|
1163
|
+
if (cfg.embeddings && typeof cfg.embeddings.enabled === "boolean") {
|
|
1164
|
+
return cfg.embeddings.enabled;
|
|
1165
|
+
}
|
|
1166
|
+
if (_migrated) {
|
|
1167
|
+
return migrationValueFromEnv();
|
|
1168
|
+
}
|
|
1169
|
+
_migrated = true;
|
|
1170
|
+
const enabled = migrationValueFromEnv();
|
|
1171
|
+
try {
|
|
1172
|
+
writeUserConfig({ embeddings: { enabled } });
|
|
1173
|
+
} catch {
|
|
1174
|
+
_cache = { ...cfg ?? {}, embeddings: { ...cfg?.embeddings ?? {}, enabled } };
|
|
1175
|
+
}
|
|
1176
|
+
return enabled;
|
|
1177
|
+
}
|
|
1178
|
+
function migrationValueFromEnv() {
|
|
1179
|
+
const raw = process.env.HIVEMIND_EMBEDDINGS;
|
|
1180
|
+
if (raw === void 0)
|
|
1181
|
+
return false;
|
|
1182
|
+
if (raw === "false")
|
|
1183
|
+
return false;
|
|
1184
|
+
return true;
|
|
1185
|
+
}
|
|
1186
|
+
function isPlainObject(value) {
|
|
1187
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1188
|
+
}
|
|
1189
|
+
function deepMerge(base, patch) {
|
|
1190
|
+
const out = { ...base };
|
|
1191
|
+
for (const key of Object.keys(patch)) {
|
|
1192
|
+
const patchVal = patch[key];
|
|
1193
|
+
const baseVal = base[key];
|
|
1194
|
+
if (isPlainObject(patchVal) && isPlainObject(baseVal)) {
|
|
1195
|
+
out[key] = { ...baseVal, ...patchVal };
|
|
1196
|
+
} else if (patchVal !== void 0) {
|
|
1197
|
+
out[key] = patchVal;
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
return out;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
// dist/src/embeddings/disable.js
|
|
815
1204
|
var cachedStatus = null;
|
|
816
1205
|
function defaultResolveTransformers() {
|
|
1206
|
+
const sharedDir = join8(homedir7(), ".hivemind", "embed-deps");
|
|
817
1207
|
try {
|
|
818
|
-
createRequire(
|
|
1208
|
+
createRequire(pathToFileURL(`${sharedDir}/`).href).resolve("@huggingface/transformers");
|
|
819
1209
|
return;
|
|
820
1210
|
} catch {
|
|
821
1211
|
}
|
|
822
|
-
|
|
823
|
-
createRequire(pathToFileURL(`${sharedDir}/`).href).resolve("@huggingface/transformers");
|
|
1212
|
+
createRequire(import.meta.url).resolve("@huggingface/transformers");
|
|
824
1213
|
}
|
|
825
1214
|
var _resolve = defaultResolveTransformers;
|
|
1215
|
+
var _readEnabled = getEmbeddingsEnabled;
|
|
826
1216
|
function detectStatus() {
|
|
827
|
-
if (
|
|
828
|
-
return "
|
|
1217
|
+
if (!_readEnabled())
|
|
1218
|
+
return "user-disabled";
|
|
829
1219
|
try {
|
|
830
1220
|
_resolve();
|
|
831
1221
|
return "enabled";
|
|
@@ -843,55 +1233,120 @@ function embeddingsDisabled() {
|
|
|
843
1233
|
return embeddingsStatus() !== "enabled";
|
|
844
1234
|
}
|
|
845
1235
|
|
|
1236
|
+
// dist/src/embeddings/self-heal.js
|
|
1237
|
+
import { existsSync as existsSync5, lstatSync, mkdirSync as mkdirSync5, readlinkSync, renameSync as renameSync3, rmSync, symlinkSync, statSync as statSync2 } from "node:fs";
|
|
1238
|
+
import { homedir as homedir8 } from "node:os";
|
|
1239
|
+
import { basename, dirname as dirname2, join as join9 } from "node:path";
|
|
1240
|
+
function ensurePluginNodeModulesLink(opts) {
|
|
1241
|
+
if (basename(opts.bundleDir) !== "bundle") {
|
|
1242
|
+
return { kind: "not-bundle-layout", bundleDir: opts.bundleDir };
|
|
1243
|
+
}
|
|
1244
|
+
const target = opts.sharedNodeModules ?? join9(homedir8(), ".hivemind", "embed-deps", "node_modules");
|
|
1245
|
+
const pluginDir = dirname2(opts.bundleDir);
|
|
1246
|
+
const link = join9(pluginDir, "node_modules");
|
|
1247
|
+
if (!existsSync5(target)) {
|
|
1248
|
+
return { kind: "shared-deps-missing", target };
|
|
1249
|
+
}
|
|
1250
|
+
let linkStat;
|
|
1251
|
+
try {
|
|
1252
|
+
linkStat = lstatSync(link);
|
|
1253
|
+
} catch {
|
|
1254
|
+
return createSymlinkAtomic(target, link);
|
|
1255
|
+
}
|
|
1256
|
+
if (linkStat.isSymbolicLink()) {
|
|
1257
|
+
let existingTarget;
|
|
1258
|
+
try {
|
|
1259
|
+
existingTarget = readlinkSync(link);
|
|
1260
|
+
} catch (e) {
|
|
1261
|
+
return { kind: "error", detail: `readlink failed: ${e instanceof Error ? e.message : String(e)}` };
|
|
1262
|
+
}
|
|
1263
|
+
if (existingTarget === target) {
|
|
1264
|
+
return { kind: "already-linked", target, link };
|
|
1265
|
+
}
|
|
1266
|
+
try {
|
|
1267
|
+
statSync2(link);
|
|
1268
|
+
return { kind: "linked-elsewhere", link, existingTarget };
|
|
1269
|
+
} catch {
|
|
1270
|
+
try {
|
|
1271
|
+
rmSync(link);
|
|
1272
|
+
} catch {
|
|
1273
|
+
}
|
|
1274
|
+
const recreated = createSymlinkAtomic(target, link);
|
|
1275
|
+
if (recreated.kind === "linked") {
|
|
1276
|
+
return { kind: "stale-link-removed", link, danglingTarget: existingTarget };
|
|
1277
|
+
}
|
|
1278
|
+
return recreated;
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
return { kind: "plugin-owns-node-modules", link };
|
|
1282
|
+
}
|
|
1283
|
+
function createSymlinkAtomic(target, link) {
|
|
1284
|
+
try {
|
|
1285
|
+
const parent = dirname2(link);
|
|
1286
|
+
if (!existsSync5(parent))
|
|
1287
|
+
mkdirSync5(parent, { recursive: true });
|
|
1288
|
+
const tmp = `${link}.tmp.${process.pid}`;
|
|
1289
|
+
try {
|
|
1290
|
+
rmSync(tmp, { force: true });
|
|
1291
|
+
} catch {
|
|
1292
|
+
}
|
|
1293
|
+
symlinkSync(target, tmp);
|
|
1294
|
+
renameSync3(tmp, link);
|
|
1295
|
+
return { kind: "linked", target, link };
|
|
1296
|
+
} catch (e) {
|
|
1297
|
+
return { kind: "error", detail: e instanceof Error ? e.message : String(e) };
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
|
|
846
1301
|
// dist/src/hooks/hermes/capture.js
|
|
847
1302
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
848
|
-
import { dirname as
|
|
1303
|
+
import { dirname as dirname6, join as join19 } from "node:path";
|
|
849
1304
|
|
|
850
1305
|
// dist/src/hooks/summary-state.js
|
|
851
|
-
import { readFileSync as
|
|
852
|
-
import { homedir as
|
|
853
|
-
import { join as
|
|
1306
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, writeSync as writeSync2, mkdirSync as mkdirSync6, renameSync as renameSync4, existsSync as existsSync6, unlinkSync as unlinkSync4, openSync as openSync3, closeSync as closeSync3 } from "node:fs";
|
|
1307
|
+
import { homedir as homedir9 } from "node:os";
|
|
1308
|
+
import { join as join10 } from "node:path";
|
|
854
1309
|
var dlog = (msg) => log("summary-state", msg);
|
|
855
|
-
var STATE_DIR =
|
|
1310
|
+
var STATE_DIR = join10(homedir9(), ".claude", "hooks", "summary-state");
|
|
856
1311
|
var YIELD_BUF = new Int32Array(new SharedArrayBuffer(4));
|
|
857
1312
|
function statePath(sessionId) {
|
|
858
|
-
return
|
|
1313
|
+
return join10(STATE_DIR, `${sessionId}.json`);
|
|
859
1314
|
}
|
|
860
|
-
function
|
|
861
|
-
return
|
|
1315
|
+
function lockPath2(sessionId) {
|
|
1316
|
+
return join10(STATE_DIR, `${sessionId}.lock`);
|
|
862
1317
|
}
|
|
863
1318
|
function readState(sessionId) {
|
|
864
1319
|
const p = statePath(sessionId);
|
|
865
|
-
if (!
|
|
1320
|
+
if (!existsSync6(p))
|
|
866
1321
|
return null;
|
|
867
1322
|
try {
|
|
868
|
-
return JSON.parse(
|
|
1323
|
+
return JSON.parse(readFileSync7(p, "utf-8"));
|
|
869
1324
|
} catch {
|
|
870
1325
|
return null;
|
|
871
1326
|
}
|
|
872
1327
|
}
|
|
873
1328
|
function writeState(sessionId, state) {
|
|
874
|
-
|
|
1329
|
+
mkdirSync6(STATE_DIR, { recursive: true });
|
|
875
1330
|
const p = statePath(sessionId);
|
|
876
1331
|
const tmp = `${p}.${process.pid}.${Date.now()}.tmp`;
|
|
877
|
-
|
|
878
|
-
|
|
1332
|
+
writeFileSync5(tmp, JSON.stringify(state));
|
|
1333
|
+
renameSync4(tmp, p);
|
|
879
1334
|
}
|
|
880
1335
|
function withRmwLock(sessionId, fn) {
|
|
881
|
-
|
|
1336
|
+
mkdirSync6(STATE_DIR, { recursive: true });
|
|
882
1337
|
const rmwLock = statePath(sessionId) + ".rmw";
|
|
883
1338
|
const deadline = Date.now() + 2e3;
|
|
884
1339
|
let fd = null;
|
|
885
1340
|
while (fd === null) {
|
|
886
1341
|
try {
|
|
887
|
-
fd =
|
|
1342
|
+
fd = openSync3(rmwLock, "wx");
|
|
888
1343
|
} catch (e) {
|
|
889
1344
|
if (e.code !== "EEXIST")
|
|
890
1345
|
throw e;
|
|
891
1346
|
if (Date.now() > deadline) {
|
|
892
1347
|
dlog(`rmw lock deadline exceeded for ${sessionId}, reclaiming stale lock`);
|
|
893
1348
|
try {
|
|
894
|
-
|
|
1349
|
+
unlinkSync4(rmwLock);
|
|
895
1350
|
} catch (unlinkErr) {
|
|
896
1351
|
dlog(`stale rmw lock unlink failed for ${sessionId}: ${unlinkErr.message}`);
|
|
897
1352
|
}
|
|
@@ -903,9 +1358,9 @@ function withRmwLock(sessionId, fn) {
|
|
|
903
1358
|
try {
|
|
904
1359
|
return fn();
|
|
905
1360
|
} finally {
|
|
906
|
-
|
|
1361
|
+
closeSync3(fd);
|
|
907
1362
|
try {
|
|
908
|
-
|
|
1363
|
+
unlinkSync4(rmwLock);
|
|
909
1364
|
} catch (unlinkErr) {
|
|
910
1365
|
dlog(`rmw lock cleanup failed for ${sessionId}: ${unlinkErr.message}`);
|
|
911
1366
|
}
|
|
@@ -940,29 +1395,29 @@ function shouldTrigger(state, cfg, now = Date.now()) {
|
|
|
940
1395
|
return false;
|
|
941
1396
|
}
|
|
942
1397
|
function tryAcquireLock(sessionId, maxAgeMs = 10 * 60 * 1e3) {
|
|
943
|
-
|
|
944
|
-
const p =
|
|
945
|
-
if (
|
|
1398
|
+
mkdirSync6(STATE_DIR, { recursive: true });
|
|
1399
|
+
const p = lockPath2(sessionId);
|
|
1400
|
+
if (existsSync6(p)) {
|
|
946
1401
|
try {
|
|
947
|
-
const ageMs = Date.now() - parseInt(
|
|
1402
|
+
const ageMs = Date.now() - parseInt(readFileSync7(p, "utf-8"), 10);
|
|
948
1403
|
if (Number.isFinite(ageMs) && ageMs < maxAgeMs)
|
|
949
1404
|
return false;
|
|
950
1405
|
} catch (readErr) {
|
|
951
1406
|
dlog(`lock file unreadable for ${sessionId}, treating as stale: ${readErr.message}`);
|
|
952
1407
|
}
|
|
953
1408
|
try {
|
|
954
|
-
|
|
1409
|
+
unlinkSync4(p);
|
|
955
1410
|
} catch (unlinkErr) {
|
|
956
1411
|
dlog(`could not unlink stale lock for ${sessionId}: ${unlinkErr.message}`);
|
|
957
1412
|
return false;
|
|
958
1413
|
}
|
|
959
1414
|
}
|
|
960
1415
|
try {
|
|
961
|
-
const fd =
|
|
1416
|
+
const fd = openSync3(p, "wx");
|
|
962
1417
|
try {
|
|
963
1418
|
writeSync2(fd, String(Date.now()));
|
|
964
1419
|
} finally {
|
|
965
|
-
|
|
1420
|
+
closeSync3(fd);
|
|
966
1421
|
}
|
|
967
1422
|
return true;
|
|
968
1423
|
} catch (e) {
|
|
@@ -973,7 +1428,7 @@ function tryAcquireLock(sessionId, maxAgeMs = 10 * 60 * 1e3) {
|
|
|
973
1428
|
}
|
|
974
1429
|
function releaseLock(sessionId) {
|
|
975
1430
|
try {
|
|
976
|
-
|
|
1431
|
+
unlinkSync4(lockPath2(sessionId));
|
|
977
1432
|
} catch (e) {
|
|
978
1433
|
if (e?.code !== "ENOENT") {
|
|
979
1434
|
dlog(`releaseLock unlink failed for ${sessionId}: ${e.message}`);
|
|
@@ -984,20 +1439,20 @@ function releaseLock(sessionId) {
|
|
|
984
1439
|
// dist/src/hooks/hermes/spawn-wiki-worker.js
|
|
985
1440
|
import { spawn as spawn2, execSync } from "node:child_process";
|
|
986
1441
|
import { fileURLToPath } from "node:url";
|
|
987
|
-
import { dirname as
|
|
988
|
-
import { writeFileSync as
|
|
989
|
-
import { homedir as
|
|
1442
|
+
import { dirname as dirname4, join as join13 } from "node:path";
|
|
1443
|
+
import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync8 } from "node:fs";
|
|
1444
|
+
import { homedir as homedir10, tmpdir as tmpdir2 } from "node:os";
|
|
990
1445
|
|
|
991
1446
|
// dist/src/utils/wiki-log.js
|
|
992
|
-
import { mkdirSync as
|
|
993
|
-
import { join as
|
|
1447
|
+
import { mkdirSync as mkdirSync7, appendFileSync as appendFileSync2 } from "node:fs";
|
|
1448
|
+
import { join as join11 } from "node:path";
|
|
994
1449
|
function makeWikiLogger(hooksDir, filename = "deeplake-wiki.log") {
|
|
995
|
-
const path =
|
|
1450
|
+
const path = join11(hooksDir, filename);
|
|
996
1451
|
return {
|
|
997
1452
|
path,
|
|
998
1453
|
log(msg) {
|
|
999
1454
|
try {
|
|
1000
|
-
|
|
1455
|
+
mkdirSync7(hooksDir, { recursive: true });
|
|
1001
1456
|
appendFileSync2(path, `[${utcTimestamp()}] ${msg}
|
|
1002
1457
|
`);
|
|
1003
1458
|
} catch {
|
|
@@ -1007,18 +1462,18 @@ function makeWikiLogger(hooksDir, filename = "deeplake-wiki.log") {
|
|
|
1007
1462
|
}
|
|
1008
1463
|
|
|
1009
1464
|
// dist/src/utils/version-check.js
|
|
1010
|
-
import { readFileSync as
|
|
1011
|
-
import { dirname, join as
|
|
1465
|
+
import { readFileSync as readFileSync8 } from "node:fs";
|
|
1466
|
+
import { dirname as dirname3, join as join12 } from "node:path";
|
|
1012
1467
|
function getInstalledVersion(bundleDir, pluginManifestDir) {
|
|
1013
1468
|
try {
|
|
1014
|
-
const pluginJson =
|
|
1015
|
-
const plugin = JSON.parse(
|
|
1469
|
+
const pluginJson = join12(bundleDir, "..", pluginManifestDir, "plugin.json");
|
|
1470
|
+
const plugin = JSON.parse(readFileSync8(pluginJson, "utf-8"));
|
|
1016
1471
|
if (plugin.version)
|
|
1017
1472
|
return plugin.version;
|
|
1018
1473
|
} catch {
|
|
1019
1474
|
}
|
|
1020
1475
|
try {
|
|
1021
|
-
const stamp =
|
|
1476
|
+
const stamp = readFileSync8(join12(bundleDir, "..", ".hivemind_version"), "utf-8").trim();
|
|
1022
1477
|
if (stamp)
|
|
1023
1478
|
return stamp;
|
|
1024
1479
|
} catch {
|
|
@@ -1033,14 +1488,14 @@ function getInstalledVersion(bundleDir, pluginManifestDir) {
|
|
|
1033
1488
|
]);
|
|
1034
1489
|
let dir = bundleDir;
|
|
1035
1490
|
for (let i = 0; i < 5; i++) {
|
|
1036
|
-
const candidate =
|
|
1491
|
+
const candidate = join12(dir, "package.json");
|
|
1037
1492
|
try {
|
|
1038
|
-
const pkg = JSON.parse(
|
|
1493
|
+
const pkg = JSON.parse(readFileSync8(candidate, "utf-8"));
|
|
1039
1494
|
if (HIVEMIND_PKG_NAMES.has(pkg.name) && pkg.version)
|
|
1040
1495
|
return pkg.version;
|
|
1041
1496
|
} catch {
|
|
1042
1497
|
}
|
|
1043
|
-
const parent =
|
|
1498
|
+
const parent = dirname3(dir);
|
|
1044
1499
|
if (parent === dir)
|
|
1045
1500
|
break;
|
|
1046
1501
|
dir = parent;
|
|
@@ -1049,8 +1504,8 @@ function getInstalledVersion(bundleDir, pluginManifestDir) {
|
|
|
1049
1504
|
}
|
|
1050
1505
|
|
|
1051
1506
|
// dist/src/hooks/hermes/spawn-wiki-worker.js
|
|
1052
|
-
var HOME =
|
|
1053
|
-
var wikiLogger = makeWikiLogger(
|
|
1507
|
+
var HOME = homedir10();
|
|
1508
|
+
var wikiLogger = makeWikiLogger(join13(HOME, ".hermes", "hooks"));
|
|
1054
1509
|
var WIKI_LOG = wikiLogger.path;
|
|
1055
1510
|
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
1511
|
|
|
@@ -1112,11 +1567,11 @@ function findHermesBin() {
|
|
|
1112
1567
|
function spawnHermesWikiWorker(opts) {
|
|
1113
1568
|
const { config, sessionId, cwd, bundleDir, reason } = opts;
|
|
1114
1569
|
const projectName = cwd.split("/").pop() || "unknown";
|
|
1115
|
-
const tmpDir =
|
|
1116
|
-
|
|
1570
|
+
const tmpDir = join13(tmpdir2(), `deeplake-wiki-${sessionId}-${Date.now()}`);
|
|
1571
|
+
mkdirSync8(tmpDir, { recursive: true });
|
|
1117
1572
|
const pluginVersion = getInstalledVersion(bundleDir, ".claude-plugin") ?? "";
|
|
1118
|
-
const configFile =
|
|
1119
|
-
|
|
1573
|
+
const configFile = join13(tmpDir, "config.json");
|
|
1574
|
+
writeFileSync6(configFile, JSON.stringify({
|
|
1120
1575
|
apiUrl: config.apiUrl,
|
|
1121
1576
|
token: config.token,
|
|
1122
1577
|
orgId: config.orgId,
|
|
@@ -1132,11 +1587,11 @@ function spawnHermesWikiWorker(opts) {
|
|
|
1132
1587
|
hermesProvider: process.env.HIVEMIND_HERMES_PROVIDER ?? "openrouter",
|
|
1133
1588
|
hermesModel: process.env.HIVEMIND_HERMES_MODEL ?? "anthropic/claude-haiku-4-5",
|
|
1134
1589
|
wikiLog: WIKI_LOG,
|
|
1135
|
-
hooksDir:
|
|
1590
|
+
hooksDir: join13(HOME, ".hermes", "hooks"),
|
|
1136
1591
|
promptTemplate: WIKI_PROMPT_TEMPLATE
|
|
1137
1592
|
}));
|
|
1138
1593
|
wikiLog(`${reason}: spawning summary worker for ${sessionId}`);
|
|
1139
|
-
const workerPath =
|
|
1594
|
+
const workerPath = join13(bundleDir, "wiki-worker.js");
|
|
1140
1595
|
spawn2("nohup", ["node", workerPath, configFile], {
|
|
1141
1596
|
detached: true,
|
|
1142
1597
|
stdio: ["ignore", "ignore", "ignore"]
|
|
@@ -1144,33 +1599,33 @@ function spawnHermesWikiWorker(opts) {
|
|
|
1144
1599
|
wikiLog(`${reason}: spawned summary worker for ${sessionId}`);
|
|
1145
1600
|
}
|
|
1146
1601
|
function bundleDirFromImportMeta(importMetaUrl) {
|
|
1147
|
-
return
|
|
1602
|
+
return dirname4(fileURLToPath(importMetaUrl));
|
|
1148
1603
|
}
|
|
1149
1604
|
|
|
1150
1605
|
// dist/src/skillify/spawn-skillify-worker.js
|
|
1151
1606
|
import { spawn as spawn3 } from "node:child_process";
|
|
1152
1607
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
1153
|
-
import { dirname as
|
|
1154
|
-
import { writeFileSync as
|
|
1155
|
-
import { homedir as
|
|
1608
|
+
import { dirname as dirname5, join as join15 } from "node:path";
|
|
1609
|
+
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync9, appendFileSync as appendFileSync3, chmodSync } from "node:fs";
|
|
1610
|
+
import { homedir as homedir12, tmpdir as tmpdir3 } from "node:os";
|
|
1156
1611
|
|
|
1157
1612
|
// dist/src/skillify/gate-runner.js
|
|
1158
|
-
import { existsSync as
|
|
1613
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
1159
1614
|
import { createRequire as createRequire2 } from "node:module";
|
|
1160
|
-
import { homedir as
|
|
1161
|
-
import { join as
|
|
1615
|
+
import { homedir as homedir11 } from "node:os";
|
|
1616
|
+
import { join as join14 } from "node:path";
|
|
1162
1617
|
var requireForCp = createRequire2(import.meta.url);
|
|
1163
1618
|
var { execFileSync: runChildProcess } = requireForCp("node:child_process");
|
|
1164
1619
|
var inheritedEnv = process;
|
|
1165
1620
|
function firstExistingPath(candidates) {
|
|
1166
1621
|
for (const c of candidates) {
|
|
1167
|
-
if (
|
|
1622
|
+
if (existsSync7(c))
|
|
1168
1623
|
return c;
|
|
1169
1624
|
}
|
|
1170
1625
|
return null;
|
|
1171
1626
|
}
|
|
1172
1627
|
function findAgentBin(agent) {
|
|
1173
|
-
const home =
|
|
1628
|
+
const home = homedir11();
|
|
1174
1629
|
switch (agent) {
|
|
1175
1630
|
// /usr/bin/<name> is included in every candidate list — that's the
|
|
1176
1631
|
// common Linux package-manager install path (apt, dnf, pacman). Old
|
|
@@ -1179,54 +1634,54 @@ function findAgentBin(agent) {
|
|
|
1179
1634
|
// #170 caught the gap.
|
|
1180
1635
|
case "claude_code":
|
|
1181
1636
|
return firstExistingPath([
|
|
1182
|
-
|
|
1637
|
+
join14(home, ".claude", "local", "claude"),
|
|
1183
1638
|
"/usr/local/bin/claude",
|
|
1184
1639
|
"/usr/bin/claude",
|
|
1185
|
-
|
|
1186
|
-
|
|
1640
|
+
join14(home, ".npm-global", "bin", "claude"),
|
|
1641
|
+
join14(home, ".local", "bin", "claude"),
|
|
1187
1642
|
"/opt/homebrew/bin/claude"
|
|
1188
|
-
]) ??
|
|
1643
|
+
]) ?? join14(home, ".claude", "local", "claude");
|
|
1189
1644
|
case "codex":
|
|
1190
1645
|
return firstExistingPath([
|
|
1191
1646
|
"/usr/local/bin/codex",
|
|
1192
1647
|
"/usr/bin/codex",
|
|
1193
|
-
|
|
1194
|
-
|
|
1648
|
+
join14(home, ".npm-global", "bin", "codex"),
|
|
1649
|
+
join14(home, ".local", "bin", "codex"),
|
|
1195
1650
|
"/opt/homebrew/bin/codex"
|
|
1196
1651
|
]) ?? "/usr/local/bin/codex";
|
|
1197
1652
|
case "cursor":
|
|
1198
1653
|
return firstExistingPath([
|
|
1199
1654
|
"/usr/local/bin/cursor-agent",
|
|
1200
1655
|
"/usr/bin/cursor-agent",
|
|
1201
|
-
|
|
1202
|
-
|
|
1656
|
+
join14(home, ".npm-global", "bin", "cursor-agent"),
|
|
1657
|
+
join14(home, ".local", "bin", "cursor-agent"),
|
|
1203
1658
|
"/opt/homebrew/bin/cursor-agent"
|
|
1204
1659
|
]) ?? "/usr/local/bin/cursor-agent";
|
|
1205
1660
|
case "hermes":
|
|
1206
1661
|
return firstExistingPath([
|
|
1207
|
-
|
|
1662
|
+
join14(home, ".local", "bin", "hermes"),
|
|
1208
1663
|
"/usr/local/bin/hermes",
|
|
1209
1664
|
"/usr/bin/hermes",
|
|
1210
|
-
|
|
1665
|
+
join14(home, ".npm-global", "bin", "hermes"),
|
|
1211
1666
|
"/opt/homebrew/bin/hermes"
|
|
1212
|
-
]) ??
|
|
1667
|
+
]) ?? join14(home, ".local", "bin", "hermes");
|
|
1213
1668
|
case "pi":
|
|
1214
1669
|
return firstExistingPath([
|
|
1215
|
-
|
|
1670
|
+
join14(home, ".local", "bin", "pi"),
|
|
1216
1671
|
"/usr/local/bin/pi",
|
|
1217
1672
|
"/usr/bin/pi",
|
|
1218
|
-
|
|
1673
|
+
join14(home, ".npm-global", "bin", "pi"),
|
|
1219
1674
|
"/opt/homebrew/bin/pi"
|
|
1220
|
-
]) ??
|
|
1675
|
+
]) ?? join14(home, ".local", "bin", "pi");
|
|
1221
1676
|
}
|
|
1222
1677
|
}
|
|
1223
1678
|
|
|
1224
1679
|
// dist/src/skillify/spawn-skillify-worker.js
|
|
1225
|
-
var HOME2 =
|
|
1226
|
-
var SKILLIFY_LOG =
|
|
1680
|
+
var HOME2 = homedir12();
|
|
1681
|
+
var SKILLIFY_LOG = join15(HOME2, ".claude", "hooks", "skillify.log");
|
|
1227
1682
|
function skillifyLog(msg) {
|
|
1228
1683
|
try {
|
|
1229
|
-
|
|
1684
|
+
mkdirSync9(dirname5(SKILLIFY_LOG), { recursive: true });
|
|
1230
1685
|
appendFileSync3(SKILLIFY_LOG, `[${utcTimestamp()}] ${msg}
|
|
1231
1686
|
`);
|
|
1232
1687
|
} catch {
|
|
@@ -1234,11 +1689,11 @@ function skillifyLog(msg) {
|
|
|
1234
1689
|
}
|
|
1235
1690
|
function spawnSkillifyWorker(opts) {
|
|
1236
1691
|
const { config, cwd, projectKey, project, bundleDir, agent, scopeConfig, currentSessionId, reason } = opts;
|
|
1237
|
-
const tmpDir =
|
|
1238
|
-
|
|
1692
|
+
const tmpDir = join15(tmpdir3(), `deeplake-skillify-${projectKey}-${Date.now()}`);
|
|
1693
|
+
mkdirSync9(tmpDir, { recursive: true, mode: 448 });
|
|
1239
1694
|
const gateBin = findAgentBin(agent);
|
|
1240
|
-
const configFile =
|
|
1241
|
-
|
|
1695
|
+
const configFile = join15(tmpDir, "config.json");
|
|
1696
|
+
writeFileSync7(configFile, JSON.stringify({
|
|
1242
1697
|
apiUrl: config.apiUrl,
|
|
1243
1698
|
token: config.token,
|
|
1244
1699
|
orgId: config.orgId,
|
|
@@ -1268,7 +1723,7 @@ function spawnSkillifyWorker(opts) {
|
|
|
1268
1723
|
} catch {
|
|
1269
1724
|
}
|
|
1270
1725
|
skillifyLog(`${reason}: spawning skillify worker for project=${project} key=${projectKey}`);
|
|
1271
|
-
const workerPath =
|
|
1726
|
+
const workerPath = join15(bundleDir, "skillify-worker.js");
|
|
1272
1727
|
spawn3("nohup", ["node", workerPath, configFile], {
|
|
1273
1728
|
detached: true,
|
|
1274
1729
|
stdio: ["ignore", "ignore", "ignore"]
|
|
@@ -1277,31 +1732,31 @@ function spawnSkillifyWorker(opts) {
|
|
|
1277
1732
|
}
|
|
1278
1733
|
|
|
1279
1734
|
// dist/src/skillify/state.js
|
|
1280
|
-
import { readFileSync as
|
|
1735
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync8, writeSync as writeSync3, mkdirSync as mkdirSync10, renameSync as renameSync6, existsSync as existsSync9, unlinkSync as unlinkSync5, openSync as openSync4, closeSync as closeSync4 } from "node:fs";
|
|
1281
1736
|
import { execSync as execSync2 } from "node:child_process";
|
|
1282
|
-
import { homedir as
|
|
1737
|
+
import { homedir as homedir14 } from "node:os";
|
|
1283
1738
|
import { createHash } from "node:crypto";
|
|
1284
|
-
import { join as
|
|
1739
|
+
import { join as join17, basename as basename2 } from "node:path";
|
|
1285
1740
|
|
|
1286
1741
|
// dist/src/skillify/legacy-migration.js
|
|
1287
|
-
import { existsSync as
|
|
1288
|
-
import { homedir as
|
|
1289
|
-
import { join as
|
|
1742
|
+
import { existsSync as existsSync8, renameSync as renameSync5 } from "node:fs";
|
|
1743
|
+
import { homedir as homedir13 } from "node:os";
|
|
1744
|
+
import { join as join16 } from "node:path";
|
|
1290
1745
|
var dlog2 = (msg) => log("skillify-migrate", msg);
|
|
1291
1746
|
var attempted = false;
|
|
1292
1747
|
function migrateLegacyStateDir() {
|
|
1293
1748
|
if (attempted)
|
|
1294
1749
|
return;
|
|
1295
1750
|
attempted = true;
|
|
1296
|
-
const root =
|
|
1297
|
-
const legacy =
|
|
1298
|
-
const current =
|
|
1299
|
-
if (!
|
|
1751
|
+
const root = join16(homedir13(), ".deeplake", "state");
|
|
1752
|
+
const legacy = join16(root, "skilify");
|
|
1753
|
+
const current = join16(root, "skillify");
|
|
1754
|
+
if (!existsSync8(legacy))
|
|
1300
1755
|
return;
|
|
1301
|
-
if (
|
|
1756
|
+
if (existsSync8(current))
|
|
1302
1757
|
return;
|
|
1303
1758
|
try {
|
|
1304
|
-
|
|
1759
|
+
renameSync5(legacy, current);
|
|
1305
1760
|
dlog2(`migrated ${legacy} -> ${current}`);
|
|
1306
1761
|
} catch (err) {
|
|
1307
1762
|
const code = err.code;
|
|
@@ -1315,17 +1770,17 @@ function migrateLegacyStateDir() {
|
|
|
1315
1770
|
|
|
1316
1771
|
// dist/src/skillify/state.js
|
|
1317
1772
|
var dlog3 = (msg) => log("skillify-state", msg);
|
|
1318
|
-
var STATE_DIR2 =
|
|
1773
|
+
var STATE_DIR2 = join17(homedir14(), ".deeplake", "state", "skillify");
|
|
1319
1774
|
var YIELD_BUF2 = new Int32Array(new SharedArrayBuffer(4));
|
|
1320
1775
|
var TRIGGER_THRESHOLD = (() => {
|
|
1321
1776
|
const n = Number(process.env.HIVEMIND_SKILLIFY_EVERY_N_TURNS ?? "");
|
|
1322
1777
|
return Number.isInteger(n) && n > 0 ? n : 20;
|
|
1323
1778
|
})();
|
|
1324
1779
|
function statePath2(projectKey) {
|
|
1325
|
-
return
|
|
1780
|
+
return join17(STATE_DIR2, `${projectKey}.json`);
|
|
1326
1781
|
}
|
|
1327
|
-
function
|
|
1328
|
-
return
|
|
1782
|
+
function lockPath3(projectKey) {
|
|
1783
|
+
return join17(STATE_DIR2, `${projectKey}.lock`);
|
|
1329
1784
|
}
|
|
1330
1785
|
var DEFAULT_PORTS = {
|
|
1331
1786
|
http: "80",
|
|
@@ -1353,7 +1808,7 @@ function normalizeGitRemoteUrl(url) {
|
|
|
1353
1808
|
return s.toLowerCase();
|
|
1354
1809
|
}
|
|
1355
1810
|
function deriveProjectKey(cwd) {
|
|
1356
|
-
const project =
|
|
1811
|
+
const project = basename2(cwd) || "unknown";
|
|
1357
1812
|
let signature = null;
|
|
1358
1813
|
try {
|
|
1359
1814
|
const raw = execSync2("git config --get remote.origin.url", {
|
|
@@ -1371,38 +1826,38 @@ function deriveProjectKey(cwd) {
|
|
|
1371
1826
|
function readState2(projectKey) {
|
|
1372
1827
|
migrateLegacyStateDir();
|
|
1373
1828
|
const p = statePath2(projectKey);
|
|
1374
|
-
if (!
|
|
1829
|
+
if (!existsSync9(p))
|
|
1375
1830
|
return null;
|
|
1376
1831
|
try {
|
|
1377
|
-
return JSON.parse(
|
|
1832
|
+
return JSON.parse(readFileSync9(p, "utf-8"));
|
|
1378
1833
|
} catch {
|
|
1379
1834
|
return null;
|
|
1380
1835
|
}
|
|
1381
1836
|
}
|
|
1382
1837
|
function writeState2(projectKey, state) {
|
|
1383
1838
|
migrateLegacyStateDir();
|
|
1384
|
-
|
|
1839
|
+
mkdirSync10(STATE_DIR2, { recursive: true });
|
|
1385
1840
|
const p = statePath2(projectKey);
|
|
1386
1841
|
const tmp = `${p}.${process.pid}.${Date.now()}.tmp`;
|
|
1387
|
-
|
|
1388
|
-
|
|
1842
|
+
writeFileSync8(tmp, JSON.stringify(state, null, 2));
|
|
1843
|
+
renameSync6(tmp, p);
|
|
1389
1844
|
}
|
|
1390
1845
|
function withRmwLock2(projectKey, fn) {
|
|
1391
1846
|
migrateLegacyStateDir();
|
|
1392
|
-
|
|
1393
|
-
const rmw =
|
|
1847
|
+
mkdirSync10(STATE_DIR2, { recursive: true });
|
|
1848
|
+
const rmw = lockPath3(projectKey) + ".rmw";
|
|
1394
1849
|
const deadline = Date.now() + 2e3;
|
|
1395
1850
|
let fd = null;
|
|
1396
1851
|
while (fd === null) {
|
|
1397
1852
|
try {
|
|
1398
|
-
fd =
|
|
1853
|
+
fd = openSync4(rmw, "wx");
|
|
1399
1854
|
} catch (e) {
|
|
1400
1855
|
if (e.code !== "EEXIST")
|
|
1401
1856
|
throw e;
|
|
1402
1857
|
if (Date.now() > deadline) {
|
|
1403
1858
|
dlog3(`rmw lock deadline exceeded for ${projectKey}, reclaiming stale lock`);
|
|
1404
1859
|
try {
|
|
1405
|
-
|
|
1860
|
+
unlinkSync5(rmw);
|
|
1406
1861
|
} catch (unlinkErr) {
|
|
1407
1862
|
dlog3(`stale rmw lock unlink failed for ${projectKey}: ${unlinkErr.message}`);
|
|
1408
1863
|
}
|
|
@@ -1414,9 +1869,9 @@ function withRmwLock2(projectKey, fn) {
|
|
|
1414
1869
|
try {
|
|
1415
1870
|
return fn();
|
|
1416
1871
|
} finally {
|
|
1417
|
-
|
|
1872
|
+
closeSync4(fd);
|
|
1418
1873
|
try {
|
|
1419
|
-
|
|
1874
|
+
unlinkSync5(rmw);
|
|
1420
1875
|
} catch (unlinkErr) {
|
|
1421
1876
|
dlog3(`rmw lock cleanup failed for ${projectKey}: ${unlinkErr.message}`);
|
|
1422
1877
|
}
|
|
@@ -1449,29 +1904,29 @@ function resetCounter(projectKey) {
|
|
|
1449
1904
|
}
|
|
1450
1905
|
function tryAcquireWorkerLock(projectKey, maxAgeMs = 10 * 60 * 1e3) {
|
|
1451
1906
|
migrateLegacyStateDir();
|
|
1452
|
-
|
|
1453
|
-
const p =
|
|
1454
|
-
if (
|
|
1907
|
+
mkdirSync10(STATE_DIR2, { recursive: true });
|
|
1908
|
+
const p = lockPath3(projectKey);
|
|
1909
|
+
if (existsSync9(p)) {
|
|
1455
1910
|
try {
|
|
1456
|
-
const ageMs = Date.now() - parseInt(
|
|
1911
|
+
const ageMs = Date.now() - parseInt(readFileSync9(p, "utf-8"), 10);
|
|
1457
1912
|
if (Number.isFinite(ageMs) && ageMs < maxAgeMs)
|
|
1458
1913
|
return false;
|
|
1459
1914
|
} catch (readErr) {
|
|
1460
1915
|
dlog3(`worker lock unreadable for ${projectKey}, treating as stale: ${readErr.message}`);
|
|
1461
1916
|
}
|
|
1462
1917
|
try {
|
|
1463
|
-
|
|
1918
|
+
unlinkSync5(p);
|
|
1464
1919
|
} catch (unlinkErr) {
|
|
1465
1920
|
dlog3(`could not unlink stale worker lock for ${projectKey}: ${unlinkErr.message}`);
|
|
1466
1921
|
return false;
|
|
1467
1922
|
}
|
|
1468
1923
|
}
|
|
1469
1924
|
try {
|
|
1470
|
-
const fd =
|
|
1925
|
+
const fd = openSync4(p, "wx");
|
|
1471
1926
|
try {
|
|
1472
1927
|
writeSync3(fd, String(Date.now()));
|
|
1473
1928
|
} finally {
|
|
1474
|
-
|
|
1929
|
+
closeSync4(fd);
|
|
1475
1930
|
}
|
|
1476
1931
|
return true;
|
|
1477
1932
|
} catch {
|
|
@@ -1479,26 +1934,26 @@ function tryAcquireWorkerLock(projectKey, maxAgeMs = 10 * 60 * 1e3) {
|
|
|
1479
1934
|
}
|
|
1480
1935
|
}
|
|
1481
1936
|
function releaseWorkerLock(projectKey) {
|
|
1482
|
-
const p =
|
|
1937
|
+
const p = lockPath3(projectKey);
|
|
1483
1938
|
try {
|
|
1484
|
-
|
|
1939
|
+
unlinkSync5(p);
|
|
1485
1940
|
} catch {
|
|
1486
1941
|
}
|
|
1487
1942
|
}
|
|
1488
1943
|
|
|
1489
1944
|
// 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 =
|
|
1945
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync11, readFileSync as readFileSync10, writeFileSync as writeFileSync9 } from "node:fs";
|
|
1946
|
+
import { homedir as homedir15 } from "node:os";
|
|
1947
|
+
import { join as join18 } from "node:path";
|
|
1948
|
+
var STATE_DIR3 = join18(homedir15(), ".deeplake", "state", "skillify");
|
|
1949
|
+
var CONFIG_PATH = join18(STATE_DIR3, "config.json");
|
|
1495
1950
|
var DEFAULT = { scope: "me", team: [], install: "project" };
|
|
1496
1951
|
function loadScopeConfig() {
|
|
1497
1952
|
migrateLegacyStateDir();
|
|
1498
|
-
if (!
|
|
1953
|
+
if (!existsSync10(CONFIG_PATH))
|
|
1499
1954
|
return DEFAULT;
|
|
1500
1955
|
try {
|
|
1501
|
-
const raw = JSON.parse(
|
|
1956
|
+
const raw = JSON.parse(readFileSync10(CONFIG_PATH, "utf-8"));
|
|
1502
1957
|
const scope = raw.scope === "team" ? "team" : raw.scope === "org" ? "team" : "me";
|
|
1503
1958
|
const team = Array.isArray(raw.team) ? raw.team.filter((s) => typeof s === "string") : [];
|
|
1504
1959
|
const install = raw.install === "global" ? "global" : "project";
|
|
@@ -1549,12 +2004,18 @@ function tryStopCounterTrigger(opts) {
|
|
|
1549
2004
|
}
|
|
1550
2005
|
|
|
1551
2006
|
// dist/src/hooks/hermes/capture.js
|
|
1552
|
-
var
|
|
2007
|
+
var log5 = (msg) => log("hermes-capture", msg);
|
|
1553
2008
|
function resolveEmbedDaemonPath() {
|
|
1554
|
-
return
|
|
2009
|
+
return join19(dirname6(fileURLToPath3(import.meta.url)), "embeddings", "embed-daemon.js");
|
|
1555
2010
|
}
|
|
1556
|
-
var __bundleDir =
|
|
2011
|
+
var __bundleDir = dirname6(fileURLToPath3(import.meta.url));
|
|
1557
2012
|
var PLUGIN_VERSION = getInstalledVersion(__bundleDir, ".claude-plugin") ?? "";
|
|
2013
|
+
if (!embeddingsDisabled()) {
|
|
2014
|
+
try {
|
|
2015
|
+
ensurePluginNodeModulesLink({ bundleDir: __bundleDir });
|
|
2016
|
+
} catch {
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
1558
2019
|
var CAPTURE = process.env.HIVEMIND_CAPTURE !== "false";
|
|
1559
2020
|
function pickString(...candidates) {
|
|
1560
2021
|
for (const c of candidates) {
|
|
@@ -1569,7 +2030,7 @@ async function main() {
|
|
|
1569
2030
|
const input = await readStdin();
|
|
1570
2031
|
const config = loadConfig();
|
|
1571
2032
|
if (!config) {
|
|
1572
|
-
|
|
2033
|
+
log5("no config");
|
|
1573
2034
|
return;
|
|
1574
2035
|
}
|
|
1575
2036
|
const sessionId = input.session_id ?? `hermes-${Date.now()}`;
|
|
@@ -1589,14 +2050,14 @@ async function main() {
|
|
|
1589
2050
|
if (event === "pre_llm_call") {
|
|
1590
2051
|
const prompt = pickString(extra.prompt, extra.user_message, extra.message?.content);
|
|
1591
2052
|
if (!prompt) {
|
|
1592
|
-
|
|
2053
|
+
log5(`pre_llm_call: no prompt found in extra`);
|
|
1593
2054
|
return;
|
|
1594
2055
|
}
|
|
1595
|
-
|
|
2056
|
+
log5(`user session=${sessionId}`);
|
|
1596
2057
|
entry = { id: crypto.randomUUID(), ...meta, type: "user_message", content: prompt };
|
|
1597
2058
|
} else if (event === "post_tool_call" && typeof input.tool_name === "string") {
|
|
1598
2059
|
const toolResponse = extra.tool_result ?? extra.tool_output ?? extra.result ?? extra.output;
|
|
1599
|
-
|
|
2060
|
+
log5(`tool=${input.tool_name} session=${sessionId}`);
|
|
1600
2061
|
entry = {
|
|
1601
2062
|
id: crypto.randomUUID(),
|
|
1602
2063
|
...meta,
|
|
@@ -1608,18 +2069,18 @@ async function main() {
|
|
|
1608
2069
|
} else if (event === "post_llm_call") {
|
|
1609
2070
|
const text = pickString(extra.response, extra.assistant_message, extra.message?.content);
|
|
1610
2071
|
if (!text) {
|
|
1611
|
-
|
|
2072
|
+
log5(`post_llm_call: no response found in extra`);
|
|
1612
2073
|
return;
|
|
1613
2074
|
}
|
|
1614
|
-
|
|
2075
|
+
log5(`assistant session=${sessionId}`);
|
|
1615
2076
|
entry = { id: crypto.randomUUID(), ...meta, type: "assistant_message", content: text };
|
|
1616
2077
|
} else {
|
|
1617
|
-
|
|
2078
|
+
log5(`unknown/unhandled event: ${event}, skipping`);
|
|
1618
2079
|
return;
|
|
1619
2080
|
}
|
|
1620
2081
|
const sessionPath = buildSessionPath(config, sessionId);
|
|
1621
2082
|
const line = JSON.stringify(entry);
|
|
1622
|
-
|
|
2083
|
+
log5(`writing to ${sessionPath}`);
|
|
1623
2084
|
const projectName = cwd.split("/").pop() || "unknown";
|
|
1624
2085
|
const filename = sessionPath.split("/").pop() ?? "";
|
|
1625
2086
|
const jsonForSql = line.replace(/'/g, "''");
|
|
@@ -1630,14 +2091,14 @@ async function main() {
|
|
|
1630
2091
|
await api.query(insertSql);
|
|
1631
2092
|
} catch (e) {
|
|
1632
2093
|
if (e.message?.includes("permission denied") || e.message?.includes("does not exist")) {
|
|
1633
|
-
|
|
2094
|
+
log5("table missing, creating and retrying");
|
|
1634
2095
|
await api.ensureSessionsTable(sessionsTable);
|
|
1635
2096
|
await api.query(insertSql);
|
|
1636
2097
|
} else {
|
|
1637
2098
|
throw e;
|
|
1638
2099
|
}
|
|
1639
2100
|
}
|
|
1640
|
-
|
|
2101
|
+
log5("capture ok \u2192 cloud");
|
|
1641
2102
|
maybeTriggerPeriodicSummary(sessionId, cwd, config);
|
|
1642
2103
|
if (event === "post_llm_call" && process.env.HIVEMIND_WIKI_WORKER !== "1" && process.env.HIVEMIND_SKILLIFY_WORKER !== "1") {
|
|
1643
2104
|
tryStopCounterTrigger({
|
|
@@ -1658,7 +2119,7 @@ function maybeTriggerPeriodicSummary(sessionId, cwd, config) {
|
|
|
1658
2119
|
if (!shouldTrigger(state, cfg))
|
|
1659
2120
|
return;
|
|
1660
2121
|
if (!tryAcquireLock(sessionId)) {
|
|
1661
|
-
|
|
2122
|
+
log5(`periodic trigger suppressed (lock held) session=${sessionId}`);
|
|
1662
2123
|
return;
|
|
1663
2124
|
}
|
|
1664
2125
|
wikiLog(`Periodic: threshold hit (total=${state.totalCount}, since=${state.totalCount - state.lastSummaryCount}, N=${cfg.everyNMessages}, hours=${cfg.everyHours})`);
|
|
@@ -1671,17 +2132,17 @@ function maybeTriggerPeriodicSummary(sessionId, cwd, config) {
|
|
|
1671
2132
|
reason: "Periodic"
|
|
1672
2133
|
});
|
|
1673
2134
|
} catch (e) {
|
|
1674
|
-
|
|
2135
|
+
log5(`periodic spawn failed: ${e.message}`);
|
|
1675
2136
|
try {
|
|
1676
2137
|
releaseLock(sessionId);
|
|
1677
2138
|
} catch {
|
|
1678
2139
|
}
|
|
1679
2140
|
}
|
|
1680
2141
|
} catch (e) {
|
|
1681
|
-
|
|
2142
|
+
log5(`periodic trigger error: ${e.message}`);
|
|
1682
2143
|
}
|
|
1683
2144
|
}
|
|
1684
2145
|
main().catch((e) => {
|
|
1685
|
-
|
|
2146
|
+
log5(`fatal: ${e.message}`);
|
|
1686
2147
|
process.exit(0);
|
|
1687
2148
|
});
|