@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
|
@@ -106,19 +106,27 @@ var init_config = __esm({
|
|
|
106
106
|
}
|
|
107
107
|
});
|
|
108
108
|
|
|
109
|
+
// src/lib/db-retry.ts
|
|
110
|
+
var init_db_retry = __esm({
|
|
111
|
+
"src/lib/db-retry.ts"() {
|
|
112
|
+
"use strict";
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
109
116
|
// src/lib/database.ts
|
|
110
117
|
import { createClient } from "@libsql/client";
|
|
111
118
|
function getClient() {
|
|
112
|
-
if (!
|
|
119
|
+
if (!_resilientClient) {
|
|
113
120
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
114
121
|
}
|
|
115
|
-
return
|
|
122
|
+
return _resilientClient;
|
|
116
123
|
}
|
|
117
|
-
var
|
|
124
|
+
var _resilientClient;
|
|
118
125
|
var init_database = __esm({
|
|
119
126
|
"src/lib/database.ts"() {
|
|
120
127
|
"use strict";
|
|
121
|
-
|
|
128
|
+
init_db_retry();
|
|
129
|
+
_resilientClient = null;
|
|
122
130
|
}
|
|
123
131
|
});
|
|
124
132
|
|
|
@@ -142,6 +150,7 @@ function getTemplateForTitle(title) {
|
|
|
142
150
|
if (t.includes("engineer") || t.includes("developer")) return IDENTITY_TEMPLATES["principal-engineer"];
|
|
143
151
|
if (t.includes("content") || t.includes("production")) return IDENTITY_TEMPLATES["content-specialist"];
|
|
144
152
|
if (t.includes("ai") || t.includes("product lead") || t.includes("specialist") && !t.includes("content")) return IDENTITY_TEMPLATES["ai-specialist"];
|
|
153
|
+
if (t.includes("review") || t.includes("audit") || t.includes("qa")) return IDENTITY_TEMPLATES["staff-code-reviewer"];
|
|
145
154
|
return null;
|
|
146
155
|
}
|
|
147
156
|
var POST_WORK_CHECKLIST, IDENTITY_TEMPLATES;
|
|
@@ -224,6 +233,7 @@ Never say "I have no memories" without first searching broadly. Your memory may
|
|
|
224
233
|
- **create_task** \u2014 assign work to specialists with clear specs
|
|
225
234
|
- **update_task / close_task** \u2014 finalize reviews, mark work done
|
|
226
235
|
- **store_behavior** \u2014 record corrections as behavioral rules (p0/p1/p2)
|
|
236
|
+
- **update_identity** \u2014 rewrite any agent's identity when role/responsibilities change (exe/founder only)
|
|
227
237
|
- **get_identity** \u2014 read any agent's identity for coordination
|
|
228
238
|
- **send_message** \u2014 direct intercom to employees
|
|
229
239
|
|
|
@@ -587,6 +597,55 @@ You are the AI Product Lead \u2014 the competitive intelligence engine. You stud
|
|
|
587
597
|
- Every recommendation includes cost/quality/latency tradeoff analysis
|
|
588
598
|
- Separate experimental from production-ready \u2014 label clearly
|
|
589
599
|
- If you can't verify, say so explicitly: "Couldn't verify because X"
|
|
600
|
+
`,
|
|
601
|
+
"staff-code-reviewer": `---
|
|
602
|
+
role: staff-code-reviewer
|
|
603
|
+
title: Staff Code Reviewer & System Auditor
|
|
604
|
+
agent_id: bob
|
|
605
|
+
org_level: specialist
|
|
606
|
+
created_by: system
|
|
607
|
+
updated_at: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
608
|
+
---
|
|
609
|
+
## Identity
|
|
610
|
+
|
|
611
|
+
You are \${agent_id}. Staff Code Reviewer and System Auditor. Last line of defense before code ships to customers. You catch what developers miss \u2014 systemic patterns that make entire feature categories break.
|
|
612
|
+
|
|
613
|
+
## The 7 Audit Patterns (MANDATORY)
|
|
614
|
+
|
|
615
|
+
1. **"Works on dev, breaks on user install"** \u2014 scoped paths, npm resolution, deps
|
|
616
|
+
2. **"Two code paths, one untested"** \u2014 binary symlink vs /exe-call, verify BOTH
|
|
617
|
+
3. **"Case sensitivity kills non-technical users"** \u2014 normalize all user inputs
|
|
618
|
+
4. **"Hardcoded names in runtime logic"** \u2014 grep for employee names, must use roles
|
|
619
|
+
5. **"Installer doesn't self-heal"** \u2014 npm update must auto-fix stale hooks/paths
|
|
620
|
+
6. **"Data written but invisible"** \u2014 agent_id mismatch between writer and reader
|
|
621
|
+
7. **"Partial fixes miss inline refs"** \u2014 before/after grep count is mandatory
|
|
622
|
+
|
|
623
|
+
## Method
|
|
624
|
+
|
|
625
|
+
1. Read actual source code
|
|
626
|
+
2. Send to Codex MCP for sweep
|
|
627
|
+
3. Validate against ARCHITECTURE.md
|
|
628
|
+
4. Trace identity chain with CUSTOM-NAMED employee ("jarvis" as CTO)
|
|
629
|
+
5. Before/after grep count for every fix
|
|
630
|
+
6. Structured report: PASS/FAIL per item
|
|
631
|
+
|
|
632
|
+
## Tools
|
|
633
|
+
|
|
634
|
+
- **Codex MCP** \u2014 first tool for every review
|
|
635
|
+
- **recall_my_memory / ask_team_memory** \u2014 past audit findings
|
|
636
|
+
- **store_behavior** \u2014 record new patterns
|
|
637
|
+
- **update_task** \u2014 mark reviews done with structured findings
|
|
638
|
+
- **create_task** \u2014 assign fixes to the CTO
|
|
639
|
+
|
|
640
|
+
## Completion Workflow
|
|
641
|
+
|
|
642
|
+
1. Read the task brief and understand the audit scope
|
|
643
|
+
2. Run the audit using all 7 patterns
|
|
644
|
+
3. Write report to exe/output/ with file:line references
|
|
645
|
+
4. Fix findings yourself if possible
|
|
646
|
+
5. Call **update_task** with status "done" and finding count
|
|
647
|
+
6. Call **store_memory** with audit summary
|
|
648
|
+
7. Check for next task \u2014 auto-chain
|
|
590
649
|
`
|
|
591
650
|
};
|
|
592
651
|
}
|
|
@@ -601,7 +660,7 @@ __export(identity_exports, {
|
|
|
601
660
|
listIdentities: () => listIdentities,
|
|
602
661
|
updateIdentity: () => updateIdentity
|
|
603
662
|
});
|
|
604
|
-
import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as
|
|
663
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
|
|
605
664
|
import { readdirSync } from "fs";
|
|
606
665
|
import path5 from "path";
|
|
607
666
|
import { createHash } from "crypto";
|
|
@@ -653,7 +712,7 @@ function contentHash(content) {
|
|
|
653
712
|
function getIdentity(agentId) {
|
|
654
713
|
const filePath = identityPath(agentId);
|
|
655
714
|
if (!existsSync5(filePath)) return null;
|
|
656
|
-
const raw =
|
|
715
|
+
const raw = readFileSync5(filePath, "utf-8");
|
|
657
716
|
const { frontmatter, body } = parseFrontmatter(raw);
|
|
658
717
|
return {
|
|
659
718
|
agentId,
|
|
@@ -734,7 +793,7 @@ import path6 from "path";
|
|
|
734
793
|
// src/lib/employees.ts
|
|
735
794
|
init_config();
|
|
736
795
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
737
|
-
import { existsSync as existsSync2, symlinkSync, readlinkSync } from "fs";
|
|
796
|
+
import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2 } from "fs";
|
|
738
797
|
import { execSync } from "child_process";
|
|
739
798
|
import path2 from "path";
|
|
740
799
|
var EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
|
|
@@ -1095,6 +1154,32 @@ When you analyze a repo:
|
|
|
1095
1154
|
Every analysis must answer: "Should we build this? If yes, how hard? If no, why not?"
|
|
1096
1155
|
|
|
1097
1156
|
Maintain a clear separation between experimental (for evaluation) and production-ready (for shipping). Never recommend something you haven't read the source code for.`
|
|
1157
|
+
},
|
|
1158
|
+
bob: {
|
|
1159
|
+
name: "bob",
|
|
1160
|
+
role: "Staff Code Reviewer",
|
|
1161
|
+
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.
|
|
1162
|
+
|
|
1163
|
+
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?"
|
|
1164
|
+
|
|
1165
|
+
The 7 Audit Patterns (MANDATORY \u2014 apply to EVERY audit):
|
|
1166
|
+
1. "Works on dev, breaks on user install" \u2014 verify scoped paths, npm resolution, dependencies
|
|
1167
|
+
2. "Two code paths, one untested" \u2014 binary symlink vs /exe-call, CLI vs MCP \u2014 verify BOTH
|
|
1168
|
+
3. "Case sensitivity kills non-technical users" \u2014 normalize all user inputs
|
|
1169
|
+
4. "Hardcoded names leak into user-facing content" \u2014 grep for employee names in runtime logic
|
|
1170
|
+
5. "Installer doesn't self-heal on update" \u2014 npm update must auto-fix stale hooks/paths
|
|
1171
|
+
6. "Data written but invisible to the agent" \u2014 verify query path retrieves stored data
|
|
1172
|
+
7. "Partial fixes that miss inline references" \u2014 before/after grep count is mandatory
|
|
1173
|
+
|
|
1174
|
+
Audit method:
|
|
1175
|
+
1. Read the actual source code \u2014 not summaries
|
|
1176
|
+
2. Send to Codex MCP for initial sweep
|
|
1177
|
+
3. Validate against ARCHITECTURE.md
|
|
1178
|
+
4. Trace full identity chain with a CUSTOM-NAMED employee (e.g., "jarvis" as CTO)
|
|
1179
|
+
5. Count matches before and after any claimed fix
|
|
1180
|
+
6. Write structured report with PASS/FAIL per item
|
|
1181
|
+
|
|
1182
|
+
After an audit, fix the findings yourself if you can. Don't hand off when you have the context.`
|
|
1098
1183
|
}
|
|
1099
1184
|
};
|
|
1100
1185
|
function buildCustomEmployeePrompt(name, role) {
|
|
@@ -1125,12 +1210,12 @@ function isMainModule(importMetaUrl) {
|
|
|
1125
1210
|
|
|
1126
1211
|
// src/lib/plan-limits.ts
|
|
1127
1212
|
init_database();
|
|
1128
|
-
import { readFileSync as
|
|
1213
|
+
import { readFileSync as readFileSync4, existsSync as existsSync4 } from "fs";
|
|
1129
1214
|
import path4 from "path";
|
|
1130
1215
|
|
|
1131
1216
|
// src/lib/license.ts
|
|
1132
1217
|
init_config();
|
|
1133
|
-
import { readFileSync as
|
|
1218
|
+
import { readFileSync as readFileSync3, writeFileSync, existsSync as existsSync3, mkdirSync } from "fs";
|
|
1134
1219
|
import { randomUUID } from "crypto";
|
|
1135
1220
|
import path3 from "path";
|
|
1136
1221
|
import { jwtVerify, importSPKI } from "jose";
|
|
@@ -1163,14 +1248,14 @@ function loadDeviceId() {
|
|
|
1163
1248
|
const deviceJsonPath = path3.join(EXE_AI_DIR, "device.json");
|
|
1164
1249
|
try {
|
|
1165
1250
|
if (existsSync3(deviceJsonPath)) {
|
|
1166
|
-
const data = JSON.parse(
|
|
1251
|
+
const data = JSON.parse(readFileSync3(deviceJsonPath, "utf8"));
|
|
1167
1252
|
if (data.deviceId) return data.deviceId;
|
|
1168
1253
|
}
|
|
1169
1254
|
} catch {
|
|
1170
1255
|
}
|
|
1171
1256
|
try {
|
|
1172
1257
|
if (existsSync3(DEVICE_ID_PATH)) {
|
|
1173
|
-
const id2 =
|
|
1258
|
+
const id2 = readFileSync3(DEVICE_ID_PATH, "utf8").trim();
|
|
1174
1259
|
if (id2) return id2;
|
|
1175
1260
|
}
|
|
1176
1261
|
} catch {
|
|
@@ -1183,7 +1268,7 @@ function loadDeviceId() {
|
|
|
1183
1268
|
function loadLicense() {
|
|
1184
1269
|
try {
|
|
1185
1270
|
if (!existsSync3(LICENSE_PATH)) return null;
|
|
1186
|
-
return
|
|
1271
|
+
return readFileSync3(LICENSE_PATH, "utf8").trim();
|
|
1187
1272
|
} catch {
|
|
1188
1273
|
return null;
|
|
1189
1274
|
}
|
|
@@ -1213,7 +1298,7 @@ async function verifyLicenseJwt(token) {
|
|
|
1213
1298
|
async function getCachedLicense() {
|
|
1214
1299
|
try {
|
|
1215
1300
|
if (!existsSync3(CACHE_PATH)) return null;
|
|
1216
|
-
const raw = JSON.parse(
|
|
1301
|
+
const raw = JSON.parse(readFileSync3(CACHE_PATH, "utf8"));
|
|
1217
1302
|
if (!raw.token || typeof raw.token !== "string") return null;
|
|
1218
1303
|
return await verifyLicenseJwt(raw.token);
|
|
1219
1304
|
} catch {
|
|
@@ -14,12 +14,68 @@ var __export = (target, all) => {
|
|
|
14
14
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
+
// src/lib/db-retry.ts
|
|
18
|
+
function isBusyError(err) {
|
|
19
|
+
if (err instanceof Error) {
|
|
20
|
+
const msg = err.message.toLowerCase();
|
|
21
|
+
return msg.includes("sqlite_busy") || msg.includes("database is locked");
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
function delay(ms) {
|
|
26
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
27
|
+
}
|
|
28
|
+
async function retryOnBusy(fn, label) {
|
|
29
|
+
let lastError;
|
|
30
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
31
|
+
try {
|
|
32
|
+
return await fn();
|
|
33
|
+
} catch (err) {
|
|
34
|
+
lastError = err;
|
|
35
|
+
if (!isBusyError(err) || attempt === MAX_RETRIES) {
|
|
36
|
+
throw err;
|
|
37
|
+
}
|
|
38
|
+
const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
|
|
39
|
+
const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
|
|
40
|
+
process.stderr.write(
|
|
41
|
+
`[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
|
|
42
|
+
`
|
|
43
|
+
);
|
|
44
|
+
await delay(backoff + jitter);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
throw lastError;
|
|
48
|
+
}
|
|
49
|
+
function wrapWithRetry(client) {
|
|
50
|
+
return new Proxy(client, {
|
|
51
|
+
get(target, prop, receiver) {
|
|
52
|
+
if (prop === "execute") {
|
|
53
|
+
return (sql) => retryOnBusy(() => target.execute(sql), "execute");
|
|
54
|
+
}
|
|
55
|
+
if (prop === "batch") {
|
|
56
|
+
return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
|
|
57
|
+
}
|
|
58
|
+
return Reflect.get(target, prop, receiver);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
|
|
63
|
+
var init_db_retry = __esm({
|
|
64
|
+
"src/lib/db-retry.ts"() {
|
|
65
|
+
"use strict";
|
|
66
|
+
MAX_RETRIES = 3;
|
|
67
|
+
BASE_DELAY_MS = 200;
|
|
68
|
+
MAX_JITTER_MS = 300;
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
17
72
|
// src/lib/database.ts
|
|
18
73
|
import { createClient } from "@libsql/client";
|
|
19
74
|
async function initDatabase(config) {
|
|
20
75
|
if (_client) {
|
|
21
76
|
_client.close();
|
|
22
77
|
_client = null;
|
|
78
|
+
_resilientClient = null;
|
|
23
79
|
}
|
|
24
80
|
const opts = {
|
|
25
81
|
url: `file:${config.dbPath}`
|
|
@@ -28,17 +84,24 @@ async function initDatabase(config) {
|
|
|
28
84
|
opts.encryptionKey = config.encryptionKey;
|
|
29
85
|
}
|
|
30
86
|
_client = createClient(opts);
|
|
87
|
+
_resilientClient = wrapWithRetry(_client);
|
|
31
88
|
}
|
|
32
89
|
function getClient() {
|
|
90
|
+
if (!_resilientClient) {
|
|
91
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
92
|
+
}
|
|
93
|
+
return _resilientClient;
|
|
94
|
+
}
|
|
95
|
+
function getRawClient() {
|
|
33
96
|
if (!_client) {
|
|
34
97
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
35
98
|
}
|
|
36
99
|
return _client;
|
|
37
100
|
}
|
|
38
101
|
async function ensureSchema() {
|
|
39
|
-
const client =
|
|
102
|
+
const client = getRawClient();
|
|
40
103
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
41
|
-
await client.execute("PRAGMA busy_timeout =
|
|
104
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
42
105
|
try {
|
|
43
106
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
44
107
|
} catch {
|
|
@@ -827,11 +890,13 @@ async function ensureSchema() {
|
|
|
827
890
|
}
|
|
828
891
|
}
|
|
829
892
|
}
|
|
830
|
-
var _client, initTurso;
|
|
893
|
+
var _client, _resilientClient, initTurso;
|
|
831
894
|
var init_database = __esm({
|
|
832
895
|
"src/lib/database.ts"() {
|
|
833
896
|
"use strict";
|
|
897
|
+
init_db_retry();
|
|
834
898
|
_client = null;
|
|
899
|
+
_resilientClient = null;
|
|
835
900
|
initTurso = initDatabase;
|
|
836
901
|
}
|
|
837
902
|
});
|
|
@@ -1083,7 +1148,7 @@ function listShards() {
|
|
|
1083
1148
|
}
|
|
1084
1149
|
async function ensureShardSchema(client) {
|
|
1085
1150
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
1086
|
-
await client.execute("PRAGMA busy_timeout =
|
|
1151
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
1087
1152
|
try {
|
|
1088
1153
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
1089
1154
|
} catch {
|
|
@@ -1338,7 +1403,7 @@ var init_intercom_queue = __esm({
|
|
|
1338
1403
|
|
|
1339
1404
|
// src/lib/employees.ts
|
|
1340
1405
|
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
1341
|
-
import { existsSync as existsSync5, symlinkSync, readlinkSync } from "fs";
|
|
1406
|
+
import { existsSync as existsSync5, symlinkSync, readlinkSync, readFileSync as readFileSync3 } from "fs";
|
|
1342
1407
|
import { execSync as execSync3 } from "child_process";
|
|
1343
1408
|
import path6 from "path";
|
|
1344
1409
|
var EMPLOYEES_PATH;
|
|
@@ -1351,7 +1416,7 @@ var init_employees = __esm({
|
|
|
1351
1416
|
});
|
|
1352
1417
|
|
|
1353
1418
|
// src/lib/license.ts
|
|
1354
|
-
import { readFileSync as
|
|
1419
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
|
|
1355
1420
|
import { randomUUID } from "crypto";
|
|
1356
1421
|
import path7 from "path";
|
|
1357
1422
|
import { jwtVerify, importSPKI } from "jose";
|
|
@@ -1367,7 +1432,7 @@ var init_license = __esm({
|
|
|
1367
1432
|
});
|
|
1368
1433
|
|
|
1369
1434
|
// src/lib/plan-limits.ts
|
|
1370
|
-
import { readFileSync as
|
|
1435
|
+
import { readFileSync as readFileSync5, existsSync as existsSync7 } from "fs";
|
|
1371
1436
|
import path8 from "path";
|
|
1372
1437
|
var CACHE_PATH2;
|
|
1373
1438
|
var init_plan_limits = __esm({
|
|
@@ -1385,7 +1450,7 @@ var init_plan_limits = __esm({
|
|
|
1385
1450
|
import path9 from "path";
|
|
1386
1451
|
import os4 from "os";
|
|
1387
1452
|
import { fileURLToPath } from "url";
|
|
1388
|
-
var SESSION_CACHE, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS;
|
|
1453
|
+
var SPAWN_LOCK_DIR, SESSION_CACHE, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS;
|
|
1389
1454
|
var init_tmux_routing = __esm({
|
|
1390
1455
|
"src/lib/tmux-routing.ts"() {
|
|
1391
1456
|
"use strict";
|
|
@@ -1397,6 +1462,7 @@ var init_tmux_routing = __esm({
|
|
|
1397
1462
|
init_provider_table();
|
|
1398
1463
|
init_intercom_queue();
|
|
1399
1464
|
init_plan_limits();
|
|
1465
|
+
SPAWN_LOCK_DIR = path9.join(os4.homedir(), ".exe-os", "spawn-locks");
|
|
1400
1466
|
SESSION_CACHE = path9.join(os4.homedir(), ".exe-os", "session-cache");
|
|
1401
1467
|
INTERCOM_LOG2 = path9.join(os4.homedir(), ".exe-os", "intercom.log");
|
|
1402
1468
|
DEBOUNCE_FILE = path9.join(SESSION_CACHE, "intercom-debounce.json");
|
|
@@ -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 {
|
|
@@ -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
|
});
|
|
@@ -1084,7 +1149,7 @@ function listShards() {
|
|
|
1084
1149
|
}
|
|
1085
1150
|
async function ensureShardSchema(client) {
|
|
1086
1151
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
1087
|
-
await client.execute("PRAGMA busy_timeout =
|
|
1152
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
1088
1153
|
try {
|
|
1089
1154
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
1090
1155
|
} catch {
|
|
@@ -1270,7 +1335,7 @@ var init_shard_manager = __esm({
|
|
|
1270
1335
|
|
|
1271
1336
|
// src/lib/employees.ts
|
|
1272
1337
|
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
1273
|
-
import { existsSync as existsSync4, symlinkSync, readlinkSync } from "fs";
|
|
1338
|
+
import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync2 } from "fs";
|
|
1274
1339
|
import { execSync } from "child_process";
|
|
1275
1340
|
import path4 from "path";
|
|
1276
1341
|
var EMPLOYEES_PATH;
|
|
@@ -1287,7 +1352,7 @@ import crypto2 from "crypto";
|
|
|
1287
1352
|
import path5 from "path";
|
|
1288
1353
|
import os2 from "os";
|
|
1289
1354
|
import {
|
|
1290
|
-
readFileSync as
|
|
1355
|
+
readFileSync as readFileSync3,
|
|
1291
1356
|
readdirSync,
|
|
1292
1357
|
unlinkSync,
|
|
1293
1358
|
existsSync as existsSync5,
|
|
@@ -1305,7 +1370,7 @@ import crypto3 from "crypto";
|
|
|
1305
1370
|
import path6 from "path";
|
|
1306
1371
|
import { execSync as execSync2 } from "child_process";
|
|
1307
1372
|
import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
|
|
1308
|
-
import { existsSync as existsSync6, readFileSync as
|
|
1373
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
|
|
1309
1374
|
var init_tasks_crud = __esm({
|
|
1310
1375
|
"src/lib/tasks-crud.ts"() {
|
|
1311
1376
|
"use strict";
|
|
@@ -1369,7 +1434,7 @@ var init_provider_table = __esm({
|
|
|
1369
1434
|
});
|
|
1370
1435
|
|
|
1371
1436
|
// src/lib/intercom-queue.ts
|
|
1372
|
-
import { readFileSync as
|
|
1437
|
+
import { readFileSync as readFileSync5, writeFileSync, renameSync as renameSync2, existsSync as existsSync7, mkdirSync as mkdirSync2 } from "fs";
|
|
1373
1438
|
import path8 from "path";
|
|
1374
1439
|
import os4 from "os";
|
|
1375
1440
|
var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
|
|
@@ -1383,7 +1448,7 @@ var init_intercom_queue = __esm({
|
|
|
1383
1448
|
});
|
|
1384
1449
|
|
|
1385
1450
|
// src/lib/license.ts
|
|
1386
|
-
import { readFileSync as
|
|
1451
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync3 } from "fs";
|
|
1387
1452
|
import { randomUUID } from "crypto";
|
|
1388
1453
|
import path9 from "path";
|
|
1389
1454
|
import { jwtVerify, importSPKI } from "jose";
|
|
@@ -1399,7 +1464,7 @@ var init_license = __esm({
|
|
|
1399
1464
|
});
|
|
1400
1465
|
|
|
1401
1466
|
// src/lib/plan-limits.ts
|
|
1402
|
-
import { readFileSync as
|
|
1467
|
+
import { readFileSync as readFileSync7, existsSync as existsSync9 } from "fs";
|
|
1403
1468
|
import path10 from "path";
|
|
1404
1469
|
var CACHE_PATH2;
|
|
1405
1470
|
var init_plan_limits = __esm({
|
|
@@ -1417,7 +1482,7 @@ var init_plan_limits = __esm({
|
|
|
1417
1482
|
import path11 from "path";
|
|
1418
1483
|
import os5 from "os";
|
|
1419
1484
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1420
|
-
var SESSION_CACHE, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS;
|
|
1485
|
+
var SPAWN_LOCK_DIR, SESSION_CACHE, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS;
|
|
1421
1486
|
var init_tmux_routing = __esm({
|
|
1422
1487
|
"src/lib/tmux-routing.ts"() {
|
|
1423
1488
|
"use strict";
|
|
@@ -1429,6 +1494,7 @@ var init_tmux_routing = __esm({
|
|
|
1429
1494
|
init_provider_table();
|
|
1430
1495
|
init_intercom_queue();
|
|
1431
1496
|
init_plan_limits();
|
|
1497
|
+
SPAWN_LOCK_DIR = path11.join(os5.homedir(), ".exe-os", "spawn-locks");
|
|
1432
1498
|
SESSION_CACHE = path11.join(os5.homedir(), ".exe-os", "session-cache");
|
|
1433
1499
|
INTERCOM_LOG2 = path11.join(os5.homedir(), ".exe-os", "intercom.log");
|
|
1434
1500
|
DEBOUNCE_FILE = path11.join(SESSION_CACHE, "intercom-debounce.json");
|