@askexenow/exe-os 0.9.66 → 0.9.67

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) 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} +1523 -2275
  6. package/dist/bin/backfill-conversations.js +506 -20
  7. package/dist/bin/backfill-responses.js +506 -20
  8. package/dist/bin/backfill-vectors.js +233 -20
  9. package/dist/bin/bulk-sync-postgres.js +4876 -0
  10. package/dist/bin/cleanup-stale-review-tasks.js +507 -21
  11. package/dist/bin/cli.js +2450 -1530
  12. package/dist/bin/exe-assign.js +506 -20
  13. package/dist/bin/exe-boot.js +410 -60
  14. package/dist/bin/exe-cloud.js +795 -105
  15. package/dist/bin/exe-dispatch.js +516 -22
  16. package/dist/bin/exe-doctor.js +587 -30
  17. package/dist/bin/exe-export-behaviors.js +518 -24
  18. package/dist/bin/exe-forget.js +507 -21
  19. package/dist/bin/exe-gateway.js +571 -25
  20. package/dist/bin/exe-heartbeat.js +518 -24
  21. package/dist/bin/exe-kill.js +507 -21
  22. package/dist/bin/exe-launch-agent.js +2312 -1069
  23. package/dist/bin/exe-new-employee.js +197 -165
  24. package/dist/bin/exe-pending-messages.js +507 -21
  25. package/dist/bin/exe-pending-notifications.js +507 -21
  26. package/dist/bin/exe-pending-reviews.js +507 -21
  27. package/dist/bin/exe-rename.js +507 -21
  28. package/dist/bin/exe-review.js +507 -21
  29. package/dist/bin/exe-search.js +518 -24
  30. package/dist/bin/exe-session-cleanup.js +516 -22
  31. package/dist/bin/exe-settings.js +4 -0
  32. package/dist/bin/exe-start-codex.js +682 -143
  33. package/dist/bin/exe-start-opencode.js +627 -79
  34. package/dist/bin/exe-status.js +507 -21
  35. package/dist/bin/exe-team.js +507 -21
  36. package/dist/bin/git-sweep.js +516 -22
  37. package/dist/bin/graph-backfill.js +558 -21
  38. package/dist/bin/graph-export.js +507 -21
  39. package/dist/bin/graph-layer-benchmark.js +109 -0
  40. package/dist/bin/install.js +305 -288
  41. package/dist/bin/intercom-check.js +516 -22
  42. package/dist/bin/postgres-agentic-reflection-backfill.js +187 -0
  43. package/dist/bin/postgres-agentic-semantic-backfill.js +237 -0
  44. package/dist/bin/scan-tasks.js +516 -22
  45. package/dist/bin/setup.js +412 -62
  46. package/dist/bin/shard-migrate.js +506 -20
  47. package/dist/gateway/index.js +569 -23
  48. package/dist/hooks/bug-report-worker.js +519 -25
  49. package/dist/hooks/codex-stop-task-finalizer.js +516 -22
  50. package/dist/hooks/commit-complete.js +516 -22
  51. package/dist/hooks/error-recall.js +518 -24
  52. package/dist/hooks/ingest.js +516 -22
  53. package/dist/hooks/instructions-loaded.js +507 -21
  54. package/dist/hooks/notification.js +507 -21
  55. package/dist/hooks/post-compact.js +507 -21
  56. package/dist/hooks/post-tool-combined.js +519 -25
  57. package/dist/hooks/pre-compact.js +516 -22
  58. package/dist/hooks/pre-tool-use.js +507 -21
  59. package/dist/hooks/prompt-submit.js +519 -25
  60. package/dist/hooks/session-end.js +516 -22
  61. package/dist/hooks/session-start.js +520 -26
  62. package/dist/hooks/stop.js +517 -23
  63. package/dist/hooks/subagent-stop.js +507 -21
  64. package/dist/hooks/summary-worker.js +411 -61
  65. package/dist/index.js +569 -23
  66. package/dist/lib/cloud-sync.js +391 -53
  67. package/dist/lib/config.js +13 -1
  68. package/dist/lib/consolidation.js +1 -1
  69. package/dist/lib/database.js +124 -0
  70. package/dist/lib/db.js +124 -0
  71. package/dist/lib/device-registry.js +124 -0
  72. package/dist/lib/embedder.js +13 -1
  73. package/dist/lib/exe-daemon.js +2184 -561
  74. package/dist/lib/hybrid-search.js +518 -24
  75. package/dist/lib/identity.js +3 -0
  76. package/dist/lib/keychain.js +178 -22
  77. package/dist/lib/messaging.js +3 -0
  78. package/dist/lib/reminders.js +3 -0
  79. package/dist/lib/schedules.js +233 -20
  80. package/dist/lib/skill-learning.js +16 -1
  81. package/dist/lib/store.js +506 -20
  82. package/dist/lib/tasks.js +16 -1
  83. package/dist/lib/tmux-routing.js +16 -1
  84. package/dist/lib/token-spend.js +3 -0
  85. package/dist/mcp/server.js +1757 -428
  86. package/dist/mcp/tools/complete-reminder.js +3 -0
  87. package/dist/mcp/tools/create-reminder.js +3 -0
  88. package/dist/mcp/tools/create-task.js +16 -1
  89. package/dist/mcp/tools/deactivate-behavior.js +3 -0
  90. package/dist/mcp/tools/list-reminders.js +3 -0
  91. package/dist/mcp/tools/list-tasks.js +3 -0
  92. package/dist/mcp/tools/send-message.js +3 -0
  93. package/dist/mcp/tools/update-task.js +16 -1
  94. package/dist/runtime/index.js +516 -22
  95. package/dist/tui/App.js +594 -29
  96. package/package.json +8 -5
  97. package/src/commands/exe/cloud.md +6 -10
  98. package/stack.release.json +3 -3
  99. package/src/commands/exe/link.md +0 -18
@@ -20,12 +20,13 @@ var keychain_exports = {};
20
20
  __export(keychain_exports, {
21
21
  deleteMasterKey: () => deleteMasterKey,
22
22
  exportMnemonic: () => exportMnemonic,
23
+ getKeyStorageInfo: () => getKeyStorageInfo,
23
24
  getMasterKey: () => getMasterKey,
24
25
  importMnemonic: () => importMnemonic,
25
26
  setMasterKey: () => setMasterKey
26
27
  });
27
28
  import { readFile, writeFile, unlink, mkdir, chmod } from "fs/promises";
28
- import { existsSync } from "fs";
29
+ import { existsSync, statSync } from "fs";
29
30
  import { execSync } from "child_process";
30
31
  import path from "path";
31
32
  import os from "os";
@@ -35,29 +36,65 @@ function getKeyDir() {
35
36
  function getKeyPath() {
36
37
  return path.join(getKeyDir(), "master.key");
37
38
  }
38
- function macKeychainGet() {
39
+ function nativeKeychainAllowed() {
40
+ return process.env.EXE_OS_DISABLE_NATIVE_KEYCHAIN !== "1";
41
+ }
42
+ function linuxSecretAvailable() {
43
+ if (!nativeKeychainAllowed()) return false;
44
+ if (process.platform !== "linux") return false;
45
+ if (linuxSecretAvailability !== null) return linuxSecretAvailability;
46
+ try {
47
+ execSync("command -v secret-tool >/dev/null 2>&1", { timeout: 1e3 });
48
+ } catch {
49
+ linuxSecretAvailability = false;
50
+ return false;
51
+ }
52
+ try {
53
+ execSync("secret-tool search --all exe-os probe >/dev/null 2>&1", { timeout: 1e3 });
54
+ linuxSecretAvailability = true;
55
+ } catch {
56
+ linuxSecretAvailability = false;
57
+ }
58
+ return linuxSecretAvailability;
59
+ }
60
+ function isRootOnlyTrustedServerKeyFile(keyPath) {
61
+ if (process.platform !== "linux") return false;
62
+ try {
63
+ const uid = typeof os.userInfo().uid === "number" ? os.userInfo().uid : -1;
64
+ const st = statSync(keyPath);
65
+ if (!st.isFile() || (st.mode & 63) !== 0) return false;
66
+ if (uid === 0) return true;
67
+ const exeOsDir = process.env.EXE_OS_DIR;
68
+ return Boolean(exeOsDir && path.resolve(keyPath).startsWith(path.resolve(exeOsDir) + path.sep));
69
+ } catch {
70
+ return false;
71
+ }
72
+ }
73
+ function macKeychainGet(service = SERVICE) {
74
+ if (!nativeKeychainAllowed()) return null;
39
75
  if (process.platform !== "darwin") return null;
40
76
  try {
41
77
  return execSync(
42
- `security find-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w 2>/dev/null`,
78
+ `security find-generic-password -s "${service}" -a "${ACCOUNT}" -w 2>/dev/null`,
43
79
  { encoding: "utf-8", timeout: 5e3 }
44
80
  ).trim();
45
81
  } catch {
46
82
  return null;
47
83
  }
48
84
  }
49
- function macKeychainSet(value) {
85
+ function macKeychainSet(value, service = SERVICE) {
86
+ if (!nativeKeychainAllowed()) return false;
50
87
  if (process.platform !== "darwin") return false;
51
88
  try {
52
89
  try {
53
90
  execSync(
54
- `security delete-generic-password -s "${SERVICE}" -a "${ACCOUNT}" 2>/dev/null`,
91
+ `security delete-generic-password -s "${service}" -a "${ACCOUNT}" 2>/dev/null`,
55
92
  { timeout: 5e3 }
56
93
  );
57
94
  } catch {
58
95
  }
59
96
  execSync(
60
- `security add-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w "${value}"`,
97
+ `security add-generic-password -s "${service}" -a "${ACCOUNT}" -w "${value}"`,
61
98
  { timeout: 5e3 }
62
99
  );
63
100
  return true;
@@ -65,11 +102,12 @@ function macKeychainSet(value) {
65
102
  return false;
66
103
  }
67
104
  }
68
- function macKeychainDelete() {
105
+ function macKeychainDelete(service = SERVICE) {
106
+ if (!nativeKeychainAllowed()) return false;
69
107
  if (process.platform !== "darwin") return false;
70
108
  try {
71
109
  execSync(
72
- `security delete-generic-password -s "${SERVICE}" -a "${ACCOUNT}" 2>/dev/null`,
110
+ `security delete-generic-password -s "${service}" -a "${ACCOUNT}" 2>/dev/null`,
73
111
  { timeout: 5e3 }
74
112
  );
75
113
  return true;
@@ -77,22 +115,22 @@ function macKeychainDelete() {
77
115
  return false;
78
116
  }
79
117
  }
80
- function linuxSecretGet() {
81
- if (process.platform !== "linux") return null;
118
+ function linuxSecretGet(service = SERVICE) {
119
+ if (!linuxSecretAvailable()) return null;
82
120
  try {
83
121
  return execSync(
84
- `secret-tool lookup service "${SERVICE}" account "${ACCOUNT}" 2>/dev/null`,
122
+ `secret-tool lookup service "${service}" account "${ACCOUNT}" 2>/dev/null`,
85
123
  { encoding: "utf-8", timeout: 5e3 }
86
124
  ).trim();
87
125
  } catch {
88
126
  return null;
89
127
  }
90
128
  }
91
- function linuxSecretSet(value) {
92
- if (process.platform !== "linux") return false;
129
+ function linuxSecretSet(value, service = SERVICE) {
130
+ if (!linuxSecretAvailable()) return false;
93
131
  try {
94
132
  execSync(
95
- `echo -n "${value}" | secret-tool store --label="exe-os master key" service "${SERVICE}" account "${ACCOUNT}"`,
133
+ `echo -n "${value}" | secret-tool store --label="exe-os master key" service "${service}" account "${ACCOUNT}" 2>/dev/null`,
96
134
  { timeout: 5e3 }
97
135
  );
98
136
  return true;
@@ -100,11 +138,12 @@ function linuxSecretSet(value) {
100
138
  return false;
101
139
  }
102
140
  }
103
- function linuxSecretDelete() {
141
+ function linuxSecretDelete(service = SERVICE) {
142
+ if (!nativeKeychainAllowed()) return false;
104
143
  if (process.platform !== "linux") return false;
105
144
  try {
106
145
  execSync(
107
- `secret-tool clear service "${SERVICE}" account "${ACCOUNT}" 2>/dev/null`,
146
+ `secret-tool clear service "${service}" account "${ACCOUNT}" 2>/dev/null`,
108
147
  { timeout: 5e3 }
109
148
  );
110
149
  return true;
@@ -113,6 +152,7 @@ function linuxSecretDelete() {
113
152
  }
114
153
  }
115
154
  async function tryKeytar() {
155
+ if (!nativeKeychainAllowed()) return null;
116
156
  try {
117
157
  return await import("keytar");
118
158
  } catch {
@@ -186,7 +226,19 @@ async function writeMachineBoundFileFallback(b64) {
186
226
  return "plaintext";
187
227
  }
188
228
  async function getMasterKey() {
189
- const nativeValue = macKeychainGet() ?? linuxSecretGet();
229
+ let nativeValue = macKeychainGet() ?? linuxSecretGet();
230
+ if (!nativeValue) {
231
+ const legacyValue = macKeychainGet(LEGACY_SERVICE) ?? linuxSecretGet(LEGACY_SERVICE);
232
+ if (legacyValue) {
233
+ const migrated = macKeychainSet(legacyValue) || linuxSecretSet(legacyValue);
234
+ if (migrated) {
235
+ macKeychainDelete(LEGACY_SERVICE);
236
+ linuxSecretDelete(LEGACY_SERVICE);
237
+ process.stderr.write("[keychain] Migrated keychain service from exe-mem to exe-os.\n");
238
+ }
239
+ nativeValue = legacyValue;
240
+ }
241
+ }
190
242
  if (nativeValue) {
191
243
  return Buffer.from(nativeValue, "base64");
192
244
  }
@@ -194,12 +246,17 @@ async function getMasterKey() {
194
246
  if (keytar) {
195
247
  try {
196
248
  const keytarValue = await keytar.getPassword(SERVICE, ACCOUNT);
197
- if (keytarValue) {
198
- const migrated = macKeychainSet(keytarValue) || linuxSecretSet(keytarValue);
249
+ const legacyKeytarValue = keytarValue ?? await keytar.getPassword(LEGACY_SERVICE, ACCOUNT);
250
+ if (legacyKeytarValue) {
251
+ const migrated = macKeychainSet(legacyKeytarValue) || linuxSecretSet(legacyKeytarValue);
199
252
  if (migrated) {
200
253
  process.stderr.write("[keychain] Migrated key from keytar to native keychain.\n");
254
+ try {
255
+ await keytar.deletePassword(LEGACY_SERVICE, ACCOUNT);
256
+ } catch {
257
+ }
201
258
  }
202
- return Buffer.from(keytarValue, "base64");
259
+ return Buffer.from(legacyKeytarValue, "base64");
203
260
  }
204
261
  } catch {
205
262
  }
@@ -224,7 +281,7 @@ async function getMasterKey() {
224
281
  const decrypted = decryptWithMachineKey(content, machineKey);
225
282
  if (!decrypted) {
226
283
  process.stderr.write(
227
- "[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase: exe-os link import\n"
284
+ "[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase during setup: exe-os setup\n"
228
285
  );
229
286
  return null;
230
287
  }
@@ -233,6 +290,9 @@ async function getMasterKey() {
233
290
  b64Value = content;
234
291
  }
235
292
  const key = Buffer.from(b64Value, "base64");
293
+ if (!content.startsWith(ENCRYPTED_PREFIX) && isRootOnlyTrustedServerKeyFile(keyPath)) {
294
+ return key;
295
+ }
236
296
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
237
297
  if (migrated) {
238
298
  process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
@@ -260,6 +320,97 @@ async function getMasterKey() {
260
320
  return null;
261
321
  }
262
322
  }
323
+ async function getKeyStorageInfo() {
324
+ if (macKeychainGet()) {
325
+ return {
326
+ kind: "macos-keychain",
327
+ secure: true,
328
+ note: "stored in macOS Keychain via built-in security CLI"
329
+ };
330
+ }
331
+ if (macKeychainGet(LEGACY_SERVICE)) {
332
+ return {
333
+ kind: "macos-keychain",
334
+ secure: true,
335
+ note: "stored in legacy macOS Keychain service exe-mem; next key read migrates it to exe-os"
336
+ };
337
+ }
338
+ if (linuxSecretGet()) {
339
+ return {
340
+ kind: "linux-secret-service",
341
+ secure: true,
342
+ note: "stored in Linux Secret Service via secret-tool"
343
+ };
344
+ }
345
+ if (linuxSecretGet(LEGACY_SERVICE)) {
346
+ return {
347
+ kind: "linux-secret-service",
348
+ secure: true,
349
+ note: "stored in legacy Linux Secret Service service exe-mem; next key read migrates it to exe-os"
350
+ };
351
+ }
352
+ const keytar = await tryKeytar();
353
+ if (keytar) {
354
+ try {
355
+ if (await keytar.getPassword(SERVICE, ACCOUNT)) {
356
+ return {
357
+ kind: "legacy-keytar",
358
+ secure: true,
359
+ note: "stored in legacy keytar backend; will migrate to native keychain when possible"
360
+ };
361
+ }
362
+ if (await keytar.getPassword(LEGACY_SERVICE, ACCOUNT)) {
363
+ return {
364
+ kind: "legacy-keytar",
365
+ secure: true,
366
+ note: "stored in legacy keytar service exe-mem; will migrate to native exe-os keychain when possible"
367
+ };
368
+ }
369
+ } catch {
370
+ }
371
+ }
372
+ const keyPath = getKeyPath();
373
+ if (!existsSync(keyPath)) {
374
+ return {
375
+ kind: "missing",
376
+ secure: false,
377
+ path: keyPath,
378
+ note: "no key found in OS keychain, legacy keytar, or file fallback"
379
+ };
380
+ }
381
+ try {
382
+ const content = (await readFile(keyPath, "utf-8")).trim();
383
+ if (content.startsWith(ENCRYPTED_PREFIX)) {
384
+ return {
385
+ kind: "encrypted-file",
386
+ secure: true,
387
+ path: keyPath,
388
+ note: "stored in machine-bound encrypted file fallback"
389
+ };
390
+ }
391
+ if (isRootOnlyTrustedServerKeyFile(keyPath)) {
392
+ return {
393
+ kind: "server-secret-file",
394
+ secure: true,
395
+ path: keyPath,
396
+ note: "stored as root-only trusted server secret file"
397
+ };
398
+ }
399
+ return {
400
+ kind: "plaintext-file",
401
+ secure: false,
402
+ path: keyPath,
403
+ note: "stored in legacy plaintext file; reading it will migrate or encrypt it"
404
+ };
405
+ } catch {
406
+ return {
407
+ kind: "missing",
408
+ secure: false,
409
+ path: keyPath,
410
+ note: "key file exists but could not be read"
411
+ };
412
+ }
413
+ }
263
414
  async function setMasterKey(key) {
264
415
  const b64 = key.toString("base64");
265
416
  if (macKeychainSet(b64) || linuxSecretSet(b64)) {
@@ -285,10 +436,13 @@ async function setMasterKey(key) {
285
436
  async function deleteMasterKey() {
286
437
  macKeychainDelete();
287
438
  linuxSecretDelete();
439
+ macKeychainDelete(LEGACY_SERVICE);
440
+ linuxSecretDelete(LEGACY_SERVICE);
288
441
  const keytar = await tryKeytar();
289
442
  if (keytar) {
290
443
  try {
291
444
  await keytar.deletePassword(SERVICE, ACCOUNT);
445
+ await keytar.deletePassword(LEGACY_SERVICE, ACCOUNT);
292
446
  } catch {
293
447
  }
294
448
  }
@@ -326,12 +480,14 @@ async function importMnemonic(mnemonic) {
326
480
  const entropy = mnemonicToEntropy(trimmed);
327
481
  return Buffer.from(entropy, "hex");
328
482
  }
329
- var SERVICE, ACCOUNT, ENCRYPTED_PREFIX;
483
+ var SERVICE, LEGACY_SERVICE, ACCOUNT, linuxSecretAvailability, ENCRYPTED_PREFIX;
330
484
  var init_keychain = __esm({
331
485
  "src/lib/keychain.ts"() {
332
486
  "use strict";
333
- SERVICE = "exe-mem";
487
+ SERVICE = "exe-os";
488
+ LEGACY_SERVICE = "exe-mem";
334
489
  ACCOUNT = "master-key";
490
+ linuxSecretAvailability = null;
335
491
  ENCRYPTED_PREFIX = "enc:";
336
492
  }
337
493
  });
@@ -479,6 +635,10 @@ async function loadConfig() {
479
635
  if (config.dbPath.startsWith("~")) {
480
636
  config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
481
637
  }
638
+ const envDbPath = path2.join(dir, "memories.db");
639
+ if (process.env.EXE_OS_DIR && config.dbPath !== envDbPath && !existsSync3(config.dbPath) && existsSync3(envDbPath)) {
640
+ config.dbPath = envDbPath;
641
+ }
482
642
  return config;
483
643
  } catch {
484
644
  return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
@@ -1370,7 +1530,7 @@ import net from "net";
1370
1530
  import os5 from "os";
1371
1531
  import { spawn } from "child_process";
1372
1532
  import { randomUUID } from "crypto";
1373
- import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
1533
+ import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync as statSync2 } from "fs";
1374
1534
  import path6 from "path";
1375
1535
  import { fileURLToPath as fileURLToPath2 } from "url";
1376
1536
  function handleData(chunk) {
@@ -1520,7 +1680,7 @@ function acquireSpawnLock() {
1520
1680
  return true;
1521
1681
  } catch {
1522
1682
  try {
1523
- const stat = statSync(SPAWN_LOCK_PATH);
1683
+ const stat = statSync2(SPAWN_LOCK_PATH);
1524
1684
  if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
1525
1685
  try {
1526
1686
  unlinkSync2(SPAWN_LOCK_PATH);
@@ -1681,7 +1841,7 @@ function killAndRespawnDaemon() {
1681
1841
  }
1682
1842
  function isDaemonTooYoung() {
1683
1843
  try {
1684
- const stat = statSync(PID_PATH);
1844
+ const stat = statSync2(PID_PATH);
1685
1845
  return Date.now() - stat.mtimeMs < MIN_DAEMON_AGE_MS;
1686
1846
  } catch {
1687
1847
  return false;
@@ -2092,6 +2252,9 @@ function getClient() {
2092
2252
  if (_daemonClient && _daemonClient._isDaemonActive()) {
2093
2253
  return _daemonClient;
2094
2254
  }
2255
+ if (!_resilientClient) {
2256
+ return _adapterClient;
2257
+ }
2095
2258
  return _resilientClient;
2096
2259
  }
2097
2260
  async function initDaemonClient() {
@@ -3124,6 +3287,127 @@ async function ensureSchema() {
3124
3287
  VALUES (new.rowid, new.content, new.subject, new.predicate, new.object);
3125
3288
  END;
3126
3289
  `);
3290
+ await client.executeMultiple(`
3291
+ CREATE TABLE IF NOT EXISTS agent_sessions (
3292
+ id TEXT PRIMARY KEY,
3293
+ agent_id TEXT NOT NULL,
3294
+ project_name TEXT,
3295
+ started_at TEXT NOT NULL,
3296
+ last_event_at TEXT NOT NULL,
3297
+ event_count INTEGER NOT NULL DEFAULT 0,
3298
+ properties TEXT DEFAULT '{}'
3299
+ );
3300
+
3301
+ CREATE INDEX IF NOT EXISTS idx_agent_sessions_agent_time
3302
+ ON agent_sessions(agent_id, started_at);
3303
+
3304
+ CREATE TABLE IF NOT EXISTS agent_goals (
3305
+ id TEXT PRIMARY KEY,
3306
+ statement TEXT NOT NULL,
3307
+ owner_agent_id TEXT,
3308
+ project_name TEXT,
3309
+ status TEXT NOT NULL DEFAULT 'open',
3310
+ priority INTEGER NOT NULL DEFAULT 5,
3311
+ success_criteria TEXT,
3312
+ parent_goal_id TEXT,
3313
+ due_at TEXT,
3314
+ achieved_at TEXT,
3315
+ supersedes_id TEXT,
3316
+ created_at TEXT NOT NULL,
3317
+ updated_at TEXT NOT NULL,
3318
+ source_memory_id TEXT
3319
+ );
3320
+
3321
+ CREATE INDEX IF NOT EXISTS idx_agent_goals_project_status
3322
+ ON agent_goals(project_name, status, priority);
3323
+
3324
+ CREATE TABLE IF NOT EXISTS agent_events (
3325
+ id TEXT PRIMARY KEY,
3326
+ event_type TEXT NOT NULL,
3327
+ occurred_at TEXT NOT NULL,
3328
+ sequence_index INTEGER NOT NULL,
3329
+ actor_agent_id TEXT,
3330
+ agent_role TEXT,
3331
+ project_name TEXT,
3332
+ session_id TEXT,
3333
+ task_id TEXT,
3334
+ goal_id TEXT,
3335
+ parent_event_id TEXT,
3336
+ intention TEXT,
3337
+ outcome TEXT,
3338
+ evidence_memory_id TEXT,
3339
+ impact TEXT,
3340
+ payload TEXT DEFAULT '{}',
3341
+ created_at TEXT NOT NULL
3342
+ );
3343
+
3344
+ CREATE INDEX IF NOT EXISTS idx_agent_events_time
3345
+ ON agent_events(occurred_at, sequence_index);
3346
+
3347
+ CREATE INDEX IF NOT EXISTS idx_agent_events_session_seq
3348
+ ON agent_events(session_id, sequence_index);
3349
+
3350
+ CREATE INDEX IF NOT EXISTS idx_agent_events_goal_time
3351
+ ON agent_events(goal_id, occurred_at);
3352
+
3353
+ CREATE INDEX IF NOT EXISTS idx_agent_events_memory
3354
+ ON agent_events(evidence_memory_id);
3355
+
3356
+ CREATE TABLE IF NOT EXISTS agent_goal_links (
3357
+ id TEXT PRIMARY KEY,
3358
+ goal_id TEXT NOT NULL,
3359
+ link_type TEXT NOT NULL,
3360
+ target_id TEXT NOT NULL,
3361
+ target_type TEXT NOT NULL,
3362
+ created_at TEXT NOT NULL
3363
+ );
3364
+
3365
+ CREATE INDEX IF NOT EXISTS idx_agent_goal_links_goal
3366
+ ON agent_goal_links(goal_id, target_type);
3367
+
3368
+ CREATE TABLE IF NOT EXISTS agent_semantic_labels (
3369
+ id TEXT PRIMARY KEY,
3370
+ source_memory_id TEXT NOT NULL,
3371
+ event_id TEXT,
3372
+ labeler TEXT NOT NULL,
3373
+ schema_version INTEGER NOT NULL DEFAULT 1,
3374
+ confidence REAL NOT NULL DEFAULT 0,
3375
+ labels TEXT NOT NULL,
3376
+ created_at TEXT NOT NULL,
3377
+ updated_at TEXT NOT NULL
3378
+ );
3379
+
3380
+ CREATE INDEX IF NOT EXISTS idx_agent_semantic_labels_memory
3381
+ ON agent_semantic_labels(source_memory_id, labeler);
3382
+
3383
+ CREATE INDEX IF NOT EXISTS idx_agent_semantic_labels_event
3384
+ ON agent_semantic_labels(event_id);
3385
+
3386
+ CREATE TABLE IF NOT EXISTS agent_reflection_checkpoints (
3387
+ id TEXT PRIMARY KEY,
3388
+ project_name TEXT,
3389
+ session_id TEXT,
3390
+ window_start_at TEXT NOT NULL,
3391
+ window_end_at TEXT NOT NULL,
3392
+ event_count INTEGER NOT NULL DEFAULT 0,
3393
+ goal_count INTEGER NOT NULL DEFAULT 0,
3394
+ success_count INTEGER NOT NULL DEFAULT 0,
3395
+ failure_count INTEGER NOT NULL DEFAULT 0,
3396
+ risk_count INTEGER NOT NULL DEFAULT 0,
3397
+ summary TEXT NOT NULL,
3398
+ learnings TEXT NOT NULL DEFAULT '[]',
3399
+ next_actions TEXT NOT NULL DEFAULT '[]',
3400
+ evidence_event_ids TEXT NOT NULL DEFAULT '[]',
3401
+ confidence REAL NOT NULL DEFAULT 0,
3402
+ created_at TEXT NOT NULL
3403
+ );
3404
+
3405
+ CREATE INDEX IF NOT EXISTS idx_agent_reflection_project_time
3406
+ ON agent_reflection_checkpoints(project_name, window_end_at);
3407
+
3408
+ CREATE INDEX IF NOT EXISTS idx_agent_reflection_session_time
3409
+ ON agent_reflection_checkpoints(session_id, window_end_at);
3410
+ `);
3127
3411
  try {
3128
3412
  await client.execute({
3129
3413
  sql: `ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3`,
@@ -3611,8 +3895,8 @@ async function validateLicense(apiKey, deviceId) {
3611
3895
  }
3612
3896
  function getCacheAgeMs() {
3613
3897
  try {
3614
- const { statSync: statSync5 } = __require("fs");
3615
- const s = statSync5(CACHE_PATH);
3898
+ const { statSync: statSync6 } = __require("fs");
3899
+ const s = statSync6(CACHE_PATH);
3616
3900
  return Date.now() - s.mtimeMs;
3617
3901
  } catch {
3618
3902
  return Infinity;
@@ -4053,7 +4337,7 @@ __export(db_backup_exports, {
4053
4337
  listBackups: () => listBackups,
4054
4338
  rotateBackups: () => rotateBackups
4055
4339
  });
4056
- import { copyFileSync, existsSync as existsSync9, mkdirSync as mkdirSync4, readdirSync, unlinkSync as unlinkSync4, statSync as statSync2 } from "fs";
4340
+ import { copyFileSync, existsSync as existsSync9, mkdirSync as mkdirSync4, readdirSync, unlinkSync as unlinkSync4, statSync as statSync3 } from "fs";
4057
4341
  import path9 from "path";
4058
4342
  function findActiveDb() {
4059
4343
  for (const name of DB_NAMES) {
@@ -4097,7 +4381,7 @@ function rotateBackups(keepDays = DEFAULT_KEEP_DAYS) {
4097
4381
  if (!file.endsWith(".db") && !file.endsWith(".db-wal") && !file.endsWith(".db-shm")) continue;
4098
4382
  const filePath = path9.join(BACKUP_DIR, file);
4099
4383
  try {
4100
- const stat = statSync2(filePath);
4384
+ const stat = statSync3(filePath);
4101
4385
  if (stat.mtimeMs < cutoff) {
4102
4386
  unlinkSync4(filePath);
4103
4387
  deleted++;
@@ -4115,7 +4399,7 @@ function listBackups() {
4115
4399
  const files = readdirSync(BACKUP_DIR).filter((f) => f.endsWith(".db") && !f.endsWith("-wal") && !f.endsWith("-shm"));
4116
4400
  return files.map((name) => {
4117
4401
  const p = path9.join(BACKUP_DIR, name);
4118
- const stat = statSync2(p);
4402
+ const stat = statSync3(p);
4119
4403
  return { path: p, name, size: stat.size, date: stat.mtime };
4120
4404
  }).sort((a, b) => b.date.getTime() - a.date.getTime());
4121
4405
  } catch {
@@ -4148,10 +4432,10 @@ var init_db_backup = __esm({
4148
4432
  // src/lib/cloud-sync.ts
4149
4433
  var cloud_sync_exports = {};
4150
4434
  __export(cloud_sync_exports, {
4151
- CLOUD_RELINK_REQUIRED_MESSAGE: () => CLOUD_RELINK_REQUIRED_MESSAGE,
4435
+ CLOUD_REUPLOAD_REQUIRED_MESSAGE: () => CLOUD_REUPLOAD_REQUIRED_MESSAGE,
4152
4436
  assertSecureEndpoint: () => assertSecureEndpoint,
4153
4437
  buildRosterBlob: () => buildRosterBlob,
4154
- clearCloudRelinkRequired: () => clearCloudRelinkRequired,
4438
+ clearCloudReuploadRequired: () => clearCloudReuploadRequired,
4155
4439
  cloudPull: () => cloudPull,
4156
4440
  cloudPullBehaviors: () => cloudPullBehaviors,
4157
4441
  cloudPullBlob: () => cloudPullBlob,
@@ -4170,14 +4454,16 @@ __export(cloud_sync_exports, {
4170
4454
  cloudPushGraphRAG: () => cloudPushGraphRAG,
4171
4455
  cloudPushRoster: () => cloudPushRoster,
4172
4456
  cloudPushTasks: () => cloudPushTasks,
4457
+ cloudResetMemoryBlobs: () => cloudResetMemoryBlobs,
4173
4458
  cloudSync: () => cloudSync,
4174
- getCloudRelinkRequired: () => getCloudRelinkRequired,
4459
+ getCloudReuploadRequired: () => getCloudReuploadRequired,
4460
+ markCloudReuploadRequired: () => markCloudReuploadRequired,
4175
4461
  mergeConfig: () => mergeConfig,
4176
4462
  mergeRosterFromRemote: () => mergeRosterFromRemote,
4177
4463
  pushToPostgres: () => pushToPostgres,
4178
4464
  recordRosterDeletion: () => recordRosterDeletion
4179
4465
  });
4180
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync10, readdirSync as readdirSync2, mkdirSync as mkdirSync5, appendFileSync, unlinkSync as unlinkSync5, openSync as openSync2, closeSync as closeSync2, statSync as statSync3 } from "fs";
4466
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync10, readdirSync as readdirSync2, mkdirSync as mkdirSync5, appendFileSync, unlinkSync as unlinkSync5, openSync as openSync2, closeSync as closeSync2, statSync as statSync4 } from "fs";
4181
4467
  import crypto4 from "crypto";
4182
4468
  import path10 from "path";
4183
4469
  import { homedir as homedir2 } from "os";
@@ -4224,18 +4510,36 @@ function loadPgClient() {
4224
4510
  const { pathToFileURL: pathToFileURL3 } = await import("url");
4225
4511
  const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
4226
4512
  if (explicitPath) {
4227
- const mod2 = await import(pathToFileURL3(explicitPath).href);
4228
- const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
4229
- if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
4230
- return new Ctor2();
4513
+ const mod = await import(pathToFileURL3(explicitPath).href);
4514
+ const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
4515
+ if (!Ctor) throw new Error(`No PrismaClient at ${explicitPath}`);
4516
+ return new Ctor();
4231
4517
  }
4232
4518
  const exeDbRoot = process.env.EXE_DB_ROOT ?? path10.join(homedir2(), "exe-db");
4233
- const req = createRequire3(path10.join(exeDbRoot, "package.json"));
4234
- const entry = req.resolve("@prisma/client");
4235
- const mod = await import(pathToFileURL3(entry).href);
4236
- const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
4237
- if (!Ctor) throw new Error("No PrismaClient");
4238
- return new Ctor();
4519
+ const packagePath = path10.join(exeDbRoot, "package.json");
4520
+ if (existsSync10(packagePath)) {
4521
+ const req = createRequire3(packagePath);
4522
+ const entry = req.resolve("@prisma/client");
4523
+ const mod = await import(pathToFileURL3(entry).href);
4524
+ const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
4525
+ if (!Ctor) throw new Error("No PrismaClient");
4526
+ return new Ctor();
4527
+ }
4528
+ const { Pool } = await import("pg");
4529
+ const pool = new Pool({ connectionString: process.env.DATABASE_URL });
4530
+ return {
4531
+ async $queryRawUnsafe(query, ...values) {
4532
+ const result = await pool.query(query, values);
4533
+ return result.rows;
4534
+ },
4535
+ async $executeRawUnsafe(query, ...values) {
4536
+ const result = await pool.query(query, values);
4537
+ return result.rowCount ?? 0;
4538
+ },
4539
+ async $disconnect() {
4540
+ await pool.end();
4541
+ }
4542
+ };
4239
4543
  })().catch(() => {
4240
4544
  _pgFailed = true;
4241
4545
  _pgPromise = null;
@@ -4256,7 +4560,7 @@ async function pushToPostgres(records) {
4256
4560
  let inserted = 0;
4257
4561
  for (const rec of records) {
4258
4562
  try {
4259
- await prisma.$executeRawUnsafe(
4563
+ const changed = await prisma.$executeRawUnsafe(
4260
4564
  `INSERT INTO raw.raw_events (id, source, source_id, event_type, payload, metadata, timestamp)
4261
4565
  VALUES (gen_random_uuid(), 'cloud_sync', $1, 'memory', $2::jsonb, $3::jsonb, $4)
4262
4566
  ON CONFLICT (source, source_id, event_type) DO NOTHING`,
@@ -4265,7 +4569,7 @@ async function pushToPostgres(records) {
4265
4569
  JSON.stringify({ agent_id: rec.agent_id, project_name: rec.project_name, tool_name: rec.tool_name }),
4266
4570
  rec.timestamp ? new Date(String(rec.timestamp)) : /* @__PURE__ */ new Date()
4267
4571
  );
4268
- inserted++;
4572
+ inserted += Number(changed ?? 0);
4269
4573
  } catch {
4270
4574
  }
4271
4575
  }
@@ -4370,6 +4674,23 @@ async function cloudPush(records, maxVersion, config) {
4370
4674
  return false;
4371
4675
  }
4372
4676
  }
4677
+ async function cloudResetMemoryBlobs(config) {
4678
+ assertSecureEndpoint(config.endpoint);
4679
+ const resp = await fetchWithRetry(`${config.endpoint}/sync/reset-memory`, {
4680
+ method: "POST",
4681
+ headers: {
4682
+ Authorization: `Bearer ${config.apiKey}`,
4683
+ "Content-Type": "application/json",
4684
+ "X-Device-Id": loadDeviceId()
4685
+ },
4686
+ body: JSON.stringify({ confirm: "LOCAL DB IS SOURCE OF TRUTH" })
4687
+ });
4688
+ if (!resp.ok) {
4689
+ throw new Error(`cloud reset failed: HTTP ${resp.status}`);
4690
+ }
4691
+ const data = await resp.json();
4692
+ return { deleted: Number(data.deleted ?? 0), freedBytes: Number(data.freed_bytes ?? 0) };
4693
+ }
4373
4694
  async function cloudPull(sinceVersion, config) {
4374
4695
  assertSecureEndpoint(config.endpoint);
4375
4696
  try {
@@ -4389,40 +4710,61 @@ async function cloudPull(sinceVersion, config) {
4389
4710
  if (!response.ok) return { records: [], maxVersion: sinceVersion };
4390
4711
  const data = await response.json();
4391
4712
  const allRecords = [];
4392
- for (const { blob } of data.blobs ?? []) {
4713
+ let maxReadableVersion = sinceVersion;
4714
+ let skippedBlobs = 0;
4715
+ for (const { version, blob } of data.blobs ?? []) {
4393
4716
  try {
4394
4717
  const compressed = decryptSyncBlob(blob);
4395
4718
  const json = decompress(compressed).toString("utf8");
4396
4719
  const records = JSON.parse(json);
4397
4720
  allRecords.push(...records);
4721
+ const recordMax = records.reduce((max, rec) => {
4722
+ const v = Number(rec.version ?? 0);
4723
+ return Number.isFinite(v) ? Math.max(max, v) : max;
4724
+ }, 0);
4725
+ const blobVersion = Number(version ?? 0);
4726
+ maxReadableVersion = Math.max(
4727
+ maxReadableVersion,
4728
+ Number.isFinite(blobVersion) ? blobVersion : 0,
4729
+ recordMax
4730
+ );
4398
4731
  } catch {
4732
+ skippedBlobs++;
4399
4733
  continue;
4400
4734
  }
4401
4735
  }
4402
- return { records: allRecords, maxVersion: data.max_version ?? sinceVersion };
4736
+ if (skippedBlobs > 0) {
4737
+ logError(`[cloud-sync] PULL skipped ${skippedBlobs} undecryptable blob(s); pull cursor advanced only to last readable version ${maxReadableVersion}`);
4738
+ }
4739
+ return { records: allRecords, maxVersion: maxReadableVersion };
4403
4740
  } catch (err) {
4404
4741
  logError(`[cloud-sync] PULL FAILED: ${err instanceof Error ? err.message : String(err)}`);
4405
4742
  return { records: [], maxVersion: sinceVersion };
4406
4743
  }
4407
4744
  }
4408
- async function getCloudRelinkRequired(client = getClient()) {
4745
+ async function getCloudReuploadRequired(client = getClient()) {
4409
4746
  try {
4410
4747
  await client.execute("CREATE TABLE IF NOT EXISTS sync_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)");
4411
- const relink = await client.execute("SELECT value FROM sync_meta WHERE key = 'cloud_relink_required' LIMIT 1");
4412
- return String(relink.rows[0]?.value ?? "") === "1";
4748
+ const result = await client.execute("SELECT key, value FROM sync_meta WHERE key IN ('cloud_reupload_required', 'cloud_relink_required')");
4749
+ return result.rows.some((row) => String(row.value ?? "") === "1");
4413
4750
  } catch {
4414
4751
  return false;
4415
4752
  }
4416
4753
  }
4417
- async function clearCloudRelinkRequired(client = getClient()) {
4754
+ async function clearCloudReuploadRequired(client = getClient()) {
4418
4755
  await client.execute("CREATE TABLE IF NOT EXISTS sync_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)");
4756
+ await client.execute("INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('cloud_reupload_required', '0')");
4419
4757
  await client.execute("INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('cloud_relink_required', '0')");
4420
4758
  await client.execute({
4421
- sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('cloud_relinked_at', ?)",
4759
+ sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('cloud_reuploaded_at', ?)",
4422
4760
  args: [(/* @__PURE__ */ new Date()).toISOString()]
4423
4761
  });
4424
4762
  await client.execute("DELETE FROM sync_meta WHERE key IN ('last_cloud_pull_version', 'last_cloud_push_version')");
4425
4763
  }
4764
+ async function markCloudReuploadRequired(client = getClient()) {
4765
+ await client.execute("CREATE TABLE IF NOT EXISTS sync_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)");
4766
+ await client.execute("INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('cloud_reupload_required', '1')");
4767
+ }
4426
4768
  async function cloudSync(config) {
4427
4769
  if (!isSyncCryptoInitialized()) {
4428
4770
  try {
@@ -4444,10 +4786,10 @@ async function cloudSync(config) {
4444
4786
  throw new Error("[cloud-sync] Database not initialized. Call initStore() before cloudSync().");
4445
4787
  }
4446
4788
  try {
4447
- if (await getCloudRelinkRequired(client)) throw new Error(CLOUD_RELINK_REQUIRED_MESSAGE);
4789
+ if (await getCloudReuploadRequired(client)) throw new Error(CLOUD_REUPLOAD_REQUIRED_MESSAGE);
4448
4790
  } catch (err) {
4449
4791
  const msg = err instanceof Error ? err.message : String(err);
4450
- if (msg.includes("Paused after key rotation")) throw err;
4792
+ if (msg === CLOUD_REUPLOAD_REQUIRED_MESSAGE || msg.includes("key rotation")) throw err;
4451
4793
  }
4452
4794
  try {
4453
4795
  const { getRawClient: getRawClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
@@ -4705,7 +5047,7 @@ async function cloudSync(config) {
4705
5047
  const { getLatestBackup: getLatestBackup2 } = await Promise.resolve().then(() => (init_db_backup(), db_backup_exports));
4706
5048
  const latestBackup = getLatestBackup2();
4707
5049
  if (latestBackup) {
4708
- const backupSize = statSync3(latestBackup).size;
5050
+ const backupSize = statSync4(latestBackup).size;
4709
5051
  const MAX_CLOUD_BACKUP_BYTES = 50 * 1024 * 1024;
4710
5052
  if (backupSize <= MAX_CLOUD_BACKUP_BYTES) {
4711
5053
  const backupData = readFileSync7(latestBackup);
@@ -5376,7 +5718,7 @@ async function cloudPullDocuments(config) {
5376
5718
  }
5377
5719
  return { pulled };
5378
5720
  }
5379
- var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, _pgPromise, _pgFailed, CLOUD_RELINK_REQUIRED_MESSAGE, ROSTER_DELETIONS_PATH;
5721
+ var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, _pgPromise, _pgFailed, CLOUD_REUPLOAD_REQUIRED_MESSAGE, ROSTER_DELETIONS_PATH;
5380
5722
  var init_cloud_sync = __esm({
5381
5723
  "src/lib/cloud-sync.ts"() {
5382
5724
  "use strict";
@@ -5395,7 +5737,7 @@ var init_cloud_sync = __esm({
5395
5737
  LOCK_STALE_MS = 3e4;
5396
5738
  _pgPromise = null;
5397
5739
  _pgFailed = false;
5398
- CLOUD_RELINK_REQUIRED_MESSAGE = "[cloud-sync] Paused after key rotation. Run `exe-os cloud relink --dry-run` for the safe relink checklist.";
5740
+ 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.";
5399
5741
  ROSTER_DELETIONS_PATH = path10.join(EXE_AI_DIR, "roster-deletions.json");
5400
5742
  }
5401
5743
  });
@@ -5665,7 +6007,7 @@ __export(shard_manager_exports, {
5665
6007
  shardExists: () => shardExists
5666
6008
  });
5667
6009
  import path11 from "path";
5668
- import { existsSync as existsSync11, mkdirSync as mkdirSync6, readdirSync as readdirSync3, renameSync as renameSync3, statSync as statSync4 } from "fs";
6010
+ import { existsSync as existsSync11, mkdirSync as mkdirSync6, readdirSync as readdirSync3, renameSync as renameSync3, statSync as statSync5 } from "fs";
5669
6011
  import { createClient as createClient2 } from "@libsql/client";
5670
6012
  function initShardManager(encryptionKey) {
5671
6013
  _encryptionKey = encryptionKey;
@@ -5729,7 +6071,7 @@ async function auditShardHealth(options = {}) {
5729
6071
  const shards = [];
5730
6072
  for (const name of names) {
5731
6073
  const dbPath = path11.join(SHARDS_DIR, `${name}.db`);
5732
- const stat = statSync4(dbPath);
6074
+ const stat = statSync5(dbPath);
5733
6075
  const item = {
5734
6076
  name,
5735
6077
  path: dbPath,
@@ -5982,7 +6324,7 @@ async function getReadyShardClient(projectName) {
5982
6324
  _shardLastAccess.delete(safeName);
5983
6325
  const dbPath = path11.join(SHARDS_DIR, `${safeName}.db`);
5984
6326
  if (existsSync11(dbPath)) {
5985
- const stat = statSync4(dbPath);
6327
+ const stat = statSync5(dbPath);
5986
6328
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
5987
6329
  const archivedPath = path11.join(SHARDS_DIR, `${safeName}.db.broken-${stamp}`);
5988
6330
  renameSync3(dbPath, archivedPath);
@@ -6489,6 +6831,274 @@ var init_memory_cards = __esm({
6489
6831
  }
6490
6832
  });
6491
6833
 
6834
+ // src/lib/agentic-ontology.ts
6835
+ var agentic_ontology_exports = {};
6836
+ __export(agentic_ontology_exports, {
6837
+ clean: () => clean,
6838
+ extractGoalCandidates: () => extractGoalCandidates,
6839
+ inferIntention: () => inferIntention,
6840
+ inferOntologyEventType: () => inferOntologyEventType,
6841
+ inferOutcome: () => inferOutcome,
6842
+ inferSemanticLabel: () => inferSemanticLabel,
6843
+ insertOntologyForBatch: () => insertOntologyForBatch,
6844
+ insertOntologyForMemory: () => insertOntologyForMemory,
6845
+ ontologyPayload: () => ontologyPayload,
6846
+ stableId: () => stableId2
6847
+ });
6848
+ import { createHash as createHash3 } from "crypto";
6849
+ function stableId2(...parts) {
6850
+ return createHash3("sha256").update(parts.map((p) => String(p ?? "")).join("::")).digest("hex").slice(0, 32);
6851
+ }
6852
+ function clean(text, max = 240) {
6853
+ return text.replace(/\u0000/g, "").replace(/```[\s\S]*?```/g, " ").replace(/\s+/g, " ").trim().slice(0, max);
6854
+ }
6855
+ function inferOntologyEventType(row) {
6856
+ const lower = row.raw_text.toLowerCase();
6857
+ if (row.has_error) return "error";
6858
+ if (/\b(done|complete|completed|fixed|resolved|shipped|deployed|pushed|published)\b/.test(lower)) return "milestone";
6859
+ if (/\b(blocked|failed|error|bug|regression|broken)\b/.test(lower)) return "problem";
6860
+ if (/\b(decided|decision|adr|we chose|approved|rejected)\b/.test(lower)) return "decision";
6861
+ if (/\b(goal|need to|we need|want to|trying to|objective)\b/.test(lower)) return "goal_signal";
6862
+ if (["Bash", "Read", "Edit", "Write", "Grep", "Glob"].includes(row.tool_name)) return "tool_action";
6863
+ if (row.tool_name.startsWith("memory_card")) return "memory_card";
6864
+ return "memory_observation";
6865
+ }
6866
+ function inferIntention(row) {
6867
+ if (row.intent) return clean(row.intent, 220);
6868
+ const text = clean(row.raw_text, 1e3);
6869
+ const patterns = [
6870
+ /(?:we need to|need to|let'?s|i want to|we should|goal is to|objective is to|trying to)\s+([^.!?\n]{8,220})/i,
6871
+ /(?:so that|in order to)\s+([^.!?\n]{8,220})/i,
6872
+ /(?:task|plan):\s*([^.!?\n]{8,220})/i
6873
+ ];
6874
+ for (const p of patterns) {
6875
+ const m = text.match(p);
6876
+ if (m?.[1]) return clean(m[1], 220);
6877
+ }
6878
+ if (["Bash", "Read", "Edit", "Write", "Grep", "Glob"].includes(row.tool_name)) {
6879
+ return `${row.tool_name} during ${row.project_name}`;
6880
+ }
6881
+ return null;
6882
+ }
6883
+ function inferOutcome(row) {
6884
+ if (row.outcome) return clean(row.outcome, 220);
6885
+ if (row.has_error) return "error";
6886
+ const lower = row.raw_text.toLowerCase();
6887
+ if (/\b(done|complete|completed|fixed|resolved|shipped|deployed|pushed|published|passed)\b/.test(lower)) return "success_signal";
6888
+ if (/\b(blocked|failed|error|regression|broken|not working|could not)\b/.test(lower)) return "failure_signal";
6889
+ if (/\b(warning|risk|concern|caveat)\b/.test(lower)) return "risk_signal";
6890
+ return null;
6891
+ }
6892
+ function extractGoalCandidates(row) {
6893
+ const text = clean(row.raw_text, 1600);
6894
+ const patterns = [
6895
+ /(?:we need to|need to|i want to|we should|goal is to|objective is to|trying to|let'?s)\s+([^.!?\n]{12,220})/gi,
6896
+ /(?:success means|success criteria|so that)\s+([^.!?\n]{12,220})/gi
6897
+ ];
6898
+ const out = [];
6899
+ for (const pattern of patterns) {
6900
+ for (const m of text.matchAll(pattern)) {
6901
+ const candidate = clean(m[1] ?? "", 220);
6902
+ if (candidate.length >= 12 && !out.some((x) => x.toLowerCase() === candidate.toLowerCase())) out.push(candidate);
6903
+ if (out.length >= 3) return out;
6904
+ }
6905
+ }
6906
+ return out;
6907
+ }
6908
+ function uniq(values, max = 6) {
6909
+ const out = [];
6910
+ for (const value of values.map((v) => clean(v, 220)).filter(Boolean)) {
6911
+ if (!out.some((x) => x.toLowerCase() === value.toLowerCase())) out.push(value);
6912
+ if (out.length >= max) break;
6913
+ }
6914
+ return out;
6915
+ }
6916
+ function extractMatches(text, patterns, max = 5) {
6917
+ const out = [];
6918
+ for (const pattern of patterns) {
6919
+ for (const match of text.matchAll(pattern)) {
6920
+ const value = match[1] ?? match[0];
6921
+ if (value) out.push(value);
6922
+ if (out.length >= max) return uniq(out, max);
6923
+ }
6924
+ }
6925
+ return uniq(out, max);
6926
+ }
6927
+ function inferSemanticLabel(row) {
6928
+ const text = clean(row.raw_text, 2400);
6929
+ const eventType = inferOntologyEventType(row);
6930
+ const intention = inferIntention(row);
6931
+ const outcome = inferOutcome(row);
6932
+ const goals = extractGoalCandidates(row);
6933
+ const milestones = extractMatches(text, [
6934
+ /\b(?:completed|finished|fixed|resolved|shipped|deployed|published|pushed|passed)\b([^.!?\n]{0,180})/gi,
6935
+ /(?:milestone|done):\s*([^.!?\n]{8,220})/gi
6936
+ ]);
6937
+ const problems = extractMatches(text, [
6938
+ /\b(?:blocked by|failed because|bug|regression|broken|not working|error)\b([^.!?\n]{0,180})/gi,
6939
+ /(?:problem|issue|risk):\s*([^.!?\n]{8,220})/gi
6940
+ ]);
6941
+ const decisions = extractMatches(text, [
6942
+ /(?:decided|decision|adr|we chose|approved|rejected)\s+([^.!?\n]{8,220})/gi
6943
+ ]);
6944
+ const temporalAnchors = extractMatches(text, [
6945
+ /\b(\d{4}-\d{2}-\d{2}(?:[T ][0-9:.+-Z]+)?)\b/g,
6946
+ /\b(today|yesterday|tomorrow|this week|next week|last week|morning|afternoon|tonight)\b/gi
6947
+ ], 8);
6948
+ const nextActions = extractMatches(text, [
6949
+ /(?:next|todo|follow[- ]?up|remaining|need to)\s*:?\s*([^.!?\n]{8,220})/gi
6950
+ ]);
6951
+ const actors = uniq([
6952
+ row.agent_id,
6953
+ ...extractMatches(text, [/\b(?:agent|employee|owner|assignee)[:= ]+([a-zA-Z][a-zA-Z0-9_-]{1,40})/gi], 5)
6954
+ ], 6);
6955
+ const successSignals = milestones.length ? milestones : outcome === "success_signal" ? [clean(text, 180)] : [];
6956
+ const failureSignals = problems.length ? problems : outcome === "failure_signal" || row.has_error ? [clean(text, 180)] : [];
6957
+ const impact = successSignals.length && failureSignals.length ? "mixed" : failureSignals.length ? "negative" : successSignals.length ? "positive" : "neutral";
6958
+ const signalCount = goals.length + milestones.length + problems.length + decisions.length + nextActions.length;
6959
+ return {
6960
+ labeler: "deterministic",
6961
+ schemaVersion: 1,
6962
+ eventType,
6963
+ intention,
6964
+ outcome,
6965
+ impact,
6966
+ confidence: Math.min(0.95, 0.45 + signalCount * 0.08 + (intention ? 0.1 : 0) + (outcome ? 0.1 : 0)),
6967
+ goals,
6968
+ milestones,
6969
+ problems,
6970
+ decisions,
6971
+ actors,
6972
+ temporalAnchors,
6973
+ successSignals,
6974
+ failureSignals,
6975
+ nextActions,
6976
+ summary: clean(text, 280)
6977
+ };
6978
+ }
6979
+ function ontologyPayload(row) {
6980
+ const semantic = inferSemanticLabel(row);
6981
+ return {
6982
+ tool_name: row.tool_name,
6983
+ memory_version: row.version ?? null,
6984
+ domain: row.domain ?? null,
6985
+ trajectory: row.trajectory ? safeJson(row.trajectory) : null,
6986
+ semantic
6987
+ };
6988
+ }
6989
+ function safeJson(value) {
6990
+ try {
6991
+ return JSON.parse(value);
6992
+ } catch {
6993
+ return value.slice(0, 1e3);
6994
+ }
6995
+ }
6996
+ async function resolveClient(client) {
6997
+ if (client) return client;
6998
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
6999
+ return getClient2();
7000
+ }
7001
+ async function insertOntologyForMemory(row, client) {
7002
+ const db = await resolveClient(client);
7003
+ const occurredAt = row.timestamp;
7004
+ const sequence = Number(row.version ?? 0) || Math.floor(new Date(occurredAt).getTime() / 1e3);
7005
+ const eventType = inferOntologyEventType(row);
7006
+ const intention = inferIntention(row);
7007
+ const outcome = inferOutcome(row);
7008
+ const eventId = stableId2("event", row.id);
7009
+ const now = (/* @__PURE__ */ new Date()).toISOString();
7010
+ await db.execute({
7011
+ sql: `INSERT INTO agent_sessions (id, agent_id, project_name, started_at, last_event_at, event_count, properties)
7012
+ VALUES (?, ?, ?, ?, ?, 1, ?)
7013
+ ON CONFLICT(id) DO UPDATE SET last_event_at = MAX(last_event_at, excluded.last_event_at),
7014
+ event_count = event_count + 1`,
7015
+ args: [row.session_id, row.agent_id, row.project_name, occurredAt, occurredAt, JSON.stringify({ agent_role: row.agent_role })]
7016
+ });
7017
+ await db.execute({
7018
+ sql: `INSERT OR IGNORE INTO agent_events
7019
+ (id, event_type, occurred_at, sequence_index, actor_agent_id, agent_role, project_name,
7020
+ session_id, task_id, goal_id, parent_event_id, intention, outcome, evidence_memory_id,
7021
+ impact, payload, created_at)
7022
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, NULL, ?, ?, ?, ?, ?, ?)`,
7023
+ args: [
7024
+ eventId,
7025
+ eventType,
7026
+ occurredAt,
7027
+ sequence,
7028
+ row.agent_id,
7029
+ row.agent_role,
7030
+ row.project_name,
7031
+ row.session_id,
7032
+ row.task_id ?? null,
7033
+ intention,
7034
+ outcome,
7035
+ row.id,
7036
+ row.has_error ? "negative" : outcome === "success_signal" ? "positive" : "neutral",
7037
+ JSON.stringify(ontologyPayload(row)),
7038
+ now
7039
+ ]
7040
+ });
7041
+ const semantic = inferSemanticLabel(row);
7042
+ await db.execute({
7043
+ sql: `INSERT INTO agent_semantic_labels
7044
+ (id, source_memory_id, event_id, labeler, schema_version, confidence, labels, created_at, updated_at)
7045
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
7046
+ ON CONFLICT(id) DO UPDATE SET confidence = excluded.confidence,
7047
+ labels = excluded.labels, updated_at = excluded.updated_at`,
7048
+ args: [
7049
+ stableId2("semantic", row.id, semantic.labeler, semantic.schemaVersion),
7050
+ row.id,
7051
+ eventId,
7052
+ semantic.labeler,
7053
+ semantic.schemaVersion,
7054
+ semantic.confidence,
7055
+ JSON.stringify(semantic),
7056
+ now,
7057
+ now
7058
+ ]
7059
+ });
7060
+ for (const statement of extractGoalCandidates(row)) {
7061
+ const goalId = stableId2("goal", row.project_name, statement.toLowerCase());
7062
+ await db.execute({
7063
+ sql: `INSERT INTO agent_goals
7064
+ (id, statement, owner_agent_id, project_name, status, priority, success_criteria,
7065
+ parent_goal_id, due_at, achieved_at, supersedes_id, created_at, updated_at, source_memory_id)
7066
+ VALUES (?, ?, ?, ?, 'open', 5, NULL, NULL, NULL, NULL, NULL, ?, ?, ?)
7067
+ ON CONFLICT(id) DO UPDATE SET updated_at = excluded.updated_at`,
7068
+ args: [goalId, statement, row.agent_id, row.project_name, now, now, row.id]
7069
+ });
7070
+ await db.execute({
7071
+ sql: `INSERT OR IGNORE INTO agent_goal_links
7072
+ (id, goal_id, link_type, target_id, target_type, created_at)
7073
+ VALUES (?, ?, 'evidence', ?, 'memory', ?)`,
7074
+ args: [stableId2("goal_link", goalId, row.id, "memory"), goalId, row.id, now]
7075
+ });
7076
+ await db.execute({
7077
+ sql: `INSERT OR IGNORE INTO agent_goal_links
7078
+ (id, goal_id, link_type, target_id, target_type, created_at)
7079
+ VALUES (?, ?, 'event', ?, 'event', ?)`,
7080
+ args: [stableId2("goal_link", goalId, eventId, "event"), goalId, eventId, now]
7081
+ });
7082
+ }
7083
+ }
7084
+ async function insertOntologyForBatch(rows, client) {
7085
+ const db = await resolveClient(client);
7086
+ let count = 0;
7087
+ for (const row of rows) {
7088
+ try {
7089
+ await insertOntologyForMemory(row, db);
7090
+ count++;
7091
+ } catch {
7092
+ }
7093
+ }
7094
+ return count;
7095
+ }
7096
+ var init_agentic_ontology = __esm({
7097
+ "src/lib/agentic-ontology.ts"() {
7098
+ "use strict";
7099
+ }
7100
+ });
7101
+
6492
7102
  // src/lib/store.ts
6493
7103
  var store_exports = {};
6494
7104
  __export(store_exports, {
@@ -6832,6 +7442,11 @@ async function flushBatch() {
6832
7442
  await insertMemoryCardsForBatch2(batch);
6833
7443
  } catch {
6834
7444
  }
7445
+ try {
7446
+ const { insertOntologyForBatch: insertOntologyForBatch2 } = await Promise.resolve().then(() => (init_agentic_ontology(), agentic_ontology_exports));
7447
+ await insertOntologyForBatch2(batch);
7448
+ } catch {
7449
+ }
6835
7450
  schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
6836
7451
  _pendingRecords.splice(0, batch.length);
6837
7452
  try {
@@ -7201,8 +7816,8 @@ function ask(rl, question) {
7201
7816
  rl.question(question, (answer) => resolve(answer.trim()));
7202
7817
  });
7203
7818
  }
7204
- function shouldRunRelinkMutation(options) {
7205
- return options.relinkRequired && !options.dryRun && (options.force || options.interactive);
7819
+ function shouldRunReuploadMutation(options) {
7820
+ return options.reuploadRequired && !options.dryRun && (options.force || options.interactive);
7206
7821
  }
7207
7822
  async function runCloudSyncOnce() {
7208
7823
  const { initStore: initStore2 } = await Promise.resolve().then(() => (init_store(), store_exports));
@@ -7240,12 +7855,12 @@ async function setupMode() {
7240
7855
  console.log(" EXE CLOUD SETUP");
7241
7856
  console.log(BAR);
7242
7857
  console.log("");
7243
- console.log("Step 1: Encryption Key");
7858
+ console.log("Step 1: Memory encryption key");
7244
7859
  const existingKey = await getMasterKey();
7245
7860
  if (existingKey) {
7246
- console.log(" \u2713 Master key already present. Skipping.\n");
7861
+ console.log(" \u2713 This device already has its memory encryption key. Skipping.\n");
7247
7862
  } else {
7248
- const hasPhrase = await ask(rl, " Do you have a 24-word recovery phrase from another device? (y/n) ");
7863
+ const hasPhrase = await ask(rl, " Do you have a 24-word recovery phrase from another Exe OS device? (y/n) ");
7249
7864
  if (hasPhrase.toLowerCase().startsWith("y")) {
7250
7865
  const mnemonic = await ask(rl, " Paste your 24 words: ");
7251
7866
  try {
@@ -7258,8 +7873,9 @@ async function setupMode() {
7258
7873
  process.exit(1);
7259
7874
  }
7260
7875
  } else {
7261
- console.log(" No recovery phrase \u2014 run /exe-setup on your first device to generate one.");
7262
- console.log(" Then use /exe-cloud link on that device to get the phrase.\n");
7876
+ console.log(" No recovery phrase yet.");
7877
+ console.log(" On your first device, run: exe-os setup");
7878
+ console.log(" Then reveal the phrase there with: exe-os cloud link --show-full\n");
7263
7879
  rl.close();
7264
7880
  return;
7265
7881
  }
@@ -7287,9 +7903,9 @@ async function setupMode() {
7287
7903
  }
7288
7904
  }
7289
7905
  } else {
7290
- const apiKey = await ask(rl, " Paste your API key (exe_sk_...): ");
7906
+ const apiKey = await ask(rl, " Paste your Exe Cloud API key (exe_sk_...): ");
7291
7907
  if (!apiKey) {
7292
- console.log(" Skipped. Use /exe-settings to configure later.");
7908
+ console.log(" Skipped. You can configure cloud later with: exe-os cloud");
7293
7909
  rl.close();
7294
7910
  return;
7295
7911
  }
@@ -7379,7 +7995,7 @@ async function linkMode() {
7379
7995
  console.log(" EXE CLOUD \u2014 DEVICE SYNC INFO");
7380
7996
  console.log(BAR);
7381
7997
  console.log("");
7382
- console.log("Run /exe-cloud on the new device and paste these when prompted:\n");
7998
+ console.log("On the new device, run `exe-os cloud` and paste these when prompted:\n");
7383
7999
  console.log(" 24-word recovery phrase:");
7384
8000
  if (showFull) {
7385
8001
  console.log(` ${mnemonic}
@@ -7412,6 +8028,38 @@ async function linkMode() {
7412
8028
  async function statusMode() {
7413
8029
  const key = await getMasterKey();
7414
8030
  const config = await loadConfig();
8031
+ let reuploadRequired = false;
8032
+ let localMemoryVersion = 0;
8033
+ let lastPushedMemoryVersion = 0;
8034
+ let pendingMemoryCount = 0;
8035
+ let lastPullVersion = 0;
8036
+ let cloudReuploadedAt = null;
8037
+ try {
8038
+ const { initStore: initStore2 } = await Promise.resolve().then(() => (init_store(), store_exports));
8039
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
8040
+ const { getCloudReuploadRequired: getCloudReuploadRequired2 } = await Promise.resolve().then(() => (init_cloud_sync(), cloud_sync_exports));
8041
+ await initStore2({ lightweight: true });
8042
+ const client = getClient2();
8043
+ reuploadRequired = await getCloudReuploadRequired2(client);
8044
+ const maxVersion = await client.execute("SELECT COALESCE(MAX(version), 0) as version FROM memories WHERE scope IS NULL OR scope != 'personal'");
8045
+ localMemoryVersion = Number(maxVersion.rows[0]?.version ?? 0);
8046
+ const meta = await client.execute("SELECT key, value FROM sync_meta WHERE key IN ('last_cloud_push_version', 'last_cloud_pull_version', 'cloud_reuploaded_at', 'cloud_relinked_at')");
8047
+ for (const row of meta.rows) {
8048
+ const keyName = String(row.key ?? "");
8049
+ const value = String(row.value ?? "");
8050
+ if (keyName === "last_cloud_push_version") lastPushedMemoryVersion = Number(value || 0);
8051
+ if (keyName === "last_cloud_pull_version") lastPullVersion = Number(value || 0);
8052
+ if (keyName === "cloud_reuploaded_at" || keyName === "cloud_relinked_at") cloudReuploadedAt = value;
8053
+ }
8054
+ const pending = await client.execute({
8055
+ sql: "SELECT COUNT(*) as cnt FROM memories WHERE (scope IS NULL OR scope != 'personal') AND version > ?",
8056
+ args: [lastPushedMemoryVersion]
8057
+ });
8058
+ pendingMemoryCount = Number(pending.rows[0]?.cnt ?? 0);
8059
+ } catch {
8060
+ } finally {
8061
+ await disposeCloudCliResources();
8062
+ }
7415
8063
  console.log(BAR);
7416
8064
  console.log(" EXE CLOUD STATUS");
7417
8065
  console.log(BAR);
@@ -7424,8 +8072,26 @@ async function statusMode() {
7424
8072
  }
7425
8073
  if (config.cloud?.apiKey) {
7426
8074
  const masked = config.cloud.apiKey.slice(0, 10) + "..." + config.cloud.apiKey.slice(-4);
7427
- console.log(` Cloud sync: \u2713 Exe Cloud (${masked})`);
8075
+ console.log(` Cloud account: connected to Exe Cloud (${masked})`);
7428
8076
  console.log(` Endpoint: ${config.cloud.endpoint}`);
8077
+ console.log(" Status check: read-only; no data was uploaded or downloaded");
8078
+ console.log("");
8079
+ if (reuploadRequired) {
8080
+ console.log(" Sync status: NOT SYNCING \u2014 blocked for safety");
8081
+ console.log(" Why: this device changed its memory encryption key");
8082
+ console.log(" Required fix: run `exe-os cloud reupload`");
8083
+ console.log(" After reupload: automatic background sync will resume");
8084
+ } else if (pendingMemoryCount > 0) {
8085
+ console.log(" Sync status: SYNC ENABLED \u2014 waiting for background sync");
8086
+ console.log(` Pending upload: ${pendingMemoryCount} local memor${pendingMemoryCount === 1 ? "y" : "ies"}`);
8087
+ console.log(" Manual option: run `exe-os cloud sync` to sync now");
8088
+ } else {
8089
+ console.log(" Sync status: SYNCED \u2014 no local memory changes waiting to upload");
8090
+ console.log(" Auto sync: enabled \u2014 daemon syncs in the background");
8091
+ }
8092
+ console.log("");
8093
+ console.log(` Memory cursor: local=${localMemoryVersion} last pushed=${lastPushedMemoryVersion} last pulled=${lastPullVersion}`);
8094
+ if (cloudReuploadedAt) console.log(` Last re-upload: ${cloudReuploadedAt}`);
7429
8095
  } else if (config.turso?.url) {
7430
8096
  console.log(` Cloud sync: \u2713 libSQL (legacy) (${config.turso.url})`);
7431
8097
  } else {
@@ -7434,41 +8100,45 @@ async function statusMode() {
7434
8100
  console.log("");
7435
8101
  console.log(BAR);
7436
8102
  }
7437
- async function relinkMode() {
8103
+ async function reuploadMode() {
7438
8104
  const dryRun = process.argv.includes("--dry-run");
7439
8105
  const force = process.argv.includes("--local-db-is-source-of-truth") || process.argv.includes("--yes");
7440
8106
  const { initStore: initStore2 } = await Promise.resolve().then(() => (init_store(), store_exports));
7441
8107
  const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
7442
- const { getCloudRelinkRequired: getCloudRelinkRequired2, clearCloudRelinkRequired: clearCloudRelinkRequired2 } = await Promise.resolve().then(() => (init_cloud_sync(), cloud_sync_exports));
8108
+ const { getCloudReuploadRequired: getCloudReuploadRequired2, clearCloudReuploadRequired: clearCloudReuploadRequired2, markCloudReuploadRequired: markCloudReuploadRequired2, cloudResetMemoryBlobs: cloudResetMemoryBlobs2 } = await Promise.resolve().then(() => (init_cloud_sync(), cloud_sync_exports));
7443
8109
  await initStore2({ lightweight: true });
7444
8110
  try {
7445
8111
  const client = getClient2();
7446
- const required = await getCloudRelinkRequired2(client);
8112
+ const required = await getCloudReuploadRequired2(client);
7447
8113
  console.log(BAR);
7448
- console.log(" EXE CLOUD RELINK AFTER KEY ROTATION");
8114
+ console.log(" EXE CLOUD RE-UPLOAD AFTER KEY ROTATION");
7449
8115
  console.log(BAR);
7450
8116
  console.log("");
7451
8117
  if (!required) {
7452
- console.log("Cloud relink flag: not set. Normal cloud sync is allowed.");
8118
+ console.log("Cloud status: connected");
8119
+ console.log("Normal cloud sync is safe to run.");
7453
8120
  return;
7454
8121
  }
7455
- console.log("Cloud relink flag: REQUIRED");
8122
+ console.log("Cloud status: needs cloud re-upload after key rotation");
7456
8123
  console.log("");
7457
- console.log("Why: this device rotated its memory encryption key. Old cloud blobs may be encrypted with the previous key.");
8124
+ console.log("What this means:");
8125
+ console.log(" This device now uses a new memory encryption key.");
8126
+ console.log(" The cloud copy may still be encrypted with the old key.");
8127
+ console.log(" Normal cloud sync is paused until this device re-uploads the cloud backup.");
7458
8128
  console.log("");
7459
- console.log("This wizard will:");
7460
- console.log(" 1. Verify the local DB is open with the current key.");
7461
- console.log(" 2. Confirm you saved the current 24-word recovery phrase.");
7462
- console.log(" 3. Treat this local DB as source of truth.");
7463
- console.log(" 4. Reset cloud cursors and reupload under the current key.");
8129
+ console.log("Running `exe-os cloud reupload` will:");
8130
+ console.log(" 1. Open your local memory DB with the current key.");
8131
+ console.log(" 2. Ask you to confirm you saved the current 24-word recovery phrase.");
8132
+ console.log(" 3. Treat this device as the correct copy of your memory.");
8133
+ console.log(" 4. Re-upload your cloud backup encrypted with the current key.");
7464
8134
  console.log("");
7465
8135
  if (dryRun) {
7466
- console.log("No changes made.");
7467
- console.log("When ready, run: exe-os cloud relink");
8136
+ console.log("Dry run only. No changes made.");
8137
+ console.log("When ready, run: exe-os cloud reupload");
7468
8138
  return;
7469
8139
  }
7470
- if (!shouldRunRelinkMutation({ dryRun, force, interactive: isInteractiveTerminal(), relinkRequired: required })) {
7471
- console.error("Refusing to relink cloud from a non-interactive session. Use local Terminal or pass --local-db-is-source-of-truth for automation.");
8140
+ if (!shouldRunReuploadMutation({ dryRun, force, interactive: isInteractiveTerminal(), reuploadRequired: required })) {
8141
+ console.error("For safety, cloud re-upload must be run in your local Terminal: exe-os cloud reupload\nAutomation only: pass --local-db-is-source-of-truth if this device is definitely the correct copy.");
7472
8142
  process.exitCode = 1;
7473
8143
  return;
7474
8144
  }
@@ -7481,7 +8151,7 @@ async function relinkMode() {
7481
8151
  process.exitCode = 1;
7482
8152
  return;
7483
8153
  }
7484
- const confirm = await ask(rl, 'Type "LOCAL DB IS SOURCE OF TRUTH" to reupload cloud data: ');
8154
+ const confirm = await ask(rl, 'Type "LOCAL DB IS SOURCE OF TRUTH" to re-upload cloud data: ');
7485
8155
  if (confirm !== "LOCAL DB IS SOURCE OF TRUTH") {
7486
8156
  console.log("Cancelled.");
7487
8157
  process.exitCode = 1;
@@ -7491,16 +8161,31 @@ async function relinkMode() {
7491
8161
  rl.close();
7492
8162
  }
7493
8163
  }
7494
- await clearCloudRelinkRequired2(client);
7495
- console.log("\u2713 Cloud relink flag cleared. Reuploading local DB under current key...");
7496
- const result = await runCloudSyncOnce();
8164
+ console.log("\u2713 Local DB confirmed. Clearing old cloud memory blobs, then re-uploading with the current key...");
8165
+ try {
8166
+ const reset = await cloudResetMemoryBlobs2((await loadConfig()).cloud);
8167
+ console.log(`\u2713 Cleared ${reset.deleted} old cloud memory blob(s) before re-upload.`);
8168
+ } catch (err) {
8169
+ console.error(`Cloud reset failed before re-upload: ${err instanceof Error ? err.message : String(err)}`);
8170
+ console.error("Cloud sync remains paused. Try again after the endpoint is available.");
8171
+ process.exitCode = 1;
8172
+ return;
8173
+ }
8174
+ await clearCloudReuploadRequired2(client);
8175
+ let result;
8176
+ try {
8177
+ result = await runCloudSyncOnce();
8178
+ } catch (err) {
8179
+ await markCloudReuploadRequired2(client);
8180
+ throw err;
8181
+ }
7497
8182
  console.log("");
7498
- console.log("Cloud relink complete.");
7499
- console.log(` Memories: ${result.totalMemories} total (\u2191 ${result.pushed} pushed, \u2193 ${result.pulled} pulled)`);
8183
+ console.log("Cloud re-upload complete. Normal cloud sync is safe again.");
8184
+ console.log(` Active memories: ${result.totalMemories} (\u2191 ${result.pushed} syncable rows pushed, \u2193 ${result.pulled} pulled)`);
7500
8185
  console.log(` Behaviors: ${result.behaviors.pushed} total (\u2193 ${result.behaviors.pulled} pulled)`);
7501
8186
  console.log(` Roster: ${result.roster.employees} employees, ${result.roster.identities} identities`);
7502
8187
  console.log(` Tasks: ${result.tasks.pushed} total (\u2193 ${result.tasks.pulled} pulled)`);
7503
- console.log("Other devices must relink with the current recovery phrase.");
8188
+ console.log("Other devices must reconnect using the current 24-word recovery phrase.");
7504
8189
  } finally {
7505
8190
  await disposeCloudCliResources();
7506
8191
  }
@@ -7510,21 +8195,26 @@ async function main() {
7510
8195
  if (mode === "link" || mode === "export") {
7511
8196
  await linkMode();
7512
8197
  } else if (mode === "sync") {
7513
- console.error("[deprecated] exe-cloud sync for device linking is now exe-cloud link. Use exe-os cloud sync for data sync.");
7514
- await linkMode();
8198
+ console.error("Use `exe-os cloud sync` for data sync.");
8199
+ console.error("Use `exe-os cloud link --show-full` to reveal the recovery phrase in your local Terminal.");
8200
+ process.exitCode = 1;
7515
8201
  } else if (mode === "status") {
7516
8202
  await statusMode();
8203
+ } else if (mode === "reupload") {
8204
+ await reuploadMode();
7517
8205
  } else if (mode === "relink") {
7518
- await relinkMode();
8206
+ console.error("`exe-os cloud relink` has been removed. Use: exe-os cloud reupload");
8207
+ process.exitCode = 1;
7519
8208
  } else if (!mode || mode === "setup") {
7520
8209
  await setupMode();
7521
8210
  } else {
7522
8211
  console.error("Usage:");
7523
- console.error(" exe-cloud \u2014 full device setup (key + cloud)");
7524
- console.error(" exe-cloud link \u2014 export recovery phrase + cloud info for a new device");
7525
- console.error(" exe-cloud status \u2014 show current sync status");
7526
- console.error(" exe-cloud relink \u2014 guided post-key-rotation cloud reupload");
7527
- console.error(" exe-cloud relink --dry-run");
8212
+ console.error(" exe-os cloud \u2014 full device setup (key + cloud)");
8213
+ console.error(" exe-os cloud link \u2014 show recovery phrase + cloud info for a new device");
8214
+ console.error(" exe-os cloud status \u2014 show current cloud status");
8215
+ console.error(" exe-os cloud sync \u2014 run sync now (normally automatic)");
8216
+ console.error(" exe-os cloud reupload \u2014 re-upload cloud backup after key rotation");
8217
+ console.error(" exe-os cloud reupload --dry-run");
7528
8218
  process.exit(1);
7529
8219
  }
7530
8220
  }
@@ -7536,5 +8226,5 @@ if (isMainModule(import.meta.url) && process.argv[1]?.includes("exe-cloud")) {
7536
8226
  }
7537
8227
  export {
7538
8228
  main,
7539
- shouldRunRelinkMutation
8229
+ shouldRunReuploadMutation
7540
8230
  };