@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
@@ -446,6 +446,17 @@ function normalizeOrchestration(raw) {
446
446
  const userOrg = raw.orchestration ?? {};
447
447
  raw.orchestration = { ...defaultOrg, ...userOrg };
448
448
  }
449
+ function normalizeCloudEndpoint(raw) {
450
+ const cloud = raw.cloud;
451
+ if (!cloud?.endpoint) return;
452
+ const ep = String(cloud.endpoint);
453
+ if (ep === "https://askexe.com/cloud" || ep === "https://askexe.com/cloud/") {
454
+ cloud.endpoint = "https://cloud.askexe.com";
455
+ process.stderr.write(
456
+ "[config] Auto-migrated cloud endpoint: askexe.com/cloud \u2192 cloud.askexe.com\n"
457
+ );
458
+ }
459
+ }
449
460
  async function loadConfig() {
450
461
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
451
462
  await ensurePrivateDir(dir);
@@ -471,6 +482,7 @@ async function loadConfig() {
471
482
  normalizeSessionLifecycle(migratedCfg);
472
483
  normalizeAutoUpdate(migratedCfg);
473
484
  normalizeOrchestration(migratedCfg);
485
+ normalizeCloudEndpoint(migratedCfg);
474
486
  const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
475
487
  if (config.dbPath.startsWith("~")) {
476
488
  config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
@@ -499,6 +511,7 @@ function loadConfigSync() {
499
511
  normalizeSessionLifecycle(migratedCfg);
500
512
  normalizeAutoUpdate(migratedCfg);
501
513
  normalizeOrchestration(migratedCfg);
514
+ normalizeCloudEndpoint(migratedCfg);
502
515
  const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
503
516
  if (config.dbPath.startsWith("~")) {
504
517
  config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
@@ -659,6 +672,7 @@ __export(agent_config_exports, {
659
672
  clearAgentRuntime: () => clearAgentRuntime,
660
673
  getAgentRuntime: () => getAgentRuntime,
661
674
  loadAgentConfig: () => loadAgentConfig,
675
+ normalizeCcModelName: () => normalizeCcModelName,
662
676
  saveAgentConfig: () => saveAgentConfig,
663
677
  setAgentMcps: () => setAgentMcps,
664
678
  setAgentRuntime: () => setAgentRuntime
@@ -687,6 +701,13 @@ function getAgentRuntime(agentId) {
687
701
  if (orgDefault) return orgDefault;
688
702
  return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
689
703
  }
704
+ function normalizeCcModelName(model) {
705
+ let ccModel = model.replace(/(\d+)\.(\d+)/g, "$1-$2");
706
+ if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
707
+ ccModel += "[1m]";
708
+ }
709
+ return ccModel;
710
+ }
690
711
  function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
691
712
  const knownModels = KNOWN_RUNTIMES[runtime];
692
713
  if (!knownModels) {
@@ -756,6 +777,7 @@ var init_agent_config = __esm({
756
777
  // src/lib/intercom-queue.ts
757
778
  var intercom_queue_exports = {};
758
779
  __export(intercom_queue_exports, {
780
+ _resetDrainGuard: () => _resetDrainGuard,
759
781
  clearQueueForAgent: () => clearQueueForAgent,
760
782
  drainForSession: () => drainForSession,
761
783
  drainQueue: () => drainQueue,
@@ -801,38 +823,47 @@ function queueIntercom(targetSession, reason) {
801
823
  writeQueue(queue);
802
824
  }
803
825
  function drainQueue(isSessionBusy2, sendKeys) {
826
+ if (_draining) {
827
+ logQueue("SKIP_DRAIN \u2014 previous drain still running (possible tmux hang)");
828
+ return { drained: 0, failed: 0 };
829
+ }
804
830
  const queue = readQueue();
805
831
  if (queue.length === 0) return { drained: 0, failed: 0 };
832
+ _draining = true;
806
833
  const remaining = [];
807
834
  let drained = 0;
808
835
  let failed = 0;
809
- for (const item of queue) {
810
- const age = Date.now() - new Date(item.queuedAt).getTime();
811
- if (age > TTL_MS) {
812
- logQueue(`EXPIRED \u2192 ${item.targetSession} (${Math.round(age / 6e4)}min old, reason: ${item.reason})`);
813
- failed++;
814
- continue;
815
- }
816
- try {
817
- if (!isSessionBusy2(item.targetSession)) {
818
- const success = sendKeys(item.targetSession);
819
- if (success) {
820
- logQueue(`DRAINED \u2192 ${item.targetSession} (after ${item.attempts} retries)`);
821
- drained++;
822
- continue;
836
+ try {
837
+ for (const item of queue) {
838
+ const age = Date.now() - new Date(item.queuedAt).getTime();
839
+ if (age > TTL_MS) {
840
+ logQueue(`EXPIRED \u2192 ${item.targetSession} (${Math.round(age / 6e4)}min old, reason: ${item.reason})`);
841
+ failed++;
842
+ continue;
843
+ }
844
+ try {
845
+ if (!isSessionBusy2(item.targetSession)) {
846
+ const success = sendKeys(item.targetSession);
847
+ if (success) {
848
+ logQueue(`DRAINED \u2192 ${item.targetSession} (after ${item.attempts} retries)`);
849
+ drained++;
850
+ continue;
851
+ }
823
852
  }
853
+ } catch {
824
854
  }
825
- } catch {
826
- }
827
- item.attempts++;
828
- if (item.attempts >= MAX_RETRIES) {
829
- logQueue(`FAILED \u2192 ${item.targetSession} (${MAX_RETRIES} retries exhausted, reason: ${item.reason})`);
830
- failed++;
831
- continue;
855
+ item.attempts++;
856
+ if (item.attempts >= MAX_RETRIES) {
857
+ logQueue(`FAILED \u2192 ${item.targetSession} (${MAX_RETRIES} retries exhausted, reason: ${item.reason})`);
858
+ failed++;
859
+ continue;
860
+ }
861
+ remaining.push(item);
832
862
  }
833
- remaining.push(item);
863
+ writeQueue(remaining);
864
+ } finally {
865
+ _draining = false;
834
866
  }
835
- writeQueue(remaining);
836
867
  return { drained, failed };
837
868
  }
838
869
  function drainForSession(targetSession, sendKeys) {
@@ -857,6 +888,9 @@ function clearQueueForAgent(agentName) {
857
888
  logQueue(`CLEARED ${before - filtered.length} stale item(s) for ${agentName}`);
858
889
  }
859
890
  }
891
+ function _resetDrainGuard() {
892
+ _draining = false;
893
+ }
860
894
  function logQueue(msg) {
861
895
  const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [queue] ${msg}
862
896
  `;
@@ -868,13 +902,14 @@ function logQueue(msg) {
868
902
  } catch {
869
903
  }
870
904
  }
871
- var QUEUE_PATH, MAX_RETRIES, TTL_MS, INTERCOM_LOG;
905
+ var QUEUE_PATH, MAX_RETRIES, TTL_MS, _draining, INTERCOM_LOG;
872
906
  var init_intercom_queue = __esm({
873
907
  "src/lib/intercom-queue.ts"() {
874
908
  "use strict";
875
909
  QUEUE_PATH = path4.join(os3.homedir(), ".exe-os", "intercom-queue.json");
876
910
  MAX_RETRIES = 5;
877
911
  TTL_MS = 60 * 60 * 1e3;
912
+ _draining = false;
878
913
  INTERCOM_LOG = path4.join(os3.homedir(), ".exe-os", "intercom.log");
879
914
  }
880
915
  });
@@ -1869,6 +1904,17 @@ var init_task_scope = __esm({
1869
1904
  });
1870
1905
 
1871
1906
  // src/lib/notifications.ts
1907
+ var notifications_exports = {};
1908
+ __export(notifications_exports, {
1909
+ cleanupOldNotifications: () => cleanupOldNotifications,
1910
+ formatNotifications: () => formatNotifications,
1911
+ markAsRead: () => markAsRead,
1912
+ markAsReadByTaskFile: () => markAsReadByTaskFile,
1913
+ markDoneTaskNotificationsAsRead: () => markDoneTaskNotificationsAsRead,
1914
+ migrateJsonNotifications: () => migrateJsonNotifications,
1915
+ readUnreadNotifications: () => readUnreadNotifications,
1916
+ writeNotification: () => writeNotification
1917
+ });
1872
1918
  import crypto from "crypto";
1873
1919
  import path10 from "path";
1874
1920
  import os8 from "os";
@@ -1905,6 +1951,52 @@ async function writeNotification(notification) {
1905
1951
  `);
1906
1952
  }
1907
1953
  }
1954
+ async function readUnreadNotifications(agentFilter, sessionScope) {
1955
+ try {
1956
+ const client = getClient();
1957
+ const conditions = ["read = 0"];
1958
+ const args = [];
1959
+ const scope = strictSessionScopeFilter(sessionScope);
1960
+ if (agentFilter) {
1961
+ conditions.push("agent_id = ?");
1962
+ args.push(agentFilter);
1963
+ }
1964
+ const result = await client.execute({
1965
+ sql: `SELECT id, agent_id, agent_role, event, project, summary, task_file, session_scope, created_at
1966
+ FROM notifications
1967
+ WHERE ${conditions.join(" AND ")}${scope.sql}
1968
+ ORDER BY created_at ASC`,
1969
+ args: [...args, ...scope.args]
1970
+ });
1971
+ return result.rows.map((r) => ({
1972
+ id: String(r.id),
1973
+ agentId: String(r.agent_id),
1974
+ agentRole: String(r.agent_role),
1975
+ event: String(r.event),
1976
+ project: String(r.project),
1977
+ summary: String(r.summary),
1978
+ taskFile: r.task_file ? String(r.task_file) : void 0,
1979
+ sessionScope: r.session_scope == null ? null : String(r.session_scope),
1980
+ timestamp: String(r.created_at),
1981
+ read: false
1982
+ }));
1983
+ } catch {
1984
+ return [];
1985
+ }
1986
+ }
1987
+ async function markAsRead(ids, sessionScope) {
1988
+ if (ids.length === 0) return;
1989
+ try {
1990
+ const client = getClient();
1991
+ const placeholders = ids.map(() => "?").join(", ");
1992
+ const scope = strictSessionScopeFilter(sessionScope);
1993
+ await client.execute({
1994
+ sql: `UPDATE notifications SET read = 1 WHERE id IN (${placeholders})${scope.sql}`,
1995
+ args: [...ids, ...scope.args]
1996
+ });
1997
+ } catch {
1998
+ }
1999
+ }
1908
2000
  async function markAsReadByTaskFile(taskFile, sessionScope) {
1909
2001
  try {
1910
2002
  const client = getClient();
@@ -1917,11 +2009,144 @@ async function markAsReadByTaskFile(taskFile, sessionScope) {
1917
2009
  } catch {
1918
2010
  }
1919
2011
  }
2012
+ async function cleanupOldNotifications(daysOld = CLEANUP_DAYS, sessionScope) {
2013
+ try {
2014
+ const client = getClient();
2015
+ const cutoff = new Date(
2016
+ Date.now() - daysOld * 24 * 60 * 60 * 1e3
2017
+ ).toISOString();
2018
+ const scope = strictSessionScopeFilter(sessionScope);
2019
+ const result = await client.execute({
2020
+ sql: `DELETE FROM notifications WHERE created_at < ?${scope.sql}`,
2021
+ args: [cutoff, ...scope.args]
2022
+ });
2023
+ return result.rowsAffected;
2024
+ } catch {
2025
+ return 0;
2026
+ }
2027
+ }
2028
+ async function markDoneTaskNotificationsAsRead(sessionScope) {
2029
+ try {
2030
+ const client = getClient();
2031
+ const scope = strictSessionScopeFilter(sessionScope);
2032
+ const result = await client.execute({
2033
+ sql: `UPDATE notifications SET read = 1
2034
+ WHERE read = 0
2035
+ AND task_file IS NOT NULL
2036
+ ${scope.sql}
2037
+ AND task_file IN (
2038
+ SELECT task_file FROM tasks WHERE status = 'done'${scope.sql}
2039
+ )`,
2040
+ args: [...scope.args, ...scope.args]
2041
+ });
2042
+ return result.rowsAffected;
2043
+ } catch {
2044
+ return 0;
2045
+ }
2046
+ }
2047
+ function formatNotifications(notifications) {
2048
+ if (notifications.length === 0) return "";
2049
+ const grouped = /* @__PURE__ */ new Map();
2050
+ for (const n of notifications) {
2051
+ const key = `${n.agentId}|${n.agentRole}`;
2052
+ if (!grouped.has(key)) grouped.set(key, []);
2053
+ grouped.get(key).push(n);
2054
+ }
2055
+ const lines = [];
2056
+ lines.push(`## Notifications (${notifications.length} unread)
2057
+ `);
2058
+ for (const [key, items] of grouped) {
2059
+ const [agentId, agentRole] = key.split("|");
2060
+ lines.push(`**${agentId}** (${agentRole}):`);
2061
+ for (const item of items) {
2062
+ const ago = formatTimeAgo(item.timestamp);
2063
+ const icon = eventIcon(item.event);
2064
+ lines.push(`- ${icon} ${item.summary} (${item.project}) \u2014 ${ago}`);
2065
+ }
2066
+ lines.push("");
2067
+ }
2068
+ return lines.join("\n");
2069
+ }
2070
+ async function migrateJsonNotifications() {
2071
+ const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR || path10.join(os8.homedir(), ".exe-os");
2072
+ const notifDir = path10.join(base, "notifications");
2073
+ if (!existsSync11(notifDir)) return 0;
2074
+ let migrated = 0;
2075
+ try {
2076
+ const files = readdirSync(notifDir).filter((f) => f.endsWith(".json"));
2077
+ if (files.length === 0) return 0;
2078
+ const client = getClient();
2079
+ for (const file of files) {
2080
+ try {
2081
+ const filePath = path10.join(notifDir, file);
2082
+ const data = JSON.parse(readFileSync8(filePath, "utf8"));
2083
+ await client.execute({
2084
+ sql: `INSERT OR IGNORE INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
2085
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
2086
+ args: [
2087
+ crypto.randomUUID(),
2088
+ data.agentId ?? "unknown",
2089
+ data.agentRole ?? "unknown",
2090
+ data.event ?? "session_summary",
2091
+ data.project ?? "unknown",
2092
+ data.summary ?? "",
2093
+ data.taskFile ?? null,
2094
+ null,
2095
+ data.read ? 1 : 0,
2096
+ data.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
2097
+ ]
2098
+ });
2099
+ unlinkSync3(filePath);
2100
+ migrated++;
2101
+ } catch {
2102
+ }
2103
+ }
2104
+ try {
2105
+ const remaining = readdirSync(notifDir);
2106
+ if (remaining.length === 0) {
2107
+ rmdirSync(notifDir);
2108
+ }
2109
+ } catch {
2110
+ }
2111
+ } catch {
2112
+ }
2113
+ return migrated;
2114
+ }
2115
+ function eventIcon(event) {
2116
+ switch (event) {
2117
+ case "task_complete":
2118
+ return "Completed:";
2119
+ case "task_needs_fix":
2120
+ return "Needs fix:";
2121
+ case "session_summary":
2122
+ return "Session:";
2123
+ case "error_spike":
2124
+ return "Errors:";
2125
+ case "orphan_task":
2126
+ return "Orphan:";
2127
+ case "subtasks_complete":
2128
+ return "Subtasks done:";
2129
+ case "capacity_relaunch":
2130
+ return "Relaunched:";
2131
+ }
2132
+ }
2133
+ function formatTimeAgo(timestamp) {
2134
+ const diffMs = Date.now() - new Date(timestamp).getTime();
2135
+ const mins = Math.floor(diffMs / 6e4);
2136
+ if (mins < 1) return "just now";
2137
+ if (mins < 60) return `${mins}m ago`;
2138
+ const hours = Math.floor(mins / 60);
2139
+ if (hours < 24) return `${hours}h ago`;
2140
+ const days = Math.floor(hours / 24);
2141
+ return `${days}d ago`;
2142
+ }
2143
+ var CLEANUP_DAYS;
1920
2144
  var init_notifications = __esm({
1921
2145
  "src/lib/notifications.ts"() {
1922
2146
  "use strict";
1923
2147
  init_database();
1924
2148
  init_task_scope();
2149
+ CLEANUP_DAYS = 7;
1925
2150
  }
1926
2151
  });
1927
2152
 
@@ -2967,7 +3192,13 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
2967
3192
  taskFile
2968
3193
  });
2969
3194
  const originalPriority = String(row.priority).toLowerCase();
2970
- const autoApprove = originalPriority === "p2" && result?.toLowerCase().includes("tests pass");
3195
+ const resultLower = result?.toLowerCase() ?? "";
3196
+ const hasTestEvidence = (
3197
+ // Vitest/Jest output patterns (hard to fake without actually running tests)
3198
+ /\d+\s+pass(ed|ing)/.test(resultLower) || /test files?\s+\d+\s+passed/.test(resultLower) || /tests?\s+\d+\s+passed/.test(resultLower)
3199
+ );
3200
+ const hasNoFailures = !/fail(ed|ure|ing)|error/i.test(resultLower);
3201
+ const autoApprove = originalPriority === "p2" && hasTestEvidence && hasNoFailures;
2971
3202
  if (!autoApprove) {
2972
3203
  try {
2973
3204
  const key = getSessionKey();
@@ -2975,6 +3206,13 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
2975
3206
  if (exeSession) {
2976
3207
  sendIntercom(exeSession);
2977
3208
  }
3209
+ if (reviewer && reviewer !== coordinatorName && reviewer !== exeSession) {
3210
+ const { employeeSessionName: employeeSessionName2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
3211
+ if (exeSession) {
3212
+ const reviewerSession = employeeSessionName2(reviewer, exeSession);
3213
+ sendIntercom(reviewerSession);
3214
+ }
3215
+ }
2978
3216
  } catch {
2979
3217
  }
2980
3218
  }
@@ -4199,6 +4437,20 @@ async function updateTask(input) {
4199
4437
  notifyTaskDone();
4200
4438
  }
4201
4439
  await markTaskNotificationsRead(taskFile);
4440
+ if (input.status === "needs_review" && !isCoordinator) {
4441
+ try {
4442
+ const { writeNotification: writeNotification2 } = await Promise.resolve().then(() => (init_notifications(), notifications_exports));
4443
+ await writeNotification2({
4444
+ agentId: String(row.assigned_to),
4445
+ agentRole: String(row.assigned_to),
4446
+ event: "task_complete",
4447
+ project: String(row.project_name),
4448
+ summary: `"${String(row.title)}" is ready for review`,
4449
+ taskFile
4450
+ });
4451
+ } catch {
4452
+ }
4453
+ }
4202
4454
  if (input.status === "done" || input.status === "closed") {
4203
4455
  try {
4204
4456
  await cascadeUnblock(taskId, input.baseDir, now);
@@ -4631,18 +4883,31 @@ function acquireSpawnLock2(sessionName) {
4631
4883
  mkdirSync8(SPAWN_LOCK_DIR, { recursive: true });
4632
4884
  }
4633
4885
  const lockFile = spawnLockPath(sessionName);
4634
- if (existsSync16(lockFile)) {
4635
- try {
4636
- const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
4637
- const age = Date.now() - lock.timestamp;
4638
- if (isProcessAlive(lock.pid) && age < 6e4) {
4639
- return false;
4640
- }
4641
- } catch {
4886
+ const lockData = JSON.stringify({ pid: process.pid, timestamp: Date.now() });
4887
+ const { openSync: openSync3, closeSync: closeSync3, writeSync } = __require("fs");
4888
+ const { constants } = __require("fs");
4889
+ try {
4890
+ const fd = openSync3(lockFile, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 420);
4891
+ writeSync(fd, lockData);
4892
+ closeSync3(fd);
4893
+ return true;
4894
+ } catch (err) {
4895
+ if (err?.code !== "EEXIST") {
4896
+ return true;
4642
4897
  }
4643
4898
  }
4644
- writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
4645
- return true;
4899
+ try {
4900
+ const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
4901
+ const age = Date.now() - lock.timestamp;
4902
+ if (isProcessAlive(lock.pid) && age < 6e4) {
4903
+ return false;
4904
+ }
4905
+ writeFileSync8(lockFile, lockData);
4906
+ return true;
4907
+ } catch {
4908
+ writeFileSync8(lockFile, lockData);
4909
+ return true;
4910
+ }
4646
4911
  }
4647
4912
  function releaseSpawnLock2(sessionName) {
4648
4913
  try {
@@ -4721,6 +4986,21 @@ function parseParentExe(sessionName, agentId) {
4721
4986
  function extractRootExe(name) {
4722
4987
  if (!name) return null;
4723
4988
  if (!name.includes("-")) return name;
4989
+ try {
4990
+ const roster = (init_employees(), __toCommonJS(employees_exports)).loadEmployeesSync();
4991
+ if (roster.length > 0) {
4992
+ const sortedNames = roster.map((e) => e.name).sort((a, b) => b.length - a.length);
4993
+ for (const agentName of sortedNames) {
4994
+ const escaped = agentName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4995
+ const regex = new RegExp(`^${escaped}\\d*-(.+)$`);
4996
+ const match = name.match(regex);
4997
+ if (match) {
4998
+ return extractRootExe(match[1]);
4999
+ }
5000
+ }
5001
+ }
5002
+ } catch {
5003
+ }
4724
5004
  const parts = name.split("-").filter(Boolean);
4725
5005
  return parts.length > 0 ? parts[parts.length - 1] : null;
4726
5006
  }
@@ -4739,6 +5019,10 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
4739
5019
  function getParentExe(sessionKey) {
4740
5020
  try {
4741
5021
  const data = JSON.parse(readFileSync12(path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
5022
+ if (data.registeredAt) {
5023
+ const age = Date.now() - new Date(data.registeredAt).getTime();
5024
+ if (age > PARENT_EXE_CACHE_TTL_MS) return null;
5025
+ }
4742
5026
  return data.parentExe || null;
4743
5027
  } catch {
4744
5028
  return null;
@@ -5287,7 +5571,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5287
5571
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
5288
5572
  } catch {
5289
5573
  }
5290
- let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName}`;
5574
+ let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName} EXE_SESSION_START_ISO=${(/* @__PURE__ */ new Date()).toISOString()}`;
5291
5575
  if (ccProvider !== DEFAULT_PROVIDER) {
5292
5576
  const cfg = PROVIDER_TABLE[ccProvider];
5293
5577
  if (cfg?.apiKeyEnv) {
@@ -5322,10 +5606,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5322
5606
  }
5323
5607
  if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
5324
5608
  if (agentRtConfig.runtime === "claude" && agentRtConfig.model) {
5325
- let ccModel = agentRtConfig.model.replace(/(\d+)\.(\d+)/g, "$1-$2");
5326
- if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
5327
- ccModel += "[1m]";
5328
- }
5609
+ const { normalizeCcModelName: normalizeCcModelName2 } = (init_agent_config(), __toCommonJS(agent_config_exports));
5610
+ const ccModel = normalizeCcModelName2(agentRtConfig.model);
5329
5611
  envPrefix = `${envPrefix} ANTHROPIC_MODEL=${ccModel}`;
5330
5612
  }
5331
5613
  }
@@ -5426,7 +5708,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5426
5708
  releaseSpawnLock2(sessionName);
5427
5709
  return { sessionName };
5428
5710
  }
5429
- 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;
5711
+ 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;
5430
5712
  var init_tmux_routing = __esm({
5431
5713
  "src/lib/tmux-routing.ts"() {
5432
5714
  init_session_registry();
@@ -5445,6 +5727,7 @@ var init_tmux_routing = __esm({
5445
5727
  SESSION_CACHE = path18.join(os11.homedir(), ".exe-os", "session-cache");
5446
5728
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
5447
5729
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
5730
+ PARENT_EXE_CACHE_TTL_MS = 4 * 60 * 60 * 1e3;
5448
5731
  VERIFY_PANE_LINES = 200;
5449
5732
  INTERCOM_DEBOUNCE_MS = 3e4;
5450
5733
  CODEX_DEBOUNCE_MS = 12e4;