@askexenow/exe-os 0.9.11 → 0.9.13

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 (67) hide show
  1. package/dist/bin/backfill-conversations.js +22 -1
  2. package/dist/bin/backfill-responses.js +22 -1
  3. package/dist/bin/backfill-vectors.js +22 -1
  4. package/dist/bin/cleanup-stale-review-tasks.js +22 -1
  5. package/dist/bin/cli.js +22 -1
  6. package/dist/bin/exe-assign.js +22 -1
  7. package/dist/bin/exe-boot.js +22 -1
  8. package/dist/bin/exe-dispatch.js +22 -1
  9. package/dist/bin/exe-doctor.js +22 -1
  10. package/dist/bin/exe-export-behaviors.js +22 -1
  11. package/dist/bin/exe-forget.js +22 -1
  12. package/dist/bin/exe-gateway.js +22 -1
  13. package/dist/bin/exe-heartbeat.js +22 -1
  14. package/dist/bin/exe-kill.js +22 -1
  15. package/dist/bin/exe-launch-agent.js +22 -1
  16. package/dist/bin/exe-link.js +22 -1
  17. package/dist/bin/exe-pending-messages.js +22 -1
  18. package/dist/bin/exe-pending-notifications.js +22 -1
  19. package/dist/bin/exe-pending-reviews.js +22 -1
  20. package/dist/bin/exe-rename.js +22 -1
  21. package/dist/bin/exe-review.js +22 -1
  22. package/dist/bin/exe-search.js +22 -1
  23. package/dist/bin/exe-session-cleanup.js +22 -1
  24. package/dist/bin/exe-start-codex.js +22 -1
  25. package/dist/bin/exe-start-opencode.js +22 -1
  26. package/dist/bin/exe-status.js +22 -1
  27. package/dist/bin/exe-team.js +22 -1
  28. package/dist/bin/git-sweep.js +22 -1
  29. package/dist/bin/graph-backfill.js +22 -1
  30. package/dist/bin/graph-export.js +22 -1
  31. package/dist/bin/scan-tasks.js +22 -1
  32. package/dist/bin/setup.js +22 -1
  33. package/dist/bin/shard-migrate.js +22 -1
  34. package/dist/bin/wiki-sync.js +22 -1
  35. package/dist/gateway/index.js +22 -1
  36. package/dist/hooks/bug-report-worker.js +22 -1
  37. package/dist/hooks/codex-stop-task-finalizer.js +22 -1
  38. package/dist/hooks/commit-complete.js +22 -1
  39. package/dist/hooks/error-recall.js +22 -1
  40. package/dist/hooks/ingest-worker.js +22 -1
  41. package/dist/hooks/ingest.js +3345 -232
  42. package/dist/hooks/instructions-loaded.js +22 -1
  43. package/dist/hooks/notification.js +22 -1
  44. package/dist/hooks/post-compact.js +22 -1
  45. package/dist/hooks/pre-compact.js +22 -1
  46. package/dist/hooks/pre-tool-use.js +22 -1
  47. package/dist/hooks/prompt-ingest-worker.js +22 -1
  48. package/dist/hooks/prompt-submit.js +1700 -1396
  49. package/dist/hooks/response-ingest-worker.js +22 -1
  50. package/dist/hooks/session-end.js +345 -187
  51. package/dist/hooks/session-start.js +304 -15
  52. package/dist/hooks/stop.js +22 -1
  53. package/dist/hooks/subagent-stop.js +22 -1
  54. package/dist/hooks/summary-worker.js +22 -1
  55. package/dist/index.js +22 -1
  56. package/dist/lib/cloud-sync.js +22 -1
  57. package/dist/lib/database.js +22 -1
  58. package/dist/lib/db.js +22 -1
  59. package/dist/lib/device-registry.js +22 -1
  60. package/dist/lib/exe-daemon.js +39 -1
  61. package/dist/lib/hybrid-search.js +22 -1
  62. package/dist/lib/schedules.js +22 -1
  63. package/dist/lib/store.js +22 -1
  64. package/dist/mcp/server.js +126 -1
  65. package/dist/runtime/index.js +22 -1
  66. package/dist/tui/App.js +22 -1
  67. package/package.json +1 -1
@@ -391,13 +391,13 @@ var init_employees = __esm({
391
391
  });
392
392
 
393
393
  // src/lib/session-registry.ts
394
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync4 } from "fs";
395
- import path4 from "path";
396
- import os3 from "os";
394
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync5 } from "fs";
395
+ import path5 from "path";
396
+ import os4 from "os";
397
397
  function registerSession(entry) {
398
- const dir = path4.dirname(REGISTRY_PATH);
399
- if (!existsSync4(dir)) {
400
- mkdirSync3(dir, { recursive: true });
398
+ const dir = path5.dirname(REGISTRY_PATH);
399
+ if (!existsSync5(dir)) {
400
+ mkdirSync4(dir, { recursive: true });
401
401
  }
402
402
  const sessions = listSessions();
403
403
  const idx = sessions.findIndex((s) => s.windowName === entry.windowName);
@@ -406,11 +406,11 @@ function registerSession(entry) {
406
406
  } else {
407
407
  sessions.push(entry);
408
408
  }
409
- writeFileSync3(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
409
+ writeFileSync4(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
410
410
  }
411
411
  function listSessions() {
412
412
  try {
413
- const raw = readFileSync4(REGISTRY_PATH, "utf8");
413
+ const raw = readFileSync5(REGISTRY_PATH, "utf8");
414
414
  return JSON.parse(raw);
415
415
  } catch {
416
416
  return [];
@@ -420,7 +420,7 @@ var REGISTRY_PATH;
420
420
  var init_session_registry = __esm({
421
421
  "src/lib/session-registry.ts"() {
422
422
  "use strict";
423
- REGISTRY_PATH = path4.join(os3.homedir(), ".exe-os", "session-registry.json");
423
+ REGISTRY_PATH = path5.join(os4.homedir(), ".exe-os", "session-registry.json");
424
424
  }
425
425
  });
426
426
 
@@ -632,12 +632,12 @@ var init_runtime_table = __esm({
632
632
  });
633
633
 
634
634
  // src/lib/agent-config.ts
635
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync5 } from "fs";
636
- import path5 from "path";
635
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync6 } from "fs";
636
+ import path6 from "path";
637
637
  function loadAgentConfig() {
638
- if (!existsSync5(AGENT_CONFIG_PATH)) return {};
638
+ if (!existsSync6(AGENT_CONFIG_PATH)) return {};
639
639
  try {
640
- return JSON.parse(readFileSync5(AGENT_CONFIG_PATH, "utf-8"));
640
+ return JSON.parse(readFileSync6(AGENT_CONFIG_PATH, "utf-8"));
641
641
  } catch {
642
642
  return {};
643
643
  }
@@ -657,7 +657,7 @@ var init_agent_config = __esm({
657
657
  init_config();
658
658
  init_runtime_table();
659
659
  init_secure_files();
660
- AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
660
+ AGENT_CONFIG_PATH = path6.join(EXE_AI_DIR, "agent-config.json");
661
661
  DEFAULT_MODELS = {
662
662
  claude: "claude-opus-4",
663
663
  codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
@@ -675,17 +675,17 @@ __export(intercom_queue_exports, {
675
675
  queueIntercom: () => queueIntercom,
676
676
  readQueue: () => readQueue
677
677
  });
678
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, renameSync as renameSync3, existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
679
- import path6 from "path";
680
- import os4 from "os";
678
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, renameSync as renameSync3, existsSync as existsSync7, mkdirSync as mkdirSync5 } from "fs";
679
+ import path7 from "path";
680
+ import os5 from "os";
681
681
  function ensureDir() {
682
- const dir = path6.dirname(QUEUE_PATH);
683
- if (!existsSync6(dir)) mkdirSync4(dir, { recursive: true });
682
+ const dir = path7.dirname(QUEUE_PATH);
683
+ if (!existsSync7(dir)) mkdirSync5(dir, { recursive: true });
684
684
  }
685
685
  function readQueue() {
686
686
  try {
687
- if (!existsSync6(QUEUE_PATH)) return [];
688
- return JSON.parse(readFileSync6(QUEUE_PATH, "utf8"));
687
+ if (!existsSync7(QUEUE_PATH)) return [];
688
+ return JSON.parse(readFileSync7(QUEUE_PATH, "utf8"));
689
689
  } catch {
690
690
  return [];
691
691
  }
@@ -693,7 +693,7 @@ function readQueue() {
693
693
  function writeQueue(queue) {
694
694
  ensureDir();
695
695
  const tmp = `${QUEUE_PATH}.tmp`;
696
- writeFileSync5(tmp, JSON.stringify(queue, null, 2));
696
+ writeFileSync6(tmp, JSON.stringify(queue, null, 2));
697
697
  renameSync3(tmp, QUEUE_PATH);
698
698
  }
699
699
  function queueIntercom(targetSession, reason) {
@@ -785,10 +785,10 @@ var QUEUE_PATH, MAX_RETRIES, TTL_MS, INTERCOM_LOG;
785
785
  var init_intercom_queue = __esm({
786
786
  "src/lib/intercom-queue.ts"() {
787
787
  "use strict";
788
- QUEUE_PATH = path6.join(os4.homedir(), ".exe-os", "intercom-queue.json");
788
+ QUEUE_PATH = path7.join(os5.homedir(), ".exe-os", "intercom-queue.json");
789
789
  MAX_RETRIES = 5;
790
790
  TTL_MS = 60 * 60 * 1e3;
791
- INTERCOM_LOG = path6.join(os4.homedir(), ".exe-os", "intercom.log");
791
+ INTERCOM_LOG = path7.join(os5.homedir(), ".exe-os", "intercom.log");
792
792
  }
793
793
  });
794
794
 
@@ -848,8 +848,8 @@ var init_db_retry = __esm({
848
848
  });
849
849
 
850
850
  // src/lib/database-adapter.ts
851
- import os5 from "os";
852
- import path7 from "path";
851
+ import os6 from "os";
852
+ import path8 from "path";
853
853
  import { createRequire } from "module";
854
854
  import { pathToFileURL } from "url";
855
855
  function quotedIdentifier(identifier) {
@@ -1160,8 +1160,8 @@ async function loadPrismaClient() {
1160
1160
  }
1161
1161
  return new PrismaClient2();
1162
1162
  }
1163
- const exeDbRoot = process.env.EXE_DB_ROOT ?? path7.join(os5.homedir(), "exe-db");
1164
- const requireFromExeDb = createRequire(path7.join(exeDbRoot, "package.json"));
1163
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path8.join(os6.homedir(), "exe-db");
1164
+ const requireFromExeDb = createRequire(path8.join(exeDbRoot, "package.json"));
1165
1165
  const prismaEntry = requireFromExeDb.resolve("@prisma/client");
1166
1166
  const module = await import(pathToFileURL(prismaEntry).href);
1167
1167
  const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
@@ -1433,8 +1433,8 @@ var init_database_adapter = __esm({
1433
1433
 
1434
1434
  // src/lib/daemon-auth.ts
1435
1435
  import crypto from "crypto";
1436
- import path8 from "path";
1437
- import { existsSync as existsSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
1436
+ import path9 from "path";
1437
+ import { existsSync as existsSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "fs";
1438
1438
  function normalizeToken(token) {
1439
1439
  if (!token) return null;
1440
1440
  const trimmed = token.trim();
@@ -1442,8 +1442,8 @@ function normalizeToken(token) {
1442
1442
  }
1443
1443
  function readDaemonToken() {
1444
1444
  try {
1445
- if (!existsSync7(DAEMON_TOKEN_PATH)) return null;
1446
- return normalizeToken(readFileSync7(DAEMON_TOKEN_PATH, "utf8"));
1445
+ if (!existsSync8(DAEMON_TOKEN_PATH)) return null;
1446
+ return normalizeToken(readFileSync8(DAEMON_TOKEN_PATH, "utf8"));
1447
1447
  } catch {
1448
1448
  return null;
1449
1449
  }
@@ -1453,7 +1453,7 @@ function ensureDaemonToken(seed) {
1453
1453
  if (existing) return existing;
1454
1454
  const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
1455
1455
  ensurePrivateDirSync(EXE_AI_DIR);
1456
- writeFileSync6(DAEMON_TOKEN_PATH, `${token}
1456
+ writeFileSync7(DAEMON_TOKEN_PATH, `${token}
1457
1457
  `, "utf8");
1458
1458
  enforcePrivateFileSync(DAEMON_TOKEN_PATH);
1459
1459
  return token;
@@ -1464,17 +1464,17 @@ var init_daemon_auth = __esm({
1464
1464
  "use strict";
1465
1465
  init_config();
1466
1466
  init_secure_files();
1467
- DAEMON_TOKEN_PATH = path8.join(EXE_AI_DIR, "exed.token");
1467
+ DAEMON_TOKEN_PATH = path9.join(EXE_AI_DIR, "exed.token");
1468
1468
  }
1469
1469
  });
1470
1470
 
1471
1471
  // src/lib/exe-daemon-client.ts
1472
1472
  import net from "net";
1473
- import os6 from "os";
1473
+ import os7 from "os";
1474
1474
  import { spawn } from "child_process";
1475
1475
  import { randomUUID } from "crypto";
1476
- import { existsSync as existsSync8, unlinkSync as unlinkSync3, readFileSync as readFileSync8, openSync, closeSync, statSync } from "fs";
1477
- import path9 from "path";
1476
+ import { existsSync as existsSync9, unlinkSync as unlinkSync4, readFileSync as readFileSync9, openSync, closeSync, statSync } from "fs";
1477
+ import path10 from "path";
1478
1478
  import { fileURLToPath } from "url";
1479
1479
  function handleData(chunk) {
1480
1480
  _buffer += chunk.toString();
@@ -1502,9 +1502,9 @@ function handleData(chunk) {
1502
1502
  }
1503
1503
  }
1504
1504
  function cleanupStaleFiles() {
1505
- if (existsSync8(PID_PATH)) {
1505
+ if (existsSync9(PID_PATH)) {
1506
1506
  try {
1507
- const pid = parseInt(readFileSync8(PID_PATH, "utf8").trim(), 10);
1507
+ const pid = parseInt(readFileSync9(PID_PATH, "utf8").trim(), 10);
1508
1508
  if (pid > 0) {
1509
1509
  try {
1510
1510
  process.kill(pid, 0);
@@ -1515,27 +1515,27 @@ function cleanupStaleFiles() {
1515
1515
  } catch {
1516
1516
  }
1517
1517
  try {
1518
- unlinkSync3(PID_PATH);
1518
+ unlinkSync4(PID_PATH);
1519
1519
  } catch {
1520
1520
  }
1521
1521
  try {
1522
- unlinkSync3(SOCKET_PATH);
1522
+ unlinkSync4(SOCKET_PATH);
1523
1523
  } catch {
1524
1524
  }
1525
1525
  }
1526
1526
  }
1527
1527
  function findPackageRoot() {
1528
- let dir = path9.dirname(fileURLToPath(import.meta.url));
1529
- const { root } = path9.parse(dir);
1528
+ let dir = path10.dirname(fileURLToPath(import.meta.url));
1529
+ const { root } = path10.parse(dir);
1530
1530
  while (dir !== root) {
1531
- if (existsSync8(path9.join(dir, "package.json"))) return dir;
1532
- dir = path9.dirname(dir);
1531
+ if (existsSync9(path10.join(dir, "package.json"))) return dir;
1532
+ dir = path10.dirname(dir);
1533
1533
  }
1534
1534
  return null;
1535
1535
  }
1536
1536
  function spawnDaemon() {
1537
- const freeGB = os6.freemem() / (1024 * 1024 * 1024);
1538
- const totalGB = os6.totalmem() / (1024 * 1024 * 1024);
1537
+ const freeGB = os7.freemem() / (1024 * 1024 * 1024);
1538
+ const totalGB = os7.totalmem() / (1024 * 1024 * 1024);
1539
1539
  if (totalGB <= 8) {
1540
1540
  process.stderr.write(
1541
1541
  `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
@@ -1555,8 +1555,8 @@ function spawnDaemon() {
1555
1555
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
1556
1556
  return;
1557
1557
  }
1558
- const daemonPath = path9.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1559
- if (!existsSync8(daemonPath)) {
1558
+ const daemonPath = path10.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1559
+ if (!existsSync9(daemonPath)) {
1560
1560
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
1561
1561
  `);
1562
1562
  return;
@@ -1565,7 +1565,7 @@ function spawnDaemon() {
1565
1565
  const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
1566
1566
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
1567
1567
  `);
1568
- const logPath = path9.join(path9.dirname(SOCKET_PATH), "exed.log");
1568
+ const logPath = path10.join(path10.dirname(SOCKET_PATH), "exed.log");
1569
1569
  let stderrFd = "ignore";
1570
1570
  try {
1571
1571
  stderrFd = openSync(logPath, "a");
@@ -1605,7 +1605,7 @@ function acquireSpawnLock() {
1605
1605
  const stat = statSync(SPAWN_LOCK_PATH);
1606
1606
  if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
1607
1607
  try {
1608
- unlinkSync3(SPAWN_LOCK_PATH);
1608
+ unlinkSync4(SPAWN_LOCK_PATH);
1609
1609
  } catch {
1610
1610
  }
1611
1611
  try {
@@ -1622,7 +1622,7 @@ function acquireSpawnLock() {
1622
1622
  }
1623
1623
  function releaseSpawnLock() {
1624
1624
  try {
1625
- unlinkSync3(SPAWN_LOCK_PATH);
1625
+ unlinkSync4(SPAWN_LOCK_PATH);
1626
1626
  } catch {
1627
1627
  }
1628
1628
  }
@@ -1715,9 +1715,9 @@ var init_exe_daemon_client = __esm({
1715
1715
  "use strict";
1716
1716
  init_config();
1717
1717
  init_daemon_auth();
1718
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path9.join(EXE_AI_DIR, "exed.sock");
1719
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path9.join(EXE_AI_DIR, "exed.pid");
1720
- SPAWN_LOCK_PATH = path9.join(EXE_AI_DIR, "exed-spawn.lock");
1718
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path10.join(EXE_AI_DIR, "exed.sock");
1719
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path10.join(EXE_AI_DIR, "exed.pid");
1720
+ SPAWN_LOCK_PATH = path10.join(EXE_AI_DIR, "exed-spawn.lock");
1721
1721
  SPAWN_LOCK_STALE_MS = 3e4;
1722
1722
  CONNECT_TIMEOUT_MS = 15e3;
1723
1723
  REQUEST_TIMEOUT_MS = 3e4;
@@ -2794,12 +2794,26 @@ async function ensureSchema() {
2794
2794
  session_name TEXT,
2795
2795
  task_id TEXT,
2796
2796
  project_name TEXT,
2797
- started_at TEXT NOT NULL
2797
+ started_at TEXT NOT NULL,
2798
+ cache_cold_count INTEGER NOT NULL DEFAULT 0
2798
2799
  );
2799
2800
 
2800
2801
  CREATE INDEX IF NOT EXISTS idx_session_agent_map_agent
2801
2802
  ON session_agent_map(agent_id);
2802
2803
  `);
2804
+ await client.executeMultiple(`
2805
+ CREATE TABLE IF NOT EXISTS agent_file_reads (
2806
+ session_uuid TEXT NOT NULL,
2807
+ agent_id TEXT NOT NULL,
2808
+ file_path TEXT NOT NULL,
2809
+ read_at TEXT NOT NULL,
2810
+ commit_hash TEXT,
2811
+ PRIMARY KEY (session_uuid, file_path)
2812
+ );
2813
+
2814
+ CREATE INDEX IF NOT EXISTS idx_agent_file_reads_agent_read_at
2815
+ ON agent_file_reads(agent_id, read_at);
2816
+ `);
2803
2817
  try {
2804
2818
  const mapCount = await client.execute({ sql: `SELECT COUNT(*) as cnt FROM session_agent_map`, args: [] });
2805
2819
  if (Number(mapCount.rows[0]?.cnt ?? 0) === 0) {
@@ -2814,6 +2828,13 @@ async function ensureSchema() {
2814
2828
  }
2815
2829
  } catch {
2816
2830
  }
2831
+ try {
2832
+ await client.execute({
2833
+ sql: `ALTER TABLE session_agent_map ADD COLUMN cache_cold_count INTEGER NOT NULL DEFAULT 0`,
2834
+ args: []
2835
+ });
2836
+ } catch {
2837
+ }
2817
2838
  try {
2818
2839
  await client.execute({
2819
2840
  sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
@@ -3014,21 +3035,21 @@ var init_database = __esm({
3014
3035
  });
3015
3036
 
3016
3037
  // src/lib/license.ts
3017
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync7, existsSync as existsSync9, mkdirSync as mkdirSync5 } from "fs";
3038
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync8, existsSync as existsSync10, mkdirSync as mkdirSync6 } from "fs";
3018
3039
  import { randomUUID as randomUUID2 } from "crypto";
3019
3040
  import { createRequire as createRequire2 } from "module";
3020
3041
  import { pathToFileURL as pathToFileURL2 } from "url";
3021
- import os7 from "os";
3022
- import path10 from "path";
3042
+ import os8 from "os";
3043
+ import path11 from "path";
3023
3044
  import { jwtVerify, importSPKI } from "jose";
3024
3045
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
3025
3046
  var init_license = __esm({
3026
3047
  "src/lib/license.ts"() {
3027
3048
  "use strict";
3028
3049
  init_config();
3029
- LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
3030
- CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
3031
- DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
3050
+ LICENSE_PATH = path11.join(EXE_AI_DIR, "license.key");
3051
+ CACHE_PATH = path11.join(EXE_AI_DIR, "license-cache.json");
3052
+ DEVICE_ID_PATH = path11.join(EXE_AI_DIR, "device-id");
3032
3053
  PLAN_LIMITS = {
3033
3054
  free: { devices: 1, employees: 1, memories: 5e3 },
3034
3055
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -3040,12 +3061,12 @@ var init_license = __esm({
3040
3061
  });
3041
3062
 
3042
3063
  // src/lib/plan-limits.ts
3043
- import { readFileSync as readFileSync10, existsSync as existsSync10 } from "fs";
3044
- import path11 from "path";
3064
+ import { readFileSync as readFileSync11, existsSync as existsSync11 } from "fs";
3065
+ import path12 from "path";
3045
3066
  function getLicenseSync() {
3046
3067
  try {
3047
- if (!existsSync10(CACHE_PATH2)) return freeLicense();
3048
- const raw = JSON.parse(readFileSync10(CACHE_PATH2, "utf8"));
3068
+ if (!existsSync11(CACHE_PATH2)) return freeLicense();
3069
+ const raw = JSON.parse(readFileSync11(CACHE_PATH2, "utf8"));
3049
3070
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
3050
3071
  const parts = raw.token.split(".");
3051
3072
  if (parts.length !== 3) return freeLicense();
@@ -3083,8 +3104,8 @@ function assertEmployeeLimitSync(rosterPath) {
3083
3104
  const filePath = rosterPath ?? EMPLOYEES_PATH;
3084
3105
  let count = 0;
3085
3106
  try {
3086
- if (existsSync10(filePath)) {
3087
- const raw = readFileSync10(filePath, "utf8");
3107
+ if (existsSync11(filePath)) {
3108
+ const raw = readFileSync11(filePath, "utf8");
3088
3109
  const employees = JSON.parse(raw);
3089
3110
  count = Array.isArray(employees) ? employees.length : 0;
3090
3111
  }
@@ -3113,7 +3134,7 @@ var init_plan_limits = __esm({
3113
3134
  this.name = "PlanLimitError";
3114
3135
  }
3115
3136
  };
3116
- CACHE_PATH2 = path11.join(EXE_AI_DIR, "license-cache.json");
3137
+ CACHE_PATH2 = path12.join(EXE_AI_DIR, "license-cache.json");
3117
3138
  }
3118
3139
  });
3119
3140
 
@@ -3130,13 +3151,13 @@ __export(notifications_exports, {
3130
3151
  writeNotification: () => writeNotification
3131
3152
  });
3132
3153
  import crypto2 from "crypto";
3133
- import path12 from "path";
3134
- import os8 from "os";
3154
+ import path13 from "path";
3155
+ import os9 from "os";
3135
3156
  import {
3136
- readFileSync as readFileSync11,
3157
+ readFileSync as readFileSync12,
3137
3158
  readdirSync as readdirSync2,
3138
- unlinkSync as unlinkSync4,
3139
- existsSync as existsSync11,
3159
+ unlinkSync as unlinkSync5,
3160
+ existsSync as existsSync12,
3140
3161
  rmdirSync
3141
3162
  } from "fs";
3142
3163
  async function writeNotification(notification) {
@@ -3282,9 +3303,9 @@ function formatNotifications(notifications) {
3282
3303
  return lines.join("\n");
3283
3304
  }
3284
3305
  async function migrateJsonNotifications() {
3285
- const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR || path12.join(os8.homedir(), ".exe-os");
3286
- const notifDir = path12.join(base, "notifications");
3287
- if (!existsSync11(notifDir)) return 0;
3306
+ const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR || path13.join(os9.homedir(), ".exe-os");
3307
+ const notifDir = path13.join(base, "notifications");
3308
+ if (!existsSync12(notifDir)) return 0;
3288
3309
  let migrated = 0;
3289
3310
  try {
3290
3311
  const files = readdirSync2(notifDir).filter((f) => f.endsWith(".json"));
@@ -3292,8 +3313,8 @@ async function migrateJsonNotifications() {
3292
3313
  const client = getClient();
3293
3314
  for (const file of files) {
3294
3315
  try {
3295
- const filePath = path12.join(notifDir, file);
3296
- const data = JSON.parse(readFileSync11(filePath, "utf8"));
3316
+ const filePath = path13.join(notifDir, file);
3317
+ const data = JSON.parse(readFileSync12(filePath, "utf8"));
3297
3318
  await client.execute({
3298
3319
  sql: `INSERT OR IGNORE INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
3299
3320
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
@@ -3310,7 +3331,7 @@ async function migrateJsonNotifications() {
3310
3331
  data.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
3311
3332
  ]
3312
3333
  });
3313
- unlinkSync4(filePath);
3334
+ unlinkSync5(filePath);
3314
3335
  migrated++;
3315
3336
  } catch {
3316
3337
  }
@@ -3455,7 +3476,7 @@ var init_state_bus = __esm({
3455
3476
 
3456
3477
  // src/lib/project-name.ts
3457
3478
  import { execSync as execSync5 } from "child_process";
3458
- import path13 from "path";
3479
+ import path14 from "path";
3459
3480
  function getProjectName(cwd) {
3460
3481
  const dir = cwd ?? process.cwd();
3461
3482
  if (_cached2 && _cachedCwd === dir) return _cached2;
@@ -3468,7 +3489,7 @@ function getProjectName(cwd) {
3468
3489
  timeout: 2e3,
3469
3490
  stdio: ["pipe", "pipe", "pipe"]
3470
3491
  }).trim();
3471
- repoRoot = path13.dirname(gitCommonDir);
3492
+ repoRoot = path14.dirname(gitCommonDir);
3472
3493
  } catch {
3473
3494
  repoRoot = execSync5("git rev-parse --show-toplevel", {
3474
3495
  cwd: dir,
@@ -3477,11 +3498,11 @@ function getProjectName(cwd) {
3477
3498
  stdio: ["pipe", "pipe", "pipe"]
3478
3499
  }).trim();
3479
3500
  }
3480
- _cached2 = path13.basename(repoRoot);
3501
+ _cached2 = path14.basename(repoRoot);
3481
3502
  _cachedCwd = dir;
3482
3503
  return _cached2;
3483
3504
  } catch {
3484
- _cached2 = path13.basename(dir);
3505
+ _cached2 = path14.basename(dir);
3485
3506
  _cachedCwd = dir;
3486
3507
  return _cached2;
3487
3508
  }
@@ -3575,11 +3596,11 @@ __export(tasks_crud_exports, {
3575
3596
  writeCheckpoint: () => writeCheckpoint
3576
3597
  });
3577
3598
  import crypto4 from "crypto";
3578
- import path14 from "path";
3579
- import os9 from "os";
3599
+ import path15 from "path";
3600
+ import os10 from "os";
3580
3601
  import { execSync as execSync6 } from "child_process";
3581
3602
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
3582
- import { existsSync as existsSync12, readFileSync as readFileSync12 } from "fs";
3603
+ import { existsSync as existsSync13, readFileSync as readFileSync13 } from "fs";
3583
3604
  async function writeCheckpoint(input2) {
3584
3605
  const client = getClient();
3585
3606
  const row = await resolveTask(client, input2.taskId);
@@ -3773,8 +3794,8 @@ ${scopeMismatchWarning}` : scopeMismatchWarning;
3773
3794
  }
3774
3795
  if (input2.baseDir) {
3775
3796
  try {
3776
- await mkdir3(path14.join(input2.baseDir, "exe", "output"), { recursive: true });
3777
- await mkdir3(path14.join(input2.baseDir, "exe", "research"), { recursive: true });
3797
+ await mkdir3(path15.join(input2.baseDir, "exe", "output"), { recursive: true });
3798
+ await mkdir3(path15.join(input2.baseDir, "exe", "research"), { recursive: true });
3778
3799
  await ensureArchitectureDoc(input2.baseDir, input2.projectName);
3779
3800
  await ensureGitignoreExe(input2.baseDir);
3780
3801
  } catch {
@@ -3810,10 +3831,10 @@ ${scopeMismatchWarning}` : scopeMismatchWarning;
3810
3831
  });
3811
3832
  if (input2.baseDir) {
3812
3833
  try {
3813
- const EXE_OS_DIR = path14.join(os9.homedir(), ".exe-os");
3814
- const mdPath = path14.join(EXE_OS_DIR, taskFile);
3815
- const mdDir = path14.dirname(mdPath);
3816
- if (!existsSync12(mdDir)) await mkdir3(mdDir, { recursive: true });
3834
+ const EXE_OS_DIR = path15.join(os10.homedir(), ".exe-os");
3835
+ const mdPath = path15.join(EXE_OS_DIR, taskFile);
3836
+ const mdDir = path15.dirname(mdPath);
3837
+ if (!existsSync13(mdDir)) await mkdir3(mdDir, { recursive: true });
3817
3838
  const reviewer = input2.reviewer ?? input2.assignedBy;
3818
3839
  const mdContent = `# ${input2.title}
3819
3840
 
@@ -4113,9 +4134,9 @@ async function deleteTaskCore(taskId, _baseDir) {
4113
4134
  return { taskFile, assignedTo, assignedBy, taskSlug };
4114
4135
  }
4115
4136
  async function ensureArchitectureDoc(baseDir, projectName) {
4116
- const archPath = path14.join(baseDir, "exe", "ARCHITECTURE.md");
4137
+ const archPath = path15.join(baseDir, "exe", "ARCHITECTURE.md");
4117
4138
  try {
4118
- if (existsSync12(archPath)) return;
4139
+ if (existsSync13(archPath)) return;
4119
4140
  const template = [
4120
4141
  `# ${projectName} \u2014 System Architecture`,
4121
4142
  "",
@@ -4148,10 +4169,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
4148
4169
  }
4149
4170
  }
4150
4171
  async function ensureGitignoreExe(baseDir) {
4151
- const gitignorePath = path14.join(baseDir, ".gitignore");
4172
+ const gitignorePath = path15.join(baseDir, ".gitignore");
4152
4173
  try {
4153
- if (existsSync12(gitignorePath)) {
4154
- const content = readFileSync12(gitignorePath, "utf-8");
4174
+ if (existsSync13(gitignorePath)) {
4175
+ const content = readFileSync13(gitignorePath, "utf-8");
4155
4176
  if (/^\/?exe\/?$/m.test(content)) return;
4156
4177
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
4157
4178
  } else {
@@ -4194,8 +4215,8 @@ __export(tasks_review_exports, {
4194
4215
  isStale: () => isStale,
4195
4216
  listPendingReviews: () => listPendingReviews
4196
4217
  });
4197
- import path15 from "path";
4198
- import { existsSync as existsSync13, readdirSync as readdirSync3, unlinkSync as unlinkSync5 } from "fs";
4218
+ import path16 from "path";
4219
+ import { existsSync as existsSync14, readdirSync as readdirSync3, unlinkSync as unlinkSync6 } from "fs";
4199
4220
  function formatAge(isoTimestamp) {
4200
4221
  if (!isoTimestamp) return "";
4201
4222
  const ms = Date.now() - new Date(isoTimestamp).getTime();
@@ -4464,11 +4485,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
4464
4485
  );
4465
4486
  }
4466
4487
  try {
4467
- const cacheDir = path15.join(EXE_AI_DIR, "session-cache");
4468
- if (existsSync13(cacheDir)) {
4488
+ const cacheDir = path16.join(EXE_AI_DIR, "session-cache");
4489
+ if (existsSync14(cacheDir)) {
4469
4490
  for (const f of readdirSync3(cacheDir)) {
4470
4491
  if (f.startsWith("review-notified-")) {
4471
- unlinkSync5(path15.join(cacheDir, f));
4492
+ unlinkSync6(path16.join(cacheDir, f));
4472
4493
  }
4473
4494
  }
4474
4495
  }
@@ -4490,7 +4511,7 @@ var init_tasks_review = __esm({
4490
4511
  });
4491
4512
 
4492
4513
  // src/lib/tasks-chain.ts
4493
- import path16 from "path";
4514
+ import path17 from "path";
4494
4515
  import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
4495
4516
  async function cascadeUnblock(taskId, baseDir, now) {
4496
4517
  const client = getClient();
@@ -4507,7 +4528,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
4507
4528
  });
4508
4529
  for (const ur of unblockedRows.rows) {
4509
4530
  try {
4510
- const ubFile = path16.join(baseDir, String(ur.task_file));
4531
+ const ubFile = path17.join(baseDir, String(ur.task_file));
4511
4532
  let ubContent = await readFile3(ubFile, "utf-8");
4512
4533
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
4513
4534
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -4971,8 +4992,8 @@ __export(tasks_exports, {
4971
4992
  updateTaskStatus: () => updateTaskStatus,
4972
4993
  writeCheckpoint: () => writeCheckpoint
4973
4994
  });
4974
- import path17 from "path";
4975
- import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, unlinkSync as unlinkSync6 } from "fs";
4995
+ import path18 from "path";
4996
+ import { writeFileSync as writeFileSync9, mkdirSync as mkdirSync7, unlinkSync as unlinkSync7 } from "fs";
4976
4997
  async function createTask(input2) {
4977
4998
  const result = await createTaskCore(input2);
4978
4999
  if (!input2.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -4991,14 +5012,14 @@ async function updateTask(input2) {
4991
5012
  const { row, taskFile, now, taskId } = await updateTaskStatus(input2);
4992
5013
  try {
4993
5014
  const agent = String(row.assigned_to);
4994
- const cacheDir = path17.join(EXE_AI_DIR, "session-cache");
4995
- const cachePath = path17.join(cacheDir, `current-task-${agent}.json`);
5015
+ const cacheDir = path18.join(EXE_AI_DIR, "session-cache");
5016
+ const cachePath = path18.join(cacheDir, `current-task-${agent}.json`);
4996
5017
  if (input2.status === "in_progress") {
4997
- mkdirSync6(cacheDir, { recursive: true });
4998
- writeFileSync8(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
5018
+ mkdirSync7(cacheDir, { recursive: true });
5019
+ writeFileSync9(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
4999
5020
  } else if (input2.status === "done" || input2.status === "blocked" || input2.status === "cancelled" || input2.status === "closed") {
5000
5021
  try {
5001
- unlinkSync6(cachePath);
5022
+ unlinkSync7(cachePath);
5002
5023
  } catch {
5003
5024
  }
5004
5025
  }
@@ -5463,13 +5484,13 @@ __export(tmux_routing_exports, {
5463
5484
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
5464
5485
  });
5465
5486
  import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
5466
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync9, mkdirSync as mkdirSync7, existsSync as existsSync14, appendFileSync, readdirSync as readdirSync4 } from "fs";
5467
- import path18 from "path";
5468
- import os10 from "os";
5487
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync10, mkdirSync as mkdirSync8, existsSync as existsSync15, appendFileSync, readdirSync as readdirSync4 } from "fs";
5488
+ import path19 from "path";
5489
+ import os11 from "os";
5469
5490
  import { fileURLToPath as fileURLToPath2 } from "url";
5470
- import { unlinkSync as unlinkSync7 } from "fs";
5491
+ import { unlinkSync as unlinkSync8 } from "fs";
5471
5492
  function spawnLockPath(sessionName) {
5472
- return path18.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
5493
+ return path19.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
5473
5494
  }
5474
5495
  function isProcessAlive(pid) {
5475
5496
  try {
@@ -5480,13 +5501,13 @@ function isProcessAlive(pid) {
5480
5501
  }
5481
5502
  }
5482
5503
  function acquireSpawnLock2(sessionName) {
5483
- if (!existsSync14(SPAWN_LOCK_DIR)) {
5484
- mkdirSync7(SPAWN_LOCK_DIR, { recursive: true });
5504
+ if (!existsSync15(SPAWN_LOCK_DIR)) {
5505
+ mkdirSync8(SPAWN_LOCK_DIR, { recursive: true });
5485
5506
  }
5486
5507
  const lockFile = spawnLockPath(sessionName);
5487
- if (existsSync14(lockFile)) {
5508
+ if (existsSync15(lockFile)) {
5488
5509
  try {
5489
- const lock = JSON.parse(readFileSync13(lockFile, "utf8"));
5510
+ const lock = JSON.parse(readFileSync14(lockFile, "utf8"));
5490
5511
  const age = Date.now() - lock.timestamp;
5491
5512
  if (isProcessAlive(lock.pid) && age < 6e4) {
5492
5513
  return false;
@@ -5494,25 +5515,25 @@ function acquireSpawnLock2(sessionName) {
5494
5515
  } catch {
5495
5516
  }
5496
5517
  }
5497
- writeFileSync9(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
5518
+ writeFileSync10(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
5498
5519
  return true;
5499
5520
  }
5500
5521
  function releaseSpawnLock2(sessionName) {
5501
5522
  try {
5502
- unlinkSync7(spawnLockPath(sessionName));
5523
+ unlinkSync8(spawnLockPath(sessionName));
5503
5524
  } catch {
5504
5525
  }
5505
5526
  }
5506
5527
  function resolveBehaviorsExporterScript() {
5507
5528
  try {
5508
5529
  const thisFile = fileURLToPath2(import.meta.url);
5509
- const scriptPath = path18.join(
5510
- path18.dirname(thisFile),
5530
+ const scriptPath = path19.join(
5531
+ path19.dirname(thisFile),
5511
5532
  "..",
5512
5533
  "bin",
5513
5534
  "exe-export-behaviors.js"
5514
5535
  );
5515
- return existsSync14(scriptPath) ? scriptPath : null;
5536
+ return existsSync15(scriptPath) ? scriptPath : null;
5516
5537
  } catch {
5517
5538
  return null;
5518
5539
  }
@@ -5578,12 +5599,12 @@ function extractRootExe(name) {
5578
5599
  return parts.length > 0 ? parts[parts.length - 1] : null;
5579
5600
  }
5580
5601
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
5581
- if (!existsSync14(SESSION_CACHE)) {
5582
- mkdirSync7(SESSION_CACHE, { recursive: true });
5602
+ if (!existsSync15(SESSION_CACHE)) {
5603
+ mkdirSync8(SESSION_CACHE, { recursive: true });
5583
5604
  }
5584
5605
  const rootExe = extractRootExe(parentExe) ?? parentExe;
5585
- const filePath = path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
5586
- writeFileSync9(filePath, JSON.stringify({
5606
+ const filePath = path19.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
5607
+ writeFileSync10(filePath, JSON.stringify({
5587
5608
  parentExe: rootExe,
5588
5609
  dispatchedBy: dispatchedBy || rootExe,
5589
5610
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -5591,7 +5612,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
5591
5612
  }
5592
5613
  function getParentExe(sessionKey) {
5593
5614
  try {
5594
- const data = JSON.parse(readFileSync13(path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
5615
+ const data = JSON.parse(readFileSync14(path19.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
5595
5616
  return data.parentExe || null;
5596
5617
  } catch {
5597
5618
  return null;
@@ -5599,8 +5620,8 @@ function getParentExe(sessionKey) {
5599
5620
  }
5600
5621
  function getDispatchedBy(sessionKey) {
5601
5622
  try {
5602
- const data = JSON.parse(readFileSync13(
5603
- path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
5623
+ const data = JSON.parse(readFileSync14(
5624
+ path19.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
5604
5625
  "utf8"
5605
5626
  ));
5606
5627
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -5670,8 +5691,8 @@ async function verifyPaneAtCapacity(sessionName) {
5670
5691
  }
5671
5692
  function readDebounceState() {
5672
5693
  try {
5673
- if (!existsSync14(DEBOUNCE_FILE)) return {};
5674
- const raw = JSON.parse(readFileSync13(DEBOUNCE_FILE, "utf8"));
5694
+ if (!existsSync15(DEBOUNCE_FILE)) return {};
5695
+ const raw = JSON.parse(readFileSync14(DEBOUNCE_FILE, "utf8"));
5675
5696
  const state = {};
5676
5697
  for (const [key, val] of Object.entries(raw)) {
5677
5698
  if (typeof val === "number") {
@@ -5687,8 +5708,8 @@ function readDebounceState() {
5687
5708
  }
5688
5709
  function writeDebounceState(state) {
5689
5710
  try {
5690
- if (!existsSync14(SESSION_CACHE)) mkdirSync7(SESSION_CACHE, { recursive: true });
5691
- writeFileSync9(DEBOUNCE_FILE, JSON.stringify(state));
5711
+ if (!existsSync15(SESSION_CACHE)) mkdirSync8(SESSION_CACHE, { recursive: true });
5712
+ writeFileSync10(DEBOUNCE_FILE, JSON.stringify(state));
5692
5713
  } catch {
5693
5714
  }
5694
5715
  }
@@ -5787,8 +5808,8 @@ function sendIntercom(targetSession) {
5787
5808
  try {
5788
5809
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
5789
5810
  const agent = baseAgentName(rawAgent);
5790
- const markerPath = path18.join(SESSION_CACHE, `current-task-${agent}.json`);
5791
- if (existsSync14(markerPath)) {
5811
+ const markerPath = path19.join(SESSION_CACHE, `current-task-${agent}.json`);
5812
+ if (existsSync15(markerPath)) {
5792
5813
  logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker + not idle \u2014 will auto-chain)`);
5793
5814
  return "debounced";
5794
5815
  }
@@ -5798,8 +5819,8 @@ function sendIntercom(targetSession) {
5798
5819
  try {
5799
5820
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
5800
5821
  const agent = baseAgentName(rawAgent);
5801
- const taskDir = path18.join(process.cwd(), "exe", agent);
5802
- if (existsSync14(taskDir)) {
5822
+ const taskDir = path19.join(process.cwd(), "exe", agent);
5823
+ if (existsSync15(taskDir)) {
5803
5824
  const files = readdirSync4(taskDir).filter(
5804
5825
  (f) => f.endsWith(".md") && f !== "DONE.txt"
5805
5826
  );
@@ -5965,26 +5986,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5965
5986
  const transport = getTransport();
5966
5987
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
5967
5988
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
5968
- const logDir = path18.join(os10.homedir(), ".exe-os", "session-logs");
5969
- const logFile = path18.join(logDir, `${instanceLabel}-${Date.now()}.log`);
5970
- if (!existsSync14(logDir)) {
5971
- mkdirSync7(logDir, { recursive: true });
5989
+ const logDir = path19.join(os11.homedir(), ".exe-os", "session-logs");
5990
+ const logFile = path19.join(logDir, `${instanceLabel}-${Date.now()}.log`);
5991
+ if (!existsSync15(logDir)) {
5992
+ mkdirSync8(logDir, { recursive: true });
5972
5993
  }
5973
5994
  transport.kill(sessionName);
5974
5995
  let cleanupSuffix = "";
5975
5996
  try {
5976
5997
  const thisFile = fileURLToPath2(import.meta.url);
5977
- const cleanupScript = path18.join(path18.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
5978
- if (existsSync14(cleanupScript)) {
5998
+ const cleanupScript = path19.join(path19.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
5999
+ if (existsSync15(cleanupScript)) {
5979
6000
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
5980
6001
  }
5981
6002
  } catch {
5982
6003
  }
5983
6004
  try {
5984
- const claudeJsonPath = path18.join(os10.homedir(), ".claude.json");
6005
+ const claudeJsonPath = path19.join(os11.homedir(), ".claude.json");
5985
6006
  let claudeJson = {};
5986
6007
  try {
5987
- claudeJson = JSON.parse(readFileSync13(claudeJsonPath, "utf8"));
6008
+ claudeJson = JSON.parse(readFileSync14(claudeJsonPath, "utf8"));
5988
6009
  } catch {
5989
6010
  }
5990
6011
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -5992,17 +6013,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5992
6013
  const trustDir = opts?.cwd ?? projectDir;
5993
6014
  if (!projects[trustDir]) projects[trustDir] = {};
5994
6015
  projects[trustDir].hasTrustDialogAccepted = true;
5995
- writeFileSync9(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
6016
+ writeFileSync10(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
5996
6017
  } catch {
5997
6018
  }
5998
6019
  try {
5999
- const settingsDir = path18.join(os10.homedir(), ".claude", "projects");
6020
+ const settingsDir = path19.join(os11.homedir(), ".claude", "projects");
6000
6021
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
6001
- const projSettingsDir = path18.join(settingsDir, normalizedKey);
6002
- const settingsPath = path18.join(projSettingsDir, "settings.json");
6022
+ const projSettingsDir = path19.join(settingsDir, normalizedKey);
6023
+ const settingsPath = path19.join(projSettingsDir, "settings.json");
6003
6024
  let settings = {};
6004
6025
  try {
6005
- settings = JSON.parse(readFileSync13(settingsPath, "utf8"));
6026
+ settings = JSON.parse(readFileSync14(settingsPath, "utf8"));
6006
6027
  } catch {
6007
6028
  }
6008
6029
  const perms = settings.permissions ?? {};
@@ -6030,8 +6051,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6030
6051
  if (changed) {
6031
6052
  perms.allow = allow;
6032
6053
  settings.permissions = perms;
6033
- mkdirSync7(projSettingsDir, { recursive: true });
6034
- writeFileSync9(settingsPath, JSON.stringify(settings, null, 2) + "\n");
6054
+ mkdirSync8(projSettingsDir, { recursive: true });
6055
+ writeFileSync10(settingsPath, JSON.stringify(settings, null, 2) + "\n");
6035
6056
  }
6036
6057
  } catch {
6037
6058
  }
@@ -6046,8 +6067,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6046
6067
  let behaviorsFlag = "";
6047
6068
  let legacyFallbackWarned = false;
6048
6069
  if (!useExeAgent && !useBinSymlink) {
6049
- const identityPath = path18.join(
6050
- os10.homedir(),
6070
+ const identityPath = path19.join(
6071
+ os11.homedir(),
6051
6072
  ".exe-os",
6052
6073
  "identity",
6053
6074
  `${employeeName}.md`
@@ -6056,13 +6077,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6056
6077
  const hasAgentFlag = claudeSupportsAgentFlag();
6057
6078
  if (hasAgentFlag) {
6058
6079
  identityFlag = ` --agent ${employeeName}`;
6059
- } else if (existsSync14(identityPath)) {
6080
+ } else if (existsSync15(identityPath)) {
6060
6081
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
6061
6082
  legacyFallbackWarned = true;
6062
6083
  }
6063
6084
  const behaviorsFile = exportBehaviorsSync(
6064
6085
  employeeName,
6065
- path18.basename(spawnCwd),
6086
+ path19.basename(spawnCwd),
6066
6087
  sessionName
6067
6088
  );
6068
6089
  if (behaviorsFile) {
@@ -6077,16 +6098,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6077
6098
  }
6078
6099
  let sessionContextFlag = "";
6079
6100
  try {
6080
- const ctxDir = path18.join(os10.homedir(), ".exe-os", "session-cache");
6081
- mkdirSync7(ctxDir, { recursive: true });
6082
- const ctxFile = path18.join(ctxDir, `session-context-${sessionName}.md`);
6101
+ const ctxDir = path19.join(os11.homedir(), ".exe-os", "session-cache");
6102
+ mkdirSync8(ctxDir, { recursive: true });
6103
+ const ctxFile = path19.join(ctxDir, `session-context-${sessionName}.md`);
6083
6104
  const ctxContent = [
6084
6105
  `## Session Context`,
6085
6106
  `You are running in tmux session: ${sessionName}.`,
6086
6107
  `Your parent coordinator session is ${exeSession}.`,
6087
6108
  `Your employees (if any) use the -${exeSession} suffix.`
6088
6109
  ].join("\n");
6089
- writeFileSync9(ctxFile, ctxContent);
6110
+ writeFileSync10(ctxFile, ctxContent);
6090
6111
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
6091
6112
  } catch {
6092
6113
  }
@@ -6163,8 +6184,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6163
6184
  transport.pipeLog(sessionName, logFile);
6164
6185
  try {
6165
6186
  const mySession = getMySession();
6166
- const dispatchInfo = path18.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
6167
- writeFileSync9(dispatchInfo, JSON.stringify({
6187
+ const dispatchInfo = path19.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
6188
+ writeFileSync10(dispatchInfo, JSON.stringify({
6168
6189
  dispatchedBy: mySession,
6169
6190
  rootExe: exeSession,
6170
6191
  provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
@@ -6238,15 +6259,15 @@ var init_tmux_routing = __esm({
6238
6259
  init_intercom_queue();
6239
6260
  init_plan_limits();
6240
6261
  init_employees();
6241
- SPAWN_LOCK_DIR = path18.join(os10.homedir(), ".exe-os", "spawn-locks");
6242
- SESSION_CACHE = path18.join(os10.homedir(), ".exe-os", "session-cache");
6262
+ SPAWN_LOCK_DIR = path19.join(os11.homedir(), ".exe-os", "spawn-locks");
6263
+ SESSION_CACHE = path19.join(os11.homedir(), ".exe-os", "session-cache");
6243
6264
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
6244
6265
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
6245
6266
  VERIFY_PANE_LINES = 200;
6246
6267
  INTERCOM_DEBOUNCE_MS = 3e4;
6247
6268
  CODEX_DEBOUNCE_MS = 12e4;
6248
- INTERCOM_LOG2 = path18.join(os10.homedir(), ".exe-os", "intercom.log");
6249
- DEBOUNCE_FILE = path18.join(SESSION_CACHE, "intercom-debounce.json");
6269
+ INTERCOM_LOG2 = path19.join(os11.homedir(), ".exe-os", "intercom.log");
6270
+ DEBOUNCE_FILE = path19.join(SESSION_CACHE, "intercom-debounce.json");
6250
6271
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
6251
6272
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
6252
6273
  }
@@ -6296,14 +6317,14 @@ var init_memory = __esm({
6296
6317
 
6297
6318
  // src/lib/keychain.ts
6298
6319
  import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
6299
- import { existsSync as existsSync15 } from "fs";
6300
- import path19 from "path";
6301
- import os11 from "os";
6320
+ import { existsSync as existsSync16 } from "fs";
6321
+ import path20 from "path";
6322
+ import os12 from "os";
6302
6323
  function getKeyDir() {
6303
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path19.join(os11.homedir(), ".exe-os");
6324
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path20.join(os12.homedir(), ".exe-os");
6304
6325
  }
6305
6326
  function getKeyPath() {
6306
- return path19.join(getKeyDir(), "master.key");
6327
+ return path20.join(getKeyDir(), "master.key");
6307
6328
  }
6308
6329
  async function tryKeytar() {
6309
6330
  try {
@@ -6324,9 +6345,9 @@ async function getMasterKey() {
6324
6345
  }
6325
6346
  }
6326
6347
  const keyPath = getKeyPath();
6327
- if (!existsSync15(keyPath)) {
6348
+ if (!existsSync16(keyPath)) {
6328
6349
  process.stderr.write(
6329
- `[keychain] Key not found at ${keyPath} (HOME=${os11.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
6350
+ `[keychain] Key not found at ${keyPath} (HOME=${os12.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
6330
6351
  `
6331
6352
  );
6332
6353
  return null;
@@ -6365,13 +6386,13 @@ __export(shard_manager_exports, {
6365
6386
  listShards: () => listShards,
6366
6387
  shardExists: () => shardExists
6367
6388
  });
6368
- import path20 from "path";
6369
- import { existsSync as existsSync16, mkdirSync as mkdirSync8, readdirSync as readdirSync5 } from "fs";
6389
+ import path21 from "path";
6390
+ import { existsSync as existsSync17, mkdirSync as mkdirSync9, readdirSync as readdirSync5 } from "fs";
6370
6391
  import { createClient as createClient2 } from "@libsql/client";
6371
6392
  function initShardManager(encryptionKey) {
6372
6393
  _encryptionKey = encryptionKey;
6373
- if (!existsSync16(SHARDS_DIR)) {
6374
- mkdirSync8(SHARDS_DIR, { recursive: true });
6394
+ if (!existsSync17(SHARDS_DIR)) {
6395
+ mkdirSync9(SHARDS_DIR, { recursive: true });
6375
6396
  }
6376
6397
  _shardingEnabled = true;
6377
6398
  if (_evictionTimer) clearInterval(_evictionTimer);
@@ -6400,7 +6421,7 @@ function getShardClient(projectName) {
6400
6421
  while (_shards.size >= MAX_OPEN_SHARDS) {
6401
6422
  evictLRU();
6402
6423
  }
6403
- const dbPath = path20.join(SHARDS_DIR, `${safeName}.db`);
6424
+ const dbPath = path21.join(SHARDS_DIR, `${safeName}.db`);
6404
6425
  const client = createClient2({
6405
6426
  url: `file:${dbPath}`,
6406
6427
  encryptionKey: _encryptionKey
@@ -6411,10 +6432,10 @@ function getShardClient(projectName) {
6411
6432
  }
6412
6433
  function shardExists(projectName) {
6413
6434
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
6414
- return existsSync16(path20.join(SHARDS_DIR, `${safeName}.db`));
6435
+ return existsSync17(path21.join(SHARDS_DIR, `${safeName}.db`));
6415
6436
  }
6416
6437
  function listShards() {
6417
- if (!existsSync16(SHARDS_DIR)) return [];
6438
+ if (!existsSync17(SHARDS_DIR)) return [];
6418
6439
  return readdirSync5(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
6419
6440
  }
6420
6441
  async function ensureShardSchema(client) {
@@ -6661,7 +6682,7 @@ var init_shard_manager = __esm({
6661
6682
  "src/lib/shard-manager.ts"() {
6662
6683
  "use strict";
6663
6684
  init_config();
6664
- SHARDS_DIR = path20.join(EXE_AI_DIR, "shards");
6685
+ SHARDS_DIR = path21.join(EXE_AI_DIR, "shards");
6665
6686
  SHARD_IDLE_MS = 5 * 60 * 1e3;
6666
6687
  MAX_OPEN_SHARDS = 10;
6667
6688
  EVICTION_INTERVAL_MS = 60 * 1e3;
@@ -7430,6 +7451,115 @@ var init_store = __esm({
7430
7451
  }
7431
7452
  });
7432
7453
 
7454
+ // src/lib/git-staleness.ts
7455
+ var git_staleness_exports = {};
7456
+ __export(git_staleness_exports, {
7457
+ clearSessionFileReads: () => clearSessionFileReads,
7458
+ detectStaleFiles: () => detectStaleFiles,
7459
+ recordFileRead: () => recordFileRead
7460
+ });
7461
+ import { execSync as execSync8 } from "child_process";
7462
+ import path22 from "path";
7463
+ function getHeadCommit(cwd) {
7464
+ try {
7465
+ return execSync8("git rev-parse --short HEAD", {
7466
+ cwd,
7467
+ timeout: GIT_TIMEOUT_MS,
7468
+ encoding: "utf-8"
7469
+ }).trim() || null;
7470
+ } catch {
7471
+ return null;
7472
+ }
7473
+ }
7474
+ function normalizeTrackedPath(cwd, filePath) {
7475
+ const resolved = path22.resolve(cwd, filePath);
7476
+ const relative = path22.relative(cwd, resolved);
7477
+ if (!relative || relative.startsWith("..") || path22.isAbsolute(relative)) {
7478
+ return null;
7479
+ }
7480
+ return relative;
7481
+ }
7482
+ function formatGitSummary(filePath, summary) {
7483
+ const match = summary.trim().match(/^([a-f0-9]+)\s+([^:]+):\s+(.+)$/i);
7484
+ if (!match) return `${filePath} (${summary.trim()})`;
7485
+ const [, hash, author, subject] = match;
7486
+ return `${filePath} (modified by ${author} in commit ${hash}: "${subject}")`;
7487
+ }
7488
+ async function recordFileRead(sessionUuid, agentId, cwd, filePath) {
7489
+ if (!sessionUuid || !filePath) return;
7490
+ const trackedPath = normalizeTrackedPath(cwd, filePath);
7491
+ if (!trackedPath) return;
7492
+ const client = getClient();
7493
+ await client.execute({
7494
+ sql: `INSERT INTO agent_file_reads (session_uuid, agent_id, file_path, read_at, commit_hash)
7495
+ VALUES (?, ?, ?, ?, ?)
7496
+ ON CONFLICT(session_uuid, file_path) DO UPDATE SET
7497
+ read_at = excluded.read_at,
7498
+ commit_hash = excluded.commit_hash`,
7499
+ args: [
7500
+ sessionUuid,
7501
+ agentId,
7502
+ trackedPath,
7503
+ (/* @__PURE__ */ new Date()).toISOString(),
7504
+ getHeadCommit(cwd)
7505
+ ]
7506
+ });
7507
+ }
7508
+ async function detectStaleFiles(agentId, cwd) {
7509
+ const client = getClient();
7510
+ const recentCutoff = new Date(Date.now() - RECENT_READ_LOOKBACK_MS).toISOString();
7511
+ const result = await client.execute({
7512
+ sql: `SELECT file_path, MAX(read_at) AS read_at
7513
+ FROM agent_file_reads
7514
+ WHERE agent_id = ? AND read_at >= ?
7515
+ GROUP BY file_path
7516
+ ORDER BY MAX(read_at) DESC
7517
+ LIMIT 10`,
7518
+ args: [agentId, recentCutoff]
7519
+ });
7520
+ const stale = [];
7521
+ for (const row of result.rows) {
7522
+ if (stale.length >= MAX_STALE_FILES) break;
7523
+ const record = row;
7524
+ const filePath = String(record.file_path ?? "");
7525
+ const readAt = String(record.read_at ?? "");
7526
+ if (!filePath || !readAt) continue;
7527
+ try {
7528
+ const gitSummary = execSync8(
7529
+ `git log -1 --oneline --after=${JSON.stringify(readAt)} --format="%h %an: %s" -- ${JSON.stringify(filePath)}`,
7530
+ {
7531
+ cwd,
7532
+ timeout: GIT_TIMEOUT_MS,
7533
+ encoding: "utf-8"
7534
+ }
7535
+ ).trim();
7536
+ if (gitSummary) {
7537
+ stale.push(formatGitSummary(filePath, gitSummary));
7538
+ }
7539
+ } catch {
7540
+ }
7541
+ }
7542
+ return stale.slice(0, MAX_STALE_FILES);
7543
+ }
7544
+ async function clearSessionFileReads(sessionUuid) {
7545
+ if (!sessionUuid) return;
7546
+ const client = getClient();
7547
+ await client.execute({
7548
+ sql: "DELETE FROM agent_file_reads WHERE session_uuid = ?",
7549
+ args: [sessionUuid]
7550
+ });
7551
+ }
7552
+ var RECENT_READ_LOOKBACK_MS, MAX_STALE_FILES, GIT_TIMEOUT_MS;
7553
+ var init_git_staleness = __esm({
7554
+ "src/lib/git-staleness.ts"() {
7555
+ "use strict";
7556
+ init_database();
7557
+ RECENT_READ_LOOKBACK_MS = 24 * 60 * 60 * 1e3;
7558
+ MAX_STALE_FILES = 10;
7559
+ GIT_TIMEOUT_MS = 400;
7560
+ }
7561
+ });
7562
+
7433
7563
  // src/lib/git-task-sweep.ts
7434
7564
  var git_task_sweep_exports = {};
7435
7565
  __export(git_task_sweep_exports, {
@@ -7439,7 +7569,7 @@ __export(git_task_sweep_exports, {
7439
7569
  matchScore: () => matchScore,
7440
7570
  sweepTasks: () => sweepTasks
7441
7571
  });
7442
- import { execSync as execSync8 } from "child_process";
7572
+ import { execSync as execSync9 } from "child_process";
7443
7573
  function extractKeywords(text) {
7444
7574
  return text.toLowerCase().replace(/[^a-z0-9\s-]/g, " ").split(/\s+/).filter((w) => w.length >= 3 && !STOP_WORDS.has(w));
7445
7575
  }
@@ -7468,7 +7598,7 @@ function matchScore(task, commitMessage, changedFiles) {
7468
7598
  function getRecentCommits(limit = DEFAULT_COMMIT_LIMIT) {
7469
7599
  try {
7470
7600
  const SEPARATOR = "<<SEP>>";
7471
- const output = execSync8(
7601
+ const output = execSync9(
7472
7602
  `git log --format="%h${SEPARATOR}%s${SEPARATOR}%aI" --name-only -n ${limit} -z`,
7473
7603
  { encoding: "utf8", timeout: 1e4 }
7474
7604
  );
@@ -7751,6 +7881,28 @@ function getActiveAgent() {
7751
7881
  };
7752
7882
  }
7753
7883
 
7884
+ // src/adapters/claude/session-key.ts
7885
+ init_session_key();
7886
+
7887
+ // src/lib/cache-warmth.ts
7888
+ import os3 from "os";
7889
+ import path4 from "path";
7890
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync4, unlinkSync as unlinkSync3, writeFileSync as writeFileSync3 } from "fs";
7891
+ var CACHE_TTL_MS = 5 * 60 * 1e3;
7892
+ var CACHE_DIR2 = path4.join(
7893
+ process.env.EXE_OS_DIR ?? path4.join(os3.homedir(), ".exe-os"),
7894
+ "session-cache"
7895
+ );
7896
+ function getStatePath(sessionKey) {
7897
+ return path4.join(CACHE_DIR2, `cache-warmth-${sessionKey}.json`);
7898
+ }
7899
+ function clearCacheState(sessionKey) {
7900
+ try {
7901
+ unlinkSync3(getStatePath(sessionKey));
7902
+ } catch {
7903
+ }
7904
+ }
7905
+
7754
7906
  // src/adapters/claude/hooks/session-end.ts
7755
7907
  init_task_scope();
7756
7908
  init_employees();
@@ -7803,6 +7955,11 @@ Orphaned tasks at session end: ${orphanResult.rows.map((r) => `"${String(r.title
7803
7955
  vector: null
7804
7956
  });
7805
7957
  await flushBatch2();
7958
+ try {
7959
+ const { clearSessionFileReads: clearSessionFileReads2 } = await Promise.resolve().then(() => (init_git_staleness(), git_staleness_exports));
7960
+ await clearSessionFileReads2(data.session_id);
7961
+ } catch {
7962
+ }
7806
7963
  if (!canCoordinate(agent.agentId, agent.agentRole)) {
7807
7964
  const inProgress = orphanResult.rows.filter((r) => String(r.status) === "in_progress");
7808
7965
  if (inProgress.length > 0) {
@@ -7891,6 +8048,7 @@ Orphaned tasks at session end: ${orphanResult.rows.map((r) => `"${String(r.title
7891
8048
  }
7892
8049
  }
7893
8050
  clearActiveAgent();
8051
+ clearCacheState(getSessionKey());
7894
8052
  } catch {
7895
8053
  }
7896
8054
  clearTimeout(timeout);