@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
@@ -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 path from "path";
47
85
  import os 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 = path.join(os.homedir(), ".exe-os");
52
90
  const legacyDir = path.join(os.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 = path.join(dir, "config.json");
118
- if (!existsSync(configPath)) {
156
+ if (!existsSync2(configPath)) {
119
157
  return { ...DEFAULT_CONFIG, dbPath: path.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 = path.join(dir, "config.json");
149
- if (!existsSync(configPath)) {
188
+ if (!existsSync2(configPath)) {
150
189
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
151
190
  }
152
191
  try {
@@ -164,12 +203,10 @@ function loadConfigSync() {
164
203
  }
165
204
  async function saveConfig(config) {
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 = path.join(dir, "config.json");
169
208
  await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
170
- if (config.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 = path.join(EXE_AI_DIR, "memories.db");
194
232
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
@@ -329,6 +367,120 @@ var init_db_retry = __esm({
329
367
  }
330
368
  });
331
369
 
370
+ // src/lib/runtime-table.ts
371
+ var RUNTIME_TABLE, DEFAULT_RUNTIME;
372
+ var init_runtime_table = __esm({
373
+ "src/lib/runtime-table.ts"() {
374
+ "use strict";
375
+ RUNTIME_TABLE = {
376
+ codex: {
377
+ binary: "codex",
378
+ launchMode: "interactive",
379
+ autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
380
+ inlineFlag: "--no-alt-screen",
381
+ apiKeyEnv: "OPENAI_API_KEY",
382
+ defaultModel: "gpt-5.4"
383
+ },
384
+ opencode: {
385
+ binary: "opencode",
386
+ launchMode: "exec",
387
+ autoApproveFlag: "--dangerously-skip-permissions",
388
+ inlineFlag: "",
389
+ apiKeyEnv: "ANTHROPIC_API_KEY",
390
+ defaultModel: "anthropic/claude-sonnet-4-6"
391
+ }
392
+ };
393
+ DEFAULT_RUNTIME = "claude";
394
+ }
395
+ });
396
+
397
+ // src/lib/agent-config.ts
398
+ var agent_config_exports = {};
399
+ __export(agent_config_exports, {
400
+ AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
401
+ DEFAULT_MODELS: () => DEFAULT_MODELS,
402
+ KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
403
+ RUNTIME_LABELS: () => RUNTIME_LABELS,
404
+ clearAgentRuntime: () => clearAgentRuntime,
405
+ getAgentRuntime: () => getAgentRuntime,
406
+ loadAgentConfig: () => loadAgentConfig,
407
+ saveAgentConfig: () => saveAgentConfig,
408
+ setAgentRuntime: () => setAgentRuntime
409
+ });
410
+ import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
411
+ import path2 from "path";
412
+ function loadAgentConfig() {
413
+ if (!existsSync3(AGENT_CONFIG_PATH)) return {};
414
+ try {
415
+ return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
416
+ } catch {
417
+ return {};
418
+ }
419
+ }
420
+ function saveAgentConfig(config) {
421
+ const dir = path2.dirname(AGENT_CONFIG_PATH);
422
+ ensurePrivateDirSync(dir);
423
+ writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
424
+ enforcePrivateFileSync(AGENT_CONFIG_PATH);
425
+ }
426
+ function getAgentRuntime(agentId) {
427
+ const config = loadAgentConfig();
428
+ const entry = config[agentId];
429
+ if (entry) return entry;
430
+ const orgDefault = config["default"];
431
+ if (orgDefault) return orgDefault;
432
+ return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
433
+ }
434
+ function setAgentRuntime(agentId, runtime, model) {
435
+ const knownModels = KNOWN_RUNTIMES[runtime];
436
+ if (!knownModels) {
437
+ return {
438
+ ok: false,
439
+ error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
440
+ };
441
+ }
442
+ if (!knownModels.includes(model)) {
443
+ return {
444
+ ok: false,
445
+ error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
446
+ };
447
+ }
448
+ const config = loadAgentConfig();
449
+ config[agentId] = { runtime, model };
450
+ saveAgentConfig(config);
451
+ return { ok: true };
452
+ }
453
+ function clearAgentRuntime(agentId) {
454
+ const config = loadAgentConfig();
455
+ delete config[agentId];
456
+ saveAgentConfig(config);
457
+ }
458
+ var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
459
+ var init_agent_config = __esm({
460
+ "src/lib/agent-config.ts"() {
461
+ "use strict";
462
+ init_config();
463
+ init_runtime_table();
464
+ init_secure_files();
465
+ AGENT_CONFIG_PATH = path2.join(EXE_AI_DIR, "agent-config.json");
466
+ KNOWN_RUNTIMES = {
467
+ claude: ["claude-opus-4", "claude-sonnet-4", "claude-haiku-4.5"],
468
+ codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
469
+ opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
470
+ };
471
+ RUNTIME_LABELS = {
472
+ claude: "Claude Code (Anthropic)",
473
+ codex: "Codex (OpenAI)",
474
+ opencode: "OpenCode (open source)"
475
+ };
476
+ DEFAULT_MODELS = {
477
+ claude: "claude-opus-4",
478
+ codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
479
+ opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
480
+ };
481
+ }
482
+ });
483
+
332
484
  // src/lib/employees.ts
333
485
  var employees_exports = {};
334
486
  __export(employees_exports, {
@@ -344,6 +496,7 @@ __export(employees_exports, {
344
496
  getEmployeeByRole: () => getEmployeeByRole,
345
497
  getEmployeeNamesByRole: () => getEmployeeNamesByRole,
346
498
  hasRole: () => hasRole,
499
+ hireEmployee: () => hireEmployee,
347
500
  isCoordinatorName: () => isCoordinatorName,
348
501
  isCoordinatorRole: () => isCoordinatorRole,
349
502
  isMultiInstance: () => isMultiInstance,
@@ -356,9 +509,9 @@ __export(employees_exports, {
356
509
  validateEmployeeName: () => validateEmployeeName
357
510
  });
358
511
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
359
- import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
512
+ import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
360
513
  import { execSync } from "child_process";
361
- import path2 from "path";
514
+ import path3 from "path";
362
515
  import os2 from "os";
363
516
  function normalizeRole(role) {
364
517
  return (role ?? "").trim().toLowerCase();
@@ -395,7 +548,7 @@ function validateEmployeeName(name) {
395
548
  return { valid: true };
396
549
  }
397
550
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
398
- if (!existsSync2(employeesPath)) {
551
+ if (!existsSync4(employeesPath)) {
399
552
  return [];
400
553
  }
401
554
  const raw = await readFile2(employeesPath, "utf-8");
@@ -406,13 +559,13 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
406
559
  }
407
560
  }
408
561
  async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
409
- await mkdir2(path2.dirname(employeesPath), { recursive: true });
562
+ await mkdir2(path3.dirname(employeesPath), { recursive: true });
410
563
  await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
411
564
  }
412
565
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
413
- if (!existsSync2(employeesPath)) return [];
566
+ if (!existsSync4(employeesPath)) return [];
414
567
  try {
415
- return JSON.parse(readFileSync2(employeesPath, "utf-8"));
568
+ return JSON.parse(readFileSync3(employeesPath, "utf-8"));
416
569
  } catch {
417
570
  return [];
418
571
  }
@@ -454,6 +607,52 @@ function addEmployee(employees, employee) {
454
607
  }
455
608
  return [...employees, normalized];
456
609
  }
610
+ function appendToCoordinatorTeam(employee) {
611
+ const coordinator = getCoordinatorEmployee(loadEmployeesSync());
612
+ if (!coordinator) return;
613
+ const idPath = path3.join(IDENTITY_DIR, `${coordinator.name}.md`);
614
+ if (!existsSync4(idPath)) return;
615
+ const content = readFileSync3(idPath, "utf-8");
616
+ if (content.includes(`**${capitalize(employee.name)}`)) return;
617
+ const teamMatch = content.match(TEAM_SECTION_RE);
618
+ if (!teamMatch || teamMatch.index === void 0) return;
619
+ const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
620
+ const nextHeading = afterTeam.match(/\n## /);
621
+ const entry = `
622
+ **${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
623
+ `;
624
+ let updated;
625
+ if (nextHeading && nextHeading.index !== void 0) {
626
+ const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
627
+ updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
628
+ } else {
629
+ updated = content.trimEnd() + "\n" + entry;
630
+ }
631
+ writeFileSync2(idPath, updated, "utf-8");
632
+ }
633
+ function capitalize(s) {
634
+ return s.charAt(0).toUpperCase() + s.slice(1);
635
+ }
636
+ async function hireEmployee(employee) {
637
+ const employees = await loadEmployees();
638
+ const updated = addEmployee(employees, employee);
639
+ await saveEmployees(updated);
640
+ try {
641
+ appendToCoordinatorTeam(employee);
642
+ } catch {
643
+ }
644
+ try {
645
+ const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
646
+ const config = loadAgentConfig2();
647
+ const name = employee.name.toLowerCase();
648
+ if (!config[name] && config["default"]) {
649
+ config[name] = { ...config["default"] };
650
+ saveAgentConfig2(config);
651
+ }
652
+ } catch {
653
+ }
654
+ return updated;
655
+ }
457
656
  async function normalizeRosterCase(rosterPath) {
458
657
  const employees = await loadEmployees(rosterPath);
459
658
  let changed = false;
@@ -463,14 +662,14 @@ async function normalizeRosterCase(rosterPath) {
463
662
  emp.name = emp.name.toLowerCase();
464
663
  changed = true;
465
664
  try {
466
- const identityDir = path2.join(os2.homedir(), ".exe-os", "identity");
467
- const oldPath = path2.join(identityDir, `${oldName}.md`);
468
- const newPath = path2.join(identityDir, `${emp.name}.md`);
469
- if (existsSync2(oldPath) && !existsSync2(newPath)) {
665
+ const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
666
+ const oldPath = path3.join(identityDir, `${oldName}.md`);
667
+ const newPath = path3.join(identityDir, `${emp.name}.md`);
668
+ if (existsSync4(oldPath) && !existsSync4(newPath)) {
470
669
  renameSync2(oldPath, newPath);
471
- } else if (existsSync2(oldPath) && oldPath !== newPath) {
472
- const content = readFileSync2(oldPath, "utf-8");
473
- writeFileSync(newPath, content, "utf-8");
670
+ } else if (existsSync4(oldPath) && oldPath !== newPath) {
671
+ const content = readFileSync3(oldPath, "utf-8");
672
+ writeFileSync2(newPath, content, "utf-8");
474
673
  if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
475
674
  unlinkSync(oldPath);
476
675
  }
@@ -500,7 +699,7 @@ function registerBinSymlinks(name) {
500
699
  errors.push("Could not find 'exe-os' in PATH");
501
700
  return { created, skipped, errors };
502
701
  }
503
- const binDir = path2.dirname(exeBinPath);
702
+ const binDir = path3.dirname(exeBinPath);
504
703
  let target;
505
704
  try {
506
705
  target = readlinkSync(exeBinPath);
@@ -510,8 +709,8 @@ function registerBinSymlinks(name) {
510
709
  }
511
710
  for (const suffix of ["", "-opencode"]) {
512
711
  const linkName = `${name}${suffix}`;
513
- const linkPath = path2.join(binDir, linkName);
514
- if (existsSync2(linkPath)) {
712
+ const linkPath = path3.join(binDir, linkName);
713
+ if (existsSync4(linkPath)) {
515
714
  skipped.push(linkName);
516
715
  continue;
517
716
  }
@@ -524,21 +723,619 @@ function registerBinSymlinks(name) {
524
723
  }
525
724
  return { created, skipped, errors };
526
725
  }
527
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
726
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
528
727
  var init_employees = __esm({
529
728
  "src/lib/employees.ts"() {
530
729
  "use strict";
531
730
  init_config();
532
- EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
731
+ EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
533
732
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
534
733
  COORDINATOR_ROLE = "COO";
535
734
  MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
735
+ IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
736
+ TEAM_SECTION_RE = /^## Team\b.*$/m;
737
+ }
738
+ });
739
+
740
+ // src/lib/database-adapter.ts
741
+ import os3 from "os";
742
+ import path4 from "path";
743
+ import { createRequire } from "module";
744
+ import { pathToFileURL } from "url";
745
+ function quotedIdentifier(identifier) {
746
+ return `"${identifier.replace(/"/g, '""')}"`;
747
+ }
748
+ function unqualifiedTableName(name) {
749
+ const raw = name.trim().replace(/^"|"$/g, "");
750
+ const parts = raw.split(".");
751
+ return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
752
+ }
753
+ function stripTrailingSemicolon(sql) {
754
+ return sql.trim().replace(/;+\s*$/u, "");
755
+ }
756
+ function appendClause(sql, clause) {
757
+ const trimmed = stripTrailingSemicolon(sql);
758
+ const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
759
+ if (!returningMatch) {
760
+ return `${trimmed}${clause}`;
761
+ }
762
+ const idx = returningMatch.index;
763
+ return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
764
+ }
765
+ function normalizeStatement(stmt) {
766
+ if (typeof stmt === "string") {
767
+ return { kind: "positional", sql: stmt, args: [] };
768
+ }
769
+ const sql = stmt.sql;
770
+ if (Array.isArray(stmt.args) || stmt.args === void 0) {
771
+ return { kind: "positional", sql, args: stmt.args ?? [] };
772
+ }
773
+ return { kind: "named", sql, args: stmt.args };
774
+ }
775
+ function rewriteBooleanLiterals(sql) {
776
+ let out = sql;
777
+ for (const column of BOOLEAN_COLUMN_NAMES) {
778
+ const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
779
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
780
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
781
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
782
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
783
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
784
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
785
+ }
786
+ return out;
787
+ }
788
+ function rewriteInsertOrIgnore(sql) {
789
+ if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
790
+ return sql;
791
+ }
792
+ const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
793
+ return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
794
+ }
795
+ function rewriteInsertOrReplace(sql) {
796
+ const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
797
+ if (!match) {
798
+ return sql;
799
+ }
800
+ const rawTable = match[1];
801
+ const rawColumns = match[2];
802
+ const remainder = match[3];
803
+ const tableName = unqualifiedTableName(rawTable);
804
+ const conflictKeys = UPSERT_KEYS[tableName];
805
+ if (!conflictKeys?.length) {
806
+ return sql;
807
+ }
808
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
809
+ const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
810
+ const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
811
+ const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
812
+ return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
813
+ }
814
+ function rewriteSql(sql) {
815
+ let out = sql;
816
+ out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
817
+ out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
818
+ out = rewriteBooleanLiterals(out);
819
+ out = rewriteInsertOrReplace(out);
820
+ out = rewriteInsertOrIgnore(out);
821
+ return stripTrailingSemicolon(out);
822
+ }
823
+ function toBoolean(value) {
824
+ if (value === null || value === void 0) return value;
825
+ if (typeof value === "boolean") return value;
826
+ if (typeof value === "number") return value !== 0;
827
+ if (typeof value === "bigint") return value !== 0n;
828
+ if (typeof value === "string") {
829
+ const normalized = value.trim().toLowerCase();
830
+ if (normalized === "0" || normalized === "false") return false;
831
+ if (normalized === "1" || normalized === "true") return true;
832
+ }
833
+ return Boolean(value);
834
+ }
835
+ function countQuestionMarks(sql, end) {
836
+ let count = 0;
837
+ let inSingle = false;
838
+ let inDouble = false;
839
+ let inLineComment = false;
840
+ let inBlockComment = false;
841
+ for (let i = 0; i < end; i++) {
842
+ const ch = sql[i];
843
+ const next = sql[i + 1];
844
+ if (inLineComment) {
845
+ if (ch === "\n") inLineComment = false;
846
+ continue;
847
+ }
848
+ if (inBlockComment) {
849
+ if (ch === "*" && next === "/") {
850
+ inBlockComment = false;
851
+ i += 1;
852
+ }
853
+ continue;
854
+ }
855
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
856
+ inLineComment = true;
857
+ i += 1;
858
+ continue;
859
+ }
860
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
861
+ inBlockComment = true;
862
+ i += 1;
863
+ continue;
864
+ }
865
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
866
+ inSingle = !inSingle;
867
+ continue;
868
+ }
869
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
870
+ inDouble = !inDouble;
871
+ continue;
872
+ }
873
+ if (!inSingle && !inDouble && ch === "?") {
874
+ count += 1;
875
+ }
876
+ }
877
+ return count;
878
+ }
879
+ function findBooleanPlaceholderIndexes(sql) {
880
+ const indexes = /* @__PURE__ */ new Set();
881
+ for (const column of BOOLEAN_COLUMN_NAMES) {
882
+ const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
883
+ for (const match of sql.matchAll(pattern)) {
884
+ const matchText = match[0];
885
+ const qIndex = match.index + matchText.lastIndexOf("?");
886
+ indexes.add(countQuestionMarks(sql, qIndex + 1));
887
+ }
888
+ }
889
+ return indexes;
890
+ }
891
+ function coerceInsertBooleanArgs(sql, args) {
892
+ const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
893
+ if (!match) return;
894
+ const rawTable = match[1];
895
+ const rawColumns = match[2];
896
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
897
+ if (!boolColumns?.size) return;
898
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
899
+ for (const [index, column] of columns.entries()) {
900
+ if (boolColumns.has(column) && index < args.length) {
901
+ args[index] = toBoolean(args[index]);
902
+ }
903
+ }
904
+ }
905
+ function coerceUpdateBooleanArgs(sql, args) {
906
+ const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
907
+ if (!match) return;
908
+ const rawTable = match[1];
909
+ const setClause = match[2];
910
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
911
+ if (!boolColumns?.size) return;
912
+ const assignments = setClause.split(",");
913
+ let placeholderIndex = 0;
914
+ for (const assignment of assignments) {
915
+ if (!assignment.includes("?")) continue;
916
+ placeholderIndex += 1;
917
+ const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
918
+ if (colMatch && boolColumns.has(colMatch[1])) {
919
+ args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
920
+ }
921
+ }
922
+ }
923
+ function coerceBooleanArgs(sql, args) {
924
+ const nextArgs = [...args];
925
+ coerceInsertBooleanArgs(sql, nextArgs);
926
+ coerceUpdateBooleanArgs(sql, nextArgs);
927
+ const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
928
+ for (const index of placeholderIndexes) {
929
+ if (index > 0 && index <= nextArgs.length) {
930
+ nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
931
+ }
932
+ }
933
+ return nextArgs;
934
+ }
935
+ function convertQuestionMarksToDollarParams(sql) {
936
+ let out = "";
937
+ let placeholder = 0;
938
+ let inSingle = false;
939
+ let inDouble = false;
940
+ let inLineComment = false;
941
+ let inBlockComment = false;
942
+ for (let i = 0; i < sql.length; i++) {
943
+ const ch = sql[i];
944
+ const next = sql[i + 1];
945
+ if (inLineComment) {
946
+ out += ch;
947
+ if (ch === "\n") inLineComment = false;
948
+ continue;
949
+ }
950
+ if (inBlockComment) {
951
+ out += ch;
952
+ if (ch === "*" && next === "/") {
953
+ out += next;
954
+ inBlockComment = false;
955
+ i += 1;
956
+ }
957
+ continue;
958
+ }
959
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
960
+ out += ch + next;
961
+ inLineComment = true;
962
+ i += 1;
963
+ continue;
964
+ }
965
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
966
+ out += ch + next;
967
+ inBlockComment = true;
968
+ i += 1;
969
+ continue;
970
+ }
971
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
972
+ inSingle = !inSingle;
973
+ out += ch;
974
+ continue;
975
+ }
976
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
977
+ inDouble = !inDouble;
978
+ out += ch;
979
+ continue;
980
+ }
981
+ if (!inSingle && !inDouble && ch === "?") {
982
+ placeholder += 1;
983
+ out += `$${placeholder}`;
984
+ continue;
985
+ }
986
+ out += ch;
987
+ }
988
+ return out;
989
+ }
990
+ function translateStatementForPostgres(stmt) {
991
+ const normalized = normalizeStatement(stmt);
992
+ if (normalized.kind === "named") {
993
+ throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
994
+ }
995
+ const rewrittenSql = rewriteSql(normalized.sql);
996
+ const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
997
+ return {
998
+ sql: convertQuestionMarksToDollarParams(rewrittenSql),
999
+ args: coercedArgs
1000
+ };
1001
+ }
1002
+ function shouldBypassPostgres(stmt) {
1003
+ const normalized = normalizeStatement(stmt);
1004
+ if (normalized.kind === "named") {
1005
+ return true;
1006
+ }
1007
+ return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
1008
+ }
1009
+ function shouldFallbackOnError(error) {
1010
+ const message = error instanceof Error ? error.message : String(error);
1011
+ return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
1012
+ }
1013
+ function isReadQuery(sql) {
1014
+ const trimmed = sql.trimStart();
1015
+ return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
1016
+ }
1017
+ function buildRow(row, columns) {
1018
+ const values = columns.map((column) => row[column]);
1019
+ return Object.assign(values, row);
1020
+ }
1021
+ function buildResultSet(rows, rowsAffected = 0) {
1022
+ const columns = rows[0] ? Object.keys(rows[0]) : [];
1023
+ const resultRows = rows.map((row) => buildRow(row, columns));
1024
+ return {
1025
+ columns,
1026
+ columnTypes: columns.map(() => ""),
1027
+ rows: resultRows,
1028
+ rowsAffected,
1029
+ lastInsertRowid: void 0,
1030
+ toJSON() {
1031
+ return {
1032
+ columns,
1033
+ columnTypes: columns.map(() => ""),
1034
+ rows,
1035
+ rowsAffected,
1036
+ lastInsertRowid: void 0
1037
+ };
1038
+ }
1039
+ };
1040
+ }
1041
+ async function loadPrismaClient() {
1042
+ if (!prismaClientPromise) {
1043
+ prismaClientPromise = (async () => {
1044
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
1045
+ if (explicitPath) {
1046
+ const module2 = await import(pathToFileURL(explicitPath).href);
1047
+ const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
1048
+ if (!PrismaClient2) {
1049
+ throw new Error(`No PrismaClient export found at ${explicitPath}`);
1050
+ }
1051
+ return new PrismaClient2();
1052
+ }
1053
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path4.join(os3.homedir(), "exe-db");
1054
+ const requireFromExeDb = createRequire(path4.join(exeDbRoot, "package.json"));
1055
+ const prismaEntry = requireFromExeDb.resolve("@prisma/client");
1056
+ const module = await import(pathToFileURL(prismaEntry).href);
1057
+ const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
1058
+ if (!PrismaClient) {
1059
+ throw new Error(`No PrismaClient export found in ${prismaEntry}`);
1060
+ }
1061
+ return new PrismaClient();
1062
+ })();
1063
+ }
1064
+ return prismaClientPromise;
1065
+ }
1066
+ async function ensureCompatibilityViews(prisma) {
1067
+ if (!compatibilityBootstrapPromise) {
1068
+ compatibilityBootstrapPromise = (async () => {
1069
+ for (const mapping of VIEW_MAPPINGS) {
1070
+ const relation = mapping.source.replace(/"/g, "");
1071
+ const rows = await prisma.$queryRawUnsafe(
1072
+ "SELECT to_regclass($1) AS regclass",
1073
+ relation
1074
+ );
1075
+ if (!rows[0]?.regclass) {
1076
+ continue;
1077
+ }
1078
+ await prisma.$executeRawUnsafe(
1079
+ `CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
1080
+ );
1081
+ }
1082
+ })();
1083
+ }
1084
+ return compatibilityBootstrapPromise;
1085
+ }
1086
+ async function executeOnPrisma(executor, stmt) {
1087
+ const translated = translateStatementForPostgres(stmt);
1088
+ if (isReadQuery(translated.sql)) {
1089
+ const rows = await executor.$queryRawUnsafe(
1090
+ translated.sql,
1091
+ ...translated.args
1092
+ );
1093
+ return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
1094
+ }
1095
+ const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
1096
+ return buildResultSet([], rowsAffected);
1097
+ }
1098
+ function splitSqlStatements(sql) {
1099
+ const parts = [];
1100
+ let current = "";
1101
+ let inSingle = false;
1102
+ let inDouble = false;
1103
+ let inLineComment = false;
1104
+ let inBlockComment = false;
1105
+ for (let i = 0; i < sql.length; i++) {
1106
+ const ch = sql[i];
1107
+ const next = sql[i + 1];
1108
+ if (inLineComment) {
1109
+ current += ch;
1110
+ if (ch === "\n") inLineComment = false;
1111
+ continue;
1112
+ }
1113
+ if (inBlockComment) {
1114
+ current += ch;
1115
+ if (ch === "*" && next === "/") {
1116
+ current += next;
1117
+ inBlockComment = false;
1118
+ i += 1;
1119
+ }
1120
+ continue;
1121
+ }
1122
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
1123
+ current += ch + next;
1124
+ inLineComment = true;
1125
+ i += 1;
1126
+ continue;
1127
+ }
1128
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
1129
+ current += ch + next;
1130
+ inBlockComment = true;
1131
+ i += 1;
1132
+ continue;
1133
+ }
1134
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
1135
+ inSingle = !inSingle;
1136
+ current += ch;
1137
+ continue;
1138
+ }
1139
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
1140
+ inDouble = !inDouble;
1141
+ current += ch;
1142
+ continue;
1143
+ }
1144
+ if (!inSingle && !inDouble && ch === ";") {
1145
+ if (current.trim()) {
1146
+ parts.push(current.trim());
1147
+ }
1148
+ current = "";
1149
+ continue;
1150
+ }
1151
+ current += ch;
1152
+ }
1153
+ if (current.trim()) {
1154
+ parts.push(current.trim());
1155
+ }
1156
+ return parts;
1157
+ }
1158
+ async function createPrismaDbAdapter(fallbackClient) {
1159
+ const prisma = await loadPrismaClient();
1160
+ await ensureCompatibilityViews(prisma);
1161
+ let closed = false;
1162
+ let adapter;
1163
+ const fallbackExecute = async (stmt, error) => {
1164
+ if (!fallbackClient) {
1165
+ if (error) throw error;
1166
+ throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
1167
+ }
1168
+ if (error) {
1169
+ process.stderr.write(
1170
+ `[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
1171
+ `
1172
+ );
1173
+ }
1174
+ return fallbackClient.execute(stmt);
1175
+ };
1176
+ adapter = {
1177
+ async execute(stmt) {
1178
+ if (shouldBypassPostgres(stmt)) {
1179
+ return fallbackExecute(stmt);
1180
+ }
1181
+ try {
1182
+ return await executeOnPrisma(prisma, stmt);
1183
+ } catch (error) {
1184
+ if (shouldFallbackOnError(error)) {
1185
+ return fallbackExecute(stmt, error);
1186
+ }
1187
+ throw error;
1188
+ }
1189
+ },
1190
+ async batch(stmts, mode) {
1191
+ if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
1192
+ if (!fallbackClient) {
1193
+ throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
1194
+ }
1195
+ return fallbackClient.batch(stmts, mode);
1196
+ }
1197
+ try {
1198
+ if (prisma.$transaction) {
1199
+ return await prisma.$transaction(async (tx) => {
1200
+ const results2 = [];
1201
+ for (const stmt of stmts) {
1202
+ results2.push(await executeOnPrisma(tx, stmt));
1203
+ }
1204
+ return results2;
1205
+ });
1206
+ }
1207
+ const results = [];
1208
+ for (const stmt of stmts) {
1209
+ results.push(await executeOnPrisma(prisma, stmt));
1210
+ }
1211
+ return results;
1212
+ } catch (error) {
1213
+ if (fallbackClient && shouldFallbackOnError(error)) {
1214
+ process.stderr.write(
1215
+ `[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
1216
+ `
1217
+ );
1218
+ return fallbackClient.batch(stmts, mode);
1219
+ }
1220
+ throw error;
1221
+ }
1222
+ },
1223
+ async migrate(stmts) {
1224
+ if (fallbackClient) {
1225
+ return fallbackClient.migrate(stmts);
1226
+ }
1227
+ return adapter.batch(stmts, "deferred");
1228
+ },
1229
+ async transaction(mode) {
1230
+ if (!fallbackClient) {
1231
+ throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
1232
+ }
1233
+ return fallbackClient.transaction(mode);
1234
+ },
1235
+ async executeMultiple(sql) {
1236
+ if (fallbackClient && shouldBypassPostgres(sql)) {
1237
+ return fallbackClient.executeMultiple(sql);
1238
+ }
1239
+ for (const statement of splitSqlStatements(sql)) {
1240
+ await adapter.execute(statement);
1241
+ }
1242
+ },
1243
+ async sync() {
1244
+ if (fallbackClient) {
1245
+ return fallbackClient.sync();
1246
+ }
1247
+ return { frame_no: 0, frames_synced: 0 };
1248
+ },
1249
+ close() {
1250
+ closed = true;
1251
+ prismaClientPromise = null;
1252
+ compatibilityBootstrapPromise = null;
1253
+ void prisma.$disconnect?.();
1254
+ },
1255
+ get closed() {
1256
+ return closed;
1257
+ },
1258
+ get protocol() {
1259
+ return "prisma-postgres";
1260
+ }
1261
+ };
1262
+ return adapter;
1263
+ }
1264
+ var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
1265
+ var init_database_adapter = __esm({
1266
+ "src/lib/database-adapter.ts"() {
1267
+ "use strict";
1268
+ VIEW_MAPPINGS = [
1269
+ { view: "memories", source: "memory.memory_records" },
1270
+ { view: "tasks", source: "memory.tasks" },
1271
+ { view: "behaviors", source: "memory.behaviors" },
1272
+ { view: "entities", source: "memory.entities" },
1273
+ { view: "relationships", source: "memory.relationships" },
1274
+ { view: "entity_memories", source: "memory.entity_memories" },
1275
+ { view: "entity_aliases", source: "memory.entity_aliases" },
1276
+ { view: "notifications", source: "memory.notifications" },
1277
+ { view: "messages", source: "memory.messages" },
1278
+ { view: "users", source: "wiki.users" },
1279
+ { view: "workspaces", source: "wiki.workspaces" },
1280
+ { view: "workspace_users", source: "wiki.workspace_users" },
1281
+ { view: "documents", source: "wiki.workspace_documents" },
1282
+ { view: "chats", source: "wiki.workspace_chats" }
1283
+ ];
1284
+ UPSERT_KEYS = {
1285
+ memories: ["id"],
1286
+ tasks: ["id"],
1287
+ behaviors: ["id"],
1288
+ entities: ["id"],
1289
+ relationships: ["id"],
1290
+ entity_aliases: ["alias"],
1291
+ notifications: ["id"],
1292
+ messages: ["id"],
1293
+ users: ["id"],
1294
+ workspaces: ["id"],
1295
+ workspace_users: ["id"],
1296
+ documents: ["id"],
1297
+ chats: ["id"]
1298
+ };
1299
+ BOOLEAN_COLUMNS_BY_TABLE = {
1300
+ memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
1301
+ behaviors: /* @__PURE__ */ new Set(["active"]),
1302
+ notifications: /* @__PURE__ */ new Set(["read"]),
1303
+ users: /* @__PURE__ */ new Set(["has_personal_memory"])
1304
+ };
1305
+ BOOLEAN_COLUMN_NAMES = new Set(
1306
+ Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
1307
+ );
1308
+ IMMEDIATE_FALLBACK_PATTERNS = [
1309
+ /\bPRAGMA\b/i,
1310
+ /\bsqlite_master\b/i,
1311
+ /(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
1312
+ /\bMATCH\b/i,
1313
+ /\bvector_distance_cos\s*\(/i,
1314
+ /\bjson_extract\s*\(/i,
1315
+ /\bjulianday\s*\(/i,
1316
+ /\bstrftime\s*\(/i,
1317
+ /\blast_insert_rowid\s*\(/i
1318
+ ];
1319
+ prismaClientPromise = null;
1320
+ compatibilityBootstrapPromise = null;
536
1321
  }
537
1322
  });
538
1323
 
539
1324
  // src/lib/database.ts
540
1325
  import { createClient } from "@libsql/client";
541
1326
  async function initDatabase(config) {
1327
+ if (_walCheckpointTimer) {
1328
+ clearInterval(_walCheckpointTimer);
1329
+ _walCheckpointTimer = null;
1330
+ }
1331
+ if (_daemonClient) {
1332
+ _daemonClient.close();
1333
+ _daemonClient = null;
1334
+ }
1335
+ if (_adapterClient && _adapterClient !== _resilientClient) {
1336
+ _adapterClient.close();
1337
+ }
1338
+ _adapterClient = null;
542
1339
  if (_client) {
543
1340
  _client.close();
544
1341
  _client = null;
@@ -552,6 +1349,7 @@ async function initDatabase(config) {
552
1349
  }
553
1350
  _client = createClient(opts);
554
1351
  _resilientClient = wrapWithRetry(_client);
1352
+ _adapterClient = _resilientClient;
555
1353
  _client.execute("PRAGMA busy_timeout = 30000").catch(() => {
556
1354
  });
557
1355
  _client.execute("PRAGMA journal_mode = WAL").catch(() => {
@@ -562,11 +1360,17 @@ async function initDatabase(config) {
562
1360
  });
563
1361
  }, 3e4);
564
1362
  _walCheckpointTimer.unref();
1363
+ if (process.env.DATABASE_URL) {
1364
+ _adapterClient = await createPrismaDbAdapter(_resilientClient);
1365
+ }
565
1366
  }
566
1367
  function getClient() {
567
- if (!_resilientClient) {
1368
+ if (!_adapterClient) {
568
1369
  throw new Error("Database client not initialized. Call initDatabase() first.");
569
1370
  }
1371
+ if (process.env.DATABASE_URL) {
1372
+ return _adapterClient;
1373
+ }
570
1374
  if (process.env.EXE_IS_DAEMON === "1") {
571
1375
  return _resilientClient;
572
1376
  }
@@ -859,6 +1663,7 @@ async function ensureSchema() {
859
1663
  project TEXT NOT NULL,
860
1664
  summary TEXT NOT NULL,
861
1665
  task_file TEXT,
1666
+ session_scope TEXT,
862
1667
  read INTEGER NOT NULL DEFAULT 0,
863
1668
  created_at TEXT NOT NULL
864
1669
  );
@@ -867,7 +1672,7 @@ async function ensureSchema() {
867
1672
  ON notifications(read);
868
1673
 
869
1674
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
870
- ON notifications(agent_id);
1675
+ ON notifications(agent_id, session_scope);
871
1676
 
872
1677
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
873
1678
  ON notifications(task_file);
@@ -905,6 +1710,7 @@ async function ensureSchema() {
905
1710
  target_agent TEXT NOT NULL,
906
1711
  target_project TEXT,
907
1712
  target_device TEXT NOT NULL DEFAULT 'local',
1713
+ session_scope TEXT,
908
1714
  content TEXT NOT NULL,
909
1715
  priority TEXT DEFAULT 'normal',
910
1716
  status TEXT DEFAULT 'pending',
@@ -918,10 +1724,31 @@ async function ensureSchema() {
918
1724
  );
919
1725
 
920
1726
  CREATE INDEX IF NOT EXISTS idx_messages_target
921
- ON messages(target_agent, status);
1727
+ ON messages(target_agent, session_scope, status);
922
1728
 
923
1729
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
924
- ON messages(target_agent, from_agent, server_seq);
1730
+ ON messages(target_agent, session_scope, from_agent, server_seq);
1731
+ `);
1732
+ try {
1733
+ await client.execute({
1734
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
1735
+ args: []
1736
+ });
1737
+ } catch {
1738
+ }
1739
+ try {
1740
+ await client.execute({
1741
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
1742
+ args: []
1743
+ });
1744
+ } catch {
1745
+ }
1746
+ await client.executeMultiple(`
1747
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
1748
+ ON notifications(agent_id, session_scope, read, created_at);
1749
+
1750
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
1751
+ ON messages(target_agent, session_scope, status, created_at);
925
1752
  `);
926
1753
  try {
927
1754
  await client.execute({
@@ -1505,28 +2332,45 @@ async function ensureSchema() {
1505
2332
  } catch {
1506
2333
  }
1507
2334
  }
2335
+ try {
2336
+ await client.execute({
2337
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
2338
+ args: []
2339
+ });
2340
+ } catch {
2341
+ }
1508
2342
  }
1509
2343
  async function disposeDatabase() {
2344
+ if (_walCheckpointTimer) {
2345
+ clearInterval(_walCheckpointTimer);
2346
+ _walCheckpointTimer = null;
2347
+ }
1510
2348
  if (_daemonClient) {
1511
2349
  _daemonClient.close();
1512
2350
  _daemonClient = null;
1513
2351
  }
2352
+ if (_adapterClient && _adapterClient !== _resilientClient) {
2353
+ _adapterClient.close();
2354
+ }
2355
+ _adapterClient = null;
1514
2356
  if (_client) {
1515
2357
  _client.close();
1516
2358
  _client = null;
1517
2359
  _resilientClient = null;
1518
2360
  }
1519
2361
  }
1520
- var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
2362
+ var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
1521
2363
  var init_database = __esm({
1522
2364
  "src/lib/database.ts"() {
1523
2365
  "use strict";
1524
2366
  init_db_retry();
1525
2367
  init_employees();
2368
+ init_database_adapter();
1526
2369
  _client = null;
1527
2370
  _resilientClient = null;
1528
2371
  _walCheckpointTimer = null;
1529
2372
  _daemonClient = null;
2373
+ _adapterClient = null;
1530
2374
  initTurso = initDatabase;
1531
2375
  disposeTurso = disposeDatabase;
1532
2376
  }
@@ -1534,14 +2378,14 @@ var init_database = __esm({
1534
2378
 
1535
2379
  // src/lib/keychain.ts
1536
2380
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
1537
- import { existsSync as existsSync3 } from "fs";
1538
- import path3 from "path";
1539
- import os3 from "os";
2381
+ import { existsSync as existsSync5 } from "fs";
2382
+ import path5 from "path";
2383
+ import os4 from "os";
1540
2384
  function getKeyDir() {
1541
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path3.join(os3.homedir(), ".exe-os");
2385
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path5.join(os4.homedir(), ".exe-os");
1542
2386
  }
1543
2387
  function getKeyPath() {
1544
- return path3.join(getKeyDir(), "master.key");
2388
+ return path5.join(getKeyDir(), "master.key");
1545
2389
  }
1546
2390
  async function tryKeytar() {
1547
2391
  try {
@@ -1562,9 +2406,9 @@ async function getMasterKey() {
1562
2406
  }
1563
2407
  }
1564
2408
  const keyPath = getKeyPath();
1565
- if (!existsSync3(keyPath)) {
2409
+ if (!existsSync5(keyPath)) {
1566
2410
  process.stderr.write(
1567
- `[keychain] Key not found at ${keyPath} (HOME=${os3.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
2411
+ `[keychain] Key not found at ${keyPath} (HOME=${os4.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
1568
2412
  `
1569
2413
  );
1570
2414
  return null;
@@ -1649,6 +2493,7 @@ var shard_manager_exports = {};
1649
2493
  __export(shard_manager_exports, {
1650
2494
  disposeShards: () => disposeShards,
1651
2495
  ensureShardSchema: () => ensureShardSchema,
2496
+ getOpenShardCount: () => getOpenShardCount,
1652
2497
  getReadyShardClient: () => getReadyShardClient,
1653
2498
  getShardClient: () => getShardClient,
1654
2499
  getShardsDir: () => getShardsDir,
@@ -1657,15 +2502,18 @@ __export(shard_manager_exports, {
1657
2502
  listShards: () => listShards,
1658
2503
  shardExists: () => shardExists
1659
2504
  });
1660
- import path4 from "path";
1661
- import { existsSync as existsSync4, mkdirSync, readdirSync } from "fs";
2505
+ import path6 from "path";
2506
+ import { existsSync as existsSync6, mkdirSync as mkdirSync2, readdirSync } from "fs";
1662
2507
  import { createClient as createClient2 } from "@libsql/client";
1663
2508
  function initShardManager(encryptionKey) {
1664
2509
  _encryptionKey = encryptionKey;
1665
- if (!existsSync4(SHARDS_DIR)) {
1666
- mkdirSync(SHARDS_DIR, { recursive: true });
2510
+ if (!existsSync6(SHARDS_DIR)) {
2511
+ mkdirSync2(SHARDS_DIR, { recursive: true });
1667
2512
  }
1668
2513
  _shardingEnabled = true;
2514
+ if (_evictionTimer) clearInterval(_evictionTimer);
2515
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
2516
+ _evictionTimer.unref();
1669
2517
  }
1670
2518
  function isShardingEnabled() {
1671
2519
  return _shardingEnabled;
@@ -1682,21 +2530,28 @@ function getShardClient(projectName) {
1682
2530
  throw new Error(`Invalid project name for shard: "${projectName}"`);
1683
2531
  }
1684
2532
  const cached = _shards.get(safeName);
1685
- if (cached) return cached;
1686
- const dbPath = path4.join(SHARDS_DIR, `${safeName}.db`);
2533
+ if (cached) {
2534
+ _shardLastAccess.set(safeName, Date.now());
2535
+ return cached;
2536
+ }
2537
+ while (_shards.size >= MAX_OPEN_SHARDS) {
2538
+ evictLRU();
2539
+ }
2540
+ const dbPath = path6.join(SHARDS_DIR, `${safeName}.db`);
1687
2541
  const client = createClient2({
1688
2542
  url: `file:${dbPath}`,
1689
2543
  encryptionKey: _encryptionKey
1690
2544
  });
1691
2545
  _shards.set(safeName, client);
2546
+ _shardLastAccess.set(safeName, Date.now());
1692
2547
  return client;
1693
2548
  }
1694
2549
  function shardExists(projectName) {
1695
2550
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
1696
- return existsSync4(path4.join(SHARDS_DIR, `${safeName}.db`));
2551
+ return existsSync6(path6.join(SHARDS_DIR, `${safeName}.db`));
1697
2552
  }
1698
2553
  function listShards() {
1699
- if (!existsSync4(SHARDS_DIR)) return [];
2554
+ if (!existsSync6(SHARDS_DIR)) return [];
1700
2555
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
1701
2556
  }
1702
2557
  async function ensureShardSchema(client) {
@@ -1748,6 +2603,8 @@ async function ensureShardSchema(client) {
1748
2603
  for (const col of [
1749
2604
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
1750
2605
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
2606
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
2607
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
1751
2608
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
1752
2609
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
1753
2610
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -1770,7 +2627,23 @@ async function ensureShardSchema(client) {
1770
2627
  // MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
1771
2628
  "ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
1772
2629
  "ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
1773
- "ALTER TABLE memories ADD COLUMN trajectory TEXT"
2630
+ "ALTER TABLE memories ADD COLUMN trajectory TEXT",
2631
+ // Metadata enrichment columns (must match database.ts)
2632
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
2633
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
2634
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
2635
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
2636
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
2637
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
2638
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
2639
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
2640
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
2641
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
2642
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
2643
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
2644
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
2645
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
2646
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
1774
2647
  ]) {
1775
2648
  try {
1776
2649
  await client.execute(col);
@@ -1869,21 +2742,69 @@ async function getReadyShardClient(projectName) {
1869
2742
  await ensureShardSchema(client);
1870
2743
  return client;
1871
2744
  }
2745
+ function evictLRU() {
2746
+ let oldest = null;
2747
+ let oldestTime = Infinity;
2748
+ for (const [name, time] of _shardLastAccess) {
2749
+ if (time < oldestTime) {
2750
+ oldestTime = time;
2751
+ oldest = name;
2752
+ }
2753
+ }
2754
+ if (oldest) {
2755
+ const client = _shards.get(oldest);
2756
+ if (client) {
2757
+ client.close();
2758
+ }
2759
+ _shards.delete(oldest);
2760
+ _shardLastAccess.delete(oldest);
2761
+ }
2762
+ }
2763
+ function evictIdleShards() {
2764
+ const now = Date.now();
2765
+ const toEvict = [];
2766
+ for (const [name, lastAccess] of _shardLastAccess) {
2767
+ if (now - lastAccess > SHARD_IDLE_MS) {
2768
+ toEvict.push(name);
2769
+ }
2770
+ }
2771
+ for (const name of toEvict) {
2772
+ const client = _shards.get(name);
2773
+ if (client) {
2774
+ client.close();
2775
+ }
2776
+ _shards.delete(name);
2777
+ _shardLastAccess.delete(name);
2778
+ }
2779
+ }
2780
+ function getOpenShardCount() {
2781
+ return _shards.size;
2782
+ }
1872
2783
  function disposeShards() {
2784
+ if (_evictionTimer) {
2785
+ clearInterval(_evictionTimer);
2786
+ _evictionTimer = null;
2787
+ }
1873
2788
  for (const [, client] of _shards) {
1874
2789
  client.close();
1875
2790
  }
1876
2791
  _shards.clear();
2792
+ _shardLastAccess.clear();
1877
2793
  _shardingEnabled = false;
1878
2794
  _encryptionKey = null;
1879
2795
  }
1880
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
2796
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
1881
2797
  var init_shard_manager = __esm({
1882
2798
  "src/lib/shard-manager.ts"() {
1883
2799
  "use strict";
1884
2800
  init_config();
1885
- SHARDS_DIR = path4.join(EXE_AI_DIR, "shards");
2801
+ SHARDS_DIR = path6.join(EXE_AI_DIR, "shards");
2802
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
2803
+ MAX_OPEN_SHARDS = 10;
2804
+ EVICTION_INTERVAL_MS = 60 * 1e3;
1886
2805
  _shards = /* @__PURE__ */ new Map();
2806
+ _shardLastAccess = /* @__PURE__ */ new Map();
2807
+ _evictionTimer = null;
1887
2808
  _encryptionKey = null;
1888
2809
  _shardingEnabled = false;
1889
2810
  }
@@ -2745,8 +3666,8 @@ __export(reranker_exports, {
2745
3666
  rerankWithContext: () => rerankWithContext,
2746
3667
  rerankWithScores: () => rerankWithScores
2747
3668
  });
2748
- import path5 from "path";
2749
- import { existsSync as existsSync5 } from "fs";
3669
+ import path7 from "path";
3670
+ import { existsSync as existsSync7 } from "fs";
2750
3671
  function resetIdleTimer() {
2751
3672
  if (_idleTimer) clearTimeout(_idleTimer);
2752
3673
  _idleTimer = setTimeout(() => {
@@ -2757,18 +3678,18 @@ function resetIdleTimer() {
2757
3678
  }
2758
3679
  }
2759
3680
  function isRerankerAvailable() {
2760
- return existsSync5(path5.join(MODELS_DIR, RERANKER_MODEL_FILE));
3681
+ return existsSync7(path7.join(MODELS_DIR, RERANKER_MODEL_FILE));
2761
3682
  }
2762
3683
  function getRerankerModelPath() {
2763
- return path5.join(MODELS_DIR, RERANKER_MODEL_FILE);
3684
+ return path7.join(MODELS_DIR, RERANKER_MODEL_FILE);
2764
3685
  }
2765
3686
  async function ensureLoaded() {
2766
3687
  if (_rerankerContext) {
2767
3688
  resetIdleTimer();
2768
3689
  return;
2769
3690
  }
2770
- const modelPath = path5.join(MODELS_DIR, RERANKER_MODEL_FILE);
2771
- if (!existsSync5(modelPath)) {
3691
+ const modelPath = path7.join(MODELS_DIR, RERANKER_MODEL_FILE);
3692
+ if (!existsSync7(modelPath)) {
2772
3693
  throw new Error(
2773
3694
  `Reranker model not found at ${modelPath}. Run /exe-setup to download it.`
2774
3695
  );
@@ -2864,13 +3785,50 @@ var init_reranker = __esm({
2864
3785
  }
2865
3786
  });
2866
3787
 
3788
+ // src/lib/daemon-auth.ts
3789
+ import crypto from "crypto";
3790
+ import path8 from "path";
3791
+ import { existsSync as existsSync8, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
3792
+ function normalizeToken(token) {
3793
+ if (!token) return null;
3794
+ const trimmed = token.trim();
3795
+ return trimmed.length > 0 ? trimmed : null;
3796
+ }
3797
+ function readDaemonToken() {
3798
+ try {
3799
+ if (!existsSync8(DAEMON_TOKEN_PATH)) return null;
3800
+ return normalizeToken(readFileSync4(DAEMON_TOKEN_PATH, "utf8"));
3801
+ } catch {
3802
+ return null;
3803
+ }
3804
+ }
3805
+ function ensureDaemonToken(seed) {
3806
+ const existing = readDaemonToken();
3807
+ if (existing) return existing;
3808
+ const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
3809
+ ensurePrivateDirSync(EXE_AI_DIR);
3810
+ writeFileSync3(DAEMON_TOKEN_PATH, `${token}
3811
+ `, "utf8");
3812
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
3813
+ return token;
3814
+ }
3815
+ var DAEMON_TOKEN_PATH;
3816
+ var init_daemon_auth = __esm({
3817
+ "src/lib/daemon-auth.ts"() {
3818
+ "use strict";
3819
+ init_config();
3820
+ init_secure_files();
3821
+ DAEMON_TOKEN_PATH = path8.join(EXE_AI_DIR, "exed.token");
3822
+ }
3823
+ });
3824
+
2867
3825
  // src/lib/exe-daemon-client.ts
2868
3826
  import net from "net";
2869
- import os4 from "os";
3827
+ import os5 from "os";
2870
3828
  import { spawn } from "child_process";
2871
3829
  import { randomUUID as randomUUID2 } from "crypto";
2872
- import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
2873
- import path6 from "path";
3830
+ import { existsSync as existsSync9, unlinkSync as unlinkSync2, readFileSync as readFileSync5, openSync, closeSync, statSync } from "fs";
3831
+ import path9 from "path";
2874
3832
  import { fileURLToPath } from "url";
2875
3833
  function handleData(chunk) {
2876
3834
  _buffer += chunk.toString();
@@ -2898,9 +3856,9 @@ function handleData(chunk) {
2898
3856
  }
2899
3857
  }
2900
3858
  function cleanupStaleFiles() {
2901
- if (existsSync6(PID_PATH)) {
3859
+ if (existsSync9(PID_PATH)) {
2902
3860
  try {
2903
- const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
3861
+ const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
2904
3862
  if (pid > 0) {
2905
3863
  try {
2906
3864
  process.kill(pid, 0);
@@ -2921,17 +3879,17 @@ function cleanupStaleFiles() {
2921
3879
  }
2922
3880
  }
2923
3881
  function findPackageRoot() {
2924
- let dir = path6.dirname(fileURLToPath(import.meta.url));
2925
- const { root } = path6.parse(dir);
3882
+ let dir = path9.dirname(fileURLToPath(import.meta.url));
3883
+ const { root } = path9.parse(dir);
2926
3884
  while (dir !== root) {
2927
- if (existsSync6(path6.join(dir, "package.json"))) return dir;
2928
- dir = path6.dirname(dir);
3885
+ if (existsSync9(path9.join(dir, "package.json"))) return dir;
3886
+ dir = path9.dirname(dir);
2929
3887
  }
2930
3888
  return null;
2931
3889
  }
2932
3890
  function spawnDaemon() {
2933
- const freeGB = os4.freemem() / (1024 * 1024 * 1024);
2934
- const totalGB = os4.totalmem() / (1024 * 1024 * 1024);
3891
+ const freeGB = os5.freemem() / (1024 * 1024 * 1024);
3892
+ const totalGB = os5.totalmem() / (1024 * 1024 * 1024);
2935
3893
  if (totalGB <= 8) {
2936
3894
  process.stderr.write(
2937
3895
  `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
@@ -2951,16 +3909,17 @@ function spawnDaemon() {
2951
3909
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
2952
3910
  return;
2953
3911
  }
2954
- const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
2955
- if (!existsSync6(daemonPath)) {
3912
+ const daemonPath = path9.join(pkgRoot, "dist", "lib", "exe-daemon.js");
3913
+ if (!existsSync9(daemonPath)) {
2956
3914
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
2957
3915
  `);
2958
3916
  return;
2959
3917
  }
2960
3918
  const resolvedPath = daemonPath;
3919
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
2961
3920
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
2962
3921
  `);
2963
- const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
3922
+ const logPath = path9.join(path9.dirname(SOCKET_PATH), "exed.log");
2964
3923
  let stderrFd = "ignore";
2965
3924
  try {
2966
3925
  stderrFd = openSync(logPath, "a");
@@ -2978,7 +3937,8 @@ function spawnDaemon() {
2978
3937
  TMUX_PANE: void 0,
2979
3938
  // Prevents resolveExeSession() from scoping to one session
2980
3939
  EXE_DAEMON_SOCK: SOCKET_PATH,
2981
- EXE_DAEMON_PID: PID_PATH
3940
+ EXE_DAEMON_PID: PID_PATH,
3941
+ [DAEMON_TOKEN_ENV]: daemonToken
2982
3942
  }
2983
3943
  });
2984
3944
  child.unref();
@@ -3088,13 +4048,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
3088
4048
  return;
3089
4049
  }
3090
4050
  const id = randomUUID2();
4051
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
3091
4052
  const timer = setTimeout(() => {
3092
4053
  _pending.delete(id);
3093
4054
  resolve({ error: "Request timeout" });
3094
4055
  }, timeoutMs);
3095
4056
  _pending.set(id, { resolve, timer });
3096
4057
  try {
3097
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
4058
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
3098
4059
  } catch {
3099
4060
  clearTimeout(timer);
3100
4061
  _pending.delete(id);
@@ -3111,74 +4072,123 @@ async function pingDaemon() {
3111
4072
  return null;
3112
4073
  }
3113
4074
  function killAndRespawnDaemon() {
3114
- process.stderr.write("[exed-client] Killing daemon for restart...\n");
3115
- if (existsSync6(PID_PATH)) {
3116
- try {
3117
- const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
3118
- if (pid > 0) {
3119
- try {
3120
- process.kill(pid, "SIGKILL");
3121
- } catch {
4075
+ if (!acquireSpawnLock()) {
4076
+ process.stderr.write("[exed-client] Another process is already restarting daemon \u2014 skipping\n");
4077
+ if (_socket) {
4078
+ _socket.destroy();
4079
+ _socket = null;
4080
+ }
4081
+ _connected = false;
4082
+ _buffer = "";
4083
+ return;
4084
+ }
4085
+ try {
4086
+ process.stderr.write("[exed-client] Killing daemon for restart...\n");
4087
+ if (existsSync9(PID_PATH)) {
4088
+ try {
4089
+ const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
4090
+ if (pid > 0) {
4091
+ try {
4092
+ process.kill(pid, "SIGKILL");
4093
+ } catch {
4094
+ }
3122
4095
  }
4096
+ } catch {
3123
4097
  }
4098
+ }
4099
+ if (_socket) {
4100
+ _socket.destroy();
4101
+ _socket = null;
4102
+ }
4103
+ _connected = false;
4104
+ _buffer = "";
4105
+ try {
4106
+ unlinkSync2(PID_PATH);
3124
4107
  } catch {
3125
4108
  }
4109
+ try {
4110
+ unlinkSync2(SOCKET_PATH);
4111
+ } catch {
4112
+ }
4113
+ spawnDaemon();
4114
+ } finally {
4115
+ releaseSpawnLock();
3126
4116
  }
3127
- if (_socket) {
3128
- _socket.destroy();
3129
- _socket = null;
3130
- }
3131
- _connected = false;
3132
- _buffer = "";
4117
+ }
4118
+ function isDaemonTooYoung() {
3133
4119
  try {
3134
- unlinkSync2(PID_PATH);
4120
+ const stat = statSync(PID_PATH);
4121
+ return Date.now() - stat.mtimeMs < MIN_DAEMON_AGE_MS;
3135
4122
  } catch {
4123
+ return false;
3136
4124
  }
3137
- try {
3138
- unlinkSync2(SOCKET_PATH);
3139
- } catch {
4125
+ }
4126
+ async function retryThenRestart(doRequest, label) {
4127
+ const result = await doRequest();
4128
+ if (!result.error) {
4129
+ _consecutiveFailures = 0;
4130
+ return result;
3140
4131
  }
3141
- spawnDaemon();
4132
+ _consecutiveFailures++;
4133
+ for (let i = 0; i < MAX_RETRIES_BEFORE_RESTART; i++) {
4134
+ const delayMs = RETRY_DELAYS_MS[i] ?? 5e3;
4135
+ process.stderr.write(`[exed-client] ${label} failed (${result.error}), retry ${i + 1}/${MAX_RETRIES_BEFORE_RESTART} in ${delayMs}ms
4136
+ `);
4137
+ await new Promise((r) => setTimeout(r, delayMs));
4138
+ if (!_connected) {
4139
+ if (!await connectToSocket()) continue;
4140
+ }
4141
+ const retry = await doRequest();
4142
+ if (!retry.error) {
4143
+ _consecutiveFailures = 0;
4144
+ return retry;
4145
+ }
4146
+ _consecutiveFailures++;
4147
+ }
4148
+ if (isDaemonTooYoung()) {
4149
+ process.stderr.write(`[exed-client] ${label}: daemon too young (< ${MIN_DAEMON_AGE_MS / 1e3}s) \u2014 skipping restart
4150
+ `);
4151
+ return { error: result.error };
4152
+ }
4153
+ process.stderr.write(`[exed-client] ${label}: ${_consecutiveFailures} consecutive failures \u2014 restarting daemon
4154
+ `);
4155
+ killAndRespawnDaemon();
4156
+ const start = Date.now();
4157
+ let delay2 = 200;
4158
+ while (Date.now() - start < CONNECT_TIMEOUT_MS) {
4159
+ await new Promise((r) => setTimeout(r, delay2));
4160
+ if (await connectToSocket()) break;
4161
+ delay2 = Math.min(delay2 * 2, 3e3);
4162
+ }
4163
+ if (!_connected) return { error: "Daemon restart failed" };
4164
+ const final = await doRequest();
4165
+ if (!final.error) _consecutiveFailures = 0;
4166
+ return final;
3142
4167
  }
3143
4168
  async function embedViaClient(text, priority = "high") {
3144
4169
  if (!_connected && !await connectEmbedDaemon()) return null;
3145
4170
  _requestCount++;
3146
4171
  if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
3147
4172
  const health = await pingDaemon();
3148
- if (!health) {
4173
+ if (!health && !isDaemonTooYoung()) {
3149
4174
  process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
3150
4175
  `);
3151
4176
  killAndRespawnDaemon();
3152
4177
  const start = Date.now();
3153
- let delay2 = 200;
4178
+ let d = 200;
3154
4179
  while (Date.now() - start < CONNECT_TIMEOUT_MS) {
3155
- await new Promise((r) => setTimeout(r, delay2));
4180
+ await new Promise((r) => setTimeout(r, d));
3156
4181
  if (await connectToSocket()) break;
3157
- delay2 = Math.min(delay2 * 2, 3e3);
4182
+ d = Math.min(d * 2, 3e3);
3158
4183
  }
3159
4184
  if (!_connected) return null;
3160
4185
  }
3161
4186
  }
3162
- const result = await sendRequest([text], priority);
3163
- if (!result.error && result.vectors?.[0]) return result.vectors[0];
3164
- if (result.error) {
3165
- process.stderr.write(`[exed-client] Embed failed (${result.error}) \u2014 attempting restart
3166
- `);
3167
- killAndRespawnDaemon();
3168
- const start = Date.now();
3169
- let delay2 = 200;
3170
- while (Date.now() - start < CONNECT_TIMEOUT_MS) {
3171
- await new Promise((r) => setTimeout(r, delay2));
3172
- if (await connectToSocket()) break;
3173
- delay2 = Math.min(delay2 * 2, 3e3);
3174
- }
3175
- if (!_connected) return null;
3176
- const retry = await sendRequest([text], priority);
3177
- if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
3178
- process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
3179
- `);
3180
- }
3181
- return null;
4187
+ const result = await retryThenRestart(
4188
+ () => sendRequest([text], priority),
4189
+ "Embed"
4190
+ );
4191
+ return !result.error && result.vectors?.[0] ? result.vectors[0] : null;
3182
4192
  }
3183
4193
  function disconnectClient() {
3184
4194
  if (_socket) {
@@ -3193,22 +4203,28 @@ function disconnectClient() {
3193
4203
  entry.resolve({ error: "Client disconnected" });
3194
4204
  }
3195
4205
  }
3196
- var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, HEALTH_CHECK_INTERVAL, _pending, MAX_BUFFER;
4206
+ 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;
3197
4207
  var init_exe_daemon_client = __esm({
3198
4208
  "src/lib/exe-daemon-client.ts"() {
3199
4209
  "use strict";
3200
4210
  init_config();
3201
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
3202
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
3203
- SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
4211
+ init_daemon_auth();
4212
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path9.join(EXE_AI_DIR, "exed.sock");
4213
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path9.join(EXE_AI_DIR, "exed.pid");
4214
+ SPAWN_LOCK_PATH = path9.join(EXE_AI_DIR, "exed-spawn.lock");
3204
4215
  SPAWN_LOCK_STALE_MS = 3e4;
3205
4216
  CONNECT_TIMEOUT_MS = 15e3;
3206
4217
  REQUEST_TIMEOUT_MS = 3e4;
4218
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
3207
4219
  _socket = null;
3208
4220
  _connected = false;
3209
4221
  _buffer = "";
3210
4222
  _requestCount = 0;
4223
+ _consecutiveFailures = 0;
3211
4224
  HEALTH_CHECK_INTERVAL = 100;
4225
+ MAX_RETRIES_BEFORE_RESTART = 3;
4226
+ RETRY_DELAYS_MS = [1e3, 3e3, 5e3];
4227
+ MIN_DAEMON_AGE_MS = 3e4;
3212
4228
  _pending = /* @__PURE__ */ new Map();
3213
4229
  MAX_BUFFER = 1e7;
3214
4230
  }
@@ -3251,10 +4267,10 @@ async function disposeEmbedder() {
3251
4267
  async function embedDirect(text) {
3252
4268
  const llamaCpp = await import("node-llama-cpp");
3253
4269
  const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
3254
- const { existsSync: existsSync18 } = await import("fs");
3255
- const path22 = await import("path");
3256
- const modelPath = path22.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
3257
- if (!existsSync18(modelPath)) {
4270
+ const { existsSync: existsSync20 } = await import("fs");
4271
+ const path24 = await import("path");
4272
+ const modelPath = path24.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
4273
+ if (!existsSync20(modelPath)) {
3258
4274
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
3259
4275
  }
3260
4276
  const llama = await llamaCpp.getLlama();
@@ -3289,7 +4305,7 @@ __export(project_name_exports, {
3289
4305
  getProjectName: () => getProjectName
3290
4306
  });
3291
4307
  import { execSync as execSync2 } from "child_process";
3292
- import path7 from "path";
4308
+ import path10 from "path";
3293
4309
  function getProjectName(cwd) {
3294
4310
  const dir = cwd ?? process.cwd();
3295
4311
  if (_cached && _cachedCwd === dir) return _cached;
@@ -3302,7 +4318,7 @@ function getProjectName(cwd) {
3302
4318
  timeout: 2e3,
3303
4319
  stdio: ["pipe", "pipe", "pipe"]
3304
4320
  }).trim();
3305
- repoRoot = path7.dirname(gitCommonDir);
4321
+ repoRoot = path10.dirname(gitCommonDir);
3306
4322
  } catch {
3307
4323
  repoRoot = execSync2("git rev-parse --show-toplevel", {
3308
4324
  cwd: dir,
@@ -3311,11 +4327,11 @@ function getProjectName(cwd) {
3311
4327
  stdio: ["pipe", "pipe", "pipe"]
3312
4328
  }).trim();
3313
4329
  }
3314
- _cached = path7.basename(repoRoot);
4330
+ _cached = path10.basename(repoRoot);
3315
4331
  _cachedCwd = dir;
3316
4332
  return _cached;
3317
4333
  } catch {
3318
- _cached = path7.basename(dir);
4334
+ _cached = path10.basename(dir);
3319
4335
  _cachedCwd = dir;
3320
4336
  return _cached;
3321
4337
  }
@@ -3339,9 +4355,9 @@ __export(file_grep_exports, {
3339
4355
  grepProjectFiles: () => grepProjectFiles
3340
4356
  });
3341
4357
  import { execSync as execSync3 } from "child_process";
3342
- import { readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync2, existsSync as existsSync7 } from "fs";
3343
- import path8 from "path";
3344
- import crypto from "crypto";
4358
+ import { readFileSync as readFileSync6, readdirSync as readdirSync2, statSync as statSync2, existsSync as existsSync10 } from "fs";
4359
+ import path11 from "path";
4360
+ import crypto2 from "crypto";
3345
4361
  function hasRipgrep() {
3346
4362
  if (_hasRg === null) {
3347
4363
  try {
@@ -3374,13 +4390,13 @@ async function grepProjectFiles(query, projectRoot, options) {
3374
4390
  const chunkCtx = getChunkContext(hit.filePath, hit.lineNumber);
3375
4391
  const prefix = chunkCtx ? `[file: ${hit.filePath}:${hit.lineNumber} in ${chunkCtx}]` : `[file: ${hit.filePath}:${hit.lineNumber}]`;
3376
4392
  return {
3377
- id: crypto.createHash("sha256").update(`${hit.filePath}:${hit.lineNumber}`).digest("hex").slice(0, 36),
4393
+ id: crypto2.createHash("sha256").update(`${hit.filePath}:${hit.lineNumber}`).digest("hex").slice(0, 36),
3378
4394
  agent_id: "project",
3379
4395
  agent_role: "file",
3380
4396
  session_id: "file-grep",
3381
4397
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3382
4398
  tool_name: "file_grep",
3383
- project_name: path8.basename(projectRoot),
4399
+ project_name: path11.basename(projectRoot),
3384
4400
  has_error: false,
3385
4401
  raw_text: `${prefix} ${buildSnippet(hit, projectRoot)}`,
3386
4402
  vector: null,
@@ -3392,7 +4408,7 @@ function getChunkContext(filePath, lineNumber) {
3392
4408
  try {
3393
4409
  const ext = filePath.split(".").pop()?.toLowerCase();
3394
4410
  if (ext !== "ts" && ext !== "tsx" && ext !== "js" && ext !== "jsx") return "";
3395
- const source = readFileSync4(filePath, "utf8");
4411
+ const source = readFileSync6(filePath, "utf8");
3396
4412
  const lines = source.split("\n");
3397
4413
  for (let i = Math.min(lineNumber - 1, lines.length - 1); i >= 0; i--) {
3398
4414
  const line = lines[i];
@@ -3454,11 +4470,11 @@ function grepWithNodeFs(pattern, projectRoot, patterns) {
3454
4470
  const files = collectFiles(projectRoot, patterns ?? DEFAULT_PATTERNS);
3455
4471
  const hits = [];
3456
4472
  for (const filePath of files.slice(0, MAX_FILES)) {
3457
- const absPath = path8.join(projectRoot, filePath);
4473
+ const absPath = path11.join(projectRoot, filePath);
3458
4474
  try {
3459
4475
  const stat = statSync2(absPath);
3460
4476
  if (stat.size > MAX_FILE_SIZE) continue;
3461
- const content = readFileSync4(absPath, "utf8");
4477
+ const content = readFileSync6(absPath, "utf8");
3462
4478
  const lines = content.split("\n");
3463
4479
  const matches = content.match(regex);
3464
4480
  if (!matches || matches.length === 0) continue;
@@ -3481,15 +4497,15 @@ function collectFiles(root, patterns) {
3481
4497
  const files = [];
3482
4498
  function walk(dir, relative) {
3483
4499
  if (files.length >= MAX_FILES) return;
3484
- const basename = path8.basename(dir);
4500
+ const basename = path11.basename(dir);
3485
4501
  if (EXCLUDE_DIRS.includes(basename)) return;
3486
4502
  try {
3487
4503
  const entries = readdirSync2(dir, { withFileTypes: true });
3488
4504
  for (const entry of entries) {
3489
4505
  if (files.length >= MAX_FILES) return;
3490
- const rel = path8.join(relative, entry.name);
4506
+ const rel = path11.join(relative, entry.name);
3491
4507
  if (entry.isDirectory()) {
3492
- walk(path8.join(dir, entry.name), rel);
4508
+ walk(path11.join(dir, entry.name), rel);
3493
4509
  } else if (entry.isFile()) {
3494
4510
  for (const pat of patterns) {
3495
4511
  if (matchGlob(rel, pat)) {
@@ -3521,7 +4537,7 @@ function matchGlob(filePath, pattern) {
3521
4537
  if (slashIdx !== -1) {
3522
4538
  const dir = pattern.slice(0, slashIdx);
3523
4539
  const ext2 = pattern.slice(slashIdx + 1).replace("*", "");
3524
- const fileDir = path8.dirname(filePath);
4540
+ const fileDir = path11.dirname(filePath);
3525
4541
  return fileDir === dir && filePath.endsWith(ext2);
3526
4542
  }
3527
4543
  const ext = pattern.replace("*", "");
@@ -3529,9 +4545,9 @@ function matchGlob(filePath, pattern) {
3529
4545
  }
3530
4546
  function buildSnippet(hit, projectRoot) {
3531
4547
  try {
3532
- const absPath = path8.join(projectRoot, hit.filePath);
3533
- if (!existsSync7(absPath)) return hit.matchLine;
3534
- const lines = readFileSync4(absPath, "utf8").split("\n");
4548
+ const absPath = path11.join(projectRoot, hit.filePath);
4549
+ if (!existsSync10(absPath)) return hit.matchLine;
4550
+ const lines = readFileSync6(absPath, "utf8").split("\n");
3535
4551
  const start = Math.max(0, hit.lineNumber - 3);
3536
4552
  const end = Math.min(lines.length, hit.lineNumber + 2);
3537
4553
  return lines.slice(start, end).join("\n").slice(0, 500);
@@ -4196,13 +5212,13 @@ var init_session_key = __esm({
4196
5212
  });
4197
5213
 
4198
5214
  // src/lib/session-registry.ts
4199
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync8 } from "fs";
4200
- import path10 from "path";
4201
- import os5 from "os";
5215
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, existsSync as existsSync11 } from "fs";
5216
+ import path13 from "path";
5217
+ import os6 from "os";
4202
5218
  function registerSession(entry) {
4203
- const dir = path10.dirname(REGISTRY_PATH);
4204
- if (!existsSync8(dir)) {
4205
- mkdirSync3(dir, { recursive: true });
5219
+ const dir = path13.dirname(REGISTRY_PATH);
5220
+ if (!existsSync11(dir)) {
5221
+ mkdirSync4(dir, { recursive: true });
4206
5222
  }
4207
5223
  const sessions = listSessions();
4208
5224
  const idx = sessions.findIndex((s) => s.windowName === entry.windowName);
@@ -4211,11 +5227,11 @@ function registerSession(entry) {
4211
5227
  } else {
4212
5228
  sessions.push(entry);
4213
5229
  }
4214
- writeFileSync3(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
5230
+ writeFileSync5(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
4215
5231
  }
4216
5232
  function listSessions() {
4217
5233
  try {
4218
- const raw = readFileSync6(REGISTRY_PATH, "utf8");
5234
+ const raw = readFileSync8(REGISTRY_PATH, "utf8");
4219
5235
  return JSON.parse(raw);
4220
5236
  } catch {
4221
5237
  return [];
@@ -4225,7 +5241,7 @@ var REGISTRY_PATH;
4225
5241
  var init_session_registry = __esm({
4226
5242
  "src/lib/session-registry.ts"() {
4227
5243
  "use strict";
4228
- REGISTRY_PATH = path10.join(os5.homedir(), ".exe-os", "session-registry.json");
5244
+ REGISTRY_PATH = path13.join(os6.homedir(), ".exe-os", "session-registry.json");
4229
5245
  }
4230
5246
  });
4231
5247
 
@@ -4417,118 +5433,6 @@ var init_provider_table = __esm({
4417
5433
  }
4418
5434
  });
4419
5435
 
4420
- // src/lib/runtime-table.ts
4421
- var RUNTIME_TABLE, DEFAULT_RUNTIME;
4422
- var init_runtime_table = __esm({
4423
- "src/lib/runtime-table.ts"() {
4424
- "use strict";
4425
- RUNTIME_TABLE = {
4426
- codex: {
4427
- binary: "codex",
4428
- launchMode: "interactive",
4429
- autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
4430
- inlineFlag: "--no-alt-screen",
4431
- apiKeyEnv: "OPENAI_API_KEY",
4432
- defaultModel: "gpt-5.4"
4433
- },
4434
- opencode: {
4435
- binary: "opencode",
4436
- launchMode: "exec",
4437
- autoApproveFlag: "--dangerously-skip-permissions",
4438
- inlineFlag: "",
4439
- apiKeyEnv: "ANTHROPIC_API_KEY",
4440
- defaultModel: "anthropic/claude-sonnet-4-6"
4441
- }
4442
- };
4443
- DEFAULT_RUNTIME = "claude";
4444
- }
4445
- });
4446
-
4447
- // src/lib/agent-config.ts
4448
- var agent_config_exports = {};
4449
- __export(agent_config_exports, {
4450
- AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
4451
- DEFAULT_MODELS: () => DEFAULT_MODELS,
4452
- KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
4453
- RUNTIME_LABELS: () => RUNTIME_LABELS,
4454
- clearAgentRuntime: () => clearAgentRuntime,
4455
- getAgentRuntime: () => getAgentRuntime,
4456
- loadAgentConfig: () => loadAgentConfig,
4457
- saveAgentConfig: () => saveAgentConfig,
4458
- setAgentRuntime: () => setAgentRuntime
4459
- });
4460
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, existsSync as existsSync9, mkdirSync as mkdirSync4 } from "fs";
4461
- import path11 from "path";
4462
- function loadAgentConfig() {
4463
- if (!existsSync9(AGENT_CONFIG_PATH)) return {};
4464
- try {
4465
- return JSON.parse(readFileSync7(AGENT_CONFIG_PATH, "utf-8"));
4466
- } catch {
4467
- return {};
4468
- }
4469
- }
4470
- function saveAgentConfig(config) {
4471
- const dir = path11.dirname(AGENT_CONFIG_PATH);
4472
- if (!existsSync9(dir)) mkdirSync4(dir, { recursive: true });
4473
- writeFileSync4(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
4474
- }
4475
- function getAgentRuntime(agentId) {
4476
- const config = loadAgentConfig();
4477
- const entry = config[agentId];
4478
- if (entry) return entry;
4479
- const orgDefault = config["default"];
4480
- if (orgDefault) return orgDefault;
4481
- return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
4482
- }
4483
- function setAgentRuntime(agentId, runtime, model) {
4484
- const knownModels = KNOWN_RUNTIMES[runtime];
4485
- if (!knownModels) {
4486
- return {
4487
- ok: false,
4488
- error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
4489
- };
4490
- }
4491
- if (!knownModels.includes(model)) {
4492
- return {
4493
- ok: false,
4494
- error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
4495
- };
4496
- }
4497
- const config = loadAgentConfig();
4498
- config[agentId] = { runtime, model };
4499
- saveAgentConfig(config);
4500
- return { ok: true };
4501
- }
4502
- function clearAgentRuntime(agentId) {
4503
- const config = loadAgentConfig();
4504
- delete config[agentId];
4505
- saveAgentConfig(config);
4506
- }
4507
- var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
4508
- var init_agent_config = __esm({
4509
- "src/lib/agent-config.ts"() {
4510
- "use strict";
4511
- init_config();
4512
- init_runtime_table();
4513
- AGENT_CONFIG_PATH = path11.join(EXE_AI_DIR, "agent-config.json");
4514
- KNOWN_RUNTIMES = {
4515
- claude: ["claude-opus-4", "claude-sonnet-4", "claude-haiku-4.5"],
4516
- codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
4517
- opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
4518
- };
4519
- RUNTIME_LABELS = {
4520
- claude: "Claude Code (Anthropic)",
4521
- codex: "Codex (OpenAI)",
4522
- opencode: "OpenCode (open source)"
4523
- };
4524
- DEFAULT_MODELS = {
4525
- claude: "claude-opus-4",
4526
- codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
4527
- opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
4528
- };
4529
- }
4530
- });
4531
-
4532
5436
  // src/lib/intercom-queue.ts
4533
5437
  var intercom_queue_exports = {};
4534
5438
  __export(intercom_queue_exports, {
@@ -4538,17 +5442,17 @@ __export(intercom_queue_exports, {
4538
5442
  queueIntercom: () => queueIntercom,
4539
5443
  readQueue: () => readQueue
4540
5444
  });
4541
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, renameSync as renameSync3, existsSync as existsSync10, mkdirSync as mkdirSync5 } from "fs";
4542
- import path12 from "path";
4543
- import os6 from "os";
5445
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, renameSync as renameSync3, existsSync as existsSync12, mkdirSync as mkdirSync5 } from "fs";
5446
+ import path14 from "path";
5447
+ import os7 from "os";
4544
5448
  function ensureDir() {
4545
- const dir = path12.dirname(QUEUE_PATH);
4546
- if (!existsSync10(dir)) mkdirSync5(dir, { recursive: true });
5449
+ const dir = path14.dirname(QUEUE_PATH);
5450
+ if (!existsSync12(dir)) mkdirSync5(dir, { recursive: true });
4547
5451
  }
4548
5452
  function readQueue() {
4549
5453
  try {
4550
- if (!existsSync10(QUEUE_PATH)) return [];
4551
- return JSON.parse(readFileSync8(QUEUE_PATH, "utf8"));
5454
+ if (!existsSync12(QUEUE_PATH)) return [];
5455
+ return JSON.parse(readFileSync9(QUEUE_PATH, "utf8"));
4552
5456
  } catch {
4553
5457
  return [];
4554
5458
  }
@@ -4556,7 +5460,7 @@ function readQueue() {
4556
5460
  function writeQueue(queue) {
4557
5461
  ensureDir();
4558
5462
  const tmp = `${QUEUE_PATH}.tmp`;
4559
- writeFileSync5(tmp, JSON.stringify(queue, null, 2));
5463
+ writeFileSync6(tmp, JSON.stringify(queue, null, 2));
4560
5464
  renameSync3(tmp, QUEUE_PATH);
4561
5465
  }
4562
5466
  function queueIntercom(targetSession, reason) {
@@ -4648,26 +5552,29 @@ var QUEUE_PATH, MAX_RETRIES2, TTL_MS, INTERCOM_LOG;
4648
5552
  var init_intercom_queue = __esm({
4649
5553
  "src/lib/intercom-queue.ts"() {
4650
5554
  "use strict";
4651
- QUEUE_PATH = path12.join(os6.homedir(), ".exe-os", "intercom-queue.json");
5555
+ QUEUE_PATH = path14.join(os7.homedir(), ".exe-os", "intercom-queue.json");
4652
5556
  MAX_RETRIES2 = 5;
4653
5557
  TTL_MS = 60 * 60 * 1e3;
4654
- INTERCOM_LOG = path12.join(os6.homedir(), ".exe-os", "intercom.log");
5558
+ INTERCOM_LOG = path14.join(os7.homedir(), ".exe-os", "intercom.log");
4655
5559
  }
4656
5560
  });
4657
5561
 
4658
5562
  // src/lib/license.ts
4659
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, existsSync as existsSync11, mkdirSync as mkdirSync6 } from "fs";
5563
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, existsSync as existsSync13, mkdirSync as mkdirSync6 } from "fs";
4660
5564
  import { randomUUID as randomUUID3 } from "crypto";
4661
- import path13 from "path";
5565
+ import { createRequire as createRequire2 } from "module";
5566
+ import { pathToFileURL as pathToFileURL2 } from "url";
5567
+ import os8 from "os";
5568
+ import path15 from "path";
4662
5569
  import { jwtVerify, importSPKI } from "jose";
4663
5570
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
4664
5571
  var init_license = __esm({
4665
5572
  "src/lib/license.ts"() {
4666
5573
  "use strict";
4667
5574
  init_config();
4668
- LICENSE_PATH = path13.join(EXE_AI_DIR, "license.key");
4669
- CACHE_PATH = path13.join(EXE_AI_DIR, "license-cache.json");
4670
- DEVICE_ID_PATH = path13.join(EXE_AI_DIR, "device-id");
5575
+ LICENSE_PATH = path15.join(EXE_AI_DIR, "license.key");
5576
+ CACHE_PATH = path15.join(EXE_AI_DIR, "license-cache.json");
5577
+ DEVICE_ID_PATH = path15.join(EXE_AI_DIR, "device-id");
4671
5578
  PLAN_LIMITS = {
4672
5579
  free: { devices: 1, employees: 1, memories: 5e3 },
4673
5580
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -4679,12 +5586,12 @@ var init_license = __esm({
4679
5586
  });
4680
5587
 
4681
5588
  // src/lib/plan-limits.ts
4682
- import { readFileSync as readFileSync10, existsSync as existsSync12 } from "fs";
4683
- import path14 from "path";
5589
+ import { readFileSync as readFileSync11, existsSync as existsSync14 } from "fs";
5590
+ import path16 from "path";
4684
5591
  function getLicenseSync() {
4685
5592
  try {
4686
- if (!existsSync12(CACHE_PATH2)) return freeLicense();
4687
- const raw = JSON.parse(readFileSync10(CACHE_PATH2, "utf8"));
5593
+ if (!existsSync14(CACHE_PATH2)) return freeLicense();
5594
+ const raw = JSON.parse(readFileSync11(CACHE_PATH2, "utf8"));
4688
5595
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
4689
5596
  const parts = raw.token.split(".");
4690
5597
  if (parts.length !== 3) return freeLicense();
@@ -4722,8 +5629,8 @@ function assertEmployeeLimitSync(rosterPath) {
4722
5629
  const filePath = rosterPath ?? EMPLOYEES_PATH;
4723
5630
  let count = 0;
4724
5631
  try {
4725
- if (existsSync12(filePath)) {
4726
- const raw = readFileSync10(filePath, "utf8");
5632
+ if (existsSync14(filePath)) {
5633
+ const raw = readFileSync11(filePath, "utf8");
4727
5634
  const employees = JSON.parse(raw);
4728
5635
  count = Array.isArray(employees) ? employees.length : 0;
4729
5636
  }
@@ -4752,29 +5659,63 @@ var init_plan_limits = __esm({
4752
5659
  this.name = "PlanLimitError";
4753
5660
  }
4754
5661
  };
4755
- CACHE_PATH2 = path14.join(EXE_AI_DIR, "license-cache.json");
5662
+ CACHE_PATH2 = path16.join(EXE_AI_DIR, "license-cache.json");
5663
+ }
5664
+ });
5665
+
5666
+ // src/lib/task-scope.ts
5667
+ function getCurrentSessionScope() {
5668
+ try {
5669
+ return resolveExeSession();
5670
+ } catch {
5671
+ return null;
5672
+ }
5673
+ }
5674
+ function sessionScopeFilter(sessionScope, tableAlias) {
5675
+ const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
5676
+ if (!scope) return { sql: "", args: [] };
5677
+ const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
5678
+ return {
5679
+ sql: ` AND (${col} IS NULL OR ${col} = ?)`,
5680
+ args: [scope]
5681
+ };
5682
+ }
5683
+ function strictSessionScopeFilter(sessionScope, tableAlias) {
5684
+ const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
5685
+ if (!scope) return { sql: "", args: [] };
5686
+ const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
5687
+ return {
5688
+ sql: ` AND ${col} = ?`,
5689
+ args: [scope]
5690
+ };
5691
+ }
5692
+ var init_task_scope = __esm({
5693
+ "src/lib/task-scope.ts"() {
5694
+ "use strict";
5695
+ init_tmux_routing();
4756
5696
  }
4757
5697
  });
4758
5698
 
4759
5699
  // src/lib/notifications.ts
4760
- import crypto2 from "crypto";
4761
- import path15 from "path";
4762
- import os7 from "os";
5700
+ import crypto3 from "crypto";
5701
+ import path17 from "path";
5702
+ import os9 from "os";
4763
5703
  import {
4764
- readFileSync as readFileSync11,
5704
+ readFileSync as readFileSync12,
4765
5705
  readdirSync as readdirSync4,
4766
5706
  unlinkSync as unlinkSync4,
4767
- existsSync as existsSync13,
5707
+ existsSync as existsSync15,
4768
5708
  rmdirSync
4769
5709
  } from "fs";
4770
5710
  async function writeNotification(notification) {
4771
5711
  try {
4772
5712
  const client = getClient();
4773
- const id = crypto2.randomUUID();
5713
+ const id = crypto3.randomUUID();
4774
5714
  const now = (/* @__PURE__ */ new Date()).toISOString();
5715
+ const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
4775
5716
  await client.execute({
4776
- sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
4777
- VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
5717
+ sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
5718
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
4778
5719
  args: [
4779
5720
  id,
4780
5721
  notification.agentId,
@@ -4783,6 +5724,7 @@ async function writeNotification(notification) {
4783
5724
  notification.project,
4784
5725
  notification.summary,
4785
5726
  notification.taskFile ?? null,
5727
+ sessionScope,
4786
5728
  now
4787
5729
  ]
4788
5730
  });
@@ -4791,12 +5733,14 @@ async function writeNotification(notification) {
4791
5733
  `);
4792
5734
  }
4793
5735
  }
4794
- async function markAsReadByTaskFile(taskFile) {
5736
+ async function markAsReadByTaskFile(taskFile, sessionScope) {
4795
5737
  try {
4796
5738
  const client = getClient();
5739
+ const scope = strictSessionScopeFilter(sessionScope);
4797
5740
  await client.execute({
4798
- sql: "UPDATE notifications SET read = 1 WHERE task_file = ? AND read = 0",
4799
- args: [taskFile]
5741
+ sql: `UPDATE notifications SET read = 1
5742
+ WHERE task_file = ? AND read = 0${scope.sql}`,
5743
+ args: [taskFile, ...scope.args]
4800
5744
  });
4801
5745
  } catch {
4802
5746
  }
@@ -4805,11 +5749,12 @@ var init_notifications = __esm({
4805
5749
  "src/lib/notifications.ts"() {
4806
5750
  "use strict";
4807
5751
  init_database();
5752
+ init_task_scope();
4808
5753
  }
4809
5754
  });
4810
5755
 
4811
5756
  // src/lib/session-kill-telemetry.ts
4812
- import crypto3 from "crypto";
5757
+ import crypto4 from "crypto";
4813
5758
  async function recordSessionKill(input2) {
4814
5759
  try {
4815
5760
  const client = getClient();
@@ -4819,7 +5764,7 @@ async function recordSessionKill(input2) {
4819
5764
  ticks_idle, estimated_tokens_saved)
4820
5765
  VALUES (?, ?, ?, ?, ?, ?, ?)`,
4821
5766
  args: [
4822
- crypto3.randomUUID(),
5767
+ crypto4.randomUUID(),
4823
5768
  input2.sessionName,
4824
5769
  input2.agentId,
4825
5770
  (/* @__PURE__ */ new Date()).toISOString(),
@@ -4842,37 +5787,13 @@ var init_session_kill_telemetry = __esm({
4842
5787
  }
4843
5788
  });
4844
5789
 
4845
- // src/lib/task-scope.ts
4846
- function getCurrentSessionScope() {
4847
- try {
4848
- return resolveExeSession();
4849
- } catch {
4850
- return null;
4851
- }
4852
- }
4853
- function sessionScopeFilter(sessionScope, tableAlias) {
4854
- const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
4855
- if (!scope) return { sql: "", args: [] };
4856
- const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
4857
- return {
4858
- sql: ` AND (${col} IS NULL OR ${col} = ?)`,
4859
- args: [scope]
4860
- };
4861
- }
4862
- var init_task_scope = __esm({
4863
- "src/lib/task-scope.ts"() {
4864
- "use strict";
4865
- init_tmux_routing();
4866
- }
4867
- });
4868
-
4869
5790
  // src/lib/tasks-crud.ts
4870
- import crypto4 from "crypto";
4871
- import path16 from "path";
4872
- import os8 from "os";
5791
+ import crypto5 from "crypto";
5792
+ import path18 from "path";
5793
+ import os10 from "os";
4873
5794
  import { execSync as execSync7 } from "child_process";
4874
5795
  import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
4875
- import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
5796
+ import { existsSync as existsSync16, readFileSync as readFileSync13 } from "fs";
4876
5797
  async function writeCheckpoint(input2) {
4877
5798
  const client = getClient();
4878
5799
  const row = await resolveTask(client, input2.taskId);
@@ -4988,7 +5909,7 @@ async function resolveTask(client, identifier, scopeSession) {
4988
5909
  }
4989
5910
  async function createTaskCore(input2) {
4990
5911
  const client = getClient();
4991
- const id = crypto4.randomUUID();
5912
+ const id = crypto5.randomUUID();
4992
5913
  const now = (/* @__PURE__ */ new Date()).toISOString();
4993
5914
  const slug = slugify(input2.title);
4994
5915
  let earlySessionScope = null;
@@ -5047,8 +5968,8 @@ ${laneWarning}` : laneWarning;
5047
5968
  }
5048
5969
  if (input2.baseDir) {
5049
5970
  try {
5050
- await mkdir4(path16.join(input2.baseDir, "exe", "output"), { recursive: true });
5051
- await mkdir4(path16.join(input2.baseDir, "exe", "research"), { recursive: true });
5971
+ await mkdir4(path18.join(input2.baseDir, "exe", "output"), { recursive: true });
5972
+ await mkdir4(path18.join(input2.baseDir, "exe", "research"), { recursive: true });
5052
5973
  await ensureArchitectureDoc(input2.baseDir, input2.projectName);
5053
5974
  await ensureGitignoreExe(input2.baseDir);
5054
5975
  } catch {
@@ -5084,13 +6005,19 @@ ${laneWarning}` : laneWarning;
5084
6005
  });
5085
6006
  if (input2.baseDir) {
5086
6007
  try {
5087
- const EXE_OS_DIR = path16.join(os8.homedir(), ".exe-os");
5088
- const mdPath = path16.join(EXE_OS_DIR, taskFile);
5089
- const mdDir = path16.dirname(mdPath);
5090
- if (!existsSync14(mdDir)) await mkdir4(mdDir, { recursive: true });
6008
+ const EXE_OS_DIR = path18.join(os10.homedir(), ".exe-os");
6009
+ const mdPath = path18.join(EXE_OS_DIR, taskFile);
6010
+ const mdDir = path18.dirname(mdPath);
6011
+ if (!existsSync16(mdDir)) await mkdir4(mdDir, { recursive: true });
5091
6012
  const reviewer = input2.reviewer ?? input2.assignedBy;
5092
6013
  const mdContent = `# ${input2.title}
5093
6014
 
6015
+ ## MANDATORY: When done
6016
+
6017
+ You MUST call update_task with status "done" and a result summary when finished.
6018
+ If you skip this, your reviewer will not know you're done and your work won't be reviewed.
6019
+ Do NOT let a failed commit or any error prevent you from calling update_task(done).
6020
+
5094
6021
  **ID:** ${id}
5095
6022
  **Status:** ${initialStatus}
5096
6023
  **Priority:** ${input2.priority}
@@ -5104,12 +6031,6 @@ ${laneWarning}` : laneWarning;
5104
6031
  ## Context
5105
6032
 
5106
6033
  ${input2.context}
5107
-
5108
- ## MANDATORY: When done
5109
-
5110
- You MUST call update_task with status "done" and a result summary when finished.
5111
- If you skip this, your reviewer will not know you're done and your work won't be reviewed.
5112
- Do NOT let a failed commit or any error prevent you from calling update_task(done).
5113
6034
  `;
5114
6035
  await writeFile4(mdPath, mdContent, "utf-8");
5115
6036
  } catch (err) {
@@ -5358,7 +6279,7 @@ ${input2.result}` : `\u26A0\uFE0F ${warning}`;
5358
6279
  await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
5359
6280
  } catch {
5360
6281
  }
5361
- if (input2.status === "done" || input2.status === "cancelled") {
6282
+ if (input2.status === "done" || input2.status === "cancelled" || input2.status === "closed") {
5362
6283
  try {
5363
6284
  const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
5364
6285
  clearQueueForAgent2(String(row.assigned_to));
@@ -5387,9 +6308,9 @@ async function deleteTaskCore(taskId, _baseDir) {
5387
6308
  return { taskFile, assignedTo, assignedBy, taskSlug };
5388
6309
  }
5389
6310
  async function ensureArchitectureDoc(baseDir, projectName) {
5390
- const archPath = path16.join(baseDir, "exe", "ARCHITECTURE.md");
6311
+ const archPath = path18.join(baseDir, "exe", "ARCHITECTURE.md");
5391
6312
  try {
5392
- if (existsSync14(archPath)) return;
6313
+ if (existsSync16(archPath)) return;
5393
6314
  const template = [
5394
6315
  `# ${projectName} \u2014 System Architecture`,
5395
6316
  "",
@@ -5422,10 +6343,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
5422
6343
  }
5423
6344
  }
5424
6345
  async function ensureGitignoreExe(baseDir) {
5425
- const gitignorePath = path16.join(baseDir, ".gitignore");
6346
+ const gitignorePath = path18.join(baseDir, ".gitignore");
5426
6347
  try {
5427
- if (existsSync14(gitignorePath)) {
5428
- const content = readFileSync12(gitignorePath, "utf-8");
6348
+ if (existsSync16(gitignorePath)) {
6349
+ const content = readFileSync13(gitignorePath, "utf-8");
5429
6350
  if (/^\/?exe\/?$/m.test(content)) return;
5430
6351
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
5431
6352
  } else {
@@ -5468,8 +6389,8 @@ __export(tasks_review_exports, {
5468
6389
  isStale: () => isStale,
5469
6390
  listPendingReviews: () => listPendingReviews
5470
6391
  });
5471
- import path17 from "path";
5472
- import { existsSync as existsSync15, readdirSync as readdirSync5, unlinkSync as unlinkSync5 } from "fs";
6392
+ import path19 from "path";
6393
+ import { existsSync as existsSync17, readdirSync as readdirSync5, unlinkSync as unlinkSync5 } from "fs";
5473
6394
  function formatAge(isoTimestamp) {
5474
6395
  if (!isoTimestamp) return "";
5475
6396
  const ms = Date.now() - new Date(isoTimestamp).getTime();
@@ -5487,54 +6408,38 @@ function isStale(isoTimestamp) {
5487
6408
  }
5488
6409
  async function countPendingReviews(sessionScope) {
5489
6410
  const client = getClient();
5490
- if (sessionScope) {
5491
- const result2 = await client.execute({
5492
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review' AND (session_scope = ? OR session_scope IS NULL)",
5493
- args: [sessionScope]
5494
- });
5495
- return Number(result2.rows[0]?.cnt) || 0;
5496
- }
6411
+ const scope = strictSessionScopeFilter(
6412
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
6413
+ );
5497
6414
  const result = await client.execute({
5498
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review'",
5499
- args: []
6415
+ sql: `SELECT COUNT(*) as cnt FROM tasks
6416
+ WHERE status = 'needs_review'${scope.sql}`,
6417
+ args: [...scope.args]
5500
6418
  });
5501
6419
  return Number(result.rows[0]?.cnt) || 0;
5502
6420
  }
5503
6421
  async function countNewPendingReviewsSince(sinceIso, sessionScope) {
5504
6422
  const client = getClient();
5505
- if (sessionScope) {
5506
- const result2 = await client.execute({
5507
- sql: `SELECT COUNT(*) as cnt FROM tasks
5508
- WHERE status = 'needs_review' AND updated_at > ?
5509
- AND session_scope = ?`,
5510
- args: [sinceIso, sessionScope]
5511
- });
5512
- return Number(result2.rows[0]?.cnt) || 0;
5513
- }
6423
+ const scope = strictSessionScopeFilter(
6424
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
6425
+ );
5514
6426
  const result = await client.execute({
5515
6427
  sql: `SELECT COUNT(*) as cnt FROM tasks
5516
- WHERE status = 'needs_review' AND updated_at > ?`,
5517
- args: [sinceIso]
6428
+ WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
6429
+ args: [sinceIso, ...scope.args]
5518
6430
  });
5519
6431
  return Number(result.rows[0]?.cnt) || 0;
5520
6432
  }
5521
6433
  async function listPendingReviews(limit, sessionScope) {
5522
6434
  const client = getClient();
5523
- if (sessionScope) {
5524
- const result2 = await client.execute({
5525
- sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
5526
- WHERE status = 'needs_review'
5527
- AND session_scope = ?
5528
- ORDER BY updated_at ASC LIMIT ?`,
5529
- args: [sessionScope, limit]
5530
- });
5531
- return result2.rows;
5532
- }
6435
+ const scope = strictSessionScopeFilter(
6436
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
6437
+ );
5533
6438
  const result = await client.execute({
5534
6439
  sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
5535
- WHERE status = 'needs_review'
6440
+ WHERE status = 'needs_review'${scope.sql}
5536
6441
  ORDER BY updated_at ASC LIMIT ?`,
5537
- args: [limit]
6442
+ args: [...scope.args, limit]
5538
6443
  });
5539
6444
  return result.rows;
5540
6445
  }
@@ -5546,7 +6451,7 @@ async function cleanupOrphanedReviews() {
5546
6451
  WHERE status IN ('open', 'needs_review', 'in_progress')
5547
6452
  AND assigned_by = 'system'
5548
6453
  AND title LIKE 'Review:%'
5549
- AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
6454
+ AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
5550
6455
  args: [now]
5551
6456
  });
5552
6457
  const r1b = await client.execute({
@@ -5754,11 +6659,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
5754
6659
  );
5755
6660
  }
5756
6661
  try {
5757
- const cacheDir = path17.join(EXE_AI_DIR, "session-cache");
5758
- if (existsSync15(cacheDir)) {
6662
+ const cacheDir = path19.join(EXE_AI_DIR, "session-cache");
6663
+ if (existsSync17(cacheDir)) {
5759
6664
  for (const f of readdirSync5(cacheDir)) {
5760
6665
  if (f.startsWith("review-notified-")) {
5761
- unlinkSync5(path17.join(cacheDir, f));
6666
+ unlinkSync5(path19.join(cacheDir, f));
5762
6667
  }
5763
6668
  }
5764
6669
  }
@@ -5775,11 +6680,12 @@ var init_tasks_review = __esm({
5775
6680
  init_tmux_routing();
5776
6681
  init_session_key();
5777
6682
  init_state_bus();
6683
+ init_task_scope();
5778
6684
  }
5779
6685
  });
5780
6686
 
5781
6687
  // src/lib/tasks-chain.ts
5782
- import path18 from "path";
6688
+ import path20 from "path";
5783
6689
  import { readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
5784
6690
  async function cascadeUnblock(taskId, baseDir, now) {
5785
6691
  const client = getClient();
@@ -5796,7 +6702,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
5796
6702
  });
5797
6703
  for (const ur of unblockedRows.rows) {
5798
6704
  try {
5799
- const ubFile = path18.join(baseDir, String(ur.task_file));
6705
+ const ubFile = path20.join(baseDir, String(ur.task_file));
5800
6706
  let ubContent = await readFile4(ubFile, "utf-8");
5801
6707
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
5802
6708
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -5831,7 +6737,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
5831
6737
  const scScope = sessionScopeFilter();
5832
6738
  const remaining = await client.execute({
5833
6739
  sql: `SELECT COUNT(*) as cnt FROM tasks
5834
- WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
6740
+ WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
5835
6741
  args: [parentTaskId, ...scScope.args]
5836
6742
  });
5837
6743
  const cnt = Number(remaining.rows[0]?.cnt ?? 1);
@@ -5992,10 +6898,10 @@ var init_tasks_notify = __esm({
5992
6898
  });
5993
6899
 
5994
6900
  // src/lib/behaviors.ts
5995
- import crypto5 from "crypto";
6901
+ import crypto6 from "crypto";
5996
6902
  async function storeBehavior(opts) {
5997
6903
  const client = getClient();
5998
- const id = crypto5.randomUUID();
6904
+ const id = crypto6.randomUUID();
5999
6905
  const now = (/* @__PURE__ */ new Date()).toISOString();
6000
6906
  await client.execute({
6001
6907
  sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
@@ -6024,7 +6930,7 @@ __export(skill_learning_exports, {
6024
6930
  storeTrajectory: () => storeTrajectory,
6025
6931
  sweepTrajectories: () => sweepTrajectories
6026
6932
  });
6027
- import crypto6 from "crypto";
6933
+ import crypto7 from "crypto";
6028
6934
  async function extractTrajectory(taskId, agentId) {
6029
6935
  const client = getClient();
6030
6936
  const result = await client.execute({
@@ -6053,11 +6959,11 @@ async function extractTrajectory(taskId, agentId) {
6053
6959
  return signature;
6054
6960
  }
6055
6961
  function hashSignature(signature) {
6056
- return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
6962
+ return crypto7.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
6057
6963
  }
6058
6964
  async function storeTrajectory(opts) {
6059
6965
  const client = getClient();
6060
- const id = crypto6.randomUUID();
6966
+ const id = crypto7.randomUUID();
6061
6967
  const now = (/* @__PURE__ */ new Date()).toISOString();
6062
6968
  const signatureHash = hashSignature(opts.signature);
6063
6969
  await client.execute({
@@ -6322,8 +7228,8 @@ __export(tasks_exports, {
6322
7228
  updateTaskStatus: () => updateTaskStatus,
6323
7229
  writeCheckpoint: () => writeCheckpoint
6324
7230
  });
6325
- import path19 from "path";
6326
- import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync7, unlinkSync as unlinkSync6 } from "fs";
7231
+ import path21 from "path";
7232
+ import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync7, unlinkSync as unlinkSync6 } from "fs";
6327
7233
  async function createTask(input2) {
6328
7234
  const result = await createTaskCore(input2);
6329
7235
  if (!input2.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -6342,12 +7248,12 @@ async function updateTask(input2) {
6342
7248
  const { row, taskFile, now, taskId } = await updateTaskStatus(input2);
6343
7249
  try {
6344
7250
  const agent = String(row.assigned_to);
6345
- const cacheDir = path19.join(EXE_AI_DIR, "session-cache");
6346
- const cachePath = path19.join(cacheDir, `current-task-${agent}.json`);
7251
+ const cacheDir = path21.join(EXE_AI_DIR, "session-cache");
7252
+ const cachePath = path21.join(cacheDir, `current-task-${agent}.json`);
6347
7253
  if (input2.status === "in_progress") {
6348
7254
  mkdirSync7(cacheDir, { recursive: true });
6349
- writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
6350
- } else if (input2.status === "done" || input2.status === "blocked" || input2.status === "cancelled") {
7255
+ writeFileSync8(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
7256
+ } else if (input2.status === "done" || input2.status === "blocked" || input2.status === "cancelled" || input2.status === "closed") {
6351
7257
  try {
6352
7258
  unlinkSync6(cachePath);
6353
7259
  } catch {
@@ -6355,10 +7261,10 @@ async function updateTask(input2) {
6355
7261
  }
6356
7262
  } catch {
6357
7263
  }
6358
- if (input2.status === "done") {
7264
+ if (input2.status === "done" || input2.status === "closed") {
6359
7265
  await cleanupReviewFile(row, taskFile, input2.baseDir);
6360
7266
  }
6361
- if (input2.status === "done" || input2.status === "cancelled") {
7267
+ if (input2.status === "done" || input2.status === "cancelled" || input2.status === "closed") {
6362
7268
  try {
6363
7269
  const client = getClient();
6364
7270
  const taskTitle = String(row.title);
@@ -6374,7 +7280,7 @@ async function updateTask(input2) {
6374
7280
  if (!isCoordinatorName(assignedAgent)) {
6375
7281
  try {
6376
7282
  const draftClient = getClient();
6377
- if (input2.status === "done") {
7283
+ if (input2.status === "done" || input2.status === "closed") {
6378
7284
  await draftClient.execute({
6379
7285
  sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
6380
7286
  args: [assignedAgent]
@@ -6391,7 +7297,7 @@ async function updateTask(input2) {
6391
7297
  try {
6392
7298
  const client = getClient();
6393
7299
  const cascaded = await client.execute({
6394
- sql: `UPDATE tasks SET status = 'done', updated_at = ?
7300
+ sql: `UPDATE tasks SET status = 'closed', updated_at = ?
6395
7301
  WHERE parent_task_id = ? AND status = 'needs_review'`,
6396
7302
  args: [now, taskId]
6397
7303
  });
@@ -6404,14 +7310,14 @@ async function updateTask(input2) {
6404
7310
  } catch {
6405
7311
  }
6406
7312
  }
6407
- const isTerminal = input2.status === "done" || input2.status === "needs_review";
7313
+ const isTerminal = input2.status === "done" || input2.status === "needs_review" || input2.status === "closed";
6408
7314
  if (isTerminal) {
6409
7315
  const isCoordinator = isCoordinatorName(String(row.assigned_to));
6410
7316
  if (!isCoordinator) {
6411
7317
  notifyTaskDone();
6412
7318
  }
6413
7319
  await markTaskNotificationsRead(taskFile);
6414
- if (input2.status === "done") {
7320
+ if (input2.status === "done" || input2.status === "closed") {
6415
7321
  try {
6416
7322
  await cascadeUnblock(taskId, input2.baseDir, now);
6417
7323
  } catch {
@@ -6431,7 +7337,7 @@ async function updateTask(input2) {
6431
7337
  }
6432
7338
  }
6433
7339
  }
6434
- if (input2.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
7340
+ if ((input2.status === "done" || input2.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
6435
7341
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
6436
7342
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
6437
7343
  taskId,
@@ -6803,6 +7709,7 @@ __export(tmux_routing_exports, {
6803
7709
  isEmployeeAlive: () => isEmployeeAlive,
6804
7710
  isExeSession: () => isExeSession,
6805
7711
  isSessionBusy: () => isSessionBusy,
7712
+ notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
6806
7713
  notifyParentExe: () => notifyParentExe,
6807
7714
  parseParentExe: () => parseParentExe,
6808
7715
  registerParentExe: () => registerParentExe,
@@ -6813,13 +7720,13 @@ __export(tmux_routing_exports, {
6813
7720
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
6814
7721
  });
6815
7722
  import { execFileSync as execFileSync2, execSync as execSync8 } from "child_process";
6816
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, mkdirSync as mkdirSync8, existsSync as existsSync16, appendFileSync, readdirSync as readdirSync6 } from "fs";
6817
- import path20 from "path";
6818
- import os9 from "os";
7723
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync9, mkdirSync as mkdirSync8, existsSync as existsSync18, appendFileSync, readdirSync as readdirSync6 } from "fs";
7724
+ import path22 from "path";
7725
+ import os11 from "os";
6819
7726
  import { fileURLToPath as fileURLToPath2 } from "url";
6820
7727
  import { unlinkSync as unlinkSync7 } from "fs";
6821
7728
  function spawnLockPath(sessionName) {
6822
- return path20.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
7729
+ return path22.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
6823
7730
  }
6824
7731
  function isProcessAlive(pid) {
6825
7732
  try {
@@ -6830,13 +7737,13 @@ function isProcessAlive(pid) {
6830
7737
  }
6831
7738
  }
6832
7739
  function acquireSpawnLock2(sessionName) {
6833
- if (!existsSync16(SPAWN_LOCK_DIR)) {
7740
+ if (!existsSync18(SPAWN_LOCK_DIR)) {
6834
7741
  mkdirSync8(SPAWN_LOCK_DIR, { recursive: true });
6835
7742
  }
6836
7743
  const lockFile = spawnLockPath(sessionName);
6837
- if (existsSync16(lockFile)) {
7744
+ if (existsSync18(lockFile)) {
6838
7745
  try {
6839
- const lock = JSON.parse(readFileSync13(lockFile, "utf8"));
7746
+ const lock = JSON.parse(readFileSync14(lockFile, "utf8"));
6840
7747
  const age = Date.now() - lock.timestamp;
6841
7748
  if (isProcessAlive(lock.pid) && age < 6e4) {
6842
7749
  return false;
@@ -6844,7 +7751,7 @@ function acquireSpawnLock2(sessionName) {
6844
7751
  } catch {
6845
7752
  }
6846
7753
  }
6847
- writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
7754
+ writeFileSync9(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
6848
7755
  return true;
6849
7756
  }
6850
7757
  function releaseSpawnLock2(sessionName) {
@@ -6856,13 +7763,13 @@ function releaseSpawnLock2(sessionName) {
6856
7763
  function resolveBehaviorsExporterScript() {
6857
7764
  try {
6858
7765
  const thisFile = fileURLToPath2(import.meta.url);
6859
- const scriptPath = path20.join(
6860
- path20.dirname(thisFile),
7766
+ const scriptPath = path22.join(
7767
+ path22.dirname(thisFile),
6861
7768
  "..",
6862
7769
  "bin",
6863
7770
  "exe-export-behaviors.js"
6864
7771
  );
6865
- return existsSync16(scriptPath) ? scriptPath : null;
7772
+ return existsSync18(scriptPath) ? scriptPath : null;
6866
7773
  } catch {
6867
7774
  return null;
6868
7775
  }
@@ -6928,12 +7835,12 @@ function extractRootExe(name) {
6928
7835
  return parts.length > 0 ? parts[parts.length - 1] : null;
6929
7836
  }
6930
7837
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
6931
- if (!existsSync16(SESSION_CACHE)) {
7838
+ if (!existsSync18(SESSION_CACHE)) {
6932
7839
  mkdirSync8(SESSION_CACHE, { recursive: true });
6933
7840
  }
6934
7841
  const rootExe = extractRootExe(parentExe) ?? parentExe;
6935
- const filePath = path20.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
6936
- writeFileSync8(filePath, JSON.stringify({
7842
+ const filePath = path22.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
7843
+ writeFileSync9(filePath, JSON.stringify({
6937
7844
  parentExe: rootExe,
6938
7845
  dispatchedBy: dispatchedBy || rootExe,
6939
7846
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -6941,7 +7848,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
6941
7848
  }
6942
7849
  function getParentExe(sessionKey) {
6943
7850
  try {
6944
- const data = JSON.parse(readFileSync13(path20.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
7851
+ const data = JSON.parse(readFileSync14(path22.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
6945
7852
  return data.parentExe || null;
6946
7853
  } catch {
6947
7854
  return null;
@@ -6949,8 +7856,8 @@ function getParentExe(sessionKey) {
6949
7856
  }
6950
7857
  function getDispatchedBy(sessionKey) {
6951
7858
  try {
6952
- const data = JSON.parse(readFileSync13(
6953
- path20.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
7859
+ const data = JSON.parse(readFileSync14(
7860
+ path22.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
6954
7861
  "utf8"
6955
7862
  ));
6956
7863
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -7020,8 +7927,8 @@ async function verifyPaneAtCapacity(sessionName) {
7020
7927
  }
7021
7928
  function readDebounceState() {
7022
7929
  try {
7023
- if (!existsSync16(DEBOUNCE_FILE)) return {};
7024
- const raw = JSON.parse(readFileSync13(DEBOUNCE_FILE, "utf8"));
7930
+ if (!existsSync18(DEBOUNCE_FILE)) return {};
7931
+ const raw = JSON.parse(readFileSync14(DEBOUNCE_FILE, "utf8"));
7025
7932
  const state = {};
7026
7933
  for (const [key, val] of Object.entries(raw)) {
7027
7934
  if (typeof val === "number") {
@@ -7037,8 +7944,8 @@ function readDebounceState() {
7037
7944
  }
7038
7945
  function writeDebounceState(state) {
7039
7946
  try {
7040
- if (!existsSync16(SESSION_CACHE)) mkdirSync8(SESSION_CACHE, { recursive: true });
7041
- writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
7947
+ if (!existsSync18(SESSION_CACHE)) mkdirSync8(SESSION_CACHE, { recursive: true });
7948
+ writeFileSync9(DEBOUNCE_FILE, JSON.stringify(state));
7042
7949
  } catch {
7043
7950
  }
7044
7951
  }
@@ -7136,8 +8043,8 @@ function sendIntercom(targetSession) {
7136
8043
  try {
7137
8044
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
7138
8045
  const agent = baseAgentName(rawAgent);
7139
- const markerPath = path20.join(SESSION_CACHE, `current-task-${agent}.json`);
7140
- if (existsSync16(markerPath)) {
8046
+ const markerPath = path22.join(SESSION_CACHE, `current-task-${agent}.json`);
8047
+ if (existsSync18(markerPath)) {
7141
8048
  logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
7142
8049
  return "debounced";
7143
8050
  }
@@ -7146,8 +8053,8 @@ function sendIntercom(targetSession) {
7146
8053
  try {
7147
8054
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
7148
8055
  const agent = baseAgentName(rawAgent);
7149
- const taskDir = path20.join(process.cwd(), "exe", agent);
7150
- if (existsSync16(taskDir)) {
8056
+ const taskDir = path22.join(process.cwd(), "exe", agent);
8057
+ if (existsSync18(taskDir)) {
7151
8058
  const files = readdirSync6(taskDir).filter(
7152
8059
  (f) => f.endsWith(".md") && f !== "DONE.txt"
7153
8060
  );
@@ -7207,6 +8114,21 @@ function notifyParentExe(sessionKey) {
7207
8114
  }
7208
8115
  return true;
7209
8116
  }
8117
+ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
8118
+ const transport = getTransport();
8119
+ try {
8120
+ const sessions = transport.listSessions();
8121
+ if (!sessions.includes(coordinatorSession)) return false;
8122
+ execSync8(
8123
+ `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
8124
+ { timeout: 3e3 }
8125
+ );
8126
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
8127
+ return true;
8128
+ } catch {
8129
+ return false;
8130
+ }
8131
+ }
7210
8132
  function ensureEmployee(employeeName, exeSession, projectDir, opts) {
7211
8133
  if (isCoordinatorName(employeeName)) {
7212
8134
  return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
@@ -7280,26 +8202,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
7280
8202
  const transport = getTransport();
7281
8203
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
7282
8204
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
7283
- const logDir = path20.join(os9.homedir(), ".exe-os", "session-logs");
7284
- const logFile = path20.join(logDir, `${instanceLabel}-${Date.now()}.log`);
7285
- if (!existsSync16(logDir)) {
8205
+ const logDir = path22.join(os11.homedir(), ".exe-os", "session-logs");
8206
+ const logFile = path22.join(logDir, `${instanceLabel}-${Date.now()}.log`);
8207
+ if (!existsSync18(logDir)) {
7286
8208
  mkdirSync8(logDir, { recursive: true });
7287
8209
  }
7288
8210
  transport.kill(sessionName);
7289
8211
  let cleanupSuffix = "";
7290
8212
  try {
7291
8213
  const thisFile = fileURLToPath2(import.meta.url);
7292
- const cleanupScript = path20.join(path20.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
7293
- if (existsSync16(cleanupScript)) {
8214
+ const cleanupScript = path22.join(path22.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
8215
+ if (existsSync18(cleanupScript)) {
7294
8216
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
7295
8217
  }
7296
8218
  } catch {
7297
8219
  }
7298
8220
  try {
7299
- const claudeJsonPath = path20.join(os9.homedir(), ".claude.json");
8221
+ const claudeJsonPath = path22.join(os11.homedir(), ".claude.json");
7300
8222
  let claudeJson = {};
7301
8223
  try {
7302
- claudeJson = JSON.parse(readFileSync13(claudeJsonPath, "utf8"));
8224
+ claudeJson = JSON.parse(readFileSync14(claudeJsonPath, "utf8"));
7303
8225
  } catch {
7304
8226
  }
7305
8227
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -7307,17 +8229,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
7307
8229
  const trustDir = opts?.cwd ?? projectDir;
7308
8230
  if (!projects[trustDir]) projects[trustDir] = {};
7309
8231
  projects[trustDir].hasTrustDialogAccepted = true;
7310
- writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
8232
+ writeFileSync9(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
7311
8233
  } catch {
7312
8234
  }
7313
8235
  try {
7314
- const settingsDir = path20.join(os9.homedir(), ".claude", "projects");
8236
+ const settingsDir = path22.join(os11.homedir(), ".claude", "projects");
7315
8237
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
7316
- const projSettingsDir = path20.join(settingsDir, normalizedKey);
7317
- const settingsPath = path20.join(projSettingsDir, "settings.json");
8238
+ const projSettingsDir = path22.join(settingsDir, normalizedKey);
8239
+ const settingsPath = path22.join(projSettingsDir, "settings.json");
7318
8240
  let settings = {};
7319
8241
  try {
7320
- settings = JSON.parse(readFileSync13(settingsPath, "utf8"));
8242
+ settings = JSON.parse(readFileSync14(settingsPath, "utf8"));
7321
8243
  } catch {
7322
8244
  }
7323
8245
  const perms = settings.permissions ?? {};
@@ -7346,7 +8268,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
7346
8268
  perms.allow = allow;
7347
8269
  settings.permissions = perms;
7348
8270
  mkdirSync8(projSettingsDir, { recursive: true });
7349
- writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
8271
+ writeFileSync9(settingsPath, JSON.stringify(settings, null, 2) + "\n");
7350
8272
  }
7351
8273
  } catch {
7352
8274
  }
@@ -7361,8 +8283,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
7361
8283
  let behaviorsFlag = "";
7362
8284
  let legacyFallbackWarned = false;
7363
8285
  if (!useExeAgent && !useBinSymlink) {
7364
- const identityPath = path20.join(
7365
- os9.homedir(),
8286
+ const identityPath = path22.join(
8287
+ os11.homedir(),
7366
8288
  ".exe-os",
7367
8289
  "identity",
7368
8290
  `${employeeName}.md`
@@ -7371,13 +8293,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
7371
8293
  const hasAgentFlag = claudeSupportsAgentFlag();
7372
8294
  if (hasAgentFlag) {
7373
8295
  identityFlag = ` --agent ${employeeName}`;
7374
- } else if (existsSync16(identityPath)) {
8296
+ } else if (existsSync18(identityPath)) {
7375
8297
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
7376
8298
  legacyFallbackWarned = true;
7377
8299
  }
7378
8300
  const behaviorsFile = exportBehaviorsSync(
7379
8301
  employeeName,
7380
- path20.basename(spawnCwd),
8302
+ path22.basename(spawnCwd),
7381
8303
  sessionName
7382
8304
  );
7383
8305
  if (behaviorsFile) {
@@ -7392,16 +8314,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
7392
8314
  }
7393
8315
  let sessionContextFlag = "";
7394
8316
  try {
7395
- const ctxDir = path20.join(os9.homedir(), ".exe-os", "session-cache");
8317
+ const ctxDir = path22.join(os11.homedir(), ".exe-os", "session-cache");
7396
8318
  mkdirSync8(ctxDir, { recursive: true });
7397
- const ctxFile = path20.join(ctxDir, `session-context-${sessionName}.md`);
8319
+ const ctxFile = path22.join(ctxDir, `session-context-${sessionName}.md`);
7398
8320
  const ctxContent = [
7399
8321
  `## Session Context`,
7400
8322
  `You are running in tmux session: ${sessionName}.`,
7401
8323
  `Your parent coordinator session is ${exeSession}.`,
7402
8324
  `Your employees (if any) use the -${exeSession} suffix.`
7403
8325
  ].join("\n");
7404
- writeFileSync8(ctxFile, ctxContent);
8326
+ writeFileSync9(ctxFile, ctxContent);
7405
8327
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
7406
8328
  } catch {
7407
8329
  }
@@ -7478,8 +8400,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
7478
8400
  transport.pipeLog(sessionName, logFile);
7479
8401
  try {
7480
8402
  const mySession = getMySession();
7481
- const dispatchInfo = path20.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
7482
- writeFileSync8(dispatchInfo, JSON.stringify({
8403
+ const dispatchInfo = path22.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
8404
+ writeFileSync9(dispatchInfo, JSON.stringify({
7483
8405
  dispatchedBy: mySession,
7484
8406
  rootExe: exeSession,
7485
8407
  provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
@@ -7553,15 +8475,15 @@ var init_tmux_routing = __esm({
7553
8475
  init_intercom_queue();
7554
8476
  init_plan_limits();
7555
8477
  init_employees();
7556
- SPAWN_LOCK_DIR = path20.join(os9.homedir(), ".exe-os", "spawn-locks");
7557
- SESSION_CACHE = path20.join(os9.homedir(), ".exe-os", "session-cache");
8478
+ SPAWN_LOCK_DIR = path22.join(os11.homedir(), ".exe-os", "spawn-locks");
8479
+ SESSION_CACHE = path22.join(os11.homedir(), ".exe-os", "session-cache");
7558
8480
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
7559
8481
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
7560
8482
  VERIFY_PANE_LINES = 200;
7561
8483
  INTERCOM_DEBOUNCE_MS = 3e4;
7562
8484
  CODEX_DEBOUNCE_MS = 12e4;
7563
- INTERCOM_LOG2 = path20.join(os9.homedir(), ".exe-os", "intercom.log");
7564
- DEBOUNCE_FILE = path20.join(SESSION_CACHE, "intercom-debounce.json");
8485
+ INTERCOM_LOG2 = path22.join(os11.homedir(), ".exe-os", "intercom.log");
8486
+ DEBOUNCE_FILE = path22.join(SESSION_CACHE, "intercom-debounce.json");
7565
8487
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
7566
8488
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
7567
8489
  }
@@ -7584,10 +8506,10 @@ __export(messaging_exports, {
7584
8506
  sendMessage: () => sendMessage,
7585
8507
  setWsClientSend: () => setWsClientSend
7586
8508
  });
7587
- import crypto7 from "crypto";
8509
+ import crypto8 from "crypto";
7588
8510
  function generateUlid() {
7589
8511
  const timestamp = Date.now().toString(36).padStart(10, "0");
7590
- const random = crypto7.randomBytes(10).toString("hex").slice(0, 16);
8512
+ const random = crypto8.randomBytes(10).toString("hex").slice(0, 16);
7591
8513
  return (timestamp + random).toUpperCase();
7592
8514
  }
7593
8515
  function rowToMessage(row) {
@@ -7598,6 +8520,7 @@ function rowToMessage(row) {
7598
8520
  targetAgent: row.target_agent,
7599
8521
  targetProject: row.target_project ?? null,
7600
8522
  targetDevice: row.target_device,
8523
+ sessionScope: row.session_scope ?? null,
7601
8524
  content: row.content,
7602
8525
  priority: row.priority ?? "normal",
7603
8526
  status: row.status ?? "pending",
@@ -7615,15 +8538,17 @@ async function sendMessage(input2) {
7615
8538
  const id = generateUlid();
7616
8539
  const now = (/* @__PURE__ */ new Date()).toISOString();
7617
8540
  const targetDevice = input2.targetDevice ?? "local";
8541
+ const sessionScope = input2.sessionScope === void 0 ? resolveExeSession() : input2.sessionScope;
7618
8542
  await client.execute({
7619
- sql: `INSERT INTO messages (id, from_agent, from_device, target_agent, target_project, target_device, content, priority, status, created_at)
7620
- VALUES (?, ?, 'local', ?, ?, ?, ?, ?, 'pending', ?)`,
8543
+ sql: `INSERT INTO messages (id, from_agent, from_device, target_agent, target_project, target_device, session_scope, content, priority, status, created_at)
8544
+ VALUES (?, ?, 'local', ?, ?, ?, ?, ?, ?, 'pending', ?)`,
7621
8545
  args: [
7622
8546
  id,
7623
8547
  input2.fromAgent,
7624
8548
  input2.targetAgent,
7625
8549
  input2.targetProject ?? null,
7626
8550
  targetDevice,
8551
+ sessionScope,
7627
8552
  input2.content,
7628
8553
  input2.priority ?? "normal",
7629
8554
  now
@@ -7637,9 +8562,10 @@ async function sendMessage(input2) {
7637
8562
  }
7638
8563
  } catch {
7639
8564
  }
8565
+ const sentScope = strictSessionScopeFilter(sessionScope);
7640
8566
  const result = await client.execute({
7641
- sql: "SELECT * FROM messages WHERE id = ?",
7642
- args: [id]
8567
+ sql: `SELECT * FROM messages WHERE id = ?${sentScope.sql}`,
8568
+ args: [id, ...sentScope.args]
7643
8569
  });
7644
8570
  return rowToMessage(result.rows[0]);
7645
8571
  }
@@ -7663,6 +8589,7 @@ async function deliverCrossMachineMessage(messageId, targetDevice) {
7663
8589
  fromAgent: msg.fromAgent,
7664
8590
  targetAgent: msg.targetAgent,
7665
8591
  targetProject: msg.targetProject,
8592
+ sessionScope: msg.sessionScope,
7666
8593
  content: msg.content,
7667
8594
  priority: msg.priority,
7668
8595
  createdAt: msg.createdAt
@@ -7706,7 +8633,7 @@ async function deliverLocalMessage(messageId) {
7706
8633
  } catch {
7707
8634
  const newRetryCount = msg.retryCount + 1;
7708
8635
  if (newRetryCount >= MAX_RETRIES3) {
7709
- await markFailed(messageId, "session unavailable after 10 retries");
8636
+ await markFailed(messageId, "session unavailable after 10 retries", msg.sessionScope);
7710
8637
  } else {
7711
8638
  await client.execute({
7712
8639
  sql: "UPDATE messages SET retry_count = ? WHERE id = ?",
@@ -7716,85 +8643,101 @@ async function deliverLocalMessage(messageId) {
7716
8643
  return false;
7717
8644
  }
7718
8645
  }
7719
- async function getPendingMessages(targetAgent) {
8646
+ async function getPendingMessages(targetAgent, sessionScope) {
7720
8647
  const client = getClient();
8648
+ const scope = strictSessionScopeFilter(sessionScope);
7721
8649
  const result = await client.execute({
7722
8650
  sql: `SELECT * FROM messages
7723
- WHERE target_agent = ? AND status IN ('pending', 'delivered')
8651
+ WHERE target_agent = ? AND status IN ('pending', 'delivered')${scope.sql}
7724
8652
  ORDER BY id`,
7725
- args: [targetAgent]
8653
+ args: [targetAgent, ...scope.args]
7726
8654
  });
7727
8655
  return result.rows.map((row) => rowToMessage(row));
7728
8656
  }
7729
- async function markRead(messageId) {
8657
+ async function markRead(messageId, sessionScope) {
7730
8658
  const client = getClient();
8659
+ const scope = strictSessionScopeFilter(sessionScope);
7731
8660
  await client.execute({
7732
- sql: "UPDATE messages SET status = 'read' WHERE id = ? AND status IN ('pending', 'delivered')",
7733
- args: [messageId]
8661
+ sql: `UPDATE messages SET status = 'read'
8662
+ WHERE id = ? AND status IN ('pending', 'delivered')${scope.sql}`,
8663
+ args: [messageId, ...scope.args]
7734
8664
  });
7735
8665
  }
7736
- async function markAcknowledged(messageId) {
8666
+ async function markAcknowledged(messageId, sessionScope) {
7737
8667
  const client = getClient();
8668
+ const scope = strictSessionScopeFilter(sessionScope);
7738
8669
  await client.execute({
7739
- sql: "UPDATE messages SET status = 'acknowledged', processed_at = ? WHERE id = ? AND status = 'read'",
7740
- args: [(/* @__PURE__ */ new Date()).toISOString(), messageId]
8670
+ sql: `UPDATE messages SET status = 'acknowledged', processed_at = ?
8671
+ WHERE id = ? AND status = 'read'${scope.sql}`,
8672
+ args: [(/* @__PURE__ */ new Date()).toISOString(), messageId, ...scope.args]
7741
8673
  });
7742
8674
  }
7743
- async function markProcessed(messageId) {
8675
+ async function markProcessed(messageId, sessionScope) {
7744
8676
  const client = getClient();
8677
+ const scope = strictSessionScopeFilter(sessionScope);
7745
8678
  await client.execute({
7746
- sql: "UPDATE messages SET status = 'processed', processed_at = ? WHERE id = ?",
7747
- args: [(/* @__PURE__ */ new Date()).toISOString(), messageId]
8679
+ sql: `UPDATE messages SET status = 'processed', processed_at = ?
8680
+ WHERE id = ?${scope.sql}`,
8681
+ args: [(/* @__PURE__ */ new Date()).toISOString(), messageId, ...scope.args]
7748
8682
  });
7749
8683
  }
7750
- async function getMessageStatus(messageId) {
8684
+ async function getMessageStatus(messageId, sessionScope) {
7751
8685
  const client = getClient();
8686
+ const scope = strictSessionScopeFilter(sessionScope);
7752
8687
  const result = await client.execute({
7753
- sql: "SELECT status FROM messages WHERE id = ?",
7754
- args: [messageId]
8688
+ sql: `SELECT status FROM messages WHERE id = ?${scope.sql}`,
8689
+ args: [messageId, ...scope.args]
7755
8690
  });
7756
8691
  return result.rows[0]?.status ?? null;
7757
8692
  }
7758
- async function getUnacknowledgedMessages(targetAgent) {
8693
+ async function getUnacknowledgedMessages(targetAgent, sessionScope) {
7759
8694
  const client = getClient();
8695
+ const scope = strictSessionScopeFilter(sessionScope);
7760
8696
  const result = await client.execute({
7761
8697
  sql: `SELECT * FROM messages
7762
- WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')
8698
+ WHERE target_agent = ? AND status IN ('pending', 'delivered', 'read')${scope.sql}
7763
8699
  ORDER BY id`,
7764
- args: [targetAgent]
8700
+ args: [targetAgent, ...scope.args]
7765
8701
  });
7766
8702
  return result.rows.map((row) => rowToMessage(row));
7767
8703
  }
7768
- async function getReadMessages(targetAgent) {
8704
+ async function getReadMessages(targetAgent, sessionScope) {
7769
8705
  const client = getClient();
8706
+ const scope = strictSessionScopeFilter(sessionScope);
7770
8707
  const result = await client.execute({
7771
- sql: "SELECT * FROM messages WHERE target_agent = ? AND status = 'read' ORDER BY id",
7772
- args: [targetAgent]
8708
+ sql: `SELECT * FROM messages
8709
+ WHERE target_agent = ? AND status = 'read'${scope.sql}
8710
+ ORDER BY id`,
8711
+ args: [targetAgent, ...scope.args]
7773
8712
  });
7774
8713
  return result.rows.map((row) => rowToMessage(row));
7775
8714
  }
7776
- async function markFailed(messageId, reason) {
8715
+ async function markFailed(messageId, reason, sessionScope) {
7777
8716
  const client = getClient();
8717
+ const scope = strictSessionScopeFilter(sessionScope);
7778
8718
  await client.execute({
7779
- sql: "UPDATE messages SET status = 'failed', failed_at = ?, failure_reason = ? WHERE id = ?",
7780
- args: [(/* @__PURE__ */ new Date()).toISOString(), reason, messageId]
8719
+ sql: `UPDATE messages SET status = 'failed', failed_at = ?, failure_reason = ?
8720
+ WHERE id = ?${scope.sql}`,
8721
+ args: [(/* @__PURE__ */ new Date()).toISOString(), reason, messageId, ...scope.args]
7781
8722
  });
7782
8723
  }
7783
- async function getFailedMessages() {
8724
+ async function getFailedMessages(sessionScope) {
7784
8725
  const client = getClient();
8726
+ const scope = strictSessionScopeFilter(sessionScope);
7785
8727
  const result = await client.execute({
7786
- sql: "SELECT * FROM messages WHERE status = 'failed' ORDER BY created_at DESC",
7787
- args: []
8728
+ sql: `SELECT * FROM messages WHERE status = 'failed'${scope.sql} ORDER BY created_at DESC`,
8729
+ args: [...scope.args]
7788
8730
  });
7789
8731
  return result.rows.map((row) => rowToMessage(row));
7790
8732
  }
7791
- async function retryPendingMessages() {
8733
+ async function retryPendingMessages(sessionScope) {
7792
8734
  const client = getClient();
8735
+ const scope = strictSessionScopeFilter(sessionScope);
7793
8736
  const result = await client.execute({
7794
8737
  sql: `SELECT * FROM messages
7795
- WHERE status = 'pending' AND retry_count < ?
8738
+ WHERE status = 'pending' AND retry_count < ?${scope.sql}
7796
8739
  ORDER BY id`,
7797
- args: [MAX_RETRIES3]
8740
+ args: [MAX_RETRIES3, ...scope.args]
7798
8741
  });
7799
8742
  let delivered = 0;
7800
8743
  for (const row of result.rows) {
@@ -7813,6 +8756,7 @@ var init_messaging = __esm({
7813
8756
  "use strict";
7814
8757
  init_database();
7815
8758
  init_tmux_routing();
8759
+ init_task_scope();
7816
8760
  MAX_RETRIES3 = 10;
7817
8761
  _wsClientSend = null;
7818
8762
  }
@@ -7823,8 +8767,8 @@ init_config();
7823
8767
  init_config();
7824
8768
  init_store();
7825
8769
  import { spawn as spawn2 } from "child_process";
7826
- import { readFileSync as readFileSync14, writeFileSync as writeFileSync9, mkdirSync as mkdirSync9, existsSync as existsSync17, openSync as openSync2, closeSync as closeSync2 } from "fs";
7827
- import path21 from "path";
8770
+ import { readFileSync as readFileSync15, writeFileSync as writeFileSync10, mkdirSync as mkdirSync9, existsSync as existsSync19, openSync as openSync2, closeSync as closeSync2 } from "fs";
8771
+ import path23 from "path";
7828
8772
  import { fileURLToPath as fileURLToPath3 } from "url";
7829
8773
 
7830
8774
  // src/lib/hybrid-search.ts
@@ -8364,10 +9308,10 @@ async function trajectoryBypass(queryText, agentId, options, limit) {
8364
9308
  init_config();
8365
9309
  init_session_key();
8366
9310
  init_employees();
8367
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, unlinkSync as unlinkSync3, readdirSync as readdirSync3 } from "fs";
9311
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3, unlinkSync as unlinkSync3, readdirSync as readdirSync3 } from "fs";
8368
9312
  import { execSync as execSync5 } from "child_process";
8369
- import path9 from "path";
8370
- var CACHE_DIR = path9.join(EXE_AI_DIR, "session-cache");
9313
+ import path12 from "path";
9314
+ var CACHE_DIR = path12.join(EXE_AI_DIR, "session-cache");
8371
9315
  var STALE_MS = 24 * 60 * 60 * 1e3;
8372
9316
  function isNameWithOptionalInstance(candidate, baseName) {
8373
9317
  if (candidate === baseName) return true;
@@ -8412,12 +9356,12 @@ function resolveActiveAgentFromTmuxSession(sessionName) {
8412
9356
  return null;
8413
9357
  }
8414
9358
  function getMarkerPath() {
8415
- return path9.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
9359
+ return path12.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
8416
9360
  }
8417
9361
  function getActiveAgent() {
8418
9362
  try {
8419
9363
  const markerPath = getMarkerPath();
8420
- const raw = readFileSync5(markerPath, "utf8");
9364
+ const raw = readFileSync7(markerPath, "utf8");
8421
9365
  const data = JSON.parse(raw);
8422
9366
  if (data.agentId) {
8423
9367
  if (data.startedAt) {
@@ -8470,7 +9414,7 @@ if (!process.env.AGENT_ID) {
8470
9414
  if (!loadConfigSync().autoRetrieval) {
8471
9415
  process.exit(0);
8472
9416
  }
8473
- var WORKER_LOG_PATH = path21.join(EXE_AI_DIR, "workers.log");
9417
+ var WORKER_LOG_PATH = path23.join(EXE_AI_DIR, "workers.log");
8474
9418
  function openWorkerLog() {
8475
9419
  try {
8476
9420
  return openSync2(WORKER_LOG_PATH, "a");
@@ -8478,10 +9422,10 @@ function openWorkerLog() {
8478
9422
  return "ignore";
8479
9423
  }
8480
9424
  }
8481
- var CACHE_DIR2 = path21.join(EXE_AI_DIR, "session-cache");
9425
+ var CACHE_DIR2 = path23.join(EXE_AI_DIR, "session-cache");
8482
9426
  function loadInjectedIds(sessionId) {
8483
9427
  try {
8484
- const raw = readFileSync14(path21.join(CACHE_DIR2, `${sessionId}.json`), "utf8");
9428
+ const raw = readFileSync15(path23.join(CACHE_DIR2, `${sessionId}.json`), "utf8");
8485
9429
  return new Set(JSON.parse(raw));
8486
9430
  } catch {
8487
9431
  return /* @__PURE__ */ new Set();
@@ -8490,8 +9434,8 @@ function loadInjectedIds(sessionId) {
8490
9434
  function saveInjectedIds(sessionId, ids) {
8491
9435
  try {
8492
9436
  mkdirSync9(CACHE_DIR2, { recursive: true });
8493
- writeFileSync9(
8494
- path21.join(CACHE_DIR2, `${sessionId}.json`),
9437
+ writeFileSync10(
9438
+ path23.join(CACHE_DIR2, `${sessionId}.json`),
8495
9439
  JSON.stringify([...ids])
8496
9440
  );
8497
9441
  } catch {
@@ -8541,13 +9485,23 @@ process.stdin.on("end", async () => {
8541
9485
  const { getAgentRuntime: getAgentRuntime2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
8542
9486
  const { baseAgentName: baseAgentName2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
8543
9487
  const { isSessionBusy: isSessionBusy2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
9488
+ const MARKER_STALE_MS = 4 * 60 * 60 * 1e3;
8544
9489
  const hasInProgressTask = (agentName) => {
8545
9490
  try {
8546
- const path22 = __require("path");
8547
- const { existsSync: existsSync18 } = __require("fs");
8548
- const os10 = __require("os");
8549
- const markerPath = path22.join(os10.homedir(), ".exe-os", "session-cache", `current-task-${agentName}.json`);
8550
- return existsSync18(markerPath);
9491
+ const p = __require("path");
9492
+ const fs = __require("fs");
9493
+ const o = __require("os");
9494
+ const markerPath = p.join(o.homedir(), ".exe-os", "session-cache", `current-task-${agentName}.json`);
9495
+ if (!fs.existsSync(markerPath)) return false;
9496
+ const stat = fs.statSync(markerPath);
9497
+ if (Date.now() - stat.mtimeMs > MARKER_STALE_MS) {
9498
+ try {
9499
+ fs.unlinkSync(markerPath);
9500
+ } catch {
9501
+ }
9502
+ return false;
9503
+ }
9504
+ return true;
8551
9505
  } catch {
8552
9506
  return false;
8553
9507
  }
@@ -8599,7 +9553,7 @@ ${fresh.map(
8599
9553
  try {
8600
9554
  const { countPendingReviews: countPendingReviews2, countNewPendingReviewsSince: countNewPendingReviewsSince2 } = await Promise.resolve().then(() => (init_tasks_review(), tasks_review_exports));
8601
9555
  const sessionKey = getSessionKey();
8602
- const lastCheckPath = path21.join(CACHE_DIR2, `review-lastcheck-${sessionKey}.json`);
9556
+ const lastCheckPath = path23.join(CACHE_DIR2, `review-lastcheck-${sessionKey}.json`);
8603
9557
  let sessionScope;
8604
9558
  try {
8605
9559
  const { execSync: execSync9 } = await import("child_process");
@@ -8609,7 +9563,7 @@ ${fresh.map(
8609
9563
  }
8610
9564
  let lastCheckedAt = "";
8611
9565
  try {
8612
- lastCheckedAt = readFileSync14(lastCheckPath, "utf8").trim();
9566
+ lastCheckedAt = readFileSync15(lastCheckPath, "utf8").trim();
8613
9567
  } catch {
8614
9568
  }
8615
9569
  const totalCount = await countPendingReviews2(sessionScope);
@@ -8667,11 +9621,11 @@ IMPORTANT: After completing your current task, you MUST address the pending revi
8667
9621
  function spawnPromptWorker(prompt, sessionId, agent) {
8668
9622
  if (!loadConfigSync().autoIngestion) return;
8669
9623
  try {
8670
- const workerPath = path21.resolve(
8671
- path21.dirname(fileURLToPath3(import.meta.url)),
9624
+ const workerPath = path23.resolve(
9625
+ path23.dirname(fileURLToPath3(import.meta.url)),
8672
9626
  "prompt-ingest-worker.js"
8673
9627
  );
8674
- if (!existsSync17(workerPath)) {
9628
+ if (!existsSync19(workerPath)) {
8675
9629
  process.stderr.write(`[prompt-submit] WARN: prompt-ingest-worker not found at ${workerPath}
8676
9630
  `);
8677
9631
  return;