@askexenow/exe-os 0.8.33 → 0.8.36

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 (86) hide show
  1. package/dist/bin/backfill-conversations.js +332 -348
  2. package/dist/bin/backfill-responses.js +72 -12
  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 +1491 -1095
  6. package/dist/bin/exe-assign.js +80 -18
  7. package/dist/bin/exe-boot.js +407 -88
  8. package/dist/bin/exe-call.js +61 -2
  9. package/dist/bin/exe-dispatch.js +18 -10
  10. package/dist/bin/exe-doctor.js +63 -3
  11. package/dist/bin/exe-export-behaviors.js +64 -3
  12. package/dist/bin/exe-forget.js +69 -4
  13. package/dist/bin/exe-gateway.js +121 -36
  14. package/dist/bin/exe-heartbeat.js +77 -13
  15. package/dist/bin/exe-kill.js +64 -3
  16. package/dist/bin/exe-launch-agent.js +140 -13
  17. package/dist/bin/exe-link.js +946 -0
  18. package/dist/bin/exe-new-employee.js +98 -13
  19. package/dist/bin/exe-pending-messages.js +72 -7
  20. package/dist/bin/exe-pending-notifications.js +63 -3
  21. package/dist/bin/exe-pending-reviews.js +75 -10
  22. package/dist/bin/exe-rename.js +1287 -0
  23. package/dist/bin/exe-review.js +64 -4
  24. package/dist/bin/exe-search.js +79 -13
  25. package/dist/bin/exe-session-cleanup.js +91 -26
  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 +71 -4
  29. package/dist/bin/graph-backfill.js +64 -3
  30. package/dist/bin/graph-export.js +64 -3
  31. package/dist/bin/install.js +3 -3
  32. package/dist/bin/scan-tasks.js +71 -4
  33. package/dist/bin/setup.js +128 -10
  34. package/dist/bin/shard-migrate.js +64 -3
  35. package/dist/bin/wiki-sync.js +64 -3
  36. package/dist/gateway/index.js +122 -37
  37. package/dist/hooks/bug-report-worker.js +209 -23
  38. package/dist/hooks/commit-complete.js +71 -4
  39. package/dist/hooks/error-recall.js +79 -13
  40. package/dist/hooks/ingest-worker.js +129 -43
  41. package/dist/hooks/instructions-loaded.js +71 -4
  42. package/dist/hooks/notification.js +71 -4
  43. package/dist/hooks/post-compact.js +71 -4
  44. package/dist/hooks/pre-compact.js +71 -4
  45. package/dist/hooks/pre-tool-use.js +413 -194
  46. package/dist/hooks/prompt-ingest-worker.js +82 -22
  47. package/dist/hooks/prompt-submit.js +103 -37
  48. package/dist/hooks/response-ingest-worker.js +87 -22
  49. package/dist/hooks/session-end.js +71 -4
  50. package/dist/hooks/session-start.js +79 -13
  51. package/dist/hooks/stop.js +71 -4
  52. package/dist/hooks/subagent-stop.js +71 -4
  53. package/dist/hooks/summary-worker.js +303 -50
  54. package/dist/index.js +134 -46
  55. package/dist/lib/cloud-sync.js +209 -15
  56. package/dist/lib/consolidation.js +4 -4
  57. package/dist/lib/database.js +64 -2
  58. package/dist/lib/device-registry.js +70 -3
  59. package/dist/lib/employee-templates.js +26 -0
  60. package/dist/lib/employees.js +34 -1
  61. package/dist/lib/exe-daemon.js +136 -53
  62. package/dist/lib/hybrid-search.js +79 -13
  63. package/dist/lib/identity-templates.js +51 -0
  64. package/dist/lib/identity.js +3 -3
  65. package/dist/lib/messaging.js +22 -14
  66. package/dist/lib/reminders.js +3 -3
  67. package/dist/lib/schedules.js +63 -3
  68. package/dist/lib/skill-learning.js +3 -3
  69. package/dist/lib/status-brief.js +63 -5
  70. package/dist/lib/store.js +64 -3
  71. package/dist/lib/task-router.js +4 -2
  72. package/dist/lib/tasks.js +48 -21
  73. package/dist/lib/tmux-routing.js +47 -20
  74. package/dist/mcp/server.js +727 -58
  75. package/dist/mcp/tools/complete-reminder.js +3 -3
  76. package/dist/mcp/tools/create-reminder.js +3 -3
  77. package/dist/mcp/tools/create-task.js +151 -24
  78. package/dist/mcp/tools/deactivate-behavior.js +3 -3
  79. package/dist/mcp/tools/list-reminders.js +3 -3
  80. package/dist/mcp/tools/list-tasks.js +17 -8
  81. package/dist/mcp/tools/send-message.js +24 -16
  82. package/dist/mcp/tools/update-task.js +25 -16
  83. package/dist/runtime/index.js +112 -24
  84. package/dist/tui/App.js +139 -36
  85. package/package.json +6 -2
  86. package/src/commands/exe/rename.md +12 -0
@@ -112,13 +112,18 @@ __export(employees_exports, {
112
112
  EMPLOYEES_PATH: () => EMPLOYEES_PATH,
113
113
  addEmployee: () => addEmployee,
114
114
  getEmployee: () => getEmployee,
115
+ getEmployeeByRole: () => getEmployeeByRole,
116
+ getEmployeeNamesByRole: () => getEmployeeNamesByRole,
117
+ hasRole: () => hasRole,
118
+ isMultiInstance: () => isMultiInstance,
115
119
  loadEmployees: () => loadEmployees,
120
+ loadEmployeesSync: () => loadEmployeesSync,
116
121
  registerBinSymlinks: () => registerBinSymlinks,
117
122
  saveEmployees: () => saveEmployees,
118
123
  validateEmployeeName: () => validateEmployeeName
119
124
  });
120
125
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
121
- import { existsSync as existsSync2, symlinkSync, readlinkSync } from "fs";
126
+ import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2 } from "fs";
122
127
  import { execSync } from "child_process";
123
128
  import path2 from "path";
124
129
  function validateEmployeeName(name) {
@@ -151,9 +156,36 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
151
156
  await mkdir2(path2.dirname(employeesPath), { recursive: true });
152
157
  await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
153
158
  }
159
+ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
160
+ if (!existsSync2(employeesPath)) return [];
161
+ try {
162
+ return JSON.parse(readFileSync2(employeesPath, "utf-8"));
163
+ } catch {
164
+ return [];
165
+ }
166
+ }
154
167
  function getEmployee(employees, name) {
155
168
  return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
156
169
  }
170
+ function getEmployeeByRole(employees, role) {
171
+ const lower = role.toLowerCase();
172
+ return employees.find((e) => e.role.toLowerCase() === lower);
173
+ }
174
+ function getEmployeeNamesByRole(employees, role) {
175
+ const lower = role.toLowerCase();
176
+ return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
177
+ }
178
+ function hasRole(agentName, role) {
179
+ const employees = loadEmployeesSync();
180
+ const emp = getEmployee(employees, agentName);
181
+ return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
182
+ }
183
+ function isMultiInstance(agentName, employees) {
184
+ const roster = employees ?? loadEmployeesSync();
185
+ const emp = getEmployee(roster, agentName);
186
+ if (!emp) return false;
187
+ return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
188
+ }
157
189
  function addEmployee(employees, employee) {
158
190
  const normalized = { ...employee, name: employee.name.toLowerCase() };
159
191
  if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
@@ -196,12 +228,13 @@ function registerBinSymlinks(name) {
196
228
  }
197
229
  return { created, skipped, errors };
198
230
  }
199
- var EMPLOYEES_PATH;
231
+ var EMPLOYEES_PATH, MULTI_INSTANCE_ROLES;
200
232
  var init_employees = __esm({
201
233
  "src/lib/employees.ts"() {
202
234
  "use strict";
203
235
  init_config();
204
236
  EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
237
+ MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
205
238
  }
206
239
  });
207
240
 
@@ -686,6 +719,32 @@ When you analyze a repo:
686
719
  Every analysis must answer: "Should we build this? If yes, how hard? If no, why not?"
687
720
 
688
721
  Maintain a clear separation between experimental (for evaluation) and production-ready (for shipping). Never recommend something you haven't read the source code for.`
722
+ },
723
+ bob: {
724
+ name: "bob",
725
+ role: "Staff Code Reviewer",
726
+ systemPrompt: `You are bob, the Staff Code Reviewer and System Auditor. You are the last line of defense before code ships to customers. You catch what developers miss \u2014 not just code bugs, but systemic patterns that make entire feature categories break. You report to the COO.
727
+
728
+ Your core job: audit code, find bugs, verify fixes, and ensure customer-readiness. Every audit answers: "Would this break for a customer who customized their setup?"
729
+
730
+ The 7 Audit Patterns (MANDATORY \u2014 apply to EVERY audit):
731
+ 1. "Works on dev, breaks on user install" \u2014 verify scoped paths, npm resolution, dependencies
732
+ 2. "Two code paths, one untested" \u2014 binary symlink vs /exe-call, CLI vs MCP \u2014 verify BOTH
733
+ 3. "Case sensitivity kills non-technical users" \u2014 normalize all user inputs
734
+ 4. "Hardcoded names leak into user-facing content" \u2014 grep for employee names in runtime logic
735
+ 5. "Installer doesn't self-heal on update" \u2014 npm update must auto-fix stale hooks/paths
736
+ 6. "Data written but invisible to the agent" \u2014 verify query path retrieves stored data
737
+ 7. "Partial fixes that miss inline references" \u2014 before/after grep count is mandatory
738
+
739
+ Audit method:
740
+ 1. Read the actual source code \u2014 not summaries
741
+ 2. Send to Codex MCP for initial sweep
742
+ 3. Validate against ARCHITECTURE.md
743
+ 4. Trace full identity chain with a CUSTOM-NAMED employee (e.g., "jarvis" as CTO)
744
+ 5. Count matches before and after any claimed fix
745
+ 6. Write structured report with PASS/FAIL per item
746
+
747
+ After an audit, fix the findings yourself if you can. Don't hand off when you have the context.`
689
748
  }
690
749
  };
691
750
  CLIENT_COO_TEMPLATE = `---
@@ -318,11 +318,19 @@ var init_intercom_queue = __esm({
318
318
  }
319
319
  });
320
320
 
321
+ // src/lib/db-retry.ts
322
+ var init_db_retry = __esm({
323
+ "src/lib/db-retry.ts"() {
324
+ "use strict";
325
+ }
326
+ });
327
+
321
328
  // src/lib/database.ts
322
329
  import { createClient } from "@libsql/client";
323
330
  var init_database = __esm({
324
331
  "src/lib/database.ts"() {
325
332
  "use strict";
333
+ init_db_retry();
326
334
  }
327
335
  });
328
336
 
@@ -425,7 +433,7 @@ var init_config = __esm({
425
433
 
426
434
  // src/lib/employees.ts
427
435
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
428
- import { existsSync as existsSync4, symlinkSync, readlinkSync } from "fs";
436
+ import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync4 } from "fs";
429
437
  import { execSync as execSync3 } from "child_process";
430
438
  import path4 from "path";
431
439
  var EMPLOYEES_PATH;
@@ -438,7 +446,7 @@ var init_employees = __esm({
438
446
  });
439
447
 
440
448
  // src/lib/license.ts
441
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
449
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
442
450
  import { randomUUID } from "crypto";
443
451
  import path5 from "path";
444
452
  import { jwtVerify, importSPKI } from "jose";
@@ -461,12 +469,12 @@ var init_license = __esm({
461
469
  });
462
470
 
463
471
  // src/lib/plan-limits.ts
464
- import { readFileSync as readFileSync5, existsSync as existsSync6 } from "fs";
472
+ import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
465
473
  import path6 from "path";
466
474
  function getLicenseSync() {
467
475
  try {
468
476
  if (!existsSync6(CACHE_PATH2)) return freeLicense();
469
- const raw = JSON.parse(readFileSync5(CACHE_PATH2, "utf8"));
477
+ const raw = JSON.parse(readFileSync6(CACHE_PATH2, "utf8"));
470
478
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
471
479
  const parts = raw.token.split(".");
472
480
  if (parts.length !== 3) return freeLicense();
@@ -505,7 +513,7 @@ function assertEmployeeLimitSync(rosterPath) {
505
513
  let count = 0;
506
514
  try {
507
515
  if (existsSync6(filePath)) {
508
- const raw = readFileSync5(filePath, "utf8");
516
+ const raw = readFileSync6(filePath, "utf8");
509
517
  const employees = JSON.parse(raw);
510
518
  count = Array.isArray(employees) ? employees.length : 0;
511
519
  }
@@ -540,7 +548,7 @@ var init_plan_limits = __esm({
540
548
 
541
549
  // src/lib/tmux-routing.ts
542
550
  import { execFileSync as execFileSync2, execSync as execSync4 } from "child_process";
543
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync7, appendFileSync } from "fs";
551
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync7, appendFileSync } from "fs";
544
552
  import path7 from "path";
545
553
  import os4 from "os";
546
554
  import { fileURLToPath } from "url";
@@ -589,7 +597,7 @@ function extractRootExe(name) {
589
597
  }
590
598
  function getParentExe(sessionKey) {
591
599
  try {
592
- const data = JSON.parse(readFileSync6(path7.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
600
+ const data = JSON.parse(readFileSync7(path7.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
593
601
  return data.parentExe || null;
594
602
  } catch {
595
603
  return null;
@@ -623,7 +631,7 @@ function findFreeInstance(employeeName2, exeSession2, maxInstances = 10, isAlive
623
631
  function readDebounceState() {
624
632
  try {
625
633
  if (!existsSync7(DEBOUNCE_FILE)) return {};
626
- return JSON.parse(readFileSync6(DEBOUNCE_FILE, "utf8"));
634
+ return JSON.parse(readFileSync7(DEBOUNCE_FILE, "utf8"));
627
635
  } catch {
628
636
  return {};
629
637
  }
@@ -797,7 +805,7 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
797
805
  const claudeJsonPath = path7.join(os4.homedir(), ".claude.json");
798
806
  let claudeJson = {};
799
807
  try {
800
- claudeJson = JSON.parse(readFileSync6(claudeJsonPath, "utf8"));
808
+ claudeJson = JSON.parse(readFileSync7(claudeJsonPath, "utf8"));
801
809
  } catch {
802
810
  }
803
811
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -815,7 +823,7 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
815
823
  const settingsPath = path7.join(projSettingsDir, "settings.json");
816
824
  let settings = {};
817
825
  try {
818
- settings = JSON.parse(readFileSync6(settingsPath, "utf8"));
826
+ settings = JSON.parse(readFileSync7(settingsPath, "utf8"));
819
827
  } catch {
820
828
  }
821
829
  const perms = settings.permissions ?? {};
@@ -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 {
@@ -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
 
@@ -22,12 +22,68 @@ var init_memory = __esm({
22
22
  }
23
23
  });
24
24
 
25
+ // src/lib/db-retry.ts
26
+ function isBusyError(err) {
27
+ if (err instanceof Error) {
28
+ const msg = err.message.toLowerCase();
29
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
30
+ }
31
+ return false;
32
+ }
33
+ function delay(ms) {
34
+ return new Promise((resolve) => setTimeout(resolve, ms));
35
+ }
36
+ async function retryOnBusy(fn, label) {
37
+ let lastError;
38
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
39
+ try {
40
+ return await fn();
41
+ } catch (err) {
42
+ lastError = err;
43
+ if (!isBusyError(err) || attempt === MAX_RETRIES) {
44
+ throw err;
45
+ }
46
+ const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
47
+ const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
48
+ process.stderr.write(
49
+ `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
50
+ `
51
+ );
52
+ await delay(backoff + jitter);
53
+ }
54
+ }
55
+ throw lastError;
56
+ }
57
+ function wrapWithRetry(client) {
58
+ return new Proxy(client, {
59
+ get(target, prop, receiver) {
60
+ if (prop === "execute") {
61
+ return (sql) => retryOnBusy(() => target.execute(sql), "execute");
62
+ }
63
+ if (prop === "batch") {
64
+ return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
65
+ }
66
+ return Reflect.get(target, prop, receiver);
67
+ }
68
+ });
69
+ }
70
+ var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
71
+ var init_db_retry = __esm({
72
+ "src/lib/db-retry.ts"() {
73
+ "use strict";
74
+ MAX_RETRIES = 3;
75
+ BASE_DELAY_MS = 200;
76
+ MAX_JITTER_MS = 300;
77
+ }
78
+ });
79
+
25
80
  // src/lib/database.ts
26
81
  import { createClient } from "@libsql/client";
27
82
  async function initDatabase(config) {
28
83
  if (_client) {
29
84
  _client.close();
30
85
  _client = null;
86
+ _resilientClient = null;
31
87
  }
32
88
  const opts = {
33
89
  url: `file:${config.dbPath}`
@@ -36,17 +92,24 @@ async function initDatabase(config) {
36
92
  opts.encryptionKey = config.encryptionKey;
37
93
  }
38
94
  _client = createClient(opts);
95
+ _resilientClient = wrapWithRetry(_client);
39
96
  }
40
97
  function getClient() {
98
+ if (!_resilientClient) {
99
+ throw new Error("Database client not initialized. Call initDatabase() first.");
100
+ }
101
+ return _resilientClient;
102
+ }
103
+ function getRawClient() {
41
104
  if (!_client) {
42
105
  throw new Error("Database client not initialized. Call initDatabase() first.");
43
106
  }
44
107
  return _client;
45
108
  }
46
109
  async function ensureSchema() {
47
- const client = getClient();
110
+ const client = getRawClient();
48
111
  await client.execute("PRAGMA journal_mode = WAL");
49
- await client.execute("PRAGMA busy_timeout = 5000");
112
+ await client.execute("PRAGMA busy_timeout = 30000");
50
113
  try {
51
114
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
52
115
  } catch {
@@ -835,11 +898,13 @@ async function ensureSchema() {
835
898
  }
836
899
  }
837
900
  }
838
- var _client, initTurso;
901
+ var _client, _resilientClient, initTurso;
839
902
  var init_database = __esm({
840
903
  "src/lib/database.ts"() {
841
904
  "use strict";
905
+ init_db_retry();
842
906
  _client = null;
907
+ _resilientClient = null;
843
908
  initTurso = initDatabase;
844
909
  }
845
910
  });
@@ -1140,7 +1205,7 @@ function listShards() {
1140
1205
  }
1141
1206
  async function ensureShardSchema(client) {
1142
1207
  await client.execute("PRAGMA journal_mode = WAL");
1143
- await client.execute("PRAGMA busy_timeout = 5000");
1208
+ await client.execute("PRAGMA busy_timeout = 30000");
1144
1209
  try {
1145
1210
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
1146
1211
  } catch {