@askexenow/exe-os 0.9.112 → 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 +54 -11
  3. package/dist/bin/agentic-reflection-backfill.js +29 -1
  4. package/dist/bin/agentic-semantic-label.js +29 -1
  5. package/dist/bin/backfill-conversations.js +53 -10
  6. package/dist/bin/backfill-responses.js +54 -11
  7. package/dist/bin/backfill-vectors.js +29 -1
  8. package/dist/bin/bulk-sync-postgres.js +55 -12
  9. package/dist/bin/cleanup-stale-review-tasks.js +75 -15
  10. package/dist/bin/cli.js +293 -76
  11. package/dist/bin/exe-agent-config.js +7 -1
  12. package/dist/bin/exe-agent.js +28 -2
  13. package/dist/bin/exe-assign.js +54 -11
  14. package/dist/bin/exe-boot.js +481 -147
  15. package/dist/bin/exe-call.js +45 -4
  16. package/dist/bin/exe-cloud.js +93 -15
  17. package/dist/bin/exe-dispatch.js +369 -24
  18. package/dist/bin/exe-doctor.js +53 -10
  19. package/dist/bin/exe-export-behaviors.js +54 -11
  20. package/dist/bin/exe-forget.js +54 -11
  21. package/dist/bin/exe-gateway.js +128 -23
  22. package/dist/bin/exe-heartbeat.js +75 -15
  23. package/dist/bin/exe-kill.js +54 -11
  24. package/dist/bin/exe-launch-agent.js +70 -12
  25. package/dist/bin/exe-new-employee.js +175 -7
  26. package/dist/bin/exe-pending-messages.js +75 -15
  27. package/dist/bin/exe-pending-notifications.js +75 -15
  28. package/dist/bin/exe-pending-reviews.js +75 -15
  29. package/dist/bin/exe-rename.js +54 -11
  30. package/dist/bin/exe-review.js +54 -11
  31. package/dist/bin/exe-search.js +54 -11
  32. package/dist/bin/exe-session-cleanup.js +491 -146
  33. package/dist/bin/exe-settings.js +10 -4
  34. package/dist/bin/exe-start-codex.js +524 -245
  35. package/dist/bin/exe-start-opencode.js +534 -165
  36. package/dist/bin/exe-status.js +75 -15
  37. package/dist/bin/exe-support.js +1 -1
  38. package/dist/bin/exe-team.js +54 -11
  39. package/dist/bin/git-sweep.js +369 -24
  40. package/dist/bin/graph-backfill.js +54 -11
  41. package/dist/bin/graph-export.js +54 -11
  42. package/dist/bin/install.js +62 -4
  43. package/dist/bin/intercom-check.js +491 -146
  44. package/dist/bin/pre-publish.js +13 -1
  45. package/dist/bin/scan-tasks.js +369 -24
  46. package/dist/bin/setup.js +91 -13
  47. package/dist/bin/shard-migrate.js +54 -11
  48. package/dist/bin/stack-update.js +1 -1
  49. package/dist/bin/update.js +3 -3
  50. package/dist/gateway/index.js +128 -23
  51. package/dist/hooks/bug-report-worker.js +128 -23
  52. package/dist/hooks/codex-stop-task-finalizer.js +512 -140
  53. package/dist/hooks/commit-complete.js +369 -24
  54. package/dist/hooks/error-recall.js +54 -11
  55. package/dist/hooks/ingest.js +4575 -252
  56. package/dist/hooks/instructions-loaded.js +54 -11
  57. package/dist/hooks/notification.js +54 -11
  58. package/dist/hooks/post-compact.js +75 -15
  59. package/dist/hooks/post-tool-combined.js +75 -15
  60. package/dist/hooks/pre-compact.js +449 -104
  61. package/dist/hooks/pre-tool-use.js +90 -15
  62. package/dist/hooks/prompt-submit.js +129 -24
  63. package/dist/hooks/session-end.js +451 -109
  64. package/dist/hooks/session-start.js +104 -16
  65. package/dist/hooks/stop.js +74 -14
  66. package/dist/hooks/subagent-stop.js +75 -15
  67. package/dist/hooks/summary-worker.js +73 -7
  68. package/dist/index.js +128 -23
  69. package/dist/lib/agent-config.js +16 -1
  70. package/dist/lib/cloud-sync.js +38 -1
  71. package/dist/lib/consolidation.js +16 -1
  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 +16 -1
  77. package/dist/lib/exe-daemon.js +268 -42
  78. package/dist/lib/hybrid-search.js +54 -11
  79. package/dist/lib/license.js +3 -3
  80. package/dist/lib/messaging.js +21 -4
  81. package/dist/lib/schedules.js +29 -1
  82. package/dist/lib/skill-learning.js +458 -70
  83. package/dist/lib/status-brief.js +14 -1
  84. package/dist/lib/store.js +54 -11
  85. package/dist/lib/tasks.js +393 -91
  86. package/dist/lib/tmux-routing.js +316 -14
  87. package/dist/mcp/server.js +169 -30
  88. package/dist/mcp/tools/create-task.js +75 -13
  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 +390 -91
  93. package/dist/runtime/index.js +446 -101
  94. package/dist/tui/App.js +208 -54
  95. package/package.json +1 -1
@@ -3260,6 +3260,22 @@ async function ensureSchema() {
3260
3260
  } catch (e) {
3261
3261
  logCatchDebug("migration", e);
3262
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
+ }
3263
3279
  }
3264
3280
  async function disposeDatabase() {
3265
3281
  if (_walCheckpointTimer) {
@@ -3610,7 +3626,7 @@ var init_license = __esm({
3610
3626
  LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
3611
3627
  CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
3612
3628
  DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
3613
- API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
3629
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
3614
3630
  }
3615
3631
  });
3616
3632
 
@@ -3663,6 +3679,18 @@ function extractRootExe(name) {
3663
3679
  const parts = name.split("-").filter(Boolean);
3664
3680
  return parts.length > 0 ? parts[parts.length - 1] : null;
3665
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
+ }
3666
3694
  function getParentExe(sessionKey) {
3667
3695
  try {
3668
3696
  const data = JSON.parse(readFileSync9(path12.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
@@ -3672,11 +3700,12 @@ function getParentExe(sessionKey) {
3672
3700
  }
3673
3701
  }
3674
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
+ }
3675
3707
  const mySession = getMySession();
3676
3708
  if (!mySession) {
3677
- if (process.env.EXE_SESSION_NAME) {
3678
- return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
3679
- }
3680
3709
  return null;
3681
3710
  }
3682
3711
  const fromSessionName = extractRootExe(mySession);
@@ -3691,6 +3720,10 @@ function resolveExeSession() {
3691
3720
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
3692
3721
  `
3693
3722
  );
3723
+ try {
3724
+ registerParentExe(key, fromSessionName);
3725
+ } catch {
3726
+ }
3694
3727
  candidate = fromSessionName;
3695
3728
  } else {
3696
3729
  candidate = fromCache;
@@ -4919,11 +4952,17 @@ var init_platform_procedures = __esm({
4919
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."
4920
4953
  },
4921
4954
  {
4922
- title: "Customer orchestration maturity \u2014 recommend, never trap",
4955
+ title: "Orchestration phase guidance \u2014 recommend, never trap",
4923
4956
  domain: "workflow",
4924
4957
  priority: "p1",
4925
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."
4926
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
+ },
4927
4966
  {
4928
4967
  title: "Single dispatch path \u2014 create_task only",
4929
4968
  domain: "workflow",
@@ -4957,6 +4996,12 @@ var init_platform_procedures = __esm({
4957
4996
  priority: "p0",
4958
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."
4959
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
+ },
4960
5005
  {
4961
5006
  title: "Customer patch triage \u2014 upstream bug vs customization",
4962
5007
  domain: "support",
@@ -5242,10 +5287,24 @@ function stableId(memoryId, type, content) {
5242
5287
  return createHash2("sha256").update(`${memoryId}:${type}:${content}`).digest("hex").slice(0, 32);
5243
5288
  }
5244
5289
  function cleanText(text) {
5245
- return text.replace(/```[\s\S]*?```/g, " ").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
5246
- }
5247
- function splitSentences(text) {
5248
- 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;
5249
5308
  }
5250
5309
  function inferCardType(sentence, toolName) {
5251
5310
  const lower = sentence.toLowerCase();
@@ -5277,12 +5336,12 @@ function predicateFor(type) {
5277
5336
  }
5278
5337
  }
5279
5338
  function extractMemoryCards(row) {
5280
- const sentences = splitSentences(row.raw_text);
5339
+ const segments = splitSegments(row.raw_text);
5281
5340
  const cards = [];
5282
- for (const sentence of sentences) {
5341
+ for (const sentence of segments) {
5283
5342
  const type = inferCardType(sentence, row.tool_name);
5284
5343
  const subject = extractSubject(sentence, row.agent_id);
5285
- 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;
5286
5345
  cards.push({
5287
5346
  id: stableId(row.id, type, content),
5288
5347
  memory_id: row.id,
@@ -5378,13 +5437,14 @@ Source memory: ${String(row.source_ref ?? row.memory_id)}`,
5378
5437
  last_accessed: String(row.timestamp)
5379
5438
  }));
5380
5439
  }
5381
- var MAX_CARDS_PER_MEMORY, MAX_SENTENCE_CHARS;
5440
+ var MAX_CARDS_PER_MEMORY, MAX_SEGMENT_CHARS, MIN_SEGMENT_CHARS;
5382
5441
  var init_memory_cards = __esm({
5383
5442
  "src/lib/memory-cards.ts"() {
5384
5443
  "use strict";
5385
5444
  init_database();
5386
- MAX_CARDS_PER_MEMORY = 6;
5387
- MAX_SENTENCE_CHARS = 360;
5445
+ MAX_CARDS_PER_MEMORY = 8;
5446
+ MAX_SEGMENT_CHARS = 500;
5447
+ MIN_SEGMENT_CHARS = 20;
5388
5448
  }
5389
5449
  });
5390
5450
 
@@ -3260,6 +3260,22 @@ async function ensureSchema() {
3260
3260
  } catch (e) {
3261
3261
  logCatchDebug("migration", e);
3262
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
+ }
3263
3279
  }
3264
3280
  async function disposeDatabase() {
3265
3281
  if (_walCheckpointTimer) {
@@ -3610,7 +3626,7 @@ var init_license = __esm({
3610
3626
  LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
3611
3627
  CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
3612
3628
  DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
3613
- API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
3629
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
3614
3630
  }
3615
3631
  });
3616
3632
 
@@ -3663,6 +3679,18 @@ function extractRootExe(name) {
3663
3679
  const parts = name.split("-").filter(Boolean);
3664
3680
  return parts.length > 0 ? parts[parts.length - 1] : null;
3665
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
+ }
3666
3694
  function getParentExe(sessionKey) {
3667
3695
  try {
3668
3696
  const data = JSON.parse(readFileSync9(path12.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
@@ -3672,11 +3700,12 @@ function getParentExe(sessionKey) {
3672
3700
  }
3673
3701
  }
3674
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
+ }
3675
3707
  const mySession = getMySession();
3676
3708
  if (!mySession) {
3677
- if (process.env.EXE_SESSION_NAME) {
3678
- return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
3679
- }
3680
3709
  return null;
3681
3710
  }
3682
3711
  const fromSessionName = extractRootExe(mySession);
@@ -3691,6 +3720,10 @@ function resolveExeSession() {
3691
3720
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
3692
3721
  `
3693
3722
  );
3723
+ try {
3724
+ registerParentExe(key, fromSessionName);
3725
+ } catch {
3726
+ }
3694
3727
  candidate = fromSessionName;
3695
3728
  } else {
3696
3729
  candidate = fromCache;
@@ -4958,11 +4991,17 @@ var init_platform_procedures = __esm({
4958
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."
4959
4992
  },
4960
4993
  {
4961
- title: "Customer orchestration maturity \u2014 recommend, never trap",
4994
+ title: "Orchestration phase guidance \u2014 recommend, never trap",
4962
4995
  domain: "workflow",
4963
4996
  priority: "p1",
4964
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."
4965
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
+ },
4966
5005
  {
4967
5006
  title: "Single dispatch path \u2014 create_task only",
4968
5007
  domain: "workflow",
@@ -4996,6 +5035,12 @@ var init_platform_procedures = __esm({
4996
5035
  priority: "p0",
4997
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."
4998
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
+ },
4999
5044
  {
5000
5045
  title: "Customer patch triage \u2014 upstream bug vs customization",
5001
5046
  domain: "support",
@@ -5281,10 +5326,24 @@ function stableId(memoryId, type, content) {
5281
5326
  return createHash2("sha256").update(`${memoryId}:${type}:${content}`).digest("hex").slice(0, 32);
5282
5327
  }
5283
5328
  function cleanText(text) {
5284
- return text.replace(/```[\s\S]*?```/g, " ").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
5285
- }
5286
- function splitSentences(text) {
5287
- 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;
5288
5347
  }
5289
5348
  function inferCardType(sentence, toolName) {
5290
5349
  const lower = sentence.toLowerCase();
@@ -5316,12 +5375,12 @@ function predicateFor(type) {
5316
5375
  }
5317
5376
  }
5318
5377
  function extractMemoryCards(row) {
5319
- const sentences = splitSentences(row.raw_text);
5378
+ const segments = splitSegments(row.raw_text);
5320
5379
  const cards = [];
5321
- for (const sentence of sentences) {
5380
+ for (const sentence of segments) {
5322
5381
  const type = inferCardType(sentence, row.tool_name);
5323
5382
  const subject = extractSubject(sentence, row.agent_id);
5324
- 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;
5325
5384
  cards.push({
5326
5385
  id: stableId(row.id, type, content),
5327
5386
  memory_id: row.id,
@@ -5417,13 +5476,14 @@ Source memory: ${String(row.source_ref ?? row.memory_id)}`,
5417
5476
  last_accessed: String(row.timestamp)
5418
5477
  }));
5419
5478
  }
5420
- var MAX_CARDS_PER_MEMORY, MAX_SENTENCE_CHARS;
5479
+ var MAX_CARDS_PER_MEMORY, MAX_SEGMENT_CHARS, MIN_SEGMENT_CHARS;
5421
5480
  var init_memory_cards = __esm({
5422
5481
  "src/lib/memory-cards.ts"() {
5423
5482
  "use strict";
5424
5483
  init_database();
5425
- MAX_CARDS_PER_MEMORY = 6;
5426
- MAX_SENTENCE_CHARS = 360;
5484
+ MAX_CARDS_PER_MEMORY = 8;
5485
+ MAX_SEGMENT_CHARS = 500;
5486
+ MIN_SEGMENT_CHARS = 20;
5427
5487
  }
5428
5488
  });
5429
5489
 
@@ -3104,6 +3104,22 @@ async function ensureSchema() {
3104
3104
  } catch (e) {
3105
3105
  logCatchDebug("migration", e);
3106
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
+ }
3107
3123
  }
3108
3124
  async function disposeDatabase() {
3109
3125
  if (_walCheckpointTimer) {
@@ -3206,11 +3222,17 @@ var init_platform_procedures = __esm({
3206
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."
3207
3223
  },
3208
3224
  {
3209
- title: "Customer orchestration maturity \u2014 recommend, never trap",
3225
+ title: "Orchestration phase guidance \u2014 recommend, never trap",
3210
3226
  domain: "workflow",
3211
3227
  priority: "p1",
3212
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."
3213
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
+ },
3214
3236
  {
3215
3237
  title: "Single dispatch path \u2014 create_task only",
3216
3238
  domain: "workflow",
@@ -3244,6 +3266,12 @@ var init_platform_procedures = __esm({
3244
3266
  priority: "p0",
3245
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."
3246
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
+ },
3247
3275
  {
3248
3276
  title: "Customer patch triage \u2014 upstream bug vs customization",
3249
3277
  domain: "support",
@@ -4550,10 +4578,24 @@ function stableId(memoryId, type, content) {
4550
4578
  return createHash2("sha256").update(`${memoryId}:${type}:${content}`).digest("hex").slice(0, 32);
4551
4579
  }
4552
4580
  function cleanText(text) {
4553
- return text.replace(/```[\s\S]*?```/g, " ").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
4554
- }
4555
- function splitSentences(text) {
4556
- 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;
4557
4599
  }
4558
4600
  function inferCardType(sentence, toolName) {
4559
4601
  const lower = sentence.toLowerCase();
@@ -4585,12 +4627,12 @@ function predicateFor(type) {
4585
4627
  }
4586
4628
  }
4587
4629
  function extractMemoryCards(row) {
4588
- const sentences = splitSentences(row.raw_text);
4630
+ const segments = splitSegments(row.raw_text);
4589
4631
  const cards = [];
4590
- for (const sentence of sentences) {
4632
+ for (const sentence of segments) {
4591
4633
  const type = inferCardType(sentence, row.tool_name);
4592
4634
  const subject = extractSubject(sentence, row.agent_id);
4593
- 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;
4594
4636
  cards.push({
4595
4637
  id: stableId(row.id, type, content),
4596
4638
  memory_id: row.id,
@@ -4686,13 +4728,14 @@ Source memory: ${String(row.source_ref ?? row.memory_id)}`,
4686
4728
  last_accessed: String(row.timestamp)
4687
4729
  }));
4688
4730
  }
4689
- var MAX_CARDS_PER_MEMORY, MAX_SENTENCE_CHARS;
4731
+ var MAX_CARDS_PER_MEMORY, MAX_SEGMENT_CHARS, MIN_SEGMENT_CHARS;
4690
4732
  var init_memory_cards = __esm({
4691
4733
  "src/lib/memory-cards.ts"() {
4692
4734
  "use strict";
4693
4735
  init_database();
4694
- MAX_CARDS_PER_MEMORY = 6;
4695
- MAX_SENTENCE_CHARS = 360;
4736
+ MAX_CARDS_PER_MEMORY = 8;
4737
+ MAX_SEGMENT_CHARS = 500;
4738
+ MIN_SEGMENT_CHARS = 20;
4696
4739
  }
4697
4740
  });
4698
4741
 
@@ -3263,6 +3263,22 @@ async function ensureSchema() {
3263
3263
  } catch (e) {
3264
3264
  logCatchDebug("migration", e);
3265
3265
  }
3266
+ try {
3267
+ await client.execute({
3268
+ sql: `ALTER TABLE memories ADD COLUMN visibility TEXT DEFAULT 'private'`,
3269
+ args: []
3270
+ });
3271
+ } catch (e) {
3272
+ logCatchDebug("migration", e);
3273
+ }
3274
+ try {
3275
+ await client.execute({
3276
+ sql: `ALTER TABLE memories ADD COLUMN strength REAL DEFAULT 1.0`,
3277
+ args: []
3278
+ });
3279
+ } catch (e) {
3280
+ logCatchDebug("migration", e);
3281
+ }
3266
3282
  }
3267
3283
  async function disposeDatabase() {
3268
3284
  if (_walCheckpointTimer) {
@@ -4386,11 +4402,17 @@ var init_platform_procedures = __esm({
4386
4402
  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."
4387
4403
  },
4388
4404
  {
4389
- title: "Customer orchestration maturity \u2014 recommend, never trap",
4405
+ title: "Orchestration phase guidance \u2014 recommend, never trap",
4390
4406
  domain: "workflow",
4391
4407
  priority: "p1",
4392
4408
  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."
4393
4409
  },
4410
+ {
4411
+ title: "Routing slot vs display title \u2014 internal 'coo' is plumbing, not your name",
4412
+ domain: "identity",
4413
+ priority: "p0",
4414
+ 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."
4415
+ },
4394
4416
  {
4395
4417
  title: "Single dispatch path \u2014 create_task only",
4396
4418
  domain: "workflow",
@@ -4424,6 +4446,12 @@ var init_platform_procedures = __esm({
4424
4446
  priority: "p0",
4425
4447
  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."
4426
4448
  },
4449
+ {
4450
+ title: "Destructive operations \u2014 mandatory reviewer gate",
4451
+ domain: "security",
4452
+ priority: "p0",
4453
+ 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."
4454
+ },
4427
4455
  {
4428
4456
  title: "Customer patch triage \u2014 upstream bug vs customization",
4429
4457
  domain: "support",
@@ -4709,10 +4737,24 @@ function stableId(memoryId, type, content) {
4709
4737
  return createHash2("sha256").update(`${memoryId}:${type}:${content}`).digest("hex").slice(0, 32);
4710
4738
  }
4711
4739
  function cleanText(text) {
4712
- return text.replace(/```[\s\S]*?```/g, " ").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
4713
- }
4714
- function splitSentences(text) {
4715
- return cleanText(text).split(/(?<=[.!?])\s+|\n+/).map((s) => s.trim()).filter((s) => s.length >= 24 && s.length <= MAX_SENTENCE_CHARS);
4740
+ let cleaned = text.replace(
4741
+ /```(\w*)\n(.*?)(?:\n[\s\S]*?)```/g,
4742
+ (_m, lang, firstLine) => `[code${lang ? `:${lang}` : ""}] ${firstLine.trim()}`
4743
+ );
4744
+ cleaned = cleaned.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
4745
+ return cleaned;
4746
+ }
4747
+ function splitSegments(text) {
4748
+ const cleaned = cleanText(text);
4749
+ 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);
4750
+ if (segments.length === 0 && cleaned.length >= MIN_SEGMENT_CHARS) {
4751
+ const lines = cleaned.split(/\n+/).map((l) => l.trim()).filter((l) => l.length >= MIN_SEGMENT_CHARS && l.length <= MAX_SEGMENT_CHARS);
4752
+ if (lines.length > 0) return lines;
4753
+ if (cleaned.length >= MIN_SEGMENT_CHARS) {
4754
+ return [cleaned.slice(0, MAX_SEGMENT_CHARS)];
4755
+ }
4756
+ }
4757
+ return segments;
4716
4758
  }
4717
4759
  function inferCardType(sentence, toolName) {
4718
4760
  const lower = sentence.toLowerCase();
@@ -4744,12 +4786,12 @@ function predicateFor(type) {
4744
4786
  }
4745
4787
  }
4746
4788
  function extractMemoryCards(row) {
4747
- const sentences = splitSentences(row.raw_text);
4789
+ const segments = splitSegments(row.raw_text);
4748
4790
  const cards = [];
4749
- for (const sentence of sentences) {
4791
+ for (const sentence of segments) {
4750
4792
  const type = inferCardType(sentence, row.tool_name);
4751
4793
  const subject = extractSubject(sentence, row.agent_id);
4752
- const content = sentence.length > MAX_SENTENCE_CHARS ? `${sentence.slice(0, MAX_SENTENCE_CHARS - 1)}\u2026` : sentence;
4794
+ const content = sentence.length > MAX_SEGMENT_CHARS ? `${sentence.slice(0, MAX_SEGMENT_CHARS - 1)}\u2026` : sentence;
4753
4795
  cards.push({
4754
4796
  id: stableId(row.id, type, content),
4755
4797
  memory_id: row.id,
@@ -4845,13 +4887,14 @@ Source memory: ${String(row.source_ref ?? row.memory_id)}`,
4845
4887
  last_accessed: String(row.timestamp)
4846
4888
  }));
4847
4889
  }
4848
- var MAX_CARDS_PER_MEMORY, MAX_SENTENCE_CHARS;
4890
+ var MAX_CARDS_PER_MEMORY, MAX_SEGMENT_CHARS, MIN_SEGMENT_CHARS;
4849
4891
  var init_memory_cards = __esm({
4850
4892
  "src/lib/memory-cards.ts"() {
4851
4893
  "use strict";
4852
4894
  init_database();
4853
- MAX_CARDS_PER_MEMORY = 6;
4854
- MAX_SENTENCE_CHARS = 360;
4895
+ MAX_CARDS_PER_MEMORY = 8;
4896
+ MAX_SEGMENT_CHARS = 500;
4897
+ MIN_SEGMENT_CHARS = 20;
4855
4898
  }
4856
4899
  });
4857
4900