@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
@@ -290,11 +290,17 @@ var init_platform_procedures = __esm({
290
290
  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."
291
291
  },
292
292
  {
293
- title: "Customer orchestration maturity \u2014 recommend, never trap",
293
+ title: "Orchestration phase guidance \u2014 recommend, never trap",
294
294
  domain: "workflow",
295
295
  priority: "p1",
296
296
  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."
297
297
  },
298
+ {
299
+ title: "Routing slot vs display title \u2014 internal 'coo' is plumbing, not your name",
300
+ domain: "identity",
301
+ priority: "p0",
302
+ 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."
303
+ },
298
304
  {
299
305
  title: "Single dispatch path \u2014 create_task only",
300
306
  domain: "workflow",
@@ -328,6 +334,12 @@ var init_platform_procedures = __esm({
328
334
  priority: "p0",
329
335
  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."
330
336
  },
337
+ {
338
+ title: "Destructive operations \u2014 mandatory reviewer gate",
339
+ domain: "security",
340
+ priority: "p0",
341
+ 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."
342
+ },
331
343
  {
332
344
  title: "Customer patch triage \u2014 upstream bug vs customization",
333
345
  domain: "support",
@@ -573,9 +585,23 @@ __export(employee_templates_exports, {
573
585
  function getSessionPrompt(storedPrompt) {
574
586
  const markerIndex = storedPrompt.indexOf(PROCEDURES_MARKER);
575
587
  const withoutProcedures = markerIndex >= 0 ? storedPrompt.slice(0, markerIndex).trimEnd() : storedPrompt;
588
+ let titlePrefix = "";
589
+ const frontmatterMatch = withoutProcedures.match(/^---\r?\n([\s\S]*?)\r?\n---/);
590
+ if (frontmatterMatch) {
591
+ const titleMatch = frontmatterMatch[1].match(/^title:\s*(.+)$/m);
592
+ const roleMatch = frontmatterMatch[1].match(/^role:\s*(.+)$/m);
593
+ if (titleMatch) {
594
+ const title = titleMatch[1].trim();
595
+ const role = roleMatch ? roleMatch[1].trim() : "";
596
+ if (title && role && title.toLowerCase() !== role.toLowerCase()) {
597
+ titlePrefix = `## Your Identity
598
+ You are **${title}** (specialist). `;
599
+ }
600
+ }
601
+ }
576
602
  const rolePrompt = withoutProcedures.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "").replace(/<!--[\s\S]*?-->/g, "").trimStart();
577
603
  const globalBlock = getGlobalProceduresBlock();
578
- return `${globalBlock}${rolePrompt}
604
+ return `${globalBlock}${titlePrefix}${rolePrompt}
579
605
  ${BASE_OPERATING_PROCEDURES}`;
580
606
  }
581
607
  function buildCustomEmployeePrompt(name, role) {
@@ -594,7 +620,7 @@ function personalizePrompt(prompt, templateName, actualName) {
594
620
  return prompt.replace(new RegExp(`\\bYou are ${escaped}\\b`, "g"), `You are ${actualName}`);
595
621
  }
596
622
  function renderClientCOOTemplate(vars) {
597
- const resolved = { ...vars, title: vars.title || "Chief Operating Officer" };
623
+ const resolved = { ...vars, title: vars.title || "Chief of Staff" };
598
624
  for (const key of CLIENT_COO_PLACEHOLDERS) {
599
625
  const value = resolved[key];
600
626
  if (typeof value !== "string" || value.length === 0) {
@@ -1237,7 +1263,9 @@ __export(agent_config_exports, {
1237
1263
  clearAgentRuntime: () => clearAgentRuntime,
1238
1264
  getAgentRuntime: () => getAgentRuntime,
1239
1265
  loadAgentConfig: () => loadAgentConfig,
1266
+ normalizeCcModelName: () => normalizeCcModelName,
1240
1267
  saveAgentConfig: () => saveAgentConfig,
1268
+ setAgentMcps: () => setAgentMcps,
1241
1269
  setAgentRuntime: () => setAgentRuntime
1242
1270
  });
1243
1271
  import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync5 } from "fs";
@@ -1264,7 +1292,14 @@ function getAgentRuntime(agentId) {
1264
1292
  if (orgDefault) return orgDefault;
1265
1293
  return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
1266
1294
  }
1267
- function setAgentRuntime(agentId, runtime, model, reasoning_effort) {
1295
+ function normalizeCcModelName(model) {
1296
+ let ccModel = model.replace(/(\d+)\.(\d+)/g, "$1-$2");
1297
+ if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
1298
+ ccModel += "[1m]";
1299
+ }
1300
+ return ccModel;
1301
+ }
1302
+ function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
1268
1303
  const knownModels = KNOWN_RUNTIMES[runtime];
1269
1304
  if (!knownModels) {
1270
1305
  return {
@@ -1279,12 +1314,26 @@ function setAgentRuntime(agentId, runtime, model, reasoning_effort) {
1279
1314
  };
1280
1315
  }
1281
1316
  const config = loadAgentConfig();
1317
+ const existing = config[agentId];
1282
1318
  const entry = { runtime, model };
1283
1319
  if (reasoning_effort) entry.reasoning_effort = reasoning_effort;
1320
+ if (mcps !== void 0) {
1321
+ entry.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
1322
+ } else if (existing?.mcps) {
1323
+ entry.mcps = existing.mcps;
1324
+ }
1284
1325
  config[agentId] = entry;
1285
1326
  saveAgentConfig(config);
1286
1327
  return { ok: true };
1287
1328
  }
1329
+ function setAgentMcps(agentId, mcps) {
1330
+ const config = loadAgentConfig();
1331
+ const existing = config[agentId] ?? getAgentRuntime(agentId);
1332
+ existing.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
1333
+ config[agentId] = existing;
1334
+ saveAgentConfig(config);
1335
+ return { ok: true };
1336
+ }
1288
1337
  function clearAgentRuntime(agentId) {
1289
1338
  const config = loadAgentConfig();
1290
1339
  delete config[agentId];
@@ -25,7 +25,7 @@ __export(keychain_exports, {
25
25
  importMnemonic: () => importMnemonic,
26
26
  setMasterKey: () => setMasterKey
27
27
  });
28
- import { readFile, writeFile, unlink, mkdir, chmod } from "fs/promises";
28
+ import { readFile, writeFile, unlink, mkdir, chmod, rename, copyFile } from "fs/promises";
29
29
  import { existsSync, statSync } from "fs";
30
30
  import { execSync } from "child_process";
31
31
  import path from "path";
@@ -60,12 +60,14 @@ function linuxSecretAvailable() {
60
60
  function isRootOnlyTrustedServerKeyFile(keyPath) {
61
61
  if (process.platform !== "linux") return false;
62
62
  try {
63
- const uid = typeof os.userInfo().uid === "number" ? os.userInfo().uid : -1;
64
63
  const st = statSync(keyPath);
65
64
  if (!st.isFile() || (st.mode & 63) !== 0) return false;
65
+ const uid = typeof os.userInfo().uid === "number" ? os.userInfo().uid : -1;
66
66
  if (uid === 0) return true;
67
67
  const exeOsDir = process.env.EXE_OS_DIR;
68
- return Boolean(exeOsDir && path.resolve(keyPath).startsWith(path.resolve(exeOsDir) + path.sep));
68
+ if (exeOsDir && path.resolve(keyPath).startsWith(path.resolve(exeOsDir) + path.sep)) return true;
69
+ if (!linuxSecretAvailable()) return true;
70
+ return false;
69
71
  } catch {
70
72
  return false;
71
73
  }
@@ -215,15 +217,25 @@ async function writeMachineBoundFileFallback(b64) {
215
217
  await mkdir(dir, { recursive: true });
216
218
  const keyPath = getKeyPath();
217
219
  const machineKey = deriveMachineKey();
218
- if (machineKey) {
219
- const encrypted = encryptWithMachineKey(b64, machineKey);
220
- await writeFile(keyPath, encrypted + "\n", "utf-8");
221
- await chmod(keyPath, 384);
222
- return "encrypted";
220
+ const content = machineKey ? encryptWithMachineKey(b64, machineKey) + "\n" : b64 + "\n";
221
+ const result = machineKey ? "encrypted" : "plaintext";
222
+ const tmpPath = keyPath + ".tmp";
223
+ try {
224
+ if (existsSync(keyPath)) {
225
+ await copyFile(keyPath, keyPath + ".bak").catch(() => {
226
+ });
227
+ }
228
+ await writeFile(tmpPath, content, "utf-8");
229
+ await chmod(tmpPath, 384);
230
+ await rename(tmpPath, keyPath);
231
+ } catch (err) {
232
+ try {
233
+ await unlink(tmpPath);
234
+ } catch {
235
+ }
236
+ throw err;
223
237
  }
224
- await writeFile(keyPath, b64 + "\n", "utf-8");
225
- await chmod(keyPath, 384);
226
- return "plaintext";
238
+ return result;
227
239
  }
228
240
  async function getMasterKey() {
229
241
  let nativeValue = macKeychainGet() ?? linuxSecretGet();
@@ -3772,6 +3784,22 @@ async function ensureSchema() {
3772
3784
  } catch (e) {
3773
3785
  logCatchDebug("migration", e);
3774
3786
  }
3787
+ try {
3788
+ await client.execute({
3789
+ sql: `ALTER TABLE memories ADD COLUMN visibility TEXT DEFAULT 'private'`,
3790
+ args: []
3791
+ });
3792
+ } catch (e) {
3793
+ logCatchDebug("migration", e);
3794
+ }
3795
+ try {
3796
+ await client.execute({
3797
+ sql: `ALTER TABLE memories ADD COLUMN strength REAL DEFAULT 1.0`,
3798
+ args: []
3799
+ });
3800
+ } catch (e) {
3801
+ logCatchDebug("migration", e);
3802
+ }
3775
3803
  }
3776
3804
  async function disposeDatabase() {
3777
3805
  if (_walCheckpointTimer) {
@@ -4269,7 +4297,7 @@ async function assertVpsLicense(opts) {
4269
4297
  }
4270
4298
  if (!transientFailure) {
4271
4299
  throw new Error(
4272
- "License validation failed: unknown backend state. Restore network connectivity to https://askexe.com/cloud and retry."
4300
+ "License validation failed: unknown backend state. Restore network connectivity to https://cloud.askexe.com and retry."
4273
4301
  );
4274
4302
  }
4275
4303
  const fresh = await getCachedLicense();
@@ -4306,7 +4334,7 @@ async function assertVpsLicense(opts) {
4306
4334
  } catch {
4307
4335
  }
4308
4336
  throw new Error(
4309
- `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.`
4337
+ `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.`
4310
4338
  );
4311
4339
  }
4312
4340
  function startLicenseRevalidation(intervalMs = 36e5) {
@@ -4338,7 +4366,7 @@ var init_license = __esm({
4338
4366
  LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
4339
4367
  CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
4340
4368
  DEVICE_ID_PATH = path7.join(EXE_AI_DIR, "device-id");
4341
- API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
4369
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
4342
4370
  RETRY_DELAY_MS = 500;
4343
4371
  LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
4344
4372
  MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
@@ -4745,6 +4773,7 @@ __export(cloud_sync_exports, {
4745
4773
  markCloudReuploadRequired: () => markCloudReuploadRequired,
4746
4774
  mergeConfig: () => mergeConfig,
4747
4775
  mergeRosterFromRemote: () => mergeRosterFromRemote,
4776
+ migrateEndpoint: () => migrateEndpoint,
4748
4777
  pushToPostgres: () => pushToPostgres,
4749
4778
  recordRosterDeletion: () => recordRosterDeletion
4750
4779
  });
@@ -4915,6 +4944,15 @@ async function fetchWithRetry(url, init) {
4915
4944
  }
4916
4945
  throw lastError;
4917
4946
  }
4947
+ function migrateEndpoint(endpoint) {
4948
+ if (endpoint === "https://askexe.com/cloud" || endpoint === "https://askexe.com/cloud/") {
4949
+ process.stderr.write(
4950
+ "[cloud-sync] Auto-migrating endpoint from askexe.com/cloud to cloud.askexe.com (bypasses Cloudflare WAF for datacenter IPs)\n"
4951
+ );
4952
+ return "https://cloud.askexe.com";
4953
+ }
4954
+ return endpoint;
4955
+ }
4918
4956
  function assertSecureEndpoint(endpoint) {
4919
4957
  if (endpoint.startsWith("https://")) return;
4920
4958
  if (endpoint.startsWith("http://")) {
@@ -5052,6 +5090,7 @@ async function markCloudReuploadRequired(client = getClient()) {
5052
5090
  await client.execute("INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('cloud_reupload_required', '1')");
5053
5091
  }
5054
5092
  async function cloudSync(config) {
5093
+ config = { ...config, endpoint: migrateEndpoint(config.endpoint) };
5055
5094
  if (!isSyncCryptoInitialized()) {
5056
5095
  try {
5057
5096
  const { getMasterKey: getMasterKey2 } = await Promise.resolve().then(() => (init_keychain(), keychain_exports));
@@ -5141,6 +5180,27 @@ async function cloudSync(config) {
5141
5180
  if (stmts.length > 0) await client.batch(stmts, "write");
5142
5181
  pulled = pullResult.records.length;
5143
5182
  } else {
5183
+ try {
5184
+ const incomingIds = pullResult.records.map((r) => sqlSafe(r.id));
5185
+ if (incomingIds.length > 0) {
5186
+ const ph = incomingIds.map(() => "?").join(",");
5187
+ const existing = await client.execute({
5188
+ sql: `SELECT id, version, timestamp FROM memories WHERE id IN (${ph})`,
5189
+ args: incomingIds
5190
+ });
5191
+ const localMap = new Map(existing.rows.map((r) => [String(r.id), r]));
5192
+ for (const rec of pullResult.records) {
5193
+ const local = localMap.get(String(rec.id));
5194
+ if (local && Number(local.version) > 0 && Number(local.version) !== Number(rec.version ?? 0)) {
5195
+ process.stderr.write(
5196
+ `[cloud-sync] CONFLICT: memory ${String(rec.id).slice(0, 8)} \u2014 local v${local.version} vs remote v${rec.version ?? 0}. Remote wins (LWW).
5197
+ `
5198
+ );
5199
+ }
5200
+ }
5201
+ }
5202
+ } catch {
5203
+ }
5144
5204
  const stmts = pullResult.records.map((rec) => ({
5145
5205
  sql: `INSERT OR REPLACE INTO memories
5146
5206
  (id, agent_id, agent_role, session_id, timestamp,
@@ -6898,11 +6958,17 @@ var init_platform_procedures = __esm({
6898
6958
  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."
6899
6959
  },
6900
6960
  {
6901
- title: "Customer orchestration maturity \u2014 recommend, never trap",
6961
+ title: "Orchestration phase guidance \u2014 recommend, never trap",
6902
6962
  domain: "workflow",
6903
6963
  priority: "p1",
6904
6964
  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."
6905
6965
  },
6966
+ {
6967
+ title: "Routing slot vs display title \u2014 internal 'coo' is plumbing, not your name",
6968
+ domain: "identity",
6969
+ priority: "p0",
6970
+ 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."
6971
+ },
6906
6972
  {
6907
6973
  title: "Single dispatch path \u2014 create_task only",
6908
6974
  domain: "workflow",
@@ -6936,6 +7002,12 @@ var init_platform_procedures = __esm({
6936
7002
  priority: "p0",
6937
7003
  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."
6938
7004
  },
7005
+ {
7006
+ title: "Destructive operations \u2014 mandatory reviewer gate",
7007
+ domain: "security",
7008
+ priority: "p0",
7009
+ 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."
7010
+ },
6939
7011
  {
6940
7012
  title: "Customer patch triage \u2014 upstream bug vs customization",
6941
7013
  domain: "support",
@@ -7221,10 +7293,24 @@ function stableId(memoryId, type, content) {
7221
7293
  return createHash2("sha256").update(`${memoryId}:${type}:${content}`).digest("hex").slice(0, 32);
7222
7294
  }
7223
7295
  function cleanText(text) {
7224
- return text.replace(/```[\s\S]*?```/g, " ").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
7296
+ let cleaned = text.replace(
7297
+ /```(\w*)\n(.*?)(?:\n[\s\S]*?)```/g,
7298
+ (_m, lang, firstLine) => `[code${lang ? `:${lang}` : ""}] ${firstLine.trim()}`
7299
+ );
7300
+ cleaned = cleaned.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
7301
+ return cleaned;
7225
7302
  }
7226
- function splitSentences(text) {
7227
- return cleanText(text).split(/(?<=[.!?])\s+|\n+/).map((s) => s.trim()).filter((s) => s.length >= 24 && s.length <= MAX_SENTENCE_CHARS);
7303
+ function splitSegments(text) {
7304
+ const cleaned = cleanText(text);
7305
+ 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);
7306
+ if (segments.length === 0 && cleaned.length >= MIN_SEGMENT_CHARS) {
7307
+ const lines = cleaned.split(/\n+/).map((l) => l.trim()).filter((l) => l.length >= MIN_SEGMENT_CHARS && l.length <= MAX_SEGMENT_CHARS);
7308
+ if (lines.length > 0) return lines;
7309
+ if (cleaned.length >= MIN_SEGMENT_CHARS) {
7310
+ return [cleaned.slice(0, MAX_SEGMENT_CHARS)];
7311
+ }
7312
+ }
7313
+ return segments;
7228
7314
  }
7229
7315
  function inferCardType(sentence, toolName) {
7230
7316
  const lower = sentence.toLowerCase();
@@ -7256,12 +7342,12 @@ function predicateFor(type) {
7256
7342
  }
7257
7343
  }
7258
7344
  function extractMemoryCards(row) {
7259
- const sentences = splitSentences(row.raw_text);
7345
+ const segments = splitSegments(row.raw_text);
7260
7346
  const cards = [];
7261
- for (const sentence of sentences) {
7347
+ for (const sentence of segments) {
7262
7348
  const type = inferCardType(sentence, row.tool_name);
7263
7349
  const subject = extractSubject(sentence, row.agent_id);
7264
- const content = sentence.length > MAX_SENTENCE_CHARS ? `${sentence.slice(0, MAX_SENTENCE_CHARS - 1)}\u2026` : sentence;
7350
+ const content = sentence.length > MAX_SEGMENT_CHARS ? `${sentence.slice(0, MAX_SEGMENT_CHARS - 1)}\u2026` : sentence;
7265
7351
  cards.push({
7266
7352
  id: stableId(row.id, type, content),
7267
7353
  memory_id: row.id,
@@ -7357,13 +7443,14 @@ Source memory: ${String(row.source_ref ?? row.memory_id)}`,
7357
7443
  last_accessed: String(row.timestamp)
7358
7444
  }));
7359
7445
  }
7360
- var MAX_CARDS_PER_MEMORY, MAX_SENTENCE_CHARS;
7446
+ var MAX_CARDS_PER_MEMORY, MAX_SEGMENT_CHARS, MIN_SEGMENT_CHARS;
7361
7447
  var init_memory_cards = __esm({
7362
7448
  "src/lib/memory-cards.ts"() {
7363
7449
  "use strict";
7364
7450
  init_database();
7365
- MAX_CARDS_PER_MEMORY = 6;
7366
- MAX_SENTENCE_CHARS = 360;
7451
+ MAX_CARDS_PER_MEMORY = 8;
7452
+ MAX_SEGMENT_CHARS = 500;
7453
+ MIN_SEGMENT_CHARS = 20;
7367
7454
  }
7368
7455
  });
7369
7456
 
@@ -8486,7 +8573,7 @@ async function setupMode() {
8486
8573
  rl.close();
8487
8574
  return;
8488
8575
  }
8489
- const endpoint = await ask(rl, " Endpoint [https://askexe.com/cloud]: ") || "https://askexe.com/cloud";
8576
+ const endpoint = await ask(rl, " Endpoint [https://cloud.askexe.com]: ") || "https://cloud.askexe.com";
8490
8577
  console.log(" Validating...");
8491
8578
  try {
8492
8579
  assertSecureEndpoint(endpoint);
@@ -8552,6 +8639,14 @@ async function setupMode() {
8552
8639
  }
8553
8640
  console.log("");
8554
8641
  }
8642
+ try {
8643
+ const keyInfo = await getKeyStorageInfo();
8644
+ if (keyInfo.kind !== "missing") {
8645
+ console.log(` \u2713 Encryption key: ${keyInfo.note}`);
8646
+ }
8647
+ } catch {
8648
+ }
8649
+ console.log("");
8555
8650
  console.log(BAR);
8556
8651
  console.log(latestConfig.cloud?.apiKey ? " Setup complete. Cloud sync is connected." : " Setup complete. Cloud sync is not configured yet.");
8557
8652
  console.log(BAR);
@@ -8643,7 +8738,13 @@ async function statusMode() {
8643
8738
  console.log("");
8644
8739
  if (key) {
8645
8740
  const fingerprint = key.toString("hex").slice(0, 8);
8646
- console.log(` Encryption key: \u2713 present (${fingerprint}...)`);
8741
+ let storageNote = "";
8742
+ try {
8743
+ const keyInfo = await getKeyStorageInfo();
8744
+ if (keyInfo.kind !== "missing") storageNote = ` [${keyInfo.note}]`;
8745
+ } catch {
8746
+ }
8747
+ console.log(` Encryption key: \u2713 present (${fingerprint}...)${storageNote}`);
8647
8748
  } else {
8648
8749
  console.log(" Encryption key: \u2717 not found \u2014 run /exe-setup");
8649
8750
  }