@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
@@ -32,6 +32,44 @@ var init_db_retry = __esm({
32
32
  }
33
33
  });
34
34
 
35
+ // src/lib/secure-files.ts
36
+ import { chmodSync, existsSync, mkdirSync } from "fs";
37
+ import { chmod, mkdir } from "fs/promises";
38
+ async function ensurePrivateDir(dirPath) {
39
+ await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
40
+ try {
41
+ await chmod(dirPath, PRIVATE_DIR_MODE);
42
+ } catch {
43
+ }
44
+ }
45
+ function ensurePrivateDirSync(dirPath) {
46
+ mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
47
+ try {
48
+ chmodSync(dirPath, PRIVATE_DIR_MODE);
49
+ } catch {
50
+ }
51
+ }
52
+ async function enforcePrivateFile(filePath) {
53
+ try {
54
+ await chmod(filePath, PRIVATE_FILE_MODE);
55
+ } catch {
56
+ }
57
+ }
58
+ function enforcePrivateFileSync(filePath) {
59
+ try {
60
+ if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
61
+ } catch {
62
+ }
63
+ }
64
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
65
+ var init_secure_files = __esm({
66
+ "src/lib/secure-files.ts"() {
67
+ "use strict";
68
+ PRIVATE_DIR_MODE = 448;
69
+ PRIVATE_FILE_MODE = 384;
70
+ }
71
+ });
72
+
35
73
  // src/lib/config.ts
36
74
  var config_exports = {};
37
75
  __export(config_exports, {
@@ -48,8 +86,8 @@ __export(config_exports, {
48
86
  migrateConfig: () => migrateConfig,
49
87
  saveConfig: () => saveConfig
50
88
  });
51
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
52
- import { readFileSync, existsSync, renameSync } from "fs";
89
+ import { readFile, writeFile } from "fs/promises";
90
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
53
91
  import path from "path";
54
92
  import os from "os";
55
93
  function resolveDataDir() {
@@ -57,7 +95,7 @@ function resolveDataDir() {
57
95
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
58
96
  const newDir = path.join(os.homedir(), ".exe-os");
59
97
  const legacyDir = path.join(os.homedir(), ".exe-mem");
60
- if (!existsSync(newDir) && existsSync(legacyDir)) {
98
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
61
99
  try {
62
100
  renameSync(legacyDir, newDir);
63
101
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -120,9 +158,9 @@ function normalizeAutoUpdate(raw) {
120
158
  }
121
159
  async function loadConfig() {
122
160
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
123
- await mkdir(dir, { recursive: true });
161
+ await ensurePrivateDir(dir);
124
162
  const configPath = path.join(dir, "config.json");
125
- if (!existsSync(configPath)) {
163
+ if (!existsSync2(configPath)) {
126
164
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
127
165
  }
128
166
  const raw = await readFile(configPath, "utf-8");
@@ -135,6 +173,7 @@ async function loadConfig() {
135
173
  `);
136
174
  try {
137
175
  await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
176
+ await enforcePrivateFile(configPath);
138
177
  } catch {
139
178
  }
140
179
  }
@@ -153,7 +192,7 @@ async function loadConfig() {
153
192
  function loadConfigSync() {
154
193
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
155
194
  const configPath = path.join(dir, "config.json");
156
- if (!existsSync(configPath)) {
195
+ if (!existsSync2(configPath)) {
157
196
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
158
197
  }
159
198
  try {
@@ -171,12 +210,10 @@ function loadConfigSync() {
171
210
  }
172
211
  async function saveConfig(config) {
173
212
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
174
- await mkdir(dir, { recursive: true });
213
+ await ensurePrivateDir(dir);
175
214
  const configPath = path.join(dir, "config.json");
176
215
  await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
177
- if (config.cloud?.apiKey) {
178
- await chmod(configPath, 384);
179
- }
216
+ await enforcePrivateFile(configPath);
180
217
  }
181
218
  async function loadConfigFrom(configPath) {
182
219
  const raw = await readFile(configPath, "utf-8");
@@ -196,6 +233,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
196
233
  var init_config = __esm({
197
234
  "src/lib/config.ts"() {
198
235
  "use strict";
236
+ init_secure_files();
199
237
  EXE_AI_DIR = resolveDataDir();
200
238
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
201
239
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
@@ -272,6 +310,120 @@ var init_config = __esm({
272
310
  }
273
311
  });
274
312
 
313
+ // src/lib/runtime-table.ts
314
+ var RUNTIME_TABLE, DEFAULT_RUNTIME;
315
+ var init_runtime_table = __esm({
316
+ "src/lib/runtime-table.ts"() {
317
+ "use strict";
318
+ RUNTIME_TABLE = {
319
+ codex: {
320
+ binary: "codex",
321
+ launchMode: "interactive",
322
+ autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
323
+ inlineFlag: "--no-alt-screen",
324
+ apiKeyEnv: "OPENAI_API_KEY",
325
+ defaultModel: "gpt-5.4"
326
+ },
327
+ opencode: {
328
+ binary: "opencode",
329
+ launchMode: "exec",
330
+ autoApproveFlag: "--dangerously-skip-permissions",
331
+ inlineFlag: "",
332
+ apiKeyEnv: "ANTHROPIC_API_KEY",
333
+ defaultModel: "anthropic/claude-sonnet-4-6"
334
+ }
335
+ };
336
+ DEFAULT_RUNTIME = "claude";
337
+ }
338
+ });
339
+
340
+ // src/lib/agent-config.ts
341
+ var agent_config_exports = {};
342
+ __export(agent_config_exports, {
343
+ AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
344
+ DEFAULT_MODELS: () => DEFAULT_MODELS,
345
+ KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
346
+ RUNTIME_LABELS: () => RUNTIME_LABELS,
347
+ clearAgentRuntime: () => clearAgentRuntime,
348
+ getAgentRuntime: () => getAgentRuntime,
349
+ loadAgentConfig: () => loadAgentConfig,
350
+ saveAgentConfig: () => saveAgentConfig,
351
+ setAgentRuntime: () => setAgentRuntime
352
+ });
353
+ import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
354
+ import path2 from "path";
355
+ function loadAgentConfig() {
356
+ if (!existsSync3(AGENT_CONFIG_PATH)) return {};
357
+ try {
358
+ return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
359
+ } catch {
360
+ return {};
361
+ }
362
+ }
363
+ function saveAgentConfig(config) {
364
+ const dir = path2.dirname(AGENT_CONFIG_PATH);
365
+ ensurePrivateDirSync(dir);
366
+ writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
367
+ enforcePrivateFileSync(AGENT_CONFIG_PATH);
368
+ }
369
+ function getAgentRuntime(agentId) {
370
+ const config = loadAgentConfig();
371
+ const entry = config[agentId];
372
+ if (entry) return entry;
373
+ const orgDefault = config["default"];
374
+ if (orgDefault) return orgDefault;
375
+ return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
376
+ }
377
+ function setAgentRuntime(agentId, runtime, model) {
378
+ const knownModels = KNOWN_RUNTIMES[runtime];
379
+ if (!knownModels) {
380
+ return {
381
+ ok: false,
382
+ error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
383
+ };
384
+ }
385
+ if (!knownModels.includes(model)) {
386
+ return {
387
+ ok: false,
388
+ error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
389
+ };
390
+ }
391
+ const config = loadAgentConfig();
392
+ config[agentId] = { runtime, model };
393
+ saveAgentConfig(config);
394
+ return { ok: true };
395
+ }
396
+ function clearAgentRuntime(agentId) {
397
+ const config = loadAgentConfig();
398
+ delete config[agentId];
399
+ saveAgentConfig(config);
400
+ }
401
+ var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
402
+ var init_agent_config = __esm({
403
+ "src/lib/agent-config.ts"() {
404
+ "use strict";
405
+ init_config();
406
+ init_runtime_table();
407
+ init_secure_files();
408
+ AGENT_CONFIG_PATH = path2.join(EXE_AI_DIR, "agent-config.json");
409
+ KNOWN_RUNTIMES = {
410
+ claude: ["claude-opus-4", "claude-sonnet-4", "claude-haiku-4.5"],
411
+ codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
412
+ opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
413
+ };
414
+ RUNTIME_LABELS = {
415
+ claude: "Claude Code (Anthropic)",
416
+ codex: "Codex (OpenAI)",
417
+ opencode: "OpenCode (open source)"
418
+ };
419
+ DEFAULT_MODELS = {
420
+ claude: "claude-opus-4",
421
+ codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
422
+ opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
423
+ };
424
+ }
425
+ });
426
+
275
427
  // src/lib/employees.ts
276
428
  var employees_exports = {};
277
429
  __export(employees_exports, {
@@ -287,6 +439,7 @@ __export(employees_exports, {
287
439
  getEmployeeByRole: () => getEmployeeByRole,
288
440
  getEmployeeNamesByRole: () => getEmployeeNamesByRole,
289
441
  hasRole: () => hasRole,
442
+ hireEmployee: () => hireEmployee,
290
443
  isCoordinatorName: () => isCoordinatorName,
291
444
  isCoordinatorRole: () => isCoordinatorRole,
292
445
  isMultiInstance: () => isMultiInstance,
@@ -299,9 +452,9 @@ __export(employees_exports, {
299
452
  validateEmployeeName: () => validateEmployeeName
300
453
  });
301
454
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
302
- import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
455
+ import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
303
456
  import { execSync } from "child_process";
304
- import path2 from "path";
457
+ import path3 from "path";
305
458
  import os2 from "os";
306
459
  function normalizeRole(role) {
307
460
  return (role ?? "").trim().toLowerCase();
@@ -338,7 +491,7 @@ function validateEmployeeName(name) {
338
491
  return { valid: true };
339
492
  }
340
493
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
341
- if (!existsSync2(employeesPath)) {
494
+ if (!existsSync4(employeesPath)) {
342
495
  return [];
343
496
  }
344
497
  const raw = await readFile2(employeesPath, "utf-8");
@@ -349,13 +502,13 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
349
502
  }
350
503
  }
351
504
  async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
352
- await mkdir2(path2.dirname(employeesPath), { recursive: true });
505
+ await mkdir2(path3.dirname(employeesPath), { recursive: true });
353
506
  await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
354
507
  }
355
508
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
356
- if (!existsSync2(employeesPath)) return [];
509
+ if (!existsSync4(employeesPath)) return [];
357
510
  try {
358
- return JSON.parse(readFileSync2(employeesPath, "utf-8"));
511
+ return JSON.parse(readFileSync3(employeesPath, "utf-8"));
359
512
  } catch {
360
513
  return [];
361
514
  }
@@ -397,6 +550,52 @@ function addEmployee(employees, employee) {
397
550
  }
398
551
  return [...employees, normalized];
399
552
  }
553
+ function appendToCoordinatorTeam(employee) {
554
+ const coordinator = getCoordinatorEmployee(loadEmployeesSync());
555
+ if (!coordinator) return;
556
+ const idPath = path3.join(IDENTITY_DIR, `${coordinator.name}.md`);
557
+ if (!existsSync4(idPath)) return;
558
+ const content = readFileSync3(idPath, "utf-8");
559
+ if (content.includes(`**${capitalize(employee.name)}`)) return;
560
+ const teamMatch = content.match(TEAM_SECTION_RE);
561
+ if (!teamMatch || teamMatch.index === void 0) return;
562
+ const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
563
+ const nextHeading = afterTeam.match(/\n## /);
564
+ const entry = `
565
+ **${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
566
+ `;
567
+ let updated;
568
+ if (nextHeading && nextHeading.index !== void 0) {
569
+ const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
570
+ updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
571
+ } else {
572
+ updated = content.trimEnd() + "\n" + entry;
573
+ }
574
+ writeFileSync2(idPath, updated, "utf-8");
575
+ }
576
+ function capitalize(s) {
577
+ return s.charAt(0).toUpperCase() + s.slice(1);
578
+ }
579
+ async function hireEmployee(employee) {
580
+ const employees = await loadEmployees();
581
+ const updated = addEmployee(employees, employee);
582
+ await saveEmployees(updated);
583
+ try {
584
+ appendToCoordinatorTeam(employee);
585
+ } catch {
586
+ }
587
+ try {
588
+ const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
589
+ const config = loadAgentConfig2();
590
+ const name = employee.name.toLowerCase();
591
+ if (!config[name] && config["default"]) {
592
+ config[name] = { ...config["default"] };
593
+ saveAgentConfig2(config);
594
+ }
595
+ } catch {
596
+ }
597
+ return updated;
598
+ }
400
599
  async function normalizeRosterCase(rosterPath) {
401
600
  const employees = await loadEmployees(rosterPath);
402
601
  let changed = false;
@@ -406,14 +605,14 @@ async function normalizeRosterCase(rosterPath) {
406
605
  emp.name = emp.name.toLowerCase();
407
606
  changed = true;
408
607
  try {
409
- const identityDir = path2.join(os2.homedir(), ".exe-os", "identity");
410
- const oldPath = path2.join(identityDir, `${oldName}.md`);
411
- const newPath = path2.join(identityDir, `${emp.name}.md`);
412
- if (existsSync2(oldPath) && !existsSync2(newPath)) {
608
+ const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
609
+ const oldPath = path3.join(identityDir, `${oldName}.md`);
610
+ const newPath = path3.join(identityDir, `${emp.name}.md`);
611
+ if (existsSync4(oldPath) && !existsSync4(newPath)) {
413
612
  renameSync2(oldPath, newPath);
414
- } else if (existsSync2(oldPath) && oldPath !== newPath) {
415
- const content = readFileSync2(oldPath, "utf-8");
416
- writeFileSync(newPath, content, "utf-8");
613
+ } else if (existsSync4(oldPath) && oldPath !== newPath) {
614
+ const content = readFileSync3(oldPath, "utf-8");
615
+ writeFileSync2(newPath, content, "utf-8");
417
616
  if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
418
617
  unlinkSync(oldPath);
419
618
  }
@@ -443,7 +642,7 @@ function registerBinSymlinks(name) {
443
642
  errors.push("Could not find 'exe-os' in PATH");
444
643
  return { created, skipped, errors };
445
644
  }
446
- const binDir = path2.dirname(exeBinPath);
645
+ const binDir = path3.dirname(exeBinPath);
447
646
  let target;
448
647
  try {
449
648
  target = readlinkSync(exeBinPath);
@@ -453,8 +652,8 @@ function registerBinSymlinks(name) {
453
652
  }
454
653
  for (const suffix of ["", "-opencode"]) {
455
654
  const linkName = `${name}${suffix}`;
456
- const linkPath = path2.join(binDir, linkName);
457
- if (existsSync2(linkPath)) {
655
+ const linkPath = path3.join(binDir, linkName);
656
+ if (existsSync4(linkPath)) {
458
657
  skipped.push(linkName);
459
658
  continue;
460
659
  }
@@ -467,24 +666,50 @@ function registerBinSymlinks(name) {
467
666
  }
468
667
  return { created, skipped, errors };
469
668
  }
470
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
669
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
471
670
  var init_employees = __esm({
472
671
  "src/lib/employees.ts"() {
473
672
  "use strict";
474
673
  init_config();
475
- EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
674
+ EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
476
675
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
477
676
  COORDINATOR_ROLE = "COO";
478
677
  MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
678
+ IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
679
+ TEAM_SECTION_RE = /^## Team\b.*$/m;
680
+ }
681
+ });
682
+
683
+ // src/lib/database-adapter.ts
684
+ import os3 from "os";
685
+ import path4 from "path";
686
+ import { createRequire } from "module";
687
+ import { pathToFileURL } from "url";
688
+ var BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES;
689
+ var init_database_adapter = __esm({
690
+ "src/lib/database-adapter.ts"() {
691
+ "use strict";
692
+ BOOLEAN_COLUMNS_BY_TABLE = {
693
+ memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
694
+ behaviors: /* @__PURE__ */ new Set(["active"]),
695
+ notifications: /* @__PURE__ */ new Set(["read"]),
696
+ users: /* @__PURE__ */ new Set(["has_personal_memory"])
697
+ };
698
+ BOOLEAN_COLUMN_NAMES = new Set(
699
+ Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
700
+ );
479
701
  }
480
702
  });
481
703
 
482
704
  // src/lib/database.ts
483
705
  import { createClient } from "@libsql/client";
484
706
  function getClient() {
485
- if (!_resilientClient) {
707
+ if (!_adapterClient) {
486
708
  throw new Error("Database client not initialized. Call initDatabase() first.");
487
709
  }
710
+ if (process.env.DATABASE_URL) {
711
+ return _adapterClient;
712
+ }
488
713
  if (process.env.EXE_IS_DAEMON === "1") {
489
714
  return _resilientClient;
490
715
  }
@@ -493,132 +718,27 @@ function getClient() {
493
718
  }
494
719
  return _resilientClient;
495
720
  }
496
- var _resilientClient, _daemonClient;
721
+ var _resilientClient, _daemonClient, _adapterClient;
497
722
  var init_database = __esm({
498
723
  "src/lib/database.ts"() {
499
724
  "use strict";
500
725
  init_db_retry();
501
726
  init_employees();
727
+ init_database_adapter();
502
728
  _resilientClient = null;
503
729
  _daemonClient = null;
504
- }
505
- });
506
-
507
- // src/lib/notifications.ts
508
- import crypto from "crypto";
509
- import path3 from "path";
510
- import os3 from "os";
511
- import {
512
- readFileSync as readFileSync3,
513
- readdirSync,
514
- unlinkSync as unlinkSync2,
515
- existsSync as existsSync3,
516
- rmdirSync
517
- } from "fs";
518
- async function writeNotification(notification) {
519
- try {
520
- const client = getClient();
521
- const id = crypto.randomUUID();
522
- const now = (/* @__PURE__ */ new Date()).toISOString();
523
- await client.execute({
524
- sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
525
- VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
526
- args: [
527
- id,
528
- notification.agentId,
529
- notification.agentRole,
530
- notification.event,
531
- notification.project,
532
- notification.summary,
533
- notification.taskFile ?? null,
534
- now
535
- ]
536
- });
537
- } catch (err) {
538
- process.stderr.write(`[notifications] WRITE FAILED: ${err instanceof Error ? err.message : String(err)}
539
- `);
540
- }
541
- }
542
- async function markAsReadByTaskFile(taskFile) {
543
- try {
544
- const client = getClient();
545
- await client.execute({
546
- sql: "UPDATE notifications SET read = 1 WHERE task_file = ? AND read = 0",
547
- args: [taskFile]
548
- });
549
- } catch {
550
- }
551
- }
552
- var init_notifications = __esm({
553
- "src/lib/notifications.ts"() {
554
- "use strict";
555
- init_database();
556
- }
557
- });
558
-
559
- // src/lib/state-bus.ts
560
- var StateBus, orgBus;
561
- var init_state_bus = __esm({
562
- "src/lib/state-bus.ts"() {
563
- "use strict";
564
- StateBus = class {
565
- handlers = /* @__PURE__ */ new Map();
566
- globalHandlers = /* @__PURE__ */ new Set();
567
- /** Emit an event to all subscribers */
568
- emit(event) {
569
- const typeHandlers = this.handlers.get(event.type);
570
- if (typeHandlers) {
571
- for (const handler of typeHandlers) {
572
- try {
573
- handler(event);
574
- } catch {
575
- }
576
- }
577
- }
578
- for (const handler of this.globalHandlers) {
579
- try {
580
- handler(event);
581
- } catch {
582
- }
583
- }
584
- }
585
- /** Subscribe to a specific event type */
586
- on(type, handler) {
587
- if (!this.handlers.has(type)) {
588
- this.handlers.set(type, /* @__PURE__ */ new Set());
589
- }
590
- this.handlers.get(type).add(handler);
591
- }
592
- /** Subscribe to ALL events */
593
- onAny(handler) {
594
- this.globalHandlers.add(handler);
595
- }
596
- /** Unsubscribe from a specific event type */
597
- off(type, handler) {
598
- this.handlers.get(type)?.delete(handler);
599
- }
600
- /** Unsubscribe from ALL events */
601
- offAny(handler) {
602
- this.globalHandlers.delete(handler);
603
- }
604
- /** Remove all listeners */
605
- clear() {
606
- this.handlers.clear();
607
- this.globalHandlers.clear();
608
- }
609
- };
610
- orgBus = new StateBus();
730
+ _adapterClient = null;
611
731
  }
612
732
  });
613
733
 
614
734
  // src/lib/session-registry.ts
615
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync, existsSync as existsSync4 } from "fs";
616
- import path4 from "path";
735
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync5 } from "fs";
736
+ import path5 from "path";
617
737
  import os4 from "os";
618
738
  function registerSession(entry) {
619
- const dir = path4.dirname(REGISTRY_PATH);
620
- if (!existsSync4(dir)) {
621
- mkdirSync(dir, { recursive: true });
739
+ const dir = path5.dirname(REGISTRY_PATH);
740
+ if (!existsSync5(dir)) {
741
+ mkdirSync2(dir, { recursive: true });
622
742
  }
623
743
  const sessions = listSessions();
624
744
  const idx = sessions.findIndex((s) => s.windowName === entry.windowName);
@@ -627,7 +747,7 @@ function registerSession(entry) {
627
747
  } else {
628
748
  sessions.push(entry);
629
749
  }
630
- writeFileSync2(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
750
+ writeFileSync3(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
631
751
  }
632
752
  function listSessions() {
633
753
  try {
@@ -641,7 +761,7 @@ var REGISTRY_PATH;
641
761
  var init_session_registry = __esm({
642
762
  "src/lib/session-registry.ts"() {
643
763
  "use strict";
644
- REGISTRY_PATH = path4.join(os4.homedir(), ".exe-os", "session-registry.json");
764
+ REGISTRY_PATH = path5.join(os4.homedir(), ".exe-os", "session-registry.json");
645
765
  }
646
766
  });
647
767
 
@@ -893,67 +1013,6 @@ var init_provider_table = __esm({
893
1013
  }
894
1014
  });
895
1015
 
896
- // src/lib/runtime-table.ts
897
- var RUNTIME_TABLE, DEFAULT_RUNTIME;
898
- var init_runtime_table = __esm({
899
- "src/lib/runtime-table.ts"() {
900
- "use strict";
901
- RUNTIME_TABLE = {
902
- codex: {
903
- binary: "codex",
904
- launchMode: "interactive",
905
- autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
906
- inlineFlag: "--no-alt-screen",
907
- apiKeyEnv: "OPENAI_API_KEY",
908
- defaultModel: "gpt-5.4"
909
- },
910
- opencode: {
911
- binary: "opencode",
912
- launchMode: "exec",
913
- autoApproveFlag: "--dangerously-skip-permissions",
914
- inlineFlag: "",
915
- apiKeyEnv: "ANTHROPIC_API_KEY",
916
- defaultModel: "anthropic/claude-sonnet-4-6"
917
- }
918
- };
919
- DEFAULT_RUNTIME = "claude";
920
- }
921
- });
922
-
923
- // src/lib/agent-config.ts
924
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
925
- import path5 from "path";
926
- function loadAgentConfig() {
927
- if (!existsSync5(AGENT_CONFIG_PATH)) return {};
928
- try {
929
- return JSON.parse(readFileSync5(AGENT_CONFIG_PATH, "utf-8"));
930
- } catch {
931
- return {};
932
- }
933
- }
934
- function getAgentRuntime(agentId) {
935
- const config = loadAgentConfig();
936
- const entry = config[agentId];
937
- if (entry) return entry;
938
- const orgDefault = config["default"];
939
- if (orgDefault) return orgDefault;
940
- return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
941
- }
942
- var AGENT_CONFIG_PATH, DEFAULT_MODELS;
943
- var init_agent_config = __esm({
944
- "src/lib/agent-config.ts"() {
945
- "use strict";
946
- init_config();
947
- init_runtime_table();
948
- AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
949
- DEFAULT_MODELS = {
950
- claude: "claude-opus-4",
951
- codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
952
- opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
953
- };
954
- }
955
- });
956
-
957
1016
  // src/lib/intercom-queue.ts
958
1017
  var intercom_queue_exports = {};
959
1018
  __export(intercom_queue_exports, {
@@ -963,7 +1022,7 @@ __export(intercom_queue_exports, {
963
1022
  queueIntercom: () => queueIntercom,
964
1023
  readQueue: () => readQueue
965
1024
  });
966
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
1025
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
967
1026
  import path6 from "path";
968
1027
  import os5 from "os";
969
1028
  function ensureDir() {
@@ -973,7 +1032,7 @@ function ensureDir() {
973
1032
  function readQueue() {
974
1033
  try {
975
1034
  if (!existsSync6(QUEUE_PATH)) return [];
976
- return JSON.parse(readFileSync6(QUEUE_PATH, "utf8"));
1035
+ return JSON.parse(readFileSync5(QUEUE_PATH, "utf8"));
977
1036
  } catch {
978
1037
  return [];
979
1038
  }
@@ -1081,8 +1140,11 @@ var init_intercom_queue = __esm({
1081
1140
  });
1082
1141
 
1083
1142
  // src/lib/license.ts
1084
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
1143
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
1085
1144
  import { randomUUID } from "crypto";
1145
+ import { createRequire as createRequire2 } from "module";
1146
+ import { pathToFileURL as pathToFileURL2 } from "url";
1147
+ import os6 from "os";
1086
1148
  import path7 from "path";
1087
1149
  import { jwtVerify, importSPKI } from "jose";
1088
1150
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
@@ -1104,12 +1166,12 @@ var init_license = __esm({
1104
1166
  });
1105
1167
 
1106
1168
  // src/lib/plan-limits.ts
1107
- import { readFileSync as readFileSync8, existsSync as existsSync8 } from "fs";
1169
+ import { readFileSync as readFileSync7, existsSync as existsSync8 } from "fs";
1108
1170
  import path8 from "path";
1109
1171
  function getLicenseSync() {
1110
1172
  try {
1111
1173
  if (!existsSync8(CACHE_PATH2)) return freeLicense();
1112
- const raw = JSON.parse(readFileSync8(CACHE_PATH2, "utf8"));
1174
+ const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
1113
1175
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
1114
1176
  const parts = raw.token.split(".");
1115
1177
  if (parts.length !== 3) return freeLicense();
@@ -1148,7 +1210,7 @@ function assertEmployeeLimitSync(rosterPath) {
1148
1210
  let count = 0;
1149
1211
  try {
1150
1212
  if (existsSync8(filePath)) {
1151
- const raw = readFileSync8(filePath, "utf8");
1213
+ const raw = readFileSync7(filePath, "utf8");
1152
1214
  const employees = JSON.parse(raw);
1153
1215
  count = Array.isArray(employees) ? employees.length : 0;
1154
1216
  }
@@ -1182,7 +1244,7 @@ var init_plan_limits = __esm({
1182
1244
  });
1183
1245
 
1184
1246
  // src/lib/session-kill-telemetry.ts
1185
- import crypto2 from "crypto";
1247
+ import crypto from "crypto";
1186
1248
  async function recordSessionKill(input) {
1187
1249
  try {
1188
1250
  const client = getClient();
@@ -1192,7 +1254,7 @@ async function recordSessionKill(input) {
1192
1254
  ticks_idle, estimated_tokens_saved)
1193
1255
  VALUES (?, ?, ?, ?, ?, ?, ?)`,
1194
1256
  args: [
1195
- crypto2.randomUUID(),
1257
+ crypto.randomUUID(),
1196
1258
  input.sessionName,
1197
1259
  input.agentId,
1198
1260
  (/* @__PURE__ */ new Date()).toISOString(),
@@ -1515,6 +1577,7 @@ __export(tmux_routing_exports, {
1515
1577
  isEmployeeAlive: () => isEmployeeAlive,
1516
1578
  isExeSession: () => isExeSession,
1517
1579
  isSessionBusy: () => isSessionBusy,
1580
+ notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
1518
1581
  notifyParentExe: () => notifyParentExe,
1519
1582
  parseParentExe: () => parseParentExe,
1520
1583
  registerParentExe: () => registerParentExe,
@@ -1525,11 +1588,11 @@ __export(tmux_routing_exports, {
1525
1588
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
1526
1589
  });
1527
1590
  import { execFileSync as execFileSync2, execSync as execSync4 } from "child_process";
1528
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, existsSync as existsSync9, appendFileSync, readdirSync as readdirSync2 } from "fs";
1591
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, existsSync as existsSync9, appendFileSync, readdirSync } from "fs";
1529
1592
  import path9 from "path";
1530
- import os6 from "os";
1593
+ import os7 from "os";
1531
1594
  import { fileURLToPath } from "url";
1532
- import { unlinkSync as unlinkSync3 } from "fs";
1595
+ import { unlinkSync as unlinkSync2 } from "fs";
1533
1596
  function spawnLockPath(sessionName) {
1534
1597
  return path9.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
1535
1598
  }
@@ -1548,7 +1611,7 @@ function acquireSpawnLock(sessionName) {
1548
1611
  const lockFile = spawnLockPath(sessionName);
1549
1612
  if (existsSync9(lockFile)) {
1550
1613
  try {
1551
- const lock = JSON.parse(readFileSync9(lockFile, "utf8"));
1614
+ const lock = JSON.parse(readFileSync8(lockFile, "utf8"));
1552
1615
  const age = Date.now() - lock.timestamp;
1553
1616
  if (isProcessAlive(lock.pid) && age < 6e4) {
1554
1617
  return false;
@@ -1561,7 +1624,7 @@ function acquireSpawnLock(sessionName) {
1561
1624
  }
1562
1625
  function releaseSpawnLock(sessionName) {
1563
1626
  try {
1564
- unlinkSync3(spawnLockPath(sessionName));
1627
+ unlinkSync2(spawnLockPath(sessionName));
1565
1628
  } catch {
1566
1629
  }
1567
1630
  }
@@ -1653,7 +1716,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
1653
1716
  }
1654
1717
  function getParentExe(sessionKey) {
1655
1718
  try {
1656
- const data = JSON.parse(readFileSync9(path9.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
1719
+ const data = JSON.parse(readFileSync8(path9.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
1657
1720
  return data.parentExe || null;
1658
1721
  } catch {
1659
1722
  return null;
@@ -1661,7 +1724,7 @@ function getParentExe(sessionKey) {
1661
1724
  }
1662
1725
  function getDispatchedBy(sessionKey) {
1663
1726
  try {
1664
- const data = JSON.parse(readFileSync9(
1727
+ const data = JSON.parse(readFileSync8(
1665
1728
  path9.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
1666
1729
  "utf8"
1667
1730
  ));
@@ -1733,7 +1796,7 @@ async function verifyPaneAtCapacity(sessionName) {
1733
1796
  function readDebounceState() {
1734
1797
  try {
1735
1798
  if (!existsSync9(DEBOUNCE_FILE)) return {};
1736
- const raw = JSON.parse(readFileSync9(DEBOUNCE_FILE, "utf8"));
1799
+ const raw = JSON.parse(readFileSync8(DEBOUNCE_FILE, "utf8"));
1737
1800
  const state = {};
1738
1801
  for (const [key, val] of Object.entries(raw)) {
1739
1802
  if (typeof val === "number") {
@@ -1860,7 +1923,7 @@ function sendIntercom(targetSession) {
1860
1923
  const agent = baseAgentName(rawAgent);
1861
1924
  const taskDir = path9.join(process.cwd(), "exe", agent);
1862
1925
  if (existsSync9(taskDir)) {
1863
- const files = readdirSync2(taskDir).filter(
1926
+ const files = readdirSync(taskDir).filter(
1864
1927
  (f) => f.endsWith(".md") && f !== "DONE.txt"
1865
1928
  );
1866
1929
  if (files.length === 0) {
@@ -1919,6 +1982,21 @@ function notifyParentExe(sessionKey) {
1919
1982
  }
1920
1983
  return true;
1921
1984
  }
1985
+ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
1986
+ const transport = getTransport();
1987
+ try {
1988
+ const sessions = transport.listSessions();
1989
+ if (!sessions.includes(coordinatorSession)) return false;
1990
+ execSync4(
1991
+ `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
1992
+ { timeout: 3e3 }
1993
+ );
1994
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
1995
+ return true;
1996
+ } catch {
1997
+ return false;
1998
+ }
1999
+ }
1922
2000
  function ensureEmployee(employeeName, exeSession, projectDir, opts) {
1923
2001
  if (isCoordinatorName(employeeName)) {
1924
2002
  return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
@@ -1992,7 +2070,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
1992
2070
  const transport = getTransport();
1993
2071
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
1994
2072
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
1995
- const logDir = path9.join(os6.homedir(), ".exe-os", "session-logs");
2073
+ const logDir = path9.join(os7.homedir(), ".exe-os", "session-logs");
1996
2074
  const logFile = path9.join(logDir, `${instanceLabel}-${Date.now()}.log`);
1997
2075
  if (!existsSync9(logDir)) {
1998
2076
  mkdirSync5(logDir, { recursive: true });
@@ -2008,10 +2086,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
2008
2086
  } catch {
2009
2087
  }
2010
2088
  try {
2011
- const claudeJsonPath = path9.join(os6.homedir(), ".claude.json");
2089
+ const claudeJsonPath = path9.join(os7.homedir(), ".claude.json");
2012
2090
  let claudeJson = {};
2013
2091
  try {
2014
- claudeJson = JSON.parse(readFileSync9(claudeJsonPath, "utf8"));
2092
+ claudeJson = JSON.parse(readFileSync8(claudeJsonPath, "utf8"));
2015
2093
  } catch {
2016
2094
  }
2017
2095
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -2023,13 +2101,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
2023
2101
  } catch {
2024
2102
  }
2025
2103
  try {
2026
- const settingsDir = path9.join(os6.homedir(), ".claude", "projects");
2104
+ const settingsDir = path9.join(os7.homedir(), ".claude", "projects");
2027
2105
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
2028
2106
  const projSettingsDir = path9.join(settingsDir, normalizedKey);
2029
2107
  const settingsPath = path9.join(projSettingsDir, "settings.json");
2030
2108
  let settings = {};
2031
2109
  try {
2032
- settings = JSON.parse(readFileSync9(settingsPath, "utf8"));
2110
+ settings = JSON.parse(readFileSync8(settingsPath, "utf8"));
2033
2111
  } catch {
2034
2112
  }
2035
2113
  const perms = settings.permissions ?? {};
@@ -2074,7 +2152,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
2074
2152
  let legacyFallbackWarned = false;
2075
2153
  if (!useExeAgent && !useBinSymlink) {
2076
2154
  const identityPath2 = path9.join(
2077
- os6.homedir(),
2155
+ os7.homedir(),
2078
2156
  ".exe-os",
2079
2157
  "identity",
2080
2158
  `${employeeName}.md`
@@ -2104,7 +2182,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
2104
2182
  }
2105
2183
  let sessionContextFlag = "";
2106
2184
  try {
2107
- const ctxDir = path9.join(os6.homedir(), ".exe-os", "session-cache");
2185
+ const ctxDir = path9.join(os7.homedir(), ".exe-os", "session-cache");
2108
2186
  mkdirSync5(ctxDir, { recursive: true });
2109
2187
  const ctxFile = path9.join(ctxDir, `session-context-${sessionName}.md`);
2110
2188
  const ctxContent = [
@@ -2265,14 +2343,14 @@ var init_tmux_routing = __esm({
2265
2343
  init_intercom_queue();
2266
2344
  init_plan_limits();
2267
2345
  init_employees();
2268
- SPAWN_LOCK_DIR = path9.join(os6.homedir(), ".exe-os", "spawn-locks");
2269
- SESSION_CACHE = path9.join(os6.homedir(), ".exe-os", "session-cache");
2346
+ SPAWN_LOCK_DIR = path9.join(os7.homedir(), ".exe-os", "spawn-locks");
2347
+ SESSION_CACHE = path9.join(os7.homedir(), ".exe-os", "session-cache");
2270
2348
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
2271
2349
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
2272
2350
  VERIFY_PANE_LINES = 200;
2273
2351
  INTERCOM_DEBOUNCE_MS = 3e4;
2274
2352
  CODEX_DEBOUNCE_MS = 12e4;
2275
- INTERCOM_LOG2 = path9.join(os6.homedir(), ".exe-os", "intercom.log");
2353
+ INTERCOM_LOG2 = path9.join(os7.homedir(), ".exe-os", "intercom.log");
2276
2354
  DEBOUNCE_FILE = path9.join(SESSION_CACHE, "intercom-debounce.json");
2277
2355
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
2278
2356
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
@@ -2296,6 +2374,15 @@ function sessionScopeFilter(sessionScope, tableAlias) {
2296
2374
  args: [scope]
2297
2375
  };
2298
2376
  }
2377
+ function strictSessionScopeFilter(sessionScope, tableAlias) {
2378
+ const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
2379
+ if (!scope) return { sql: "", args: [] };
2380
+ const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
2381
+ return {
2382
+ sql: ` AND ${col} = ?`,
2383
+ args: [scope]
2384
+ };
2385
+ }
2299
2386
  var init_task_scope = __esm({
2300
2387
  "src/lib/task-scope.ts"() {
2301
2388
  "use strict";
@@ -2303,13 +2390,125 @@ var init_task_scope = __esm({
2303
2390
  }
2304
2391
  });
2305
2392
 
2393
+ // src/lib/notifications.ts
2394
+ import crypto2 from "crypto";
2395
+ import path10 from "path";
2396
+ import os8 from "os";
2397
+ import {
2398
+ readFileSync as readFileSync9,
2399
+ readdirSync as readdirSync2,
2400
+ unlinkSync as unlinkSync3,
2401
+ existsSync as existsSync10,
2402
+ rmdirSync
2403
+ } from "fs";
2404
+ async function writeNotification(notification) {
2405
+ try {
2406
+ const client = getClient();
2407
+ const id = crypto2.randomUUID();
2408
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2409
+ const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
2410
+ await client.execute({
2411
+ sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
2412
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
2413
+ args: [
2414
+ id,
2415
+ notification.agentId,
2416
+ notification.agentRole,
2417
+ notification.event,
2418
+ notification.project,
2419
+ notification.summary,
2420
+ notification.taskFile ?? null,
2421
+ sessionScope,
2422
+ now
2423
+ ]
2424
+ });
2425
+ } catch (err) {
2426
+ process.stderr.write(`[notifications] WRITE FAILED: ${err instanceof Error ? err.message : String(err)}
2427
+ `);
2428
+ }
2429
+ }
2430
+ async function markAsReadByTaskFile(taskFile, sessionScope) {
2431
+ try {
2432
+ const client = getClient();
2433
+ const scope = strictSessionScopeFilter(sessionScope);
2434
+ await client.execute({
2435
+ sql: `UPDATE notifications SET read = 1
2436
+ WHERE task_file = ? AND read = 0${scope.sql}`,
2437
+ args: [taskFile, ...scope.args]
2438
+ });
2439
+ } catch {
2440
+ }
2441
+ }
2442
+ var init_notifications = __esm({
2443
+ "src/lib/notifications.ts"() {
2444
+ "use strict";
2445
+ init_database();
2446
+ init_task_scope();
2447
+ }
2448
+ });
2449
+
2450
+ // src/lib/state-bus.ts
2451
+ var StateBus, orgBus;
2452
+ var init_state_bus = __esm({
2453
+ "src/lib/state-bus.ts"() {
2454
+ "use strict";
2455
+ StateBus = class {
2456
+ handlers = /* @__PURE__ */ new Map();
2457
+ globalHandlers = /* @__PURE__ */ new Set();
2458
+ /** Emit an event to all subscribers */
2459
+ emit(event) {
2460
+ const typeHandlers = this.handlers.get(event.type);
2461
+ if (typeHandlers) {
2462
+ for (const handler of typeHandlers) {
2463
+ try {
2464
+ handler(event);
2465
+ } catch {
2466
+ }
2467
+ }
2468
+ }
2469
+ for (const handler of this.globalHandlers) {
2470
+ try {
2471
+ handler(event);
2472
+ } catch {
2473
+ }
2474
+ }
2475
+ }
2476
+ /** Subscribe to a specific event type */
2477
+ on(type, handler) {
2478
+ if (!this.handlers.has(type)) {
2479
+ this.handlers.set(type, /* @__PURE__ */ new Set());
2480
+ }
2481
+ this.handlers.get(type).add(handler);
2482
+ }
2483
+ /** Subscribe to ALL events */
2484
+ onAny(handler) {
2485
+ this.globalHandlers.add(handler);
2486
+ }
2487
+ /** Unsubscribe from a specific event type */
2488
+ off(type, handler) {
2489
+ this.handlers.get(type)?.delete(handler);
2490
+ }
2491
+ /** Unsubscribe from ALL events */
2492
+ offAny(handler) {
2493
+ this.globalHandlers.delete(handler);
2494
+ }
2495
+ /** Remove all listeners */
2496
+ clear() {
2497
+ this.handlers.clear();
2498
+ this.globalHandlers.clear();
2499
+ }
2500
+ };
2501
+ orgBus = new StateBus();
2502
+ }
2503
+ });
2504
+
2306
2505
  // src/lib/tasks-crud.ts
2307
2506
  import crypto3 from "crypto";
2308
- import path10 from "path";
2309
- import os7 from "os";
2507
+ import path11 from "path";
2508
+ import os9 from "os";
2310
2509
  import { execSync as execSync5 } from "child_process";
2311
2510
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
2312
- import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
2511
+ import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
2313
2512
  async function writeCheckpoint(input) {
2314
2513
  const client = getClient();
2315
2514
  const row = await resolveTask(client, input.taskId);
@@ -2484,8 +2683,8 @@ ${laneWarning}` : laneWarning;
2484
2683
  }
2485
2684
  if (input.baseDir) {
2486
2685
  try {
2487
- await mkdir3(path10.join(input.baseDir, "exe", "output"), { recursive: true });
2488
- await mkdir3(path10.join(input.baseDir, "exe", "research"), { recursive: true });
2686
+ await mkdir3(path11.join(input.baseDir, "exe", "output"), { recursive: true });
2687
+ await mkdir3(path11.join(input.baseDir, "exe", "research"), { recursive: true });
2489
2688
  await ensureArchitectureDoc(input.baseDir, input.projectName);
2490
2689
  await ensureGitignoreExe(input.baseDir);
2491
2690
  } catch {
@@ -2521,13 +2720,19 @@ ${laneWarning}` : laneWarning;
2521
2720
  });
2522
2721
  if (input.baseDir) {
2523
2722
  try {
2524
- const EXE_OS_DIR = path10.join(os7.homedir(), ".exe-os");
2525
- const mdPath = path10.join(EXE_OS_DIR, taskFile);
2526
- const mdDir = path10.dirname(mdPath);
2527
- if (!existsSync10(mdDir)) await mkdir3(mdDir, { recursive: true });
2723
+ const EXE_OS_DIR = path11.join(os9.homedir(), ".exe-os");
2724
+ const mdPath = path11.join(EXE_OS_DIR, taskFile);
2725
+ const mdDir = path11.dirname(mdPath);
2726
+ if (!existsSync11(mdDir)) await mkdir3(mdDir, { recursive: true });
2528
2727
  const reviewer = input.reviewer ?? input.assignedBy;
2529
2728
  const mdContent = `# ${input.title}
2530
2729
 
2730
+ ## MANDATORY: When done
2731
+
2732
+ You MUST call update_task with status "done" and a result summary when finished.
2733
+ If you skip this, your reviewer will not know you're done and your work won't be reviewed.
2734
+ Do NOT let a failed commit or any error prevent you from calling update_task(done).
2735
+
2531
2736
  **ID:** ${id}
2532
2737
  **Status:** ${initialStatus}
2533
2738
  **Priority:** ${input.priority}
@@ -2541,12 +2746,6 @@ ${laneWarning}` : laneWarning;
2541
2746
  ## Context
2542
2747
 
2543
2748
  ${input.context}
2544
-
2545
- ## MANDATORY: When done
2546
-
2547
- You MUST call update_task with status "done" and a result summary when finished.
2548
- If you skip this, your reviewer will not know you're done and your work won't be reviewed.
2549
- Do NOT let a failed commit or any error prevent you from calling update_task(done).
2550
2749
  `;
2551
2750
  await writeFile3(mdPath, mdContent, "utf-8");
2552
2751
  } catch (err) {
@@ -2795,7 +2994,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
2795
2994
  await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
2796
2995
  } catch {
2797
2996
  }
2798
- if (input.status === "done" || input.status === "cancelled") {
2997
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
2799
2998
  try {
2800
2999
  const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
2801
3000
  clearQueueForAgent2(String(row.assigned_to));
@@ -2824,9 +3023,9 @@ async function deleteTaskCore(taskId, _baseDir) {
2824
3023
  return { taskFile, assignedTo, assignedBy, taskSlug };
2825
3024
  }
2826
3025
  async function ensureArchitectureDoc(baseDir, projectName) {
2827
- const archPath = path10.join(baseDir, "exe", "ARCHITECTURE.md");
3026
+ const archPath = path11.join(baseDir, "exe", "ARCHITECTURE.md");
2828
3027
  try {
2829
- if (existsSync10(archPath)) return;
3028
+ if (existsSync11(archPath)) return;
2830
3029
  const template = [
2831
3030
  `# ${projectName} \u2014 System Architecture`,
2832
3031
  "",
@@ -2859,9 +3058,9 @@ async function ensureArchitectureDoc(baseDir, projectName) {
2859
3058
  }
2860
3059
  }
2861
3060
  async function ensureGitignoreExe(baseDir) {
2862
- const gitignorePath = path10.join(baseDir, ".gitignore");
3061
+ const gitignorePath = path11.join(baseDir, ".gitignore");
2863
3062
  try {
2864
- if (existsSync10(gitignorePath)) {
3063
+ if (existsSync11(gitignorePath)) {
2865
3064
  const content = readFileSync10(gitignorePath, "utf-8");
2866
3065
  if (/^\/?exe\/?$/m.test(content)) return;
2867
3066
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
@@ -2893,58 +3092,42 @@ var init_tasks_crud = __esm({
2893
3092
  });
2894
3093
 
2895
3094
  // src/lib/tasks-review.ts
2896
- import path11 from "path";
2897
- import { existsSync as existsSync11, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
3095
+ import path12 from "path";
3096
+ import { existsSync as existsSync12, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
2898
3097
  async function countPendingReviews(sessionScope) {
2899
3098
  const client = getClient();
2900
- if (sessionScope) {
2901
- const result2 = await client.execute({
2902
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review' AND (session_scope = ? OR session_scope IS NULL)",
2903
- args: [sessionScope]
2904
- });
2905
- return Number(result2.rows[0]?.cnt) || 0;
2906
- }
3099
+ const scope = strictSessionScopeFilter(
3100
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
3101
+ );
2907
3102
  const result = await client.execute({
2908
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review'",
2909
- args: []
3103
+ sql: `SELECT COUNT(*) as cnt FROM tasks
3104
+ WHERE status = 'needs_review'${scope.sql}`,
3105
+ args: [...scope.args]
2910
3106
  });
2911
3107
  return Number(result.rows[0]?.cnt) || 0;
2912
3108
  }
2913
3109
  async function countNewPendingReviewsSince(sinceIso, sessionScope) {
2914
3110
  const client = getClient();
2915
- if (sessionScope) {
2916
- const result2 = await client.execute({
2917
- sql: `SELECT COUNT(*) as cnt FROM tasks
2918
- WHERE status = 'needs_review' AND updated_at > ?
2919
- AND session_scope = ?`,
2920
- args: [sinceIso, sessionScope]
2921
- });
2922
- return Number(result2.rows[0]?.cnt) || 0;
2923
- }
3111
+ const scope = strictSessionScopeFilter(
3112
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
3113
+ );
2924
3114
  const result = await client.execute({
2925
3115
  sql: `SELECT COUNT(*) as cnt FROM tasks
2926
- WHERE status = 'needs_review' AND updated_at > ?`,
2927
- args: [sinceIso]
3116
+ WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
3117
+ args: [sinceIso, ...scope.args]
2928
3118
  });
2929
3119
  return Number(result.rows[0]?.cnt) || 0;
2930
3120
  }
2931
3121
  async function listPendingReviews(limit, sessionScope) {
2932
3122
  const client = getClient();
2933
- if (sessionScope) {
2934
- const result2 = await client.execute({
2935
- sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
2936
- WHERE status = 'needs_review'
2937
- AND session_scope = ?
2938
- ORDER BY updated_at ASC LIMIT ?`,
2939
- args: [sessionScope, limit]
2940
- });
2941
- return result2.rows;
2942
- }
3123
+ const scope = strictSessionScopeFilter(
3124
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
3125
+ );
2943
3126
  const result = await client.execute({
2944
3127
  sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
2945
- WHERE status = 'needs_review'
3128
+ WHERE status = 'needs_review'${scope.sql}
2946
3129
  ORDER BY updated_at ASC LIMIT ?`,
2947
- args: [limit]
3130
+ args: [...scope.args, limit]
2948
3131
  });
2949
3132
  return result.rows;
2950
3133
  }
@@ -2956,7 +3139,7 @@ async function cleanupOrphanedReviews() {
2956
3139
  WHERE status IN ('open', 'needs_review', 'in_progress')
2957
3140
  AND assigned_by = 'system'
2958
3141
  AND title LIKE 'Review:%'
2959
- AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
3142
+ AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
2960
3143
  args: [now]
2961
3144
  });
2962
3145
  const r1b = await client.execute({
@@ -3075,11 +3258,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
3075
3258
  );
3076
3259
  }
3077
3260
  try {
3078
- const cacheDir = path11.join(EXE_AI_DIR, "session-cache");
3079
- if (existsSync11(cacheDir)) {
3261
+ const cacheDir = path12.join(EXE_AI_DIR, "session-cache");
3262
+ if (existsSync12(cacheDir)) {
3080
3263
  for (const f of readdirSync3(cacheDir)) {
3081
3264
  if (f.startsWith("review-notified-")) {
3082
- unlinkSync4(path11.join(cacheDir, f));
3265
+ unlinkSync4(path12.join(cacheDir, f));
3083
3266
  }
3084
3267
  }
3085
3268
  }
@@ -3096,11 +3279,12 @@ var init_tasks_review = __esm({
3096
3279
  init_tmux_routing();
3097
3280
  init_session_key();
3098
3281
  init_state_bus();
3282
+ init_task_scope();
3099
3283
  }
3100
3284
  });
3101
3285
 
3102
3286
  // src/lib/tasks-chain.ts
3103
- import path12 from "path";
3287
+ import path13 from "path";
3104
3288
  import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
3105
3289
  async function cascadeUnblock(taskId, baseDir, now) {
3106
3290
  const client = getClient();
@@ -3117,7 +3301,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
3117
3301
  });
3118
3302
  for (const ur of unblockedRows.rows) {
3119
3303
  try {
3120
- const ubFile = path12.join(baseDir, String(ur.task_file));
3304
+ const ubFile = path13.join(baseDir, String(ur.task_file));
3121
3305
  let ubContent = await readFile3(ubFile, "utf-8");
3122
3306
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
3123
3307
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -3152,7 +3336,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
3152
3336
  const scScope = sessionScopeFilter();
3153
3337
  const remaining = await client.execute({
3154
3338
  sql: `SELECT COUNT(*) as cnt FROM tasks
3155
- WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
3339
+ WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
3156
3340
  args: [parentTaskId, ...scScope.args]
3157
3341
  });
3158
3342
  const cnt = Number(remaining.rows[0]?.cnt ?? 1);
@@ -3186,7 +3370,7 @@ var init_tasks_chain = __esm({
3186
3370
 
3187
3371
  // src/lib/project-name.ts
3188
3372
  import { execSync as execSync6 } from "child_process";
3189
- import path13 from "path";
3373
+ import path14 from "path";
3190
3374
  function getProjectName(cwd) {
3191
3375
  const dir = cwd ?? process.cwd();
3192
3376
  if (_cached2 && _cachedCwd === dir) return _cached2;
@@ -3199,7 +3383,7 @@ function getProjectName(cwd) {
3199
3383
  timeout: 2e3,
3200
3384
  stdio: ["pipe", "pipe", "pipe"]
3201
3385
  }).trim();
3202
- repoRoot = path13.dirname(gitCommonDir);
3386
+ repoRoot = path14.dirname(gitCommonDir);
3203
3387
  } catch {
3204
3388
  repoRoot = execSync6("git rev-parse --show-toplevel", {
3205
3389
  cwd: dir,
@@ -3208,11 +3392,11 @@ function getProjectName(cwd) {
3208
3392
  stdio: ["pipe", "pipe", "pipe"]
3209
3393
  }).trim();
3210
3394
  }
3211
- _cached2 = path13.basename(repoRoot);
3395
+ _cached2 = path14.basename(repoRoot);
3212
3396
  _cachedCwd = dir;
3213
3397
  return _cached2;
3214
3398
  } catch {
3215
- _cached2 = path13.basename(dir);
3399
+ _cached2 = path14.basename(dir);
3216
3400
  _cachedCwd = dir;
3217
3401
  return _cached2;
3218
3402
  }
@@ -3685,7 +3869,7 @@ __export(tasks_exports, {
3685
3869
  updateTaskStatus: () => updateTaskStatus,
3686
3870
  writeCheckpoint: () => writeCheckpoint
3687
3871
  });
3688
- import path14 from "path";
3872
+ import path15 from "path";
3689
3873
  import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, unlinkSync as unlinkSync5 } from "fs";
3690
3874
  async function createTask(input) {
3691
3875
  const result = await createTaskCore(input);
@@ -3705,12 +3889,12 @@ async function updateTask(input) {
3705
3889
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
3706
3890
  try {
3707
3891
  const agent = String(row.assigned_to);
3708
- const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
3709
- const cachePath = path14.join(cacheDir, `current-task-${agent}.json`);
3892
+ const cacheDir = path15.join(EXE_AI_DIR, "session-cache");
3893
+ const cachePath = path15.join(cacheDir, `current-task-${agent}.json`);
3710
3894
  if (input.status === "in_progress") {
3711
3895
  mkdirSync6(cacheDir, { recursive: true });
3712
3896
  writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
3713
- } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
3897
+ } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
3714
3898
  try {
3715
3899
  unlinkSync5(cachePath);
3716
3900
  } catch {
@@ -3718,10 +3902,10 @@ async function updateTask(input) {
3718
3902
  }
3719
3903
  } catch {
3720
3904
  }
3721
- if (input.status === "done") {
3905
+ if (input.status === "done" || input.status === "closed") {
3722
3906
  await cleanupReviewFile(row, taskFile, input.baseDir);
3723
3907
  }
3724
- if (input.status === "done" || input.status === "cancelled") {
3908
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
3725
3909
  try {
3726
3910
  const client = getClient();
3727
3911
  const taskTitle = String(row.title);
@@ -3737,7 +3921,7 @@ async function updateTask(input) {
3737
3921
  if (!isCoordinatorName(assignedAgent)) {
3738
3922
  try {
3739
3923
  const draftClient = getClient();
3740
- if (input.status === "done") {
3924
+ if (input.status === "done" || input.status === "closed") {
3741
3925
  await draftClient.execute({
3742
3926
  sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
3743
3927
  args: [assignedAgent]
@@ -3754,7 +3938,7 @@ async function updateTask(input) {
3754
3938
  try {
3755
3939
  const client = getClient();
3756
3940
  const cascaded = await client.execute({
3757
- sql: `UPDATE tasks SET status = 'done', updated_at = ?
3941
+ sql: `UPDATE tasks SET status = 'closed', updated_at = ?
3758
3942
  WHERE parent_task_id = ? AND status = 'needs_review'`,
3759
3943
  args: [now, taskId]
3760
3944
  });
@@ -3767,14 +3951,14 @@ async function updateTask(input) {
3767
3951
  } catch {
3768
3952
  }
3769
3953
  }
3770
- const isTerminal = input.status === "done" || input.status === "needs_review";
3954
+ const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
3771
3955
  if (isTerminal) {
3772
3956
  const isCoordinator = isCoordinatorName(String(row.assigned_to));
3773
3957
  if (!isCoordinator) {
3774
3958
  notifyTaskDone();
3775
3959
  }
3776
3960
  await markTaskNotificationsRead(taskFile);
3777
- if (input.status === "done") {
3961
+ if (input.status === "done" || input.status === "closed") {
3778
3962
  try {
3779
3963
  await cascadeUnblock(taskId, input.baseDir, now);
3780
3964
  } catch {
@@ -3794,7 +3978,7 @@ async function updateTask(input) {
3794
3978
  }
3795
3979
  }
3796
3980
  }
3797
- if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
3981
+ if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
3798
3982
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
3799
3983
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
3800
3984
  taskId,
@@ -3875,17 +4059,17 @@ __export(identity_exports, {
3875
4059
  listIdentities: () => listIdentities,
3876
4060
  updateIdentity: () => updateIdentity
3877
4061
  });
3878
- import { existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync12, writeFileSync as writeFileSync9 } from "fs";
4062
+ import { existsSync as existsSync13, mkdirSync as mkdirSync8, readFileSync as readFileSync12, writeFileSync as writeFileSync9 } from "fs";
3879
4063
  import { readdirSync as readdirSync5 } from "fs";
3880
- import path16 from "path";
4064
+ import path17 from "path";
3881
4065
  import { createHash } from "crypto";
3882
4066
  function ensureDir2() {
3883
- if (!existsSync12(IDENTITY_DIR)) {
3884
- mkdirSync8(IDENTITY_DIR, { recursive: true });
4067
+ if (!existsSync13(IDENTITY_DIR2)) {
4068
+ mkdirSync8(IDENTITY_DIR2, { recursive: true });
3885
4069
  }
3886
4070
  }
3887
4071
  function identityPath(agentId) {
3888
- return path16.join(IDENTITY_DIR, `${agentId}.md`);
4072
+ return path17.join(IDENTITY_DIR2, `${agentId}.md`);
3889
4073
  }
3890
4074
  function parseFrontmatter(raw) {
3891
4075
  const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
@@ -3926,7 +4110,7 @@ function contentHash(content) {
3926
4110
  }
3927
4111
  function getIdentity(agentId) {
3928
4112
  const filePath = identityPath(agentId);
3929
- if (!existsSync12(filePath)) return null;
4113
+ if (!existsSync13(filePath)) return null;
3930
4114
  const raw = readFileSync12(filePath, "utf-8");
3931
4115
  const { frontmatter, body } = parseFrontmatter(raw);
3932
4116
  return {
@@ -3958,7 +4142,7 @@ async function updateIdentity(agentId, content, updatedBy) {
3958
4142
  }
3959
4143
  function listIdentities() {
3960
4144
  ensureDir2();
3961
- const files = readdirSync5(IDENTITY_DIR).filter((f) => f.endsWith(".md"));
4145
+ const files = readdirSync5(IDENTITY_DIR2).filter((f) => f.endsWith(".md"));
3962
4146
  const results = [];
3963
4147
  for (const file of files) {
3964
4148
  const agentId = file.replace(".md", "");
@@ -3991,13 +4175,13 @@ ${teamLines.join("\n")}`);
3991
4175
  }
3992
4176
  return parts.join("\n\n");
3993
4177
  }
3994
- var IDENTITY_DIR;
4178
+ var IDENTITY_DIR2;
3995
4179
  var init_identity = __esm({
3996
4180
  "src/lib/identity.ts"() {
3997
4181
  "use strict";
3998
4182
  init_config();
3999
4183
  init_database();
4000
- IDENTITY_DIR = path16.join(EXE_AI_DIR, "identity");
4184
+ IDENTITY_DIR2 = path17.join(EXE_AI_DIR, "identity");
4001
4185
  }
4002
4186
  });
4003
4187
 
@@ -4552,8 +4736,8 @@ init_session_key();
4552
4736
  init_employees();
4553
4737
  import { readFileSync as readFileSync11, writeFileSync as writeFileSync8, mkdirSync as mkdirSync7, unlinkSync as unlinkSync6, readdirSync as readdirSync4 } from "fs";
4554
4738
  import { execSync as execSync7 } from "child_process";
4555
- import path15 from "path";
4556
- var CACHE_DIR = path15.join(EXE_AI_DIR, "session-cache");
4739
+ import path16 from "path";
4740
+ var CACHE_DIR = path16.join(EXE_AI_DIR, "session-cache");
4557
4741
  var STALE_MS = 24 * 60 * 60 * 1e3;
4558
4742
  function isNameWithOptionalInstance(candidate, baseName) {
4559
4743
  if (candidate === baseName) return true;
@@ -4598,7 +4782,7 @@ function resolveActiveAgentFromTmuxSession(sessionName) {
4598
4782
  return null;
4599
4783
  }
4600
4784
  function getMarkerPath() {
4601
- return path15.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
4785
+ return path16.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
4602
4786
  }
4603
4787
  function getActiveAgent() {
4604
4788
  try {
@@ -4702,10 +4886,10 @@ function registerCreateTask(server) {
4702
4886
  skipDispatch: true
4703
4887
  });
4704
4888
  try {
4705
- const { existsSync: existsSync13, mkdirSync: mkdirSync9, writeFileSync: writeFileSync10 } = await import("fs");
4889
+ const { existsSync: existsSync14, mkdirSync: mkdirSync9, writeFileSync: writeFileSync10 } = await import("fs");
4706
4890
  const { identityPath: identityPath2 } = await Promise.resolve().then(() => (init_identity(), identity_exports));
4707
4891
  const idPath = identityPath2(assigned_to);
4708
- if (!existsSync13(idPath)) {
4892
+ if (!existsSync14(idPath)) {
4709
4893
  const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
4710
4894
  const employees = await loadEmployees2();
4711
4895
  const emp = employees.find((e) => e.name === assigned_to);
@@ -4714,7 +4898,7 @@ function registerCreateTask(server) {
4714
4898
  const template = getTemplateForTitle2(emp.role);
4715
4899
  if (template) {
4716
4900
  const dir = (await import("path")).dirname(idPath);
4717
- if (!existsSync13(dir)) mkdirSync9(dir, { recursive: true });
4901
+ if (!existsSync14(dir)) mkdirSync9(dir, { recursive: true });
4718
4902
  writeFileSync10(idPath, template.replace(/^agent_id: \w+/m, `agent_id: ${assigned_to}`), "utf-8");
4719
4903
  }
4720
4904
  }