@askexenow/exe-os 0.8.33 → 0.8.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/backfill-conversations.js +332 -348
- package/dist/bin/backfill-responses.js +72 -12
- package/dist/bin/backfill-vectors.js +72 -12
- package/dist/bin/cleanup-stale-review-tasks.js +63 -3
- package/dist/bin/cli.js +1491 -1095
- package/dist/bin/exe-assign.js +80 -18
- package/dist/bin/exe-boot.js +407 -88
- package/dist/bin/exe-call.js +61 -2
- package/dist/bin/exe-dispatch.js +18 -10
- package/dist/bin/exe-doctor.js +63 -3
- package/dist/bin/exe-export-behaviors.js +64 -3
- package/dist/bin/exe-forget.js +69 -4
- package/dist/bin/exe-gateway.js +121 -36
- package/dist/bin/exe-heartbeat.js +77 -13
- package/dist/bin/exe-kill.js +64 -3
- package/dist/bin/exe-launch-agent.js +140 -13
- package/dist/bin/exe-link.js +946 -0
- package/dist/bin/exe-new-employee.js +98 -13
- package/dist/bin/exe-pending-messages.js +72 -7
- package/dist/bin/exe-pending-notifications.js +63 -3
- package/dist/bin/exe-pending-reviews.js +75 -10
- package/dist/bin/exe-rename.js +1287 -0
- package/dist/bin/exe-review.js +64 -4
- package/dist/bin/exe-search.js +79 -13
- package/dist/bin/exe-session-cleanup.js +91 -26
- package/dist/bin/exe-status.js +64 -4
- package/dist/bin/exe-team.js +64 -4
- package/dist/bin/git-sweep.js +71 -4
- package/dist/bin/graph-backfill.js +64 -3
- package/dist/bin/graph-export.js +64 -3
- package/dist/bin/install.js +3 -3
- package/dist/bin/scan-tasks.js +71 -4
- package/dist/bin/setup.js +128 -10
- package/dist/bin/shard-migrate.js +64 -3
- package/dist/bin/wiki-sync.js +64 -3
- package/dist/gateway/index.js +122 -37
- package/dist/hooks/bug-report-worker.js +209 -23
- package/dist/hooks/commit-complete.js +71 -4
- package/dist/hooks/error-recall.js +79 -13
- package/dist/hooks/ingest-worker.js +129 -43
- package/dist/hooks/instructions-loaded.js +71 -4
- package/dist/hooks/notification.js +71 -4
- package/dist/hooks/post-compact.js +71 -4
- package/dist/hooks/pre-compact.js +71 -4
- package/dist/hooks/pre-tool-use.js +413 -194
- package/dist/hooks/prompt-ingest-worker.js +82 -22
- package/dist/hooks/prompt-submit.js +103 -37
- package/dist/hooks/response-ingest-worker.js +87 -22
- package/dist/hooks/session-end.js +71 -4
- package/dist/hooks/session-start.js +79 -13
- package/dist/hooks/stop.js +71 -4
- package/dist/hooks/subagent-stop.js +71 -4
- package/dist/hooks/summary-worker.js +303 -50
- package/dist/index.js +134 -46
- package/dist/lib/cloud-sync.js +209 -15
- package/dist/lib/consolidation.js +4 -4
- package/dist/lib/database.js +64 -2
- package/dist/lib/device-registry.js +70 -3
- package/dist/lib/employee-templates.js +26 -0
- package/dist/lib/employees.js +34 -1
- package/dist/lib/exe-daemon.js +136 -53
- package/dist/lib/hybrid-search.js +79 -13
- package/dist/lib/identity-templates.js +51 -0
- package/dist/lib/identity.js +3 -3
- package/dist/lib/messaging.js +22 -14
- package/dist/lib/reminders.js +3 -3
- package/dist/lib/schedules.js +63 -3
- package/dist/lib/skill-learning.js +3 -3
- package/dist/lib/status-brief.js +63 -5
- package/dist/lib/store.js +64 -3
- package/dist/lib/task-router.js +4 -2
- package/dist/lib/tasks.js +48 -21
- package/dist/lib/tmux-routing.js +47 -20
- package/dist/mcp/server.js +727 -58
- package/dist/mcp/tools/complete-reminder.js +3 -3
- package/dist/mcp/tools/create-reminder.js +3 -3
- package/dist/mcp/tools/create-task.js +151 -24
- package/dist/mcp/tools/deactivate-behavior.js +3 -3
- package/dist/mcp/tools/list-reminders.js +3 -3
- package/dist/mcp/tools/list-tasks.js +17 -8
- package/dist/mcp/tools/send-message.js +24 -16
- package/dist/mcp/tools/update-task.js +25 -16
- package/dist/runtime/index.js +112 -24
- package/dist/tui/App.js +139 -36
- package/package.json +6 -2
- package/src/commands/exe/rename.md +12 -0
package/dist/bin/exe-call.js
CHANGED
|
@@ -112,13 +112,18 @@ __export(employees_exports, {
|
|
|
112
112
|
EMPLOYEES_PATH: () => EMPLOYEES_PATH,
|
|
113
113
|
addEmployee: () => addEmployee,
|
|
114
114
|
getEmployee: () => getEmployee,
|
|
115
|
+
getEmployeeByRole: () => getEmployeeByRole,
|
|
116
|
+
getEmployeeNamesByRole: () => getEmployeeNamesByRole,
|
|
117
|
+
hasRole: () => hasRole,
|
|
118
|
+
isMultiInstance: () => isMultiInstance,
|
|
115
119
|
loadEmployees: () => loadEmployees,
|
|
120
|
+
loadEmployeesSync: () => loadEmployeesSync,
|
|
116
121
|
registerBinSymlinks: () => registerBinSymlinks,
|
|
117
122
|
saveEmployees: () => saveEmployees,
|
|
118
123
|
validateEmployeeName: () => validateEmployeeName
|
|
119
124
|
});
|
|
120
125
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
121
|
-
import { existsSync as existsSync2, symlinkSync, readlinkSync } from "fs";
|
|
126
|
+
import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2 } from "fs";
|
|
122
127
|
import { execSync } from "child_process";
|
|
123
128
|
import path2 from "path";
|
|
124
129
|
function validateEmployeeName(name) {
|
|
@@ -151,9 +156,36 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
|
151
156
|
await mkdir2(path2.dirname(employeesPath), { recursive: true });
|
|
152
157
|
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
153
158
|
}
|
|
159
|
+
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
160
|
+
if (!existsSync2(employeesPath)) return [];
|
|
161
|
+
try {
|
|
162
|
+
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
163
|
+
} catch {
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
}
|
|
154
167
|
function getEmployee(employees, name) {
|
|
155
168
|
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
156
169
|
}
|
|
170
|
+
function getEmployeeByRole(employees, role) {
|
|
171
|
+
const lower = role.toLowerCase();
|
|
172
|
+
return employees.find((e) => e.role.toLowerCase() === lower);
|
|
173
|
+
}
|
|
174
|
+
function getEmployeeNamesByRole(employees, role) {
|
|
175
|
+
const lower = role.toLowerCase();
|
|
176
|
+
return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
|
|
177
|
+
}
|
|
178
|
+
function hasRole(agentName, role) {
|
|
179
|
+
const employees = loadEmployeesSync();
|
|
180
|
+
const emp = getEmployee(employees, agentName);
|
|
181
|
+
return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
|
|
182
|
+
}
|
|
183
|
+
function isMultiInstance(agentName, employees) {
|
|
184
|
+
const roster = employees ?? loadEmployeesSync();
|
|
185
|
+
const emp = getEmployee(roster, agentName);
|
|
186
|
+
if (!emp) return false;
|
|
187
|
+
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
188
|
+
}
|
|
157
189
|
function addEmployee(employees, employee) {
|
|
158
190
|
const normalized = { ...employee, name: employee.name.toLowerCase() };
|
|
159
191
|
if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
|
|
@@ -196,12 +228,13 @@ function registerBinSymlinks(name) {
|
|
|
196
228
|
}
|
|
197
229
|
return { created, skipped, errors };
|
|
198
230
|
}
|
|
199
|
-
var EMPLOYEES_PATH;
|
|
231
|
+
var EMPLOYEES_PATH, MULTI_INSTANCE_ROLES;
|
|
200
232
|
var init_employees = __esm({
|
|
201
233
|
"src/lib/employees.ts"() {
|
|
202
234
|
"use strict";
|
|
203
235
|
init_config();
|
|
204
236
|
EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
|
|
237
|
+
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
205
238
|
}
|
|
206
239
|
});
|
|
207
240
|
|
|
@@ -686,6 +719,32 @@ When you analyze a repo:
|
|
|
686
719
|
Every analysis must answer: "Should we build this? If yes, how hard? If no, why not?"
|
|
687
720
|
|
|
688
721
|
Maintain a clear separation between experimental (for evaluation) and production-ready (for shipping). Never recommend something you haven't read the source code for.`
|
|
722
|
+
},
|
|
723
|
+
bob: {
|
|
724
|
+
name: "bob",
|
|
725
|
+
role: "Staff Code Reviewer",
|
|
726
|
+
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.
|
|
727
|
+
|
|
728
|
+
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?"
|
|
729
|
+
|
|
730
|
+
The 7 Audit Patterns (MANDATORY \u2014 apply to EVERY audit):
|
|
731
|
+
1. "Works on dev, breaks on user install" \u2014 verify scoped paths, npm resolution, dependencies
|
|
732
|
+
2. "Two code paths, one untested" \u2014 binary symlink vs /exe-call, CLI vs MCP \u2014 verify BOTH
|
|
733
|
+
3. "Case sensitivity kills non-technical users" \u2014 normalize all user inputs
|
|
734
|
+
4. "Hardcoded names leak into user-facing content" \u2014 grep for employee names in runtime logic
|
|
735
|
+
5. "Installer doesn't self-heal on update" \u2014 npm update must auto-fix stale hooks/paths
|
|
736
|
+
6. "Data written but invisible to the agent" \u2014 verify query path retrieves stored data
|
|
737
|
+
7. "Partial fixes that miss inline references" \u2014 before/after grep count is mandatory
|
|
738
|
+
|
|
739
|
+
Audit method:
|
|
740
|
+
1. Read the actual source code \u2014 not summaries
|
|
741
|
+
2. Send to Codex MCP for initial sweep
|
|
742
|
+
3. Validate against ARCHITECTURE.md
|
|
743
|
+
4. Trace full identity chain with a CUSTOM-NAMED employee (e.g., "jarvis" as CTO)
|
|
744
|
+
5. Count matches before and after any claimed fix
|
|
745
|
+
6. Write structured report with PASS/FAIL per item
|
|
746
|
+
|
|
747
|
+
After an audit, fix the findings yourself if you can. Don't hand off when you have the context.`
|
|
689
748
|
}
|
|
690
749
|
};
|
|
691
750
|
CLIENT_COO_TEMPLATE = `---
|
package/dist/bin/exe-dispatch.js
CHANGED
|
@@ -318,11 +318,19 @@ var init_intercom_queue = __esm({
|
|
|
318
318
|
}
|
|
319
319
|
});
|
|
320
320
|
|
|
321
|
+
// src/lib/db-retry.ts
|
|
322
|
+
var init_db_retry = __esm({
|
|
323
|
+
"src/lib/db-retry.ts"() {
|
|
324
|
+
"use strict";
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
|
|
321
328
|
// src/lib/database.ts
|
|
322
329
|
import { createClient } from "@libsql/client";
|
|
323
330
|
var init_database = __esm({
|
|
324
331
|
"src/lib/database.ts"() {
|
|
325
332
|
"use strict";
|
|
333
|
+
init_db_retry();
|
|
326
334
|
}
|
|
327
335
|
});
|
|
328
336
|
|
|
@@ -425,7 +433,7 @@ var init_config = __esm({
|
|
|
425
433
|
|
|
426
434
|
// src/lib/employees.ts
|
|
427
435
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
428
|
-
import { existsSync as existsSync4, symlinkSync, readlinkSync } from "fs";
|
|
436
|
+
import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync4 } from "fs";
|
|
429
437
|
import { execSync as execSync3 } from "child_process";
|
|
430
438
|
import path4 from "path";
|
|
431
439
|
var EMPLOYEES_PATH;
|
|
@@ -438,7 +446,7 @@ var init_employees = __esm({
|
|
|
438
446
|
});
|
|
439
447
|
|
|
440
448
|
// src/lib/license.ts
|
|
441
|
-
import { readFileSync as
|
|
449
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
|
|
442
450
|
import { randomUUID } from "crypto";
|
|
443
451
|
import path5 from "path";
|
|
444
452
|
import { jwtVerify, importSPKI } from "jose";
|
|
@@ -461,12 +469,12 @@ var init_license = __esm({
|
|
|
461
469
|
});
|
|
462
470
|
|
|
463
471
|
// src/lib/plan-limits.ts
|
|
464
|
-
import { readFileSync as
|
|
472
|
+
import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
|
|
465
473
|
import path6 from "path";
|
|
466
474
|
function getLicenseSync() {
|
|
467
475
|
try {
|
|
468
476
|
if (!existsSync6(CACHE_PATH2)) return freeLicense();
|
|
469
|
-
const raw = JSON.parse(
|
|
477
|
+
const raw = JSON.parse(readFileSync6(CACHE_PATH2, "utf8"));
|
|
470
478
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
471
479
|
const parts = raw.token.split(".");
|
|
472
480
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -505,7 +513,7 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
505
513
|
let count = 0;
|
|
506
514
|
try {
|
|
507
515
|
if (existsSync6(filePath)) {
|
|
508
|
-
const raw =
|
|
516
|
+
const raw = readFileSync6(filePath, "utf8");
|
|
509
517
|
const employees = JSON.parse(raw);
|
|
510
518
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
511
519
|
}
|
|
@@ -540,7 +548,7 @@ var init_plan_limits = __esm({
|
|
|
540
548
|
|
|
541
549
|
// src/lib/tmux-routing.ts
|
|
542
550
|
import { execFileSync as execFileSync2, execSync as execSync4 } from "child_process";
|
|
543
|
-
import { readFileSync as
|
|
551
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync7, appendFileSync } from "fs";
|
|
544
552
|
import path7 from "path";
|
|
545
553
|
import os4 from "os";
|
|
546
554
|
import { fileURLToPath } from "url";
|
|
@@ -589,7 +597,7 @@ function extractRootExe(name) {
|
|
|
589
597
|
}
|
|
590
598
|
function getParentExe(sessionKey) {
|
|
591
599
|
try {
|
|
592
|
-
const data = JSON.parse(
|
|
600
|
+
const data = JSON.parse(readFileSync7(path7.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
593
601
|
return data.parentExe || null;
|
|
594
602
|
} catch {
|
|
595
603
|
return null;
|
|
@@ -623,7 +631,7 @@ function findFreeInstance(employeeName2, exeSession2, maxInstances = 10, isAlive
|
|
|
623
631
|
function readDebounceState() {
|
|
624
632
|
try {
|
|
625
633
|
if (!existsSync7(DEBOUNCE_FILE)) return {};
|
|
626
|
-
return JSON.parse(
|
|
634
|
+
return JSON.parse(readFileSync7(DEBOUNCE_FILE, "utf8"));
|
|
627
635
|
} catch {
|
|
628
636
|
return {};
|
|
629
637
|
}
|
|
@@ -797,7 +805,7 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
|
797
805
|
const claudeJsonPath = path7.join(os4.homedir(), ".claude.json");
|
|
798
806
|
let claudeJson = {};
|
|
799
807
|
try {
|
|
800
|
-
claudeJson = JSON.parse(
|
|
808
|
+
claudeJson = JSON.parse(readFileSync7(claudeJsonPath, "utf8"));
|
|
801
809
|
} catch {
|
|
802
810
|
}
|
|
803
811
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -815,7 +823,7 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
|
815
823
|
const settingsPath = path7.join(projSettingsDir, "settings.json");
|
|
816
824
|
let settings = {};
|
|
817
825
|
try {
|
|
818
|
-
settings = JSON.parse(
|
|
826
|
+
settings = JSON.parse(readFileSync7(settingsPath, "utf8"));
|
|
819
827
|
} catch {
|
|
820
828
|
}
|
|
821
829
|
const perms = settings.permissions ?? {};
|
package/dist/bin/exe-doctor.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 {
|
|
@@ -448,12 +448,65 @@ var init_shard_manager = __esm({
|
|
|
448
448
|
|
|
449
449
|
// src/lib/database.ts
|
|
450
450
|
import { createClient } from "@libsql/client";
|
|
451
|
+
|
|
452
|
+
// src/lib/db-retry.ts
|
|
453
|
+
var MAX_RETRIES = 3;
|
|
454
|
+
var BASE_DELAY_MS = 200;
|
|
455
|
+
var MAX_JITTER_MS = 300;
|
|
456
|
+
function isBusyError(err) {
|
|
457
|
+
if (err instanceof Error) {
|
|
458
|
+
const msg = err.message.toLowerCase();
|
|
459
|
+
return msg.includes("sqlite_busy") || msg.includes("database is locked");
|
|
460
|
+
}
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
463
|
+
function delay(ms) {
|
|
464
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
465
|
+
}
|
|
466
|
+
async function retryOnBusy(fn, label) {
|
|
467
|
+
let lastError;
|
|
468
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
469
|
+
try {
|
|
470
|
+
return await fn();
|
|
471
|
+
} catch (err) {
|
|
472
|
+
lastError = err;
|
|
473
|
+
if (!isBusyError(err) || attempt === MAX_RETRIES) {
|
|
474
|
+
throw err;
|
|
475
|
+
}
|
|
476
|
+
const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
|
|
477
|
+
const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
|
|
478
|
+
process.stderr.write(
|
|
479
|
+
`[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
|
|
480
|
+
`
|
|
481
|
+
);
|
|
482
|
+
await delay(backoff + jitter);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
throw lastError;
|
|
486
|
+
}
|
|
487
|
+
function wrapWithRetry(client) {
|
|
488
|
+
return new Proxy(client, {
|
|
489
|
+
get(target, prop, receiver) {
|
|
490
|
+
if (prop === "execute") {
|
|
491
|
+
return (sql) => retryOnBusy(() => target.execute(sql), "execute");
|
|
492
|
+
}
|
|
493
|
+
if (prop === "batch") {
|
|
494
|
+
return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
|
|
495
|
+
}
|
|
496
|
+
return Reflect.get(target, prop, receiver);
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// src/lib/database.ts
|
|
451
502
|
var _client = null;
|
|
503
|
+
var _resilientClient = null;
|
|
452
504
|
var initTurso = initDatabase;
|
|
453
505
|
async function initDatabase(config) {
|
|
454
506
|
if (_client) {
|
|
455
507
|
_client.close();
|
|
456
508
|
_client = null;
|
|
509
|
+
_resilientClient = null;
|
|
457
510
|
}
|
|
458
511
|
const opts = {
|
|
459
512
|
url: `file:${config.dbPath}`
|
|
@@ -462,17 +515,24 @@ async function initDatabase(config) {
|
|
|
462
515
|
opts.encryptionKey = config.encryptionKey;
|
|
463
516
|
}
|
|
464
517
|
_client = createClient(opts);
|
|
518
|
+
_resilientClient = wrapWithRetry(_client);
|
|
465
519
|
}
|
|
466
520
|
function getClient() {
|
|
521
|
+
if (!_resilientClient) {
|
|
522
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
523
|
+
}
|
|
524
|
+
return _resilientClient;
|
|
525
|
+
}
|
|
526
|
+
function getRawClient() {
|
|
467
527
|
if (!_client) {
|
|
468
528
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
469
529
|
}
|
|
470
530
|
return _client;
|
|
471
531
|
}
|
|
472
532
|
async function ensureSchema() {
|
|
473
|
-
const client =
|
|
533
|
+
const client = getRawClient();
|
|
474
534
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
475
|
-
await client.execute("PRAGMA busy_timeout =
|
|
535
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
476
536
|
try {
|
|
477
537
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
478
538
|
} catch {
|
|
@@ -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 {
|
|
@@ -448,12 +448,65 @@ var init_shard_manager = __esm({
|
|
|
448
448
|
|
|
449
449
|
// src/lib/database.ts
|
|
450
450
|
import { createClient } from "@libsql/client";
|
|
451
|
+
|
|
452
|
+
// src/lib/db-retry.ts
|
|
453
|
+
var MAX_RETRIES = 3;
|
|
454
|
+
var BASE_DELAY_MS = 200;
|
|
455
|
+
var MAX_JITTER_MS = 300;
|
|
456
|
+
function isBusyError(err) {
|
|
457
|
+
if (err instanceof Error) {
|
|
458
|
+
const msg = err.message.toLowerCase();
|
|
459
|
+
return msg.includes("sqlite_busy") || msg.includes("database is locked");
|
|
460
|
+
}
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
463
|
+
function delay(ms) {
|
|
464
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
465
|
+
}
|
|
466
|
+
async function retryOnBusy(fn, label) {
|
|
467
|
+
let lastError;
|
|
468
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
469
|
+
try {
|
|
470
|
+
return await fn();
|
|
471
|
+
} catch (err) {
|
|
472
|
+
lastError = err;
|
|
473
|
+
if (!isBusyError(err) || attempt === MAX_RETRIES) {
|
|
474
|
+
throw err;
|
|
475
|
+
}
|
|
476
|
+
const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
|
|
477
|
+
const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
|
|
478
|
+
process.stderr.write(
|
|
479
|
+
`[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
|
|
480
|
+
`
|
|
481
|
+
);
|
|
482
|
+
await delay(backoff + jitter);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
throw lastError;
|
|
486
|
+
}
|
|
487
|
+
function wrapWithRetry(client) {
|
|
488
|
+
return new Proxy(client, {
|
|
489
|
+
get(target, prop, receiver) {
|
|
490
|
+
if (prop === "execute") {
|
|
491
|
+
return (sql) => retryOnBusy(() => target.execute(sql), "execute");
|
|
492
|
+
}
|
|
493
|
+
if (prop === "batch") {
|
|
494
|
+
return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
|
|
495
|
+
}
|
|
496
|
+
return Reflect.get(target, prop, receiver);
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// src/lib/database.ts
|
|
451
502
|
var _client = null;
|
|
503
|
+
var _resilientClient = null;
|
|
452
504
|
var initTurso = initDatabase;
|
|
453
505
|
async function initDatabase(config) {
|
|
454
506
|
if (_client) {
|
|
455
507
|
_client.close();
|
|
456
508
|
_client = null;
|
|
509
|
+
_resilientClient = null;
|
|
457
510
|
}
|
|
458
511
|
const opts = {
|
|
459
512
|
url: `file:${config.dbPath}`
|
|
@@ -462,17 +515,24 @@ async function initDatabase(config) {
|
|
|
462
515
|
opts.encryptionKey = config.encryptionKey;
|
|
463
516
|
}
|
|
464
517
|
_client = createClient(opts);
|
|
518
|
+
_resilientClient = wrapWithRetry(_client);
|
|
465
519
|
}
|
|
466
520
|
function getClient() {
|
|
521
|
+
if (!_resilientClient) {
|
|
522
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
523
|
+
}
|
|
524
|
+
return _resilientClient;
|
|
525
|
+
}
|
|
526
|
+
function getRawClient() {
|
|
467
527
|
if (!_client) {
|
|
468
528
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
469
529
|
}
|
|
470
530
|
return _client;
|
|
471
531
|
}
|
|
472
532
|
async function ensureSchema() {
|
|
473
|
-
const client =
|
|
533
|
+
const client = getRawClient();
|
|
474
534
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
475
|
-
await client.execute("PRAGMA busy_timeout =
|
|
535
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
476
536
|
try {
|
|
477
537
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
478
538
|
} catch {
|
|
@@ -1266,6 +1326,7 @@ async function disposeDatabase() {
|
|
|
1266
1326
|
if (_client) {
|
|
1267
1327
|
_client.close();
|
|
1268
1328
|
_client = null;
|
|
1329
|
+
_resilientClient = null;
|
|
1269
1330
|
}
|
|
1270
1331
|
}
|
|
1271
1332
|
|
package/dist/bin/exe-forget.js
CHANGED
|
@@ -22,12 +22,68 @@ var init_memory = __esm({
|
|
|
22
22
|
}
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
+
// src/lib/db-retry.ts
|
|
26
|
+
function isBusyError(err) {
|
|
27
|
+
if (err instanceof Error) {
|
|
28
|
+
const msg = err.message.toLowerCase();
|
|
29
|
+
return msg.includes("sqlite_busy") || msg.includes("database is locked");
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
function delay(ms) {
|
|
34
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
35
|
+
}
|
|
36
|
+
async function retryOnBusy(fn, label) {
|
|
37
|
+
let lastError;
|
|
38
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
39
|
+
try {
|
|
40
|
+
return await fn();
|
|
41
|
+
} catch (err) {
|
|
42
|
+
lastError = err;
|
|
43
|
+
if (!isBusyError(err) || attempt === MAX_RETRIES) {
|
|
44
|
+
throw err;
|
|
45
|
+
}
|
|
46
|
+
const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
|
|
47
|
+
const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
|
|
48
|
+
process.stderr.write(
|
|
49
|
+
`[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
|
|
50
|
+
`
|
|
51
|
+
);
|
|
52
|
+
await delay(backoff + jitter);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
throw lastError;
|
|
56
|
+
}
|
|
57
|
+
function wrapWithRetry(client) {
|
|
58
|
+
return new Proxy(client, {
|
|
59
|
+
get(target, prop, receiver) {
|
|
60
|
+
if (prop === "execute") {
|
|
61
|
+
return (sql) => retryOnBusy(() => target.execute(sql), "execute");
|
|
62
|
+
}
|
|
63
|
+
if (prop === "batch") {
|
|
64
|
+
return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
|
|
65
|
+
}
|
|
66
|
+
return Reflect.get(target, prop, receiver);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
|
|
71
|
+
var init_db_retry = __esm({
|
|
72
|
+
"src/lib/db-retry.ts"() {
|
|
73
|
+
"use strict";
|
|
74
|
+
MAX_RETRIES = 3;
|
|
75
|
+
BASE_DELAY_MS = 200;
|
|
76
|
+
MAX_JITTER_MS = 300;
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
25
80
|
// src/lib/database.ts
|
|
26
81
|
import { createClient } from "@libsql/client";
|
|
27
82
|
async function initDatabase(config) {
|
|
28
83
|
if (_client) {
|
|
29
84
|
_client.close();
|
|
30
85
|
_client = null;
|
|
86
|
+
_resilientClient = null;
|
|
31
87
|
}
|
|
32
88
|
const opts = {
|
|
33
89
|
url: `file:${config.dbPath}`
|
|
@@ -36,17 +92,24 @@ async function initDatabase(config) {
|
|
|
36
92
|
opts.encryptionKey = config.encryptionKey;
|
|
37
93
|
}
|
|
38
94
|
_client = createClient(opts);
|
|
95
|
+
_resilientClient = wrapWithRetry(_client);
|
|
39
96
|
}
|
|
40
97
|
function getClient() {
|
|
98
|
+
if (!_resilientClient) {
|
|
99
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
100
|
+
}
|
|
101
|
+
return _resilientClient;
|
|
102
|
+
}
|
|
103
|
+
function getRawClient() {
|
|
41
104
|
if (!_client) {
|
|
42
105
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
43
106
|
}
|
|
44
107
|
return _client;
|
|
45
108
|
}
|
|
46
109
|
async function ensureSchema() {
|
|
47
|
-
const client =
|
|
110
|
+
const client = getRawClient();
|
|
48
111
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
49
|
-
await client.execute("PRAGMA busy_timeout =
|
|
112
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
50
113
|
try {
|
|
51
114
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
52
115
|
} catch {
|
|
@@ -835,11 +898,13 @@ async function ensureSchema() {
|
|
|
835
898
|
}
|
|
836
899
|
}
|
|
837
900
|
}
|
|
838
|
-
var _client, initTurso;
|
|
901
|
+
var _client, _resilientClient, initTurso;
|
|
839
902
|
var init_database = __esm({
|
|
840
903
|
"src/lib/database.ts"() {
|
|
841
904
|
"use strict";
|
|
905
|
+
init_db_retry();
|
|
842
906
|
_client = null;
|
|
907
|
+
_resilientClient = null;
|
|
843
908
|
initTurso = initDatabase;
|
|
844
909
|
}
|
|
845
910
|
});
|
|
@@ -1140,7 +1205,7 @@ function listShards() {
|
|
|
1140
1205
|
}
|
|
1141
1206
|
async function ensureShardSchema(client) {
|
|
1142
1207
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
1143
|
-
await client.execute("PRAGMA busy_timeout =
|
|
1208
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
1144
1209
|
try {
|
|
1145
1210
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
1146
1211
|
} catch {
|