@askexenow/exe-os 0.9.7 → 0.9.8

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 (95) hide show
  1. package/dist/bin/backfill-conversations.js +754 -79
  2. package/dist/bin/backfill-responses.js +752 -77
  3. package/dist/bin/backfill-vectors.js +752 -77
  4. package/dist/bin/cleanup-stale-review-tasks.js +657 -35
  5. package/dist/bin/cli.js +1388 -605
  6. package/dist/bin/exe-agent-config.js +123 -95
  7. package/dist/bin/exe-agent.js +41 -25
  8. package/dist/bin/exe-assign.js +732 -57
  9. package/dist/bin/exe-boot.js +784 -153
  10. package/dist/bin/exe-call.js +209 -138
  11. package/dist/bin/exe-cloud.js +35 -12
  12. package/dist/bin/exe-dispatch.js +692 -70
  13. package/dist/bin/exe-doctor.js +648 -26
  14. package/dist/bin/exe-export-behaviors.js +650 -20
  15. package/dist/bin/exe-forget.js +635 -13
  16. package/dist/bin/exe-gateway.js +1053 -271
  17. package/dist/bin/exe-heartbeat.js +665 -43
  18. package/dist/bin/exe-kill.js +646 -16
  19. package/dist/bin/exe-launch-agent.js +887 -97
  20. package/dist/bin/exe-link.js +658 -43
  21. package/dist/bin/exe-new-employee.js +378 -177
  22. package/dist/bin/exe-pending-messages.js +656 -34
  23. package/dist/bin/exe-pending-notifications.js +635 -13
  24. package/dist/bin/exe-pending-reviews.js +659 -37
  25. package/dist/bin/exe-rename.js +645 -30
  26. package/dist/bin/exe-review.js +635 -13
  27. package/dist/bin/exe-search.js +771 -88
  28. package/dist/bin/exe-session-cleanup.js +834 -150
  29. package/dist/bin/exe-settings.js +127 -91
  30. package/dist/bin/exe-start-codex.js +729 -94
  31. package/dist/bin/exe-start-opencode.js +717 -82
  32. package/dist/bin/exe-status.js +657 -35
  33. package/dist/bin/exe-team.js +635 -13
  34. package/dist/bin/git-sweep.js +720 -89
  35. package/dist/bin/graph-backfill.js +643 -13
  36. package/dist/bin/graph-export.js +646 -16
  37. package/dist/bin/install.js +596 -193
  38. package/dist/bin/scan-tasks.js +724 -93
  39. package/dist/bin/setup.js +1038 -210
  40. package/dist/bin/shard-migrate.js +645 -15
  41. package/dist/bin/wiki-sync.js +646 -16
  42. package/dist/gateway/index.js +1027 -245
  43. package/dist/hooks/bug-report-worker.js +891 -170
  44. package/dist/hooks/commit-complete.js +718 -87
  45. package/dist/hooks/error-recall.js +776 -93
  46. package/dist/hooks/exe-heartbeat-hook.js +85 -71
  47. package/dist/hooks/ingest-worker.js +840 -156
  48. package/dist/hooks/ingest.js +90 -73
  49. package/dist/hooks/instructions-loaded.js +669 -38
  50. package/dist/hooks/notification.js +661 -30
  51. package/dist/hooks/post-compact.js +674 -43
  52. package/dist/hooks/pre-compact.js +718 -87
  53. package/dist/hooks/pre-tool-use.js +872 -125
  54. package/dist/hooks/prompt-ingest-worker.js +758 -83
  55. package/dist/hooks/prompt-submit.js +1060 -319
  56. package/dist/hooks/response-ingest-worker.js +758 -83
  57. package/dist/hooks/session-end.js +721 -90
  58. package/dist/hooks/session-start.js +1031 -207
  59. package/dist/hooks/stop.js +680 -49
  60. package/dist/hooks/subagent-stop.js +674 -43
  61. package/dist/hooks/summary-worker.js +816 -132
  62. package/dist/index.js +1015 -232
  63. package/dist/lib/cloud-sync.js +663 -48
  64. package/dist/lib/consolidation.js +26 -3
  65. package/dist/lib/database.js +626 -18
  66. package/dist/lib/db.js +2261 -0
  67. package/dist/lib/device-registry.js +640 -25
  68. package/dist/lib/embedder.js +96 -43
  69. package/dist/lib/employee-templates.js +16 -0
  70. package/dist/lib/employees.js +259 -83
  71. package/dist/lib/exe-daemon-client.js +101 -63
  72. package/dist/lib/exe-daemon.js +894 -162
  73. package/dist/lib/hybrid-search.js +771 -88
  74. package/dist/lib/identity.js +27 -7
  75. package/dist/lib/messaging.js +55 -28
  76. package/dist/lib/reminders.js +21 -1
  77. package/dist/lib/schedules.js +636 -14
  78. package/dist/lib/skill-learning.js +21 -1
  79. package/dist/lib/store.js +643 -13
  80. package/dist/lib/task-router.js +82 -71
  81. package/dist/lib/tasks.js +98 -71
  82. package/dist/lib/tmux-routing.js +87 -60
  83. package/dist/lib/token-spend.js +26 -6
  84. package/dist/mcp/server.js +1784 -458
  85. package/dist/mcp/tools/complete-reminder.js +21 -1
  86. package/dist/mcp/tools/create-reminder.js +21 -1
  87. package/dist/mcp/tools/create-task.js +290 -164
  88. package/dist/mcp/tools/deactivate-behavior.js +24 -4
  89. package/dist/mcp/tools/list-reminders.js +21 -1
  90. package/dist/mcp/tools/list-tasks.js +195 -38
  91. package/dist/mcp/tools/send-message.js +58 -31
  92. package/dist/mcp/tools/update-task.js +75 -48
  93. package/dist/runtime/index.js +720 -89
  94. package/dist/tui/App.js +853 -123
  95. package/package.json +3 -2
@@ -339,7 +339,7 @@ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
339
339
  return [];
340
340
  }
341
341
  }
342
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE;
342
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, IDENTITY_DIR;
343
343
  var init_employees = __esm({
344
344
  "src/lib/employees.ts"() {
345
345
  "use strict";
@@ -347,12 +347,609 @@ var init_employees = __esm({
347
347
  EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
348
348
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
349
349
  COORDINATOR_ROLE = "COO";
350
+ IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
351
+ }
352
+ });
353
+
354
+ // src/lib/database-adapter.ts
355
+ import os3 from "os";
356
+ import path3 from "path";
357
+ import { createRequire } from "module";
358
+ import { pathToFileURL } from "url";
359
+ function quotedIdentifier(identifier) {
360
+ return `"${identifier.replace(/"/g, '""')}"`;
361
+ }
362
+ function unqualifiedTableName(name) {
363
+ const raw = name.trim().replace(/^"|"$/g, "");
364
+ const parts = raw.split(".");
365
+ return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
366
+ }
367
+ function stripTrailingSemicolon(sql) {
368
+ return sql.trim().replace(/;+\s*$/u, "");
369
+ }
370
+ function appendClause(sql, clause) {
371
+ const trimmed = stripTrailingSemicolon(sql);
372
+ const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
373
+ if (!returningMatch) {
374
+ return `${trimmed}${clause}`;
375
+ }
376
+ const idx = returningMatch.index;
377
+ return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
378
+ }
379
+ function normalizeStatement(stmt) {
380
+ if (typeof stmt === "string") {
381
+ return { kind: "positional", sql: stmt, args: [] };
382
+ }
383
+ const sql = stmt.sql;
384
+ if (Array.isArray(stmt.args) || stmt.args === void 0) {
385
+ return { kind: "positional", sql, args: stmt.args ?? [] };
386
+ }
387
+ return { kind: "named", sql, args: stmt.args };
388
+ }
389
+ function rewriteBooleanLiterals(sql) {
390
+ let out = sql;
391
+ for (const column of BOOLEAN_COLUMN_NAMES) {
392
+ const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
393
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
394
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
395
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
396
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
397
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
398
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
399
+ }
400
+ return out;
401
+ }
402
+ function rewriteInsertOrIgnore(sql) {
403
+ if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
404
+ return sql;
405
+ }
406
+ const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
407
+ return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
408
+ }
409
+ function rewriteInsertOrReplace(sql) {
410
+ const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
411
+ if (!match) {
412
+ return sql;
413
+ }
414
+ const rawTable = match[1];
415
+ const rawColumns = match[2];
416
+ const remainder = match[3];
417
+ const tableName = unqualifiedTableName(rawTable);
418
+ const conflictKeys = UPSERT_KEYS[tableName];
419
+ if (!conflictKeys?.length) {
420
+ return sql;
421
+ }
422
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
423
+ const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
424
+ const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
425
+ const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
426
+ return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
427
+ }
428
+ function rewriteSql(sql) {
429
+ let out = sql;
430
+ out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
431
+ out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
432
+ out = rewriteBooleanLiterals(out);
433
+ out = rewriteInsertOrReplace(out);
434
+ out = rewriteInsertOrIgnore(out);
435
+ return stripTrailingSemicolon(out);
436
+ }
437
+ function toBoolean(value) {
438
+ if (value === null || value === void 0) return value;
439
+ if (typeof value === "boolean") return value;
440
+ if (typeof value === "number") return value !== 0;
441
+ if (typeof value === "bigint") return value !== 0n;
442
+ if (typeof value === "string") {
443
+ const normalized = value.trim().toLowerCase();
444
+ if (normalized === "0" || normalized === "false") return false;
445
+ if (normalized === "1" || normalized === "true") return true;
446
+ }
447
+ return Boolean(value);
448
+ }
449
+ function countQuestionMarks(sql, end) {
450
+ let count = 0;
451
+ let inSingle = false;
452
+ let inDouble = false;
453
+ let inLineComment = false;
454
+ let inBlockComment = false;
455
+ for (let i = 0; i < end; i++) {
456
+ const ch = sql[i];
457
+ const next = sql[i + 1];
458
+ if (inLineComment) {
459
+ if (ch === "\n") inLineComment = false;
460
+ continue;
461
+ }
462
+ if (inBlockComment) {
463
+ if (ch === "*" && next === "/") {
464
+ inBlockComment = false;
465
+ i += 1;
466
+ }
467
+ continue;
468
+ }
469
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
470
+ inLineComment = true;
471
+ i += 1;
472
+ continue;
473
+ }
474
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
475
+ inBlockComment = true;
476
+ i += 1;
477
+ continue;
478
+ }
479
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
480
+ inSingle = !inSingle;
481
+ continue;
482
+ }
483
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
484
+ inDouble = !inDouble;
485
+ continue;
486
+ }
487
+ if (!inSingle && !inDouble && ch === "?") {
488
+ count += 1;
489
+ }
490
+ }
491
+ return count;
492
+ }
493
+ function findBooleanPlaceholderIndexes(sql) {
494
+ const indexes = /* @__PURE__ */ new Set();
495
+ for (const column of BOOLEAN_COLUMN_NAMES) {
496
+ const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
497
+ for (const match of sql.matchAll(pattern)) {
498
+ const matchText = match[0];
499
+ const qIndex = match.index + matchText.lastIndexOf("?");
500
+ indexes.add(countQuestionMarks(sql, qIndex + 1));
501
+ }
502
+ }
503
+ return indexes;
504
+ }
505
+ function coerceInsertBooleanArgs(sql, args) {
506
+ const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
507
+ if (!match) return;
508
+ const rawTable = match[1];
509
+ const rawColumns = match[2];
510
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
511
+ if (!boolColumns?.size) return;
512
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
513
+ for (const [index, column] of columns.entries()) {
514
+ if (boolColumns.has(column) && index < args.length) {
515
+ args[index] = toBoolean(args[index]);
516
+ }
517
+ }
518
+ }
519
+ function coerceUpdateBooleanArgs(sql, args) {
520
+ const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
521
+ if (!match) return;
522
+ const rawTable = match[1];
523
+ const setClause = match[2];
524
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
525
+ if (!boolColumns?.size) return;
526
+ const assignments = setClause.split(",");
527
+ let placeholderIndex = 0;
528
+ for (const assignment of assignments) {
529
+ if (!assignment.includes("?")) continue;
530
+ placeholderIndex += 1;
531
+ const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
532
+ if (colMatch && boolColumns.has(colMatch[1])) {
533
+ args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
534
+ }
535
+ }
536
+ }
537
+ function coerceBooleanArgs(sql, args) {
538
+ const nextArgs = [...args];
539
+ coerceInsertBooleanArgs(sql, nextArgs);
540
+ coerceUpdateBooleanArgs(sql, nextArgs);
541
+ const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
542
+ for (const index of placeholderIndexes) {
543
+ if (index > 0 && index <= nextArgs.length) {
544
+ nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
545
+ }
546
+ }
547
+ return nextArgs;
548
+ }
549
+ function convertQuestionMarksToDollarParams(sql) {
550
+ let out = "";
551
+ let placeholder = 0;
552
+ let inSingle = false;
553
+ let inDouble = false;
554
+ let inLineComment = false;
555
+ let inBlockComment = false;
556
+ for (let i = 0; i < sql.length; i++) {
557
+ const ch = sql[i];
558
+ const next = sql[i + 1];
559
+ if (inLineComment) {
560
+ out += ch;
561
+ if (ch === "\n") inLineComment = false;
562
+ continue;
563
+ }
564
+ if (inBlockComment) {
565
+ out += ch;
566
+ if (ch === "*" && next === "/") {
567
+ out += next;
568
+ inBlockComment = false;
569
+ i += 1;
570
+ }
571
+ continue;
572
+ }
573
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
574
+ out += ch + next;
575
+ inLineComment = true;
576
+ i += 1;
577
+ continue;
578
+ }
579
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
580
+ out += ch + next;
581
+ inBlockComment = true;
582
+ i += 1;
583
+ continue;
584
+ }
585
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
586
+ inSingle = !inSingle;
587
+ out += ch;
588
+ continue;
589
+ }
590
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
591
+ inDouble = !inDouble;
592
+ out += ch;
593
+ continue;
594
+ }
595
+ if (!inSingle && !inDouble && ch === "?") {
596
+ placeholder += 1;
597
+ out += `$${placeholder}`;
598
+ continue;
599
+ }
600
+ out += ch;
601
+ }
602
+ return out;
603
+ }
604
+ function translateStatementForPostgres(stmt) {
605
+ const normalized = normalizeStatement(stmt);
606
+ if (normalized.kind === "named") {
607
+ throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
608
+ }
609
+ const rewrittenSql = rewriteSql(normalized.sql);
610
+ const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
611
+ return {
612
+ sql: convertQuestionMarksToDollarParams(rewrittenSql),
613
+ args: coercedArgs
614
+ };
615
+ }
616
+ function shouldBypassPostgres(stmt) {
617
+ const normalized = normalizeStatement(stmt);
618
+ if (normalized.kind === "named") {
619
+ return true;
620
+ }
621
+ return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
622
+ }
623
+ function shouldFallbackOnError(error) {
624
+ const message = error instanceof Error ? error.message : String(error);
625
+ return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
626
+ }
627
+ function isReadQuery(sql) {
628
+ const trimmed = sql.trimStart();
629
+ return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
630
+ }
631
+ function buildRow(row, columns) {
632
+ const values = columns.map((column) => row[column]);
633
+ return Object.assign(values, row);
634
+ }
635
+ function buildResultSet(rows, rowsAffected = 0) {
636
+ const columns = rows[0] ? Object.keys(rows[0]) : [];
637
+ const resultRows = rows.map((row) => buildRow(row, columns));
638
+ return {
639
+ columns,
640
+ columnTypes: columns.map(() => ""),
641
+ rows: resultRows,
642
+ rowsAffected,
643
+ lastInsertRowid: void 0,
644
+ toJSON() {
645
+ return {
646
+ columns,
647
+ columnTypes: columns.map(() => ""),
648
+ rows,
649
+ rowsAffected,
650
+ lastInsertRowid: void 0
651
+ };
652
+ }
653
+ };
654
+ }
655
+ async function loadPrismaClient() {
656
+ if (!prismaClientPromise) {
657
+ prismaClientPromise = (async () => {
658
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
659
+ if (explicitPath) {
660
+ const module2 = await import(pathToFileURL(explicitPath).href);
661
+ const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
662
+ if (!PrismaClient2) {
663
+ throw new Error(`No PrismaClient export found at ${explicitPath}`);
664
+ }
665
+ return new PrismaClient2();
666
+ }
667
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path3.join(os3.homedir(), "exe-db");
668
+ const requireFromExeDb = createRequire(path3.join(exeDbRoot, "package.json"));
669
+ const prismaEntry = requireFromExeDb.resolve("@prisma/client");
670
+ const module = await import(pathToFileURL(prismaEntry).href);
671
+ const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
672
+ if (!PrismaClient) {
673
+ throw new Error(`No PrismaClient export found in ${prismaEntry}`);
674
+ }
675
+ return new PrismaClient();
676
+ })();
677
+ }
678
+ return prismaClientPromise;
679
+ }
680
+ async function ensureCompatibilityViews(prisma) {
681
+ if (!compatibilityBootstrapPromise) {
682
+ compatibilityBootstrapPromise = (async () => {
683
+ for (const mapping of VIEW_MAPPINGS) {
684
+ const relation = mapping.source.replace(/"/g, "");
685
+ const rows = await prisma.$queryRawUnsafe(
686
+ "SELECT to_regclass($1) AS regclass",
687
+ relation
688
+ );
689
+ if (!rows[0]?.regclass) {
690
+ continue;
691
+ }
692
+ await prisma.$executeRawUnsafe(
693
+ `CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
694
+ );
695
+ }
696
+ })();
697
+ }
698
+ return compatibilityBootstrapPromise;
699
+ }
700
+ async function executeOnPrisma(executor, stmt) {
701
+ const translated = translateStatementForPostgres(stmt);
702
+ if (isReadQuery(translated.sql)) {
703
+ const rows = await executor.$queryRawUnsafe(
704
+ translated.sql,
705
+ ...translated.args
706
+ );
707
+ return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
708
+ }
709
+ const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
710
+ return buildResultSet([], rowsAffected);
711
+ }
712
+ function splitSqlStatements(sql) {
713
+ const parts = [];
714
+ let current = "";
715
+ let inSingle = false;
716
+ let inDouble = false;
717
+ let inLineComment = false;
718
+ let inBlockComment = false;
719
+ for (let i = 0; i < sql.length; i++) {
720
+ const ch = sql[i];
721
+ const next = sql[i + 1];
722
+ if (inLineComment) {
723
+ current += ch;
724
+ if (ch === "\n") inLineComment = false;
725
+ continue;
726
+ }
727
+ if (inBlockComment) {
728
+ current += ch;
729
+ if (ch === "*" && next === "/") {
730
+ current += next;
731
+ inBlockComment = false;
732
+ i += 1;
733
+ }
734
+ continue;
735
+ }
736
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
737
+ current += ch + next;
738
+ inLineComment = true;
739
+ i += 1;
740
+ continue;
741
+ }
742
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
743
+ current += ch + next;
744
+ inBlockComment = true;
745
+ i += 1;
746
+ continue;
747
+ }
748
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
749
+ inSingle = !inSingle;
750
+ current += ch;
751
+ continue;
752
+ }
753
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
754
+ inDouble = !inDouble;
755
+ current += ch;
756
+ continue;
757
+ }
758
+ if (!inSingle && !inDouble && ch === ";") {
759
+ if (current.trim()) {
760
+ parts.push(current.trim());
761
+ }
762
+ current = "";
763
+ continue;
764
+ }
765
+ current += ch;
766
+ }
767
+ if (current.trim()) {
768
+ parts.push(current.trim());
769
+ }
770
+ return parts;
771
+ }
772
+ async function createPrismaDbAdapter(fallbackClient) {
773
+ const prisma = await loadPrismaClient();
774
+ await ensureCompatibilityViews(prisma);
775
+ let closed = false;
776
+ let adapter;
777
+ const fallbackExecute = async (stmt, error) => {
778
+ if (!fallbackClient) {
779
+ if (error) throw error;
780
+ throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
781
+ }
782
+ if (error) {
783
+ process.stderr.write(
784
+ `[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
785
+ `
786
+ );
787
+ }
788
+ return fallbackClient.execute(stmt);
789
+ };
790
+ adapter = {
791
+ async execute(stmt) {
792
+ if (shouldBypassPostgres(stmt)) {
793
+ return fallbackExecute(stmt);
794
+ }
795
+ try {
796
+ return await executeOnPrisma(prisma, stmt);
797
+ } catch (error) {
798
+ if (shouldFallbackOnError(error)) {
799
+ return fallbackExecute(stmt, error);
800
+ }
801
+ throw error;
802
+ }
803
+ },
804
+ async batch(stmts, mode) {
805
+ if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
806
+ if (!fallbackClient) {
807
+ throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
808
+ }
809
+ return fallbackClient.batch(stmts, mode);
810
+ }
811
+ try {
812
+ if (prisma.$transaction) {
813
+ return await prisma.$transaction(async (tx) => {
814
+ const results2 = [];
815
+ for (const stmt of stmts) {
816
+ results2.push(await executeOnPrisma(tx, stmt));
817
+ }
818
+ return results2;
819
+ });
820
+ }
821
+ const results = [];
822
+ for (const stmt of stmts) {
823
+ results.push(await executeOnPrisma(prisma, stmt));
824
+ }
825
+ return results;
826
+ } catch (error) {
827
+ if (fallbackClient && shouldFallbackOnError(error)) {
828
+ process.stderr.write(
829
+ `[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
830
+ `
831
+ );
832
+ return fallbackClient.batch(stmts, mode);
833
+ }
834
+ throw error;
835
+ }
836
+ },
837
+ async migrate(stmts) {
838
+ if (fallbackClient) {
839
+ return fallbackClient.migrate(stmts);
840
+ }
841
+ return adapter.batch(stmts, "deferred");
842
+ },
843
+ async transaction(mode) {
844
+ if (!fallbackClient) {
845
+ throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
846
+ }
847
+ return fallbackClient.transaction(mode);
848
+ },
849
+ async executeMultiple(sql) {
850
+ if (fallbackClient && shouldBypassPostgres(sql)) {
851
+ return fallbackClient.executeMultiple(sql);
852
+ }
853
+ for (const statement of splitSqlStatements(sql)) {
854
+ await adapter.execute(statement);
855
+ }
856
+ },
857
+ async sync() {
858
+ if (fallbackClient) {
859
+ return fallbackClient.sync();
860
+ }
861
+ return { frame_no: 0, frames_synced: 0 };
862
+ },
863
+ close() {
864
+ closed = true;
865
+ prismaClientPromise = null;
866
+ compatibilityBootstrapPromise = null;
867
+ void prisma.$disconnect?.();
868
+ },
869
+ get closed() {
870
+ return closed;
871
+ },
872
+ get protocol() {
873
+ return "prisma-postgres";
874
+ }
875
+ };
876
+ return adapter;
877
+ }
878
+ var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
879
+ var init_database_adapter = __esm({
880
+ "src/lib/database-adapter.ts"() {
881
+ "use strict";
882
+ VIEW_MAPPINGS = [
883
+ { view: "memories", source: "memory.memory_records" },
884
+ { view: "tasks", source: "memory.tasks" },
885
+ { view: "behaviors", source: "memory.behaviors" },
886
+ { view: "entities", source: "memory.entities" },
887
+ { view: "relationships", source: "memory.relationships" },
888
+ { view: "entity_memories", source: "memory.entity_memories" },
889
+ { view: "entity_aliases", source: "memory.entity_aliases" },
890
+ { view: "notifications", source: "memory.notifications" },
891
+ { view: "messages", source: "memory.messages" },
892
+ { view: "users", source: "wiki.users" },
893
+ { view: "workspaces", source: "wiki.workspaces" },
894
+ { view: "workspace_users", source: "wiki.workspace_users" },
895
+ { view: "documents", source: "wiki.workspace_documents" },
896
+ { view: "chats", source: "wiki.workspace_chats" }
897
+ ];
898
+ UPSERT_KEYS = {
899
+ memories: ["id"],
900
+ tasks: ["id"],
901
+ behaviors: ["id"],
902
+ entities: ["id"],
903
+ relationships: ["id"],
904
+ entity_aliases: ["alias"],
905
+ notifications: ["id"],
906
+ messages: ["id"],
907
+ users: ["id"],
908
+ workspaces: ["id"],
909
+ workspace_users: ["id"],
910
+ documents: ["id"],
911
+ chats: ["id"]
912
+ };
913
+ BOOLEAN_COLUMNS_BY_TABLE = {
914
+ memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
915
+ behaviors: /* @__PURE__ */ new Set(["active"]),
916
+ notifications: /* @__PURE__ */ new Set(["read"]),
917
+ users: /* @__PURE__ */ new Set(["has_personal_memory"])
918
+ };
919
+ BOOLEAN_COLUMN_NAMES = new Set(
920
+ Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
921
+ );
922
+ IMMEDIATE_FALLBACK_PATTERNS = [
923
+ /\bPRAGMA\b/i,
924
+ /\bsqlite_master\b/i,
925
+ /(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
926
+ /\bMATCH\b/i,
927
+ /\bvector_distance_cos\s*\(/i,
928
+ /\bjson_extract\s*\(/i,
929
+ /\bjulianday\s*\(/i,
930
+ /\bstrftime\s*\(/i,
931
+ /\blast_insert_rowid\s*\(/i
932
+ ];
933
+ prismaClientPromise = null;
934
+ compatibilityBootstrapPromise = null;
350
935
  }
351
936
  });
352
937
 
353
938
  // src/lib/database.ts
354
939
  import { createClient } from "@libsql/client";
355
940
  async function initDatabase(config) {
941
+ if (_walCheckpointTimer) {
942
+ clearInterval(_walCheckpointTimer);
943
+ _walCheckpointTimer = null;
944
+ }
945
+ if (_daemonClient) {
946
+ _daemonClient.close();
947
+ _daemonClient = null;
948
+ }
949
+ if (_adapterClient && _adapterClient !== _resilientClient) {
950
+ _adapterClient.close();
951
+ }
952
+ _adapterClient = null;
356
953
  if (_client) {
357
954
  _client.close();
358
955
  _client = null;
@@ -366,6 +963,7 @@ async function initDatabase(config) {
366
963
  }
367
964
  _client = createClient(opts);
368
965
  _resilientClient = wrapWithRetry(_client);
966
+ _adapterClient = _resilientClient;
369
967
  _client.execute("PRAGMA busy_timeout = 30000").catch(() => {
370
968
  });
371
969
  _client.execute("PRAGMA journal_mode = WAL").catch(() => {
@@ -376,11 +974,17 @@ async function initDatabase(config) {
376
974
  });
377
975
  }, 3e4);
378
976
  _walCheckpointTimer.unref();
977
+ if (process.env.DATABASE_URL) {
978
+ _adapterClient = await createPrismaDbAdapter(_resilientClient);
979
+ }
379
980
  }
380
981
  function getClient() {
381
- if (!_resilientClient) {
982
+ if (!_adapterClient) {
382
983
  throw new Error("Database client not initialized. Call initDatabase() first.");
383
984
  }
985
+ if (process.env.DATABASE_URL) {
986
+ return _adapterClient;
987
+ }
384
988
  if (process.env.EXE_IS_DAEMON === "1") {
385
989
  return _resilientClient;
386
990
  }
@@ -1321,26 +1925,36 @@ async function ensureSchema() {
1321
1925
  }
1322
1926
  }
1323
1927
  async function disposeDatabase() {
1928
+ if (_walCheckpointTimer) {
1929
+ clearInterval(_walCheckpointTimer);
1930
+ _walCheckpointTimer = null;
1931
+ }
1324
1932
  if (_daemonClient) {
1325
1933
  _daemonClient.close();
1326
1934
  _daemonClient = null;
1327
1935
  }
1936
+ if (_adapterClient && _adapterClient !== _resilientClient) {
1937
+ _adapterClient.close();
1938
+ }
1939
+ _adapterClient = null;
1328
1940
  if (_client) {
1329
1941
  _client.close();
1330
1942
  _client = null;
1331
1943
  _resilientClient = null;
1332
1944
  }
1333
1945
  }
1334
- var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
1946
+ var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
1335
1947
  var init_database = __esm({
1336
1948
  "src/lib/database.ts"() {
1337
1949
  "use strict";
1338
1950
  init_db_retry();
1339
1951
  init_employees();
1952
+ init_database_adapter();
1340
1953
  _client = null;
1341
1954
  _resilientClient = null;
1342
1955
  _walCheckpointTimer = null;
1343
1956
  _daemonClient = null;
1957
+ _adapterClient = null;
1344
1958
  initTurso = initDatabase;
1345
1959
  disposeTurso = disposeDatabase;
1346
1960
  }
@@ -1349,13 +1963,13 @@ var init_database = __esm({
1349
1963
  // src/lib/keychain.ts
1350
1964
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
1351
1965
  import { existsSync as existsSync3 } from "fs";
1352
- import path3 from "path";
1353
- import os3 from "os";
1966
+ import path4 from "path";
1967
+ import os4 from "os";
1354
1968
  function getKeyDir() {
1355
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path3.join(os3.homedir(), ".exe-os");
1969
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path4.join(os4.homedir(), ".exe-os");
1356
1970
  }
1357
1971
  function getKeyPath() {
1358
- return path3.join(getKeyDir(), "master.key");
1972
+ return path4.join(getKeyDir(), "master.key");
1359
1973
  }
1360
1974
  async function tryKeytar() {
1361
1975
  try {
@@ -1378,7 +1992,7 @@ async function getMasterKey() {
1378
1992
  const keyPath = getKeyPath();
1379
1993
  if (!existsSync3(keyPath)) {
1380
1994
  process.stderr.write(
1381
- `[keychain] Key not found at ${keyPath} (HOME=${os3.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
1995
+ `[keychain] Key not found at ${keyPath} (HOME=${os4.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
1382
1996
  `
1383
1997
  );
1384
1998
  return null;
@@ -1471,7 +2085,7 @@ __export(shard_manager_exports, {
1471
2085
  listShards: () => listShards,
1472
2086
  shardExists: () => shardExists
1473
2087
  });
1474
- import path4 from "path";
2088
+ import path5 from "path";
1475
2089
  import { existsSync as existsSync4, mkdirSync, readdirSync } from "fs";
1476
2090
  import { createClient as createClient2 } from "@libsql/client";
1477
2091
  function initShardManager(encryptionKey) {
@@ -1497,7 +2111,7 @@ function getShardClient(projectName) {
1497
2111
  }
1498
2112
  const cached = _shards.get(safeName);
1499
2113
  if (cached) return cached;
1500
- const dbPath = path4.join(SHARDS_DIR, `${safeName}.db`);
2114
+ const dbPath = path5.join(SHARDS_DIR, `${safeName}.db`);
1501
2115
  const client = createClient2({
1502
2116
  url: `file:${dbPath}`,
1503
2117
  encryptionKey: _encryptionKey
@@ -1507,7 +2121,7 @@ function getShardClient(projectName) {
1507
2121
  }
1508
2122
  function shardExists(projectName) {
1509
2123
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
1510
- return existsSync4(path4.join(SHARDS_DIR, `${safeName}.db`));
2124
+ return existsSync4(path5.join(SHARDS_DIR, `${safeName}.db`));
1511
2125
  }
1512
2126
  function listShards() {
1513
2127
  if (!existsSync4(SHARDS_DIR)) return [];
@@ -1584,7 +2198,23 @@ async function ensureShardSchema(client) {
1584
2198
  // MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
1585
2199
  "ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
1586
2200
  "ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
1587
- "ALTER TABLE memories ADD COLUMN trajectory TEXT"
2201
+ "ALTER TABLE memories ADD COLUMN trajectory TEXT",
2202
+ // Metadata enrichment columns (must match database.ts)
2203
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
2204
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
2205
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
2206
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
2207
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
2208
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
2209
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
2210
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
2211
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
2212
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
2213
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
2214
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
2215
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
2216
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
2217
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
1588
2218
  ]) {
1589
2219
  try {
1590
2220
  await client.execute(col);
@@ -1696,7 +2326,7 @@ var init_shard_manager = __esm({
1696
2326
  "src/lib/shard-manager.ts"() {
1697
2327
  "use strict";
1698
2328
  init_config();
1699
- SHARDS_DIR = path4.join(EXE_AI_DIR, "shards");
2329
+ SHARDS_DIR = path5.join(EXE_AI_DIR, "shards");
1700
2330
  _shards = /* @__PURE__ */ new Map();
1701
2331
  _encryptionKey = null;
1702
2332
  _shardingEnabled = false;
@@ -2559,7 +3189,7 @@ __export(reranker_exports, {
2559
3189
  rerankWithContext: () => rerankWithContext,
2560
3190
  rerankWithScores: () => rerankWithScores
2561
3191
  });
2562
- import path5 from "path";
3192
+ import path6 from "path";
2563
3193
  import { existsSync as existsSync5 } from "fs";
2564
3194
  function resetIdleTimer() {
2565
3195
  if (_idleTimer) clearTimeout(_idleTimer);
@@ -2571,17 +3201,17 @@ function resetIdleTimer() {
2571
3201
  }
2572
3202
  }
2573
3203
  function isRerankerAvailable() {
2574
- return existsSync5(path5.join(MODELS_DIR, RERANKER_MODEL_FILE));
3204
+ return existsSync5(path6.join(MODELS_DIR, RERANKER_MODEL_FILE));
2575
3205
  }
2576
3206
  function getRerankerModelPath() {
2577
- return path5.join(MODELS_DIR, RERANKER_MODEL_FILE);
3207
+ return path6.join(MODELS_DIR, RERANKER_MODEL_FILE);
2578
3208
  }
2579
3209
  async function ensureLoaded() {
2580
3210
  if (_rerankerContext) {
2581
3211
  resetIdleTimer();
2582
3212
  return;
2583
3213
  }
2584
- const modelPath = path5.join(MODELS_DIR, RERANKER_MODEL_FILE);
3214
+ const modelPath = path6.join(MODELS_DIR, RERANKER_MODEL_FILE);
2585
3215
  if (!existsSync5(modelPath)) {
2586
3216
  throw new Error(
2587
3217
  `Reranker model not found at ${modelPath}. Run /exe-setup to download it.`
@@ -2680,11 +3310,11 @@ var init_reranker = __esm({
2680
3310
 
2681
3311
  // src/lib/exe-daemon-client.ts
2682
3312
  import net from "net";
2683
- import os4 from "os";
3313
+ import os5 from "os";
2684
3314
  import { spawn } from "child_process";
2685
3315
  import { randomUUID as randomUUID2 } from "crypto";
2686
3316
  import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
2687
- import path6 from "path";
3317
+ import path7 from "path";
2688
3318
  import { fileURLToPath } from "url";
2689
3319
  function handleData(chunk) {
2690
3320
  _buffer += chunk.toString();
@@ -2735,17 +3365,17 @@ function cleanupStaleFiles() {
2735
3365
  }
2736
3366
  }
2737
3367
  function findPackageRoot() {
2738
- let dir = path6.dirname(fileURLToPath(import.meta.url));
2739
- const { root } = path6.parse(dir);
3368
+ let dir = path7.dirname(fileURLToPath(import.meta.url));
3369
+ const { root } = path7.parse(dir);
2740
3370
  while (dir !== root) {
2741
- if (existsSync6(path6.join(dir, "package.json"))) return dir;
2742
- dir = path6.dirname(dir);
3371
+ if (existsSync6(path7.join(dir, "package.json"))) return dir;
3372
+ dir = path7.dirname(dir);
2743
3373
  }
2744
3374
  return null;
2745
3375
  }
2746
3376
  function spawnDaemon() {
2747
- const freeGB = os4.freemem() / (1024 * 1024 * 1024);
2748
- const totalGB = os4.totalmem() / (1024 * 1024 * 1024);
3377
+ const freeGB = os5.freemem() / (1024 * 1024 * 1024);
3378
+ const totalGB = os5.totalmem() / (1024 * 1024 * 1024);
2749
3379
  if (totalGB <= 8) {
2750
3380
  process.stderr.write(
2751
3381
  `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
@@ -2765,7 +3395,7 @@ function spawnDaemon() {
2765
3395
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
2766
3396
  return;
2767
3397
  }
2768
- const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
3398
+ const daemonPath = path7.join(pkgRoot, "dist", "lib", "exe-daemon.js");
2769
3399
  if (!existsSync6(daemonPath)) {
2770
3400
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
2771
3401
  `);
@@ -2774,7 +3404,7 @@ function spawnDaemon() {
2774
3404
  const resolvedPath = daemonPath;
2775
3405
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
2776
3406
  `);
2777
- const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
3407
+ const logPath = path7.join(path7.dirname(SOCKET_PATH), "exed.log");
2778
3408
  let stderrFd = "ignore";
2779
3409
  try {
2780
3410
  stderrFd = openSync(logPath, "a");
@@ -2925,74 +3555,123 @@ async function pingDaemon() {
2925
3555
  return null;
2926
3556
  }
2927
3557
  function killAndRespawnDaemon() {
2928
- process.stderr.write("[exed-client] Killing daemon for restart...\n");
2929
- if (existsSync6(PID_PATH)) {
2930
- try {
2931
- const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
2932
- if (pid > 0) {
2933
- try {
2934
- process.kill(pid, "SIGKILL");
2935
- } catch {
3558
+ if (!acquireSpawnLock()) {
3559
+ process.stderr.write("[exed-client] Another process is already restarting daemon \u2014 skipping\n");
3560
+ if (_socket) {
3561
+ _socket.destroy();
3562
+ _socket = null;
3563
+ }
3564
+ _connected = false;
3565
+ _buffer = "";
3566
+ return;
3567
+ }
3568
+ try {
3569
+ process.stderr.write("[exed-client] Killing daemon for restart...\n");
3570
+ if (existsSync6(PID_PATH)) {
3571
+ try {
3572
+ const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
3573
+ if (pid > 0) {
3574
+ try {
3575
+ process.kill(pid, "SIGKILL");
3576
+ } catch {
3577
+ }
2936
3578
  }
3579
+ } catch {
2937
3580
  }
3581
+ }
3582
+ if (_socket) {
3583
+ _socket.destroy();
3584
+ _socket = null;
3585
+ }
3586
+ _connected = false;
3587
+ _buffer = "";
3588
+ try {
3589
+ unlinkSync2(PID_PATH);
2938
3590
  } catch {
2939
3591
  }
3592
+ try {
3593
+ unlinkSync2(SOCKET_PATH);
3594
+ } catch {
3595
+ }
3596
+ spawnDaemon();
3597
+ } finally {
3598
+ releaseSpawnLock();
2940
3599
  }
2941
- if (_socket) {
2942
- _socket.destroy();
2943
- _socket = null;
2944
- }
2945
- _connected = false;
2946
- _buffer = "";
3600
+ }
3601
+ function isDaemonTooYoung() {
2947
3602
  try {
2948
- unlinkSync2(PID_PATH);
3603
+ const stat = statSync(PID_PATH);
3604
+ return Date.now() - stat.mtimeMs < MIN_DAEMON_AGE_MS;
2949
3605
  } catch {
3606
+ return false;
2950
3607
  }
2951
- try {
2952
- unlinkSync2(SOCKET_PATH);
2953
- } catch {
3608
+ }
3609
+ async function retryThenRestart(doRequest, label) {
3610
+ const result = await doRequest();
3611
+ if (!result.error) {
3612
+ _consecutiveFailures = 0;
3613
+ return result;
3614
+ }
3615
+ _consecutiveFailures++;
3616
+ for (let i = 0; i < MAX_RETRIES_BEFORE_RESTART; i++) {
3617
+ const delayMs = RETRY_DELAYS_MS[i] ?? 5e3;
3618
+ process.stderr.write(`[exed-client] ${label} failed (${result.error}), retry ${i + 1}/${MAX_RETRIES_BEFORE_RESTART} in ${delayMs}ms
3619
+ `);
3620
+ await new Promise((r) => setTimeout(r, delayMs));
3621
+ if (!_connected) {
3622
+ if (!await connectToSocket()) continue;
3623
+ }
3624
+ const retry = await doRequest();
3625
+ if (!retry.error) {
3626
+ _consecutiveFailures = 0;
3627
+ return retry;
3628
+ }
3629
+ _consecutiveFailures++;
3630
+ }
3631
+ if (isDaemonTooYoung()) {
3632
+ process.stderr.write(`[exed-client] ${label}: daemon too young (< ${MIN_DAEMON_AGE_MS / 1e3}s) \u2014 skipping restart
3633
+ `);
3634
+ return { error: result.error };
3635
+ }
3636
+ process.stderr.write(`[exed-client] ${label}: ${_consecutiveFailures} consecutive failures \u2014 restarting daemon
3637
+ `);
3638
+ killAndRespawnDaemon();
3639
+ const start = Date.now();
3640
+ let delay2 = 200;
3641
+ while (Date.now() - start < CONNECT_TIMEOUT_MS) {
3642
+ await new Promise((r) => setTimeout(r, delay2));
3643
+ if (await connectToSocket()) break;
3644
+ delay2 = Math.min(delay2 * 2, 3e3);
2954
3645
  }
2955
- spawnDaemon();
3646
+ if (!_connected) return { error: "Daemon restart failed" };
3647
+ const final = await doRequest();
3648
+ if (!final.error) _consecutiveFailures = 0;
3649
+ return final;
2956
3650
  }
2957
3651
  async function embedViaClient(text, priority = "high") {
2958
3652
  if (!_connected && !await connectEmbedDaemon()) return null;
2959
3653
  _requestCount++;
2960
3654
  if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
2961
3655
  const health = await pingDaemon();
2962
- if (!health) {
3656
+ if (!health && !isDaemonTooYoung()) {
2963
3657
  process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
2964
3658
  `);
2965
3659
  killAndRespawnDaemon();
2966
3660
  const start = Date.now();
2967
- let delay2 = 200;
3661
+ let d = 200;
2968
3662
  while (Date.now() - start < CONNECT_TIMEOUT_MS) {
2969
- await new Promise((r) => setTimeout(r, delay2));
3663
+ await new Promise((r) => setTimeout(r, d));
2970
3664
  if (await connectToSocket()) break;
2971
- delay2 = Math.min(delay2 * 2, 3e3);
3665
+ d = Math.min(d * 2, 3e3);
2972
3666
  }
2973
3667
  if (!_connected) return null;
2974
3668
  }
2975
3669
  }
2976
- const result = await sendRequest([text], priority);
2977
- if (!result.error && result.vectors?.[0]) return result.vectors[0];
2978
- if (result.error) {
2979
- process.stderr.write(`[exed-client] Embed failed (${result.error}) \u2014 attempting restart
2980
- `);
2981
- killAndRespawnDaemon();
2982
- const start = Date.now();
2983
- let delay2 = 200;
2984
- while (Date.now() - start < CONNECT_TIMEOUT_MS) {
2985
- await new Promise((r) => setTimeout(r, delay2));
2986
- if (await connectToSocket()) break;
2987
- delay2 = Math.min(delay2 * 2, 3e3);
2988
- }
2989
- if (!_connected) return null;
2990
- const retry = await sendRequest([text], priority);
2991
- if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
2992
- process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
2993
- `);
2994
- }
2995
- return null;
3670
+ const result = await retryThenRestart(
3671
+ () => sendRequest([text], priority),
3672
+ "Embed"
3673
+ );
3674
+ return !result.error && result.vectors?.[0] ? result.vectors[0] : null;
2996
3675
  }
2997
3676
  function disconnectClient() {
2998
3677
  if (_socket) {
@@ -3007,14 +3686,14 @@ function disconnectClient() {
3007
3686
  entry.resolve({ error: "Client disconnected" });
3008
3687
  }
3009
3688
  }
3010
- 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;
3689
+ var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, _consecutiveFailures, HEALTH_CHECK_INTERVAL, MAX_RETRIES_BEFORE_RESTART, RETRY_DELAYS_MS, MIN_DAEMON_AGE_MS, _pending, MAX_BUFFER;
3011
3690
  var init_exe_daemon_client = __esm({
3012
3691
  "src/lib/exe-daemon-client.ts"() {
3013
3692
  "use strict";
3014
3693
  init_config();
3015
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
3016
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
3017
- SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
3694
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path7.join(EXE_AI_DIR, "exed.sock");
3695
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path7.join(EXE_AI_DIR, "exed.pid");
3696
+ SPAWN_LOCK_PATH = path7.join(EXE_AI_DIR, "exed-spawn.lock");
3018
3697
  SPAWN_LOCK_STALE_MS = 3e4;
3019
3698
  CONNECT_TIMEOUT_MS = 15e3;
3020
3699
  REQUEST_TIMEOUT_MS = 3e4;
@@ -3022,7 +3701,11 @@ var init_exe_daemon_client = __esm({
3022
3701
  _connected = false;
3023
3702
  _buffer = "";
3024
3703
  _requestCount = 0;
3704
+ _consecutiveFailures = 0;
3025
3705
  HEALTH_CHECK_INTERVAL = 100;
3706
+ MAX_RETRIES_BEFORE_RESTART = 3;
3707
+ RETRY_DELAYS_MS = [1e3, 3e3, 5e3];
3708
+ MIN_DAEMON_AGE_MS = 3e4;
3026
3709
  _pending = /* @__PURE__ */ new Map();
3027
3710
  MAX_BUFFER = 1e7;
3028
3711
  }
@@ -3066,8 +3749,8 @@ async function embedDirect(text) {
3066
3749
  const llamaCpp = await import("node-llama-cpp");
3067
3750
  const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
3068
3751
  const { existsSync: existsSync8 } = await import("fs");
3069
- const path9 = await import("path");
3070
- const modelPath = path9.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
3752
+ const path10 = await import("path");
3753
+ const modelPath = path10.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
3071
3754
  if (!existsSync8(modelPath)) {
3072
3755
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
3073
3756
  }
@@ -3103,7 +3786,7 @@ __export(project_name_exports, {
3103
3786
  getProjectName: () => getProjectName
3104
3787
  });
3105
3788
  import { execSync as execSync2 } from "child_process";
3106
- import path7 from "path";
3789
+ import path8 from "path";
3107
3790
  function getProjectName(cwd) {
3108
3791
  const dir = cwd ?? process.cwd();
3109
3792
  if (_cached && _cachedCwd === dir) return _cached;
@@ -3116,7 +3799,7 @@ function getProjectName(cwd) {
3116
3799
  timeout: 2e3,
3117
3800
  stdio: ["pipe", "pipe", "pipe"]
3118
3801
  }).trim();
3119
- repoRoot = path7.dirname(gitCommonDir);
3802
+ repoRoot = path8.dirname(gitCommonDir);
3120
3803
  } catch {
3121
3804
  repoRoot = execSync2("git rev-parse --show-toplevel", {
3122
3805
  cwd: dir,
@@ -3125,11 +3808,11 @@ function getProjectName(cwd) {
3125
3808
  stdio: ["pipe", "pipe", "pipe"]
3126
3809
  }).trim();
3127
3810
  }
3128
- _cached = path7.basename(repoRoot);
3811
+ _cached = path8.basename(repoRoot);
3129
3812
  _cachedCwd = dir;
3130
3813
  return _cached;
3131
3814
  } catch {
3132
- _cached = path7.basename(dir);
3815
+ _cached = path8.basename(dir);
3133
3816
  _cachedCwd = dir;
3134
3817
  return _cached;
3135
3818
  }
@@ -3154,7 +3837,7 @@ __export(file_grep_exports, {
3154
3837
  });
3155
3838
  import { execSync as execSync3 } from "child_process";
3156
3839
  import { readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync2, existsSync as existsSync7 } from "fs";
3157
- import path8 from "path";
3840
+ import path9 from "path";
3158
3841
  import crypto from "crypto";
3159
3842
  function hasRipgrep() {
3160
3843
  if (_hasRg === null) {
@@ -3194,7 +3877,7 @@ async function grepProjectFiles(query, projectRoot, options) {
3194
3877
  session_id: "file-grep",
3195
3878
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3196
3879
  tool_name: "file_grep",
3197
- project_name: path8.basename(projectRoot),
3880
+ project_name: path9.basename(projectRoot),
3198
3881
  has_error: false,
3199
3882
  raw_text: `${prefix} ${buildSnippet(hit, projectRoot)}`,
3200
3883
  vector: null,
@@ -3268,7 +3951,7 @@ function grepWithNodeFs(pattern, projectRoot, patterns) {
3268
3951
  const files = collectFiles(projectRoot, patterns ?? DEFAULT_PATTERNS);
3269
3952
  const hits = [];
3270
3953
  for (const filePath of files.slice(0, MAX_FILES)) {
3271
- const absPath = path8.join(projectRoot, filePath);
3954
+ const absPath = path9.join(projectRoot, filePath);
3272
3955
  try {
3273
3956
  const stat = statSync2(absPath);
3274
3957
  if (stat.size > MAX_FILE_SIZE) continue;
@@ -3295,15 +3978,15 @@ function collectFiles(root, patterns) {
3295
3978
  const files = [];
3296
3979
  function walk(dir, relative) {
3297
3980
  if (files.length >= MAX_FILES) return;
3298
- const basename = path8.basename(dir);
3981
+ const basename = path9.basename(dir);
3299
3982
  if (EXCLUDE_DIRS.includes(basename)) return;
3300
3983
  try {
3301
3984
  const entries = readdirSync2(dir, { withFileTypes: true });
3302
3985
  for (const entry of entries) {
3303
3986
  if (files.length >= MAX_FILES) return;
3304
- const rel = path8.join(relative, entry.name);
3987
+ const rel = path9.join(relative, entry.name);
3305
3988
  if (entry.isDirectory()) {
3306
- walk(path8.join(dir, entry.name), rel);
3989
+ walk(path9.join(dir, entry.name), rel);
3307
3990
  } else if (entry.isFile()) {
3308
3991
  for (const pat of patterns) {
3309
3992
  if (matchGlob(rel, pat)) {
@@ -3335,7 +4018,7 @@ function matchGlob(filePath, pattern) {
3335
4018
  if (slashIdx !== -1) {
3336
4019
  const dir = pattern.slice(0, slashIdx);
3337
4020
  const ext2 = pattern.slice(slashIdx + 1).replace("*", "");
3338
- const fileDir = path8.dirname(filePath);
4021
+ const fileDir = path9.dirname(filePath);
3339
4022
  return fileDir === dir && filePath.endsWith(ext2);
3340
4023
  }
3341
4024
  const ext = pattern.replace("*", "");
@@ -3343,7 +4026,7 @@ function matchGlob(filePath, pattern) {
3343
4026
  }
3344
4027
  function buildSnippet(hit, projectRoot) {
3345
4028
  try {
3346
- const absPath = path8.join(projectRoot, hit.filePath);
4029
+ const absPath = path9.join(projectRoot, hit.filePath);
3347
4030
  if (!existsSync7(absPath)) return hit.matchLine;
3348
4031
  const lines = readFileSync4(absPath, "utf8").split("\n");
3349
4032
  const start = Math.max(0, hit.lineNumber - 3);