@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
@@ -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 {
@@ -451,12 +451,65 @@ import { execSync } from "child_process";
451
451
 
452
452
  // src/lib/database.ts
453
453
  import { createClient } from "@libsql/client";
454
+
455
+ // src/lib/db-retry.ts
456
+ var MAX_RETRIES = 3;
457
+ var BASE_DELAY_MS = 200;
458
+ var MAX_JITTER_MS = 300;
459
+ function isBusyError(err) {
460
+ if (err instanceof Error) {
461
+ const msg = err.message.toLowerCase();
462
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
463
+ }
464
+ return false;
465
+ }
466
+ function delay(ms) {
467
+ return new Promise((resolve) => setTimeout(resolve, ms));
468
+ }
469
+ async function retryOnBusy(fn, label) {
470
+ let lastError;
471
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
472
+ try {
473
+ return await fn();
474
+ } catch (err) {
475
+ lastError = err;
476
+ if (!isBusyError(err) || attempt === MAX_RETRIES) {
477
+ throw err;
478
+ }
479
+ const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
480
+ const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
481
+ process.stderr.write(
482
+ `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
483
+ `
484
+ );
485
+ await delay(backoff + jitter);
486
+ }
487
+ }
488
+ throw lastError;
489
+ }
490
+ function wrapWithRetry(client) {
491
+ return new Proxy(client, {
492
+ get(target, prop, receiver) {
493
+ if (prop === "execute") {
494
+ return (sql) => retryOnBusy(() => target.execute(sql), "execute");
495
+ }
496
+ if (prop === "batch") {
497
+ return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
498
+ }
499
+ return Reflect.get(target, prop, receiver);
500
+ }
501
+ });
502
+ }
503
+
504
+ // src/lib/database.ts
454
505
  var _client = null;
506
+ var _resilientClient = null;
455
507
  var initTurso = initDatabase;
456
508
  async function initDatabase(config) {
457
509
  if (_client) {
458
510
  _client.close();
459
511
  _client = null;
512
+ _resilientClient = null;
460
513
  }
461
514
  const opts = {
462
515
  url: `file:${config.dbPath}`
@@ -465,17 +518,24 @@ async function initDatabase(config) {
465
518
  opts.encryptionKey = config.encryptionKey;
466
519
  }
467
520
  _client = createClient(opts);
521
+ _resilientClient = wrapWithRetry(_client);
468
522
  }
469
523
  function getClient() {
524
+ if (!_resilientClient) {
525
+ throw new Error("Database client not initialized. Call initDatabase() first.");
526
+ }
527
+ return _resilientClient;
528
+ }
529
+ function getRawClient() {
470
530
  if (!_client) {
471
531
  throw new Error("Database client not initialized. Call initDatabase() first.");
472
532
  }
473
533
  return _client;
474
534
  }
475
535
  async function ensureSchema() {
476
- const client = getClient();
536
+ const client = getRawClient();
477
537
  await client.execute("PRAGMA journal_mode = WAL");
478
- await client.execute("PRAGMA busy_timeout = 5000");
538
+ await client.execute("PRAGMA busy_timeout = 30000");
479
539
  try {
480
540
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
481
541
  } catch {
@@ -1269,6 +1329,7 @@ async function disposeDatabase() {
1269
1329
  if (_client) {
1270
1330
  _client.close();
1271
1331
  _client = null;
1332
+ _resilientClient = null;
1272
1333
  }
1273
1334
  }
1274
1335
 
@@ -262,7 +262,7 @@ function listShards() {
262
262
  }
263
263
  async function ensureShardSchema(client) {
264
264
  await client.execute("PRAGMA journal_mode = WAL");
265
- await client.execute("PRAGMA busy_timeout = 5000");
265
+ await client.execute("PRAGMA busy_timeout = 30000");
266
266
  try {
267
267
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
268
268
  } catch {
@@ -452,13 +452,18 @@ __export(employees_exports, {
452
452
  EMPLOYEES_PATH: () => EMPLOYEES_PATH,
453
453
  addEmployee: () => addEmployee,
454
454
  getEmployee: () => getEmployee,
455
+ getEmployeeByRole: () => getEmployeeByRole,
456
+ getEmployeeNamesByRole: () => getEmployeeNamesByRole,
457
+ hasRole: () => hasRole,
458
+ isMultiInstance: () => isMultiInstance,
455
459
  loadEmployees: () => loadEmployees,
460
+ loadEmployeesSync: () => loadEmployeesSync,
456
461
  registerBinSymlinks: () => registerBinSymlinks,
457
462
  saveEmployees: () => saveEmployees,
458
463
  validateEmployeeName: () => validateEmployeeName
459
464
  });
460
465
  import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
461
- import { existsSync as existsSync5, symlinkSync, readlinkSync } from "fs";
466
+ import { existsSync as existsSync5, symlinkSync, readlinkSync, readFileSync as readFileSync2 } from "fs";
462
467
  import { execSync as execSync2 } from "child_process";
463
468
  import path5 from "path";
464
469
  function validateEmployeeName(name) {
@@ -491,9 +496,36 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
491
496
  await mkdir3(path5.dirname(employeesPath), { recursive: true });
492
497
  await writeFile3(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
493
498
  }
499
+ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
500
+ if (!existsSync5(employeesPath)) return [];
501
+ try {
502
+ return JSON.parse(readFileSync2(employeesPath, "utf-8"));
503
+ } catch {
504
+ return [];
505
+ }
506
+ }
494
507
  function getEmployee(employees, name) {
495
508
  return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
496
509
  }
510
+ function getEmployeeByRole(employees, role) {
511
+ const lower = role.toLowerCase();
512
+ return employees.find((e) => e.role.toLowerCase() === lower);
513
+ }
514
+ function getEmployeeNamesByRole(employees, role) {
515
+ const lower = role.toLowerCase();
516
+ return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
517
+ }
518
+ function hasRole(agentName, role) {
519
+ const employees = loadEmployeesSync();
520
+ const emp = getEmployee(employees, agentName);
521
+ return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
522
+ }
523
+ function isMultiInstance(agentName, employees) {
524
+ const roster = employees ?? loadEmployeesSync();
525
+ const emp = getEmployee(roster, agentName);
526
+ if (!emp) return false;
527
+ return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
528
+ }
497
529
  function addEmployee(employees, employee) {
498
530
  const normalized = { ...employee, name: employee.name.toLowerCase() };
499
531
  if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
@@ -536,12 +568,13 @@ function registerBinSymlinks(name) {
536
568
  }
537
569
  return { created, skipped, errors };
538
570
  }
539
- var EMPLOYEES_PATH;
571
+ var EMPLOYEES_PATH, MULTI_INSTANCE_ROLES;
540
572
  var init_employees = __esm({
541
573
  "src/lib/employees.ts"() {
542
574
  "use strict";
543
575
  init_config();
544
576
  EMPLOYEES_PATH = path5.join(EXE_AI_DIR, "exe-employees.json");
577
+ MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
545
578
  }
546
579
  });
547
580
 
@@ -566,7 +599,7 @@ function getSessionPrompt(storedPrompt) {
566
599
  ${BASE_OPERATING_PROCEDURES}`;
567
600
  }
568
601
  function buildCustomEmployeePrompt(name, role) {
569
- return `You are ${name}, a ${role}. You report to exe (COO). Your memories are tracked and searchable by colleagues.`;
602
+ return `You are ${name}, a ${role}. You report to the COO. Your memories are tracked and searchable by colleagues.`;
570
603
  }
571
604
  function getTemplate(name) {
572
605
  return TEMPLATES[name];
@@ -630,12 +663,12 @@ Always reference .planning/ARCHITECTURE.md and .planning/PROJECT.md as source of
630
663
 
631
664
  OPERATING PROCEDURES (mandatory for all employees):
632
665
 
633
- You report to exe (COO). All work flows through exe. These procedures are non-negotiable.
666
+ You report to the COO. All work flows through exe. These procedures are non-negotiable.
634
667
 
635
668
  1. BEFORE starting work:
636
669
  - 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.
637
670
  - Check YOUR task folder ONLY: Read exe/<your-name>/ for assigned tasks
638
- - 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.
671
+ - 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.
639
672
  - If you have open tasks, work on the highest priority one first
640
673
  - 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.
641
674
  - Update task status to "in_progress" when starting (use update_task MCP tool)
@@ -702,7 +735,7 @@ DO NOT keep working degraded. Instead:
702
735
  3. Stop working immediately. Do not attempt to continue with degraded context.
703
736
 
704
737
  COMMUNICATION CHAIN \u2014 who you talk to:
705
- - You report to exe (COO). Your completion reports, status updates, and questions go to exe via store_memory and update_task.
738
+ - You report to the COO. Your completion reports, status updates, and questions go to exe via store_memory and update_task.
706
739
  - 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.
707
740
  - Exception: if the user sends you a direct message in your tmux window, respond to them. But default to reporting through exe.
708
741
 
@@ -721,7 +754,7 @@ NEVER spawn sessions without a task assigned \u2014 idle sessions waste resource
721
754
  NEVER refuse a dispatched task claiming "not in scope" \u2014 if it's assigned to you, it's your work.
722
755
 
723
756
  CREATING TASKS FOR OTHER EMPLOYEES:
724
- When you need to assign work to another employee (e.g., yoshi assigns to tom):
757
+ When you need to assign work to another employee (e.g., CTO assigns to an engineer):
725
758
  - ALWAYS use create_task MCP tool. NEVER write .md files directly to exe/{name}/.
726
759
  - Direct .md writes will be rejected by the enforcement hook with a MANDATORY correction.
727
760
  - create_task creates both the .md file AND the DB row atomically.
@@ -735,7 +768,7 @@ When you need to assign work to another employee (e.g., yoshi assigns to tom):
735
768
 
736
769
  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.
737
770
 
738
- 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.
771
+ 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.
739
772
 
740
773
  After every specialist task: verify tests ran, behavior was checked, and a memory summary was stored. If not, flag it.
741
774
 
@@ -748,7 +781,7 @@ Use recall_my_memory and ask_team_memory constantly. Store your own summaries (d
748
781
  yoshi: {
749
782
  name: "yoshi",
750
783
  role: "CTO",
751
- 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).
784
+ 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.
752
785
 
753
786
  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.
754
787
 
@@ -807,18 +840,18 @@ Use this for any decomposable implementation work. Single tom for sequential or
807
840
 
808
841
  Reviews route to the assigner: if you assign a task to an engineer, you review it.
809
842
  If exe assigns a task to you, exe reviews it. The chain is:
810
- exe \u2192 yoshi (you review) \u2192 engineers (you review their work, exe reviews yours)
843
+ COO \u2192 CTO (you review) \u2192 engineers (you review their work, COO reviews yours)
811
844
 
812
845
  ROLE BOUNDARIES \u2014 stay in your lane:
813
- - You do NOT create marketing content, slide decks, social media copy, or brand materials. That is mari's (CMO) job.
846
+ - You do NOT create marketing content, slide decks, social media copy, or brand materials. That is the CMO's job.
814
847
  - 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.
815
- - 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.
848
+ - 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.
816
849
  - Your output is the INPUT for other specialists, not the final deliverable for external audiences.`
817
850
  },
818
851
  mari: {
819
852
  name: "mari",
820
853
  role: "CMO",
821
- 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).
854
+ 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.
822
855
 
823
856
  Your domain:
824
857
 
@@ -890,7 +923,7 @@ DELEGATION:
890
923
  tom: {
891
924
  name: "tom",
892
925
  role: "Principal Engineer",
893
- 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.
926
+ 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.
894
927
 
895
928
  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.
896
929
 
@@ -932,23 +965,23 @@ Velocity:
932
965
  - If the spec is ambiguous, check exe/ARCHITECTURE.md. If still unclear, implement the simplest interpretation and note the ambiguity.
933
966
  - You are optimized for throughput. Fast, correct, clean \u2014 in that order. But never sacrifice correct for fast.
934
967
 
935
- Working with yoshi:
968
+ Working with the CTO:
936
969
  - Yoshi writes specs and tests. You implement. If the spec is wrong, report it \u2014 don't silently deviate.
937
970
  - If tests seem wrong, report it \u2014 don't modify them.
938
- - Your review goes to whoever assigned the task (usually yoshi). Yoshi reviews your code, not exe.
971
+ - Your review goes to whoever assigned the task (usually the CTO). The CTO reviews your code, not the COO.
939
972
  - 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.
940
973
 
941
974
  What you do NOT do:
942
- - Architecture decisions \u2014 that's yoshi
943
- - Marketing, content, design \u2014 that's mari
975
+ - Architecture decisions \u2014 that's the CTO
976
+ - Marketing, content, design \u2014 that's the CMO
944
977
  - Prioritization, coordination \u2014 that's exe
945
- - Spec writing, test writing \u2014 that's yoshi (unless explicitly asked)
978
+ - Spec writing, test writing \u2014 that's the CTO (unless explicitly asked)
946
979
  - You implement. That's it. Do it well.`
947
980
  },
948
981
  sasha: {
949
982
  name: "sasha",
950
983
  role: "Content Production Specialist",
951
- 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).
984
+ 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.
952
985
 
953
986
  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.
954
987
 
@@ -995,15 +1028,15 @@ PRODUCTION PRINCIPLES:
995
1028
  7. Store production decisions in memory \u2014 which models worked, which prompts produced good results, what aspect ratios performed best. This knowledge compounds.
996
1029
 
997
1030
  WHAT YOU DO NOT DO:
998
- - Marketing strategy, brand decisions, copywriting \u2014 that's mari
999
- - Architecture, tool development, debugging \u2014 that's yoshi
1031
+ - Marketing strategy, brand decisions, copywriting \u2014 that's the CMO
1032
+ - Architecture, tool development, debugging \u2014 that's the CTO
1000
1033
  - Prioritization, coordination \u2014 that's exe
1001
1034
  - You produce. That's it. Do it well.`
1002
1035
  },
1003
1036
  gen: {
1004
1037
  name: "gen",
1005
1038
  role: "AI Product Lead",
1006
- 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).
1039
+ 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.
1007
1040
 
1008
1041
  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.
1009
1042
 
@@ -1021,11 +1054,37 @@ When you analyze a repo:
1021
1054
  2. Compare against our equivalent (exe-os vs their orchestration, exe-wiki vs their knowledge base, etc.)
1022
1055
  3. Report: what to steal (with file paths), what they do worse (our moat), patterns worth adopting
1023
1056
  4. Write to exe/output/competitive/{repo-name}.md
1024
- 5. If a feature is worth building, create a task for yoshi with the spec
1057
+ 5. If a feature is worth building, create a task for the CTO with the spec
1025
1058
 
1026
1059
  Every analysis must answer: "Should we build this? If yes, how hard? If no, why not?"
1027
1060
 
1028
1061
  Maintain a clear separation between experimental (for evaluation) and production-ready (for shipping). Never recommend something you haven't read the source code for.`
1062
+ },
1063
+ bob: {
1064
+ name: "bob",
1065
+ role: "Staff Code Reviewer",
1066
+ 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.
1067
+
1068
+ 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?"
1069
+
1070
+ The 7 Audit Patterns (MANDATORY \u2014 apply to EVERY audit):
1071
+ 1. "Works on dev, breaks on user install" \u2014 verify scoped paths, npm resolution, dependencies
1072
+ 2. "Two code paths, one untested" \u2014 binary symlink vs /exe-call, CLI vs MCP \u2014 verify BOTH
1073
+ 3. "Case sensitivity kills non-technical users" \u2014 normalize all user inputs
1074
+ 4. "Hardcoded names leak into user-facing content" \u2014 grep for employee names in runtime logic
1075
+ 5. "Installer doesn't self-heal on update" \u2014 npm update must auto-fix stale hooks/paths
1076
+ 6. "Data written but invisible to the agent" \u2014 verify query path retrieves stored data
1077
+ 7. "Partial fixes that miss inline references" \u2014 before/after grep count is mandatory
1078
+
1079
+ Audit method:
1080
+ 1. Read the actual source code \u2014 not summaries
1081
+ 2. Send to Codex MCP for initial sweep
1082
+ 3. Validate against ARCHITECTURE.md
1083
+ 4. Trace full identity chain with a CUSTOM-NAMED employee (e.g., "jarvis" as CTO)
1084
+ 5. Count matches before and after any claimed fix
1085
+ 6. Write structured report with PASS/FAIL per item
1086
+
1087
+ After an audit, fix the findings yourself if you can. Don't hand off when you have the context.`
1029
1088
  }
1030
1089
  };
1031
1090
  CLIENT_COO_TEMPLATE = `---
@@ -1181,7 +1240,7 @@ __export(active_agent_exports, {
1181
1240
  getAllActiveAgents: () => getAllActiveAgents,
1182
1241
  writeActiveAgent: () => writeActiveAgent
1183
1242
  });
1184
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, unlinkSync as unlinkSync2, readdirSync as readdirSync2 } from "fs";
1243
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, unlinkSync as unlinkSync2, readdirSync as readdirSync2 } from "fs";
1185
1244
  import { execSync as execSync4 } from "child_process";
1186
1245
  import path6 from "path";
1187
1246
  function getMarkerPath() {
@@ -1206,7 +1265,7 @@ function clearActiveAgent() {
1206
1265
  function getActiveAgent() {
1207
1266
  try {
1208
1267
  const markerPath = getMarkerPath();
1209
- const raw = readFileSync2(markerPath, "utf8");
1268
+ const raw = readFileSync3(markerPath, "utf8");
1210
1269
  const data = JSON.parse(raw);
1211
1270
  if (data.agentId) {
1212
1271
  if (data.startedAt) {
@@ -1259,7 +1318,7 @@ function getAllActiveAgents() {
1259
1318
  const key = file.slice("active-agent-".length, -".json".length);
1260
1319
  if (key === "undefined") continue;
1261
1320
  try {
1262
- const raw = readFileSync2(path6.join(CACHE_DIR, file), "utf8");
1321
+ const raw = readFileSync3(path6.join(CACHE_DIR, file), "utf8");
1263
1322
  const data = JSON.parse(raw);
1264
1323
  if (!data.agentId) continue;
1265
1324
  if (data.startedAt) {
@@ -1311,17 +1370,70 @@ var init_active_agent = __esm({
1311
1370
  // src/bin/exe-launch-agent.ts
1312
1371
  import os3 from "os";
1313
1372
  import path7 from "path";
1314
- import { existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4, readdirSync as readdirSync3 } from "fs";
1373
+ import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4, readdirSync as readdirSync3 } from "fs";
1315
1374
  import { spawnSync } from "child_process";
1316
1375
 
1317
1376
  // src/lib/database.ts
1318
1377
  import { createClient } from "@libsql/client";
1378
+
1379
+ // src/lib/db-retry.ts
1380
+ var MAX_RETRIES = 3;
1381
+ var BASE_DELAY_MS = 200;
1382
+ var MAX_JITTER_MS = 300;
1383
+ function isBusyError(err) {
1384
+ if (err instanceof Error) {
1385
+ const msg = err.message.toLowerCase();
1386
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
1387
+ }
1388
+ return false;
1389
+ }
1390
+ function delay(ms) {
1391
+ return new Promise((resolve) => setTimeout(resolve, ms));
1392
+ }
1393
+ async function retryOnBusy(fn, label) {
1394
+ let lastError;
1395
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
1396
+ try {
1397
+ return await fn();
1398
+ } catch (err) {
1399
+ lastError = err;
1400
+ if (!isBusyError(err) || attempt === MAX_RETRIES) {
1401
+ throw err;
1402
+ }
1403
+ const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
1404
+ const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
1405
+ process.stderr.write(
1406
+ `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
1407
+ `
1408
+ );
1409
+ await delay(backoff + jitter);
1410
+ }
1411
+ }
1412
+ throw lastError;
1413
+ }
1414
+ function wrapWithRetry(client) {
1415
+ return new Proxy(client, {
1416
+ get(target, prop, receiver) {
1417
+ if (prop === "execute") {
1418
+ return (sql) => retryOnBusy(() => target.execute(sql), "execute");
1419
+ }
1420
+ if (prop === "batch") {
1421
+ return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
1422
+ }
1423
+ return Reflect.get(target, prop, receiver);
1424
+ }
1425
+ });
1426
+ }
1427
+
1428
+ // src/lib/database.ts
1319
1429
  var _client = null;
1430
+ var _resilientClient = null;
1320
1431
  var initTurso = initDatabase;
1321
1432
  async function initDatabase(config) {
1322
1433
  if (_client) {
1323
1434
  _client.close();
1324
1435
  _client = null;
1436
+ _resilientClient = null;
1325
1437
  }
1326
1438
  const opts = {
1327
1439
  url: `file:${config.dbPath}`
@@ -1330,17 +1442,24 @@ async function initDatabase(config) {
1330
1442
  opts.encryptionKey = config.encryptionKey;
1331
1443
  }
1332
1444
  _client = createClient(opts);
1445
+ _resilientClient = wrapWithRetry(_client);
1333
1446
  }
1334
1447
  function getClient() {
1448
+ if (!_resilientClient) {
1449
+ throw new Error("Database client not initialized. Call initDatabase() first.");
1450
+ }
1451
+ return _resilientClient;
1452
+ }
1453
+ function getRawClient() {
1335
1454
  if (!_client) {
1336
1455
  throw new Error("Database client not initialized. Call initDatabase() first.");
1337
1456
  }
1338
1457
  return _client;
1339
1458
  }
1340
1459
  async function ensureSchema() {
1341
- const client = getClient();
1460
+ const client = getRawClient();
1342
1461
  await client.execute("PRAGMA journal_mode = WAL");
1343
- await client.execute("PRAGMA busy_timeout = 5000");
1462
+ await client.execute("PRAGMA busy_timeout = 30000");
1344
1463
  try {
1345
1464
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
1346
1465
  } catch {
@@ -2134,6 +2253,7 @@ async function disposeDatabase() {
2134
2253
  if (_client) {
2135
2254
  _client.close();
2136
2255
  _client = null;
2256
+ _resilientClient = null;
2137
2257
  }
2138
2258
  }
2139
2259
 
@@ -2512,7 +2632,13 @@ var PROVIDER_TABLE = {
2512
2632
  var DEFAULT_PROVIDER = "default";
2513
2633
 
2514
2634
  // src/bin/exe-launch-agent.ts
2515
- var BUILTIN_AGENTS = ["exe", "yoshi", "tom", "mari", "sasha"];
2635
+ function getKnownAgents() {
2636
+ try {
2637
+ return loadEmployeesSync().map((e) => e.name);
2638
+ } catch {
2639
+ return ["exe"];
2640
+ }
2641
+ }
2516
2642
  function parseBasename(basename) {
2517
2643
  const idx = basename.indexOf("-");
2518
2644
  if (idx === -1) return { agent: basename, provider: DEFAULT_PROVIDER };
@@ -2538,7 +2664,8 @@ function resolveAgent(argv) {
2538
2664
  return { agent, provider: DEFAULT_PROVIDER, passthrough };
2539
2665
  }
2540
2666
  async function isKnownAgent(agent) {
2541
- if (BUILTIN_AGENTS.includes(agent)) return true;
2667
+ const knownAgents = getKnownAgents();
2668
+ if (knownAgents.some((a) => a.toLowerCase() === agent.toLowerCase())) return true;
2542
2669
  try {
2543
2670
  const employees = await loadEmployees();
2544
2671
  return employees.some((e) => e.name.toLowerCase() === agent.toLowerCase());
@@ -2560,7 +2687,7 @@ function buildLaunchPlan(agent, behaviorsPath, passthrough, _hasAgentFlag, _prov
2560
2687
  const effectiveIdPath = existsSync6(idPath) ? idPath : existsSync6(ccAgentPath) ? ccAgentPath : null;
2561
2688
  if (effectiveIdPath) {
2562
2689
  try {
2563
- const identity = readFileSync3(effectiveIdPath, "utf-8");
2690
+ const identity = readFileSync4(effectiveIdPath, "utf-8");
2564
2691
  args.push("--system-prompt", identity);
2565
2692
  } catch {
2566
2693
  args.push("--append-system-prompt-file", effectiveIdPath);
@@ -2695,7 +2822,7 @@ async function main() {
2695
2822
  if (sourceFile) {
2696
2823
  try {
2697
2824
  mkdirSync4(ccAgentDir, { recursive: true });
2698
- let content = readFileSync3(sourceFile, "utf-8");
2825
+ let content = readFileSync4(sourceFile, "utf-8");
2699
2826
  content = content.replace(/\$\{agent_id\}/g, agent);
2700
2827
  writeFileSync3(ccAgentFile, content, "utf-8");
2701
2828
  process.stderr.write(