@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.
- package/dist/bin/backfill-conversations.js +332 -348
- package/dist/bin/backfill-responses.js +72 -12
- package/dist/bin/backfill-vectors.js +72 -12
- package/dist/bin/cleanup-stale-review-tasks.js +63 -3
- package/dist/bin/cli.js +1518 -1122
- package/dist/bin/exe-agent.js +4 -4
- package/dist/bin/exe-assign.js +80 -18
- package/dist/bin/exe-boot.js +408 -89
- package/dist/bin/exe-call.js +83 -24
- package/dist/bin/exe-dispatch.js +18 -10
- package/dist/bin/exe-doctor.js +63 -3
- package/dist/bin/exe-export-behaviors.js +64 -3
- package/dist/bin/exe-forget.js +69 -4
- package/dist/bin/exe-gateway.js +121 -36
- package/dist/bin/exe-heartbeat.js +77 -13
- package/dist/bin/exe-kill.js +64 -3
- package/dist/bin/exe-launch-agent.js +162 -35
- package/dist/bin/exe-link.js +946 -0
- package/dist/bin/exe-new-employee.js +121 -36
- package/dist/bin/exe-pending-messages.js +72 -7
- package/dist/bin/exe-pending-notifications.js +63 -3
- package/dist/bin/exe-pending-reviews.js +75 -10
- package/dist/bin/exe-rename.js +1287 -0
- package/dist/bin/exe-review.js +64 -4
- package/dist/bin/exe-search.js +79 -13
- package/dist/bin/exe-session-cleanup.js +91 -26
- package/dist/bin/exe-status.js +64 -4
- package/dist/bin/exe-team.js +64 -4
- package/dist/bin/git-sweep.js +71 -4
- package/dist/bin/graph-backfill.js +64 -3
- package/dist/bin/graph-export.js +64 -3
- package/dist/bin/install.js +3 -3
- package/dist/bin/scan-tasks.js +71 -4
- package/dist/bin/setup.js +156 -38
- package/dist/bin/shard-migrate.js +64 -3
- package/dist/bin/wiki-sync.js +64 -3
- package/dist/gateway/index.js +122 -37
- package/dist/hooks/bug-report-worker.js +209 -23
- package/dist/hooks/commit-complete.js +71 -4
- package/dist/hooks/error-recall.js +79 -13
- package/dist/hooks/ingest-worker.js +129 -43
- package/dist/hooks/instructions-loaded.js +71 -4
- package/dist/hooks/notification.js +71 -4
- package/dist/hooks/post-compact.js +71 -4
- package/dist/hooks/pre-compact.js +71 -4
- package/dist/hooks/pre-tool-use.js +413 -194
- package/dist/hooks/prompt-ingest-worker.js +82 -22
- package/dist/hooks/prompt-submit.js +103 -37
- package/dist/hooks/response-ingest-worker.js +87 -22
- package/dist/hooks/session-end.js +71 -4
- package/dist/hooks/session-start.js +79 -13
- package/dist/hooks/stop.js +71 -4
- package/dist/hooks/subagent-stop.js +71 -4
- package/dist/hooks/summary-worker.js +303 -50
- package/dist/index.js +134 -46
- package/dist/lib/cloud-sync.js +209 -15
- package/dist/lib/consolidation.js +4 -4
- package/dist/lib/database.js +64 -2
- package/dist/lib/device-registry.js +70 -3
- package/dist/lib/employee-templates.js +48 -22
- package/dist/lib/employees.js +34 -1
- package/dist/lib/exe-daemon.js +136 -53
- package/dist/lib/hybrid-search.js +79 -13
- package/dist/lib/identity-templates.js +57 -6
- package/dist/lib/identity.js +3 -3
- package/dist/lib/messaging.js +22 -14
- package/dist/lib/reminders.js +3 -3
- package/dist/lib/schedules.js +63 -3
- package/dist/lib/skill-learning.js +3 -3
- package/dist/lib/status-brief.js +63 -5
- package/dist/lib/store.js +64 -3
- package/dist/lib/task-router.js +4 -2
- package/dist/lib/tasks.js +48 -21
- package/dist/lib/tmux-routing.js +47 -20
- package/dist/mcp/server.js +727 -58
- package/dist/mcp/tools/complete-reminder.js +3 -3
- package/dist/mcp/tools/create-reminder.js +3 -3
- package/dist/mcp/tools/create-task.js +151 -24
- package/dist/mcp/tools/deactivate-behavior.js +3 -3
- package/dist/mcp/tools/list-reminders.js +3 -3
- package/dist/mcp/tools/list-tasks.js +17 -8
- package/dist/mcp/tools/send-message.js +24 -16
- package/dist/mcp/tools/update-task.js +25 -16
- package/dist/runtime/index.js +112 -24
- package/dist/tui/App.js +139 -36
- package/package.json +6 -2
- 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 =
|
|
113
|
+
const client = getRawClient();
|
|
51
114
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
52
|
-
await client.execute("PRAGMA busy_timeout =
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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(
|
|
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 =
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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 =
|
|
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:
|
|
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 =
|
|
125
|
+
const client = getRawClient();
|
|
62
126
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
63
|
-
await client.execute("PRAGMA busy_timeout =
|
|
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 =
|
|
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 {
|