@askexenow/exe-os 0.9.7 → 0.9.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/dist/bin/backfill-conversations.js +953 -105
  2. package/dist/bin/backfill-responses.js +952 -104
  3. package/dist/bin/backfill-vectors.js +956 -108
  4. package/dist/bin/cleanup-stale-review-tasks.js +802 -58
  5. package/dist/bin/cli.js +2292 -1070
  6. package/dist/bin/exe-agent-config.js +157 -101
  7. package/dist/bin/exe-agent.js +55 -29
  8. package/dist/bin/exe-assign.js +940 -92
  9. package/dist/bin/exe-boot.js +1424 -442
  10. package/dist/bin/exe-call.js +240 -141
  11. package/dist/bin/exe-cloud.js +198 -70
  12. package/dist/bin/exe-dispatch.js +951 -192
  13. package/dist/bin/exe-doctor.js +791 -51
  14. package/dist/bin/exe-export-behaviors.js +790 -42
  15. package/dist/bin/exe-forget.js +771 -31
  16. package/dist/bin/exe-gateway.js +1592 -521
  17. package/dist/bin/exe-heartbeat.js +850 -109
  18. package/dist/bin/exe-kill.js +783 -35
  19. package/dist/bin/exe-launch-agent.js +1030 -107
  20. package/dist/bin/exe-link.js +916 -110
  21. package/dist/bin/exe-new-employee.js +526 -217
  22. package/dist/bin/exe-pending-messages.js +1046 -62
  23. package/dist/bin/exe-pending-notifications.js +1318 -111
  24. package/dist/bin/exe-pending-reviews.js +1040 -72
  25. package/dist/bin/exe-rename.js +772 -59
  26. package/dist/bin/exe-review.js +772 -32
  27. package/dist/bin/exe-search.js +982 -128
  28. package/dist/bin/exe-session-cleanup.js +1180 -306
  29. package/dist/bin/exe-settings.js +185 -105
  30. package/dist/bin/exe-start-codex.js +886 -132
  31. package/dist/bin/exe-start-opencode.js +873 -119
  32. package/dist/bin/exe-status.js +803 -59
  33. package/dist/bin/exe-team.js +772 -32
  34. package/dist/bin/git-sweep.js +1046 -223
  35. package/dist/bin/graph-backfill.js +779 -31
  36. package/dist/bin/graph-export.js +785 -37
  37. package/dist/bin/install.js +632 -200
  38. package/dist/bin/scan-tasks.js +1055 -232
  39. package/dist/bin/setup.js +1419 -320
  40. package/dist/bin/shard-migrate.js +783 -35
  41. package/dist/bin/update.js +138 -49
  42. package/dist/bin/wiki-sync.js +782 -34
  43. package/dist/gateway/index.js +1444 -449
  44. package/dist/hooks/bug-report-worker.js +1141 -269
  45. package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
  46. package/dist/hooks/commit-complete.js +1044 -221
  47. package/dist/hooks/error-recall.js +989 -135
  48. package/dist/hooks/exe-heartbeat-hook.js +99 -75
  49. package/dist/hooks/ingest-worker.js +4176 -3226
  50. package/dist/hooks/ingest.js +920 -168
  51. package/dist/hooks/instructions-loaded.js +874 -70
  52. package/dist/hooks/notification.js +860 -56
  53. package/dist/hooks/post-compact.js +881 -73
  54. package/dist/hooks/pre-compact.js +1050 -227
  55. package/dist/hooks/pre-tool-use.js +1084 -159
  56. package/dist/hooks/prompt-ingest-worker.js +1089 -164
  57. package/dist/hooks/prompt-submit.js +1469 -515
  58. package/dist/hooks/response-ingest-worker.js +1104 -179
  59. package/dist/hooks/session-end.js +1085 -251
  60. package/dist/hooks/session-start.js +1241 -231
  61. package/dist/hooks/stop.js +935 -109
  62. package/dist/hooks/subagent-stop.js +881 -73
  63. package/dist/hooks/summary-worker.js +1323 -307
  64. package/dist/index.js +1449 -452
  65. package/dist/lib/agent-config.js +28 -6
  66. package/dist/lib/cloud-sync.js +909 -115
  67. package/dist/lib/config.js +30 -10
  68. package/dist/lib/consolidation.js +42 -9
  69. package/dist/lib/database.js +739 -33
  70. package/dist/lib/db-daemon-client.js +73 -19
  71. package/dist/lib/db.js +2359 -0
  72. package/dist/lib/device-registry.js +760 -47
  73. package/dist/lib/embedder.js +201 -73
  74. package/dist/lib/employee-templates.js +30 -4
  75. package/dist/lib/employees.js +290 -86
  76. package/dist/lib/exe-daemon-client.js +187 -83
  77. package/dist/lib/exe-daemon.js +1696 -616
  78. package/dist/lib/hybrid-search.js +982 -128
  79. package/dist/lib/identity.js +43 -13
  80. package/dist/lib/license.js +133 -48
  81. package/dist/lib/messaging.js +167 -80
  82. package/dist/lib/reminders.js +35 -5
  83. package/dist/lib/schedules.js +772 -32
  84. package/dist/lib/skill-learning.js +54 -7
  85. package/dist/lib/store.js +779 -31
  86. package/dist/lib/task-router.js +94 -73
  87. package/dist/lib/tasks.js +298 -225
  88. package/dist/lib/tmux-routing.js +246 -172
  89. package/dist/lib/token-spend.js +52 -14
  90. package/dist/mcp/server.js +2893 -850
  91. package/dist/mcp/tools/complete-reminder.js +35 -5
  92. package/dist/mcp/tools/create-reminder.js +35 -5
  93. package/dist/mcp/tools/create-task.js +507 -323
  94. package/dist/mcp/tools/deactivate-behavior.js +40 -10
  95. package/dist/mcp/tools/list-reminders.js +35 -5
  96. package/dist/mcp/tools/list-tasks.js +277 -104
  97. package/dist/mcp/tools/send-message.js +129 -56
  98. package/dist/mcp/tools/update-task.js +1864 -188
  99. package/dist/runtime/index.js +1083 -259
  100. package/dist/tui/App.js +1501 -434
  101. package/package.json +3 -2
@@ -19,9 +19,47 @@ var __copyProps = (to, from, except, desc) => {
19
19
  };
20
20
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
21
21
 
22
+ // src/lib/secure-files.ts
23
+ import { chmodSync, existsSync, mkdirSync } from "fs";
24
+ import { chmod, mkdir } from "fs/promises";
25
+ async function ensurePrivateDir(dirPath) {
26
+ await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
27
+ try {
28
+ await chmod(dirPath, PRIVATE_DIR_MODE);
29
+ } catch {
30
+ }
31
+ }
32
+ function ensurePrivateDirSync(dirPath) {
33
+ mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
34
+ try {
35
+ chmodSync(dirPath, PRIVATE_DIR_MODE);
36
+ } catch {
37
+ }
38
+ }
39
+ async function enforcePrivateFile(filePath) {
40
+ try {
41
+ await chmod(filePath, PRIVATE_FILE_MODE);
42
+ } catch {
43
+ }
44
+ }
45
+ function enforcePrivateFileSync(filePath) {
46
+ try {
47
+ if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
48
+ } catch {
49
+ }
50
+ }
51
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
52
+ var init_secure_files = __esm({
53
+ "src/lib/secure-files.ts"() {
54
+ "use strict";
55
+ PRIVATE_DIR_MODE = 448;
56
+ PRIVATE_FILE_MODE = 384;
57
+ }
58
+ });
59
+
22
60
  // src/lib/config.ts
23
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
24
- import { readFileSync, existsSync, renameSync } from "fs";
61
+ import { readFile, writeFile } from "fs/promises";
62
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
25
63
  import path from "path";
26
64
  import os from "os";
27
65
  function resolveDataDir() {
@@ -29,7 +67,7 @@ function resolveDataDir() {
29
67
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
30
68
  const newDir = path.join(os.homedir(), ".exe-os");
31
69
  const legacyDir = path.join(os.homedir(), ".exe-mem");
32
- if (!existsSync(newDir) && existsSync(legacyDir)) {
70
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
33
71
  try {
34
72
  renameSync(legacyDir, newDir);
35
73
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -92,9 +130,9 @@ function normalizeAutoUpdate(raw) {
92
130
  }
93
131
  async function loadConfig() {
94
132
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
95
- await mkdir(dir, { recursive: true });
133
+ await ensurePrivateDir(dir);
96
134
  const configPath = path.join(dir, "config.json");
97
- if (!existsSync(configPath)) {
135
+ if (!existsSync2(configPath)) {
98
136
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
99
137
  }
100
138
  const raw = await readFile(configPath, "utf-8");
@@ -107,6 +145,7 @@ async function loadConfig() {
107
145
  `);
108
146
  try {
109
147
  await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
148
+ await enforcePrivateFile(configPath);
110
149
  } catch {
111
150
  }
112
151
  }
@@ -126,6 +165,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
126
165
  var init_config = __esm({
127
166
  "src/lib/config.ts"() {
128
167
  "use strict";
168
+ init_secure_files();
129
169
  EXE_AI_DIR = resolveDataDir();
130
170
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
131
171
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
@@ -270,6 +310,120 @@ var init_session_key = __esm({
270
310
  }
271
311
  });
272
312
 
313
+ // src/lib/runtime-table.ts
314
+ var RUNTIME_TABLE, DEFAULT_RUNTIME;
315
+ var init_runtime_table = __esm({
316
+ "src/lib/runtime-table.ts"() {
317
+ "use strict";
318
+ RUNTIME_TABLE = {
319
+ codex: {
320
+ binary: "codex",
321
+ launchMode: "interactive",
322
+ autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
323
+ inlineFlag: "--no-alt-screen",
324
+ apiKeyEnv: "OPENAI_API_KEY",
325
+ defaultModel: "gpt-5.4"
326
+ },
327
+ opencode: {
328
+ binary: "opencode",
329
+ launchMode: "exec",
330
+ autoApproveFlag: "--dangerously-skip-permissions",
331
+ inlineFlag: "",
332
+ apiKeyEnv: "ANTHROPIC_API_KEY",
333
+ defaultModel: "anthropic/claude-sonnet-4-6"
334
+ }
335
+ };
336
+ DEFAULT_RUNTIME = "claude";
337
+ }
338
+ });
339
+
340
+ // src/lib/agent-config.ts
341
+ var agent_config_exports = {};
342
+ __export(agent_config_exports, {
343
+ AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
344
+ DEFAULT_MODELS: () => DEFAULT_MODELS,
345
+ KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
346
+ RUNTIME_LABELS: () => RUNTIME_LABELS,
347
+ clearAgentRuntime: () => clearAgentRuntime,
348
+ getAgentRuntime: () => getAgentRuntime,
349
+ loadAgentConfig: () => loadAgentConfig,
350
+ saveAgentConfig: () => saveAgentConfig,
351
+ setAgentRuntime: () => setAgentRuntime
352
+ });
353
+ import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
354
+ import path2 from "path";
355
+ function loadAgentConfig() {
356
+ if (!existsSync3(AGENT_CONFIG_PATH)) return {};
357
+ try {
358
+ return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
359
+ } catch {
360
+ return {};
361
+ }
362
+ }
363
+ function saveAgentConfig(config) {
364
+ const dir = path2.dirname(AGENT_CONFIG_PATH);
365
+ ensurePrivateDirSync(dir);
366
+ writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
367
+ enforcePrivateFileSync(AGENT_CONFIG_PATH);
368
+ }
369
+ function getAgentRuntime(agentId) {
370
+ const config = loadAgentConfig();
371
+ const entry = config[agentId];
372
+ if (entry) return entry;
373
+ const orgDefault = config["default"];
374
+ if (orgDefault) return orgDefault;
375
+ return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
376
+ }
377
+ function setAgentRuntime(agentId, runtime, model) {
378
+ const knownModels = KNOWN_RUNTIMES[runtime];
379
+ if (!knownModels) {
380
+ return {
381
+ ok: false,
382
+ error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
383
+ };
384
+ }
385
+ if (!knownModels.includes(model)) {
386
+ return {
387
+ ok: false,
388
+ error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
389
+ };
390
+ }
391
+ const config = loadAgentConfig();
392
+ config[agentId] = { runtime, model };
393
+ saveAgentConfig(config);
394
+ return { ok: true };
395
+ }
396
+ function clearAgentRuntime(agentId) {
397
+ const config = loadAgentConfig();
398
+ delete config[agentId];
399
+ saveAgentConfig(config);
400
+ }
401
+ var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
402
+ var init_agent_config = __esm({
403
+ "src/lib/agent-config.ts"() {
404
+ "use strict";
405
+ init_config();
406
+ init_runtime_table();
407
+ init_secure_files();
408
+ AGENT_CONFIG_PATH = path2.join(EXE_AI_DIR, "agent-config.json");
409
+ KNOWN_RUNTIMES = {
410
+ claude: ["claude-opus-4", "claude-sonnet-4", "claude-haiku-4.5"],
411
+ codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
412
+ opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
413
+ };
414
+ RUNTIME_LABELS = {
415
+ claude: "Claude Code (Anthropic)",
416
+ codex: "Codex (OpenAI)",
417
+ opencode: "OpenCode (open source)"
418
+ };
419
+ DEFAULT_MODELS = {
420
+ claude: "claude-opus-4",
421
+ codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
422
+ opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
423
+ };
424
+ }
425
+ });
426
+
273
427
  // src/lib/employees.ts
274
428
  var employees_exports = {};
275
429
  __export(employees_exports, {
@@ -285,6 +439,7 @@ __export(employees_exports, {
285
439
  getEmployeeByRole: () => getEmployeeByRole,
286
440
  getEmployeeNamesByRole: () => getEmployeeNamesByRole,
287
441
  hasRole: () => hasRole,
442
+ hireEmployee: () => hireEmployee,
288
443
  isCoordinatorName: () => isCoordinatorName,
289
444
  isCoordinatorRole: () => isCoordinatorRole,
290
445
  isMultiInstance: () => isMultiInstance,
@@ -297,9 +452,9 @@ __export(employees_exports, {
297
452
  validateEmployeeName: () => validateEmployeeName
298
453
  });
299
454
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
300
- import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
455
+ import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
301
456
  import { execSync as execSync2 } from "child_process";
302
- import path2 from "path";
457
+ import path3 from "path";
303
458
  import os2 from "os";
304
459
  function normalizeRole(role) {
305
460
  return (role ?? "").trim().toLowerCase();
@@ -336,7 +491,7 @@ function validateEmployeeName(name) {
336
491
  return { valid: true };
337
492
  }
338
493
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
339
- if (!existsSync2(employeesPath)) {
494
+ if (!existsSync4(employeesPath)) {
340
495
  return [];
341
496
  }
342
497
  const raw = await readFile2(employeesPath, "utf-8");
@@ -347,13 +502,13 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
347
502
  }
348
503
  }
349
504
  async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
350
- await mkdir2(path2.dirname(employeesPath), { recursive: true });
505
+ await mkdir2(path3.dirname(employeesPath), { recursive: true });
351
506
  await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
352
507
  }
353
508
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
354
- if (!existsSync2(employeesPath)) return [];
509
+ if (!existsSync4(employeesPath)) return [];
355
510
  try {
356
- return JSON.parse(readFileSync2(employeesPath, "utf-8"));
511
+ return JSON.parse(readFileSync3(employeesPath, "utf-8"));
357
512
  } catch {
358
513
  return [];
359
514
  }
@@ -395,6 +550,52 @@ function addEmployee(employees, employee) {
395
550
  }
396
551
  return [...employees, normalized];
397
552
  }
553
+ function appendToCoordinatorTeam(employee) {
554
+ const coordinator = getCoordinatorEmployee(loadEmployeesSync());
555
+ if (!coordinator) return;
556
+ const idPath = path3.join(IDENTITY_DIR, `${coordinator.name}.md`);
557
+ if (!existsSync4(idPath)) return;
558
+ const content = readFileSync3(idPath, "utf-8");
559
+ if (content.includes(`**${capitalize(employee.name)}`)) return;
560
+ const teamMatch = content.match(TEAM_SECTION_RE);
561
+ if (!teamMatch || teamMatch.index === void 0) return;
562
+ const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
563
+ const nextHeading = afterTeam.match(/\n## /);
564
+ const entry = `
565
+ **${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
566
+ `;
567
+ let updated;
568
+ if (nextHeading && nextHeading.index !== void 0) {
569
+ const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
570
+ updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
571
+ } else {
572
+ updated = content.trimEnd() + "\n" + entry;
573
+ }
574
+ writeFileSync2(idPath, updated, "utf-8");
575
+ }
576
+ function capitalize(s) {
577
+ return s.charAt(0).toUpperCase() + s.slice(1);
578
+ }
579
+ async function hireEmployee(employee) {
580
+ const employees = await loadEmployees();
581
+ const updated = addEmployee(employees, employee);
582
+ await saveEmployees(updated);
583
+ try {
584
+ appendToCoordinatorTeam(employee);
585
+ } catch {
586
+ }
587
+ try {
588
+ const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
589
+ const config = loadAgentConfig2();
590
+ const name = employee.name.toLowerCase();
591
+ if (!config[name] && config["default"]) {
592
+ config[name] = { ...config["default"] };
593
+ saveAgentConfig2(config);
594
+ }
595
+ } catch {
596
+ }
597
+ return updated;
598
+ }
398
599
  async function normalizeRosterCase(rosterPath) {
399
600
  const employees = await loadEmployees(rosterPath);
400
601
  let changed = false;
@@ -404,14 +605,14 @@ async function normalizeRosterCase(rosterPath) {
404
605
  emp.name = emp.name.toLowerCase();
405
606
  changed = true;
406
607
  try {
407
- const identityDir = path2.join(os2.homedir(), ".exe-os", "identity");
408
- const oldPath = path2.join(identityDir, `${oldName}.md`);
409
- const newPath = path2.join(identityDir, `${emp.name}.md`);
410
- if (existsSync2(oldPath) && !existsSync2(newPath)) {
608
+ const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
609
+ const oldPath = path3.join(identityDir, `${oldName}.md`);
610
+ const newPath = path3.join(identityDir, `${emp.name}.md`);
611
+ if (existsSync4(oldPath) && !existsSync4(newPath)) {
411
612
  renameSync2(oldPath, newPath);
412
- } else if (existsSync2(oldPath) && oldPath !== newPath) {
413
- const content = readFileSync2(oldPath, "utf-8");
414
- writeFileSync(newPath, content, "utf-8");
613
+ } else if (existsSync4(oldPath) && oldPath !== newPath) {
614
+ const content = readFileSync3(oldPath, "utf-8");
615
+ writeFileSync2(newPath, content, "utf-8");
415
616
  if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
416
617
  unlinkSync(oldPath);
417
618
  }
@@ -441,7 +642,7 @@ function registerBinSymlinks(name) {
441
642
  errors.push("Could not find 'exe-os' in PATH");
442
643
  return { created, skipped, errors };
443
644
  }
444
- const binDir = path2.dirname(exeBinPath);
645
+ const binDir = path3.dirname(exeBinPath);
445
646
  let target;
446
647
  try {
447
648
  target = readlinkSync(exeBinPath);
@@ -451,8 +652,8 @@ function registerBinSymlinks(name) {
451
652
  }
452
653
  for (const suffix of ["", "-opencode"]) {
453
654
  const linkName = `${name}${suffix}`;
454
- const linkPath = path2.join(binDir, linkName);
455
- if (existsSync2(linkPath)) {
655
+ const linkPath = path3.join(binDir, linkName);
656
+ if (existsSync4(linkPath)) {
456
657
  skipped.push(linkName);
457
658
  continue;
458
659
  }
@@ -465,26 +666,28 @@ function registerBinSymlinks(name) {
465
666
  }
466
667
  return { created, skipped, errors };
467
668
  }
468
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
669
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
469
670
  var init_employees = __esm({
470
671
  "src/lib/employees.ts"() {
471
672
  "use strict";
472
673
  init_config();
473
- EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
674
+ EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
474
675
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
475
676
  COORDINATOR_ROLE = "COO";
476
677
  MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
678
+ IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
679
+ TEAM_SECTION_RE = /^## Team\b.*$/m;
477
680
  }
478
681
  });
479
682
 
480
683
  // src/lib/session-registry.ts
481
- import path4 from "path";
684
+ import path5 from "path";
482
685
  import os3 from "os";
483
686
  var REGISTRY_PATH;
484
687
  var init_session_registry = __esm({
485
688
  "src/lib/session-registry.ts"() {
486
689
  "use strict";
487
- REGISTRY_PATH = path4.join(os3.homedir(), ".exe-os", "session-registry.json");
690
+ REGISTRY_PATH = path5.join(os3.homedir(), ".exe-os", "session-registry.json");
488
691
  }
489
692
  });
490
693
 
@@ -624,52 +827,8 @@ var init_provider_table = __esm({
624
827
  }
625
828
  });
626
829
 
627
- // src/lib/runtime-table.ts
628
- var RUNTIME_TABLE;
629
- var init_runtime_table = __esm({
630
- "src/lib/runtime-table.ts"() {
631
- "use strict";
632
- RUNTIME_TABLE = {
633
- codex: {
634
- binary: "codex",
635
- launchMode: "interactive",
636
- autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
637
- inlineFlag: "--no-alt-screen",
638
- apiKeyEnv: "OPENAI_API_KEY",
639
- defaultModel: "gpt-5.4"
640
- },
641
- opencode: {
642
- binary: "opencode",
643
- launchMode: "exec",
644
- autoApproveFlag: "--dangerously-skip-permissions",
645
- inlineFlag: "",
646
- apiKeyEnv: "ANTHROPIC_API_KEY",
647
- defaultModel: "anthropic/claude-sonnet-4-6"
648
- }
649
- };
650
- }
651
- });
652
-
653
- // src/lib/agent-config.ts
654
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
655
- import path5 from "path";
656
- var AGENT_CONFIG_PATH, DEFAULT_MODELS;
657
- var init_agent_config = __esm({
658
- "src/lib/agent-config.ts"() {
659
- "use strict";
660
- init_config();
661
- init_runtime_table();
662
- AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
663
- DEFAULT_MODELS = {
664
- claude: "claude-opus-4",
665
- codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
666
- opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
667
- };
668
- }
669
- });
670
-
671
830
  // src/lib/intercom-queue.ts
672
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
831
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
673
832
  import path6 from "path";
674
833
  import os4 from "os";
675
834
  var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
@@ -737,13 +896,634 @@ var init_db_retry = __esm({
737
896
  }
738
897
  });
739
898
 
899
+ // src/lib/database-adapter.ts
900
+ import os5 from "os";
901
+ import path7 from "path";
902
+ import { createRequire } from "module";
903
+ import { pathToFileURL } from "url";
904
+ function quotedIdentifier(identifier) {
905
+ return `"${identifier.replace(/"/g, '""')}"`;
906
+ }
907
+ function unqualifiedTableName(name) {
908
+ const raw = name.trim().replace(/^"|"$/g, "");
909
+ const parts = raw.split(".");
910
+ return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
911
+ }
912
+ function stripTrailingSemicolon(sql) {
913
+ return sql.trim().replace(/;+\s*$/u, "");
914
+ }
915
+ function appendClause(sql, clause) {
916
+ const trimmed = stripTrailingSemicolon(sql);
917
+ const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
918
+ if (!returningMatch) {
919
+ return `${trimmed}${clause}`;
920
+ }
921
+ const idx = returningMatch.index;
922
+ return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
923
+ }
924
+ function normalizeStatement(stmt) {
925
+ if (typeof stmt === "string") {
926
+ return { kind: "positional", sql: stmt, args: [] };
927
+ }
928
+ const sql = stmt.sql;
929
+ if (Array.isArray(stmt.args) || stmt.args === void 0) {
930
+ return { kind: "positional", sql, args: stmt.args ?? [] };
931
+ }
932
+ return { kind: "named", sql, args: stmt.args };
933
+ }
934
+ function rewriteBooleanLiterals(sql) {
935
+ let out = sql;
936
+ for (const column of BOOLEAN_COLUMN_NAMES) {
937
+ const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
938
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
939
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
940
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
941
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
942
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
943
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
944
+ }
945
+ return out;
946
+ }
947
+ function rewriteInsertOrIgnore(sql) {
948
+ if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
949
+ return sql;
950
+ }
951
+ const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
952
+ return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
953
+ }
954
+ function rewriteInsertOrReplace(sql) {
955
+ const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
956
+ if (!match) {
957
+ return sql;
958
+ }
959
+ const rawTable = match[1];
960
+ const rawColumns = match[2];
961
+ const remainder = match[3];
962
+ const tableName = unqualifiedTableName(rawTable);
963
+ const conflictKeys = UPSERT_KEYS[tableName];
964
+ if (!conflictKeys?.length) {
965
+ return sql;
966
+ }
967
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
968
+ const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
969
+ const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
970
+ const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
971
+ return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
972
+ }
973
+ function rewriteSql(sql) {
974
+ let out = sql;
975
+ out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
976
+ out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
977
+ out = rewriteBooleanLiterals(out);
978
+ out = rewriteInsertOrReplace(out);
979
+ out = rewriteInsertOrIgnore(out);
980
+ return stripTrailingSemicolon(out);
981
+ }
982
+ function toBoolean(value) {
983
+ if (value === null || value === void 0) return value;
984
+ if (typeof value === "boolean") return value;
985
+ if (typeof value === "number") return value !== 0;
986
+ if (typeof value === "bigint") return value !== 0n;
987
+ if (typeof value === "string") {
988
+ const normalized = value.trim().toLowerCase();
989
+ if (normalized === "0" || normalized === "false") return false;
990
+ if (normalized === "1" || normalized === "true") return true;
991
+ }
992
+ return Boolean(value);
993
+ }
994
+ function countQuestionMarks(sql, end) {
995
+ let count = 0;
996
+ let inSingle = false;
997
+ let inDouble = false;
998
+ let inLineComment = false;
999
+ let inBlockComment = false;
1000
+ for (let i = 0; i < end; i++) {
1001
+ const ch = sql[i];
1002
+ const next = sql[i + 1];
1003
+ if (inLineComment) {
1004
+ if (ch === "\n") inLineComment = false;
1005
+ continue;
1006
+ }
1007
+ if (inBlockComment) {
1008
+ if (ch === "*" && next === "/") {
1009
+ inBlockComment = false;
1010
+ i += 1;
1011
+ }
1012
+ continue;
1013
+ }
1014
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
1015
+ inLineComment = true;
1016
+ i += 1;
1017
+ continue;
1018
+ }
1019
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
1020
+ inBlockComment = true;
1021
+ i += 1;
1022
+ continue;
1023
+ }
1024
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
1025
+ inSingle = !inSingle;
1026
+ continue;
1027
+ }
1028
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
1029
+ inDouble = !inDouble;
1030
+ continue;
1031
+ }
1032
+ if (!inSingle && !inDouble && ch === "?") {
1033
+ count += 1;
1034
+ }
1035
+ }
1036
+ return count;
1037
+ }
1038
+ function findBooleanPlaceholderIndexes(sql) {
1039
+ const indexes = /* @__PURE__ */ new Set();
1040
+ for (const column of BOOLEAN_COLUMN_NAMES) {
1041
+ const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
1042
+ for (const match of sql.matchAll(pattern)) {
1043
+ const matchText = match[0];
1044
+ const qIndex = match.index + matchText.lastIndexOf("?");
1045
+ indexes.add(countQuestionMarks(sql, qIndex + 1));
1046
+ }
1047
+ }
1048
+ return indexes;
1049
+ }
1050
+ function coerceInsertBooleanArgs(sql, args) {
1051
+ const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
1052
+ if (!match) return;
1053
+ const rawTable = match[1];
1054
+ const rawColumns = match[2];
1055
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
1056
+ if (!boolColumns?.size) return;
1057
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
1058
+ for (const [index, column] of columns.entries()) {
1059
+ if (boolColumns.has(column) && index < args.length) {
1060
+ args[index] = toBoolean(args[index]);
1061
+ }
1062
+ }
1063
+ }
1064
+ function coerceUpdateBooleanArgs(sql, args) {
1065
+ const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
1066
+ if (!match) return;
1067
+ const rawTable = match[1];
1068
+ const setClause = match[2];
1069
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
1070
+ if (!boolColumns?.size) return;
1071
+ const assignments = setClause.split(",");
1072
+ let placeholderIndex = 0;
1073
+ for (const assignment of assignments) {
1074
+ if (!assignment.includes("?")) continue;
1075
+ placeholderIndex += 1;
1076
+ const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
1077
+ if (colMatch && boolColumns.has(colMatch[1])) {
1078
+ args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
1079
+ }
1080
+ }
1081
+ }
1082
+ function coerceBooleanArgs(sql, args) {
1083
+ const nextArgs = [...args];
1084
+ coerceInsertBooleanArgs(sql, nextArgs);
1085
+ coerceUpdateBooleanArgs(sql, nextArgs);
1086
+ const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
1087
+ for (const index of placeholderIndexes) {
1088
+ if (index > 0 && index <= nextArgs.length) {
1089
+ nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
1090
+ }
1091
+ }
1092
+ return nextArgs;
1093
+ }
1094
+ function convertQuestionMarksToDollarParams(sql) {
1095
+ let out = "";
1096
+ let placeholder = 0;
1097
+ let inSingle = false;
1098
+ let inDouble = false;
1099
+ let inLineComment = false;
1100
+ let inBlockComment = false;
1101
+ for (let i = 0; i < sql.length; i++) {
1102
+ const ch = sql[i];
1103
+ const next = sql[i + 1];
1104
+ if (inLineComment) {
1105
+ out += ch;
1106
+ if (ch === "\n") inLineComment = false;
1107
+ continue;
1108
+ }
1109
+ if (inBlockComment) {
1110
+ out += ch;
1111
+ if (ch === "*" && next === "/") {
1112
+ out += next;
1113
+ inBlockComment = false;
1114
+ i += 1;
1115
+ }
1116
+ continue;
1117
+ }
1118
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
1119
+ out += ch + next;
1120
+ inLineComment = true;
1121
+ i += 1;
1122
+ continue;
1123
+ }
1124
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
1125
+ out += ch + next;
1126
+ inBlockComment = true;
1127
+ i += 1;
1128
+ continue;
1129
+ }
1130
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
1131
+ inSingle = !inSingle;
1132
+ out += ch;
1133
+ continue;
1134
+ }
1135
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
1136
+ inDouble = !inDouble;
1137
+ out += ch;
1138
+ continue;
1139
+ }
1140
+ if (!inSingle && !inDouble && ch === "?") {
1141
+ placeholder += 1;
1142
+ out += `$${placeholder}`;
1143
+ continue;
1144
+ }
1145
+ out += ch;
1146
+ }
1147
+ return out;
1148
+ }
1149
+ function translateStatementForPostgres(stmt) {
1150
+ const normalized = normalizeStatement(stmt);
1151
+ if (normalized.kind === "named") {
1152
+ throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
1153
+ }
1154
+ const rewrittenSql = rewriteSql(normalized.sql);
1155
+ const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
1156
+ return {
1157
+ sql: convertQuestionMarksToDollarParams(rewrittenSql),
1158
+ args: coercedArgs
1159
+ };
1160
+ }
1161
+ function shouldBypassPostgres(stmt) {
1162
+ const normalized = normalizeStatement(stmt);
1163
+ if (normalized.kind === "named") {
1164
+ return true;
1165
+ }
1166
+ return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
1167
+ }
1168
+ function shouldFallbackOnError(error) {
1169
+ const message = error instanceof Error ? error.message : String(error);
1170
+ return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
1171
+ }
1172
+ function isReadQuery(sql) {
1173
+ const trimmed = sql.trimStart();
1174
+ return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
1175
+ }
1176
+ function buildRow(row, columns) {
1177
+ const values = columns.map((column) => row[column]);
1178
+ return Object.assign(values, row);
1179
+ }
1180
+ function buildResultSet(rows, rowsAffected = 0) {
1181
+ const columns = rows[0] ? Object.keys(rows[0]) : [];
1182
+ const resultRows = rows.map((row) => buildRow(row, columns));
1183
+ return {
1184
+ columns,
1185
+ columnTypes: columns.map(() => ""),
1186
+ rows: resultRows,
1187
+ rowsAffected,
1188
+ lastInsertRowid: void 0,
1189
+ toJSON() {
1190
+ return {
1191
+ columns,
1192
+ columnTypes: columns.map(() => ""),
1193
+ rows,
1194
+ rowsAffected,
1195
+ lastInsertRowid: void 0
1196
+ };
1197
+ }
1198
+ };
1199
+ }
1200
+ async function loadPrismaClient() {
1201
+ if (!prismaClientPromise) {
1202
+ prismaClientPromise = (async () => {
1203
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
1204
+ if (explicitPath) {
1205
+ const module2 = await import(pathToFileURL(explicitPath).href);
1206
+ const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
1207
+ if (!PrismaClient2) {
1208
+ throw new Error(`No PrismaClient export found at ${explicitPath}`);
1209
+ }
1210
+ return new PrismaClient2();
1211
+ }
1212
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path7.join(os5.homedir(), "exe-db");
1213
+ const requireFromExeDb = createRequire(path7.join(exeDbRoot, "package.json"));
1214
+ const prismaEntry = requireFromExeDb.resolve("@prisma/client");
1215
+ const module = await import(pathToFileURL(prismaEntry).href);
1216
+ const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
1217
+ if (!PrismaClient) {
1218
+ throw new Error(`No PrismaClient export found in ${prismaEntry}`);
1219
+ }
1220
+ return new PrismaClient();
1221
+ })();
1222
+ }
1223
+ return prismaClientPromise;
1224
+ }
1225
+ async function ensureCompatibilityViews(prisma) {
1226
+ if (!compatibilityBootstrapPromise) {
1227
+ compatibilityBootstrapPromise = (async () => {
1228
+ for (const mapping of VIEW_MAPPINGS) {
1229
+ const relation = mapping.source.replace(/"/g, "");
1230
+ const rows = await prisma.$queryRawUnsafe(
1231
+ "SELECT to_regclass($1) AS regclass",
1232
+ relation
1233
+ );
1234
+ if (!rows[0]?.regclass) {
1235
+ continue;
1236
+ }
1237
+ await prisma.$executeRawUnsafe(
1238
+ `CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
1239
+ );
1240
+ }
1241
+ })();
1242
+ }
1243
+ return compatibilityBootstrapPromise;
1244
+ }
1245
+ async function executeOnPrisma(executor, stmt) {
1246
+ const translated = translateStatementForPostgres(stmt);
1247
+ if (isReadQuery(translated.sql)) {
1248
+ const rows = await executor.$queryRawUnsafe(
1249
+ translated.sql,
1250
+ ...translated.args
1251
+ );
1252
+ return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
1253
+ }
1254
+ const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
1255
+ return buildResultSet([], rowsAffected);
1256
+ }
1257
+ function splitSqlStatements(sql) {
1258
+ const parts = [];
1259
+ let current = "";
1260
+ let inSingle = false;
1261
+ let inDouble = false;
1262
+ let inLineComment = false;
1263
+ let inBlockComment = false;
1264
+ for (let i = 0; i < sql.length; i++) {
1265
+ const ch = sql[i];
1266
+ const next = sql[i + 1];
1267
+ if (inLineComment) {
1268
+ current += ch;
1269
+ if (ch === "\n") inLineComment = false;
1270
+ continue;
1271
+ }
1272
+ if (inBlockComment) {
1273
+ current += ch;
1274
+ if (ch === "*" && next === "/") {
1275
+ current += next;
1276
+ inBlockComment = false;
1277
+ i += 1;
1278
+ }
1279
+ continue;
1280
+ }
1281
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
1282
+ current += ch + next;
1283
+ inLineComment = true;
1284
+ i += 1;
1285
+ continue;
1286
+ }
1287
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
1288
+ current += ch + next;
1289
+ inBlockComment = true;
1290
+ i += 1;
1291
+ continue;
1292
+ }
1293
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
1294
+ inSingle = !inSingle;
1295
+ current += ch;
1296
+ continue;
1297
+ }
1298
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
1299
+ inDouble = !inDouble;
1300
+ current += ch;
1301
+ continue;
1302
+ }
1303
+ if (!inSingle && !inDouble && ch === ";") {
1304
+ if (current.trim()) {
1305
+ parts.push(current.trim());
1306
+ }
1307
+ current = "";
1308
+ continue;
1309
+ }
1310
+ current += ch;
1311
+ }
1312
+ if (current.trim()) {
1313
+ parts.push(current.trim());
1314
+ }
1315
+ return parts;
1316
+ }
1317
+ async function createPrismaDbAdapter(fallbackClient) {
1318
+ const prisma = await loadPrismaClient();
1319
+ await ensureCompatibilityViews(prisma);
1320
+ let closed = false;
1321
+ let adapter;
1322
+ const fallbackExecute = async (stmt, error) => {
1323
+ if (!fallbackClient) {
1324
+ if (error) throw error;
1325
+ throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
1326
+ }
1327
+ if (error) {
1328
+ process.stderr.write(
1329
+ `[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
1330
+ `
1331
+ );
1332
+ }
1333
+ return fallbackClient.execute(stmt);
1334
+ };
1335
+ adapter = {
1336
+ async execute(stmt) {
1337
+ if (shouldBypassPostgres(stmt)) {
1338
+ return fallbackExecute(stmt);
1339
+ }
1340
+ try {
1341
+ return await executeOnPrisma(prisma, stmt);
1342
+ } catch (error) {
1343
+ if (shouldFallbackOnError(error)) {
1344
+ return fallbackExecute(stmt, error);
1345
+ }
1346
+ throw error;
1347
+ }
1348
+ },
1349
+ async batch(stmts, mode) {
1350
+ if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
1351
+ if (!fallbackClient) {
1352
+ throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
1353
+ }
1354
+ return fallbackClient.batch(stmts, mode);
1355
+ }
1356
+ try {
1357
+ if (prisma.$transaction) {
1358
+ return await prisma.$transaction(async (tx) => {
1359
+ const results2 = [];
1360
+ for (const stmt of stmts) {
1361
+ results2.push(await executeOnPrisma(tx, stmt));
1362
+ }
1363
+ return results2;
1364
+ });
1365
+ }
1366
+ const results = [];
1367
+ for (const stmt of stmts) {
1368
+ results.push(await executeOnPrisma(prisma, stmt));
1369
+ }
1370
+ return results;
1371
+ } catch (error) {
1372
+ if (fallbackClient && shouldFallbackOnError(error)) {
1373
+ process.stderr.write(
1374
+ `[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
1375
+ `
1376
+ );
1377
+ return fallbackClient.batch(stmts, mode);
1378
+ }
1379
+ throw error;
1380
+ }
1381
+ },
1382
+ async migrate(stmts) {
1383
+ if (fallbackClient) {
1384
+ return fallbackClient.migrate(stmts);
1385
+ }
1386
+ return adapter.batch(stmts, "deferred");
1387
+ },
1388
+ async transaction(mode) {
1389
+ if (!fallbackClient) {
1390
+ throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
1391
+ }
1392
+ return fallbackClient.transaction(mode);
1393
+ },
1394
+ async executeMultiple(sql) {
1395
+ if (fallbackClient && shouldBypassPostgres(sql)) {
1396
+ return fallbackClient.executeMultiple(sql);
1397
+ }
1398
+ for (const statement of splitSqlStatements(sql)) {
1399
+ await adapter.execute(statement);
1400
+ }
1401
+ },
1402
+ async sync() {
1403
+ if (fallbackClient) {
1404
+ return fallbackClient.sync();
1405
+ }
1406
+ return { frame_no: 0, frames_synced: 0 };
1407
+ },
1408
+ close() {
1409
+ closed = true;
1410
+ prismaClientPromise = null;
1411
+ compatibilityBootstrapPromise = null;
1412
+ void prisma.$disconnect?.();
1413
+ },
1414
+ get closed() {
1415
+ return closed;
1416
+ },
1417
+ get protocol() {
1418
+ return "prisma-postgres";
1419
+ }
1420
+ };
1421
+ return adapter;
1422
+ }
1423
+ var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
1424
+ var init_database_adapter = __esm({
1425
+ "src/lib/database-adapter.ts"() {
1426
+ "use strict";
1427
+ VIEW_MAPPINGS = [
1428
+ { view: "memories", source: "memory.memory_records" },
1429
+ { view: "tasks", source: "memory.tasks" },
1430
+ { view: "behaviors", source: "memory.behaviors" },
1431
+ { view: "entities", source: "memory.entities" },
1432
+ { view: "relationships", source: "memory.relationships" },
1433
+ { view: "entity_memories", source: "memory.entity_memories" },
1434
+ { view: "entity_aliases", source: "memory.entity_aliases" },
1435
+ { view: "notifications", source: "memory.notifications" },
1436
+ { view: "messages", source: "memory.messages" },
1437
+ { view: "users", source: "wiki.users" },
1438
+ { view: "workspaces", source: "wiki.workspaces" },
1439
+ { view: "workspace_users", source: "wiki.workspace_users" },
1440
+ { view: "documents", source: "wiki.workspace_documents" },
1441
+ { view: "chats", source: "wiki.workspace_chats" }
1442
+ ];
1443
+ UPSERT_KEYS = {
1444
+ memories: ["id"],
1445
+ tasks: ["id"],
1446
+ behaviors: ["id"],
1447
+ entities: ["id"],
1448
+ relationships: ["id"],
1449
+ entity_aliases: ["alias"],
1450
+ notifications: ["id"],
1451
+ messages: ["id"],
1452
+ users: ["id"],
1453
+ workspaces: ["id"],
1454
+ workspace_users: ["id"],
1455
+ documents: ["id"],
1456
+ chats: ["id"]
1457
+ };
1458
+ BOOLEAN_COLUMNS_BY_TABLE = {
1459
+ memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
1460
+ behaviors: /* @__PURE__ */ new Set(["active"]),
1461
+ notifications: /* @__PURE__ */ new Set(["read"]),
1462
+ users: /* @__PURE__ */ new Set(["has_personal_memory"])
1463
+ };
1464
+ BOOLEAN_COLUMN_NAMES = new Set(
1465
+ Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
1466
+ );
1467
+ IMMEDIATE_FALLBACK_PATTERNS = [
1468
+ /\bPRAGMA\b/i,
1469
+ /\bsqlite_master\b/i,
1470
+ /(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
1471
+ /\bMATCH\b/i,
1472
+ /\bvector_distance_cos\s*\(/i,
1473
+ /\bjson_extract\s*\(/i,
1474
+ /\bjulianday\s*\(/i,
1475
+ /\bstrftime\s*\(/i,
1476
+ /\blast_insert_rowid\s*\(/i
1477
+ ];
1478
+ prismaClientPromise = null;
1479
+ compatibilityBootstrapPromise = null;
1480
+ }
1481
+ });
1482
+
1483
+ // src/lib/daemon-auth.ts
1484
+ import crypto from "crypto";
1485
+ import path8 from "path";
1486
+ import { existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
1487
+ function normalizeToken(token) {
1488
+ if (!token) return null;
1489
+ const trimmed = token.trim();
1490
+ return trimmed.length > 0 ? trimmed : null;
1491
+ }
1492
+ function readDaemonToken() {
1493
+ try {
1494
+ if (!existsSync6(DAEMON_TOKEN_PATH)) return null;
1495
+ return normalizeToken(readFileSync6(DAEMON_TOKEN_PATH, "utf8"));
1496
+ } catch {
1497
+ return null;
1498
+ }
1499
+ }
1500
+ function ensureDaemonToken(seed) {
1501
+ const existing = readDaemonToken();
1502
+ if (existing) return existing;
1503
+ const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
1504
+ ensurePrivateDirSync(EXE_AI_DIR);
1505
+ writeFileSync5(DAEMON_TOKEN_PATH, `${token}
1506
+ `, "utf8");
1507
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
1508
+ return token;
1509
+ }
1510
+ var DAEMON_TOKEN_PATH;
1511
+ var init_daemon_auth = __esm({
1512
+ "src/lib/daemon-auth.ts"() {
1513
+ "use strict";
1514
+ init_config();
1515
+ init_secure_files();
1516
+ DAEMON_TOKEN_PATH = path8.join(EXE_AI_DIR, "exed.token");
1517
+ }
1518
+ });
1519
+
740
1520
  // src/lib/exe-daemon-client.ts
741
1521
  import net from "net";
742
- import os5 from "os";
1522
+ import os6 from "os";
743
1523
  import { spawn } from "child_process";
744
1524
  import { randomUUID } from "crypto";
745
- import { existsSync as existsSync5, unlinkSync as unlinkSync3, readFileSync as readFileSync6, openSync, closeSync, statSync } from "fs";
746
- import path7 from "path";
1525
+ import { existsSync as existsSync7, unlinkSync as unlinkSync3, readFileSync as readFileSync7, openSync, closeSync, statSync } from "fs";
1526
+ import path9 from "path";
747
1527
  import { fileURLToPath } from "url";
748
1528
  function handleData(chunk) {
749
1529
  _buffer += chunk.toString();
@@ -771,9 +1551,9 @@ function handleData(chunk) {
771
1551
  }
772
1552
  }
773
1553
  function cleanupStaleFiles() {
774
- if (existsSync5(PID_PATH)) {
1554
+ if (existsSync7(PID_PATH)) {
775
1555
  try {
776
- const pid = parseInt(readFileSync6(PID_PATH, "utf8").trim(), 10);
1556
+ const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
777
1557
  if (pid > 0) {
778
1558
  try {
779
1559
  process.kill(pid, 0);
@@ -794,17 +1574,17 @@ function cleanupStaleFiles() {
794
1574
  }
795
1575
  }
796
1576
  function findPackageRoot() {
797
- let dir = path7.dirname(fileURLToPath(import.meta.url));
798
- const { root } = path7.parse(dir);
1577
+ let dir = path9.dirname(fileURLToPath(import.meta.url));
1578
+ const { root } = path9.parse(dir);
799
1579
  while (dir !== root) {
800
- if (existsSync5(path7.join(dir, "package.json"))) return dir;
801
- dir = path7.dirname(dir);
1580
+ if (existsSync7(path9.join(dir, "package.json"))) return dir;
1581
+ dir = path9.dirname(dir);
802
1582
  }
803
1583
  return null;
804
1584
  }
805
1585
  function spawnDaemon() {
806
- const freeGB = os5.freemem() / (1024 * 1024 * 1024);
807
- const totalGB = os5.totalmem() / (1024 * 1024 * 1024);
1586
+ const freeGB = os6.freemem() / (1024 * 1024 * 1024);
1587
+ const totalGB = os6.totalmem() / (1024 * 1024 * 1024);
808
1588
  if (totalGB <= 8) {
809
1589
  process.stderr.write(
810
1590
  `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
@@ -824,16 +1604,17 @@ function spawnDaemon() {
824
1604
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
825
1605
  return;
826
1606
  }
827
- const daemonPath = path7.join(pkgRoot, "dist", "lib", "exe-daemon.js");
828
- if (!existsSync5(daemonPath)) {
1607
+ const daemonPath = path9.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1608
+ if (!existsSync7(daemonPath)) {
829
1609
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
830
1610
  `);
831
1611
  return;
832
1612
  }
833
1613
  const resolvedPath = daemonPath;
1614
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
834
1615
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
835
1616
  `);
836
- const logPath = path7.join(path7.dirname(SOCKET_PATH), "exed.log");
1617
+ const logPath = path9.join(path9.dirname(SOCKET_PATH), "exed.log");
837
1618
  let stderrFd = "ignore";
838
1619
  try {
839
1620
  stderrFd = openSync(logPath, "a");
@@ -851,7 +1632,8 @@ function spawnDaemon() {
851
1632
  TMUX_PANE: void 0,
852
1633
  // Prevents resolveExeSession() from scoping to one session
853
1634
  EXE_DAEMON_SOCK: SOCKET_PATH,
854
- EXE_DAEMON_PID: PID_PATH
1635
+ EXE_DAEMON_PID: PID_PATH,
1636
+ [DAEMON_TOKEN_ENV]: daemonToken
855
1637
  }
856
1638
  });
857
1639
  child.unref();
@@ -958,13 +1740,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
958
1740
  return;
959
1741
  }
960
1742
  const id = randomUUID();
1743
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
961
1744
  const timer = setTimeout(() => {
962
1745
  _pending.delete(id);
963
1746
  resolve({ error: "Request timeout" });
964
1747
  }, timeoutMs);
965
1748
  _pending.set(id, { resolve, timer });
966
1749
  try {
967
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
1750
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
968
1751
  } catch {
969
1752
  clearTimeout(timer);
970
1753
  _pending.delete(id);
@@ -975,17 +1758,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
975
1758
  function isClientConnected() {
976
1759
  return _connected;
977
1760
  }
978
- var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
1761
+ var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, DAEMON_TOKEN_ENV, _socket, _connected, _buffer, _pending, MAX_BUFFER;
979
1762
  var init_exe_daemon_client = __esm({
980
1763
  "src/lib/exe-daemon-client.ts"() {
981
1764
  "use strict";
982
1765
  init_config();
983
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path7.join(EXE_AI_DIR, "exed.sock");
984
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path7.join(EXE_AI_DIR, "exed.pid");
985
- SPAWN_LOCK_PATH = path7.join(EXE_AI_DIR, "exed-spawn.lock");
1766
+ init_daemon_auth();
1767
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path9.join(EXE_AI_DIR, "exed.sock");
1768
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path9.join(EXE_AI_DIR, "exed.pid");
1769
+ SPAWN_LOCK_PATH = path9.join(EXE_AI_DIR, "exed-spawn.lock");
986
1770
  SPAWN_LOCK_STALE_MS = 3e4;
987
1771
  CONNECT_TIMEOUT_MS = 15e3;
988
1772
  REQUEST_TIMEOUT_MS = 3e4;
1773
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
989
1774
  _socket = null;
990
1775
  _connected = false;
991
1776
  _buffer = "";
@@ -1064,7 +1849,7 @@ __export(db_daemon_client_exports, {
1064
1849
  createDaemonDbClient: () => createDaemonDbClient,
1065
1850
  initDaemonDbClient: () => initDaemonDbClient
1066
1851
  });
1067
- function normalizeStatement(stmt) {
1852
+ function normalizeStatement2(stmt) {
1068
1853
  if (typeof stmt === "string") {
1069
1854
  return { sql: stmt, args: [] };
1070
1855
  }
@@ -1088,7 +1873,7 @@ function createDaemonDbClient(fallbackClient) {
1088
1873
  if (!_useDaemon || !isClientConnected()) {
1089
1874
  return fallbackClient.execute(stmt);
1090
1875
  }
1091
- const { sql, args } = normalizeStatement(stmt);
1876
+ const { sql, args } = normalizeStatement2(stmt);
1092
1877
  const response = await sendDaemonRequest({
1093
1878
  type: "db-execute",
1094
1879
  sql,
@@ -1113,7 +1898,7 @@ function createDaemonDbClient(fallbackClient) {
1113
1898
  if (!_useDaemon || !isClientConnected()) {
1114
1899
  return fallbackClient.batch(stmts, mode);
1115
1900
  }
1116
- const statements = stmts.map(normalizeStatement);
1901
+ const statements = stmts.map(normalizeStatement2);
1117
1902
  const response = await sendDaemonRequest({
1118
1903
  type: "db-batch",
1119
1904
  statements,
@@ -1208,6 +1993,18 @@ __export(database_exports, {
1208
1993
  });
1209
1994
  import { createClient } from "@libsql/client";
1210
1995
  async function initDatabase(config) {
1996
+ if (_walCheckpointTimer) {
1997
+ clearInterval(_walCheckpointTimer);
1998
+ _walCheckpointTimer = null;
1999
+ }
2000
+ if (_daemonClient) {
2001
+ _daemonClient.close();
2002
+ _daemonClient = null;
2003
+ }
2004
+ if (_adapterClient && _adapterClient !== _resilientClient) {
2005
+ _adapterClient.close();
2006
+ }
2007
+ _adapterClient = null;
1211
2008
  if (_client) {
1212
2009
  _client.close();
1213
2010
  _client = null;
@@ -1221,6 +2018,7 @@ async function initDatabase(config) {
1221
2018
  }
1222
2019
  _client = createClient(opts);
1223
2020
  _resilientClient = wrapWithRetry(_client);
2021
+ _adapterClient = _resilientClient;
1224
2022
  _client.execute("PRAGMA busy_timeout = 30000").catch(() => {
1225
2023
  });
1226
2024
  _client.execute("PRAGMA journal_mode = WAL").catch(() => {
@@ -1231,14 +2029,20 @@ async function initDatabase(config) {
1231
2029
  });
1232
2030
  }, 3e4);
1233
2031
  _walCheckpointTimer.unref();
2032
+ if (process.env.DATABASE_URL) {
2033
+ _adapterClient = await createPrismaDbAdapter(_resilientClient);
2034
+ }
1234
2035
  }
1235
2036
  function isInitialized() {
1236
- return _client !== null;
2037
+ return _adapterClient !== null || _client !== null;
1237
2038
  }
1238
2039
  function getClient() {
1239
- if (!_resilientClient) {
2040
+ if (!_adapterClient) {
1240
2041
  throw new Error("Database client not initialized. Call initDatabase() first.");
1241
2042
  }
2043
+ if (process.env.DATABASE_URL) {
2044
+ return _adapterClient;
2045
+ }
1242
2046
  if (process.env.EXE_IS_DAEMON === "1") {
1243
2047
  return _resilientClient;
1244
2048
  }
@@ -1248,6 +2052,7 @@ function getClient() {
1248
2052
  return _resilientClient;
1249
2053
  }
1250
2054
  async function initDaemonClient() {
2055
+ if (process.env.DATABASE_URL) return;
1251
2056
  if (process.env.EXE_IS_DAEMON === "1") return;
1252
2057
  if (!_resilientClient) return;
1253
2058
  try {
@@ -1544,6 +2349,7 @@ async function ensureSchema() {
1544
2349
  project TEXT NOT NULL,
1545
2350
  summary TEXT NOT NULL,
1546
2351
  task_file TEXT,
2352
+ session_scope TEXT,
1547
2353
  read INTEGER NOT NULL DEFAULT 0,
1548
2354
  created_at TEXT NOT NULL
1549
2355
  );
@@ -1552,7 +2358,7 @@ async function ensureSchema() {
1552
2358
  ON notifications(read);
1553
2359
 
1554
2360
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
1555
- ON notifications(agent_id);
2361
+ ON notifications(agent_id, session_scope);
1556
2362
 
1557
2363
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
1558
2364
  ON notifications(task_file);
@@ -1590,6 +2396,7 @@ async function ensureSchema() {
1590
2396
  target_agent TEXT NOT NULL,
1591
2397
  target_project TEXT,
1592
2398
  target_device TEXT NOT NULL DEFAULT 'local',
2399
+ session_scope TEXT,
1593
2400
  content TEXT NOT NULL,
1594
2401
  priority TEXT DEFAULT 'normal',
1595
2402
  status TEXT DEFAULT 'pending',
@@ -1603,10 +2410,31 @@ async function ensureSchema() {
1603
2410
  );
1604
2411
 
1605
2412
  CREATE INDEX IF NOT EXISTS idx_messages_target
1606
- ON messages(target_agent, status);
2413
+ ON messages(target_agent, session_scope, status);
1607
2414
 
1608
2415
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
1609
- ON messages(target_agent, from_agent, server_seq);
2416
+ ON messages(target_agent, session_scope, from_agent, server_seq);
2417
+ `);
2418
+ try {
2419
+ await client.execute({
2420
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
2421
+ args: []
2422
+ });
2423
+ } catch {
2424
+ }
2425
+ try {
2426
+ await client.execute({
2427
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
2428
+ args: []
2429
+ });
2430
+ } catch {
2431
+ }
2432
+ await client.executeMultiple(`
2433
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
2434
+ ON notifications(agent_id, session_scope, read, created_at);
2435
+
2436
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
2437
+ ON messages(target_agent, session_scope, status, created_at);
1610
2438
  `);
1611
2439
  try {
1612
2440
  await client.execute({
@@ -2190,52 +3018,72 @@ async function ensureSchema() {
2190
3018
  } catch {
2191
3019
  }
2192
3020
  }
3021
+ try {
3022
+ await client.execute({
3023
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
3024
+ args: []
3025
+ });
3026
+ } catch {
3027
+ }
2193
3028
  }
2194
3029
  async function disposeDatabase() {
3030
+ if (_walCheckpointTimer) {
3031
+ clearInterval(_walCheckpointTimer);
3032
+ _walCheckpointTimer = null;
3033
+ }
2195
3034
  if (_daemonClient) {
2196
3035
  _daemonClient.close();
2197
3036
  _daemonClient = null;
2198
3037
  }
3038
+ if (_adapterClient && _adapterClient !== _resilientClient) {
3039
+ _adapterClient.close();
3040
+ }
3041
+ _adapterClient = null;
2199
3042
  if (_client) {
2200
3043
  _client.close();
2201
3044
  _client = null;
2202
3045
  _resilientClient = null;
2203
3046
  }
2204
3047
  }
2205
- var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
3048
+ var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
2206
3049
  var init_database = __esm({
2207
3050
  "src/lib/database.ts"() {
2208
3051
  "use strict";
2209
3052
  init_db_retry();
2210
3053
  init_employees();
3054
+ init_database_adapter();
2211
3055
  _client = null;
2212
3056
  _resilientClient = null;
2213
3057
  _walCheckpointTimer = null;
2214
3058
  _daemonClient = null;
3059
+ _adapterClient = null;
2215
3060
  initTurso = initDatabase;
2216
3061
  disposeTurso = disposeDatabase;
2217
3062
  }
2218
3063
  });
2219
3064
 
2220
3065
  // src/lib/license.ts
2221
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
3066
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync8, mkdirSync as mkdirSync4 } from "fs";
2222
3067
  import { randomUUID as randomUUID2 } from "crypto";
2223
- import path8 from "path";
3068
+ import { createRequire as createRequire2 } from "module";
3069
+ import { pathToFileURL as pathToFileURL2 } from "url";
3070
+ import os7 from "os";
3071
+ import path10 from "path";
2224
3072
  import { jwtVerify, importSPKI } from "jose";
2225
3073
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH;
2226
3074
  var init_license = __esm({
2227
3075
  "src/lib/license.ts"() {
2228
3076
  "use strict";
2229
3077
  init_config();
2230
- LICENSE_PATH = path8.join(EXE_AI_DIR, "license.key");
2231
- CACHE_PATH = path8.join(EXE_AI_DIR, "license-cache.json");
2232
- DEVICE_ID_PATH = path8.join(EXE_AI_DIR, "device-id");
3078
+ LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
3079
+ CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
3080
+ DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
2233
3081
  }
2234
3082
  });
2235
3083
 
2236
3084
  // src/lib/plan-limits.ts
2237
- import { readFileSync as readFileSync8, existsSync as existsSync7 } from "fs";
2238
- import path9 from "path";
3085
+ import { readFileSync as readFileSync9, existsSync as existsSync9 } from "fs";
3086
+ import path11 from "path";
2239
3087
  var CACHE_PATH2;
2240
3088
  var init_plan_limits = __esm({
2241
3089
  "src/lib/plan-limits.ts"() {
@@ -2244,14 +3092,14 @@ var init_plan_limits = __esm({
2244
3092
  init_employees();
2245
3093
  init_license();
2246
3094
  init_config();
2247
- CACHE_PATH2 = path9.join(EXE_AI_DIR, "license-cache.json");
3095
+ CACHE_PATH2 = path11.join(EXE_AI_DIR, "license-cache.json");
2248
3096
  }
2249
3097
  });
2250
3098
 
2251
3099
  // src/lib/tmux-routing.ts
2252
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, existsSync as existsSync8, appendFileSync, readdirSync as readdirSync2 } from "fs";
2253
- import path10 from "path";
2254
- import os6 from "os";
3100
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, mkdirSync as mkdirSync5, existsSync as existsSync10, appendFileSync, readdirSync as readdirSync2 } from "fs";
3101
+ import path12 from "path";
3102
+ import os8 from "os";
2255
3103
  import { fileURLToPath as fileURLToPath2 } from "url";
2256
3104
  function getMySession() {
2257
3105
  return getTransport().getMySession();
@@ -2264,7 +3112,7 @@ function extractRootExe(name) {
2264
3112
  }
2265
3113
  function getParentExe(sessionKey) {
2266
3114
  try {
2267
- const data = JSON.parse(readFileSync9(path10.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
3115
+ const data = JSON.parse(readFileSync10(path12.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
2268
3116
  return data.parentExe || null;
2269
3117
  } catch {
2270
3118
  return null;
@@ -2307,10 +3155,10 @@ var init_tmux_routing = __esm({
2307
3155
  init_intercom_queue();
2308
3156
  init_plan_limits();
2309
3157
  init_employees();
2310
- SPAWN_LOCK_DIR = path10.join(os6.homedir(), ".exe-os", "spawn-locks");
2311
- SESSION_CACHE = path10.join(os6.homedir(), ".exe-os", "session-cache");
2312
- INTERCOM_LOG2 = path10.join(os6.homedir(), ".exe-os", "intercom.log");
2313
- DEBOUNCE_FILE = path10.join(SESSION_CACHE, "intercom-debounce.json");
3158
+ SPAWN_LOCK_DIR = path12.join(os8.homedir(), ".exe-os", "spawn-locks");
3159
+ SESSION_CACHE = path12.join(os8.homedir(), ".exe-os", "session-cache");
3160
+ INTERCOM_LOG2 = path12.join(os8.homedir(), ".exe-os", "intercom.log");
3161
+ DEBOUNCE_FILE = path12.join(SESSION_CACHE, "intercom-debounce.json");
2314
3162
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
2315
3163
  }
2316
3164
  });
@@ -2352,9 +3200,9 @@ __export(cto_delegation_gate_exports, {
2352
3200
  hasValidScratchpadEscape: () => hasValidScratchpadEscape,
2353
3201
  scratchpadPath: () => scratchpadPath
2354
3202
  });
2355
- import os7 from "os";
2356
- import path11 from "path";
2357
- import { existsSync as existsSync9, readFileSync as readFileSync10, statSync as statSync2 } from "fs";
3203
+ import os9 from "os";
3204
+ import path13 from "path";
3205
+ import { existsSync as existsSync11, readFileSync as readFileSync11, statSync as statSync2 } from "fs";
2358
3206
  function resolveGatedAgent() {
2359
3207
  try {
2360
3208
  const employees = loadEmployeesSync();
@@ -2368,12 +3216,12 @@ function getGatedAgent() {
2368
3216
  return resolveGatedAgent() || GATED_AGENT;
2369
3217
  }
2370
3218
  function toWorkspaceRelative(filePath, cwd = process.cwd()) {
2371
- const normalized = path11.normalize(filePath);
2372
- if (normalized.startsWith(cwd + path11.sep)) {
3219
+ const normalized = path13.normalize(filePath);
3220
+ if (normalized.startsWith(cwd + path13.sep)) {
2373
3221
  return normalized.slice(cwd.length + 1);
2374
3222
  }
2375
- if (path11.isAbsolute(normalized)) {
2376
- return path11.basename(normalized);
3223
+ if (path13.isAbsolute(normalized)) {
3224
+ return path13.basename(normalized);
2377
3225
  }
2378
3226
  return normalized;
2379
3227
  }
@@ -2382,8 +3230,8 @@ function isDockerfile(basename) {
2382
3230
  }
2383
3231
  function classifyPath(filePath, cwd) {
2384
3232
  const rel = toWorkspaceRelative(filePath, cwd);
2385
- const basename = path11.basename(rel);
2386
- const ext = path11.extname(rel).toLowerCase();
3233
+ const basename = path13.basename(rel);
3234
+ const ext = path13.extname(rel).toLowerCase();
2387
3235
  if (ext === ".md") {
2388
3236
  for (const prefix of EXEMPT_MD_DIR_PREFIXES) {
2389
3237
  if (rel.startsWith(prefix)) return "exempt";
@@ -2425,22 +3273,22 @@ async function hasRecentEngineerDispatch(now = Date.now()) {
2425
3273
  return false;
2426
3274
  }
2427
3275
  }
2428
- function scratchpadPath(sessionId, homeDir = os7.homedir()) {
2429
- return path11.join(
3276
+ function scratchpadPath(sessionId, homeDir = os9.homedir()) {
3277
+ return path13.join(
2430
3278
  homeDir,
2431
3279
  ".exe-os",
2432
3280
  "session-cache",
2433
3281
  `cto-scratchpad-${sessionId}.txt`
2434
3282
  );
2435
3283
  }
2436
- function hasValidScratchpadEscape(sessionId, now = Date.now(), homeDir = os7.homedir()) {
3284
+ function hasValidScratchpadEscape(sessionId, now = Date.now(), homeDir = os9.homedir()) {
2437
3285
  const paths = [scratchpadPath(sessionId, homeDir)];
2438
3286
  for (const filePath of paths) {
2439
- if (!existsSync9(filePath)) continue;
3287
+ if (!existsSync11(filePath)) continue;
2440
3288
  try {
2441
3289
  const stat = statSync2(filePath);
2442
3290
  if (now - stat.mtimeMs > SCRATCHPAD_MAX_AGE_MS) continue;
2443
- const body = readFileSync10(filePath, "utf-8");
3291
+ const body = readFileSync11(filePath, "utf-8");
2444
3292
  if (SCRATCHPAD_ESCAPE_PATTERN.test(body)) return true;
2445
3293
  } catch {
2446
3294
  }
@@ -2531,14 +3379,14 @@ var init_memory = __esm({
2531
3379
 
2532
3380
  // src/lib/keychain.ts
2533
3381
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
2534
- import { existsSync as existsSync10 } from "fs";
2535
- import path12 from "path";
2536
- import os8 from "os";
3382
+ import { existsSync as existsSync12 } from "fs";
3383
+ import path14 from "path";
3384
+ import os10 from "os";
2537
3385
  function getKeyDir() {
2538
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path12.join(os8.homedir(), ".exe-os");
3386
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path14.join(os10.homedir(), ".exe-os");
2539
3387
  }
2540
3388
  function getKeyPath() {
2541
- return path12.join(getKeyDir(), "master.key");
3389
+ return path14.join(getKeyDir(), "master.key");
2542
3390
  }
2543
3391
  async function tryKeytar() {
2544
3392
  try {
@@ -2559,9 +3407,9 @@ async function getMasterKey() {
2559
3407
  }
2560
3408
  }
2561
3409
  const keyPath = getKeyPath();
2562
- if (!existsSync10(keyPath)) {
3410
+ if (!existsSync12(keyPath)) {
2563
3411
  process.stderr.write(
2564
- `[keychain] Key not found at ${keyPath} (HOME=${os8.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
3412
+ `[keychain] Key not found at ${keyPath} (HOME=${os10.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
2565
3413
  `
2566
3414
  );
2567
3415
  return null;
@@ -2646,6 +3494,7 @@ var shard_manager_exports = {};
2646
3494
  __export(shard_manager_exports, {
2647
3495
  disposeShards: () => disposeShards,
2648
3496
  ensureShardSchema: () => ensureShardSchema,
3497
+ getOpenShardCount: () => getOpenShardCount,
2649
3498
  getReadyShardClient: () => getReadyShardClient,
2650
3499
  getShardClient: () => getShardClient,
2651
3500
  getShardsDir: () => getShardsDir,
@@ -2654,15 +3503,18 @@ __export(shard_manager_exports, {
2654
3503
  listShards: () => listShards,
2655
3504
  shardExists: () => shardExists
2656
3505
  });
2657
- import path13 from "path";
2658
- import { existsSync as existsSync11, mkdirSync as mkdirSync6, readdirSync as readdirSync3 } from "fs";
3506
+ import path15 from "path";
3507
+ import { existsSync as existsSync13, mkdirSync as mkdirSync6, readdirSync as readdirSync3 } from "fs";
2659
3508
  import { createClient as createClient2 } from "@libsql/client";
2660
3509
  function initShardManager(encryptionKey) {
2661
3510
  _encryptionKey = encryptionKey;
2662
- if (!existsSync11(SHARDS_DIR)) {
3511
+ if (!existsSync13(SHARDS_DIR)) {
2663
3512
  mkdirSync6(SHARDS_DIR, { recursive: true });
2664
3513
  }
2665
3514
  _shardingEnabled = true;
3515
+ if (_evictionTimer) clearInterval(_evictionTimer);
3516
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
3517
+ _evictionTimer.unref();
2666
3518
  }
2667
3519
  function isShardingEnabled() {
2668
3520
  return _shardingEnabled;
@@ -2679,21 +3531,28 @@ function getShardClient(projectName) {
2679
3531
  throw new Error(`Invalid project name for shard: "${projectName}"`);
2680
3532
  }
2681
3533
  const cached = _shards.get(safeName);
2682
- if (cached) return cached;
2683
- const dbPath = path13.join(SHARDS_DIR, `${safeName}.db`);
3534
+ if (cached) {
3535
+ _shardLastAccess.set(safeName, Date.now());
3536
+ return cached;
3537
+ }
3538
+ while (_shards.size >= MAX_OPEN_SHARDS) {
3539
+ evictLRU();
3540
+ }
3541
+ const dbPath = path15.join(SHARDS_DIR, `${safeName}.db`);
2684
3542
  const client = createClient2({
2685
3543
  url: `file:${dbPath}`,
2686
3544
  encryptionKey: _encryptionKey
2687
3545
  });
2688
3546
  _shards.set(safeName, client);
3547
+ _shardLastAccess.set(safeName, Date.now());
2689
3548
  return client;
2690
3549
  }
2691
3550
  function shardExists(projectName) {
2692
3551
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
2693
- return existsSync11(path13.join(SHARDS_DIR, `${safeName}.db`));
3552
+ return existsSync13(path15.join(SHARDS_DIR, `${safeName}.db`));
2694
3553
  }
2695
3554
  function listShards() {
2696
- if (!existsSync11(SHARDS_DIR)) return [];
3555
+ if (!existsSync13(SHARDS_DIR)) return [];
2697
3556
  return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
2698
3557
  }
2699
3558
  async function ensureShardSchema(client) {
@@ -2745,6 +3604,8 @@ async function ensureShardSchema(client) {
2745
3604
  for (const col of [
2746
3605
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
2747
3606
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
3607
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
3608
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
2748
3609
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
2749
3610
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
2750
3611
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -2767,7 +3628,23 @@ async function ensureShardSchema(client) {
2767
3628
  // MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
2768
3629
  "ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
2769
3630
  "ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
2770
- "ALTER TABLE memories ADD COLUMN trajectory TEXT"
3631
+ "ALTER TABLE memories ADD COLUMN trajectory TEXT",
3632
+ // Metadata enrichment columns (must match database.ts)
3633
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
3634
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
3635
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
3636
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
3637
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
3638
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
3639
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
3640
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
3641
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
3642
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
3643
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
3644
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
3645
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
3646
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
3647
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
2771
3648
  ]) {
2772
3649
  try {
2773
3650
  await client.execute(col);
@@ -2866,21 +3743,69 @@ async function getReadyShardClient(projectName) {
2866
3743
  await ensureShardSchema(client);
2867
3744
  return client;
2868
3745
  }
3746
+ function evictLRU() {
3747
+ let oldest = null;
3748
+ let oldestTime = Infinity;
3749
+ for (const [name, time] of _shardLastAccess) {
3750
+ if (time < oldestTime) {
3751
+ oldestTime = time;
3752
+ oldest = name;
3753
+ }
3754
+ }
3755
+ if (oldest) {
3756
+ const client = _shards.get(oldest);
3757
+ if (client) {
3758
+ client.close();
3759
+ }
3760
+ _shards.delete(oldest);
3761
+ _shardLastAccess.delete(oldest);
3762
+ }
3763
+ }
3764
+ function evictIdleShards() {
3765
+ const now = Date.now();
3766
+ const toEvict = [];
3767
+ for (const [name, lastAccess] of _shardLastAccess) {
3768
+ if (now - lastAccess > SHARD_IDLE_MS) {
3769
+ toEvict.push(name);
3770
+ }
3771
+ }
3772
+ for (const name of toEvict) {
3773
+ const client = _shards.get(name);
3774
+ if (client) {
3775
+ client.close();
3776
+ }
3777
+ _shards.delete(name);
3778
+ _shardLastAccess.delete(name);
3779
+ }
3780
+ }
3781
+ function getOpenShardCount() {
3782
+ return _shards.size;
3783
+ }
2869
3784
  function disposeShards() {
3785
+ if (_evictionTimer) {
3786
+ clearInterval(_evictionTimer);
3787
+ _evictionTimer = null;
3788
+ }
2870
3789
  for (const [, client] of _shards) {
2871
3790
  client.close();
2872
3791
  }
2873
3792
  _shards.clear();
3793
+ _shardLastAccess.clear();
2874
3794
  _shardingEnabled = false;
2875
3795
  _encryptionKey = null;
2876
3796
  }
2877
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
3797
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
2878
3798
  var init_shard_manager = __esm({
2879
3799
  "src/lib/shard-manager.ts"() {
2880
3800
  "use strict";
2881
3801
  init_config();
2882
- SHARDS_DIR = path13.join(EXE_AI_DIR, "shards");
3802
+ SHARDS_DIR = path15.join(EXE_AI_DIR, "shards");
3803
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
3804
+ MAX_OPEN_SHARDS = 10;
3805
+ EVICTION_INTERVAL_MS = 60 * 1e3;
2883
3806
  _shards = /* @__PURE__ */ new Map();
3807
+ _shardLastAccess = /* @__PURE__ */ new Map();
3808
+ _evictionTimer = null;
2884
3809
  _encryptionKey = null;
2885
3810
  _shardingEnabled = false;
2886
3811
  }
@@ -3653,7 +4578,7 @@ __export(review_gate_exports, {
3653
4578
  runReviewGate: () => runReviewGate
3654
4579
  });
3655
4580
  import { execSync as execSync5 } from "child_process";
3656
- import { existsSync as existsSync12 } from "fs";
4581
+ import { existsSync as existsSync14 } from "fs";
3657
4582
  function checkCommitsExist(taskCreatedAt) {
3658
4583
  try {
3659
4584
  const since = new Date(taskCreatedAt).toISOString();
@@ -3716,7 +4641,7 @@ function checkTestCoverage(taskCreatedAt) {
3716
4641
  const testPath = file.replace(/^src\//, "tests/").replace(/\.ts$/, ".test.ts");
3717
4642
  const baseName = file.split("/").pop()?.replace(/\.ts$/, "") ?? "";
3718
4643
  const testDir = testPath.substring(0, testPath.lastIndexOf("/"));
3719
- const hasDirectTest = existsSync12(testPath);
4644
+ const hasDirectTest = existsSync14(testPath);
3720
4645
  let hasRelatedTest = false;
3721
4646
  try {
3722
4647
  const related = execSync5(
@@ -3770,18 +4695,18 @@ var init_review_gate = __esm({
3770
4695
  });
3771
4696
 
3772
4697
  // src/adapters/claude/hooks/pre-tool-use.ts
3773
- import { existsSync as existsSync13, writeFileSync as writeFileSync7, mkdirSync as mkdirSync7 } from "fs";
4698
+ import { existsSync as existsSync15, writeFileSync as writeFileSync8, mkdirSync as mkdirSync7 } from "fs";
3774
4699
  import { execSync as execSync6 } from "child_process";
3775
- import path14 from "path";
4700
+ import path16 from "path";
3776
4701
 
3777
4702
  // src/lib/active-agent.ts
3778
4703
  init_config();
3779
4704
  init_session_key();
3780
4705
  init_employees();
3781
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync, unlinkSync as unlinkSync2, readdirSync } from "fs";
4706
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, readdirSync } from "fs";
3782
4707
  import { execSync as execSync3 } from "child_process";
3783
- import path3 from "path";
3784
- var CACHE_DIR = path3.join(EXE_AI_DIR, "session-cache");
4708
+ import path4 from "path";
4709
+ var CACHE_DIR = path4.join(EXE_AI_DIR, "session-cache");
3785
4710
  var STALE_MS = 24 * 60 * 60 * 1e3;
3786
4711
  function isNameWithOptionalInstance(candidate, baseName) {
3787
4712
  if (candidate === baseName) return true;
@@ -3826,12 +4751,12 @@ function resolveActiveAgentFromTmuxSession(sessionName) {
3826
4751
  return null;
3827
4752
  }
3828
4753
  function getMarkerPath() {
3829
- return path3.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
4754
+ return path4.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
3830
4755
  }
3831
4756
  function getActiveAgent() {
3832
4757
  try {
3833
4758
  const markerPath = getMarkerPath();
3834
- const raw = readFileSync3(markerPath, "utf8");
4759
+ const raw = readFileSync4(markerPath, "utf8");
3835
4760
  const data = JSON.parse(raw);
3836
4761
  if (data.agentId) {
3837
4762
  if (data.startedAt) {
@@ -3886,17 +4811,17 @@ if (!process.env.AGENT_ID) {
3886
4811
  }
3887
4812
  var DELEGATION_TASK_THRESHOLD = 3;
3888
4813
  var CTO_ROLES = ["CTO", "executive"];
3889
- var CACHE_DIR2 = path14.join(EXE_AI_DIR, "session-cache");
4814
+ var CACHE_DIR2 = path16.join(EXE_AI_DIR, "session-cache");
3890
4815
  var timeout = setTimeout(() => {
3891
4816
  process.exit(0);
3892
4817
  }, 5e3);
3893
4818
  timeout.unref();
3894
4819
  function getDelegationFlagPath() {
3895
- return path14.join(CACHE_DIR2, `delegation-checkpoint-${getSessionKey()}.json`);
4820
+ return path16.join(CACHE_DIR2, `delegation-checkpoint-${getSessionKey()}.json`);
3896
4821
  }
3897
4822
  function hasDelegationFired() {
3898
4823
  try {
3899
- return existsSync13(getDelegationFlagPath());
4824
+ return existsSync15(getDelegationFlagPath());
3900
4825
  } catch {
3901
4826
  return false;
3902
4827
  }
@@ -3904,7 +4829,7 @@ function hasDelegationFired() {
3904
4829
  function markDelegationFired() {
3905
4830
  try {
3906
4831
  mkdirSync7(CACHE_DIR2, { recursive: true });
3907
- writeFileSync7(getDelegationFlagPath(), JSON.stringify({ fired: true }));
4832
+ writeFileSync8(getDelegationFlagPath(), JSON.stringify({ fired: true }));
3908
4833
  } catch {
3909
4834
  }
3910
4835
  }