@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
@@ -568,6 +568,7 @@ __export(agent_config_exports, {
568
568
  getAgentRuntime: () => getAgentRuntime,
569
569
  loadAgentConfig: () => loadAgentConfig,
570
570
  saveAgentConfig: () => saveAgentConfig,
571
+ setAgentMcps: () => setAgentMcps,
571
572
  setAgentRuntime: () => setAgentRuntime
572
573
  });
573
574
  import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5 } from "fs";
@@ -594,7 +595,7 @@ function getAgentRuntime(agentId) {
594
595
  if (orgDefault) return orgDefault;
595
596
  return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
596
597
  }
597
- function setAgentRuntime(agentId, runtime, model, reasoning_effort) {
598
+ function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
598
599
  const knownModels = KNOWN_RUNTIMES[runtime];
599
600
  if (!knownModels) {
600
601
  return {
@@ -609,12 +610,26 @@ function setAgentRuntime(agentId, runtime, model, reasoning_effort) {
609
610
  };
610
611
  }
611
612
  const config2 = loadAgentConfig();
613
+ const existing = config2[agentId];
612
614
  const entry = { runtime, model };
613
615
  if (reasoning_effort) entry.reasoning_effort = reasoning_effort;
616
+ if (mcps !== void 0) {
617
+ entry.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
618
+ } else if (existing?.mcps) {
619
+ entry.mcps = existing.mcps;
620
+ }
614
621
  config2[agentId] = entry;
615
622
  saveAgentConfig(config2);
616
623
  return { ok: true };
617
624
  }
625
+ function setAgentMcps(agentId, mcps) {
626
+ const config2 = loadAgentConfig();
627
+ const existing = config2[agentId] ?? getAgentRuntime(agentId);
628
+ existing.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
629
+ config2[agentId] = existing;
630
+ saveAgentConfig(config2);
631
+ return { ok: true };
632
+ }
618
633
  function clearAgentRuntime(agentId) {
619
634
  const config2 = loadAgentConfig();
620
635
  delete config2[agentId];
@@ -3659,6 +3674,22 @@ async function ensureSchema() {
3659
3674
  } catch (e) {
3660
3675
  logCatchDebug("migration", e);
3661
3676
  }
3677
+ try {
3678
+ await client.execute({
3679
+ sql: `ALTER TABLE memories ADD COLUMN visibility TEXT DEFAULT 'private'`,
3680
+ args: []
3681
+ });
3682
+ } catch (e) {
3683
+ logCatchDebug("migration", e);
3684
+ }
3685
+ try {
3686
+ await client.execute({
3687
+ sql: `ALTER TABLE memories ADD COLUMN strength REAL DEFAULT 1.0`,
3688
+ args: []
3689
+ });
3690
+ } catch (e) {
3691
+ logCatchDebug("migration", e);
3692
+ }
3662
3693
  }
3663
3694
  async function disposeDatabase() {
3664
3695
  if (_walCheckpointTimer) {
@@ -6072,11 +6103,17 @@ var init_platform_procedures = __esm({
6072
6103
  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."
6073
6104
  },
6074
6105
  {
6075
- title: "Customer orchestration maturity \u2014 recommend, never trap",
6106
+ title: "Orchestration phase guidance \u2014 recommend, never trap",
6076
6107
  domain: "workflow",
6077
6108
  priority: "p1",
6078
6109
  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."
6079
6110
  },
6111
+ {
6112
+ title: "Routing slot vs display title \u2014 internal 'coo' is plumbing, not your name",
6113
+ domain: "identity",
6114
+ priority: "p0",
6115
+ 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."
6116
+ },
6080
6117
  {
6081
6118
  title: "Single dispatch path \u2014 create_task only",
6082
6119
  domain: "workflow",
@@ -6110,6 +6147,12 @@ var init_platform_procedures = __esm({
6110
6147
  priority: "p0",
6111
6148
  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."
6112
6149
  },
6150
+ {
6151
+ title: "Destructive operations \u2014 mandatory reviewer gate",
6152
+ domain: "security",
6153
+ priority: "p0",
6154
+ 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."
6155
+ },
6113
6156
  {
6114
6157
  title: "Customer patch triage \u2014 upstream bug vs customization",
6115
6158
  domain: "support",
@@ -6395,10 +6438,24 @@ function stableId3(memoryId, type, content) {
6395
6438
  return createHash3("sha256").update(`${memoryId}:${type}:${content}`).digest("hex").slice(0, 32);
6396
6439
  }
6397
6440
  function cleanText(text3) {
6398
- return text3.replace(/```[\s\S]*?```/g, " ").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
6441
+ let cleaned = text3.replace(
6442
+ /```(\w*)\n(.*?)(?:\n[\s\S]*?)```/g,
6443
+ (_m, lang, firstLine) => `[code${lang ? `:${lang}` : ""}] ${firstLine.trim()}`
6444
+ );
6445
+ cleaned = cleaned.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
6446
+ return cleaned;
6399
6447
  }
6400
- function splitSentences(text3) {
6401
- return cleanText(text3).split(/(?<=[.!?])\s+|\n+/).map((s) => s.trim()).filter((s) => s.length >= 24 && s.length <= MAX_SENTENCE_CHARS);
6448
+ function splitSegments(text3) {
6449
+ const cleaned = cleanText(text3);
6450
+ 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);
6451
+ if (segments.length === 0 && cleaned.length >= MIN_SEGMENT_CHARS) {
6452
+ const lines = cleaned.split(/\n+/).map((l) => l.trim()).filter((l) => l.length >= MIN_SEGMENT_CHARS && l.length <= MAX_SEGMENT_CHARS);
6453
+ if (lines.length > 0) return lines;
6454
+ if (cleaned.length >= MIN_SEGMENT_CHARS) {
6455
+ return [cleaned.slice(0, MAX_SEGMENT_CHARS)];
6456
+ }
6457
+ }
6458
+ return segments;
6402
6459
  }
6403
6460
  function inferCardType(sentence, toolName) {
6404
6461
  const lower = sentence.toLowerCase();
@@ -6430,12 +6487,12 @@ function predicateFor(type) {
6430
6487
  }
6431
6488
  }
6432
6489
  function extractMemoryCards(row) {
6433
- const sentences = splitSentences(row.raw_text);
6490
+ const segments = splitSegments(row.raw_text);
6434
6491
  const cards = [];
6435
- for (const sentence of sentences) {
6492
+ for (const sentence of segments) {
6436
6493
  const type = inferCardType(sentence, row.tool_name);
6437
6494
  const subject = extractSubject(sentence, row.agent_id);
6438
- const content = sentence.length > MAX_SENTENCE_CHARS ? `${sentence.slice(0, MAX_SENTENCE_CHARS - 1)}\u2026` : sentence;
6495
+ const content = sentence.length > MAX_SEGMENT_CHARS ? `${sentence.slice(0, MAX_SEGMENT_CHARS - 1)}\u2026` : sentence;
6439
6496
  cards.push({
6440
6497
  id: stableId3(row.id, type, content),
6441
6498
  memory_id: row.id,
@@ -6531,13 +6588,14 @@ Source memory: ${String(row.source_ref ?? row.memory_id)}`,
6531
6588
  last_accessed: String(row.timestamp)
6532
6589
  }));
6533
6590
  }
6534
- var MAX_CARDS_PER_MEMORY, MAX_SENTENCE_CHARS;
6591
+ var MAX_CARDS_PER_MEMORY, MAX_SEGMENT_CHARS, MIN_SEGMENT_CHARS;
6535
6592
  var init_memory_cards = __esm({
6536
6593
  "src/lib/memory-cards.ts"() {
6537
6594
  "use strict";
6538
6595
  init_database();
6539
- MAX_CARDS_PER_MEMORY = 6;
6540
- MAX_SENTENCE_CHARS = 360;
6596
+ MAX_CARDS_PER_MEMORY = 8;
6597
+ MAX_SEGMENT_CHARS = 500;
6598
+ MIN_SEGMENT_CHARS = 20;
6541
6599
  }
6542
6600
  });
6543
6601
 
@@ -8728,7 +8786,7 @@ async function hybridSearch(queryText, agentId, options) {
8728
8786
  try {
8729
8787
  const client = getClient();
8730
8788
  void client.execute({
8731
- sql: `UPDATE memories SET last_accessed = ?, retrieval_count = COALESCE(retrieval_count, 0) + 1 WHERE id IN (${placeholders})`,
8789
+ sql: `UPDATE memories SET last_accessed = ?, retrieval_count = COALESCE(retrieval_count, 0) + 1, strength = MIN(1.0, COALESCE(strength, 1.0) + 0.1) WHERE id IN (${placeholders})`,
8732
8790
  args: [now2, ...ids]
8733
8791
  }).catch(() => {
8734
8792
  });
@@ -10093,7 +10151,7 @@ async function assertVpsLicense(opts) {
10093
10151
  }
10094
10152
  if (!transientFailure) {
10095
10153
  throw new Error(
10096
- "License validation failed: unknown backend state. Restore network connectivity to https://askexe.com/cloud and retry."
10154
+ "License validation failed: unknown backend state. Restore network connectivity to https://cloud.askexe.com and retry."
10097
10155
  );
10098
10156
  }
10099
10157
  const fresh = await getCachedLicense();
@@ -10130,7 +10188,7 @@ async function assertVpsLicense(opts) {
10130
10188
  } catch {
10131
10189
  }
10132
10190
  throw new Error(
10133
- `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.`
10191
+ `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.`
10134
10192
  );
10135
10193
  }
10136
10194
  function startLicenseRevalidation(intervalMs = 36e5) {
@@ -10162,7 +10220,7 @@ var init_license = __esm({
10162
10220
  LICENSE_PATH = path17.join(EXE_AI_DIR, "license.key");
10163
10221
  CACHE_PATH = path17.join(EXE_AI_DIR, "license-cache.json");
10164
10222
  DEVICE_ID_PATH = path17.join(EXE_AI_DIR, "device-id");
10165
- API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
10223
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
10166
10224
  RETRY_DELAY_MS = 500;
10167
10225
  LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
10168
10226
  MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
@@ -13344,11 +13402,12 @@ function getDispatchedBy(sessionKey) {
13344
13402
  }
13345
13403
  }
13346
13404
  function resolveExeSession() {
13405
+ if (process.env.EXE_SESSION_NAME) {
13406
+ const fromEnv = extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
13407
+ if (fromEnv) return fromEnv;
13408
+ }
13347
13409
  const mySession = getMySession();
13348
13410
  if (!mySession) {
13349
- if (process.env.EXE_SESSION_NAME) {
13350
- return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
13351
- }
13352
13411
  return null;
13353
13412
  }
13354
13413
  const fromSessionName = extractRootExe(mySession);
@@ -13363,6 +13422,10 @@ function resolveExeSession() {
13363
13422
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
13364
13423
  `
13365
13424
  );
13425
+ try {
13426
+ registerParentExe(key, fromSessionName);
13427
+ } catch {
13428
+ }
13366
13429
  candidate = fromSessionName;
13367
13430
  } else {
13368
13431
  candidate = fromCache;
@@ -14296,6 +14359,19 @@ async function resolveTask(client, identifier, scopeSession) {
14296
14359
  args: [identifier, ...scope.args]
14297
14360
  });
14298
14361
  if (result3.rows.length === 1) return result3.rows[0];
14362
+ if (/^[a-f0-9]{7,12}$/i.test(identifier)) {
14363
+ result3 = await client.execute({
14364
+ sql: `SELECT * FROM tasks WHERE id LIKE ?`,
14365
+ args: [`${identifier}%`]
14366
+ });
14367
+ if (result3.rows.length === 1) return result3.rows[0];
14368
+ if (result3.rows.length > 1) {
14369
+ const matches = result3.rows.map((r) => `${String(r.id)} "${String(r.title)}" (${String(r.status)})`).join(", ");
14370
+ throw new Error(
14371
+ `Multiple tasks match short-ID "${identifier}": ${matches}. Use a longer prefix to disambiguate.`
14372
+ );
14373
+ }
14374
+ }
14299
14375
  result3 = await client.execute({
14300
14376
  sql: `SELECT * FROM tasks WHERE task_file LIKE ?${scope.sql}`,
14301
14377
  args: [`%${identifier}%`, ...scope.args]
@@ -14842,12 +14918,13 @@ async function cascadeUnblock(taskId, baseDir, now2) {
14842
14918
  WHERE blocked_by = ? AND status = 'blocked'`,
14843
14919
  args: [now2, taskId]
14844
14920
  });
14845
- if (baseDir && unblocked.rowsAffected > 0) {
14846
- const ubScope = sessionScopeFilter();
14847
- const unblockedRows = await client.execute({
14848
- sql: `SELECT task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
14849
- args: [now2, ...ubScope.args]
14850
- });
14921
+ if (unblocked.rowsAffected === 0) return;
14922
+ const ubScope = sessionScopeFilter();
14923
+ const unblockedRows = await client.execute({
14924
+ sql: `SELECT id, title, assigned_to, priority, task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
14925
+ args: [now2, ...ubScope.args]
14926
+ });
14927
+ if (baseDir) {
14851
14928
  for (const ur of unblockedRows.rows) {
14852
14929
  try {
14853
14930
  const ubFile = path28.join(baseDir, String(ur.task_file));
@@ -14859,6 +14936,19 @@ async function cascadeUnblock(taskId, baseDir, now2) {
14859
14936
  }
14860
14937
  }
14861
14938
  }
14939
+ if (unblockedRows.rows.length > 0 && !process.env.VITEST) {
14940
+ try {
14941
+ const { queueIntercom: queueIntercom2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
14942
+ const dispatched = /* @__PURE__ */ new Set();
14943
+ for (const ur of unblockedRows.rows) {
14944
+ const assignee = String(ur.assigned_to);
14945
+ if (dispatched.has(assignee)) continue;
14946
+ dispatched.add(assignee);
14947
+ queueIntercom2(`${assignee}`, `unblocked: "${String(ur.title)}" is now ready`);
14948
+ }
14949
+ } catch {
14950
+ }
14951
+ }
14862
14952
  }
14863
14953
  async function findNextTask(assignedTo) {
14864
14954
  const client = getClient();
@@ -15000,6 +15090,15 @@ var init_tasks_notify = __esm({
15000
15090
  // src/lib/behaviors.ts
15001
15091
  import crypto9 from "crypto";
15002
15092
  async function storeBehavior(opts) {
15093
+ try {
15094
+ const { loadEmployeesSync: loadEmployeesSync2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
15095
+ const roster = loadEmployeesSync2();
15096
+ if (roster.length > 0 && !roster.some((e) => e.name === opts.agentId)) {
15097
+ throw new Error(`Agent "${opts.agentId}" not found in roster. Cannot store behavior for unregistered agent.`);
15098
+ }
15099
+ } catch (e) {
15100
+ if (e instanceof Error && e.message.includes("not found in roster")) throw e;
15101
+ }
15003
15102
  const client = getClient();
15004
15103
  const id = crypto9.randomUUID();
15005
15104
  const now2 = (/* @__PURE__ */ new Date()).toISOString();
@@ -15485,6 +15584,12 @@ async function updateTask(input) {
15485
15584
  }
15486
15585
  }
15487
15586
  }
15587
+ if (input.status === "cancelled") {
15588
+ try {
15589
+ await cascadeUnblock(taskId, input.baseDir, now2);
15590
+ } catch {
15591
+ }
15592
+ }
15488
15593
  if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
15489
15594
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
15490
15595
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
@@ -20926,15 +21031,16 @@ function registerSetAgentConfig(server) {
20926
21031
  "set_agent_config",
20927
21032
  {
20928
21033
  title: "Set Agent Config",
20929
- description: "Set or view per-agent runtime + model configuration. Controls which runtime (claude, codex, opencode) and model each agent uses when dispatched. COO-only. Omit runtime/model to view current config for an agent or all agents.",
21034
+ description: "Set or view per-agent runtime, model, and MCP configuration. Controls which runtime (claude, codex, opencode), model, and MCP servers each agent uses when dispatched. COO-only. Omit runtime/model to view current config for an agent or all agents. Pass mcps to restrict which MCP servers an agent loads (exe-os always included).",
20930
21035
  inputSchema: {
20931
21036
  agent_id: z48.string().optional().describe("Agent name, or 'default' for org-wide default. Omit to view all agents."),
20932
21037
  runtime: z48.string().optional().describe(`Runtime: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`),
20933
21038
  model: z48.string().optional().describe("Model name (e.g. claude-opus-4, gpt-5.4, anthropic/claude-sonnet-4-6)"),
20934
- reasoning_effort: z48.string().optional().describe("Codex reasoning effort: low, medium, high, xhigh. Per-agent override for thinking/reasoning depth.")
21039
+ reasoning_effort: z48.string().optional().describe("Codex reasoning effort: low, medium, high, xhigh. Per-agent override for thinking/reasoning depth."),
21040
+ mcps: z48.array(z48.string()).optional().describe("MCP server allowlist. Only these servers load for this agent. exe-os always included. Omit to load all MCPs.")
20935
21041
  }
20936
21042
  },
20937
- async ({ agent_id, runtime, model, reasoning_effort }) => {
21043
+ async ({ agent_id, runtime, model, reasoning_effort, mcps }) => {
20938
21044
  const { agentId, agentRole } = getActiveAgent();
20939
21045
  const isCoordinator = isCoordinatorRole(agentRole) || (() => {
20940
21046
  try {
@@ -20966,15 +21072,26 @@ function registerSetAgentConfig(server) {
20966
21072
  isError: true
20967
21073
  };
20968
21074
  }
21075
+ if (mcps && agent_id && !runtime && !model) {
21076
+ const { setAgentMcps: setAgentMcps2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
21077
+ const result4 = setAgentMcps2(agent_id, mcps);
21078
+ if (!result4.ok) {
21079
+ return { content: [{ type: "text", text: result4.error }], isError: true };
21080
+ }
21081
+ return {
21082
+ content: [{ type: "text", text: `Set ${agent_id} MCPs \u2192 [${mcps.join(", ")}] (exe-os always included)` }]
21083
+ };
21084
+ }
20969
21085
  if (!runtime || !model) {
20970
21086
  if (agent_id) {
20971
21087
  const cfg = getAgentRuntime(agent_id);
20972
21088
  const label = RUNTIME_LABELS[cfg.runtime] ?? cfg.runtime;
21089
+ const mcpInfo = cfg.mcps ? ` | MCPs: [${cfg.mcps.join(", ")}]` : " | MCPs: all";
20973
21090
  return {
20974
21091
  content: [
20975
21092
  {
20976
21093
  type: "text",
20977
- text: `${agent_id}: ${label} / ${cfg.model}`
21094
+ text: `${agent_id}: ${label} / ${cfg.model}${mcpInfo}`
20978
21095
  }
20979
21096
  ]
20980
21097
  };
@@ -21021,7 +21138,7 @@ function registerSetAgentConfig(server) {
21021
21138
  isError: true
21022
21139
  };
21023
21140
  }
21024
- const result3 = setAgentRuntime(agent_id, runtime, model, reasoning_effort);
21141
+ const result3 = setAgentRuntime(agent_id, runtime, model, reasoning_effort, mcps);
21025
21142
  if (!result3.ok) {
21026
21143
  return {
21027
21144
  content: [{ type: "text", text: result3.error }],
@@ -21030,6 +21147,7 @@ function registerSetAgentConfig(server) {
21030
21147
  }
21031
21148
  const parts = [`Set ${agent_id} \u2192 runtime=${runtime} model=${model}`];
21032
21149
  if (reasoning_effort) parts[0] += ` reasoning_effort=${reasoning_effort}`;
21150
+ if (mcps) parts[0] += ` mcps=[${mcps.join(", ")}]`;
21033
21151
  return {
21034
21152
  content: [{ type: "text", text: parts[0] }]
21035
21153
  };
@@ -22629,10 +22747,18 @@ async function pollOrphanedTasks(deps, nowMs = Date.now()) {
22629
22747
  } catch {
22630
22748
  return [];
22631
22749
  }
22750
+ const coordinatorSet = new Set(coordinatorSessions);
22632
22751
  const agentTasks = /* @__PURE__ */ new Map();
22633
22752
  for (const t of tasksByAgent) {
22634
22753
  const scope = t.sessionScope || coordinatorSessions[0] || null;
22635
22754
  if (!scope) continue;
22755
+ if (t.sessionScope && !coordinatorSet.has(t.sessionScope)) {
22756
+ process.stderr.write(
22757
+ `[auto-wake] Skipping ${t.agentId} task ${t.taskId} \u2014 coordinator session "${t.sessionScope}" is dead
22758
+ `
22759
+ );
22760
+ continue;
22761
+ }
22636
22762
  const key = `${t.agentId}::${scope}`;
22637
22763
  const existing = agentTasks.get(key) ?? [];
22638
22764
  existing.push({ taskId: t.taskId, priority: t.priority, sessionScope: scope });
@@ -25359,6 +25485,27 @@ async function cloudSync(config2) {
25359
25485
  if (stmts.length > 0) await client.batch(stmts, "write");
25360
25486
  pulled = pullResult.records.length;
25361
25487
  } else {
25488
+ try {
25489
+ const incomingIds = pullResult.records.map((r) => sqlSafe(r.id));
25490
+ if (incomingIds.length > 0) {
25491
+ const ph = incomingIds.map(() => "?").join(",");
25492
+ const existing = await client.execute({
25493
+ sql: `SELECT id, version, timestamp FROM memories WHERE id IN (${ph})`,
25494
+ args: incomingIds
25495
+ });
25496
+ const localMap = new Map(existing.rows.map((r) => [String(r.id), r]));
25497
+ for (const rec of pullResult.records) {
25498
+ const local = localMap.get(String(rec.id));
25499
+ if (local && Number(local.version) > 0 && Number(local.version) !== Number(rec.version ?? 0)) {
25500
+ process.stderr.write(
25501
+ `[cloud-sync] CONFLICT: memory ${String(rec.id).slice(0, 8)} \u2014 local v${local.version} vs remote v${rec.version ?? 0}. Remote wins (LWW).
25502
+ `
25503
+ );
25504
+ }
25505
+ }
25506
+ }
25507
+ } catch {
25508
+ }
25362
25509
  const stmts = pullResult.records.map((rec) => ({
25363
25510
  sql: `INSERT OR REPLACE INTO memories
25364
25511
  (id, agent_id, agent_role, session_id, timestamp,
@@ -28413,7 +28560,7 @@ var init_starter_packs = __esm({
28413
28560
 
28414
28561
  // src/lib/employee-templates.ts
28415
28562
  function renderClientCOOTemplate(vars) {
28416
- const resolved = { ...vars, title: vars.title || "Chief Operating Officer" };
28563
+ const resolved = { ...vars, title: vars.title || "Chief of Staff" };
28417
28564
  for (const key of CLIENT_COO_PLACEHOLDERS) {
28418
28565
  const value = resolved[key];
28419
28566
  if (typeof value !== "string" || value.length === 0) {
@@ -35300,12 +35447,14 @@ __export(task_enforcement_exports, {
35300
35447
  IDLE_PATTERNS: () => IDLE_PATTERNS,
35301
35448
  MANAGER_GRACE_PERIOD_MS: () => MANAGER_GRACE_PERIOD_MS,
35302
35449
  MANAGER_ROLES: () => MANAGER_ROLES,
35450
+ NUDGE_ECHO_PATTERNS: () => NUDGE_ECHO_PATTERNS,
35303
35451
  TASK_ENFORCEMENT_DEBOUNCE_MS: () => TASK_ENFORCEMENT_DEBOUNCE_MS,
35304
35452
  TASK_ENFORCEMENT_INTERVAL_MS: () => TASK_ENFORCEMENT_INTERVAL_MS,
35305
35453
  _resetNudgeState: () => _resetNudgeState,
35306
35454
  decideManagerAction: () => decideManagerAction,
35307
35455
  decideWorkerAction: () => decideWorkerAction,
35308
35456
  isIdlePane: () => isIdlePane,
35457
+ isNudgeEcho: () => isNudgeEcho,
35309
35458
  runTaskEnforcementTick: () => runTaskEnforcementTick,
35310
35459
  sendNudge: () => sendNudge
35311
35460
  });
@@ -35318,20 +35467,26 @@ function writeAuditEntry(entry) {
35318
35467
  } catch {
35319
35468
  }
35320
35469
  }
35321
- function decideManagerAction(counts, isIdle, withinGracePeriod) {
35470
+ function decideManagerAction(counts, isIdle, withinGracePeriod, nudgeEcho) {
35322
35471
  if (counts.openTasks === 0) return "skip";
35323
35472
  if (withinGracePeriod) return "grace_period";
35324
35473
  if (!isIdle) return "active";
35474
+ if (nudgeEcho) return "active";
35475
+ if ((counts.inProgressTasks ?? 0) > 0) return "active";
35325
35476
  if (counts.activeSubtasks === 0) return "nudge";
35326
35477
  return "soft_nudge";
35327
35478
  }
35328
- function decideWorkerAction(taskCount, isIdle) {
35479
+ function decideWorkerAction(taskCount, isIdle, nudgeEcho) {
35329
35480
  if (taskCount === 0) return "skip";
35481
+ if (nudgeEcho) return "idle";
35330
35482
  return isIdle ? "nudge" : "idle";
35331
35483
  }
35332
35484
  function isIdlePane(paneText) {
35333
35485
  return IDLE_PATTERNS.some((p) => paneText.includes(p));
35334
35486
  }
35487
+ function isNudgeEcho(paneText) {
35488
+ return NUDGE_ECHO_PATTERNS.some((p) => paneText.includes(p));
35489
+ }
35335
35490
  function sendNudge(transport, session, runtime, action) {
35336
35491
  const message = action === "nudge" ? "You have open tasks. Run list_tasks to find them." : "You have more open tasks to dispatch. Run list_tasks.";
35337
35492
  if (transport.sendKeysLiteral) {
@@ -35351,16 +35506,21 @@ async function runTaskEnforcementTick(deps) {
35351
35506
  const { transport, employees, client, scopeFilter } = deps;
35352
35507
  const now2 = deps.now ?? Date.now();
35353
35508
  const sessions = transport.listSessions();
35509
+ let processedAnyEmployee = false;
35354
35510
  for (const session of sessions) {
35355
35511
  if (!session.includes("-")) continue;
35356
35512
  const agentName = session.split("-")[0]?.replace(/\d+$/, "");
35357
35513
  if (!agentName) continue;
35514
+ if ("isAlive" in transport && typeof transport.isAlive === "function") {
35515
+ if (!transport.isAlive(session)) continue;
35516
+ }
35358
35517
  const lastNudgeTime = _lastNudge.get(session) ?? 0;
35359
35518
  if (now2 - lastNudgeTime < TASK_ENFORCEMENT_DEBOUNCE_MS) {
35360
35519
  continue;
35361
35520
  }
35362
35521
  const employee = employees.find((e) => e.name === agentName);
35363
35522
  if (!employee) continue;
35523
+ processedAnyEmployee = true;
35364
35524
  let effectiveScope = scopeFilter;
35365
35525
  const dashIndex = session.indexOf("-");
35366
35526
  if (dashIndex > 0) {
@@ -35408,12 +35568,21 @@ async function runTaskEnforcementTick(deps) {
35408
35568
  }
35409
35569
  }
35410
35570
  }
35571
+ let inProgressTasks = 0;
35572
+ if (openTasks > 0) {
35573
+ const ipResult = await client.execute({
35574
+ sql: `SELECT COUNT(*) as cnt FROM tasks WHERE assigned_to = ? AND status = 'in_progress'${effectiveScope.sql}`,
35575
+ args: [agentName, ...effectiveScope.args]
35576
+ });
35577
+ inProgressTasks = Number(ipResult.rows[0]?.cnt ?? 0);
35578
+ }
35411
35579
  const pane = transport.capturePane(session, 20);
35412
35580
  paneIdle = isIdlePane(pane);
35413
- action = decideManagerAction({ openTasks, activeSubtasks }, paneIdle, withinGracePeriod);
35581
+ const nudgeEcho = isNudgeEcho(pane);
35582
+ action = decideManagerAction({ openTasks, activeSubtasks, inProgressTasks }, paneIdle, withinGracePeriod, nudgeEcho);
35414
35583
  if (action === "skip") reason = "no open tasks";
35415
35584
  else if (action === "grace_period") reason = `task assigned <10min ago (${graceRemaining}s remaining)`;
35416
- else if (action === "active") reason = "pane shows active work";
35585
+ else if (action === "active") reason = nudgeEcho ? "pane shows previous nudge (anti-loop)" : inProgressTasks > 0 ? `${inProgressTasks} tasks in_progress` : "pane shows active work";
35417
35586
  else if (action === "nudge") reason = `${openTasks} open tasks, 0 delegated, pane idle`;
35418
35587
  else if (action === "soft_nudge") reason = `${openTasks} open tasks, ${activeSubtasks} delegated, pane idle`;
35419
35588
  writeAuditEntry({
@@ -35442,7 +35611,8 @@ async function runTaskEnforcementTick(deps) {
35442
35611
  if (taskCount > 0) {
35443
35612
  const pane = transport.capturePane(session, 20);
35444
35613
  paneIdle = isIdlePane(pane);
35445
- action = decideWorkerAction(taskCount, paneIdle);
35614
+ const workerNudgeEcho = isNudgeEcho(pane);
35615
+ action = decideWorkerAction(taskCount, paneIdle, workerNudgeEcho);
35446
35616
  }
35447
35617
  if (action === "skip") reason = "no tasks";
35448
35618
  else if (action === "idle") reason = `${taskCount} tasks but pane active`;
@@ -35468,8 +35638,32 @@ async function runTaskEnforcementTick(deps) {
35468
35638
  } catch {
35469
35639
  }
35470
35640
  }
35641
+ if (processedAnyEmployee) {
35642
+ try {
35643
+ const staleBlocked = await client.execute({
35644
+ sql: `SELECT t.id, t.title, b.status as blocker_status
35645
+ FROM tasks t
35646
+ JOIN tasks b ON t.blocked_by = b.id
35647
+ WHERE t.status = 'blocked'
35648
+ AND b.status IN ('done', 'cancelled', 'closed')${scopeFilter.sql}`,
35649
+ args: [...scopeFilter.args]
35650
+ });
35651
+ const nowIso = new Date(now2).toISOString();
35652
+ for (const row of staleBlocked.rows) {
35653
+ await client.execute({
35654
+ sql: `UPDATE tasks SET status = 'open', blocked_by = NULL, updated_at = ? WHERE id = ?`,
35655
+ args: [nowIso, String(row.id)]
35656
+ });
35657
+ process.stderr.write(
35658
+ `[exed] Auto-unblocked "${String(row.title)}" \u2014 blocker already ${String(row.blocker_status)}
35659
+ `
35660
+ );
35661
+ }
35662
+ } catch {
35663
+ }
35664
+ }
35471
35665
  }
35472
- var TASK_ENFORCEMENT_INTERVAL_MS, TASK_ENFORCEMENT_DEBOUNCE_MS, MANAGER_GRACE_PERIOD_MS, IDLE_PATTERNS, MANAGER_ROLES, AUDIT_LOG_PATH, _lastNudge;
35666
+ var TASK_ENFORCEMENT_INTERVAL_MS, TASK_ENFORCEMENT_DEBOUNCE_MS, MANAGER_GRACE_PERIOD_MS, IDLE_PATTERNS, NUDGE_ECHO_PATTERNS, MANAGER_ROLES, AUDIT_LOG_PATH, _lastNudge;
35473
35667
  var init_task_enforcement = __esm({
35474
35668
  "src/lib/task-enforcement.ts"() {
35475
35669
  "use strict";
@@ -35484,6 +35678,13 @@ var init_task_enforcement = __esm({
35484
35678
  "Anything else?",
35485
35679
  "What do you need?"
35486
35680
  ];
35681
+ NUDGE_ECHO_PATTERNS = [
35682
+ "You have pending notifications",
35683
+ "You have open tasks. Run list_tasks",
35684
+ "You have more open tasks to dispatch",
35685
+ "Run list_tasks to check for assigned work",
35686
+ "Run list_tasks to find them"
35687
+ ];
35487
35688
  MANAGER_ROLES = ["COO", "CTO"];
35488
35689
  AUDIT_LOG_PATH = path59.join(
35489
35690
  process.env.HOME ?? process.env.USERPROFILE ?? "/tmp",
@@ -36227,8 +36428,8 @@ async function loadModel() {
36227
36428
  const { getLlama } = await import("node-llama-cpp");
36228
36429
  _llama = await getLlama();
36229
36430
  _model = await _llama.loadModel({ modelPath });
36230
- _context = await _model.createEmbeddingContext();
36231
- process.stderr.write("[exed] Model loaded and ready.\n");
36431
+ _context = await _model.createEmbeddingContext({ contextSize: 8192 });
36432
+ process.stderr.write("[exed] Model loaded and ready (contextSize=8192).\n");
36232
36433
  } catch (err) {
36233
36434
  process.stderr.write(`[exed] Model load failed (non-fatal): ${err instanceof Error ? err.message : String(err)}
36234
36435
  `);
@@ -36403,6 +36604,11 @@ async function handleHealthCheck(socket, requestId) {
36403
36604
  heap_used_mb: Math.round(mem.heapUsed / 1024 / 1024),
36404
36605
  external_mb: Math.round(mem.external / 1024 / 1024),
36405
36606
  array_buffers_mb: Math.round(mem.arrayBuffers / 1024 / 1024)
36607
+ },
36608
+ connections: {
36609
+ active: _activeConnections,
36610
+ high_queue: highQueue.length,
36611
+ low_queue: lowQueue.length
36406
36612
  }
36407
36613
  }
36408
36614
  } : { error: "unhealthy: model not loaded or test embed failed" }
@@ -36782,10 +36988,12 @@ function startServer() {
36782
36988
  }
36783
36989
  });
36784
36990
  socket.on("close", () => {
36991
+ buffer = "";
36785
36992
  _activeConnections--;
36786
36993
  checkIdle();
36787
36994
  });
36788
36995
  socket.on("error", () => {
36996
+ buffer = "";
36789
36997
  _activeConnections--;
36790
36998
  checkIdle();
36791
36999
  });
@@ -36922,7 +37130,7 @@ async function startMcpHttpServer() {
36922
37130
  const httpServer = createHttpServer(async (req, res) => {
36923
37131
  res.setHeader("Access-Control-Allow-Origin", "http://127.0.0.1");
36924
37132
  res.setHeader("Access-Control-Allow-Methods", "POST, GET, DELETE, OPTIONS");
36925
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Agent-Id, X-Agent-Role, Mcp-Session-Id");
37133
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Agent-Id, X-Agent-Role, X-Exe-Session, Mcp-Session-Id");
36926
37134
  res.setHeader("Access-Control-Expose-Headers", "Mcp-Session-Id");
36927
37135
  if (req.method === "OPTIONS") {
36928
37136
  res.writeHead(204);
@@ -36930,9 +37138,19 @@ async function startMcpHttpServer() {
36930
37138
  return;
36931
37139
  }
36932
37140
  const url = new URL(req.url || "/", `http://127.0.0.1:${MCP_HTTP_PORT}`);
37141
+ if (url.pathname === "/.well-known/oauth-protected-resource" || url.pathname === "/.well-known/oauth-protected-resource/mcp") {
37142
+ res.writeHead(200, { "Content-Type": "application/json" });
37143
+ res.end(JSON.stringify({
37144
+ resource: `http://127.0.0.1:${MCP_HTTP_PORT}/mcp`,
37145
+ authorization_servers: [],
37146
+ scopes_supported: [],
37147
+ bearer_methods_supported: ["header"]
37148
+ }));
37149
+ return;
37150
+ }
36933
37151
  if (url.pathname !== "/mcp") {
36934
- res.writeHead(404, { "Content-Type": "text/plain" });
36935
- res.end("Not Found");
37152
+ res.writeHead(404, { "Content-Type": "application/json" });
37153
+ res.end(JSON.stringify({ error: "not_found" }));
36936
37154
  return;
36937
37155
  }
36938
37156
  const authHeader = req.headers.authorization;
@@ -36945,6 +37163,7 @@ async function startMcpHttpServer() {
36945
37163
  }
36946
37164
  const agentId = req.headers["x-agent-id"] || "default";
36947
37165
  const agentRole = req.headers["x-agent-role"] || "employee";
37166
+ const sessionHint = req.headers["x-exe-session"] || "";
36948
37167
  const runtime = inferMcpRuntime({
36949
37168
  runtimeHeader: req.headers["x-agent-runtime"] || req.headers["x-runtime"],
36950
37169
  userAgent: req.headers["user-agent"],
@@ -36981,7 +37200,7 @@ async function startMcpHttpServer() {
36981
37200
  onsessioninitialized: (sid) => {
36982
37201
  transports.set(sid, transport);
36983
37202
  sessionLastSeen.set(sid, Date.now());
36984
- sessionDetails.set(sid, { agentId, agentRole, runtime });
37203
+ sessionDetails.set(sid, { agentId, agentRole, runtime, sessionHint });
36985
37204
  recordMcpHttpEvent({
36986
37205
  level: "info",
36987
37206
  message: "session_initialized",
@@ -37016,6 +37235,8 @@ async function startMcpHttpServer() {
37016
37235
  });
37017
37236
  return;
37018
37237
  }
37238
+ const prevSession = process.env.EXE_SESSION_NAME;
37239
+ if (sessionHint) process.env.EXE_SESSION_NAME = sessionHint;
37019
37240
  const startedAt = Date.now();
37020
37241
  const parsedRequest = parsedBody;
37021
37242
  const toolName = parsedRequest?.method === "tools/call" ? parsedRequest.params?.name : void 0;
@@ -37072,6 +37293,11 @@ async function startMcpHttpServer() {
37072
37293
  runtime
37073
37294
  });
37074
37295
  }
37296
+ } finally {
37297
+ if (sessionHint) {
37298
+ if (prevSession) process.env.EXE_SESSION_NAME = prevSession;
37299
+ else delete process.env.EXE_SESSION_NAME;
37300
+ }
37075
37301
  }
37076
37302
  });
37077
37303
  const MCP_HTTP_HOST = process.env.EXE_MCP_HOST || process.env.EXED_HOST || "127.0.0.1";