@askexenow/exe-os 0.8.33 → 0.8.37

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 (89) hide show
  1. package/dist/bin/backfill-conversations.js +341 -349
  2. package/dist/bin/backfill-responses.js +81 -13
  3. package/dist/bin/backfill-vectors.js +72 -12
  4. package/dist/bin/cleanup-stale-review-tasks.js +63 -3
  5. package/dist/bin/cli.js +1737 -1117
  6. package/dist/bin/exe-assign.js +89 -19
  7. package/dist/bin/exe-boot.js +951 -101
  8. package/dist/bin/exe-call.js +61 -2
  9. package/dist/bin/exe-dispatch.js +61 -13
  10. package/dist/bin/exe-doctor.js +63 -3
  11. package/dist/bin/exe-export-behaviors.js +71 -3
  12. package/dist/bin/exe-forget.js +69 -4
  13. package/dist/bin/exe-gateway.js +178 -45
  14. package/dist/bin/exe-heartbeat.js +79 -14
  15. package/dist/bin/exe-kill.js +71 -3
  16. package/dist/bin/exe-launch-agent.js +148 -14
  17. package/dist/bin/exe-link.js +1437 -0
  18. package/dist/bin/exe-new-employee.js +98 -13
  19. package/dist/bin/exe-pending-messages.js +74 -8
  20. package/dist/bin/exe-pending-notifications.js +63 -3
  21. package/dist/bin/exe-pending-reviews.js +77 -11
  22. package/dist/bin/exe-rename.js +1287 -0
  23. package/dist/bin/exe-review.js +73 -5
  24. package/dist/bin/exe-search.js +88 -14
  25. package/dist/bin/exe-session-cleanup.js +102 -28
  26. package/dist/bin/exe-status.js +64 -4
  27. package/dist/bin/exe-team.js +64 -4
  28. package/dist/bin/git-sweep.js +80 -5
  29. package/dist/bin/graph-backfill.js +71 -3
  30. package/dist/bin/graph-export.js +71 -3
  31. package/dist/bin/install.js +38 -8
  32. package/dist/bin/scan-tasks.js +80 -5
  33. package/dist/bin/setup.js +128 -10
  34. package/dist/bin/shard-migrate.js +71 -3
  35. package/dist/bin/wiki-sync.js +71 -3
  36. package/dist/gateway/index.js +179 -46
  37. package/dist/hooks/bug-report-worker.js +254 -28
  38. package/dist/hooks/commit-complete.js +80 -5
  39. package/dist/hooks/error-recall.js +89 -15
  40. package/dist/hooks/exe-heartbeat-hook.js +1 -1
  41. package/dist/hooks/ingest-worker.js +185 -51
  42. package/dist/hooks/ingest.js +1 -1
  43. package/dist/hooks/instructions-loaded.js +81 -6
  44. package/dist/hooks/notification.js +81 -6
  45. package/dist/hooks/post-compact.js +81 -6
  46. package/dist/hooks/pre-compact.js +81 -6
  47. package/dist/hooks/pre-tool-use.js +423 -196
  48. package/dist/hooks/prompt-ingest-worker.js +91 -23
  49. package/dist/hooks/prompt-submit.js +159 -45
  50. package/dist/hooks/response-ingest-worker.js +96 -23
  51. package/dist/hooks/session-end.js +81 -6
  52. package/dist/hooks/session-start.js +89 -15
  53. package/dist/hooks/stop.js +81 -6
  54. package/dist/hooks/subagent-stop.js +81 -6
  55. package/dist/hooks/summary-worker.js +807 -55
  56. package/dist/index.js +198 -60
  57. package/dist/lib/cloud-sync.js +703 -18
  58. package/dist/lib/consolidation.js +4 -4
  59. package/dist/lib/database.js +64 -2
  60. package/dist/lib/device-registry.js +70 -3
  61. package/dist/lib/employee-templates.js +26 -0
  62. package/dist/lib/employees.js +34 -1
  63. package/dist/lib/exe-daemon.js +207 -74
  64. package/dist/lib/hybrid-search.js +88 -14
  65. package/dist/lib/identity-templates.js +51 -0
  66. package/dist/lib/identity.js +3 -3
  67. package/dist/lib/messaging.js +65 -17
  68. package/dist/lib/reminders.js +3 -3
  69. package/dist/lib/schedules.js +63 -3
  70. package/dist/lib/skill-learning.js +3 -3
  71. package/dist/lib/status-brief.js +63 -5
  72. package/dist/lib/store.js +73 -4
  73. package/dist/lib/task-router.js +4 -2
  74. package/dist/lib/tasks.js +95 -28
  75. package/dist/lib/tmux-routing.js +92 -23
  76. package/dist/mcp/server.js +800 -74
  77. package/dist/mcp/tools/complete-reminder.js +3 -3
  78. package/dist/mcp/tools/create-reminder.js +3 -3
  79. package/dist/mcp/tools/create-task.js +198 -31
  80. package/dist/mcp/tools/deactivate-behavior.js +4 -4
  81. package/dist/mcp/tools/list-reminders.js +3 -3
  82. package/dist/mcp/tools/list-tasks.js +19 -9
  83. package/dist/mcp/tools/send-message.js +69 -21
  84. package/dist/mcp/tools/update-task.js +28 -18
  85. package/dist/runtime/index.js +166 -28
  86. package/dist/tui/App.js +193 -40
  87. package/package.json +7 -3
  88. package/src/commands/exe/afk.md +116 -0
  89. package/src/commands/exe/rename.md +12 -0
@@ -23,12 +23,68 @@ var init_memory = __esm({
23
23
  }
24
24
  });
25
25
 
26
+ // src/lib/db-retry.ts
27
+ function isBusyError(err) {
28
+ if (err instanceof Error) {
29
+ const msg = err.message.toLowerCase();
30
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
31
+ }
32
+ return false;
33
+ }
34
+ function delay(ms) {
35
+ return new Promise((resolve) => setTimeout(resolve, ms));
36
+ }
37
+ async function retryOnBusy(fn, label) {
38
+ let lastError;
39
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
40
+ try {
41
+ return await fn();
42
+ } catch (err) {
43
+ lastError = err;
44
+ if (!isBusyError(err) || attempt === MAX_RETRIES) {
45
+ throw err;
46
+ }
47
+ const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
48
+ const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
49
+ process.stderr.write(
50
+ `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
51
+ `
52
+ );
53
+ await delay(backoff + jitter);
54
+ }
55
+ }
56
+ throw lastError;
57
+ }
58
+ function wrapWithRetry(client) {
59
+ return new Proxy(client, {
60
+ get(target, prop, receiver) {
61
+ if (prop === "execute") {
62
+ return (sql) => retryOnBusy(() => target.execute(sql), "execute");
63
+ }
64
+ if (prop === "batch") {
65
+ return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
66
+ }
67
+ return Reflect.get(target, prop, receiver);
68
+ }
69
+ });
70
+ }
71
+ var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
72
+ var init_db_retry = __esm({
73
+ "src/lib/db-retry.ts"() {
74
+ "use strict";
75
+ MAX_RETRIES = 3;
76
+ BASE_DELAY_MS = 200;
77
+ MAX_JITTER_MS = 300;
78
+ }
79
+ });
80
+
26
81
  // src/lib/database.ts
27
82
  import { createClient } from "@libsql/client";
28
83
  async function initDatabase(config) {
29
84
  if (_client) {
30
85
  _client.close();
31
86
  _client = null;
87
+ _resilientClient = null;
32
88
  }
33
89
  const opts = {
34
90
  url: `file:${config.dbPath}`
@@ -37,20 +93,27 @@ async function initDatabase(config) {
37
93
  opts.encryptionKey = config.encryptionKey;
38
94
  }
39
95
  _client = createClient(opts);
96
+ _resilientClient = wrapWithRetry(_client);
40
97
  }
41
98
  function isInitialized() {
42
99
  return _client !== null;
43
100
  }
44
101
  function getClient() {
102
+ if (!_resilientClient) {
103
+ throw new Error("Database client not initialized. Call initDatabase() first.");
104
+ }
105
+ return _resilientClient;
106
+ }
107
+ function getRawClient() {
45
108
  if (!_client) {
46
109
  throw new Error("Database client not initialized. Call initDatabase() first.");
47
110
  }
48
111
  return _client;
49
112
  }
50
113
  async function ensureSchema() {
51
- const client = getClient();
114
+ const client = getRawClient();
52
115
  await client.execute("PRAGMA journal_mode = WAL");
53
- await client.execute("PRAGMA busy_timeout = 5000");
116
+ await client.execute("PRAGMA busy_timeout = 30000");
54
117
  try {
55
118
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
56
119
  } catch {
@@ -839,11 +902,13 @@ async function ensureSchema() {
839
902
  }
840
903
  }
841
904
  }
842
- var _client, initTurso;
905
+ var _client, _resilientClient, initTurso;
843
906
  var init_database = __esm({
844
907
  "src/lib/database.ts"() {
845
908
  "use strict";
909
+ init_db_retry();
846
910
  _client = null;
911
+ _resilientClient = null;
847
912
  initTurso = initDatabase;
848
913
  }
849
914
  });
@@ -1149,7 +1214,7 @@ function listShards() {
1149
1214
  }
1150
1215
  async function ensureShardSchema(client) {
1151
1216
  await client.execute("PRAGMA journal_mode = WAL");
1152
- await client.execute("PRAGMA busy_timeout = 5000");
1217
+ await client.execute("PRAGMA busy_timeout = 30000");
1153
1218
  try {
1154
1219
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
1155
1220
  } catch {
@@ -1510,11 +1575,11 @@ async function connectEmbedDaemon() {
1510
1575
  }
1511
1576
  }
1512
1577
  const start = Date.now();
1513
- let delay = 100;
1578
+ let delay2 = 100;
1514
1579
  while (Date.now() - start < CONNECT_TIMEOUT_MS) {
1515
- await new Promise((r) => setTimeout(r, delay));
1580
+ await new Promise((r) => setTimeout(r, delay2));
1516
1581
  if (await connectToSocket()) return true;
1517
- delay = Math.min(delay * 2, 3e3);
1582
+ delay2 = Math.min(delay2 * 2, 3e3);
1518
1583
  }
1519
1584
  return false;
1520
1585
  }
@@ -1606,11 +1671,11 @@ async function embedViaClient(text, priority = "high") {
1606
1671
  `);
1607
1672
  killAndRespawnDaemon();
1608
1673
  const start = Date.now();
1609
- let delay = 200;
1674
+ let delay2 = 200;
1610
1675
  while (Date.now() - start < CONNECT_TIMEOUT_MS) {
1611
- await new Promise((r) => setTimeout(r, delay));
1676
+ await new Promise((r) => setTimeout(r, delay2));
1612
1677
  if (await connectToSocket()) break;
1613
- delay = Math.min(delay * 2, 3e3);
1678
+ delay2 = Math.min(delay2 * 2, 3e3);
1614
1679
  }
1615
1680
  if (!_connected) return null;
1616
1681
  }
@@ -1622,11 +1687,11 @@ async function embedViaClient(text, priority = "high") {
1622
1687
  `);
1623
1688
  killAndRespawnDaemon();
1624
1689
  const start = Date.now();
1625
- let delay = 200;
1690
+ let delay2 = 200;
1626
1691
  while (Date.now() - start < CONNECT_TIMEOUT_MS) {
1627
- await new Promise((r) => setTimeout(r, delay));
1692
+ await new Promise((r) => setTimeout(r, delay2));
1628
1693
  if (await connectToSocket()) break;
1629
- delay = Math.min(delay * 2, 3e3);
1694
+ delay2 = Math.min(delay2 * 2, 3e3);
1630
1695
  }
1631
1696
  if (!_connected) return null;
1632
1697
  const retry = await sendRequest([text], priority);
@@ -1739,7 +1804,7 @@ var init_embedder = __esm({
1739
1804
 
1740
1805
  // src/lib/employees.ts
1741
1806
  import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
1742
- import { existsSync as existsSync5, symlinkSync, readlinkSync } from "fs";
1807
+ import { existsSync as existsSync5, symlinkSync, readlinkSync, readFileSync as readFileSync3 } from "fs";
1743
1808
  import { execSync as execSync2 } from "child_process";
1744
1809
  import path6 from "path";
1745
1810
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
@@ -1763,7 +1828,7 @@ var init_employees = __esm({
1763
1828
  });
1764
1829
 
1765
1830
  // src/lib/license.ts
1766
- import { readFileSync as readFileSync3, writeFileSync, existsSync as existsSync6, mkdirSync as mkdirSync2 } from "fs";
1831
+ import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync6, mkdirSync as mkdirSync2 } from "fs";
1767
1832
  import { randomUUID as randomUUID2 } from "crypto";
1768
1833
  import path7 from "path";
1769
1834
  import { jwtVerify, importSPKI } from "jose";
@@ -1771,14 +1836,14 @@ function loadDeviceId() {
1771
1836
  const deviceJsonPath = path7.join(EXE_AI_DIR, "device.json");
1772
1837
  try {
1773
1838
  if (existsSync6(deviceJsonPath)) {
1774
- const data = JSON.parse(readFileSync3(deviceJsonPath, "utf8"));
1839
+ const data = JSON.parse(readFileSync4(deviceJsonPath, "utf8"));
1775
1840
  if (data.deviceId) return data.deviceId;
1776
1841
  }
1777
1842
  } catch {
1778
1843
  }
1779
1844
  try {
1780
1845
  if (existsSync6(DEVICE_ID_PATH)) {
1781
- const id2 = readFileSync3(DEVICE_ID_PATH, "utf8").trim();
1846
+ const id2 = readFileSync4(DEVICE_ID_PATH, "utf8").trim();
1782
1847
  if (id2) return id2;
1783
1848
  }
1784
1849
  } catch {
@@ -1791,7 +1856,7 @@ function loadDeviceId() {
1791
1856
  function loadLicense() {
1792
1857
  try {
1793
1858
  if (!existsSync6(LICENSE_PATH)) return null;
1794
- return readFileSync3(LICENSE_PATH, "utf8").trim();
1859
+ return readFileSync4(LICENSE_PATH, "utf8").trim();
1795
1860
  } catch {
1796
1861
  return null;
1797
1862
  }
@@ -1821,7 +1886,7 @@ async function verifyLicenseJwt(token) {
1821
1886
  async function getCachedLicense() {
1822
1887
  try {
1823
1888
  if (!existsSync6(CACHE_PATH)) return null;
1824
- const raw = JSON.parse(readFileSync3(CACHE_PATH, "utf8"));
1889
+ const raw = JSON.parse(readFileSync4(CACHE_PATH, "utf8"));
1825
1890
  if (!raw.token || typeof raw.token !== "string") return null;
1826
1891
  return await verifyLicenseJwt(raw.token);
1827
1892
  } catch {
@@ -1937,12 +2002,12 @@ __export(plan_limits_exports, {
1937
2002
  countActiveMemories: () => countActiveMemories,
1938
2003
  getLicenseSync: () => getLicenseSync
1939
2004
  });
1940
- import { readFileSync as readFileSync4, existsSync as existsSync7 } from "fs";
2005
+ import { readFileSync as readFileSync5, existsSync as existsSync7 } from "fs";
1941
2006
  import path8 from "path";
1942
2007
  function getLicenseSync() {
1943
2008
  try {
1944
2009
  if (!existsSync7(CACHE_PATH2)) return freeLicense();
1945
- const raw = JSON.parse(readFileSync4(CACHE_PATH2, "utf8"));
2010
+ const raw = JSON.parse(readFileSync5(CACHE_PATH2, "utf8"));
1946
2011
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
1947
2012
  const parts = raw.token.split(".");
1948
2013
  if (parts.length !== 3) return freeLicense();
@@ -2010,7 +2075,7 @@ function assertEmployeeLimitSync(rosterPath) {
2010
2075
  let count = 0;
2011
2076
  try {
2012
2077
  if (existsSync7(filePath)) {
2013
- const raw = readFileSync4(filePath, "utf8");
2078
+ const raw = readFileSync5(filePath, "utf8");
2014
2079
  const employees = JSON.parse(raw);
2015
2080
  count = Array.isArray(employees) ? employees.length : 0;
2016
2081
  }
@@ -2207,7 +2272,8 @@ async function writeMemory(record) {
2207
2272
  has_error: record.has_error ? 1 : 0,
2208
2273
  raw_text: record.raw_text,
2209
2274
  vector: record.vector,
2210
- version: _nextVersion++,
2275
+ version: 0,
2276
+ // Placeholder — assigned atomically at flush time
2211
2277
  task_id: record.task_id ?? null,
2212
2278
  importance: record.importance ?? 5,
2213
2279
  status: record.status ?? "active",
@@ -2241,6 +2307,13 @@ async function flushBatch() {
2241
2307
  _flushing = true;
2242
2308
  try {
2243
2309
  const batch = _pendingRecords.slice(0);
2310
+ const client = getClient();
2311
+ const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
2312
+ let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
2313
+ for (const row of batch) {
2314
+ row.version = baseVersion++;
2315
+ }
2316
+ _nextVersion = baseVersion;
2244
2317
  const buildStmt = (row) => {
2245
2318
  const hasVector = row.vector !== null;
2246
2319
  const taskId = row.task_id ?? null;
@@ -212,6 +212,61 @@ var init_memory = __esm({
212
212
  }
213
213
  });
214
214
 
215
+ // src/lib/db-retry.ts
216
+ function isBusyError(err) {
217
+ if (err instanceof Error) {
218
+ const msg = err.message.toLowerCase();
219
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
220
+ }
221
+ return false;
222
+ }
223
+ function delay(ms) {
224
+ return new Promise((resolve) => setTimeout(resolve, ms));
225
+ }
226
+ async function retryOnBusy(fn, label) {
227
+ let lastError;
228
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
229
+ try {
230
+ return await fn();
231
+ } catch (err) {
232
+ lastError = err;
233
+ if (!isBusyError(err) || attempt === MAX_RETRIES) {
234
+ throw err;
235
+ }
236
+ const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
237
+ const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
238
+ process.stderr.write(
239
+ `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
240
+ `
241
+ );
242
+ await delay(backoff + jitter);
243
+ }
244
+ }
245
+ throw lastError;
246
+ }
247
+ function wrapWithRetry(client) {
248
+ return new Proxy(client, {
249
+ get(target, prop, receiver) {
250
+ if (prop === "execute") {
251
+ return (sql) => retryOnBusy(() => target.execute(sql), "execute");
252
+ }
253
+ if (prop === "batch") {
254
+ return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
255
+ }
256
+ return Reflect.get(target, prop, receiver);
257
+ }
258
+ });
259
+ }
260
+ var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
261
+ var init_db_retry = __esm({
262
+ "src/lib/db-retry.ts"() {
263
+ "use strict";
264
+ MAX_RETRIES = 3;
265
+ BASE_DELAY_MS = 200;
266
+ MAX_JITTER_MS = 300;
267
+ }
268
+ });
269
+
215
270
  // src/lib/database.ts
216
271
  var database_exports = {};
217
272
  __export(database_exports, {
@@ -219,6 +274,7 @@ __export(database_exports, {
219
274
  disposeTurso: () => disposeTurso,
220
275
  ensureSchema: () => ensureSchema,
221
276
  getClient: () => getClient,
277
+ getRawClient: () => getRawClient,
222
278
  initDatabase: () => initDatabase,
223
279
  initTurso: () => initTurso,
224
280
  isInitialized: () => isInitialized
@@ -228,6 +284,7 @@ async function initDatabase(config) {
228
284
  if (_client) {
229
285
  _client.close();
230
286
  _client = null;
287
+ _resilientClient = null;
231
288
  }
232
289
  const opts = {
233
290
  url: `file:${config.dbPath}`
@@ -236,20 +293,27 @@ async function initDatabase(config) {
236
293
  opts.encryptionKey = config.encryptionKey;
237
294
  }
238
295
  _client = createClient(opts);
296
+ _resilientClient = wrapWithRetry(_client);
239
297
  }
240
298
  function isInitialized() {
241
299
  return _client !== null;
242
300
  }
243
301
  function getClient() {
302
+ if (!_resilientClient) {
303
+ throw new Error("Database client not initialized. Call initDatabase() first.");
304
+ }
305
+ return _resilientClient;
306
+ }
307
+ function getRawClient() {
244
308
  if (!_client) {
245
309
  throw new Error("Database client not initialized. Call initDatabase() first.");
246
310
  }
247
311
  return _client;
248
312
  }
249
313
  async function ensureSchema() {
250
- const client = getClient();
314
+ const client = getRawClient();
251
315
  await client.execute("PRAGMA journal_mode = WAL");
252
- await client.execute("PRAGMA busy_timeout = 5000");
316
+ await client.execute("PRAGMA busy_timeout = 30000");
253
317
  try {
254
318
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
255
319
  } catch {
@@ -1042,13 +1106,16 @@ async function disposeDatabase() {
1042
1106
  if (_client) {
1043
1107
  _client.close();
1044
1108
  _client = null;
1109
+ _resilientClient = null;
1045
1110
  }
1046
1111
  }
1047
- var _client, initTurso, disposeTurso;
1112
+ var _client, _resilientClient, initTurso, disposeTurso;
1048
1113
  var init_database = __esm({
1049
1114
  "src/lib/database.ts"() {
1050
1115
  "use strict";
1116
+ init_db_retry();
1051
1117
  _client = null;
1118
+ _resilientClient = null;
1052
1119
  initTurso = initDatabase;
1053
1120
  disposeTurso = disposeDatabase;
1054
1121
  }
@@ -1161,7 +1228,7 @@ function listShards() {
1161
1228
  }
1162
1229
  async function ensureShardSchema(client) {
1163
1230
  await client.execute("PRAGMA journal_mode = WAL");
1164
- await client.execute("PRAGMA busy_timeout = 5000");
1231
+ await client.execute("PRAGMA busy_timeout = 30000");
1165
1232
  try {
1166
1233
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
1167
1234
  } catch {
@@ -1422,7 +1489,8 @@ async function writeMemory(record) {
1422
1489
  has_error: record.has_error ? 1 : 0,
1423
1490
  raw_text: record.raw_text,
1424
1491
  vector: record.vector,
1425
- version: _nextVersion++,
1492
+ version: 0,
1493
+ // Placeholder — assigned atomically at flush time
1426
1494
  task_id: record.task_id ?? null,
1427
1495
  importance: record.importance ?? 5,
1428
1496
  status: record.status ?? "active",
@@ -1456,6 +1524,13 @@ async function flushBatch() {
1456
1524
  _flushing = true;
1457
1525
  try {
1458
1526
  const batch = _pendingRecords.slice(0);
1527
+ const client = getClient();
1528
+ const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
1529
+ let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
1530
+ for (const row of batch) {
1531
+ row.version = baseVersion++;
1532
+ }
1533
+ _nextVersion = baseVersion;
1459
1534
  const buildStmt = (row) => {
1460
1535
  const hasVector = row.vector !== null;
1461
1536
  const taskId = row.task_id ?? null;
@@ -2095,7 +2170,7 @@ function getActiveAgent() {
2095
2170
  "tmux display-message -p '#{session_name}' 2>/dev/null",
2096
2171
  { encoding: "utf8", timeout: 2e3 }
2097
2172
  ).trim();
2098
- const empMatch = sessionName.match(/^(\w+)-exe\d+$/);
2173
+ const empMatch = sessionName.match(/^([a-zA-Z]+)\d*-exe\d+$/);
2099
2174
  if (empMatch && empMatch[1] !== "exe") {
2100
2175
  return { agentId: empMatch[1], agentRole: "employee" };
2101
2176
  }
@@ -266,12 +266,68 @@ var init_memory = __esm({
266
266
  }
267
267
  });
268
268
 
269
+ // src/lib/db-retry.ts
270
+ function isBusyError(err) {
271
+ if (err instanceof Error) {
272
+ const msg = err.message.toLowerCase();
273
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
274
+ }
275
+ return false;
276
+ }
277
+ function delay(ms) {
278
+ return new Promise((resolve) => setTimeout(resolve, ms));
279
+ }
280
+ async function retryOnBusy(fn, label) {
281
+ let lastError;
282
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
283
+ try {
284
+ return await fn();
285
+ } catch (err) {
286
+ lastError = err;
287
+ if (!isBusyError(err) || attempt === MAX_RETRIES) {
288
+ throw err;
289
+ }
290
+ const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
291
+ const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
292
+ process.stderr.write(
293
+ `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
294
+ `
295
+ );
296
+ await delay(backoff + jitter);
297
+ }
298
+ }
299
+ throw lastError;
300
+ }
301
+ function wrapWithRetry(client) {
302
+ return new Proxy(client, {
303
+ get(target, prop, receiver) {
304
+ if (prop === "execute") {
305
+ return (sql) => retryOnBusy(() => target.execute(sql), "execute");
306
+ }
307
+ if (prop === "batch") {
308
+ return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
309
+ }
310
+ return Reflect.get(target, prop, receiver);
311
+ }
312
+ });
313
+ }
314
+ var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
315
+ var init_db_retry = __esm({
316
+ "src/lib/db-retry.ts"() {
317
+ "use strict";
318
+ MAX_RETRIES = 3;
319
+ BASE_DELAY_MS = 200;
320
+ MAX_JITTER_MS = 300;
321
+ }
322
+ });
323
+
269
324
  // src/lib/database.ts
270
325
  import { createClient } from "@libsql/client";
271
326
  async function initDatabase(config) {
272
327
  if (_client) {
273
328
  _client.close();
274
329
  _client = null;
330
+ _resilientClient = null;
275
331
  }
276
332
  const opts = {
277
333
  url: `file:${config.dbPath}`
@@ -280,17 +336,24 @@ async function initDatabase(config) {
280
336
  opts.encryptionKey = config.encryptionKey;
281
337
  }
282
338
  _client = createClient(opts);
339
+ _resilientClient = wrapWithRetry(_client);
283
340
  }
284
341
  function getClient() {
342
+ if (!_resilientClient) {
343
+ throw new Error("Database client not initialized. Call initDatabase() first.");
344
+ }
345
+ return _resilientClient;
346
+ }
347
+ function getRawClient() {
285
348
  if (!_client) {
286
349
  throw new Error("Database client not initialized. Call initDatabase() first.");
287
350
  }
288
351
  return _client;
289
352
  }
290
353
  async function ensureSchema() {
291
- const client = getClient();
354
+ const client = getRawClient();
292
355
  await client.execute("PRAGMA journal_mode = WAL");
293
- await client.execute("PRAGMA busy_timeout = 5000");
356
+ await client.execute("PRAGMA busy_timeout = 30000");
294
357
  try {
295
358
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
296
359
  } catch {
@@ -1083,13 +1146,16 @@ async function disposeDatabase() {
1083
1146
  if (_client) {
1084
1147
  _client.close();
1085
1148
  _client = null;
1149
+ _resilientClient = null;
1086
1150
  }
1087
1151
  }
1088
- var _client, initTurso, disposeTurso;
1152
+ var _client, _resilientClient, initTurso, disposeTurso;
1089
1153
  var init_database = __esm({
1090
1154
  "src/lib/database.ts"() {
1091
1155
  "use strict";
1156
+ init_db_retry();
1092
1157
  _client = null;
1158
+ _resilientClient = null;
1093
1159
  initTurso = initDatabase;
1094
1160
  disposeTurso = disposeDatabase;
1095
1161
  }
@@ -1202,7 +1268,7 @@ function listShards() {
1202
1268
  }
1203
1269
  async function ensureShardSchema(client) {
1204
1270
  await client.execute("PRAGMA journal_mode = WAL");
1205
- await client.execute("PRAGMA busy_timeout = 5000");
1271
+ await client.execute("PRAGMA busy_timeout = 30000");
1206
1272
  try {
1207
1273
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
1208
1274
  } catch {
@@ -1463,7 +1529,8 @@ async function writeMemory(record) {
1463
1529
  has_error: record.has_error ? 1 : 0,
1464
1530
  raw_text: record.raw_text,
1465
1531
  vector: record.vector,
1466
- version: _nextVersion++,
1532
+ version: 0,
1533
+ // Placeholder — assigned atomically at flush time
1467
1534
  task_id: record.task_id ?? null,
1468
1535
  importance: record.importance ?? 5,
1469
1536
  status: record.status ?? "active",
@@ -1497,6 +1564,13 @@ async function flushBatch() {
1497
1564
  _flushing = true;
1498
1565
  try {
1499
1566
  const batch = _pendingRecords.slice(0);
1567
+ const client = getClient();
1568
+ const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
1569
+ let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
1570
+ for (const row of batch) {
1571
+ row.version = baseVersion++;
1572
+ }
1573
+ _nextVersion = baseVersion;
1500
1574
  const buildStmt = (row) => {
1501
1575
  const hasVector = row.vector !== null;
1502
1576
  const taskId = row.task_id ?? null;
@@ -2087,11 +2161,11 @@ async function connectEmbedDaemon() {
2087
2161
  }
2088
2162
  }
2089
2163
  const start = Date.now();
2090
- let delay = 100;
2164
+ let delay2 = 100;
2091
2165
  while (Date.now() - start < CONNECT_TIMEOUT_MS) {
2092
- await new Promise((r) => setTimeout(r, delay));
2166
+ await new Promise((r) => setTimeout(r, delay2));
2093
2167
  if (await connectToSocket()) return true;
2094
- delay = Math.min(delay * 2, 3e3);
2168
+ delay2 = Math.min(delay2 * 2, 3e3);
2095
2169
  }
2096
2170
  return false;
2097
2171
  }
@@ -2183,11 +2257,11 @@ async function embedViaClient(text, priority = "high") {
2183
2257
  `);
2184
2258
  killAndRespawnDaemon();
2185
2259
  const start = Date.now();
2186
- let delay = 200;
2260
+ let delay2 = 200;
2187
2261
  while (Date.now() - start < CONNECT_TIMEOUT_MS) {
2188
- await new Promise((r) => setTimeout(r, delay));
2262
+ await new Promise((r) => setTimeout(r, delay2));
2189
2263
  if (await connectToSocket()) break;
2190
- delay = Math.min(delay * 2, 3e3);
2264
+ delay2 = Math.min(delay2 * 2, 3e3);
2191
2265
  }
2192
2266
  if (!_connected) return null;
2193
2267
  }
@@ -2199,11 +2273,11 @@ async function embedViaClient(text, priority = "high") {
2199
2273
  `);
2200
2274
  killAndRespawnDaemon();
2201
2275
  const start = Date.now();
2202
- let delay = 200;
2276
+ let delay2 = 200;
2203
2277
  while (Date.now() - start < CONNECT_TIMEOUT_MS) {
2204
- await new Promise((r) => setTimeout(r, delay));
2278
+ await new Promise((r) => setTimeout(r, delay2));
2205
2279
  if (await connectToSocket()) break;
2206
- delay = Math.min(delay * 2, 3e3);
2280
+ delay2 = Math.min(delay2 * 2, 3e3);
2207
2281
  }
2208
2282
  if (!_connected) return null;
2209
2283
  const retry = await sendRequest([text], priority);
@@ -3135,7 +3209,7 @@ function getActiveAgent() {
3135
3209
  "tmux display-message -p '#{session_name}' 2>/dev/null",
3136
3210
  { encoding: "utf8", timeout: 2e3 }
3137
3211
  ).trim();
3138
- const empMatch = sessionName.match(/^(\w+)-exe\d+$/);
3212
+ const empMatch = sessionName.match(/^([a-zA-Z]+)\d*-exe\d+$/);
3139
3213
  if (empMatch && empMatch[1] !== "exe") {
3140
3214
  return { agentId: empMatch[1], agentRole: "employee" };
3141
3215
  }