@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
@@ -188,6 +188,11 @@ function normalizeAutoUpdate(raw) {
188
188
  const userAU = raw.autoUpdate ?? {};
189
189
  raw.autoUpdate = { ...defaultAU, ...userAU };
190
190
  }
191
+ function normalizeOrchestration(raw) {
192
+ const defaultOrg = DEFAULT_CONFIG.orchestration;
193
+ const userOrg = raw.orchestration ?? {};
194
+ raw.orchestration = { ...defaultOrg, ...userOrg };
195
+ }
191
196
  async function loadConfig() {
192
197
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
193
198
  await ensurePrivateDir(dir);
@@ -212,10 +217,15 @@ async function loadConfig() {
212
217
  normalizeScalingRoadmap(migratedCfg);
213
218
  normalizeSessionLifecycle(migratedCfg);
214
219
  normalizeAutoUpdate(migratedCfg);
220
+ normalizeOrchestration(migratedCfg);
215
221
  const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
216
222
  if (config.dbPath.startsWith("~")) {
217
223
  config.dbPath = config.dbPath.replace(/^~/, os.homedir());
218
224
  }
225
+ const envDbPath = path.join(dir, "memories.db");
226
+ if (process.env.EXE_OS_DIR && config.dbPath !== envDbPath && !existsSync2(config.dbPath) && existsSync2(envDbPath)) {
227
+ config.dbPath = envDbPath;
228
+ }
219
229
  return config;
220
230
  } catch {
221
231
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
@@ -287,6 +297,10 @@ var init_config = __esm({
287
297
  checkOnBoot: true,
288
298
  autoInstall: false,
289
299
  checkIntervalMs: 24 * 60 * 60 * 1e3
300
+ },
301
+ orchestration: {
302
+ phase: "phase_1_coo",
303
+ phaseSetBy: "default"
290
304
  }
291
305
  };
292
306
  CONFIG_MIGRATIONS = [
@@ -1655,6 +1669,9 @@ function getClient() {
1655
1669
  if (_daemonClient && _daemonClient._isDaemonActive()) {
1656
1670
  return _daemonClient;
1657
1671
  }
1672
+ if (!_resilientClient) {
1673
+ return _adapterClient;
1674
+ }
1658
1675
  return _resilientClient;
1659
1676
  }
1660
1677
  async function initDaemonClient() {
@@ -2687,6 +2704,127 @@ async function ensureSchema() {
2687
2704
  VALUES (new.rowid, new.content, new.subject, new.predicate, new.object);
2688
2705
  END;
2689
2706
  `);
2707
+ await client.executeMultiple(`
2708
+ CREATE TABLE IF NOT EXISTS agent_sessions (
2709
+ id TEXT PRIMARY KEY,
2710
+ agent_id TEXT NOT NULL,
2711
+ project_name TEXT,
2712
+ started_at TEXT NOT NULL,
2713
+ last_event_at TEXT NOT NULL,
2714
+ event_count INTEGER NOT NULL DEFAULT 0,
2715
+ properties TEXT DEFAULT '{}'
2716
+ );
2717
+
2718
+ CREATE INDEX IF NOT EXISTS idx_agent_sessions_agent_time
2719
+ ON agent_sessions(agent_id, started_at);
2720
+
2721
+ CREATE TABLE IF NOT EXISTS agent_goals (
2722
+ id TEXT PRIMARY KEY,
2723
+ statement TEXT NOT NULL,
2724
+ owner_agent_id TEXT,
2725
+ project_name TEXT,
2726
+ status TEXT NOT NULL DEFAULT 'open',
2727
+ priority INTEGER NOT NULL DEFAULT 5,
2728
+ success_criteria TEXT,
2729
+ parent_goal_id TEXT,
2730
+ due_at TEXT,
2731
+ achieved_at TEXT,
2732
+ supersedes_id TEXT,
2733
+ created_at TEXT NOT NULL,
2734
+ updated_at TEXT NOT NULL,
2735
+ source_memory_id TEXT
2736
+ );
2737
+
2738
+ CREATE INDEX IF NOT EXISTS idx_agent_goals_project_status
2739
+ ON agent_goals(project_name, status, priority);
2740
+
2741
+ CREATE TABLE IF NOT EXISTS agent_events (
2742
+ id TEXT PRIMARY KEY,
2743
+ event_type TEXT NOT NULL,
2744
+ occurred_at TEXT NOT NULL,
2745
+ sequence_index INTEGER NOT NULL,
2746
+ actor_agent_id TEXT,
2747
+ agent_role TEXT,
2748
+ project_name TEXT,
2749
+ session_id TEXT,
2750
+ task_id TEXT,
2751
+ goal_id TEXT,
2752
+ parent_event_id TEXT,
2753
+ intention TEXT,
2754
+ outcome TEXT,
2755
+ evidence_memory_id TEXT,
2756
+ impact TEXT,
2757
+ payload TEXT DEFAULT '{}',
2758
+ created_at TEXT NOT NULL
2759
+ );
2760
+
2761
+ CREATE INDEX IF NOT EXISTS idx_agent_events_time
2762
+ ON agent_events(occurred_at, sequence_index);
2763
+
2764
+ CREATE INDEX IF NOT EXISTS idx_agent_events_session_seq
2765
+ ON agent_events(session_id, sequence_index);
2766
+
2767
+ CREATE INDEX IF NOT EXISTS idx_agent_events_goal_time
2768
+ ON agent_events(goal_id, occurred_at);
2769
+
2770
+ CREATE INDEX IF NOT EXISTS idx_agent_events_memory
2771
+ ON agent_events(evidence_memory_id);
2772
+
2773
+ CREATE TABLE IF NOT EXISTS agent_goal_links (
2774
+ id TEXT PRIMARY KEY,
2775
+ goal_id TEXT NOT NULL,
2776
+ link_type TEXT NOT NULL,
2777
+ target_id TEXT NOT NULL,
2778
+ target_type TEXT NOT NULL,
2779
+ created_at TEXT NOT NULL
2780
+ );
2781
+
2782
+ CREATE INDEX IF NOT EXISTS idx_agent_goal_links_goal
2783
+ ON agent_goal_links(goal_id, target_type);
2784
+
2785
+ CREATE TABLE IF NOT EXISTS agent_semantic_labels (
2786
+ id TEXT PRIMARY KEY,
2787
+ source_memory_id TEXT NOT NULL,
2788
+ event_id TEXT,
2789
+ labeler TEXT NOT NULL,
2790
+ schema_version INTEGER NOT NULL DEFAULT 1,
2791
+ confidence REAL NOT NULL DEFAULT 0,
2792
+ labels TEXT NOT NULL,
2793
+ created_at TEXT NOT NULL,
2794
+ updated_at TEXT NOT NULL
2795
+ );
2796
+
2797
+ CREATE INDEX IF NOT EXISTS idx_agent_semantic_labels_memory
2798
+ ON agent_semantic_labels(source_memory_id, labeler);
2799
+
2800
+ CREATE INDEX IF NOT EXISTS idx_agent_semantic_labels_event
2801
+ ON agent_semantic_labels(event_id);
2802
+
2803
+ CREATE TABLE IF NOT EXISTS agent_reflection_checkpoints (
2804
+ id TEXT PRIMARY KEY,
2805
+ project_name TEXT,
2806
+ session_id TEXT,
2807
+ window_start_at TEXT NOT NULL,
2808
+ window_end_at TEXT NOT NULL,
2809
+ event_count INTEGER NOT NULL DEFAULT 0,
2810
+ goal_count INTEGER NOT NULL DEFAULT 0,
2811
+ success_count INTEGER NOT NULL DEFAULT 0,
2812
+ failure_count INTEGER NOT NULL DEFAULT 0,
2813
+ risk_count INTEGER NOT NULL DEFAULT 0,
2814
+ summary TEXT NOT NULL,
2815
+ learnings TEXT NOT NULL DEFAULT '[]',
2816
+ next_actions TEXT NOT NULL DEFAULT '[]',
2817
+ evidence_event_ids TEXT NOT NULL DEFAULT '[]',
2818
+ confidence REAL NOT NULL DEFAULT 0,
2819
+ created_at TEXT NOT NULL
2820
+ );
2821
+
2822
+ CREATE INDEX IF NOT EXISTS idx_agent_reflection_project_time
2823
+ ON agent_reflection_checkpoints(project_name, window_end_at);
2824
+
2825
+ CREATE INDEX IF NOT EXISTS idx_agent_reflection_session_time
2826
+ ON agent_reflection_checkpoints(session_id, window_end_at);
2827
+ `);
2690
2828
  try {
2691
2829
  await client.execute({
2692
2830
  sql: `ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3`,
@@ -2850,7 +2988,7 @@ __export(shard_manager_exports, {
2850
2988
  shardExists: () => shardExists
2851
2989
  });
2852
2990
  import path7 from "path";
2853
- import { existsSync as existsSync7, mkdirSync as mkdirSync2, readdirSync, renameSync as renameSync3, statSync as statSync2 } from "fs";
2991
+ import { existsSync as existsSync7, mkdirSync as mkdirSync2, readdirSync, renameSync as renameSync3, statSync as statSync3 } from "fs";
2854
2992
  import { createClient as createClient2 } from "@libsql/client";
2855
2993
  function initShardManager(encryptionKey) {
2856
2994
  _encryptionKey = encryptionKey;
@@ -2914,7 +3052,7 @@ async function auditShardHealth(options = {}) {
2914
3052
  const shards = [];
2915
3053
  for (const name of names) {
2916
3054
  const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
2917
- const stat = statSync2(dbPath);
3055
+ const stat = statSync3(dbPath);
2918
3056
  const item = {
2919
3057
  name,
2920
3058
  path: dbPath,
@@ -3167,7 +3305,7 @@ async function getReadyShardClient(projectName) {
3167
3305
  _shardLastAccess.delete(safeName);
3168
3306
  const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
3169
3307
  if (existsSync7(dbPath)) {
3170
- const stat = statSync2(dbPath);
3308
+ const stat = statSync3(dbPath);
3171
3309
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3172
3310
  const archivedPath = path7.join(SHARDS_DIR, `${safeName}.db.broken-${stamp}`);
3173
3311
  renameSync3(dbPath, archivedPath);
@@ -3287,6 +3425,12 @@ var init_platform_procedures = __esm({
3287
3425
  priority: "p0",
3288
3426
  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."
3289
3427
  },
3428
+ {
3429
+ title: "Customer orchestration maturity \u2014 recommend, never trap",
3430
+ domain: "workflow",
3431
+ priority: "p1",
3432
+ 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."
3433
+ },
3290
3434
  {
3291
3435
  title: "Single dispatch path \u2014 create_task only",
3292
3436
  domain: "workflow",
@@ -3345,6 +3489,12 @@ var init_platform_procedures = __esm({
3345
3489
  priority: "p0",
3346
3490
  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."
3347
3491
  },
3492
+ {
3493
+ title: "Commit discipline \u2014 never leave verified work floating",
3494
+ domain: "workflow",
3495
+ priority: "p1",
3496
+ 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."
3497
+ },
3348
3498
  {
3349
3499
  title: "Desktop and TUI are the same product",
3350
3500
  domain: "architecture",
@@ -3510,11 +3660,12 @@ init_database();
3510
3660
 
3511
3661
  // src/lib/keychain.ts
3512
3662
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
3513
- import { existsSync as existsSync6 } from "fs";
3663
+ import { existsSync as existsSync6, statSync as statSync2 } from "fs";
3514
3664
  import { execSync as execSync2 } from "child_process";
3515
3665
  import path6 from "path";
3516
3666
  import os5 from "os";
3517
- var SERVICE = "exe-mem";
3667
+ var SERVICE = "exe-os";
3668
+ var LEGACY_SERVICE = "exe-mem";
3518
3669
  var ACCOUNT = "master-key";
3519
3670
  function getKeyDir() {
3520
3671
  return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path6.join(os5.homedir(), ".exe-os");
@@ -3522,29 +3673,66 @@ function getKeyDir() {
3522
3673
  function getKeyPath() {
3523
3674
  return path6.join(getKeyDir(), "master.key");
3524
3675
  }
3525
- function macKeychainGet() {
3676
+ function nativeKeychainAllowed() {
3677
+ return process.env.EXE_OS_DISABLE_NATIVE_KEYCHAIN !== "1";
3678
+ }
3679
+ var linuxSecretAvailability = null;
3680
+ function linuxSecretAvailable() {
3681
+ if (!nativeKeychainAllowed()) return false;
3682
+ if (process.platform !== "linux") return false;
3683
+ if (linuxSecretAvailability !== null) return linuxSecretAvailability;
3684
+ try {
3685
+ execSync2("command -v secret-tool >/dev/null 2>&1", { timeout: 1e3 });
3686
+ } catch {
3687
+ linuxSecretAvailability = false;
3688
+ return false;
3689
+ }
3690
+ try {
3691
+ execSync2("secret-tool search --all exe-os probe >/dev/null 2>&1", { timeout: 1e3 });
3692
+ linuxSecretAvailability = true;
3693
+ } catch {
3694
+ linuxSecretAvailability = false;
3695
+ }
3696
+ return linuxSecretAvailability;
3697
+ }
3698
+ function isRootOnlyTrustedServerKeyFile(keyPath) {
3699
+ if (process.platform !== "linux") return false;
3700
+ try {
3701
+ const uid = typeof os5.userInfo().uid === "number" ? os5.userInfo().uid : -1;
3702
+ const st = statSync2(keyPath);
3703
+ if (!st.isFile() || (st.mode & 63) !== 0) return false;
3704
+ if (uid === 0) return true;
3705
+ const exeOsDir = process.env.EXE_OS_DIR;
3706
+ return Boolean(exeOsDir && path6.resolve(keyPath).startsWith(path6.resolve(exeOsDir) + path6.sep));
3707
+ } catch {
3708
+ return false;
3709
+ }
3710
+ }
3711
+ function macKeychainGet(service = SERVICE) {
3712
+ if (!nativeKeychainAllowed()) return null;
3526
3713
  if (process.platform !== "darwin") return null;
3527
3714
  try {
3528
3715
  return execSync2(
3529
- `security find-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w 2>/dev/null`,
3716
+ `security find-generic-password -s "${service}" -a "${ACCOUNT}" -w 2>/dev/null`,
3530
3717
  { encoding: "utf-8", timeout: 5e3 }
3531
3718
  ).trim();
3532
3719
  } catch {
3533
3720
  return null;
3534
3721
  }
3535
3722
  }
3536
- function macKeychainSet(value) {
3723
+ function macKeychainSet(value, service = SERVICE) {
3724
+ if (!nativeKeychainAllowed()) return false;
3537
3725
  if (process.platform !== "darwin") return false;
3538
3726
  try {
3539
3727
  try {
3540
3728
  execSync2(
3541
- `security delete-generic-password -s "${SERVICE}" -a "${ACCOUNT}" 2>/dev/null`,
3729
+ `security delete-generic-password -s "${service}" -a "${ACCOUNT}" 2>/dev/null`,
3542
3730
  { timeout: 5e3 }
3543
3731
  );
3544
3732
  } catch {
3545
3733
  }
3546
3734
  execSync2(
3547
- `security add-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w "${value}"`,
3735
+ `security add-generic-password -s "${service}" -a "${ACCOUNT}" -w "${value}"`,
3548
3736
  { timeout: 5e3 }
3549
3737
  );
3550
3738
  return true;
@@ -3552,22 +3740,48 @@ function macKeychainSet(value) {
3552
3740
  return false;
3553
3741
  }
3554
3742
  }
3555
- function linuxSecretGet() {
3556
- if (process.platform !== "linux") return null;
3743
+ function macKeychainDelete(service = SERVICE) {
3744
+ if (!nativeKeychainAllowed()) return false;
3745
+ if (process.platform !== "darwin") return false;
3746
+ try {
3747
+ execSync2(
3748
+ `security delete-generic-password -s "${service}" -a "${ACCOUNT}" 2>/dev/null`,
3749
+ { timeout: 5e3 }
3750
+ );
3751
+ return true;
3752
+ } catch {
3753
+ return false;
3754
+ }
3755
+ }
3756
+ function linuxSecretGet(service = SERVICE) {
3757
+ if (!linuxSecretAvailable()) return null;
3557
3758
  try {
3558
3759
  return execSync2(
3559
- `secret-tool lookup service "${SERVICE}" account "${ACCOUNT}" 2>/dev/null`,
3760
+ `secret-tool lookup service "${service}" account "${ACCOUNT}" 2>/dev/null`,
3560
3761
  { encoding: "utf-8", timeout: 5e3 }
3561
3762
  ).trim();
3562
3763
  } catch {
3563
3764
  return null;
3564
3765
  }
3565
3766
  }
3566
- function linuxSecretSet(value) {
3767
+ function linuxSecretSet(value, service = SERVICE) {
3768
+ if (!linuxSecretAvailable()) return false;
3769
+ try {
3770
+ execSync2(
3771
+ `echo -n "${value}" | secret-tool store --label="exe-os master key" service "${service}" account "${ACCOUNT}" 2>/dev/null`,
3772
+ { timeout: 5e3 }
3773
+ );
3774
+ return true;
3775
+ } catch {
3776
+ return false;
3777
+ }
3778
+ }
3779
+ function linuxSecretDelete(service = SERVICE) {
3780
+ if (!nativeKeychainAllowed()) return false;
3567
3781
  if (process.platform !== "linux") return false;
3568
3782
  try {
3569
3783
  execSync2(
3570
- `echo -n "${value}" | secret-tool store --label="exe-os master key" service "${SERVICE}" account "${ACCOUNT}"`,
3784
+ `secret-tool clear service "${service}" account "${ACCOUNT}" 2>/dev/null`,
3571
3785
  { timeout: 5e3 }
3572
3786
  );
3573
3787
  return true;
@@ -3576,6 +3790,7 @@ function linuxSecretSet(value) {
3576
3790
  }
3577
3791
  }
3578
3792
  async function tryKeytar() {
3793
+ if (!nativeKeychainAllowed()) return null;
3579
3794
  try {
3580
3795
  return await import("keytar");
3581
3796
  } catch {
@@ -3650,7 +3865,19 @@ async function writeMachineBoundFileFallback(b64) {
3650
3865
  return "plaintext";
3651
3866
  }
3652
3867
  async function getMasterKey() {
3653
- const nativeValue = macKeychainGet() ?? linuxSecretGet();
3868
+ let nativeValue = macKeychainGet() ?? linuxSecretGet();
3869
+ if (!nativeValue) {
3870
+ const legacyValue = macKeychainGet(LEGACY_SERVICE) ?? linuxSecretGet(LEGACY_SERVICE);
3871
+ if (legacyValue) {
3872
+ const migrated = macKeychainSet(legacyValue) || linuxSecretSet(legacyValue);
3873
+ if (migrated) {
3874
+ macKeychainDelete(LEGACY_SERVICE);
3875
+ linuxSecretDelete(LEGACY_SERVICE);
3876
+ process.stderr.write("[keychain] Migrated keychain service from exe-mem to exe-os.\n");
3877
+ }
3878
+ nativeValue = legacyValue;
3879
+ }
3880
+ }
3654
3881
  if (nativeValue) {
3655
3882
  return Buffer.from(nativeValue, "base64");
3656
3883
  }
@@ -3658,12 +3885,17 @@ async function getMasterKey() {
3658
3885
  if (keytar) {
3659
3886
  try {
3660
3887
  const keytarValue = await keytar.getPassword(SERVICE, ACCOUNT);
3661
- if (keytarValue) {
3662
- const migrated = macKeychainSet(keytarValue) || linuxSecretSet(keytarValue);
3888
+ const legacyKeytarValue = keytarValue ?? await keytar.getPassword(LEGACY_SERVICE, ACCOUNT);
3889
+ if (legacyKeytarValue) {
3890
+ const migrated = macKeychainSet(legacyKeytarValue) || linuxSecretSet(legacyKeytarValue);
3663
3891
  if (migrated) {
3664
3892
  process.stderr.write("[keychain] Migrated key from keytar to native keychain.\n");
3893
+ try {
3894
+ await keytar.deletePassword(LEGACY_SERVICE, ACCOUNT);
3895
+ } catch {
3896
+ }
3665
3897
  }
3666
- return Buffer.from(keytarValue, "base64");
3898
+ return Buffer.from(legacyKeytarValue, "base64");
3667
3899
  }
3668
3900
  } catch {
3669
3901
  }
@@ -3688,7 +3920,7 @@ async function getMasterKey() {
3688
3920
  const decrypted = decryptWithMachineKey(content, machineKey);
3689
3921
  if (!decrypted) {
3690
3922
  process.stderr.write(
3691
- "[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase: exe-os link import\n"
3923
+ "[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase during setup: exe-os setup\n"
3692
3924
  );
3693
3925
  return null;
3694
3926
  }
@@ -3697,6 +3929,9 @@ async function getMasterKey() {
3697
3929
  b64Value = content;
3698
3930
  }
3699
3931
  const key = Buffer.from(b64Value, "base64");
3932
+ if (!content.startsWith(ENCRYPTED_PREFIX) && isRootOnlyTrustedServerKeyFile(keyPath)) {
3933
+ return key;
3934
+ }
3700
3935
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
3701
3936
  if (migrated) {
3702
3937
  process.stderr.write("[keychain] Migrated key from file to native keychain.\n");