@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
@@ -163,6 +163,17 @@ function normalizeOrchestration(raw) {
163
163
  const userOrg = raw.orchestration ?? {};
164
164
  raw.orchestration = { ...defaultOrg, ...userOrg };
165
165
  }
166
+ function normalizeCloudEndpoint(raw) {
167
+ const cloud = raw.cloud;
168
+ if (!cloud?.endpoint) return;
169
+ const ep = String(cloud.endpoint);
170
+ if (ep === "https://askexe.com/cloud" || ep === "https://askexe.com/cloud/") {
171
+ cloud.endpoint = "https://cloud.askexe.com";
172
+ process.stderr.write(
173
+ "[config] Auto-migrated cloud endpoint: askexe.com/cloud \u2192 cloud.askexe.com\n"
174
+ );
175
+ }
176
+ }
166
177
  async function loadConfig() {
167
178
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
168
179
  await ensurePrivateDir(dir);
@@ -188,6 +199,7 @@ async function loadConfig() {
188
199
  normalizeSessionLifecycle(migratedCfg);
189
200
  normalizeAutoUpdate(migratedCfg);
190
201
  normalizeOrchestration(migratedCfg);
202
+ normalizeCloudEndpoint(migratedCfg);
191
203
  const config2 = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
192
204
  if (config2.dbPath.startsWith("~")) {
193
205
  config2.dbPath = config2.dbPath.replace(/^~/, os.homedir());
@@ -216,6 +228,7 @@ function loadConfigSync() {
216
228
  normalizeSessionLifecycle(migratedCfg);
217
229
  normalizeAutoUpdate(migratedCfg);
218
230
  normalizeOrchestration(migratedCfg);
231
+ normalizeCloudEndpoint(migratedCfg);
219
232
  const config2 = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
220
233
  if (config2.dbPath.startsWith("~")) {
221
234
  config2.dbPath = config2.dbPath.replace(/^~/, os.homedir());
@@ -982,6 +995,7 @@ __export(agent_config_exports, {
982
995
  clearAgentRuntime: () => clearAgentRuntime,
983
996
  getAgentRuntime: () => getAgentRuntime,
984
997
  loadAgentConfig: () => loadAgentConfig,
998
+ normalizeCcModelName: () => normalizeCcModelName,
985
999
  saveAgentConfig: () => saveAgentConfig,
986
1000
  setAgentMcps: () => setAgentMcps,
987
1001
  setAgentRuntime: () => setAgentRuntime
@@ -1010,6 +1024,13 @@ function getAgentRuntime(agentId) {
1010
1024
  if (orgDefault) return orgDefault;
1011
1025
  return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
1012
1026
  }
1027
+ function normalizeCcModelName(model) {
1028
+ let ccModel = model.replace(/(\d+)\.(\d+)/g, "$1-$2");
1029
+ if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
1030
+ ccModel += "[1m]";
1031
+ }
1032
+ return ccModel;
1033
+ }
1013
1034
  function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
1014
1035
  const knownModels = KNOWN_RUNTIMES[runtime];
1015
1036
  if (!knownModels) {
@@ -3818,7 +3839,7 @@ __export(keychain_exports, {
3818
3839
  importMnemonic: () => importMnemonic,
3819
3840
  setMasterKey: () => setMasterKey
3820
3841
  });
3821
- import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
3842
+ import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2, rename, copyFile } from "fs/promises";
3822
3843
  import { existsSync as existsSync8, statSync as statSync3 } from "fs";
3823
3844
  import { execSync as execSync3 } from "child_process";
3824
3845
  import path7 from "path";
@@ -3853,12 +3874,14 @@ function linuxSecretAvailable() {
3853
3874
  function isRootOnlyTrustedServerKeyFile(keyPath) {
3854
3875
  if (process.platform !== "linux") return false;
3855
3876
  try {
3856
- const uid = typeof os5.userInfo().uid === "number" ? os5.userInfo().uid : -1;
3857
3877
  const st = statSync3(keyPath);
3858
3878
  if (!st.isFile() || (st.mode & 63) !== 0) return false;
3879
+ const uid = typeof os5.userInfo().uid === "number" ? os5.userInfo().uid : -1;
3859
3880
  if (uid === 0) return true;
3860
3881
  const exeOsDir = process.env.EXE_OS_DIR;
3861
- return Boolean(exeOsDir && path7.resolve(keyPath).startsWith(path7.resolve(exeOsDir) + path7.sep));
3882
+ if (exeOsDir && path7.resolve(keyPath).startsWith(path7.resolve(exeOsDir) + path7.sep)) return true;
3883
+ if (!linuxSecretAvailable()) return true;
3884
+ return false;
3862
3885
  } catch {
3863
3886
  return false;
3864
3887
  }
@@ -4008,15 +4031,25 @@ async function writeMachineBoundFileFallback(b64) {
4008
4031
  await mkdir3(dir, { recursive: true });
4009
4032
  const keyPath = getKeyPath();
4010
4033
  const machineKey = deriveMachineKey();
4011
- if (machineKey) {
4012
- const encrypted = encryptWithMachineKey(b64, machineKey);
4013
- await writeFile3(keyPath, encrypted + "\n", "utf-8");
4014
- await chmod2(keyPath, 384);
4015
- return "encrypted";
4034
+ const content = machineKey ? encryptWithMachineKey(b64, machineKey) + "\n" : b64 + "\n";
4035
+ const result3 = machineKey ? "encrypted" : "plaintext";
4036
+ const tmpPath = keyPath + ".tmp";
4037
+ try {
4038
+ if (existsSync8(keyPath)) {
4039
+ await copyFile(keyPath, keyPath + ".bak").catch(() => {
4040
+ });
4041
+ }
4042
+ await writeFile3(tmpPath, content, "utf-8");
4043
+ await chmod2(tmpPath, 384);
4044
+ await rename(tmpPath, keyPath);
4045
+ } catch (err) {
4046
+ try {
4047
+ await unlink(tmpPath);
4048
+ } catch {
4049
+ }
4050
+ throw err;
4016
4051
  }
4017
- await writeFile3(keyPath, b64 + "\n", "utf-8");
4018
- await chmod2(keyPath, 384);
4019
- return "plaintext";
4052
+ return result3;
4020
4053
  }
4021
4054
  async function getMasterKey() {
4022
4055
  let nativeValue = macKeychainGet() ?? linuxSecretGet();
@@ -4083,7 +4116,7 @@ async function getMasterKey() {
4083
4116
  b64Value = content;
4084
4117
  }
4085
4118
  const key = Buffer.from(b64Value, "base64");
4086
- if (!content.startsWith(ENCRYPTED_PREFIX) && isRootOnlyTrustedServerKeyFile(keyPath)) {
4119
+ if (isRootOnlyTrustedServerKeyFile(keyPath)) {
4087
4120
  return key;
4088
4121
  }
4089
4122
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
@@ -9896,6 +9929,7 @@ var init_provider_table = __esm({
9896
9929
  // src/lib/intercom-queue.ts
9897
9930
  var intercom_queue_exports = {};
9898
9931
  __export(intercom_queue_exports, {
9932
+ _resetDrainGuard: () => _resetDrainGuard,
9899
9933
  clearQueueForAgent: () => clearQueueForAgent,
9900
9934
  drainForSession: () => drainForSession,
9901
9935
  drainQueue: () => drainQueue,
@@ -9941,38 +9975,47 @@ function queueIntercom(targetSession, reason) {
9941
9975
  writeQueue(queue);
9942
9976
  }
9943
9977
  function drainQueue(isSessionBusy2, sendKeys) {
9978
+ if (_draining) {
9979
+ logQueue("SKIP_DRAIN \u2014 previous drain still running (possible tmux hang)");
9980
+ return { drained: 0, failed: 0 };
9981
+ }
9944
9982
  const queue = readQueue();
9945
9983
  if (queue.length === 0) return { drained: 0, failed: 0 };
9984
+ _draining = true;
9946
9985
  const remaining = [];
9947
9986
  let drained = 0;
9948
9987
  let failed = 0;
9949
- for (const item of queue) {
9950
- const age = Date.now() - new Date(item.queuedAt).getTime();
9951
- if (age > TTL_MS) {
9952
- logQueue(`EXPIRED \u2192 ${item.targetSession} (${Math.round(age / 6e4)}min old, reason: ${item.reason})`);
9953
- failed++;
9954
- continue;
9955
- }
9956
- try {
9957
- if (!isSessionBusy2(item.targetSession)) {
9958
- const success = sendKeys(item.targetSession);
9959
- if (success) {
9960
- logQueue(`DRAINED \u2192 ${item.targetSession} (after ${item.attempts} retries)`);
9961
- drained++;
9962
- continue;
9988
+ try {
9989
+ for (const item of queue) {
9990
+ const age = Date.now() - new Date(item.queuedAt).getTime();
9991
+ if (age > TTL_MS) {
9992
+ logQueue(`EXPIRED \u2192 ${item.targetSession} (${Math.round(age / 6e4)}min old, reason: ${item.reason})`);
9993
+ failed++;
9994
+ continue;
9995
+ }
9996
+ try {
9997
+ if (!isSessionBusy2(item.targetSession)) {
9998
+ const success = sendKeys(item.targetSession);
9999
+ if (success) {
10000
+ logQueue(`DRAINED \u2192 ${item.targetSession} (after ${item.attempts} retries)`);
10001
+ drained++;
10002
+ continue;
10003
+ }
9963
10004
  }
10005
+ } catch {
9964
10006
  }
9965
- } catch {
9966
- }
9967
- item.attempts++;
9968
- if (item.attempts >= MAX_RETRIES2) {
9969
- logQueue(`FAILED \u2192 ${item.targetSession} (${MAX_RETRIES2} retries exhausted, reason: ${item.reason})`);
9970
- failed++;
9971
- continue;
10007
+ item.attempts++;
10008
+ if (item.attempts >= MAX_RETRIES2) {
10009
+ logQueue(`FAILED \u2192 ${item.targetSession} (${MAX_RETRIES2} retries exhausted, reason: ${item.reason})`);
10010
+ failed++;
10011
+ continue;
10012
+ }
10013
+ remaining.push(item);
9972
10014
  }
9973
- remaining.push(item);
10015
+ writeQueue(remaining);
10016
+ } finally {
10017
+ _draining = false;
9974
10018
  }
9975
- writeQueue(remaining);
9976
10019
  return { drained, failed };
9977
10020
  }
9978
10021
  function drainForSession(targetSession, sendKeys) {
@@ -9997,6 +10040,9 @@ function clearQueueForAgent(agentName) {
9997
10040
  logQueue(`CLEARED ${before - filtered.length} stale item(s) for ${agentName}`);
9998
10041
  }
9999
10042
  }
10043
+ function _resetDrainGuard() {
10044
+ _draining = false;
10045
+ }
10000
10046
  function logQueue(msg) {
10001
10047
  const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [queue] ${msg}
10002
10048
  `;
@@ -10008,13 +10054,14 @@ function logQueue(msg) {
10008
10054
  } catch {
10009
10055
  }
10010
10056
  }
10011
- var QUEUE_PATH, MAX_RETRIES2, TTL_MS, INTERCOM_LOG;
10057
+ var QUEUE_PATH, MAX_RETRIES2, TTL_MS, _draining, INTERCOM_LOG;
10012
10058
  var init_intercom_queue = __esm({
10013
10059
  "src/lib/intercom-queue.ts"() {
10014
10060
  "use strict";
10015
10061
  QUEUE_PATH = path18.join(os8.homedir(), ".exe-os", "intercom-queue.json");
10016
10062
  MAX_RETRIES2 = 5;
10017
10063
  TTL_MS = 60 * 60 * 1e3;
10064
+ _draining = false;
10018
10065
  INTERCOM_LOG = path18.join(os8.homedir(), ".exe-os", "intercom.log");
10019
10066
  }
10020
10067
  });
@@ -10649,7 +10696,13 @@ async function createReviewForCompletedTask(row, result3, _baseDir, now) {
10649
10696
  taskFile
10650
10697
  });
10651
10698
  const originalPriority = String(row.priority).toLowerCase();
10652
- const autoApprove = originalPriority === "p2" && result3?.toLowerCase().includes("tests pass");
10699
+ const resultLower = result3?.toLowerCase() ?? "";
10700
+ const hasTestEvidence = (
10701
+ // Vitest/Jest output patterns (hard to fake without actually running tests)
10702
+ /\d+\s+pass(ed|ing)/.test(resultLower) || /test files?\s+\d+\s+passed/.test(resultLower) || /tests?\s+\d+\s+passed/.test(resultLower)
10703
+ );
10704
+ const hasNoFailures = !/fail(ed|ure|ing)|error/i.test(resultLower);
10705
+ const autoApprove = originalPriority === "p2" && hasTestEvidence && hasNoFailures;
10653
10706
  if (!autoApprove) {
10654
10707
  try {
10655
10708
  const key = getSessionKey();
@@ -10657,6 +10710,13 @@ async function createReviewForCompletedTask(row, result3, _baseDir, now) {
10657
10710
  if (exeSession) {
10658
10711
  sendIntercom(exeSession);
10659
10712
  }
10713
+ if (reviewer && reviewer !== coordinatorName && reviewer !== exeSession) {
10714
+ const { employeeSessionName: employeeSessionName2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
10715
+ if (exeSession) {
10716
+ const reviewerSession = employeeSessionName2(reviewer, exeSession);
10717
+ sendIntercom(reviewerSession);
10718
+ }
10719
+ }
10660
10720
  } catch {
10661
10721
  }
10662
10722
  }
@@ -10787,18 +10847,31 @@ function acquireSpawnLock2(sessionName) {
10787
10847
  mkdirSync9(SPAWN_LOCK_DIR, { recursive: true });
10788
10848
  }
10789
10849
  const lockFile = spawnLockPath(sessionName);
10790
- if (existsSync18(lockFile)) {
10791
- try {
10792
- const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
10793
- const age = Date.now() - lock.timestamp;
10794
- if (isProcessAlive(lock.pid) && age < 6e4) {
10795
- return false;
10796
- }
10797
- } catch {
10850
+ const lockData = JSON.stringify({ pid: process.pid, timestamp: Date.now() });
10851
+ const { openSync: openSync6, closeSync: closeSync6, writeSync } = __require("fs");
10852
+ const { constants: constants2 } = __require("fs");
10853
+ try {
10854
+ const fd = openSync6(lockFile, constants2.O_WRONLY | constants2.O_CREAT | constants2.O_EXCL, 420);
10855
+ writeSync(fd, lockData);
10856
+ closeSync6(fd);
10857
+ return true;
10858
+ } catch (err) {
10859
+ if (err?.code !== "EEXIST") {
10860
+ return true;
10798
10861
  }
10799
10862
  }
10800
- writeFileSync10(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
10801
- return true;
10863
+ try {
10864
+ const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
10865
+ const age = Date.now() - lock.timestamp;
10866
+ if (isProcessAlive(lock.pid) && age < 6e4) {
10867
+ return false;
10868
+ }
10869
+ writeFileSync10(lockFile, lockData);
10870
+ return true;
10871
+ } catch {
10872
+ writeFileSync10(lockFile, lockData);
10873
+ return true;
10874
+ }
10802
10875
  }
10803
10876
  function releaseSpawnLock2(sessionName) {
10804
10877
  try {
@@ -10877,6 +10950,21 @@ function parseParentExe(sessionName, agentId) {
10877
10950
  function extractRootExe(name) {
10878
10951
  if (!name) return null;
10879
10952
  if (!name.includes("-")) return name;
10953
+ try {
10954
+ const roster = (init_employees(), __toCommonJS(employees_exports)).loadEmployeesSync();
10955
+ if (roster.length > 0) {
10956
+ const sortedNames = roster.map((e) => e.name).sort((a, b) => b.length - a.length);
10957
+ for (const agentName of sortedNames) {
10958
+ const escaped = agentName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
10959
+ const regex = new RegExp(`^${escaped}\\d*-(.+)$`);
10960
+ const match = name.match(regex);
10961
+ if (match) {
10962
+ return extractRootExe(match[1]);
10963
+ }
10964
+ }
10965
+ }
10966
+ } catch {
10967
+ }
10880
10968
  const parts = name.split("-").filter(Boolean);
10881
10969
  return parts.length > 0 ? parts[parts.length - 1] : null;
10882
10970
  }
@@ -10895,6 +10983,10 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
10895
10983
  function getParentExe(sessionKey) {
10896
10984
  try {
10897
10985
  const data = JSON.parse(readFileSync12(path21.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
10986
+ if (data.registeredAt) {
10987
+ const age = Date.now() - new Date(data.registeredAt).getTime();
10988
+ if (age > PARENT_EXE_CACHE_TTL_MS) return null;
10989
+ }
10898
10990
  return data.parentExe || null;
10899
10991
  } catch {
10900
10992
  return null;
@@ -11443,7 +11535,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
11443
11535
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
11444
11536
  } catch {
11445
11537
  }
11446
- let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName}`;
11538
+ let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName} EXE_SESSION_START_ISO=${(/* @__PURE__ */ new Date()).toISOString()}`;
11447
11539
  if (ccProvider !== DEFAULT_PROVIDER) {
11448
11540
  const cfg = PROVIDER_TABLE[ccProvider];
11449
11541
  if (cfg?.apiKeyEnv) {
@@ -11478,10 +11570,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
11478
11570
  }
11479
11571
  if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
11480
11572
  if (agentRtConfig.runtime === "claude" && agentRtConfig.model) {
11481
- let ccModel = agentRtConfig.model.replace(/(\d+)\.(\d+)/g, "$1-$2");
11482
- if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
11483
- ccModel += "[1m]";
11484
- }
11573
+ const { normalizeCcModelName: normalizeCcModelName2 } = (init_agent_config(), __toCommonJS(agent_config_exports));
11574
+ const ccModel = normalizeCcModelName2(agentRtConfig.model);
11485
11575
  envPrefix = `${envPrefix} ANTHROPIC_MODEL=${ccModel}`;
11486
11576
  }
11487
11577
  }
@@ -11582,7 +11672,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
11582
11672
  releaseSpawnLock2(sessionName);
11583
11673
  return { sessionName };
11584
11674
  }
11585
- 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;
11675
+ 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;
11586
11676
  var init_tmux_routing = __esm({
11587
11677
  "src/lib/tmux-routing.ts"() {
11588
11678
  "use strict";
@@ -11602,6 +11692,7 @@ var init_tmux_routing = __esm({
11602
11692
  SESSION_CACHE = path21.join(os10.homedir(), ".exe-os", "session-cache");
11603
11693
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
11604
11694
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
11695
+ PARENT_EXE_CACHE_TTL_MS = 4 * 60 * 60 * 1e3;
11605
11696
  VERIFY_PANE_LINES = 200;
11606
11697
  INTERCOM_DEBOUNCE_MS = 3e4;
11607
11698
  CODEX_DEBOUNCE_MS = 12e4;
@@ -11646,6 +11737,17 @@ var init_task_scope = __esm({
11646
11737
  });
11647
11738
 
11648
11739
  // src/lib/notifications.ts
11740
+ var notifications_exports = {};
11741
+ __export(notifications_exports, {
11742
+ cleanupOldNotifications: () => cleanupOldNotifications,
11743
+ formatNotifications: () => formatNotifications,
11744
+ markAsRead: () => markAsRead,
11745
+ markAsReadByTaskFile: () => markAsReadByTaskFile,
11746
+ markDoneTaskNotificationsAsRead: () => markDoneTaskNotificationsAsRead,
11747
+ migrateJsonNotifications: () => migrateJsonNotifications,
11748
+ readUnreadNotifications: () => readUnreadNotifications,
11749
+ writeNotification: () => writeNotification
11750
+ });
11649
11751
  import crypto6 from "crypto";
11650
11752
  import path22 from "path";
11651
11753
  import os11 from "os";
@@ -11682,6 +11784,52 @@ async function writeNotification(notification) {
11682
11784
  `);
11683
11785
  }
11684
11786
  }
11787
+ async function readUnreadNotifications(agentFilter2, sessionScope) {
11788
+ try {
11789
+ const client = getClient();
11790
+ const conditions = ["read = 0"];
11791
+ const args = [];
11792
+ const scope = strictSessionScopeFilter(sessionScope);
11793
+ if (agentFilter2) {
11794
+ conditions.push("agent_id = ?");
11795
+ args.push(agentFilter2);
11796
+ }
11797
+ const result3 = await client.execute({
11798
+ sql: `SELECT id, agent_id, agent_role, event, project, summary, task_file, session_scope, created_at
11799
+ FROM notifications
11800
+ WHERE ${conditions.join(" AND ")}${scope.sql}
11801
+ ORDER BY created_at ASC`,
11802
+ args: [...args, ...scope.args]
11803
+ });
11804
+ return result3.rows.map((r) => ({
11805
+ id: String(r.id),
11806
+ agentId: String(r.agent_id),
11807
+ agentRole: String(r.agent_role),
11808
+ event: String(r.event),
11809
+ project: String(r.project),
11810
+ summary: String(r.summary),
11811
+ taskFile: r.task_file ? String(r.task_file) : void 0,
11812
+ sessionScope: r.session_scope == null ? null : String(r.session_scope),
11813
+ timestamp: String(r.created_at),
11814
+ read: false
11815
+ }));
11816
+ } catch {
11817
+ return [];
11818
+ }
11819
+ }
11820
+ async function markAsRead(ids, sessionScope) {
11821
+ if (ids.length === 0) return;
11822
+ try {
11823
+ const client = getClient();
11824
+ const placeholders = ids.map(() => "?").join(", ");
11825
+ const scope = strictSessionScopeFilter(sessionScope);
11826
+ await client.execute({
11827
+ sql: `UPDATE notifications SET read = 1 WHERE id IN (${placeholders})${scope.sql}`,
11828
+ args: [...ids, ...scope.args]
11829
+ });
11830
+ } catch {
11831
+ }
11832
+ }
11685
11833
  async function markAsReadByTaskFile(taskFile, sessionScope) {
11686
11834
  try {
11687
11835
  const client = getClient();
@@ -11694,11 +11842,144 @@ async function markAsReadByTaskFile(taskFile, sessionScope) {
11694
11842
  } catch {
11695
11843
  }
11696
11844
  }
11845
+ async function cleanupOldNotifications(daysOld = CLEANUP_DAYS, sessionScope) {
11846
+ try {
11847
+ const client = getClient();
11848
+ const cutoff = new Date(
11849
+ Date.now() - daysOld * 24 * 60 * 60 * 1e3
11850
+ ).toISOString();
11851
+ const scope = strictSessionScopeFilter(sessionScope);
11852
+ const result3 = await client.execute({
11853
+ sql: `DELETE FROM notifications WHERE created_at < ?${scope.sql}`,
11854
+ args: [cutoff, ...scope.args]
11855
+ });
11856
+ return result3.rowsAffected;
11857
+ } catch {
11858
+ return 0;
11859
+ }
11860
+ }
11861
+ async function markDoneTaskNotificationsAsRead(sessionScope) {
11862
+ try {
11863
+ const client = getClient();
11864
+ const scope = strictSessionScopeFilter(sessionScope);
11865
+ const result3 = await client.execute({
11866
+ sql: `UPDATE notifications SET read = 1
11867
+ WHERE read = 0
11868
+ AND task_file IS NOT NULL
11869
+ ${scope.sql}
11870
+ AND task_file IN (
11871
+ SELECT task_file FROM tasks WHERE status = 'done'${scope.sql}
11872
+ )`,
11873
+ args: [...scope.args, ...scope.args]
11874
+ });
11875
+ return result3.rowsAffected;
11876
+ } catch {
11877
+ return 0;
11878
+ }
11879
+ }
11880
+ function formatNotifications(notifications) {
11881
+ if (notifications.length === 0) return "";
11882
+ const grouped = /* @__PURE__ */ new Map();
11883
+ for (const n of notifications) {
11884
+ const key = `${n.agentId}|${n.agentRole}`;
11885
+ if (!grouped.has(key)) grouped.set(key, []);
11886
+ grouped.get(key).push(n);
11887
+ }
11888
+ const lines = [];
11889
+ lines.push(`## Notifications (${notifications.length} unread)
11890
+ `);
11891
+ for (const [key, items] of grouped) {
11892
+ const [agentId, agentRole] = key.split("|");
11893
+ lines.push(`**${agentId}** (${agentRole}):`);
11894
+ for (const item of items) {
11895
+ const ago = formatTimeAgo(item.timestamp);
11896
+ const icon = eventIcon(item.event);
11897
+ lines.push(`- ${icon} ${item.summary} (${item.project}) \u2014 ${ago}`);
11898
+ }
11899
+ lines.push("");
11900
+ }
11901
+ return lines.join("\n");
11902
+ }
11903
+ async function migrateJsonNotifications() {
11904
+ const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR || path22.join(os11.homedir(), ".exe-os");
11905
+ const notifDir = path22.join(base, "notifications");
11906
+ if (!existsSync19(notifDir)) return 0;
11907
+ let migrated = 0;
11908
+ try {
11909
+ const files = readdirSync6(notifDir).filter((f) => f.endsWith(".json"));
11910
+ if (files.length === 0) return 0;
11911
+ const client = getClient();
11912
+ for (const file of files) {
11913
+ try {
11914
+ const filePath = path22.join(notifDir, file);
11915
+ const data = JSON.parse(readFileSync13(filePath, "utf8"));
11916
+ await client.execute({
11917
+ sql: `INSERT OR IGNORE INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
11918
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
11919
+ args: [
11920
+ crypto6.randomUUID(),
11921
+ data.agentId ?? "unknown",
11922
+ data.agentRole ?? "unknown",
11923
+ data.event ?? "session_summary",
11924
+ data.project ?? "unknown",
11925
+ data.summary ?? "",
11926
+ data.taskFile ?? null,
11927
+ null,
11928
+ data.read ? 1 : 0,
11929
+ data.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
11930
+ ]
11931
+ });
11932
+ unlinkSync7(filePath);
11933
+ migrated++;
11934
+ } catch {
11935
+ }
11936
+ }
11937
+ try {
11938
+ const remaining = readdirSync6(notifDir);
11939
+ if (remaining.length === 0) {
11940
+ rmdirSync(notifDir);
11941
+ }
11942
+ } catch {
11943
+ }
11944
+ } catch {
11945
+ }
11946
+ return migrated;
11947
+ }
11948
+ function eventIcon(event) {
11949
+ switch (event) {
11950
+ case "task_complete":
11951
+ return "Completed:";
11952
+ case "task_needs_fix":
11953
+ return "Needs fix:";
11954
+ case "session_summary":
11955
+ return "Session:";
11956
+ case "error_spike":
11957
+ return "Errors:";
11958
+ case "orphan_task":
11959
+ return "Orphan:";
11960
+ case "subtasks_complete":
11961
+ return "Subtasks done:";
11962
+ case "capacity_relaunch":
11963
+ return "Relaunched:";
11964
+ }
11965
+ }
11966
+ function formatTimeAgo(timestamp) {
11967
+ const diffMs = Date.now() - new Date(timestamp).getTime();
11968
+ const mins = Math.floor(diffMs / 6e4);
11969
+ if (mins < 1) return "just now";
11970
+ if (mins < 60) return `${mins}m ago`;
11971
+ const hours = Math.floor(mins / 60);
11972
+ if (hours < 24) return `${hours}h ago`;
11973
+ const days = Math.floor(hours / 24);
11974
+ return `${days}d ago`;
11975
+ }
11976
+ var CLEANUP_DAYS;
11697
11977
  var init_notifications = __esm({
11698
11978
  "src/lib/notifications.ts"() {
11699
11979
  "use strict";
11700
11980
  init_database();
11701
11981
  init_task_scope();
11982
+ CLEANUP_DAYS = 7;
11702
11983
  }
11703
11984
  });
11704
11985
 
@@ -13068,6 +13349,20 @@ async function updateTask(input) {
13068
13349
  notifyTaskDone();
13069
13350
  }
13070
13351
  await markTaskNotificationsRead(taskFile);
13352
+ if (input.status === "needs_review" && !isCoordinator) {
13353
+ try {
13354
+ const { writeNotification: writeNotification2 } = await Promise.resolve().then(() => (init_notifications(), notifications_exports));
13355
+ await writeNotification2({
13356
+ agentId: String(row.assigned_to),
13357
+ agentRole: String(row.assigned_to),
13358
+ event: "task_complete",
13359
+ project: String(row.project_name),
13360
+ summary: `"${String(row.title)}" is ready for review`,
13361
+ taskFile
13362
+ });
13363
+ } catch {
13364
+ }
13365
+ }
13071
13366
  if (input.status === "done" || input.status === "closed") {
13072
13367
  try {
13073
13368
  await cascadeUnblock(taskId, input.baseDir, now);
@@ -14072,7 +14367,21 @@ function tryAcquireWorkerSlot() {
14072
14367
  for (const f of files) {
14073
14368
  if (!f.endsWith(".pid")) continue;
14074
14369
  if (f.startsWith("res-")) {
14075
- alive++;
14370
+ const resParts = f.replace(".pid", "").split("-");
14371
+ const resPid = parseInt(resParts[1] ?? "", 10);
14372
+ if (!isNaN(resPid) && resPid > 0) {
14373
+ try {
14374
+ process.kill(resPid, 0);
14375
+ alive++;
14376
+ } catch {
14377
+ try {
14378
+ unlinkSync9(path31.join(WORKER_PID_DIR, f));
14379
+ } catch {
14380
+ }
14381
+ }
14382
+ } else {
14383
+ alive++;
14384
+ }
14076
14385
  continue;
14077
14386
  }
14078
14387
  const dashIdx = f.lastIndexOf("-");
@@ -20871,6 +21180,7 @@ var IDLE_KILL_INTERCOM_ACK_WINDOW_MS = Number(process.env.EXE_INTERCOM_ACK_WINDO
20871
21180
  var NUDGE_STATE_PATH = join4(homedir6(), ".exe-os", "review-nudge-state.json");
20872
21181
  var AUTO_WAKE_COOLDOWN_MS = 5 * 60 * 1e3;
20873
21182
  var AUTO_WAKE_MAX_RETRIES = 3;
21183
+ var STUCK_TASK_GRACE_MS = 5 * 60 * 1e3;
20874
21184
 
20875
21185
  // src/mcp/tools/get-auto-wake-status.ts
20876
21186
  init_employees();
@@ -22569,6 +22879,15 @@ async function fetchWithRetry(url, init) {
22569
22879
  }
22570
22880
  throw lastError;
22571
22881
  }
22882
+ function migrateEndpoint(endpoint2) {
22883
+ if (endpoint2 === "https://askexe.com/cloud" || endpoint2 === "https://askexe.com/cloud/") {
22884
+ process.stderr.write(
22885
+ "[cloud-sync] Auto-migrating endpoint from askexe.com/cloud to cloud.askexe.com (bypasses Cloudflare WAF for datacenter IPs)\n"
22886
+ );
22887
+ return "https://cloud.askexe.com";
22888
+ }
22889
+ return endpoint2;
22890
+ }
22572
22891
  function assertSecureEndpoint(endpoint2) {
22573
22892
  if (endpoint2.startsWith("https://")) return;
22574
22893
  if (endpoint2.startsWith("http://")) {
@@ -22690,6 +23009,7 @@ async function markCloudReuploadRequired(client = getClient()) {
22690
23009
  await client.execute("INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('cloud_reupload_required', '1')");
22691
23010
  }
22692
23011
  async function cloudSync(config2) {
23012
+ config2 = { ...config2, endpoint: migrateEndpoint(config2.endpoint) };
22693
23013
  if (!isSyncCryptoInitialized()) {
22694
23014
  try {
22695
23015
  const { getMasterKey: getMasterKey2 } = await Promise.resolve().then(() => (init_keychain(), keychain_exports));