@askexenow/exe-os 0.8.80 → 0.8.82

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 (110) hide show
  1. package/dist/bin/backfill-conversations.js +359 -267
  2. package/dist/bin/backfill-responses.js +357 -265
  3. package/dist/bin/backfill-vectors.js +339 -264
  4. package/dist/bin/cleanup-stale-review-tasks.js +315 -256
  5. package/dist/bin/cli.js +494 -240
  6. package/dist/bin/exe-agent.js +141 -46
  7. package/dist/bin/exe-assign.js +151 -63
  8. package/dist/bin/exe-boot.js +294 -115
  9. package/dist/bin/exe-call.js +76 -51
  10. package/dist/bin/exe-cloud.js +58 -45
  11. package/dist/bin/exe-dispatch.js +434 -277
  12. package/dist/bin/exe-doctor.js +317 -246
  13. package/dist/bin/exe-export-behaviors.js +328 -248
  14. package/dist/bin/exe-forget.js +314 -231
  15. package/dist/bin/exe-gateway.js +2676 -1402
  16. package/dist/bin/exe-heartbeat.js +329 -264
  17. package/dist/bin/exe-kill.js +324 -244
  18. package/dist/bin/exe-launch-agent.js +574 -463
  19. package/dist/bin/exe-link.js +1055 -95
  20. package/dist/bin/exe-new-employee.js +49 -54
  21. package/dist/bin/exe-pending-messages.js +310 -253
  22. package/dist/bin/exe-pending-notifications.js +299 -228
  23. package/dist/bin/exe-pending-reviews.js +314 -245
  24. package/dist/bin/exe-rename.js +259 -195
  25. package/dist/bin/exe-review.js +140 -64
  26. package/dist/bin/exe-search.js +543 -356
  27. package/dist/bin/exe-session-cleanup.js +463 -382
  28. package/dist/bin/exe-settings.js +129 -99
  29. package/dist/bin/exe-start.sh +6 -6
  30. package/dist/bin/exe-status.js +95 -36
  31. package/dist/bin/exe-team.js +116 -51
  32. package/dist/bin/git-sweep.js +482 -307
  33. package/dist/bin/graph-backfill.js +357 -245
  34. package/dist/bin/graph-export.js +324 -244
  35. package/dist/bin/install.js +33 -10
  36. package/dist/bin/scan-tasks.js +481 -307
  37. package/dist/bin/setup.js +1147 -140
  38. package/dist/bin/shard-migrate.js +321 -241
  39. package/dist/bin/update.js +1 -7
  40. package/dist/bin/wiki-sync.js +318 -238
  41. package/dist/gateway/index.js +2656 -1383
  42. package/dist/hooks/bug-report-worker.js +641 -472
  43. package/dist/hooks/commit-complete.js +482 -307
  44. package/dist/hooks/error-recall.js +363 -135
  45. package/dist/hooks/exe-heartbeat-hook.js +97 -27
  46. package/dist/hooks/ingest-worker.js +584 -397
  47. package/dist/hooks/ingest.js +123 -58
  48. package/dist/hooks/instructions-loaded.js +212 -82
  49. package/dist/hooks/notification.js +200 -70
  50. package/dist/hooks/post-compact.js +199 -81
  51. package/dist/hooks/pre-compact.js +352 -140
  52. package/dist/hooks/pre-tool-use.js +416 -278
  53. package/dist/hooks/prompt-ingest-worker.js +376 -299
  54. package/dist/hooks/prompt-submit.js +414 -188
  55. package/dist/hooks/response-ingest-worker.js +408 -338
  56. package/dist/hooks/session-end.js +209 -83
  57. package/dist/hooks/session-start.js +382 -158
  58. package/dist/hooks/stop.js +209 -83
  59. package/dist/hooks/subagent-stop.js +209 -85
  60. package/dist/hooks/summary-worker.js +606 -510
  61. package/dist/index.js +2133 -855
  62. package/dist/lib/cloud-sync.js +1175 -184
  63. package/dist/lib/config.js +1 -9
  64. package/dist/lib/consolidation.js +71 -34
  65. package/dist/lib/database.js +166 -14
  66. package/dist/lib/device-registry.js +189 -117
  67. package/dist/lib/embedder.js +6 -10
  68. package/dist/lib/employee-templates.js +134 -39
  69. package/dist/lib/employees.js +30 -7
  70. package/dist/lib/exe-daemon-client.js +5 -7
  71. package/dist/lib/exe-daemon.js +514 -152
  72. package/dist/lib/hybrid-search.js +543 -356
  73. package/dist/lib/identity-templates.js +15 -15
  74. package/dist/lib/identity.js +19 -15
  75. package/dist/lib/license.js +1 -7
  76. package/dist/lib/messaging.js +157 -135
  77. package/dist/lib/reminders.js +97 -0
  78. package/dist/lib/schedules.js +302 -231
  79. package/dist/lib/skill-learning.js +33 -27
  80. package/dist/lib/status-brief.js +11 -14
  81. package/dist/lib/store.js +326 -237
  82. package/dist/lib/task-router.js +105 -1
  83. package/dist/lib/tasks.js +233 -116
  84. package/dist/lib/tmux-routing.js +173 -56
  85. package/dist/lib/ws-client.js +13 -3
  86. package/dist/mcp/server.js +2009 -1015
  87. package/dist/mcp/tools/complete-reminder.js +97 -0
  88. package/dist/mcp/tools/create-reminder.js +97 -0
  89. package/dist/mcp/tools/create-task.js +426 -262
  90. package/dist/mcp/tools/deactivate-behavior.js +119 -44
  91. package/dist/mcp/tools/list-reminders.js +97 -0
  92. package/dist/mcp/tools/list-tasks.js +56 -57
  93. package/dist/mcp/tools/send-message.js +206 -143
  94. package/dist/mcp/tools/update-task.js +259 -85
  95. package/dist/runtime/index.js +495 -316
  96. package/dist/tui/App.js +1128 -919
  97. package/package.json +2 -10
  98. package/src/commands/exe/afk.md +8 -8
  99. package/src/commands/exe/assign.md +1 -1
  100. package/src/commands/exe/build-adv.md +1 -1
  101. package/src/commands/exe/call.md +10 -10
  102. package/src/commands/exe/employee-heartbeat.md +9 -6
  103. package/src/commands/exe/heartbeat.md +5 -5
  104. package/src/commands/exe/intercom.md +26 -15
  105. package/src/commands/exe/launch.md +2 -2
  106. package/src/commands/exe/new-employee.md +1 -1
  107. package/src/commands/exe/review.md +2 -2
  108. package/src/commands/exe/schedule.md +1 -1
  109. package/src/commands/exe/sessions.md +2 -2
  110. package/src/commands/exe.md +22 -20
@@ -26,29 +26,11 @@ var init_db_retry = __esm({
26
26
  }
27
27
  });
28
28
 
29
- // src/lib/database.ts
30
- import { createClient } from "@libsql/client";
31
- function getClient() {
32
- if (!_resilientClient) {
33
- throw new Error("Database client not initialized. Call initDatabase() first.");
34
- }
35
- return _resilientClient;
36
- }
37
- var _resilientClient;
38
- var init_database = __esm({
39
- "src/lib/database.ts"() {
40
- "use strict";
41
- init_db_retry();
42
- _resilientClient = null;
43
- }
44
- });
45
-
46
29
  // src/lib/config.ts
47
30
  var config_exports = {};
48
31
  __export(config_exports, {
49
32
  CONFIG_MIGRATIONS: () => CONFIG_MIGRATIONS,
50
33
  CONFIG_PATH: () => CONFIG_PATH,
51
- COO_AGENT_NAME: () => COO_AGENT_NAME,
52
34
  CURRENT_CONFIG_VERSION: () => CURRENT_CONFIG_VERSION,
53
35
  DB_PATH: () => DB_PATH,
54
36
  EXE_AI_DIR: () => EXE_AI_DIR,
@@ -204,7 +186,7 @@ async function loadConfigFrom(configPath) {
204
186
  return { ...DEFAULT_CONFIG };
205
187
  }
206
188
  }
207
- var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, COO_AGENT_NAME, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
189
+ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
208
190
  var init_config = __esm({
209
191
  "src/lib/config.ts"() {
210
192
  "use strict";
@@ -212,7 +194,6 @@ var init_config = __esm({
212
194
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
213
195
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
214
196
  CONFIG_PATH = path.join(EXE_AI_DIR, "config.json");
215
- COO_AGENT_NAME = "exe";
216
197
  LEGACY_LANCE_PATH = path.join(EXE_AI_DIR, "local.lance");
217
198
  CURRENT_CONFIG_VERSION = 1;
218
199
  DEFAULT_CONFIG = {
@@ -248,13 +229,7 @@ var init_config = __esm({
248
229
  wikiUrl: "",
249
230
  wikiApiKey: "",
250
231
  wikiSyncIntervalMs: 30 * 60 * 1e3,
251
- wikiWorkspaceMapping: {
252
- exe: "Executive",
253
- yoshi: "Engineering",
254
- mari: "Marketing",
255
- tom: "Engineering",
256
- sasha: "Production"
257
- },
232
+ wikiWorkspaceMapping: {},
258
233
  wikiAutoUpdate: true,
259
234
  wikiAutoUpdateThreshold: 0.5,
260
235
  wikiAutoUpdateCreateNew: true,
@@ -291,15 +266,231 @@ var init_config = __esm({
291
266
  }
292
267
  });
293
268
 
294
- // src/lib/notifications.ts
295
- import crypto from "crypto";
269
+ // src/lib/employees.ts
270
+ var employees_exports = {};
271
+ __export(employees_exports, {
272
+ COORDINATOR_ROLE: () => COORDINATOR_ROLE,
273
+ DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
274
+ EMPLOYEES_PATH: () => EMPLOYEES_PATH,
275
+ addEmployee: () => addEmployee,
276
+ canCoordinate: () => canCoordinate,
277
+ getCoordinatorEmployee: () => getCoordinatorEmployee,
278
+ getCoordinatorName: () => getCoordinatorName,
279
+ getEmployee: () => getEmployee,
280
+ getEmployeeByRole: () => getEmployeeByRole,
281
+ getEmployeeNamesByRole: () => getEmployeeNamesByRole,
282
+ hasRole: () => hasRole,
283
+ isCoordinatorName: () => isCoordinatorName,
284
+ isCoordinatorRole: () => isCoordinatorRole,
285
+ isMultiInstance: () => isMultiInstance,
286
+ loadEmployees: () => loadEmployees,
287
+ loadEmployeesSync: () => loadEmployeesSync,
288
+ normalizeRole: () => normalizeRole,
289
+ normalizeRosterCase: () => normalizeRosterCase,
290
+ registerBinSymlinks: () => registerBinSymlinks,
291
+ saveEmployees: () => saveEmployees,
292
+ validateEmployeeName: () => validateEmployeeName
293
+ });
294
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
295
+ import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
296
+ import { execSync } from "child_process";
296
297
  import path2 from "path";
297
298
  import os2 from "os";
299
+ function normalizeRole(role) {
300
+ return (role ?? "").trim().toLowerCase();
301
+ }
302
+ function isCoordinatorRole(role) {
303
+ return normalizeRole(role) === normalizeRole(COORDINATOR_ROLE);
304
+ }
305
+ function getCoordinatorEmployee(employees) {
306
+ return employees.find((e) => isCoordinatorRole(e.role));
307
+ }
308
+ function getCoordinatorName(employees = loadEmployeesSync()) {
309
+ return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
310
+ }
311
+ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
312
+ if (!agentName) return false;
313
+ return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
314
+ }
315
+ function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
316
+ return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
317
+ }
318
+ function validateEmployeeName(name) {
319
+ if (!name) {
320
+ return { valid: false, error: "Name is required" };
321
+ }
322
+ if (name.length > 32) {
323
+ return { valid: false, error: "Name must be 32 characters or fewer" };
324
+ }
325
+ if (!/^[a-z][a-z0-9]*$/.test(name)) {
326
+ return {
327
+ valid: false,
328
+ error: "Name must start with a letter and contain only lowercase alphanumeric characters"
329
+ };
330
+ }
331
+ return { valid: true };
332
+ }
333
+ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
334
+ if (!existsSync2(employeesPath)) {
335
+ return [];
336
+ }
337
+ const raw = await readFile2(employeesPath, "utf-8");
338
+ try {
339
+ return JSON.parse(raw);
340
+ } catch {
341
+ return [];
342
+ }
343
+ }
344
+ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
345
+ await mkdir2(path2.dirname(employeesPath), { recursive: true });
346
+ await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
347
+ }
348
+ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
349
+ if (!existsSync2(employeesPath)) return [];
350
+ try {
351
+ return JSON.parse(readFileSync2(employeesPath, "utf-8"));
352
+ } catch {
353
+ return [];
354
+ }
355
+ }
356
+ function getEmployee(employees, name) {
357
+ return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
358
+ }
359
+ function getEmployeeByRole(employees, role) {
360
+ const lower = role.toLowerCase();
361
+ return employees.find((e) => e.role.toLowerCase() === lower);
362
+ }
363
+ function getEmployeeNamesByRole(employees, role) {
364
+ const lower = role.toLowerCase();
365
+ return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
366
+ }
367
+ function hasRole(agentName, role) {
368
+ const employees = loadEmployeesSync();
369
+ const emp = getEmployee(employees, agentName);
370
+ return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
371
+ }
372
+ function isMultiInstance(agentName, employees) {
373
+ const roster = employees ?? loadEmployeesSync();
374
+ const emp = getEmployee(roster, agentName);
375
+ if (!emp) return false;
376
+ return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
377
+ }
378
+ function addEmployee(employees, employee) {
379
+ const normalized = { ...employee, name: employee.name.toLowerCase() };
380
+ if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
381
+ throw new Error(`Employee '${normalized.name}' already exists`);
382
+ }
383
+ return [...employees, normalized];
384
+ }
385
+ async function normalizeRosterCase(rosterPath) {
386
+ const employees = await loadEmployees(rosterPath);
387
+ let changed = false;
388
+ for (const emp of employees) {
389
+ if (emp.name !== emp.name.toLowerCase()) {
390
+ const oldName = emp.name;
391
+ emp.name = emp.name.toLowerCase();
392
+ changed = true;
393
+ try {
394
+ const identityDir = path2.join(os2.homedir(), ".exe-os", "identity");
395
+ const oldPath = path2.join(identityDir, `${oldName}.md`);
396
+ const newPath = path2.join(identityDir, `${emp.name}.md`);
397
+ if (existsSync2(oldPath) && !existsSync2(newPath)) {
398
+ renameSync2(oldPath, newPath);
399
+ } else if (existsSync2(oldPath) && oldPath !== newPath) {
400
+ const content = readFileSync2(oldPath, "utf-8");
401
+ writeFileSync(newPath, content, "utf-8");
402
+ if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
403
+ unlinkSync(oldPath);
404
+ }
405
+ }
406
+ } catch {
407
+ }
408
+ }
409
+ }
410
+ if (changed) {
411
+ await saveEmployees(employees, rosterPath);
412
+ }
413
+ return changed;
414
+ }
415
+ function findExeBin() {
416
+ try {
417
+ return execSync(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
418
+ } catch {
419
+ return null;
420
+ }
421
+ }
422
+ function registerBinSymlinks(name) {
423
+ const created = [];
424
+ const skipped = [];
425
+ const errors = [];
426
+ const exeBinPath = findExeBin();
427
+ if (!exeBinPath) {
428
+ errors.push("Could not find 'exe-os' in PATH");
429
+ return { created, skipped, errors };
430
+ }
431
+ const binDir = path2.dirname(exeBinPath);
432
+ let target;
433
+ try {
434
+ target = readlinkSync(exeBinPath);
435
+ } catch {
436
+ errors.push("Could not read 'exe' symlink");
437
+ return { created, skipped, errors };
438
+ }
439
+ for (const suffix of ["", "-opencode"]) {
440
+ const linkName = `${name}${suffix}`;
441
+ const linkPath = path2.join(binDir, linkName);
442
+ if (existsSync2(linkPath)) {
443
+ skipped.push(linkName);
444
+ continue;
445
+ }
446
+ try {
447
+ symlinkSync(target, linkPath);
448
+ created.push(linkName);
449
+ } catch (err) {
450
+ errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
451
+ }
452
+ }
453
+ return { created, skipped, errors };
454
+ }
455
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
456
+ var init_employees = __esm({
457
+ "src/lib/employees.ts"() {
458
+ "use strict";
459
+ init_config();
460
+ EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
461
+ DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
462
+ COORDINATOR_ROLE = "COO";
463
+ MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
464
+ }
465
+ });
466
+
467
+ // src/lib/database.ts
468
+ import { createClient } from "@libsql/client";
469
+ function getClient() {
470
+ if (!_resilientClient) {
471
+ throw new Error("Database client not initialized. Call initDatabase() first.");
472
+ }
473
+ return _resilientClient;
474
+ }
475
+ var _resilientClient;
476
+ var init_database = __esm({
477
+ "src/lib/database.ts"() {
478
+ "use strict";
479
+ init_db_retry();
480
+ init_employees();
481
+ _resilientClient = null;
482
+ }
483
+ });
484
+
485
+ // src/lib/notifications.ts
486
+ import crypto from "crypto";
487
+ import path3 from "path";
488
+ import os3 from "os";
298
489
  import {
299
- readFileSync as readFileSync2,
490
+ readFileSync as readFileSync3,
300
491
  readdirSync,
301
- unlinkSync,
302
- existsSync as existsSync2,
492
+ unlinkSync as unlinkSync2,
493
+ existsSync as existsSync3,
303
494
  rmdirSync
304
495
  } from "fs";
305
496
  async function writeNotification(notification) {
@@ -399,12 +590,12 @@ var init_state_bus = __esm({
399
590
  });
400
591
 
401
592
  // src/lib/session-registry.ts
402
- import { readFileSync as readFileSync3, writeFileSync, mkdirSync, existsSync as existsSync3 } from "fs";
403
- import path3 from "path";
404
- import os3 from "os";
593
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync, existsSync as existsSync4 } from "fs";
594
+ import path4 from "path";
595
+ import os4 from "os";
405
596
  function registerSession(entry) {
406
- const dir = path3.dirname(REGISTRY_PATH);
407
- if (!existsSync3(dir)) {
597
+ const dir = path4.dirname(REGISTRY_PATH);
598
+ if (!existsSync4(dir)) {
408
599
  mkdirSync(dir, { recursive: true });
409
600
  }
410
601
  const sessions = listSessions();
@@ -414,11 +605,11 @@ function registerSession(entry) {
414
605
  } else {
415
606
  sessions.push(entry);
416
607
  }
417
- writeFileSync(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
608
+ writeFileSync2(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
418
609
  }
419
610
  function listSessions() {
420
611
  try {
421
- const raw = readFileSync3(REGISTRY_PATH, "utf8");
612
+ const raw = readFileSync4(REGISTRY_PATH, "utf8");
422
613
  return JSON.parse(raw);
423
614
  } catch {
424
615
  return [];
@@ -428,18 +619,18 @@ var REGISTRY_PATH;
428
619
  var init_session_registry = __esm({
429
620
  "src/lib/session-registry.ts"() {
430
621
  "use strict";
431
- REGISTRY_PATH = path3.join(os3.homedir(), ".exe-os", "session-registry.json");
622
+ REGISTRY_PATH = path4.join(os4.homedir(), ".exe-os", "session-registry.json");
432
623
  }
433
624
  });
434
625
 
435
626
  // src/lib/session-key.ts
436
- import { execSync } from "child_process";
627
+ import { execSync as execSync2 } from "child_process";
437
628
  function getSessionKey() {
438
629
  if (_cached) return _cached;
439
630
  let pid = process.ppid;
440
631
  for (let i = 0; i < 10; i++) {
441
632
  try {
442
- const info = execSync(`ps -p ${pid} -o ppid=,comm=`, {
633
+ const info = execSync2(`ps -p ${pid} -o ppid=,comm=`, {
443
634
  encoding: "utf8",
444
635
  timeout: 2e3
445
636
  }).trim();
@@ -575,14 +766,14 @@ var init_transport = __esm({
575
766
  });
576
767
 
577
768
  // src/lib/cc-agent-support.ts
578
- import { execSync as execSync2 } from "child_process";
769
+ import { execSync as execSync3 } from "child_process";
579
770
  function _resetCcAgentSupportCache() {
580
771
  _cachedSupport = null;
581
772
  }
582
773
  function claudeSupportsAgentFlag() {
583
774
  if (_cachedSupport !== null) return _cachedSupport;
584
775
  try {
585
- const helpOutput = execSync2("claude --help 2>&1", {
776
+ const helpOutput = execSync3("claude --help 2>&1", {
586
777
  encoding: "utf-8",
587
778
  timeout: 5e3
588
779
  });
@@ -648,17 +839,17 @@ var init_provider_table = __esm({
648
839
  });
649
840
 
650
841
  // src/lib/intercom-queue.ts
651
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, renameSync as renameSync2, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
652
- import path4 from "path";
653
- import os4 from "os";
842
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, renameSync as renameSync3, existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
843
+ import path5 from "path";
844
+ import os5 from "os";
654
845
  function ensureDir() {
655
- const dir = path4.dirname(QUEUE_PATH);
656
- if (!existsSync4(dir)) mkdirSync2(dir, { recursive: true });
846
+ const dir = path5.dirname(QUEUE_PATH);
847
+ if (!existsSync5(dir)) mkdirSync2(dir, { recursive: true });
657
848
  }
658
849
  function readQueue() {
659
850
  try {
660
- if (!existsSync4(QUEUE_PATH)) return [];
661
- return JSON.parse(readFileSync4(QUEUE_PATH, "utf8"));
851
+ if (!existsSync5(QUEUE_PATH)) return [];
852
+ return JSON.parse(readFileSync5(QUEUE_PATH, "utf8"));
662
853
  } catch {
663
854
  return [];
664
855
  }
@@ -666,8 +857,8 @@ function readQueue() {
666
857
  function writeQueue(queue) {
667
858
  ensureDir();
668
859
  const tmp = `${QUEUE_PATH}.tmp`;
669
- writeFileSync2(tmp, JSON.stringify(queue, null, 2));
670
- renameSync2(tmp, QUEUE_PATH);
860
+ writeFileSync3(tmp, JSON.stringify(queue, null, 2));
861
+ renameSync3(tmp, QUEUE_PATH);
671
862
  }
672
863
  function queueIntercom(targetSession, reason) {
673
864
  const queue = readQueue();
@@ -690,178 +881,9 @@ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
690
881
  var init_intercom_queue = __esm({
691
882
  "src/lib/intercom-queue.ts"() {
692
883
  "use strict";
693
- QUEUE_PATH = path4.join(os4.homedir(), ".exe-os", "intercom-queue.json");
884
+ QUEUE_PATH = path5.join(os5.homedir(), ".exe-os", "intercom-queue.json");
694
885
  TTL_MS = 60 * 60 * 1e3;
695
- INTERCOM_LOG = path4.join(os4.homedir(), ".exe-os", "intercom.log");
696
- }
697
- });
698
-
699
- // src/lib/employees.ts
700
- var employees_exports = {};
701
- __export(employees_exports, {
702
- EMPLOYEES_PATH: () => EMPLOYEES_PATH,
703
- addEmployee: () => addEmployee,
704
- getEmployee: () => getEmployee,
705
- getEmployeeByRole: () => getEmployeeByRole,
706
- getEmployeeNamesByRole: () => getEmployeeNamesByRole,
707
- hasRole: () => hasRole,
708
- isMultiInstance: () => isMultiInstance,
709
- loadEmployees: () => loadEmployees,
710
- loadEmployeesSync: () => loadEmployeesSync,
711
- normalizeRosterCase: () => normalizeRosterCase,
712
- registerBinSymlinks: () => registerBinSymlinks,
713
- saveEmployees: () => saveEmployees,
714
- validateEmployeeName: () => validateEmployeeName
715
- });
716
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
717
- import { existsSync as existsSync5, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
718
- import { execSync as execSync3 } from "child_process";
719
- import path5 from "path";
720
- import os5 from "os";
721
- function validateEmployeeName(name) {
722
- if (!name) {
723
- return { valid: false, error: "Name is required" };
724
- }
725
- if (name.length > 32) {
726
- return { valid: false, error: "Name must be 32 characters or fewer" };
727
- }
728
- if (!/^[a-z][a-z0-9]*$/.test(name)) {
729
- return {
730
- valid: false,
731
- error: "Name must start with a letter and contain only lowercase alphanumeric characters"
732
- };
733
- }
734
- return { valid: true };
735
- }
736
- async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
737
- if (!existsSync5(employeesPath)) {
738
- return [];
739
- }
740
- const raw = await readFile2(employeesPath, "utf-8");
741
- try {
742
- return JSON.parse(raw);
743
- } catch {
744
- return [];
745
- }
746
- }
747
- async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
748
- await mkdir2(path5.dirname(employeesPath), { recursive: true });
749
- await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
750
- }
751
- function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
752
- if (!existsSync5(employeesPath)) return [];
753
- try {
754
- return JSON.parse(readFileSync5(employeesPath, "utf-8"));
755
- } catch {
756
- return [];
757
- }
758
- }
759
- function getEmployee(employees, name) {
760
- return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
761
- }
762
- function getEmployeeByRole(employees, role) {
763
- const lower = role.toLowerCase();
764
- return employees.find((e) => e.role.toLowerCase() === lower);
765
- }
766
- function getEmployeeNamesByRole(employees, role) {
767
- const lower = role.toLowerCase();
768
- return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
769
- }
770
- function hasRole(agentName, role) {
771
- const employees = loadEmployeesSync();
772
- const emp = getEmployee(employees, agentName);
773
- return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
774
- }
775
- function isMultiInstance(agentName, employees) {
776
- const roster = employees ?? loadEmployeesSync();
777
- const emp = getEmployee(roster, agentName);
778
- if (!emp) return false;
779
- return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
780
- }
781
- function addEmployee(employees, employee) {
782
- const normalized = { ...employee, name: employee.name.toLowerCase() };
783
- if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
784
- throw new Error(`Employee '${normalized.name}' already exists`);
785
- }
786
- return [...employees, normalized];
787
- }
788
- async function normalizeRosterCase(rosterPath) {
789
- const employees = await loadEmployees(rosterPath);
790
- let changed = false;
791
- for (const emp of employees) {
792
- if (emp.name !== emp.name.toLowerCase()) {
793
- const oldName = emp.name;
794
- emp.name = emp.name.toLowerCase();
795
- changed = true;
796
- try {
797
- const identityDir = path5.join(os5.homedir(), ".exe-os", "identity");
798
- const oldPath = path5.join(identityDir, `${oldName}.md`);
799
- const newPath = path5.join(identityDir, `${emp.name}.md`);
800
- if (existsSync5(oldPath) && !existsSync5(newPath)) {
801
- renameSync3(oldPath, newPath);
802
- } else if (existsSync5(oldPath) && oldPath !== newPath) {
803
- const content = readFileSync5(oldPath, "utf-8");
804
- writeFileSync3(newPath, content, "utf-8");
805
- if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
806
- unlinkSync2(oldPath);
807
- }
808
- }
809
- } catch {
810
- }
811
- }
812
- }
813
- if (changed) {
814
- await saveEmployees(employees, rosterPath);
815
- }
816
- return changed;
817
- }
818
- function findExeBin() {
819
- try {
820
- return execSync3(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
821
- } catch {
822
- return null;
823
- }
824
- }
825
- function registerBinSymlinks(name) {
826
- const created = [];
827
- const skipped = [];
828
- const errors = [];
829
- const exeBinPath = findExeBin();
830
- if (!exeBinPath) {
831
- errors.push("Could not find 'exe-os' in PATH");
832
- return { created, skipped, errors };
833
- }
834
- const binDir = path5.dirname(exeBinPath);
835
- let target;
836
- try {
837
- target = readlinkSync(exeBinPath);
838
- } catch {
839
- errors.push("Could not read 'exe' symlink");
840
- return { created, skipped, errors };
841
- }
842
- for (const suffix of ["", "-opencode"]) {
843
- const linkName = `${name}${suffix}`;
844
- const linkPath = path5.join(binDir, linkName);
845
- if (existsSync5(linkPath)) {
846
- skipped.push(linkName);
847
- continue;
848
- }
849
- try {
850
- symlinkSync(target, linkPath);
851
- created.push(linkName);
852
- } catch (err) {
853
- errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
854
- }
855
- }
856
- return { created, skipped, errors };
857
- }
858
- var EMPLOYEES_PATH, MULTI_INSTANCE_ROLES;
859
- var init_employees = __esm({
860
- "src/lib/employees.ts"() {
861
- "use strict";
862
- init_config();
863
- EMPLOYEES_PATH = path5.join(EXE_AI_DIR, "exe-employees.json");
864
- MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
886
+ INTERCOM_LOG = path5.join(os5.homedir(), ".exe-os", "intercom.log");
865
887
  }
866
888
  });
867
889
 
@@ -1076,7 +1098,7 @@ function _resetLastRelaunchCache() {
1076
1098
  }
1077
1099
  async function lastResumeCreatedAtMs(agentId) {
1078
1100
  const client = getClient();
1079
- const cmScope = sessionScopeFilter();
1101
+ const cmScope = sessionScopeFilter(null);
1080
1102
  const result = await client.execute({
1081
1103
  sql: `SELECT MAX(created_at) AS last_created_at
1082
1104
  FROM tasks
@@ -1101,7 +1123,7 @@ async function createOrRefreshResumeTask(agentId, projectDir, openTasks) {
1101
1123
  const client = getClient();
1102
1124
  const now = (/* @__PURE__ */ new Date()).toISOString();
1103
1125
  const context = buildResumeContext(agentId, openTasks);
1104
- const rdScope = sessionScopeFilter();
1126
+ const rdScope = sessionScopeFilter(null);
1105
1127
  const existing = await client.execute({
1106
1128
  sql: `SELECT id FROM tasks
1107
1129
  WHERE assigned_to = ?
@@ -1135,7 +1157,7 @@ async function pollCapacityDead() {
1135
1157
  const transport = getTransport();
1136
1158
  const relaunched = [];
1137
1159
  const registered = listSessions().filter(
1138
- (s) => s.agentId !== "exe"
1160
+ (s) => s.agentId !== "exe" && !isCoordinatorName(s.agentId)
1139
1161
  );
1140
1162
  if (registered.length === 0) return [];
1141
1163
  let liveSessions;
@@ -1195,7 +1217,7 @@ async function pollCapacityDead() {
1195
1217
  reason: "capacity"
1196
1218
  });
1197
1219
  const client = getClient();
1198
- const rlScope = sessionScopeFilter();
1220
+ const rlScope = sessionScopeFilter(null);
1199
1221
  const openTasks = await client.execute({
1200
1222
  sql: `SELECT id, title, priority, task_file, status
1201
1223
  FROM tasks
@@ -1249,6 +1271,7 @@ var init_capacity_monitor = __esm({
1249
1271
  init_session_kill_telemetry();
1250
1272
  init_tmux_routing();
1251
1273
  init_task_scope();
1274
+ init_employees();
1252
1275
  CAPACITY_PATTERNS = [
1253
1276
  /conversation is too long/i,
1254
1277
  /maximum context length/i,
@@ -1398,7 +1421,7 @@ function employeeSessionName(employee, exeSession, instance) {
1398
1421
  exeSession = root;
1399
1422
  } else {
1400
1423
  throw new Error(
1401
- `Invalid exeSession "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name (e.g., "exe1", "work", "yoda1")`
1424
+ `Invalid coordinator session "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name.`
1402
1425
  );
1403
1426
  }
1404
1427
  }
@@ -1418,8 +1441,10 @@ function parseParentExe(sessionName, agentId) {
1418
1441
  return match?.[1] ?? null;
1419
1442
  }
1420
1443
  function extractRootExe(name) {
1421
- const match = name.match(/(exe\d+)$/);
1422
- return match?.[1] ?? null;
1444
+ if (!name) return null;
1445
+ if (!name.includes("-")) return name;
1446
+ const parts = name.split("-").filter(Boolean);
1447
+ return parts.length > 0 ? parts[parts.length - 1] : null;
1423
1448
  }
1424
1449
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
1425
1450
  if (!existsSync8(SESSION_CACHE)) {
@@ -1564,12 +1589,14 @@ function isSessionBusy(sessionName) {
1564
1589
  return state === "thinking" || state === "tool";
1565
1590
  }
1566
1591
  function isExeSession(sessionName) {
1567
- return /^exe\d*$/.test(sessionName);
1592
+ const matchesBaseWithInstance = (baseName) => sessionName === baseName || sessionName.startsWith(baseName) && /^\d+$/.test(sessionName.slice(baseName.length));
1593
+ const coordinatorName = getCoordinatorName();
1594
+ return matchesBaseWithInstance(coordinatorName) || matchesBaseWithInstance("exe");
1568
1595
  }
1569
1596
  function sendIntercom(targetSession) {
1570
1597
  const transport = getTransport();
1571
1598
  if (isExeSession(targetSession)) {
1572
- logIntercom(`SKIP_EXE \u2192 ${targetSession} (exe sessions use prompt-submit hook)`);
1599
+ logIntercom(`SKIP_COORDINATOR \u2192 ${targetSession} (coordinator sessions use prompt-submit hook)`);
1573
1600
  return "skipped_exe";
1574
1601
  }
1575
1602
  if (isDebounced(targetSession)) {
@@ -1621,7 +1648,7 @@ function notifyParentExe(sessionKey) {
1621
1648
  if (result === "failed") {
1622
1649
  const rootExe = resolveExeSession();
1623
1650
  if (rootExe && rootExe !== target) {
1624
- process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root exe ${rootExe}
1651
+ process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root coordinator session ${rootExe}
1625
1652
  `);
1626
1653
  const fallback = sendIntercom(rootExe);
1627
1654
  return fallback !== "failed";
@@ -1631,8 +1658,8 @@ function notifyParentExe(sessionKey) {
1631
1658
  return true;
1632
1659
  }
1633
1660
  function ensureEmployee(employeeName, exeSession, projectDir, opts) {
1634
- if (employeeName === "exe") {
1635
- return { status: "failed", sessionName: "", error: "exe is the COO, not a dispatchable employee" };
1661
+ if (employeeName === "exe" || isCoordinatorName(employeeName)) {
1662
+ return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
1636
1663
  }
1637
1664
  try {
1638
1665
  assertEmployeeLimitSync();
@@ -1641,8 +1668,8 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
1641
1668
  return { status: "failed", sessionName: "", error: err.message };
1642
1669
  }
1643
1670
  }
1644
- if (/-exe\d*$/.test(employeeName)) {
1645
- const bare = employeeName.replace(/-exe\d*$/, "").replace(/\d+$/, "");
1671
+ if (employeeName.includes("-")) {
1672
+ const bare = employeeName.split("-")[0].replace(/\d+$/, "");
1646
1673
  return {
1647
1674
  status: "failed",
1648
1675
  sessionName: "",
@@ -1661,7 +1688,7 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
1661
1688
  return {
1662
1689
  status: "failed",
1663
1690
  sessionName: "",
1664
- error: `Invalid exeSession "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name (e.g., "exe1", "work", "yoda1")`
1691
+ error: `Invalid coordinator session "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name.`
1665
1692
  };
1666
1693
  }
1667
1694
  }
@@ -1818,8 +1845,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
1818
1845
  const ctxContent = [
1819
1846
  `## Session Context`,
1820
1847
  `You are running in tmux session: ${sessionName}.`,
1821
- `Your parent exe session is ${exeSession}.`,
1822
- `Your employees (if any) use the -${exeSession} suffix (e.g., tom-${exeSession}).`
1848
+ `Your parent coordinator session is ${exeSession}.`,
1849
+ `Your employees (if any) use the -${exeSession} suffix.`
1823
1850
  ].join("\n");
1824
1851
  writeFileSync5(ctxFile, ctxContent);
1825
1852
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
@@ -1923,6 +1950,7 @@ var init_tmux_routing = __esm({
1923
1950
  init_provider_table();
1924
1951
  init_intercom_queue();
1925
1952
  init_plan_limits();
1953
+ init_employees();
1926
1954
  SPAWN_LOCK_DIR = path8.join(os6.homedir(), ".exe-os", "spawn-locks");
1927
1955
  SESSION_CACHE = path8.join(os6.homedir(), ".exe-os", "session-cache");
1928
1956
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
@@ -2205,6 +2233,36 @@ async function listTasks(input) {
2205
2233
  tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
2206
2234
  }));
2207
2235
  }
2236
+ function isTmuxSessionAlive(identifier) {
2237
+ if (!identifier || identifier === "unknown") return true;
2238
+ try {
2239
+ if (identifier.startsWith("%")) {
2240
+ const output = execSync5("tmux list-panes -a -F '#{pane_id}'", {
2241
+ timeout: 2e3,
2242
+ encoding: "utf8",
2243
+ stdio: ["pipe", "pipe", "pipe"]
2244
+ });
2245
+ return output.split("\n").some((l) => l.trim() === identifier);
2246
+ } else {
2247
+ execSync5(`tmux has-session -t ${JSON.stringify(identifier)}`, {
2248
+ timeout: 2e3,
2249
+ stdio: ["pipe", "pipe", "pipe"]
2250
+ });
2251
+ return true;
2252
+ }
2253
+ } catch {
2254
+ if (identifier.startsWith("%")) return true;
2255
+ try {
2256
+ execSync5("tmux list-sessions", {
2257
+ timeout: 2e3,
2258
+ stdio: ["pipe", "pipe", "pipe"]
2259
+ });
2260
+ return false;
2261
+ } catch {
2262
+ return true;
2263
+ }
2264
+ }
2265
+ }
2208
2266
  function checkStaleCompletion(taskContext, taskCreatedAt) {
2209
2267
  if (!taskContext) return null;
2210
2268
  if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
@@ -2267,13 +2325,59 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
2267
2325
  });
2268
2326
  if (claim.rowsAffected === 0) {
2269
2327
  const current = await client.execute({
2270
- sql: "SELECT status, assigned_tmux FROM tasks WHERE id = ?",
2328
+ sql: "SELECT status, assigned_tmux, assigned_by FROM tasks WHERE id = ?",
2271
2329
  args: [taskId]
2272
2330
  });
2273
2331
  const cur = current.rows[0];
2274
- const status = cur?.status ?? "unknown";
2275
- const claimedBy = cur?.assigned_tmux ? ` (claimed by ${cur.assigned_tmux})` : "";
2276
- throw new Error(`${TASK_ALREADY_CLAIMED_PREFIX}: task ${taskId} is ${status}${claimedBy}`);
2332
+ const curStatus = cur?.status ?? "unknown";
2333
+ const claimedBySession = cur?.assigned_tmux ?? "";
2334
+ const assignedBy = cur?.assigned_by ?? "";
2335
+ if (curStatus === "in_progress" && claimedBySession && !isTmuxSessionAlive(claimedBySession)) {
2336
+ process.stderr.write(
2337
+ `[tasks] Auto-releasing dead claim on ${taskId} (was ${claimedBySession})
2338
+ `
2339
+ );
2340
+ await client.execute({
2341
+ sql: "UPDATE tasks SET status = 'open', assigned_tmux = NULL, updated_at = ? WHERE id = ?",
2342
+ args: [now, taskId]
2343
+ });
2344
+ const retried = await client.execute({
2345
+ sql: `UPDATE tasks SET status = 'in_progress', assigned_tmux = ?, updated_at = ? WHERE id = ? AND status = 'open'`,
2346
+ args: [tmuxSession, now, taskId]
2347
+ });
2348
+ if (retried.rowsAffected > 0) {
2349
+ try {
2350
+ await writeCheckpoint({
2351
+ taskId,
2352
+ step: "reclaimed_dead_session",
2353
+ contextSummary: `Task reclaimed after dead session ${claimedBySession} released.`
2354
+ });
2355
+ } catch {
2356
+ }
2357
+ return { row, taskFile, now, taskId };
2358
+ }
2359
+ }
2360
+ if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId === "exe")) {
2361
+ process.stderr.write(
2362
+ `[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
2363
+ `
2364
+ );
2365
+ await client.execute({
2366
+ sql: `UPDATE tasks SET status = 'in_progress', assigned_tmux = ?, updated_at = ? WHERE id = ?`,
2367
+ args: [tmuxSession, now, taskId]
2368
+ });
2369
+ try {
2370
+ await writeCheckpoint({
2371
+ taskId,
2372
+ step: "assigner_override",
2373
+ contextSummary: `Task force-reclaimed by assigner ${input.callerAgentId}.`
2374
+ });
2375
+ } catch {
2376
+ }
2377
+ return { row, taskFile, now, taskId };
2378
+ }
2379
+ const claimedBy = claimedBySession ? ` (claimed by ${claimedBySession})` : "";
2380
+ throw new Error(`${TASK_ALREADY_CLAIMED_PREFIX}: task ${taskId} is ${curStatus}${claimedBy}`);
2277
2381
  }
2278
2382
  try {
2279
2383
  await writeCheckpoint({
@@ -2371,7 +2475,7 @@ var init_tasks_crud = __esm({
2371
2475
  "use strict";
2372
2476
  init_database();
2373
2477
  init_task_scope();
2374
- DELEGATION_KEYWORDS = /parallel|delegate|wave|tom\d*-exe/i;
2478
+ DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
2375
2479
  TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
2376
2480
  }
2377
2481
  });
@@ -2728,7 +2832,7 @@ function findSessionForProject(projectName) {
2728
2832
  const sessions = listSessions();
2729
2833
  for (const s of sessions) {
2730
2834
  const proj = s.projectDir.split("/").filter(Boolean).pop();
2731
- if (proj === projectName && s.agentId === "exe") return s;
2835
+ if (proj === projectName && (s.agentId === "exe" || isCoordinatorName(s.agentId))) return s;
2732
2836
  }
2733
2837
  return null;
2734
2838
  }
@@ -2768,12 +2872,13 @@ var init_session_scope = __esm({
2768
2872
  init_session_registry();
2769
2873
  init_project_name();
2770
2874
  init_tmux_routing();
2875
+ init_employees();
2771
2876
  }
2772
2877
  });
2773
2878
 
2774
2879
  // src/lib/tasks-notify.ts
2775
2880
  async function dispatchTaskToEmployee(input) {
2776
- if (input.assignedTo === "exe") return { dispatched: "skipped" };
2881
+ if (input.assignedTo === "exe" || isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
2777
2882
  let crossProject = false;
2778
2883
  if (input.projectName) {
2779
2884
  try {
@@ -3216,6 +3321,24 @@ async function updateTask(input) {
3216
3321
  });
3217
3322
  } catch {
3218
3323
  }
3324
+ const assignedAgent = String(row.assigned_to);
3325
+ if (!isCoordinatorName(assignedAgent)) {
3326
+ try {
3327
+ const draftClient = getClient();
3328
+ if (input.status === "done") {
3329
+ await draftClient.execute({
3330
+ sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
3331
+ args: [assignedAgent]
3332
+ });
3333
+ } else if (input.status === "cancelled") {
3334
+ await draftClient.execute({
3335
+ sql: `DELETE FROM memories WHERE agent_id = ? AND draft = 1`,
3336
+ args: [assignedAgent]
3337
+ });
3338
+ }
3339
+ } catch {
3340
+ }
3341
+ }
3219
3342
  try {
3220
3343
  const client = getClient();
3221
3344
  const cascaded = await client.execute({
@@ -3234,8 +3357,8 @@ async function updateTask(input) {
3234
3357
  }
3235
3358
  const isTerminal = input.status === "done" || input.status === "needs_review";
3236
3359
  if (isTerminal) {
3237
- const isExe = String(row.assigned_to) === "exe";
3238
- if (!isExe) {
3360
+ const isCoordinator = String(row.assigned_to) === "exe" || isCoordinatorName(String(row.assigned_to));
3361
+ if (!isCoordinator) {
3239
3362
  notifyTaskDone();
3240
3363
  }
3241
3364
  await markTaskNotificationsRead(taskFile);
@@ -3259,7 +3382,7 @@ async function updateTask(input) {
3259
3382
  }
3260
3383
  }
3261
3384
  }
3262
- if (input.status === "done" && String(row.assigned_to) !== "exe" && !process.env.VITEST) {
3385
+ if (input.status === "done" && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
3263
3386
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
3264
3387
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
3265
3388
  taskId,
@@ -3275,7 +3398,7 @@ async function updateTask(input) {
3275
3398
  });
3276
3399
  }
3277
3400
  let nextTask;
3278
- if (isTerminal && String(row.assigned_to) !== "exe") {
3401
+ if (isTerminal && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to))) {
3279
3402
  try {
3280
3403
  nextTask = await findNextTask(String(row.assigned_to));
3281
3404
  } catch {
@@ -3302,12 +3425,14 @@ async function updateTask(input) {
3302
3425
  async function deleteTask(taskId, baseDir) {
3303
3426
  const client = getClient();
3304
3427
  const { taskFile, assignedTo, assignedBy, taskSlug } = await deleteTaskCore(taskId, baseDir);
3305
- const reviewer = assignedBy || "exe";
3428
+ const coordinatorName = getCoordinatorName();
3429
+ const reviewer = assignedBy || coordinatorName;
3306
3430
  const reviewSlug = `review-${assignedTo}-${taskSlug}`;
3307
3431
  const reviewFile = `exe/${reviewer}/${reviewSlug}.md`;
3432
+ const legacyReviewFile = `exe/${coordinatorName}/${reviewSlug}.md`;
3308
3433
  await client.execute({
3309
- sql: "DELETE FROM tasks WHERE task_file = ? OR task_file = ?",
3310
- args: [reviewFile, `exe/exe/${reviewSlug}.md`]
3434
+ sql: "DELETE FROM tasks WHERE task_file = ? OR task_file = ? OR task_file = ?",
3435
+ args: [reviewFile, legacyReviewFile, `exe/exe/${reviewSlug}.md`]
3311
3436
  });
3312
3437
  await markAsReadByTaskFile(taskFile);
3313
3438
  await markAsReadByTaskFile(reviewFile);
@@ -3319,6 +3444,7 @@ var init_tasks = __esm({
3319
3444
  init_config();
3320
3445
  init_notifications();
3321
3446
  init_state_bus();
3447
+ init_employees();
3322
3448
  init_tasks_crud();
3323
3449
  init_tasks_review();
3324
3450
  init_tasks_crud();
@@ -3342,8 +3468,51 @@ import path14 from "path";
3342
3468
  init_session_key();
3343
3469
 
3344
3470
  // src/adapters/claude/active-agent.ts
3471
+ init_employees();
3345
3472
  var CACHE_DIR = path14.join(EXE_AI_DIR, "session-cache");
3346
3473
  var STALE_MS = 24 * 60 * 60 * 1e3;
3474
+ function isNameWithOptionalInstance(candidate, baseName) {
3475
+ if (candidate === baseName) return true;
3476
+ if (!candidate.startsWith(baseName)) return false;
3477
+ return /^\d+$/.test(candidate.slice(baseName.length));
3478
+ }
3479
+ function resolveEmployeeFromSessionPrefix(prefix, employees) {
3480
+ const sorted = [...employees].sort((a, b) => b.name.length - a.name.length);
3481
+ for (const employee of sorted) {
3482
+ if (isNameWithOptionalInstance(prefix, employee.name)) {
3483
+ return { agentId: employee.name, agentRole: employee.role };
3484
+ }
3485
+ }
3486
+ return null;
3487
+ }
3488
+ function resolveActiveAgentFromTmuxSession(sessionName) {
3489
+ const employees = loadEmployeesSync();
3490
+ const coordinator = getCoordinatorEmployee(employees);
3491
+ const coordinatorName = coordinator?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
3492
+ if (isNameWithOptionalInstance(sessionName, coordinatorName)) {
3493
+ return {
3494
+ agentId: coordinatorName,
3495
+ agentRole: coordinator?.role ?? "COO"
3496
+ };
3497
+ }
3498
+ if (isNameWithOptionalInstance(sessionName, DEFAULT_COORDINATOR_TEMPLATE_NAME)) {
3499
+ return {
3500
+ agentId: coordinator?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME,
3501
+ agentRole: coordinator?.role ?? "COO"
3502
+ };
3503
+ }
3504
+ if (sessionName.includes("-")) {
3505
+ const prefix = sessionName.split("-")[0] ?? "";
3506
+ const employee = resolveEmployeeFromSessionPrefix(prefix, employees);
3507
+ if (employee) return employee;
3508
+ const legacy = prefix.match(/^([a-zA-Z]+)\d*$/);
3509
+ if (legacy?.[1] && legacy[1] !== DEFAULT_COORDINATOR_TEMPLATE_NAME) {
3510
+ const emp = getEmployee(employees, legacy[1]);
3511
+ return { agentId: emp?.name ?? legacy[1], agentRole: emp?.role ?? "employee" };
3512
+ }
3513
+ }
3514
+ return null;
3515
+ }
3347
3516
  function getMarkerPath() {
3348
3517
  return path14.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
3349
3518
  }
@@ -3380,13 +3549,8 @@ function getActiveAgent() {
3380
3549
  "tmux display-message -p '#{session_name}' 2>/dev/null",
3381
3550
  { encoding: "utf8", timeout: 2e3 }
3382
3551
  ).trim();
3383
- const empMatch = sessionName.match(/^([a-zA-Z]+)\d*-exe\d+$/);
3384
- if (empMatch && empMatch[1] !== "exe") {
3385
- return { agentId: empMatch[1], agentRole: "employee" };
3386
- }
3387
- if (/^exe\d+$/.test(sessionName)) {
3388
- return { agentId: "exe", agentRole: "COO" };
3389
- }
3552
+ const resolved = resolveActiveAgentFromTmuxSession(sessionName);
3553
+ if (resolved) return resolved;
3390
3554
  } catch {
3391
3555
  }
3392
3556
  return {