@deeplake/hivemind 0.6.47 → 0.7.4
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 +158 -51
- package/bundle/cli.js +4103 -282
- package/codex/bundle/capture.js +510 -90
- package/codex/bundle/commands/auth-login.js +219 -72
- package/codex/bundle/embeddings/embed-daemon.js +243 -0
- package/codex/bundle/pre-tool-use.js +713 -108
- package/codex/bundle/session-start-setup.js +209 -58
- package/codex/bundle/session-start.js +40 -11
- package/codex/bundle/shell/deeplake-shell.js +679 -112
- package/codex/bundle/stop.js +477 -59
- package/codex/bundle/wiki-worker.js +312 -11
- package/cursor/bundle/capture.js +768 -57
- package/cursor/bundle/commands/auth-login.js +219 -72
- package/cursor/bundle/embeddings/embed-daemon.js +243 -0
- package/cursor/bundle/pre-tool-use.js +1684 -0
- package/cursor/bundle/session-end.js +223 -2
- package/cursor/bundle/session-start.js +209 -57
- package/cursor/bundle/shell/deeplake-shell.js +679 -112
- package/cursor/bundle/wiki-worker.js +571 -0
- package/hermes/bundle/capture.js +1194 -0
- package/hermes/bundle/commands/auth-login.js +1009 -0
- package/hermes/bundle/embeddings/embed-daemon.js +243 -0
- package/hermes/bundle/package.json +1 -0
- package/hermes/bundle/pre-tool-use.js +1681 -0
- package/hermes/bundle/session-end.js +265 -0
- package/hermes/bundle/session-start.js +655 -0
- package/hermes/bundle/shell/deeplake-shell.js +69905 -0
- package/hermes/bundle/wiki-worker.js +572 -0
- package/mcp/bundle/server.js +289 -69
- package/openclaw/dist/chunks/auth-creds-AEKS6D3P.js +14 -0
- package/openclaw/dist/chunks/chunk-SRCBBT4H.js +37 -0
- package/openclaw/dist/chunks/config-G23NI5TV.js +33 -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 +752 -702
- package/openclaw/openclaw.plugin.json +1 -1
- package/openclaw/package.json +1 -1
- package/package.json +7 -3
- package/pi/extension-source/hivemind.ts +807 -0
package/codex/bundle/capture.js
CHANGED
|
@@ -1,4 +1,56 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// dist/src/index-marker-store.js
|
|
13
|
+
var index_marker_store_exports = {};
|
|
14
|
+
__export(index_marker_store_exports, {
|
|
15
|
+
buildIndexMarkerPath: () => buildIndexMarkerPath,
|
|
16
|
+
getIndexMarkerDir: () => getIndexMarkerDir,
|
|
17
|
+
hasFreshIndexMarker: () => hasFreshIndexMarker,
|
|
18
|
+
writeIndexMarker: () => writeIndexMarker
|
|
19
|
+
});
|
|
20
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
|
|
21
|
+
import { join as join3 } from "node:path";
|
|
22
|
+
import { tmpdir } from "node:os";
|
|
23
|
+
function getIndexMarkerDir() {
|
|
24
|
+
return process.env.HIVEMIND_INDEX_MARKER_DIR ?? join3(tmpdir(), "hivemind-deeplake-indexes");
|
|
25
|
+
}
|
|
26
|
+
function buildIndexMarkerPath(workspaceId, orgId, table, suffix) {
|
|
27
|
+
const markerKey = [workspaceId, orgId, table, suffix].join("__").replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
28
|
+
return join3(getIndexMarkerDir(), `${markerKey}.json`);
|
|
29
|
+
}
|
|
30
|
+
function hasFreshIndexMarker(markerPath) {
|
|
31
|
+
if (!existsSync2(markerPath))
|
|
32
|
+
return false;
|
|
33
|
+
try {
|
|
34
|
+
const raw = JSON.parse(readFileSync2(markerPath, "utf-8"));
|
|
35
|
+
const updatedAt = raw.updatedAt ? new Date(raw.updatedAt).getTime() : NaN;
|
|
36
|
+
if (!Number.isFinite(updatedAt) || Date.now() - updatedAt > INDEX_MARKER_TTL_MS)
|
|
37
|
+
return false;
|
|
38
|
+
return true;
|
|
39
|
+
} catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function writeIndexMarker(markerPath) {
|
|
44
|
+
mkdirSync(getIndexMarkerDir(), { recursive: true });
|
|
45
|
+
writeFileSync(markerPath, JSON.stringify({ updatedAt: (/* @__PURE__ */ new Date()).toISOString() }), "utf-8");
|
|
46
|
+
}
|
|
47
|
+
var INDEX_MARKER_TTL_MS;
|
|
48
|
+
var init_index_marker_store = __esm({
|
|
49
|
+
"dist/src/index-marker-store.js"() {
|
|
50
|
+
"use strict";
|
|
51
|
+
INDEX_MARKER_TTL_MS = Number(process.env.HIVEMIND_INDEX_MARKER_TTL_MS ?? 6 * 60 * 6e4);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
2
54
|
|
|
3
55
|
// dist/src/utils/stdin.js
|
|
4
56
|
function readStdin() {
|
|
@@ -51,9 +103,6 @@ function loadConfig() {
|
|
|
51
103
|
|
|
52
104
|
// dist/src/deeplake-api.js
|
|
53
105
|
import { randomUUID } from "node:crypto";
|
|
54
|
-
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
|
|
55
|
-
import { join as join3 } from "node:path";
|
|
56
|
-
import { tmpdir } from "node:os";
|
|
57
106
|
|
|
58
107
|
// dist/src/utils/debug.js
|
|
59
108
|
import { appendFileSync } from "node:fs";
|
|
@@ -76,7 +125,26 @@ function sqlStr(value) {
|
|
|
76
125
|
return value.replace(/\\/g, "\\\\").replace(/'/g, "''").replace(/\0/g, "").replace(/[\x01-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "");
|
|
77
126
|
}
|
|
78
127
|
|
|
128
|
+
// dist/src/embeddings/columns.js
|
|
129
|
+
var SUMMARY_EMBEDDING_COL = "summary_embedding";
|
|
130
|
+
var MESSAGE_EMBEDDING_COL = "message_embedding";
|
|
131
|
+
|
|
132
|
+
// dist/src/utils/client-header.js
|
|
133
|
+
var DEEPLAKE_CLIENT_HEADER = "X-Deeplake-Client";
|
|
134
|
+
function deeplakeClientValue() {
|
|
135
|
+
return "hivemind";
|
|
136
|
+
}
|
|
137
|
+
function deeplakeClientHeader() {
|
|
138
|
+
return { [DEEPLAKE_CLIENT_HEADER]: deeplakeClientValue() };
|
|
139
|
+
}
|
|
140
|
+
|
|
79
141
|
// dist/src/deeplake-api.js
|
|
142
|
+
var indexMarkerStorePromise = null;
|
|
143
|
+
function getIndexMarkerStore() {
|
|
144
|
+
if (!indexMarkerStorePromise)
|
|
145
|
+
indexMarkerStorePromise = Promise.resolve().then(() => (init_index_marker_store(), index_marker_store_exports));
|
|
146
|
+
return indexMarkerStorePromise;
|
|
147
|
+
}
|
|
80
148
|
var log2 = (msg) => log("sdk", msg);
|
|
81
149
|
function summarizeSql(sql, maxLen = 220) {
|
|
82
150
|
const compact = sql.replace(/\s+/g, " ").trim();
|
|
@@ -96,7 +164,6 @@ var MAX_RETRIES = 3;
|
|
|
96
164
|
var BASE_DELAY_MS = 500;
|
|
97
165
|
var MAX_CONCURRENCY = 5;
|
|
98
166
|
var QUERY_TIMEOUT_MS = Number(process.env.HIVEMIND_QUERY_TIMEOUT_MS ?? 1e4);
|
|
99
|
-
var INDEX_MARKER_TTL_MS = Number(process.env.HIVEMIND_INDEX_MARKER_TTL_MS ?? 6 * 60 * 6e4);
|
|
100
167
|
function sleep(ms) {
|
|
101
168
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
102
169
|
}
|
|
@@ -116,9 +183,6 @@ function isTransientHtml403(text) {
|
|
|
116
183
|
const body = text.toLowerCase();
|
|
117
184
|
return body.includes("<html") || body.includes("403 forbidden") || body.includes("cloudflare") || body.includes("nginx");
|
|
118
185
|
}
|
|
119
|
-
function getIndexMarkerDir() {
|
|
120
|
-
return process.env.HIVEMIND_INDEX_MARKER_DIR ?? join3(tmpdir(), "hivemind-deeplake-indexes");
|
|
121
|
-
}
|
|
122
186
|
var Semaphore = class {
|
|
123
187
|
max;
|
|
124
188
|
waiting = [];
|
|
@@ -187,7 +251,8 @@ var DeeplakeApi = class {
|
|
|
187
251
|
headers: {
|
|
188
252
|
Authorization: `Bearer ${this.token}`,
|
|
189
253
|
"Content-Type": "application/json",
|
|
190
|
-
"X-Activeloop-Org-Id": this.orgId
|
|
254
|
+
"X-Activeloop-Org-Id": this.orgId,
|
|
255
|
+
...deeplakeClientHeader()
|
|
191
256
|
},
|
|
192
257
|
signal,
|
|
193
258
|
body: JSON.stringify({ query: sql })
|
|
@@ -214,7 +279,8 @@ var DeeplakeApi = class {
|
|
|
214
279
|
}
|
|
215
280
|
const text = await resp.text().catch(() => "");
|
|
216
281
|
const retryable403 = isSessionInsertQuery(sql) && (resp.status === 401 || resp.status === 403 && (text.length === 0 || isTransientHtml403(text)));
|
|
217
|
-
|
|
282
|
+
const alreadyExists = resp.status === 500 && isDuplicateIndexError(text);
|
|
283
|
+
if (!alreadyExists && attempt < MAX_RETRIES && (RETRYABLE_CODES.has(resp.status) || retryable403)) {
|
|
218
284
|
const delay = BASE_DELAY_MS * Math.pow(2, attempt) + Math.random() * 200;
|
|
219
285
|
log2(`query retry ${attempt + 1}/${MAX_RETRIES} (${resp.status}) in ${delay.toFixed(0)}ms`);
|
|
220
286
|
await sleep(delay);
|
|
@@ -248,7 +314,7 @@ var DeeplakeApi = class {
|
|
|
248
314
|
const lud = row.lastUpdateDate ?? ts;
|
|
249
315
|
const exists = await this.query(`SELECT path FROM "${this.tableName}" WHERE path = '${sqlStr(row.path)}' LIMIT 1`);
|
|
250
316
|
if (exists.length > 0) {
|
|
251
|
-
let setClauses = `summary = E'${sqlStr(row.contentText)}', mime_type = '${sqlStr(row.mimeType)}', size_bytes = ${row.sizeBytes}, last_update_date = '${lud}'`;
|
|
317
|
+
let setClauses = `summary = E'${sqlStr(row.contentText)}', ${SUMMARY_EMBEDDING_COL} = NULL, mime_type = '${sqlStr(row.mimeType)}', size_bytes = ${row.sizeBytes}, last_update_date = '${lud}'`;
|
|
252
318
|
if (row.project !== void 0)
|
|
253
319
|
setClauses += `, project = '${sqlStr(row.project)}'`;
|
|
254
320
|
if (row.description !== void 0)
|
|
@@ -256,8 +322,8 @@ var DeeplakeApi = class {
|
|
|
256
322
|
await this.query(`UPDATE "${this.tableName}" SET ${setClauses} WHERE path = '${sqlStr(row.path)}'`);
|
|
257
323
|
} else {
|
|
258
324
|
const id = randomUUID();
|
|
259
|
-
let cols =
|
|
260
|
-
let vals = `'${id}', '${sqlStr(row.path)}', '${sqlStr(row.filename)}', E'${sqlStr(row.contentText)}', '${sqlStr(row.mimeType)}', ${row.sizeBytes}, '${cd}', '${lud}'`;
|
|
325
|
+
let cols = `id, path, filename, summary, ${SUMMARY_EMBEDDING_COL}, mime_type, size_bytes, creation_date, last_update_date`;
|
|
326
|
+
let vals = `'${id}', '${sqlStr(row.path)}', '${sqlStr(row.filename)}', E'${sqlStr(row.contentText)}', NULL, '${sqlStr(row.mimeType)}', ${row.sizeBytes}, '${cd}', '${lud}'`;
|
|
261
327
|
if (row.project !== void 0) {
|
|
262
328
|
cols += ", project";
|
|
263
329
|
vals += `, '${sqlStr(row.project)}'`;
|
|
@@ -282,48 +348,83 @@ var DeeplakeApi = class {
|
|
|
282
348
|
buildLookupIndexName(table, suffix) {
|
|
283
349
|
return `idx_${table}_${suffix}`.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
284
350
|
}
|
|
285
|
-
getLookupIndexMarkerPath(table, suffix) {
|
|
286
|
-
const markerKey = [
|
|
287
|
-
this.workspaceId,
|
|
288
|
-
this.orgId,
|
|
289
|
-
table,
|
|
290
|
-
suffix
|
|
291
|
-
].join("__").replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
292
|
-
return join3(getIndexMarkerDir(), `${markerKey}.json`);
|
|
293
|
-
}
|
|
294
|
-
hasFreshLookupIndexMarker(table, suffix) {
|
|
295
|
-
const markerPath = this.getLookupIndexMarkerPath(table, suffix);
|
|
296
|
-
if (!existsSync2(markerPath))
|
|
297
|
-
return false;
|
|
298
|
-
try {
|
|
299
|
-
const raw = JSON.parse(readFileSync2(markerPath, "utf-8"));
|
|
300
|
-
const updatedAt = raw.updatedAt ? new Date(raw.updatedAt).getTime() : NaN;
|
|
301
|
-
if (!Number.isFinite(updatedAt) || Date.now() - updatedAt > INDEX_MARKER_TTL_MS)
|
|
302
|
-
return false;
|
|
303
|
-
return true;
|
|
304
|
-
} catch {
|
|
305
|
-
return false;
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
markLookupIndexReady(table, suffix) {
|
|
309
|
-
mkdirSync(getIndexMarkerDir(), { recursive: true });
|
|
310
|
-
writeFileSync(this.getLookupIndexMarkerPath(table, suffix), JSON.stringify({ updatedAt: (/* @__PURE__ */ new Date()).toISOString() }), "utf-8");
|
|
311
|
-
}
|
|
312
351
|
async ensureLookupIndex(table, suffix, columnsSql) {
|
|
313
|
-
|
|
352
|
+
const markers = await getIndexMarkerStore();
|
|
353
|
+
const markerPath = markers.buildIndexMarkerPath(this.workspaceId, this.orgId, table, suffix);
|
|
354
|
+
if (markers.hasFreshIndexMarker(markerPath))
|
|
314
355
|
return;
|
|
315
356
|
const indexName = this.buildLookupIndexName(table, suffix);
|
|
316
357
|
try {
|
|
317
358
|
await this.query(`CREATE INDEX IF NOT EXISTS "${indexName}" ON "${table}" ${columnsSql}`);
|
|
318
|
-
|
|
359
|
+
markers.writeIndexMarker(markerPath);
|
|
319
360
|
} catch (e) {
|
|
320
361
|
if (isDuplicateIndexError(e)) {
|
|
321
|
-
|
|
362
|
+
markers.writeIndexMarker(markerPath);
|
|
322
363
|
return;
|
|
323
364
|
}
|
|
324
365
|
log2(`index "${indexName}" skipped: ${e.message}`);
|
|
325
366
|
}
|
|
326
367
|
}
|
|
368
|
+
/**
|
|
369
|
+
* Ensure a vector column exists on the given table.
|
|
370
|
+
*
|
|
371
|
+
* The previous implementation always issued `ALTER TABLE ADD COLUMN IF NOT
|
|
372
|
+
* EXISTS …` on every SessionStart. On a long-running workspace that's
|
|
373
|
+
* already migrated, every call returns 500 "Column already exists" — noisy
|
|
374
|
+
* in the log and a wasted round-trip. Worse, the very first call after the
|
|
375
|
+
* column is genuinely added triggers Deeplake's post-ALTER `vector::at`
|
|
376
|
+
* window (~30s) during which subsequent INSERTs fail; minimising the
|
|
377
|
+
* number of ALTER calls minimises exposure to that window.
|
|
378
|
+
*
|
|
379
|
+
* New flow:
|
|
380
|
+
* 1. Check the local marker file (mirrors ensureLookupIndex). If fresh,
|
|
381
|
+
* return — zero network calls.
|
|
382
|
+
* 2. SELECT 1 FROM information_schema.columns WHERE table_name = T AND
|
|
383
|
+
* column_name = C. Read-only, idempotent, can't tickle the post-ALTER
|
|
384
|
+
* bug. If the column is present → mark + return.
|
|
385
|
+
* 3. Only if step 2 says the column is missing, fall back to ALTER ADD
|
|
386
|
+
* COLUMN IF NOT EXISTS. Mark on success, also mark if Deeplake reports
|
|
387
|
+
* "already exists" (race: another client added it between our SELECT
|
|
388
|
+
* and ALTER).
|
|
389
|
+
*
|
|
390
|
+
* Marker uses the same dir / TTL as ensureLookupIndex so both schema
|
|
391
|
+
* caches share an opt-out (HIVEMIND_INDEX_MARKER_DIR) and a TTL knob.
|
|
392
|
+
*/
|
|
393
|
+
async ensureEmbeddingColumn(table, column) {
|
|
394
|
+
await this.ensureColumn(table, column, "FLOAT4[]");
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Generic marker-gated column migration. Same SELECT-then-ALTER flow as
|
|
398
|
+
* ensureEmbeddingColumn, parameterized by SQL type so it can patch up any
|
|
399
|
+
* column that was added to the schema after the table was originally
|
|
400
|
+
* created. Used today for `summary_embedding`, `message_embedding`, and
|
|
401
|
+
* the `agent` column (added 2026-04-11) — the latter has no fallback if
|
|
402
|
+
* a user upgraded over a pre-2026-04-11 table, so every INSERT fails
|
|
403
|
+
* with `column "agent" does not exist`.
|
|
404
|
+
*/
|
|
405
|
+
async ensureColumn(table, column, sqlType) {
|
|
406
|
+
const markers = await getIndexMarkerStore();
|
|
407
|
+
const markerPath = markers.buildIndexMarkerPath(this.workspaceId, this.orgId, table, `col_${column}`);
|
|
408
|
+
if (markers.hasFreshIndexMarker(markerPath))
|
|
409
|
+
return;
|
|
410
|
+
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`;
|
|
411
|
+
const rows = await this.query(colCheck);
|
|
412
|
+
if (rows.length > 0) {
|
|
413
|
+
markers.writeIndexMarker(markerPath);
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
try {
|
|
417
|
+
await this.query(`ALTER TABLE "${table}" ADD COLUMN ${column} ${sqlType}`);
|
|
418
|
+
} catch (e) {
|
|
419
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
420
|
+
if (!/already exists/i.test(msg))
|
|
421
|
+
throw e;
|
|
422
|
+
const recheck = await this.query(colCheck);
|
|
423
|
+
if (recheck.length === 0)
|
|
424
|
+
throw e;
|
|
425
|
+
}
|
|
426
|
+
markers.writeIndexMarker(markerPath);
|
|
427
|
+
}
|
|
327
428
|
/** List all tables in the workspace (with retry). */
|
|
328
429
|
async listTables(forceRefresh = false) {
|
|
329
430
|
if (!forceRefresh && this._tablesCache)
|
|
@@ -339,7 +440,8 @@ var DeeplakeApi = class {
|
|
|
339
440
|
const resp = await fetch(`${this.apiUrl}/workspaces/${this.workspaceId}/tables`, {
|
|
340
441
|
headers: {
|
|
341
442
|
Authorization: `Bearer ${this.token}`,
|
|
342
|
-
"X-Activeloop-Org-Id": this.orgId
|
|
443
|
+
"X-Activeloop-Org-Id": this.orgId,
|
|
444
|
+
...deeplakeClientHeader()
|
|
343
445
|
}
|
|
344
446
|
});
|
|
345
447
|
if (resp.ok) {
|
|
@@ -364,28 +466,60 @@ var DeeplakeApi = class {
|
|
|
364
466
|
}
|
|
365
467
|
return { tables: [], cacheable: false };
|
|
366
468
|
}
|
|
469
|
+
/**
|
|
470
|
+
* Run a `CREATE TABLE` with an extra outer retry budget. The base
|
|
471
|
+
* `query()` already retries 3 times on fetch errors (~3.5s total), but a
|
|
472
|
+
* failed CREATE is permanent corruption — every subsequent SELECT against
|
|
473
|
+
* the missing table fails. Wrapping in an outer loop with longer backoff
|
|
474
|
+
* (2s, 5s, then 10s) gives us ~17s of reach across transient network
|
|
475
|
+
* blips before giving up. Failures still propagate; getApi() resets its
|
|
476
|
+
* cache on init failure (openclaw plugin) so the next call retries the
|
|
477
|
+
* whole init flow.
|
|
478
|
+
*/
|
|
479
|
+
async createTableWithRetry(sql, label) {
|
|
480
|
+
const OUTER_BACKOFFS_MS = [2e3, 5e3, 1e4];
|
|
481
|
+
let lastErr = null;
|
|
482
|
+
for (let attempt = 0; attempt <= OUTER_BACKOFFS_MS.length; attempt++) {
|
|
483
|
+
try {
|
|
484
|
+
await this.query(sql);
|
|
485
|
+
return;
|
|
486
|
+
} catch (err) {
|
|
487
|
+
lastErr = err;
|
|
488
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
489
|
+
log2(`CREATE TABLE "${label}" attempt ${attempt + 1}/${OUTER_BACKOFFS_MS.length + 1} failed: ${msg}`);
|
|
490
|
+
if (attempt < OUTER_BACKOFFS_MS.length) {
|
|
491
|
+
await sleep(OUTER_BACKOFFS_MS[attempt]);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
throw lastErr;
|
|
496
|
+
}
|
|
367
497
|
/** Create the memory table if it doesn't already exist. Migrate columns on existing tables. */
|
|
368
498
|
async ensureTable(name) {
|
|
369
499
|
const tbl = name ?? this.tableName;
|
|
370
500
|
const tables = await this.listTables();
|
|
371
501
|
if (!tables.includes(tbl)) {
|
|
372
502
|
log2(`table "${tbl}" not found, creating`);
|
|
373
|
-
await this.
|
|
503
|
+
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);
|
|
374
504
|
log2(`table "${tbl}" created`);
|
|
375
505
|
if (!tables.includes(tbl))
|
|
376
506
|
this._tablesCache = [...tables, tbl];
|
|
377
507
|
}
|
|
508
|
+
await this.ensureEmbeddingColumn(tbl, SUMMARY_EMBEDDING_COL);
|
|
509
|
+
await this.ensureColumn(tbl, "agent", "TEXT NOT NULL DEFAULT ''");
|
|
378
510
|
}
|
|
379
511
|
/** Create the sessions table (uses JSONB for message since every row is a JSON event). */
|
|
380
512
|
async ensureSessionsTable(name) {
|
|
381
513
|
const tables = await this.listTables();
|
|
382
514
|
if (!tables.includes(name)) {
|
|
383
515
|
log2(`table "${name}" not found, creating`);
|
|
384
|
-
await this.
|
|
516
|
+
await this.createTableWithRetry(`CREATE TABLE IF NOT EXISTS "${name}" (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`, name);
|
|
385
517
|
log2(`table "${name}" created`);
|
|
386
518
|
if (!tables.includes(name))
|
|
387
519
|
this._tablesCache = [...tables, name];
|
|
388
520
|
}
|
|
521
|
+
await this.ensureEmbeddingColumn(name, MESSAGE_EMBEDDING_COL);
|
|
522
|
+
await this.ensureColumn(name, "agent", "TEXT NOT NULL DEFAULT ''");
|
|
389
523
|
await this.ensureLookupIndex(name, "path_creation_date", `("path", "creation_date")`);
|
|
390
524
|
}
|
|
391
525
|
};
|
|
@@ -396,25 +530,306 @@ function buildSessionPath(config, sessionId) {
|
|
|
396
530
|
return `/sessions/${config.userName}/${config.userName}_${config.orgName}_${workspace}_${sessionId}.jsonl`;
|
|
397
531
|
}
|
|
398
532
|
|
|
399
|
-
// dist/src/
|
|
400
|
-
import {
|
|
533
|
+
// dist/src/embeddings/client.js
|
|
534
|
+
import { connect } from "node:net";
|
|
535
|
+
import { spawn } from "node:child_process";
|
|
536
|
+
import { openSync, closeSync, writeSync, unlinkSync, existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
|
|
401
537
|
import { homedir as homedir3 } from "node:os";
|
|
402
538
|
import { join as join4 } from "node:path";
|
|
539
|
+
|
|
540
|
+
// dist/src/embeddings/protocol.js
|
|
541
|
+
var DEFAULT_SOCKET_DIR = "/tmp";
|
|
542
|
+
var DEFAULT_IDLE_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
543
|
+
var DEFAULT_CLIENT_TIMEOUT_MS = 2e3;
|
|
544
|
+
function socketPathFor(uid, dir = DEFAULT_SOCKET_DIR) {
|
|
545
|
+
return `${dir}/hivemind-embed-${uid}.sock`;
|
|
546
|
+
}
|
|
547
|
+
function pidPathFor(uid, dir = DEFAULT_SOCKET_DIR) {
|
|
548
|
+
return `${dir}/hivemind-embed-${uid}.pid`;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// dist/src/embeddings/client.js
|
|
552
|
+
var SHARED_DAEMON_PATH = join4(homedir3(), ".hivemind", "embed-deps", "embed-daemon.js");
|
|
553
|
+
var log3 = (m) => log("embed-client", m);
|
|
554
|
+
function getUid() {
|
|
555
|
+
const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
|
|
556
|
+
return uid !== void 0 ? String(uid) : process.env.USER ?? "default";
|
|
557
|
+
}
|
|
558
|
+
var EmbedClient = class {
|
|
559
|
+
socketPath;
|
|
560
|
+
pidPath;
|
|
561
|
+
timeoutMs;
|
|
562
|
+
daemonEntry;
|
|
563
|
+
autoSpawn;
|
|
564
|
+
spawnWaitMs;
|
|
565
|
+
nextId = 0;
|
|
566
|
+
constructor(opts = {}) {
|
|
567
|
+
const uid = getUid();
|
|
568
|
+
const dir = opts.socketDir ?? "/tmp";
|
|
569
|
+
this.socketPath = socketPathFor(uid, dir);
|
|
570
|
+
this.pidPath = pidPathFor(uid, dir);
|
|
571
|
+
this.timeoutMs = opts.timeoutMs ?? DEFAULT_CLIENT_TIMEOUT_MS;
|
|
572
|
+
this.daemonEntry = opts.daemonEntry ?? process.env.HIVEMIND_EMBED_DAEMON ?? (existsSync3(SHARED_DAEMON_PATH) ? SHARED_DAEMON_PATH : void 0);
|
|
573
|
+
this.autoSpawn = opts.autoSpawn ?? true;
|
|
574
|
+
this.spawnWaitMs = opts.spawnWaitMs ?? 5e3;
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Returns an embedding vector, or null on timeout/failure. Hooks MUST treat
|
|
578
|
+
* null as "skip embedding column" — never block the write path on us.
|
|
579
|
+
*
|
|
580
|
+
* Fire-and-forget spawn on miss: if the daemon isn't up, this call returns
|
|
581
|
+
* null AND kicks off a background spawn. The next call finds a ready daemon.
|
|
582
|
+
*/
|
|
583
|
+
async embed(text, kind = "document") {
|
|
584
|
+
let sock;
|
|
585
|
+
try {
|
|
586
|
+
sock = await this.connectOnce();
|
|
587
|
+
} catch {
|
|
588
|
+
if (this.autoSpawn)
|
|
589
|
+
this.trySpawnDaemon();
|
|
590
|
+
return null;
|
|
591
|
+
}
|
|
592
|
+
try {
|
|
593
|
+
const id = String(++this.nextId);
|
|
594
|
+
const req = { op: "embed", id, kind, text };
|
|
595
|
+
const resp = await this.sendAndWait(sock, req);
|
|
596
|
+
if (resp.error || !("embedding" in resp) || !resp.embedding) {
|
|
597
|
+
log3(`embed err: ${resp.error ?? "no embedding"}`);
|
|
598
|
+
return null;
|
|
599
|
+
}
|
|
600
|
+
return resp.embedding;
|
|
601
|
+
} catch (e) {
|
|
602
|
+
const err = e instanceof Error ? e.message : String(e);
|
|
603
|
+
log3(`embed failed: ${err}`);
|
|
604
|
+
return null;
|
|
605
|
+
} finally {
|
|
606
|
+
try {
|
|
607
|
+
sock.end();
|
|
608
|
+
} catch {
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Wait up to spawnWaitMs for the daemon to accept connections, spawning if
|
|
614
|
+
* necessary. Meant for SessionStart / long-running batches — not the hot path.
|
|
615
|
+
*/
|
|
616
|
+
async warmup() {
|
|
617
|
+
try {
|
|
618
|
+
const s = await this.connectOnce();
|
|
619
|
+
s.end();
|
|
620
|
+
return true;
|
|
621
|
+
} catch {
|
|
622
|
+
if (!this.autoSpawn)
|
|
623
|
+
return false;
|
|
624
|
+
this.trySpawnDaemon();
|
|
625
|
+
try {
|
|
626
|
+
const s = await this.waitForSocket();
|
|
627
|
+
s.end();
|
|
628
|
+
return true;
|
|
629
|
+
} catch {
|
|
630
|
+
return false;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
connectOnce() {
|
|
635
|
+
return new Promise((resolve, reject) => {
|
|
636
|
+
const sock = connect(this.socketPath);
|
|
637
|
+
const to = setTimeout(() => {
|
|
638
|
+
sock.destroy();
|
|
639
|
+
reject(new Error("connect timeout"));
|
|
640
|
+
}, this.timeoutMs);
|
|
641
|
+
sock.once("connect", () => {
|
|
642
|
+
clearTimeout(to);
|
|
643
|
+
resolve(sock);
|
|
644
|
+
});
|
|
645
|
+
sock.once("error", (e) => {
|
|
646
|
+
clearTimeout(to);
|
|
647
|
+
reject(e);
|
|
648
|
+
});
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
trySpawnDaemon() {
|
|
652
|
+
let fd;
|
|
653
|
+
try {
|
|
654
|
+
fd = openSync(this.pidPath, "wx", 384);
|
|
655
|
+
writeSync(fd, String(process.pid));
|
|
656
|
+
} catch (e) {
|
|
657
|
+
if (this.isPidFileStale()) {
|
|
658
|
+
try {
|
|
659
|
+
unlinkSync(this.pidPath);
|
|
660
|
+
} catch {
|
|
661
|
+
}
|
|
662
|
+
try {
|
|
663
|
+
fd = openSync(this.pidPath, "wx", 384);
|
|
664
|
+
writeSync(fd, String(process.pid));
|
|
665
|
+
} catch {
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
} else {
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
if (!this.daemonEntry || !existsSync3(this.daemonEntry)) {
|
|
673
|
+
log3(`daemonEntry not configured or missing: ${this.daemonEntry}`);
|
|
674
|
+
try {
|
|
675
|
+
closeSync(fd);
|
|
676
|
+
unlinkSync(this.pidPath);
|
|
677
|
+
} catch {
|
|
678
|
+
}
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
try {
|
|
682
|
+
const child = spawn(process.execPath, [this.daemonEntry], {
|
|
683
|
+
detached: true,
|
|
684
|
+
stdio: "ignore",
|
|
685
|
+
env: process.env
|
|
686
|
+
});
|
|
687
|
+
child.unref();
|
|
688
|
+
log3(`spawned daemon pid=${child.pid}`);
|
|
689
|
+
} finally {
|
|
690
|
+
closeSync(fd);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
isPidFileStale() {
|
|
694
|
+
try {
|
|
695
|
+
const raw = readFileSync3(this.pidPath, "utf-8").trim();
|
|
696
|
+
const pid = Number(raw);
|
|
697
|
+
if (!pid || Number.isNaN(pid))
|
|
698
|
+
return true;
|
|
699
|
+
try {
|
|
700
|
+
process.kill(pid, 0);
|
|
701
|
+
return false;
|
|
702
|
+
} catch {
|
|
703
|
+
return true;
|
|
704
|
+
}
|
|
705
|
+
} catch {
|
|
706
|
+
return true;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
async waitForSocket() {
|
|
710
|
+
const deadline = Date.now() + this.spawnWaitMs;
|
|
711
|
+
let delay = 30;
|
|
712
|
+
while (Date.now() < deadline) {
|
|
713
|
+
await sleep2(delay);
|
|
714
|
+
delay = Math.min(delay * 1.5, 300);
|
|
715
|
+
if (!existsSync3(this.socketPath))
|
|
716
|
+
continue;
|
|
717
|
+
try {
|
|
718
|
+
return await this.connectOnce();
|
|
719
|
+
} catch {
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
throw new Error("daemon did not become ready within spawnWaitMs");
|
|
723
|
+
}
|
|
724
|
+
sendAndWait(sock, req) {
|
|
725
|
+
return new Promise((resolve, reject) => {
|
|
726
|
+
let buf = "";
|
|
727
|
+
const to = setTimeout(() => {
|
|
728
|
+
sock.destroy();
|
|
729
|
+
reject(new Error("request timeout"));
|
|
730
|
+
}, this.timeoutMs);
|
|
731
|
+
sock.setEncoding("utf-8");
|
|
732
|
+
sock.on("data", (chunk) => {
|
|
733
|
+
buf += chunk;
|
|
734
|
+
const nl = buf.indexOf("\n");
|
|
735
|
+
if (nl === -1)
|
|
736
|
+
return;
|
|
737
|
+
const line = buf.slice(0, nl);
|
|
738
|
+
clearTimeout(to);
|
|
739
|
+
try {
|
|
740
|
+
resolve(JSON.parse(line));
|
|
741
|
+
} catch (e) {
|
|
742
|
+
reject(e);
|
|
743
|
+
}
|
|
744
|
+
});
|
|
745
|
+
sock.on("error", (e) => {
|
|
746
|
+
clearTimeout(to);
|
|
747
|
+
reject(e);
|
|
748
|
+
});
|
|
749
|
+
sock.on("end", () => {
|
|
750
|
+
clearTimeout(to);
|
|
751
|
+
reject(new Error("connection closed without response"));
|
|
752
|
+
});
|
|
753
|
+
sock.write(JSON.stringify(req) + "\n");
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
};
|
|
757
|
+
function sleep2(ms) {
|
|
758
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// dist/src/embeddings/sql.js
|
|
762
|
+
function embeddingSqlLiteral(vec) {
|
|
763
|
+
if (!vec || vec.length === 0)
|
|
764
|
+
return "NULL";
|
|
765
|
+
const parts = [];
|
|
766
|
+
for (const v of vec) {
|
|
767
|
+
if (!Number.isFinite(v))
|
|
768
|
+
return "NULL";
|
|
769
|
+
parts.push(String(v));
|
|
770
|
+
}
|
|
771
|
+
return `ARRAY[${parts.join(",")}]::float4[]`;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// dist/src/embeddings/disable.js
|
|
775
|
+
import { createRequire } from "node:module";
|
|
776
|
+
import { homedir as homedir4 } from "node:os";
|
|
777
|
+
import { join as join5 } from "node:path";
|
|
778
|
+
import { pathToFileURL } from "node:url";
|
|
779
|
+
var cachedStatus = null;
|
|
780
|
+
function defaultResolveTransformers() {
|
|
781
|
+
try {
|
|
782
|
+
createRequire(import.meta.url).resolve("@huggingface/transformers");
|
|
783
|
+
return;
|
|
784
|
+
} catch {
|
|
785
|
+
}
|
|
786
|
+
const sharedDir = join5(homedir4(), ".hivemind", "embed-deps");
|
|
787
|
+
createRequire(pathToFileURL(`${sharedDir}/`).href).resolve("@huggingface/transformers");
|
|
788
|
+
}
|
|
789
|
+
var _resolve = defaultResolveTransformers;
|
|
790
|
+
function detectStatus() {
|
|
791
|
+
if (process.env.HIVEMIND_EMBEDDINGS === "false")
|
|
792
|
+
return "env-disabled";
|
|
793
|
+
try {
|
|
794
|
+
_resolve();
|
|
795
|
+
return "enabled";
|
|
796
|
+
} catch {
|
|
797
|
+
return "no-transformers";
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
function embeddingsStatus() {
|
|
801
|
+
if (cachedStatus !== null)
|
|
802
|
+
return cachedStatus;
|
|
803
|
+
cachedStatus = detectStatus();
|
|
804
|
+
return cachedStatus;
|
|
805
|
+
}
|
|
806
|
+
function embeddingsDisabled() {
|
|
807
|
+
return embeddingsStatus() !== "enabled";
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// dist/src/hooks/codex/capture.js
|
|
811
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
812
|
+
import { dirname as dirname2, join as join9 } from "node:path";
|
|
813
|
+
|
|
814
|
+
// dist/src/hooks/summary-state.js
|
|
815
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, writeSync as writeSync2, mkdirSync as mkdirSync2, renameSync, existsSync as existsSync4, unlinkSync as unlinkSync2, openSync as openSync2, closeSync as closeSync2 } from "node:fs";
|
|
816
|
+
import { homedir as homedir5 } from "node:os";
|
|
817
|
+
import { join as join6 } from "node:path";
|
|
403
818
|
var dlog = (msg) => log("summary-state", msg);
|
|
404
|
-
var STATE_DIR =
|
|
819
|
+
var STATE_DIR = join6(homedir5(), ".claude", "hooks", "summary-state");
|
|
405
820
|
var YIELD_BUF = new Int32Array(new SharedArrayBuffer(4));
|
|
406
821
|
function statePath(sessionId) {
|
|
407
|
-
return
|
|
822
|
+
return join6(STATE_DIR, `${sessionId}.json`);
|
|
408
823
|
}
|
|
409
824
|
function lockPath(sessionId) {
|
|
410
|
-
return
|
|
825
|
+
return join6(STATE_DIR, `${sessionId}.lock`);
|
|
411
826
|
}
|
|
412
827
|
function readState(sessionId) {
|
|
413
828
|
const p = statePath(sessionId);
|
|
414
|
-
if (!
|
|
829
|
+
if (!existsSync4(p))
|
|
415
830
|
return null;
|
|
416
831
|
try {
|
|
417
|
-
return JSON.parse(
|
|
832
|
+
return JSON.parse(readFileSync4(p, "utf-8"));
|
|
418
833
|
} catch {
|
|
419
834
|
return null;
|
|
420
835
|
}
|
|
@@ -433,14 +848,14 @@ function withRmwLock(sessionId, fn) {
|
|
|
433
848
|
let fd = null;
|
|
434
849
|
while (fd === null) {
|
|
435
850
|
try {
|
|
436
|
-
fd =
|
|
851
|
+
fd = openSync2(rmwLock, "wx");
|
|
437
852
|
} catch (e) {
|
|
438
853
|
if (e.code !== "EEXIST")
|
|
439
854
|
throw e;
|
|
440
855
|
if (Date.now() > deadline) {
|
|
441
856
|
dlog(`rmw lock deadline exceeded for ${sessionId}, reclaiming stale lock`);
|
|
442
857
|
try {
|
|
443
|
-
|
|
858
|
+
unlinkSync2(rmwLock);
|
|
444
859
|
} catch (unlinkErr) {
|
|
445
860
|
dlog(`stale rmw lock unlink failed for ${sessionId}: ${unlinkErr.message}`);
|
|
446
861
|
}
|
|
@@ -452,9 +867,9 @@ function withRmwLock(sessionId, fn) {
|
|
|
452
867
|
try {
|
|
453
868
|
return fn();
|
|
454
869
|
} finally {
|
|
455
|
-
|
|
870
|
+
closeSync2(fd);
|
|
456
871
|
try {
|
|
457
|
-
|
|
872
|
+
unlinkSync2(rmwLock);
|
|
458
873
|
} catch (unlinkErr) {
|
|
459
874
|
dlog(`rmw lock cleanup failed for ${sessionId}: ${unlinkErr.message}`);
|
|
460
875
|
}
|
|
@@ -491,27 +906,27 @@ function shouldTrigger(state, cfg, now = Date.now()) {
|
|
|
491
906
|
function tryAcquireLock(sessionId, maxAgeMs = 10 * 60 * 1e3) {
|
|
492
907
|
mkdirSync2(STATE_DIR, { recursive: true });
|
|
493
908
|
const p = lockPath(sessionId);
|
|
494
|
-
if (
|
|
909
|
+
if (existsSync4(p)) {
|
|
495
910
|
try {
|
|
496
|
-
const ageMs = Date.now() - parseInt(
|
|
911
|
+
const ageMs = Date.now() - parseInt(readFileSync4(p, "utf-8"), 10);
|
|
497
912
|
if (Number.isFinite(ageMs) && ageMs < maxAgeMs)
|
|
498
913
|
return false;
|
|
499
914
|
} catch (readErr) {
|
|
500
915
|
dlog(`lock file unreadable for ${sessionId}, treating as stale: ${readErr.message}`);
|
|
501
916
|
}
|
|
502
917
|
try {
|
|
503
|
-
|
|
918
|
+
unlinkSync2(p);
|
|
504
919
|
} catch (unlinkErr) {
|
|
505
920
|
dlog(`could not unlink stale lock for ${sessionId}: ${unlinkErr.message}`);
|
|
506
921
|
return false;
|
|
507
922
|
}
|
|
508
923
|
}
|
|
509
924
|
try {
|
|
510
|
-
const fd =
|
|
925
|
+
const fd = openSync2(p, "wx");
|
|
511
926
|
try {
|
|
512
|
-
|
|
927
|
+
writeSync2(fd, String(Date.now()));
|
|
513
928
|
} finally {
|
|
514
|
-
|
|
929
|
+
closeSync2(fd);
|
|
515
930
|
}
|
|
516
931
|
return true;
|
|
517
932
|
} catch (e) {
|
|
@@ -522,7 +937,7 @@ function tryAcquireLock(sessionId, maxAgeMs = 10 * 60 * 1e3) {
|
|
|
522
937
|
}
|
|
523
938
|
function releaseLock(sessionId) {
|
|
524
939
|
try {
|
|
525
|
-
|
|
940
|
+
unlinkSync2(lockPath(sessionId));
|
|
526
941
|
} catch (e) {
|
|
527
942
|
if (e?.code !== "ENOENT") {
|
|
528
943
|
dlog(`releaseLock unlink failed for ${sessionId}: ${e.message}`);
|
|
@@ -531,17 +946,17 @@ function releaseLock(sessionId) {
|
|
|
531
946
|
}
|
|
532
947
|
|
|
533
948
|
// dist/src/hooks/codex/spawn-wiki-worker.js
|
|
534
|
-
import { spawn, execSync } from "node:child_process";
|
|
949
|
+
import { spawn as spawn2, execSync } from "node:child_process";
|
|
535
950
|
import { fileURLToPath } from "node:url";
|
|
536
|
-
import { dirname, join as
|
|
951
|
+
import { dirname, join as join8 } from "node:path";
|
|
537
952
|
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync4 } from "node:fs";
|
|
538
|
-
import { homedir as
|
|
953
|
+
import { homedir as homedir6, tmpdir as tmpdir2 } from "node:os";
|
|
539
954
|
|
|
540
955
|
// dist/src/utils/wiki-log.js
|
|
541
956
|
import { mkdirSync as mkdirSync3, appendFileSync as appendFileSync2 } from "node:fs";
|
|
542
|
-
import { join as
|
|
957
|
+
import { join as join7 } from "node:path";
|
|
543
958
|
function makeWikiLogger(hooksDir, filename = "deeplake-wiki.log") {
|
|
544
|
-
const path =
|
|
959
|
+
const path = join7(hooksDir, filename);
|
|
545
960
|
return {
|
|
546
961
|
path,
|
|
547
962
|
log(msg) {
|
|
@@ -556,8 +971,8 @@ function makeWikiLogger(hooksDir, filename = "deeplake-wiki.log") {
|
|
|
556
971
|
}
|
|
557
972
|
|
|
558
973
|
// dist/src/hooks/codex/spawn-wiki-worker.js
|
|
559
|
-
var HOME =
|
|
560
|
-
var wikiLogger = makeWikiLogger(
|
|
974
|
+
var HOME = homedir6();
|
|
975
|
+
var wikiLogger = makeWikiLogger(join8(HOME, ".codex", "hooks"));
|
|
561
976
|
var WIKI_LOG = wikiLogger.path;
|
|
562
977
|
var WIKI_PROMPT_TEMPLATE = `You are building a personal wiki from a coding session. Your goal is to extract every piece of knowledge \u2014 entities, decisions, relationships, and facts \u2014 into a structured, searchable wiki entry.
|
|
563
978
|
|
|
@@ -619,9 +1034,9 @@ function findCodexBin() {
|
|
|
619
1034
|
function spawnCodexWikiWorker(opts) {
|
|
620
1035
|
const { config, sessionId, cwd, bundleDir, reason } = opts;
|
|
621
1036
|
const projectName = cwd.split("/").pop() || "unknown";
|
|
622
|
-
const tmpDir =
|
|
1037
|
+
const tmpDir = join8(tmpdir2(), `deeplake-wiki-${sessionId}-${Date.now()}`);
|
|
623
1038
|
mkdirSync4(tmpDir, { recursive: true });
|
|
624
|
-
const configFile =
|
|
1039
|
+
const configFile = join8(tmpDir, "config.json");
|
|
625
1040
|
writeFileSync3(configFile, JSON.stringify({
|
|
626
1041
|
apiUrl: config.apiUrl,
|
|
627
1042
|
token: config.token,
|
|
@@ -635,12 +1050,12 @@ function spawnCodexWikiWorker(opts) {
|
|
|
635
1050
|
tmpDir,
|
|
636
1051
|
codexBin: findCodexBin(),
|
|
637
1052
|
wikiLog: WIKI_LOG,
|
|
638
|
-
hooksDir:
|
|
1053
|
+
hooksDir: join8(HOME, ".codex", "hooks"),
|
|
639
1054
|
promptTemplate: WIKI_PROMPT_TEMPLATE
|
|
640
1055
|
}));
|
|
641
1056
|
wikiLog(`${reason}: spawning summary worker for ${sessionId}`);
|
|
642
|
-
const workerPath =
|
|
643
|
-
|
|
1057
|
+
const workerPath = join8(bundleDir, "wiki-worker.js");
|
|
1058
|
+
spawn2("nohup", ["node", workerPath, configFile], {
|
|
644
1059
|
detached: true,
|
|
645
1060
|
stdio: ["ignore", "ignore", "ignore"]
|
|
646
1061
|
}).unref();
|
|
@@ -651,7 +1066,10 @@ function bundleDirFromImportMeta(importMetaUrl) {
|
|
|
651
1066
|
}
|
|
652
1067
|
|
|
653
1068
|
// dist/src/hooks/codex/capture.js
|
|
654
|
-
var
|
|
1069
|
+
var log4 = (msg) => log("codex-capture", msg);
|
|
1070
|
+
function resolveEmbedDaemonPath() {
|
|
1071
|
+
return join9(dirname2(fileURLToPath2(import.meta.url)), "embeddings", "embed-daemon.js");
|
|
1072
|
+
}
|
|
655
1073
|
var CAPTURE = process.env.HIVEMIND_CAPTURE !== "false";
|
|
656
1074
|
async function main() {
|
|
657
1075
|
if (!CAPTURE)
|
|
@@ -659,7 +1077,7 @@ async function main() {
|
|
|
659
1077
|
const input = await readStdin();
|
|
660
1078
|
const config = loadConfig();
|
|
661
1079
|
if (!config) {
|
|
662
|
-
|
|
1080
|
+
log4("no config");
|
|
663
1081
|
return;
|
|
664
1082
|
}
|
|
665
1083
|
const sessionsTable = config.sessionsTableName;
|
|
@@ -676,7 +1094,7 @@ async function main() {
|
|
|
676
1094
|
};
|
|
677
1095
|
let entry;
|
|
678
1096
|
if (input.hook_event_name === "UserPromptSubmit" && input.prompt !== void 0) {
|
|
679
|
-
|
|
1097
|
+
log4(`user session=${input.session_id}`);
|
|
680
1098
|
entry = {
|
|
681
1099
|
id: crypto.randomUUID(),
|
|
682
1100
|
...meta,
|
|
@@ -684,7 +1102,7 @@ async function main() {
|
|
|
684
1102
|
content: input.prompt
|
|
685
1103
|
};
|
|
686
1104
|
} else if (input.hook_event_name === "PostToolUse" && input.tool_name !== void 0) {
|
|
687
|
-
|
|
1105
|
+
log4(`tool=${input.tool_name} session=${input.session_id}`);
|
|
688
1106
|
entry = {
|
|
689
1107
|
id: crypto.randomUUID(),
|
|
690
1108
|
...meta,
|
|
@@ -695,28 +1113,30 @@ async function main() {
|
|
|
695
1113
|
tool_response: JSON.stringify(input.tool_response)
|
|
696
1114
|
};
|
|
697
1115
|
} else {
|
|
698
|
-
|
|
1116
|
+
log4(`unknown event: ${input.hook_event_name}, skipping`);
|
|
699
1117
|
return;
|
|
700
1118
|
}
|
|
701
1119
|
const sessionPath = buildSessionPath(config, input.session_id);
|
|
702
1120
|
const line = JSON.stringify(entry);
|
|
703
|
-
|
|
1121
|
+
log4(`writing to ${sessionPath}`);
|
|
704
1122
|
const projectName = (input.cwd ?? "").split("/").pop() || "unknown";
|
|
705
1123
|
const filename = sessionPath.split("/").pop() ?? "";
|
|
706
|
-
const jsonForSql =
|
|
707
|
-
const
|
|
1124
|
+
const jsonForSql = line.replace(/'/g, "''");
|
|
1125
|
+
const embedding = embeddingsDisabled() ? null : await new EmbedClient({ daemonEntry: resolveEmbedDaemonPath() }).embed(line, "document");
|
|
1126
|
+
const embeddingSql = embeddingSqlLiteral(embedding);
|
|
1127
|
+
const insertSql = `INSERT INTO "${sessionsTable}" (id, path, filename, message, message_embedding, author, size_bytes, project, description, agent, creation_date, last_update_date) VALUES ('${crypto.randomUUID()}', '${sqlStr(sessionPath)}', '${sqlStr(filename)}', '${jsonForSql}'::jsonb, ${embeddingSql}, '${sqlStr(config.userName)}', ${Buffer.byteLength(line, "utf-8")}, '${sqlStr(projectName)}', '${sqlStr(input.hook_event_name ?? "")}', 'codex', '${ts}', '${ts}')`;
|
|
708
1128
|
try {
|
|
709
1129
|
await api.query(insertSql);
|
|
710
1130
|
} catch (e) {
|
|
711
1131
|
if (e.message?.includes("permission denied") || e.message?.includes("does not exist")) {
|
|
712
|
-
|
|
1132
|
+
log4("table missing, creating and retrying");
|
|
713
1133
|
await api.ensureSessionsTable(sessionsTable);
|
|
714
1134
|
await api.query(insertSql);
|
|
715
1135
|
} else {
|
|
716
1136
|
throw e;
|
|
717
1137
|
}
|
|
718
1138
|
}
|
|
719
|
-
|
|
1139
|
+
log4("capture ok");
|
|
720
1140
|
maybeTriggerPeriodicSummary(input.session_id, input.cwd ?? "", config);
|
|
721
1141
|
}
|
|
722
1142
|
function maybeTriggerPeriodicSummary(sessionId, cwd, config) {
|
|
@@ -728,7 +1148,7 @@ function maybeTriggerPeriodicSummary(sessionId, cwd, config) {
|
|
|
728
1148
|
if (!shouldTrigger(state, cfg))
|
|
729
1149
|
return;
|
|
730
1150
|
if (!tryAcquireLock(sessionId)) {
|
|
731
|
-
|
|
1151
|
+
log4(`periodic trigger suppressed (lock held) session=${sessionId}`);
|
|
732
1152
|
return;
|
|
733
1153
|
}
|
|
734
1154
|
wikiLog(`Periodic: threshold hit (total=${state.totalCount}, since=${state.totalCount - state.lastSummaryCount}, N=${cfg.everyNMessages}, hours=${cfg.everyHours})`);
|
|
@@ -741,19 +1161,19 @@ function maybeTriggerPeriodicSummary(sessionId, cwd, config) {
|
|
|
741
1161
|
reason: "Periodic"
|
|
742
1162
|
});
|
|
743
1163
|
} catch (e) {
|
|
744
|
-
|
|
1164
|
+
log4(`periodic spawn failed: ${e.message}`);
|
|
745
1165
|
try {
|
|
746
1166
|
releaseLock(sessionId);
|
|
747
1167
|
} catch (releaseErr) {
|
|
748
|
-
|
|
1168
|
+
log4(`releaseLock after periodic spawn failure also failed: ${releaseErr.message}`);
|
|
749
1169
|
}
|
|
750
1170
|
throw e;
|
|
751
1171
|
}
|
|
752
1172
|
} catch (e) {
|
|
753
|
-
|
|
1173
|
+
log4(`periodic trigger error: ${e.message}`);
|
|
754
1174
|
}
|
|
755
1175
|
}
|
|
756
1176
|
main().catch((e) => {
|
|
757
|
-
|
|
1177
|
+
log4(`fatal: ${e.message}`);
|
|
758
1178
|
process.exit(0);
|
|
759
1179
|
});
|