@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
package/dist/index.js CHANGED
@@ -25,6 +25,44 @@ var __copyProps = (to, from, except, desc) => {
25
25
  };
26
26
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
27
27
 
28
+ // src/lib/secure-files.ts
29
+ import { chmodSync, existsSync, mkdirSync } from "fs";
30
+ import { chmod, mkdir } from "fs/promises";
31
+ async function ensurePrivateDir(dirPath) {
32
+ await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
33
+ try {
34
+ await chmod(dirPath, PRIVATE_DIR_MODE);
35
+ } catch {
36
+ }
37
+ }
38
+ function ensurePrivateDirSync(dirPath) {
39
+ mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
40
+ try {
41
+ chmodSync(dirPath, PRIVATE_DIR_MODE);
42
+ } catch {
43
+ }
44
+ }
45
+ async function enforcePrivateFile(filePath) {
46
+ try {
47
+ await chmod(filePath, PRIVATE_FILE_MODE);
48
+ } catch {
49
+ }
50
+ }
51
+ function enforcePrivateFileSync(filePath) {
52
+ try {
53
+ if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
54
+ } catch {
55
+ }
56
+ }
57
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
58
+ var init_secure_files = __esm({
59
+ "src/lib/secure-files.ts"() {
60
+ "use strict";
61
+ PRIVATE_DIR_MODE = 448;
62
+ PRIVATE_FILE_MODE = 384;
63
+ }
64
+ });
65
+
28
66
  // src/lib/config.ts
29
67
  var config_exports = {};
30
68
  __export(config_exports, {
@@ -41,8 +79,8 @@ __export(config_exports, {
41
79
  migrateConfig: () => migrateConfig,
42
80
  saveConfig: () => saveConfig
43
81
  });
44
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
45
- import { readFileSync, existsSync, renameSync } from "fs";
82
+ import { readFile, writeFile } from "fs/promises";
83
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
46
84
  import path2 from "path";
47
85
  import os2 from "os";
48
86
  function resolveDataDir() {
@@ -50,7 +88,7 @@ function resolveDataDir() {
50
88
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
51
89
  const newDir = path2.join(os2.homedir(), ".exe-os");
52
90
  const legacyDir = path2.join(os2.homedir(), ".exe-mem");
53
- if (!existsSync(newDir) && existsSync(legacyDir)) {
91
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
54
92
  try {
55
93
  renameSync(legacyDir, newDir);
56
94
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -113,9 +151,9 @@ function normalizeAutoUpdate(raw) {
113
151
  }
114
152
  async function loadConfig() {
115
153
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
116
- await mkdir(dir, { recursive: true });
154
+ await ensurePrivateDir(dir);
117
155
  const configPath = path2.join(dir, "config.json");
118
- if (!existsSync(configPath)) {
156
+ if (!existsSync2(configPath)) {
119
157
  return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
120
158
  }
121
159
  const raw = await readFile(configPath, "utf-8");
@@ -128,6 +166,7 @@ async function loadConfig() {
128
166
  `);
129
167
  try {
130
168
  await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
169
+ await enforcePrivateFile(configPath);
131
170
  } catch {
132
171
  }
133
172
  }
@@ -146,7 +185,7 @@ async function loadConfig() {
146
185
  function loadConfigSync() {
147
186
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
148
187
  const configPath = path2.join(dir, "config.json");
149
- if (!existsSync(configPath)) {
188
+ if (!existsSync2(configPath)) {
150
189
  return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
151
190
  }
152
191
  try {
@@ -164,12 +203,10 @@ function loadConfigSync() {
164
203
  }
165
204
  async function saveConfig(config2) {
166
205
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
167
- await mkdir(dir, { recursive: true });
206
+ await ensurePrivateDir(dir);
168
207
  const configPath = path2.join(dir, "config.json");
169
208
  await writeFile(configPath, JSON.stringify(config2, null, 2) + "\n");
170
- if (config2.cloud?.apiKey) {
171
- await chmod(configPath, 384);
172
- }
209
+ await enforcePrivateFile(configPath);
173
210
  }
174
211
  async function loadConfigFrom(configPath) {
175
212
  const raw = await readFile(configPath, "utf-8");
@@ -189,6 +226,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
189
226
  var init_config = __esm({
190
227
  "src/lib/config.ts"() {
191
228
  "use strict";
229
+ init_secure_files();
192
230
  EXE_AI_DIR = resolveDataDir();
193
231
  DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
194
232
  MODELS_DIR = path2.join(EXE_AI_DIR, "models");
@@ -265,6 +303,120 @@ var init_config = __esm({
265
303
  }
266
304
  });
267
305
 
306
+ // src/lib/runtime-table.ts
307
+ var RUNTIME_TABLE, DEFAULT_RUNTIME;
308
+ var init_runtime_table = __esm({
309
+ "src/lib/runtime-table.ts"() {
310
+ "use strict";
311
+ RUNTIME_TABLE = {
312
+ codex: {
313
+ binary: "codex",
314
+ launchMode: "interactive",
315
+ autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
316
+ inlineFlag: "--no-alt-screen",
317
+ apiKeyEnv: "OPENAI_API_KEY",
318
+ defaultModel: "gpt-5.4"
319
+ },
320
+ opencode: {
321
+ binary: "opencode",
322
+ launchMode: "exec",
323
+ autoApproveFlag: "--dangerously-skip-permissions",
324
+ inlineFlag: "",
325
+ apiKeyEnv: "ANTHROPIC_API_KEY",
326
+ defaultModel: "anthropic/claude-sonnet-4-6"
327
+ }
328
+ };
329
+ DEFAULT_RUNTIME = "claude";
330
+ }
331
+ });
332
+
333
+ // src/lib/agent-config.ts
334
+ var agent_config_exports = {};
335
+ __export(agent_config_exports, {
336
+ AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
337
+ DEFAULT_MODELS: () => DEFAULT_MODELS,
338
+ KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
339
+ RUNTIME_LABELS: () => RUNTIME_LABELS,
340
+ clearAgentRuntime: () => clearAgentRuntime,
341
+ getAgentRuntime: () => getAgentRuntime,
342
+ loadAgentConfig: () => loadAgentConfig,
343
+ saveAgentConfig: () => saveAgentConfig,
344
+ setAgentRuntime: () => setAgentRuntime
345
+ });
346
+ import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
347
+ import path3 from "path";
348
+ function loadAgentConfig() {
349
+ if (!existsSync3(AGENT_CONFIG_PATH)) return {};
350
+ try {
351
+ return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
352
+ } catch {
353
+ return {};
354
+ }
355
+ }
356
+ function saveAgentConfig(config2) {
357
+ const dir = path3.dirname(AGENT_CONFIG_PATH);
358
+ ensurePrivateDirSync(dir);
359
+ writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config2, null, 2) + "\n", "utf-8");
360
+ enforcePrivateFileSync(AGENT_CONFIG_PATH);
361
+ }
362
+ function getAgentRuntime(agentId) {
363
+ const config2 = loadAgentConfig();
364
+ const entry = config2[agentId];
365
+ if (entry) return entry;
366
+ const orgDefault = config2["default"];
367
+ if (orgDefault) return orgDefault;
368
+ return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
369
+ }
370
+ function setAgentRuntime(agentId, runtime, model) {
371
+ const knownModels = KNOWN_RUNTIMES[runtime];
372
+ if (!knownModels) {
373
+ return {
374
+ ok: false,
375
+ error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
376
+ };
377
+ }
378
+ if (!knownModels.includes(model)) {
379
+ return {
380
+ ok: false,
381
+ error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
382
+ };
383
+ }
384
+ const config2 = loadAgentConfig();
385
+ config2[agentId] = { runtime, model };
386
+ saveAgentConfig(config2);
387
+ return { ok: true };
388
+ }
389
+ function clearAgentRuntime(agentId) {
390
+ const config2 = loadAgentConfig();
391
+ delete config2[agentId];
392
+ saveAgentConfig(config2);
393
+ }
394
+ var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
395
+ var init_agent_config = __esm({
396
+ "src/lib/agent-config.ts"() {
397
+ "use strict";
398
+ init_config();
399
+ init_runtime_table();
400
+ init_secure_files();
401
+ AGENT_CONFIG_PATH = path3.join(EXE_AI_DIR, "agent-config.json");
402
+ KNOWN_RUNTIMES = {
403
+ claude: ["claude-opus-4", "claude-sonnet-4", "claude-haiku-4.5"],
404
+ codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
405
+ opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
406
+ };
407
+ RUNTIME_LABELS = {
408
+ claude: "Claude Code (Anthropic)",
409
+ codex: "Codex (OpenAI)",
410
+ opencode: "OpenCode (open source)"
411
+ };
412
+ DEFAULT_MODELS = {
413
+ claude: "claude-opus-4",
414
+ codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
415
+ opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
416
+ };
417
+ }
418
+ });
419
+
268
420
  // src/lib/employees.ts
269
421
  var employees_exports = {};
270
422
  __export(employees_exports, {
@@ -280,6 +432,7 @@ __export(employees_exports, {
280
432
  getEmployeeByRole: () => getEmployeeByRole,
281
433
  getEmployeeNamesByRole: () => getEmployeeNamesByRole,
282
434
  hasRole: () => hasRole,
435
+ hireEmployee: () => hireEmployee,
283
436
  isCoordinatorName: () => isCoordinatorName,
284
437
  isCoordinatorRole: () => isCoordinatorRole,
285
438
  isMultiInstance: () => isMultiInstance,
@@ -292,9 +445,9 @@ __export(employees_exports, {
292
445
  validateEmployeeName: () => validateEmployeeName
293
446
  });
294
447
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
295
- import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
448
+ import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
296
449
  import { execSync } from "child_process";
297
- import path3 from "path";
450
+ import path4 from "path";
298
451
  import os3 from "os";
299
452
  function normalizeRole(role) {
300
453
  return (role ?? "").trim().toLowerCase();
@@ -331,7 +484,7 @@ function validateEmployeeName(name) {
331
484
  return { valid: true };
332
485
  }
333
486
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
334
- if (!existsSync2(employeesPath)) {
487
+ if (!existsSync4(employeesPath)) {
335
488
  return [];
336
489
  }
337
490
  const raw = await readFile2(employeesPath, "utf-8");
@@ -342,13 +495,13 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
342
495
  }
343
496
  }
344
497
  async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
345
- await mkdir2(path3.dirname(employeesPath), { recursive: true });
498
+ await mkdir2(path4.dirname(employeesPath), { recursive: true });
346
499
  await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
347
500
  }
348
501
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
349
- if (!existsSync2(employeesPath)) return [];
502
+ if (!existsSync4(employeesPath)) return [];
350
503
  try {
351
- return JSON.parse(readFileSync2(employeesPath, "utf-8"));
504
+ return JSON.parse(readFileSync3(employeesPath, "utf-8"));
352
505
  } catch {
353
506
  return [];
354
507
  }
@@ -390,6 +543,52 @@ function addEmployee(employees, employee) {
390
543
  }
391
544
  return [...employees, normalized];
392
545
  }
546
+ function appendToCoordinatorTeam(employee) {
547
+ const coordinator = getCoordinatorEmployee(loadEmployeesSync());
548
+ if (!coordinator) return;
549
+ const idPath = path4.join(IDENTITY_DIR, `${coordinator.name}.md`);
550
+ if (!existsSync4(idPath)) return;
551
+ const content = readFileSync3(idPath, "utf-8");
552
+ if (content.includes(`**${capitalize(employee.name)}`)) return;
553
+ const teamMatch = content.match(TEAM_SECTION_RE);
554
+ if (!teamMatch || teamMatch.index === void 0) return;
555
+ const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
556
+ const nextHeading = afterTeam.match(/\n## /);
557
+ const entry = `
558
+ **${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
559
+ `;
560
+ let updated;
561
+ if (nextHeading && nextHeading.index !== void 0) {
562
+ const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
563
+ updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
564
+ } else {
565
+ updated = content.trimEnd() + "\n" + entry;
566
+ }
567
+ writeFileSync2(idPath, updated, "utf-8");
568
+ }
569
+ function capitalize(s) {
570
+ return s.charAt(0).toUpperCase() + s.slice(1);
571
+ }
572
+ async function hireEmployee(employee) {
573
+ const employees = await loadEmployees();
574
+ const updated = addEmployee(employees, employee);
575
+ await saveEmployees(updated);
576
+ try {
577
+ appendToCoordinatorTeam(employee);
578
+ } catch {
579
+ }
580
+ try {
581
+ const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
582
+ const config2 = loadAgentConfig2();
583
+ const name = employee.name.toLowerCase();
584
+ if (!config2[name] && config2["default"]) {
585
+ config2[name] = { ...config2["default"] };
586
+ saveAgentConfig2(config2);
587
+ }
588
+ } catch {
589
+ }
590
+ return updated;
591
+ }
393
592
  async function normalizeRosterCase(rosterPath) {
394
593
  const employees = await loadEmployees(rosterPath);
395
594
  let changed = false;
@@ -399,14 +598,14 @@ async function normalizeRosterCase(rosterPath) {
399
598
  emp.name = emp.name.toLowerCase();
400
599
  changed = true;
401
600
  try {
402
- const identityDir = path3.join(os3.homedir(), ".exe-os", "identity");
403
- const oldPath = path3.join(identityDir, `${oldName}.md`);
404
- const newPath = path3.join(identityDir, `${emp.name}.md`);
405
- if (existsSync2(oldPath) && !existsSync2(newPath)) {
601
+ const identityDir = path4.join(os3.homedir(), ".exe-os", "identity");
602
+ const oldPath = path4.join(identityDir, `${oldName}.md`);
603
+ const newPath = path4.join(identityDir, `${emp.name}.md`);
604
+ if (existsSync4(oldPath) && !existsSync4(newPath)) {
406
605
  renameSync2(oldPath, newPath);
407
- } else if (existsSync2(oldPath) && oldPath !== newPath) {
408
- const content = readFileSync2(oldPath, "utf-8");
409
- writeFileSync(newPath, content, "utf-8");
606
+ } else if (existsSync4(oldPath) && oldPath !== newPath) {
607
+ const content = readFileSync3(oldPath, "utf-8");
608
+ writeFileSync2(newPath, content, "utf-8");
410
609
  if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
411
610
  unlinkSync(oldPath);
412
611
  }
@@ -436,7 +635,7 @@ function registerBinSymlinks(name) {
436
635
  errors.push("Could not find 'exe-os' in PATH");
437
636
  return { created, skipped, errors };
438
637
  }
439
- const binDir = path3.dirname(exeBinPath);
638
+ const binDir = path4.dirname(exeBinPath);
440
639
  let target;
441
640
  try {
442
641
  target = readlinkSync(exeBinPath);
@@ -446,8 +645,8 @@ function registerBinSymlinks(name) {
446
645
  }
447
646
  for (const suffix of ["", "-opencode"]) {
448
647
  const linkName = `${name}${suffix}`;
449
- const linkPath = path3.join(binDir, linkName);
450
- if (existsSync2(linkPath)) {
648
+ const linkPath = path4.join(binDir, linkName);
649
+ if (existsSync4(linkPath)) {
451
650
  skipped.push(linkName);
452
651
  continue;
453
652
  }
@@ -460,15 +659,17 @@ function registerBinSymlinks(name) {
460
659
  }
461
660
  return { created, skipped, errors };
462
661
  }
463
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
662
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
464
663
  var init_employees = __esm({
465
664
  "src/lib/employees.ts"() {
466
665
  "use strict";
467
666
  init_config();
468
- EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
667
+ EMPLOYEES_PATH = path4.join(EXE_AI_DIR, "exe-employees.json");
469
668
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
470
669
  COORDINATOR_ROLE = "COO";
471
670
  MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
671
+ IDENTITY_DIR = path4.join(EXE_AI_DIR, "identity");
672
+ TEAM_SECTION_RE = /^## Team\b.*$/m;
472
673
  }
473
674
  });
474
675
 
@@ -479,14 +680,14 @@ __export(session_registry_exports, {
479
680
  pruneStaleSessions: () => pruneStaleSessions,
480
681
  registerSession: () => registerSession
481
682
  });
482
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync, existsSync as existsSync3 } from "fs";
683
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync5 } from "fs";
483
684
  import { execSync as execSync2 } from "child_process";
484
- import path4 from "path";
685
+ import path5 from "path";
485
686
  import os4 from "os";
486
687
  function registerSession(entry) {
487
- const dir = path4.dirname(REGISTRY_PATH);
488
- if (!existsSync3(dir)) {
489
- mkdirSync(dir, { recursive: true });
688
+ const dir = path5.dirname(REGISTRY_PATH);
689
+ if (!existsSync5(dir)) {
690
+ mkdirSync2(dir, { recursive: true });
490
691
  }
491
692
  const sessions = listSessions();
492
693
  const idx = sessions.findIndex((s) => s.windowName === entry.windowName);
@@ -495,11 +696,11 @@ function registerSession(entry) {
495
696
  } else {
496
697
  sessions.push(entry);
497
698
  }
498
- writeFileSync2(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
699
+ writeFileSync3(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
499
700
  }
500
701
  function listSessions() {
501
702
  try {
502
- const raw = readFileSync3(REGISTRY_PATH, "utf8");
703
+ const raw = readFileSync4(REGISTRY_PATH, "utf8");
503
704
  return JSON.parse(raw);
504
705
  } catch {
505
706
  return [];
@@ -520,7 +721,7 @@ function pruneStaleSessions() {
520
721
  const alive = sessions.filter((s) => liveSet.has(s.windowName));
521
722
  const pruned = sessions.length - alive.length;
522
723
  if (pruned > 0) {
523
- writeFileSync2(REGISTRY_PATH, JSON.stringify(alive, null, 2));
724
+ writeFileSync3(REGISTRY_PATH, JSON.stringify(alive, null, 2));
524
725
  }
525
726
  return pruned;
526
727
  }
@@ -528,7 +729,7 @@ var REGISTRY_PATH;
528
729
  var init_session_registry = __esm({
529
730
  "src/lib/session-registry.ts"() {
530
731
  "use strict";
531
- REGISTRY_PATH = path4.join(os4.homedir(), ".exe-os", "session-registry.json");
732
+ REGISTRY_PATH = path5.join(os4.homedir(), ".exe-os", "session-registry.json");
532
733
  }
533
734
  });
534
735
 
@@ -790,67 +991,6 @@ var init_provider_table = __esm({
790
991
  }
791
992
  });
792
993
 
793
- // src/lib/runtime-table.ts
794
- var RUNTIME_TABLE, DEFAULT_RUNTIME;
795
- var init_runtime_table = __esm({
796
- "src/lib/runtime-table.ts"() {
797
- "use strict";
798
- RUNTIME_TABLE = {
799
- codex: {
800
- binary: "codex",
801
- launchMode: "interactive",
802
- autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
803
- inlineFlag: "--no-alt-screen",
804
- apiKeyEnv: "OPENAI_API_KEY",
805
- defaultModel: "gpt-5.4"
806
- },
807
- opencode: {
808
- binary: "opencode",
809
- launchMode: "exec",
810
- autoApproveFlag: "--dangerously-skip-permissions",
811
- inlineFlag: "",
812
- apiKeyEnv: "ANTHROPIC_API_KEY",
813
- defaultModel: "anthropic/claude-sonnet-4-6"
814
- }
815
- };
816
- DEFAULT_RUNTIME = "claude";
817
- }
818
- });
819
-
820
- // src/lib/agent-config.ts
821
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
822
- import path5 from "path";
823
- function loadAgentConfig() {
824
- if (!existsSync4(AGENT_CONFIG_PATH)) return {};
825
- try {
826
- return JSON.parse(readFileSync4(AGENT_CONFIG_PATH, "utf-8"));
827
- } catch {
828
- return {};
829
- }
830
- }
831
- function getAgentRuntime(agentId) {
832
- const config2 = loadAgentConfig();
833
- const entry = config2[agentId];
834
- if (entry) return entry;
835
- const orgDefault = config2["default"];
836
- if (orgDefault) return orgDefault;
837
- return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
838
- }
839
- var AGENT_CONFIG_PATH, DEFAULT_MODELS;
840
- var init_agent_config = __esm({
841
- "src/lib/agent-config.ts"() {
842
- "use strict";
843
- init_config();
844
- init_runtime_table();
845
- AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
846
- DEFAULT_MODELS = {
847
- claude: "claude-opus-4",
848
- codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
849
- opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
850
- };
851
- }
852
- });
853
-
854
994
  // src/lib/intercom-queue.ts
855
995
  var intercom_queue_exports = {};
856
996
  __export(intercom_queue_exports, {
@@ -860,16 +1000,16 @@ __export(intercom_queue_exports, {
860
1000
  queueIntercom: () => queueIntercom,
861
1001
  readQueue: () => readQueue
862
1002
  });
863
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
1003
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
864
1004
  import path6 from "path";
865
1005
  import os5 from "os";
866
1006
  function ensureDir() {
867
1007
  const dir = path6.dirname(QUEUE_PATH);
868
- if (!existsSync5(dir)) mkdirSync3(dir, { recursive: true });
1008
+ if (!existsSync6(dir)) mkdirSync3(dir, { recursive: true });
869
1009
  }
870
1010
  function readQueue() {
871
1011
  try {
872
- if (!existsSync5(QUEUE_PATH)) return [];
1012
+ if (!existsSync6(QUEUE_PATH)) return [];
873
1013
  return JSON.parse(readFileSync5(QUEUE_PATH, "utf8"));
874
1014
  } catch {
875
1015
  return [];
@@ -1032,13 +1172,634 @@ var init_db_retry = __esm({
1032
1172
  }
1033
1173
  });
1034
1174
 
1175
+ // src/lib/database-adapter.ts
1176
+ import os6 from "os";
1177
+ import path7 from "path";
1178
+ import { createRequire } from "module";
1179
+ import { pathToFileURL } from "url";
1180
+ function quotedIdentifier(identifier) {
1181
+ return `"${identifier.replace(/"/g, '""')}"`;
1182
+ }
1183
+ function unqualifiedTableName(name) {
1184
+ const raw = name.trim().replace(/^"|"$/g, "");
1185
+ const parts = raw.split(".");
1186
+ return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
1187
+ }
1188
+ function stripTrailingSemicolon(sql) {
1189
+ return sql.trim().replace(/;+\s*$/u, "");
1190
+ }
1191
+ function appendClause(sql, clause) {
1192
+ const trimmed = stripTrailingSemicolon(sql);
1193
+ const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
1194
+ if (!returningMatch) {
1195
+ return `${trimmed}${clause}`;
1196
+ }
1197
+ const idx = returningMatch.index;
1198
+ return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
1199
+ }
1200
+ function normalizeStatement(stmt) {
1201
+ if (typeof stmt === "string") {
1202
+ return { kind: "positional", sql: stmt, args: [] };
1203
+ }
1204
+ const sql = stmt.sql;
1205
+ if (Array.isArray(stmt.args) || stmt.args === void 0) {
1206
+ return { kind: "positional", sql, args: stmt.args ?? [] };
1207
+ }
1208
+ return { kind: "named", sql, args: stmt.args };
1209
+ }
1210
+ function rewriteBooleanLiterals(sql) {
1211
+ let out = sql;
1212
+ for (const column of BOOLEAN_COLUMN_NAMES) {
1213
+ const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
1214
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
1215
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
1216
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
1217
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
1218
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
1219
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
1220
+ }
1221
+ return out;
1222
+ }
1223
+ function rewriteInsertOrIgnore(sql) {
1224
+ if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
1225
+ return sql;
1226
+ }
1227
+ const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
1228
+ return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
1229
+ }
1230
+ function rewriteInsertOrReplace(sql) {
1231
+ const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
1232
+ if (!match) {
1233
+ return sql;
1234
+ }
1235
+ const rawTable = match[1];
1236
+ const rawColumns = match[2];
1237
+ const remainder = match[3];
1238
+ const tableName = unqualifiedTableName(rawTable);
1239
+ const conflictKeys = UPSERT_KEYS[tableName];
1240
+ if (!conflictKeys?.length) {
1241
+ return sql;
1242
+ }
1243
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
1244
+ const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
1245
+ const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
1246
+ const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
1247
+ return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
1248
+ }
1249
+ function rewriteSql(sql) {
1250
+ let out = sql;
1251
+ out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
1252
+ out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
1253
+ out = rewriteBooleanLiterals(out);
1254
+ out = rewriteInsertOrReplace(out);
1255
+ out = rewriteInsertOrIgnore(out);
1256
+ return stripTrailingSemicolon(out);
1257
+ }
1258
+ function toBoolean(value) {
1259
+ if (value === null || value === void 0) return value;
1260
+ if (typeof value === "boolean") return value;
1261
+ if (typeof value === "number") return value !== 0;
1262
+ if (typeof value === "bigint") return value !== 0n;
1263
+ if (typeof value === "string") {
1264
+ const normalized = value.trim().toLowerCase();
1265
+ if (normalized === "0" || normalized === "false") return false;
1266
+ if (normalized === "1" || normalized === "true") return true;
1267
+ }
1268
+ return Boolean(value);
1269
+ }
1270
+ function countQuestionMarks(sql, end) {
1271
+ let count = 0;
1272
+ let inSingle = false;
1273
+ let inDouble = false;
1274
+ let inLineComment = false;
1275
+ let inBlockComment = false;
1276
+ for (let i = 0; i < end; i++) {
1277
+ const ch = sql[i];
1278
+ const next = sql[i + 1];
1279
+ if (inLineComment) {
1280
+ if (ch === "\n") inLineComment = false;
1281
+ continue;
1282
+ }
1283
+ if (inBlockComment) {
1284
+ if (ch === "*" && next === "/") {
1285
+ inBlockComment = false;
1286
+ i += 1;
1287
+ }
1288
+ continue;
1289
+ }
1290
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
1291
+ inLineComment = true;
1292
+ i += 1;
1293
+ continue;
1294
+ }
1295
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
1296
+ inBlockComment = true;
1297
+ i += 1;
1298
+ continue;
1299
+ }
1300
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
1301
+ inSingle = !inSingle;
1302
+ continue;
1303
+ }
1304
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
1305
+ inDouble = !inDouble;
1306
+ continue;
1307
+ }
1308
+ if (!inSingle && !inDouble && ch === "?") {
1309
+ count += 1;
1310
+ }
1311
+ }
1312
+ return count;
1313
+ }
1314
+ function findBooleanPlaceholderIndexes(sql) {
1315
+ const indexes = /* @__PURE__ */ new Set();
1316
+ for (const column of BOOLEAN_COLUMN_NAMES) {
1317
+ const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
1318
+ for (const match of sql.matchAll(pattern)) {
1319
+ const matchText = match[0];
1320
+ const qIndex = match.index + matchText.lastIndexOf("?");
1321
+ indexes.add(countQuestionMarks(sql, qIndex + 1));
1322
+ }
1323
+ }
1324
+ return indexes;
1325
+ }
1326
+ function coerceInsertBooleanArgs(sql, args) {
1327
+ const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
1328
+ if (!match) return;
1329
+ const rawTable = match[1];
1330
+ const rawColumns = match[2];
1331
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
1332
+ if (!boolColumns?.size) return;
1333
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
1334
+ for (const [index, column] of columns.entries()) {
1335
+ if (boolColumns.has(column) && index < args.length) {
1336
+ args[index] = toBoolean(args[index]);
1337
+ }
1338
+ }
1339
+ }
1340
+ function coerceUpdateBooleanArgs(sql, args) {
1341
+ const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
1342
+ if (!match) return;
1343
+ const rawTable = match[1];
1344
+ const setClause = match[2];
1345
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
1346
+ if (!boolColumns?.size) return;
1347
+ const assignments = setClause.split(",");
1348
+ let placeholderIndex = 0;
1349
+ for (const assignment of assignments) {
1350
+ if (!assignment.includes("?")) continue;
1351
+ placeholderIndex += 1;
1352
+ const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
1353
+ if (colMatch && boolColumns.has(colMatch[1])) {
1354
+ args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
1355
+ }
1356
+ }
1357
+ }
1358
+ function coerceBooleanArgs(sql, args) {
1359
+ const nextArgs = [...args];
1360
+ coerceInsertBooleanArgs(sql, nextArgs);
1361
+ coerceUpdateBooleanArgs(sql, nextArgs);
1362
+ const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
1363
+ for (const index of placeholderIndexes) {
1364
+ if (index > 0 && index <= nextArgs.length) {
1365
+ nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
1366
+ }
1367
+ }
1368
+ return nextArgs;
1369
+ }
1370
+ function convertQuestionMarksToDollarParams(sql) {
1371
+ let out = "";
1372
+ let placeholder = 0;
1373
+ let inSingle = false;
1374
+ let inDouble = false;
1375
+ let inLineComment = false;
1376
+ let inBlockComment = false;
1377
+ for (let i = 0; i < sql.length; i++) {
1378
+ const ch = sql[i];
1379
+ const next = sql[i + 1];
1380
+ if (inLineComment) {
1381
+ out += ch;
1382
+ if (ch === "\n") inLineComment = false;
1383
+ continue;
1384
+ }
1385
+ if (inBlockComment) {
1386
+ out += ch;
1387
+ if (ch === "*" && next === "/") {
1388
+ out += next;
1389
+ inBlockComment = false;
1390
+ i += 1;
1391
+ }
1392
+ continue;
1393
+ }
1394
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
1395
+ out += ch + next;
1396
+ inLineComment = true;
1397
+ i += 1;
1398
+ continue;
1399
+ }
1400
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
1401
+ out += ch + next;
1402
+ inBlockComment = true;
1403
+ i += 1;
1404
+ continue;
1405
+ }
1406
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
1407
+ inSingle = !inSingle;
1408
+ out += ch;
1409
+ continue;
1410
+ }
1411
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
1412
+ inDouble = !inDouble;
1413
+ out += ch;
1414
+ continue;
1415
+ }
1416
+ if (!inSingle && !inDouble && ch === "?") {
1417
+ placeholder += 1;
1418
+ out += `$${placeholder}`;
1419
+ continue;
1420
+ }
1421
+ out += ch;
1422
+ }
1423
+ return out;
1424
+ }
1425
+ function translateStatementForPostgres(stmt) {
1426
+ const normalized = normalizeStatement(stmt);
1427
+ if (normalized.kind === "named") {
1428
+ throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
1429
+ }
1430
+ const rewrittenSql = rewriteSql(normalized.sql);
1431
+ const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
1432
+ return {
1433
+ sql: convertQuestionMarksToDollarParams(rewrittenSql),
1434
+ args: coercedArgs
1435
+ };
1436
+ }
1437
+ function shouldBypassPostgres(stmt) {
1438
+ const normalized = normalizeStatement(stmt);
1439
+ if (normalized.kind === "named") {
1440
+ return true;
1441
+ }
1442
+ return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
1443
+ }
1444
+ function shouldFallbackOnError(error) {
1445
+ const message = error instanceof Error ? error.message : String(error);
1446
+ return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
1447
+ }
1448
+ function isReadQuery(sql) {
1449
+ const trimmed = sql.trimStart();
1450
+ return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
1451
+ }
1452
+ function buildRow(row, columns) {
1453
+ const values = columns.map((column) => row[column]);
1454
+ return Object.assign(values, row);
1455
+ }
1456
+ function buildResultSet(rows, rowsAffected = 0) {
1457
+ const columns = rows[0] ? Object.keys(rows[0]) : [];
1458
+ const resultRows = rows.map((row) => buildRow(row, columns));
1459
+ return {
1460
+ columns,
1461
+ columnTypes: columns.map(() => ""),
1462
+ rows: resultRows,
1463
+ rowsAffected,
1464
+ lastInsertRowid: void 0,
1465
+ toJSON() {
1466
+ return {
1467
+ columns,
1468
+ columnTypes: columns.map(() => ""),
1469
+ rows,
1470
+ rowsAffected,
1471
+ lastInsertRowid: void 0
1472
+ };
1473
+ }
1474
+ };
1475
+ }
1476
+ async function loadPrismaClient() {
1477
+ if (!prismaClientPromise) {
1478
+ prismaClientPromise = (async () => {
1479
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
1480
+ if (explicitPath) {
1481
+ const module2 = await import(pathToFileURL(explicitPath).href);
1482
+ const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
1483
+ if (!PrismaClient2) {
1484
+ throw new Error(`No PrismaClient export found at ${explicitPath}`);
1485
+ }
1486
+ return new PrismaClient2();
1487
+ }
1488
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path7.join(os6.homedir(), "exe-db");
1489
+ const requireFromExeDb = createRequire(path7.join(exeDbRoot, "package.json"));
1490
+ const prismaEntry = requireFromExeDb.resolve("@prisma/client");
1491
+ const module = await import(pathToFileURL(prismaEntry).href);
1492
+ const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
1493
+ if (!PrismaClient) {
1494
+ throw new Error(`No PrismaClient export found in ${prismaEntry}`);
1495
+ }
1496
+ return new PrismaClient();
1497
+ })();
1498
+ }
1499
+ return prismaClientPromise;
1500
+ }
1501
+ async function ensureCompatibilityViews(prisma) {
1502
+ if (!compatibilityBootstrapPromise) {
1503
+ compatibilityBootstrapPromise = (async () => {
1504
+ for (const mapping of VIEW_MAPPINGS) {
1505
+ const relation = mapping.source.replace(/"/g, "");
1506
+ const rows = await prisma.$queryRawUnsafe(
1507
+ "SELECT to_regclass($1) AS regclass",
1508
+ relation
1509
+ );
1510
+ if (!rows[0]?.regclass) {
1511
+ continue;
1512
+ }
1513
+ await prisma.$executeRawUnsafe(
1514
+ `CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
1515
+ );
1516
+ }
1517
+ })();
1518
+ }
1519
+ return compatibilityBootstrapPromise;
1520
+ }
1521
+ async function executeOnPrisma(executor, stmt) {
1522
+ const translated = translateStatementForPostgres(stmt);
1523
+ if (isReadQuery(translated.sql)) {
1524
+ const rows = await executor.$queryRawUnsafe(
1525
+ translated.sql,
1526
+ ...translated.args
1527
+ );
1528
+ return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
1529
+ }
1530
+ const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
1531
+ return buildResultSet([], rowsAffected);
1532
+ }
1533
+ function splitSqlStatements(sql) {
1534
+ const parts = [];
1535
+ let current = "";
1536
+ let inSingle = false;
1537
+ let inDouble = false;
1538
+ let inLineComment = false;
1539
+ let inBlockComment = false;
1540
+ for (let i = 0; i < sql.length; i++) {
1541
+ const ch = sql[i];
1542
+ const next = sql[i + 1];
1543
+ if (inLineComment) {
1544
+ current += ch;
1545
+ if (ch === "\n") inLineComment = false;
1546
+ continue;
1547
+ }
1548
+ if (inBlockComment) {
1549
+ current += ch;
1550
+ if (ch === "*" && next === "/") {
1551
+ current += next;
1552
+ inBlockComment = false;
1553
+ i += 1;
1554
+ }
1555
+ continue;
1556
+ }
1557
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
1558
+ current += ch + next;
1559
+ inLineComment = true;
1560
+ i += 1;
1561
+ continue;
1562
+ }
1563
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
1564
+ current += ch + next;
1565
+ inBlockComment = true;
1566
+ i += 1;
1567
+ continue;
1568
+ }
1569
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
1570
+ inSingle = !inSingle;
1571
+ current += ch;
1572
+ continue;
1573
+ }
1574
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
1575
+ inDouble = !inDouble;
1576
+ current += ch;
1577
+ continue;
1578
+ }
1579
+ if (!inSingle && !inDouble && ch === ";") {
1580
+ if (current.trim()) {
1581
+ parts.push(current.trim());
1582
+ }
1583
+ current = "";
1584
+ continue;
1585
+ }
1586
+ current += ch;
1587
+ }
1588
+ if (current.trim()) {
1589
+ parts.push(current.trim());
1590
+ }
1591
+ return parts;
1592
+ }
1593
+ async function createPrismaDbAdapter(fallbackClient) {
1594
+ const prisma = await loadPrismaClient();
1595
+ await ensureCompatibilityViews(prisma);
1596
+ let closed = false;
1597
+ let adapter;
1598
+ const fallbackExecute = async (stmt, error) => {
1599
+ if (!fallbackClient) {
1600
+ if (error) throw error;
1601
+ throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
1602
+ }
1603
+ if (error) {
1604
+ process.stderr.write(
1605
+ `[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
1606
+ `
1607
+ );
1608
+ }
1609
+ return fallbackClient.execute(stmt);
1610
+ };
1611
+ adapter = {
1612
+ async execute(stmt) {
1613
+ if (shouldBypassPostgres(stmt)) {
1614
+ return fallbackExecute(stmt);
1615
+ }
1616
+ try {
1617
+ return await executeOnPrisma(prisma, stmt);
1618
+ } catch (error) {
1619
+ if (shouldFallbackOnError(error)) {
1620
+ return fallbackExecute(stmt, error);
1621
+ }
1622
+ throw error;
1623
+ }
1624
+ },
1625
+ async batch(stmts, mode) {
1626
+ if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
1627
+ if (!fallbackClient) {
1628
+ throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
1629
+ }
1630
+ return fallbackClient.batch(stmts, mode);
1631
+ }
1632
+ try {
1633
+ if (prisma.$transaction) {
1634
+ return await prisma.$transaction(async (tx) => {
1635
+ const results2 = [];
1636
+ for (const stmt of stmts) {
1637
+ results2.push(await executeOnPrisma(tx, stmt));
1638
+ }
1639
+ return results2;
1640
+ });
1641
+ }
1642
+ const results = [];
1643
+ for (const stmt of stmts) {
1644
+ results.push(await executeOnPrisma(prisma, stmt));
1645
+ }
1646
+ return results;
1647
+ } catch (error) {
1648
+ if (fallbackClient && shouldFallbackOnError(error)) {
1649
+ process.stderr.write(
1650
+ `[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
1651
+ `
1652
+ );
1653
+ return fallbackClient.batch(stmts, mode);
1654
+ }
1655
+ throw error;
1656
+ }
1657
+ },
1658
+ async migrate(stmts) {
1659
+ if (fallbackClient) {
1660
+ return fallbackClient.migrate(stmts);
1661
+ }
1662
+ return adapter.batch(stmts, "deferred");
1663
+ },
1664
+ async transaction(mode) {
1665
+ if (!fallbackClient) {
1666
+ throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
1667
+ }
1668
+ return fallbackClient.transaction(mode);
1669
+ },
1670
+ async executeMultiple(sql) {
1671
+ if (fallbackClient && shouldBypassPostgres(sql)) {
1672
+ return fallbackClient.executeMultiple(sql);
1673
+ }
1674
+ for (const statement of splitSqlStatements(sql)) {
1675
+ await adapter.execute(statement);
1676
+ }
1677
+ },
1678
+ async sync() {
1679
+ if (fallbackClient) {
1680
+ return fallbackClient.sync();
1681
+ }
1682
+ return { frame_no: 0, frames_synced: 0 };
1683
+ },
1684
+ close() {
1685
+ closed = true;
1686
+ prismaClientPromise = null;
1687
+ compatibilityBootstrapPromise = null;
1688
+ void prisma.$disconnect?.();
1689
+ },
1690
+ get closed() {
1691
+ return closed;
1692
+ },
1693
+ get protocol() {
1694
+ return "prisma-postgres";
1695
+ }
1696
+ };
1697
+ return adapter;
1698
+ }
1699
+ var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
1700
+ var init_database_adapter = __esm({
1701
+ "src/lib/database-adapter.ts"() {
1702
+ "use strict";
1703
+ VIEW_MAPPINGS = [
1704
+ { view: "memories", source: "memory.memory_records" },
1705
+ { view: "tasks", source: "memory.tasks" },
1706
+ { view: "behaviors", source: "memory.behaviors" },
1707
+ { view: "entities", source: "memory.entities" },
1708
+ { view: "relationships", source: "memory.relationships" },
1709
+ { view: "entity_memories", source: "memory.entity_memories" },
1710
+ { view: "entity_aliases", source: "memory.entity_aliases" },
1711
+ { view: "notifications", source: "memory.notifications" },
1712
+ { view: "messages", source: "memory.messages" },
1713
+ { view: "users", source: "wiki.users" },
1714
+ { view: "workspaces", source: "wiki.workspaces" },
1715
+ { view: "workspace_users", source: "wiki.workspace_users" },
1716
+ { view: "documents", source: "wiki.workspace_documents" },
1717
+ { view: "chats", source: "wiki.workspace_chats" }
1718
+ ];
1719
+ UPSERT_KEYS = {
1720
+ memories: ["id"],
1721
+ tasks: ["id"],
1722
+ behaviors: ["id"],
1723
+ entities: ["id"],
1724
+ relationships: ["id"],
1725
+ entity_aliases: ["alias"],
1726
+ notifications: ["id"],
1727
+ messages: ["id"],
1728
+ users: ["id"],
1729
+ workspaces: ["id"],
1730
+ workspace_users: ["id"],
1731
+ documents: ["id"],
1732
+ chats: ["id"]
1733
+ };
1734
+ BOOLEAN_COLUMNS_BY_TABLE = {
1735
+ memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
1736
+ behaviors: /* @__PURE__ */ new Set(["active"]),
1737
+ notifications: /* @__PURE__ */ new Set(["read"]),
1738
+ users: /* @__PURE__ */ new Set(["has_personal_memory"])
1739
+ };
1740
+ BOOLEAN_COLUMN_NAMES = new Set(
1741
+ Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
1742
+ );
1743
+ IMMEDIATE_FALLBACK_PATTERNS = [
1744
+ /\bPRAGMA\b/i,
1745
+ /\bsqlite_master\b/i,
1746
+ /(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
1747
+ /\bMATCH\b/i,
1748
+ /\bvector_distance_cos\s*\(/i,
1749
+ /\bjson_extract\s*\(/i,
1750
+ /\bjulianday\s*\(/i,
1751
+ /\bstrftime\s*\(/i,
1752
+ /\blast_insert_rowid\s*\(/i
1753
+ ];
1754
+ prismaClientPromise = null;
1755
+ compatibilityBootstrapPromise = null;
1756
+ }
1757
+ });
1758
+
1759
+ // src/lib/daemon-auth.ts
1760
+ import crypto from "crypto";
1761
+ import path8 from "path";
1762
+ import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
1763
+ function normalizeToken(token) {
1764
+ if (!token) return null;
1765
+ const trimmed = token.trim();
1766
+ return trimmed.length > 0 ? trimmed : null;
1767
+ }
1768
+ function readDaemonToken() {
1769
+ try {
1770
+ if (!existsSync7(DAEMON_TOKEN_PATH)) return null;
1771
+ return normalizeToken(readFileSync6(DAEMON_TOKEN_PATH, "utf8"));
1772
+ } catch {
1773
+ return null;
1774
+ }
1775
+ }
1776
+ function ensureDaemonToken(seed) {
1777
+ const existing = readDaemonToken();
1778
+ if (existing) return existing;
1779
+ const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
1780
+ ensurePrivateDirSync(EXE_AI_DIR);
1781
+ writeFileSync5(DAEMON_TOKEN_PATH, `${token}
1782
+ `, "utf8");
1783
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
1784
+ return token;
1785
+ }
1786
+ var DAEMON_TOKEN_PATH;
1787
+ var init_daemon_auth = __esm({
1788
+ "src/lib/daemon-auth.ts"() {
1789
+ "use strict";
1790
+ init_config();
1791
+ init_secure_files();
1792
+ DAEMON_TOKEN_PATH = path8.join(EXE_AI_DIR, "exed.token");
1793
+ }
1794
+ });
1795
+
1035
1796
  // src/lib/exe-daemon-client.ts
1036
1797
  import net from "net";
1037
- import os6 from "os";
1798
+ import os7 from "os";
1038
1799
  import { spawn } from "child_process";
1039
1800
  import { randomUUID as randomUUID2 } from "crypto";
1040
- import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync6, openSync, closeSync, statSync } from "fs";
1041
- import path7 from "path";
1801
+ import { existsSync as existsSync8, unlinkSync as unlinkSync2, readFileSync as readFileSync7, openSync, closeSync, statSync } from "fs";
1802
+ import path9 from "path";
1042
1803
  import { fileURLToPath } from "url";
1043
1804
  function handleData(chunk) {
1044
1805
  _buffer += chunk.toString();
@@ -1066,9 +1827,9 @@ function handleData(chunk) {
1066
1827
  }
1067
1828
  }
1068
1829
  function cleanupStaleFiles() {
1069
- if (existsSync6(PID_PATH)) {
1830
+ if (existsSync8(PID_PATH)) {
1070
1831
  try {
1071
- const pid = parseInt(readFileSync6(PID_PATH, "utf8").trim(), 10);
1832
+ const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
1072
1833
  if (pid > 0) {
1073
1834
  try {
1074
1835
  process.kill(pid, 0);
@@ -1089,17 +1850,17 @@ function cleanupStaleFiles() {
1089
1850
  }
1090
1851
  }
1091
1852
  function findPackageRoot() {
1092
- let dir = path7.dirname(fileURLToPath(import.meta.url));
1093
- const { root } = path7.parse(dir);
1853
+ let dir = path9.dirname(fileURLToPath(import.meta.url));
1854
+ const { root } = path9.parse(dir);
1094
1855
  while (dir !== root) {
1095
- if (existsSync6(path7.join(dir, "package.json"))) return dir;
1096
- dir = path7.dirname(dir);
1856
+ if (existsSync8(path9.join(dir, "package.json"))) return dir;
1857
+ dir = path9.dirname(dir);
1097
1858
  }
1098
1859
  return null;
1099
1860
  }
1100
1861
  function spawnDaemon() {
1101
- const freeGB = os6.freemem() / (1024 * 1024 * 1024);
1102
- const totalGB = os6.totalmem() / (1024 * 1024 * 1024);
1862
+ const freeGB = os7.freemem() / (1024 * 1024 * 1024);
1863
+ const totalGB = os7.totalmem() / (1024 * 1024 * 1024);
1103
1864
  if (totalGB <= 8) {
1104
1865
  process.stderr.write(
1105
1866
  `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
@@ -1119,16 +1880,17 @@ function spawnDaemon() {
1119
1880
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
1120
1881
  return;
1121
1882
  }
1122
- const daemonPath = path7.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1123
- if (!existsSync6(daemonPath)) {
1883
+ const daemonPath = path9.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1884
+ if (!existsSync8(daemonPath)) {
1124
1885
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
1125
1886
  `);
1126
1887
  return;
1127
1888
  }
1128
1889
  const resolvedPath = daemonPath;
1890
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
1129
1891
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
1130
1892
  `);
1131
- const logPath = path7.join(path7.dirname(SOCKET_PATH), "exed.log");
1893
+ const logPath = path9.join(path9.dirname(SOCKET_PATH), "exed.log");
1132
1894
  let stderrFd = "ignore";
1133
1895
  try {
1134
1896
  stderrFd = openSync(logPath, "a");
@@ -1146,7 +1908,8 @@ function spawnDaemon() {
1146
1908
  TMUX_PANE: void 0,
1147
1909
  // Prevents resolveExeSession() from scoping to one session
1148
1910
  EXE_DAEMON_SOCK: SOCKET_PATH,
1149
- EXE_DAEMON_PID: PID_PATH
1911
+ EXE_DAEMON_PID: PID_PATH,
1912
+ [DAEMON_TOKEN_ENV]: daemonToken
1150
1913
  }
1151
1914
  });
1152
1915
  child.unref();
@@ -1256,13 +2019,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1256
2019
  return;
1257
2020
  }
1258
2021
  const id = randomUUID2();
2022
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
1259
2023
  const timer = setTimeout(() => {
1260
2024
  _pending.delete(id);
1261
2025
  resolve({ error: "Request timeout" });
1262
2026
  }, timeoutMs);
1263
2027
  _pending.set(id, { resolve, timer });
1264
2028
  try {
1265
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
2029
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
1266
2030
  } catch {
1267
2031
  clearTimeout(timer);
1268
2032
  _pending.delete(id);
@@ -1279,74 +2043,123 @@ async function pingDaemon() {
1279
2043
  return null;
1280
2044
  }
1281
2045
  function killAndRespawnDaemon() {
1282
- process.stderr.write("[exed-client] Killing daemon for restart...\n");
1283
- if (existsSync6(PID_PATH)) {
1284
- try {
1285
- const pid = parseInt(readFileSync6(PID_PATH, "utf8").trim(), 10);
1286
- if (pid > 0) {
1287
- try {
1288
- process.kill(pid, "SIGKILL");
1289
- } catch {
2046
+ if (!acquireSpawnLock()) {
2047
+ process.stderr.write("[exed-client] Another process is already restarting daemon \u2014 skipping\n");
2048
+ if (_socket) {
2049
+ _socket.destroy();
2050
+ _socket = null;
2051
+ }
2052
+ _connected = false;
2053
+ _buffer = "";
2054
+ return;
2055
+ }
2056
+ try {
2057
+ process.stderr.write("[exed-client] Killing daemon for restart...\n");
2058
+ if (existsSync8(PID_PATH)) {
2059
+ try {
2060
+ const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
2061
+ if (pid > 0) {
2062
+ try {
2063
+ process.kill(pid, "SIGKILL");
2064
+ } catch {
2065
+ }
1290
2066
  }
2067
+ } catch {
1291
2068
  }
2069
+ }
2070
+ if (_socket) {
2071
+ _socket.destroy();
2072
+ _socket = null;
2073
+ }
2074
+ _connected = false;
2075
+ _buffer = "";
2076
+ try {
2077
+ unlinkSync2(PID_PATH);
1292
2078
  } catch {
1293
2079
  }
2080
+ try {
2081
+ unlinkSync2(SOCKET_PATH);
2082
+ } catch {
2083
+ }
2084
+ spawnDaemon();
2085
+ } finally {
2086
+ releaseSpawnLock();
1294
2087
  }
1295
- if (_socket) {
1296
- _socket.destroy();
1297
- _socket = null;
1298
- }
1299
- _connected = false;
1300
- _buffer = "";
1301
- try {
1302
- unlinkSync2(PID_PATH);
1303
- } catch {
1304
- }
2088
+ }
2089
+ function isDaemonTooYoung() {
1305
2090
  try {
1306
- unlinkSync2(SOCKET_PATH);
2091
+ const stat = statSync(PID_PATH);
2092
+ return Date.now() - stat.mtimeMs < MIN_DAEMON_AGE_MS;
1307
2093
  } catch {
2094
+ return false;
2095
+ }
2096
+ }
2097
+ async function retryThenRestart(doRequest, label) {
2098
+ const result = await doRequest();
2099
+ if (!result.error) {
2100
+ _consecutiveFailures = 0;
2101
+ return result;
2102
+ }
2103
+ _consecutiveFailures++;
2104
+ for (let i = 0; i < MAX_RETRIES_BEFORE_RESTART; i++) {
2105
+ const delayMs = RETRY_DELAYS_MS[i] ?? 5e3;
2106
+ process.stderr.write(`[exed-client] ${label} failed (${result.error}), retry ${i + 1}/${MAX_RETRIES_BEFORE_RESTART} in ${delayMs}ms
2107
+ `);
2108
+ await new Promise((r) => setTimeout(r, delayMs));
2109
+ if (!_connected) {
2110
+ if (!await connectToSocket()) continue;
2111
+ }
2112
+ const retry = await doRequest();
2113
+ if (!retry.error) {
2114
+ _consecutiveFailures = 0;
2115
+ return retry;
2116
+ }
2117
+ _consecutiveFailures++;
2118
+ }
2119
+ if (isDaemonTooYoung()) {
2120
+ process.stderr.write(`[exed-client] ${label}: daemon too young (< ${MIN_DAEMON_AGE_MS / 1e3}s) \u2014 skipping restart
2121
+ `);
2122
+ return { error: result.error };
2123
+ }
2124
+ process.stderr.write(`[exed-client] ${label}: ${_consecutiveFailures} consecutive failures \u2014 restarting daemon
2125
+ `);
2126
+ killAndRespawnDaemon();
2127
+ const start = Date.now();
2128
+ let delay2 = 200;
2129
+ while (Date.now() - start < CONNECT_TIMEOUT_MS) {
2130
+ await new Promise((r) => setTimeout(r, delay2));
2131
+ if (await connectToSocket()) break;
2132
+ delay2 = Math.min(delay2 * 2, 3e3);
1308
2133
  }
1309
- spawnDaemon();
2134
+ if (!_connected) return { error: "Daemon restart failed" };
2135
+ const final = await doRequest();
2136
+ if (!final.error) _consecutiveFailures = 0;
2137
+ return final;
1310
2138
  }
1311
2139
  async function embedViaClient(text, priority = "high") {
1312
2140
  if (!_connected && !await connectEmbedDaemon()) return null;
1313
2141
  _requestCount++;
1314
2142
  if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
1315
2143
  const health = await pingDaemon();
1316
- if (!health) {
2144
+ if (!health && !isDaemonTooYoung()) {
1317
2145
  process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
1318
2146
  `);
1319
2147
  killAndRespawnDaemon();
1320
2148
  const start = Date.now();
1321
- let delay2 = 200;
2149
+ let d = 200;
1322
2150
  while (Date.now() - start < CONNECT_TIMEOUT_MS) {
1323
- await new Promise((r) => setTimeout(r, delay2));
2151
+ await new Promise((r) => setTimeout(r, d));
1324
2152
  if (await connectToSocket()) break;
1325
- delay2 = Math.min(delay2 * 2, 3e3);
2153
+ d = Math.min(d * 2, 3e3);
1326
2154
  }
1327
2155
  if (!_connected) return null;
1328
2156
  }
1329
2157
  }
1330
- const result = await sendRequest([text], priority);
1331
- if (!result.error && result.vectors?.[0]) return result.vectors[0];
1332
- if (result.error) {
1333
- process.stderr.write(`[exed-client] Embed failed (${result.error}) \u2014 attempting restart
1334
- `);
1335
- killAndRespawnDaemon();
1336
- const start = Date.now();
1337
- let delay2 = 200;
1338
- while (Date.now() - start < CONNECT_TIMEOUT_MS) {
1339
- await new Promise((r) => setTimeout(r, delay2));
1340
- if (await connectToSocket()) break;
1341
- delay2 = Math.min(delay2 * 2, 3e3);
1342
- }
1343
- if (!_connected) return null;
1344
- const retry = await sendRequest([text], priority);
1345
- if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
1346
- process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
1347
- `);
1348
- }
1349
- return null;
2158
+ const result = await retryThenRestart(
2159
+ () => sendRequest([text], priority),
2160
+ "Embed"
2161
+ );
2162
+ return !result.error && result.vectors?.[0] ? result.vectors[0] : null;
1350
2163
  }
1351
2164
  function disconnectClient() {
1352
2165
  if (_socket) {
@@ -1364,22 +2177,28 @@ function disconnectClient() {
1364
2177
  function isClientConnected() {
1365
2178
  return _connected;
1366
2179
  }
1367
- 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;
2180
+ var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, DAEMON_TOKEN_ENV, _socket, _connected, _buffer, _requestCount, _consecutiveFailures, HEALTH_CHECK_INTERVAL, MAX_RETRIES_BEFORE_RESTART, RETRY_DELAYS_MS, MIN_DAEMON_AGE_MS, _pending, MAX_BUFFER;
1368
2181
  var init_exe_daemon_client = __esm({
1369
2182
  "src/lib/exe-daemon-client.ts"() {
1370
2183
  "use strict";
1371
2184
  init_config();
1372
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path7.join(EXE_AI_DIR, "exed.sock");
1373
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path7.join(EXE_AI_DIR, "exed.pid");
1374
- SPAWN_LOCK_PATH = path7.join(EXE_AI_DIR, "exed-spawn.lock");
2185
+ init_daemon_auth();
2186
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path9.join(EXE_AI_DIR, "exed.sock");
2187
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path9.join(EXE_AI_DIR, "exed.pid");
2188
+ SPAWN_LOCK_PATH = path9.join(EXE_AI_DIR, "exed-spawn.lock");
1375
2189
  SPAWN_LOCK_STALE_MS = 3e4;
1376
2190
  CONNECT_TIMEOUT_MS = 15e3;
1377
2191
  REQUEST_TIMEOUT_MS = 3e4;
2192
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
1378
2193
  _socket = null;
1379
2194
  _connected = false;
1380
2195
  _buffer = "";
1381
2196
  _requestCount = 0;
2197
+ _consecutiveFailures = 0;
1382
2198
  HEALTH_CHECK_INTERVAL = 100;
2199
+ MAX_RETRIES_BEFORE_RESTART = 3;
2200
+ RETRY_DELAYS_MS = [1e3, 3e3, 5e3];
2201
+ MIN_DAEMON_AGE_MS = 3e4;
1383
2202
  _pending = /* @__PURE__ */ new Map();
1384
2203
  MAX_BUFFER = 1e7;
1385
2204
  }
@@ -1455,7 +2274,7 @@ __export(db_daemon_client_exports, {
1455
2274
  createDaemonDbClient: () => createDaemonDbClient,
1456
2275
  initDaemonDbClient: () => initDaemonDbClient
1457
2276
  });
1458
- function normalizeStatement(stmt) {
2277
+ function normalizeStatement2(stmt) {
1459
2278
  if (typeof stmt === "string") {
1460
2279
  return { sql: stmt, args: [] };
1461
2280
  }
@@ -1479,7 +2298,7 @@ function createDaemonDbClient(fallbackClient) {
1479
2298
  if (!_useDaemon || !isClientConnected()) {
1480
2299
  return fallbackClient.execute(stmt);
1481
2300
  }
1482
- const { sql, args } = normalizeStatement(stmt);
2301
+ const { sql, args } = normalizeStatement2(stmt);
1483
2302
  const response = await sendDaemonRequest({
1484
2303
  type: "db-execute",
1485
2304
  sql,
@@ -1504,7 +2323,7 @@ function createDaemonDbClient(fallbackClient) {
1504
2323
  if (!_useDaemon || !isClientConnected()) {
1505
2324
  return fallbackClient.batch(stmts, mode);
1506
2325
  }
1507
- const statements = stmts.map(normalizeStatement);
2326
+ const statements = stmts.map(normalizeStatement2);
1508
2327
  const response = await sendDaemonRequest({
1509
2328
  type: "db-batch",
1510
2329
  statements,
@@ -1599,6 +2418,18 @@ __export(database_exports, {
1599
2418
  });
1600
2419
  import { createClient } from "@libsql/client";
1601
2420
  async function initDatabase(config2) {
2421
+ if (_walCheckpointTimer) {
2422
+ clearInterval(_walCheckpointTimer);
2423
+ _walCheckpointTimer = null;
2424
+ }
2425
+ if (_daemonClient) {
2426
+ _daemonClient.close();
2427
+ _daemonClient = null;
2428
+ }
2429
+ if (_adapterClient && _adapterClient !== _resilientClient) {
2430
+ _adapterClient.close();
2431
+ }
2432
+ _adapterClient = null;
1602
2433
  if (_client) {
1603
2434
  _client.close();
1604
2435
  _client = null;
@@ -1612,6 +2443,7 @@ async function initDatabase(config2) {
1612
2443
  }
1613
2444
  _client = createClient(opts);
1614
2445
  _resilientClient = wrapWithRetry(_client);
2446
+ _adapterClient = _resilientClient;
1615
2447
  _client.execute("PRAGMA busy_timeout = 30000").catch(() => {
1616
2448
  });
1617
2449
  _client.execute("PRAGMA journal_mode = WAL").catch(() => {
@@ -1622,14 +2454,20 @@ async function initDatabase(config2) {
1622
2454
  });
1623
2455
  }, 3e4);
1624
2456
  _walCheckpointTimer.unref();
2457
+ if (process.env.DATABASE_URL) {
2458
+ _adapterClient = await createPrismaDbAdapter(_resilientClient);
2459
+ }
1625
2460
  }
1626
2461
  function isInitialized() {
1627
- return _client !== null;
2462
+ return _adapterClient !== null || _client !== null;
1628
2463
  }
1629
2464
  function getClient() {
1630
- if (!_resilientClient) {
2465
+ if (!_adapterClient) {
1631
2466
  throw new Error("Database client not initialized. Call initDatabase() first.");
1632
2467
  }
2468
+ if (process.env.DATABASE_URL) {
2469
+ return _adapterClient;
2470
+ }
1633
2471
  if (process.env.EXE_IS_DAEMON === "1") {
1634
2472
  return _resilientClient;
1635
2473
  }
@@ -1639,6 +2477,7 @@ function getClient() {
1639
2477
  return _resilientClient;
1640
2478
  }
1641
2479
  async function initDaemonClient() {
2480
+ if (process.env.DATABASE_URL) return;
1642
2481
  if (process.env.EXE_IS_DAEMON === "1") return;
1643
2482
  if (!_resilientClient) return;
1644
2483
  try {
@@ -1935,6 +2774,7 @@ async function ensureSchema() {
1935
2774
  project TEXT NOT NULL,
1936
2775
  summary TEXT NOT NULL,
1937
2776
  task_file TEXT,
2777
+ session_scope TEXT,
1938
2778
  read INTEGER NOT NULL DEFAULT 0,
1939
2779
  created_at TEXT NOT NULL
1940
2780
  );
@@ -1943,7 +2783,7 @@ async function ensureSchema() {
1943
2783
  ON notifications(read);
1944
2784
 
1945
2785
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
1946
- ON notifications(agent_id);
2786
+ ON notifications(agent_id, session_scope);
1947
2787
 
1948
2788
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
1949
2789
  ON notifications(task_file);
@@ -1981,6 +2821,7 @@ async function ensureSchema() {
1981
2821
  target_agent TEXT NOT NULL,
1982
2822
  target_project TEXT,
1983
2823
  target_device TEXT NOT NULL DEFAULT 'local',
2824
+ session_scope TEXT,
1984
2825
  content TEXT NOT NULL,
1985
2826
  priority TEXT DEFAULT 'normal',
1986
2827
  status TEXT DEFAULT 'pending',
@@ -1994,10 +2835,31 @@ async function ensureSchema() {
1994
2835
  );
1995
2836
 
1996
2837
  CREATE INDEX IF NOT EXISTS idx_messages_target
1997
- ON messages(target_agent, status);
2838
+ ON messages(target_agent, session_scope, status);
1998
2839
 
1999
2840
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
2000
- ON messages(target_agent, from_agent, server_seq);
2841
+ ON messages(target_agent, session_scope, from_agent, server_seq);
2842
+ `);
2843
+ try {
2844
+ await client.execute({
2845
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
2846
+ args: []
2847
+ });
2848
+ } catch {
2849
+ }
2850
+ try {
2851
+ await client.execute({
2852
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
2853
+ args: []
2854
+ });
2855
+ } catch {
2856
+ }
2857
+ await client.executeMultiple(`
2858
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
2859
+ ON notifications(agent_id, session_scope, read, created_at);
2860
+
2861
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
2862
+ ON messages(target_agent, session_scope, status, created_at);
2001
2863
  `);
2002
2864
  try {
2003
2865
  await client.execute({
@@ -2581,46 +3443,66 @@ async function ensureSchema() {
2581
3443
  } catch {
2582
3444
  }
2583
3445
  }
3446
+ try {
3447
+ await client.execute({
3448
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
3449
+ args: []
3450
+ });
3451
+ } catch {
3452
+ }
2584
3453
  }
2585
3454
  async function disposeDatabase() {
3455
+ if (_walCheckpointTimer) {
3456
+ clearInterval(_walCheckpointTimer);
3457
+ _walCheckpointTimer = null;
3458
+ }
2586
3459
  if (_daemonClient) {
2587
3460
  _daemonClient.close();
2588
3461
  _daemonClient = null;
2589
3462
  }
3463
+ if (_adapterClient && _adapterClient !== _resilientClient) {
3464
+ _adapterClient.close();
3465
+ }
3466
+ _adapterClient = null;
2590
3467
  if (_client) {
2591
3468
  _client.close();
2592
3469
  _client = null;
2593
3470
  _resilientClient = null;
2594
3471
  }
2595
3472
  }
2596
- var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
3473
+ var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
2597
3474
  var init_database = __esm({
2598
3475
  "src/lib/database.ts"() {
2599
3476
  "use strict";
2600
3477
  init_db_retry();
2601
3478
  init_employees();
3479
+ init_database_adapter();
2602
3480
  _client = null;
2603
3481
  _resilientClient = null;
2604
3482
  _walCheckpointTimer = null;
2605
3483
  _daemonClient = null;
3484
+ _adapterClient = null;
2606
3485
  initTurso = initDatabase;
2607
3486
  disposeTurso = disposeDatabase;
2608
3487
  }
2609
3488
  });
2610
3489
 
2611
3490
  // src/lib/license.ts
2612
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
3491
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync9, mkdirSync as mkdirSync4 } from "fs";
2613
3492
  import { randomUUID as randomUUID3 } from "crypto";
2614
- import path8 from "path";
3493
+ import { createRequire as createRequire2 } from "module";
3494
+ import { pathToFileURL as pathToFileURL2 } from "url";
3495
+ import os8 from "os";
3496
+ import path10 from "path";
2615
3497
  import { jwtVerify, importSPKI } from "jose";
2616
3498
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
2617
3499
  var init_license = __esm({
2618
3500
  "src/lib/license.ts"() {
2619
3501
  "use strict";
2620
3502
  init_config();
2621
- LICENSE_PATH = path8.join(EXE_AI_DIR, "license.key");
2622
- CACHE_PATH = path8.join(EXE_AI_DIR, "license-cache.json");
2623
- DEVICE_ID_PATH = path8.join(EXE_AI_DIR, "device-id");
3503
+ LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
3504
+ CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
3505
+ DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
2624
3506
  PLAN_LIMITS = {
2625
3507
  free: { devices: 1, employees: 1, memories: 5e3 },
2626
3508
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -2632,12 +3514,12 @@ var init_license = __esm({
2632
3514
  });
2633
3515
 
2634
3516
  // src/lib/plan-limits.ts
2635
- import { readFileSync as readFileSync8, existsSync as existsSync8 } from "fs";
2636
- import path9 from "path";
3517
+ import { readFileSync as readFileSync9, existsSync as existsSync10 } from "fs";
3518
+ import path11 from "path";
2637
3519
  function getLicenseSync() {
2638
3520
  try {
2639
- if (!existsSync8(CACHE_PATH2)) return freeLicense();
2640
- const raw = JSON.parse(readFileSync8(CACHE_PATH2, "utf8"));
3521
+ if (!existsSync10(CACHE_PATH2)) return freeLicense();
3522
+ const raw = JSON.parse(readFileSync9(CACHE_PATH2, "utf8"));
2641
3523
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
2642
3524
  const parts = raw.token.split(".");
2643
3525
  if (parts.length !== 3) return freeLicense();
@@ -2675,8 +3557,8 @@ function assertEmployeeLimitSync(rosterPath) {
2675
3557
  const filePath = rosterPath ?? EMPLOYEES_PATH;
2676
3558
  let count = 0;
2677
3559
  try {
2678
- if (existsSync8(filePath)) {
2679
- const raw = readFileSync8(filePath, "utf8");
3560
+ if (existsSync10(filePath)) {
3561
+ const raw = readFileSync9(filePath, "utf8");
2680
3562
  const employees = JSON.parse(raw);
2681
3563
  count = Array.isArray(employees) ? employees.length : 0;
2682
3564
  }
@@ -2705,29 +3587,69 @@ var init_plan_limits = __esm({
2705
3587
  this.name = "PlanLimitError";
2706
3588
  }
2707
3589
  };
2708
- CACHE_PATH2 = path9.join(EXE_AI_DIR, "license-cache.json");
3590
+ CACHE_PATH2 = path11.join(EXE_AI_DIR, "license-cache.json");
3591
+ }
3592
+ });
3593
+
3594
+ // src/lib/task-scope.ts
3595
+ var task_scope_exports = {};
3596
+ __export(task_scope_exports, {
3597
+ getCurrentSessionScope: () => getCurrentSessionScope,
3598
+ sessionScopeFilter: () => sessionScopeFilter,
3599
+ strictSessionScopeFilter: () => strictSessionScopeFilter
3600
+ });
3601
+ function getCurrentSessionScope() {
3602
+ try {
3603
+ return resolveExeSession();
3604
+ } catch {
3605
+ return null;
3606
+ }
3607
+ }
3608
+ function sessionScopeFilter(sessionScope, tableAlias) {
3609
+ const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
3610
+ if (!scope) return { sql: "", args: [] };
3611
+ const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
3612
+ return {
3613
+ sql: ` AND (${col} IS NULL OR ${col} = ?)`,
3614
+ args: [scope]
3615
+ };
3616
+ }
3617
+ function strictSessionScopeFilter(sessionScope, tableAlias) {
3618
+ const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
3619
+ if (!scope) return { sql: "", args: [] };
3620
+ const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
3621
+ return {
3622
+ sql: ` AND ${col} = ?`,
3623
+ args: [scope]
3624
+ };
3625
+ }
3626
+ var init_task_scope = __esm({
3627
+ "src/lib/task-scope.ts"() {
3628
+ "use strict";
3629
+ init_tmux_routing();
2709
3630
  }
2710
3631
  });
2711
3632
 
2712
3633
  // src/lib/notifications.ts
2713
- import crypto from "crypto";
2714
- import path10 from "path";
2715
- import os7 from "os";
3634
+ import crypto2 from "crypto";
3635
+ import path12 from "path";
3636
+ import os9 from "os";
2716
3637
  import {
2717
- readFileSync as readFileSync9,
3638
+ readFileSync as readFileSync10,
2718
3639
  readdirSync,
2719
3640
  unlinkSync as unlinkSync3,
2720
- existsSync as existsSync9,
3641
+ existsSync as existsSync11,
2721
3642
  rmdirSync
2722
3643
  } from "fs";
2723
3644
  async function writeNotification(notification) {
2724
3645
  try {
2725
3646
  const client = getClient();
2726
- const id = crypto.randomUUID();
3647
+ const id = crypto2.randomUUID();
2727
3648
  const now = (/* @__PURE__ */ new Date()).toISOString();
3649
+ const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
2728
3650
  await client.execute({
2729
- sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
2730
- VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
3651
+ sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
3652
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
2731
3653
  args: [
2732
3654
  id,
2733
3655
  notification.agentId,
@@ -2736,6 +3658,7 @@ async function writeNotification(notification) {
2736
3658
  notification.project,
2737
3659
  notification.summary,
2738
3660
  notification.taskFile ?? null,
3661
+ sessionScope,
2739
3662
  now
2740
3663
  ]
2741
3664
  });
@@ -2744,12 +3667,14 @@ async function writeNotification(notification) {
2744
3667
  `);
2745
3668
  }
2746
3669
  }
2747
- async function markAsReadByTaskFile(taskFile) {
3670
+ async function markAsReadByTaskFile(taskFile, sessionScope) {
2748
3671
  try {
2749
3672
  const client = getClient();
3673
+ const scope = strictSessionScopeFilter(sessionScope);
2750
3674
  await client.execute({
2751
- sql: "UPDATE notifications SET read = 1 WHERE task_file = ? AND read = 0",
2752
- args: [taskFile]
3675
+ sql: `UPDATE notifications SET read = 1
3676
+ WHERE task_file = ? AND read = 0${scope.sql}`,
3677
+ args: [taskFile, ...scope.args]
2753
3678
  });
2754
3679
  } catch {
2755
3680
  }
@@ -2758,11 +3683,12 @@ var init_notifications = __esm({
2758
3683
  "src/lib/notifications.ts"() {
2759
3684
  "use strict";
2760
3685
  init_database();
3686
+ init_task_scope();
2761
3687
  }
2762
3688
  });
2763
3689
 
2764
3690
  // src/lib/session-kill-telemetry.ts
2765
- import crypto2 from "crypto";
3691
+ import crypto3 from "crypto";
2766
3692
  async function recordSessionKill(input) {
2767
3693
  try {
2768
3694
  const client = getClient();
@@ -2772,7 +3698,7 @@ async function recordSessionKill(input) {
2772
3698
  ticks_idle, estimated_tokens_saved)
2773
3699
  VALUES (?, ?, ?, ?, ?, ?, ?)`,
2774
3700
  args: [
2775
- crypto2.randomUUID(),
3701
+ crypto3.randomUUID(),
2776
3702
  input.sessionName,
2777
3703
  input.agentId,
2778
3704
  (/* @__PURE__ */ new Date()).toISOString(),
@@ -2795,35 +3721,6 @@ var init_session_kill_telemetry = __esm({
2795
3721
  }
2796
3722
  });
2797
3723
 
2798
- // src/lib/task-scope.ts
2799
- var task_scope_exports = {};
2800
- __export(task_scope_exports, {
2801
- getCurrentSessionScope: () => getCurrentSessionScope,
2802
- sessionScopeFilter: () => sessionScopeFilter
2803
- });
2804
- function getCurrentSessionScope() {
2805
- try {
2806
- return resolveExeSession();
2807
- } catch {
2808
- return null;
2809
- }
2810
- }
2811
- function sessionScopeFilter(sessionScope, tableAlias) {
2812
- const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
2813
- if (!scope) return { sql: "", args: [] };
2814
- const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
2815
- return {
2816
- sql: ` AND (${col} IS NULL OR ${col} = ?)`,
2817
- args: [scope]
2818
- };
2819
- }
2820
- var init_task_scope = __esm({
2821
- "src/lib/task-scope.ts"() {
2822
- "use strict";
2823
- init_tmux_routing();
2824
- }
2825
- });
2826
-
2827
3724
  // src/lib/state-bus.ts
2828
3725
  var StateBus, orgBus;
2829
3726
  var init_state_bus = __esm({
@@ -2880,12 +3777,12 @@ var init_state_bus = __esm({
2880
3777
  });
2881
3778
 
2882
3779
  // src/lib/tasks-crud.ts
2883
- import crypto3 from "crypto";
2884
- import path11 from "path";
2885
- import os8 from "os";
3780
+ import crypto4 from "crypto";
3781
+ import path13 from "path";
3782
+ import os10 from "os";
2886
3783
  import { execSync as execSync5 } from "child_process";
2887
3784
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
2888
- import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
3785
+ import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
2889
3786
  async function writeCheckpoint(input) {
2890
3787
  const client = getClient();
2891
3788
  const row = await resolveTask(client, input.taskId);
@@ -3001,7 +3898,7 @@ async function resolveTask(client, identifier, scopeSession) {
3001
3898
  }
3002
3899
  async function createTaskCore(input) {
3003
3900
  const client = getClient();
3004
- const id = crypto3.randomUUID();
3901
+ const id = crypto4.randomUUID();
3005
3902
  const now = (/* @__PURE__ */ new Date()).toISOString();
3006
3903
  const slug = slugify(input.title);
3007
3904
  let earlySessionScope = null;
@@ -3060,8 +3957,8 @@ ${laneWarning}` : laneWarning;
3060
3957
  }
3061
3958
  if (input.baseDir) {
3062
3959
  try {
3063
- await mkdir3(path11.join(input.baseDir, "exe", "output"), { recursive: true });
3064
- await mkdir3(path11.join(input.baseDir, "exe", "research"), { recursive: true });
3960
+ await mkdir3(path13.join(input.baseDir, "exe", "output"), { recursive: true });
3961
+ await mkdir3(path13.join(input.baseDir, "exe", "research"), { recursive: true });
3065
3962
  await ensureArchitectureDoc(input.baseDir, input.projectName);
3066
3963
  await ensureGitignoreExe(input.baseDir);
3067
3964
  } catch {
@@ -3097,13 +3994,19 @@ ${laneWarning}` : laneWarning;
3097
3994
  });
3098
3995
  if (input.baseDir) {
3099
3996
  try {
3100
- const EXE_OS_DIR = path11.join(os8.homedir(), ".exe-os");
3101
- const mdPath = path11.join(EXE_OS_DIR, taskFile);
3102
- const mdDir = path11.dirname(mdPath);
3103
- if (!existsSync10(mdDir)) await mkdir3(mdDir, { recursive: true });
3997
+ const EXE_OS_DIR = path13.join(os10.homedir(), ".exe-os");
3998
+ const mdPath = path13.join(EXE_OS_DIR, taskFile);
3999
+ const mdDir = path13.dirname(mdPath);
4000
+ if (!existsSync12(mdDir)) await mkdir3(mdDir, { recursive: true });
3104
4001
  const reviewer = input.reviewer ?? input.assignedBy;
3105
4002
  const mdContent = `# ${input.title}
3106
4003
 
4004
+ ## MANDATORY: When done
4005
+
4006
+ You MUST call update_task with status "done" and a result summary when finished.
4007
+ If you skip this, your reviewer will not know you're done and your work won't be reviewed.
4008
+ Do NOT let a failed commit or any error prevent you from calling update_task(done).
4009
+
3107
4010
  **ID:** ${id}
3108
4011
  **Status:** ${initialStatus}
3109
4012
  **Priority:** ${input.priority}
@@ -3117,12 +4020,6 @@ ${laneWarning}` : laneWarning;
3117
4020
  ## Context
3118
4021
 
3119
4022
  ${input.context}
3120
-
3121
- ## MANDATORY: When done
3122
-
3123
- You MUST call update_task with status "done" and a result summary when finished.
3124
- If you skip this, your reviewer will not know you're done and your work won't be reviewed.
3125
- Do NOT let a failed commit or any error prevent you from calling update_task(done).
3126
4023
  `;
3127
4024
  await writeFile3(mdPath, mdContent, "utf-8");
3128
4025
  } catch (err) {
@@ -3371,7 +4268,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
3371
4268
  await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
3372
4269
  } catch {
3373
4270
  }
3374
- if (input.status === "done" || input.status === "cancelled") {
4271
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
3375
4272
  try {
3376
4273
  const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
3377
4274
  clearQueueForAgent2(String(row.assigned_to));
@@ -3400,9 +4297,9 @@ async function deleteTaskCore(taskId, _baseDir) {
3400
4297
  return { taskFile, assignedTo, assignedBy, taskSlug };
3401
4298
  }
3402
4299
  async function ensureArchitectureDoc(baseDir, projectName) {
3403
- const archPath = path11.join(baseDir, "exe", "ARCHITECTURE.md");
4300
+ const archPath = path13.join(baseDir, "exe", "ARCHITECTURE.md");
3404
4301
  try {
3405
- if (existsSync10(archPath)) return;
4302
+ if (existsSync12(archPath)) return;
3406
4303
  const template = [
3407
4304
  `# ${projectName} \u2014 System Architecture`,
3408
4305
  "",
@@ -3435,10 +4332,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
3435
4332
  }
3436
4333
  }
3437
4334
  async function ensureGitignoreExe(baseDir) {
3438
- const gitignorePath = path11.join(baseDir, ".gitignore");
4335
+ const gitignorePath = path13.join(baseDir, ".gitignore");
3439
4336
  try {
3440
- if (existsSync10(gitignorePath)) {
3441
- const content = readFileSync10(gitignorePath, "utf-8");
4337
+ if (existsSync12(gitignorePath)) {
4338
+ const content = readFileSync11(gitignorePath, "utf-8");
3442
4339
  if (/^\/?exe\/?$/m.test(content)) return;
3443
4340
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
3444
4341
  } else {
@@ -3469,58 +4366,42 @@ var init_tasks_crud = __esm({
3469
4366
  });
3470
4367
 
3471
4368
  // src/lib/tasks-review.ts
3472
- import path12 from "path";
3473
- import { existsSync as existsSync11, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
4369
+ import path14 from "path";
4370
+ import { existsSync as existsSync13, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
3474
4371
  async function countPendingReviews(sessionScope) {
3475
4372
  const client = getClient();
3476
- if (sessionScope) {
3477
- const result2 = await client.execute({
3478
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review' AND (session_scope = ? OR session_scope IS NULL)",
3479
- args: [sessionScope]
3480
- });
3481
- return Number(result2.rows[0]?.cnt) || 0;
3482
- }
4373
+ const scope = strictSessionScopeFilter(
4374
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
4375
+ );
3483
4376
  const result = await client.execute({
3484
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review'",
3485
- args: []
4377
+ sql: `SELECT COUNT(*) as cnt FROM tasks
4378
+ WHERE status = 'needs_review'${scope.sql}`,
4379
+ args: [...scope.args]
3486
4380
  });
3487
4381
  return Number(result.rows[0]?.cnt) || 0;
3488
4382
  }
3489
4383
  async function countNewPendingReviewsSince(sinceIso, sessionScope) {
3490
4384
  const client = getClient();
3491
- if (sessionScope) {
3492
- const result2 = await client.execute({
3493
- sql: `SELECT COUNT(*) as cnt FROM tasks
3494
- WHERE status = 'needs_review' AND updated_at > ?
3495
- AND session_scope = ?`,
3496
- args: [sinceIso, sessionScope]
3497
- });
3498
- return Number(result2.rows[0]?.cnt) || 0;
3499
- }
4385
+ const scope = strictSessionScopeFilter(
4386
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
4387
+ );
3500
4388
  const result = await client.execute({
3501
4389
  sql: `SELECT COUNT(*) as cnt FROM tasks
3502
- WHERE status = 'needs_review' AND updated_at > ?`,
3503
- args: [sinceIso]
4390
+ WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
4391
+ args: [sinceIso, ...scope.args]
3504
4392
  });
3505
4393
  return Number(result.rows[0]?.cnt) || 0;
3506
4394
  }
3507
4395
  async function listPendingReviews(limit, sessionScope) {
3508
4396
  const client = getClient();
3509
- if (sessionScope) {
3510
- const result2 = await client.execute({
3511
- sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
3512
- WHERE status = 'needs_review'
3513
- AND session_scope = ?
3514
- ORDER BY updated_at ASC LIMIT ?`,
3515
- args: [sessionScope, limit]
3516
- });
3517
- return result2.rows;
3518
- }
4397
+ const scope = strictSessionScopeFilter(
4398
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
4399
+ );
3519
4400
  const result = await client.execute({
3520
4401
  sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
3521
- WHERE status = 'needs_review'
4402
+ WHERE status = 'needs_review'${scope.sql}
3522
4403
  ORDER BY updated_at ASC LIMIT ?`,
3523
- args: [limit]
4404
+ args: [...scope.args, limit]
3524
4405
  });
3525
4406
  return result.rows;
3526
4407
  }
@@ -3532,7 +4413,7 @@ async function cleanupOrphanedReviews() {
3532
4413
  WHERE status IN ('open', 'needs_review', 'in_progress')
3533
4414
  AND assigned_by = 'system'
3534
4415
  AND title LIKE 'Review:%'
3535
- AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
4416
+ AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
3536
4417
  args: [now]
3537
4418
  });
3538
4419
  const r1b = await client.execute({
@@ -3651,11 +4532,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
3651
4532
  );
3652
4533
  }
3653
4534
  try {
3654
- const cacheDir = path12.join(EXE_AI_DIR, "session-cache");
3655
- if (existsSync11(cacheDir)) {
4535
+ const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
4536
+ if (existsSync13(cacheDir)) {
3656
4537
  for (const f of readdirSync2(cacheDir)) {
3657
4538
  if (f.startsWith("review-notified-")) {
3658
- unlinkSync4(path12.join(cacheDir, f));
4539
+ unlinkSync4(path14.join(cacheDir, f));
3659
4540
  }
3660
4541
  }
3661
4542
  }
@@ -3672,11 +4553,12 @@ var init_tasks_review = __esm({
3672
4553
  init_tmux_routing();
3673
4554
  init_session_key();
3674
4555
  init_state_bus();
4556
+ init_task_scope();
3675
4557
  }
3676
4558
  });
3677
4559
 
3678
4560
  // src/lib/tasks-chain.ts
3679
- import path13 from "path";
4561
+ import path15 from "path";
3680
4562
  import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
3681
4563
  async function cascadeUnblock(taskId, baseDir, now) {
3682
4564
  const client = getClient();
@@ -3693,7 +4575,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
3693
4575
  });
3694
4576
  for (const ur of unblockedRows.rows) {
3695
4577
  try {
3696
- const ubFile = path13.join(baseDir, String(ur.task_file));
4578
+ const ubFile = path15.join(baseDir, String(ur.task_file));
3697
4579
  let ubContent = await readFile3(ubFile, "utf-8");
3698
4580
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
3699
4581
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -3728,7 +4610,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
3728
4610
  const scScope = sessionScopeFilter();
3729
4611
  const remaining = await client.execute({
3730
4612
  sql: `SELECT COUNT(*) as cnt FROM tasks
3731
- WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
4613
+ WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
3732
4614
  args: [parentTaskId, ...scScope.args]
3733
4615
  });
3734
4616
  const cnt = Number(remaining.rows[0]?.cnt ?? 1);
@@ -3762,7 +4644,7 @@ var init_tasks_chain = __esm({
3762
4644
 
3763
4645
  // src/lib/project-name.ts
3764
4646
  import { execSync as execSync6 } from "child_process";
3765
- import path14 from "path";
4647
+ import path16 from "path";
3766
4648
  function getProjectName(cwd) {
3767
4649
  const dir = cwd ?? process.cwd();
3768
4650
  if (_cached2 && _cachedCwd === dir) return _cached2;
@@ -3775,7 +4657,7 @@ function getProjectName(cwd) {
3775
4657
  timeout: 2e3,
3776
4658
  stdio: ["pipe", "pipe", "pipe"]
3777
4659
  }).trim();
3778
- repoRoot = path14.dirname(gitCommonDir);
4660
+ repoRoot = path16.dirname(gitCommonDir);
3779
4661
  } catch {
3780
4662
  repoRoot = execSync6("git rev-parse --show-toplevel", {
3781
4663
  cwd: dir,
@@ -3784,11 +4666,11 @@ function getProjectName(cwd) {
3784
4666
  stdio: ["pipe", "pipe", "pipe"]
3785
4667
  }).trim();
3786
4668
  }
3787
- _cached2 = path14.basename(repoRoot);
4669
+ _cached2 = path16.basename(repoRoot);
3788
4670
  _cachedCwd = dir;
3789
4671
  return _cached2;
3790
4672
  } catch {
3791
- _cached2 = path14.basename(dir);
4673
+ _cached2 = path16.basename(dir);
3792
4674
  _cachedCwd = dir;
3793
4675
  return _cached2;
3794
4676
  }
@@ -3938,10 +4820,10 @@ __export(behaviors_exports, {
3938
4820
  listBehaviorsByDomain: () => listBehaviorsByDomain,
3939
4821
  storeBehavior: () => storeBehavior
3940
4822
  });
3941
- import crypto4 from "crypto";
4823
+ import crypto5 from "crypto";
3942
4824
  async function storeBehavior(opts) {
3943
4825
  const client = getClient();
3944
- const id = crypto4.randomUUID();
4826
+ const id = crypto5.randomUUID();
3945
4827
  const now = (/* @__PURE__ */ new Date()).toISOString();
3946
4828
  await client.execute({
3947
4829
  sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
@@ -4025,7 +4907,7 @@ __export(skill_learning_exports, {
4025
4907
  storeTrajectory: () => storeTrajectory,
4026
4908
  sweepTrajectories: () => sweepTrajectories
4027
4909
  });
4028
- import crypto5 from "crypto";
4910
+ import crypto6 from "crypto";
4029
4911
  async function extractTrajectory(taskId, agentId) {
4030
4912
  const client = getClient();
4031
4913
  const result = await client.execute({
@@ -4054,11 +4936,11 @@ async function extractTrajectory(taskId, agentId) {
4054
4936
  return signature;
4055
4937
  }
4056
4938
  function hashSignature(signature) {
4057
- return crypto5.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
4939
+ return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
4058
4940
  }
4059
4941
  async function storeTrajectory(opts) {
4060
4942
  const client = getClient();
4061
- const id = crypto5.randomUUID();
4943
+ const id = crypto6.randomUUID();
4062
4944
  const now = (/* @__PURE__ */ new Date()).toISOString();
4063
4945
  const signatureHash = hashSignature(opts.signature);
4064
4946
  await client.execute({
@@ -4323,8 +5205,8 @@ __export(tasks_exports, {
4323
5205
  updateTaskStatus: () => updateTaskStatus,
4324
5206
  writeCheckpoint: () => writeCheckpoint
4325
5207
  });
4326
- import path15 from "path";
4327
- import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
5208
+ import path17 from "path";
5209
+ import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
4328
5210
  async function createTask(input) {
4329
5211
  const result = await createTaskCore(input);
4330
5212
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -4343,12 +5225,12 @@ async function updateTask(input) {
4343
5225
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
4344
5226
  try {
4345
5227
  const agent = String(row.assigned_to);
4346
- const cacheDir = path15.join(EXE_AI_DIR, "session-cache");
4347
- const cachePath = path15.join(cacheDir, `current-task-${agent}.json`);
5228
+ const cacheDir = path17.join(EXE_AI_DIR, "session-cache");
5229
+ const cachePath = path17.join(cacheDir, `current-task-${agent}.json`);
4348
5230
  if (input.status === "in_progress") {
4349
5231
  mkdirSync5(cacheDir, { recursive: true });
4350
- writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
4351
- } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
5232
+ writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
5233
+ } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
4352
5234
  try {
4353
5235
  unlinkSync5(cachePath);
4354
5236
  } catch {
@@ -4356,10 +5238,10 @@ async function updateTask(input) {
4356
5238
  }
4357
5239
  } catch {
4358
5240
  }
4359
- if (input.status === "done") {
5241
+ if (input.status === "done" || input.status === "closed") {
4360
5242
  await cleanupReviewFile(row, taskFile, input.baseDir);
4361
5243
  }
4362
- if (input.status === "done" || input.status === "cancelled") {
5244
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
4363
5245
  try {
4364
5246
  const client = getClient();
4365
5247
  const taskTitle = String(row.title);
@@ -4375,7 +5257,7 @@ async function updateTask(input) {
4375
5257
  if (!isCoordinatorName(assignedAgent)) {
4376
5258
  try {
4377
5259
  const draftClient = getClient();
4378
- if (input.status === "done") {
5260
+ if (input.status === "done" || input.status === "closed") {
4379
5261
  await draftClient.execute({
4380
5262
  sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
4381
5263
  args: [assignedAgent]
@@ -4392,7 +5274,7 @@ async function updateTask(input) {
4392
5274
  try {
4393
5275
  const client = getClient();
4394
5276
  const cascaded = await client.execute({
4395
- sql: `UPDATE tasks SET status = 'done', updated_at = ?
5277
+ sql: `UPDATE tasks SET status = 'closed', updated_at = ?
4396
5278
  WHERE parent_task_id = ? AND status = 'needs_review'`,
4397
5279
  args: [now, taskId]
4398
5280
  });
@@ -4405,14 +5287,14 @@ async function updateTask(input) {
4405
5287
  } catch {
4406
5288
  }
4407
5289
  }
4408
- const isTerminal = input.status === "done" || input.status === "needs_review";
5290
+ const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
4409
5291
  if (isTerminal) {
4410
5292
  const isCoordinator = isCoordinatorName(String(row.assigned_to));
4411
5293
  if (!isCoordinator) {
4412
5294
  notifyTaskDone();
4413
5295
  }
4414
5296
  await markTaskNotificationsRead(taskFile);
4415
- if (input.status === "done") {
5297
+ if (input.status === "done" || input.status === "closed") {
4416
5298
  try {
4417
5299
  await cascadeUnblock(taskId, input.baseDir, now);
4418
5300
  } catch {
@@ -4432,7 +5314,7 @@ async function updateTask(input) {
4432
5314
  }
4433
5315
  }
4434
5316
  }
4435
- if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
5317
+ if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
4436
5318
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
4437
5319
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
4438
5320
  taskId,
@@ -4804,6 +5686,7 @@ __export(tmux_routing_exports, {
4804
5686
  isEmployeeAlive: () => isEmployeeAlive,
4805
5687
  isExeSession: () => isExeSession,
4806
5688
  isSessionBusy: () => isSessionBusy,
5689
+ notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
4807
5690
  notifyParentExe: () => notifyParentExe,
4808
5691
  parseParentExe: () => parseParentExe,
4809
5692
  registerParentExe: () => registerParentExe,
@@ -4814,13 +5697,13 @@ __export(tmux_routing_exports, {
4814
5697
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
4815
5698
  });
4816
5699
  import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
4817
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync12, appendFileSync, readdirSync as readdirSync3 } from "fs";
4818
- import path16 from "path";
4819
- import os9 from "os";
5700
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, existsSync as existsSync14, appendFileSync, readdirSync as readdirSync3 } from "fs";
5701
+ import path18 from "path";
5702
+ import os11 from "os";
4820
5703
  import { fileURLToPath as fileURLToPath2 } from "url";
4821
5704
  import { unlinkSync as unlinkSync6 } from "fs";
4822
5705
  function spawnLockPath(sessionName) {
4823
- return path16.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
5706
+ return path18.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
4824
5707
  }
4825
5708
  function isProcessAlive(pid) {
4826
5709
  try {
@@ -4831,13 +5714,13 @@ function isProcessAlive(pid) {
4831
5714
  }
4832
5715
  }
4833
5716
  function acquireSpawnLock2(sessionName) {
4834
- if (!existsSync12(SPAWN_LOCK_DIR)) {
5717
+ if (!existsSync14(SPAWN_LOCK_DIR)) {
4835
5718
  mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
4836
5719
  }
4837
5720
  const lockFile = spawnLockPath(sessionName);
4838
- if (existsSync12(lockFile)) {
5721
+ if (existsSync14(lockFile)) {
4839
5722
  try {
4840
- const lock = JSON.parse(readFileSync11(lockFile, "utf8"));
5723
+ const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
4841
5724
  const age = Date.now() - lock.timestamp;
4842
5725
  if (isProcessAlive(lock.pid) && age < 6e4) {
4843
5726
  return false;
@@ -4845,7 +5728,7 @@ function acquireSpawnLock2(sessionName) {
4845
5728
  } catch {
4846
5729
  }
4847
5730
  }
4848
- writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
5731
+ writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
4849
5732
  return true;
4850
5733
  }
4851
5734
  function releaseSpawnLock2(sessionName) {
@@ -4857,13 +5740,13 @@ function releaseSpawnLock2(sessionName) {
4857
5740
  function resolveBehaviorsExporterScript() {
4858
5741
  try {
4859
5742
  const thisFile = fileURLToPath2(import.meta.url);
4860
- const scriptPath = path16.join(
4861
- path16.dirname(thisFile),
5743
+ const scriptPath = path18.join(
5744
+ path18.dirname(thisFile),
4862
5745
  "..",
4863
5746
  "bin",
4864
5747
  "exe-export-behaviors.js"
4865
5748
  );
4866
- return existsSync12(scriptPath) ? scriptPath : null;
5749
+ return existsSync14(scriptPath) ? scriptPath : null;
4867
5750
  } catch {
4868
5751
  return null;
4869
5752
  }
@@ -4929,12 +5812,12 @@ function extractRootExe(name) {
4929
5812
  return parts.length > 0 ? parts[parts.length - 1] : null;
4930
5813
  }
4931
5814
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
4932
- if (!existsSync12(SESSION_CACHE)) {
5815
+ if (!existsSync14(SESSION_CACHE)) {
4933
5816
  mkdirSync6(SESSION_CACHE, { recursive: true });
4934
5817
  }
4935
5818
  const rootExe = extractRootExe(parentExe) ?? parentExe;
4936
- const filePath = path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
4937
- writeFileSync7(filePath, JSON.stringify({
5819
+ const filePath = path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
5820
+ writeFileSync8(filePath, JSON.stringify({
4938
5821
  parentExe: rootExe,
4939
5822
  dispatchedBy: dispatchedBy || rootExe,
4940
5823
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -4942,7 +5825,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
4942
5825
  }
4943
5826
  function getParentExe(sessionKey) {
4944
5827
  try {
4945
- const data = JSON.parse(readFileSync11(path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
5828
+ const data = JSON.parse(readFileSync12(path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4946
5829
  return data.parentExe || null;
4947
5830
  } catch {
4948
5831
  return null;
@@ -4950,8 +5833,8 @@ function getParentExe(sessionKey) {
4950
5833
  }
4951
5834
  function getDispatchedBy(sessionKey) {
4952
5835
  try {
4953
- const data = JSON.parse(readFileSync11(
4954
- path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
5836
+ const data = JSON.parse(readFileSync12(
5837
+ path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
4955
5838
  "utf8"
4956
5839
  ));
4957
5840
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -5021,8 +5904,8 @@ async function verifyPaneAtCapacity(sessionName) {
5021
5904
  }
5022
5905
  function readDebounceState() {
5023
5906
  try {
5024
- if (!existsSync12(DEBOUNCE_FILE)) return {};
5025
- const raw = JSON.parse(readFileSync11(DEBOUNCE_FILE, "utf8"));
5907
+ if (!existsSync14(DEBOUNCE_FILE)) return {};
5908
+ const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
5026
5909
  const state = {};
5027
5910
  for (const [key, val] of Object.entries(raw)) {
5028
5911
  if (typeof val === "number") {
@@ -5038,8 +5921,8 @@ function readDebounceState() {
5038
5921
  }
5039
5922
  function writeDebounceState(state) {
5040
5923
  try {
5041
- if (!existsSync12(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
5042
- writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
5924
+ if (!existsSync14(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
5925
+ writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
5043
5926
  } catch {
5044
5927
  }
5045
5928
  }
@@ -5137,8 +6020,8 @@ function sendIntercom(targetSession) {
5137
6020
  try {
5138
6021
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
5139
6022
  const agent = baseAgentName(rawAgent);
5140
- const markerPath = path16.join(SESSION_CACHE, `current-task-${agent}.json`);
5141
- if (existsSync12(markerPath)) {
6023
+ const markerPath = path18.join(SESSION_CACHE, `current-task-${agent}.json`);
6024
+ if (existsSync14(markerPath)) {
5142
6025
  logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
5143
6026
  return "debounced";
5144
6027
  }
@@ -5147,8 +6030,8 @@ function sendIntercom(targetSession) {
5147
6030
  try {
5148
6031
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
5149
6032
  const agent = baseAgentName(rawAgent);
5150
- const taskDir = path16.join(process.cwd(), "exe", agent);
5151
- if (existsSync12(taskDir)) {
6033
+ const taskDir = path18.join(process.cwd(), "exe", agent);
6034
+ if (existsSync14(taskDir)) {
5152
6035
  const files = readdirSync3(taskDir).filter(
5153
6036
  (f) => f.endsWith(".md") && f !== "DONE.txt"
5154
6037
  );
@@ -5208,6 +6091,21 @@ function notifyParentExe(sessionKey) {
5208
6091
  }
5209
6092
  return true;
5210
6093
  }
6094
+ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
6095
+ const transport = getTransport();
6096
+ try {
6097
+ const sessions = transport.listSessions();
6098
+ if (!sessions.includes(coordinatorSession)) return false;
6099
+ execSync7(
6100
+ `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
6101
+ { timeout: 3e3 }
6102
+ );
6103
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
6104
+ return true;
6105
+ } catch {
6106
+ return false;
6107
+ }
6108
+ }
5211
6109
  function ensureEmployee(employeeName, exeSession, projectDir, opts) {
5212
6110
  if (isCoordinatorName(employeeName)) {
5213
6111
  return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
@@ -5281,26 +6179,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5281
6179
  const transport = getTransport();
5282
6180
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
5283
6181
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
5284
- const logDir = path16.join(os9.homedir(), ".exe-os", "session-logs");
5285
- const logFile = path16.join(logDir, `${instanceLabel}-${Date.now()}.log`);
5286
- if (!existsSync12(logDir)) {
6182
+ const logDir = path18.join(os11.homedir(), ".exe-os", "session-logs");
6183
+ const logFile = path18.join(logDir, `${instanceLabel}-${Date.now()}.log`);
6184
+ if (!existsSync14(logDir)) {
5287
6185
  mkdirSync6(logDir, { recursive: true });
5288
6186
  }
5289
6187
  transport.kill(sessionName);
5290
6188
  let cleanupSuffix = "";
5291
6189
  try {
5292
6190
  const thisFile = fileURLToPath2(import.meta.url);
5293
- const cleanupScript = path16.join(path16.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
5294
- if (existsSync12(cleanupScript)) {
6191
+ const cleanupScript = path18.join(path18.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
6192
+ if (existsSync14(cleanupScript)) {
5295
6193
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
5296
6194
  }
5297
6195
  } catch {
5298
6196
  }
5299
6197
  try {
5300
- const claudeJsonPath = path16.join(os9.homedir(), ".claude.json");
6198
+ const claudeJsonPath = path18.join(os11.homedir(), ".claude.json");
5301
6199
  let claudeJson = {};
5302
6200
  try {
5303
- claudeJson = JSON.parse(readFileSync11(claudeJsonPath, "utf8"));
6201
+ claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
5304
6202
  } catch {
5305
6203
  }
5306
6204
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -5308,17 +6206,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5308
6206
  const trustDir = opts?.cwd ?? projectDir;
5309
6207
  if (!projects[trustDir]) projects[trustDir] = {};
5310
6208
  projects[trustDir].hasTrustDialogAccepted = true;
5311
- writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
6209
+ writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
5312
6210
  } catch {
5313
6211
  }
5314
6212
  try {
5315
- const settingsDir = path16.join(os9.homedir(), ".claude", "projects");
6213
+ const settingsDir = path18.join(os11.homedir(), ".claude", "projects");
5316
6214
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
5317
- const projSettingsDir = path16.join(settingsDir, normalizedKey);
5318
- const settingsPath = path16.join(projSettingsDir, "settings.json");
6215
+ const projSettingsDir = path18.join(settingsDir, normalizedKey);
6216
+ const settingsPath = path18.join(projSettingsDir, "settings.json");
5319
6217
  let settings = {};
5320
6218
  try {
5321
- settings = JSON.parse(readFileSync11(settingsPath, "utf8"));
6219
+ settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
5322
6220
  } catch {
5323
6221
  }
5324
6222
  const perms = settings.permissions ?? {};
@@ -5347,7 +6245,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5347
6245
  perms.allow = allow;
5348
6246
  settings.permissions = perms;
5349
6247
  mkdirSync6(projSettingsDir, { recursive: true });
5350
- writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
6248
+ writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
5351
6249
  }
5352
6250
  } catch {
5353
6251
  }
@@ -5362,8 +6260,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5362
6260
  let behaviorsFlag = "";
5363
6261
  let legacyFallbackWarned = false;
5364
6262
  if (!useExeAgent && !useBinSymlink) {
5365
- const identityPath = path16.join(
5366
- os9.homedir(),
6263
+ const identityPath = path18.join(
6264
+ os11.homedir(),
5367
6265
  ".exe-os",
5368
6266
  "identity",
5369
6267
  `${employeeName}.md`
@@ -5372,13 +6270,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5372
6270
  const hasAgentFlag = claudeSupportsAgentFlag();
5373
6271
  if (hasAgentFlag) {
5374
6272
  identityFlag = ` --agent ${employeeName}`;
5375
- } else if (existsSync12(identityPath)) {
6273
+ } else if (existsSync14(identityPath)) {
5376
6274
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
5377
6275
  legacyFallbackWarned = true;
5378
6276
  }
5379
6277
  const behaviorsFile = exportBehaviorsSync(
5380
6278
  employeeName,
5381
- path16.basename(spawnCwd),
6279
+ path18.basename(spawnCwd),
5382
6280
  sessionName
5383
6281
  );
5384
6282
  if (behaviorsFile) {
@@ -5393,16 +6291,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5393
6291
  }
5394
6292
  let sessionContextFlag = "";
5395
6293
  try {
5396
- const ctxDir = path16.join(os9.homedir(), ".exe-os", "session-cache");
6294
+ const ctxDir = path18.join(os11.homedir(), ".exe-os", "session-cache");
5397
6295
  mkdirSync6(ctxDir, { recursive: true });
5398
- const ctxFile = path16.join(ctxDir, `session-context-${sessionName}.md`);
6296
+ const ctxFile = path18.join(ctxDir, `session-context-${sessionName}.md`);
5399
6297
  const ctxContent = [
5400
6298
  `## Session Context`,
5401
6299
  `You are running in tmux session: ${sessionName}.`,
5402
6300
  `Your parent coordinator session is ${exeSession}.`,
5403
6301
  `Your employees (if any) use the -${exeSession} suffix.`
5404
6302
  ].join("\n");
5405
- writeFileSync7(ctxFile, ctxContent);
6303
+ writeFileSync8(ctxFile, ctxContent);
5406
6304
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
5407
6305
  } catch {
5408
6306
  }
@@ -5479,8 +6377,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5479
6377
  transport.pipeLog(sessionName, logFile);
5480
6378
  try {
5481
6379
  const mySession = getMySession();
5482
- const dispatchInfo = path16.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
5483
- writeFileSync7(dispatchInfo, JSON.stringify({
6380
+ const dispatchInfo = path18.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
6381
+ writeFileSync8(dispatchInfo, JSON.stringify({
5484
6382
  dispatchedBy: mySession,
5485
6383
  rootExe: exeSession,
5486
6384
  provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
@@ -5554,15 +6452,15 @@ var init_tmux_routing = __esm({
5554
6452
  init_intercom_queue();
5555
6453
  init_plan_limits();
5556
6454
  init_employees();
5557
- SPAWN_LOCK_DIR = path16.join(os9.homedir(), ".exe-os", "spawn-locks");
5558
- SESSION_CACHE = path16.join(os9.homedir(), ".exe-os", "session-cache");
6455
+ SPAWN_LOCK_DIR = path18.join(os11.homedir(), ".exe-os", "spawn-locks");
6456
+ SESSION_CACHE = path18.join(os11.homedir(), ".exe-os", "session-cache");
5559
6457
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
5560
6458
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
5561
6459
  VERIFY_PANE_LINES = 200;
5562
6460
  INTERCOM_DEBOUNCE_MS = 3e4;
5563
6461
  CODEX_DEBOUNCE_MS = 12e4;
5564
- INTERCOM_LOG2 = path16.join(os9.homedir(), ".exe-os", "intercom.log");
5565
- DEBOUNCE_FILE = path16.join(SESSION_CACHE, "intercom-debounce.json");
6462
+ INTERCOM_LOG2 = path18.join(os11.homedir(), ".exe-os", "intercom.log");
6463
+ DEBOUNCE_FILE = path18.join(SESSION_CACHE, "intercom-debounce.json");
5566
6464
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
5567
6465
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
5568
6466
  }
@@ -5579,14 +6477,14 @@ var init_memory = __esm({
5579
6477
 
5580
6478
  // src/lib/keychain.ts
5581
6479
  import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
5582
- import { existsSync as existsSync13 } from "fs";
5583
- import path17 from "path";
5584
- import os10 from "os";
6480
+ import { existsSync as existsSync15 } from "fs";
6481
+ import path19 from "path";
6482
+ import os12 from "os";
5585
6483
  function getKeyDir() {
5586
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path17.join(os10.homedir(), ".exe-os");
6484
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path19.join(os12.homedir(), ".exe-os");
5587
6485
  }
5588
6486
  function getKeyPath() {
5589
- return path17.join(getKeyDir(), "master.key");
6487
+ return path19.join(getKeyDir(), "master.key");
5590
6488
  }
5591
6489
  async function tryKeytar() {
5592
6490
  try {
@@ -5607,9 +6505,9 @@ async function getMasterKey() {
5607
6505
  }
5608
6506
  }
5609
6507
  const keyPath = getKeyPath();
5610
- if (!existsSync13(keyPath)) {
6508
+ if (!existsSync15(keyPath)) {
5611
6509
  process.stderr.write(
5612
- `[keychain] Key not found at ${keyPath} (HOME=${os10.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
6510
+ `[keychain] Key not found at ${keyPath} (HOME=${os12.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
5613
6511
  `
5614
6512
  );
5615
6513
  return null;
@@ -5639,6 +6537,7 @@ var shard_manager_exports = {};
5639
6537
  __export(shard_manager_exports, {
5640
6538
  disposeShards: () => disposeShards,
5641
6539
  ensureShardSchema: () => ensureShardSchema,
6540
+ getOpenShardCount: () => getOpenShardCount,
5642
6541
  getReadyShardClient: () => getReadyShardClient,
5643
6542
  getShardClient: () => getShardClient,
5644
6543
  getShardsDir: () => getShardsDir,
@@ -5647,15 +6546,18 @@ __export(shard_manager_exports, {
5647
6546
  listShards: () => listShards,
5648
6547
  shardExists: () => shardExists
5649
6548
  });
5650
- import path18 from "path";
5651
- import { existsSync as existsSync14, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
6549
+ import path20 from "path";
6550
+ import { existsSync as existsSync16, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
5652
6551
  import { createClient as createClient2 } from "@libsql/client";
5653
6552
  function initShardManager(encryptionKey) {
5654
6553
  _encryptionKey = encryptionKey;
5655
- if (!existsSync14(SHARDS_DIR)) {
6554
+ if (!existsSync16(SHARDS_DIR)) {
5656
6555
  mkdirSync7(SHARDS_DIR, { recursive: true });
5657
6556
  }
5658
6557
  _shardingEnabled = true;
6558
+ if (_evictionTimer) clearInterval(_evictionTimer);
6559
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
6560
+ _evictionTimer.unref();
5659
6561
  }
5660
6562
  function isShardingEnabled() {
5661
6563
  return _shardingEnabled;
@@ -5672,21 +6574,28 @@ function getShardClient(projectName) {
5672
6574
  throw new Error(`Invalid project name for shard: "${projectName}"`);
5673
6575
  }
5674
6576
  const cached = _shards.get(safeName);
5675
- if (cached) return cached;
5676
- const dbPath = path18.join(SHARDS_DIR, `${safeName}.db`);
6577
+ if (cached) {
6578
+ _shardLastAccess.set(safeName, Date.now());
6579
+ return cached;
6580
+ }
6581
+ while (_shards.size >= MAX_OPEN_SHARDS) {
6582
+ evictLRU();
6583
+ }
6584
+ const dbPath = path20.join(SHARDS_DIR, `${safeName}.db`);
5677
6585
  const client = createClient2({
5678
6586
  url: `file:${dbPath}`,
5679
6587
  encryptionKey: _encryptionKey
5680
6588
  });
5681
6589
  _shards.set(safeName, client);
6590
+ _shardLastAccess.set(safeName, Date.now());
5682
6591
  return client;
5683
6592
  }
5684
6593
  function shardExists(projectName) {
5685
6594
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
5686
- return existsSync14(path18.join(SHARDS_DIR, `${safeName}.db`));
6595
+ return existsSync16(path20.join(SHARDS_DIR, `${safeName}.db`));
5687
6596
  }
5688
6597
  function listShards() {
5689
- if (!existsSync14(SHARDS_DIR)) return [];
6598
+ if (!existsSync16(SHARDS_DIR)) return [];
5690
6599
  return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
5691
6600
  }
5692
6601
  async function ensureShardSchema(client) {
@@ -5738,6 +6647,8 @@ async function ensureShardSchema(client) {
5738
6647
  for (const col of [
5739
6648
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
5740
6649
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
6650
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
6651
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
5741
6652
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
5742
6653
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
5743
6654
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -5760,7 +6671,23 @@ async function ensureShardSchema(client) {
5760
6671
  // MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
5761
6672
  "ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
5762
6673
  "ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
5763
- "ALTER TABLE memories ADD COLUMN trajectory TEXT"
6674
+ "ALTER TABLE memories ADD COLUMN trajectory TEXT",
6675
+ // Metadata enrichment columns (must match database.ts)
6676
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
6677
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
6678
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
6679
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
6680
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
6681
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
6682
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
6683
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
6684
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
6685
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
6686
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
6687
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
6688
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
6689
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
6690
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
5764
6691
  ]) {
5765
6692
  try {
5766
6693
  await client.execute(col);
@@ -5859,21 +6786,69 @@ async function getReadyShardClient(projectName) {
5859
6786
  await ensureShardSchema(client);
5860
6787
  return client;
5861
6788
  }
6789
+ function evictLRU() {
6790
+ let oldest = null;
6791
+ let oldestTime = Infinity;
6792
+ for (const [name, time] of _shardLastAccess) {
6793
+ if (time < oldestTime) {
6794
+ oldestTime = time;
6795
+ oldest = name;
6796
+ }
6797
+ }
6798
+ if (oldest) {
6799
+ const client = _shards.get(oldest);
6800
+ if (client) {
6801
+ client.close();
6802
+ }
6803
+ _shards.delete(oldest);
6804
+ _shardLastAccess.delete(oldest);
6805
+ }
6806
+ }
6807
+ function evictIdleShards() {
6808
+ const now = Date.now();
6809
+ const toEvict = [];
6810
+ for (const [name, lastAccess] of _shardLastAccess) {
6811
+ if (now - lastAccess > SHARD_IDLE_MS) {
6812
+ toEvict.push(name);
6813
+ }
6814
+ }
6815
+ for (const name of toEvict) {
6816
+ const client = _shards.get(name);
6817
+ if (client) {
6818
+ client.close();
6819
+ }
6820
+ _shards.delete(name);
6821
+ _shardLastAccess.delete(name);
6822
+ }
6823
+ }
6824
+ function getOpenShardCount() {
6825
+ return _shards.size;
6826
+ }
5862
6827
  function disposeShards() {
6828
+ if (_evictionTimer) {
6829
+ clearInterval(_evictionTimer);
6830
+ _evictionTimer = null;
6831
+ }
5863
6832
  for (const [, client] of _shards) {
5864
6833
  client.close();
5865
6834
  }
5866
6835
  _shards.clear();
6836
+ _shardLastAccess.clear();
5867
6837
  _shardingEnabled = false;
5868
6838
  _encryptionKey = null;
5869
6839
  }
5870
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
6840
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
5871
6841
  var init_shard_manager = __esm({
5872
6842
  "src/lib/shard-manager.ts"() {
5873
6843
  "use strict";
5874
6844
  init_config();
5875
- SHARDS_DIR = path18.join(EXE_AI_DIR, "shards");
6845
+ SHARDS_DIR = path20.join(EXE_AI_DIR, "shards");
6846
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
6847
+ MAX_OPEN_SHARDS = 10;
6848
+ EVICTION_INTERVAL_MS = 60 * 1e3;
5876
6849
  _shards = /* @__PURE__ */ new Map();
6850
+ _shardLastAccess = /* @__PURE__ */ new Map();
6851
+ _evictionTimer = null;
5877
6852
  _encryptionKey = null;
5878
6853
  _shardingEnabled = false;
5879
6854
  }
@@ -6669,8 +7644,8 @@ function findContainingChunk(filePath, snippet) {
6669
7644
  try {
6670
7645
  const ext = filePath.split(".").pop()?.toLowerCase();
6671
7646
  if (ext !== "ts" && ext !== "tsx" && ext !== "js" && ext !== "jsx") return "";
6672
- const { readFileSync: readFileSync14 } = __require("fs");
6673
- const source = readFileSync14(filePath, "utf8");
7647
+ const { readFileSync: readFileSync15 } = __require("fs");
7648
+ const source = readFileSync15(filePath, "utf8");
6674
7649
  const lines = source.split("\n");
6675
7650
  const lowerSnippet = snippet.toLowerCase().slice(0, 80);
6676
7651
  let matchLine = -1;
@@ -6736,9 +7711,9 @@ function extractBash(input, response) {
6736
7711
  }
6737
7712
  function extractGrep(input, response) {
6738
7713
  const pattern = String(input.pattern ?? "");
6739
- const path21 = input.path ? String(input.path) : "";
7714
+ const path23 = input.path ? String(input.path) : "";
6740
7715
  const output = String(response.text ?? response.content ?? JSON.stringify(response).slice(0, MAX_OUTPUT));
6741
- return `Searched for "${pattern}"${path21 ? ` in ${path21}` : ""}
7716
+ return `Searched for "${pattern}"${path23 ? ` in ${path23}` : ""}
6742
7717
  ${output.slice(0, MAX_OUTPUT)}`;
6743
7718
  }
6744
7719
  function extractGlob(input, response) {
@@ -6841,7 +7816,7 @@ __export(error_detector_exports, {
6841
7816
  errorFingerprint: () => errorFingerprint,
6842
7817
  isExeOsError: () => isExeOsError
6843
7818
  });
6844
- import crypto6 from "crypto";
7819
+ import crypto7 from "crypto";
6845
7820
  function isRealStderr(stderr) {
6846
7821
  const lines = stderr.trim().split("\n");
6847
7822
  const meaningful = lines.filter(
@@ -6912,7 +7887,7 @@ function classifyError(errorText) {
6912
7887
  }
6913
7888
  function errorFingerprint(toolName, errorText) {
6914
7889
  const normalized = errorText.replace(/\d{4}-\d{2}-\d{2}T[\d:.]+Z/g, "TIMESTAMP").replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, "UUID").replace(/\/Users\/[^\s]+/g, "PATH").replace(/:\d+:\d+/g, ":LINE:COL").slice(0, 200);
6915
- return crypto6.createHash("sha256").update(`${toolName}:${normalized}`).digest("hex").slice(0, 16);
7890
+ return crypto7.createHash("sha256").update(`${toolName}:${normalized}`).digest("hex").slice(0, 16);
6916
7891
  }
6917
7892
  var ERROR_PATTERNS, FILE_CONTENT_TOOLS, STDERR_IGNORE_PATTERNS, USER_ERROR_PATTERNS, SYSTEM_BUG_PATTERNS;
6918
7893
  var init_error_detector = __esm({
@@ -7276,10 +8251,10 @@ async function disposeEmbedder() {
7276
8251
  async function embedDirect(text) {
7277
8252
  const llamaCpp = await import("node-llama-cpp");
7278
8253
  const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
7279
- const { existsSync: existsSync16 } = await import("fs");
7280
- const path21 = await import("path");
7281
- const modelPath = path21.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
7282
- if (!existsSync16(modelPath)) {
8254
+ const { existsSync: existsSync18 } = await import("fs");
8255
+ const path23 = await import("path");
8256
+ const modelPath = path23.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
8257
+ if (!existsSync18(modelPath)) {
7283
8258
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
7284
8259
  }
7285
8260
  const llama = await llamaCpp.getLlama();
@@ -7316,8 +8291,8 @@ __export(wiki_client_exports, {
7316
8291
  listDocuments: () => listDocuments,
7317
8292
  listWorkspaces: () => listWorkspaces
7318
8293
  });
7319
- async function wikiFetch(config2, path21, method = "GET", body) {
7320
- const url = `${config2.baseUrl}/api/v1${path21}`;
8294
+ async function wikiFetch(config2, path23, method = "GET", body) {
8295
+ const url = `${config2.baseUrl}/api/v1${path23}`;
7321
8296
  const headers = {
7322
8297
  Authorization: `Bearer ${config2.apiKey}`,
7323
8298
  "Content-Type": "application/json"
@@ -7350,7 +8325,7 @@ async function wikiFetch(config2, path21, method = "GET", body) {
7350
8325
  }
7351
8326
  }
7352
8327
  if (!response.ok) {
7353
- throw new Error(`Wiki API ${method} ${path21}: ${response.status} ${response.statusText}`);
8328
+ throw new Error(`Wiki API ${method} ${path23}: ${response.status} ${response.statusText}`);
7354
8329
  }
7355
8330
  return response.json();
7356
8331
  } finally {
@@ -7643,13 +8618,13 @@ __export(graph_rag_exports, {
7643
8618
  resolveAlias: () => resolveAlias,
7644
8619
  storeExtraction: () => storeExtraction
7645
8620
  });
7646
- import crypto7 from "crypto";
8621
+ import crypto8 from "crypto";
7647
8622
  function normalizeEntityName(name) {
7648
8623
  return name.replace(/\s*\([^)]*\)\s*/g, "").trim().toLowerCase();
7649
8624
  }
7650
8625
  function entityId(name, type) {
7651
8626
  const normalized = normalizeEntityName(name);
7652
- return crypto7.createHash("sha256").update(`${normalized}::${type.toLowerCase()}`).digest("hex").slice(0, 16);
8627
+ return crypto8.createHash("sha256").update(`${normalized}::${type.toLowerCase()}`).digest("hex").slice(0, 16);
7653
8628
  }
7654
8629
  async function resolveAlias(client, name) {
7655
8630
  const normalized = normalizeEntityName(name);
@@ -7899,7 +8874,7 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
7899
8874
  const targetAlias = await resolveAlias(client, r.target);
7900
8875
  const sourceId = sourceAlias ?? entityId(r.source, r.sourceType);
7901
8876
  const targetId = targetAlias ?? entityId(r.target, r.targetType);
7902
- const relId = crypto7.randomUUID().slice(0, 16);
8877
+ const relId = crypto8.randomUUID().slice(0, 16);
7903
8878
  try {
7904
8879
  await client.execute({
7905
8880
  sql: `INSERT OR IGNORE INTO entities (id, name, type, first_seen, last_seen)
@@ -7962,7 +8937,7 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
7962
8937
  }
7963
8938
  }
7964
8939
  for (const h of extraction.hyperedges) {
7965
- const hId = crypto7.randomUUID().slice(0, 16);
8940
+ const hId = crypto8.randomUUID().slice(0, 16);
7966
8941
  try {
7967
8942
  await client.execute({
7968
8943
  sql: `INSERT OR IGNORE INTO hyperedges (id, label, relation, confidence, timestamp)
@@ -8026,7 +9001,7 @@ async function extractBatch(client, batchSize = 50, model = "claude-haiku-4-5-20
8026
9001
  totalEntities += stored.entitiesStored;
8027
9002
  totalRelationships += stored.relationshipsStored;
8028
9003
  }
8029
- const contentHash = crypto7.createHash("sha256").update(rawContent).digest("hex").slice(0, 32);
9004
+ const contentHash = crypto8.createHash("sha256").update(rawContent).digest("hex").slice(0, 32);
8030
9005
  await client.execute({
8031
9006
  sql: "UPDATE memories SET graph_extracted = 1, content_hash = ?, graph_extracted_hash = ? WHERE id = ?",
8032
9007
  args: [contentHash, contentHash, memoryId]
@@ -8371,13 +9346,13 @@ __export(whatsapp_accounts_exports, {
8371
9346
  getDefaultAccount: () => getDefaultAccount,
8372
9347
  loadAccounts: () => loadAccounts
8373
9348
  });
8374
- import { readFileSync as readFileSync12 } from "fs";
9349
+ import { readFileSync as readFileSync13 } from "fs";
8375
9350
  import { join as join2 } from "path";
8376
9351
  import { homedir as homedir2 } from "os";
8377
9352
  function loadAccounts() {
8378
9353
  if (cachedAccounts !== null) return cachedAccounts;
8379
9354
  try {
8380
- const raw = readFileSync12(CONFIG_PATH2, "utf8");
9355
+ const raw = readFileSync13(CONFIG_PATH2, "utf8");
8381
9356
  const parsed = JSON.parse(raw);
8382
9357
  if (!Array.isArray(parsed)) {
8383
9358
  console.warn("[whatsapp] Config is not an array, ignoring");
@@ -8433,10 +9408,10 @@ __export(messaging_exports, {
8433
9408
  sendMessage: () => sendMessage,
8434
9409
  setWsClientSend: () => setWsClientSend
8435
9410
  });
8436
- import crypto9 from "crypto";
9411
+ import crypto10 from "crypto";
8437
9412
  function generateUlid() {
8438
9413
  const timestamp = Date.now().toString(36).padStart(10, "0");
8439
- const random = crypto9.randomBytes(10).toString("hex").slice(0, 16);
9414
+ const random = crypto10.randomBytes(10).toString("hex").slice(0, 16);
8440
9415
  return (timestamp + random).toUpperCase();
8441
9416
  }
8442
9417
  function rowToMessage(row) {
@@ -8447,6 +9422,7 @@ function rowToMessage(row) {
8447
9422
  targetAgent: row.target_agent,
8448
9423
  targetProject: row.target_project ?? null,
8449
9424
  targetDevice: row.target_device,
9425
+ sessionScope: row.session_scope ?? null,
8450
9426
  content: row.content,
8451
9427
  priority: row.priority ?? "normal",
8452
9428
  status: row.status ?? "pending",
@@ -8464,15 +9440,17 @@ async function sendMessage(input) {
8464
9440
  const id = generateUlid();
8465
9441
  const now = (/* @__PURE__ */ new Date()).toISOString();
8466
9442
  const targetDevice = input.targetDevice ?? "local";
9443
+ const sessionScope = input.sessionScope === void 0 ? resolveExeSession() : input.sessionScope;
8467
9444
  await client.execute({
8468
- sql: `INSERT INTO messages (id, from_agent, from_device, target_agent, target_project, target_device, content, priority, status, created_at)
8469
- VALUES (?, ?, 'local', ?, ?, ?, ?, ?, 'pending', ?)`,
9445
+ sql: `INSERT INTO messages (id, from_agent, from_device, target_agent, target_project, target_device, session_scope, content, priority, status, created_at)
9446
+ VALUES (?, ?, 'local', ?, ?, ?, ?, ?, ?, 'pending', ?)`,
8470
9447
  args: [
8471
9448
  id,
8472
9449
  input.fromAgent,
8473
9450
  input.targetAgent,
8474
9451
  input.targetProject ?? null,
8475
9452
  targetDevice,
9453
+ sessionScope,
8476
9454
  input.content,
8477
9455
  input.priority ?? "normal",
8478
9456
  now
@@ -8486,9 +9464,10 @@ async function sendMessage(input) {
8486
9464
  }
8487
9465
  } catch {
8488
9466
  }
9467
+ const sentScope = strictSessionScopeFilter(sessionScope);
8489
9468
  const result = await client.execute({
8490
- sql: "SELECT * FROM messages WHERE id = ?",
8491
- args: [id]
9469
+ sql: `SELECT * FROM messages WHERE id = ?${sentScope.sql}`,
9470
+ args: [id, ...sentScope.args]
8492
9471
  });
8493
9472
  return rowToMessage(result.rows[0]);
8494
9473
  }
@@ -8512,6 +9491,7 @@ async function deliverCrossMachineMessage(messageId, targetDevice) {
8512
9491
  fromAgent: msg.fromAgent,
8513
9492
  targetAgent: msg.targetAgent,
8514
9493
  targetProject: msg.targetProject,
9494
+ sessionScope: msg.sessionScope,
8515
9495
  content: msg.content,
8516
9496
  priority: msg.priority,
8517
9497
  createdAt: msg.createdAt
@@ -8555,7 +9535,7 @@ async function deliverLocalMessage(messageId) {
8555
9535
  } catch {
8556
9536
  const newRetryCount = msg.retryCount + 1;
8557
9537
  if (newRetryCount >= MAX_RETRIES3) {
8558
- await markFailed(messageId, "session unavailable after 10 retries");
9538
+ await markFailed(messageId, "session unavailable after 10 retries", msg.sessionScope);
8559
9539
  } else {
8560
9540
  await client.execute({
8561
9541
  sql: "UPDATE messages SET retry_count = ? WHERE id = ?",
@@ -8565,85 +9545,101 @@ async function deliverLocalMessage(messageId) {
8565
9545
  return false;
8566
9546
  }
8567
9547
  }
8568
- async function getPendingMessages(targetAgent) {
9548
+ async function getPendingMessages(targetAgent, sessionScope) {
8569
9549
  const client = getClient();
9550
+ const scope = strictSessionScopeFilter(sessionScope);
8570
9551
  const result = await client.execute({
8571
9552
  sql: `SELECT * FROM messages
8572
- WHERE target_agent = ? AND status IN ('pending', 'delivered')
9553
+ WHERE target_agent = ? AND status IN ('pending', 'delivered')${scope.sql}
8573
9554
  ORDER BY id`,
8574
- args: [targetAgent]
9555
+ args: [targetAgent, ...scope.args]
8575
9556
  });
8576
9557
  return result.rows.map((row) => rowToMessage(row));
8577
9558
  }
8578
- async function markRead(messageId) {
9559
+ async function markRead(messageId, sessionScope) {
8579
9560
  const client = getClient();
9561
+ const scope = strictSessionScopeFilter(sessionScope);
8580
9562
  await client.execute({
8581
- sql: "UPDATE messages SET status = 'read' WHERE id = ? AND status IN ('pending', 'delivered')",
8582
- args: [messageId]
9563
+ sql: `UPDATE messages SET status = 'read'
9564
+ WHERE id = ? AND status IN ('pending', 'delivered')${scope.sql}`,
9565
+ args: [messageId, ...scope.args]
8583
9566
  });
8584
9567
  }
8585
- async function markAcknowledged(messageId) {
9568
+ async function markAcknowledged(messageId, sessionScope) {
8586
9569
  const client = getClient();
9570
+ const scope = strictSessionScopeFilter(sessionScope);
8587
9571
  await client.execute({
8588
- sql: "UPDATE messages SET status = 'acknowledged', processed_at = ? WHERE id = ? AND status = 'read'",
8589
- args: [(/* @__PURE__ */ new Date()).toISOString(), messageId]
9572
+ sql: `UPDATE messages SET status = 'acknowledged', processed_at = ?
9573
+ WHERE id = ? AND status = 'read'${scope.sql}`,
9574
+ args: [(/* @__PURE__ */ new Date()).toISOString(), messageId, ...scope.args]
8590
9575
  });
8591
9576
  }
8592
- async function markProcessed(messageId) {
9577
+ async function markProcessed(messageId, sessionScope) {
8593
9578
  const client = getClient();
9579
+ const scope = strictSessionScopeFilter(sessionScope);
8594
9580
  await client.execute({
8595
- sql: "UPDATE messages SET status = 'processed', processed_at = ? WHERE id = ?",
8596
- args: [(/* @__PURE__ */ new Date()).toISOString(), messageId]
9581
+ sql: `UPDATE messages SET status = 'processed', processed_at = ?
9582
+ WHERE id = ?${scope.sql}`,
9583
+ args: [(/* @__PURE__ */ new Date()).toISOString(), messageId, ...scope.args]
8597
9584
  });
8598
9585
  }
8599
- async function getMessageStatus(messageId) {
9586
+ async function getMessageStatus(messageId, sessionScope) {
8600
9587
  const client = getClient();
9588
+ const scope = strictSessionScopeFilter(sessionScope);
8601
9589
  const result = await client.execute({
8602
- sql: "SELECT status FROM messages WHERE id = ?",
8603
- args: [messageId]
9590
+ sql: `SELECT status FROM messages WHERE id = ?${scope.sql}`,
9591
+ args: [messageId, ...scope.args]
8604
9592
  });
8605
9593
  return result.rows[0]?.status ?? null;
8606
9594
  }
8607
- async function getUnacknowledgedMessages(targetAgent) {
9595
+ async function getUnacknowledgedMessages(targetAgent, sessionScope) {
8608
9596
  const client = getClient();
9597
+ const scope = strictSessionScopeFilter(sessionScope);
8609
9598
  const result = await client.execute({
8610
9599
  sql: `SELECT * FROM messages
8611
- WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')
9600
+ WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')${scope.sql}
8612
9601
  ORDER BY id`,
8613
- args: [targetAgent]
9602
+ args: [targetAgent, ...scope.args]
8614
9603
  });
8615
9604
  return result.rows.map((row) => rowToMessage(row));
8616
9605
  }
8617
- async function getReadMessages(targetAgent) {
9606
+ async function getReadMessages(targetAgent, sessionScope) {
8618
9607
  const client = getClient();
9608
+ const scope = strictSessionScopeFilter(sessionScope);
8619
9609
  const result = await client.execute({
8620
- sql: "SELECT * FROM messages WHERE target_agent = ? AND status = 'read' ORDER BY id",
8621
- args: [targetAgent]
9610
+ sql: `SELECT * FROM messages
9611
+ WHERE target_agent = ? AND status = 'read'${scope.sql}
9612
+ ORDER BY id`,
9613
+ args: [targetAgent, ...scope.args]
8622
9614
  });
8623
9615
  return result.rows.map((row) => rowToMessage(row));
8624
9616
  }
8625
- async function markFailed(messageId, reason) {
9617
+ async function markFailed(messageId, reason, sessionScope) {
8626
9618
  const client = getClient();
9619
+ const scope = strictSessionScopeFilter(sessionScope);
8627
9620
  await client.execute({
8628
- sql: "UPDATE messages SET status = 'failed', failed_at = ?, failure_reason = ? WHERE id = ?",
8629
- args: [(/* @__PURE__ */ new Date()).toISOString(), reason, messageId]
9621
+ sql: `UPDATE messages SET status = 'failed', failed_at = ?, failure_reason = ?
9622
+ WHERE id = ?${scope.sql}`,
9623
+ args: [(/* @__PURE__ */ new Date()).toISOString(), reason, messageId, ...scope.args]
8630
9624
  });
8631
9625
  }
8632
- async function getFailedMessages() {
9626
+ async function getFailedMessages(sessionScope) {
8633
9627
  const client = getClient();
9628
+ const scope = strictSessionScopeFilter(sessionScope);
8634
9629
  const result = await client.execute({
8635
- sql: "SELECT * FROM messages WHERE status = 'failed' ORDER BY created_at DESC",
8636
- args: []
9630
+ sql: `SELECT * FROM messages WHERE status = 'failed'${scope.sql} ORDER BY created_at DESC`,
9631
+ args: [...scope.args]
8637
9632
  });
8638
9633
  return result.rows.map((row) => rowToMessage(row));
8639
9634
  }
8640
- async function retryPendingMessages() {
9635
+ async function retryPendingMessages(sessionScope) {
8641
9636
  const client = getClient();
9637
+ const scope = strictSessionScopeFilter(sessionScope);
8642
9638
  const result = await client.execute({
8643
9639
  sql: `SELECT * FROM messages
8644
- WHERE status = 'pending' AND retry_count < ?
9640
+ WHERE status = 'pending' AND retry_count < ?${scope.sql}
8645
9641
  ORDER BY id`,
8646
- args: [MAX_RETRIES3]
9642
+ args: [MAX_RETRIES3, ...scope.args]
8647
9643
  });
8648
9644
  let delivered = 0;
8649
9645
  for (const row of result.rows) {
@@ -8662,6 +9658,7 @@ var init_messaging = __esm({
8662
9658
  "use strict";
8663
9659
  init_database();
8664
9660
  init_tmux_routing();
9661
+ init_task_scope();
8665
9662
  MAX_RETRIES3 = 10;
8666
9663
  _wsClientSend = null;
8667
9664
  }
@@ -11142,11 +12139,11 @@ init_crm_bridge();
11142
12139
 
11143
12140
  // src/lib/pipeline-router.ts
11144
12141
  init_database();
11145
- import crypto8 from "crypto";
12142
+ import crypto9 from "crypto";
11146
12143
  async function sinkConversationStore(msg, agentResponse, agentName) {
11147
12144
  try {
11148
12145
  const client = getClient();
11149
- const id = crypto8.randomUUID();
12146
+ const id = crypto9.randomUUID();
11150
12147
  const mediaJson = msg.media ? JSON.stringify(msg.media) : null;
11151
12148
  await client.execute({
11152
12149
  sql: `INSERT INTO conversations
@@ -11196,7 +12193,7 @@ async function sinkMemory(msg, agentResponse, agentName) {
11196
12193
  ].filter(Boolean).join("\n");
11197
12194
  const vector = await embed2(rawText);
11198
12195
  await writeMemory2({
11199
- id: crypto8.randomUUID(),
12196
+ id: crypto9.randomUUID(),
11200
12197
  agent_id: agentName ?? "gateway",
11201
12198
  agent_role: "gateway",
11202
12199
  session_id: `gateway-${msg.platform}`,
@@ -13877,12 +14874,12 @@ var SlackAdapter = class {
13877
14874
  // src/gateway/adapters/imessage.ts
13878
14875
  import { execFile } from "child_process";
13879
14876
  import { promisify } from "util";
13880
- import os11 from "os";
13881
- import path19 from "path";
14877
+ import os13 from "os";
14878
+ import path21 from "path";
13882
14879
  var execFileAsync = promisify(execFile);
13883
14880
  var POLL_INTERVAL_MS = 5e3;
13884
- var MESSAGES_DB_PATH = path19.join(
13885
- process.env.HOME ?? os11.homedir(),
14881
+ var MESSAGES_DB_PATH = path21.join(
14882
+ process.env.HOME ?? os13.homedir(),
13886
14883
  "Library/Messages/chat.db"
13887
14884
  );
13888
14885
  var IMessageAdapter = class {
@@ -14725,11 +15722,11 @@ async function ensureCRMContact(info) {
14725
15722
  }
14726
15723
 
14727
15724
  // src/automation/trigger-engine.ts
14728
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, existsSync as existsSync15, mkdirSync as mkdirSync9 } from "fs";
15725
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync9, existsSync as existsSync17, mkdirSync as mkdirSync9 } from "fs";
14729
15726
  import { randomUUID as randomUUID15 } from "crypto";
14730
- import path20 from "path";
14731
- import os12 from "os";
14732
- var TRIGGERS_PATH = path20.join(os12.homedir(), ".exe-os", "triggers.json");
15727
+ import path22 from "path";
15728
+ import os14 from "os";
15729
+ var TRIGGERS_PATH = path22.join(os14.homedir(), ".exe-os", "triggers.json");
14733
15730
  var GRAPH_API_VERSION = "v21.0";
14734
15731
  function substituteTemplate(template, record) {
14735
15732
  return template.replace(
@@ -14783,9 +15780,9 @@ function evaluateConditions(conditions, record) {
14783
15780
  return conditions.every((c) => evaluateCondition(c, record));
14784
15781
  }
14785
15782
  function loadTriggers(project) {
14786
- if (!existsSync15(TRIGGERS_PATH)) return [];
15783
+ if (!existsSync17(TRIGGERS_PATH)) return [];
14787
15784
  try {
14788
- const raw = readFileSync13(TRIGGERS_PATH, "utf-8");
15785
+ const raw = readFileSync14(TRIGGERS_PATH, "utf-8");
14789
15786
  const all = JSON.parse(raw);
14790
15787
  if (!Array.isArray(all)) return [];
14791
15788
  if (project) {