@webmcp-auto-ui/agent 2.5.25 → 2.5.27

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 (73) hide show
  1. package/package.json +1 -1
  2. package/src/autoui-server.ts +44 -0
  3. package/src/diagnostics.ts +6 -6
  4. package/src/discovery-cache.ts +17 -3
  5. package/src/index.ts +18 -4
  6. package/src/loop.ts +31 -34
  7. package/src/notebook-widgets/compact.ts +312 -0
  8. package/src/notebook-widgets/document.ts +372 -0
  9. package/src/notebook-widgets/editorial.ts +348 -0
  10. package/src/notebook-widgets/recipes/compact.md +104 -0
  11. package/src/notebook-widgets/recipes/document.md +100 -0
  12. package/src/notebook-widgets/recipes/editorial.md +104 -0
  13. package/src/notebook-widgets/recipes/workspace.md +94 -0
  14. package/src/notebook-widgets/shared.ts +1064 -0
  15. package/src/notebook-widgets/workspace.ts +328 -0
  16. package/src/prompts/claude-prompt-builder.ts +81 -0
  17. package/src/prompts/gemma4-prompt-builder.ts +205 -0
  18. package/src/prompts/index.ts +55 -0
  19. package/src/prompts/mistral-prompt-builder.ts +90 -0
  20. package/src/prompts/qwen-prompt-builder.ts +90 -0
  21. package/src/prompts/tool-call-parsers.ts +322 -0
  22. package/src/prompts/tool-refs.ts +196 -0
  23. package/src/providers/factory.ts +20 -3
  24. package/src/providers/transformers-models.ts +143 -0
  25. package/src/providers/transformers-serialize.ts +81 -0
  26. package/src/providers/transformers.ts +329 -0
  27. package/src/providers/transformers.worker.ts +667 -0
  28. package/src/providers/wasm.ts +150 -510
  29. package/src/recipes/_generated.ts +515 -0
  30. package/src/recipes/canary-data.md +50 -0
  31. package/src/recipes/canary-display.md +99 -0
  32. package/src/recipes/canary-middle.md +32 -0
  33. package/src/recipes/hackathon-assemblee-nationale.md +111 -0
  34. package/src/recipes/hummingbird-data.md +32 -0
  35. package/src/recipes/hummingbird-display.md +36 -0
  36. package/src/recipes/hummingbird-middle.md +18 -0
  37. package/src/recipes/notebook-playbook.md +129 -0
  38. package/src/tool-layers.ts +33 -157
  39. package/src/trace-observer.ts +669 -0
  40. package/src/types.ts +20 -5
  41. package/src/util/opfs-cache.ts +265 -0
  42. package/tests/gemma-prompt.test.ts +472 -0
  43. package/tests/loop.test.ts +5 -5
  44. package/tests/transformers-serialize.test.ts +103 -0
  45. package/src/providers/gemma.worker.legacy.ts +0 -123
  46. package/src/providers/litert.worker.ts +0 -294
  47. package/src/recipes/widgets/actions.md +0 -28
  48. package/src/recipes/widgets/alert.md +0 -27
  49. package/src/recipes/widgets/cards.md +0 -41
  50. package/src/recipes/widgets/carousel.md +0 -39
  51. package/src/recipes/widgets/chart-rich.md +0 -51
  52. package/src/recipes/widgets/chart.md +0 -32
  53. package/src/recipes/widgets/code.md +0 -21
  54. package/src/recipes/widgets/d3.md +0 -36
  55. package/src/recipes/widgets/data-table.md +0 -46
  56. package/src/recipes/widgets/gallery.md +0 -39
  57. package/src/recipes/widgets/grid-data.md +0 -57
  58. package/src/recipes/widgets/hemicycle.md +0 -43
  59. package/src/recipes/widgets/js-sandbox.md +0 -32
  60. package/src/recipes/widgets/json-viewer.md +0 -27
  61. package/src/recipes/widgets/kv.md +0 -31
  62. package/src/recipes/widgets/list.md +0 -24
  63. package/src/recipes/widgets/log.md +0 -39
  64. package/src/recipes/widgets/map.md +0 -49
  65. package/src/recipes/widgets/profile.md +0 -49
  66. package/src/recipes/widgets/recipe-browser.md +0 -102
  67. package/src/recipes/widgets/sankey.md +0 -54
  68. package/src/recipes/widgets/stat-card.md +0 -43
  69. package/src/recipes/widgets/stat.md +0 -35
  70. package/src/recipes/widgets/tags.md +0 -30
  71. package/src/recipes/widgets/text.md +0 -19
  72. package/src/recipes/widgets/timeline.md +0 -38
  73. package/src/recipes/widgets/trombinoscope.md +0 -39
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webmcp-auto-ui/agent",
3
- "version": "2.5.25",
3
+ "version": "2.5.27",
4
4
  "description": "LLM agent loop + remote/WASM/local providers + MCP wrapper",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "type": "module",
@@ -3,6 +3,23 @@
3
3
  // ---------------------------------------------------------------------------
4
4
 
5
5
  import { createWebMcpServer, parseFrontmatter } from '@webmcp-auto-ui/core';
6
+ import { RAW_RECIPES } from './recipes/_generated.js';
7
+
8
+ // Notebook widget recipes (vanilla renderers)
9
+ // @ts-ignore — Vite raw imports, not resolved by tsc
10
+ import compactRecipe from './notebook-widgets/recipes/compact.md?raw';
11
+ // @ts-ignore
12
+ import workspaceRecipe from './notebook-widgets/recipes/workspace.md?raw';
13
+ // @ts-ignore
14
+ import documentRecipe from './notebook-widgets/recipes/document.md?raw';
15
+ // @ts-ignore
16
+ import editorialRecipe from './notebook-widgets/recipes/editorial.md?raw';
17
+
18
+ // Notebook widget renderers (vanilla JS)
19
+ import { render as renderCompact } from './notebook-widgets/compact.js';
20
+ import { render as renderWorkspace } from './notebook-widgets/workspace.js';
21
+ import { render as renderDocument } from './notebook-widgets/document.js';
22
+ import { render as renderEditorial } from './notebook-widgets/editorial.js';
6
23
 
7
24
  // ---------------------------------------------------------------------------
8
25
  // Inline recipes (frontmatter + body)
@@ -1007,6 +1024,33 @@ for (const recipe of RECIPES) {
1007
1024
  autoui.registerWidget(recipe, undefined);
1008
1025
  }
1009
1026
 
1027
+ // Notebook widgets — vanilla renderers (resolved via WidgetRenderer vanilla path)
1028
+ const NOTEBOOK_WIDGETS: Array<[string, (container: HTMLElement, data: any) => any]> = [
1029
+ [compactRecipe as string, renderCompact],
1030
+ [workspaceRecipe as string, renderWorkspace],
1031
+ [documentRecipe as string, renderDocument],
1032
+ [editorialRecipe as string, renderEditorial],
1033
+ ];
1034
+ for (const [recipe, renderer] of NOTEBOOK_WIDGETS) {
1035
+ autoui.registerWidget(recipe, renderer as any);
1036
+ }
1037
+
1038
+ // Register flow recipes (multi-step procedures) from the global recipe registry
1039
+ // that declare this server (autoui) in their frontmatter.
1040
+ for (const [key, rawMd] of Object.entries(RAW_RECIPES)) {
1041
+ const { frontmatter } = parseFrontmatter(rawMd);
1042
+ const servers = (Array.isArray(frontmatter.servers) ? frontmatter.servers : []) as string[];
1043
+ if (servers.map((s) => s.toLowerCase()).includes('autoui')) {
1044
+ try {
1045
+ autoui.registerRecipe(rawMd);
1046
+ } catch (e) {
1047
+ // Skip malformed recipes (missing id/name) — log for debugging
1048
+ // eslint-disable-next-line no-console
1049
+ console.warn(`[autoui] Failed to register flow recipe "${key}":`, e);
1050
+ }
1051
+ }
1052
+ }
1053
+
1010
1054
  // Expose recipe summaries to the UI browser
1011
1055
  const parsedRecipes = RECIPES.map((md) => {
1012
1056
  const { frontmatter, body } = parseFrontmatter(md);
@@ -2,7 +2,7 @@
2
2
 
3
3
  import type { ToolLayer } from './tool-layers.js';
4
4
  import type { ProviderTool } from './types.js';
5
- import { sanitizeServerName } from './tool-layers.js';
5
+ import { sanitizeServerName, protocolToken } from './tool-layers.js';
6
6
  import { sanitizeSchemaWithReport } from '@webmcp-auto-ui/core';
7
7
  import type { JsonSchema, SchemaPatch } from '@webmcp-auto-ui/core';
8
8
 
@@ -41,8 +41,8 @@ export function runDiagnostics(
41
41
  diagnostics.push({
42
42
  severity: 'error',
43
43
  title: `Bruit dans le prefixe "${prefix}"`,
44
- detail: `Le serveur "${layer.serverName}" produit un prefixe contenant "${noise.join(', ')}". Les outils auront des noms ambigus comme ${prefix}_mcp_list_recipes.`,
45
- quickFix: `Ajoutez au system prompt:\n"Note: le serveur ${layer.serverName} utilise le prefixe '${prefix}'. Utilisez ${prefix}_mcp_<tool> ou ${prefix}_webmcp_<tool>."`,
44
+ detail: `Le serveur "${layer.serverName}" produit un prefixe contenant "${noise.join(', ')}". Les outils auront des noms ambigus comme ${prefix}_data_list_recipes.`,
45
+ quickFix: `Ajoutez au system prompt:\n"Note: le serveur ${layer.serverName} utilise le prefixe '${prefix}'. Utilisez ${prefix}_data_<tool> ou ${prefix}_ui_<tool>."`,
46
46
  codeFix: `Renommer le serveur MCP cote serveur pour ne pas inclure "mcp"/"server" dans le nom.`,
47
47
  });
48
48
  }
@@ -88,14 +88,14 @@ export function runDiagnostics(
88
88
  // Include discovery tools (search_tools, list_tools) which are generated dynamically
89
89
  const toolNames = new Set(tools.map(t => t.name));
90
90
  for (const layer of layers) {
91
- const prefix = `${sanitizeServerName(layer.serverName)}_${layer.protocol}_`;
91
+ const prefix = `${sanitizeServerName(layer.serverName)}_${protocolToken(layer.protocol)}_`;
92
92
  toolNames.add(`${prefix}search_tools`);
93
93
  toolNames.add(`${prefix}list_tools`);
94
94
  toolNames.add(`${prefix}search_recipes`);
95
95
  toolNames.add(`${prefix}list_recipes`);
96
96
  toolNames.add(`${prefix}get_recipe`);
97
97
  }
98
- const toolPattern = /\b([a-z][a-z0-9_]{2,})_(mcp|webmcp)_([a-z][a-z0-9_]+)\b/g;
98
+ const toolPattern = /\b([a-z][a-z0-9_]{2,})_(data|ui)_([a-z][a-z0-9_]+)\b/g;
99
99
  let match;
100
100
  while ((match = toolPattern.exec(systemPrompt)) !== null) {
101
101
  const fullName = match[0];
@@ -112,7 +112,7 @@ export function runDiagnostics(
112
112
  // 5. Strict mode — schemas that were auto-patched
113
113
  // Must run on raw (pre-sanitize) schemas; sanitized tools will never show patches.
114
114
  // Skipped for Gemma — it doesn't use additionalProperties/strict mode at all.
115
- if (!skipSchemaChecks) {
115
+ if (!skipSchemaChecks && schemaOptions?.strict) {
116
116
  const checkTools = rawTools ?? tools;
117
117
  for (const tool of checkTools) {
118
118
  const { patches } = sanitizeSchemaWithReport(tool.input_schema as JsonSchema);
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
  import type { ProviderTool } from './types.js';
8
+ import type { PipelineTrace } from './pipeline-trace.js';
8
9
 
9
10
  /** Tool names that are resolved locally from cache — hidden from user-facing browsers. */
10
11
  export const DISCOVERY_TOOL_NAMES = new Set(['list_recipes', 'search_recipes', 'get_recipe', 'list_tools', 'search_tools']);
@@ -100,13 +101,18 @@ export class DiscoveryCache {
100
101
  serverPrefix: string,
101
102
  realToolName: string,
102
103
  params: Record<string, unknown>,
104
+ trace?: PipelineTrace,
103
105
  ): string | null {
104
106
  const cache = this.servers.get(serverPrefix);
105
107
  if (!cache) return null;
106
108
 
107
109
  switch (realToolName) {
108
110
  case 'search_recipes': {
109
- const query = ((params.query ?? '') as string).toLowerCase();
111
+ const rawQuery = params.query ?? params.q ?? params.keyword ?? params.search;
112
+ const query = String(rawQuery ?? '').toLowerCase();
113
+ if (rawQuery === undefined && Object.keys(params).length > 0) {
114
+ trace?.push('discovery', realToolName, `unknown param keys: ${Object.keys(params).join(',')} — expected query/q/keyword/search`, 'warn');
115
+ }
110
116
  const results = query
111
117
  ? cache.recipes.filter(r =>
112
118
  r.name.toLowerCase().includes(query) ||
@@ -121,7 +127,11 @@ export class DiscoveryCache {
121
127
  }
122
128
 
123
129
  case 'get_recipe': {
124
- const key = String(params.name ?? params.id ?? '').toLowerCase();
130
+ const rawKey = params.name ?? params.id ?? params.recipe_id ?? params.key;
131
+ const key = String(rawKey ?? '').toLowerCase();
132
+ if (rawKey === undefined && Object.keys(params).length > 0) {
133
+ trace?.push('discovery', realToolName, `unknown param keys: ${Object.keys(params).join(',')} — expected name/id/recipe_id/key`, 'warn');
134
+ }
125
135
  const recipe = cache.recipes.find(r =>
126
136
  (r.name?.toLowerCase() === key) ||
127
137
  ((r as Record<string, unknown>).id as string | undefined)?.toLowerCase() === key
@@ -131,7 +141,11 @@ export class DiscoveryCache {
131
141
  }
132
142
 
133
143
  case 'search_tools': {
134
- const query = ((params.query ?? '') as string).toLowerCase();
144
+ const rawQuery = params.query ?? params.q ?? params.keyword ?? params.search;
145
+ const query = String(rawQuery ?? '').toLowerCase();
146
+ if (rawQuery === undefined && Object.keys(params).length > 0) {
147
+ trace?.push('discovery', realToolName, `unknown param keys: ${Object.keys(params).join(',')} — expected query/q/keyword/search`, 'warn');
148
+ }
135
149
  const results = query
136
150
  ? cache.tools.filter(t =>
137
151
  t.name.toLowerCase().includes(query) ||
package/src/index.ts CHANGED
@@ -5,6 +5,16 @@ export { RemoteLLMProvider } from './providers/remote.js';
5
5
  export type { RemoteLLMProviderOptions } from './providers/remote.js';
6
6
  export { WasmProvider } from './providers/wasm.js';
7
7
  export type { WasmProviderOptions, WasmStatus } from './providers/wasm.js';
8
+ export { TransformersProvider } from './providers/transformers.js';
9
+ export type { TransformersProviderOptions, TransformersStatus } from './providers/transformers.js';
10
+ export { TRANSFORMERS_MODELS, getTransformersModel, listTransformersModels } from './providers/transformers-models.js';
11
+ export type { TransformersModelEntry, TransformersFamily, ToolCallFormat } from './providers/transformers-models.js';
12
+ export { parseToolCalls } from './prompts/tool-call-parsers.js';
13
+ export type { ParseResult } from './prompts/tool-call-parsers.js';
14
+ export { loadOrDownloadModel, clearModelCache } from './util/opfs-cache.js';
15
+ export type { ModelFileSpec, CacheProgress } from './util/opfs-cache.js';
16
+ export { buildGemmaPrompt } from './prompts/index.js';
17
+ export type { BuildGemmaPromptInput } from './prompts/index.js';
8
18
  export { LocalLLMProvider } from './providers/local.js';
9
19
  export type { LocalLLMProviderOptions, LocalBackend } from './providers/local.js';
10
20
  export { createProvider } from './providers/factory.js';
@@ -18,15 +28,16 @@ export type { GemmaProviderOptions, GemmaStatus } from './providers/gemma.js';
18
28
 
19
29
  // Agent loop
20
30
  export { runAgentLoop, toProviderTools, fromMcpTools, trimConversationHistory } from './loop.js';
21
- export { buildSystemPrompt } from './tool-layers.js';
31
+ export { buildSystemPrompt, buildSystemPromptWithAliases } from './prompts/index.js';
32
+ export type { SystemPromptResult } from './prompts/index.js';
22
33
  export type { AgentLoopOptions } from './loop.js';
23
34
 
24
35
  // autoui — built-in WebMCP server
25
36
  export { autoui, NATIVE_WIDGET_NAMES } from './autoui-server.js';
26
37
 
27
38
  // Tool layers
28
- export { buildToolsFromLayers, buildDiscoveryTools, buildDiscoveryToolsWithAliases, activateServerTools, resolveCanonicalTools, toolAliasMap, buildSystemPromptWithAliases, flattenPathMaps, buildDiscoveryCache } from './tool-layers.js';
29
- export type { ToolLayer, McpLayer, WebMcpLayer, SystemPromptResult, DiscoveryToolsResult, SchemaTransformOptions, BuildToolsResult } from './tool-layers.js';
39
+ export { buildToolsFromLayers, buildDiscoveryTools, buildDiscoveryToolsWithAliases, activateServerTools, resolveCanonicalTools, toolAliasMap, flattenPathMaps, buildDiscoveryCache } from './tool-layers.js';
40
+ export type { ToolLayer, McpLayer, WebMcpLayer, DiscoveryToolsResult, SchemaTransformOptions, BuildToolsResult, ProviderKind } from './tool-layers.js';
30
41
 
31
42
  // Discovery cache
32
43
  export { DiscoveryCache, DISCOVERY_TOOL_NAMES } from './discovery-cache.js';
@@ -63,12 +74,15 @@ export type { RepairResult } from './auto-repair.js';
63
74
  // Pipeline trace
64
75
  export { PipelineTrace, type TraceEntry } from './pipeline-trace.js';
65
76
 
77
+ // Trace observer — live visual trace for runAgentLoop
78
+ export { createTraceObserver, type TraceObserver, type TraceObserverContext, type RoundTripDetail } from './trace-observer.js';
79
+
66
80
  // Nano-RAG — context compaction
67
81
  export { ContextRAG, type ContextRAGOptions } from './nano-rag/mod.js';
68
82
 
69
83
  // Types
70
84
  export type {
71
- RemoteModelId, WasmModelId, LLMId, ModelId,
85
+ RemoteModelId, WasmModelId, TransformersModelId, LLMId, ModelId,
72
86
  ChatMessage, ContentBlock, McpToolDef, ProviderTool,
73
87
  LLMProvider, LLMResponse, ToolCall, AgentMetrics, AgentResult, AgentCallbacks,
74
88
  Recipe, McpRecipe,
package/src/loop.ts CHANGED
@@ -9,7 +9,8 @@ import type {
9
9
  LLMProvider, ProviderTool, McpToolDef, AgentCallbacks,
10
10
  } from './types.js';
11
11
  import type { ToolLayer, SchemaTransformOptions } from './tool-layers.js';
12
- import { buildToolsFromLayers, buildSystemPromptWithAliases, buildDiscoveryToolsWithAliases, buildSystemPrompt, activateServerTools, toProviderTools, sanitizeServerName, flattenPathMaps } from './tool-layers.js';
12
+ import { buildToolsFromLayers, buildDiscoveryToolsWithAliases, activateServerTools, toProviderTools, sanitizeServerName, flattenPathMaps } from './tool-layers.js';
13
+ import { buildSystemPromptWithAliases, buildSystemPrompt } from './prompts/index.js';
13
14
  import type { DiscoveryCache } from './discovery-cache.js';
14
15
  import { unflattenParams, validateJsonSchema } from '@webmcp-auto-ui/core';
15
16
  import type { JsonSchema } from '@webmcp-auto-ui/core';
@@ -17,7 +18,7 @@ import { autoRepairParams } from './auto-repair.js';
17
18
  import { PipelineTrace } from './pipeline-trace.js';
18
19
 
19
20
  // Re-export buildSystemPrompt for backward compat
20
- export { buildSystemPrompt } from './tool-layers.js';
21
+ export { buildSystemPrompt } from './prompts/index.js';
21
22
 
22
23
  const MAX_RESULT_LEN = 10_000;
23
24
 
@@ -28,11 +29,18 @@ const DISCOVERY_TOOL_NAMES = new Set([
28
29
  ]);
29
30
 
30
31
  function isDiscoveryTool(prefixedName: string): boolean {
31
- const match = prefixedName.match(/^.+?_(mcp|webmcp)_(.+)$/);
32
+ const match = prefixedName.match(/^.+?_(data|ui)_(.+)$/);
32
33
  if (!match) return false;
33
34
  return DISCOVERY_TOOL_NAMES.has(match[2]);
34
35
  }
35
36
 
37
+ /** Map a tool-name token (data/ui) back to its protocol discriminator (mcp/webmcp). */
38
+ function tokenToProtocol(token: string): 'mcp' | 'webmcp' | null {
39
+ if (token === 'data') return 'mcp';
40
+ if (token === 'ui') return 'webmcp';
41
+ return null;
42
+ }
43
+
36
44
  /**
37
45
  * Compress old tool_result blocks in conversation history.
38
46
  * IMPORTANT: mutates messages in-place intentionally to reduce memory usage.
@@ -91,9 +99,6 @@ export interface AgentLoopOptions {
91
99
  layers?: ToolLayer[];
92
100
  maxIterations?: number;
93
101
  maxTokens?: number;
94
- maxTools?: number;
95
- /** WASM-only: cap on conversation messages sent to the model. Default: derived from contextSize. */
96
- maxMessages?: number;
97
102
  temperature?: number;
98
103
  topK?: number;
99
104
  cacheEnabled?: boolean;
@@ -118,7 +123,7 @@ export interface AgentLoopOptions {
118
123
  }
119
124
 
120
125
  export async function runAgentLoop(
121
- userMessage: string,
126
+ userMessage: string | ContentBlock[],
122
127
  options: AgentLoopOptions
123
128
  ): Promise<AgentResult> {
124
129
  const {
@@ -126,8 +131,6 @@ export async function runAgentLoop(
126
131
  provider,
127
132
  maxIterations = 5,
128
133
  maxTokens,
129
- maxTools,
130
- maxMessages,
131
134
  temperature,
132
135
  topK,
133
136
  cacheEnabled = true,
@@ -182,14 +185,14 @@ export async function runAgentLoop(
182
185
  if (options.systemPrompt) {
183
186
  baseSystemPrompt = options.systemPrompt;
184
187
  } else {
185
- const sp = buildSystemPromptWithAliases(options.layers ?? []);
188
+ const sp = buildSystemPromptWithAliases(options.layers ?? [], {
189
+ providerKind: provider.promptKind ?? 'generic',
190
+ });
186
191
  baseSystemPrompt = sp.prompt;
187
192
  for (const [k, v] of sp.aliasMap) localAliasMap.set(k, v);
188
193
  }
189
194
 
190
- const systemPrompt = maxTokens
191
- ? `${baseSystemPrompt}\n\nIMPORTANT: Limit your responses to ${maxTokens} tokens.`
192
- : baseSystemPrompt;
195
+ const systemPrompt = baseSystemPrompt;
193
196
 
194
197
  const messages: ChatMessage[] = [
195
198
  ...initialMessages.map(m => ({
@@ -207,7 +210,6 @@ export async function runAgentLoop(
207
210
  const allToolCalls: ToolCall[] = [];
208
211
  let lastText = '';
209
212
  let finishedNormally = false;
210
- let discoveryPhase = false;
211
213
  let iterationsWithoutRender = 0;
212
214
  let nudgedOnce = false;
213
215
  let hasRendered = false;
@@ -267,7 +269,7 @@ export async function runAgentLoop(
267
269
  const t0 = performance.now();
268
270
  let streamingText = '';
269
271
  const response = await provider.chat(messages, iterationTools, {
270
- signal, cacheEnabled, system: iterationSystemPrompt, maxTokens, maxTools, maxMessages, temperature, topK,
272
+ signal, cacheEnabled, system: iterationSystemPrompt, maxTokens, temperature, topK,
271
273
  onToken: callbacks.onToken ? (token) => {
272
274
  callbacks.onToken!(token);
273
275
  streamingText += token;
@@ -318,14 +320,6 @@ export async function runAgentLoop(
318
320
  const toolResults: ContentBlock[] = [];
319
321
  for (const block of toolBlocks) {
320
322
  const call: ToolCall = { id: block.id, name: block.name, args: block.input };
321
- const wasDiscovering = discoveryPhase;
322
- if (isDiscoveryTool(block.name)) {
323
- discoveryPhase = true;
324
- call.guided = false;
325
- } else {
326
- call.guided = wasDiscovering;
327
- discoveryPhase = false;
328
- }
329
323
  const t1 = performance.now();
330
324
 
331
325
  try {
@@ -335,12 +329,12 @@ export async function runAgentLoop(
335
329
  // Resolve alias (canonical name → real tool name) if needed
336
330
  const resolvedName = localAliasMap.get(name) ?? name;
337
331
 
338
- // Parse tool name: {serverName}_{protocol}_{toolName}
339
- const toolMatch = resolvedName.match(/^(.+?)_(mcp|webmcp)_(.+)$/);
332
+ // Parse tool name: {serverName}_{token}_{toolName} where token ∈ {data, ui}
333
+ const toolMatch = resolvedName.match(/^(.+?)_(data|ui)_(.+)$/);
340
334
 
341
335
  // ── Discovery cache — resolve search/list/get locally if cached ──
342
336
  if (discoveryCache && toolMatch) {
343
- const cached = discoveryCache.resolve(toolMatch[1], toolMatch[3], block.input as Record<string, unknown>);
337
+ const cached = discoveryCache.resolve(toolMatch[1], toolMatch[3], block.input as Record<string, unknown>, trace);
344
338
  if (cached !== null) {
345
339
  result = cached;
346
340
  // Store + push result, then continue to next tool block
@@ -358,7 +352,8 @@ export async function runAgentLoop(
358
352
  // ── Intercept list_tools / search_tools (local pseudo-tools) ──
359
353
  // These are read-only discovery operations — do NOT activate the server.
360
354
  if (toolMatch && (toolMatch[3] === 'list_tools' || toolMatch[3] === 'search_tools')) {
361
- const [, serverName, protocol, pseudoTool] = toolMatch;
355
+ const [, serverName, token, pseudoTool] = toolMatch;
356
+ const protocol = tokenToProtocol(token);
362
357
  const layer = (options.layers ?? []).find(l => sanitizeServerName(l.serverName) === serverName && l.protocol === protocol);
363
358
  if (!layer) {
364
359
  result = 'Error: server not found';
@@ -384,10 +379,11 @@ export async function runAgentLoop(
384
379
 
385
380
  // Parse tool name to extract server — activate on first contact
386
381
  {
387
- const activateMatch = resolvedName.match(/^(.+?)_(mcp|webmcp)_(.+)$/);
382
+ const activateMatch = resolvedName.match(/^(.+?)_(data|ui)_(.+)$/);
388
383
  if (activateMatch) {
389
- const [, serverName, protocol] = activateMatch;
390
- const serverKey = `${serverName}_${protocol}`;
384
+ const [, serverName, token] = activateMatch;
385
+ const protocol = tokenToProtocol(token);
386
+ const serverKey = `${serverName}_${token}`;
391
387
  if (!activatedServers.has(serverKey)) {
392
388
  activatedServers.add(serverKey);
393
389
  const layer = (options.layers ?? []).find(l => sanitizeServerName(l.serverName) === serverName && l.protocol === protocol);
@@ -398,10 +394,11 @@ export async function runAgentLoop(
398
394
  }
399
395
  }
400
396
  if (!toolMatch) {
401
- trace.push('dispatch', name, `unknown tool format, expected {source}_{protocol}_{tool}`, 'error');
402
- result = `Error: unknown tool format "${name}". Expected {source}_{protocol}_{tool}.`;
397
+ trace.push('dispatch', name, `unknown tool format, expected {source}_{token}_{tool} where token in {data,ui}`, 'error');
398
+ result = `Error: unknown tool format "${name}". Expected {source}_{token}_{tool} where token in {data,ui}.`;
403
399
  } else {
404
- const [, serverName, protocol, realToolName] = toolMatch;
400
+ const [, serverName, token, realToolName] = toolMatch;
401
+ const protocol = tokenToProtocol(token);
405
402
 
406
403
  // Auto-repair + validate params before dispatch
407
404
  let toolInput = block.input as Record<string, unknown>;
@@ -587,7 +584,7 @@ export async function runAgentLoop(
587
584
 
588
585
  // Track iterations without render — widget_display means a render happened
589
586
  const renderedThisIteration = toolBlocks.some(b => {
590
- const match = b.name.match(/^.+?_(mcp|webmcp)_(.+)$/);
587
+ const match = b.name.match(/^.+?_(data|ui)_(.+)$/);
591
588
  return match && match[2] === 'widget_display';
592
589
  });
593
590
  if (renderedThisIteration) {