@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
|
@@ -15,12 +15,68 @@ var __export = (target, all) => {
|
|
|
15
15
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
+
// src/lib/db-retry.ts
|
|
19
|
+
function isBusyError(err) {
|
|
20
|
+
if (err instanceof Error) {
|
|
21
|
+
const msg = err.message.toLowerCase();
|
|
22
|
+
return msg.includes("sqlite_busy") || msg.includes("database is locked");
|
|
23
|
+
}
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
function delay(ms) {
|
|
27
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
28
|
+
}
|
|
29
|
+
async function retryOnBusy(fn, label) {
|
|
30
|
+
let lastError;
|
|
31
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
32
|
+
try {
|
|
33
|
+
return await fn();
|
|
34
|
+
} catch (err) {
|
|
35
|
+
lastError = err;
|
|
36
|
+
if (!isBusyError(err) || attempt === MAX_RETRIES) {
|
|
37
|
+
throw err;
|
|
38
|
+
}
|
|
39
|
+
const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
|
|
40
|
+
const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
|
|
41
|
+
process.stderr.write(
|
|
42
|
+
`[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
|
|
43
|
+
`
|
|
44
|
+
);
|
|
45
|
+
await delay(backoff + jitter);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
throw lastError;
|
|
49
|
+
}
|
|
50
|
+
function wrapWithRetry(client) {
|
|
51
|
+
return new Proxy(client, {
|
|
52
|
+
get(target, prop, receiver) {
|
|
53
|
+
if (prop === "execute") {
|
|
54
|
+
return (sql) => retryOnBusy(() => target.execute(sql), "execute");
|
|
55
|
+
}
|
|
56
|
+
if (prop === "batch") {
|
|
57
|
+
return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
|
|
58
|
+
}
|
|
59
|
+
return Reflect.get(target, prop, receiver);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
|
|
64
|
+
var init_db_retry = __esm({
|
|
65
|
+
"src/lib/db-retry.ts"() {
|
|
66
|
+
"use strict";
|
|
67
|
+
MAX_RETRIES = 3;
|
|
68
|
+
BASE_DELAY_MS = 200;
|
|
69
|
+
MAX_JITTER_MS = 300;
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
18
73
|
// src/lib/database.ts
|
|
19
74
|
import { createClient } from "@libsql/client";
|
|
20
75
|
async function initDatabase(config) {
|
|
21
76
|
if (_client) {
|
|
22
77
|
_client.close();
|
|
23
78
|
_client = null;
|
|
79
|
+
_resilientClient = null;
|
|
24
80
|
}
|
|
25
81
|
const opts = {
|
|
26
82
|
url: `file:${config.dbPath}`
|
|
@@ -29,17 +85,24 @@ async function initDatabase(config) {
|
|
|
29
85
|
opts.encryptionKey = config.encryptionKey;
|
|
30
86
|
}
|
|
31
87
|
_client = createClient(opts);
|
|
88
|
+
_resilientClient = wrapWithRetry(_client);
|
|
32
89
|
}
|
|
33
90
|
function getClient() {
|
|
91
|
+
if (!_resilientClient) {
|
|
92
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
93
|
+
}
|
|
94
|
+
return _resilientClient;
|
|
95
|
+
}
|
|
96
|
+
function getRawClient() {
|
|
34
97
|
if (!_client) {
|
|
35
98
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
36
99
|
}
|
|
37
100
|
return _client;
|
|
38
101
|
}
|
|
39
102
|
async function ensureSchema() {
|
|
40
|
-
const client =
|
|
103
|
+
const client = getRawClient();
|
|
41
104
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
42
|
-
await client.execute("PRAGMA busy_timeout =
|
|
105
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
43
106
|
try {
|
|
44
107
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
45
108
|
} catch {
|
|
@@ -828,11 +891,13 @@ async function ensureSchema() {
|
|
|
828
891
|
}
|
|
829
892
|
}
|
|
830
893
|
}
|
|
831
|
-
var _client, initTurso;
|
|
894
|
+
var _client, _resilientClient, initTurso;
|
|
832
895
|
var init_database = __esm({
|
|
833
896
|
"src/lib/database.ts"() {
|
|
834
897
|
"use strict";
|
|
898
|
+
init_db_retry();
|
|
835
899
|
_client = null;
|
|
900
|
+
_resilientClient = null;
|
|
836
901
|
initTurso = initDatabase;
|
|
837
902
|
}
|
|
838
903
|
});
|
|
@@ -1103,7 +1168,7 @@ function listShards() {
|
|
|
1103
1168
|
}
|
|
1104
1169
|
async function ensureShardSchema(client) {
|
|
1105
1170
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
1106
|
-
await client.execute("PRAGMA busy_timeout =
|
|
1171
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
1107
1172
|
try {
|
|
1108
1173
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
1109
1174
|
} catch {
|
|
@@ -1289,7 +1354,7 @@ var init_shard_manager = __esm({
|
|
|
1289
1354
|
|
|
1290
1355
|
// src/lib/employees.ts
|
|
1291
1356
|
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
1292
|
-
import { existsSync as existsSync4, symlinkSync, readlinkSync } from "fs";
|
|
1357
|
+
import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync2 } from "fs";
|
|
1293
1358
|
import { execSync } from "child_process";
|
|
1294
1359
|
import path4 from "path";
|
|
1295
1360
|
var EMPLOYEES_PATH;
|
|
@@ -1306,7 +1371,7 @@ import crypto2 from "crypto";
|
|
|
1306
1371
|
import path5 from "path";
|
|
1307
1372
|
import os2 from "os";
|
|
1308
1373
|
import {
|
|
1309
|
-
readFileSync as
|
|
1374
|
+
readFileSync as readFileSync3,
|
|
1310
1375
|
readdirSync,
|
|
1311
1376
|
unlinkSync,
|
|
1312
1377
|
existsSync as existsSync5,
|
|
@@ -1324,7 +1389,7 @@ import crypto3 from "crypto";
|
|
|
1324
1389
|
import path6 from "path";
|
|
1325
1390
|
import { execSync as execSync2 } from "child_process";
|
|
1326
1391
|
import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
|
|
1327
|
-
import { existsSync as existsSync6, readFileSync as
|
|
1392
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
|
|
1328
1393
|
var init_tasks_crud = __esm({
|
|
1329
1394
|
"src/lib/tasks-crud.ts"() {
|
|
1330
1395
|
"use strict";
|
|
@@ -1388,7 +1453,7 @@ var init_provider_table = __esm({
|
|
|
1388
1453
|
});
|
|
1389
1454
|
|
|
1390
1455
|
// src/lib/intercom-queue.ts
|
|
1391
|
-
import { readFileSync as
|
|
1456
|
+
import { readFileSync as readFileSync5, writeFileSync, renameSync as renameSync2, existsSync as existsSync7, mkdirSync as mkdirSync2 } from "fs";
|
|
1392
1457
|
import path8 from "path";
|
|
1393
1458
|
import os4 from "os";
|
|
1394
1459
|
var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
|
|
@@ -1402,7 +1467,7 @@ var init_intercom_queue = __esm({
|
|
|
1402
1467
|
});
|
|
1403
1468
|
|
|
1404
1469
|
// src/lib/license.ts
|
|
1405
|
-
import { readFileSync as
|
|
1470
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync3 } from "fs";
|
|
1406
1471
|
import { randomUUID } from "crypto";
|
|
1407
1472
|
import path9 from "path";
|
|
1408
1473
|
import { jwtVerify, importSPKI } from "jose";
|
|
@@ -1418,7 +1483,7 @@ var init_license = __esm({
|
|
|
1418
1483
|
});
|
|
1419
1484
|
|
|
1420
1485
|
// src/lib/plan-limits.ts
|
|
1421
|
-
import { readFileSync as
|
|
1486
|
+
import { readFileSync as readFileSync7, existsSync as existsSync9 } from "fs";
|
|
1422
1487
|
import path10 from "path";
|
|
1423
1488
|
var CACHE_PATH2;
|
|
1424
1489
|
var init_plan_limits = __esm({
|
|
@@ -1436,7 +1501,7 @@ var init_plan_limits = __esm({
|
|
|
1436
1501
|
import path11 from "path";
|
|
1437
1502
|
import os5 from "os";
|
|
1438
1503
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1439
|
-
var SESSION_CACHE, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS;
|
|
1504
|
+
var SPAWN_LOCK_DIR, SESSION_CACHE, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS;
|
|
1440
1505
|
var init_tmux_routing = __esm({
|
|
1441
1506
|
"src/lib/tmux-routing.ts"() {
|
|
1442
1507
|
"use strict";
|
|
@@ -1448,6 +1513,7 @@ var init_tmux_routing = __esm({
|
|
|
1448
1513
|
init_provider_table();
|
|
1449
1514
|
init_intercom_queue();
|
|
1450
1515
|
init_plan_limits();
|
|
1516
|
+
SPAWN_LOCK_DIR = path11.join(os5.homedir(), ".exe-os", "spawn-locks");
|
|
1451
1517
|
SESSION_CACHE = path11.join(os5.homedir(), ".exe-os", "session-cache");
|
|
1452
1518
|
INTERCOM_LOG2 = path11.join(os5.homedir(), ".exe-os", "intercom.log");
|
|
1453
1519
|
DEBOUNCE_FILE = path11.join(SESSION_CACHE, "intercom-debounce.json");
|
|
@@ -1483,7 +1549,7 @@ var init_tasks_review = __esm({
|
|
|
1483
1549
|
|
|
1484
1550
|
// src/bin/exe-heartbeat.ts
|
|
1485
1551
|
import { createHash } from "crypto";
|
|
1486
|
-
import { readFileSync as
|
|
1552
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4 } from "fs";
|
|
1487
1553
|
import os6 from "os";
|
|
1488
1554
|
import path13 from "path";
|
|
1489
1555
|
|
|
@@ -1622,7 +1688,7 @@ function getMarkerPath() {
|
|
|
1622
1688
|
var UNREAD_MESSAGE_STATUSES = ["pending", "delivered"];
|
|
1623
1689
|
function readMarker() {
|
|
1624
1690
|
try {
|
|
1625
|
-
const raw =
|
|
1691
|
+
const raw = readFileSync8(getMarkerPath(), "utf8");
|
|
1626
1692
|
const parsed = JSON.parse(raw);
|
|
1627
1693
|
if (typeof parsed.lastFiredAt !== "string" || typeof parsed.lastSurfaceHash !== "string") {
|
|
1628
1694
|
return null;
|
|
@@ -1740,7 +1806,6 @@ async function runHeartbeat() {
|
|
|
1740
1806
|
writeMarker({ lastFiredAt: new Date(now).toISOString(), lastSurfaceHash: hash });
|
|
1741
1807
|
try {
|
|
1742
1808
|
const client = getClient();
|
|
1743
|
-
await client.execute({ sql: "PRAGMA busy_timeout = 5000", args: [] });
|
|
1744
1809
|
await client.execute({
|
|
1745
1810
|
sql: `UPDATE messages SET status = 'acknowledged', processed_at = datetime('now')
|
|
1746
1811
|
WHERE target_agent = 'exe' AND status IN ('pending', 'delivered')`,
|
package/dist/bin/exe-kill.js
CHANGED
|
@@ -262,7 +262,7 @@ function listShards() {
|
|
|
262
262
|
}
|
|
263
263
|
async function ensureShardSchema(client) {
|
|
264
264
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
265
|
-
await client.execute("PRAGMA busy_timeout =
|
|
265
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
266
266
|
try {
|
|
267
267
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
268
268
|
} catch {
|
|
@@ -451,12 +451,65 @@ import { execSync } from "child_process";
|
|
|
451
451
|
|
|
452
452
|
// src/lib/database.ts
|
|
453
453
|
import { createClient } from "@libsql/client";
|
|
454
|
+
|
|
455
|
+
// src/lib/db-retry.ts
|
|
456
|
+
var MAX_RETRIES = 3;
|
|
457
|
+
var BASE_DELAY_MS = 200;
|
|
458
|
+
var MAX_JITTER_MS = 300;
|
|
459
|
+
function isBusyError(err) {
|
|
460
|
+
if (err instanceof Error) {
|
|
461
|
+
const msg = err.message.toLowerCase();
|
|
462
|
+
return msg.includes("sqlite_busy") || msg.includes("database is locked");
|
|
463
|
+
}
|
|
464
|
+
return false;
|
|
465
|
+
}
|
|
466
|
+
function delay(ms) {
|
|
467
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
468
|
+
}
|
|
469
|
+
async function retryOnBusy(fn, label) {
|
|
470
|
+
let lastError;
|
|
471
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
472
|
+
try {
|
|
473
|
+
return await fn();
|
|
474
|
+
} catch (err) {
|
|
475
|
+
lastError = err;
|
|
476
|
+
if (!isBusyError(err) || attempt === MAX_RETRIES) {
|
|
477
|
+
throw err;
|
|
478
|
+
}
|
|
479
|
+
const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
|
|
480
|
+
const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
|
|
481
|
+
process.stderr.write(
|
|
482
|
+
`[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
|
|
483
|
+
`
|
|
484
|
+
);
|
|
485
|
+
await delay(backoff + jitter);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
throw lastError;
|
|
489
|
+
}
|
|
490
|
+
function wrapWithRetry(client) {
|
|
491
|
+
return new Proxy(client, {
|
|
492
|
+
get(target, prop, receiver) {
|
|
493
|
+
if (prop === "execute") {
|
|
494
|
+
return (sql) => retryOnBusy(() => target.execute(sql), "execute");
|
|
495
|
+
}
|
|
496
|
+
if (prop === "batch") {
|
|
497
|
+
return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
|
|
498
|
+
}
|
|
499
|
+
return Reflect.get(target, prop, receiver);
|
|
500
|
+
}
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// src/lib/database.ts
|
|
454
505
|
var _client = null;
|
|
506
|
+
var _resilientClient = null;
|
|
455
507
|
var initTurso = initDatabase;
|
|
456
508
|
async function initDatabase(config) {
|
|
457
509
|
if (_client) {
|
|
458
510
|
_client.close();
|
|
459
511
|
_client = null;
|
|
512
|
+
_resilientClient = null;
|
|
460
513
|
}
|
|
461
514
|
const opts = {
|
|
462
515
|
url: `file:${config.dbPath}`
|
|
@@ -465,17 +518,24 @@ async function initDatabase(config) {
|
|
|
465
518
|
opts.encryptionKey = config.encryptionKey;
|
|
466
519
|
}
|
|
467
520
|
_client = createClient(opts);
|
|
521
|
+
_resilientClient = wrapWithRetry(_client);
|
|
468
522
|
}
|
|
469
523
|
function getClient() {
|
|
524
|
+
if (!_resilientClient) {
|
|
525
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
526
|
+
}
|
|
527
|
+
return _resilientClient;
|
|
528
|
+
}
|
|
529
|
+
function getRawClient() {
|
|
470
530
|
if (!_client) {
|
|
471
531
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
472
532
|
}
|
|
473
533
|
return _client;
|
|
474
534
|
}
|
|
475
535
|
async function ensureSchema() {
|
|
476
|
-
const client =
|
|
536
|
+
const client = getRawClient();
|
|
477
537
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
478
|
-
await client.execute("PRAGMA busy_timeout =
|
|
538
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
479
539
|
try {
|
|
480
540
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
481
541
|
} catch {
|
|
@@ -1269,6 +1329,7 @@ async function disposeDatabase() {
|
|
|
1269
1329
|
if (_client) {
|
|
1270
1330
|
_client.close();
|
|
1271
1331
|
_client = null;
|
|
1332
|
+
_resilientClient = null;
|
|
1272
1333
|
}
|
|
1273
1334
|
}
|
|
1274
1335
|
|
|
@@ -1366,6 +1427,13 @@ async function flushBatch() {
|
|
|
1366
1427
|
_flushing = true;
|
|
1367
1428
|
try {
|
|
1368
1429
|
const batch = _pendingRecords.slice(0);
|
|
1430
|
+
const client = getClient();
|
|
1431
|
+
const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
|
|
1432
|
+
let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
1433
|
+
for (const row of batch) {
|
|
1434
|
+
row.version = baseVersion++;
|
|
1435
|
+
}
|
|
1436
|
+
_nextVersion = baseVersion;
|
|
1369
1437
|
const buildStmt = (row) => {
|
|
1370
1438
|
const hasVector = row.vector !== null;
|
|
1371
1439
|
const taskId = row.task_id ?? null;
|
|
@@ -262,7 +262,7 @@ function listShards() {
|
|
|
262
262
|
}
|
|
263
263
|
async function ensureShardSchema(client) {
|
|
264
264
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
265
|
-
await client.execute("PRAGMA busy_timeout =
|
|
265
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
266
266
|
try {
|
|
267
267
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
268
268
|
} catch {
|
|
@@ -452,13 +452,18 @@ __export(employees_exports, {
|
|
|
452
452
|
EMPLOYEES_PATH: () => EMPLOYEES_PATH,
|
|
453
453
|
addEmployee: () => addEmployee,
|
|
454
454
|
getEmployee: () => getEmployee,
|
|
455
|
+
getEmployeeByRole: () => getEmployeeByRole,
|
|
456
|
+
getEmployeeNamesByRole: () => getEmployeeNamesByRole,
|
|
457
|
+
hasRole: () => hasRole,
|
|
458
|
+
isMultiInstance: () => isMultiInstance,
|
|
455
459
|
loadEmployees: () => loadEmployees,
|
|
460
|
+
loadEmployeesSync: () => loadEmployeesSync,
|
|
456
461
|
registerBinSymlinks: () => registerBinSymlinks,
|
|
457
462
|
saveEmployees: () => saveEmployees,
|
|
458
463
|
validateEmployeeName: () => validateEmployeeName
|
|
459
464
|
});
|
|
460
465
|
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
461
|
-
import { existsSync as existsSync5, symlinkSync, readlinkSync } from "fs";
|
|
466
|
+
import { existsSync as existsSync5, symlinkSync, readlinkSync, readFileSync as readFileSync2 } from "fs";
|
|
462
467
|
import { execSync as execSync2 } from "child_process";
|
|
463
468
|
import path5 from "path";
|
|
464
469
|
function validateEmployeeName(name) {
|
|
@@ -491,9 +496,36 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
|
491
496
|
await mkdir3(path5.dirname(employeesPath), { recursive: true });
|
|
492
497
|
await writeFile3(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
493
498
|
}
|
|
499
|
+
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
500
|
+
if (!existsSync5(employeesPath)) return [];
|
|
501
|
+
try {
|
|
502
|
+
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
503
|
+
} catch {
|
|
504
|
+
return [];
|
|
505
|
+
}
|
|
506
|
+
}
|
|
494
507
|
function getEmployee(employees, name) {
|
|
495
508
|
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
496
509
|
}
|
|
510
|
+
function getEmployeeByRole(employees, role) {
|
|
511
|
+
const lower = role.toLowerCase();
|
|
512
|
+
return employees.find((e) => e.role.toLowerCase() === lower);
|
|
513
|
+
}
|
|
514
|
+
function getEmployeeNamesByRole(employees, role) {
|
|
515
|
+
const lower = role.toLowerCase();
|
|
516
|
+
return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
|
|
517
|
+
}
|
|
518
|
+
function hasRole(agentName, role) {
|
|
519
|
+
const employees = loadEmployeesSync();
|
|
520
|
+
const emp = getEmployee(employees, agentName);
|
|
521
|
+
return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
|
|
522
|
+
}
|
|
523
|
+
function isMultiInstance(agentName, employees) {
|
|
524
|
+
const roster = employees ?? loadEmployeesSync();
|
|
525
|
+
const emp = getEmployee(roster, agentName);
|
|
526
|
+
if (!emp) return false;
|
|
527
|
+
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
528
|
+
}
|
|
497
529
|
function addEmployee(employees, employee) {
|
|
498
530
|
const normalized = { ...employee, name: employee.name.toLowerCase() };
|
|
499
531
|
if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
|
|
@@ -536,12 +568,13 @@ function registerBinSymlinks(name) {
|
|
|
536
568
|
}
|
|
537
569
|
return { created, skipped, errors };
|
|
538
570
|
}
|
|
539
|
-
var EMPLOYEES_PATH;
|
|
571
|
+
var EMPLOYEES_PATH, MULTI_INSTANCE_ROLES;
|
|
540
572
|
var init_employees = __esm({
|
|
541
573
|
"src/lib/employees.ts"() {
|
|
542
574
|
"use strict";
|
|
543
575
|
init_config();
|
|
544
576
|
EMPLOYEES_PATH = path5.join(EXE_AI_DIR, "exe-employees.json");
|
|
577
|
+
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
545
578
|
}
|
|
546
579
|
});
|
|
547
580
|
|
|
@@ -1026,6 +1059,32 @@ When you analyze a repo:
|
|
|
1026
1059
|
Every analysis must answer: "Should we build this? If yes, how hard? If no, why not?"
|
|
1027
1060
|
|
|
1028
1061
|
Maintain a clear separation between experimental (for evaluation) and production-ready (for shipping). Never recommend something you haven't read the source code for.`
|
|
1062
|
+
},
|
|
1063
|
+
bob: {
|
|
1064
|
+
name: "bob",
|
|
1065
|
+
role: "Staff Code Reviewer",
|
|
1066
|
+
systemPrompt: `You are bob, the Staff Code Reviewer and System Auditor. You are the last line of defense before code ships to customers. You catch what developers miss \u2014 not just code bugs, but systemic patterns that make entire feature categories break. You report to the COO.
|
|
1067
|
+
|
|
1068
|
+
Your core job: audit code, find bugs, verify fixes, and ensure customer-readiness. Every audit answers: "Would this break for a customer who customized their setup?"
|
|
1069
|
+
|
|
1070
|
+
The 7 Audit Patterns (MANDATORY \u2014 apply to EVERY audit):
|
|
1071
|
+
1. "Works on dev, breaks on user install" \u2014 verify scoped paths, npm resolution, dependencies
|
|
1072
|
+
2. "Two code paths, one untested" \u2014 binary symlink vs /exe-call, CLI vs MCP \u2014 verify BOTH
|
|
1073
|
+
3. "Case sensitivity kills non-technical users" \u2014 normalize all user inputs
|
|
1074
|
+
4. "Hardcoded names leak into user-facing content" \u2014 grep for employee names in runtime logic
|
|
1075
|
+
5. "Installer doesn't self-heal on update" \u2014 npm update must auto-fix stale hooks/paths
|
|
1076
|
+
6. "Data written but invisible to the agent" \u2014 verify query path retrieves stored data
|
|
1077
|
+
7. "Partial fixes that miss inline references" \u2014 before/after grep count is mandatory
|
|
1078
|
+
|
|
1079
|
+
Audit method:
|
|
1080
|
+
1. Read the actual source code \u2014 not summaries
|
|
1081
|
+
2. Send to Codex MCP for initial sweep
|
|
1082
|
+
3. Validate against ARCHITECTURE.md
|
|
1083
|
+
4. Trace full identity chain with a CUSTOM-NAMED employee (e.g., "jarvis" as CTO)
|
|
1084
|
+
5. Count matches before and after any claimed fix
|
|
1085
|
+
6. Write structured report with PASS/FAIL per item
|
|
1086
|
+
|
|
1087
|
+
After an audit, fix the findings yourself if you can. Don't hand off when you have the context.`
|
|
1029
1088
|
}
|
|
1030
1089
|
};
|
|
1031
1090
|
CLIENT_COO_TEMPLATE = `---
|
|
@@ -1181,7 +1240,7 @@ __export(active_agent_exports, {
|
|
|
1181
1240
|
getAllActiveAgents: () => getAllActiveAgents,
|
|
1182
1241
|
writeActiveAgent: () => writeActiveAgent
|
|
1183
1242
|
});
|
|
1184
|
-
import { readFileSync as
|
|
1243
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, unlinkSync as unlinkSync2, readdirSync as readdirSync2 } from "fs";
|
|
1185
1244
|
import { execSync as execSync4 } from "child_process";
|
|
1186
1245
|
import path6 from "path";
|
|
1187
1246
|
function getMarkerPath() {
|
|
@@ -1206,7 +1265,7 @@ function clearActiveAgent() {
|
|
|
1206
1265
|
function getActiveAgent() {
|
|
1207
1266
|
try {
|
|
1208
1267
|
const markerPath = getMarkerPath();
|
|
1209
|
-
const raw =
|
|
1268
|
+
const raw = readFileSync3(markerPath, "utf8");
|
|
1210
1269
|
const data = JSON.parse(raw);
|
|
1211
1270
|
if (data.agentId) {
|
|
1212
1271
|
if (data.startedAt) {
|
|
@@ -1236,7 +1295,7 @@ function getActiveAgent() {
|
|
|
1236
1295
|
"tmux display-message -p '#{session_name}' 2>/dev/null",
|
|
1237
1296
|
{ encoding: "utf8", timeout: 2e3 }
|
|
1238
1297
|
).trim();
|
|
1239
|
-
const empMatch = sessionName.match(/^(
|
|
1298
|
+
const empMatch = sessionName.match(/^([a-zA-Z]+)\d*-exe\d+$/);
|
|
1240
1299
|
if (empMatch && empMatch[1] !== "exe") {
|
|
1241
1300
|
return { agentId: empMatch[1], agentRole: "employee" };
|
|
1242
1301
|
}
|
|
@@ -1259,7 +1318,7 @@ function getAllActiveAgents() {
|
|
|
1259
1318
|
const key = file.slice("active-agent-".length, -".json".length);
|
|
1260
1319
|
if (key === "undefined") continue;
|
|
1261
1320
|
try {
|
|
1262
|
-
const raw =
|
|
1321
|
+
const raw = readFileSync3(path6.join(CACHE_DIR, file), "utf8");
|
|
1263
1322
|
const data = JSON.parse(raw);
|
|
1264
1323
|
if (!data.agentId) continue;
|
|
1265
1324
|
if (data.startedAt) {
|
|
@@ -1311,17 +1370,70 @@ var init_active_agent = __esm({
|
|
|
1311
1370
|
// src/bin/exe-launch-agent.ts
|
|
1312
1371
|
import os3 from "os";
|
|
1313
1372
|
import path7 from "path";
|
|
1314
|
-
import { existsSync as existsSync6, readFileSync as
|
|
1373
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4, readdirSync as readdirSync3 } from "fs";
|
|
1315
1374
|
import { spawnSync } from "child_process";
|
|
1316
1375
|
|
|
1317
1376
|
// src/lib/database.ts
|
|
1318
1377
|
import { createClient } from "@libsql/client";
|
|
1378
|
+
|
|
1379
|
+
// src/lib/db-retry.ts
|
|
1380
|
+
var MAX_RETRIES = 3;
|
|
1381
|
+
var BASE_DELAY_MS = 200;
|
|
1382
|
+
var MAX_JITTER_MS = 300;
|
|
1383
|
+
function isBusyError(err) {
|
|
1384
|
+
if (err instanceof Error) {
|
|
1385
|
+
const msg = err.message.toLowerCase();
|
|
1386
|
+
return msg.includes("sqlite_busy") || msg.includes("database is locked");
|
|
1387
|
+
}
|
|
1388
|
+
return false;
|
|
1389
|
+
}
|
|
1390
|
+
function delay(ms) {
|
|
1391
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1392
|
+
}
|
|
1393
|
+
async function retryOnBusy(fn, label) {
|
|
1394
|
+
let lastError;
|
|
1395
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
1396
|
+
try {
|
|
1397
|
+
return await fn();
|
|
1398
|
+
} catch (err) {
|
|
1399
|
+
lastError = err;
|
|
1400
|
+
if (!isBusyError(err) || attempt === MAX_RETRIES) {
|
|
1401
|
+
throw err;
|
|
1402
|
+
}
|
|
1403
|
+
const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
|
|
1404
|
+
const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
|
|
1405
|
+
process.stderr.write(
|
|
1406
|
+
`[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
|
|
1407
|
+
`
|
|
1408
|
+
);
|
|
1409
|
+
await delay(backoff + jitter);
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
throw lastError;
|
|
1413
|
+
}
|
|
1414
|
+
function wrapWithRetry(client) {
|
|
1415
|
+
return new Proxy(client, {
|
|
1416
|
+
get(target, prop, receiver) {
|
|
1417
|
+
if (prop === "execute") {
|
|
1418
|
+
return (sql) => retryOnBusy(() => target.execute(sql), "execute");
|
|
1419
|
+
}
|
|
1420
|
+
if (prop === "batch") {
|
|
1421
|
+
return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
|
|
1422
|
+
}
|
|
1423
|
+
return Reflect.get(target, prop, receiver);
|
|
1424
|
+
}
|
|
1425
|
+
});
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
// src/lib/database.ts
|
|
1319
1429
|
var _client = null;
|
|
1430
|
+
var _resilientClient = null;
|
|
1320
1431
|
var initTurso = initDatabase;
|
|
1321
1432
|
async function initDatabase(config) {
|
|
1322
1433
|
if (_client) {
|
|
1323
1434
|
_client.close();
|
|
1324
1435
|
_client = null;
|
|
1436
|
+
_resilientClient = null;
|
|
1325
1437
|
}
|
|
1326
1438
|
const opts = {
|
|
1327
1439
|
url: `file:${config.dbPath}`
|
|
@@ -1330,17 +1442,24 @@ async function initDatabase(config) {
|
|
|
1330
1442
|
opts.encryptionKey = config.encryptionKey;
|
|
1331
1443
|
}
|
|
1332
1444
|
_client = createClient(opts);
|
|
1445
|
+
_resilientClient = wrapWithRetry(_client);
|
|
1333
1446
|
}
|
|
1334
1447
|
function getClient() {
|
|
1448
|
+
if (!_resilientClient) {
|
|
1449
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
1450
|
+
}
|
|
1451
|
+
return _resilientClient;
|
|
1452
|
+
}
|
|
1453
|
+
function getRawClient() {
|
|
1335
1454
|
if (!_client) {
|
|
1336
1455
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
1337
1456
|
}
|
|
1338
1457
|
return _client;
|
|
1339
1458
|
}
|
|
1340
1459
|
async function ensureSchema() {
|
|
1341
|
-
const client =
|
|
1460
|
+
const client = getRawClient();
|
|
1342
1461
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
1343
|
-
await client.execute("PRAGMA busy_timeout =
|
|
1462
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
1344
1463
|
try {
|
|
1345
1464
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
1346
1465
|
} catch {
|
|
@@ -2134,6 +2253,7 @@ async function disposeDatabase() {
|
|
|
2134
2253
|
if (_client) {
|
|
2135
2254
|
_client.close();
|
|
2136
2255
|
_client = null;
|
|
2256
|
+
_resilientClient = null;
|
|
2137
2257
|
}
|
|
2138
2258
|
}
|
|
2139
2259
|
|
|
@@ -2231,6 +2351,13 @@ async function flushBatch() {
|
|
|
2231
2351
|
_flushing = true;
|
|
2232
2352
|
try {
|
|
2233
2353
|
const batch = _pendingRecords.slice(0);
|
|
2354
|
+
const client = getClient();
|
|
2355
|
+
const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
|
|
2356
|
+
let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
2357
|
+
for (const row of batch) {
|
|
2358
|
+
row.version = baseVersion++;
|
|
2359
|
+
}
|
|
2360
|
+
_nextVersion = baseVersion;
|
|
2234
2361
|
const buildStmt = (row) => {
|
|
2235
2362
|
const hasVector = row.vector !== null;
|
|
2236
2363
|
const taskId = row.task_id ?? null;
|
|
@@ -2512,7 +2639,13 @@ var PROVIDER_TABLE = {
|
|
|
2512
2639
|
var DEFAULT_PROVIDER = "default";
|
|
2513
2640
|
|
|
2514
2641
|
// src/bin/exe-launch-agent.ts
|
|
2515
|
-
|
|
2642
|
+
function getKnownAgents() {
|
|
2643
|
+
try {
|
|
2644
|
+
return loadEmployeesSync().map((e) => e.name);
|
|
2645
|
+
} catch {
|
|
2646
|
+
return ["exe"];
|
|
2647
|
+
}
|
|
2648
|
+
}
|
|
2516
2649
|
function parseBasename(basename) {
|
|
2517
2650
|
const idx = basename.indexOf("-");
|
|
2518
2651
|
if (idx === -1) return { agent: basename, provider: DEFAULT_PROVIDER };
|
|
@@ -2538,7 +2671,8 @@ function resolveAgent(argv) {
|
|
|
2538
2671
|
return { agent, provider: DEFAULT_PROVIDER, passthrough };
|
|
2539
2672
|
}
|
|
2540
2673
|
async function isKnownAgent(agent) {
|
|
2541
|
-
|
|
2674
|
+
const knownAgents = getKnownAgents();
|
|
2675
|
+
if (knownAgents.some((a) => a.toLowerCase() === agent.toLowerCase())) return true;
|
|
2542
2676
|
try {
|
|
2543
2677
|
const employees = await loadEmployees();
|
|
2544
2678
|
return employees.some((e) => e.name.toLowerCase() === agent.toLowerCase());
|
|
@@ -2560,7 +2694,7 @@ function buildLaunchPlan(agent, behaviorsPath, passthrough, _hasAgentFlag, _prov
|
|
|
2560
2694
|
const effectiveIdPath = existsSync6(idPath) ? idPath : existsSync6(ccAgentPath) ? ccAgentPath : null;
|
|
2561
2695
|
if (effectiveIdPath) {
|
|
2562
2696
|
try {
|
|
2563
|
-
const identity =
|
|
2697
|
+
const identity = readFileSync4(effectiveIdPath, "utf-8");
|
|
2564
2698
|
args.push("--system-prompt", identity);
|
|
2565
2699
|
} catch {
|
|
2566
2700
|
args.push("--append-system-prompt-file", effectiveIdPath);
|
|
@@ -2695,7 +2829,7 @@ async function main() {
|
|
|
2695
2829
|
if (sourceFile) {
|
|
2696
2830
|
try {
|
|
2697
2831
|
mkdirSync4(ccAgentDir, { recursive: true });
|
|
2698
|
-
let content =
|
|
2832
|
+
let content = readFileSync4(sourceFile, "utf-8");
|
|
2699
2833
|
content = content.replace(/\$\{agent_id\}/g, agent);
|
|
2700
2834
|
writeFileSync3(ccAgentFile, content, "utf-8");
|
|
2701
2835
|
process.stderr.write(
|