@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.
- package/dist/bin/backfill-conversations.js +341 -349
- package/dist/bin/backfill-responses.js +81 -13
- package/dist/bin/backfill-vectors.js +72 -12
- package/dist/bin/cleanup-stale-review-tasks.js +63 -3
- package/dist/bin/cli.js +1737 -1117
- package/dist/bin/exe-assign.js +89 -19
- package/dist/bin/exe-boot.js +951 -101
- package/dist/bin/exe-call.js +61 -2
- package/dist/bin/exe-dispatch.js +61 -13
- package/dist/bin/exe-doctor.js +63 -3
- package/dist/bin/exe-export-behaviors.js +71 -3
- package/dist/bin/exe-forget.js +69 -4
- package/dist/bin/exe-gateway.js +178 -45
- package/dist/bin/exe-heartbeat.js +79 -14
- package/dist/bin/exe-kill.js +71 -3
- package/dist/bin/exe-launch-agent.js +148 -14
- package/dist/bin/exe-link.js +1437 -0
- package/dist/bin/exe-new-employee.js +98 -13
- package/dist/bin/exe-pending-messages.js +74 -8
- package/dist/bin/exe-pending-notifications.js +63 -3
- package/dist/bin/exe-pending-reviews.js +77 -11
- package/dist/bin/exe-rename.js +1287 -0
- package/dist/bin/exe-review.js +73 -5
- package/dist/bin/exe-search.js +88 -14
- package/dist/bin/exe-session-cleanup.js +102 -28
- package/dist/bin/exe-status.js +64 -4
- package/dist/bin/exe-team.js +64 -4
- package/dist/bin/git-sweep.js +80 -5
- package/dist/bin/graph-backfill.js +71 -3
- package/dist/bin/graph-export.js +71 -3
- package/dist/bin/install.js +38 -8
- package/dist/bin/scan-tasks.js +80 -5
- package/dist/bin/setup.js +128 -10
- package/dist/bin/shard-migrate.js +71 -3
- package/dist/bin/wiki-sync.js +71 -3
- package/dist/gateway/index.js +179 -46
- package/dist/hooks/bug-report-worker.js +254 -28
- package/dist/hooks/commit-complete.js +80 -5
- package/dist/hooks/error-recall.js +89 -15
- package/dist/hooks/exe-heartbeat-hook.js +1 -1
- package/dist/hooks/ingest-worker.js +185 -51
- package/dist/hooks/ingest.js +1 -1
- package/dist/hooks/instructions-loaded.js +81 -6
- package/dist/hooks/notification.js +81 -6
- package/dist/hooks/post-compact.js +81 -6
- package/dist/hooks/pre-compact.js +81 -6
- package/dist/hooks/pre-tool-use.js +423 -196
- package/dist/hooks/prompt-ingest-worker.js +91 -23
- package/dist/hooks/prompt-submit.js +159 -45
- package/dist/hooks/response-ingest-worker.js +96 -23
- package/dist/hooks/session-end.js +81 -6
- package/dist/hooks/session-start.js +89 -15
- package/dist/hooks/stop.js +81 -6
- package/dist/hooks/subagent-stop.js +81 -6
- package/dist/hooks/summary-worker.js +807 -55
- package/dist/index.js +198 -60
- package/dist/lib/cloud-sync.js +703 -18
- 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 +26 -0
- package/dist/lib/employees.js +34 -1
- package/dist/lib/exe-daemon.js +207 -74
- package/dist/lib/hybrid-search.js +88 -14
- package/dist/lib/identity-templates.js +51 -0
- package/dist/lib/identity.js +3 -3
- package/dist/lib/messaging.js +65 -17
- 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 +73 -4
- package/dist/lib/task-router.js +4 -2
- package/dist/lib/tasks.js +95 -28
- package/dist/lib/tmux-routing.js +92 -23
- package/dist/mcp/server.js +800 -74
- 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 +198 -31
- package/dist/mcp/tools/deactivate-behavior.js +4 -4
- package/dist/mcp/tools/list-reminders.js +3 -3
- package/dist/mcp/tools/list-tasks.js +19 -9
- package/dist/mcp/tools/send-message.js +69 -21
- package/dist/mcp/tools/update-task.js +28 -18
- package/dist/runtime/index.js +166 -28
- package/dist/tui/App.js +193 -40
- package/package.json +7 -3
- package/src/commands/exe/afk.md +116 -0
- 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,10 +2105,46 @@ 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";
|
|
2112
|
+
import { unlinkSync as unlinkSync2 } from "fs";
|
|
2113
|
+
function spawnLockPath(sessionName) {
|
|
2114
|
+
return path11.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
2115
|
+
}
|
|
2116
|
+
function isProcessAlive(pid) {
|
|
2117
|
+
try {
|
|
2118
|
+
process.kill(pid, 0);
|
|
2119
|
+
return true;
|
|
2120
|
+
} catch {
|
|
2121
|
+
return false;
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
function acquireSpawnLock(sessionName) {
|
|
2125
|
+
if (!existsSync11(SPAWN_LOCK_DIR)) {
|
|
2126
|
+
mkdirSync5(SPAWN_LOCK_DIR, { recursive: true });
|
|
2127
|
+
}
|
|
2128
|
+
const lockFile = spawnLockPath(sessionName);
|
|
2129
|
+
if (existsSync11(lockFile)) {
|
|
2130
|
+
try {
|
|
2131
|
+
const lock = JSON.parse(readFileSync9(lockFile, "utf8"));
|
|
2132
|
+
const age = Date.now() - lock.timestamp;
|
|
2133
|
+
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
2134
|
+
return false;
|
|
2135
|
+
}
|
|
2136
|
+
} catch {
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
writeFileSync4(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
2140
|
+
return true;
|
|
2141
|
+
}
|
|
2142
|
+
function releaseSpawnLock(sessionName) {
|
|
2143
|
+
try {
|
|
2144
|
+
unlinkSync2(spawnLockPath(sessionName));
|
|
2145
|
+
} catch {
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
1929
2148
|
function resolveBehaviorsExporterScript() {
|
|
1930
2149
|
try {
|
|
1931
2150
|
const thisFile = fileURLToPath(import.meta.url);
|
|
@@ -1971,7 +2190,7 @@ function extractRootExe(name) {
|
|
|
1971
2190
|
}
|
|
1972
2191
|
function getParentExe(sessionKey) {
|
|
1973
2192
|
try {
|
|
1974
|
-
const data = JSON.parse(
|
|
2193
|
+
const data = JSON.parse(readFileSync9(path11.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
1975
2194
|
return data.parentExe || null;
|
|
1976
2195
|
} catch {
|
|
1977
2196
|
return null;
|
|
@@ -1995,17 +2214,17 @@ function isEmployeeAlive(sessionName) {
|
|
|
1995
2214
|
}
|
|
1996
2215
|
function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
|
|
1997
2216
|
const base = employeeSessionName(employeeName, exeSession);
|
|
1998
|
-
if (!isAlive(base)) return 0;
|
|
2217
|
+
if (!isAlive(base) && acquireSpawnLock(base)) return 0;
|
|
1999
2218
|
for (let i = 2; i <= maxInstances; i++) {
|
|
2000
2219
|
const candidate = employeeSessionName(employeeName, exeSession, i);
|
|
2001
|
-
if (!isAlive(candidate)) return i;
|
|
2220
|
+
if (!isAlive(candidate) && acquireSpawnLock(candidate)) return i;
|
|
2002
2221
|
}
|
|
2003
2222
|
return null;
|
|
2004
2223
|
}
|
|
2005
2224
|
function readDebounceState() {
|
|
2006
2225
|
try {
|
|
2007
2226
|
if (!existsSync11(DEBOUNCE_FILE)) return {};
|
|
2008
|
-
return JSON.parse(
|
|
2227
|
+
return JSON.parse(readFileSync9(DEBOUNCE_FILE, "utf8"));
|
|
2009
2228
|
} catch {
|
|
2010
2229
|
return {};
|
|
2011
2230
|
}
|
|
@@ -2179,7 +2398,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
2179
2398
|
const claudeJsonPath = path11.join(os5.homedir(), ".claude.json");
|
|
2180
2399
|
let claudeJson = {};
|
|
2181
2400
|
try {
|
|
2182
|
-
claudeJson = JSON.parse(
|
|
2401
|
+
claudeJson = JSON.parse(readFileSync9(claudeJsonPath, "utf8"));
|
|
2183
2402
|
} catch {
|
|
2184
2403
|
}
|
|
2185
2404
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -2197,7 +2416,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
2197
2416
|
const settingsPath = path11.join(projSettingsDir, "settings.json");
|
|
2198
2417
|
let settings = {};
|
|
2199
2418
|
try {
|
|
2200
|
-
settings = JSON.parse(
|
|
2419
|
+
settings = JSON.parse(readFileSync9(settingsPath, "utf8"));
|
|
2201
2420
|
} catch {
|
|
2202
2421
|
}
|
|
2203
2422
|
const perms = settings.permissions ?? {};
|
|
@@ -2310,6 +2529,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
2310
2529
|
command: spawnCommand
|
|
2311
2530
|
});
|
|
2312
2531
|
if (spawnResult.error) {
|
|
2532
|
+
releaseSpawnLock(sessionName);
|
|
2313
2533
|
return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
|
|
2314
2534
|
}
|
|
2315
2535
|
transport.pipeLog(sessionName, logFile);
|
|
@@ -2347,6 +2567,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
2347
2567
|
}
|
|
2348
2568
|
}
|
|
2349
2569
|
if (!booted) {
|
|
2570
|
+
releaseSpawnLock(sessionName);
|
|
2350
2571
|
return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
|
|
2351
2572
|
}
|
|
2352
2573
|
if (!useExeAgent) {
|
|
@@ -2363,9 +2584,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
2363
2584
|
pid: 0,
|
|
2364
2585
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2365
2586
|
});
|
|
2587
|
+
releaseSpawnLock(sessionName);
|
|
2366
2588
|
return { sessionName };
|
|
2367
2589
|
}
|
|
2368
|
-
var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
2590
|
+
var SPAWN_LOCK_DIR, SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
2369
2591
|
var init_tmux_routing = __esm({
|
|
2370
2592
|
"src/lib/tmux-routing.ts"() {
|
|
2371
2593
|
"use strict";
|
|
@@ -2377,6 +2599,7 @@ var init_tmux_routing = __esm({
|
|
|
2377
2599
|
init_provider_table();
|
|
2378
2600
|
init_intercom_queue();
|
|
2379
2601
|
init_plan_limits();
|
|
2602
|
+
SPAWN_LOCK_DIR = path11.join(os5.homedir(), ".exe-os", "spawn-locks");
|
|
2380
2603
|
SESSION_CACHE = path11.join(os5.homedir(), ".exe-os", "session-cache");
|
|
2381
2604
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
2382
2605
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
@@ -2389,7 +2612,7 @@ var init_tmux_routing = __esm({
|
|
|
2389
2612
|
|
|
2390
2613
|
// src/lib/tasks-review.ts
|
|
2391
2614
|
import path12 from "path";
|
|
2392
|
-
import { existsSync as existsSync12, readdirSync as readdirSync2, unlinkSync as
|
|
2615
|
+
import { existsSync as existsSync12, readdirSync as readdirSync2, unlinkSync as unlinkSync3 } from "fs";
|
|
2393
2616
|
var init_tasks_review = __esm({
|
|
2394
2617
|
"src/lib/tasks-review.ts"() {
|
|
2395
2618
|
"use strict";
|
|
@@ -2544,7 +2767,7 @@ async function dispatchTaskToEmployee(input) {
|
|
|
2544
2767
|
} else {
|
|
2545
2768
|
const projectDir = input.projectDir ?? process.cwd();
|
|
2546
2769
|
const result = ensureEmployee(input.assignedTo, exeSession, projectDir, {
|
|
2547
|
-
autoInstance: input.assignedTo
|
|
2770
|
+
autoInstance: isMultiInstance(input.assignedTo)
|
|
2548
2771
|
});
|
|
2549
2772
|
if (result.status === "failed") {
|
|
2550
2773
|
process.stderr.write(
|
|
@@ -2566,12 +2789,13 @@ var init_tasks_notify = __esm({
|
|
|
2566
2789
|
init_session_key();
|
|
2567
2790
|
init_notifications();
|
|
2568
2791
|
init_transport();
|
|
2792
|
+
init_employees();
|
|
2569
2793
|
}
|
|
2570
2794
|
});
|
|
2571
2795
|
|
|
2572
2796
|
// src/lib/tasks.ts
|
|
2573
2797
|
import path15 from "path";
|
|
2574
|
-
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync6, unlinkSync as
|
|
2798
|
+
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync6, unlinkSync as unlinkSync4 } from "fs";
|
|
2575
2799
|
async function createTask(input) {
|
|
2576
2800
|
const result = await createTaskCore(input);
|
|
2577
2801
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -2708,14 +2932,16 @@ async function main() {
|
|
|
2708
2932
|
await initStore();
|
|
2709
2933
|
const fpPrefix = fingerprint.slice(0, 8);
|
|
2710
2934
|
const client = getClient();
|
|
2935
|
+
const { loadEmployeesSync: loadEmployeesSync2, getEmployeeByRole: getEmployeeByRole2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
2936
|
+
const ctoName = getEmployeeByRole2(loadEmployeesSync2(), "CTO")?.name ?? "yoshi";
|
|
2711
2937
|
const existing = await client.execute({
|
|
2712
2938
|
sql: `SELECT id FROM tasks
|
|
2713
|
-
WHERE assigned_to =
|
|
2939
|
+
WHERE assigned_to = ?
|
|
2714
2940
|
AND status IN ('open', 'in_progress')
|
|
2715
2941
|
AND title LIKE '[auto-bug]%'
|
|
2716
2942
|
AND task_file LIKE ?
|
|
2717
2943
|
LIMIT 1`,
|
|
2718
|
-
args: [`%${fpPrefix}%`]
|
|
2944
|
+
args: [ctoName, `%${fpPrefix}%`]
|
|
2719
2945
|
});
|
|
2720
2946
|
if (existing.rows.length > 0) {
|
|
2721
2947
|
process.stderr.write(`[bug-report-worker] Duplicate found for fingerprint ${fingerprint}, skipping
|
|
@@ -2751,7 +2977,7 @@ async function main() {
|
|
|
2751
2977
|
].join("\n");
|
|
2752
2978
|
await createTask({
|
|
2753
2979
|
title: `[auto-bug] ${toolName}: ${errorSummary}`,
|
|
2754
|
-
assignedTo:
|
|
2980
|
+
assignedTo: ctoName,
|
|
2755
2981
|
assignedBy: "system",
|
|
2756
2982
|
projectName,
|
|
2757
2983
|
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 {
|
|
@@ -1422,7 +1489,8 @@ async function writeMemory(record) {
|
|
|
1422
1489
|
has_error: record.has_error ? 1 : 0,
|
|
1423
1490
|
raw_text: record.raw_text,
|
|
1424
1491
|
vector: record.vector,
|
|
1425
|
-
version:
|
|
1492
|
+
version: 0,
|
|
1493
|
+
// Placeholder — assigned atomically at flush time
|
|
1426
1494
|
task_id: record.task_id ?? null,
|
|
1427
1495
|
importance: record.importance ?? 5,
|
|
1428
1496
|
status: record.status ?? "active",
|
|
@@ -1456,6 +1524,13 @@ async function flushBatch() {
|
|
|
1456
1524
|
_flushing = true;
|
|
1457
1525
|
try {
|
|
1458
1526
|
const batch = _pendingRecords.slice(0);
|
|
1527
|
+
const client = getClient();
|
|
1528
|
+
const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
|
|
1529
|
+
let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
1530
|
+
for (const row of batch) {
|
|
1531
|
+
row.version = baseVersion++;
|
|
1532
|
+
}
|
|
1533
|
+
_nextVersion = baseVersion;
|
|
1459
1534
|
const buildStmt = (row) => {
|
|
1460
1535
|
const hasVector = row.vector !== null;
|
|
1461
1536
|
const taskId = row.task_id ?? null;
|