@deeplake/hivemind 0.7.32 → 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.
@@ -104,13 +104,112 @@ function sqlIdent(name) {
104
104
  var SUMMARY_EMBEDDING_COL = "summary_embedding";
105
105
  var MESSAGE_EMBEDDING_COL = "message_embedding";
106
106
 
107
+ // src/notifications/queue.ts
108
+ import { readFileSync, writeFileSync, renameSync, mkdirSync, openSync, closeSync, unlinkSync, statSync } from "node:fs";
109
+ import { join as join2, resolve } from "node:path";
110
+ import { homedir as homedir2 } from "node:os";
111
+ import { setTimeout as sleep } from "node:timers/promises";
112
+ var log2 = (msg) => log("notifications-queue", msg);
113
+ var LOCK_RETRY_MAX = 50;
114
+ var LOCK_RETRY_BASE_MS = 5;
115
+ var LOCK_STALE_MS = 5e3;
116
+ function queuePath() {
117
+ return join2(homedir2(), ".deeplake", "notifications-queue.json");
118
+ }
119
+ function lockPath() {
120
+ return `${queuePath()}.lock`;
121
+ }
122
+ function readQueue() {
123
+ try {
124
+ const raw = readFileSync(queuePath(), "utf-8");
125
+ const parsed = JSON.parse(raw);
126
+ if (!parsed || !Array.isArray(parsed.queue)) {
127
+ log2(`queue malformed \u2192 treating as empty`);
128
+ return { queue: [] };
129
+ }
130
+ return { queue: parsed.queue };
131
+ } catch {
132
+ return { queue: [] };
133
+ }
134
+ }
135
+ function _isQueuePathInsideHome(path, home) {
136
+ const r = resolve(path);
137
+ const h = resolve(home);
138
+ return r.startsWith(h + "/") || r === h;
139
+ }
140
+ function writeQueue(q) {
141
+ const path = queuePath();
142
+ const home = resolve(homedir2());
143
+ if (!_isQueuePathInsideHome(path, home)) {
144
+ throw new Error(`notifications-queue write blocked: ${path} is outside ${home}`);
145
+ }
146
+ mkdirSync(join2(home, ".deeplake"), { recursive: true, mode: 448 });
147
+ const tmp = `${path}.${process.pid}.tmp`;
148
+ writeFileSync(tmp, JSON.stringify(q, null, 2), { mode: 384 });
149
+ renameSync(tmp, path);
150
+ }
151
+ async function withQueueLock(fn) {
152
+ const path = lockPath();
153
+ mkdirSync(join2(homedir2(), ".deeplake"), { recursive: true, mode: 448 });
154
+ let fd = null;
155
+ for (let attempt = 0; attempt < LOCK_RETRY_MAX; attempt++) {
156
+ try {
157
+ fd = openSync(path, "wx", 384);
158
+ break;
159
+ } catch (e) {
160
+ const code = e.code;
161
+ if (code !== "EEXIST") throw e;
162
+ try {
163
+ const age = Date.now() - statSync(path).mtimeMs;
164
+ if (age > LOCK_STALE_MS) {
165
+ unlinkSync(path);
166
+ continue;
167
+ }
168
+ } catch {
169
+ }
170
+ const delay = LOCK_RETRY_BASE_MS * (attempt + 1);
171
+ await sleep(delay);
172
+ }
173
+ }
174
+ if (fd === null) {
175
+ log2(`lock acquisition gave up after ${LOCK_RETRY_MAX} attempts \u2014 proceeding unlocked (last-writer-wins)`);
176
+ return fn();
177
+ }
178
+ try {
179
+ return fn();
180
+ } finally {
181
+ try {
182
+ closeSync(fd);
183
+ } catch {
184
+ }
185
+ try {
186
+ unlinkSync(path);
187
+ } catch {
188
+ }
189
+ }
190
+ }
191
+ function sameDedupKey(a, b) {
192
+ if (a.id !== b.id) return false;
193
+ return JSON.stringify(a.dedupKey) === JSON.stringify(b.dedupKey);
194
+ }
195
+ async function enqueueNotification(n) {
196
+ await withQueueLock(() => {
197
+ const q = readQueue();
198
+ if (q.queue.some((existing) => sameDedupKey(existing, n))) {
199
+ return;
200
+ }
201
+ q.queue.push(n);
202
+ writeQueue(q);
203
+ });
204
+ }
205
+
107
206
  // src/deeplake-api.ts
108
207
  var indexMarkerStorePromise = null;
109
208
  function getIndexMarkerStore() {
110
209
  if (!indexMarkerStorePromise) indexMarkerStorePromise = import("./chunks/index-marker-store-CPGF2BI7.js");
111
210
  return indexMarkerStorePromise;
112
211
  }
113
- var log2 = (msg) => log("sdk", msg);
212
+ var log3 = (msg) => log("sdk", msg);
114
213
  function summarizeSql(sql, maxLen = 220) {
115
214
  const compact = sql.replace(/\s+/g, " ").trim();
116
215
  return compact.length > maxLen ? `${compact.slice(0, maxLen)}...` : compact;
@@ -120,7 +219,35 @@ function traceSql(msg) {
120
219
  if (!traceEnabled) return;
121
220
  process.stderr.write(`[deeplake-sql] ${msg}
122
221
  `);
123
- if (globalThis.__hivemind_tuning__.HIVEMIND_DEBUG === "1") log2(msg);
222
+ if (globalThis.__hivemind_tuning__.HIVEMIND_DEBUG === "1") log3(msg);
223
+ }
224
+ var _signalledBalanceExhausted = false;
225
+ function maybeSignalBalanceExhausted(status, bodyText) {
226
+ if (status !== 402) return;
227
+ if (!bodyText.includes("balance_cents")) return;
228
+ if (_signalledBalanceExhausted) return;
229
+ _signalledBalanceExhausted = true;
230
+ log3(`balance exhausted \u2014 enqueuing session-start banner (body=${bodyText.slice(0, 120)})`);
231
+ enqueueNotification({
232
+ id: "balance-exhausted",
233
+ severity: "warn",
234
+ transient: true,
235
+ title: "Hivemind credits exhausted \u2014 top up to keep capturing",
236
+ body: `Sessions are not being saved and memory recall is returning empty. Top up at ${billingUrl()} to restore capture and recall.`,
237
+ dedupKey: { reason: "balance-zero" }
238
+ }).catch((e) => {
239
+ log3(`enqueue balance-exhausted failed: ${e instanceof Error ? e.message : String(e)}`);
240
+ });
241
+ }
242
+ function billingUrl() {
243
+ try {
244
+ const c = loadCredentials();
245
+ if (c?.orgName && c?.workspaceId) {
246
+ return `https://deeplake.ai/${encodeURIComponent(c.orgName)}/workspace/${encodeURIComponent(c.workspaceId)}/billing`;
247
+ }
248
+ } catch {
249
+ }
250
+ return "https://deeplake.ai";
124
251
  }
125
252
  var RETRYABLE_CODES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
126
253
  var MAX_RETRIES = 3;
@@ -129,8 +256,8 @@ var MAX_CONCURRENCY = 5;
129
256
  function getQueryTimeoutMs() {
130
257
  return Number(globalThis.__hivemind_tuning__.HIVEMIND_QUERY_TIMEOUT_MS ?? 1e4);
131
258
  }
132
- function sleep(ms) {
133
- return new Promise((resolve) => setTimeout(resolve, ms));
259
+ function sleep2(ms) {
260
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
134
261
  }
135
262
  function isTimeoutError(error) {
136
263
  const name = error instanceof Error ? error.name.toLowerCase() : "";
@@ -160,7 +287,7 @@ var Semaphore = class {
160
287
  this.active++;
161
288
  return;
162
289
  }
163
- await new Promise((resolve) => this.waiting.push(resolve));
290
+ await new Promise((resolve2) => this.waiting.push(resolve2));
164
291
  }
165
292
  release() {
166
293
  this.active--;
@@ -231,8 +358,8 @@ var DeeplakeApi = class {
231
358
  lastError = e instanceof Error ? e : new Error(String(e));
232
359
  if (attempt < MAX_RETRIES) {
233
360
  const delay = BASE_DELAY_MS * Math.pow(2, attempt) + Math.random() * 200;
234
- log2(`query retry ${attempt + 1}/${MAX_RETRIES} (fetch error: ${lastError.message}) in ${delay.toFixed(0)}ms`);
235
- await sleep(delay);
361
+ log3(`query retry ${attempt + 1}/${MAX_RETRIES} (fetch error: ${lastError.message}) in ${delay.toFixed(0)}ms`);
362
+ await sleep2(delay);
236
363
  continue;
237
364
  }
238
365
  throw lastError;
@@ -249,10 +376,11 @@ var DeeplakeApi = class {
249
376
  const alreadyExists = resp.status === 500 && isDuplicateIndexError(text);
250
377
  if (!alreadyExists && attempt < MAX_RETRIES && (RETRYABLE_CODES.has(resp.status) || retryable403)) {
251
378
  const delay = BASE_DELAY_MS * Math.pow(2, attempt) + Math.random() * 200;
252
- log2(`query retry ${attempt + 1}/${MAX_RETRIES} (${resp.status}) in ${delay.toFixed(0)}ms`);
253
- await sleep(delay);
379
+ log3(`query retry ${attempt + 1}/${MAX_RETRIES} (${resp.status}) in ${delay.toFixed(0)}ms`);
380
+ await sleep2(delay);
254
381
  continue;
255
382
  }
383
+ maybeSignalBalanceExhausted(resp.status, text);
256
384
  throw new Error(`Query failed: ${resp.status}: ${text.slice(0, 200)}`);
257
385
  }
258
386
  throw lastError ?? new Error("Query failed: max retries exceeded");
@@ -272,7 +400,7 @@ var DeeplakeApi = class {
272
400
  const chunk = rows.slice(i, i + CONCURRENCY);
273
401
  await Promise.allSettled(chunk.map((r) => this.upsertRowSql(r)));
274
402
  }
275
- log2(`commit: ${rows.length} rows`);
403
+ log3(`commit: ${rows.length} rows`);
276
404
  }
277
405
  async upsertRowSql(row) {
278
406
  const ts = (/* @__PURE__ */ new Date()).toISOString();
@@ -333,7 +461,7 @@ var DeeplakeApi = class {
333
461
  markers.writeIndexMarker(markerPath);
334
462
  return;
335
463
  }
336
- log2(`index "${indexName}" skipped: ${e.message}`);
464
+ log3(`index "${indexName}" skipped: ${e.message}`);
337
465
  }
338
466
  }
339
467
  /**
@@ -418,13 +546,13 @@ var DeeplakeApi = class {
418
546
  };
419
547
  }
420
548
  if (attempt < MAX_RETRIES && RETRYABLE_CODES.has(resp.status)) {
421
- await sleep(BASE_DELAY_MS * Math.pow(2, attempt) + Math.random() * 200);
549
+ await sleep2(BASE_DELAY_MS * Math.pow(2, attempt) + Math.random() * 200);
422
550
  continue;
423
551
  }
424
552
  return { tables: [], cacheable: false };
425
553
  } catch {
426
554
  if (attempt < MAX_RETRIES) {
427
- await sleep(BASE_DELAY_MS * Math.pow(2, attempt));
555
+ await sleep2(BASE_DELAY_MS * Math.pow(2, attempt));
428
556
  continue;
429
557
  }
430
558
  return { tables: [], cacheable: false };
@@ -452,9 +580,9 @@ var DeeplakeApi = class {
452
580
  } catch (err) {
453
581
  lastErr = err;
454
582
  const msg = err instanceof Error ? err.message : String(err);
455
- log2(`CREATE TABLE "${label}" attempt ${attempt + 1}/${OUTER_BACKOFFS_MS.length + 1} failed: ${msg}`);
583
+ log3(`CREATE TABLE "${label}" attempt ${attempt + 1}/${OUTER_BACKOFFS_MS.length + 1} failed: ${msg}`);
456
584
  if (attempt < OUTER_BACKOFFS_MS.length) {
457
- await sleep(OUTER_BACKOFFS_MS[attempt]);
585
+ await sleep2(OUTER_BACKOFFS_MS[attempt]);
458
586
  }
459
587
  }
460
588
  }
@@ -465,12 +593,12 @@ var DeeplakeApi = class {
465
593
  const tbl = sqlIdent(name ?? this.tableName);
466
594
  const tables = await this.listTables();
467
595
  if (!tables.includes(tbl)) {
468
- log2(`table "${tbl}" not found, creating`);
596
+ log3(`table "${tbl}" not found, creating`);
469
597
  await this.createTableWithRetry(
470
598
  `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`,
471
599
  tbl
472
600
  );
473
- log2(`table "${tbl}" created`);
601
+ log3(`table "${tbl}" created`);
474
602
  if (!tables.includes(tbl)) this._tablesCache = [...tables, tbl];
475
603
  }
476
604
  await this.ensureEmbeddingColumn(tbl, SUMMARY_EMBEDDING_COL);
@@ -482,12 +610,12 @@ var DeeplakeApi = class {
482
610
  const safe = sqlIdent(name);
483
611
  const tables = await this.listTables();
484
612
  if (!tables.includes(safe)) {
485
- log2(`table "${safe}" not found, creating`);
613
+ log3(`table "${safe}" not found, creating`);
486
614
  await this.createTableWithRetry(
487
615
  `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`,
488
616
  safe
489
617
  );
490
- log2(`table "${safe}" created`);
618
+ log3(`table "${safe}" created`);
491
619
  if (!tables.includes(safe)) this._tablesCache = [...tables, safe];
492
620
  }
493
621
  await this.ensureEmbeddingColumn(safe, MESSAGE_EMBEDDING_COL);
@@ -509,12 +637,12 @@ var DeeplakeApi = class {
509
637
  const safe = sqlIdent(name);
510
638
  const tables = await this.listTables();
511
639
  if (!tables.includes(safe)) {
512
- log2(`table "${safe}" not found, creating`);
640
+ log3(`table "${safe}" not found, creating`);
513
641
  await this.createTableWithRetry(
514
642
  `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`,
515
643
  safe
516
644
  );
517
- log2(`table "${safe}" created`);
645
+ log3(`table "${safe}" created`);
518
646
  if (!tables.includes(safe)) this._tablesCache = [...tables, safe];
519
647
  }
520
648
  await this.ensureLookupIndex(safe, "project_key_name", `("project_key", "name")`);
@@ -1028,7 +1156,7 @@ async function readVirtualPathContent(api2, memoryTable2, sessionsTable2, virtua
1028
1156
  // openclaw/src/index.ts
1029
1157
  import { fileURLToPath } from "node:url";
1030
1158
  import { join as joinPath, dirname as dirnamePath } from "node:path";
1031
- import { homedir as homedir2, tmpdir } from "node:os";
1159
+ import { homedir as homedir3, tmpdir } from "node:os";
1032
1160
  import {
1033
1161
  existsSync as fsExists,
1034
1162
  mkdirSync as fsMkdir,
@@ -1111,7 +1239,7 @@ function extractLatestVersion(body) {
1111
1239
  return typeof v === "string" && v.length > 0 ? v : null;
1112
1240
  }
1113
1241
  function getInstalledVersion() {
1114
- return "0.7.32".length > 0 ? "0.7.32" : null;
1242
+ return "0.7.33".length > 0 ? "0.7.33" : null;
1115
1243
  }
1116
1244
  function isNewer(latest, current) {
1117
1245
  const parse = (v) => v.replace(/-.*$/, "").split(".").map(Number);
@@ -1218,8 +1346,8 @@ var skillifySpawnedFor = /* @__PURE__ */ new Set();
1218
1346
  var __openclaw_filename = fileURLToPath(import.meta.url);
1219
1347
  var __openclaw_dirname = dirnamePath(__openclaw_filename);
1220
1348
  var OPENCLAW_SKILLIFY_WORKER_PATH = joinPath(__openclaw_dirname, "skillify-worker.js");
1221
- var OPENCLAW_SKILLIFY_STATE_DIR = joinPath(homedir2(), ".deeplake", "state", "skillify");
1222
- var OPENCLAW_SKILLIFY_LEGACY_STATE_DIR = joinPath(homedir2(), ".deeplake", "state", "skilify");
1349
+ var OPENCLAW_SKILLIFY_STATE_DIR = joinPath(homedir3(), ".deeplake", "state", "skillify");
1350
+ var OPENCLAW_SKILLIFY_LEGACY_STATE_DIR = joinPath(homedir3(), ".deeplake", "state", "skilify");
1223
1351
  var openclawSkillifyMigrationAttempted = false;
1224
1352
  function migrateOpenclawSkillifyLegacyStateDir() {
1225
1353
  if (openclawSkillifyMigrationAttempted) return;
@@ -1244,9 +1372,9 @@ function tryAcquireOpenclawSkillifyLock(projectKey) {
1244
1372
  try {
1245
1373
  migrateOpenclawSkillifyLegacyStateDir();
1246
1374
  fsMkdir(OPENCLAW_SKILLIFY_STATE_DIR, { recursive: true });
1247
- const lockPath = joinPath(OPENCLAW_SKILLIFY_STATE_DIR, `${projectKey}.worker.lock`);
1375
+ const lockPath2 = joinPath(OPENCLAW_SKILLIFY_STATE_DIR, `${projectKey}.worker.lock`);
1248
1376
  const acquire = () => {
1249
- const fd = fsOpen(lockPath, fsConstants.O_CREAT | fsConstants.O_EXCL | fsConstants.O_WRONLY);
1377
+ const fd = fsOpen(lockPath2, fsConstants.O_CREAT | fsConstants.O_EXCL | fsConstants.O_WRONLY);
1250
1378
  try {
1251
1379
  fsWriteFile(fd, String(Date.now()));
1252
1380
  } finally {
@@ -1258,19 +1386,19 @@ function tryAcquireOpenclawSkillifyLock(projectKey) {
1258
1386
  return acquire();
1259
1387
  } catch {
1260
1388
  try {
1261
- const body = fsReadFile(lockPath, "utf-8");
1389
+ const body = fsReadFile(lockPath2, "utf-8");
1262
1390
  const ts = Number.parseInt(body.trim(), 10);
1263
1391
  const ageByBody = Number.isFinite(ts) ? Date.now() - ts : Number.POSITIVE_INFINITY;
1264
1392
  let ageByMtime = 0;
1265
1393
  try {
1266
- ageByMtime = Date.now() - fsStat(lockPath).mtimeMs;
1394
+ ageByMtime = Date.now() - fsStat(lockPath2).mtimeMs;
1267
1395
  } catch {
1268
1396
  ageByMtime = 0;
1269
1397
  }
1270
1398
  const effectiveAge = Number.isFinite(ts) ? ageByBody : ageByMtime;
1271
1399
  if (effectiveAge > LOCK_MAX_AGE_MS) {
1272
1400
  try {
1273
- fsUnlink(lockPath);
1401
+ fsUnlink(lockPath2);
1274
1402
  } catch {
1275
1403
  }
1276
1404
  try {
@@ -1335,7 +1463,7 @@ function spawnOpenclawSkillifyWorker(a) {
1335
1463
  sessionsTable,
1336
1464
  skillsTable,
1337
1465
  userName: a.userName,
1338
- cwd: homedir2(),
1466
+ cwd: homedir3(),
1339
1467
  // sentinel — only used by worker if install=project
1340
1468
  projectKey,
1341
1469
  project,
@@ -1351,7 +1479,7 @@ function spawnOpenclawSkillifyWorker(a) {
1351
1479
  cursorModel: void 0,
1352
1480
  hermesProvider: void 0,
1353
1481
  hermesModel: void 0,
1354
- skillifyLog: joinPath(homedir2(), ".deeplake", "hivemind-openclaw-skillify.log"),
1482
+ skillifyLog: joinPath(homedir3(), ".deeplake", "hivemind-openclaw-skillify.log"),
1355
1483
  currentSessionId: a.sessionId,
1356
1484
  // Pass the tuning dispatch through so the worker can repopulate its
1357
1485
  // own globalThis (each process has its own globalThis). The worker
@@ -52,5 +52,5 @@
52
52
  }
53
53
  }
54
54
  },
55
- "version": "0.7.32"
55
+ "version": "0.7.33"
56
56
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hivemind",
3
- "version": "0.7.32",
3
+ "version": "0.7.33",
4
4
  "type": "module",
5
5
  "description": "Hivemind — cloud-backed persistent shared memory for AI agents, powered by DeepLake",
6
6
  "license": "Apache-2.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deeplake/hivemind",
3
- "version": "0.7.32",
3
+ "version": "0.7.33",
4
4
  "description": "Cloud-backed persistent shared memory for AI agents powered by Deeplake",
5
5
  "type": "module",
6
6
  "repository": {