@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
|
@@ -109,6 +109,61 @@ var init_memory = __esm({
|
|
|
109
109
|
}
|
|
110
110
|
});
|
|
111
111
|
|
|
112
|
+
// src/lib/db-retry.ts
|
|
113
|
+
function isBusyError(err) {
|
|
114
|
+
if (err instanceof Error) {
|
|
115
|
+
const msg = err.message.toLowerCase();
|
|
116
|
+
return msg.includes("sqlite_busy") || msg.includes("database is locked");
|
|
117
|
+
}
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
function delay(ms) {
|
|
121
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
122
|
+
}
|
|
123
|
+
async function retryOnBusy(fn, label) {
|
|
124
|
+
let lastError;
|
|
125
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
126
|
+
try {
|
|
127
|
+
return await fn();
|
|
128
|
+
} catch (err) {
|
|
129
|
+
lastError = err;
|
|
130
|
+
if (!isBusyError(err) || attempt === MAX_RETRIES) {
|
|
131
|
+
throw err;
|
|
132
|
+
}
|
|
133
|
+
const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
|
|
134
|
+
const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
|
|
135
|
+
process.stderr.write(
|
|
136
|
+
`[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
|
|
137
|
+
`
|
|
138
|
+
);
|
|
139
|
+
await delay(backoff + jitter);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
throw lastError;
|
|
143
|
+
}
|
|
144
|
+
function wrapWithRetry(client) {
|
|
145
|
+
return new Proxy(client, {
|
|
146
|
+
get(target, prop, receiver) {
|
|
147
|
+
if (prop === "execute") {
|
|
148
|
+
return (sql) => retryOnBusy(() => target.execute(sql), "execute");
|
|
149
|
+
}
|
|
150
|
+
if (prop === "batch") {
|
|
151
|
+
return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
|
|
152
|
+
}
|
|
153
|
+
return Reflect.get(target, prop, receiver);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
|
|
158
|
+
var init_db_retry = __esm({
|
|
159
|
+
"src/lib/db-retry.ts"() {
|
|
160
|
+
"use strict";
|
|
161
|
+
MAX_RETRIES = 3;
|
|
162
|
+
BASE_DELAY_MS = 200;
|
|
163
|
+
MAX_JITTER_MS = 300;
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
112
167
|
// src/lib/database.ts
|
|
113
168
|
var database_exports = {};
|
|
114
169
|
__export(database_exports, {
|
|
@@ -116,6 +171,7 @@ __export(database_exports, {
|
|
|
116
171
|
disposeTurso: () => disposeTurso,
|
|
117
172
|
ensureSchema: () => ensureSchema,
|
|
118
173
|
getClient: () => getClient,
|
|
174
|
+
getRawClient: () => getRawClient,
|
|
119
175
|
initDatabase: () => initDatabase,
|
|
120
176
|
initTurso: () => initTurso,
|
|
121
177
|
isInitialized: () => isInitialized
|
|
@@ -125,6 +181,7 @@ async function initDatabase(config) {
|
|
|
125
181
|
if (_client) {
|
|
126
182
|
_client.close();
|
|
127
183
|
_client = null;
|
|
184
|
+
_resilientClient = null;
|
|
128
185
|
}
|
|
129
186
|
const opts = {
|
|
130
187
|
url: `file:${config.dbPath}`
|
|
@@ -133,20 +190,27 @@ async function initDatabase(config) {
|
|
|
133
190
|
opts.encryptionKey = config.encryptionKey;
|
|
134
191
|
}
|
|
135
192
|
_client = createClient(opts);
|
|
193
|
+
_resilientClient = wrapWithRetry(_client);
|
|
136
194
|
}
|
|
137
195
|
function isInitialized() {
|
|
138
196
|
return _client !== null;
|
|
139
197
|
}
|
|
140
198
|
function getClient() {
|
|
199
|
+
if (!_resilientClient) {
|
|
200
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
201
|
+
}
|
|
202
|
+
return _resilientClient;
|
|
203
|
+
}
|
|
204
|
+
function getRawClient() {
|
|
141
205
|
if (!_client) {
|
|
142
206
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
143
207
|
}
|
|
144
208
|
return _client;
|
|
145
209
|
}
|
|
146
210
|
async function ensureSchema() {
|
|
147
|
-
const client =
|
|
211
|
+
const client = getRawClient();
|
|
148
212
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
149
|
-
await client.execute("PRAGMA busy_timeout =
|
|
213
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
150
214
|
try {
|
|
151
215
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
152
216
|
} catch {
|
|
@@ -939,13 +1003,16 @@ async function disposeDatabase() {
|
|
|
939
1003
|
if (_client) {
|
|
940
1004
|
_client.close();
|
|
941
1005
|
_client = null;
|
|
1006
|
+
_resilientClient = null;
|
|
942
1007
|
}
|
|
943
1008
|
}
|
|
944
|
-
var _client, initTurso, disposeTurso;
|
|
1009
|
+
var _client, _resilientClient, initTurso, disposeTurso;
|
|
945
1010
|
var init_database = __esm({
|
|
946
1011
|
"src/lib/database.ts"() {
|
|
947
1012
|
"use strict";
|
|
1013
|
+
init_db_retry();
|
|
948
1014
|
_client = null;
|
|
1015
|
+
_resilientClient = null;
|
|
949
1016
|
initTurso = initDatabase;
|
|
950
1017
|
disposeTurso = disposeDatabase;
|
|
951
1018
|
}
|
|
@@ -1252,7 +1319,7 @@ function listShards() {
|
|
|
1252
1319
|
}
|
|
1253
1320
|
async function ensureShardSchema(client) {
|
|
1254
1321
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
1255
|
-
await client.execute("PRAGMA busy_timeout =
|
|
1322
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
1256
1323
|
try {
|
|
1257
1324
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
1258
1325
|
} catch {
|
|
@@ -1490,20 +1557,38 @@ var init_notifications = __esm({
|
|
|
1490
1557
|
|
|
1491
1558
|
// src/lib/employees.ts
|
|
1492
1559
|
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
1493
|
-
import { existsSync as existsSync6, symlinkSync, readlinkSync } from "fs";
|
|
1560
|
+
import { existsSync as existsSync6, symlinkSync, readlinkSync, readFileSync as readFileSync4 } from "fs";
|
|
1494
1561
|
import { execSync as execSync3 } from "child_process";
|
|
1495
1562
|
import path7 from "path";
|
|
1496
|
-
|
|
1563
|
+
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
1564
|
+
if (!existsSync6(employeesPath)) return [];
|
|
1565
|
+
try {
|
|
1566
|
+
return JSON.parse(readFileSync4(employeesPath, "utf-8"));
|
|
1567
|
+
} catch {
|
|
1568
|
+
return [];
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
function getEmployee(employees, name) {
|
|
1572
|
+
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
1573
|
+
}
|
|
1574
|
+
function isMultiInstance(agentName, employees) {
|
|
1575
|
+
const roster = employees ?? loadEmployeesSync();
|
|
1576
|
+
const emp = getEmployee(roster, agentName);
|
|
1577
|
+
if (!emp) return false;
|
|
1578
|
+
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
1579
|
+
}
|
|
1580
|
+
var EMPLOYEES_PATH, MULTI_INSTANCE_ROLES;
|
|
1497
1581
|
var init_employees = __esm({
|
|
1498
1582
|
"src/lib/employees.ts"() {
|
|
1499
1583
|
"use strict";
|
|
1500
1584
|
init_config();
|
|
1501
1585
|
EMPLOYEES_PATH = path7.join(EXE_AI_DIR, "exe-employees.json");
|
|
1586
|
+
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
1502
1587
|
}
|
|
1503
1588
|
});
|
|
1504
1589
|
|
|
1505
1590
|
// src/lib/license.ts
|
|
1506
|
-
import { readFileSync as
|
|
1591
|
+
import { readFileSync as readFileSync5, writeFileSync, existsSync as existsSync7, mkdirSync as mkdirSync2 } from "fs";
|
|
1507
1592
|
import { randomUUID } from "crypto";
|
|
1508
1593
|
import path8 from "path";
|
|
1509
1594
|
import { jwtVerify, importSPKI } from "jose";
|
|
@@ -1511,14 +1596,14 @@ function loadDeviceId() {
|
|
|
1511
1596
|
const deviceJsonPath = path8.join(EXE_AI_DIR, "device.json");
|
|
1512
1597
|
try {
|
|
1513
1598
|
if (existsSync7(deviceJsonPath)) {
|
|
1514
|
-
const data = JSON.parse(
|
|
1599
|
+
const data = JSON.parse(readFileSync5(deviceJsonPath, "utf8"));
|
|
1515
1600
|
if (data.deviceId) return data.deviceId;
|
|
1516
1601
|
}
|
|
1517
1602
|
} catch {
|
|
1518
1603
|
}
|
|
1519
1604
|
try {
|
|
1520
1605
|
if (existsSync7(DEVICE_ID_PATH)) {
|
|
1521
|
-
const id2 =
|
|
1606
|
+
const id2 = readFileSync5(DEVICE_ID_PATH, "utf8").trim();
|
|
1522
1607
|
if (id2) return id2;
|
|
1523
1608
|
}
|
|
1524
1609
|
} catch {
|
|
@@ -1531,7 +1616,7 @@ function loadDeviceId() {
|
|
|
1531
1616
|
function loadLicense() {
|
|
1532
1617
|
try {
|
|
1533
1618
|
if (!existsSync7(LICENSE_PATH)) return null;
|
|
1534
|
-
return
|
|
1619
|
+
return readFileSync5(LICENSE_PATH, "utf8").trim();
|
|
1535
1620
|
} catch {
|
|
1536
1621
|
return null;
|
|
1537
1622
|
}
|
|
@@ -1561,7 +1646,7 @@ async function verifyLicenseJwt(token) {
|
|
|
1561
1646
|
async function getCachedLicense() {
|
|
1562
1647
|
try {
|
|
1563
1648
|
if (!existsSync7(CACHE_PATH)) return null;
|
|
1564
|
-
const raw = JSON.parse(
|
|
1649
|
+
const raw = JSON.parse(readFileSync5(CACHE_PATH, "utf8"));
|
|
1565
1650
|
if (!raw.token || typeof raw.token !== "string") return null;
|
|
1566
1651
|
return await verifyLicenseJwt(raw.token);
|
|
1567
1652
|
} catch {
|
|
@@ -1657,12 +1742,12 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
|
1657
1742
|
});
|
|
1658
1743
|
|
|
1659
1744
|
// src/lib/plan-limits.ts
|
|
1660
|
-
import { readFileSync as
|
|
1745
|
+
import { readFileSync as readFileSync6, existsSync as existsSync8 } from "fs";
|
|
1661
1746
|
import path9 from "path";
|
|
1662
1747
|
function getLicenseSync() {
|
|
1663
1748
|
try {
|
|
1664
1749
|
if (!existsSync8(CACHE_PATH2)) return freeLicense();
|
|
1665
|
-
const raw = JSON.parse(
|
|
1750
|
+
const raw = JSON.parse(readFileSync6(CACHE_PATH2, "utf8"));
|
|
1666
1751
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
1667
1752
|
const parts = raw.token.split(".");
|
|
1668
1753
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -1720,7 +1805,7 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
1720
1805
|
let count = 0;
|
|
1721
1806
|
try {
|
|
1722
1807
|
if (existsSync8(filePath)) {
|
|
1723
|
-
const raw =
|
|
1808
|
+
const raw = readFileSync6(filePath, "utf8");
|
|
1724
1809
|
const employees = JSON.parse(raw);
|
|
1725
1810
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
1726
1811
|
}
|
|
@@ -1757,7 +1842,7 @@ var init_plan_limits = __esm({
|
|
|
1757
1842
|
import net from "net";
|
|
1758
1843
|
import { spawn } from "child_process";
|
|
1759
1844
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
1760
|
-
import { existsSync as existsSync9, unlinkSync as unlinkSync2, readFileSync as
|
|
1845
|
+
import { existsSync as existsSync9, unlinkSync as unlinkSync2, readFileSync as readFileSync7, openSync, closeSync, statSync as statSync2 } from "fs";
|
|
1761
1846
|
import path10 from "path";
|
|
1762
1847
|
import { fileURLToPath } from "url";
|
|
1763
1848
|
function handleData(chunk) {
|
|
@@ -1782,7 +1867,7 @@ function handleData(chunk) {
|
|
|
1782
1867
|
function cleanupStaleFiles() {
|
|
1783
1868
|
if (existsSync9(PID_PATH)) {
|
|
1784
1869
|
try {
|
|
1785
|
-
const pid = parseInt(
|
|
1870
|
+
const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
|
|
1786
1871
|
if (pid > 0) {
|
|
1787
1872
|
try {
|
|
1788
1873
|
process.kill(pid, 0);
|
|
@@ -1930,11 +2015,11 @@ async function connectEmbedDaemon() {
|
|
|
1930
2015
|
}
|
|
1931
2016
|
}
|
|
1932
2017
|
const start = Date.now();
|
|
1933
|
-
let
|
|
2018
|
+
let delay2 = 100;
|
|
1934
2019
|
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
1935
|
-
await new Promise((r) => setTimeout(r,
|
|
2020
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
1936
2021
|
if (await connectToSocket()) return true;
|
|
1937
|
-
|
|
2022
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
1938
2023
|
}
|
|
1939
2024
|
return false;
|
|
1940
2025
|
}
|
|
@@ -1990,7 +2075,7 @@ function killAndRespawnDaemon() {
|
|
|
1990
2075
|
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
1991
2076
|
if (existsSync9(PID_PATH)) {
|
|
1992
2077
|
try {
|
|
1993
|
-
const pid = parseInt(
|
|
2078
|
+
const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
|
|
1994
2079
|
if (pid > 0) {
|
|
1995
2080
|
try {
|
|
1996
2081
|
process.kill(pid, "SIGKILL");
|
|
@@ -2026,11 +2111,11 @@ async function embedViaClient(text, priority = "high") {
|
|
|
2026
2111
|
`);
|
|
2027
2112
|
killAndRespawnDaemon();
|
|
2028
2113
|
const start = Date.now();
|
|
2029
|
-
let
|
|
2114
|
+
let delay2 = 200;
|
|
2030
2115
|
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
2031
|
-
await new Promise((r) => setTimeout(r,
|
|
2116
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
2032
2117
|
if (await connectToSocket()) break;
|
|
2033
|
-
|
|
2118
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
2034
2119
|
}
|
|
2035
2120
|
if (!_connected) return null;
|
|
2036
2121
|
}
|
|
@@ -2042,11 +2127,11 @@ async function embedViaClient(text, priority = "high") {
|
|
|
2042
2127
|
`);
|
|
2043
2128
|
killAndRespawnDaemon();
|
|
2044
2129
|
const start = Date.now();
|
|
2045
|
-
let
|
|
2130
|
+
let delay2 = 200;
|
|
2046
2131
|
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
2047
|
-
await new Promise((r) => setTimeout(r,
|
|
2132
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
2048
2133
|
if (await connectToSocket()) break;
|
|
2049
|
-
|
|
2134
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
2050
2135
|
}
|
|
2051
2136
|
if (!_connected) return null;
|
|
2052
2137
|
const retry = await sendRequest([text], priority);
|
|
@@ -2162,7 +2247,7 @@ import crypto4 from "crypto";
|
|
|
2162
2247
|
import path11 from "path";
|
|
2163
2248
|
import { execSync as execSync4 } from "child_process";
|
|
2164
2249
|
import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
|
|
2165
|
-
import { existsSync as existsSync10, readFileSync as
|
|
2250
|
+
import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
|
|
2166
2251
|
async function writeCheckpoint(input2) {
|
|
2167
2252
|
const client = getClient();
|
|
2168
2253
|
const row = await resolveTask(client, input2.taskId);
|
|
@@ -2539,7 +2624,7 @@ async function ensureGitignoreExe(baseDir) {
|
|
|
2539
2624
|
const gitignorePath = path11.join(baseDir, ".gitignore");
|
|
2540
2625
|
try {
|
|
2541
2626
|
if (existsSync10(gitignorePath)) {
|
|
2542
|
-
const content =
|
|
2627
|
+
const content = readFileSync8(gitignorePath, "utf-8");
|
|
2543
2628
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
2544
2629
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
2545
2630
|
} else {
|
|
@@ -2559,7 +2644,7 @@ var init_tasks_crud = __esm({
|
|
|
2559
2644
|
});
|
|
2560
2645
|
|
|
2561
2646
|
// src/lib/session-registry.ts
|
|
2562
|
-
import { readFileSync as
|
|
2647
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, existsSync as existsSync11 } from "fs";
|
|
2563
2648
|
import path12 from "path";
|
|
2564
2649
|
import os3 from "os";
|
|
2565
2650
|
function registerSession(entry) {
|
|
@@ -2578,7 +2663,7 @@ function registerSession(entry) {
|
|
|
2578
2663
|
}
|
|
2579
2664
|
function listSessions() {
|
|
2580
2665
|
try {
|
|
2581
|
-
const raw =
|
|
2666
|
+
const raw = readFileSync9(REGISTRY_PATH, "utf8");
|
|
2582
2667
|
return JSON.parse(raw);
|
|
2583
2668
|
} catch {
|
|
2584
2669
|
return [];
|
|
@@ -2785,7 +2870,7 @@ var init_provider_table = __esm({
|
|
|
2785
2870
|
});
|
|
2786
2871
|
|
|
2787
2872
|
// src/lib/intercom-queue.ts
|
|
2788
|
-
import { readFileSync as
|
|
2873
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync12, mkdirSync as mkdirSync4 } from "fs";
|
|
2789
2874
|
import path13 from "path";
|
|
2790
2875
|
import os4 from "os";
|
|
2791
2876
|
function ensureDir() {
|
|
@@ -2795,7 +2880,7 @@ function ensureDir() {
|
|
|
2795
2880
|
function readQueue() {
|
|
2796
2881
|
try {
|
|
2797
2882
|
if (!existsSync12(QUEUE_PATH)) return [];
|
|
2798
|
-
return JSON.parse(
|
|
2883
|
+
return JSON.parse(readFileSync10(QUEUE_PATH, "utf8"));
|
|
2799
2884
|
} catch {
|
|
2800
2885
|
return [];
|
|
2801
2886
|
}
|
|
@@ -2835,10 +2920,46 @@ var init_intercom_queue = __esm({
|
|
|
2835
2920
|
|
|
2836
2921
|
// src/lib/tmux-routing.ts
|
|
2837
2922
|
import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
|
|
2838
|
-
import { readFileSync as
|
|
2923
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, existsSync as existsSync13, appendFileSync } from "fs";
|
|
2839
2924
|
import path14 from "path";
|
|
2840
2925
|
import os5 from "os";
|
|
2841
2926
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2927
|
+
import { unlinkSync as unlinkSync3 } from "fs";
|
|
2928
|
+
function spawnLockPath(sessionName) {
|
|
2929
|
+
return path14.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
2930
|
+
}
|
|
2931
|
+
function isProcessAlive(pid) {
|
|
2932
|
+
try {
|
|
2933
|
+
process.kill(pid, 0);
|
|
2934
|
+
return true;
|
|
2935
|
+
} catch {
|
|
2936
|
+
return false;
|
|
2937
|
+
}
|
|
2938
|
+
}
|
|
2939
|
+
function acquireSpawnLock2(sessionName) {
|
|
2940
|
+
if (!existsSync13(SPAWN_LOCK_DIR)) {
|
|
2941
|
+
mkdirSync5(SPAWN_LOCK_DIR, { recursive: true });
|
|
2942
|
+
}
|
|
2943
|
+
const lockFile = spawnLockPath(sessionName);
|
|
2944
|
+
if (existsSync13(lockFile)) {
|
|
2945
|
+
try {
|
|
2946
|
+
const lock = JSON.parse(readFileSync11(lockFile, "utf8"));
|
|
2947
|
+
const age = Date.now() - lock.timestamp;
|
|
2948
|
+
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
2949
|
+
return false;
|
|
2950
|
+
}
|
|
2951
|
+
} catch {
|
|
2952
|
+
}
|
|
2953
|
+
}
|
|
2954
|
+
writeFileSync4(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
2955
|
+
return true;
|
|
2956
|
+
}
|
|
2957
|
+
function releaseSpawnLock2(sessionName) {
|
|
2958
|
+
try {
|
|
2959
|
+
unlinkSync3(spawnLockPath(sessionName));
|
|
2960
|
+
} catch {
|
|
2961
|
+
}
|
|
2962
|
+
}
|
|
2842
2963
|
function resolveBehaviorsExporterScript() {
|
|
2843
2964
|
try {
|
|
2844
2965
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
@@ -2884,7 +3005,7 @@ function extractRootExe(name) {
|
|
|
2884
3005
|
}
|
|
2885
3006
|
function getParentExe(sessionKey) {
|
|
2886
3007
|
try {
|
|
2887
|
-
const data = JSON.parse(
|
|
3008
|
+
const data = JSON.parse(readFileSync11(path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
2888
3009
|
return data.parentExe || null;
|
|
2889
3010
|
} catch {
|
|
2890
3011
|
return null;
|
|
@@ -2892,7 +3013,7 @@ function getParentExe(sessionKey) {
|
|
|
2892
3013
|
}
|
|
2893
3014
|
function getDispatchedBy(sessionKey) {
|
|
2894
3015
|
try {
|
|
2895
|
-
const data = JSON.parse(
|
|
3016
|
+
const data = JSON.parse(readFileSync11(
|
|
2896
3017
|
path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
2897
3018
|
"utf8"
|
|
2898
3019
|
));
|
|
@@ -2919,17 +3040,17 @@ function isEmployeeAlive(sessionName) {
|
|
|
2919
3040
|
}
|
|
2920
3041
|
function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
|
|
2921
3042
|
const base = employeeSessionName(employeeName, exeSession);
|
|
2922
|
-
if (!isAlive(base)) return 0;
|
|
3043
|
+
if (!isAlive(base) && acquireSpawnLock2(base)) return 0;
|
|
2923
3044
|
for (let i = 2; i <= maxInstances; i++) {
|
|
2924
3045
|
const candidate = employeeSessionName(employeeName, exeSession, i);
|
|
2925
|
-
if (!isAlive(candidate)) return i;
|
|
3046
|
+
if (!isAlive(candidate) && acquireSpawnLock2(candidate)) return i;
|
|
2926
3047
|
}
|
|
2927
3048
|
return null;
|
|
2928
3049
|
}
|
|
2929
3050
|
function readDebounceState() {
|
|
2930
3051
|
try {
|
|
2931
3052
|
if (!existsSync13(DEBOUNCE_FILE)) return {};
|
|
2932
|
-
return JSON.parse(
|
|
3053
|
+
return JSON.parse(readFileSync11(DEBOUNCE_FILE, "utf8"));
|
|
2933
3054
|
} catch {
|
|
2934
3055
|
return {};
|
|
2935
3056
|
}
|
|
@@ -3125,7 +3246,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3125
3246
|
const claudeJsonPath = path14.join(os5.homedir(), ".claude.json");
|
|
3126
3247
|
let claudeJson = {};
|
|
3127
3248
|
try {
|
|
3128
|
-
claudeJson = JSON.parse(
|
|
3249
|
+
claudeJson = JSON.parse(readFileSync11(claudeJsonPath, "utf8"));
|
|
3129
3250
|
} catch {
|
|
3130
3251
|
}
|
|
3131
3252
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -3143,7 +3264,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3143
3264
|
const settingsPath = path14.join(projSettingsDir, "settings.json");
|
|
3144
3265
|
let settings = {};
|
|
3145
3266
|
try {
|
|
3146
|
-
settings = JSON.parse(
|
|
3267
|
+
settings = JSON.parse(readFileSync11(settingsPath, "utf8"));
|
|
3147
3268
|
} catch {
|
|
3148
3269
|
}
|
|
3149
3270
|
const perms = settings.permissions ?? {};
|
|
@@ -3256,6 +3377,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3256
3377
|
command: spawnCommand
|
|
3257
3378
|
});
|
|
3258
3379
|
if (spawnResult.error) {
|
|
3380
|
+
releaseSpawnLock2(sessionName);
|
|
3259
3381
|
return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
|
|
3260
3382
|
}
|
|
3261
3383
|
transport.pipeLog(sessionName, logFile);
|
|
@@ -3293,6 +3415,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3293
3415
|
}
|
|
3294
3416
|
}
|
|
3295
3417
|
if (!booted) {
|
|
3418
|
+
releaseSpawnLock2(sessionName);
|
|
3296
3419
|
return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
|
|
3297
3420
|
}
|
|
3298
3421
|
if (!useExeAgent) {
|
|
@@ -3309,9 +3432,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3309
3432
|
pid: 0,
|
|
3310
3433
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3311
3434
|
});
|
|
3435
|
+
releaseSpawnLock2(sessionName);
|
|
3312
3436
|
return { sessionName };
|
|
3313
3437
|
}
|
|
3314
|
-
var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
3438
|
+
var SPAWN_LOCK_DIR, SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
3315
3439
|
var init_tmux_routing = __esm({
|
|
3316
3440
|
"src/lib/tmux-routing.ts"() {
|
|
3317
3441
|
"use strict";
|
|
@@ -3323,6 +3447,7 @@ var init_tmux_routing = __esm({
|
|
|
3323
3447
|
init_provider_table();
|
|
3324
3448
|
init_intercom_queue();
|
|
3325
3449
|
init_plan_limits();
|
|
3450
|
+
SPAWN_LOCK_DIR = path14.join(os5.homedir(), ".exe-os", "spawn-locks");
|
|
3326
3451
|
SESSION_CACHE = path14.join(os5.homedir(), ".exe-os", "session-cache");
|
|
3327
3452
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
3328
3453
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
@@ -3335,7 +3460,7 @@ var init_tmux_routing = __esm({
|
|
|
3335
3460
|
|
|
3336
3461
|
// src/lib/tasks-review.ts
|
|
3337
3462
|
import path15 from "path";
|
|
3338
|
-
import { existsSync as existsSync14, readdirSync as readdirSync3, unlinkSync as
|
|
3463
|
+
import { existsSync as existsSync14, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
|
|
3339
3464
|
async function countPendingReviews() {
|
|
3340
3465
|
const client = getClient();
|
|
3341
3466
|
const result = await client.execute({
|
|
@@ -3459,7 +3584,7 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
3459
3584
|
if (existsSync14(cacheDir)) {
|
|
3460
3585
|
for (const f of readdirSync3(cacheDir)) {
|
|
3461
3586
|
if (f.startsWith("review-notified-")) {
|
|
3462
|
-
|
|
3587
|
+
unlinkSync4(path15.join(cacheDir, f));
|
|
3463
3588
|
}
|
|
3464
3589
|
}
|
|
3465
3590
|
}
|
|
@@ -3648,7 +3773,7 @@ async function dispatchTaskToEmployee(input2) {
|
|
|
3648
3773
|
} else {
|
|
3649
3774
|
const projectDir = input2.projectDir ?? process.cwd();
|
|
3650
3775
|
const result = ensureEmployee(input2.assignedTo, exeSession, projectDir, {
|
|
3651
|
-
autoInstance: input2.assignedTo
|
|
3776
|
+
autoInstance: isMultiInstance(input2.assignedTo)
|
|
3652
3777
|
});
|
|
3653
3778
|
if (result.status === "failed") {
|
|
3654
3779
|
process.stderr.write(
|
|
@@ -3683,6 +3808,7 @@ var init_tasks_notify = __esm({
|
|
|
3683
3808
|
init_session_key();
|
|
3684
3809
|
init_notifications();
|
|
3685
3810
|
init_transport();
|
|
3811
|
+
init_employees();
|
|
3686
3812
|
}
|
|
3687
3813
|
});
|
|
3688
3814
|
|
|
@@ -4017,7 +4143,7 @@ __export(tasks_exports, {
|
|
|
4017
4143
|
writeCheckpoint: () => writeCheckpoint
|
|
4018
4144
|
});
|
|
4019
4145
|
import path17 from "path";
|
|
4020
|
-
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync6, unlinkSync as
|
|
4146
|
+
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync6, unlinkSync as unlinkSync5 } from "fs";
|
|
4021
4147
|
async function createTask(input2) {
|
|
4022
4148
|
const result = await createTaskCore(input2);
|
|
4023
4149
|
if (!input2.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -4043,7 +4169,7 @@ async function updateTask(input2) {
|
|
|
4043
4169
|
writeFileSync5(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
4044
4170
|
} else if (input2.status === "done" || input2.status === "blocked" || input2.status === "cancelled") {
|
|
4045
4171
|
try {
|
|
4046
|
-
|
|
4172
|
+
unlinkSync5(cachePath);
|
|
4047
4173
|
} catch {
|
|
4048
4174
|
}
|
|
4049
4175
|
}
|
|
@@ -4378,7 +4504,8 @@ async function writeMemory(record) {
|
|
|
4378
4504
|
has_error: record.has_error ? 1 : 0,
|
|
4379
4505
|
raw_text: record.raw_text,
|
|
4380
4506
|
vector: record.vector,
|
|
4381
|
-
version:
|
|
4507
|
+
version: 0,
|
|
4508
|
+
// Placeholder — assigned atomically at flush time
|
|
4382
4509
|
task_id: record.task_id ?? null,
|
|
4383
4510
|
importance: record.importance ?? 5,
|
|
4384
4511
|
status: record.status ?? "active",
|
|
@@ -4412,6 +4539,13 @@ async function flushBatch() {
|
|
|
4412
4539
|
_flushing = true;
|
|
4413
4540
|
try {
|
|
4414
4541
|
const batch = _pendingRecords.slice(0);
|
|
4542
|
+
const client = getClient();
|
|
4543
|
+
const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
|
|
4544
|
+
let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
4545
|
+
for (const row of batch) {
|
|
4546
|
+
row.version = baseVersion++;
|
|
4547
|
+
}
|
|
4548
|
+
_nextVersion = baseVersion;
|
|
4415
4549
|
const buildStmt = (row) => {
|
|
4416
4550
|
const hasVector = row.vector !== null;
|
|
4417
4551
|
const taskId = row.task_id ?? null;
|
|
@@ -4571,8 +4705,8 @@ function findContainingChunk(filePath, snippet) {
|
|
|
4571
4705
|
try {
|
|
4572
4706
|
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
4573
4707
|
if (ext !== "ts" && ext !== "tsx" && ext !== "js" && ext !== "jsx") return "";
|
|
4574
|
-
const { readFileSync:
|
|
4575
|
-
const source =
|
|
4708
|
+
const { readFileSync: readFileSync12 } = __require("fs");
|
|
4709
|
+
const source = readFileSync12(filePath, "utf8");
|
|
4576
4710
|
const lines = source.split("\n");
|
|
4577
4711
|
const lowerSnippet = snippet.toLowerCase().slice(0, 80);
|
|
4578
4712
|
let matchLine = -1;
|
|
@@ -4853,8 +4987,8 @@ process.stdin.on("end", async () => {
|
|
|
4853
4987
|
fileContent = data.tool_input?.content ?? "";
|
|
4854
4988
|
} else {
|
|
4855
4989
|
try {
|
|
4856
|
-
const { readFileSync:
|
|
4857
|
-
fileContent =
|
|
4990
|
+
const { readFileSync: readFileSync12 } = await import("fs");
|
|
4991
|
+
fileContent = readFileSync12(filePath, "utf8");
|
|
4858
4992
|
} catch (err) {
|
|
4859
4993
|
process.stderr.write(`[ingest-worker] file read failed (${filePath}): ${err instanceof Error ? err.message : String(err)}
|
|
4860
4994
|
`);
|
package/dist/hooks/ingest.js
CHANGED
|
@@ -247,7 +247,7 @@ function getActiveAgent() {
|
|
|
247
247
|
"tmux display-message -p '#{session_name}' 2>/dev/null",
|
|
248
248
|
{ encoding: "utf8", timeout: 2e3 }
|
|
249
249
|
).trim();
|
|
250
|
-
const empMatch = sessionName.match(/^(
|
|
250
|
+
const empMatch = sessionName.match(/^([a-zA-Z]+)\d*-exe\d+$/);
|
|
251
251
|
if (empMatch && empMatch[1] !== "exe") {
|
|
252
252
|
return { agentId: empMatch[1], agentRole: "employee" };
|
|
253
253
|
}
|