@askexenow/exe-os 0.9.102 → 0.9.105

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 (92) hide show
  1. package/dist/bin/agentic-ontology-backfill.js +349 -103
  2. package/dist/bin/agentic-reflection-backfill.js +343 -100
  3. package/dist/bin/agentic-semantic-label.js +343 -100
  4. package/dist/bin/backfill-conversations.js +347 -100
  5. package/dist/bin/backfill-responses.js +347 -100
  6. package/dist/bin/backfill-vectors.js +352 -109
  7. package/dist/bin/bulk-sync-postgres.js +350 -104
  8. package/dist/bin/cleanup-stale-review-tasks.js +371 -111
  9. package/dist/bin/cli.js +668 -408
  10. package/dist/bin/exe-agent.js +36 -6
  11. package/dist/bin/exe-assign.js +353 -97
  12. package/dist/bin/exe-boot.js +487 -242
  13. package/dist/bin/exe-call.js +37 -8
  14. package/dist/bin/exe-cloud.js +419 -161
  15. package/dist/bin/exe-dispatch.js +405 -145
  16. package/dist/bin/exe-doctor.js +364 -106
  17. package/dist/bin/exe-export-behaviors.js +366 -108
  18. package/dist/bin/exe-forget.js +367 -106
  19. package/dist/bin/exe-gateway.js +435 -175
  20. package/dist/bin/exe-heartbeat.js +376 -116
  21. package/dist/bin/exe-kill.js +359 -101
  22. package/dist/bin/exe-launch-agent.js +390 -132
  23. package/dist/bin/exe-new-employee.js +98 -70
  24. package/dist/bin/exe-pending-messages.js +371 -111
  25. package/dist/bin/exe-pending-notifications.js +373 -113
  26. package/dist/bin/exe-pending-reviews.js +374 -114
  27. package/dist/bin/exe-rename.js +369 -111
  28. package/dist/bin/exe-review.js +358 -100
  29. package/dist/bin/exe-search.js +378 -116
  30. package/dist/bin/exe-session-cleanup.js +418 -158
  31. package/dist/bin/exe-settings.js +14 -9
  32. package/dist/bin/exe-start-codex.js +380 -134
  33. package/dist/bin/exe-start-opencode.js +374 -128
  34. package/dist/bin/exe-status.js +371 -111
  35. package/dist/bin/exe-team.js +358 -100
  36. package/dist/bin/git-sweep.js +405 -145
  37. package/dist/bin/graph-backfill.js +349 -103
  38. package/dist/bin/graph-export.js +361 -103
  39. package/dist/bin/install.js +1 -0
  40. package/dist/bin/intercom-check.js +418 -158
  41. package/dist/bin/pre-publish.js +27 -3
  42. package/dist/bin/scan-tasks.js +408 -148
  43. package/dist/bin/setup.js +346 -162
  44. package/dist/bin/shard-migrate.js +343 -97
  45. package/dist/gateway/index.js +421 -161
  46. package/dist/hooks/bug-report-worker.js +411 -151
  47. package/dist/hooks/codex-stop-task-finalizer.js +389 -129
  48. package/dist/hooks/commit-complete.js +405 -145
  49. package/dist/hooks/error-recall.js +380 -118
  50. package/dist/hooks/ingest.js +372 -114
  51. package/dist/hooks/instructions-loaded.js +366 -108
  52. package/dist/hooks/notification.js +358 -100
  53. package/dist/hooks/post-compact.js +373 -113
  54. package/dist/hooks/post-tool-combined.js +399 -135
  55. package/dist/hooks/pre-compact.js +406 -146
  56. package/dist/hooks/pre-tool-use.js +377 -117
  57. package/dist/hooks/prompt-submit.js +437 -173
  58. package/dist/hooks/session-end.js +408 -148
  59. package/dist/hooks/session-start.js +405 -141
  60. package/dist/hooks/stop.js +376 -116
  61. package/dist/hooks/subagent-stop.js +369 -109
  62. package/dist/hooks/summary-worker.js +433 -188
  63. package/dist/index.js +415 -155
  64. package/dist/lib/cloud-sync.js +291 -131
  65. package/dist/lib/consolidation.js +8 -2
  66. package/dist/lib/database.js +233 -73
  67. package/dist/lib/db.js +233 -73
  68. package/dist/lib/device-registry.js +237 -77
  69. package/dist/lib/employee-templates.js +34 -4
  70. package/dist/lib/exe-daemon.js +720 -412
  71. package/dist/lib/hybrid-search.js +378 -116
  72. package/dist/lib/identity.js +9 -5
  73. package/dist/lib/messaging.js +26 -20
  74. package/dist/lib/reminders.js +5 -1
  75. package/dist/lib/schedules.js +335 -92
  76. package/dist/lib/skill-learning.js +28 -24
  77. package/dist/lib/store.js +357 -99
  78. package/dist/lib/tasks.js +82 -76
  79. package/dist/lib/tmux-routing.js +74 -68
  80. package/dist/lib/token-spend.js +5 -1
  81. package/dist/mcp/server.js +643 -358
  82. package/dist/mcp/tools/complete-reminder.js +5 -1
  83. package/dist/mcp/tools/create-reminder.js +5 -1
  84. package/dist/mcp/tools/create-task.js +89 -83
  85. package/dist/mcp/tools/deactivate-behavior.js +7 -3
  86. package/dist/mcp/tools/list-reminders.js +5 -1
  87. package/dist/mcp/tools/list-tasks.js +28 -21
  88. package/dist/mcp/tools/send-message.js +28 -22
  89. package/dist/mcp/tools/update-task.js +89 -83
  90. package/dist/runtime/index.js +405 -145
  91. package/dist/tui/App.js +452 -192
  92. package/package.json +1 -1
@@ -1458,9 +1458,79 @@ __export(database_exports, {
1458
1458
  isInitialized: () => isInitialized,
1459
1459
  setExternalClient: () => setExternalClient
1460
1460
  });
1461
- import { chmodSync as chmodSync2 } from "fs";
1461
+ import { chmodSync as chmodSync2, existsSync as existsSync6, statSync as statSync2, copyFileSync, unlinkSync as unlinkSync3, openSync as openSync2, closeSync as closeSync2, mkdirSync as mkdirSync2 } from "fs";
1462
1462
  import { createClient } from "@libsql/client";
1463
+ import { homedir } from "os";
1464
+ import { join } from "path";
1465
+ function logCatchDebug(context, err) {
1466
+ if (_debugDb) {
1467
+ process.stderr.write(
1468
+ `[database] ${context}: ${err instanceof Error ? err.message : String(err)}
1469
+ `
1470
+ );
1471
+ }
1472
+ }
1473
+ function acquireDbLock() {
1474
+ mkdirSync2(join(homedir(), ".exe-os"), { recursive: true });
1475
+ try {
1476
+ _lockFd = openSync2(DB_LOCK_PATH, "wx");
1477
+ } catch (err) {
1478
+ if (err && typeof err === "object" && "code" in err && err.code === "EEXIST") {
1479
+ try {
1480
+ const lockStat = statSync2(DB_LOCK_PATH);
1481
+ if (Date.now() - lockStat.mtimeMs > 6e4) {
1482
+ unlinkSync3(DB_LOCK_PATH);
1483
+ _lockFd = openSync2(DB_LOCK_PATH, "wx");
1484
+ return;
1485
+ }
1486
+ } catch (e) {
1487
+ logCatchDebug("stale lock check", e);
1488
+ }
1489
+ process.stderr.write(
1490
+ "[database] WARN: Another process holds db.lock \u2014 waiting briefly then proceeding.\n"
1491
+ );
1492
+ return;
1493
+ }
1494
+ throw err;
1495
+ }
1496
+ }
1497
+ function releaseDbLock() {
1498
+ if (_lockFd !== null) {
1499
+ try {
1500
+ closeSync2(_lockFd);
1501
+ } catch (e) {
1502
+ logCatchDebug("lock close", e);
1503
+ }
1504
+ _lockFd = null;
1505
+ }
1506
+ try {
1507
+ unlinkSync3(DB_LOCK_PATH);
1508
+ } catch (e) {
1509
+ logCatchDebug("lock unlink", e);
1510
+ }
1511
+ }
1463
1512
  async function initDatabase(config) {
1513
+ acquireDbLock();
1514
+ if (existsSync6(config.dbPath)) {
1515
+ const dbStat = statSync2(config.dbPath);
1516
+ if (dbStat.size === 0) {
1517
+ const walPath = config.dbPath + "-wal";
1518
+ if (existsSync6(walPath) && statSync2(walPath).size > 0) {
1519
+ const backupPath = config.dbPath + ".zeroed-" + Date.now();
1520
+ copyFileSync(config.dbPath, backupPath);
1521
+ unlinkSync3(config.dbPath);
1522
+ process.stderr.write(
1523
+ `[database] CRITICAL: DB was 0 bytes. Moved to ${backupPath}, attempting WAL recovery.
1524
+ `
1525
+ );
1526
+ } else {
1527
+ process.stderr.write(
1528
+ `[database] CRITICAL: DB is 0 bytes and no WAL available for recovery. Data may be lost. Check backups at ${config.dbPath}.bak
1529
+ `
1530
+ );
1531
+ }
1532
+ }
1533
+ }
1464
1534
  if (_walCheckpointTimer) {
1465
1535
  clearInterval(_walCheckpointTimer);
1466
1536
  _walCheckpointTimer = null;
@@ -1487,10 +1557,8 @@ async function initDatabase(config) {
1487
1557
  _client = createClient(opts);
1488
1558
  _resilientClient = wrapWithRetry(_client);
1489
1559
  _adapterClient = _resilientClient;
1490
- _client.execute("PRAGMA busy_timeout = 30000").catch(() => {
1491
- });
1492
- _client.execute("PRAGMA journal_mode = WAL").catch(() => {
1493
- });
1560
+ await _client.execute("PRAGMA busy_timeout = 30000");
1561
+ await _client.execute("PRAGMA journal_mode = WAL");
1494
1562
  if (_walCheckpointTimer) clearInterval(_walCheckpointTimer);
1495
1563
  _walCheckpointTimer = setInterval(() => {
1496
1564
  _client?.execute("PRAGMA wal_checkpoint(PASSIVE)").catch(() => {
@@ -1505,11 +1573,16 @@ async function initDatabase(config) {
1505
1573
  for (const suffix of ["-wal", "-shm"]) {
1506
1574
  try {
1507
1575
  chmodSync2(config.dbPath + suffix, 384);
1508
- } catch {
1576
+ } catch (chmodErr) {
1577
+ process.stderr.write(`[database] chmod ${suffix} failed: ${chmodErr instanceof Error ? chmodErr.message : String(chmodErr)}
1578
+ `);
1509
1579
  }
1510
1580
  }
1511
- } catch {
1581
+ } catch (chmodErr) {
1582
+ process.stderr.write(`[database] chmod db failed: ${chmodErr instanceof Error ? chmodErr.message : String(chmodErr)}
1583
+ `);
1512
1584
  }
1585
+ releaseDbLock();
1513
1586
  }
1514
1587
  function isInitialized() {
1515
1588
  return _adapterClient !== null || _client !== null;
@@ -1564,7 +1637,8 @@ async function ensureSchema() {
1564
1637
  await client.execute("PRAGMA wal_autocheckpoint = 1000");
1565
1638
  try {
1566
1639
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
1567
- } catch {
1640
+ } catch (e) {
1641
+ logCatchDebug("migration", e);
1568
1642
  }
1569
1643
  await client.executeMultiple(`
1570
1644
  CREATE TABLE IF NOT EXISTS memories (
@@ -1629,6 +1703,23 @@ async function ensureSchema() {
1629
1703
  INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
1630
1704
  END;
1631
1705
  `);
1706
+ try {
1707
+ await client.execute("SELECT COUNT(*) FROM memories_fts LIMIT 1");
1708
+ } catch (ftsErr) {
1709
+ process.stderr.write(
1710
+ `[database] WARN: memories_fts corrupted (${ftsErr instanceof Error ? ftsErr.message : String(ftsErr)}) \u2014 rebuilding FTS index.
1711
+ `
1712
+ );
1713
+ try {
1714
+ await client.execute("INSERT INTO memories_fts(memories_fts) VALUES('rebuild')");
1715
+ process.stderr.write("[database] FTS index rebuilt successfully.\n");
1716
+ } catch (rebuildErr) {
1717
+ process.stderr.write(
1718
+ `[database] ERROR: FTS rebuild failed: ${rebuildErr instanceof Error ? rebuildErr.message : String(rebuildErr)}
1719
+ `
1720
+ );
1721
+ }
1722
+ }
1632
1723
  await client.executeMultiple(`
1633
1724
  CREATE TABLE IF NOT EXISTS sync_meta (
1634
1725
  key TEXT PRIMARY KEY,
@@ -1687,35 +1778,40 @@ async function ensureSchema() {
1687
1778
  });
1688
1779
  }
1689
1780
  }
1690
- } catch {
1781
+ } catch (seedErr) {
1782
+ logCatchDebug("behavior seed", seedErr);
1691
1783
  }
1692
1784
  try {
1693
1785
  await client.execute({
1694
1786
  sql: `ALTER TABLE behaviors ADD COLUMN priority TEXT DEFAULT 'p1'`,
1695
1787
  args: []
1696
1788
  });
1697
- } catch {
1789
+ } catch (e) {
1790
+ logCatchDebug("migration", e);
1698
1791
  }
1699
1792
  try {
1700
1793
  await client.execute({
1701
1794
  sql: `ALTER TABLE behaviors ADD COLUMN vector F32_BLOB(${EMBEDDING_DIM})`,
1702
1795
  args: []
1703
1796
  });
1704
- } catch {
1797
+ } catch (e) {
1798
+ logCatchDebug("migration", e);
1705
1799
  }
1706
1800
  try {
1707
1801
  await client.execute({
1708
1802
  sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
1709
1803
  args: []
1710
1804
  });
1711
- } catch {
1805
+ } catch (e) {
1806
+ logCatchDebug("migration", e);
1712
1807
  }
1713
1808
  try {
1714
1809
  await client.execute({
1715
1810
  sql: `ALTER TABLE tasks ADD COLUMN parent_task_id TEXT`,
1716
1811
  args: []
1717
1812
  });
1718
- } catch {
1813
+ } catch (e) {
1814
+ logCatchDebug("migration", e);
1719
1815
  }
1720
1816
  try {
1721
1817
  await client.execute({
@@ -1724,98 +1820,112 @@ async function ensureSchema() {
1724
1820
  WHERE parent_task_id IS NOT NULL`,
1725
1821
  args: []
1726
1822
  });
1727
- } catch {
1823
+ } catch (e) {
1824
+ logCatchDebug("migration", e);
1728
1825
  }
1729
1826
  try {
1730
1827
  await client.execute({
1731
1828
  sql: `UPDATE tasks SET status = 'done' WHERE status = 'completed'`,
1732
1829
  args: []
1733
1830
  });
1734
- } catch {
1831
+ } catch (e) {
1832
+ logCatchDebug("migration", e);
1735
1833
  }
1736
1834
  try {
1737
1835
  await client.execute({
1738
1836
  sql: `ALTER TABLE tasks ADD COLUMN reviewer TEXT`,
1739
1837
  args: []
1740
1838
  });
1741
- } catch {
1839
+ } catch (e) {
1840
+ logCatchDebug("migration", e);
1742
1841
  }
1743
1842
  try {
1744
1843
  await client.execute({
1745
1844
  sql: `ALTER TABLE tasks ADD COLUMN context TEXT`,
1746
1845
  args: []
1747
1846
  });
1748
- } catch {
1847
+ } catch (e) {
1848
+ logCatchDebug("migration", e);
1749
1849
  }
1750
1850
  try {
1751
1851
  await client.execute({
1752
1852
  sql: `ALTER TABLE tasks ADD COLUMN result TEXT`,
1753
1853
  args: []
1754
1854
  });
1755
- } catch {
1855
+ } catch (e) {
1856
+ logCatchDebug("migration", e);
1756
1857
  }
1757
1858
  try {
1758
1859
  await client.execute({
1759
1860
  sql: `ALTER TABLE tasks ADD COLUMN assigned_tmux TEXT`,
1760
1861
  args: []
1761
1862
  });
1762
- } catch {
1863
+ } catch (e) {
1864
+ logCatchDebug("migration", e);
1763
1865
  }
1764
1866
  try {
1765
1867
  await client.execute({
1766
1868
  sql: `ALTER TABLE tasks ADD COLUMN checkpoint TEXT`,
1767
1869
  args: []
1768
1870
  });
1769
- } catch {
1871
+ } catch (e) {
1872
+ logCatchDebug("migration", e);
1770
1873
  }
1771
1874
  try {
1772
1875
  await client.execute({
1773
1876
  sql: `ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER NOT NULL DEFAULT 0`,
1774
1877
  args: []
1775
1878
  });
1776
- } catch {
1879
+ } catch (e) {
1880
+ logCatchDebug("migration", e);
1777
1881
  }
1778
1882
  try {
1779
1883
  await client.execute({
1780
1884
  sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
1781
1885
  args: []
1782
1886
  });
1783
- } catch {
1887
+ } catch (e) {
1888
+ logCatchDebug("migration", e);
1784
1889
  }
1785
1890
  try {
1786
1891
  await client.execute({
1787
1892
  sql: `ALTER TABLE tasks ADD COLUMN session_scope TEXT`,
1788
1893
  args: []
1789
1894
  });
1790
- } catch {
1895
+ } catch (e) {
1896
+ logCatchDebug("migration", e);
1791
1897
  }
1792
1898
  try {
1793
1899
  await client.execute({
1794
1900
  sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
1795
1901
  args: []
1796
1902
  });
1797
- } catch {
1903
+ } catch (e) {
1904
+ logCatchDebug("migration", e);
1798
1905
  }
1799
1906
  try {
1800
1907
  await client.execute({
1801
1908
  sql: `ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0`,
1802
1909
  args: []
1803
1910
  });
1804
- } catch {
1911
+ } catch (e) {
1912
+ logCatchDebug("migration", e);
1805
1913
  }
1806
1914
  try {
1807
1915
  await client.execute({
1808
1916
  sql: `ALTER TABLE memories ADD COLUMN author_device_id TEXT`,
1809
1917
  args: []
1810
1918
  });
1811
- } catch {
1919
+ } catch (e) {
1920
+ logCatchDebug("migration", e);
1812
1921
  }
1813
1922
  try {
1814
1923
  await client.execute({
1815
1924
  sql: `ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'`,
1816
1925
  args: []
1817
1926
  });
1818
- } catch {
1927
+ } catch (e) {
1928
+ logCatchDebug("migration", e);
1819
1929
  }
1820
1930
  await client.executeMultiple(`
1821
1931
  CREATE TABLE IF NOT EXISTS consolidations (
@@ -1920,14 +2030,16 @@ async function ensureSchema() {
1920
2030
  sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
1921
2031
  args: []
1922
2032
  });
1923
- } catch {
2033
+ } catch (e) {
2034
+ logCatchDebug("migration", e);
1924
2035
  }
1925
2036
  try {
1926
2037
  await client.execute({
1927
2038
  sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
1928
2039
  args: []
1929
2040
  });
1930
- } catch {
2041
+ } catch (e) {
2042
+ logCatchDebug("migration", e);
1931
2043
  }
1932
2044
  await client.executeMultiple(`
1933
2045
  CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
@@ -1953,7 +2065,8 @@ async function ensureSchema() {
1953
2065
  sql: `UPDATE tasks SET project_name = 'exe-os' WHERE project_name = 'worker'`,
1954
2066
  args: []
1955
2067
  });
1956
- } catch {
2068
+ } catch (e) {
2069
+ logCatchDebug("migration", e);
1957
2070
  }
1958
2071
  await client.executeMultiple(`
1959
2072
  CREATE TABLE IF NOT EXISTS trajectories (
@@ -1977,7 +2090,8 @@ async function ensureSchema() {
1977
2090
  `);
1978
2091
  try {
1979
2092
  await client.execute("ALTER TABLE trajectories ADD COLUMN skill_id TEXT");
1980
- } catch {
2093
+ } catch (e) {
2094
+ logCatchDebug("migration", e);
1981
2095
  }
1982
2096
  await client.executeMultiple(`
1983
2097
  CREATE TABLE IF NOT EXISTS consolidations (
@@ -2014,63 +2128,72 @@ async function ensureSchema() {
2014
2128
  sql: `ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0`,
2015
2129
  args: []
2016
2130
  });
2017
- } catch {
2131
+ } catch (e) {
2132
+ logCatchDebug("migration", e);
2018
2133
  }
2019
2134
  try {
2020
2135
  await client.execute({
2021
2136
  sql: `ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5`,
2022
2137
  args: []
2023
2138
  });
2024
- } catch {
2139
+ } catch (e) {
2140
+ logCatchDebug("migration", e);
2025
2141
  }
2026
2142
  try {
2027
2143
  await client.execute({
2028
2144
  sql: `ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'`,
2029
2145
  args: []
2030
2146
  });
2031
- } catch {
2147
+ } catch (e) {
2148
+ logCatchDebug("migration", e);
2032
2149
  }
2033
2150
  try {
2034
2151
  await client.execute({
2035
2152
  sql: `ALTER TABLE memories ADD COLUMN deleted_at TEXT`,
2036
2153
  args: []
2037
2154
  });
2038
- } catch {
2155
+ } catch (e) {
2156
+ logCatchDebug("migration", e);
2039
2157
  }
2040
2158
  try {
2041
2159
  await client.execute({
2042
2160
  sql: `ALTER TABLE memories ADD COLUMN confidence REAL DEFAULT 0.7`,
2043
2161
  args: []
2044
2162
  });
2045
- } catch {
2163
+ } catch (e) {
2164
+ logCatchDebug("migration", e);
2046
2165
  }
2047
2166
  try {
2048
2167
  await client.execute({
2049
2168
  sql: `ALTER TABLE memories ADD COLUMN last_accessed TEXT`,
2050
2169
  args: []
2051
2170
  });
2052
- } catch {
2171
+ } catch (e) {
2172
+ logCatchDebug("migration", e);
2053
2173
  }
2054
2174
  try {
2055
2175
  await client.execute({
2056
2176
  sql: `UPDATE memories SET last_accessed = timestamp WHERE last_accessed IS NULL`,
2057
2177
  args: []
2058
2178
  });
2059
- } catch {
2179
+ } catch (e) {
2180
+ logCatchDebug("migration", e);
2060
2181
  }
2061
2182
  try {
2062
2183
  await client.execute({
2063
2184
  sql: `ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0`,
2064
2185
  args: []
2065
2186
  });
2066
- } catch {
2187
+ } catch (e) {
2188
+ logCatchDebug("migration", e);
2067
2189
  }
2068
2190
  try {
2069
2191
  await client.execute({
2070
2192
  sql: `ALTER TABLE memories ADD COLUMN graph_extracted INTEGER DEFAULT 0`,
2071
2193
  args: []
2072
2194
  });
2073
- } catch {
2195
+ } catch (e) {
2196
+ logCatchDebug("migration", e);
2074
2197
  }
2075
2198
  for (const col of [
2076
2199
  "ALTER TABLE memories ADD COLUMN content_hash TEXT",
@@ -2078,14 +2201,16 @@ async function ensureSchema() {
2078
2201
  ]) {
2079
2202
  try {
2080
2203
  await client.execute(col);
2081
- } catch {
2204
+ } catch (e) {
2205
+ logCatchDebug("migration", e);
2082
2206
  }
2083
2207
  }
2084
2208
  try {
2085
2209
  await client.execute(
2086
2210
  `CREATE INDEX IF NOT EXISTS idx_memories_content_hash ON memories(content_hash, agent_id)`
2087
2211
  );
2088
- } catch {
2212
+ } catch (e) {
2213
+ logCatchDebug("migration", e);
2089
2214
  }
2090
2215
  try {
2091
2216
  await client.execute(
@@ -2093,7 +2218,8 @@ async function ensureSchema() {
2093
2218
  ON memories(content_hash, agent_id, project_name, memory_type)
2094
2219
  WHERE content_hash IS NOT NULL`
2095
2220
  );
2096
- } catch {
2221
+ } catch (e) {
2222
+ logCatchDebug("migration", e);
2097
2223
  }
2098
2224
  await client.executeMultiple(`
2099
2225
  CREATE TABLE IF NOT EXISTS entities (
@@ -2169,7 +2295,8 @@ async function ensureSchema() {
2169
2295
  `);
2170
2296
  try {
2171
2297
  await client.execute("INSERT INTO entities_fts(entities_fts) VALUES('rebuild')");
2172
- } catch {
2298
+ } catch (e) {
2299
+ logCatchDebug("migration", e);
2173
2300
  }
2174
2301
  await client.executeMultiple(`
2175
2302
  CREATE TABLE IF NOT EXISTS entity_aliases (
@@ -2184,14 +2311,16 @@ async function ensureSchema() {
2184
2311
  ]) {
2185
2312
  try {
2186
2313
  await client.execute(col);
2187
- } catch {
2314
+ } catch (e) {
2315
+ logCatchDebug("migration", e);
2188
2316
  }
2189
2317
  }
2190
2318
  try {
2191
2319
  await client.execute(
2192
2320
  `CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status)`
2193
2321
  );
2194
- } catch {
2322
+ } catch (e) {
2323
+ logCatchDebug("migration", e);
2195
2324
  }
2196
2325
  await client.executeMultiple(`
2197
2326
  CREATE TABLE IF NOT EXISTS identity (
@@ -2290,7 +2419,8 @@ async function ensureSchema() {
2290
2419
  sql: `ALTER TABLE memories ADD COLUMN ${column}`,
2291
2420
  args: []
2292
2421
  });
2293
- } catch {
2422
+ } catch (e) {
2423
+ logCatchDebug("migration", e);
2294
2424
  }
2295
2425
  }
2296
2426
  for (const col of [
@@ -2299,7 +2429,8 @@ async function ensureSchema() {
2299
2429
  ]) {
2300
2430
  try {
2301
2431
  await client.execute(col);
2302
- } catch {
2432
+ } catch (e) {
2433
+ logCatchDebug("migration", e);
2303
2434
  }
2304
2435
  }
2305
2436
  await client.executeMultiple(`
@@ -2484,56 +2615,64 @@ async function ensureSchema() {
2484
2615
  args: []
2485
2616
  });
2486
2617
  }
2487
- } catch {
2618
+ } catch (e) {
2619
+ logCatchDebug("session_agent_map backfill", e);
2488
2620
  }
2489
2621
  try {
2490
2622
  await client.execute({
2491
2623
  sql: `ALTER TABLE session_agent_map ADD COLUMN cache_cold_count INTEGER NOT NULL DEFAULT 0`,
2492
2624
  args: []
2493
2625
  });
2494
- } catch {
2626
+ } catch (e) {
2627
+ logCatchDebug("migration", e);
2495
2628
  }
2496
2629
  try {
2497
2630
  await client.execute({
2498
2631
  sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
2499
2632
  args: []
2500
2633
  });
2501
- } catch {
2634
+ } catch (e) {
2635
+ logCatchDebug("migration", e);
2502
2636
  }
2503
2637
  try {
2504
2638
  await client.execute({
2505
2639
  sql: `ALTER TABLE tasks ADD COLUMN budget_fallback_model TEXT`,
2506
2640
  args: []
2507
2641
  });
2508
- } catch {
2642
+ } catch (e) {
2643
+ logCatchDebug("migration", e);
2509
2644
  }
2510
2645
  try {
2511
2646
  await client.execute({
2512
2647
  sql: `ALTER TABLE tasks ADD COLUMN tokens_used INTEGER DEFAULT 0`,
2513
2648
  args: []
2514
2649
  });
2515
- } catch {
2650
+ } catch (e) {
2651
+ logCatchDebug("migration", e);
2516
2652
  }
2517
2653
  try {
2518
2654
  await client.execute({
2519
2655
  sql: `ALTER TABLE tasks ADD COLUMN tokens_warned_at INTEGER`,
2520
2656
  args: []
2521
2657
  });
2522
- } catch {
2658
+ } catch (e) {
2659
+ logCatchDebug("migration", e);
2523
2660
  }
2524
2661
  try {
2525
2662
  await client.execute({
2526
2663
  sql: `ALTER TABLE tasks ADD COLUMN spawn_runtime TEXT`,
2527
2664
  args: []
2528
2665
  });
2529
- } catch {
2666
+ } catch (e) {
2667
+ logCatchDebug("migration", e);
2530
2668
  }
2531
2669
  try {
2532
2670
  await client.execute({
2533
2671
  sql: `ALTER TABLE tasks ADD COLUMN spawn_model TEXT`,
2534
2672
  args: []
2535
2673
  });
2536
- } catch {
2674
+ } catch (e) {
2675
+ logCatchDebug("migration", e);
2537
2676
  }
2538
2677
  await client.executeMultiple(`
2539
2678
  CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
@@ -2732,13 +2871,15 @@ async function ensureSchema() {
2732
2871
  sql: `ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3`,
2733
2872
  args: []
2734
2873
  });
2735
- } catch {
2874
+ } catch (e) {
2875
+ logCatchDebug("migration", e);
2736
2876
  }
2737
2877
  try {
2738
2878
  await client.execute(
2739
2879
  `CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)`
2740
2880
  );
2741
- } catch {
2881
+ } catch (e) {
2882
+ logCatchDebug("migration", e);
2742
2883
  }
2743
2884
  try {
2744
2885
  await client.execute({
@@ -2749,20 +2890,23 @@ async function ensureSchema() {
2749
2890
  sql: `UPDATE memories SET tier = 2 WHERE tool_name IN ('store_memory', 'manual') AND importance >= 5 AND tier = 3`,
2750
2891
  args: []
2751
2892
  });
2752
- } catch {
2893
+ } catch (e) {
2894
+ logCatchDebug("migration", e);
2753
2895
  }
2754
2896
  try {
2755
2897
  await client.execute({
2756
2898
  sql: `ALTER TABLE memories ADD COLUMN supersedes_id TEXT`,
2757
2899
  args: []
2758
2900
  });
2759
- } catch {
2901
+ } catch (e) {
2902
+ logCatchDebug("migration", e);
2760
2903
  }
2761
2904
  try {
2762
2905
  await client.execute(
2763
2906
  `CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL`
2764
2907
  );
2765
- } catch {
2908
+ } catch (e) {
2909
+ logCatchDebug("migration", e);
2766
2910
  }
2767
2911
  for (const col of [
2768
2912
  "ALTER TABLE tasks ADD COLUMN checkpoint TEXT",
@@ -2770,7 +2914,8 @@ async function ensureSchema() {
2770
2914
  ]) {
2771
2915
  try {
2772
2916
  await client.execute(col);
2773
- } catch {
2917
+ } catch (e) {
2918
+ logCatchDebug("migration", e);
2774
2919
  }
2775
2920
  }
2776
2921
  try {
@@ -2778,13 +2923,15 @@ async function ensureSchema() {
2778
2923
  sql: `ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0`,
2779
2924
  args: []
2780
2925
  });
2781
- } catch {
2926
+ } catch (e) {
2927
+ logCatchDebug("migration", e);
2782
2928
  }
2783
2929
  try {
2784
2930
  await client.execute(
2785
2931
  `CREATE INDEX IF NOT EXISTS idx_memories_draft ON memories(draft) WHERE draft = 1`
2786
2932
  );
2787
- } catch {
2933
+ } catch (e) {
2934
+ logCatchDebug("migration", e);
2788
2935
  }
2789
2936
  for (const col of [
2790
2937
  "ALTER TABLE memories ADD COLUMN valid_from TEXT",
@@ -2792,7 +2939,8 @@ async function ensureSchema() {
2792
2939
  ]) {
2793
2940
  try {
2794
2941
  await client.execute(col);
2795
- } catch {
2942
+ } catch (e) {
2943
+ logCatchDebug("migration", e);
2796
2944
  }
2797
2945
  }
2798
2946
  try {
@@ -2800,27 +2948,31 @@ async function ensureSchema() {
2800
2948
  sql: `UPDATE memories SET valid_from = timestamp WHERE valid_from IS NULL`,
2801
2949
  args: []
2802
2950
  });
2803
- } catch {
2951
+ } catch (e) {
2952
+ logCatchDebug("migration", e);
2804
2953
  }
2805
2954
  try {
2806
2955
  await client.execute({
2807
2956
  sql: `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`,
2808
2957
  args: []
2809
2958
  });
2810
- } catch {
2959
+ } catch (e) {
2960
+ logCatchDebug("migration", e);
2811
2961
  }
2812
2962
  try {
2813
2963
  await client.execute(
2814
2964
  `CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(memory_type)`
2815
2965
  );
2816
- } catch {
2966
+ } catch (e) {
2967
+ logCatchDebug("migration", e);
2817
2968
  }
2818
2969
  try {
2819
2970
  await client.execute({
2820
2971
  sql: `ALTER TABLE memories ADD COLUMN trajectory TEXT`,
2821
2972
  args: []
2822
2973
  });
2823
- } catch {
2974
+ } catch (e) {
2975
+ logCatchDebug("migration", e);
2824
2976
  }
2825
2977
  for (const col of [
2826
2978
  "ALTER TABLE memories ADD COLUMN intent TEXT",
@@ -2841,7 +2993,8 @@ async function ensureSchema() {
2841
2993
  ]) {
2842
2994
  try {
2843
2995
  await client.execute(col);
2844
- } catch {
2996
+ } catch (e) {
2997
+ logCatchDebug("migration", e);
2845
2998
  }
2846
2999
  }
2847
3000
  try {
@@ -2849,14 +3002,16 @@ async function ensureSchema() {
2849
3002
  sql: `ALTER TABLE memories ADD COLUMN procedure_for TEXT`,
2850
3003
  args: []
2851
3004
  });
2852
- } catch {
3005
+ } catch (e) {
3006
+ logCatchDebug("migration", e);
2853
3007
  }
2854
3008
  try {
2855
3009
  await client.execute({
2856
3010
  sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
2857
3011
  args: []
2858
3012
  });
2859
- } catch {
3013
+ } catch (e) {
3014
+ logCatchDebug("migration", e);
2860
3015
  }
2861
3016
  }
2862
3017
  async function disposeDatabase() {
@@ -2867,7 +3022,8 @@ async function disposeDatabase() {
2867
3022
  if (_client) {
2868
3023
  try {
2869
3024
  await _client.execute("PRAGMA wal_checkpoint(PASSIVE)");
2870
- } catch {
3025
+ } catch (e) {
3026
+ logCatchDebug("WAL checkpoint", e);
2871
3027
  }
2872
3028
  }
2873
3029
  if (_daemonClient) {
@@ -2883,8 +3039,9 @@ async function disposeDatabase() {
2883
3039
  _client = null;
2884
3040
  _resilientClient = null;
2885
3041
  }
3042
+ releaseDbLock();
2886
3043
  }
2887
- var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, SOFT_DELETE_RETENTION_DAYS, disposeTurso;
3044
+ var _debugDb, _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, _lockFd, DB_LOCK_PATH, initTurso, SOFT_DELETE_RETENTION_DAYS, disposeTurso;
2888
3045
  var init_database = __esm({
2889
3046
  "src/lib/database.ts"() {
2890
3047
  "use strict";
@@ -2892,11 +3049,14 @@ var init_database = __esm({
2892
3049
  init_employees();
2893
3050
  init_database_adapter();
2894
3051
  init_memory();
3052
+ _debugDb = process.env.EXE_DEBUG === "1";
2895
3053
  _client = null;
2896
3054
  _resilientClient = null;
2897
3055
  _walCheckpointTimer = null;
2898
3056
  _daemonClient = null;
2899
3057
  _adapterClient = null;
3058
+ _lockFd = null;
3059
+ DB_LOCK_PATH = join(homedir(), ".exe-os", "db.lock");
2900
3060
  initTurso = initDatabase;
2901
3061
  SOFT_DELETE_RETENTION_DAYS = 7;
2902
3062
  disposeTurso = disposeDatabase;
@@ -2919,18 +3079,54 @@ __export(shard_manager_exports, {
2919
3079
  shardExists: () => shardExists
2920
3080
  });
2921
3081
  import path7 from "path";
2922
- import { existsSync as existsSync7, mkdirSync as mkdirSync2, readdirSync, renameSync as renameSync3, statSync as statSync3 } from "fs";
3082
+ import { existsSync as existsSync8, mkdirSync as mkdirSync3, readdirSync, renameSync as renameSync3, statSync as statSync4 } from "fs";
2923
3083
  import { createClient as createClient2 } from "@libsql/client";
2924
3084
  function initShardManager(encryptionKey) {
2925
3085
  _encryptionKey = encryptionKey;
2926
- if (!existsSync7(SHARDS_DIR)) {
2927
- mkdirSync2(SHARDS_DIR, { recursive: true });
3086
+ _keyValidated = false;
3087
+ _keyValidationPromise = null;
3088
+ if (!existsSync8(SHARDS_DIR)) {
3089
+ mkdirSync3(SHARDS_DIR, { recursive: true });
3090
+ }
3091
+ const existingShards = readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db"));
3092
+ if (existingShards.length === 0) {
3093
+ _keyValidated = true;
2928
3094
  }
2929
3095
  _shardingEnabled = true;
2930
3096
  if (_evictionTimer) clearInterval(_evictionTimer);
2931
3097
  _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
2932
3098
  _evictionTimer.unref();
2933
3099
  }
3100
+ async function validateEncryptionKey() {
3101
+ if (_keyValidated) return true;
3102
+ if (!_encryptionKey) return false;
3103
+ const existingShards = readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db"));
3104
+ if (existingShards.length === 0) {
3105
+ _keyValidated = true;
3106
+ return true;
3107
+ }
3108
+ for (const shardFile of existingShards.slice(0, 3)) {
3109
+ const dbPath = path7.join(SHARDS_DIR, shardFile);
3110
+ const testClient = createClient2({ url: `file:${dbPath}`, encryptionKey: _encryptionKey });
3111
+ try {
3112
+ await testClient.execute("SELECT COUNT(*) FROM sqlite_schema");
3113
+ testClient.close();
3114
+ _keyValidated = true;
3115
+ return true;
3116
+ } catch {
3117
+ try {
3118
+ testClient.close();
3119
+ } catch {
3120
+ }
3121
+ }
3122
+ }
3123
+ process.stderr.write(
3124
+ `[shard-manager] WARNING: encryption key cannot read any existing shards (${existingShards.length} found). New shard creation disabled to prevent stranded files. Run /exe-doctor to audit.
3125
+ `
3126
+ );
3127
+ _shardingEnabled = false;
3128
+ return false;
3129
+ }
2934
3130
  function isShardingEnabled() {
2935
3131
  return _shardingEnabled;
2936
3132
  }
@@ -2964,13 +3160,13 @@ function getShardClient(projectName) {
2964
3160
  }
2965
3161
  function shardExists(projectName) {
2966
3162
  const safeName = safeShardName(projectName);
2967
- return existsSync7(path7.join(SHARDS_DIR, `${safeName}.db`));
3163
+ return existsSync8(path7.join(SHARDS_DIR, `${safeName}.db`));
2968
3164
  }
2969
3165
  function safeShardName(projectName) {
2970
3166
  return projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
2971
3167
  }
2972
3168
  function listShards() {
2973
- if (!existsSync7(SHARDS_DIR)) return [];
3169
+ if (!existsSync8(SHARDS_DIR)) return [];
2974
3170
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
2975
3171
  }
2976
3172
  async function auditShardHealth(options = {}) {
@@ -2983,7 +3179,7 @@ async function auditShardHealth(options = {}) {
2983
3179
  const shards = [];
2984
3180
  for (const name of names) {
2985
3181
  const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
2986
- const stat = statSync3(dbPath);
3182
+ const stat = statSync4(dbPath);
2987
3183
  const item = {
2988
3184
  name,
2989
3185
  path: dbPath,
@@ -3223,6 +3419,17 @@ async function ensureShardSchema(client) {
3223
3419
  }
3224
3420
  }
3225
3421
  async function getReadyShardClient(projectName) {
3422
+ if (!_keyValidated) {
3423
+ if (!_keyValidationPromise) {
3424
+ _keyValidationPromise = validateEncryptionKey();
3425
+ }
3426
+ const valid = await _keyValidationPromise;
3427
+ if (!valid) {
3428
+ throw new Error(
3429
+ `Shard creation blocked: encryption key mismatch with existing shards. Run /exe-doctor to audit.`
3430
+ );
3431
+ }
3432
+ }
3226
3433
  const safeName = safeShardName(projectName);
3227
3434
  let client = getShardClient(projectName);
3228
3435
  try {
@@ -3235,8 +3442,8 @@ async function getReadyShardClient(projectName) {
3235
3442
  _shards.delete(safeName);
3236
3443
  _shardLastAccess.delete(safeName);
3237
3444
  const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
3238
- if (existsSync7(dbPath)) {
3239
- const stat = statSync3(dbPath);
3445
+ if (existsSync8(dbPath)) {
3446
+ const stat = statSync4(dbPath);
3240
3447
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3241
3448
  const archivedPath = path7.join(SHARDS_DIR, `${safeName}.db.broken-${stamp}`);
3242
3449
  renameSync3(dbPath, archivedPath);
@@ -3301,7 +3508,7 @@ function disposeShards() {
3301
3508
  _shardingEnabled = false;
3302
3509
  _encryptionKey = null;
3303
3510
  }
3304
- var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
3511
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled, _keyValidated, _keyValidationPromise;
3305
3512
  var init_shard_manager = __esm({
3306
3513
  "src/lib/shard-manager.ts"() {
3307
3514
  "use strict";
@@ -3315,6 +3522,8 @@ var init_shard_manager = __esm({
3315
3522
  _evictionTimer = null;
3316
3523
  _encryptionKey = null;
3317
3524
  _shardingEnabled = false;
3525
+ _keyValidated = false;
3526
+ _keyValidationPromise = null;
3318
3527
  }
3319
3528
  });
3320
3529
 
@@ -3418,7 +3627,7 @@ var init_platform_procedures = __esm({
3418
3627
  title: "Bug report status check \u2014 surface available fixes on boot",
3419
3628
  domain: "support",
3420
3629
  priority: "p1",
3421
- content: "Once per session (COO boot only, never repeat), call list_my_bug_reports to check if any previously filed bug reports have been fixed by AskExe. If any report has status 'fixed' with a fixed_version, surface it to the founder immediately: '\u{1F527} N bug fix(es) available \u2014 run exe-os update to get version X.Y.Z'. This is a one-time check at boot, not a recurring poll. If no reports exist or none are fixed, skip silently. If the MCP tool is unavailable or the network call fails, skip silently \u2014 this is informational, not blocking."
3630
+ content: "Once per session (COO boot only, never repeat), call support(action='list_my_bugs') to check if any previously filed bug reports have been fixed by AskExe. If any report has status 'closed' with a fixed_version, surface it to the founder immediately: '\u{1F527} N bug fix(es) available \u2014 run exe-os update to get version X.Y.Z'. You can also file new bugs with support(action='create_bug') and check status anytime with support(action='list_my_bugs'). This boot check is one-time, not a recurring poll. If the tool is unavailable, skip silently."
3422
3631
  },
3423
3632
  {
3424
3633
  title: "Feature request triage \u2014 upstream feature vs local customization",
@@ -3430,7 +3639,7 @@ var init_platform_procedures = __esm({
3430
3639
  title: "Feature request status check \u2014 surface shipped features on boot",
3431
3640
  domain: "support",
3432
3641
  priority: "p1",
3433
- content: "Once per session (COO boot only, never repeat), call list_my_feature_requests to check if any previously filed feature requests have been shipped by AskExe. If any request has status 'shipped' with a shipped_version, surface it to the founder immediately: '\u{1F680} N feature(s) shipped \u2014 run exe-os update to get version X.Y.Z'. This is a one-time check at boot, not a recurring poll. If no requests exist or none are shipped, skip silently. If the MCP tool is unavailable or the network call fails, skip silently \u2014 this is informational, not blocking."
3642
+ content: "Once per session (COO boot only, never repeat), call support(action='list_my_features') to check if any previously filed feature requests have been shipped by AskExe. If any request has status 'shipped' with a shipped_version, surface it to the founder immediately: '\u{1F680} N feature(s) shipped \u2014 run exe-os update to get version X.Y.Z'. You can also file new requests with support(action='create_feature') and check status anytime with support(action='list_my_features'). This boot check is one-time, not a recurring poll. If the tool is unavailable, skip silently."
3434
3643
  },
3435
3644
  // --- Tool guidance ---
3436
3645
  {
@@ -3466,6 +3675,18 @@ var init_platform_procedures = __esm({
3466
3675
  priority: "p0",
3467
3676
  content: "exe-os has TWO version numbers that move independently. This is normal \u2014 do not treat a mismatch as an error. (1) CLI version (e.g. 0.9.89, 0.9.90) \u2014 the npm package installed locally. Updates frequently: bug fixes, new MCP tools, platform procedures, search improvements, client-side changes. Update with `npm install -g @askexenow/exe-os@latest`. (2) Stack version (e.g. 0.9.7, 0.9.8) \u2014 the Docker images on the VPS. Updates less frequently: only when server-side daemon, gateway, wiki, or CRM images need rebuilding. Update with `exe-os stack-update --target <version> --yes`. The CLI version will almost always be higher than the stack version. A CLI at 0.9.90 with a stack at 0.9.8 is perfectly normal \u2014 it means the CLI got 12 patches since the last Docker image rebuild. Only update the stack when: (a) the boot brief surfaces a fix that mentions 'stack update required', (b) a new stack manifest version is bundled in the CLI (`exe-os stack-update --check` shows pending changes), or (c) AskExe support explicitly tells you to. Do NOT attempt to make the numbers match \u2014 they are separate tracks."
3468
3677
  },
3678
+ {
3679
+ title: "Update lifecycle \u2014 what each command does and what picks up new code",
3680
+ domain: "operations",
3681
+ priority: "p0",
3682
+ content: "Three update paths exist \u2014 know which does what. (1) `npm install -g @askexenow/exe-os@latest` \u2014 customer update. Replaces all dist/ files. postinstall copies slash commands AND restarts the daemon automatically. MCP server picks up new code on next `/mcp` reconnect or new Claude Code session. Hooks pick up new code on next session (they spawn fresh processes). (2) `exe-os update` \u2014 interactive update command. Runs `npm install -g` then `install --global` which restarts daemon, regenerates session wrappers, normalizes roster, registers Codex hooks. This is the recommended customer path. (3) `npm run deploy` \u2014 dev-only (COO/CTO on main branch). Builds, installs globally, runs `install --global`, restarts daemon. Never run from a worktree. NEVER confuse `exe-os setup` (first-time setup wizard for new installs) with `exe-os update` (update existing install). Setup creates encryption keys, configures cloud, runs first sync. Update just replaces code and restarts services. They are completely different commands."
3683
+ },
3684
+ {
3685
+ title: "First install \u2014 setup wizard and license activation",
3686
+ domain: "operations",
3687
+ priority: "p1",
3688
+ content: "Fresh install: `npm install -g @askexenow/exe-os` then run `exe` to start the setup wizard. The wizard prompts for: encryption passphrase (creates master key), license key (exe_sk_* from AskExe team), COO name, and optional team members. No license key = free tier (1 employee, 5K memories). After setup: hooks install automatically, MCP server registers in ~/.claude.json, daemon starts. Verify health: run `exe-os healthcheck` or use mcp_ping() tool."
3689
+ },
3469
3690
  // --- Operations ---
3470
3691
  {
3471
3692
  title: "Managers must supervise deployed workers",
@@ -3510,6 +3731,18 @@ var init_platform_procedures = __esm({
3510
3731
  priority: "p0",
3511
3732
  content: "create_task is dispatch + delivery. Task lifecycle: open \u2192 in_progress (you start) \u2192 done (update_task when finished) \u2192 needs_review (reviewer nudged) \u2192 closed (COO only via close_task). DB is the reliable delivery \u2014 intercom is just a speedup nudge. If you finish a task, self-chain: check for next task immediately (step 7). Never wait for a nudge. Never say 'standing by.'"
3512
3733
  },
3734
+ {
3735
+ title: "Review chain \u2014 managers must actively pull completed work, never wait for nudges",
3736
+ domain: "workflow",
3737
+ priority: "p0",
3738
+ content: "When you dispatch work, you OWN the review. Check list_tasks(status='needs_review') on EVERY prompt \u2014 don't wait for intercom nudges (they're unreliable). When a task shows needs_review: (1) read the deliverable (git diff in worktree, exe/output/ files, or task result summary), (2) verify it works (tsc, build, run), (3) close_task if good or create a fix task if not. Reviews sitting >30 minutes is a pipeline stall. The whole chain: worker calls update_task(done) \u2192 system flags needs_review \u2192 manager pulls and verifies \u2192 close_task \u2192 COO reviews manager's work \u2192 merge to main. Every level actively pulls \u2014 nobody waits."
3739
+ },
3740
+ {
3741
+ title: "Bug fix lifecycle \u2014 triage upstream after every verified fix so customers see the status",
3742
+ domain: "workflow",
3743
+ priority: "p0",
3744
+ content: "When a bug from support(action='list_bugs') is fixed and verified, the reviewer MUST triage it upstream: support(action='triage_bug', id='<bug-id>', notes='<what was fixed>', fixed_version='<version>', linked_commit='<hash>'). This closes the bug in the customer's view \u2014 their COO checks list_my_bugs and sees status change from open \u2192 closed with the fix version. Without triage, customers see 'open' forever even after the fix ships. Same for feature requests: support(action='triage_feature', ..., shipped_version='<version>'). Triage is part of the review gate \u2014 a fix is not done until the upstream report is closed."
3745
+ },
3513
3746
  {
3514
3747
  title: "Intercom is a speedup, not delivery \u2014 DB is the source of truth",
3515
3748
  domain: "architecture",
@@ -3565,7 +3798,7 @@ var init_platform_procedures = __esm({
3565
3798
  title: "MCP tools \u2014 identity, behavior, and support",
3566
3799
  domain: "tool-use",
3567
3800
  priority: "p1",
3568
- content: `identity(action="get") / get_identity: read an agent's exe.md (Layer 1 identity). identity(action="update") / update_identity: write an agent's exe.md. Identity > behavior. behavior(action="store") / store_behavior: record a correction or pattern (Layer 2 expertise). behavior(action="list") / list_behaviors: view active behaviors. behavior(action="deactivate") / deactivate_behavior: soft-delete a stale behavior. support(action="create_bug") / create_bug_report: file a bug report to AskExe (title, description, severity). support(action="create_feature") / create_feature_request: file a feature request. support(action="health") / support_health: check support server readiness. support(action="triage_bug"): triage a bug (id, triage_notes). AskExe-internal only. CRITICAL: triage uses triage_notes NOT notes.`
3801
+ content: `identity(action="get") / get_identity: read an agent's exe.md (Layer 1 identity). identity(action="update") / update_identity: write an agent's exe.md. Identity > behavior. behavior(action="store") / store_behavior: record a correction or pattern (Layer 2 expertise). behavior(action="list") / list_behaviors: view active behaviors. behavior(action="deactivate") / deactivate_behavior: soft-delete a stale behavior. support(action="create_bug"): file a bug report (title, description, severity p0-p3). Auto-delivers to AskExe. support(action="create_feature"): file a feature request (title, description, use_case). support(action="list_my_bugs"): check status of YOUR filed bug reports \u2014 see which are open, triaged, or fixed with version number. support(action="list_my_features"): check status of YOUR filed feature requests \u2014 see which are planned, shipped, or closed. support(action="health"): verify support server is reachable. When a bug you filed shows status='fixed' with a fixed_version, update exe-os to that version to get the fix. support(action="triage_bug"): AskExe-internal only. CRITICAL: triage uses triage_notes NOT notes.`
3569
3802
  },
3570
3803
  {
3571
3804
  title: "MCP tools \u2014 communication and messaging",
@@ -4095,7 +4328,7 @@ init_database();
4095
4328
 
4096
4329
  // src/lib/keychain.ts
4097
4330
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
4098
- import { existsSync as existsSync6, statSync as statSync2 } from "fs";
4331
+ import { existsSync as existsSync7, statSync as statSync3 } from "fs";
4099
4332
  import { execSync as execSync3 } from "child_process";
4100
4333
  import path6 from "path";
4101
4334
  import os5 from "os";
@@ -4134,7 +4367,7 @@ function isRootOnlyTrustedServerKeyFile(keyPath) {
4134
4367
  if (process.platform !== "linux") return false;
4135
4368
  try {
4136
4369
  const uid = typeof os5.userInfo().uid === "number" ? os5.userInfo().uid : -1;
4137
- const st = statSync2(keyPath);
4370
+ const st = statSync3(keyPath);
4138
4371
  if (!st.isFile() || (st.mode & 63) !== 0) return false;
4139
4372
  if (uid === 0) return true;
4140
4373
  const exeOsDir = process.env.EXE_OS_DIR;
@@ -4336,7 +4569,7 @@ async function getMasterKey() {
4336
4569
  }
4337
4570
  }
4338
4571
  const keyPath = getKeyPath();
4339
- if (!existsSync6(keyPath)) {
4572
+ if (!existsSync7(keyPath)) {
4340
4573
  process.stderr.write(
4341
4574
  `[keychain] Key not found at ${keyPath} (HOME=${os5.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
4342
4575
  `
@@ -4530,6 +4763,13 @@ function schedulePostWriteMemoryHygiene(memoryIds) {
4530
4763
  }
4531
4764
 
4532
4765
  // src/lib/store.ts
4766
+ var _debugStore = process.env.EXE_DEBUG === "1";
4767
+ function logStoreWarn(context, err) {
4768
+ process.stderr.write(
4769
+ `[store] WARN ${context}: ${err instanceof Error ? err.message : String(err)}
4770
+ `
4771
+ );
4772
+ }
4533
4773
  var INIT_MAX_RETRIES = 3;
4534
4774
  var INIT_RETRY_DELAY_MS = 1e3;
4535
4775
  function isBusyError2(err) {
@@ -4592,13 +4832,15 @@ async function initStore(options) {
4592
4832
  try {
4593
4833
  const { initDaemonClient: initDaemonClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
4594
4834
  await initDaemonClient2();
4595
- } catch {
4835
+ } catch (e) {
4836
+ logStoreWarn("catch", e);
4596
4837
  }
4597
4838
  if (!options?.lightweight) {
4598
4839
  try {
4599
4840
  const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
4600
4841
  initShardManager2(hexKey);
4601
- } catch {
4842
+ } catch (e) {
4843
+ logStoreWarn("catch", e);
4602
4844
  }
4603
4845
  const client = getClient();
4604
4846
  const vResult = await retryOnBusy2(
@@ -4609,7 +4851,8 @@ async function initStore(options) {
4609
4851
  try {
4610
4852
  const { loadGlobalProcedures: loadGlobalProcedures2 } = await Promise.resolve().then(() => (init_global_procedures(), global_procedures_exports));
4611
4853
  await loadGlobalProcedures2();
4612
- } catch {
4854
+ } catch (e) {
4855
+ logStoreWarn("catch", e);
4613
4856
  }
4614
4857
  }
4615
4858
  }
@@ -4737,12 +4980,14 @@ async function flushBatch() {
4737
4980
  try {
4738
4981
  const { insertMemoryCardsForBatch: insertMemoryCardsForBatch2 } = await Promise.resolve().then(() => (init_memory_cards(), memory_cards_exports));
4739
4982
  await insertMemoryCardsForBatch2(batch);
4740
- } catch {
4983
+ } catch (e) {
4984
+ logStoreWarn("catch", e);
4741
4985
  }
4742
4986
  try {
4743
4987
  const { insertOntologyForBatch: insertOntologyForBatch2 } = await Promise.resolve().then(() => (init_agentic_ontology(), agentic_ontology_exports));
4744
4988
  await insertOntologyForBatch2(batch);
4745
- } catch {
4989
+ } catch (e) {
4990
+ logStoreWarn("catch", e);
4746
4991
  }
4747
4992
  schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
4748
4993
  _pendingRecords.splice(0, batch.length);
@@ -4781,7 +5026,8 @@ ${err.stack.split("\n").slice(1, 3).join("\n")}` : ""}` : String(err);
4781
5026
  }
4782
5027
  }
4783
5028
  }
4784
- } catch {
5029
+ } catch (e) {
5030
+ logStoreWarn("catch", e);
4785
5031
  }
4786
5032
  return batch.length;
4787
5033
  } finally {
@@ -4811,7 +5057,7 @@ init_agentic_ontology();
4811
5057
 
4812
5058
  // src/lib/background-jobs.ts
4813
5059
  init_config();
4814
- import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3, unlinkSync as unlinkSync3 } from "fs";
5060
+ import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync3, unlinkSync as unlinkSync4 } from "fs";
4815
5061
  import { execFileSync } from "child_process";
4816
5062
  import os6 from "os";
4817
5063
  import path8 from "path";
@@ -4821,7 +5067,7 @@ var LOCK_DIR = path8.join(JOB_DIR, "locks");
4821
5067
  var DEFAULT_LOCK_TTL_MS = 6 * 60 * 60 * 1e3;
4822
5068
  var MAX_HISTORY = 200;
4823
5069
  function ensureDirs() {
4824
- mkdirSync3(LOCK_DIR, { recursive: true });
5070
+ mkdirSync4(LOCK_DIR, { recursive: true });
4825
5071
  }
4826
5072
  function now() {
4827
5073
  return (/* @__PURE__ */ new Date()).toISOString();
@@ -4837,7 +5083,7 @@ function isAlive(pid) {
4837
5083
  }
4838
5084
  function readJobsRaw() {
4839
5085
  ensureDirs();
4840
- if (!existsSync8(JOBS_FILE)) return [];
5086
+ if (!existsSync9(JOBS_FILE)) return [];
4841
5087
  try {
4842
5088
  const parsed = JSON.parse(readFileSync5(JOBS_FILE, "utf8"));
4843
5089
  return Array.isArray(parsed) ? parsed : [];
@@ -4857,7 +5103,7 @@ function lockPath(type) {
4857
5103
  function acquireJobLock(type, ttlMs = DEFAULT_LOCK_TTL_MS) {
4858
5104
  ensureDirs();
4859
5105
  const file = lockPath(type);
4860
- if (existsSync8(file)) {
5106
+ if (existsSync9(file)) {
4861
5107
  try {
4862
5108
  const lock = JSON.parse(readFileSync5(file, "utf8"));
4863
5109
  const age = Date.now() - Date.parse(lock.updatedAt ?? "");
@@ -4865,7 +5111,7 @@ function acquireJobLock(type, ttlMs = DEFAULT_LOCK_TTL_MS) {
4865
5111
  } catch {
4866
5112
  }
4867
5113
  try {
4868
- unlinkSync3(file);
5114
+ unlinkSync4(file);
4869
5115
  } catch {
4870
5116
  }
4871
5117
  }
@@ -4879,12 +5125,12 @@ function acquireJobLock(type, ttlMs = DEFAULT_LOCK_TTL_MS) {
4879
5125
  function releaseJobLock(type) {
4880
5126
  const file = lockPath(type);
4881
5127
  try {
4882
- if (!existsSync8(file)) return;
5128
+ if (!existsSync9(file)) return;
4883
5129
  const lock = JSON.parse(readFileSync5(file, "utf8"));
4884
- if (lock.pid === process.pid || !lock.pid || !isAlive(lock.pid)) unlinkSync3(file);
5130
+ if (lock.pid === process.pid || !lock.pid || !isAlive(lock.pid)) unlinkSync4(file);
4885
5131
  } catch {
4886
5132
  try {
4887
- unlinkSync3(file);
5133
+ unlinkSync4(file);
4888
5134
  } catch {
4889
5135
  }
4890
5136
  }