@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
@@ -262,7 +262,7 @@ function listShards() {
262
262
  }
263
263
  async function ensureShardSchema(client) {
264
264
  await client.execute("PRAGMA journal_mode = WAL");
265
- await client.execute("PRAGMA busy_timeout = 5000");
265
+ await client.execute("PRAGMA busy_timeout = 30000");
266
266
  try {
267
267
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
268
268
  } catch {
@@ -449,7 +449,7 @@ var init_shard_manager = __esm({
449
449
  // src/lib/employees.ts
450
450
  init_config();
451
451
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
452
- import { existsSync as existsSync2, symlinkSync, readlinkSync } from "fs";
452
+ import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2 } from "fs";
453
453
  import { execSync } from "child_process";
454
454
  import path2 from "path";
455
455
  var EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
@@ -467,12 +467,65 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
467
467
 
468
468
  // src/lib/database.ts
469
469
  import { createClient } from "@libsql/client";
470
+
471
+ // src/lib/db-retry.ts
472
+ var MAX_RETRIES = 3;
473
+ var BASE_DELAY_MS = 200;
474
+ var MAX_JITTER_MS = 300;
475
+ function isBusyError(err) {
476
+ if (err instanceof Error) {
477
+ const msg = err.message.toLowerCase();
478
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
479
+ }
480
+ return false;
481
+ }
482
+ function delay(ms) {
483
+ return new Promise((resolve) => setTimeout(resolve, ms));
484
+ }
485
+ async function retryOnBusy(fn, label) {
486
+ let lastError;
487
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
488
+ try {
489
+ return await fn();
490
+ } catch (err) {
491
+ lastError = err;
492
+ if (!isBusyError(err) || attempt === MAX_RETRIES) {
493
+ throw err;
494
+ }
495
+ const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
496
+ const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
497
+ process.stderr.write(
498
+ `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
499
+ `
500
+ );
501
+ await delay(backoff + jitter);
502
+ }
503
+ }
504
+ throw lastError;
505
+ }
506
+ function wrapWithRetry(client) {
507
+ return new Proxy(client, {
508
+ get(target, prop, receiver) {
509
+ if (prop === "execute") {
510
+ return (sql) => retryOnBusy(() => target.execute(sql), "execute");
511
+ }
512
+ if (prop === "batch") {
513
+ return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
514
+ }
515
+ return Reflect.get(target, prop, receiver);
516
+ }
517
+ });
518
+ }
519
+
520
+ // src/lib/database.ts
470
521
  var _client = null;
522
+ var _resilientClient = null;
471
523
  var initTurso = initDatabase;
472
524
  async function initDatabase(config) {
473
525
  if (_client) {
474
526
  _client.close();
475
527
  _client = null;
528
+ _resilientClient = null;
476
529
  }
477
530
  const opts = {
478
531
  url: `file:${config.dbPath}`
@@ -481,17 +534,24 @@ async function initDatabase(config) {
481
534
  opts.encryptionKey = config.encryptionKey;
482
535
  }
483
536
  _client = createClient(opts);
537
+ _resilientClient = wrapWithRetry(_client);
484
538
  }
485
539
  function getClient() {
540
+ if (!_resilientClient) {
541
+ throw new Error("Database client not initialized. Call initDatabase() first.");
542
+ }
543
+ return _resilientClient;
544
+ }
545
+ function getRawClient() {
486
546
  if (!_client) {
487
547
  throw new Error("Database client not initialized. Call initDatabase() first.");
488
548
  }
489
549
  return _client;
490
550
  }
491
551
  async function ensureSchema() {
492
- const client = getClient();
552
+ const client = getRawClient();
493
553
  await client.execute("PRAGMA journal_mode = WAL");
494
- await client.execute("PRAGMA busy_timeout = 5000");
554
+ await client.execute("PRAGMA busy_timeout = 30000");
495
555
  try {
496
556
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
497
557
  } catch {
@@ -262,7 +262,7 @@ function listShards() {
262
262
  }
263
263
  async function ensureShardSchema(client) {
264
264
  await client.execute("PRAGMA journal_mode = WAL");
265
- await client.execute("PRAGMA busy_timeout = 5000");
265
+ await client.execute("PRAGMA busy_timeout = 30000");
266
266
  try {
267
267
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
268
268
  } catch {
@@ -449,7 +449,7 @@ var init_shard_manager = __esm({
449
449
  // src/lib/employees.ts
450
450
  init_config();
451
451
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
452
- import { existsSync as existsSync2, symlinkSync, readlinkSync } from "fs";
452
+ import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2 } from "fs";
453
453
  import { execSync } from "child_process";
454
454
  import path2 from "path";
455
455
  var EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
@@ -467,12 +467,65 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
467
467
 
468
468
  // src/lib/database.ts
469
469
  import { createClient } from "@libsql/client";
470
+
471
+ // src/lib/db-retry.ts
472
+ var MAX_RETRIES = 3;
473
+ var BASE_DELAY_MS = 200;
474
+ var MAX_JITTER_MS = 300;
475
+ function isBusyError(err) {
476
+ if (err instanceof Error) {
477
+ const msg = err.message.toLowerCase();
478
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
479
+ }
480
+ return false;
481
+ }
482
+ function delay(ms) {
483
+ return new Promise((resolve) => setTimeout(resolve, ms));
484
+ }
485
+ async function retryOnBusy(fn, label) {
486
+ let lastError;
487
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
488
+ try {
489
+ return await fn();
490
+ } catch (err) {
491
+ lastError = err;
492
+ if (!isBusyError(err) || attempt === MAX_RETRIES) {
493
+ throw err;
494
+ }
495
+ const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
496
+ const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
497
+ process.stderr.write(
498
+ `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
499
+ `
500
+ );
501
+ await delay(backoff + jitter);
502
+ }
503
+ }
504
+ throw lastError;
505
+ }
506
+ function wrapWithRetry(client) {
507
+ return new Proxy(client, {
508
+ get(target, prop, receiver) {
509
+ if (prop === "execute") {
510
+ return (sql) => retryOnBusy(() => target.execute(sql), "execute");
511
+ }
512
+ if (prop === "batch") {
513
+ return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
514
+ }
515
+ return Reflect.get(target, prop, receiver);
516
+ }
517
+ });
518
+ }
519
+
520
+ // src/lib/database.ts
470
521
  var _client = null;
522
+ var _resilientClient = null;
471
523
  var initTurso = initDatabase;
472
524
  async function initDatabase(config) {
473
525
  if (_client) {
474
526
  _client.close();
475
527
  _client = null;
528
+ _resilientClient = null;
476
529
  }
477
530
  const opts = {
478
531
  url: `file:${config.dbPath}`
@@ -481,17 +534,24 @@ async function initDatabase(config) {
481
534
  opts.encryptionKey = config.encryptionKey;
482
535
  }
483
536
  _client = createClient(opts);
537
+ _resilientClient = wrapWithRetry(_client);
484
538
  }
485
539
  function getClient() {
540
+ if (!_resilientClient) {
541
+ throw new Error("Database client not initialized. Call initDatabase() first.");
542
+ }
543
+ return _resilientClient;
544
+ }
545
+ function getRawClient() {
486
546
  if (!_client) {
487
547
  throw new Error("Database client not initialized. Call initDatabase() first.");
488
548
  }
489
549
  return _client;
490
550
  }
491
551
  async function ensureSchema() {
492
- const client = getClient();
552
+ const client = getRawClient();
493
553
  await client.execute("PRAGMA journal_mode = WAL");
494
- await client.execute("PRAGMA busy_timeout = 5000");
554
+ await client.execute("PRAGMA busy_timeout = 30000");
495
555
  try {
496
556
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
497
557
  } catch {
@@ -24,6 +24,61 @@ var init_memory = __esm({
24
24
  }
25
25
  });
26
26
 
27
+ // src/lib/db-retry.ts
28
+ function isBusyError(err) {
29
+ if (err instanceof Error) {
30
+ const msg = err.message.toLowerCase();
31
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
32
+ }
33
+ return false;
34
+ }
35
+ function delay(ms) {
36
+ return new Promise((resolve) => setTimeout(resolve, ms));
37
+ }
38
+ async function retryOnBusy(fn, label) {
39
+ let lastError;
40
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
41
+ try {
42
+ return await fn();
43
+ } catch (err) {
44
+ lastError = err;
45
+ if (!isBusyError(err) || attempt === MAX_RETRIES) {
46
+ throw err;
47
+ }
48
+ const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
49
+ const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
50
+ process.stderr.write(
51
+ `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
52
+ `
53
+ );
54
+ await delay(backoff + jitter);
55
+ }
56
+ }
57
+ throw lastError;
58
+ }
59
+ function wrapWithRetry(client) {
60
+ return new Proxy(client, {
61
+ get(target, prop, receiver) {
62
+ if (prop === "execute") {
63
+ return (sql) => retryOnBusy(() => target.execute(sql), "execute");
64
+ }
65
+ if (prop === "batch") {
66
+ return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
67
+ }
68
+ return Reflect.get(target, prop, receiver);
69
+ }
70
+ });
71
+ }
72
+ var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
73
+ var init_db_retry = __esm({
74
+ "src/lib/db-retry.ts"() {
75
+ "use strict";
76
+ MAX_RETRIES = 3;
77
+ BASE_DELAY_MS = 200;
78
+ MAX_JITTER_MS = 300;
79
+ }
80
+ });
81
+
27
82
  // src/lib/database.ts
28
83
  var database_exports = {};
29
84
  __export(database_exports, {
@@ -31,6 +86,7 @@ __export(database_exports, {
31
86
  disposeTurso: () => disposeTurso,
32
87
  ensureSchema: () => ensureSchema,
33
88
  getClient: () => getClient,
89
+ getRawClient: () => getRawClient,
34
90
  initDatabase: () => initDatabase,
35
91
  initTurso: () => initTurso,
36
92
  isInitialized: () => isInitialized
@@ -40,6 +96,7 @@ async function initDatabase(config) {
40
96
  if (_client) {
41
97
  _client.close();
42
98
  _client = null;
99
+ _resilientClient = null;
43
100
  }
44
101
  const opts = {
45
102
  url: `file:${config.dbPath}`
@@ -48,20 +105,27 @@ async function initDatabase(config) {
48
105
  opts.encryptionKey = config.encryptionKey;
49
106
  }
50
107
  _client = createClient(opts);
108
+ _resilientClient = wrapWithRetry(_client);
51
109
  }
52
110
  function isInitialized() {
53
111
  return _client !== null;
54
112
  }
55
113
  function getClient() {
114
+ if (!_resilientClient) {
115
+ throw new Error("Database client not initialized. Call initDatabase() first.");
116
+ }
117
+ return _resilientClient;
118
+ }
119
+ function getRawClient() {
56
120
  if (!_client) {
57
121
  throw new Error("Database client not initialized. Call initDatabase() first.");
58
122
  }
59
123
  return _client;
60
124
  }
61
125
  async function ensureSchema() {
62
- const client = getClient();
126
+ const client = getRawClient();
63
127
  await client.execute("PRAGMA journal_mode = WAL");
64
- await client.execute("PRAGMA busy_timeout = 5000");
128
+ await client.execute("PRAGMA busy_timeout = 30000");
65
129
  try {
66
130
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
67
131
  } catch {
@@ -854,13 +918,16 @@ async function disposeDatabase() {
854
918
  if (_client) {
855
919
  _client.close();
856
920
  _client = null;
921
+ _resilientClient = null;
857
922
  }
858
923
  }
859
- var _client, initTurso, disposeTurso;
924
+ var _client, _resilientClient, initTurso, disposeTurso;
860
925
  var init_database = __esm({
861
926
  "src/lib/database.ts"() {
862
927
  "use strict";
928
+ init_db_retry();
863
929
  _client = null;
930
+ _resilientClient = null;
864
931
  initTurso = initDatabase;
865
932
  disposeTurso = disposeDatabase;
866
933
  }
@@ -1162,7 +1229,7 @@ function listShards() {
1162
1229
  }
1163
1230
  async function ensureShardSchema(client) {
1164
1231
  await client.execute("PRAGMA journal_mode = WAL");
1165
- await client.execute("PRAGMA busy_timeout = 5000");
1232
+ await client.execute("PRAGMA busy_timeout = 30000");
1166
1233
  try {
1167
1234
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
1168
1235
  } catch {
@@ -1423,7 +1490,8 @@ async function writeMemory(record) {
1423
1490
  has_error: record.has_error ? 1 : 0,
1424
1491
  raw_text: record.raw_text,
1425
1492
  vector: record.vector,
1426
- version: _nextVersion++,
1493
+ version: 0,
1494
+ // Placeholder — assigned atomically at flush time
1427
1495
  task_id: record.task_id ?? null,
1428
1496
  importance: record.importance ?? 5,
1429
1497
  status: record.status ?? "active",
@@ -1457,6 +1525,13 @@ async function flushBatch() {
1457
1525
  _flushing = true;
1458
1526
  try {
1459
1527
  const batch = _pendingRecords.slice(0);
1528
+ const client = getClient();
1529
+ const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
1530
+ let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
1531
+ for (const row of batch) {
1532
+ row.version = baseVersion++;
1533
+ }
1534
+ _nextVersion = baseVersion;
1460
1535
  const buildStmt = (row) => {
1461
1536
  const hasVector = row.vector !== null;
1462
1537
  const taskId = row.task_id ?? null;
@@ -262,7 +262,7 @@ function listShards() {
262
262
  }
263
263
  async function ensureShardSchema(client) {
264
264
  await client.execute("PRAGMA journal_mode = WAL");
265
- await client.execute("PRAGMA busy_timeout = 5000");
265
+ await client.execute("PRAGMA busy_timeout = 30000");
266
266
  try {
267
267
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
268
268
  } catch {
@@ -448,12 +448,65 @@ var init_shard_manager = __esm({
448
448
 
449
449
  // src/lib/database.ts
450
450
  import { createClient } from "@libsql/client";
451
+
452
+ // src/lib/db-retry.ts
453
+ var MAX_RETRIES = 3;
454
+ var BASE_DELAY_MS = 200;
455
+ var MAX_JITTER_MS = 300;
456
+ function isBusyError(err) {
457
+ if (err instanceof Error) {
458
+ const msg = err.message.toLowerCase();
459
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
460
+ }
461
+ return false;
462
+ }
463
+ function delay(ms) {
464
+ return new Promise((resolve) => setTimeout(resolve, ms));
465
+ }
466
+ async function retryOnBusy(fn, label) {
467
+ let lastError;
468
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
469
+ try {
470
+ return await fn();
471
+ } catch (err) {
472
+ lastError = err;
473
+ if (!isBusyError(err) || attempt === MAX_RETRIES) {
474
+ throw err;
475
+ }
476
+ const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
477
+ const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
478
+ process.stderr.write(
479
+ `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
480
+ `
481
+ );
482
+ await delay(backoff + jitter);
483
+ }
484
+ }
485
+ throw lastError;
486
+ }
487
+ function wrapWithRetry(client) {
488
+ return new Proxy(client, {
489
+ get(target, prop, receiver) {
490
+ if (prop === "execute") {
491
+ return (sql) => retryOnBusy(() => target.execute(sql), "execute");
492
+ }
493
+ if (prop === "batch") {
494
+ return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
495
+ }
496
+ return Reflect.get(target, prop, receiver);
497
+ }
498
+ });
499
+ }
500
+
501
+ // src/lib/database.ts
451
502
  var _client = null;
503
+ var _resilientClient = null;
452
504
  var initTurso = initDatabase;
453
505
  async function initDatabase(config) {
454
506
  if (_client) {
455
507
  _client.close();
456
508
  _client = null;
509
+ _resilientClient = null;
457
510
  }
458
511
  const opts = {
459
512
  url: `file:${config.dbPath}`
@@ -462,17 +515,24 @@ async function initDatabase(config) {
462
515
  opts.encryptionKey = config.encryptionKey;
463
516
  }
464
517
  _client = createClient(opts);
518
+ _resilientClient = wrapWithRetry(_client);
465
519
  }
466
520
  function getClient() {
521
+ if (!_resilientClient) {
522
+ throw new Error("Database client not initialized. Call initDatabase() first.");
523
+ }
524
+ return _resilientClient;
525
+ }
526
+ function getRawClient() {
467
527
  if (!_client) {
468
528
  throw new Error("Database client not initialized. Call initDatabase() first.");
469
529
  }
470
530
  return _client;
471
531
  }
472
532
  async function ensureSchema() {
473
- const client = getClient();
533
+ const client = getRawClient();
474
534
  await client.execute("PRAGMA journal_mode = WAL");
475
- await client.execute("PRAGMA busy_timeout = 5000");
535
+ await client.execute("PRAGMA busy_timeout = 30000");
476
536
  try {
477
537
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
478
538
  } catch {
@@ -1266,6 +1326,7 @@ async function disposeDatabase() {
1266
1326
  if (_client) {
1267
1327
  _client.close();
1268
1328
  _client = null;
1329
+ _resilientClient = null;
1269
1330
  }
1270
1331
  }
1271
1332
 
@@ -1363,6 +1424,13 @@ async function flushBatch() {
1363
1424
  _flushing = true;
1364
1425
  try {
1365
1426
  const batch = _pendingRecords.slice(0);
1427
+ const client = getClient();
1428
+ const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
1429
+ let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
1430
+ for (const row of batch) {
1431
+ row.version = baseVersion++;
1432
+ }
1433
+ _nextVersion = baseVersion;
1366
1434
  const buildStmt = (row) => {
1367
1435
  const hasVector = row.vector !== null;
1368
1436
  const taskId = row.task_id ?? null;
@@ -262,7 +262,7 @@ function listShards() {
262
262
  }
263
263
  async function ensureShardSchema(client) {
264
264
  await client.execute("PRAGMA journal_mode = WAL");
265
- await client.execute("PRAGMA busy_timeout = 5000");
265
+ await client.execute("PRAGMA busy_timeout = 30000");
266
266
  try {
267
267
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
268
268
  } catch {
@@ -452,12 +452,65 @@ import path4 from "path";
452
452
 
453
453
  // src/lib/database.ts
454
454
  import { createClient } from "@libsql/client";
455
+
456
+ // src/lib/db-retry.ts
457
+ var MAX_RETRIES = 3;
458
+ var BASE_DELAY_MS = 200;
459
+ var MAX_JITTER_MS = 300;
460
+ function isBusyError(err) {
461
+ if (err instanceof Error) {
462
+ const msg = err.message.toLowerCase();
463
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
464
+ }
465
+ return false;
466
+ }
467
+ function delay(ms) {
468
+ return new Promise((resolve) => setTimeout(resolve, ms));
469
+ }
470
+ async function retryOnBusy(fn, label) {
471
+ let lastError;
472
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
473
+ try {
474
+ return await fn();
475
+ } catch (err) {
476
+ lastError = err;
477
+ if (!isBusyError(err) || attempt === MAX_RETRIES) {
478
+ throw err;
479
+ }
480
+ const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
481
+ const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
482
+ process.stderr.write(
483
+ `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
484
+ `
485
+ );
486
+ await delay(backoff + jitter);
487
+ }
488
+ }
489
+ throw lastError;
490
+ }
491
+ function wrapWithRetry(client) {
492
+ return new Proxy(client, {
493
+ get(target, prop, receiver) {
494
+ if (prop === "execute") {
495
+ return (sql) => retryOnBusy(() => target.execute(sql), "execute");
496
+ }
497
+ if (prop === "batch") {
498
+ return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
499
+ }
500
+ return Reflect.get(target, prop, receiver);
501
+ }
502
+ });
503
+ }
504
+
505
+ // src/lib/database.ts
455
506
  var _client = null;
507
+ var _resilientClient = null;
456
508
  var initTurso = initDatabase;
457
509
  async function initDatabase(config) {
458
510
  if (_client) {
459
511
  _client.close();
460
512
  _client = null;
513
+ _resilientClient = null;
461
514
  }
462
515
  const opts = {
463
516
  url: `file:${config.dbPath}`
@@ -466,17 +519,24 @@ async function initDatabase(config) {
466
519
  opts.encryptionKey = config.encryptionKey;
467
520
  }
468
521
  _client = createClient(opts);
522
+ _resilientClient = wrapWithRetry(_client);
469
523
  }
470
524
  function getClient() {
525
+ if (!_resilientClient) {
526
+ throw new Error("Database client not initialized. Call initDatabase() first.");
527
+ }
528
+ return _resilientClient;
529
+ }
530
+ function getRawClient() {
471
531
  if (!_client) {
472
532
  throw new Error("Database client not initialized. Call initDatabase() first.");
473
533
  }
474
534
  return _client;
475
535
  }
476
536
  async function ensureSchema() {
477
- const client = getClient();
537
+ const client = getRawClient();
478
538
  await client.execute("PRAGMA journal_mode = WAL");
479
- await client.execute("PRAGMA busy_timeout = 5000");
539
+ await client.execute("PRAGMA busy_timeout = 30000");
480
540
  try {
481
541
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
482
542
  } catch {
@@ -1270,6 +1330,7 @@ async function disposeDatabase() {
1270
1330
  if (_client) {
1271
1331
  _client.close();
1272
1332
  _client = null;
1333
+ _resilientClient = null;
1273
1334
  }
1274
1335
  }
1275
1336
 
@@ -1367,6 +1428,13 @@ async function flushBatch() {
1367
1428
  _flushing = true;
1368
1429
  try {
1369
1430
  const batch = _pendingRecords.slice(0);
1431
+ const client = getClient();
1432
+ const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
1433
+ let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
1434
+ for (const row of batch) {
1435
+ row.version = baseVersion++;
1436
+ }
1437
+ _nextVersion = baseVersion;
1370
1438
  const buildStmt = (row) => {
1371
1439
  const hasVector = row.vector !== null;
1372
1440
  const taskId = row.task_id ?? null;