@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
@@ -1861,6 +1861,33 @@ var DANGEROUS_PATTERNS = [
1861
1861
  regex: /\bkill\s+-9\b/,
1862
1862
  severity: "warning",
1863
1863
  reason: "Force kill signal"
1864
+ },
1865
+ // MCP bypass — agents must use MCP tools, never access the DB directly.
1866
+ // These patterns catch attempts to work around a disconnected MCP server.
1867
+ {
1868
+ regex: /\bsqlite3\b.*\bmemories\.db\b/,
1869
+ severity: "critical",
1870
+ reason: "Direct SQLite access bypasses MCP contract boundary \u2014 use MCP tools"
1871
+ },
1872
+ {
1873
+ regex: /\bsqlite3\b.*\.exe-os\b/,
1874
+ severity: "critical",
1875
+ reason: "Direct SQLite access to exe-os database \u2014 use MCP tools"
1876
+ },
1877
+ {
1878
+ regex: /\bnode\s+-e\b.*\b(better-sqlite3|libsql|sqlite3)\b/,
1879
+ severity: "critical",
1880
+ reason: "Inline Node.js script accessing SQLite directly \u2014 use MCP tools"
1881
+ },
1882
+ {
1883
+ regex: /\brequire\s*\(\s*['"].*memories\.db['"]\s*\)/,
1884
+ severity: "critical",
1885
+ reason: "Direct require of memories database \u2014 use MCP tools"
1886
+ },
1887
+ {
1888
+ regex: /\bcat\b.*\bmemories\.db\b/,
1889
+ severity: "warning",
1890
+ reason: "Reading raw database file \u2014 encrypted data, use MCP tools instead"
1864
1891
  }
1865
1892
  ];
1866
1893
  function checkDangerousPatterns(command) {
@@ -129,6 +129,17 @@ function normalizeOrchestration(raw) {
129
129
  const userOrg = raw.orchestration ?? {};
130
130
  raw.orchestration = { ...defaultOrg, ...userOrg };
131
131
  }
132
+ function normalizeCloudEndpoint(raw) {
133
+ const cloud = raw.cloud;
134
+ if (!cloud?.endpoint) return;
135
+ const ep = String(cloud.endpoint);
136
+ if (ep === "https://askexe.com/cloud" || ep === "https://askexe.com/cloud/") {
137
+ cloud.endpoint = "https://cloud.askexe.com";
138
+ process.stderr.write(
139
+ "[config] Auto-migrated cloud endpoint: askexe.com/cloud \u2192 cloud.askexe.com\n"
140
+ );
141
+ }
142
+ }
132
143
  async function loadConfig() {
133
144
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
134
145
  await ensurePrivateDir(dir);
@@ -154,6 +165,7 @@ async function loadConfig() {
154
165
  normalizeSessionLifecycle(migratedCfg);
155
166
  normalizeAutoUpdate(migratedCfg);
156
167
  normalizeOrchestration(migratedCfg);
168
+ normalizeCloudEndpoint(migratedCfg);
157
169
  const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
158
170
  if (config.dbPath.startsWith("~")) {
159
171
  config.dbPath = config.dbPath.replace(/^~/, os.homedir());
@@ -4681,7 +4693,7 @@ init_memory();
4681
4693
  init_database();
4682
4694
 
4683
4695
  // src/lib/keychain.ts
4684
- import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
4696
+ import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2, rename, copyFile } from "fs/promises";
4685
4697
  import { existsSync as existsSync7, statSync as statSync3 } from "fs";
4686
4698
  import { execSync as execSync3 } from "child_process";
4687
4699
  import path6 from "path";
@@ -4720,12 +4732,14 @@ function linuxSecretAvailable() {
4720
4732
  function isRootOnlyTrustedServerKeyFile(keyPath) {
4721
4733
  if (process.platform !== "linux") return false;
4722
4734
  try {
4723
- const uid = typeof os5.userInfo().uid === "number" ? os5.userInfo().uid : -1;
4724
4735
  const st = statSync3(keyPath);
4725
4736
  if (!st.isFile() || (st.mode & 63) !== 0) return false;
4737
+ const uid = typeof os5.userInfo().uid === "number" ? os5.userInfo().uid : -1;
4726
4738
  if (uid === 0) return true;
4727
4739
  const exeOsDir = process.env.EXE_OS_DIR;
4728
- return Boolean(exeOsDir && path6.resolve(keyPath).startsWith(path6.resolve(exeOsDir) + path6.sep));
4740
+ if (exeOsDir && path6.resolve(keyPath).startsWith(path6.resolve(exeOsDir) + path6.sep)) return true;
4741
+ if (!linuxSecretAvailable()) return true;
4742
+ return false;
4729
4743
  } catch {
4730
4744
  return false;
4731
4745
  }
@@ -4876,15 +4890,25 @@ async function writeMachineBoundFileFallback(b64) {
4876
4890
  await mkdir3(dir, { recursive: true });
4877
4891
  const keyPath = getKeyPath();
4878
4892
  const machineKey = deriveMachineKey();
4879
- if (machineKey) {
4880
- const encrypted = encryptWithMachineKey(b64, machineKey);
4881
- await writeFile3(keyPath, encrypted + "\n", "utf-8");
4882
- await chmod2(keyPath, 384);
4883
- return "encrypted";
4893
+ const content = machineKey ? encryptWithMachineKey(b64, machineKey) + "\n" : b64 + "\n";
4894
+ const result = machineKey ? "encrypted" : "plaintext";
4895
+ const tmpPath = keyPath + ".tmp";
4896
+ try {
4897
+ if (existsSync7(keyPath)) {
4898
+ await copyFile(keyPath, keyPath + ".bak").catch(() => {
4899
+ });
4900
+ }
4901
+ await writeFile3(tmpPath, content, "utf-8");
4902
+ await chmod2(tmpPath, 384);
4903
+ await rename(tmpPath, keyPath);
4904
+ } catch (err) {
4905
+ try {
4906
+ await unlink(tmpPath);
4907
+ } catch {
4908
+ }
4909
+ throw err;
4884
4910
  }
4885
- await writeFile3(keyPath, b64 + "\n", "utf-8");
4886
- await chmod2(keyPath, 384);
4887
- return "plaintext";
4911
+ return result;
4888
4912
  }
4889
4913
  async function getMasterKey() {
4890
4914
  let nativeValue = macKeychainGet() ?? linuxSecretGet();
@@ -4951,7 +4975,7 @@ async function getMasterKey() {
4951
4975
  b64Value = content;
4952
4976
  }
4953
4977
  const key = Buffer.from(b64Value, "base64");
4954
- if (!content.startsWith(ENCRYPTED_PREFIX) && isRootOnlyTrustedServerKeyFile(keyPath)) {
4978
+ if (isRootOnlyTrustedServerKeyFile(keyPath)) {
4955
4979
  return key;
4956
4980
  }
4957
4981
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
@@ -155,6 +155,17 @@ function normalizeOrchestration(raw) {
155
155
  const userOrg = raw.orchestration ?? {};
156
156
  raw.orchestration = { ...defaultOrg, ...userOrg };
157
157
  }
158
+ function normalizeCloudEndpoint(raw) {
159
+ const cloud = raw.cloud;
160
+ if (!cloud?.endpoint) return;
161
+ const ep = String(cloud.endpoint);
162
+ if (ep === "https://askexe.com/cloud" || ep === "https://askexe.com/cloud/") {
163
+ cloud.endpoint = "https://cloud.askexe.com";
164
+ process.stderr.write(
165
+ "[config] Auto-migrated cloud endpoint: askexe.com/cloud \u2192 cloud.askexe.com\n"
166
+ );
167
+ }
168
+ }
158
169
  async function loadConfig() {
159
170
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
160
171
  await ensurePrivateDir(dir);
@@ -180,6 +191,7 @@ async function loadConfig() {
180
191
  normalizeSessionLifecycle(migratedCfg);
181
192
  normalizeAutoUpdate(migratedCfg);
182
193
  normalizeOrchestration(migratedCfg);
194
+ normalizeCloudEndpoint(migratedCfg);
183
195
  const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
184
196
  if (config.dbPath.startsWith("~")) {
185
197
  config.dbPath = config.dbPath.replace(/^~/, os.homedir());
@@ -208,6 +220,7 @@ function loadConfigSync() {
208
220
  normalizeSessionLifecycle(migratedCfg);
209
221
  normalizeAutoUpdate(migratedCfg);
210
222
  normalizeOrchestration(migratedCfg);
223
+ normalizeCloudEndpoint(migratedCfg);
211
224
  const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
212
225
  if (config.dbPath.startsWith("~")) {
213
226
  config.dbPath = config.dbPath.replace(/^~/, os.homedir());
@@ -368,6 +381,7 @@ __export(agent_config_exports, {
368
381
  clearAgentRuntime: () => clearAgentRuntime,
369
382
  getAgentRuntime: () => getAgentRuntime,
370
383
  loadAgentConfig: () => loadAgentConfig,
384
+ normalizeCcModelName: () => normalizeCcModelName,
371
385
  saveAgentConfig: () => saveAgentConfig,
372
386
  setAgentMcps: () => setAgentMcps,
373
387
  setAgentRuntime: () => setAgentRuntime
@@ -396,6 +410,13 @@ function getAgentRuntime(agentId) {
396
410
  if (orgDefault) return orgDefault;
397
411
  return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
398
412
  }
413
+ function normalizeCcModelName(model) {
414
+ let ccModel = model.replace(/(\d+)\.(\d+)/g, "$1-$2");
415
+ if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
416
+ ccModel += "[1m]";
417
+ }
418
+ return ccModel;
419
+ }
399
420
  function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
400
421
  const knownModels = KNOWN_RUNTIMES[runtime];
401
422
  if (!knownModels) {
@@ -4060,7 +4081,7 @@ __export(keychain_exports, {
4060
4081
  importMnemonic: () => importMnemonic,
4061
4082
  setMasterKey: () => setMasterKey
4062
4083
  });
4063
- import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
4084
+ import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2, rename, copyFile } from "fs/promises";
4064
4085
  import { existsSync as existsSync8, statSync as statSync3 } from "fs";
4065
4086
  import { execSync as execSync3 } from "child_process";
4066
4087
  import path7 from "path";
@@ -4095,12 +4116,14 @@ function linuxSecretAvailable() {
4095
4116
  function isRootOnlyTrustedServerKeyFile(keyPath) {
4096
4117
  if (process.platform !== "linux") return false;
4097
4118
  try {
4098
- const uid = typeof os5.userInfo().uid === "number" ? os5.userInfo().uid : -1;
4099
4119
  const st = statSync3(keyPath);
4100
4120
  if (!st.isFile() || (st.mode & 63) !== 0) return false;
4121
+ const uid = typeof os5.userInfo().uid === "number" ? os5.userInfo().uid : -1;
4101
4122
  if (uid === 0) return true;
4102
4123
  const exeOsDir = process.env.EXE_OS_DIR;
4103
- return Boolean(exeOsDir && path7.resolve(keyPath).startsWith(path7.resolve(exeOsDir) + path7.sep));
4124
+ if (exeOsDir && path7.resolve(keyPath).startsWith(path7.resolve(exeOsDir) + path7.sep)) return true;
4125
+ if (!linuxSecretAvailable()) return true;
4126
+ return false;
4104
4127
  } catch {
4105
4128
  return false;
4106
4129
  }
@@ -4250,15 +4273,25 @@ async function writeMachineBoundFileFallback(b64) {
4250
4273
  await mkdir3(dir, { recursive: true });
4251
4274
  const keyPath = getKeyPath();
4252
4275
  const machineKey = deriveMachineKey();
4253
- if (machineKey) {
4254
- const encrypted = encryptWithMachineKey(b64, machineKey);
4255
- await writeFile3(keyPath, encrypted + "\n", "utf-8");
4256
- await chmod2(keyPath, 384);
4257
- return "encrypted";
4276
+ const content = machineKey ? encryptWithMachineKey(b64, machineKey) + "\n" : b64 + "\n";
4277
+ const result = machineKey ? "encrypted" : "plaintext";
4278
+ const tmpPath = keyPath + ".tmp";
4279
+ try {
4280
+ if (existsSync8(keyPath)) {
4281
+ await copyFile(keyPath, keyPath + ".bak").catch(() => {
4282
+ });
4283
+ }
4284
+ await writeFile3(tmpPath, content, "utf-8");
4285
+ await chmod2(tmpPath, 384);
4286
+ await rename(tmpPath, keyPath);
4287
+ } catch (err) {
4288
+ try {
4289
+ await unlink(tmpPath);
4290
+ } catch {
4291
+ }
4292
+ throw err;
4258
4293
  }
4259
- await writeFile3(keyPath, b64 + "\n", "utf-8");
4260
- await chmod2(keyPath, 384);
4261
- return "plaintext";
4294
+ return result;
4262
4295
  }
4263
4296
  async function getMasterKey() {
4264
4297
  let nativeValue = macKeychainGet() ?? linuxSecretGet();
@@ -4325,7 +4358,7 @@ async function getMasterKey() {
4325
4358
  b64Value = content;
4326
4359
  }
4327
4360
  const key = Buffer.from(b64Value, "base64");
4328
- if (!content.startsWith(ENCRYPTED_PREFIX) && isRootOnlyTrustedServerKeyFile(keyPath)) {
4361
+ if (isRootOnlyTrustedServerKeyFile(keyPath)) {
4329
4362
  return key;
4330
4363
  }
4331
4364
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
@@ -5512,6 +5545,7 @@ var init_provider_table = __esm({
5512
5545
  // src/lib/intercom-queue.ts
5513
5546
  var intercom_queue_exports = {};
5514
5547
  __export(intercom_queue_exports, {
5548
+ _resetDrainGuard: () => _resetDrainGuard,
5515
5549
  clearQueueForAgent: () => clearQueueForAgent,
5516
5550
  drainForSession: () => drainForSession,
5517
5551
  drainQueue: () => drainQueue,
@@ -5557,38 +5591,47 @@ function queueIntercom(targetSession, reason) {
5557
5591
  writeQueue(queue);
5558
5592
  }
5559
5593
  function drainQueue(isSessionBusy2, sendKeys) {
5594
+ if (_draining) {
5595
+ logQueue("SKIP_DRAIN \u2014 previous drain still running (possible tmux hang)");
5596
+ return { drained: 0, failed: 0 };
5597
+ }
5560
5598
  const queue = readQueue();
5561
5599
  if (queue.length === 0) return { drained: 0, failed: 0 };
5600
+ _draining = true;
5562
5601
  const remaining = [];
5563
5602
  let drained = 0;
5564
5603
  let failed = 0;
5565
- for (const item of queue) {
5566
- const age = Date.now() - new Date(item.queuedAt).getTime();
5567
- if (age > TTL_MS) {
5568
- logQueue(`EXPIRED \u2192 ${item.targetSession} (${Math.round(age / 6e4)}min old, reason: ${item.reason})`);
5569
- failed++;
5570
- continue;
5571
- }
5572
- try {
5573
- if (!isSessionBusy2(item.targetSession)) {
5574
- const success = sendKeys(item.targetSession);
5575
- if (success) {
5576
- logQueue(`DRAINED \u2192 ${item.targetSession} (after ${item.attempts} retries)`);
5577
- drained++;
5578
- continue;
5604
+ try {
5605
+ for (const item of queue) {
5606
+ const age = Date.now() - new Date(item.queuedAt).getTime();
5607
+ if (age > TTL_MS) {
5608
+ logQueue(`EXPIRED \u2192 ${item.targetSession} (${Math.round(age / 6e4)}min old, reason: ${item.reason})`);
5609
+ failed++;
5610
+ continue;
5611
+ }
5612
+ try {
5613
+ if (!isSessionBusy2(item.targetSession)) {
5614
+ const success = sendKeys(item.targetSession);
5615
+ if (success) {
5616
+ logQueue(`DRAINED \u2192 ${item.targetSession} (after ${item.attempts} retries)`);
5617
+ drained++;
5618
+ continue;
5619
+ }
5579
5620
  }
5621
+ } catch {
5580
5622
  }
5581
- } catch {
5582
- }
5583
- item.attempts++;
5584
- if (item.attempts >= MAX_RETRIES2) {
5585
- logQueue(`FAILED \u2192 ${item.targetSession} (${MAX_RETRIES2} retries exhausted, reason: ${item.reason})`);
5586
- failed++;
5587
- continue;
5623
+ item.attempts++;
5624
+ if (item.attempts >= MAX_RETRIES2) {
5625
+ logQueue(`FAILED \u2192 ${item.targetSession} (${MAX_RETRIES2} retries exhausted, reason: ${item.reason})`);
5626
+ failed++;
5627
+ continue;
5628
+ }
5629
+ remaining.push(item);
5588
5630
  }
5589
- remaining.push(item);
5631
+ writeQueue(remaining);
5632
+ } finally {
5633
+ _draining = false;
5590
5634
  }
5591
- writeQueue(remaining);
5592
5635
  return { drained, failed };
5593
5636
  }
5594
5637
  function drainForSession(targetSession, sendKeys) {
@@ -5613,6 +5656,9 @@ function clearQueueForAgent(agentName) {
5613
5656
  logQueue(`CLEARED ${before - filtered.length} stale item(s) for ${agentName}`);
5614
5657
  }
5615
5658
  }
5659
+ function _resetDrainGuard() {
5660
+ _draining = false;
5661
+ }
5616
5662
  function logQueue(msg) {
5617
5663
  const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [queue] ${msg}
5618
5664
  `;
@@ -5624,13 +5670,14 @@ function logQueue(msg) {
5624
5670
  } catch {
5625
5671
  }
5626
5672
  }
5627
- var QUEUE_PATH, MAX_RETRIES2, TTL_MS, INTERCOM_LOG;
5673
+ var QUEUE_PATH, MAX_RETRIES2, TTL_MS, _draining, INTERCOM_LOG;
5628
5674
  var init_intercom_queue = __esm({
5629
5675
  "src/lib/intercom-queue.ts"() {
5630
5676
  "use strict";
5631
5677
  QUEUE_PATH = path10.join(os7.homedir(), ".exe-os", "intercom-queue.json");
5632
5678
  MAX_RETRIES2 = 5;
5633
5679
  TTL_MS = 60 * 60 * 1e3;
5680
+ _draining = false;
5634
5681
  INTERCOM_LOG = path10.join(os7.homedir(), ".exe-os", "intercom.log");
5635
5682
  }
5636
5683
  });
@@ -6245,6 +6292,17 @@ var init_agent_symlinks = __esm({
6245
6292
  });
6246
6293
 
6247
6294
  // src/lib/notifications.ts
6295
+ var notifications_exports = {};
6296
+ __export(notifications_exports, {
6297
+ cleanupOldNotifications: () => cleanupOldNotifications,
6298
+ formatNotifications: () => formatNotifications,
6299
+ markAsRead: () => markAsRead,
6300
+ markAsReadByTaskFile: () => markAsReadByTaskFile,
6301
+ markDoneTaskNotificationsAsRead: () => markDoneTaskNotificationsAsRead,
6302
+ migrateJsonNotifications: () => migrateJsonNotifications,
6303
+ readUnreadNotifications: () => readUnreadNotifications,
6304
+ writeNotification: () => writeNotification
6305
+ });
6248
6306
  import crypto2 from "crypto";
6249
6307
  import path14 from "path";
6250
6308
  import os10 from "os";
@@ -6374,6 +6432,29 @@ async function markDoneTaskNotificationsAsRead(sessionScope) {
6374
6432
  return 0;
6375
6433
  }
6376
6434
  }
6435
+ function formatNotifications(notifications) {
6436
+ if (notifications.length === 0) return "";
6437
+ const grouped = /* @__PURE__ */ new Map();
6438
+ for (const n of notifications) {
6439
+ const key = `${n.agentId}|${n.agentRole}`;
6440
+ if (!grouped.has(key)) grouped.set(key, []);
6441
+ grouped.get(key).push(n);
6442
+ }
6443
+ const lines = [];
6444
+ lines.push(`## Notifications (${notifications.length} unread)
6445
+ `);
6446
+ for (const [key, items] of grouped) {
6447
+ const [agentId, agentRole] = key.split("|");
6448
+ lines.push(`**${agentId}** (${agentRole}):`);
6449
+ for (const item of items) {
6450
+ const ago = formatTimeAgo(item.timestamp);
6451
+ const icon = eventIcon(item.event);
6452
+ lines.push(`- ${icon} ${item.summary} (${item.project}) \u2014 ${ago}`);
6453
+ }
6454
+ lines.push("");
6455
+ }
6456
+ return lines.join("\n");
6457
+ }
6377
6458
  async function migrateJsonNotifications() {
6378
6459
  const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR || path14.join(os10.homedir(), ".exe-os");
6379
6460
  const notifDir = path14.join(base, "notifications");
@@ -6419,6 +6500,34 @@ async function migrateJsonNotifications() {
6419
6500
  }
6420
6501
  return migrated;
6421
6502
  }
6503
+ function eventIcon(event) {
6504
+ switch (event) {
6505
+ case "task_complete":
6506
+ return "Completed:";
6507
+ case "task_needs_fix":
6508
+ return "Needs fix:";
6509
+ case "session_summary":
6510
+ return "Session:";
6511
+ case "error_spike":
6512
+ return "Errors:";
6513
+ case "orphan_task":
6514
+ return "Orphan:";
6515
+ case "subtasks_complete":
6516
+ return "Subtasks done:";
6517
+ case "capacity_relaunch":
6518
+ return "Relaunched:";
6519
+ }
6520
+ }
6521
+ function formatTimeAgo(timestamp) {
6522
+ const diffMs = Date.now() - new Date(timestamp).getTime();
6523
+ const mins = Math.floor(diffMs / 6e4);
6524
+ if (mins < 1) return "just now";
6525
+ if (mins < 60) return `${mins}m ago`;
6526
+ const hours = Math.floor(mins / 60);
6527
+ if (hours < 24) return `${hours}h ago`;
6528
+ const days = Math.floor(hours / 24);
6529
+ return `${days}d ago`;
6530
+ }
6422
6531
  var CLEANUP_DAYS;
6423
6532
  var init_notifications = __esm({
6424
6533
  "src/lib/notifications.ts"() {
@@ -7495,7 +7604,13 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
7495
7604
  taskFile
7496
7605
  });
7497
7606
  const originalPriority = String(row.priority).toLowerCase();
7498
- const autoApprove = originalPriority === "p2" && result?.toLowerCase().includes("tests pass");
7607
+ const resultLower = result?.toLowerCase() ?? "";
7608
+ const hasTestEvidence = (
7609
+ // Vitest/Jest output patterns (hard to fake without actually running tests)
7610
+ /\d+\s+pass(ed|ing)/.test(resultLower) || /test files?\s+\d+\s+passed/.test(resultLower) || /tests?\s+\d+\s+passed/.test(resultLower)
7611
+ );
7612
+ const hasNoFailures = !/fail(ed|ure|ing)|error/i.test(resultLower);
7613
+ const autoApprove = originalPriority === "p2" && hasTestEvidence && hasNoFailures;
7499
7614
  if (!autoApprove) {
7500
7615
  try {
7501
7616
  const key = getSessionKey();
@@ -7503,6 +7618,13 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
7503
7618
  if (exeSession) {
7504
7619
  sendIntercom(exeSession);
7505
7620
  }
7621
+ if (reviewer && reviewer !== coordinatorName && reviewer !== exeSession) {
7622
+ const { employeeSessionName: employeeSessionName2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
7623
+ if (exeSession) {
7624
+ const reviewerSession = employeeSessionName2(reviewer, exeSession);
7625
+ sendIntercom(reviewerSession);
7626
+ }
7627
+ }
7506
7628
  } catch {
7507
7629
  }
7508
7630
  }
@@ -8278,6 +8400,20 @@ async function updateTask(input) {
8278
8400
  notifyTaskDone();
8279
8401
  }
8280
8402
  await markTaskNotificationsRead(taskFile);
8403
+ if (input.status === "needs_review" && !isCoordinator) {
8404
+ try {
8405
+ const { writeNotification: writeNotification2 } = await Promise.resolve().then(() => (init_notifications(), notifications_exports));
8406
+ await writeNotification2({
8407
+ agentId: String(row.assigned_to),
8408
+ agentRole: String(row.assigned_to),
8409
+ event: "task_complete",
8410
+ project: String(row.project_name),
8411
+ summary: `"${String(row.title)}" is ready for review`,
8412
+ taskFile
8413
+ });
8414
+ } catch {
8415
+ }
8416
+ }
8281
8417
  if (input.status === "done" || input.status === "closed") {
8282
8418
  try {
8283
8419
  await cascadeUnblock(taskId, input.baseDir, now);
@@ -8710,18 +8846,31 @@ function acquireSpawnLock2(sessionName) {
8710
8846
  mkdirSync9(SPAWN_LOCK_DIR, { recursive: true });
8711
8847
  }
8712
8848
  const lockFile = spawnLockPath(sessionName);
8713
- if (existsSync18(lockFile)) {
8714
- try {
8715
- const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
8716
- const age = Date.now() - lock.timestamp;
8717
- if (isProcessAlive(lock.pid) && age < 6e4) {
8718
- return false;
8719
- }
8720
- } catch {
8849
+ const lockData = JSON.stringify({ pid: process.pid, timestamp: Date.now() });
8850
+ const { openSync: openSync4, closeSync: closeSync4, writeSync } = __require("fs");
8851
+ const { constants: constants2 } = __require("fs");
8852
+ try {
8853
+ const fd = openSync4(lockFile, constants2.O_WRONLY | constants2.O_CREAT | constants2.O_EXCL, 420);
8854
+ writeSync(fd, lockData);
8855
+ closeSync4(fd);
8856
+ return true;
8857
+ } catch (err) {
8858
+ if (err?.code !== "EEXIST") {
8859
+ return true;
8721
8860
  }
8722
8861
  }
8723
- writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
8724
- return true;
8862
+ try {
8863
+ const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
8864
+ const age = Date.now() - lock.timestamp;
8865
+ if (isProcessAlive(lock.pid) && age < 6e4) {
8866
+ return false;
8867
+ }
8868
+ writeFileSync8(lockFile, lockData);
8869
+ return true;
8870
+ } catch {
8871
+ writeFileSync8(lockFile, lockData);
8872
+ return true;
8873
+ }
8725
8874
  }
8726
8875
  function releaseSpawnLock2(sessionName) {
8727
8876
  try {
@@ -8800,6 +8949,21 @@ function parseParentExe(sessionName, agentId) {
8800
8949
  function extractRootExe(name) {
8801
8950
  if (!name) return null;
8802
8951
  if (!name.includes("-")) return name;
8952
+ try {
8953
+ const roster = (init_employees(), __toCommonJS(employees_exports)).loadEmployeesSync();
8954
+ if (roster.length > 0) {
8955
+ const sortedNames = roster.map((e) => e.name).sort((a, b) => b.length - a.length);
8956
+ for (const agentName of sortedNames) {
8957
+ const escaped = agentName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8958
+ const regex = new RegExp(`^${escaped}\\d*-(.+)$`);
8959
+ const match = name.match(regex);
8960
+ if (match) {
8961
+ return extractRootExe(match[1]);
8962
+ }
8963
+ }
8964
+ }
8965
+ } catch {
8966
+ }
8803
8967
  const parts = name.split("-").filter(Boolean);
8804
8968
  return parts.length > 0 ? parts[parts.length - 1] : null;
8805
8969
  }
@@ -8818,6 +8982,10 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
8818
8982
  function getParentExe(sessionKey) {
8819
8983
  try {
8820
8984
  const data = JSON.parse(readFileSync12(path20.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
8985
+ if (data.registeredAt) {
8986
+ const age = Date.now() - new Date(data.registeredAt).getTime();
8987
+ if (age > PARENT_EXE_CACHE_TTL_MS) return null;
8988
+ }
8821
8989
  return data.parentExe || null;
8822
8990
  } catch {
8823
8991
  return null;
@@ -9366,7 +9534,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
9366
9534
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
9367
9535
  } catch {
9368
9536
  }
9369
- let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName}`;
9537
+ let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName} EXE_SESSION_START_ISO=${(/* @__PURE__ */ new Date()).toISOString()}`;
9370
9538
  if (ccProvider !== DEFAULT_PROVIDER) {
9371
9539
  const cfg = PROVIDER_TABLE[ccProvider];
9372
9540
  if (cfg?.apiKeyEnv) {
@@ -9401,10 +9569,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
9401
9569
  }
9402
9570
  if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
9403
9571
  if (agentRtConfig.runtime === "claude" && agentRtConfig.model) {
9404
- let ccModel = agentRtConfig.model.replace(/(\d+)\.(\d+)/g, "$1-$2");
9405
- if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
9406
- ccModel += "[1m]";
9407
- }
9572
+ const { normalizeCcModelName: normalizeCcModelName2 } = (init_agent_config(), __toCommonJS(agent_config_exports));
9573
+ const ccModel = normalizeCcModelName2(agentRtConfig.model);
9408
9574
  envPrefix = `${envPrefix} ANTHROPIC_MODEL=${ccModel}`;
9409
9575
  }
9410
9576
  }
@@ -9505,7 +9671,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
9505
9671
  releaseSpawnLock2(sessionName);
9506
9672
  return { sessionName };
9507
9673
  }
9508
- 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;
9674
+ 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;
9509
9675
  var init_tmux_routing = __esm({
9510
9676
  "src/lib/tmux-routing.ts"() {
9511
9677
  "use strict";
@@ -9525,6 +9691,7 @@ var init_tmux_routing = __esm({
9525
9691
  SESSION_CACHE = path20.join(os12.homedir(), ".exe-os", "session-cache");
9526
9692
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
9527
9693
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
9694
+ PARENT_EXE_CACHE_TTL_MS = 4 * 60 * 60 * 1e3;
9528
9695
  VERIFY_PANE_LINES = 200;
9529
9696
  INTERCOM_DEBOUNCE_MS = 3e4;
9530
9697
  CODEX_DEBOUNCE_MS = 12e4;
@@ -9608,7 +9775,21 @@ function tryAcquireWorkerSlot() {
9608
9775
  for (const f of files) {
9609
9776
  if (!f.endsWith(".pid")) continue;
9610
9777
  if (f.startsWith("res-")) {
9611
- alive++;
9778
+ const resParts = f.replace(".pid", "").split("-");
9779
+ const resPid = parseInt(resParts[1] ?? "", 10);
9780
+ if (!isNaN(resPid) && resPid > 0) {
9781
+ try {
9782
+ process.kill(resPid, 0);
9783
+ alive++;
9784
+ } catch {
9785
+ try {
9786
+ unlinkSync9(path22.join(WORKER_PID_DIR, f));
9787
+ } catch {
9788
+ }
9789
+ }
9790
+ } else {
9791
+ alive++;
9792
+ }
9612
9793
  continue;
9613
9794
  }
9614
9795
  const dashIdx = f.lastIndexOf("-");
@@ -10151,6 +10332,7 @@ __export(cloud_sync_exports, {
10151
10332
  markCloudReuploadRequired: () => markCloudReuploadRequired,
10152
10333
  mergeConfig: () => mergeConfig,
10153
10334
  mergeRosterFromRemote: () => mergeRosterFromRemote,
10335
+ migrateEndpoint: () => migrateEndpoint,
10154
10336
  pushToPostgres: () => pushToPostgres,
10155
10337
  recordRosterDeletion: () => recordRosterDeletion
10156
10338
  });
@@ -10321,6 +10503,15 @@ async function fetchWithRetry(url, init) {
10321
10503
  }
10322
10504
  throw lastError;
10323
10505
  }
10506
+ function migrateEndpoint(endpoint) {
10507
+ if (endpoint === "https://askexe.com/cloud" || endpoint === "https://askexe.com/cloud/") {
10508
+ process.stderr.write(
10509
+ "[cloud-sync] Auto-migrating endpoint from askexe.com/cloud to cloud.askexe.com (bypasses Cloudflare WAF for datacenter IPs)\n"
10510
+ );
10511
+ return "https://cloud.askexe.com";
10512
+ }
10513
+ return endpoint;
10514
+ }
10324
10515
  function assertSecureEndpoint(endpoint) {
10325
10516
  if (endpoint.startsWith("https://")) return;
10326
10517
  if (endpoint.startsWith("http://")) {
@@ -10458,6 +10649,7 @@ async function markCloudReuploadRequired(client = getClient()) {
10458
10649
  await client.execute("INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('cloud_reupload_required', '1')");
10459
10650
  }
10460
10651
  async function cloudSync(config) {
10652
+ config = { ...config, endpoint: migrateEndpoint(config.endpoint) };
10461
10653
  if (!isSyncCryptoInitialized()) {
10462
10654
  try {
10463
10655
  const { getMasterKey: getMasterKey2 } = await Promise.resolve().then(() => (init_keychain(), keychain_exports));
@@ -1263,6 +1263,7 @@ __export(agent_config_exports, {
1263
1263
  clearAgentRuntime: () => clearAgentRuntime,
1264
1264
  getAgentRuntime: () => getAgentRuntime,
1265
1265
  loadAgentConfig: () => loadAgentConfig,
1266
+ normalizeCcModelName: () => normalizeCcModelName,
1266
1267
  saveAgentConfig: () => saveAgentConfig,
1267
1268
  setAgentMcps: () => setAgentMcps,
1268
1269
  setAgentRuntime: () => setAgentRuntime
@@ -1291,6 +1292,13 @@ function getAgentRuntime(agentId) {
1291
1292
  if (orgDefault) return orgDefault;
1292
1293
  return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
1293
1294
  }
1295
+ function normalizeCcModelName(model) {
1296
+ let ccModel = model.replace(/(\d+)\.(\d+)/g, "$1-$2");
1297
+ if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
1298
+ ccModel += "[1m]";
1299
+ }
1300
+ return ccModel;
1301
+ }
1294
1302
  function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
1295
1303
  const knownModels = KNOWN_RUNTIMES[runtime];
1296
1304
  if (!knownModels) {