@askexenow/exe-os 0.9.92 → 0.9.94

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 +118 -13
  10. package/dist/bin/cli.js +1605 -456
  11. package/dist/bin/customer-readiness.js +51 -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 +111 -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 +133 -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 +132 -18
  22. package/dist/bin/exe-heartbeat.js +118 -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 +118 -13
  27. package/dist/bin/exe-pending-notifications.js +118 -13
  28. package/dist/bin/exe-pending-reviews.js +118 -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 +133 -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 +118 -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 +133 -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 +133 -18
  43. package/dist/bin/scan-tasks.js +133 -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 +57 -1
  47. package/dist/bin/update.js +1 -1
  48. package/dist/gateway/index.js +133 -18
  49. package/dist/hooks/bug-report-worker.js +133 -18
  50. package/dist/hooks/codex-stop-task-finalizer.js +123 -14
  51. package/dist/hooks/commit-complete.js +133 -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 +310 -50
  57. package/dist/hooks/post-tool-combined.js +433 -13
  58. package/dist/hooks/pre-compact.js +133 -18
  59. package/dist/hooks/pre-tool-use.js +118 -13
  60. package/dist/hooks/prompt-submit.js +191 -19
  61. package/dist/hooks/session-end.js +133 -18
  62. package/dist/hooks/session-start.js +143 -13
  63. package/dist/hooks/stop.js +118 -13
  64. package/dist/hooks/subagent-stop.js +118 -13
  65. package/dist/hooks/summary-worker.js +96 -7
  66. package/dist/index.js +133 -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 +913 -42
  74. package/dist/lib/hybrid-search.js +100 -9
  75. package/dist/lib/license.js +1 -1
  76. package/dist/lib/messaging.js +40 -4
  77. package/dist/lib/schedules.js +54 -3
  78. package/dist/lib/store.js +75 -9
  79. package/dist/lib/tasks.js +58 -9
  80. package/dist/lib/tmux-routing.js +58 -9
  81. package/dist/mcp/server.js +875 -42
  82. package/dist/mcp/tools/create-task.js +67 -12
  83. package/dist/mcp/tools/list-tasks.js +46 -5
  84. package/dist/mcp/tools/send-message.js +40 -4
  85. package/dist/mcp/tools/update-task.js +58 -9
  86. package/dist/runtime/index.js +133 -18
  87. package/dist/tui/App.js +132 -18
  88. package/package.json +1 -1
@@ -3243,6 +3243,20 @@ async function ensureSchema() {
3243
3243
  });
3244
3244
  } catch {
3245
3245
  }
3246
+ try {
3247
+ await client.execute({
3248
+ sql: `ALTER TABLE tasks ADD COLUMN spawn_runtime TEXT`,
3249
+ args: []
3250
+ });
3251
+ } catch {
3252
+ }
3253
+ try {
3254
+ await client.execute({
3255
+ sql: `ALTER TABLE tasks ADD COLUMN spawn_model TEXT`,
3256
+ args: []
3257
+ });
3258
+ } catch {
3259
+ }
3246
3260
  await client.executeMultiple(`
3247
3261
  CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
3248
3262
  content_text,
@@ -3494,6 +3508,22 @@ async function ensureSchema() {
3494
3508
  );
3495
3509
  } catch {
3496
3510
  }
3511
+ for (const col of [
3512
+ "ALTER TABLE memories ADD COLUMN valid_from TEXT",
3513
+ "ALTER TABLE memories ADD COLUMN invalid_at TEXT"
3514
+ ]) {
3515
+ try {
3516
+ await client.execute(col);
3517
+ } catch {
3518
+ }
3519
+ }
3520
+ try {
3521
+ await client.execute({
3522
+ sql: `UPDATE memories SET valid_from = timestamp WHERE valid_from IS NULL`,
3523
+ args: []
3524
+ });
3525
+ } catch {
3526
+ }
3497
3527
  try {
3498
3528
  await client.execute({
3499
3529
  sql: `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`,
@@ -3536,6 +3566,13 @@ async function ensureSchema() {
3536
3566
  } catch {
3537
3567
  }
3538
3568
  }
3569
+ try {
3570
+ await client.execute({
3571
+ sql: `ALTER TABLE memories ADD COLUMN procedure_for TEXT`,
3572
+ args: []
3573
+ });
3574
+ } catch {
3575
+ }
3539
3576
  try {
3540
3577
  await client.execute({
3541
3578
  sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
@@ -4843,6 +4880,20 @@ var init_platform_procedures = __esm({
4843
4880
  priority: "p1",
4844
4881
  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."
4845
4882
  },
4883
+ // --- Tool guidance ---
4884
+ {
4885
+ title: "How to use company_actions \u2014 execute business actions through gateway connectors",
4886
+ domain: "tools",
4887
+ priority: "p2",
4888
+ 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."
4889
+ },
4890
+ // --- Release awareness ---
4891
+ {
4892
+ title: "What's New check \u2014 surface new features after update",
4893
+ domain: "support",
4894
+ priority: "p1",
4895
+ 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."
4896
+ },
4846
4897
  // --- Platform vs Customer ownership ---
4847
4898
  {
4848
4899
  title: "What the platform provides vs what you customize",
@@ -4931,13 +4982,13 @@ var init_platform_procedures = __esm({
4931
4982
  title: "MCP tools \u2014 memory, decision, and search",
4932
4983
  domain: "tool-use",
4933
4984
  priority: "p1",
4934
- 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.`
4985
+ content: `memory(action="recall") / recall_my_memory: search your own memories (semantic + FTS). Supports as_of param for bi-temporal queries (what did I know at time X?), kind param to filter by memory type (decision, procedure, observation, raw, conversation, behavior). memory(action="ask_team") / ask_team_memory: search a colleague's memories by agent name. memory(action="store") / store_memory: persist a memory. Supports kind param and procedure_for domain tag for procedure-type memories. 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. memory(action="supersede") / 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.`
4935
4986
  },
4936
4987
  {
4937
4988
  title: "MCP tools \u2014 task orchestration",
4938
4989
  domain: "tool-use",
4939
4990
  priority: "p1",
4940
- 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.'
4991
+ content: `task(action="create") / create_task: dispatch work (title, assigned_to, context). The ONLY dispatch path. Auto-spawns session. Supports spawn_runtime and spawn_model params to override the agent's default runtime/model for a specific task. 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.`
4941
4992
  },
4942
4993
  {
4943
4994
  title: "MCP tools \u2014 knowledge graph (GraphRAG)",
@@ -4967,7 +5018,7 @@ var init_platform_procedures = __esm({
4967
5018
  title: "MCP tools \u2014 admin, config, and operations",
4968
5019
  domain: "tool-use",
4969
5020
  priority: "p1",
4970
- 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.'
5021
+ 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. diagnostics(action="tool_search"): semantic tool discovery \u2014 find relevant MCP tools by natural language query. Returns top-K tools ranked by relevance. diagnostics(action="drift"): identity drift detection \u2014 score how far an agent has drifted from its role identity. Returns drift score + recommendations. mcp_ping(): daemon health + license status + tool usage stats.'
4971
5022
  }
4972
5023
  ];
4973
5024
  PLATFORM_PROCEDURE_TITLES = new Set(
@@ -5651,6 +5702,8 @@ async function writeMemory(record) {
5651
5702
  source_type: record.source_type ?? null,
5652
5703
  tier: record.tier ?? classifyTier(record),
5653
5704
  supersedes_id: record.supersedes_id ?? null,
5705
+ valid_from: record.valid_from ?? record.timestamp,
5706
+ invalid_at: record.invalid_at ?? null,
5654
5707
  draft: record.draft ? 1 : 0,
5655
5708
  memory_type: memoryType,
5656
5709
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
@@ -5669,7 +5722,8 @@ async function writeMemory(record) {
5669
5722
  token_cost: record.token_cost ?? null,
5670
5723
  audience: record.audience ?? null,
5671
5724
  language_type: record.language_type ?? inferLanguageType(record),
5672
- parent_memory_id: record.parent_memory_id ?? null
5725
+ parent_memory_id: record.parent_memory_id ?? null,
5726
+ procedure_for: record.procedure_for ?? null
5673
5727
  };
5674
5728
  _pendingRecords.push(dbRow);
5675
5729
  orgBus.emit({
@@ -5724,6 +5778,8 @@ async function flushBatch() {
5724
5778
  const sourceType = row.source_type ?? null;
5725
5779
  const tier = row.tier ?? 3;
5726
5780
  const supersedesId = row.supersedes_id ?? null;
5781
+ const validFrom = row.valid_from ?? row.timestamp;
5782
+ const invalidAt = row.invalid_at ?? null;
5727
5783
  const draft = row.draft ? 1 : 0;
5728
5784
  const memoryType = row.memory_type ?? "raw";
5729
5785
  const trajectory = row.trajectory ?? null;
@@ -5743,15 +5799,16 @@ async function flushBatch() {
5743
5799
  const audience = row.audience ?? null;
5744
5800
  const languageType = row.language_type ?? null;
5745
5801
  const parentMemoryId = row.parent_memory_id ?? null;
5802
+ const procedureFor = row.procedure_for ?? null;
5746
5803
  const cols = `id, agent_id, agent_role, session_id, timestamp,
5747
5804
  tool_name, project_name,
5748
5805
  has_error, raw_text, vector, version, task_id, importance, status,
5749
5806
  confidence, last_accessed,
5750
5807
  workspace_id, document_id, user_id, char_offset, page_number,
5751
- source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
5808
+ source_path, source_type, tier, supersedes_id, valid_from, invalid_at, draft, memory_type, trajectory, content_hash,
5752
5809
  intent, outcome, domain, referenced_entities, retrieval_count,
5753
5810
  chain_position, review_status, context_window_pct, file_paths, commit_hash,
5754
- duration_ms, token_cost, audience, language_type, parent_memory_id`;
5811
+ duration_ms, token_cost, audience, language_type, parent_memory_id, procedure_for`;
5755
5812
  const metaArgs = [
5756
5813
  intent,
5757
5814
  outcome,
@@ -5767,7 +5824,8 @@ async function flushBatch() {
5767
5824
  tokenCost,
5768
5825
  audience,
5769
5826
  languageType,
5770
- parentMemoryId
5827
+ parentMemoryId,
5828
+ procedureFor
5771
5829
  ];
5772
5830
  const baseArgs = [
5773
5831
  row.id,
@@ -5796,6 +5854,8 @@ async function flushBatch() {
5796
5854
  sourceType,
5797
5855
  tier,
5798
5856
  supersedesId,
5857
+ validFrom,
5858
+ invalidAt,
5799
5859
  draft,
5800
5860
  memoryType,
5801
5861
  trajectory,
@@ -5803,8 +5863,8 @@ async function flushBatch() {
5803
5863
  ];
5804
5864
  return {
5805
5865
  sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
5806
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
5807
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
5866
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
5867
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
5808
5868
  args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
5809
5869
  };
5810
5870
  };
@@ -5920,6 +5980,12 @@ async function searchMemories(queryVector, agentId, options) {
5920
5980
  AND vector IS NOT NULL${statusFilter}${draftFilter}
5921
5981
  AND COALESCE(confidence, 0.7) >= 0.3`;
5922
5982
  const args = [agentId];
5983
+ if (options?.asOf) {
5984
+ sql += ` AND (valid_from IS NULL OR valid_from <= ?) AND (invalid_at IS NULL OR invalid_at > ?)`;
5985
+ args.push(options.asOf, options.asOf);
5986
+ } else {
5987
+ sql += ` AND invalid_at IS NULL`;
5988
+ }
5923
5989
  const scope = buildWikiScopeFilter(options, "");
5924
5990
  sql += scope.clause;
5925
5991
  args.push(...scope.args);
@@ -7176,6 +7242,19 @@ __export(hybrid_search_exports, {
7176
7242
  rrfMerge: () => rrfMerge,
7177
7243
  rrfMergeMulti: () => rrfMergeMulti
7178
7244
  });
7245
+ function buildTemporalFilter(options, columnPrefix) {
7246
+ const asOf = options?.asOf;
7247
+ if (asOf) {
7248
+ return {
7249
+ clause: ` AND (${columnPrefix}valid_from IS NULL OR ${columnPrefix}valid_from <= ?) AND (${columnPrefix}invalid_at IS NULL OR ${columnPrefix}invalid_at > ?)`,
7250
+ args: [asOf, asOf]
7251
+ };
7252
+ }
7253
+ return {
7254
+ clause: ` AND ${columnPrefix}invalid_at IS NULL`,
7255
+ args: []
7256
+ };
7257
+ }
7179
7258
  function appendMemoryTypeFilter(sql, args, column, options) {
7180
7259
  if (options?.memoryTypes && options.memoryTypes.length > 0) {
7181
7260
  const uniqueTypes = [...new Set(options.memoryTypes)];
@@ -7408,6 +7487,9 @@ async function estimateCardinality(agentId, options) {
7408
7487
  AND COALESCE(status, 'active') = 'active'
7409
7488
  AND COALESCE(confidence, 0.7) >= 0.3`;
7410
7489
  const args = [agentId];
7490
+ const temporal = buildTemporalFilter(options, "");
7491
+ sql += temporal.clause;
7492
+ args.push(...temporal.args);
7411
7493
  const rawVisibility = buildRawVisibilityFilter(options, "");
7412
7494
  sql += rawVisibility.clause;
7413
7495
  args.push(...rawVisibility.args);
@@ -7536,6 +7618,9 @@ async function ftsQuery(client, matchExpr, agentId, options, limit) {
7536
7618
  AND m.agent_id = ?${statusFilter}${draftFilter}
7537
7619
  AND COALESCE(m.confidence, 0.7) >= 0.3`;
7538
7620
  const args = [matchExpr, agentId];
7621
+ const temporal = buildTemporalFilter(options, "m.");
7622
+ sql += temporal.clause;
7623
+ args.push(...temporal.args);
7539
7624
  const scope = buildWikiScopeFilter(options, "m.");
7540
7625
  sql += scope.clause;
7541
7626
  args.push(...scope.args);
@@ -7648,6 +7733,9 @@ async function recentRecords(agentId, options, limit, textFilter) {
7648
7733
  WHERE agent_id = ?${statusFilter}${draftFilter}
7649
7734
  AND COALESCE(confidence, 0.7) >= 0.3`;
7650
7735
  const args = [agentId];
7736
+ const temporal = buildTemporalFilter(options, "");
7737
+ sql += temporal.clause;
7738
+ args.push(...temporal.args);
7651
7739
  const scope = buildWikiScopeFilter(options, "");
7652
7740
  sql += scope.clause;
7653
7741
  args.push(...scope.args);
@@ -7748,6 +7836,9 @@ async function trajectoryBypass(queryText, agentId, options, limit) {
7748
7836
  AND json_extract(trajectory, '$.tool') = ?
7749
7837
  AND agent_id = ?${statusFilter}${draftFilter}`;
7750
7838
  const args = [toolName, agentId];
7839
+ const temporal = buildTemporalFilter(options, "");
7840
+ sql += temporal.clause;
7841
+ args.push(...temporal.args);
7751
7842
  const rawVisibility = buildRawVisibilityFilter(options, "");
7752
7843
  sql += rawVisibility.clause;
7753
7844
  args.push(...rawVisibility.args);
@@ -8509,7 +8600,7 @@ var init_license = __esm({
8509
8600
  LICENSE_PATH = path13.join(EXE_AI_DIR, "license.key");
8510
8601
  CACHE_PATH = path13.join(EXE_AI_DIR, "license-cache.json");
8511
8602
  DEVICE_ID_PATH = path13.join(EXE_AI_DIR, "device-id");
8512
- API_BASE = "https://askexe.com/cloud";
8603
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
8513
8604
  RETRY_DELAY_MS = 500;
8514
8605
  LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
8515
8606
  MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
@@ -8825,6 +8916,7 @@ async function selectUnconsolidated(client, limit = 200) {
8825
8916
  sql: `SELECT id, agent_id, project_name, tool_name, raw_text, timestamp
8826
8917
  FROM memories
8827
8918
  WHERE consolidated = 0
8919
+ AND COALESCE(memory_type, 'raw') != 'procedure'
8828
8920
  ORDER BY timestamp DESC
8829
8921
  LIMIT ?`,
8830
8922
  args: [limit]
@@ -9168,7 +9260,8 @@ async function isUserIdle(client, idleMinutes = 30) {
9168
9260
  async function countUnconsolidated(client) {
9169
9261
  const result3 = await client.execute({
9170
9262
  sql: `SELECT COUNT(*) as cnt FROM memories
9171
- WHERE consolidated = 0`,
9263
+ WHERE consolidated = 0
9264
+ AND COALESCE(memory_type, 'raw') != 'procedure'`,
9172
9265
  args: []
9173
9266
  });
9174
9267
  return Number(result3.rows[0]?.cnt ?? 0);
@@ -10459,6 +10552,7 @@ function resolveExeSession() {
10459
10552
  const mySession = getMySession();
10460
10553
  if (!mySession) return null;
10461
10554
  const fromSessionName = extractRootExe(mySession);
10555
+ let candidate = null;
10462
10556
  try {
10463
10557
  const key = getSessionKey();
10464
10558
  const parentExe = getParentExe(key);
@@ -10469,13 +10563,47 @@ function resolveExeSession() {
10469
10563
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
10470
10564
  `
10471
10565
  );
10472
- return fromSessionName;
10566
+ candidate = fromSessionName;
10567
+ } else {
10568
+ candidate = fromCache;
10473
10569
  }
10474
- return fromCache;
10475
10570
  }
10476
10571
  } catch {
10477
10572
  }
10478
- return fromSessionName ?? mySession;
10573
+ if (!candidate) {
10574
+ candidate = fromSessionName ?? mySession;
10575
+ }
10576
+ if (candidate && isRootSession(candidate)) {
10577
+ try {
10578
+ const transport = getTransport();
10579
+ const liveSessions = transport.listSessions();
10580
+ if (!liveSessions.includes(candidate)) {
10581
+ const liveRoots = liveSessions.filter((s) => isRootSession(s));
10582
+ if (liveRoots.length === 1) {
10583
+ process.stderr.write(
10584
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. Using live coordinator "${liveRoots[0]}".
10585
+ `
10586
+ );
10587
+ return liveRoots[0];
10588
+ } else if (liveRoots.length > 1) {
10589
+ const base = candidate.replace(/\d+$/, "");
10590
+ const match = liveRoots.find((s) => s.startsWith(base));
10591
+ const chosen = match ?? liveRoots[0];
10592
+ process.stderr.write(
10593
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. ${liveRoots.length} live roots found, using "${chosen}".
10594
+ `
10595
+ );
10596
+ return chosen;
10597
+ }
10598
+ process.stderr.write(
10599
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead and no live coordinator found.
10600
+ `
10601
+ );
10602
+ }
10603
+ } catch {
10604
+ }
10605
+ }
10606
+ return candidate;
10479
10607
  }
10480
10608
  function isEmployeeAlive(sessionName) {
10481
10609
  return getTransport().isAlive(sessionName);
@@ -10877,7 +11005,12 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
10877
11005
  }
10878
11006
  const spawnCwd = opts?.cwd ?? projectDir;
10879
11007
  const useExeAgent = !!(opts?.model && opts?.provider);
10880
- const agentRtConfig = getAgentRuntime(employeeName);
11008
+ const baseRtConfig = getAgentRuntime(employeeName);
11009
+ const agentRtConfig = {
11010
+ ...baseRtConfig,
11011
+ ...opts?.runtimeOverride ? { runtime: opts.runtimeOverride } : {},
11012
+ ...opts?.modelOverride ? { model: opts.modelOverride } : {}
11013
+ };
10881
11014
  const useCodex = !useExeAgent && agentRtConfig.runtime === "codex";
10882
11015
  const useOpencode = !useExeAgent && !useCodex && agentRtConfig.runtime === "opencode";
10883
11016
  const ccProvider = useExeAgent || useCodex || useOpencode ? DEFAULT_PROVIDER : detectActiveProvider();
@@ -11489,8 +11622,8 @@ ${scopeMismatchWarning}` : scopeMismatchWarning;
11489
11622
  const complexity = input.complexity ?? "standard";
11490
11623
  const sessionScope = earlySessionScope;
11491
11624
  await client.execute({
11492
- 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)
11493
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
11625
+ 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)
11626
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
11494
11627
  args: [
11495
11628
  id,
11496
11629
  input.title,
@@ -11510,6 +11643,8 @@ ${scopeMismatchWarning}` : scopeMismatchWarning;
11510
11643
  0,
11511
11644
  null,
11512
11645
  sessionScope,
11646
+ input.spawnRuntime ?? null,
11647
+ input.spawnModel ?? null,
11513
11648
  now,
11514
11649
  now
11515
11650
  ]
@@ -11566,7 +11701,9 @@ ${input.context}
11566
11701
  budgetTokens: input.budgetTokens ?? null,
11567
11702
  budgetFallbackModel: input.budgetFallbackModel ?? null,
11568
11703
  tokensUsed: 0,
11569
- tokensWarnedAt: null
11704
+ tokensWarnedAt: null,
11705
+ spawnRuntime: input.spawnRuntime ?? null,
11706
+ spawnModel: input.spawnModel ?? null
11570
11707
  };
11571
11708
  }
11572
11709
  async function listTasks(input) {
@@ -11616,7 +11753,9 @@ async function listTasks(input) {
11616
11753
  budgetTokens: r.budget_tokens !== null ? Number(r.budget_tokens) : null,
11617
11754
  budgetFallbackModel: r.budget_fallback_model !== null ? String(r.budget_fallback_model) : null,
11618
11755
  tokensUsed: Number(r.tokens_used ?? 0),
11619
- tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
11756
+ tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null,
11757
+ spawnRuntime: r.spawn_runtime !== null && r.spawn_runtime !== void 0 ? String(r.spawn_runtime) : null,
11758
+ spawnModel: r.spawn_model !== null && r.spawn_model !== void 0 ? String(r.spawn_model) : null
11620
11759
  }));
11621
11760
  }
11622
11761
  function isTmuxSessionAlive(identifier) {
@@ -12566,6 +12705,8 @@ async function updateTask(input) {
12566
12705
  budgetFallbackModel: row.budget_fallback_model !== void 0 && row.budget_fallback_model !== null ? String(row.budget_fallback_model) : null,
12567
12706
  tokensUsed: Number(row.tokens_used ?? 0),
12568
12707
  tokensWarnedAt: row.tokens_warned_at !== void 0 && row.tokens_warned_at !== null ? Number(row.tokens_warned_at) : null,
12708
+ spawnRuntime: row.spawn_runtime !== void 0 && row.spawn_runtime !== null ? String(row.spawn_runtime) : null,
12709
+ spawnModel: row.spawn_model !== void 0 && row.spawn_model !== null ? String(row.spawn_model) : null,
12569
12710
  nextTask
12570
12711
  };
12571
12712
  }
@@ -14397,7 +14538,11 @@ function registerRecallMyMemory(server2) {
14397
14538
  include_source: z.boolean().optional().default(false).describe(
14398
14539
  "When true, attach parent document metadata (filename, mime, source_type) to each result. Default false."
14399
14540
  ),
14400
- retrieval_mode: z.enum(RETRIEVAL_MODES).optional().default("all").describe(`Typed retrieval mode. ${formatRetrievalModes()}`)
14541
+ retrieval_mode: z.enum(RETRIEVAL_MODES).optional().default("all").describe(`Typed retrieval mode. ${formatRetrievalModes()}`),
14542
+ kind: z.string().optional().describe(
14543
+ "Filter by memory_type: decision, procedure, observation, raw, conversation, behavior."
14544
+ ),
14545
+ as_of: z.string().optional().describe("ISO 8601 timestamp for time-travel query. Returns facts valid at that point in time.")
14401
14546
  }
14402
14547
  },
14403
14548
  async ({
@@ -14414,7 +14559,9 @@ function registerRecallMyMemory(server2) {
14414
14559
  workspace_id,
14415
14560
  user_id,
14416
14561
  include_source,
14417
- retrieval_mode
14562
+ retrieval_mode,
14563
+ kind,
14564
+ as_of
14418
14565
  }) => {
14419
14566
  try {
14420
14567
  if (!recent && !query) {
@@ -14434,7 +14581,9 @@ function registerRecallMyMemory(server2) {
14434
14581
  workspaceId: workspace_id,
14435
14582
  includeSource: include_source,
14436
14583
  includeDrafts: true,
14437
- ...user_id !== void 0 ? { userId: user_id } : {}
14584
+ ...user_id !== void 0 ? { userId: user_id } : {},
14585
+ ...kind ? { memoryType: kind } : {},
14586
+ ...as_of ? { asOf: as_of } : {}
14438
14587
  }, retrieval_mode);
14439
14588
  let results;
14440
14589
  if (recent) {
@@ -14614,10 +14763,12 @@ function registerStoreMemory(server2) {
14614
14763
  project_name: z3.string().optional().describe("Project name"),
14615
14764
  has_error: z3.boolean().optional().default(false).describe("Whether this memory is error-related"),
14616
14765
  source_path: z3.string().optional().describe("Original file path, URL, or document reference"),
14617
- source_type: z3.string().optional().describe("Content type: text, pdf, image, audio, video, url, document")
14766
+ source_type: z3.string().optional().describe("Content type: text, pdf, image, audio, video, url, document"),
14767
+ kind: z3.string().optional().describe("Memory type: decision, procedure, observation, raw, conversation, behavior"),
14768
+ procedure_for: z3.string().optional().describe("Domain this procedure applies to (only for kind='procedure')")
14618
14769
  }
14619
14770
  },
14620
- async ({ text: text3, query, tool_name, project_name, has_error, source_path, source_type }) => {
14771
+ async ({ text: text3, query, tool_name, project_name, has_error, source_path, source_type, kind, procedure_for }) => {
14621
14772
  const resolvedText = text3 ?? query;
14622
14773
  if (!resolvedText) {
14623
14774
  return {
@@ -14662,7 +14813,9 @@ function registerStoreMemory(server2) {
14662
14813
  raw_text: resolvedText,
14663
14814
  vector,
14664
14815
  source_path: source_path ?? null,
14665
- source_type: source_type ?? null
14816
+ source_type: source_type ?? null,
14817
+ memory_type: kind ?? void 0,
14818
+ procedure_for: kind === "procedure" ? procedure_for ?? null : null
14666
14819
  });
14667
14820
  await flushBatch();
14668
14821
  if (needsBackfill) {
@@ -14813,9 +14966,9 @@ function truncate(text3, max) {
14813
14966
  const marker = ` \u2026 [truncated; +${text3.length - max} chars]`;
14814
14967
  return text3.slice(0, Math.max(0, max - marker.length)) + marker;
14815
14968
  }
14816
- async function searchMemories2(query, agentId, limit) {
14969
+ async function searchMemories2(query, agentId, limit, memoryType) {
14817
14970
  const { hybridSearch: hybridSearch2 } = await Promise.resolve().then(() => (init_hybrid_search(), hybrid_search_exports));
14818
- const results = await hybridSearch2(query, agentId, { limit });
14971
+ const results = await hybridSearch2(query, agentId, { limit, memoryType });
14819
14972
  return results.map((r, i) => ({
14820
14973
  source: "memory",
14821
14974
  score: 1 - i / Math.max(results.length, 1),
@@ -14939,7 +15092,7 @@ async function unifiedSearch(query, agentId, opts) {
14939
15092
  const perSourceLimit = Math.max(Math.ceil(limit * 1.5), 5);
14940
15093
  const promises = [];
14941
15094
  if (sources.includes("memory")) {
14942
- promises.push(searchMemories2(query, agentId, perSourceLimit));
15095
+ promises.push(searchMemories2(query, agentId, perSourceLimit, opts?.memoryType));
14943
15096
  }
14944
15097
  if (sources.includes("conversations")) {
14945
15098
  promises.push(searchConversations(query, perSourceLimit));
@@ -14974,7 +15127,8 @@ function registerSearchEverything(server2) {
14974
15127
  sources: z5.array(z5.enum(["memory", "conversations", "wiki"])).optional().describe(
14975
15128
  "Which sources to search (default: all). Options: memory, conversations, wiki"
14976
15129
  ),
14977
- limit: z5.coerce.number().int().min(1).max(50).optional().default(10).describe("Max results to return (default 10, max 50)")
15130
+ limit: z5.coerce.number().int().min(1).max(50).optional().default(10).describe("Max results to return (default 10, max 50)"),
15131
+ kind: z5.string().optional().describe("Filter memories by memory_type: decision, procedure, observation, raw, conversation, behavior.")
14978
15132
  }
14979
15133
  },
14980
15134
  async (params) => {
@@ -14983,7 +15137,8 @@ function registerSearchEverything(server2) {
14983
15137
  const sources = params.sources;
14984
15138
  const results = await unifiedSearch(params.query, agentId, {
14985
15139
  limit: params.limit,
14986
- sources
15140
+ sources,
15141
+ memoryType: params.kind
14987
15142
  });
14988
15143
  if (results.length === 0) {
14989
15144
  return {
@@ -15356,7 +15511,8 @@ var ACTION_TO_LEGACY_TOOL = {
15356
15511
  session_context: "get_session_context",
15357
15512
  get_by_id: "get_memory_by_id",
15358
15513
  consolidate: "consolidate_memories",
15359
- cardinality: "get_memory_cardinality"
15514
+ cardinality: "get_memory_cardinality",
15515
+ supersede: "__inline_supersede__"
15360
15516
  };
15361
15517
  var REQUIRED_FIELDS = {
15362
15518
  recall: [],
@@ -15367,7 +15523,8 @@ var REQUIRED_FIELDS = {
15367
15523
  session_context: ["session_id", "target_timestamp"],
15368
15524
  get_by_id: ["id"],
15369
15525
  consolidate: [],
15370
- cardinality: []
15526
+ cardinality: [],
15527
+ supersede: ["old_id", "text"]
15371
15528
  };
15372
15529
  function errorResult(text3) {
15373
15530
  return {
@@ -15396,15 +15553,74 @@ function buildLegacyMemoryHandlers() {
15396
15553
  registerGetMemoryCardinality(localServer);
15397
15554
  return tools;
15398
15555
  }
15556
+ async function handleSupersede(input) {
15557
+ const oldId = input.old_id;
15558
+ const text3 = input.text ?? input.query;
15559
+ if (!oldId || !text3) {
15560
+ return errorResult('memory action "supersede" requires old_id and text');
15561
+ }
15562
+ try {
15563
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
15564
+ const { getActiveAgent: getActiveAgent2 } = await Promise.resolve().then(() => (init_active_agent(), active_agent_exports));
15565
+ const { writeMemory: writeMemory2, flushBatch: flushBatch2 } = await Promise.resolve().then(() => (init_store(), store_exports));
15566
+ const { randomUUID: randomUUID11 } = await import("crypto");
15567
+ const client = getClient2();
15568
+ const now = (/* @__PURE__ */ new Date()).toISOString();
15569
+ const oldRow = await client.execute({
15570
+ sql: `SELECT id, agent_id, agent_role, project_name FROM memories WHERE id = ?`,
15571
+ args: [oldId]
15572
+ });
15573
+ if (oldRow.rows.length === 0) {
15574
+ return errorResult(`Memory not found: ${oldId}`);
15575
+ }
15576
+ await client.execute({
15577
+ sql: `UPDATE memories SET invalid_at = ? WHERE id = ?`,
15578
+ args: [now, oldId]
15579
+ });
15580
+ const { agentId, agentRole } = getActiveAgent2();
15581
+ const sessionId = process.env.SESSION_ID ?? "manual";
15582
+ const { getProjectName: getProjectName2 } = await Promise.resolve().then(() => (init_project_name(), project_name_exports));
15583
+ const projectName = input.project_name ?? getProjectName2(process.cwd());
15584
+ const newId = randomUUID11();
15585
+ await writeMemory2({
15586
+ id: newId,
15587
+ agent_id: agentId,
15588
+ agent_role: agentRole,
15589
+ session_id: sessionId,
15590
+ timestamp: now,
15591
+ tool_name: "manual",
15592
+ project_name: projectName,
15593
+ has_error: false,
15594
+ raw_text: text3,
15595
+ vector: null,
15596
+ valid_from: now,
15597
+ supersedes_id: oldId,
15598
+ importance: 7,
15599
+ memory_type: "adr"
15600
+ });
15601
+ await flushBatch2();
15602
+ return {
15603
+ content: [{
15604
+ type: "text",
15605
+ text: `Superseded memory ${oldId.slice(0, 8)}\u2026 \u2192 new memory ${newId.slice(0, 8)}\u2026
15606
+ Old memory invalid_at set to ${now}.
15607
+ New memory id: ${newId}`
15608
+ }]
15609
+ };
15610
+ } catch (err) {
15611
+ const msg = err instanceof Error ? err.message : String(err);
15612
+ return errorResult(`Supersede failed: ${msg}`);
15613
+ }
15614
+ }
15399
15615
  function registerMemory(server2) {
15400
15616
  const legacyTools = buildLegacyMemoryHandlers();
15401
15617
  server2.registerTool(
15402
15618
  "memory",
15403
15619
  {
15404
15620
  title: "Memory",
15405
- 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.",
15621
+ 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.",
15406
15622
  inputSchema: {
15407
- action: z10.enum(["recall", "ask_team", "store", "commit", "search", "session_context", "consolidate", "cardinality"]).describe("Memory operation to perform"),
15623
+ action: z10.enum(["recall", "ask_team", "store", "commit", "search", "session_context", "consolidate", "cardinality", "supersede"]).describe("Memory operation to perform"),
15408
15624
  query: z10.string().optional().describe("Search query. Also accepted as store text alias."),
15409
15625
  text: z10.string().optional().describe("Memory text for action=store"),
15410
15626
  summary: z10.string().optional().describe("High-importance summary for action=commit"),
@@ -15437,7 +15653,11 @@ function registerMemory(server2) {
15437
15653
  window_size: z10.number().optional().describe("Window size for action=session_context"),
15438
15654
  max_clusters: z10.coerce.number().optional().describe("Max clusters for action=consolidate"),
15439
15655
  model: z10.string().optional().describe("Model for action=consolidate"),
15440
- agent_id: z10.string().optional().describe("Agent filter for action=cardinality")
15656
+ agent_id: z10.string().optional().describe("Agent filter for action=cardinality"),
15657
+ kind: z10.string().optional().describe("Filter by memory_type: decision, procedure, observation, raw, conversation, behavior. For action=recall and action=search."),
15658
+ procedure_for: z10.string().optional().describe("Domain this procedure applies to (only for storing procedure-type memories)."),
15659
+ as_of: z10.string().optional().describe("ISO 8601 timestamp for time-travel query. Returns facts valid at that point in time. For action=recall."),
15660
+ old_id: z10.string().optional().describe("UUID of memory to supersede. For action=supersede.")
15441
15661
  }
15442
15662
  },
15443
15663
  async (input, extra) => {
@@ -15446,6 +15666,9 @@ function registerMemory(server2) {
15446
15666
  if (!legacyToolName) {
15447
15667
  return errorResult(`Unknown memory action: ${String(input.action)}`);
15448
15668
  }
15669
+ if (action === "supersede") {
15670
+ return handleSupersede(input);
15671
+ }
15449
15672
  const { action: _action, ...legacyArgs } = input;
15450
15673
  const missing = missingRequiredFields(legacyArgs, REQUIRED_FIELDS[action]);
15451
15674
  if (missing.length > 0) {
@@ -15631,10 +15854,12 @@ function registerCreateTask(server2) {
15631
15854
  parent_task_id: z12.string().optional().describe("Parent task ID or slug. Links this task as a subtask."),
15632
15855
  reviewer: z12.string().optional().describe("Who should review this task when done. Defaults to assigner."),
15633
15856
  budget_tokens: z12.number().optional().describe("Max tokens allowed for this task (null = unlimited)"),
15634
- budget_fallback_model: z12.string().optional().describe("Model to route to when budget is exhausted")
15857
+ budget_fallback_model: z12.string().optional().describe("Model to route to when budget is exhausted"),
15858
+ spawn_runtime: z12.string().optional().describe("Override runtime for spawned session (e.g., 'claude', 'codex', 'opencode')"),
15859
+ spawn_model: z12.string().optional().describe("Override model for spawned session (e.g., 'claude-sonnet-4.6', 'claude-haiku-4.5')")
15635
15860
  }
15636
15861
  },
15637
- async ({ title, assigned_to, project_name, priority, complexity, context, blocked_by, parent_task_id, reviewer, budget_tokens, budget_fallback_model }) => {
15862
+ async ({ title, assigned_to, project_name, priority, complexity, context, blocked_by, parent_task_id, reviewer, budget_tokens, budget_fallback_model, spawn_runtime, spawn_model }) => {
15638
15863
  if (!isCoordinatorName(assigned_to)) {
15639
15864
  const license = getLicenseSync();
15640
15865
  if (license.plan === "free") {
@@ -15662,6 +15887,8 @@ function registerCreateTask(server2) {
15662
15887
  reviewer,
15663
15888
  budgetTokens: budget_tokens,
15664
15889
  budgetFallbackModel: budget_fallback_model,
15890
+ spawnRuntime: spawn_runtime,
15891
+ spawnModel: spawn_model,
15665
15892
  // Skip internal dispatch — we handle it below with autoInstance
15666
15893
  // support. Without this, createTask fires dispatchTaskToEmployee
15667
15894
  // (no autoInstance) in parallel, racing with our ensureEmployee
@@ -15699,7 +15926,9 @@ function registerCreateTask(server2) {
15699
15926
  const cfg = loadConfigSync2();
15700
15927
  const result3 = ensureEmployee(assigned_to, exeSession, process.cwd(), {
15701
15928
  autoInstance: useAutoInstance,
15702
- maxAutoInstances: useAutoInstance ? cfg.sessionLifecycle.maxAutoInstances : void 0
15929
+ maxAutoInstances: useAutoInstance ? cfg.sessionLifecycle.maxAutoInstances : void 0,
15930
+ runtimeOverride: spawn_runtime ?? void 0,
15931
+ modelOverride: spawn_model ?? void 0
15703
15932
  });
15704
15933
  switch (result3.status) {
15705
15934
  case "intercom_sent":
@@ -15929,6 +16158,14 @@ function registerGetTask(server2) {
15929
16158
  }
15930
16159
  }
15931
16160
  }
16161
+ const spawnRuntime = row.spawn_runtime ? String(row.spawn_runtime) : null;
16162
+ const spawnModel = row.spawn_model ? String(row.spawn_model) : null;
16163
+ if (spawnRuntime || spawnModel) {
16164
+ lines.push("");
16165
+ lines.push("**Spawn Override:**");
16166
+ if (spawnRuntime) lines.push(`Runtime: ${spawnRuntime}`);
16167
+ if (spawnModel) lines.push(`Model: ${spawnModel}`);
16168
+ }
15932
16169
  if (row.context) {
15933
16170
  lines.push("", "## Context", "", contextText);
15934
16171
  }
@@ -16377,6 +16614,8 @@ function registerTask(server2) {
16377
16614
  reviewer: z19.string().optional().describe("Reviewer for action=create"),
16378
16615
  budget_tokens: z19.number().optional().describe("Max token budget for action=create"),
16379
16616
  budget_fallback_model: z19.string().optional().describe("Fallback model for action=create"),
16617
+ spawn_runtime: z19.string().optional().describe("Override runtime for spawned session (e.g., 'claude', 'codex', 'opencode') for action=create"),
16618
+ spawn_model: z19.string().optional().describe("Override model for spawned session (e.g., 'claude-sonnet-4.6') for action=create"),
16380
16619
  status: z19.enum(["open", "in_progress", "done", "needs_review", "blocked", "cancelled", "closed"]).optional().describe("Status for action=update/list/close"),
16381
16620
  result: z19.string().optional().describe("Result summary for action=update or action=close"),
16382
16621
  step: z19.string().optional().describe("Checkpoint step for action=checkpoint"),
@@ -27607,6 +27846,15 @@ function registerCreateBugReport(server2) {
27607
27846
  project_name,
27608
27847
  send_upstream
27609
27848
  }) => {
27849
+ if (!summary || summary.trim().length === 0) {
27850
+ return {
27851
+ content: [{
27852
+ type: "text",
27853
+ text: "Error: summary is required but was empty. If calling via support(action='create_bug'), use 'description' which maps to 'summary'."
27854
+ }],
27855
+ isError: true
27856
+ };
27857
+ }
27610
27858
  const { agentId, agentRole } = getActiveAgent();
27611
27859
  const id = crypto18.randomUUID();
27612
27860
  const version = package_version ?? "unknown";
@@ -30096,6 +30344,25 @@ function registerSupportConsolidated(server2) {
30096
30344
  const handler = handlers.get(toolName);
30097
30345
  if (!handler) return { content: [{ type: "text", text: `Handler not found: ${toolName}` }], isError: true };
30098
30346
  const { action: _, ...args } = input;
30347
+ if (action === "create_bug") {
30348
+ if (args.description && !args.summary) {
30349
+ args.summary = args.description;
30350
+ delete args.description;
30351
+ }
30352
+ if (args.steps_to_reproduce && !args.reproduction_steps) {
30353
+ const raw = args.steps_to_reproduce;
30354
+ args.reproduction_steps = raw.split(/\n/).map((s) => s.replace(/^\d+\.\s*/, "").trim()).filter(Boolean);
30355
+ delete args.steps_to_reproduce;
30356
+ }
30357
+ if (args.expected_behavior && !args.expected) {
30358
+ args.expected = args.expected_behavior;
30359
+ delete args.expected_behavior;
30360
+ }
30361
+ if (args.actual_behavior && !args.actual) {
30362
+ args.actual = args.actual_behavior;
30363
+ delete args.actual_behavior;
30364
+ }
30365
+ }
30099
30366
  if ((action === "triage_bug" || action === "triage_feature") && args.notes && !args.triage_notes) {
30100
30367
  args.triage_notes = args.notes;
30101
30368
  delete args.notes;
@@ -30110,6 +30377,498 @@ function registerSupportConsolidated(server2) {
30110
30377
 
30111
30378
  // src/mcp/tools/diagnostics.ts
30112
30379
  import { z as z98 } from "zod";
30380
+
30381
+ // src/lib/tool-capability-index.ts
30382
+ init_memory();
30383
+ var ALWAYS_ON = /* @__PURE__ */ new Set([
30384
+ "mcp_ping",
30385
+ "memory",
30386
+ "task",
30387
+ "message",
30388
+ "identity",
30389
+ "diagnostics",
30390
+ "session"
30391
+ ]);
30392
+ function categoryForToolName(toolName, categoryMap) {
30393
+ const normalized = toolName.replace(/[_-]/g, "").toLowerCase();
30394
+ for (const [registerFn, cat] of Object.entries(categoryMap)) {
30395
+ const fnNormalized = registerFn.replace(/^register/, "").toLowerCase();
30396
+ if (fnNormalized === normalized) return cat;
30397
+ }
30398
+ return "unknown";
30399
+ }
30400
+ function extractParamNames(inputSchema) {
30401
+ if (!inputSchema || typeof inputSchema !== "object") return "";
30402
+ return Object.keys(inputSchema).join(", ");
30403
+ }
30404
+ function cosineSimilarity4(a, b) {
30405
+ if (a.length !== b.length || a.length === 0) return 0;
30406
+ let dot = 0, normA = 0, normB = 0;
30407
+ for (let i = 0; i < a.length; i++) {
30408
+ dot += a[i] * b[i];
30409
+ normA += a[i] * a[i];
30410
+ normB += b[i] * b[i];
30411
+ }
30412
+ const denom = Math.sqrt(normA) * Math.sqrt(normB);
30413
+ return denom === 0 ? 0 : dot / denom;
30414
+ }
30415
+ function keywordScore(query, tool) {
30416
+ const words = query.toLowerCase().split(/\s+/).filter((w) => w.length > 2);
30417
+ if (words.length === 0) return 0;
30418
+ const searchText = `${tool.name} ${tool.description} ${tool.category} ${tool.paramSummary}`.toLowerCase();
30419
+ let matches = 0;
30420
+ for (const word of words) {
30421
+ if (searchText.includes(word)) matches++;
30422
+ }
30423
+ return matches / words.length;
30424
+ }
30425
+ var ToolCapabilityIndex = class {
30426
+ capabilities = [];
30427
+ initialized = false;
30428
+ /**
30429
+ * Build the index by intercepting registerAllTools via a fake McpServer.
30430
+ *
30431
+ * @param registerFn - function that registers tools on a server (e.g. registerAllTools)
30432
+ * @param categoryMap - optional map of registerFnName → category (from tool-gates TOOL_CATEGORIES)
30433
+ */
30434
+ async buildIndex(registerFn, categoryMap) {
30435
+ const captured = [];
30436
+ const fakeServer = {
30437
+ registerTool(name, config2, _handler) {
30438
+ captured.push({
30439
+ name,
30440
+ description: config2.description ?? "",
30441
+ inputSchema: config2.inputSchema ?? {}
30442
+ });
30443
+ }
30444
+ };
30445
+ registerFn(fakeServer);
30446
+ let embedFn = null;
30447
+ try {
30448
+ const { embed: embed2 } = await Promise.resolve().then(() => (init_embedder(), embedder_exports));
30449
+ const test = await embed2("test");
30450
+ if (test.length === EMBEDDING_DIM) {
30451
+ embedFn = embed2;
30452
+ }
30453
+ } catch {
30454
+ }
30455
+ const capabilities = [];
30456
+ for (const tool of captured) {
30457
+ const paramSummary = extractParamNames(tool.inputSchema);
30458
+ const category = categoryMap ? categoryForToolName(tool.name, categoryMap) : "unknown";
30459
+ const capabilityDoc = `${tool.name}: ${tool.description}. Parameters: ${paramSummary}`;
30460
+ let vector = null;
30461
+ if (embedFn) {
30462
+ try {
30463
+ vector = await embedFn(capabilityDoc.slice(0, 500));
30464
+ } catch {
30465
+ }
30466
+ }
30467
+ capabilities.push({
30468
+ name: tool.name,
30469
+ description: tool.description,
30470
+ category,
30471
+ vector,
30472
+ paramSummary
30473
+ });
30474
+ }
30475
+ this.capabilities = capabilities;
30476
+ this.initialized = true;
30477
+ process.stderr.write(
30478
+ `[tool-capability-index] Indexed ${capabilities.length} tools (${capabilities.filter((c) => c.vector).length} with vectors)
30479
+ `
30480
+ );
30481
+ }
30482
+ /**
30483
+ * Semantic search for tools matching a natural-language query.
30484
+ * Returns tools sorted by relevance, with always-on tools boosted.
30485
+ */
30486
+ async search(query, limit = 20) {
30487
+ if (!this.initialized || this.capabilities.length === 0) return [];
30488
+ let queryVector = null;
30489
+ try {
30490
+ const { embed: embed2 } = await Promise.resolve().then(() => (init_embedder(), embedder_exports));
30491
+ queryVector = await embed2(query);
30492
+ } catch {
30493
+ }
30494
+ const scored = [];
30495
+ for (const tool of this.capabilities) {
30496
+ let relevance;
30497
+ if (queryVector && tool.vector) {
30498
+ const vecScore = cosineSimilarity4(queryVector, tool.vector);
30499
+ const kwScore = keywordScore(query, tool);
30500
+ relevance = vecScore * 0.8 + kwScore * 0.2;
30501
+ } else {
30502
+ relevance = keywordScore(query, tool);
30503
+ }
30504
+ if (ALWAYS_ON.has(tool.name)) {
30505
+ relevance = Math.max(relevance, 0.5);
30506
+ }
30507
+ scored.push({ ...tool, relevance });
30508
+ }
30509
+ scored.sort((a, b) => b.relevance - a.relevance);
30510
+ return scored.slice(0, limit);
30511
+ }
30512
+ /** Whether the index has been built. */
30513
+ isReady() {
30514
+ return this.initialized;
30515
+ }
30516
+ /** Total number of indexed tools. */
30517
+ get toolCount() {
30518
+ return this.capabilities.length;
30519
+ }
30520
+ };
30521
+ var _instance = null;
30522
+ function getToolCapabilityIndex() {
30523
+ if (!_instance) {
30524
+ _instance = new ToolCapabilityIndex();
30525
+ }
30526
+ return _instance;
30527
+ }
30528
+
30529
+ // src/lib/drift-probes.ts
30530
+ init_identity();
30531
+ init_employees();
30532
+ var DRIFT_THRESHOLD = 80;
30533
+ async function getRecentMemories(agentId, limit) {
30534
+ try {
30535
+ const { getClient: getClient2, isInitialized: isInitialized2 } = await Promise.resolve().then(() => (init_database(), database_exports));
30536
+ if (!isInitialized2()) return { count: 0, texts: [] };
30537
+ const client = getClient2();
30538
+ const result3 = await client.execute({
30539
+ sql: `SELECT text FROM memories WHERE agent_id = ? ORDER BY created_at DESC LIMIT ?`,
30540
+ args: [agentId, limit]
30541
+ });
30542
+ const texts = result3.rows.map((r) => String(r.text ?? ""));
30543
+ return { count: texts.length, texts };
30544
+ } catch {
30545
+ return { count: 0, texts: [] };
30546
+ }
30547
+ }
30548
+ async function getDecisionCount(agentId) {
30549
+ try {
30550
+ const { getClient: getClient2, isInitialized: isInitialized2 } = await Promise.resolve().then(() => (init_database(), database_exports));
30551
+ if (!isInitialized2()) return 0;
30552
+ const client = getClient2();
30553
+ const result3 = await client.execute({
30554
+ sql: `SELECT COUNT(*) as cnt FROM memories WHERE agent_id = ? AND memory_type = 'decision'`,
30555
+ args: [agentId]
30556
+ });
30557
+ return Number(result3.rows[0]?.cnt ?? 0);
30558
+ } catch {
30559
+ return 0;
30560
+ }
30561
+ }
30562
+ function probeContinuity(ctx) {
30563
+ const memCount = ctx.recentMemoryCount;
30564
+ let score;
30565
+ let detail;
30566
+ if (memCount >= 20) {
30567
+ score = 95;
30568
+ detail = `${memCount} recent memories \u2014 strong continuity`;
30569
+ } else if (memCount >= 10) {
30570
+ score = 85;
30571
+ detail = `${memCount} recent memories \u2014 adequate continuity`;
30572
+ } else if (memCount >= 3) {
30573
+ score = 70;
30574
+ detail = `${memCount} recent memories \u2014 limited continuity`;
30575
+ } else if (memCount >= 1) {
30576
+ score = 50;
30577
+ detail = `${memCount} recent memories \u2014 weak continuity`;
30578
+ } else {
30579
+ score = 30;
30580
+ detail = "No recent memories \u2014 agent may lack session context";
30581
+ }
30582
+ if (ctx.storedDecisionCount > 0) {
30583
+ score = Math.min(100, score + 5);
30584
+ detail += ` (+${ctx.storedDecisionCount} decisions)`;
30585
+ }
30586
+ return { axis: "continuity", score, detail };
30587
+ }
30588
+ function probeConsistency(ctx) {
30589
+ if (!ctx.identityBody || ctx.recentMemoryTexts.length === 0) {
30590
+ return {
30591
+ axis: "consistency",
30592
+ score: ctx.identityBody ? 80 : 50,
30593
+ detail: ctx.identityBody ? "No memories to check against identity" : "No identity file \u2014 cannot assess consistency"
30594
+ };
30595
+ }
30596
+ const identityTerms = extractKeyTerms(ctx.identityBody);
30597
+ if (identityTerms.length === 0) {
30598
+ return { axis: "consistency", score: 75, detail: "Identity has no extractable key terms" };
30599
+ }
30600
+ const memoryText = ctx.recentMemoryTexts.join(" ").toLowerCase();
30601
+ let matched = 0;
30602
+ for (const term of identityTerms) {
30603
+ if (memoryText.includes(term.toLowerCase())) matched++;
30604
+ }
30605
+ const ratio = matched / identityTerms.length;
30606
+ const score = Math.round(50 + ratio * 50);
30607
+ const detail = `${matched}/${identityTerms.length} identity terms found in recent memories`;
30608
+ return { axis: "consistency", score, detail };
30609
+ }
30610
+ function probeRoleFidelity(ctx) {
30611
+ const flags = [];
30612
+ if (!ctx.identityBody) {
30613
+ return {
30614
+ axis: "role-fidelity",
30615
+ score: 40,
30616
+ detail: `No identity file for ${ctx.agent.name}`
30617
+ };
30618
+ }
30619
+ let score = 100;
30620
+ const identityLower = ctx.identityBody.toLowerCase();
30621
+ const rosterRole = ctx.agent.role.toLowerCase();
30622
+ if (!identityLower.includes(rosterRole)) {
30623
+ score -= 15;
30624
+ flags.push(`Identity does not mention role "${ctx.agent.role}"`);
30625
+ }
30626
+ if (!identityLower.includes(ctx.agent.name.toLowerCase())) {
30627
+ score -= 10;
30628
+ flags.push(`Identity does not mention agent name "${ctx.agent.name}"`);
30629
+ }
30630
+ const hasScopeBoundary = identityLower.includes("do not") || identityLower.includes("don't") || identityLower.includes("not your") || identityLower.includes("outside your") || identityLower.includes("you do not");
30631
+ if (!hasScopeBoundary) {
30632
+ score -= 10;
30633
+ flags.push("Identity lacks explicit scope boundaries (what NOT to do)");
30634
+ }
30635
+ if (ctx.agent.systemPrompt) {
30636
+ const promptTerms = extractKeyTerms(ctx.agent.systemPrompt);
30637
+ const identityText = ctx.identityBody.toLowerCase();
30638
+ let promptMatched = 0;
30639
+ for (const term of promptTerms.slice(0, 20)) {
30640
+ if (identityText.includes(term.toLowerCase())) promptMatched++;
30641
+ }
30642
+ const promptRatio = promptTerms.length > 0 ? promptMatched / Math.min(promptTerms.length, 20) : 1;
30643
+ if (promptRatio < 0.3) {
30644
+ score -= 15;
30645
+ flags.push("Identity content diverges significantly from roster systemPrompt");
30646
+ }
30647
+ }
30648
+ if (ctx.recentMemoryTexts.length > 5) {
30649
+ const roleKeywords = extractRoleKeywords(ctx.agent.role);
30650
+ const memoryText = ctx.recentMemoryTexts.join(" ").toLowerCase();
30651
+ let domainHits = 0;
30652
+ for (const kw of roleKeywords) {
30653
+ if (memoryText.includes(kw)) domainHits++;
30654
+ }
30655
+ const domainRatio = roleKeywords.length > 0 ? domainHits / roleKeywords.length : 1;
30656
+ if (domainRatio < 0.2) {
30657
+ score -= 10;
30658
+ flags.push(`Recent work shows little overlap with ${ctx.agent.role} domain keywords`);
30659
+ }
30660
+ }
30661
+ score = Math.max(0, Math.min(100, score));
30662
+ const detail = flags.length > 0 ? flags.join("; ") : "Identity aligns with roster definition";
30663
+ return { axis: "role-fidelity", score, detail };
30664
+ }
30665
+ function probeWorldModel(ctx) {
30666
+ if (!ctx.identityBody) {
30667
+ return {
30668
+ axis: "world-model",
30669
+ score: 50,
30670
+ detail: "No identity file \u2014 cannot assess org awareness"
30671
+ };
30672
+ }
30673
+ const identityLower = ctx.identityBody.toLowerCase();
30674
+ const flags = [];
30675
+ let score = 100;
30676
+ const otherAgents = ctx.allEmployees.filter((e) => e.name !== ctx.agent.name);
30677
+ let mentionedAgents = 0;
30678
+ for (const other of otherAgents) {
30679
+ if (identityLower.includes(other.name.toLowerCase())) mentionedAgents++;
30680
+ }
30681
+ if (otherAgents.length > 0) {
30682
+ const mentionRatio = mentionedAgents / otherAgents.length;
30683
+ if (mentionRatio < 0.3) {
30684
+ score -= 15;
30685
+ flags.push(`Identity mentions ${mentionedAgents}/${otherAgents.length} team members`);
30686
+ }
30687
+ }
30688
+ const hasReportingChain = identityLower.includes("report") || identityLower.includes("manager") || identityLower.includes("coo") || identityLower.includes("coordinator");
30689
+ if (!hasReportingChain) {
30690
+ score -= 10;
30691
+ flags.push("Identity does not reference reporting chain");
30692
+ }
30693
+ const orgTerms = ["exe-os", "exe-wiki", "exe-crm", "askexe"];
30694
+ let orgMentions = 0;
30695
+ for (const term of orgTerms) {
30696
+ if (identityLower.includes(term)) orgMentions++;
30697
+ }
30698
+ if (orgMentions === 0) {
30699
+ score -= 5;
30700
+ flags.push("Identity does not reference any org products");
30701
+ }
30702
+ score = Math.max(0, Math.min(100, score));
30703
+ const detail = flags.length > 0 ? flags.join("; ") : "Identity reflects org structure";
30704
+ return { axis: "world-model", score, detail };
30705
+ }
30706
+ function extractKeyTerms(text3) {
30707
+ const stopwords = /* @__PURE__ */ new Set([
30708
+ "the",
30709
+ "and",
30710
+ "for",
30711
+ "are",
30712
+ "but",
30713
+ "not",
30714
+ "you",
30715
+ "all",
30716
+ "any",
30717
+ "can",
30718
+ "had",
30719
+ "her",
30720
+ "was",
30721
+ "one",
30722
+ "our",
30723
+ "out",
30724
+ "has",
30725
+ "his",
30726
+ "how",
30727
+ "its",
30728
+ "may",
30729
+ "new",
30730
+ "now",
30731
+ "old",
30732
+ "see",
30733
+ "way",
30734
+ "who",
30735
+ "did",
30736
+ "get",
30737
+ "got",
30738
+ "let",
30739
+ "say",
30740
+ "she",
30741
+ "too",
30742
+ "use",
30743
+ "with",
30744
+ "this",
30745
+ "that",
30746
+ "from",
30747
+ "they",
30748
+ "been",
30749
+ "have",
30750
+ "will",
30751
+ "your",
30752
+ "what",
30753
+ "when",
30754
+ "make",
30755
+ "like",
30756
+ "just",
30757
+ "over",
30758
+ "such",
30759
+ "take",
30760
+ "than",
30761
+ "them",
30762
+ "very",
30763
+ "some",
30764
+ "could",
30765
+ "into",
30766
+ "then",
30767
+ "more",
30768
+ "also",
30769
+ "after",
30770
+ "should",
30771
+ "would",
30772
+ "about",
30773
+ "their",
30774
+ "which",
30775
+ "these",
30776
+ "other",
30777
+ "every",
30778
+ "does",
30779
+ "being",
30780
+ "those",
30781
+ "never",
30782
+ "before",
30783
+ "through"
30784
+ ]);
30785
+ const words = text3.replace(/[^\w\s-]/g, " ").split(/\s+/).filter((w) => w.length > 3 && !stopwords.has(w.toLowerCase()));
30786
+ return [...new Set(words.map((w) => w.toLowerCase()))].slice(0, 50);
30787
+ }
30788
+ function extractRoleKeywords(role) {
30789
+ const roleKeywordMap = {
30790
+ "coo": ["coordinate", "review", "status", "team", "task", "priority", "dispatch"],
30791
+ "cto": ["architecture", "code", "technical", "system", "design", "review", "security"],
30792
+ "cmo": ["marketing", "brand", "content", "design", "seo", "social", "campaign"],
30793
+ "principal engineer": ["code", "implement", "test", "fix", "feature", "refactor", "build"],
30794
+ "staff code reviewer": ["review", "code", "quality", "issue", "fix", "pattern"],
30795
+ "content production specialist": ["video", "image", "render", "content", "produce", "media"],
30796
+ "ai product lead": ["competitive", "analysis", "feature", "product", "research", "repo"]
30797
+ };
30798
+ const normalized = role.toLowerCase();
30799
+ return roleKeywordMap[normalized] ?? normalized.split(/\s+/).filter((w) => w.length > 2);
30800
+ }
30801
+ async function runDriftProbes(options = {}) {
30802
+ const employees = loadEmployeesSync();
30803
+ const targetAgents = options.agentId ? employees.filter((e) => e.name === options.agentId) : employees;
30804
+ if (targetAgents.length === 0) {
30805
+ return [];
30806
+ }
30807
+ const axes = options.axes ?? ["continuity", "consistency", "role-fidelity", "world-model"];
30808
+ const results = [];
30809
+ for (const agent of targetAgents) {
30810
+ const identity = getIdentity(agent.name);
30811
+ const { count: memCount, texts: memTexts } = await getRecentMemories(agent.name, 50);
30812
+ const decisionCount = await getDecisionCount(agent.name);
30813
+ const ctx = {
30814
+ agent,
30815
+ identityBody: identity?.body ?? null,
30816
+ identityRole: identity?.frontmatter.role ?? null,
30817
+ recentMemoryCount: memCount,
30818
+ recentMemoryTexts: memTexts,
30819
+ storedDecisionCount: decisionCount,
30820
+ allEmployees: employees
30821
+ };
30822
+ const probes = [];
30823
+ const probeFns = {
30824
+ continuity: probeContinuity,
30825
+ consistency: probeConsistency,
30826
+ "role-fidelity": probeRoleFidelity,
30827
+ "world-model": probeWorldModel
30828
+ };
30829
+ for (const axis of axes) {
30830
+ probes.push(probeFns[axis](ctx));
30831
+ }
30832
+ const scores = {
30833
+ continuity: 0,
30834
+ consistency: 0,
30835
+ "role-fidelity": 0,
30836
+ "world-model": 0
30837
+ };
30838
+ for (const probe of probes) {
30839
+ scores[probe.axis] = probe.score;
30840
+ }
30841
+ const weights = {
30842
+ continuity: 0.2,
30843
+ consistency: 0.2,
30844
+ "role-fidelity": 0.35,
30845
+ "world-model": 0.25
30846
+ };
30847
+ let weightedSum = 0;
30848
+ let weightTotal = 0;
30849
+ for (const axis of axes) {
30850
+ weightedSum += scores[axis] * weights[axis];
30851
+ weightTotal += weights[axis];
30852
+ }
30853
+ const overall = Math.round(weightTotal > 0 ? weightedSum / weightTotal : 0);
30854
+ const flags = [];
30855
+ for (const probe of probes) {
30856
+ if (probe.score < DRIFT_THRESHOLD) {
30857
+ flags.push(`${probe.axis} at ${probe.score}% \u2014 ${probe.detail}`);
30858
+ }
30859
+ }
30860
+ results.push({
30861
+ agent: agent.name,
30862
+ scores,
30863
+ overall,
30864
+ drifting: flags.length > 0,
30865
+ flags
30866
+ });
30867
+ }
30868
+ return results;
30869
+ }
30870
+
30871
+ // src/mcp/tools/diagnostics.ts
30113
30872
  function buildHandlers9() {
30114
30873
  const tools = /* @__PURE__ */ new Map();
30115
30874
  const fake = {
@@ -30123,23 +30882,31 @@ function buildHandlers9() {
30123
30882
  function registerDiagnostics(server2) {
30124
30883
  const handlers = buildHandlers9();
30125
30884
  const toolNames = Array.from(handlers.keys());
30126
- void (toolNames.length > 0 ? toolNames : ["healthcheck"]);
30885
+ toolNames.push("tool_search", "drift");
30127
30886
  server2.registerTool(
30128
30887
  "diagnostics",
30129
30888
  {
30130
30889
  title: "Diagnostics",
30131
- description: `System diagnostics and admin operations. Actions: ${toolNames.join(", ")}. Covers health checks, update status, cloud status, key management, and employee rename.`,
30890
+ 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).`,
30132
30891
  inputSchema: {
30133
30892
  action: z98.string().describe(`Diagnostic operation: ${toolNames.join(", ")}`),
30134
30893
  // Pass-through params used by various sub-tools
30894
+ agent_id: z98.string().optional().describe("Agent to probe (drift). Defaults to all agents."),
30135
30895
  name: z98.string().optional().describe("Employee name (rename_employee)"),
30136
30896
  new_name: z98.string().optional().describe("New employee name (rename_employee)"),
30137
- query: z98.string().optional().describe("Search/filter query"),
30138
- format: z98.string().optional().describe("Output format")
30897
+ query: z98.string().optional().describe("Search/filter query, or natural language for tool_search"),
30898
+ format: z98.string().optional().describe("Output format"),
30899
+ limit: z98.coerce.number().int().min(1).max(100).optional().describe("Max results for tool_search (default 20)")
30139
30900
  }
30140
30901
  },
30141
30902
  async (input, extra) => {
30142
30903
  const action = input.action;
30904
+ if (action === "tool_search") {
30905
+ return handleToolSearch(input);
30906
+ }
30907
+ if (action === "drift") {
30908
+ return handleDrift(input);
30909
+ }
30143
30910
  const handler = handlers.get(action);
30144
30911
  if (!handler) {
30145
30912
  return {
@@ -30152,6 +30919,72 @@ function registerDiagnostics(server2) {
30152
30919
  }
30153
30920
  );
30154
30921
  }
30922
+ async function handleToolSearch(input) {
30923
+ const query = input.query ?? "";
30924
+ const limit = input.limit ?? 20;
30925
+ if (!query) {
30926
+ return {
30927
+ content: [{ type: "text", text: JSON.stringify({ error: "query is required for tool_search" }) }],
30928
+ isError: true
30929
+ };
30930
+ }
30931
+ const index = getToolCapabilityIndex();
30932
+ if (!index.isReady()) {
30933
+ return {
30934
+ content: [{
30935
+ type: "text",
30936
+ text: JSON.stringify({
30937
+ error: "Tool capability index not initialized yet. It is built asynchronously at daemon boot.",
30938
+ query
30939
+ })
30940
+ }],
30941
+ isError: true
30942
+ };
30943
+ }
30944
+ const results = await index.search(query, limit);
30945
+ const tools = results.map((r) => ({
30946
+ name: r.name,
30947
+ description: r.description,
30948
+ category: r.category,
30949
+ relevance: Math.round(r.relevance * 1e3) / 1e3
30950
+ }));
30951
+ return {
30952
+ content: [{
30953
+ type: "text",
30954
+ text: JSON.stringify({
30955
+ tools,
30956
+ total_tools: index.toolCount,
30957
+ query
30958
+ }, null, 2)
30959
+ }]
30960
+ };
30961
+ }
30962
+ async function handleDrift(input) {
30963
+ const agentId = input.agent_id;
30964
+ const axesRaw = input.query;
30965
+ let axes;
30966
+ if (axesRaw) {
30967
+ const valid = ["continuity", "consistency", "role-fidelity", "world-model"];
30968
+ const parsed = axesRaw.split(",").map((a) => a.trim()).filter((a) => valid.includes(a));
30969
+ if (parsed.length > 0) axes = parsed;
30970
+ }
30971
+ const results = await runDriftProbes({ agentId, axes });
30972
+ if (results.length === 0) {
30973
+ return {
30974
+ content: [{
30975
+ type: "text",
30976
+ text: JSON.stringify({ error: agentId ? `Agent "${agentId}" not found` : "No agents found" })
30977
+ }],
30978
+ isError: true
30979
+ };
30980
+ }
30981
+ return {
30982
+ content: [{
30983
+ type: "text",
30984
+ text: JSON.stringify(results.length === 1 ? results[0] : results, null, 2)
30985
+ }]
30986
+ };
30987
+ }
30155
30988
 
30156
30989
  // src/mcp/tool-gates.ts
30157
30990
  var TOOL_GATES = {