@askexenow/exe-os 0.9.66 → 0.9.67
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/deploy/stack-manifests/v0.9.json +54 -5
- package/dist/bin/age-ontology-load.js +61 -0
- package/dist/bin/agentic-ontology-backfill.js +4708 -0
- package/dist/bin/agentic-reflection-backfill.js +4144 -0
- package/dist/bin/{exe-link.js → agentic-semantic-label.js} +1523 -2275
- package/dist/bin/backfill-conversations.js +506 -20
- package/dist/bin/backfill-responses.js +506 -20
- package/dist/bin/backfill-vectors.js +233 -20
- package/dist/bin/bulk-sync-postgres.js +4876 -0
- package/dist/bin/cleanup-stale-review-tasks.js +507 -21
- package/dist/bin/cli.js +2450 -1530
- package/dist/bin/exe-assign.js +506 -20
- package/dist/bin/exe-boot.js +410 -60
- package/dist/bin/exe-cloud.js +795 -105
- package/dist/bin/exe-dispatch.js +516 -22
- package/dist/bin/exe-doctor.js +587 -30
- package/dist/bin/exe-export-behaviors.js +518 -24
- package/dist/bin/exe-forget.js +507 -21
- package/dist/bin/exe-gateway.js +571 -25
- package/dist/bin/exe-heartbeat.js +518 -24
- package/dist/bin/exe-kill.js +507 -21
- package/dist/bin/exe-launch-agent.js +2312 -1069
- package/dist/bin/exe-new-employee.js +197 -165
- package/dist/bin/exe-pending-messages.js +507 -21
- package/dist/bin/exe-pending-notifications.js +507 -21
- package/dist/bin/exe-pending-reviews.js +507 -21
- package/dist/bin/exe-rename.js +507 -21
- package/dist/bin/exe-review.js +507 -21
- package/dist/bin/exe-search.js +518 -24
- package/dist/bin/exe-session-cleanup.js +516 -22
- package/dist/bin/exe-settings.js +4 -0
- package/dist/bin/exe-start-codex.js +682 -143
- package/dist/bin/exe-start-opencode.js +627 -79
- package/dist/bin/exe-status.js +507 -21
- package/dist/bin/exe-team.js +507 -21
- package/dist/bin/git-sweep.js +516 -22
- package/dist/bin/graph-backfill.js +558 -21
- package/dist/bin/graph-export.js +507 -21
- package/dist/bin/graph-layer-benchmark.js +109 -0
- package/dist/bin/install.js +305 -288
- package/dist/bin/intercom-check.js +516 -22
- package/dist/bin/postgres-agentic-reflection-backfill.js +187 -0
- package/dist/bin/postgres-agentic-semantic-backfill.js +237 -0
- package/dist/bin/scan-tasks.js +516 -22
- package/dist/bin/setup.js +412 -62
- package/dist/bin/shard-migrate.js +506 -20
- package/dist/gateway/index.js +569 -23
- package/dist/hooks/bug-report-worker.js +519 -25
- package/dist/hooks/codex-stop-task-finalizer.js +516 -22
- package/dist/hooks/commit-complete.js +516 -22
- package/dist/hooks/error-recall.js +518 -24
- package/dist/hooks/ingest.js +516 -22
- package/dist/hooks/instructions-loaded.js +507 -21
- package/dist/hooks/notification.js +507 -21
- package/dist/hooks/post-compact.js +507 -21
- package/dist/hooks/post-tool-combined.js +519 -25
- package/dist/hooks/pre-compact.js +516 -22
- package/dist/hooks/pre-tool-use.js +507 -21
- package/dist/hooks/prompt-submit.js +519 -25
- package/dist/hooks/session-end.js +516 -22
- package/dist/hooks/session-start.js +520 -26
- package/dist/hooks/stop.js +517 -23
- package/dist/hooks/subagent-stop.js +507 -21
- package/dist/hooks/summary-worker.js +411 -61
- package/dist/index.js +569 -23
- package/dist/lib/cloud-sync.js +391 -53
- package/dist/lib/config.js +13 -1
- package/dist/lib/consolidation.js +1 -1
- package/dist/lib/database.js +124 -0
- package/dist/lib/db.js +124 -0
- package/dist/lib/device-registry.js +124 -0
- package/dist/lib/embedder.js +13 -1
- package/dist/lib/exe-daemon.js +2184 -561
- package/dist/lib/hybrid-search.js +518 -24
- package/dist/lib/identity.js +3 -0
- package/dist/lib/keychain.js +178 -22
- package/dist/lib/messaging.js +3 -0
- package/dist/lib/reminders.js +3 -0
- package/dist/lib/schedules.js +233 -20
- package/dist/lib/skill-learning.js +16 -1
- package/dist/lib/store.js +506 -20
- package/dist/lib/tasks.js +16 -1
- package/dist/lib/tmux-routing.js +16 -1
- package/dist/lib/token-spend.js +3 -0
- package/dist/mcp/server.js +1757 -428
- package/dist/mcp/tools/complete-reminder.js +3 -0
- package/dist/mcp/tools/create-reminder.js +3 -0
- package/dist/mcp/tools/create-task.js +16 -1
- package/dist/mcp/tools/deactivate-behavior.js +3 -0
- package/dist/mcp/tools/list-reminders.js +3 -0
- package/dist/mcp/tools/list-tasks.js +3 -0
- package/dist/mcp/tools/send-message.js +3 -0
- package/dist/mcp/tools/update-task.js +16 -1
- package/dist/runtime/index.js +516 -22
- package/dist/tui/App.js +594 -29
- package/package.json +8 -5
- package/src/commands/exe/cloud.md +6 -10
- package/stack.release.json +3 -3
- package/src/commands/exe/link.md +0 -18
package/dist/bin/setup.js
CHANGED
|
@@ -173,6 +173,10 @@ async function loadConfig() {
|
|
|
173
173
|
if (config.dbPath.startsWith("~")) {
|
|
174
174
|
config.dbPath = config.dbPath.replace(/^~/, os.homedir());
|
|
175
175
|
}
|
|
176
|
+
const envDbPath = path.join(dir, "memories.db");
|
|
177
|
+
if (process.env.EXE_OS_DIR && config.dbPath !== envDbPath && !existsSync2(config.dbPath) && existsSync2(envDbPath)) {
|
|
178
|
+
config.dbPath = envDbPath;
|
|
179
|
+
}
|
|
176
180
|
return config;
|
|
177
181
|
} catch {
|
|
178
182
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
@@ -193,7 +197,15 @@ function loadConfigSync() {
|
|
|
193
197
|
normalizeSessionLifecycle(migratedCfg);
|
|
194
198
|
normalizeAutoUpdate(migratedCfg);
|
|
195
199
|
normalizeOrchestration(migratedCfg);
|
|
196
|
-
|
|
200
|
+
const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
201
|
+
if (config.dbPath.startsWith("~")) {
|
|
202
|
+
config.dbPath = config.dbPath.replace(/^~/, os.homedir());
|
|
203
|
+
}
|
|
204
|
+
const envDbPath = path.join(dir, "memories.db");
|
|
205
|
+
if (process.env.EXE_OS_DIR && config.dbPath !== envDbPath && !existsSync2(config.dbPath) && existsSync2(envDbPath)) {
|
|
206
|
+
config.dbPath = envDbPath;
|
|
207
|
+
}
|
|
208
|
+
return config;
|
|
197
209
|
} catch {
|
|
198
210
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
199
211
|
}
|
|
@@ -310,12 +322,13 @@ var keychain_exports = {};
|
|
|
310
322
|
__export(keychain_exports, {
|
|
311
323
|
deleteMasterKey: () => deleteMasterKey,
|
|
312
324
|
exportMnemonic: () => exportMnemonic,
|
|
325
|
+
getKeyStorageInfo: () => getKeyStorageInfo,
|
|
313
326
|
getMasterKey: () => getMasterKey,
|
|
314
327
|
importMnemonic: () => importMnemonic,
|
|
315
328
|
setMasterKey: () => setMasterKey
|
|
316
329
|
});
|
|
317
330
|
import { readFile as readFile2, writeFile as writeFile2, unlink, mkdir as mkdir2, chmod as chmod2 } from "fs/promises";
|
|
318
|
-
import { existsSync as existsSync3 } from "fs";
|
|
331
|
+
import { existsSync as existsSync3, statSync } from "fs";
|
|
319
332
|
import { execSync } from "child_process";
|
|
320
333
|
import path2 from "path";
|
|
321
334
|
import os2 from "os";
|
|
@@ -325,29 +338,65 @@ function getKeyDir() {
|
|
|
325
338
|
function getKeyPath() {
|
|
326
339
|
return path2.join(getKeyDir(), "master.key");
|
|
327
340
|
}
|
|
328
|
-
function
|
|
341
|
+
function nativeKeychainAllowed() {
|
|
342
|
+
return process.env.EXE_OS_DISABLE_NATIVE_KEYCHAIN !== "1";
|
|
343
|
+
}
|
|
344
|
+
function linuxSecretAvailable() {
|
|
345
|
+
if (!nativeKeychainAllowed()) return false;
|
|
346
|
+
if (process.platform !== "linux") return false;
|
|
347
|
+
if (linuxSecretAvailability !== null) return linuxSecretAvailability;
|
|
348
|
+
try {
|
|
349
|
+
execSync("command -v secret-tool >/dev/null 2>&1", { timeout: 1e3 });
|
|
350
|
+
} catch {
|
|
351
|
+
linuxSecretAvailability = false;
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
354
|
+
try {
|
|
355
|
+
execSync("secret-tool search --all exe-os probe >/dev/null 2>&1", { timeout: 1e3 });
|
|
356
|
+
linuxSecretAvailability = true;
|
|
357
|
+
} catch {
|
|
358
|
+
linuxSecretAvailability = false;
|
|
359
|
+
}
|
|
360
|
+
return linuxSecretAvailability;
|
|
361
|
+
}
|
|
362
|
+
function isRootOnlyTrustedServerKeyFile(keyPath) {
|
|
363
|
+
if (process.platform !== "linux") return false;
|
|
364
|
+
try {
|
|
365
|
+
const uid = typeof os2.userInfo().uid === "number" ? os2.userInfo().uid : -1;
|
|
366
|
+
const st = statSync(keyPath);
|
|
367
|
+
if (!st.isFile() || (st.mode & 63) !== 0) return false;
|
|
368
|
+
if (uid === 0) return true;
|
|
369
|
+
const exeOsDir = process.env.EXE_OS_DIR;
|
|
370
|
+
return Boolean(exeOsDir && path2.resolve(keyPath).startsWith(path2.resolve(exeOsDir) + path2.sep));
|
|
371
|
+
} catch {
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
function macKeychainGet(service = SERVICE) {
|
|
376
|
+
if (!nativeKeychainAllowed()) return null;
|
|
329
377
|
if (process.platform !== "darwin") return null;
|
|
330
378
|
try {
|
|
331
379
|
return execSync(
|
|
332
|
-
`security find-generic-password -s "${
|
|
380
|
+
`security find-generic-password -s "${service}" -a "${ACCOUNT}" -w 2>/dev/null`,
|
|
333
381
|
{ encoding: "utf-8", timeout: 5e3 }
|
|
334
382
|
).trim();
|
|
335
383
|
} catch {
|
|
336
384
|
return null;
|
|
337
385
|
}
|
|
338
386
|
}
|
|
339
|
-
function macKeychainSet(value) {
|
|
387
|
+
function macKeychainSet(value, service = SERVICE) {
|
|
388
|
+
if (!nativeKeychainAllowed()) return false;
|
|
340
389
|
if (process.platform !== "darwin") return false;
|
|
341
390
|
try {
|
|
342
391
|
try {
|
|
343
392
|
execSync(
|
|
344
|
-
`security delete-generic-password -s "${
|
|
393
|
+
`security delete-generic-password -s "${service}" -a "${ACCOUNT}" 2>/dev/null`,
|
|
345
394
|
{ timeout: 5e3 }
|
|
346
395
|
);
|
|
347
396
|
} catch {
|
|
348
397
|
}
|
|
349
398
|
execSync(
|
|
350
|
-
`security add-generic-password -s "${
|
|
399
|
+
`security add-generic-password -s "${service}" -a "${ACCOUNT}" -w "${value}"`,
|
|
351
400
|
{ timeout: 5e3 }
|
|
352
401
|
);
|
|
353
402
|
return true;
|
|
@@ -355,11 +404,12 @@ function macKeychainSet(value) {
|
|
|
355
404
|
return false;
|
|
356
405
|
}
|
|
357
406
|
}
|
|
358
|
-
function macKeychainDelete() {
|
|
407
|
+
function macKeychainDelete(service = SERVICE) {
|
|
408
|
+
if (!nativeKeychainAllowed()) return false;
|
|
359
409
|
if (process.platform !== "darwin") return false;
|
|
360
410
|
try {
|
|
361
411
|
execSync(
|
|
362
|
-
`security delete-generic-password -s "${
|
|
412
|
+
`security delete-generic-password -s "${service}" -a "${ACCOUNT}" 2>/dev/null`,
|
|
363
413
|
{ timeout: 5e3 }
|
|
364
414
|
);
|
|
365
415
|
return true;
|
|
@@ -367,22 +417,22 @@ function macKeychainDelete() {
|
|
|
367
417
|
return false;
|
|
368
418
|
}
|
|
369
419
|
}
|
|
370
|
-
function linuxSecretGet() {
|
|
371
|
-
if (
|
|
420
|
+
function linuxSecretGet(service = SERVICE) {
|
|
421
|
+
if (!linuxSecretAvailable()) return null;
|
|
372
422
|
try {
|
|
373
423
|
return execSync(
|
|
374
|
-
`secret-tool lookup service "${
|
|
424
|
+
`secret-tool lookup service "${service}" account "${ACCOUNT}" 2>/dev/null`,
|
|
375
425
|
{ encoding: "utf-8", timeout: 5e3 }
|
|
376
426
|
).trim();
|
|
377
427
|
} catch {
|
|
378
428
|
return null;
|
|
379
429
|
}
|
|
380
430
|
}
|
|
381
|
-
function linuxSecretSet(value) {
|
|
382
|
-
if (
|
|
431
|
+
function linuxSecretSet(value, service = SERVICE) {
|
|
432
|
+
if (!linuxSecretAvailable()) return false;
|
|
383
433
|
try {
|
|
384
434
|
execSync(
|
|
385
|
-
`echo -n "${value}" | secret-tool store --label="exe-os master key" service "${
|
|
435
|
+
`echo -n "${value}" | secret-tool store --label="exe-os master key" service "${service}" account "${ACCOUNT}" 2>/dev/null`,
|
|
386
436
|
{ timeout: 5e3 }
|
|
387
437
|
);
|
|
388
438
|
return true;
|
|
@@ -390,11 +440,12 @@ function linuxSecretSet(value) {
|
|
|
390
440
|
return false;
|
|
391
441
|
}
|
|
392
442
|
}
|
|
393
|
-
function linuxSecretDelete() {
|
|
443
|
+
function linuxSecretDelete(service = SERVICE) {
|
|
444
|
+
if (!nativeKeychainAllowed()) return false;
|
|
394
445
|
if (process.platform !== "linux") return false;
|
|
395
446
|
try {
|
|
396
447
|
execSync(
|
|
397
|
-
`secret-tool clear service "${
|
|
448
|
+
`secret-tool clear service "${service}" account "${ACCOUNT}" 2>/dev/null`,
|
|
398
449
|
{ timeout: 5e3 }
|
|
399
450
|
);
|
|
400
451
|
return true;
|
|
@@ -403,6 +454,7 @@ function linuxSecretDelete() {
|
|
|
403
454
|
}
|
|
404
455
|
}
|
|
405
456
|
async function tryKeytar() {
|
|
457
|
+
if (!nativeKeychainAllowed()) return null;
|
|
406
458
|
try {
|
|
407
459
|
return await import("keytar");
|
|
408
460
|
} catch {
|
|
@@ -476,7 +528,19 @@ async function writeMachineBoundFileFallback(b64) {
|
|
|
476
528
|
return "plaintext";
|
|
477
529
|
}
|
|
478
530
|
async function getMasterKey() {
|
|
479
|
-
|
|
531
|
+
let nativeValue = macKeychainGet() ?? linuxSecretGet();
|
|
532
|
+
if (!nativeValue) {
|
|
533
|
+
const legacyValue = macKeychainGet(LEGACY_SERVICE) ?? linuxSecretGet(LEGACY_SERVICE);
|
|
534
|
+
if (legacyValue) {
|
|
535
|
+
const migrated = macKeychainSet(legacyValue) || linuxSecretSet(legacyValue);
|
|
536
|
+
if (migrated) {
|
|
537
|
+
macKeychainDelete(LEGACY_SERVICE);
|
|
538
|
+
linuxSecretDelete(LEGACY_SERVICE);
|
|
539
|
+
process.stderr.write("[keychain] Migrated keychain service from exe-mem to exe-os.\n");
|
|
540
|
+
}
|
|
541
|
+
nativeValue = legacyValue;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
480
544
|
if (nativeValue) {
|
|
481
545
|
return Buffer.from(nativeValue, "base64");
|
|
482
546
|
}
|
|
@@ -484,12 +548,17 @@ async function getMasterKey() {
|
|
|
484
548
|
if (keytar) {
|
|
485
549
|
try {
|
|
486
550
|
const keytarValue = await keytar.getPassword(SERVICE, ACCOUNT);
|
|
487
|
-
|
|
488
|
-
|
|
551
|
+
const legacyKeytarValue = keytarValue ?? await keytar.getPassword(LEGACY_SERVICE, ACCOUNT);
|
|
552
|
+
if (legacyKeytarValue) {
|
|
553
|
+
const migrated = macKeychainSet(legacyKeytarValue) || linuxSecretSet(legacyKeytarValue);
|
|
489
554
|
if (migrated) {
|
|
490
555
|
process.stderr.write("[keychain] Migrated key from keytar to native keychain.\n");
|
|
556
|
+
try {
|
|
557
|
+
await keytar.deletePassword(LEGACY_SERVICE, ACCOUNT);
|
|
558
|
+
} catch {
|
|
559
|
+
}
|
|
491
560
|
}
|
|
492
|
-
return Buffer.from(
|
|
561
|
+
return Buffer.from(legacyKeytarValue, "base64");
|
|
493
562
|
}
|
|
494
563
|
} catch {
|
|
495
564
|
}
|
|
@@ -514,7 +583,7 @@ async function getMasterKey() {
|
|
|
514
583
|
const decrypted = decryptWithMachineKey(content, machineKey);
|
|
515
584
|
if (!decrypted) {
|
|
516
585
|
process.stderr.write(
|
|
517
|
-
"[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase: exe-os
|
|
586
|
+
"[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase during setup: exe-os setup\n"
|
|
518
587
|
);
|
|
519
588
|
return null;
|
|
520
589
|
}
|
|
@@ -523,6 +592,9 @@ async function getMasterKey() {
|
|
|
523
592
|
b64Value = content;
|
|
524
593
|
}
|
|
525
594
|
const key = Buffer.from(b64Value, "base64");
|
|
595
|
+
if (!content.startsWith(ENCRYPTED_PREFIX) && isRootOnlyTrustedServerKeyFile(keyPath)) {
|
|
596
|
+
return key;
|
|
597
|
+
}
|
|
526
598
|
const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
|
|
527
599
|
if (migrated) {
|
|
528
600
|
process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
|
|
@@ -550,6 +622,97 @@ async function getMasterKey() {
|
|
|
550
622
|
return null;
|
|
551
623
|
}
|
|
552
624
|
}
|
|
625
|
+
async function getKeyStorageInfo() {
|
|
626
|
+
if (macKeychainGet()) {
|
|
627
|
+
return {
|
|
628
|
+
kind: "macos-keychain",
|
|
629
|
+
secure: true,
|
|
630
|
+
note: "stored in macOS Keychain via built-in security CLI"
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
if (macKeychainGet(LEGACY_SERVICE)) {
|
|
634
|
+
return {
|
|
635
|
+
kind: "macos-keychain",
|
|
636
|
+
secure: true,
|
|
637
|
+
note: "stored in legacy macOS Keychain service exe-mem; next key read migrates it to exe-os"
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
if (linuxSecretGet()) {
|
|
641
|
+
return {
|
|
642
|
+
kind: "linux-secret-service",
|
|
643
|
+
secure: true,
|
|
644
|
+
note: "stored in Linux Secret Service via secret-tool"
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
if (linuxSecretGet(LEGACY_SERVICE)) {
|
|
648
|
+
return {
|
|
649
|
+
kind: "linux-secret-service",
|
|
650
|
+
secure: true,
|
|
651
|
+
note: "stored in legacy Linux Secret Service service exe-mem; next key read migrates it to exe-os"
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
const keytar = await tryKeytar();
|
|
655
|
+
if (keytar) {
|
|
656
|
+
try {
|
|
657
|
+
if (await keytar.getPassword(SERVICE, ACCOUNT)) {
|
|
658
|
+
return {
|
|
659
|
+
kind: "legacy-keytar",
|
|
660
|
+
secure: true,
|
|
661
|
+
note: "stored in legacy keytar backend; will migrate to native keychain when possible"
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
if (await keytar.getPassword(LEGACY_SERVICE, ACCOUNT)) {
|
|
665
|
+
return {
|
|
666
|
+
kind: "legacy-keytar",
|
|
667
|
+
secure: true,
|
|
668
|
+
note: "stored in legacy keytar service exe-mem; will migrate to native exe-os keychain when possible"
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
} catch {
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
const keyPath = getKeyPath();
|
|
675
|
+
if (!existsSync3(keyPath)) {
|
|
676
|
+
return {
|
|
677
|
+
kind: "missing",
|
|
678
|
+
secure: false,
|
|
679
|
+
path: keyPath,
|
|
680
|
+
note: "no key found in OS keychain, legacy keytar, or file fallback"
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
try {
|
|
684
|
+
const content = (await readFile2(keyPath, "utf-8")).trim();
|
|
685
|
+
if (content.startsWith(ENCRYPTED_PREFIX)) {
|
|
686
|
+
return {
|
|
687
|
+
kind: "encrypted-file",
|
|
688
|
+
secure: true,
|
|
689
|
+
path: keyPath,
|
|
690
|
+
note: "stored in machine-bound encrypted file fallback"
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
if (isRootOnlyTrustedServerKeyFile(keyPath)) {
|
|
694
|
+
return {
|
|
695
|
+
kind: "server-secret-file",
|
|
696
|
+
secure: true,
|
|
697
|
+
path: keyPath,
|
|
698
|
+
note: "stored as root-only trusted server secret file"
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
return {
|
|
702
|
+
kind: "plaintext-file",
|
|
703
|
+
secure: false,
|
|
704
|
+
path: keyPath,
|
|
705
|
+
note: "stored in legacy plaintext file; reading it will migrate or encrypt it"
|
|
706
|
+
};
|
|
707
|
+
} catch {
|
|
708
|
+
return {
|
|
709
|
+
kind: "missing",
|
|
710
|
+
secure: false,
|
|
711
|
+
path: keyPath,
|
|
712
|
+
note: "key file exists but could not be read"
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
}
|
|
553
716
|
async function setMasterKey(key) {
|
|
554
717
|
const b64 = key.toString("base64");
|
|
555
718
|
if (macKeychainSet(b64) || linuxSecretSet(b64)) {
|
|
@@ -575,10 +738,13 @@ async function setMasterKey(key) {
|
|
|
575
738
|
async function deleteMasterKey() {
|
|
576
739
|
macKeychainDelete();
|
|
577
740
|
linuxSecretDelete();
|
|
741
|
+
macKeychainDelete(LEGACY_SERVICE);
|
|
742
|
+
linuxSecretDelete(LEGACY_SERVICE);
|
|
578
743
|
const keytar = await tryKeytar();
|
|
579
744
|
if (keytar) {
|
|
580
745
|
try {
|
|
581
746
|
await keytar.deletePassword(SERVICE, ACCOUNT);
|
|
747
|
+
await keytar.deletePassword(LEGACY_SERVICE, ACCOUNT);
|
|
582
748
|
} catch {
|
|
583
749
|
}
|
|
584
750
|
}
|
|
@@ -616,12 +782,14 @@ async function importMnemonic(mnemonic) {
|
|
|
616
782
|
const entropy = mnemonicToEntropy(trimmed);
|
|
617
783
|
return Buffer.from(entropy, "hex");
|
|
618
784
|
}
|
|
619
|
-
var SERVICE, ACCOUNT, ENCRYPTED_PREFIX;
|
|
785
|
+
var SERVICE, LEGACY_SERVICE, ACCOUNT, linuxSecretAvailability, ENCRYPTED_PREFIX;
|
|
620
786
|
var init_keychain = __esm({
|
|
621
787
|
"src/lib/keychain.ts"() {
|
|
622
788
|
"use strict";
|
|
623
|
-
SERVICE = "exe-
|
|
789
|
+
SERVICE = "exe-os";
|
|
790
|
+
LEGACY_SERVICE = "exe-mem";
|
|
624
791
|
ACCOUNT = "master-key";
|
|
792
|
+
linuxSecretAvailability = null;
|
|
625
793
|
ENCRYPTED_PREFIX = "enc:";
|
|
626
794
|
}
|
|
627
795
|
});
|
|
@@ -677,7 +845,7 @@ import net from "net";
|
|
|
677
845
|
import os3 from "os";
|
|
678
846
|
import { spawn } from "child_process";
|
|
679
847
|
import { randomUUID } from "crypto";
|
|
680
|
-
import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
|
|
848
|
+
import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync as statSync2 } from "fs";
|
|
681
849
|
import path5 from "path";
|
|
682
850
|
import { fileURLToPath } from "url";
|
|
683
851
|
function handleData(chunk) {
|
|
@@ -827,7 +995,7 @@ function acquireSpawnLock() {
|
|
|
827
995
|
return true;
|
|
828
996
|
} catch {
|
|
829
997
|
try {
|
|
830
|
-
const stat =
|
|
998
|
+
const stat = statSync2(SPAWN_LOCK_PATH);
|
|
831
999
|
if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
|
|
832
1000
|
try {
|
|
833
1001
|
unlinkSync2(SPAWN_LOCK_PATH);
|
|
@@ -988,7 +1156,7 @@ function killAndRespawnDaemon() {
|
|
|
988
1156
|
}
|
|
989
1157
|
function isDaemonTooYoung() {
|
|
990
1158
|
try {
|
|
991
|
-
const stat =
|
|
1159
|
+
const stat = statSync2(PID_PATH);
|
|
992
1160
|
return Date.now() - stat.mtimeMs < MIN_DAEMON_AGE_MS;
|
|
993
1161
|
} catch {
|
|
994
1162
|
return false;
|
|
@@ -1433,8 +1601,8 @@ async function validateLicense(apiKey, deviceId) {
|
|
|
1433
1601
|
}
|
|
1434
1602
|
function getCacheAgeMs() {
|
|
1435
1603
|
try {
|
|
1436
|
-
const { statSync:
|
|
1437
|
-
const s =
|
|
1604
|
+
const { statSync: statSync5 } = __require("fs");
|
|
1605
|
+
const s = statSync5(CACHE_PATH);
|
|
1438
1606
|
return Date.now() - s.mtimeMs;
|
|
1439
1607
|
} catch {
|
|
1440
1608
|
return Infinity;
|
|
@@ -2984,6 +3152,9 @@ function getClient() {
|
|
|
2984
3152
|
if (_daemonClient && _daemonClient._isDaemonActive()) {
|
|
2985
3153
|
return _daemonClient;
|
|
2986
3154
|
}
|
|
3155
|
+
if (!_resilientClient) {
|
|
3156
|
+
return _adapterClient;
|
|
3157
|
+
}
|
|
2987
3158
|
return _resilientClient;
|
|
2988
3159
|
}
|
|
2989
3160
|
async function initDaemonClient() {
|
|
@@ -4016,6 +4187,127 @@ async function ensureSchema() {
|
|
|
4016
4187
|
VALUES (new.rowid, new.content, new.subject, new.predicate, new.object);
|
|
4017
4188
|
END;
|
|
4018
4189
|
`);
|
|
4190
|
+
await client.executeMultiple(`
|
|
4191
|
+
CREATE TABLE IF NOT EXISTS agent_sessions (
|
|
4192
|
+
id TEXT PRIMARY KEY,
|
|
4193
|
+
agent_id TEXT NOT NULL,
|
|
4194
|
+
project_name TEXT,
|
|
4195
|
+
started_at TEXT NOT NULL,
|
|
4196
|
+
last_event_at TEXT NOT NULL,
|
|
4197
|
+
event_count INTEGER NOT NULL DEFAULT 0,
|
|
4198
|
+
properties TEXT DEFAULT '{}'
|
|
4199
|
+
);
|
|
4200
|
+
|
|
4201
|
+
CREATE INDEX IF NOT EXISTS idx_agent_sessions_agent_time
|
|
4202
|
+
ON agent_sessions(agent_id, started_at);
|
|
4203
|
+
|
|
4204
|
+
CREATE TABLE IF NOT EXISTS agent_goals (
|
|
4205
|
+
id TEXT PRIMARY KEY,
|
|
4206
|
+
statement TEXT NOT NULL,
|
|
4207
|
+
owner_agent_id TEXT,
|
|
4208
|
+
project_name TEXT,
|
|
4209
|
+
status TEXT NOT NULL DEFAULT 'open',
|
|
4210
|
+
priority INTEGER NOT NULL DEFAULT 5,
|
|
4211
|
+
success_criteria TEXT,
|
|
4212
|
+
parent_goal_id TEXT,
|
|
4213
|
+
due_at TEXT,
|
|
4214
|
+
achieved_at TEXT,
|
|
4215
|
+
supersedes_id TEXT,
|
|
4216
|
+
created_at TEXT NOT NULL,
|
|
4217
|
+
updated_at TEXT NOT NULL,
|
|
4218
|
+
source_memory_id TEXT
|
|
4219
|
+
);
|
|
4220
|
+
|
|
4221
|
+
CREATE INDEX IF NOT EXISTS idx_agent_goals_project_status
|
|
4222
|
+
ON agent_goals(project_name, status, priority);
|
|
4223
|
+
|
|
4224
|
+
CREATE TABLE IF NOT EXISTS agent_events (
|
|
4225
|
+
id TEXT PRIMARY KEY,
|
|
4226
|
+
event_type TEXT NOT NULL,
|
|
4227
|
+
occurred_at TEXT NOT NULL,
|
|
4228
|
+
sequence_index INTEGER NOT NULL,
|
|
4229
|
+
actor_agent_id TEXT,
|
|
4230
|
+
agent_role TEXT,
|
|
4231
|
+
project_name TEXT,
|
|
4232
|
+
session_id TEXT,
|
|
4233
|
+
task_id TEXT,
|
|
4234
|
+
goal_id TEXT,
|
|
4235
|
+
parent_event_id TEXT,
|
|
4236
|
+
intention TEXT,
|
|
4237
|
+
outcome TEXT,
|
|
4238
|
+
evidence_memory_id TEXT,
|
|
4239
|
+
impact TEXT,
|
|
4240
|
+
payload TEXT DEFAULT '{}',
|
|
4241
|
+
created_at TEXT NOT NULL
|
|
4242
|
+
);
|
|
4243
|
+
|
|
4244
|
+
CREATE INDEX IF NOT EXISTS idx_agent_events_time
|
|
4245
|
+
ON agent_events(occurred_at, sequence_index);
|
|
4246
|
+
|
|
4247
|
+
CREATE INDEX IF NOT EXISTS idx_agent_events_session_seq
|
|
4248
|
+
ON agent_events(session_id, sequence_index);
|
|
4249
|
+
|
|
4250
|
+
CREATE INDEX IF NOT EXISTS idx_agent_events_goal_time
|
|
4251
|
+
ON agent_events(goal_id, occurred_at);
|
|
4252
|
+
|
|
4253
|
+
CREATE INDEX IF NOT EXISTS idx_agent_events_memory
|
|
4254
|
+
ON agent_events(evidence_memory_id);
|
|
4255
|
+
|
|
4256
|
+
CREATE TABLE IF NOT EXISTS agent_goal_links (
|
|
4257
|
+
id TEXT PRIMARY KEY,
|
|
4258
|
+
goal_id TEXT NOT NULL,
|
|
4259
|
+
link_type TEXT NOT NULL,
|
|
4260
|
+
target_id TEXT NOT NULL,
|
|
4261
|
+
target_type TEXT NOT NULL,
|
|
4262
|
+
created_at TEXT NOT NULL
|
|
4263
|
+
);
|
|
4264
|
+
|
|
4265
|
+
CREATE INDEX IF NOT EXISTS idx_agent_goal_links_goal
|
|
4266
|
+
ON agent_goal_links(goal_id, target_type);
|
|
4267
|
+
|
|
4268
|
+
CREATE TABLE IF NOT EXISTS agent_semantic_labels (
|
|
4269
|
+
id TEXT PRIMARY KEY,
|
|
4270
|
+
source_memory_id TEXT NOT NULL,
|
|
4271
|
+
event_id TEXT,
|
|
4272
|
+
labeler TEXT NOT NULL,
|
|
4273
|
+
schema_version INTEGER NOT NULL DEFAULT 1,
|
|
4274
|
+
confidence REAL NOT NULL DEFAULT 0,
|
|
4275
|
+
labels TEXT NOT NULL,
|
|
4276
|
+
created_at TEXT NOT NULL,
|
|
4277
|
+
updated_at TEXT NOT NULL
|
|
4278
|
+
);
|
|
4279
|
+
|
|
4280
|
+
CREATE INDEX IF NOT EXISTS idx_agent_semantic_labels_memory
|
|
4281
|
+
ON agent_semantic_labels(source_memory_id, labeler);
|
|
4282
|
+
|
|
4283
|
+
CREATE INDEX IF NOT EXISTS idx_agent_semantic_labels_event
|
|
4284
|
+
ON agent_semantic_labels(event_id);
|
|
4285
|
+
|
|
4286
|
+
CREATE TABLE IF NOT EXISTS agent_reflection_checkpoints (
|
|
4287
|
+
id TEXT PRIMARY KEY,
|
|
4288
|
+
project_name TEXT,
|
|
4289
|
+
session_id TEXT,
|
|
4290
|
+
window_start_at TEXT NOT NULL,
|
|
4291
|
+
window_end_at TEXT NOT NULL,
|
|
4292
|
+
event_count INTEGER NOT NULL DEFAULT 0,
|
|
4293
|
+
goal_count INTEGER NOT NULL DEFAULT 0,
|
|
4294
|
+
success_count INTEGER NOT NULL DEFAULT 0,
|
|
4295
|
+
failure_count INTEGER NOT NULL DEFAULT 0,
|
|
4296
|
+
risk_count INTEGER NOT NULL DEFAULT 0,
|
|
4297
|
+
summary TEXT NOT NULL,
|
|
4298
|
+
learnings TEXT NOT NULL DEFAULT '[]',
|
|
4299
|
+
next_actions TEXT NOT NULL DEFAULT '[]',
|
|
4300
|
+
evidence_event_ids TEXT NOT NULL DEFAULT '[]',
|
|
4301
|
+
confidence REAL NOT NULL DEFAULT 0,
|
|
4302
|
+
created_at TEXT NOT NULL
|
|
4303
|
+
);
|
|
4304
|
+
|
|
4305
|
+
CREATE INDEX IF NOT EXISTS idx_agent_reflection_project_time
|
|
4306
|
+
ON agent_reflection_checkpoints(project_name, window_end_at);
|
|
4307
|
+
|
|
4308
|
+
CREATE INDEX IF NOT EXISTS idx_agent_reflection_session_time
|
|
4309
|
+
ON agent_reflection_checkpoints(session_id, window_end_at);
|
|
4310
|
+
`);
|
|
4019
4311
|
try {
|
|
4020
4312
|
await client.execute({
|
|
4021
4313
|
sql: `ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3`,
|
|
@@ -4420,7 +4712,7 @@ __export(db_backup_exports, {
|
|
|
4420
4712
|
listBackups: () => listBackups,
|
|
4421
4713
|
rotateBackups: () => rotateBackups
|
|
4422
4714
|
});
|
|
4423
|
-
import { copyFileSync, existsSync as existsSync11, mkdirSync as mkdirSync4, readdirSync, unlinkSync as unlinkSync5, statSync as
|
|
4715
|
+
import { copyFileSync, existsSync as existsSync11, mkdirSync as mkdirSync4, readdirSync, unlinkSync as unlinkSync5, statSync as statSync3 } from "fs";
|
|
4424
4716
|
import path11 from "path";
|
|
4425
4717
|
function findActiveDb() {
|
|
4426
4718
|
for (const name of DB_NAMES) {
|
|
@@ -4464,7 +4756,7 @@ function rotateBackups(keepDays = DEFAULT_KEEP_DAYS) {
|
|
|
4464
4756
|
if (!file.endsWith(".db") && !file.endsWith(".db-wal") && !file.endsWith(".db-shm")) continue;
|
|
4465
4757
|
const filePath = path11.join(BACKUP_DIR, file);
|
|
4466
4758
|
try {
|
|
4467
|
-
const stat =
|
|
4759
|
+
const stat = statSync3(filePath);
|
|
4468
4760
|
if (stat.mtimeMs < cutoff) {
|
|
4469
4761
|
unlinkSync5(filePath);
|
|
4470
4762
|
deleted++;
|
|
@@ -4482,7 +4774,7 @@ function listBackups() {
|
|
|
4482
4774
|
const files = readdirSync(BACKUP_DIR).filter((f) => f.endsWith(".db") && !f.endsWith("-wal") && !f.endsWith("-shm"));
|
|
4483
4775
|
return files.map((name) => {
|
|
4484
4776
|
const p = path11.join(BACKUP_DIR, name);
|
|
4485
|
-
const stat =
|
|
4777
|
+
const stat = statSync3(p);
|
|
4486
4778
|
return { path: p, name, size: stat.size, date: stat.mtime };
|
|
4487
4779
|
}).sort((a, b) => b.date.getTime() - a.date.getTime());
|
|
4488
4780
|
} catch {
|
|
@@ -4515,10 +4807,10 @@ var init_db_backup = __esm({
|
|
|
4515
4807
|
// src/lib/cloud-sync.ts
|
|
4516
4808
|
var cloud_sync_exports = {};
|
|
4517
4809
|
__export(cloud_sync_exports, {
|
|
4518
|
-
|
|
4810
|
+
CLOUD_REUPLOAD_REQUIRED_MESSAGE: () => CLOUD_REUPLOAD_REQUIRED_MESSAGE,
|
|
4519
4811
|
assertSecureEndpoint: () => assertSecureEndpoint,
|
|
4520
4812
|
buildRosterBlob: () => buildRosterBlob,
|
|
4521
|
-
|
|
4813
|
+
clearCloudReuploadRequired: () => clearCloudReuploadRequired,
|
|
4522
4814
|
cloudPull: () => cloudPull,
|
|
4523
4815
|
cloudPullBehaviors: () => cloudPullBehaviors,
|
|
4524
4816
|
cloudPullBlob: () => cloudPullBlob,
|
|
@@ -4537,14 +4829,16 @@ __export(cloud_sync_exports, {
|
|
|
4537
4829
|
cloudPushGraphRAG: () => cloudPushGraphRAG,
|
|
4538
4830
|
cloudPushRoster: () => cloudPushRoster,
|
|
4539
4831
|
cloudPushTasks: () => cloudPushTasks,
|
|
4832
|
+
cloudResetMemoryBlobs: () => cloudResetMemoryBlobs,
|
|
4540
4833
|
cloudSync: () => cloudSync,
|
|
4541
|
-
|
|
4834
|
+
getCloudReuploadRequired: () => getCloudReuploadRequired,
|
|
4835
|
+
markCloudReuploadRequired: () => markCloudReuploadRequired,
|
|
4542
4836
|
mergeConfig: () => mergeConfig,
|
|
4543
4837
|
mergeRosterFromRemote: () => mergeRosterFromRemote,
|
|
4544
4838
|
pushToPostgres: () => pushToPostgres,
|
|
4545
4839
|
recordRosterDeletion: () => recordRosterDeletion
|
|
4546
4840
|
});
|
|
4547
|
-
import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync12, readdirSync as readdirSync2, mkdirSync as mkdirSync5, appendFileSync, unlinkSync as unlinkSync6, openSync as openSync2, closeSync as closeSync2, statSync as
|
|
4841
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync12, readdirSync as readdirSync2, mkdirSync as mkdirSync5, appendFileSync, unlinkSync as unlinkSync6, openSync as openSync2, closeSync as closeSync2, statSync as statSync4 } from "fs";
|
|
4548
4842
|
import crypto3 from "crypto";
|
|
4549
4843
|
import path12 from "path";
|
|
4550
4844
|
import { homedir as homedir2 } from "os";
|
|
@@ -4591,18 +4885,36 @@ function loadPgClient() {
|
|
|
4591
4885
|
const { pathToFileURL: pathToFileURL3 } = await import("url");
|
|
4592
4886
|
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
4593
4887
|
if (explicitPath) {
|
|
4594
|
-
const
|
|
4595
|
-
const
|
|
4596
|
-
if (!
|
|
4597
|
-
return new
|
|
4888
|
+
const mod = await import(pathToFileURL3(explicitPath).href);
|
|
4889
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
4890
|
+
if (!Ctor) throw new Error(`No PrismaClient at ${explicitPath}`);
|
|
4891
|
+
return new Ctor();
|
|
4598
4892
|
}
|
|
4599
4893
|
const exeDbRoot = process.env.EXE_DB_ROOT ?? path12.join(homedir2(), "exe-db");
|
|
4600
|
-
const
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4894
|
+
const packagePath = path12.join(exeDbRoot, "package.json");
|
|
4895
|
+
if (existsSync12(packagePath)) {
|
|
4896
|
+
const req = createRequire3(packagePath);
|
|
4897
|
+
const entry = req.resolve("@prisma/client");
|
|
4898
|
+
const mod = await import(pathToFileURL3(entry).href);
|
|
4899
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
4900
|
+
if (!Ctor) throw new Error("No PrismaClient");
|
|
4901
|
+
return new Ctor();
|
|
4902
|
+
}
|
|
4903
|
+
const { Pool } = await import("pg");
|
|
4904
|
+
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
|
|
4905
|
+
return {
|
|
4906
|
+
async $queryRawUnsafe(query, ...values) {
|
|
4907
|
+
const result = await pool.query(query, values);
|
|
4908
|
+
return result.rows;
|
|
4909
|
+
},
|
|
4910
|
+
async $executeRawUnsafe(query, ...values) {
|
|
4911
|
+
const result = await pool.query(query, values);
|
|
4912
|
+
return result.rowCount ?? 0;
|
|
4913
|
+
},
|
|
4914
|
+
async $disconnect() {
|
|
4915
|
+
await pool.end();
|
|
4916
|
+
}
|
|
4917
|
+
};
|
|
4606
4918
|
})().catch(() => {
|
|
4607
4919
|
_pgFailed = true;
|
|
4608
4920
|
_pgPromise = null;
|
|
@@ -4623,7 +4935,7 @@ async function pushToPostgres(records) {
|
|
|
4623
4935
|
let inserted = 0;
|
|
4624
4936
|
for (const rec of records) {
|
|
4625
4937
|
try {
|
|
4626
|
-
await prisma.$executeRawUnsafe(
|
|
4938
|
+
const changed = await prisma.$executeRawUnsafe(
|
|
4627
4939
|
`INSERT INTO raw.raw_events (id, source, source_id, event_type, payload, metadata, timestamp)
|
|
4628
4940
|
VALUES (gen_random_uuid(), 'cloud_sync', $1, 'memory', $2::jsonb, $3::jsonb, $4)
|
|
4629
4941
|
ON CONFLICT (source, source_id, event_type) DO NOTHING`,
|
|
@@ -4632,7 +4944,7 @@ async function pushToPostgres(records) {
|
|
|
4632
4944
|
JSON.stringify({ agent_id: rec.agent_id, project_name: rec.project_name, tool_name: rec.tool_name }),
|
|
4633
4945
|
rec.timestamp ? new Date(String(rec.timestamp)) : /* @__PURE__ */ new Date()
|
|
4634
4946
|
);
|
|
4635
|
-
inserted
|
|
4947
|
+
inserted += Number(changed ?? 0);
|
|
4636
4948
|
} catch {
|
|
4637
4949
|
}
|
|
4638
4950
|
}
|
|
@@ -4737,6 +5049,23 @@ async function cloudPush(records, maxVersion, config) {
|
|
|
4737
5049
|
return false;
|
|
4738
5050
|
}
|
|
4739
5051
|
}
|
|
5052
|
+
async function cloudResetMemoryBlobs(config) {
|
|
5053
|
+
assertSecureEndpoint(config.endpoint);
|
|
5054
|
+
const resp = await fetchWithRetry(`${config.endpoint}/sync/reset-memory`, {
|
|
5055
|
+
method: "POST",
|
|
5056
|
+
headers: {
|
|
5057
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
5058
|
+
"Content-Type": "application/json",
|
|
5059
|
+
"X-Device-Id": loadDeviceId()
|
|
5060
|
+
},
|
|
5061
|
+
body: JSON.stringify({ confirm: "LOCAL DB IS SOURCE OF TRUTH" })
|
|
5062
|
+
});
|
|
5063
|
+
if (!resp.ok) {
|
|
5064
|
+
throw new Error(`cloud reset failed: HTTP ${resp.status}`);
|
|
5065
|
+
}
|
|
5066
|
+
const data = await resp.json();
|
|
5067
|
+
return { deleted: Number(data.deleted ?? 0), freedBytes: Number(data.freed_bytes ?? 0) };
|
|
5068
|
+
}
|
|
4740
5069
|
async function cloudPull(sinceVersion, config) {
|
|
4741
5070
|
assertSecureEndpoint(config.endpoint);
|
|
4742
5071
|
try {
|
|
@@ -4756,40 +5085,61 @@ async function cloudPull(sinceVersion, config) {
|
|
|
4756
5085
|
if (!response.ok) return { records: [], maxVersion: sinceVersion };
|
|
4757
5086
|
const data = await response.json();
|
|
4758
5087
|
const allRecords = [];
|
|
4759
|
-
|
|
5088
|
+
let maxReadableVersion = sinceVersion;
|
|
5089
|
+
let skippedBlobs = 0;
|
|
5090
|
+
for (const { version, blob } of data.blobs ?? []) {
|
|
4760
5091
|
try {
|
|
4761
5092
|
const compressed = decryptSyncBlob(blob);
|
|
4762
5093
|
const json = decompress(compressed).toString("utf8");
|
|
4763
5094
|
const records = JSON.parse(json);
|
|
4764
5095
|
allRecords.push(...records);
|
|
5096
|
+
const recordMax = records.reduce((max, rec) => {
|
|
5097
|
+
const v = Number(rec.version ?? 0);
|
|
5098
|
+
return Number.isFinite(v) ? Math.max(max, v) : max;
|
|
5099
|
+
}, 0);
|
|
5100
|
+
const blobVersion = Number(version ?? 0);
|
|
5101
|
+
maxReadableVersion = Math.max(
|
|
5102
|
+
maxReadableVersion,
|
|
5103
|
+
Number.isFinite(blobVersion) ? blobVersion : 0,
|
|
5104
|
+
recordMax
|
|
5105
|
+
);
|
|
4765
5106
|
} catch {
|
|
5107
|
+
skippedBlobs++;
|
|
4766
5108
|
continue;
|
|
4767
5109
|
}
|
|
4768
5110
|
}
|
|
4769
|
-
|
|
5111
|
+
if (skippedBlobs > 0) {
|
|
5112
|
+
logError(`[cloud-sync] PULL skipped ${skippedBlobs} undecryptable blob(s); pull cursor advanced only to last readable version ${maxReadableVersion}`);
|
|
5113
|
+
}
|
|
5114
|
+
return { records: allRecords, maxVersion: maxReadableVersion };
|
|
4770
5115
|
} catch (err) {
|
|
4771
5116
|
logError(`[cloud-sync] PULL FAILED: ${err instanceof Error ? err.message : String(err)}`);
|
|
4772
5117
|
return { records: [], maxVersion: sinceVersion };
|
|
4773
5118
|
}
|
|
4774
5119
|
}
|
|
4775
|
-
async function
|
|
5120
|
+
async function getCloudReuploadRequired(client = getClient()) {
|
|
4776
5121
|
try {
|
|
4777
5122
|
await client.execute("CREATE TABLE IF NOT EXISTS sync_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)");
|
|
4778
|
-
const
|
|
4779
|
-
return String(
|
|
5123
|
+
const result = await client.execute("SELECT key, value FROM sync_meta WHERE key IN ('cloud_reupload_required', 'cloud_relink_required')");
|
|
5124
|
+
return result.rows.some((row) => String(row.value ?? "") === "1");
|
|
4780
5125
|
} catch {
|
|
4781
5126
|
return false;
|
|
4782
5127
|
}
|
|
4783
5128
|
}
|
|
4784
|
-
async function
|
|
5129
|
+
async function clearCloudReuploadRequired(client = getClient()) {
|
|
4785
5130
|
await client.execute("CREATE TABLE IF NOT EXISTS sync_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)");
|
|
5131
|
+
await client.execute("INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('cloud_reupload_required', '0')");
|
|
4786
5132
|
await client.execute("INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('cloud_relink_required', '0')");
|
|
4787
5133
|
await client.execute({
|
|
4788
|
-
sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('
|
|
5134
|
+
sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('cloud_reuploaded_at', ?)",
|
|
4789
5135
|
args: [(/* @__PURE__ */ new Date()).toISOString()]
|
|
4790
5136
|
});
|
|
4791
5137
|
await client.execute("DELETE FROM sync_meta WHERE key IN ('last_cloud_pull_version', 'last_cloud_push_version')");
|
|
4792
5138
|
}
|
|
5139
|
+
async function markCloudReuploadRequired(client = getClient()) {
|
|
5140
|
+
await client.execute("CREATE TABLE IF NOT EXISTS sync_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)");
|
|
5141
|
+
await client.execute("INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('cloud_reupload_required', '1')");
|
|
5142
|
+
}
|
|
4793
5143
|
async function cloudSync(config) {
|
|
4794
5144
|
if (!isSyncCryptoInitialized()) {
|
|
4795
5145
|
try {
|
|
@@ -4811,10 +5161,10 @@ async function cloudSync(config) {
|
|
|
4811
5161
|
throw new Error("[cloud-sync] Database not initialized. Call initStore() before cloudSync().");
|
|
4812
5162
|
}
|
|
4813
5163
|
try {
|
|
4814
|
-
if (await
|
|
5164
|
+
if (await getCloudReuploadRequired(client)) throw new Error(CLOUD_REUPLOAD_REQUIRED_MESSAGE);
|
|
4815
5165
|
} catch (err) {
|
|
4816
5166
|
const msg = err instanceof Error ? err.message : String(err);
|
|
4817
|
-
if (msg.includes("
|
|
5167
|
+
if (msg === CLOUD_REUPLOAD_REQUIRED_MESSAGE || msg.includes("key rotation")) throw err;
|
|
4818
5168
|
}
|
|
4819
5169
|
try {
|
|
4820
5170
|
const { getRawClient: getRawClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
@@ -5072,7 +5422,7 @@ async function cloudSync(config) {
|
|
|
5072
5422
|
const { getLatestBackup: getLatestBackup2 } = await Promise.resolve().then(() => (init_db_backup(), db_backup_exports));
|
|
5073
5423
|
const latestBackup = getLatestBackup2();
|
|
5074
5424
|
if (latestBackup) {
|
|
5075
|
-
const backupSize =
|
|
5425
|
+
const backupSize = statSync4(latestBackup).size;
|
|
5076
5426
|
const MAX_CLOUD_BACKUP_BYTES = 50 * 1024 * 1024;
|
|
5077
5427
|
if (backupSize <= MAX_CLOUD_BACKUP_BYTES) {
|
|
5078
5428
|
const backupData = readFileSync8(latestBackup);
|
|
@@ -5743,7 +6093,7 @@ async function cloudPullDocuments(config) {
|
|
|
5743
6093
|
}
|
|
5744
6094
|
return { pulled };
|
|
5745
6095
|
}
|
|
5746
|
-
var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, _pgPromise, _pgFailed,
|
|
6096
|
+
var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, _pgPromise, _pgFailed, CLOUD_REUPLOAD_REQUIRED_MESSAGE, ROSTER_DELETIONS_PATH;
|
|
5747
6097
|
var init_cloud_sync = __esm({
|
|
5748
6098
|
"src/lib/cloud-sync.ts"() {
|
|
5749
6099
|
"use strict";
|
|
@@ -5762,7 +6112,7 @@ var init_cloud_sync = __esm({
|
|
|
5762
6112
|
LOCK_STALE_MS = 3e4;
|
|
5763
6113
|
_pgPromise = null;
|
|
5764
6114
|
_pgFailed = false;
|
|
5765
|
-
|
|
6115
|
+
CLOUD_REUPLOAD_REQUIRED_MESSAGE = "Cloud sync is blocked because this device rotated its memory encryption key. Run `exe-os cloud reupload` first to re-upload the cloud backup with the new key.";
|
|
5766
6116
|
ROSTER_DELETIONS_PATH = path12.join(EXE_AI_DIR, "roster-deletions.json");
|
|
5767
6117
|
}
|
|
5768
6118
|
});
|
|
@@ -7822,8 +8172,8 @@ async function validateModel(log) {
|
|
|
7822
8172
|
log("Skipping in-memory model validation (low memory \u2014 will validate on first use).");
|
|
7823
8173
|
const modelPath = path17.join(MODELS_DIR, LOCAL_FILENAME);
|
|
7824
8174
|
if (existsSync17(modelPath)) {
|
|
7825
|
-
const { statSync:
|
|
7826
|
-
const size =
|
|
8175
|
+
const { statSync: statSync5 } = await import("fs");
|
|
8176
|
+
const size = statSync5(modelPath).size;
|
|
7827
8177
|
if (size > 300 * 1e6) {
|
|
7828
8178
|
log(`Model file verified (${(size / 1e6).toFixed(0)} MB).`);
|
|
7829
8179
|
return;
|