@webmcp-auto-ui/agent 2.5.37 → 2.5.38

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webmcp-auto-ui/agent",
3
- "version": "2.5.37",
3
+ "version": "2.5.38",
4
4
  "description": "LLM agent loop + remote/WASM/local providers + MCP wrapper",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "type": "module",
@@ -340,24 +340,37 @@ schema:
340
340
  title:
341
341
  type: string
342
342
  columns:
343
+ description: Column definitions. Also accepts plain strings ["A","B"]. Alias "headers" is also accepted.
343
344
  type: array
344
345
  items:
345
- type: object
346
- required:
347
- - key
348
- - label
349
- properties:
350
- key:
351
- type: string
352
- label:
353
- type: string
354
- align:
355
- type: string
356
- enum: [left, center, right]
346
+ oneOf:
347
+ - type: string
348
+ - type: object
349
+ required:
350
+ - key
351
+ - label
352
+ properties:
353
+ key:
354
+ type: string
355
+ label:
356
+ type: string
357
+ align:
358
+ type: string
359
+ enum: [left, center, right]
360
+ headers:
361
+ description: Alias for columns. Accepts plain strings ["A","B"] or full column objects.
362
+ type: array
363
+ items:
364
+ oneOf:
365
+ - type: string
366
+ - type: object
357
367
  rows:
368
+ description: Array of row objects OR array of arrays (positionally mapped to columns/headers keys).
358
369
  type: array
359
370
  items:
360
- type: object
371
+ oneOf:
372
+ - type: object
373
+ - type: array
361
374
  ---
362
375
 
363
376
  ## When to use
@@ -365,6 +378,7 @@ Display structured data in a table with column sorting.
365
378
 
366
379
  ## How to use
367
380
  Call widget_display({name: "data-table", params: {columns: [{key:"name",label:"Nom"}], rows: [{name:"Alice"}]}}).
381
+ Rows can also be arrays: {headers:["name","age"], rows:[["Alice",30],["Bob",25]]}.
368
382
  `,
369
383
 
370
384
  // ── timeline ─────────────────────────────────────────────────────────────
@@ -579,8 +593,6 @@ schema:
579
593
  type: array
580
594
  items:
581
595
  type: object
582
- required:
583
- - values
584
596
  properties:
585
597
  label:
586
598
  type: string
@@ -588,6 +600,9 @@ schema:
588
600
  type: array
589
601
  items:
590
602
  type: number
603
+ value:
604
+ type: number
605
+ description: Scalar shorthand — treated as values:[value]. Use for single-point series.
591
606
  color:
592
607
  type: string
593
608
  ---
@@ -597,6 +612,7 @@ Pour des graphiques multi-series (barres, lignes, aires, camembert, donut).
597
612
 
598
613
  ## How to use
599
614
  Call widget_display({name: "chart-rich", params: {type: "bar", labels: ["Q1","Q2"], data: [{label:"Ventes", values:[10,20]}]}}).
615
+ Single-point shorthand: data: [{label:"A", value:5}, {label:"B", value:8}].
600
616
  `,
601
617
 
602
618
  // ── cards ────────────────────────────────────────────────────────────────
@@ -605,13 +621,30 @@ widget: cards
605
621
  description: Card grid (results, records, entities).
606
622
  schema:
607
623
  type: object
608
- required:
609
- - cards
610
624
  properties:
611
625
  title:
612
626
  type: string
613
627
  cards:
614
628
  type: array
629
+ description: List of cards (alias: items)
630
+ items:
631
+ type: object
632
+ required:
633
+ - title
634
+ properties:
635
+ title:
636
+ type: string
637
+ description:
638
+ type: string
639
+ subtitle:
640
+ type: string
641
+ tags:
642
+ type: array
643
+ items:
644
+ type: string
645
+ items:
646
+ type: array
647
+ description: Alias for cards — use either cards or items
615
648
  items:
616
649
  type: object
617
650
  required:
@@ -807,12 +840,11 @@ Call widget_display({name: "carousel", params: {slides: [{src: "https://...", ti
807
840
  // ── stat-card ────────────────────────────────────────────────────────────
808
841
  `---
809
842
  widget: stat-card
810
- description: Enriched KPI with unit, delta, and colored variant (success/warning/error/info).
843
+ description: >
844
+ Enriched KPI card with unit, delta, icon, and colored variant.
845
+ Supports single card OR a grid of N cards via the "items" array.
811
846
  schema:
812
847
  type: object
813
- required:
814
- - label
815
- - value
816
848
  properties:
817
849
  label:
818
850
  type: string
@@ -821,6 +853,11 @@ schema:
821
853
  unit:
822
854
  type: string
823
855
  description: Unite affichee apres la valeur (ex "%", "km")
856
+ icon:
857
+ type: string
858
+ description: >
859
+ Emoji, unicode symbol, or keyword (e.g. "fire", "trending-up", "star").
860
+ Unknown keywords fall back silently to ℹ.
824
861
  delta:
825
862
  type: string
826
863
  description: Variation affichee (ex "+12%")
@@ -832,13 +869,47 @@ schema:
832
869
  variant:
833
870
  type: string
834
871
  enum: [default, success, warning, error, info]
872
+ items:
873
+ type: array
874
+ description: >
875
+ Grid mode: pass an array of stat items to render N cards side by side.
876
+ When present, top-level label/value/icon are ignored.
877
+ items:
878
+ type: object
879
+ required:
880
+ - label
881
+ - value
882
+ properties:
883
+ label:
884
+ type: string
885
+ value:
886
+ type: string
887
+ unit:
888
+ type: string
889
+ icon:
890
+ type: string
891
+ delta:
892
+ type: string
893
+ trend:
894
+ type: string
895
+ enum: [up, down, flat]
896
+ previousValue:
897
+ type: string
898
+ variant:
899
+ type: string
900
+ enum: [default, success, warning, error, info]
835
901
  ---
836
902
 
837
903
  ## When to use
838
- Pour un KPI enrichi avec delta, unite et variante de couleur.
904
+ Pour un KPI enrichi avec delta, unite, icone et variante de couleur.
905
+ Pour plusieurs KPIs cote a cote, utiliser le champ "items".
839
906
 
840
907
  ## How to use
841
- Call widget_display({name: "stat-card", params: {label: "Uptime", value: "99.9", unit: "%", trend: "up", variant: "success"}}).
908
+ Single card:
909
+ Call widget_display({name: "stat-card", params: {label: "Uptime", value: "99.9", unit: "%", trend: "up", variant: "success", icon: "🟢"}}).
910
+
911
+ Grid of cards:
912
+ Call widget_display({name: "stat-card", params: {items: [{label: "CPU", value: "42", unit: "%", icon: "💻", variant: "info"}, {label: "RAM", value: "8", unit: "GB", icon: "🗄", variant: "warning"}]}}).
842
913
  `,
843
914
 
844
915
  // ── grid-data ────────────────────────────────────────────────────────────
@@ -927,6 +998,78 @@ Pour des visualisations custom, animations, ou prototypes interactifs en JS pur.
927
998
 
928
999
  ## How to use
929
1000
  Call widget_display({name: "js-sandbox", params: {code: "document.getElementById('root').innerHTML = '<h1>Hello</h1>'"}}).
1001
+ `,
1002
+
1003
+ // ── map ─────────────────────────────────────────────────────────────────
1004
+ `---
1005
+ widget: map
1006
+ description: Interactive geospatial map (MapLibre GL, light theme) with markers and/or GeoJSON overlay. Auto-fits view to data via Turf bbox.
1007
+ group: rich
1008
+ schema:
1009
+ type: object
1010
+ properties:
1011
+ markers:
1012
+ type: array
1013
+ description: Points to display as map markers. Aliases tolerated per marker — \`lng\` accepted for \`lon\`, \`popup\` accepted for \`label\`.
1014
+ items:
1015
+ type: object
1016
+ required:
1017
+ - lat
1018
+ properties:
1019
+ lat:
1020
+ type: number
1021
+ lon:
1022
+ type: number
1023
+ description: Longitude. Alias \`lng\` also accepted.
1024
+ label:
1025
+ type: string
1026
+ description: Popup text shown when the marker is clicked. Alias \`popup\` also accepted.
1027
+ color:
1028
+ type: string
1029
+ description: CSS color for the marker pin (defaults to blue).
1030
+ geojson:
1031
+ type: object
1032
+ description: GeoJSON Feature or FeatureCollection rendered as fill/line/circle layers.
1033
+ center:
1034
+ description: "Initial center. Either \`[lon, lat]\` array (MapLibre/Turf convention) OR object \`{lat, lon}\` / \`{lat, lng}\`. If omitted, computed from data via Turf bbox."
1035
+ zoom:
1036
+ type: number
1037
+ description: Initial zoom. If omitted, derived by fitting the bbox of the data.
1038
+ height:
1039
+ type: string
1040
+ description: CSS height of the map container (default "400px").
1041
+ cluster:
1042
+ type: boolean
1043
+ description: When true, group nearby markers into clusters (MapLibre native clustering, clusterRadius 50). Click a cluster to zoom in.
1044
+ tileLayers:
1045
+ type: array
1046
+ description: Raster tile overlays drawn on top of the base style (e.g. NASA GIBS imagery).
1047
+ items:
1048
+ type: object
1049
+ required:
1050
+ - url
1051
+ properties:
1052
+ name:
1053
+ type: string
1054
+ description: Optional human-readable layer name.
1055
+ url:
1056
+ type: string
1057
+ description: Tile URL template with \`{z}/{x}/{y}\` placeholders.
1058
+ opacity:
1059
+ type: number
1060
+ description: Layer opacity 0..1 (default 1).
1061
+ required: []
1062
+ ---
1063
+
1064
+ ## When to use
1065
+ Pour visualiser des donnees geospatiales : positions, itineraires, polygones (zones, regions). Choisir cette carte pour points + popups + overlays GeoJSON sur fond clair (Carto Positron).
1066
+
1067
+ ## How to use
1068
+ Call widget_display({name: "map", params: {markers: [{lat: 48.85, lon: 2.35, label: "Paris"}]}}).
1069
+
1070
+ ## Common mistakes
1071
+ - Coordonnees au format [lon, lat] (PAS [lat, lon]) pour le champ \`center\` et pour les Point GeoJSON — convention Turf/MapLibre.
1072
+ - Ne PAS fournir un \`zoom\` sans \`center\` si tu veux l'auto-fit : laisse les deux vides pour que la vue s'adapte automatiquement aux donnees.
930
1073
  `,
931
1074
 
932
1075
  `---
package/src/index.ts CHANGED
@@ -88,7 +88,7 @@ export {
88
88
  } from './ort-version.js';
89
89
 
90
90
  // Trace observer — live visual trace for runAgentLoop
91
- export { createTraceObserver, type TraceObserver, type TraceObserverContext, type RoundTripDetail } from './trace-observer.js';
91
+ export { createTraceObserver, type TraceObserver, type TraceObserverContext, type RoundTripDetail, type WidgetLineage } from './trace-observer.js';
92
92
 
93
93
  // Nano-RAG — context compaction
94
94
  export { ContextRAG, type ContextRAGOptions } from './nano-rag/mod.js';
package/src/loop.ts CHANGED
@@ -12,6 +12,7 @@ import type { ToolLayer, SchemaTransformOptions } from './tool-layers.js';
12
12
  import { buildToolsFromLayers, buildDiscoveryToolsWithAliases, activateServerTools, toProviderTools, sanitizeServerName } from './tool-layers.js';
13
13
  import { buildSystemPromptWithAliases } from './prompts/index.js';
14
14
  import type { DiscoveryCache } from './discovery-cache.js';
15
+ import { DISCOVERY_TOOL_NAMES } from './discovery-cache.js';
15
16
  import { unflattenParams, validateJsonSchema } from '@webmcp-auto-ui/core';
16
17
  import type { JsonSchema } from '@webmcp-auto-ui/core';
17
18
  import { autoRepairParams } from './auto-repair.js';
@@ -22,12 +23,6 @@ export { buildSystemPrompt } from './prompts/index.js';
22
23
 
23
24
  const MAX_RESULT_LEN = 10_000;
24
25
 
25
- /** Tool names (after prefix strip) that indicate discovery/exploration */
26
- const DISCOVERY_TOOL_NAMES = new Set([
27
- 'search_recipes', 'get_recipe', 'list_recipes', 'list_tables', 'describe_table',
28
- 'get_json_schemas', 'get_typescript_types', 'recall',
29
- ]);
30
-
31
26
  function isDiscoveryTool(prefixedName: string): boolean {
32
27
  const match = prefixedName.match(/^.+?_(data|ui)_(.+)$/);
33
28
  if (!match) return false;
@@ -364,14 +359,28 @@ export async function runAgentLoop(
364
359
  }
365
360
 
366
361
  // ── Intercept list_tools / search_tools (local pseudo-tools) ──
367
- // These are read-only discovery operations do NOT activate the server.
362
+ // The agent inspecting a server's tools is a strong signal of intent to use them.
363
+ // Activate the server now so the next iteration can invoke its data tools directly,
364
+ // matching the system-prompt promise that "list_tools → use it directly in STEP 3".
368
365
  if (toolMatch && (toolMatch[3] === 'list_tools' || toolMatch[3] === 'search_tools')) {
369
366
  const [, serverName, token, pseudoTool] = toolMatch;
370
367
  const protocol = tokenToProtocol(token);
371
368
  const layer = (options.layers ?? []).find(l => sanitizeServerName(l.serverName) === serverName && l.protocol === protocol);
372
369
  if (!layer) {
373
370
  result = 'Error: server not found';
374
- } else if (pseudoTool === 'list_tools') {
371
+ } else {
372
+ const serverKey = `${serverName}_${token}`;
373
+ if (!activatedServers.has(serverKey)) {
374
+ try {
375
+ const act = activateServerTools(activeTools, layer, schemaOptions, trace);
376
+ activeTools = act.tools;
377
+ for (const [k, v] of act.pathMaps) localPathMaps.set(k, v);
378
+ activatedServers.add(serverKey);
379
+ } catch (e) {
380
+ trace.push('activate', name, `activation failed for ${serverKey}: ${e instanceof Error ? e.message : String(e)}`, 'error');
381
+ }
382
+ }
383
+ if (pseudoTool === 'list_tools') {
375
384
  const tools = layer.tools.map(t => ({ name: t.name, description: t.description, inputSchema: (t as any).inputSchema }));
376
385
  result = JSON.stringify(tools, null, 2);
377
386
  } else {
@@ -388,6 +397,7 @@ export async function runAgentLoop(
388
397
  result = JSON.stringify(tools, null, 2);
389
398
  }
390
399
  }
400
+ }
391
401
  } else {
392
402
  // ── Normal tool dispatch — activate server on first contact ──
393
403
 
@@ -42,6 +42,19 @@ export interface RoundTripDetail {
42
42
  originRecipe?: string;
43
43
  }
44
44
 
45
+ /** Lineage of a widget mount: tool_calls in the same iteration that produced it. */
46
+ export interface WidgetLineage {
47
+ widgetType: string;
48
+ widgetParams: Record<string, unknown>;
49
+ toolCalls: Array<{
50
+ serverName: string;
51
+ toolName: string;
52
+ args: Record<string, unknown>;
53
+ resultPreview?: string;
54
+ }>;
55
+ originRecipe?: string;
56
+ }
57
+
45
58
  export interface TraceObserver {
46
59
  /** Partial AgentCallbacks to merge into runAgentLoop callbacks. */
47
60
  callbacks: Partial<AgentCallbacks>;
@@ -57,6 +70,8 @@ export interface TraceObserver {
57
70
  getLoadedRecipes: () => Map<string, string>;
58
71
  /** Current recipe context: name of the most recently loaded recipe via get_recipe, or undefined. */
59
72
  getCurrentRecipeContext: () => string | undefined;
73
+ /** Resolve the chain of tool_calls in the same iteration that produced a widget. */
74
+ getWidgetLineage: (blockIdOrTraceNodeId: string) => WidgetLineage | null;
60
75
  }
61
76
 
62
77
  type NodeKind =
@@ -552,11 +567,12 @@ export function createTraceObserver(ctx: TraceObserverContext): TraceObserver {
552
567
  scheduleFlush();
553
568
  },
554
569
 
555
- onWidget: (type: string, _data: Record<string, unknown>, serverName?: string): void => {
570
+ onWidget: (type: string, data: Record<string, unknown>, serverName?: string): void => {
556
571
  const node = addNode('widget', `${serverName ?? '?'}/${type}`, {
557
572
  iterationId: currentIterationId,
558
573
  type,
559
574
  serverName,
575
+ data,
560
576
  });
561
577
  const parent = lastToolCallId ?? currentLlmRespId;
562
578
  if (parent) {
@@ -654,5 +670,66 @@ export function createTraceObserver(ctx: TraceObserverContext): TraceObserver {
654
670
  getCurrentRecipeContext(): string | undefined {
655
671
  return currentRecipeContext;
656
672
  },
673
+ getWidgetLineage(blockIdOrTraceNodeId: string): WidgetLineage | null {
674
+ let widgetNode = nodes.find(
675
+ (n) => n.kind === 'widget' && n.id === blockIdOrTraceNodeId,
676
+ );
677
+ if (!widgetNode) {
678
+ widgetNode = nodes.find((n) => {
679
+ if (n.kind !== 'widget') return false;
680
+ for (const v of Object.values(n.meta)) {
681
+ if (v === blockIdOrTraceNodeId) return true;
682
+ if (v && typeof v === 'object') {
683
+ for (const vv of Object.values(v as Record<string, unknown>)) {
684
+ if (vv === blockIdOrTraceNodeId) return true;
685
+ }
686
+ }
687
+ }
688
+ return false;
689
+ });
690
+ }
691
+ if (!widgetNode) return null;
692
+ const iter = iterationAncestor(widgetNode);
693
+ if (!iter) {
694
+ return {
695
+ widgetType: (widgetNode.meta.type as string) ?? '',
696
+ widgetParams: ((widgetNode.meta.data as Record<string, unknown>) ?? {}) as Record<string, unknown>,
697
+ toolCalls: [],
698
+ };
699
+ }
700
+ const callNodes = nodes
701
+ .filter(
702
+ (n) =>
703
+ n.kind === 'tool_call' &&
704
+ n.meta.iterationId === iter.id &&
705
+ n.startMs <= widgetNode!.startMs,
706
+ )
707
+ .sort((a, b) => a.startMs - b.startMs);
708
+ const toolCalls: WidgetLineage['toolCalls'] = [];
709
+ let originRecipe: string | undefined;
710
+ for (const c of callNodes) {
711
+ const detail = nodeDetails.get(c.id);
712
+ const toolName = detail?.toolName ?? '';
713
+ const serverName = (c.meta.serverName as string | undefined)
714
+ ?? (toolName.includes('_') ? toolName.split('_')[0]! : '');
715
+ const args = ((c.meta.args as Record<string, unknown>) ?? {}) as Record<string, unknown>;
716
+ let resultPreview: string | undefined;
717
+ if (detail?.toolResult !== undefined) {
718
+ resultPreview = detail.toolResult.slice(0, 4096);
719
+ }
720
+ toolCalls.push({ serverName, toolName, args, resultPreview });
721
+ if (toolName === 'get_recipe' || toolName.endsWith('_get_recipe')) {
722
+ const r = (args.recipe_name ?? args.name ?? args.id) as unknown;
723
+ if (typeof r === 'string') originRecipe = r;
724
+ }
725
+ }
726
+ const lineage: WidgetLineage = {
727
+ widgetType: (widgetNode.meta.type as string) ?? '',
728
+ widgetParams: ((widgetNode.meta.data as Record<string, unknown>) ?? {}) as Record<string, unknown>,
729
+ toolCalls,
730
+ };
731
+ if (originRecipe) lineage.originRecipe = originRecipe;
732
+ return lineage;
733
+ },
657
734
  };
658
735
  }