@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
@@ -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
  });
@@ -1103,7 +1168,7 @@ function listShards() {
1103
1168
  }
1104
1169
  async function ensureShardSchema(client) {
1105
1170
  await client.execute("PRAGMA journal_mode = WAL");
1106
- await client.execute("PRAGMA busy_timeout = 5000");
1171
+ await client.execute("PRAGMA busy_timeout = 30000");
1107
1172
  try {
1108
1173
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
1109
1174
  } catch {
@@ -1289,7 +1354,7 @@ var init_shard_manager = __esm({
1289
1354
 
1290
1355
  // src/lib/employees.ts
1291
1356
  import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
1292
- import { existsSync as existsSync4, symlinkSync, readlinkSync } from "fs";
1357
+ import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync2 } from "fs";
1293
1358
  import { execSync } from "child_process";
1294
1359
  import path4 from "path";
1295
1360
  var EMPLOYEES_PATH;
@@ -1306,7 +1371,7 @@ import crypto2 from "crypto";
1306
1371
  import path5 from "path";
1307
1372
  import os2 from "os";
1308
1373
  import {
1309
- readFileSync as readFileSync2,
1374
+ readFileSync as readFileSync3,
1310
1375
  readdirSync,
1311
1376
  unlinkSync,
1312
1377
  existsSync as existsSync5,
@@ -1324,7 +1389,7 @@ import crypto3 from "crypto";
1324
1389
  import path6 from "path";
1325
1390
  import { execSync as execSync2 } from "child_process";
1326
1391
  import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
1327
- import { existsSync as existsSync6, readFileSync as readFileSync3 } from "fs";
1392
+ import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
1328
1393
  var init_tasks_crud = __esm({
1329
1394
  "src/lib/tasks-crud.ts"() {
1330
1395
  "use strict";
@@ -1388,7 +1453,7 @@ var init_provider_table = __esm({
1388
1453
  });
1389
1454
 
1390
1455
  // src/lib/intercom-queue.ts
1391
- import { readFileSync as readFileSync4, writeFileSync, renameSync as renameSync2, existsSync as existsSync7, mkdirSync as mkdirSync2 } from "fs";
1456
+ import { readFileSync as readFileSync5, writeFileSync, renameSync as renameSync2, existsSync as existsSync7, mkdirSync as mkdirSync2 } from "fs";
1392
1457
  import path8 from "path";
1393
1458
  import os4 from "os";
1394
1459
  var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
@@ -1402,7 +1467,7 @@ var init_intercom_queue = __esm({
1402
1467
  });
1403
1468
 
1404
1469
  // src/lib/license.ts
1405
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync3 } from "fs";
1470
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync3 } from "fs";
1406
1471
  import { randomUUID } from "crypto";
1407
1472
  import path9 from "path";
1408
1473
  import { jwtVerify, importSPKI } from "jose";
@@ -1418,7 +1483,7 @@ var init_license = __esm({
1418
1483
  });
1419
1484
 
1420
1485
  // src/lib/plan-limits.ts
1421
- import { readFileSync as readFileSync6, existsSync as existsSync9 } from "fs";
1486
+ import { readFileSync as readFileSync7, existsSync as existsSync9 } from "fs";
1422
1487
  import path10 from "path";
1423
1488
  var CACHE_PATH2;
1424
1489
  var init_plan_limits = __esm({
@@ -1436,7 +1501,7 @@ var init_plan_limits = __esm({
1436
1501
  import path11 from "path";
1437
1502
  import os5 from "os";
1438
1503
  import { fileURLToPath as fileURLToPath2 } from "url";
1439
- var SESSION_CACHE, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS;
1504
+ var SPAWN_LOCK_DIR, SESSION_CACHE, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS;
1440
1505
  var init_tmux_routing = __esm({
1441
1506
  "src/lib/tmux-routing.ts"() {
1442
1507
  "use strict";
@@ -1448,6 +1513,7 @@ var init_tmux_routing = __esm({
1448
1513
  init_provider_table();
1449
1514
  init_intercom_queue();
1450
1515
  init_plan_limits();
1516
+ SPAWN_LOCK_DIR = path11.join(os5.homedir(), ".exe-os", "spawn-locks");
1451
1517
  SESSION_CACHE = path11.join(os5.homedir(), ".exe-os", "session-cache");
1452
1518
  INTERCOM_LOG2 = path11.join(os5.homedir(), ".exe-os", "intercom.log");
1453
1519
  DEBOUNCE_FILE = path11.join(SESSION_CACHE, "intercom-debounce.json");
@@ -1483,7 +1549,7 @@ var init_tasks_review = __esm({
1483
1549
 
1484
1550
  // src/bin/exe-heartbeat.ts
1485
1551
  import { createHash } from "crypto";
1486
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4 } from "fs";
1552
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4 } from "fs";
1487
1553
  import os6 from "os";
1488
1554
  import path13 from "path";
1489
1555
 
@@ -1622,7 +1688,7 @@ function getMarkerPath() {
1622
1688
  var UNREAD_MESSAGE_STATUSES = ["pending", "delivered"];
1623
1689
  function readMarker() {
1624
1690
  try {
1625
- const raw = readFileSync7(getMarkerPath(), "utf8");
1691
+ const raw = readFileSync8(getMarkerPath(), "utf8");
1626
1692
  const parsed = JSON.parse(raw);
1627
1693
  if (typeof parsed.lastFiredAt !== "string" || typeof parsed.lastSurfaceHash !== "string") {
1628
1694
  return null;
@@ -1740,7 +1806,6 @@ async function runHeartbeat() {
1740
1806
  writeMarker({ lastFiredAt: new Date(now).toISOString(), lastSurfaceHash: hash });
1741
1807
  try {
1742
1808
  const client = getClient();
1743
- await client.execute({ sql: "PRAGMA busy_timeout = 5000", args: [] });
1744
1809
  await client.execute({
1745
1810
  sql: `UPDATE messages SET status = 'acknowledged', processed_at = datetime('now')
1746
1811
  WHERE target_agent = 'exe' AND status IN ('pending', 'delivered')`,
@@ -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
 
@@ -1366,6 +1427,13 @@ async function flushBatch() {
1366
1427
  _flushing = true;
1367
1428
  try {
1368
1429
  const batch = _pendingRecords.slice(0);
1430
+ const client = getClient();
1431
+ const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
1432
+ let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
1433
+ for (const row of batch) {
1434
+ row.version = baseVersion++;
1435
+ }
1436
+ _nextVersion = baseVersion;
1369
1437
  const buildStmt = (row) => {
1370
1438
  const hasVector = row.vector !== null;
1371
1439
  const taskId = row.task_id ?? null;
@@ -262,7 +262,7 @@ function listShards() {
262
262
  }
263
263
  async function ensureShardSchema(client) {
264
264
  await client.execute("PRAGMA journal_mode = WAL");
265
- await client.execute("PRAGMA busy_timeout = 5000");
265
+ await client.execute("PRAGMA busy_timeout = 30000");
266
266
  try {
267
267
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
268
268
  } catch {
@@ -452,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
 
@@ -1026,6 +1059,32 @@ When you analyze a repo:
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) {
@@ -1236,7 +1295,7 @@ function getActiveAgent() {
1236
1295
  "tmux display-message -p '#{session_name}' 2>/dev/null",
1237
1296
  { encoding: "utf8", timeout: 2e3 }
1238
1297
  ).trim();
1239
- const empMatch = sessionName.match(/^(\w+)-exe\d+$/);
1298
+ const empMatch = sessionName.match(/^([a-zA-Z]+)\d*-exe\d+$/);
1240
1299
  if (empMatch && empMatch[1] !== "exe") {
1241
1300
  return { agentId: empMatch[1], agentRole: "employee" };
1242
1301
  }
@@ -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
 
@@ -2231,6 +2351,13 @@ async function flushBatch() {
2231
2351
  _flushing = true;
2232
2352
  try {
2233
2353
  const batch = _pendingRecords.slice(0);
2354
+ const client = getClient();
2355
+ const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
2356
+ let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
2357
+ for (const row of batch) {
2358
+ row.version = baseVersion++;
2359
+ }
2360
+ _nextVersion = baseVersion;
2234
2361
  const buildStmt = (row) => {
2235
2362
  const hasVector = row.vector !== null;
2236
2363
  const taskId = row.task_id ?? null;
@@ -2512,7 +2639,13 @@ var PROVIDER_TABLE = {
2512
2639
  var DEFAULT_PROVIDER = "default";
2513
2640
 
2514
2641
  // src/bin/exe-launch-agent.ts
2515
- var BUILTIN_AGENTS = ["exe", "yoshi", "tom", "mari", "sasha"];
2642
+ function getKnownAgents() {
2643
+ try {
2644
+ return loadEmployeesSync().map((e) => e.name);
2645
+ } catch {
2646
+ return ["exe"];
2647
+ }
2648
+ }
2516
2649
  function parseBasename(basename) {
2517
2650
  const idx = basename.indexOf("-");
2518
2651
  if (idx === -1) return { agent: basename, provider: DEFAULT_PROVIDER };
@@ -2538,7 +2671,8 @@ function resolveAgent(argv) {
2538
2671
  return { agent, provider: DEFAULT_PROVIDER, passthrough };
2539
2672
  }
2540
2673
  async function isKnownAgent(agent) {
2541
- if (BUILTIN_AGENTS.includes(agent)) return true;
2674
+ const knownAgents = getKnownAgents();
2675
+ if (knownAgents.some((a) => a.toLowerCase() === agent.toLowerCase())) return true;
2542
2676
  try {
2543
2677
  const employees = await loadEmployees();
2544
2678
  return employees.some((e) => e.name.toLowerCase() === agent.toLowerCase());
@@ -2560,7 +2694,7 @@ function buildLaunchPlan(agent, behaviorsPath, passthrough, _hasAgentFlag, _prov
2560
2694
  const effectiveIdPath = existsSync6(idPath) ? idPath : existsSync6(ccAgentPath) ? ccAgentPath : null;
2561
2695
  if (effectiveIdPath) {
2562
2696
  try {
2563
- const identity = readFileSync3(effectiveIdPath, "utf-8");
2697
+ const identity = readFileSync4(effectiveIdPath, "utf-8");
2564
2698
  args.push("--system-prompt", identity);
2565
2699
  } catch {
2566
2700
  args.push("--append-system-prompt-file", effectiveIdPath);
@@ -2695,7 +2829,7 @@ async function main() {
2695
2829
  if (sourceFile) {
2696
2830
  try {
2697
2831
  mkdirSync4(ccAgentDir, { recursive: true });
2698
- let content = readFileSync3(sourceFile, "utf-8");
2832
+ let content = readFileSync4(sourceFile, "utf-8");
2699
2833
  content = content.replace(/\$\{agent_id\}/g, agent);
2700
2834
  writeFileSync3(ccAgentFile, content, "utf-8");
2701
2835
  process.stderr.write(