@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.
@@ -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 readFileSync2, writeFileSync } from "node:fs";
20
- import { join as join3 } from "node:path";
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 ?? join3(tmpdir(), "hivemind-deeplake-indexes");
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 join3(getIndexMarkerDir(), `${markerKey}.json`);
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(readFileSync2(markerPath, "utf-8"));
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
- mkdirSync(getIndexMarkerDir(), { recursive: true });
44
- writeFileSync(markerPath, JSON.stringify({ updatedAt: (/* @__PURE__ */ new Date()).toISOString() }), "utf-8");
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((resolve, reject) => {
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
- resolve(JSON.parse(data));
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 log2 = (msg) => log("sdk", msg);
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
- log2(msg);
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 sleep(ms) {
178
- return new Promise((resolve) => setTimeout(resolve, ms));
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((resolve) => this.waiting.push(resolve));
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
- log2(`query retry ${attempt + 1}/${MAX_RETRIES} (fetch error: ${lastError.message}) in ${delay.toFixed(0)}ms`);
280
- await sleep(delay);
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
- log2(`query retry ${attempt + 1}/${MAX_RETRIES} (${resp.status}) in ${delay.toFixed(0)}ms`);
297
- await sleep(delay);
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
- log2(`commit: ${rows.length} rows`);
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
- log2(`index "${indexName}" skipped: ${e.message}`);
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 sleep(BASE_DELAY_MS * Math.pow(2, attempt) + Math.random() * 200);
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 sleep(BASE_DELAY_MS * Math.pow(2, attempt));
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
- log2(`CREATE TABLE "${label}" attempt ${attempt + 1}/${OUTER_BACKOFFS_MS.length + 1} failed: ${msg}`);
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 sleep(OUTER_BACKOFFS_MS[attempt]);
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
- log2(`table "${tbl}" not found, creating`);
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
- log2(`table "${tbl}" created`);
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
- log2(`table "${safe}" not found, creating`);
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
- log2(`table "${safe}" created`);
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
- log2(`table "${safe}" not found, creating`);
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
- log2(`table "${safe}" created`);
706
+ log3(`table "${safe}" created`);
556
707
  if (!tables.includes(safe))
557
708
  this._tablesCache = [...tables, safe];
558
709
  }
@@ -1042,9 +1193,9 @@ function capOutputForClaude(output, options = {}) {
1042
1193
  // dist/src/embeddings/client.js
1043
1194
  import { connect } from "node:net";
1044
1195
  import { spawn } from "node:child_process";
1045
- import { openSync, closeSync, writeSync, unlinkSync, existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
1046
- import { homedir as homedir3 } from "node:os";
1047
- import { join as join4 } from "node:path";
1196
+ import { openSync as openSync2, closeSync as closeSync2, writeSync, unlinkSync as unlinkSync3, existsSync as existsSync3, readFileSync as readFileSync5 } from "node:fs";
1197
+ import { homedir as homedir5 } from "node:os";
1198
+ import { join as join6 } from "node:path";
1048
1199
 
1049
1200
  // dist/src/embeddings/protocol.js
1050
1201
  var DEFAULT_SOCKET_DIR = "/tmp";
@@ -1058,12 +1209,13 @@ function pidPathFor(uid, dir = DEFAULT_SOCKET_DIR) {
1058
1209
  }
1059
1210
 
1060
1211
  // dist/src/embeddings/client.js
1061
- var SHARED_DAEMON_PATH = join4(homedir3(), ".hivemind", "embed-deps", "embed-daemon.js");
1062
- var log3 = (m) => log("embed-client", m);
1212
+ var SHARED_DAEMON_PATH = join6(homedir5(), ".hivemind", "embed-deps", "embed-daemon.js");
1213
+ var log4 = (m) => log("embed-client", m);
1063
1214
  function getUid() {
1064
1215
  const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
1065
1216
  return uid !== void 0 ? String(uid) : process.env.USER ?? "default";
1066
1217
  }
1218
+ var _recycledStuckDaemon = false;
1067
1219
  var EmbedClient = class {
1068
1220
  socketPath;
1069
1221
  pidPath;
@@ -1072,6 +1224,7 @@ var EmbedClient = class {
1072
1224
  autoSpawn;
1073
1225
  spawnWaitMs;
1074
1226
  nextId = 0;
1227
+ helloVerified = false;
1075
1228
  constructor(opts = {}) {
1076
1229
  const uid = getUid();
1077
1230
  const dir = opts.socketDir ?? "/tmp";
@@ -1088,8 +1241,33 @@ var EmbedClient = class {
1088
1241
  *
1089
1242
  * Fire-and-forget spawn on miss: if the daemon isn't up, this call returns
1090
1243
  * null AND kicks off a background spawn. The next call finds a ready daemon.
1244
+ *
1245
+ * Stuck-daemon recycle: if the daemon returns a transformers-missing
1246
+ * error (typical after a marketplace upgrade left an older daemon process
1247
+ * alive but with no node_modules accessible from its bundle path), we
1248
+ * SIGTERM it and clear its sock/pid so the very next call spawns a fresh
1249
+ * daemon from the current bundle. Without this, the stuck daemon would
1250
+ * keep poisoning every session until its 10-minute idle-out fires.
1091
1251
  */
1092
1252
  async embed(text, kind = "document") {
1253
+ const v = await this.embedAttempt(text, kind);
1254
+ if (v !== "recycled")
1255
+ return v;
1256
+ if (!this.autoSpawn)
1257
+ return null;
1258
+ this.trySpawnDaemon();
1259
+ await this.waitForDaemonReady();
1260
+ const retry = await this.embedAttempt(text, kind);
1261
+ return retry === "recycled" ? null : retry;
1262
+ }
1263
+ /**
1264
+ * One round-trip: connect → verify → embed. Returns:
1265
+ * - number[] : embedding vector (happy path)
1266
+ * - null : timeout / daemon error / transformers-missing
1267
+ * - "recycled": verifyDaemonOnce killed the daemon mid-call;
1268
+ * caller should respawn and retry once.
1269
+ */
1270
+ async embedAttempt(text, kind) {
1093
1271
  let sock;
1094
1272
  try {
1095
1273
  sock = await this.connectOnce();
@@ -1099,17 +1277,25 @@ var EmbedClient = class {
1099
1277
  return null;
1100
1278
  }
1101
1279
  try {
1280
+ const recycled = await this.verifyDaemonOnce(sock);
1281
+ if (recycled) {
1282
+ return "recycled";
1283
+ }
1102
1284
  const id = String(++this.nextId);
1103
1285
  const req = { op: "embed", id, kind, text };
1104
1286
  const resp = await this.sendAndWait(sock, req);
1105
1287
  if (resp.error || !("embedding" in resp) || !resp.embedding) {
1106
- log3(`embed err: ${resp.error ?? "no embedding"}`);
1288
+ const err = resp.error ?? "no embedding";
1289
+ log4(`embed err: ${err}`);
1290
+ if (isTransformersMissingError(err)) {
1291
+ this.handleTransformersMissing(err);
1292
+ }
1107
1293
  return null;
1108
1294
  }
1109
1295
  return resp.embedding;
1110
1296
  } catch (e) {
1111
1297
  const err = e instanceof Error ? e.message : String(e);
1112
- log3(`embed failed: ${err}`);
1298
+ log4(`embed failed: ${err}`);
1113
1299
  return null;
1114
1300
  } finally {
1115
1301
  try {
@@ -1118,6 +1304,123 @@ var EmbedClient = class {
1118
1304
  }
1119
1305
  }
1120
1306
  }
1307
+ /**
1308
+ * Poll for the sock file to come back after `trySpawnDaemon` — used by
1309
+ * the recycle retry path. Best-effort: caps at `spawnWaitMs` and
1310
+ * returns regardless so the retry attempt can run.
1311
+ */
1312
+ async waitForDaemonReady() {
1313
+ const deadline = Date.now() + this.spawnWaitMs;
1314
+ while (Date.now() < deadline) {
1315
+ if (existsSync3(this.socketPath))
1316
+ return;
1317
+ await new Promise((r) => setTimeout(r, 50));
1318
+ }
1319
+ }
1320
+ /**
1321
+ * Send a `hello` on first successful connect per EmbedClient instance.
1322
+ * If the daemon answers with a path that doesn't match our configured
1323
+ * daemonEntry — typical after a marketplace upgrade replaced the bundle
1324
+ * — SIGTERM the daemon + clear sock/pid so the next call spawns from the
1325
+ * current bundle.
1326
+ *
1327
+ * `helloVerified` is set ONLY after we've seen a compatible response,
1328
+ * so a transient probe failure or a recycle-triggering mismatch leaves
1329
+ * the flag false; the next reconnect re-runs verification against
1330
+ * whatever daemon is then live (typically the fresh spawn).
1331
+ */
1332
+ async verifyDaemonOnce(sock) {
1333
+ if (this.helloVerified)
1334
+ return false;
1335
+ if (!this.daemonEntry) {
1336
+ this.helloVerified = true;
1337
+ return false;
1338
+ }
1339
+ const id = String(++this.nextId);
1340
+ const req = { op: "hello", id };
1341
+ let resp;
1342
+ try {
1343
+ resp = await this.sendAndWait(sock, req);
1344
+ } catch (e) {
1345
+ log4(`hello probe failed (inconclusive, will retry next connect): ${e instanceof Error ? e.message : String(e)}`);
1346
+ return false;
1347
+ }
1348
+ const hello = resp;
1349
+ if (_recycledStuckDaemon) {
1350
+ return false;
1351
+ }
1352
+ if (!hello.daemonPath) {
1353
+ _recycledStuckDaemon = true;
1354
+ log4(`daemon does not implement hello (older protocol); recycling`);
1355
+ this.recycleDaemon(hello.pid);
1356
+ return true;
1357
+ }
1358
+ if (hello.daemonPath !== this.daemonEntry && !existsSync3(hello.daemonPath)) {
1359
+ _recycledStuckDaemon = true;
1360
+ log4(`daemon path no longer on disk \u2014 running=${hello.daemonPath} (gone) expected=${this.daemonEntry}; recycling`);
1361
+ this.recycleDaemon(hello.pid);
1362
+ return true;
1363
+ }
1364
+ this.helloVerified = true;
1365
+ return false;
1366
+ }
1367
+ /**
1368
+ * On a transformers-missing error from the daemon, SIGTERM the stuck
1369
+ * daemon (the bundle daemon that can't find its deps) and clear
1370
+ * sock/pid so the next call spawns fresh.
1371
+ *
1372
+ * Previously this also enqueued a user-visible "Hivemind embeddings
1373
+ * disabled — deps missing" notification telling the user to run
1374
+ * `hivemind embeddings install`. The notification was removed because
1375
+ * (a) the recycle alone often fixes the issue silently, and (b) the
1376
+ * warning kept stacking on top of the primary session-start banner
1377
+ * which clashed with the single-slot priority model. The `detail`
1378
+ * argument is retained for future telemetry / debug logging.
1379
+ */
1380
+ handleTransformersMissing(_detail) {
1381
+ if (!_recycledStuckDaemon) {
1382
+ _recycledStuckDaemon = true;
1383
+ this.recycleDaemon(null);
1384
+ }
1385
+ }
1386
+ /**
1387
+ * Best-effort SIGTERM + sock/pid cleanup. Tolerant of every missing-file
1388
+ * combination and dead-PID cases.
1389
+ *
1390
+ * Identity check: gate the SIGTERM on the daemon's socket file still
1391
+ * existing. We know the daemon was alive moments ago (we either just
1392
+ * got a hello response or the caller saw a transformers-missing error
1393
+ * the daemon emitted), but if the socket file is gone by the time we
1394
+ * try to kill, the daemon process is also gone and the PID we
1395
+ * captured may already have been recycled by the OS to an unrelated
1396
+ * user process. Mirrors the gate added to `killEmbedDaemon` in the
1397
+ * CLI — same failure mode, rarer trigger.
1398
+ */
1399
+ recycleDaemon(reportedPid) {
1400
+ let pid = reportedPid;
1401
+ if (pid === null) {
1402
+ try {
1403
+ pid = Number.parseInt(readFileSync5(this.pidPath, "utf-8").trim(), 10);
1404
+ } catch {
1405
+ }
1406
+ }
1407
+ if (Number.isFinite(pid) && pid !== null && pid > 0 && existsSync3(this.socketPath)) {
1408
+ try {
1409
+ process.kill(pid, "SIGTERM");
1410
+ } catch {
1411
+ }
1412
+ } else if (pid !== null) {
1413
+ log4(`recycle: socket gone, skipping SIGTERM on possibly-stale pid ${pid}`);
1414
+ }
1415
+ try {
1416
+ unlinkSync3(this.socketPath);
1417
+ } catch {
1418
+ }
1419
+ try {
1420
+ unlinkSync3(this.pidPath);
1421
+ } catch {
1422
+ }
1423
+ }
1121
1424
  /**
1122
1425
  * Wait up to spawnWaitMs for the daemon to accept connections, spawning if
1123
1426
  * necessary. Meant for SessionStart / long-running batches — not the hot path.
@@ -1141,7 +1444,7 @@ var EmbedClient = class {
1141
1444
  }
1142
1445
  }
1143
1446
  connectOnce() {
1144
- return new Promise((resolve, reject) => {
1447
+ return new Promise((resolve2, reject) => {
1145
1448
  const sock = connect(this.socketPath);
1146
1449
  const to = setTimeout(() => {
1147
1450
  sock.destroy();
@@ -1149,7 +1452,7 @@ var EmbedClient = class {
1149
1452
  }, this.timeoutMs);
1150
1453
  sock.once("connect", () => {
1151
1454
  clearTimeout(to);
1152
- resolve(sock);
1455
+ resolve2(sock);
1153
1456
  });
1154
1457
  sock.once("error", (e) => {
1155
1458
  clearTimeout(to);
@@ -1160,16 +1463,16 @@ var EmbedClient = class {
1160
1463
  trySpawnDaemon() {
1161
1464
  let fd;
1162
1465
  try {
1163
- fd = openSync(this.pidPath, "wx", 384);
1466
+ fd = openSync2(this.pidPath, "wx", 384);
1164
1467
  writeSync(fd, String(process.pid));
1165
1468
  } catch (e) {
1166
1469
  if (this.isPidFileStale()) {
1167
1470
  try {
1168
- unlinkSync(this.pidPath);
1471
+ unlinkSync3(this.pidPath);
1169
1472
  } catch {
1170
1473
  }
1171
1474
  try {
1172
- fd = openSync(this.pidPath, "wx", 384);
1475
+ fd = openSync2(this.pidPath, "wx", 384);
1173
1476
  writeSync(fd, String(process.pid));
1174
1477
  } catch {
1175
1478
  return;
@@ -1179,10 +1482,10 @@ var EmbedClient = class {
1179
1482
  }
1180
1483
  }
1181
1484
  if (!this.daemonEntry || !existsSync3(this.daemonEntry)) {
1182
- log3(`daemonEntry not configured or missing: ${this.daemonEntry}`);
1485
+ log4(`daemonEntry not configured or missing: ${this.daemonEntry}`);
1183
1486
  try {
1184
- closeSync(fd);
1185
- unlinkSync(this.pidPath);
1487
+ closeSync2(fd);
1488
+ unlinkSync3(this.pidPath);
1186
1489
  } catch {
1187
1490
  }
1188
1491
  return;
@@ -1194,14 +1497,14 @@ var EmbedClient = class {
1194
1497
  env: process.env
1195
1498
  });
1196
1499
  child.unref();
1197
- log3(`spawned daemon pid=${child.pid}`);
1500
+ log4(`spawned daemon pid=${child.pid}`);
1198
1501
  } finally {
1199
- closeSync(fd);
1502
+ closeSync2(fd);
1200
1503
  }
1201
1504
  }
1202
1505
  isPidFileStale() {
1203
1506
  try {
1204
- const raw = readFileSync3(this.pidPath, "utf-8").trim();
1507
+ const raw = readFileSync5(this.pidPath, "utf-8").trim();
1205
1508
  const pid = Number(raw);
1206
1509
  if (!pid || Number.isNaN(pid))
1207
1510
  return true;
@@ -1219,7 +1522,7 @@ var EmbedClient = class {
1219
1522
  const deadline = Date.now() + this.spawnWaitMs;
1220
1523
  let delay = 30;
1221
1524
  while (Date.now() < deadline) {
1222
- await sleep2(delay);
1525
+ await sleep3(delay);
1223
1526
  delay = Math.min(delay * 1.5, 300);
1224
1527
  if (!existsSync3(this.socketPath))
1225
1528
  continue;
@@ -1231,7 +1534,7 @@ var EmbedClient = class {
1231
1534
  throw new Error("daemon did not become ready within spawnWaitMs");
1232
1535
  }
1233
1536
  sendAndWait(sock, req) {
1234
- return new Promise((resolve, reject) => {
1537
+ return new Promise((resolve2, reject) => {
1235
1538
  let buf = "";
1236
1539
  const to = setTimeout(() => {
1237
1540
  sock.destroy();
@@ -1246,7 +1549,7 @@ var EmbedClient = class {
1246
1549
  const line = buf.slice(0, nl);
1247
1550
  clearTimeout(to);
1248
1551
  try {
1249
- resolve(JSON.parse(line));
1552
+ resolve2(JSON.parse(line));
1250
1553
  } catch (e) {
1251
1554
  reject(e);
1252
1555
  }
@@ -1263,29 +1566,116 @@ var EmbedClient = class {
1263
1566
  });
1264
1567
  }
1265
1568
  };
1266
- function sleep2(ms) {
1569
+ function sleep3(ms) {
1267
1570
  return new Promise((r) => setTimeout(r, ms));
1268
1571
  }
1572
+ function isTransformersMissingError(err) {
1573
+ if (/hivemind embeddings install/i.test(err))
1574
+ return true;
1575
+ return /@huggingface\/transformers/i.test(err);
1576
+ }
1269
1577
 
1270
1578
  // dist/src/embeddings/disable.js
1271
1579
  import { createRequire } from "node:module";
1272
- import { homedir as homedir4 } from "node:os";
1273
- import { join as join5 } from "node:path";
1580
+ import { homedir as homedir7 } from "node:os";
1581
+ import { join as join8 } from "node:path";
1274
1582
  import { pathToFileURL } from "node:url";
1583
+
1584
+ // dist/src/user-config.js
1585
+ import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync6, renameSync as renameSync2, writeFileSync as writeFileSync4 } from "node:fs";
1586
+ import { homedir as homedir6 } from "node:os";
1587
+ import { dirname, join as join7 } from "node:path";
1588
+ var _configPath = () => process.env.HIVEMIND_CONFIG_PATH ?? join7(homedir6(), ".deeplake", "config.json");
1589
+ var _cache = null;
1590
+ var _migrated = false;
1591
+ function readUserConfig() {
1592
+ if (_cache !== null)
1593
+ return _cache;
1594
+ const path = _configPath();
1595
+ if (!existsSync4(path)) {
1596
+ _cache = {};
1597
+ return _cache;
1598
+ }
1599
+ try {
1600
+ const raw = readFileSync6(path, "utf-8");
1601
+ const parsed = JSON.parse(raw);
1602
+ _cache = isPlainObject(parsed) ? parsed : {};
1603
+ } catch {
1604
+ _cache = {};
1605
+ }
1606
+ return _cache;
1607
+ }
1608
+ function writeUserConfig(patch) {
1609
+ const current = readUserConfig();
1610
+ const merged = deepMerge(current, patch);
1611
+ const path = _configPath();
1612
+ const dir = dirname(path);
1613
+ if (!existsSync4(dir))
1614
+ mkdirSync4(dir, { recursive: true });
1615
+ const tmp = `${path}.tmp.${process.pid}`;
1616
+ writeFileSync4(tmp, JSON.stringify(merged, null, 2) + "\n", "utf-8");
1617
+ renameSync2(tmp, path);
1618
+ _cache = merged;
1619
+ return merged;
1620
+ }
1621
+ function getEmbeddingsEnabled() {
1622
+ const cfg = readUserConfig();
1623
+ if (cfg.embeddings && typeof cfg.embeddings.enabled === "boolean") {
1624
+ return cfg.embeddings.enabled;
1625
+ }
1626
+ if (_migrated) {
1627
+ return migrationValueFromEnv();
1628
+ }
1629
+ _migrated = true;
1630
+ const enabled = migrationValueFromEnv();
1631
+ try {
1632
+ writeUserConfig({ embeddings: { enabled } });
1633
+ } catch {
1634
+ _cache = { ...cfg ?? {}, embeddings: { ...cfg?.embeddings ?? {}, enabled } };
1635
+ }
1636
+ return enabled;
1637
+ }
1638
+ function migrationValueFromEnv() {
1639
+ const raw = process.env.HIVEMIND_EMBEDDINGS;
1640
+ if (raw === void 0)
1641
+ return false;
1642
+ if (raw === "false")
1643
+ return false;
1644
+ return true;
1645
+ }
1646
+ function isPlainObject(value) {
1647
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1648
+ }
1649
+ function deepMerge(base, patch) {
1650
+ const out = { ...base };
1651
+ for (const key of Object.keys(patch)) {
1652
+ const patchVal = patch[key];
1653
+ const baseVal = base[key];
1654
+ if (isPlainObject(patchVal) && isPlainObject(baseVal)) {
1655
+ out[key] = { ...baseVal, ...patchVal };
1656
+ } else if (patchVal !== void 0) {
1657
+ out[key] = patchVal;
1658
+ }
1659
+ }
1660
+ return out;
1661
+ }
1662
+
1663
+ // dist/src/embeddings/disable.js
1275
1664
  var cachedStatus = null;
1276
1665
  function defaultResolveTransformers() {
1666
+ const sharedDir = join8(homedir7(), ".hivemind", "embed-deps");
1277
1667
  try {
1278
- createRequire(import.meta.url).resolve("@huggingface/transformers");
1668
+ createRequire(pathToFileURL(`${sharedDir}/`).href).resolve("@huggingface/transformers");
1279
1669
  return;
1280
1670
  } catch {
1281
1671
  }
1282
- const sharedDir = join5(homedir4(), ".hivemind", "embed-deps");
1283
- createRequire(pathToFileURL(`${sharedDir}/`).href).resolve("@huggingface/transformers");
1672
+ createRequire(import.meta.url).resolve("@huggingface/transformers");
1284
1673
  }
1285
1674
  var _resolve = defaultResolveTransformers;
1675
+ var _readEnabled = getEmbeddingsEnabled;
1286
1676
  function detectStatus() {
1287
- if (process.env.HIVEMIND_EMBEDDINGS === "false")
1288
- return "env-disabled";
1677
+ if (!_readEnabled())
1678
+ return "user-disabled";
1289
1679
  try {
1290
1680
  _resolve();
1291
1681
  return "enabled";
@@ -1305,11 +1695,11 @@ function embeddingsDisabled() {
1305
1695
 
1306
1696
  // dist/src/hooks/grep-direct.js
1307
1697
  import { fileURLToPath } from "node:url";
1308
- import { dirname, join as join6 } from "node:path";
1698
+ import { dirname as dirname2, join as join9 } from "node:path";
1309
1699
  var SEMANTIC_ENABLED = process.env.HIVEMIND_SEMANTIC_SEARCH !== "false" && !embeddingsDisabled();
1310
1700
  var SEMANTIC_TIMEOUT_MS = Number(process.env.HIVEMIND_SEMANTIC_EMBED_TIMEOUT_MS ?? "500");
1311
1701
  function resolveDaemonPath() {
1312
- return join6(dirname(fileURLToPath(import.meta.url)), "..", "embeddings", "embed-daemon.js");
1702
+ return join9(dirname2(fileURLToPath(import.meta.url)), "..", "embeddings", "embed-daemon.js");
1313
1703
  }
1314
1704
  var sharedEmbedClient = null;
1315
1705
  function getEmbedClient() {
@@ -1662,9 +2052,9 @@ async function handleGrepDirect(api, table, sessionsTable, params) {
1662
2052
  }
1663
2053
 
1664
2054
  // dist/src/hooks/memory-path-utils.js
1665
- import { homedir as homedir5 } from "node:os";
1666
- import { join as join7 } from "node:path";
1667
- var MEMORY_PATH = join7(homedir5(), ".deeplake", "memory");
2055
+ import { homedir as homedir8 } from "node:os";
2056
+ import { join as join10 } from "node:path";
2057
+ var MEMORY_PATH = join10(homedir8(), ".deeplake", "memory");
1668
2058
  var TILDE_PATH = "~/.deeplake/memory";
1669
2059
  var HOME_VAR_PATH = "$HOME/.deeplake/memory";
1670
2060
  function touchesMemory(p) {
@@ -1675,7 +2065,7 @@ function rewritePaths(cmd) {
1675
2065
  }
1676
2066
 
1677
2067
  // dist/src/hooks/hermes/pre-tool-use.js
1678
- var log4 = (msg) => log("hermes-pre-tool-use", msg);
2068
+ var log5 = (msg) => log("hermes-pre-tool-use", msg);
1679
2069
  async function main() {
1680
2070
  const input = await readStdin();
1681
2071
  if (input.tool_name !== "terminal")
@@ -1692,7 +2082,7 @@ async function main() {
1692
2082
  return;
1693
2083
  const config = loadConfig();
1694
2084
  if (!config) {
1695
- log4("no config \u2014 falling through to Hermes");
2085
+ log5("no config \u2014 falling through to Hermes");
1696
2086
  return;
1697
2087
  }
1698
2088
  const api = new DeeplakeApi(config.token, config.apiUrl, config.orgId, config.workspaceId, config.tableName);
@@ -1700,7 +2090,7 @@ async function main() {
1700
2090
  const result = await handleGrepDirect(api, config.tableName, config.sessionsTableName, grepParams);
1701
2091
  if (result === null)
1702
2092
  return;
1703
- log4(`intercepted ${command.slice(0, 80)} \u2192 ${result.length} chars from SQL fast-path`);
2093
+ log5(`intercepted ${command.slice(0, 80)} \u2192 ${result.length} chars from SQL fast-path`);
1704
2094
  const message = [
1705
2095
  result,
1706
2096
  "",
@@ -1709,10 +2099,10 @@ async function main() {
1709
2099
  process.stdout.write(JSON.stringify({ action: "block", message }));
1710
2100
  } catch (err) {
1711
2101
  const msg = err instanceof Error ? err.message : String(err);
1712
- log4(`fast-path failed, falling through: ${msg}`);
2102
+ log5(`fast-path failed, falling through: ${msg}`);
1713
2103
  }
1714
2104
  }
1715
2105
  main().catch((e) => {
1716
- log4(`fatal: ${e.message}`);
2106
+ log5(`fatal: ${e.message}`);
1717
2107
  process.exit(0);
1718
2108
  });