@webmcp-auto-ui/agent 2.5.21 → 2.5.23

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.21",
3
+ "version": "2.5.23",
4
4
  "description": "LLM agent loop + remote/WASM/local providers + MCP wrapper",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "type": "module",
@@ -4,7 +4,7 @@ import type { ToolLayer } from './tool-layers.js';
4
4
  import type { ProviderTool } from './types.js';
5
5
  import { sanitizeServerName } from './tool-layers.js';
6
6
  import { sanitizeSchemaWithReport } from '@webmcp-auto-ui/core';
7
- import type { JsonSchema } from '@webmcp-auto-ui/core';
7
+ import type { JsonSchema, SchemaPatch } from '@webmcp-auto-ui/core';
8
8
 
9
9
  export interface Diagnostic {
10
10
  severity: 'error' | 'warning';
@@ -22,11 +22,15 @@ export function runDiagnostics(
22
22
  layers: ToolLayer[],
23
23
  tools: ProviderTool[],
24
24
  systemPrompt: string,
25
- schemaOptions?: { sanitize?: boolean; flatten?: boolean; strict?: boolean },
25
+ schemaOptions?: { sanitize?: boolean; flatten?: boolean; strict?: boolean; providerKind?: 'remote' | 'wasm' | 'gemma' | 'local' },
26
26
  /** Original (pre-sanitize) tools — used for check #5 to detect patchable schemas */
27
27
  rawTools?: ProviderTool[],
28
28
  ): Diagnostic[] {
29
29
  const diagnostics: Diagnostic[] = [];
30
+ // Gemma uses a native tool-declaration format that ignores JSON-Schema features
31
+ // like additionalProperties, oneOf/anyOf/$ref, strict mode, etc.
32
+ // Skip schema-schema checks for Gemma — they produce noise without actionable value.
33
+ const skipSchemaChecks = schemaOptions?.providerKind === 'gemma';
30
34
 
31
35
  // 1. Tool name hygiene — check for residual "mcp"/"server" noise in prefixes
32
36
  for (const layer of layers) {
@@ -63,7 +67,9 @@ export function runDiagnostics(
63
67
  }
64
68
 
65
69
  // 3. Schema depth warning for Gemma when flatten is OFF
66
- if (schemaOptions && !schemaOptions.flatten) {
70
+ // Skipped entirely for Gemma provider — its native format ignores schema structure beyond one level,
71
+ // so the "flatten" toggle has no effect there.
72
+ if (!skipSchemaChecks && schemaOptions && !schemaOptions.flatten) {
67
73
  for (const tool of tools) {
68
74
  const schema = tool.input_schema as Record<string, unknown>;
69
75
  if (hasNestedObjects(schema)) {
@@ -105,22 +111,71 @@ export function runDiagnostics(
105
111
 
106
112
  // 5. Strict mode — schemas that were auto-patched
107
113
  // Must run on raw (pre-sanitize) schemas; sanitized tools will never show patches.
108
- const checkTools = rawTools ?? tools;
109
- for (const tool of checkTools) {
110
- const { patches } = sanitizeSchemaWithReport(tool.input_schema as JsonSchema);
111
- if (patches.length > 0) {
112
- diagnostics.push({
113
- severity: 'warning',
114
- title: `Schema patched: ${tool.name}`,
115
- detail: `${patches.length} correction(s) for strict mode: ${patches.map(p => p.path).join(', ')}. additionalProperties: false added automatically.`,
116
- codeFix: `Add "additionalProperties": false to the MCP server schema for ${tool.name}.`,
117
- });
114
+ // Skipped for Gemma it doesn't use additionalProperties/strict mode at all.
115
+ if (!skipSchemaChecks) {
116
+ const checkTools = rawTools ?? tools;
117
+ for (const tool of checkTools) {
118
+ const { patches } = sanitizeSchemaWithReport(tool.input_schema as JsonSchema);
119
+ if (patches.length > 0) {
120
+ const addedPatches = patches.filter((p: SchemaPatch) => p.type === 'additionalProperties');
121
+ const removedPatches = patches.filter((p: SchemaPatch) => p.type === 'removed');
122
+
123
+ // Dedupe paths (with multiplier) for additionalProperties patches
124
+ const addedPathsFmt = formatCounted(addedPatches.map(p => p.path));
125
+ // Dedupe "keyword@path" entries for removed patches
126
+ const removedEntriesFmt = formatCounted(
127
+ removedPatches.map(p => `${p.keyword ?? 'unknown'}@${p.path}`),
128
+ );
129
+ const removedKeywords = Array.from(
130
+ new Set(removedPatches.map(p => p.keyword).filter((k): k is string => !!k)),
131
+ );
132
+
133
+ const detailParts: string[] = [];
134
+ if (addedPatches.length > 0) {
135
+ detailParts.push(
136
+ `${addedPatches.length} correction(s) for strict mode at ${addedPathsFmt}: additionalProperties: false added automatically.`,
137
+ );
138
+ }
139
+ if (removedPatches.length > 0) {
140
+ detailParts.push(
141
+ `${removedPatches.length} keyword(s) removed for strict mode: ${removedEntriesFmt}.`,
142
+ );
143
+ }
144
+
145
+ let codeFix: string;
146
+ if (addedPatches.length > 0 && removedPatches.length === 0) {
147
+ codeFix = `Add "additionalProperties": false to the MCP server schema for ${tool.name}.`;
148
+ } else if (removedPatches.length > 0 && addedPatches.length === 0) {
149
+ codeFix = `Remove unsupported JSON Schema keywords (${removedKeywords.join(', ')}) from the MCP server schema for ${tool.name}, or accept that Claude strict mode will ignore them.`;
150
+ } else {
151
+ codeFix = `Add "additionalProperties": false and remove unsupported keywords (${removedKeywords.join(', ')}) from the MCP server schema for ${tool.name}.`;
152
+ }
153
+
154
+ diagnostics.push({
155
+ severity: 'warning',
156
+ title: `Schema patched: ${tool.name}`,
157
+ detail: detailParts.join(' '),
158
+ codeFix,
159
+ });
160
+ }
118
161
  }
119
162
  }
120
163
 
121
164
  return diagnostics;
122
165
  }
123
166
 
167
+ /**
168
+ * Dedupe a list of entries and format with a count multiplier when > 1.
169
+ * ["a", "a", "b"] → "a ×2, b"
170
+ */
171
+ function formatCounted(entries: string[]): string {
172
+ const counts = new Map<string, number>();
173
+ for (const e of entries) counts.set(e, (counts.get(e) ?? 0) + 1);
174
+ return Array.from(counts.entries())
175
+ .map(([entry, n]) => (n > 1 ? `${entry} ×${n}` : entry))
176
+ .join(', ');
177
+ }
178
+
124
179
  /** Check if a JSON schema has nested object properties (depth > 1) */
125
180
  function hasNestedObjects(schema: Record<string, unknown>): boolean {
126
181
  const props = schema.properties as Record<string, Record<string, unknown>> | undefined;
@@ -112,9 +112,12 @@ export class DiscoveryCache {
112
112
  }
113
113
 
114
114
  case 'get_recipe': {
115
- const name = (params.name ?? '') as string;
116
- const recipe = cache.recipes.find(r => r.name.toLowerCase() === name.toLowerCase());
117
- if (!recipe) return JSON.stringify({ error: `Recipe "${name}" not found` });
115
+ const key = String(params.name ?? params.id ?? '').toLowerCase();
116
+ const recipe = cache.recipes.find(r =>
117
+ (r.name?.toLowerCase() === key) ||
118
+ ((r as Record<string, unknown>).id as string | undefined)?.toLowerCase() === key
119
+ );
120
+ if (!recipe) return JSON.stringify({ error: `Recipe "${key}" not found` });
118
121
  return JSON.stringify(recipe);
119
122
  }
120
123
 
@@ -260,8 +260,6 @@ export class WasmProvider implements LLMProvider {
260
260
  let lastToken = '';
261
261
  let repeatCount = 0;
262
262
  const MAX_REPEATS = 20;
263
- // P2 fix: track if we have a complete tool call to enable early cancellation
264
- let hasCompleteToolCall = false;
265
263
  const TOOL_CALL_MAX_CHARS = 3000;
266
264
 
267
265
  const result = await this.inference.generateResponse(prompt, (partialResult: string, _done: boolean) => {
@@ -297,18 +295,6 @@ export class WasmProvider implements LLMProvider {
297
295
  }
298
296
  }
299
297
 
300
- // Cancel immediately after complete tool call — don't let Gemma hallucinate
301
- if (!hasCompleteToolCall && fullText.includes('<tool_call|>')) {
302
- hasCompleteToolCall = true;
303
- // Check if there's a new tool_call opening after the last closing
304
- const lastEnd = fullText.lastIndexOf('<tool_call|>');
305
- const afterEnd = fullText.slice(lastEnd + '<tool_call|>'.length);
306
- if (!afterEnd.includes('<|tool_call>')) {
307
- // No new tool call — cancel immediately
308
- this.inference?.cancelProcessing();
309
- return;
310
- }
311
- }
312
298
  // Safety: if text grows way too long, force cancel
313
299
  if (fullText.length > TOOL_CALL_MAX_CHARS * 2) {
314
300
  this.inference?.cancelProcessing();