@askexenow/exe-os 0.9.111 → 0.9.113

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 (95) hide show
  1. package/README.md +9 -7
  2. package/dist/bin/agentic-ontology-backfill.js +62 -12
  3. package/dist/bin/agentic-reflection-backfill.js +37 -2
  4. package/dist/bin/agentic-semantic-label.js +37 -2
  5. package/dist/bin/backfill-conversations.js +61 -11
  6. package/dist/bin/backfill-responses.js +62 -12
  7. package/dist/bin/backfill-vectors.js +37 -2
  8. package/dist/bin/bulk-sync-postgres.js +63 -13
  9. package/dist/bin/cleanup-stale-review-tasks.js +83 -16
  10. package/dist/bin/cli.js +312 -80
  11. package/dist/bin/exe-agent-config.js +7 -1
  12. package/dist/bin/exe-agent.js +29 -3
  13. package/dist/bin/exe-assign.js +62 -12
  14. package/dist/bin/exe-boot.js +500 -151
  15. package/dist/bin/exe-call.js +46 -5
  16. package/dist/bin/exe-cloud.js +101 -16
  17. package/dist/bin/exe-dispatch.js +827 -27
  18. package/dist/bin/exe-doctor.js +61 -11
  19. package/dist/bin/exe-export-behaviors.js +67 -14
  20. package/dist/bin/exe-forget.js +62 -12
  21. package/dist/bin/exe-gateway.js +147 -27
  22. package/dist/bin/exe-heartbeat.js +83 -16
  23. package/dist/bin/exe-kill.js +62 -12
  24. package/dist/bin/exe-launch-agent.js +83 -15
  25. package/dist/bin/exe-new-employee.js +176 -8
  26. package/dist/bin/exe-pending-messages.js +83 -16
  27. package/dist/bin/exe-pending-notifications.js +83 -16
  28. package/dist/bin/exe-pending-reviews.js +83 -16
  29. package/dist/bin/exe-rename.js +62 -12
  30. package/dist/bin/exe-review.js +62 -12
  31. package/dist/bin/exe-search.js +62 -12
  32. package/dist/bin/exe-session-cleanup.js +949 -149
  33. package/dist/bin/exe-settings.js +10 -4
  34. package/dist/bin/exe-start-codex.js +537 -248
  35. package/dist/bin/exe-start-opencode.js +547 -168
  36. package/dist/bin/exe-status.js +83 -16
  37. package/dist/bin/exe-support.js +1 -1
  38. package/dist/bin/exe-team.js +62 -12
  39. package/dist/bin/git-sweep.js +827 -27
  40. package/dist/bin/graph-backfill.js +62 -12
  41. package/dist/bin/graph-export.js +62 -12
  42. package/dist/bin/install.js +62 -4
  43. package/dist/bin/intercom-check.js +949 -149
  44. package/dist/bin/pre-publish.js +14 -2
  45. package/dist/bin/scan-tasks.js +827 -27
  46. package/dist/bin/setup.js +99 -14
  47. package/dist/bin/shard-migrate.js +62 -12
  48. package/dist/bin/stack-update.js +1 -1
  49. package/dist/bin/update.js +3 -3
  50. package/dist/gateway/index.js +586 -26
  51. package/dist/hooks/bug-report-worker.js +586 -26
  52. package/dist/hooks/codex-stop-task-finalizer.js +977 -143
  53. package/dist/hooks/commit-complete.js +827 -27
  54. package/dist/hooks/error-recall.js +62 -12
  55. package/dist/hooks/ingest.js +4579 -249
  56. package/dist/hooks/instructions-loaded.js +62 -12
  57. package/dist/hooks/notification.js +62 -12
  58. package/dist/hooks/post-compact.js +83 -16
  59. package/dist/hooks/post-tool-combined.js +83 -16
  60. package/dist/hooks/pre-compact.js +907 -107
  61. package/dist/hooks/pre-tool-use.js +98 -16
  62. package/dist/hooks/prompt-submit.js +596 -30
  63. package/dist/hooks/session-end.js +909 -112
  64. package/dist/hooks/session-start.js +112 -17
  65. package/dist/hooks/stop.js +82 -15
  66. package/dist/hooks/subagent-stop.js +83 -16
  67. package/dist/hooks/summary-worker.js +81 -8
  68. package/dist/index.js +595 -29
  69. package/dist/lib/agent-config.js +16 -1
  70. package/dist/lib/cloud-sync.js +45 -1
  71. package/dist/lib/consolidation.js +16 -1
  72. package/dist/lib/database.js +23 -0
  73. package/dist/lib/db.js +23 -0
  74. package/dist/lib/device-registry.js +23 -0
  75. package/dist/lib/employee-templates.js +30 -4
  76. package/dist/lib/employees.js +16 -1
  77. package/dist/lib/exe-daemon.js +482 -52
  78. package/dist/lib/hybrid-search.js +62 -12
  79. package/dist/lib/license.js +3 -3
  80. package/dist/lib/messaging.js +21 -4
  81. package/dist/lib/schedules.js +37 -2
  82. package/dist/lib/skill-learning.js +910 -41
  83. package/dist/lib/status-brief.js +14 -1
  84. package/dist/lib/store.js +62 -12
  85. package/dist/lib/tasks.js +843 -93
  86. package/dist/lib/tmux-routing.js +766 -16
  87. package/dist/mcp/server.js +238 -41
  88. package/dist/mcp/tools/create-task.js +525 -15
  89. package/dist/mcp/tools/deactivate-behavior.js +33 -24
  90. package/dist/mcp/tools/list-tasks.js +21 -4
  91. package/dist/mcp/tools/send-message.js +21 -4
  92. package/dist/mcp/tools/update-task.js +840 -93
  93. package/dist/runtime/index.js +913 -107
  94. package/dist/tui/App.js +227 -58
  95. package/package.json +1 -1
@@ -2037,6 +2037,13 @@ async function ensureSchema() {
2037
2037
  } catch (e) {
2038
2038
  logCatchDebug("migration", e);
2039
2039
  }
2040
+ for (const col of ["created_by_agent TEXT", "created_by_device TEXT", "source_session_id TEXT"]) {
2041
+ try {
2042
+ await client.execute({ sql: `ALTER TABLE behaviors ADD COLUMN ${col}`, args: [] });
2043
+ } catch (e) {
2044
+ logCatchDebug("migration", e);
2045
+ }
2046
+ }
2040
2047
  try {
2041
2048
  await client.execute({
2042
2049
  sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
@@ -3253,6 +3260,22 @@ async function ensureSchema() {
3253
3260
  } catch (e) {
3254
3261
  logCatchDebug("migration", e);
3255
3262
  }
3263
+ try {
3264
+ await client.execute({
3265
+ sql: `ALTER TABLE memories ADD COLUMN visibility TEXT DEFAULT 'private'`,
3266
+ args: []
3267
+ });
3268
+ } catch (e) {
3269
+ logCatchDebug("migration", e);
3270
+ }
3271
+ try {
3272
+ await client.execute({
3273
+ sql: `ALTER TABLE memories ADD COLUMN strength REAL DEFAULT 1.0`,
3274
+ args: []
3275
+ });
3276
+ } catch (e) {
3277
+ logCatchDebug("migration", e);
3278
+ }
3256
3279
  }
3257
3280
  async function disposeDatabase() {
3258
3281
  if (_walCheckpointTimer) {
@@ -3603,7 +3626,7 @@ var init_license = __esm({
3603
3626
  LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
3604
3627
  CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
3605
3628
  DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
3606
- API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
3629
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
3607
3630
  }
3608
3631
  });
3609
3632
 
@@ -3656,6 +3679,18 @@ function extractRootExe(name) {
3656
3679
  const parts = name.split("-").filter(Boolean);
3657
3680
  return parts.length > 0 ? parts[parts.length - 1] : null;
3658
3681
  }
3682
+ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
3683
+ if (!existsSync12(SESSION_CACHE)) {
3684
+ mkdirSync6(SESSION_CACHE, { recursive: true });
3685
+ }
3686
+ const rootExe = extractRootExe(parentExe) ?? parentExe;
3687
+ const filePath = path12.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
3688
+ writeFileSync6(filePath, JSON.stringify({
3689
+ parentExe: rootExe,
3690
+ dispatchedBy: dispatchedBy || rootExe,
3691
+ registeredAt: (/* @__PURE__ */ new Date()).toISOString()
3692
+ }));
3693
+ }
3659
3694
  function getParentExe(sessionKey) {
3660
3695
  try {
3661
3696
  const data = JSON.parse(readFileSync9(path12.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
@@ -3665,11 +3700,12 @@ function getParentExe(sessionKey) {
3665
3700
  }
3666
3701
  }
3667
3702
  function resolveExeSession() {
3703
+ if (process.env.EXE_SESSION_NAME) {
3704
+ const fromEnv = extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
3705
+ if (fromEnv) return fromEnv;
3706
+ }
3668
3707
  const mySession = getMySession();
3669
3708
  if (!mySession) {
3670
- if (process.env.EXE_SESSION_NAME) {
3671
- return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
3672
- }
3673
3709
  return null;
3674
3710
  }
3675
3711
  const fromSessionName = extractRootExe(mySession);
@@ -3684,6 +3720,10 @@ function resolveExeSession() {
3684
3720
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
3685
3721
  `
3686
3722
  );
3723
+ try {
3724
+ registerParentExe(key, fromSessionName);
3725
+ } catch {
3726
+ }
3687
3727
  candidate = fromSessionName;
3688
3728
  } else {
3689
3729
  candidate = fromCache;
@@ -4912,11 +4952,17 @@ var init_platform_procedures = __esm({
4912
4952
  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."
4913
4953
  },
4914
4954
  {
4915
- title: "Customer orchestration maturity \u2014 recommend, never trap",
4955
+ title: "Orchestration phase guidance \u2014 recommend, never trap",
4916
4956
  domain: "workflow",
4917
4957
  priority: "p1",
4918
4958
  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."
4919
4959
  },
4960
+ {
4961
+ title: "Routing slot vs display title \u2014 internal 'coo' is plumbing, not your name",
4962
+ domain: "identity",
4963
+ priority: "p0",
4964
+ 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."
4965
+ },
4920
4966
  {
4921
4967
  title: "Single dispatch path \u2014 create_task only",
4922
4968
  domain: "workflow",
@@ -4950,6 +4996,12 @@ var init_platform_procedures = __esm({
4950
4996
  priority: "p0",
4951
4997
  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."
4952
4998
  },
4999
+ {
5000
+ title: "Destructive operations \u2014 mandatory reviewer gate",
5001
+ domain: "security",
5002
+ priority: "p0",
5003
+ 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."
5004
+ },
4953
5005
  {
4954
5006
  title: "Customer patch triage \u2014 upstream bug vs customization",
4955
5007
  domain: "support",
@@ -5101,7 +5153,7 @@ var init_platform_procedures = __esm({
5101
5153
  title: "MCP tool dispatch \u2014 all tools use action parameter",
5102
5154
  domain: "tool-use",
5103
5155
  priority: "p0",
5104
- content: 'exe-os MCP tools come in two surfaces depending on EXE_MCP_TOOL_SURFACE config. Consolidated (19 tools): action-based dispatch \u2014 memory(action="recall"), task(action="create"), etc. Legacy (108 tools): one tool per operation \u2014 recall_my_memory, create_task, etc. Both surfaces have identical functionality. Use whichever tool names are available in your session. If you see domain tools (memory, task, config, etc.), use the action parameter. If you see specific tools (recall_my_memory, create_task, etc.), call them directly.'
5156
+ content: 'exe-os MCP tools use consolidated action-based dispatch by default (19 tools). Call domain tools with an action parameter: memory(action="recall"), task(action="create"), config(action="list_employees"), etc. Legacy mode (108 separate tools like recall_my_memory, create_task) is still available via EXE_MCP_TOOL_SURFACE=legacy but will be removed in a future version. If you see specific tool names, call them directly \u2014 both surfaces are identical. Consolidated is the default and recommended surface.'
5105
5157
  },
5106
5158
  {
5107
5159
  title: "MCP tools \u2014 memory, decision, and search",
@@ -5235,10 +5287,24 @@ function stableId(memoryId, type, content) {
5235
5287
  return createHash2("sha256").update(`${memoryId}:${type}:${content}`).digest("hex").slice(0, 32);
5236
5288
  }
5237
5289
  function cleanText(text) {
5238
- return text.replace(/```[\s\S]*?```/g, " ").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
5239
- }
5240
- function splitSentences(text) {
5241
- return cleanText(text).split(/(?<=[.!?])\s+|\n+/).map((s) => s.trim()).filter((s) => s.length >= 24 && s.length <= MAX_SENTENCE_CHARS);
5290
+ let cleaned = text.replace(
5291
+ /```(\w*)\n(.*?)(?:\n[\s\S]*?)```/g,
5292
+ (_m, lang, firstLine) => `[code${lang ? `:${lang}` : ""}] ${firstLine.trim()}`
5293
+ );
5294
+ cleaned = cleaned.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
5295
+ return cleaned;
5296
+ }
5297
+ function splitSegments(text) {
5298
+ const cleaned = cleanText(text);
5299
+ 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);
5300
+ if (segments.length === 0 && cleaned.length >= MIN_SEGMENT_CHARS) {
5301
+ const lines = cleaned.split(/\n+/).map((l) => l.trim()).filter((l) => l.length >= MIN_SEGMENT_CHARS && l.length <= MAX_SEGMENT_CHARS);
5302
+ if (lines.length > 0) return lines;
5303
+ if (cleaned.length >= MIN_SEGMENT_CHARS) {
5304
+ return [cleaned.slice(0, MAX_SEGMENT_CHARS)];
5305
+ }
5306
+ }
5307
+ return segments;
5242
5308
  }
5243
5309
  function inferCardType(sentence, toolName) {
5244
5310
  const lower = sentence.toLowerCase();
@@ -5270,12 +5336,12 @@ function predicateFor(type) {
5270
5336
  }
5271
5337
  }
5272
5338
  function extractMemoryCards(row) {
5273
- const sentences = splitSentences(row.raw_text);
5339
+ const segments = splitSegments(row.raw_text);
5274
5340
  const cards = [];
5275
- for (const sentence of sentences) {
5341
+ for (const sentence of segments) {
5276
5342
  const type = inferCardType(sentence, row.tool_name);
5277
5343
  const subject = extractSubject(sentence, row.agent_id);
5278
- const content = sentence.length > MAX_SENTENCE_CHARS ? `${sentence.slice(0, MAX_SENTENCE_CHARS - 1)}\u2026` : sentence;
5344
+ const content = sentence.length > MAX_SEGMENT_CHARS ? `${sentence.slice(0, MAX_SEGMENT_CHARS - 1)}\u2026` : sentence;
5279
5345
  cards.push({
5280
5346
  id: stableId(row.id, type, content),
5281
5347
  memory_id: row.id,
@@ -5371,13 +5437,14 @@ Source memory: ${String(row.source_ref ?? row.memory_id)}`,
5371
5437
  last_accessed: String(row.timestamp)
5372
5438
  }));
5373
5439
  }
5374
- var MAX_CARDS_PER_MEMORY, MAX_SENTENCE_CHARS;
5440
+ var MAX_CARDS_PER_MEMORY, MAX_SEGMENT_CHARS, MIN_SEGMENT_CHARS;
5375
5441
  var init_memory_cards = __esm({
5376
5442
  "src/lib/memory-cards.ts"() {
5377
5443
  "use strict";
5378
5444
  init_database();
5379
- MAX_CARDS_PER_MEMORY = 6;
5380
- MAX_SENTENCE_CHARS = 360;
5445
+ MAX_CARDS_PER_MEMORY = 8;
5446
+ MAX_SEGMENT_CHARS = 500;
5447
+ MIN_SEGMENT_CHARS = 20;
5381
5448
  }
5382
5449
  });
5383
5450
 
@@ -2037,6 +2037,13 @@ async function ensureSchema() {
2037
2037
  } catch (e) {
2038
2038
  logCatchDebug("migration", e);
2039
2039
  }
2040
+ for (const col of ["created_by_agent TEXT", "created_by_device TEXT", "source_session_id TEXT"]) {
2041
+ try {
2042
+ await client.execute({ sql: `ALTER TABLE behaviors ADD COLUMN ${col}`, args: [] });
2043
+ } catch (e) {
2044
+ logCatchDebug("migration", e);
2045
+ }
2046
+ }
2040
2047
  try {
2041
2048
  await client.execute({
2042
2049
  sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
@@ -3253,6 +3260,22 @@ async function ensureSchema() {
3253
3260
  } catch (e) {
3254
3261
  logCatchDebug("migration", e);
3255
3262
  }
3263
+ try {
3264
+ await client.execute({
3265
+ sql: `ALTER TABLE memories ADD COLUMN visibility TEXT DEFAULT 'private'`,
3266
+ args: []
3267
+ });
3268
+ } catch (e) {
3269
+ logCatchDebug("migration", e);
3270
+ }
3271
+ try {
3272
+ await client.execute({
3273
+ sql: `ALTER TABLE memories ADD COLUMN strength REAL DEFAULT 1.0`,
3274
+ args: []
3275
+ });
3276
+ } catch (e) {
3277
+ logCatchDebug("migration", e);
3278
+ }
3256
3279
  }
3257
3280
  async function disposeDatabase() {
3258
3281
  if (_walCheckpointTimer) {
@@ -3603,7 +3626,7 @@ var init_license = __esm({
3603
3626
  LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
3604
3627
  CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
3605
3628
  DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
3606
- API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
3629
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
3607
3630
  }
3608
3631
  });
3609
3632
 
@@ -3656,6 +3679,18 @@ function extractRootExe(name) {
3656
3679
  const parts = name.split("-").filter(Boolean);
3657
3680
  return parts.length > 0 ? parts[parts.length - 1] : null;
3658
3681
  }
3682
+ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
3683
+ if (!existsSync12(SESSION_CACHE)) {
3684
+ mkdirSync6(SESSION_CACHE, { recursive: true });
3685
+ }
3686
+ const rootExe = extractRootExe(parentExe) ?? parentExe;
3687
+ const filePath = path12.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
3688
+ writeFileSync6(filePath, JSON.stringify({
3689
+ parentExe: rootExe,
3690
+ dispatchedBy: dispatchedBy || rootExe,
3691
+ registeredAt: (/* @__PURE__ */ new Date()).toISOString()
3692
+ }));
3693
+ }
3659
3694
  function getParentExe(sessionKey) {
3660
3695
  try {
3661
3696
  const data = JSON.parse(readFileSync9(path12.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
@@ -3665,11 +3700,12 @@ function getParentExe(sessionKey) {
3665
3700
  }
3666
3701
  }
3667
3702
  function resolveExeSession() {
3703
+ if (process.env.EXE_SESSION_NAME) {
3704
+ const fromEnv = extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
3705
+ if (fromEnv) return fromEnv;
3706
+ }
3668
3707
  const mySession = getMySession();
3669
3708
  if (!mySession) {
3670
- if (process.env.EXE_SESSION_NAME) {
3671
- return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
3672
- }
3673
3709
  return null;
3674
3710
  }
3675
3711
  const fromSessionName = extractRootExe(mySession);
@@ -3684,6 +3720,10 @@ function resolveExeSession() {
3684
3720
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
3685
3721
  `
3686
3722
  );
3723
+ try {
3724
+ registerParentExe(key, fromSessionName);
3725
+ } catch {
3726
+ }
3687
3727
  candidate = fromSessionName;
3688
3728
  } else {
3689
3729
  candidate = fromCache;
@@ -4951,11 +4991,17 @@ var init_platform_procedures = __esm({
4951
4991
  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."
4952
4992
  },
4953
4993
  {
4954
- title: "Customer orchestration maturity \u2014 recommend, never trap",
4994
+ title: "Orchestration phase guidance \u2014 recommend, never trap",
4955
4995
  domain: "workflow",
4956
4996
  priority: "p1",
4957
4997
  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."
4958
4998
  },
4999
+ {
5000
+ title: "Routing slot vs display title \u2014 internal 'coo' is plumbing, not your name",
5001
+ domain: "identity",
5002
+ priority: "p0",
5003
+ 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."
5004
+ },
4959
5005
  {
4960
5006
  title: "Single dispatch path \u2014 create_task only",
4961
5007
  domain: "workflow",
@@ -4989,6 +5035,12 @@ var init_platform_procedures = __esm({
4989
5035
  priority: "p0",
4990
5036
  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."
4991
5037
  },
5038
+ {
5039
+ title: "Destructive operations \u2014 mandatory reviewer gate",
5040
+ domain: "security",
5041
+ priority: "p0",
5042
+ 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."
5043
+ },
4992
5044
  {
4993
5045
  title: "Customer patch triage \u2014 upstream bug vs customization",
4994
5046
  domain: "support",
@@ -5140,7 +5192,7 @@ var init_platform_procedures = __esm({
5140
5192
  title: "MCP tool dispatch \u2014 all tools use action parameter",
5141
5193
  domain: "tool-use",
5142
5194
  priority: "p0",
5143
- content: 'exe-os MCP tools come in two surfaces depending on EXE_MCP_TOOL_SURFACE config. Consolidated (19 tools): action-based dispatch \u2014 memory(action="recall"), task(action="create"), etc. Legacy (108 tools): one tool per operation \u2014 recall_my_memory, create_task, etc. Both surfaces have identical functionality. Use whichever tool names are available in your session. If you see domain tools (memory, task, config, etc.), use the action parameter. If you see specific tools (recall_my_memory, create_task, etc.), call them directly.'
5195
+ content: 'exe-os MCP tools use consolidated action-based dispatch by default (19 tools). Call domain tools with an action parameter: memory(action="recall"), task(action="create"), config(action="list_employees"), etc. Legacy mode (108 separate tools like recall_my_memory, create_task) is still available via EXE_MCP_TOOL_SURFACE=legacy but will be removed in a future version. If you see specific tool names, call them directly \u2014 both surfaces are identical. Consolidated is the default and recommended surface.'
5144
5196
  },
5145
5197
  {
5146
5198
  title: "MCP tools \u2014 memory, decision, and search",
@@ -5274,10 +5326,24 @@ function stableId(memoryId, type, content) {
5274
5326
  return createHash2("sha256").update(`${memoryId}:${type}:${content}`).digest("hex").slice(0, 32);
5275
5327
  }
5276
5328
  function cleanText(text) {
5277
- return text.replace(/```[\s\S]*?```/g, " ").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
5278
- }
5279
- function splitSentences(text) {
5280
- return cleanText(text).split(/(?<=[.!?])\s+|\n+/).map((s) => s.trim()).filter((s) => s.length >= 24 && s.length <= MAX_SENTENCE_CHARS);
5329
+ let cleaned = text.replace(
5330
+ /```(\w*)\n(.*?)(?:\n[\s\S]*?)```/g,
5331
+ (_m, lang, firstLine) => `[code${lang ? `:${lang}` : ""}] ${firstLine.trim()}`
5332
+ );
5333
+ cleaned = cleaned.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
5334
+ return cleaned;
5335
+ }
5336
+ function splitSegments(text) {
5337
+ const cleaned = cleanText(text);
5338
+ 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);
5339
+ if (segments.length === 0 && cleaned.length >= MIN_SEGMENT_CHARS) {
5340
+ const lines = cleaned.split(/\n+/).map((l) => l.trim()).filter((l) => l.length >= MIN_SEGMENT_CHARS && l.length <= MAX_SEGMENT_CHARS);
5341
+ if (lines.length > 0) return lines;
5342
+ if (cleaned.length >= MIN_SEGMENT_CHARS) {
5343
+ return [cleaned.slice(0, MAX_SEGMENT_CHARS)];
5344
+ }
5345
+ }
5346
+ return segments;
5281
5347
  }
5282
5348
  function inferCardType(sentence, toolName) {
5283
5349
  const lower = sentence.toLowerCase();
@@ -5309,12 +5375,12 @@ function predicateFor(type) {
5309
5375
  }
5310
5376
  }
5311
5377
  function extractMemoryCards(row) {
5312
- const sentences = splitSentences(row.raw_text);
5378
+ const segments = splitSegments(row.raw_text);
5313
5379
  const cards = [];
5314
- for (const sentence of sentences) {
5380
+ for (const sentence of segments) {
5315
5381
  const type = inferCardType(sentence, row.tool_name);
5316
5382
  const subject = extractSubject(sentence, row.agent_id);
5317
- const content = sentence.length > MAX_SENTENCE_CHARS ? `${sentence.slice(0, MAX_SENTENCE_CHARS - 1)}\u2026` : sentence;
5383
+ const content = sentence.length > MAX_SEGMENT_CHARS ? `${sentence.slice(0, MAX_SEGMENT_CHARS - 1)}\u2026` : sentence;
5318
5384
  cards.push({
5319
5385
  id: stableId(row.id, type, content),
5320
5386
  memory_id: row.id,
@@ -5410,13 +5476,14 @@ Source memory: ${String(row.source_ref ?? row.memory_id)}`,
5410
5476
  last_accessed: String(row.timestamp)
5411
5477
  }));
5412
5478
  }
5413
- var MAX_CARDS_PER_MEMORY, MAX_SENTENCE_CHARS;
5479
+ var MAX_CARDS_PER_MEMORY, MAX_SEGMENT_CHARS, MIN_SEGMENT_CHARS;
5414
5480
  var init_memory_cards = __esm({
5415
5481
  "src/lib/memory-cards.ts"() {
5416
5482
  "use strict";
5417
5483
  init_database();
5418
- MAX_CARDS_PER_MEMORY = 6;
5419
- MAX_SENTENCE_CHARS = 360;
5484
+ MAX_CARDS_PER_MEMORY = 8;
5485
+ MAX_SEGMENT_CHARS = 500;
5486
+ MIN_SEGMENT_CHARS = 20;
5420
5487
  }
5421
5488
  });
5422
5489
 
@@ -1881,6 +1881,13 @@ async function ensureSchema() {
1881
1881
  } catch (e) {
1882
1882
  logCatchDebug("migration", e);
1883
1883
  }
1884
+ for (const col of ["created_by_agent TEXT", "created_by_device TEXT", "source_session_id TEXT"]) {
1885
+ try {
1886
+ await client.execute({ sql: `ALTER TABLE behaviors ADD COLUMN ${col}`, args: [] });
1887
+ } catch (e) {
1888
+ logCatchDebug("migration", e);
1889
+ }
1890
+ }
1884
1891
  try {
1885
1892
  await client.execute({
1886
1893
  sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
@@ -3097,6 +3104,22 @@ async function ensureSchema() {
3097
3104
  } catch (e) {
3098
3105
  logCatchDebug("migration", e);
3099
3106
  }
3107
+ try {
3108
+ await client.execute({
3109
+ sql: `ALTER TABLE memories ADD COLUMN visibility TEXT DEFAULT 'private'`,
3110
+ args: []
3111
+ });
3112
+ } catch (e) {
3113
+ logCatchDebug("migration", e);
3114
+ }
3115
+ try {
3116
+ await client.execute({
3117
+ sql: `ALTER TABLE memories ADD COLUMN strength REAL DEFAULT 1.0`,
3118
+ args: []
3119
+ });
3120
+ } catch (e) {
3121
+ logCatchDebug("migration", e);
3122
+ }
3100
3123
  }
3101
3124
  async function disposeDatabase() {
3102
3125
  if (_walCheckpointTimer) {
@@ -3199,11 +3222,17 @@ var init_platform_procedures = __esm({
3199
3222
  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."
3200
3223
  },
3201
3224
  {
3202
- title: "Customer orchestration maturity \u2014 recommend, never trap",
3225
+ title: "Orchestration phase guidance \u2014 recommend, never trap",
3203
3226
  domain: "workflow",
3204
3227
  priority: "p1",
3205
3228
  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."
3206
3229
  },
3230
+ {
3231
+ title: "Routing slot vs display title \u2014 internal 'coo' is plumbing, not your name",
3232
+ domain: "identity",
3233
+ priority: "p0",
3234
+ 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."
3235
+ },
3207
3236
  {
3208
3237
  title: "Single dispatch path \u2014 create_task only",
3209
3238
  domain: "workflow",
@@ -3237,6 +3266,12 @@ var init_platform_procedures = __esm({
3237
3266
  priority: "p0",
3238
3267
  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."
3239
3268
  },
3269
+ {
3270
+ title: "Destructive operations \u2014 mandatory reviewer gate",
3271
+ domain: "security",
3272
+ priority: "p0",
3273
+ 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."
3274
+ },
3240
3275
  {
3241
3276
  title: "Customer patch triage \u2014 upstream bug vs customization",
3242
3277
  domain: "support",
@@ -3388,7 +3423,7 @@ var init_platform_procedures = __esm({
3388
3423
  title: "MCP tool dispatch \u2014 all tools use action parameter",
3389
3424
  domain: "tool-use",
3390
3425
  priority: "p0",
3391
- content: 'exe-os MCP tools come in two surfaces depending on EXE_MCP_TOOL_SURFACE config. Consolidated (19 tools): action-based dispatch \u2014 memory(action="recall"), task(action="create"), etc. Legacy (108 tools): one tool per operation \u2014 recall_my_memory, create_task, etc. Both surfaces have identical functionality. Use whichever tool names are available in your session. If you see domain tools (memory, task, config, etc.), use the action parameter. If you see specific tools (recall_my_memory, create_task, etc.), call them directly.'
3426
+ content: 'exe-os MCP tools use consolidated action-based dispatch by default (19 tools). Call domain tools with an action parameter: memory(action="recall"), task(action="create"), config(action="list_employees"), etc. Legacy mode (108 separate tools like recall_my_memory, create_task) is still available via EXE_MCP_TOOL_SURFACE=legacy but will be removed in a future version. If you see specific tool names, call them directly \u2014 both surfaces are identical. Consolidated is the default and recommended surface.'
3392
3427
  },
3393
3428
  {
3394
3429
  title: "MCP tools \u2014 memory, decision, and search",
@@ -4543,10 +4578,24 @@ function stableId(memoryId, type, content) {
4543
4578
  return createHash2("sha256").update(`${memoryId}:${type}:${content}`).digest("hex").slice(0, 32);
4544
4579
  }
4545
4580
  function cleanText(text) {
4546
- return text.replace(/```[\s\S]*?```/g, " ").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
4547
- }
4548
- function splitSentences(text) {
4549
- return cleanText(text).split(/(?<=[.!?])\s+|\n+/).map((s) => s.trim()).filter((s) => s.length >= 24 && s.length <= MAX_SENTENCE_CHARS);
4581
+ let cleaned = text.replace(
4582
+ /```(\w*)\n(.*?)(?:\n[\s\S]*?)```/g,
4583
+ (_m, lang, firstLine) => `[code${lang ? `:${lang}` : ""}] ${firstLine.trim()}`
4584
+ );
4585
+ cleaned = cleaned.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
4586
+ return cleaned;
4587
+ }
4588
+ function splitSegments(text) {
4589
+ const cleaned = cleanText(text);
4590
+ 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);
4591
+ if (segments.length === 0 && cleaned.length >= MIN_SEGMENT_CHARS) {
4592
+ const lines = cleaned.split(/\n+/).map((l) => l.trim()).filter((l) => l.length >= MIN_SEGMENT_CHARS && l.length <= MAX_SEGMENT_CHARS);
4593
+ if (lines.length > 0) return lines;
4594
+ if (cleaned.length >= MIN_SEGMENT_CHARS) {
4595
+ return [cleaned.slice(0, MAX_SEGMENT_CHARS)];
4596
+ }
4597
+ }
4598
+ return segments;
4550
4599
  }
4551
4600
  function inferCardType(sentence, toolName) {
4552
4601
  const lower = sentence.toLowerCase();
@@ -4578,12 +4627,12 @@ function predicateFor(type) {
4578
4627
  }
4579
4628
  }
4580
4629
  function extractMemoryCards(row) {
4581
- const sentences = splitSentences(row.raw_text);
4630
+ const segments = splitSegments(row.raw_text);
4582
4631
  const cards = [];
4583
- for (const sentence of sentences) {
4632
+ for (const sentence of segments) {
4584
4633
  const type = inferCardType(sentence, row.tool_name);
4585
4634
  const subject = extractSubject(sentence, row.agent_id);
4586
- const content = sentence.length > MAX_SENTENCE_CHARS ? `${sentence.slice(0, MAX_SENTENCE_CHARS - 1)}\u2026` : sentence;
4635
+ const content = sentence.length > MAX_SEGMENT_CHARS ? `${sentence.slice(0, MAX_SEGMENT_CHARS - 1)}\u2026` : sentence;
4587
4636
  cards.push({
4588
4637
  id: stableId(row.id, type, content),
4589
4638
  memory_id: row.id,
@@ -4679,13 +4728,14 @@ Source memory: ${String(row.source_ref ?? row.memory_id)}`,
4679
4728
  last_accessed: String(row.timestamp)
4680
4729
  }));
4681
4730
  }
4682
- var MAX_CARDS_PER_MEMORY, MAX_SENTENCE_CHARS;
4731
+ var MAX_CARDS_PER_MEMORY, MAX_SEGMENT_CHARS, MIN_SEGMENT_CHARS;
4683
4732
  var init_memory_cards = __esm({
4684
4733
  "src/lib/memory-cards.ts"() {
4685
4734
  "use strict";
4686
4735
  init_database();
4687
- MAX_CARDS_PER_MEMORY = 6;
4688
- MAX_SENTENCE_CHARS = 360;
4736
+ MAX_CARDS_PER_MEMORY = 8;
4737
+ MAX_SEGMENT_CHARS = 500;
4738
+ MIN_SEGMENT_CHARS = 20;
4689
4739
  }
4690
4740
  });
4691
4741