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