@askexenow/exe-os 0.9.113 → 0.9.115

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 (86) hide show
  1. package/dist/bin/agentic-ontology-backfill.js +36 -12
  2. package/dist/bin/agentic-reflection-backfill.js +36 -12
  3. package/dist/bin/agentic-semantic-label.js +36 -12
  4. package/dist/bin/backfill-conversations.js +36 -12
  5. package/dist/bin/backfill-responses.js +36 -12
  6. package/dist/bin/backfill-vectors.js +36 -12
  7. package/dist/bin/bulk-sync-postgres.js +36 -12
  8. package/dist/bin/cleanup-stale-review-tasks.js +470 -113
  9. package/dist/bin/cli.js +413 -62
  10. package/dist/bin/exe-agent.js +27 -0
  11. package/dist/bin/exe-assign.js +36 -12
  12. package/dist/bin/exe-boot.js +246 -54
  13. package/dist/bin/exe-call.js +8 -0
  14. package/dist/bin/exe-cloud.js +47 -12
  15. package/dist/bin/exe-dispatch.js +348 -53
  16. package/dist/bin/exe-doctor.js +51 -13
  17. package/dist/bin/exe-export-behaviors.js +37 -12
  18. package/dist/bin/exe-forget.js +36 -12
  19. package/dist/bin/exe-gateway.js +348 -53
  20. package/dist/bin/exe-heartbeat.js +471 -113
  21. package/dist/bin/exe-kill.js +36 -12
  22. package/dist/bin/exe-launch-agent.js +117 -18
  23. package/dist/bin/exe-new-employee.js +9 -1
  24. package/dist/bin/exe-pending-messages.js +452 -95
  25. package/dist/bin/exe-pending-notifications.js +452 -95
  26. package/dist/bin/exe-pending-reviews.js +452 -95
  27. package/dist/bin/exe-rename.js +36 -12
  28. package/dist/bin/exe-review.js +36 -12
  29. package/dist/bin/exe-search.js +37 -12
  30. package/dist/bin/exe-session-cleanup.js +348 -53
  31. package/dist/bin/exe-settings.js +12 -0
  32. package/dist/bin/exe-start-codex.js +46 -13
  33. package/dist/bin/exe-start-opencode.js +46 -13
  34. package/dist/bin/exe-status.js +460 -114
  35. package/dist/bin/exe-support.js +12 -0
  36. package/dist/bin/exe-team.js +36 -12
  37. package/dist/bin/git-sweep.js +348 -53
  38. package/dist/bin/graph-backfill.js +36 -12
  39. package/dist/bin/graph-export.js +36 -12
  40. package/dist/bin/install.js +9 -1
  41. package/dist/bin/intercom-check.js +255 -53
  42. package/dist/bin/scan-tasks.js +348 -53
  43. package/dist/bin/setup.js +74 -12
  44. package/dist/bin/shard-migrate.js +36 -12
  45. package/dist/gateway/index.js +348 -53
  46. package/dist/hooks/bug-report-worker.js +348 -53
  47. package/dist/hooks/codex-stop-task-finalizer.js +308 -37
  48. package/dist/hooks/commit-complete.js +348 -53
  49. package/dist/hooks/error-recall.js +37 -12
  50. package/dist/hooks/ingest.js +363 -54
  51. package/dist/hooks/instructions-loaded.js +36 -12
  52. package/dist/hooks/notification.js +36 -12
  53. package/dist/hooks/post-compact.js +426 -72
  54. package/dist/hooks/post-tool-combined.js +501 -146
  55. package/dist/hooks/pre-compact.js +348 -53
  56. package/dist/hooks/pre-tool-use.js +92 -13
  57. package/dist/hooks/prompt-submit.js +348 -53
  58. package/dist/hooks/session-end.js +158 -53
  59. package/dist/hooks/session-start.js +66 -13
  60. package/dist/hooks/stop.js +420 -72
  61. package/dist/hooks/subagent-stop.js +419 -72
  62. package/dist/hooks/summary-worker.js +442 -121
  63. package/dist/index.js +375 -53
  64. package/dist/lib/agent-config.js +8 -0
  65. package/dist/lib/cloud-sync.js +35 -12
  66. package/dist/lib/config.js +13 -0
  67. package/dist/lib/consolidation.js +9 -1
  68. package/dist/lib/embedder.js +13 -0
  69. package/dist/lib/employees.js +8 -0
  70. package/dist/lib/exe-daemon.js +524 -60
  71. package/dist/lib/hybrid-search.js +37 -12
  72. package/dist/lib/keychain.js +25 -13
  73. package/dist/lib/messaging.js +395 -74
  74. package/dist/lib/schedules.js +36 -12
  75. package/dist/lib/skill-learning.js +21 -0
  76. package/dist/lib/store.js +36 -12
  77. package/dist/lib/tasks.js +324 -41
  78. package/dist/lib/tmux-routing.js +324 -41
  79. package/dist/mcp/server.js +374 -54
  80. package/dist/mcp/tools/create-task.js +324 -41
  81. package/dist/mcp/tools/list-tasks.js +406 -57
  82. package/dist/mcp/tools/send-message.js +395 -74
  83. package/dist/mcp/tools/update-task.js +324 -41
  84. package/dist/runtime/index.js +375 -53
  85. package/dist/tui/App.js +377 -55
  86. package/package.json +1 -1
@@ -154,6 +154,17 @@ function normalizeOrchestration(raw) {
154
154
  const userOrg = raw.orchestration ?? {};
155
155
  raw.orchestration = { ...defaultOrg, ...userOrg };
156
156
  }
157
+ function normalizeCloudEndpoint(raw) {
158
+ const cloud = raw.cloud;
159
+ if (!cloud?.endpoint) return;
160
+ const ep = String(cloud.endpoint);
161
+ if (ep === "https://askexe.com/cloud" || ep === "https://askexe.com/cloud/") {
162
+ cloud.endpoint = "https://cloud.askexe.com";
163
+ process.stderr.write(
164
+ "[config] Auto-migrated cloud endpoint: askexe.com/cloud \u2192 cloud.askexe.com\n"
165
+ );
166
+ }
167
+ }
157
168
  async function loadConfig() {
158
169
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
159
170
  await ensurePrivateDir(dir);
@@ -179,6 +190,7 @@ async function loadConfig() {
179
190
  normalizeSessionLifecycle(migratedCfg);
180
191
  normalizeAutoUpdate(migratedCfg);
181
192
  normalizeOrchestration(migratedCfg);
193
+ normalizeCloudEndpoint(migratedCfg);
182
194
  const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
183
195
  if (config.dbPath.startsWith("~")) {
184
196
  config.dbPath = config.dbPath.replace(/^~/, os.homedir());
@@ -207,6 +219,7 @@ function loadConfigSync() {
207
219
  normalizeSessionLifecycle(migratedCfg);
208
220
  normalizeAutoUpdate(migratedCfg);
209
221
  normalizeOrchestration(migratedCfg);
222
+ normalizeCloudEndpoint(migratedCfg);
210
223
  const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
211
224
  if (config.dbPath.startsWith("~")) {
212
225
  config.dbPath = config.dbPath.replace(/^~/, os.homedir());
@@ -431,6 +444,7 @@ __export(agent_config_exports, {
431
444
  clearAgentRuntime: () => clearAgentRuntime,
432
445
  getAgentRuntime: () => getAgentRuntime,
433
446
  loadAgentConfig: () => loadAgentConfig,
447
+ normalizeCcModelName: () => normalizeCcModelName,
434
448
  saveAgentConfig: () => saveAgentConfig,
435
449
  setAgentMcps: () => setAgentMcps,
436
450
  setAgentRuntime: () => setAgentRuntime
@@ -459,6 +473,13 @@ function getAgentRuntime(agentId) {
459
473
  if (orgDefault) return orgDefault;
460
474
  return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
461
475
  }
476
+ function normalizeCcModelName(model) {
477
+ let ccModel = model.replace(/(\d+)\.(\d+)/g, "$1-$2");
478
+ if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
479
+ ccModel += "[1m]";
480
+ }
481
+ return ccModel;
482
+ }
462
483
  function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
463
484
  const knownModels = KNOWN_RUNTIMES[runtime];
464
485
  if (!knownModels) {
@@ -3741,7 +3762,7 @@ var init_database = __esm({
3741
3762
  });
3742
3763
 
3743
3764
  // src/lib/keychain.ts
3744
- import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
3765
+ import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2, rename, copyFile } from "fs/promises";
3745
3766
  import { existsSync as existsSync8, statSync as statSync3 } from "fs";
3746
3767
  import { execSync as execSync3 } from "child_process";
3747
3768
  import path7 from "path";
@@ -3776,12 +3797,14 @@ function linuxSecretAvailable() {
3776
3797
  function isRootOnlyTrustedServerKeyFile(keyPath) {
3777
3798
  if (process.platform !== "linux") return false;
3778
3799
  try {
3779
- const uid = typeof os5.userInfo().uid === "number" ? os5.userInfo().uid : -1;
3780
3800
  const st = statSync3(keyPath);
3781
3801
  if (!st.isFile() || (st.mode & 63) !== 0) return false;
3802
+ const uid = typeof os5.userInfo().uid === "number" ? os5.userInfo().uid : -1;
3782
3803
  if (uid === 0) return true;
3783
3804
  const exeOsDir = process.env.EXE_OS_DIR;
3784
- return Boolean(exeOsDir && path7.resolve(keyPath).startsWith(path7.resolve(exeOsDir) + path7.sep));
3805
+ if (exeOsDir && path7.resolve(keyPath).startsWith(path7.resolve(exeOsDir) + path7.sep)) return true;
3806
+ if (!linuxSecretAvailable()) return true;
3807
+ return false;
3785
3808
  } catch {
3786
3809
  return false;
3787
3810
  }
@@ -3931,15 +3954,25 @@ async function writeMachineBoundFileFallback(b64) {
3931
3954
  await mkdir3(dir, { recursive: true });
3932
3955
  const keyPath = getKeyPath();
3933
3956
  const machineKey = deriveMachineKey();
3934
- if (machineKey) {
3935
- const encrypted = encryptWithMachineKey(b64, machineKey);
3936
- await writeFile3(keyPath, encrypted + "\n", "utf-8");
3937
- await chmod2(keyPath, 384);
3938
- return "encrypted";
3957
+ const content = machineKey ? encryptWithMachineKey(b64, machineKey) + "\n" : b64 + "\n";
3958
+ const result = machineKey ? "encrypted" : "plaintext";
3959
+ const tmpPath = keyPath + ".tmp";
3960
+ try {
3961
+ if (existsSync8(keyPath)) {
3962
+ await copyFile(keyPath, keyPath + ".bak").catch(() => {
3963
+ });
3964
+ }
3965
+ await writeFile3(tmpPath, content, "utf-8");
3966
+ await chmod2(tmpPath, 384);
3967
+ await rename(tmpPath, keyPath);
3968
+ } catch (err) {
3969
+ try {
3970
+ await unlink(tmpPath);
3971
+ } catch {
3972
+ }
3973
+ throw err;
3939
3974
  }
3940
- await writeFile3(keyPath, b64 + "\n", "utf-8");
3941
- await chmod2(keyPath, 384);
3942
- return "plaintext";
3975
+ return result;
3943
3976
  }
3944
3977
  async function getMasterKey() {
3945
3978
  let nativeValue = macKeychainGet() ?? linuxSecretGet();
@@ -4006,7 +4039,7 @@ async function getMasterKey() {
4006
4039
  b64Value = content;
4007
4040
  }
4008
4041
  const key = Buffer.from(b64Value, "base64");
4009
- if (!content.startsWith(ENCRYPTED_PREFIX) && isRootOnlyTrustedServerKeyFile(keyPath)) {
4042
+ if (isRootOnlyTrustedServerKeyFile(keyPath)) {
4010
4043
  return key;
4011
4044
  }
4012
4045
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
@@ -7718,6 +7751,7 @@ var init_provider_table = __esm({
7718
7751
  // src/lib/intercom-queue.ts
7719
7752
  var intercom_queue_exports = {};
7720
7753
  __export(intercom_queue_exports, {
7754
+ _resetDrainGuard: () => _resetDrainGuard,
7721
7755
  clearQueueForAgent: () => clearQueueForAgent,
7722
7756
  drainForSession: () => drainForSession,
7723
7757
  drainQueue: () => drainQueue,
@@ -7763,38 +7797,47 @@ function queueIntercom(targetSession, reason) {
7763
7797
  writeQueue(queue);
7764
7798
  }
7765
7799
  function drainQueue(isSessionBusy2, sendKeys) {
7800
+ if (_draining) {
7801
+ logQueue("SKIP_DRAIN \u2014 previous drain still running (possible tmux hang)");
7802
+ return { drained: 0, failed: 0 };
7803
+ }
7766
7804
  const queue = readQueue();
7767
7805
  if (queue.length === 0) return { drained: 0, failed: 0 };
7806
+ _draining = true;
7768
7807
  const remaining = [];
7769
7808
  let drained = 0;
7770
7809
  let failed = 0;
7771
- for (const item of queue) {
7772
- const age = Date.now() - new Date(item.queuedAt).getTime();
7773
- if (age > TTL_MS) {
7774
- logQueue(`EXPIRED \u2192 ${item.targetSession} (${Math.round(age / 6e4)}min old, reason: ${item.reason})`);
7775
- failed++;
7776
- continue;
7777
- }
7778
- try {
7779
- if (!isSessionBusy2(item.targetSession)) {
7780
- const success = sendKeys(item.targetSession);
7781
- if (success) {
7782
- logQueue(`DRAINED \u2192 ${item.targetSession} (after ${item.attempts} retries)`);
7783
- drained++;
7784
- continue;
7810
+ try {
7811
+ for (const item of queue) {
7812
+ const age = Date.now() - new Date(item.queuedAt).getTime();
7813
+ if (age > TTL_MS) {
7814
+ logQueue(`EXPIRED \u2192 ${item.targetSession} (${Math.round(age / 6e4)}min old, reason: ${item.reason})`);
7815
+ failed++;
7816
+ continue;
7817
+ }
7818
+ try {
7819
+ if (!isSessionBusy2(item.targetSession)) {
7820
+ const success = sendKeys(item.targetSession);
7821
+ if (success) {
7822
+ logQueue(`DRAINED \u2192 ${item.targetSession} (after ${item.attempts} retries)`);
7823
+ drained++;
7824
+ continue;
7825
+ }
7785
7826
  }
7827
+ } catch {
7786
7828
  }
7787
- } catch {
7788
- }
7789
- item.attempts++;
7790
- if (item.attempts >= MAX_RETRIES2) {
7791
- logQueue(`FAILED \u2192 ${item.targetSession} (${MAX_RETRIES2} retries exhausted, reason: ${item.reason})`);
7792
- failed++;
7793
- continue;
7829
+ item.attempts++;
7830
+ if (item.attempts >= MAX_RETRIES2) {
7831
+ logQueue(`FAILED \u2192 ${item.targetSession} (${MAX_RETRIES2} retries exhausted, reason: ${item.reason})`);
7832
+ failed++;
7833
+ continue;
7834
+ }
7835
+ remaining.push(item);
7794
7836
  }
7795
- remaining.push(item);
7837
+ writeQueue(remaining);
7838
+ } finally {
7839
+ _draining = false;
7796
7840
  }
7797
- writeQueue(remaining);
7798
7841
  return { drained, failed };
7799
7842
  }
7800
7843
  function drainForSession(targetSession, sendKeys) {
@@ -7819,6 +7862,9 @@ function clearQueueForAgent(agentName) {
7819
7862
  logQueue(`CLEARED ${before - filtered.length} stale item(s) for ${agentName}`);
7820
7863
  }
7821
7864
  }
7865
+ function _resetDrainGuard() {
7866
+ _draining = false;
7867
+ }
7822
7868
  function logQueue(msg) {
7823
7869
  const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [queue] ${msg}
7824
7870
  `;
@@ -7830,13 +7876,14 @@ function logQueue(msg) {
7830
7876
  } catch {
7831
7877
  }
7832
7878
  }
7833
- var QUEUE_PATH, MAX_RETRIES2, TTL_MS, INTERCOM_LOG;
7879
+ var QUEUE_PATH, MAX_RETRIES2, TTL_MS, _draining, INTERCOM_LOG;
7834
7880
  var init_intercom_queue = __esm({
7835
7881
  "src/lib/intercom-queue.ts"() {
7836
7882
  "use strict";
7837
7883
  QUEUE_PATH = path15.join(os8.homedir(), ".exe-os", "intercom-queue.json");
7838
7884
  MAX_RETRIES2 = 5;
7839
7885
  TTL_MS = 60 * 60 * 1e3;
7886
+ _draining = false;
7840
7887
  INTERCOM_LOG = path15.join(os8.homedir(), ".exe-os", "intercom.log");
7841
7888
  }
7842
7889
  });
@@ -8484,6 +8531,17 @@ var init_task_scope = __esm({
8484
8531
  });
8485
8532
 
8486
8533
  // src/lib/notifications.ts
8534
+ var notifications_exports = {};
8535
+ __export(notifications_exports, {
8536
+ cleanupOldNotifications: () => cleanupOldNotifications,
8537
+ formatNotifications: () => formatNotifications,
8538
+ markAsRead: () => markAsRead,
8539
+ markAsReadByTaskFile: () => markAsReadByTaskFile,
8540
+ markDoneTaskNotificationsAsRead: () => markDoneTaskNotificationsAsRead,
8541
+ migrateJsonNotifications: () => migrateJsonNotifications,
8542
+ readUnreadNotifications: () => readUnreadNotifications,
8543
+ writeNotification: () => writeNotification
8544
+ });
8487
8545
  import crypto3 from "crypto";
8488
8546
  import path19 from "path";
8489
8547
  import os11 from "os";
@@ -8520,6 +8578,52 @@ async function writeNotification(notification) {
8520
8578
  `);
8521
8579
  }
8522
8580
  }
8581
+ async function readUnreadNotifications(agentFilter, sessionScope) {
8582
+ try {
8583
+ const client = getClient();
8584
+ const conditions = ["read = 0"];
8585
+ const args = [];
8586
+ const scope = strictSessionScopeFilter(sessionScope);
8587
+ if (agentFilter) {
8588
+ conditions.push("agent_id = ?");
8589
+ args.push(agentFilter);
8590
+ }
8591
+ const result = await client.execute({
8592
+ sql: `SELECT id, agent_id, agent_role, event, project, summary, task_file, session_scope, created_at
8593
+ FROM notifications
8594
+ WHERE ${conditions.join(" AND ")}${scope.sql}
8595
+ ORDER BY created_at ASC`,
8596
+ args: [...args, ...scope.args]
8597
+ });
8598
+ return result.rows.map((r) => ({
8599
+ id: String(r.id),
8600
+ agentId: String(r.agent_id),
8601
+ agentRole: String(r.agent_role),
8602
+ event: String(r.event),
8603
+ project: String(r.project),
8604
+ summary: String(r.summary),
8605
+ taskFile: r.task_file ? String(r.task_file) : void 0,
8606
+ sessionScope: r.session_scope == null ? null : String(r.session_scope),
8607
+ timestamp: String(r.created_at),
8608
+ read: false
8609
+ }));
8610
+ } catch {
8611
+ return [];
8612
+ }
8613
+ }
8614
+ async function markAsRead(ids, sessionScope) {
8615
+ if (ids.length === 0) return;
8616
+ try {
8617
+ const client = getClient();
8618
+ const placeholders = ids.map(() => "?").join(", ");
8619
+ const scope = strictSessionScopeFilter(sessionScope);
8620
+ await client.execute({
8621
+ sql: `UPDATE notifications SET read = 1 WHERE id IN (${placeholders})${scope.sql}`,
8622
+ args: [...ids, ...scope.args]
8623
+ });
8624
+ } catch {
8625
+ }
8626
+ }
8523
8627
  async function markAsReadByTaskFile(taskFile, sessionScope) {
8524
8628
  try {
8525
8629
  const client = getClient();
@@ -8532,11 +8636,144 @@ async function markAsReadByTaskFile(taskFile, sessionScope) {
8532
8636
  } catch {
8533
8637
  }
8534
8638
  }
8639
+ async function cleanupOldNotifications(daysOld = CLEANUP_DAYS, sessionScope) {
8640
+ try {
8641
+ const client = getClient();
8642
+ const cutoff = new Date(
8643
+ Date.now() - daysOld * 24 * 60 * 60 * 1e3
8644
+ ).toISOString();
8645
+ const scope = strictSessionScopeFilter(sessionScope);
8646
+ const result = await client.execute({
8647
+ sql: `DELETE FROM notifications WHERE created_at < ?${scope.sql}`,
8648
+ args: [cutoff, ...scope.args]
8649
+ });
8650
+ return result.rowsAffected;
8651
+ } catch {
8652
+ return 0;
8653
+ }
8654
+ }
8655
+ async function markDoneTaskNotificationsAsRead(sessionScope) {
8656
+ try {
8657
+ const client = getClient();
8658
+ const scope = strictSessionScopeFilter(sessionScope);
8659
+ const result = await client.execute({
8660
+ sql: `UPDATE notifications SET read = 1
8661
+ WHERE read = 0
8662
+ AND task_file IS NOT NULL
8663
+ ${scope.sql}
8664
+ AND task_file IN (
8665
+ SELECT task_file FROM tasks WHERE status = 'done'${scope.sql}
8666
+ )`,
8667
+ args: [...scope.args, ...scope.args]
8668
+ });
8669
+ return result.rowsAffected;
8670
+ } catch {
8671
+ return 0;
8672
+ }
8673
+ }
8674
+ function formatNotifications(notifications) {
8675
+ if (notifications.length === 0) return "";
8676
+ const grouped = /* @__PURE__ */ new Map();
8677
+ for (const n of notifications) {
8678
+ const key = `${n.agentId}|${n.agentRole}`;
8679
+ if (!grouped.has(key)) grouped.set(key, []);
8680
+ grouped.get(key).push(n);
8681
+ }
8682
+ const lines = [];
8683
+ lines.push(`## Notifications (${notifications.length} unread)
8684
+ `);
8685
+ for (const [key, items] of grouped) {
8686
+ const [agentId, agentRole] = key.split("|");
8687
+ lines.push(`**${agentId}** (${agentRole}):`);
8688
+ for (const item of items) {
8689
+ const ago = formatTimeAgo(item.timestamp);
8690
+ const icon = eventIcon(item.event);
8691
+ lines.push(`- ${icon} ${item.summary} (${item.project}) \u2014 ${ago}`);
8692
+ }
8693
+ lines.push("");
8694
+ }
8695
+ return lines.join("\n");
8696
+ }
8697
+ async function migrateJsonNotifications() {
8698
+ const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR || path19.join(os11.homedir(), ".exe-os");
8699
+ const notifDir = path19.join(base, "notifications");
8700
+ if (!existsSync18(notifDir)) return 0;
8701
+ let migrated = 0;
8702
+ try {
8703
+ const files = readdirSync4(notifDir).filter((f) => f.endsWith(".json"));
8704
+ if (files.length === 0) return 0;
8705
+ const client = getClient();
8706
+ for (const file of files) {
8707
+ try {
8708
+ const filePath = path19.join(notifDir, file);
8709
+ const data = JSON.parse(readFileSync13(filePath, "utf8"));
8710
+ await client.execute({
8711
+ sql: `INSERT OR IGNORE INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
8712
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
8713
+ args: [
8714
+ crypto3.randomUUID(),
8715
+ data.agentId ?? "unknown",
8716
+ data.agentRole ?? "unknown",
8717
+ data.event ?? "session_summary",
8718
+ data.project ?? "unknown",
8719
+ data.summary ?? "",
8720
+ data.taskFile ?? null,
8721
+ null,
8722
+ data.read ? 1 : 0,
8723
+ data.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
8724
+ ]
8725
+ });
8726
+ unlinkSync6(filePath);
8727
+ migrated++;
8728
+ } catch {
8729
+ }
8730
+ }
8731
+ try {
8732
+ const remaining = readdirSync4(notifDir);
8733
+ if (remaining.length === 0) {
8734
+ rmdirSync(notifDir);
8735
+ }
8736
+ } catch {
8737
+ }
8738
+ } catch {
8739
+ }
8740
+ return migrated;
8741
+ }
8742
+ function eventIcon(event) {
8743
+ switch (event) {
8744
+ case "task_complete":
8745
+ return "Completed:";
8746
+ case "task_needs_fix":
8747
+ return "Needs fix:";
8748
+ case "session_summary":
8749
+ return "Session:";
8750
+ case "error_spike":
8751
+ return "Errors:";
8752
+ case "orphan_task":
8753
+ return "Orphan:";
8754
+ case "subtasks_complete":
8755
+ return "Subtasks done:";
8756
+ case "capacity_relaunch":
8757
+ return "Relaunched:";
8758
+ }
8759
+ }
8760
+ function formatTimeAgo(timestamp) {
8761
+ const diffMs = Date.now() - new Date(timestamp).getTime();
8762
+ const mins = Math.floor(diffMs / 6e4);
8763
+ if (mins < 1) return "just now";
8764
+ if (mins < 60) return `${mins}m ago`;
8765
+ const hours = Math.floor(mins / 60);
8766
+ if (hours < 24) return `${hours}h ago`;
8767
+ const days = Math.floor(hours / 24);
8768
+ return `${days}d ago`;
8769
+ }
8770
+ var CLEANUP_DAYS;
8535
8771
  var init_notifications = __esm({
8536
8772
  "src/lib/notifications.ts"() {
8537
8773
  "use strict";
8538
8774
  init_database();
8539
8775
  init_task_scope();
8776
+ CLEANUP_DAYS = 7;
8540
8777
  }
8541
8778
  });
8542
8779
 
@@ -9485,7 +9722,13 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
9485
9722
  taskFile
9486
9723
  });
9487
9724
  const originalPriority = String(row.priority).toLowerCase();
9488
- const autoApprove = originalPriority === "p2" && result?.toLowerCase().includes("tests pass");
9725
+ const resultLower = result?.toLowerCase() ?? "";
9726
+ const hasTestEvidence = (
9727
+ // Vitest/Jest output patterns (hard to fake without actually running tests)
9728
+ /\d+\s+pass(ed|ing)/.test(resultLower) || /test files?\s+\d+\s+passed/.test(resultLower) || /tests?\s+\d+\s+passed/.test(resultLower)
9729
+ );
9730
+ const hasNoFailures = !/fail(ed|ure|ing)|error/i.test(resultLower);
9731
+ const autoApprove = originalPriority === "p2" && hasTestEvidence && hasNoFailures;
9489
9732
  if (!autoApprove) {
9490
9733
  try {
9491
9734
  const key = getSessionKey();
@@ -9493,6 +9736,13 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
9493
9736
  if (exeSession) {
9494
9737
  sendIntercom(exeSession);
9495
9738
  }
9739
+ if (reviewer && reviewer !== coordinatorName && reviewer !== exeSession) {
9740
+ const { employeeSessionName: employeeSessionName2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
9741
+ if (exeSession) {
9742
+ const reviewerSession = employeeSessionName2(reviewer, exeSession);
9743
+ sendIntercom(reviewerSession);
9744
+ }
9745
+ }
9496
9746
  } catch {
9497
9747
  }
9498
9748
  }
@@ -10277,6 +10527,20 @@ async function updateTask(input2) {
10277
10527
  notifyTaskDone();
10278
10528
  }
10279
10529
  await markTaskNotificationsRead(taskFile);
10530
+ if (input2.status === "needs_review" && !isCoordinator) {
10531
+ try {
10532
+ const { writeNotification: writeNotification2 } = await Promise.resolve().then(() => (init_notifications(), notifications_exports));
10533
+ await writeNotification2({
10534
+ agentId: String(row.assigned_to),
10535
+ agentRole: String(row.assigned_to),
10536
+ event: "task_complete",
10537
+ project: String(row.project_name),
10538
+ summary: `"${String(row.title)}" is ready for review`,
10539
+ taskFile
10540
+ });
10541
+ } catch {
10542
+ }
10543
+ }
10280
10544
  if (input2.status === "done" || input2.status === "closed") {
10281
10545
  try {
10282
10546
  await cascadeUnblock(taskId, input2.baseDir, now);
@@ -10709,18 +10973,31 @@ function acquireSpawnLock2(sessionName) {
10709
10973
  mkdirSync11(SPAWN_LOCK_DIR, { recursive: true });
10710
10974
  }
10711
10975
  const lockFile = spawnLockPath(sessionName);
10712
- if (existsSync21(lockFile)) {
10713
- try {
10714
- const lock = JSON.parse(readFileSync15(lockFile, "utf8"));
10715
- const age = Date.now() - lock.timestamp;
10716
- if (isProcessAlive(lock.pid) && age < 6e4) {
10717
- return false;
10718
- }
10719
- } catch {
10976
+ const lockData = JSON.stringify({ pid: process.pid, timestamp: Date.now() });
10977
+ const { openSync: openSync3, closeSync: closeSync3, writeSync } = __require("fs");
10978
+ const { constants } = __require("fs");
10979
+ try {
10980
+ const fd = openSync3(lockFile, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 420);
10981
+ writeSync(fd, lockData);
10982
+ closeSync3(fd);
10983
+ return true;
10984
+ } catch (err) {
10985
+ if (err?.code !== "EEXIST") {
10986
+ return true;
10720
10987
  }
10721
10988
  }
10722
- writeFileSync10(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
10723
- return true;
10989
+ try {
10990
+ const lock = JSON.parse(readFileSync15(lockFile, "utf8"));
10991
+ const age = Date.now() - lock.timestamp;
10992
+ if (isProcessAlive(lock.pid) && age < 6e4) {
10993
+ return false;
10994
+ }
10995
+ writeFileSync10(lockFile, lockData);
10996
+ return true;
10997
+ } catch {
10998
+ writeFileSync10(lockFile, lockData);
10999
+ return true;
11000
+ }
10724
11001
  }
10725
11002
  function releaseSpawnLock2(sessionName) {
10726
11003
  try {
@@ -10799,6 +11076,21 @@ function parseParentExe(sessionName, agentId) {
10799
11076
  function extractRootExe(name) {
10800
11077
  if (!name) return null;
10801
11078
  if (!name.includes("-")) return name;
11079
+ try {
11080
+ const roster = (init_employees(), __toCommonJS(employees_exports)).loadEmployeesSync();
11081
+ if (roster.length > 0) {
11082
+ const sortedNames = roster.map((e) => e.name).sort((a, b) => b.length - a.length);
11083
+ for (const agentName of sortedNames) {
11084
+ const escaped = agentName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
11085
+ const regex = new RegExp(`^${escaped}\\d*-(.+)$`);
11086
+ const match = name.match(regex);
11087
+ if (match) {
11088
+ return extractRootExe(match[1]);
11089
+ }
11090
+ }
11091
+ }
11092
+ } catch {
11093
+ }
10802
11094
  const parts = name.split("-").filter(Boolean);
10803
11095
  return parts.length > 0 ? parts[parts.length - 1] : null;
10804
11096
  }
@@ -10817,6 +11109,10 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
10817
11109
  function getParentExe(sessionKey) {
10818
11110
  try {
10819
11111
  const data = JSON.parse(readFileSync15(path24.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
11112
+ if (data.registeredAt) {
11113
+ const age = Date.now() - new Date(data.registeredAt).getTime();
11114
+ if (age > PARENT_EXE_CACHE_TTL_MS) return null;
11115
+ }
10820
11116
  return data.parentExe || null;
10821
11117
  } catch {
10822
11118
  return null;
@@ -11365,7 +11661,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
11365
11661
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
11366
11662
  } catch {
11367
11663
  }
11368
- let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName}`;
11664
+ let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName} EXE_SESSION_START_ISO=${(/* @__PURE__ */ new Date()).toISOString()}`;
11369
11665
  if (ccProvider !== DEFAULT_PROVIDER) {
11370
11666
  const cfg = PROVIDER_TABLE[ccProvider];
11371
11667
  if (cfg?.apiKeyEnv) {
@@ -11400,10 +11696,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
11400
11696
  }
11401
11697
  if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
11402
11698
  if (agentRtConfig.runtime === "claude" && agentRtConfig.model) {
11403
- let ccModel = agentRtConfig.model.replace(/(\d+)\.(\d+)/g, "$1-$2");
11404
- if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
11405
- ccModel += "[1m]";
11406
- }
11699
+ const { normalizeCcModelName: normalizeCcModelName2 } = (init_agent_config(), __toCommonJS(agent_config_exports));
11700
+ const ccModel = normalizeCcModelName2(agentRtConfig.model);
11407
11701
  envPrefix = `${envPrefix} ANTHROPIC_MODEL=${ccModel}`;
11408
11702
  }
11409
11703
  }
@@ -11504,7 +11798,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
11504
11798
  releaseSpawnLock2(sessionName);
11505
11799
  return { sessionName };
11506
11800
  }
11507
- var SPAWN_LOCK_DIR, SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VALID_SESSION_NAME, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, CODEX_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
11801
+ var SPAWN_LOCK_DIR, SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VALID_SESSION_NAME, PARENT_EXE_CACHE_TTL_MS, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, CODEX_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
11508
11802
  var init_tmux_routing = __esm({
11509
11803
  "src/lib/tmux-routing.ts"() {
11510
11804
  "use strict";
@@ -11524,6 +11818,7 @@ var init_tmux_routing = __esm({
11524
11818
  SESSION_CACHE = path24.join(os13.homedir(), ".exe-os", "session-cache");
11525
11819
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
11526
11820
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
11821
+ PARENT_EXE_CACHE_TTL_MS = 4 * 60 * 60 * 1e3;
11527
11822
  VERIFY_PANE_LINES = 200;
11528
11823
  INTERCOM_DEBOUNCE_MS = 3e4;
11529
11824
  CODEX_DEBOUNCE_MS = 12e4;