@askexenow/exe-os 0.9.93 → 0.9.95

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 (88) hide show
  1. package/deploy/compose/docker-compose.yml +1 -0
  2. package/dist/bin/agentic-ontology-backfill.js +65 -8
  3. package/dist/bin/agentic-reflection-backfill.js +54 -3
  4. package/dist/bin/agentic-semantic-label.js +54 -3
  5. package/dist/bin/backfill-conversations.js +69 -9
  6. package/dist/bin/backfill-responses.js +69 -9
  7. package/dist/bin/backfill-vectors.js +54 -3
  8. package/dist/bin/bulk-sync-postgres.js +66 -8
  9. package/dist/bin/cleanup-stale-review-tasks.js +121 -13
  10. package/dist/bin/cli.js +1561 -466
  11. package/dist/bin/customer-readiness.js +61 -0
  12. package/dist/bin/exe-agent.js +17 -3
  13. package/dist/bin/exe-assign.js +75 -9
  14. package/dist/bin/exe-boot.js +114 -12
  15. package/dist/bin/exe-call.js +17 -3
  16. package/dist/bin/exe-cloud.js +76 -10
  17. package/dist/bin/exe-dispatch.js +136 -18
  18. package/dist/bin/exe-doctor.js +75 -9
  19. package/dist/bin/exe-export-behaviors.js +75 -9
  20. package/dist/bin/exe-forget.js +94 -9
  21. package/dist/bin/exe-gateway.js +135 -18
  22. package/dist/bin/exe-heartbeat.js +121 -13
  23. package/dist/bin/exe-kill.js +75 -9
  24. package/dist/bin/exe-launch-agent.js +75 -9
  25. package/dist/bin/exe-new-employee.js +18 -4
  26. package/dist/bin/exe-pending-messages.js +121 -13
  27. package/dist/bin/exe-pending-notifications.js +121 -13
  28. package/dist/bin/exe-pending-reviews.js +121 -13
  29. package/dist/bin/exe-rename.js +75 -9
  30. package/dist/bin/exe-review.js +75 -9
  31. package/dist/bin/exe-search.js +100 -9
  32. package/dist/bin/exe-session-cleanup.js +136 -18
  33. package/dist/bin/exe-settings.js +1 -0
  34. package/dist/bin/exe-start-codex.js +65 -8
  35. package/dist/bin/exe-start-opencode.js +65 -8
  36. package/dist/bin/exe-status.js +121 -13
  37. package/dist/bin/exe-support.js +1 -0
  38. package/dist/bin/exe-team.js +75 -9
  39. package/dist/bin/git-sweep.js +136 -18
  40. package/dist/bin/graph-backfill.js +65 -8
  41. package/dist/bin/graph-export.js +75 -9
  42. package/dist/bin/intercom-check.js +136 -18
  43. package/dist/bin/scan-tasks.js +136 -18
  44. package/dist/bin/setup.js +55 -4
  45. package/dist/bin/shard-migrate.js +65 -8
  46. package/dist/bin/stack-update.js +5 -6
  47. package/dist/bin/update.js +1 -1
  48. package/dist/gateway/index.js +136 -18
  49. package/dist/hooks/bug-report-worker.js +136 -18
  50. package/dist/hooks/codex-stop-task-finalizer.js +126 -14
  51. package/dist/hooks/commit-complete.js +136 -18
  52. package/dist/hooks/error-recall.js +100 -9
  53. package/dist/hooks/ingest.js +75 -9
  54. package/dist/hooks/instructions-loaded.js +75 -9
  55. package/dist/hooks/notification.js +75 -9
  56. package/dist/hooks/post-compact.js +313 -50
  57. package/dist/hooks/post-tool-combined.js +436 -13
  58. package/dist/hooks/pre-compact.js +136 -18
  59. package/dist/hooks/pre-tool-use.js +121 -13
  60. package/dist/hooks/prompt-submit.js +194 -19
  61. package/dist/hooks/session-end.js +136 -18
  62. package/dist/hooks/session-start.js +146 -13
  63. package/dist/hooks/stop.js +121 -13
  64. package/dist/hooks/subagent-stop.js +121 -13
  65. package/dist/hooks/summary-worker.js +99 -7
  66. package/dist/index.js +136 -18
  67. package/dist/lib/cloud-sync.js +38 -0
  68. package/dist/lib/consolidation.js +3 -1
  69. package/dist/lib/database.js +37 -0
  70. package/dist/lib/db.js +37 -0
  71. package/dist/lib/device-registry.js +37 -0
  72. package/dist/lib/employee-templates.js +17 -3
  73. package/dist/lib/exe-daemon.js +916 -42
  74. package/dist/lib/hybrid-search.js +100 -9
  75. package/dist/lib/license.js +1 -1
  76. package/dist/lib/messaging.js +43 -4
  77. package/dist/lib/schedules.js +54 -3
  78. package/dist/lib/store.js +75 -9
  79. package/dist/lib/tasks.js +61 -9
  80. package/dist/lib/tmux-routing.js +61 -9
  81. package/dist/mcp/server.js +878 -42
  82. package/dist/mcp/tools/create-task.js +70 -12
  83. package/dist/mcp/tools/list-tasks.js +49 -5
  84. package/dist/mcp/tools/send-message.js +43 -4
  85. package/dist/mcp/tools/update-task.js +61 -9
  86. package/dist/runtime/index.js +136 -18
  87. package/dist/tui/App.js +135 -18
  88. package/package.json +1 -1
@@ -3175,6 +3175,20 @@ async function ensureSchema() {
3175
3175
  });
3176
3176
  } catch {
3177
3177
  }
3178
+ try {
3179
+ await client.execute({
3180
+ sql: `ALTER TABLE tasks ADD COLUMN spawn_runtime TEXT`,
3181
+ args: []
3182
+ });
3183
+ } catch {
3184
+ }
3185
+ try {
3186
+ await client.execute({
3187
+ sql: `ALTER TABLE tasks ADD COLUMN spawn_model TEXT`,
3188
+ args: []
3189
+ });
3190
+ } catch {
3191
+ }
3178
3192
  await client.executeMultiple(`
3179
3193
  CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
3180
3194
  content_text,
@@ -3426,6 +3440,22 @@ async function ensureSchema() {
3426
3440
  );
3427
3441
  } catch {
3428
3442
  }
3443
+ for (const col of [
3444
+ "ALTER TABLE memories ADD COLUMN valid_from TEXT",
3445
+ "ALTER TABLE memories ADD COLUMN invalid_at TEXT"
3446
+ ]) {
3447
+ try {
3448
+ await client.execute(col);
3449
+ } catch {
3450
+ }
3451
+ }
3452
+ try {
3453
+ await client.execute({
3454
+ sql: `UPDATE memories SET valid_from = timestamp WHERE valid_from IS NULL`,
3455
+ args: []
3456
+ });
3457
+ } catch {
3458
+ }
3429
3459
  try {
3430
3460
  await client.execute({
3431
3461
  sql: `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`,
@@ -3468,6 +3498,13 @@ async function ensureSchema() {
3468
3498
  } catch {
3469
3499
  }
3470
3500
  }
3501
+ try {
3502
+ await client.execute({
3503
+ sql: `ALTER TABLE memories ADD COLUMN procedure_for TEXT`,
3504
+ args: []
3505
+ });
3506
+ } catch {
3507
+ }
3471
3508
  try {
3472
3509
  await client.execute({
3473
3510
  sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
@@ -5877,6 +5914,20 @@ var init_platform_procedures = __esm({
5877
5914
  priority: "p1",
5878
5915
  content: "Once per session (COO boot only, never repeat), call list_my_feature_requests to check if any previously filed feature requests have been shipped by AskExe. If any request has status 'shipped' with a shipped_version, surface it to the founder immediately: '\u{1F680} N feature(s) shipped \u2014 run exe-os update to get version X.Y.Z'. This is a one-time check at boot, not a recurring poll. If no requests exist or none are shipped, skip silently. If the MCP tool is unavailable or the network call fails, skip silently \u2014 this is informational, not blocking."
5879
5916
  },
5917
+ // --- Tool guidance ---
5918
+ {
5919
+ title: "How to use company_actions \u2014 execute business actions through gateway connectors",
5920
+ domain: "tools",
5921
+ priority: "p2",
5922
+ content: "The company_actions tool executes business actions through gateway connectors (e.g. send WhatsApp, trigger workflows, update CRM). It routes through the exe-gateway on the VPS. Actions are defined by the customer's gateway configuration \u2014 each connector (WhatsApp, Shopify, email, etc.) exposes specific actions. Use query_company_brain to find available data first, then company_actions to act on it. Requires gateway auth token. Read-only founders should NOT use this \u2014 it mutates external state."
5923
+ },
5924
+ // --- Release awareness ---
5925
+ {
5926
+ title: "What's New check \u2014 surface new features after update",
5927
+ domain: "support",
5928
+ priority: "p1",
5929
+ content: "Once per session (COO boot only, never repeat), check if the installed exe-os version is newer than the last session. If it is, read the bundled release-notes.json (at the package root) and surface a brief summary to the founder: 'Updated to exe-os vX.Y.Z \u2014 N new features, M fixes.' List the top 3 features by name. This helps the founder know what they got from the update. If release-notes.json doesn't exist or the version hasn't changed, skip silently. Never repeat this check in the same session."
5930
+ },
5880
5931
  // --- Platform vs Customer ownership ---
5881
5932
  {
5882
5933
  title: "What the platform provides vs what you customize",
@@ -5965,13 +6016,13 @@ var init_platform_procedures = __esm({
5965
6016
  title: "MCP tools \u2014 memory, decision, and search",
5966
6017
  domain: "tool-use",
5967
6018
  priority: "p1",
5968
- content: `memory(action="recall") / recall_my_memory: search your own memories (semantic + FTS). memory(action="ask_team") / ask_team_memory: search a colleague's memories by agent name. memory(action="store") / store_memory: persist a memory. memory(action="commit") / commit_memory: high-importance memory that survives consolidation. Requires summary. memory(action="search") / search_everything: unified search across memories, tasks, entities, conversations. memory(action="session_context") / get_session_context: temporal memory window. Requires session_id + target_timestamp. memory(action="consolidate") / consolidate_memories: merge duplicate/related memories. memory(action="cardinality") / get_memory_cardinality: count memories per agent. decision(action="store") / store_decision: record an architectural decision (domain, decision, rationale). decision(action="get") / get_decision: retrieve a past decision by domain or query.`
6019
+ content: `memory(action="recall") / recall_my_memory: search memories (semantic + FTS). Params: as_of (bi-temporal \u2014 what did I know at time X?), kind (decision|procedure|observation|raw|conversation|behavior), retrieval_mode (all|decisions_only|procedures_only|operational|recent_high_value). memory(action="ask_team") / ask_team_memory: search a colleague's memories. memory(action="store") / store_memory: persist a memory. Params: kind, procedure_for (domain tag for procedures). memory(action="commit") / commit_memory: high-importance, survives consolidation. Requires summary. memory(action="search") / search_everything: unified search across memories, tasks, entities, conversations. memory(action="session_context") / get_session_context: temporal window. Requires session_id + target_timestamp. memory(action="get_by_id"): fetch one memory by UUID with full untruncated text. memory(action="consolidate") / consolidate_memories: merge duplicate/related memories. memory(action="cardinality") / get_memory_cardinality: count memories per agent. memory(action="supersede"): replace an old memory with a new version (old_id + new text). decision(action="store") / store_decision: record an architectural decision (domain, decision, rationale). decision(action="get") / get_decision: retrieve a past decision by domain or query.`
5969
6020
  },
5970
6021
  {
5971
6022
  title: "MCP tools \u2014 task orchestration",
5972
6023
  domain: "tool-use",
5973
6024
  priority: "p1",
5974
- content: 'task(action="create") / create_task: dispatch work (title, assigned_to, context). The ONLY dispatch path. Auto-spawns session. task(action="list") / list_tasks: query tasks by status, assignee, project. task(action="get") / get_task: fetch full task details by task_id. task(action="update") / update_task: change status (in_progress, done, blocked, cancelled) + result summary. task(action="close") / close_task: finalize a reviewed task (COO only). task(action="checkpoint") / checkpoint_task: save progress state for crash recovery. task(action="resume") / resume_employee: re-spawn an employee session for an existing task.'
6025
+ content: 'task(action="create") / create_task: dispatch work (title, assigned_to, context). The ONLY dispatch path. Auto-spawns session. Params: blocked_by (task ID for dependency), parent_task_id (subtask hierarchy), reviewer, complexity (routine|standard|complex|critical), budget_tokens (max token cap), budget_fallback_model, spawn_runtime (override runtime: claude|codex|opencode), spawn_model (override model). task(action="list") / list_tasks: query tasks by status, assignee, project. task(action="get") / get_task: fetch full task details by task_id. task(action="update") / update_task: change status (in_progress, done, blocked, cancelled) + result summary. task(action="close") / close_task: finalize a reviewed task (COO only). task(action="checkpoint") / checkpoint_task: save progress state for crash recovery. task(action="resume") / resume_employee: re-spawn an employee session for an existing task.'
5975
6026
  },
5976
6027
  {
5977
6028
  title: "MCP tools \u2014 knowledge graph (GraphRAG)",
@@ -6001,7 +6052,7 @@ var init_platform_procedures = __esm({
6001
6052
  title: "MCP tools \u2014 admin, config, and operations",
6002
6053
  domain: "tool-use",
6003
6054
  priority: "p1",
6004
- content: 'config(action="list_employees"): view roster. config(action="agent_spend"): token usage per agent. config(action="daemon_health"): check exed status. config(action="license_status"): check license. config(action="cloud_sync"): force sync. config(action="memory_audit"): health check (dupes, null vectors). config(action="run_consolidation"): trigger memory consolidation. config(action="company_procedure", subaction="store|list|deactivate"): manage company procedures. config(action="global_procedure"): list all procedures (platform + company). config(action="create_trigger|list_triggers"): scheduled agent jobs. config(action="export_orchestration|import_orchestration"): portable org state. diagnostics(action="healthcheck|doctor|status_brief|check_update|cloud_status"): system diagnostics. mcp_ping(): daemon health + license status + tool usage stats.'
6055
+ content: 'config(action="list_employees"): view roster. config(action="set_agent_config"): view or change per-agent runtime + model. Call with no args to show all agents. config(action="agent_spend"): token usage per agent. config(action="daemon_health"): check exed status. config(action="license_status"): check license. config(action="cloud_sync"): force sync. Supports cloud_action param: status|sync|reupload. config(action="memory_audit"): health check (dupes, null vectors). config(action="run_consolidation"): trigger memory consolidation. config(action="worker_gate"): check spawn slot availability \u2014 alive/stale/reserved counts vs max. Use before dispatching. config(action="auto_wake_status"): orphaned tasks, blocked tasks, auto-wake retry status. config(action="orchestration_phase"): view/change org phase (phase_1_coo|phase_2_executives|phase_3_parallel_org). config(action="company_procedure", subaction="store|list|deactivate"): manage company procedures. config(action="global_procedure"): list all procedures (platform + company). config(action="create_trigger|list_triggers"): scheduled agent jobs. config(action="export_orchestration|import_orchestration"): portable org state. diagnostics(action="healthcheck|doctor|status_brief|check_update|cloud_status"): system diagnostics. diagnostics(action="pending_work_summary"): pending reviews + messages + notifications in one call. diagnostics(action="rename_employee"): rename an agent across all systems (roster, identity, DB, symlinks). diagnostics(action="tool_search"): semantic tool discovery \u2014 find relevant MCP tools by natural language query. diagnostics(action="drift"): identity drift detection \u2014 score how far an agent has drifted from its role. mcp_ping(): daemon health + license status + tool usage stats.'
6005
6056
  }
6006
6057
  ];
6007
6058
  PLATFORM_PROCEDURE_TITLES = new Set(
@@ -6417,6 +6468,8 @@ async function writeMemory(record) {
6417
6468
  source_type: record.source_type ?? null,
6418
6469
  tier: record.tier ?? classifyTier(record),
6419
6470
  supersedes_id: record.supersedes_id ?? null,
6471
+ valid_from: record.valid_from ?? record.timestamp,
6472
+ invalid_at: record.invalid_at ?? null,
6420
6473
  draft: record.draft ? 1 : 0,
6421
6474
  memory_type: memoryType,
6422
6475
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
@@ -6435,7 +6488,8 @@ async function writeMemory(record) {
6435
6488
  token_cost: record.token_cost ?? null,
6436
6489
  audience: record.audience ?? null,
6437
6490
  language_type: record.language_type ?? inferLanguageType(record),
6438
- parent_memory_id: record.parent_memory_id ?? null
6491
+ parent_memory_id: record.parent_memory_id ?? null,
6492
+ procedure_for: record.procedure_for ?? null
6439
6493
  };
6440
6494
  _pendingRecords.push(dbRow);
6441
6495
  orgBus.emit({
@@ -6490,6 +6544,8 @@ async function flushBatch() {
6490
6544
  const sourceType = row.source_type ?? null;
6491
6545
  const tier = row.tier ?? 3;
6492
6546
  const supersedesId = row.supersedes_id ?? null;
6547
+ const validFrom = row.valid_from ?? row.timestamp;
6548
+ const invalidAt = row.invalid_at ?? null;
6493
6549
  const draft = row.draft ? 1 : 0;
6494
6550
  const memoryType = row.memory_type ?? "raw";
6495
6551
  const trajectory = row.trajectory ?? null;
@@ -6509,15 +6565,16 @@ async function flushBatch() {
6509
6565
  const audience = row.audience ?? null;
6510
6566
  const languageType = row.language_type ?? null;
6511
6567
  const parentMemoryId = row.parent_memory_id ?? null;
6568
+ const procedureFor = row.procedure_for ?? null;
6512
6569
  const cols = `id, agent_id, agent_role, session_id, timestamp,
6513
6570
  tool_name, project_name,
6514
6571
  has_error, raw_text, vector, version, task_id, importance, status,
6515
6572
  confidence, last_accessed,
6516
6573
  workspace_id, document_id, user_id, char_offset, page_number,
6517
- source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
6574
+ source_path, source_type, tier, supersedes_id, valid_from, invalid_at, draft, memory_type, trajectory, content_hash,
6518
6575
  intent, outcome, domain, referenced_entities, retrieval_count,
6519
6576
  chain_position, review_status, context_window_pct, file_paths, commit_hash,
6520
- duration_ms, token_cost, audience, language_type, parent_memory_id`;
6577
+ duration_ms, token_cost, audience, language_type, parent_memory_id, procedure_for`;
6521
6578
  const metaArgs = [
6522
6579
  intent,
6523
6580
  outcome,
@@ -6533,7 +6590,8 @@ async function flushBatch() {
6533
6590
  tokenCost,
6534
6591
  audience,
6535
6592
  languageType,
6536
- parentMemoryId
6593
+ parentMemoryId,
6594
+ procedureFor
6537
6595
  ];
6538
6596
  const baseArgs = [
6539
6597
  row.id,
@@ -6562,6 +6620,8 @@ async function flushBatch() {
6562
6620
  sourceType,
6563
6621
  tier,
6564
6622
  supersedesId,
6623
+ validFrom,
6624
+ invalidAt,
6565
6625
  draft,
6566
6626
  memoryType,
6567
6627
  trajectory,
@@ -6569,8 +6629,8 @@ async function flushBatch() {
6569
6629
  ];
6570
6630
  return {
6571
6631
  sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
6572
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
6573
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
6632
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
6633
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
6574
6634
  args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
6575
6635
  };
6576
6636
  };
@@ -6686,6 +6746,12 @@ async function searchMemories(queryVector, agentId, options) {
6686
6746
  AND vector IS NOT NULL${statusFilter}${draftFilter}
6687
6747
  AND COALESCE(confidence, 0.7) >= 0.3`;
6688
6748
  const args = [agentId];
6749
+ if (options?.asOf) {
6750
+ sql += ` AND (valid_from IS NULL OR valid_from <= ?) AND (invalid_at IS NULL OR invalid_at > ?)`;
6751
+ args.push(options.asOf, options.asOf);
6752
+ } else {
6753
+ sql += ` AND invalid_at IS NULL`;
6754
+ }
6689
6755
  const scope = buildWikiScopeFilter(options, "");
6690
6756
  sql += scope.clause;
6691
6757
  args.push(...scope.args);
@@ -8157,6 +8223,19 @@ __export(hybrid_search_exports, {
8157
8223
  rrfMerge: () => rrfMerge,
8158
8224
  rrfMergeMulti: () => rrfMergeMulti
8159
8225
  });
8226
+ function buildTemporalFilter(options, columnPrefix) {
8227
+ const asOf = options?.asOf;
8228
+ if (asOf) {
8229
+ return {
8230
+ clause: ` AND (${columnPrefix}valid_from IS NULL OR ${columnPrefix}valid_from <= ?) AND (${columnPrefix}invalid_at IS NULL OR ${columnPrefix}invalid_at > ?)`,
8231
+ args: [asOf, asOf]
8232
+ };
8233
+ }
8234
+ return {
8235
+ clause: ` AND ${columnPrefix}invalid_at IS NULL`,
8236
+ args: []
8237
+ };
8238
+ }
8160
8239
  function appendMemoryTypeFilter(sql, args, column, options) {
8161
8240
  if (options?.memoryTypes && options.memoryTypes.length > 0) {
8162
8241
  const uniqueTypes = [...new Set(options.memoryTypes)];
@@ -8389,6 +8468,9 @@ async function estimateCardinality(agentId, options) {
8389
8468
  AND COALESCE(status, 'active') = 'active'
8390
8469
  AND COALESCE(confidence, 0.7) >= 0.3`;
8391
8470
  const args = [agentId];
8471
+ const temporal = buildTemporalFilter(options, "");
8472
+ sql += temporal.clause;
8473
+ args.push(...temporal.args);
8392
8474
  const rawVisibility = buildRawVisibilityFilter(options, "");
8393
8475
  sql += rawVisibility.clause;
8394
8476
  args.push(...rawVisibility.args);
@@ -8517,6 +8599,9 @@ async function ftsQuery(client, matchExpr, agentId, options, limit) {
8517
8599
  AND m.agent_id = ?${statusFilter}${draftFilter}
8518
8600
  AND COALESCE(m.confidence, 0.7) >= 0.3`;
8519
8601
  const args = [matchExpr, agentId];
8602
+ const temporal = buildTemporalFilter(options, "m.");
8603
+ sql += temporal.clause;
8604
+ args.push(...temporal.args);
8520
8605
  const scope = buildWikiScopeFilter(options, "m.");
8521
8606
  sql += scope.clause;
8522
8607
  args.push(...scope.args);
@@ -8629,6 +8714,9 @@ async function recentRecords(agentId, options, limit, textFilter) {
8629
8714
  WHERE agent_id = ?${statusFilter}${draftFilter}
8630
8715
  AND COALESCE(confidence, 0.7) >= 0.3`;
8631
8716
  const args = [agentId];
8717
+ const temporal = buildTemporalFilter(options, "");
8718
+ sql += temporal.clause;
8719
+ args.push(...temporal.args);
8632
8720
  const scope = buildWikiScopeFilter(options, "");
8633
8721
  sql += scope.clause;
8634
8722
  args.push(...scope.args);
@@ -8729,6 +8817,9 @@ async function trajectoryBypass(queryText, agentId, options, limit) {
8729
8817
  AND json_extract(trajectory, '$.tool') = ?
8730
8818
  AND agent_id = ?${statusFilter}${draftFilter}`;
8731
8819
  const args = [toolName, agentId];
8820
+ const temporal = buildTemporalFilter(options, "");
8821
+ sql += temporal.clause;
8822
+ args.push(...temporal.args);
8732
8823
  const rawVisibility = buildRawVisibilityFilter(options, "");
8733
8824
  sql += rawVisibility.clause;
8734
8825
  args.push(...rawVisibility.args);
@@ -9110,7 +9201,11 @@ function registerRecallMyMemory(server) {
9110
9201
  include_source: z.boolean().optional().default(false).describe(
9111
9202
  "When true, attach parent document metadata (filename, mime, source_type) to each result. Default false."
9112
9203
  ),
9113
- retrieval_mode: z.enum(RETRIEVAL_MODES).optional().default("all").describe(`Typed retrieval mode. ${formatRetrievalModes()}`)
9204
+ retrieval_mode: z.enum(RETRIEVAL_MODES).optional().default("all").describe(`Typed retrieval mode. ${formatRetrievalModes()}`),
9205
+ kind: z.string().optional().describe(
9206
+ "Filter by memory_type: decision, procedure, observation, raw, conversation, behavior."
9207
+ ),
9208
+ as_of: z.string().optional().describe("ISO 8601 timestamp for time-travel query. Returns facts valid at that point in time.")
9114
9209
  }
9115
9210
  },
9116
9211
  async ({
@@ -9127,7 +9222,9 @@ function registerRecallMyMemory(server) {
9127
9222
  workspace_id,
9128
9223
  user_id,
9129
9224
  include_source,
9130
- retrieval_mode
9225
+ retrieval_mode,
9226
+ kind,
9227
+ as_of
9131
9228
  }) => {
9132
9229
  try {
9133
9230
  if (!recent && !query) {
@@ -9147,7 +9244,9 @@ function registerRecallMyMemory(server) {
9147
9244
  workspaceId: workspace_id,
9148
9245
  includeSource: include_source,
9149
9246
  includeDrafts: true,
9150
- ...user_id !== void 0 ? { userId: user_id } : {}
9247
+ ...user_id !== void 0 ? { userId: user_id } : {},
9248
+ ...kind ? { memoryType: kind } : {},
9249
+ ...as_of ? { asOf: as_of } : {}
9151
9250
  }, retrieval_mode);
9152
9251
  let results;
9153
9252
  if (recent) {
@@ -9612,7 +9711,7 @@ var init_license = __esm({
9612
9711
  LICENSE_PATH = path17.join(EXE_AI_DIR, "license.key");
9613
9712
  CACHE_PATH = path17.join(EXE_AI_DIR, "license-cache.json");
9614
9713
  DEVICE_ID_PATH = path17.join(EXE_AI_DIR, "device-id");
9615
- API_BASE = "https://askexe.com/cloud";
9714
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
9616
9715
  RETRY_DELAY_MS = 500;
9617
9716
  LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
9618
9717
  MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
@@ -9784,10 +9883,12 @@ function registerStoreMemory(server) {
9784
9883
  project_name: z3.string().optional().describe("Project name"),
9785
9884
  has_error: z3.boolean().optional().default(false).describe("Whether this memory is error-related"),
9786
9885
  source_path: z3.string().optional().describe("Original file path, URL, or document reference"),
9787
- source_type: z3.string().optional().describe("Content type: text, pdf, image, audio, video, url, document")
9886
+ source_type: z3.string().optional().describe("Content type: text, pdf, image, audio, video, url, document"),
9887
+ kind: z3.string().optional().describe("Memory type: decision, procedure, observation, raw, conversation, behavior"),
9888
+ procedure_for: z3.string().optional().describe("Domain this procedure applies to (only for kind='procedure')")
9788
9889
  }
9789
9890
  },
9790
- async ({ text: text3, query, tool_name, project_name, has_error, source_path, source_type }) => {
9891
+ async ({ text: text3, query, tool_name, project_name, has_error, source_path, source_type, kind, procedure_for }) => {
9791
9892
  const resolvedText = text3 ?? query;
9792
9893
  if (!resolvedText) {
9793
9894
  return {
@@ -9832,7 +9933,9 @@ function registerStoreMemory(server) {
9832
9933
  raw_text: resolvedText,
9833
9934
  vector,
9834
9935
  source_path: source_path ?? null,
9835
- source_type: source_type ?? null
9936
+ source_type: source_type ?? null,
9937
+ memory_type: kind ?? void 0,
9938
+ procedure_for: kind === "procedure" ? procedure_for ?? null : null
9836
9939
  });
9837
9940
  await flushBatch();
9838
9941
  if (needsBackfill) {
@@ -10133,9 +10236,9 @@ function truncate(text3, max) {
10133
10236
  const marker = ` \u2026 [truncated; +${text3.length - max} chars]`;
10134
10237
  return text3.slice(0, Math.max(0, max - marker.length)) + marker;
10135
10238
  }
10136
- async function searchMemories2(query, agentId, limit) {
10239
+ async function searchMemories2(query, agentId, limit, memoryType) {
10137
10240
  const { hybridSearch: hybridSearch2 } = await Promise.resolve().then(() => (init_hybrid_search(), hybrid_search_exports));
10138
- const results = await hybridSearch2(query, agentId, { limit });
10241
+ const results = await hybridSearch2(query, agentId, { limit, memoryType });
10139
10242
  return results.map((r, i) => ({
10140
10243
  source: "memory",
10141
10244
  score: 1 - i / Math.max(results.length, 1),
@@ -10259,7 +10362,7 @@ async function unifiedSearch(query, agentId, opts) {
10259
10362
  const perSourceLimit = Math.max(Math.ceil(limit * 1.5), 5);
10260
10363
  const promises = [];
10261
10364
  if (sources.includes("memory")) {
10262
- promises.push(searchMemories2(query, agentId, perSourceLimit));
10365
+ promises.push(searchMemories2(query, agentId, perSourceLimit, opts?.memoryType));
10263
10366
  }
10264
10367
  if (sources.includes("conversations")) {
10265
10368
  promises.push(searchConversations(query, perSourceLimit));
@@ -10303,7 +10406,8 @@ function registerSearchEverything(server) {
10303
10406
  sources: z5.array(z5.enum(["memory", "conversations", "wiki"])).optional().describe(
10304
10407
  "Which sources to search (default: all). Options: memory, conversations, wiki"
10305
10408
  ),
10306
- limit: z5.coerce.number().int().min(1).max(50).optional().default(10).describe("Max results to return (default 10, max 50)")
10409
+ limit: z5.coerce.number().int().min(1).max(50).optional().default(10).describe("Max results to return (default 10, max 50)"),
10410
+ kind: z5.string().optional().describe("Filter memories by memory_type: decision, procedure, observation, raw, conversation, behavior.")
10307
10411
  }
10308
10412
  },
10309
10413
  async (params) => {
@@ -10312,7 +10416,8 @@ function registerSearchEverything(server) {
10312
10416
  const sources = params.sources;
10313
10417
  const results = await unifiedSearch(params.query, agentId, {
10314
10418
  limit: params.limit,
10315
- sources
10419
+ sources,
10420
+ memoryType: params.kind
10316
10421
  });
10317
10422
  if (results.length === 0) {
10318
10423
  return {
@@ -10558,6 +10663,7 @@ async function selectUnconsolidated(client, limit = 200) {
10558
10663
  sql: `SELECT id, agent_id, project_name, tool_name, raw_text, timestamp
10559
10664
  FROM memories
10560
10665
  WHERE consolidated = 0
10666
+ AND COALESCE(memory_type, 'raw') != 'procedure'
10561
10667
  ORDER BY timestamp DESC
10562
10668
  LIMIT ?`,
10563
10669
  args: [limit]
@@ -10901,7 +11007,8 @@ async function isUserIdle(client, idleMinutes = 30) {
10901
11007
  async function countUnconsolidated(client) {
10902
11008
  const result3 = await client.execute({
10903
11009
  sql: `SELECT COUNT(*) as cnt FROM memories
10904
- WHERE consolidated = 0`,
11010
+ WHERE consolidated = 0
11011
+ AND COALESCE(memory_type, 'raw') != 'procedure'`,
10905
11012
  args: []
10906
11013
  });
10907
11014
  return Number(result3.rows[0]?.cnt ?? 0);
@@ -11138,15 +11245,74 @@ function buildLegacyMemoryHandlers() {
11138
11245
  registerGetMemoryCardinality(localServer);
11139
11246
  return tools;
11140
11247
  }
11248
+ async function handleSupersede(input) {
11249
+ const oldId = input.old_id;
11250
+ const text3 = input.text ?? input.query;
11251
+ if (!oldId || !text3) {
11252
+ return errorResult('memory action "supersede" requires old_id and text');
11253
+ }
11254
+ try {
11255
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
11256
+ const { getActiveAgent: getActiveAgent2 } = await Promise.resolve().then(() => (init_active_agent(), active_agent_exports));
11257
+ const { writeMemory: writeMemory2, flushBatch: flushBatch2 } = await Promise.resolve().then(() => (init_store(), store_exports));
11258
+ const { randomUUID: randomUUID12 } = await import("crypto");
11259
+ const client = getClient2();
11260
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
11261
+ const oldRow = await client.execute({
11262
+ sql: `SELECT id, agent_id, agent_role, project_name FROM memories WHERE id = ?`,
11263
+ args: [oldId]
11264
+ });
11265
+ if (oldRow.rows.length === 0) {
11266
+ return errorResult(`Memory not found: ${oldId}`);
11267
+ }
11268
+ await client.execute({
11269
+ sql: `UPDATE memories SET invalid_at = ? WHERE id = ?`,
11270
+ args: [now2, oldId]
11271
+ });
11272
+ const { agentId, agentRole } = getActiveAgent2();
11273
+ const sessionId = process.env.SESSION_ID ?? "manual";
11274
+ const { getProjectName: getProjectName2 } = await Promise.resolve().then(() => (init_project_name(), project_name_exports));
11275
+ const projectName = input.project_name ?? getProjectName2(process.cwd());
11276
+ const newId = randomUUID12();
11277
+ await writeMemory2({
11278
+ id: newId,
11279
+ agent_id: agentId,
11280
+ agent_role: agentRole,
11281
+ session_id: sessionId,
11282
+ timestamp: now2,
11283
+ tool_name: "manual",
11284
+ project_name: projectName,
11285
+ has_error: false,
11286
+ raw_text: text3,
11287
+ vector: null,
11288
+ valid_from: now2,
11289
+ supersedes_id: oldId,
11290
+ importance: 7,
11291
+ memory_type: "adr"
11292
+ });
11293
+ await flushBatch2();
11294
+ return {
11295
+ content: [{
11296
+ type: "text",
11297
+ text: `Superseded memory ${oldId.slice(0, 8)}\u2026 \u2192 new memory ${newId.slice(0, 8)}\u2026
11298
+ Old memory invalid_at set to ${now2}.
11299
+ New memory id: ${newId}`
11300
+ }]
11301
+ };
11302
+ } catch (err) {
11303
+ const msg = err instanceof Error ? err.message : String(err);
11304
+ return errorResult(`Supersede failed: ${msg}`);
11305
+ }
11306
+ }
11141
11307
  function registerMemory(server) {
11142
11308
  const legacyTools = buildLegacyMemoryHandlers();
11143
11309
  server.registerTool(
11144
11310
  "memory",
11145
11311
  {
11146
11312
  title: "Memory",
11147
- description: "Consolidated memory domain tool. Actions: recall, ask_team, store, commit, search, session_context, get_by_id, consolidate, cardinality. Legacy memory tools remain available during migration.",
11313
+ description: "Consolidated memory domain tool. Actions: recall, ask_team, store, commit, search, session_context, get_by_id, consolidate, cardinality, supersede. Legacy memory tools remain available during migration.",
11148
11314
  inputSchema: {
11149
- action: z10.enum(["recall", "ask_team", "store", "commit", "search", "session_context", "consolidate", "cardinality"]).describe("Memory operation to perform"),
11315
+ action: z10.enum(["recall", "ask_team", "store", "commit", "search", "session_context", "consolidate", "cardinality", "supersede"]).describe("Memory operation to perform"),
11150
11316
  query: z10.string().optional().describe("Search query. Also accepted as store text alias."),
11151
11317
  text: z10.string().optional().describe("Memory text for action=store"),
11152
11318
  summary: z10.string().optional().describe("High-importance summary for action=commit"),
@@ -11179,7 +11345,11 @@ function registerMemory(server) {
11179
11345
  window_size: z10.number().optional().describe("Window size for action=session_context"),
11180
11346
  max_clusters: z10.coerce.number().optional().describe("Max clusters for action=consolidate"),
11181
11347
  model: z10.string().optional().describe("Model for action=consolidate"),
11182
- agent_id: z10.string().optional().describe("Agent filter for action=cardinality")
11348
+ agent_id: z10.string().optional().describe("Agent filter for action=cardinality"),
11349
+ kind: z10.string().optional().describe("Filter by memory_type: decision, procedure, observation, raw, conversation, behavior. For action=recall and action=search."),
11350
+ procedure_for: z10.string().optional().describe("Domain this procedure applies to (only for storing procedure-type memories)."),
11351
+ as_of: z10.string().optional().describe("ISO 8601 timestamp for time-travel query. Returns facts valid at that point in time. For action=recall."),
11352
+ old_id: z10.string().optional().describe("UUID of memory to supersede. For action=supersede.")
11183
11353
  }
11184
11354
  },
11185
11355
  async (input, extra) => {
@@ -11188,6 +11358,9 @@ function registerMemory(server) {
11188
11358
  if (!legacyToolName) {
11189
11359
  return errorResult(`Unknown memory action: ${String(input.action)}`);
11190
11360
  }
11361
+ if (action === "supersede") {
11362
+ return handleSupersede(input);
11363
+ }
11191
11364
  const { action: _action, ...legacyArgs } = input;
11192
11365
  const missing = missingRequiredFields(legacyArgs, REQUIRED_FIELDS[action]);
11193
11366
  if (missing.length > 0) {
@@ -11256,7 +11429,8 @@ var init_memory2 = __esm({
11256
11429
  session_context: "get_session_context",
11257
11430
  get_by_id: "get_memory_by_id",
11258
11431
  consolidate: "consolidate_memories",
11259
- cardinality: "get_memory_cardinality"
11432
+ cardinality: "get_memory_cardinality",
11433
+ supersede: "__inline_supersede__"
11260
11434
  };
11261
11435
  REQUIRED_FIELDS = {
11262
11436
  recall: [],
@@ -11267,7 +11441,8 @@ var init_memory2 = __esm({
11267
11441
  session_context: ["session_id", "target_timestamp"],
11268
11442
  get_by_id: ["id"],
11269
11443
  consolidate: [],
11270
- cardinality: []
11444
+ cardinality: [],
11445
+ supersede: ["old_id", "text"]
11271
11446
  };
11272
11447
  }
11273
11448
  });
@@ -12717,9 +12892,13 @@ function getDispatchedBy(sessionKey) {
12717
12892
  }
12718
12893
  }
12719
12894
  function resolveExeSession() {
12895
+ if (process.env.EXE_SESSION_NAME) {
12896
+ return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
12897
+ }
12720
12898
  const mySession = getMySession();
12721
12899
  if (!mySession) return null;
12722
12900
  const fromSessionName = extractRootExe(mySession);
12901
+ let candidate = null;
12723
12902
  try {
12724
12903
  const key = getSessionKey();
12725
12904
  const parentExe = getParentExe(key);
@@ -12730,13 +12909,47 @@ function resolveExeSession() {
12730
12909
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
12731
12910
  `
12732
12911
  );
12733
- return fromSessionName;
12912
+ candidate = fromSessionName;
12913
+ } else {
12914
+ candidate = fromCache;
12734
12915
  }
12735
- return fromCache;
12736
12916
  }
12737
12917
  } catch {
12738
12918
  }
12739
- return fromSessionName ?? mySession;
12919
+ if (!candidate) {
12920
+ candidate = fromSessionName ?? mySession;
12921
+ }
12922
+ if (candidate && isRootSession(candidate)) {
12923
+ try {
12924
+ const transport = getTransport();
12925
+ const liveSessions = transport.listSessions();
12926
+ if (!liveSessions.includes(candidate)) {
12927
+ const liveRoots = liveSessions.filter((s) => isRootSession(s));
12928
+ if (liveRoots.length === 1) {
12929
+ process.stderr.write(
12930
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. Using live coordinator "${liveRoots[0]}".
12931
+ `
12932
+ );
12933
+ return liveRoots[0];
12934
+ } else if (liveRoots.length > 1) {
12935
+ const base = candidate.replace(/\d+$/, "");
12936
+ const match = liveRoots.find((s) => s.startsWith(base));
12937
+ const chosen = match ?? liveRoots[0];
12938
+ process.stderr.write(
12939
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. ${liveRoots.length} live roots found, using "${chosen}".
12940
+ `
12941
+ );
12942
+ return chosen;
12943
+ }
12944
+ process.stderr.write(
12945
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead and no live coordinator found.
12946
+ `
12947
+ );
12948
+ }
12949
+ } catch {
12950
+ }
12951
+ }
12952
+ return candidate;
12740
12953
  }
12741
12954
  function isEmployeeAlive(sessionName) {
12742
12955
  return getTransport().isAlive(sessionName);
@@ -13138,7 +13351,12 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
13138
13351
  }
13139
13352
  const spawnCwd = opts?.cwd ?? projectDir;
13140
13353
  const useExeAgent = !!(opts?.model && opts?.provider);
13141
- const agentRtConfig = getAgentRuntime(employeeName);
13354
+ const baseRtConfig = getAgentRuntime(employeeName);
13355
+ const agentRtConfig = {
13356
+ ...baseRtConfig,
13357
+ ...opts?.runtimeOverride ? { runtime: opts.runtimeOverride } : {},
13358
+ ...opts?.modelOverride ? { model: opts.modelOverride } : {}
13359
+ };
13142
13360
  const useCodex = !useExeAgent && agentRtConfig.runtime === "codex";
13143
13361
  const useOpencode = !useExeAgent && !useCodex && agentRtConfig.runtime === "opencode";
13144
13362
  const ccProvider = useExeAgent || useCodex || useOpencode ? DEFAULT_PROVIDER : detectActiveProvider();
@@ -13756,8 +13974,8 @@ ${scopeMismatchWarning}` : scopeMismatchWarning;
13756
13974
  const complexity = input.complexity ?? "standard";
13757
13975
  const sessionScope = earlySessionScope;
13758
13976
  await client.execute({
13759
- sql: `INSERT INTO tasks (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, blocked_by, parent_task_id, reviewer, context, complexity, budget_tokens, budget_fallback_model, tokens_used, tokens_warned_at, session_scope, created_at, updated_at)
13760
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
13977
+ sql: `INSERT INTO tasks (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, blocked_by, parent_task_id, reviewer, context, complexity, budget_tokens, budget_fallback_model, tokens_used, tokens_warned_at, session_scope, spawn_runtime, spawn_model, created_at, updated_at)
13978
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
13761
13979
  args: [
13762
13980
  id,
13763
13981
  input.title,
@@ -13777,6 +13995,8 @@ ${scopeMismatchWarning}` : scopeMismatchWarning;
13777
13995
  0,
13778
13996
  null,
13779
13997
  sessionScope,
13998
+ input.spawnRuntime ?? null,
13999
+ input.spawnModel ?? null,
13780
14000
  now2,
13781
14001
  now2
13782
14002
  ]
@@ -13833,7 +14053,9 @@ ${input.context}
13833
14053
  budgetTokens: input.budgetTokens ?? null,
13834
14054
  budgetFallbackModel: input.budgetFallbackModel ?? null,
13835
14055
  tokensUsed: 0,
13836
- tokensWarnedAt: null
14056
+ tokensWarnedAt: null,
14057
+ spawnRuntime: input.spawnRuntime ?? null,
14058
+ spawnModel: input.spawnModel ?? null
13837
14059
  };
13838
14060
  }
13839
14061
  async function listTasks(input) {
@@ -13883,7 +14105,9 @@ async function listTasks(input) {
13883
14105
  budgetTokens: r.budget_tokens !== null ? Number(r.budget_tokens) : null,
13884
14106
  budgetFallbackModel: r.budget_fallback_model !== null ? String(r.budget_fallback_model) : null,
13885
14107
  tokensUsed: Number(r.tokens_used ?? 0),
13886
- tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
14108
+ tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null,
14109
+ spawnRuntime: r.spawn_runtime !== null && r.spawn_runtime !== void 0 ? String(r.spawn_runtime) : null,
14110
+ spawnModel: r.spawn_model !== null && r.spawn_model !== void 0 ? String(r.spawn_model) : null
13887
14111
  }));
13888
14112
  }
13889
14113
  function isTmuxSessionAlive(identifier) {
@@ -14833,6 +15057,8 @@ async function updateTask(input) {
14833
15057
  budgetFallbackModel: row.budget_fallback_model !== void 0 && row.budget_fallback_model !== null ? String(row.budget_fallback_model) : null,
14834
15058
  tokensUsed: Number(row.tokens_used ?? 0),
14835
15059
  tokensWarnedAt: row.tokens_warned_at !== void 0 && row.tokens_warned_at !== null ? Number(row.tokens_warned_at) : null,
15060
+ spawnRuntime: row.spawn_runtime !== void 0 && row.spawn_runtime !== null ? String(row.spawn_runtime) : null,
15061
+ spawnModel: row.spawn_model !== void 0 && row.spawn_model !== null ? String(row.spawn_model) : null,
14836
15062
  nextTask
14837
15063
  };
14838
15064
  }
@@ -15572,10 +15798,12 @@ function registerCreateTask(server) {
15572
15798
  parent_task_id: z12.string().optional().describe("Parent task ID or slug. Links this task as a subtask."),
15573
15799
  reviewer: z12.string().optional().describe("Who should review this task when done. Defaults to assigner."),
15574
15800
  budget_tokens: z12.number().optional().describe("Max tokens allowed for this task (null = unlimited)"),
15575
- budget_fallback_model: z12.string().optional().describe("Model to route to when budget is exhausted")
15801
+ budget_fallback_model: z12.string().optional().describe("Model to route to when budget is exhausted"),
15802
+ spawn_runtime: z12.string().optional().describe("Override runtime for spawned session (e.g., 'claude', 'codex', 'opencode')"),
15803
+ spawn_model: z12.string().optional().describe("Override model for spawned session (e.g., 'claude-sonnet-4.6', 'claude-haiku-4.5')")
15576
15804
  }
15577
15805
  },
15578
- async ({ title, assigned_to, project_name, priority, complexity, context, blocked_by, parent_task_id, reviewer, budget_tokens, budget_fallback_model }) => {
15806
+ async ({ title, assigned_to, project_name, priority, complexity, context, blocked_by, parent_task_id, reviewer, budget_tokens, budget_fallback_model, spawn_runtime, spawn_model }) => {
15579
15807
  if (!isCoordinatorName(assigned_to)) {
15580
15808
  const license = getLicenseSync();
15581
15809
  if (license.plan === "free") {
@@ -15603,6 +15831,8 @@ function registerCreateTask(server) {
15603
15831
  reviewer,
15604
15832
  budgetTokens: budget_tokens,
15605
15833
  budgetFallbackModel: budget_fallback_model,
15834
+ spawnRuntime: spawn_runtime,
15835
+ spawnModel: spawn_model,
15606
15836
  // Skip internal dispatch — we handle it below with autoInstance
15607
15837
  // support. Without this, createTask fires dispatchTaskToEmployee
15608
15838
  // (no autoInstance) in parallel, racing with our ensureEmployee
@@ -15640,7 +15870,9 @@ function registerCreateTask(server) {
15640
15870
  const cfg = loadConfigSync2();
15641
15871
  const result3 = ensureEmployee(assigned_to, exeSession, process.cwd(), {
15642
15872
  autoInstance: useAutoInstance,
15643
- maxAutoInstances: useAutoInstance ? cfg.sessionLifecycle.maxAutoInstances : void 0
15873
+ maxAutoInstances: useAutoInstance ? cfg.sessionLifecycle.maxAutoInstances : void 0,
15874
+ runtimeOverride: spawn_runtime ?? void 0,
15875
+ modelOverride: spawn_model ?? void 0
15644
15876
  });
15645
15877
  switch (result3.status) {
15646
15878
  case "intercom_sent":
@@ -15881,6 +16113,14 @@ function registerGetTask(server) {
15881
16113
  }
15882
16114
  }
15883
16115
  }
16116
+ const spawnRuntime = row.spawn_runtime ? String(row.spawn_runtime) : null;
16117
+ const spawnModel = row.spawn_model ? String(row.spawn_model) : null;
16118
+ if (spawnRuntime || spawnModel) {
16119
+ lines.push("");
16120
+ lines.push("**Spawn Override:**");
16121
+ if (spawnRuntime) lines.push(`Runtime: ${spawnRuntime}`);
16122
+ if (spawnModel) lines.push(`Model: ${spawnModel}`);
16123
+ }
15884
16124
  if (row.context) {
15885
16125
  lines.push("", "## Context", "", contextText);
15886
16126
  }
@@ -16343,6 +16583,8 @@ function registerTask(server) {
16343
16583
  reviewer: z19.string().optional().describe("Reviewer for action=create"),
16344
16584
  budget_tokens: z19.number().optional().describe("Max token budget for action=create"),
16345
16585
  budget_fallback_model: z19.string().optional().describe("Fallback model for action=create"),
16586
+ spawn_runtime: z19.string().optional().describe("Override runtime for spawned session (e.g., 'claude', 'codex', 'opencode') for action=create"),
16587
+ spawn_model: z19.string().optional().describe("Override model for spawned session (e.g., 'claude-sonnet-4.6') for action=create"),
16346
16588
  status: z19.enum(["open", "in_progress", "done", "needs_review", "blocked", "cancelled", "closed"]).optional().describe("Status for action=update/list/close"),
16347
16589
  result: z19.string().optional().describe("Result summary for action=update or action=close"),
16348
16590
  step: z19.string().optional().describe("Checkpoint step for action=checkpoint"),
@@ -30442,6 +30684,15 @@ function registerCreateBugReport(server) {
30442
30684
  project_name,
30443
30685
  send_upstream
30444
30686
  }) => {
30687
+ if (!summary || summary.trim().length === 0) {
30688
+ return {
30689
+ content: [{
30690
+ type: "text",
30691
+ text: "Error: summary is required but was empty. If calling via support(action='create_bug'), use 'description' which maps to 'summary'."
30692
+ }],
30693
+ isError: true
30694
+ };
30695
+ }
30445
30696
  const { agentId, agentRole } = getActiveAgent();
30446
30697
  const id = crypto19.randomUUID();
30447
30698
  const version = package_version ?? "unknown";
@@ -33024,6 +33275,25 @@ function registerSupportConsolidated(server) {
33024
33275
  const handler = handlers.get(toolName);
33025
33276
  if (!handler) return { content: [{ type: "text", text: `Handler not found: ${toolName}` }], isError: true };
33026
33277
  const { action: _, ...args } = input;
33278
+ if (action === "create_bug") {
33279
+ if (args.description && !args.summary) {
33280
+ args.summary = args.description;
33281
+ delete args.description;
33282
+ }
33283
+ if (args.steps_to_reproduce && !args.reproduction_steps) {
33284
+ const raw = args.steps_to_reproduce;
33285
+ args.reproduction_steps = raw.split(/\n/).map((s) => s.replace(/^\d+\.\s*/, "").trim()).filter(Boolean);
33286
+ delete args.steps_to_reproduce;
33287
+ }
33288
+ if (args.expected_behavior && !args.expected) {
33289
+ args.expected = args.expected_behavior;
33290
+ delete args.expected_behavior;
33291
+ }
33292
+ if (args.actual_behavior && !args.actual) {
33293
+ args.actual = args.actual_behavior;
33294
+ delete args.actual_behavior;
33295
+ }
33296
+ }
33027
33297
  if ((action === "triage_bug" || action === "triage_feature") && args.notes && !args.triage_notes) {
33028
33298
  args.triage_notes = args.notes;
33029
33299
  delete args.notes;
@@ -33060,6 +33330,513 @@ var init_support_consolidated = __esm({
33060
33330
  }
33061
33331
  });
33062
33332
 
33333
+ // src/lib/tool-capability-index.ts
33334
+ var tool_capability_index_exports = {};
33335
+ __export(tool_capability_index_exports, {
33336
+ ToolCapabilityIndex: () => ToolCapabilityIndex,
33337
+ getToolCapabilityIndex: () => getToolCapabilityIndex
33338
+ });
33339
+ function categoryForToolName(toolName, categoryMap) {
33340
+ const normalized = toolName.replace(/[_-]/g, "").toLowerCase();
33341
+ for (const [registerFn, cat] of Object.entries(categoryMap)) {
33342
+ const fnNormalized = registerFn.replace(/^register/, "").toLowerCase();
33343
+ if (fnNormalized === normalized) return cat;
33344
+ }
33345
+ return "unknown";
33346
+ }
33347
+ function extractParamNames(inputSchema) {
33348
+ if (!inputSchema || typeof inputSchema !== "object") return "";
33349
+ return Object.keys(inputSchema).join(", ");
33350
+ }
33351
+ function cosineSimilarity4(a, b) {
33352
+ if (a.length !== b.length || a.length === 0) return 0;
33353
+ let dot = 0, normA = 0, normB = 0;
33354
+ for (let i = 0; i < a.length; i++) {
33355
+ dot += a[i] * b[i];
33356
+ normA += a[i] * a[i];
33357
+ normB += b[i] * b[i];
33358
+ }
33359
+ const denom = Math.sqrt(normA) * Math.sqrt(normB);
33360
+ return denom === 0 ? 0 : dot / denom;
33361
+ }
33362
+ function keywordScore(query, tool) {
33363
+ const words = query.toLowerCase().split(/\s+/).filter((w) => w.length > 2);
33364
+ if (words.length === 0) return 0;
33365
+ const searchText = `${tool.name} ${tool.description} ${tool.category} ${tool.paramSummary}`.toLowerCase();
33366
+ let matches = 0;
33367
+ for (const word of words) {
33368
+ if (searchText.includes(word)) matches++;
33369
+ }
33370
+ return matches / words.length;
33371
+ }
33372
+ function getToolCapabilityIndex() {
33373
+ if (!_instance) {
33374
+ _instance = new ToolCapabilityIndex();
33375
+ }
33376
+ return _instance;
33377
+ }
33378
+ var ALWAYS_ON, ToolCapabilityIndex, _instance;
33379
+ var init_tool_capability_index = __esm({
33380
+ "src/lib/tool-capability-index.ts"() {
33381
+ "use strict";
33382
+ init_memory();
33383
+ ALWAYS_ON = /* @__PURE__ */ new Set([
33384
+ "mcp_ping",
33385
+ "memory",
33386
+ "task",
33387
+ "message",
33388
+ "identity",
33389
+ "diagnostics",
33390
+ "session"
33391
+ ]);
33392
+ ToolCapabilityIndex = class {
33393
+ capabilities = [];
33394
+ initialized = false;
33395
+ /**
33396
+ * Build the index by intercepting registerAllTools via a fake McpServer.
33397
+ *
33398
+ * @param registerFn - function that registers tools on a server (e.g. registerAllTools)
33399
+ * @param categoryMap - optional map of registerFnName → category (from tool-gates TOOL_CATEGORIES)
33400
+ */
33401
+ async buildIndex(registerFn, categoryMap) {
33402
+ const captured = [];
33403
+ const fakeServer = {
33404
+ registerTool(name, config2, _handler) {
33405
+ captured.push({
33406
+ name,
33407
+ description: config2.description ?? "",
33408
+ inputSchema: config2.inputSchema ?? {}
33409
+ });
33410
+ }
33411
+ };
33412
+ registerFn(fakeServer);
33413
+ let embedFn = null;
33414
+ try {
33415
+ const { embed: embed2 } = await Promise.resolve().then(() => (init_embedder(), embedder_exports));
33416
+ const test = await embed2("test");
33417
+ if (test.length === EMBEDDING_DIM) {
33418
+ embedFn = embed2;
33419
+ }
33420
+ } catch {
33421
+ }
33422
+ const capabilities = [];
33423
+ for (const tool of captured) {
33424
+ const paramSummary = extractParamNames(tool.inputSchema);
33425
+ const category = categoryMap ? categoryForToolName(tool.name, categoryMap) : "unknown";
33426
+ const capabilityDoc = `${tool.name}: ${tool.description}. Parameters: ${paramSummary}`;
33427
+ let vector = null;
33428
+ if (embedFn) {
33429
+ try {
33430
+ vector = await embedFn(capabilityDoc.slice(0, 500));
33431
+ } catch {
33432
+ }
33433
+ }
33434
+ capabilities.push({
33435
+ name: tool.name,
33436
+ description: tool.description,
33437
+ category,
33438
+ vector,
33439
+ paramSummary
33440
+ });
33441
+ }
33442
+ this.capabilities = capabilities;
33443
+ this.initialized = true;
33444
+ process.stderr.write(
33445
+ `[tool-capability-index] Indexed ${capabilities.length} tools (${capabilities.filter((c) => c.vector).length} with vectors)
33446
+ `
33447
+ );
33448
+ }
33449
+ /**
33450
+ * Semantic search for tools matching a natural-language query.
33451
+ * Returns tools sorted by relevance, with always-on tools boosted.
33452
+ */
33453
+ async search(query, limit = 20) {
33454
+ if (!this.initialized || this.capabilities.length === 0) return [];
33455
+ let queryVector = null;
33456
+ try {
33457
+ const { embed: embed2 } = await Promise.resolve().then(() => (init_embedder(), embedder_exports));
33458
+ queryVector = await embed2(query);
33459
+ } catch {
33460
+ }
33461
+ const scored = [];
33462
+ for (const tool of this.capabilities) {
33463
+ let relevance;
33464
+ if (queryVector && tool.vector) {
33465
+ const vecScore = cosineSimilarity4(queryVector, tool.vector);
33466
+ const kwScore = keywordScore(query, tool);
33467
+ relevance = vecScore * 0.8 + kwScore * 0.2;
33468
+ } else {
33469
+ relevance = keywordScore(query, tool);
33470
+ }
33471
+ if (ALWAYS_ON.has(tool.name)) {
33472
+ relevance = Math.max(relevance, 0.5);
33473
+ }
33474
+ scored.push({ ...tool, relevance });
33475
+ }
33476
+ scored.sort((a, b) => b.relevance - a.relevance);
33477
+ return scored.slice(0, limit);
33478
+ }
33479
+ /** Whether the index has been built. */
33480
+ isReady() {
33481
+ return this.initialized;
33482
+ }
33483
+ /** Total number of indexed tools. */
33484
+ get toolCount() {
33485
+ return this.capabilities.length;
33486
+ }
33487
+ };
33488
+ _instance = null;
33489
+ }
33490
+ });
33491
+
33492
+ // src/lib/drift-probes.ts
33493
+ async function getRecentMemories(agentId, limit) {
33494
+ try {
33495
+ const { getClient: getClient2, isInitialized: isInitialized2 } = await Promise.resolve().then(() => (init_database(), database_exports));
33496
+ if (!isInitialized2()) return { count: 0, texts: [] };
33497
+ const client = getClient2();
33498
+ const result3 = await client.execute({
33499
+ sql: `SELECT text FROM memories WHERE agent_id = ? ORDER BY created_at DESC LIMIT ?`,
33500
+ args: [agentId, limit]
33501
+ });
33502
+ const texts = result3.rows.map((r) => String(r.text ?? ""));
33503
+ return { count: texts.length, texts };
33504
+ } catch {
33505
+ return { count: 0, texts: [] };
33506
+ }
33507
+ }
33508
+ async function getDecisionCount(agentId) {
33509
+ try {
33510
+ const { getClient: getClient2, isInitialized: isInitialized2 } = await Promise.resolve().then(() => (init_database(), database_exports));
33511
+ if (!isInitialized2()) return 0;
33512
+ const client = getClient2();
33513
+ const result3 = await client.execute({
33514
+ sql: `SELECT COUNT(*) as cnt FROM memories WHERE agent_id = ? AND memory_type = 'decision'`,
33515
+ args: [agentId]
33516
+ });
33517
+ return Number(result3.rows[0]?.cnt ?? 0);
33518
+ } catch {
33519
+ return 0;
33520
+ }
33521
+ }
33522
+ function probeContinuity(ctx) {
33523
+ const memCount = ctx.recentMemoryCount;
33524
+ let score;
33525
+ let detail;
33526
+ if (memCount >= 20) {
33527
+ score = 95;
33528
+ detail = `${memCount} recent memories \u2014 strong continuity`;
33529
+ } else if (memCount >= 10) {
33530
+ score = 85;
33531
+ detail = `${memCount} recent memories \u2014 adequate continuity`;
33532
+ } else if (memCount >= 3) {
33533
+ score = 70;
33534
+ detail = `${memCount} recent memories \u2014 limited continuity`;
33535
+ } else if (memCount >= 1) {
33536
+ score = 50;
33537
+ detail = `${memCount} recent memories \u2014 weak continuity`;
33538
+ } else {
33539
+ score = 30;
33540
+ detail = "No recent memories \u2014 agent may lack session context";
33541
+ }
33542
+ if (ctx.storedDecisionCount > 0) {
33543
+ score = Math.min(100, score + 5);
33544
+ detail += ` (+${ctx.storedDecisionCount} decisions)`;
33545
+ }
33546
+ return { axis: "continuity", score, detail };
33547
+ }
33548
+ function probeConsistency(ctx) {
33549
+ if (!ctx.identityBody || ctx.recentMemoryTexts.length === 0) {
33550
+ return {
33551
+ axis: "consistency",
33552
+ score: ctx.identityBody ? 80 : 50,
33553
+ detail: ctx.identityBody ? "No memories to check against identity" : "No identity file \u2014 cannot assess consistency"
33554
+ };
33555
+ }
33556
+ const identityTerms = extractKeyTerms(ctx.identityBody);
33557
+ if (identityTerms.length === 0) {
33558
+ return { axis: "consistency", score: 75, detail: "Identity has no extractable key terms" };
33559
+ }
33560
+ const memoryText = ctx.recentMemoryTexts.join(" ").toLowerCase();
33561
+ let matched = 0;
33562
+ for (const term of identityTerms) {
33563
+ if (memoryText.includes(term.toLowerCase())) matched++;
33564
+ }
33565
+ const ratio = matched / identityTerms.length;
33566
+ const score = Math.round(50 + ratio * 50);
33567
+ const detail = `${matched}/${identityTerms.length} identity terms found in recent memories`;
33568
+ return { axis: "consistency", score, detail };
33569
+ }
33570
+ function probeRoleFidelity(ctx) {
33571
+ const flags = [];
33572
+ if (!ctx.identityBody) {
33573
+ return {
33574
+ axis: "role-fidelity",
33575
+ score: 40,
33576
+ detail: `No identity file for ${ctx.agent.name}`
33577
+ };
33578
+ }
33579
+ let score = 100;
33580
+ const identityLower = ctx.identityBody.toLowerCase();
33581
+ const rosterRole = ctx.agent.role.toLowerCase();
33582
+ if (!identityLower.includes(rosterRole)) {
33583
+ score -= 15;
33584
+ flags.push(`Identity does not mention role "${ctx.agent.role}"`);
33585
+ }
33586
+ if (!identityLower.includes(ctx.agent.name.toLowerCase())) {
33587
+ score -= 10;
33588
+ flags.push(`Identity does not mention agent name "${ctx.agent.name}"`);
33589
+ }
33590
+ const hasScopeBoundary = identityLower.includes("do not") || identityLower.includes("don't") || identityLower.includes("not your") || identityLower.includes("outside your") || identityLower.includes("you do not");
33591
+ if (!hasScopeBoundary) {
33592
+ score -= 10;
33593
+ flags.push("Identity lacks explicit scope boundaries (what NOT to do)");
33594
+ }
33595
+ if (ctx.agent.systemPrompt) {
33596
+ const promptTerms = extractKeyTerms(ctx.agent.systemPrompt);
33597
+ const identityText = ctx.identityBody.toLowerCase();
33598
+ let promptMatched = 0;
33599
+ for (const term of promptTerms.slice(0, 20)) {
33600
+ if (identityText.includes(term.toLowerCase())) promptMatched++;
33601
+ }
33602
+ const promptRatio = promptTerms.length > 0 ? promptMatched / Math.min(promptTerms.length, 20) : 1;
33603
+ if (promptRatio < 0.3) {
33604
+ score -= 15;
33605
+ flags.push("Identity content diverges significantly from roster systemPrompt");
33606
+ }
33607
+ }
33608
+ if (ctx.recentMemoryTexts.length > 5) {
33609
+ const roleKeywords = extractRoleKeywords(ctx.agent.role);
33610
+ const memoryText = ctx.recentMemoryTexts.join(" ").toLowerCase();
33611
+ let domainHits = 0;
33612
+ for (const kw of roleKeywords) {
33613
+ if (memoryText.includes(kw)) domainHits++;
33614
+ }
33615
+ const domainRatio = roleKeywords.length > 0 ? domainHits / roleKeywords.length : 1;
33616
+ if (domainRatio < 0.2) {
33617
+ score -= 10;
33618
+ flags.push(`Recent work shows little overlap with ${ctx.agent.role} domain keywords`);
33619
+ }
33620
+ }
33621
+ score = Math.max(0, Math.min(100, score));
33622
+ const detail = flags.length > 0 ? flags.join("; ") : "Identity aligns with roster definition";
33623
+ return { axis: "role-fidelity", score, detail };
33624
+ }
33625
+ function probeWorldModel(ctx) {
33626
+ if (!ctx.identityBody) {
33627
+ return {
33628
+ axis: "world-model",
33629
+ score: 50,
33630
+ detail: "No identity file \u2014 cannot assess org awareness"
33631
+ };
33632
+ }
33633
+ const identityLower = ctx.identityBody.toLowerCase();
33634
+ const flags = [];
33635
+ let score = 100;
33636
+ const otherAgents = ctx.allEmployees.filter((e) => e.name !== ctx.agent.name);
33637
+ let mentionedAgents = 0;
33638
+ for (const other of otherAgents) {
33639
+ if (identityLower.includes(other.name.toLowerCase())) mentionedAgents++;
33640
+ }
33641
+ if (otherAgents.length > 0) {
33642
+ const mentionRatio = mentionedAgents / otherAgents.length;
33643
+ if (mentionRatio < 0.3) {
33644
+ score -= 15;
33645
+ flags.push(`Identity mentions ${mentionedAgents}/${otherAgents.length} team members`);
33646
+ }
33647
+ }
33648
+ const hasReportingChain = identityLower.includes("report") || identityLower.includes("manager") || identityLower.includes("coo") || identityLower.includes("coordinator");
33649
+ if (!hasReportingChain) {
33650
+ score -= 10;
33651
+ flags.push("Identity does not reference reporting chain");
33652
+ }
33653
+ const orgTerms = ["exe-os", "exe-wiki", "exe-crm", "askexe"];
33654
+ let orgMentions = 0;
33655
+ for (const term of orgTerms) {
33656
+ if (identityLower.includes(term)) orgMentions++;
33657
+ }
33658
+ if (orgMentions === 0) {
33659
+ score -= 5;
33660
+ flags.push("Identity does not reference any org products");
33661
+ }
33662
+ score = Math.max(0, Math.min(100, score));
33663
+ const detail = flags.length > 0 ? flags.join("; ") : "Identity reflects org structure";
33664
+ return { axis: "world-model", score, detail };
33665
+ }
33666
+ function extractKeyTerms(text3) {
33667
+ const stopwords = /* @__PURE__ */ new Set([
33668
+ "the",
33669
+ "and",
33670
+ "for",
33671
+ "are",
33672
+ "but",
33673
+ "not",
33674
+ "you",
33675
+ "all",
33676
+ "any",
33677
+ "can",
33678
+ "had",
33679
+ "her",
33680
+ "was",
33681
+ "one",
33682
+ "our",
33683
+ "out",
33684
+ "has",
33685
+ "his",
33686
+ "how",
33687
+ "its",
33688
+ "may",
33689
+ "new",
33690
+ "now",
33691
+ "old",
33692
+ "see",
33693
+ "way",
33694
+ "who",
33695
+ "did",
33696
+ "get",
33697
+ "got",
33698
+ "let",
33699
+ "say",
33700
+ "she",
33701
+ "too",
33702
+ "use",
33703
+ "with",
33704
+ "this",
33705
+ "that",
33706
+ "from",
33707
+ "they",
33708
+ "been",
33709
+ "have",
33710
+ "will",
33711
+ "your",
33712
+ "what",
33713
+ "when",
33714
+ "make",
33715
+ "like",
33716
+ "just",
33717
+ "over",
33718
+ "such",
33719
+ "take",
33720
+ "than",
33721
+ "them",
33722
+ "very",
33723
+ "some",
33724
+ "could",
33725
+ "into",
33726
+ "then",
33727
+ "more",
33728
+ "also",
33729
+ "after",
33730
+ "should",
33731
+ "would",
33732
+ "about",
33733
+ "their",
33734
+ "which",
33735
+ "these",
33736
+ "other",
33737
+ "every",
33738
+ "does",
33739
+ "being",
33740
+ "those",
33741
+ "never",
33742
+ "before",
33743
+ "through"
33744
+ ]);
33745
+ const words = text3.replace(/[^\w\s-]/g, " ").split(/\s+/).filter((w) => w.length > 3 && !stopwords.has(w.toLowerCase()));
33746
+ return [...new Set(words.map((w) => w.toLowerCase()))].slice(0, 50);
33747
+ }
33748
+ function extractRoleKeywords(role) {
33749
+ const roleKeywordMap = {
33750
+ "coo": ["coordinate", "review", "status", "team", "task", "priority", "dispatch"],
33751
+ "cto": ["architecture", "code", "technical", "system", "design", "review", "security"],
33752
+ "cmo": ["marketing", "brand", "content", "design", "seo", "social", "campaign"],
33753
+ "principal engineer": ["code", "implement", "test", "fix", "feature", "refactor", "build"],
33754
+ "staff code reviewer": ["review", "code", "quality", "issue", "fix", "pattern"],
33755
+ "content production specialist": ["video", "image", "render", "content", "produce", "media"],
33756
+ "ai product lead": ["competitive", "analysis", "feature", "product", "research", "repo"]
33757
+ };
33758
+ const normalized = role.toLowerCase();
33759
+ return roleKeywordMap[normalized] ?? normalized.split(/\s+/).filter((w) => w.length > 2);
33760
+ }
33761
+ async function runDriftProbes(options = {}) {
33762
+ const employees = loadEmployeesSync();
33763
+ const targetAgents = options.agentId ? employees.filter((e) => e.name === options.agentId) : employees;
33764
+ if (targetAgents.length === 0) {
33765
+ return [];
33766
+ }
33767
+ const axes = options.axes ?? ["continuity", "consistency", "role-fidelity", "world-model"];
33768
+ const results = [];
33769
+ for (const agent of targetAgents) {
33770
+ const identity = getIdentity(agent.name);
33771
+ const { count: memCount, texts: memTexts } = await getRecentMemories(agent.name, 50);
33772
+ const decisionCount = await getDecisionCount(agent.name);
33773
+ const ctx = {
33774
+ agent,
33775
+ identityBody: identity?.body ?? null,
33776
+ identityRole: identity?.frontmatter.role ?? null,
33777
+ recentMemoryCount: memCount,
33778
+ recentMemoryTexts: memTexts,
33779
+ storedDecisionCount: decisionCount,
33780
+ allEmployees: employees
33781
+ };
33782
+ const probes = [];
33783
+ const probeFns = {
33784
+ continuity: probeContinuity,
33785
+ consistency: probeConsistency,
33786
+ "role-fidelity": probeRoleFidelity,
33787
+ "world-model": probeWorldModel
33788
+ };
33789
+ for (const axis of axes) {
33790
+ probes.push(probeFns[axis](ctx));
33791
+ }
33792
+ const scores = {
33793
+ continuity: 0,
33794
+ consistency: 0,
33795
+ "role-fidelity": 0,
33796
+ "world-model": 0
33797
+ };
33798
+ for (const probe of probes) {
33799
+ scores[probe.axis] = probe.score;
33800
+ }
33801
+ const weights = {
33802
+ continuity: 0.2,
33803
+ consistency: 0.2,
33804
+ "role-fidelity": 0.35,
33805
+ "world-model": 0.25
33806
+ };
33807
+ let weightedSum = 0;
33808
+ let weightTotal = 0;
33809
+ for (const axis of axes) {
33810
+ weightedSum += scores[axis] * weights[axis];
33811
+ weightTotal += weights[axis];
33812
+ }
33813
+ const overall = Math.round(weightTotal > 0 ? weightedSum / weightTotal : 0);
33814
+ const flags = [];
33815
+ for (const probe of probes) {
33816
+ if (probe.score < DRIFT_THRESHOLD) {
33817
+ flags.push(`${probe.axis} at ${probe.score}% \u2014 ${probe.detail}`);
33818
+ }
33819
+ }
33820
+ results.push({
33821
+ agent: agent.name,
33822
+ scores,
33823
+ overall,
33824
+ drifting: flags.length > 0,
33825
+ flags
33826
+ });
33827
+ }
33828
+ return results;
33829
+ }
33830
+ var DRIFT_THRESHOLD;
33831
+ var init_drift_probes = __esm({
33832
+ "src/lib/drift-probes.ts"() {
33833
+ "use strict";
33834
+ init_identity();
33835
+ init_employees();
33836
+ DRIFT_THRESHOLD = 80;
33837
+ }
33838
+ });
33839
+
33063
33840
  // src/mcp/tools/diagnostics.ts
33064
33841
  import { z as z98 } from "zod";
33065
33842
  function buildHandlers9() {
@@ -33075,23 +33852,31 @@ function buildHandlers9() {
33075
33852
  function registerDiagnostics(server) {
33076
33853
  const handlers = buildHandlers9();
33077
33854
  const toolNames = Array.from(handlers.keys());
33078
- void (toolNames.length > 0 ? toolNames : ["healthcheck"]);
33855
+ toolNames.push("tool_search", "drift");
33079
33856
  server.registerTool(
33080
33857
  "diagnostics",
33081
33858
  {
33082
33859
  title: "Diagnostics",
33083
- description: `System diagnostics and admin operations. Actions: ${toolNames.join(", ")}. Covers health checks, update status, cloud status, key management, and employee rename.`,
33860
+ description: `System diagnostics and admin operations. Actions: ${toolNames.join(", ")}. Covers health checks, update status, cloud status, key management, employee rename, semantic tool discovery (tool_search), and identity drift detection (drift).`,
33084
33861
  inputSchema: {
33085
33862
  action: z98.string().describe(`Diagnostic operation: ${toolNames.join(", ")}`),
33086
33863
  // Pass-through params used by various sub-tools
33864
+ agent_id: z98.string().optional().describe("Agent to probe (drift). Defaults to all agents."),
33087
33865
  name: z98.string().optional().describe("Employee name (rename_employee)"),
33088
33866
  new_name: z98.string().optional().describe("New employee name (rename_employee)"),
33089
- query: z98.string().optional().describe("Search/filter query"),
33090
- format: z98.string().optional().describe("Output format")
33867
+ query: z98.string().optional().describe("Search/filter query, or natural language for tool_search"),
33868
+ format: z98.string().optional().describe("Output format"),
33869
+ limit: z98.coerce.number().int().min(1).max(100).optional().describe("Max results for tool_search (default 20)")
33091
33870
  }
33092
33871
  },
33093
33872
  async (input, extra) => {
33094
33873
  const action = input.action;
33874
+ if (action === "tool_search") {
33875
+ return handleToolSearch(input);
33876
+ }
33877
+ if (action === "drift") {
33878
+ return handleDrift(input);
33879
+ }
33095
33880
  const handler = handlers.get(action);
33096
33881
  if (!handler) {
33097
33882
  return {
@@ -33104,14 +33889,89 @@ function registerDiagnostics(server) {
33104
33889
  }
33105
33890
  );
33106
33891
  }
33892
+ async function handleToolSearch(input) {
33893
+ const query = input.query ?? "";
33894
+ const limit = input.limit ?? 20;
33895
+ if (!query) {
33896
+ return {
33897
+ content: [{ type: "text", text: JSON.stringify({ error: "query is required for tool_search" }) }],
33898
+ isError: true
33899
+ };
33900
+ }
33901
+ const index = getToolCapabilityIndex();
33902
+ if (!index.isReady()) {
33903
+ return {
33904
+ content: [{
33905
+ type: "text",
33906
+ text: JSON.stringify({
33907
+ error: "Tool capability index not initialized yet. It is built asynchronously at daemon boot.",
33908
+ query
33909
+ })
33910
+ }],
33911
+ isError: true
33912
+ };
33913
+ }
33914
+ const results = await index.search(query, limit);
33915
+ const tools = results.map((r) => ({
33916
+ name: r.name,
33917
+ description: r.description,
33918
+ category: r.category,
33919
+ relevance: Math.round(r.relevance * 1e3) / 1e3
33920
+ }));
33921
+ return {
33922
+ content: [{
33923
+ type: "text",
33924
+ text: JSON.stringify({
33925
+ tools,
33926
+ total_tools: index.toolCount,
33927
+ query
33928
+ }, null, 2)
33929
+ }]
33930
+ };
33931
+ }
33932
+ async function handleDrift(input) {
33933
+ const agentId = input.agent_id;
33934
+ const axesRaw = input.query;
33935
+ let axes;
33936
+ if (axesRaw) {
33937
+ const valid = ["continuity", "consistency", "role-fidelity", "world-model"];
33938
+ const parsed = axesRaw.split(",").map((a) => a.trim()).filter((a) => valid.includes(a));
33939
+ if (parsed.length > 0) axes = parsed;
33940
+ }
33941
+ const results = await runDriftProbes({ agentId, axes });
33942
+ if (results.length === 0) {
33943
+ return {
33944
+ content: [{
33945
+ type: "text",
33946
+ text: JSON.stringify({ error: agentId ? `Agent "${agentId}" not found` : "No agents found" })
33947
+ }],
33948
+ isError: true
33949
+ };
33950
+ }
33951
+ return {
33952
+ content: [{
33953
+ type: "text",
33954
+ text: JSON.stringify(results.length === 1 ? results[0] : results, null, 2)
33955
+ }]
33956
+ };
33957
+ }
33107
33958
  var init_diagnostics = __esm({
33108
33959
  "src/mcp/tools/diagnostics.ts"() {
33109
33960
  "use strict";
33110
33961
  init_cli_parity();
33962
+ init_tool_capability_index();
33963
+ init_drift_probes();
33111
33964
  }
33112
33965
  });
33113
33966
 
33114
33967
  // src/mcp/tool-gates.ts
33968
+ var tool_gates_exports = {};
33969
+ __export(tool_gates_exports, {
33970
+ TOOL_CATEGORIES: () => TOOL_CATEGORIES,
33971
+ TOOL_GATES: () => TOOL_GATES,
33972
+ isToolAllowed: () => isToolAllowed,
33973
+ isToolAllowedForPlan: () => isToolAllowedForPlan
33974
+ });
33115
33975
  function isToolAllowedForPlan(registerFnName, plan) {
33116
33976
  const category = TOOL_CATEGORIES[registerFnName];
33117
33977
  if (!category) return true;
@@ -35478,6 +36338,20 @@ async function startMcpHttpServer() {
35478
36338
  `
35479
36339
  );
35480
36340
  }
36341
+ Promise.all([
36342
+ Promise.resolve().then(() => (init_tool_capability_index(), tool_capability_index_exports)),
36343
+ Promise.resolve().then(() => (init_tool_gates(), tool_gates_exports))
36344
+ ]).then(async ([{ getToolCapabilityIndex: getToolCapabilityIndex2 }, { TOOL_CATEGORIES: TOOL_CATEGORIES2 }]) => {
36345
+ const gateState = getCachedLicenseGate2();
36346
+ const index = getToolCapabilityIndex2();
36347
+ await index.buildIndex(
36348
+ (fakeServer) => registerAllTools2(fakeServer, gateState.license, gateState.hasKey),
36349
+ TOOL_CATEGORIES2
36350
+ );
36351
+ }).catch((err) => {
36352
+ process.stderr.write(`[exed] Tool capability index failed: ${err.message}
36353
+ `);
36354
+ });
35481
36355
  const transports = /* @__PURE__ */ new Map();
35482
36356
  const MCP_HTTP_PORT = parseInt(process.env.EXE_MCP_PORT || "48739", 10);
35483
36357
  const MCP_SESSION_TTL_MS = parseDurationMs2(process.env.EXE_MCP_SESSION_TTL_MS, 30 * 60 * 1e3, { allowZero: true });