@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.
Files changed (113) hide show
  1. package/deploy/stack-manifests/v0.9.json +54 -5
  2. package/dist/bin/age-ontology-load.js +61 -0
  3. package/dist/bin/agentic-ontology-backfill.js +4708 -0
  4. package/dist/bin/agentic-reflection-backfill.js +4144 -0
  5. package/dist/bin/{exe-link.js → agentic-semantic-label.js} +1532 -2173
  6. package/dist/bin/backfill-conversations.js +528 -20
  7. package/dist/bin/backfill-responses.js +528 -20
  8. package/dist/bin/backfill-vectors.js +255 -20
  9. package/dist/bin/bulk-sync-postgres.js +4876 -0
  10. package/dist/bin/cleanup-stale-review-tasks.js +529 -21
  11. package/dist/bin/cli.js +3471 -1491
  12. package/dist/bin/exe-agent-config.js +4 -0
  13. package/dist/bin/exe-agent.js +16 -0
  14. package/dist/bin/exe-assign.js +528 -20
  15. package/dist/bin/exe-boot.js +492 -54
  16. package/dist/bin/exe-call.js +16 -0
  17. package/dist/bin/exe-cloud.js +7415 -518
  18. package/dist/bin/exe-dispatch.js +540 -22
  19. package/dist/bin/exe-doctor.js +3404 -1225
  20. package/dist/bin/exe-export-behaviors.js +542 -24
  21. package/dist/bin/exe-forget.js +529 -21
  22. package/dist/bin/exe-gateway.js +595 -25
  23. package/dist/bin/exe-heartbeat.js +541 -24
  24. package/dist/bin/exe-kill.js +529 -21
  25. package/dist/bin/exe-launch-agent.js +2334 -1067
  26. package/dist/bin/exe-new-employee.js +324 -166
  27. package/dist/bin/exe-pending-messages.js +529 -21
  28. package/dist/bin/exe-pending-notifications.js +529 -21
  29. package/dist/bin/exe-pending-reviews.js +529 -21
  30. package/dist/bin/exe-rename.js +529 -21
  31. package/dist/bin/exe-review.js +529 -21
  32. package/dist/bin/exe-search.js +542 -24
  33. package/dist/bin/exe-session-cleanup.js +540 -22
  34. package/dist/bin/exe-settings.js +14 -0
  35. package/dist/bin/exe-start-codex.js +817 -144
  36. package/dist/bin/exe-start-opencode.js +776 -80
  37. package/dist/bin/exe-status.js +529 -21
  38. package/dist/bin/exe-team.js +529 -21
  39. package/dist/bin/git-sweep.js +540 -22
  40. package/dist/bin/graph-backfill.js +580 -21
  41. package/dist/bin/graph-export.js +529 -21
  42. package/dist/bin/graph-layer-benchmark.js +109 -0
  43. package/dist/bin/install.js +420 -289
  44. package/dist/bin/intercom-check.js +540 -22
  45. package/dist/bin/postgres-agentic-reflection-backfill.js +187 -0
  46. package/dist/bin/postgres-agentic-semantic-backfill.js +237 -0
  47. package/dist/bin/scan-tasks.js +540 -22
  48. package/dist/bin/setup.js +790 -206
  49. package/dist/bin/shard-migrate.js +528 -20
  50. package/dist/bin/update.js +4 -0
  51. package/dist/gateway/index.js +593 -23
  52. package/dist/hooks/bug-report-worker.js +651 -64
  53. package/dist/hooks/codex-stop-task-finalizer.js +540 -22
  54. package/dist/hooks/commit-complete.js +540 -22
  55. package/dist/hooks/error-recall.js +542 -24
  56. package/dist/hooks/exe-heartbeat-hook.js +4 -0
  57. package/dist/hooks/ingest-worker.js +4 -0
  58. package/dist/hooks/ingest.js +539 -22
  59. package/dist/hooks/instructions-loaded.js +529 -21
  60. package/dist/hooks/notification.js +529 -21
  61. package/dist/hooks/post-compact.js +529 -21
  62. package/dist/hooks/post-tool-combined.js +543 -25
  63. package/dist/hooks/pre-compact.js +772 -127
  64. package/dist/hooks/pre-tool-use.js +529 -21
  65. package/dist/hooks/prompt-submit.js +543 -25
  66. package/dist/hooks/session-end.js +673 -140
  67. package/dist/hooks/session-start.js +662 -26
  68. package/dist/hooks/stop.js +540 -23
  69. package/dist/hooks/subagent-stop.js +529 -21
  70. package/dist/hooks/summary-worker.js +571 -126
  71. package/dist/index.js +593 -23
  72. package/dist/lib/agent-config.js +4 -0
  73. package/dist/lib/cloud-sync.js +408 -47
  74. package/dist/lib/config.js +25 -1
  75. package/dist/lib/consolidation.js +5 -1
  76. package/dist/lib/database.js +128 -0
  77. package/dist/lib/db-daemon-client.js +4 -0
  78. package/dist/lib/db.js +128 -0
  79. package/dist/lib/device-registry.js +128 -0
  80. package/dist/lib/embedder.js +25 -1
  81. package/dist/lib/employee-templates.js +16 -0
  82. package/dist/lib/employees.js +4 -0
  83. package/dist/lib/exe-daemon-client.js +4 -0
  84. package/dist/lib/exe-daemon.js +3158 -930
  85. package/dist/lib/hybrid-search.js +542 -24
  86. package/dist/lib/identity.js +7 -0
  87. package/dist/lib/keychain.js +178 -22
  88. package/dist/lib/license.js +4 -0
  89. package/dist/lib/messaging.js +7 -0
  90. package/dist/lib/reminders.js +7 -0
  91. package/dist/lib/schedules.js +255 -20
  92. package/dist/lib/skill-learning.js +28 -1
  93. package/dist/lib/status-brief.js +39 -0
  94. package/dist/lib/store.js +528 -20
  95. package/dist/lib/task-router.js +4 -0
  96. package/dist/lib/tasks.js +28 -1
  97. package/dist/lib/tmux-routing.js +28 -1
  98. package/dist/lib/token-spend.js +7 -0
  99. package/dist/mcp/server.js +2739 -813
  100. package/dist/mcp/tools/complete-reminder.js +7 -0
  101. package/dist/mcp/tools/create-reminder.js +7 -0
  102. package/dist/mcp/tools/create-task.js +28 -1
  103. package/dist/mcp/tools/deactivate-behavior.js +7 -0
  104. package/dist/mcp/tools/list-reminders.js +7 -0
  105. package/dist/mcp/tools/list-tasks.js +7 -0
  106. package/dist/mcp/tools/send-message.js +7 -0
  107. package/dist/mcp/tools/update-task.js +28 -1
  108. package/dist/runtime/index.js +540 -22
  109. package/dist/tui/App.js +618 -29
  110. package/package.json +9 -5
  111. package/src/commands/exe/cloud.md +11 -8
  112. package/stack.release.json +3 -3
  113. package/src/commands/exe/link.md +0 -17
@@ -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
- return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
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 macKeychainGet() {
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 "${SERVICE}" -a "${ACCOUNT}" -w 2>/dev/null`,
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 "${SERVICE}" -a "${ACCOUNT}" 2>/dev/null`,
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 "${SERVICE}" -a "${ACCOUNT}" -w "${value}"`,
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 "${SERVICE}" -a "${ACCOUNT}" 2>/dev/null`,
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 (process.platform !== "linux") return null;
3508
+ function linuxSecretGet(service = SERVICE) {
3509
+ if (!linuxSecretAvailable()) return null;
3312
3510
  try {
3313
3511
  return execSync2(
3314
- `secret-tool lookup service "${SERVICE}" account "${ACCOUNT}" 2>/dev/null`,
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 (process.platform !== "linux") return false;
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 "${SERVICE}" account "${ACCOUNT}"`,
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 "${SERVICE}" account "${ACCOUNT}" 2>/dev/null`,
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
- const nativeValue = macKeychainGet() ?? linuxSecretGet();
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
- if (keytarValue) {
3428
- const migrated = macKeychainSet(keytarValue) || linuxSecretSet(keytarValue);
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(keytarValue, "base64");
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 link import\n"
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-mem";
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 statSync2 } from "fs";
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 = statSync2(dbPath);
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 = statSync2(dbPath);
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: statSync5 } = __require("fs");
4923
- const s = statSync5(CACHE_PATH);
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 statSync3 } from "fs";
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 = statSync3(filePath);
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 = statSync3(p);
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 statSync4 } from "fs";
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 mod2 = await import(pathToFileURL3(explicitPath).href);
9097
- const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
9098
- if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
9099
- return new Ctor2();
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 req = createRequire3(path25.join(exeDbRoot, "package.json"));
9103
- const entry = req.resolve("@prisma/client");
9104
- const mod = await import(pathToFileURL3(entry).href);
9105
- const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
9106
- if (!Ctor) throw new Error("No PrismaClient");
9107
- return new Ctor();
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
- for (const { blob } of data.blobs ?? []) {
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
- return { records: allRecords, maxVersion: data.max_version ?? sinceVersion };
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
- const relink = await client.execute("SELECT value FROM sync_meta WHERE key = 'cloud_relink_required' LIMIT 1");
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("Paused after key rotation")) throw err;
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 = statSync4(latestBackup).size;
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();