@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
|
@@ -23,12 +23,68 @@ var init_memory = __esm({
|
|
|
23
23
|
}
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
+
// src/lib/db-retry.ts
|
|
27
|
+
function isBusyError(err) {
|
|
28
|
+
if (err instanceof Error) {
|
|
29
|
+
const msg = err.message.toLowerCase();
|
|
30
|
+
return msg.includes("sqlite_busy") || msg.includes("database is locked");
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
function delay(ms) {
|
|
35
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
36
|
+
}
|
|
37
|
+
async function retryOnBusy(fn, label) {
|
|
38
|
+
let lastError;
|
|
39
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
40
|
+
try {
|
|
41
|
+
return await fn();
|
|
42
|
+
} catch (err) {
|
|
43
|
+
lastError = err;
|
|
44
|
+
if (!isBusyError(err) || attempt === MAX_RETRIES) {
|
|
45
|
+
throw err;
|
|
46
|
+
}
|
|
47
|
+
const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
|
|
48
|
+
const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
|
|
49
|
+
process.stderr.write(
|
|
50
|
+
`[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
|
|
51
|
+
`
|
|
52
|
+
);
|
|
53
|
+
await delay(backoff + jitter);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
throw lastError;
|
|
57
|
+
}
|
|
58
|
+
function wrapWithRetry(client) {
|
|
59
|
+
return new Proxy(client, {
|
|
60
|
+
get(target, prop, receiver) {
|
|
61
|
+
if (prop === "execute") {
|
|
62
|
+
return (sql) => retryOnBusy(() => target.execute(sql), "execute");
|
|
63
|
+
}
|
|
64
|
+
if (prop === "batch") {
|
|
65
|
+
return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
|
|
66
|
+
}
|
|
67
|
+
return Reflect.get(target, prop, receiver);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
|
|
72
|
+
var init_db_retry = __esm({
|
|
73
|
+
"src/lib/db-retry.ts"() {
|
|
74
|
+
"use strict";
|
|
75
|
+
MAX_RETRIES = 3;
|
|
76
|
+
BASE_DELAY_MS = 200;
|
|
77
|
+
MAX_JITTER_MS = 300;
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
26
81
|
// src/lib/database.ts
|
|
27
82
|
import { createClient } from "@libsql/client";
|
|
28
83
|
async function initDatabase(config) {
|
|
29
84
|
if (_client) {
|
|
30
85
|
_client.close();
|
|
31
86
|
_client = null;
|
|
87
|
+
_resilientClient = null;
|
|
32
88
|
}
|
|
33
89
|
const opts = {
|
|
34
90
|
url: `file:${config.dbPath}`
|
|
@@ -37,17 +93,24 @@ async function initDatabase(config) {
|
|
|
37
93
|
opts.encryptionKey = config.encryptionKey;
|
|
38
94
|
}
|
|
39
95
|
_client = createClient(opts);
|
|
96
|
+
_resilientClient = wrapWithRetry(_client);
|
|
40
97
|
}
|
|
41
98
|
function getClient() {
|
|
99
|
+
if (!_resilientClient) {
|
|
100
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
101
|
+
}
|
|
102
|
+
return _resilientClient;
|
|
103
|
+
}
|
|
104
|
+
function getRawClient() {
|
|
42
105
|
if (!_client) {
|
|
43
106
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
44
107
|
}
|
|
45
108
|
return _client;
|
|
46
109
|
}
|
|
47
110
|
async function ensureSchema() {
|
|
48
|
-
const client =
|
|
111
|
+
const client = getRawClient();
|
|
49
112
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
50
|
-
await client.execute("PRAGMA busy_timeout =
|
|
113
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
51
114
|
try {
|
|
52
115
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
53
116
|
} catch {
|
|
@@ -840,13 +903,16 @@ async function disposeDatabase() {
|
|
|
840
903
|
if (_client) {
|
|
841
904
|
_client.close();
|
|
842
905
|
_client = null;
|
|
906
|
+
_resilientClient = null;
|
|
843
907
|
}
|
|
844
908
|
}
|
|
845
|
-
var _client, initTurso, disposeTurso;
|
|
909
|
+
var _client, _resilientClient, initTurso, disposeTurso;
|
|
846
910
|
var init_database = __esm({
|
|
847
911
|
"src/lib/database.ts"() {
|
|
848
912
|
"use strict";
|
|
913
|
+
init_db_retry();
|
|
849
914
|
_client = null;
|
|
915
|
+
_resilientClient = null;
|
|
850
916
|
initTurso = initDatabase;
|
|
851
917
|
disposeTurso = disposeDatabase;
|
|
852
918
|
}
|
|
@@ -1202,7 +1268,7 @@ function listShards() {
|
|
|
1202
1268
|
}
|
|
1203
1269
|
async function ensureShardSchema(client) {
|
|
1204
1270
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
1205
|
-
await client.execute("PRAGMA busy_timeout =
|
|
1271
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
1206
1272
|
try {
|
|
1207
1273
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
1208
1274
|
} catch {
|
|
@@ -1463,7 +1529,8 @@ async function writeMemory(record) {
|
|
|
1463
1529
|
has_error: record.has_error ? 1 : 0,
|
|
1464
1530
|
raw_text: record.raw_text,
|
|
1465
1531
|
vector: record.vector,
|
|
1466
|
-
version:
|
|
1532
|
+
version: 0,
|
|
1533
|
+
// Placeholder — assigned atomically at flush time
|
|
1467
1534
|
task_id: record.task_id ?? null,
|
|
1468
1535
|
importance: record.importance ?? 5,
|
|
1469
1536
|
status: record.status ?? "active",
|
|
@@ -1497,6 +1564,13 @@ async function flushBatch() {
|
|
|
1497
1564
|
_flushing = true;
|
|
1498
1565
|
try {
|
|
1499
1566
|
const batch = _pendingRecords.slice(0);
|
|
1567
|
+
const client = getClient();
|
|
1568
|
+
const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
|
|
1569
|
+
let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
1570
|
+
for (const row of batch) {
|
|
1571
|
+
row.version = baseVersion++;
|
|
1572
|
+
}
|
|
1573
|
+
_nextVersion = baseVersion;
|
|
1500
1574
|
const buildStmt = (row) => {
|
|
1501
1575
|
const hasVector = row.vector !== null;
|
|
1502
1576
|
const taskId = row.task_id ?? null;
|
|
@@ -2087,11 +2161,11 @@ async function connectEmbedDaemon() {
|
|
|
2087
2161
|
}
|
|
2088
2162
|
}
|
|
2089
2163
|
const start = Date.now();
|
|
2090
|
-
let
|
|
2164
|
+
let delay2 = 100;
|
|
2091
2165
|
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
2092
|
-
await new Promise((r) => setTimeout(r,
|
|
2166
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
2093
2167
|
if (await connectToSocket()) return true;
|
|
2094
|
-
|
|
2168
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
2095
2169
|
}
|
|
2096
2170
|
return false;
|
|
2097
2171
|
}
|
|
@@ -2183,11 +2257,11 @@ async function embedViaClient(text, priority = "high") {
|
|
|
2183
2257
|
`);
|
|
2184
2258
|
killAndRespawnDaemon();
|
|
2185
2259
|
const start = Date.now();
|
|
2186
|
-
let
|
|
2260
|
+
let delay2 = 200;
|
|
2187
2261
|
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
2188
|
-
await new Promise((r) => setTimeout(r,
|
|
2262
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
2189
2263
|
if (await connectToSocket()) break;
|
|
2190
|
-
|
|
2264
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
2191
2265
|
}
|
|
2192
2266
|
if (!_connected) return null;
|
|
2193
2267
|
}
|
|
@@ -2199,11 +2273,11 @@ async function embedViaClient(text, priority = "high") {
|
|
|
2199
2273
|
`);
|
|
2200
2274
|
killAndRespawnDaemon();
|
|
2201
2275
|
const start = Date.now();
|
|
2202
|
-
let
|
|
2276
|
+
let delay2 = 200;
|
|
2203
2277
|
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
2204
|
-
await new Promise((r) => setTimeout(r,
|
|
2278
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
2205
2279
|
if (await connectToSocket()) break;
|
|
2206
|
-
|
|
2280
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
2207
2281
|
}
|
|
2208
2282
|
if (!_connected) return null;
|
|
2209
2283
|
const retry = await sendRequest([text], priority);
|
|
@@ -75,6 +75,7 @@ Never say "I have no memories" without first searching broadly. Your memory may
|
|
|
75
75
|
- **create_task** \u2014 assign work to specialists with clear specs
|
|
76
76
|
- **update_task / close_task** \u2014 finalize reviews, mark work done
|
|
77
77
|
- **store_behavior** \u2014 record corrections as behavioral rules (p0/p1/p2)
|
|
78
|
+
- **update_identity** \u2014 rewrite any agent's identity when role/responsibilities change (exe/founder only)
|
|
78
79
|
- **get_identity** \u2014 read any agent's identity for coordination
|
|
79
80
|
- **send_message** \u2014 direct intercom to employees
|
|
80
81
|
|
|
@@ -438,6 +439,55 @@ You are the AI Product Lead \u2014 the competitive intelligence engine. You stud
|
|
|
438
439
|
- Every recommendation includes cost/quality/latency tradeoff analysis
|
|
439
440
|
- Separate experimental from production-ready \u2014 label clearly
|
|
440
441
|
- If you can't verify, say so explicitly: "Couldn't verify because X"
|
|
442
|
+
`,
|
|
443
|
+
"staff-code-reviewer": `---
|
|
444
|
+
role: staff-code-reviewer
|
|
445
|
+
title: Staff Code Reviewer & System Auditor
|
|
446
|
+
agent_id: bob
|
|
447
|
+
org_level: specialist
|
|
448
|
+
created_by: system
|
|
449
|
+
updated_at: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
450
|
+
---
|
|
451
|
+
## Identity
|
|
452
|
+
|
|
453
|
+
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.
|
|
454
|
+
|
|
455
|
+
## The 7 Audit Patterns (MANDATORY)
|
|
456
|
+
|
|
457
|
+
1. **"Works on dev, breaks on user install"** \u2014 scoped paths, npm resolution, deps
|
|
458
|
+
2. **"Two code paths, one untested"** \u2014 binary symlink vs /exe-call, verify BOTH
|
|
459
|
+
3. **"Case sensitivity kills non-technical users"** \u2014 normalize all user inputs
|
|
460
|
+
4. **"Hardcoded names in runtime logic"** \u2014 grep for employee names, must use roles
|
|
461
|
+
5. **"Installer doesn't self-heal"** \u2014 npm update must auto-fix stale hooks/paths
|
|
462
|
+
6. **"Data written but invisible"** \u2014 agent_id mismatch between writer and reader
|
|
463
|
+
7. **"Partial fixes miss inline refs"** \u2014 before/after grep count is mandatory
|
|
464
|
+
|
|
465
|
+
## Method
|
|
466
|
+
|
|
467
|
+
1. Read actual source code
|
|
468
|
+
2. Send to Codex MCP for sweep
|
|
469
|
+
3. Validate against ARCHITECTURE.md
|
|
470
|
+
4. Trace identity chain with CUSTOM-NAMED employee ("jarvis" as CTO)
|
|
471
|
+
5. Before/after grep count for every fix
|
|
472
|
+
6. Structured report: PASS/FAIL per item
|
|
473
|
+
|
|
474
|
+
## Tools
|
|
475
|
+
|
|
476
|
+
- **Codex MCP** \u2014 first tool for every review
|
|
477
|
+
- **recall_my_memory / ask_team_memory** \u2014 past audit findings
|
|
478
|
+
- **store_behavior** \u2014 record new patterns
|
|
479
|
+
- **update_task** \u2014 mark reviews done with structured findings
|
|
480
|
+
- **create_task** \u2014 assign fixes to the CTO
|
|
481
|
+
|
|
482
|
+
## Completion Workflow
|
|
483
|
+
|
|
484
|
+
1. Read the task brief and understand the audit scope
|
|
485
|
+
2. Run the audit using all 7 patterns
|
|
486
|
+
3. Write report to exe/output/ with file:line references
|
|
487
|
+
4. Fix findings yourself if possible
|
|
488
|
+
5. Call **update_task** with status "done" and finding count
|
|
489
|
+
6. Call **store_memory** with audit summary
|
|
490
|
+
7. Check for next task \u2014 auto-chain
|
|
441
491
|
`
|
|
442
492
|
};
|
|
443
493
|
function getTemplate(role) {
|
|
@@ -452,6 +502,7 @@ function getTemplateForTitle(title) {
|
|
|
452
502
|
if (t.includes("engineer") || t.includes("developer")) return IDENTITY_TEMPLATES["principal-engineer"];
|
|
453
503
|
if (t.includes("content") || t.includes("production")) return IDENTITY_TEMPLATES["content-specialist"];
|
|
454
504
|
if (t.includes("ai") || t.includes("product lead") || t.includes("specialist") && !t.includes("content")) return IDENTITY_TEMPLATES["ai-specialist"];
|
|
505
|
+
if (t.includes("review") || t.includes("audit") || t.includes("qa")) return IDENTITY_TEMPLATES["staff-code-reviewer"];
|
|
455
506
|
return null;
|
|
456
507
|
}
|
|
457
508
|
export {
|
package/dist/lib/identity.js
CHANGED
|
@@ -97,12 +97,12 @@ var DEFAULT_CONFIG = {
|
|
|
97
97
|
|
|
98
98
|
// src/lib/database.ts
|
|
99
99
|
import { createClient } from "@libsql/client";
|
|
100
|
-
var
|
|
100
|
+
var _resilientClient = null;
|
|
101
101
|
function getClient() {
|
|
102
|
-
if (!
|
|
102
|
+
if (!_resilientClient) {
|
|
103
103
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
104
104
|
}
|
|
105
|
-
return
|
|
105
|
+
return _resilientClient;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
// src/lib/identity.ts
|
package/dist/lib/messaging.js
CHANGED
|
@@ -19,19 +19,27 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
19
19
|
};
|
|
20
20
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
21
21
|
|
|
22
|
+
// src/lib/db-retry.ts
|
|
23
|
+
var init_db_retry = __esm({
|
|
24
|
+
"src/lib/db-retry.ts"() {
|
|
25
|
+
"use strict";
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
22
29
|
// src/lib/database.ts
|
|
23
30
|
import { createClient } from "@libsql/client";
|
|
24
31
|
function getClient() {
|
|
25
|
-
if (!
|
|
32
|
+
if (!_resilientClient) {
|
|
26
33
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
27
34
|
}
|
|
28
|
-
return
|
|
35
|
+
return _resilientClient;
|
|
29
36
|
}
|
|
30
|
-
var
|
|
37
|
+
var _resilientClient;
|
|
31
38
|
var init_database = __esm({
|
|
32
39
|
"src/lib/database.ts"() {
|
|
33
40
|
"use strict";
|
|
34
|
-
|
|
41
|
+
init_db_retry();
|
|
42
|
+
_resilientClient = null;
|
|
35
43
|
}
|
|
36
44
|
});
|
|
37
45
|
|
|
@@ -432,7 +440,7 @@ var init_config = __esm({
|
|
|
432
440
|
|
|
433
441
|
// src/lib/employees.ts
|
|
434
442
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
435
|
-
import { existsSync as existsSync4, symlinkSync, readlinkSync } from "fs";
|
|
443
|
+
import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync4 } from "fs";
|
|
436
444
|
import { execSync as execSync3 } from "child_process";
|
|
437
445
|
import path4 from "path";
|
|
438
446
|
var EMPLOYEES_PATH;
|
|
@@ -445,7 +453,7 @@ var init_employees = __esm({
|
|
|
445
453
|
});
|
|
446
454
|
|
|
447
455
|
// src/lib/license.ts
|
|
448
|
-
import { readFileSync as
|
|
456
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
|
|
449
457
|
import { randomUUID } from "crypto";
|
|
450
458
|
import path5 from "path";
|
|
451
459
|
import { jwtVerify, importSPKI } from "jose";
|
|
@@ -468,12 +476,12 @@ var init_license = __esm({
|
|
|
468
476
|
});
|
|
469
477
|
|
|
470
478
|
// src/lib/plan-limits.ts
|
|
471
|
-
import { readFileSync as
|
|
479
|
+
import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
|
|
472
480
|
import path6 from "path";
|
|
473
481
|
function getLicenseSync() {
|
|
474
482
|
try {
|
|
475
483
|
if (!existsSync6(CACHE_PATH2)) return freeLicense();
|
|
476
|
-
const raw = JSON.parse(
|
|
484
|
+
const raw = JSON.parse(readFileSync6(CACHE_PATH2, "utf8"));
|
|
477
485
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
478
486
|
const parts = raw.token.split(".");
|
|
479
487
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -512,7 +520,7 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
512
520
|
let count = 0;
|
|
513
521
|
try {
|
|
514
522
|
if (existsSync6(filePath)) {
|
|
515
|
-
const raw =
|
|
523
|
+
const raw = readFileSync6(filePath, "utf8");
|
|
516
524
|
const employees = JSON.parse(raw);
|
|
517
525
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
518
526
|
}
|
|
@@ -547,10 +555,46 @@ var init_plan_limits = __esm({
|
|
|
547
555
|
|
|
548
556
|
// src/lib/tmux-routing.ts
|
|
549
557
|
import { execFileSync as execFileSync2, execSync as execSync4 } from "child_process";
|
|
550
|
-
import { readFileSync as
|
|
558
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync7, appendFileSync } from "fs";
|
|
551
559
|
import path7 from "path";
|
|
552
560
|
import os4 from "os";
|
|
553
561
|
import { fileURLToPath } from "url";
|
|
562
|
+
import { unlinkSync } from "fs";
|
|
563
|
+
function spawnLockPath(sessionName) {
|
|
564
|
+
return path7.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
565
|
+
}
|
|
566
|
+
function isProcessAlive(pid) {
|
|
567
|
+
try {
|
|
568
|
+
process.kill(pid, 0);
|
|
569
|
+
return true;
|
|
570
|
+
} catch {
|
|
571
|
+
return false;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
function acquireSpawnLock(sessionName) {
|
|
575
|
+
if (!existsSync7(SPAWN_LOCK_DIR)) {
|
|
576
|
+
mkdirSync4(SPAWN_LOCK_DIR, { recursive: true });
|
|
577
|
+
}
|
|
578
|
+
const lockFile = spawnLockPath(sessionName);
|
|
579
|
+
if (existsSync7(lockFile)) {
|
|
580
|
+
try {
|
|
581
|
+
const lock = JSON.parse(readFileSync7(lockFile, "utf8"));
|
|
582
|
+
const age = Date.now() - lock.timestamp;
|
|
583
|
+
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
584
|
+
return false;
|
|
585
|
+
}
|
|
586
|
+
} catch {
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
writeFileSync4(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
590
|
+
return true;
|
|
591
|
+
}
|
|
592
|
+
function releaseSpawnLock(sessionName) {
|
|
593
|
+
try {
|
|
594
|
+
unlinkSync(spawnLockPath(sessionName));
|
|
595
|
+
} catch {
|
|
596
|
+
}
|
|
597
|
+
}
|
|
554
598
|
function resolveBehaviorsExporterScript() {
|
|
555
599
|
try {
|
|
556
600
|
const thisFile = fileURLToPath(import.meta.url);
|
|
@@ -596,7 +640,7 @@ function extractRootExe(name) {
|
|
|
596
640
|
}
|
|
597
641
|
function getParentExe(sessionKey) {
|
|
598
642
|
try {
|
|
599
|
-
const data = JSON.parse(
|
|
643
|
+
const data = JSON.parse(readFileSync7(path7.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
600
644
|
return data.parentExe || null;
|
|
601
645
|
} catch {
|
|
602
646
|
return null;
|
|
@@ -620,17 +664,17 @@ function isEmployeeAlive(sessionName) {
|
|
|
620
664
|
}
|
|
621
665
|
function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
|
|
622
666
|
const base = employeeSessionName(employeeName, exeSession);
|
|
623
|
-
if (!isAlive(base)) return 0;
|
|
667
|
+
if (!isAlive(base) && acquireSpawnLock(base)) return 0;
|
|
624
668
|
for (let i = 2; i <= maxInstances; i++) {
|
|
625
669
|
const candidate = employeeSessionName(employeeName, exeSession, i);
|
|
626
|
-
if (!isAlive(candidate)) return i;
|
|
670
|
+
if (!isAlive(candidate) && acquireSpawnLock(candidate)) return i;
|
|
627
671
|
}
|
|
628
672
|
return null;
|
|
629
673
|
}
|
|
630
674
|
function readDebounceState() {
|
|
631
675
|
try {
|
|
632
676
|
if (!existsSync7(DEBOUNCE_FILE)) return {};
|
|
633
|
-
return JSON.parse(
|
|
677
|
+
return JSON.parse(readFileSync7(DEBOUNCE_FILE, "utf8"));
|
|
634
678
|
} catch {
|
|
635
679
|
return {};
|
|
636
680
|
}
|
|
@@ -804,7 +848,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
804
848
|
const claudeJsonPath = path7.join(os4.homedir(), ".claude.json");
|
|
805
849
|
let claudeJson = {};
|
|
806
850
|
try {
|
|
807
|
-
claudeJson = JSON.parse(
|
|
851
|
+
claudeJson = JSON.parse(readFileSync7(claudeJsonPath, "utf8"));
|
|
808
852
|
} catch {
|
|
809
853
|
}
|
|
810
854
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -822,7 +866,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
822
866
|
const settingsPath = path7.join(projSettingsDir, "settings.json");
|
|
823
867
|
let settings = {};
|
|
824
868
|
try {
|
|
825
|
-
settings = JSON.parse(
|
|
869
|
+
settings = JSON.parse(readFileSync7(settingsPath, "utf8"));
|
|
826
870
|
} catch {
|
|
827
871
|
}
|
|
828
872
|
const perms = settings.permissions ?? {};
|
|
@@ -935,6 +979,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
935
979
|
command: spawnCommand
|
|
936
980
|
});
|
|
937
981
|
if (spawnResult.error) {
|
|
982
|
+
releaseSpawnLock(sessionName);
|
|
938
983
|
return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
|
|
939
984
|
}
|
|
940
985
|
transport.pipeLog(sessionName, logFile);
|
|
@@ -972,6 +1017,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
972
1017
|
}
|
|
973
1018
|
}
|
|
974
1019
|
if (!booted) {
|
|
1020
|
+
releaseSpawnLock(sessionName);
|
|
975
1021
|
return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
|
|
976
1022
|
}
|
|
977
1023
|
if (!useExeAgent) {
|
|
@@ -988,9 +1034,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
988
1034
|
pid: 0,
|
|
989
1035
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
990
1036
|
});
|
|
1037
|
+
releaseSpawnLock(sessionName);
|
|
991
1038
|
return { sessionName };
|
|
992
1039
|
}
|
|
993
|
-
var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
1040
|
+
var SPAWN_LOCK_DIR, SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
994
1041
|
var init_tmux_routing = __esm({
|
|
995
1042
|
"src/lib/tmux-routing.ts"() {
|
|
996
1043
|
"use strict";
|
|
@@ -1002,6 +1049,7 @@ var init_tmux_routing = __esm({
|
|
|
1002
1049
|
init_provider_table();
|
|
1003
1050
|
init_intercom_queue();
|
|
1004
1051
|
init_plan_limits();
|
|
1052
|
+
SPAWN_LOCK_DIR = path7.join(os4.homedir(), ".exe-os", "spawn-locks");
|
|
1005
1053
|
SESSION_CACHE = path7.join(os4.homedir(), ".exe-os", "session-cache");
|
|
1006
1054
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
1007
1055
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
package/dist/lib/reminders.js
CHANGED
|
@@ -3,12 +3,12 @@ import crypto from "crypto";
|
|
|
3
3
|
|
|
4
4
|
// src/lib/database.ts
|
|
5
5
|
import { createClient } from "@libsql/client";
|
|
6
|
-
var
|
|
6
|
+
var _resilientClient = null;
|
|
7
7
|
function getClient() {
|
|
8
|
-
if (!
|
|
8
|
+
if (!_resilientClient) {
|
|
9
9
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
10
10
|
}
|
|
11
|
-
return
|
|
11
|
+
return _resilientClient;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
// src/lib/reminders.ts
|
package/dist/lib/schedules.js
CHANGED
|
@@ -261,7 +261,7 @@ function listShards() {
|
|
|
261
261
|
}
|
|
262
262
|
async function ensureShardSchema(client) {
|
|
263
263
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
264
|
-
await client.execute("PRAGMA busy_timeout =
|
|
264
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
265
265
|
try {
|
|
266
266
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
267
267
|
} 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,20 +518,27 @@ 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 isInitialized() {
|
|
470
524
|
return _client !== null;
|
|
471
525
|
}
|
|
472
526
|
function getClient() {
|
|
527
|
+
if (!_resilientClient) {
|
|
528
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
529
|
+
}
|
|
530
|
+
return _resilientClient;
|
|
531
|
+
}
|
|
532
|
+
function getRawClient() {
|
|
473
533
|
if (!_client) {
|
|
474
534
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
475
535
|
}
|
|
476
536
|
return _client;
|
|
477
537
|
}
|
|
478
538
|
async function ensureSchema() {
|
|
479
|
-
const client =
|
|
539
|
+
const client = getRawClient();
|
|
480
540
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
481
|
-
await client.execute("PRAGMA busy_timeout =
|
|
541
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
482
542
|
try {
|
|
483
543
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
484
544
|
} catch {
|
|
@@ -3,12 +3,12 @@ import crypto2 from "crypto";
|
|
|
3
3
|
|
|
4
4
|
// src/lib/database.ts
|
|
5
5
|
import { createClient } from "@libsql/client";
|
|
6
|
-
var
|
|
6
|
+
var _resilientClient = null;
|
|
7
7
|
function getClient() {
|
|
8
|
-
if (!
|
|
8
|
+
if (!_resilientClient) {
|
|
9
9
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
10
10
|
}
|
|
11
|
-
return
|
|
11
|
+
return _resilientClient;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
// src/lib/behaviors.ts
|