@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
@@ -25,12 +25,68 @@ var __copyProps = (to, from, except, desc) => {
25
25
  };
26
26
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
27
27
 
28
+ // src/lib/db-retry.ts
29
+ function isBusyError(err) {
30
+ if (err instanceof Error) {
31
+ const msg = err.message.toLowerCase();
32
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
33
+ }
34
+ return false;
35
+ }
36
+ function delay(ms) {
37
+ return new Promise((resolve) => setTimeout(resolve, ms));
38
+ }
39
+ async function retryOnBusy(fn, label) {
40
+ let lastError;
41
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
42
+ try {
43
+ return await fn();
44
+ } catch (err) {
45
+ lastError = err;
46
+ if (!isBusyError(err) || attempt === MAX_RETRIES) {
47
+ throw err;
48
+ }
49
+ const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
50
+ const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
51
+ process.stderr.write(
52
+ `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
53
+ `
54
+ );
55
+ await delay(backoff + jitter);
56
+ }
57
+ }
58
+ throw lastError;
59
+ }
60
+ function wrapWithRetry(client) {
61
+ return new Proxy(client, {
62
+ get(target, prop, receiver) {
63
+ if (prop === "execute") {
64
+ return (sql) => retryOnBusy(() => target.execute(sql), "execute");
65
+ }
66
+ if (prop === "batch") {
67
+ return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
68
+ }
69
+ return Reflect.get(target, prop, receiver);
70
+ }
71
+ });
72
+ }
73
+ var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
74
+ var init_db_retry = __esm({
75
+ "src/lib/db-retry.ts"() {
76
+ "use strict";
77
+ MAX_RETRIES = 3;
78
+ BASE_DELAY_MS = 200;
79
+ MAX_JITTER_MS = 300;
80
+ }
81
+ });
82
+
28
83
  // src/lib/database.ts
29
84
  import { createClient } from "@libsql/client";
30
85
  async function initDatabase(config) {
31
86
  if (_client) {
32
87
  _client.close();
33
88
  _client = null;
89
+ _resilientClient = null;
34
90
  }
35
91
  const opts = {
36
92
  url: `file:${config.dbPath}`
@@ -39,17 +95,24 @@ async function initDatabase(config) {
39
95
  opts.encryptionKey = config.encryptionKey;
40
96
  }
41
97
  _client = createClient(opts);
98
+ _resilientClient = wrapWithRetry(_client);
42
99
  }
43
100
  function getClient() {
101
+ if (!_resilientClient) {
102
+ throw new Error("Database client not initialized. Call initDatabase() first.");
103
+ }
104
+ return _resilientClient;
105
+ }
106
+ function getRawClient() {
44
107
  if (!_client) {
45
108
  throw new Error("Database client not initialized. Call initDatabase() first.");
46
109
  }
47
110
  return _client;
48
111
  }
49
112
  async function ensureSchema() {
50
- const client = getClient();
113
+ const client = getRawClient();
51
114
  await client.execute("PRAGMA journal_mode = WAL");
52
- await client.execute("PRAGMA busy_timeout = 5000");
115
+ await client.execute("PRAGMA busy_timeout = 30000");
53
116
  try {
54
117
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
55
118
  } catch {
@@ -838,11 +901,13 @@ async function ensureSchema() {
838
901
  }
839
902
  }
840
903
  }
841
- var _client, initTurso;
904
+ var _client, _resilientClient, initTurso;
842
905
  var init_database = __esm({
843
906
  "src/lib/database.ts"() {
844
907
  "use strict";
908
+ init_db_retry();
845
909
  _client = null;
910
+ _resilientClient = null;
846
911
  initTurso = initDatabase;
847
912
  }
848
913
  });
@@ -1094,7 +1159,7 @@ function listShards() {
1094
1159
  }
1095
1160
  async function ensureShardSchema(client) {
1096
1161
  await client.execute("PRAGMA journal_mode = WAL");
1097
- await client.execute("PRAGMA busy_timeout = 5000");
1162
+ await client.execute("PRAGMA busy_timeout = 30000");
1098
1163
  try {
1099
1164
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
1100
1165
  } catch {
@@ -1508,21 +1573,139 @@ var init_tasks_crud = __esm({
1508
1573
  });
1509
1574
 
1510
1575
  // src/lib/employees.ts
1576
+ var employees_exports = {};
1577
+ __export(employees_exports, {
1578
+ EMPLOYEES_PATH: () => EMPLOYEES_PATH,
1579
+ addEmployee: () => addEmployee,
1580
+ getEmployee: () => getEmployee,
1581
+ getEmployeeByRole: () => getEmployeeByRole,
1582
+ getEmployeeNamesByRole: () => getEmployeeNamesByRole,
1583
+ hasRole: () => hasRole,
1584
+ isMultiInstance: () => isMultiInstance,
1585
+ loadEmployees: () => loadEmployees,
1586
+ loadEmployeesSync: () => loadEmployeesSync,
1587
+ registerBinSymlinks: () => registerBinSymlinks,
1588
+ saveEmployees: () => saveEmployees,
1589
+ validateEmployeeName: () => validateEmployeeName
1590
+ });
1511
1591
  import { readFile as readFile3, writeFile as writeFile4, mkdir as mkdir4 } from "fs/promises";
1512
- import { existsSync as existsSync6, symlinkSync, readlinkSync } from "fs";
1592
+ import { existsSync as existsSync6, symlinkSync, readlinkSync, readFileSync as readFileSync4 } from "fs";
1513
1593
  import { execSync as execSync2 } from "child_process";
1514
1594
  import path6 from "path";
1515
- var EMPLOYEES_PATH;
1595
+ function validateEmployeeName(name) {
1596
+ if (!name) {
1597
+ return { valid: false, error: "Name is required" };
1598
+ }
1599
+ if (name.length > 32) {
1600
+ return { valid: false, error: "Name must be 32 characters or fewer" };
1601
+ }
1602
+ if (!/^[a-z][a-z0-9]*$/.test(name)) {
1603
+ return {
1604
+ valid: false,
1605
+ error: "Name must start with a letter and contain only lowercase alphanumeric characters"
1606
+ };
1607
+ }
1608
+ return { valid: true };
1609
+ }
1610
+ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
1611
+ if (!existsSync6(employeesPath)) {
1612
+ return [];
1613
+ }
1614
+ const raw = await readFile3(employeesPath, "utf-8");
1615
+ try {
1616
+ return JSON.parse(raw);
1617
+ } catch {
1618
+ return [];
1619
+ }
1620
+ }
1621
+ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
1622
+ await mkdir4(path6.dirname(employeesPath), { recursive: true });
1623
+ await writeFile4(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
1624
+ }
1625
+ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
1626
+ if (!existsSync6(employeesPath)) return [];
1627
+ try {
1628
+ return JSON.parse(readFileSync4(employeesPath, "utf-8"));
1629
+ } catch {
1630
+ return [];
1631
+ }
1632
+ }
1633
+ function getEmployee(employees, name) {
1634
+ return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
1635
+ }
1636
+ function getEmployeeByRole(employees, role) {
1637
+ const lower = role.toLowerCase();
1638
+ return employees.find((e) => e.role.toLowerCase() === lower);
1639
+ }
1640
+ function getEmployeeNamesByRole(employees, role) {
1641
+ const lower = role.toLowerCase();
1642
+ return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
1643
+ }
1644
+ function hasRole(agentName, role) {
1645
+ const employees = loadEmployeesSync();
1646
+ const emp = getEmployee(employees, agentName);
1647
+ return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
1648
+ }
1649
+ function isMultiInstance(agentName, employees) {
1650
+ const roster = employees ?? loadEmployeesSync();
1651
+ const emp = getEmployee(roster, agentName);
1652
+ if (!emp) return false;
1653
+ return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
1654
+ }
1655
+ function addEmployee(employees, employee) {
1656
+ const normalized = { ...employee, name: employee.name.toLowerCase() };
1657
+ if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
1658
+ throw new Error(`Employee '${normalized.name}' already exists`);
1659
+ }
1660
+ return [...employees, normalized];
1661
+ }
1662
+ function registerBinSymlinks(name) {
1663
+ const created = [];
1664
+ const skipped = [];
1665
+ const errors = [];
1666
+ let exeBinPath;
1667
+ try {
1668
+ exeBinPath = execSync2("which exe", { encoding: "utf-8" }).trim();
1669
+ } catch {
1670
+ errors.push("Could not find 'exe' in PATH");
1671
+ return { created, skipped, errors };
1672
+ }
1673
+ const binDir = path6.dirname(exeBinPath);
1674
+ let target;
1675
+ try {
1676
+ target = readlinkSync(exeBinPath);
1677
+ } catch {
1678
+ errors.push("Could not read 'exe' symlink");
1679
+ return { created, skipped, errors };
1680
+ }
1681
+ for (const suffix of ["", "-opencode"]) {
1682
+ const linkName = `${name}${suffix}`;
1683
+ const linkPath = path6.join(binDir, linkName);
1684
+ if (existsSync6(linkPath)) {
1685
+ skipped.push(linkName);
1686
+ continue;
1687
+ }
1688
+ try {
1689
+ symlinkSync(target, linkPath);
1690
+ created.push(linkName);
1691
+ } catch (err) {
1692
+ errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
1693
+ }
1694
+ }
1695
+ return { created, skipped, errors };
1696
+ }
1697
+ var EMPLOYEES_PATH, MULTI_INSTANCE_ROLES;
1516
1698
  var init_employees = __esm({
1517
1699
  "src/lib/employees.ts"() {
1518
1700
  "use strict";
1519
1701
  init_config();
1520
1702
  EMPLOYEES_PATH = path6.join(EXE_AI_DIR, "exe-employees.json");
1703
+ MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
1521
1704
  }
1522
1705
  });
1523
1706
 
1524
1707
  // src/lib/session-registry.ts
1525
- import { readFileSync as readFileSync4, writeFileSync, mkdirSync as mkdirSync2, existsSync as existsSync7 } from "fs";
1708
+ import { readFileSync as readFileSync5, writeFileSync, mkdirSync as mkdirSync2, existsSync as existsSync7 } from "fs";
1526
1709
  import path7 from "path";
1527
1710
  import os3 from "os";
1528
1711
  function registerSession(entry) {
@@ -1541,7 +1724,7 @@ function registerSession(entry) {
1541
1724
  }
1542
1725
  function listSessions() {
1543
1726
  try {
1544
- const raw = readFileSync4(REGISTRY_PATH, "utf8");
1727
+ const raw = readFileSync5(REGISTRY_PATH, "utf8");
1545
1728
  return JSON.parse(raw);
1546
1729
  } catch {
1547
1730
  return [];
@@ -1771,7 +1954,7 @@ var init_provider_table = __esm({
1771
1954
  });
1772
1955
 
1773
1956
  // src/lib/intercom-queue.ts
1774
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, renameSync as renameSync2, existsSync as existsSync8, mkdirSync as mkdirSync3 } from "fs";
1957
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync2, renameSync as renameSync2, existsSync as existsSync8, mkdirSync as mkdirSync3 } from "fs";
1775
1958
  import path8 from "path";
1776
1959
  import os4 from "os";
1777
1960
  function ensureDir() {
@@ -1781,7 +1964,7 @@ function ensureDir() {
1781
1964
  function readQueue() {
1782
1965
  try {
1783
1966
  if (!existsSync8(QUEUE_PATH)) return [];
1784
- return JSON.parse(readFileSync5(QUEUE_PATH, "utf8"));
1967
+ return JSON.parse(readFileSync6(QUEUE_PATH, "utf8"));
1785
1968
  } catch {
1786
1969
  return [];
1787
1970
  }
@@ -1820,7 +2003,7 @@ var init_intercom_queue = __esm({
1820
2003
  });
1821
2004
 
1822
2005
  // src/lib/license.ts
1823
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, existsSync as existsSync9, mkdirSync as mkdirSync4 } from "fs";
2006
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync3, existsSync as existsSync9, mkdirSync as mkdirSync4 } from "fs";
1824
2007
  import { randomUUID } from "crypto";
1825
2008
  import path9 from "path";
1826
2009
  import { jwtVerify, importSPKI } from "jose";
@@ -1843,12 +2026,12 @@ var init_license = __esm({
1843
2026
  });
1844
2027
 
1845
2028
  // src/lib/plan-limits.ts
1846
- import { readFileSync as readFileSync7, existsSync as existsSync10 } from "fs";
2029
+ import { readFileSync as readFileSync8, existsSync as existsSync10 } from "fs";
1847
2030
  import path10 from "path";
1848
2031
  function getLicenseSync() {
1849
2032
  try {
1850
2033
  if (!existsSync10(CACHE_PATH2)) return freeLicense();
1851
- const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
2034
+ const raw = JSON.parse(readFileSync8(CACHE_PATH2, "utf8"));
1852
2035
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
1853
2036
  const parts = raw.token.split(".");
1854
2037
  if (parts.length !== 3) return freeLicense();
@@ -1887,7 +2070,7 @@ function assertEmployeeLimitSync(rosterPath) {
1887
2070
  let count = 0;
1888
2071
  try {
1889
2072
  if (existsSync10(filePath)) {
1890
- const raw = readFileSync7(filePath, "utf8");
2073
+ const raw = readFileSync8(filePath, "utf8");
1891
2074
  const employees = JSON.parse(raw);
1892
2075
  count = Array.isArray(employees) ? employees.length : 0;
1893
2076
  }
@@ -1922,7 +2105,7 @@ var init_plan_limits = __esm({
1922
2105
 
1923
2106
  // src/lib/tmux-routing.ts
1924
2107
  import { execFileSync as execFileSync2, execSync as execSync5 } from "child_process";
1925
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, existsSync as existsSync11, appendFileSync } from "fs";
2108
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, existsSync as existsSync11, appendFileSync } from "fs";
1926
2109
  import path11 from "path";
1927
2110
  import os5 from "os";
1928
2111
  import { fileURLToPath } from "url";
@@ -1971,7 +2154,7 @@ function extractRootExe(name) {
1971
2154
  }
1972
2155
  function getParentExe(sessionKey) {
1973
2156
  try {
1974
- const data = JSON.parse(readFileSync8(path11.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
2157
+ const data = JSON.parse(readFileSync9(path11.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
1975
2158
  return data.parentExe || null;
1976
2159
  } catch {
1977
2160
  return null;
@@ -2005,7 +2188,7 @@ function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive =
2005
2188
  function readDebounceState() {
2006
2189
  try {
2007
2190
  if (!existsSync11(DEBOUNCE_FILE)) return {};
2008
- return JSON.parse(readFileSync8(DEBOUNCE_FILE, "utf8"));
2191
+ return JSON.parse(readFileSync9(DEBOUNCE_FILE, "utf8"));
2009
2192
  } catch {
2010
2193
  return {};
2011
2194
  }
@@ -2179,7 +2362,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
2179
2362
  const claudeJsonPath = path11.join(os5.homedir(), ".claude.json");
2180
2363
  let claudeJson = {};
2181
2364
  try {
2182
- claudeJson = JSON.parse(readFileSync8(claudeJsonPath, "utf8"));
2365
+ claudeJson = JSON.parse(readFileSync9(claudeJsonPath, "utf8"));
2183
2366
  } catch {
2184
2367
  }
2185
2368
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -2197,7 +2380,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
2197
2380
  const settingsPath = path11.join(projSettingsDir, "settings.json");
2198
2381
  let settings = {};
2199
2382
  try {
2200
- settings = JSON.parse(readFileSync8(settingsPath, "utf8"));
2383
+ settings = JSON.parse(readFileSync9(settingsPath, "utf8"));
2201
2384
  } catch {
2202
2385
  }
2203
2386
  const perms = settings.permissions ?? {};
@@ -2544,7 +2727,7 @@ async function dispatchTaskToEmployee(input) {
2544
2727
  } else {
2545
2728
  const projectDir = input.projectDir ?? process.cwd();
2546
2729
  const result = ensureEmployee(input.assignedTo, exeSession, projectDir, {
2547
- autoInstance: input.assignedTo === "tom" || input.assignedTo === "sasha"
2730
+ autoInstance: isMultiInstance(input.assignedTo)
2548
2731
  });
2549
2732
  if (result.status === "failed") {
2550
2733
  process.stderr.write(
@@ -2566,6 +2749,7 @@ var init_tasks_notify = __esm({
2566
2749
  init_session_key();
2567
2750
  init_notifications();
2568
2751
  init_transport();
2752
+ init_employees();
2569
2753
  }
2570
2754
  });
2571
2755
 
@@ -2708,14 +2892,16 @@ async function main() {
2708
2892
  await initStore();
2709
2893
  const fpPrefix = fingerprint.slice(0, 8);
2710
2894
  const client = getClient();
2895
+ const { loadEmployeesSync: loadEmployeesSync2, getEmployeeByRole: getEmployeeByRole2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
2896
+ const ctoName = getEmployeeByRole2(loadEmployeesSync2(), "CTO")?.name ?? "yoshi";
2711
2897
  const existing = await client.execute({
2712
2898
  sql: `SELECT id FROM tasks
2713
- WHERE assigned_to = 'yoshi'
2899
+ WHERE assigned_to = ?
2714
2900
  AND status IN ('open', 'in_progress')
2715
2901
  AND title LIKE '[auto-bug]%'
2716
2902
  AND task_file LIKE ?
2717
2903
  LIMIT 1`,
2718
- args: [`%${fpPrefix}%`]
2904
+ args: [ctoName, `%${fpPrefix}%`]
2719
2905
  });
2720
2906
  if (existing.rows.length > 0) {
2721
2907
  process.stderr.write(`[bug-report-worker] Duplicate found for fingerprint ${fingerprint}, skipping
@@ -2751,7 +2937,7 @@ async function main() {
2751
2937
  ].join("\n");
2752
2938
  await createTask({
2753
2939
  title: `[auto-bug] ${toolName}: ${errorSummary}`,
2754
- assignedTo: "yoshi",
2940
+ assignedTo: ctoName,
2755
2941
  assignedBy: "system",
2756
2942
  projectName,
2757
2943
  priority: "p1",
@@ -23,6 +23,61 @@ var init_memory = __esm({
23
23
  }
24
24
  });
25
25
 
26
+ // src/lib/db-retry.ts
27
+ function isBusyError(err) {
28
+ if (err instanceof Error) {
29
+ const msg = err.message.toLowerCase();
30
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
31
+ }
32
+ return false;
33
+ }
34
+ function delay(ms) {
35
+ return new Promise((resolve) => setTimeout(resolve, ms));
36
+ }
37
+ async function retryOnBusy(fn, label) {
38
+ let lastError;
39
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
40
+ try {
41
+ return await fn();
42
+ } catch (err) {
43
+ lastError = err;
44
+ if (!isBusyError(err) || attempt === MAX_RETRIES) {
45
+ throw err;
46
+ }
47
+ const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
48
+ const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
49
+ process.stderr.write(
50
+ `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
51
+ `
52
+ );
53
+ await delay(backoff + jitter);
54
+ }
55
+ }
56
+ throw lastError;
57
+ }
58
+ function wrapWithRetry(client) {
59
+ return new Proxy(client, {
60
+ get(target, prop, receiver) {
61
+ if (prop === "execute") {
62
+ return (sql) => retryOnBusy(() => target.execute(sql), "execute");
63
+ }
64
+ if (prop === "batch") {
65
+ return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
66
+ }
67
+ return Reflect.get(target, prop, receiver);
68
+ }
69
+ });
70
+ }
71
+ var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
72
+ var init_db_retry = __esm({
73
+ "src/lib/db-retry.ts"() {
74
+ "use strict";
75
+ MAX_RETRIES = 3;
76
+ BASE_DELAY_MS = 200;
77
+ MAX_JITTER_MS = 300;
78
+ }
79
+ });
80
+
26
81
  // src/lib/database.ts
27
82
  var database_exports = {};
28
83
  __export(database_exports, {
@@ -30,6 +85,7 @@ __export(database_exports, {
30
85
  disposeTurso: () => disposeTurso,
31
86
  ensureSchema: () => ensureSchema,
32
87
  getClient: () => getClient,
88
+ getRawClient: () => getRawClient,
33
89
  initDatabase: () => initDatabase,
34
90
  initTurso: () => initTurso,
35
91
  isInitialized: () => isInitialized
@@ -39,6 +95,7 @@ async function initDatabase(config) {
39
95
  if (_client) {
40
96
  _client.close();
41
97
  _client = null;
98
+ _resilientClient = null;
42
99
  }
43
100
  const opts = {
44
101
  url: `file:${config.dbPath}`
@@ -47,20 +104,27 @@ async function initDatabase(config) {
47
104
  opts.encryptionKey = config.encryptionKey;
48
105
  }
49
106
  _client = createClient(opts);
107
+ _resilientClient = wrapWithRetry(_client);
50
108
  }
51
109
  function isInitialized() {
52
110
  return _client !== null;
53
111
  }
54
112
  function getClient() {
113
+ if (!_resilientClient) {
114
+ throw new Error("Database client not initialized. Call initDatabase() first.");
115
+ }
116
+ return _resilientClient;
117
+ }
118
+ function getRawClient() {
55
119
  if (!_client) {
56
120
  throw new Error("Database client not initialized. Call initDatabase() first.");
57
121
  }
58
122
  return _client;
59
123
  }
60
124
  async function ensureSchema() {
61
- const client = getClient();
125
+ const client = getRawClient();
62
126
  await client.execute("PRAGMA journal_mode = WAL");
63
- await client.execute("PRAGMA busy_timeout = 5000");
127
+ await client.execute("PRAGMA busy_timeout = 30000");
64
128
  try {
65
129
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
66
130
  } catch {
@@ -853,13 +917,16 @@ async function disposeDatabase() {
853
917
  if (_client) {
854
918
  _client.close();
855
919
  _client = null;
920
+ _resilientClient = null;
856
921
  }
857
922
  }
858
- var _client, initTurso, disposeTurso;
923
+ var _client, _resilientClient, initTurso, disposeTurso;
859
924
  var init_database = __esm({
860
925
  "src/lib/database.ts"() {
861
926
  "use strict";
927
+ init_db_retry();
862
928
  _client = null;
929
+ _resilientClient = null;
863
930
  initTurso = initDatabase;
864
931
  disposeTurso = disposeDatabase;
865
932
  }
@@ -1161,7 +1228,7 @@ function listShards() {
1161
1228
  }
1162
1229
  async function ensureShardSchema(client) {
1163
1230
  await client.execute("PRAGMA journal_mode = WAL");
1164
- await client.execute("PRAGMA busy_timeout = 5000");
1231
+ await client.execute("PRAGMA busy_timeout = 30000");
1165
1232
  try {
1166
1233
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
1167
1234
  } catch {