@askexenow/exe-os 0.9.30 → 0.9.32

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 (64) hide show
  1. package/dist/bin/backfill-conversations.js +135 -7
  2. package/dist/bin/backfill-responses.js +135 -7
  3. package/dist/bin/backfill-vectors.js +135 -7
  4. package/dist/bin/cleanup-stale-review-tasks.js +139 -11
  5. package/dist/bin/cli.js +812 -486
  6. package/dist/bin/exe-assign.js +135 -7
  7. package/dist/bin/exe-boot.js +422 -113
  8. package/dist/bin/exe-cloud.js +160 -9
  9. package/dist/bin/exe-dispatch.js +136 -8
  10. package/dist/bin/exe-doctor.js +255 -13
  11. package/dist/bin/exe-export-behaviors.js +136 -8
  12. package/dist/bin/exe-forget.js +136 -8
  13. package/dist/bin/exe-gateway.js +171 -24
  14. package/dist/bin/exe-heartbeat.js +141 -13
  15. package/dist/bin/exe-kill.js +140 -12
  16. package/dist/bin/exe-launch-agent.js +143 -15
  17. package/dist/bin/exe-link.js +357 -48
  18. package/dist/bin/exe-pending-messages.js +136 -8
  19. package/dist/bin/exe-pending-notifications.js +136 -8
  20. package/dist/bin/exe-pending-reviews.js +138 -10
  21. package/dist/bin/exe-review.js +136 -8
  22. package/dist/bin/exe-search.js +155 -20
  23. package/dist/bin/exe-session-cleanup.js +166 -38
  24. package/dist/bin/exe-start-codex.js +142 -14
  25. package/dist/bin/exe-start-opencode.js +140 -12
  26. package/dist/bin/exe-status.js +148 -20
  27. package/dist/bin/exe-team.js +136 -8
  28. package/dist/bin/git-sweep.js +138 -10
  29. package/dist/bin/graph-backfill.js +135 -7
  30. package/dist/bin/graph-export.js +136 -8
  31. package/dist/bin/intercom-check.js +153 -25
  32. package/dist/bin/scan-tasks.js +138 -10
  33. package/dist/bin/setup.js +447 -121
  34. package/dist/bin/shard-migrate.js +135 -7
  35. package/dist/gateway/index.js +151 -23
  36. package/dist/hooks/bug-report-worker.js +151 -23
  37. package/dist/hooks/codex-stop-task-finalizer.js +145 -17
  38. package/dist/hooks/commit-complete.js +138 -10
  39. package/dist/hooks/error-recall.js +159 -24
  40. package/dist/hooks/ingest.js +142 -14
  41. package/dist/hooks/instructions-loaded.js +136 -8
  42. package/dist/hooks/notification.js +136 -8
  43. package/dist/hooks/post-compact.js +136 -8
  44. package/dist/hooks/post-tool-combined.js +159 -24
  45. package/dist/hooks/pre-compact.js +136 -8
  46. package/dist/hooks/pre-tool-use.js +144 -16
  47. package/dist/hooks/prompt-submit.js +195 -55
  48. package/dist/hooks/session-end.js +141 -13
  49. package/dist/hooks/session-start.js +165 -30
  50. package/dist/hooks/stop.js +136 -8
  51. package/dist/hooks/subagent-stop.js +136 -8
  52. package/dist/hooks/summary-worker.js +374 -65
  53. package/dist/index.js +136 -8
  54. package/dist/lib/cloud-sync.js +355 -46
  55. package/dist/lib/consolidation.js +1 -0
  56. package/dist/lib/exe-daemon.js +469 -127
  57. package/dist/lib/hybrid-search.js +155 -20
  58. package/dist/lib/keychain.js +191 -7
  59. package/dist/lib/schedules.js +138 -10
  60. package/dist/lib/store.js +135 -7
  61. package/dist/mcp/server.js +706 -213
  62. package/dist/runtime/index.js +136 -8
  63. package/dist/tui/App.js +208 -31
  64. package/package.json +1 -1
@@ -1439,8 +1439,8 @@ function findPackageRoot() {
1439
1439
  function getAvailableMemoryGB() {
1440
1440
  if (process.platform === "darwin") {
1441
1441
  try {
1442
- const { execSync: execSync7 } = __require("child_process");
1443
- const vmstat = execSync7("vm_stat", { encoding: "utf8" });
1442
+ const { execSync: execSync8 } = __require("child_process");
1443
+ const vmstat = execSync8("vm_stat", { encoding: "utf8" });
1444
1444
  const pageSize = 16384;
1445
1445
  const pageSizeMatch = vmstat.match(/page size of (\d+) bytes/);
1446
1446
  const actualPageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : pageSize;
@@ -3179,6 +3179,7 @@ var init_database = __esm({
3179
3179
  // src/lib/keychain.ts
3180
3180
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
3181
3181
  import { existsSync as existsSync7 } from "fs";
3182
+ import { execSync as execSync2 } from "child_process";
3182
3183
  import path7 from "path";
3183
3184
  import os5 from "os";
3184
3185
  function getKeyDir() {
@@ -3187,6 +3188,59 @@ function getKeyDir() {
3187
3188
  function getKeyPath() {
3188
3189
  return path7.join(getKeyDir(), "master.key");
3189
3190
  }
3191
+ function macKeychainGet() {
3192
+ if (process.platform !== "darwin") return null;
3193
+ try {
3194
+ return execSync2(
3195
+ `security find-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w 2>/dev/null`,
3196
+ { encoding: "utf-8", timeout: 5e3 }
3197
+ ).trim();
3198
+ } catch {
3199
+ return null;
3200
+ }
3201
+ }
3202
+ function macKeychainSet(value) {
3203
+ if (process.platform !== "darwin") return false;
3204
+ try {
3205
+ try {
3206
+ execSync2(
3207
+ `security delete-generic-password -s "${SERVICE}" -a "${ACCOUNT}" 2>/dev/null`,
3208
+ { timeout: 5e3 }
3209
+ );
3210
+ } catch {
3211
+ }
3212
+ execSync2(
3213
+ `security add-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w "${value}"`,
3214
+ { timeout: 5e3 }
3215
+ );
3216
+ return true;
3217
+ } catch {
3218
+ return false;
3219
+ }
3220
+ }
3221
+ function linuxSecretGet() {
3222
+ if (process.platform !== "linux") return null;
3223
+ try {
3224
+ return execSync2(
3225
+ `secret-tool lookup service "${SERVICE}" account "${ACCOUNT}" 2>/dev/null`,
3226
+ { encoding: "utf-8", timeout: 5e3 }
3227
+ ).trim();
3228
+ } catch {
3229
+ return null;
3230
+ }
3231
+ }
3232
+ function linuxSecretSet(value) {
3233
+ if (process.platform !== "linux") return false;
3234
+ try {
3235
+ execSync2(
3236
+ `echo -n "${value}" | secret-tool store --label="exe-os master key" service "${SERVICE}" account "${ACCOUNT}"`,
3237
+ { timeout: 5e3 }
3238
+ );
3239
+ return true;
3240
+ } catch {
3241
+ return false;
3242
+ }
3243
+ }
3190
3244
  async function tryKeytar() {
3191
3245
  try {
3192
3246
  return await import("keytar");
@@ -3194,13 +3248,63 @@ async function tryKeytar() {
3194
3248
  return null;
3195
3249
  }
3196
3250
  }
3251
+ function deriveMachineKey() {
3252
+ try {
3253
+ const crypto7 = __require("crypto");
3254
+ const material = [
3255
+ os5.hostname(),
3256
+ os5.userInfo().username,
3257
+ os5.arch(),
3258
+ os5.platform(),
3259
+ // Machine ID on Linux (stable across reboots)
3260
+ process.platform === "linux" ? readMachineId() : ""
3261
+ ].join("|");
3262
+ return crypto7.createHash("sha256").update(material).digest();
3263
+ } catch {
3264
+ return null;
3265
+ }
3266
+ }
3267
+ function readMachineId() {
3268
+ try {
3269
+ const { readFileSync: readFileSync13 } = __require("fs");
3270
+ return readFileSync13("/etc/machine-id", "utf-8").trim();
3271
+ } catch {
3272
+ return "";
3273
+ }
3274
+ }
3275
+ function decryptWithMachineKey(encrypted, machineKey) {
3276
+ if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
3277
+ try {
3278
+ const crypto7 = __require("crypto");
3279
+ const parts = encrypted.slice(ENCRYPTED_PREFIX.length).split(":");
3280
+ if (parts.length !== 3) return null;
3281
+ const [ivB64, tagB64, cipherB64] = parts;
3282
+ const iv = Buffer.from(ivB64, "base64");
3283
+ const authTag = Buffer.from(tagB64, "base64");
3284
+ const decipher = crypto7.createDecipheriv("aes-256-gcm", machineKey, iv);
3285
+ decipher.setAuthTag(authTag);
3286
+ let decrypted = decipher.update(cipherB64, "base64", "utf-8");
3287
+ decrypted += decipher.final("utf-8");
3288
+ return decrypted;
3289
+ } catch {
3290
+ return null;
3291
+ }
3292
+ }
3197
3293
  async function getMasterKey() {
3294
+ const nativeValue = macKeychainGet() ?? linuxSecretGet();
3295
+ if (nativeValue) {
3296
+ return Buffer.from(nativeValue, "base64");
3297
+ }
3198
3298
  const keytar = await tryKeytar();
3199
3299
  if (keytar) {
3200
3300
  try {
3201
- const stored = await keytar.getPassword(SERVICE, ACCOUNT);
3202
- if (stored) {
3203
- return Buffer.from(stored, "base64");
3301
+ const keytarValue = await keytar.getPassword(SERVICE, ACCOUNT);
3302
+ if (keytarValue) {
3303
+ const migrated = macKeychainSet(keytarValue) || linuxSecretSet(keytarValue);
3304
+ if (migrated) {
3305
+ process.stderr.write("[keychain] Migrated key from keytar to native keychain.\n");
3306
+ }
3307
+ return Buffer.from(keytarValue, "base64");
3204
3308
  }
3205
3309
  } catch {
3206
3310
  }
@@ -3214,8 +3318,31 @@ async function getMasterKey() {
3214
3318
  return null;
3215
3319
  }
3216
3320
  try {
3217
- const content = await readFile3(keyPath, "utf-8");
3218
- return Buffer.from(content.trim(), "base64");
3321
+ const content = (await readFile3(keyPath, "utf-8")).trim();
3322
+ let b64Value;
3323
+ if (content.startsWith(ENCRYPTED_PREFIX)) {
3324
+ const machineKey = deriveMachineKey();
3325
+ if (!machineKey) {
3326
+ process.stderr.write("[keychain] Cannot derive machine key to decrypt stored key.\n");
3327
+ return null;
3328
+ }
3329
+ const decrypted = decryptWithMachineKey(content, machineKey);
3330
+ if (!decrypted) {
3331
+ process.stderr.write(
3332
+ "[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase: exe-os link import\n"
3333
+ );
3334
+ return null;
3335
+ }
3336
+ b64Value = decrypted;
3337
+ } else {
3338
+ b64Value = content;
3339
+ }
3340
+ const key = Buffer.from(b64Value, "base64");
3341
+ const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
3342
+ if (migrated) {
3343
+ process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3344
+ }
3345
+ return key;
3219
3346
  } catch (err) {
3220
3347
  process.stderr.write(
3221
3348
  `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
@@ -3224,12 +3351,13 @@ async function getMasterKey() {
3224
3351
  return null;
3225
3352
  }
3226
3353
  }
3227
- var SERVICE, ACCOUNT;
3354
+ var SERVICE, ACCOUNT, ENCRYPTED_PREFIX;
3228
3355
  var init_keychain = __esm({
3229
3356
  "src/lib/keychain.ts"() {
3230
3357
  "use strict";
3231
3358
  SERVICE = "exe-mem";
3232
3359
  ACCOUNT = "master-key";
3360
+ ENCRYPTED_PREFIX = "enc:";
3233
3361
  }
3234
3362
  });
3235
3363
 
@@ -4495,7 +4623,7 @@ var init_session_registry = __esm({
4495
4623
  });
4496
4624
 
4497
4625
  // src/lib/session-key.ts
4498
- import { execSync as execSync2 } from "child_process";
4626
+ import { execSync as execSync3 } from "child_process";
4499
4627
  function normalizeCommand(command) {
4500
4628
  const trimmed = command.trim().toLowerCase();
4501
4629
  const parts = trimmed.split(/[\\/]/);
@@ -4514,7 +4642,7 @@ function resolveRuntimeProcess() {
4514
4642
  let pid = process.ppid;
4515
4643
  for (let i = 0; i < 10; i++) {
4516
4644
  try {
4517
- const info = execSync2(`ps -p ${pid} -o ppid=,comm=`, {
4645
+ const info = execSync3(`ps -p ${pid} -o ppid=,comm=`, {
4518
4646
  encoding: "utf8",
4519
4647
  timeout: 2e3
4520
4648
  }).trim();
@@ -4680,14 +4808,14 @@ var init_transport = __esm({
4680
4808
  });
4681
4809
 
4682
4810
  // src/lib/cc-agent-support.ts
4683
- import { execSync as execSync3 } from "child_process";
4811
+ import { execSync as execSync4 } from "child_process";
4684
4812
  function _resetCcAgentSupportCache() {
4685
4813
  _cachedSupport = null;
4686
4814
  }
4687
4815
  function claudeSupportsAgentFlag() {
4688
4816
  if (_cachedSupport !== null) return _cachedSupport;
4689
4817
  try {
4690
- const helpOutput = execSync3("claude --help 2>&1", {
4818
+ const helpOutput = execSync4("claude --help 2>&1", {
4691
4819
  encoding: "utf-8",
4692
4820
  timeout: 5e3
4693
4821
  });
@@ -5634,7 +5762,7 @@ __export(tmux_routing_exports, {
5634
5762
  spawnEmployee: () => spawnEmployee,
5635
5763
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
5636
5764
  });
5637
- import { execFileSync as execFileSync2, execSync as execSync4 } from "child_process";
5765
+ import { execFileSync as execFileSync2, execSync as execSync5 } from "child_process";
5638
5766
  import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync14, appendFileSync, readdirSync as readdirSync3 } from "fs";
5639
5767
  import path14 from "path";
5640
5768
  import os9 from "os";
@@ -6344,7 +6472,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6344
6472
  let booted = false;
6345
6473
  for (let i = 0; i < 30; i++) {
6346
6474
  try {
6347
- execSync4("sleep 0.5");
6475
+ execSync5("sleep 0.5");
6348
6476
  } catch {
6349
6477
  }
6350
6478
  try {
@@ -6510,7 +6638,7 @@ var init_notifications = __esm({
6510
6638
  });
6511
6639
 
6512
6640
  // src/lib/project-name.ts
6513
- import { execSync as execSync5 } from "child_process";
6641
+ import { execSync as execSync6 } from "child_process";
6514
6642
  import path16 from "path";
6515
6643
  function getProjectName(cwd) {
6516
6644
  const dir = cwd ?? process.cwd();
@@ -6518,7 +6646,7 @@ function getProjectName(cwd) {
6518
6646
  try {
6519
6647
  let repoRoot;
6520
6648
  try {
6521
- const gitCommonDir = execSync5("git rev-parse --path-format=absolute --git-common-dir", {
6649
+ const gitCommonDir = execSync6("git rev-parse --path-format=absolute --git-common-dir", {
6522
6650
  cwd: dir,
6523
6651
  encoding: "utf8",
6524
6652
  timeout: 2e3,
@@ -6526,7 +6654,7 @@ function getProjectName(cwd) {
6526
6654
  }).trim();
6527
6655
  repoRoot = path16.dirname(gitCommonDir);
6528
6656
  } catch {
6529
- repoRoot = execSync5("git rev-parse --show-toplevel", {
6657
+ repoRoot = execSync6("git rev-parse --show-toplevel", {
6530
6658
  cwd: dir,
6531
6659
  encoding: "utf8",
6532
6660
  timeout: 2e3,
@@ -6617,7 +6745,7 @@ var init_session_scope = __esm({
6617
6745
  import crypto4 from "crypto";
6618
6746
  import path17 from "path";
6619
6747
  import os11 from "os";
6620
- import { execSync as execSync6 } from "child_process";
6748
+ import { execSync as execSync7 } from "child_process";
6621
6749
  import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
6622
6750
  import { existsSync as existsSync16, readFileSync as readFileSync12 } from "fs";
6623
6751
  async function writeCheckpoint(input) {
@@ -6962,14 +7090,14 @@ function isTmuxSessionAlive(identifier) {
6962
7090
  if (!identifier || identifier === "unknown") return true;
6963
7091
  try {
6964
7092
  if (identifier.startsWith("%")) {
6965
- const output = execSync6("tmux list-panes -a -F '#{pane_id}'", {
7093
+ const output = execSync7("tmux list-panes -a -F '#{pane_id}'", {
6966
7094
  timeout: 2e3,
6967
7095
  encoding: "utf8",
6968
7096
  stdio: ["pipe", "pipe", "pipe"]
6969
7097
  });
6970
7098
  return output.split("\n").some((l) => l.trim() === identifier);
6971
7099
  } else {
6972
- execSync6(`tmux has-session -t ${JSON.stringify(identifier)}`, {
7100
+ execSync7(`tmux has-session -t ${JSON.stringify(identifier)}`, {
6973
7101
  timeout: 2e3,
6974
7102
  stdio: ["pipe", "pipe", "pipe"]
6975
7103
  });
@@ -6978,7 +7106,7 @@ function isTmuxSessionAlive(identifier) {
6978
7106
  } catch {
6979
7107
  if (identifier.startsWith("%")) return true;
6980
7108
  try {
6981
- execSync6("tmux list-sessions", {
7109
+ execSync7("tmux list-sessions", {
6982
7110
  timeout: 2e3,
6983
7111
  stdio: ["pipe", "pipe", "pipe"]
6984
7112
  });
@@ -6993,12 +7121,12 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
6993
7121
  if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
6994
7122
  try {
6995
7123
  const since = new Date(taskCreatedAt).toISOString();
6996
- const branch = execSync6(
7124
+ const branch = execSync7(
6997
7125
  "git rev-parse --abbrev-ref HEAD 2>/dev/null",
6998
7126
  { encoding: "utf8", timeout: 3e3 }
6999
7127
  ).trim();
7000
7128
  const branchArg = branch && branch !== "HEAD" ? branch : "";
7001
- const commitCount = execSync6(
7129
+ const commitCount = execSync7(
7002
7130
  `git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
7003
7131
  { encoding: "utf8", timeout: 5e3 }
7004
7132
  ).trim();
@@ -1123,8 +1123,8 @@ function findPackageRoot() {
1123
1123
  function getAvailableMemoryGB() {
1124
1124
  if (process.platform === "darwin") {
1125
1125
  try {
1126
- const { execSync: execSync5 } = __require("child_process");
1127
- const vmstat = execSync5("vm_stat", { encoding: "utf8" });
1126
+ const { execSync: execSync6 } = __require("child_process");
1127
+ const vmstat = execSync6("vm_stat", { encoding: "utf8" });
1128
1128
  const pageSize = 16384;
1129
1129
  const pageSizeMatch = vmstat.match(/page size of (\d+) bytes/);
1130
1130
  const actualPageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : pageSize;
@@ -2863,6 +2863,7 @@ var init_database = __esm({
2863
2863
  // src/lib/keychain.ts
2864
2864
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
2865
2865
  import { existsSync as existsSync6 } from "fs";
2866
+ import { execSync as execSync2 } from "child_process";
2866
2867
  import path6 from "path";
2867
2868
  import os5 from "os";
2868
2869
  function getKeyDir() {
@@ -2871,6 +2872,59 @@ function getKeyDir() {
2871
2872
  function getKeyPath() {
2872
2873
  return path6.join(getKeyDir(), "master.key");
2873
2874
  }
2875
+ function macKeychainGet() {
2876
+ if (process.platform !== "darwin") return null;
2877
+ try {
2878
+ return execSync2(
2879
+ `security find-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w 2>/dev/null`,
2880
+ { encoding: "utf-8", timeout: 5e3 }
2881
+ ).trim();
2882
+ } catch {
2883
+ return null;
2884
+ }
2885
+ }
2886
+ function macKeychainSet(value) {
2887
+ if (process.platform !== "darwin") return false;
2888
+ try {
2889
+ try {
2890
+ execSync2(
2891
+ `security delete-generic-password -s "${SERVICE}" -a "${ACCOUNT}" 2>/dev/null`,
2892
+ { timeout: 5e3 }
2893
+ );
2894
+ } catch {
2895
+ }
2896
+ execSync2(
2897
+ `security add-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w "${value}"`,
2898
+ { timeout: 5e3 }
2899
+ );
2900
+ return true;
2901
+ } catch {
2902
+ return false;
2903
+ }
2904
+ }
2905
+ function linuxSecretGet() {
2906
+ if (process.platform !== "linux") return null;
2907
+ try {
2908
+ return execSync2(
2909
+ `secret-tool lookup service "${SERVICE}" account "${ACCOUNT}" 2>/dev/null`,
2910
+ { encoding: "utf-8", timeout: 5e3 }
2911
+ ).trim();
2912
+ } catch {
2913
+ return null;
2914
+ }
2915
+ }
2916
+ function linuxSecretSet(value) {
2917
+ if (process.platform !== "linux") return false;
2918
+ try {
2919
+ execSync2(
2920
+ `echo -n "${value}" | secret-tool store --label="exe-os master key" service "${SERVICE}" account "${ACCOUNT}"`,
2921
+ { timeout: 5e3 }
2922
+ );
2923
+ return true;
2924
+ } catch {
2925
+ return false;
2926
+ }
2927
+ }
2874
2928
  async function tryKeytar() {
2875
2929
  try {
2876
2930
  return await import("keytar");
@@ -2878,13 +2932,63 @@ async function tryKeytar() {
2878
2932
  return null;
2879
2933
  }
2880
2934
  }
2935
+ function deriveMachineKey() {
2936
+ try {
2937
+ const crypto6 = __require("crypto");
2938
+ const material = [
2939
+ os5.hostname(),
2940
+ os5.userInfo().username,
2941
+ os5.arch(),
2942
+ os5.platform(),
2943
+ // Machine ID on Linux (stable across reboots)
2944
+ process.platform === "linux" ? readMachineId() : ""
2945
+ ].join("|");
2946
+ return crypto6.createHash("sha256").update(material).digest();
2947
+ } catch {
2948
+ return null;
2949
+ }
2950
+ }
2951
+ function readMachineId() {
2952
+ try {
2953
+ const { readFileSync: readFileSync12 } = __require("fs");
2954
+ return readFileSync12("/etc/machine-id", "utf-8").trim();
2955
+ } catch {
2956
+ return "";
2957
+ }
2958
+ }
2959
+ function decryptWithMachineKey(encrypted, machineKey) {
2960
+ if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
2961
+ try {
2962
+ const crypto6 = __require("crypto");
2963
+ const parts = encrypted.slice(ENCRYPTED_PREFIX.length).split(":");
2964
+ if (parts.length !== 3) return null;
2965
+ const [ivB64, tagB64, cipherB64] = parts;
2966
+ const iv = Buffer.from(ivB64, "base64");
2967
+ const authTag = Buffer.from(tagB64, "base64");
2968
+ const decipher = crypto6.createDecipheriv("aes-256-gcm", machineKey, iv);
2969
+ decipher.setAuthTag(authTag);
2970
+ let decrypted = decipher.update(cipherB64, "base64", "utf-8");
2971
+ decrypted += decipher.final("utf-8");
2972
+ return decrypted;
2973
+ } catch {
2974
+ return null;
2975
+ }
2976
+ }
2881
2977
  async function getMasterKey() {
2978
+ const nativeValue = macKeychainGet() ?? linuxSecretGet();
2979
+ if (nativeValue) {
2980
+ return Buffer.from(nativeValue, "base64");
2981
+ }
2882
2982
  const keytar = await tryKeytar();
2883
2983
  if (keytar) {
2884
2984
  try {
2885
- const stored = await keytar.getPassword(SERVICE, ACCOUNT);
2886
- if (stored) {
2887
- return Buffer.from(stored, "base64");
2985
+ const keytarValue = await keytar.getPassword(SERVICE, ACCOUNT);
2986
+ if (keytarValue) {
2987
+ const migrated = macKeychainSet(keytarValue) || linuxSecretSet(keytarValue);
2988
+ if (migrated) {
2989
+ process.stderr.write("[keychain] Migrated key from keytar to native keychain.\n");
2990
+ }
2991
+ return Buffer.from(keytarValue, "base64");
2888
2992
  }
2889
2993
  } catch {
2890
2994
  }
@@ -2898,8 +3002,31 @@ async function getMasterKey() {
2898
3002
  return null;
2899
3003
  }
2900
3004
  try {
2901
- const content = await readFile3(keyPath, "utf-8");
2902
- return Buffer.from(content.trim(), "base64");
3005
+ const content = (await readFile3(keyPath, "utf-8")).trim();
3006
+ let b64Value;
3007
+ if (content.startsWith(ENCRYPTED_PREFIX)) {
3008
+ const machineKey = deriveMachineKey();
3009
+ if (!machineKey) {
3010
+ process.stderr.write("[keychain] Cannot derive machine key to decrypt stored key.\n");
3011
+ return null;
3012
+ }
3013
+ const decrypted = decryptWithMachineKey(content, machineKey);
3014
+ if (!decrypted) {
3015
+ process.stderr.write(
3016
+ "[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase: exe-os link import\n"
3017
+ );
3018
+ return null;
3019
+ }
3020
+ b64Value = decrypted;
3021
+ } else {
3022
+ b64Value = content;
3023
+ }
3024
+ const key = Buffer.from(b64Value, "base64");
3025
+ const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
3026
+ if (migrated) {
3027
+ process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3028
+ }
3029
+ return key;
2903
3030
  } catch (err) {
2904
3031
  process.stderr.write(
2905
3032
  `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
@@ -2908,12 +3035,13 @@ async function getMasterKey() {
2908
3035
  return null;
2909
3036
  }
2910
3037
  }
2911
- var SERVICE, ACCOUNT;
3038
+ var SERVICE, ACCOUNT, ENCRYPTED_PREFIX;
2912
3039
  var init_keychain = __esm({
2913
3040
  "src/lib/keychain.ts"() {
2914
3041
  "use strict";
2915
3042
  SERVICE = "exe-mem";
2916
3043
  ACCOUNT = "master-key";
3044
+ ENCRYPTED_PREFIX = "enc:";
2917
3045
  }
2918
3046
  });
2919
3047
 
@@ -4156,7 +4284,7 @@ var init_session_registry = __esm({
4156
4284
  });
4157
4285
 
4158
4286
  // src/lib/session-key.ts
4159
- import { execSync as execSync2 } from "child_process";
4287
+ import { execSync as execSync3 } from "child_process";
4160
4288
  function normalizeCommand(command) {
4161
4289
  const trimmed = command.trim().toLowerCase();
4162
4290
  const parts = trimmed.split(/[\\/]/);
@@ -4175,7 +4303,7 @@ function resolveRuntimeProcess() {
4175
4303
  let pid = process.ppid;
4176
4304
  for (let i = 0; i < 10; i++) {
4177
4305
  try {
4178
- const info = execSync2(`ps -p ${pid} -o ppid=,comm=`, {
4306
+ const info = execSync3(`ps -p ${pid} -o ppid=,comm=`, {
4179
4307
  encoding: "utf8",
4180
4308
  timeout: 2e3
4181
4309
  }).trim();
@@ -4341,7 +4469,7 @@ var init_transport = __esm({
4341
4469
  });
4342
4470
 
4343
4471
  // src/lib/cc-agent-support.ts
4344
- import { execSync as execSync3 } from "child_process";
4472
+ import { execSync as execSync4 } from "child_process";
4345
4473
  var init_cc_agent_support = __esm({
4346
4474
  "src/lib/cc-agent-support.ts"() {
4347
4475
  "use strict";
@@ -4954,7 +5082,7 @@ var init_notifications = __esm({
4954
5082
  import crypto3 from "crypto";
4955
5083
  import path15 from "path";
4956
5084
  import os11 from "os";
4957
- import { execSync as execSync4 } from "child_process";
5085
+ import { execSync as execSync5 } from "child_process";
4958
5086
  import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
4959
5087
  import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
4960
5088
  async function writeCheckpoint(input) {
@@ -5096,14 +5224,14 @@ function isTmuxSessionAlive(identifier) {
5096
5224
  if (!identifier || identifier === "unknown") return true;
5097
5225
  try {
5098
5226
  if (identifier.startsWith("%")) {
5099
- const output = execSync4("tmux list-panes -a -F '#{pane_id}'", {
5227
+ const output = execSync5("tmux list-panes -a -F '#{pane_id}'", {
5100
5228
  timeout: 2e3,
5101
5229
  encoding: "utf8",
5102
5230
  stdio: ["pipe", "pipe", "pipe"]
5103
5231
  });
5104
5232
  return output.split("\n").some((l) => l.trim() === identifier);
5105
5233
  } else {
5106
- execSync4(`tmux has-session -t ${JSON.stringify(identifier)}`, {
5234
+ execSync5(`tmux has-session -t ${JSON.stringify(identifier)}`, {
5107
5235
  timeout: 2e3,
5108
5236
  stdio: ["pipe", "pipe", "pipe"]
5109
5237
  });
@@ -5112,7 +5240,7 @@ function isTmuxSessionAlive(identifier) {
5112
5240
  } catch {
5113
5241
  if (identifier.startsWith("%")) return true;
5114
5242
  try {
5115
- execSync4("tmux list-sessions", {
5243
+ execSync5("tmux list-sessions", {
5116
5244
  timeout: 2e3,
5117
5245
  stdio: ["pipe", "pipe", "pipe"]
5118
5246
  });
@@ -5127,12 +5255,12 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
5127
5255
  if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
5128
5256
  try {
5129
5257
  const since = new Date(taskCreatedAt).toISOString();
5130
- const branch = execSync4(
5258
+ const branch = execSync5(
5131
5259
  "git rev-parse --abbrev-ref HEAD 2>/dev/null",
5132
5260
  { encoding: "utf8", timeout: 3e3 }
5133
5261
  ).trim();
5134
5262
  const branchArg = branch && branch !== "HEAD" ? branch : "";
5135
- const commitCount = execSync4(
5263
+ const commitCount = execSync5(
5136
5264
  `git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
5137
5265
  { encoding: "utf8", timeout: 5e3 }
5138
5266
  ).trim();