@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
@@ -329,6 +329,118 @@ var init_db_retry = __esm({
329
329
  }
330
330
  });
331
331
 
332
+ // src/lib/runtime-table.ts
333
+ var RUNTIME_TABLE, DEFAULT_RUNTIME;
334
+ var init_runtime_table = __esm({
335
+ "src/lib/runtime-table.ts"() {
336
+ "use strict";
337
+ RUNTIME_TABLE = {
338
+ codex: {
339
+ binary: "codex",
340
+ launchMode: "interactive",
341
+ autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
342
+ inlineFlag: "--no-alt-screen",
343
+ apiKeyEnv: "OPENAI_API_KEY",
344
+ defaultModel: "gpt-5.4"
345
+ },
346
+ opencode: {
347
+ binary: "opencode",
348
+ launchMode: "exec",
349
+ autoApproveFlag: "--dangerously-skip-permissions",
350
+ inlineFlag: "",
351
+ apiKeyEnv: "ANTHROPIC_API_KEY",
352
+ defaultModel: "anthropic/claude-sonnet-4-6"
353
+ }
354
+ };
355
+ DEFAULT_RUNTIME = "claude";
356
+ }
357
+ });
358
+
359
+ // src/lib/agent-config.ts
360
+ var agent_config_exports = {};
361
+ __export(agent_config_exports, {
362
+ AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
363
+ DEFAULT_MODELS: () => DEFAULT_MODELS,
364
+ KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
365
+ RUNTIME_LABELS: () => RUNTIME_LABELS,
366
+ clearAgentRuntime: () => clearAgentRuntime,
367
+ getAgentRuntime: () => getAgentRuntime,
368
+ loadAgentConfig: () => loadAgentConfig,
369
+ saveAgentConfig: () => saveAgentConfig,
370
+ setAgentRuntime: () => setAgentRuntime
371
+ });
372
+ import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2, mkdirSync } from "fs";
373
+ import path2 from "path";
374
+ function loadAgentConfig() {
375
+ if (!existsSync2(AGENT_CONFIG_PATH)) return {};
376
+ try {
377
+ return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
378
+ } catch {
379
+ return {};
380
+ }
381
+ }
382
+ function saveAgentConfig(config) {
383
+ const dir = path2.dirname(AGENT_CONFIG_PATH);
384
+ if (!existsSync2(dir)) mkdirSync(dir, { recursive: true });
385
+ writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
386
+ }
387
+ function getAgentRuntime(agentId) {
388
+ const config = loadAgentConfig();
389
+ const entry = config[agentId];
390
+ if (entry) return entry;
391
+ const orgDefault = config["default"];
392
+ if (orgDefault) return orgDefault;
393
+ return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
394
+ }
395
+ function setAgentRuntime(agentId, runtime, model) {
396
+ const knownModels = KNOWN_RUNTIMES[runtime];
397
+ if (!knownModels) {
398
+ return {
399
+ ok: false,
400
+ error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
401
+ };
402
+ }
403
+ if (!knownModels.includes(model)) {
404
+ return {
405
+ ok: false,
406
+ error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
407
+ };
408
+ }
409
+ const config = loadAgentConfig();
410
+ config[agentId] = { runtime, model };
411
+ saveAgentConfig(config);
412
+ return { ok: true };
413
+ }
414
+ function clearAgentRuntime(agentId) {
415
+ const config = loadAgentConfig();
416
+ delete config[agentId];
417
+ saveAgentConfig(config);
418
+ }
419
+ var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
420
+ var init_agent_config = __esm({
421
+ "src/lib/agent-config.ts"() {
422
+ "use strict";
423
+ init_config();
424
+ init_runtime_table();
425
+ AGENT_CONFIG_PATH = path2.join(EXE_AI_DIR, "agent-config.json");
426
+ KNOWN_RUNTIMES = {
427
+ claude: ["claude-opus-4", "claude-sonnet-4", "claude-haiku-4.5"],
428
+ codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
429
+ opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
430
+ };
431
+ RUNTIME_LABELS = {
432
+ claude: "Claude Code (Anthropic)",
433
+ codex: "Codex (OpenAI)",
434
+ opencode: "OpenCode (open source)"
435
+ };
436
+ DEFAULT_MODELS = {
437
+ claude: "claude-opus-4",
438
+ codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
439
+ opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
440
+ };
441
+ }
442
+ });
443
+
332
444
  // src/lib/employees.ts
333
445
  var employees_exports = {};
334
446
  __export(employees_exports, {
@@ -344,6 +456,7 @@ __export(employees_exports, {
344
456
  getEmployeeByRole: () => getEmployeeByRole,
345
457
  getEmployeeNamesByRole: () => getEmployeeNamesByRole,
346
458
  hasRole: () => hasRole,
459
+ hireEmployee: () => hireEmployee,
347
460
  isCoordinatorName: () => isCoordinatorName,
348
461
  isCoordinatorRole: () => isCoordinatorRole,
349
462
  isMultiInstance: () => isMultiInstance,
@@ -356,9 +469,9 @@ __export(employees_exports, {
356
469
  validateEmployeeName: () => validateEmployeeName
357
470
  });
358
471
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
359
- import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
472
+ import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
360
473
  import { execSync } from "child_process";
361
- import path2 from "path";
474
+ import path3 from "path";
362
475
  import os2 from "os";
363
476
  function normalizeRole(role) {
364
477
  return (role ?? "").trim().toLowerCase();
@@ -395,7 +508,7 @@ function validateEmployeeName(name) {
395
508
  return { valid: true };
396
509
  }
397
510
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
398
- if (!existsSync2(employeesPath)) {
511
+ if (!existsSync3(employeesPath)) {
399
512
  return [];
400
513
  }
401
514
  const raw = await readFile2(employeesPath, "utf-8");
@@ -406,13 +519,13 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
406
519
  }
407
520
  }
408
521
  async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
409
- await mkdir2(path2.dirname(employeesPath), { recursive: true });
522
+ await mkdir2(path3.dirname(employeesPath), { recursive: true });
410
523
  await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
411
524
  }
412
525
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
413
- if (!existsSync2(employeesPath)) return [];
526
+ if (!existsSync3(employeesPath)) return [];
414
527
  try {
415
- return JSON.parse(readFileSync2(employeesPath, "utf-8"));
528
+ return JSON.parse(readFileSync3(employeesPath, "utf-8"));
416
529
  } catch {
417
530
  return [];
418
531
  }
@@ -454,6 +567,52 @@ function addEmployee(employees, employee) {
454
567
  }
455
568
  return [...employees, normalized];
456
569
  }
570
+ function appendToCoordinatorTeam(employee) {
571
+ const coordinator = getCoordinatorEmployee(loadEmployeesSync());
572
+ if (!coordinator) return;
573
+ const idPath = path3.join(IDENTITY_DIR, `${coordinator.name}.md`);
574
+ if (!existsSync3(idPath)) return;
575
+ const content = readFileSync3(idPath, "utf-8");
576
+ if (content.includes(`**${capitalize(employee.name)}`)) return;
577
+ const teamMatch = content.match(TEAM_SECTION_RE);
578
+ if (!teamMatch || teamMatch.index === void 0) return;
579
+ const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
580
+ const nextHeading = afterTeam.match(/\n## /);
581
+ const entry = `
582
+ **${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
583
+ `;
584
+ let updated;
585
+ if (nextHeading && nextHeading.index !== void 0) {
586
+ const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
587
+ updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
588
+ } else {
589
+ updated = content.trimEnd() + "\n" + entry;
590
+ }
591
+ writeFileSync2(idPath, updated, "utf-8");
592
+ }
593
+ function capitalize(s) {
594
+ return s.charAt(0).toUpperCase() + s.slice(1);
595
+ }
596
+ async function hireEmployee(employee) {
597
+ const employees = await loadEmployees();
598
+ const updated = addEmployee(employees, employee);
599
+ await saveEmployees(updated);
600
+ try {
601
+ appendToCoordinatorTeam(employee);
602
+ } catch {
603
+ }
604
+ try {
605
+ const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
606
+ const config = loadAgentConfig2();
607
+ const name = employee.name.toLowerCase();
608
+ if (!config[name] && config["default"]) {
609
+ config[name] = { ...config["default"] };
610
+ saveAgentConfig2(config);
611
+ }
612
+ } catch {
613
+ }
614
+ return updated;
615
+ }
457
616
  async function normalizeRosterCase(rosterPath) {
458
617
  const employees = await loadEmployees(rosterPath);
459
618
  let changed = false;
@@ -463,14 +622,14 @@ async function normalizeRosterCase(rosterPath) {
463
622
  emp.name = emp.name.toLowerCase();
464
623
  changed = true;
465
624
  try {
466
- const identityDir = path2.join(os2.homedir(), ".exe-os", "identity");
467
- const oldPath = path2.join(identityDir, `${oldName}.md`);
468
- const newPath = path2.join(identityDir, `${emp.name}.md`);
469
- if (existsSync2(oldPath) && !existsSync2(newPath)) {
625
+ const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
626
+ const oldPath = path3.join(identityDir, `${oldName}.md`);
627
+ const newPath = path3.join(identityDir, `${emp.name}.md`);
628
+ if (existsSync3(oldPath) && !existsSync3(newPath)) {
470
629
  renameSync2(oldPath, newPath);
471
- } else if (existsSync2(oldPath) && oldPath !== newPath) {
472
- const content = readFileSync2(oldPath, "utf-8");
473
- writeFileSync(newPath, content, "utf-8");
630
+ } else if (existsSync3(oldPath) && oldPath !== newPath) {
631
+ const content = readFileSync3(oldPath, "utf-8");
632
+ writeFileSync2(newPath, content, "utf-8");
474
633
  if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
475
634
  unlinkSync(oldPath);
476
635
  }
@@ -500,7 +659,7 @@ function registerBinSymlinks(name) {
500
659
  errors.push("Could not find 'exe-os' in PATH");
501
660
  return { created, skipped, errors };
502
661
  }
503
- const binDir = path2.dirname(exeBinPath);
662
+ const binDir = path3.dirname(exeBinPath);
504
663
  let target;
505
664
  try {
506
665
  target = readlinkSync(exeBinPath);
@@ -510,8 +669,8 @@ function registerBinSymlinks(name) {
510
669
  }
511
670
  for (const suffix of ["", "-opencode"]) {
512
671
  const linkName = `${name}${suffix}`;
513
- const linkPath = path2.join(binDir, linkName);
514
- if (existsSync2(linkPath)) {
672
+ const linkPath = path3.join(binDir, linkName);
673
+ if (existsSync3(linkPath)) {
515
674
  skipped.push(linkName);
516
675
  continue;
517
676
  }
@@ -524,21 +683,619 @@ function registerBinSymlinks(name) {
524
683
  }
525
684
  return { created, skipped, errors };
526
685
  }
527
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
686
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
528
687
  var init_employees = __esm({
529
688
  "src/lib/employees.ts"() {
530
689
  "use strict";
531
690
  init_config();
532
- EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
691
+ EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
533
692
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
534
693
  COORDINATOR_ROLE = "COO";
535
694
  MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
695
+ IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
696
+ TEAM_SECTION_RE = /^## Team\b.*$/m;
697
+ }
698
+ });
699
+
700
+ // src/lib/database-adapter.ts
701
+ import os3 from "os";
702
+ import path4 from "path";
703
+ import { createRequire } from "module";
704
+ import { pathToFileURL } from "url";
705
+ function quotedIdentifier(identifier) {
706
+ return `"${identifier.replace(/"/g, '""')}"`;
707
+ }
708
+ function unqualifiedTableName(name) {
709
+ const raw = name.trim().replace(/^"|"$/g, "");
710
+ const parts = raw.split(".");
711
+ return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
712
+ }
713
+ function stripTrailingSemicolon(sql) {
714
+ return sql.trim().replace(/;+\s*$/u, "");
715
+ }
716
+ function appendClause(sql, clause) {
717
+ const trimmed = stripTrailingSemicolon(sql);
718
+ const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
719
+ if (!returningMatch) {
720
+ return `${trimmed}${clause}`;
721
+ }
722
+ const idx = returningMatch.index;
723
+ return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
724
+ }
725
+ function normalizeStatement(stmt) {
726
+ if (typeof stmt === "string") {
727
+ return { kind: "positional", sql: stmt, args: [] };
728
+ }
729
+ const sql = stmt.sql;
730
+ if (Array.isArray(stmt.args) || stmt.args === void 0) {
731
+ return { kind: "positional", sql, args: stmt.args ?? [] };
732
+ }
733
+ return { kind: "named", sql, args: stmt.args };
734
+ }
735
+ function rewriteBooleanLiterals(sql) {
736
+ let out = sql;
737
+ for (const column of BOOLEAN_COLUMN_NAMES) {
738
+ const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
739
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
740
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
741
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
742
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
743
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
744
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
745
+ }
746
+ return out;
747
+ }
748
+ function rewriteInsertOrIgnore(sql) {
749
+ if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
750
+ return sql;
751
+ }
752
+ const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
753
+ return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
754
+ }
755
+ function rewriteInsertOrReplace(sql) {
756
+ const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
757
+ if (!match) {
758
+ return sql;
759
+ }
760
+ const rawTable = match[1];
761
+ const rawColumns = match[2];
762
+ const remainder = match[3];
763
+ const tableName = unqualifiedTableName(rawTable);
764
+ const conflictKeys = UPSERT_KEYS[tableName];
765
+ if (!conflictKeys?.length) {
766
+ return sql;
767
+ }
768
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
769
+ const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
770
+ const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
771
+ const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
772
+ return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
773
+ }
774
+ function rewriteSql(sql) {
775
+ let out = sql;
776
+ out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
777
+ out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
778
+ out = rewriteBooleanLiterals(out);
779
+ out = rewriteInsertOrReplace(out);
780
+ out = rewriteInsertOrIgnore(out);
781
+ return stripTrailingSemicolon(out);
782
+ }
783
+ function toBoolean(value) {
784
+ if (value === null || value === void 0) return value;
785
+ if (typeof value === "boolean") return value;
786
+ if (typeof value === "number") return value !== 0;
787
+ if (typeof value === "bigint") return value !== 0n;
788
+ if (typeof value === "string") {
789
+ const normalized = value.trim().toLowerCase();
790
+ if (normalized === "0" || normalized === "false") return false;
791
+ if (normalized === "1" || normalized === "true") return true;
792
+ }
793
+ return Boolean(value);
794
+ }
795
+ function countQuestionMarks(sql, end) {
796
+ let count = 0;
797
+ let inSingle = false;
798
+ let inDouble = false;
799
+ let inLineComment = false;
800
+ let inBlockComment = false;
801
+ for (let i = 0; i < end; i++) {
802
+ const ch = sql[i];
803
+ const next = sql[i + 1];
804
+ if (inLineComment) {
805
+ if (ch === "\n") inLineComment = false;
806
+ continue;
807
+ }
808
+ if (inBlockComment) {
809
+ if (ch === "*" && next === "/") {
810
+ inBlockComment = false;
811
+ i += 1;
812
+ }
813
+ continue;
814
+ }
815
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
816
+ inLineComment = true;
817
+ i += 1;
818
+ continue;
819
+ }
820
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
821
+ inBlockComment = true;
822
+ i += 1;
823
+ continue;
824
+ }
825
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
826
+ inSingle = !inSingle;
827
+ continue;
828
+ }
829
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
830
+ inDouble = !inDouble;
831
+ continue;
832
+ }
833
+ if (!inSingle && !inDouble && ch === "?") {
834
+ count += 1;
835
+ }
836
+ }
837
+ return count;
838
+ }
839
+ function findBooleanPlaceholderIndexes(sql) {
840
+ const indexes = /* @__PURE__ */ new Set();
841
+ for (const column of BOOLEAN_COLUMN_NAMES) {
842
+ const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
843
+ for (const match of sql.matchAll(pattern)) {
844
+ const matchText = match[0];
845
+ const qIndex = match.index + matchText.lastIndexOf("?");
846
+ indexes.add(countQuestionMarks(sql, qIndex + 1));
847
+ }
848
+ }
849
+ return indexes;
850
+ }
851
+ function coerceInsertBooleanArgs(sql, args) {
852
+ const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
853
+ if (!match) return;
854
+ const rawTable = match[1];
855
+ const rawColumns = match[2];
856
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
857
+ if (!boolColumns?.size) return;
858
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
859
+ for (const [index, column] of columns.entries()) {
860
+ if (boolColumns.has(column) && index < args.length) {
861
+ args[index] = toBoolean(args[index]);
862
+ }
863
+ }
864
+ }
865
+ function coerceUpdateBooleanArgs(sql, args) {
866
+ const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
867
+ if (!match) return;
868
+ const rawTable = match[1];
869
+ const setClause = match[2];
870
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
871
+ if (!boolColumns?.size) return;
872
+ const assignments = setClause.split(",");
873
+ let placeholderIndex = 0;
874
+ for (const assignment of assignments) {
875
+ if (!assignment.includes("?")) continue;
876
+ placeholderIndex += 1;
877
+ const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
878
+ if (colMatch && boolColumns.has(colMatch[1])) {
879
+ args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
880
+ }
881
+ }
882
+ }
883
+ function coerceBooleanArgs(sql, args) {
884
+ const nextArgs = [...args];
885
+ coerceInsertBooleanArgs(sql, nextArgs);
886
+ coerceUpdateBooleanArgs(sql, nextArgs);
887
+ const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
888
+ for (const index of placeholderIndexes) {
889
+ if (index > 0 && index <= nextArgs.length) {
890
+ nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
891
+ }
892
+ }
893
+ return nextArgs;
894
+ }
895
+ function convertQuestionMarksToDollarParams(sql) {
896
+ let out = "";
897
+ let placeholder = 0;
898
+ let inSingle = false;
899
+ let inDouble = false;
900
+ let inLineComment = false;
901
+ let inBlockComment = false;
902
+ for (let i = 0; i < sql.length; i++) {
903
+ const ch = sql[i];
904
+ const next = sql[i + 1];
905
+ if (inLineComment) {
906
+ out += ch;
907
+ if (ch === "\n") inLineComment = false;
908
+ continue;
909
+ }
910
+ if (inBlockComment) {
911
+ out += ch;
912
+ if (ch === "*" && next === "/") {
913
+ out += next;
914
+ inBlockComment = false;
915
+ i += 1;
916
+ }
917
+ continue;
918
+ }
919
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
920
+ out += ch + next;
921
+ inLineComment = true;
922
+ i += 1;
923
+ continue;
924
+ }
925
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
926
+ out += ch + next;
927
+ inBlockComment = true;
928
+ i += 1;
929
+ continue;
930
+ }
931
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
932
+ inSingle = !inSingle;
933
+ out += ch;
934
+ continue;
935
+ }
936
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
937
+ inDouble = !inDouble;
938
+ out += ch;
939
+ continue;
940
+ }
941
+ if (!inSingle && !inDouble && ch === "?") {
942
+ placeholder += 1;
943
+ out += `$${placeholder}`;
944
+ continue;
945
+ }
946
+ out += ch;
947
+ }
948
+ return out;
949
+ }
950
+ function translateStatementForPostgres(stmt) {
951
+ const normalized = normalizeStatement(stmt);
952
+ if (normalized.kind === "named") {
953
+ throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
954
+ }
955
+ const rewrittenSql = rewriteSql(normalized.sql);
956
+ const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
957
+ return {
958
+ sql: convertQuestionMarksToDollarParams(rewrittenSql),
959
+ args: coercedArgs
960
+ };
961
+ }
962
+ function shouldBypassPostgres(stmt) {
963
+ const normalized = normalizeStatement(stmt);
964
+ if (normalized.kind === "named") {
965
+ return true;
966
+ }
967
+ return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
968
+ }
969
+ function shouldFallbackOnError(error) {
970
+ const message = error instanceof Error ? error.message : String(error);
971
+ return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
972
+ }
973
+ function isReadQuery(sql) {
974
+ const trimmed = sql.trimStart();
975
+ return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
976
+ }
977
+ function buildRow(row, columns) {
978
+ const values = columns.map((column) => row[column]);
979
+ return Object.assign(values, row);
980
+ }
981
+ function buildResultSet(rows, rowsAffected = 0) {
982
+ const columns = rows[0] ? Object.keys(rows[0]) : [];
983
+ const resultRows = rows.map((row) => buildRow(row, columns));
984
+ return {
985
+ columns,
986
+ columnTypes: columns.map(() => ""),
987
+ rows: resultRows,
988
+ rowsAffected,
989
+ lastInsertRowid: void 0,
990
+ toJSON() {
991
+ return {
992
+ columns,
993
+ columnTypes: columns.map(() => ""),
994
+ rows,
995
+ rowsAffected,
996
+ lastInsertRowid: void 0
997
+ };
998
+ }
999
+ };
1000
+ }
1001
+ async function loadPrismaClient() {
1002
+ if (!prismaClientPromise) {
1003
+ prismaClientPromise = (async () => {
1004
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
1005
+ if (explicitPath) {
1006
+ const module2 = await import(pathToFileURL(explicitPath).href);
1007
+ const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
1008
+ if (!PrismaClient2) {
1009
+ throw new Error(`No PrismaClient export found at ${explicitPath}`);
1010
+ }
1011
+ return new PrismaClient2();
1012
+ }
1013
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path4.join(os3.homedir(), "exe-db");
1014
+ const requireFromExeDb = createRequire(path4.join(exeDbRoot, "package.json"));
1015
+ const prismaEntry = requireFromExeDb.resolve("@prisma/client");
1016
+ const module = await import(pathToFileURL(prismaEntry).href);
1017
+ const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
1018
+ if (!PrismaClient) {
1019
+ throw new Error(`No PrismaClient export found in ${prismaEntry}`);
1020
+ }
1021
+ return new PrismaClient();
1022
+ })();
1023
+ }
1024
+ return prismaClientPromise;
1025
+ }
1026
+ async function ensureCompatibilityViews(prisma) {
1027
+ if (!compatibilityBootstrapPromise) {
1028
+ compatibilityBootstrapPromise = (async () => {
1029
+ for (const mapping of VIEW_MAPPINGS) {
1030
+ const relation = mapping.source.replace(/"/g, "");
1031
+ const rows = await prisma.$queryRawUnsafe(
1032
+ "SELECT to_regclass($1) AS regclass",
1033
+ relation
1034
+ );
1035
+ if (!rows[0]?.regclass) {
1036
+ continue;
1037
+ }
1038
+ await prisma.$executeRawUnsafe(
1039
+ `CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
1040
+ );
1041
+ }
1042
+ })();
1043
+ }
1044
+ return compatibilityBootstrapPromise;
1045
+ }
1046
+ async function executeOnPrisma(executor, stmt) {
1047
+ const translated = translateStatementForPostgres(stmt);
1048
+ if (isReadQuery(translated.sql)) {
1049
+ const rows = await executor.$queryRawUnsafe(
1050
+ translated.sql,
1051
+ ...translated.args
1052
+ );
1053
+ return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
1054
+ }
1055
+ const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
1056
+ return buildResultSet([], rowsAffected);
1057
+ }
1058
+ function splitSqlStatements(sql) {
1059
+ const parts = [];
1060
+ let current = "";
1061
+ let inSingle = false;
1062
+ let inDouble = false;
1063
+ let inLineComment = false;
1064
+ let inBlockComment = false;
1065
+ for (let i = 0; i < sql.length; i++) {
1066
+ const ch = sql[i];
1067
+ const next = sql[i + 1];
1068
+ if (inLineComment) {
1069
+ current += ch;
1070
+ if (ch === "\n") inLineComment = false;
1071
+ continue;
1072
+ }
1073
+ if (inBlockComment) {
1074
+ current += ch;
1075
+ if (ch === "*" && next === "/") {
1076
+ current += next;
1077
+ inBlockComment = false;
1078
+ i += 1;
1079
+ }
1080
+ continue;
1081
+ }
1082
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
1083
+ current += ch + next;
1084
+ inLineComment = true;
1085
+ i += 1;
1086
+ continue;
1087
+ }
1088
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
1089
+ current += ch + next;
1090
+ inBlockComment = true;
1091
+ i += 1;
1092
+ continue;
1093
+ }
1094
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
1095
+ inSingle = !inSingle;
1096
+ current += ch;
1097
+ continue;
1098
+ }
1099
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
1100
+ inDouble = !inDouble;
1101
+ current += ch;
1102
+ continue;
1103
+ }
1104
+ if (!inSingle && !inDouble && ch === ";") {
1105
+ if (current.trim()) {
1106
+ parts.push(current.trim());
1107
+ }
1108
+ current = "";
1109
+ continue;
1110
+ }
1111
+ current += ch;
1112
+ }
1113
+ if (current.trim()) {
1114
+ parts.push(current.trim());
1115
+ }
1116
+ return parts;
1117
+ }
1118
+ async function createPrismaDbAdapter(fallbackClient) {
1119
+ const prisma = await loadPrismaClient();
1120
+ await ensureCompatibilityViews(prisma);
1121
+ let closed = false;
1122
+ let adapter;
1123
+ const fallbackExecute = async (stmt, error) => {
1124
+ if (!fallbackClient) {
1125
+ if (error) throw error;
1126
+ throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
1127
+ }
1128
+ if (error) {
1129
+ process.stderr.write(
1130
+ `[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
1131
+ `
1132
+ );
1133
+ }
1134
+ return fallbackClient.execute(stmt);
1135
+ };
1136
+ adapter = {
1137
+ async execute(stmt) {
1138
+ if (shouldBypassPostgres(stmt)) {
1139
+ return fallbackExecute(stmt);
1140
+ }
1141
+ try {
1142
+ return await executeOnPrisma(prisma, stmt);
1143
+ } catch (error) {
1144
+ if (shouldFallbackOnError(error)) {
1145
+ return fallbackExecute(stmt, error);
1146
+ }
1147
+ throw error;
1148
+ }
1149
+ },
1150
+ async batch(stmts, mode) {
1151
+ if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
1152
+ if (!fallbackClient) {
1153
+ throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
1154
+ }
1155
+ return fallbackClient.batch(stmts, mode);
1156
+ }
1157
+ try {
1158
+ if (prisma.$transaction) {
1159
+ return await prisma.$transaction(async (tx) => {
1160
+ const results2 = [];
1161
+ for (const stmt of stmts) {
1162
+ results2.push(await executeOnPrisma(tx, stmt));
1163
+ }
1164
+ return results2;
1165
+ });
1166
+ }
1167
+ const results = [];
1168
+ for (const stmt of stmts) {
1169
+ results.push(await executeOnPrisma(prisma, stmt));
1170
+ }
1171
+ return results;
1172
+ } catch (error) {
1173
+ if (fallbackClient && shouldFallbackOnError(error)) {
1174
+ process.stderr.write(
1175
+ `[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
1176
+ `
1177
+ );
1178
+ return fallbackClient.batch(stmts, mode);
1179
+ }
1180
+ throw error;
1181
+ }
1182
+ },
1183
+ async migrate(stmts) {
1184
+ if (fallbackClient) {
1185
+ return fallbackClient.migrate(stmts);
1186
+ }
1187
+ return adapter.batch(stmts, "deferred");
1188
+ },
1189
+ async transaction(mode) {
1190
+ if (!fallbackClient) {
1191
+ throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
1192
+ }
1193
+ return fallbackClient.transaction(mode);
1194
+ },
1195
+ async executeMultiple(sql) {
1196
+ if (fallbackClient && shouldBypassPostgres(sql)) {
1197
+ return fallbackClient.executeMultiple(sql);
1198
+ }
1199
+ for (const statement of splitSqlStatements(sql)) {
1200
+ await adapter.execute(statement);
1201
+ }
1202
+ },
1203
+ async sync() {
1204
+ if (fallbackClient) {
1205
+ return fallbackClient.sync();
1206
+ }
1207
+ return { frame_no: 0, frames_synced: 0 };
1208
+ },
1209
+ close() {
1210
+ closed = true;
1211
+ prismaClientPromise = null;
1212
+ compatibilityBootstrapPromise = null;
1213
+ void prisma.$disconnect?.();
1214
+ },
1215
+ get closed() {
1216
+ return closed;
1217
+ },
1218
+ get protocol() {
1219
+ return "prisma-postgres";
1220
+ }
1221
+ };
1222
+ return adapter;
1223
+ }
1224
+ var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
1225
+ var init_database_adapter = __esm({
1226
+ "src/lib/database-adapter.ts"() {
1227
+ "use strict";
1228
+ VIEW_MAPPINGS = [
1229
+ { view: "memories", source: "memory.memory_records" },
1230
+ { view: "tasks", source: "memory.tasks" },
1231
+ { view: "behaviors", source: "memory.behaviors" },
1232
+ { view: "entities", source: "memory.entities" },
1233
+ { view: "relationships", source: "memory.relationships" },
1234
+ { view: "entity_memories", source: "memory.entity_memories" },
1235
+ { view: "entity_aliases", source: "memory.entity_aliases" },
1236
+ { view: "notifications", source: "memory.notifications" },
1237
+ { view: "messages", source: "memory.messages" },
1238
+ { view: "users", source: "wiki.users" },
1239
+ { view: "workspaces", source: "wiki.workspaces" },
1240
+ { view: "workspace_users", source: "wiki.workspace_users" },
1241
+ { view: "documents", source: "wiki.workspace_documents" },
1242
+ { view: "chats", source: "wiki.workspace_chats" }
1243
+ ];
1244
+ UPSERT_KEYS = {
1245
+ memories: ["id"],
1246
+ tasks: ["id"],
1247
+ behaviors: ["id"],
1248
+ entities: ["id"],
1249
+ relationships: ["id"],
1250
+ entity_aliases: ["alias"],
1251
+ notifications: ["id"],
1252
+ messages: ["id"],
1253
+ users: ["id"],
1254
+ workspaces: ["id"],
1255
+ workspace_users: ["id"],
1256
+ documents: ["id"],
1257
+ chats: ["id"]
1258
+ };
1259
+ BOOLEAN_COLUMNS_BY_TABLE = {
1260
+ memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
1261
+ behaviors: /* @__PURE__ */ new Set(["active"]),
1262
+ notifications: /* @__PURE__ */ new Set(["read"]),
1263
+ users: /* @__PURE__ */ new Set(["has_personal_memory"])
1264
+ };
1265
+ BOOLEAN_COLUMN_NAMES = new Set(
1266
+ Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
1267
+ );
1268
+ IMMEDIATE_FALLBACK_PATTERNS = [
1269
+ /\bPRAGMA\b/i,
1270
+ /\bsqlite_master\b/i,
1271
+ /(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
1272
+ /\bMATCH\b/i,
1273
+ /\bvector_distance_cos\s*\(/i,
1274
+ /\bjson_extract\s*\(/i,
1275
+ /\bjulianday\s*\(/i,
1276
+ /\bstrftime\s*\(/i,
1277
+ /\blast_insert_rowid\s*\(/i
1278
+ ];
1279
+ prismaClientPromise = null;
1280
+ compatibilityBootstrapPromise = null;
536
1281
  }
537
1282
  });
538
1283
 
539
1284
  // src/lib/database.ts
540
1285
  import { createClient } from "@libsql/client";
541
1286
  async function initDatabase(config) {
1287
+ if (_walCheckpointTimer) {
1288
+ clearInterval(_walCheckpointTimer);
1289
+ _walCheckpointTimer = null;
1290
+ }
1291
+ if (_daemonClient) {
1292
+ _daemonClient.close();
1293
+ _daemonClient = null;
1294
+ }
1295
+ if (_adapterClient && _adapterClient !== _resilientClient) {
1296
+ _adapterClient.close();
1297
+ }
1298
+ _adapterClient = null;
542
1299
  if (_client) {
543
1300
  _client.close();
544
1301
  _client = null;
@@ -552,6 +1309,7 @@ async function initDatabase(config) {
552
1309
  }
553
1310
  _client = createClient(opts);
554
1311
  _resilientClient = wrapWithRetry(_client);
1312
+ _adapterClient = _resilientClient;
555
1313
  _client.execute("PRAGMA busy_timeout = 30000").catch(() => {
556
1314
  });
557
1315
  _client.execute("PRAGMA journal_mode = WAL").catch(() => {
@@ -562,11 +1320,17 @@ async function initDatabase(config) {
562
1320
  });
563
1321
  }, 3e4);
564
1322
  _walCheckpointTimer.unref();
1323
+ if (process.env.DATABASE_URL) {
1324
+ _adapterClient = await createPrismaDbAdapter(_resilientClient);
1325
+ }
565
1326
  }
566
1327
  function getClient() {
567
- if (!_resilientClient) {
1328
+ if (!_adapterClient) {
568
1329
  throw new Error("Database client not initialized. Call initDatabase() first.");
569
1330
  }
1331
+ if (process.env.DATABASE_URL) {
1332
+ return _adapterClient;
1333
+ }
570
1334
  if (process.env.EXE_IS_DAEMON === "1") {
571
1335
  return _resilientClient;
572
1336
  }
@@ -1507,26 +2271,36 @@ async function ensureSchema() {
1507
2271
  }
1508
2272
  }
1509
2273
  async function disposeDatabase() {
2274
+ if (_walCheckpointTimer) {
2275
+ clearInterval(_walCheckpointTimer);
2276
+ _walCheckpointTimer = null;
2277
+ }
1510
2278
  if (_daemonClient) {
1511
2279
  _daemonClient.close();
1512
2280
  _daemonClient = null;
1513
2281
  }
2282
+ if (_adapterClient && _adapterClient !== _resilientClient) {
2283
+ _adapterClient.close();
2284
+ }
2285
+ _adapterClient = null;
1514
2286
  if (_client) {
1515
2287
  _client.close();
1516
2288
  _client = null;
1517
2289
  _resilientClient = null;
1518
2290
  }
1519
2291
  }
1520
- var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
2292
+ var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
1521
2293
  var init_database = __esm({
1522
2294
  "src/lib/database.ts"() {
1523
2295
  "use strict";
1524
2296
  init_db_retry();
1525
2297
  init_employees();
2298
+ init_database_adapter();
1526
2299
  _client = null;
1527
2300
  _resilientClient = null;
1528
2301
  _walCheckpointTimer = null;
1529
2302
  _daemonClient = null;
2303
+ _adapterClient = null;
1530
2304
  initTurso = initDatabase;
1531
2305
  disposeTurso = disposeDatabase;
1532
2306
  }
@@ -1534,14 +2308,14 @@ var init_database = __esm({
1534
2308
 
1535
2309
  // src/lib/keychain.ts
1536
2310
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
1537
- import { existsSync as existsSync3 } from "fs";
1538
- import path3 from "path";
1539
- import os3 from "os";
2311
+ import { existsSync as existsSync4 } from "fs";
2312
+ import path5 from "path";
2313
+ import os4 from "os";
1540
2314
  function getKeyDir() {
1541
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path3.join(os3.homedir(), ".exe-os");
2315
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path5.join(os4.homedir(), ".exe-os");
1542
2316
  }
1543
2317
  function getKeyPath() {
1544
- return path3.join(getKeyDir(), "master.key");
2318
+ return path5.join(getKeyDir(), "master.key");
1545
2319
  }
1546
2320
  async function tryKeytar() {
1547
2321
  try {
@@ -1562,9 +2336,9 @@ async function getMasterKey() {
1562
2336
  }
1563
2337
  }
1564
2338
  const keyPath = getKeyPath();
1565
- if (!existsSync3(keyPath)) {
2339
+ if (!existsSync4(keyPath)) {
1566
2340
  process.stderr.write(
1567
- `[keychain] Key not found at ${keyPath} (HOME=${os3.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
2341
+ `[keychain] Key not found at ${keyPath} (HOME=${os4.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
1568
2342
  `
1569
2343
  );
1570
2344
  return null;
@@ -1657,13 +2431,13 @@ __export(shard_manager_exports, {
1657
2431
  listShards: () => listShards,
1658
2432
  shardExists: () => shardExists
1659
2433
  });
1660
- import path4 from "path";
1661
- import { existsSync as existsSync4, mkdirSync, readdirSync } from "fs";
2434
+ import path6 from "path";
2435
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync } from "fs";
1662
2436
  import { createClient as createClient2 } from "@libsql/client";
1663
2437
  function initShardManager(encryptionKey) {
1664
2438
  _encryptionKey = encryptionKey;
1665
- if (!existsSync4(SHARDS_DIR)) {
1666
- mkdirSync(SHARDS_DIR, { recursive: true });
2439
+ if (!existsSync5(SHARDS_DIR)) {
2440
+ mkdirSync2(SHARDS_DIR, { recursive: true });
1667
2441
  }
1668
2442
  _shardingEnabled = true;
1669
2443
  }
@@ -1683,7 +2457,7 @@ function getShardClient(projectName) {
1683
2457
  }
1684
2458
  const cached = _shards.get(safeName);
1685
2459
  if (cached) return cached;
1686
- const dbPath = path4.join(SHARDS_DIR, `${safeName}.db`);
2460
+ const dbPath = path6.join(SHARDS_DIR, `${safeName}.db`);
1687
2461
  const client = createClient2({
1688
2462
  url: `file:${dbPath}`,
1689
2463
  encryptionKey: _encryptionKey
@@ -1693,10 +2467,10 @@ function getShardClient(projectName) {
1693
2467
  }
1694
2468
  function shardExists(projectName) {
1695
2469
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
1696
- return existsSync4(path4.join(SHARDS_DIR, `${safeName}.db`));
2470
+ return existsSync5(path6.join(SHARDS_DIR, `${safeName}.db`));
1697
2471
  }
1698
2472
  function listShards() {
1699
- if (!existsSync4(SHARDS_DIR)) return [];
2473
+ if (!existsSync5(SHARDS_DIR)) return [];
1700
2474
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
1701
2475
  }
1702
2476
  async function ensureShardSchema(client) {
@@ -1770,7 +2544,23 @@ async function ensureShardSchema(client) {
1770
2544
  // MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
1771
2545
  "ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
1772
2546
  "ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
1773
- "ALTER TABLE memories ADD COLUMN trajectory TEXT"
2547
+ "ALTER TABLE memories ADD COLUMN trajectory TEXT",
2548
+ // Metadata enrichment columns (must match database.ts)
2549
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
2550
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
2551
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
2552
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
2553
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
2554
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
2555
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
2556
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
2557
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
2558
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
2559
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
2560
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
2561
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
2562
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
2563
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
1774
2564
  ]) {
1775
2565
  try {
1776
2566
  await client.execute(col);
@@ -1882,7 +2672,7 @@ var init_shard_manager = __esm({
1882
2672
  "src/lib/shard-manager.ts"() {
1883
2673
  "use strict";
1884
2674
  init_config();
1885
- SHARDS_DIR = path4.join(EXE_AI_DIR, "shards");
2675
+ SHARDS_DIR = path6.join(EXE_AI_DIR, "shards");
1886
2676
  _shards = /* @__PURE__ */ new Map();
1887
2677
  _encryptionKey = null;
1888
2678
  _shardingEnabled = false;
@@ -2745,8 +3535,8 @@ __export(reranker_exports, {
2745
3535
  rerankWithContext: () => rerankWithContext,
2746
3536
  rerankWithScores: () => rerankWithScores
2747
3537
  });
2748
- import path5 from "path";
2749
- import { existsSync as existsSync5 } from "fs";
3538
+ import path7 from "path";
3539
+ import { existsSync as existsSync6 } from "fs";
2750
3540
  function resetIdleTimer() {
2751
3541
  if (_idleTimer) clearTimeout(_idleTimer);
2752
3542
  _idleTimer = setTimeout(() => {
@@ -2757,18 +3547,18 @@ function resetIdleTimer() {
2757
3547
  }
2758
3548
  }
2759
3549
  function isRerankerAvailable() {
2760
- return existsSync5(path5.join(MODELS_DIR, RERANKER_MODEL_FILE));
3550
+ return existsSync6(path7.join(MODELS_DIR, RERANKER_MODEL_FILE));
2761
3551
  }
2762
3552
  function getRerankerModelPath() {
2763
- return path5.join(MODELS_DIR, RERANKER_MODEL_FILE);
3553
+ return path7.join(MODELS_DIR, RERANKER_MODEL_FILE);
2764
3554
  }
2765
3555
  async function ensureLoaded() {
2766
3556
  if (_rerankerContext) {
2767
3557
  resetIdleTimer();
2768
3558
  return;
2769
3559
  }
2770
- const modelPath = path5.join(MODELS_DIR, RERANKER_MODEL_FILE);
2771
- if (!existsSync5(modelPath)) {
3560
+ const modelPath = path7.join(MODELS_DIR, RERANKER_MODEL_FILE);
3561
+ if (!existsSync6(modelPath)) {
2772
3562
  throw new Error(
2773
3563
  `Reranker model not found at ${modelPath}. Run /exe-setup to download it.`
2774
3564
  );
@@ -2866,11 +3656,11 @@ var init_reranker = __esm({
2866
3656
 
2867
3657
  // src/lib/exe-daemon-client.ts
2868
3658
  import net from "net";
2869
- import os4 from "os";
3659
+ import os5 from "os";
2870
3660
  import { spawn } from "child_process";
2871
3661
  import { randomUUID as randomUUID2 } from "crypto";
2872
- import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
2873
- import path6 from "path";
3662
+ import { existsSync as existsSync7, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
3663
+ import path8 from "path";
2874
3664
  import { fileURLToPath } from "url";
2875
3665
  function handleData(chunk) {
2876
3666
  _buffer += chunk.toString();
@@ -2898,9 +3688,9 @@ function handleData(chunk) {
2898
3688
  }
2899
3689
  }
2900
3690
  function cleanupStaleFiles() {
2901
- if (existsSync6(PID_PATH)) {
3691
+ if (existsSync7(PID_PATH)) {
2902
3692
  try {
2903
- const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
3693
+ const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
2904
3694
  if (pid > 0) {
2905
3695
  try {
2906
3696
  process.kill(pid, 0);
@@ -2921,17 +3711,17 @@ function cleanupStaleFiles() {
2921
3711
  }
2922
3712
  }
2923
3713
  function findPackageRoot() {
2924
- let dir = path6.dirname(fileURLToPath(import.meta.url));
2925
- const { root } = path6.parse(dir);
3714
+ let dir = path8.dirname(fileURLToPath(import.meta.url));
3715
+ const { root } = path8.parse(dir);
2926
3716
  while (dir !== root) {
2927
- if (existsSync6(path6.join(dir, "package.json"))) return dir;
2928
- dir = path6.dirname(dir);
3717
+ if (existsSync7(path8.join(dir, "package.json"))) return dir;
3718
+ dir = path8.dirname(dir);
2929
3719
  }
2930
3720
  return null;
2931
3721
  }
2932
3722
  function spawnDaemon() {
2933
- const freeGB = os4.freemem() / (1024 * 1024 * 1024);
2934
- const totalGB = os4.totalmem() / (1024 * 1024 * 1024);
3723
+ const freeGB = os5.freemem() / (1024 * 1024 * 1024);
3724
+ const totalGB = os5.totalmem() / (1024 * 1024 * 1024);
2935
3725
  if (totalGB <= 8) {
2936
3726
  process.stderr.write(
2937
3727
  `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
@@ -2951,8 +3741,8 @@ function spawnDaemon() {
2951
3741
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
2952
3742
  return;
2953
3743
  }
2954
- const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
2955
- if (!existsSync6(daemonPath)) {
3744
+ const daemonPath = path8.join(pkgRoot, "dist", "lib", "exe-daemon.js");
3745
+ if (!existsSync7(daemonPath)) {
2956
3746
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
2957
3747
  `);
2958
3748
  return;
@@ -2960,7 +3750,7 @@ function spawnDaemon() {
2960
3750
  const resolvedPath = daemonPath;
2961
3751
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
2962
3752
  `);
2963
- const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
3753
+ const logPath = path8.join(path8.dirname(SOCKET_PATH), "exed.log");
2964
3754
  let stderrFd = "ignore";
2965
3755
  try {
2966
3756
  stderrFd = openSync(logPath, "a");
@@ -3111,74 +3901,123 @@ async function pingDaemon() {
3111
3901
  return null;
3112
3902
  }
3113
3903
  function killAndRespawnDaemon() {
3114
- process.stderr.write("[exed-client] Killing daemon for restart...\n");
3115
- if (existsSync6(PID_PATH)) {
3116
- try {
3117
- const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
3118
- if (pid > 0) {
3119
- try {
3120
- process.kill(pid, "SIGKILL");
3121
- } catch {
3904
+ if (!acquireSpawnLock()) {
3905
+ process.stderr.write("[exed-client] Another process is already restarting daemon \u2014 skipping\n");
3906
+ if (_socket) {
3907
+ _socket.destroy();
3908
+ _socket = null;
3909
+ }
3910
+ _connected = false;
3911
+ _buffer = "";
3912
+ return;
3913
+ }
3914
+ try {
3915
+ process.stderr.write("[exed-client] Killing daemon for restart...\n");
3916
+ if (existsSync7(PID_PATH)) {
3917
+ try {
3918
+ const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
3919
+ if (pid > 0) {
3920
+ try {
3921
+ process.kill(pid, "SIGKILL");
3922
+ } catch {
3923
+ }
3122
3924
  }
3925
+ } catch {
3123
3926
  }
3927
+ }
3928
+ if (_socket) {
3929
+ _socket.destroy();
3930
+ _socket = null;
3931
+ }
3932
+ _connected = false;
3933
+ _buffer = "";
3934
+ try {
3935
+ unlinkSync2(PID_PATH);
3124
3936
  } catch {
3125
3937
  }
3938
+ try {
3939
+ unlinkSync2(SOCKET_PATH);
3940
+ } catch {
3941
+ }
3942
+ spawnDaemon();
3943
+ } finally {
3944
+ releaseSpawnLock();
3126
3945
  }
3127
- if (_socket) {
3128
- _socket.destroy();
3129
- _socket = null;
3130
- }
3131
- _connected = false;
3132
- _buffer = "";
3946
+ }
3947
+ function isDaemonTooYoung() {
3133
3948
  try {
3134
- unlinkSync2(PID_PATH);
3949
+ const stat = statSync(PID_PATH);
3950
+ return Date.now() - stat.mtimeMs < MIN_DAEMON_AGE_MS;
3135
3951
  } catch {
3952
+ return false;
3136
3953
  }
3137
- try {
3138
- unlinkSync2(SOCKET_PATH);
3139
- } catch {
3954
+ }
3955
+ async function retryThenRestart(doRequest, label) {
3956
+ const result = await doRequest();
3957
+ if (!result.error) {
3958
+ _consecutiveFailures = 0;
3959
+ return result;
3960
+ }
3961
+ _consecutiveFailures++;
3962
+ for (let i = 0; i < MAX_RETRIES_BEFORE_RESTART; i++) {
3963
+ const delayMs = RETRY_DELAYS_MS[i] ?? 5e3;
3964
+ process.stderr.write(`[exed-client] ${label} failed (${result.error}), retry ${i + 1}/${MAX_RETRIES_BEFORE_RESTART} in ${delayMs}ms
3965
+ `);
3966
+ await new Promise((r) => setTimeout(r, delayMs));
3967
+ if (!_connected) {
3968
+ if (!await connectToSocket()) continue;
3969
+ }
3970
+ const retry = await doRequest();
3971
+ if (!retry.error) {
3972
+ _consecutiveFailures = 0;
3973
+ return retry;
3974
+ }
3975
+ _consecutiveFailures++;
3976
+ }
3977
+ if (isDaemonTooYoung()) {
3978
+ process.stderr.write(`[exed-client] ${label}: daemon too young (< ${MIN_DAEMON_AGE_MS / 1e3}s) \u2014 skipping restart
3979
+ `);
3980
+ return { error: result.error };
3981
+ }
3982
+ process.stderr.write(`[exed-client] ${label}: ${_consecutiveFailures} consecutive failures \u2014 restarting daemon
3983
+ `);
3984
+ killAndRespawnDaemon();
3985
+ const start = Date.now();
3986
+ let delay2 = 200;
3987
+ while (Date.now() - start < CONNECT_TIMEOUT_MS) {
3988
+ await new Promise((r) => setTimeout(r, delay2));
3989
+ if (await connectToSocket()) break;
3990
+ delay2 = Math.min(delay2 * 2, 3e3);
3140
3991
  }
3141
- spawnDaemon();
3992
+ if (!_connected) return { error: "Daemon restart failed" };
3993
+ const final = await doRequest();
3994
+ if (!final.error) _consecutiveFailures = 0;
3995
+ return final;
3142
3996
  }
3143
3997
  async function embedViaClient(text, priority = "high") {
3144
3998
  if (!_connected && !await connectEmbedDaemon()) return null;
3145
3999
  _requestCount++;
3146
4000
  if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
3147
4001
  const health = await pingDaemon();
3148
- if (!health) {
4002
+ if (!health && !isDaemonTooYoung()) {
3149
4003
  process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
3150
4004
  `);
3151
4005
  killAndRespawnDaemon();
3152
4006
  const start = Date.now();
3153
- let delay2 = 200;
4007
+ let d = 200;
3154
4008
  while (Date.now() - start < CONNECT_TIMEOUT_MS) {
3155
- await new Promise((r) => setTimeout(r, delay2));
4009
+ await new Promise((r) => setTimeout(r, d));
3156
4010
  if (await connectToSocket()) break;
3157
- delay2 = Math.min(delay2 * 2, 3e3);
4011
+ d = Math.min(d * 2, 3e3);
3158
4012
  }
3159
4013
  if (!_connected) return null;
3160
4014
  }
3161
4015
  }
3162
- const result = await sendRequest([text], priority);
3163
- if (!result.error && result.vectors?.[0]) return result.vectors[0];
3164
- if (result.error) {
3165
- process.stderr.write(`[exed-client] Embed failed (${result.error}) \u2014 attempting restart
3166
- `);
3167
- killAndRespawnDaemon();
3168
- const start = Date.now();
3169
- let delay2 = 200;
3170
- while (Date.now() - start < CONNECT_TIMEOUT_MS) {
3171
- await new Promise((r) => setTimeout(r, delay2));
3172
- if (await connectToSocket()) break;
3173
- delay2 = Math.min(delay2 * 2, 3e3);
3174
- }
3175
- if (!_connected) return null;
3176
- const retry = await sendRequest([text], priority);
3177
- if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
3178
- process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
3179
- `);
3180
- }
3181
- return null;
4016
+ const result = await retryThenRestart(
4017
+ () => sendRequest([text], priority),
4018
+ "Embed"
4019
+ );
4020
+ return !result.error && result.vectors?.[0] ? result.vectors[0] : null;
3182
4021
  }
3183
4022
  function disconnectClient() {
3184
4023
  if (_socket) {
@@ -3193,14 +4032,14 @@ function disconnectClient() {
3193
4032
  entry.resolve({ error: "Client disconnected" });
3194
4033
  }
3195
4034
  }
3196
- 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;
4035
+ 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;
3197
4036
  var init_exe_daemon_client = __esm({
3198
4037
  "src/lib/exe-daemon-client.ts"() {
3199
4038
  "use strict";
3200
4039
  init_config();
3201
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
3202
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
3203
- SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
4040
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path8.join(EXE_AI_DIR, "exed.sock");
4041
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path8.join(EXE_AI_DIR, "exed.pid");
4042
+ SPAWN_LOCK_PATH = path8.join(EXE_AI_DIR, "exed-spawn.lock");
3204
4043
  SPAWN_LOCK_STALE_MS = 3e4;
3205
4044
  CONNECT_TIMEOUT_MS = 15e3;
3206
4045
  REQUEST_TIMEOUT_MS = 3e4;
@@ -3208,7 +4047,11 @@ var init_exe_daemon_client = __esm({
3208
4047
  _connected = false;
3209
4048
  _buffer = "";
3210
4049
  _requestCount = 0;
4050
+ _consecutiveFailures = 0;
3211
4051
  HEALTH_CHECK_INTERVAL = 100;
4052
+ MAX_RETRIES_BEFORE_RESTART = 3;
4053
+ RETRY_DELAYS_MS = [1e3, 3e3, 5e3];
4054
+ MIN_DAEMON_AGE_MS = 3e4;
3212
4055
  _pending = /* @__PURE__ */ new Map();
3213
4056
  MAX_BUFFER = 1e7;
3214
4057
  }
@@ -3252,8 +4095,8 @@ async function embedDirect(text) {
3252
4095
  const llamaCpp = await import("node-llama-cpp");
3253
4096
  const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
3254
4097
  const { existsSync: existsSync18 } = await import("fs");
3255
- const path22 = await import("path");
3256
- const modelPath = path22.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
4098
+ const path23 = await import("path");
4099
+ const modelPath = path23.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
3257
4100
  if (!existsSync18(modelPath)) {
3258
4101
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
3259
4102
  }
@@ -3289,7 +4132,7 @@ __export(project_name_exports, {
3289
4132
  getProjectName: () => getProjectName
3290
4133
  });
3291
4134
  import { execSync as execSync2 } from "child_process";
3292
- import path7 from "path";
4135
+ import path9 from "path";
3293
4136
  function getProjectName(cwd) {
3294
4137
  const dir = cwd ?? process.cwd();
3295
4138
  if (_cached && _cachedCwd === dir) return _cached;
@@ -3302,7 +4145,7 @@ function getProjectName(cwd) {
3302
4145
  timeout: 2e3,
3303
4146
  stdio: ["pipe", "pipe", "pipe"]
3304
4147
  }).trim();
3305
- repoRoot = path7.dirname(gitCommonDir);
4148
+ repoRoot = path9.dirname(gitCommonDir);
3306
4149
  } catch {
3307
4150
  repoRoot = execSync2("git rev-parse --show-toplevel", {
3308
4151
  cwd: dir,
@@ -3311,11 +4154,11 @@ function getProjectName(cwd) {
3311
4154
  stdio: ["pipe", "pipe", "pipe"]
3312
4155
  }).trim();
3313
4156
  }
3314
- _cached = path7.basename(repoRoot);
4157
+ _cached = path9.basename(repoRoot);
3315
4158
  _cachedCwd = dir;
3316
4159
  return _cached;
3317
4160
  } catch {
3318
- _cached = path7.basename(dir);
4161
+ _cached = path9.basename(dir);
3319
4162
  _cachedCwd = dir;
3320
4163
  return _cached;
3321
4164
  }
@@ -3339,8 +4182,8 @@ __export(file_grep_exports, {
3339
4182
  grepProjectFiles: () => grepProjectFiles
3340
4183
  });
3341
4184
  import { execSync as execSync3 } from "child_process";
3342
- import { readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync2, existsSync as existsSync7 } from "fs";
3343
- import path8 from "path";
4185
+ import { readFileSync as readFileSync5, readdirSync as readdirSync2, statSync as statSync2, existsSync as existsSync8 } from "fs";
4186
+ import path10 from "path";
3344
4187
  import crypto from "crypto";
3345
4188
  function hasRipgrep() {
3346
4189
  if (_hasRg === null) {
@@ -3380,7 +4223,7 @@ async function grepProjectFiles(query, projectRoot, options) {
3380
4223
  session_id: "file-grep",
3381
4224
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3382
4225
  tool_name: "file_grep",
3383
- project_name: path8.basename(projectRoot),
4226
+ project_name: path10.basename(projectRoot),
3384
4227
  has_error: false,
3385
4228
  raw_text: `${prefix} ${buildSnippet(hit, projectRoot)}`,
3386
4229
  vector: null,
@@ -3392,7 +4235,7 @@ function getChunkContext(filePath, lineNumber) {
3392
4235
  try {
3393
4236
  const ext = filePath.split(".").pop()?.toLowerCase();
3394
4237
  if (ext !== "ts" && ext !== "tsx" && ext !== "js" && ext !== "jsx") return "";
3395
- const source = readFileSync4(filePath, "utf8");
4238
+ const source = readFileSync5(filePath, "utf8");
3396
4239
  const lines = source.split("\n");
3397
4240
  for (let i = Math.min(lineNumber - 1, lines.length - 1); i >= 0; i--) {
3398
4241
  const line = lines[i];
@@ -3454,11 +4297,11 @@ function grepWithNodeFs(pattern, projectRoot, patterns) {
3454
4297
  const files = collectFiles(projectRoot, patterns ?? DEFAULT_PATTERNS);
3455
4298
  const hits = [];
3456
4299
  for (const filePath of files.slice(0, MAX_FILES)) {
3457
- const absPath = path8.join(projectRoot, filePath);
4300
+ const absPath = path10.join(projectRoot, filePath);
3458
4301
  try {
3459
4302
  const stat = statSync2(absPath);
3460
4303
  if (stat.size > MAX_FILE_SIZE) continue;
3461
- const content = readFileSync4(absPath, "utf8");
4304
+ const content = readFileSync5(absPath, "utf8");
3462
4305
  const lines = content.split("\n");
3463
4306
  const matches = content.match(regex);
3464
4307
  if (!matches || matches.length === 0) continue;
@@ -3481,15 +4324,15 @@ function collectFiles(root, patterns) {
3481
4324
  const files = [];
3482
4325
  function walk(dir, relative) {
3483
4326
  if (files.length >= MAX_FILES) return;
3484
- const basename = path8.basename(dir);
4327
+ const basename = path10.basename(dir);
3485
4328
  if (EXCLUDE_DIRS.includes(basename)) return;
3486
4329
  try {
3487
4330
  const entries = readdirSync2(dir, { withFileTypes: true });
3488
4331
  for (const entry of entries) {
3489
4332
  if (files.length >= MAX_FILES) return;
3490
- const rel = path8.join(relative, entry.name);
4333
+ const rel = path10.join(relative, entry.name);
3491
4334
  if (entry.isDirectory()) {
3492
- walk(path8.join(dir, entry.name), rel);
4335
+ walk(path10.join(dir, entry.name), rel);
3493
4336
  } else if (entry.isFile()) {
3494
4337
  for (const pat of patterns) {
3495
4338
  if (matchGlob(rel, pat)) {
@@ -3521,7 +4364,7 @@ function matchGlob(filePath, pattern) {
3521
4364
  if (slashIdx !== -1) {
3522
4365
  const dir = pattern.slice(0, slashIdx);
3523
4366
  const ext2 = pattern.slice(slashIdx + 1).replace("*", "");
3524
- const fileDir = path8.dirname(filePath);
4367
+ const fileDir = path10.dirname(filePath);
3525
4368
  return fileDir === dir && filePath.endsWith(ext2);
3526
4369
  }
3527
4370
  const ext = pattern.replace("*", "");
@@ -3529,9 +4372,9 @@ function matchGlob(filePath, pattern) {
3529
4372
  }
3530
4373
  function buildSnippet(hit, projectRoot) {
3531
4374
  try {
3532
- const absPath = path8.join(projectRoot, hit.filePath);
3533
- if (!existsSync7(absPath)) return hit.matchLine;
3534
- const lines = readFileSync4(absPath, "utf8").split("\n");
4375
+ const absPath = path10.join(projectRoot, hit.filePath);
4376
+ if (!existsSync8(absPath)) return hit.matchLine;
4377
+ const lines = readFileSync5(absPath, "utf8").split("\n");
3535
4378
  const start = Math.max(0, hit.lineNumber - 3);
3536
4379
  const end = Math.min(lines.length, hit.lineNumber + 2);
3537
4380
  return lines.slice(start, end).join("\n").slice(0, 500);
@@ -4196,13 +5039,13 @@ var init_session_key = __esm({
4196
5039
  });
4197
5040
 
4198
5041
  // src/lib/session-registry.ts
4199
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync8 } from "fs";
4200
- import path10 from "path";
4201
- import os5 from "os";
5042
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync9 } from "fs";
5043
+ import path12 from "path";
5044
+ import os6 from "os";
4202
5045
  function registerSession(entry) {
4203
- const dir = path10.dirname(REGISTRY_PATH);
4204
- if (!existsSync8(dir)) {
4205
- mkdirSync3(dir, { recursive: true });
5046
+ const dir = path12.dirname(REGISTRY_PATH);
5047
+ if (!existsSync9(dir)) {
5048
+ mkdirSync4(dir, { recursive: true });
4206
5049
  }
4207
5050
  const sessions = listSessions();
4208
5051
  const idx = sessions.findIndex((s) => s.windowName === entry.windowName);
@@ -4211,11 +5054,11 @@ function registerSession(entry) {
4211
5054
  } else {
4212
5055
  sessions.push(entry);
4213
5056
  }
4214
- writeFileSync3(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
5057
+ writeFileSync4(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
4215
5058
  }
4216
5059
  function listSessions() {
4217
5060
  try {
4218
- const raw = readFileSync6(REGISTRY_PATH, "utf8");
5061
+ const raw = readFileSync7(REGISTRY_PATH, "utf8");
4219
5062
  return JSON.parse(raw);
4220
5063
  } catch {
4221
5064
  return [];
@@ -4225,7 +5068,7 @@ var REGISTRY_PATH;
4225
5068
  var init_session_registry = __esm({
4226
5069
  "src/lib/session-registry.ts"() {
4227
5070
  "use strict";
4228
- REGISTRY_PATH = path10.join(os5.homedir(), ".exe-os", "session-registry.json");
5071
+ REGISTRY_PATH = path12.join(os6.homedir(), ".exe-os", "session-registry.json");
4229
5072
  }
4230
5073
  });
4231
5074
 
@@ -4417,118 +5260,6 @@ var init_provider_table = __esm({
4417
5260
  }
4418
5261
  });
4419
5262
 
4420
- // src/lib/runtime-table.ts
4421
- var RUNTIME_TABLE, DEFAULT_RUNTIME;
4422
- var init_runtime_table = __esm({
4423
- "src/lib/runtime-table.ts"() {
4424
- "use strict";
4425
- RUNTIME_TABLE = {
4426
- codex: {
4427
- binary: "codex",
4428
- launchMode: "interactive",
4429
- autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
4430
- inlineFlag: "--no-alt-screen",
4431
- apiKeyEnv: "OPENAI_API_KEY",
4432
- defaultModel: "gpt-5.4"
4433
- },
4434
- opencode: {
4435
- binary: "opencode",
4436
- launchMode: "exec",
4437
- autoApproveFlag: "--dangerously-skip-permissions",
4438
- inlineFlag: "",
4439
- apiKeyEnv: "ANTHROPIC_API_KEY",
4440
- defaultModel: "anthropic/claude-sonnet-4-6"
4441
- }
4442
- };
4443
- DEFAULT_RUNTIME = "claude";
4444
- }
4445
- });
4446
-
4447
- // src/lib/agent-config.ts
4448
- var agent_config_exports = {};
4449
- __export(agent_config_exports, {
4450
- AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
4451
- DEFAULT_MODELS: () => DEFAULT_MODELS,
4452
- KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
4453
- RUNTIME_LABELS: () => RUNTIME_LABELS,
4454
- clearAgentRuntime: () => clearAgentRuntime,
4455
- getAgentRuntime: () => getAgentRuntime,
4456
- loadAgentConfig: () => loadAgentConfig,
4457
- saveAgentConfig: () => saveAgentConfig,
4458
- setAgentRuntime: () => setAgentRuntime
4459
- });
4460
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, existsSync as existsSync9, mkdirSync as mkdirSync4 } from "fs";
4461
- import path11 from "path";
4462
- function loadAgentConfig() {
4463
- if (!existsSync9(AGENT_CONFIG_PATH)) return {};
4464
- try {
4465
- return JSON.parse(readFileSync7(AGENT_CONFIG_PATH, "utf-8"));
4466
- } catch {
4467
- return {};
4468
- }
4469
- }
4470
- function saveAgentConfig(config) {
4471
- const dir = path11.dirname(AGENT_CONFIG_PATH);
4472
- if (!existsSync9(dir)) mkdirSync4(dir, { recursive: true });
4473
- writeFileSync4(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
4474
- }
4475
- function getAgentRuntime(agentId) {
4476
- const config = loadAgentConfig();
4477
- const entry = config[agentId];
4478
- if (entry) return entry;
4479
- const orgDefault = config["default"];
4480
- if (orgDefault) return orgDefault;
4481
- return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
4482
- }
4483
- function setAgentRuntime(agentId, runtime, model) {
4484
- const knownModels = KNOWN_RUNTIMES[runtime];
4485
- if (!knownModels) {
4486
- return {
4487
- ok: false,
4488
- error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
4489
- };
4490
- }
4491
- if (!knownModels.includes(model)) {
4492
- return {
4493
- ok: false,
4494
- error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
4495
- };
4496
- }
4497
- const config = loadAgentConfig();
4498
- config[agentId] = { runtime, model };
4499
- saveAgentConfig(config);
4500
- return { ok: true };
4501
- }
4502
- function clearAgentRuntime(agentId) {
4503
- const config = loadAgentConfig();
4504
- delete config[agentId];
4505
- saveAgentConfig(config);
4506
- }
4507
- var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
4508
- var init_agent_config = __esm({
4509
- "src/lib/agent-config.ts"() {
4510
- "use strict";
4511
- init_config();
4512
- init_runtime_table();
4513
- AGENT_CONFIG_PATH = path11.join(EXE_AI_DIR, "agent-config.json");
4514
- KNOWN_RUNTIMES = {
4515
- claude: ["claude-opus-4", "claude-sonnet-4", "claude-haiku-4.5"],
4516
- codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
4517
- opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
4518
- };
4519
- RUNTIME_LABELS = {
4520
- claude: "Claude Code (Anthropic)",
4521
- codex: "Codex (OpenAI)",
4522
- opencode: "OpenCode (open source)"
4523
- };
4524
- DEFAULT_MODELS = {
4525
- claude: "claude-opus-4",
4526
- codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
4527
- opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
4528
- };
4529
- }
4530
- });
4531
-
4532
5263
  // src/lib/intercom-queue.ts
4533
5264
  var intercom_queue_exports = {};
4534
5265
  __export(intercom_queue_exports, {
@@ -4539,10 +5270,10 @@ __export(intercom_queue_exports, {
4539
5270
  readQueue: () => readQueue
4540
5271
  });
4541
5272
  import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, renameSync as renameSync3, existsSync as existsSync10, mkdirSync as mkdirSync5 } from "fs";
4542
- import path12 from "path";
4543
- import os6 from "os";
5273
+ import path13 from "path";
5274
+ import os7 from "os";
4544
5275
  function ensureDir() {
4545
- const dir = path12.dirname(QUEUE_PATH);
5276
+ const dir = path13.dirname(QUEUE_PATH);
4546
5277
  if (!existsSync10(dir)) mkdirSync5(dir, { recursive: true });
4547
5278
  }
4548
5279
  function readQueue() {
@@ -4648,26 +5379,26 @@ var QUEUE_PATH, MAX_RETRIES2, TTL_MS, INTERCOM_LOG;
4648
5379
  var init_intercom_queue = __esm({
4649
5380
  "src/lib/intercom-queue.ts"() {
4650
5381
  "use strict";
4651
- QUEUE_PATH = path12.join(os6.homedir(), ".exe-os", "intercom-queue.json");
5382
+ QUEUE_PATH = path13.join(os7.homedir(), ".exe-os", "intercom-queue.json");
4652
5383
  MAX_RETRIES2 = 5;
4653
5384
  TTL_MS = 60 * 60 * 1e3;
4654
- INTERCOM_LOG = path12.join(os6.homedir(), ".exe-os", "intercom.log");
5385
+ INTERCOM_LOG = path13.join(os7.homedir(), ".exe-os", "intercom.log");
4655
5386
  }
4656
5387
  });
4657
5388
 
4658
5389
  // src/lib/license.ts
4659
5390
  import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, existsSync as existsSync11, mkdirSync as mkdirSync6 } from "fs";
4660
5391
  import { randomUUID as randomUUID3 } from "crypto";
4661
- import path13 from "path";
5392
+ import path14 from "path";
4662
5393
  import { jwtVerify, importSPKI } from "jose";
4663
5394
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
4664
5395
  var init_license = __esm({
4665
5396
  "src/lib/license.ts"() {
4666
5397
  "use strict";
4667
5398
  init_config();
4668
- LICENSE_PATH = path13.join(EXE_AI_DIR, "license.key");
4669
- CACHE_PATH = path13.join(EXE_AI_DIR, "license-cache.json");
4670
- DEVICE_ID_PATH = path13.join(EXE_AI_DIR, "device-id");
5399
+ LICENSE_PATH = path14.join(EXE_AI_DIR, "license.key");
5400
+ CACHE_PATH = path14.join(EXE_AI_DIR, "license-cache.json");
5401
+ DEVICE_ID_PATH = path14.join(EXE_AI_DIR, "device-id");
4671
5402
  PLAN_LIMITS = {
4672
5403
  free: { devices: 1, employees: 1, memories: 5e3 },
4673
5404
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -4680,7 +5411,7 @@ var init_license = __esm({
4680
5411
 
4681
5412
  // src/lib/plan-limits.ts
4682
5413
  import { readFileSync as readFileSync10, existsSync as existsSync12 } from "fs";
4683
- import path14 from "path";
5414
+ import path15 from "path";
4684
5415
  function getLicenseSync() {
4685
5416
  try {
4686
5417
  if (!existsSync12(CACHE_PATH2)) return freeLicense();
@@ -4752,14 +5483,14 @@ var init_plan_limits = __esm({
4752
5483
  this.name = "PlanLimitError";
4753
5484
  }
4754
5485
  };
4755
- CACHE_PATH2 = path14.join(EXE_AI_DIR, "license-cache.json");
5486
+ CACHE_PATH2 = path15.join(EXE_AI_DIR, "license-cache.json");
4756
5487
  }
4757
5488
  });
4758
5489
 
4759
5490
  // src/lib/notifications.ts
4760
5491
  import crypto2 from "crypto";
4761
- import path15 from "path";
4762
- import os7 from "os";
5492
+ import path16 from "path";
5493
+ import os8 from "os";
4763
5494
  import {
4764
5495
  readFileSync as readFileSync11,
4765
5496
  readdirSync as readdirSync4,
@@ -4868,8 +5599,8 @@ var init_task_scope = __esm({
4868
5599
 
4869
5600
  // src/lib/tasks-crud.ts
4870
5601
  import crypto4 from "crypto";
4871
- import path16 from "path";
4872
- import os8 from "os";
5602
+ import path17 from "path";
5603
+ import os9 from "os";
4873
5604
  import { execSync as execSync7 } from "child_process";
4874
5605
  import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
4875
5606
  import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
@@ -5047,8 +5778,8 @@ ${laneWarning}` : laneWarning;
5047
5778
  }
5048
5779
  if (input2.baseDir) {
5049
5780
  try {
5050
- await mkdir4(path16.join(input2.baseDir, "exe", "output"), { recursive: true });
5051
- await mkdir4(path16.join(input2.baseDir, "exe", "research"), { recursive: true });
5781
+ await mkdir4(path17.join(input2.baseDir, "exe", "output"), { recursive: true });
5782
+ await mkdir4(path17.join(input2.baseDir, "exe", "research"), { recursive: true });
5052
5783
  await ensureArchitectureDoc(input2.baseDir, input2.projectName);
5053
5784
  await ensureGitignoreExe(input2.baseDir);
5054
5785
  } catch {
@@ -5084,9 +5815,9 @@ ${laneWarning}` : laneWarning;
5084
5815
  });
5085
5816
  if (input2.baseDir) {
5086
5817
  try {
5087
- const EXE_OS_DIR = path16.join(os8.homedir(), ".exe-os");
5088
- const mdPath = path16.join(EXE_OS_DIR, taskFile);
5089
- const mdDir = path16.dirname(mdPath);
5818
+ const EXE_OS_DIR = path17.join(os9.homedir(), ".exe-os");
5819
+ const mdPath = path17.join(EXE_OS_DIR, taskFile);
5820
+ const mdDir = path17.dirname(mdPath);
5090
5821
  if (!existsSync14(mdDir)) await mkdir4(mdDir, { recursive: true });
5091
5822
  const reviewer = input2.reviewer ?? input2.assignedBy;
5092
5823
  const mdContent = `# ${input2.title}
@@ -5387,7 +6118,7 @@ async function deleteTaskCore(taskId, _baseDir) {
5387
6118
  return { taskFile, assignedTo, assignedBy, taskSlug };
5388
6119
  }
5389
6120
  async function ensureArchitectureDoc(baseDir, projectName) {
5390
- const archPath = path16.join(baseDir, "exe", "ARCHITECTURE.md");
6121
+ const archPath = path17.join(baseDir, "exe", "ARCHITECTURE.md");
5391
6122
  try {
5392
6123
  if (existsSync14(archPath)) return;
5393
6124
  const template = [
@@ -5422,7 +6153,7 @@ async function ensureArchitectureDoc(baseDir, projectName) {
5422
6153
  }
5423
6154
  }
5424
6155
  async function ensureGitignoreExe(baseDir) {
5425
- const gitignorePath = path16.join(baseDir, ".gitignore");
6156
+ const gitignorePath = path17.join(baseDir, ".gitignore");
5426
6157
  try {
5427
6158
  if (existsSync14(gitignorePath)) {
5428
6159
  const content = readFileSync12(gitignorePath, "utf-8");
@@ -5468,7 +6199,7 @@ __export(tasks_review_exports, {
5468
6199
  isStale: () => isStale,
5469
6200
  listPendingReviews: () => listPendingReviews
5470
6201
  });
5471
- import path17 from "path";
6202
+ import path18 from "path";
5472
6203
  import { existsSync as existsSync15, readdirSync as readdirSync5, unlinkSync as unlinkSync5 } from "fs";
5473
6204
  function formatAge(isoTimestamp) {
5474
6205
  if (!isoTimestamp) return "";
@@ -5489,7 +6220,7 @@ async function countPendingReviews(sessionScope) {
5489
6220
  const client = getClient();
5490
6221
  if (sessionScope) {
5491
6222
  const result2 = await client.execute({
5492
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review' AND (session_scope = ? OR session_scope IS NULL)",
6223
+ sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review' AND session_scope = ?",
5493
6224
  args: [sessionScope]
5494
6225
  });
5495
6226
  return Number(result2.rows[0]?.cnt) || 0;
@@ -5754,11 +6485,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
5754
6485
  );
5755
6486
  }
5756
6487
  try {
5757
- const cacheDir = path17.join(EXE_AI_DIR, "session-cache");
6488
+ const cacheDir = path18.join(EXE_AI_DIR, "session-cache");
5758
6489
  if (existsSync15(cacheDir)) {
5759
6490
  for (const f of readdirSync5(cacheDir)) {
5760
6491
  if (f.startsWith("review-notified-")) {
5761
- unlinkSync5(path17.join(cacheDir, f));
6492
+ unlinkSync5(path18.join(cacheDir, f));
5762
6493
  }
5763
6494
  }
5764
6495
  }
@@ -5779,7 +6510,7 @@ var init_tasks_review = __esm({
5779
6510
  });
5780
6511
 
5781
6512
  // src/lib/tasks-chain.ts
5782
- import path18 from "path";
6513
+ import path19 from "path";
5783
6514
  import { readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
5784
6515
  async function cascadeUnblock(taskId, baseDir, now) {
5785
6516
  const client = getClient();
@@ -5796,7 +6527,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
5796
6527
  });
5797
6528
  for (const ur of unblockedRows.rows) {
5798
6529
  try {
5799
- const ubFile = path18.join(baseDir, String(ur.task_file));
6530
+ const ubFile = path19.join(baseDir, String(ur.task_file));
5800
6531
  let ubContent = await readFile4(ubFile, "utf-8");
5801
6532
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
5802
6533
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -6322,7 +7053,7 @@ __export(tasks_exports, {
6322
7053
  updateTaskStatus: () => updateTaskStatus,
6323
7054
  writeCheckpoint: () => writeCheckpoint
6324
7055
  });
6325
- import path19 from "path";
7056
+ import path20 from "path";
6326
7057
  import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync7, unlinkSync as unlinkSync6 } from "fs";
6327
7058
  async function createTask(input2) {
6328
7059
  const result = await createTaskCore(input2);
@@ -6342,8 +7073,8 @@ async function updateTask(input2) {
6342
7073
  const { row, taskFile, now, taskId } = await updateTaskStatus(input2);
6343
7074
  try {
6344
7075
  const agent = String(row.assigned_to);
6345
- const cacheDir = path19.join(EXE_AI_DIR, "session-cache");
6346
- const cachePath = path19.join(cacheDir, `current-task-${agent}.json`);
7076
+ const cacheDir = path20.join(EXE_AI_DIR, "session-cache");
7077
+ const cachePath = path20.join(cacheDir, `current-task-${agent}.json`);
6347
7078
  if (input2.status === "in_progress") {
6348
7079
  mkdirSync7(cacheDir, { recursive: true });
6349
7080
  writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
@@ -6814,12 +7545,12 @@ __export(tmux_routing_exports, {
6814
7545
  });
6815
7546
  import { execFileSync as execFileSync2, execSync as execSync8 } from "child_process";
6816
7547
  import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, mkdirSync as mkdirSync8, existsSync as existsSync16, appendFileSync, readdirSync as readdirSync6 } from "fs";
6817
- import path20 from "path";
6818
- import os9 from "os";
7548
+ import path21 from "path";
7549
+ import os10 from "os";
6819
7550
  import { fileURLToPath as fileURLToPath2 } from "url";
6820
7551
  import { unlinkSync as unlinkSync7 } from "fs";
6821
7552
  function spawnLockPath(sessionName) {
6822
- return path20.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
7553
+ return path21.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
6823
7554
  }
6824
7555
  function isProcessAlive(pid) {
6825
7556
  try {
@@ -6856,8 +7587,8 @@ function releaseSpawnLock2(sessionName) {
6856
7587
  function resolveBehaviorsExporterScript() {
6857
7588
  try {
6858
7589
  const thisFile = fileURLToPath2(import.meta.url);
6859
- const scriptPath = path20.join(
6860
- path20.dirname(thisFile),
7590
+ const scriptPath = path21.join(
7591
+ path21.dirname(thisFile),
6861
7592
  "..",
6862
7593
  "bin",
6863
7594
  "exe-export-behaviors.js"
@@ -6932,7 +7663,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
6932
7663
  mkdirSync8(SESSION_CACHE, { recursive: true });
6933
7664
  }
6934
7665
  const rootExe = extractRootExe(parentExe) ?? parentExe;
6935
- const filePath = path20.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
7666
+ const filePath = path21.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
6936
7667
  writeFileSync8(filePath, JSON.stringify({
6937
7668
  parentExe: rootExe,
6938
7669
  dispatchedBy: dispatchedBy || rootExe,
@@ -6941,7 +7672,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
6941
7672
  }
6942
7673
  function getParentExe(sessionKey) {
6943
7674
  try {
6944
- const data = JSON.parse(readFileSync13(path20.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
7675
+ const data = JSON.parse(readFileSync13(path21.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
6945
7676
  return data.parentExe || null;
6946
7677
  } catch {
6947
7678
  return null;
@@ -6950,7 +7681,7 @@ function getParentExe(sessionKey) {
6950
7681
  function getDispatchedBy(sessionKey) {
6951
7682
  try {
6952
7683
  const data = JSON.parse(readFileSync13(
6953
- path20.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
7684
+ path21.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
6954
7685
  "utf8"
6955
7686
  ));
6956
7687
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -7136,7 +7867,7 @@ function sendIntercom(targetSession) {
7136
7867
  try {
7137
7868
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
7138
7869
  const agent = baseAgentName(rawAgent);
7139
- const markerPath = path20.join(SESSION_CACHE, `current-task-${agent}.json`);
7870
+ const markerPath = path21.join(SESSION_CACHE, `current-task-${agent}.json`);
7140
7871
  if (existsSync16(markerPath)) {
7141
7872
  logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
7142
7873
  return "debounced";
@@ -7146,7 +7877,7 @@ function sendIntercom(targetSession) {
7146
7877
  try {
7147
7878
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
7148
7879
  const agent = baseAgentName(rawAgent);
7149
- const taskDir = path20.join(process.cwd(), "exe", agent);
7880
+ const taskDir = path21.join(process.cwd(), "exe", agent);
7150
7881
  if (existsSync16(taskDir)) {
7151
7882
  const files = readdirSync6(taskDir).filter(
7152
7883
  (f) => f.endsWith(".md") && f !== "DONE.txt"
@@ -7280,8 +8011,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
7280
8011
  const transport = getTransport();
7281
8012
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
7282
8013
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
7283
- const logDir = path20.join(os9.homedir(), ".exe-os", "session-logs");
7284
- const logFile = path20.join(logDir, `${instanceLabel}-${Date.now()}.log`);
8014
+ const logDir = path21.join(os10.homedir(), ".exe-os", "session-logs");
8015
+ const logFile = path21.join(logDir, `${instanceLabel}-${Date.now()}.log`);
7285
8016
  if (!existsSync16(logDir)) {
7286
8017
  mkdirSync8(logDir, { recursive: true });
7287
8018
  }
@@ -7289,14 +8020,14 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
7289
8020
  let cleanupSuffix = "";
7290
8021
  try {
7291
8022
  const thisFile = fileURLToPath2(import.meta.url);
7292
- const cleanupScript = path20.join(path20.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
8023
+ const cleanupScript = path21.join(path21.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
7293
8024
  if (existsSync16(cleanupScript)) {
7294
8025
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
7295
8026
  }
7296
8027
  } catch {
7297
8028
  }
7298
8029
  try {
7299
- const claudeJsonPath = path20.join(os9.homedir(), ".claude.json");
8030
+ const claudeJsonPath = path21.join(os10.homedir(), ".claude.json");
7300
8031
  let claudeJson = {};
7301
8032
  try {
7302
8033
  claudeJson = JSON.parse(readFileSync13(claudeJsonPath, "utf8"));
@@ -7311,10 +8042,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
7311
8042
  } catch {
7312
8043
  }
7313
8044
  try {
7314
- const settingsDir = path20.join(os9.homedir(), ".claude", "projects");
8045
+ const settingsDir = path21.join(os10.homedir(), ".claude", "projects");
7315
8046
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
7316
- const projSettingsDir = path20.join(settingsDir, normalizedKey);
7317
- const settingsPath = path20.join(projSettingsDir, "settings.json");
8047
+ const projSettingsDir = path21.join(settingsDir, normalizedKey);
8048
+ const settingsPath = path21.join(projSettingsDir, "settings.json");
7318
8049
  let settings = {};
7319
8050
  try {
7320
8051
  settings = JSON.parse(readFileSync13(settingsPath, "utf8"));
@@ -7361,8 +8092,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
7361
8092
  let behaviorsFlag = "";
7362
8093
  let legacyFallbackWarned = false;
7363
8094
  if (!useExeAgent && !useBinSymlink) {
7364
- const identityPath = path20.join(
7365
- os9.homedir(),
8095
+ const identityPath = path21.join(
8096
+ os10.homedir(),
7366
8097
  ".exe-os",
7367
8098
  "identity",
7368
8099
  `${employeeName}.md`
@@ -7377,7 +8108,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
7377
8108
  }
7378
8109
  const behaviorsFile = exportBehaviorsSync(
7379
8110
  employeeName,
7380
- path20.basename(spawnCwd),
8111
+ path21.basename(spawnCwd),
7381
8112
  sessionName
7382
8113
  );
7383
8114
  if (behaviorsFile) {
@@ -7392,9 +8123,9 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
7392
8123
  }
7393
8124
  let sessionContextFlag = "";
7394
8125
  try {
7395
- const ctxDir = path20.join(os9.homedir(), ".exe-os", "session-cache");
8126
+ const ctxDir = path21.join(os10.homedir(), ".exe-os", "session-cache");
7396
8127
  mkdirSync8(ctxDir, { recursive: true });
7397
- const ctxFile = path20.join(ctxDir, `session-context-${sessionName}.md`);
8128
+ const ctxFile = path21.join(ctxDir, `session-context-${sessionName}.md`);
7398
8129
  const ctxContent = [
7399
8130
  `## Session Context`,
7400
8131
  `You are running in tmux session: ${sessionName}.`,
@@ -7478,7 +8209,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
7478
8209
  transport.pipeLog(sessionName, logFile);
7479
8210
  try {
7480
8211
  const mySession = getMySession();
7481
- const dispatchInfo = path20.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
8212
+ const dispatchInfo = path21.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
7482
8213
  writeFileSync8(dispatchInfo, JSON.stringify({
7483
8214
  dispatchedBy: mySession,
7484
8215
  rootExe: exeSession,
@@ -7553,15 +8284,15 @@ var init_tmux_routing = __esm({
7553
8284
  init_intercom_queue();
7554
8285
  init_plan_limits();
7555
8286
  init_employees();
7556
- SPAWN_LOCK_DIR = path20.join(os9.homedir(), ".exe-os", "spawn-locks");
7557
- SESSION_CACHE = path20.join(os9.homedir(), ".exe-os", "session-cache");
8287
+ SPAWN_LOCK_DIR = path21.join(os10.homedir(), ".exe-os", "spawn-locks");
8288
+ SESSION_CACHE = path21.join(os10.homedir(), ".exe-os", "session-cache");
7558
8289
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
7559
8290
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
7560
8291
  VERIFY_PANE_LINES = 200;
7561
8292
  INTERCOM_DEBOUNCE_MS = 3e4;
7562
8293
  CODEX_DEBOUNCE_MS = 12e4;
7563
- INTERCOM_LOG2 = path20.join(os9.homedir(), ".exe-os", "intercom.log");
7564
- DEBOUNCE_FILE = path20.join(SESSION_CACHE, "intercom-debounce.json");
8294
+ INTERCOM_LOG2 = path21.join(os10.homedir(), ".exe-os", "intercom.log");
8295
+ DEBOUNCE_FILE = path21.join(SESSION_CACHE, "intercom-debounce.json");
7565
8296
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
7566
8297
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
7567
8298
  }
@@ -7824,7 +8555,7 @@ init_config();
7824
8555
  init_store();
7825
8556
  import { spawn as spawn2 } from "child_process";
7826
8557
  import { readFileSync as readFileSync14, writeFileSync as writeFileSync9, mkdirSync as mkdirSync9, existsSync as existsSync17, openSync as openSync2, closeSync as closeSync2 } from "fs";
7827
- import path21 from "path";
8558
+ import path22 from "path";
7828
8559
  import { fileURLToPath as fileURLToPath3 } from "url";
7829
8560
 
7830
8561
  // src/lib/hybrid-search.ts
@@ -8364,10 +9095,10 @@ async function trajectoryBypass(queryText, agentId, options, limit) {
8364
9095
  init_config();
8365
9096
  init_session_key();
8366
9097
  init_employees();
8367
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, unlinkSync as unlinkSync3, readdirSync as readdirSync3 } from "fs";
9098
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, unlinkSync as unlinkSync3, readdirSync as readdirSync3 } from "fs";
8368
9099
  import { execSync as execSync5 } from "child_process";
8369
- import path9 from "path";
8370
- var CACHE_DIR = path9.join(EXE_AI_DIR, "session-cache");
9100
+ import path11 from "path";
9101
+ var CACHE_DIR = path11.join(EXE_AI_DIR, "session-cache");
8371
9102
  var STALE_MS = 24 * 60 * 60 * 1e3;
8372
9103
  function isNameWithOptionalInstance(candidate, baseName) {
8373
9104
  if (candidate === baseName) return true;
@@ -8412,12 +9143,12 @@ function resolveActiveAgentFromTmuxSession(sessionName) {
8412
9143
  return null;
8413
9144
  }
8414
9145
  function getMarkerPath() {
8415
- return path9.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
9146
+ return path11.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
8416
9147
  }
8417
9148
  function getActiveAgent() {
8418
9149
  try {
8419
9150
  const markerPath = getMarkerPath();
8420
- const raw = readFileSync5(markerPath, "utf8");
9151
+ const raw = readFileSync6(markerPath, "utf8");
8421
9152
  const data = JSON.parse(raw);
8422
9153
  if (data.agentId) {
8423
9154
  if (data.startedAt) {
@@ -8470,7 +9201,7 @@ if (!process.env.AGENT_ID) {
8470
9201
  if (!loadConfigSync().autoRetrieval) {
8471
9202
  process.exit(0);
8472
9203
  }
8473
- var WORKER_LOG_PATH = path21.join(EXE_AI_DIR, "workers.log");
9204
+ var WORKER_LOG_PATH = path22.join(EXE_AI_DIR, "workers.log");
8474
9205
  function openWorkerLog() {
8475
9206
  try {
8476
9207
  return openSync2(WORKER_LOG_PATH, "a");
@@ -8478,10 +9209,10 @@ function openWorkerLog() {
8478
9209
  return "ignore";
8479
9210
  }
8480
9211
  }
8481
- var CACHE_DIR2 = path21.join(EXE_AI_DIR, "session-cache");
9212
+ var CACHE_DIR2 = path22.join(EXE_AI_DIR, "session-cache");
8482
9213
  function loadInjectedIds(sessionId) {
8483
9214
  try {
8484
- const raw = readFileSync14(path21.join(CACHE_DIR2, `${sessionId}.json`), "utf8");
9215
+ const raw = readFileSync14(path22.join(CACHE_DIR2, `${sessionId}.json`), "utf8");
8485
9216
  return new Set(JSON.parse(raw));
8486
9217
  } catch {
8487
9218
  return /* @__PURE__ */ new Set();
@@ -8491,7 +9222,7 @@ function saveInjectedIds(sessionId, ids) {
8491
9222
  try {
8492
9223
  mkdirSync9(CACHE_DIR2, { recursive: true });
8493
9224
  writeFileSync9(
8494
- path21.join(CACHE_DIR2, `${sessionId}.json`),
9225
+ path22.join(CACHE_DIR2, `${sessionId}.json`),
8495
9226
  JSON.stringify([...ids])
8496
9227
  );
8497
9228
  } catch {
@@ -8541,13 +9272,23 @@ process.stdin.on("end", async () => {
8541
9272
  const { getAgentRuntime: getAgentRuntime2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
8542
9273
  const { baseAgentName: baseAgentName2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
8543
9274
  const { isSessionBusy: isSessionBusy2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
9275
+ const MARKER_STALE_MS = 4 * 60 * 60 * 1e3;
8544
9276
  const hasInProgressTask = (agentName) => {
8545
9277
  try {
8546
- const path22 = __require("path");
8547
- const { existsSync: existsSync18 } = __require("fs");
8548
- const os10 = __require("os");
8549
- const markerPath = path22.join(os10.homedir(), ".exe-os", "session-cache", `current-task-${agentName}.json`);
8550
- return existsSync18(markerPath);
9278
+ const p = __require("path");
9279
+ const fs = __require("fs");
9280
+ const o = __require("os");
9281
+ const markerPath = p.join(o.homedir(), ".exe-os", "session-cache", `current-task-${agentName}.json`);
9282
+ if (!fs.existsSync(markerPath)) return false;
9283
+ const stat = fs.statSync(markerPath);
9284
+ if (Date.now() - stat.mtimeMs > MARKER_STALE_MS) {
9285
+ try {
9286
+ fs.unlinkSync(markerPath);
9287
+ } catch {
9288
+ }
9289
+ return false;
9290
+ }
9291
+ return true;
8551
9292
  } catch {
8552
9293
  return false;
8553
9294
  }
@@ -8599,7 +9340,7 @@ ${fresh.map(
8599
9340
  try {
8600
9341
  const { countPendingReviews: countPendingReviews2, countNewPendingReviewsSince: countNewPendingReviewsSince2 } = await Promise.resolve().then(() => (init_tasks_review(), tasks_review_exports));
8601
9342
  const sessionKey = getSessionKey();
8602
- const lastCheckPath = path21.join(CACHE_DIR2, `review-lastcheck-${sessionKey}.json`);
9343
+ const lastCheckPath = path22.join(CACHE_DIR2, `review-lastcheck-${sessionKey}.json`);
8603
9344
  let sessionScope;
8604
9345
  try {
8605
9346
  const { execSync: execSync9 } = await import("child_process");
@@ -8667,8 +9408,8 @@ IMPORTANT: After completing your current task, you MUST address the pending revi
8667
9408
  function spawnPromptWorker(prompt, sessionId, agent) {
8668
9409
  if (!loadConfigSync().autoIngestion) return;
8669
9410
  try {
8670
- const workerPath = path21.resolve(
8671
- path21.dirname(fileURLToPath3(import.meta.url)),
9411
+ const workerPath = path22.resolve(
9412
+ path22.dirname(fileURLToPath3(import.meta.url)),
8672
9413
  "prompt-ingest-worker.js"
8673
9414
  );
8674
9415
  if (!existsSync17(workerPath)) {