@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
|
@@ -247,6 +247,10 @@ async function loadConfig() {
|
|
|
247
247
|
if (config.dbPath.startsWith("~")) {
|
|
248
248
|
config.dbPath = config.dbPath.replace(/^~/, os.homedir());
|
|
249
249
|
}
|
|
250
|
+
const envDbPath = path.join(dir, "memories.db");
|
|
251
|
+
if (process.env.EXE_OS_DIR && config.dbPath !== envDbPath && !existsSync2(config.dbPath) && existsSync2(envDbPath)) {
|
|
252
|
+
config.dbPath = envDbPath;
|
|
253
|
+
}
|
|
250
254
|
return config;
|
|
251
255
|
} catch {
|
|
252
256
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
@@ -267,7 +271,15 @@ function loadConfigSync() {
|
|
|
267
271
|
normalizeSessionLifecycle(migratedCfg);
|
|
268
272
|
normalizeAutoUpdate(migratedCfg);
|
|
269
273
|
normalizeOrchestration(migratedCfg);
|
|
270
|
-
|
|
274
|
+
const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
275
|
+
if (config.dbPath.startsWith("~")) {
|
|
276
|
+
config.dbPath = config.dbPath.replace(/^~/, os.homedir());
|
|
277
|
+
}
|
|
278
|
+
const envDbPath = path.join(dir, "memories.db");
|
|
279
|
+
if (process.env.EXE_OS_DIR && config.dbPath !== envDbPath && !existsSync2(config.dbPath) && existsSync2(envDbPath)) {
|
|
280
|
+
config.dbPath = envDbPath;
|
|
281
|
+
}
|
|
282
|
+
return config;
|
|
271
283
|
} catch {
|
|
272
284
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
273
285
|
}
|
|
@@ -1807,6 +1819,9 @@ function getClient() {
|
|
|
1807
1819
|
if (_daemonClient && _daemonClient._isDaemonActive()) {
|
|
1808
1820
|
return _daemonClient;
|
|
1809
1821
|
}
|
|
1822
|
+
if (!_resilientClient) {
|
|
1823
|
+
return _adapterClient;
|
|
1824
|
+
}
|
|
1810
1825
|
return _resilientClient;
|
|
1811
1826
|
}
|
|
1812
1827
|
async function initDaemonClient() {
|
|
@@ -2839,6 +2854,127 @@ async function ensureSchema() {
|
|
|
2839
2854
|
VALUES (new.rowid, new.content, new.subject, new.predicate, new.object);
|
|
2840
2855
|
END;
|
|
2841
2856
|
`);
|
|
2857
|
+
await client.executeMultiple(`
|
|
2858
|
+
CREATE TABLE IF NOT EXISTS agent_sessions (
|
|
2859
|
+
id TEXT PRIMARY KEY,
|
|
2860
|
+
agent_id TEXT NOT NULL,
|
|
2861
|
+
project_name TEXT,
|
|
2862
|
+
started_at TEXT NOT NULL,
|
|
2863
|
+
last_event_at TEXT NOT NULL,
|
|
2864
|
+
event_count INTEGER NOT NULL DEFAULT 0,
|
|
2865
|
+
properties TEXT DEFAULT '{}'
|
|
2866
|
+
);
|
|
2867
|
+
|
|
2868
|
+
CREATE INDEX IF NOT EXISTS idx_agent_sessions_agent_time
|
|
2869
|
+
ON agent_sessions(agent_id, started_at);
|
|
2870
|
+
|
|
2871
|
+
CREATE TABLE IF NOT EXISTS agent_goals (
|
|
2872
|
+
id TEXT PRIMARY KEY,
|
|
2873
|
+
statement TEXT NOT NULL,
|
|
2874
|
+
owner_agent_id TEXT,
|
|
2875
|
+
project_name TEXT,
|
|
2876
|
+
status TEXT NOT NULL DEFAULT 'open',
|
|
2877
|
+
priority INTEGER NOT NULL DEFAULT 5,
|
|
2878
|
+
success_criteria TEXT,
|
|
2879
|
+
parent_goal_id TEXT,
|
|
2880
|
+
due_at TEXT,
|
|
2881
|
+
achieved_at TEXT,
|
|
2882
|
+
supersedes_id TEXT,
|
|
2883
|
+
created_at TEXT NOT NULL,
|
|
2884
|
+
updated_at TEXT NOT NULL,
|
|
2885
|
+
source_memory_id TEXT
|
|
2886
|
+
);
|
|
2887
|
+
|
|
2888
|
+
CREATE INDEX IF NOT EXISTS idx_agent_goals_project_status
|
|
2889
|
+
ON agent_goals(project_name, status, priority);
|
|
2890
|
+
|
|
2891
|
+
CREATE TABLE IF NOT EXISTS agent_events (
|
|
2892
|
+
id TEXT PRIMARY KEY,
|
|
2893
|
+
event_type TEXT NOT NULL,
|
|
2894
|
+
occurred_at TEXT NOT NULL,
|
|
2895
|
+
sequence_index INTEGER NOT NULL,
|
|
2896
|
+
actor_agent_id TEXT,
|
|
2897
|
+
agent_role TEXT,
|
|
2898
|
+
project_name TEXT,
|
|
2899
|
+
session_id TEXT,
|
|
2900
|
+
task_id TEXT,
|
|
2901
|
+
goal_id TEXT,
|
|
2902
|
+
parent_event_id TEXT,
|
|
2903
|
+
intention TEXT,
|
|
2904
|
+
outcome TEXT,
|
|
2905
|
+
evidence_memory_id TEXT,
|
|
2906
|
+
impact TEXT,
|
|
2907
|
+
payload TEXT DEFAULT '{}',
|
|
2908
|
+
created_at TEXT NOT NULL
|
|
2909
|
+
);
|
|
2910
|
+
|
|
2911
|
+
CREATE INDEX IF NOT EXISTS idx_agent_events_time
|
|
2912
|
+
ON agent_events(occurred_at, sequence_index);
|
|
2913
|
+
|
|
2914
|
+
CREATE INDEX IF NOT EXISTS idx_agent_events_session_seq
|
|
2915
|
+
ON agent_events(session_id, sequence_index);
|
|
2916
|
+
|
|
2917
|
+
CREATE INDEX IF NOT EXISTS idx_agent_events_goal_time
|
|
2918
|
+
ON agent_events(goal_id, occurred_at);
|
|
2919
|
+
|
|
2920
|
+
CREATE INDEX IF NOT EXISTS idx_agent_events_memory
|
|
2921
|
+
ON agent_events(evidence_memory_id);
|
|
2922
|
+
|
|
2923
|
+
CREATE TABLE IF NOT EXISTS agent_goal_links (
|
|
2924
|
+
id TEXT PRIMARY KEY,
|
|
2925
|
+
goal_id TEXT NOT NULL,
|
|
2926
|
+
link_type TEXT NOT NULL,
|
|
2927
|
+
target_id TEXT NOT NULL,
|
|
2928
|
+
target_type TEXT NOT NULL,
|
|
2929
|
+
created_at TEXT NOT NULL
|
|
2930
|
+
);
|
|
2931
|
+
|
|
2932
|
+
CREATE INDEX IF NOT EXISTS idx_agent_goal_links_goal
|
|
2933
|
+
ON agent_goal_links(goal_id, target_type);
|
|
2934
|
+
|
|
2935
|
+
CREATE TABLE IF NOT EXISTS agent_semantic_labels (
|
|
2936
|
+
id TEXT PRIMARY KEY,
|
|
2937
|
+
source_memory_id TEXT NOT NULL,
|
|
2938
|
+
event_id TEXT,
|
|
2939
|
+
labeler TEXT NOT NULL,
|
|
2940
|
+
schema_version INTEGER NOT NULL DEFAULT 1,
|
|
2941
|
+
confidence REAL NOT NULL DEFAULT 0,
|
|
2942
|
+
labels TEXT NOT NULL,
|
|
2943
|
+
created_at TEXT NOT NULL,
|
|
2944
|
+
updated_at TEXT NOT NULL
|
|
2945
|
+
);
|
|
2946
|
+
|
|
2947
|
+
CREATE INDEX IF NOT EXISTS idx_agent_semantic_labels_memory
|
|
2948
|
+
ON agent_semantic_labels(source_memory_id, labeler);
|
|
2949
|
+
|
|
2950
|
+
CREATE INDEX IF NOT EXISTS idx_agent_semantic_labels_event
|
|
2951
|
+
ON agent_semantic_labels(event_id);
|
|
2952
|
+
|
|
2953
|
+
CREATE TABLE IF NOT EXISTS agent_reflection_checkpoints (
|
|
2954
|
+
id TEXT PRIMARY KEY,
|
|
2955
|
+
project_name TEXT,
|
|
2956
|
+
session_id TEXT,
|
|
2957
|
+
window_start_at TEXT NOT NULL,
|
|
2958
|
+
window_end_at TEXT NOT NULL,
|
|
2959
|
+
event_count INTEGER NOT NULL DEFAULT 0,
|
|
2960
|
+
goal_count INTEGER NOT NULL DEFAULT 0,
|
|
2961
|
+
success_count INTEGER NOT NULL DEFAULT 0,
|
|
2962
|
+
failure_count INTEGER NOT NULL DEFAULT 0,
|
|
2963
|
+
risk_count INTEGER NOT NULL DEFAULT 0,
|
|
2964
|
+
summary TEXT NOT NULL,
|
|
2965
|
+
learnings TEXT NOT NULL DEFAULT '[]',
|
|
2966
|
+
next_actions TEXT NOT NULL DEFAULT '[]',
|
|
2967
|
+
evidence_event_ids TEXT NOT NULL DEFAULT '[]',
|
|
2968
|
+
confidence REAL NOT NULL DEFAULT 0,
|
|
2969
|
+
created_at TEXT NOT NULL
|
|
2970
|
+
);
|
|
2971
|
+
|
|
2972
|
+
CREATE INDEX IF NOT EXISTS idx_agent_reflection_project_time
|
|
2973
|
+
ON agent_reflection_checkpoints(project_name, window_end_at);
|
|
2974
|
+
|
|
2975
|
+
CREATE INDEX IF NOT EXISTS idx_agent_reflection_session_time
|
|
2976
|
+
ON agent_reflection_checkpoints(session_id, window_end_at);
|
|
2977
|
+
`);
|
|
2842
2978
|
try {
|
|
2843
2979
|
await client.execute({
|
|
2844
2980
|
sql: `ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3`,
|
|
@@ -2991,12 +3127,13 @@ var keychain_exports = {};
|
|
|
2991
3127
|
__export(keychain_exports, {
|
|
2992
3128
|
deleteMasterKey: () => deleteMasterKey,
|
|
2993
3129
|
exportMnemonic: () => exportMnemonic,
|
|
3130
|
+
getKeyStorageInfo: () => getKeyStorageInfo,
|
|
2994
3131
|
getMasterKey: () => getMasterKey,
|
|
2995
3132
|
importMnemonic: () => importMnemonic,
|
|
2996
3133
|
setMasterKey: () => setMasterKey
|
|
2997
3134
|
});
|
|
2998
3135
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
2999
|
-
import { existsSync as existsSync6 } from "fs";
|
|
3136
|
+
import { existsSync as existsSync6, statSync as statSync2 } from "fs";
|
|
3000
3137
|
import { execSync as execSync2 } from "child_process";
|
|
3001
3138
|
import path6 from "path";
|
|
3002
3139
|
import os5 from "os";
|
|
@@ -3006,29 +3143,65 @@ function getKeyDir() {
|
|
|
3006
3143
|
function getKeyPath() {
|
|
3007
3144
|
return path6.join(getKeyDir(), "master.key");
|
|
3008
3145
|
}
|
|
3009
|
-
function
|
|
3146
|
+
function nativeKeychainAllowed() {
|
|
3147
|
+
return process.env.EXE_OS_DISABLE_NATIVE_KEYCHAIN !== "1";
|
|
3148
|
+
}
|
|
3149
|
+
function linuxSecretAvailable() {
|
|
3150
|
+
if (!nativeKeychainAllowed()) return false;
|
|
3151
|
+
if (process.platform !== "linux") return false;
|
|
3152
|
+
if (linuxSecretAvailability !== null) return linuxSecretAvailability;
|
|
3153
|
+
try {
|
|
3154
|
+
execSync2("command -v secret-tool >/dev/null 2>&1", { timeout: 1e3 });
|
|
3155
|
+
} catch {
|
|
3156
|
+
linuxSecretAvailability = false;
|
|
3157
|
+
return false;
|
|
3158
|
+
}
|
|
3159
|
+
try {
|
|
3160
|
+
execSync2("secret-tool search --all exe-os probe >/dev/null 2>&1", { timeout: 1e3 });
|
|
3161
|
+
linuxSecretAvailability = true;
|
|
3162
|
+
} catch {
|
|
3163
|
+
linuxSecretAvailability = false;
|
|
3164
|
+
}
|
|
3165
|
+
return linuxSecretAvailability;
|
|
3166
|
+
}
|
|
3167
|
+
function isRootOnlyTrustedServerKeyFile(keyPath) {
|
|
3168
|
+
if (process.platform !== "linux") return false;
|
|
3169
|
+
try {
|
|
3170
|
+
const uid = typeof os5.userInfo().uid === "number" ? os5.userInfo().uid : -1;
|
|
3171
|
+
const st = statSync2(keyPath);
|
|
3172
|
+
if (!st.isFile() || (st.mode & 63) !== 0) return false;
|
|
3173
|
+
if (uid === 0) return true;
|
|
3174
|
+
const exeOsDir = process.env.EXE_OS_DIR;
|
|
3175
|
+
return Boolean(exeOsDir && path6.resolve(keyPath).startsWith(path6.resolve(exeOsDir) + path6.sep));
|
|
3176
|
+
} catch {
|
|
3177
|
+
return false;
|
|
3178
|
+
}
|
|
3179
|
+
}
|
|
3180
|
+
function macKeychainGet(service = SERVICE) {
|
|
3181
|
+
if (!nativeKeychainAllowed()) return null;
|
|
3010
3182
|
if (process.platform !== "darwin") return null;
|
|
3011
3183
|
try {
|
|
3012
3184
|
return execSync2(
|
|
3013
|
-
`security find-generic-password -s "${
|
|
3185
|
+
`security find-generic-password -s "${service}" -a "${ACCOUNT}" -w 2>/dev/null`,
|
|
3014
3186
|
{ encoding: "utf-8", timeout: 5e3 }
|
|
3015
3187
|
).trim();
|
|
3016
3188
|
} catch {
|
|
3017
3189
|
return null;
|
|
3018
3190
|
}
|
|
3019
3191
|
}
|
|
3020
|
-
function macKeychainSet(value) {
|
|
3192
|
+
function macKeychainSet(value, service = SERVICE) {
|
|
3193
|
+
if (!nativeKeychainAllowed()) return false;
|
|
3021
3194
|
if (process.platform !== "darwin") return false;
|
|
3022
3195
|
try {
|
|
3023
3196
|
try {
|
|
3024
3197
|
execSync2(
|
|
3025
|
-
`security delete-generic-password -s "${
|
|
3198
|
+
`security delete-generic-password -s "${service}" -a "${ACCOUNT}" 2>/dev/null`,
|
|
3026
3199
|
{ timeout: 5e3 }
|
|
3027
3200
|
);
|
|
3028
3201
|
} catch {
|
|
3029
3202
|
}
|
|
3030
3203
|
execSync2(
|
|
3031
|
-
`security add-generic-password -s "${
|
|
3204
|
+
`security add-generic-password -s "${service}" -a "${ACCOUNT}" -w "${value}"`,
|
|
3032
3205
|
{ timeout: 5e3 }
|
|
3033
3206
|
);
|
|
3034
3207
|
return true;
|
|
@@ -3036,11 +3209,12 @@ function macKeychainSet(value) {
|
|
|
3036
3209
|
return false;
|
|
3037
3210
|
}
|
|
3038
3211
|
}
|
|
3039
|
-
function macKeychainDelete() {
|
|
3212
|
+
function macKeychainDelete(service = SERVICE) {
|
|
3213
|
+
if (!nativeKeychainAllowed()) return false;
|
|
3040
3214
|
if (process.platform !== "darwin") return false;
|
|
3041
3215
|
try {
|
|
3042
3216
|
execSync2(
|
|
3043
|
-
`security delete-generic-password -s "${
|
|
3217
|
+
`security delete-generic-password -s "${service}" -a "${ACCOUNT}" 2>/dev/null`,
|
|
3044
3218
|
{ timeout: 5e3 }
|
|
3045
3219
|
);
|
|
3046
3220
|
return true;
|
|
@@ -3048,22 +3222,22 @@ function macKeychainDelete() {
|
|
|
3048
3222
|
return false;
|
|
3049
3223
|
}
|
|
3050
3224
|
}
|
|
3051
|
-
function linuxSecretGet() {
|
|
3052
|
-
if (
|
|
3225
|
+
function linuxSecretGet(service = SERVICE) {
|
|
3226
|
+
if (!linuxSecretAvailable()) return null;
|
|
3053
3227
|
try {
|
|
3054
3228
|
return execSync2(
|
|
3055
|
-
`secret-tool lookup service "${
|
|
3229
|
+
`secret-tool lookup service "${service}" account "${ACCOUNT}" 2>/dev/null`,
|
|
3056
3230
|
{ encoding: "utf-8", timeout: 5e3 }
|
|
3057
3231
|
).trim();
|
|
3058
3232
|
} catch {
|
|
3059
3233
|
return null;
|
|
3060
3234
|
}
|
|
3061
3235
|
}
|
|
3062
|
-
function linuxSecretSet(value) {
|
|
3063
|
-
if (
|
|
3236
|
+
function linuxSecretSet(value, service = SERVICE) {
|
|
3237
|
+
if (!linuxSecretAvailable()) return false;
|
|
3064
3238
|
try {
|
|
3065
3239
|
execSync2(
|
|
3066
|
-
`echo -n "${value}" | secret-tool store --label="exe-os master key" service "${
|
|
3240
|
+
`echo -n "${value}" | secret-tool store --label="exe-os master key" service "${service}" account "${ACCOUNT}" 2>/dev/null`,
|
|
3067
3241
|
{ timeout: 5e3 }
|
|
3068
3242
|
);
|
|
3069
3243
|
return true;
|
|
@@ -3071,11 +3245,12 @@ function linuxSecretSet(value) {
|
|
|
3071
3245
|
return false;
|
|
3072
3246
|
}
|
|
3073
3247
|
}
|
|
3074
|
-
function linuxSecretDelete() {
|
|
3248
|
+
function linuxSecretDelete(service = SERVICE) {
|
|
3249
|
+
if (!nativeKeychainAllowed()) return false;
|
|
3075
3250
|
if (process.platform !== "linux") return false;
|
|
3076
3251
|
try {
|
|
3077
3252
|
execSync2(
|
|
3078
|
-
`secret-tool clear service "${
|
|
3253
|
+
`secret-tool clear service "${service}" account "${ACCOUNT}" 2>/dev/null`,
|
|
3079
3254
|
{ timeout: 5e3 }
|
|
3080
3255
|
);
|
|
3081
3256
|
return true;
|
|
@@ -3084,6 +3259,7 @@ function linuxSecretDelete() {
|
|
|
3084
3259
|
}
|
|
3085
3260
|
}
|
|
3086
3261
|
async function tryKeytar() {
|
|
3262
|
+
if (!nativeKeychainAllowed()) return null;
|
|
3087
3263
|
try {
|
|
3088
3264
|
return await import("keytar");
|
|
3089
3265
|
} catch {
|
|
@@ -3157,7 +3333,19 @@ async function writeMachineBoundFileFallback(b64) {
|
|
|
3157
3333
|
return "plaintext";
|
|
3158
3334
|
}
|
|
3159
3335
|
async function getMasterKey() {
|
|
3160
|
-
|
|
3336
|
+
let nativeValue = macKeychainGet() ?? linuxSecretGet();
|
|
3337
|
+
if (!nativeValue) {
|
|
3338
|
+
const legacyValue = macKeychainGet(LEGACY_SERVICE) ?? linuxSecretGet(LEGACY_SERVICE);
|
|
3339
|
+
if (legacyValue) {
|
|
3340
|
+
const migrated = macKeychainSet(legacyValue) || linuxSecretSet(legacyValue);
|
|
3341
|
+
if (migrated) {
|
|
3342
|
+
macKeychainDelete(LEGACY_SERVICE);
|
|
3343
|
+
linuxSecretDelete(LEGACY_SERVICE);
|
|
3344
|
+
process.stderr.write("[keychain] Migrated keychain service from exe-mem to exe-os.\n");
|
|
3345
|
+
}
|
|
3346
|
+
nativeValue = legacyValue;
|
|
3347
|
+
}
|
|
3348
|
+
}
|
|
3161
3349
|
if (nativeValue) {
|
|
3162
3350
|
return Buffer.from(nativeValue, "base64");
|
|
3163
3351
|
}
|
|
@@ -3165,12 +3353,17 @@ async function getMasterKey() {
|
|
|
3165
3353
|
if (keytar) {
|
|
3166
3354
|
try {
|
|
3167
3355
|
const keytarValue = await keytar.getPassword(SERVICE, ACCOUNT);
|
|
3168
|
-
|
|
3169
|
-
|
|
3356
|
+
const legacyKeytarValue = keytarValue ?? await keytar.getPassword(LEGACY_SERVICE, ACCOUNT);
|
|
3357
|
+
if (legacyKeytarValue) {
|
|
3358
|
+
const migrated = macKeychainSet(legacyKeytarValue) || linuxSecretSet(legacyKeytarValue);
|
|
3170
3359
|
if (migrated) {
|
|
3171
3360
|
process.stderr.write("[keychain] Migrated key from keytar to native keychain.\n");
|
|
3361
|
+
try {
|
|
3362
|
+
await keytar.deletePassword(LEGACY_SERVICE, ACCOUNT);
|
|
3363
|
+
} catch {
|
|
3364
|
+
}
|
|
3172
3365
|
}
|
|
3173
|
-
return Buffer.from(
|
|
3366
|
+
return Buffer.from(legacyKeytarValue, "base64");
|
|
3174
3367
|
}
|
|
3175
3368
|
} catch {
|
|
3176
3369
|
}
|
|
@@ -3195,7 +3388,7 @@ async function getMasterKey() {
|
|
|
3195
3388
|
const decrypted = decryptWithMachineKey(content, machineKey);
|
|
3196
3389
|
if (!decrypted) {
|
|
3197
3390
|
process.stderr.write(
|
|
3198
|
-
"[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase: exe-os
|
|
3391
|
+
"[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase during setup: exe-os setup\n"
|
|
3199
3392
|
);
|
|
3200
3393
|
return null;
|
|
3201
3394
|
}
|
|
@@ -3204,6 +3397,9 @@ async function getMasterKey() {
|
|
|
3204
3397
|
b64Value = content;
|
|
3205
3398
|
}
|
|
3206
3399
|
const key = Buffer.from(b64Value, "base64");
|
|
3400
|
+
if (!content.startsWith(ENCRYPTED_PREFIX) && isRootOnlyTrustedServerKeyFile(keyPath)) {
|
|
3401
|
+
return key;
|
|
3402
|
+
}
|
|
3207
3403
|
const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
|
|
3208
3404
|
if (migrated) {
|
|
3209
3405
|
process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
|
|
@@ -3231,6 +3427,97 @@ async function getMasterKey() {
|
|
|
3231
3427
|
return null;
|
|
3232
3428
|
}
|
|
3233
3429
|
}
|
|
3430
|
+
async function getKeyStorageInfo() {
|
|
3431
|
+
if (macKeychainGet()) {
|
|
3432
|
+
return {
|
|
3433
|
+
kind: "macos-keychain",
|
|
3434
|
+
secure: true,
|
|
3435
|
+
note: "stored in macOS Keychain via built-in security CLI"
|
|
3436
|
+
};
|
|
3437
|
+
}
|
|
3438
|
+
if (macKeychainGet(LEGACY_SERVICE)) {
|
|
3439
|
+
return {
|
|
3440
|
+
kind: "macos-keychain",
|
|
3441
|
+
secure: true,
|
|
3442
|
+
note: "stored in legacy macOS Keychain service exe-mem; next key read migrates it to exe-os"
|
|
3443
|
+
};
|
|
3444
|
+
}
|
|
3445
|
+
if (linuxSecretGet()) {
|
|
3446
|
+
return {
|
|
3447
|
+
kind: "linux-secret-service",
|
|
3448
|
+
secure: true,
|
|
3449
|
+
note: "stored in Linux Secret Service via secret-tool"
|
|
3450
|
+
};
|
|
3451
|
+
}
|
|
3452
|
+
if (linuxSecretGet(LEGACY_SERVICE)) {
|
|
3453
|
+
return {
|
|
3454
|
+
kind: "linux-secret-service",
|
|
3455
|
+
secure: true,
|
|
3456
|
+
note: "stored in legacy Linux Secret Service service exe-mem; next key read migrates it to exe-os"
|
|
3457
|
+
};
|
|
3458
|
+
}
|
|
3459
|
+
const keytar = await tryKeytar();
|
|
3460
|
+
if (keytar) {
|
|
3461
|
+
try {
|
|
3462
|
+
if (await keytar.getPassword(SERVICE, ACCOUNT)) {
|
|
3463
|
+
return {
|
|
3464
|
+
kind: "legacy-keytar",
|
|
3465
|
+
secure: true,
|
|
3466
|
+
note: "stored in legacy keytar backend; will migrate to native keychain when possible"
|
|
3467
|
+
};
|
|
3468
|
+
}
|
|
3469
|
+
if (await keytar.getPassword(LEGACY_SERVICE, ACCOUNT)) {
|
|
3470
|
+
return {
|
|
3471
|
+
kind: "legacy-keytar",
|
|
3472
|
+
secure: true,
|
|
3473
|
+
note: "stored in legacy keytar service exe-mem; will migrate to native exe-os keychain when possible"
|
|
3474
|
+
};
|
|
3475
|
+
}
|
|
3476
|
+
} catch {
|
|
3477
|
+
}
|
|
3478
|
+
}
|
|
3479
|
+
const keyPath = getKeyPath();
|
|
3480
|
+
if (!existsSync6(keyPath)) {
|
|
3481
|
+
return {
|
|
3482
|
+
kind: "missing",
|
|
3483
|
+
secure: false,
|
|
3484
|
+
path: keyPath,
|
|
3485
|
+
note: "no key found in OS keychain, legacy keytar, or file fallback"
|
|
3486
|
+
};
|
|
3487
|
+
}
|
|
3488
|
+
try {
|
|
3489
|
+
const content = (await readFile3(keyPath, "utf-8")).trim();
|
|
3490
|
+
if (content.startsWith(ENCRYPTED_PREFIX)) {
|
|
3491
|
+
return {
|
|
3492
|
+
kind: "encrypted-file",
|
|
3493
|
+
secure: true,
|
|
3494
|
+
path: keyPath,
|
|
3495
|
+
note: "stored in machine-bound encrypted file fallback"
|
|
3496
|
+
};
|
|
3497
|
+
}
|
|
3498
|
+
if (isRootOnlyTrustedServerKeyFile(keyPath)) {
|
|
3499
|
+
return {
|
|
3500
|
+
kind: "server-secret-file",
|
|
3501
|
+
secure: true,
|
|
3502
|
+
path: keyPath,
|
|
3503
|
+
note: "stored as root-only trusted server secret file"
|
|
3504
|
+
};
|
|
3505
|
+
}
|
|
3506
|
+
return {
|
|
3507
|
+
kind: "plaintext-file",
|
|
3508
|
+
secure: false,
|
|
3509
|
+
path: keyPath,
|
|
3510
|
+
note: "stored in legacy plaintext file; reading it will migrate or encrypt it"
|
|
3511
|
+
};
|
|
3512
|
+
} catch {
|
|
3513
|
+
return {
|
|
3514
|
+
kind: "missing",
|
|
3515
|
+
secure: false,
|
|
3516
|
+
path: keyPath,
|
|
3517
|
+
note: "key file exists but could not be read"
|
|
3518
|
+
};
|
|
3519
|
+
}
|
|
3520
|
+
}
|
|
3234
3521
|
async function setMasterKey(key) {
|
|
3235
3522
|
const b64 = key.toString("base64");
|
|
3236
3523
|
if (macKeychainSet(b64) || linuxSecretSet(b64)) {
|
|
@@ -3256,10 +3543,13 @@ async function setMasterKey(key) {
|
|
|
3256
3543
|
async function deleteMasterKey() {
|
|
3257
3544
|
macKeychainDelete();
|
|
3258
3545
|
linuxSecretDelete();
|
|
3546
|
+
macKeychainDelete(LEGACY_SERVICE);
|
|
3547
|
+
linuxSecretDelete(LEGACY_SERVICE);
|
|
3259
3548
|
const keytar = await tryKeytar();
|
|
3260
3549
|
if (keytar) {
|
|
3261
3550
|
try {
|
|
3262
3551
|
await keytar.deletePassword(SERVICE, ACCOUNT);
|
|
3552
|
+
await keytar.deletePassword(LEGACY_SERVICE, ACCOUNT);
|
|
3263
3553
|
} catch {
|
|
3264
3554
|
}
|
|
3265
3555
|
}
|
|
@@ -3297,12 +3587,14 @@ async function importMnemonic(mnemonic) {
|
|
|
3297
3587
|
const entropy = mnemonicToEntropy(trimmed);
|
|
3298
3588
|
return Buffer.from(entropy, "hex");
|
|
3299
3589
|
}
|
|
3300
|
-
var SERVICE, ACCOUNT, ENCRYPTED_PREFIX;
|
|
3590
|
+
var SERVICE, LEGACY_SERVICE, ACCOUNT, linuxSecretAvailability, ENCRYPTED_PREFIX;
|
|
3301
3591
|
var init_keychain = __esm({
|
|
3302
3592
|
"src/lib/keychain.ts"() {
|
|
3303
3593
|
"use strict";
|
|
3304
|
-
SERVICE = "exe-
|
|
3594
|
+
SERVICE = "exe-os";
|
|
3595
|
+
LEGACY_SERVICE = "exe-mem";
|
|
3305
3596
|
ACCOUNT = "master-key";
|
|
3597
|
+
linuxSecretAvailability = null;
|
|
3306
3598
|
ENCRYPTED_PREFIX = "enc:";
|
|
3307
3599
|
}
|
|
3308
3600
|
});
|
|
@@ -3378,7 +3670,7 @@ __export(shard_manager_exports, {
|
|
|
3378
3670
|
shardExists: () => shardExists
|
|
3379
3671
|
});
|
|
3380
3672
|
import path7 from "path";
|
|
3381
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync2, readdirSync, renameSync as renameSync3, statSync as
|
|
3673
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync2, readdirSync, renameSync as renameSync3, statSync as statSync3 } from "fs";
|
|
3382
3674
|
import { createClient as createClient2 } from "@libsql/client";
|
|
3383
3675
|
function initShardManager(encryptionKey) {
|
|
3384
3676
|
_encryptionKey = encryptionKey;
|
|
@@ -3442,7 +3734,7 @@ async function auditShardHealth(options = {}) {
|
|
|
3442
3734
|
const shards = [];
|
|
3443
3735
|
for (const name of names) {
|
|
3444
3736
|
const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
|
|
3445
|
-
const stat =
|
|
3737
|
+
const stat = statSync3(dbPath);
|
|
3446
3738
|
const item = {
|
|
3447
3739
|
name,
|
|
3448
3740
|
path: dbPath,
|
|
@@ -3695,7 +3987,7 @@ async function getReadyShardClient(projectName) {
|
|
|
3695
3987
|
_shardLastAccess.delete(safeName);
|
|
3696
3988
|
const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
|
|
3697
3989
|
if (existsSync7(dbPath)) {
|
|
3698
|
-
const stat =
|
|
3990
|
+
const stat = statSync3(dbPath);
|
|
3699
3991
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
3700
3992
|
const archivedPath = path7.join(SHARDS_DIR, `${safeName}.db.broken-${stamp}`);
|
|
3701
3993
|
renameSync3(dbPath, archivedPath);
|
|
@@ -4589,8 +4881,8 @@ async function validateLicense(apiKey, deviceId) {
|
|
|
4589
4881
|
}
|
|
4590
4882
|
function getCacheAgeMs() {
|
|
4591
4883
|
try {
|
|
4592
|
-
const { statSync:
|
|
4593
|
-
const s =
|
|
4884
|
+
const { statSync: statSync7 } = __require("fs");
|
|
4885
|
+
const s = statSync7(CACHE_PATH);
|
|
4594
4886
|
return Date.now() - s.mtimeMs;
|
|
4595
4887
|
} catch {
|
|
4596
4888
|
return Infinity;
|
|
@@ -5567,7 +5859,7 @@ __export(db_backup_exports, {
|
|
|
5567
5859
|
listBackups: () => listBackups,
|
|
5568
5860
|
rotateBackups: () => rotateBackups
|
|
5569
5861
|
});
|
|
5570
|
-
import { copyFileSync, existsSync as existsSync18, mkdirSync as mkdirSync9, readdirSync as readdirSync5, unlinkSync as unlinkSync7, statSync as
|
|
5862
|
+
import { copyFileSync, existsSync as existsSync18, mkdirSync as mkdirSync9, readdirSync as readdirSync5, unlinkSync as unlinkSync7, statSync as statSync5 } from "fs";
|
|
5571
5863
|
import path19 from "path";
|
|
5572
5864
|
function findActiveDb() {
|
|
5573
5865
|
for (const name of DB_NAMES) {
|
|
@@ -5611,7 +5903,7 @@ function rotateBackups(keepDays = DEFAULT_KEEP_DAYS) {
|
|
|
5611
5903
|
if (!file.endsWith(".db") && !file.endsWith(".db-wal") && !file.endsWith(".db-shm")) continue;
|
|
5612
5904
|
const filePath = path19.join(BACKUP_DIR, file);
|
|
5613
5905
|
try {
|
|
5614
|
-
const stat =
|
|
5906
|
+
const stat = statSync5(filePath);
|
|
5615
5907
|
if (stat.mtimeMs < cutoff) {
|
|
5616
5908
|
unlinkSync7(filePath);
|
|
5617
5909
|
deleted++;
|
|
@@ -5629,7 +5921,7 @@ function listBackups() {
|
|
|
5629
5921
|
const files = readdirSync5(BACKUP_DIR).filter((f) => f.endsWith(".db") && !f.endsWith("-wal") && !f.endsWith("-shm"));
|
|
5630
5922
|
return files.map((name) => {
|
|
5631
5923
|
const p = path19.join(BACKUP_DIR, name);
|
|
5632
|
-
const stat =
|
|
5924
|
+
const stat = statSync5(p);
|
|
5633
5925
|
return { path: p, name, size: stat.size, date: stat.mtime };
|
|
5634
5926
|
}).sort((a, b) => b.date.getTime() - a.date.getTime());
|
|
5635
5927
|
} catch {
|
|
@@ -5662,10 +5954,10 @@ var init_db_backup = __esm({
|
|
|
5662
5954
|
// src/lib/cloud-sync.ts
|
|
5663
5955
|
var cloud_sync_exports = {};
|
|
5664
5956
|
__export(cloud_sync_exports, {
|
|
5665
|
-
|
|
5957
|
+
CLOUD_REUPLOAD_REQUIRED_MESSAGE: () => CLOUD_REUPLOAD_REQUIRED_MESSAGE,
|
|
5666
5958
|
assertSecureEndpoint: () => assertSecureEndpoint,
|
|
5667
5959
|
buildRosterBlob: () => buildRosterBlob,
|
|
5668
|
-
|
|
5960
|
+
clearCloudReuploadRequired: () => clearCloudReuploadRequired,
|
|
5669
5961
|
cloudPull: () => cloudPull,
|
|
5670
5962
|
cloudPullBehaviors: () => cloudPullBehaviors,
|
|
5671
5963
|
cloudPullBlob: () => cloudPullBlob,
|
|
@@ -5684,14 +5976,16 @@ __export(cloud_sync_exports, {
|
|
|
5684
5976
|
cloudPushGraphRAG: () => cloudPushGraphRAG,
|
|
5685
5977
|
cloudPushRoster: () => cloudPushRoster,
|
|
5686
5978
|
cloudPushTasks: () => cloudPushTasks,
|
|
5979
|
+
cloudResetMemoryBlobs: () => cloudResetMemoryBlobs,
|
|
5687
5980
|
cloudSync: () => cloudSync,
|
|
5688
|
-
|
|
5981
|
+
getCloudReuploadRequired: () => getCloudReuploadRequired,
|
|
5982
|
+
markCloudReuploadRequired: () => markCloudReuploadRequired,
|
|
5689
5983
|
mergeConfig: () => mergeConfig,
|
|
5690
5984
|
mergeRosterFromRemote: () => mergeRosterFromRemote,
|
|
5691
5985
|
pushToPostgres: () => pushToPostgres,
|
|
5692
5986
|
recordRosterDeletion: () => recordRosterDeletion
|
|
5693
5987
|
});
|
|
5694
|
-
import { readFileSync as readFileSync13, writeFileSync as writeFileSync9, existsSync as existsSync19, readdirSync as readdirSync6, mkdirSync as mkdirSync10, appendFileSync as appendFileSync3, unlinkSync as unlinkSync8, openSync as openSync2, closeSync as closeSync2, statSync as
|
|
5988
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync9, existsSync as existsSync19, readdirSync as readdirSync6, mkdirSync as mkdirSync10, appendFileSync as appendFileSync3, unlinkSync as unlinkSync8, openSync as openSync2, closeSync as closeSync2, statSync as statSync6 } from "fs";
|
|
5695
5989
|
import crypto4 from "crypto";
|
|
5696
5990
|
import path20 from "path";
|
|
5697
5991
|
import { homedir as homedir2 } from "os";
|
|
@@ -5738,18 +6032,36 @@ function loadPgClient() {
|
|
|
5738
6032
|
const { pathToFileURL: pathToFileURL3 } = await import("url");
|
|
5739
6033
|
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
5740
6034
|
if (explicitPath) {
|
|
5741
|
-
const
|
|
5742
|
-
const
|
|
5743
|
-
if (!
|
|
5744
|
-
return new
|
|
6035
|
+
const mod = await import(pathToFileURL3(explicitPath).href);
|
|
6036
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
6037
|
+
if (!Ctor) throw new Error(`No PrismaClient at ${explicitPath}`);
|
|
6038
|
+
return new Ctor();
|
|
5745
6039
|
}
|
|
5746
6040
|
const exeDbRoot = process.env.EXE_DB_ROOT ?? path20.join(homedir2(), "exe-db");
|
|
5747
|
-
const
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
6041
|
+
const packagePath = path20.join(exeDbRoot, "package.json");
|
|
6042
|
+
if (existsSync19(packagePath)) {
|
|
6043
|
+
const req = createRequire3(packagePath);
|
|
6044
|
+
const entry = req.resolve("@prisma/client");
|
|
6045
|
+
const mod = await import(pathToFileURL3(entry).href);
|
|
6046
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
6047
|
+
if (!Ctor) throw new Error("No PrismaClient");
|
|
6048
|
+
return new Ctor();
|
|
6049
|
+
}
|
|
6050
|
+
const { Pool } = await import("pg");
|
|
6051
|
+
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
|
|
6052
|
+
return {
|
|
6053
|
+
async $queryRawUnsafe(query, ...values) {
|
|
6054
|
+
const result = await pool.query(query, values);
|
|
6055
|
+
return result.rows;
|
|
6056
|
+
},
|
|
6057
|
+
async $executeRawUnsafe(query, ...values) {
|
|
6058
|
+
const result = await pool.query(query, values);
|
|
6059
|
+
return result.rowCount ?? 0;
|
|
6060
|
+
},
|
|
6061
|
+
async $disconnect() {
|
|
6062
|
+
await pool.end();
|
|
6063
|
+
}
|
|
6064
|
+
};
|
|
5753
6065
|
})().catch(() => {
|
|
5754
6066
|
_pgFailed = true;
|
|
5755
6067
|
_pgPromise = null;
|
|
@@ -5770,7 +6082,7 @@ async function pushToPostgres(records) {
|
|
|
5770
6082
|
let inserted = 0;
|
|
5771
6083
|
for (const rec of records) {
|
|
5772
6084
|
try {
|
|
5773
|
-
await prisma.$executeRawUnsafe(
|
|
6085
|
+
const changed = await prisma.$executeRawUnsafe(
|
|
5774
6086
|
`INSERT INTO raw.raw_events (id, source, source_id, event_type, payload, metadata, timestamp)
|
|
5775
6087
|
VALUES (gen_random_uuid(), 'cloud_sync', $1, 'memory', $2::jsonb, $3::jsonb, $4)
|
|
5776
6088
|
ON CONFLICT (source, source_id, event_type) DO NOTHING`,
|
|
@@ -5779,7 +6091,7 @@ async function pushToPostgres(records) {
|
|
|
5779
6091
|
JSON.stringify({ agent_id: rec.agent_id, project_name: rec.project_name, tool_name: rec.tool_name }),
|
|
5780
6092
|
rec.timestamp ? new Date(String(rec.timestamp)) : /* @__PURE__ */ new Date()
|
|
5781
6093
|
);
|
|
5782
|
-
inserted
|
|
6094
|
+
inserted += Number(changed ?? 0);
|
|
5783
6095
|
} catch {
|
|
5784
6096
|
}
|
|
5785
6097
|
}
|
|
@@ -5884,6 +6196,23 @@ async function cloudPush(records, maxVersion, config) {
|
|
|
5884
6196
|
return false;
|
|
5885
6197
|
}
|
|
5886
6198
|
}
|
|
6199
|
+
async function cloudResetMemoryBlobs(config) {
|
|
6200
|
+
assertSecureEndpoint(config.endpoint);
|
|
6201
|
+
const resp = await fetchWithRetry(`${config.endpoint}/sync/reset-memory`, {
|
|
6202
|
+
method: "POST",
|
|
6203
|
+
headers: {
|
|
6204
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
6205
|
+
"Content-Type": "application/json",
|
|
6206
|
+
"X-Device-Id": loadDeviceId()
|
|
6207
|
+
},
|
|
6208
|
+
body: JSON.stringify({ confirm: "LOCAL DB IS SOURCE OF TRUTH" })
|
|
6209
|
+
});
|
|
6210
|
+
if (!resp.ok) {
|
|
6211
|
+
throw new Error(`cloud reset failed: HTTP ${resp.status}`);
|
|
6212
|
+
}
|
|
6213
|
+
const data = await resp.json();
|
|
6214
|
+
return { deleted: Number(data.deleted ?? 0), freedBytes: Number(data.freed_bytes ?? 0) };
|
|
6215
|
+
}
|
|
5887
6216
|
async function cloudPull(sinceVersion, config) {
|
|
5888
6217
|
assertSecureEndpoint(config.endpoint);
|
|
5889
6218
|
try {
|
|
@@ -5903,40 +6232,61 @@ async function cloudPull(sinceVersion, config) {
|
|
|
5903
6232
|
if (!response.ok) return { records: [], maxVersion: sinceVersion };
|
|
5904
6233
|
const data = await response.json();
|
|
5905
6234
|
const allRecords = [];
|
|
5906
|
-
|
|
6235
|
+
let maxReadableVersion = sinceVersion;
|
|
6236
|
+
let skippedBlobs = 0;
|
|
6237
|
+
for (const { version, blob } of data.blobs ?? []) {
|
|
5907
6238
|
try {
|
|
5908
6239
|
const compressed = decryptSyncBlob(blob);
|
|
5909
6240
|
const json = decompress(compressed).toString("utf8");
|
|
5910
6241
|
const records = JSON.parse(json);
|
|
5911
6242
|
allRecords.push(...records);
|
|
6243
|
+
const recordMax = records.reduce((max, rec) => {
|
|
6244
|
+
const v = Number(rec.version ?? 0);
|
|
6245
|
+
return Number.isFinite(v) ? Math.max(max, v) : max;
|
|
6246
|
+
}, 0);
|
|
6247
|
+
const blobVersion = Number(version ?? 0);
|
|
6248
|
+
maxReadableVersion = Math.max(
|
|
6249
|
+
maxReadableVersion,
|
|
6250
|
+
Number.isFinite(blobVersion) ? blobVersion : 0,
|
|
6251
|
+
recordMax
|
|
6252
|
+
);
|
|
5912
6253
|
} catch {
|
|
6254
|
+
skippedBlobs++;
|
|
5913
6255
|
continue;
|
|
5914
6256
|
}
|
|
5915
6257
|
}
|
|
5916
|
-
|
|
6258
|
+
if (skippedBlobs > 0) {
|
|
6259
|
+
logError(`[cloud-sync] PULL skipped ${skippedBlobs} undecryptable blob(s); pull cursor advanced only to last readable version ${maxReadableVersion}`);
|
|
6260
|
+
}
|
|
6261
|
+
return { records: allRecords, maxVersion: maxReadableVersion };
|
|
5917
6262
|
} catch (err) {
|
|
5918
6263
|
logError(`[cloud-sync] PULL FAILED: ${err instanceof Error ? err.message : String(err)}`);
|
|
5919
6264
|
return { records: [], maxVersion: sinceVersion };
|
|
5920
6265
|
}
|
|
5921
6266
|
}
|
|
5922
|
-
async function
|
|
6267
|
+
async function getCloudReuploadRequired(client = getClient()) {
|
|
5923
6268
|
try {
|
|
5924
6269
|
await client.execute("CREATE TABLE IF NOT EXISTS sync_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)");
|
|
5925
|
-
const
|
|
5926
|
-
return String(
|
|
6270
|
+
const result = await client.execute("SELECT key, value FROM sync_meta WHERE key IN ('cloud_reupload_required', 'cloud_relink_required')");
|
|
6271
|
+
return result.rows.some((row) => String(row.value ?? "") === "1");
|
|
5927
6272
|
} catch {
|
|
5928
6273
|
return false;
|
|
5929
6274
|
}
|
|
5930
6275
|
}
|
|
5931
|
-
async function
|
|
6276
|
+
async function clearCloudReuploadRequired(client = getClient()) {
|
|
5932
6277
|
await client.execute("CREATE TABLE IF NOT EXISTS sync_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)");
|
|
6278
|
+
await client.execute("INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('cloud_reupload_required', '0')");
|
|
5933
6279
|
await client.execute("INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('cloud_relink_required', '0')");
|
|
5934
6280
|
await client.execute({
|
|
5935
|
-
sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('
|
|
6281
|
+
sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('cloud_reuploaded_at', ?)",
|
|
5936
6282
|
args: [(/* @__PURE__ */ new Date()).toISOString()]
|
|
5937
6283
|
});
|
|
5938
6284
|
await client.execute("DELETE FROM sync_meta WHERE key IN ('last_cloud_pull_version', 'last_cloud_push_version')");
|
|
5939
6285
|
}
|
|
6286
|
+
async function markCloudReuploadRequired(client = getClient()) {
|
|
6287
|
+
await client.execute("CREATE TABLE IF NOT EXISTS sync_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)");
|
|
6288
|
+
await client.execute("INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('cloud_reupload_required', '1')");
|
|
6289
|
+
}
|
|
5940
6290
|
async function cloudSync(config) {
|
|
5941
6291
|
if (!isSyncCryptoInitialized()) {
|
|
5942
6292
|
try {
|
|
@@ -5958,10 +6308,10 @@ async function cloudSync(config) {
|
|
|
5958
6308
|
throw new Error("[cloud-sync] Database not initialized. Call initStore() before cloudSync().");
|
|
5959
6309
|
}
|
|
5960
6310
|
try {
|
|
5961
|
-
if (await
|
|
6311
|
+
if (await getCloudReuploadRequired(client)) throw new Error(CLOUD_REUPLOAD_REQUIRED_MESSAGE);
|
|
5962
6312
|
} catch (err) {
|
|
5963
6313
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5964
|
-
if (msg.includes("
|
|
6314
|
+
if (msg === CLOUD_REUPLOAD_REQUIRED_MESSAGE || msg.includes("key rotation")) throw err;
|
|
5965
6315
|
}
|
|
5966
6316
|
try {
|
|
5967
6317
|
const { getRawClient: getRawClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
@@ -6219,7 +6569,7 @@ async function cloudSync(config) {
|
|
|
6219
6569
|
const { getLatestBackup: getLatestBackup2 } = await Promise.resolve().then(() => (init_db_backup(), db_backup_exports));
|
|
6220
6570
|
const latestBackup = getLatestBackup2();
|
|
6221
6571
|
if (latestBackup) {
|
|
6222
|
-
const backupSize =
|
|
6572
|
+
const backupSize = statSync6(latestBackup).size;
|
|
6223
6573
|
const MAX_CLOUD_BACKUP_BYTES = 50 * 1024 * 1024;
|
|
6224
6574
|
if (backupSize <= MAX_CLOUD_BACKUP_BYTES) {
|
|
6225
6575
|
const backupData = readFileSync13(latestBackup);
|
|
@@ -6890,7 +7240,7 @@ async function cloudPullDocuments(config) {
|
|
|
6890
7240
|
}
|
|
6891
7241
|
return { pulled };
|
|
6892
7242
|
}
|
|
6893
|
-
var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, _pgPromise, _pgFailed,
|
|
7243
|
+
var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, _pgPromise, _pgFailed, CLOUD_REUPLOAD_REQUIRED_MESSAGE, ROSTER_DELETIONS_PATH;
|
|
6894
7244
|
var init_cloud_sync = __esm({
|
|
6895
7245
|
"src/lib/cloud-sync.ts"() {
|
|
6896
7246
|
"use strict";
|
|
@@ -6909,7 +7259,7 @@ var init_cloud_sync = __esm({
|
|
|
6909
7259
|
LOCK_STALE_MS = 3e4;
|
|
6910
7260
|
_pgPromise = null;
|
|
6911
7261
|
_pgFailed = false;
|
|
6912
|
-
|
|
7262
|
+
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.";
|
|
6913
7263
|
ROSTER_DELETIONS_PATH = path20.join(EXE_AI_DIR, "roster-deletions.json");
|
|
6914
7264
|
}
|
|
6915
7265
|
});
|
|
@@ -7107,7 +7457,7 @@ init_exe_daemon_client();
|
|
|
7107
7457
|
|
|
7108
7458
|
// src/lib/memory-queue.ts
|
|
7109
7459
|
init_config();
|
|
7110
|
-
import { appendFileSync as appendFileSync2, readFileSync as readFileSync11, renameSync as renameSync5, unlinkSync as unlinkSync4, existsSync as existsSync15, statSync as
|
|
7460
|
+
import { appendFileSync as appendFileSync2, readFileSync as readFileSync11, renameSync as renameSync5, unlinkSync as unlinkSync4, existsSync as existsSync15, statSync as statSync4 } from "fs";
|
|
7111
7461
|
import path16 from "path";
|
|
7112
7462
|
var QUEUE_PATH2 = path16.join(EXE_AI_DIR, "memory-queue.jsonl");
|
|
7113
7463
|
var PROCESSING_PATH = QUEUE_PATH2 + ".processing";
|