@askexenow/exe-os 0.9.7 → 0.9.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 (101) hide show
  1. package/dist/bin/backfill-conversations.js +953 -105
  2. package/dist/bin/backfill-responses.js +952 -104
  3. package/dist/bin/backfill-vectors.js +956 -108
  4. package/dist/bin/cleanup-stale-review-tasks.js +802 -58
  5. package/dist/bin/cli.js +2292 -1070
  6. package/dist/bin/exe-agent-config.js +157 -101
  7. package/dist/bin/exe-agent.js +55 -29
  8. package/dist/bin/exe-assign.js +940 -92
  9. package/dist/bin/exe-boot.js +1424 -442
  10. package/dist/bin/exe-call.js +240 -141
  11. package/dist/bin/exe-cloud.js +198 -70
  12. package/dist/bin/exe-dispatch.js +951 -192
  13. package/dist/bin/exe-doctor.js +791 -51
  14. package/dist/bin/exe-export-behaviors.js +790 -42
  15. package/dist/bin/exe-forget.js +771 -31
  16. package/dist/bin/exe-gateway.js +1592 -521
  17. package/dist/bin/exe-heartbeat.js +850 -109
  18. package/dist/bin/exe-kill.js +783 -35
  19. package/dist/bin/exe-launch-agent.js +1030 -107
  20. package/dist/bin/exe-link.js +916 -110
  21. package/dist/bin/exe-new-employee.js +526 -217
  22. package/dist/bin/exe-pending-messages.js +1046 -62
  23. package/dist/bin/exe-pending-notifications.js +1318 -111
  24. package/dist/bin/exe-pending-reviews.js +1040 -72
  25. package/dist/bin/exe-rename.js +772 -59
  26. package/dist/bin/exe-review.js +772 -32
  27. package/dist/bin/exe-search.js +982 -128
  28. package/dist/bin/exe-session-cleanup.js +1180 -306
  29. package/dist/bin/exe-settings.js +185 -105
  30. package/dist/bin/exe-start-codex.js +886 -132
  31. package/dist/bin/exe-start-opencode.js +873 -119
  32. package/dist/bin/exe-status.js +803 -59
  33. package/dist/bin/exe-team.js +772 -32
  34. package/dist/bin/git-sweep.js +1046 -223
  35. package/dist/bin/graph-backfill.js +779 -31
  36. package/dist/bin/graph-export.js +785 -37
  37. package/dist/bin/install.js +632 -200
  38. package/dist/bin/scan-tasks.js +1055 -232
  39. package/dist/bin/setup.js +1419 -320
  40. package/dist/bin/shard-migrate.js +783 -35
  41. package/dist/bin/update.js +138 -49
  42. package/dist/bin/wiki-sync.js +782 -34
  43. package/dist/gateway/index.js +1444 -449
  44. package/dist/hooks/bug-report-worker.js +1141 -269
  45. package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
  46. package/dist/hooks/commit-complete.js +1044 -221
  47. package/dist/hooks/error-recall.js +989 -135
  48. package/dist/hooks/exe-heartbeat-hook.js +99 -75
  49. package/dist/hooks/ingest-worker.js +4176 -3226
  50. package/dist/hooks/ingest.js +920 -168
  51. package/dist/hooks/instructions-loaded.js +874 -70
  52. package/dist/hooks/notification.js +860 -56
  53. package/dist/hooks/post-compact.js +881 -73
  54. package/dist/hooks/pre-compact.js +1050 -227
  55. package/dist/hooks/pre-tool-use.js +1084 -159
  56. package/dist/hooks/prompt-ingest-worker.js +1089 -164
  57. package/dist/hooks/prompt-submit.js +1469 -515
  58. package/dist/hooks/response-ingest-worker.js +1104 -179
  59. package/dist/hooks/session-end.js +1085 -251
  60. package/dist/hooks/session-start.js +1241 -231
  61. package/dist/hooks/stop.js +935 -109
  62. package/dist/hooks/subagent-stop.js +881 -73
  63. package/dist/hooks/summary-worker.js +1323 -307
  64. package/dist/index.js +1449 -452
  65. package/dist/lib/agent-config.js +28 -6
  66. package/dist/lib/cloud-sync.js +909 -115
  67. package/dist/lib/config.js +30 -10
  68. package/dist/lib/consolidation.js +42 -9
  69. package/dist/lib/database.js +739 -33
  70. package/dist/lib/db-daemon-client.js +73 -19
  71. package/dist/lib/db.js +2359 -0
  72. package/dist/lib/device-registry.js +760 -47
  73. package/dist/lib/embedder.js +201 -73
  74. package/dist/lib/employee-templates.js +30 -4
  75. package/dist/lib/employees.js +290 -86
  76. package/dist/lib/exe-daemon-client.js +187 -83
  77. package/dist/lib/exe-daemon.js +1696 -616
  78. package/dist/lib/hybrid-search.js +982 -128
  79. package/dist/lib/identity.js +43 -13
  80. package/dist/lib/license.js +133 -48
  81. package/dist/lib/messaging.js +167 -80
  82. package/dist/lib/reminders.js +35 -5
  83. package/dist/lib/schedules.js +772 -32
  84. package/dist/lib/skill-learning.js +54 -7
  85. package/dist/lib/store.js +779 -31
  86. package/dist/lib/task-router.js +94 -73
  87. package/dist/lib/tasks.js +298 -225
  88. package/dist/lib/tmux-routing.js +246 -172
  89. package/dist/lib/token-spend.js +52 -14
  90. package/dist/mcp/server.js +2893 -850
  91. package/dist/mcp/tools/complete-reminder.js +35 -5
  92. package/dist/mcp/tools/create-reminder.js +35 -5
  93. package/dist/mcp/tools/create-task.js +507 -323
  94. package/dist/mcp/tools/deactivate-behavior.js +40 -10
  95. package/dist/mcp/tools/list-reminders.js +35 -5
  96. package/dist/mcp/tools/list-tasks.js +277 -104
  97. package/dist/mcp/tools/send-message.js +129 -56
  98. package/dist/mcp/tools/update-task.js +1864 -188
  99. package/dist/runtime/index.js +1083 -259
  100. package/dist/tui/App.js +1501 -434
  101. package/package.json +3 -2
@@ -64,9 +64,47 @@ var init_db_retry = __esm({
64
64
  }
65
65
  });
66
66
 
67
+ // src/lib/secure-files.ts
68
+ import { chmodSync, existsSync, mkdirSync } from "fs";
69
+ import { chmod, mkdir } from "fs/promises";
70
+ async function ensurePrivateDir(dirPath) {
71
+ await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
72
+ try {
73
+ await chmod(dirPath, PRIVATE_DIR_MODE);
74
+ } catch {
75
+ }
76
+ }
77
+ function ensurePrivateDirSync(dirPath) {
78
+ mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
79
+ try {
80
+ chmodSync(dirPath, PRIVATE_DIR_MODE);
81
+ } catch {
82
+ }
83
+ }
84
+ async function enforcePrivateFile(filePath) {
85
+ try {
86
+ await chmod(filePath, PRIVATE_FILE_MODE);
87
+ } catch {
88
+ }
89
+ }
90
+ function enforcePrivateFileSync(filePath) {
91
+ try {
92
+ if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
93
+ } catch {
94
+ }
95
+ }
96
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
97
+ var init_secure_files = __esm({
98
+ "src/lib/secure-files.ts"() {
99
+ "use strict";
100
+ PRIVATE_DIR_MODE = 448;
101
+ PRIVATE_FILE_MODE = 384;
102
+ }
103
+ });
104
+
67
105
  // src/lib/config.ts
68
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
69
- import { readFileSync, existsSync, renameSync } from "fs";
106
+ import { readFile, writeFile } from "fs/promises";
107
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
70
108
  import path from "path";
71
109
  import os from "os";
72
110
  function resolveDataDir() {
@@ -74,7 +112,7 @@ function resolveDataDir() {
74
112
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
75
113
  const newDir = path.join(os.homedir(), ".exe-os");
76
114
  const legacyDir = path.join(os.homedir(), ".exe-mem");
77
- if (!existsSync(newDir) && existsSync(legacyDir)) {
115
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
78
116
  try {
79
117
  renameSync(legacyDir, newDir);
80
118
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -137,9 +175,9 @@ function normalizeAutoUpdate(raw) {
137
175
  }
138
176
  async function loadConfig() {
139
177
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
140
- await mkdir(dir, { recursive: true });
178
+ await ensurePrivateDir(dir);
141
179
  const configPath = path.join(dir, "config.json");
142
- if (!existsSync(configPath)) {
180
+ if (!existsSync2(configPath)) {
143
181
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
144
182
  }
145
183
  const raw = await readFile(configPath, "utf-8");
@@ -152,6 +190,7 @@ async function loadConfig() {
152
190
  `);
153
191
  try {
154
192
  await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
193
+ await enforcePrivateFile(configPath);
155
194
  } catch {
156
195
  }
157
196
  }
@@ -171,6 +210,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
171
210
  var init_config = __esm({
172
211
  "src/lib/config.ts"() {
173
212
  "use strict";
213
+ init_secure_files();
174
214
  EXE_AI_DIR = resolveDataDir();
175
215
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
176
216
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
@@ -249,7 +289,7 @@ var init_config = __esm({
249
289
 
250
290
  // src/lib/employees.ts
251
291
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
252
- import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
292
+ import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
253
293
  import { execSync } from "child_process";
254
294
  import path2 from "path";
255
295
  import os2 from "os";
@@ -270,14 +310,14 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
270
310
  return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
271
311
  }
272
312
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
273
- if (!existsSync2(employeesPath)) return [];
313
+ if (!existsSync3(employeesPath)) return [];
274
314
  try {
275
315
  return JSON.parse(readFileSync2(employeesPath, "utf-8"));
276
316
  } catch {
277
317
  return [];
278
318
  }
279
319
  }
280
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE;
320
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, IDENTITY_DIR;
281
321
  var init_employees = __esm({
282
322
  "src/lib/employees.ts"() {
283
323
  "use strict";
@@ -285,12 +325,609 @@ var init_employees = __esm({
285
325
  EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
286
326
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
287
327
  COORDINATOR_ROLE = "COO";
328
+ IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
329
+ }
330
+ });
331
+
332
+ // src/lib/database-adapter.ts
333
+ import os3 from "os";
334
+ import path3 from "path";
335
+ import { createRequire } from "module";
336
+ import { pathToFileURL } from "url";
337
+ function quotedIdentifier(identifier) {
338
+ return `"${identifier.replace(/"/g, '""')}"`;
339
+ }
340
+ function unqualifiedTableName(name) {
341
+ const raw = name.trim().replace(/^"|"$/g, "");
342
+ const parts = raw.split(".");
343
+ return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
344
+ }
345
+ function stripTrailingSemicolon(sql) {
346
+ return sql.trim().replace(/;+\s*$/u, "");
347
+ }
348
+ function appendClause(sql, clause) {
349
+ const trimmed = stripTrailingSemicolon(sql);
350
+ const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
351
+ if (!returningMatch) {
352
+ return `${trimmed}${clause}`;
353
+ }
354
+ const idx = returningMatch.index;
355
+ return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
356
+ }
357
+ function normalizeStatement(stmt) {
358
+ if (typeof stmt === "string") {
359
+ return { kind: "positional", sql: stmt, args: [] };
360
+ }
361
+ const sql = stmt.sql;
362
+ if (Array.isArray(stmt.args) || stmt.args === void 0) {
363
+ return { kind: "positional", sql, args: stmt.args ?? [] };
364
+ }
365
+ return { kind: "named", sql, args: stmt.args };
366
+ }
367
+ function rewriteBooleanLiterals(sql) {
368
+ let out = sql;
369
+ for (const column of BOOLEAN_COLUMN_NAMES) {
370
+ const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
371
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
372
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
373
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
374
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
375
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
376
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
377
+ }
378
+ return out;
379
+ }
380
+ function rewriteInsertOrIgnore(sql) {
381
+ if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
382
+ return sql;
383
+ }
384
+ const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
385
+ return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
386
+ }
387
+ function rewriteInsertOrReplace(sql) {
388
+ const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
389
+ if (!match) {
390
+ return sql;
391
+ }
392
+ const rawTable = match[1];
393
+ const rawColumns = match[2];
394
+ const remainder = match[3];
395
+ const tableName = unqualifiedTableName(rawTable);
396
+ const conflictKeys = UPSERT_KEYS[tableName];
397
+ if (!conflictKeys?.length) {
398
+ return sql;
399
+ }
400
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
401
+ const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
402
+ const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
403
+ const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
404
+ return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
405
+ }
406
+ function rewriteSql(sql) {
407
+ let out = sql;
408
+ out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
409
+ out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
410
+ out = rewriteBooleanLiterals(out);
411
+ out = rewriteInsertOrReplace(out);
412
+ out = rewriteInsertOrIgnore(out);
413
+ return stripTrailingSemicolon(out);
414
+ }
415
+ function toBoolean(value) {
416
+ if (value === null || value === void 0) return value;
417
+ if (typeof value === "boolean") return value;
418
+ if (typeof value === "number") return value !== 0;
419
+ if (typeof value === "bigint") return value !== 0n;
420
+ if (typeof value === "string") {
421
+ const normalized = value.trim().toLowerCase();
422
+ if (normalized === "0" || normalized === "false") return false;
423
+ if (normalized === "1" || normalized === "true") return true;
424
+ }
425
+ return Boolean(value);
426
+ }
427
+ function countQuestionMarks(sql, end) {
428
+ let count = 0;
429
+ let inSingle = false;
430
+ let inDouble = false;
431
+ let inLineComment = false;
432
+ let inBlockComment = false;
433
+ for (let i = 0; i < end; i++) {
434
+ const ch = sql[i];
435
+ const next = sql[i + 1];
436
+ if (inLineComment) {
437
+ if (ch === "\n") inLineComment = false;
438
+ continue;
439
+ }
440
+ if (inBlockComment) {
441
+ if (ch === "*" && next === "/") {
442
+ inBlockComment = false;
443
+ i += 1;
444
+ }
445
+ continue;
446
+ }
447
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
448
+ inLineComment = true;
449
+ i += 1;
450
+ continue;
451
+ }
452
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
453
+ inBlockComment = true;
454
+ i += 1;
455
+ continue;
456
+ }
457
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
458
+ inSingle = !inSingle;
459
+ continue;
460
+ }
461
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
462
+ inDouble = !inDouble;
463
+ continue;
464
+ }
465
+ if (!inSingle && !inDouble && ch === "?") {
466
+ count += 1;
467
+ }
468
+ }
469
+ return count;
470
+ }
471
+ function findBooleanPlaceholderIndexes(sql) {
472
+ const indexes = /* @__PURE__ */ new Set();
473
+ for (const column of BOOLEAN_COLUMN_NAMES) {
474
+ const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
475
+ for (const match of sql.matchAll(pattern)) {
476
+ const matchText = match[0];
477
+ const qIndex = match.index + matchText.lastIndexOf("?");
478
+ indexes.add(countQuestionMarks(sql, qIndex + 1));
479
+ }
480
+ }
481
+ return indexes;
482
+ }
483
+ function coerceInsertBooleanArgs(sql, args) {
484
+ const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
485
+ if (!match) return;
486
+ const rawTable = match[1];
487
+ const rawColumns = match[2];
488
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
489
+ if (!boolColumns?.size) return;
490
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
491
+ for (const [index, column] of columns.entries()) {
492
+ if (boolColumns.has(column) && index < args.length) {
493
+ args[index] = toBoolean(args[index]);
494
+ }
495
+ }
496
+ }
497
+ function coerceUpdateBooleanArgs(sql, args) {
498
+ const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
499
+ if (!match) return;
500
+ const rawTable = match[1];
501
+ const setClause = match[2];
502
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
503
+ if (!boolColumns?.size) return;
504
+ const assignments = setClause.split(",");
505
+ let placeholderIndex = 0;
506
+ for (const assignment of assignments) {
507
+ if (!assignment.includes("?")) continue;
508
+ placeholderIndex += 1;
509
+ const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
510
+ if (colMatch && boolColumns.has(colMatch[1])) {
511
+ args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
512
+ }
513
+ }
514
+ }
515
+ function coerceBooleanArgs(sql, args) {
516
+ const nextArgs = [...args];
517
+ coerceInsertBooleanArgs(sql, nextArgs);
518
+ coerceUpdateBooleanArgs(sql, nextArgs);
519
+ const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
520
+ for (const index of placeholderIndexes) {
521
+ if (index > 0 && index <= nextArgs.length) {
522
+ nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
523
+ }
524
+ }
525
+ return nextArgs;
526
+ }
527
+ function convertQuestionMarksToDollarParams(sql) {
528
+ let out = "";
529
+ let placeholder = 0;
530
+ let inSingle = false;
531
+ let inDouble = false;
532
+ let inLineComment = false;
533
+ let inBlockComment = false;
534
+ for (let i = 0; i < sql.length; i++) {
535
+ const ch = sql[i];
536
+ const next = sql[i + 1];
537
+ if (inLineComment) {
538
+ out += ch;
539
+ if (ch === "\n") inLineComment = false;
540
+ continue;
541
+ }
542
+ if (inBlockComment) {
543
+ out += ch;
544
+ if (ch === "*" && next === "/") {
545
+ out += next;
546
+ inBlockComment = false;
547
+ i += 1;
548
+ }
549
+ continue;
550
+ }
551
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
552
+ out += ch + next;
553
+ inLineComment = true;
554
+ i += 1;
555
+ continue;
556
+ }
557
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
558
+ out += ch + next;
559
+ inBlockComment = true;
560
+ i += 1;
561
+ continue;
562
+ }
563
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
564
+ inSingle = !inSingle;
565
+ out += ch;
566
+ continue;
567
+ }
568
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
569
+ inDouble = !inDouble;
570
+ out += ch;
571
+ continue;
572
+ }
573
+ if (!inSingle && !inDouble && ch === "?") {
574
+ placeholder += 1;
575
+ out += `$${placeholder}`;
576
+ continue;
577
+ }
578
+ out += ch;
579
+ }
580
+ return out;
581
+ }
582
+ function translateStatementForPostgres(stmt) {
583
+ const normalized = normalizeStatement(stmt);
584
+ if (normalized.kind === "named") {
585
+ throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
586
+ }
587
+ const rewrittenSql = rewriteSql(normalized.sql);
588
+ const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
589
+ return {
590
+ sql: convertQuestionMarksToDollarParams(rewrittenSql),
591
+ args: coercedArgs
592
+ };
593
+ }
594
+ function shouldBypassPostgres(stmt) {
595
+ const normalized = normalizeStatement(stmt);
596
+ if (normalized.kind === "named") {
597
+ return true;
598
+ }
599
+ return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
600
+ }
601
+ function shouldFallbackOnError(error) {
602
+ const message = error instanceof Error ? error.message : String(error);
603
+ return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
604
+ }
605
+ function isReadQuery(sql) {
606
+ const trimmed = sql.trimStart();
607
+ return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
608
+ }
609
+ function buildRow(row, columns) {
610
+ const values = columns.map((column) => row[column]);
611
+ return Object.assign(values, row);
612
+ }
613
+ function buildResultSet(rows, rowsAffected = 0) {
614
+ const columns = rows[0] ? Object.keys(rows[0]) : [];
615
+ const resultRows = rows.map((row) => buildRow(row, columns));
616
+ return {
617
+ columns,
618
+ columnTypes: columns.map(() => ""),
619
+ rows: resultRows,
620
+ rowsAffected,
621
+ lastInsertRowid: void 0,
622
+ toJSON() {
623
+ return {
624
+ columns,
625
+ columnTypes: columns.map(() => ""),
626
+ rows,
627
+ rowsAffected,
628
+ lastInsertRowid: void 0
629
+ };
630
+ }
631
+ };
632
+ }
633
+ async function loadPrismaClient() {
634
+ if (!prismaClientPromise) {
635
+ prismaClientPromise = (async () => {
636
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
637
+ if (explicitPath) {
638
+ const module2 = await import(pathToFileURL(explicitPath).href);
639
+ const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
640
+ if (!PrismaClient2) {
641
+ throw new Error(`No PrismaClient export found at ${explicitPath}`);
642
+ }
643
+ return new PrismaClient2();
644
+ }
645
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path3.join(os3.homedir(), "exe-db");
646
+ const requireFromExeDb = createRequire(path3.join(exeDbRoot, "package.json"));
647
+ const prismaEntry = requireFromExeDb.resolve("@prisma/client");
648
+ const module = await import(pathToFileURL(prismaEntry).href);
649
+ const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
650
+ if (!PrismaClient) {
651
+ throw new Error(`No PrismaClient export found in ${prismaEntry}`);
652
+ }
653
+ return new PrismaClient();
654
+ })();
655
+ }
656
+ return prismaClientPromise;
657
+ }
658
+ async function ensureCompatibilityViews(prisma) {
659
+ if (!compatibilityBootstrapPromise) {
660
+ compatibilityBootstrapPromise = (async () => {
661
+ for (const mapping of VIEW_MAPPINGS) {
662
+ const relation = mapping.source.replace(/"/g, "");
663
+ const rows = await prisma.$queryRawUnsafe(
664
+ "SELECT to_regclass($1) AS regclass",
665
+ relation
666
+ );
667
+ if (!rows[0]?.regclass) {
668
+ continue;
669
+ }
670
+ await prisma.$executeRawUnsafe(
671
+ `CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
672
+ );
673
+ }
674
+ })();
675
+ }
676
+ return compatibilityBootstrapPromise;
677
+ }
678
+ async function executeOnPrisma(executor, stmt) {
679
+ const translated = translateStatementForPostgres(stmt);
680
+ if (isReadQuery(translated.sql)) {
681
+ const rows = await executor.$queryRawUnsafe(
682
+ translated.sql,
683
+ ...translated.args
684
+ );
685
+ return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
686
+ }
687
+ const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
688
+ return buildResultSet([], rowsAffected);
689
+ }
690
+ function splitSqlStatements(sql) {
691
+ const parts = [];
692
+ let current = "";
693
+ let inSingle = false;
694
+ let inDouble = false;
695
+ let inLineComment = false;
696
+ let inBlockComment = false;
697
+ for (let i = 0; i < sql.length; i++) {
698
+ const ch = sql[i];
699
+ const next = sql[i + 1];
700
+ if (inLineComment) {
701
+ current += ch;
702
+ if (ch === "\n") inLineComment = false;
703
+ continue;
704
+ }
705
+ if (inBlockComment) {
706
+ current += ch;
707
+ if (ch === "*" && next === "/") {
708
+ current += next;
709
+ inBlockComment = false;
710
+ i += 1;
711
+ }
712
+ continue;
713
+ }
714
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
715
+ current += ch + next;
716
+ inLineComment = true;
717
+ i += 1;
718
+ continue;
719
+ }
720
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
721
+ current += ch + next;
722
+ inBlockComment = true;
723
+ i += 1;
724
+ continue;
725
+ }
726
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
727
+ inSingle = !inSingle;
728
+ current += ch;
729
+ continue;
730
+ }
731
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
732
+ inDouble = !inDouble;
733
+ current += ch;
734
+ continue;
735
+ }
736
+ if (!inSingle && !inDouble && ch === ";") {
737
+ if (current.trim()) {
738
+ parts.push(current.trim());
739
+ }
740
+ current = "";
741
+ continue;
742
+ }
743
+ current += ch;
744
+ }
745
+ if (current.trim()) {
746
+ parts.push(current.trim());
747
+ }
748
+ return parts;
749
+ }
750
+ async function createPrismaDbAdapter(fallbackClient) {
751
+ const prisma = await loadPrismaClient();
752
+ await ensureCompatibilityViews(prisma);
753
+ let closed = false;
754
+ let adapter;
755
+ const fallbackExecute = async (stmt, error) => {
756
+ if (!fallbackClient) {
757
+ if (error) throw error;
758
+ throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
759
+ }
760
+ if (error) {
761
+ process.stderr.write(
762
+ `[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
763
+ `
764
+ );
765
+ }
766
+ return fallbackClient.execute(stmt);
767
+ };
768
+ adapter = {
769
+ async execute(stmt) {
770
+ if (shouldBypassPostgres(stmt)) {
771
+ return fallbackExecute(stmt);
772
+ }
773
+ try {
774
+ return await executeOnPrisma(prisma, stmt);
775
+ } catch (error) {
776
+ if (shouldFallbackOnError(error)) {
777
+ return fallbackExecute(stmt, error);
778
+ }
779
+ throw error;
780
+ }
781
+ },
782
+ async batch(stmts, mode) {
783
+ if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
784
+ if (!fallbackClient) {
785
+ throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
786
+ }
787
+ return fallbackClient.batch(stmts, mode);
788
+ }
789
+ try {
790
+ if (prisma.$transaction) {
791
+ return await prisma.$transaction(async (tx) => {
792
+ const results2 = [];
793
+ for (const stmt of stmts) {
794
+ results2.push(await executeOnPrisma(tx, stmt));
795
+ }
796
+ return results2;
797
+ });
798
+ }
799
+ const results = [];
800
+ for (const stmt of stmts) {
801
+ results.push(await executeOnPrisma(prisma, stmt));
802
+ }
803
+ return results;
804
+ } catch (error) {
805
+ if (fallbackClient && shouldFallbackOnError(error)) {
806
+ process.stderr.write(
807
+ `[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
808
+ `
809
+ );
810
+ return fallbackClient.batch(stmts, mode);
811
+ }
812
+ throw error;
813
+ }
814
+ },
815
+ async migrate(stmts) {
816
+ if (fallbackClient) {
817
+ return fallbackClient.migrate(stmts);
818
+ }
819
+ return adapter.batch(stmts, "deferred");
820
+ },
821
+ async transaction(mode) {
822
+ if (!fallbackClient) {
823
+ throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
824
+ }
825
+ return fallbackClient.transaction(mode);
826
+ },
827
+ async executeMultiple(sql) {
828
+ if (fallbackClient && shouldBypassPostgres(sql)) {
829
+ return fallbackClient.executeMultiple(sql);
830
+ }
831
+ for (const statement of splitSqlStatements(sql)) {
832
+ await adapter.execute(statement);
833
+ }
834
+ },
835
+ async sync() {
836
+ if (fallbackClient) {
837
+ return fallbackClient.sync();
838
+ }
839
+ return { frame_no: 0, frames_synced: 0 };
840
+ },
841
+ close() {
842
+ closed = true;
843
+ prismaClientPromise = null;
844
+ compatibilityBootstrapPromise = null;
845
+ void prisma.$disconnect?.();
846
+ },
847
+ get closed() {
848
+ return closed;
849
+ },
850
+ get protocol() {
851
+ return "prisma-postgres";
852
+ }
853
+ };
854
+ return adapter;
855
+ }
856
+ var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
857
+ var init_database_adapter = __esm({
858
+ "src/lib/database-adapter.ts"() {
859
+ "use strict";
860
+ VIEW_MAPPINGS = [
861
+ { view: "memories", source: "memory.memory_records" },
862
+ { view: "tasks", source: "memory.tasks" },
863
+ { view: "behaviors", source: "memory.behaviors" },
864
+ { view: "entities", source: "memory.entities" },
865
+ { view: "relationships", source: "memory.relationships" },
866
+ { view: "entity_memories", source: "memory.entity_memories" },
867
+ { view: "entity_aliases", source: "memory.entity_aliases" },
868
+ { view: "notifications", source: "memory.notifications" },
869
+ { view: "messages", source: "memory.messages" },
870
+ { view: "users", source: "wiki.users" },
871
+ { view: "workspaces", source: "wiki.workspaces" },
872
+ { view: "workspace_users", source: "wiki.workspace_users" },
873
+ { view: "documents", source: "wiki.workspace_documents" },
874
+ { view: "chats", source: "wiki.workspace_chats" }
875
+ ];
876
+ UPSERT_KEYS = {
877
+ memories: ["id"],
878
+ tasks: ["id"],
879
+ behaviors: ["id"],
880
+ entities: ["id"],
881
+ relationships: ["id"],
882
+ entity_aliases: ["alias"],
883
+ notifications: ["id"],
884
+ messages: ["id"],
885
+ users: ["id"],
886
+ workspaces: ["id"],
887
+ workspace_users: ["id"],
888
+ documents: ["id"],
889
+ chats: ["id"]
890
+ };
891
+ BOOLEAN_COLUMNS_BY_TABLE = {
892
+ memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
893
+ behaviors: /* @__PURE__ */ new Set(["active"]),
894
+ notifications: /* @__PURE__ */ new Set(["read"]),
895
+ users: /* @__PURE__ */ new Set(["has_personal_memory"])
896
+ };
897
+ BOOLEAN_COLUMN_NAMES = new Set(
898
+ Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
899
+ );
900
+ IMMEDIATE_FALLBACK_PATTERNS = [
901
+ /\bPRAGMA\b/i,
902
+ /\bsqlite_master\b/i,
903
+ /(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
904
+ /\bMATCH\b/i,
905
+ /\bvector_distance_cos\s*\(/i,
906
+ /\bjson_extract\s*\(/i,
907
+ /\bjulianday\s*\(/i,
908
+ /\bstrftime\s*\(/i,
909
+ /\blast_insert_rowid\s*\(/i
910
+ ];
911
+ prismaClientPromise = null;
912
+ compatibilityBootstrapPromise = null;
288
913
  }
289
914
  });
290
915
 
291
916
  // src/lib/database.ts
292
917
  import { createClient } from "@libsql/client";
293
918
  async function initDatabase(config) {
919
+ if (_walCheckpointTimer) {
920
+ clearInterval(_walCheckpointTimer);
921
+ _walCheckpointTimer = null;
922
+ }
923
+ if (_daemonClient) {
924
+ _daemonClient.close();
925
+ _daemonClient = null;
926
+ }
927
+ if (_adapterClient && _adapterClient !== _resilientClient) {
928
+ _adapterClient.close();
929
+ }
930
+ _adapterClient = null;
294
931
  if (_client) {
295
932
  _client.close();
296
933
  _client = null;
@@ -304,6 +941,7 @@ async function initDatabase(config) {
304
941
  }
305
942
  _client = createClient(opts);
306
943
  _resilientClient = wrapWithRetry(_client);
944
+ _adapterClient = _resilientClient;
307
945
  _client.execute("PRAGMA busy_timeout = 30000").catch(() => {
308
946
  });
309
947
  _client.execute("PRAGMA journal_mode = WAL").catch(() => {
@@ -314,11 +952,17 @@ async function initDatabase(config) {
314
952
  });
315
953
  }, 3e4);
316
954
  _walCheckpointTimer.unref();
955
+ if (process.env.DATABASE_URL) {
956
+ _adapterClient = await createPrismaDbAdapter(_resilientClient);
957
+ }
317
958
  }
318
959
  function getClient() {
319
- if (!_resilientClient) {
960
+ if (!_adapterClient) {
320
961
  throw new Error("Database client not initialized. Call initDatabase() first.");
321
962
  }
963
+ if (process.env.DATABASE_URL) {
964
+ return _adapterClient;
965
+ }
322
966
  if (process.env.EXE_IS_DAEMON === "1") {
323
967
  return _resilientClient;
324
968
  }
@@ -611,6 +1255,7 @@ async function ensureSchema() {
611
1255
  project TEXT NOT NULL,
612
1256
  summary TEXT NOT NULL,
613
1257
  task_file TEXT,
1258
+ session_scope TEXT,
614
1259
  read INTEGER NOT NULL DEFAULT 0,
615
1260
  created_at TEXT NOT NULL
616
1261
  );
@@ -619,7 +1264,7 @@ async function ensureSchema() {
619
1264
  ON notifications(read);
620
1265
 
621
1266
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
622
- ON notifications(agent_id);
1267
+ ON notifications(agent_id, session_scope);
623
1268
 
624
1269
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
625
1270
  ON notifications(task_file);
@@ -657,6 +1302,7 @@ async function ensureSchema() {
657
1302
  target_agent TEXT NOT NULL,
658
1303
  target_project TEXT,
659
1304
  target_device TEXT NOT NULL DEFAULT 'local',
1305
+ session_scope TEXT,
660
1306
  content TEXT NOT NULL,
661
1307
  priority TEXT DEFAULT 'normal',
662
1308
  status TEXT DEFAULT 'pending',
@@ -670,10 +1316,31 @@ async function ensureSchema() {
670
1316
  );
671
1317
 
672
1318
  CREATE INDEX IF NOT EXISTS idx_messages_target
673
- ON messages(target_agent, status);
1319
+ ON messages(target_agent, session_scope, status);
674
1320
 
675
1321
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
676
- ON messages(target_agent, from_agent, server_seq);
1322
+ ON messages(target_agent, session_scope, from_agent, server_seq);
1323
+ `);
1324
+ try {
1325
+ await client.execute({
1326
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
1327
+ args: []
1328
+ });
1329
+ } catch {
1330
+ }
1331
+ try {
1332
+ await client.execute({
1333
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
1334
+ args: []
1335
+ });
1336
+ } catch {
1337
+ }
1338
+ await client.executeMultiple(`
1339
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
1340
+ ON notifications(agent_id, session_scope, read, created_at);
1341
+
1342
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
1343
+ ON messages(target_agent, session_scope, status, created_at);
677
1344
  `);
678
1345
  try {
679
1346
  await client.execute({
@@ -1257,17 +1924,26 @@ async function ensureSchema() {
1257
1924
  } catch {
1258
1925
  }
1259
1926
  }
1927
+ try {
1928
+ await client.execute({
1929
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
1930
+ args: []
1931
+ });
1932
+ } catch {
1933
+ }
1260
1934
  }
1261
- var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso;
1935
+ var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso;
1262
1936
  var init_database = __esm({
1263
1937
  "src/lib/database.ts"() {
1264
1938
  "use strict";
1265
1939
  init_db_retry();
1266
1940
  init_employees();
1941
+ init_database_adapter();
1267
1942
  _client = null;
1268
1943
  _resilientClient = null;
1269
1944
  _walCheckpointTimer = null;
1270
1945
  _daemonClient = null;
1946
+ _adapterClient = null;
1271
1947
  initTurso = initDatabase;
1272
1948
  }
1273
1949
  });
@@ -1277,6 +1953,7 @@ var shard_manager_exports = {};
1277
1953
  __export(shard_manager_exports, {
1278
1954
  disposeShards: () => disposeShards,
1279
1955
  ensureShardSchema: () => ensureShardSchema,
1956
+ getOpenShardCount: () => getOpenShardCount,
1280
1957
  getReadyShardClient: () => getReadyShardClient,
1281
1958
  getShardClient: () => getShardClient,
1282
1959
  getShardsDir: () => getShardsDir,
@@ -1285,15 +1962,18 @@ __export(shard_manager_exports, {
1285
1962
  listShards: () => listShards,
1286
1963
  shardExists: () => shardExists
1287
1964
  });
1288
- import path4 from "path";
1289
- import { existsSync as existsSync4, mkdirSync, readdirSync } from "fs";
1965
+ import path5 from "path";
1966
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync } from "fs";
1290
1967
  import { createClient as createClient2 } from "@libsql/client";
1291
1968
  function initShardManager(encryptionKey) {
1292
1969
  _encryptionKey = encryptionKey;
1293
- if (!existsSync4(SHARDS_DIR)) {
1294
- mkdirSync(SHARDS_DIR, { recursive: true });
1970
+ if (!existsSync5(SHARDS_DIR)) {
1971
+ mkdirSync2(SHARDS_DIR, { recursive: true });
1295
1972
  }
1296
1973
  _shardingEnabled = true;
1974
+ if (_evictionTimer) clearInterval(_evictionTimer);
1975
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
1976
+ _evictionTimer.unref();
1297
1977
  }
1298
1978
  function isShardingEnabled() {
1299
1979
  return _shardingEnabled;
@@ -1310,21 +1990,28 @@ function getShardClient(projectName) {
1310
1990
  throw new Error(`Invalid project name for shard: "${projectName}"`);
1311
1991
  }
1312
1992
  const cached = _shards.get(safeName);
1313
- if (cached) return cached;
1314
- const dbPath = path4.join(SHARDS_DIR, `${safeName}.db`);
1993
+ if (cached) {
1994
+ _shardLastAccess.set(safeName, Date.now());
1995
+ return cached;
1996
+ }
1997
+ while (_shards.size >= MAX_OPEN_SHARDS) {
1998
+ evictLRU();
1999
+ }
2000
+ const dbPath = path5.join(SHARDS_DIR, `${safeName}.db`);
1315
2001
  const client = createClient2({
1316
2002
  url: `file:${dbPath}`,
1317
2003
  encryptionKey: _encryptionKey
1318
2004
  });
1319
2005
  _shards.set(safeName, client);
2006
+ _shardLastAccess.set(safeName, Date.now());
1320
2007
  return client;
1321
2008
  }
1322
2009
  function shardExists(projectName) {
1323
2010
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
1324
- return existsSync4(path4.join(SHARDS_DIR, `${safeName}.db`));
2011
+ return existsSync5(path5.join(SHARDS_DIR, `${safeName}.db`));
1325
2012
  }
1326
2013
  function listShards() {
1327
- if (!existsSync4(SHARDS_DIR)) return [];
2014
+ if (!existsSync5(SHARDS_DIR)) return [];
1328
2015
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
1329
2016
  }
1330
2017
  async function ensureShardSchema(client) {
@@ -1376,6 +2063,8 @@ async function ensureShardSchema(client) {
1376
2063
  for (const col of [
1377
2064
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
1378
2065
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
2066
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
2067
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
1379
2068
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
1380
2069
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
1381
2070
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -1398,7 +2087,23 @@ async function ensureShardSchema(client) {
1398
2087
  // MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
1399
2088
  "ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
1400
2089
  "ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
1401
- "ALTER TABLE memories ADD COLUMN trajectory TEXT"
2090
+ "ALTER TABLE memories ADD COLUMN trajectory TEXT",
2091
+ // Metadata enrichment columns (must match database.ts)
2092
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
2093
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
2094
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
2095
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
2096
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
2097
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
2098
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
2099
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
2100
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
2101
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
2102
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
2103
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
2104
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
2105
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
2106
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
1402
2107
  ]) {
1403
2108
  try {
1404
2109
  await client.execute(col);
@@ -1497,21 +2202,69 @@ async function getReadyShardClient(projectName) {
1497
2202
  await ensureShardSchema(client);
1498
2203
  return client;
1499
2204
  }
2205
+ function evictLRU() {
2206
+ let oldest = null;
2207
+ let oldestTime = Infinity;
2208
+ for (const [name, time] of _shardLastAccess) {
2209
+ if (time < oldestTime) {
2210
+ oldestTime = time;
2211
+ oldest = name;
2212
+ }
2213
+ }
2214
+ if (oldest) {
2215
+ const client = _shards.get(oldest);
2216
+ if (client) {
2217
+ client.close();
2218
+ }
2219
+ _shards.delete(oldest);
2220
+ _shardLastAccess.delete(oldest);
2221
+ }
2222
+ }
2223
+ function evictIdleShards() {
2224
+ const now = Date.now();
2225
+ const toEvict = [];
2226
+ for (const [name, lastAccess] of _shardLastAccess) {
2227
+ if (now - lastAccess > SHARD_IDLE_MS) {
2228
+ toEvict.push(name);
2229
+ }
2230
+ }
2231
+ for (const name of toEvict) {
2232
+ const client = _shards.get(name);
2233
+ if (client) {
2234
+ client.close();
2235
+ }
2236
+ _shards.delete(name);
2237
+ _shardLastAccess.delete(name);
2238
+ }
2239
+ }
2240
+ function getOpenShardCount() {
2241
+ return _shards.size;
2242
+ }
1500
2243
  function disposeShards() {
2244
+ if (_evictionTimer) {
2245
+ clearInterval(_evictionTimer);
2246
+ _evictionTimer = null;
2247
+ }
1501
2248
  for (const [, client] of _shards) {
1502
2249
  client.close();
1503
2250
  }
1504
2251
  _shards.clear();
2252
+ _shardLastAccess.clear();
1505
2253
  _shardingEnabled = false;
1506
2254
  _encryptionKey = null;
1507
2255
  }
1508
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
2256
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
1509
2257
  var init_shard_manager = __esm({
1510
2258
  "src/lib/shard-manager.ts"() {
1511
2259
  "use strict";
1512
2260
  init_config();
1513
- SHARDS_DIR = path4.join(EXE_AI_DIR, "shards");
2261
+ SHARDS_DIR = path5.join(EXE_AI_DIR, "shards");
2262
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
2263
+ MAX_OPEN_SHARDS = 10;
2264
+ EVICTION_INTERVAL_MS = 60 * 1e3;
1514
2265
  _shards = /* @__PURE__ */ new Map();
2266
+ _shardLastAccess = /* @__PURE__ */ new Map();
2267
+ _evictionTimer = null;
1515
2268
  _encryptionKey = null;
1516
2269
  _shardingEnabled = false;
1517
2270
  }
@@ -1704,13 +2457,50 @@ ${p.content}`).join("\n\n");
1704
2457
  }
1705
2458
  });
1706
2459
 
2460
+ // src/lib/daemon-auth.ts
2461
+ import crypto from "crypto";
2462
+ import path6 from "path";
2463
+ import { existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
2464
+ function normalizeToken(token) {
2465
+ if (!token) return null;
2466
+ const trimmed = token.trim();
2467
+ return trimmed.length > 0 ? trimmed : null;
2468
+ }
2469
+ function readDaemonToken() {
2470
+ try {
2471
+ if (!existsSync6(DAEMON_TOKEN_PATH)) return null;
2472
+ return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
2473
+ } catch {
2474
+ return null;
2475
+ }
2476
+ }
2477
+ function ensureDaemonToken(seed) {
2478
+ const existing = readDaemonToken();
2479
+ if (existing) return existing;
2480
+ const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
2481
+ ensurePrivateDirSync(EXE_AI_DIR);
2482
+ writeFileSync2(DAEMON_TOKEN_PATH, `${token}
2483
+ `, "utf8");
2484
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
2485
+ return token;
2486
+ }
2487
+ var DAEMON_TOKEN_PATH;
2488
+ var init_daemon_auth = __esm({
2489
+ "src/lib/daemon-auth.ts"() {
2490
+ "use strict";
2491
+ init_config();
2492
+ init_secure_files();
2493
+ DAEMON_TOKEN_PATH = path6.join(EXE_AI_DIR, "exed.token");
2494
+ }
2495
+ });
2496
+
1707
2497
  // src/lib/exe-daemon-client.ts
1708
2498
  import net from "net";
1709
- import os4 from "os";
2499
+ import os5 from "os";
1710
2500
  import { spawn } from "child_process";
1711
2501
  import { randomUUID as randomUUID2 } from "crypto";
1712
- import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
1713
- import path5 from "path";
2502
+ import { existsSync as existsSync7, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
2503
+ import path7 from "path";
1714
2504
  import { fileURLToPath } from "url";
1715
2505
  function handleData(chunk) {
1716
2506
  _buffer += chunk.toString();
@@ -1738,9 +2528,9 @@ function handleData(chunk) {
1738
2528
  }
1739
2529
  }
1740
2530
  function cleanupStaleFiles() {
1741
- if (existsSync5(PID_PATH)) {
2531
+ if (existsSync7(PID_PATH)) {
1742
2532
  try {
1743
- const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
2533
+ const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
1744
2534
  if (pid > 0) {
1745
2535
  try {
1746
2536
  process.kill(pid, 0);
@@ -1761,17 +2551,17 @@ function cleanupStaleFiles() {
1761
2551
  }
1762
2552
  }
1763
2553
  function findPackageRoot() {
1764
- let dir = path5.dirname(fileURLToPath(import.meta.url));
1765
- const { root } = path5.parse(dir);
2554
+ let dir = path7.dirname(fileURLToPath(import.meta.url));
2555
+ const { root } = path7.parse(dir);
1766
2556
  while (dir !== root) {
1767
- if (existsSync5(path5.join(dir, "package.json"))) return dir;
1768
- dir = path5.dirname(dir);
2557
+ if (existsSync7(path7.join(dir, "package.json"))) return dir;
2558
+ dir = path7.dirname(dir);
1769
2559
  }
1770
2560
  return null;
1771
2561
  }
1772
2562
  function spawnDaemon() {
1773
- const freeGB = os4.freemem() / (1024 * 1024 * 1024);
1774
- const totalGB = os4.totalmem() / (1024 * 1024 * 1024);
2563
+ const freeGB = os5.freemem() / (1024 * 1024 * 1024);
2564
+ const totalGB = os5.totalmem() / (1024 * 1024 * 1024);
1775
2565
  if (totalGB <= 8) {
1776
2566
  process.stderr.write(
1777
2567
  `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
@@ -1791,16 +2581,17 @@ function spawnDaemon() {
1791
2581
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
1792
2582
  return;
1793
2583
  }
1794
- const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1795
- if (!existsSync5(daemonPath)) {
2584
+ const daemonPath = path7.join(pkgRoot, "dist", "lib", "exe-daemon.js");
2585
+ if (!existsSync7(daemonPath)) {
1796
2586
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
1797
2587
  `);
1798
2588
  return;
1799
2589
  }
1800
2590
  const resolvedPath = daemonPath;
2591
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
1801
2592
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
1802
2593
  `);
1803
- const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
2594
+ const logPath = path7.join(path7.dirname(SOCKET_PATH), "exed.log");
1804
2595
  let stderrFd = "ignore";
1805
2596
  try {
1806
2597
  stderrFd = openSync(logPath, "a");
@@ -1818,7 +2609,8 @@ function spawnDaemon() {
1818
2609
  TMUX_PANE: void 0,
1819
2610
  // Prevents resolveExeSession() from scoping to one session
1820
2611
  EXE_DAEMON_SOCK: SOCKET_PATH,
1821
- EXE_DAEMON_PID: PID_PATH
2612
+ EXE_DAEMON_PID: PID_PATH,
2613
+ [DAEMON_TOKEN_ENV]: daemonToken
1822
2614
  }
1823
2615
  });
1824
2616
  child.unref();
@@ -1928,13 +2720,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1928
2720
  return;
1929
2721
  }
1930
2722
  const id = randomUUID2();
2723
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
1931
2724
  const timer = setTimeout(() => {
1932
2725
  _pending.delete(id);
1933
2726
  resolve({ error: "Request timeout" });
1934
2727
  }, timeoutMs);
1935
2728
  _pending.set(id, { resolve, timer });
1936
2729
  try {
1937
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
2730
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
1938
2731
  } catch {
1939
2732
  clearTimeout(timer);
1940
2733
  _pending.delete(id);
@@ -1951,101 +2744,156 @@ async function pingDaemon() {
1951
2744
  return null;
1952
2745
  }
1953
2746
  function killAndRespawnDaemon() {
1954
- process.stderr.write("[exed-client] Killing daemon for restart...\n");
1955
- if (existsSync5(PID_PATH)) {
1956
- try {
1957
- const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
1958
- if (pid > 0) {
1959
- try {
1960
- process.kill(pid, "SIGKILL");
1961
- } catch {
2747
+ if (!acquireSpawnLock()) {
2748
+ process.stderr.write("[exed-client] Another process is already restarting daemon \u2014 skipping\n");
2749
+ if (_socket) {
2750
+ _socket.destroy();
2751
+ _socket = null;
2752
+ }
2753
+ _connected = false;
2754
+ _buffer = "";
2755
+ return;
2756
+ }
2757
+ try {
2758
+ process.stderr.write("[exed-client] Killing daemon for restart...\n");
2759
+ if (existsSync7(PID_PATH)) {
2760
+ try {
2761
+ const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
2762
+ if (pid > 0) {
2763
+ try {
2764
+ process.kill(pid, "SIGKILL");
2765
+ } catch {
2766
+ }
1962
2767
  }
2768
+ } catch {
1963
2769
  }
2770
+ }
2771
+ if (_socket) {
2772
+ _socket.destroy();
2773
+ _socket = null;
2774
+ }
2775
+ _connected = false;
2776
+ _buffer = "";
2777
+ try {
2778
+ unlinkSync2(PID_PATH);
1964
2779
  } catch {
1965
2780
  }
2781
+ try {
2782
+ unlinkSync2(SOCKET_PATH);
2783
+ } catch {
2784
+ }
2785
+ spawnDaemon();
2786
+ } finally {
2787
+ releaseSpawnLock();
1966
2788
  }
1967
- if (_socket) {
1968
- _socket.destroy();
1969
- _socket = null;
1970
- }
1971
- _connected = false;
1972
- _buffer = "";
2789
+ }
2790
+ function isDaemonTooYoung() {
1973
2791
  try {
1974
- unlinkSync2(PID_PATH);
2792
+ const stat2 = statSync(PID_PATH);
2793
+ return Date.now() - stat2.mtimeMs < MIN_DAEMON_AGE_MS;
1975
2794
  } catch {
2795
+ return false;
1976
2796
  }
1977
- try {
1978
- unlinkSync2(SOCKET_PATH);
1979
- } catch {
2797
+ }
2798
+ async function retryThenRestart(doRequest, label) {
2799
+ const result = await doRequest();
2800
+ if (!result.error) {
2801
+ _consecutiveFailures = 0;
2802
+ return result;
2803
+ }
2804
+ _consecutiveFailures++;
2805
+ for (let i = 0; i < MAX_RETRIES_BEFORE_RESTART; i++) {
2806
+ const delayMs = RETRY_DELAYS_MS[i] ?? 5e3;
2807
+ process.stderr.write(`[exed-client] ${label} failed (${result.error}), retry ${i + 1}/${MAX_RETRIES_BEFORE_RESTART} in ${delayMs}ms
2808
+ `);
2809
+ await new Promise((r) => setTimeout(r, delayMs));
2810
+ if (!_connected) {
2811
+ if (!await connectToSocket()) continue;
2812
+ }
2813
+ const retry = await doRequest();
2814
+ if (!retry.error) {
2815
+ _consecutiveFailures = 0;
2816
+ return retry;
2817
+ }
2818
+ _consecutiveFailures++;
2819
+ }
2820
+ if (isDaemonTooYoung()) {
2821
+ process.stderr.write(`[exed-client] ${label}: daemon too young (< ${MIN_DAEMON_AGE_MS / 1e3}s) \u2014 skipping restart
2822
+ `);
2823
+ return { error: result.error };
1980
2824
  }
1981
- spawnDaemon();
2825
+ process.stderr.write(`[exed-client] ${label}: ${_consecutiveFailures} consecutive failures \u2014 restarting daemon
2826
+ `);
2827
+ killAndRespawnDaemon();
2828
+ const start = Date.now();
2829
+ let delay2 = 200;
2830
+ while (Date.now() - start < CONNECT_TIMEOUT_MS) {
2831
+ await new Promise((r) => setTimeout(r, delay2));
2832
+ if (await connectToSocket()) break;
2833
+ delay2 = Math.min(delay2 * 2, 3e3);
2834
+ }
2835
+ if (!_connected) return { error: "Daemon restart failed" };
2836
+ const final = await doRequest();
2837
+ if (!final.error) _consecutiveFailures = 0;
2838
+ return final;
1982
2839
  }
1983
2840
  async function embedViaClient(text, priority = "high") {
1984
2841
  if (!_connected && !await connectEmbedDaemon()) return null;
1985
2842
  _requestCount++;
1986
2843
  if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
1987
2844
  const health = await pingDaemon();
1988
- if (!health) {
2845
+ if (!health && !isDaemonTooYoung()) {
1989
2846
  process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
1990
2847
  `);
1991
2848
  killAndRespawnDaemon();
1992
2849
  const start = Date.now();
1993
- let delay2 = 200;
2850
+ let d = 200;
1994
2851
  while (Date.now() - start < CONNECT_TIMEOUT_MS) {
1995
- await new Promise((r) => setTimeout(r, delay2));
2852
+ await new Promise((r) => setTimeout(r, d));
1996
2853
  if (await connectToSocket()) break;
1997
- delay2 = Math.min(delay2 * 2, 3e3);
2854
+ d = Math.min(d * 2, 3e3);
1998
2855
  }
1999
2856
  if (!_connected) return null;
2000
2857
  }
2001
2858
  }
2002
- const result = await sendRequest([text], priority);
2003
- if (!result.error && result.vectors?.[0]) return result.vectors[0];
2004
- if (result.error) {
2005
- process.stderr.write(`[exed-client] Embed failed (${result.error}) \u2014 attempting restart
2006
- `);
2007
- killAndRespawnDaemon();
2008
- const start = Date.now();
2009
- let delay2 = 200;
2010
- while (Date.now() - start < CONNECT_TIMEOUT_MS) {
2011
- await new Promise((r) => setTimeout(r, delay2));
2012
- if (await connectToSocket()) break;
2013
- delay2 = Math.min(delay2 * 2, 3e3);
2014
- }
2015
- if (!_connected) return null;
2016
- const retry = await sendRequest([text], priority);
2017
- if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
2018
- process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
2019
- `);
2020
- }
2021
- return null;
2859
+ const result = await retryThenRestart(
2860
+ () => sendRequest([text], priority),
2861
+ "Embed"
2862
+ );
2863
+ return !result.error && result.vectors?.[0] ? result.vectors[0] : null;
2022
2864
  }
2023
- var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, HEALTH_CHECK_INTERVAL, _pending, MAX_BUFFER;
2865
+ var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, DAEMON_TOKEN_ENV, _socket, _connected, _buffer, _requestCount, _consecutiveFailures, HEALTH_CHECK_INTERVAL, MAX_RETRIES_BEFORE_RESTART, RETRY_DELAYS_MS, MIN_DAEMON_AGE_MS, _pending, MAX_BUFFER;
2024
2866
  var init_exe_daemon_client = __esm({
2025
2867
  "src/lib/exe-daemon-client.ts"() {
2026
2868
  "use strict";
2027
2869
  init_config();
2028
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
2029
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
2030
- SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
2870
+ init_daemon_auth();
2871
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path7.join(EXE_AI_DIR, "exed.sock");
2872
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path7.join(EXE_AI_DIR, "exed.pid");
2873
+ SPAWN_LOCK_PATH = path7.join(EXE_AI_DIR, "exed-spawn.lock");
2031
2874
  SPAWN_LOCK_STALE_MS = 3e4;
2032
2875
  CONNECT_TIMEOUT_MS = 15e3;
2033
2876
  REQUEST_TIMEOUT_MS = 3e4;
2877
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
2034
2878
  _socket = null;
2035
2879
  _connected = false;
2036
2880
  _buffer = "";
2037
2881
  _requestCount = 0;
2882
+ _consecutiveFailures = 0;
2038
2883
  HEALTH_CHECK_INTERVAL = 100;
2884
+ MAX_RETRIES_BEFORE_RESTART = 3;
2885
+ RETRY_DELAYS_MS = [1e3, 3e3, 5e3];
2886
+ MIN_DAEMON_AGE_MS = 3e4;
2039
2887
  _pending = /* @__PURE__ */ new Map();
2040
2888
  MAX_BUFFER = 1e7;
2041
2889
  }
2042
2890
  });
2043
2891
 
2044
2892
  // src/bin/backfill-conversations.ts
2045
- import crypto from "crypto";
2893
+ import crypto2 from "crypto";
2046
2894
  import { createReadStream } from "fs";
2047
2895
  import { readdir, stat } from "fs/promises";
2048
- import path6 from "path";
2896
+ import path8 from "path";
2049
2897
  import { createInterface } from "readline";
2050
2898
  import { homedir } from "os";
2051
2899
  import { parseArgs } from "util";
@@ -2061,16 +2909,16 @@ init_database();
2061
2909
 
2062
2910
  // src/lib/keychain.ts
2063
2911
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
2064
- import { existsSync as existsSync3 } from "fs";
2065
- import path3 from "path";
2066
- import os3 from "os";
2912
+ import { existsSync as existsSync4 } from "fs";
2913
+ import path4 from "path";
2914
+ import os4 from "os";
2067
2915
  var SERVICE = "exe-mem";
2068
2916
  var ACCOUNT = "master-key";
2069
2917
  function getKeyDir() {
2070
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path3.join(os3.homedir(), ".exe-os");
2918
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path4.join(os4.homedir(), ".exe-os");
2071
2919
  }
2072
2920
  function getKeyPath() {
2073
- return path3.join(getKeyDir(), "master.key");
2921
+ return path4.join(getKeyDir(), "master.key");
2074
2922
  }
2075
2923
  async function tryKeytar() {
2076
2924
  try {
@@ -2091,9 +2939,9 @@ async function getMasterKey() {
2091
2939
  }
2092
2940
  }
2093
2941
  const keyPath = getKeyPath();
2094
- if (!existsSync3(keyPath)) {
2942
+ if (!existsSync4(keyPath)) {
2095
2943
  process.stderr.write(
2096
- `[keychain] Key not found at ${keyPath} (HOME=${os3.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
2944
+ `[keychain] Key not found at ${keyPath} (HOME=${os4.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
2097
2945
  `
2098
2946
  );
2099
2947
  return null;
@@ -2540,7 +3388,7 @@ var MIN_MESSAGES = 3;
2540
3388
  var MAX_SUMMARY_LENGTH = 4e3;
2541
3389
  var MAX_WALK_DEPTH = 10;
2542
3390
  async function findJsonlFiles(sinceDate, projectFilter) {
2543
- const projectsDir = path6.join(homedir(), ".claude", "projects");
3391
+ const projectsDir = path8.join(homedir(), ".claude", "projects");
2544
3392
  const files = [];
2545
3393
  async function walk(dir, depth = 0) {
2546
3394
  if (depth > MAX_WALK_DEPTH) return;
@@ -2551,7 +3399,7 @@ async function findJsonlFiles(sinceDate, projectFilter) {
2551
3399
  return;
2552
3400
  }
2553
3401
  for (const entry of entries) {
2554
- const full = path6.join(dir, entry.name);
3402
+ const full = path8.join(dir, entry.name);
2555
3403
  if (entry.isDirectory()) {
2556
3404
  if (entry.name === "subagents" || entry.name === "tool-results") continue;
2557
3405
  await walk(full, depth + 1);
@@ -2576,7 +3424,7 @@ async function findJsonlFiles(sinceDate, projectFilter) {
2576
3424
  if (!entry.isDirectory()) continue;
2577
3425
  const decoded = decodeProjectDir(entry.name);
2578
3426
  if (decoded.toLowerCase().includes(projectFilter.toLowerCase())) {
2579
- await walk(path6.join(projectsDir, entry.name));
3427
+ await walk(path8.join(projectsDir, entry.name));
2580
3428
  }
2581
3429
  }
2582
3430
  } else {
@@ -2593,14 +3441,14 @@ function decodeProjectDir(dirName) {
2593
3441
  return dirName;
2594
3442
  }
2595
3443
  function projectNameFromPath(filePath) {
2596
- const projectsDir = path6.join(homedir(), ".claude", "projects");
2597
- const relative = path6.relative(projectsDir, filePath);
2598
- const projectDir = relative.split(path6.sep)[0] ?? "unknown";
3444
+ const projectsDir = path8.join(homedir(), ".claude", "projects");
3445
+ const relative = path8.relative(projectsDir, filePath);
3446
+ const projectDir = relative.split(path8.sep)[0] ?? "unknown";
2599
3447
  return decodeProjectDir(projectDir);
2600
3448
  }
2601
3449
  async function parseConversation(filePath) {
2602
3450
  const conv = {
2603
- sessionId: path6.basename(filePath, ".jsonl"),
3451
+ sessionId: path8.basename(filePath, ".jsonl"),
2604
3452
  projectName: projectNameFromPath(filePath),
2605
3453
  cwd: void 0,
2606
3454
  startTime: void 0,
@@ -2664,7 +3512,7 @@ async function parseConversation(filePath) {
2664
3512
  }
2665
3513
  }
2666
3514
  if (conv.cwd) {
2667
- conv.projectName = path6.basename(conv.cwd);
3515
+ conv.projectName = path8.basename(conv.cwd);
2668
3516
  const worktreeMatch = conv.cwd.match(/\.worktrees\/([^/]+)/);
2669
3517
  if (worktreeMatch?.[1]) {
2670
3518
  conv.agentId = worktreeMatch[1];
@@ -2837,7 +3685,7 @@ async function backfillConversations(options) {
2837
3685
  }
2838
3686
  }
2839
3687
  await writeMemory({
2840
- id: crypto.randomUUID(),
3688
+ id: crypto2.randomUUID(),
2841
3689
  agent_id: conv.agentId,
2842
3690
  agent_role: isCoordinatorName(conv.agentId) ? "COO" : "specialist",
2843
3691
  session_id: conv.sessionId,