@tooluminati/diagnostics 0.1.0

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 (54) hide show
  1. package/LICENSE +21 -0
  2. package/dist/action-availability.d.ts +24 -0
  3. package/dist/action-availability.d.ts.map +1 -0
  4. package/dist/action-availability.js +44 -0
  5. package/dist/app-info.d.ts +16 -0
  6. package/dist/app-info.d.ts.map +1 -0
  7. package/dist/app-info.js +28 -0
  8. package/dist/errors.d.ts +35 -0
  9. package/dist/errors.d.ts.map +1 -0
  10. package/dist/errors.js +91 -0
  11. package/dist/feature-flags.d.ts +10 -0
  12. package/dist/feature-flags.d.ts.map +1 -0
  13. package/dist/feature-flags.js +13 -0
  14. package/dist/hydration.d.ts +11 -0
  15. package/dist/hydration.d.ts.map +1 -0
  16. package/dist/hydration.js +26 -0
  17. package/dist/index.cjs +1199 -0
  18. package/dist/index.d.ts +15 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +1141 -0
  21. package/dist/mounted-forms.d.ts +26 -0
  22. package/dist/mounted-forms.d.ts.map +1 -0
  23. package/dist/mounted-forms.js +32 -0
  24. package/dist/timeline.d.ts +99 -0
  25. package/dist/timeline.d.ts.map +1 -0
  26. package/dist/timeline.js +302 -0
  27. package/dist/timeline.test.d.ts +2 -0
  28. package/dist/timeline.test.d.ts.map +1 -0
  29. package/dist/timeline.test.js +94 -0
  30. package/dist/troubleshooting-guidance.d.ts +15 -0
  31. package/dist/troubleshooting-guidance.d.ts.map +1 -0
  32. package/dist/troubleshooting-guidance.js +130 -0
  33. package/dist/troubleshooting-guidance.test.d.ts +2 -0
  34. package/dist/troubleshooting-guidance.test.d.ts.map +1 -0
  35. package/dist/troubleshooting-guidance.test.js +27 -0
  36. package/dist/troubleshooting-panel.d.ts +77 -0
  37. package/dist/troubleshooting-panel.d.ts.map +1 -0
  38. package/dist/troubleshooting-panel.js +243 -0
  39. package/dist/troubleshooting-panel.test.d.ts +2 -0
  40. package/dist/troubleshooting-panel.test.d.ts.map +1 -0
  41. package/dist/troubleshooting-panel.test.js +105 -0
  42. package/dist/webmcp-environment.d.ts +23 -0
  43. package/dist/webmcp-environment.d.ts.map +1 -0
  44. package/dist/webmcp-environment.js +156 -0
  45. package/dist/webmcp-environment.test.d.ts +2 -0
  46. package/dist/webmcp-environment.test.d.ts.map +1 -0
  47. package/dist/webmcp-environment.test.js +49 -0
  48. package/dist/workflow-blockers.d.ts +53 -0
  49. package/dist/workflow-blockers.d.ts.map +1 -0
  50. package/dist/workflow-blockers.js +62 -0
  51. package/dist/workflow-blockers.test.d.ts +2 -0
  52. package/dist/workflow-blockers.test.d.ts.map +1 -0
  53. package/dist/workflow-blockers.test.js +41 -0
  54. package/package.json +50 -0
package/dist/index.js ADDED
@@ -0,0 +1,1141 @@
1
+ // src/action-availability.ts
2
+ function createActionAvailabilityTool(provider, name = "why_is_action_unavailable") {
3
+ return {
4
+ name,
5
+ description: "Explains whether a named UI action is available and returns validation, permission, or domain-rule blockers.",
6
+ inputSchema: {
7
+ type: "object",
8
+ properties: {
9
+ actionId: {
10
+ type: "string",
11
+ description: "Stable id of the action to inspect."
12
+ }
13
+ },
14
+ required: ["actionId"],
15
+ additionalProperties: false
16
+ },
17
+ annotations: { readOnlyHint: true },
18
+ validateArgs(args) {
19
+ if (typeof args !== "object" || args === null || typeof args.actionId !== "string") {
20
+ throw new Error("Expected { actionId: string }.");
21
+ }
22
+ return args;
23
+ },
24
+ execute: ({ actionId }) => provider.getActionAvailability(actionId) ?? {
25
+ actionId,
26
+ available: false,
27
+ reasons: ["Unknown action id."]
28
+ }
29
+ };
30
+ }
31
+ function createListActionsTool(provider, name = "list_available_actions") {
32
+ return {
33
+ name,
34
+ description: "Lists safe action availability summaries for the current page or app scope.",
35
+ inputSchema: {
36
+ type: "object",
37
+ properties: {},
38
+ additionalProperties: false
39
+ },
40
+ annotations: { readOnlyHint: true },
41
+ execute: () => ({ actions: provider.listActions?.() ?? [] })
42
+ };
43
+ }
44
+
45
+ // src/app-info.ts
46
+ function createAppInfoTool(getInfo, name = "get_app_info") {
47
+ return {
48
+ name,
49
+ description: "Returns safe application metadata, environment, build, and Tooluminati status.",
50
+ inputSchema: {
51
+ type: "object",
52
+ properties: {},
53
+ additionalProperties: false
54
+ },
55
+ annotations: { readOnlyHint: true },
56
+ execute: () => getInfo()
57
+ };
58
+ }
59
+ function createVisibleToolsTool(name = "get_visible_agent_tools") {
60
+ return {
61
+ name,
62
+ description: "Returns visible WebMCP tools with safe metadata such as names, descriptions, annotations, and scope.",
63
+ inputSchema: {
64
+ type: "object",
65
+ properties: {},
66
+ additionalProperties: false
67
+ },
68
+ annotations: { readOnlyHint: true },
69
+ execute: (_args, context) => ({
70
+ tools: context.registry.getVisibleToolSummaries()
71
+ })
72
+ };
73
+ }
74
+
75
+ // src/errors.ts
76
+ function isDevEnvironment() {
77
+ return typeof process !== "undefined" && process.env?.NODE_ENV !== "production";
78
+ }
79
+ var ClientErrorBuffer = class {
80
+ errors = [];
81
+ maxSize;
82
+ redact;
83
+ includeStacks;
84
+ getRoute;
85
+ constructor(options = 20) {
86
+ if (typeof options === "number") {
87
+ this.maxSize = options;
88
+ this.includeStacks = false;
89
+ } else {
90
+ this.maxSize = options.maxSize ?? 20;
91
+ if (options.redact) {
92
+ this.redact = options.redact;
93
+ }
94
+ this.includeStacks = options.includeStacks ?? false;
95
+ if (options.getRoute) {
96
+ this.getRoute = options.getRoute;
97
+ }
98
+ }
99
+ }
100
+ push(error) {
101
+ const route = error.route ?? this.getRoute?.();
102
+ const entry = {
103
+ message: error.message,
104
+ source: error.source,
105
+ timestamp: error.timestamp,
106
+ ...route ? { route } : {}
107
+ };
108
+ if (this.includeStacks && isDevEnvironment() && error.stack) {
109
+ entry.stack = error.stack;
110
+ }
111
+ const stored = this.redact?.(entry) ?? entry;
112
+ this.errors.unshift(stored);
113
+ this.errors.splice(this.maxSize);
114
+ }
115
+ list() {
116
+ return [...this.errors];
117
+ }
118
+ clear() {
119
+ this.errors.splice(0);
120
+ }
121
+ };
122
+ function createErrorCollector(options) {
123
+ if (options.enabled === false || typeof window === "undefined") {
124
+ return () => {
125
+ };
126
+ }
127
+ const onError = (event) => {
128
+ const stack = event.error instanceof Error ? event.error.stack : void 0;
129
+ options.buffer.push({
130
+ message: event.message,
131
+ source: event.filename ?? "window.onerror",
132
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
133
+ ...stack ? { stack } : {}
134
+ });
135
+ };
136
+ const onRejection = (event) => {
137
+ const reason = event.reason;
138
+ const stack = reason instanceof Error ? reason.stack : void 0;
139
+ options.buffer.push({
140
+ message: reason instanceof Error ? reason.message : String(reason),
141
+ source: "unhandledrejection",
142
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
143
+ ...stack ? { stack } : {}
144
+ });
145
+ };
146
+ window.addEventListener("error", onError);
147
+ window.addEventListener("unhandledrejection", onRejection);
148
+ return () => {
149
+ window.removeEventListener("error", onError);
150
+ window.removeEventListener("unhandledrejection", onRejection);
151
+ };
152
+ }
153
+ function createRecentClientErrorsTool(buffer, name = "get_recent_client_errors") {
154
+ return {
155
+ name,
156
+ description: "Returns recent redacted client-side errors captured by the app diagnostics buffer.",
157
+ inputSchema: {
158
+ type: "object",
159
+ properties: {},
160
+ additionalProperties: false
161
+ },
162
+ annotations: { readOnlyHint: true, untrustedContentHint: true },
163
+ execute: () => ({ errors: buffer.list() })
164
+ };
165
+ }
166
+
167
+ // src/feature-flags.ts
168
+ function createFeatureFlagsTool(getFlags, name = "get_feature_flags_summary") {
169
+ return {
170
+ name,
171
+ description: "Returns safe feature flag names and enabled states without exposing user targeting data.",
172
+ inputSchema: {
173
+ type: "object",
174
+ properties: {},
175
+ additionalProperties: false
176
+ },
177
+ annotations: { readOnlyHint: true },
178
+ execute: () => ({ flags: getFlags() })
179
+ };
180
+ }
181
+
182
+ // src/hydration.ts
183
+ function createHydrationHealthTracker() {
184
+ const health = { recoverableErrorCount: 0 };
185
+ return {
186
+ recordRecoverableError(error) {
187
+ health.recoverableErrorCount += 1;
188
+ health.lastRecoverableError = error instanceof Error ? error.message : String(error);
189
+ },
190
+ getHealth() {
191
+ return { ...health };
192
+ }
193
+ };
194
+ }
195
+ function createHydrationHealthTool(getHealth, name = "get_hydration_health") {
196
+ return {
197
+ name,
198
+ description: "Returns development-only React hydration health and recoverable error summary.",
199
+ inputSchema: {
200
+ type: "object",
201
+ properties: {},
202
+ additionalProperties: false
203
+ },
204
+ annotations: { readOnlyHint: true },
205
+ execute: () => getHealth()
206
+ };
207
+ }
208
+
209
+ // src/mounted-forms.ts
210
+ function createMountedFormsRegistry() {
211
+ const forms = /* @__PURE__ */ new Map();
212
+ return {
213
+ register(summary) {
214
+ forms.set(summary.name, summary);
215
+ return () => {
216
+ forms.delete(summary.name);
217
+ };
218
+ },
219
+ unregister(name) {
220
+ forms.delete(name);
221
+ },
222
+ list() {
223
+ return [...forms.values()];
224
+ }
225
+ };
226
+ }
227
+ function createMountedFormsSummaryTool(registry, name = "get_mounted_forms_summary") {
228
+ return {
229
+ name,
230
+ description: "Returns summaries of mounted form diagnostics registered in the app.",
231
+ inputSchema: {
232
+ type: "object",
233
+ properties: {},
234
+ additionalProperties: false
235
+ },
236
+ annotations: { readOnlyHint: true },
237
+ execute: () => ({ forms: registry.list() })
238
+ };
239
+ }
240
+
241
+ // src/timeline.ts
242
+ var DEFAULT_MAX_SIZE = 50;
243
+ var DEFAULT_TOOL_LIMIT = 20;
244
+ var VALID_CATEGORIES = /* @__PURE__ */ new Set([
245
+ "client_error",
246
+ "fetch_failure",
247
+ "route_change",
248
+ "action_blocked",
249
+ "tool_activated",
250
+ "tool_cancelled",
251
+ "agent_form_submit",
252
+ "toolchange",
253
+ "custom"
254
+ ]);
255
+ var nextEventId = 0;
256
+ function createEventId() {
257
+ nextEventId += 1;
258
+ return `tlm-${nextEventId}`;
259
+ }
260
+ function stripQuerySecrets(url) {
261
+ try {
262
+ const parsed = new URL(url, "http://local");
263
+ parsed.search = "";
264
+ return parsed.pathname + (parsed.hash || "");
265
+ } catch {
266
+ return url.split("?")[0] ?? url;
267
+ }
268
+ }
269
+ var TroubleshootingTimelineBuffer = class {
270
+ events = [];
271
+ maxSize;
272
+ redact;
273
+ constructor(options = {}) {
274
+ if (typeof options === "number") {
275
+ this.maxSize = options;
276
+ } else {
277
+ this.maxSize = options.maxSize ?? DEFAULT_MAX_SIZE;
278
+ if (options.redact) {
279
+ this.redact = options.redact;
280
+ }
281
+ }
282
+ }
283
+ push(event) {
284
+ const stored = {
285
+ ...event,
286
+ id: event.id ?? createEventId(),
287
+ timestamp: event.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
288
+ };
289
+ const finalEvent = this.redact?.(stored) ?? stored;
290
+ this.events.unshift(finalEvent);
291
+ this.events.splice(this.maxSize);
292
+ return finalEvent;
293
+ }
294
+ list(options) {
295
+ const limit = Math.max(
296
+ 0,
297
+ Math.min(options?.limit ?? DEFAULT_TOOL_LIMIT, DEFAULT_TOOL_LIMIT)
298
+ );
299
+ let result = [...this.events];
300
+ if (options?.since) {
301
+ const sinceMs = Date.parse(options.since);
302
+ if (Number.isNaN(sinceMs)) {
303
+ return [];
304
+ }
305
+ result = result.filter((e) => Date.parse(e.timestamp) >= sinceMs);
306
+ }
307
+ if (options?.categories?.length) {
308
+ const cats = new Set(options.categories);
309
+ result = result.filter((e) => cats.has(e.category));
310
+ }
311
+ return result.slice(0, limit);
312
+ }
313
+ summary(events = this.events) {
314
+ const byCategory = {};
315
+ for (const event of events) {
316
+ byCategory[event.category] = (byCategory[event.category] ?? 0) + 1;
317
+ }
318
+ return { total: events.length, byCategory };
319
+ }
320
+ clear() {
321
+ this.events.splice(0);
322
+ }
323
+ };
324
+ var fetchTrackerCount = 0;
325
+ var originalFetch;
326
+ function createFetchFailureTracker(options) {
327
+ if (options.enabled === false || typeof window === "undefined") {
328
+ return () => {
329
+ };
330
+ }
331
+ if (fetchTrackerCount === 0) {
332
+ originalFetch = window.fetch.bind(window);
333
+ }
334
+ fetchTrackerCount += 1;
335
+ const baseFetch = originalFetch;
336
+ window.fetch = async (input, init) => {
337
+ const started = performance.now();
338
+ const method = (init?.method ?? "GET").toUpperCase();
339
+ const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
340
+ if (options.ignoreUrls?.test(url)) {
341
+ return baseFetch(input, init);
342
+ }
343
+ try {
344
+ const response = await baseFetch(input, init);
345
+ if (!response.ok) {
346
+ const detail = options.redactBody === false ? `HTTP request failed before UI state updated.` : "HTTP request failed before UI state updated.";
347
+ options.buffer.push({
348
+ category: "fetch_failure",
349
+ title: `${method} ${stripQuerySecrets(url)} \u2014 ${response.status}`,
350
+ method,
351
+ url: stripQuerySecrets(url),
352
+ status: response.status,
353
+ durationMs: Math.round(performance.now() - started),
354
+ detail,
355
+ metadata: {
356
+ host: safeHost(url),
357
+ status: String(response.status)
358
+ }
359
+ });
360
+ }
361
+ return response;
362
+ } catch (error) {
363
+ options.buffer.push({
364
+ category: "fetch_failure",
365
+ title: `${method} ${stripQuerySecrets(url)} \u2014 network error`,
366
+ method,
367
+ url: stripQuerySecrets(url),
368
+ durationMs: Math.round(performance.now() - started),
369
+ detail: error instanceof Error ? error.message : "Network request failed.",
370
+ metadata: { host: safeHost(url) }
371
+ });
372
+ throw error;
373
+ }
374
+ };
375
+ return () => {
376
+ fetchTrackerCount = Math.max(0, fetchTrackerCount - 1);
377
+ if (fetchTrackerCount === 0 && originalFetch) {
378
+ window.fetch = originalFetch;
379
+ originalFetch = void 0;
380
+ }
381
+ };
382
+ }
383
+ function safeHost(url) {
384
+ try {
385
+ return new URL(url, window.location.origin).host;
386
+ } catch {
387
+ return "unknown";
388
+ }
389
+ }
390
+ function createTimelineFromErrorBuffer(buffer, timeline) {
391
+ const originalPush = buffer.push.bind(buffer);
392
+ buffer.push = (error) => {
393
+ originalPush(error);
394
+ timeline.push({
395
+ category: "client_error",
396
+ title: error.message,
397
+ message: error.message,
398
+ source: error.source,
399
+ detail: "Client error captured by diagnostics buffer.",
400
+ metadata: {
401
+ source: error.source,
402
+ ...error.route ? { route: error.route } : {}
403
+ },
404
+ untrusted: true
405
+ });
406
+ };
407
+ return () => {
408
+ buffer.push = originalPush;
409
+ };
410
+ }
411
+ function createWebMcpLifecycleCollector(options) {
412
+ if (options.enabled === false || typeof window === "undefined") {
413
+ return () => {
414
+ };
415
+ }
416
+ const cleanups = [];
417
+ const onToolActivated = (event) => {
418
+ const detail = event.detail;
419
+ options.buffer.push({
420
+ category: "tool_activated",
421
+ title: `agent activated ${detail?.toolName ?? "form tool"}`,
422
+ toolName: detail?.toolName,
423
+ formId: detail?.form?.id,
424
+ detail: "Declarative form tool activated by the agent."
425
+ });
426
+ };
427
+ const onToolCancel = (event) => {
428
+ const detail = event.detail;
429
+ options.buffer.push({
430
+ category: "tool_cancelled",
431
+ title: `agent cancelled ${detail?.toolName ?? "form tool"}`,
432
+ toolName: detail?.toolName,
433
+ detail: "Agent cancelled a declarative form tool."
434
+ });
435
+ };
436
+ const onAgentFormSubmit = (event) => {
437
+ const submitEvent = event;
438
+ if (!submitEvent.agentInvoked) {
439
+ return;
440
+ }
441
+ const form = event.target;
442
+ const toolName = form?.getAttribute("toolname") ?? "form";
443
+ options.buffer.push({
444
+ category: "agent_form_submit",
445
+ title: `agent submitted ${toolName}`,
446
+ toolName,
447
+ formId: form?.id,
448
+ detail: "Agent invoked declarative form submit."
449
+ });
450
+ };
451
+ const onToolChange = () => {
452
+ options.buffer.push({
453
+ category: "toolchange",
454
+ title: "tool registry changed",
455
+ added: [],
456
+ removed: [],
457
+ detail: "WebMCP tool registry changed on document.modelContext."
458
+ });
459
+ };
460
+ window.addEventListener("toolactivated", onToolActivated);
461
+ window.addEventListener("toolcancel", onToolCancel);
462
+ window.addEventListener("submit", onAgentFormSubmit, true);
463
+ cleanups.push(() => {
464
+ window.removeEventListener("toolactivated", onToolActivated);
465
+ window.removeEventListener("toolcancel", onToolCancel);
466
+ window.removeEventListener("submit", onAgentFormSubmit, true);
467
+ });
468
+ const modelContext = document.modelContext;
469
+ if (modelContext?.addEventListener) {
470
+ modelContext.addEventListener("toolchange", onToolChange);
471
+ cleanups.push(() => {
472
+ modelContext.removeEventListener("toolchange", onToolChange);
473
+ });
474
+ }
475
+ return () => {
476
+ for (const cleanup of cleanups) {
477
+ cleanup();
478
+ }
479
+ };
480
+ }
481
+ function createTroubleshootingTimelineTool(buffer, name = "get_troubleshooting_timeline") {
482
+ return {
483
+ name,
484
+ description: "Returns a bounded, redacted timeline of recent runtime incidents.",
485
+ inputSchema: {
486
+ type: "object",
487
+ properties: {
488
+ categories: {
489
+ type: "array",
490
+ items: { type: "string" },
491
+ description: "Optional event categories to include."
492
+ },
493
+ since: {
494
+ type: "string",
495
+ description: "ISO timestamp; only events after this time."
496
+ },
497
+ limit: {
498
+ type: "integer",
499
+ maximum: DEFAULT_TOOL_LIMIT,
500
+ description: "Max events to return (cap 20)."
501
+ }
502
+ },
503
+ additionalProperties: false
504
+ },
505
+ annotations: { readOnlyHint: true, untrustedContentHint: true },
506
+ validateArgs(args) {
507
+ if (typeof args !== "object" || args === null) {
508
+ return {};
509
+ }
510
+ const input = args;
511
+ if (input.categories?.length) {
512
+ const invalid = input.categories.filter(
513
+ (category) => !VALID_CATEGORIES.has(category)
514
+ );
515
+ if (invalid.length) {
516
+ throw new Error(`Unknown timeline categories: ${invalid.join(", ")}`);
517
+ }
518
+ }
519
+ if (input.limit !== void 0 && input.limit < 0) {
520
+ throw new Error("limit must be >= 0.");
521
+ }
522
+ return input;
523
+ },
524
+ execute: (input) => {
525
+ const events = buffer.list({
526
+ ...input.categories?.length ? {
527
+ categories: input.categories
528
+ } : {},
529
+ ...input.since ? { since: input.since } : {},
530
+ ...input.limit !== void 0 ? { limit: input.limit } : {}
531
+ });
532
+ return {
533
+ events,
534
+ summary: buffer.summary(events)
535
+ };
536
+ }
537
+ };
538
+ }
539
+
540
+ // src/webmcp-environment.ts
541
+ import {
542
+ getModelContext,
543
+ isWebMcpSupported
544
+ } from "@tooluminati/core";
545
+ function createWebMcpEnvironmentSummary(options = {}) {
546
+ const globalObject = options.globalObject ?? globalThis;
547
+ let usingNavigatorFallback = false;
548
+ const documentContext = typeof globalObject.document !== "undefined" ? globalObject.document.modelContext : void 0;
549
+ getModelContext({
550
+ globalObject,
551
+ onNavigatorFallback: () => {
552
+ usingNavigatorFallback = true;
553
+ }
554
+ });
555
+ const supported = isWebMcpSupported({ globalObject });
556
+ const origin = typeof globalObject.location !== "undefined" ? globalObject.location.origin : "unknown";
557
+ const toolCount = options.registry?.getRegisteredToolNames().length ?? 0;
558
+ const checks = [];
559
+ const warnings = [];
560
+ checks.push({
561
+ id: "document.modelContext",
562
+ label: "document.modelContext",
563
+ value: documentContext ? "available" : "missing",
564
+ status: documentContext ? "ok" : "fail",
565
+ ...documentContext ? {} : {
566
+ hint: "No document.modelContext on this page. Enable WebMCP in Chrome or attach the Model Context Inspector Extension."
567
+ }
568
+ });
569
+ checks.push({
570
+ id: "navigator.modelContext",
571
+ label: "navigator.modelContext fallback",
572
+ value: usingNavigatorFallback ? "in use" : "not used",
573
+ status: usingNavigatorFallback ? "warn" : "ok",
574
+ ...usingNavigatorFallback ? {
575
+ hint: "Deprecated navigator.modelContext path detected. Migrate to document.modelContext."
576
+ } : {}
577
+ });
578
+ const originIsolation = detectOriginIsolationIssue(globalObject);
579
+ checks.push({
580
+ id: "origin-isolation",
581
+ label: "Origin isolation",
582
+ value: originIsolation.value,
583
+ status: originIsolation.status,
584
+ ...originIsolation.hint ? { hint: originIsolation.hint } : {}
585
+ });
586
+ if (originIsolation.warning) {
587
+ warnings.push(originIsolation.warning);
588
+ }
589
+ const permissionsPolicy = detectPermissionsPolicy(globalObject);
590
+ checks.push({
591
+ id: "permissions-policy",
592
+ label: "tools Permissions-Policy",
593
+ value: permissionsPolicy.value,
594
+ status: permissionsPolicy.status,
595
+ ...permissionsPolicy.hint ? { hint: permissionsPolicy.hint } : {}
596
+ });
597
+ if (permissionsPolicy.warning) {
598
+ warnings.push(permissionsPolicy.warning);
599
+ }
600
+ if (toolCount === 0 && supported) {
601
+ warnings.push("No tools registered yet.");
602
+ checks.push({
603
+ id: "tool-count",
604
+ label: "Registered tools",
605
+ value: "0",
606
+ status: "warn",
607
+ hint: "WebMCP is supported but no tools are registered. Verify the Tooluminati provider is mounted."
608
+ });
609
+ }
610
+ if (!supported) {
611
+ warnings.push("WebMCP is unsupported in this browser context.");
612
+ }
613
+ return {
614
+ supported,
615
+ toolCount,
616
+ origin,
617
+ usingNavigatorFallback,
618
+ checks,
619
+ warnings
620
+ };
621
+ }
622
+ function detectOriginIsolationIssue(globalObject) {
623
+ if (typeof globalObject.document === "undefined") {
624
+ return { value: "n/a", status: "muted" };
625
+ }
626
+ const doc = globalObject.document;
627
+ if (doc.domain && doc.domain.length > 0) {
628
+ const hint = "document.domain is set. Origin-Agent-Cluster may be ?0. Remove document.domain writes or set Origin-Agent-Cluster: ?1.";
629
+ return {
630
+ value: "review",
631
+ status: "warn",
632
+ hint,
633
+ warning: hint
634
+ };
635
+ }
636
+ return { value: "ok", status: "ok" };
637
+ }
638
+ function detectPermissionsPolicy(globalObject) {
639
+ if (typeof globalObject.document === "undefined") {
640
+ return { value: "n/a", status: "muted" };
641
+ }
642
+ const doc = globalObject.document;
643
+ if (doc.permissionsPolicy?.allowsFeature) {
644
+ const allowed = doc.permissionsPolicy.allowsFeature("tools");
645
+ if (!allowed) {
646
+ const hint = "tools is blocked by Permissions-Policy. Allow tools for this origin or review iframe allow attributes.";
647
+ return {
648
+ value: "blocked",
649
+ status: "fail",
650
+ hint,
651
+ warning: hint
652
+ };
653
+ }
654
+ }
655
+ if (typeof globalObject.document.querySelector !== "function") {
656
+ return { value: "self", status: "ok" };
657
+ }
658
+ const iframe = globalObject.document.querySelector("iframe");
659
+ if (iframe && iframe.src && !iframe.src.startsWith(globalObject.location.origin)) {
660
+ const hint = "Cross-origin iframe detected. Verify Permissions-Policy allows tools for embedded origins.";
661
+ return {
662
+ value: "review cross-origin iframe",
663
+ status: "warn",
664
+ hint,
665
+ warning: hint
666
+ };
667
+ }
668
+ return { value: "self", status: "ok" };
669
+ }
670
+ function createWebMcpEnvironmentTool(getSummary, name = "get_webmcp_environment") {
671
+ return {
672
+ name,
673
+ description: "Reports WebMCP browser support, registered tool count, and setup warnings.",
674
+ inputSchema: {
675
+ type: "object",
676
+ properties: {},
677
+ additionalProperties: false
678
+ },
679
+ annotations: { readOnlyHint: true },
680
+ execute: () => getSummary()
681
+ };
682
+ }
683
+
684
+ // src/workflow-blockers.ts
685
+ function createWorkflowBlockersTool(sources, name = "get_workflow_blockers") {
686
+ return {
687
+ name,
688
+ description: "Aggregates action, form, query, and error blockers for the current workflow.",
689
+ inputSchema: {
690
+ type: "object",
691
+ properties: {},
692
+ additionalProperties: false
693
+ },
694
+ annotations: { readOnlyHint: true, untrustedContentHint: true },
695
+ execute: () => {
696
+ const actions = sources.actionProvider?.listActions?.().map((action) => ({
697
+ actionId: action.actionId,
698
+ available: action.available,
699
+ reasons: action.available ? [] : action.reasons
700
+ })) ?? [];
701
+ const result = {
702
+ actions,
703
+ forms: sources.formSummaries?.() ?? [],
704
+ declarativeForms: sources.declarativeFormSummaries?.() ?? [],
705
+ queries: sources.querySummaries?.() ?? [],
706
+ errors: sources.recentErrors?.() ?? []
707
+ };
708
+ const hydration = sources.hydrationHealth?.();
709
+ if (hydration) {
710
+ result.hydration = hydration;
711
+ }
712
+ const flags = sources.featureFlags?.();
713
+ if (flags) {
714
+ result.featureFlags = flags;
715
+ }
716
+ return result;
717
+ }
718
+ };
719
+ }
720
+ function formSummaryFromMounted(form) {
721
+ const blockers = form.errors.map((e) => `${e.path}: ${e.message}`);
722
+ if (form.submitting) {
723
+ blockers.push("Form is submitting.");
724
+ }
725
+ return {
726
+ name: form.name,
727
+ valid: blockers.length === 0,
728
+ submitting: form.submitting,
729
+ blockers
730
+ };
731
+ }
732
+ function discoverDeclarativeForms(root = typeof document !== "undefined" ? document : null) {
733
+ if (!root) {
734
+ return [];
735
+ }
736
+ const summaries = /* @__PURE__ */ new Map();
737
+ for (const form of root.querySelectorAll("form[toolname]")) {
738
+ const el = form;
739
+ summaries.set(el.getAttribute("toolname") ?? "unknown", {
740
+ toolName: el.getAttribute("toolname") ?? "unknown",
741
+ ...el.id ? { formId: el.id } : {},
742
+ autoSubmit: el.hasAttribute("toolautosubmit")
743
+ });
744
+ }
745
+ return [...summaries.values()];
746
+ }
747
+
748
+ // src/troubleshooting-guidance.ts
749
+ var TROUBLESHOOTING_GUIDANCE = {
750
+ "document.modelContext": {
751
+ title: "WebMCP host missing",
752
+ hint: "No document.modelContext on this page. Enable WebMCP in Chrome or attach the Model Context Inspector Extension. Tools cannot register until this resolves.",
753
+ severity: "error"
754
+ },
755
+ "navigator.modelContext": {
756
+ title: "Deprecated fallback in use",
757
+ hint: "Migrate from navigator.modelContext to document.modelContext. The fallback is not a supported long-term surface.",
758
+ severity: "warn"
759
+ },
760
+ "origin-isolation": {
761
+ title: "Origin isolation review",
762
+ hint: "Origin-Agent-Cluster may be ?0 when document.domain is set. Remove document.domain writes or set Origin-Agent-Cluster: ?1.",
763
+ severity: "warn"
764
+ },
765
+ "permissions-policy": {
766
+ title: "Permissions-Policy review",
767
+ hint: "Cross-origin iframes may block tools unless Permissions-Policy allows the tools feature for this origin.",
768
+ severity: "warn"
769
+ },
770
+ "tool-count": {
771
+ title: "No tools registered",
772
+ hint: "Verify WebMcpProvider or provideWebMcpRegistry is mounted and tools are registered in the current route scope.",
773
+ severity: "warn"
774
+ },
775
+ "output-budget": {
776
+ title: "Tool output budget exceeded",
777
+ hint: "A tool emitted more than the recommended ~1.5K chars. Trim fields or enable redaction to stay within Chrome guidance.",
778
+ severity: "warn"
779
+ },
780
+ fetch_failure: {
781
+ title: "Fetch failure",
782
+ hint: "Inspect get_troubleshooting_timeline and get_workflow_blockers for structured HTTP failure context.",
783
+ severity: "error"
784
+ },
785
+ client_error: {
786
+ title: "Client error",
787
+ hint: "See get_recent_client_errors and the timeline client_error entries for redacted stack traces in dev.",
788
+ severity: "error"
789
+ },
790
+ action_blocked: {
791
+ title: "Action blocked",
792
+ hint: "Call why_is_action_unavailable or get_workflow_blockers for reasons beyond disabled=true in the DOM.",
793
+ severity: "warn"
794
+ },
795
+ toolchange: {
796
+ title: "Tool registry changed",
797
+ hint: "Tools were added or removed. Check route mounts and scoped providers if the count is unexpected.",
798
+ severity: "info"
799
+ },
800
+ WebMcpUnavailable: {
801
+ title: "WebMCP unavailable",
802
+ hint: "Tooluminati could not find document.modelContext. Registration was deferred until a host becomes available.",
803
+ severity: "error"
804
+ },
805
+ TypeError: {
806
+ title: "Runtime TypeError",
807
+ hint: "Check component props and optional chaining. Errors are captured by WebMcpErrorBoundary when enabled.",
808
+ severity: "error"
809
+ },
810
+ NetworkError: {
811
+ title: "Network failure",
812
+ hint: "Verify API availability and CORS. Timeline fetch_failure entries include status and timing.",
813
+ severity: "error"
814
+ }
815
+ };
816
+ function getGuidanceForEnvironmentCheck(check) {
817
+ if (check.hint) {
818
+ return {
819
+ title: check.label,
820
+ hint: check.hint,
821
+ severity: check.status === "fail" ? "error" : check.status === "warn" ? "warn" : "info"
822
+ };
823
+ }
824
+ return TROUBLESHOOTING_GUIDANCE[check.id];
825
+ }
826
+ function getGuidanceForTimelineEvent(event) {
827
+ const base = TROUBLESHOOTING_GUIDANCE[event.category];
828
+ if (base) {
829
+ return {
830
+ ...base,
831
+ hint: event.detail ? `${base.hint} ${event.detail}` : base.hint
832
+ };
833
+ }
834
+ return void 0;
835
+ }
836
+ function getGuidanceForClientError(error) {
837
+ for (const key of ["WebMcpUnavailable", "TypeError", "NetworkError"]) {
838
+ if (error.message.includes(key)) {
839
+ return TROUBLESHOOTING_GUIDANCE[key];
840
+ }
841
+ }
842
+ return TROUBLESHOOTING_GUIDANCE.client_error;
843
+ }
844
+ function timelineEventGroup(category) {
845
+ if (category === "fetch_failure" || category === "client_error") {
846
+ return "failures";
847
+ }
848
+ if (category === "action_blocked") {
849
+ return "blocked";
850
+ }
851
+ if (category === "tool_activated" || category === "tool_cancelled" || category === "toolchange") {
852
+ return "tools";
853
+ }
854
+ if (category === "route_change" || category === "agent_form_submit") {
855
+ return "nav";
856
+ }
857
+ return "all";
858
+ }
859
+
860
+ // src/troubleshooting-panel.ts
861
+ var STORAGE_KEY = "tooluminati.troubleshootingPanel.collapsed";
862
+ function relativeTime(iso) {
863
+ const delta = Date.now() - Date.parse(iso);
864
+ if (Number.isNaN(delta) || delta < 0) {
865
+ return "now";
866
+ }
867
+ const seconds = Math.round(delta / 1e3);
868
+ if (seconds < 60) {
869
+ return `${seconds}s`;
870
+ }
871
+ const minutes = Math.round(seconds / 60);
872
+ return `${minutes}m`;
873
+ }
874
+ function dedupeErrors(errors) {
875
+ const counts = /* @__PURE__ */ new Map();
876
+ for (const error of errors) {
877
+ counts.set(error.message, (counts.get(error.message) ?? 0) + 1);
878
+ }
879
+ const seen = /* @__PURE__ */ new Set();
880
+ const views = [];
881
+ for (const error of errors) {
882
+ if (seen.has(error.message)) {
883
+ continue;
884
+ }
885
+ seen.add(error.message);
886
+ const guidance = getGuidanceForClientError(error)?.hint;
887
+ views.push({
888
+ id: error.message,
889
+ message: error.message,
890
+ source: error.source,
891
+ time: relativeTime(error.timestamp),
892
+ count: counts.get(error.message) ?? 1,
893
+ ...error.stack ? { stack: error.stack } : {},
894
+ ...guidance ? { guidance } : {}
895
+ });
896
+ }
897
+ return views;
898
+ }
899
+ function computeTroubleshootingPanelState(snapshot) {
900
+ if (!snapshot.supported) {
901
+ return "unsupported";
902
+ }
903
+ const hasIncidents = snapshot.issueCount > 0 || snapshot.errors.length > 0 || snapshot.events.some(
904
+ (event) => event.group === "failures" || event.group === "blocked" || event.category === "toolchange"
905
+ );
906
+ if (!hasIncidents && snapshot.events.length === 0 && snapshot.errors.length === 0) {
907
+ return snapshot.toolCount === 0 ? "empty" : "ok";
908
+ }
909
+ if (hasIncidents) {
910
+ return "attention";
911
+ }
912
+ return "ok";
913
+ }
914
+ function serializeTroubleshootingDiagnostics(snapshot) {
915
+ return JSON.stringify(
916
+ {
917
+ webmcp: {
918
+ supported: snapshot.supported,
919
+ toolCount: snapshot.toolCount,
920
+ origin: snapshot.origin,
921
+ panelState: snapshot.panelState
922
+ },
923
+ checks: snapshot.checks.map((c) => ({
924
+ label: c.label,
925
+ value: c.value,
926
+ status: c.status,
927
+ ...c.guidance ? { guidance: c.guidance } : {}
928
+ })),
929
+ timeline: snapshot.events.map((e) => ({
930
+ category: e.category,
931
+ title: e.title,
932
+ t: e.time,
933
+ ...e.guidance ? { guidance: e.guidance } : {},
934
+ ...e.metadata ? { metadata: e.metadata } : {}
935
+ })),
936
+ clientErrors: snapshot.errors.map((e) => ({
937
+ message: e.message,
938
+ count: e.count,
939
+ ...e.guidance ? { guidance: e.guidance } : {}
940
+ })),
941
+ warnings: snapshot.warnings
942
+ },
943
+ null,
944
+ 2
945
+ );
946
+ }
947
+ function buildTroubleshootingPanelSnapshot(sources, options = {}) {
948
+ const env = sources.environment() ?? createWebMcpEnvironmentSummary({
949
+ ...sources.registry ? { registry: sources.registry } : {},
950
+ ...options.registry ? { registry: options.registry } : {}
951
+ });
952
+ const eventLimit = options.eventLimit ?? 20;
953
+ const rawEvents = sources.timeline.list({ limit: eventLimit });
954
+ const events = rawEvents.map((event) => {
955
+ const guidance = getGuidanceForTimelineEvent(event)?.hint;
956
+ return {
957
+ id: event.id,
958
+ category: event.category,
959
+ group: timelineEventGroup(event.category),
960
+ title: event.title,
961
+ time: relativeTime(event.timestamp),
962
+ ...event.detail ? { detail: event.detail } : {},
963
+ ...event.metadata ? { metadata: event.metadata } : {},
964
+ ...guidance ? { guidance } : {}
965
+ };
966
+ });
967
+ const checks = env.checks.map((check) => {
968
+ const guidance = getGuidanceForEnvironmentCheck(check)?.hint;
969
+ return {
970
+ ...check,
971
+ ...guidance ? { guidance } : {}
972
+ };
973
+ });
974
+ const errors = dedupeErrors(sources.errors.list());
975
+ const warnCount = checks.filter(
976
+ (c) => c.status === "warn" || c.status === "fail"
977
+ ).length;
978
+ const incidentEvents = events.filter(
979
+ (event) => event.group === "failures" || event.group === "blocked"
980
+ ).length;
981
+ const issueCount = warnCount + errors.length + incidentEvents;
982
+ const snapshot = {
983
+ supported: env.supported,
984
+ toolCount: env.toolCount,
985
+ origin: env.origin,
986
+ issueCount,
987
+ checks,
988
+ events,
989
+ errors,
990
+ warnings: env.warnings,
991
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
992
+ panelState: "ok"
993
+ };
994
+ snapshot.panelState = computeTroubleshootingPanelState(snapshot);
995
+ return snapshot;
996
+ }
997
+ function createTroubleshootingPanelViewModel(sources, options = {}) {
998
+ const listeners = /* @__PURE__ */ new Set();
999
+ let timer;
1000
+ const notify = () => {
1001
+ for (const listener of listeners) {
1002
+ listener();
1003
+ }
1004
+ };
1005
+ if (options.pollMs && options.pollMs > 0) {
1006
+ timer = setInterval(notify, options.pollMs);
1007
+ }
1008
+ return {
1009
+ refresh() {
1010
+ return buildTroubleshootingPanelSnapshot(sources, options);
1011
+ },
1012
+ subscribe(listener) {
1013
+ listeners.add(listener);
1014
+ return () => {
1015
+ listeners.delete(listener);
1016
+ };
1017
+ },
1018
+ dispose() {
1019
+ listeners.clear();
1020
+ if (timer) {
1021
+ clearInterval(timer);
1022
+ }
1023
+ }
1024
+ };
1025
+ }
1026
+ function createTroubleshootingPanelVisibilityController(options) {
1027
+ const storageKey = options?.storageKey ?? STORAGE_KEY;
1028
+ const listeners = /* @__PURE__ */ new Set();
1029
+ let visible = true;
1030
+ let collapsed = options?.startCollapsed ?? readCollapsed(storageKey);
1031
+ const notify = () => {
1032
+ for (const listener of listeners) {
1033
+ listener();
1034
+ }
1035
+ };
1036
+ return {
1037
+ get visible() {
1038
+ return visible;
1039
+ },
1040
+ get collapsed() {
1041
+ return collapsed;
1042
+ },
1043
+ show() {
1044
+ visible = true;
1045
+ notify();
1046
+ },
1047
+ hide() {
1048
+ visible = false;
1049
+ notify();
1050
+ },
1051
+ toggle() {
1052
+ visible = !visible;
1053
+ notify();
1054
+ },
1055
+ setCollapsed(next) {
1056
+ collapsed = next;
1057
+ writeCollapsed(storageKey, next);
1058
+ notify();
1059
+ },
1060
+ subscribe(listener) {
1061
+ listeners.add(listener);
1062
+ return () => {
1063
+ listeners.delete(listener);
1064
+ };
1065
+ }
1066
+ };
1067
+ }
1068
+ function isPanelEnabledByDefault(enabled) {
1069
+ if (enabled === true) {
1070
+ return true;
1071
+ }
1072
+ if (enabled === false) {
1073
+ return false;
1074
+ }
1075
+ const nodeEnv = typeof process !== "undefined" ? process.env?.NODE_ENV : void 0;
1076
+ if (!nodeEnv || nodeEnv === "development" || nodeEnv === "test") {
1077
+ return true;
1078
+ }
1079
+ return nodeEnv !== "production";
1080
+ }
1081
+ function isPanelUrlOverrideEnabled(allowUrlOverride) {
1082
+ if (!allowUrlOverride || typeof window === "undefined") {
1083
+ return false;
1084
+ }
1085
+ return new URLSearchParams(window.location.search).has("tooluminati-panel");
1086
+ }
1087
+ function readCollapsed(storageKey) {
1088
+ if (typeof sessionStorage === "undefined") {
1089
+ return false;
1090
+ }
1091
+ return sessionStorage.getItem(storageKey) === "1";
1092
+ }
1093
+ function writeCollapsed(storageKey, collapsed) {
1094
+ if (typeof sessionStorage === "undefined") {
1095
+ return;
1096
+ }
1097
+ sessionStorage.setItem(storageKey, collapsed ? "1" : "0");
1098
+ }
1099
+ function filterPanelEvents(events, filter) {
1100
+ if (filter === "all") {
1101
+ return events;
1102
+ }
1103
+ return events.filter((event) => event.group === filter);
1104
+ }
1105
+ export {
1106
+ ClientErrorBuffer,
1107
+ TROUBLESHOOTING_GUIDANCE,
1108
+ TroubleshootingTimelineBuffer,
1109
+ buildTroubleshootingPanelSnapshot,
1110
+ computeTroubleshootingPanelState,
1111
+ createActionAvailabilityTool,
1112
+ createAppInfoTool,
1113
+ createErrorCollector,
1114
+ createFeatureFlagsTool,
1115
+ createFetchFailureTracker,
1116
+ createHydrationHealthTool,
1117
+ createHydrationHealthTracker,
1118
+ createListActionsTool,
1119
+ createMountedFormsRegistry,
1120
+ createMountedFormsSummaryTool,
1121
+ createRecentClientErrorsTool,
1122
+ createTimelineFromErrorBuffer,
1123
+ createTroubleshootingPanelViewModel,
1124
+ createTroubleshootingPanelVisibilityController,
1125
+ createTroubleshootingTimelineTool,
1126
+ createVisibleToolsTool,
1127
+ createWebMcpEnvironmentSummary,
1128
+ createWebMcpEnvironmentTool,
1129
+ createWebMcpLifecycleCollector,
1130
+ createWorkflowBlockersTool,
1131
+ discoverDeclarativeForms,
1132
+ filterPanelEvents,
1133
+ formSummaryFromMounted,
1134
+ getGuidanceForClientError,
1135
+ getGuidanceForEnvironmentCheck,
1136
+ getGuidanceForTimelineEvent,
1137
+ isPanelEnabledByDefault,
1138
+ isPanelUrlOverrideEnabled,
1139
+ serializeTroubleshootingDiagnostics,
1140
+ timelineEventGroup
1141
+ };