@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
|
@@ -79,6 +79,17 @@ var init_db_retry = __esm({
|
|
|
79
79
|
});
|
|
80
80
|
|
|
81
81
|
// src/lib/database.ts
|
|
82
|
+
var database_exports = {};
|
|
83
|
+
__export(database_exports, {
|
|
84
|
+
disposeDatabase: () => disposeDatabase,
|
|
85
|
+
disposeTurso: () => disposeTurso,
|
|
86
|
+
ensureSchema: () => ensureSchema,
|
|
87
|
+
getClient: () => getClient,
|
|
88
|
+
getRawClient: () => getRawClient,
|
|
89
|
+
initDatabase: () => initDatabase,
|
|
90
|
+
initTurso: () => initTurso,
|
|
91
|
+
isInitialized: () => isInitialized
|
|
92
|
+
});
|
|
82
93
|
import { createClient } from "@libsql/client";
|
|
83
94
|
async function initDatabase(config) {
|
|
84
95
|
if (_client) {
|
|
@@ -114,6 +125,7 @@ async function ensureSchema() {
|
|
|
114
125
|
const client = getRawClient();
|
|
115
126
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
116
127
|
await client.execute("PRAGMA busy_timeout = 30000");
|
|
128
|
+
await client.execute("PRAGMA wal_autocheckpoint = 1000");
|
|
117
129
|
try {
|
|
118
130
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
119
131
|
} catch {
|
|
@@ -902,7 +914,14 @@ async function ensureSchema() {
|
|
|
902
914
|
}
|
|
903
915
|
}
|
|
904
916
|
}
|
|
905
|
-
|
|
917
|
+
async function disposeDatabase() {
|
|
918
|
+
if (_client) {
|
|
919
|
+
_client.close();
|
|
920
|
+
_client = null;
|
|
921
|
+
_resilientClient = null;
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
var _client, _resilientClient, initTurso, disposeTurso;
|
|
906
925
|
var init_database = __esm({
|
|
907
926
|
"src/lib/database.ts"() {
|
|
908
927
|
"use strict";
|
|
@@ -910,6 +929,7 @@ var init_database = __esm({
|
|
|
910
929
|
_client = null;
|
|
911
930
|
_resilientClient = null;
|
|
912
931
|
initTurso = initDatabase;
|
|
932
|
+
disposeTurso = disposeDatabase;
|
|
913
933
|
}
|
|
914
934
|
});
|
|
915
935
|
|
|
@@ -925,9 +945,10 @@ __export(keychain_exports, {
|
|
|
925
945
|
import { readFile, writeFile, unlink, mkdir, chmod } from "fs/promises";
|
|
926
946
|
import { existsSync } from "fs";
|
|
927
947
|
import path from "path";
|
|
948
|
+
import os from "os";
|
|
928
949
|
import crypto from "crypto";
|
|
929
950
|
function getKeyDir() {
|
|
930
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(
|
|
951
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(os.homedir(), ".exe-os");
|
|
931
952
|
}
|
|
932
953
|
function getKeyPath() {
|
|
933
954
|
return path.join(getKeyDir(), "master.key");
|
|
@@ -1075,15 +1096,15 @@ __export(config_exports, {
|
|
|
1075
1096
|
migrateConfig: () => migrateConfig,
|
|
1076
1097
|
saveConfig: () => saveConfig
|
|
1077
1098
|
});
|
|
1078
|
-
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
1099
|
+
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, chmod as chmod2 } from "fs/promises";
|
|
1079
1100
|
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
1080
1101
|
import path2 from "path";
|
|
1081
|
-
import
|
|
1102
|
+
import os2 from "os";
|
|
1082
1103
|
function resolveDataDir() {
|
|
1083
1104
|
if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
|
|
1084
1105
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
1085
|
-
const newDir = path2.join(
|
|
1086
|
-
const legacyDir = path2.join(
|
|
1106
|
+
const newDir = path2.join(os2.homedir(), ".exe-os");
|
|
1107
|
+
const legacyDir = path2.join(os2.homedir(), ".exe-mem");
|
|
1087
1108
|
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
1088
1109
|
try {
|
|
1089
1110
|
renameSync(legacyDir, newDir);
|
|
@@ -1170,7 +1191,7 @@ async function loadConfig() {
|
|
|
1170
1191
|
normalizeAutoUpdate(migratedCfg);
|
|
1171
1192
|
const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
|
|
1172
1193
|
if (config.dbPath.startsWith("~")) {
|
|
1173
|
-
config.dbPath = config.dbPath.replace(/^~/,
|
|
1194
|
+
config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
|
|
1174
1195
|
}
|
|
1175
1196
|
return config;
|
|
1176
1197
|
} catch {
|
|
@@ -1201,6 +1222,9 @@ async function saveConfig(config) {
|
|
|
1201
1222
|
await mkdir2(dir, { recursive: true });
|
|
1202
1223
|
const configPath = path2.join(dir, "config.json");
|
|
1203
1224
|
await writeFile2(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
1225
|
+
if (config.cloud?.apiKey) {
|
|
1226
|
+
await chmod2(configPath, 384);
|
|
1227
|
+
}
|
|
1204
1228
|
}
|
|
1205
1229
|
async function loadConfigFrom(configPath) {
|
|
1206
1230
|
const raw = await readFile2(configPath, "utf-8");
|
|
@@ -1316,7 +1340,7 @@ __export(shard_manager_exports, {
|
|
|
1316
1340
|
shardExists: () => shardExists
|
|
1317
1341
|
});
|
|
1318
1342
|
import path3 from "path";
|
|
1319
|
-
import { existsSync as existsSync3, mkdirSync } from "fs";
|
|
1343
|
+
import { existsSync as existsSync3, mkdirSync, readdirSync } from "fs";
|
|
1320
1344
|
import { createClient as createClient2 } from "@libsql/client";
|
|
1321
1345
|
function initShardManager(encryptionKey) {
|
|
1322
1346
|
_encryptionKey = encryptionKey;
|
|
@@ -1355,8 +1379,7 @@ function shardExists(projectName) {
|
|
|
1355
1379
|
}
|
|
1356
1380
|
function listShards() {
|
|
1357
1381
|
if (!existsSync3(SHARDS_DIR)) return [];
|
|
1358
|
-
|
|
1359
|
-
return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
1382
|
+
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
1360
1383
|
}
|
|
1361
1384
|
async function ensureShardSchema(client) {
|
|
1362
1385
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
@@ -1564,15 +1587,20 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
|
1564
1587
|
await mkdir3(path5.dirname(employeesPath), { recursive: true });
|
|
1565
1588
|
await writeFile3(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
1566
1589
|
}
|
|
1590
|
+
function findExeBin() {
|
|
1591
|
+
try {
|
|
1592
|
+
return execSync(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
|
|
1593
|
+
} catch {
|
|
1594
|
+
return null;
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1567
1597
|
function registerBinSymlinks(name) {
|
|
1568
1598
|
const created = [];
|
|
1569
1599
|
const skipped = [];
|
|
1570
1600
|
const errors = [];
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
} catch {
|
|
1575
|
-
errors.push("Could not find 'exe' in PATH");
|
|
1601
|
+
const exeBinPath = findExeBin();
|
|
1602
|
+
if (!exeBinPath) {
|
|
1603
|
+
errors.push("Could not find 'exe-os' in PATH");
|
|
1576
1604
|
return { created, skipped, errors };
|
|
1577
1605
|
}
|
|
1578
1606
|
const binDir = path5.dirname(exeBinPath);
|
|
@@ -1613,6 +1641,14 @@ import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync6
|
|
|
1613
1641
|
import { randomUUID } from "crypto";
|
|
1614
1642
|
import path6 from "path";
|
|
1615
1643
|
import { jwtVerify, importSPKI } from "jose";
|
|
1644
|
+
async function fetchRetry(url, init) {
|
|
1645
|
+
try {
|
|
1646
|
+
return await fetch(url, init);
|
|
1647
|
+
} catch {
|
|
1648
|
+
await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
|
|
1649
|
+
return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1616
1652
|
function loadDeviceId() {
|
|
1617
1653
|
const deviceJsonPath = path6.join(EXE_AI_DIR, "device.json");
|
|
1618
1654
|
try {
|
|
@@ -1683,7 +1719,7 @@ function cacheResponse(token) {
|
|
|
1683
1719
|
async function validateLicense(apiKey, deviceId) {
|
|
1684
1720
|
const did = deviceId ?? loadDeviceId();
|
|
1685
1721
|
try {
|
|
1686
|
-
const res = await
|
|
1722
|
+
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
1687
1723
|
method: "POST",
|
|
1688
1724
|
headers: { "Content-Type": "application/json" },
|
|
1689
1725
|
body: JSON.stringify({ apiKey, deviceId: did }),
|
|
@@ -1718,14 +1754,23 @@ async function validateLicense(apiKey, deviceId) {
|
|
|
1718
1754
|
} catch {
|
|
1719
1755
|
const cached = await getCachedLicense();
|
|
1720
1756
|
if (cached) return cached;
|
|
1721
|
-
return FREE_LICENSE;
|
|
1757
|
+
return { ...FREE_LICENSE, valid: false, error: "offline" };
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
function getCacheAgeMs() {
|
|
1761
|
+
try {
|
|
1762
|
+
const { statSync: statSync2 } = __require("fs");
|
|
1763
|
+
const s = statSync2(CACHE_PATH);
|
|
1764
|
+
return Date.now() - s.mtimeMs;
|
|
1765
|
+
} catch {
|
|
1766
|
+
return Infinity;
|
|
1722
1767
|
}
|
|
1723
1768
|
}
|
|
1724
1769
|
async function checkLicense() {
|
|
1725
1770
|
const key = loadLicense();
|
|
1726
1771
|
if (!key) return FREE_LICENSE;
|
|
1727
1772
|
const cached = await getCachedLicense();
|
|
1728
|
-
if (cached) return cached;
|
|
1773
|
+
if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
|
|
1729
1774
|
const deviceId = loadDeviceId();
|
|
1730
1775
|
return validateLicense(key, deviceId);
|
|
1731
1776
|
}
|
|
@@ -1739,7 +1784,7 @@ function isFeatureAllowed(license, feature) {
|
|
|
1739
1784
|
return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
|
|
1740
1785
|
}
|
|
1741
1786
|
}
|
|
1742
|
-
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE;
|
|
1787
|
+
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, CACHE_MAX_AGE_MS;
|
|
1743
1788
|
var init_license = __esm({
|
|
1744
1789
|
"src/lib/license.ts"() {
|
|
1745
1790
|
"use strict";
|
|
@@ -1748,6 +1793,7 @@ var init_license = __esm({
|
|
|
1748
1793
|
CACHE_PATH = path6.join(EXE_AI_DIR, "license-cache.json");
|
|
1749
1794
|
DEVICE_ID_PATH = path6.join(EXE_AI_DIR, "device-id");
|
|
1750
1795
|
API_BASE = "https://askexe.com/cloud";
|
|
1796
|
+
RETRY_DELAY_MS = 500;
|
|
1751
1797
|
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
1752
1798
|
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
1753
1799
|
4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
|
|
@@ -1769,6 +1815,7 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
|
1769
1815
|
employeeLimit: 1,
|
|
1770
1816
|
memoryLimit: 5e3
|
|
1771
1817
|
};
|
|
1818
|
+
CACHE_MAX_AGE_MS = 36e5;
|
|
1772
1819
|
}
|
|
1773
1820
|
});
|
|
1774
1821
|
|
|
@@ -1906,6 +1953,10 @@ import path8 from "path";
|
|
|
1906
1953
|
import { fileURLToPath } from "url";
|
|
1907
1954
|
function handleData(chunk) {
|
|
1908
1955
|
_buffer += chunk.toString();
|
|
1956
|
+
if (_buffer.length > MAX_BUFFER) {
|
|
1957
|
+
_buffer = "";
|
|
1958
|
+
return;
|
|
1959
|
+
}
|
|
1909
1960
|
let newlineIdx;
|
|
1910
1961
|
while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
|
|
1911
1962
|
const line = _buffer.slice(0, newlineIdx).trim();
|
|
@@ -2213,7 +2264,7 @@ function disconnectClient() {
|
|
|
2213
2264
|
entry.resolve({ error: "Client disconnected" });
|
|
2214
2265
|
}
|
|
2215
2266
|
}
|
|
2216
|
-
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, HEALTH_CHECK_INTERVAL, _pending;
|
|
2267
|
+
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, HEALTH_CHECK_INTERVAL, _pending, MAX_BUFFER;
|
|
2217
2268
|
var init_exe_daemon_client = __esm({
|
|
2218
2269
|
"src/lib/exe-daemon-client.ts"() {
|
|
2219
2270
|
"use strict";
|
|
@@ -2230,6 +2281,7 @@ var init_exe_daemon_client = __esm({
|
|
|
2230
2281
|
_requestCount = 0;
|
|
2231
2282
|
HEALTH_CHECK_INTERVAL = 100;
|
|
2232
2283
|
_pending = /* @__PURE__ */ new Map();
|
|
2284
|
+
MAX_BUFFER = 1e7;
|
|
2233
2285
|
}
|
|
2234
2286
|
});
|
|
2235
2287
|
|
|
@@ -2271,8 +2323,8 @@ async function embedDirect(text) {
|
|
|
2271
2323
|
const llamaCpp = await import("node-llama-cpp");
|
|
2272
2324
|
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
2273
2325
|
const { existsSync: existsSync11 } = await import("fs");
|
|
2274
|
-
const
|
|
2275
|
-
const modelPath =
|
|
2326
|
+
const path12 = await import("path");
|
|
2327
|
+
const modelPath = path12.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
2276
2328
|
if (!existsSync11(modelPath)) {
|
|
2277
2329
|
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
2278
2330
|
}
|
|
@@ -2383,6 +2435,7 @@ var init_compress = __esm({
|
|
|
2383
2435
|
// src/lib/cloud-sync.ts
|
|
2384
2436
|
var cloud_sync_exports = {};
|
|
2385
2437
|
__export(cloud_sync_exports, {
|
|
2438
|
+
assertSecureEndpoint: () => assertSecureEndpoint,
|
|
2386
2439
|
buildRosterBlob: () => buildRosterBlob,
|
|
2387
2440
|
cloudPull: () => cloudPull,
|
|
2388
2441
|
cloudPullBehaviors: () => cloudPullBehaviors,
|
|
@@ -2402,9 +2455,10 @@ __export(cloud_sync_exports, {
|
|
|
2402
2455
|
cloudPushTasks: () => cloudPushTasks,
|
|
2403
2456
|
cloudSync: () => cloudSync,
|
|
2404
2457
|
mergeConfig: () => mergeConfig,
|
|
2405
|
-
mergeRosterFromRemote: () => mergeRosterFromRemote
|
|
2458
|
+
mergeRosterFromRemote: () => mergeRosterFromRemote,
|
|
2459
|
+
recordRosterDeletion: () => recordRosterDeletion
|
|
2406
2460
|
});
|
|
2407
|
-
import { readFileSync as readFileSync7, writeFileSync as writeFileSync2, existsSync as existsSync9, readdirSync as
|
|
2461
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync2, existsSync as existsSync9, readdirSync as readdirSync3, mkdirSync as mkdirSync3, appendFileSync, unlinkSync as unlinkSync3 } from "fs";
|
|
2408
2462
|
import path9 from "path";
|
|
2409
2463
|
import { homedir } from "os";
|
|
2410
2464
|
function logError(msg) {
|
|
@@ -2415,16 +2469,47 @@ function logError(msg) {
|
|
|
2415
2469
|
} catch {
|
|
2416
2470
|
}
|
|
2417
2471
|
}
|
|
2472
|
+
async function withRosterLock(fn) {
|
|
2473
|
+
if (existsSync9(ROSTER_LOCK_PATH)) {
|
|
2474
|
+
try {
|
|
2475
|
+
const ts = parseInt(readFileSync7(ROSTER_LOCK_PATH, "utf-8"), 10);
|
|
2476
|
+
if (Date.now() - ts < LOCK_STALE_MS) {
|
|
2477
|
+
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
2478
|
+
}
|
|
2479
|
+
} catch (err) {
|
|
2480
|
+
if (err instanceof Error && err.message.includes("already in progress")) throw err;
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
writeFileSync2(ROSTER_LOCK_PATH, String(Date.now()));
|
|
2484
|
+
try {
|
|
2485
|
+
return await fn();
|
|
2486
|
+
} finally {
|
|
2487
|
+
try {
|
|
2488
|
+
unlinkSync3(ROSTER_LOCK_PATH);
|
|
2489
|
+
} catch {
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2418
2493
|
async function fetchWithRetry(url, init) {
|
|
2419
|
-
const
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2494
|
+
const MAX_RETRIES2 = 3;
|
|
2495
|
+
const BASE_DELAY_MS2 = 200;
|
|
2496
|
+
let lastError;
|
|
2497
|
+
for (let attempt = 0; attempt <= MAX_RETRIES2; attempt++) {
|
|
2498
|
+
try {
|
|
2499
|
+
const signal = AbortSignal.timeout(FETCH_TIMEOUT_MS);
|
|
2500
|
+
const resp = await fetch(url, { ...init, signal });
|
|
2501
|
+
if (resp.status >= 500 && attempt < MAX_RETRIES2) {
|
|
2502
|
+
await new Promise((r) => setTimeout(r, BASE_DELAY_MS2 * Math.pow(2, attempt)));
|
|
2503
|
+
continue;
|
|
2504
|
+
}
|
|
2505
|
+
return resp;
|
|
2506
|
+
} catch (err) {
|
|
2507
|
+
lastError = err;
|
|
2508
|
+
if (attempt === MAX_RETRIES2) throw err;
|
|
2509
|
+
await new Promise((r) => setTimeout(r, BASE_DELAY_MS2 * Math.pow(2, attempt)));
|
|
2510
|
+
}
|
|
2426
2511
|
}
|
|
2427
|
-
|
|
2512
|
+
throw lastError;
|
|
2428
2513
|
}
|
|
2429
2514
|
function assertSecureEndpoint(endpoint) {
|
|
2430
2515
|
if (endpoint.startsWith("https://")) return;
|
|
@@ -2452,10 +2537,15 @@ async function cloudPush(records, maxVersion, config) {
|
|
|
2452
2537
|
headers: {
|
|
2453
2538
|
Authorization: `Bearer ${config.apiKey}`,
|
|
2454
2539
|
"Content-Type": "application/json",
|
|
2455
|
-
"X-Device-Id": loadDeviceId()
|
|
2540
|
+
"X-Device-Id": loadDeviceId(),
|
|
2541
|
+
"X-Expected-Version": String(maxVersion)
|
|
2456
2542
|
},
|
|
2457
2543
|
body: JSON.stringify({ version: maxVersion, blob })
|
|
2458
2544
|
});
|
|
2545
|
+
if (resp.status === 409) {
|
|
2546
|
+
logError("[cloud-sync] PUSH VERSION CONFLICT \u2014 re-pull required before next push");
|
|
2547
|
+
return false;
|
|
2548
|
+
}
|
|
2459
2549
|
return resp.ok;
|
|
2460
2550
|
} catch (err) {
|
|
2461
2551
|
logError(`[cloud-sync] PUSH FAILED: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -2542,18 +2632,21 @@ async function cloudSync(config) {
|
|
|
2542
2632
|
"SELECT value FROM sync_meta WHERE key = 'last_cloud_push_version'"
|
|
2543
2633
|
);
|
|
2544
2634
|
const lastPushVersion = pushMeta.rows.length > 0 ? Number(pushMeta.rows[0].value) : 0;
|
|
2545
|
-
const recordsResult = await client.execute({
|
|
2546
|
-
sql: `SELECT id, agent_id, agent_role, session_id, timestamp,
|
|
2547
|
-
tool_name, project_name, has_error, raw_text, version,
|
|
2548
|
-
author_device_id, scope
|
|
2549
|
-
FROM memories
|
|
2550
|
-
WHERE version > ?
|
|
2551
|
-
AND (scope IS NULL OR scope != 'personal')
|
|
2552
|
-
ORDER BY version ASC`,
|
|
2553
|
-
args: [lastPushVersion]
|
|
2554
|
-
});
|
|
2555
2635
|
let pushed = 0;
|
|
2556
|
-
|
|
2636
|
+
let batchCursor = lastPushVersion;
|
|
2637
|
+
while (true) {
|
|
2638
|
+
const recordsResult = await client.execute({
|
|
2639
|
+
sql: `SELECT id, agent_id, agent_role, session_id, timestamp,
|
|
2640
|
+
tool_name, project_name, has_error, raw_text, version,
|
|
2641
|
+
author_device_id, scope
|
|
2642
|
+
FROM memories
|
|
2643
|
+
WHERE version > ?
|
|
2644
|
+
AND (scope IS NULL OR scope != 'personal')
|
|
2645
|
+
ORDER BY version ASC
|
|
2646
|
+
LIMIT ?`,
|
|
2647
|
+
args: [batchCursor, PUSH_BATCH_SIZE]
|
|
2648
|
+
});
|
|
2649
|
+
if (recordsResult.rows.length === 0) break;
|
|
2557
2650
|
const records = recordsResult.rows.map((row) => ({
|
|
2558
2651
|
id: row.id,
|
|
2559
2652
|
agent_id: row.agent_id,
|
|
@@ -2570,13 +2663,14 @@ async function cloudSync(config) {
|
|
|
2570
2663
|
}));
|
|
2571
2664
|
const maxVersion = Number(records[records.length - 1].version);
|
|
2572
2665
|
const pushOk = await cloudPush(records, maxVersion, config);
|
|
2573
|
-
if (pushOk)
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2666
|
+
if (!pushOk) break;
|
|
2667
|
+
await client.execute({
|
|
2668
|
+
sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_cloud_push_version', ?)",
|
|
2669
|
+
args: [String(maxVersion)]
|
|
2670
|
+
});
|
|
2671
|
+
pushed += records.length;
|
|
2672
|
+
batchCursor = maxVersion;
|
|
2673
|
+
if (recordsResult.rows.length < PUSH_BATCH_SIZE) break;
|
|
2580
2674
|
}
|
|
2581
2675
|
try {
|
|
2582
2676
|
await cloudPushRoster(config);
|
|
@@ -2658,6 +2752,27 @@ async function cloudSync(config) {
|
|
|
2658
2752
|
documents: documentsResult
|
|
2659
2753
|
};
|
|
2660
2754
|
}
|
|
2755
|
+
function recordRosterDeletion(name) {
|
|
2756
|
+
let deletions = [];
|
|
2757
|
+
try {
|
|
2758
|
+
if (existsSync9(ROSTER_DELETIONS_PATH)) {
|
|
2759
|
+
deletions = JSON.parse(readFileSync7(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
2760
|
+
}
|
|
2761
|
+
} catch {
|
|
2762
|
+
}
|
|
2763
|
+
if (!deletions.includes(name)) deletions.push(name);
|
|
2764
|
+
writeFileSync2(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
|
|
2765
|
+
}
|
|
2766
|
+
function consumeRosterDeletions() {
|
|
2767
|
+
try {
|
|
2768
|
+
if (!existsSync9(ROSTER_DELETIONS_PATH)) return [];
|
|
2769
|
+
const deletions = JSON.parse(readFileSync7(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
2770
|
+
writeFileSync2(ROSTER_DELETIONS_PATH, "[]");
|
|
2771
|
+
return deletions;
|
|
2772
|
+
} catch {
|
|
2773
|
+
return [];
|
|
2774
|
+
}
|
|
2775
|
+
}
|
|
2661
2776
|
function buildRosterBlob(paths) {
|
|
2662
2777
|
const rosterPath = paths?.rosterPath ?? path9.join(EXE_AI_DIR, "exe-employees.json");
|
|
2663
2778
|
const identityDir = paths?.identityDir ?? path9.join(EXE_AI_DIR, "identity");
|
|
@@ -2671,7 +2786,7 @@ function buildRosterBlob(paths) {
|
|
|
2671
2786
|
}
|
|
2672
2787
|
const identities = {};
|
|
2673
2788
|
if (existsSync9(identityDir)) {
|
|
2674
|
-
for (const file of
|
|
2789
|
+
for (const file of readdirSync3(identityDir).filter((f) => f.endsWith(".md"))) {
|
|
2675
2790
|
try {
|
|
2676
2791
|
identities[file] = readFileSync7(path9.join(identityDir, file), "utf-8");
|
|
2677
2792
|
} catch {
|
|
@@ -2685,9 +2800,10 @@ function buildRosterBlob(paths) {
|
|
|
2685
2800
|
} catch {
|
|
2686
2801
|
}
|
|
2687
2802
|
}
|
|
2688
|
-
const
|
|
2803
|
+
const deletedNames = consumeRosterDeletions();
|
|
2804
|
+
const content = JSON.stringify({ roster, identities, config, deletedNames });
|
|
2689
2805
|
const hash = Buffer.from(content).length;
|
|
2690
|
-
return { roster, identities, config, version: hash };
|
|
2806
|
+
return { roster, identities, config, deletedNames, version: hash };
|
|
2691
2807
|
}
|
|
2692
2808
|
async function cloudPushRoster(config) {
|
|
2693
2809
|
assertSecureEndpoint(config.endpoint);
|
|
@@ -2770,38 +2886,50 @@ function mergeConfig(remoteConfig, configPath) {
|
|
|
2770
2886
|
writeFileSync2(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
2771
2887
|
}
|
|
2772
2888
|
async function mergeRosterFromRemote(remote, paths) {
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
if (
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2889
|
+
return withRosterLock(async () => {
|
|
2890
|
+
const rosterPath = paths?.rosterPath ?? void 0;
|
|
2891
|
+
const identityDir = paths?.identityDir ?? path9.join(EXE_AI_DIR, "identity");
|
|
2892
|
+
const localEmployees = await loadEmployees(rosterPath);
|
|
2893
|
+
const localNames = new Set(localEmployees.map((e) => e.name));
|
|
2894
|
+
let added = 0;
|
|
2895
|
+
for (const remoteEmp of remote.roster) {
|
|
2896
|
+
if (localNames.has(remoteEmp.name)) continue;
|
|
2897
|
+
localEmployees.push(remoteEmp);
|
|
2898
|
+
localNames.add(remoteEmp.name);
|
|
2899
|
+
added++;
|
|
2900
|
+
if (remote.identities[`${remoteEmp.name}.md`]) {
|
|
2901
|
+
if (!existsSync9(identityDir)) mkdirSync3(identityDir, { recursive: true });
|
|
2902
|
+
const idPath = path9.join(identityDir, `${remoteEmp.name}.md`);
|
|
2903
|
+
if (!existsSync9(idPath)) {
|
|
2904
|
+
writeFileSync2(idPath, remote.identities[`${remoteEmp.name}.md`], "utf-8");
|
|
2905
|
+
}
|
|
2906
|
+
}
|
|
2907
|
+
try {
|
|
2908
|
+
registerBinSymlinks(remoteEmp.name);
|
|
2909
|
+
} catch {
|
|
2788
2910
|
}
|
|
2789
2911
|
}
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2912
|
+
let removed = 0;
|
|
2913
|
+
if (remote.deletedNames && remote.deletedNames.length > 0) {
|
|
2914
|
+
const toRemove = new Set(remote.deletedNames);
|
|
2915
|
+
const filtered = localEmployees.filter((e) => !toRemove.has(e.name));
|
|
2916
|
+
removed = localEmployees.length - filtered.length;
|
|
2917
|
+
if (removed > 0) {
|
|
2918
|
+
localEmployees.length = 0;
|
|
2919
|
+
localEmployees.push(...filtered);
|
|
2920
|
+
}
|
|
2793
2921
|
}
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
await saveEmployees(localEmployees, rosterPath);
|
|
2797
|
-
}
|
|
2798
|
-
if (remote.config && Object.keys(remote.config).length > 0) {
|
|
2799
|
-
try {
|
|
2800
|
-
mergeConfig(remote.config, paths?.configPath);
|
|
2801
|
-
} catch {
|
|
2922
|
+
if (added > 0 || removed > 0) {
|
|
2923
|
+
await saveEmployees(localEmployees, rosterPath);
|
|
2802
2924
|
}
|
|
2803
|
-
|
|
2804
|
-
|
|
2925
|
+
if (remote.config && Object.keys(remote.config).length > 0) {
|
|
2926
|
+
try {
|
|
2927
|
+
mergeConfig(remote.config, paths?.configPath);
|
|
2928
|
+
} catch {
|
|
2929
|
+
}
|
|
2930
|
+
}
|
|
2931
|
+
return { added };
|
|
2932
|
+
});
|
|
2805
2933
|
}
|
|
2806
2934
|
async function cloudPushBlob(route, data, metaKey, config) {
|
|
2807
2935
|
if (data.length === 0) return { ok: true };
|
|
@@ -2869,7 +2997,7 @@ async function cloudPullBlob(route, config) {
|
|
|
2869
2997
|
}
|
|
2870
2998
|
async function cloudPushBehaviors(config) {
|
|
2871
2999
|
const client = getClient();
|
|
2872
|
-
const result = await client.execute("SELECT * FROM behaviors");
|
|
3000
|
+
const result = await client.execute("SELECT * FROM behaviors LIMIT 10000");
|
|
2873
3001
|
const rows = result.rows;
|
|
2874
3002
|
const { ok } = await cloudPushBlob(
|
|
2875
3003
|
"/sync/push-behaviors",
|
|
@@ -2917,13 +3045,13 @@ async function cloudPullBehaviors(config) {
|
|
|
2917
3045
|
async function cloudPushGraphRAG(config) {
|
|
2918
3046
|
const client = getClient();
|
|
2919
3047
|
const [entities, relationships, aliases, entityMems, relMems, hyperedges, hyperedgeNodes] = await Promise.all([
|
|
2920
|
-
client.execute("SELECT * FROM entities"),
|
|
2921
|
-
client.execute("SELECT * FROM relationships"),
|
|
2922
|
-
client.execute("SELECT * FROM entity_aliases"),
|
|
2923
|
-
client.execute("SELECT * FROM entity_memories"),
|
|
2924
|
-
client.execute("SELECT * FROM relationship_memories"),
|
|
2925
|
-
client.execute("SELECT * FROM hyperedges"),
|
|
2926
|
-
client.execute("SELECT * FROM hyperedge_nodes")
|
|
3048
|
+
client.execute("SELECT * FROM entities LIMIT 50000"),
|
|
3049
|
+
client.execute("SELECT * FROM relationships LIMIT 50000"),
|
|
3050
|
+
client.execute("SELECT * FROM entity_aliases LIMIT 50000"),
|
|
3051
|
+
client.execute("SELECT * FROM entity_memories LIMIT 50000"),
|
|
3052
|
+
client.execute("SELECT * FROM relationship_memories LIMIT 50000"),
|
|
3053
|
+
client.execute("SELECT * FROM hyperedges LIMIT 50000"),
|
|
3054
|
+
client.execute("SELECT * FROM hyperedge_nodes LIMIT 50000")
|
|
2927
3055
|
]);
|
|
2928
3056
|
const blob = {
|
|
2929
3057
|
entities: entities.rows,
|
|
@@ -3025,7 +3153,7 @@ async function cloudPullGraphRAG(config) {
|
|
|
3025
3153
|
}
|
|
3026
3154
|
async function cloudPushTasks(config) {
|
|
3027
3155
|
const client = getClient();
|
|
3028
|
-
const result = await client.execute("SELECT * FROM tasks");
|
|
3156
|
+
const result = await client.execute("SELECT * FROM tasks LIMIT 10000");
|
|
3029
3157
|
const rows = result.rows;
|
|
3030
3158
|
const { ok } = await cloudPushBlob(
|
|
3031
3159
|
"/sync/push-tasks",
|
|
@@ -3071,7 +3199,7 @@ async function cloudPullTasks(config) {
|
|
|
3071
3199
|
}
|
|
3072
3200
|
async function cloudPushConversations(config) {
|
|
3073
3201
|
const client = getClient();
|
|
3074
|
-
const result = await client.execute("SELECT * FROM conversations");
|
|
3202
|
+
const result = await client.execute("SELECT * FROM conversations LIMIT 50000");
|
|
3075
3203
|
const rows = result.rows;
|
|
3076
3204
|
const { ok } = await cloudPushBlob(
|
|
3077
3205
|
"/sync/push-conversations",
|
|
@@ -3121,8 +3249,8 @@ async function cloudPullConversations(config) {
|
|
|
3121
3249
|
async function cloudPushDocuments(config) {
|
|
3122
3250
|
const client = getClient();
|
|
3123
3251
|
const [workspaces, documents] = await Promise.all([
|
|
3124
|
-
client.execute("SELECT * FROM workspaces"),
|
|
3125
|
-
client.execute("SELECT * FROM documents")
|
|
3252
|
+
client.execute("SELECT * FROM workspaces LIMIT 1000"),
|
|
3253
|
+
client.execute("SELECT * FROM documents LIMIT 10000")
|
|
3126
3254
|
]);
|
|
3127
3255
|
const blob = {
|
|
3128
3256
|
workspaces: workspaces.rows,
|
|
@@ -3175,7 +3303,7 @@ async function cloudPullDocuments(config) {
|
|
|
3175
3303
|
}
|
|
3176
3304
|
return { pulled };
|
|
3177
3305
|
}
|
|
3178
|
-
var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS;
|
|
3306
|
+
var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, ROSTER_DELETIONS_PATH;
|
|
3179
3307
|
var init_cloud_sync = __esm({
|
|
3180
3308
|
"src/lib/cloud-sync.ts"() {
|
|
3181
3309
|
"use strict";
|
|
@@ -3188,6 +3316,68 @@ var init_cloud_sync = __esm({
|
|
|
3188
3316
|
init_employees();
|
|
3189
3317
|
LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
|
|
3190
3318
|
FETCH_TIMEOUT_MS = 3e4;
|
|
3319
|
+
PUSH_BATCH_SIZE = 5e3;
|
|
3320
|
+
ROSTER_LOCK_PATH = path9.join(EXE_AI_DIR, "roster-merge.lock");
|
|
3321
|
+
LOCK_STALE_MS = 3e4;
|
|
3322
|
+
ROSTER_DELETIONS_PATH = path9.join(EXE_AI_DIR, "roster-deletions.json");
|
|
3323
|
+
}
|
|
3324
|
+
});
|
|
3325
|
+
|
|
3326
|
+
// src/lib/worker-gate.ts
|
|
3327
|
+
var worker_gate_exports = {};
|
|
3328
|
+
__export(worker_gate_exports, {
|
|
3329
|
+
MAX_CONCURRENT_WORKERS: () => MAX_CONCURRENT_WORKERS,
|
|
3330
|
+
cleanupWorkerPid: () => cleanupWorkerPid,
|
|
3331
|
+
registerWorkerPid: () => registerWorkerPid,
|
|
3332
|
+
tryAcquireWorkerSlot: () => tryAcquireWorkerSlot
|
|
3333
|
+
});
|
|
3334
|
+
import { readdirSync as readdirSync4, writeFileSync as writeFileSync3, unlinkSync as unlinkSync4, mkdirSync as mkdirSync4 } from "fs";
|
|
3335
|
+
import path10 from "path";
|
|
3336
|
+
function tryAcquireWorkerSlot() {
|
|
3337
|
+
try {
|
|
3338
|
+
mkdirSync4(WORKER_PID_DIR, { recursive: true });
|
|
3339
|
+
const files = readdirSync4(WORKER_PID_DIR);
|
|
3340
|
+
let alive = 0;
|
|
3341
|
+
for (const f of files) {
|
|
3342
|
+
if (!f.endsWith(".pid")) continue;
|
|
3343
|
+
const dashIdx = f.lastIndexOf("-");
|
|
3344
|
+
const pid = parseInt(f.slice(dashIdx + 1).replace(".pid", ""), 10);
|
|
3345
|
+
if (isNaN(pid)) continue;
|
|
3346
|
+
try {
|
|
3347
|
+
process.kill(pid, 0);
|
|
3348
|
+
alive++;
|
|
3349
|
+
} catch {
|
|
3350
|
+
try {
|
|
3351
|
+
unlinkSync4(path10.join(WORKER_PID_DIR, f));
|
|
3352
|
+
} catch {
|
|
3353
|
+
}
|
|
3354
|
+
}
|
|
3355
|
+
}
|
|
3356
|
+
return alive < MAX_CONCURRENT_WORKERS;
|
|
3357
|
+
} catch {
|
|
3358
|
+
return true;
|
|
3359
|
+
}
|
|
3360
|
+
}
|
|
3361
|
+
function registerWorkerPid(pid) {
|
|
3362
|
+
try {
|
|
3363
|
+
mkdirSync4(WORKER_PID_DIR, { recursive: true });
|
|
3364
|
+
writeFileSync3(path10.join(WORKER_PID_DIR, `worker-${pid}.pid`), String(pid));
|
|
3365
|
+
} catch {
|
|
3366
|
+
}
|
|
3367
|
+
}
|
|
3368
|
+
function cleanupWorkerPid() {
|
|
3369
|
+
try {
|
|
3370
|
+
unlinkSync4(path10.join(WORKER_PID_DIR, `worker-${process.pid}.pid`));
|
|
3371
|
+
} catch {
|
|
3372
|
+
}
|
|
3373
|
+
}
|
|
3374
|
+
var WORKER_PID_DIR, MAX_CONCURRENT_WORKERS;
|
|
3375
|
+
var init_worker_gate = __esm({
|
|
3376
|
+
"src/lib/worker-gate.ts"() {
|
|
3377
|
+
"use strict";
|
|
3378
|
+
init_config();
|
|
3379
|
+
WORKER_PID_DIR = path10.join(EXE_AI_DIR, "worker-pids");
|
|
3380
|
+
MAX_CONCURRENT_WORKERS = 3;
|
|
3191
3381
|
}
|
|
3192
3382
|
});
|
|
3193
3383
|
|
|
@@ -3196,6 +3386,30 @@ init_memory();
|
|
|
3196
3386
|
init_database();
|
|
3197
3387
|
init_keychain();
|
|
3198
3388
|
init_config();
|
|
3389
|
+
var INIT_MAX_RETRIES = 3;
|
|
3390
|
+
var INIT_RETRY_DELAY_MS = 1e3;
|
|
3391
|
+
function isBusyError2(err) {
|
|
3392
|
+
if (err instanceof Error) {
|
|
3393
|
+
const msg = err.message.toLowerCase();
|
|
3394
|
+
return msg.includes("sqlite_busy") || msg.includes("database is locked");
|
|
3395
|
+
}
|
|
3396
|
+
return false;
|
|
3397
|
+
}
|
|
3398
|
+
async function retryOnBusy2(fn, label) {
|
|
3399
|
+
for (let attempt = 0; attempt <= INIT_MAX_RETRIES; attempt++) {
|
|
3400
|
+
try {
|
|
3401
|
+
return await fn();
|
|
3402
|
+
} catch (err) {
|
|
3403
|
+
if (!isBusyError2(err) || attempt === INIT_MAX_RETRIES) throw err;
|
|
3404
|
+
process.stderr.write(
|
|
3405
|
+
`[store] SQLITE_BUSY during ${label}, retry ${attempt + 1}/${INIT_MAX_RETRIES}
|
|
3406
|
+
`
|
|
3407
|
+
);
|
|
3408
|
+
await new Promise((r) => setTimeout(r, INIT_RETRY_DELAY_MS * (attempt + 1)));
|
|
3409
|
+
}
|
|
3410
|
+
}
|
|
3411
|
+
throw new Error("unreachable");
|
|
3412
|
+
}
|
|
3199
3413
|
var _pendingRecords = [];
|
|
3200
3414
|
var _batchSize = 20;
|
|
3201
3415
|
var _flushIntervalMs = 1e4;
|
|
@@ -3230,14 +3444,17 @@ async function initStore(options) {
|
|
|
3230
3444
|
dbPath,
|
|
3231
3445
|
encryptionKey: hexKey
|
|
3232
3446
|
});
|
|
3233
|
-
await ensureSchema();
|
|
3447
|
+
await retryOnBusy2(() => ensureSchema(), "ensureSchema");
|
|
3234
3448
|
try {
|
|
3235
3449
|
const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
|
|
3236
3450
|
initShardManager2(hexKey);
|
|
3237
3451
|
} catch {
|
|
3238
3452
|
}
|
|
3239
3453
|
const client = getClient();
|
|
3240
|
-
const vResult = await
|
|
3454
|
+
const vResult = await retryOnBusy2(
|
|
3455
|
+
() => client.execute("SELECT MAX(version) as max_v FROM memories"),
|
|
3456
|
+
"version-query"
|
|
3457
|
+
);
|
|
3241
3458
|
_nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
3242
3459
|
}
|
|
3243
3460
|
function classifyTier(record) {
|
|
@@ -3280,6 +3497,12 @@ async function writeMemory(record) {
|
|
|
3280
3497
|
supersedes_id: record.supersedes_id ?? null
|
|
3281
3498
|
};
|
|
3282
3499
|
_pendingRecords.push(dbRow);
|
|
3500
|
+
const MAX_PENDING = 1e3;
|
|
3501
|
+
if (_pendingRecords.length > MAX_PENDING) {
|
|
3502
|
+
const dropped = _pendingRecords.length - MAX_PENDING;
|
|
3503
|
+
_pendingRecords = _pendingRecords.slice(-MAX_PENDING);
|
|
3504
|
+
console.warn(`[store] Dropped ${dropped} oldest pending records (overflow)`);
|
|
3505
|
+
}
|
|
3283
3506
|
if (_flushTimer === null) {
|
|
3284
3507
|
_flushTimer = setInterval(() => {
|
|
3285
3508
|
void flushBatch();
|
|
@@ -3436,10 +3659,10 @@ import crypto4 from "crypto";
|
|
|
3436
3659
|
init_database();
|
|
3437
3660
|
import crypto2 from "crypto";
|
|
3438
3661
|
import path4 from "path";
|
|
3439
|
-
import
|
|
3662
|
+
import os3 from "os";
|
|
3440
3663
|
import {
|
|
3441
3664
|
readFileSync as readFileSync2,
|
|
3442
|
-
readdirSync,
|
|
3665
|
+
readdirSync as readdirSync2,
|
|
3443
3666
|
unlinkSync,
|
|
3444
3667
|
existsSync as existsSync4,
|
|
3445
3668
|
rmdirSync
|
|
@@ -3471,8 +3694,8 @@ async function writeNotification(notification) {
|
|
|
3471
3694
|
|
|
3472
3695
|
// src/adapters/claude/hooks/summary-worker.ts
|
|
3473
3696
|
import { execSync as execSync2 } from "child_process";
|
|
3474
|
-
import { existsSync as existsSync10, mkdirSync as
|
|
3475
|
-
import
|
|
3697
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync5, openSync as openSync2, closeSync as closeSync2 } from "fs";
|
|
3698
|
+
import path11 from "path";
|
|
3476
3699
|
async function main() {
|
|
3477
3700
|
const agentId = process.env.AGENT_ID ?? "default";
|
|
3478
3701
|
const agentRole = process.env.AGENT_ROLE ?? "employee";
|
|
@@ -3596,16 +3819,16 @@ async function main() {
|
|
|
3596
3819
|
}
|
|
3597
3820
|
try {
|
|
3598
3821
|
const { EXE_AI_DIR: EXE_AI_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
3599
|
-
const flagPath =
|
|
3822
|
+
const flagPath = path11.join(EXE_AI_DIR2, "session-cache", "needs-backfill");
|
|
3600
3823
|
if (existsSync10(flagPath)) {
|
|
3601
3824
|
const { spawn: spawn2 } = await import("child_process");
|
|
3602
3825
|
const { fileURLToPath: fileURLToPath2 } = await import("url");
|
|
3603
3826
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
3604
|
-
const backfillPath =
|
|
3827
|
+
const backfillPath = path11.resolve(path11.dirname(thisFile), "backfill-vectors.js");
|
|
3605
3828
|
if (existsSync10(backfillPath)) {
|
|
3606
3829
|
const { EXE_AI_DIR: exeDir2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
3607
|
-
const bLogPath =
|
|
3608
|
-
|
|
3830
|
+
const bLogPath = path11.join(exeDir2, "workers.log");
|
|
3831
|
+
mkdirSync5(path11.dirname(bLogPath), { recursive: true });
|
|
3609
3832
|
const bLogFd = openSync2(bLogPath, "a");
|
|
3610
3833
|
const child = spawn2(process.execPath, [backfillPath], {
|
|
3611
3834
|
detached: true,
|
|
@@ -3678,9 +3901,19 @@ async function main() {
|
|
|
3678
3901
|
process.stderr.write("[summary-worker] orphan scan failed: " + (err instanceof Error ? err.message : String(err)) + "\n");
|
|
3679
3902
|
}
|
|
3680
3903
|
}
|
|
3904
|
+
try {
|
|
3905
|
+
const { getRawClient: getRawClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
3906
|
+
await getRawClient2().execute("PRAGMA wal_checkpoint(PASSIVE)");
|
|
3907
|
+
} catch {
|
|
3908
|
+
}
|
|
3681
3909
|
}
|
|
3682
3910
|
main().catch((err) => {
|
|
3683
3911
|
process.stderr.write("[summary-worker] FATAL: " + (err instanceof Error ? err.message : String(err)) + "\n");
|
|
3684
|
-
}).finally(() => {
|
|
3912
|
+
}).finally(async () => {
|
|
3913
|
+
try {
|
|
3914
|
+
const { cleanupWorkerPid: cleanupWorkerPid2 } = await Promise.resolve().then(() => (init_worker_gate(), worker_gate_exports));
|
|
3915
|
+
cleanupWorkerPid2();
|
|
3916
|
+
} catch {
|
|
3917
|
+
}
|
|
3685
3918
|
process.exit(0);
|
|
3686
3919
|
});
|