@askexenow/exe-os 0.9.114 → 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 (79) hide show
  1. package/dist/bin/agentic-ontology-backfill.js +13 -1
  2. package/dist/bin/agentic-reflection-backfill.js +13 -1
  3. package/dist/bin/agentic-semantic-label.js +13 -1
  4. package/dist/bin/backfill-conversations.js +13 -1
  5. package/dist/bin/backfill-responses.js +13 -1
  6. package/dist/bin/backfill-vectors.js +13 -1
  7. package/dist/bin/bulk-sync-postgres.js +13 -1
  8. package/dist/bin/cleanup-stale-review-tasks.js +449 -104
  9. package/dist/bin/cli.js +317 -40
  10. package/dist/bin/exe-assign.js +13 -1
  11. package/dist/bin/exe-boot.js +202 -39
  12. package/dist/bin/exe-cloud.js +13 -1
  13. package/dist/bin/exe-dispatch.js +315 -38
  14. package/dist/bin/exe-doctor.js +28 -2
  15. package/dist/bin/exe-export-behaviors.js +14 -1
  16. package/dist/bin/exe-forget.js +13 -1
  17. package/dist/bin/exe-gateway.js +315 -38
  18. package/dist/bin/exe-heartbeat.js +450 -104
  19. package/dist/bin/exe-kill.js +13 -1
  20. package/dist/bin/exe-launch-agent.js +14 -1
  21. package/dist/bin/exe-pending-messages.js +429 -84
  22. package/dist/bin/exe-pending-notifications.js +429 -84
  23. package/dist/bin/exe-pending-reviews.js +429 -84
  24. package/dist/bin/exe-rename.js +13 -1
  25. package/dist/bin/exe-review.js +13 -1
  26. package/dist/bin/exe-search.js +14 -1
  27. package/dist/bin/exe-session-cleanup.js +315 -38
  28. package/dist/bin/exe-settings.js +12 -0
  29. package/dist/bin/exe-start-codex.js +14 -1
  30. package/dist/bin/exe-start-opencode.js +14 -1
  31. package/dist/bin/exe-status.js +439 -105
  32. package/dist/bin/exe-support.js +12 -0
  33. package/dist/bin/exe-team.js +13 -1
  34. package/dist/bin/git-sweep.js +315 -38
  35. package/dist/bin/graph-backfill.js +13 -1
  36. package/dist/bin/graph-export.js +13 -1
  37. package/dist/bin/intercom-check.js +222 -38
  38. package/dist/bin/scan-tasks.js +315 -38
  39. package/dist/bin/setup.js +14 -1
  40. package/dist/bin/shard-migrate.js +13 -1
  41. package/dist/gateway/index.js +315 -38
  42. package/dist/hooks/bug-report-worker.js +315 -38
  43. package/dist/hooks/codex-stop-task-finalizer.js +277 -26
  44. package/dist/hooks/commit-complete.js +315 -38
  45. package/dist/hooks/error-recall.js +14 -1
  46. package/dist/hooks/ingest.js +330 -39
  47. package/dist/hooks/instructions-loaded.js +13 -1
  48. package/dist/hooks/notification.js +13 -1
  49. package/dist/hooks/post-compact.js +403 -61
  50. package/dist/hooks/post-tool-combined.js +480 -137
  51. package/dist/hooks/pre-compact.js +315 -38
  52. package/dist/hooks/pre-tool-use.js +34 -2
  53. package/dist/hooks/prompt-submit.js +315 -38
  54. package/dist/hooks/session-end.js +125 -38
  55. package/dist/hooks/session-start.js +35 -2
  56. package/dist/hooks/stop.js +397 -61
  57. package/dist/hooks/subagent-stop.js +396 -61
  58. package/dist/hooks/summary-worker.js +410 -112
  59. package/dist/index.js +315 -38
  60. package/dist/lib/cloud-sync.js +1 -1
  61. package/dist/lib/config.js +13 -0
  62. package/dist/lib/embedder.js +13 -0
  63. package/dist/lib/exe-daemon.js +351 -44
  64. package/dist/lib/hybrid-search.js +14 -1
  65. package/dist/lib/keychain.js +1 -1
  66. package/dist/lib/messaging.js +395 -74
  67. package/dist/lib/schedules.js +13 -1
  68. package/dist/lib/skill-learning.js +13 -0
  69. package/dist/lib/store.js +13 -1
  70. package/dist/lib/tasks.js +314 -37
  71. package/dist/lib/tmux-routing.js +314 -37
  72. package/dist/mcp/server.js +331 -40
  73. package/dist/mcp/tools/create-task.js +314 -37
  74. package/dist/mcp/tools/list-tasks.js +406 -57
  75. package/dist/mcp/tools/send-message.js +395 -74
  76. package/dist/mcp/tools/update-task.js +314 -37
  77. package/dist/runtime/index.js +315 -38
  78. package/dist/tui/App.js +317 -40
  79. package/package.json +1 -1
package/dist/bin/cli.js CHANGED
@@ -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());
@@ -2840,7 +2853,7 @@ async function getMasterKey() {
2840
2853
  b64Value = content;
2841
2854
  }
2842
2855
  const key = Buffer.from(b64Value, "base64");
2843
- if (!content.startsWith(ENCRYPTED_PREFIX) && isRootOnlyTrustedServerKeyFile(keyPath)) {
2856
+ if (isRootOnlyTrustedServerKeyFile(keyPath)) {
2844
2857
  return key;
2845
2858
  }
2846
2859
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
@@ -13960,6 +13973,7 @@ var init_provider_table = __esm({
13960
13973
  // src/lib/intercom-queue.ts
13961
13974
  var intercom_queue_exports = {};
13962
13975
  __export(intercom_queue_exports, {
13976
+ _resetDrainGuard: () => _resetDrainGuard,
13963
13977
  clearQueueForAgent: () => clearQueueForAgent,
13964
13978
  drainForSession: () => drainForSession,
13965
13979
  drainQueue: () => drainQueue,
@@ -14005,38 +14019,47 @@ function queueIntercom(targetSession, reason) {
14005
14019
  writeQueue(queue);
14006
14020
  }
14007
14021
  function drainQueue(isSessionBusy2, sendKeys) {
14022
+ if (_draining) {
14023
+ logQueue("SKIP_DRAIN \u2014 previous drain still running (possible tmux hang)");
14024
+ return { drained: 0, failed: 0 };
14025
+ }
14008
14026
  const queue = readQueue();
14009
14027
  if (queue.length === 0) return { drained: 0, failed: 0 };
14028
+ _draining = true;
14010
14029
  const remaining = [];
14011
14030
  let drained = 0;
14012
14031
  let failed = 0;
14013
- for (const item of queue) {
14014
- const age = Date.now() - new Date(item.queuedAt).getTime();
14015
- if (age > TTL_MS) {
14016
- logQueue(`EXPIRED \u2192 ${item.targetSession} (${Math.round(age / 6e4)}min old, reason: ${item.reason})`);
14017
- failed++;
14018
- continue;
14019
- }
14020
- try {
14021
- if (!isSessionBusy2(item.targetSession)) {
14022
- const success = sendKeys(item.targetSession);
14023
- if (success) {
14024
- logQueue(`DRAINED \u2192 ${item.targetSession} (after ${item.attempts} retries)`);
14025
- drained++;
14026
- continue;
14032
+ try {
14033
+ for (const item of queue) {
14034
+ const age = Date.now() - new Date(item.queuedAt).getTime();
14035
+ if (age > TTL_MS) {
14036
+ logQueue(`EXPIRED \u2192 ${item.targetSession} (${Math.round(age / 6e4)}min old, reason: ${item.reason})`);
14037
+ failed++;
14038
+ continue;
14039
+ }
14040
+ try {
14041
+ if (!isSessionBusy2(item.targetSession)) {
14042
+ const success = sendKeys(item.targetSession);
14043
+ if (success) {
14044
+ logQueue(`DRAINED \u2192 ${item.targetSession} (after ${item.attempts} retries)`);
14045
+ drained++;
14046
+ continue;
14047
+ }
14027
14048
  }
14049
+ } catch {
14028
14050
  }
14029
- } catch {
14030
- }
14031
- item.attempts++;
14032
- if (item.attempts >= MAX_RETRIES2) {
14033
- logQueue(`FAILED \u2192 ${item.targetSession} (${MAX_RETRIES2} retries exhausted, reason: ${item.reason})`);
14034
- failed++;
14035
- continue;
14051
+ item.attempts++;
14052
+ if (item.attempts >= MAX_RETRIES2) {
14053
+ logQueue(`FAILED \u2192 ${item.targetSession} (${MAX_RETRIES2} retries exhausted, reason: ${item.reason})`);
14054
+ failed++;
14055
+ continue;
14056
+ }
14057
+ remaining.push(item);
14036
14058
  }
14037
- remaining.push(item);
14059
+ writeQueue(remaining);
14060
+ } finally {
14061
+ _draining = false;
14038
14062
  }
14039
- writeQueue(remaining);
14040
14063
  return { drained, failed };
14041
14064
  }
14042
14065
  function drainForSession(targetSession, sendKeys) {
@@ -14061,6 +14084,9 @@ function clearQueueForAgent(agentName) {
14061
14084
  logQueue(`CLEARED ${before - filtered.length} stale item(s) for ${agentName}`);
14062
14085
  }
14063
14086
  }
14087
+ function _resetDrainGuard() {
14088
+ _draining = false;
14089
+ }
14064
14090
  function logQueue(msg) {
14065
14091
  const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [queue] ${msg}
14066
14092
  `;
@@ -14072,13 +14098,14 @@ function logQueue(msg) {
14072
14098
  } catch {
14073
14099
  }
14074
14100
  }
14075
- var QUEUE_PATH, MAX_RETRIES2, TTL_MS, INTERCOM_LOG;
14101
+ var QUEUE_PATH, MAX_RETRIES2, TTL_MS, _draining, INTERCOM_LOG;
14076
14102
  var init_intercom_queue = __esm({
14077
14103
  "src/lib/intercom-queue.ts"() {
14078
14104
  "use strict";
14079
14105
  QUEUE_PATH = path26.join(os15.homedir(), ".exe-os", "intercom-queue.json");
14080
14106
  MAX_RETRIES2 = 5;
14081
14107
  TTL_MS = 60 * 60 * 1e3;
14108
+ _draining = false;
14082
14109
  INTERCOM_LOG = path26.join(os15.homedir(), ".exe-os", "intercom.log");
14083
14110
  }
14084
14111
  });
@@ -14201,6 +14228,17 @@ var init_task_scope = __esm({
14201
14228
  });
14202
14229
 
14203
14230
  // src/lib/notifications.ts
14231
+ var notifications_exports = {};
14232
+ __export(notifications_exports, {
14233
+ cleanupOldNotifications: () => cleanupOldNotifications,
14234
+ formatNotifications: () => formatNotifications,
14235
+ markAsRead: () => markAsRead,
14236
+ markAsReadByTaskFile: () => markAsReadByTaskFile,
14237
+ markDoneTaskNotificationsAsRead: () => markDoneTaskNotificationsAsRead,
14238
+ migrateJsonNotifications: () => migrateJsonNotifications,
14239
+ readUnreadNotifications: () => readUnreadNotifications,
14240
+ writeNotification: () => writeNotification
14241
+ });
14204
14242
  import crypto7 from "crypto";
14205
14243
  import path28 from "path";
14206
14244
  import os16 from "os";
@@ -14237,6 +14275,52 @@ async function writeNotification(notification) {
14237
14275
  `);
14238
14276
  }
14239
14277
  }
14278
+ async function readUnreadNotifications(agentFilter, sessionScope) {
14279
+ try {
14280
+ const client = getClient();
14281
+ const conditions = ["read = 0"];
14282
+ const args2 = [];
14283
+ const scope = strictSessionScopeFilter(sessionScope);
14284
+ if (agentFilter) {
14285
+ conditions.push("agent_id = ?");
14286
+ args2.push(agentFilter);
14287
+ }
14288
+ const result = await client.execute({
14289
+ sql: `SELECT id, agent_id, agent_role, event, project, summary, task_file, session_scope, created_at
14290
+ FROM notifications
14291
+ WHERE ${conditions.join(" AND ")}${scope.sql}
14292
+ ORDER BY created_at ASC`,
14293
+ args: [...args2, ...scope.args]
14294
+ });
14295
+ return result.rows.map((r) => ({
14296
+ id: String(r.id),
14297
+ agentId: String(r.agent_id),
14298
+ agentRole: String(r.agent_role),
14299
+ event: String(r.event),
14300
+ project: String(r.project),
14301
+ summary: String(r.summary),
14302
+ taskFile: r.task_file ? String(r.task_file) : void 0,
14303
+ sessionScope: r.session_scope == null ? null : String(r.session_scope),
14304
+ timestamp: String(r.created_at),
14305
+ read: false
14306
+ }));
14307
+ } catch {
14308
+ return [];
14309
+ }
14310
+ }
14311
+ async function markAsRead(ids, sessionScope) {
14312
+ if (ids.length === 0) return;
14313
+ try {
14314
+ const client = getClient();
14315
+ const placeholders = ids.map(() => "?").join(", ");
14316
+ const scope = strictSessionScopeFilter(sessionScope);
14317
+ await client.execute({
14318
+ sql: `UPDATE notifications SET read = 1 WHERE id IN (${placeholders})${scope.sql}`,
14319
+ args: [...ids, ...scope.args]
14320
+ });
14321
+ } catch {
14322
+ }
14323
+ }
14240
14324
  async function markAsReadByTaskFile(taskFile, sessionScope) {
14241
14325
  try {
14242
14326
  const client = getClient();
@@ -14249,11 +14333,144 @@ async function markAsReadByTaskFile(taskFile, sessionScope) {
14249
14333
  } catch {
14250
14334
  }
14251
14335
  }
14336
+ async function cleanupOldNotifications(daysOld = CLEANUP_DAYS, sessionScope) {
14337
+ try {
14338
+ const client = getClient();
14339
+ const cutoff = new Date(
14340
+ Date.now() - daysOld * 24 * 60 * 60 * 1e3
14341
+ ).toISOString();
14342
+ const scope = strictSessionScopeFilter(sessionScope);
14343
+ const result = await client.execute({
14344
+ sql: `DELETE FROM notifications WHERE created_at < ?${scope.sql}`,
14345
+ args: [cutoff, ...scope.args]
14346
+ });
14347
+ return result.rowsAffected;
14348
+ } catch {
14349
+ return 0;
14350
+ }
14351
+ }
14352
+ async function markDoneTaskNotificationsAsRead(sessionScope) {
14353
+ try {
14354
+ const client = getClient();
14355
+ const scope = strictSessionScopeFilter(sessionScope);
14356
+ const result = await client.execute({
14357
+ sql: `UPDATE notifications SET read = 1
14358
+ WHERE read = 0
14359
+ AND task_file IS NOT NULL
14360
+ ${scope.sql}
14361
+ AND task_file IN (
14362
+ SELECT task_file FROM tasks WHERE status = 'done'${scope.sql}
14363
+ )`,
14364
+ args: [...scope.args, ...scope.args]
14365
+ });
14366
+ return result.rowsAffected;
14367
+ } catch {
14368
+ return 0;
14369
+ }
14370
+ }
14371
+ function formatNotifications(notifications) {
14372
+ if (notifications.length === 0) return "";
14373
+ const grouped = /* @__PURE__ */ new Map();
14374
+ for (const n of notifications) {
14375
+ const key = `${n.agentId}|${n.agentRole}`;
14376
+ if (!grouped.has(key)) grouped.set(key, []);
14377
+ grouped.get(key).push(n);
14378
+ }
14379
+ const lines = [];
14380
+ lines.push(`## Notifications (${notifications.length} unread)
14381
+ `);
14382
+ for (const [key, items] of grouped) {
14383
+ const [agentId, agentRole] = key.split("|");
14384
+ lines.push(`**${agentId}** (${agentRole}):`);
14385
+ for (const item of items) {
14386
+ const ago = formatTimeAgo(item.timestamp);
14387
+ const icon = eventIcon(item.event);
14388
+ lines.push(`- ${icon} ${item.summary} (${item.project}) \u2014 ${ago}`);
14389
+ }
14390
+ lines.push("");
14391
+ }
14392
+ return lines.join("\n");
14393
+ }
14394
+ async function migrateJsonNotifications() {
14395
+ const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR || path28.join(os16.homedir(), ".exe-os");
14396
+ const notifDir = path28.join(base, "notifications");
14397
+ if (!existsSync26(notifDir)) return 0;
14398
+ let migrated = 0;
14399
+ try {
14400
+ const files = readdirSync5(notifDir).filter((f) => f.endsWith(".json"));
14401
+ if (files.length === 0) return 0;
14402
+ const client = getClient();
14403
+ for (const file of files) {
14404
+ try {
14405
+ const filePath = path28.join(notifDir, file);
14406
+ const data = JSON.parse(readFileSync21(filePath, "utf8"));
14407
+ await client.execute({
14408
+ sql: `INSERT OR IGNORE INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
14409
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
14410
+ args: [
14411
+ crypto7.randomUUID(),
14412
+ data.agentId ?? "unknown",
14413
+ data.agentRole ?? "unknown",
14414
+ data.event ?? "session_summary",
14415
+ data.project ?? "unknown",
14416
+ data.summary ?? "",
14417
+ data.taskFile ?? null,
14418
+ null,
14419
+ data.read ? 1 : 0,
14420
+ data.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
14421
+ ]
14422
+ });
14423
+ unlinkSync9(filePath);
14424
+ migrated++;
14425
+ } catch {
14426
+ }
14427
+ }
14428
+ try {
14429
+ const remaining = readdirSync5(notifDir);
14430
+ if (remaining.length === 0) {
14431
+ rmdirSync(notifDir);
14432
+ }
14433
+ } catch {
14434
+ }
14435
+ } catch {
14436
+ }
14437
+ return migrated;
14438
+ }
14439
+ function eventIcon(event) {
14440
+ switch (event) {
14441
+ case "task_complete":
14442
+ return "Completed:";
14443
+ case "task_needs_fix":
14444
+ return "Needs fix:";
14445
+ case "session_summary":
14446
+ return "Session:";
14447
+ case "error_spike":
14448
+ return "Errors:";
14449
+ case "orphan_task":
14450
+ return "Orphan:";
14451
+ case "subtasks_complete":
14452
+ return "Subtasks done:";
14453
+ case "capacity_relaunch":
14454
+ return "Relaunched:";
14455
+ }
14456
+ }
14457
+ function formatTimeAgo(timestamp) {
14458
+ const diffMs = Date.now() - new Date(timestamp).getTime();
14459
+ const mins = Math.floor(diffMs / 6e4);
14460
+ if (mins < 1) return "just now";
14461
+ if (mins < 60) return `${mins}m ago`;
14462
+ const hours = Math.floor(mins / 60);
14463
+ if (hours < 24) return `${hours}h ago`;
14464
+ const days = Math.floor(hours / 24);
14465
+ return `${days}d ago`;
14466
+ }
14467
+ var CLEANUP_DAYS;
14252
14468
  var init_notifications = __esm({
14253
14469
  "src/lib/notifications.ts"() {
14254
14470
  "use strict";
14255
14471
  init_database();
14256
14472
  init_task_scope();
14473
+ CLEANUP_DAYS = 7;
14257
14474
  }
14258
14475
  });
14259
14476
 
@@ -15260,7 +15477,13 @@ async function createReviewForCompletedTask(row, result, _baseDir, now2) {
15260
15477
  taskFile
15261
15478
  });
15262
15479
  const originalPriority = String(row.priority).toLowerCase();
15263
- const autoApprove = originalPriority === "p2" && result?.toLowerCase().includes("tests pass");
15480
+ const resultLower = result?.toLowerCase() ?? "";
15481
+ const hasTestEvidence = (
15482
+ // Vitest/Jest output patterns (hard to fake without actually running tests)
15483
+ /\d+\s+pass(ed|ing)/.test(resultLower) || /test files?\s+\d+\s+passed/.test(resultLower) || /tests?\s+\d+\s+passed/.test(resultLower)
15484
+ );
15485
+ const hasNoFailures = !/fail(ed|ure|ing)|error/i.test(resultLower);
15486
+ const autoApprove = originalPriority === "p2" && hasTestEvidence && hasNoFailures;
15264
15487
  if (!autoApprove) {
15265
15488
  try {
15266
15489
  const key = getSessionKey();
@@ -15268,6 +15491,13 @@ async function createReviewForCompletedTask(row, result, _baseDir, now2) {
15268
15491
  if (exeSession) {
15269
15492
  sendIntercom(exeSession);
15270
15493
  }
15494
+ if (reviewer && reviewer !== coordinatorName && reviewer !== exeSession) {
15495
+ const { employeeSessionName: employeeSessionName2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
15496
+ if (exeSession) {
15497
+ const reviewerSession = employeeSessionName2(reviewer, exeSession);
15498
+ sendIntercom(reviewerSession);
15499
+ }
15500
+ }
15271
15501
  } catch {
15272
15502
  }
15273
15503
  }
@@ -16043,6 +16273,20 @@ async function updateTask(input) {
16043
16273
  notifyTaskDone();
16044
16274
  }
16045
16275
  await markTaskNotificationsRead(taskFile);
16276
+ if (input.status === "needs_review" && !isCoordinator) {
16277
+ try {
16278
+ const { writeNotification: writeNotification2 } = await Promise.resolve().then(() => (init_notifications(), notifications_exports));
16279
+ await writeNotification2({
16280
+ agentId: String(row.assigned_to),
16281
+ agentRole: String(row.assigned_to),
16282
+ event: "task_complete",
16283
+ project: String(row.project_name),
16284
+ summary: `"${String(row.title)}" is ready for review`,
16285
+ taskFile
16286
+ });
16287
+ } catch {
16288
+ }
16289
+ }
16046
16290
  if (input.status === "done" || input.status === "closed") {
16047
16291
  try {
16048
16292
  await cascadeUnblock(taskId, input.baseDir, now2);
@@ -16475,18 +16719,31 @@ function acquireSpawnLock2(sessionName) {
16475
16719
  mkdirSync21(SPAWN_LOCK_DIR, { recursive: true });
16476
16720
  }
16477
16721
  const lockFile = spawnLockPath(sessionName);
16478
- if (existsSync29(lockFile)) {
16479
- try {
16480
- const lock = JSON.parse(readFileSync23(lockFile, "utf8"));
16481
- const age = Date.now() - lock.timestamp;
16482
- if (isProcessAlive(lock.pid) && age < 6e4) {
16483
- return false;
16484
- }
16485
- } catch {
16722
+ const lockData = JSON.stringify({ pid: process.pid, timestamp: Date.now() });
16723
+ const { openSync: openSync5, closeSync: closeSync5, writeSync } = __require("fs");
16724
+ const { constants: constants3 } = __require("fs");
16725
+ try {
16726
+ const fd = openSync5(lockFile, constants3.O_WRONLY | constants3.O_CREAT | constants3.O_EXCL, 420);
16727
+ writeSync(fd, lockData);
16728
+ closeSync5(fd);
16729
+ return true;
16730
+ } catch (err) {
16731
+ if (err?.code !== "EEXIST") {
16732
+ return true;
16486
16733
  }
16487
16734
  }
16488
- writeFileSync20(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
16489
- return true;
16735
+ try {
16736
+ const lock = JSON.parse(readFileSync23(lockFile, "utf8"));
16737
+ const age = Date.now() - lock.timestamp;
16738
+ if (isProcessAlive(lock.pid) && age < 6e4) {
16739
+ return false;
16740
+ }
16741
+ writeFileSync20(lockFile, lockData);
16742
+ return true;
16743
+ } catch {
16744
+ writeFileSync20(lockFile, lockData);
16745
+ return true;
16746
+ }
16490
16747
  }
16491
16748
  function releaseSpawnLock2(sessionName) {
16492
16749
  try {
@@ -16565,6 +16822,21 @@ function parseParentExe(sessionName, agentId) {
16565
16822
  function extractRootExe(name) {
16566
16823
  if (!name) return null;
16567
16824
  if (!name.includes("-")) return name;
16825
+ try {
16826
+ const roster = (init_employees(), __toCommonJS(employees_exports)).loadEmployeesSync();
16827
+ if (roster.length > 0) {
16828
+ const sortedNames = roster.map((e) => e.name).sort((a, b) => b.length - a.length);
16829
+ for (const agentName of sortedNames) {
16830
+ const escaped = agentName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
16831
+ const regex = new RegExp(`^${escaped}\\d*-(.+)$`);
16832
+ const match = name.match(regex);
16833
+ if (match) {
16834
+ return extractRootExe(match[1]);
16835
+ }
16836
+ }
16837
+ }
16838
+ } catch {
16839
+ }
16568
16840
  const parts = name.split("-").filter(Boolean);
16569
16841
  return parts.length > 0 ? parts[parts.length - 1] : null;
16570
16842
  }
@@ -16583,6 +16855,10 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
16583
16855
  function getParentExe(sessionKey) {
16584
16856
  try {
16585
16857
  const data = JSON.parse(readFileSync23(path34.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
16858
+ if (data.registeredAt) {
16859
+ const age = Date.now() - new Date(data.registeredAt).getTime();
16860
+ if (age > PARENT_EXE_CACHE_TTL_MS) return null;
16861
+ }
16586
16862
  return data.parentExe || null;
16587
16863
  } catch {
16588
16864
  return null;
@@ -17131,7 +17407,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
17131
17407
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
17132
17408
  } catch {
17133
17409
  }
17134
- let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName}`;
17410
+ let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName} EXE_SESSION_START_ISO=${(/* @__PURE__ */ new Date()).toISOString()}`;
17135
17411
  if (ccProvider !== DEFAULT_PROVIDER) {
17136
17412
  const cfg = PROVIDER_TABLE[ccProvider];
17137
17413
  if (cfg?.apiKeyEnv) {
@@ -17268,7 +17544,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
17268
17544
  releaseSpawnLock2(sessionName);
17269
17545
  return { sessionName };
17270
17546
  }
17271
- 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;
17547
+ 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;
17272
17548
  var init_tmux_routing = __esm({
17273
17549
  "src/lib/tmux-routing.ts"() {
17274
17550
  "use strict";
@@ -17288,6 +17564,7 @@ var init_tmux_routing = __esm({
17288
17564
  SESSION_CACHE = path34.join(os18.homedir(), ".exe-os", "session-cache");
17289
17565
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
17290
17566
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
17567
+ PARENT_EXE_CACHE_TTL_MS = 4 * 60 * 60 * 1e3;
17291
17568
  VERIFY_PANE_LINES = 200;
17292
17569
  INTERCOM_DEBOUNCE_MS = 3e4;
17293
17570
  CODEX_DEBOUNCE_MS = 12e4;
@@ -35767,7 +36044,7 @@ function agentColor(agentId) {
35767
36044
  return "#F0EDE8";
35768
36045
  }
35769
36046
  }
35770
- function formatTimeAgo(iso) {
36047
+ function formatTimeAgo2(iso) {
35771
36048
  const diff2 = Date.now() - new Date(iso).getTime();
35772
36049
  const hours = Math.floor(diff2 / 36e5);
35773
36050
  if (hours < 1) return "just now";
@@ -36093,7 +36370,7 @@ function WikiView({ onBack }) {
36093
36370
  result.rawText.length > 80 ? "..." : ""
36094
36371
  ] }),
36095
36372
  " ",
36096
- /* @__PURE__ */ jsx13(Text, { color: isSelected ? "#F5D76E" : "#3D3660", dimColor: !isSelected, children: formatTimeAgo(result.timestamp) })
36373
+ /* @__PURE__ */ jsx13(Text, { color: isSelected ? "#F5D76E" : "#3D3660", dimColor: !isSelected, children: formatTimeAgo2(result.timestamp) })
36097
36374
  ]
36098
36375
  }
36099
36376
  ),
@@ -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());
@@ -4963,7 +4975,7 @@ async function getMasterKey() {
4963
4975
  b64Value = content;
4964
4976
  }
4965
4977
  const key = Buffer.from(b64Value, "base64");
4966
- if (!content.startsWith(ENCRYPTED_PREFIX) && isRootOnlyTrustedServerKeyFile(keyPath)) {
4978
+ if (isRootOnlyTrustedServerKeyFile(keyPath)) {
4967
4979
  return key;
4968
4980
  }
4969
4981
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);