@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
@@ -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];
@@ -2436,6 +2451,13 @@ async function ensureSchema() {
2436
2451
  } catch (e) {
2437
2452
  logCatchDebug("migration", e);
2438
2453
  }
2454
+ for (const col of ["created_by_agent TEXT", "created_by_device TEXT", "source_session_id TEXT"]) {
2455
+ try {
2456
+ await client.execute({ sql: `ALTER TABLE behaviors ADD COLUMN ${col}`, args: [] });
2457
+ } catch (e) {
2458
+ logCatchDebug("migration", e);
2459
+ }
2460
+ }
2439
2461
  try {
2440
2462
  await client.execute({
2441
2463
  sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
@@ -3652,6 +3674,22 @@ async function ensureSchema() {
3652
3674
  } catch (e) {
3653
3675
  logCatchDebug("migration", e);
3654
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
+ }
3655
3693
  }
3656
3694
  async function disposeDatabase() {
3657
3695
  if (_walCheckpointTimer) {
@@ -6065,11 +6103,17 @@ var init_platform_procedures = __esm({
6065
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."
6066
6104
  },
6067
6105
  {
6068
- title: "Customer orchestration maturity \u2014 recommend, never trap",
6106
+ title: "Orchestration phase guidance \u2014 recommend, never trap",
6069
6107
  domain: "workflow",
6070
6108
  priority: "p1",
6071
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."
6072
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
+ },
6073
6117
  {
6074
6118
  title: "Single dispatch path \u2014 create_task only",
6075
6119
  domain: "workflow",
@@ -6103,6 +6147,12 @@ var init_platform_procedures = __esm({
6103
6147
  priority: "p0",
6104
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."
6105
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
+ },
6106
6156
  {
6107
6157
  title: "Customer patch triage \u2014 upstream bug vs customization",
6108
6158
  domain: "support",
@@ -6254,7 +6304,7 @@ var init_platform_procedures = __esm({
6254
6304
  title: "MCP tool dispatch \u2014 all tools use action parameter",
6255
6305
  domain: "tool-use",
6256
6306
  priority: "p0",
6257
- 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.'
6307
+ 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.'
6258
6308
  },
6259
6309
  {
6260
6310
  title: "MCP tools \u2014 memory, decision, and search",
@@ -6388,10 +6438,24 @@ function stableId3(memoryId, type, content) {
6388
6438
  return createHash3("sha256").update(`${memoryId}:${type}:${content}`).digest("hex").slice(0, 32);
6389
6439
  }
6390
6440
  function cleanText(text3) {
6391
- 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;
6392
6447
  }
6393
- function splitSentences(text3) {
6394
- 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;
6395
6459
  }
6396
6460
  function inferCardType(sentence, toolName) {
6397
6461
  const lower = sentence.toLowerCase();
@@ -6423,12 +6487,12 @@ function predicateFor(type) {
6423
6487
  }
6424
6488
  }
6425
6489
  function extractMemoryCards(row) {
6426
- const sentences = splitSentences(row.raw_text);
6490
+ const segments = splitSegments(row.raw_text);
6427
6491
  const cards = [];
6428
- for (const sentence of sentences) {
6492
+ for (const sentence of segments) {
6429
6493
  const type = inferCardType(sentence, row.tool_name);
6430
6494
  const subject = extractSubject(sentence, row.agent_id);
6431
- 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;
6432
6496
  cards.push({
6433
6497
  id: stableId3(row.id, type, content),
6434
6498
  memory_id: row.id,
@@ -6524,13 +6588,14 @@ Source memory: ${String(row.source_ref ?? row.memory_id)}`,
6524
6588
  last_accessed: String(row.timestamp)
6525
6589
  }));
6526
6590
  }
6527
- var MAX_CARDS_PER_MEMORY, MAX_SENTENCE_CHARS;
6591
+ var MAX_CARDS_PER_MEMORY, MAX_SEGMENT_CHARS, MIN_SEGMENT_CHARS;
6528
6592
  var init_memory_cards = __esm({
6529
6593
  "src/lib/memory-cards.ts"() {
6530
6594
  "use strict";
6531
6595
  init_database();
6532
- MAX_CARDS_PER_MEMORY = 6;
6533
- MAX_SENTENCE_CHARS = 360;
6596
+ MAX_CARDS_PER_MEMORY = 8;
6597
+ MAX_SEGMENT_CHARS = 500;
6598
+ MIN_SEGMENT_CHARS = 20;
6534
6599
  }
6535
6600
  });
6536
6601
 
@@ -8721,7 +8786,7 @@ async function hybridSearch(queryText, agentId, options) {
8721
8786
  try {
8722
8787
  const client = getClient();
8723
8788
  void client.execute({
8724
- 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})`,
8725
8790
  args: [now2, ...ids]
8726
8791
  }).catch(() => {
8727
8792
  });
@@ -9719,6 +9784,23 @@ var init_ask_team_memory = __esm({
9719
9784
  });
9720
9785
 
9721
9786
  // src/lib/license.ts
9787
+ var license_exports = {};
9788
+ __export(license_exports, {
9789
+ LICENSE_PUBLIC_KEY_PEM: () => LICENSE_PUBLIC_KEY_PEM,
9790
+ PLAN_LIMITS: () => PLAN_LIMITS,
9791
+ assertVpsLicense: () => assertVpsLicense,
9792
+ checkLicense: () => checkLicense,
9793
+ getCachedLicense: () => getCachedLicense,
9794
+ isFeatureAllowed: () => isFeatureAllowed,
9795
+ loadDeviceId: () => loadDeviceId,
9796
+ loadLicense: () => loadLicense,
9797
+ mirrorLicenseKey: () => mirrorLicenseKey,
9798
+ readCachedLicenseToken: () => readCachedLicenseToken,
9799
+ saveLicense: () => saveLicense,
9800
+ startLicenseRevalidation: () => startLicenseRevalidation,
9801
+ stopLicenseRevalidation: () => stopLicenseRevalidation,
9802
+ validateLicense: () => validateLicense
9803
+ });
9722
9804
  import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, existsSync as existsSync16, mkdirSync as mkdirSync6 } from "fs";
9723
9805
  import { randomUUID as randomUUID3 } from "crypto";
9724
9806
  import { createRequire as createRequire3 } from "module";
@@ -10002,7 +10084,135 @@ function isFeatureAllowed(license, feature) {
10002
10084
  return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
10003
10085
  }
10004
10086
  }
10005
- var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, _prismaPromise, _prismaFailed, CACHE_MAX_AGE_MS;
10087
+ function mirrorLicenseKey(apiKey) {
10088
+ const trimmed = apiKey.trim();
10089
+ if (!trimmed) return;
10090
+ saveLicense(trimmed);
10091
+ }
10092
+ async function assertVpsLicense(opts) {
10093
+ const env = opts?.env ?? process.env;
10094
+ const inProduction = env.NODE_ENV === "production";
10095
+ if (!opts?.force && !inProduction) {
10096
+ return { ...FREE_LICENSE, plan: "free" };
10097
+ }
10098
+ const envKey = env.EXE_LICENSE_KEY?.trim();
10099
+ if (envKey) {
10100
+ saveLicense(envKey);
10101
+ }
10102
+ const apiKey = envKey ?? loadLicense();
10103
+ if (!apiKey) {
10104
+ throw new Error(
10105
+ "License required: set EXE_LICENSE_KEY env var with your exe_sk_* key. Purchase at https://askexe.com. This VPS image refuses to boot without a valid license."
10106
+ );
10107
+ }
10108
+ const deviceId = loadDeviceId();
10109
+ let backendResponse = null;
10110
+ let explicitRejection = false;
10111
+ let transientFailure = false;
10112
+ try {
10113
+ const res = await fetchRetry(`${API_BASE}/auth/activate`, {
10114
+ method: "POST",
10115
+ headers: { "Content-Type": "application/json" },
10116
+ body: JSON.stringify({ apiKey, deviceId }),
10117
+ signal: AbortSignal.timeout(1e4)
10118
+ });
10119
+ if (res.ok) {
10120
+ backendResponse = await res.json();
10121
+ if (!backendResponse.valid) explicitRejection = true;
10122
+ } else if (res.status === 401 || res.status === 403) {
10123
+ explicitRejection = true;
10124
+ } else {
10125
+ transientFailure = true;
10126
+ }
10127
+ } catch {
10128
+ transientFailure = true;
10129
+ }
10130
+ if (backendResponse?.valid) {
10131
+ if (backendResponse.token) {
10132
+ cacheResponse(backendResponse.token);
10133
+ const verified = await verifyLicenseJwt(backendResponse.token);
10134
+ if (verified) return verified;
10135
+ }
10136
+ const limits = PLAN_LIMITS[backendResponse.plan] ?? PLAN_LIMITS.free;
10137
+ return {
10138
+ valid: true,
10139
+ plan: backendResponse.plan,
10140
+ email: backendResponse.email,
10141
+ expiresAt: backendResponse.expiresAt,
10142
+ deviceLimit: limits.devices,
10143
+ employeeLimit: limits.employees,
10144
+ memoryLimit: limits.memories
10145
+ };
10146
+ }
10147
+ if (explicitRejection) {
10148
+ throw new Error(
10149
+ `License invalid or expired. Renew at https://askexe.com, then restart. Backend rejected key ending in ...${apiKey.slice(-4)}.`
10150
+ );
10151
+ }
10152
+ if (!transientFailure) {
10153
+ throw new Error(
10154
+ "License validation failed: unknown backend state. Restore network connectivity to https://cloud.askexe.com and retry."
10155
+ );
10156
+ }
10157
+ const fresh = await getCachedLicense();
10158
+ if (fresh && fresh.valid) return fresh;
10159
+ const graceDays = opts?.offlineGraceDays ?? 7;
10160
+ const graceMs = graceDays * 24 * 60 * 60 * 1e3;
10161
+ try {
10162
+ const token = readCachedLicenseToken();
10163
+ if (token) {
10164
+ const payloadB64 = token.split(".")[1];
10165
+ if (payloadB64) {
10166
+ const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
10167
+ const expMs = (payload.exp ?? 0) * 1e3;
10168
+ if (Date.now() < expMs + graceMs) {
10169
+ const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
10170
+ const { payload: verified } = await jwtVerify(token, key, {
10171
+ algorithms: [LICENSE_JWT_ALG],
10172
+ clockTolerance: graceDays * 24 * 60 * 60
10173
+ });
10174
+ const plan = verified.plan ?? "free";
10175
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
10176
+ return {
10177
+ valid: true,
10178
+ plan,
10179
+ email: verified.sub ?? "",
10180
+ expiresAt: verified.exp ? new Date(verified.exp * 1e3).toISOString() : null,
10181
+ deviceLimit: limits.devices,
10182
+ employeeLimit: limits.employees,
10183
+ memoryLimit: limits.memories
10184
+ };
10185
+ }
10186
+ }
10187
+ }
10188
+ } catch {
10189
+ }
10190
+ throw new Error(
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.`
10192
+ );
10193
+ }
10194
+ function startLicenseRevalidation(intervalMs = 36e5) {
10195
+ if (_revalTimer) return;
10196
+ _revalTimer = setInterval(async () => {
10197
+ try {
10198
+ const license = await checkLicense();
10199
+ if (!license.valid) {
10200
+ process.stderr.write("[exe-os] License expired or invalid \u2014 features may be restricted\n");
10201
+ }
10202
+ } catch {
10203
+ }
10204
+ }, intervalMs);
10205
+ if (_revalTimer && typeof _revalTimer === "object" && "unref" in _revalTimer) {
10206
+ _revalTimer.unref();
10207
+ }
10208
+ }
10209
+ function stopLicenseRevalidation() {
10210
+ if (_revalTimer) {
10211
+ clearInterval(_revalTimer);
10212
+ _revalTimer = null;
10213
+ }
10214
+ }
10215
+ var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, _prismaPromise, _prismaFailed, CACHE_MAX_AGE_MS, _revalTimer;
10006
10216
  var init_license = __esm({
10007
10217
  "src/lib/license.ts"() {
10008
10218
  "use strict";
@@ -10010,7 +10220,7 @@ var init_license = __esm({
10010
10220
  LICENSE_PATH = path17.join(EXE_AI_DIR, "license.key");
10011
10221
  CACHE_PATH = path17.join(EXE_AI_DIR, "license-cache.json");
10012
10222
  DEVICE_ID_PATH = path17.join(EXE_AI_DIR, "device-id");
10013
- API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
10223
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
10014
10224
  RETRY_DELAY_MS = 500;
10015
10225
  LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
10016
10226
  MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
@@ -10036,6 +10246,7 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
10036
10246
  _prismaPromise = null;
10037
10247
  _prismaFailed = false;
10038
10248
  CACHE_MAX_AGE_MS = 36e5;
10249
+ _revalTimer = null;
10039
10250
  }
10040
10251
  });
10041
10252
 
@@ -13191,11 +13402,12 @@ function getDispatchedBy(sessionKey) {
13191
13402
  }
13192
13403
  }
13193
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
+ }
13194
13409
  const mySession = getMySession();
13195
13410
  if (!mySession) {
13196
- if (process.env.EXE_SESSION_NAME) {
13197
- return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
13198
- }
13199
13411
  return null;
13200
13412
  }
13201
13413
  const fromSessionName = extractRootExe(mySession);
@@ -13210,6 +13422,10 @@ function resolveExeSession() {
13210
13422
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
13211
13423
  `
13212
13424
  );
13425
+ try {
13426
+ registerParentExe(key, fromSessionName);
13427
+ } catch {
13428
+ }
13213
13429
  candidate = fromSessionName;
13214
13430
  } else {
13215
13431
  candidate = fromCache;
@@ -14143,6 +14359,19 @@ async function resolveTask(client, identifier, scopeSession) {
14143
14359
  args: [identifier, ...scope.args]
14144
14360
  });
14145
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
+ }
14146
14375
  result3 = await client.execute({
14147
14376
  sql: `SELECT * FROM tasks WHERE task_file LIKE ?${scope.sql}`,
14148
14377
  args: [`%${identifier}%`, ...scope.args]
@@ -14689,12 +14918,13 @@ async function cascadeUnblock(taskId, baseDir, now2) {
14689
14918
  WHERE blocked_by = ? AND status = 'blocked'`,
14690
14919
  args: [now2, taskId]
14691
14920
  });
14692
- if (baseDir && unblocked.rowsAffected > 0) {
14693
- const ubScope = sessionScopeFilter();
14694
- const unblockedRows = await client.execute({
14695
- sql: `SELECT task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
14696
- args: [now2, ...ubScope.args]
14697
- });
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) {
14698
14928
  for (const ur of unblockedRows.rows) {
14699
14929
  try {
14700
14930
  const ubFile = path28.join(baseDir, String(ur.task_file));
@@ -14706,6 +14936,19 @@ async function cascadeUnblock(taskId, baseDir, now2) {
14706
14936
  }
14707
14937
  }
14708
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
+ }
14709
14952
  }
14710
14953
  async function findNextTask(assignedTo) {
14711
14954
  const client = getClient();
@@ -14847,6 +15090,15 @@ var init_tasks_notify = __esm({
14847
15090
  // src/lib/behaviors.ts
14848
15091
  import crypto9 from "crypto";
14849
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
+ }
14850
15102
  const client = getClient();
14851
15103
  const id = crypto9.randomUUID();
14852
15104
  const now2 = (/* @__PURE__ */ new Date()).toISOString();
@@ -14857,10 +15109,18 @@ async function storeBehavior(opts) {
14857
15109
  vector = new Float32Array(vec);
14858
15110
  } catch {
14859
15111
  }
15112
+ let createdByDevice = null;
15113
+ try {
15114
+ const { loadDeviceId: loadDeviceId2 } = await Promise.resolve().then(() => (init_license(), license_exports));
15115
+ createdByDevice = loadDeviceId2() ?? null;
15116
+ } catch {
15117
+ }
15118
+ const createdByAgent = process.env.AGENT_ID ?? null;
15119
+ const sourceSessionId = process.env.CLAUDE_SESSION_ID ?? process.env.SESSION_ID ?? null;
14860
15120
  await client.execute({
14861
- sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector)
14862
- VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?)`,
14863
- args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now2, now2, vector ? vector.buffer : null]
15121
+ sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector, created_by_agent, created_by_device, source_session_id)
15122
+ VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?)`,
15123
+ args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now2, now2, vector ? vector.buffer : null, createdByAgent, createdByDevice, sourceSessionId]
14864
15124
  });
14865
15125
  return id;
14866
15126
  }
@@ -14882,7 +15142,10 @@ async function listBehaviorsByDomain(agentId, domain) {
14882
15142
  active: Number(r.active),
14883
15143
  created_at: String(r.created_at),
14884
15144
  updated_at: String(r.updated_at),
14885
- vector: r.vector ? Array.from(new Float32Array(r.vector)) : null
15145
+ vector: r.vector ? Array.from(new Float32Array(r.vector)) : null,
15146
+ created_by_agent: r.created_by_agent ? String(r.created_by_agent) : null,
15147
+ created_by_device: r.created_by_device ? String(r.created_by_device) : null,
15148
+ source_session_id: r.source_session_id ? String(r.source_session_id) : null
14886
15149
  }));
14887
15150
  }
14888
15151
  async function deactivateBehavior(id) {
@@ -15321,6 +15584,12 @@ async function updateTask(input) {
15321
15584
  }
15322
15585
  }
15323
15586
  }
15587
+ if (input.status === "cancelled") {
15588
+ try {
15589
+ await cascadeUnblock(taskId, input.baseDir, now2);
15590
+ } catch {
15591
+ }
15592
+ }
15324
15593
  if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
15325
15594
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
15326
15595
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
@@ -17589,8 +17858,11 @@ function rowToBehavior(r) {
17589
17858
  active: Number(r.active),
17590
17859
  created_at: String(r.created_at),
17591
17860
  updated_at: String(r.updated_at),
17592
- vector: null
17861
+ vector: null,
17593
17862
  // Not needed for listing
17863
+ created_by_agent: r.created_by_agent ? String(r.created_by_agent) : null,
17864
+ created_by_device: r.created_by_device ? String(r.created_by_device) : null,
17865
+ source_session_id: r.source_session_id ? String(r.source_session_id) : null
17594
17866
  };
17595
17867
  }
17596
17868
  function registerListBehaviors(server) {
@@ -20759,15 +21031,16 @@ function registerSetAgentConfig(server) {
20759
21031
  "set_agent_config",
20760
21032
  {
20761
21033
  title: "Set Agent Config",
20762
- 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).",
20763
21035
  inputSchema: {
20764
21036
  agent_id: z48.string().optional().describe("Agent name, or 'default' for org-wide default. Omit to view all agents."),
20765
21037
  runtime: z48.string().optional().describe(`Runtime: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`),
20766
21038
  model: z48.string().optional().describe("Model name (e.g. claude-opus-4, gpt-5.4, anthropic/claude-sonnet-4-6)"),
20767
- 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.")
20768
21041
  }
20769
21042
  },
20770
- async ({ agent_id, runtime, model, reasoning_effort }) => {
21043
+ async ({ agent_id, runtime, model, reasoning_effort, mcps }) => {
20771
21044
  const { agentId, agentRole } = getActiveAgent();
20772
21045
  const isCoordinator = isCoordinatorRole(agentRole) || (() => {
20773
21046
  try {
@@ -20799,15 +21072,26 @@ function registerSetAgentConfig(server) {
20799
21072
  isError: true
20800
21073
  };
20801
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
+ }
20802
21085
  if (!runtime || !model) {
20803
21086
  if (agent_id) {
20804
21087
  const cfg = getAgentRuntime(agent_id);
20805
21088
  const label = RUNTIME_LABELS[cfg.runtime] ?? cfg.runtime;
21089
+ const mcpInfo = cfg.mcps ? ` | MCPs: [${cfg.mcps.join(", ")}]` : " | MCPs: all";
20806
21090
  return {
20807
21091
  content: [
20808
21092
  {
20809
21093
  type: "text",
20810
- text: `${agent_id}: ${label} / ${cfg.model}`
21094
+ text: `${agent_id}: ${label} / ${cfg.model}${mcpInfo}`
20811
21095
  }
20812
21096
  ]
20813
21097
  };
@@ -20854,7 +21138,7 @@ function registerSetAgentConfig(server) {
20854
21138
  isError: true
20855
21139
  };
20856
21140
  }
20857
- const result3 = setAgentRuntime(agent_id, runtime, model, reasoning_effort);
21141
+ const result3 = setAgentRuntime(agent_id, runtime, model, reasoning_effort, mcps);
20858
21142
  if (!result3.ok) {
20859
21143
  return {
20860
21144
  content: [{ type: "text", text: result3.error }],
@@ -20863,6 +21147,7 @@ function registerSetAgentConfig(server) {
20863
21147
  }
20864
21148
  const parts = [`Set ${agent_id} \u2192 runtime=${runtime} model=${model}`];
20865
21149
  if (reasoning_effort) parts[0] += ` reasoning_effort=${reasoning_effort}`;
21150
+ if (mcps) parts[0] += ` mcps=[${mcps.join(", ")}]`;
20866
21151
  return {
20867
21152
  content: [{ type: "text", text: parts[0] }]
20868
21153
  };
@@ -22462,10 +22747,18 @@ async function pollOrphanedTasks(deps, nowMs = Date.now()) {
22462
22747
  } catch {
22463
22748
  return [];
22464
22749
  }
22750
+ const coordinatorSet = new Set(coordinatorSessions);
22465
22751
  const agentTasks = /* @__PURE__ */ new Map();
22466
22752
  for (const t of tasksByAgent) {
22467
22753
  const scope = t.sessionScope || coordinatorSessions[0] || null;
22468
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
+ }
22469
22762
  const key = `${t.agentId}::${scope}`;
22470
22763
  const existing = agentTasks.get(key) ?? [];
22471
22764
  existing.push({ taskId: t.taskId, priority: t.priority, sessionScope: scope });
@@ -25192,6 +25485,27 @@ async function cloudSync(config2) {
25192
25485
  if (stmts.length > 0) await client.batch(stmts, "write");
25193
25486
  pulled = pullResult.records.length;
25194
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
+ }
25195
25509
  const stmts = pullResult.records.map((rec) => ({
25196
25510
  sql: `INSERT OR REPLACE INTO memories
25197
25511
  (id, agent_id, agent_role, session_id, timestamp,
@@ -28246,7 +28560,7 @@ var init_starter_packs = __esm({
28246
28560
 
28247
28561
  // src/lib/employee-templates.ts
28248
28562
  function renderClientCOOTemplate(vars) {
28249
- const resolved = { ...vars, title: vars.title || "Chief Operating Officer" };
28563
+ const resolved = { ...vars, title: vars.title || "Chief of Staff" };
28250
28564
  for (const key of CLIENT_COO_PLACEHOLDERS) {
28251
28565
  const value = resolved[key];
28252
28566
  if (typeof value !== "string" || value.length === 0) {
@@ -29828,7 +30142,10 @@ function rowToBehavior2(r) {
29828
30142
  active: Number(r.active),
29829
30143
  created_at: String(r.created_at),
29830
30144
  updated_at: String(r.updated_at),
29831
- vector: null
30145
+ vector: null,
30146
+ created_by_agent: r.created_by_agent ? String(r.created_by_agent) : null,
30147
+ created_by_device: r.created_by_device ? String(r.created_by_device) : null,
30148
+ source_session_id: r.source_session_id ? String(r.source_session_id) : null
29832
30149
  };
29833
30150
  }
29834
30151
  function registerBehavior(server) {
@@ -29836,9 +30153,9 @@ function registerBehavior(server) {
29836
30153
  "behavior",
29837
30154
  {
29838
30155
  title: "Behavior",
29839
- description: "Manage behavioral patterns, corrections, and reusable procedures for employees. Actions: store (create new), list (query existing), deactivate (soft-delete, restricted).",
30156
+ description: "Manage behavioral patterns, corrections, and reusable procedures for employees. Actions: store (create new), list (query existing), audit (creation context), deactivate (soft-delete, restricted).",
29840
30157
  inputSchema: {
29841
- action: z78.enum(["store", "list", "deactivate"]).describe("Action to perform"),
30158
+ action: z78.enum(["store", "list", "deactivate", "audit"]).describe("Action to perform"),
29842
30159
  content: z78.string().max(500).optional().describe("The behavioral instruction \u2014 one clear sentence (store)"),
29843
30160
  domain: z78.string().optional().describe("Category: workflow, code-style, tool-use, communication, architecture, testing"),
29844
30161
  priority: z78.enum(["p0", "p1", "p2"]).optional().describe("Priority tier. p0 = always included. p1 = standard (default). p2 = nice-to-have."),
@@ -29887,7 +30204,8 @@ function registerBehavior(server) {
29887
30204
  const lines = behaviors.map((b) => {
29888
30205
  const scope = b.project_name ?? "global";
29889
30206
  const dom = b.domain ?? "general";
29890
- return `- [${b.agent_id}] [${dom}] (${scope}) ${b.content}
30207
+ const createdBy = b.created_by_agent ? ` (by: ${b.created_by_agent})` : "";
30208
+ return `- [${b.agent_id}] [${dom}] (${scope})${createdBy} ${b.content}
29891
30209
  ID: ${b.id}`;
29892
30210
  });
29893
30211
  return {
@@ -29972,6 +30290,39 @@ Use /exe-forget to remove any that this supersedes.`;
29972
30290
  content: [{ type: "text", text: responseText }]
29973
30291
  };
29974
30292
  }
30293
+ if (action === "audit") {
30294
+ if (!behavior_id) {
30295
+ return {
30296
+ content: [{ type: "text", text: "Missing required field: behavior_id is required for audit action." }],
30297
+ isError: true
30298
+ };
30299
+ }
30300
+ const client2 = getClient();
30301
+ const auditResult = await client2.execute({
30302
+ sql: `SELECT id, agent_id, content, domain, priority, project_name, active,
30303
+ created_at, updated_at, created_by_agent, created_by_device, source_session_id
30304
+ FROM behaviors WHERE id = ?`,
30305
+ args: [behavior_id]
30306
+ });
30307
+ if (auditResult.rows.length === 0) {
30308
+ return { content: [{ type: "text", text: `Behavior not found: ${behavior_id}` }], isError: true };
30309
+ }
30310
+ const b = auditResult.rows[0];
30311
+ const auditText = [
30312
+ `## Behavior Audit: ${b.id}`,
30313
+ `**Agent:** ${b.agent_id}`,
30314
+ `**Content:** ${b.content}`,
30315
+ `**Domain:** ${b.domain ?? "none"} | **Priority:** ${b.priority} | **Active:** ${b.active ? "yes" : "no"}`,
30316
+ `**Project:** ${b.project_name ?? "global"}`,
30317
+ `**Created:** ${b.created_at} | **Updated:** ${b.updated_at}`,
30318
+ ``,
30319
+ `### Creation context`,
30320
+ `**Created by agent:** ${b.created_by_agent ?? "unknown (pre-audit)"}`,
30321
+ `**Created from device:** ${b.created_by_device ?? "unknown (pre-audit)"}`,
30322
+ `**Source session:** ${b.source_session_id ?? "unknown (pre-audit)"}`
30323
+ ].join("\n");
30324
+ return { content: [{ type: "text", text: auditText }] };
30325
+ }
29975
30326
  const caller = getActiveAgent();
29976
30327
  const allowed = canCoordinate(caller.agentId, caller.agentRole);
29977
30328
  if (!allowed) {
@@ -34547,7 +34898,7 @@ function registerAllTools(server, license, hasKey) {
34547
34898
  if (license && !isToolAllowedForPlan(name, license.plan)) return;
34548
34899
  fn(server);
34549
34900
  };
34550
- const mcpToolSurface = process.env.EXE_MCP_TOOL_SURFACE ?? "legacy";
34901
+ const mcpToolSurface = process.env.EXE_MCP_TOOL_SURFACE ?? "consolidated";
34551
34902
  const exposeConsolidatedTasks = mcpToolSurface === "both" || mcpToolSurface === "consolidated";
34552
34903
  const exposeLegacyTasks = mcpToolSurface !== "consolidated";
34553
34904
  const exposeConsolidatedMemory = mcpToolSurface === "both" || mcpToolSurface === "consolidated";
@@ -35096,12 +35447,14 @@ __export(task_enforcement_exports, {
35096
35447
  IDLE_PATTERNS: () => IDLE_PATTERNS,
35097
35448
  MANAGER_GRACE_PERIOD_MS: () => MANAGER_GRACE_PERIOD_MS,
35098
35449
  MANAGER_ROLES: () => MANAGER_ROLES,
35450
+ NUDGE_ECHO_PATTERNS: () => NUDGE_ECHO_PATTERNS,
35099
35451
  TASK_ENFORCEMENT_DEBOUNCE_MS: () => TASK_ENFORCEMENT_DEBOUNCE_MS,
35100
35452
  TASK_ENFORCEMENT_INTERVAL_MS: () => TASK_ENFORCEMENT_INTERVAL_MS,
35101
35453
  _resetNudgeState: () => _resetNudgeState,
35102
35454
  decideManagerAction: () => decideManagerAction,
35103
35455
  decideWorkerAction: () => decideWorkerAction,
35104
35456
  isIdlePane: () => isIdlePane,
35457
+ isNudgeEcho: () => isNudgeEcho,
35105
35458
  runTaskEnforcementTick: () => runTaskEnforcementTick,
35106
35459
  sendNudge: () => sendNudge
35107
35460
  });
@@ -35114,20 +35467,26 @@ function writeAuditEntry(entry) {
35114
35467
  } catch {
35115
35468
  }
35116
35469
  }
35117
- function decideManagerAction(counts, isIdle, withinGracePeriod) {
35470
+ function decideManagerAction(counts, isIdle, withinGracePeriod, nudgeEcho) {
35118
35471
  if (counts.openTasks === 0) return "skip";
35119
35472
  if (withinGracePeriod) return "grace_period";
35120
35473
  if (!isIdle) return "active";
35474
+ if (nudgeEcho) return "active";
35475
+ if ((counts.inProgressTasks ?? 0) > 0) return "active";
35121
35476
  if (counts.activeSubtasks === 0) return "nudge";
35122
35477
  return "soft_nudge";
35123
35478
  }
35124
- function decideWorkerAction(taskCount, isIdle) {
35479
+ function decideWorkerAction(taskCount, isIdle, nudgeEcho) {
35125
35480
  if (taskCount === 0) return "skip";
35481
+ if (nudgeEcho) return "idle";
35126
35482
  return isIdle ? "nudge" : "idle";
35127
35483
  }
35128
35484
  function isIdlePane(paneText) {
35129
35485
  return IDLE_PATTERNS.some((p) => paneText.includes(p));
35130
35486
  }
35487
+ function isNudgeEcho(paneText) {
35488
+ return NUDGE_ECHO_PATTERNS.some((p) => paneText.includes(p));
35489
+ }
35131
35490
  function sendNudge(transport, session, runtime, action) {
35132
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.";
35133
35492
  if (transport.sendKeysLiteral) {
@@ -35147,16 +35506,21 @@ async function runTaskEnforcementTick(deps) {
35147
35506
  const { transport, employees, client, scopeFilter } = deps;
35148
35507
  const now2 = deps.now ?? Date.now();
35149
35508
  const sessions = transport.listSessions();
35509
+ let processedAnyEmployee = false;
35150
35510
  for (const session of sessions) {
35151
35511
  if (!session.includes("-")) continue;
35152
35512
  const agentName = session.split("-")[0]?.replace(/\d+$/, "");
35153
35513
  if (!agentName) continue;
35514
+ if ("isAlive" in transport && typeof transport.isAlive === "function") {
35515
+ if (!transport.isAlive(session)) continue;
35516
+ }
35154
35517
  const lastNudgeTime = _lastNudge.get(session) ?? 0;
35155
35518
  if (now2 - lastNudgeTime < TASK_ENFORCEMENT_DEBOUNCE_MS) {
35156
35519
  continue;
35157
35520
  }
35158
35521
  const employee = employees.find((e) => e.name === agentName);
35159
35522
  if (!employee) continue;
35523
+ processedAnyEmployee = true;
35160
35524
  let effectiveScope = scopeFilter;
35161
35525
  const dashIndex = session.indexOf("-");
35162
35526
  if (dashIndex > 0) {
@@ -35204,12 +35568,21 @@ async function runTaskEnforcementTick(deps) {
35204
35568
  }
35205
35569
  }
35206
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
+ }
35207
35579
  const pane = transport.capturePane(session, 20);
35208
35580
  paneIdle = isIdlePane(pane);
35209
- action = decideManagerAction({ openTasks, activeSubtasks }, paneIdle, withinGracePeriod);
35581
+ const nudgeEcho = isNudgeEcho(pane);
35582
+ action = decideManagerAction({ openTasks, activeSubtasks, inProgressTasks }, paneIdle, withinGracePeriod, nudgeEcho);
35210
35583
  if (action === "skip") reason = "no open tasks";
35211
35584
  else if (action === "grace_period") reason = `task assigned <10min ago (${graceRemaining}s remaining)`;
35212
- 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";
35213
35586
  else if (action === "nudge") reason = `${openTasks} open tasks, 0 delegated, pane idle`;
35214
35587
  else if (action === "soft_nudge") reason = `${openTasks} open tasks, ${activeSubtasks} delegated, pane idle`;
35215
35588
  writeAuditEntry({
@@ -35238,7 +35611,8 @@ async function runTaskEnforcementTick(deps) {
35238
35611
  if (taskCount > 0) {
35239
35612
  const pane = transport.capturePane(session, 20);
35240
35613
  paneIdle = isIdlePane(pane);
35241
- action = decideWorkerAction(taskCount, paneIdle);
35614
+ const workerNudgeEcho = isNudgeEcho(pane);
35615
+ action = decideWorkerAction(taskCount, paneIdle, workerNudgeEcho);
35242
35616
  }
35243
35617
  if (action === "skip") reason = "no tasks";
35244
35618
  else if (action === "idle") reason = `${taskCount} tasks but pane active`;
@@ -35264,8 +35638,32 @@ async function runTaskEnforcementTick(deps) {
35264
35638
  } catch {
35265
35639
  }
35266
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
+ }
35267
35665
  }
35268
- 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;
35269
35667
  var init_task_enforcement = __esm({
35270
35668
  "src/lib/task-enforcement.ts"() {
35271
35669
  "use strict";
@@ -35280,6 +35678,13 @@ var init_task_enforcement = __esm({
35280
35678
  "Anything else?",
35281
35679
  "What do you need?"
35282
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
+ ];
35283
35688
  MANAGER_ROLES = ["COO", "CTO"];
35284
35689
  AUDIT_LOG_PATH = path59.join(
35285
35690
  process.env.HOME ?? process.env.USERPROFILE ?? "/tmp",
@@ -36023,8 +36428,8 @@ async function loadModel() {
36023
36428
  const { getLlama } = await import("node-llama-cpp");
36024
36429
  _llama = await getLlama();
36025
36430
  _model = await _llama.loadModel({ modelPath });
36026
- _context = await _model.createEmbeddingContext();
36027
- 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");
36028
36433
  } catch (err) {
36029
36434
  process.stderr.write(`[exed] Model load failed (non-fatal): ${err instanceof Error ? err.message : String(err)}
36030
36435
  `);
@@ -36199,6 +36604,11 @@ async function handleHealthCheck(socket, requestId) {
36199
36604
  heap_used_mb: Math.round(mem.heapUsed / 1024 / 1024),
36200
36605
  external_mb: Math.round(mem.external / 1024 / 1024),
36201
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
36202
36612
  }
36203
36613
  }
36204
36614
  } : { error: "unhealthy: model not loaded or test embed failed" }
@@ -36578,10 +36988,12 @@ function startServer() {
36578
36988
  }
36579
36989
  });
36580
36990
  socket.on("close", () => {
36991
+ buffer = "";
36581
36992
  _activeConnections--;
36582
36993
  checkIdle();
36583
36994
  });
36584
36995
  socket.on("error", () => {
36996
+ buffer = "";
36585
36997
  _activeConnections--;
36586
36998
  checkIdle();
36587
36999
  });
@@ -36718,7 +37130,7 @@ async function startMcpHttpServer() {
36718
37130
  const httpServer = createHttpServer(async (req, res) => {
36719
37131
  res.setHeader("Access-Control-Allow-Origin", "http://127.0.0.1");
36720
37132
  res.setHeader("Access-Control-Allow-Methods", "POST, GET, DELETE, OPTIONS");
36721
- 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");
36722
37134
  res.setHeader("Access-Control-Expose-Headers", "Mcp-Session-Id");
36723
37135
  if (req.method === "OPTIONS") {
36724
37136
  res.writeHead(204);
@@ -36726,9 +37138,19 @@ async function startMcpHttpServer() {
36726
37138
  return;
36727
37139
  }
36728
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
+ }
36729
37151
  if (url.pathname !== "/mcp") {
36730
- res.writeHead(404, { "Content-Type": "text/plain" });
36731
- res.end("Not Found");
37152
+ res.writeHead(404, { "Content-Type": "application/json" });
37153
+ res.end(JSON.stringify({ error: "not_found" }));
36732
37154
  return;
36733
37155
  }
36734
37156
  const authHeader = req.headers.authorization;
@@ -36741,6 +37163,7 @@ async function startMcpHttpServer() {
36741
37163
  }
36742
37164
  const agentId = req.headers["x-agent-id"] || "default";
36743
37165
  const agentRole = req.headers["x-agent-role"] || "employee";
37166
+ const sessionHint = req.headers["x-exe-session"] || "";
36744
37167
  const runtime = inferMcpRuntime({
36745
37168
  runtimeHeader: req.headers["x-agent-runtime"] || req.headers["x-runtime"],
36746
37169
  userAgent: req.headers["user-agent"],
@@ -36777,7 +37200,7 @@ async function startMcpHttpServer() {
36777
37200
  onsessioninitialized: (sid) => {
36778
37201
  transports.set(sid, transport);
36779
37202
  sessionLastSeen.set(sid, Date.now());
36780
- sessionDetails.set(sid, { agentId, agentRole, runtime });
37203
+ sessionDetails.set(sid, { agentId, agentRole, runtime, sessionHint });
36781
37204
  recordMcpHttpEvent({
36782
37205
  level: "info",
36783
37206
  message: "session_initialized",
@@ -36812,6 +37235,8 @@ async function startMcpHttpServer() {
36812
37235
  });
36813
37236
  return;
36814
37237
  }
37238
+ const prevSession = process.env.EXE_SESSION_NAME;
37239
+ if (sessionHint) process.env.EXE_SESSION_NAME = sessionHint;
36815
37240
  const startedAt = Date.now();
36816
37241
  const parsedRequest = parsedBody;
36817
37242
  const toolName = parsedRequest?.method === "tools/call" ? parsedRequest.params?.name : void 0;
@@ -36868,6 +37293,11 @@ async function startMcpHttpServer() {
36868
37293
  runtime
36869
37294
  });
36870
37295
  }
37296
+ } finally {
37297
+ if (sessionHint) {
37298
+ if (prevSession) process.env.EXE_SESSION_NAME = prevSession;
37299
+ else delete process.env.EXE_SESSION_NAME;
37300
+ }
36871
37301
  }
36872
37302
  });
36873
37303
  const MCP_HTTP_HOST = process.env.EXE_MCP_HOST || process.env.EXED_HOST || "127.0.0.1";