@askexenow/exe-os 0.8.61 → 0.8.63
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 +2 -3
- package/dist/bin/backfill-responses.js +3 -4
- package/dist/bin/backfill-vectors.js +0 -1
- package/dist/bin/cleanup-stale-review-tasks.js +0 -1
- package/dist/bin/cli.js +1654 -397
- package/dist/bin/exe-assign.js +0 -1
- package/dist/bin/exe-boot.js +61 -84
- package/dist/bin/exe-cloud.js +28 -60
- package/dist/bin/exe-dispatch.js +0 -1
- package/dist/bin/exe-doctor.js +0 -1
- package/dist/bin/exe-export-behaviors.js +1 -2
- package/dist/bin/exe-forget.js +0 -1
- package/dist/bin/exe-gateway.js +16 -17
- package/dist/bin/exe-heartbeat.js +1 -2
- package/dist/bin/exe-kill.js +2 -3
- package/dist/bin/exe-launch-agent.js +1 -2
- package/dist/bin/exe-link.js +49 -78
- package/dist/bin/exe-pending-messages.js +1 -2
- package/dist/bin/exe-pending-notifications.js +0 -1
- package/dist/bin/exe-pending-reviews.js +1 -2
- package/dist/bin/exe-review.js +0 -1
- package/dist/bin/exe-search.js +2 -3
- package/dist/bin/exe-session-cleanup.js +4 -5
- package/dist/bin/exe-status.js +0 -1
- package/dist/bin/exe-team.js +0 -1
- package/dist/bin/git-sweep.js +0 -1
- package/dist/bin/graph-backfill.js +5 -6
- package/dist/bin/graph-export.js +0 -1
- package/dist/bin/scan-tasks.js +0 -1
- package/dist/bin/setup.js +1460 -115
- package/dist/bin/shard-migrate.js +0 -1
- package/dist/bin/wiki-sync.js +0 -1
- package/dist/gateway/index.js +16 -17
- package/dist/hooks/bug-report-worker.js +11 -12
- package/dist/hooks/commit-complete.js +0 -1
- package/dist/hooks/error-recall.js +2 -3
- package/dist/hooks/ingest-worker.js +14 -15
- package/dist/hooks/instructions-loaded.js +0 -1
- package/dist/hooks/notification.js +0 -1
- package/dist/hooks/post-compact.js +0 -1
- package/dist/hooks/pre-compact.js +2 -3
- package/dist/hooks/pre-tool-use.js +0 -1
- package/dist/hooks/prompt-ingest-worker.js +2 -3
- package/dist/hooks/prompt-submit.js +6 -7
- package/dist/hooks/response-ingest-worker.js +2 -3
- package/dist/hooks/session-end.js +3 -4
- package/dist/hooks/session-start.js +2 -3
- package/dist/hooks/stop.js +2 -3
- package/dist/hooks/subagent-stop.js +0 -1
- package/dist/hooks/summary-worker.js +51 -74
- package/dist/index.js +7 -8
- package/dist/lib/cloud-sync.js +21 -12
- package/dist/lib/consolidation.js +0 -1
- package/dist/lib/exe-daemon.js +33 -65
- package/dist/lib/hybrid-search.js +2 -3
- package/dist/lib/keychain.js +19 -58
- package/dist/lib/schedules.js +2 -3
- package/dist/lib/store.js +0 -1
- package/dist/mcp/server.js +29 -30
- package/dist/runtime/index.js +2 -3
- package/dist/tui/App.js +24 -56
- package/package.json +1 -1
package/dist/bin/setup.js
CHANGED
|
@@ -263,6 +263,121 @@ var init_config = __esm({
|
|
|
263
263
|
}
|
|
264
264
|
});
|
|
265
265
|
|
|
266
|
+
// src/lib/keychain.ts
|
|
267
|
+
var keychain_exports = {};
|
|
268
|
+
__export(keychain_exports, {
|
|
269
|
+
deleteMasterKey: () => deleteMasterKey,
|
|
270
|
+
exportMnemonic: () => exportMnemonic,
|
|
271
|
+
getMasterKey: () => getMasterKey,
|
|
272
|
+
importMnemonic: () => importMnemonic,
|
|
273
|
+
setMasterKey: () => setMasterKey
|
|
274
|
+
});
|
|
275
|
+
import { readFile as readFile2, writeFile as writeFile2, unlink, mkdir as mkdir2, chmod as chmod2 } from "fs/promises";
|
|
276
|
+
import { existsSync as existsSync2 } from "fs";
|
|
277
|
+
import path2 from "path";
|
|
278
|
+
import os2 from "os";
|
|
279
|
+
function getKeyDir() {
|
|
280
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path2.join(os2.homedir(), ".exe-os");
|
|
281
|
+
}
|
|
282
|
+
function getKeyPath() {
|
|
283
|
+
return path2.join(getKeyDir(), "master.key");
|
|
284
|
+
}
|
|
285
|
+
async function tryKeytar() {
|
|
286
|
+
try {
|
|
287
|
+
return await import("keytar");
|
|
288
|
+
} catch {
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
async function getMasterKey() {
|
|
293
|
+
const keytar = await tryKeytar();
|
|
294
|
+
if (keytar) {
|
|
295
|
+
try {
|
|
296
|
+
const stored = await keytar.getPassword(SERVICE, ACCOUNT);
|
|
297
|
+
if (stored) {
|
|
298
|
+
return Buffer.from(stored, "base64");
|
|
299
|
+
}
|
|
300
|
+
} catch {
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
const keyPath = getKeyPath();
|
|
304
|
+
if (!existsSync2(keyPath)) {
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
try {
|
|
308
|
+
const content = await readFile2(keyPath, "utf-8");
|
|
309
|
+
return Buffer.from(content.trim(), "base64");
|
|
310
|
+
} catch {
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
async function setMasterKey(key) {
|
|
315
|
+
const b64 = key.toString("base64");
|
|
316
|
+
const keytar = await tryKeytar();
|
|
317
|
+
if (keytar) {
|
|
318
|
+
try {
|
|
319
|
+
await keytar.setPassword(SERVICE, ACCOUNT, b64);
|
|
320
|
+
return;
|
|
321
|
+
} catch {
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
const dir = getKeyDir();
|
|
325
|
+
await mkdir2(dir, { recursive: true });
|
|
326
|
+
const keyPath = getKeyPath();
|
|
327
|
+
await writeFile2(keyPath, b64 + "\n", "utf-8");
|
|
328
|
+
await chmod2(keyPath, 384);
|
|
329
|
+
}
|
|
330
|
+
async function deleteMasterKey() {
|
|
331
|
+
const keytar = await tryKeytar();
|
|
332
|
+
if (keytar) {
|
|
333
|
+
try {
|
|
334
|
+
await keytar.deletePassword(SERVICE, ACCOUNT);
|
|
335
|
+
} catch {
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
const keyPath = getKeyPath();
|
|
339
|
+
if (existsSync2(keyPath)) {
|
|
340
|
+
await unlink(keyPath);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
async function loadBip39() {
|
|
344
|
+
try {
|
|
345
|
+
return await import("bip39");
|
|
346
|
+
} catch {
|
|
347
|
+
throw new Error(
|
|
348
|
+
"bip39 package not found. Run: npm install -g bip39\nOr reinstall exe-os: npm install -g @askexenow/exe-os"
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
async function exportMnemonic(key) {
|
|
353
|
+
if (key.length !== 32) {
|
|
354
|
+
throw new Error(`Key must be 32 bytes, got ${key.length}`);
|
|
355
|
+
}
|
|
356
|
+
const { entropyToMnemonic } = await loadBip39();
|
|
357
|
+
return entropyToMnemonic(key.toString("hex"));
|
|
358
|
+
}
|
|
359
|
+
async function importMnemonic(mnemonic) {
|
|
360
|
+
const trimmed = mnemonic.trim();
|
|
361
|
+
const words = trimmed.split(/\s+/);
|
|
362
|
+
if (words.length !== 24) {
|
|
363
|
+
throw new Error(`Expected 24 words, got ${words.length}`);
|
|
364
|
+
}
|
|
365
|
+
const { validateMnemonic, mnemonicToEntropy } = await loadBip39();
|
|
366
|
+
if (!validateMnemonic(trimmed)) {
|
|
367
|
+
throw new Error("Invalid mnemonic \u2014 check for typos or missing words");
|
|
368
|
+
}
|
|
369
|
+
const entropy = mnemonicToEntropy(trimmed);
|
|
370
|
+
return Buffer.from(entropy, "hex");
|
|
371
|
+
}
|
|
372
|
+
var SERVICE, ACCOUNT;
|
|
373
|
+
var init_keychain = __esm({
|
|
374
|
+
"src/lib/keychain.ts"() {
|
|
375
|
+
"use strict";
|
|
376
|
+
SERVICE = "exe-mem";
|
|
377
|
+
ACCOUNT = "master-key";
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
|
|
266
381
|
// src/types/memory.ts
|
|
267
382
|
var EMBEDDING_DIM;
|
|
268
383
|
var init_memory = __esm({
|
|
@@ -650,10 +765,10 @@ async function disposeEmbedder() {
|
|
|
650
765
|
async function embedDirect(text) {
|
|
651
766
|
const llamaCpp = await import("node-llama-cpp");
|
|
652
767
|
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
653
|
-
const { existsSync:
|
|
654
|
-
const
|
|
655
|
-
const modelPath =
|
|
656
|
-
if (!
|
|
768
|
+
const { existsSync: existsSync11 } = await import("fs");
|
|
769
|
+
const path11 = await import("path");
|
|
770
|
+
const modelPath = path11.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
771
|
+
if (!existsSync11(modelPath)) {
|
|
657
772
|
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
658
773
|
}
|
|
659
774
|
const llama = await llamaCpp.getLlama();
|
|
@@ -1068,6 +1183,109 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
|
1068
1183
|
}
|
|
1069
1184
|
});
|
|
1070
1185
|
|
|
1186
|
+
// src/lib/crypto.ts
|
|
1187
|
+
var crypto_exports = {};
|
|
1188
|
+
__export(crypto_exports, {
|
|
1189
|
+
decryptSyncBlob: () => decryptSyncBlob,
|
|
1190
|
+
encryptSyncBlob: () => encryptSyncBlob,
|
|
1191
|
+
initSyncCrypto: () => initSyncCrypto,
|
|
1192
|
+
isSyncCryptoInitialized: () => isSyncCryptoInitialized
|
|
1193
|
+
});
|
|
1194
|
+
import crypto from "crypto";
|
|
1195
|
+
function initSyncCrypto(masterKey) {
|
|
1196
|
+
if (masterKey.length !== 32) {
|
|
1197
|
+
throw new Error(`Master key must be 32 bytes, got ${masterKey.length}`);
|
|
1198
|
+
}
|
|
1199
|
+
_syncKey = Buffer.from(
|
|
1200
|
+
crypto.hkdfSync("sha256", masterKey, "", SYNC_HKDF_INFO, 32)
|
|
1201
|
+
);
|
|
1202
|
+
}
|
|
1203
|
+
function isSyncCryptoInitialized() {
|
|
1204
|
+
return _syncKey !== null;
|
|
1205
|
+
}
|
|
1206
|
+
function requireSyncKey() {
|
|
1207
|
+
if (!_syncKey) {
|
|
1208
|
+
throw new Error("Sync crypto not initialized. Call initSyncCrypto(masterKey) first.");
|
|
1209
|
+
}
|
|
1210
|
+
return _syncKey;
|
|
1211
|
+
}
|
|
1212
|
+
function encryptSyncBlob(data) {
|
|
1213
|
+
const key = requireSyncKey();
|
|
1214
|
+
const iv = crypto.randomBytes(IV_LENGTH);
|
|
1215
|
+
const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
|
|
1216
|
+
const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
|
|
1217
|
+
const tag = cipher.getAuthTag();
|
|
1218
|
+
return Buffer.concat([iv, encrypted, tag]).toString("base64");
|
|
1219
|
+
}
|
|
1220
|
+
function decryptSyncBlob(ciphertext) {
|
|
1221
|
+
const key = requireSyncKey();
|
|
1222
|
+
const combined = Buffer.from(ciphertext, "base64");
|
|
1223
|
+
if (combined.length < IV_LENGTH + TAG_LENGTH) {
|
|
1224
|
+
throw new Error("Sync blob too short to contain IV + tag");
|
|
1225
|
+
}
|
|
1226
|
+
const iv = combined.subarray(0, IV_LENGTH);
|
|
1227
|
+
const tag = combined.subarray(combined.length - TAG_LENGTH);
|
|
1228
|
+
const encrypted = combined.subarray(IV_LENGTH, combined.length - TAG_LENGTH);
|
|
1229
|
+
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
|
|
1230
|
+
decipher.setAuthTag(tag);
|
|
1231
|
+
return Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
|
1232
|
+
}
|
|
1233
|
+
var ALGORITHM, IV_LENGTH, TAG_LENGTH, SYNC_HKDF_INFO, _syncKey;
|
|
1234
|
+
var init_crypto = __esm({
|
|
1235
|
+
"src/lib/crypto.ts"() {
|
|
1236
|
+
"use strict";
|
|
1237
|
+
ALGORITHM = "aes-256-gcm";
|
|
1238
|
+
IV_LENGTH = 12;
|
|
1239
|
+
TAG_LENGTH = 16;
|
|
1240
|
+
SYNC_HKDF_INFO = "exe-mem-sync-v2";
|
|
1241
|
+
_syncKey = null;
|
|
1242
|
+
}
|
|
1243
|
+
});
|
|
1244
|
+
|
|
1245
|
+
// src/lib/db-retry.ts
|
|
1246
|
+
var init_db_retry = __esm({
|
|
1247
|
+
"src/lib/db-retry.ts"() {
|
|
1248
|
+
"use strict";
|
|
1249
|
+
}
|
|
1250
|
+
});
|
|
1251
|
+
|
|
1252
|
+
// src/lib/database.ts
|
|
1253
|
+
import { createClient } from "@libsql/client";
|
|
1254
|
+
function getClient() {
|
|
1255
|
+
if (!_resilientClient) {
|
|
1256
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
1257
|
+
}
|
|
1258
|
+
return _resilientClient;
|
|
1259
|
+
}
|
|
1260
|
+
var _resilientClient;
|
|
1261
|
+
var init_database = __esm({
|
|
1262
|
+
"src/lib/database.ts"() {
|
|
1263
|
+
"use strict";
|
|
1264
|
+
init_db_retry();
|
|
1265
|
+
_resilientClient = null;
|
|
1266
|
+
}
|
|
1267
|
+
});
|
|
1268
|
+
|
|
1269
|
+
// src/lib/compress.ts
|
|
1270
|
+
import { brotliCompressSync, brotliDecompressSync, constants } from "zlib";
|
|
1271
|
+
function compress(input) {
|
|
1272
|
+
if (input.length === 0) return Buffer.alloc(0);
|
|
1273
|
+
return brotliCompressSync(input, {
|
|
1274
|
+
params: {
|
|
1275
|
+
[constants.BROTLI_PARAM_QUALITY]: 4
|
|
1276
|
+
}
|
|
1277
|
+
});
|
|
1278
|
+
}
|
|
1279
|
+
function decompress(input) {
|
|
1280
|
+
if (input.length === 0) return Buffer.alloc(0);
|
|
1281
|
+
return brotliDecompressSync(input);
|
|
1282
|
+
}
|
|
1283
|
+
var init_compress = __esm({
|
|
1284
|
+
"src/lib/compress.ts"() {
|
|
1285
|
+
"use strict";
|
|
1286
|
+
}
|
|
1287
|
+
});
|
|
1288
|
+
|
|
1071
1289
|
// src/lib/employees.ts
|
|
1072
1290
|
var employees_exports = {};
|
|
1073
1291
|
__export(employees_exports, {
|
|
@@ -1205,27 +1423,989 @@ var init_employees = __esm({
|
|
|
1205
1423
|
}
|
|
1206
1424
|
});
|
|
1207
1425
|
|
|
1208
|
-
// src/lib/
|
|
1209
|
-
var
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1426
|
+
// src/lib/cloud-sync.ts
|
|
1427
|
+
var cloud_sync_exports = {};
|
|
1428
|
+
__export(cloud_sync_exports, {
|
|
1429
|
+
assertSecureEndpoint: () => assertSecureEndpoint,
|
|
1430
|
+
buildRosterBlob: () => buildRosterBlob,
|
|
1431
|
+
cloudPull: () => cloudPull,
|
|
1432
|
+
cloudPullBehaviors: () => cloudPullBehaviors,
|
|
1433
|
+
cloudPullBlob: () => cloudPullBlob,
|
|
1434
|
+
cloudPullConversations: () => cloudPullConversations,
|
|
1435
|
+
cloudPullDocuments: () => cloudPullDocuments,
|
|
1436
|
+
cloudPullGlobalProcedures: () => cloudPullGlobalProcedures,
|
|
1437
|
+
cloudPullGraphRAG: () => cloudPullGraphRAG,
|
|
1438
|
+
cloudPullRoster: () => cloudPullRoster,
|
|
1439
|
+
cloudPullTasks: () => cloudPullTasks,
|
|
1440
|
+
cloudPush: () => cloudPush,
|
|
1441
|
+
cloudPushBehaviors: () => cloudPushBehaviors,
|
|
1442
|
+
cloudPushBlob: () => cloudPushBlob,
|
|
1443
|
+
cloudPushConversations: () => cloudPushConversations,
|
|
1444
|
+
cloudPushDocuments: () => cloudPushDocuments,
|
|
1445
|
+
cloudPushGlobalProcedures: () => cloudPushGlobalProcedures,
|
|
1446
|
+
cloudPushGraphRAG: () => cloudPushGraphRAG,
|
|
1447
|
+
cloudPushRoster: () => cloudPushRoster,
|
|
1448
|
+
cloudPushTasks: () => cloudPushTasks,
|
|
1449
|
+
cloudSync: () => cloudSync,
|
|
1450
|
+
mergeConfig: () => mergeConfig,
|
|
1451
|
+
mergeRosterFromRemote: () => mergeRosterFromRemote,
|
|
1452
|
+
recordRosterDeletion: () => recordRosterDeletion
|
|
1213
1453
|
});
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
import
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1454
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, existsSync as existsSync7, readdirSync, mkdirSync as mkdirSync2, appendFileSync, unlinkSync as unlinkSync3, openSync as openSync2, closeSync as closeSync2 } from "fs";
|
|
1455
|
+
import crypto2 from "crypto";
|
|
1456
|
+
import path7 from "path";
|
|
1457
|
+
import { homedir } from "os";
|
|
1458
|
+
function sqlSafe(v) {
|
|
1459
|
+
return v === void 0 ? null : v;
|
|
1460
|
+
}
|
|
1461
|
+
function logError(msg) {
|
|
1462
|
+
try {
|
|
1463
|
+
const logPath = path7.join(homedir(), ".exe-os", "workers.log");
|
|
1464
|
+
appendFileSync(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
|
|
1465
|
+
`);
|
|
1466
|
+
} catch {
|
|
1220
1467
|
}
|
|
1221
|
-
return _resilientClient;
|
|
1222
1468
|
}
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1469
|
+
async function withRosterLock(fn) {
|
|
1470
|
+
try {
|
|
1471
|
+
const fd = openSync2(ROSTER_LOCK_PATH, "wx");
|
|
1472
|
+
closeSync2(fd);
|
|
1473
|
+
writeFileSync2(ROSTER_LOCK_PATH, String(Date.now()));
|
|
1474
|
+
} catch (err) {
|
|
1475
|
+
if (err.code === "EEXIST") {
|
|
1476
|
+
try {
|
|
1477
|
+
const ts = parseInt(readFileSync5(ROSTER_LOCK_PATH, "utf-8"), 10);
|
|
1478
|
+
if (Date.now() - ts < LOCK_STALE_MS) {
|
|
1479
|
+
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
1480
|
+
}
|
|
1481
|
+
unlinkSync3(ROSTER_LOCK_PATH);
|
|
1482
|
+
const fd = openSync2(ROSTER_LOCK_PATH, "wx");
|
|
1483
|
+
closeSync2(fd);
|
|
1484
|
+
writeFileSync2(ROSTER_LOCK_PATH, String(Date.now()));
|
|
1485
|
+
} catch (retryErr) {
|
|
1486
|
+
if (retryErr instanceof Error && retryErr.message.includes("already in progress")) throw retryErr;
|
|
1487
|
+
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
1488
|
+
}
|
|
1489
|
+
} else {
|
|
1490
|
+
throw err;
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
try {
|
|
1494
|
+
return await fn();
|
|
1495
|
+
} finally {
|
|
1496
|
+
try {
|
|
1497
|
+
unlinkSync3(ROSTER_LOCK_PATH);
|
|
1498
|
+
} catch {
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
async function fetchWithRetry(url, init) {
|
|
1503
|
+
const MAX_RETRIES = 3;
|
|
1504
|
+
const BASE_DELAY_MS = 200;
|
|
1505
|
+
let lastError;
|
|
1506
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
1507
|
+
try {
|
|
1508
|
+
const signal = AbortSignal.timeout(FETCH_TIMEOUT_MS);
|
|
1509
|
+
const resp = await fetch(url, { ...init, signal });
|
|
1510
|
+
if (resp && resp.status >= 500 && attempt < MAX_RETRIES) {
|
|
1511
|
+
await new Promise((r) => setTimeout(r, BASE_DELAY_MS * Math.pow(2, attempt)));
|
|
1512
|
+
continue;
|
|
1513
|
+
}
|
|
1514
|
+
return resp;
|
|
1515
|
+
} catch (err) {
|
|
1516
|
+
lastError = err;
|
|
1517
|
+
if (attempt === MAX_RETRIES) throw err;
|
|
1518
|
+
await new Promise((r) => setTimeout(r, BASE_DELAY_MS * Math.pow(2, attempt)));
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
throw lastError;
|
|
1522
|
+
}
|
|
1523
|
+
function assertSecureEndpoint(endpoint) {
|
|
1524
|
+
if (endpoint.startsWith("https://")) return;
|
|
1525
|
+
if (endpoint.startsWith("http://")) {
|
|
1526
|
+
try {
|
|
1527
|
+
const parsed = new URL(endpoint);
|
|
1528
|
+
if (LOCALHOST_PATTERNS.test(parsed.hostname)) return;
|
|
1529
|
+
} catch {
|
|
1530
|
+
return;
|
|
1531
|
+
}
|
|
1532
|
+
throw new Error(
|
|
1533
|
+
`Insecure cloud endpoint rejected: "${endpoint}". Use https:// for remote hosts. Plain http:// is only allowed for localhost.`
|
|
1534
|
+
);
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
async function cloudPush(records, maxVersion, config) {
|
|
1538
|
+
if (records.length === 0) return true;
|
|
1539
|
+
assertSecureEndpoint(config.endpoint);
|
|
1540
|
+
try {
|
|
1541
|
+
const json = JSON.stringify(records);
|
|
1542
|
+
const compressed = compress(Buffer.from(json, "utf8"));
|
|
1543
|
+
const blob = encryptSyncBlob(compressed);
|
|
1544
|
+
const resp = await fetchWithRetry(`${config.endpoint}/sync/push`, {
|
|
1545
|
+
method: "POST",
|
|
1546
|
+
headers: {
|
|
1547
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
1548
|
+
"Content-Type": "application/json",
|
|
1549
|
+
"X-Device-Id": loadDeviceId(),
|
|
1550
|
+
"X-Expected-Version": String(maxVersion)
|
|
1551
|
+
},
|
|
1552
|
+
body: JSON.stringify({ version: maxVersion, blob })
|
|
1553
|
+
});
|
|
1554
|
+
if (resp == null) {
|
|
1555
|
+
logError("[cloud-sync] PUSH FAILED: no response from server");
|
|
1556
|
+
return false;
|
|
1557
|
+
}
|
|
1558
|
+
if (resp.status === 409) {
|
|
1559
|
+
logError("[cloud-sync] PUSH VERSION CONFLICT \u2014 re-pull required before next push");
|
|
1560
|
+
return false;
|
|
1561
|
+
}
|
|
1562
|
+
return resp.ok;
|
|
1563
|
+
} catch (err) {
|
|
1564
|
+
logError(`[cloud-sync] PUSH FAILED: ${err instanceof Error ? err.message : String(err)}`);
|
|
1565
|
+
return false;
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
async function cloudPull(sinceVersion, config) {
|
|
1569
|
+
assertSecureEndpoint(config.endpoint);
|
|
1570
|
+
try {
|
|
1571
|
+
const response = await fetchWithRetry(`${config.endpoint}/sync/pull`, {
|
|
1572
|
+
method: "POST",
|
|
1573
|
+
headers: {
|
|
1574
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
1575
|
+
"Content-Type": "application/json",
|
|
1576
|
+
"X-Device-Id": loadDeviceId()
|
|
1577
|
+
},
|
|
1578
|
+
body: JSON.stringify({ since_version: sinceVersion })
|
|
1579
|
+
});
|
|
1580
|
+
if (response == null) {
|
|
1581
|
+
logError("[cloud-sync] PULL FAILED: no response from server");
|
|
1582
|
+
return { records: [], maxVersion: sinceVersion };
|
|
1583
|
+
}
|
|
1584
|
+
if (!response.ok) return { records: [], maxVersion: sinceVersion };
|
|
1585
|
+
const data = await response.json();
|
|
1586
|
+
const allRecords = [];
|
|
1587
|
+
for (const { blob } of data.blobs ?? []) {
|
|
1588
|
+
try {
|
|
1589
|
+
const compressed = decryptSyncBlob(blob);
|
|
1590
|
+
const json = decompress(compressed).toString("utf8");
|
|
1591
|
+
const records = JSON.parse(json);
|
|
1592
|
+
allRecords.push(...records);
|
|
1593
|
+
} catch {
|
|
1594
|
+
continue;
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
return { records: allRecords, maxVersion: data.max_version ?? sinceVersion };
|
|
1598
|
+
} catch (err) {
|
|
1599
|
+
logError(`[cloud-sync] PULL FAILED: ${err instanceof Error ? err.message : String(err)}`);
|
|
1600
|
+
return { records: [], maxVersion: sinceVersion };
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
async function cloudSync(config) {
|
|
1604
|
+
let client;
|
|
1605
|
+
try {
|
|
1606
|
+
client = getClient();
|
|
1607
|
+
} catch {
|
|
1608
|
+
throw new Error("[cloud-sync] Database not initialized. Call initStore() before cloudSync().");
|
|
1609
|
+
}
|
|
1610
|
+
try {
|
|
1611
|
+
await client.execute(
|
|
1612
|
+
"CREATE TABLE IF NOT EXISTS sync_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)"
|
|
1613
|
+
);
|
|
1614
|
+
} catch (e) {
|
|
1615
|
+
logError(`[cloud-sync] sync_meta CREATE failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
1616
|
+
}
|
|
1617
|
+
const pullMeta = await client.execute(
|
|
1618
|
+
"SELECT value FROM sync_meta WHERE key = 'last_cloud_pull_version'"
|
|
1619
|
+
);
|
|
1620
|
+
const lastPullVersion = pullMeta.rows.length > 0 ? Number(pullMeta.rows[0].value) : 0;
|
|
1621
|
+
const pullResult = await cloudPull(lastPullVersion, config);
|
|
1622
|
+
let pulled = 0;
|
|
1623
|
+
if (pullResult.records.length > 0) {
|
|
1624
|
+
const stmts = pullResult.records.map((rec) => ({
|
|
1625
|
+
sql: `INSERT OR REPLACE INTO memories
|
|
1626
|
+
(id, agent_id, agent_role, session_id, timestamp,
|
|
1627
|
+
tool_name, project_name, has_error, raw_text, version,
|
|
1628
|
+
author_device_id, scope)
|
|
1629
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1630
|
+
args: [
|
|
1631
|
+
sqlSafe(rec.id),
|
|
1632
|
+
sqlSafe(rec.agent_id),
|
|
1633
|
+
sqlSafe(rec.agent_role),
|
|
1634
|
+
sqlSafe(rec.session_id),
|
|
1635
|
+
sqlSafe(rec.timestamp),
|
|
1636
|
+
sqlSafe(rec.tool_name),
|
|
1637
|
+
sqlSafe(rec.project_name),
|
|
1638
|
+
sqlSafe(rec.has_error ?? 0),
|
|
1639
|
+
sqlSafe(rec.raw_text ?? ""),
|
|
1640
|
+
sqlSafe(rec.version ?? 0),
|
|
1641
|
+
sqlSafe(rec.author_device_id),
|
|
1642
|
+
sqlSafe(rec.scope ?? "business")
|
|
1643
|
+
]
|
|
1644
|
+
}));
|
|
1645
|
+
await client.batch(stmts, "write");
|
|
1646
|
+
pulled = pullResult.records.length;
|
|
1647
|
+
}
|
|
1648
|
+
if (pullResult.maxVersion > lastPullVersion) {
|
|
1649
|
+
await client.execute({
|
|
1650
|
+
sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_cloud_pull_version', ?)",
|
|
1651
|
+
args: [String(pullResult.maxVersion)]
|
|
1652
|
+
});
|
|
1653
|
+
}
|
|
1654
|
+
const pushMeta = await client.execute(
|
|
1655
|
+
"SELECT value FROM sync_meta WHERE key = 'last_cloud_push_version'"
|
|
1656
|
+
);
|
|
1657
|
+
const lastPushVersion = pushMeta.rows.length > 0 ? Number(pushMeta.rows[0].value) : 0;
|
|
1658
|
+
let pushed = 0;
|
|
1659
|
+
let batchCursor = lastPushVersion;
|
|
1660
|
+
while (true) {
|
|
1661
|
+
const recordsResult = await client.execute({
|
|
1662
|
+
sql: `SELECT id, agent_id, agent_role, session_id, timestamp,
|
|
1663
|
+
tool_name, project_name, has_error, raw_text, version,
|
|
1664
|
+
author_device_id, scope
|
|
1665
|
+
FROM memories
|
|
1666
|
+
WHERE version > ?
|
|
1667
|
+
AND (scope IS NULL OR scope != 'personal')
|
|
1668
|
+
ORDER BY version ASC
|
|
1669
|
+
LIMIT ?`,
|
|
1670
|
+
args: [batchCursor, PUSH_BATCH_SIZE]
|
|
1671
|
+
});
|
|
1672
|
+
if (recordsResult.rows.length === 0) break;
|
|
1673
|
+
const records = recordsResult.rows.map((row) => ({
|
|
1674
|
+
id: row.id,
|
|
1675
|
+
agent_id: row.agent_id,
|
|
1676
|
+
agent_role: row.agent_role,
|
|
1677
|
+
session_id: row.session_id,
|
|
1678
|
+
timestamp: row.timestamp,
|
|
1679
|
+
tool_name: row.tool_name,
|
|
1680
|
+
project_name: row.project_name,
|
|
1681
|
+
has_error: row.has_error,
|
|
1682
|
+
raw_text: row.raw_text,
|
|
1683
|
+
version: row.version,
|
|
1684
|
+
author_device_id: row.author_device_id,
|
|
1685
|
+
scope: row.scope
|
|
1686
|
+
}));
|
|
1687
|
+
const maxVersion = Number(records[records.length - 1].version);
|
|
1688
|
+
const pushOk = await cloudPush(records, maxVersion, config);
|
|
1689
|
+
if (!pushOk) break;
|
|
1690
|
+
await client.execute({
|
|
1691
|
+
sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_cloud_push_version', ?)",
|
|
1692
|
+
args: [String(maxVersion)]
|
|
1693
|
+
});
|
|
1694
|
+
pushed += records.length;
|
|
1695
|
+
batchCursor = maxVersion;
|
|
1696
|
+
if (recordsResult.rows.length < PUSH_BATCH_SIZE) break;
|
|
1697
|
+
}
|
|
1698
|
+
try {
|
|
1699
|
+
await cloudPushRoster(config);
|
|
1700
|
+
} catch (err) {
|
|
1701
|
+
logError(`[cloud-sync] Roster push: ${err instanceof Error ? err.message : String(err)}`);
|
|
1702
|
+
}
|
|
1703
|
+
try {
|
|
1704
|
+
await cloudPullRoster(config);
|
|
1705
|
+
} catch (err) {
|
|
1706
|
+
logError(`[cloud-sync] Roster pull: ${err instanceof Error ? err.message : String(err)}`);
|
|
1707
|
+
}
|
|
1708
|
+
try {
|
|
1709
|
+
await cloudPushGlobalProcedures(config);
|
|
1710
|
+
} catch (err) {
|
|
1711
|
+
logError(`[cloud-sync] Global procedures push: ${err instanceof Error ? err.message : String(err)}`);
|
|
1712
|
+
}
|
|
1713
|
+
try {
|
|
1714
|
+
await cloudPullGlobalProcedures(config);
|
|
1715
|
+
} catch (err) {
|
|
1716
|
+
logError(`[cloud-sync] Global procedures pull: ${err instanceof Error ? err.message : String(err)}`);
|
|
1717
|
+
}
|
|
1718
|
+
let behaviorsResult = { pushed: false, pulled: 0 };
|
|
1719
|
+
try {
|
|
1720
|
+
behaviorsResult.pushed = await cloudPushBehaviors(config);
|
|
1721
|
+
} catch (err) {
|
|
1722
|
+
logError(`[cloud-sync] Behaviors push: ${err instanceof Error ? err.message : String(err)}`);
|
|
1723
|
+
}
|
|
1724
|
+
try {
|
|
1725
|
+
const pullResult2 = await cloudPullBehaviors(config);
|
|
1726
|
+
behaviorsResult.pulled = pullResult2.pulled;
|
|
1727
|
+
} catch (err) {
|
|
1728
|
+
logError(`[cloud-sync] Behaviors pull: ${err instanceof Error ? err.message : String(err)}`);
|
|
1729
|
+
}
|
|
1730
|
+
let graphragResult = { pushed: false, pulled: 0 };
|
|
1731
|
+
try {
|
|
1732
|
+
graphragResult.pushed = await cloudPushGraphRAG(config);
|
|
1733
|
+
} catch (err) {
|
|
1734
|
+
logError(`[cloud-sync] GraphRAG push: ${err instanceof Error ? err.message : String(err)}`);
|
|
1735
|
+
}
|
|
1736
|
+
try {
|
|
1737
|
+
const pullResult2 = await cloudPullGraphRAG(config);
|
|
1738
|
+
graphragResult.pulled = pullResult2.pulled;
|
|
1739
|
+
} catch (err) {
|
|
1740
|
+
logError(`[cloud-sync] GraphRAG pull: ${err instanceof Error ? err.message : String(err)}`);
|
|
1741
|
+
}
|
|
1742
|
+
let tasksResult = { pushed: false, pulled: 0 };
|
|
1743
|
+
try {
|
|
1744
|
+
tasksResult.pushed = await cloudPushTasks(config);
|
|
1745
|
+
} catch (err) {
|
|
1746
|
+
logError(`[cloud-sync] Tasks push: ${err instanceof Error ? err.message : String(err)}`);
|
|
1747
|
+
}
|
|
1748
|
+
try {
|
|
1749
|
+
const pullResult2 = await cloudPullTasks(config);
|
|
1750
|
+
tasksResult.pulled = pullResult2.pulled;
|
|
1751
|
+
} catch (err) {
|
|
1752
|
+
logError(`[cloud-sync] Tasks pull: ${err instanceof Error ? err.message : String(err)}`);
|
|
1753
|
+
}
|
|
1754
|
+
let conversationsResult = { pushed: false, pulled: 0 };
|
|
1755
|
+
try {
|
|
1756
|
+
conversationsResult.pushed = await cloudPushConversations(config);
|
|
1757
|
+
} catch (err) {
|
|
1758
|
+
logError(`[cloud-sync] Conversations push: ${err instanceof Error ? err.message : String(err)}`);
|
|
1759
|
+
}
|
|
1760
|
+
try {
|
|
1761
|
+
const pullResult2 = await cloudPullConversations(config);
|
|
1762
|
+
conversationsResult.pulled = pullResult2.pulled;
|
|
1763
|
+
} catch (err) {
|
|
1764
|
+
logError(`[cloud-sync] Conversations pull: ${err instanceof Error ? err.message : String(err)}`);
|
|
1765
|
+
}
|
|
1766
|
+
let documentsResult = { pushed: false, pulled: 0 };
|
|
1767
|
+
try {
|
|
1768
|
+
documentsResult.pushed = await cloudPushDocuments(config);
|
|
1769
|
+
} catch (err) {
|
|
1770
|
+
logError(`[cloud-sync] Documents push: ${err instanceof Error ? err.message : String(err)}`);
|
|
1771
|
+
}
|
|
1772
|
+
try {
|
|
1773
|
+
const pullResult2 = await cloudPullDocuments(config);
|
|
1774
|
+
documentsResult.pulled = pullResult2.pulled;
|
|
1775
|
+
} catch (err) {
|
|
1776
|
+
logError(`[cloud-sync] Documents pull: ${err instanceof Error ? err.message : String(err)}`);
|
|
1777
|
+
}
|
|
1778
|
+
return {
|
|
1779
|
+
pushed,
|
|
1780
|
+
pulled,
|
|
1781
|
+
behaviors: behaviorsResult,
|
|
1782
|
+
graphrag: graphragResult,
|
|
1783
|
+
tasks: tasksResult,
|
|
1784
|
+
conversations: conversationsResult,
|
|
1785
|
+
documents: documentsResult
|
|
1786
|
+
};
|
|
1787
|
+
}
|
|
1788
|
+
function recordRosterDeletion(name) {
|
|
1789
|
+
let deletions = [];
|
|
1790
|
+
try {
|
|
1791
|
+
if (existsSync7(ROSTER_DELETIONS_PATH)) {
|
|
1792
|
+
deletions = JSON.parse(readFileSync5(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
1793
|
+
}
|
|
1794
|
+
} catch {
|
|
1795
|
+
}
|
|
1796
|
+
if (!deletions.includes(name)) deletions.push(name);
|
|
1797
|
+
writeFileSync2(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
|
|
1798
|
+
}
|
|
1799
|
+
function consumeRosterDeletions() {
|
|
1800
|
+
try {
|
|
1801
|
+
if (!existsSync7(ROSTER_DELETIONS_PATH)) return [];
|
|
1802
|
+
const deletions = JSON.parse(readFileSync5(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
1803
|
+
writeFileSync2(ROSTER_DELETIONS_PATH, "[]");
|
|
1804
|
+
return deletions;
|
|
1805
|
+
} catch {
|
|
1806
|
+
return [];
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
function buildRosterBlob(paths) {
|
|
1810
|
+
const rosterPath = paths?.rosterPath ?? path7.join(EXE_AI_DIR, "exe-employees.json");
|
|
1811
|
+
const identityDir = paths?.identityDir ?? path7.join(EXE_AI_DIR, "identity");
|
|
1812
|
+
const configPath = paths?.configPath ?? path7.join(EXE_AI_DIR, "config.json");
|
|
1813
|
+
let roster = [];
|
|
1814
|
+
if (existsSync7(rosterPath)) {
|
|
1815
|
+
try {
|
|
1816
|
+
roster = JSON.parse(readFileSync5(rosterPath, "utf-8"));
|
|
1817
|
+
} catch {
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
const identities = {};
|
|
1821
|
+
if (existsSync7(identityDir)) {
|
|
1822
|
+
for (const file of readdirSync(identityDir).filter((f) => f.endsWith(".md"))) {
|
|
1823
|
+
try {
|
|
1824
|
+
identities[file] = readFileSync5(path7.join(identityDir, file), "utf-8");
|
|
1825
|
+
} catch {
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
let config;
|
|
1830
|
+
if (existsSync7(configPath)) {
|
|
1831
|
+
try {
|
|
1832
|
+
config = JSON.parse(readFileSync5(configPath, "utf-8"));
|
|
1833
|
+
} catch {
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
const deletedNames = consumeRosterDeletions();
|
|
1837
|
+
const content = JSON.stringify({ roster, identities, config, deletedNames });
|
|
1838
|
+
const hash = crypto2.createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
1839
|
+
return { roster, identities, config, deletedNames, version: hash };
|
|
1840
|
+
}
|
|
1841
|
+
async function cloudPushRoster(config) {
|
|
1842
|
+
assertSecureEndpoint(config.endpoint);
|
|
1843
|
+
const blob = buildRosterBlob();
|
|
1844
|
+
if (blob.roster.length === 0) return true;
|
|
1845
|
+
try {
|
|
1846
|
+
const client = getClient();
|
|
1847
|
+
const meta = await client.execute(
|
|
1848
|
+
"SELECT value FROM sync_meta WHERE key = 'last_roster_push_version'"
|
|
1849
|
+
);
|
|
1850
|
+
const lastVersion = meta.rows.length > 0 ? Number(meta.rows[0].value) : 0;
|
|
1851
|
+
if (blob.version === lastVersion) return true;
|
|
1852
|
+
} catch {
|
|
1853
|
+
}
|
|
1854
|
+
try {
|
|
1855
|
+
const json = JSON.stringify(blob);
|
|
1856
|
+
const compressed = compress(Buffer.from(json, "utf8"));
|
|
1857
|
+
const encrypted = encryptSyncBlob(compressed);
|
|
1858
|
+
const resp = await fetchWithRetry(`${config.endpoint}/sync/push-roster`, {
|
|
1859
|
+
method: "POST",
|
|
1860
|
+
headers: {
|
|
1861
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
1862
|
+
"Content-Type": "application/json",
|
|
1863
|
+
"X-Device-Id": loadDeviceId()
|
|
1864
|
+
},
|
|
1865
|
+
body: JSON.stringify({ blob: encrypted })
|
|
1866
|
+
});
|
|
1867
|
+
if (resp.ok) {
|
|
1868
|
+
try {
|
|
1869
|
+
const client = getClient();
|
|
1870
|
+
await client.execute({
|
|
1871
|
+
sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_roster_push_version', ?)",
|
|
1872
|
+
args: [String(blob.version)]
|
|
1873
|
+
});
|
|
1874
|
+
} catch {
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
return resp.ok;
|
|
1878
|
+
} catch (err) {
|
|
1879
|
+
process.stderr.write(`[cloud-sync] ROSTER PUSH FAILED: ${err instanceof Error ? err.message : String(err)}
|
|
1880
|
+
`);
|
|
1881
|
+
return false;
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
async function cloudPullRoster(config) {
|
|
1885
|
+
assertSecureEndpoint(config.endpoint);
|
|
1886
|
+
try {
|
|
1887
|
+
const resp = await fetchWithRetry(`${config.endpoint}/sync/pull-roster`, {
|
|
1888
|
+
method: "GET",
|
|
1889
|
+
headers: {
|
|
1890
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
1891
|
+
"X-Device-Id": loadDeviceId()
|
|
1892
|
+
}
|
|
1893
|
+
});
|
|
1894
|
+
if (!resp.ok) return { added: 0 };
|
|
1895
|
+
const data = await resp.json();
|
|
1896
|
+
if (!data.blob) return { added: 0 };
|
|
1897
|
+
const compressed = decryptSyncBlob(data.blob);
|
|
1898
|
+
const json = decompress(compressed).toString("utf8");
|
|
1899
|
+
const remote = JSON.parse(json);
|
|
1900
|
+
return mergeRosterFromRemote(remote);
|
|
1901
|
+
} catch (err) {
|
|
1902
|
+
process.stderr.write(`[cloud-sync] ROSTER PULL FAILED: ${err instanceof Error ? err.message : String(err)}
|
|
1903
|
+
`);
|
|
1904
|
+
return { added: 0 };
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
function mergeConfig(remoteConfig, configPath) {
|
|
1908
|
+
const cfgPath = configPath ?? path7.join(EXE_AI_DIR, "config.json");
|
|
1909
|
+
let local = {};
|
|
1910
|
+
if (existsSync7(cfgPath)) {
|
|
1911
|
+
try {
|
|
1912
|
+
local = JSON.parse(readFileSync5(cfgPath, "utf-8"));
|
|
1913
|
+
} catch {
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
const merged = { ...remoteConfig, ...local };
|
|
1917
|
+
const dir = path7.dirname(cfgPath);
|
|
1918
|
+
if (!existsSync7(dir)) mkdirSync2(dir, { recursive: true });
|
|
1919
|
+
writeFileSync2(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
1920
|
+
}
|
|
1921
|
+
async function mergeRosterFromRemote(remote, paths) {
|
|
1922
|
+
return withRosterLock(async () => {
|
|
1923
|
+
const rosterPath = paths?.rosterPath ?? void 0;
|
|
1924
|
+
const identityDir = paths?.identityDir ?? path7.join(EXE_AI_DIR, "identity");
|
|
1925
|
+
const localEmployees = await loadEmployees(rosterPath);
|
|
1926
|
+
const localNames = new Set(localEmployees.map((e) => e.name));
|
|
1927
|
+
let added = 0;
|
|
1928
|
+
let identitiesUpdated = 0;
|
|
1929
|
+
for (const remoteEmp of remote.roster) {
|
|
1930
|
+
if (!localNames.has(remoteEmp.name)) {
|
|
1931
|
+
localEmployees.push(remoteEmp);
|
|
1932
|
+
localNames.add(remoteEmp.name);
|
|
1933
|
+
added++;
|
|
1934
|
+
try {
|
|
1935
|
+
registerBinSymlinks(remoteEmp.name);
|
|
1936
|
+
} catch {
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
const remoteIdentity = remote.identities[`${remoteEmp.name}.md`];
|
|
1940
|
+
if (remoteIdentity) {
|
|
1941
|
+
if (!existsSync7(identityDir)) mkdirSync2(identityDir, { recursive: true });
|
|
1942
|
+
const idPath = path7.join(identityDir, `${remoteEmp.name}.md`);
|
|
1943
|
+
let localIdentity = null;
|
|
1944
|
+
try {
|
|
1945
|
+
localIdentity = existsSync7(idPath) ? readFileSync5(idPath, "utf-8") : null;
|
|
1946
|
+
} catch {
|
|
1947
|
+
}
|
|
1948
|
+
if (localIdentity !== remoteIdentity) {
|
|
1949
|
+
writeFileSync2(idPath, remoteIdentity, "utf-8");
|
|
1950
|
+
identitiesUpdated++;
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
let removed = 0;
|
|
1955
|
+
if (remote.deletedNames && remote.deletedNames.length > 0) {
|
|
1956
|
+
const toRemove = new Set(remote.deletedNames);
|
|
1957
|
+
const filtered = localEmployees.filter((e) => !toRemove.has(e.name));
|
|
1958
|
+
removed = localEmployees.length - filtered.length;
|
|
1959
|
+
if (removed > 0) {
|
|
1960
|
+
localEmployees.length = 0;
|
|
1961
|
+
localEmployees.push(...filtered);
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
if (added > 0 || removed > 0) {
|
|
1965
|
+
await saveEmployees(localEmployees, rosterPath);
|
|
1966
|
+
}
|
|
1967
|
+
if (remote.config && Object.keys(remote.config).length > 0) {
|
|
1968
|
+
try {
|
|
1969
|
+
mergeConfig(remote.config, paths?.configPath);
|
|
1970
|
+
} catch {
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
return { added, identitiesUpdated };
|
|
1974
|
+
});
|
|
1975
|
+
}
|
|
1976
|
+
async function cloudPushBlob(route, data, metaKey, config) {
|
|
1977
|
+
if (data.length === 0) return { ok: true };
|
|
1978
|
+
assertSecureEndpoint(config.endpoint);
|
|
1979
|
+
const json = JSON.stringify(data);
|
|
1980
|
+
const version = Buffer.from(json).length;
|
|
1981
|
+
try {
|
|
1982
|
+
const client = getClient();
|
|
1983
|
+
const meta = await client.execute({
|
|
1984
|
+
sql: "SELECT value FROM sync_meta WHERE key = ?",
|
|
1985
|
+
args: [metaKey]
|
|
1986
|
+
});
|
|
1987
|
+
const lastVersion = meta.rows.length > 0 ? Number(meta.rows[0].value) : 0;
|
|
1988
|
+
if (version === lastVersion) return { ok: true };
|
|
1989
|
+
} catch {
|
|
1990
|
+
}
|
|
1991
|
+
try {
|
|
1992
|
+
const compressed = compress(Buffer.from(json, "utf8"));
|
|
1993
|
+
const encrypted = encryptSyncBlob(compressed);
|
|
1994
|
+
const resp = await fetchWithRetry(`${config.endpoint}${route}`, {
|
|
1995
|
+
method: "POST",
|
|
1996
|
+
headers: {
|
|
1997
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
1998
|
+
"Content-Type": "application/json",
|
|
1999
|
+
"X-Device-Id": loadDeviceId()
|
|
2000
|
+
},
|
|
2001
|
+
body: JSON.stringify({ blob: encrypted })
|
|
2002
|
+
});
|
|
2003
|
+
if (resp.ok) {
|
|
2004
|
+
try {
|
|
2005
|
+
const client = getClient();
|
|
2006
|
+
await client.execute({
|
|
2007
|
+
sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES (?, ?)",
|
|
2008
|
+
args: [metaKey, String(version)]
|
|
2009
|
+
});
|
|
2010
|
+
} catch {
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
return { ok: resp.ok };
|
|
2014
|
+
} catch (err) {
|
|
2015
|
+
logError(`[cloud-sync] PUSH ${route}: ${err instanceof Error ? err.message : String(err)}`);
|
|
2016
|
+
return { ok: false };
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
async function cloudPullBlob(route, config) {
|
|
2020
|
+
assertSecureEndpoint(config.endpoint);
|
|
2021
|
+
try {
|
|
2022
|
+
const resp = await fetchWithRetry(`${config.endpoint}${route}`, {
|
|
2023
|
+
method: "GET",
|
|
2024
|
+
headers: {
|
|
2025
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
2026
|
+
"X-Device-Id": loadDeviceId()
|
|
2027
|
+
}
|
|
2028
|
+
});
|
|
2029
|
+
if (!resp.ok) return null;
|
|
2030
|
+
const data = await resp.json();
|
|
2031
|
+
if (!data.blob) return null;
|
|
2032
|
+
const compressed = decryptSyncBlob(data.blob);
|
|
2033
|
+
const json = decompress(compressed).toString("utf8");
|
|
2034
|
+
return JSON.parse(json);
|
|
2035
|
+
} catch (err) {
|
|
2036
|
+
logError(`[cloud-sync] PULL ${route}: ${err instanceof Error ? err.message : String(err)}`);
|
|
2037
|
+
return null;
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
async function cloudPushGlobalProcedures(config) {
|
|
2041
|
+
const client = getClient();
|
|
2042
|
+
const result = await client.execute("SELECT * FROM global_procedures LIMIT 1000");
|
|
2043
|
+
const rows = result.rows;
|
|
2044
|
+
const { ok } = await cloudPushBlob(
|
|
2045
|
+
"/sync/push-global-procedures",
|
|
2046
|
+
rows,
|
|
2047
|
+
"last_global_procedures_push_version",
|
|
2048
|
+
config
|
|
2049
|
+
);
|
|
2050
|
+
return ok;
|
|
2051
|
+
}
|
|
2052
|
+
async function cloudPullGlobalProcedures(config) {
|
|
2053
|
+
const remoteProcs = await cloudPullBlob(
|
|
2054
|
+
"/sync/pull-global-procedures",
|
|
2055
|
+
config
|
|
2056
|
+
);
|
|
2057
|
+
if (!remoteProcs || remoteProcs.length === 0) return { pulled: 0 };
|
|
2058
|
+
const client = getClient();
|
|
2059
|
+
const stmts = remoteProcs.map((p) => ({
|
|
2060
|
+
sql: `INSERT INTO global_procedures
|
|
2061
|
+
(id, title, content, priority, domain, active, created_at, updated_at)
|
|
2062
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
2063
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
2064
|
+
title = excluded.title,
|
|
2065
|
+
content = excluded.content,
|
|
2066
|
+
priority = excluded.priority,
|
|
2067
|
+
domain = excluded.domain,
|
|
2068
|
+
active = excluded.active,
|
|
2069
|
+
updated_at = excluded.updated_at
|
|
2070
|
+
WHERE excluded.updated_at > global_procedures.updated_at`,
|
|
2071
|
+
args: [
|
|
2072
|
+
sqlSafe(p.id),
|
|
2073
|
+
sqlSafe(p.title),
|
|
2074
|
+
sqlSafe(p.content),
|
|
2075
|
+
sqlSafe(p.priority ?? "p0"),
|
|
2076
|
+
sqlSafe(p.domain),
|
|
2077
|
+
sqlSafe(p.active ?? 1),
|
|
2078
|
+
sqlSafe(p.created_at),
|
|
2079
|
+
sqlSafe(p.updated_at)
|
|
2080
|
+
]
|
|
2081
|
+
}));
|
|
2082
|
+
await client.batch(stmts, "write");
|
|
2083
|
+
return { pulled: remoteProcs.length };
|
|
2084
|
+
}
|
|
2085
|
+
async function cloudPushBehaviors(config) {
|
|
2086
|
+
const client = getClient();
|
|
2087
|
+
const result = await client.execute("SELECT * FROM behaviors LIMIT 10000");
|
|
2088
|
+
const rows = result.rows;
|
|
2089
|
+
const { ok } = await cloudPushBlob(
|
|
2090
|
+
"/sync/push-behaviors",
|
|
2091
|
+
rows,
|
|
2092
|
+
"last_behaviors_push_version",
|
|
2093
|
+
config
|
|
2094
|
+
);
|
|
2095
|
+
return ok;
|
|
2096
|
+
}
|
|
2097
|
+
async function cloudPullBehaviors(config) {
|
|
2098
|
+
const remoteBehaviors = await cloudPullBlob(
|
|
2099
|
+
"/sync/pull-behaviors",
|
|
2100
|
+
config
|
|
2101
|
+
);
|
|
2102
|
+
if (!remoteBehaviors || remoteBehaviors.length === 0) return { pulled: 0 };
|
|
2103
|
+
const client = getClient();
|
|
2104
|
+
let pulled = 0;
|
|
2105
|
+
for (const behavior of remoteBehaviors) {
|
|
2106
|
+
const existing = await client.execute({
|
|
2107
|
+
sql: `SELECT COUNT(*) as cnt FROM behaviors
|
|
2108
|
+
WHERE agent_id = ? AND content = ?`,
|
|
2109
|
+
args: [sqlSafe(behavior.agent_id), sqlSafe(behavior.content)]
|
|
2110
|
+
});
|
|
2111
|
+
if (Number(existing.rows[0]?.cnt) > 0) continue;
|
|
2112
|
+
await client.execute({
|
|
2113
|
+
sql: `INSERT OR IGNORE INTO behaviors
|
|
2114
|
+
(id, agent_id, project_name, domain, content, active, priority, created_at, updated_at)
|
|
2115
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
2116
|
+
args: [
|
|
2117
|
+
sqlSafe(behavior.id),
|
|
2118
|
+
sqlSafe(behavior.agent_id),
|
|
2119
|
+
sqlSafe(behavior.project_name),
|
|
2120
|
+
sqlSafe(behavior.domain),
|
|
2121
|
+
sqlSafe(behavior.content),
|
|
2122
|
+
sqlSafe(behavior.active ?? 1),
|
|
2123
|
+
sqlSafe(behavior.priority ?? "p1"),
|
|
2124
|
+
sqlSafe(behavior.created_at),
|
|
2125
|
+
sqlSafe(behavior.updated_at)
|
|
2126
|
+
]
|
|
2127
|
+
});
|
|
2128
|
+
pulled++;
|
|
2129
|
+
}
|
|
2130
|
+
return { pulled };
|
|
2131
|
+
}
|
|
2132
|
+
async function cloudPushGraphRAG(config) {
|
|
2133
|
+
const client = getClient();
|
|
2134
|
+
const [entities, relationships, aliases, entityMems, relMems, hyperedges, hyperedgeNodes] = await Promise.all([
|
|
2135
|
+
client.execute("SELECT * FROM entities LIMIT 50000"),
|
|
2136
|
+
client.execute("SELECT * FROM relationships LIMIT 50000"),
|
|
2137
|
+
client.execute("SELECT * FROM entity_aliases LIMIT 50000"),
|
|
2138
|
+
client.execute("SELECT * FROM entity_memories LIMIT 50000"),
|
|
2139
|
+
client.execute("SELECT * FROM relationship_memories LIMIT 50000"),
|
|
2140
|
+
client.execute("SELECT * FROM hyperedges LIMIT 50000"),
|
|
2141
|
+
client.execute("SELECT * FROM hyperedge_nodes LIMIT 50000")
|
|
2142
|
+
]);
|
|
2143
|
+
const blob = {
|
|
2144
|
+
entities: entities.rows,
|
|
2145
|
+
relationships: relationships.rows,
|
|
2146
|
+
entity_aliases: aliases.rows,
|
|
2147
|
+
entity_memories: entityMems.rows,
|
|
2148
|
+
relationship_memories: relMems.rows,
|
|
2149
|
+
hyperedges: hyperedges.rows,
|
|
2150
|
+
hyperedge_nodes: hyperedgeNodes.rows
|
|
2151
|
+
};
|
|
2152
|
+
const { ok } = await cloudPushBlob(
|
|
2153
|
+
"/sync/push-graphrag",
|
|
2154
|
+
[blob],
|
|
2155
|
+
"last_graphrag_push_version",
|
|
2156
|
+
config
|
|
2157
|
+
);
|
|
2158
|
+
return ok;
|
|
2159
|
+
}
|
|
2160
|
+
async function cloudPullGraphRAG(config) {
|
|
2161
|
+
const data = await cloudPullBlob(
|
|
2162
|
+
"/sync/pull-graphrag",
|
|
2163
|
+
config
|
|
2164
|
+
);
|
|
2165
|
+
if (!data || data.length === 0) return { pulled: 0 };
|
|
2166
|
+
const blob = data[0];
|
|
2167
|
+
const client = getClient();
|
|
2168
|
+
let pulled = 0;
|
|
2169
|
+
if (blob.entities.length > 0) {
|
|
2170
|
+
const stmts = blob.entities.map((e) => ({
|
|
2171
|
+
sql: `INSERT OR IGNORE INTO entities (id, name, type, first_seen, last_seen, properties)
|
|
2172
|
+
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
2173
|
+
args: [sqlSafe(e.id), sqlSafe(e.name), sqlSafe(e.type), sqlSafe(e.first_seen), sqlSafe(e.last_seen), sqlSafe(e.properties ?? "{}")]
|
|
2174
|
+
}));
|
|
2175
|
+
await client.batch(stmts, "write");
|
|
2176
|
+
pulled += stmts.length;
|
|
2177
|
+
}
|
|
2178
|
+
if (blob.relationships.length > 0) {
|
|
2179
|
+
const stmts = blob.relationships.map((r) => ({
|
|
2180
|
+
sql: `INSERT OR IGNORE INTO relationships
|
|
2181
|
+
(id, source_entity_id, target_entity_id, type, weight, timestamp, properties, confidence, confidence_label)
|
|
2182
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
2183
|
+
args: [
|
|
2184
|
+
sqlSafe(r.id),
|
|
2185
|
+
sqlSafe(r.source_entity_id),
|
|
2186
|
+
sqlSafe(r.target_entity_id),
|
|
2187
|
+
sqlSafe(r.type),
|
|
2188
|
+
sqlSafe(r.weight ?? 1),
|
|
2189
|
+
sqlSafe(r.timestamp),
|
|
2190
|
+
sqlSafe(r.properties ?? "{}"),
|
|
2191
|
+
sqlSafe(r.confidence ?? 1),
|
|
2192
|
+
sqlSafe(r.confidence_label ?? "extracted")
|
|
2193
|
+
]
|
|
2194
|
+
}));
|
|
2195
|
+
await client.batch(stmts, "write");
|
|
2196
|
+
pulled += stmts.length;
|
|
2197
|
+
}
|
|
2198
|
+
if (blob.entity_aliases.length > 0) {
|
|
2199
|
+
const stmts = blob.entity_aliases.map((a) => ({
|
|
2200
|
+
sql: `INSERT OR IGNORE INTO entity_aliases (alias, canonical_entity_id) VALUES (?, ?)`,
|
|
2201
|
+
args: [sqlSafe(a.alias), sqlSafe(a.canonical_entity_id)]
|
|
2202
|
+
}));
|
|
2203
|
+
await client.batch(stmts, "write");
|
|
2204
|
+
pulled += stmts.length;
|
|
2205
|
+
}
|
|
2206
|
+
if (blob.entity_memories.length > 0) {
|
|
2207
|
+
const stmts = blob.entity_memories.map((em) => ({
|
|
2208
|
+
sql: `INSERT OR IGNORE INTO entity_memories (entity_id, memory_id) VALUES (?, ?)`,
|
|
2209
|
+
args: [sqlSafe(em.entity_id), sqlSafe(em.memory_id)]
|
|
2210
|
+
}));
|
|
2211
|
+
await client.batch(stmts, "write");
|
|
2212
|
+
pulled += stmts.length;
|
|
2213
|
+
}
|
|
2214
|
+
if (blob.relationship_memories.length > 0) {
|
|
2215
|
+
const stmts = blob.relationship_memories.map((rm) => ({
|
|
2216
|
+
sql: `INSERT OR IGNORE INTO relationship_memories (relationship_id, memory_id) VALUES (?, ?)`,
|
|
2217
|
+
args: [sqlSafe(rm.relationship_id), sqlSafe(rm.memory_id)]
|
|
2218
|
+
}));
|
|
2219
|
+
await client.batch(stmts, "write");
|
|
2220
|
+
pulled += stmts.length;
|
|
2221
|
+
}
|
|
2222
|
+
if (blob.hyperedges.length > 0) {
|
|
2223
|
+
const stmts = blob.hyperedges.map((h) => ({
|
|
2224
|
+
sql: `INSERT OR IGNORE INTO hyperedges (id, label, relation, confidence, timestamp)
|
|
2225
|
+
VALUES (?, ?, ?, ?, ?)`,
|
|
2226
|
+
args: [sqlSafe(h.id), sqlSafe(h.label), sqlSafe(h.relation), sqlSafe(h.confidence ?? 1), sqlSafe(h.timestamp)]
|
|
2227
|
+
}));
|
|
2228
|
+
await client.batch(stmts, "write");
|
|
2229
|
+
pulled += stmts.length;
|
|
2230
|
+
}
|
|
2231
|
+
if (blob.hyperedge_nodes.length > 0) {
|
|
2232
|
+
const stmts = blob.hyperedge_nodes.map((hn) => ({
|
|
2233
|
+
sql: `INSERT OR IGNORE INTO hyperedge_nodes (hyperedge_id, entity_id) VALUES (?, ?)`,
|
|
2234
|
+
args: [sqlSafe(hn.hyperedge_id), sqlSafe(hn.entity_id)]
|
|
2235
|
+
}));
|
|
2236
|
+
await client.batch(stmts, "write");
|
|
2237
|
+
pulled += stmts.length;
|
|
2238
|
+
}
|
|
2239
|
+
return { pulled };
|
|
2240
|
+
}
|
|
2241
|
+
async function cloudPushTasks(config) {
|
|
2242
|
+
const client = getClient();
|
|
2243
|
+
const result = await client.execute("SELECT * FROM tasks LIMIT 10000");
|
|
2244
|
+
const rows = result.rows;
|
|
2245
|
+
const { ok } = await cloudPushBlob(
|
|
2246
|
+
"/sync/push-tasks",
|
|
2247
|
+
rows,
|
|
2248
|
+
"last_tasks_push_version",
|
|
2249
|
+
config
|
|
2250
|
+
);
|
|
2251
|
+
return ok;
|
|
2252
|
+
}
|
|
2253
|
+
async function cloudPullTasks(config) {
|
|
2254
|
+
const remoteTasks = await cloudPullBlob(
|
|
2255
|
+
"/sync/pull-tasks",
|
|
2256
|
+
config
|
|
2257
|
+
);
|
|
2258
|
+
if (!remoteTasks || remoteTasks.length === 0) return { pulled: 0 };
|
|
2259
|
+
const client = getClient();
|
|
2260
|
+
const stmts = remoteTasks.map((t) => ({
|
|
2261
|
+
sql: `INSERT OR IGNORE INTO tasks
|
|
2262
|
+
(id, title, assigned_to, assigned_by, project_name, priority, status, task_file, created_at, updated_at,
|
|
2263
|
+
blocked_by, parent_task_id, budget_tokens, budget_fallback_model, tokens_used, tokens_warned_at)
|
|
2264
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
2265
|
+
args: [
|
|
2266
|
+
sqlSafe(t.id),
|
|
2267
|
+
sqlSafe(t.title),
|
|
2268
|
+
sqlSafe(t.assigned_to),
|
|
2269
|
+
sqlSafe(t.assigned_by),
|
|
2270
|
+
sqlSafe(t.project_name),
|
|
2271
|
+
sqlSafe(t.priority ?? "p1"),
|
|
2272
|
+
sqlSafe(t.status ?? "open"),
|
|
2273
|
+
sqlSafe(t.task_file),
|
|
2274
|
+
sqlSafe(t.created_at),
|
|
2275
|
+
sqlSafe(t.updated_at),
|
|
2276
|
+
sqlSafe(t.blocked_by),
|
|
2277
|
+
sqlSafe(t.parent_task_id),
|
|
2278
|
+
sqlSafe(t.budget_tokens),
|
|
2279
|
+
sqlSafe(t.budget_fallback_model),
|
|
2280
|
+
sqlSafe(t.tokens_used ?? 0),
|
|
2281
|
+
sqlSafe(t.tokens_warned_at)
|
|
2282
|
+
]
|
|
2283
|
+
}));
|
|
2284
|
+
await client.batch(stmts, "write");
|
|
2285
|
+
return { pulled: remoteTasks.length };
|
|
2286
|
+
}
|
|
2287
|
+
async function cloudPushConversations(config) {
|
|
2288
|
+
const client = getClient();
|
|
2289
|
+
const result = await client.execute("SELECT * FROM conversations LIMIT 50000");
|
|
2290
|
+
const rows = result.rows;
|
|
2291
|
+
const { ok } = await cloudPushBlob(
|
|
2292
|
+
"/sync/push-conversations",
|
|
2293
|
+
rows,
|
|
2294
|
+
"last_conversations_push_version",
|
|
2295
|
+
config
|
|
2296
|
+
);
|
|
2297
|
+
return ok;
|
|
2298
|
+
}
|
|
2299
|
+
async function cloudPullConversations(config) {
|
|
2300
|
+
const remoteConvos = await cloudPullBlob(
|
|
2301
|
+
"/sync/pull-conversations",
|
|
2302
|
+
config
|
|
2303
|
+
);
|
|
2304
|
+
if (!remoteConvos || remoteConvos.length === 0) return { pulled: 0 };
|
|
2305
|
+
const client = getClient();
|
|
2306
|
+
const stmts = remoteConvos.map((c) => ({
|
|
2307
|
+
sql: `INSERT OR IGNORE INTO conversations
|
|
2308
|
+
(id, platform, external_id, sender_id, sender_name, sender_phone, sender_email,
|
|
2309
|
+
recipient_id, channel_id, thread_id, reply_to_id, content_text, content_media,
|
|
2310
|
+
content_metadata, agent_response, agent_name, timestamp, ingested_at)
|
|
2311
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
2312
|
+
args: [
|
|
2313
|
+
sqlSafe(c.id),
|
|
2314
|
+
sqlSafe(c.platform),
|
|
2315
|
+
sqlSafe(c.external_id),
|
|
2316
|
+
sqlSafe(c.sender_id),
|
|
2317
|
+
sqlSafe(c.sender_name),
|
|
2318
|
+
sqlSafe(c.sender_phone),
|
|
2319
|
+
sqlSafe(c.sender_email),
|
|
2320
|
+
sqlSafe(c.recipient_id),
|
|
2321
|
+
sqlSafe(c.channel_id),
|
|
2322
|
+
sqlSafe(c.thread_id),
|
|
2323
|
+
sqlSafe(c.reply_to_id),
|
|
2324
|
+
sqlSafe(c.content_text),
|
|
2325
|
+
sqlSafe(c.content_media),
|
|
2326
|
+
sqlSafe(c.content_metadata),
|
|
2327
|
+
sqlSafe(c.agent_response),
|
|
2328
|
+
sqlSafe(c.agent_name),
|
|
2329
|
+
sqlSafe(c.timestamp),
|
|
2330
|
+
sqlSafe(c.ingested_at)
|
|
2331
|
+
]
|
|
2332
|
+
}));
|
|
2333
|
+
await client.batch(stmts, "write");
|
|
2334
|
+
return { pulled: remoteConvos.length };
|
|
2335
|
+
}
|
|
2336
|
+
async function cloudPushDocuments(config) {
|
|
2337
|
+
const client = getClient();
|
|
2338
|
+
const [workspaces, documents] = await Promise.all([
|
|
2339
|
+
client.execute("SELECT * FROM workspaces LIMIT 1000"),
|
|
2340
|
+
client.execute("SELECT * FROM documents LIMIT 10000")
|
|
2341
|
+
]);
|
|
2342
|
+
const blob = {
|
|
2343
|
+
workspaces: workspaces.rows,
|
|
2344
|
+
documents: documents.rows
|
|
2345
|
+
};
|
|
2346
|
+
const { ok } = await cloudPushBlob(
|
|
2347
|
+
"/sync/push-documents",
|
|
2348
|
+
[blob],
|
|
2349
|
+
"last_documents_push_version",
|
|
2350
|
+
config
|
|
2351
|
+
);
|
|
2352
|
+
return ok;
|
|
2353
|
+
}
|
|
2354
|
+
async function cloudPullDocuments(config) {
|
|
2355
|
+
const data = await cloudPullBlob(
|
|
2356
|
+
"/sync/pull-documents",
|
|
2357
|
+
config
|
|
2358
|
+
);
|
|
2359
|
+
if (!data || data.length === 0) return { pulled: 0 };
|
|
2360
|
+
const blob = data[0];
|
|
2361
|
+
const client = getClient();
|
|
2362
|
+
let pulled = 0;
|
|
2363
|
+
if (blob.workspaces.length > 0) {
|
|
2364
|
+
const stmts = blob.workspaces.map((w) => ({
|
|
2365
|
+
sql: `INSERT OR IGNORE INTO workspaces (id, slug, name, owner_agent_id, created_at, metadata)
|
|
2366
|
+
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
2367
|
+
args: [sqlSafe(w.id), sqlSafe(w.slug), sqlSafe(w.name), sqlSafe(w.owner_agent_id), sqlSafe(w.created_at), sqlSafe(w.metadata)]
|
|
2368
|
+
}));
|
|
2369
|
+
await client.batch(stmts, "write");
|
|
2370
|
+
pulled += stmts.length;
|
|
2371
|
+
}
|
|
2372
|
+
if (blob.documents.length > 0) {
|
|
2373
|
+
const stmts = blob.documents.map((d) => ({
|
|
2374
|
+
sql: `INSERT OR IGNORE INTO documents
|
|
2375
|
+
(id, workspace_id, filename, mime, source_type, user_id, uploaded_at, metadata)
|
|
2376
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
2377
|
+
args: [
|
|
2378
|
+
sqlSafe(d.id),
|
|
2379
|
+
sqlSafe(d.workspace_id),
|
|
2380
|
+
sqlSafe(d.filename),
|
|
2381
|
+
sqlSafe(d.mime),
|
|
2382
|
+
sqlSafe(d.source_type),
|
|
2383
|
+
sqlSafe(d.user_id),
|
|
2384
|
+
sqlSafe(d.uploaded_at),
|
|
2385
|
+
sqlSafe(d.metadata)
|
|
2386
|
+
]
|
|
2387
|
+
}));
|
|
2388
|
+
await client.batch(stmts, "write");
|
|
2389
|
+
pulled += stmts.length;
|
|
2390
|
+
}
|
|
2391
|
+
return { pulled };
|
|
2392
|
+
}
|
|
2393
|
+
var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, ROSTER_DELETIONS_PATH;
|
|
2394
|
+
var init_cloud_sync = __esm({
|
|
2395
|
+
"src/lib/cloud-sync.ts"() {
|
|
1226
2396
|
"use strict";
|
|
1227
|
-
|
|
1228
|
-
|
|
2397
|
+
init_database();
|
|
2398
|
+
init_crypto();
|
|
2399
|
+
init_compress();
|
|
2400
|
+
init_license();
|
|
2401
|
+
init_config();
|
|
2402
|
+
init_employees();
|
|
2403
|
+
LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
|
|
2404
|
+
FETCH_TIMEOUT_MS = 3e4;
|
|
2405
|
+
PUSH_BATCH_SIZE = 5e3;
|
|
2406
|
+
ROSTER_LOCK_PATH = path7.join(EXE_AI_DIR, "roster-merge.lock");
|
|
2407
|
+
LOCK_STALE_MS = 3e4;
|
|
2408
|
+
ROSTER_DELETIONS_PATH = path7.join(EXE_AI_DIR, "roster-deletions.json");
|
|
1229
2409
|
}
|
|
1230
2410
|
});
|
|
1231
2411
|
|
|
@@ -1990,17 +3170,17 @@ __export(identity_exports, {
|
|
|
1990
3170
|
listIdentities: () => listIdentities,
|
|
1991
3171
|
updateIdentity: () => updateIdentity
|
|
1992
3172
|
});
|
|
1993
|
-
import { existsSync as
|
|
1994
|
-
import { readdirSync } from "fs";
|
|
1995
|
-
import
|
|
3173
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
|
|
3174
|
+
import { readdirSync as readdirSync2 } from "fs";
|
|
3175
|
+
import path8 from "path";
|
|
1996
3176
|
import { createHash as createHash2 } from "crypto";
|
|
1997
3177
|
function ensureDir() {
|
|
1998
|
-
if (!
|
|
1999
|
-
|
|
3178
|
+
if (!existsSync8(IDENTITY_DIR)) {
|
|
3179
|
+
mkdirSync3(IDENTITY_DIR, { recursive: true });
|
|
2000
3180
|
}
|
|
2001
3181
|
}
|
|
2002
3182
|
function identityPath(agentId) {
|
|
2003
|
-
return
|
|
3183
|
+
return path8.join(IDENTITY_DIR, `${agentId}.md`);
|
|
2004
3184
|
}
|
|
2005
3185
|
function parseFrontmatter(raw) {
|
|
2006
3186
|
const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
@@ -2041,8 +3221,8 @@ function contentHash(content) {
|
|
|
2041
3221
|
}
|
|
2042
3222
|
function getIdentity(agentId) {
|
|
2043
3223
|
const filePath = identityPath(agentId);
|
|
2044
|
-
if (!
|
|
2045
|
-
const raw =
|
|
3224
|
+
if (!existsSync8(filePath)) return null;
|
|
3225
|
+
const raw = readFileSync6(filePath, "utf-8");
|
|
2046
3226
|
const { frontmatter, body } = parseFrontmatter(raw);
|
|
2047
3227
|
return {
|
|
2048
3228
|
agentId,
|
|
@@ -2056,7 +3236,7 @@ async function updateIdentity(agentId, content, updatedBy) {
|
|
|
2056
3236
|
ensureDir();
|
|
2057
3237
|
const filePath = identityPath(agentId);
|
|
2058
3238
|
const hash = contentHash(content);
|
|
2059
|
-
|
|
3239
|
+
writeFileSync3(filePath, content, "utf-8");
|
|
2060
3240
|
try {
|
|
2061
3241
|
const client = getClient();
|
|
2062
3242
|
await client.execute({
|
|
@@ -2073,7 +3253,7 @@ async function updateIdentity(agentId, content, updatedBy) {
|
|
|
2073
3253
|
}
|
|
2074
3254
|
function listIdentities() {
|
|
2075
3255
|
ensureDir();
|
|
2076
|
-
const files =
|
|
3256
|
+
const files = readdirSync2(IDENTITY_DIR).filter((f) => f.endsWith(".md"));
|
|
2077
3257
|
const results = [];
|
|
2078
3258
|
for (const file of files) {
|
|
2079
3259
|
const agentId = file.replace(".md", "");
|
|
@@ -2112,7 +3292,7 @@ var init_identity = __esm({
|
|
|
2112
3292
|
"use strict";
|
|
2113
3293
|
init_config();
|
|
2114
3294
|
init_database();
|
|
2115
|
-
IDENTITY_DIR =
|
|
3295
|
+
IDENTITY_DIR = path8.join(EXE_AI_DIR, "identity");
|
|
2116
3296
|
}
|
|
2117
3297
|
});
|
|
2118
3298
|
|
|
@@ -2646,73 +3826,129 @@ ${PLAN_MODE_COMPAT}
|
|
|
2646
3826
|
}
|
|
2647
3827
|
});
|
|
2648
3828
|
|
|
2649
|
-
// src/lib/
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
import
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
function
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
3829
|
+
// src/lib/session-wrappers.ts
|
|
3830
|
+
var session_wrappers_exports = {};
|
|
3831
|
+
__export(session_wrappers_exports, {
|
|
3832
|
+
generateSessionWrappers: () => generateSessionWrappers
|
|
3833
|
+
});
|
|
3834
|
+
import {
|
|
3835
|
+
existsSync as existsSync9,
|
|
3836
|
+
readFileSync as readFileSync7,
|
|
3837
|
+
writeFileSync as writeFileSync4,
|
|
3838
|
+
mkdirSync as mkdirSync4,
|
|
3839
|
+
chmodSync,
|
|
3840
|
+
readdirSync as readdirSync3,
|
|
3841
|
+
unlinkSync as unlinkSync4
|
|
3842
|
+
} from "fs";
|
|
3843
|
+
import path9 from "path";
|
|
3844
|
+
import { homedir as homedir2 } from "os";
|
|
3845
|
+
function generateSessionWrappers(packageRoot, homeDir) {
|
|
3846
|
+
const home = homeDir ?? homedir2();
|
|
3847
|
+
const binDir = path9.join(home, ".exe-os", "bin");
|
|
3848
|
+
const rosterPath = path9.join(home, ".exe-os", "exe-employees.json");
|
|
3849
|
+
mkdirSync4(binDir, { recursive: true });
|
|
3850
|
+
const exeStartDst = path9.join(binDir, "exe-start");
|
|
3851
|
+
const candidates = [
|
|
3852
|
+
path9.join(packageRoot, "dist", "bin", "exe-start.sh"),
|
|
3853
|
+
path9.join(packageRoot, "src", "bin", "exe-start.sh")
|
|
3854
|
+
];
|
|
3855
|
+
for (const src of candidates) {
|
|
3856
|
+
if (existsSync9(src)) {
|
|
3857
|
+
writeFileSync4(exeStartDst, readFileSync7(src));
|
|
3858
|
+
chmodSync(exeStartDst, 493);
|
|
3859
|
+
break;
|
|
3860
|
+
}
|
|
3861
|
+
}
|
|
3862
|
+
let employees = [];
|
|
2672
3863
|
try {
|
|
2673
|
-
|
|
3864
|
+
employees = JSON.parse(readFileSync7(rosterPath, "utf8"));
|
|
2674
3865
|
} catch {
|
|
2675
|
-
return
|
|
3866
|
+
return { created: 0, pathConfigured: false };
|
|
2676
3867
|
}
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
const keytar = await tryKeytar();
|
|
2680
|
-
if (keytar) {
|
|
2681
|
-
try {
|
|
2682
|
-
const stored = await keytar.getPassword(SERVICE, ACCOUNT);
|
|
2683
|
-
if (stored) {
|
|
2684
|
-
return Buffer.from(stored, "base64");
|
|
2685
|
-
}
|
|
2686
|
-
} catch {
|
|
2687
|
-
}
|
|
2688
|
-
}
|
|
2689
|
-
const keyPath = getKeyPath();
|
|
2690
|
-
if (!existsSync2(keyPath)) {
|
|
2691
|
-
return null;
|
|
3868
|
+
if (employees.length === 0) {
|
|
3869
|
+
return { created: 0, pathConfigured: false };
|
|
2692
3870
|
}
|
|
2693
3871
|
try {
|
|
2694
|
-
const
|
|
2695
|
-
|
|
3872
|
+
for (const f of readdirSync3(binDir)) {
|
|
3873
|
+
if (f === "exe-start") continue;
|
|
3874
|
+
const fPath = path9.join(binDir, f);
|
|
3875
|
+
try {
|
|
3876
|
+
const content = readFileSync7(fPath, "utf8");
|
|
3877
|
+
if (content.includes("exe-start")) {
|
|
3878
|
+
unlinkSync4(fPath);
|
|
3879
|
+
}
|
|
3880
|
+
} catch {
|
|
3881
|
+
}
|
|
3882
|
+
}
|
|
2696
3883
|
} catch {
|
|
2697
|
-
return null;
|
|
2698
3884
|
}
|
|
3885
|
+
let created = 0;
|
|
3886
|
+
const wrapperContent = `#!/bin/bash
|
|
3887
|
+
exec "${exeStartDst}" "$0" "$@"
|
|
3888
|
+
`;
|
|
3889
|
+
for (const emp of employees) {
|
|
3890
|
+
for (let n = 1; n <= MAX_N; n++) {
|
|
3891
|
+
const wrapperPath = path9.join(binDir, `${emp.name}${n}`);
|
|
3892
|
+
writeFileSync4(wrapperPath, wrapperContent);
|
|
3893
|
+
chmodSync(wrapperPath, 493);
|
|
3894
|
+
created++;
|
|
3895
|
+
}
|
|
3896
|
+
}
|
|
3897
|
+
const pathConfigured = ensurePath(home, binDir);
|
|
3898
|
+
return { created, pathConfigured };
|
|
2699
3899
|
}
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
3900
|
+
function ensurePath(home, binDir) {
|
|
3901
|
+
if (process.env.PATH?.split(":").includes(binDir)) {
|
|
3902
|
+
return false;
|
|
3903
|
+
}
|
|
3904
|
+
const exportLine = `
|
|
3905
|
+
# exe-os session commands
|
|
3906
|
+
export PATH="${binDir}:$PATH"
|
|
3907
|
+
`;
|
|
3908
|
+
const shell = process.env.SHELL ?? "/bin/bash";
|
|
3909
|
+
const profilePaths = [];
|
|
3910
|
+
if (shell.includes("zsh")) {
|
|
3911
|
+
profilePaths.push(path9.join(home, ".zshrc"));
|
|
3912
|
+
} else if (shell.includes("bash")) {
|
|
3913
|
+
profilePaths.push(path9.join(home, ".bashrc"));
|
|
3914
|
+
profilePaths.push(path9.join(home, ".bash_profile"));
|
|
3915
|
+
} else {
|
|
3916
|
+
profilePaths.push(path9.join(home, ".profile"));
|
|
3917
|
+
}
|
|
3918
|
+
for (const profilePath of profilePaths) {
|
|
2704
3919
|
try {
|
|
2705
|
-
|
|
2706
|
-
|
|
3920
|
+
let content = "";
|
|
3921
|
+
try {
|
|
3922
|
+
content = readFileSync7(profilePath, "utf8");
|
|
3923
|
+
} catch {
|
|
3924
|
+
}
|
|
3925
|
+
if (content.includes(".exe-os/bin")) {
|
|
3926
|
+
return false;
|
|
3927
|
+
}
|
|
3928
|
+
writeFileSync4(profilePath, content + exportLine);
|
|
3929
|
+
return true;
|
|
2707
3930
|
} catch {
|
|
3931
|
+
continue;
|
|
2708
3932
|
}
|
|
2709
3933
|
}
|
|
2710
|
-
|
|
2711
|
-
await mkdir2(dir, { recursive: true });
|
|
2712
|
-
const keyPath = getKeyPath();
|
|
2713
|
-
await writeFile2(keyPath, b64 + "\n", "utf-8");
|
|
2714
|
-
await chmod2(keyPath, 384);
|
|
3934
|
+
return false;
|
|
2715
3935
|
}
|
|
3936
|
+
var MAX_N;
|
|
3937
|
+
var init_session_wrappers = __esm({
|
|
3938
|
+
"src/lib/session-wrappers.ts"() {
|
|
3939
|
+
"use strict";
|
|
3940
|
+
MAX_N = 9;
|
|
3941
|
+
}
|
|
3942
|
+
});
|
|
3943
|
+
|
|
3944
|
+
// src/lib/setup-wizard.ts
|
|
3945
|
+
init_config();
|
|
3946
|
+
init_keychain();
|
|
3947
|
+
import crypto3 from "crypto";
|
|
3948
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync5, unlinkSync as unlinkSync5 } from "fs";
|
|
3949
|
+
import os3 from "os";
|
|
3950
|
+
import path10 from "path";
|
|
3951
|
+
import { createInterface } from "readline";
|
|
2716
3952
|
|
|
2717
3953
|
// src/lib/model-downloader.ts
|
|
2718
3954
|
import { createWriteStream, createReadStream, existsSync as existsSync3, unlinkSync, renameSync as renameSync2 } from "fs";
|
|
@@ -2802,21 +4038,37 @@ async function fileHash(filePath) {
|
|
|
2802
4038
|
}
|
|
2803
4039
|
|
|
2804
4040
|
// src/lib/setup-wizard.ts
|
|
2805
|
-
|
|
4041
|
+
function findPackageRoot2() {
|
|
4042
|
+
let dir = path10.dirname(new URL(import.meta.url).pathname);
|
|
4043
|
+
const root = path10.parse(dir).root;
|
|
4044
|
+
while (dir !== root) {
|
|
4045
|
+
const pkgPath = path10.join(dir, "package.json");
|
|
4046
|
+
if (existsSync10(pkgPath)) {
|
|
4047
|
+
try {
|
|
4048
|
+
const pkg = JSON.parse(readFileSync8(pkgPath, "utf-8"));
|
|
4049
|
+
if (pkg.name === "@askexenow/exe-os" || pkg.name === "exe-os") return dir;
|
|
4050
|
+
} catch {
|
|
4051
|
+
}
|
|
4052
|
+
}
|
|
4053
|
+
dir = path10.dirname(dir);
|
|
4054
|
+
}
|
|
4055
|
+
return null;
|
|
4056
|
+
}
|
|
4057
|
+
var SETUP_STATE_PATH = path10.join(os3.homedir(), ".exe-os", "setup-state.json");
|
|
2806
4058
|
function loadSetupState() {
|
|
2807
4059
|
try {
|
|
2808
|
-
return JSON.parse(
|
|
4060
|
+
return JSON.parse(readFileSync8(SETUP_STATE_PATH, "utf8"));
|
|
2809
4061
|
} catch {
|
|
2810
4062
|
return { completedSteps: [], startedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
2811
4063
|
}
|
|
2812
4064
|
}
|
|
2813
4065
|
function saveSetupState(state) {
|
|
2814
|
-
|
|
2815
|
-
|
|
4066
|
+
mkdirSync5(path10.dirname(SETUP_STATE_PATH), { recursive: true });
|
|
4067
|
+
writeFileSync5(SETUP_STATE_PATH, JSON.stringify(state, null, 2));
|
|
2816
4068
|
}
|
|
2817
4069
|
function clearSetupState() {
|
|
2818
4070
|
try {
|
|
2819
|
-
|
|
4071
|
+
unlinkSync5(SETUP_STATE_PATH);
|
|
2820
4072
|
} catch {
|
|
2821
4073
|
}
|
|
2822
4074
|
}
|
|
@@ -2850,20 +4102,67 @@ async function runSetupWizard(opts = {}) {
|
|
|
2850
4102
|
log("");
|
|
2851
4103
|
log("=== exe-os Setup ===");
|
|
2852
4104
|
log("");
|
|
2853
|
-
log("
|
|
2854
|
-
log(" Run `exe-link` to sync your encryption key and skip setup.");
|
|
4105
|
+
log("Is this a new installation or pairing with an existing device?");
|
|
2855
4106
|
log("");
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
4107
|
+
log(" [1] New installation (first device)");
|
|
4108
|
+
log(" [2] Pair with existing device (I have a 24-word phrase)");
|
|
4109
|
+
log("");
|
|
4110
|
+
const installType = await ask(rl, "Choice (1/2): ");
|
|
4111
|
+
let isPairing = false;
|
|
4112
|
+
let pairingRosterPulled = false;
|
|
4113
|
+
if (installType === "2") {
|
|
4114
|
+
isPairing = true;
|
|
4115
|
+
log("");
|
|
4116
|
+
const { importMnemonic: importMnemonic2 } = await Promise.resolve().then(() => (init_keychain(), keychain_exports));
|
|
4117
|
+
const mnemonic = await ask(rl, "Paste your 24-word recovery phrase: ");
|
|
4118
|
+
try {
|
|
4119
|
+
const key = await importMnemonic2(mnemonic);
|
|
4120
|
+
await setMasterKey(key);
|
|
4121
|
+
log("Master key imported and stored securely.");
|
|
4122
|
+
log("");
|
|
4123
|
+
log("Enter the API key from your existing device.");
|
|
4124
|
+
log("(Find it in ~/.exe-os/config.json under cloud.apiKey, or ask your team lead.)");
|
|
4125
|
+
log("");
|
|
4126
|
+
const apiKey = await ask(rl, "API key (exe_sk_...): ");
|
|
4127
|
+
if (apiKey && apiKey.startsWith("exe_sk_")) {
|
|
4128
|
+
const cloudEndpoint = "https://askexe.com/cloud";
|
|
4129
|
+
const cloudCfg = { apiKey, endpoint: cloudEndpoint };
|
|
4130
|
+
const earlyConfig = await loadConfig();
|
|
4131
|
+
earlyConfig.cloud = cloudCfg;
|
|
4132
|
+
await saveConfig(earlyConfig);
|
|
4133
|
+
const { saveLicense: saveLic, mirrorLicenseKey: mirrorLic } = await Promise.resolve().then(() => (init_license(), license_exports));
|
|
4134
|
+
saveLic(apiKey);
|
|
4135
|
+
mirrorLic(apiKey);
|
|
4136
|
+
log("Cloud sync configured.");
|
|
4137
|
+
try {
|
|
4138
|
+
const { initSyncCrypto: initSyncCrypto2 } = await Promise.resolve().then(() => (init_crypto(), crypto_exports));
|
|
4139
|
+
const { cloudPullRoster: cloudPullRoster2 } = await Promise.resolve().then(() => (init_cloud_sync(), cloud_sync_exports));
|
|
4140
|
+
initSyncCrypto2(key);
|
|
4141
|
+
const result = await cloudPullRoster2({ apiKey, endpoint: cloudEndpoint });
|
|
4142
|
+
if (result.added > 0) {
|
|
4143
|
+
log(`Pulled ${result.added} employee(s) from Exe Cloud.`);
|
|
4144
|
+
pairingRosterPulled = true;
|
|
4145
|
+
}
|
|
4146
|
+
} catch {
|
|
4147
|
+
log("Could not pull roster from cloud \u2014 you can set up employees manually.");
|
|
4148
|
+
}
|
|
4149
|
+
} else {
|
|
4150
|
+
log("No API key provided \u2014 cloud sync will need to be configured later.");
|
|
4151
|
+
log("Run /exe-cloud after setup to connect.");
|
|
4152
|
+
}
|
|
4153
|
+
log("");
|
|
4154
|
+
} catch (err) {
|
|
4155
|
+
log(`Key import failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
4156
|
+
log("Check your phrase and try again, or choose option 1 for a fresh install.");
|
|
4157
|
+
rl.close();
|
|
4158
|
+
return;
|
|
4159
|
+
}
|
|
2861
4160
|
}
|
|
2862
4161
|
const state = loadSetupState();
|
|
2863
4162
|
if (state.completedSteps.length > 0) {
|
|
2864
4163
|
log(`Resuming setup from step ${Math.max(...state.completedSteps) + 1}...`);
|
|
2865
4164
|
}
|
|
2866
|
-
if (
|
|
4165
|
+
if (existsSync10(LEGACY_LANCE_PATH)) {
|
|
2867
4166
|
log("\u26A0 Found v1.0 LanceDB at ~/.exe-os/local.lance");
|
|
2868
4167
|
log(" v1.1 uses libSQL (SQLite). Your existing memories are not automatically migrated.");
|
|
2869
4168
|
log(" The old directory will not be modified or deleted.");
|
|
@@ -2875,7 +4174,7 @@ async function runSetupWizard(opts = {}) {
|
|
|
2875
4174
|
log("Encryption key already exists \u2014 skipping generation.");
|
|
2876
4175
|
} else {
|
|
2877
4176
|
log("Generating 256-bit encryption key...");
|
|
2878
|
-
const key =
|
|
4177
|
+
const key = crypto3.randomBytes(32);
|
|
2879
4178
|
await setMasterKey(key);
|
|
2880
4179
|
log("Encryption key generated and stored securely.");
|
|
2881
4180
|
}
|
|
@@ -2886,7 +4185,15 @@ async function runSetupWizard(opts = {}) {
|
|
|
2886
4185
|
}
|
|
2887
4186
|
log("");
|
|
2888
4187
|
let cloudConfig;
|
|
2889
|
-
if (
|
|
4188
|
+
if (isPairing) {
|
|
4189
|
+
const pairingConfig = await loadConfig();
|
|
4190
|
+
if (pairingConfig.cloud?.apiKey) {
|
|
4191
|
+
cloudConfig = pairingConfig.cloud;
|
|
4192
|
+
log("Cloud sync: using shared API key from pairing.");
|
|
4193
|
+
}
|
|
4194
|
+
state.completedSteps.push(2);
|
|
4195
|
+
saveSetupState(state);
|
|
4196
|
+
} else if (!state.completedSteps.includes(2)) {
|
|
2890
4197
|
log("Exe Cloud: your memories are end-to-end encrypted, compressed, and");
|
|
2891
4198
|
log("backed up on Exe Cloud. Free for all plans. We can't read your data \u2014");
|
|
2892
4199
|
log("only your encryption key can decrypt it.");
|
|
@@ -2967,10 +4274,10 @@ async function runSetupWizard(opts = {}) {
|
|
|
2967
4274
|
await saveConfig(config);
|
|
2968
4275
|
log("");
|
|
2969
4276
|
try {
|
|
2970
|
-
const claudeJsonPath =
|
|
4277
|
+
const claudeJsonPath = path10.join(os3.homedir(), ".claude.json");
|
|
2971
4278
|
let claudeJson = {};
|
|
2972
4279
|
try {
|
|
2973
|
-
claudeJson = JSON.parse(
|
|
4280
|
+
claudeJson = JSON.parse(readFileSync8(claudeJsonPath, "utf8"));
|
|
2974
4281
|
} catch {
|
|
2975
4282
|
}
|
|
2976
4283
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -2979,7 +4286,7 @@ async function runSetupWizard(opts = {}) {
|
|
|
2979
4286
|
if (!projects[dir]) projects[dir] = {};
|
|
2980
4287
|
projects[dir].hasTrustDialogAccepted = true;
|
|
2981
4288
|
}
|
|
2982
|
-
|
|
4289
|
+
writeFileSync5(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
2983
4290
|
} catch {
|
|
2984
4291
|
}
|
|
2985
4292
|
state.completedSteps.push(5);
|
|
@@ -3005,7 +4312,34 @@ async function runSetupWizard(opts = {}) {
|
|
|
3005
4312
|
} = await Promise.resolve().then(() => (init_license(), license_exports));
|
|
3006
4313
|
const createdEmployees = [];
|
|
3007
4314
|
let cooName = "exe";
|
|
3008
|
-
if (
|
|
4315
|
+
if (pairingRosterPulled) {
|
|
4316
|
+
const roster = await loadEmployees2(EMPLOYEES_PATH2).catch(() => []);
|
|
4317
|
+
const existingCoo = roster.find((e) => e.role === "COO");
|
|
4318
|
+
if (existingCoo) {
|
|
4319
|
+
cooName = existingCoo.name;
|
|
4320
|
+
log(`Team synced from cloud. COO: ${cooName}`);
|
|
4321
|
+
const teamList = roster.map((e) => `${e.name} (${e.role})`).join(", ");
|
|
4322
|
+
log(`Team: ${teamList}`);
|
|
4323
|
+
createdEmployees.push(...roster.map((e) => ({ name: e.name, role: e.role })));
|
|
4324
|
+
}
|
|
4325
|
+
for (const emp of roster) {
|
|
4326
|
+
registerBinSymlinks2(emp.name);
|
|
4327
|
+
}
|
|
4328
|
+
try {
|
|
4329
|
+
const { generateSessionWrappers: generateSessionWrappers2 } = await Promise.resolve().then(() => (init_session_wrappers(), session_wrappers_exports));
|
|
4330
|
+
const pkgRoot = findPackageRoot2();
|
|
4331
|
+
if (pkgRoot) {
|
|
4332
|
+
const wrapResult = generateSessionWrappers2(pkgRoot);
|
|
4333
|
+
if (wrapResult.created > 0) {
|
|
4334
|
+
log(`Session shortcuts generated: ${roster.map((e) => `${e.name}1`).join(", ")}, ...`);
|
|
4335
|
+
}
|
|
4336
|
+
}
|
|
4337
|
+
} catch {
|
|
4338
|
+
}
|
|
4339
|
+
state.completedSteps.push(6, 7, 8);
|
|
4340
|
+
saveSetupState(state);
|
|
4341
|
+
log("");
|
|
4342
|
+
} else if (!state.completedSteps.includes(6)) {
|
|
3009
4343
|
log("=== Your Team ===");
|
|
3010
4344
|
log("");
|
|
3011
4345
|
log("Every install starts with a COO \u2014 your right-hand operator.");
|
|
@@ -3031,9 +4365,9 @@ async function runSetupWizard(opts = {}) {
|
|
|
3031
4365
|
const cooIdentityContent = getIdentityTemplate("coo");
|
|
3032
4366
|
if (cooIdentityContent) {
|
|
3033
4367
|
const cooIdPath = identityPath2(cooName);
|
|
3034
|
-
|
|
4368
|
+
mkdirSync5(path10.dirname(cooIdPath), { recursive: true });
|
|
3035
4369
|
const replaced = cooIdentityContent.replace(/agent_id:\s*exe/g, `agent_id: ${cooName}`).replace(/\$\{agent_id\}/g, cooName);
|
|
3036
|
-
|
|
4370
|
+
writeFileSync5(cooIdPath, replaced, "utf-8");
|
|
3037
4371
|
}
|
|
3038
4372
|
registerBinSymlinks2(cooName);
|
|
3039
4373
|
createdEmployees.push({ name: cooName, role: "COO" });
|
|
@@ -3132,9 +4466,9 @@ async function runSetupWizard(opts = {}) {
|
|
|
3132
4466
|
const ctoIdentityContent = getIdentityTemplate("cto");
|
|
3133
4467
|
if (ctoIdentityContent) {
|
|
3134
4468
|
const ctoIdPath = identityPath2(ctoName);
|
|
3135
|
-
|
|
4469
|
+
mkdirSync5(path10.dirname(ctoIdPath), { recursive: true });
|
|
3136
4470
|
const replaced = ctoIdentityContent.replace(/agent_id:\s*\w+/g, `agent_id: ${ctoName}`).replace(/\$\{agent_id\}/g, ctoName);
|
|
3137
|
-
|
|
4471
|
+
writeFileSync5(ctoIdPath, replaced, "utf-8");
|
|
3138
4472
|
}
|
|
3139
4473
|
registerBinSymlinks2(ctoName);
|
|
3140
4474
|
createdEmployees.push({ name: ctoName, role: "CTO" });
|
|
@@ -3160,9 +4494,9 @@ async function runSetupWizard(opts = {}) {
|
|
|
3160
4494
|
const cmoIdentityContent = getIdentityTemplate("cmo");
|
|
3161
4495
|
if (cmoIdentityContent) {
|
|
3162
4496
|
const cmoIdPath = identityPath2(cmoName);
|
|
3163
|
-
|
|
4497
|
+
mkdirSync5(path10.dirname(cmoIdPath), { recursive: true });
|
|
3164
4498
|
const replaced = cmoIdentityContent.replace(/agent_id:\s*\w+/g, `agent_id: ${cmoName}`).replace(/\$\{agent_id\}/g, cmoName);
|
|
3165
|
-
|
|
4499
|
+
writeFileSync5(cmoIdPath, replaced, "utf-8");
|
|
3166
4500
|
}
|
|
3167
4501
|
registerBinSymlinks2(cmoName);
|
|
3168
4502
|
createdEmployees.push({ name: cmoName, role: "CMO" });
|
|
@@ -3175,11 +4509,24 @@ async function runSetupWizard(opts = {}) {
|
|
|
3175
4509
|
} else {
|
|
3176
4510
|
log("Step 8 already complete \u2014 skipping.");
|
|
3177
4511
|
}
|
|
4512
|
+
if (!pairingRosterPulled) {
|
|
4513
|
+
try {
|
|
4514
|
+
const { generateSessionWrappers: generateSessionWrappers2 } = await Promise.resolve().then(() => (init_session_wrappers(), session_wrappers_exports));
|
|
4515
|
+
const pkgRoot = findPackageRoot2();
|
|
4516
|
+
if (pkgRoot) {
|
|
4517
|
+
const wrapResult = generateSessionWrappers2(pkgRoot);
|
|
4518
|
+
if (wrapResult.created > 0) {
|
|
4519
|
+
log(`Session shortcuts generated (${cooName}1, ${cooName}2, ...)`);
|
|
4520
|
+
}
|
|
4521
|
+
}
|
|
4522
|
+
} catch {
|
|
4523
|
+
}
|
|
4524
|
+
}
|
|
3178
4525
|
clearSetupState();
|
|
3179
4526
|
log("=== Two Ways to Work ===");
|
|
3180
4527
|
log("");
|
|
3181
4528
|
log(" 1. Claude Code mode");
|
|
3182
|
-
log(` Type \`${cooName}\` in your
|
|
4529
|
+
log(` Type \`${cooName}1\` in your project folder. Works with your Claude Code subscription.`);
|
|
3183
4530
|
log(" Best for developers who live in the terminal.");
|
|
3184
4531
|
log("");
|
|
3185
4532
|
log(" 2. Dashboard mode");
|
|
@@ -3189,8 +4536,6 @@ async function runSetupWizard(opts = {}) {
|
|
|
3189
4536
|
log(" Both modes share the same memory, employees, and data.");
|
|
3190
4537
|
log(" You can switch anytime.");
|
|
3191
4538
|
log("");
|
|
3192
|
-
log(" For Claude Code mode, run: exe-os claude");
|
|
3193
|
-
log("");
|
|
3194
4539
|
log("=== Setup Complete ===");
|
|
3195
4540
|
log("Database: " + config.dbPath);
|
|
3196
4541
|
if (cloudConfig) {
|
|
@@ -3206,7 +4551,7 @@ async function runSetupWizard(opts = {}) {
|
|
|
3206
4551
|
log("Team: " + createdEmployees.map((e) => `${e.name} (${e.role})`).join(", "));
|
|
3207
4552
|
}
|
|
3208
4553
|
log("");
|
|
3209
|
-
log(`Type \`${cooName}\` to start (Claude Code) or \`exe-os\` for dashboard.`);
|
|
4554
|
+
log(`Type \`${cooName}1\` to start (Claude Code) or \`exe-os\` for dashboard.`);
|
|
3210
4555
|
log("");
|
|
3211
4556
|
} finally {
|
|
3212
4557
|
rl.close();
|