chrome-devtools-frontend 1.0.1618066 → 1.0.1621064
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/docs/checklist/README.md +8 -7
- package/eslint.config.mjs +7 -0
- package/front_end/core/sdk/NetworkManager.ts +23 -2
- package/front_end/core/sdk/ServerSentEventsProtocol.ts +1 -1
- package/front_end/entrypoints/greendev_floaty/FloatyEntrypoint.ts +119 -13
- package/front_end/generated/InspectorBackendCommands.ts +6 -6
- package/front_end/generated/SupportedCSSProperties.js +100 -100
- package/front_end/generated/protocol-mapping.d.ts +1 -9
- package/front_end/generated/protocol-proxy-api.d.ts +1 -9
- package/front_end/generated/protocol.ts +5 -0
- package/front_end/models/ai_assistance/agents/GreenDevAgent.ts +512 -0
- package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +65 -85
- package/front_end/models/ai_assistance/agents/StylingAgent.ts +9 -16
- package/front_end/models/ai_assistance/ai_assistance.ts +2 -0
- package/front_end/models/bindings/DebuggerWorkspaceBinding.ts +21 -4
- package/front_end/models/bindings/SymbolizedError.ts +69 -2
- package/front_end/models/javascript_metadata/NativeFunctions.js +2 -2
- package/front_end/models/stack_trace/DetailedErrorStackParser.ts +11 -3
- package/front_end/models/stack_trace/ErrorStackParser.ts +18 -0
- package/front_end/panels/ai_assistance/components/ChatMessage.ts +84 -2
- package/front_end/panels/ai_assistance/components/WalkthroughUtils.ts +1 -1
- package/front_end/panels/ai_assistance/components/WalkthroughView.ts +9 -2
- package/front_end/panels/application/WebMCPView.ts +212 -89
- package/front_end/panels/application/webMCPView.css +260 -199
- package/front_end/panels/browser_debugger/CategorizedBreakpointsSidebarPane.ts +9 -5
- package/front_end/panels/changes/ChangesSidebar.ts +2 -2
- package/front_end/panels/changes/ChangesView.ts +4 -7
- package/front_end/panels/console/ConsoleViewMessage.ts +1 -19
- package/front_end/panels/coverage/CoverageView.ts +4 -5
- package/front_end/panels/elements/ElementsPanel.ts +9 -12
- package/front_end/panels/elements/StylePropertiesSection.ts +1 -1
- package/front_end/panels/elements/components/ElementsBreadcrumbs.ts +19 -20
- package/front_end/panels/elements/components/ElementsBreadcrumbsUtils.ts +29 -29
- package/front_end/panels/elements/components/QueryContainer.ts +5 -6
- package/front_end/panels/elements/components/components.ts +0 -2
- package/front_end/panels/emulation/MediaQueryInspector.ts +4 -7
- package/front_end/panels/mobile_throttling/NetworkThrottlingSelector.ts +47 -66
- package/front_end/panels/network/RequestConditionsDrawer.ts +6 -4
- package/front_end/panels/performance_monitor/PerformanceMonitor.ts +6 -9
- package/front_end/panels/protocol_monitor/JSONEditor.ts +2 -2
- package/front_end/panels/search/SearchView.ts +4 -7
- package/front_end/panels/sources/DebuggerPausedMessage.ts +18 -22
- package/front_end/panels/sources/ThreadsSidebarPane.ts +2 -4
- package/front_end/panels/web_audio/WebAudioView.ts +5 -4
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/ui/components/icon_button/iconButton.css +1 -0
- package/front_end/ui/components/lists/list.css +4 -0
- package/front_end/ui/legacy/ViewRegistration.ts +2 -2
- package/front_end/ui/legacy/Widget.ts +9 -2
- package/front_end/ui/legacy/components/utils/Linkifier.ts +32 -11
- package/front_end/ui/visual_logging/KnownContextValues.ts +17 -0
- package/package.json +1 -1
- package/front_end/Images/src/dots-circle.svg +0 -10
- package/front_end/panels/elements/components/Helper.ts +0 -35
|
@@ -272,6 +272,12 @@ export interface StepPart {
|
|
|
272
272
|
step: Step;
|
|
273
273
|
}
|
|
274
274
|
|
|
275
|
+
/**
|
|
276
|
+
* Represents a part of the message that consists of one or more widgets.
|
|
277
|
+
* The agent can yield widgets directly as part of its response, separate
|
|
278
|
+
* from those returned by a specific tool call (which are encapsulated
|
|
279
|
+
* within a StepPart).
|
|
280
|
+
*/
|
|
275
281
|
export interface WidgetPart {
|
|
276
282
|
type: 'widget';
|
|
277
283
|
widgets: AiWidget[];
|
|
@@ -1111,8 +1117,82 @@ async function makeDomTreeWidget(widgetData: DomTreeAiWidget): Promise<WidgetMak
|
|
|
1111
1117
|
*
|
|
1112
1118
|
* This allows for a flexible and extensible system where new widget types
|
|
1113
1119
|
* can be added to the AI responses and rendered in DevTools by adding
|
|
1114
|
-
* corresponding
|
|
1120
|
+
* corresponding `make...Widget` functions and handling them here.
|
|
1121
|
+
*/
|
|
1122
|
+
/**
|
|
1123
|
+
* Generates a deterministic unique identifier for a given AiWidget based on
|
|
1124
|
+
* its name and identifying data. This signature is used for widget deduplication.
|
|
1115
1125
|
*/
|
|
1126
|
+
export function getWidgetSignature(widget: AiWidget): string {
|
|
1127
|
+
switch (widget.name) {
|
|
1128
|
+
case 'COMPUTED_STYLES':
|
|
1129
|
+
return `${widget.name}:${widget.data.backendNodeId}`;
|
|
1130
|
+
case 'CORE_VITALS':
|
|
1131
|
+
return `${widget.name}:${widget.data.insightSetKey}`;
|
|
1132
|
+
case 'STYLE_PROPERTIES':
|
|
1133
|
+
return `${widget.name}:${widget.data.backendNodeId}:${widget.data.selector ?? ''}`;
|
|
1134
|
+
case 'DOM_TREE':
|
|
1135
|
+
return `${widget.name}:${widget.data.root.backendNodeId()}`;
|
|
1136
|
+
case 'PERFORMANCE_TRACE':
|
|
1137
|
+
return `${widget.name}`;
|
|
1138
|
+
case 'PERF_INSIGHT':
|
|
1139
|
+
return `${widget.name}:${widget.data.insight}:${widget.data.insightData.insightKey}:${
|
|
1140
|
+
widget.data.insightData.navigation?.args?.data?.navigationId ?? 'no-nav-id'}`;
|
|
1141
|
+
case 'TIMELINE_RANGE_SUMMARY':
|
|
1142
|
+
return `${widget.name}:${widget.data.track}:${widget.data.bounds.min}-${widget.data.bounds.max}`;
|
|
1143
|
+
case 'BOTTOM_UP_TREE':
|
|
1144
|
+
return `${widget.name}:${widget.data.bounds.min}-${widget.data.bounds.max}`;
|
|
1145
|
+
default:
|
|
1146
|
+
Platform.assertNever(widget, 'Unknown AiWidget name');
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
/**
|
|
1151
|
+
* Returns a new ModelChatMessage where widgets have been deduplicated
|
|
1152
|
+
* across all parts and steps of the message. The first occurrence of each
|
|
1153
|
+
* unique widget (determined by its signature) is preserved.
|
|
1154
|
+
*/
|
|
1155
|
+
export function getDeduplicatedWidgetsMessage(message: ModelChatMessage): ModelChatMessage {
|
|
1156
|
+
const seenWidgets = new Set<string>();
|
|
1157
|
+
|
|
1158
|
+
const filterWidgets = (widgets: AiWidget[]): AiWidget[] => {
|
|
1159
|
+
return widgets.filter(widget => {
|
|
1160
|
+
const signature = getWidgetSignature(widget);
|
|
1161
|
+
if (seenWidgets.has(signature)) {
|
|
1162
|
+
return false;
|
|
1163
|
+
}
|
|
1164
|
+
seenWidgets.add(signature);
|
|
1165
|
+
return true;
|
|
1166
|
+
});
|
|
1167
|
+
};
|
|
1168
|
+
|
|
1169
|
+
const deduplicatedParts = message.parts.map(part => {
|
|
1170
|
+
if (part.type === 'widget') {
|
|
1171
|
+
return {
|
|
1172
|
+
...part,
|
|
1173
|
+
widgets: filterWidgets(part.widgets),
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
if (part.type === 'step' && part.step.widgets) {
|
|
1178
|
+
return {
|
|
1179
|
+
...part,
|
|
1180
|
+
step: {
|
|
1181
|
+
...part.step,
|
|
1182
|
+
widgets: filterWidgets(part.step.widgets),
|
|
1183
|
+
},
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
return part;
|
|
1188
|
+
});
|
|
1189
|
+
|
|
1190
|
+
return {
|
|
1191
|
+
...message,
|
|
1192
|
+
parts: deduplicatedParts,
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1116
1196
|
async function renderWidgets(
|
|
1117
1197
|
widgets: AiWidget[]|undefined, options: {wrapperClass?: string} = {}): Promise<Lit.LitTemplate> {
|
|
1118
1198
|
if (!Root.Runtime.hostConfig.devToolsAiAssistanceV2?.enabled || !widgets || widgets.length === 0) {
|
|
@@ -1464,9 +1544,11 @@ export class ChatMessage extends UI.Widget.Widget {
|
|
|
1464
1544
|
}
|
|
1465
1545
|
|
|
1466
1546
|
override performUpdate(): Promise<void>|void {
|
|
1547
|
+
const message =
|
|
1548
|
+
this.message.entity === ChatMessageEntity.MODEL ? getDeduplicatedWidgetsMessage(this.message) : this.message;
|
|
1467
1549
|
this.#view(
|
|
1468
1550
|
{
|
|
1469
|
-
message
|
|
1551
|
+
message,
|
|
1470
1552
|
isLoading: this.isLoading,
|
|
1471
1553
|
isReadOnly: this.isReadOnly,
|
|
1472
1554
|
canShowFeedbackForm: this.canShowFeedbackForm,
|
|
@@ -70,5 +70,5 @@ export function getButtonLabel(input: {
|
|
|
70
70
|
const TARGET_LENGTH = 50;
|
|
71
71
|
const {truncatedText, moreCharacters} = smartTruncate(input.prompt, TARGET_LENGTH);
|
|
72
72
|
const promptSuffix = moreCharacters > 0 ? ` (and ${moreCharacters} more characters)` : '';
|
|
73
|
-
return `${labelBase} for prompt
|
|
73
|
+
return `${labelBase} for prompt ${truncatedText}${promptSuffix}`;
|
|
74
74
|
}
|
|
@@ -12,7 +12,13 @@ import * as Lit from '../../../ui/lit/lit.js';
|
|
|
12
12
|
import * as VisualLogging from '../../../ui/visual_logging/visual_logging.js';
|
|
13
13
|
|
|
14
14
|
import chatMessageStyles from './chatMessage.css.js';
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
getDeduplicatedWidgetsMessage,
|
|
17
|
+
type ModelChatMessage,
|
|
18
|
+
renderStep,
|
|
19
|
+
type Step,
|
|
20
|
+
titleForStep
|
|
21
|
+
} from './ChatMessage.js';
|
|
16
22
|
import {getButtonLabel} from './WalkthroughUtils.js';
|
|
17
23
|
import walkthroughViewStyles from './walkthroughView.css.js';
|
|
18
24
|
|
|
@@ -411,6 +417,7 @@ export class WalkthroughView extends UI.Widget.Widget {
|
|
|
411
417
|
if (!this.#markdownRenderer) {
|
|
412
418
|
return;
|
|
413
419
|
}
|
|
420
|
+
const message = this.#message ? getDeduplicatedWidgetsMessage(this.#message) : null;
|
|
414
421
|
this.#view(
|
|
415
422
|
{
|
|
416
423
|
isLoading: this.#isLoading,
|
|
@@ -420,7 +427,7 @@ export class WalkthroughView extends UI.Widget.Widget {
|
|
|
420
427
|
isInlined: this.#isInlined,
|
|
421
428
|
isExpanded: this.#isExpanded,
|
|
422
429
|
prompt: this.#prompt,
|
|
423
|
-
message
|
|
430
|
+
message,
|
|
424
431
|
handleScroll: this.#handleScroll,
|
|
425
432
|
},
|
|
426
433
|
this.#output, this.contentElement);
|
|
@@ -171,6 +171,22 @@ const UIStrings = {
|
|
|
171
171
|
* @description Text for the header of the tool run section
|
|
172
172
|
*/
|
|
173
173
|
runTool: 'Run Tool',
|
|
174
|
+
/**
|
|
175
|
+
* @description Context menu action to reveal the tool in the tool list
|
|
176
|
+
*/
|
|
177
|
+
revealTool: 'Reveal tool',
|
|
178
|
+
/**
|
|
179
|
+
* @description Context menu action to edit and run the tool
|
|
180
|
+
*/
|
|
181
|
+
editAndRun: 'Edit and run',
|
|
182
|
+
/**
|
|
183
|
+
* @description Tooltip for the paste button
|
|
184
|
+
*/
|
|
185
|
+
paste: 'Paste',
|
|
186
|
+
/**
|
|
187
|
+
* @description Notice to display when a tool has been unregistered
|
|
188
|
+
*/
|
|
189
|
+
toolUnregisteredNotice: 'This tool has been unregistered',
|
|
174
190
|
} as const;
|
|
175
191
|
const str_ = i18n.i18n.registerUIStrings('panels/application/WebMCPView.ts', UIStrings);
|
|
176
192
|
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
|
|
@@ -186,6 +202,7 @@ export interface FilterState {
|
|
|
186
202
|
completed?: boolean,
|
|
187
203
|
error?: boolean,
|
|
188
204
|
pending?: boolean,
|
|
205
|
+
canceled?: boolean,
|
|
189
206
|
};
|
|
190
207
|
}
|
|
191
208
|
|
|
@@ -198,10 +215,15 @@ export interface FilterMenuButtons {
|
|
|
198
215
|
toolTypes: FilterMenuButton;
|
|
199
216
|
statusTypes: FilterMenuButton;
|
|
200
217
|
}
|
|
218
|
+
export interface SelectedTool {
|
|
219
|
+
tool: WebMCP.WebMCPModel.Tool;
|
|
220
|
+
parameters?: Record<string, unknown>;
|
|
221
|
+
}
|
|
201
222
|
export interface ViewInput {
|
|
202
223
|
tools: WebMCP.WebMCPModel.Tool[];
|
|
203
|
-
selectedTool:
|
|
224
|
+
selectedTool: SelectedTool|null;
|
|
204
225
|
onToolSelect: (tool: WebMCP.WebMCPModel.Tool|null) => void;
|
|
226
|
+
onRevealTool: (tool: WebMCP.WebMCPModel.Tool, parameters?: Record<string, unknown>) => void;
|
|
205
227
|
selectedCall: WebMCP.WebMCPModel.Call|null;
|
|
206
228
|
onCallSelect: (call: WebMCP.WebMCPModel.Call|null) => void;
|
|
207
229
|
filters: FilterState;
|
|
@@ -210,6 +232,7 @@ export interface ViewInput {
|
|
|
210
232
|
onFilterChange: (filters: FilterState) => void;
|
|
211
233
|
toolCalls: WebMCP.WebMCPModel.Call[];
|
|
212
234
|
onRunTool: (event: Common.EventTarget.EventTargetEvent<ProtocolMonitor.JSONEditor.Command>) => void;
|
|
235
|
+
onPaste: () => void;
|
|
213
236
|
}
|
|
214
237
|
|
|
215
238
|
export function filterToolCalls(
|
|
@@ -219,13 +242,16 @@ export function filterToolCalls(
|
|
|
219
242
|
const statusTypes = filterState.statusTypes;
|
|
220
243
|
if (statusTypes) {
|
|
221
244
|
filtered = filtered.filter(call => {
|
|
222
|
-
const {completed, error, pending} = statusTypes;
|
|
245
|
+
const {completed, error, pending, canceled} = statusTypes;
|
|
223
246
|
if (completed && call.result?.status === Protocol.WebMCP.InvocationStatus.Completed) {
|
|
224
247
|
return true;
|
|
225
248
|
}
|
|
226
249
|
if (error && call.result?.status === Protocol.WebMCP.InvocationStatus.Error) {
|
|
227
250
|
return true;
|
|
228
251
|
}
|
|
252
|
+
if (canceled && call.result?.status === Protocol.WebMCP.InvocationStatus.Canceled) {
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
229
255
|
if (pending && call.result === undefined) {
|
|
230
256
|
return true;
|
|
231
257
|
}
|
|
@@ -259,63 +285,54 @@ export function filterToolCalls(
|
|
|
259
285
|
return filtered;
|
|
260
286
|
}
|
|
261
287
|
export type View = (input: ViewInput, output: object, target: HTMLElement) => void;
|
|
288
|
+
|
|
289
|
+
type ToolStats = Map<Protocol.WebMCP.InvocationStatus|undefined, number>;
|
|
290
|
+
|
|
262
291
|
function calculateToolStats(calls: WebMCP.WebMCPModel.Call[]):
|
|
263
|
-
{
|
|
264
|
-
|
|
292
|
+
{stats: Map<WebMCP.WebMCPModel.Tool, ToolStats>, totals: ToolStats} {
|
|
293
|
+
const stats = new Map<WebMCP.WebMCPModel.Tool, ToolStats>();
|
|
294
|
+
const totals: ToolStats = new Map();
|
|
295
|
+
|
|
265
296
|
for (const call of calls) {
|
|
266
|
-
|
|
267
|
-
if (
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
canceled++;
|
|
271
|
-
} else if (call.result?.status === Protocol.WebMCP.InvocationStatus.Completed) {
|
|
272
|
-
completed++;
|
|
273
|
-
} else if (call.result === undefined) {
|
|
274
|
-
inProgress++;
|
|
297
|
+
let toolStats = stats.get(call.tool);
|
|
298
|
+
if (!toolStats) {
|
|
299
|
+
toolStats = new Map();
|
|
300
|
+
stats.set(call.tool, toolStats);
|
|
275
301
|
}
|
|
302
|
+
toolStats.set(call.result?.status, (toolStats.get(call.result?.status) ?? 0) + 1);
|
|
303
|
+
totals.set(call.result?.status, (totals.get(call.result?.status) ?? 0) + 1);
|
|
276
304
|
}
|
|
277
|
-
return {
|
|
305
|
+
return {totals, stats};
|
|
278
306
|
}
|
|
279
307
|
|
|
280
|
-
function
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
iconName: '
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
});
|
|
308
|
+
function toolStatsIcon(status: Protocol.WebMCP.InvocationStatus|undefined): {iconName: string, iconColor?: string} {
|
|
309
|
+
switch (status) {
|
|
310
|
+
case Protocol.WebMCP.InvocationStatus.Completed:
|
|
311
|
+
return {iconName: 'check-circle', iconColor: 'var(--sys-color-green)'};
|
|
312
|
+
case Protocol.WebMCP.InvocationStatus.Error:
|
|
313
|
+
return {iconName: 'cross-circle-filled', iconColor: 'var(--sys-color-error)'};
|
|
314
|
+
case Protocol.WebMCP.InvocationStatus.Canceled:
|
|
315
|
+
return {iconName: 'record-stop', iconColor: 'var(--sys-color-on-surface-light)'};
|
|
316
|
+
case undefined:
|
|
317
|
+
return {iconName: 'watch'};
|
|
291
318
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
}
|
|
310
|
-
if (toolStats.inProgress > 0) {
|
|
311
|
-
groups.push({
|
|
312
|
-
iconName: 'dots-circle',
|
|
313
|
-
iconWidth: '16px',
|
|
314
|
-
iconHeight: '16px',
|
|
315
|
-
text: String(toolStats.inProgress),
|
|
316
|
-
});
|
|
317
|
-
}
|
|
318
|
-
return groups;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function getIconGroupsFromStats(toolStats?: ToolStats):
|
|
322
|
+
Array<IconButton.IconButton.IconWithTextData&{status: Protocol.WebMCP.InvocationStatus | undefined}> {
|
|
323
|
+
const status = [
|
|
324
|
+
Protocol.WebMCP.InvocationStatus.Completed, Protocol.WebMCP.InvocationStatus.Error,
|
|
325
|
+
Protocol.WebMCP.InvocationStatus.Canceled, undefined
|
|
326
|
+
];
|
|
327
|
+
return status
|
|
328
|
+
.map(status => ({
|
|
329
|
+
...toolStatsIcon(status),
|
|
330
|
+
iconWidth: 'var(--sys-size-8)',
|
|
331
|
+
iconHeight: 'var(--sys-size-8)',
|
|
332
|
+
text: String(toolStats?.get(status) ?? 0),
|
|
333
|
+
status,
|
|
334
|
+
}))
|
|
335
|
+
.filter(({text}) => text !== '0');
|
|
319
336
|
}
|
|
320
337
|
|
|
321
338
|
export function parsePayload(payload?: unknown): {
|
|
@@ -360,7 +377,7 @@ export function getJSONEditorParameters(tool: WebMCP.WebMCPModel.Tool): {
|
|
|
360
377
|
export const DEFAULT_VIEW: View = (input, output, target) => {
|
|
361
378
|
const tools = input.tools;
|
|
362
379
|
let editorWidget: ProtocolMonitor.JSONEditor.JSONEditor|null = null;
|
|
363
|
-
const
|
|
380
|
+
const toolStats = calculateToolStats(input.toolCalls);
|
|
364
381
|
const isFilterActive =
|
|
365
382
|
Boolean(input.filters.text) || Boolean(input.filters.toolTypes) || Boolean(input.filters.statusTypes);
|
|
366
383
|
const iconName = (call: WebMCP.WebMCPModel.Call): string => {
|
|
@@ -370,7 +387,7 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
|
|
|
370
387
|
case Protocol.WebMCP.InvocationStatus.Canceled:
|
|
371
388
|
return 'record-stop';
|
|
372
389
|
case undefined:
|
|
373
|
-
return '
|
|
390
|
+
return 'watch';
|
|
374
391
|
default:
|
|
375
392
|
return '';
|
|
376
393
|
}
|
|
@@ -387,6 +404,23 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
|
|
|
387
404
|
return i18nString(UIStrings.inProgress);
|
|
388
405
|
}
|
|
389
406
|
};
|
|
407
|
+
const onIconClick = (toolName: string, status: Protocol.WebMCP.InvocationStatus|undefined): void => {
|
|
408
|
+
let statusTypes: FilterState['statusTypes'] = undefined;
|
|
409
|
+
if (status === Protocol.WebMCP.InvocationStatus.Completed) {
|
|
410
|
+
statusTypes = {completed: true};
|
|
411
|
+
} else if (status === Protocol.WebMCP.InvocationStatus.Error) {
|
|
412
|
+
statusTypes = {error: true};
|
|
413
|
+
} else if (status === Protocol.WebMCP.InvocationStatus.Canceled) {
|
|
414
|
+
statusTypes = {canceled: true};
|
|
415
|
+
} else if (status === undefined) {
|
|
416
|
+
statusTypes = {pending: true};
|
|
417
|
+
}
|
|
418
|
+
input.onFilterChange({
|
|
419
|
+
...input.filters,
|
|
420
|
+
text: toolName,
|
|
421
|
+
statusTypes,
|
|
422
|
+
});
|
|
423
|
+
};
|
|
390
424
|
const onToolContextMenu = (event: Event, tool: WebMCP.WebMCPModel.Tool): void => {
|
|
391
425
|
const contextMenu = new UI.ContextMenu.ContextMenu(event);
|
|
392
426
|
contextMenu.defaultSection().appendItem(i18nString(UIStrings.copyName), () => {
|
|
@@ -412,9 +446,9 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
|
|
|
412
446
|
<div class="toolbar-divider"></div>
|
|
413
447
|
<devtools-toolbar-input type="filter"
|
|
414
448
|
placeholder=${i18nString(UIStrings.filter)}
|
|
415
|
-
.value=${input.filters.text}
|
|
416
449
|
@change=${(e: CustomEvent<string>) =>
|
|
417
|
-
input.onFilterChange({...input.filters, text: e.detail})}
|
|
450
|
+
input.onFilterChange({...input.filters, text: e.detail})}
|
|
451
|
+
.value=${input.filters.text}>
|
|
418
452
|
</devtools-toolbar-input>
|
|
419
453
|
<div class="toolbar-divider"></div>
|
|
420
454
|
${input.filterButtons.toolTypes.button.element}
|
|
@@ -453,8 +487,34 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
|
|
|
453
487
|
'status-error': call.result?.status === Protocol.WebMCP.InvocationStatus.Error,
|
|
454
488
|
'status-cancelled': call.result?.status === Protocol.WebMCP.InvocationStatus.Canceled,
|
|
455
489
|
selected: call === input.selectedCall,
|
|
456
|
-
})} @click=${() => input.onCallSelect(call)}
|
|
457
|
-
|
|
490
|
+
})} @click=${() => input.onCallSelect(call)}
|
|
491
|
+
@contextmenu=${(e: CustomEvent<UI.ContextMenu.ContextMenu>) => {
|
|
492
|
+
const contextMenu = e.detail;
|
|
493
|
+
const isUnregistered = !input.tools.includes(call.tool);
|
|
494
|
+
contextMenu.defaultSection().appendItem(i18nString(UIStrings.revealTool), () => {
|
|
495
|
+
input.onRevealTool(call.tool);
|
|
496
|
+
}, {jslogContext: 'webmcp.reveal-tool', disabled: isUnregistered});
|
|
497
|
+
contextMenu.defaultSection().appendItem(i18nString(UIStrings.editAndRun), () => {
|
|
498
|
+
const payload = parsePayload(call.input);
|
|
499
|
+
input.onRevealTool(call.tool, payload.valueObject as Record<string, unknown> | undefined);
|
|
500
|
+
}, {jslogContext: 'webmcp.edit-and-run', disabled: isUnregistered});
|
|
501
|
+
}}>
|
|
502
|
+
<td>
|
|
503
|
+
<div class="name-cell">
|
|
504
|
+
<span>${call.tool.name}</span>
|
|
505
|
+
<button class="run-tool-action-button"
|
|
506
|
+
title=${i18nString(UIStrings.editAndRun)}
|
|
507
|
+
aria-label=${i18nString(UIStrings.editAndRun)}
|
|
508
|
+
@click=${(e: Event) => {
|
|
509
|
+
e.stopPropagation();
|
|
510
|
+
const payload = parsePayload(call.input);
|
|
511
|
+
input.onRevealTool(call.tool,
|
|
512
|
+
payload.valueObject as Record<string, unknown> | undefined);
|
|
513
|
+
}}>
|
|
514
|
+
<devtools-icon name="goto-filled"></devtools-icon>
|
|
515
|
+
</button>
|
|
516
|
+
</div>
|
|
517
|
+
</td>
|
|
458
518
|
<td>
|
|
459
519
|
<div class="status-cell">
|
|
460
520
|
${iconName(call) ? html`<devtools-icon class="small" name=${iconName(call)}></devtools-icon>`
|
|
@@ -485,7 +545,7 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
|
|
|
485
545
|
<devtools-widget
|
|
486
546
|
id="webmcp.tool-details"
|
|
487
547
|
title=${i18nString(UIStrings.toolDetails)}
|
|
488
|
-
${widget(ToolDetailsWidget, {tool: input.selectedCall?.tool})}>
|
|
548
|
+
${widget(ToolDetailsWidget, {tool: input.selectedCall?.tool, isUnregistered: input.selectedCall ? !input.tools.includes(input.selectedCall.tool) : false})}>
|
|
489
549
|
</devtools-widget>
|
|
490
550
|
<devtools-widget
|
|
491
551
|
id="webmcp.call-inputs"
|
|
@@ -506,14 +566,18 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
|
|
|
506
566
|
</devtools-split-view>
|
|
507
567
|
<div class="webmcp-toolbar-container" role="toolbar">
|
|
508
568
|
<devtools-toolbar class="webmcp-toolbar" role="presentation" wrappable>
|
|
509
|
-
<span class="toolbar-text">${i18nString(UIStrings.totalCalls, {PH1:
|
|
569
|
+
<span class="toolbar-text">${i18nString(UIStrings.totalCalls, {PH1: input.toolCalls.length})}</span>
|
|
510
570
|
<div class="toolbar-divider"></div>
|
|
511
|
-
<span class="toolbar-text status-error-text">${
|
|
571
|
+
<span class="toolbar-text status-error-text">${
|
|
572
|
+
i18nString(UIStrings.failed,
|
|
573
|
+
{PH1: toolStats.totals.get(Protocol.WebMCP.InvocationStatus.Error) ?? 0})}</span>
|
|
512
574
|
<div class="toolbar-divider"></div>
|
|
513
575
|
<span class="toolbar-text status-cancelled-text">${
|
|
514
|
-
i18nString(UIStrings.canceledCount,
|
|
576
|
+
i18nString(UIStrings.canceledCount,
|
|
577
|
+
{PH1: toolStats.totals.get(Protocol.WebMCP.InvocationStatus.Canceled) ?? 0})}</span>
|
|
515
578
|
<div class="toolbar-divider"></div>
|
|
516
|
-
<span class="toolbar-text">${i18nString(UIStrings.inProgressCount,
|
|
579
|
+
<span class="toolbar-text">${i18nString(UIStrings.inProgressCount,
|
|
580
|
+
{PH1: toolStats.totals.get(undefined) ?? 0})}</span>
|
|
517
581
|
</devtools-toolbar>
|
|
518
582
|
</div>
|
|
519
583
|
` : html`
|
|
@@ -532,23 +596,26 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
|
|
|
532
596
|
${UI.Widget.widget(UI.EmptyWidget.EmptyWidget, {header: i18nString(UIStrings.noToolsPlaceholderTitle),
|
|
533
597
|
text: i18nString(UIStrings.noToolsPlaceholder)})}
|
|
534
598
|
` : html`
|
|
535
|
-
<devtools-list>
|
|
536
|
-
${tools.map(tool =>
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
599
|
+
<devtools-list class="square-corners">
|
|
600
|
+
${tools.map(tool => html`
|
|
601
|
+
<div class=${Directives.classMap({'tool-item': true, selected: tool === input.selectedTool?.tool})}
|
|
602
|
+
@click=${() => input.onToolSelect(tool)}
|
|
603
|
+
@contextmenu=${(e: Event) => onToolContextMenu(e, tool)}>
|
|
604
|
+
<div class="tool-name-container">
|
|
605
|
+
<div class="tool-name source-code">${tool.name}</div>
|
|
606
|
+
<div class="tool-icons">
|
|
607
|
+
${getIconGroupsFromStats(toolStats.stats.get(tool)).map(group => html`
|
|
608
|
+
<icon-button
|
|
609
|
+
.data=${{
|
|
610
|
+
groups: [group],
|
|
611
|
+
compact: false,
|
|
612
|
+
clickHandler: () => onIconClick(tool.name, group.status),
|
|
613
|
+
} as IconButton.IconButton.IconButtonData}
|
|
614
|
+
@click=${(e: Event) => e.stopPropagation()}></icon-button>`)}
|
|
547
615
|
</div>
|
|
548
|
-
<div class="tool-description">${tool.description}</div>
|
|
549
616
|
</div>
|
|
550
|
-
|
|
551
|
-
|
|
617
|
+
<div class="tool-description">${tool.description}</div>
|
|
618
|
+
</div>`)}
|
|
552
619
|
</devtools-list>
|
|
553
620
|
`}
|
|
554
621
|
</div>
|
|
@@ -565,10 +632,18 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
|
|
|
565
632
|
</div>
|
|
566
633
|
${input.selectedTool ? html`
|
|
567
634
|
<div class="sidebar-tool-details">
|
|
568
|
-
${widget(ToolDetailsWidget, {tool: input.selectedTool})}
|
|
635
|
+
${widget(ToolDetailsWidget, {tool: input.selectedTool.tool})}
|
|
569
636
|
</div>
|
|
570
637
|
<div class="section-title">
|
|
571
638
|
<span>${i18nString(UIStrings.runTool)}</span>
|
|
639
|
+
<div style="flex: auto;"></div>
|
|
640
|
+
<devtools-button
|
|
641
|
+
.iconName=${'import'}
|
|
642
|
+
.size=${Buttons.Button.Size.SMALL}
|
|
643
|
+
.variant=${Buttons.Button.Variant.TEXT}
|
|
644
|
+
title=${i18nString(UIStrings.paste)}
|
|
645
|
+
@click=${input.onPaste}
|
|
646
|
+
>${i18nString(UIStrings.paste)}</devtools-button>
|
|
572
647
|
</div>
|
|
573
648
|
<devtools-widget
|
|
574
649
|
class="json-editor-widget"
|
|
@@ -576,9 +651,12 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
|
|
|
576
651
|
displayTargetSelector: false,
|
|
577
652
|
displayCommandInput: false,
|
|
578
653
|
displayToolbar: false,
|
|
579
|
-
...getJSONEditorParameters(input.selectedTool),
|
|
580
|
-
commandToDisplay:
|
|
581
|
-
|
|
654
|
+
...getJSONEditorParameters(input.selectedTool.tool),
|
|
655
|
+
commandToDisplay: {
|
|
656
|
+
command: input.selectedTool.tool.name,
|
|
657
|
+
parameters: input.selectedTool.parameters || {}
|
|
658
|
+
},
|
|
659
|
+
})}
|
|
582
660
|
${UI.Widget.widgetRef(ProtocolMonitor.JSONEditor.JSONEditor, e => { editorWidget = e; })}
|
|
583
661
|
@submiteditor=${(e: CustomEvent<ProtocolMonitor.JSONEditor.Command>) => input.onRunTool({data: e.detail})}
|
|
584
662
|
></devtools-widget>
|
|
@@ -592,7 +670,7 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
|
|
|
592
670
|
const params = editorWidget.getParameters();
|
|
593
671
|
input.onRunTool({
|
|
594
672
|
data: {
|
|
595
|
-
command: input.selectedTool.name,
|
|
673
|
+
command: input.selectedTool.tool.name,
|
|
596
674
|
parameters: params,
|
|
597
675
|
} as ProtocolMonitor.JSONEditor.Command
|
|
598
676
|
});
|
|
@@ -608,7 +686,7 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
|
|
|
608
686
|
|
|
609
687
|
export class WebMCPView extends UI.Widget.VBox {
|
|
610
688
|
readonly #view: View;
|
|
611
|
-
#selectedTool:
|
|
689
|
+
#selectedTool: SelectedTool|null = null;
|
|
612
690
|
#selectedCall: WebMCP.WebMCPModel.Call|null = null;
|
|
613
691
|
|
|
614
692
|
#filterState: FilterState = {
|
|
@@ -686,11 +764,11 @@ export class WebMCPView extends UI.Widget.VBox {
|
|
|
686
764
|
}
|
|
687
765
|
|
|
688
766
|
#showStatusTypesContextMenu(contextMenu: UI.ContextMenu.ContextMenu): void {
|
|
689
|
-
const toggle = (key: 'completed'|'error'|'pending'): void => {
|
|
767
|
+
const toggle = (key: 'completed'|'error'|'pending'|'canceled'): void => {
|
|
690
768
|
const current = this.#filterState.statusTypes ?? {};
|
|
691
769
|
const next = {...current, [key]: !current[key]};
|
|
692
770
|
let statusTypesToPass: FilterState['statusTypes'] = next;
|
|
693
|
-
if (!next.completed && !next.error && !next.pending) {
|
|
771
|
+
if (!next.completed && !next.error && !next.pending && !next.canceled) {
|
|
694
772
|
statusTypesToPass = undefined;
|
|
695
773
|
}
|
|
696
774
|
this.#handleFilterChange({...this.#filterState, statusTypes: statusTypesToPass});
|
|
@@ -702,6 +780,9 @@ export class WebMCPView extends UI.Widget.VBox {
|
|
|
702
780
|
contextMenu.defaultSection().appendCheckboxItem(
|
|
703
781
|
i18nString(UIStrings.error), () => toggle('error'),
|
|
704
782
|
{checked: this.#filterState.statusTypes?.['error'] ?? false, jslogContext: 'webmcp.error'});
|
|
783
|
+
contextMenu.defaultSection().appendCheckboxItem(
|
|
784
|
+
i18nString(UIStrings.canceled), () => toggle('canceled'),
|
|
785
|
+
{checked: this.#filterState.statusTypes?.['canceled'] ?? false, jslogContext: 'webmcp.canceled'});
|
|
705
786
|
contextMenu.defaultSection().appendCheckboxItem(
|
|
706
787
|
i18nString(UIStrings.pending), () => toggle('pending'),
|
|
707
788
|
{checked: this.#filterState.statusTypes?.['pending'] ?? false, jslogContext: 'webmcp.pending'});
|
|
@@ -721,7 +802,7 @@ export class WebMCPView extends UI.Widget.VBox {
|
|
|
721
802
|
}
|
|
722
803
|
|
|
723
804
|
#toolsRemoved(event: Common.EventTarget.EventTargetEvent<readonly WebMCP.WebMCPModel.Tool[]>): void {
|
|
724
|
-
if (this.#selectedTool && event.data.includes(this.#selectedTool)) {
|
|
805
|
+
if (this.#selectedTool && event.data.includes(this.#selectedTool.tool)) {
|
|
725
806
|
this.#selectedTool = null;
|
|
726
807
|
}
|
|
727
808
|
this.requestUpdate();
|
|
@@ -763,7 +844,11 @@ export class WebMCPView extends UI.Widget.VBox {
|
|
|
763
844
|
tools,
|
|
764
845
|
selectedTool: this.#selectedTool,
|
|
765
846
|
onToolSelect: tool => {
|
|
766
|
-
this.#selectedTool = tool;
|
|
847
|
+
this.#selectedTool = tool ? {tool} : null;
|
|
848
|
+
this.requestUpdate();
|
|
849
|
+
},
|
|
850
|
+
onRevealTool: (tool, parameters) => {
|
|
851
|
+
this.#selectedTool = {tool, parameters};
|
|
767
852
|
this.requestUpdate();
|
|
768
853
|
},
|
|
769
854
|
selectedCall: this.#selectedCall,
|
|
@@ -778,7 +863,21 @@ export class WebMCPView extends UI.Widget.VBox {
|
|
|
778
863
|
onFilterChange: this.#handleFilterChange,
|
|
779
864
|
onRunTool: event => {
|
|
780
865
|
if (this.#selectedTool) {
|
|
781
|
-
void this.#selectedTool.invoke(event.data.parameters || {});
|
|
866
|
+
void this.#selectedTool.tool.invoke(event.data.parameters || {});
|
|
867
|
+
}
|
|
868
|
+
},
|
|
869
|
+
onPaste: async () => {
|
|
870
|
+
try {
|
|
871
|
+
const text = await navigator.clipboard.readText();
|
|
872
|
+
const json = JSON.parse(text);
|
|
873
|
+
if (typeof json !== 'object' || json === null || Array.isArray(json)) {
|
|
874
|
+
throw new Error('Pasted JSON must be an object');
|
|
875
|
+
}
|
|
876
|
+
if (this.#selectedTool) {
|
|
877
|
+
this.#selectedTool.parameters = json as Record<string, unknown>;
|
|
878
|
+
this.requestUpdate();
|
|
879
|
+
}
|
|
880
|
+
} catch {
|
|
782
881
|
}
|
|
783
882
|
},
|
|
784
883
|
};
|
|
@@ -964,6 +1063,7 @@ export class PayloadWidget extends UI.Widget.Widget {
|
|
|
964
1063
|
|
|
965
1064
|
export interface ToolDetailsViewInput {
|
|
966
1065
|
tool: WebMCP.WebMCPModel.Tool|null|undefined;
|
|
1066
|
+
isUnregistered?: boolean;
|
|
967
1067
|
origin: SDK.DOMModel.DOMNode|StackTrace.StackTrace.StackTrace|undefined;
|
|
968
1068
|
highlightNode: (node: SDK.DOMModel.DOMNode) => void;
|
|
969
1069
|
clearHighlight: () => void;
|
|
@@ -1020,6 +1120,16 @@ const TOOL_DETAILS_VIEW = (input: ToolDetailsViewInput, output: undefined, targe
|
|
|
1020
1120
|
{stackTrace: origin, options: { expandable: true}})}
|
|
1021
1121
|
</div>` : nothing}
|
|
1022
1122
|
</div>
|
|
1123
|
+
${input.isUnregistered ? html`
|
|
1124
|
+
<div class="call-to-action">
|
|
1125
|
+
<div class="call-to-action-body">
|
|
1126
|
+
<div class="explanation">
|
|
1127
|
+
<devtools-icon class="inline-icon medium" name="warning-filled"></devtools-icon>
|
|
1128
|
+
${i18nString(UIStrings.toolUnregisteredNotice)}
|
|
1129
|
+
</div>
|
|
1130
|
+
</div>
|
|
1131
|
+
</div>
|
|
1132
|
+
` : nothing}
|
|
1023
1133
|
`, target);
|
|
1024
1134
|
};
|
|
1025
1135
|
// clang-format on
|
|
@@ -1027,6 +1137,7 @@ const TOOL_DETAILS_VIEW = (input: ToolDetailsViewInput, output: undefined, targe
|
|
|
1027
1137
|
export class ToolDetailsWidget extends UI.Widget.Widget {
|
|
1028
1138
|
#tool: WebMCP.WebMCPModel.Tool|null|undefined = null;
|
|
1029
1139
|
#origin: SDK.DOMModel.DOMNode|StackTrace.StackTrace.StackTrace|undefined;
|
|
1140
|
+
#isUnregistered = false;
|
|
1030
1141
|
|
|
1031
1142
|
#view: typeof TOOL_DETAILS_VIEW;
|
|
1032
1143
|
|
|
@@ -1035,6 +1146,17 @@ export class ToolDetailsWidget extends UI.Widget.Widget {
|
|
|
1035
1146
|
this.#view = view;
|
|
1036
1147
|
}
|
|
1037
1148
|
|
|
1149
|
+
set isUnregistered(isUnregistered: boolean) {
|
|
1150
|
+
if (this.#isUnregistered === isUnregistered) {
|
|
1151
|
+
return;
|
|
1152
|
+
}
|
|
1153
|
+
this.#isUnregistered = isUnregistered;
|
|
1154
|
+
this.requestUpdate();
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
get isUnregistered(): boolean {
|
|
1158
|
+
return this.#isUnregistered;
|
|
1159
|
+
}
|
|
1038
1160
|
set tool(tool: WebMCP.WebMCPModel.Tool|null|undefined) {
|
|
1039
1161
|
if (this.#tool === tool) {
|
|
1040
1162
|
return;
|
|
@@ -1076,6 +1198,7 @@ export class ToolDetailsWidget extends UI.Widget.Widget {
|
|
|
1076
1198
|
override performUpdate(): void {
|
|
1077
1199
|
const viewInput = {
|
|
1078
1200
|
tool: this.#tool,
|
|
1201
|
+
isUnregistered: this.#isUnregistered,
|
|
1079
1202
|
origin: this.#origin,
|
|
1080
1203
|
highlightNode: this.#highlightNode,
|
|
1081
1204
|
clearHighlight: this.#clearHighlight,
|