@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
@@ -210,6 +210,17 @@ function normalizeOrchestration(raw) {
210
210
  const userOrg = raw.orchestration ?? {};
211
211
  raw.orchestration = { ...defaultOrg, ...userOrg };
212
212
  }
213
+ function normalizeCloudEndpoint(raw) {
214
+ const cloud = raw.cloud;
215
+ if (!cloud?.endpoint) return;
216
+ const ep = String(cloud.endpoint);
217
+ if (ep === "https://askexe.com/cloud" || ep === "https://askexe.com/cloud/") {
218
+ cloud.endpoint = "https://cloud.askexe.com";
219
+ process.stderr.write(
220
+ "[config] Auto-migrated cloud endpoint: askexe.com/cloud \u2192 cloud.askexe.com\n"
221
+ );
222
+ }
223
+ }
213
224
  async function loadConfig() {
214
225
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
215
226
  await ensurePrivateDir(dir);
@@ -235,6 +246,7 @@ async function loadConfig() {
235
246
  normalizeSessionLifecycle(migratedCfg);
236
247
  normalizeAutoUpdate(migratedCfg);
237
248
  normalizeOrchestration(migratedCfg);
249
+ normalizeCloudEndpoint(migratedCfg);
238
250
  const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
239
251
  if (config.dbPath.startsWith("~")) {
240
252
  config.dbPath = config.dbPath.replace(/^~/, os.homedir());
@@ -263,6 +275,7 @@ function loadConfigSync() {
263
275
  normalizeSessionLifecycle(migratedCfg);
264
276
  normalizeAutoUpdate(migratedCfg);
265
277
  normalizeOrchestration(migratedCfg);
278
+ normalizeCloudEndpoint(migratedCfg);
266
279
  const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
267
280
  if (config.dbPath.startsWith("~")) {
268
281
  config.dbPath = config.dbPath.replace(/^~/, os.homedir());
@@ -423,6 +436,7 @@ __export(agent_config_exports, {
423
436
  clearAgentRuntime: () => clearAgentRuntime,
424
437
  getAgentRuntime: () => getAgentRuntime,
425
438
  loadAgentConfig: () => loadAgentConfig,
439
+ normalizeCcModelName: () => normalizeCcModelName,
426
440
  saveAgentConfig: () => saveAgentConfig,
427
441
  setAgentMcps: () => setAgentMcps,
428
442
  setAgentRuntime: () => setAgentRuntime
@@ -451,6 +465,13 @@ function getAgentRuntime(agentId) {
451
465
  if (orgDefault) return orgDefault;
452
466
  return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
453
467
  }
468
+ function normalizeCcModelName(model) {
469
+ let ccModel = model.replace(/(\d+)\.(\d+)/g, "$1-$2");
470
+ if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
471
+ ccModel += "[1m]";
472
+ }
473
+ return ccModel;
474
+ }
454
475
  function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
455
476
  const knownModels = KNOWN_RUNTIMES[runtime];
456
477
  if (!knownModels) {
@@ -3742,7 +3763,7 @@ var init_database = __esm({
3742
3763
  });
3743
3764
 
3744
3765
  // src/lib/keychain.ts
3745
- import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
3766
+ import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2, rename, copyFile } from "fs/promises";
3746
3767
  import { existsSync as existsSync8, statSync as statSync3 } from "fs";
3747
3768
  import { execSync as execSync3 } from "child_process";
3748
3769
  import path7 from "path";
@@ -3777,12 +3798,14 @@ function linuxSecretAvailable() {
3777
3798
  function isRootOnlyTrustedServerKeyFile(keyPath) {
3778
3799
  if (process.platform !== "linux") return false;
3779
3800
  try {
3780
- const uid = typeof os5.userInfo().uid === "number" ? os5.userInfo().uid : -1;
3781
3801
  const st = statSync3(keyPath);
3782
3802
  if (!st.isFile() || (st.mode & 63) !== 0) return false;
3803
+ const uid = typeof os5.userInfo().uid === "number" ? os5.userInfo().uid : -1;
3783
3804
  if (uid === 0) return true;
3784
3805
  const exeOsDir = process.env.EXE_OS_DIR;
3785
- return Boolean(exeOsDir && path7.resolve(keyPath).startsWith(path7.resolve(exeOsDir) + path7.sep));
3806
+ if (exeOsDir && path7.resolve(keyPath).startsWith(path7.resolve(exeOsDir) + path7.sep)) return true;
3807
+ if (!linuxSecretAvailable()) return true;
3808
+ return false;
3786
3809
  } catch {
3787
3810
  return false;
3788
3811
  }
@@ -3932,15 +3955,25 @@ async function writeMachineBoundFileFallback(b64) {
3932
3955
  await mkdir3(dir, { recursive: true });
3933
3956
  const keyPath = getKeyPath();
3934
3957
  const machineKey = deriveMachineKey();
3935
- if (machineKey) {
3936
- const encrypted = encryptWithMachineKey(b64, machineKey);
3937
- await writeFile3(keyPath, encrypted + "\n", "utf-8");
3938
- await chmod2(keyPath, 384);
3939
- return "encrypted";
3958
+ const content = machineKey ? encryptWithMachineKey(b64, machineKey) + "\n" : b64 + "\n";
3959
+ const result = machineKey ? "encrypted" : "plaintext";
3960
+ const tmpPath = keyPath + ".tmp";
3961
+ try {
3962
+ if (existsSync8(keyPath)) {
3963
+ await copyFile(keyPath, keyPath + ".bak").catch(() => {
3964
+ });
3965
+ }
3966
+ await writeFile3(tmpPath, content, "utf-8");
3967
+ await chmod2(tmpPath, 384);
3968
+ await rename(tmpPath, keyPath);
3969
+ } catch (err) {
3970
+ try {
3971
+ await unlink(tmpPath);
3972
+ } catch {
3973
+ }
3974
+ throw err;
3940
3975
  }
3941
- await writeFile3(keyPath, b64 + "\n", "utf-8");
3942
- await chmod2(keyPath, 384);
3943
- return "plaintext";
3976
+ return result;
3944
3977
  }
3945
3978
  async function getMasterKey() {
3946
3979
  let nativeValue = macKeychainGet() ?? linuxSecretGet();
@@ -4007,7 +4040,7 @@ async function getMasterKey() {
4007
4040
  b64Value = content;
4008
4041
  }
4009
4042
  const key = Buffer.from(b64Value, "base64");
4010
- if (!content.startsWith(ENCRYPTED_PREFIX) && isRootOnlyTrustedServerKeyFile(keyPath)) {
4043
+ if (isRootOnlyTrustedServerKeyFile(keyPath)) {
4011
4044
  return key;
4012
4045
  }
4013
4046
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
@@ -6620,6 +6653,7 @@ var init_provider_table = __esm({
6620
6653
  // src/lib/intercom-queue.ts
6621
6654
  var intercom_queue_exports = {};
6622
6655
  __export(intercom_queue_exports, {
6656
+ _resetDrainGuard: () => _resetDrainGuard,
6623
6657
  clearQueueForAgent: () => clearQueueForAgent,
6624
6658
  drainForSession: () => drainForSession,
6625
6659
  drainQueue: () => drainQueue,
@@ -6665,38 +6699,47 @@ function queueIntercom(targetSession, reason) {
6665
6699
  writeQueue(queue);
6666
6700
  }
6667
6701
  function drainQueue(isSessionBusy2, sendKeys) {
6702
+ if (_draining) {
6703
+ logQueue("SKIP_DRAIN \u2014 previous drain still running (possible tmux hang)");
6704
+ return { drained: 0, failed: 0 };
6705
+ }
6668
6706
  const queue = readQueue();
6669
6707
  if (queue.length === 0) return { drained: 0, failed: 0 };
6708
+ _draining = true;
6670
6709
  const remaining = [];
6671
6710
  let drained = 0;
6672
6711
  let failed = 0;
6673
- for (const item of queue) {
6674
- const age = Date.now() - new Date(item.queuedAt).getTime();
6675
- if (age > TTL_MS) {
6676
- logQueue(`EXPIRED \u2192 ${item.targetSession} (${Math.round(age / 6e4)}min old, reason: ${item.reason})`);
6677
- failed++;
6678
- continue;
6679
- }
6680
- try {
6681
- if (!isSessionBusy2(item.targetSession)) {
6682
- const success = sendKeys(item.targetSession);
6683
- if (success) {
6684
- logQueue(`DRAINED \u2192 ${item.targetSession} (after ${item.attempts} retries)`);
6685
- drained++;
6686
- continue;
6712
+ try {
6713
+ for (const item of queue) {
6714
+ const age = Date.now() - new Date(item.queuedAt).getTime();
6715
+ if (age > TTL_MS) {
6716
+ logQueue(`EXPIRED \u2192 ${item.targetSession} (${Math.round(age / 6e4)}min old, reason: ${item.reason})`);
6717
+ failed++;
6718
+ continue;
6719
+ }
6720
+ try {
6721
+ if (!isSessionBusy2(item.targetSession)) {
6722
+ const success = sendKeys(item.targetSession);
6723
+ if (success) {
6724
+ logQueue(`DRAINED \u2192 ${item.targetSession} (after ${item.attempts} retries)`);
6725
+ drained++;
6726
+ continue;
6727
+ }
6687
6728
  }
6729
+ } catch {
6688
6730
  }
6689
- } catch {
6690
- }
6691
- item.attempts++;
6692
- if (item.attempts >= MAX_RETRIES2) {
6693
- logQueue(`FAILED \u2192 ${item.targetSession} (${MAX_RETRIES2} retries exhausted, reason: ${item.reason})`);
6694
- failed++;
6695
- continue;
6731
+ item.attempts++;
6732
+ if (item.attempts >= MAX_RETRIES2) {
6733
+ logQueue(`FAILED \u2192 ${item.targetSession} (${MAX_RETRIES2} retries exhausted, reason: ${item.reason})`);
6734
+ failed++;
6735
+ continue;
6736
+ }
6737
+ remaining.push(item);
6696
6738
  }
6697
- remaining.push(item);
6739
+ writeQueue(remaining);
6740
+ } finally {
6741
+ _draining = false;
6698
6742
  }
6699
- writeQueue(remaining);
6700
6743
  return { drained, failed };
6701
6744
  }
6702
6745
  function drainForSession(targetSession, sendKeys) {
@@ -6721,6 +6764,9 @@ function clearQueueForAgent(agentName) {
6721
6764
  logQueue(`CLEARED ${before - filtered.length} stale item(s) for ${agentName}`);
6722
6765
  }
6723
6766
  }
6767
+ function _resetDrainGuard() {
6768
+ _draining = false;
6769
+ }
6724
6770
  function logQueue(msg) {
6725
6771
  const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [queue] ${msg}
6726
6772
  `;
@@ -6732,13 +6778,14 @@ function logQueue(msg) {
6732
6778
  } catch {
6733
6779
  }
6734
6780
  }
6735
- var QUEUE_PATH, MAX_RETRIES2, TTL_MS, INTERCOM_LOG;
6781
+ var QUEUE_PATH, MAX_RETRIES2, TTL_MS, _draining, INTERCOM_LOG;
6736
6782
  var init_intercom_queue = __esm({
6737
6783
  "src/lib/intercom-queue.ts"() {
6738
6784
  "use strict";
6739
6785
  QUEUE_PATH = path10.join(os7.homedir(), ".exe-os", "intercom-queue.json");
6740
6786
  MAX_RETRIES2 = 5;
6741
6787
  TTL_MS = 60 * 60 * 1e3;
6788
+ _draining = false;
6742
6789
  INTERCOM_LOG = path10.join(os7.homedir(), ".exe-os", "intercom.log");
6743
6790
  }
6744
6791
  });
@@ -8379,6 +8426,13 @@ var init_embedder = __esm({
8379
8426
  });
8380
8427
 
8381
8428
  // src/lib/behaviors.ts
8429
+ var behaviors_exports = {};
8430
+ __export(behaviors_exports, {
8431
+ deactivateBehavior: () => deactivateBehavior,
8432
+ listBehaviors: () => listBehaviors,
8433
+ listBehaviorsByDomain: () => listBehaviorsByDomain,
8434
+ storeBehavior: () => storeBehavior
8435
+ });
8382
8436
  import crypto4 from "crypto";
8383
8437
  async function storeBehavior(opts) {
8384
8438
  try {
@@ -8415,6 +8469,76 @@ async function storeBehavior(opts) {
8415
8469
  });
8416
8470
  return id;
8417
8471
  }
8472
+ async function listBehaviors(agentId, projectName, limit = 30) {
8473
+ const client = getClient();
8474
+ const result = await client.execute({
8475
+ sql: `SELECT id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector, created_by_agent, created_by_device, source_session_id
8476
+ FROM behaviors
8477
+ WHERE agent_id = ? AND active = 1
8478
+ AND (project_name IS NULL OR project_name = ?)
8479
+ ORDER BY
8480
+ CASE WHEN priority = 'p0' THEN 0
8481
+ WHEN priority = 'p1' THEN 1
8482
+ ELSE 2 END,
8483
+ CASE
8484
+ WHEN lower(coalesce(domain, '')) IN ('identity', 'title', 'role') THEN 0
8485
+ WHEN lower(content) LIKE '%identity%' THEN 0
8486
+ WHEN lower(content) LIKE '%title%' THEN 0
8487
+ WHEN lower(content) LIKE '%who are you%' THEN 0
8488
+ ELSE 1
8489
+ END,
8490
+ updated_at DESC
8491
+ LIMIT ?`,
8492
+ args: [agentId, projectName ?? "", limit]
8493
+ });
8494
+ return result.rows.map((r) => ({
8495
+ id: String(r.id),
8496
+ agent_id: String(r.agent_id),
8497
+ project_name: r.project_name ? String(r.project_name) : null,
8498
+ domain: r.domain ? String(r.domain) : null,
8499
+ priority: String(r.priority || "p1"),
8500
+ content: String(r.content),
8501
+ active: Number(r.active),
8502
+ created_at: String(r.created_at),
8503
+ updated_at: String(r.updated_at),
8504
+ vector: r.vector ? Array.from(new Float32Array(r.vector)) : null,
8505
+ created_by_agent: r.created_by_agent ? String(r.created_by_agent) : null,
8506
+ created_by_device: r.created_by_device ? String(r.created_by_device) : null,
8507
+ source_session_id: r.source_session_id ? String(r.source_session_id) : null
8508
+ }));
8509
+ }
8510
+ async function listBehaviorsByDomain(agentId, domain) {
8511
+ const client = getClient();
8512
+ const result = await client.execute({
8513
+ sql: `SELECT id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector
8514
+ FROM behaviors
8515
+ WHERE agent_id = ? AND domain = ? AND active = 1`,
8516
+ args: [agentId, domain]
8517
+ });
8518
+ return result.rows.map((r) => ({
8519
+ id: String(r.id),
8520
+ agent_id: String(r.agent_id),
8521
+ project_name: r.project_name ? String(r.project_name) : null,
8522
+ domain: r.domain ? String(r.domain) : null,
8523
+ priority: String(r.priority || "p1"),
8524
+ content: String(r.content),
8525
+ active: Number(r.active),
8526
+ created_at: String(r.created_at),
8527
+ updated_at: String(r.updated_at),
8528
+ vector: r.vector ? Array.from(new Float32Array(r.vector)) : null,
8529
+ created_by_agent: r.created_by_agent ? String(r.created_by_agent) : null,
8530
+ created_by_device: r.created_by_device ? String(r.created_by_device) : null,
8531
+ source_session_id: r.source_session_id ? String(r.source_session_id) : null
8532
+ }));
8533
+ }
8534
+ async function deactivateBehavior(id) {
8535
+ const client = getClient();
8536
+ const result = await client.execute({
8537
+ sql: `UPDATE behaviors SET active = 0, updated_at = ? WHERE id = ? AND active = 1`,
8538
+ args: [(/* @__PURE__ */ new Date()).toISOString(), id]
8539
+ });
8540
+ return (result.rowsAffected ?? 0) > 0;
8541
+ }
8418
8542
  var init_behaviors = __esm({
8419
8543
  "src/lib/behaviors.ts"() {
8420
8544
  "use strict";
@@ -8823,6 +8947,20 @@ async function updateTask(input) {
8823
8947
  notifyTaskDone();
8824
8948
  }
8825
8949
  await markTaskNotificationsRead(taskFile);
8950
+ if (input.status === "needs_review" && !isCoordinator) {
8951
+ try {
8952
+ const { writeNotification: writeNotification2 } = await Promise.resolve().then(() => (init_notifications(), notifications_exports));
8953
+ await writeNotification2({
8954
+ agentId: String(row.assigned_to),
8955
+ agentRole: String(row.assigned_to),
8956
+ event: "task_complete",
8957
+ project: String(row.project_name),
8958
+ summary: `"${String(row.title)}" is ready for review`,
8959
+ taskFile
8960
+ });
8961
+ } catch {
8962
+ }
8963
+ }
8826
8964
  if (input.status === "done" || input.status === "closed") {
8827
8965
  try {
8828
8966
  await cascadeUnblock(taskId, input.baseDir, now);
@@ -9255,18 +9393,31 @@ function acquireSpawnLock2(sessionName) {
9255
9393
  mkdirSync9(SPAWN_LOCK_DIR, { recursive: true });
9256
9394
  }
9257
9395
  const lockFile = spawnLockPath(sessionName);
9258
- if (existsSync16(lockFile)) {
9259
- try {
9260
- const lock = JSON.parse(readFileSync11(lockFile, "utf8"));
9261
- const age = Date.now() - lock.timestamp;
9262
- if (isProcessAlive(lock.pid) && age < 6e4) {
9263
- return false;
9264
- }
9265
- } catch {
9396
+ const lockData = JSON.stringify({ pid: process.pid, timestamp: Date.now() });
9397
+ const { openSync: openSync3, closeSync: closeSync3, writeSync } = __require("fs");
9398
+ const { constants } = __require("fs");
9399
+ try {
9400
+ const fd = openSync3(lockFile, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 420);
9401
+ writeSync(fd, lockData);
9402
+ closeSync3(fd);
9403
+ return true;
9404
+ } catch (err) {
9405
+ if (err?.code !== "EEXIST") {
9406
+ return true;
9266
9407
  }
9267
9408
  }
9268
- writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
9269
- return true;
9409
+ try {
9410
+ const lock = JSON.parse(readFileSync11(lockFile, "utf8"));
9411
+ const age = Date.now() - lock.timestamp;
9412
+ if (isProcessAlive(lock.pid) && age < 6e4) {
9413
+ return false;
9414
+ }
9415
+ writeFileSync8(lockFile, lockData);
9416
+ return true;
9417
+ } catch {
9418
+ writeFileSync8(lockFile, lockData);
9419
+ return true;
9420
+ }
9270
9421
  }
9271
9422
  function releaseSpawnLock2(sessionName) {
9272
9423
  try {
@@ -9345,6 +9496,21 @@ function parseParentExe(sessionName, agentId) {
9345
9496
  function extractRootExe(name) {
9346
9497
  if (!name) return null;
9347
9498
  if (!name.includes("-")) return name;
9499
+ try {
9500
+ const roster = (init_employees(), __toCommonJS(employees_exports)).loadEmployeesSync();
9501
+ if (roster.length > 0) {
9502
+ const sortedNames = roster.map((e) => e.name).sort((a, b) => b.length - a.length);
9503
+ for (const agentName of sortedNames) {
9504
+ const escaped = agentName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
9505
+ const regex = new RegExp(`^${escaped}\\d*-(.+)$`);
9506
+ const match = name.match(regex);
9507
+ if (match) {
9508
+ return extractRootExe(match[1]);
9509
+ }
9510
+ }
9511
+ }
9512
+ } catch {
9513
+ }
9348
9514
  const parts = name.split("-").filter(Boolean);
9349
9515
  return parts.length > 0 ? parts[parts.length - 1] : null;
9350
9516
  }
@@ -9363,6 +9529,10 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
9363
9529
  function getParentExe(sessionKey) {
9364
9530
  try {
9365
9531
  const data = JSON.parse(readFileSync11(path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
9532
+ if (data.registeredAt) {
9533
+ const age = Date.now() - new Date(data.registeredAt).getTime();
9534
+ if (age > PARENT_EXE_CACHE_TTL_MS) return null;
9535
+ }
9366
9536
  return data.parentExe || null;
9367
9537
  } catch {
9368
9538
  return null;
@@ -9911,7 +10081,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
9911
10081
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
9912
10082
  } catch {
9913
10083
  }
9914
- let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName}`;
10084
+ let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName} EXE_SESSION_START_ISO=${(/* @__PURE__ */ new Date()).toISOString()}`;
9915
10085
  if (ccProvider !== DEFAULT_PROVIDER) {
9916
10086
  const cfg = PROVIDER_TABLE[ccProvider];
9917
10087
  if (cfg?.apiKeyEnv) {
@@ -9946,10 +10116,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
9946
10116
  }
9947
10117
  if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
9948
10118
  if (agentRtConfig.runtime === "claude" && agentRtConfig.model) {
9949
- let ccModel = agentRtConfig.model.replace(/(\d+)\.(\d+)/g, "$1-$2");
9950
- if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
9951
- ccModel += "[1m]";
9952
- }
10119
+ const { normalizeCcModelName: normalizeCcModelName2 } = (init_agent_config(), __toCommonJS(agent_config_exports));
10120
+ const ccModel = normalizeCcModelName2(agentRtConfig.model);
9953
10121
  envPrefix = `${envPrefix} ANTHROPIC_MODEL=${ccModel}`;
9954
10122
  }
9955
10123
  }
@@ -10050,7 +10218,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
10050
10218
  releaseSpawnLock2(sessionName);
10051
10219
  return { sessionName };
10052
10220
  }
10053
- 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;
10221
+ 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;
10054
10222
  var init_tmux_routing = __esm({
10055
10223
  "src/lib/tmux-routing.ts"() {
10056
10224
  "use strict";
@@ -10070,6 +10238,7 @@ var init_tmux_routing = __esm({
10070
10238
  SESSION_CACHE = path18.join(os11.homedir(), ".exe-os", "session-cache");
10071
10239
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
10072
10240
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
10241
+ PARENT_EXE_CACHE_TTL_MS = 4 * 60 * 60 * 1e3;
10073
10242
  VERIFY_PANE_LINES = 200;
10074
10243
  INTERCOM_DEBOUNCE_MS = 3e4;
10075
10244
  CODEX_DEBOUNCE_MS = 12e4;
@@ -10577,7 +10746,13 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
10577
10746
  taskFile
10578
10747
  });
10579
10748
  const originalPriority = String(row.priority).toLowerCase();
10580
- const autoApprove = originalPriority === "p2" && result?.toLowerCase().includes("tests pass");
10749
+ const resultLower = result?.toLowerCase() ?? "";
10750
+ const hasTestEvidence = (
10751
+ // Vitest/Jest output patterns (hard to fake without actually running tests)
10752
+ /\d+\s+pass(ed|ing)/.test(resultLower) || /test files?\s+\d+\s+passed/.test(resultLower) || /tests?\s+\d+\s+passed/.test(resultLower)
10753
+ );
10754
+ const hasNoFailures = !/fail(ed|ure|ing)|error/i.test(resultLower);
10755
+ const autoApprove = originalPriority === "p2" && hasTestEvidence && hasNoFailures;
10581
10756
  if (!autoApprove) {
10582
10757
  try {
10583
10758
  const key = getSessionKey();
@@ -10585,6 +10760,13 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
10585
10760
  if (exeSession) {
10586
10761
  sendIntercom(exeSession);
10587
10762
  }
10763
+ if (reviewer && reviewer !== coordinatorName && reviewer !== exeSession) {
10764
+ const { employeeSessionName: employeeSessionName2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
10765
+ if (exeSession) {
10766
+ const reviewerSession = employeeSessionName2(reviewer, exeSession);
10767
+ sendIntercom(reviewerSession);
10768
+ }
10769
+ }
10588
10770
  } catch {
10589
10771
  }
10590
10772
  }
@@ -11273,6 +11455,26 @@ async function main2() {
11273
11455
  } catch {
11274
11456
  }
11275
11457
  }
11458
+ if (!isCoordinator) {
11459
+ try {
11460
+ const { listBehaviors: listBehaviors2 } = await Promise.resolve().then(() => (init_behaviors(), behaviors_exports));
11461
+ const sessionStart = process.env.EXE_SESSION_START_ISO ?? "";
11462
+ if (sessionStart) {
11463
+ const behaviors = await listBehaviors2(agent);
11464
+ const newBehaviors = behaviors.filter(
11465
+ (b) => b.created_at > sessionStart
11466
+ );
11467
+ if (newBehaviors.length > 0) {
11468
+ process.stdout.write("\n[NEW BEHAVIORS \u2014 apply these immediately]\n");
11469
+ for (const b of newBehaviors) {
11470
+ process.stdout.write(` [${b.priority}] ${b.content.slice(0, 300)}
11471
+ `);
11472
+ }
11473
+ }
11474
+ }
11475
+ } catch {
11476
+ }
11477
+ }
11276
11478
  try {
11277
11479
  const { readUnreadNotifications: readUnreadNotifications2, markAsRead: markAsRead2 } = await Promise.resolve().then(() => (init_notifications(), notifications_exports));
11278
11480
  const notifications = await readUnreadNotifications2(agent);