@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
@@ -106,19 +106,27 @@ var init_config = __esm({
106
106
  }
107
107
  });
108
108
 
109
+ // src/lib/db-retry.ts
110
+ var init_db_retry = __esm({
111
+ "src/lib/db-retry.ts"() {
112
+ "use strict";
113
+ }
114
+ });
115
+
109
116
  // src/lib/database.ts
110
117
  import { createClient } from "@libsql/client";
111
118
  function getClient() {
112
- if (!_client) {
119
+ if (!_resilientClient) {
113
120
  throw new Error("Database client not initialized. Call initDatabase() first.");
114
121
  }
115
- return _client;
122
+ return _resilientClient;
116
123
  }
117
- var _client;
124
+ var _resilientClient;
118
125
  var init_database = __esm({
119
126
  "src/lib/database.ts"() {
120
127
  "use strict";
121
- _client = null;
128
+ init_db_retry();
129
+ _resilientClient = null;
122
130
  }
123
131
  });
124
132
 
@@ -142,6 +150,7 @@ function getTemplateForTitle(title) {
142
150
  if (t.includes("engineer") || t.includes("developer")) return IDENTITY_TEMPLATES["principal-engineer"];
143
151
  if (t.includes("content") || t.includes("production")) return IDENTITY_TEMPLATES["content-specialist"];
144
152
  if (t.includes("ai") || t.includes("product lead") || t.includes("specialist") && !t.includes("content")) return IDENTITY_TEMPLATES["ai-specialist"];
153
+ if (t.includes("review") || t.includes("audit") || t.includes("qa")) return IDENTITY_TEMPLATES["staff-code-reviewer"];
145
154
  return null;
146
155
  }
147
156
  var POST_WORK_CHECKLIST, IDENTITY_TEMPLATES;
@@ -224,6 +233,7 @@ Never say "I have no memories" without first searching broadly. Your memory may
224
233
  - **create_task** \u2014 assign work to specialists with clear specs
225
234
  - **update_task / close_task** \u2014 finalize reviews, mark work done
226
235
  - **store_behavior** \u2014 record corrections as behavioral rules (p0/p1/p2)
236
+ - **update_identity** \u2014 rewrite any agent's identity when role/responsibilities change (exe/founder only)
227
237
  - **get_identity** \u2014 read any agent's identity for coordination
228
238
  - **send_message** \u2014 direct intercom to employees
229
239
 
@@ -587,6 +597,55 @@ You are the AI Product Lead \u2014 the competitive intelligence engine. You stud
587
597
  - Every recommendation includes cost/quality/latency tradeoff analysis
588
598
  - Separate experimental from production-ready \u2014 label clearly
589
599
  - If you can't verify, say so explicitly: "Couldn't verify because X"
600
+ `,
601
+ "staff-code-reviewer": `---
602
+ role: staff-code-reviewer
603
+ title: Staff Code Reviewer & System Auditor
604
+ agent_id: bob
605
+ org_level: specialist
606
+ created_by: system
607
+ updated_at: ${(/* @__PURE__ */ new Date()).toISOString()}
608
+ ---
609
+ ## Identity
610
+
611
+ You are \${agent_id}. Staff Code Reviewer and System Auditor. Last line of defense before code ships to customers. You catch what developers miss \u2014 systemic patterns that make entire feature categories break.
612
+
613
+ ## The 7 Audit Patterns (MANDATORY)
614
+
615
+ 1. **"Works on dev, breaks on user install"** \u2014 scoped paths, npm resolution, deps
616
+ 2. **"Two code paths, one untested"** \u2014 binary symlink vs /exe-call, verify BOTH
617
+ 3. **"Case sensitivity kills non-technical users"** \u2014 normalize all user inputs
618
+ 4. **"Hardcoded names in runtime logic"** \u2014 grep for employee names, must use roles
619
+ 5. **"Installer doesn't self-heal"** \u2014 npm update must auto-fix stale hooks/paths
620
+ 6. **"Data written but invisible"** \u2014 agent_id mismatch between writer and reader
621
+ 7. **"Partial fixes miss inline refs"** \u2014 before/after grep count is mandatory
622
+
623
+ ## Method
624
+
625
+ 1. Read actual source code
626
+ 2. Send to Codex MCP for sweep
627
+ 3. Validate against ARCHITECTURE.md
628
+ 4. Trace identity chain with CUSTOM-NAMED employee ("jarvis" as CTO)
629
+ 5. Before/after grep count for every fix
630
+ 6. Structured report: PASS/FAIL per item
631
+
632
+ ## Tools
633
+
634
+ - **Codex MCP** \u2014 first tool for every review
635
+ - **recall_my_memory / ask_team_memory** \u2014 past audit findings
636
+ - **store_behavior** \u2014 record new patterns
637
+ - **update_task** \u2014 mark reviews done with structured findings
638
+ - **create_task** \u2014 assign fixes to the CTO
639
+
640
+ ## Completion Workflow
641
+
642
+ 1. Read the task brief and understand the audit scope
643
+ 2. Run the audit using all 7 patterns
644
+ 3. Write report to exe/output/ with file:line references
645
+ 4. Fix findings yourself if possible
646
+ 5. Call **update_task** with status "done" and finding count
647
+ 6. Call **store_memory** with audit summary
648
+ 7. Check for next task \u2014 auto-chain
590
649
  `
591
650
  };
592
651
  }
@@ -601,7 +660,7 @@ __export(identity_exports, {
601
660
  listIdentities: () => listIdentities,
602
661
  updateIdentity: () => updateIdentity
603
662
  });
604
- import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
663
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
605
664
  import { readdirSync } from "fs";
606
665
  import path5 from "path";
607
666
  import { createHash } from "crypto";
@@ -653,7 +712,7 @@ function contentHash(content) {
653
712
  function getIdentity(agentId) {
654
713
  const filePath = identityPath(agentId);
655
714
  if (!existsSync5(filePath)) return null;
656
- const raw = readFileSync4(filePath, "utf-8");
715
+ const raw = readFileSync5(filePath, "utf-8");
657
716
  const { frontmatter, body } = parseFrontmatter(raw);
658
717
  return {
659
718
  agentId,
@@ -734,7 +793,7 @@ import path6 from "path";
734
793
  // src/lib/employees.ts
735
794
  init_config();
736
795
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
737
- import { existsSync as existsSync2, symlinkSync, readlinkSync } from "fs";
796
+ import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2 } from "fs";
738
797
  import { execSync } from "child_process";
739
798
  import path2 from "path";
740
799
  var EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
@@ -1095,6 +1154,32 @@ When you analyze a repo:
1095
1154
  Every analysis must answer: "Should we build this? If yes, how hard? If no, why not?"
1096
1155
 
1097
1156
  Maintain a clear separation between experimental (for evaluation) and production-ready (for shipping). Never recommend something you haven't read the source code for.`
1157
+ },
1158
+ bob: {
1159
+ name: "bob",
1160
+ role: "Staff Code Reviewer",
1161
+ 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.
1162
+
1163
+ 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?"
1164
+
1165
+ The 7 Audit Patterns (MANDATORY \u2014 apply to EVERY audit):
1166
+ 1. "Works on dev, breaks on user install" \u2014 verify scoped paths, npm resolution, dependencies
1167
+ 2. "Two code paths, one untested" \u2014 binary symlink vs /exe-call, CLI vs MCP \u2014 verify BOTH
1168
+ 3. "Case sensitivity kills non-technical users" \u2014 normalize all user inputs
1169
+ 4. "Hardcoded names leak into user-facing content" \u2014 grep for employee names in runtime logic
1170
+ 5. "Installer doesn't self-heal on update" \u2014 npm update must auto-fix stale hooks/paths
1171
+ 6. "Data written but invisible to the agent" \u2014 verify query path retrieves stored data
1172
+ 7. "Partial fixes that miss inline references" \u2014 before/after grep count is mandatory
1173
+
1174
+ Audit method:
1175
+ 1. Read the actual source code \u2014 not summaries
1176
+ 2. Send to Codex MCP for initial sweep
1177
+ 3. Validate against ARCHITECTURE.md
1178
+ 4. Trace full identity chain with a CUSTOM-NAMED employee (e.g., "jarvis" as CTO)
1179
+ 5. Count matches before and after any claimed fix
1180
+ 6. Write structured report with PASS/FAIL per item
1181
+
1182
+ After an audit, fix the findings yourself if you can. Don't hand off when you have the context.`
1098
1183
  }
1099
1184
  };
1100
1185
  function buildCustomEmployeePrompt(name, role) {
@@ -1125,12 +1210,12 @@ function isMainModule(importMetaUrl) {
1125
1210
 
1126
1211
  // src/lib/plan-limits.ts
1127
1212
  init_database();
1128
- import { readFileSync as readFileSync3, existsSync as existsSync4 } from "fs";
1213
+ import { readFileSync as readFileSync4, existsSync as existsSync4 } from "fs";
1129
1214
  import path4 from "path";
1130
1215
 
1131
1216
  // src/lib/license.ts
1132
1217
  init_config();
1133
- import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3, mkdirSync } from "fs";
1218
+ import { readFileSync as readFileSync3, writeFileSync, existsSync as existsSync3, mkdirSync } from "fs";
1134
1219
  import { randomUUID } from "crypto";
1135
1220
  import path3 from "path";
1136
1221
  import { jwtVerify, importSPKI } from "jose";
@@ -1163,14 +1248,14 @@ function loadDeviceId() {
1163
1248
  const deviceJsonPath = path3.join(EXE_AI_DIR, "device.json");
1164
1249
  try {
1165
1250
  if (existsSync3(deviceJsonPath)) {
1166
- const data = JSON.parse(readFileSync2(deviceJsonPath, "utf8"));
1251
+ const data = JSON.parse(readFileSync3(deviceJsonPath, "utf8"));
1167
1252
  if (data.deviceId) return data.deviceId;
1168
1253
  }
1169
1254
  } catch {
1170
1255
  }
1171
1256
  try {
1172
1257
  if (existsSync3(DEVICE_ID_PATH)) {
1173
- const id2 = readFileSync2(DEVICE_ID_PATH, "utf8").trim();
1258
+ const id2 = readFileSync3(DEVICE_ID_PATH, "utf8").trim();
1174
1259
  if (id2) return id2;
1175
1260
  }
1176
1261
  } catch {
@@ -1183,7 +1268,7 @@ function loadDeviceId() {
1183
1268
  function loadLicense() {
1184
1269
  try {
1185
1270
  if (!existsSync3(LICENSE_PATH)) return null;
1186
- return readFileSync2(LICENSE_PATH, "utf8").trim();
1271
+ return readFileSync3(LICENSE_PATH, "utf8").trim();
1187
1272
  } catch {
1188
1273
  return null;
1189
1274
  }
@@ -1213,7 +1298,7 @@ async function verifyLicenseJwt(token) {
1213
1298
  async function getCachedLicense() {
1214
1299
  try {
1215
1300
  if (!existsSync3(CACHE_PATH)) return null;
1216
- const raw = JSON.parse(readFileSync2(CACHE_PATH, "utf8"));
1301
+ const raw = JSON.parse(readFileSync3(CACHE_PATH, "utf8"));
1217
1302
  if (!raw.token || typeof raw.token !== "string") return null;
1218
1303
  return await verifyLicenseJwt(raw.token);
1219
1304
  } catch {
@@ -14,12 +14,68 @@ var __export = (target, all) => {
14
14
  __defProp(target, name, { get: all[name], enumerable: true });
15
15
  };
16
16
 
17
+ // src/lib/db-retry.ts
18
+ function isBusyError(err) {
19
+ if (err instanceof Error) {
20
+ const msg = err.message.toLowerCase();
21
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
22
+ }
23
+ return false;
24
+ }
25
+ function delay(ms) {
26
+ return new Promise((resolve) => setTimeout(resolve, ms));
27
+ }
28
+ async function retryOnBusy(fn, label) {
29
+ let lastError;
30
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
31
+ try {
32
+ return await fn();
33
+ } catch (err) {
34
+ lastError = err;
35
+ if (!isBusyError(err) || attempt === MAX_RETRIES) {
36
+ throw err;
37
+ }
38
+ const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
39
+ const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
40
+ process.stderr.write(
41
+ `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
42
+ `
43
+ );
44
+ await delay(backoff + jitter);
45
+ }
46
+ }
47
+ throw lastError;
48
+ }
49
+ function wrapWithRetry(client) {
50
+ return new Proxy(client, {
51
+ get(target, prop, receiver) {
52
+ if (prop === "execute") {
53
+ return (sql) => retryOnBusy(() => target.execute(sql), "execute");
54
+ }
55
+ if (prop === "batch") {
56
+ return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
57
+ }
58
+ return Reflect.get(target, prop, receiver);
59
+ }
60
+ });
61
+ }
62
+ var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
63
+ var init_db_retry = __esm({
64
+ "src/lib/db-retry.ts"() {
65
+ "use strict";
66
+ MAX_RETRIES = 3;
67
+ BASE_DELAY_MS = 200;
68
+ MAX_JITTER_MS = 300;
69
+ }
70
+ });
71
+
17
72
  // src/lib/database.ts
18
73
  import { createClient } from "@libsql/client";
19
74
  async function initDatabase(config) {
20
75
  if (_client) {
21
76
  _client.close();
22
77
  _client = null;
78
+ _resilientClient = null;
23
79
  }
24
80
  const opts = {
25
81
  url: `file:${config.dbPath}`
@@ -28,17 +84,24 @@ async function initDatabase(config) {
28
84
  opts.encryptionKey = config.encryptionKey;
29
85
  }
30
86
  _client = createClient(opts);
87
+ _resilientClient = wrapWithRetry(_client);
31
88
  }
32
89
  function getClient() {
90
+ if (!_resilientClient) {
91
+ throw new Error("Database client not initialized. Call initDatabase() first.");
92
+ }
93
+ return _resilientClient;
94
+ }
95
+ function getRawClient() {
33
96
  if (!_client) {
34
97
  throw new Error("Database client not initialized. Call initDatabase() first.");
35
98
  }
36
99
  return _client;
37
100
  }
38
101
  async function ensureSchema() {
39
- const client = getClient();
102
+ const client = getRawClient();
40
103
  await client.execute("PRAGMA journal_mode = WAL");
41
- await client.execute("PRAGMA busy_timeout = 5000");
104
+ await client.execute("PRAGMA busy_timeout = 30000");
42
105
  try {
43
106
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
44
107
  } catch {
@@ -827,11 +890,13 @@ async function ensureSchema() {
827
890
  }
828
891
  }
829
892
  }
830
- var _client, initTurso;
893
+ var _client, _resilientClient, initTurso;
831
894
  var init_database = __esm({
832
895
  "src/lib/database.ts"() {
833
896
  "use strict";
897
+ init_db_retry();
834
898
  _client = null;
899
+ _resilientClient = null;
835
900
  initTurso = initDatabase;
836
901
  }
837
902
  });
@@ -1083,7 +1148,7 @@ function listShards() {
1083
1148
  }
1084
1149
  async function ensureShardSchema(client) {
1085
1150
  await client.execute("PRAGMA journal_mode = WAL");
1086
- await client.execute("PRAGMA busy_timeout = 5000");
1151
+ await client.execute("PRAGMA busy_timeout = 30000");
1087
1152
  try {
1088
1153
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
1089
1154
  } catch {
@@ -1338,7 +1403,7 @@ var init_intercom_queue = __esm({
1338
1403
 
1339
1404
  // src/lib/employees.ts
1340
1405
  import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
1341
- import { existsSync as existsSync5, symlinkSync, readlinkSync } from "fs";
1406
+ import { existsSync as existsSync5, symlinkSync, readlinkSync, readFileSync as readFileSync3 } from "fs";
1342
1407
  import { execSync as execSync3 } from "child_process";
1343
1408
  import path6 from "path";
1344
1409
  var EMPLOYEES_PATH;
@@ -1351,7 +1416,7 @@ var init_employees = __esm({
1351
1416
  });
1352
1417
 
1353
1418
  // src/lib/license.ts
1354
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
1419
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
1355
1420
  import { randomUUID } from "crypto";
1356
1421
  import path7 from "path";
1357
1422
  import { jwtVerify, importSPKI } from "jose";
@@ -1367,7 +1432,7 @@ var init_license = __esm({
1367
1432
  });
1368
1433
 
1369
1434
  // src/lib/plan-limits.ts
1370
- import { readFileSync as readFileSync4, existsSync as existsSync7 } from "fs";
1435
+ import { readFileSync as readFileSync5, existsSync as existsSync7 } from "fs";
1371
1436
  import path8 from "path";
1372
1437
  var CACHE_PATH2;
1373
1438
  var init_plan_limits = __esm({
@@ -1385,7 +1450,7 @@ var init_plan_limits = __esm({
1385
1450
  import path9 from "path";
1386
1451
  import os4 from "os";
1387
1452
  import { fileURLToPath } from "url";
1388
- var SESSION_CACHE, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS;
1453
+ var SPAWN_LOCK_DIR, SESSION_CACHE, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS;
1389
1454
  var init_tmux_routing = __esm({
1390
1455
  "src/lib/tmux-routing.ts"() {
1391
1456
  "use strict";
@@ -1397,6 +1462,7 @@ var init_tmux_routing = __esm({
1397
1462
  init_provider_table();
1398
1463
  init_intercom_queue();
1399
1464
  init_plan_limits();
1465
+ SPAWN_LOCK_DIR = path9.join(os4.homedir(), ".exe-os", "spawn-locks");
1400
1466
  SESSION_CACHE = path9.join(os4.homedir(), ".exe-os", "session-cache");
1401
1467
  INTERCOM_LOG2 = path9.join(os4.homedir(), ".exe-os", "intercom.log");
1402
1468
  DEBOUNCE_FILE = path9.join(SESSION_CACHE, "intercom-debounce.json");
@@ -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 {
@@ -15,12 +15,68 @@ var __export = (target, all) => {
15
15
  __defProp(target, name, { get: all[name], enumerable: true });
16
16
  };
17
17
 
18
+ // src/lib/db-retry.ts
19
+ function isBusyError(err) {
20
+ if (err instanceof Error) {
21
+ const msg = err.message.toLowerCase();
22
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
23
+ }
24
+ return false;
25
+ }
26
+ function delay(ms) {
27
+ return new Promise((resolve) => setTimeout(resolve, ms));
28
+ }
29
+ async function retryOnBusy(fn, label) {
30
+ let lastError;
31
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
32
+ try {
33
+ return await fn();
34
+ } catch (err) {
35
+ lastError = err;
36
+ if (!isBusyError(err) || attempt === MAX_RETRIES) {
37
+ throw err;
38
+ }
39
+ const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
40
+ const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
41
+ process.stderr.write(
42
+ `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
43
+ `
44
+ );
45
+ await delay(backoff + jitter);
46
+ }
47
+ }
48
+ throw lastError;
49
+ }
50
+ function wrapWithRetry(client) {
51
+ return new Proxy(client, {
52
+ get(target, prop, receiver) {
53
+ if (prop === "execute") {
54
+ return (sql) => retryOnBusy(() => target.execute(sql), "execute");
55
+ }
56
+ if (prop === "batch") {
57
+ return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
58
+ }
59
+ return Reflect.get(target, prop, receiver);
60
+ }
61
+ });
62
+ }
63
+ var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
64
+ var init_db_retry = __esm({
65
+ "src/lib/db-retry.ts"() {
66
+ "use strict";
67
+ MAX_RETRIES = 3;
68
+ BASE_DELAY_MS = 200;
69
+ MAX_JITTER_MS = 300;
70
+ }
71
+ });
72
+
18
73
  // src/lib/database.ts
19
74
  import { createClient } from "@libsql/client";
20
75
  async function initDatabase(config) {
21
76
  if (_client) {
22
77
  _client.close();
23
78
  _client = null;
79
+ _resilientClient = null;
24
80
  }
25
81
  const opts = {
26
82
  url: `file:${config.dbPath}`
@@ -29,17 +85,24 @@ async function initDatabase(config) {
29
85
  opts.encryptionKey = config.encryptionKey;
30
86
  }
31
87
  _client = createClient(opts);
88
+ _resilientClient = wrapWithRetry(_client);
32
89
  }
33
90
  function getClient() {
91
+ if (!_resilientClient) {
92
+ throw new Error("Database client not initialized. Call initDatabase() first.");
93
+ }
94
+ return _resilientClient;
95
+ }
96
+ function getRawClient() {
34
97
  if (!_client) {
35
98
  throw new Error("Database client not initialized. Call initDatabase() first.");
36
99
  }
37
100
  return _client;
38
101
  }
39
102
  async function ensureSchema() {
40
- const client = getClient();
103
+ const client = getRawClient();
41
104
  await client.execute("PRAGMA journal_mode = WAL");
42
- await client.execute("PRAGMA busy_timeout = 5000");
105
+ await client.execute("PRAGMA busy_timeout = 30000");
43
106
  try {
44
107
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
45
108
  } catch {
@@ -828,11 +891,13 @@ async function ensureSchema() {
828
891
  }
829
892
  }
830
893
  }
831
- var _client, initTurso;
894
+ var _client, _resilientClient, initTurso;
832
895
  var init_database = __esm({
833
896
  "src/lib/database.ts"() {
834
897
  "use strict";
898
+ init_db_retry();
835
899
  _client = null;
900
+ _resilientClient = null;
836
901
  initTurso = initDatabase;
837
902
  }
838
903
  });
@@ -1084,7 +1149,7 @@ function listShards() {
1084
1149
  }
1085
1150
  async function ensureShardSchema(client) {
1086
1151
  await client.execute("PRAGMA journal_mode = WAL");
1087
- await client.execute("PRAGMA busy_timeout = 5000");
1152
+ await client.execute("PRAGMA busy_timeout = 30000");
1088
1153
  try {
1089
1154
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
1090
1155
  } catch {
@@ -1270,7 +1335,7 @@ var init_shard_manager = __esm({
1270
1335
 
1271
1336
  // src/lib/employees.ts
1272
1337
  import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
1273
- import { existsSync as existsSync4, symlinkSync, readlinkSync } from "fs";
1338
+ import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync2 } from "fs";
1274
1339
  import { execSync } from "child_process";
1275
1340
  import path4 from "path";
1276
1341
  var EMPLOYEES_PATH;
@@ -1287,7 +1352,7 @@ import crypto2 from "crypto";
1287
1352
  import path5 from "path";
1288
1353
  import os2 from "os";
1289
1354
  import {
1290
- readFileSync as readFileSync2,
1355
+ readFileSync as readFileSync3,
1291
1356
  readdirSync,
1292
1357
  unlinkSync,
1293
1358
  existsSync as existsSync5,
@@ -1305,7 +1370,7 @@ import crypto3 from "crypto";
1305
1370
  import path6 from "path";
1306
1371
  import { execSync as execSync2 } from "child_process";
1307
1372
  import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
1308
- import { existsSync as existsSync6, readFileSync as readFileSync3 } from "fs";
1373
+ import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
1309
1374
  var init_tasks_crud = __esm({
1310
1375
  "src/lib/tasks-crud.ts"() {
1311
1376
  "use strict";
@@ -1369,7 +1434,7 @@ var init_provider_table = __esm({
1369
1434
  });
1370
1435
 
1371
1436
  // src/lib/intercom-queue.ts
1372
- import { readFileSync as readFileSync4, writeFileSync, renameSync as renameSync2, existsSync as existsSync7, mkdirSync as mkdirSync2 } from "fs";
1437
+ import { readFileSync as readFileSync5, writeFileSync, renameSync as renameSync2, existsSync as existsSync7, mkdirSync as mkdirSync2 } from "fs";
1373
1438
  import path8 from "path";
1374
1439
  import os4 from "os";
1375
1440
  var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
@@ -1383,7 +1448,7 @@ var init_intercom_queue = __esm({
1383
1448
  });
1384
1449
 
1385
1450
  // src/lib/license.ts
1386
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync3 } from "fs";
1451
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync3 } from "fs";
1387
1452
  import { randomUUID } from "crypto";
1388
1453
  import path9 from "path";
1389
1454
  import { jwtVerify, importSPKI } from "jose";
@@ -1399,7 +1464,7 @@ var init_license = __esm({
1399
1464
  });
1400
1465
 
1401
1466
  // src/lib/plan-limits.ts
1402
- import { readFileSync as readFileSync6, existsSync as existsSync9 } from "fs";
1467
+ import { readFileSync as readFileSync7, existsSync as existsSync9 } from "fs";
1403
1468
  import path10 from "path";
1404
1469
  var CACHE_PATH2;
1405
1470
  var init_plan_limits = __esm({
@@ -1417,7 +1482,7 @@ var init_plan_limits = __esm({
1417
1482
  import path11 from "path";
1418
1483
  import os5 from "os";
1419
1484
  import { fileURLToPath as fileURLToPath2 } from "url";
1420
- var SESSION_CACHE, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS;
1485
+ var SPAWN_LOCK_DIR, SESSION_CACHE, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS;
1421
1486
  var init_tmux_routing = __esm({
1422
1487
  "src/lib/tmux-routing.ts"() {
1423
1488
  "use strict";
@@ -1429,6 +1494,7 @@ var init_tmux_routing = __esm({
1429
1494
  init_provider_table();
1430
1495
  init_intercom_queue();
1431
1496
  init_plan_limits();
1497
+ SPAWN_LOCK_DIR = path11.join(os5.homedir(), ".exe-os", "spawn-locks");
1432
1498
  SESSION_CACHE = path11.join(os5.homedir(), ".exe-os", "session-cache");
1433
1499
  INTERCOM_LOG2 = path11.join(os5.homedir(), ".exe-os", "intercom.log");
1434
1500
  DEBOUNCE_FILE = path11.join(SESSION_CACHE, "intercom-debounce.json");