@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/exe-boot.js
CHANGED
|
@@ -184,6 +184,10 @@ async function loadConfig() {
|
|
|
184
184
|
if (config.dbPath.startsWith("~")) {
|
|
185
185
|
config.dbPath = config.dbPath.replace(/^~/, os.homedir());
|
|
186
186
|
}
|
|
187
|
+
const envDbPath = path.join(dir, "memories.db");
|
|
188
|
+
if (process.env.EXE_OS_DIR && config.dbPath !== envDbPath && !existsSync2(config.dbPath) && existsSync2(envDbPath)) {
|
|
189
|
+
config.dbPath = envDbPath;
|
|
190
|
+
}
|
|
187
191
|
return config;
|
|
188
192
|
} catch {
|
|
189
193
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
@@ -204,7 +208,15 @@ function loadConfigSync() {
|
|
|
204
208
|
normalizeSessionLifecycle(migratedCfg);
|
|
205
209
|
normalizeAutoUpdate(migratedCfg);
|
|
206
210
|
normalizeOrchestration(migratedCfg);
|
|
207
|
-
|
|
211
|
+
const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
212
|
+
if (config.dbPath.startsWith("~")) {
|
|
213
|
+
config.dbPath = config.dbPath.replace(/^~/, os.homedir());
|
|
214
|
+
}
|
|
215
|
+
const envDbPath = path.join(dir, "memories.db");
|
|
216
|
+
if (process.env.EXE_OS_DIR && config.dbPath !== envDbPath && !existsSync2(config.dbPath) && existsSync2(envDbPath)) {
|
|
217
|
+
config.dbPath = envDbPath;
|
|
218
|
+
}
|
|
219
|
+
return config;
|
|
208
220
|
} catch {
|
|
209
221
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
210
222
|
}
|
|
@@ -1823,6 +1835,9 @@ function getClient() {
|
|
|
1823
1835
|
if (_daemonClient && _daemonClient._isDaemonActive()) {
|
|
1824
1836
|
return _daemonClient;
|
|
1825
1837
|
}
|
|
1838
|
+
if (!_resilientClient) {
|
|
1839
|
+
return _adapterClient;
|
|
1840
|
+
}
|
|
1826
1841
|
return _resilientClient;
|
|
1827
1842
|
}
|
|
1828
1843
|
async function initDaemonClient() {
|
|
@@ -2855,6 +2870,127 @@ async function ensureSchema() {
|
|
|
2855
2870
|
VALUES (new.rowid, new.content, new.subject, new.predicate, new.object);
|
|
2856
2871
|
END;
|
|
2857
2872
|
`);
|
|
2873
|
+
await client.executeMultiple(`
|
|
2874
|
+
CREATE TABLE IF NOT EXISTS agent_sessions (
|
|
2875
|
+
id TEXT PRIMARY KEY,
|
|
2876
|
+
agent_id TEXT NOT NULL,
|
|
2877
|
+
project_name TEXT,
|
|
2878
|
+
started_at TEXT NOT NULL,
|
|
2879
|
+
last_event_at TEXT NOT NULL,
|
|
2880
|
+
event_count INTEGER NOT NULL DEFAULT 0,
|
|
2881
|
+
properties TEXT DEFAULT '{}'
|
|
2882
|
+
);
|
|
2883
|
+
|
|
2884
|
+
CREATE INDEX IF NOT EXISTS idx_agent_sessions_agent_time
|
|
2885
|
+
ON agent_sessions(agent_id, started_at);
|
|
2886
|
+
|
|
2887
|
+
CREATE TABLE IF NOT EXISTS agent_goals (
|
|
2888
|
+
id TEXT PRIMARY KEY,
|
|
2889
|
+
statement TEXT NOT NULL,
|
|
2890
|
+
owner_agent_id TEXT,
|
|
2891
|
+
project_name TEXT,
|
|
2892
|
+
status TEXT NOT NULL DEFAULT 'open',
|
|
2893
|
+
priority INTEGER NOT NULL DEFAULT 5,
|
|
2894
|
+
success_criteria TEXT,
|
|
2895
|
+
parent_goal_id TEXT,
|
|
2896
|
+
due_at TEXT,
|
|
2897
|
+
achieved_at TEXT,
|
|
2898
|
+
supersedes_id TEXT,
|
|
2899
|
+
created_at TEXT NOT NULL,
|
|
2900
|
+
updated_at TEXT NOT NULL,
|
|
2901
|
+
source_memory_id TEXT
|
|
2902
|
+
);
|
|
2903
|
+
|
|
2904
|
+
CREATE INDEX IF NOT EXISTS idx_agent_goals_project_status
|
|
2905
|
+
ON agent_goals(project_name, status, priority);
|
|
2906
|
+
|
|
2907
|
+
CREATE TABLE IF NOT EXISTS agent_events (
|
|
2908
|
+
id TEXT PRIMARY KEY,
|
|
2909
|
+
event_type TEXT NOT NULL,
|
|
2910
|
+
occurred_at TEXT NOT NULL,
|
|
2911
|
+
sequence_index INTEGER NOT NULL,
|
|
2912
|
+
actor_agent_id TEXT,
|
|
2913
|
+
agent_role TEXT,
|
|
2914
|
+
project_name TEXT,
|
|
2915
|
+
session_id TEXT,
|
|
2916
|
+
task_id TEXT,
|
|
2917
|
+
goal_id TEXT,
|
|
2918
|
+
parent_event_id TEXT,
|
|
2919
|
+
intention TEXT,
|
|
2920
|
+
outcome TEXT,
|
|
2921
|
+
evidence_memory_id TEXT,
|
|
2922
|
+
impact TEXT,
|
|
2923
|
+
payload TEXT DEFAULT '{}',
|
|
2924
|
+
created_at TEXT NOT NULL
|
|
2925
|
+
);
|
|
2926
|
+
|
|
2927
|
+
CREATE INDEX IF NOT EXISTS idx_agent_events_time
|
|
2928
|
+
ON agent_events(occurred_at, sequence_index);
|
|
2929
|
+
|
|
2930
|
+
CREATE INDEX IF NOT EXISTS idx_agent_events_session_seq
|
|
2931
|
+
ON agent_events(session_id, sequence_index);
|
|
2932
|
+
|
|
2933
|
+
CREATE INDEX IF NOT EXISTS idx_agent_events_goal_time
|
|
2934
|
+
ON agent_events(goal_id, occurred_at);
|
|
2935
|
+
|
|
2936
|
+
CREATE INDEX IF NOT EXISTS idx_agent_events_memory
|
|
2937
|
+
ON agent_events(evidence_memory_id);
|
|
2938
|
+
|
|
2939
|
+
CREATE TABLE IF NOT EXISTS agent_goal_links (
|
|
2940
|
+
id TEXT PRIMARY KEY,
|
|
2941
|
+
goal_id TEXT NOT NULL,
|
|
2942
|
+
link_type TEXT NOT NULL,
|
|
2943
|
+
target_id TEXT NOT NULL,
|
|
2944
|
+
target_type TEXT NOT NULL,
|
|
2945
|
+
created_at TEXT NOT NULL
|
|
2946
|
+
);
|
|
2947
|
+
|
|
2948
|
+
CREATE INDEX IF NOT EXISTS idx_agent_goal_links_goal
|
|
2949
|
+
ON agent_goal_links(goal_id, target_type);
|
|
2950
|
+
|
|
2951
|
+
CREATE TABLE IF NOT EXISTS agent_semantic_labels (
|
|
2952
|
+
id TEXT PRIMARY KEY,
|
|
2953
|
+
source_memory_id TEXT NOT NULL,
|
|
2954
|
+
event_id TEXT,
|
|
2955
|
+
labeler TEXT NOT NULL,
|
|
2956
|
+
schema_version INTEGER NOT NULL DEFAULT 1,
|
|
2957
|
+
confidence REAL NOT NULL DEFAULT 0,
|
|
2958
|
+
labels TEXT NOT NULL,
|
|
2959
|
+
created_at TEXT NOT NULL,
|
|
2960
|
+
updated_at TEXT NOT NULL
|
|
2961
|
+
);
|
|
2962
|
+
|
|
2963
|
+
CREATE INDEX IF NOT EXISTS idx_agent_semantic_labels_memory
|
|
2964
|
+
ON agent_semantic_labels(source_memory_id, labeler);
|
|
2965
|
+
|
|
2966
|
+
CREATE INDEX IF NOT EXISTS idx_agent_semantic_labels_event
|
|
2967
|
+
ON agent_semantic_labels(event_id);
|
|
2968
|
+
|
|
2969
|
+
CREATE TABLE IF NOT EXISTS agent_reflection_checkpoints (
|
|
2970
|
+
id TEXT PRIMARY KEY,
|
|
2971
|
+
project_name TEXT,
|
|
2972
|
+
session_id TEXT,
|
|
2973
|
+
window_start_at TEXT NOT NULL,
|
|
2974
|
+
window_end_at TEXT NOT NULL,
|
|
2975
|
+
event_count INTEGER NOT NULL DEFAULT 0,
|
|
2976
|
+
goal_count INTEGER NOT NULL DEFAULT 0,
|
|
2977
|
+
success_count INTEGER NOT NULL DEFAULT 0,
|
|
2978
|
+
failure_count INTEGER NOT NULL DEFAULT 0,
|
|
2979
|
+
risk_count INTEGER NOT NULL DEFAULT 0,
|
|
2980
|
+
summary TEXT NOT NULL,
|
|
2981
|
+
learnings TEXT NOT NULL DEFAULT '[]',
|
|
2982
|
+
next_actions TEXT NOT NULL DEFAULT '[]',
|
|
2983
|
+
evidence_event_ids TEXT NOT NULL DEFAULT '[]',
|
|
2984
|
+
confidence REAL NOT NULL DEFAULT 0,
|
|
2985
|
+
created_at TEXT NOT NULL
|
|
2986
|
+
);
|
|
2987
|
+
|
|
2988
|
+
CREATE INDEX IF NOT EXISTS idx_agent_reflection_project_time
|
|
2989
|
+
ON agent_reflection_checkpoints(project_name, window_end_at);
|
|
2990
|
+
|
|
2991
|
+
CREATE INDEX IF NOT EXISTS idx_agent_reflection_session_time
|
|
2992
|
+
ON agent_reflection_checkpoints(session_id, window_end_at);
|
|
2993
|
+
`);
|
|
2858
2994
|
try {
|
|
2859
2995
|
await client.execute({
|
|
2860
2996
|
sql: `ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3`,
|
|
@@ -3274,12 +3410,13 @@ var keychain_exports = {};
|
|
|
3274
3410
|
__export(keychain_exports, {
|
|
3275
3411
|
deleteMasterKey: () => deleteMasterKey,
|
|
3276
3412
|
exportMnemonic: () => exportMnemonic,
|
|
3413
|
+
getKeyStorageInfo: () => getKeyStorageInfo,
|
|
3277
3414
|
getMasterKey: () => getMasterKey,
|
|
3278
3415
|
importMnemonic: () => importMnemonic,
|
|
3279
3416
|
setMasterKey: () => setMasterKey
|
|
3280
3417
|
});
|
|
3281
3418
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
3282
|
-
import { existsSync as existsSync6 } from "fs";
|
|
3419
|
+
import { existsSync as existsSync6, statSync as statSync2 } from "fs";
|
|
3283
3420
|
import { execSync as execSync2 } from "child_process";
|
|
3284
3421
|
import path6 from "path";
|
|
3285
3422
|
import os5 from "os";
|
|
@@ -3289,29 +3426,65 @@ function getKeyDir() {
|
|
|
3289
3426
|
function getKeyPath() {
|
|
3290
3427
|
return path6.join(getKeyDir(), "master.key");
|
|
3291
3428
|
}
|
|
3292
|
-
function
|
|
3429
|
+
function nativeKeychainAllowed() {
|
|
3430
|
+
return process.env.EXE_OS_DISABLE_NATIVE_KEYCHAIN !== "1";
|
|
3431
|
+
}
|
|
3432
|
+
function linuxSecretAvailable() {
|
|
3433
|
+
if (!nativeKeychainAllowed()) return false;
|
|
3434
|
+
if (process.platform !== "linux") return false;
|
|
3435
|
+
if (linuxSecretAvailability !== null) return linuxSecretAvailability;
|
|
3436
|
+
try {
|
|
3437
|
+
execSync2("command -v secret-tool >/dev/null 2>&1", { timeout: 1e3 });
|
|
3438
|
+
} catch {
|
|
3439
|
+
linuxSecretAvailability = false;
|
|
3440
|
+
return false;
|
|
3441
|
+
}
|
|
3442
|
+
try {
|
|
3443
|
+
execSync2("secret-tool search --all exe-os probe >/dev/null 2>&1", { timeout: 1e3 });
|
|
3444
|
+
linuxSecretAvailability = true;
|
|
3445
|
+
} catch {
|
|
3446
|
+
linuxSecretAvailability = false;
|
|
3447
|
+
}
|
|
3448
|
+
return linuxSecretAvailability;
|
|
3449
|
+
}
|
|
3450
|
+
function isRootOnlyTrustedServerKeyFile(keyPath) {
|
|
3451
|
+
if (process.platform !== "linux") return false;
|
|
3452
|
+
try {
|
|
3453
|
+
const uid = typeof os5.userInfo().uid === "number" ? os5.userInfo().uid : -1;
|
|
3454
|
+
const st = statSync2(keyPath);
|
|
3455
|
+
if (!st.isFile() || (st.mode & 63) !== 0) return false;
|
|
3456
|
+
if (uid === 0) return true;
|
|
3457
|
+
const exeOsDir = process.env.EXE_OS_DIR;
|
|
3458
|
+
return Boolean(exeOsDir && path6.resolve(keyPath).startsWith(path6.resolve(exeOsDir) + path6.sep));
|
|
3459
|
+
} catch {
|
|
3460
|
+
return false;
|
|
3461
|
+
}
|
|
3462
|
+
}
|
|
3463
|
+
function macKeychainGet(service = SERVICE) {
|
|
3464
|
+
if (!nativeKeychainAllowed()) return null;
|
|
3293
3465
|
if (process.platform !== "darwin") return null;
|
|
3294
3466
|
try {
|
|
3295
3467
|
return execSync2(
|
|
3296
|
-
`security find-generic-password -s "${
|
|
3468
|
+
`security find-generic-password -s "${service}" -a "${ACCOUNT}" -w 2>/dev/null`,
|
|
3297
3469
|
{ encoding: "utf-8", timeout: 5e3 }
|
|
3298
3470
|
).trim();
|
|
3299
3471
|
} catch {
|
|
3300
3472
|
return null;
|
|
3301
3473
|
}
|
|
3302
3474
|
}
|
|
3303
|
-
function macKeychainSet(value) {
|
|
3475
|
+
function macKeychainSet(value, service = SERVICE) {
|
|
3476
|
+
if (!nativeKeychainAllowed()) return false;
|
|
3304
3477
|
if (process.platform !== "darwin") return false;
|
|
3305
3478
|
try {
|
|
3306
3479
|
try {
|
|
3307
3480
|
execSync2(
|
|
3308
|
-
`security delete-generic-password -s "${
|
|
3481
|
+
`security delete-generic-password -s "${service}" -a "${ACCOUNT}" 2>/dev/null`,
|
|
3309
3482
|
{ timeout: 5e3 }
|
|
3310
3483
|
);
|
|
3311
3484
|
} catch {
|
|
3312
3485
|
}
|
|
3313
3486
|
execSync2(
|
|
3314
|
-
`security add-generic-password -s "${
|
|
3487
|
+
`security add-generic-password -s "${service}" -a "${ACCOUNT}" -w "${value}"`,
|
|
3315
3488
|
{ timeout: 5e3 }
|
|
3316
3489
|
);
|
|
3317
3490
|
return true;
|
|
@@ -3319,11 +3492,12 @@ function macKeychainSet(value) {
|
|
|
3319
3492
|
return false;
|
|
3320
3493
|
}
|
|
3321
3494
|
}
|
|
3322
|
-
function macKeychainDelete() {
|
|
3495
|
+
function macKeychainDelete(service = SERVICE) {
|
|
3496
|
+
if (!nativeKeychainAllowed()) return false;
|
|
3323
3497
|
if (process.platform !== "darwin") return false;
|
|
3324
3498
|
try {
|
|
3325
3499
|
execSync2(
|
|
3326
|
-
`security delete-generic-password -s "${
|
|
3500
|
+
`security delete-generic-password -s "${service}" -a "${ACCOUNT}" 2>/dev/null`,
|
|
3327
3501
|
{ timeout: 5e3 }
|
|
3328
3502
|
);
|
|
3329
3503
|
return true;
|
|
@@ -3331,22 +3505,22 @@ function macKeychainDelete() {
|
|
|
3331
3505
|
return false;
|
|
3332
3506
|
}
|
|
3333
3507
|
}
|
|
3334
|
-
function linuxSecretGet() {
|
|
3335
|
-
if (
|
|
3508
|
+
function linuxSecretGet(service = SERVICE) {
|
|
3509
|
+
if (!linuxSecretAvailable()) return null;
|
|
3336
3510
|
try {
|
|
3337
3511
|
return execSync2(
|
|
3338
|
-
`secret-tool lookup service "${
|
|
3512
|
+
`secret-tool lookup service "${service}" account "${ACCOUNT}" 2>/dev/null`,
|
|
3339
3513
|
{ encoding: "utf-8", timeout: 5e3 }
|
|
3340
3514
|
).trim();
|
|
3341
3515
|
} catch {
|
|
3342
3516
|
return null;
|
|
3343
3517
|
}
|
|
3344
3518
|
}
|
|
3345
|
-
function linuxSecretSet(value) {
|
|
3346
|
-
if (
|
|
3519
|
+
function linuxSecretSet(value, service = SERVICE) {
|
|
3520
|
+
if (!linuxSecretAvailable()) return false;
|
|
3347
3521
|
try {
|
|
3348
3522
|
execSync2(
|
|
3349
|
-
`echo -n "${value}" | secret-tool store --label="exe-os master key" service "${
|
|
3523
|
+
`echo -n "${value}" | secret-tool store --label="exe-os master key" service "${service}" account "${ACCOUNT}" 2>/dev/null`,
|
|
3350
3524
|
{ timeout: 5e3 }
|
|
3351
3525
|
);
|
|
3352
3526
|
return true;
|
|
@@ -3354,11 +3528,12 @@ function linuxSecretSet(value) {
|
|
|
3354
3528
|
return false;
|
|
3355
3529
|
}
|
|
3356
3530
|
}
|
|
3357
|
-
function linuxSecretDelete() {
|
|
3531
|
+
function linuxSecretDelete(service = SERVICE) {
|
|
3532
|
+
if (!nativeKeychainAllowed()) return false;
|
|
3358
3533
|
if (process.platform !== "linux") return false;
|
|
3359
3534
|
try {
|
|
3360
3535
|
execSync2(
|
|
3361
|
-
`secret-tool clear service "${
|
|
3536
|
+
`secret-tool clear service "${service}" account "${ACCOUNT}" 2>/dev/null`,
|
|
3362
3537
|
{ timeout: 5e3 }
|
|
3363
3538
|
);
|
|
3364
3539
|
return true;
|
|
@@ -3367,6 +3542,7 @@ function linuxSecretDelete() {
|
|
|
3367
3542
|
}
|
|
3368
3543
|
}
|
|
3369
3544
|
async function tryKeytar() {
|
|
3545
|
+
if (!nativeKeychainAllowed()) return null;
|
|
3370
3546
|
try {
|
|
3371
3547
|
return await import("keytar");
|
|
3372
3548
|
} catch {
|
|
@@ -3440,7 +3616,19 @@ async function writeMachineBoundFileFallback(b64) {
|
|
|
3440
3616
|
return "plaintext";
|
|
3441
3617
|
}
|
|
3442
3618
|
async function getMasterKey() {
|
|
3443
|
-
|
|
3619
|
+
let nativeValue = macKeychainGet() ?? linuxSecretGet();
|
|
3620
|
+
if (!nativeValue) {
|
|
3621
|
+
const legacyValue = macKeychainGet(LEGACY_SERVICE) ?? linuxSecretGet(LEGACY_SERVICE);
|
|
3622
|
+
if (legacyValue) {
|
|
3623
|
+
const migrated = macKeychainSet(legacyValue) || linuxSecretSet(legacyValue);
|
|
3624
|
+
if (migrated) {
|
|
3625
|
+
macKeychainDelete(LEGACY_SERVICE);
|
|
3626
|
+
linuxSecretDelete(LEGACY_SERVICE);
|
|
3627
|
+
process.stderr.write("[keychain] Migrated keychain service from exe-mem to exe-os.\n");
|
|
3628
|
+
}
|
|
3629
|
+
nativeValue = legacyValue;
|
|
3630
|
+
}
|
|
3631
|
+
}
|
|
3444
3632
|
if (nativeValue) {
|
|
3445
3633
|
return Buffer.from(nativeValue, "base64");
|
|
3446
3634
|
}
|
|
@@ -3448,12 +3636,17 @@ async function getMasterKey() {
|
|
|
3448
3636
|
if (keytar) {
|
|
3449
3637
|
try {
|
|
3450
3638
|
const keytarValue = await keytar.getPassword(SERVICE, ACCOUNT);
|
|
3451
|
-
|
|
3452
|
-
|
|
3639
|
+
const legacyKeytarValue = keytarValue ?? await keytar.getPassword(LEGACY_SERVICE, ACCOUNT);
|
|
3640
|
+
if (legacyKeytarValue) {
|
|
3641
|
+
const migrated = macKeychainSet(legacyKeytarValue) || linuxSecretSet(legacyKeytarValue);
|
|
3453
3642
|
if (migrated) {
|
|
3454
3643
|
process.stderr.write("[keychain] Migrated key from keytar to native keychain.\n");
|
|
3644
|
+
try {
|
|
3645
|
+
await keytar.deletePassword(LEGACY_SERVICE, ACCOUNT);
|
|
3646
|
+
} catch {
|
|
3647
|
+
}
|
|
3455
3648
|
}
|
|
3456
|
-
return Buffer.from(
|
|
3649
|
+
return Buffer.from(legacyKeytarValue, "base64");
|
|
3457
3650
|
}
|
|
3458
3651
|
} catch {
|
|
3459
3652
|
}
|
|
@@ -3478,7 +3671,7 @@ async function getMasterKey() {
|
|
|
3478
3671
|
const decrypted = decryptWithMachineKey(content, machineKey);
|
|
3479
3672
|
if (!decrypted) {
|
|
3480
3673
|
process.stderr.write(
|
|
3481
|
-
"[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase: exe-os
|
|
3674
|
+
"[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase during setup: exe-os setup\n"
|
|
3482
3675
|
);
|
|
3483
3676
|
return null;
|
|
3484
3677
|
}
|
|
@@ -3487,6 +3680,9 @@ async function getMasterKey() {
|
|
|
3487
3680
|
b64Value = content;
|
|
3488
3681
|
}
|
|
3489
3682
|
const key = Buffer.from(b64Value, "base64");
|
|
3683
|
+
if (!content.startsWith(ENCRYPTED_PREFIX) && isRootOnlyTrustedServerKeyFile(keyPath)) {
|
|
3684
|
+
return key;
|
|
3685
|
+
}
|
|
3490
3686
|
const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
|
|
3491
3687
|
if (migrated) {
|
|
3492
3688
|
process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
|
|
@@ -3514,6 +3710,97 @@ async function getMasterKey() {
|
|
|
3514
3710
|
return null;
|
|
3515
3711
|
}
|
|
3516
3712
|
}
|
|
3713
|
+
async function getKeyStorageInfo() {
|
|
3714
|
+
if (macKeychainGet()) {
|
|
3715
|
+
return {
|
|
3716
|
+
kind: "macos-keychain",
|
|
3717
|
+
secure: true,
|
|
3718
|
+
note: "stored in macOS Keychain via built-in security CLI"
|
|
3719
|
+
};
|
|
3720
|
+
}
|
|
3721
|
+
if (macKeychainGet(LEGACY_SERVICE)) {
|
|
3722
|
+
return {
|
|
3723
|
+
kind: "macos-keychain",
|
|
3724
|
+
secure: true,
|
|
3725
|
+
note: "stored in legacy macOS Keychain service exe-mem; next key read migrates it to exe-os"
|
|
3726
|
+
};
|
|
3727
|
+
}
|
|
3728
|
+
if (linuxSecretGet()) {
|
|
3729
|
+
return {
|
|
3730
|
+
kind: "linux-secret-service",
|
|
3731
|
+
secure: true,
|
|
3732
|
+
note: "stored in Linux Secret Service via secret-tool"
|
|
3733
|
+
};
|
|
3734
|
+
}
|
|
3735
|
+
if (linuxSecretGet(LEGACY_SERVICE)) {
|
|
3736
|
+
return {
|
|
3737
|
+
kind: "linux-secret-service",
|
|
3738
|
+
secure: true,
|
|
3739
|
+
note: "stored in legacy Linux Secret Service service exe-mem; next key read migrates it to exe-os"
|
|
3740
|
+
};
|
|
3741
|
+
}
|
|
3742
|
+
const keytar = await tryKeytar();
|
|
3743
|
+
if (keytar) {
|
|
3744
|
+
try {
|
|
3745
|
+
if (await keytar.getPassword(SERVICE, ACCOUNT)) {
|
|
3746
|
+
return {
|
|
3747
|
+
kind: "legacy-keytar",
|
|
3748
|
+
secure: true,
|
|
3749
|
+
note: "stored in legacy keytar backend; will migrate to native keychain when possible"
|
|
3750
|
+
};
|
|
3751
|
+
}
|
|
3752
|
+
if (await keytar.getPassword(LEGACY_SERVICE, ACCOUNT)) {
|
|
3753
|
+
return {
|
|
3754
|
+
kind: "legacy-keytar",
|
|
3755
|
+
secure: true,
|
|
3756
|
+
note: "stored in legacy keytar service exe-mem; will migrate to native exe-os keychain when possible"
|
|
3757
|
+
};
|
|
3758
|
+
}
|
|
3759
|
+
} catch {
|
|
3760
|
+
}
|
|
3761
|
+
}
|
|
3762
|
+
const keyPath = getKeyPath();
|
|
3763
|
+
if (!existsSync6(keyPath)) {
|
|
3764
|
+
return {
|
|
3765
|
+
kind: "missing",
|
|
3766
|
+
secure: false,
|
|
3767
|
+
path: keyPath,
|
|
3768
|
+
note: "no key found in OS keychain, legacy keytar, or file fallback"
|
|
3769
|
+
};
|
|
3770
|
+
}
|
|
3771
|
+
try {
|
|
3772
|
+
const content = (await readFile3(keyPath, "utf-8")).trim();
|
|
3773
|
+
if (content.startsWith(ENCRYPTED_PREFIX)) {
|
|
3774
|
+
return {
|
|
3775
|
+
kind: "encrypted-file",
|
|
3776
|
+
secure: true,
|
|
3777
|
+
path: keyPath,
|
|
3778
|
+
note: "stored in machine-bound encrypted file fallback"
|
|
3779
|
+
};
|
|
3780
|
+
}
|
|
3781
|
+
if (isRootOnlyTrustedServerKeyFile(keyPath)) {
|
|
3782
|
+
return {
|
|
3783
|
+
kind: "server-secret-file",
|
|
3784
|
+
secure: true,
|
|
3785
|
+
path: keyPath,
|
|
3786
|
+
note: "stored as root-only trusted server secret file"
|
|
3787
|
+
};
|
|
3788
|
+
}
|
|
3789
|
+
return {
|
|
3790
|
+
kind: "plaintext-file",
|
|
3791
|
+
secure: false,
|
|
3792
|
+
path: keyPath,
|
|
3793
|
+
note: "stored in legacy plaintext file; reading it will migrate or encrypt it"
|
|
3794
|
+
};
|
|
3795
|
+
} catch {
|
|
3796
|
+
return {
|
|
3797
|
+
kind: "missing",
|
|
3798
|
+
secure: false,
|
|
3799
|
+
path: keyPath,
|
|
3800
|
+
note: "key file exists but could not be read"
|
|
3801
|
+
};
|
|
3802
|
+
}
|
|
3803
|
+
}
|
|
3517
3804
|
async function setMasterKey(key) {
|
|
3518
3805
|
const b64 = key.toString("base64");
|
|
3519
3806
|
if (macKeychainSet(b64) || linuxSecretSet(b64)) {
|
|
@@ -3539,10 +3826,13 @@ async function setMasterKey(key) {
|
|
|
3539
3826
|
async function deleteMasterKey() {
|
|
3540
3827
|
macKeychainDelete();
|
|
3541
3828
|
linuxSecretDelete();
|
|
3829
|
+
macKeychainDelete(LEGACY_SERVICE);
|
|
3830
|
+
linuxSecretDelete(LEGACY_SERVICE);
|
|
3542
3831
|
const keytar = await tryKeytar();
|
|
3543
3832
|
if (keytar) {
|
|
3544
3833
|
try {
|
|
3545
3834
|
await keytar.deletePassword(SERVICE, ACCOUNT);
|
|
3835
|
+
await keytar.deletePassword(LEGACY_SERVICE, ACCOUNT);
|
|
3546
3836
|
} catch {
|
|
3547
3837
|
}
|
|
3548
3838
|
}
|
|
@@ -3580,12 +3870,14 @@ async function importMnemonic(mnemonic) {
|
|
|
3580
3870
|
const entropy = mnemonicToEntropy(trimmed);
|
|
3581
3871
|
return Buffer.from(entropy, "hex");
|
|
3582
3872
|
}
|
|
3583
|
-
var SERVICE, ACCOUNT, ENCRYPTED_PREFIX;
|
|
3873
|
+
var SERVICE, LEGACY_SERVICE, ACCOUNT, linuxSecretAvailability, ENCRYPTED_PREFIX;
|
|
3584
3874
|
var init_keychain = __esm({
|
|
3585
3875
|
"src/lib/keychain.ts"() {
|
|
3586
3876
|
"use strict";
|
|
3587
|
-
SERVICE = "exe-
|
|
3877
|
+
SERVICE = "exe-os";
|
|
3878
|
+
LEGACY_SERVICE = "exe-mem";
|
|
3588
3879
|
ACCOUNT = "master-key";
|
|
3880
|
+
linuxSecretAvailability = null;
|
|
3589
3881
|
ENCRYPTED_PREFIX = "enc:";
|
|
3590
3882
|
}
|
|
3591
3883
|
});
|
|
@@ -3669,7 +3961,7 @@ __export(shard_manager_exports, {
|
|
|
3669
3961
|
shardExists: () => shardExists
|
|
3670
3962
|
});
|
|
3671
3963
|
import path7 from "path";
|
|
3672
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync2, readdirSync, renameSync as renameSync3, statSync as
|
|
3964
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync2, readdirSync, renameSync as renameSync3, statSync as statSync3 } from "fs";
|
|
3673
3965
|
import { createClient as createClient2 } from "@libsql/client";
|
|
3674
3966
|
function initShardManager(encryptionKey) {
|
|
3675
3967
|
_encryptionKey = encryptionKey;
|
|
@@ -3733,7 +4025,7 @@ async function auditShardHealth(options = {}) {
|
|
|
3733
4025
|
const shards = [];
|
|
3734
4026
|
for (const name of names) {
|
|
3735
4027
|
const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
|
|
3736
|
-
const stat =
|
|
4028
|
+
const stat = statSync3(dbPath);
|
|
3737
4029
|
const item = {
|
|
3738
4030
|
name,
|
|
3739
4031
|
path: dbPath,
|
|
@@ -3986,7 +4278,7 @@ async function getReadyShardClient(projectName) {
|
|
|
3986
4278
|
_shardLastAccess.delete(safeName);
|
|
3987
4279
|
const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
|
|
3988
4280
|
if (existsSync7(dbPath)) {
|
|
3989
|
-
const stat =
|
|
4281
|
+
const stat = statSync3(dbPath);
|
|
3990
4282
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
3991
4283
|
const archivedPath = path7.join(SHARDS_DIR, `${safeName}.db.broken-${stamp}`);
|
|
3992
4284
|
renameSync3(dbPath, archivedPath);
|
|
@@ -4943,8 +5235,8 @@ async function validateLicense(apiKey, deviceId) {
|
|
|
4943
5235
|
}
|
|
4944
5236
|
function getCacheAgeMs() {
|
|
4945
5237
|
try {
|
|
4946
|
-
const { statSync:
|
|
4947
|
-
const s =
|
|
5238
|
+
const { statSync: statSync6 } = __require("fs");
|
|
5239
|
+
const s = statSync6(CACHE_PATH);
|
|
4948
5240
|
return Date.now() - s.mtimeMs;
|
|
4949
5241
|
} catch {
|
|
4950
5242
|
return Infinity;
|
|
@@ -8949,7 +9241,7 @@ __export(db_backup_exports, {
|
|
|
8949
9241
|
listBackups: () => listBackups,
|
|
8950
9242
|
rotateBackups: () => rotateBackups
|
|
8951
9243
|
});
|
|
8952
|
-
import { copyFileSync, existsSync as existsSync20, mkdirSync as mkdirSync12, readdirSync as readdirSync7, unlinkSync as unlinkSync10, statSync as
|
|
9244
|
+
import { copyFileSync, existsSync as existsSync20, mkdirSync as mkdirSync12, readdirSync as readdirSync7, unlinkSync as unlinkSync10, statSync as statSync4 } from "fs";
|
|
8953
9245
|
import path24 from "path";
|
|
8954
9246
|
function findActiveDb() {
|
|
8955
9247
|
for (const name of DB_NAMES) {
|
|
@@ -8993,7 +9285,7 @@ function rotateBackups(keepDays = DEFAULT_KEEP_DAYS) {
|
|
|
8993
9285
|
if (!file.endsWith(".db") && !file.endsWith(".db-wal") && !file.endsWith(".db-shm")) continue;
|
|
8994
9286
|
const filePath = path24.join(BACKUP_DIR, file);
|
|
8995
9287
|
try {
|
|
8996
|
-
const stat =
|
|
9288
|
+
const stat = statSync4(filePath);
|
|
8997
9289
|
if (stat.mtimeMs < cutoff) {
|
|
8998
9290
|
unlinkSync10(filePath);
|
|
8999
9291
|
deleted++;
|
|
@@ -9011,7 +9303,7 @@ function listBackups() {
|
|
|
9011
9303
|
const files = readdirSync7(BACKUP_DIR).filter((f) => f.endsWith(".db") && !f.endsWith("-wal") && !f.endsWith("-shm"));
|
|
9012
9304
|
return files.map((name) => {
|
|
9013
9305
|
const p = path24.join(BACKUP_DIR, name);
|
|
9014
|
-
const stat =
|
|
9306
|
+
const stat = statSync4(p);
|
|
9015
9307
|
return { path: p, name, size: stat.size, date: stat.mtime };
|
|
9016
9308
|
}).sort((a, b) => b.date.getTime() - a.date.getTime());
|
|
9017
9309
|
} catch {
|
|
@@ -9044,10 +9336,10 @@ var init_db_backup = __esm({
|
|
|
9044
9336
|
// src/lib/cloud-sync.ts
|
|
9045
9337
|
var cloud_sync_exports = {};
|
|
9046
9338
|
__export(cloud_sync_exports, {
|
|
9047
|
-
|
|
9339
|
+
CLOUD_REUPLOAD_REQUIRED_MESSAGE: () => CLOUD_REUPLOAD_REQUIRED_MESSAGE,
|
|
9048
9340
|
assertSecureEndpoint: () => assertSecureEndpoint,
|
|
9049
9341
|
buildRosterBlob: () => buildRosterBlob,
|
|
9050
|
-
|
|
9342
|
+
clearCloudReuploadRequired: () => clearCloudReuploadRequired,
|
|
9051
9343
|
cloudPull: () => cloudPull,
|
|
9052
9344
|
cloudPullBehaviors: () => cloudPullBehaviors,
|
|
9053
9345
|
cloudPullBlob: () => cloudPullBlob,
|
|
@@ -9066,14 +9358,16 @@ __export(cloud_sync_exports, {
|
|
|
9066
9358
|
cloudPushGraphRAG: () => cloudPushGraphRAG,
|
|
9067
9359
|
cloudPushRoster: () => cloudPushRoster,
|
|
9068
9360
|
cloudPushTasks: () => cloudPushTasks,
|
|
9361
|
+
cloudResetMemoryBlobs: () => cloudResetMemoryBlobs,
|
|
9069
9362
|
cloudSync: () => cloudSync,
|
|
9070
|
-
|
|
9363
|
+
getCloudReuploadRequired: () => getCloudReuploadRequired,
|
|
9364
|
+
markCloudReuploadRequired: () => markCloudReuploadRequired,
|
|
9071
9365
|
mergeConfig: () => mergeConfig,
|
|
9072
9366
|
mergeRosterFromRemote: () => mergeRosterFromRemote,
|
|
9073
9367
|
pushToPostgres: () => pushToPostgres,
|
|
9074
9368
|
recordRosterDeletion: () => recordRosterDeletion
|
|
9075
9369
|
});
|
|
9076
|
-
import { readFileSync as readFileSync15, writeFileSync as writeFileSync12, existsSync as existsSync21, readdirSync as readdirSync8, mkdirSync as mkdirSync13, appendFileSync as appendFileSync2, unlinkSync as unlinkSync11, openSync as openSync2, closeSync as closeSync2, statSync as
|
|
9370
|
+
import { readFileSync as readFileSync15, writeFileSync as writeFileSync12, existsSync as existsSync21, readdirSync as readdirSync8, mkdirSync as mkdirSync13, appendFileSync as appendFileSync2, unlinkSync as unlinkSync11, openSync as openSync2, closeSync as closeSync2, statSync as statSync5 } from "fs";
|
|
9077
9371
|
import crypto8 from "crypto";
|
|
9078
9372
|
import path25 from "path";
|
|
9079
9373
|
import { homedir as homedir2 } from "os";
|
|
@@ -9120,18 +9414,36 @@ function loadPgClient() {
|
|
|
9120
9414
|
const { pathToFileURL: pathToFileURL3 } = await import("url");
|
|
9121
9415
|
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
9122
9416
|
if (explicitPath) {
|
|
9123
|
-
const
|
|
9124
|
-
const
|
|
9125
|
-
if (!
|
|
9126
|
-
return new
|
|
9417
|
+
const mod = await import(pathToFileURL3(explicitPath).href);
|
|
9418
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
9419
|
+
if (!Ctor) throw new Error(`No PrismaClient at ${explicitPath}`);
|
|
9420
|
+
return new Ctor();
|
|
9127
9421
|
}
|
|
9128
9422
|
const exeDbRoot = process.env.EXE_DB_ROOT ?? path25.join(homedir2(), "exe-db");
|
|
9129
|
-
const
|
|
9130
|
-
|
|
9131
|
-
|
|
9132
|
-
|
|
9133
|
-
|
|
9134
|
-
|
|
9423
|
+
const packagePath = path25.join(exeDbRoot, "package.json");
|
|
9424
|
+
if (existsSync21(packagePath)) {
|
|
9425
|
+
const req = createRequire3(packagePath);
|
|
9426
|
+
const entry = req.resolve("@prisma/client");
|
|
9427
|
+
const mod = await import(pathToFileURL3(entry).href);
|
|
9428
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
9429
|
+
if (!Ctor) throw new Error("No PrismaClient");
|
|
9430
|
+
return new Ctor();
|
|
9431
|
+
}
|
|
9432
|
+
const { Pool } = await import("pg");
|
|
9433
|
+
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
|
|
9434
|
+
return {
|
|
9435
|
+
async $queryRawUnsafe(query, ...values) {
|
|
9436
|
+
const result = await pool.query(query, values);
|
|
9437
|
+
return result.rows;
|
|
9438
|
+
},
|
|
9439
|
+
async $executeRawUnsafe(query, ...values) {
|
|
9440
|
+
const result = await pool.query(query, values);
|
|
9441
|
+
return result.rowCount ?? 0;
|
|
9442
|
+
},
|
|
9443
|
+
async $disconnect() {
|
|
9444
|
+
await pool.end();
|
|
9445
|
+
}
|
|
9446
|
+
};
|
|
9135
9447
|
})().catch(() => {
|
|
9136
9448
|
_pgFailed = true;
|
|
9137
9449
|
_pgPromise = null;
|
|
@@ -9152,7 +9464,7 @@ async function pushToPostgres(records) {
|
|
|
9152
9464
|
let inserted = 0;
|
|
9153
9465
|
for (const rec of records) {
|
|
9154
9466
|
try {
|
|
9155
|
-
await prisma.$executeRawUnsafe(
|
|
9467
|
+
const changed = await prisma.$executeRawUnsafe(
|
|
9156
9468
|
`INSERT INTO raw.raw_events (id, source, source_id, event_type, payload, metadata, timestamp)
|
|
9157
9469
|
VALUES (gen_random_uuid(), 'cloud_sync', $1, 'memory', $2::jsonb, $3::jsonb, $4)
|
|
9158
9470
|
ON CONFLICT (source, source_id, event_type) DO NOTHING`,
|
|
@@ -9161,7 +9473,7 @@ async function pushToPostgres(records) {
|
|
|
9161
9473
|
JSON.stringify({ agent_id: rec.agent_id, project_name: rec.project_name, tool_name: rec.tool_name }),
|
|
9162
9474
|
rec.timestamp ? new Date(String(rec.timestamp)) : /* @__PURE__ */ new Date()
|
|
9163
9475
|
);
|
|
9164
|
-
inserted
|
|
9476
|
+
inserted += Number(changed ?? 0);
|
|
9165
9477
|
} catch {
|
|
9166
9478
|
}
|
|
9167
9479
|
}
|
|
@@ -9266,6 +9578,23 @@ async function cloudPush(records, maxVersion, config) {
|
|
|
9266
9578
|
return false;
|
|
9267
9579
|
}
|
|
9268
9580
|
}
|
|
9581
|
+
async function cloudResetMemoryBlobs(config) {
|
|
9582
|
+
assertSecureEndpoint(config.endpoint);
|
|
9583
|
+
const resp = await fetchWithRetry(`${config.endpoint}/sync/reset-memory`, {
|
|
9584
|
+
method: "POST",
|
|
9585
|
+
headers: {
|
|
9586
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
9587
|
+
"Content-Type": "application/json",
|
|
9588
|
+
"X-Device-Id": loadDeviceId()
|
|
9589
|
+
},
|
|
9590
|
+
body: JSON.stringify({ confirm: "LOCAL DB IS SOURCE OF TRUTH" })
|
|
9591
|
+
});
|
|
9592
|
+
if (!resp.ok) {
|
|
9593
|
+
throw new Error(`cloud reset failed: HTTP ${resp.status}`);
|
|
9594
|
+
}
|
|
9595
|
+
const data = await resp.json();
|
|
9596
|
+
return { deleted: Number(data.deleted ?? 0), freedBytes: Number(data.freed_bytes ?? 0) };
|
|
9597
|
+
}
|
|
9269
9598
|
async function cloudPull(sinceVersion, config) {
|
|
9270
9599
|
assertSecureEndpoint(config.endpoint);
|
|
9271
9600
|
try {
|
|
@@ -9285,40 +9614,61 @@ async function cloudPull(sinceVersion, config) {
|
|
|
9285
9614
|
if (!response.ok) return { records: [], maxVersion: sinceVersion };
|
|
9286
9615
|
const data = await response.json();
|
|
9287
9616
|
const allRecords = [];
|
|
9288
|
-
|
|
9617
|
+
let maxReadableVersion = sinceVersion;
|
|
9618
|
+
let skippedBlobs = 0;
|
|
9619
|
+
for (const { version, blob } of data.blobs ?? []) {
|
|
9289
9620
|
try {
|
|
9290
9621
|
const compressed = decryptSyncBlob(blob);
|
|
9291
9622
|
const json = decompress(compressed).toString("utf8");
|
|
9292
9623
|
const records = JSON.parse(json);
|
|
9293
9624
|
allRecords.push(...records);
|
|
9625
|
+
const recordMax = records.reduce((max, rec) => {
|
|
9626
|
+
const v = Number(rec.version ?? 0);
|
|
9627
|
+
return Number.isFinite(v) ? Math.max(max, v) : max;
|
|
9628
|
+
}, 0);
|
|
9629
|
+
const blobVersion = Number(version ?? 0);
|
|
9630
|
+
maxReadableVersion = Math.max(
|
|
9631
|
+
maxReadableVersion,
|
|
9632
|
+
Number.isFinite(blobVersion) ? blobVersion : 0,
|
|
9633
|
+
recordMax
|
|
9634
|
+
);
|
|
9294
9635
|
} catch {
|
|
9636
|
+
skippedBlobs++;
|
|
9295
9637
|
continue;
|
|
9296
9638
|
}
|
|
9297
9639
|
}
|
|
9298
|
-
|
|
9640
|
+
if (skippedBlobs > 0) {
|
|
9641
|
+
logError(`[cloud-sync] PULL skipped ${skippedBlobs} undecryptable blob(s); pull cursor advanced only to last readable version ${maxReadableVersion}`);
|
|
9642
|
+
}
|
|
9643
|
+
return { records: allRecords, maxVersion: maxReadableVersion };
|
|
9299
9644
|
} catch (err) {
|
|
9300
9645
|
logError(`[cloud-sync] PULL FAILED: ${err instanceof Error ? err.message : String(err)}`);
|
|
9301
9646
|
return { records: [], maxVersion: sinceVersion };
|
|
9302
9647
|
}
|
|
9303
9648
|
}
|
|
9304
|
-
async function
|
|
9649
|
+
async function getCloudReuploadRequired(client = getClient()) {
|
|
9305
9650
|
try {
|
|
9306
9651
|
await client.execute("CREATE TABLE IF NOT EXISTS sync_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)");
|
|
9307
|
-
const
|
|
9308
|
-
return String(
|
|
9652
|
+
const result = await client.execute("SELECT key, value FROM sync_meta WHERE key IN ('cloud_reupload_required', 'cloud_relink_required')");
|
|
9653
|
+
return result.rows.some((row) => String(row.value ?? "") === "1");
|
|
9309
9654
|
} catch {
|
|
9310
9655
|
return false;
|
|
9311
9656
|
}
|
|
9312
9657
|
}
|
|
9313
|
-
async function
|
|
9658
|
+
async function clearCloudReuploadRequired(client = getClient()) {
|
|
9314
9659
|
await client.execute("CREATE TABLE IF NOT EXISTS sync_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)");
|
|
9660
|
+
await client.execute("INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('cloud_reupload_required', '0')");
|
|
9315
9661
|
await client.execute("INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('cloud_relink_required', '0')");
|
|
9316
9662
|
await client.execute({
|
|
9317
|
-
sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('
|
|
9663
|
+
sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('cloud_reuploaded_at', ?)",
|
|
9318
9664
|
args: [(/* @__PURE__ */ new Date()).toISOString()]
|
|
9319
9665
|
});
|
|
9320
9666
|
await client.execute("DELETE FROM sync_meta WHERE key IN ('last_cloud_pull_version', 'last_cloud_push_version')");
|
|
9321
9667
|
}
|
|
9668
|
+
async function markCloudReuploadRequired(client = getClient()) {
|
|
9669
|
+
await client.execute("CREATE TABLE IF NOT EXISTS sync_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)");
|
|
9670
|
+
await client.execute("INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('cloud_reupload_required', '1')");
|
|
9671
|
+
}
|
|
9322
9672
|
async function cloudSync(config) {
|
|
9323
9673
|
if (!isSyncCryptoInitialized()) {
|
|
9324
9674
|
try {
|
|
@@ -9340,10 +9690,10 @@ async function cloudSync(config) {
|
|
|
9340
9690
|
throw new Error("[cloud-sync] Database not initialized. Call initStore() before cloudSync().");
|
|
9341
9691
|
}
|
|
9342
9692
|
try {
|
|
9343
|
-
if (await
|
|
9693
|
+
if (await getCloudReuploadRequired(client)) throw new Error(CLOUD_REUPLOAD_REQUIRED_MESSAGE);
|
|
9344
9694
|
} catch (err) {
|
|
9345
9695
|
const msg = err instanceof Error ? err.message : String(err);
|
|
9346
|
-
if (msg.includes("
|
|
9696
|
+
if (msg === CLOUD_REUPLOAD_REQUIRED_MESSAGE || msg.includes("key rotation")) throw err;
|
|
9347
9697
|
}
|
|
9348
9698
|
try {
|
|
9349
9699
|
const { getRawClient: getRawClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
@@ -9601,7 +9951,7 @@ async function cloudSync(config) {
|
|
|
9601
9951
|
const { getLatestBackup: getLatestBackup2 } = await Promise.resolve().then(() => (init_db_backup(), db_backup_exports));
|
|
9602
9952
|
const latestBackup = getLatestBackup2();
|
|
9603
9953
|
if (latestBackup) {
|
|
9604
|
-
const backupSize =
|
|
9954
|
+
const backupSize = statSync5(latestBackup).size;
|
|
9605
9955
|
const MAX_CLOUD_BACKUP_BYTES = 50 * 1024 * 1024;
|
|
9606
9956
|
if (backupSize <= MAX_CLOUD_BACKUP_BYTES) {
|
|
9607
9957
|
const backupData = readFileSync15(latestBackup);
|
|
@@ -10272,7 +10622,7 @@ async function cloudPullDocuments(config) {
|
|
|
10272
10622
|
}
|
|
10273
10623
|
return { pulled };
|
|
10274
10624
|
}
|
|
10275
|
-
var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, _pgPromise, _pgFailed,
|
|
10625
|
+
var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, _pgPromise, _pgFailed, CLOUD_REUPLOAD_REQUIRED_MESSAGE, ROSTER_DELETIONS_PATH;
|
|
10276
10626
|
var init_cloud_sync = __esm({
|
|
10277
10627
|
"src/lib/cloud-sync.ts"() {
|
|
10278
10628
|
"use strict";
|
|
@@ -10291,7 +10641,7 @@ var init_cloud_sync = __esm({
|
|
|
10291
10641
|
LOCK_STALE_MS = 3e4;
|
|
10292
10642
|
_pgPromise = null;
|
|
10293
10643
|
_pgFailed = false;
|
|
10294
|
-
|
|
10644
|
+
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.";
|
|
10295
10645
|
ROSTER_DELETIONS_PATH = path25.join(EXE_AI_DIR, "roster-deletions.json");
|
|
10296
10646
|
}
|
|
10297
10647
|
});
|