@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
@@ -116,6 +116,11 @@ function normalizeSessionLifecycle(raw) {
116
116
  const userSL = raw.sessionLifecycle ?? {};
117
117
  raw.sessionLifecycle = { ...defaultSL, ...userSL };
118
118
  }
119
+ function normalizeAutoUpdate(raw) {
120
+ const defaultAU = DEFAULT_CONFIG.autoUpdate;
121
+ const userAU = raw.autoUpdate ?? {};
122
+ raw.autoUpdate = { ...defaultAU, ...userAU };
123
+ }
119
124
  async function loadConfig() {
120
125
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
121
126
  await mkdir(dir, { recursive: true });
@@ -138,6 +143,7 @@ async function loadConfig() {
138
143
  }
139
144
  normalizeScalingRoadmap(migratedCfg);
140
145
  normalizeSessionLifecycle(migratedCfg);
146
+ normalizeAutoUpdate(migratedCfg);
141
147
  const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
142
148
  if (config.dbPath.startsWith("~")) {
143
149
  config.dbPath = config.dbPath.replace(/^~/, os.homedir());
@@ -160,6 +166,7 @@ function loadConfigSync() {
160
166
  const { config: migratedCfg } = migrateConfig(parsed);
161
167
  normalizeScalingRoadmap(migratedCfg);
162
168
  normalizeSessionLifecycle(migratedCfg);
169
+ normalizeAutoUpdate(migratedCfg);
163
170
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
164
171
  } catch {
165
172
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
@@ -179,6 +186,7 @@ async function loadConfigFrom(configPath) {
179
186
  const { config: migratedCfg } = migrateConfig(parsed);
180
187
  normalizeScalingRoadmap(migratedCfg);
181
188
  normalizeSessionLifecycle(migratedCfg);
189
+ normalizeAutoUpdate(migratedCfg);
182
190
  return { ...DEFAULT_CONFIG, ...migratedCfg };
183
191
  } catch {
184
192
  return { ...DEFAULT_CONFIG };
@@ -250,6 +258,11 @@ var init_config = __esm({
250
258
  idleKillTicksRequired: 3,
251
259
  idleKillIntercomAckWindowMs: 1e4,
252
260
  maxAutoInstances: 10
261
+ },
262
+ autoUpdate: {
263
+ checkOnBoot: true,
264
+ autoInstall: false,
265
+ checkIntervalMs: 24 * 60 * 60 * 1e3
253
266
  }
254
267
  };
255
268
  CONFIG_MIGRATIONS = [
@@ -395,9 +408,10 @@ async function createTaskCore(input) {
395
408
  } catch {
396
409
  }
397
410
  }
411
+ const complexity = input.complexity ?? "standard";
398
412
  await client.execute({
399
- 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)
400
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
413
+ 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)
414
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
401
415
  args: [
402
416
  id,
403
417
  input.title,
@@ -411,6 +425,11 @@ async function createTaskCore(input) {
411
425
  parentTaskId,
412
426
  input.reviewer ?? null,
413
427
  input.context,
428
+ input.budgetTokens ?? null,
429
+ input.budgetFallbackModel ?? null,
430
+ 0,
431
+ null,
432
+ complexity,
414
433
  now,
415
434
  now
416
435
  ]
@@ -426,7 +445,11 @@ async function createTaskCore(input) {
426
445
  taskFile,
427
446
  createdAt: now,
428
447
  updatedAt: now,
429
- warning
448
+ warning,
449
+ budgetTokens: input.budgetTokens ?? null,
450
+ budgetFallbackModel: input.budgetFallbackModel ?? null,
451
+ tokensUsed: 0,
452
+ tokensWarnedAt: null
430
453
  };
431
454
  }
432
455
  async function ensureArchitectureDoc(baseDir, projectName) {
@@ -786,11 +809,12 @@ function queueIntercom(targetSession, reason) {
786
809
  }
787
810
  writeQueue(queue);
788
811
  }
789
- var QUEUE_PATH, INTERCOM_LOG;
812
+ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
790
813
  var init_intercom_queue = __esm({
791
814
  "src/lib/intercom-queue.ts"() {
792
815
  "use strict";
793
816
  QUEUE_PATH = path6.join(os4.homedir(), ".exe-os", "intercom-queue.json");
817
+ TTL_MS = 60 * 60 * 1e3;
794
818
  INTERCOM_LOG = path6.join(os4.homedir(), ".exe-os", "intercom.log");
795
819
  }
796
820
  });
@@ -913,6 +937,17 @@ function getGitRoot(dir) {
913
937
  return null;
914
938
  }
915
939
  }
940
+ function getMainRepoRoot(dir) {
941
+ try {
942
+ const commonDir = execSync5(
943
+ "git rev-parse --path-format=absolute --git-common-dir",
944
+ { cwd: dir, encoding: "utf-8", timeout: GIT_TIMEOUT_MS, stdio: ["pipe", "pipe", "pipe"] }
945
+ ).trim();
946
+ return realpath(path9.dirname(commonDir));
947
+ } catch {
948
+ return null;
949
+ }
950
+ }
916
951
  function worktreePath(repoRoot, employeeName, instance) {
917
952
  const label = instanceLabel(employeeName, instance);
918
953
  return path9.join(repoRoot, ".worktrees", label);
@@ -1138,6 +1173,11 @@ function getSessionState(sessionName) {
1138
1173
  if (!transport.isAlive(sessionName)) return "offline";
1139
1174
  try {
1140
1175
  const pane = transport.capturePane(sessionName, 5);
1176
+ if (!pane.includes("\u276F") && !pane.includes("Claude Code") && !BUSY_PATTERN.test(pane) && !/Running…/.test(pane)) {
1177
+ if (/\$\s*$/.test(pane) || /% $/.test(pane.trimEnd())) {
1178
+ return "no_claude";
1179
+ }
1180
+ }
1141
1181
  if (/Running…/.test(pane)) return "tool";
1142
1182
  if (BUSY_PATTERN.test(pane)) return "thinking";
1143
1183
  return "idle";
@@ -1145,10 +1185,6 @@ function getSessionState(sessionName) {
1145
1185
  return "offline";
1146
1186
  }
1147
1187
  }
1148
- function isSessionBusy(sessionName) {
1149
- const state = getSessionState(sessionName);
1150
- return state === "thinking" || state === "tool";
1151
- }
1152
1188
  function isExeSession(sessionName) {
1153
1189
  return /^exe\d*$/.test(sessionName);
1154
1190
  }
@@ -1168,7 +1204,14 @@ function sendIntercom(targetSession) {
1168
1204
  logIntercom(`SKIP \u2192 ${targetSession} (session not found)`);
1169
1205
  return "failed";
1170
1206
  }
1171
- if (isSessionBusy(targetSession)) {
1207
+ const sessionState = getSessionState(targetSession);
1208
+ if (sessionState === "no_claude") {
1209
+ queueIntercom(targetSession, "claude not running in session");
1210
+ recordDebounce(targetSession);
1211
+ logIntercom(`QUEUED \u2192 ${targetSession} (no claude process \u2014 raw shell detected)`);
1212
+ return "queued";
1213
+ }
1214
+ if (sessionState === "thinking" || sessionState === "tool") {
1172
1215
  queueIntercom(targetSession, "session busy at send time");
1173
1216
  recordDebounce(targetSession);
1174
1217
  logIntercom(`QUEUED \u2192 ${targetSession} (session busy, will retry from queue)`);
@@ -1180,18 +1223,7 @@ function sendIntercom(targetSession) {
1180
1223
  }
1181
1224
  transport.sendKeys(targetSession, "/exe-intercom");
1182
1225
  recordDebounce(targetSession);
1183
- for (let i = 0; i < INTERCOM_POLL_MAX_ATTEMPTS; i++) {
1184
- try {
1185
- execSync6(`sleep ${INTERCOM_POLL_INTERVAL_S}`);
1186
- } catch {
1187
- }
1188
- const state = getSessionState(targetSession);
1189
- if (state === "thinking" || state === "tool") {
1190
- logIntercom(`ACKNOWLEDGED \u2192 ${targetSession} (state=${state}, poll=${i + 1})`);
1191
- return "acknowledged";
1192
- }
1193
- }
1194
- logIntercom(`DELIVERED \u2192 ${targetSession} (no state transition after ${INTERCOM_POLL_MAX_ATTEMPTS}s)`);
1226
+ logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
1195
1227
  return "delivered";
1196
1228
  } catch {
1197
1229
  logIntercom(`FAIL \u2192 ${targetSession}`);
@@ -1245,7 +1277,8 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
1245
1277
  return { status: "failed", sessionName, error: "intercom delivery failed" };
1246
1278
  }
1247
1279
  const spawnOpts = { ...opts, instance: effectiveInstance };
1248
- const wtPath = ensureWorktree(projectDir, employeeName, effectiveInstance);
1280
+ const mainRoot = getMainRepoRoot(projectDir) ?? projectDir;
1281
+ const wtPath = ensureWorktree(mainRoot, employeeName, effectiveInstance);
1249
1282
  if (wtPath) {
1250
1283
  spawnOpts.cwd = wtPath;
1251
1284
  }
@@ -1426,7 +1459,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
1426
1459
  let booted = false;
1427
1460
  for (let i = 0; i < 30; i++) {
1428
1461
  try {
1429
- execSync6("sleep 1");
1462
+ execSync6("sleep 0.5");
1430
1463
  } catch {
1431
1464
  }
1432
1465
  try {
@@ -1446,7 +1479,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
1446
1479
  }
1447
1480
  }
1448
1481
  if (!booted) {
1449
- return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 30s` };
1482
+ return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
1450
1483
  }
1451
1484
  if (!useExeAgent) {
1452
1485
  try {
@@ -1464,7 +1497,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
1464
1497
  });
1465
1498
  return { sessionName };
1466
1499
  }
1467
- var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN, INTERCOM_POLL_INTERVAL_S, INTERCOM_POLL_MAX_ATTEMPTS;
1500
+ var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
1468
1501
  var init_tmux_routing = __esm({
1469
1502
  "src/lib/tmux-routing.ts"() {
1470
1503
  "use strict";
@@ -1484,8 +1517,6 @@ var init_tmux_routing = __esm({
1484
1517
  DEBOUNCE_FILE = path10.join(SESSION_CACHE, "intercom-debounce.json");
1485
1518
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
1486
1519
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
1487
- INTERCOM_POLL_INTERVAL_S = 1;
1488
- INTERCOM_POLL_MAX_ATTEMPTS = 8;
1489
1520
  }
1490
1521
  });
1491
1522
 
@@ -1523,12 +1554,23 @@ function getProjectName(cwd) {
1523
1554
  const dir = cwd ?? process.cwd();
1524
1555
  if (_cached2 && _cachedCwd === dir) return _cached2;
1525
1556
  try {
1526
- const repoRoot = execSync7("git rev-parse --show-toplevel", {
1527
- cwd: dir,
1528
- encoding: "utf8",
1529
- timeout: 2e3,
1530
- stdio: ["pipe", "pipe", "pipe"]
1531
- }).trim();
1557
+ let repoRoot;
1558
+ try {
1559
+ const gitCommonDir = execSync7("git rev-parse --path-format=absolute --git-common-dir", {
1560
+ cwd: dir,
1561
+ encoding: "utf8",
1562
+ timeout: 2e3,
1563
+ stdio: ["pipe", "pipe", "pipe"]
1564
+ }).trim();
1565
+ repoRoot = path13.dirname(gitCommonDir);
1566
+ } catch {
1567
+ repoRoot = execSync7("git rev-parse --show-toplevel", {
1568
+ cwd: dir,
1569
+ encoding: "utf8",
1570
+ timeout: 2e3,
1571
+ stdio: ["pipe", "pipe", "pipe"]
1572
+ }).trim();
1573
+ }
1532
1574
  _cached2 = path13.basename(repoRoot);
1533
1575
  _cachedCwd = dir;
1534
1576
  return _cached2;
@@ -1634,7 +1676,9 @@ async function dispatchTaskToEmployee(input) {
1634
1676
  return { dispatched, session: sessionName, crossProject };
1635
1677
  } else {
1636
1678
  const projectDir = input.projectDir ?? process.cwd();
1637
- const result = ensureEmployee(input.assignedTo, exeSession, projectDir);
1679
+ const result = ensureEmployee(input.assignedTo, exeSession, projectDir, {
1680
+ autoInstance: input.assignedTo === "tom" || input.assignedTo === "sasha"
1681
+ });
1638
1682
  if (result.status === "failed") {
1639
1683
  process.stderr.write(
1640
1684
  `[dispatch] Failed to spawn ${input.assignedTo}: ${result.error}
@@ -1770,13 +1814,16 @@ function registerCreateTask(server) {
1770
1814
  assigned_to: z.string().describe("Employee name to assign to"),
1771
1815
  project_name: z.string().describe("Project name"),
1772
1816
  priority: z.enum(["p0", "p1", "p2"]).default("p1").describe("Priority level"),
1817
+ complexity: z.enum(["routine", "standard", "complex", "critical"]).default("standard").describe("Bloom taxonomy complexity level. Affects routing tier and review requirements."),
1773
1818
  context: z.string().describe("Full task description and context"),
1774
1819
  blocked_by: z.string().optional().describe("Task ID or slug of the blocking task. If set, task starts as 'blocked'."),
1775
1820
  parent_task_id: z.string().optional().describe("Parent task ID or slug. Links this task as a subtask."),
1776
- reviewer: z.string().optional().describe("Who should review this task when done. Defaults to assigner.")
1821
+ reviewer: z.string().optional().describe("Who should review this task when done. Defaults to assigner."),
1822
+ budget_tokens: z.number().optional().describe("Max tokens allowed for this task (null = unlimited)"),
1823
+ budget_fallback_model: z.string().optional().describe("Model to route to when budget is exhausted")
1777
1824
  }
1778
1825
  },
1779
- async ({ title, assigned_to, project_name, priority, context, blocked_by, parent_task_id, reviewer }) => {
1826
+ async ({ title, assigned_to, project_name, priority, complexity, context, blocked_by, parent_task_id, reviewer, budget_tokens, budget_fallback_model }) => {
1780
1827
  const { agentId: assignedBy } = getActiveAgent();
1781
1828
  const task = await createTask({
1782
1829
  title,
@@ -1784,11 +1831,14 @@ function registerCreateTask(server) {
1784
1831
  assignedBy,
1785
1832
  projectName: project_name,
1786
1833
  priority,
1834
+ complexity,
1787
1835
  context,
1788
1836
  baseDir: process.cwd(),
1789
1837
  blockedBy: blocked_by,
1790
1838
  parentTaskId: parent_task_id,
1791
1839
  reviewer,
1840
+ budgetTokens: budget_tokens,
1841
+ budgetFallbackModel: budget_fallback_model,
1792
1842
  // Skip internal dispatch — we handle it below with autoInstance
1793
1843
  // support. Without this, createTask fires dispatchTaskToEmployee
1794
1844
  // (no autoInstance) in parallel, racing with our ensureEmployee
@@ -112,6 +112,11 @@ var DEFAULT_CONFIG = {
112
112
  idleKillTicksRequired: 3,
113
113
  idleKillIntercomAckWindowMs: 1e4,
114
114
  maxAutoInstances: 10
115
+ },
116
+ autoUpdate: {
117
+ checkOnBoot: true,
118
+ autoInstall: false,
119
+ checkIntervalMs: 24 * 60 * 60 * 1e3
115
120
  }
116
121
  };
117
122
 
@@ -106,6 +106,11 @@ var init_config = __esm({
106
106
  idleKillTicksRequired: 3,
107
107
  idleKillIntercomAckWindowMs: 1e4,
108
108
  maxAutoInstances: 10
109
+ },
110
+ autoUpdate: {
111
+ checkOnBoot: true,
112
+ autoInstall: false,
113
+ checkIntervalMs: 24 * 60 * 60 * 1e3
109
114
  }
110
115
  };
111
116
  }
@@ -172,7 +177,12 @@ async function listTasks(input) {
172
177
  status: String(r.status),
173
178
  taskFile: String(r.task_file),
174
179
  createdAt: String(r.created_at),
175
- updatedAt: String(r.updated_at)
180
+ updatedAt: String(r.updated_at),
181
+ checkpointCount: Number(r.checkpoint_count ?? 0),
182
+ budgetTokens: r.budget_tokens !== null ? Number(r.budget_tokens) : null,
183
+ budgetFallbackModel: r.budget_fallback_model !== null ? String(r.budget_fallback_model) : null,
184
+ tokensUsed: Number(r.tokens_used ?? 0),
185
+ tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
176
186
  }));
177
187
  }
178
188
  var init_tasks_crud = __esm({
@@ -255,11 +265,12 @@ var init_provider_table = __esm({
255
265
  import { readFileSync as readFileSync4, writeFileSync, renameSync as renameSync2, existsSync as existsSync5, mkdirSync } from "fs";
256
266
  import path6 from "path";
257
267
  import os4 from "os";
258
- var QUEUE_PATH, INTERCOM_LOG;
268
+ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
259
269
  var init_intercom_queue = __esm({
260
270
  "src/lib/intercom-queue.ts"() {
261
271
  "use strict";
262
272
  QUEUE_PATH = path6.join(os4.homedir(), ".exe-os", "intercom-queue.json");
273
+ TTL_MS = 60 * 60 * 1e3;
263
274
  INTERCOM_LOG = path6.join(os4.homedir(), ".exe-os", "intercom.log");
264
275
  }
265
276
  });
@@ -389,12 +400,23 @@ function getProjectName(cwd) {
389
400
  const dir = cwd ?? process.cwd();
390
401
  if (_cached && _cachedCwd === dir) return _cached;
391
402
  try {
392
- const repoRoot = execSync5("git rev-parse --show-toplevel", {
393
- cwd: dir,
394
- encoding: "utf8",
395
- timeout: 2e3,
396
- stdio: ["pipe", "pipe", "pipe"]
397
- }).trim();
403
+ let repoRoot;
404
+ try {
405
+ const gitCommonDir = execSync5("git rev-parse --path-format=absolute --git-common-dir", {
406
+ cwd: dir,
407
+ encoding: "utf8",
408
+ timeout: 2e3,
409
+ stdio: ["pipe", "pipe", "pipe"]
410
+ }).trim();
411
+ repoRoot = path13.dirname(gitCommonDir);
412
+ } catch {
413
+ repoRoot = execSync5("git rev-parse --show-toplevel", {
414
+ cwd: dir,
415
+ encoding: "utf8",
416
+ timeout: 2e3,
417
+ stdio: ["pipe", "pipe", "pipe"]
418
+ }).trim();
419
+ }
398
420
  _cached = path13.basename(repoRoot);
399
421
  _cachedCwd = dir;
400
422
  return _cached;
@@ -443,9 +465,15 @@ function registerListTasks(server) {
443
465
  content: [{ type: "text", text: "No tasks found." }]
444
466
  };
445
467
  }
446
- const lines = tasks.map(
447
- (t) => `- [${t.priority.toUpperCase()}] ${t.title} (${t.projectName}) \u2014 ${t.status} \u2192 ${t.assignedTo}`
448
- );
468
+ const lines = tasks.map((t) => {
469
+ const cpIndicator = t.checkpointCount && t.checkpointCount > 0 ? ` [cp:${t.checkpointCount}]` : "";
470
+ let budgetNote = "";
471
+ if (t.budgetTokens !== null) {
472
+ const pct = Math.round(t.tokensUsed / t.budgetTokens * 100);
473
+ budgetNote = ` [${t.tokensUsed}/${t.budgetTokens} tokens, ${pct}%]`;
474
+ }
475
+ return `- [${t.priority.toUpperCase()}] ${t.title} (${t.projectName}) \u2014 ${t.status}${cpIndicator}${budgetNote} \u2192 ${t.assignedTo}`;
476
+ });
449
477
  return {
450
478
  content: [
451
479
  {
@@ -323,11 +323,12 @@ function queueIntercom(targetSession, reason) {
323
323
  }
324
324
  writeQueue(queue);
325
325
  }
326
- var QUEUE_PATH, INTERCOM_LOG;
326
+ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
327
327
  var init_intercom_queue = __esm({
328
328
  "src/lib/intercom-queue.ts"() {
329
329
  "use strict";
330
330
  QUEUE_PATH = path2.join(os2.homedir(), ".exe-os", "intercom-queue.json");
331
+ TTL_MS = 60 * 60 * 1e3;
331
332
  INTERCOM_LOG = path2.join(os2.homedir(), ".exe-os", "intercom.log");
332
333
  }
333
334
  });
@@ -419,6 +420,11 @@ var init_config = __esm({
419
420
  idleKillTicksRequired: 3,
420
421
  idleKillIntercomAckWindowMs: 1e4,
421
422
  maxAutoInstances: 10
423
+ },
424
+ autoUpdate: {
425
+ checkOnBoot: true,
426
+ autoInstall: false,
427
+ checkIntervalMs: 24 * 60 * 60 * 1e3
422
428
  }
423
429
  };
424
430
  }
@@ -556,6 +562,17 @@ function getGitRoot(dir) {
556
562
  return null;
557
563
  }
558
564
  }
565
+ function getMainRepoRoot(dir) {
566
+ try {
567
+ const commonDir = execSync4(
568
+ "git rev-parse --path-format=absolute --git-common-dir",
569
+ { cwd: dir, encoding: "utf-8", timeout: GIT_TIMEOUT_MS, stdio: ["pipe", "pipe", "pipe"] }
570
+ ).trim();
571
+ return realpath(path7.dirname(commonDir));
572
+ } catch {
573
+ return null;
574
+ }
575
+ }
559
576
  function worktreePath(repoRoot, employeeName, instance) {
560
577
  const label = instanceLabel(employeeName, instance);
561
578
  return path7.join(repoRoot, ".worktrees", label);
@@ -781,6 +798,11 @@ function getSessionState(sessionName) {
781
798
  if (!transport.isAlive(sessionName)) return "offline";
782
799
  try {
783
800
  const pane = transport.capturePane(sessionName, 5);
801
+ if (!pane.includes("\u276F") && !pane.includes("Claude Code") && !BUSY_PATTERN.test(pane) && !/Running…/.test(pane)) {
802
+ if (/\$\s*$/.test(pane) || /% $/.test(pane.trimEnd())) {
803
+ return "no_claude";
804
+ }
805
+ }
784
806
  if (/Running…/.test(pane)) return "tool";
785
807
  if (BUSY_PATTERN.test(pane)) return "thinking";
786
808
  return "idle";
@@ -788,10 +810,6 @@ function getSessionState(sessionName) {
788
810
  return "offline";
789
811
  }
790
812
  }
791
- function isSessionBusy(sessionName) {
792
- const state = getSessionState(sessionName);
793
- return state === "thinking" || state === "tool";
794
- }
795
813
  function isExeSession(sessionName) {
796
814
  return /^exe\d*$/.test(sessionName);
797
815
  }
@@ -811,7 +829,14 @@ function sendIntercom(targetSession) {
811
829
  logIntercom(`SKIP \u2192 ${targetSession} (session not found)`);
812
830
  return "failed";
813
831
  }
814
- if (isSessionBusy(targetSession)) {
832
+ const sessionState = getSessionState(targetSession);
833
+ if (sessionState === "no_claude") {
834
+ queueIntercom(targetSession, "claude not running in session");
835
+ recordDebounce(targetSession);
836
+ logIntercom(`QUEUED \u2192 ${targetSession} (no claude process \u2014 raw shell detected)`);
837
+ return "queued";
838
+ }
839
+ if (sessionState === "thinking" || sessionState === "tool") {
815
840
  queueIntercom(targetSession, "session busy at send time");
816
841
  recordDebounce(targetSession);
817
842
  logIntercom(`QUEUED \u2192 ${targetSession} (session busy, will retry from queue)`);
@@ -823,18 +848,7 @@ function sendIntercom(targetSession) {
823
848
  }
824
849
  transport.sendKeys(targetSession, "/exe-intercom");
825
850
  recordDebounce(targetSession);
826
- for (let i = 0; i < INTERCOM_POLL_MAX_ATTEMPTS; i++) {
827
- try {
828
- execSync5(`sleep ${INTERCOM_POLL_INTERVAL_S}`);
829
- } catch {
830
- }
831
- const state = getSessionState(targetSession);
832
- if (state === "thinking" || state === "tool") {
833
- logIntercom(`ACKNOWLEDGED \u2192 ${targetSession} (state=${state}, poll=${i + 1})`);
834
- return "acknowledged";
835
- }
836
- }
837
- logIntercom(`DELIVERED \u2192 ${targetSession} (no state transition after ${INTERCOM_POLL_MAX_ATTEMPTS}s)`);
851
+ logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
838
852
  return "delivered";
839
853
  } catch {
840
854
  logIntercom(`FAIL \u2192 ${targetSession}`);
@@ -888,7 +902,8 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
888
902
  return { status: "failed", sessionName, error: "intercom delivery failed" };
889
903
  }
890
904
  const spawnOpts = { ...opts, instance: effectiveInstance };
891
- const wtPath = ensureWorktree(projectDir, employeeName, effectiveInstance);
905
+ const mainRoot = getMainRepoRoot(projectDir) ?? projectDir;
906
+ const wtPath = ensureWorktree(mainRoot, employeeName, effectiveInstance);
892
907
  if (wtPath) {
893
908
  spawnOpts.cwd = wtPath;
894
909
  }
@@ -1069,7 +1084,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
1069
1084
  let booted = false;
1070
1085
  for (let i = 0; i < 30; i++) {
1071
1086
  try {
1072
- execSync5("sleep 1");
1087
+ execSync5("sleep 0.5");
1073
1088
  } catch {
1074
1089
  }
1075
1090
  try {
@@ -1089,7 +1104,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
1089
1104
  }
1090
1105
  }
1091
1106
  if (!booted) {
1092
- return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 30s` };
1107
+ return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
1093
1108
  }
1094
1109
  if (!useExeAgent) {
1095
1110
  try {
@@ -1107,7 +1122,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
1107
1122
  });
1108
1123
  return { sessionName };
1109
1124
  }
1110
- var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN, INTERCOM_POLL_INTERVAL_S, INTERCOM_POLL_MAX_ATTEMPTS;
1125
+ var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
1111
1126
  var init_tmux_routing = __esm({
1112
1127
  "src/lib/tmux-routing.ts"() {
1113
1128
  "use strict";
@@ -1127,8 +1142,6 @@ var init_tmux_routing = __esm({
1127
1142
  DEBOUNCE_FILE = path8.join(SESSION_CACHE, "intercom-debounce.json");
1128
1143
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
1129
1144
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
1130
- INTERCOM_POLL_INTERVAL_S = 1;
1131
- INTERCOM_POLL_MAX_ATTEMPTS = 8;
1132
1145
  }
1133
1146
  });
1134
1147