@askexenow/exe-os 0.9.112 → 0.9.114

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 (96) hide show
  1. package/README.md +9 -7
  2. package/dist/bin/agentic-ontology-backfill.js +78 -23
  3. package/dist/bin/agentic-reflection-backfill.js +53 -13
  4. package/dist/bin/agentic-semantic-label.js +53 -13
  5. package/dist/bin/backfill-conversations.js +77 -22
  6. package/dist/bin/backfill-responses.js +78 -23
  7. package/dist/bin/backfill-vectors.js +53 -13
  8. package/dist/bin/bulk-sync-postgres.js +78 -23
  9. package/dist/bin/cleanup-stale-review-tasks.js +98 -26
  10. package/dist/bin/cli.js +388 -97
  11. package/dist/bin/exe-agent-config.js +7 -1
  12. package/dist/bin/exe-agent.js +55 -2
  13. package/dist/bin/exe-assign.js +78 -23
  14. package/dist/bin/exe-boot.js +524 -161
  15. package/dist/bin/exe-call.js +53 -4
  16. package/dist/bin/exe-cloud.js +127 -26
  17. package/dist/bin/exe-dispatch.js +402 -39
  18. package/dist/bin/exe-doctor.js +76 -21
  19. package/dist/bin/exe-export-behaviors.js +77 -22
  20. package/dist/bin/exe-forget.js +77 -22
  21. package/dist/bin/exe-gateway.js +161 -38
  22. package/dist/bin/exe-heartbeat.js +98 -26
  23. package/dist/bin/exe-kill.js +77 -22
  24. package/dist/bin/exe-launch-agent.js +173 -29
  25. package/dist/bin/exe-new-employee.js +183 -7
  26. package/dist/bin/exe-pending-messages.js +98 -26
  27. package/dist/bin/exe-pending-notifications.js +98 -26
  28. package/dist/bin/exe-pending-reviews.js +98 -26
  29. package/dist/bin/exe-rename.js +77 -22
  30. package/dist/bin/exe-review.js +77 -22
  31. package/dist/bin/exe-search.js +77 -22
  32. package/dist/bin/exe-session-cleanup.js +523 -160
  33. package/dist/bin/exe-settings.js +10 -4
  34. package/dist/bin/exe-start-codex.js +554 -255
  35. package/dist/bin/exe-start-opencode.js +564 -175
  36. package/dist/bin/exe-status.js +98 -26
  37. package/dist/bin/exe-support.js +1 -1
  38. package/dist/bin/exe-team.js +77 -22
  39. package/dist/bin/git-sweep.js +402 -39
  40. package/dist/bin/graph-backfill.js +78 -23
  41. package/dist/bin/graph-export.js +77 -22
  42. package/dist/bin/install.js +70 -4
  43. package/dist/bin/intercom-check.js +523 -160
  44. package/dist/bin/pre-publish.js +13 -1
  45. package/dist/bin/scan-tasks.js +402 -39
  46. package/dist/bin/setup.js +151 -24
  47. package/dist/bin/shard-migrate.js +78 -23
  48. package/dist/bin/stack-update.js +1 -1
  49. package/dist/bin/update.js +3 -3
  50. package/dist/gateway/index.js +161 -38
  51. package/dist/hooks/bug-report-worker.js +161 -38
  52. package/dist/hooks/codex-stop-task-finalizer.js +542 -150
  53. package/dist/hooks/commit-complete.js +402 -39
  54. package/dist/hooks/error-recall.js +77 -22
  55. package/dist/hooks/ingest.js +4592 -251
  56. package/dist/hooks/instructions-loaded.js +77 -22
  57. package/dist/hooks/notification.js +77 -22
  58. package/dist/hooks/post-compact.js +98 -26
  59. package/dist/hooks/post-tool-combined.js +98 -26
  60. package/dist/hooks/pre-compact.js +482 -119
  61. package/dist/hooks/pre-tool-use.js +148 -26
  62. package/dist/hooks/prompt-submit.js +162 -39
  63. package/dist/hooks/session-end.js +484 -124
  64. package/dist/hooks/session-start.js +135 -27
  65. package/dist/hooks/stop.js +97 -25
  66. package/dist/hooks/subagent-stop.js +98 -26
  67. package/dist/hooks/summary-worker.js +107 -18
  68. package/dist/index.js +188 -38
  69. package/dist/lib/agent-config.js +24 -1
  70. package/dist/lib/cloud-sync.js +72 -12
  71. package/dist/lib/consolidation.js +25 -2
  72. package/dist/lib/database.js +16 -0
  73. package/dist/lib/db.js +16 -0
  74. package/dist/lib/device-registry.js +16 -0
  75. package/dist/lib/employee-templates.js +29 -3
  76. package/dist/lib/employees.js +24 -1
  77. package/dist/lib/exe-daemon.js +441 -58
  78. package/dist/lib/hybrid-search.js +77 -22
  79. package/dist/lib/keychain.js +24 -12
  80. package/dist/lib/license.js +3 -3
  81. package/dist/lib/messaging.js +21 -4
  82. package/dist/lib/schedules.js +53 -13
  83. package/dist/lib/skill-learning.js +466 -70
  84. package/dist/lib/status-brief.js +14 -1
  85. package/dist/lib/store.js +78 -23
  86. package/dist/lib/tasks.js +403 -95
  87. package/dist/lib/tmux-routing.js +326 -18
  88. package/dist/mcp/server.js +213 -45
  89. package/dist/mcp/tools/create-task.js +85 -17
  90. package/dist/mcp/tools/deactivate-behavior.js +33 -24
  91. package/dist/mcp/tools/list-tasks.js +21 -4
  92. package/dist/mcp/tools/send-message.js +21 -4
  93. package/dist/mcp/tools/update-task.js +400 -95
  94. package/dist/runtime/index.js +506 -116
  95. package/dist/tui/App.js +268 -69
  96. package/package.json +1 -1
@@ -3553,6 +3553,22 @@ async function ensureSchema() {
3553
3553
  } catch (e) {
3554
3554
  logCatchDebug("migration", e);
3555
3555
  }
3556
+ try {
3557
+ await client.execute({
3558
+ sql: `ALTER TABLE memories ADD COLUMN visibility TEXT DEFAULT 'private'`,
3559
+ args: []
3560
+ });
3561
+ } catch (e) {
3562
+ logCatchDebug("migration", e);
3563
+ }
3564
+ try {
3565
+ await client.execute({
3566
+ sql: `ALTER TABLE memories ADD COLUMN strength REAL DEFAULT 1.0`,
3567
+ args: []
3568
+ });
3569
+ } catch (e) {
3570
+ logCatchDebug("migration", e);
3571
+ }
3556
3572
  }
3557
3573
  async function disposeDatabase() {
3558
3574
  if (_walCheckpointTimer) {
@@ -3619,7 +3635,7 @@ var init_license = __esm({
3619
3635
  LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
3620
3636
  CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
3621
3637
  DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
3622
- API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
3638
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
3623
3639
  }
3624
3640
  });
3625
3641
 
@@ -3672,6 +3688,18 @@ function extractRootExe(name) {
3672
3688
  const parts = name.split("-").filter(Boolean);
3673
3689
  return parts.length > 0 ? parts[parts.length - 1] : null;
3674
3690
  }
3691
+ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
3692
+ if (!existsSync12(SESSION_CACHE)) {
3693
+ mkdirSync7(SESSION_CACHE, { recursive: true });
3694
+ }
3695
+ const rootExe = extractRootExe(parentExe) ?? parentExe;
3696
+ const filePath = path13.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
3697
+ writeFileSync7(filePath, JSON.stringify({
3698
+ parentExe: rootExe,
3699
+ dispatchedBy: dispatchedBy || rootExe,
3700
+ registeredAt: (/* @__PURE__ */ new Date()).toISOString()
3701
+ }));
3702
+ }
3675
3703
  function getParentExe(sessionKey) {
3676
3704
  try {
3677
3705
  const data = JSON.parse(readFileSync10(path13.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
@@ -3681,11 +3709,12 @@ function getParentExe(sessionKey) {
3681
3709
  }
3682
3710
  }
3683
3711
  function resolveExeSession() {
3712
+ if (process.env.EXE_SESSION_NAME) {
3713
+ const fromEnv = extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
3714
+ if (fromEnv) return fromEnv;
3715
+ }
3684
3716
  const mySession = getMySession();
3685
3717
  if (!mySession) {
3686
- if (process.env.EXE_SESSION_NAME) {
3687
- return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
3688
- }
3689
3718
  return null;
3690
3719
  }
3691
3720
  const fromSessionName = extractRootExe(mySession);
@@ -3700,6 +3729,10 @@ function resolveExeSession() {
3700
3729
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
3701
3730
  `
3702
3731
  );
3732
+ try {
3733
+ registerParentExe(key, fromSessionName);
3734
+ } catch {
3735
+ }
3703
3736
  candidate = fromSessionName;
3704
3737
  } else {
3705
3738
  candidate = fromCache;
@@ -3791,7 +3824,7 @@ var init_task_scope = __esm({
3791
3824
  });
3792
3825
 
3793
3826
  // src/lib/keychain.ts
3794
- import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
3827
+ import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2, rename, copyFile } from "fs/promises";
3795
3828
  import { existsSync as existsSync13, statSync as statSync3 } from "fs";
3796
3829
  import { execSync as execSync6 } from "child_process";
3797
3830
  import path14 from "path";
@@ -3826,12 +3859,14 @@ function linuxSecretAvailable() {
3826
3859
  function isRootOnlyTrustedServerKeyFile(keyPath) {
3827
3860
  if (process.platform !== "linux") return false;
3828
3861
  try {
3829
- const uid = typeof os10.userInfo().uid === "number" ? os10.userInfo().uid : -1;
3830
3862
  const st = statSync3(keyPath);
3831
3863
  if (!st.isFile() || (st.mode & 63) !== 0) return false;
3864
+ const uid = typeof os10.userInfo().uid === "number" ? os10.userInfo().uid : -1;
3832
3865
  if (uid === 0) return true;
3833
3866
  const exeOsDir = process.env.EXE_OS_DIR;
3834
- return Boolean(exeOsDir && path14.resolve(keyPath).startsWith(path14.resolve(exeOsDir) + path14.sep));
3867
+ if (exeOsDir && path14.resolve(keyPath).startsWith(path14.resolve(exeOsDir) + path14.sep)) return true;
3868
+ if (!linuxSecretAvailable()) return true;
3869
+ return false;
3835
3870
  } catch {
3836
3871
  return false;
3837
3872
  }
@@ -3981,15 +4016,25 @@ async function writeMachineBoundFileFallback(b64) {
3981
4016
  await mkdir3(dir, { recursive: true });
3982
4017
  const keyPath = getKeyPath();
3983
4018
  const machineKey = deriveMachineKey();
3984
- if (machineKey) {
3985
- const encrypted = encryptWithMachineKey(b64, machineKey);
3986
- await writeFile3(keyPath, encrypted + "\n", "utf-8");
3987
- await chmod2(keyPath, 384);
3988
- return "encrypted";
4019
+ const content = machineKey ? encryptWithMachineKey(b64, machineKey) + "\n" : b64 + "\n";
4020
+ const result = machineKey ? "encrypted" : "plaintext";
4021
+ const tmpPath = keyPath + ".tmp";
4022
+ try {
4023
+ if (existsSync13(keyPath)) {
4024
+ await copyFile(keyPath, keyPath + ".bak").catch(() => {
4025
+ });
4026
+ }
4027
+ await writeFile3(tmpPath, content, "utf-8");
4028
+ await chmod2(tmpPath, 384);
4029
+ await rename(tmpPath, keyPath);
4030
+ } catch (err) {
4031
+ try {
4032
+ await unlink(tmpPath);
4033
+ } catch {
4034
+ }
4035
+ throw err;
3989
4036
  }
3990
- await writeFile3(keyPath, b64 + "\n", "utf-8");
3991
- await chmod2(keyPath, 384);
3992
- return "plaintext";
4037
+ return result;
3993
4038
  }
3994
4039
  async function getMasterKey() {
3995
4040
  let nativeValue = macKeychainGet() ?? linuxSecretGet();
@@ -4863,11 +4908,17 @@ var init_platform_procedures = __esm({
4863
4908
  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."
4864
4909
  },
4865
4910
  {
4866
- title: "Customer orchestration maturity \u2014 recommend, never trap",
4911
+ title: "Orchestration phase guidance \u2014 recommend, never trap",
4867
4912
  domain: "workflow",
4868
4913
  priority: "p1",
4869
4914
  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."
4870
4915
  },
4916
+ {
4917
+ title: "Routing slot vs display title \u2014 internal 'coo' is plumbing, not your name",
4918
+ domain: "identity",
4919
+ priority: "p0",
4920
+ content: "These procedures reference 'COO' as a shorthand for the coordinator role. This is an INTERNAL routing slot used by exe-os code (chain-of-command checks, dispatch logic, session detection). It is NOT your display title. Your actual title comes from your identity file's `title:` field \u2014 that is what you use externally: introductions, sign-offs, team comms, and any user-facing text. If your identity says `title: AI Chief of Staff`, you are the AI Chief of Staff. The routing slot stays `role: coo` for code compatibility \u2014 never rename it, but also never introduce yourself as 'COO' unless your identity file explicitly says so. The founder chose your title; respect it."
4921
+ },
4871
4922
  {
4872
4923
  title: "Single dispatch path \u2014 create_task only",
4873
4924
  domain: "workflow",
@@ -4901,6 +4952,12 @@ var init_platform_procedures = __esm({
4901
4952
  priority: "p0",
4902
4953
  content: "NEVER: (1) Access the database directly \u2014 it's SQLCipher encrypted, always fails. Use MCP tools only. (2) Manually spawn tmux sessions \u2014 create_task handles it. (3) Run git checkout main \u2014 agents work in worktrees. (4) Modify another agent's in-progress task. (5) Push to remote \u2014 the COO reviews and pushes. (6) Skip update_task(done) \u2014 it's the ONLY way your work gets reviewed. (7) Run git init."
4903
4954
  },
4955
+ {
4956
+ title: "Destructive operations \u2014 mandatory reviewer gate",
4957
+ domain: "security",
4958
+ priority: "p0",
4959
+ content: "Before ANY destructive operation (delete, remove, overwrite, drop, reset, force-push, truncate), you MUST: (1) Have your full task spec accessible \u2014 if you cannot read it, STOP and report to your reviewer. Never improvise destructive actions. (2) Confirm with your reviewer (assigned_by or COO) before executing. (3) If the task spec explicitly authorizes the operation, proceed \u2014 but log it. Violation = immediate task failure. This applies to ALL agents regardless of role."
4960
+ },
4904
4961
  {
4905
4962
  title: "Customer patch triage \u2014 upstream bug vs customization",
4906
4963
  domain: "support",
@@ -5186,10 +5243,24 @@ function stableId(memoryId, type, content) {
5186
5243
  return createHash2("sha256").update(`${memoryId}:${type}:${content}`).digest("hex").slice(0, 32);
5187
5244
  }
5188
5245
  function cleanText(text) {
5189
- return text.replace(/```[\s\S]*?```/g, " ").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
5190
- }
5191
- function splitSentences(text) {
5192
- return cleanText(text).split(/(?<=[.!?])\s+|\n+/).map((s) => s.trim()).filter((s) => s.length >= 24 && s.length <= MAX_SENTENCE_CHARS);
5246
+ let cleaned = text.replace(
5247
+ /```(\w*)\n(.*?)(?:\n[\s\S]*?)```/g,
5248
+ (_m, lang, firstLine) => `[code${lang ? `:${lang}` : ""}] ${firstLine.trim()}`
5249
+ );
5250
+ cleaned = cleaned.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
5251
+ return cleaned;
5252
+ }
5253
+ function splitSegments(text) {
5254
+ const cleaned = cleanText(text);
5255
+ const segments = cleaned.split(/(?<=[.!?:;])\s+|\n{2,}|(?<=\))\s+(?=[A-Z])|\s*[|│]\s*/).map((s) => s.trim()).filter((s) => s.length >= MIN_SEGMENT_CHARS && s.length <= MAX_SEGMENT_CHARS);
5256
+ if (segments.length === 0 && cleaned.length >= MIN_SEGMENT_CHARS) {
5257
+ const lines = cleaned.split(/\n+/).map((l) => l.trim()).filter((l) => l.length >= MIN_SEGMENT_CHARS && l.length <= MAX_SEGMENT_CHARS);
5258
+ if (lines.length > 0) return lines;
5259
+ if (cleaned.length >= MIN_SEGMENT_CHARS) {
5260
+ return [cleaned.slice(0, MAX_SEGMENT_CHARS)];
5261
+ }
5262
+ }
5263
+ return segments;
5193
5264
  }
5194
5265
  function inferCardType(sentence, toolName) {
5195
5266
  const lower = sentence.toLowerCase();
@@ -5221,12 +5292,12 @@ function predicateFor(type) {
5221
5292
  }
5222
5293
  }
5223
5294
  function extractMemoryCards(row) {
5224
- const sentences = splitSentences(row.raw_text);
5295
+ const segments = splitSegments(row.raw_text);
5225
5296
  const cards = [];
5226
- for (const sentence of sentences) {
5297
+ for (const sentence of segments) {
5227
5298
  const type = inferCardType(sentence, row.tool_name);
5228
5299
  const subject = extractSubject(sentence, row.agent_id);
5229
- const content = sentence.length > MAX_SENTENCE_CHARS ? `${sentence.slice(0, MAX_SENTENCE_CHARS - 1)}\u2026` : sentence;
5300
+ const content = sentence.length > MAX_SEGMENT_CHARS ? `${sentence.slice(0, MAX_SEGMENT_CHARS - 1)}\u2026` : sentence;
5230
5301
  cards.push({
5231
5302
  id: stableId(row.id, type, content),
5232
5303
  memory_id: row.id,
@@ -5322,13 +5393,14 @@ Source memory: ${String(row.source_ref ?? row.memory_id)}`,
5322
5393
  last_accessed: String(row.timestamp)
5323
5394
  }));
5324
5395
  }
5325
- var MAX_CARDS_PER_MEMORY, MAX_SENTENCE_CHARS;
5396
+ var MAX_CARDS_PER_MEMORY, MAX_SEGMENT_CHARS, MIN_SEGMENT_CHARS;
5326
5397
  var init_memory_cards = __esm({
5327
5398
  "src/lib/memory-cards.ts"() {
5328
5399
  "use strict";
5329
5400
  init_database();
5330
- MAX_CARDS_PER_MEMORY = 6;
5331
- MAX_SENTENCE_CHARS = 360;
5401
+ MAX_CARDS_PER_MEMORY = 8;
5402
+ MAX_SEGMENT_CHARS = 500;
5403
+ MIN_SEGMENT_CHARS = 20;
5332
5404
  }
5333
5405
  });
5334
5406
 
@@ -3320,6 +3320,22 @@ async function ensureSchema() {
3320
3320
  } catch (e) {
3321
3321
  logCatchDebug("migration", e);
3322
3322
  }
3323
+ try {
3324
+ await client.execute({
3325
+ sql: `ALTER TABLE memories ADD COLUMN visibility TEXT DEFAULT 'private'`,
3326
+ args: []
3327
+ });
3328
+ } catch (e) {
3329
+ logCatchDebug("migration", e);
3330
+ }
3331
+ try {
3332
+ await client.execute({
3333
+ sql: `ALTER TABLE memories ADD COLUMN strength REAL DEFAULT 1.0`,
3334
+ args: []
3335
+ });
3336
+ } catch (e) {
3337
+ logCatchDebug("migration", e);
3338
+ }
3323
3339
  }
3324
3340
  async function disposeDatabase() {
3325
3341
  if (_walCheckpointTimer) {
@@ -3380,7 +3396,7 @@ __export(keychain_exports, {
3380
3396
  importMnemonic: () => importMnemonic,
3381
3397
  setMasterKey: () => setMasterKey
3382
3398
  });
3383
- import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
3399
+ import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2, rename, copyFile } from "fs/promises";
3384
3400
  import { existsSync as existsSync7, statSync as statSync3 } from "fs";
3385
3401
  import { execSync as execSync3 } from "child_process";
3386
3402
  import path6 from "path";
@@ -3415,12 +3431,14 @@ function linuxSecretAvailable() {
3415
3431
  function isRootOnlyTrustedServerKeyFile(keyPath) {
3416
3432
  if (process.platform !== "linux") return false;
3417
3433
  try {
3418
- const uid = typeof os5.userInfo().uid === "number" ? os5.userInfo().uid : -1;
3419
3434
  const st = statSync3(keyPath);
3420
3435
  if (!st.isFile() || (st.mode & 63) !== 0) return false;
3436
+ const uid = typeof os5.userInfo().uid === "number" ? os5.userInfo().uid : -1;
3421
3437
  if (uid === 0) return true;
3422
3438
  const exeOsDir = process.env.EXE_OS_DIR;
3423
- return Boolean(exeOsDir && path6.resolve(keyPath).startsWith(path6.resolve(exeOsDir) + path6.sep));
3439
+ if (exeOsDir && path6.resolve(keyPath).startsWith(path6.resolve(exeOsDir) + path6.sep)) return true;
3440
+ if (!linuxSecretAvailable()) return true;
3441
+ return false;
3424
3442
  } catch {
3425
3443
  return false;
3426
3444
  }
@@ -3570,15 +3588,25 @@ async function writeMachineBoundFileFallback(b64) {
3570
3588
  await mkdir3(dir, { recursive: true });
3571
3589
  const keyPath = getKeyPath();
3572
3590
  const machineKey = deriveMachineKey();
3573
- if (machineKey) {
3574
- const encrypted = encryptWithMachineKey(b64, machineKey);
3575
- await writeFile3(keyPath, encrypted + "\n", "utf-8");
3576
- await chmod2(keyPath, 384);
3577
- return "encrypted";
3591
+ const content = machineKey ? encryptWithMachineKey(b64, machineKey) + "\n" : b64 + "\n";
3592
+ const result = machineKey ? "encrypted" : "plaintext";
3593
+ const tmpPath = keyPath + ".tmp";
3594
+ try {
3595
+ if (existsSync7(keyPath)) {
3596
+ await copyFile(keyPath, keyPath + ".bak").catch(() => {
3597
+ });
3598
+ }
3599
+ await writeFile3(tmpPath, content, "utf-8");
3600
+ await chmod2(tmpPath, 384);
3601
+ await rename(tmpPath, keyPath);
3602
+ } catch (err) {
3603
+ try {
3604
+ await unlink(tmpPath);
3605
+ } catch {
3606
+ }
3607
+ throw err;
3578
3608
  }
3579
- await writeFile3(keyPath, b64 + "\n", "utf-8");
3580
- await chmod2(keyPath, 384);
3581
- return "plaintext";
3609
+ return result;
3582
3610
  }
3583
3611
  async function getMasterKey() {
3584
3612
  let nativeValue = macKeychainGet() ?? linuxSecretGet();
@@ -4437,11 +4465,17 @@ var init_platform_procedures = __esm({
4437
4465
  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."
4438
4466
  },
4439
4467
  {
4440
- title: "Customer orchestration maturity \u2014 recommend, never trap",
4468
+ title: "Orchestration phase guidance \u2014 recommend, never trap",
4441
4469
  domain: "workflow",
4442
4470
  priority: "p1",
4443
4471
  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."
4444
4472
  },
4473
+ {
4474
+ title: "Routing slot vs display title \u2014 internal 'coo' is plumbing, not your name",
4475
+ domain: "identity",
4476
+ priority: "p0",
4477
+ content: "These procedures reference 'COO' as a shorthand for the coordinator role. This is an INTERNAL routing slot used by exe-os code (chain-of-command checks, dispatch logic, session detection). It is NOT your display title. Your actual title comes from your identity file's `title:` field \u2014 that is what you use externally: introductions, sign-offs, team comms, and any user-facing text. If your identity says `title: AI Chief of Staff`, you are the AI Chief of Staff. The routing slot stays `role: coo` for code compatibility \u2014 never rename it, but also never introduce yourself as 'COO' unless your identity file explicitly says so. The founder chose your title; respect it."
4478
+ },
4445
4479
  {
4446
4480
  title: "Single dispatch path \u2014 create_task only",
4447
4481
  domain: "workflow",
@@ -4475,6 +4509,12 @@ var init_platform_procedures = __esm({
4475
4509
  priority: "p0",
4476
4510
  content: "NEVER: (1) Access the database directly \u2014 it's SQLCipher encrypted, always fails. Use MCP tools only. (2) Manually spawn tmux sessions \u2014 create_task handles it. (3) Run git checkout main \u2014 agents work in worktrees. (4) Modify another agent's in-progress task. (5) Push to remote \u2014 the COO reviews and pushes. (6) Skip update_task(done) \u2014 it's the ONLY way your work gets reviewed. (7) Run git init."
4477
4511
  },
4512
+ {
4513
+ title: "Destructive operations \u2014 mandatory reviewer gate",
4514
+ domain: "security",
4515
+ priority: "p0",
4516
+ content: "Before ANY destructive operation (delete, remove, overwrite, drop, reset, force-push, truncate), you MUST: (1) Have your full task spec accessible \u2014 if you cannot read it, STOP and report to your reviewer. Never improvise destructive actions. (2) Confirm with your reviewer (assigned_by or COO) before executing. (3) If the task spec explicitly authorizes the operation, proceed \u2014 but log it. Violation = immediate task failure. This applies to ALL agents regardless of role."
4517
+ },
4478
4518
  {
4479
4519
  title: "Customer patch triage \u2014 upstream bug vs customization",
4480
4520
  domain: "support",
@@ -5400,7 +5440,7 @@ async function assertVpsLicense(opts) {
5400
5440
  }
5401
5441
  if (!transientFailure) {
5402
5442
  throw new Error(
5403
- "License validation failed: unknown backend state. Restore network connectivity to https://askexe.com/cloud and retry."
5443
+ "License validation failed: unknown backend state. Restore network connectivity to https://cloud.askexe.com and retry."
5404
5444
  );
5405
5445
  }
5406
5446
  const fresh = await getCachedLicense();
@@ -5437,7 +5477,7 @@ async function assertVpsLicense(opts) {
5437
5477
  } catch {
5438
5478
  }
5439
5479
  throw new Error(
5440
- `License validation unreachable for more than ${graceDays} days. Restore network connectivity to https://askexe.com/cloud and retry. This VPS image refuses to boot after the offline grace window.`
5480
+ `License validation unreachable for more than ${graceDays} days. Restore network connectivity to https://cloud.askexe.com and retry. This VPS image refuses to boot after the offline grace window.`
5441
5481
  );
5442
5482
  }
5443
5483
  function startLicenseRevalidation(intervalMs = 36e5) {
@@ -5469,7 +5509,7 @@ var init_license = __esm({
5469
5509
  LICENSE_PATH = path11.join(EXE_AI_DIR, "license.key");
5470
5510
  CACHE_PATH = path11.join(EXE_AI_DIR, "license-cache.json");
5471
5511
  DEVICE_ID_PATH = path11.join(EXE_AI_DIR, "device-id");
5472
- API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
5512
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
5473
5513
  RETRY_DELAY_MS = 500;
5474
5514
  LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
5475
5515
  MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
@@ -5658,6 +5698,18 @@ function extractRootExe(name) {
5658
5698
  const parts = name.split("-").filter(Boolean);
5659
5699
  return parts.length > 0 ? parts[parts.length - 1] : null;
5660
5700
  }
5701
+ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
5702
+ if (!existsSync14(SESSION_CACHE)) {
5703
+ mkdirSync7(SESSION_CACHE, { recursive: true });
5704
+ }
5705
+ const rootExe = extractRootExe(parentExe) ?? parentExe;
5706
+ const filePath = path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
5707
+ writeFileSync6(filePath, JSON.stringify({
5708
+ parentExe: rootExe,
5709
+ dispatchedBy: dispatchedBy || rootExe,
5710
+ registeredAt: (/* @__PURE__ */ new Date()).toISOString()
5711
+ }));
5712
+ }
5661
5713
  function getParentExe(sessionKey) {
5662
5714
  try {
5663
5715
  const data = JSON.parse(readFileSync9(path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
@@ -5667,11 +5719,12 @@ function getParentExe(sessionKey) {
5667
5719
  }
5668
5720
  }
5669
5721
  function resolveExeSession() {
5722
+ if (process.env.EXE_SESSION_NAME) {
5723
+ const fromEnv = extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
5724
+ if (fromEnv) return fromEnv;
5725
+ }
5670
5726
  const mySession = getMySession();
5671
5727
  if (!mySession) {
5672
- if (process.env.EXE_SESSION_NAME) {
5673
- return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
5674
- }
5675
5728
  return null;
5676
5729
  }
5677
5730
  const fromSessionName = extractRootExe(mySession);
@@ -5686,6 +5739,10 @@ function resolveExeSession() {
5686
5739
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
5687
5740
  `
5688
5741
  );
5742
+ try {
5743
+ registerParentExe(key, fromSessionName);
5744
+ } catch {
5745
+ }
5689
5746
  candidate = fromSessionName;
5690
5747
  } else {
5691
5748
  candidate = fromCache;
@@ -6455,6 +6512,7 @@ __export(cloud_sync_exports, {
6455
6512
  markCloudReuploadRequired: () => markCloudReuploadRequired,
6456
6513
  mergeConfig: () => mergeConfig,
6457
6514
  mergeRosterFromRemote: () => mergeRosterFromRemote,
6515
+ migrateEndpoint: () => migrateEndpoint,
6458
6516
  pushToPostgres: () => pushToPostgres,
6459
6517
  recordRosterDeletion: () => recordRosterDeletion
6460
6518
  });
@@ -6625,6 +6683,15 @@ async function fetchWithRetry(url, init) {
6625
6683
  }
6626
6684
  throw lastError;
6627
6685
  }
6686
+ function migrateEndpoint(endpoint) {
6687
+ if (endpoint === "https://askexe.com/cloud" || endpoint === "https://askexe.com/cloud/") {
6688
+ process.stderr.write(
6689
+ "[cloud-sync] Auto-migrating endpoint from askexe.com/cloud to cloud.askexe.com (bypasses Cloudflare WAF for datacenter IPs)\n"
6690
+ );
6691
+ return "https://cloud.askexe.com";
6692
+ }
6693
+ return endpoint;
6694
+ }
6628
6695
  function assertSecureEndpoint(endpoint) {
6629
6696
  if (endpoint.startsWith("https://")) return;
6630
6697
  if (endpoint.startsWith("http://")) {
@@ -6762,6 +6829,7 @@ async function markCloudReuploadRequired(client = getClient()) {
6762
6829
  await client.execute("INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('cloud_reupload_required', '1')");
6763
6830
  }
6764
6831
  async function cloudSync(config) {
6832
+ config = { ...config, endpoint: migrateEndpoint(config.endpoint) };
6765
6833
  if (!isSyncCryptoInitialized()) {
6766
6834
  try {
6767
6835
  const { getMasterKey: getMasterKey2 } = await Promise.resolve().then(() => (init_keychain(), keychain_exports));
@@ -6851,6 +6919,27 @@ async function cloudSync(config) {
6851
6919
  if (stmts.length > 0) await client.batch(stmts, "write");
6852
6920
  pulled = pullResult.records.length;
6853
6921
  } else {
6922
+ try {
6923
+ const incomingIds = pullResult.records.map((r) => sqlSafe(r.id));
6924
+ if (incomingIds.length > 0) {
6925
+ const ph = incomingIds.map(() => "?").join(",");
6926
+ const existing = await client.execute({
6927
+ sql: `SELECT id, version, timestamp FROM memories WHERE id IN (${ph})`,
6928
+ args: incomingIds
6929
+ });
6930
+ const localMap = new Map(existing.rows.map((r) => [String(r.id), r]));
6931
+ for (const rec of pullResult.records) {
6932
+ const local = localMap.get(String(rec.id));
6933
+ if (local && Number(local.version) > 0 && Number(local.version) !== Number(rec.version ?? 0)) {
6934
+ process.stderr.write(
6935
+ `[cloud-sync] CONFLICT: memory ${String(rec.id).slice(0, 8)} \u2014 local v${local.version} vs remote v${rec.version ?? 0}. Remote wins (LWW).
6936
+ `
6937
+ );
6938
+ }
6939
+ }
6940
+ }
6941
+ } catch {
6942
+ }
6854
6943
  const stmts = pullResult.records.map((rec) => ({
6855
6944
  sql: `INSERT OR REPLACE INTO memories
6856
6945
  (id, agent_id, agent_role, session_id, timestamp,