@askexenow/exe-os 0.9.65 → 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} +1532 -2173
- package/dist/bin/backfill-conversations.js +528 -20
- package/dist/bin/backfill-responses.js +528 -20
- package/dist/bin/backfill-vectors.js +255 -20
- package/dist/bin/bulk-sync-postgres.js +4876 -0
- package/dist/bin/cleanup-stale-review-tasks.js +529 -21
- package/dist/bin/cli.js +3471 -1491
- package/dist/bin/exe-agent-config.js +4 -0
- package/dist/bin/exe-agent.js +16 -0
- package/dist/bin/exe-assign.js +528 -20
- package/dist/bin/exe-boot.js +492 -54
- package/dist/bin/exe-call.js +16 -0
- package/dist/bin/exe-cloud.js +7415 -518
- package/dist/bin/exe-dispatch.js +540 -22
- package/dist/bin/exe-doctor.js +3404 -1225
- package/dist/bin/exe-export-behaviors.js +542 -24
- package/dist/bin/exe-forget.js +529 -21
- package/dist/bin/exe-gateway.js +595 -25
- package/dist/bin/exe-heartbeat.js +541 -24
- package/dist/bin/exe-kill.js +529 -21
- package/dist/bin/exe-launch-agent.js +2334 -1067
- package/dist/bin/exe-new-employee.js +324 -166
- package/dist/bin/exe-pending-messages.js +529 -21
- package/dist/bin/exe-pending-notifications.js +529 -21
- package/dist/bin/exe-pending-reviews.js +529 -21
- package/dist/bin/exe-rename.js +529 -21
- package/dist/bin/exe-review.js +529 -21
- package/dist/bin/exe-search.js +542 -24
- package/dist/bin/exe-session-cleanup.js +540 -22
- package/dist/bin/exe-settings.js +14 -0
- package/dist/bin/exe-start-codex.js +817 -144
- package/dist/bin/exe-start-opencode.js +776 -80
- package/dist/bin/exe-status.js +529 -21
- package/dist/bin/exe-team.js +529 -21
- package/dist/bin/git-sweep.js +540 -22
- package/dist/bin/graph-backfill.js +580 -21
- package/dist/bin/graph-export.js +529 -21
- package/dist/bin/graph-layer-benchmark.js +109 -0
- package/dist/bin/install.js +420 -289
- package/dist/bin/intercom-check.js +540 -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 +540 -22
- package/dist/bin/setup.js +790 -206
- package/dist/bin/shard-migrate.js +528 -20
- package/dist/bin/update.js +4 -0
- package/dist/gateway/index.js +593 -23
- package/dist/hooks/bug-report-worker.js +651 -64
- package/dist/hooks/codex-stop-task-finalizer.js +540 -22
- package/dist/hooks/commit-complete.js +540 -22
- package/dist/hooks/error-recall.js +542 -24
- package/dist/hooks/exe-heartbeat-hook.js +4 -0
- package/dist/hooks/ingest-worker.js +4 -0
- package/dist/hooks/ingest.js +539 -22
- package/dist/hooks/instructions-loaded.js +529 -21
- package/dist/hooks/notification.js +529 -21
- package/dist/hooks/post-compact.js +529 -21
- package/dist/hooks/post-tool-combined.js +543 -25
- package/dist/hooks/pre-compact.js +772 -127
- package/dist/hooks/pre-tool-use.js +529 -21
- package/dist/hooks/prompt-submit.js +543 -25
- package/dist/hooks/session-end.js +673 -140
- package/dist/hooks/session-start.js +662 -26
- package/dist/hooks/stop.js +540 -23
- package/dist/hooks/subagent-stop.js +529 -21
- package/dist/hooks/summary-worker.js +571 -126
- package/dist/index.js +593 -23
- package/dist/lib/agent-config.js +4 -0
- package/dist/lib/cloud-sync.js +408 -47
- package/dist/lib/config.js +25 -1
- package/dist/lib/consolidation.js +5 -1
- package/dist/lib/database.js +128 -0
- package/dist/lib/db-daemon-client.js +4 -0
- package/dist/lib/db.js +128 -0
- package/dist/lib/device-registry.js +128 -0
- package/dist/lib/embedder.js +25 -1
- package/dist/lib/employee-templates.js +16 -0
- package/dist/lib/employees.js +4 -0
- package/dist/lib/exe-daemon-client.js +4 -0
- package/dist/lib/exe-daemon.js +3158 -930
- package/dist/lib/hybrid-search.js +542 -24
- package/dist/lib/identity.js +7 -0
- package/dist/lib/keychain.js +178 -22
- package/dist/lib/license.js +4 -0
- package/dist/lib/messaging.js +7 -0
- package/dist/lib/reminders.js +7 -0
- package/dist/lib/schedules.js +255 -20
- package/dist/lib/skill-learning.js +28 -1
- package/dist/lib/status-brief.js +39 -0
- package/dist/lib/store.js +528 -20
- package/dist/lib/task-router.js +4 -0
- package/dist/lib/tasks.js +28 -1
- package/dist/lib/tmux-routing.js +28 -1
- package/dist/lib/token-spend.js +7 -0
- package/dist/mcp/server.js +2739 -813
- package/dist/mcp/tools/complete-reminder.js +7 -0
- package/dist/mcp/tools/create-reminder.js +7 -0
- package/dist/mcp/tools/create-task.js +28 -1
- package/dist/mcp/tools/deactivate-behavior.js +7 -0
- package/dist/mcp/tools/list-reminders.js +7 -0
- package/dist/mcp/tools/list-tasks.js +7 -0
- package/dist/mcp/tools/send-message.js +7 -0
- package/dist/mcp/tools/update-task.js +28 -1
- package/dist/runtime/index.js +540 -22
- package/dist/tui/App.js +618 -29
- package/package.json +9 -5
- package/src/commands/exe/cloud.md +11 -8
- package/stack.release.json +3 -3
- package/src/commands/exe/link.md +0 -17
package/dist/bin/exe-boot.js
CHANGED
|
@@ -150,6 +150,11 @@ function normalizeAutoUpdate(raw) {
|
|
|
150
150
|
const userAU = raw.autoUpdate ?? {};
|
|
151
151
|
raw.autoUpdate = { ...defaultAU, ...userAU };
|
|
152
152
|
}
|
|
153
|
+
function normalizeOrchestration(raw) {
|
|
154
|
+
const defaultOrg = DEFAULT_CONFIG.orchestration;
|
|
155
|
+
const userOrg = raw.orchestration ?? {};
|
|
156
|
+
raw.orchestration = { ...defaultOrg, ...userOrg };
|
|
157
|
+
}
|
|
153
158
|
async function loadConfig() {
|
|
154
159
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
155
160
|
await ensurePrivateDir(dir);
|
|
@@ -174,10 +179,15 @@ async function loadConfig() {
|
|
|
174
179
|
normalizeScalingRoadmap(migratedCfg);
|
|
175
180
|
normalizeSessionLifecycle(migratedCfg);
|
|
176
181
|
normalizeAutoUpdate(migratedCfg);
|
|
182
|
+
normalizeOrchestration(migratedCfg);
|
|
177
183
|
const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
178
184
|
if (config.dbPath.startsWith("~")) {
|
|
179
185
|
config.dbPath = config.dbPath.replace(/^~/, os.homedir());
|
|
180
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
|
+
}
|
|
181
191
|
return config;
|
|
182
192
|
} catch {
|
|
183
193
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
@@ -197,7 +207,16 @@ function loadConfigSync() {
|
|
|
197
207
|
normalizeScalingRoadmap(migratedCfg);
|
|
198
208
|
normalizeSessionLifecycle(migratedCfg);
|
|
199
209
|
normalizeAutoUpdate(migratedCfg);
|
|
200
|
-
|
|
210
|
+
normalizeOrchestration(migratedCfg);
|
|
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;
|
|
201
220
|
} catch {
|
|
202
221
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
203
222
|
}
|
|
@@ -218,6 +237,7 @@ async function loadConfigFrom(configPath) {
|
|
|
218
237
|
normalizeScalingRoadmap(migratedCfg);
|
|
219
238
|
normalizeSessionLifecycle(migratedCfg);
|
|
220
239
|
normalizeAutoUpdate(migratedCfg);
|
|
240
|
+
normalizeOrchestration(migratedCfg);
|
|
221
241
|
return { ...DEFAULT_CONFIG, ...migratedCfg };
|
|
222
242
|
} catch {
|
|
223
243
|
return { ...DEFAULT_CONFIG };
|
|
@@ -289,6 +309,10 @@ var init_config = __esm({
|
|
|
289
309
|
checkOnBoot: true,
|
|
290
310
|
autoInstall: false,
|
|
291
311
|
checkIntervalMs: 24 * 60 * 60 * 1e3
|
|
312
|
+
},
|
|
313
|
+
orchestration: {
|
|
314
|
+
phase: "phase_1_coo",
|
|
315
|
+
phaseSetBy: "default"
|
|
292
316
|
}
|
|
293
317
|
};
|
|
294
318
|
CONFIG_MIGRATIONS = [
|
|
@@ -1811,6 +1835,9 @@ function getClient() {
|
|
|
1811
1835
|
if (_daemonClient && _daemonClient._isDaemonActive()) {
|
|
1812
1836
|
return _daemonClient;
|
|
1813
1837
|
}
|
|
1838
|
+
if (!_resilientClient) {
|
|
1839
|
+
return _adapterClient;
|
|
1840
|
+
}
|
|
1814
1841
|
return _resilientClient;
|
|
1815
1842
|
}
|
|
1816
1843
|
async function initDaemonClient() {
|
|
@@ -2843,6 +2870,127 @@ async function ensureSchema() {
|
|
|
2843
2870
|
VALUES (new.rowid, new.content, new.subject, new.predicate, new.object);
|
|
2844
2871
|
END;
|
|
2845
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
|
+
`);
|
|
2846
2994
|
try {
|
|
2847
2995
|
await client.execute({
|
|
2848
2996
|
sql: `ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3`,
|
|
@@ -3028,6 +3176,12 @@ var init_platform_procedures = __esm({
|
|
|
3028
3176
|
priority: "p0",
|
|
3029
3177
|
content: "Founder -> coordinator (the executive agent, internally routed as 'COO') -> CTO/CMO. CTO -> engineers. CMO -> content production. Never skip levels: the coordinator does not bypass managers for specialist work. Specialists report to their manager. If you need cross-team info, use ask_team_memory \u2014 don't read other agents' task folders. Each level owns dispatch downward and review upward."
|
|
3030
3178
|
},
|
|
3179
|
+
{
|
|
3180
|
+
title: "Customer orchestration maturity \u2014 recommend, never trap",
|
|
3181
|
+
domain: "workflow",
|
|
3182
|
+
priority: "p1",
|
|
3183
|
+
content: "New customers start best in Phase 1: founder \u2194 coordinator/Chief of Staff, building company context. Suggest Phase 2 executives when domain work repeats; suggest Phase 3 parallel execution only when review/permission gates are ready. This is guidance, not a blocker: users may jump phases anytime. Never overwrite their phase, role titles, identities, or custom org design."
|
|
3184
|
+
},
|
|
3031
3185
|
{
|
|
3032
3186
|
title: "Single dispatch path \u2014 create_task only",
|
|
3033
3187
|
domain: "workflow",
|
|
@@ -3086,6 +3240,12 @@ var init_platform_procedures = __esm({
|
|
|
3086
3240
|
priority: "p0",
|
|
3087
3241
|
content: "exe-build-adv is MANDATORY for ALL work touching 3+ files. Run /exe-build-adv --auto BEFORE implementation. Pipeline: Spec \u2192 AC \u2192 Tests \u2192 Evaluate \u2192 Fix. No multi-file feature ships without pipeline artifacts. No exceptions \u2014 managers reject work without them."
|
|
3088
3242
|
},
|
|
3243
|
+
{
|
|
3244
|
+
title: "Commit discipline \u2014 never leave verified work floating",
|
|
3245
|
+
domain: "workflow",
|
|
3246
|
+
priority: "p1",
|
|
3247
|
+
content: "After any code-change batch passes typecheck/tests/build, run git status, summarize changed files, and commit with a clear message before ending the session. If work must remain uncommitted for review/dogfood, explicitly say so, list the files, and state the blocker. Never imply work is complete while verified changes are still floating locally."
|
|
3248
|
+
},
|
|
3089
3249
|
{
|
|
3090
3250
|
title: "Desktop and TUI are the same product",
|
|
3091
3251
|
domain: "architecture",
|
|
@@ -3250,12 +3410,13 @@ var keychain_exports = {};
|
|
|
3250
3410
|
__export(keychain_exports, {
|
|
3251
3411
|
deleteMasterKey: () => deleteMasterKey,
|
|
3252
3412
|
exportMnemonic: () => exportMnemonic,
|
|
3413
|
+
getKeyStorageInfo: () => getKeyStorageInfo,
|
|
3253
3414
|
getMasterKey: () => getMasterKey,
|
|
3254
3415
|
importMnemonic: () => importMnemonic,
|
|
3255
3416
|
setMasterKey: () => setMasterKey
|
|
3256
3417
|
});
|
|
3257
3418
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
3258
|
-
import { existsSync as existsSync6 } from "fs";
|
|
3419
|
+
import { existsSync as existsSync6, statSync as statSync2 } from "fs";
|
|
3259
3420
|
import { execSync as execSync2 } from "child_process";
|
|
3260
3421
|
import path6 from "path";
|
|
3261
3422
|
import os5 from "os";
|
|
@@ -3265,29 +3426,65 @@ function getKeyDir() {
|
|
|
3265
3426
|
function getKeyPath() {
|
|
3266
3427
|
return path6.join(getKeyDir(), "master.key");
|
|
3267
3428
|
}
|
|
3268
|
-
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;
|
|
3269
3465
|
if (process.platform !== "darwin") return null;
|
|
3270
3466
|
try {
|
|
3271
3467
|
return execSync2(
|
|
3272
|
-
`security find-generic-password -s "${
|
|
3468
|
+
`security find-generic-password -s "${service}" -a "${ACCOUNT}" -w 2>/dev/null`,
|
|
3273
3469
|
{ encoding: "utf-8", timeout: 5e3 }
|
|
3274
3470
|
).trim();
|
|
3275
3471
|
} catch {
|
|
3276
3472
|
return null;
|
|
3277
3473
|
}
|
|
3278
3474
|
}
|
|
3279
|
-
function macKeychainSet(value) {
|
|
3475
|
+
function macKeychainSet(value, service = SERVICE) {
|
|
3476
|
+
if (!nativeKeychainAllowed()) return false;
|
|
3280
3477
|
if (process.platform !== "darwin") return false;
|
|
3281
3478
|
try {
|
|
3282
3479
|
try {
|
|
3283
3480
|
execSync2(
|
|
3284
|
-
`security delete-generic-password -s "${
|
|
3481
|
+
`security delete-generic-password -s "${service}" -a "${ACCOUNT}" 2>/dev/null`,
|
|
3285
3482
|
{ timeout: 5e3 }
|
|
3286
3483
|
);
|
|
3287
3484
|
} catch {
|
|
3288
3485
|
}
|
|
3289
3486
|
execSync2(
|
|
3290
|
-
`security add-generic-password -s "${
|
|
3487
|
+
`security add-generic-password -s "${service}" -a "${ACCOUNT}" -w "${value}"`,
|
|
3291
3488
|
{ timeout: 5e3 }
|
|
3292
3489
|
);
|
|
3293
3490
|
return true;
|
|
@@ -3295,11 +3492,12 @@ function macKeychainSet(value) {
|
|
|
3295
3492
|
return false;
|
|
3296
3493
|
}
|
|
3297
3494
|
}
|
|
3298
|
-
function macKeychainDelete() {
|
|
3495
|
+
function macKeychainDelete(service = SERVICE) {
|
|
3496
|
+
if (!nativeKeychainAllowed()) return false;
|
|
3299
3497
|
if (process.platform !== "darwin") return false;
|
|
3300
3498
|
try {
|
|
3301
3499
|
execSync2(
|
|
3302
|
-
`security delete-generic-password -s "${
|
|
3500
|
+
`security delete-generic-password -s "${service}" -a "${ACCOUNT}" 2>/dev/null`,
|
|
3303
3501
|
{ timeout: 5e3 }
|
|
3304
3502
|
);
|
|
3305
3503
|
return true;
|
|
@@ -3307,22 +3505,22 @@ function macKeychainDelete() {
|
|
|
3307
3505
|
return false;
|
|
3308
3506
|
}
|
|
3309
3507
|
}
|
|
3310
|
-
function linuxSecretGet() {
|
|
3311
|
-
if (
|
|
3508
|
+
function linuxSecretGet(service = SERVICE) {
|
|
3509
|
+
if (!linuxSecretAvailable()) return null;
|
|
3312
3510
|
try {
|
|
3313
3511
|
return execSync2(
|
|
3314
|
-
`secret-tool lookup service "${
|
|
3512
|
+
`secret-tool lookup service "${service}" account "${ACCOUNT}" 2>/dev/null`,
|
|
3315
3513
|
{ encoding: "utf-8", timeout: 5e3 }
|
|
3316
3514
|
).trim();
|
|
3317
3515
|
} catch {
|
|
3318
3516
|
return null;
|
|
3319
3517
|
}
|
|
3320
3518
|
}
|
|
3321
|
-
function linuxSecretSet(value) {
|
|
3322
|
-
if (
|
|
3519
|
+
function linuxSecretSet(value, service = SERVICE) {
|
|
3520
|
+
if (!linuxSecretAvailable()) return false;
|
|
3323
3521
|
try {
|
|
3324
3522
|
execSync2(
|
|
3325
|
-
`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`,
|
|
3326
3524
|
{ timeout: 5e3 }
|
|
3327
3525
|
);
|
|
3328
3526
|
return true;
|
|
@@ -3330,11 +3528,12 @@ function linuxSecretSet(value) {
|
|
|
3330
3528
|
return false;
|
|
3331
3529
|
}
|
|
3332
3530
|
}
|
|
3333
|
-
function linuxSecretDelete() {
|
|
3531
|
+
function linuxSecretDelete(service = SERVICE) {
|
|
3532
|
+
if (!nativeKeychainAllowed()) return false;
|
|
3334
3533
|
if (process.platform !== "linux") return false;
|
|
3335
3534
|
try {
|
|
3336
3535
|
execSync2(
|
|
3337
|
-
`secret-tool clear service "${
|
|
3536
|
+
`secret-tool clear service "${service}" account "${ACCOUNT}" 2>/dev/null`,
|
|
3338
3537
|
{ timeout: 5e3 }
|
|
3339
3538
|
);
|
|
3340
3539
|
return true;
|
|
@@ -3343,6 +3542,7 @@ function linuxSecretDelete() {
|
|
|
3343
3542
|
}
|
|
3344
3543
|
}
|
|
3345
3544
|
async function tryKeytar() {
|
|
3545
|
+
if (!nativeKeychainAllowed()) return null;
|
|
3346
3546
|
try {
|
|
3347
3547
|
return await import("keytar");
|
|
3348
3548
|
} catch {
|
|
@@ -3416,7 +3616,19 @@ async function writeMachineBoundFileFallback(b64) {
|
|
|
3416
3616
|
return "plaintext";
|
|
3417
3617
|
}
|
|
3418
3618
|
async function getMasterKey() {
|
|
3419
|
-
|
|
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
|
+
}
|
|
3420
3632
|
if (nativeValue) {
|
|
3421
3633
|
return Buffer.from(nativeValue, "base64");
|
|
3422
3634
|
}
|
|
@@ -3424,12 +3636,17 @@ async function getMasterKey() {
|
|
|
3424
3636
|
if (keytar) {
|
|
3425
3637
|
try {
|
|
3426
3638
|
const keytarValue = await keytar.getPassword(SERVICE, ACCOUNT);
|
|
3427
|
-
|
|
3428
|
-
|
|
3639
|
+
const legacyKeytarValue = keytarValue ?? await keytar.getPassword(LEGACY_SERVICE, ACCOUNT);
|
|
3640
|
+
if (legacyKeytarValue) {
|
|
3641
|
+
const migrated = macKeychainSet(legacyKeytarValue) || linuxSecretSet(legacyKeytarValue);
|
|
3429
3642
|
if (migrated) {
|
|
3430
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
|
+
}
|
|
3431
3648
|
}
|
|
3432
|
-
return Buffer.from(
|
|
3649
|
+
return Buffer.from(legacyKeytarValue, "base64");
|
|
3433
3650
|
}
|
|
3434
3651
|
} catch {
|
|
3435
3652
|
}
|
|
@@ -3454,7 +3671,7 @@ async function getMasterKey() {
|
|
|
3454
3671
|
const decrypted = decryptWithMachineKey(content, machineKey);
|
|
3455
3672
|
if (!decrypted) {
|
|
3456
3673
|
process.stderr.write(
|
|
3457
|
-
"[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"
|
|
3458
3675
|
);
|
|
3459
3676
|
return null;
|
|
3460
3677
|
}
|
|
@@ -3463,6 +3680,9 @@ async function getMasterKey() {
|
|
|
3463
3680
|
b64Value = content;
|
|
3464
3681
|
}
|
|
3465
3682
|
const key = Buffer.from(b64Value, "base64");
|
|
3683
|
+
if (!content.startsWith(ENCRYPTED_PREFIX) && isRootOnlyTrustedServerKeyFile(keyPath)) {
|
|
3684
|
+
return key;
|
|
3685
|
+
}
|
|
3466
3686
|
const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
|
|
3467
3687
|
if (migrated) {
|
|
3468
3688
|
process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
|
|
@@ -3490,6 +3710,97 @@ async function getMasterKey() {
|
|
|
3490
3710
|
return null;
|
|
3491
3711
|
}
|
|
3492
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
|
+
}
|
|
3493
3804
|
async function setMasterKey(key) {
|
|
3494
3805
|
const b64 = key.toString("base64");
|
|
3495
3806
|
if (macKeychainSet(b64) || linuxSecretSet(b64)) {
|
|
@@ -3515,10 +3826,13 @@ async function setMasterKey(key) {
|
|
|
3515
3826
|
async function deleteMasterKey() {
|
|
3516
3827
|
macKeychainDelete();
|
|
3517
3828
|
linuxSecretDelete();
|
|
3829
|
+
macKeychainDelete(LEGACY_SERVICE);
|
|
3830
|
+
linuxSecretDelete(LEGACY_SERVICE);
|
|
3518
3831
|
const keytar = await tryKeytar();
|
|
3519
3832
|
if (keytar) {
|
|
3520
3833
|
try {
|
|
3521
3834
|
await keytar.deletePassword(SERVICE, ACCOUNT);
|
|
3835
|
+
await keytar.deletePassword(LEGACY_SERVICE, ACCOUNT);
|
|
3522
3836
|
} catch {
|
|
3523
3837
|
}
|
|
3524
3838
|
}
|
|
@@ -3556,12 +3870,14 @@ async function importMnemonic(mnemonic) {
|
|
|
3556
3870
|
const entropy = mnemonicToEntropy(trimmed);
|
|
3557
3871
|
return Buffer.from(entropy, "hex");
|
|
3558
3872
|
}
|
|
3559
|
-
var SERVICE, ACCOUNT, ENCRYPTED_PREFIX;
|
|
3873
|
+
var SERVICE, LEGACY_SERVICE, ACCOUNT, linuxSecretAvailability, ENCRYPTED_PREFIX;
|
|
3560
3874
|
var init_keychain = __esm({
|
|
3561
3875
|
"src/lib/keychain.ts"() {
|
|
3562
3876
|
"use strict";
|
|
3563
|
-
SERVICE = "exe-
|
|
3877
|
+
SERVICE = "exe-os";
|
|
3878
|
+
LEGACY_SERVICE = "exe-mem";
|
|
3564
3879
|
ACCOUNT = "master-key";
|
|
3880
|
+
linuxSecretAvailability = null;
|
|
3565
3881
|
ENCRYPTED_PREFIX = "enc:";
|
|
3566
3882
|
}
|
|
3567
3883
|
});
|
|
@@ -3645,7 +3961,7 @@ __export(shard_manager_exports, {
|
|
|
3645
3961
|
shardExists: () => shardExists
|
|
3646
3962
|
});
|
|
3647
3963
|
import path7 from "path";
|
|
3648
|
-
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";
|
|
3649
3965
|
import { createClient as createClient2 } from "@libsql/client";
|
|
3650
3966
|
function initShardManager(encryptionKey) {
|
|
3651
3967
|
_encryptionKey = encryptionKey;
|
|
@@ -3709,7 +4025,7 @@ async function auditShardHealth(options = {}) {
|
|
|
3709
4025
|
const shards = [];
|
|
3710
4026
|
for (const name of names) {
|
|
3711
4027
|
const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
|
|
3712
|
-
const stat =
|
|
4028
|
+
const stat = statSync3(dbPath);
|
|
3713
4029
|
const item = {
|
|
3714
4030
|
name,
|
|
3715
4031
|
path: dbPath,
|
|
@@ -3962,7 +4278,7 @@ async function getReadyShardClient(projectName) {
|
|
|
3962
4278
|
_shardLastAccess.delete(safeName);
|
|
3963
4279
|
const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
|
|
3964
4280
|
if (existsSync7(dbPath)) {
|
|
3965
|
-
const stat =
|
|
4281
|
+
const stat = statSync3(dbPath);
|
|
3966
4282
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
3967
4283
|
const archivedPath = path7.join(SHARDS_DIR, `${safeName}.db.broken-${stamp}`);
|
|
3968
4284
|
renameSync3(dbPath, archivedPath);
|
|
@@ -4919,8 +5235,8 @@ async function validateLicense(apiKey, deviceId) {
|
|
|
4919
5235
|
}
|
|
4920
5236
|
function getCacheAgeMs() {
|
|
4921
5237
|
try {
|
|
4922
|
-
const { statSync:
|
|
4923
|
-
const s =
|
|
5238
|
+
const { statSync: statSync6 } = __require("fs");
|
|
5239
|
+
const s = statSync6(CACHE_PATH);
|
|
4924
5240
|
return Date.now() - s.mtimeMs;
|
|
4925
5241
|
} catch {
|
|
4926
5242
|
return Infinity;
|
|
@@ -8925,7 +9241,7 @@ __export(db_backup_exports, {
|
|
|
8925
9241
|
listBackups: () => listBackups,
|
|
8926
9242
|
rotateBackups: () => rotateBackups
|
|
8927
9243
|
});
|
|
8928
|
-
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";
|
|
8929
9245
|
import path24 from "path";
|
|
8930
9246
|
function findActiveDb() {
|
|
8931
9247
|
for (const name of DB_NAMES) {
|
|
@@ -8969,7 +9285,7 @@ function rotateBackups(keepDays = DEFAULT_KEEP_DAYS) {
|
|
|
8969
9285
|
if (!file.endsWith(".db") && !file.endsWith(".db-wal") && !file.endsWith(".db-shm")) continue;
|
|
8970
9286
|
const filePath = path24.join(BACKUP_DIR, file);
|
|
8971
9287
|
try {
|
|
8972
|
-
const stat =
|
|
9288
|
+
const stat = statSync4(filePath);
|
|
8973
9289
|
if (stat.mtimeMs < cutoff) {
|
|
8974
9290
|
unlinkSync10(filePath);
|
|
8975
9291
|
deleted++;
|
|
@@ -8987,7 +9303,7 @@ function listBackups() {
|
|
|
8987
9303
|
const files = readdirSync7(BACKUP_DIR).filter((f) => f.endsWith(".db") && !f.endsWith("-wal") && !f.endsWith("-shm"));
|
|
8988
9304
|
return files.map((name) => {
|
|
8989
9305
|
const p = path24.join(BACKUP_DIR, name);
|
|
8990
|
-
const stat =
|
|
9306
|
+
const stat = statSync4(p);
|
|
8991
9307
|
return { path: p, name, size: stat.size, date: stat.mtime };
|
|
8992
9308
|
}).sort((a, b) => b.date.getTime() - a.date.getTime());
|
|
8993
9309
|
} catch {
|
|
@@ -9020,8 +9336,10 @@ var init_db_backup = __esm({
|
|
|
9020
9336
|
// src/lib/cloud-sync.ts
|
|
9021
9337
|
var cloud_sync_exports = {};
|
|
9022
9338
|
__export(cloud_sync_exports, {
|
|
9339
|
+
CLOUD_REUPLOAD_REQUIRED_MESSAGE: () => CLOUD_REUPLOAD_REQUIRED_MESSAGE,
|
|
9023
9340
|
assertSecureEndpoint: () => assertSecureEndpoint,
|
|
9024
9341
|
buildRosterBlob: () => buildRosterBlob,
|
|
9342
|
+
clearCloudReuploadRequired: () => clearCloudReuploadRequired,
|
|
9025
9343
|
cloudPull: () => cloudPull,
|
|
9026
9344
|
cloudPullBehaviors: () => cloudPullBehaviors,
|
|
9027
9345
|
cloudPullBlob: () => cloudPullBlob,
|
|
@@ -9040,13 +9358,16 @@ __export(cloud_sync_exports, {
|
|
|
9040
9358
|
cloudPushGraphRAG: () => cloudPushGraphRAG,
|
|
9041
9359
|
cloudPushRoster: () => cloudPushRoster,
|
|
9042
9360
|
cloudPushTasks: () => cloudPushTasks,
|
|
9361
|
+
cloudResetMemoryBlobs: () => cloudResetMemoryBlobs,
|
|
9043
9362
|
cloudSync: () => cloudSync,
|
|
9363
|
+
getCloudReuploadRequired: () => getCloudReuploadRequired,
|
|
9364
|
+
markCloudReuploadRequired: () => markCloudReuploadRequired,
|
|
9044
9365
|
mergeConfig: () => mergeConfig,
|
|
9045
9366
|
mergeRosterFromRemote: () => mergeRosterFromRemote,
|
|
9046
9367
|
pushToPostgres: () => pushToPostgres,
|
|
9047
9368
|
recordRosterDeletion: () => recordRosterDeletion
|
|
9048
9369
|
});
|
|
9049
|
-
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";
|
|
9050
9371
|
import crypto8 from "crypto";
|
|
9051
9372
|
import path25 from "path";
|
|
9052
9373
|
import { homedir as homedir2 } from "os";
|
|
@@ -9093,18 +9414,36 @@ function loadPgClient() {
|
|
|
9093
9414
|
const { pathToFileURL: pathToFileURL3 } = await import("url");
|
|
9094
9415
|
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
9095
9416
|
if (explicitPath) {
|
|
9096
|
-
const
|
|
9097
|
-
const
|
|
9098
|
-
if (!
|
|
9099
|
-
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();
|
|
9100
9421
|
}
|
|
9101
9422
|
const exeDbRoot = process.env.EXE_DB_ROOT ?? path25.join(homedir2(), "exe-db");
|
|
9102
|
-
const
|
|
9103
|
-
|
|
9104
|
-
|
|
9105
|
-
|
|
9106
|
-
|
|
9107
|
-
|
|
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
|
+
};
|
|
9108
9447
|
})().catch(() => {
|
|
9109
9448
|
_pgFailed = true;
|
|
9110
9449
|
_pgPromise = null;
|
|
@@ -9125,7 +9464,7 @@ async function pushToPostgres(records) {
|
|
|
9125
9464
|
let inserted = 0;
|
|
9126
9465
|
for (const rec of records) {
|
|
9127
9466
|
try {
|
|
9128
|
-
await prisma.$executeRawUnsafe(
|
|
9467
|
+
const changed = await prisma.$executeRawUnsafe(
|
|
9129
9468
|
`INSERT INTO raw.raw_events (id, source, source_id, event_type, payload, metadata, timestamp)
|
|
9130
9469
|
VALUES (gen_random_uuid(), 'cloud_sync', $1, 'memory', $2::jsonb, $3::jsonb, $4)
|
|
9131
9470
|
ON CONFLICT (source, source_id, event_type) DO NOTHING`,
|
|
@@ -9134,7 +9473,7 @@ async function pushToPostgres(records) {
|
|
|
9134
9473
|
JSON.stringify({ agent_id: rec.agent_id, project_name: rec.project_name, tool_name: rec.tool_name }),
|
|
9135
9474
|
rec.timestamp ? new Date(String(rec.timestamp)) : /* @__PURE__ */ new Date()
|
|
9136
9475
|
);
|
|
9137
|
-
inserted
|
|
9476
|
+
inserted += Number(changed ?? 0);
|
|
9138
9477
|
} catch {
|
|
9139
9478
|
}
|
|
9140
9479
|
}
|
|
@@ -9239,6 +9578,23 @@ async function cloudPush(records, maxVersion, config) {
|
|
|
9239
9578
|
return false;
|
|
9240
9579
|
}
|
|
9241
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
|
+
}
|
|
9242
9598
|
async function cloudPull(sinceVersion, config) {
|
|
9243
9599
|
assertSecureEndpoint(config.endpoint);
|
|
9244
9600
|
try {
|
|
@@ -9258,22 +9614,61 @@ async function cloudPull(sinceVersion, config) {
|
|
|
9258
9614
|
if (!response.ok) return { records: [], maxVersion: sinceVersion };
|
|
9259
9615
|
const data = await response.json();
|
|
9260
9616
|
const allRecords = [];
|
|
9261
|
-
|
|
9617
|
+
let maxReadableVersion = sinceVersion;
|
|
9618
|
+
let skippedBlobs = 0;
|
|
9619
|
+
for (const { version, blob } of data.blobs ?? []) {
|
|
9262
9620
|
try {
|
|
9263
9621
|
const compressed = decryptSyncBlob(blob);
|
|
9264
9622
|
const json = decompress(compressed).toString("utf8");
|
|
9265
9623
|
const records = JSON.parse(json);
|
|
9266
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
|
+
);
|
|
9267
9635
|
} catch {
|
|
9636
|
+
skippedBlobs++;
|
|
9268
9637
|
continue;
|
|
9269
9638
|
}
|
|
9270
9639
|
}
|
|
9271
|
-
|
|
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 };
|
|
9272
9644
|
} catch (err) {
|
|
9273
9645
|
logError(`[cloud-sync] PULL FAILED: ${err instanceof Error ? err.message : String(err)}`);
|
|
9274
9646
|
return { records: [], maxVersion: sinceVersion };
|
|
9275
9647
|
}
|
|
9276
9648
|
}
|
|
9649
|
+
async function getCloudReuploadRequired(client = getClient()) {
|
|
9650
|
+
try {
|
|
9651
|
+
await client.execute("CREATE TABLE IF NOT EXISTS sync_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)");
|
|
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");
|
|
9654
|
+
} catch {
|
|
9655
|
+
return false;
|
|
9656
|
+
}
|
|
9657
|
+
}
|
|
9658
|
+
async function clearCloudReuploadRequired(client = getClient()) {
|
|
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')");
|
|
9661
|
+
await client.execute("INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('cloud_relink_required', '0')");
|
|
9662
|
+
await client.execute({
|
|
9663
|
+
sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('cloud_reuploaded_at', ?)",
|
|
9664
|
+
args: [(/* @__PURE__ */ new Date()).toISOString()]
|
|
9665
|
+
});
|
|
9666
|
+
await client.execute("DELETE FROM sync_meta WHERE key IN ('last_cloud_pull_version', 'last_cloud_push_version')");
|
|
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
|
+
}
|
|
9277
9672
|
async function cloudSync(config) {
|
|
9278
9673
|
if (!isSyncCryptoInitialized()) {
|
|
9279
9674
|
try {
|
|
@@ -9295,13 +9690,10 @@ async function cloudSync(config) {
|
|
|
9295
9690
|
throw new Error("[cloud-sync] Database not initialized. Call initStore() before cloudSync().");
|
|
9296
9691
|
}
|
|
9297
9692
|
try {
|
|
9298
|
-
|
|
9299
|
-
if (String(relink.rows[0]?.value ?? "") === "1") {
|
|
9300
|
-
throw new Error("[cloud-sync] Paused after key rotation. Re-link/reupload cloud sync with the new recovery phrase before syncing.");
|
|
9301
|
-
}
|
|
9693
|
+
if (await getCloudReuploadRequired(client)) throw new Error(CLOUD_REUPLOAD_REQUIRED_MESSAGE);
|
|
9302
9694
|
} catch (err) {
|
|
9303
9695
|
const msg = err instanceof Error ? err.message : String(err);
|
|
9304
|
-
if (msg.includes("
|
|
9696
|
+
if (msg === CLOUD_REUPLOAD_REQUIRED_MESSAGE || msg.includes("key rotation")) throw err;
|
|
9305
9697
|
}
|
|
9306
9698
|
try {
|
|
9307
9699
|
const { getRawClient: getRawClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
@@ -9559,7 +9951,7 @@ async function cloudSync(config) {
|
|
|
9559
9951
|
const { getLatestBackup: getLatestBackup2 } = await Promise.resolve().then(() => (init_db_backup(), db_backup_exports));
|
|
9560
9952
|
const latestBackup = getLatestBackup2();
|
|
9561
9953
|
if (latestBackup) {
|
|
9562
|
-
const backupSize =
|
|
9954
|
+
const backupSize = statSync5(latestBackup).size;
|
|
9563
9955
|
const MAX_CLOUD_BACKUP_BYTES = 50 * 1024 * 1024;
|
|
9564
9956
|
if (backupSize <= MAX_CLOUD_BACKUP_BYTES) {
|
|
9565
9957
|
const backupData = readFileSync15(latestBackup);
|
|
@@ -10230,7 +10622,7 @@ async function cloudPullDocuments(config) {
|
|
|
10230
10622
|
}
|
|
10231
10623
|
return { pulled };
|
|
10232
10624
|
}
|
|
10233
|
-
var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, _pgPromise, _pgFailed, ROSTER_DELETIONS_PATH;
|
|
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;
|
|
10234
10626
|
var init_cloud_sync = __esm({
|
|
10235
10627
|
"src/lib/cloud-sync.ts"() {
|
|
10236
10628
|
"use strict";
|
|
@@ -10249,6 +10641,7 @@ var init_cloud_sync = __esm({
|
|
|
10249
10641
|
LOCK_STALE_MS = 3e4;
|
|
10250
10642
|
_pgPromise = null;
|
|
10251
10643
|
_pgFailed = false;
|
|
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.";
|
|
10252
10645
|
ROSTER_DELETIONS_PATH = path25.join(EXE_AI_DIR, "roster-deletions.json");
|
|
10253
10646
|
}
|
|
10254
10647
|
});
|
|
@@ -10676,6 +11069,8 @@ async function generateStatusBrief(employees, data, _activeAgentIds) {
|
|
|
10676
11069
|
}
|
|
10677
11070
|
const sections = [];
|
|
10678
11071
|
sections.push([` EXE STATUS BRIEF \u2014 ${dateStr}${sessionTag}`]);
|
|
11072
|
+
const orchestrationLines = buildOrchestrationPhase(data, employees);
|
|
11073
|
+
if (orchestrationLines.length > 0) sections.push(orchestrationLines);
|
|
10679
11074
|
const reminderLines = buildReminders(data);
|
|
10680
11075
|
if (reminderLines.length > 0) sections.push(reminderLines);
|
|
10681
11076
|
const actionLines = buildActionRequired(data);
|
|
@@ -10718,6 +11113,11 @@ function buildFirstBootBrief(employees, dateStr, sessionTag) {
|
|
|
10718
11113
|
bodyLines.push(` ${emoji} ${emp.name}${role}`);
|
|
10719
11114
|
}
|
|
10720
11115
|
bodyLines.push("");
|
|
11116
|
+
bodyLines.push(" \u{1F9ED} Orchestration:");
|
|
11117
|
+
bodyLines.push(" \u2022 Phase 1 \u2014 COO / Chief of Staff mode");
|
|
11118
|
+
bodyLines.push(" \u2022 Recommended start: build company context first");
|
|
11119
|
+
bodyLines.push(" \u2022 You can unlock executives or parallel mode anytime");
|
|
11120
|
+
bodyLines.push("");
|
|
10721
11121
|
bodyLines.push(" \u{1F4A1} Quick start:");
|
|
10722
11122
|
bodyLines.push(" \u2022 Run `exe-os backfill-conversations` to import Claude Code history");
|
|
10723
11123
|
bodyLines.push(" \u2022 Say `/exe` to launch your COO with a full status brief");
|
|
@@ -10748,6 +11148,38 @@ function buildReminders(data) {
|
|
|
10748
11148
|
}
|
|
10749
11149
|
return lines;
|
|
10750
11150
|
}
|
|
11151
|
+
function buildOrchestrationPhase(data, employees) {
|
|
11152
|
+
if (!data.orchestrationPhase) return [];
|
|
11153
|
+
const phase = data.orchestrationPhase;
|
|
11154
|
+
const hasExecutiveBench = employees.some((e) => ["cto", "cmo"].includes(e.role.toLowerCase()));
|
|
11155
|
+
const openWorkCount = data.globalTasks.filter((t) => t.status === "open" || t.status === "in_progress").length;
|
|
11156
|
+
const domainKeywordHits = data.globalTasks.filter(
|
|
11157
|
+
(t) => /\b(api|bug|code|repo|build|deploy|design|brand|copy|content|marketing|legal|finance|sales|crm)\b/i.test(t.title)
|
|
11158
|
+
).length;
|
|
11159
|
+
const phase1Signal = !hasExecutiveBench && (openWorkCount >= 3 || domainKeywordHits >= 2);
|
|
11160
|
+
if (phase === "phase_2_executives") {
|
|
11161
|
+
return [
|
|
11162
|
+
"\u{1F9ED} ORCHESTRATION",
|
|
11163
|
+
" Phase 2 \u2014 Executive bench",
|
|
11164
|
+
" Focus: COO works with CTO/CMO/domain executives before specialist fan-out",
|
|
11165
|
+
" You can switch phases anytime: exe-os org phase"
|
|
11166
|
+
];
|
|
11167
|
+
}
|
|
11168
|
+
if (phase === "phase_3_parallel_org") {
|
|
11169
|
+
return [
|
|
11170
|
+
"\u{1F9ED} ORCHESTRATION",
|
|
11171
|
+
" Phase 3 \u2014 Parallel execution org",
|
|
11172
|
+
" Focus: executives can delegate to specialists in parallel with review gates",
|
|
11173
|
+
" You can switch phases anytime: exe-os org phase"
|
|
11174
|
+
];
|
|
11175
|
+
}
|
|
11176
|
+
return [
|
|
11177
|
+
"\u{1F9ED} ORCHESTRATION",
|
|
11178
|
+
" Phase 1 \u2014 COO / Chief of Staff mode",
|
|
11179
|
+
" Focus: building company context before delegation",
|
|
11180
|
+
phase1Signal ? " Signal: repeated domain work detected. Consider: exe-os org unlock executives" : " Ready later? exe-os org unlock executives"
|
|
11181
|
+
];
|
|
11182
|
+
}
|
|
10751
11183
|
function buildActionRequired(data) {
|
|
10752
11184
|
const lines = [];
|
|
10753
11185
|
let hasIssues = false;
|
|
@@ -11059,6 +11491,12 @@ async function boot(options) {
|
|
|
11059
11491
|
plan: licensePlan,
|
|
11060
11492
|
employeeLimit
|
|
11061
11493
|
};
|
|
11494
|
+
try {
|
|
11495
|
+
const config = await loadConfig();
|
|
11496
|
+
briefData.orchestrationPhase = config.orchestration?.phase;
|
|
11497
|
+
} catch {
|
|
11498
|
+
briefData.orchestrationPhase = "phase_1_coo";
|
|
11499
|
+
}
|
|
11062
11500
|
if (!briefOnly) {
|
|
11063
11501
|
await migrateJsonNotifications();
|
|
11064
11502
|
await markDoneTaskNotificationsAsRead();
|