@deeplake/hivemind 0.6.48 → 0.7.9
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/README.md +244 -20
- package/bundle/cli.js +1369 -112
- package/codex/bundle/capture.js +546 -96
- package/codex/bundle/commands/auth-login.js +290 -81
- package/codex/bundle/embeddings/embed-daemon.js +243 -0
- package/codex/bundle/pre-tool-use.js +666 -111
- package/codex/bundle/session-start-setup.js +231 -64
- package/codex/bundle/session-start.js +52 -13
- package/codex/bundle/shell/deeplake-shell.js +716 -119
- package/codex/bundle/skilify-worker.js +907 -0
- package/codex/bundle/stop.js +819 -79
- package/codex/bundle/wiki-worker.js +312 -11
- package/cursor/bundle/capture.js +1116 -64
- package/cursor/bundle/commands/auth-login.js +290 -81
- package/cursor/bundle/embeddings/embed-daemon.js +243 -0
- package/cursor/bundle/pre-tool-use.js +598 -77
- package/cursor/bundle/session-end.js +520 -2
- package/cursor/bundle/session-start.js +257 -65
- package/cursor/bundle/shell/deeplake-shell.js +716 -119
- package/cursor/bundle/skilify-worker.js +907 -0
- package/cursor/bundle/wiki-worker.js +571 -0
- package/hermes/bundle/capture.js +1119 -65
- package/hermes/bundle/commands/auth-login.js +290 -81
- package/hermes/bundle/embeddings/embed-daemon.js +243 -0
- package/hermes/bundle/pre-tool-use.js +597 -76
- package/hermes/bundle/session-end.js +522 -1
- package/hermes/bundle/session-start.js +260 -65
- package/hermes/bundle/shell/deeplake-shell.js +716 -119
- package/hermes/bundle/skilify-worker.js +907 -0
- package/hermes/bundle/wiki-worker.js +572 -0
- package/mcp/bundle/server.js +290 -75
- package/openclaw/dist/chunks/auth-creds-AEKS6D3P.js +14 -0
- package/openclaw/dist/chunks/chunk-SRCBBT4H.js +37 -0
- package/openclaw/dist/chunks/config-ZLH6JFJS.js +34 -0
- package/openclaw/dist/chunks/index-marker-store-PGT5CW6T.js +33 -0
- package/openclaw/dist/chunks/setup-config-C35UK4LP.js +114 -0
- package/openclaw/dist/index.js +929 -710
- package/openclaw/dist/skilify-worker.js +907 -0
- package/openclaw/openclaw.plugin.json +1 -1
- package/openclaw/package.json +1 -1
- package/openclaw/skills/SKILL.md +19 -0
- package/package.json +7 -1
- package/pi/extension-source/hivemind.ts +603 -22
|
@@ -1,3 +1,56 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __esm = (fn, res) => function __init() {
|
|
4
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
|
+
};
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// dist/src/index-marker-store.js
|
|
12
|
+
var index_marker_store_exports = {};
|
|
13
|
+
__export(index_marker_store_exports, {
|
|
14
|
+
buildIndexMarkerPath: () => buildIndexMarkerPath,
|
|
15
|
+
getIndexMarkerDir: () => getIndexMarkerDir,
|
|
16
|
+
hasFreshIndexMarker: () => hasFreshIndexMarker,
|
|
17
|
+
writeIndexMarker: () => writeIndexMarker
|
|
18
|
+
});
|
|
19
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
|
|
20
|
+
import { join as join3 } from "node:path";
|
|
21
|
+
import { tmpdir } from "node:os";
|
|
22
|
+
function getIndexMarkerDir() {
|
|
23
|
+
return process.env.HIVEMIND_INDEX_MARKER_DIR ?? join3(tmpdir(), "hivemind-deeplake-indexes");
|
|
24
|
+
}
|
|
25
|
+
function buildIndexMarkerPath(workspaceId, orgId, table, suffix) {
|
|
26
|
+
const markerKey = [workspaceId, orgId, table, suffix].join("__").replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
27
|
+
return join3(getIndexMarkerDir(), `${markerKey}.json`);
|
|
28
|
+
}
|
|
29
|
+
function hasFreshIndexMarker(markerPath) {
|
|
30
|
+
if (!existsSync2(markerPath))
|
|
31
|
+
return false;
|
|
32
|
+
try {
|
|
33
|
+
const raw = JSON.parse(readFileSync2(markerPath, "utf-8"));
|
|
34
|
+
const updatedAt = raw.updatedAt ? new Date(raw.updatedAt).getTime() : NaN;
|
|
35
|
+
if (!Number.isFinite(updatedAt) || Date.now() - updatedAt > INDEX_MARKER_TTL_MS)
|
|
36
|
+
return false;
|
|
37
|
+
return true;
|
|
38
|
+
} catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function writeIndexMarker(markerPath) {
|
|
43
|
+
mkdirSync(getIndexMarkerDir(), { recursive: true });
|
|
44
|
+
writeFileSync(markerPath, JSON.stringify({ updatedAt: (/* @__PURE__ */ new Date()).toISOString() }), "utf-8");
|
|
45
|
+
}
|
|
46
|
+
var INDEX_MARKER_TTL_MS;
|
|
47
|
+
var init_index_marker_store = __esm({
|
|
48
|
+
"dist/src/index-marker-store.js"() {
|
|
49
|
+
"use strict";
|
|
50
|
+
INDEX_MARKER_TTL_MS = Number(process.env.HIVEMIND_INDEX_MARKER_TTL_MS ?? 6 * 60 * 6e4);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
1
54
|
// dist/src/utils/stdin.js
|
|
2
55
|
function readStdin() {
|
|
3
56
|
return new Promise((resolve, reject) => {
|
|
@@ -43,15 +96,13 @@ function loadConfig() {
|
|
|
43
96
|
apiUrl: process.env.HIVEMIND_API_URL ?? creds?.apiUrl ?? "https://api.deeplake.ai",
|
|
44
97
|
tableName: process.env.HIVEMIND_TABLE ?? "memory",
|
|
45
98
|
sessionsTableName: process.env.HIVEMIND_SESSIONS_TABLE ?? "sessions",
|
|
99
|
+
skillsTableName: process.env.HIVEMIND_SKILLS_TABLE ?? "skills",
|
|
46
100
|
memoryPath: process.env.HIVEMIND_MEMORY_PATH ?? join(home, ".deeplake", "memory")
|
|
47
101
|
};
|
|
48
102
|
}
|
|
49
103
|
|
|
50
104
|
// dist/src/deeplake-api.js
|
|
51
105
|
import { randomUUID } from "node:crypto";
|
|
52
|
-
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
|
|
53
|
-
import { join as join3 } from "node:path";
|
|
54
|
-
import { tmpdir } from "node:os";
|
|
55
106
|
|
|
56
107
|
// dist/src/utils/debug.js
|
|
57
108
|
import { appendFileSync } from "node:fs";
|
|
@@ -73,8 +124,33 @@ function sqlStr(value) {
|
|
|
73
124
|
function sqlLike(value) {
|
|
74
125
|
return sqlStr(value).replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
75
126
|
}
|
|
127
|
+
function sqlIdent(name) {
|
|
128
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
|
|
129
|
+
throw new Error(`Invalid SQL identifier: ${JSON.stringify(name)}`);
|
|
130
|
+
}
|
|
131
|
+
return name;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// dist/src/embeddings/columns.js
|
|
135
|
+
var SUMMARY_EMBEDDING_COL = "summary_embedding";
|
|
136
|
+
var MESSAGE_EMBEDDING_COL = "message_embedding";
|
|
137
|
+
|
|
138
|
+
// dist/src/utils/client-header.js
|
|
139
|
+
var DEEPLAKE_CLIENT_HEADER = "X-Deeplake-Client";
|
|
140
|
+
function deeplakeClientValue() {
|
|
141
|
+
return "hivemind";
|
|
142
|
+
}
|
|
143
|
+
function deeplakeClientHeader() {
|
|
144
|
+
return { [DEEPLAKE_CLIENT_HEADER]: deeplakeClientValue() };
|
|
145
|
+
}
|
|
76
146
|
|
|
77
147
|
// dist/src/deeplake-api.js
|
|
148
|
+
var indexMarkerStorePromise = null;
|
|
149
|
+
function getIndexMarkerStore() {
|
|
150
|
+
if (!indexMarkerStorePromise)
|
|
151
|
+
indexMarkerStorePromise = Promise.resolve().then(() => (init_index_marker_store(), index_marker_store_exports));
|
|
152
|
+
return indexMarkerStorePromise;
|
|
153
|
+
}
|
|
78
154
|
var log2 = (msg) => log("sdk", msg);
|
|
79
155
|
function summarizeSql(sql, maxLen = 220) {
|
|
80
156
|
const compact = sql.replace(/\s+/g, " ").trim();
|
|
@@ -94,7 +170,6 @@ var MAX_RETRIES = 3;
|
|
|
94
170
|
var BASE_DELAY_MS = 500;
|
|
95
171
|
var MAX_CONCURRENCY = 5;
|
|
96
172
|
var QUERY_TIMEOUT_MS = Number(process.env.HIVEMIND_QUERY_TIMEOUT_MS ?? 1e4);
|
|
97
|
-
var INDEX_MARKER_TTL_MS = Number(process.env.HIVEMIND_INDEX_MARKER_TTL_MS ?? 6 * 60 * 6e4);
|
|
98
173
|
function sleep(ms) {
|
|
99
174
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
100
175
|
}
|
|
@@ -114,9 +189,6 @@ function isTransientHtml403(text) {
|
|
|
114
189
|
const body = text.toLowerCase();
|
|
115
190
|
return body.includes("<html") || body.includes("403 forbidden") || body.includes("cloudflare") || body.includes("nginx");
|
|
116
191
|
}
|
|
117
|
-
function getIndexMarkerDir() {
|
|
118
|
-
return process.env.HIVEMIND_INDEX_MARKER_DIR ?? join3(tmpdir(), "hivemind-deeplake-indexes");
|
|
119
|
-
}
|
|
120
192
|
var Semaphore = class {
|
|
121
193
|
max;
|
|
122
194
|
waiting = [];
|
|
@@ -185,7 +257,8 @@ var DeeplakeApi = class {
|
|
|
185
257
|
headers: {
|
|
186
258
|
Authorization: `Bearer ${this.token}`,
|
|
187
259
|
"Content-Type": "application/json",
|
|
188
|
-
"X-Activeloop-Org-Id": this.orgId
|
|
260
|
+
"X-Activeloop-Org-Id": this.orgId,
|
|
261
|
+
...deeplakeClientHeader()
|
|
189
262
|
},
|
|
190
263
|
signal,
|
|
191
264
|
body: JSON.stringify({ query: sql })
|
|
@@ -212,7 +285,8 @@ var DeeplakeApi = class {
|
|
|
212
285
|
}
|
|
213
286
|
const text = await resp.text().catch(() => "");
|
|
214
287
|
const retryable403 = isSessionInsertQuery(sql) && (resp.status === 401 || resp.status === 403 && (text.length === 0 || isTransientHtml403(text)));
|
|
215
|
-
|
|
288
|
+
const alreadyExists = resp.status === 500 && isDuplicateIndexError(text);
|
|
289
|
+
if (!alreadyExists && attempt < MAX_RETRIES && (RETRYABLE_CODES.has(resp.status) || retryable403)) {
|
|
216
290
|
const delay = BASE_DELAY_MS * Math.pow(2, attempt) + Math.random() * 200;
|
|
217
291
|
log2(`query retry ${attempt + 1}/${MAX_RETRIES} (${resp.status}) in ${delay.toFixed(0)}ms`);
|
|
218
292
|
await sleep(delay);
|
|
@@ -246,7 +320,7 @@ var DeeplakeApi = class {
|
|
|
246
320
|
const lud = row.lastUpdateDate ?? ts;
|
|
247
321
|
const exists = await this.query(`SELECT path FROM "${this.tableName}" WHERE path = '${sqlStr(row.path)}' LIMIT 1`);
|
|
248
322
|
if (exists.length > 0) {
|
|
249
|
-
let setClauses = `summary = E'${sqlStr(row.contentText)}', mime_type = '${sqlStr(row.mimeType)}', size_bytes = ${row.sizeBytes}, last_update_date = '${lud}'`;
|
|
323
|
+
let setClauses = `summary = E'${sqlStr(row.contentText)}', ${SUMMARY_EMBEDDING_COL} = NULL, mime_type = '${sqlStr(row.mimeType)}', size_bytes = ${row.sizeBytes}, last_update_date = '${lud}'`;
|
|
250
324
|
if (row.project !== void 0)
|
|
251
325
|
setClauses += `, project = '${sqlStr(row.project)}'`;
|
|
252
326
|
if (row.description !== void 0)
|
|
@@ -254,8 +328,8 @@ var DeeplakeApi = class {
|
|
|
254
328
|
await this.query(`UPDATE "${this.tableName}" SET ${setClauses} WHERE path = '${sqlStr(row.path)}'`);
|
|
255
329
|
} else {
|
|
256
330
|
const id = randomUUID();
|
|
257
|
-
let cols =
|
|
258
|
-
let vals = `'${id}', '${sqlStr(row.path)}', '${sqlStr(row.filename)}', E'${sqlStr(row.contentText)}', '${sqlStr(row.mimeType)}', ${row.sizeBytes}, '${cd}', '${lud}'`;
|
|
331
|
+
let cols = `id, path, filename, summary, ${SUMMARY_EMBEDDING_COL}, mime_type, size_bytes, creation_date, last_update_date`;
|
|
332
|
+
let vals = `'${id}', '${sqlStr(row.path)}', '${sqlStr(row.filename)}', E'${sqlStr(row.contentText)}', NULL, '${sqlStr(row.mimeType)}', ${row.sizeBytes}, '${cd}', '${lud}'`;
|
|
259
333
|
if (row.project !== void 0) {
|
|
260
334
|
cols += ", project";
|
|
261
335
|
vals += `, '${sqlStr(row.project)}'`;
|
|
@@ -280,48 +354,83 @@ var DeeplakeApi = class {
|
|
|
280
354
|
buildLookupIndexName(table, suffix) {
|
|
281
355
|
return `idx_${table}_${suffix}`.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
282
356
|
}
|
|
283
|
-
getLookupIndexMarkerPath(table, suffix) {
|
|
284
|
-
const markerKey = [
|
|
285
|
-
this.workspaceId,
|
|
286
|
-
this.orgId,
|
|
287
|
-
table,
|
|
288
|
-
suffix
|
|
289
|
-
].join("__").replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
290
|
-
return join3(getIndexMarkerDir(), `${markerKey}.json`);
|
|
291
|
-
}
|
|
292
|
-
hasFreshLookupIndexMarker(table, suffix) {
|
|
293
|
-
const markerPath = this.getLookupIndexMarkerPath(table, suffix);
|
|
294
|
-
if (!existsSync2(markerPath))
|
|
295
|
-
return false;
|
|
296
|
-
try {
|
|
297
|
-
const raw = JSON.parse(readFileSync2(markerPath, "utf-8"));
|
|
298
|
-
const updatedAt = raw.updatedAt ? new Date(raw.updatedAt).getTime() : NaN;
|
|
299
|
-
if (!Number.isFinite(updatedAt) || Date.now() - updatedAt > INDEX_MARKER_TTL_MS)
|
|
300
|
-
return false;
|
|
301
|
-
return true;
|
|
302
|
-
} catch {
|
|
303
|
-
return false;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
markLookupIndexReady(table, suffix) {
|
|
307
|
-
mkdirSync(getIndexMarkerDir(), { recursive: true });
|
|
308
|
-
writeFileSync(this.getLookupIndexMarkerPath(table, suffix), JSON.stringify({ updatedAt: (/* @__PURE__ */ new Date()).toISOString() }), "utf-8");
|
|
309
|
-
}
|
|
310
357
|
async ensureLookupIndex(table, suffix, columnsSql) {
|
|
311
|
-
|
|
358
|
+
const markers = await getIndexMarkerStore();
|
|
359
|
+
const markerPath = markers.buildIndexMarkerPath(this.workspaceId, this.orgId, table, suffix);
|
|
360
|
+
if (markers.hasFreshIndexMarker(markerPath))
|
|
312
361
|
return;
|
|
313
362
|
const indexName = this.buildLookupIndexName(table, suffix);
|
|
314
363
|
try {
|
|
315
364
|
await this.query(`CREATE INDEX IF NOT EXISTS "${indexName}" ON "${table}" ${columnsSql}`);
|
|
316
|
-
|
|
365
|
+
markers.writeIndexMarker(markerPath);
|
|
317
366
|
} catch (e) {
|
|
318
367
|
if (isDuplicateIndexError(e)) {
|
|
319
|
-
|
|
368
|
+
markers.writeIndexMarker(markerPath);
|
|
320
369
|
return;
|
|
321
370
|
}
|
|
322
371
|
log2(`index "${indexName}" skipped: ${e.message}`);
|
|
323
372
|
}
|
|
324
373
|
}
|
|
374
|
+
/**
|
|
375
|
+
* Ensure a vector column exists on the given table.
|
|
376
|
+
*
|
|
377
|
+
* The previous implementation always issued `ALTER TABLE ADD COLUMN IF NOT
|
|
378
|
+
* EXISTS …` on every SessionStart. On a long-running workspace that's
|
|
379
|
+
* already migrated, every call returns 500 "Column already exists" — noisy
|
|
380
|
+
* in the log and a wasted round-trip. Worse, the very first call after the
|
|
381
|
+
* column is genuinely added triggers Deeplake's post-ALTER `vector::at`
|
|
382
|
+
* window (~30s) during which subsequent INSERTs fail; minimising the
|
|
383
|
+
* number of ALTER calls minimises exposure to that window.
|
|
384
|
+
*
|
|
385
|
+
* New flow:
|
|
386
|
+
* 1. Check the local marker file (mirrors ensureLookupIndex). If fresh,
|
|
387
|
+
* return — zero network calls.
|
|
388
|
+
* 2. SELECT 1 FROM information_schema.columns WHERE table_name = T AND
|
|
389
|
+
* column_name = C. Read-only, idempotent, can't tickle the post-ALTER
|
|
390
|
+
* bug. If the column is present → mark + return.
|
|
391
|
+
* 3. Only if step 2 says the column is missing, fall back to ALTER ADD
|
|
392
|
+
* COLUMN IF NOT EXISTS. Mark on success, also mark if Deeplake reports
|
|
393
|
+
* "already exists" (race: another client added it between our SELECT
|
|
394
|
+
* and ALTER).
|
|
395
|
+
*
|
|
396
|
+
* Marker uses the same dir / TTL as ensureLookupIndex so both schema
|
|
397
|
+
* caches share an opt-out (HIVEMIND_INDEX_MARKER_DIR) and a TTL knob.
|
|
398
|
+
*/
|
|
399
|
+
async ensureEmbeddingColumn(table, column) {
|
|
400
|
+
await this.ensureColumn(table, column, "FLOAT4[]");
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Generic marker-gated column migration. Same SELECT-then-ALTER flow as
|
|
404
|
+
* ensureEmbeddingColumn, parameterized by SQL type so it can patch up any
|
|
405
|
+
* column that was added to the schema after the table was originally
|
|
406
|
+
* created. Used today for `summary_embedding`, `message_embedding`, and
|
|
407
|
+
* the `agent` column (added 2026-04-11) — the latter has no fallback if
|
|
408
|
+
* a user upgraded over a pre-2026-04-11 table, so every INSERT fails
|
|
409
|
+
* with `column "agent" does not exist`.
|
|
410
|
+
*/
|
|
411
|
+
async ensureColumn(table, column, sqlType) {
|
|
412
|
+
const markers = await getIndexMarkerStore();
|
|
413
|
+
const markerPath = markers.buildIndexMarkerPath(this.workspaceId, this.orgId, table, `col_${column}`);
|
|
414
|
+
if (markers.hasFreshIndexMarker(markerPath))
|
|
415
|
+
return;
|
|
416
|
+
const colCheck = `SELECT 1 FROM information_schema.columns WHERE table_name = '${sqlStr(table)}' AND column_name = '${sqlStr(column)}' AND table_schema = '${sqlStr(this.workspaceId)}' LIMIT 1`;
|
|
417
|
+
const rows = await this.query(colCheck);
|
|
418
|
+
if (rows.length > 0) {
|
|
419
|
+
markers.writeIndexMarker(markerPath);
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
try {
|
|
423
|
+
await this.query(`ALTER TABLE "${table}" ADD COLUMN ${column} ${sqlType}`);
|
|
424
|
+
} catch (e) {
|
|
425
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
426
|
+
if (!/already exists/i.test(msg))
|
|
427
|
+
throw e;
|
|
428
|
+
const recheck = await this.query(colCheck);
|
|
429
|
+
if (recheck.length === 0)
|
|
430
|
+
throw e;
|
|
431
|
+
}
|
|
432
|
+
markers.writeIndexMarker(markerPath);
|
|
433
|
+
}
|
|
325
434
|
/** List all tables in the workspace (with retry). */
|
|
326
435
|
async listTables(forceRefresh = false) {
|
|
327
436
|
if (!forceRefresh && this._tablesCache)
|
|
@@ -337,7 +446,8 @@ var DeeplakeApi = class {
|
|
|
337
446
|
const resp = await fetch(`${this.apiUrl}/workspaces/${this.workspaceId}/tables`, {
|
|
338
447
|
headers: {
|
|
339
448
|
Authorization: `Bearer ${this.token}`,
|
|
340
|
-
"X-Activeloop-Org-Id": this.orgId
|
|
449
|
+
"X-Activeloop-Org-Id": this.orgId,
|
|
450
|
+
...deeplakeClientHeader()
|
|
341
451
|
}
|
|
342
452
|
});
|
|
343
453
|
if (resp.ok) {
|
|
@@ -362,29 +472,84 @@ var DeeplakeApi = class {
|
|
|
362
472
|
}
|
|
363
473
|
return { tables: [], cacheable: false };
|
|
364
474
|
}
|
|
475
|
+
/**
|
|
476
|
+
* Run a `CREATE TABLE` with an extra outer retry budget. The base
|
|
477
|
+
* `query()` already retries 3 times on fetch errors (~3.5s total), but a
|
|
478
|
+
* failed CREATE is permanent corruption — every subsequent SELECT against
|
|
479
|
+
* the missing table fails. Wrapping in an outer loop with longer backoff
|
|
480
|
+
* (2s, 5s, then 10s) gives us ~17s of reach across transient network
|
|
481
|
+
* blips before giving up. Failures still propagate; getApi() resets its
|
|
482
|
+
* cache on init failure (openclaw plugin) so the next call retries the
|
|
483
|
+
* whole init flow.
|
|
484
|
+
*/
|
|
485
|
+
async createTableWithRetry(sql, label) {
|
|
486
|
+
const OUTER_BACKOFFS_MS = [2e3, 5e3, 1e4];
|
|
487
|
+
let lastErr = null;
|
|
488
|
+
for (let attempt = 0; attempt <= OUTER_BACKOFFS_MS.length; attempt++) {
|
|
489
|
+
try {
|
|
490
|
+
await this.query(sql);
|
|
491
|
+
return;
|
|
492
|
+
} catch (err) {
|
|
493
|
+
lastErr = err;
|
|
494
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
495
|
+
log2(`CREATE TABLE "${label}" attempt ${attempt + 1}/${OUTER_BACKOFFS_MS.length + 1} failed: ${msg}`);
|
|
496
|
+
if (attempt < OUTER_BACKOFFS_MS.length) {
|
|
497
|
+
await sleep(OUTER_BACKOFFS_MS[attempt]);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
throw lastErr;
|
|
502
|
+
}
|
|
365
503
|
/** Create the memory table if it doesn't already exist. Migrate columns on existing tables. */
|
|
366
504
|
async ensureTable(name) {
|
|
367
|
-
const tbl = name ?? this.tableName;
|
|
505
|
+
const tbl = sqlIdent(name ?? this.tableName);
|
|
368
506
|
const tables = await this.listTables();
|
|
369
507
|
if (!tables.includes(tbl)) {
|
|
370
508
|
log2(`table "${tbl}" not found, creating`);
|
|
371
|
-
await this.
|
|
509
|
+
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 '', creation_date TEXT NOT NULL DEFAULT '', last_update_date TEXT NOT NULL DEFAULT '') USING deeplake`, tbl);
|
|
372
510
|
log2(`table "${tbl}" created`);
|
|
373
511
|
if (!tables.includes(tbl))
|
|
374
512
|
this._tablesCache = [...tables, tbl];
|
|
375
513
|
}
|
|
514
|
+
await this.ensureEmbeddingColumn(tbl, SUMMARY_EMBEDDING_COL);
|
|
515
|
+
await this.ensureColumn(tbl, "agent", "TEXT NOT NULL DEFAULT ''");
|
|
376
516
|
}
|
|
377
517
|
/** Create the sessions table (uses JSONB for message since every row is a JSON event). */
|
|
378
518
|
async ensureSessionsTable(name) {
|
|
519
|
+
const safe = sqlIdent(name);
|
|
520
|
+
const tables = await this.listTables();
|
|
521
|
+
if (!tables.includes(safe)) {
|
|
522
|
+
log2(`table "${safe}" not found, creating`);
|
|
523
|
+
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 '', creation_date TEXT NOT NULL DEFAULT '', last_update_date TEXT NOT NULL DEFAULT '') USING deeplake`, safe);
|
|
524
|
+
log2(`table "${safe}" created`);
|
|
525
|
+
if (!tables.includes(safe))
|
|
526
|
+
this._tablesCache = [...tables, safe];
|
|
527
|
+
}
|
|
528
|
+
await this.ensureEmbeddingColumn(safe, MESSAGE_EMBEDDING_COL);
|
|
529
|
+
await this.ensureColumn(safe, "agent", "TEXT NOT NULL DEFAULT ''");
|
|
530
|
+
await this.ensureLookupIndex(safe, "path_creation_date", `("path", "creation_date")`);
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Create the skills table.
|
|
534
|
+
*
|
|
535
|
+
* One row per skill version. Workers INSERT a fresh row on every KEEP /
|
|
536
|
+
* MERGE rather than UPDATE-ing in place, so the full version history is
|
|
537
|
+
* recoverable. Uniqueness in the *current* state is by (project_key, name)
|
|
538
|
+
* — newer rows shadow older ones at read time (ORDER BY version DESC).
|
|
539
|
+
* This sidesteps the Deeplake UPDATE-coalescing quirk that bit the wiki
|
|
540
|
+
* worker.
|
|
541
|
+
*/
|
|
542
|
+
async ensureSkillsTable(name) {
|
|
543
|
+
const safe = sqlIdent(name);
|
|
379
544
|
const tables = await this.listTables();
|
|
380
|
-
if (!tables.includes(
|
|
381
|
-
log2(`table "${
|
|
382
|
-
await this.
|
|
383
|
-
log2(`table "${
|
|
384
|
-
if (!tables.includes(
|
|
385
|
-
this._tablesCache = [...tables,
|
|
545
|
+
if (!tables.includes(safe)) {
|
|
546
|
+
log2(`table "${safe}" not found, creating`);
|
|
547
|
+
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);
|
|
548
|
+
log2(`table "${safe}" created`);
|
|
549
|
+
if (!tables.includes(safe))
|
|
550
|
+
this._tablesCache = [...tables, safe];
|
|
386
551
|
}
|
|
387
|
-
await this.ensureLookupIndex(
|
|
552
|
+
await this.ensureLookupIndex(safe, "project_key_name", `("project_key", "name")`);
|
|
388
553
|
}
|
|
389
554
|
};
|
|
390
555
|
|
|
@@ -552,24 +717,25 @@ function normalizeContent(path, raw) {
|
|
|
552
717
|
return raw;
|
|
553
718
|
}
|
|
554
719
|
if (Array.isArray(obj.turns)) {
|
|
555
|
-
const
|
|
556
|
-
if (obj.date_time)
|
|
557
|
-
header.push(`date: ${obj.date_time}`);
|
|
558
|
-
if (obj.speakers) {
|
|
559
|
-
const s = obj.speakers;
|
|
560
|
-
const names = [s.speaker_a, s.speaker_b].filter(Boolean).join(", ");
|
|
561
|
-
if (names)
|
|
562
|
-
header.push(`speakers: ${names}`);
|
|
563
|
-
}
|
|
720
|
+
const dateHeader = obj.date_time ? `(${String(obj.date_time)}) ` : "";
|
|
564
721
|
const lines = obj.turns.map((t) => {
|
|
565
722
|
const sp = String(t?.speaker ?? t?.name ?? "?").trim();
|
|
566
723
|
const tx = String(t?.text ?? t?.content ?? "").replace(/\s+/g, " ").trim();
|
|
567
724
|
const tag = t?.dia_id ? `[${t.dia_id}] ` : "";
|
|
568
|
-
return `${tag}${sp}: ${tx}`;
|
|
725
|
+
return `${dateHeader}${tag}${sp}: ${tx}`;
|
|
569
726
|
});
|
|
570
|
-
const out2 =
|
|
727
|
+
const out2 = lines.join("\n");
|
|
571
728
|
return out2.trim() ? out2 : raw;
|
|
572
729
|
}
|
|
730
|
+
if (obj.turn && typeof obj.turn === "object" && !Array.isArray(obj.turn)) {
|
|
731
|
+
const t = obj.turn;
|
|
732
|
+
const sp = String(t.speaker ?? t.name ?? "?").trim();
|
|
733
|
+
const tx = String(t.text ?? t.content ?? "").replace(/\s+/g, " ").trim();
|
|
734
|
+
const tag = t.dia_id ? `[${String(t.dia_id)}] ` : "";
|
|
735
|
+
const dateHeader = obj.date_time ? `(${String(obj.date_time)}) ` : "";
|
|
736
|
+
const line = `${dateHeader}${tag}${sp}: ${tx}`;
|
|
737
|
+
return line.trim() ? line : raw;
|
|
738
|
+
}
|
|
573
739
|
const stripRecalled = (t) => {
|
|
574
740
|
const i = t.indexOf("<recalled-memories>");
|
|
575
741
|
if (i === -1)
|
|
@@ -612,8 +778,38 @@ function buildPathCondition(targetPath) {
|
|
|
612
778
|
return `(path = '${sqlStr(clean)}' OR path LIKE '${sqlLike(clean)}/%' ESCAPE '\\')`;
|
|
613
779
|
}
|
|
614
780
|
async function searchDeeplakeTables(api, memoryTable, sessionsTable, opts) {
|
|
615
|
-
const { pathFilter, contentScanOnly, likeOp, escapedPattern, prefilterPattern, prefilterPatterns, multiWordPatterns } = opts;
|
|
781
|
+
const { pathFilter, contentScanOnly, likeOp, escapedPattern, prefilterPattern, prefilterPatterns, queryEmbedding, multiWordPatterns } = opts;
|
|
616
782
|
const limit = opts.limit ?? 100;
|
|
783
|
+
if (queryEmbedding && queryEmbedding.length > 0) {
|
|
784
|
+
const vecLit = serializeFloat4Array(queryEmbedding);
|
|
785
|
+
const semanticLimit = Math.min(limit, Number(process.env.HIVEMIND_SEMANTIC_LIMIT ?? "20"));
|
|
786
|
+
const lexicalLimit = Math.min(limit, Number(process.env.HIVEMIND_HYBRID_LEXICAL_LIMIT ?? "20"));
|
|
787
|
+
const filterPatternsForLex = contentScanOnly ? prefilterPatterns && prefilterPatterns.length > 0 ? prefilterPatterns : prefilterPattern ? [prefilterPattern] : [] : [escapedPattern];
|
|
788
|
+
const memLexFilter = buildContentFilter("summary::text", likeOp, filterPatternsForLex);
|
|
789
|
+
const sessLexFilter = buildContentFilter("message::text", likeOp, filterPatternsForLex);
|
|
790
|
+
const memLexQuery = memLexFilter ? `SELECT path, summary::text AS content, 0 AS source_order, '' AS creation_date, 1.0 AS score FROM "${memoryTable}" WHERE 1=1${pathFilter}${memLexFilter} LIMIT ${lexicalLimit}` : null;
|
|
791
|
+
const sessLexQuery = sessLexFilter ? `SELECT path, message::text AS content, 1 AS source_order, COALESCE(creation_date::text, '') AS creation_date, 1.0 AS score FROM "${sessionsTable}" WHERE 1=1${pathFilter}${sessLexFilter} LIMIT ${lexicalLimit}` : null;
|
|
792
|
+
const memSemQuery = `SELECT path, summary::text AS content, 0 AS source_order, '' AS creation_date, (summary_embedding <#> ${vecLit}) AS score FROM "${memoryTable}" WHERE ARRAY_LENGTH(summary_embedding, 1) > 0${pathFilter} ORDER BY score DESC LIMIT ${semanticLimit}`;
|
|
793
|
+
const sessSemQuery = `SELECT path, message::text AS content, 1 AS source_order, COALESCE(creation_date::text, '') AS creation_date, (message_embedding <#> ${vecLit}) AS score FROM "${sessionsTable}" WHERE ARRAY_LENGTH(message_embedding, 1) > 0${pathFilter} ORDER BY score DESC LIMIT ${semanticLimit}`;
|
|
794
|
+
const parts = [memSemQuery, sessSemQuery];
|
|
795
|
+
if (memLexQuery)
|
|
796
|
+
parts.push(memLexQuery);
|
|
797
|
+
if (sessLexQuery)
|
|
798
|
+
parts.push(sessLexQuery);
|
|
799
|
+
const unionSql = parts.map((q) => `(${q})`).join(" UNION ALL ");
|
|
800
|
+
const outerLimit = semanticLimit + lexicalLimit;
|
|
801
|
+
const rows2 = await api.query(`SELECT path, content, source_order, creation_date, score FROM (` + unionSql + `) AS combined ORDER BY score DESC LIMIT ${outerLimit}`);
|
|
802
|
+
const seen = /* @__PURE__ */ new Set();
|
|
803
|
+
const unique = [];
|
|
804
|
+
for (const row of rows2) {
|
|
805
|
+
const p = String(row["path"]);
|
|
806
|
+
if (seen.has(p))
|
|
807
|
+
continue;
|
|
808
|
+
seen.add(p);
|
|
809
|
+
unique.push({ path: p, content: String(row["content"] ?? "") });
|
|
810
|
+
}
|
|
811
|
+
return unique;
|
|
812
|
+
}
|
|
617
813
|
const filterPatterns = contentScanOnly ? prefilterPatterns && prefilterPatterns.length > 0 ? prefilterPatterns : prefilterPattern ? [prefilterPattern] : [] : multiWordPatterns && multiWordPatterns.length > 1 ? multiWordPatterns : [escapedPattern];
|
|
618
814
|
const memFilter = buildContentFilter("summary::text", likeOp, filterPatterns);
|
|
619
815
|
const sessFilter = buildContentFilter("message::text", likeOp, filterPatterns);
|
|
@@ -625,6 +821,15 @@ async function searchDeeplakeTables(api, memoryTable, sessionsTable, opts) {
|
|
|
625
821
|
content: String(row["content"] ?? "")
|
|
626
822
|
}));
|
|
627
823
|
}
|
|
824
|
+
function serializeFloat4Array(vec) {
|
|
825
|
+
const parts = [];
|
|
826
|
+
for (const v of vec) {
|
|
827
|
+
if (!Number.isFinite(v))
|
|
828
|
+
return "NULL";
|
|
829
|
+
parts.push(String(v));
|
|
830
|
+
}
|
|
831
|
+
return `ARRAY[${parts.join(",")}]::float4[]`;
|
|
832
|
+
}
|
|
628
833
|
function buildPathFilter(targetPath) {
|
|
629
834
|
const condition = buildPathCondition(targetPath);
|
|
630
835
|
return condition ? ` AND ${condition}` : "";
|
|
@@ -707,7 +912,7 @@ function buildGrepSearchOptions(params, targetPath) {
|
|
|
707
912
|
return {
|
|
708
913
|
pathFilter: buildPathFilter(targetPath),
|
|
709
914
|
contentScanOnly: hasRegexMeta,
|
|
710
|
-
likeOp:
|
|
915
|
+
likeOp: process.env.HIVEMIND_GREP_LIKE === "case-sensitive" ? "LIKE" : "ILIKE",
|
|
711
916
|
escapedPattern: sqlLike(params.pattern),
|
|
712
917
|
prefilterPattern: literalPrefilter ? sqlLike(literalPrefilter) : void 0,
|
|
713
918
|
prefilterPatterns: alternationPrefilters?.map((literal) => sqlLike(literal)),
|
|
@@ -762,11 +967,28 @@ function refineGrepMatches(rows, params, forceMultiFilePrefix) {
|
|
|
762
967
|
}
|
|
763
968
|
return output;
|
|
764
969
|
}
|
|
765
|
-
async function grepBothTables(api, memoryTable, sessionsTable, params, targetPath) {
|
|
766
|
-
const rows = await searchDeeplakeTables(api, memoryTable, sessionsTable,
|
|
970
|
+
async function grepBothTables(api, memoryTable, sessionsTable, params, targetPath, queryEmbedding) {
|
|
971
|
+
const rows = await searchDeeplakeTables(api, memoryTable, sessionsTable, {
|
|
972
|
+
...buildGrepSearchOptions(params, targetPath),
|
|
973
|
+
queryEmbedding
|
|
974
|
+
});
|
|
767
975
|
const seen = /* @__PURE__ */ new Set();
|
|
768
976
|
const unique = rows.filter((r) => seen.has(r.path) ? false : (seen.add(r.path), true));
|
|
769
977
|
const normalized = unique.map((r) => ({ path: r.path, content: normalizeContent(r.path, r.content) }));
|
|
978
|
+
if (queryEmbedding && queryEmbedding.length > 0) {
|
|
979
|
+
const emitAllLines = process.env.HIVEMIND_SEMANTIC_EMIT_ALL !== "false";
|
|
980
|
+
if (emitAllLines) {
|
|
981
|
+
const lines = [];
|
|
982
|
+
for (const r of normalized) {
|
|
983
|
+
for (const line of r.content.split("\n")) {
|
|
984
|
+
const trimmed = line.trim();
|
|
985
|
+
if (trimmed)
|
|
986
|
+
lines.push(`${r.path}:${line}`);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
return lines;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
770
992
|
return refineGrepMatches(normalized, params);
|
|
771
993
|
}
|
|
772
994
|
|
|
@@ -810,7 +1032,298 @@ function capOutputForClaude(output, options = {}) {
|
|
|
810
1032
|
return keptLines.join("\n") + footer;
|
|
811
1033
|
}
|
|
812
1034
|
|
|
1035
|
+
// dist/src/embeddings/client.js
|
|
1036
|
+
import { connect } from "node:net";
|
|
1037
|
+
import { spawn } from "node:child_process";
|
|
1038
|
+
import { openSync, closeSync, writeSync, unlinkSync, existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
|
|
1039
|
+
import { homedir as homedir3 } from "node:os";
|
|
1040
|
+
import { join as join4 } from "node:path";
|
|
1041
|
+
|
|
1042
|
+
// dist/src/embeddings/protocol.js
|
|
1043
|
+
var DEFAULT_SOCKET_DIR = "/tmp";
|
|
1044
|
+
var DEFAULT_IDLE_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
1045
|
+
var DEFAULT_CLIENT_TIMEOUT_MS = 2e3;
|
|
1046
|
+
function socketPathFor(uid, dir = DEFAULT_SOCKET_DIR) {
|
|
1047
|
+
return `${dir}/hivemind-embed-${uid}.sock`;
|
|
1048
|
+
}
|
|
1049
|
+
function pidPathFor(uid, dir = DEFAULT_SOCKET_DIR) {
|
|
1050
|
+
return `${dir}/hivemind-embed-${uid}.pid`;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
// dist/src/embeddings/client.js
|
|
1054
|
+
var SHARED_DAEMON_PATH = join4(homedir3(), ".hivemind", "embed-deps", "embed-daemon.js");
|
|
1055
|
+
var log3 = (m) => log("embed-client", m);
|
|
1056
|
+
function getUid() {
|
|
1057
|
+
const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
|
|
1058
|
+
return uid !== void 0 ? String(uid) : process.env.USER ?? "default";
|
|
1059
|
+
}
|
|
1060
|
+
var EmbedClient = class {
|
|
1061
|
+
socketPath;
|
|
1062
|
+
pidPath;
|
|
1063
|
+
timeoutMs;
|
|
1064
|
+
daemonEntry;
|
|
1065
|
+
autoSpawn;
|
|
1066
|
+
spawnWaitMs;
|
|
1067
|
+
nextId = 0;
|
|
1068
|
+
constructor(opts = {}) {
|
|
1069
|
+
const uid = getUid();
|
|
1070
|
+
const dir = opts.socketDir ?? "/tmp";
|
|
1071
|
+
this.socketPath = socketPathFor(uid, dir);
|
|
1072
|
+
this.pidPath = pidPathFor(uid, dir);
|
|
1073
|
+
this.timeoutMs = opts.timeoutMs ?? DEFAULT_CLIENT_TIMEOUT_MS;
|
|
1074
|
+
this.daemonEntry = opts.daemonEntry ?? process.env.HIVEMIND_EMBED_DAEMON ?? (existsSync3(SHARED_DAEMON_PATH) ? SHARED_DAEMON_PATH : void 0);
|
|
1075
|
+
this.autoSpawn = opts.autoSpawn ?? true;
|
|
1076
|
+
this.spawnWaitMs = opts.spawnWaitMs ?? 5e3;
|
|
1077
|
+
}
|
|
1078
|
+
/**
|
|
1079
|
+
* Returns an embedding vector, or null on timeout/failure. Hooks MUST treat
|
|
1080
|
+
* null as "skip embedding column" — never block the write path on us.
|
|
1081
|
+
*
|
|
1082
|
+
* Fire-and-forget spawn on miss: if the daemon isn't up, this call returns
|
|
1083
|
+
* null AND kicks off a background spawn. The next call finds a ready daemon.
|
|
1084
|
+
*/
|
|
1085
|
+
async embed(text, kind = "document") {
|
|
1086
|
+
let sock;
|
|
1087
|
+
try {
|
|
1088
|
+
sock = await this.connectOnce();
|
|
1089
|
+
} catch {
|
|
1090
|
+
if (this.autoSpawn)
|
|
1091
|
+
this.trySpawnDaemon();
|
|
1092
|
+
return null;
|
|
1093
|
+
}
|
|
1094
|
+
try {
|
|
1095
|
+
const id = String(++this.nextId);
|
|
1096
|
+
const req = { op: "embed", id, kind, text };
|
|
1097
|
+
const resp = await this.sendAndWait(sock, req);
|
|
1098
|
+
if (resp.error || !("embedding" in resp) || !resp.embedding) {
|
|
1099
|
+
log3(`embed err: ${resp.error ?? "no embedding"}`);
|
|
1100
|
+
return null;
|
|
1101
|
+
}
|
|
1102
|
+
return resp.embedding;
|
|
1103
|
+
} catch (e) {
|
|
1104
|
+
const err = e instanceof Error ? e.message : String(e);
|
|
1105
|
+
log3(`embed failed: ${err}`);
|
|
1106
|
+
return null;
|
|
1107
|
+
} finally {
|
|
1108
|
+
try {
|
|
1109
|
+
sock.end();
|
|
1110
|
+
} catch {
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
/**
|
|
1115
|
+
* Wait up to spawnWaitMs for the daemon to accept connections, spawning if
|
|
1116
|
+
* necessary. Meant for SessionStart / long-running batches — not the hot path.
|
|
1117
|
+
*/
|
|
1118
|
+
async warmup() {
|
|
1119
|
+
try {
|
|
1120
|
+
const s = await this.connectOnce();
|
|
1121
|
+
s.end();
|
|
1122
|
+
return true;
|
|
1123
|
+
} catch {
|
|
1124
|
+
if (!this.autoSpawn)
|
|
1125
|
+
return false;
|
|
1126
|
+
this.trySpawnDaemon();
|
|
1127
|
+
try {
|
|
1128
|
+
const s = await this.waitForSocket();
|
|
1129
|
+
s.end();
|
|
1130
|
+
return true;
|
|
1131
|
+
} catch {
|
|
1132
|
+
return false;
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
connectOnce() {
|
|
1137
|
+
return new Promise((resolve, reject) => {
|
|
1138
|
+
const sock = connect(this.socketPath);
|
|
1139
|
+
const to = setTimeout(() => {
|
|
1140
|
+
sock.destroy();
|
|
1141
|
+
reject(new Error("connect timeout"));
|
|
1142
|
+
}, this.timeoutMs);
|
|
1143
|
+
sock.once("connect", () => {
|
|
1144
|
+
clearTimeout(to);
|
|
1145
|
+
resolve(sock);
|
|
1146
|
+
});
|
|
1147
|
+
sock.once("error", (e) => {
|
|
1148
|
+
clearTimeout(to);
|
|
1149
|
+
reject(e);
|
|
1150
|
+
});
|
|
1151
|
+
});
|
|
1152
|
+
}
|
|
1153
|
+
trySpawnDaemon() {
|
|
1154
|
+
let fd;
|
|
1155
|
+
try {
|
|
1156
|
+
fd = openSync(this.pidPath, "wx", 384);
|
|
1157
|
+
writeSync(fd, String(process.pid));
|
|
1158
|
+
} catch (e) {
|
|
1159
|
+
if (this.isPidFileStale()) {
|
|
1160
|
+
try {
|
|
1161
|
+
unlinkSync(this.pidPath);
|
|
1162
|
+
} catch {
|
|
1163
|
+
}
|
|
1164
|
+
try {
|
|
1165
|
+
fd = openSync(this.pidPath, "wx", 384);
|
|
1166
|
+
writeSync(fd, String(process.pid));
|
|
1167
|
+
} catch {
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
} else {
|
|
1171
|
+
return;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
if (!this.daemonEntry || !existsSync3(this.daemonEntry)) {
|
|
1175
|
+
log3(`daemonEntry not configured or missing: ${this.daemonEntry}`);
|
|
1176
|
+
try {
|
|
1177
|
+
closeSync(fd);
|
|
1178
|
+
unlinkSync(this.pidPath);
|
|
1179
|
+
} catch {
|
|
1180
|
+
}
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
1183
|
+
try {
|
|
1184
|
+
const child = spawn(process.execPath, [this.daemonEntry], {
|
|
1185
|
+
detached: true,
|
|
1186
|
+
stdio: "ignore",
|
|
1187
|
+
env: process.env
|
|
1188
|
+
});
|
|
1189
|
+
child.unref();
|
|
1190
|
+
log3(`spawned daemon pid=${child.pid}`);
|
|
1191
|
+
} finally {
|
|
1192
|
+
closeSync(fd);
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
isPidFileStale() {
|
|
1196
|
+
try {
|
|
1197
|
+
const raw = readFileSync3(this.pidPath, "utf-8").trim();
|
|
1198
|
+
const pid = Number(raw);
|
|
1199
|
+
if (!pid || Number.isNaN(pid))
|
|
1200
|
+
return true;
|
|
1201
|
+
try {
|
|
1202
|
+
process.kill(pid, 0);
|
|
1203
|
+
return false;
|
|
1204
|
+
} catch {
|
|
1205
|
+
return true;
|
|
1206
|
+
}
|
|
1207
|
+
} catch {
|
|
1208
|
+
return true;
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
async waitForSocket() {
|
|
1212
|
+
const deadline = Date.now() + this.spawnWaitMs;
|
|
1213
|
+
let delay = 30;
|
|
1214
|
+
while (Date.now() < deadline) {
|
|
1215
|
+
await sleep2(delay);
|
|
1216
|
+
delay = Math.min(delay * 1.5, 300);
|
|
1217
|
+
if (!existsSync3(this.socketPath))
|
|
1218
|
+
continue;
|
|
1219
|
+
try {
|
|
1220
|
+
return await this.connectOnce();
|
|
1221
|
+
} catch {
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
throw new Error("daemon did not become ready within spawnWaitMs");
|
|
1225
|
+
}
|
|
1226
|
+
sendAndWait(sock, req) {
|
|
1227
|
+
return new Promise((resolve, reject) => {
|
|
1228
|
+
let buf = "";
|
|
1229
|
+
const to = setTimeout(() => {
|
|
1230
|
+
sock.destroy();
|
|
1231
|
+
reject(new Error("request timeout"));
|
|
1232
|
+
}, this.timeoutMs);
|
|
1233
|
+
sock.setEncoding("utf-8");
|
|
1234
|
+
sock.on("data", (chunk) => {
|
|
1235
|
+
buf += chunk;
|
|
1236
|
+
const nl = buf.indexOf("\n");
|
|
1237
|
+
if (nl === -1)
|
|
1238
|
+
return;
|
|
1239
|
+
const line = buf.slice(0, nl);
|
|
1240
|
+
clearTimeout(to);
|
|
1241
|
+
try {
|
|
1242
|
+
resolve(JSON.parse(line));
|
|
1243
|
+
} catch (e) {
|
|
1244
|
+
reject(e);
|
|
1245
|
+
}
|
|
1246
|
+
});
|
|
1247
|
+
sock.on("error", (e) => {
|
|
1248
|
+
clearTimeout(to);
|
|
1249
|
+
reject(e);
|
|
1250
|
+
});
|
|
1251
|
+
sock.on("end", () => {
|
|
1252
|
+
clearTimeout(to);
|
|
1253
|
+
reject(new Error("connection closed without response"));
|
|
1254
|
+
});
|
|
1255
|
+
sock.write(JSON.stringify(req) + "\n");
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1258
|
+
};
|
|
1259
|
+
function sleep2(ms) {
|
|
1260
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
// dist/src/embeddings/disable.js
|
|
1264
|
+
import { createRequire } from "node:module";
|
|
1265
|
+
import { homedir as homedir4 } from "node:os";
|
|
1266
|
+
import { join as join5 } from "node:path";
|
|
1267
|
+
import { pathToFileURL } from "node:url";
|
|
1268
|
+
var cachedStatus = null;
|
|
1269
|
+
function defaultResolveTransformers() {
|
|
1270
|
+
try {
|
|
1271
|
+
createRequire(import.meta.url).resolve("@huggingface/transformers");
|
|
1272
|
+
return;
|
|
1273
|
+
} catch {
|
|
1274
|
+
}
|
|
1275
|
+
const sharedDir = join5(homedir4(), ".hivemind", "embed-deps");
|
|
1276
|
+
createRequire(pathToFileURL(`${sharedDir}/`).href).resolve("@huggingface/transformers");
|
|
1277
|
+
}
|
|
1278
|
+
var _resolve = defaultResolveTransformers;
|
|
1279
|
+
function detectStatus() {
|
|
1280
|
+
if (process.env.HIVEMIND_EMBEDDINGS === "false")
|
|
1281
|
+
return "env-disabled";
|
|
1282
|
+
try {
|
|
1283
|
+
_resolve();
|
|
1284
|
+
return "enabled";
|
|
1285
|
+
} catch {
|
|
1286
|
+
return "no-transformers";
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
function embeddingsStatus() {
|
|
1290
|
+
if (cachedStatus !== null)
|
|
1291
|
+
return cachedStatus;
|
|
1292
|
+
cachedStatus = detectStatus();
|
|
1293
|
+
return cachedStatus;
|
|
1294
|
+
}
|
|
1295
|
+
function embeddingsDisabled() {
|
|
1296
|
+
return embeddingsStatus() !== "enabled";
|
|
1297
|
+
}
|
|
1298
|
+
|
|
813
1299
|
// dist/src/hooks/grep-direct.js
|
|
1300
|
+
import { fileURLToPath } from "node:url";
|
|
1301
|
+
import { dirname, join as join6 } from "node:path";
|
|
1302
|
+
var SEMANTIC_ENABLED = process.env.HIVEMIND_SEMANTIC_SEARCH !== "false" && !embeddingsDisabled();
|
|
1303
|
+
var SEMANTIC_TIMEOUT_MS = Number(process.env.HIVEMIND_SEMANTIC_EMBED_TIMEOUT_MS ?? "500");
|
|
1304
|
+
function resolveDaemonPath() {
|
|
1305
|
+
return join6(dirname(fileURLToPath(import.meta.url)), "..", "embeddings", "embed-daemon.js");
|
|
1306
|
+
}
|
|
1307
|
+
var sharedEmbedClient = null;
|
|
1308
|
+
function getEmbedClient() {
|
|
1309
|
+
if (!sharedEmbedClient) {
|
|
1310
|
+
sharedEmbedClient = new EmbedClient({
|
|
1311
|
+
daemonEntry: resolveDaemonPath(),
|
|
1312
|
+
timeoutMs: SEMANTIC_TIMEOUT_MS
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1315
|
+
return sharedEmbedClient;
|
|
1316
|
+
}
|
|
1317
|
+
function patternIsSemanticFriendly(pattern, fixedString) {
|
|
1318
|
+
if (!pattern || pattern.length < 2)
|
|
1319
|
+
return false;
|
|
1320
|
+
if (fixedString)
|
|
1321
|
+
return true;
|
|
1322
|
+
const meta = pattern.match(/[|()\[\]{}+?^$\\]/g);
|
|
1323
|
+
if (!meta)
|
|
1324
|
+
return true;
|
|
1325
|
+
return meta.length <= 1;
|
|
1326
|
+
}
|
|
814
1327
|
function splitFirstPipelineStage(cmd) {
|
|
815
1328
|
const input = cmd.trim();
|
|
816
1329
|
let quote = null;
|
|
@@ -1128,15 +1641,23 @@ async function handleGrepDirect(api, table, sessionsTable, params) {
|
|
|
1128
1641
|
invertMatch: params.invertMatch,
|
|
1129
1642
|
fixedString: params.fixedString
|
|
1130
1643
|
};
|
|
1131
|
-
|
|
1644
|
+
let queryEmbedding = null;
|
|
1645
|
+
if (SEMANTIC_ENABLED && patternIsSemanticFriendly(params.pattern, params.fixedString)) {
|
|
1646
|
+
try {
|
|
1647
|
+
queryEmbedding = await getEmbedClient().embed(params.pattern, "query");
|
|
1648
|
+
} catch {
|
|
1649
|
+
queryEmbedding = null;
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
const output = await grepBothTables(api, table, sessionsTable, matchParams, params.targetPath, queryEmbedding);
|
|
1132
1653
|
const joined = output.join("\n") || "(no matches)";
|
|
1133
1654
|
return capOutputForClaude(joined, { kind: "grep" });
|
|
1134
1655
|
}
|
|
1135
1656
|
|
|
1136
1657
|
// dist/src/hooks/memory-path-utils.js
|
|
1137
|
-
import { homedir as
|
|
1138
|
-
import { join as
|
|
1139
|
-
var MEMORY_PATH =
|
|
1658
|
+
import { homedir as homedir5 } from "node:os";
|
|
1659
|
+
import { join as join7 } from "node:path";
|
|
1660
|
+
var MEMORY_PATH = join7(homedir5(), ".deeplake", "memory");
|
|
1140
1661
|
var TILDE_PATH = "~/.deeplake/memory";
|
|
1141
1662
|
var HOME_VAR_PATH = "$HOME/.deeplake/memory";
|
|
1142
1663
|
function touchesMemory(p) {
|
|
@@ -1147,7 +1668,7 @@ function rewritePaths(cmd) {
|
|
|
1147
1668
|
}
|
|
1148
1669
|
|
|
1149
1670
|
// dist/src/hooks/cursor/pre-tool-use.js
|
|
1150
|
-
var
|
|
1671
|
+
var log4 = (msg) => log("cursor-pre-tool-use", msg);
|
|
1151
1672
|
async function main() {
|
|
1152
1673
|
const input = await readStdin();
|
|
1153
1674
|
if (input.tool_name !== "Shell")
|
|
@@ -1163,17 +1684,17 @@ async function main() {
|
|
|
1163
1684
|
return;
|
|
1164
1685
|
const config = loadConfig();
|
|
1165
1686
|
if (!config) {
|
|
1166
|
-
|
|
1687
|
+
log4("no config \u2014 falling through to Cursor's bash");
|
|
1167
1688
|
return;
|
|
1168
1689
|
}
|
|
1169
1690
|
const api = new DeeplakeApi(config.token, config.apiUrl, config.orgId, config.workspaceId, config.tableName);
|
|
1170
1691
|
try {
|
|
1171
1692
|
const result = await handleGrepDirect(api, config.tableName, config.sessionsTableName, grepParams);
|
|
1172
1693
|
if (result === null) {
|
|
1173
|
-
|
|
1694
|
+
log4(`fallthrough \u2014 handleGrepDirect returned null for "${grepParams.pattern}"`);
|
|
1174
1695
|
return;
|
|
1175
1696
|
}
|
|
1176
|
-
|
|
1697
|
+
log4(`intercepted ${command.slice(0, 80)} \u2192 ${result.length} chars from SQL fast-path`);
|
|
1177
1698
|
const echoCmd = `cat <<'__HIVEMIND_RESULT__'
|
|
1178
1699
|
${result}
|
|
1179
1700
|
__HIVEMIND_RESULT__`;
|
|
@@ -1184,10 +1705,10 @@ __HIVEMIND_RESULT__`;
|
|
|
1184
1705
|
}));
|
|
1185
1706
|
} catch (err) {
|
|
1186
1707
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1187
|
-
|
|
1708
|
+
log4(`fast-path failed, falling through: ${msg}`);
|
|
1188
1709
|
}
|
|
1189
1710
|
}
|
|
1190
1711
|
main().catch((e) => {
|
|
1191
|
-
|
|
1712
|
+
log4(`fatal: ${e.message}`);
|
|
1192
1713
|
process.exit(0);
|
|
1193
1714
|
});
|