@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.
Files changed (41) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +158 -51
  4. package/bundle/cli.js +4103 -282
  5. package/codex/bundle/capture.js +510 -90
  6. package/codex/bundle/commands/auth-login.js +219 -72
  7. package/codex/bundle/embeddings/embed-daemon.js +243 -0
  8. package/codex/bundle/pre-tool-use.js +713 -108
  9. package/codex/bundle/session-start-setup.js +209 -58
  10. package/codex/bundle/session-start.js +40 -11
  11. package/codex/bundle/shell/deeplake-shell.js +679 -112
  12. package/codex/bundle/stop.js +477 -59
  13. package/codex/bundle/wiki-worker.js +312 -11
  14. package/cursor/bundle/capture.js +768 -57
  15. package/cursor/bundle/commands/auth-login.js +219 -72
  16. package/cursor/bundle/embeddings/embed-daemon.js +243 -0
  17. package/cursor/bundle/pre-tool-use.js +1684 -0
  18. package/cursor/bundle/session-end.js +223 -2
  19. package/cursor/bundle/session-start.js +209 -57
  20. package/cursor/bundle/shell/deeplake-shell.js +679 -112
  21. package/cursor/bundle/wiki-worker.js +571 -0
  22. package/hermes/bundle/capture.js +1194 -0
  23. package/hermes/bundle/commands/auth-login.js +1009 -0
  24. package/hermes/bundle/embeddings/embed-daemon.js +243 -0
  25. package/hermes/bundle/package.json +1 -0
  26. package/hermes/bundle/pre-tool-use.js +1681 -0
  27. package/hermes/bundle/session-end.js +265 -0
  28. package/hermes/bundle/session-start.js +655 -0
  29. package/hermes/bundle/shell/deeplake-shell.js +69905 -0
  30. package/hermes/bundle/wiki-worker.js +572 -0
  31. package/mcp/bundle/server.js +289 -69
  32. package/openclaw/dist/chunks/auth-creds-AEKS6D3P.js +14 -0
  33. package/openclaw/dist/chunks/chunk-SRCBBT4H.js +37 -0
  34. package/openclaw/dist/chunks/config-G23NI5TV.js +33 -0
  35. package/openclaw/dist/chunks/index-marker-store-PGT5CW6T.js +33 -0
  36. package/openclaw/dist/chunks/setup-config-C35UK4LP.js +114 -0
  37. package/openclaw/dist/index.js +752 -702
  38. package/openclaw/openclaw.plugin.json +1 -1
  39. package/openclaw/package.json +1 -1
  40. package/package.json +7 -3
  41. package/pi/extension-source/hivemind.ts +807 -0
@@ -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
- if (attempt < MAX_RETRIES && (RETRYABLE_CODES.has(resp.status) || retryable403)) {
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 = "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}'`;
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
- if (this.hasFreshLookupIndexMarker(table, suffix))
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
- this.markLookupIndexReady(table, suffix);
359
+ markers.writeIndexMarker(markerPath);
319
360
  } catch (e) {
320
361
  if (isDuplicateIndexError(e)) {
321
- this.markLookupIndexReady(table, suffix);
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.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`);
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.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`);
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/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";
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 = join4(homedir3(), ".claude", "hooks", "summary-state");
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 join4(STATE_DIR, `${sessionId}.json`);
822
+ return join6(STATE_DIR, `${sessionId}.json`);
408
823
  }
409
824
  function lockPath(sessionId) {
410
- return join4(STATE_DIR, `${sessionId}.lock`);
825
+ return join6(STATE_DIR, `${sessionId}.lock`);
411
826
  }
412
827
  function readState(sessionId) {
413
828
  const p = statePath(sessionId);
414
- if (!existsSync3(p))
829
+ if (!existsSync4(p))
415
830
  return null;
416
831
  try {
417
- return JSON.parse(readFileSync3(p, "utf-8"));
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 = openSync(rmwLock, "wx");
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
- unlinkSync(rmwLock);
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
- closeSync(fd);
870
+ closeSync2(fd);
456
871
  try {
457
- unlinkSync(rmwLock);
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 (existsSync3(p)) {
909
+ if (existsSync4(p)) {
495
910
  try {
496
- const ageMs = Date.now() - parseInt(readFileSync3(p, "utf-8"), 10);
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
- unlinkSync(p);
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 = openSync(p, "wx");
925
+ const fd = openSync2(p, "wx");
511
926
  try {
512
- writeSync(fd, String(Date.now()));
927
+ writeSync2(fd, String(Date.now()));
513
928
  } finally {
514
- closeSync(fd);
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
- unlinkSync(lockPath(sessionId));
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 join6 } from "node:path";
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 homedir4, tmpdir as tmpdir2 } from "node:os";
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 join5 } from "node:path";
957
+ import { join as join7 } from "node:path";
543
958
  function makeWikiLogger(hooksDir, filename = "deeplake-wiki.log") {
544
- const path = join5(hooksDir, filename);
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 = homedir4();
560
- var wikiLogger = makeWikiLogger(join6(HOME, ".codex", "hooks"));
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 = join6(tmpdir2(), `deeplake-wiki-${sessionId}-${Date.now()}`);
1037
+ const tmpDir = join8(tmpdir2(), `deeplake-wiki-${sessionId}-${Date.now()}`);
623
1038
  mkdirSync4(tmpDir, { recursive: true });
624
- const configFile = join6(tmpDir, "config.json");
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: join6(HOME, ".codex", "hooks"),
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 = join6(bundleDir, "wiki-worker.js");
643
- spawn("nohup", ["node", workerPath, configFile], {
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 log3 = (msg) => log("codex-capture", msg);
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
- log3("no config");
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
- log3(`user session=${input.session_id}`);
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
- log3(`tool=${input.tool_name} session=${input.session_id}`);
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
- log3(`unknown event: ${input.hook_event_name}, skipping`);
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
- log3(`writing to ${sessionPath}`);
1121
+ log4(`writing to ${sessionPath}`);
704
1122
  const projectName = (input.cwd ?? "").split("/").pop() || "unknown";
705
1123
  const filename = sessionPath.split("/").pop() ?? "";
706
- const jsonForSql = sqlStr(line);
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}')`;
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
- log3("table missing, creating and retrying");
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
- log3("capture ok");
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
- log3(`periodic trigger suppressed (lock held) session=${sessionId}`);
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
- log3(`periodic spawn failed: ${e.message}`);
1164
+ log4(`periodic spawn failed: ${e.message}`);
745
1165
  try {
746
1166
  releaseLock(sessionId);
747
1167
  } catch (releaseErr) {
748
- log3(`releaseLock after periodic spawn failure also failed: ${releaseErr.message}`);
1168
+ log4(`releaseLock after periodic spawn failure also failed: ${releaseErr.message}`);
749
1169
  }
750
1170
  throw e;
751
1171
  }
752
1172
  } catch (e) {
753
- log3(`periodic trigger error: ${e.message}`);
1173
+ log4(`periodic trigger error: ${e.message}`);
754
1174
  }
755
1175
  }
756
1176
  main().catch((e) => {
757
- log3(`fatal: ${e.message}`);
1177
+ log4(`fatal: ${e.message}`);
758
1178
  process.exit(0);
759
1179
  });