@askexenow/exe-os 0.8.0 → 0.8.1

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 (90) hide show
  1. package/README.md +178 -79
  2. package/dist/bin/backfill-responses.js +160 -8
  3. package/dist/bin/backfill-vectors.js +130 -1
  4. package/dist/bin/cleanup-stale-review-tasks.js +130 -1
  5. package/dist/bin/cli.js +10111 -7540
  6. package/dist/bin/exe-agent.js +159 -1
  7. package/dist/bin/exe-assign.js +235 -16
  8. package/dist/bin/exe-boot.js +344 -472
  9. package/dist/bin/exe-call.js +145 -1
  10. package/dist/bin/exe-cloud.js +11 -0
  11. package/dist/bin/exe-dispatch.js +37 -24
  12. package/dist/bin/exe-doctor.js +130 -1
  13. package/dist/bin/exe-export-behaviors.js +150 -7
  14. package/dist/bin/exe-forget.js +822 -665
  15. package/dist/bin/exe-gateway.js +470 -62
  16. package/dist/bin/exe-heartbeat.js +133 -2
  17. package/dist/bin/exe-kill.js +150 -7
  18. package/dist/bin/exe-launch-agent.js +150 -7
  19. package/dist/bin/exe-new-employee.js +756 -224
  20. package/dist/bin/exe-pending-messages.js +132 -2
  21. package/dist/bin/exe-pending-notifications.js +130 -1
  22. package/dist/bin/exe-pending-reviews.js +132 -2
  23. package/dist/bin/exe-review.js +160 -8
  24. package/dist/bin/exe-search.js +2473 -2008
  25. package/dist/bin/exe-session-cleanup.js +238 -51
  26. package/dist/bin/exe-settings.js +11 -0
  27. package/dist/bin/exe-status.js +130 -1
  28. package/dist/bin/exe-team.js +130 -1
  29. package/dist/bin/git-sweep.js +272 -16
  30. package/dist/bin/graph-backfill.js +150 -7
  31. package/dist/bin/graph-export.js +150 -7
  32. package/dist/bin/install.js +5 -0
  33. package/dist/bin/scan-tasks.js +238 -19
  34. package/dist/bin/setup.js +1776 -10
  35. package/dist/bin/shard-migrate.js +150 -7
  36. package/dist/bin/update.js +9 -6
  37. package/dist/bin/wiki-sync.js +150 -7
  38. package/dist/gateway/index.js +470 -62
  39. package/dist/hooks/bug-report-worker.js +195 -35
  40. package/dist/hooks/commit-complete.js +272 -16
  41. package/dist/hooks/error-recall.js +2313 -1847
  42. package/dist/hooks/exe-heartbeat-hook.js +5 -0
  43. package/dist/hooks/ingest-worker.js +330 -58
  44. package/dist/hooks/ingest.js +11 -0
  45. package/dist/hooks/instructions-loaded.js +199 -10
  46. package/dist/hooks/notification.js +199 -10
  47. package/dist/hooks/post-compact.js +199 -10
  48. package/dist/hooks/pre-compact.js +199 -10
  49. package/dist/hooks/pre-tool-use.js +199 -10
  50. package/dist/hooks/prompt-ingest-worker.js +179 -14
  51. package/dist/hooks/prompt-submit.js +781 -285
  52. package/dist/hooks/response-ingest-worker.js +1900 -1405
  53. package/dist/hooks/session-end.js +456 -12
  54. package/dist/hooks/session-start.js +2188 -1724
  55. package/dist/hooks/stop.js +200 -10
  56. package/dist/hooks/subagent-stop.js +199 -10
  57. package/dist/hooks/summary-worker.js +604 -334
  58. package/dist/index.js +554 -61
  59. package/dist/lib/cloud-sync.js +5 -0
  60. package/dist/lib/config.js +13 -0
  61. package/dist/lib/consolidation.js +5 -0
  62. package/dist/lib/database.js +104 -0
  63. package/dist/lib/device-registry.js +109 -0
  64. package/dist/lib/embedder.js +13 -0
  65. package/dist/lib/employee-templates.js +53 -26
  66. package/dist/lib/employees.js +5 -0
  67. package/dist/lib/exe-daemon-client.js +5 -0
  68. package/dist/lib/exe-daemon.js +493 -79
  69. package/dist/lib/file-grep.js +20 -4
  70. package/dist/lib/hybrid-search.js +1435 -190
  71. package/dist/lib/identity-templates.js +126 -5
  72. package/dist/lib/identity.js +5 -0
  73. package/dist/lib/license.js +5 -0
  74. package/dist/lib/messaging.js +37 -24
  75. package/dist/lib/schedules.js +130 -1
  76. package/dist/lib/skill-learning.js +11 -0
  77. package/dist/lib/status-brief.js +5 -0
  78. package/dist/lib/store.js +199 -10
  79. package/dist/lib/task-router.js +72 -6
  80. package/dist/lib/tasks.js +179 -50
  81. package/dist/lib/tmux-routing.js +179 -46
  82. package/dist/mcp/server.js +2129 -1855
  83. package/dist/mcp/tools/create-task.js +86 -36
  84. package/dist/mcp/tools/deactivate-behavior.js +5 -0
  85. package/dist/mcp/tools/list-tasks.js +39 -11
  86. package/dist/mcp/tools/send-message.js +37 -24
  87. package/dist/mcp/tools/update-task.js +153 -38
  88. package/dist/runtime/index.js +451 -59
  89. package/dist/tui/App.js +454 -59
  90. package/package.json +1 -1
@@ -307,11 +307,12 @@ function queueIntercom(targetSession, reason) {
307
307
  }
308
308
  writeQueue(queue);
309
309
  }
310
- var QUEUE_PATH, INTERCOM_LOG;
310
+ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
311
311
  var init_intercom_queue = __esm({
312
312
  "src/lib/intercom-queue.ts"() {
313
313
  "use strict";
314
314
  QUEUE_PATH = path2.join(os2.homedir(), ".exe-os", "intercom-queue.json");
315
+ TTL_MS = 60 * 60 * 1e3;
315
316
  INTERCOM_LOG = path2.join(os2.homedir(), ".exe-os", "intercom.log");
316
317
  }
317
318
  });
@@ -398,6 +399,11 @@ function normalizeSessionLifecycle(raw) {
398
399
  const userSL = raw.sessionLifecycle ?? {};
399
400
  raw.sessionLifecycle = { ...defaultSL, ...userSL };
400
401
  }
402
+ function normalizeAutoUpdate(raw) {
403
+ const defaultAU = DEFAULT_CONFIG.autoUpdate;
404
+ const userAU = raw.autoUpdate ?? {};
405
+ raw.autoUpdate = { ...defaultAU, ...userAU };
406
+ }
401
407
  async function loadConfig() {
402
408
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
403
409
  await mkdir(dir, { recursive: true });
@@ -420,6 +426,7 @@ async function loadConfig() {
420
426
  }
421
427
  normalizeScalingRoadmap(migratedCfg);
422
428
  normalizeSessionLifecycle(migratedCfg);
429
+ normalizeAutoUpdate(migratedCfg);
423
430
  const config = { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db"), ...migratedCfg };
424
431
  if (config.dbPath.startsWith("~")) {
425
432
  config.dbPath = config.dbPath.replace(/^~/, os3.homedir());
@@ -495,6 +502,11 @@ var init_config = __esm({
495
502
  idleKillTicksRequired: 3,
496
503
  idleKillIntercomAckWindowMs: 1e4,
497
504
  maxAutoInstances: 10
505
+ },
506
+ autoUpdate: {
507
+ checkOnBoot: true,
508
+ autoInstall: false,
509
+ checkIntervalMs: 24 * 60 * 60 * 1e3
498
510
  }
499
511
  };
500
512
  CONFIG_MIGRATIONS = [
@@ -642,6 +654,17 @@ function getGitRoot(dir) {
642
654
  return null;
643
655
  }
644
656
  }
657
+ function getMainRepoRoot(dir) {
658
+ try {
659
+ const commonDir = execSync4(
660
+ "git rev-parse --path-format=absolute --git-common-dir",
661
+ { cwd: dir, encoding: "utf-8", timeout: GIT_TIMEOUT_MS, stdio: ["pipe", "pipe", "pipe"] }
662
+ ).trim();
663
+ return realpath(path7.dirname(commonDir));
664
+ } catch {
665
+ return null;
666
+ }
667
+ }
645
668
  function worktreePath(repoRoot, employeeName, instance) {
646
669
  const label = instanceLabel(employeeName, instance);
647
670
  return path7.join(repoRoot, ".worktrees", label);
@@ -833,6 +856,36 @@ import path9 from "path";
833
856
  import { execSync as execSync5 } from "child_process";
834
857
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
835
858
  import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
859
+ async function writeCheckpoint(input) {
860
+ const client = getClient();
861
+ const row = await resolveTask(client, input.taskId);
862
+ const taskId = String(row.id);
863
+ const now = (/* @__PURE__ */ new Date()).toISOString();
864
+ const blockedByIds = [];
865
+ if (row.blocked_by) {
866
+ blockedByIds.push(String(row.blocked_by));
867
+ }
868
+ const checkpoint = {
869
+ step: input.step,
870
+ context_summary: input.contextSummary,
871
+ files_touched: input.filesTouched ?? [],
872
+ blocked_by_ids: blockedByIds,
873
+ last_checkpoint_at: now
874
+ };
875
+ const result = await client.execute({
876
+ sql: `UPDATE tasks SET checkpoint = ?, checkpoint_count = checkpoint_count + 1, updated_at = ? WHERE id = ?`,
877
+ args: [JSON.stringify(checkpoint), now, taskId]
878
+ });
879
+ if (result.rowsAffected === 0) {
880
+ throw new Error(`Checkpoint write failed: task ${taskId} not found`);
881
+ }
882
+ const countResult = await client.execute({
883
+ sql: "SELECT checkpoint_count FROM tasks WHERE id = ?",
884
+ args: [taskId]
885
+ });
886
+ const checkpointCount = Number(countResult.rows[0]?.checkpoint_count ?? 1);
887
+ return { checkpointCount };
888
+ }
836
889
  function extractParentFromContext(contextBody) {
837
890
  if (!contextBody) return null;
838
891
  const match = contextBody.match(
@@ -939,9 +992,10 @@ async function createTaskCore(input) {
939
992
  } catch {
940
993
  }
941
994
  }
995
+ const complexity = input.complexity ?? "standard";
942
996
  await client.execute({
943
- sql: `INSERT INTO tasks (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, blocked_by, parent_task_id, reviewer, context, created_at, updated_at)
944
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
997
+ sql: `INSERT INTO tasks (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, blocked_by, parent_task_id, reviewer, context, complexity, budget_tokens, budget_fallback_model, tokens_used, tokens_warned_at, created_at, updated_at)
998
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
945
999
  args: [
946
1000
  id,
947
1001
  input.title,
@@ -955,6 +1009,11 @@ async function createTaskCore(input) {
955
1009
  parentTaskId,
956
1010
  input.reviewer ?? null,
957
1011
  input.context,
1012
+ input.budgetTokens ?? null,
1013
+ input.budgetFallbackModel ?? null,
1014
+ 0,
1015
+ null,
1016
+ complexity,
958
1017
  now,
959
1018
  now
960
1019
  ]
@@ -970,7 +1029,11 @@ async function createTaskCore(input) {
970
1029
  taskFile,
971
1030
  createdAt: now,
972
1031
  updatedAt: now,
973
- warning
1032
+ warning,
1033
+ budgetTokens: input.budgetTokens ?? null,
1034
+ budgetFallbackModel: input.budgetFallbackModel ?? null,
1035
+ tokensUsed: 0,
1036
+ tokensWarnedAt: null
974
1037
  };
975
1038
  }
976
1039
  async function listTasks(input) {
@@ -1010,7 +1073,12 @@ async function listTasks(input) {
1010
1073
  status: String(r.status),
1011
1074
  taskFile: String(r.task_file),
1012
1075
  createdAt: String(r.created_at),
1013
- updatedAt: String(r.updated_at)
1076
+ updatedAt: String(r.updated_at),
1077
+ checkpointCount: Number(r.checkpoint_count ?? 0),
1078
+ budgetTokens: r.budget_tokens !== null ? Number(r.budget_tokens) : null,
1079
+ budgetFallbackModel: r.budget_fallback_model !== null ? String(r.budget_fallback_model) : null,
1080
+ tokensUsed: Number(r.tokens_used ?? 0),
1081
+ tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
1014
1082
  }));
1015
1083
  }
1016
1084
  function checkStaleCompletion(taskContext, taskCreatedAt) {
@@ -1018,8 +1086,13 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
1018
1086
  if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
1019
1087
  try {
1020
1088
  const since = new Date(taskCreatedAt).toISOString();
1089
+ const branch = execSync5(
1090
+ "git rev-parse --abbrev-ref HEAD 2>/dev/null",
1091
+ { encoding: "utf8", timeout: 3e3 }
1092
+ ).trim();
1093
+ const branchArg = branch && branch !== "HEAD" ? branch : "";
1021
1094
  const commitCount = execSync5(
1022
- `git log --oneline --since="${since}" 2>/dev/null | wc -l`,
1095
+ `git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
1023
1096
  { encoding: "utf8", timeout: 5e3 }
1024
1097
  ).trim();
1025
1098
  const count = parseInt(commitCount, 10);
@@ -1078,6 +1151,14 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
1078
1151
  const claimedBy = cur?.assigned_tmux ? ` (claimed by ${cur.assigned_tmux})` : "";
1079
1152
  throw new Error(`${TASK_ALREADY_CLAIMED_PREFIX}: task ${taskId} is ${status}${claimedBy}`);
1080
1153
  }
1154
+ try {
1155
+ await writeCheckpoint({
1156
+ taskId,
1157
+ step: "claimed",
1158
+ contextSummary: `Task claimed by session. Transitioning open \u2192 in_progress.`
1159
+ });
1160
+ } catch {
1161
+ }
1081
1162
  return { row, taskFile, now, taskId };
1082
1163
  }
1083
1164
  if (input.result) {
@@ -1091,6 +1172,14 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
1091
1172
  args: [input.status, now, taskId]
1092
1173
  });
1093
1174
  }
1175
+ try {
1176
+ await writeCheckpoint({
1177
+ taskId,
1178
+ step: `status_transition:${input.status}`,
1179
+ contextSummary: input.result ? `Transitioned to ${input.status}. Result: ${input.result.slice(0, 500)}` : `Transitioned to ${input.status}.`
1180
+ });
1181
+ } catch {
1182
+ }
1094
1183
  return { row, taskFile, now, taskId };
1095
1184
  }
1096
1185
  async function deleteTaskCore(taskId, _baseDir) {
@@ -1244,23 +1333,38 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
1244
1333
  if (String(row.assigned_by) !== "system" || !taskFile.includes("review-")) return;
1245
1334
  try {
1246
1335
  const client = getClient();
1247
- const fileName = taskFile.split("/").pop() ?? "";
1248
- const reviewPrefix = fileName.replace(".md", "");
1249
- const parts = reviewPrefix.split("-");
1250
- if (parts.length >= 3 && parts[0] === "review") {
1251
- const agent = parts[1];
1252
- const slug = parts.slice(2).join("-");
1253
- const originalTaskFile = `exe/${agent}/${slug}.md`;
1336
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1337
+ const parentId = row.parent_task_id ? String(row.parent_task_id) : null;
1338
+ if (parentId) {
1254
1339
  const result = await client.execute({
1255
- sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
1256
- args: [(/* @__PURE__ */ new Date()).toISOString(), originalTaskFile]
1340
+ sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE id = ? AND status = 'needs_review'",
1341
+ args: [now, parentId]
1257
1342
  });
1258
1343
  if (result.rowsAffected > 0) {
1259
1344
  process.stderr.write(
1260
- `[review-cleanup] Cascaded original task to done: ${originalTaskFile}
1345
+ `[review-cleanup] Cascaded original task to done via parent_task_id: ${parentId}
1261
1346
  `
1262
1347
  );
1263
1348
  }
1349
+ } else {
1350
+ const fileName = taskFile.split("/").pop() ?? "";
1351
+ const reviewPrefix = fileName.replace(".md", "");
1352
+ const parts = reviewPrefix.split("-");
1353
+ if (parts.length >= 3 && parts[0] === "review") {
1354
+ const agent = parts[1];
1355
+ const slug = parts.slice(2).join("-");
1356
+ const originalTaskFile = `exe/${agent}/${slug}.md`;
1357
+ const result = await client.execute({
1358
+ sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
1359
+ args: [now, originalTaskFile]
1360
+ });
1361
+ if (result.rowsAffected > 0) {
1362
+ process.stderr.write(
1363
+ `[review-cleanup] Cascaded original task to done (legacy path): ${originalTaskFile}
1364
+ `
1365
+ );
1366
+ }
1367
+ }
1264
1368
  }
1265
1369
  } catch (err) {
1266
1370
  process.stderr.write(
@@ -1381,12 +1485,23 @@ function getProjectName(cwd) {
1381
1485
  const dir = cwd ?? process.cwd();
1382
1486
  if (_cached2 && _cachedCwd === dir) return _cached2;
1383
1487
  try {
1384
- const repoRoot = execSync6("git rev-parse --show-toplevel", {
1385
- cwd: dir,
1386
- encoding: "utf8",
1387
- timeout: 2e3,
1388
- stdio: ["pipe", "pipe", "pipe"]
1389
- }).trim();
1488
+ let repoRoot;
1489
+ try {
1490
+ const gitCommonDir = execSync6("git rev-parse --path-format=absolute --git-common-dir", {
1491
+ cwd: dir,
1492
+ encoding: "utf8",
1493
+ timeout: 2e3,
1494
+ stdio: ["pipe", "pipe", "pipe"]
1495
+ }).trim();
1496
+ repoRoot = path12.dirname(gitCommonDir);
1497
+ } catch {
1498
+ repoRoot = execSync6("git rev-parse --show-toplevel", {
1499
+ cwd: dir,
1500
+ encoding: "utf8",
1501
+ timeout: 2e3,
1502
+ stdio: ["pipe", "pipe", "pipe"]
1503
+ }).trim();
1504
+ }
1390
1505
  _cached2 = path12.basename(repoRoot);
1391
1506
  _cachedCwd = dir;
1392
1507
  return _cached2;
@@ -1492,7 +1607,9 @@ async function dispatchTaskToEmployee(input) {
1492
1607
  return { dispatched, session: sessionName, crossProject };
1493
1608
  } else {
1494
1609
  const projectDir = input.projectDir ?? process.cwd();
1495
- const result = ensureEmployee(input.assignedTo, exeSession, projectDir);
1610
+ const result = ensureEmployee(input.assignedTo, exeSession, projectDir, {
1611
+ autoInstance: input.assignedTo === "tom" || input.assignedTo === "sasha"
1612
+ });
1496
1613
  if (result.status === "failed") {
1497
1614
  process.stderr.write(
1498
1615
  `[dispatch] Failed to spawn ${input.assignedTo}: ${result.error}
@@ -1856,7 +1973,8 @@ __export(tasks_exports, {
1856
1973
  resolveTask: () => resolveTask,
1857
1974
  slugify: () => slugify,
1858
1975
  updateTask: () => updateTask,
1859
- updateTaskStatus: () => updateTaskStatus
1976
+ updateTaskStatus: () => updateTaskStatus,
1977
+ writeCheckpoint: () => writeCheckpoint
1860
1978
  });
1861
1979
  import path13 from "path";
1862
1980
  import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, unlinkSync as unlinkSync3 } from "fs";
@@ -1898,10 +2016,11 @@ async function updateTask(input) {
1898
2016
  try {
1899
2017
  const client = getClient();
1900
2018
  const taskTitle = String(row.title);
2019
+ const escaped = taskTitle.replace(/%/g, "\\%").replace(/_/g, "\\_");
1901
2020
  await client.execute({
1902
2021
  sql: `UPDATE tasks SET status = 'cancelled', updated_at = ?
1903
- WHERE title LIKE ? AND status IN ('open', 'in_progress')`,
1904
- args: [now, `%left%${taskTitle}%in_progress`]
2022
+ WHERE title LIKE ? ESCAPE '\\' AND status IN ('open', 'in_progress')`,
2023
+ args: [now, `%left '${escaped}' as in\\_progress%`]
1905
2024
  });
1906
2025
  } catch {
1907
2026
  }
@@ -1959,6 +2078,10 @@ async function updateTask(input) {
1959
2078
  taskFile,
1960
2079
  createdAt: String(row.created_at),
1961
2080
  updatedAt: now,
2081
+ budgetTokens: row.budget_tokens !== void 0 && row.budget_tokens !== null ? Number(row.budget_tokens) : null,
2082
+ budgetFallbackModel: row.budget_fallback_model !== void 0 && row.budget_fallback_model !== null ? String(row.budget_fallback_model) : null,
2083
+ tokensUsed: Number(row.tokens_used ?? 0),
2084
+ tokensWarnedAt: row.tokens_warned_at !== void 0 && row.tokens_warned_at !== null ? Number(row.tokens_warned_at) : null,
1962
2085
  nextTask
1963
2086
  };
1964
2087
  }
@@ -2451,6 +2574,11 @@ function getSessionState(sessionName) {
2451
2574
  if (!transport.isAlive(sessionName)) return "offline";
2452
2575
  try {
2453
2576
  const pane = transport.capturePane(sessionName, 5);
2577
+ if (!pane.includes("\u276F") && !pane.includes("Claude Code") && !BUSY_PATTERN.test(pane) && !/Running…/.test(pane)) {
2578
+ if (/\$\s*$/.test(pane) || /% $/.test(pane.trimEnd())) {
2579
+ return "no_claude";
2580
+ }
2581
+ }
2454
2582
  if (/Running…/.test(pane)) return "tool";
2455
2583
  if (BUSY_PATTERN.test(pane)) return "thinking";
2456
2584
  return "idle";
@@ -2481,7 +2609,14 @@ function sendIntercom(targetSession) {
2481
2609
  logIntercom(`SKIP \u2192 ${targetSession} (session not found)`);
2482
2610
  return "failed";
2483
2611
  }
2484
- if (isSessionBusy(targetSession)) {
2612
+ const sessionState = getSessionState(targetSession);
2613
+ if (sessionState === "no_claude") {
2614
+ queueIntercom(targetSession, "claude not running in session");
2615
+ recordDebounce(targetSession);
2616
+ logIntercom(`QUEUED \u2192 ${targetSession} (no claude process \u2014 raw shell detected)`);
2617
+ return "queued";
2618
+ }
2619
+ if (sessionState === "thinking" || sessionState === "tool") {
2485
2620
  queueIntercom(targetSession, "session busy at send time");
2486
2621
  recordDebounce(targetSession);
2487
2622
  logIntercom(`QUEUED \u2192 ${targetSession} (session busy, will retry from queue)`);
@@ -2493,18 +2628,7 @@ function sendIntercom(targetSession) {
2493
2628
  }
2494
2629
  transport.sendKeys(targetSession, "/exe-intercom");
2495
2630
  recordDebounce(targetSession);
2496
- for (let i = 0; i < INTERCOM_POLL_MAX_ATTEMPTS; i++) {
2497
- try {
2498
- execSync7(`sleep ${INTERCOM_POLL_INTERVAL_S}`);
2499
- } catch {
2500
- }
2501
- const state = getSessionState(targetSession);
2502
- if (state === "thinking" || state === "tool") {
2503
- logIntercom(`ACKNOWLEDGED \u2192 ${targetSession} (state=${state}, poll=${i + 1})`);
2504
- return "acknowledged";
2505
- }
2506
- }
2507
- logIntercom(`DELIVERED \u2192 ${targetSession} (no state transition after ${INTERCOM_POLL_MAX_ATTEMPTS}s)`);
2631
+ logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
2508
2632
  return "delivered";
2509
2633
  } catch {
2510
2634
  logIntercom(`FAIL \u2192 ${targetSession}`);
@@ -2521,7 +2645,17 @@ function notifyParentExe(sessionKey) {
2521
2645
  process.stderr.write(`[intercom] notifyParentExe \u2192 ${target}
2522
2646
  `);
2523
2647
  const result = sendIntercom(target);
2524
- return result !== "failed";
2648
+ if (result === "failed") {
2649
+ const rootExe = resolveExeSession();
2650
+ if (rootExe && rootExe !== target) {
2651
+ process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root exe ${rootExe}
2652
+ `);
2653
+ const fallback = sendIntercom(rootExe);
2654
+ return fallback !== "failed";
2655
+ }
2656
+ return false;
2657
+ }
2658
+ return true;
2525
2659
  }
2526
2660
  function ensureEmployee(employeeName, exeSession, projectDir, opts) {
2527
2661
  if (employeeName === "exe") {
@@ -2570,7 +2704,8 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
2570
2704
  return { status: "failed", sessionName, error: "intercom delivery failed" };
2571
2705
  }
2572
2706
  const spawnOpts = { ...opts, instance: effectiveInstance };
2573
- const wtPath = ensureWorktree(projectDir, employeeName, effectiveInstance);
2707
+ const mainRoot = getMainRepoRoot(projectDir) ?? projectDir;
2708
+ const wtPath = ensureWorktree(mainRoot, employeeName, effectiveInstance);
2574
2709
  if (wtPath) {
2575
2710
  spawnOpts.cwd = wtPath;
2576
2711
  }
@@ -2751,7 +2886,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
2751
2886
  let booted = false;
2752
2887
  for (let i = 0; i < 30; i++) {
2753
2888
  try {
2754
- execSync7("sleep 1");
2889
+ execSync7("sleep 0.5");
2755
2890
  } catch {
2756
2891
  }
2757
2892
  try {
@@ -2771,7 +2906,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
2771
2906
  }
2772
2907
  }
2773
2908
  if (!booted) {
2774
- return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 30s` };
2909
+ return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
2775
2910
  }
2776
2911
  if (!useExeAgent) {
2777
2912
  try {
@@ -2789,7 +2924,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
2789
2924
  });
2790
2925
  return { sessionName };
2791
2926
  }
2792
- var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN, INTERCOM_POLL_INTERVAL_S, INTERCOM_POLL_MAX_ATTEMPTS;
2927
+ var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
2793
2928
  var init_tmux_routing = __esm({
2794
2929
  "src/lib/tmux-routing.ts"() {
2795
2930
  init_session_registry();
@@ -2809,8 +2944,6 @@ var init_tmux_routing = __esm({
2809
2944
  DEBOUNCE_FILE = path14.join(SESSION_CACHE, "intercom-debounce.json");
2810
2945
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
2811
2946
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
2812
- INTERCOM_POLL_INTERVAL_S = 1;
2813
- INTERCOM_POLL_MAX_ATTEMPTS = 8;
2814
2947
  }
2815
2948
  });
2816
2949
  init_tmux_routing();