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