@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.
- package/LICENSE +21 -0
- package/dist/action-availability.d.ts +24 -0
- package/dist/action-availability.d.ts.map +1 -0
- package/dist/action-availability.js +44 -0
- package/dist/app-info.d.ts +16 -0
- package/dist/app-info.d.ts.map +1 -0
- package/dist/app-info.js +28 -0
- package/dist/errors.d.ts +35 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +91 -0
- package/dist/feature-flags.d.ts +10 -0
- package/dist/feature-flags.d.ts.map +1 -0
- package/dist/feature-flags.js +13 -0
- package/dist/hydration.d.ts +11 -0
- package/dist/hydration.d.ts.map +1 -0
- package/dist/hydration.js +26 -0
- package/dist/index.cjs +1199 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1141 -0
- package/dist/mounted-forms.d.ts +26 -0
- package/dist/mounted-forms.d.ts.map +1 -0
- package/dist/mounted-forms.js +32 -0
- package/dist/timeline.d.ts +99 -0
- package/dist/timeline.d.ts.map +1 -0
- package/dist/timeline.js +302 -0
- package/dist/timeline.test.d.ts +2 -0
- package/dist/timeline.test.d.ts.map +1 -0
- package/dist/timeline.test.js +94 -0
- package/dist/troubleshooting-guidance.d.ts +15 -0
- package/dist/troubleshooting-guidance.d.ts.map +1 -0
- package/dist/troubleshooting-guidance.js +130 -0
- package/dist/troubleshooting-guidance.test.d.ts +2 -0
- package/dist/troubleshooting-guidance.test.d.ts.map +1 -0
- package/dist/troubleshooting-guidance.test.js +27 -0
- package/dist/troubleshooting-panel.d.ts +77 -0
- package/dist/troubleshooting-panel.d.ts.map +1 -0
- package/dist/troubleshooting-panel.js +243 -0
- package/dist/troubleshooting-panel.test.d.ts +2 -0
- package/dist/troubleshooting-panel.test.d.ts.map +1 -0
- package/dist/troubleshooting-panel.test.js +105 -0
- package/dist/webmcp-environment.d.ts +23 -0
- package/dist/webmcp-environment.d.ts.map +1 -0
- package/dist/webmcp-environment.js +156 -0
- package/dist/webmcp-environment.test.d.ts +2 -0
- package/dist/webmcp-environment.test.d.ts.map +1 -0
- package/dist/webmcp-environment.test.js +49 -0
- package/dist/workflow-blockers.d.ts +53 -0
- package/dist/workflow-blockers.d.ts.map +1 -0
- package/dist/workflow-blockers.js +62 -0
- package/dist/workflow-blockers.test.d.ts +2 -0
- package/dist/workflow-blockers.test.d.ts.map +1 -0
- package/dist/workflow-blockers.test.js +41 -0
- package/package.json +50 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { JsonSchema, WebMcpToolDescriptor } from '@tooluminati/core';
|
|
2
|
+
export interface MountedFormSummary {
|
|
3
|
+
name: string;
|
|
4
|
+
submitting?: boolean | undefined;
|
|
5
|
+
validating?: boolean | undefined;
|
|
6
|
+
dirty?: boolean | undefined;
|
|
7
|
+
touched?: boolean | undefined;
|
|
8
|
+
errors: Array<{
|
|
9
|
+
path: string;
|
|
10
|
+
message: string;
|
|
11
|
+
kind?: string | undefined;
|
|
12
|
+
}>;
|
|
13
|
+
schema?: JsonSchema | undefined;
|
|
14
|
+
}
|
|
15
|
+
export interface MountedFormsRegistry {
|
|
16
|
+
register(summary: MountedFormSummary): () => void;
|
|
17
|
+
unregister(name: string): void;
|
|
18
|
+
list(): MountedFormSummary[];
|
|
19
|
+
}
|
|
20
|
+
/** @experimental */
|
|
21
|
+
export declare function createMountedFormsRegistry(): MountedFormsRegistry;
|
|
22
|
+
/** @experimental */
|
|
23
|
+
export declare function createMountedFormsSummaryTool(registry: MountedFormsRegistry, name?: string): WebMcpToolDescriptor<Record<string, never>, {
|
|
24
|
+
forms: MountedFormSummary[];
|
|
25
|
+
}>;
|
|
26
|
+
//# sourceMappingURL=mounted-forms.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mounted-forms.d.ts","sourceRoot":"","sources":["../src/mounted-forms.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,oBAAoB,EACrB,MAAM,mBAAmB,CAAC;AAE3B,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACjC,UAAU,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACjC,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC5B,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,MAAM,EAAE,KAAK,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;KAC3B,CAAC,CAAC;IACH,MAAM,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;CACjC;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,OAAO,EAAE,kBAAkB,GAAG,MAAM,IAAI,CAAC;IAClD,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,IAAI,IAAI,kBAAkB,EAAE,CAAC;CAC9B;AAED,oBAAoB;AACpB,wBAAgB,0BAA0B,IAAI,oBAAoB,CAiBjE;AAED,oBAAoB;AACpB,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,oBAAoB,EAC9B,IAAI,SAA8B,GACjC,oBAAoB,CACrB,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,EACrB;IAAE,KAAK,EAAE,kBAAkB,EAAE,CAAA;CAAE,CAChC,CAaA"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** @experimental */
|
|
2
|
+
export function createMountedFormsRegistry() {
|
|
3
|
+
const forms = new Map();
|
|
4
|
+
return {
|
|
5
|
+
register(summary) {
|
|
6
|
+
forms.set(summary.name, summary);
|
|
7
|
+
return () => {
|
|
8
|
+
forms.delete(summary.name);
|
|
9
|
+
};
|
|
10
|
+
},
|
|
11
|
+
unregister(name) {
|
|
12
|
+
forms.delete(name);
|
|
13
|
+
},
|
|
14
|
+
list() {
|
|
15
|
+
return [...forms.values()];
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/** @experimental */
|
|
20
|
+
export function createMountedFormsSummaryTool(registry, name = 'get_mounted_forms_summary') {
|
|
21
|
+
return {
|
|
22
|
+
name,
|
|
23
|
+
description: 'Returns summaries of mounted form diagnostics registered in the app.',
|
|
24
|
+
inputSchema: {
|
|
25
|
+
type: 'object',
|
|
26
|
+
properties: {},
|
|
27
|
+
additionalProperties: false,
|
|
28
|
+
},
|
|
29
|
+
annotations: { readOnlyHint: true },
|
|
30
|
+
execute: () => ({ forms: registry.list() }),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { WebMcpToolDescriptor } from '@tooluminati/core';
|
|
2
|
+
import type { ClientErrorBuffer } from './errors';
|
|
3
|
+
export type TroubleshootingEventCategory = 'client_error' | 'fetch_failure' | 'route_change' | 'action_blocked' | 'tool_activated' | 'tool_cancelled' | 'agent_form_submit' | 'toolchange' | 'custom';
|
|
4
|
+
export interface TroubleshootingEventBase {
|
|
5
|
+
id: string;
|
|
6
|
+
category: TroubleshootingEventCategory;
|
|
7
|
+
timestamp: string;
|
|
8
|
+
title: string;
|
|
9
|
+
detail?: string | undefined;
|
|
10
|
+
metadata?: Record<string, string> | undefined;
|
|
11
|
+
untrusted?: boolean | undefined;
|
|
12
|
+
}
|
|
13
|
+
export interface ClientErrorTimelineEvent extends TroubleshootingEventBase {
|
|
14
|
+
category: 'client_error';
|
|
15
|
+
message: string;
|
|
16
|
+
source?: string | undefined;
|
|
17
|
+
}
|
|
18
|
+
export interface FetchFailureTimelineEvent extends TroubleshootingEventBase {
|
|
19
|
+
category: 'fetch_failure';
|
|
20
|
+
method: string;
|
|
21
|
+
url: string;
|
|
22
|
+
status?: number | undefined;
|
|
23
|
+
durationMs?: number | undefined;
|
|
24
|
+
}
|
|
25
|
+
export interface RouteChangeTimelineEvent extends TroubleshootingEventBase {
|
|
26
|
+
category: 'route_change';
|
|
27
|
+
from?: string | undefined;
|
|
28
|
+
to: string;
|
|
29
|
+
}
|
|
30
|
+
export interface ActionBlockedTimelineEvent extends TroubleshootingEventBase {
|
|
31
|
+
category: 'action_blocked';
|
|
32
|
+
actionId: string;
|
|
33
|
+
reasons: string[];
|
|
34
|
+
}
|
|
35
|
+
export interface ToolLifecycleTimelineEvent extends TroubleshootingEventBase {
|
|
36
|
+
category: 'tool_activated' | 'tool_cancelled' | 'agent_form_submit';
|
|
37
|
+
toolName?: string | undefined;
|
|
38
|
+
formId?: string | undefined;
|
|
39
|
+
}
|
|
40
|
+
export interface ToolChangeTimelineEvent extends TroubleshootingEventBase {
|
|
41
|
+
category: 'toolchange';
|
|
42
|
+
added: string[];
|
|
43
|
+
removed: string[];
|
|
44
|
+
}
|
|
45
|
+
export interface CustomTimelineEvent extends TroubleshootingEventBase {
|
|
46
|
+
category: 'custom';
|
|
47
|
+
}
|
|
48
|
+
export type TroubleshootingEvent = ClientErrorTimelineEvent | FetchFailureTimelineEvent | RouteChangeTimelineEvent | ActionBlockedTimelineEvent | ToolLifecycleTimelineEvent | ToolChangeTimelineEvent | CustomTimelineEvent;
|
|
49
|
+
export interface TroubleshootingTimelineBufferOptions {
|
|
50
|
+
maxSize?: number;
|
|
51
|
+
redact?: (event: TroubleshootingEvent) => TroubleshootingEvent;
|
|
52
|
+
}
|
|
53
|
+
export type TroubleshootingEventInput = Omit<ClientErrorTimelineEvent, 'id' | 'timestamp'> | Omit<FetchFailureTimelineEvent, 'id' | 'timestamp'> | Omit<RouteChangeTimelineEvent, 'id' | 'timestamp'> | Omit<ActionBlockedTimelineEvent, 'id' | 'timestamp'> | Omit<ToolLifecycleTimelineEvent, 'id' | 'timestamp'> | Omit<ToolChangeTimelineEvent, 'id' | 'timestamp'> | Omit<CustomTimelineEvent, 'id' | 'timestamp'>;
|
|
54
|
+
export declare class TroubleshootingTimelineBuffer {
|
|
55
|
+
private readonly events;
|
|
56
|
+
private readonly maxSize;
|
|
57
|
+
private readonly redact?;
|
|
58
|
+
constructor(options?: TroubleshootingTimelineBufferOptions | number);
|
|
59
|
+
push(event: TroubleshootingEventInput & {
|
|
60
|
+
id?: string;
|
|
61
|
+
timestamp?: string;
|
|
62
|
+
}): TroubleshootingEvent;
|
|
63
|
+
list(options?: {
|
|
64
|
+
categories?: TroubleshootingEventCategory[];
|
|
65
|
+
since?: string;
|
|
66
|
+
limit?: number;
|
|
67
|
+
}): TroubleshootingEvent[];
|
|
68
|
+
summary(events?: TroubleshootingEvent[]): {
|
|
69
|
+
total: number;
|
|
70
|
+
byCategory: Record<string, number>;
|
|
71
|
+
};
|
|
72
|
+
clear(): void;
|
|
73
|
+
}
|
|
74
|
+
export interface FetchFailureTrackerOptions {
|
|
75
|
+
buffer: TroubleshootingTimelineBuffer;
|
|
76
|
+
enabled?: boolean;
|
|
77
|
+
redactBody?: boolean;
|
|
78
|
+
ignoreUrls?: RegExp;
|
|
79
|
+
}
|
|
80
|
+
export declare function createFetchFailureTracker(options: FetchFailureTrackerOptions): () => void;
|
|
81
|
+
export declare function createTimelineFromErrorBuffer(buffer: ClientErrorBuffer, timeline: TroubleshootingTimelineBuffer): () => void;
|
|
82
|
+
export interface WebMcpLifecycleCollectorOptions {
|
|
83
|
+
buffer: TroubleshootingTimelineBuffer;
|
|
84
|
+
enabled?: boolean;
|
|
85
|
+
}
|
|
86
|
+
export declare function createWebMcpLifecycleCollector(options: WebMcpLifecycleCollectorOptions): () => void;
|
|
87
|
+
export interface TroubleshootingTimelineToolInput {
|
|
88
|
+
categories?: string[];
|
|
89
|
+
since?: string;
|
|
90
|
+
limit?: number;
|
|
91
|
+
}
|
|
92
|
+
export declare function createTroubleshootingTimelineTool(buffer: TroubleshootingTimelineBuffer, name?: string): WebMcpToolDescriptor<TroubleshootingTimelineToolInput, {
|
|
93
|
+
events: TroubleshootingEvent[];
|
|
94
|
+
summary: {
|
|
95
|
+
total: number;
|
|
96
|
+
byCategory: Record<string, number>;
|
|
97
|
+
};
|
|
98
|
+
}>;
|
|
99
|
+
//# sourceMappingURL=timeline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timeline.d.ts","sourceRoot":"","sources":["../src/timeline.ts"],"names":[],"mappings":"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"}
|
package/dist/timeline.js
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
const DEFAULT_MAX_SIZE = 50;
|
|
2
|
+
const DEFAULT_TOOL_LIMIT = 20;
|
|
3
|
+
const VALID_CATEGORIES = new Set([
|
|
4
|
+
'client_error',
|
|
5
|
+
'fetch_failure',
|
|
6
|
+
'route_change',
|
|
7
|
+
'action_blocked',
|
|
8
|
+
'tool_activated',
|
|
9
|
+
'tool_cancelled',
|
|
10
|
+
'agent_form_submit',
|
|
11
|
+
'toolchange',
|
|
12
|
+
'custom',
|
|
13
|
+
]);
|
|
14
|
+
let nextEventId = 0;
|
|
15
|
+
function createEventId() {
|
|
16
|
+
nextEventId += 1;
|
|
17
|
+
return `tlm-${nextEventId}`;
|
|
18
|
+
}
|
|
19
|
+
function stripQuerySecrets(url) {
|
|
20
|
+
try {
|
|
21
|
+
const parsed = new URL(url, 'http://local');
|
|
22
|
+
parsed.search = '';
|
|
23
|
+
return parsed.pathname + (parsed.hash || '');
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return url.split('?')[0] ?? url;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export class TroubleshootingTimelineBuffer {
|
|
30
|
+
events = [];
|
|
31
|
+
maxSize;
|
|
32
|
+
redact;
|
|
33
|
+
constructor(options = {}) {
|
|
34
|
+
if (typeof options === 'number') {
|
|
35
|
+
this.maxSize = options;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
this.maxSize = options.maxSize ?? DEFAULT_MAX_SIZE;
|
|
39
|
+
if (options.redact) {
|
|
40
|
+
this.redact = options.redact;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
push(event) {
|
|
45
|
+
const stored = {
|
|
46
|
+
...event,
|
|
47
|
+
id: event.id ?? createEventId(),
|
|
48
|
+
timestamp: event.timestamp ?? new Date().toISOString(),
|
|
49
|
+
};
|
|
50
|
+
const finalEvent = this.redact?.(stored) ?? stored;
|
|
51
|
+
this.events.unshift(finalEvent);
|
|
52
|
+
this.events.splice(this.maxSize);
|
|
53
|
+
return finalEvent;
|
|
54
|
+
}
|
|
55
|
+
list(options) {
|
|
56
|
+
const limit = Math.max(0, Math.min(options?.limit ?? DEFAULT_TOOL_LIMIT, DEFAULT_TOOL_LIMIT));
|
|
57
|
+
let result = [...this.events];
|
|
58
|
+
if (options?.since) {
|
|
59
|
+
const sinceMs = Date.parse(options.since);
|
|
60
|
+
if (Number.isNaN(sinceMs)) {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
result = result.filter((e) => Date.parse(e.timestamp) >= sinceMs);
|
|
64
|
+
}
|
|
65
|
+
if (options?.categories?.length) {
|
|
66
|
+
const cats = new Set(options.categories);
|
|
67
|
+
result = result.filter((e) => cats.has(e.category));
|
|
68
|
+
}
|
|
69
|
+
return result.slice(0, limit);
|
|
70
|
+
}
|
|
71
|
+
summary(events = this.events) {
|
|
72
|
+
const byCategory = {};
|
|
73
|
+
for (const event of events) {
|
|
74
|
+
byCategory[event.category] = (byCategory[event.category] ?? 0) + 1;
|
|
75
|
+
}
|
|
76
|
+
return { total: events.length, byCategory };
|
|
77
|
+
}
|
|
78
|
+
clear() {
|
|
79
|
+
this.events.splice(0);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
let fetchTrackerCount = 0;
|
|
83
|
+
let originalFetch;
|
|
84
|
+
export function createFetchFailureTracker(options) {
|
|
85
|
+
if (options.enabled === false || typeof window === 'undefined') {
|
|
86
|
+
return () => { };
|
|
87
|
+
}
|
|
88
|
+
if (fetchTrackerCount === 0) {
|
|
89
|
+
originalFetch = window.fetch.bind(window);
|
|
90
|
+
}
|
|
91
|
+
fetchTrackerCount += 1;
|
|
92
|
+
const baseFetch = originalFetch;
|
|
93
|
+
window.fetch = async (input, init) => {
|
|
94
|
+
const started = performance.now();
|
|
95
|
+
const method = (init?.method ?? 'GET').toUpperCase();
|
|
96
|
+
const url = typeof input === 'string'
|
|
97
|
+
? input
|
|
98
|
+
: input instanceof URL
|
|
99
|
+
? input.toString()
|
|
100
|
+
: input.url;
|
|
101
|
+
if (options.ignoreUrls?.test(url)) {
|
|
102
|
+
return baseFetch(input, init);
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
const response = await baseFetch(input, init);
|
|
106
|
+
if (!response.ok) {
|
|
107
|
+
const detail = options.redactBody === false
|
|
108
|
+
? `HTTP request failed before UI state updated.`
|
|
109
|
+
: 'HTTP request failed before UI state updated.';
|
|
110
|
+
options.buffer.push({
|
|
111
|
+
category: 'fetch_failure',
|
|
112
|
+
title: `${method} ${stripQuerySecrets(url)} — ${response.status}`,
|
|
113
|
+
method,
|
|
114
|
+
url: stripQuerySecrets(url),
|
|
115
|
+
status: response.status,
|
|
116
|
+
durationMs: Math.round(performance.now() - started),
|
|
117
|
+
detail,
|
|
118
|
+
metadata: {
|
|
119
|
+
host: safeHost(url),
|
|
120
|
+
status: String(response.status),
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return response;
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
options.buffer.push({
|
|
128
|
+
category: 'fetch_failure',
|
|
129
|
+
title: `${method} ${stripQuerySecrets(url)} — network error`,
|
|
130
|
+
method,
|
|
131
|
+
url: stripQuerySecrets(url),
|
|
132
|
+
durationMs: Math.round(performance.now() - started),
|
|
133
|
+
detail: error instanceof Error ? error.message : 'Network request failed.',
|
|
134
|
+
metadata: { host: safeHost(url) },
|
|
135
|
+
});
|
|
136
|
+
throw error;
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
return () => {
|
|
140
|
+
fetchTrackerCount = Math.max(0, fetchTrackerCount - 1);
|
|
141
|
+
if (fetchTrackerCount === 0 && originalFetch) {
|
|
142
|
+
window.fetch = originalFetch;
|
|
143
|
+
originalFetch = undefined;
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
function safeHost(url) {
|
|
148
|
+
try {
|
|
149
|
+
return new URL(url, window.location.origin).host;
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return 'unknown';
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
export function createTimelineFromErrorBuffer(buffer, timeline) {
|
|
156
|
+
const originalPush = buffer.push.bind(buffer);
|
|
157
|
+
buffer.push = (error) => {
|
|
158
|
+
originalPush(error);
|
|
159
|
+
timeline.push({
|
|
160
|
+
category: 'client_error',
|
|
161
|
+
title: error.message,
|
|
162
|
+
message: error.message,
|
|
163
|
+
source: error.source,
|
|
164
|
+
detail: 'Client error captured by diagnostics buffer.',
|
|
165
|
+
metadata: {
|
|
166
|
+
source: error.source,
|
|
167
|
+
...(error.route ? { route: error.route } : {}),
|
|
168
|
+
},
|
|
169
|
+
untrusted: true,
|
|
170
|
+
});
|
|
171
|
+
};
|
|
172
|
+
return () => {
|
|
173
|
+
buffer.push = originalPush;
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
export function createWebMcpLifecycleCollector(options) {
|
|
177
|
+
if (options.enabled === false || typeof window === 'undefined') {
|
|
178
|
+
return () => { };
|
|
179
|
+
}
|
|
180
|
+
const cleanups = [];
|
|
181
|
+
const onToolActivated = (event) => {
|
|
182
|
+
const detail = event.detail;
|
|
183
|
+
options.buffer.push({
|
|
184
|
+
category: 'tool_activated',
|
|
185
|
+
title: `agent activated ${detail?.toolName ?? 'form tool'}`,
|
|
186
|
+
toolName: detail?.toolName,
|
|
187
|
+
formId: detail?.form?.id,
|
|
188
|
+
detail: 'Declarative form tool activated by the agent.',
|
|
189
|
+
});
|
|
190
|
+
};
|
|
191
|
+
const onToolCancel = (event) => {
|
|
192
|
+
const detail = event.detail;
|
|
193
|
+
options.buffer.push({
|
|
194
|
+
category: 'tool_cancelled',
|
|
195
|
+
title: `agent cancelled ${detail?.toolName ?? 'form tool'}`,
|
|
196
|
+
toolName: detail?.toolName,
|
|
197
|
+
detail: 'Agent cancelled a declarative form tool.',
|
|
198
|
+
});
|
|
199
|
+
};
|
|
200
|
+
const onAgentFormSubmit = (event) => {
|
|
201
|
+
const submitEvent = event;
|
|
202
|
+
if (!submitEvent.agentInvoked) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const form = event.target;
|
|
206
|
+
const toolName = form?.getAttribute('toolname') ?? 'form';
|
|
207
|
+
options.buffer.push({
|
|
208
|
+
category: 'agent_form_submit',
|
|
209
|
+
title: `agent submitted ${toolName}`,
|
|
210
|
+
toolName,
|
|
211
|
+
formId: form?.id,
|
|
212
|
+
detail: 'Agent invoked declarative form submit.',
|
|
213
|
+
});
|
|
214
|
+
};
|
|
215
|
+
const onToolChange = () => {
|
|
216
|
+
options.buffer.push({
|
|
217
|
+
category: 'toolchange',
|
|
218
|
+
title: 'tool registry changed',
|
|
219
|
+
added: [],
|
|
220
|
+
removed: [],
|
|
221
|
+
detail: 'WebMCP tool registry changed on document.modelContext.',
|
|
222
|
+
});
|
|
223
|
+
};
|
|
224
|
+
window.addEventListener('toolactivated', onToolActivated);
|
|
225
|
+
window.addEventListener('toolcancel', onToolCancel);
|
|
226
|
+
window.addEventListener('submit', onAgentFormSubmit, true);
|
|
227
|
+
cleanups.push(() => {
|
|
228
|
+
window.removeEventListener('toolactivated', onToolActivated);
|
|
229
|
+
window.removeEventListener('toolcancel', onToolCancel);
|
|
230
|
+
window.removeEventListener('submit', onAgentFormSubmit, true);
|
|
231
|
+
});
|
|
232
|
+
const modelContext = document.modelContext;
|
|
233
|
+
if (modelContext?.addEventListener) {
|
|
234
|
+
modelContext.addEventListener('toolchange', onToolChange);
|
|
235
|
+
cleanups.push(() => {
|
|
236
|
+
modelContext.removeEventListener('toolchange', onToolChange);
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
return () => {
|
|
240
|
+
for (const cleanup of cleanups) {
|
|
241
|
+
cleanup();
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
export function createTroubleshootingTimelineTool(buffer, name = 'get_troubleshooting_timeline') {
|
|
246
|
+
return {
|
|
247
|
+
name,
|
|
248
|
+
description: 'Returns a bounded, redacted timeline of recent runtime incidents.',
|
|
249
|
+
inputSchema: {
|
|
250
|
+
type: 'object',
|
|
251
|
+
properties: {
|
|
252
|
+
categories: {
|
|
253
|
+
type: 'array',
|
|
254
|
+
items: { type: 'string' },
|
|
255
|
+
description: 'Optional event categories to include.',
|
|
256
|
+
},
|
|
257
|
+
since: {
|
|
258
|
+
type: 'string',
|
|
259
|
+
description: 'ISO timestamp; only events after this time.',
|
|
260
|
+
},
|
|
261
|
+
limit: {
|
|
262
|
+
type: 'integer',
|
|
263
|
+
maximum: DEFAULT_TOOL_LIMIT,
|
|
264
|
+
description: 'Max events to return (cap 20).',
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
additionalProperties: false,
|
|
268
|
+
},
|
|
269
|
+
annotations: { readOnlyHint: true, untrustedContentHint: true },
|
|
270
|
+
validateArgs(args) {
|
|
271
|
+
if (typeof args !== 'object' || args === null) {
|
|
272
|
+
return {};
|
|
273
|
+
}
|
|
274
|
+
const input = args;
|
|
275
|
+
if (input.categories?.length) {
|
|
276
|
+
const invalid = input.categories.filter((category) => !VALID_CATEGORIES.has(category));
|
|
277
|
+
if (invalid.length) {
|
|
278
|
+
throw new Error(`Unknown timeline categories: ${invalid.join(', ')}`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (input.limit !== undefined && input.limit < 0) {
|
|
282
|
+
throw new Error('limit must be >= 0.');
|
|
283
|
+
}
|
|
284
|
+
return input;
|
|
285
|
+
},
|
|
286
|
+
execute: (input) => {
|
|
287
|
+
const events = buffer.list({
|
|
288
|
+
...(input.categories?.length
|
|
289
|
+
? {
|
|
290
|
+
categories: input.categories,
|
|
291
|
+
}
|
|
292
|
+
: {}),
|
|
293
|
+
...(input.since ? { since: input.since } : {}),
|
|
294
|
+
...(input.limit !== undefined ? { limit: input.limit } : {}),
|
|
295
|
+
});
|
|
296
|
+
return {
|
|
297
|
+
events,
|
|
298
|
+
summary: buffer.summary(events),
|
|
299
|
+
};
|
|
300
|
+
},
|
|
301
|
+
};
|
|
302
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timeline.test.d.ts","sourceRoot":"","sources":["../src/timeline.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { TroubleshootingTimelineBuffer, createFetchFailureTracker, createTroubleshootingTimelineTool, } from './timeline';
|
|
3
|
+
describe('TroubleshootingTimelineBuffer', () => {
|
|
4
|
+
it('stores events newest first with limit enforcement', () => {
|
|
5
|
+
const buffer = new TroubleshootingTimelineBuffer({ maxSize: 2 });
|
|
6
|
+
buffer.push({
|
|
7
|
+
category: 'route_change',
|
|
8
|
+
title: 'a',
|
|
9
|
+
to: '/a',
|
|
10
|
+
});
|
|
11
|
+
buffer.push({
|
|
12
|
+
category: 'route_change',
|
|
13
|
+
title: 'b',
|
|
14
|
+
to: '/b',
|
|
15
|
+
});
|
|
16
|
+
buffer.push({
|
|
17
|
+
category: 'route_change',
|
|
18
|
+
title: 'c',
|
|
19
|
+
to: '/c',
|
|
20
|
+
});
|
|
21
|
+
const events = buffer.list();
|
|
22
|
+
expect(events).toHaveLength(2);
|
|
23
|
+
expect(events[0]?.title).toBe('c');
|
|
24
|
+
});
|
|
25
|
+
it('filters by category and respects tool limit cap', () => {
|
|
26
|
+
const buffer = new TroubleshootingTimelineBuffer();
|
|
27
|
+
for (let i = 0; i < 25; i += 1) {
|
|
28
|
+
buffer.push({
|
|
29
|
+
category: 'client_error',
|
|
30
|
+
title: `err-${i}`,
|
|
31
|
+
message: `err-${i}`,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
const filtered = buffer.list({
|
|
35
|
+
categories: ['client_error'],
|
|
36
|
+
limit: 100,
|
|
37
|
+
});
|
|
38
|
+
expect(filtered).toHaveLength(20);
|
|
39
|
+
});
|
|
40
|
+
it('redacts events when configured', () => {
|
|
41
|
+
const buffer = new TroubleshootingTimelineBuffer({
|
|
42
|
+
redact: (event) => ({
|
|
43
|
+
...event,
|
|
44
|
+
title: event.title.replace(/secret/gi, '[redacted]'),
|
|
45
|
+
}),
|
|
46
|
+
});
|
|
47
|
+
buffer.push({
|
|
48
|
+
category: 'custom',
|
|
49
|
+
title: 'secret value',
|
|
50
|
+
});
|
|
51
|
+
expect(buffer.list()[0]?.title).toBe('[redacted] value');
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
describe('createFetchFailureTracker', () => {
|
|
55
|
+
it('records non-ok fetch responses', async () => {
|
|
56
|
+
const buffer = new TroubleshootingTimelineBuffer();
|
|
57
|
+
vi.stubGlobal('fetch', vi.fn(async () => ({ ok: false, status: 403 })));
|
|
58
|
+
const restore = createFetchFailureTracker({ buffer });
|
|
59
|
+
await fetch('/api/checkout?token=abc');
|
|
60
|
+
restore();
|
|
61
|
+
vi.unstubAllGlobals();
|
|
62
|
+
const events = buffer.list({ categories: ['fetch_failure'] });
|
|
63
|
+
expect(events).toHaveLength(1);
|
|
64
|
+
expect(events[0]?.title).toContain('403');
|
|
65
|
+
const fetchEvent = events[0];
|
|
66
|
+
if (fetchEvent?.category === 'fetch_failure') {
|
|
67
|
+
expect(fetchEvent.url).not.toContain('token');
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
describe('createTroubleshootingTimelineTool', () => {
|
|
72
|
+
it('returns bounded events and summary', async () => {
|
|
73
|
+
const buffer = new TroubleshootingTimelineBuffer();
|
|
74
|
+
buffer.push({
|
|
75
|
+
category: 'action_blocked',
|
|
76
|
+
title: 'submit blocked',
|
|
77
|
+
actionId: 'submit',
|
|
78
|
+
reasons: ['invalid'],
|
|
79
|
+
});
|
|
80
|
+
buffer.push({
|
|
81
|
+
category: 'client_error',
|
|
82
|
+
title: 'err',
|
|
83
|
+
message: 'err',
|
|
84
|
+
});
|
|
85
|
+
const tool = createTroubleshootingTimelineTool(buffer);
|
|
86
|
+
const result = await tool.execute({ categories: ['action_blocked'] }, {});
|
|
87
|
+
expect(result.events).toHaveLength(1);
|
|
88
|
+
expect(result.summary.total).toBe(1);
|
|
89
|
+
});
|
|
90
|
+
it('rejects negative limits', () => {
|
|
91
|
+
const tool = createTroubleshootingTimelineTool(new TroubleshootingTimelineBuffer());
|
|
92
|
+
expect(() => tool.validateArgs?.({ limit: -1 })).toThrow('limit must be >= 0');
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { TroubleshootingEvent } from './timeline';
|
|
2
|
+
import type { ClientErrorSummary } from './errors';
|
|
3
|
+
import type { WebMcpEnvironmentCheck } from './webmcp-environment';
|
|
4
|
+
export interface TroubleshootingGuidanceEntry {
|
|
5
|
+
title: string;
|
|
6
|
+
hint: string;
|
|
7
|
+
severity: 'info' | 'warn' | 'error';
|
|
8
|
+
}
|
|
9
|
+
export declare const TROUBLESHOOTING_GUIDANCE: Record<string, TroubleshootingGuidanceEntry>;
|
|
10
|
+
export declare function getGuidanceForEnvironmentCheck(check: WebMcpEnvironmentCheck): TroubleshootingGuidanceEntry | undefined;
|
|
11
|
+
export declare function getGuidanceForTimelineEvent(event: TroubleshootingEvent): TroubleshootingGuidanceEntry | undefined;
|
|
12
|
+
export declare function getGuidanceForClientError(error: ClientErrorSummary): TroubleshootingGuidanceEntry | undefined;
|
|
13
|
+
export type TimelineFilterGroup = 'all' | 'failures' | 'blocked' | 'tools' | 'nav';
|
|
14
|
+
export declare function timelineEventGroup(category: TroubleshootingEvent['category']): TimelineFilterGroup;
|
|
15
|
+
//# sourceMappingURL=troubleshooting-guidance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"troubleshooting-guidance.d.ts","sourceRoot":"","sources":["../src/troubleshooting-guidance.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AACvD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AACnD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAEnE,MAAM,WAAW,4BAA4B;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;CACrC;AAED,eAAO,MAAM,wBAAwB,EAAE,MAAM,CAAC,MAAM,EAAE,4BAA4B,CA8F/E,CAAC;AAEJ,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,sBAAsB,GAC5B,4BAA4B,GAAG,SAAS,CAc1C;AAED,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,oBAAoB,GAC1B,4BAA4B,GAAG,SAAS,CAS1C;AAED,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,kBAAkB,GACxB,4BAA4B,GAAG,SAAS,CAO1C;AAED,MAAM,MAAM,mBAAmB,GAC3B,KAAK,GACL,UAAU,GACV,SAAS,GACT,OAAO,GACP,KAAK,CAAC;AAEV,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,oBAAoB,CAAC,UAAU,CAAC,GACzC,mBAAmB,CAkBrB"}
|