@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
@@ -213,6 +213,11 @@ function normalizeAutoUpdate(raw) {
213
213
  const userAU = raw.autoUpdate ?? {};
214
214
  raw.autoUpdate = { ...defaultAU, ...userAU };
215
215
  }
216
+ function normalizeOrchestration(raw) {
217
+ const defaultOrg = DEFAULT_CONFIG.orchestration;
218
+ const userOrg = raw.orchestration ?? {};
219
+ raw.orchestration = { ...defaultOrg, ...userOrg };
220
+ }
216
221
  async function loadConfig() {
217
222
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
218
223
  await ensurePrivateDir(dir);
@@ -237,10 +242,15 @@ async function loadConfig() {
237
242
  normalizeScalingRoadmap(migratedCfg);
238
243
  normalizeSessionLifecycle(migratedCfg);
239
244
  normalizeAutoUpdate(migratedCfg);
245
+ normalizeOrchestration(migratedCfg);
240
246
  const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
241
247
  if (config.dbPath.startsWith("~")) {
242
248
  config.dbPath = config.dbPath.replace(/^~/, os.homedir());
243
249
  }
250
+ const envDbPath = path.join(dir, "memories.db");
251
+ if (process.env.EXE_OS_DIR && config.dbPath !== envDbPath && !existsSync2(config.dbPath) && existsSync2(envDbPath)) {
252
+ config.dbPath = envDbPath;
253
+ }
244
254
  return config;
245
255
  } catch {
246
256
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
@@ -260,7 +270,16 @@ function loadConfigSync() {
260
270
  normalizeScalingRoadmap(migratedCfg);
261
271
  normalizeSessionLifecycle(migratedCfg);
262
272
  normalizeAutoUpdate(migratedCfg);
263
- return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
273
+ normalizeOrchestration(migratedCfg);
274
+ const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
275
+ if (config.dbPath.startsWith("~")) {
276
+ config.dbPath = config.dbPath.replace(/^~/, os.homedir());
277
+ }
278
+ const envDbPath = path.join(dir, "memories.db");
279
+ if (process.env.EXE_OS_DIR && config.dbPath !== envDbPath && !existsSync2(config.dbPath) && existsSync2(envDbPath)) {
280
+ config.dbPath = envDbPath;
281
+ }
282
+ return config;
264
283
  } catch {
265
284
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
266
285
  }
@@ -281,6 +300,7 @@ async function loadConfigFrom(configPath) {
281
300
  normalizeScalingRoadmap(migratedCfg);
282
301
  normalizeSessionLifecycle(migratedCfg);
283
302
  normalizeAutoUpdate(migratedCfg);
303
+ normalizeOrchestration(migratedCfg);
284
304
  return { ...DEFAULT_CONFIG, ...migratedCfg };
285
305
  } catch {
286
306
  return { ...DEFAULT_CONFIG };
@@ -352,6 +372,10 @@ var init_config = __esm({
352
372
  checkOnBoot: true,
353
373
  autoInstall: false,
354
374
  checkIntervalMs: 24 * 60 * 60 * 1e3
375
+ },
376
+ orchestration: {
377
+ phase: "phase_1_coo",
378
+ phaseSetBy: "default"
355
379
  }
356
380
  };
357
381
  CONFIG_MIGRATIONS = [
@@ -1795,6 +1819,9 @@ function getClient() {
1795
1819
  if (_daemonClient && _daemonClient._isDaemonActive()) {
1796
1820
  return _daemonClient;
1797
1821
  }
1822
+ if (!_resilientClient) {
1823
+ return _adapterClient;
1824
+ }
1798
1825
  return _resilientClient;
1799
1826
  }
1800
1827
  async function initDaemonClient() {
@@ -2827,6 +2854,127 @@ async function ensureSchema() {
2827
2854
  VALUES (new.rowid, new.content, new.subject, new.predicate, new.object);
2828
2855
  END;
2829
2856
  `);
2857
+ await client.executeMultiple(`
2858
+ CREATE TABLE IF NOT EXISTS agent_sessions (
2859
+ id TEXT PRIMARY KEY,
2860
+ agent_id TEXT NOT NULL,
2861
+ project_name TEXT,
2862
+ started_at TEXT NOT NULL,
2863
+ last_event_at TEXT NOT NULL,
2864
+ event_count INTEGER NOT NULL DEFAULT 0,
2865
+ properties TEXT DEFAULT '{}'
2866
+ );
2867
+
2868
+ CREATE INDEX IF NOT EXISTS idx_agent_sessions_agent_time
2869
+ ON agent_sessions(agent_id, started_at);
2870
+
2871
+ CREATE TABLE IF NOT EXISTS agent_goals (
2872
+ id TEXT PRIMARY KEY,
2873
+ statement TEXT NOT NULL,
2874
+ owner_agent_id TEXT,
2875
+ project_name TEXT,
2876
+ status TEXT NOT NULL DEFAULT 'open',
2877
+ priority INTEGER NOT NULL DEFAULT 5,
2878
+ success_criteria TEXT,
2879
+ parent_goal_id TEXT,
2880
+ due_at TEXT,
2881
+ achieved_at TEXT,
2882
+ supersedes_id TEXT,
2883
+ created_at TEXT NOT NULL,
2884
+ updated_at TEXT NOT NULL,
2885
+ source_memory_id TEXT
2886
+ );
2887
+
2888
+ CREATE INDEX IF NOT EXISTS idx_agent_goals_project_status
2889
+ ON agent_goals(project_name, status, priority);
2890
+
2891
+ CREATE TABLE IF NOT EXISTS agent_events (
2892
+ id TEXT PRIMARY KEY,
2893
+ event_type TEXT NOT NULL,
2894
+ occurred_at TEXT NOT NULL,
2895
+ sequence_index INTEGER NOT NULL,
2896
+ actor_agent_id TEXT,
2897
+ agent_role TEXT,
2898
+ project_name TEXT,
2899
+ session_id TEXT,
2900
+ task_id TEXT,
2901
+ goal_id TEXT,
2902
+ parent_event_id TEXT,
2903
+ intention TEXT,
2904
+ outcome TEXT,
2905
+ evidence_memory_id TEXT,
2906
+ impact TEXT,
2907
+ payload TEXT DEFAULT '{}',
2908
+ created_at TEXT NOT NULL
2909
+ );
2910
+
2911
+ CREATE INDEX IF NOT EXISTS idx_agent_events_time
2912
+ ON agent_events(occurred_at, sequence_index);
2913
+
2914
+ CREATE INDEX IF NOT EXISTS idx_agent_events_session_seq
2915
+ ON agent_events(session_id, sequence_index);
2916
+
2917
+ CREATE INDEX IF NOT EXISTS idx_agent_events_goal_time
2918
+ ON agent_events(goal_id, occurred_at);
2919
+
2920
+ CREATE INDEX IF NOT EXISTS idx_agent_events_memory
2921
+ ON agent_events(evidence_memory_id);
2922
+
2923
+ CREATE TABLE IF NOT EXISTS agent_goal_links (
2924
+ id TEXT PRIMARY KEY,
2925
+ goal_id TEXT NOT NULL,
2926
+ link_type TEXT NOT NULL,
2927
+ target_id TEXT NOT NULL,
2928
+ target_type TEXT NOT NULL,
2929
+ created_at TEXT NOT NULL
2930
+ );
2931
+
2932
+ CREATE INDEX IF NOT EXISTS idx_agent_goal_links_goal
2933
+ ON agent_goal_links(goal_id, target_type);
2934
+
2935
+ CREATE TABLE IF NOT EXISTS agent_semantic_labels (
2936
+ id TEXT PRIMARY KEY,
2937
+ source_memory_id TEXT NOT NULL,
2938
+ event_id TEXT,
2939
+ labeler TEXT NOT NULL,
2940
+ schema_version INTEGER NOT NULL DEFAULT 1,
2941
+ confidence REAL NOT NULL DEFAULT 0,
2942
+ labels TEXT NOT NULL,
2943
+ created_at TEXT NOT NULL,
2944
+ updated_at TEXT NOT NULL
2945
+ );
2946
+
2947
+ CREATE INDEX IF NOT EXISTS idx_agent_semantic_labels_memory
2948
+ ON agent_semantic_labels(source_memory_id, labeler);
2949
+
2950
+ CREATE INDEX IF NOT EXISTS idx_agent_semantic_labels_event
2951
+ ON agent_semantic_labels(event_id);
2952
+
2953
+ CREATE TABLE IF NOT EXISTS agent_reflection_checkpoints (
2954
+ id TEXT PRIMARY KEY,
2955
+ project_name TEXT,
2956
+ session_id TEXT,
2957
+ window_start_at TEXT NOT NULL,
2958
+ window_end_at TEXT NOT NULL,
2959
+ event_count INTEGER NOT NULL DEFAULT 0,
2960
+ goal_count INTEGER NOT NULL DEFAULT 0,
2961
+ success_count INTEGER NOT NULL DEFAULT 0,
2962
+ failure_count INTEGER NOT NULL DEFAULT 0,
2963
+ risk_count INTEGER NOT NULL DEFAULT 0,
2964
+ summary TEXT NOT NULL,
2965
+ learnings TEXT NOT NULL DEFAULT '[]',
2966
+ next_actions TEXT NOT NULL DEFAULT '[]',
2967
+ evidence_event_ids TEXT NOT NULL DEFAULT '[]',
2968
+ confidence REAL NOT NULL DEFAULT 0,
2969
+ created_at TEXT NOT NULL
2970
+ );
2971
+
2972
+ CREATE INDEX IF NOT EXISTS idx_agent_reflection_project_time
2973
+ ON agent_reflection_checkpoints(project_name, window_end_at);
2974
+
2975
+ CREATE INDEX IF NOT EXISTS idx_agent_reflection_session_time
2976
+ ON agent_reflection_checkpoints(session_id, window_end_at);
2977
+ `);
2830
2978
  try {
2831
2979
  await client.execute({
2832
2980
  sql: `ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3`,
@@ -2979,12 +3127,13 @@ var keychain_exports = {};
2979
3127
  __export(keychain_exports, {
2980
3128
  deleteMasterKey: () => deleteMasterKey,
2981
3129
  exportMnemonic: () => exportMnemonic,
3130
+ getKeyStorageInfo: () => getKeyStorageInfo,
2982
3131
  getMasterKey: () => getMasterKey,
2983
3132
  importMnemonic: () => importMnemonic,
2984
3133
  setMasterKey: () => setMasterKey
2985
3134
  });
2986
3135
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
2987
- import { existsSync as existsSync6 } from "fs";
3136
+ import { existsSync as existsSync6, statSync as statSync2 } from "fs";
2988
3137
  import { execSync as execSync2 } from "child_process";
2989
3138
  import path6 from "path";
2990
3139
  import os5 from "os";
@@ -2994,29 +3143,65 @@ function getKeyDir() {
2994
3143
  function getKeyPath() {
2995
3144
  return path6.join(getKeyDir(), "master.key");
2996
3145
  }
2997
- function macKeychainGet() {
3146
+ function nativeKeychainAllowed() {
3147
+ return process.env.EXE_OS_DISABLE_NATIVE_KEYCHAIN !== "1";
3148
+ }
3149
+ function linuxSecretAvailable() {
3150
+ if (!nativeKeychainAllowed()) return false;
3151
+ if (process.platform !== "linux") return false;
3152
+ if (linuxSecretAvailability !== null) return linuxSecretAvailability;
3153
+ try {
3154
+ execSync2("command -v secret-tool >/dev/null 2>&1", { timeout: 1e3 });
3155
+ } catch {
3156
+ linuxSecretAvailability = false;
3157
+ return false;
3158
+ }
3159
+ try {
3160
+ execSync2("secret-tool search --all exe-os probe >/dev/null 2>&1", { timeout: 1e3 });
3161
+ linuxSecretAvailability = true;
3162
+ } catch {
3163
+ linuxSecretAvailability = false;
3164
+ }
3165
+ return linuxSecretAvailability;
3166
+ }
3167
+ function isRootOnlyTrustedServerKeyFile(keyPath) {
3168
+ if (process.platform !== "linux") return false;
3169
+ try {
3170
+ const uid = typeof os5.userInfo().uid === "number" ? os5.userInfo().uid : -1;
3171
+ const st = statSync2(keyPath);
3172
+ if (!st.isFile() || (st.mode & 63) !== 0) return false;
3173
+ if (uid === 0) return true;
3174
+ const exeOsDir = process.env.EXE_OS_DIR;
3175
+ return Boolean(exeOsDir && path6.resolve(keyPath).startsWith(path6.resolve(exeOsDir) + path6.sep));
3176
+ } catch {
3177
+ return false;
3178
+ }
3179
+ }
3180
+ function macKeychainGet(service = SERVICE) {
3181
+ if (!nativeKeychainAllowed()) return null;
2998
3182
  if (process.platform !== "darwin") return null;
2999
3183
  try {
3000
3184
  return execSync2(
3001
- `security find-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w 2>/dev/null`,
3185
+ `security find-generic-password -s "${service}" -a "${ACCOUNT}" -w 2>/dev/null`,
3002
3186
  { encoding: "utf-8", timeout: 5e3 }
3003
3187
  ).trim();
3004
3188
  } catch {
3005
3189
  return null;
3006
3190
  }
3007
3191
  }
3008
- function macKeychainSet(value) {
3192
+ function macKeychainSet(value, service = SERVICE) {
3193
+ if (!nativeKeychainAllowed()) return false;
3009
3194
  if (process.platform !== "darwin") return false;
3010
3195
  try {
3011
3196
  try {
3012
3197
  execSync2(
3013
- `security delete-generic-password -s "${SERVICE}" -a "${ACCOUNT}" 2>/dev/null`,
3198
+ `security delete-generic-password -s "${service}" -a "${ACCOUNT}" 2>/dev/null`,
3014
3199
  { timeout: 5e3 }
3015
3200
  );
3016
3201
  } catch {
3017
3202
  }
3018
3203
  execSync2(
3019
- `security add-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w "${value}"`,
3204
+ `security add-generic-password -s "${service}" -a "${ACCOUNT}" -w "${value}"`,
3020
3205
  { timeout: 5e3 }
3021
3206
  );
3022
3207
  return true;
@@ -3024,11 +3209,12 @@ function macKeychainSet(value) {
3024
3209
  return false;
3025
3210
  }
3026
3211
  }
3027
- function macKeychainDelete() {
3212
+ function macKeychainDelete(service = SERVICE) {
3213
+ if (!nativeKeychainAllowed()) return false;
3028
3214
  if (process.platform !== "darwin") return false;
3029
3215
  try {
3030
3216
  execSync2(
3031
- `security delete-generic-password -s "${SERVICE}" -a "${ACCOUNT}" 2>/dev/null`,
3217
+ `security delete-generic-password -s "${service}" -a "${ACCOUNT}" 2>/dev/null`,
3032
3218
  { timeout: 5e3 }
3033
3219
  );
3034
3220
  return true;
@@ -3036,22 +3222,22 @@ function macKeychainDelete() {
3036
3222
  return false;
3037
3223
  }
3038
3224
  }
3039
- function linuxSecretGet() {
3040
- if (process.platform !== "linux") return null;
3225
+ function linuxSecretGet(service = SERVICE) {
3226
+ if (!linuxSecretAvailable()) return null;
3041
3227
  try {
3042
3228
  return execSync2(
3043
- `secret-tool lookup service "${SERVICE}" account "${ACCOUNT}" 2>/dev/null`,
3229
+ `secret-tool lookup service "${service}" account "${ACCOUNT}" 2>/dev/null`,
3044
3230
  { encoding: "utf-8", timeout: 5e3 }
3045
3231
  ).trim();
3046
3232
  } catch {
3047
3233
  return null;
3048
3234
  }
3049
3235
  }
3050
- function linuxSecretSet(value) {
3051
- if (process.platform !== "linux") return false;
3236
+ function linuxSecretSet(value, service = SERVICE) {
3237
+ if (!linuxSecretAvailable()) return false;
3052
3238
  try {
3053
3239
  execSync2(
3054
- `echo -n "${value}" | secret-tool store --label="exe-os master key" service "${SERVICE}" account "${ACCOUNT}"`,
3240
+ `echo -n "${value}" | secret-tool store --label="exe-os master key" service "${service}" account "${ACCOUNT}" 2>/dev/null`,
3055
3241
  { timeout: 5e3 }
3056
3242
  );
3057
3243
  return true;
@@ -3059,11 +3245,12 @@ function linuxSecretSet(value) {
3059
3245
  return false;
3060
3246
  }
3061
3247
  }
3062
- function linuxSecretDelete() {
3248
+ function linuxSecretDelete(service = SERVICE) {
3249
+ if (!nativeKeychainAllowed()) return false;
3063
3250
  if (process.platform !== "linux") return false;
3064
3251
  try {
3065
3252
  execSync2(
3066
- `secret-tool clear service "${SERVICE}" account "${ACCOUNT}" 2>/dev/null`,
3253
+ `secret-tool clear service "${service}" account "${ACCOUNT}" 2>/dev/null`,
3067
3254
  { timeout: 5e3 }
3068
3255
  );
3069
3256
  return true;
@@ -3072,6 +3259,7 @@ function linuxSecretDelete() {
3072
3259
  }
3073
3260
  }
3074
3261
  async function tryKeytar() {
3262
+ if (!nativeKeychainAllowed()) return null;
3075
3263
  try {
3076
3264
  return await import("keytar");
3077
3265
  } catch {
@@ -3145,7 +3333,19 @@ async function writeMachineBoundFileFallback(b64) {
3145
3333
  return "plaintext";
3146
3334
  }
3147
3335
  async function getMasterKey() {
3148
- const nativeValue = macKeychainGet() ?? linuxSecretGet();
3336
+ let nativeValue = macKeychainGet() ?? linuxSecretGet();
3337
+ if (!nativeValue) {
3338
+ const legacyValue = macKeychainGet(LEGACY_SERVICE) ?? linuxSecretGet(LEGACY_SERVICE);
3339
+ if (legacyValue) {
3340
+ const migrated = macKeychainSet(legacyValue) || linuxSecretSet(legacyValue);
3341
+ if (migrated) {
3342
+ macKeychainDelete(LEGACY_SERVICE);
3343
+ linuxSecretDelete(LEGACY_SERVICE);
3344
+ process.stderr.write("[keychain] Migrated keychain service from exe-mem to exe-os.\n");
3345
+ }
3346
+ nativeValue = legacyValue;
3347
+ }
3348
+ }
3149
3349
  if (nativeValue) {
3150
3350
  return Buffer.from(nativeValue, "base64");
3151
3351
  }
@@ -3153,12 +3353,17 @@ async function getMasterKey() {
3153
3353
  if (keytar) {
3154
3354
  try {
3155
3355
  const keytarValue = await keytar.getPassword(SERVICE, ACCOUNT);
3156
- if (keytarValue) {
3157
- const migrated = macKeychainSet(keytarValue) || linuxSecretSet(keytarValue);
3356
+ const legacyKeytarValue = keytarValue ?? await keytar.getPassword(LEGACY_SERVICE, ACCOUNT);
3357
+ if (legacyKeytarValue) {
3358
+ const migrated = macKeychainSet(legacyKeytarValue) || linuxSecretSet(legacyKeytarValue);
3158
3359
  if (migrated) {
3159
3360
  process.stderr.write("[keychain] Migrated key from keytar to native keychain.\n");
3361
+ try {
3362
+ await keytar.deletePassword(LEGACY_SERVICE, ACCOUNT);
3363
+ } catch {
3364
+ }
3160
3365
  }
3161
- return Buffer.from(keytarValue, "base64");
3366
+ return Buffer.from(legacyKeytarValue, "base64");
3162
3367
  }
3163
3368
  } catch {
3164
3369
  }
@@ -3183,7 +3388,7 @@ async function getMasterKey() {
3183
3388
  const decrypted = decryptWithMachineKey(content, machineKey);
3184
3389
  if (!decrypted) {
3185
3390
  process.stderr.write(
3186
- "[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase: exe-os link import\n"
3391
+ "[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase during setup: exe-os setup\n"
3187
3392
  );
3188
3393
  return null;
3189
3394
  }
@@ -3192,6 +3397,9 @@ async function getMasterKey() {
3192
3397
  b64Value = content;
3193
3398
  }
3194
3399
  const key = Buffer.from(b64Value, "base64");
3400
+ if (!content.startsWith(ENCRYPTED_PREFIX) && isRootOnlyTrustedServerKeyFile(keyPath)) {
3401
+ return key;
3402
+ }
3195
3403
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
3196
3404
  if (migrated) {
3197
3405
  process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
@@ -3219,6 +3427,97 @@ async function getMasterKey() {
3219
3427
  return null;
3220
3428
  }
3221
3429
  }
3430
+ async function getKeyStorageInfo() {
3431
+ if (macKeychainGet()) {
3432
+ return {
3433
+ kind: "macos-keychain",
3434
+ secure: true,
3435
+ note: "stored in macOS Keychain via built-in security CLI"
3436
+ };
3437
+ }
3438
+ if (macKeychainGet(LEGACY_SERVICE)) {
3439
+ return {
3440
+ kind: "macos-keychain",
3441
+ secure: true,
3442
+ note: "stored in legacy macOS Keychain service exe-mem; next key read migrates it to exe-os"
3443
+ };
3444
+ }
3445
+ if (linuxSecretGet()) {
3446
+ return {
3447
+ kind: "linux-secret-service",
3448
+ secure: true,
3449
+ note: "stored in Linux Secret Service via secret-tool"
3450
+ };
3451
+ }
3452
+ if (linuxSecretGet(LEGACY_SERVICE)) {
3453
+ return {
3454
+ kind: "linux-secret-service",
3455
+ secure: true,
3456
+ note: "stored in legacy Linux Secret Service service exe-mem; next key read migrates it to exe-os"
3457
+ };
3458
+ }
3459
+ const keytar = await tryKeytar();
3460
+ if (keytar) {
3461
+ try {
3462
+ if (await keytar.getPassword(SERVICE, ACCOUNT)) {
3463
+ return {
3464
+ kind: "legacy-keytar",
3465
+ secure: true,
3466
+ note: "stored in legacy keytar backend; will migrate to native keychain when possible"
3467
+ };
3468
+ }
3469
+ if (await keytar.getPassword(LEGACY_SERVICE, ACCOUNT)) {
3470
+ return {
3471
+ kind: "legacy-keytar",
3472
+ secure: true,
3473
+ note: "stored in legacy keytar service exe-mem; will migrate to native exe-os keychain when possible"
3474
+ };
3475
+ }
3476
+ } catch {
3477
+ }
3478
+ }
3479
+ const keyPath = getKeyPath();
3480
+ if (!existsSync6(keyPath)) {
3481
+ return {
3482
+ kind: "missing",
3483
+ secure: false,
3484
+ path: keyPath,
3485
+ note: "no key found in OS keychain, legacy keytar, or file fallback"
3486
+ };
3487
+ }
3488
+ try {
3489
+ const content = (await readFile3(keyPath, "utf-8")).trim();
3490
+ if (content.startsWith(ENCRYPTED_PREFIX)) {
3491
+ return {
3492
+ kind: "encrypted-file",
3493
+ secure: true,
3494
+ path: keyPath,
3495
+ note: "stored in machine-bound encrypted file fallback"
3496
+ };
3497
+ }
3498
+ if (isRootOnlyTrustedServerKeyFile(keyPath)) {
3499
+ return {
3500
+ kind: "server-secret-file",
3501
+ secure: true,
3502
+ path: keyPath,
3503
+ note: "stored as root-only trusted server secret file"
3504
+ };
3505
+ }
3506
+ return {
3507
+ kind: "plaintext-file",
3508
+ secure: false,
3509
+ path: keyPath,
3510
+ note: "stored in legacy plaintext file; reading it will migrate or encrypt it"
3511
+ };
3512
+ } catch {
3513
+ return {
3514
+ kind: "missing",
3515
+ secure: false,
3516
+ path: keyPath,
3517
+ note: "key file exists but could not be read"
3518
+ };
3519
+ }
3520
+ }
3222
3521
  async function setMasterKey(key) {
3223
3522
  const b64 = key.toString("base64");
3224
3523
  if (macKeychainSet(b64) || linuxSecretSet(b64)) {
@@ -3244,10 +3543,13 @@ async function setMasterKey(key) {
3244
3543
  async function deleteMasterKey() {
3245
3544
  macKeychainDelete();
3246
3545
  linuxSecretDelete();
3546
+ macKeychainDelete(LEGACY_SERVICE);
3547
+ linuxSecretDelete(LEGACY_SERVICE);
3247
3548
  const keytar = await tryKeytar();
3248
3549
  if (keytar) {
3249
3550
  try {
3250
3551
  await keytar.deletePassword(SERVICE, ACCOUNT);
3552
+ await keytar.deletePassword(LEGACY_SERVICE, ACCOUNT);
3251
3553
  } catch {
3252
3554
  }
3253
3555
  }
@@ -3285,12 +3587,14 @@ async function importMnemonic(mnemonic) {
3285
3587
  const entropy = mnemonicToEntropy(trimmed);
3286
3588
  return Buffer.from(entropy, "hex");
3287
3589
  }
3288
- var SERVICE, ACCOUNT, ENCRYPTED_PREFIX;
3590
+ var SERVICE, LEGACY_SERVICE, ACCOUNT, linuxSecretAvailability, ENCRYPTED_PREFIX;
3289
3591
  var init_keychain = __esm({
3290
3592
  "src/lib/keychain.ts"() {
3291
3593
  "use strict";
3292
- SERVICE = "exe-mem";
3594
+ SERVICE = "exe-os";
3595
+ LEGACY_SERVICE = "exe-mem";
3293
3596
  ACCOUNT = "master-key";
3597
+ linuxSecretAvailability = null;
3294
3598
  ENCRYPTED_PREFIX = "enc:";
3295
3599
  }
3296
3600
  });
@@ -3366,7 +3670,7 @@ __export(shard_manager_exports, {
3366
3670
  shardExists: () => shardExists
3367
3671
  });
3368
3672
  import path7 from "path";
3369
- import { existsSync as existsSync7, mkdirSync as mkdirSync2, readdirSync, renameSync as renameSync3, statSync as statSync2 } from "fs";
3673
+ import { existsSync as existsSync7, mkdirSync as mkdirSync2, readdirSync, renameSync as renameSync3, statSync as statSync3 } from "fs";
3370
3674
  import { createClient as createClient2 } from "@libsql/client";
3371
3675
  function initShardManager(encryptionKey) {
3372
3676
  _encryptionKey = encryptionKey;
@@ -3430,7 +3734,7 @@ async function auditShardHealth(options = {}) {
3430
3734
  const shards = [];
3431
3735
  for (const name of names) {
3432
3736
  const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
3433
- const stat = statSync2(dbPath);
3737
+ const stat = statSync3(dbPath);
3434
3738
  const item = {
3435
3739
  name,
3436
3740
  path: dbPath,
@@ -3683,7 +3987,7 @@ async function getReadyShardClient(projectName) {
3683
3987
  _shardLastAccess.delete(safeName);
3684
3988
  const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
3685
3989
  if (existsSync7(dbPath)) {
3686
- const stat = statSync2(dbPath);
3990
+ const stat = statSync3(dbPath);
3687
3991
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3688
3992
  const archivedPath = path7.join(SHARDS_DIR, `${safeName}.db.broken-${stamp}`);
3689
3993
  renameSync3(dbPath, archivedPath);
@@ -3803,6 +4107,12 @@ var init_platform_procedures = __esm({
3803
4107
  priority: "p0",
3804
4108
  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."
3805
4109
  },
4110
+ {
4111
+ title: "Customer orchestration maturity \u2014 recommend, never trap",
4112
+ domain: "workflow",
4113
+ priority: "p1",
4114
+ 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."
4115
+ },
3806
4116
  {
3807
4117
  title: "Single dispatch path \u2014 create_task only",
3808
4118
  domain: "workflow",
@@ -3861,6 +4171,12 @@ var init_platform_procedures = __esm({
3861
4171
  priority: "p0",
3862
4172
  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."
3863
4173
  },
4174
+ {
4175
+ title: "Commit discipline \u2014 never leave verified work floating",
4176
+ domain: "workflow",
4177
+ priority: "p1",
4178
+ 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."
4179
+ },
3864
4180
  {
3865
4181
  title: "Desktop and TUI are the same product",
3866
4182
  domain: "architecture",
@@ -4565,8 +4881,8 @@ async function validateLicense(apiKey, deviceId) {
4565
4881
  }
4566
4882
  function getCacheAgeMs() {
4567
4883
  try {
4568
- const { statSync: statSync6 } = __require("fs");
4569
- const s = statSync6(CACHE_PATH);
4884
+ const { statSync: statSync7 } = __require("fs");
4885
+ const s = statSync7(CACHE_PATH);
4570
4886
  return Date.now() - s.mtimeMs;
4571
4887
  } catch {
4572
4888
  return Infinity;
@@ -5543,7 +5859,7 @@ __export(db_backup_exports, {
5543
5859
  listBackups: () => listBackups,
5544
5860
  rotateBackups: () => rotateBackups
5545
5861
  });
5546
- import { copyFileSync, existsSync as existsSync18, mkdirSync as mkdirSync9, readdirSync as readdirSync5, unlinkSync as unlinkSync7, statSync as statSync4 } from "fs";
5862
+ import { copyFileSync, existsSync as existsSync18, mkdirSync as mkdirSync9, readdirSync as readdirSync5, unlinkSync as unlinkSync7, statSync as statSync5 } from "fs";
5547
5863
  import path19 from "path";
5548
5864
  function findActiveDb() {
5549
5865
  for (const name of DB_NAMES) {
@@ -5587,7 +5903,7 @@ function rotateBackups(keepDays = DEFAULT_KEEP_DAYS) {
5587
5903
  if (!file.endsWith(".db") && !file.endsWith(".db-wal") && !file.endsWith(".db-shm")) continue;
5588
5904
  const filePath = path19.join(BACKUP_DIR, file);
5589
5905
  try {
5590
- const stat = statSync4(filePath);
5906
+ const stat = statSync5(filePath);
5591
5907
  if (stat.mtimeMs < cutoff) {
5592
5908
  unlinkSync7(filePath);
5593
5909
  deleted++;
@@ -5605,7 +5921,7 @@ function listBackups() {
5605
5921
  const files = readdirSync5(BACKUP_DIR).filter((f) => f.endsWith(".db") && !f.endsWith("-wal") && !f.endsWith("-shm"));
5606
5922
  return files.map((name) => {
5607
5923
  const p = path19.join(BACKUP_DIR, name);
5608
- const stat = statSync4(p);
5924
+ const stat = statSync5(p);
5609
5925
  return { path: p, name, size: stat.size, date: stat.mtime };
5610
5926
  }).sort((a, b) => b.date.getTime() - a.date.getTime());
5611
5927
  } catch {
@@ -5638,8 +5954,10 @@ var init_db_backup = __esm({
5638
5954
  // src/lib/cloud-sync.ts
5639
5955
  var cloud_sync_exports = {};
5640
5956
  __export(cloud_sync_exports, {
5957
+ CLOUD_REUPLOAD_REQUIRED_MESSAGE: () => CLOUD_REUPLOAD_REQUIRED_MESSAGE,
5641
5958
  assertSecureEndpoint: () => assertSecureEndpoint,
5642
5959
  buildRosterBlob: () => buildRosterBlob,
5960
+ clearCloudReuploadRequired: () => clearCloudReuploadRequired,
5643
5961
  cloudPull: () => cloudPull,
5644
5962
  cloudPullBehaviors: () => cloudPullBehaviors,
5645
5963
  cloudPullBlob: () => cloudPullBlob,
@@ -5658,13 +5976,16 @@ __export(cloud_sync_exports, {
5658
5976
  cloudPushGraphRAG: () => cloudPushGraphRAG,
5659
5977
  cloudPushRoster: () => cloudPushRoster,
5660
5978
  cloudPushTasks: () => cloudPushTasks,
5979
+ cloudResetMemoryBlobs: () => cloudResetMemoryBlobs,
5661
5980
  cloudSync: () => cloudSync,
5981
+ getCloudReuploadRequired: () => getCloudReuploadRequired,
5982
+ markCloudReuploadRequired: () => markCloudReuploadRequired,
5662
5983
  mergeConfig: () => mergeConfig,
5663
5984
  mergeRosterFromRemote: () => mergeRosterFromRemote,
5664
5985
  pushToPostgres: () => pushToPostgres,
5665
5986
  recordRosterDeletion: () => recordRosterDeletion
5666
5987
  });
5667
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync9, existsSync as existsSync19, readdirSync as readdirSync6, mkdirSync as mkdirSync10, appendFileSync as appendFileSync3, unlinkSync as unlinkSync8, openSync as openSync2, closeSync as closeSync2, statSync as statSync5 } from "fs";
5988
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync9, existsSync as existsSync19, readdirSync as readdirSync6, mkdirSync as mkdirSync10, appendFileSync as appendFileSync3, unlinkSync as unlinkSync8, openSync as openSync2, closeSync as closeSync2, statSync as statSync6 } from "fs";
5668
5989
  import crypto4 from "crypto";
5669
5990
  import path20 from "path";
5670
5991
  import { homedir as homedir2 } from "os";
@@ -5711,18 +6032,36 @@ function loadPgClient() {
5711
6032
  const { pathToFileURL: pathToFileURL3 } = await import("url");
5712
6033
  const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
5713
6034
  if (explicitPath) {
5714
- const mod2 = await import(pathToFileURL3(explicitPath).href);
5715
- const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
5716
- if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
5717
- return new Ctor2();
6035
+ const mod = await import(pathToFileURL3(explicitPath).href);
6036
+ const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
6037
+ if (!Ctor) throw new Error(`No PrismaClient at ${explicitPath}`);
6038
+ return new Ctor();
5718
6039
  }
5719
6040
  const exeDbRoot = process.env.EXE_DB_ROOT ?? path20.join(homedir2(), "exe-db");
5720
- const req = createRequire3(path20.join(exeDbRoot, "package.json"));
5721
- const entry = req.resolve("@prisma/client");
5722
- const mod = await import(pathToFileURL3(entry).href);
5723
- const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
5724
- if (!Ctor) throw new Error("No PrismaClient");
5725
- return new Ctor();
6041
+ const packagePath = path20.join(exeDbRoot, "package.json");
6042
+ if (existsSync19(packagePath)) {
6043
+ const req = createRequire3(packagePath);
6044
+ const entry = req.resolve("@prisma/client");
6045
+ const mod = await import(pathToFileURL3(entry).href);
6046
+ const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
6047
+ if (!Ctor) throw new Error("No PrismaClient");
6048
+ return new Ctor();
6049
+ }
6050
+ const { Pool } = await import("pg");
6051
+ const pool = new Pool({ connectionString: process.env.DATABASE_URL });
6052
+ return {
6053
+ async $queryRawUnsafe(query, ...values) {
6054
+ const result = await pool.query(query, values);
6055
+ return result.rows;
6056
+ },
6057
+ async $executeRawUnsafe(query, ...values) {
6058
+ const result = await pool.query(query, values);
6059
+ return result.rowCount ?? 0;
6060
+ },
6061
+ async $disconnect() {
6062
+ await pool.end();
6063
+ }
6064
+ };
5726
6065
  })().catch(() => {
5727
6066
  _pgFailed = true;
5728
6067
  _pgPromise = null;
@@ -5743,7 +6082,7 @@ async function pushToPostgres(records) {
5743
6082
  let inserted = 0;
5744
6083
  for (const rec of records) {
5745
6084
  try {
5746
- await prisma.$executeRawUnsafe(
6085
+ const changed = await prisma.$executeRawUnsafe(
5747
6086
  `INSERT INTO raw.raw_events (id, source, source_id, event_type, payload, metadata, timestamp)
5748
6087
  VALUES (gen_random_uuid(), 'cloud_sync', $1, 'memory', $2::jsonb, $3::jsonb, $4)
5749
6088
  ON CONFLICT (source, source_id, event_type) DO NOTHING`,
@@ -5752,7 +6091,7 @@ async function pushToPostgres(records) {
5752
6091
  JSON.stringify({ agent_id: rec.agent_id, project_name: rec.project_name, tool_name: rec.tool_name }),
5753
6092
  rec.timestamp ? new Date(String(rec.timestamp)) : /* @__PURE__ */ new Date()
5754
6093
  );
5755
- inserted++;
6094
+ inserted += Number(changed ?? 0);
5756
6095
  } catch {
5757
6096
  }
5758
6097
  }
@@ -5857,6 +6196,23 @@ async function cloudPush(records, maxVersion, config) {
5857
6196
  return false;
5858
6197
  }
5859
6198
  }
6199
+ async function cloudResetMemoryBlobs(config) {
6200
+ assertSecureEndpoint(config.endpoint);
6201
+ const resp = await fetchWithRetry(`${config.endpoint}/sync/reset-memory`, {
6202
+ method: "POST",
6203
+ headers: {
6204
+ Authorization: `Bearer ${config.apiKey}`,
6205
+ "Content-Type": "application/json",
6206
+ "X-Device-Id": loadDeviceId()
6207
+ },
6208
+ body: JSON.stringify({ confirm: "LOCAL DB IS SOURCE OF TRUTH" })
6209
+ });
6210
+ if (!resp.ok) {
6211
+ throw new Error(`cloud reset failed: HTTP ${resp.status}`);
6212
+ }
6213
+ const data = await resp.json();
6214
+ return { deleted: Number(data.deleted ?? 0), freedBytes: Number(data.freed_bytes ?? 0) };
6215
+ }
5860
6216
  async function cloudPull(sinceVersion, config) {
5861
6217
  assertSecureEndpoint(config.endpoint);
5862
6218
  try {
@@ -5876,22 +6232,61 @@ async function cloudPull(sinceVersion, config) {
5876
6232
  if (!response.ok) return { records: [], maxVersion: sinceVersion };
5877
6233
  const data = await response.json();
5878
6234
  const allRecords = [];
5879
- for (const { blob } of data.blobs ?? []) {
6235
+ let maxReadableVersion = sinceVersion;
6236
+ let skippedBlobs = 0;
6237
+ for (const { version, blob } of data.blobs ?? []) {
5880
6238
  try {
5881
6239
  const compressed = decryptSyncBlob(blob);
5882
6240
  const json = decompress(compressed).toString("utf8");
5883
6241
  const records = JSON.parse(json);
5884
6242
  allRecords.push(...records);
6243
+ const recordMax = records.reduce((max, rec) => {
6244
+ const v = Number(rec.version ?? 0);
6245
+ return Number.isFinite(v) ? Math.max(max, v) : max;
6246
+ }, 0);
6247
+ const blobVersion = Number(version ?? 0);
6248
+ maxReadableVersion = Math.max(
6249
+ maxReadableVersion,
6250
+ Number.isFinite(blobVersion) ? blobVersion : 0,
6251
+ recordMax
6252
+ );
5885
6253
  } catch {
6254
+ skippedBlobs++;
5886
6255
  continue;
5887
6256
  }
5888
6257
  }
5889
- return { records: allRecords, maxVersion: data.max_version ?? sinceVersion };
6258
+ if (skippedBlobs > 0) {
6259
+ logError(`[cloud-sync] PULL skipped ${skippedBlobs} undecryptable blob(s); pull cursor advanced only to last readable version ${maxReadableVersion}`);
6260
+ }
6261
+ return { records: allRecords, maxVersion: maxReadableVersion };
5890
6262
  } catch (err) {
5891
6263
  logError(`[cloud-sync] PULL FAILED: ${err instanceof Error ? err.message : String(err)}`);
5892
6264
  return { records: [], maxVersion: sinceVersion };
5893
6265
  }
5894
6266
  }
6267
+ async function getCloudReuploadRequired(client = getClient()) {
6268
+ try {
6269
+ await client.execute("CREATE TABLE IF NOT EXISTS sync_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)");
6270
+ const result = await client.execute("SELECT key, value FROM sync_meta WHERE key IN ('cloud_reupload_required', 'cloud_relink_required')");
6271
+ return result.rows.some((row) => String(row.value ?? "") === "1");
6272
+ } catch {
6273
+ return false;
6274
+ }
6275
+ }
6276
+ async function clearCloudReuploadRequired(client = getClient()) {
6277
+ await client.execute("CREATE TABLE IF NOT EXISTS sync_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)");
6278
+ await client.execute("INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('cloud_reupload_required', '0')");
6279
+ await client.execute("INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('cloud_relink_required', '0')");
6280
+ await client.execute({
6281
+ sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('cloud_reuploaded_at', ?)",
6282
+ args: [(/* @__PURE__ */ new Date()).toISOString()]
6283
+ });
6284
+ await client.execute("DELETE FROM sync_meta WHERE key IN ('last_cloud_pull_version', 'last_cloud_push_version')");
6285
+ }
6286
+ async function markCloudReuploadRequired(client = getClient()) {
6287
+ await client.execute("CREATE TABLE IF NOT EXISTS sync_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)");
6288
+ await client.execute("INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('cloud_reupload_required', '1')");
6289
+ }
5895
6290
  async function cloudSync(config) {
5896
6291
  if (!isSyncCryptoInitialized()) {
5897
6292
  try {
@@ -5913,13 +6308,10 @@ async function cloudSync(config) {
5913
6308
  throw new Error("[cloud-sync] Database not initialized. Call initStore() before cloudSync().");
5914
6309
  }
5915
6310
  try {
5916
- const relink = await client.execute("SELECT value FROM sync_meta WHERE key = 'cloud_relink_required' LIMIT 1");
5917
- if (String(relink.rows[0]?.value ?? "") === "1") {
5918
- throw new Error("[cloud-sync] Paused after key rotation. Re-link/reupload cloud sync with the new recovery phrase before syncing.");
5919
- }
6311
+ if (await getCloudReuploadRequired(client)) throw new Error(CLOUD_REUPLOAD_REQUIRED_MESSAGE);
5920
6312
  } catch (err) {
5921
6313
  const msg = err instanceof Error ? err.message : String(err);
5922
- if (msg.includes("Paused after key rotation")) throw err;
6314
+ if (msg === CLOUD_REUPLOAD_REQUIRED_MESSAGE || msg.includes("key rotation")) throw err;
5923
6315
  }
5924
6316
  try {
5925
6317
  const { getRawClient: getRawClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
@@ -6177,7 +6569,7 @@ async function cloudSync(config) {
6177
6569
  const { getLatestBackup: getLatestBackup2 } = await Promise.resolve().then(() => (init_db_backup(), db_backup_exports));
6178
6570
  const latestBackup = getLatestBackup2();
6179
6571
  if (latestBackup) {
6180
- const backupSize = statSync5(latestBackup).size;
6572
+ const backupSize = statSync6(latestBackup).size;
6181
6573
  const MAX_CLOUD_BACKUP_BYTES = 50 * 1024 * 1024;
6182
6574
  if (backupSize <= MAX_CLOUD_BACKUP_BYTES) {
6183
6575
  const backupData = readFileSync13(latestBackup);
@@ -6848,7 +7240,7 @@ async function cloudPullDocuments(config) {
6848
7240
  }
6849
7241
  return { pulled };
6850
7242
  }
6851
- var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, _pgPromise, _pgFailed, ROSTER_DELETIONS_PATH;
7243
+ var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, _pgPromise, _pgFailed, CLOUD_REUPLOAD_REQUIRED_MESSAGE, ROSTER_DELETIONS_PATH;
6852
7244
  var init_cloud_sync = __esm({
6853
7245
  "src/lib/cloud-sync.ts"() {
6854
7246
  "use strict";
@@ -6867,6 +7259,7 @@ var init_cloud_sync = __esm({
6867
7259
  LOCK_STALE_MS = 3e4;
6868
7260
  _pgPromise = null;
6869
7261
  _pgFailed = false;
7262
+ CLOUD_REUPLOAD_REQUIRED_MESSAGE = "Cloud sync is blocked because this device rotated its memory encryption key. Run `exe-os cloud reupload` first to re-upload the cloud backup with the new key.";
6870
7263
  ROSTER_DELETIONS_PATH = path20.join(EXE_AI_DIR, "roster-deletions.json");
6871
7264
  }
6872
7265
  });
@@ -6973,46 +7366,6 @@ function vectorToBlob(vector) {
6973
7366
  // src/adapters/claude/hooks/summary-worker.ts
6974
7367
  init_database();
6975
7368
  init_notifications();
6976
-
6977
- // src/lib/memory-queue-client.ts
6978
- init_exe_daemon_client();
6979
-
6980
- // src/lib/memory-queue.ts
6981
- init_config();
6982
- import { appendFileSync as appendFileSync2, readFileSync as readFileSync11, renameSync as renameSync5, unlinkSync as unlinkSync4, existsSync as existsSync15, statSync as statSync3 } from "fs";
6983
- import path16 from "path";
6984
- var QUEUE_PATH2 = path16.join(EXE_AI_DIR, "memory-queue.jsonl");
6985
- var PROCESSING_PATH = QUEUE_PATH2 + ".processing";
6986
- var TTL_MS2 = 24 * 60 * 60 * 1e3;
6987
- function enqueueMemory(entry) {
6988
- appendFileSync2(QUEUE_PATH2, JSON.stringify(entry) + "\n");
6989
- }
6990
-
6991
- // src/lib/memory-queue-client.ts
6992
- async function writeMemoryViaDaemon(entry) {
6993
- if (process.env.EXE_IS_DAEMON === "1") {
6994
- enqueueMemory(entry);
6995
- return false;
6996
- }
6997
- if (!isClientConnected()) {
6998
- enqueueMemory(entry);
6999
- return false;
7000
- }
7001
- try {
7002
- const response = await sendDaemonRequest({
7003
- type: "write-memory",
7004
- entry
7005
- });
7006
- if (response.ok) return true;
7007
- enqueueMemory(entry);
7008
- return false;
7009
- } catch {
7010
- enqueueMemory(entry);
7011
- return false;
7012
- }
7013
- }
7014
-
7015
- // src/adapters/claude/hooks/summary-worker.ts
7016
7369
  init_task_scope();
7017
7370
  init_employees();
7018
7371
  import { execSync as execSync5 } from "child_process";
@@ -7099,6 +7452,119 @@ function buildAutoCheckpoint(input) {
7099
7452
  };
7100
7453
  }
7101
7454
 
7455
+ // src/lib/memory-queue-client.ts
7456
+ init_exe_daemon_client();
7457
+
7458
+ // src/lib/memory-queue.ts
7459
+ init_config();
7460
+ import { appendFileSync as appendFileSync2, readFileSync as readFileSync11, renameSync as renameSync5, unlinkSync as unlinkSync4, existsSync as existsSync15, statSync as statSync4 } from "fs";
7461
+ import path16 from "path";
7462
+ var QUEUE_PATH2 = path16.join(EXE_AI_DIR, "memory-queue.jsonl");
7463
+ var PROCESSING_PATH = QUEUE_PATH2 + ".processing";
7464
+ var TTL_MS2 = 24 * 60 * 60 * 1e3;
7465
+ function enqueueMemory(entry) {
7466
+ appendFileSync2(QUEUE_PATH2, JSON.stringify(entry) + "\n");
7467
+ }
7468
+
7469
+ // src/lib/memory-queue-client.ts
7470
+ async function writeMemoryViaDaemon(entry) {
7471
+ if (process.env.EXE_IS_DAEMON === "1") {
7472
+ enqueueMemory(entry);
7473
+ return false;
7474
+ }
7475
+ if (!isClientConnected()) {
7476
+ enqueueMemory(entry);
7477
+ return false;
7478
+ }
7479
+ try {
7480
+ const response = await sendDaemonRequest({
7481
+ type: "write-memory",
7482
+ entry
7483
+ });
7484
+ if (response.ok) return true;
7485
+ enqueueMemory(entry);
7486
+ return false;
7487
+ } catch {
7488
+ enqueueMemory(entry);
7489
+ return false;
7490
+ }
7491
+ }
7492
+
7493
+ // src/lib/checkpoint-orchestrator.ts
7494
+ function toolNameForReason(reason) {
7495
+ switch (reason) {
7496
+ case "periodic":
7497
+ return "auto-summary";
7498
+ case "session-end":
7499
+ return "SessionEnd";
7500
+ case "pre-compact":
7501
+ return "pre-compact-hook";
7502
+ case "capacity-signal":
7503
+ return "auto-checkpoint";
7504
+ }
7505
+ }
7506
+ function importanceForReason(reason, override) {
7507
+ if (override !== void 0) return override;
7508
+ switch (reason) {
7509
+ case "periodic":
7510
+ return 7;
7511
+ case "session-end":
7512
+ case "pre-compact":
7513
+ case "capacity-signal":
7514
+ return 8;
7515
+ }
7516
+ }
7517
+ function buildContinuityCheckpoint(input) {
7518
+ const { checkpointText, decisionTexts } = buildAutoCheckpoint({
7519
+ agentId: input.agentId,
7520
+ agentRole: input.agentRole,
7521
+ sessionId: input.sessionId,
7522
+ projectName: input.projectName,
7523
+ reason: input.reason,
7524
+ memories: input.memories ?? [],
7525
+ tasks: input.tasks ?? [],
7526
+ maxSamples: input.maxSamples
7527
+ });
7528
+ const extra = input.extraSections?.filter((section) => section.trim().length > 0) ?? [];
7529
+ return {
7530
+ checkpointText: extra.length > 0 ? `${checkpointText}
7531
+
7532
+ ${extra.join("\n\n")}` : checkpointText,
7533
+ decisionTexts
7534
+ };
7535
+ }
7536
+ async function writeContinuityCheckpoint(input) {
7537
+ const result = buildContinuityCheckpoint(input);
7538
+ const now = (/* @__PURE__ */ new Date()).toISOString();
7539
+ await writeMemoryViaDaemon({
7540
+ raw_text: result.checkpointText,
7541
+ agent_id: input.agentId,
7542
+ agent_role: input.agentRole,
7543
+ session_id: input.sessionId,
7544
+ tool_name: toolNameForReason(input.reason),
7545
+ project_name: input.projectName,
7546
+ timestamp: now,
7547
+ importance: importanceForReason(input.reason, input.importance),
7548
+ task_id: input.taskId,
7549
+ memory_type: "checkpoint"
7550
+ });
7551
+ const decisionLimit = input.reason === "periodic" ? 3 : 5;
7552
+ for (const decisionText of result.decisionTexts.slice(0, decisionLimit)) {
7553
+ await writeMemoryViaDaemon({
7554
+ raw_text: decisionText,
7555
+ agent_id: input.agentId,
7556
+ agent_role: input.agentRole,
7557
+ session_id: input.sessionId,
7558
+ tool_name: "auto-decision",
7559
+ project_name: input.projectName,
7560
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
7561
+ importance: 8,
7562
+ memory_type: "decision"
7563
+ });
7564
+ }
7565
+ return result;
7566
+ }
7567
+
7102
7568
  // src/adapters/claude/hooks/summary-worker.ts
7103
7569
  async function main() {
7104
7570
  const agentId = process.env.AGENT_ID ?? "default";
@@ -7157,15 +7623,8 @@ async function main() {
7157
7623
  taskRows = tasks.rows;
7158
7624
  } catch {
7159
7625
  }
7160
- const { checkpointText: summaryText, decisionTexts } = buildAutoCheckpoint({
7161
- agentId,
7162
- agentRole,
7163
- sessionId: `auto-summary-${Date.now()}`,
7164
- projectName: primaryProject,
7165
- reason: "periodic",
7166
- memories: result.rows,
7167
- tasks: taskRows
7168
- });
7626
+ const summarySessionId = `auto-summary-${Date.now()}`;
7627
+ let summaryText = "";
7169
7628
  let limitReached = false;
7170
7629
  try {
7171
7630
  const { assertMemoryLimit: assertMemoryLimit2 } = await Promise.resolve().then(() => (init_plan_limits(), plan_limits_exports));
@@ -7183,30 +7642,16 @@ async function main() {
7183
7642
  }
7184
7643
  if (limitReached) {
7185
7644
  } else {
7186
- await writeMemoryViaDaemon({
7187
- raw_text: summaryText,
7188
- agent_id: agentId,
7189
- agent_role: agentRole,
7190
- session_id: `auto-summary-${Date.now()}`,
7191
- tool_name: "auto-summary",
7192
- project_name: primaryProject,
7193
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
7194
- importance: 7,
7195
- memory_type: "checkpoint"
7645
+ const checkpointResult = await writeContinuityCheckpoint({
7646
+ agentId,
7647
+ agentRole,
7648
+ sessionId: summarySessionId,
7649
+ projectName: primaryProject,
7650
+ reason: "periodic",
7651
+ memories: result.rows,
7652
+ tasks: taskRows
7196
7653
  });
7197
- for (const decisionText of decisionTexts.slice(0, 3)) {
7198
- await writeMemoryViaDaemon({
7199
- raw_text: decisionText,
7200
- agent_id: agentId,
7201
- agent_role: agentRole,
7202
- session_id: `auto-decision-${Date.now()}`,
7203
- tool_name: "auto-decision",
7204
- project_name: primaryProject,
7205
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
7206
- importance: 8,
7207
- memory_type: "decision"
7208
- });
7209
- }
7654
+ summaryText = checkpointResult.checkpointText;
7210
7655
  if (!canCoordinate(agentId, agentRole)) {
7211
7656
  let totalErrors = 0;
7212
7657
  for (const [, data] of projects) {