@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,130 @@
|
|
|
1
|
+
export const TROUBLESHOOTING_GUIDANCE = {
|
|
2
|
+
'document.modelContext': {
|
|
3
|
+
title: 'WebMCP host missing',
|
|
4
|
+
hint: 'No document.modelContext on this page. Enable WebMCP in Chrome or ' +
|
|
5
|
+
'attach the Model Context Inspector Extension. Tools cannot register ' +
|
|
6
|
+
'until this resolves.',
|
|
7
|
+
severity: 'error',
|
|
8
|
+
},
|
|
9
|
+
'navigator.modelContext': {
|
|
10
|
+
title: 'Deprecated fallback in use',
|
|
11
|
+
hint: 'Migrate from navigator.modelContext to document.modelContext. The ' +
|
|
12
|
+
'fallback is not a supported long-term surface.',
|
|
13
|
+
severity: 'warn',
|
|
14
|
+
},
|
|
15
|
+
'origin-isolation': {
|
|
16
|
+
title: 'Origin isolation review',
|
|
17
|
+
hint: 'Origin-Agent-Cluster may be ?0 when document.domain is set. Remove ' +
|
|
18
|
+
'document.domain writes or set Origin-Agent-Cluster: ?1.',
|
|
19
|
+
severity: 'warn',
|
|
20
|
+
},
|
|
21
|
+
'permissions-policy': {
|
|
22
|
+
title: 'Permissions-Policy review',
|
|
23
|
+
hint: 'Cross-origin iframes may block tools unless Permissions-Policy allows ' +
|
|
24
|
+
'the tools feature for this origin.',
|
|
25
|
+
severity: 'warn',
|
|
26
|
+
},
|
|
27
|
+
'tool-count': {
|
|
28
|
+
title: 'No tools registered',
|
|
29
|
+
hint: 'Verify WebMcpProvider or provideWebMcpRegistry is mounted and tools ' +
|
|
30
|
+
'are registered in the current route scope.',
|
|
31
|
+
severity: 'warn',
|
|
32
|
+
},
|
|
33
|
+
'output-budget': {
|
|
34
|
+
title: 'Tool output budget exceeded',
|
|
35
|
+
hint: 'A tool emitted more than the recommended ~1.5K chars. Trim fields or ' +
|
|
36
|
+
'enable redaction to stay within Chrome guidance.',
|
|
37
|
+
severity: 'warn',
|
|
38
|
+
},
|
|
39
|
+
fetch_failure: {
|
|
40
|
+
title: 'Fetch failure',
|
|
41
|
+
hint: 'Inspect get_troubleshooting_timeline and get_workflow_blockers for ' +
|
|
42
|
+
'structured HTTP failure context.',
|
|
43
|
+
severity: 'error',
|
|
44
|
+
},
|
|
45
|
+
client_error: {
|
|
46
|
+
title: 'Client error',
|
|
47
|
+
hint: 'See get_recent_client_errors and the timeline client_error entries ' +
|
|
48
|
+
'for redacted stack traces in dev.',
|
|
49
|
+
severity: 'error',
|
|
50
|
+
},
|
|
51
|
+
action_blocked: {
|
|
52
|
+
title: 'Action blocked',
|
|
53
|
+
hint: 'Call why_is_action_unavailable or get_workflow_blockers for reasons ' +
|
|
54
|
+
'beyond disabled=true in the DOM.',
|
|
55
|
+
severity: 'warn',
|
|
56
|
+
},
|
|
57
|
+
toolchange: {
|
|
58
|
+
title: 'Tool registry changed',
|
|
59
|
+
hint: 'Tools were added or removed. Check route mounts and scoped providers ' +
|
|
60
|
+
'if the count is unexpected.',
|
|
61
|
+
severity: 'info',
|
|
62
|
+
},
|
|
63
|
+
WebMcpUnavailable: {
|
|
64
|
+
title: 'WebMCP unavailable',
|
|
65
|
+
hint: 'Tooluminati could not find document.modelContext. Registration was ' +
|
|
66
|
+
'deferred until a host becomes available.',
|
|
67
|
+
severity: 'error',
|
|
68
|
+
},
|
|
69
|
+
TypeError: {
|
|
70
|
+
title: 'Runtime TypeError',
|
|
71
|
+
hint: 'Check component props and optional chaining. Errors are captured by ' +
|
|
72
|
+
'WebMcpErrorBoundary when enabled.',
|
|
73
|
+
severity: 'error',
|
|
74
|
+
},
|
|
75
|
+
NetworkError: {
|
|
76
|
+
title: 'Network failure',
|
|
77
|
+
hint: 'Verify API availability and CORS. Timeline fetch_failure entries ' +
|
|
78
|
+
'include status and timing.',
|
|
79
|
+
severity: 'error',
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
export function getGuidanceForEnvironmentCheck(check) {
|
|
83
|
+
if (check.hint) {
|
|
84
|
+
return {
|
|
85
|
+
title: check.label,
|
|
86
|
+
hint: check.hint,
|
|
87
|
+
severity: check.status === 'fail'
|
|
88
|
+
? 'error'
|
|
89
|
+
: check.status === 'warn'
|
|
90
|
+
? 'warn'
|
|
91
|
+
: 'info',
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
return TROUBLESHOOTING_GUIDANCE[check.id];
|
|
95
|
+
}
|
|
96
|
+
export function getGuidanceForTimelineEvent(event) {
|
|
97
|
+
const base = TROUBLESHOOTING_GUIDANCE[event.category];
|
|
98
|
+
if (base) {
|
|
99
|
+
return {
|
|
100
|
+
...base,
|
|
101
|
+
hint: event.detail ? `${base.hint} ${event.detail}` : base.hint,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
export function getGuidanceForClientError(error) {
|
|
107
|
+
for (const key of ['WebMcpUnavailable', 'TypeError', 'NetworkError']) {
|
|
108
|
+
if (error.message.includes(key)) {
|
|
109
|
+
return TROUBLESHOOTING_GUIDANCE[key];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return TROUBLESHOOTING_GUIDANCE.client_error;
|
|
113
|
+
}
|
|
114
|
+
export function timelineEventGroup(category) {
|
|
115
|
+
if (category === 'fetch_failure' || category === 'client_error') {
|
|
116
|
+
return 'failures';
|
|
117
|
+
}
|
|
118
|
+
if (category === 'action_blocked') {
|
|
119
|
+
return 'blocked';
|
|
120
|
+
}
|
|
121
|
+
if (category === 'tool_activated' ||
|
|
122
|
+
category === 'tool_cancelled' ||
|
|
123
|
+
category === 'toolchange') {
|
|
124
|
+
return 'tools';
|
|
125
|
+
}
|
|
126
|
+
if (category === 'route_change' || category === 'agent_form_submit') {
|
|
127
|
+
return 'nav';
|
|
128
|
+
}
|
|
129
|
+
return 'all';
|
|
130
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"troubleshooting-guidance.test.d.ts","sourceRoot":"","sources":["../src/troubleshooting-guidance.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { TROUBLESHOOTING_GUIDANCE, getGuidanceForClientError, getGuidanceForEnvironmentCheck, timelineEventGroup, } from './troubleshooting-guidance';
|
|
3
|
+
describe('troubleshooting guidance', () => {
|
|
4
|
+
it('returns environment check guidance by id', () => {
|
|
5
|
+
const guidance = getGuidanceForEnvironmentCheck({
|
|
6
|
+
id: 'document.modelContext',
|
|
7
|
+
label: 'document.modelContext',
|
|
8
|
+
value: 'missing',
|
|
9
|
+
status: 'fail',
|
|
10
|
+
});
|
|
11
|
+
expect(guidance?.severity).toBe('error');
|
|
12
|
+
expect(TROUBLESHOOTING_GUIDANCE['document.modelContext']).toBeDefined();
|
|
13
|
+
});
|
|
14
|
+
it('matches client error patterns', () => {
|
|
15
|
+
const guidance = getGuidanceForClientError({
|
|
16
|
+
message: 'NetworkError: failed to fetch',
|
|
17
|
+
source: 'api.ts',
|
|
18
|
+
timestamp: new Date().toISOString(),
|
|
19
|
+
});
|
|
20
|
+
expect(guidance?.title).toBe('Network failure');
|
|
21
|
+
});
|
|
22
|
+
it('groups timeline categories', () => {
|
|
23
|
+
expect(timelineEventGroup('fetch_failure')).toBe('failures');
|
|
24
|
+
expect(timelineEventGroup('tool_activated')).toBe('tools');
|
|
25
|
+
expect(timelineEventGroup('route_change')).toBe('nav');
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { WebMcpRegistry } from '@tooluminati/core';
|
|
2
|
+
import type { ClientErrorBuffer } from './errors';
|
|
3
|
+
import { type TimelineFilterGroup } from './troubleshooting-guidance';
|
|
4
|
+
import type { TroubleshootingEvent, TroubleshootingTimelineBuffer } from './timeline';
|
|
5
|
+
import { type WebMcpEnvironmentCheck, type WebMcpEnvironmentSummary } from './webmcp-environment';
|
|
6
|
+
export type TroubleshootingPanelState = 'ok' | 'attention' | 'unsupported' | 'empty';
|
|
7
|
+
export interface TroubleshootingPanelSources {
|
|
8
|
+
timeline: TroubleshootingTimelineBuffer;
|
|
9
|
+
environment: () => WebMcpEnvironmentSummary;
|
|
10
|
+
errors: ClientErrorBuffer;
|
|
11
|
+
registry?: WebMcpRegistry | undefined;
|
|
12
|
+
}
|
|
13
|
+
export interface TroubleshootingPanelEventView {
|
|
14
|
+
id: string;
|
|
15
|
+
category: TroubleshootingEvent['category'];
|
|
16
|
+
group: TimelineFilterGroup;
|
|
17
|
+
title: string;
|
|
18
|
+
time: string;
|
|
19
|
+
detail?: string | undefined;
|
|
20
|
+
metadata?: Record<string, string> | undefined;
|
|
21
|
+
guidance?: string | undefined;
|
|
22
|
+
}
|
|
23
|
+
export interface TroubleshootingPanelErrorView {
|
|
24
|
+
id: string;
|
|
25
|
+
message: string;
|
|
26
|
+
source: string;
|
|
27
|
+
time: string;
|
|
28
|
+
count: number;
|
|
29
|
+
stack?: string | undefined;
|
|
30
|
+
guidance?: string | undefined;
|
|
31
|
+
}
|
|
32
|
+
export interface TroubleshootingPanelCheckView extends WebMcpEnvironmentCheck {
|
|
33
|
+
guidance?: string | undefined;
|
|
34
|
+
}
|
|
35
|
+
export interface TroubleshootingPanelSnapshot {
|
|
36
|
+
supported: boolean;
|
|
37
|
+
toolCount: number;
|
|
38
|
+
origin: string;
|
|
39
|
+
panelState: TroubleshootingPanelState;
|
|
40
|
+
issueCount: number;
|
|
41
|
+
checks: TroubleshootingPanelCheckView[];
|
|
42
|
+
events: TroubleshootingPanelEventView[];
|
|
43
|
+
errors: TroubleshootingPanelErrorView[];
|
|
44
|
+
warnings: string[];
|
|
45
|
+
updatedAt: string;
|
|
46
|
+
}
|
|
47
|
+
export interface TroubleshootingPanelViewModelOptions {
|
|
48
|
+
eventLimit?: number;
|
|
49
|
+
pollMs?: number | null;
|
|
50
|
+
registry?: WebMcpRegistry | undefined;
|
|
51
|
+
}
|
|
52
|
+
export interface TroubleshootingPanelViewModel {
|
|
53
|
+
refresh(): TroubleshootingPanelSnapshot;
|
|
54
|
+
subscribe(listener: () => void): () => void;
|
|
55
|
+
dispose(): void;
|
|
56
|
+
}
|
|
57
|
+
export interface TroubleshootingPanelVisibilityController {
|
|
58
|
+
visible: boolean;
|
|
59
|
+
collapsed: boolean;
|
|
60
|
+
show(): void;
|
|
61
|
+
hide(): void;
|
|
62
|
+
toggle(): void;
|
|
63
|
+
setCollapsed(collapsed: boolean): void;
|
|
64
|
+
subscribe(listener: () => void): () => void;
|
|
65
|
+
}
|
|
66
|
+
export declare function computeTroubleshootingPanelState(snapshot: Pick<TroubleshootingPanelSnapshot, 'supported' | 'toolCount' | 'issueCount' | 'events' | 'errors' | 'warnings'>): TroubleshootingPanelState;
|
|
67
|
+
export declare function serializeTroubleshootingDiagnostics(snapshot: TroubleshootingPanelSnapshot): string;
|
|
68
|
+
export declare function buildTroubleshootingPanelSnapshot(sources: TroubleshootingPanelSources, options?: TroubleshootingPanelViewModelOptions): TroubleshootingPanelSnapshot;
|
|
69
|
+
export declare function createTroubleshootingPanelViewModel(sources: TroubleshootingPanelSources, options?: TroubleshootingPanelViewModelOptions): TroubleshootingPanelViewModel;
|
|
70
|
+
export declare function createTroubleshootingPanelVisibilityController(options?: {
|
|
71
|
+
startCollapsed?: boolean;
|
|
72
|
+
storageKey?: string;
|
|
73
|
+
}): TroubleshootingPanelVisibilityController;
|
|
74
|
+
export declare function isPanelEnabledByDefault(enabled: boolean | 'auto' | undefined): boolean;
|
|
75
|
+
export declare function isPanelUrlOverrideEnabled(allowUrlOverride: boolean | undefined): boolean;
|
|
76
|
+
export declare function filterPanelEvents(events: TroubleshootingPanelEventView[], filter: TimelineFilterGroup): TroubleshootingPanelEventView[];
|
|
77
|
+
//# sourceMappingURL=troubleshooting-panel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"troubleshooting-panel.d.ts","sourceRoot":"","sources":["../src/troubleshooting-panel.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { getGuidanceForClientError, getGuidanceForEnvironmentCheck, getGuidanceForTimelineEvent, timelineEventGroup, } from './troubleshooting-guidance';
|
|
2
|
+
import { createWebMcpEnvironmentSummary, } from './webmcp-environment';
|
|
3
|
+
const STORAGE_KEY = 'tooluminati.troubleshootingPanel.collapsed';
|
|
4
|
+
function relativeTime(iso) {
|
|
5
|
+
const delta = Date.now() - Date.parse(iso);
|
|
6
|
+
if (Number.isNaN(delta) || delta < 0) {
|
|
7
|
+
return 'now';
|
|
8
|
+
}
|
|
9
|
+
const seconds = Math.round(delta / 1000);
|
|
10
|
+
if (seconds < 60) {
|
|
11
|
+
return `${seconds}s`;
|
|
12
|
+
}
|
|
13
|
+
const minutes = Math.round(seconds / 60);
|
|
14
|
+
return `${minutes}m`;
|
|
15
|
+
}
|
|
16
|
+
function dedupeErrors(errors) {
|
|
17
|
+
const counts = new Map();
|
|
18
|
+
for (const error of errors) {
|
|
19
|
+
counts.set(error.message, (counts.get(error.message) ?? 0) + 1);
|
|
20
|
+
}
|
|
21
|
+
const seen = new Set();
|
|
22
|
+
const views = [];
|
|
23
|
+
for (const error of errors) {
|
|
24
|
+
if (seen.has(error.message)) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
seen.add(error.message);
|
|
28
|
+
const guidance = getGuidanceForClientError(error)?.hint;
|
|
29
|
+
views.push({
|
|
30
|
+
id: error.message,
|
|
31
|
+
message: error.message,
|
|
32
|
+
source: error.source,
|
|
33
|
+
time: relativeTime(error.timestamp),
|
|
34
|
+
count: counts.get(error.message) ?? 1,
|
|
35
|
+
...(error.stack ? { stack: error.stack } : {}),
|
|
36
|
+
...(guidance ? { guidance } : {}),
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return views;
|
|
40
|
+
}
|
|
41
|
+
export function computeTroubleshootingPanelState(snapshot) {
|
|
42
|
+
if (!snapshot.supported) {
|
|
43
|
+
return 'unsupported';
|
|
44
|
+
}
|
|
45
|
+
const hasIncidents = snapshot.issueCount > 0 ||
|
|
46
|
+
snapshot.errors.length > 0 ||
|
|
47
|
+
snapshot.events.some((event) => event.group === 'failures' ||
|
|
48
|
+
event.group === 'blocked' ||
|
|
49
|
+
event.category === 'toolchange');
|
|
50
|
+
if (!hasIncidents &&
|
|
51
|
+
snapshot.events.length === 0 &&
|
|
52
|
+
snapshot.errors.length === 0) {
|
|
53
|
+
return snapshot.toolCount === 0 ? 'empty' : 'ok';
|
|
54
|
+
}
|
|
55
|
+
if (hasIncidents) {
|
|
56
|
+
return 'attention';
|
|
57
|
+
}
|
|
58
|
+
return 'ok';
|
|
59
|
+
}
|
|
60
|
+
export function serializeTroubleshootingDiagnostics(snapshot) {
|
|
61
|
+
return JSON.stringify({
|
|
62
|
+
webmcp: {
|
|
63
|
+
supported: snapshot.supported,
|
|
64
|
+
toolCount: snapshot.toolCount,
|
|
65
|
+
origin: snapshot.origin,
|
|
66
|
+
panelState: snapshot.panelState,
|
|
67
|
+
},
|
|
68
|
+
checks: snapshot.checks.map((c) => ({
|
|
69
|
+
label: c.label,
|
|
70
|
+
value: c.value,
|
|
71
|
+
status: c.status,
|
|
72
|
+
...(c.guidance ? { guidance: c.guidance } : {}),
|
|
73
|
+
})),
|
|
74
|
+
timeline: snapshot.events.map((e) => ({
|
|
75
|
+
category: e.category,
|
|
76
|
+
title: e.title,
|
|
77
|
+
t: e.time,
|
|
78
|
+
...(e.guidance ? { guidance: e.guidance } : {}),
|
|
79
|
+
...(e.metadata ? { metadata: e.metadata } : {}),
|
|
80
|
+
})),
|
|
81
|
+
clientErrors: snapshot.errors.map((e) => ({
|
|
82
|
+
message: e.message,
|
|
83
|
+
count: e.count,
|
|
84
|
+
...(e.guidance ? { guidance: e.guidance } : {}),
|
|
85
|
+
})),
|
|
86
|
+
warnings: snapshot.warnings,
|
|
87
|
+
}, null, 2);
|
|
88
|
+
}
|
|
89
|
+
export function buildTroubleshootingPanelSnapshot(sources, options = {}) {
|
|
90
|
+
const env = sources.environment() ??
|
|
91
|
+
createWebMcpEnvironmentSummary({
|
|
92
|
+
...(sources.registry ? { registry: sources.registry } : {}),
|
|
93
|
+
...(options.registry ? { registry: options.registry } : {}),
|
|
94
|
+
});
|
|
95
|
+
const eventLimit = options.eventLimit ?? 20;
|
|
96
|
+
const rawEvents = sources.timeline.list({ limit: eventLimit });
|
|
97
|
+
const events = rawEvents.map((event) => {
|
|
98
|
+
const guidance = getGuidanceForTimelineEvent(event)?.hint;
|
|
99
|
+
return {
|
|
100
|
+
id: event.id,
|
|
101
|
+
category: event.category,
|
|
102
|
+
group: timelineEventGroup(event.category),
|
|
103
|
+
title: event.title,
|
|
104
|
+
time: relativeTime(event.timestamp),
|
|
105
|
+
...(event.detail ? { detail: event.detail } : {}),
|
|
106
|
+
...(event.metadata ? { metadata: event.metadata } : {}),
|
|
107
|
+
...(guidance ? { guidance } : {}),
|
|
108
|
+
};
|
|
109
|
+
});
|
|
110
|
+
const checks = env.checks.map((check) => {
|
|
111
|
+
const guidance = getGuidanceForEnvironmentCheck(check)?.hint;
|
|
112
|
+
return {
|
|
113
|
+
...check,
|
|
114
|
+
...(guidance ? { guidance } : {}),
|
|
115
|
+
};
|
|
116
|
+
});
|
|
117
|
+
const errors = dedupeErrors(sources.errors.list());
|
|
118
|
+
const warnCount = checks.filter((c) => c.status === 'warn' || c.status === 'fail').length;
|
|
119
|
+
const incidentEvents = events.filter((event) => event.group === 'failures' || event.group === 'blocked').length;
|
|
120
|
+
const issueCount = warnCount + errors.length + incidentEvents;
|
|
121
|
+
const snapshot = {
|
|
122
|
+
supported: env.supported,
|
|
123
|
+
toolCount: env.toolCount,
|
|
124
|
+
origin: env.origin,
|
|
125
|
+
issueCount,
|
|
126
|
+
checks,
|
|
127
|
+
events,
|
|
128
|
+
errors,
|
|
129
|
+
warnings: env.warnings,
|
|
130
|
+
updatedAt: new Date().toISOString(),
|
|
131
|
+
panelState: 'ok',
|
|
132
|
+
};
|
|
133
|
+
snapshot.panelState = computeTroubleshootingPanelState(snapshot);
|
|
134
|
+
return snapshot;
|
|
135
|
+
}
|
|
136
|
+
export function createTroubleshootingPanelViewModel(sources, options = {}) {
|
|
137
|
+
const listeners = new Set();
|
|
138
|
+
let timer;
|
|
139
|
+
const notify = () => {
|
|
140
|
+
for (const listener of listeners) {
|
|
141
|
+
listener();
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
if (options.pollMs && options.pollMs > 0) {
|
|
145
|
+
timer = setInterval(notify, options.pollMs);
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
refresh() {
|
|
149
|
+
return buildTroubleshootingPanelSnapshot(sources, options);
|
|
150
|
+
},
|
|
151
|
+
subscribe(listener) {
|
|
152
|
+
listeners.add(listener);
|
|
153
|
+
return () => {
|
|
154
|
+
listeners.delete(listener);
|
|
155
|
+
};
|
|
156
|
+
},
|
|
157
|
+
dispose() {
|
|
158
|
+
listeners.clear();
|
|
159
|
+
if (timer) {
|
|
160
|
+
clearInterval(timer);
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
export function createTroubleshootingPanelVisibilityController(options) {
|
|
166
|
+
const storageKey = options?.storageKey ?? STORAGE_KEY;
|
|
167
|
+
const listeners = new Set();
|
|
168
|
+
let visible = true;
|
|
169
|
+
let collapsed = options?.startCollapsed ?? readCollapsed(storageKey);
|
|
170
|
+
const notify = () => {
|
|
171
|
+
for (const listener of listeners) {
|
|
172
|
+
listener();
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
return {
|
|
176
|
+
get visible() {
|
|
177
|
+
return visible;
|
|
178
|
+
},
|
|
179
|
+
get collapsed() {
|
|
180
|
+
return collapsed;
|
|
181
|
+
},
|
|
182
|
+
show() {
|
|
183
|
+
visible = true;
|
|
184
|
+
notify();
|
|
185
|
+
},
|
|
186
|
+
hide() {
|
|
187
|
+
visible = false;
|
|
188
|
+
notify();
|
|
189
|
+
},
|
|
190
|
+
toggle() {
|
|
191
|
+
visible = !visible;
|
|
192
|
+
notify();
|
|
193
|
+
},
|
|
194
|
+
setCollapsed(next) {
|
|
195
|
+
collapsed = next;
|
|
196
|
+
writeCollapsed(storageKey, next);
|
|
197
|
+
notify();
|
|
198
|
+
},
|
|
199
|
+
subscribe(listener) {
|
|
200
|
+
listeners.add(listener);
|
|
201
|
+
return () => {
|
|
202
|
+
listeners.delete(listener);
|
|
203
|
+
};
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
export function isPanelEnabledByDefault(enabled) {
|
|
208
|
+
if (enabled === true) {
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
if (enabled === false) {
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
const nodeEnv = typeof process !== 'undefined' ? process.env?.NODE_ENV : undefined;
|
|
215
|
+
if (!nodeEnv || nodeEnv === 'development' || nodeEnv === 'test') {
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
return nodeEnv !== 'production';
|
|
219
|
+
}
|
|
220
|
+
export function isPanelUrlOverrideEnabled(allowUrlOverride) {
|
|
221
|
+
if (!allowUrlOverride || typeof window === 'undefined') {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
return new URLSearchParams(window.location.search).has('tooluminati-panel');
|
|
225
|
+
}
|
|
226
|
+
function readCollapsed(storageKey) {
|
|
227
|
+
if (typeof sessionStorage === 'undefined') {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
return sessionStorage.getItem(storageKey) === '1';
|
|
231
|
+
}
|
|
232
|
+
function writeCollapsed(storageKey, collapsed) {
|
|
233
|
+
if (typeof sessionStorage === 'undefined') {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
sessionStorage.setItem(storageKey, collapsed ? '1' : '0');
|
|
237
|
+
}
|
|
238
|
+
export function filterPanelEvents(events, filter) {
|
|
239
|
+
if (filter === 'all') {
|
|
240
|
+
return events;
|
|
241
|
+
}
|
|
242
|
+
return events.filter((event) => event.group === filter);
|
|
243
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"troubleshooting-panel.test.d.ts","sourceRoot":"","sources":["../src/troubleshooting-panel.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { ClientErrorBuffer } from './errors';
|
|
3
|
+
import { buildTroubleshootingPanelSnapshot, computeTroubleshootingPanelState, createTroubleshootingPanelVisibilityController, filterPanelEvents, serializeTroubleshootingDiagnostics, } from './troubleshooting-panel';
|
|
4
|
+
import { TroubleshootingTimelineBuffer } from './timeline';
|
|
5
|
+
import { createWebMcpEnvironmentSummary } from './webmcp-environment';
|
|
6
|
+
const globalObject = {
|
|
7
|
+
window: {},
|
|
8
|
+
document: { modelContext: {} },
|
|
9
|
+
location: { origin: 'https://app.test' },
|
|
10
|
+
};
|
|
11
|
+
describe('troubleshooting panel snapshot', () => {
|
|
12
|
+
it('computes attention when warnings and errors exist', () => {
|
|
13
|
+
const timeline = new TroubleshootingTimelineBuffer();
|
|
14
|
+
const errors = new ClientErrorBuffer();
|
|
15
|
+
errors.push({
|
|
16
|
+
message: 'TypeError: boom',
|
|
17
|
+
source: 'app.tsx',
|
|
18
|
+
timestamp: new Date().toISOString(),
|
|
19
|
+
});
|
|
20
|
+
const snapshot = buildTroubleshootingPanelSnapshot({
|
|
21
|
+
timeline,
|
|
22
|
+
errors,
|
|
23
|
+
environment: () => createWebMcpEnvironmentSummary({
|
|
24
|
+
globalObject: {
|
|
25
|
+
window: {},
|
|
26
|
+
document: { modelContext: {}, domain: 'x.com' },
|
|
27
|
+
location: { origin: 'https://app.test' },
|
|
28
|
+
},
|
|
29
|
+
}),
|
|
30
|
+
});
|
|
31
|
+
expect(snapshot.panelState).toBe('attention');
|
|
32
|
+
expect(snapshot.errors[0]?.count).toBe(1);
|
|
33
|
+
});
|
|
34
|
+
it('serializes diagnostics JSON for copy', () => {
|
|
35
|
+
const snapshot = buildTroubleshootingPanelSnapshot({
|
|
36
|
+
timeline: new TroubleshootingTimelineBuffer(),
|
|
37
|
+
errors: new ClientErrorBuffer(),
|
|
38
|
+
environment: () => createWebMcpEnvironmentSummary({ globalObject }),
|
|
39
|
+
});
|
|
40
|
+
const json = serializeTroubleshootingDiagnostics(snapshot);
|
|
41
|
+
const parsed = JSON.parse(json);
|
|
42
|
+
expect(parsed).toHaveProperty('webmcp');
|
|
43
|
+
expect(parsed).toHaveProperty('timeline');
|
|
44
|
+
});
|
|
45
|
+
it('filters timeline events by group', () => {
|
|
46
|
+
const timeline = new TroubleshootingTimelineBuffer();
|
|
47
|
+
timeline.push({
|
|
48
|
+
category: 'fetch_failure',
|
|
49
|
+
title: 'fail',
|
|
50
|
+
method: 'GET',
|
|
51
|
+
url: '/api',
|
|
52
|
+
});
|
|
53
|
+
timeline.push({
|
|
54
|
+
category: 'route_change',
|
|
55
|
+
title: 'nav',
|
|
56
|
+
to: '/x',
|
|
57
|
+
});
|
|
58
|
+
const snapshot = buildTroubleshootingPanelSnapshot({
|
|
59
|
+
timeline,
|
|
60
|
+
errors: new ClientErrorBuffer(),
|
|
61
|
+
environment: () => createWebMcpEnvironmentSummary({ globalObject }),
|
|
62
|
+
});
|
|
63
|
+
const failures = filterPanelEvents(snapshot.events, 'failures');
|
|
64
|
+
expect(failures).toHaveLength(1);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
describe('computeTroubleshootingPanelState', () => {
|
|
68
|
+
it('returns unsupported when WebMCP is missing', () => {
|
|
69
|
+
expect(computeTroubleshootingPanelState({
|
|
70
|
+
supported: false,
|
|
71
|
+
toolCount: 0,
|
|
72
|
+
issueCount: 0,
|
|
73
|
+
events: [],
|
|
74
|
+
errors: [],
|
|
75
|
+
warnings: [],
|
|
76
|
+
})).toBe('unsupported');
|
|
77
|
+
});
|
|
78
|
+
it('returns attention when only timeline failures exist', () => {
|
|
79
|
+
const timeline = new TroubleshootingTimelineBuffer();
|
|
80
|
+
timeline.push({
|
|
81
|
+
category: 'fetch_failure',
|
|
82
|
+
title: 'GET /api — 403',
|
|
83
|
+
method: 'GET',
|
|
84
|
+
url: '/api',
|
|
85
|
+
status: 403,
|
|
86
|
+
});
|
|
87
|
+
const snapshot = buildTroubleshootingPanelSnapshot({
|
|
88
|
+
timeline,
|
|
89
|
+
errors: new ClientErrorBuffer(),
|
|
90
|
+
environment: () => createWebMcpEnvironmentSummary({ globalObject }),
|
|
91
|
+
});
|
|
92
|
+
expect(snapshot.panelState).toBe('attention');
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
describe('visibility controller', () => {
|
|
96
|
+
it('toggles collapsed state', () => {
|
|
97
|
+
const controller = createTroubleshootingPanelVisibilityController({
|
|
98
|
+
startCollapsed: true,
|
|
99
|
+
storageKey: 'test.collapsed',
|
|
100
|
+
});
|
|
101
|
+
expect(controller.collapsed).toBe(true);
|
|
102
|
+
controller.setCollapsed(false);
|
|
103
|
+
expect(controller.collapsed).toBe(false);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type WebMcpRegistry, type WebMcpToolDescriptor } from '@tooluminati/core';
|
|
2
|
+
export interface WebMcpEnvironmentCheck {
|
|
3
|
+
id: string;
|
|
4
|
+
label: string;
|
|
5
|
+
value: string;
|
|
6
|
+
status: 'ok' | 'warn' | 'fail' | 'muted';
|
|
7
|
+
hint?: string | undefined;
|
|
8
|
+
}
|
|
9
|
+
export interface WebMcpEnvironmentSummary {
|
|
10
|
+
supported: boolean;
|
|
11
|
+
toolCount: number;
|
|
12
|
+
origin: string;
|
|
13
|
+
usingNavigatorFallback: boolean;
|
|
14
|
+
checks: WebMcpEnvironmentCheck[];
|
|
15
|
+
warnings: string[];
|
|
16
|
+
}
|
|
17
|
+
export interface WebMcpEnvironmentSummaryOptions {
|
|
18
|
+
registry?: WebMcpRegistry | undefined;
|
|
19
|
+
globalObject?: typeof globalThis;
|
|
20
|
+
}
|
|
21
|
+
export declare function createWebMcpEnvironmentSummary(options?: WebMcpEnvironmentSummaryOptions): WebMcpEnvironmentSummary;
|
|
22
|
+
export declare function createWebMcpEnvironmentTool(getSummary: () => WebMcpEnvironmentSummary, name?: string): WebMcpToolDescriptor<Record<string, never>, WebMcpEnvironmentSummary>;
|
|
23
|
+
//# sourceMappingURL=webmcp-environment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webmcp-environment.d.ts","sourceRoot":"","sources":["../src/webmcp-environment.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,oBAAoB,EAC1B,MAAM,mBAAmB,CAAC;AAE3B,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IACzC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3B;AAED,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,sBAAsB,EAAE,OAAO,CAAC;IAChC,MAAM,EAAE,sBAAsB,EAAE,CAAC;IACjC,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;IACtC,YAAY,CAAC,EAAE,OAAO,UAAU,CAAC;CAClC;AAED,wBAAgB,8BAA8B,CAC5C,OAAO,GAAE,+BAAoC,GAC5C,wBAAwB,CAoG1B;AA8ED,wBAAgB,2BAA2B,CACzC,UAAU,EAAE,MAAM,wBAAwB,EAC1C,IAAI,SAA2B,GAC9B,oBAAoB,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,wBAAwB,CAAC,CAavE"}
|