@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,23 +26,6 @@ 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
  import { readFile, writeFile, mkdir, chmod } from "fs/promises";
48
31
  import { readFileSync, existsSync, renameSync } from "fs";
@@ -189,13 +172,7 @@ var init_config = __esm({
189
172
  wikiUrl: "",
190
173
  wikiApiKey: "",
191
174
  wikiSyncIntervalMs: 30 * 60 * 1e3,
192
- wikiWorkspaceMapping: {
193
- exe: "Executive",
194
- yoshi: "Engineering",
195
- mari: "Marketing",
196
- tom: "Engineering",
197
- sasha: "Production"
198
- },
175
+ wikiWorkspaceMapping: {},
199
176
  wikiAutoUpdate: true,
200
177
  wikiAutoUpdateThreshold: 0.5,
201
178
  wikiAutoUpdateCreateNew: true,
@@ -232,15 +209,80 @@ var init_config = __esm({
232
209
  }
233
210
  });
234
211
 
235
- // src/lib/notifications.ts
236
- import crypto from "crypto";
212
+ // src/lib/employees.ts
213
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
214
+ import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
215
+ import { execSync } from "child_process";
237
216
  import path2 from "path";
238
217
  import os2 from "os";
218
+ function normalizeRole(role) {
219
+ return (role ?? "").trim().toLowerCase();
220
+ }
221
+ function isCoordinatorRole(role) {
222
+ return normalizeRole(role) === normalizeRole(COORDINATOR_ROLE);
223
+ }
224
+ function getCoordinatorEmployee(employees) {
225
+ return employees.find((e) => isCoordinatorRole(e.role));
226
+ }
227
+ function getCoordinatorName(employees = loadEmployeesSync()) {
228
+ return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
229
+ }
230
+ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
231
+ if (!agentName) return false;
232
+ return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
233
+ }
234
+ function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
235
+ return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
236
+ }
237
+ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
238
+ if (!existsSync2(employeesPath)) return [];
239
+ try {
240
+ return JSON.parse(readFileSync2(employeesPath, "utf-8"));
241
+ } catch {
242
+ return [];
243
+ }
244
+ }
245
+ function getEmployee(employees, name) {
246
+ return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
247
+ }
248
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE;
249
+ var init_employees = __esm({
250
+ "src/lib/employees.ts"() {
251
+ "use strict";
252
+ init_config();
253
+ EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
254
+ DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
255
+ COORDINATOR_ROLE = "COO";
256
+ }
257
+ });
258
+
259
+ // src/lib/database.ts
260
+ import { createClient } from "@libsql/client";
261
+ function getClient() {
262
+ if (!_resilientClient) {
263
+ throw new Error("Database client not initialized. Call initDatabase() first.");
264
+ }
265
+ return _resilientClient;
266
+ }
267
+ var _resilientClient;
268
+ var init_database = __esm({
269
+ "src/lib/database.ts"() {
270
+ "use strict";
271
+ init_db_retry();
272
+ init_employees();
273
+ _resilientClient = null;
274
+ }
275
+ });
276
+
277
+ // src/lib/notifications.ts
278
+ import crypto from "crypto";
279
+ import path3 from "path";
280
+ import os3 from "os";
239
281
  import {
240
- readFileSync as readFileSync2,
282
+ readFileSync as readFileSync3,
241
283
  readdirSync,
242
- unlinkSync,
243
- existsSync as existsSync2,
284
+ unlinkSync as unlinkSync2,
285
+ existsSync as existsSync3,
244
286
  rmdirSync
245
287
  } from "fs";
246
288
  async function writeNotification(notification) {
@@ -340,24 +382,24 @@ var init_state_bus = __esm({
340
382
  });
341
383
 
342
384
  // src/lib/session-registry.ts
343
- import path3 from "path";
344
- import os3 from "os";
385
+ import path4 from "path";
386
+ import os4 from "os";
345
387
  var REGISTRY_PATH;
346
388
  var init_session_registry = __esm({
347
389
  "src/lib/session-registry.ts"() {
348
390
  "use strict";
349
- REGISTRY_PATH = path3.join(os3.homedir(), ".exe-os", "session-registry.json");
391
+ REGISTRY_PATH = path4.join(os4.homedir(), ".exe-os", "session-registry.json");
350
392
  }
351
393
  });
352
394
 
353
395
  // src/lib/session-key.ts
354
- import { execSync } from "child_process";
396
+ import { execSync as execSync2 } from "child_process";
355
397
  function getSessionKey() {
356
398
  if (_cached) return _cached;
357
399
  let pid = process.ppid;
358
400
  for (let i = 0; i < 10; i++) {
359
401
  try {
360
- const info = execSync(`ps -p ${pid} -o ppid=,comm=`, {
402
+ const info = execSync2(`ps -p ${pid} -o ppid=,comm=`, {
361
403
  encoding: "utf8",
362
404
  timeout: 2e3
363
405
  }).trim();
@@ -493,7 +535,7 @@ var init_transport = __esm({
493
535
  });
494
536
 
495
537
  // src/lib/cc-agent-support.ts
496
- import { execSync as execSync2 } from "child_process";
538
+ import { execSync as execSync3 } from "child_process";
497
539
  var init_cc_agent_support = __esm({
498
540
  "src/lib/cc-agent-support.ts"() {
499
541
  "use strict";
@@ -522,17 +564,17 @@ var init_provider_table = __esm({
522
564
  });
523
565
 
524
566
  // src/lib/intercom-queue.ts
525
- import { readFileSync as readFileSync3, writeFileSync, renameSync as renameSync2, existsSync as existsSync3, mkdirSync } from "fs";
526
- import path4 from "path";
527
- import os4 from "os";
567
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, renameSync as renameSync3, existsSync as existsSync4, mkdirSync } from "fs";
568
+ import path5 from "path";
569
+ import os5 from "os";
528
570
  function ensureDir() {
529
- const dir = path4.dirname(QUEUE_PATH);
530
- if (!existsSync3(dir)) mkdirSync(dir, { recursive: true });
571
+ const dir = path5.dirname(QUEUE_PATH);
572
+ if (!existsSync4(dir)) mkdirSync(dir, { recursive: true });
531
573
  }
532
574
  function readQueue() {
533
575
  try {
534
- if (!existsSync3(QUEUE_PATH)) return [];
535
- return JSON.parse(readFileSync3(QUEUE_PATH, "utf8"));
576
+ if (!existsSync4(QUEUE_PATH)) return [];
577
+ return JSON.parse(readFileSync4(QUEUE_PATH, "utf8"));
536
578
  } catch {
537
579
  return [];
538
580
  }
@@ -540,8 +582,8 @@ function readQueue() {
540
582
  function writeQueue(queue) {
541
583
  ensureDir();
542
584
  const tmp = `${QUEUE_PATH}.tmp`;
543
- writeFileSync(tmp, JSON.stringify(queue, null, 2));
544
- renameSync2(tmp, QUEUE_PATH);
585
+ writeFileSync2(tmp, JSON.stringify(queue, null, 2));
586
+ renameSync3(tmp, QUEUE_PATH);
545
587
  }
546
588
  function queueIntercom(targetSession, reason) {
547
589
  const queue = readQueue();
@@ -564,24 +606,9 @@ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
564
606
  var init_intercom_queue = __esm({
565
607
  "src/lib/intercom-queue.ts"() {
566
608
  "use strict";
567
- QUEUE_PATH = path4.join(os4.homedir(), ".exe-os", "intercom-queue.json");
609
+ QUEUE_PATH = path5.join(os5.homedir(), ".exe-os", "intercom-queue.json");
568
610
  TTL_MS = 60 * 60 * 1e3;
569
- INTERCOM_LOG = path4.join(os4.homedir(), ".exe-os", "intercom.log");
570
- }
571
- });
572
-
573
- // src/lib/employees.ts
574
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
575
- import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync4, renameSync as renameSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
576
- import { execSync as execSync3 } from "child_process";
577
- import path5 from "path";
578
- import os5 from "os";
579
- var EMPLOYEES_PATH;
580
- var init_employees = __esm({
581
- "src/lib/employees.ts"() {
582
- "use strict";
583
- init_config();
584
- EMPLOYEES_PATH = path5.join(EXE_AI_DIR, "exe-employees.json");
611
+ INTERCOM_LOG = path5.join(os5.homedir(), ".exe-os", "intercom.log");
585
612
  }
586
613
  });
587
614
 
@@ -625,8 +652,10 @@ function getMySession() {
625
652
  return getTransport().getMySession();
626
653
  }
627
654
  function extractRootExe(name) {
628
- const match = name.match(/(exe\d+)$/);
629
- return match?.[1] ?? null;
655
+ if (!name) return null;
656
+ if (!name.includes("-")) return name;
657
+ const parts = name.split("-").filter(Boolean);
658
+ return parts.length > 0 ? parts[parts.length - 1] : null;
630
659
  }
631
660
  function getParentExe(sessionKey) {
632
661
  try {
@@ -717,12 +746,14 @@ function getSessionState(sessionName) {
717
746
  }
718
747
  }
719
748
  function isExeSession(sessionName) {
720
- return /^exe\d*$/.test(sessionName);
749
+ const matchesBaseWithInstance = (baseName) => sessionName === baseName || sessionName.startsWith(baseName) && /^\d+$/.test(sessionName.slice(baseName.length));
750
+ const coordinatorName = getCoordinatorName();
751
+ return matchesBaseWithInstance(coordinatorName) || matchesBaseWithInstance("exe");
721
752
  }
722
753
  function sendIntercom(targetSession) {
723
754
  const transport = getTransport();
724
755
  if (isExeSession(targetSession)) {
725
- logIntercom(`SKIP_EXE \u2192 ${targetSession} (exe sessions use prompt-submit hook)`);
756
+ logIntercom(`SKIP_COORDINATOR \u2192 ${targetSession} (coordinator sessions use prompt-submit hook)`);
726
757
  return "skipped_exe";
727
758
  }
728
759
  if (isDebounced(targetSession)) {
@@ -774,7 +805,7 @@ function notifyParentExe(sessionKey) {
774
805
  if (result === "failed") {
775
806
  const rootExe = resolveExeSession();
776
807
  if (rootExe && rootExe !== target) {
777
- process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root exe ${rootExe}
808
+ process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root coordinator session ${rootExe}
778
809
  `);
779
810
  const fallback = sendIntercom(rootExe);
780
811
  return fallback !== "failed";
@@ -795,6 +826,7 @@ var init_tmux_routing = __esm({
795
826
  init_provider_table();
796
827
  init_intercom_queue();
797
828
  init_plan_limits();
829
+ init_employees();
798
830
  SPAWN_LOCK_DIR = path8.join(os6.homedir(), ".exe-os", "spawn-locks");
799
831
  SESSION_CACHE = path8.join(os6.homedir(), ".exe-os", "session-cache");
800
832
  INTERCOM_DEBOUNCE_MS = 3e4;
@@ -909,6 +941,36 @@ async function resolveTask(client, identifier, scopeSession) {
909
941
  }
910
942
  throw new Error(`Task not found: ${identifier}`);
911
943
  }
944
+ function isTmuxSessionAlive(identifier) {
945
+ if (!identifier || identifier === "unknown") return true;
946
+ try {
947
+ if (identifier.startsWith("%")) {
948
+ const output = execSync4("tmux list-panes -a -F '#{pane_id}'", {
949
+ timeout: 2e3,
950
+ encoding: "utf8",
951
+ stdio: ["pipe", "pipe", "pipe"]
952
+ });
953
+ return output.split("\n").some((l) => l.trim() === identifier);
954
+ } else {
955
+ execSync4(`tmux has-session -t ${JSON.stringify(identifier)}`, {
956
+ timeout: 2e3,
957
+ stdio: ["pipe", "pipe", "pipe"]
958
+ });
959
+ return true;
960
+ }
961
+ } catch {
962
+ if (identifier.startsWith("%")) return true;
963
+ try {
964
+ execSync4("tmux list-sessions", {
965
+ timeout: 2e3,
966
+ stdio: ["pipe", "pipe", "pipe"]
967
+ });
968
+ return false;
969
+ } catch {
970
+ return true;
971
+ }
972
+ }
973
+ }
912
974
  function checkStaleCompletion(taskContext, taskCreatedAt) {
913
975
  if (!taskContext) return null;
914
976
  if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
@@ -971,13 +1033,59 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
971
1033
  });
972
1034
  if (claim.rowsAffected === 0) {
973
1035
  const current = await client.execute({
974
- sql: "SELECT status, assigned_tmux FROM tasks WHERE id = ?",
1036
+ sql: "SELECT status, assigned_tmux, assigned_by FROM tasks WHERE id = ?",
975
1037
  args: [taskId]
976
1038
  });
977
1039
  const cur = current.rows[0];
978
- const status = cur?.status ?? "unknown";
979
- const claimedBy = cur?.assigned_tmux ? ` (claimed by ${cur.assigned_tmux})` : "";
980
- throw new Error(`${TASK_ALREADY_CLAIMED_PREFIX}: task ${taskId} is ${status}${claimedBy}`);
1040
+ const curStatus = cur?.status ?? "unknown";
1041
+ const claimedBySession = cur?.assigned_tmux ?? "";
1042
+ const assignedBy = cur?.assigned_by ?? "";
1043
+ if (curStatus === "in_progress" && claimedBySession && !isTmuxSessionAlive(claimedBySession)) {
1044
+ process.stderr.write(
1045
+ `[tasks] Auto-releasing dead claim on ${taskId} (was ${claimedBySession})
1046
+ `
1047
+ );
1048
+ await client.execute({
1049
+ sql: "UPDATE tasks SET status = 'open', assigned_tmux = NULL, updated_at = ? WHERE id = ?",
1050
+ args: [now, taskId]
1051
+ });
1052
+ const retried = await client.execute({
1053
+ sql: `UPDATE tasks SET status = 'in_progress', assigned_tmux = ?, updated_at = ? WHERE id = ? AND status = 'open'`,
1054
+ args: [tmuxSession, now, taskId]
1055
+ });
1056
+ if (retried.rowsAffected > 0) {
1057
+ try {
1058
+ await writeCheckpoint({
1059
+ taskId,
1060
+ step: "reclaimed_dead_session",
1061
+ contextSummary: `Task reclaimed after dead session ${claimedBySession} released.`
1062
+ });
1063
+ } catch {
1064
+ }
1065
+ return { row, taskFile, now, taskId };
1066
+ }
1067
+ }
1068
+ if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId === "exe")) {
1069
+ process.stderr.write(
1070
+ `[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
1071
+ `
1072
+ );
1073
+ await client.execute({
1074
+ sql: `UPDATE tasks SET status = 'in_progress', assigned_tmux = ?, updated_at = ? WHERE id = ?`,
1075
+ args: [tmuxSession, now, taskId]
1076
+ });
1077
+ try {
1078
+ await writeCheckpoint({
1079
+ taskId,
1080
+ step: "assigner_override",
1081
+ contextSummary: `Task force-reclaimed by assigner ${input.callerAgentId}.`
1082
+ });
1083
+ } catch {
1084
+ }
1085
+ return { row, taskFile, now, taskId };
1086
+ }
1087
+ const claimedBy = claimedBySession ? ` (claimed by ${claimedBySession})` : "";
1088
+ throw new Error(`${TASK_ALREADY_CLAIMED_PREFIX}: task ${taskId} is ${curStatus}${claimedBy}`);
981
1089
  }
982
1090
  try {
983
1091
  await writeCheckpoint({
@@ -1016,7 +1124,7 @@ var init_tasks_crud = __esm({
1016
1124
  "use strict";
1017
1125
  init_database();
1018
1126
  init_task_scope();
1019
- DELEGATION_KEYWORDS = /parallel|delegate|wave|tom\d*-exe/i;
1127
+ DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
1020
1128
  TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
1021
1129
  }
1022
1130
  });
@@ -1547,6 +1655,24 @@ async function updateTask(input) {
1547
1655
  });
1548
1656
  } catch {
1549
1657
  }
1658
+ const assignedAgent = String(row.assigned_to);
1659
+ if (!isCoordinatorName(assignedAgent)) {
1660
+ try {
1661
+ const draftClient = getClient();
1662
+ if (input.status === "done") {
1663
+ await draftClient.execute({
1664
+ sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
1665
+ args: [assignedAgent]
1666
+ });
1667
+ } else if (input.status === "cancelled") {
1668
+ await draftClient.execute({
1669
+ sql: `DELETE FROM memories WHERE agent_id = ? AND draft = 1`,
1670
+ args: [assignedAgent]
1671
+ });
1672
+ }
1673
+ } catch {
1674
+ }
1675
+ }
1550
1676
  try {
1551
1677
  const client = getClient();
1552
1678
  const cascaded = await client.execute({
@@ -1565,8 +1691,8 @@ async function updateTask(input) {
1565
1691
  }
1566
1692
  const isTerminal = input.status === "done" || input.status === "needs_review";
1567
1693
  if (isTerminal) {
1568
- const isExe = String(row.assigned_to) === "exe";
1569
- if (!isExe) {
1694
+ const isCoordinator = String(row.assigned_to) === "exe" || isCoordinatorName(String(row.assigned_to));
1695
+ if (!isCoordinator) {
1570
1696
  notifyTaskDone();
1571
1697
  }
1572
1698
  await markTaskNotificationsRead(taskFile);
@@ -1590,7 +1716,7 @@ async function updateTask(input) {
1590
1716
  }
1591
1717
  }
1592
1718
  }
1593
- if (input.status === "done" && String(row.assigned_to) !== "exe" && !process.env.VITEST) {
1719
+ if (input.status === "done" && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
1594
1720
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
1595
1721
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
1596
1722
  taskId,
@@ -1606,7 +1732,7 @@ async function updateTask(input) {
1606
1732
  });
1607
1733
  }
1608
1734
  let nextTask;
1609
- if (isTerminal && String(row.assigned_to) !== "exe") {
1735
+ if (isTerminal && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to))) {
1610
1736
  try {
1611
1737
  nextTask = await findNextTask(String(row.assigned_to));
1612
1738
  } catch {
@@ -1637,6 +1763,7 @@ var init_tasks = __esm({
1637
1763
  init_config();
1638
1764
  init_notifications();
1639
1765
  init_state_bus();
1766
+ init_employees();
1640
1767
  init_tasks_crud();
1641
1768
  init_tasks_review();
1642
1769
  init_tasks_crud();
@@ -1661,11 +1788,54 @@ __export(active_agent_exports, {
1661
1788
  clearActiveAgent: () => clearActiveAgent,
1662
1789
  getActiveAgent: () => getActiveAgent,
1663
1790
  getAllActiveAgents: () => getAllActiveAgents,
1791
+ resolveActiveAgentFromTmuxSession: () => resolveActiveAgentFromTmuxSession,
1664
1792
  writeActiveAgent: () => writeActiveAgent
1665
1793
  });
1666
1794
  import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5, readdirSync as readdirSync3 } from "fs";
1667
1795
  import { execSync as execSync5 } from "child_process";
1668
1796
  import path13 from "path";
1797
+ function isNameWithOptionalInstance(candidate, baseName) {
1798
+ if (candidate === baseName) return true;
1799
+ if (!candidate.startsWith(baseName)) return false;
1800
+ return /^\d+$/.test(candidate.slice(baseName.length));
1801
+ }
1802
+ function resolveEmployeeFromSessionPrefix(prefix, employees) {
1803
+ const sorted = [...employees].sort((a, b) => b.name.length - a.name.length);
1804
+ for (const employee of sorted) {
1805
+ if (isNameWithOptionalInstance(prefix, employee.name)) {
1806
+ return { agentId: employee.name, agentRole: employee.role };
1807
+ }
1808
+ }
1809
+ return null;
1810
+ }
1811
+ function resolveActiveAgentFromTmuxSession(sessionName) {
1812
+ const employees = loadEmployeesSync();
1813
+ const coordinator = getCoordinatorEmployee(employees);
1814
+ const coordinatorName = coordinator?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
1815
+ if (isNameWithOptionalInstance(sessionName, coordinatorName)) {
1816
+ return {
1817
+ agentId: coordinatorName,
1818
+ agentRole: coordinator?.role ?? "COO"
1819
+ };
1820
+ }
1821
+ if (isNameWithOptionalInstance(sessionName, DEFAULT_COORDINATOR_TEMPLATE_NAME)) {
1822
+ return {
1823
+ agentId: coordinator?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME,
1824
+ agentRole: coordinator?.role ?? "COO"
1825
+ };
1826
+ }
1827
+ if (sessionName.includes("-")) {
1828
+ const prefix = sessionName.split("-")[0] ?? "";
1829
+ const employee = resolveEmployeeFromSessionPrefix(prefix, employees);
1830
+ if (employee) return employee;
1831
+ const legacy = prefix.match(/^([a-zA-Z]+)\d*$/);
1832
+ if (legacy?.[1] && legacy[1] !== DEFAULT_COORDINATOR_TEMPLATE_NAME) {
1833
+ const emp = getEmployee(employees, legacy[1]);
1834
+ return { agentId: emp?.name ?? legacy[1], agentRole: emp?.role ?? "employee" };
1835
+ }
1836
+ }
1837
+ return null;
1838
+ }
1669
1839
  function getMarkerPath() {
1670
1840
  return path13.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
1671
1841
  }
@@ -1718,13 +1888,8 @@ function getActiveAgent() {
1718
1888
  "tmux display-message -p '#{session_name}' 2>/dev/null",
1719
1889
  { encoding: "utf8", timeout: 2e3 }
1720
1890
  ).trim();
1721
- const empMatch = sessionName.match(/^([a-zA-Z]+)\d*-exe\d+$/);
1722
- if (empMatch && empMatch[1] !== "exe") {
1723
- return { agentId: empMatch[1], agentRole: "employee" };
1724
- }
1725
- if (/^exe\d+$/.test(sessionName)) {
1726
- return { agentId: "exe", agentRole: "COO" };
1727
- }
1891
+ const resolved = resolveActiveAgentFromTmuxSession(sessionName);
1892
+ if (resolved) return resolved;
1728
1893
  } catch {
1729
1894
  }
1730
1895
  return {
@@ -1785,6 +1950,7 @@ var init_active_agent = __esm({
1785
1950
  "use strict";
1786
1951
  init_config();
1787
1952
  init_session_key2();
1953
+ init_employees();
1788
1954
  CACHE_DIR = path13.join(EXE_AI_DIR, "session-cache");
1789
1955
  STALE_MS = 24 * 60 * 60 * 1e3;
1790
1956
  }
@@ -1794,6 +1960,7 @@ var init_active_agent = __esm({
1794
1960
  init_tasks();
1795
1961
  init_database();
1796
1962
  init_tasks_crud();
1963
+ init_employees();
1797
1964
  import { z } from "zod";
1798
1965
  function registerUpdateTask(server) {
1799
1966
  server.registerTool(
@@ -1813,7 +1980,7 @@ function registerUpdateTask(server) {
1813
1980
  try {
1814
1981
  const { getActiveAgent: getActiveAgent2 } = await Promise.resolve().then(() => (init_active_agent(), active_agent_exports));
1815
1982
  const agent = getActiveAgent2();
1816
- if (agent.agentId !== "exe" && agent.agentId !== "default") {
1983
+ if (!canCoordinate(agent.agentId, agent.agentRole)) {
1817
1984
  let isOwnReview = false;
1818
1985
  try {
1819
1986
  const client = getClient();
@@ -1840,7 +2007,7 @@ function registerUpdateTask(server) {
1840
2007
  if (assignedBy === "system" && taskFile.includes("review-")) {
1841
2008
  const { getActiveAgent: getActiveAgent2 } = await Promise.resolve().then(() => (init_active_agent(), active_agent_exports));
1842
2009
  const agent = getActiveAgent2();
1843
- if (agent.agentId !== assignedTo && agent.agentId !== "exe" && agent.agentId !== "default") {
2010
+ if (agent.agentId !== assignedTo && !canCoordinate(agent.agentId, agent.agentRole)) {
1844
2011
  process.stderr.write(
1845
2012
  `[update_task] BLOCKED: ${agent.agentId} tried to close review "${String(row.title)}" assigned to ${assignedTo}
1846
2013
  `
@@ -1863,13 +2030,20 @@ function registerUpdateTask(server) {
1863
2030
  };
1864
2031
  }
1865
2032
  }
2033
+ let callerAgentId;
2034
+ try {
2035
+ const { getActiveAgent: getAgent } = await Promise.resolve().then(() => (init_active_agent(), active_agent_exports));
2036
+ callerAgentId = getAgent().agentId;
2037
+ } catch {
2038
+ }
1866
2039
  let task;
1867
2040
  try {
1868
2041
  task = await updateTask({
1869
2042
  taskId: task_id,
1870
2043
  status,
1871
2044
  result,
1872
- baseDir: process.cwd()
2045
+ baseDir: process.cwd(),
2046
+ callerAgentId
1873
2047
  });
1874
2048
  } catch (err) {
1875
2049
  const msg = err instanceof Error ? err.message : String(err);