@askexenow/exe-os 0.9.113 → 0.9.115

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/dist/bin/agentic-ontology-backfill.js +36 -12
  2. package/dist/bin/agentic-reflection-backfill.js +36 -12
  3. package/dist/bin/agentic-semantic-label.js +36 -12
  4. package/dist/bin/backfill-conversations.js +36 -12
  5. package/dist/bin/backfill-responses.js +36 -12
  6. package/dist/bin/backfill-vectors.js +36 -12
  7. package/dist/bin/bulk-sync-postgres.js +36 -12
  8. package/dist/bin/cleanup-stale-review-tasks.js +470 -113
  9. package/dist/bin/cli.js +413 -62
  10. package/dist/bin/exe-agent.js +27 -0
  11. package/dist/bin/exe-assign.js +36 -12
  12. package/dist/bin/exe-boot.js +246 -54
  13. package/dist/bin/exe-call.js +8 -0
  14. package/dist/bin/exe-cloud.js +47 -12
  15. package/dist/bin/exe-dispatch.js +348 -53
  16. package/dist/bin/exe-doctor.js +51 -13
  17. package/dist/bin/exe-export-behaviors.js +37 -12
  18. package/dist/bin/exe-forget.js +36 -12
  19. package/dist/bin/exe-gateway.js +348 -53
  20. package/dist/bin/exe-heartbeat.js +471 -113
  21. package/dist/bin/exe-kill.js +36 -12
  22. package/dist/bin/exe-launch-agent.js +117 -18
  23. package/dist/bin/exe-new-employee.js +9 -1
  24. package/dist/bin/exe-pending-messages.js +452 -95
  25. package/dist/bin/exe-pending-notifications.js +452 -95
  26. package/dist/bin/exe-pending-reviews.js +452 -95
  27. package/dist/bin/exe-rename.js +36 -12
  28. package/dist/bin/exe-review.js +36 -12
  29. package/dist/bin/exe-search.js +37 -12
  30. package/dist/bin/exe-session-cleanup.js +348 -53
  31. package/dist/bin/exe-settings.js +12 -0
  32. package/dist/bin/exe-start-codex.js +46 -13
  33. package/dist/bin/exe-start-opencode.js +46 -13
  34. package/dist/bin/exe-status.js +460 -114
  35. package/dist/bin/exe-support.js +12 -0
  36. package/dist/bin/exe-team.js +36 -12
  37. package/dist/bin/git-sweep.js +348 -53
  38. package/dist/bin/graph-backfill.js +36 -12
  39. package/dist/bin/graph-export.js +36 -12
  40. package/dist/bin/install.js +9 -1
  41. package/dist/bin/intercom-check.js +255 -53
  42. package/dist/bin/scan-tasks.js +348 -53
  43. package/dist/bin/setup.js +74 -12
  44. package/dist/bin/shard-migrate.js +36 -12
  45. package/dist/gateway/index.js +348 -53
  46. package/dist/hooks/bug-report-worker.js +348 -53
  47. package/dist/hooks/codex-stop-task-finalizer.js +308 -37
  48. package/dist/hooks/commit-complete.js +348 -53
  49. package/dist/hooks/error-recall.js +37 -12
  50. package/dist/hooks/ingest.js +363 -54
  51. package/dist/hooks/instructions-loaded.js +36 -12
  52. package/dist/hooks/notification.js +36 -12
  53. package/dist/hooks/post-compact.js +426 -72
  54. package/dist/hooks/post-tool-combined.js +501 -146
  55. package/dist/hooks/pre-compact.js +348 -53
  56. package/dist/hooks/pre-tool-use.js +92 -13
  57. package/dist/hooks/prompt-submit.js +348 -53
  58. package/dist/hooks/session-end.js +158 -53
  59. package/dist/hooks/session-start.js +66 -13
  60. package/dist/hooks/stop.js +420 -72
  61. package/dist/hooks/subagent-stop.js +419 -72
  62. package/dist/hooks/summary-worker.js +442 -121
  63. package/dist/index.js +375 -53
  64. package/dist/lib/agent-config.js +8 -0
  65. package/dist/lib/cloud-sync.js +35 -12
  66. package/dist/lib/config.js +13 -0
  67. package/dist/lib/consolidation.js +9 -1
  68. package/dist/lib/embedder.js +13 -0
  69. package/dist/lib/employees.js +8 -0
  70. package/dist/lib/exe-daemon.js +524 -60
  71. package/dist/lib/hybrid-search.js +37 -12
  72. package/dist/lib/keychain.js +25 -13
  73. package/dist/lib/messaging.js +395 -74
  74. package/dist/lib/schedules.js +36 -12
  75. package/dist/lib/skill-learning.js +21 -0
  76. package/dist/lib/store.js +36 -12
  77. package/dist/lib/tasks.js +324 -41
  78. package/dist/lib/tmux-routing.js +324 -41
  79. package/dist/mcp/server.js +374 -54
  80. package/dist/mcp/tools/create-task.js +324 -41
  81. package/dist/mcp/tools/list-tasks.js +406 -57
  82. package/dist/mcp/tools/send-message.js +395 -74
  83. package/dist/mcp/tools/update-task.js +324 -41
  84. package/dist/runtime/index.js +375 -53
  85. package/dist/tui/App.js +377 -55
  86. package/package.json +1 -1
@@ -154,6 +154,17 @@ function normalizeOrchestration(raw) {
154
154
  const userOrg = raw.orchestration ?? {};
155
155
  raw.orchestration = { ...defaultOrg, ...userOrg };
156
156
  }
157
+ function normalizeCloudEndpoint(raw) {
158
+ const cloud = raw.cloud;
159
+ if (!cloud?.endpoint) return;
160
+ const ep = String(cloud.endpoint);
161
+ if (ep === "https://askexe.com/cloud" || ep === "https://askexe.com/cloud/") {
162
+ cloud.endpoint = "https://cloud.askexe.com";
163
+ process.stderr.write(
164
+ "[config] Auto-migrated cloud endpoint: askexe.com/cloud \u2192 cloud.askexe.com\n"
165
+ );
166
+ }
167
+ }
157
168
  async function loadConfig() {
158
169
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
159
170
  await ensurePrivateDir(dir);
@@ -179,6 +190,7 @@ async function loadConfig() {
179
190
  normalizeSessionLifecycle(migratedCfg);
180
191
  normalizeAutoUpdate(migratedCfg);
181
192
  normalizeOrchestration(migratedCfg);
193
+ normalizeCloudEndpoint(migratedCfg);
182
194
  const config2 = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
183
195
  if (config2.dbPath.startsWith("~")) {
184
196
  config2.dbPath = config2.dbPath.replace(/^~/, os.homedir());
@@ -207,6 +219,7 @@ function loadConfigSync() {
207
219
  normalizeSessionLifecycle(migratedCfg);
208
220
  normalizeAutoUpdate(migratedCfg);
209
221
  normalizeOrchestration(migratedCfg);
222
+ normalizeCloudEndpoint(migratedCfg);
210
223
  const config2 = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
211
224
  if (config2.dbPath.startsWith("~")) {
212
225
  config2.dbPath = config2.dbPath.replace(/^~/, os.homedir());
@@ -567,6 +580,7 @@ __export(agent_config_exports, {
567
580
  clearAgentRuntime: () => clearAgentRuntime,
568
581
  getAgentRuntime: () => getAgentRuntime,
569
582
  loadAgentConfig: () => loadAgentConfig,
583
+ normalizeCcModelName: () => normalizeCcModelName,
570
584
  saveAgentConfig: () => saveAgentConfig,
571
585
  setAgentMcps: () => setAgentMcps,
572
586
  setAgentRuntime: () => setAgentRuntime
@@ -595,6 +609,13 @@ function getAgentRuntime(agentId) {
595
609
  if (orgDefault) return orgDefault;
596
610
  return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
597
611
  }
612
+ function normalizeCcModelName(model) {
613
+ let ccModel = model.replace(/(\d+)\.(\d+)/g, "$1-$2");
614
+ if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
615
+ ccModel += "[1m]";
616
+ }
617
+ return ccModel;
618
+ }
598
619
  function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
599
620
  const knownModels = KNOWN_RUNTIMES[runtime];
600
621
  if (!knownModels) {
@@ -5510,7 +5531,7 @@ __export(keychain_exports, {
5510
5531
  importMnemonic: () => importMnemonic,
5511
5532
  setMasterKey: () => setMasterKey
5512
5533
  });
5513
- import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
5534
+ import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2, rename, copyFile } from "fs/promises";
5514
5535
  import { existsSync as existsSync12, statSync as statSync5 } from "fs";
5515
5536
  import { execSync as execSync3 } from "child_process";
5516
5537
  import path11 from "path";
@@ -5545,12 +5566,14 @@ function linuxSecretAvailable() {
5545
5566
  function isRootOnlyTrustedServerKeyFile(keyPath) {
5546
5567
  if (process.platform !== "linux") return false;
5547
5568
  try {
5548
- const uid = typeof os6.userInfo().uid === "number" ? os6.userInfo().uid : -1;
5549
5569
  const st = statSync5(keyPath);
5550
5570
  if (!st.isFile() || (st.mode & 63) !== 0) return false;
5571
+ const uid = typeof os6.userInfo().uid === "number" ? os6.userInfo().uid : -1;
5551
5572
  if (uid === 0) return true;
5552
5573
  const exeOsDir = process.env.EXE_OS_DIR;
5553
- return Boolean(exeOsDir && path11.resolve(keyPath).startsWith(path11.resolve(exeOsDir) + path11.sep));
5574
+ if (exeOsDir && path11.resolve(keyPath).startsWith(path11.resolve(exeOsDir) + path11.sep)) return true;
5575
+ if (!linuxSecretAvailable()) return true;
5576
+ return false;
5554
5577
  } catch {
5555
5578
  return false;
5556
5579
  }
@@ -5700,15 +5723,25 @@ async function writeMachineBoundFileFallback(b64) {
5700
5723
  await mkdir3(dir, { recursive: true });
5701
5724
  const keyPath = getKeyPath();
5702
5725
  const machineKey = deriveMachineKey();
5703
- if (machineKey) {
5704
- const encrypted = encryptWithMachineKey(b64, machineKey);
5705
- await writeFile3(keyPath, encrypted + "\n", "utf-8");
5706
- await chmod2(keyPath, 384);
5707
- return "encrypted";
5726
+ const content = machineKey ? encryptWithMachineKey(b64, machineKey) + "\n" : b64 + "\n";
5727
+ const result3 = machineKey ? "encrypted" : "plaintext";
5728
+ const tmpPath = keyPath + ".tmp";
5729
+ try {
5730
+ if (existsSync12(keyPath)) {
5731
+ await copyFile(keyPath, keyPath + ".bak").catch(() => {
5732
+ });
5733
+ }
5734
+ await writeFile3(tmpPath, content, "utf-8");
5735
+ await chmod2(tmpPath, 384);
5736
+ await rename(tmpPath, keyPath);
5737
+ } catch (err) {
5738
+ try {
5739
+ await unlink(tmpPath);
5740
+ } catch {
5741
+ }
5742
+ throw err;
5708
5743
  }
5709
- await writeFile3(keyPath, b64 + "\n", "utf-8");
5710
- await chmod2(keyPath, 384);
5711
- return "plaintext";
5744
+ return result3;
5712
5745
  }
5713
5746
  async function getMasterKey() {
5714
5747
  let nativeValue = macKeychainGet() ?? linuxSecretGet();
@@ -5775,7 +5808,7 @@ async function getMasterKey() {
5775
5808
  b64Value = content;
5776
5809
  }
5777
5810
  const key = Buffer.from(b64Value, "base64");
5778
- if (!content.startsWith(ENCRYPTED_PREFIX) && isRootOnlyTrustedServerKeyFile(keyPath)) {
5811
+ if (isRootOnlyTrustedServerKeyFile(keyPath)) {
5779
5812
  return key;
5780
5813
  }
5781
5814
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
@@ -12346,6 +12379,7 @@ var init_provider_table = __esm({
12346
12379
  // src/lib/intercom-queue.ts
12347
12380
  var intercom_queue_exports = {};
12348
12381
  __export(intercom_queue_exports, {
12382
+ _resetDrainGuard: () => _resetDrainGuard,
12349
12383
  clearQueueForAgent: () => clearQueueForAgent,
12350
12384
  drainForSession: () => drainForSession,
12351
12385
  drainQueue: () => drainQueue2,
@@ -12391,38 +12425,47 @@ function queueIntercom(targetSession, reason) {
12391
12425
  writeQueue(queue);
12392
12426
  }
12393
12427
  function drainQueue2(isSessionBusy2, sendKeys) {
12428
+ if (_draining) {
12429
+ logQueue("SKIP_DRAIN \u2014 previous drain still running (possible tmux hang)");
12430
+ return { drained: 0, failed: 0 };
12431
+ }
12394
12432
  const queue = readQueue();
12395
12433
  if (queue.length === 0) return { drained: 0, failed: 0 };
12434
+ _draining = true;
12396
12435
  const remaining = [];
12397
12436
  let drained = 0;
12398
12437
  let failed = 0;
12399
- for (const item of queue) {
12400
- const age = Date.now() - new Date(item.queuedAt).getTime();
12401
- if (age > TTL_MS2) {
12402
- logQueue(`EXPIRED \u2192 ${item.targetSession} (${Math.round(age / 6e4)}min old, reason: ${item.reason})`);
12403
- failed++;
12404
- continue;
12405
- }
12406
- try {
12407
- if (!isSessionBusy2(item.targetSession)) {
12408
- const success = sendKeys(item.targetSession);
12409
- if (success) {
12410
- logQueue(`DRAINED \u2192 ${item.targetSession} (after ${item.attempts} retries)`);
12411
- drained++;
12412
- continue;
12438
+ try {
12439
+ for (const item of queue) {
12440
+ const age = Date.now() - new Date(item.queuedAt).getTime();
12441
+ if (age > TTL_MS2) {
12442
+ logQueue(`EXPIRED \u2192 ${item.targetSession} (${Math.round(age / 6e4)}min old, reason: ${item.reason})`);
12443
+ failed++;
12444
+ continue;
12445
+ }
12446
+ try {
12447
+ if (!isSessionBusy2(item.targetSession)) {
12448
+ const success = sendKeys(item.targetSession);
12449
+ if (success) {
12450
+ logQueue(`DRAINED \u2192 ${item.targetSession} (after ${item.attempts} retries)`);
12451
+ drained++;
12452
+ continue;
12453
+ }
12413
12454
  }
12455
+ } catch {
12414
12456
  }
12415
- } catch {
12416
- }
12417
- item.attempts++;
12418
- if (item.attempts >= MAX_RETRIES2) {
12419
- logQueue(`FAILED \u2192 ${item.targetSession} (${MAX_RETRIES2} retries exhausted, reason: ${item.reason})`);
12420
- failed++;
12421
- continue;
12457
+ item.attempts++;
12458
+ if (item.attempts >= MAX_RETRIES2) {
12459
+ logQueue(`FAILED \u2192 ${item.targetSession} (${MAX_RETRIES2} retries exhausted, reason: ${item.reason})`);
12460
+ failed++;
12461
+ continue;
12462
+ }
12463
+ remaining.push(item);
12422
12464
  }
12423
- remaining.push(item);
12465
+ writeQueue(remaining);
12466
+ } finally {
12467
+ _draining = false;
12424
12468
  }
12425
- writeQueue(remaining);
12426
12469
  return { drained, failed };
12427
12470
  }
12428
12471
  function drainForSession(targetSession, sendKeys) {
@@ -12447,6 +12490,9 @@ function clearQueueForAgent(agentName) {
12447
12490
  logQueue(`CLEARED ${before - filtered.length} stale item(s) for ${agentName}`);
12448
12491
  }
12449
12492
  }
12493
+ function _resetDrainGuard() {
12494
+ _draining = false;
12495
+ }
12450
12496
  function logQueue(msg) {
12451
12497
  const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [queue] ${msg}
12452
12498
  `;
@@ -12458,13 +12504,14 @@ function logQueue(msg) {
12458
12504
  } catch {
12459
12505
  }
12460
12506
  }
12461
- var QUEUE_PATH2, MAX_RETRIES2, TTL_MS2, INTERCOM_LOG;
12507
+ var QUEUE_PATH2, MAX_RETRIES2, TTL_MS2, _draining, INTERCOM_LOG;
12462
12508
  var init_intercom_queue = __esm({
12463
12509
  "src/lib/intercom-queue.ts"() {
12464
12510
  "use strict";
12465
12511
  QUEUE_PATH2 = path22.join(os9.homedir(), ".exe-os", "intercom-queue.json");
12466
12512
  MAX_RETRIES2 = 5;
12467
12513
  TTL_MS2 = 60 * 60 * 1e3;
12514
+ _draining = false;
12468
12515
  INTERCOM_LOG = path22.join(os9.homedir(), ".exe-os", "intercom.log");
12469
12516
  }
12470
12517
  });
@@ -13139,7 +13186,13 @@ async function createReviewForCompletedTask(row, result3, _baseDir, now2) {
13139
13186
  taskFile
13140
13187
  });
13141
13188
  const originalPriority = String(row.priority).toLowerCase();
13142
- const autoApprove = originalPriority === "p2" && result3?.toLowerCase().includes("tests pass");
13189
+ const resultLower = result3?.toLowerCase() ?? "";
13190
+ const hasTestEvidence = (
13191
+ // Vitest/Jest output patterns (hard to fake without actually running tests)
13192
+ /\d+\s+pass(ed|ing)/.test(resultLower) || /test files?\s+\d+\s+passed/.test(resultLower) || /tests?\s+\d+\s+passed/.test(resultLower)
13193
+ );
13194
+ const hasNoFailures = !/fail(ed|ure|ing)|error/i.test(resultLower);
13195
+ const autoApprove = originalPriority === "p2" && hasTestEvidence && hasNoFailures;
13143
13196
  if (!autoApprove) {
13144
13197
  try {
13145
13198
  const key = getSessionKey();
@@ -13147,6 +13200,13 @@ async function createReviewForCompletedTask(row, result3, _baseDir, now2) {
13147
13200
  if (exeSession) {
13148
13201
  sendIntercom(exeSession);
13149
13202
  }
13203
+ if (reviewer && reviewer !== coordinatorName && reviewer !== exeSession) {
13204
+ const { employeeSessionName: employeeSessionName2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
13205
+ if (exeSession) {
13206
+ const reviewerSession = employeeSessionName2(reviewer, exeSession);
13207
+ sendIntercom(reviewerSession);
13208
+ }
13209
+ }
13150
13210
  } catch {
13151
13211
  }
13152
13212
  }
@@ -13277,18 +13337,31 @@ function acquireSpawnLock2(sessionName) {
13277
13337
  mkdirSync10(SPAWN_LOCK_DIR, { recursive: true });
13278
13338
  }
13279
13339
  const lockFile = spawnLockPath(sessionName);
13280
- if (existsSync22(lockFile)) {
13281
- try {
13282
- const lock = JSON.parse(readFileSync15(lockFile, "utf8"));
13283
- const age = Date.now() - lock.timestamp;
13284
- if (isProcessAlive(lock.pid) && age < 6e4) {
13285
- return false;
13286
- }
13287
- } catch {
13340
+ const lockData = JSON.stringify({ pid: process.pid, timestamp: Date.now() });
13341
+ const { openSync: openSync5, closeSync: closeSync5, writeSync } = __require("fs");
13342
+ const { constants: constants2 } = __require("fs");
13343
+ try {
13344
+ const fd = openSync5(lockFile, constants2.O_WRONLY | constants2.O_CREAT | constants2.O_EXCL, 420);
13345
+ writeSync(fd, lockData);
13346
+ closeSync5(fd);
13347
+ return true;
13348
+ } catch (err) {
13349
+ if (err?.code !== "EEXIST") {
13350
+ return true;
13288
13351
  }
13289
13352
  }
13290
- writeFileSync12(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
13291
- return true;
13353
+ try {
13354
+ const lock = JSON.parse(readFileSync15(lockFile, "utf8"));
13355
+ const age = Date.now() - lock.timestamp;
13356
+ if (isProcessAlive(lock.pid) && age < 6e4) {
13357
+ return false;
13358
+ }
13359
+ writeFileSync12(lockFile, lockData);
13360
+ return true;
13361
+ } catch {
13362
+ writeFileSync12(lockFile, lockData);
13363
+ return true;
13364
+ }
13292
13365
  }
13293
13366
  function releaseSpawnLock2(sessionName) {
13294
13367
  try {
@@ -13367,6 +13440,21 @@ function parseParentExe(sessionName, agentId) {
13367
13440
  function extractRootExe(name) {
13368
13441
  if (!name) return null;
13369
13442
  if (!name.includes("-")) return name;
13443
+ try {
13444
+ const roster = (init_employees(), __toCommonJS(employees_exports)).loadEmployeesSync();
13445
+ if (roster.length > 0) {
13446
+ const sortedNames = roster.map((e) => e.name).sort((a, b) => b.length - a.length);
13447
+ for (const agentName of sortedNames) {
13448
+ const escaped = agentName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
13449
+ const regex = new RegExp(`^${escaped}\\d*-(.+)$`);
13450
+ const match = name.match(regex);
13451
+ if (match) {
13452
+ return extractRootExe(match[1]);
13453
+ }
13454
+ }
13455
+ }
13456
+ } catch {
13457
+ }
13370
13458
  const parts = name.split("-").filter(Boolean);
13371
13459
  return parts.length > 0 ? parts[parts.length - 1] : null;
13372
13460
  }
@@ -13385,6 +13473,10 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
13385
13473
  function getParentExe(sessionKey) {
13386
13474
  try {
13387
13475
  const data = JSON.parse(readFileSync15(path25.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
13476
+ if (data.registeredAt) {
13477
+ const age = Date.now() - new Date(data.registeredAt).getTime();
13478
+ if (age > PARENT_EXE_CACHE_TTL_MS) return null;
13479
+ }
13388
13480
  return data.parentExe || null;
13389
13481
  } catch {
13390
13482
  return null;
@@ -13933,7 +14025,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
13933
14025
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
13934
14026
  } catch {
13935
14027
  }
13936
- let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName}`;
14028
+ let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName} EXE_SESSION_START_ISO=${(/* @__PURE__ */ new Date()).toISOString()}`;
13937
14029
  if (ccProvider !== DEFAULT_PROVIDER) {
13938
14030
  const cfg = PROVIDER_TABLE[ccProvider];
13939
14031
  if (cfg?.apiKeyEnv) {
@@ -13968,10 +14060,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
13968
14060
  }
13969
14061
  if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
13970
14062
  if (agentRtConfig.runtime === "claude" && agentRtConfig.model) {
13971
- let ccModel = agentRtConfig.model.replace(/(\d+)\.(\d+)/g, "$1-$2");
13972
- if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
13973
- ccModel += "[1m]";
13974
- }
14063
+ const { normalizeCcModelName: normalizeCcModelName2 } = (init_agent_config(), __toCommonJS(agent_config_exports));
14064
+ const ccModel = normalizeCcModelName2(agentRtConfig.model);
13975
14065
  envPrefix = `${envPrefix} ANTHROPIC_MODEL=${ccModel}`;
13976
14066
  }
13977
14067
  }
@@ -14072,7 +14162,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
14072
14162
  releaseSpawnLock2(sessionName);
14073
14163
  return { sessionName };
14074
14164
  }
14075
- 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;
14165
+ 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;
14076
14166
  var init_tmux_routing = __esm({
14077
14167
  "src/lib/tmux-routing.ts"() {
14078
14168
  "use strict";
@@ -14092,6 +14182,7 @@ var init_tmux_routing = __esm({
14092
14182
  SESSION_CACHE = path25.join(os11.homedir(), ".exe-os", "session-cache");
14093
14183
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
14094
14184
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
14185
+ PARENT_EXE_CACHE_TTL_MS = 4 * 60 * 60 * 1e3;
14095
14186
  VERIFY_PANE_LINES = 200;
14096
14187
  INTERCOM_DEBOUNCE_MS = 3e4;
14097
14188
  CODEX_DEBOUNCE_MS = 12e4;
@@ -14142,6 +14233,17 @@ var init_task_scope = __esm({
14142
14233
  });
14143
14234
 
14144
14235
  // src/lib/notifications.ts
14236
+ var notifications_exports = {};
14237
+ __export(notifications_exports, {
14238
+ cleanupOldNotifications: () => cleanupOldNotifications,
14239
+ formatNotifications: () => formatNotifications,
14240
+ markAsRead: () => markAsRead,
14241
+ markAsReadByTaskFile: () => markAsReadByTaskFile,
14242
+ markDoneTaskNotificationsAsRead: () => markDoneTaskNotificationsAsRead,
14243
+ migrateJsonNotifications: () => migrateJsonNotifications,
14244
+ readUnreadNotifications: () => readUnreadNotifications,
14245
+ writeNotification: () => writeNotification
14246
+ });
14145
14247
  import crypto7 from "crypto";
14146
14248
  import path26 from "path";
14147
14249
  import os12 from "os";
@@ -14178,6 +14280,52 @@ async function writeNotification(notification) {
14178
14280
  `);
14179
14281
  }
14180
14282
  }
14283
+ async function readUnreadNotifications(agentFilter2, sessionScope) {
14284
+ try {
14285
+ const client = getClient();
14286
+ const conditions = ["read = 0"];
14287
+ const args = [];
14288
+ const scope = strictSessionScopeFilter(sessionScope);
14289
+ if (agentFilter2) {
14290
+ conditions.push("agent_id = ?");
14291
+ args.push(agentFilter2);
14292
+ }
14293
+ const result3 = await client.execute({
14294
+ sql: `SELECT id, agent_id, agent_role, event, project, summary, task_file, session_scope, created_at
14295
+ FROM notifications
14296
+ WHERE ${conditions.join(" AND ")}${scope.sql}
14297
+ ORDER BY created_at ASC`,
14298
+ args: [...args, ...scope.args]
14299
+ });
14300
+ return result3.rows.map((r) => ({
14301
+ id: String(r.id),
14302
+ agentId: String(r.agent_id),
14303
+ agentRole: String(r.agent_role),
14304
+ event: String(r.event),
14305
+ project: String(r.project),
14306
+ summary: String(r.summary),
14307
+ taskFile: r.task_file ? String(r.task_file) : void 0,
14308
+ sessionScope: r.session_scope == null ? null : String(r.session_scope),
14309
+ timestamp: String(r.created_at),
14310
+ read: false
14311
+ }));
14312
+ } catch {
14313
+ return [];
14314
+ }
14315
+ }
14316
+ async function markAsRead(ids, sessionScope) {
14317
+ if (ids.length === 0) return;
14318
+ try {
14319
+ const client = getClient();
14320
+ const placeholders = ids.map(() => "?").join(", ");
14321
+ const scope = strictSessionScopeFilter(sessionScope);
14322
+ await client.execute({
14323
+ sql: `UPDATE notifications SET read = 1 WHERE id IN (${placeholders})${scope.sql}`,
14324
+ args: [...ids, ...scope.args]
14325
+ });
14326
+ } catch {
14327
+ }
14328
+ }
14181
14329
  async function markAsReadByTaskFile(taskFile, sessionScope) {
14182
14330
  try {
14183
14331
  const client = getClient();
@@ -14190,11 +14338,144 @@ async function markAsReadByTaskFile(taskFile, sessionScope) {
14190
14338
  } catch {
14191
14339
  }
14192
14340
  }
14341
+ async function cleanupOldNotifications(daysOld = CLEANUP_DAYS, sessionScope) {
14342
+ try {
14343
+ const client = getClient();
14344
+ const cutoff = new Date(
14345
+ Date.now() - daysOld * 24 * 60 * 60 * 1e3
14346
+ ).toISOString();
14347
+ const scope = strictSessionScopeFilter(sessionScope);
14348
+ const result3 = await client.execute({
14349
+ sql: `DELETE FROM notifications WHERE created_at < ?${scope.sql}`,
14350
+ args: [cutoff, ...scope.args]
14351
+ });
14352
+ return result3.rowsAffected;
14353
+ } catch {
14354
+ return 0;
14355
+ }
14356
+ }
14357
+ async function markDoneTaskNotificationsAsRead(sessionScope) {
14358
+ try {
14359
+ const client = getClient();
14360
+ const scope = strictSessionScopeFilter(sessionScope);
14361
+ const result3 = await client.execute({
14362
+ sql: `UPDATE notifications SET read = 1
14363
+ WHERE read = 0
14364
+ AND task_file IS NOT NULL
14365
+ ${scope.sql}
14366
+ AND task_file IN (
14367
+ SELECT task_file FROM tasks WHERE status = 'done'${scope.sql}
14368
+ )`,
14369
+ args: [...scope.args, ...scope.args]
14370
+ });
14371
+ return result3.rowsAffected;
14372
+ } catch {
14373
+ return 0;
14374
+ }
14375
+ }
14376
+ function formatNotifications(notifications) {
14377
+ if (notifications.length === 0) return "";
14378
+ const grouped = /* @__PURE__ */ new Map();
14379
+ for (const n of notifications) {
14380
+ const key = `${n.agentId}|${n.agentRole}`;
14381
+ if (!grouped.has(key)) grouped.set(key, []);
14382
+ grouped.get(key).push(n);
14383
+ }
14384
+ const lines = [];
14385
+ lines.push(`## Notifications (${notifications.length} unread)
14386
+ `);
14387
+ for (const [key, items] of grouped) {
14388
+ const [agentId, agentRole] = key.split("|");
14389
+ lines.push(`**${agentId}** (${agentRole}):`);
14390
+ for (const item of items) {
14391
+ const ago = formatTimeAgo(item.timestamp);
14392
+ const icon = eventIcon(item.event);
14393
+ lines.push(`- ${icon} ${item.summary} (${item.project}) \u2014 ${ago}`);
14394
+ }
14395
+ lines.push("");
14396
+ }
14397
+ return lines.join("\n");
14398
+ }
14399
+ async function migrateJsonNotifications() {
14400
+ const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR || path26.join(os12.homedir(), ".exe-os");
14401
+ const notifDir = path26.join(base, "notifications");
14402
+ if (!existsSync23(notifDir)) return 0;
14403
+ let migrated = 0;
14404
+ try {
14405
+ const files = readdirSync6(notifDir).filter((f) => f.endsWith(".json"));
14406
+ if (files.length === 0) return 0;
14407
+ const client = getClient();
14408
+ for (const file of files) {
14409
+ try {
14410
+ const filePath = path26.join(notifDir, file);
14411
+ const data = JSON.parse(readFileSync16(filePath, "utf8"));
14412
+ await client.execute({
14413
+ sql: `INSERT OR IGNORE INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
14414
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
14415
+ args: [
14416
+ crypto7.randomUUID(),
14417
+ data.agentId ?? "unknown",
14418
+ data.agentRole ?? "unknown",
14419
+ data.event ?? "session_summary",
14420
+ data.project ?? "unknown",
14421
+ data.summary ?? "",
14422
+ data.taskFile ?? null,
14423
+ null,
14424
+ data.read ? 1 : 0,
14425
+ data.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
14426
+ ]
14427
+ });
14428
+ unlinkSync8(filePath);
14429
+ migrated++;
14430
+ } catch {
14431
+ }
14432
+ }
14433
+ try {
14434
+ const remaining = readdirSync6(notifDir);
14435
+ if (remaining.length === 0) {
14436
+ rmdirSync(notifDir);
14437
+ }
14438
+ } catch {
14439
+ }
14440
+ } catch {
14441
+ }
14442
+ return migrated;
14443
+ }
14444
+ function eventIcon(event) {
14445
+ switch (event) {
14446
+ case "task_complete":
14447
+ return "Completed:";
14448
+ case "task_needs_fix":
14449
+ return "Needs fix:";
14450
+ case "session_summary":
14451
+ return "Session:";
14452
+ case "error_spike":
14453
+ return "Errors:";
14454
+ case "orphan_task":
14455
+ return "Orphan:";
14456
+ case "subtasks_complete":
14457
+ return "Subtasks done:";
14458
+ case "capacity_relaunch":
14459
+ return "Relaunched:";
14460
+ }
14461
+ }
14462
+ function formatTimeAgo(timestamp) {
14463
+ const diffMs = Date.now() - new Date(timestamp).getTime();
14464
+ const mins = Math.floor(diffMs / 6e4);
14465
+ if (mins < 1) return "just now";
14466
+ if (mins < 60) return `${mins}m ago`;
14467
+ const hours = Math.floor(mins / 60);
14468
+ if (hours < 24) return `${hours}h ago`;
14469
+ const days = Math.floor(hours / 24);
14470
+ return `${days}d ago`;
14471
+ }
14472
+ var CLEANUP_DAYS;
14193
14473
  var init_notifications = __esm({
14194
14474
  "src/lib/notifications.ts"() {
14195
14475
  "use strict";
14196
14476
  init_database();
14197
14477
  init_task_scope();
14478
+ CLEANUP_DAYS = 7;
14198
14479
  }
14199
14480
  });
14200
14481
 
@@ -15564,6 +15845,20 @@ async function updateTask(input) {
15564
15845
  notifyTaskDone();
15565
15846
  }
15566
15847
  await markTaskNotificationsRead(taskFile);
15848
+ if (input.status === "needs_review" && !isCoordinator) {
15849
+ try {
15850
+ const { writeNotification: writeNotification2 } = await Promise.resolve().then(() => (init_notifications(), notifications_exports));
15851
+ await writeNotification2({
15852
+ agentId: String(row.assigned_to),
15853
+ agentRole: String(row.assigned_to),
15854
+ event: "task_complete",
15855
+ project: String(row.project_name),
15856
+ summary: `"${String(row.title)}" is ready for review`,
15857
+ taskFile
15858
+ });
15859
+ } catch {
15860
+ }
15861
+ }
15567
15862
  if (input.status === "done" || input.status === "closed") {
15568
15863
  try {
15569
15864
  await cascadeUnblock(taskId, input.baseDir, now2);
@@ -22316,6 +22611,7 @@ __export(daemon_orchestration_exports, {
22316
22611
  REVIEW_NUDGE_COOLDOWN_MS: () => REVIEW_NUDGE_COOLDOWN_MS,
22317
22612
  SESSION_CONTEXT_THRESHOLD_PCT: () => SESSION_CONTEXT_THRESHOLD_PCT,
22318
22613
  SESSION_TTL_HOURS: () => SESSION_TTL_HOURS,
22614
+ STUCK_TASK_GRACE_MS: () => STUCK_TASK_GRACE_MS,
22319
22615
  _resetAutoWakeState: () => _resetAutoWakeState,
22320
22616
  checkSessionTTL: () => checkSessionTTL,
22321
22617
  classifyTtlKillReason: () => classifyTtlKillReason,
@@ -22325,12 +22621,14 @@ __export(daemon_orchestration_exports, {
22325
22621
  createOrphanReaperRealDeps: () => createOrphanReaperRealDeps,
22326
22622
  createReviewNudgeRealDeps: () => createReviewNudgeRealDeps,
22327
22623
  createSessionTTLRealDeps: () => createSessionTTLRealDeps,
22624
+ createStuckTaskRealDeps: () => createStuckTaskRealDeps,
22328
22625
  loadNudgeState: () => loadNudgeState,
22329
22626
  pollIdleEmployees: () => pollIdleEmployees,
22330
22627
  pollIdleKill: () => pollIdleKill,
22331
22628
  pollOrphanedTasks: () => pollOrphanedTasks,
22332
22629
  pollReviewNudge: () => pollReviewNudge,
22333
22630
  reapOrphanedMcpProcesses: () => reapOrphanedMcpProcesses,
22631
+ releaseStuckTasks: () => releaseStuckTasks,
22334
22632
  saveNudgeState: () => saveNudgeState,
22335
22633
  shouldAutoWake: () => shouldAutoWake,
22336
22634
  shouldKillIdleSession: () => shouldKillIdleSession,
@@ -22818,6 +23116,102 @@ async function pollOrphanedTasks(deps, nowMs = Date.now()) {
22818
23116
  }
22819
23117
  return woken;
22820
23118
  }
23119
+ async function releaseStuckTasks(deps, nowMs = Date.now()) {
23120
+ let liveSessions;
23121
+ try {
23122
+ liveSessions = deps.listTmuxSessions();
23123
+ } catch {
23124
+ return [];
23125
+ }
23126
+ const liveAgents = /* @__PURE__ */ new Set();
23127
+ for (const session of liveSessions) {
23128
+ const agent = deps.parseAgentFromSession(session);
23129
+ if (agent) liveAgents.add(agent);
23130
+ }
23131
+ for (const session of liveSessions) {
23132
+ if (isExeSession(session)) liveAgents.add(session);
23133
+ }
23134
+ let tasks;
23135
+ try {
23136
+ tasks = await deps.queryInProgressTasks();
23137
+ } catch {
23138
+ return [];
23139
+ }
23140
+ const released = [];
23141
+ for (const t of tasks) {
23142
+ if (liveAgents.has(t.agentId)) continue;
23143
+ const updatedMs = new Date(t.updatedAt).getTime();
23144
+ if (isNaN(updatedMs) || nowMs - updatedMs < STUCK_TASK_GRACE_MS) continue;
23145
+ const ageMinutes = Math.round((nowMs - updatedMs) / 6e4);
23146
+ const reason = `[auto-release] Agent "${t.agentId}" session is dead and task has been in_progress for ${ageMinutes}m with no update. Marked blocked for triage.`;
23147
+ try {
23148
+ await deps.markTaskBlocked(t.taskId, reason);
23149
+ released.push(t.taskId);
23150
+ process.stderr.write(
23151
+ `[stuck-release] Task ${t.taskId} (${t.agentId}) \u2014 in_progress for ${ageMinutes}m, agent dead \u2192 blocked
23152
+ `
23153
+ );
23154
+ if (deps.notifyOrphan) {
23155
+ try {
23156
+ await deps.notifyOrphan(t.taskId, t.agentId);
23157
+ } catch {
23158
+ }
23159
+ }
23160
+ } catch (err) {
23161
+ process.stderr.write(
23162
+ `[stuck-release] Failed to release ${t.taskId}: ${err instanceof Error ? err.message : String(err)}
23163
+ `
23164
+ );
23165
+ }
23166
+ }
23167
+ return released;
23168
+ }
23169
+ function createStuckTaskRealDeps(getClient2) {
23170
+ return {
23171
+ listTmuxSessions: () => {
23172
+ const { listTmuxSessions: listTmuxSessions2 } = (init_tmux_status(), __toCommonJS(tmux_status_exports));
23173
+ return listTmuxSessions2();
23174
+ },
23175
+ queryInProgressTasks: async () => {
23176
+ const client = getClient2();
23177
+ const result3 = await client.execute({
23178
+ sql: `SELECT id, assigned_to, session_scope, updated_at FROM tasks
23179
+ WHERE status = 'in_progress'
23180
+ ORDER BY updated_at ASC`,
23181
+ args: []
23182
+ });
23183
+ return result3.rows.map((r) => ({
23184
+ taskId: String(r.id),
23185
+ agentId: String(r.assigned_to),
23186
+ sessionScope: r.session_scope ? String(r.session_scope) : null,
23187
+ updatedAt: String(r.updated_at)
23188
+ }));
23189
+ },
23190
+ markTaskBlocked: async (taskId, reason) => {
23191
+ const client = getClient2();
23192
+ await client.execute({
23193
+ sql: `UPDATE tasks SET status = 'blocked', result = ?, updated_at = ? WHERE id = ?`,
23194
+ args: [reason, (/* @__PURE__ */ new Date()).toISOString(), taskId]
23195
+ });
23196
+ },
23197
+ parseAgentFromSession: (sessionName) => {
23198
+ const { baseAgentName: baseAgentName2 } = (init_employees(), __toCommonJS(employees_exports));
23199
+ if (!sessionName.includes("-")) return null;
23200
+ const agentPart = sessionName.split("-")[0];
23201
+ return baseAgentName2(agentPart);
23202
+ },
23203
+ notifyOrphan: async (taskId, agentId) => {
23204
+ const { writeNotification: writeNotification2 } = (init_notifications(), __toCommonJS(notifications_exports));
23205
+ await writeNotification2({
23206
+ agentId,
23207
+ agentRole: "employee",
23208
+ event: "orphan_task",
23209
+ project: "",
23210
+ summary: `Agent "${agentId}" session died \u2014 task ${taskId.slice(0, 8)} auto-released to blocked for triage`
23211
+ });
23212
+ }
23213
+ };
23214
+ }
22821
23215
  function createAutoWakeRealDeps(getClient2, projectDir) {
22822
23216
  return {
22823
23217
  listTmuxSessions: () => {
@@ -22910,7 +23304,7 @@ function createOrphanReaperRealDeps() {
22910
23304
  selfPid: process.pid
22911
23305
  };
22912
23306
  }
22913
- var IDLE_NUDGE_DEDUP_MS, SESSION_TTL_HOURS, SESSION_CONTEXT_THRESHOLD_PCT, IDLE_KILL_INTERCOM_ACK_WINDOW_MS, REVIEW_NUDGE_COOLDOWN_MS, NUDGE_STATE_PATH, AUTO_WAKE_COOLDOWN_MS, AUTO_WAKE_MAX_RETRIES, _autoWakeLastSpawn, _autoWakeTaskRetries, ORPHAN_SIGKILL_DELAY_MS, ORPHAN_PATTERNS;
23307
+ var IDLE_NUDGE_DEDUP_MS, SESSION_TTL_HOURS, SESSION_CONTEXT_THRESHOLD_PCT, IDLE_KILL_INTERCOM_ACK_WINDOW_MS, REVIEW_NUDGE_COOLDOWN_MS, NUDGE_STATE_PATH, AUTO_WAKE_COOLDOWN_MS, AUTO_WAKE_MAX_RETRIES, _autoWakeLastSpawn, _autoWakeTaskRetries, STUCK_TASK_GRACE_MS, ORPHAN_SIGKILL_DELAY_MS, ORPHAN_PATTERNS;
22914
23308
  var init_daemon_orchestration = __esm({
22915
23309
  "src/lib/daemon-orchestration.ts"() {
22916
23310
  "use strict";
@@ -22927,6 +23321,7 @@ var init_daemon_orchestration = __esm({
22927
23321
  AUTO_WAKE_MAX_RETRIES = 3;
22928
23322
  _autoWakeLastSpawn = /* @__PURE__ */ new Map();
22929
23323
  _autoWakeTaskRetries = /* @__PURE__ */ new Map();
23324
+ STUCK_TASK_GRACE_MS = 5 * 60 * 1e3;
22930
23325
  ORPHAN_SIGKILL_DELAY_MS = 5e3;
22931
23326
  ORPHAN_PATTERNS = [
22932
23327
  "exe-os/dist/mcp/server.js",
@@ -23057,7 +23452,21 @@ function tryAcquireWorkerSlot() {
23057
23452
  for (const f of files) {
23058
23453
  if (!f.endsWith(".pid")) continue;
23059
23454
  if (f.startsWith("res-")) {
23060
- alive++;
23455
+ const resParts = f.replace(".pid", "").split("-");
23456
+ const resPid = parseInt(resParts[1] ?? "", 10);
23457
+ if (!isNaN(resPid) && resPid > 0) {
23458
+ try {
23459
+ process.kill(resPid, 0);
23460
+ alive++;
23461
+ } catch {
23462
+ try {
23463
+ unlinkSync10(path35.join(WORKER_PID_DIR, f));
23464
+ } catch {
23465
+ }
23466
+ }
23467
+ } else {
23468
+ alive++;
23469
+ }
23061
23470
  continue;
23062
23471
  }
23063
23472
  const dashIdx = f.lastIndexOf("-");
@@ -25089,6 +25498,7 @@ __export(cloud_sync_exports, {
25089
25498
  markCloudReuploadRequired: () => markCloudReuploadRequired,
25090
25499
  mergeConfig: () => mergeConfig,
25091
25500
  mergeRosterFromRemote: () => mergeRosterFromRemote,
25501
+ migrateEndpoint: () => migrateEndpoint,
25092
25502
  pushToPostgres: () => pushToPostgres,
25093
25503
  recordRosterDeletion: () => recordRosterDeletion
25094
25504
  });
@@ -25259,6 +25669,15 @@ async function fetchWithRetry(url, init) {
25259
25669
  }
25260
25670
  throw lastError;
25261
25671
  }
25672
+ function migrateEndpoint(endpoint2) {
25673
+ if (endpoint2 === "https://askexe.com/cloud" || endpoint2 === "https://askexe.com/cloud/") {
25674
+ process.stderr.write(
25675
+ "[cloud-sync] Auto-migrating endpoint from askexe.com/cloud to cloud.askexe.com (bypasses Cloudflare WAF for datacenter IPs)\n"
25676
+ );
25677
+ return "https://cloud.askexe.com";
25678
+ }
25679
+ return endpoint2;
25680
+ }
25262
25681
  function assertSecureEndpoint(endpoint2) {
25263
25682
  if (endpoint2.startsWith("https://")) return;
25264
25683
  if (endpoint2.startsWith("http://")) {
@@ -25396,6 +25815,7 @@ async function markCloudReuploadRequired(client = getClient()) {
25396
25815
  await client.execute("INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('cloud_reupload_required', '1')");
25397
25816
  }
25398
25817
  async function cloudSync(config2) {
25818
+ config2 = { ...config2, endpoint: migrateEndpoint(config2.endpoint) };
25399
25819
  if (!isSyncCryptoInitialized()) {
25400
25820
  try {
25401
25821
  const { getMasterKey: getMasterKey2 } = await Promise.resolve().then(() => (init_keychain(), keychain_exports));
@@ -37163,7 +37583,25 @@ async function startMcpHttpServer() {
37163
37583
  }
37164
37584
  const agentId = req.headers["x-agent-id"] || "default";
37165
37585
  const agentRole = req.headers["x-agent-role"] || "employee";
37166
- const sessionHint = req.headers["x-exe-session"] || "";
37586
+ let sessionHint = req.headers["x-exe-session"] || "";
37587
+ if (sessionHint.includes("$(") || sessionHint.includes("#{") || sessionHint.includes("tmux")) {
37588
+ sessionHint = "";
37589
+ }
37590
+ if (!sessionHint) {
37591
+ try {
37592
+ const { getTransport: getTransport2 } = (init_transport(), __toCommonJS(transport_exports));
37593
+ const transport2 = getTransport2();
37594
+ const liveSessions = transport2.listSessions();
37595
+ const agentSessions = liveSessions.filter((s) => s.startsWith(agentId + "-"));
37596
+ if (agentSessions.length === 1) {
37597
+ sessionHint = agentSessions[0];
37598
+ } else {
37599
+ const roots = liveSessions.filter((s) => !s.includes("-") && /^[a-z]+\d*$/.test(s));
37600
+ if (roots.length === 1) sessionHint = roots[0];
37601
+ }
37602
+ } catch {
37603
+ }
37604
+ }
37167
37605
  const runtime = inferMcpRuntime({
37168
37606
  runtimeHeader: req.headers["x-agent-runtime"] || req.headers["x-runtime"],
37169
37607
  userAgent: req.headers["user-agent"],
@@ -37792,10 +38230,10 @@ function startIntercomQueueDrain() {
37792
38230
  const rtConfig = getAgentRuntime2(agentName);
37793
38231
  const nudgeMsg = "You have pending notifications. Run list_tasks to check for assigned work.";
37794
38232
  if (rtConfig.runtime === "codex" || rtConfig.runtime === "opencode") {
37795
- transport.sendKeys(session, nudgeMsg);
37796
- try {
37797
- __require("child_process").execSync(`tmux send-keys -t ${session} Tab`, { timeout: 2e3 });
37798
- } catch {
38233
+ if (transport.sendKeysLiteral) {
38234
+ transport.sendKeysLiteral(session, nudgeMsg);
38235
+ } else {
38236
+ transport.sendKeys(session, nudgeMsg);
37799
38237
  }
37800
38238
  } else {
37801
38239
  transport.sendKeys(session, nudgeMsg);
@@ -37871,6 +38309,31 @@ function startAutoWake() {
37871
38309
  process.stderr.write(`[exed] Auto-wake started (every ${AUTO_WAKE_INTERVAL_MS / 1e3}s)
37872
38310
  `);
37873
38311
  }
38312
+ var STUCK_TASK_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
38313
+ function startStuckTaskRelease() {
38314
+ const tick = async () => {
38315
+ fired("stuck_task_release");
38316
+ if (!await ensureStoreForPolling()) return;
38317
+ try {
38318
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
38319
+ const { releaseStuckTasks: releaseStuckTasks2, createStuckTaskRealDeps: createStuckTaskRealDeps2 } = await Promise.resolve().then(() => (init_daemon_orchestration(), daemon_orchestration_exports));
38320
+ const deps = createStuckTaskRealDeps2(getClient2);
38321
+ const released = await releaseStuckTasks2(deps);
38322
+ if (released.length > 0) {
38323
+ acted("stuck_task_release");
38324
+ process.stderr.write(`[exed] Stuck-release: ${released.length} task(s) moved to blocked
38325
+ `);
38326
+ }
38327
+ } catch (err) {
38328
+ process.stderr.write(`[exed] Stuck-release error: ${err instanceof Error ? err.message : String(err)}
38329
+ `);
38330
+ }
38331
+ };
38332
+ const timer = setInterval(() => void tick(), STUCK_TASK_CHECK_INTERVAL_MS);
38333
+ timer.unref();
38334
+ process.stderr.write(`[exed] Stuck-task release started (every ${STUCK_TASK_CHECK_INTERVAL_MS / 1e3}s)
38335
+ `);
38336
+ }
37874
38337
  var TOTAL_MEM_GB = os24.totalmem() / 1024 ** 3;
37875
38338
  var RSS_WARN_BYTES = Number(process.env.EXE_RSS_WARN_MB) * 1024 * 1024 || (TOTAL_MEM_GB >= 64 ? 6 * 1024 ** 3 : TOTAL_MEM_GB >= 32 ? 4 * 1024 ** 3 : TOTAL_MEM_GB >= 16 ? 2.5 * 1024 ** 3 : 1.5 * 1024 ** 3);
37876
38339
  var RSS_RESTART_BYTES = Number(process.env.EXE_RSS_RESTART_MB) * 1024 * 1024 || (TOTAL_MEM_GB >= 64 ? 8 * 1024 ** 3 : TOTAL_MEM_GB >= 32 ? 6 * 1024 ** 3 : TOTAL_MEM_GB >= 16 ? 4 * 1024 ** 3 : 3 * 1024 ** 3);
@@ -38211,6 +38674,7 @@ try {
38211
38674
  startOrphanReaper();
38212
38675
  startAgentStats();
38213
38676
  startAutoWake();
38677
+ startStuckTaskRelease();
38214
38678
  startGraphExtraction();
38215
38679
  startMemoryQueueDrain();
38216
38680
  startIntercomQueueDrain();