@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.
Files changed (45) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +244 -20
  4. package/bundle/cli.js +1369 -112
  5. package/codex/bundle/capture.js +546 -96
  6. package/codex/bundle/commands/auth-login.js +290 -81
  7. package/codex/bundle/embeddings/embed-daemon.js +243 -0
  8. package/codex/bundle/pre-tool-use.js +666 -111
  9. package/codex/bundle/session-start-setup.js +231 -64
  10. package/codex/bundle/session-start.js +52 -13
  11. package/codex/bundle/shell/deeplake-shell.js +716 -119
  12. package/codex/bundle/skilify-worker.js +907 -0
  13. package/codex/bundle/stop.js +819 -79
  14. package/codex/bundle/wiki-worker.js +312 -11
  15. package/cursor/bundle/capture.js +1116 -64
  16. package/cursor/bundle/commands/auth-login.js +290 -81
  17. package/cursor/bundle/embeddings/embed-daemon.js +243 -0
  18. package/cursor/bundle/pre-tool-use.js +598 -77
  19. package/cursor/bundle/session-end.js +520 -2
  20. package/cursor/bundle/session-start.js +257 -65
  21. package/cursor/bundle/shell/deeplake-shell.js +716 -119
  22. package/cursor/bundle/skilify-worker.js +907 -0
  23. package/cursor/bundle/wiki-worker.js +571 -0
  24. package/hermes/bundle/capture.js +1119 -65
  25. package/hermes/bundle/commands/auth-login.js +290 -81
  26. package/hermes/bundle/embeddings/embed-daemon.js +243 -0
  27. package/hermes/bundle/pre-tool-use.js +597 -76
  28. package/hermes/bundle/session-end.js +522 -1
  29. package/hermes/bundle/session-start.js +260 -65
  30. package/hermes/bundle/shell/deeplake-shell.js +716 -119
  31. package/hermes/bundle/skilify-worker.js +907 -0
  32. package/hermes/bundle/wiki-worker.js +572 -0
  33. package/mcp/bundle/server.js +290 -75
  34. package/openclaw/dist/chunks/auth-creds-AEKS6D3P.js +14 -0
  35. package/openclaw/dist/chunks/chunk-SRCBBT4H.js +37 -0
  36. package/openclaw/dist/chunks/config-ZLH6JFJS.js +34 -0
  37. package/openclaw/dist/chunks/index-marker-store-PGT5CW6T.js +33 -0
  38. package/openclaw/dist/chunks/setup-config-C35UK4LP.js +114 -0
  39. package/openclaw/dist/index.js +929 -710
  40. package/openclaw/dist/skilify-worker.js +907 -0
  41. package/openclaw/openclaw.plugin.json +1 -1
  42. package/openclaw/package.json +1 -1
  43. package/openclaw/skills/SKILL.md +19 -0
  44. package/package.json +7 -1
  45. package/pi/extension-source/hivemind.ts +603 -22
@@ -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() {
@@ -45,15 +97,13 @@ function loadConfig() {
45
97
  apiUrl: process.env.HIVEMIND_API_URL ?? creds?.apiUrl ?? "https://api.deeplake.ai",
46
98
  tableName: process.env.HIVEMIND_TABLE ?? "memory",
47
99
  sessionsTableName: process.env.HIVEMIND_SESSIONS_TABLE ?? "sessions",
100
+ skillsTableName: process.env.HIVEMIND_SKILLS_TABLE ?? "skills",
48
101
  memoryPath: process.env.HIVEMIND_MEMORY_PATH ?? join(home, ".deeplake", "memory")
49
102
  };
50
103
  }
51
104
 
52
105
  // dist/src/deeplake-api.js
53
106
  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
107
 
58
108
  // dist/src/utils/debug.js
59
109
  import { appendFileSync } from "node:fs";
@@ -75,8 +125,33 @@ function log(tag, msg) {
75
125
  function sqlStr(value) {
76
126
  return value.replace(/\\/g, "\\\\").replace(/'/g, "''").replace(/\0/g, "").replace(/[\x01-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "");
77
127
  }
128
+ function sqlIdent(name) {
129
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
130
+ throw new Error(`Invalid SQL identifier: ${JSON.stringify(name)}`);
131
+ }
132
+ return name;
133
+ }
134
+
135
+ // dist/src/embeddings/columns.js
136
+ var SUMMARY_EMBEDDING_COL = "summary_embedding";
137
+ var MESSAGE_EMBEDDING_COL = "message_embedding";
138
+
139
+ // dist/src/utils/client-header.js
140
+ var DEEPLAKE_CLIENT_HEADER = "X-Deeplake-Client";
141
+ function deeplakeClientValue() {
142
+ return "hivemind";
143
+ }
144
+ function deeplakeClientHeader() {
145
+ return { [DEEPLAKE_CLIENT_HEADER]: deeplakeClientValue() };
146
+ }
78
147
 
79
148
  // dist/src/deeplake-api.js
149
+ var indexMarkerStorePromise = null;
150
+ function getIndexMarkerStore() {
151
+ if (!indexMarkerStorePromise)
152
+ indexMarkerStorePromise = Promise.resolve().then(() => (init_index_marker_store(), index_marker_store_exports));
153
+ return indexMarkerStorePromise;
154
+ }
80
155
  var log2 = (msg) => log("sdk", msg);
81
156
  function summarizeSql(sql, maxLen = 220) {
82
157
  const compact = sql.replace(/\s+/g, " ").trim();
@@ -96,7 +171,6 @@ var MAX_RETRIES = 3;
96
171
  var BASE_DELAY_MS = 500;
97
172
  var MAX_CONCURRENCY = 5;
98
173
  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
174
  function sleep(ms) {
101
175
  return new Promise((resolve) => setTimeout(resolve, ms));
102
176
  }
@@ -116,9 +190,6 @@ function isTransientHtml403(text) {
116
190
  const body = text.toLowerCase();
117
191
  return body.includes("<html") || body.includes("403 forbidden") || body.includes("cloudflare") || body.includes("nginx");
118
192
  }
119
- function getIndexMarkerDir() {
120
- return process.env.HIVEMIND_INDEX_MARKER_DIR ?? join3(tmpdir(), "hivemind-deeplake-indexes");
121
- }
122
193
  var Semaphore = class {
123
194
  max;
124
195
  waiting = [];
@@ -187,7 +258,8 @@ var DeeplakeApi = class {
187
258
  headers: {
188
259
  Authorization: `Bearer ${this.token}`,
189
260
  "Content-Type": "application/json",
190
- "X-Activeloop-Org-Id": this.orgId
261
+ "X-Activeloop-Org-Id": this.orgId,
262
+ ...deeplakeClientHeader()
191
263
  },
192
264
  signal,
193
265
  body: JSON.stringify({ query: sql })
@@ -214,7 +286,8 @@ var DeeplakeApi = class {
214
286
  }
215
287
  const text = await resp.text().catch(() => "");
216
288
  const retryable403 = isSessionInsertQuery(sql) && (resp.status === 401 || resp.status === 403 && (text.length === 0 || isTransientHtml403(text)));
217
- if (attempt < MAX_RETRIES && (RETRYABLE_CODES.has(resp.status) || retryable403)) {
289
+ const alreadyExists = resp.status === 500 && isDuplicateIndexError(text);
290
+ if (!alreadyExists && attempt < MAX_RETRIES && (RETRYABLE_CODES.has(resp.status) || retryable403)) {
218
291
  const delay = BASE_DELAY_MS * Math.pow(2, attempt) + Math.random() * 200;
219
292
  log2(`query retry ${attempt + 1}/${MAX_RETRIES} (${resp.status}) in ${delay.toFixed(0)}ms`);
220
293
  await sleep(delay);
@@ -248,7 +321,7 @@ var DeeplakeApi = class {
248
321
  const lud = row.lastUpdateDate ?? ts;
249
322
  const exists = await this.query(`SELECT path FROM "${this.tableName}" WHERE path = '${sqlStr(row.path)}' LIMIT 1`);
250
323
  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}'`;
324
+ 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
325
  if (row.project !== void 0)
253
326
  setClauses += `, project = '${sqlStr(row.project)}'`;
254
327
  if (row.description !== void 0)
@@ -256,8 +329,8 @@ var DeeplakeApi = class {
256
329
  await this.query(`UPDATE "${this.tableName}" SET ${setClauses} WHERE path = '${sqlStr(row.path)}'`);
257
330
  } else {
258
331
  const id = randomUUID();
259
- let cols = "id, path, filename, summary, mime_type, size_bytes, creation_date, last_update_date";
260
- let vals = `'${id}', '${sqlStr(row.path)}', '${sqlStr(row.filename)}', E'${sqlStr(row.contentText)}', '${sqlStr(row.mimeType)}', ${row.sizeBytes}, '${cd}', '${lud}'`;
332
+ let cols = `id, path, filename, summary, ${SUMMARY_EMBEDDING_COL}, mime_type, size_bytes, creation_date, last_update_date`;
333
+ let vals = `'${id}', '${sqlStr(row.path)}', '${sqlStr(row.filename)}', E'${sqlStr(row.contentText)}', NULL, '${sqlStr(row.mimeType)}', ${row.sizeBytes}, '${cd}', '${lud}'`;
261
334
  if (row.project !== void 0) {
262
335
  cols += ", project";
263
336
  vals += `, '${sqlStr(row.project)}'`;
@@ -282,48 +355,83 @@ var DeeplakeApi = class {
282
355
  buildLookupIndexName(table, suffix) {
283
356
  return `idx_${table}_${suffix}`.replace(/[^a-zA-Z0-9_]/g, "_");
284
357
  }
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
358
  async ensureLookupIndex(table, suffix, columnsSql) {
313
- if (this.hasFreshLookupIndexMarker(table, suffix))
359
+ const markers = await getIndexMarkerStore();
360
+ const markerPath = markers.buildIndexMarkerPath(this.workspaceId, this.orgId, table, suffix);
361
+ if (markers.hasFreshIndexMarker(markerPath))
314
362
  return;
315
363
  const indexName = this.buildLookupIndexName(table, suffix);
316
364
  try {
317
365
  await this.query(`CREATE INDEX IF NOT EXISTS "${indexName}" ON "${table}" ${columnsSql}`);
318
- this.markLookupIndexReady(table, suffix);
366
+ markers.writeIndexMarker(markerPath);
319
367
  } catch (e) {
320
368
  if (isDuplicateIndexError(e)) {
321
- this.markLookupIndexReady(table, suffix);
369
+ markers.writeIndexMarker(markerPath);
322
370
  return;
323
371
  }
324
372
  log2(`index "${indexName}" skipped: ${e.message}`);
325
373
  }
326
374
  }
375
+ /**
376
+ * Ensure a vector column exists on the given table.
377
+ *
378
+ * The previous implementation always issued `ALTER TABLE ADD COLUMN IF NOT
379
+ * EXISTS …` on every SessionStart. On a long-running workspace that's
380
+ * already migrated, every call returns 500 "Column already exists" — noisy
381
+ * in the log and a wasted round-trip. Worse, the very first call after the
382
+ * column is genuinely added triggers Deeplake's post-ALTER `vector::at`
383
+ * window (~30s) during which subsequent INSERTs fail; minimising the
384
+ * number of ALTER calls minimises exposure to that window.
385
+ *
386
+ * New flow:
387
+ * 1. Check the local marker file (mirrors ensureLookupIndex). If fresh,
388
+ * return — zero network calls.
389
+ * 2. SELECT 1 FROM information_schema.columns WHERE table_name = T AND
390
+ * column_name = C. Read-only, idempotent, can't tickle the post-ALTER
391
+ * bug. If the column is present → mark + return.
392
+ * 3. Only if step 2 says the column is missing, fall back to ALTER ADD
393
+ * COLUMN IF NOT EXISTS. Mark on success, also mark if Deeplake reports
394
+ * "already exists" (race: another client added it between our SELECT
395
+ * and ALTER).
396
+ *
397
+ * Marker uses the same dir / TTL as ensureLookupIndex so both schema
398
+ * caches share an opt-out (HIVEMIND_INDEX_MARKER_DIR) and a TTL knob.
399
+ */
400
+ async ensureEmbeddingColumn(table, column) {
401
+ await this.ensureColumn(table, column, "FLOAT4[]");
402
+ }
403
+ /**
404
+ * Generic marker-gated column migration. Same SELECT-then-ALTER flow as
405
+ * ensureEmbeddingColumn, parameterized by SQL type so it can patch up any
406
+ * column that was added to the schema after the table was originally
407
+ * created. Used today for `summary_embedding`, `message_embedding`, and
408
+ * the `agent` column (added 2026-04-11) — the latter has no fallback if
409
+ * a user upgraded over a pre-2026-04-11 table, so every INSERT fails
410
+ * with `column "agent" does not exist`.
411
+ */
412
+ async ensureColumn(table, column, sqlType) {
413
+ const markers = await getIndexMarkerStore();
414
+ const markerPath = markers.buildIndexMarkerPath(this.workspaceId, this.orgId, table, `col_${column}`);
415
+ if (markers.hasFreshIndexMarker(markerPath))
416
+ return;
417
+ 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`;
418
+ const rows = await this.query(colCheck);
419
+ if (rows.length > 0) {
420
+ markers.writeIndexMarker(markerPath);
421
+ return;
422
+ }
423
+ try {
424
+ await this.query(`ALTER TABLE "${table}" ADD COLUMN ${column} ${sqlType}`);
425
+ } catch (e) {
426
+ const msg = e instanceof Error ? e.message : String(e);
427
+ if (!/already exists/i.test(msg))
428
+ throw e;
429
+ const recheck = await this.query(colCheck);
430
+ if (recheck.length === 0)
431
+ throw e;
432
+ }
433
+ markers.writeIndexMarker(markerPath);
434
+ }
327
435
  /** List all tables in the workspace (with retry). */
328
436
  async listTables(forceRefresh = false) {
329
437
  if (!forceRefresh && this._tablesCache)
@@ -339,7 +447,8 @@ var DeeplakeApi = class {
339
447
  const resp = await fetch(`${this.apiUrl}/workspaces/${this.workspaceId}/tables`, {
340
448
  headers: {
341
449
  Authorization: `Bearer ${this.token}`,
342
- "X-Activeloop-Org-Id": this.orgId
450
+ "X-Activeloop-Org-Id": this.orgId,
451
+ ...deeplakeClientHeader()
343
452
  }
344
453
  });
345
454
  if (resp.ok) {
@@ -364,29 +473,84 @@ var DeeplakeApi = class {
364
473
  }
365
474
  return { tables: [], cacheable: false };
366
475
  }
476
+ /**
477
+ * Run a `CREATE TABLE` with an extra outer retry budget. The base
478
+ * `query()` already retries 3 times on fetch errors (~3.5s total), but a
479
+ * failed CREATE is permanent corruption — every subsequent SELECT against
480
+ * the missing table fails. Wrapping in an outer loop with longer backoff
481
+ * (2s, 5s, then 10s) gives us ~17s of reach across transient network
482
+ * blips before giving up. Failures still propagate; getApi() resets its
483
+ * cache on init failure (openclaw plugin) so the next call retries the
484
+ * whole init flow.
485
+ */
486
+ async createTableWithRetry(sql, label) {
487
+ const OUTER_BACKOFFS_MS = [2e3, 5e3, 1e4];
488
+ let lastErr = null;
489
+ for (let attempt = 0; attempt <= OUTER_BACKOFFS_MS.length; attempt++) {
490
+ try {
491
+ await this.query(sql);
492
+ return;
493
+ } catch (err) {
494
+ lastErr = err;
495
+ const msg = err instanceof Error ? err.message : String(err);
496
+ log2(`CREATE TABLE "${label}" attempt ${attempt + 1}/${OUTER_BACKOFFS_MS.length + 1} failed: ${msg}`);
497
+ if (attempt < OUTER_BACKOFFS_MS.length) {
498
+ await sleep(OUTER_BACKOFFS_MS[attempt]);
499
+ }
500
+ }
501
+ }
502
+ throw lastErr;
503
+ }
367
504
  /** Create the memory table if it doesn't already exist. Migrate columns on existing tables. */
368
505
  async ensureTable(name) {
369
- const tbl = name ?? this.tableName;
506
+ const tbl = sqlIdent(name ?? this.tableName);
370
507
  const tables = await this.listTables();
371
508
  if (!tables.includes(tbl)) {
372
509
  log2(`table "${tbl}" not found, creating`);
373
- await this.query(`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 '', 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`);
510
+ 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
511
  log2(`table "${tbl}" created`);
375
512
  if (!tables.includes(tbl))
376
513
  this._tablesCache = [...tables, tbl];
377
514
  }
515
+ await this.ensureEmbeddingColumn(tbl, SUMMARY_EMBEDDING_COL);
516
+ await this.ensureColumn(tbl, "agent", "TEXT NOT NULL DEFAULT ''");
378
517
  }
379
518
  /** Create the sessions table (uses JSONB for message since every row is a JSON event). */
380
519
  async ensureSessionsTable(name) {
520
+ const safe = sqlIdent(name);
521
+ const tables = await this.listTables();
522
+ if (!tables.includes(safe)) {
523
+ log2(`table "${safe}" not found, creating`);
524
+ 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);
525
+ log2(`table "${safe}" created`);
526
+ if (!tables.includes(safe))
527
+ this._tablesCache = [...tables, safe];
528
+ }
529
+ await this.ensureEmbeddingColumn(safe, MESSAGE_EMBEDDING_COL);
530
+ await this.ensureColumn(safe, "agent", "TEXT NOT NULL DEFAULT ''");
531
+ await this.ensureLookupIndex(safe, "path_creation_date", `("path", "creation_date")`);
532
+ }
533
+ /**
534
+ * Create the skills table.
535
+ *
536
+ * One row per skill version. Workers INSERT a fresh row on every KEEP /
537
+ * MERGE rather than UPDATE-ing in place, so the full version history is
538
+ * recoverable. Uniqueness in the *current* state is by (project_key, name)
539
+ * — newer rows shadow older ones at read time (ORDER BY version DESC).
540
+ * This sidesteps the Deeplake UPDATE-coalescing quirk that bit the wiki
541
+ * worker.
542
+ */
543
+ async ensureSkillsTable(name) {
544
+ const safe = sqlIdent(name);
381
545
  const tables = await this.listTables();
382
- if (!tables.includes(name)) {
383
- log2(`table "${name}" not found, creating`);
384
- await this.query(`CREATE TABLE IF NOT EXISTS "${name}" (id TEXT NOT NULL DEFAULT '', path TEXT NOT NULL DEFAULT '', filename TEXT NOT NULL DEFAULT '', message JSONB, 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`);
385
- log2(`table "${name}" created`);
386
- if (!tables.includes(name))
387
- this._tablesCache = [...tables, name];
546
+ if (!tables.includes(safe)) {
547
+ log2(`table "${safe}" not found, creating`);
548
+ 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);
549
+ log2(`table "${safe}" created`);
550
+ if (!tables.includes(safe))
551
+ this._tablesCache = [...tables, safe];
388
552
  }
389
- await this.ensureLookupIndex(name, "path_creation_date", `("path", "creation_date")`);
553
+ await this.ensureLookupIndex(safe, "project_key_name", `("project_key", "name")`);
390
554
  }
391
555
  };
392
556
 
@@ -396,25 +560,306 @@ function buildSessionPath(config, sessionId) {
396
560
  return `/sessions/${config.userName}/${config.userName}_${config.orgName}_${workspace}_${sessionId}.jsonl`;
397
561
  }
398
562
 
399
- // dist/src/hooks/summary-state.js
400
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, writeSync, mkdirSync as mkdirSync2, renameSync, existsSync as existsSync3, unlinkSync, openSync, closeSync } from "node:fs";
563
+ // dist/src/embeddings/client.js
564
+ import { connect } from "node:net";
565
+ import { spawn } from "node:child_process";
566
+ import { openSync, closeSync, writeSync, unlinkSync, existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
401
567
  import { homedir as homedir3 } from "node:os";
402
568
  import { join as join4 } from "node:path";
569
+
570
+ // dist/src/embeddings/protocol.js
571
+ var DEFAULT_SOCKET_DIR = "/tmp";
572
+ var DEFAULT_IDLE_TIMEOUT_MS = 10 * 60 * 1e3;
573
+ var DEFAULT_CLIENT_TIMEOUT_MS = 2e3;
574
+ function socketPathFor(uid, dir = DEFAULT_SOCKET_DIR) {
575
+ return `${dir}/hivemind-embed-${uid}.sock`;
576
+ }
577
+ function pidPathFor(uid, dir = DEFAULT_SOCKET_DIR) {
578
+ return `${dir}/hivemind-embed-${uid}.pid`;
579
+ }
580
+
581
+ // dist/src/embeddings/client.js
582
+ var SHARED_DAEMON_PATH = join4(homedir3(), ".hivemind", "embed-deps", "embed-daemon.js");
583
+ var log3 = (m) => log("embed-client", m);
584
+ function getUid() {
585
+ const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
586
+ return uid !== void 0 ? String(uid) : process.env.USER ?? "default";
587
+ }
588
+ var EmbedClient = class {
589
+ socketPath;
590
+ pidPath;
591
+ timeoutMs;
592
+ daemonEntry;
593
+ autoSpawn;
594
+ spawnWaitMs;
595
+ nextId = 0;
596
+ constructor(opts = {}) {
597
+ const uid = getUid();
598
+ const dir = opts.socketDir ?? "/tmp";
599
+ this.socketPath = socketPathFor(uid, dir);
600
+ this.pidPath = pidPathFor(uid, dir);
601
+ this.timeoutMs = opts.timeoutMs ?? DEFAULT_CLIENT_TIMEOUT_MS;
602
+ this.daemonEntry = opts.daemonEntry ?? process.env.HIVEMIND_EMBED_DAEMON ?? (existsSync3(SHARED_DAEMON_PATH) ? SHARED_DAEMON_PATH : void 0);
603
+ this.autoSpawn = opts.autoSpawn ?? true;
604
+ this.spawnWaitMs = opts.spawnWaitMs ?? 5e3;
605
+ }
606
+ /**
607
+ * Returns an embedding vector, or null on timeout/failure. Hooks MUST treat
608
+ * null as "skip embedding column" — never block the write path on us.
609
+ *
610
+ * Fire-and-forget spawn on miss: if the daemon isn't up, this call returns
611
+ * null AND kicks off a background spawn. The next call finds a ready daemon.
612
+ */
613
+ async embed(text, kind = "document") {
614
+ let sock;
615
+ try {
616
+ sock = await this.connectOnce();
617
+ } catch {
618
+ if (this.autoSpawn)
619
+ this.trySpawnDaemon();
620
+ return null;
621
+ }
622
+ try {
623
+ const id = String(++this.nextId);
624
+ const req = { op: "embed", id, kind, text };
625
+ const resp = await this.sendAndWait(sock, req);
626
+ if (resp.error || !("embedding" in resp) || !resp.embedding) {
627
+ log3(`embed err: ${resp.error ?? "no embedding"}`);
628
+ return null;
629
+ }
630
+ return resp.embedding;
631
+ } catch (e) {
632
+ const err = e instanceof Error ? e.message : String(e);
633
+ log3(`embed failed: ${err}`);
634
+ return null;
635
+ } finally {
636
+ try {
637
+ sock.end();
638
+ } catch {
639
+ }
640
+ }
641
+ }
642
+ /**
643
+ * Wait up to spawnWaitMs for the daemon to accept connections, spawning if
644
+ * necessary. Meant for SessionStart / long-running batches — not the hot path.
645
+ */
646
+ async warmup() {
647
+ try {
648
+ const s = await this.connectOnce();
649
+ s.end();
650
+ return true;
651
+ } catch {
652
+ if (!this.autoSpawn)
653
+ return false;
654
+ this.trySpawnDaemon();
655
+ try {
656
+ const s = await this.waitForSocket();
657
+ s.end();
658
+ return true;
659
+ } catch {
660
+ return false;
661
+ }
662
+ }
663
+ }
664
+ connectOnce() {
665
+ return new Promise((resolve, reject) => {
666
+ const sock = connect(this.socketPath);
667
+ const to = setTimeout(() => {
668
+ sock.destroy();
669
+ reject(new Error("connect timeout"));
670
+ }, this.timeoutMs);
671
+ sock.once("connect", () => {
672
+ clearTimeout(to);
673
+ resolve(sock);
674
+ });
675
+ sock.once("error", (e) => {
676
+ clearTimeout(to);
677
+ reject(e);
678
+ });
679
+ });
680
+ }
681
+ trySpawnDaemon() {
682
+ let fd;
683
+ try {
684
+ fd = openSync(this.pidPath, "wx", 384);
685
+ writeSync(fd, String(process.pid));
686
+ } catch (e) {
687
+ if (this.isPidFileStale()) {
688
+ try {
689
+ unlinkSync(this.pidPath);
690
+ } catch {
691
+ }
692
+ try {
693
+ fd = openSync(this.pidPath, "wx", 384);
694
+ writeSync(fd, String(process.pid));
695
+ } catch {
696
+ return;
697
+ }
698
+ } else {
699
+ return;
700
+ }
701
+ }
702
+ if (!this.daemonEntry || !existsSync3(this.daemonEntry)) {
703
+ log3(`daemonEntry not configured or missing: ${this.daemonEntry}`);
704
+ try {
705
+ closeSync(fd);
706
+ unlinkSync(this.pidPath);
707
+ } catch {
708
+ }
709
+ return;
710
+ }
711
+ try {
712
+ const child = spawn(process.execPath, [this.daemonEntry], {
713
+ detached: true,
714
+ stdio: "ignore",
715
+ env: process.env
716
+ });
717
+ child.unref();
718
+ log3(`spawned daemon pid=${child.pid}`);
719
+ } finally {
720
+ closeSync(fd);
721
+ }
722
+ }
723
+ isPidFileStale() {
724
+ try {
725
+ const raw = readFileSync3(this.pidPath, "utf-8").trim();
726
+ const pid = Number(raw);
727
+ if (!pid || Number.isNaN(pid))
728
+ return true;
729
+ try {
730
+ process.kill(pid, 0);
731
+ return false;
732
+ } catch {
733
+ return true;
734
+ }
735
+ } catch {
736
+ return true;
737
+ }
738
+ }
739
+ async waitForSocket() {
740
+ const deadline = Date.now() + this.spawnWaitMs;
741
+ let delay = 30;
742
+ while (Date.now() < deadline) {
743
+ await sleep2(delay);
744
+ delay = Math.min(delay * 1.5, 300);
745
+ if (!existsSync3(this.socketPath))
746
+ continue;
747
+ try {
748
+ return await this.connectOnce();
749
+ } catch {
750
+ }
751
+ }
752
+ throw new Error("daemon did not become ready within spawnWaitMs");
753
+ }
754
+ sendAndWait(sock, req) {
755
+ return new Promise((resolve, reject) => {
756
+ let buf = "";
757
+ const to = setTimeout(() => {
758
+ sock.destroy();
759
+ reject(new Error("request timeout"));
760
+ }, this.timeoutMs);
761
+ sock.setEncoding("utf-8");
762
+ sock.on("data", (chunk) => {
763
+ buf += chunk;
764
+ const nl = buf.indexOf("\n");
765
+ if (nl === -1)
766
+ return;
767
+ const line = buf.slice(0, nl);
768
+ clearTimeout(to);
769
+ try {
770
+ resolve(JSON.parse(line));
771
+ } catch (e) {
772
+ reject(e);
773
+ }
774
+ });
775
+ sock.on("error", (e) => {
776
+ clearTimeout(to);
777
+ reject(e);
778
+ });
779
+ sock.on("end", () => {
780
+ clearTimeout(to);
781
+ reject(new Error("connection closed without response"));
782
+ });
783
+ sock.write(JSON.stringify(req) + "\n");
784
+ });
785
+ }
786
+ };
787
+ function sleep2(ms) {
788
+ return new Promise((r) => setTimeout(r, ms));
789
+ }
790
+
791
+ // dist/src/embeddings/sql.js
792
+ function embeddingSqlLiteral(vec) {
793
+ if (!vec || vec.length === 0)
794
+ return "NULL";
795
+ const parts = [];
796
+ for (const v of vec) {
797
+ if (!Number.isFinite(v))
798
+ return "NULL";
799
+ parts.push(String(v));
800
+ }
801
+ return `ARRAY[${parts.join(",")}]::float4[]`;
802
+ }
803
+
804
+ // dist/src/embeddings/disable.js
805
+ import { createRequire } from "node:module";
806
+ import { homedir as homedir4 } from "node:os";
807
+ import { join as join5 } from "node:path";
808
+ import { pathToFileURL } from "node:url";
809
+ var cachedStatus = null;
810
+ function defaultResolveTransformers() {
811
+ try {
812
+ createRequire(import.meta.url).resolve("@huggingface/transformers");
813
+ return;
814
+ } catch {
815
+ }
816
+ const sharedDir = join5(homedir4(), ".hivemind", "embed-deps");
817
+ createRequire(pathToFileURL(`${sharedDir}/`).href).resolve("@huggingface/transformers");
818
+ }
819
+ var _resolve = defaultResolveTransformers;
820
+ function detectStatus() {
821
+ if (process.env.HIVEMIND_EMBEDDINGS === "false")
822
+ return "env-disabled";
823
+ try {
824
+ _resolve();
825
+ return "enabled";
826
+ } catch {
827
+ return "no-transformers";
828
+ }
829
+ }
830
+ function embeddingsStatus() {
831
+ if (cachedStatus !== null)
832
+ return cachedStatus;
833
+ cachedStatus = detectStatus();
834
+ return cachedStatus;
835
+ }
836
+ function embeddingsDisabled() {
837
+ return embeddingsStatus() !== "enabled";
838
+ }
839
+
840
+ // dist/src/hooks/codex/capture.js
841
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
842
+ import { dirname as dirname2, join as join9 } from "node:path";
843
+
844
+ // dist/src/hooks/summary-state.js
845
+ 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";
846
+ import { homedir as homedir5 } from "node:os";
847
+ import { join as join6 } from "node:path";
403
848
  var dlog = (msg) => log("summary-state", msg);
404
- var STATE_DIR = join4(homedir3(), ".claude", "hooks", "summary-state");
849
+ var STATE_DIR = join6(homedir5(), ".claude", "hooks", "summary-state");
405
850
  var YIELD_BUF = new Int32Array(new SharedArrayBuffer(4));
406
851
  function statePath(sessionId) {
407
- return join4(STATE_DIR, `${sessionId}.json`);
852
+ return join6(STATE_DIR, `${sessionId}.json`);
408
853
  }
409
854
  function lockPath(sessionId) {
410
- return join4(STATE_DIR, `${sessionId}.lock`);
855
+ return join6(STATE_DIR, `${sessionId}.lock`);
411
856
  }
412
857
  function readState(sessionId) {
413
858
  const p = statePath(sessionId);
414
- if (!existsSync3(p))
859
+ if (!existsSync4(p))
415
860
  return null;
416
861
  try {
417
- return JSON.parse(readFileSync3(p, "utf-8"));
862
+ return JSON.parse(readFileSync4(p, "utf-8"));
418
863
  } catch {
419
864
  return null;
420
865
  }
@@ -433,14 +878,14 @@ function withRmwLock(sessionId, fn) {
433
878
  let fd = null;
434
879
  while (fd === null) {
435
880
  try {
436
- fd = openSync(rmwLock, "wx");
881
+ fd = openSync2(rmwLock, "wx");
437
882
  } catch (e) {
438
883
  if (e.code !== "EEXIST")
439
884
  throw e;
440
885
  if (Date.now() > deadline) {
441
886
  dlog(`rmw lock deadline exceeded for ${sessionId}, reclaiming stale lock`);
442
887
  try {
443
- unlinkSync(rmwLock);
888
+ unlinkSync2(rmwLock);
444
889
  } catch (unlinkErr) {
445
890
  dlog(`stale rmw lock unlink failed for ${sessionId}: ${unlinkErr.message}`);
446
891
  }
@@ -452,9 +897,9 @@ function withRmwLock(sessionId, fn) {
452
897
  try {
453
898
  return fn();
454
899
  } finally {
455
- closeSync(fd);
900
+ closeSync2(fd);
456
901
  try {
457
- unlinkSync(rmwLock);
902
+ unlinkSync2(rmwLock);
458
903
  } catch (unlinkErr) {
459
904
  dlog(`rmw lock cleanup failed for ${sessionId}: ${unlinkErr.message}`);
460
905
  }
@@ -491,27 +936,27 @@ function shouldTrigger(state, cfg, now = Date.now()) {
491
936
  function tryAcquireLock(sessionId, maxAgeMs = 10 * 60 * 1e3) {
492
937
  mkdirSync2(STATE_DIR, { recursive: true });
493
938
  const p = lockPath(sessionId);
494
- if (existsSync3(p)) {
939
+ if (existsSync4(p)) {
495
940
  try {
496
- const ageMs = Date.now() - parseInt(readFileSync3(p, "utf-8"), 10);
941
+ const ageMs = Date.now() - parseInt(readFileSync4(p, "utf-8"), 10);
497
942
  if (Number.isFinite(ageMs) && ageMs < maxAgeMs)
498
943
  return false;
499
944
  } catch (readErr) {
500
945
  dlog(`lock file unreadable for ${sessionId}, treating as stale: ${readErr.message}`);
501
946
  }
502
947
  try {
503
- unlinkSync(p);
948
+ unlinkSync2(p);
504
949
  } catch (unlinkErr) {
505
950
  dlog(`could not unlink stale lock for ${sessionId}: ${unlinkErr.message}`);
506
951
  return false;
507
952
  }
508
953
  }
509
954
  try {
510
- const fd = openSync(p, "wx");
955
+ const fd = openSync2(p, "wx");
511
956
  try {
512
- writeSync(fd, String(Date.now()));
957
+ writeSync2(fd, String(Date.now()));
513
958
  } finally {
514
- closeSync(fd);
959
+ closeSync2(fd);
515
960
  }
516
961
  return true;
517
962
  } catch (e) {
@@ -522,7 +967,7 @@ function tryAcquireLock(sessionId, maxAgeMs = 10 * 60 * 1e3) {
522
967
  }
523
968
  function releaseLock(sessionId) {
524
969
  try {
525
- unlinkSync(lockPath(sessionId));
970
+ unlinkSync2(lockPath(sessionId));
526
971
  } catch (e) {
527
972
  if (e?.code !== "ENOENT") {
528
973
  dlog(`releaseLock unlink failed for ${sessionId}: ${e.message}`);
@@ -531,17 +976,17 @@ function releaseLock(sessionId) {
531
976
  }
532
977
 
533
978
  // dist/src/hooks/codex/spawn-wiki-worker.js
534
- import { spawn, execSync } from "node:child_process";
979
+ import { spawn as spawn2, execSync } from "node:child_process";
535
980
  import { fileURLToPath } from "node:url";
536
- import { dirname, join as join6 } from "node:path";
981
+ import { dirname, join as join8 } from "node:path";
537
982
  import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync4 } from "node:fs";
538
- import { homedir as homedir4, tmpdir as tmpdir2 } from "node:os";
983
+ import { homedir as homedir6, tmpdir as tmpdir2 } from "node:os";
539
984
 
540
985
  // dist/src/utils/wiki-log.js
541
986
  import { mkdirSync as mkdirSync3, appendFileSync as appendFileSync2 } from "node:fs";
542
- import { join as join5 } from "node:path";
987
+ import { join as join7 } from "node:path";
543
988
  function makeWikiLogger(hooksDir, filename = "deeplake-wiki.log") {
544
- const path = join5(hooksDir, filename);
989
+ const path = join7(hooksDir, filename);
545
990
  return {
546
991
  path,
547
992
  log(msg) {
@@ -556,8 +1001,8 @@ function makeWikiLogger(hooksDir, filename = "deeplake-wiki.log") {
556
1001
  }
557
1002
 
558
1003
  // dist/src/hooks/codex/spawn-wiki-worker.js
559
- var HOME = homedir4();
560
- var wikiLogger = makeWikiLogger(join6(HOME, ".codex", "hooks"));
1004
+ var HOME = homedir6();
1005
+ var wikiLogger = makeWikiLogger(join8(HOME, ".codex", "hooks"));
561
1006
  var WIKI_LOG = wikiLogger.path;
562
1007
  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
1008
 
@@ -619,9 +1064,9 @@ function findCodexBin() {
619
1064
  function spawnCodexWikiWorker(opts) {
620
1065
  const { config, sessionId, cwd, bundleDir, reason } = opts;
621
1066
  const projectName = cwd.split("/").pop() || "unknown";
622
- const tmpDir = join6(tmpdir2(), `deeplake-wiki-${sessionId}-${Date.now()}`);
1067
+ const tmpDir = join8(tmpdir2(), `deeplake-wiki-${sessionId}-${Date.now()}`);
623
1068
  mkdirSync4(tmpDir, { recursive: true });
624
- const configFile = join6(tmpDir, "config.json");
1069
+ const configFile = join8(tmpDir, "config.json");
625
1070
  writeFileSync3(configFile, JSON.stringify({
626
1071
  apiUrl: config.apiUrl,
627
1072
  token: config.token,
@@ -635,12 +1080,12 @@ function spawnCodexWikiWorker(opts) {
635
1080
  tmpDir,
636
1081
  codexBin: findCodexBin(),
637
1082
  wikiLog: WIKI_LOG,
638
- hooksDir: join6(HOME, ".codex", "hooks"),
1083
+ hooksDir: join8(HOME, ".codex", "hooks"),
639
1084
  promptTemplate: WIKI_PROMPT_TEMPLATE
640
1085
  }));
641
1086
  wikiLog(`${reason}: spawning summary worker for ${sessionId}`);
642
- const workerPath = join6(bundleDir, "wiki-worker.js");
643
- spawn("nohup", ["node", workerPath, configFile], {
1087
+ const workerPath = join8(bundleDir, "wiki-worker.js");
1088
+ spawn2("nohup", ["node", workerPath, configFile], {
644
1089
  detached: true,
645
1090
  stdio: ["ignore", "ignore", "ignore"]
646
1091
  }).unref();
@@ -651,7 +1096,10 @@ function bundleDirFromImportMeta(importMetaUrl) {
651
1096
  }
652
1097
 
653
1098
  // dist/src/hooks/codex/capture.js
654
- var log3 = (msg) => log("codex-capture", msg);
1099
+ var log4 = (msg) => log("codex-capture", msg);
1100
+ function resolveEmbedDaemonPath() {
1101
+ return join9(dirname2(fileURLToPath2(import.meta.url)), "embeddings", "embed-daemon.js");
1102
+ }
655
1103
  var CAPTURE = process.env.HIVEMIND_CAPTURE !== "false";
656
1104
  async function main() {
657
1105
  if (!CAPTURE)
@@ -659,7 +1107,7 @@ async function main() {
659
1107
  const input = await readStdin();
660
1108
  const config = loadConfig();
661
1109
  if (!config) {
662
- log3("no config");
1110
+ log4("no config");
663
1111
  return;
664
1112
  }
665
1113
  const sessionsTable = config.sessionsTableName;
@@ -676,7 +1124,7 @@ async function main() {
676
1124
  };
677
1125
  let entry;
678
1126
  if (input.hook_event_name === "UserPromptSubmit" && input.prompt !== void 0) {
679
- log3(`user session=${input.session_id}`);
1127
+ log4(`user session=${input.session_id}`);
680
1128
  entry = {
681
1129
  id: crypto.randomUUID(),
682
1130
  ...meta,
@@ -684,7 +1132,7 @@ async function main() {
684
1132
  content: input.prompt
685
1133
  };
686
1134
  } else if (input.hook_event_name === "PostToolUse" && input.tool_name !== void 0) {
687
- log3(`tool=${input.tool_name} session=${input.session_id}`);
1135
+ log4(`tool=${input.tool_name} session=${input.session_id}`);
688
1136
  entry = {
689
1137
  id: crypto.randomUUID(),
690
1138
  ...meta,
@@ -695,28 +1143,30 @@ async function main() {
695
1143
  tool_response: JSON.stringify(input.tool_response)
696
1144
  };
697
1145
  } else {
698
- log3(`unknown event: ${input.hook_event_name}, skipping`);
1146
+ log4(`unknown event: ${input.hook_event_name}, skipping`);
699
1147
  return;
700
1148
  }
701
1149
  const sessionPath = buildSessionPath(config, input.session_id);
702
1150
  const line = JSON.stringify(entry);
703
- log3(`writing to ${sessionPath}`);
1151
+ log4(`writing to ${sessionPath}`);
704
1152
  const projectName = (input.cwd ?? "").split("/").pop() || "unknown";
705
1153
  const filename = sessionPath.split("/").pop() ?? "";
706
1154
  const jsonForSql = line.replace(/'/g, "''");
707
- const insertSql = `INSERT INTO "${sessionsTable}" (id, path, filename, message, author, size_bytes, project, description, agent, creation_date, last_update_date) VALUES ('${crypto.randomUUID()}', '${sqlStr(sessionPath)}', '${sqlStr(filename)}', '${jsonForSql}'::jsonb, '${sqlStr(config.userName)}', ${Buffer.byteLength(line, "utf-8")}, '${sqlStr(projectName)}', '${sqlStr(input.hook_event_name ?? "")}', 'codex', '${ts}', '${ts}')`;
1155
+ const embedding = embeddingsDisabled() ? null : await new EmbedClient({ daemonEntry: resolveEmbedDaemonPath() }).embed(line, "document");
1156
+ const embeddingSql = embeddingSqlLiteral(embedding);
1157
+ 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
1158
  try {
709
1159
  await api.query(insertSql);
710
1160
  } catch (e) {
711
1161
  if (e.message?.includes("permission denied") || e.message?.includes("does not exist")) {
712
- log3("table missing, creating and retrying");
1162
+ log4("table missing, creating and retrying");
713
1163
  await api.ensureSessionsTable(sessionsTable);
714
1164
  await api.query(insertSql);
715
1165
  } else {
716
1166
  throw e;
717
1167
  }
718
1168
  }
719
- log3("capture ok");
1169
+ log4("capture ok");
720
1170
  maybeTriggerPeriodicSummary(input.session_id, input.cwd ?? "", config);
721
1171
  }
722
1172
  function maybeTriggerPeriodicSummary(sessionId, cwd, config) {
@@ -728,7 +1178,7 @@ function maybeTriggerPeriodicSummary(sessionId, cwd, config) {
728
1178
  if (!shouldTrigger(state, cfg))
729
1179
  return;
730
1180
  if (!tryAcquireLock(sessionId)) {
731
- log3(`periodic trigger suppressed (lock held) session=${sessionId}`);
1181
+ log4(`periodic trigger suppressed (lock held) session=${sessionId}`);
732
1182
  return;
733
1183
  }
734
1184
  wikiLog(`Periodic: threshold hit (total=${state.totalCount}, since=${state.totalCount - state.lastSummaryCount}, N=${cfg.everyNMessages}, hours=${cfg.everyHours})`);
@@ -741,19 +1191,19 @@ function maybeTriggerPeriodicSummary(sessionId, cwd, config) {
741
1191
  reason: "Periodic"
742
1192
  });
743
1193
  } catch (e) {
744
- log3(`periodic spawn failed: ${e.message}`);
1194
+ log4(`periodic spawn failed: ${e.message}`);
745
1195
  try {
746
1196
  releaseLock(sessionId);
747
1197
  } catch (releaseErr) {
748
- log3(`releaseLock after periodic spawn failure also failed: ${releaseErr.message}`);
1198
+ log4(`releaseLock after periodic spawn failure also failed: ${releaseErr.message}`);
749
1199
  }
750
1200
  throw e;
751
1201
  }
752
1202
  } catch (e) {
753
- log3(`periodic trigger error: ${e.message}`);
1203
+ log4(`periodic trigger error: ${e.message}`);
754
1204
  }
755
1205
  }
756
1206
  main().catch((e) => {
757
- log3(`fatal: ${e.message}`);
1207
+ log4(`fatal: ${e.message}`);
758
1208
  process.exit(0);
759
1209
  });