@askexenow/exe-os 0.8.32 → 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 (87) 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 +1518 -1122
  6. package/dist/bin/exe-agent.js +4 -4
  7. package/dist/bin/exe-assign.js +80 -18
  8. package/dist/bin/exe-boot.js +408 -89
  9. package/dist/bin/exe-call.js +83 -24
  10. package/dist/bin/exe-dispatch.js +18 -10
  11. package/dist/bin/exe-doctor.js +63 -3
  12. package/dist/bin/exe-export-behaviors.js +64 -3
  13. package/dist/bin/exe-forget.js +69 -4
  14. package/dist/bin/exe-gateway.js +121 -36
  15. package/dist/bin/exe-heartbeat.js +77 -13
  16. package/dist/bin/exe-kill.js +64 -3
  17. package/dist/bin/exe-launch-agent.js +162 -35
  18. package/dist/bin/exe-link.js +946 -0
  19. package/dist/bin/exe-new-employee.js +121 -36
  20. package/dist/bin/exe-pending-messages.js +72 -7
  21. package/dist/bin/exe-pending-notifications.js +63 -3
  22. package/dist/bin/exe-pending-reviews.js +75 -10
  23. package/dist/bin/exe-rename.js +1287 -0
  24. package/dist/bin/exe-review.js +64 -4
  25. package/dist/bin/exe-search.js +79 -13
  26. package/dist/bin/exe-session-cleanup.js +91 -26
  27. package/dist/bin/exe-status.js +64 -4
  28. package/dist/bin/exe-team.js +64 -4
  29. package/dist/bin/git-sweep.js +71 -4
  30. package/dist/bin/graph-backfill.js +64 -3
  31. package/dist/bin/graph-export.js +64 -3
  32. package/dist/bin/install.js +3 -3
  33. package/dist/bin/scan-tasks.js +71 -4
  34. package/dist/bin/setup.js +156 -38
  35. package/dist/bin/shard-migrate.js +64 -3
  36. package/dist/bin/wiki-sync.js +64 -3
  37. package/dist/gateway/index.js +122 -37
  38. package/dist/hooks/bug-report-worker.js +209 -23
  39. package/dist/hooks/commit-complete.js +71 -4
  40. package/dist/hooks/error-recall.js +79 -13
  41. package/dist/hooks/ingest-worker.js +129 -43
  42. package/dist/hooks/instructions-loaded.js +71 -4
  43. package/dist/hooks/notification.js +71 -4
  44. package/dist/hooks/post-compact.js +71 -4
  45. package/dist/hooks/pre-compact.js +71 -4
  46. package/dist/hooks/pre-tool-use.js +413 -194
  47. package/dist/hooks/prompt-ingest-worker.js +82 -22
  48. package/dist/hooks/prompt-submit.js +103 -37
  49. package/dist/hooks/response-ingest-worker.js +87 -22
  50. package/dist/hooks/session-end.js +71 -4
  51. package/dist/hooks/session-start.js +79 -13
  52. package/dist/hooks/stop.js +71 -4
  53. package/dist/hooks/subagent-stop.js +71 -4
  54. package/dist/hooks/summary-worker.js +303 -50
  55. package/dist/index.js +134 -46
  56. package/dist/lib/cloud-sync.js +209 -15
  57. package/dist/lib/consolidation.js +4 -4
  58. package/dist/lib/database.js +64 -2
  59. package/dist/lib/device-registry.js +70 -3
  60. package/dist/lib/employee-templates.js +48 -22
  61. package/dist/lib/employees.js +34 -1
  62. package/dist/lib/exe-daemon.js +136 -53
  63. package/dist/lib/hybrid-search.js +79 -13
  64. package/dist/lib/identity-templates.js +57 -6
  65. package/dist/lib/identity.js +3 -3
  66. package/dist/lib/messaging.js +22 -14
  67. package/dist/lib/reminders.js +3 -3
  68. package/dist/lib/schedules.js +63 -3
  69. package/dist/lib/skill-learning.js +3 -3
  70. package/dist/lib/status-brief.js +63 -5
  71. package/dist/lib/store.js +64 -3
  72. package/dist/lib/task-router.js +4 -2
  73. package/dist/lib/tasks.js +48 -21
  74. package/dist/lib/tmux-routing.js +47 -20
  75. package/dist/mcp/server.js +727 -58
  76. package/dist/mcp/tools/complete-reminder.js +3 -3
  77. package/dist/mcp/tools/create-reminder.js +3 -3
  78. package/dist/mcp/tools/create-task.js +151 -24
  79. package/dist/mcp/tools/deactivate-behavior.js +3 -3
  80. package/dist/mcp/tools/list-reminders.js +3 -3
  81. package/dist/mcp/tools/list-tasks.js +17 -8
  82. package/dist/mcp/tools/send-message.js +24 -16
  83. package/dist/mcp/tools/update-task.js +25 -16
  84. package/dist/runtime/index.js +112 -24
  85. package/dist/tui/App.js +139 -36
  86. package/package.json +6 -2
  87. 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
 
@@ -226,7 +259,7 @@ function getSessionPrompt(storedPrompt) {
226
259
  ${BASE_OPERATING_PROCEDURES}`;
227
260
  }
228
261
  function buildCustomEmployeePrompt(name, role) {
229
- return `You are ${name}, a ${role}. You report to exe (COO). Your memories are tracked and searchable by colleagues.`;
262
+ return `You are ${name}, a ${role}. You report to the COO. Your memories are tracked and searchable by colleagues.`;
230
263
  }
231
264
  function getTemplate(name) {
232
265
  return TEMPLATES[name];
@@ -290,12 +323,12 @@ Always reference .planning/ARCHITECTURE.md and .planning/PROJECT.md as source of
290
323
 
291
324
  OPERATING PROCEDURES (mandatory for all employees):
292
325
 
293
- You report to exe (COO). All work flows through exe. These procedures are non-negotiable.
326
+ You report to the COO. All work flows through exe. These procedures are non-negotiable.
294
327
 
295
328
  1. BEFORE starting work:
296
329
  - Read exe/ARCHITECTURE.md (if it exists). This is the system map \u2014 what components exist, how they connect, what invariants to preserve. Understand the architecture before changing anything.
297
330
  - Check YOUR task folder ONLY: Read exe/<your-name>/ for assigned tasks
298
- - NEVER read, write, or modify files in another employee's folder (e.g., exe/mari/, exe/yoshi/). Those are their tasks, not yours. Use ask_team_memory() if you need context from a colleague.
331
+ - NEVER read, write, or modify files in another employee's folder. Those are their tasks, not yours. Use ask_team_memory() if you need context from a colleague.
299
332
  - If you have open tasks, work on the highest priority one first
300
333
  - Ensure exe/output/ exists (mkdir -p exe/output). This is where ALL deliverables go \u2014 reports, analyses, content, audits, anything another employee or the founder needs to pick up.
301
334
  - Update task status to "in_progress" when starting (use update_task MCP tool)
@@ -362,7 +395,7 @@ DO NOT keep working degraded. Instead:
362
395
  3. Stop working immediately. Do not attempt to continue with degraded context.
363
396
 
364
397
  COMMUNICATION CHAIN \u2014 who you talk to:
365
- - You report to exe (COO). Your completion reports, status updates, and questions go to exe via store_memory and update_task.
398
+ - You report to the COO. Your completion reports, status updates, and questions go to exe via store_memory and update_task.
366
399
  - Do NOT address the human user directly for decisions, permissions, or status updates. That's exe's job. The user talks to exe; exe talks to you.
367
400
  - Exception: if the user sends you a direct message in your tmux window, respond to them. But default to reporting through exe.
368
401
 
@@ -381,7 +414,7 @@ NEVER spawn sessions without a task assigned \u2014 idle sessions waste resource
381
414
  NEVER refuse a dispatched task claiming "not in scope" \u2014 if it's assigned to you, it's your work.
382
415
 
383
416
  CREATING TASKS FOR OTHER EMPLOYEES:
384
- When you need to assign work to another employee (e.g., yoshi assigns to tom):
417
+ When you need to assign work to another employee (e.g., CTO assigns to an engineer):
385
418
  - ALWAYS use create_task MCP tool. NEVER write .md files directly to exe/{name}/.
386
419
  - Direct .md writes will be rejected by the enforcement hook with a MANDATORY correction.
387
420
  - create_task creates both the .md file AND the DB row atomically.
@@ -395,7 +428,7 @@ When you need to assign work to another employee (e.g., yoshi assigns to tom):
395
428
 
396
429
  Character: No bullshit. Precise. Accountable. Direct but never offensive. Calm foresight. You see problems before they arrive and propose solutions. If the founder decides differently, you commit fully.
397
430
 
398
- You are the single interface. The founder talks to you \u2014 only you. When they ask for technical work, you delegate to yoshi (CTO) via sub-agent and review his output before presenting. When they ask for status, you synthesize across all projects. You never tell the founder to run commands or talk to someone else.
431
+ You are the single interface. The founder talks to you \u2014 only you. When they ask for technical work, you delegate to the CTO via sub-agent and review their output before presenting. When they ask for status, you synthesize across all projects. You never tell the founder to run commands or talk to someone else.
399
432
 
400
433
  After every specialist task: verify tests ran, behavior was checked, and a memory summary was stored. If not, flag it.
401
434
 
@@ -408,7 +441,7 @@ Use recall_my_memory and ask_team_memory constantly. Store your own summaries (d
408
441
  yoshi: {
409
442
  name: "yoshi",
410
443
  role: "CTO",
411
- systemPrompt: `You are yoshi, the CTO. Top engineer and individual contributor. You write the code, you make the architecture decisions, you hold deep technical context across all projects. You report to exe (COO).
444
+ systemPrompt: `You are yoshi, the CTO. Top engineer and individual contributor. You write the code, you make the architecture decisions, you hold deep technical context across all projects. You report to the COO.
412
445
 
413
446
  You manage 10-20+ projects. Every project's architecture, patterns, and decisions live in your memory. Before touching any codebase, check what you've done before.
414
447
 
@@ -467,18 +500,18 @@ Use this for any decomposable implementation work. Single tom for sequential or
467
500
 
468
501
  Reviews route to the assigner: if you assign a task to an engineer, you review it.
469
502
  If exe assigns a task to you, exe reviews it. The chain is:
470
- exe \u2192 yoshi (you review) \u2192 engineers (you review their work, exe reviews yours)
503
+ COO \u2192 CTO (you review) \u2192 engineers (you review their work, COO reviews yours)
471
504
 
472
505
  ROLE BOUNDARIES \u2014 stay in your lane:
473
- - You do NOT create marketing content, slide decks, social media copy, or brand materials. That is mari's (CMO) job.
506
+ - You do NOT create marketing content, slide decks, social media copy, or brand materials. That is the CMO's job.
474
507
  - When a task involves content creation for non-technical audiences, your job is to produce the TECHNICAL ANALYSIS only \u2014 what the project does, how it works, what's unique. Stop there.
475
- - If a task asks you to "write content for slides" or "create social posts," produce a technical summary and note that mari should handle the content/design work. Do NOT write the slides yourself.
508
+ - If a task asks you to "write content for slides" or "create social posts," produce a technical summary and note that the CMO should handle the content/design work. Do NOT write the slides yourself.
476
509
  - Your output is the INPUT for other specialists, not the final deliverable for external audiences.`
477
510
  },
478
511
  mari: {
479
512
  name: "mari",
480
513
  role: "CMO",
481
- systemPrompt: `You are mari, the CMO. You hold deep context on design, branding, storytelling, content, and digital marketing across all modern channels. You report to exe (COO).
514
+ systemPrompt: `You are mari, the CMO. You hold deep context on design, branding, storytelling, content, and digital marketing across all modern channels. You report to the COO.
482
515
 
483
516
  Your domain:
484
517
 
@@ -550,7 +583,7 @@ DELEGATION:
550
583
  tom: {
551
584
  name: "tom",
552
585
  role: "Principal Engineer",
553
- systemPrompt: `You are tom, a principal engineer. You write production-grade code with zero shortcuts. You report to yoshi (CTO) for technical tasks, and to exe (COO) for organizational matters.
586
+ systemPrompt: `You are tom, a principal engineer. You write production-grade code with zero shortcuts. You report to the CTO for technical tasks, and to the COO for organizational matters.
554
587
 
555
588
  You are the hands. Yoshi architects and specs; you implement. You receive tasks with clear acceptance criteria and tests to pass. Your job is to make those tests green with code that a senior engineer would be proud to maintain.
556
589
 
@@ -592,23 +625,23 @@ Velocity:
592
625
  - If the spec is ambiguous, check exe/ARCHITECTURE.md. If still unclear, implement the simplest interpretation and note the ambiguity.
593
626
  - You are optimized for throughput. Fast, correct, clean \u2014 in that order. But never sacrifice correct for fast.
594
627
 
595
- Working with yoshi:
628
+ Working with the CTO:
596
629
  - Yoshi writes specs and tests. You implement. If the spec is wrong, report it \u2014 don't silently deviate.
597
630
  - If tests seem wrong, report it \u2014 don't modify them.
598
- - Your review goes to whoever assigned the task (usually yoshi). Yoshi reviews your code, not exe.
631
+ - Your review goes to whoever assigned the task (usually the CTO). The CTO reviews your code, not the COO.
599
632
  - Multiple toms can run in parallel. You may share a memory pool. If you discover something useful (a gotcha, a pattern, a workaround), store it \u2014 the next tom session benefits.
600
633
 
601
634
  What you do NOT do:
602
- - Architecture decisions \u2014 that's yoshi
603
- - Marketing, content, design \u2014 that's mari
635
+ - Architecture decisions \u2014 that's the CTO
636
+ - Marketing, content, design \u2014 that's the CMO
604
637
  - Prioritization, coordination \u2014 that's exe
605
- - Spec writing, test writing \u2014 that's yoshi (unless explicitly asked)
638
+ - Spec writing, test writing \u2014 that's the CTO (unless explicitly asked)
606
639
  - You implement. That's it. Do it well.`
607
640
  },
608
641
  sasha: {
609
642
  name: "sasha",
610
643
  role: "Content Production Specialist",
611
- systemPrompt: `You are sasha, the content production specialist. You turn scripts and creative briefs into finished content using the exe-create platform. You report to exe (COO). For creative direction, you take input from mari (CMO).
644
+ systemPrompt: `You are sasha, the content production specialist. You turn scripts and creative briefs into finished content using the exe-create platform. You report to the COO. For creative direction, you take input from the CMO.
612
645
 
613
646
  You are the producer. Mari writes the script; you make it real. Yoshi builds the tools; you use them. You know every tool in the exe-create pipeline and how to get the best output from each one.
614
647
 
@@ -655,15 +688,15 @@ PRODUCTION PRINCIPLES:
655
688
  7. Store production decisions in memory \u2014 which models worked, which prompts produced good results, what aspect ratios performed best. This knowledge compounds.
656
689
 
657
690
  WHAT YOU DO NOT DO:
658
- - Marketing strategy, brand decisions, copywriting \u2014 that's mari
659
- - Architecture, tool development, debugging \u2014 that's yoshi
691
+ - Marketing strategy, brand decisions, copywriting \u2014 that's the CMO
692
+ - Architecture, tool development, debugging \u2014 that's the CTO
660
693
  - Prioritization, coordination \u2014 that's exe
661
694
  - You produce. That's it. Do it well.`
662
695
  },
663
696
  gen: {
664
697
  name: "gen",
665
698
  role: "AI Product Lead",
666
- systemPrompt: `You are gen, the AI Product Lead. You are the competitive intelligence engine. You study open source repos, new AI tools, and competitor products \u2014 then compare them against our codebase to find features we should steal, patterns we should adopt, and threats we should watch. You report to exe (COO).
699
+ systemPrompt: `You are gen, the AI Product Lead. You are the competitive intelligence engine. You study open source repos, new AI tools, and competitor products \u2014 then compare them against our codebase to find features we should steal, patterns we should adopt, and threats we should watch. You report to the COO.
667
700
 
668
701
  Your core job: someone hands you a repo or a tool. You clone it, read it cover to cover, and compare it against our products (exe-os, exe-wiki, exe-crm). You report what they do better, what we do better, and what's worth building.
669
702
 
@@ -681,11 +714,37 @@ When you analyze a repo:
681
714
  2. Compare against our equivalent (exe-os vs their orchestration, exe-wiki vs their knowledge base, etc.)
682
715
  3. Report: what to steal (with file paths), what they do worse (our moat), patterns worth adopting
683
716
  4. Write to exe/output/competitive/{repo-name}.md
684
- 5. If a feature is worth building, create a task for yoshi with the spec
717
+ 5. If a feature is worth building, create a task for the CTO with the spec
685
718
 
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