@askexenow/exe-os 0.8.37 → 0.8.39
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/README.md +17 -8
- package/dist/bin/backfill-conversations.js +112 -70
- package/dist/bin/backfill-responses.js +53 -18
- package/dist/bin/backfill-vectors.js +43 -16
- package/dist/bin/cleanup-stale-review-tasks.js +38 -16
- package/dist/bin/cli.js +790 -468
- package/dist/bin/exe-agent.js +19 -4
- package/dist/bin/exe-assign.js +46 -13
- package/dist/bin/exe-boot.js +288 -129
- package/dist/bin/exe-call.js +20 -10
- package/dist/bin/exe-cloud.js +135 -30
- package/dist/bin/exe-dispatch.js +1 -1
- package/dist/bin/exe-doctor.js +38 -16
- package/dist/bin/exe-export-behaviors.js +43 -21
- package/dist/bin/exe-forget.js +39 -17
- package/dist/bin/exe-gateway.js +159 -50
- package/dist/bin/exe-heartbeat.js +53 -31
- package/dist/bin/exe-kill.js +40 -18
- package/dist/bin/exe-launch-agent.js +109 -36
- package/dist/bin/exe-link.js +196 -87
- package/dist/bin/exe-new-employee.js +56 -17
- package/dist/bin/exe-pending-messages.js +47 -25
- package/dist/bin/exe-pending-notifications.js +38 -16
- package/dist/bin/exe-pending-reviews.js +51 -29
- package/dist/bin/exe-rename.js +21 -7
- package/dist/bin/exe-review.js +41 -13
- package/dist/bin/exe-search.js +57 -21
- package/dist/bin/exe-session-cleanup.js +67 -31
- package/dist/bin/exe-settings.js +63 -2
- package/dist/bin/exe-status.js +35 -13
- package/dist/bin/exe-team.js +35 -13
- package/dist/bin/git-sweep.js +45 -17
- package/dist/bin/graph-backfill.js +38 -16
- package/dist/bin/graph-export.js +38 -16
- package/dist/bin/install.js +10 -1
- package/dist/bin/scan-tasks.js +47 -19
- package/dist/bin/setup.js +444 -259
- package/dist/bin/shard-migrate.js +38 -16
- package/dist/bin/wiki-sync.js +40 -17
- package/dist/gateway/index.js +113 -48
- package/dist/hooks/bug-report-worker.js +66 -39
- package/dist/hooks/commit-complete.js +45 -17
- package/dist/hooks/error-recall.js +60 -20
- package/dist/hooks/exe-heartbeat-hook.js +3 -2
- package/dist/hooks/ingest-worker.js +174 -45
- package/dist/hooks/ingest.js +74 -28
- package/dist/hooks/instructions-loaded.js +46 -17
- package/dist/hooks/notification.js +44 -15
- package/dist/hooks/post-compact.js +44 -15
- package/dist/hooks/pre-compact.js +42 -14
- package/dist/hooks/pre-tool-use.js +59 -22
- package/dist/hooks/prompt-ingest-worker.js +75 -14
- package/dist/hooks/prompt-submit.js +75 -32
- package/dist/hooks/response-ingest-worker.js +76 -15
- package/dist/hooks/session-end.js +54 -22
- package/dist/hooks/session-start.js +57 -20
- package/dist/hooks/stop.js +44 -15
- package/dist/hooks/subagent-stop.js +44 -15
- package/dist/hooks/summary-worker.js +339 -106
- package/dist/index.js +94 -23
- package/dist/lib/cloud-sync.js +191 -80
- package/dist/lib/config.js +4 -1
- package/dist/lib/consolidation.js +5 -4
- package/dist/lib/database.js +1 -0
- package/dist/lib/device-registry.js +2 -1
- package/dist/lib/embedder.js +9 -1
- package/dist/lib/employee-templates.js +5 -0
- package/dist/lib/employees.js +11 -6
- package/dist/lib/exe-daemon-client.js +6 -1
- package/dist/lib/exe-daemon.js +95 -36
- package/dist/lib/hybrid-search.js +57 -21
- package/dist/lib/identity-templates.js +16 -7
- package/dist/lib/identity.js +1 -1
- package/dist/lib/keychain.js +2 -1
- package/dist/lib/license.js +56 -6
- package/dist/lib/messaging.js +1 -1
- package/dist/lib/reminders.js +2 -2
- package/dist/lib/schedules.js +38 -16
- package/dist/lib/skill-learning.js +1 -1
- package/dist/lib/store.js +44 -16
- package/dist/lib/tasks.js +1 -1
- package/dist/lib/tmux-routing.js +1 -1
- package/dist/mcp/server.js +280 -155
- package/dist/mcp/tools/complete-reminder.js +1 -1
- package/dist/mcp/tools/create-task.js +14 -6
- package/dist/mcp/tools/deactivate-behavior.js +2 -2
- package/dist/mcp/tools/list-reminders.js +1 -1
- package/dist/mcp/tools/list-tasks.js +36 -28
- package/dist/mcp/tools/send-message.js +1 -1
- package/dist/mcp/tools/update-task.js +1 -1
- package/dist/runtime/index.js +42 -8
- package/dist/tui/App.js +220 -99
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -88,10 +88,19 @@ You talk to your COO. Your COO delegates. Each employee has:
|
|
|
88
88
|
### 1. Install
|
|
89
89
|
|
|
90
90
|
```bash
|
|
91
|
+
# macOS
|
|
92
|
+
xcode-select --install && brew install tmux
|
|
93
|
+
npm install -g @askexenow/exe-os
|
|
94
|
+
|
|
95
|
+
# Linux / WSL2
|
|
96
|
+
sudo apt install -y tmux git cmake g++ libsecret-1-dev curl
|
|
91
97
|
npm install -g @askexenow/exe-os
|
|
98
|
+
|
|
99
|
+
# Windows — must use WSL2 (native Windows not supported)
|
|
100
|
+
# Run `wsl --install` in PowerShell first, then follow Linux steps inside WSL2
|
|
92
101
|
```
|
|
93
102
|
|
|
94
|
-
Requires Node.js
|
|
103
|
+
Requires Node.js 22+ and tmux. See **[docs/install.md](docs/install.md)** for full platform-specific instructions and troubleshooting.
|
|
95
104
|
|
|
96
105
|
### 2. Run Setup
|
|
97
106
|
|
|
@@ -219,17 +228,17 @@ exe-os/
|
|
|
219
228
|
|
|
220
229
|
## Requirements
|
|
221
230
|
|
|
222
|
-
- **Node.js
|
|
231
|
+
- **Node.js 22+**
|
|
223
232
|
- **tmux** — for multi-agent session management
|
|
224
233
|
- **Claude Code** subscription (Mode 1) or API key (Mode 2)
|
|
225
234
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
brew install tmux node
|
|
235
|
+
| Platform | Quick Install |
|
|
236
|
+
|----------|-------------|
|
|
237
|
+
| macOS | `brew install tmux node` + Xcode CLI Tools |
|
|
238
|
+
| Linux | `apt install tmux git cmake g++ libsecret-1-dev` + Node.js 22+ |
|
|
239
|
+
| Windows | WSL2 required (`wsl --install`), then follow Linux steps |
|
|
229
240
|
|
|
230
|
-
|
|
231
|
-
apt install tmux nodejs
|
|
232
|
-
```
|
|
241
|
+
Full guide: **[docs/install.md](docs/install.md)**
|
|
233
242
|
|
|
234
243
|
---
|
|
235
244
|
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
3
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
5
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
6
|
-
}) : x)(function(x) {
|
|
7
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
8
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
9
|
-
});
|
|
10
4
|
var __esm = (fn, res) => function __init() {
|
|
11
5
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
12
6
|
};
|
|
@@ -16,15 +10,15 @@ var __export = (target, all) => {
|
|
|
16
10
|
};
|
|
17
11
|
|
|
18
12
|
// src/lib/config.ts
|
|
19
|
-
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
13
|
+
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, chmod as chmod2 } from "fs/promises";
|
|
20
14
|
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
21
15
|
import path2 from "path";
|
|
22
|
-
import
|
|
16
|
+
import os2 from "os";
|
|
23
17
|
function resolveDataDir() {
|
|
24
18
|
if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
|
|
25
19
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
26
|
-
const newDir = path2.join(
|
|
27
|
-
const legacyDir = path2.join(
|
|
20
|
+
const newDir = path2.join(os2.homedir(), ".exe-os");
|
|
21
|
+
const legacyDir = path2.join(os2.homedir(), ".exe-mem");
|
|
28
22
|
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
29
23
|
try {
|
|
30
24
|
renameSync(legacyDir, newDir);
|
|
@@ -111,7 +105,7 @@ async function loadConfig() {
|
|
|
111
105
|
normalizeAutoUpdate(migratedCfg);
|
|
112
106
|
const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
|
|
113
107
|
if (config.dbPath.startsWith("~")) {
|
|
114
|
-
config.dbPath = config.dbPath.replace(/^~/,
|
|
108
|
+
config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
|
|
115
109
|
}
|
|
116
110
|
return config;
|
|
117
111
|
} catch {
|
|
@@ -218,7 +212,7 @@ __export(shard_manager_exports, {
|
|
|
218
212
|
shardExists: () => shardExists
|
|
219
213
|
});
|
|
220
214
|
import path3 from "path";
|
|
221
|
-
import { existsSync as existsSync3, mkdirSync } from "fs";
|
|
215
|
+
import { existsSync as existsSync3, mkdirSync, readdirSync } from "fs";
|
|
222
216
|
import { createClient as createClient2 } from "@libsql/client";
|
|
223
217
|
function initShardManager(encryptionKey) {
|
|
224
218
|
_encryptionKey = encryptionKey;
|
|
@@ -257,7 +251,6 @@ function shardExists(projectName) {
|
|
|
257
251
|
}
|
|
258
252
|
function listShards() {
|
|
259
253
|
if (!existsSync3(SHARDS_DIR)) return [];
|
|
260
|
-
const { readdirSync } = __require("fs");
|
|
261
254
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
262
255
|
}
|
|
263
256
|
async function ensureShardSchema(client) {
|
|
@@ -545,6 +538,7 @@ async function ensureSchema() {
|
|
|
545
538
|
const client = getRawClient();
|
|
546
539
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
547
540
|
await client.execute("PRAGMA busy_timeout = 30000");
|
|
541
|
+
await client.execute("PRAGMA wal_autocheckpoint = 1000");
|
|
548
542
|
try {
|
|
549
543
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
550
544
|
} catch {
|
|
@@ -1338,11 +1332,12 @@ async function ensureSchema() {
|
|
|
1338
1332
|
import { readFile, writeFile, unlink, mkdir, chmod } from "fs/promises";
|
|
1339
1333
|
import { existsSync } from "fs";
|
|
1340
1334
|
import path from "path";
|
|
1335
|
+
import os from "os";
|
|
1341
1336
|
import crypto from "crypto";
|
|
1342
1337
|
var SERVICE = "exe-mem";
|
|
1343
1338
|
var ACCOUNT = "master-key";
|
|
1344
1339
|
function getKeyDir() {
|
|
1345
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(
|
|
1340
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(os.homedir(), ".exe-os");
|
|
1346
1341
|
}
|
|
1347
1342
|
function getKeyPath() {
|
|
1348
1343
|
return path.join(getKeyDir(), "master.key");
|
|
@@ -1379,6 +1374,30 @@ async function getMasterKey() {
|
|
|
1379
1374
|
|
|
1380
1375
|
// src/lib/store.ts
|
|
1381
1376
|
init_config();
|
|
1377
|
+
var INIT_MAX_RETRIES = 3;
|
|
1378
|
+
var INIT_RETRY_DELAY_MS = 1e3;
|
|
1379
|
+
function isBusyError2(err) {
|
|
1380
|
+
if (err instanceof Error) {
|
|
1381
|
+
const msg = err.message.toLowerCase();
|
|
1382
|
+
return msg.includes("sqlite_busy") || msg.includes("database is locked");
|
|
1383
|
+
}
|
|
1384
|
+
return false;
|
|
1385
|
+
}
|
|
1386
|
+
async function retryOnBusy2(fn, label) {
|
|
1387
|
+
for (let attempt = 0; attempt <= INIT_MAX_RETRIES; attempt++) {
|
|
1388
|
+
try {
|
|
1389
|
+
return await fn();
|
|
1390
|
+
} catch (err) {
|
|
1391
|
+
if (!isBusyError2(err) || attempt === INIT_MAX_RETRIES) throw err;
|
|
1392
|
+
process.stderr.write(
|
|
1393
|
+
`[store] SQLITE_BUSY during ${label}, retry ${attempt + 1}/${INIT_MAX_RETRIES}
|
|
1394
|
+
`
|
|
1395
|
+
);
|
|
1396
|
+
await new Promise((r) => setTimeout(r, INIT_RETRY_DELAY_MS * (attempt + 1)));
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
throw new Error("unreachable");
|
|
1400
|
+
}
|
|
1382
1401
|
var _pendingRecords = [];
|
|
1383
1402
|
var _batchSize = 20;
|
|
1384
1403
|
var _flushIntervalMs = 1e4;
|
|
@@ -1413,14 +1432,17 @@ async function initStore(options) {
|
|
|
1413
1432
|
dbPath,
|
|
1414
1433
|
encryptionKey: hexKey
|
|
1415
1434
|
});
|
|
1416
|
-
await ensureSchema();
|
|
1435
|
+
await retryOnBusy2(() => ensureSchema(), "ensureSchema");
|
|
1417
1436
|
try {
|
|
1418
1437
|
const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
|
|
1419
1438
|
initShardManager2(hexKey);
|
|
1420
1439
|
} catch {
|
|
1421
1440
|
}
|
|
1422
1441
|
const client = getClient();
|
|
1423
|
-
const vResult = await
|
|
1442
|
+
const vResult = await retryOnBusy2(
|
|
1443
|
+
() => client.execute("SELECT MAX(version) as max_v FROM memories"),
|
|
1444
|
+
"version-query"
|
|
1445
|
+
);
|
|
1424
1446
|
_nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
1425
1447
|
}
|
|
1426
1448
|
function classifyTier(record) {
|
|
@@ -1463,6 +1485,12 @@ async function writeMemory(record) {
|
|
|
1463
1485
|
supersedes_id: record.supersedes_id ?? null
|
|
1464
1486
|
};
|
|
1465
1487
|
_pendingRecords.push(dbRow);
|
|
1488
|
+
const MAX_PENDING = 1e3;
|
|
1489
|
+
if (_pendingRecords.length > MAX_PENDING) {
|
|
1490
|
+
const dropped = _pendingRecords.length - MAX_PENDING;
|
|
1491
|
+
_pendingRecords = _pendingRecords.slice(-MAX_PENDING);
|
|
1492
|
+
console.warn(`[store] Dropped ${dropped} oldest pending records (overflow)`);
|
|
1493
|
+
}
|
|
1466
1494
|
if (_flushTimer === null) {
|
|
1467
1495
|
_flushTimer = setInterval(() => {
|
|
1468
1496
|
void flushBatch();
|
|
@@ -1631,8 +1659,13 @@ var _buffer = "";
|
|
|
1631
1659
|
var _requestCount = 0;
|
|
1632
1660
|
var HEALTH_CHECK_INTERVAL = 100;
|
|
1633
1661
|
var _pending = /* @__PURE__ */ new Map();
|
|
1662
|
+
var MAX_BUFFER = 1e7;
|
|
1634
1663
|
function handleData(chunk) {
|
|
1635
1664
|
_buffer += chunk.toString();
|
|
1665
|
+
if (_buffer.length > MAX_BUFFER) {
|
|
1666
|
+
_buffer = "";
|
|
1667
|
+
return;
|
|
1668
|
+
}
|
|
1636
1669
|
let newlineIdx;
|
|
1637
1670
|
while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
|
|
1638
1671
|
const line = _buffer.slice(0, newlineIdx).trim();
|
|
@@ -1946,10 +1979,12 @@ function isMainModule(importMetaUrl) {
|
|
|
1946
1979
|
var TOOL_NAME = "backfill-conversation";
|
|
1947
1980
|
var MIN_MESSAGES = 3;
|
|
1948
1981
|
var MAX_SUMMARY_LENGTH = 4e3;
|
|
1982
|
+
var MAX_WALK_DEPTH = 10;
|
|
1949
1983
|
async function findJsonlFiles(sinceDate, projectFilter) {
|
|
1950
1984
|
const projectsDir = path5.join(homedir(), ".claude", "projects");
|
|
1951
1985
|
const files = [];
|
|
1952
|
-
async function walk(dir) {
|
|
1986
|
+
async function walk(dir, depth = 0) {
|
|
1987
|
+
if (depth > MAX_WALK_DEPTH) return;
|
|
1953
1988
|
let entries;
|
|
1954
1989
|
try {
|
|
1955
1990
|
entries = await readdir(dir, { withFileTypes: true });
|
|
@@ -1960,7 +1995,7 @@ async function findJsonlFiles(sinceDate, projectFilter) {
|
|
|
1960
1995
|
const full = path5.join(dir, entry.name);
|
|
1961
1996
|
if (entry.isDirectory()) {
|
|
1962
1997
|
if (entry.name === "subagents" || entry.name === "tool-results") continue;
|
|
1963
|
-
await walk(full);
|
|
1998
|
+
await walk(full, depth + 1);
|
|
1964
1999
|
} else if (entry.name.endsWith(".jsonl")) {
|
|
1965
2000
|
try {
|
|
1966
2001
|
const s = await stat(full);
|
|
@@ -2201,65 +2236,72 @@ async function backfillConversations(options) {
|
|
|
2201
2236
|
process.stderr.write(`[backfill-conversations] Found ${files.length} JSONL files to process
|
|
2202
2237
|
`);
|
|
2203
2238
|
process.env.EXE_EMBED_PRIORITY = "low";
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2239
|
+
const BATCH_SIZE = 50;
|
|
2240
|
+
const MAX_DEDUP_SIZE = 5e4;
|
|
2241
|
+
for (let batchStart = 0; batchStart < files.length; batchStart += BATCH_SIZE) {
|
|
2242
|
+
const batch = files.slice(batchStart, batchStart + BATCH_SIZE);
|
|
2243
|
+
for (const file of batch) {
|
|
2244
|
+
stats.filesScanned++;
|
|
2245
|
+
if (existingPaths.size < MAX_DEDUP_SIZE && existingPaths.has(file)) {
|
|
2246
|
+
stats.skippedDedup++;
|
|
2247
|
+
continue;
|
|
2248
|
+
}
|
|
2249
|
+
const conv = await parseConversation(file);
|
|
2250
|
+
if (conv.totalMessages < MIN_MESSAGES) {
|
|
2251
|
+
stats.skippedTooShort++;
|
|
2252
|
+
continue;
|
|
2253
|
+
}
|
|
2254
|
+
const summary = buildSummary(conv);
|
|
2255
|
+
if (options.dryRun) {
|
|
2256
|
+
process.stdout.write(`
|
|
2218
2257
|
\u2500\u2500\u2500 ${file} \u2500\u2500\u2500
|
|
2219
2258
|
`);
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2259
|
+
process.stdout.write(`Project: ${conv.projectName} | Messages: ${conv.totalMessages}`);
|
|
2260
|
+
process.stdout.write(` | Tools: ${Object.keys(conv.toolCounts).length}`);
|
|
2261
|
+
process.stdout.write(` | Files: ${conv.filesTouched.size}
|
|
2223
2262
|
`);
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2263
|
+
const firstPrompt = conv.userMessages[0];
|
|
2264
|
+
if (firstPrompt) {
|
|
2265
|
+
process.stdout.write(`First prompt: ${firstPrompt.slice(0, 120)}
|
|
2227
2266
|
`);
|
|
2267
|
+
}
|
|
2268
|
+
stats.conversationsStored++;
|
|
2269
|
+
continue;
|
|
2228
2270
|
}
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
} catch {
|
|
2238
|
-
stats.embedFailed++;
|
|
2271
|
+
let vector = null;
|
|
2272
|
+
if (daemonConnected) {
|
|
2273
|
+
try {
|
|
2274
|
+
vector = await embedViaClient(summary, "low");
|
|
2275
|
+
if (!vector) stats.embedFailed++;
|
|
2276
|
+
} catch {
|
|
2277
|
+
stats.embedFailed++;
|
|
2278
|
+
}
|
|
2239
2279
|
}
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2280
|
+
await writeMemory({
|
|
2281
|
+
id: crypto2.randomUUID(),
|
|
2282
|
+
agent_id: conv.agentId,
|
|
2283
|
+
agent_role: conv.agentId === "exe" ? "COO" : "specialist",
|
|
2284
|
+
session_id: conv.sessionId,
|
|
2285
|
+
timestamp: conv.startTime ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
2286
|
+
tool_name: TOOL_NAME,
|
|
2287
|
+
project_name: conv.projectName,
|
|
2288
|
+
has_error: conv.errorCount > 0,
|
|
2289
|
+
raw_text: summary,
|
|
2290
|
+
vector,
|
|
2291
|
+
source_path: file,
|
|
2292
|
+
source_type: "conversation"
|
|
2293
|
+
});
|
|
2294
|
+
if (existingPaths.size < MAX_DEDUP_SIZE) {
|
|
2295
|
+
existingPaths.add(file);
|
|
2296
|
+
}
|
|
2297
|
+
stats.conversationsStored++;
|
|
2298
|
+
if (stats.filesScanned % 50 === 0) {
|
|
2299
|
+
process.stderr.write(
|
|
2300
|
+
`[backfill-conversations] Progress: ${stats.filesScanned}/${files.length} files, ${stats.conversationsStored} stored
|
|
2260
2301
|
`
|
|
2261
|
-
|
|
2262
|
-
|
|
2302
|
+
);
|
|
2303
|
+
await flushBatch();
|
|
2304
|
+
}
|
|
2263
2305
|
}
|
|
2264
2306
|
}
|
|
2265
2307
|
if (!options.dryRun) {
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
3
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
5
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
6
|
-
}) : x)(function(x) {
|
|
7
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
8
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
9
|
-
});
|
|
10
4
|
var __esm = (fn, res) => function __init() {
|
|
11
5
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
12
6
|
};
|
|
@@ -16,15 +10,15 @@ var __export = (target, all) => {
|
|
|
16
10
|
};
|
|
17
11
|
|
|
18
12
|
// src/lib/config.ts
|
|
19
|
-
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
13
|
+
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, chmod as chmod2 } from "fs/promises";
|
|
20
14
|
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
21
15
|
import path2 from "path";
|
|
22
|
-
import
|
|
16
|
+
import os2 from "os";
|
|
23
17
|
function resolveDataDir() {
|
|
24
18
|
if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
|
|
25
19
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
26
|
-
const newDir = path2.join(
|
|
27
|
-
const legacyDir = path2.join(
|
|
20
|
+
const newDir = path2.join(os2.homedir(), ".exe-os");
|
|
21
|
+
const legacyDir = path2.join(os2.homedir(), ".exe-mem");
|
|
28
22
|
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
29
23
|
try {
|
|
30
24
|
renameSync(legacyDir, newDir);
|
|
@@ -111,7 +105,7 @@ async function loadConfig() {
|
|
|
111
105
|
normalizeAutoUpdate(migratedCfg);
|
|
112
106
|
const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
|
|
113
107
|
if (config.dbPath.startsWith("~")) {
|
|
114
|
-
config.dbPath = config.dbPath.replace(/^~/,
|
|
108
|
+
config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
|
|
115
109
|
}
|
|
116
110
|
return config;
|
|
117
111
|
} catch {
|
|
@@ -218,7 +212,7 @@ __export(shard_manager_exports, {
|
|
|
218
212
|
shardExists: () => shardExists
|
|
219
213
|
});
|
|
220
214
|
import path3 from "path";
|
|
221
|
-
import { existsSync as existsSync3, mkdirSync } from "fs";
|
|
215
|
+
import { existsSync as existsSync3, mkdirSync, readdirSync } from "fs";
|
|
222
216
|
import { createClient as createClient2 } from "@libsql/client";
|
|
223
217
|
function initShardManager(encryptionKey) {
|
|
224
218
|
_encryptionKey = encryptionKey;
|
|
@@ -257,7 +251,6 @@ function shardExists(projectName) {
|
|
|
257
251
|
}
|
|
258
252
|
function listShards() {
|
|
259
253
|
if (!existsSync3(SHARDS_DIR)) return [];
|
|
260
|
-
const { readdirSync } = __require("fs");
|
|
261
254
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
262
255
|
}
|
|
263
256
|
async function ensureShardSchema(client) {
|
|
@@ -544,6 +537,7 @@ async function ensureSchema() {
|
|
|
544
537
|
const client = getRawClient();
|
|
545
538
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
546
539
|
await client.execute("PRAGMA busy_timeout = 30000");
|
|
540
|
+
await client.execute("PRAGMA wal_autocheckpoint = 1000");
|
|
547
541
|
try {
|
|
548
542
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
549
543
|
} catch {
|
|
@@ -1337,11 +1331,12 @@ async function ensureSchema() {
|
|
|
1337
1331
|
import { readFile, writeFile, unlink, mkdir, chmod } from "fs/promises";
|
|
1338
1332
|
import { existsSync } from "fs";
|
|
1339
1333
|
import path from "path";
|
|
1334
|
+
import os from "os";
|
|
1340
1335
|
import crypto from "crypto";
|
|
1341
1336
|
var SERVICE = "exe-mem";
|
|
1342
1337
|
var ACCOUNT = "master-key";
|
|
1343
1338
|
function getKeyDir() {
|
|
1344
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(
|
|
1339
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(os.homedir(), ".exe-os");
|
|
1345
1340
|
}
|
|
1346
1341
|
function getKeyPath() {
|
|
1347
1342
|
return path.join(getKeyDir(), "master.key");
|
|
@@ -1378,6 +1373,30 @@ async function getMasterKey() {
|
|
|
1378
1373
|
|
|
1379
1374
|
// src/lib/store.ts
|
|
1380
1375
|
init_config();
|
|
1376
|
+
var INIT_MAX_RETRIES = 3;
|
|
1377
|
+
var INIT_RETRY_DELAY_MS = 1e3;
|
|
1378
|
+
function isBusyError2(err) {
|
|
1379
|
+
if (err instanceof Error) {
|
|
1380
|
+
const msg = err.message.toLowerCase();
|
|
1381
|
+
return msg.includes("sqlite_busy") || msg.includes("database is locked");
|
|
1382
|
+
}
|
|
1383
|
+
return false;
|
|
1384
|
+
}
|
|
1385
|
+
async function retryOnBusy2(fn, label) {
|
|
1386
|
+
for (let attempt = 0; attempt <= INIT_MAX_RETRIES; attempt++) {
|
|
1387
|
+
try {
|
|
1388
|
+
return await fn();
|
|
1389
|
+
} catch (err) {
|
|
1390
|
+
if (!isBusyError2(err) || attempt === INIT_MAX_RETRIES) throw err;
|
|
1391
|
+
process.stderr.write(
|
|
1392
|
+
`[store] SQLITE_BUSY during ${label}, retry ${attempt + 1}/${INIT_MAX_RETRIES}
|
|
1393
|
+
`
|
|
1394
|
+
);
|
|
1395
|
+
await new Promise((r) => setTimeout(r, INIT_RETRY_DELAY_MS * (attempt + 1)));
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
throw new Error("unreachable");
|
|
1399
|
+
}
|
|
1381
1400
|
var _pendingRecords = [];
|
|
1382
1401
|
var _batchSize = 20;
|
|
1383
1402
|
var _flushIntervalMs = 1e4;
|
|
@@ -1412,14 +1431,17 @@ async function initStore(options) {
|
|
|
1412
1431
|
dbPath,
|
|
1413
1432
|
encryptionKey: hexKey
|
|
1414
1433
|
});
|
|
1415
|
-
await ensureSchema();
|
|
1434
|
+
await retryOnBusy2(() => ensureSchema(), "ensureSchema");
|
|
1416
1435
|
try {
|
|
1417
1436
|
const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
|
|
1418
1437
|
initShardManager2(hexKey);
|
|
1419
1438
|
} catch {
|
|
1420
1439
|
}
|
|
1421
1440
|
const client = getClient();
|
|
1422
|
-
const vResult = await
|
|
1441
|
+
const vResult = await retryOnBusy2(
|
|
1442
|
+
() => client.execute("SELECT MAX(version) as max_v FROM memories"),
|
|
1443
|
+
"version-query"
|
|
1444
|
+
);
|
|
1423
1445
|
_nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
1424
1446
|
}
|
|
1425
1447
|
function classifyTier(record) {
|
|
@@ -1462,6 +1484,12 @@ async function writeMemory(record) {
|
|
|
1462
1484
|
supersedes_id: record.supersedes_id ?? null
|
|
1463
1485
|
};
|
|
1464
1486
|
_pendingRecords.push(dbRow);
|
|
1487
|
+
const MAX_PENDING = 1e3;
|
|
1488
|
+
if (_pendingRecords.length > MAX_PENDING) {
|
|
1489
|
+
const dropped = _pendingRecords.length - MAX_PENDING;
|
|
1490
|
+
_pendingRecords = _pendingRecords.slice(-MAX_PENDING);
|
|
1491
|
+
console.warn(`[store] Dropped ${dropped} oldest pending records (overflow)`);
|
|
1492
|
+
}
|
|
1465
1493
|
if (_flushTimer === null) {
|
|
1466
1494
|
_flushTimer = setInterval(() => {
|
|
1467
1495
|
void flushBatch();
|
|
@@ -1630,8 +1658,13 @@ var _buffer = "";
|
|
|
1630
1658
|
var _requestCount = 0;
|
|
1631
1659
|
var HEALTH_CHECK_INTERVAL = 100;
|
|
1632
1660
|
var _pending = /* @__PURE__ */ new Map();
|
|
1661
|
+
var MAX_BUFFER = 1e7;
|
|
1633
1662
|
function handleData(chunk) {
|
|
1634
1663
|
_buffer += chunk.toString();
|
|
1664
|
+
if (_buffer.length > MAX_BUFFER) {
|
|
1665
|
+
_buffer = "";
|
|
1666
|
+
return;
|
|
1667
|
+
}
|
|
1635
1668
|
let newlineIdx;
|
|
1636
1669
|
while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
|
|
1637
1670
|
const line = _buffer.slice(0, newlineIdx).trim();
|
|
@@ -1946,11 +1979,13 @@ var MIN_LENGTH = 100;
|
|
|
1946
1979
|
var MAX_LENGTH = 5e3;
|
|
1947
1980
|
var SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
1948
1981
|
process.env.EXE_EMBED_PRIORITY = "low";
|
|
1982
|
+
var MAX_WALK_DEPTH = 10;
|
|
1949
1983
|
async function findRecentJsonlFiles() {
|
|
1950
1984
|
const projectsDir = path5.join(homedir(), ".claude", "projects");
|
|
1951
1985
|
const cutoff = Date.now() - SEVEN_DAYS_MS;
|
|
1952
1986
|
const files = [];
|
|
1953
|
-
async function walk(dir) {
|
|
1987
|
+
async function walk(dir, depth = 0) {
|
|
1988
|
+
if (depth > MAX_WALK_DEPTH) return;
|
|
1954
1989
|
let entries;
|
|
1955
1990
|
try {
|
|
1956
1991
|
entries = await readdir(dir, { withFileTypes: true });
|
|
@@ -1960,7 +1995,7 @@ async function findRecentJsonlFiles() {
|
|
|
1960
1995
|
for (const entry of entries) {
|
|
1961
1996
|
const full = path5.join(dir, entry.name);
|
|
1962
1997
|
if (entry.isDirectory()) {
|
|
1963
|
-
await walk(full);
|
|
1998
|
+
await walk(full, depth + 1);
|
|
1964
1999
|
} else if (entry.name.endsWith(".jsonl")) {
|
|
1965
2000
|
try {
|
|
1966
2001
|
const s = await stat(full);
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
3
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
5
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
6
|
-
}) : x)(function(x) {
|
|
7
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
8
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
9
|
-
});
|
|
10
4
|
var __esm = (fn, res) => function __init() {
|
|
11
5
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
12
6
|
};
|
|
@@ -16,15 +10,15 @@ var __export = (target, all) => {
|
|
|
16
10
|
};
|
|
17
11
|
|
|
18
12
|
// src/lib/config.ts
|
|
19
|
-
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
13
|
+
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, chmod as chmod2 } from "fs/promises";
|
|
20
14
|
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
21
15
|
import path2 from "path";
|
|
22
|
-
import
|
|
16
|
+
import os2 from "os";
|
|
23
17
|
function resolveDataDir() {
|
|
24
18
|
if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
|
|
25
19
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
26
|
-
const newDir = path2.join(
|
|
27
|
-
const legacyDir = path2.join(
|
|
20
|
+
const newDir = path2.join(os2.homedir(), ".exe-os");
|
|
21
|
+
const legacyDir = path2.join(os2.homedir(), ".exe-mem");
|
|
28
22
|
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
29
23
|
try {
|
|
30
24
|
renameSync(legacyDir, newDir);
|
|
@@ -111,7 +105,7 @@ async function loadConfig() {
|
|
|
111
105
|
normalizeAutoUpdate(migratedCfg);
|
|
112
106
|
const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
|
|
113
107
|
if (config.dbPath.startsWith("~")) {
|
|
114
|
-
config.dbPath = config.dbPath.replace(/^~/,
|
|
108
|
+
config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
|
|
115
109
|
}
|
|
116
110
|
return config;
|
|
117
111
|
} catch {
|
|
@@ -218,7 +212,7 @@ __export(shard_manager_exports, {
|
|
|
218
212
|
shardExists: () => shardExists
|
|
219
213
|
});
|
|
220
214
|
import path3 from "path";
|
|
221
|
-
import { existsSync as existsSync3, mkdirSync } from "fs";
|
|
215
|
+
import { existsSync as existsSync3, mkdirSync, readdirSync } from "fs";
|
|
222
216
|
import { createClient as createClient2 } from "@libsql/client";
|
|
223
217
|
function initShardManager(encryptionKey) {
|
|
224
218
|
_encryptionKey = encryptionKey;
|
|
@@ -257,7 +251,6 @@ function shardExists(projectName) {
|
|
|
257
251
|
}
|
|
258
252
|
function listShards() {
|
|
259
253
|
if (!existsSync3(SHARDS_DIR)) return [];
|
|
260
|
-
const { readdirSync } = __require("fs");
|
|
261
254
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
262
255
|
}
|
|
263
256
|
async function ensureShardSchema(client) {
|
|
@@ -533,6 +526,7 @@ async function ensureSchema() {
|
|
|
533
526
|
const client = getRawClient();
|
|
534
527
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
535
528
|
await client.execute("PRAGMA busy_timeout = 30000");
|
|
529
|
+
await client.execute("PRAGMA wal_autocheckpoint = 1000");
|
|
536
530
|
try {
|
|
537
531
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
538
532
|
} catch {
|
|
@@ -1326,11 +1320,12 @@ async function ensureSchema() {
|
|
|
1326
1320
|
import { readFile, writeFile, unlink, mkdir, chmod } from "fs/promises";
|
|
1327
1321
|
import { existsSync } from "fs";
|
|
1328
1322
|
import path from "path";
|
|
1323
|
+
import os from "os";
|
|
1329
1324
|
import crypto from "crypto";
|
|
1330
1325
|
var SERVICE = "exe-mem";
|
|
1331
1326
|
var ACCOUNT = "master-key";
|
|
1332
1327
|
function getKeyDir() {
|
|
1333
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(
|
|
1328
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(os.homedir(), ".exe-os");
|
|
1334
1329
|
}
|
|
1335
1330
|
function getKeyPath() {
|
|
1336
1331
|
return path.join(getKeyDir(), "master.key");
|
|
@@ -1367,6 +1362,30 @@ async function getMasterKey() {
|
|
|
1367
1362
|
|
|
1368
1363
|
// src/lib/store.ts
|
|
1369
1364
|
init_config();
|
|
1365
|
+
var INIT_MAX_RETRIES = 3;
|
|
1366
|
+
var INIT_RETRY_DELAY_MS = 1e3;
|
|
1367
|
+
function isBusyError2(err) {
|
|
1368
|
+
if (err instanceof Error) {
|
|
1369
|
+
const msg = err.message.toLowerCase();
|
|
1370
|
+
return msg.includes("sqlite_busy") || msg.includes("database is locked");
|
|
1371
|
+
}
|
|
1372
|
+
return false;
|
|
1373
|
+
}
|
|
1374
|
+
async function retryOnBusy2(fn, label) {
|
|
1375
|
+
for (let attempt = 0; attempt <= INIT_MAX_RETRIES; attempt++) {
|
|
1376
|
+
try {
|
|
1377
|
+
return await fn();
|
|
1378
|
+
} catch (err) {
|
|
1379
|
+
if (!isBusyError2(err) || attempt === INIT_MAX_RETRIES) throw err;
|
|
1380
|
+
process.stderr.write(
|
|
1381
|
+
`[store] SQLITE_BUSY during ${label}, retry ${attempt + 1}/${INIT_MAX_RETRIES}
|
|
1382
|
+
`
|
|
1383
|
+
);
|
|
1384
|
+
await new Promise((r) => setTimeout(r, INIT_RETRY_DELAY_MS * (attempt + 1)));
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
throw new Error("unreachable");
|
|
1388
|
+
}
|
|
1370
1389
|
var _pendingRecords = [];
|
|
1371
1390
|
var _batchSize = 20;
|
|
1372
1391
|
var _flushIntervalMs = 1e4;
|
|
@@ -1401,14 +1420,17 @@ async function initStore(options) {
|
|
|
1401
1420
|
dbPath,
|
|
1402
1421
|
encryptionKey: hexKey
|
|
1403
1422
|
});
|
|
1404
|
-
await ensureSchema();
|
|
1423
|
+
await retryOnBusy2(() => ensureSchema(), "ensureSchema");
|
|
1405
1424
|
try {
|
|
1406
1425
|
const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
|
|
1407
1426
|
initShardManager2(hexKey);
|
|
1408
1427
|
} catch {
|
|
1409
1428
|
}
|
|
1410
1429
|
const client = getClient();
|
|
1411
|
-
const vResult = await
|
|
1430
|
+
const vResult = await retryOnBusy2(
|
|
1431
|
+
() => client.execute("SELECT MAX(version) as max_v FROM memories"),
|
|
1432
|
+
"version-query"
|
|
1433
|
+
);
|
|
1412
1434
|
_nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
1413
1435
|
}
|
|
1414
1436
|
function vectorToBlob(vector) {
|
|
@@ -1436,8 +1458,13 @@ var _buffer = "";
|
|
|
1436
1458
|
var _requestCount = 0;
|
|
1437
1459
|
var HEALTH_CHECK_INTERVAL = 100;
|
|
1438
1460
|
var _pending = /* @__PURE__ */ new Map();
|
|
1461
|
+
var MAX_BUFFER = 1e7;
|
|
1439
1462
|
function handleData(chunk) {
|
|
1440
1463
|
_buffer += chunk.toString();
|
|
1464
|
+
if (_buffer.length > MAX_BUFFER) {
|
|
1465
|
+
_buffer = "";
|
|
1466
|
+
return;
|
|
1467
|
+
}
|
|
1441
1468
|
let newlineIdx;
|
|
1442
1469
|
while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
|
|
1443
1470
|
const line = _buffer.slice(0, newlineIdx).trim();
|