@copilotkitnext/web-inspector 0.0.28 → 0.0.30
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/.turbo/turbo-build$colon$css.log +3 -3
- package/.turbo/turbo-build.log +11 -11
- package/.turbo/turbo-check-types.log +11 -0
- package/.turbo/turbo-lint.log +4 -0
- package/.turbo/turbo-test.log +12 -0
- package/dist/index.d.mts +57 -4
- package/dist/index.d.ts +57 -4
- package/dist/index.js +1048 -282
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1050 -282
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -5
- package/src/__tests__/web-inspector.spec.ts +171 -0
- package/src/assets/inspector-logo.svg +40 -0
- package/src/index.ts +1272 -361
- package/src/lib/persistence.ts +33 -25
- package/src/styles/generated.css +1 -1
- package/vitest.config.ts +8 -0
- /package/src/assets/{logo-mark.svg → inspector-logo-icon.svg} +0 -0
package/src/index.ts
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import { LitElement, css, html, nothing, unsafeCSS } from "lit";
|
|
2
2
|
import { styleMap } from "lit/directives/style-map.js";
|
|
3
3
|
import tailwindStyles from "./styles/generated.css";
|
|
4
|
-
import
|
|
4
|
+
import inspectorLogoUrl from "./assets/inspector-logo.svg";
|
|
5
|
+
import inspectorLogoIconUrl from "./assets/inspector-logo-icon.svg";
|
|
5
6
|
import { unsafeHTML } from "lit/directives/unsafe-html.js";
|
|
7
|
+
import { marked } from "marked";
|
|
6
8
|
import { icons } from "lucide";
|
|
7
|
-
import
|
|
9
|
+
import {
|
|
10
|
+
CopilotKitCore,
|
|
11
|
+
CopilotKitCoreRuntimeConnectionStatus,
|
|
12
|
+
type CopilotKitCoreSubscriber,
|
|
13
|
+
type CopilotKitCoreErrorCode,
|
|
14
|
+
} from "@copilotkitnext/core";
|
|
8
15
|
import type { AbstractAgent, AgentSubscriber } from "@ag-ui/client";
|
|
9
16
|
import type { Anchor, ContextKey, ContextState, DockMode, Position, Size } from "./lib/types";
|
|
10
17
|
import {
|
|
@@ -26,7 +33,7 @@ import {
|
|
|
26
33
|
isValidDockMode,
|
|
27
34
|
} from "./lib/persistence";
|
|
28
35
|
|
|
29
|
-
export const WEB_INSPECTOR_TAG = "web-inspector" as const;
|
|
36
|
+
export const WEB_INSPECTOR_TAG = "cpk-web-inspector" as const;
|
|
30
37
|
|
|
31
38
|
type LucideIconName = keyof typeof icons;
|
|
32
39
|
|
|
@@ -43,34 +50,108 @@ const DRAG_THRESHOLD = 6;
|
|
|
43
50
|
const MIN_WINDOW_WIDTH = 600;
|
|
44
51
|
const MIN_WINDOW_WIDTH_DOCKED_LEFT = 420;
|
|
45
52
|
const MIN_WINDOW_HEIGHT = 200;
|
|
46
|
-
const
|
|
47
|
-
const
|
|
53
|
+
const INSPECTOR_STORAGE_KEY = "cpk:inspector:state";
|
|
54
|
+
const ANNOUNCEMENT_STORAGE_KEY = "cpk:inspector:announcements";
|
|
55
|
+
const ANNOUNCEMENT_URL = "https://cdn.copilotkit.ai/announcements.json";
|
|
48
56
|
const DEFAULT_BUTTON_SIZE: Size = { width: 48, height: 48 };
|
|
49
57
|
const DEFAULT_WINDOW_SIZE: Size = { width: 840, height: 560 };
|
|
50
58
|
const DOCKED_LEFT_WIDTH = 500; // Sensible width for left dock with collapsed sidebar
|
|
51
59
|
const MAX_AGENT_EVENTS = 200;
|
|
52
60
|
const MAX_TOTAL_EVENTS = 500;
|
|
53
61
|
|
|
62
|
+
type InspectorAgentEventType =
|
|
63
|
+
| "RUN_STARTED"
|
|
64
|
+
| "RUN_FINISHED"
|
|
65
|
+
| "RUN_ERROR"
|
|
66
|
+
| "TEXT_MESSAGE_START"
|
|
67
|
+
| "TEXT_MESSAGE_CONTENT"
|
|
68
|
+
| "TEXT_MESSAGE_END"
|
|
69
|
+
| "TOOL_CALL_START"
|
|
70
|
+
| "TOOL_CALL_ARGS"
|
|
71
|
+
| "TOOL_CALL_END"
|
|
72
|
+
| "TOOL_CALL_RESULT"
|
|
73
|
+
| "STATE_SNAPSHOT"
|
|
74
|
+
| "STATE_DELTA"
|
|
75
|
+
| "MESSAGES_SNAPSHOT"
|
|
76
|
+
| "RAW_EVENT"
|
|
77
|
+
| "CUSTOM_EVENT";
|
|
78
|
+
|
|
79
|
+
const AGENT_EVENT_TYPES: readonly InspectorAgentEventType[] = [
|
|
80
|
+
"RUN_STARTED",
|
|
81
|
+
"RUN_FINISHED",
|
|
82
|
+
"RUN_ERROR",
|
|
83
|
+
"TEXT_MESSAGE_START",
|
|
84
|
+
"TEXT_MESSAGE_CONTENT",
|
|
85
|
+
"TEXT_MESSAGE_END",
|
|
86
|
+
"TOOL_CALL_START",
|
|
87
|
+
"TOOL_CALL_ARGS",
|
|
88
|
+
"TOOL_CALL_END",
|
|
89
|
+
"TOOL_CALL_RESULT",
|
|
90
|
+
"STATE_SNAPSHOT",
|
|
91
|
+
"STATE_DELTA",
|
|
92
|
+
"MESSAGES_SNAPSHOT",
|
|
93
|
+
"RAW_EVENT",
|
|
94
|
+
"CUSTOM_EVENT",
|
|
95
|
+
] as const;
|
|
96
|
+
|
|
97
|
+
type SanitizedValue =
|
|
98
|
+
| string
|
|
99
|
+
| number
|
|
100
|
+
| boolean
|
|
101
|
+
| null
|
|
102
|
+
| SanitizedValue[]
|
|
103
|
+
| { [key: string]: SanitizedValue };
|
|
104
|
+
|
|
105
|
+
type InspectorToolCall = {
|
|
106
|
+
id?: string;
|
|
107
|
+
function?: {
|
|
108
|
+
name?: string;
|
|
109
|
+
arguments?: SanitizedValue | string;
|
|
110
|
+
};
|
|
111
|
+
toolName?: string;
|
|
112
|
+
status?: string;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
type InspectorMessage = {
|
|
116
|
+
id?: string;
|
|
117
|
+
role: string;
|
|
118
|
+
contentText: string;
|
|
119
|
+
contentRaw?: SanitizedValue;
|
|
120
|
+
toolCalls: InspectorToolCall[];
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
type InspectorToolDefinition = {
|
|
124
|
+
agentId: string;
|
|
125
|
+
name: string;
|
|
126
|
+
description?: string;
|
|
127
|
+
parameters?: unknown;
|
|
128
|
+
type: "handler" | "renderer";
|
|
129
|
+
};
|
|
130
|
+
|
|
54
131
|
type InspectorEvent = {
|
|
55
132
|
id: string;
|
|
56
133
|
agentId: string;
|
|
57
|
-
type:
|
|
134
|
+
type: InspectorAgentEventType;
|
|
58
135
|
timestamp: number;
|
|
59
|
-
payload:
|
|
136
|
+
payload: SanitizedValue;
|
|
60
137
|
};
|
|
61
138
|
|
|
62
139
|
export class WebInspectorElement extends LitElement {
|
|
63
140
|
static properties = {
|
|
64
141
|
core: { attribute: false },
|
|
142
|
+
autoAttachCore: { type: Boolean, attribute: "auto-attach-core" },
|
|
65
143
|
} as const;
|
|
66
144
|
|
|
67
145
|
private _core: CopilotKitCore | null = null;
|
|
68
146
|
private coreSubscriber: CopilotKitCoreSubscriber | null = null;
|
|
69
147
|
private coreUnsubscribe: (() => void) | null = null;
|
|
148
|
+
private runtimeStatus: CopilotKitCoreRuntimeConnectionStatus | null = null;
|
|
149
|
+
private coreProperties: Readonly<Record<string, unknown>> = {};
|
|
150
|
+
private lastCoreError: { code: CopilotKitCoreErrorCode; message: string } | null = null;
|
|
70
151
|
private agentSubscriptions: Map<string, () => void> = new Map();
|
|
71
152
|
private agentEvents: Map<string, InspectorEvent[]> = new Map();
|
|
72
|
-
private agentMessages: Map<string,
|
|
73
|
-
private agentStates: Map<string,
|
|
153
|
+
private agentMessages: Map<string, InspectorMessage[]> = new Map();
|
|
154
|
+
private agentStates: Map<string, SanitizedValue> = new Map();
|
|
74
155
|
private flattenedEvents: InspectorEvent[] = [];
|
|
75
156
|
private eventCounter = 0;
|
|
76
157
|
private contextStore: Record<string, { description?: string; value: unknown }> = {};
|
|
@@ -89,6 +170,22 @@ export class WebInspectorElement extends LitElement {
|
|
|
89
170
|
private previousBodyMargins: { left: string; bottom: string } | null = null;
|
|
90
171
|
private transitionTimeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
91
172
|
private pendingSelectedContext: string | null = null;
|
|
173
|
+
private autoAttachCore = true;
|
|
174
|
+
private attemptedAutoAttach = false;
|
|
175
|
+
private cachedTools: InspectorToolDefinition[] = [];
|
|
176
|
+
private toolSignature = "";
|
|
177
|
+
private eventFilterText = "";
|
|
178
|
+
private eventTypeFilter: InspectorAgentEventType | "all" = "all";
|
|
179
|
+
|
|
180
|
+
private announcementMarkdown: string | null = null;
|
|
181
|
+
private announcementHtml: string | null = null;
|
|
182
|
+
private announcementTimestamp: string | null = null;
|
|
183
|
+
private announcementPreviewText: string | null = null;
|
|
184
|
+
private hasUnseenAnnouncement = false;
|
|
185
|
+
private announcementLoaded = false;
|
|
186
|
+
private announcementLoadError: unknown = null;
|
|
187
|
+
private announcementPromise: Promise<void> | null = null;
|
|
188
|
+
private showAnnouncementPreview = true;
|
|
92
189
|
|
|
93
190
|
get core(): CopilotKitCore | null {
|
|
94
191
|
return this._core;
|
|
@@ -137,18 +234,34 @@ export class WebInspectorElement extends LitElement {
|
|
|
137
234
|
|
|
138
235
|
private readonly menuItems: MenuItem[] = [
|
|
139
236
|
{ key: "ag-ui-events", label: "AG-UI Events", icon: "Zap" },
|
|
140
|
-
{ key: "agents", label: "
|
|
237
|
+
{ key: "agents", label: "Agent", icon: "Bot" },
|
|
141
238
|
{ key: "frontend-tools", label: "Frontend Tools", icon: "Hammer" },
|
|
142
|
-
{ key: "agent-context", label: "
|
|
239
|
+
{ key: "agent-context", label: "Context", icon: "FileText" },
|
|
143
240
|
];
|
|
144
241
|
|
|
145
242
|
private attachToCore(core: CopilotKitCore): void {
|
|
243
|
+
this.runtimeStatus = core.runtimeConnectionStatus;
|
|
244
|
+
this.coreProperties = core.properties;
|
|
245
|
+
this.lastCoreError = null;
|
|
246
|
+
|
|
146
247
|
this.coreSubscriber = {
|
|
248
|
+
onRuntimeConnectionStatusChanged: ({ status }) => {
|
|
249
|
+
this.runtimeStatus = status;
|
|
250
|
+
this.requestUpdate();
|
|
251
|
+
},
|
|
252
|
+
onPropertiesChanged: ({ properties }) => {
|
|
253
|
+
this.coreProperties = properties;
|
|
254
|
+
this.requestUpdate();
|
|
255
|
+
},
|
|
256
|
+
onError: ({ code, error }) => {
|
|
257
|
+
this.lastCoreError = { code, message: error.message };
|
|
258
|
+
this.requestUpdate();
|
|
259
|
+
},
|
|
147
260
|
onAgentsChanged: ({ agents }) => {
|
|
148
261
|
this.processAgentsChanged(agents);
|
|
149
262
|
},
|
|
150
263
|
onContextChanged: ({ context }) => {
|
|
151
|
-
this.contextStore =
|
|
264
|
+
this.contextStore = this.normalizeContextStore(context);
|
|
152
265
|
this.requestUpdate();
|
|
153
266
|
},
|
|
154
267
|
} satisfies CopilotKitCoreSubscriber;
|
|
@@ -158,7 +271,7 @@ export class WebInspectorElement extends LitElement {
|
|
|
158
271
|
|
|
159
272
|
// Initialize context from core
|
|
160
273
|
if (core.context) {
|
|
161
|
-
this.contextStore =
|
|
274
|
+
this.contextStore = this.normalizeContextStore(core.context);
|
|
162
275
|
}
|
|
163
276
|
}
|
|
164
277
|
|
|
@@ -168,6 +281,11 @@ export class WebInspectorElement extends LitElement {
|
|
|
168
281
|
this.coreUnsubscribe = null;
|
|
169
282
|
}
|
|
170
283
|
this.coreSubscriber = null;
|
|
284
|
+
this.runtimeStatus = null;
|
|
285
|
+
this.lastCoreError = null;
|
|
286
|
+
this.coreProperties = {};
|
|
287
|
+
this.cachedTools = [];
|
|
288
|
+
this.toolSignature = "";
|
|
171
289
|
this.teardownAgentSubscriptions();
|
|
172
290
|
}
|
|
173
291
|
|
|
@@ -204,9 +322,62 @@ export class WebInspectorElement extends LitElement {
|
|
|
204
322
|
}
|
|
205
323
|
|
|
206
324
|
this.updateContextOptions(seenAgentIds);
|
|
325
|
+
this.refreshToolsSnapshot();
|
|
207
326
|
this.requestUpdate();
|
|
208
327
|
}
|
|
209
328
|
|
|
329
|
+
private refreshToolsSnapshot(): void {
|
|
330
|
+
if (!this._core) {
|
|
331
|
+
if (this.cachedTools.length > 0) {
|
|
332
|
+
this.cachedTools = [];
|
|
333
|
+
this.toolSignature = "";
|
|
334
|
+
this.requestUpdate();
|
|
335
|
+
}
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const tools = this.extractToolsFromAgents();
|
|
340
|
+
const signature = JSON.stringify(
|
|
341
|
+
tools.map((tool) => ({
|
|
342
|
+
agentId: tool.agentId,
|
|
343
|
+
name: tool.name,
|
|
344
|
+
type: tool.type,
|
|
345
|
+
hasDescription: Boolean(tool.description),
|
|
346
|
+
hasParameters: Boolean(tool.parameters),
|
|
347
|
+
})),
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
if (signature !== this.toolSignature) {
|
|
351
|
+
this.toolSignature = signature;
|
|
352
|
+
this.cachedTools = tools;
|
|
353
|
+
this.requestUpdate();
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
private tryAutoAttachCore(): void {
|
|
358
|
+
if (this.attemptedAutoAttach || this._core || !this.autoAttachCore || typeof window === "undefined") {
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
this.attemptedAutoAttach = true;
|
|
363
|
+
|
|
364
|
+
const globalWindow = window as unknown as Record<string, unknown>;
|
|
365
|
+
const globalCandidates: Array<unknown> = [
|
|
366
|
+
// Common app-level globals used during development
|
|
367
|
+
globalWindow.__COPILOTKIT_CORE__,
|
|
368
|
+
(globalWindow.copilotkit as { core?: unknown } | undefined)?.core,
|
|
369
|
+
globalWindow.copilotkitCore,
|
|
370
|
+
];
|
|
371
|
+
|
|
372
|
+
const foundCore = globalCandidates.find(
|
|
373
|
+
(candidate): candidate is CopilotKitCore => !!candidate && typeof candidate === "object",
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
if (foundCore) {
|
|
377
|
+
this.core = foundCore;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
210
381
|
private subscribeToAgent(agent: AbstractAgent): void {
|
|
211
382
|
if (!agent.agentId) {
|
|
212
383
|
return;
|
|
@@ -288,14 +459,15 @@ export class WebInspectorElement extends LitElement {
|
|
|
288
459
|
}
|
|
289
460
|
}
|
|
290
461
|
|
|
291
|
-
private recordAgentEvent(agentId: string, type:
|
|
462
|
+
private recordAgentEvent(agentId: string, type: InspectorAgentEventType, payload: unknown): void {
|
|
292
463
|
const eventId = `${agentId}:${++this.eventCounter}`;
|
|
464
|
+
const normalizedPayload = this.normalizeEventPayload(type, payload);
|
|
293
465
|
const event: InspectorEvent = {
|
|
294
466
|
id: eventId,
|
|
295
467
|
agentId,
|
|
296
468
|
type,
|
|
297
469
|
timestamp: Date.now(),
|
|
298
|
-
payload,
|
|
470
|
+
payload: normalizedPayload,
|
|
299
471
|
};
|
|
300
472
|
|
|
301
473
|
const currentAgentEvents = this.agentEvents.get(agentId) ?? [];
|
|
@@ -303,6 +475,7 @@ export class WebInspectorElement extends LitElement {
|
|
|
303
475
|
this.agentEvents.set(agentId, nextAgentEvents);
|
|
304
476
|
|
|
305
477
|
this.flattenedEvents = [event, ...this.flattenedEvents].slice(0, MAX_TOTAL_EVENTS);
|
|
478
|
+
this.refreshToolsSnapshot();
|
|
306
479
|
this.requestUpdate();
|
|
307
480
|
}
|
|
308
481
|
|
|
@@ -311,9 +484,8 @@ export class WebInspectorElement extends LitElement {
|
|
|
311
484
|
return;
|
|
312
485
|
}
|
|
313
486
|
|
|
314
|
-
const messages = (agent as { messages?: unknown }).messages;
|
|
315
|
-
|
|
316
|
-
if (Array.isArray(messages)) {
|
|
487
|
+
const messages = this.normalizeAgentMessages((agent as { messages?: unknown }).messages);
|
|
488
|
+
if (messages) {
|
|
317
489
|
this.agentMessages.set(agent.agentId, messages);
|
|
318
490
|
} else {
|
|
319
491
|
this.agentMessages.delete(agent.agentId);
|
|
@@ -332,7 +504,7 @@ export class WebInspectorElement extends LitElement {
|
|
|
332
504
|
if (state === undefined || state === null) {
|
|
333
505
|
this.agentStates.delete(agent.agentId);
|
|
334
506
|
} else {
|
|
335
|
-
this.agentStates.set(agent.agentId, state);
|
|
507
|
+
this.agentStates.set(agent.agentId, this.sanitizeForLogging(state));
|
|
336
508
|
}
|
|
337
509
|
|
|
338
510
|
this.requestUpdate();
|
|
@@ -397,17 +569,42 @@ export class WebInspectorElement extends LitElement {
|
|
|
397
569
|
return this.agentEvents.get(this.selectedContext) ?? [];
|
|
398
570
|
}
|
|
399
571
|
|
|
400
|
-
private
|
|
572
|
+
private filterEvents(events: InspectorEvent[]): InspectorEvent[] {
|
|
573
|
+
const query = this.eventFilterText.trim().toLowerCase();
|
|
574
|
+
|
|
575
|
+
return events.filter((event) => {
|
|
576
|
+
if (this.eventTypeFilter !== "all" && event.type !== this.eventTypeFilter) {
|
|
577
|
+
return false;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
if (!query) {
|
|
581
|
+
return true;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
const payloadText = this.stringifyPayload(event.payload, false).toLowerCase();
|
|
585
|
+
return (
|
|
586
|
+
event.type.toLowerCase().includes(query) ||
|
|
587
|
+
event.agentId.toLowerCase().includes(query) ||
|
|
588
|
+
payloadText.includes(query)
|
|
589
|
+
);
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
private getLatestStateForAgent(agentId: string): SanitizedValue | null {
|
|
401
594
|
if (this.agentStates.has(agentId)) {
|
|
402
|
-
|
|
595
|
+
const value = this.agentStates.get(agentId);
|
|
596
|
+
return value === undefined ? null : value;
|
|
403
597
|
}
|
|
404
598
|
|
|
405
599
|
const events = this.agentEvents.get(agentId) ?? [];
|
|
406
600
|
const stateEvent = events.find((e) => e.type === "STATE_SNAPSHOT");
|
|
407
|
-
|
|
601
|
+
if (!stateEvent) {
|
|
602
|
+
return null;
|
|
603
|
+
}
|
|
604
|
+
return stateEvent.payload;
|
|
408
605
|
}
|
|
409
606
|
|
|
410
|
-
private getLatestMessagesForAgent(agentId: string):
|
|
607
|
+
private getLatestMessagesForAgent(agentId: string): InspectorMessage[] | null {
|
|
411
608
|
const messages = this.agentMessages.get(agentId);
|
|
412
609
|
return messages ?? null;
|
|
413
610
|
}
|
|
@@ -445,22 +642,11 @@ export class WebInspectorElement extends LitElement {
|
|
|
445
642
|
|
|
446
643
|
const messages = this.agentMessages.get(agentId);
|
|
447
644
|
|
|
448
|
-
const toolCallCount =
|
|
449
|
-
?
|
|
450
|
-
if (!rawMessage || typeof rawMessage !== 'object') {
|
|
451
|
-
return count;
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
const toolCalls = (rawMessage as { toolCalls?: unknown }).toolCalls;
|
|
455
|
-
if (!Array.isArray(toolCalls)) {
|
|
456
|
-
return count;
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
return count + toolCalls.length;
|
|
460
|
-
}, 0)
|
|
645
|
+
const toolCallCount = messages
|
|
646
|
+
? messages.reduce((count, message) => count + (message.toolCalls?.length ?? 0), 0)
|
|
461
647
|
: events.filter((e) => e.type === "TOOL_CALL_END").length;
|
|
462
648
|
|
|
463
|
-
const messageCount =
|
|
649
|
+
const messageCount = messages?.length ?? 0;
|
|
464
650
|
|
|
465
651
|
return {
|
|
466
652
|
totalEvents: events.length,
|
|
@@ -471,7 +657,7 @@ export class WebInspectorElement extends LitElement {
|
|
|
471
657
|
};
|
|
472
658
|
}
|
|
473
659
|
|
|
474
|
-
private renderToolCallDetails(toolCalls:
|
|
660
|
+
private renderToolCallDetails(toolCalls: InspectorToolCall[]) {
|
|
475
661
|
if (!Array.isArray(toolCalls) || toolCalls.length === 0) {
|
|
476
662
|
return nothing;
|
|
477
663
|
}
|
|
@@ -479,10 +665,9 @@ export class WebInspectorElement extends LitElement {
|
|
|
479
665
|
return html`
|
|
480
666
|
<div class="mt-2 space-y-2">
|
|
481
667
|
${toolCalls.map((call, index) => {
|
|
482
|
-
const
|
|
483
|
-
const
|
|
484
|
-
const
|
|
485
|
-
const argsString = this.formatToolCallArguments(toolCall?.function?.arguments);
|
|
668
|
+
const functionName = call.function?.name ?? call.toolName ?? "Unknown function";
|
|
669
|
+
const callId = typeof call?.id === "string" ? call.id : `tool-call-${index + 1}`;
|
|
670
|
+
const argsString = this.formatToolCallArguments(call.function?.arguments);
|
|
486
671
|
return html`
|
|
487
672
|
<div class="rounded-md border border-gray-200 bg-gray-50 p-3 text-xs text-gray-700">
|
|
488
673
|
<div class="flex flex-wrap items-center justify-between gap-1 font-medium text-gray-900">
|
|
@@ -508,7 +693,7 @@ export class WebInspectorElement extends LitElement {
|
|
|
508
693
|
try {
|
|
509
694
|
const parsed = JSON.parse(args);
|
|
510
695
|
return JSON.stringify(parsed, null, 2);
|
|
511
|
-
} catch
|
|
696
|
+
} catch {
|
|
512
697
|
return args;
|
|
513
698
|
}
|
|
514
699
|
}
|
|
@@ -516,7 +701,7 @@ export class WebInspectorElement extends LitElement {
|
|
|
516
701
|
if (typeof args === 'object') {
|
|
517
702
|
try {
|
|
518
703
|
return JSON.stringify(args, null, 2);
|
|
519
|
-
} catch
|
|
704
|
+
} catch {
|
|
520
705
|
return String(args);
|
|
521
706
|
}
|
|
522
707
|
}
|
|
@@ -622,7 +807,7 @@ export class WebInspectorElement extends LitElement {
|
|
|
622
807
|
private extractEventFromPayload(payload: unknown): unknown {
|
|
623
808
|
// If payload is an object with an 'event' field, extract it
|
|
624
809
|
if (payload && typeof payload === "object" && "event" in payload) {
|
|
625
|
-
return (payload as
|
|
810
|
+
return (payload as Record<string, unknown>).event;
|
|
626
811
|
}
|
|
627
812
|
// Otherwise, assume the payload itself is the event
|
|
628
813
|
return payload;
|
|
@@ -695,6 +880,152 @@ export class WebInspectorElement extends LitElement {
|
|
|
695
880
|
z-index: 50;
|
|
696
881
|
background: transparent;
|
|
697
882
|
}
|
|
883
|
+
|
|
884
|
+
.tooltip-target {
|
|
885
|
+
position: relative;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
.tooltip-target::after {
|
|
889
|
+
content: attr(data-tooltip);
|
|
890
|
+
position: absolute;
|
|
891
|
+
top: calc(100% + 6px);
|
|
892
|
+
left: 50%;
|
|
893
|
+
transform: translateX(-50%) translateY(-4px);
|
|
894
|
+
white-space: nowrap;
|
|
895
|
+
background: rgba(17, 24, 39, 0.95);
|
|
896
|
+
color: white;
|
|
897
|
+
padding: 4px 8px;
|
|
898
|
+
border-radius: 6px;
|
|
899
|
+
font-size: 10px;
|
|
900
|
+
line-height: 1.2;
|
|
901
|
+
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15);
|
|
902
|
+
opacity: 0;
|
|
903
|
+
pointer-events: none;
|
|
904
|
+
transition: opacity 120ms ease, transform 120ms ease;
|
|
905
|
+
z-index: 4000;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
.tooltip-target:hover::after {
|
|
909
|
+
opacity: 1;
|
|
910
|
+
transform: translateX(-50%) translateY(0);
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
.announcement-preview {
|
|
914
|
+
position: absolute;
|
|
915
|
+
top: 50%;
|
|
916
|
+
transform: translateY(-50%);
|
|
917
|
+
min-width: 300px;
|
|
918
|
+
max-width: 300px;
|
|
919
|
+
background: white;
|
|
920
|
+
color: #111827;
|
|
921
|
+
font-size: 13px;
|
|
922
|
+
line-height: 1.4;
|
|
923
|
+
border-radius: 12px;
|
|
924
|
+
box-shadow: 0 12px 28px rgba(15, 23, 42, 0.22);
|
|
925
|
+
padding: 10px 12px;
|
|
926
|
+
display: inline-flex;
|
|
927
|
+
align-items: flex-start;
|
|
928
|
+
gap: 8px;
|
|
929
|
+
z-index: 4500;
|
|
930
|
+
animation: fade-slide-in 160ms ease;
|
|
931
|
+
border: 1px solid rgba(148, 163, 184, 0.35);
|
|
932
|
+
white-space: normal;
|
|
933
|
+
word-break: break-word;
|
|
934
|
+
text-align: left;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
.announcement-preview[data-side="left"] {
|
|
938
|
+
right: 100%;
|
|
939
|
+
margin-right: 10px;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
.announcement-preview[data-side="right"] {
|
|
943
|
+
left: 100%;
|
|
944
|
+
margin-left: 10px;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
.announcement-preview__arrow {
|
|
948
|
+
position: absolute;
|
|
949
|
+
width: 10px;
|
|
950
|
+
height: 10px;
|
|
951
|
+
background: white;
|
|
952
|
+
border: 1px solid rgba(148, 163, 184, 0.35);
|
|
953
|
+
transform: rotate(45deg);
|
|
954
|
+
top: 50%;
|
|
955
|
+
margin-top: -5px;
|
|
956
|
+
z-index: -1;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
.announcement-preview[data-side="left"] .announcement-preview__arrow {
|
|
960
|
+
right: -5px;
|
|
961
|
+
box-shadow: 6px -6px 10px rgba(15, 23, 42, 0.12);
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
.announcement-preview[data-side="right"] .announcement-preview__arrow {
|
|
965
|
+
left: -5px;
|
|
966
|
+
box-shadow: -6px 6px 10px rgba(15, 23, 42, 0.12);
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
.announcement-dismiss {
|
|
970
|
+
color: #6b7280;
|
|
971
|
+
font-size: 12px;
|
|
972
|
+
padding: 2px 8px;
|
|
973
|
+
border-radius: 8px;
|
|
974
|
+
border: 1px solid rgba(148, 163, 184, 0.5);
|
|
975
|
+
background: rgba(248, 250, 252, 0.9);
|
|
976
|
+
transition: background 120ms ease, color 120ms ease;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
.announcement-dismiss:hover {
|
|
980
|
+
background: rgba(241, 245, 249, 1);
|
|
981
|
+
color: #111827;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
.announcement-content {
|
|
985
|
+
color: #111827;
|
|
986
|
+
font-size: 14px;
|
|
987
|
+
line-height: 1.6;
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
.announcement-content h1,
|
|
991
|
+
.announcement-content h2,
|
|
992
|
+
.announcement-content h3 {
|
|
993
|
+
font-weight: 700;
|
|
994
|
+
margin: 0.4rem 0 0.2rem;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
.announcement-content h1 {
|
|
998
|
+
font-size: 1.1rem;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
.announcement-content h2 {
|
|
1002
|
+
font-size: 1rem;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
.announcement-content h3 {
|
|
1006
|
+
font-size: 0.95rem;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
.announcement-content p {
|
|
1010
|
+
margin: 0.25rem 0;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
.announcement-content ul {
|
|
1014
|
+
list-style: disc;
|
|
1015
|
+
padding-left: 1.25rem;
|
|
1016
|
+
margin: 0.3rem 0;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
.announcement-content ol {
|
|
1020
|
+
list-style: decimal;
|
|
1021
|
+
padding-left: 1.25rem;
|
|
1022
|
+
margin: 0.3rem 0;
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
.announcement-content a {
|
|
1026
|
+
color: #0f766e;
|
|
1027
|
+
text-decoration: underline;
|
|
1028
|
+
}
|
|
698
1029
|
`,
|
|
699
1030
|
];
|
|
700
1031
|
|
|
@@ -705,7 +1036,9 @@ export class WebInspectorElement extends LitElement {
|
|
|
705
1036
|
window.addEventListener("pointerdown", this.handleGlobalPointerDown as EventListener);
|
|
706
1037
|
|
|
707
1038
|
// Load state early (before first render) so menu selection is correct
|
|
708
|
-
this.
|
|
1039
|
+
this.hydrateStateFromStorageEarly();
|
|
1040
|
+
this.tryAutoAttachCore();
|
|
1041
|
+
this.ensureAnnouncementLoading();
|
|
709
1042
|
}
|
|
710
1043
|
}
|
|
711
1044
|
|
|
@@ -724,6 +1057,10 @@ export class WebInspectorElement extends LitElement {
|
|
|
724
1057
|
return;
|
|
725
1058
|
}
|
|
726
1059
|
|
|
1060
|
+
if (!this._core) {
|
|
1061
|
+
this.tryAutoAttachCore();
|
|
1062
|
+
}
|
|
1063
|
+
|
|
727
1064
|
this.measureContext("button");
|
|
728
1065
|
this.measureContext("window");
|
|
729
1066
|
|
|
@@ -733,7 +1070,7 @@ export class WebInspectorElement extends LitElement {
|
|
|
733
1070
|
this.contextState.window.anchor = { horizontal: "right", vertical: "top" };
|
|
734
1071
|
this.contextState.window.anchorOffset = { x: EDGE_MARGIN, y: EDGE_MARGIN };
|
|
735
1072
|
|
|
736
|
-
this.
|
|
1073
|
+
this.hydrateStateFromStorage();
|
|
737
1074
|
|
|
738
1075
|
// Apply docking styles if open and docked (skip transition on initial load)
|
|
739
1076
|
if (this.isOpen && this.dockMode !== 'floating') {
|
|
@@ -750,6 +1087,8 @@ export class WebInspectorElement extends LitElement {
|
|
|
750
1087
|
}
|
|
751
1088
|
}
|
|
752
1089
|
|
|
1090
|
+
this.ensureAnnouncementLoading();
|
|
1091
|
+
|
|
753
1092
|
this.updateHostTransform(this.isOpen ? "window" : "button");
|
|
754
1093
|
}
|
|
755
1094
|
|
|
@@ -761,6 +1100,7 @@ export class WebInspectorElement extends LitElement {
|
|
|
761
1100
|
const buttonClasses = [
|
|
762
1101
|
"console-button",
|
|
763
1102
|
"group",
|
|
1103
|
+
"relative",
|
|
764
1104
|
"pointer-events-auto",
|
|
765
1105
|
"inline-flex",
|
|
766
1106
|
"h-12",
|
|
@@ -803,7 +1143,8 @@ export class WebInspectorElement extends LitElement {
|
|
|
803
1143
|
@pointercancel=${this.handlePointerCancel}
|
|
804
1144
|
@click=${this.handleButtonClick}
|
|
805
1145
|
>
|
|
806
|
-
|
|
1146
|
+
${this.renderAnnouncementPreview()}
|
|
1147
|
+
<img src=${inspectorLogoIconUrl} alt="Inspector logo" class="h-5 w-auto" loading="lazy" />
|
|
807
1148
|
</button>
|
|
808
1149
|
`;
|
|
809
1150
|
}
|
|
@@ -812,7 +1153,6 @@ export class WebInspectorElement extends LitElement {
|
|
|
812
1153
|
const windowState = this.contextState.window;
|
|
813
1154
|
const isDocked = this.dockMode !== 'floating';
|
|
814
1155
|
const isTransitioning = this.hasAttribute('data-transitioning');
|
|
815
|
-
const isCollapsed = this.dockMode === 'docked-left';
|
|
816
1156
|
|
|
817
1157
|
const windowStyles = isDocked
|
|
818
1158
|
? this.getDockedWindowStyles()
|
|
@@ -823,8 +1163,17 @@ export class WebInspectorElement extends LitElement {
|
|
|
823
1163
|
minHeight: `${MIN_WINDOW_HEIGHT}px`,
|
|
824
1164
|
};
|
|
825
1165
|
|
|
826
|
-
const
|
|
827
|
-
const
|
|
1166
|
+
const hasContextDropdown = this.contextOptions.length > 0;
|
|
1167
|
+
const contextDropdown = hasContextDropdown ? this.renderContextDropdown() : nothing;
|
|
1168
|
+
const coreStatus = this.getCoreStatusSummary();
|
|
1169
|
+
const agentSelector = hasContextDropdown
|
|
1170
|
+
? contextDropdown
|
|
1171
|
+
: html`
|
|
1172
|
+
<div class="flex items-center gap-2 rounded-md border border-dashed border-gray-200 px-2 py-1 text-xs text-gray-400">
|
|
1173
|
+
<span>${this.renderIcon("Bot")}</span>
|
|
1174
|
+
<span class="truncate">No agents available</span>
|
|
1175
|
+
</div>
|
|
1176
|
+
`;
|
|
828
1177
|
|
|
829
1178
|
return html`
|
|
830
1179
|
<section
|
|
@@ -846,150 +1195,82 @@ export class WebInspectorElement extends LitElement {
|
|
|
846
1195
|
></div>
|
|
847
1196
|
`
|
|
848
1197
|
: nothing}
|
|
849
|
-
<div class="flex flex-1 overflow-hidden bg-white text-gray-800">
|
|
850
|
-
<
|
|
851
|
-
class="
|
|
852
|
-
|
|
1198
|
+
<div class="flex flex-1 flex-col overflow-hidden bg-white text-gray-800">
|
|
1199
|
+
<div
|
|
1200
|
+
class="drag-handle relative z-30 flex flex-col border-b border-gray-200 bg-white/95 backdrop-blur-sm ${isDocked ? '' : (this.isDragging && this.pointerContext === 'window' ? 'cursor-grabbing' : 'cursor-grab')}"
|
|
1201
|
+
data-drag-context="window"
|
|
1202
|
+
@pointerdown=${isDocked ? undefined : this.handlePointerDown}
|
|
1203
|
+
@pointermove=${isDocked ? undefined : this.handlePointerMove}
|
|
1204
|
+
@pointerup=${isDocked ? undefined : this.handlePointerUp}
|
|
1205
|
+
@pointercancel=${isDocked ? undefined : this.handlePointerCancel}
|
|
853
1206
|
>
|
|
854
|
-
<div class="flex flex-
|
|
855
|
-
<div
|
|
856
|
-
|
|
857
|
-
data-drag-context="window"
|
|
858
|
-
@pointerdown=${this.handlePointerDown}
|
|
859
|
-
@pointermove=${this.handlePointerMove}
|
|
860
|
-
@pointerup=${this.handlePointerUp}
|
|
861
|
-
@pointercancel=${this.handlePointerCancel}
|
|
862
|
-
title="${isCollapsed ? 'Acme Inc - Enterprise' : ''}"
|
|
863
|
-
>
|
|
864
|
-
<span
|
|
865
|
-
class="flex h-8 w-8 items-center justify-center rounded-lg bg-gray-900 text-white pointer-events-none"
|
|
866
|
-
>
|
|
867
|
-
${this.renderIcon("Building2")}
|
|
868
|
-
</span>
|
|
869
|
-
${!isCollapsed
|
|
870
|
-
? html`
|
|
871
|
-
<div class="flex flex-1 flex-col leading-tight pointer-events-none">
|
|
872
|
-
<span class="text-sm font-semibold text-gray-900">Acme Inc</span>
|
|
873
|
-
<span class="text-[10px] text-gray-500">Enterprise</span>
|
|
874
|
-
</div>
|
|
875
|
-
`
|
|
876
|
-
: nothing}
|
|
1207
|
+
<div class="flex flex-wrap items-center gap-3 px-4 py-3">
|
|
1208
|
+
<div class="flex items-center min-w-0">
|
|
1209
|
+
<img src=${inspectorLogoUrl} alt="Inspector logo" class="h-6 w-auto" loading="lazy" />
|
|
877
1210
|
</div>
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
].join(" ");
|
|
894
|
-
|
|
895
|
-
const badgeClasses = isSelected
|
|
896
|
-
? "bg-gray-800 text-white"
|
|
897
|
-
: "bg-white border border-gray-200 text-gray-500 group-hover:border-gray-300 group-hover:text-gray-700";
|
|
898
|
-
|
|
899
|
-
return html`
|
|
900
|
-
<button
|
|
901
|
-
type="button"
|
|
902
|
-
class=${buttonClasses}
|
|
903
|
-
aria-pressed=${isSelected}
|
|
904
|
-
title="${isCollapsed ? label : ''}"
|
|
905
|
-
@click=${() => this.handleMenuSelect(key)}
|
|
906
|
-
>
|
|
907
|
-
<span
|
|
908
|
-
class="flex h-6 w-6 items-center justify-center rounded ${isCollapsed && isSelected ? 'text-white' : isCollapsed ? 'text-gray-600' : badgeClasses}"
|
|
909
|
-
aria-hidden="true"
|
|
910
|
-
>
|
|
911
|
-
${this.renderIcon(icon)}
|
|
912
|
-
</span>
|
|
913
|
-
${!isCollapsed
|
|
914
|
-
? html`
|
|
915
|
-
<span class="flex-1">${label}</span>
|
|
916
|
-
<span class="text-gray-400 opacity-60">${this.renderIcon("ChevronRight")}</span>
|
|
917
|
-
`
|
|
918
|
-
: nothing}
|
|
919
|
-
</button>
|
|
920
|
-
`;
|
|
921
|
-
})}
|
|
1211
|
+
<div class="ml-auto flex min-w-0 items-center gap-2">
|
|
1212
|
+
<div class="min-w-[160px] max-w-xs">
|
|
1213
|
+
${agentSelector}
|
|
1214
|
+
</div>
|
|
1215
|
+
<div class="flex items-center gap-1">
|
|
1216
|
+
${this.renderDockControls()}
|
|
1217
|
+
<button
|
|
1218
|
+
class="flex h-8 w-8 items-center justify-center rounded-md text-gray-400 transition hover:bg-gray-100 hover:text-gray-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-400"
|
|
1219
|
+
type="button"
|
|
1220
|
+
aria-label="Close Web Inspector"
|
|
1221
|
+
@pointerdown=${this.handleClosePointerDown}
|
|
1222
|
+
@click=${this.handleCloseClick}
|
|
1223
|
+
>
|
|
1224
|
+
${this.renderIcon("X")}
|
|
1225
|
+
</button>
|
|
922
1226
|
</div>
|
|
923
1227
|
</div>
|
|
924
1228
|
</div>
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
1229
|
+
<div class="flex flex-wrap items-center gap-2 border-t border-gray-100 px-3 py-2 text-xs">
|
|
1230
|
+
${this.menuItems.map(({ key, label, icon }) => {
|
|
1231
|
+
const isSelected = this.selectedMenu === key;
|
|
1232
|
+
const tabClasses = [
|
|
1233
|
+
"inline-flex items-center gap-2 rounded-md px-3 py-2 transition focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-300",
|
|
1234
|
+
isSelected ? "bg-gray-900 text-white shadow-sm" : "text-gray-600 hover:bg-gray-100 hover:text-gray-900",
|
|
1235
|
+
].join(" ");
|
|
1236
|
+
|
|
1237
|
+
return html`
|
|
1238
|
+
<button
|
|
1239
|
+
type="button"
|
|
1240
|
+
class=${tabClasses}
|
|
1241
|
+
aria-pressed=${isSelected}
|
|
1242
|
+
@click=${() => this.handleMenuSelect(key)}
|
|
1243
|
+
>
|
|
1244
|
+
<span class="text-gray-400 ${isSelected ? 'text-white' : ''}">
|
|
1245
|
+
${this.renderIcon(icon)}
|
|
1246
|
+
</span>
|
|
1247
|
+
<span>${label}</span>
|
|
1248
|
+
</button>
|
|
1249
|
+
`;
|
|
1250
|
+
})}
|
|
944
1251
|
</div>
|
|
945
|
-
</
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
@pointerup=${isDocked ? undefined : this.handlePointerUp}
|
|
953
|
-
@pointercancel=${isDocked ? undefined : this.handlePointerCancel}
|
|
954
|
-
>
|
|
955
|
-
<div class="flex min-w-0 flex-1 items-center gap-2 text-xs text-gray-500">
|
|
956
|
-
<div class="flex min-w-0 flex-1 items-center text-xs text-gray-600">
|
|
957
|
-
<span class="flex shrink-0 items-center gap-1">
|
|
958
|
-
<span>🪁</span>
|
|
959
|
-
<span class="font-medium whitespace-nowrap">CopilotKit Inspector</span>
|
|
960
|
-
</span>
|
|
961
|
-
<span class="mx-3 h-3 w-px shrink-0 bg-gray-200"></span>
|
|
962
|
-
<span class="shrink-0 text-gray-400">
|
|
963
|
-
${this.renderIcon(this.getSelectedMenu().icon)}
|
|
964
|
-
</span>
|
|
965
|
-
<span class="ml-2 truncate">${this.getSelectedMenu().label}</span>
|
|
966
|
-
${hasContextDropdown
|
|
967
|
-
? html`
|
|
968
|
-
<span class="mx-3 h-3 w-px shrink-0 bg-gray-200"></span>
|
|
969
|
-
<div class="min-w-0">${contextDropdown}</div>
|
|
970
|
-
`
|
|
971
|
-
: nothing}
|
|
972
|
-
</div>
|
|
1252
|
+
</div>
|
|
1253
|
+
<div class="flex flex-1 flex-col overflow-hidden">
|
|
1254
|
+
<div class="flex-1 overflow-auto">
|
|
1255
|
+
${this.renderAnnouncementPanel()}
|
|
1256
|
+
${this.renderCoreWarningBanner()}
|
|
1257
|
+
${this.renderMainContent()}
|
|
1258
|
+
<slot></slot>
|
|
973
1259
|
</div>
|
|
974
|
-
<div class="
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
type="button"
|
|
979
|
-
aria-label="Close Web Inspector"
|
|
980
|
-
@pointerdown=${this.handleClosePointerDown}
|
|
981
|
-
@click=${this.handleCloseClick}
|
|
1260
|
+
<div class="border-t border-gray-200 bg-gray-50 px-4 py-2">
|
|
1261
|
+
<div
|
|
1262
|
+
class="flex items-center gap-2 rounded-md px-3 py-2 text-xs ${coreStatus.tone} w-full overflow-hidden my-1"
|
|
1263
|
+
title=${coreStatus.description}
|
|
982
1264
|
>
|
|
983
|
-
|
|
984
|
-
|
|
1265
|
+
<span class="flex h-6 w-6 items-center justify-center rounded bg-white/60">
|
|
1266
|
+
${this.renderIcon("Activity")}
|
|
1267
|
+
</span>
|
|
1268
|
+
<span class="font-medium">${coreStatus.label}</span>
|
|
1269
|
+
<span class="truncate text-[11px] opacity-80">${coreStatus.description}</span>
|
|
1270
|
+
</div>
|
|
985
1271
|
</div>
|
|
986
1272
|
</div>
|
|
987
|
-
<div class="flex-1 overflow-auto">
|
|
988
|
-
${this.renderMainContent()}
|
|
989
|
-
<slot></slot>
|
|
990
|
-
</div>
|
|
991
1273
|
</div>
|
|
992
|
-
</div>
|
|
993
1274
|
<div
|
|
994
1275
|
class="resize-handle pointer-events-auto absolute bottom-1 right-1 flex h-5 w-5 cursor-nwse-resize items-center justify-center text-gray-400 transition hover:text-gray-600"
|
|
995
1276
|
role="presentation"
|
|
@@ -1015,12 +1296,12 @@ export class WebInspectorElement extends LitElement {
|
|
|
1015
1296
|
`;
|
|
1016
1297
|
}
|
|
1017
1298
|
|
|
1018
|
-
private
|
|
1299
|
+
private hydrateStateFromStorageEarly(): void {
|
|
1019
1300
|
if (typeof document === "undefined" || typeof window === "undefined") {
|
|
1020
1301
|
return;
|
|
1021
1302
|
}
|
|
1022
1303
|
|
|
1023
|
-
const persisted = loadInspectorState(
|
|
1304
|
+
const persisted = loadInspectorState(INSPECTOR_STORAGE_KEY);
|
|
1024
1305
|
if (!persisted) {
|
|
1025
1306
|
return;
|
|
1026
1307
|
}
|
|
@@ -1050,12 +1331,12 @@ export class WebInspectorElement extends LitElement {
|
|
|
1050
1331
|
}
|
|
1051
1332
|
}
|
|
1052
1333
|
|
|
1053
|
-
private
|
|
1334
|
+
private hydrateStateFromStorage(): void {
|
|
1054
1335
|
if (typeof document === "undefined" || typeof window === "undefined") {
|
|
1055
1336
|
return;
|
|
1056
1337
|
}
|
|
1057
1338
|
|
|
1058
|
-
const persisted = loadInspectorState(
|
|
1339
|
+
const persisted = loadInspectorState(INSPECTOR_STORAGE_KEY);
|
|
1059
1340
|
if (!persisted) {
|
|
1060
1341
|
return;
|
|
1061
1342
|
}
|
|
@@ -1115,6 +1396,11 @@ export class WebInspectorElement extends LitElement {
|
|
|
1115
1396
|
const contextAttr = target?.dataset.dragContext;
|
|
1116
1397
|
const context: ContextKey = contextAttr === "window" ? "window" : "button";
|
|
1117
1398
|
|
|
1399
|
+
const eventTarget = event.target as HTMLElement | null;
|
|
1400
|
+
if (context === "window" && eventTarget?.closest("button")) {
|
|
1401
|
+
return;
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1118
1404
|
this.pointerContext = context;
|
|
1119
1405
|
this.measureContext(context);
|
|
1120
1406
|
|
|
@@ -1433,7 +1719,7 @@ export class WebInspectorElement extends LitElement {
|
|
|
1433
1719
|
selectedMenu: this.selectedMenu,
|
|
1434
1720
|
selectedContext: this.selectedContext,
|
|
1435
1721
|
};
|
|
1436
|
-
saveInspectorState(
|
|
1722
|
+
saveInspectorState(INSPECTOR_STORAGE_KEY, state);
|
|
1437
1723
|
this.pendingSelectedContext = state.selectedContext ?? null;
|
|
1438
1724
|
}
|
|
1439
1725
|
|
|
@@ -1463,7 +1749,6 @@ export class WebInspectorElement extends LitElement {
|
|
|
1463
1749
|
// Clean up previous dock state
|
|
1464
1750
|
this.removeDockStyles();
|
|
1465
1751
|
|
|
1466
|
-
const previousMode = this.dockMode;
|
|
1467
1752
|
this.dockMode = mode;
|
|
1468
1753
|
|
|
1469
1754
|
if (mode !== 'floating') {
|
|
@@ -1644,6 +1929,10 @@ export class WebInspectorElement extends LitElement {
|
|
|
1644
1929
|
return;
|
|
1645
1930
|
}
|
|
1646
1931
|
|
|
1932
|
+
this.showAnnouncementPreview = false; // hide the bubble once the inspector is opened
|
|
1933
|
+
|
|
1934
|
+
this.ensureAnnouncementLoading();
|
|
1935
|
+
|
|
1647
1936
|
this.isOpen = true;
|
|
1648
1937
|
this.persistState(); // Save the open state
|
|
1649
1938
|
|
|
@@ -1666,6 +1955,7 @@ export class WebInspectorElement extends LitElement {
|
|
|
1666
1955
|
// Update transform for docked position
|
|
1667
1956
|
this.updateHostTransform("window");
|
|
1668
1957
|
}
|
|
1958
|
+
|
|
1669
1959
|
});
|
|
1670
1960
|
}
|
|
1671
1961
|
|
|
@@ -1719,7 +2009,7 @@ export class WebInspectorElement extends LitElement {
|
|
|
1719
2009
|
// Show dock left button
|
|
1720
2010
|
return html`
|
|
1721
2011
|
<button
|
|
1722
|
-
class="flex h-
|
|
2012
|
+
class="flex h-8 w-8 items-center justify-center rounded-md text-gray-400 transition hover:bg-gray-100 hover:text-gray-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-400"
|
|
1723
2013
|
type="button"
|
|
1724
2014
|
aria-label="Dock to left"
|
|
1725
2015
|
title="Dock Left"
|
|
@@ -1732,7 +2022,7 @@ export class WebInspectorElement extends LitElement {
|
|
|
1732
2022
|
// Show float button
|
|
1733
2023
|
return html`
|
|
1734
2024
|
<button
|
|
1735
|
-
class="flex h-
|
|
2025
|
+
class="flex h-8 w-8 items-center justify-center rounded-md text-gray-400 transition hover:bg-gray-100 hover:text-gray-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-400"
|
|
1736
2026
|
type="button"
|
|
1737
2027
|
aria-label="Float window"
|
|
1738
2028
|
title="Float"
|
|
@@ -1777,6 +2067,179 @@ export class WebInspectorElement extends LitElement {
|
|
|
1777
2067
|
.join(" ");
|
|
1778
2068
|
}
|
|
1779
2069
|
|
|
2070
|
+
private sanitizeForLogging(value: unknown, depth = 0, seen = new WeakSet<object>()): SanitizedValue {
|
|
2071
|
+
if (value === undefined) {
|
|
2072
|
+
return "[undefined]";
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
if (value === null || typeof value === "number" || typeof value === "boolean") {
|
|
2076
|
+
return value;
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
if (typeof value === "string") {
|
|
2080
|
+
return value;
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
if (typeof value === "bigint" || typeof value === "symbol" || typeof value === "function") {
|
|
2084
|
+
return String(value);
|
|
2085
|
+
}
|
|
2086
|
+
|
|
2087
|
+
if (value instanceof Date) {
|
|
2088
|
+
return value.toISOString();
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
if (Array.isArray(value)) {
|
|
2092
|
+
if (depth >= 4) {
|
|
2093
|
+
return "[Truncated depth]" as SanitizedValue;
|
|
2094
|
+
}
|
|
2095
|
+
return value.map((item) => this.sanitizeForLogging(item, depth + 1, seen));
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
if (typeof value === "object") {
|
|
2099
|
+
if (seen.has(value as object)) {
|
|
2100
|
+
return "[Circular]" as SanitizedValue;
|
|
2101
|
+
}
|
|
2102
|
+
seen.add(value as object);
|
|
2103
|
+
|
|
2104
|
+
if (depth >= 4) {
|
|
2105
|
+
return "[Truncated depth]" as SanitizedValue;
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
const result: Record<string, SanitizedValue> = {};
|
|
2109
|
+
for (const [key, entry] of Object.entries(value as Record<string, unknown>)) {
|
|
2110
|
+
result[key] = this.sanitizeForLogging(entry, depth + 1, seen);
|
|
2111
|
+
}
|
|
2112
|
+
return result;
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
return String(value);
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2118
|
+
private normalizeEventPayload(_type: InspectorAgentEventType, payload: unknown): SanitizedValue {
|
|
2119
|
+
if (payload && typeof payload === "object" && "event" in payload) {
|
|
2120
|
+
const { event, ...rest } = payload as Record<string, unknown>;
|
|
2121
|
+
const cleaned = Object.keys(rest).length === 0 ? event : { event, ...rest };
|
|
2122
|
+
return this.sanitizeForLogging(cleaned);
|
|
2123
|
+
}
|
|
2124
|
+
|
|
2125
|
+
return this.sanitizeForLogging(payload);
|
|
2126
|
+
}
|
|
2127
|
+
|
|
2128
|
+
private normalizeMessageContent(content: unknown): string {
|
|
2129
|
+
if (typeof content === "string") {
|
|
2130
|
+
return content;
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
if (content && typeof content === "object" && "text" in (content as Record<string, unknown>)) {
|
|
2134
|
+
const maybeText = (content as Record<string, unknown>).text;
|
|
2135
|
+
if (typeof maybeText === "string") {
|
|
2136
|
+
return maybeText;
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
|
|
2140
|
+
if (content === null || content === undefined) {
|
|
2141
|
+
return "";
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
if (typeof content === "object") {
|
|
2145
|
+
try {
|
|
2146
|
+
return JSON.stringify(this.sanitizeForLogging(content));
|
|
2147
|
+
} catch {
|
|
2148
|
+
return "";
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2152
|
+
return String(content);
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
private normalizeToolCalls(raw: unknown): InspectorToolCall[] {
|
|
2156
|
+
if (!Array.isArray(raw)) {
|
|
2157
|
+
return [];
|
|
2158
|
+
}
|
|
2159
|
+
|
|
2160
|
+
return raw
|
|
2161
|
+
.map((entry) => {
|
|
2162
|
+
if (!entry || typeof entry !== "object") {
|
|
2163
|
+
return null;
|
|
2164
|
+
}
|
|
2165
|
+
const call = entry as Record<string, unknown>;
|
|
2166
|
+
const fn = call.function as Record<string, unknown> | undefined;
|
|
2167
|
+
const functionName = typeof fn?.name === "string" ? fn.name : typeof call.toolName === "string" ? call.toolName : undefined;
|
|
2168
|
+
const args = fn && "arguments" in fn ? (fn as Record<string, unknown>).arguments : call.arguments;
|
|
2169
|
+
|
|
2170
|
+
const normalized: InspectorToolCall = {
|
|
2171
|
+
id: typeof call.id === "string" ? call.id : undefined,
|
|
2172
|
+
toolName: typeof call.toolName === "string" ? call.toolName : functionName,
|
|
2173
|
+
status: typeof call.status === "string" ? call.status : undefined,
|
|
2174
|
+
};
|
|
2175
|
+
|
|
2176
|
+
if (functionName) {
|
|
2177
|
+
normalized.function = {
|
|
2178
|
+
name: functionName,
|
|
2179
|
+
arguments: this.sanitizeForLogging(args),
|
|
2180
|
+
};
|
|
2181
|
+
}
|
|
2182
|
+
|
|
2183
|
+
return normalized;
|
|
2184
|
+
})
|
|
2185
|
+
.filter((call): call is InspectorToolCall => Boolean(call));
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
private normalizeAgentMessage(message: unknown): InspectorMessage | null {
|
|
2189
|
+
if (!message || typeof message !== "object") {
|
|
2190
|
+
return null;
|
|
2191
|
+
}
|
|
2192
|
+
|
|
2193
|
+
const raw = message as Record<string, unknown>;
|
|
2194
|
+
const role = typeof raw.role === "string" ? raw.role : "unknown";
|
|
2195
|
+
const contentText = this.normalizeMessageContent(raw.content);
|
|
2196
|
+
const toolCalls = this.normalizeToolCalls(raw.toolCalls);
|
|
2197
|
+
|
|
2198
|
+
return {
|
|
2199
|
+
id: typeof raw.id === "string" ? raw.id : undefined,
|
|
2200
|
+
role,
|
|
2201
|
+
contentText,
|
|
2202
|
+
contentRaw: raw.content !== undefined ? this.sanitizeForLogging(raw.content) : undefined,
|
|
2203
|
+
toolCalls,
|
|
2204
|
+
};
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
private normalizeAgentMessages(messages: unknown): InspectorMessage[] | null {
|
|
2208
|
+
if (!Array.isArray(messages)) {
|
|
2209
|
+
return null;
|
|
2210
|
+
}
|
|
2211
|
+
|
|
2212
|
+
const normalized = messages
|
|
2213
|
+
.map((message) => this.normalizeAgentMessage(message))
|
|
2214
|
+
.filter((msg): msg is InspectorMessage => msg !== null);
|
|
2215
|
+
|
|
2216
|
+
return normalized;
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
private normalizeContextStore(
|
|
2220
|
+
context: Readonly<Record<string, unknown>> | null | undefined,
|
|
2221
|
+
): Record<string, { description?: string; value: unknown }> {
|
|
2222
|
+
if (!context || typeof context !== "object") {
|
|
2223
|
+
return {};
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2226
|
+
const normalized: Record<string, { description?: string; value: unknown }> = {};
|
|
2227
|
+
for (const [key, entry] of Object.entries(context)) {
|
|
2228
|
+
if (entry && typeof entry === "object" && "value" in (entry as Record<string, unknown>)) {
|
|
2229
|
+
const candidate = entry as Record<string, unknown>;
|
|
2230
|
+
const description =
|
|
2231
|
+
typeof candidate.description === "string" && candidate.description.trim().length > 0
|
|
2232
|
+
? candidate.description
|
|
2233
|
+
: undefined;
|
|
2234
|
+
normalized[key] = { description, value: candidate.value };
|
|
2235
|
+
} else {
|
|
2236
|
+
normalized[key] = { value: entry };
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2240
|
+
return normalized;
|
|
2241
|
+
}
|
|
2242
|
+
|
|
1780
2243
|
private contextOptions: Array<{ key: string; label: string }> = [
|
|
1781
2244
|
{ key: "all-agents", label: "All Agents" },
|
|
1782
2245
|
];
|
|
@@ -1786,12 +2249,75 @@ export class WebInspectorElement extends LitElement {
|
|
|
1786
2249
|
private copiedEvents: Set<string> = new Set();
|
|
1787
2250
|
private expandedTools: Set<string> = new Set();
|
|
1788
2251
|
private expandedContextItems: Set<string> = new Set();
|
|
2252
|
+
private copiedContextItems: Set<string> = new Set();
|
|
1789
2253
|
|
|
1790
2254
|
private getSelectedMenu(): MenuItem {
|
|
1791
2255
|
const found = this.menuItems.find((item) => item.key === this.selectedMenu);
|
|
1792
2256
|
return found ?? this.menuItems[0]!;
|
|
1793
2257
|
}
|
|
1794
2258
|
|
|
2259
|
+
private renderCoreWarningBanner() {
|
|
2260
|
+
if (this._core) {
|
|
2261
|
+
return nothing;
|
|
2262
|
+
}
|
|
2263
|
+
|
|
2264
|
+
return html`
|
|
2265
|
+
<div class="mx-4 my-3 flex items-start gap-2 rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-800">
|
|
2266
|
+
<span class="mt-0.5 shrink-0 text-amber-600">${this.renderIcon("AlertTriangle")}</span>
|
|
2267
|
+
<div class="space-y-1">
|
|
2268
|
+
<div class="font-semibold text-amber-900">CopilotKit core not attached</div>
|
|
2269
|
+
<p class="text-[11px] leading-snug text-amber-800">
|
|
2270
|
+
Pass a live <code>CopilotKitCore</code> instance to <code><cpk-web-inspector></code> or expose it on
|
|
2271
|
+
<code>window.__COPILOTKIT_CORE__</code> for auto-attach.
|
|
2272
|
+
</p>
|
|
2273
|
+
</div>
|
|
2274
|
+
</div>
|
|
2275
|
+
`;
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
private getCoreStatusSummary(): { label: string; tone: string; description: string } {
|
|
2279
|
+
if (!this._core) {
|
|
2280
|
+
return {
|
|
2281
|
+
label: "Core not attached",
|
|
2282
|
+
tone: "border border-amber-200 bg-amber-50 text-amber-800",
|
|
2283
|
+
description: "Pass a CopilotKitCore instance to <cpk-web-inspector> or enable auto-attach.",
|
|
2284
|
+
};
|
|
2285
|
+
}
|
|
2286
|
+
|
|
2287
|
+
const status = this.runtimeStatus ?? CopilotKitCoreRuntimeConnectionStatus.Disconnected;
|
|
2288
|
+
const lastErrorMessage = this.lastCoreError?.message;
|
|
2289
|
+
|
|
2290
|
+
if (status === CopilotKitCoreRuntimeConnectionStatus.Error) {
|
|
2291
|
+
return {
|
|
2292
|
+
label: "Runtime error",
|
|
2293
|
+
tone: "border border-rose-200 bg-rose-50 text-rose-700",
|
|
2294
|
+
description: lastErrorMessage ?? "CopilotKit runtime reported an error.",
|
|
2295
|
+
};
|
|
2296
|
+
}
|
|
2297
|
+
|
|
2298
|
+
if (status === CopilotKitCoreRuntimeConnectionStatus.Connecting) {
|
|
2299
|
+
return {
|
|
2300
|
+
label: "Connecting",
|
|
2301
|
+
tone: "border border-amber-200 bg-amber-50 text-amber-800",
|
|
2302
|
+
description: "Waiting for CopilotKit runtime to finish connecting.",
|
|
2303
|
+
};
|
|
2304
|
+
}
|
|
2305
|
+
|
|
2306
|
+
if (status === CopilotKitCoreRuntimeConnectionStatus.Connected) {
|
|
2307
|
+
return {
|
|
2308
|
+
label: "Connected",
|
|
2309
|
+
tone: "border border-emerald-200 bg-emerald-50 text-emerald-700",
|
|
2310
|
+
description: "Live runtime connection established.",
|
|
2311
|
+
};
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2314
|
+
return {
|
|
2315
|
+
label: "Disconnected",
|
|
2316
|
+
tone: "border border-gray-200 bg-gray-50 text-gray-700",
|
|
2317
|
+
description: lastErrorMessage ?? "Waiting for CopilotKit runtime to connect.",
|
|
2318
|
+
};
|
|
2319
|
+
}
|
|
2320
|
+
|
|
1795
2321
|
private renderMainContent() {
|
|
1796
2322
|
if (this.selectedMenu === "ag-ui-events") {
|
|
1797
2323
|
return this.renderEventsTable();
|
|
@@ -1809,17 +2335,13 @@ export class WebInspectorElement extends LitElement {
|
|
|
1809
2335
|
return this.renderContextView();
|
|
1810
2336
|
}
|
|
1811
2337
|
|
|
1812
|
-
|
|
1813
|
-
return html`
|
|
1814
|
-
<div class="flex flex-col gap-3 p-4">
|
|
1815
|
-
<div class="h-24 rounded-lg bg-gray-50"></div>
|
|
1816
|
-
<div class="h-20 rounded-lg bg-gray-50"></div>
|
|
1817
|
-
</div>
|
|
1818
|
-
`;
|
|
2338
|
+
return nothing;
|
|
1819
2339
|
}
|
|
1820
2340
|
|
|
1821
2341
|
private renderEventsTable() {
|
|
1822
2342
|
const events = this.getEventsForSelectedContext();
|
|
2343
|
+
const filteredEvents = this.filterEvents(events);
|
|
2344
|
+
const selectedLabel = this.selectedContext === "all-agents" ? "all agents" : `agent ${this.selectedContext}`;
|
|
1823
2345
|
|
|
1824
2346
|
if (events.length === 0) {
|
|
1825
2347
|
return html`
|
|
@@ -1835,83 +2357,221 @@ export class WebInspectorElement extends LitElement {
|
|
|
1835
2357
|
`;
|
|
1836
2358
|
}
|
|
1837
2359
|
|
|
2360
|
+
if (filteredEvents.length === 0) {
|
|
2361
|
+
return html`
|
|
2362
|
+
<div class="flex h-full items-center justify-center px-4 py-8 text-center">
|
|
2363
|
+
<div class="max-w-md space-y-3">
|
|
2364
|
+
<div class="flex justify-center text-gray-300 [&>svg]:!h-8 [&>svg]:!w-8">
|
|
2365
|
+
${this.renderIcon("Filter")}
|
|
2366
|
+
</div>
|
|
2367
|
+
<p class="text-sm text-gray-600">No events match the current filters.</p>
|
|
2368
|
+
<div>
|
|
2369
|
+
<button
|
|
2370
|
+
type="button"
|
|
2371
|
+
class="inline-flex items-center gap-1 rounded-md bg-gray-900 px-3 py-1.5 text-[11px] font-medium text-white transition hover:bg-gray-800"
|
|
2372
|
+
@click=${this.resetEventFilters}
|
|
2373
|
+
>
|
|
2374
|
+
${this.renderIcon("RefreshCw")}
|
|
2375
|
+
<span>Reset filters</span>
|
|
2376
|
+
</button>
|
|
2377
|
+
</div>
|
|
2378
|
+
</div>
|
|
2379
|
+
</div>
|
|
2380
|
+
`;
|
|
2381
|
+
}
|
|
2382
|
+
|
|
1838
2383
|
return html`
|
|
1839
|
-
<div class="
|
|
1840
|
-
<
|
|
1841
|
-
<
|
|
1842
|
-
<
|
|
1843
|
-
<
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
2384
|
+
<div class="flex h-full flex-col">
|
|
2385
|
+
<div class="flex flex-col gap-1.5 border-b border-gray-200 bg-white px-4 py-2.5">
|
|
2386
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
2387
|
+
<div class="relative min-w-[200px] flex-1">
|
|
2388
|
+
<input
|
|
2389
|
+
type="search"
|
|
2390
|
+
class="w-full rounded-md border border-gray-200 px-3 py-1.5 text-[11px] text-gray-700 shadow-sm outline-none ring-1 ring-transparent transition focus:border-gray-300 focus:ring-gray-200"
|
|
2391
|
+
placeholder="Search agent, type, payload"
|
|
2392
|
+
.value=${this.eventFilterText}
|
|
2393
|
+
@input=${this.handleEventFilterInput}
|
|
2394
|
+
/>
|
|
2395
|
+
</div>
|
|
2396
|
+
<select
|
|
2397
|
+
class="w-40 rounded-md border border-gray-200 bg-white px-2 py-1.5 text-[11px] text-gray-700 shadow-sm outline-none transition focus:border-gray-300 focus:ring-2 focus:ring-gray-200"
|
|
2398
|
+
.value=${this.eventTypeFilter}
|
|
2399
|
+
@change=${this.handleEventTypeChange}
|
|
2400
|
+
>
|
|
2401
|
+
<option value="all">All event types</option>
|
|
2402
|
+
${AGENT_EVENT_TYPES.map(
|
|
2403
|
+
(type) =>
|
|
2404
|
+
html`<option value=${type}>${type.toLowerCase().replace(/_/g, " ")}</option>`,
|
|
2405
|
+
)}
|
|
2406
|
+
</select>
|
|
2407
|
+
<div class="flex items-center gap-1 text-[11px]">
|
|
2408
|
+
<button
|
|
2409
|
+
type="button"
|
|
2410
|
+
class="tooltip-target flex h-8 w-8 items-center justify-center rounded-md border border-gray-200 bg-white text-gray-600 transition hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-50"
|
|
2411
|
+
title="Reset filters"
|
|
2412
|
+
data-tooltip="Reset filters"
|
|
2413
|
+
aria-label="Reset filters"
|
|
2414
|
+
@click=${this.resetEventFilters}
|
|
2415
|
+
?disabled=${!this.eventFilterText && this.eventTypeFilter === "all"}
|
|
2416
|
+
>
|
|
2417
|
+
${this.renderIcon("RotateCw")}
|
|
2418
|
+
</button>
|
|
2419
|
+
<button
|
|
2420
|
+
type="button"
|
|
2421
|
+
class="tooltip-target flex h-8 w-8 items-center justify-center rounded-md border border-gray-200 bg-white text-gray-600 transition hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-50"
|
|
2422
|
+
title="Export JSON"
|
|
2423
|
+
data-tooltip="Export JSON"
|
|
2424
|
+
aria-label="Export JSON"
|
|
2425
|
+
@click=${() => this.exportEvents(filteredEvents)}
|
|
2426
|
+
?disabled=${filteredEvents.length === 0}
|
|
2427
|
+
>
|
|
2428
|
+
${this.renderIcon("Download")}
|
|
2429
|
+
</button>
|
|
2430
|
+
<button
|
|
2431
|
+
type="button"
|
|
2432
|
+
class="tooltip-target flex h-8 w-8 items-center justify-center rounded-md border border-gray-200 bg-white text-gray-600 transition hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-50"
|
|
2433
|
+
title="Clear events"
|
|
2434
|
+
data-tooltip="Clear events"
|
|
2435
|
+
aria-label="Clear events"
|
|
2436
|
+
@click=${this.handleClearEvents}
|
|
2437
|
+
?disabled=${events.length === 0}
|
|
2438
|
+
>
|
|
2439
|
+
${this.renderIcon("Trash2")}
|
|
2440
|
+
</button>
|
|
2441
|
+
</div>
|
|
2442
|
+
</div>
|
|
2443
|
+
<div class="text-[11px] text-gray-500">
|
|
2444
|
+
Showing ${filteredEvents.length} of ${events.length}${this.selectedContext === "all-agents" ? "" : ` for ${selectedLabel}`}
|
|
2445
|
+
</div>
|
|
2446
|
+
</div>
|
|
2447
|
+
<div class="relative h-full w-full overflow-y-auto overflow-x-hidden">
|
|
2448
|
+
<table class="w-full table-fixed border-collapse text-xs box-border">
|
|
2449
|
+
<thead class="sticky top-0 z-10">
|
|
2450
|
+
<tr class="bg-white">
|
|
2451
|
+
<th class="border-b border-gray-200 bg-white px-3 py-2 text-left font-medium text-gray-900">
|
|
2452
|
+
Agent
|
|
2453
|
+
</th>
|
|
2454
|
+
<th class="border-b border-gray-200 bg-white px-3 py-2 text-left font-medium text-gray-900">
|
|
2455
|
+
Time
|
|
2456
|
+
</th>
|
|
2457
|
+
<th class="border-b border-gray-200 bg-white px-3 py-2 text-left font-medium text-gray-900">
|
|
2458
|
+
Event Type
|
|
2459
|
+
</th>
|
|
2460
|
+
<th class="border-b border-gray-200 bg-white px-3 py-2 text-left font-medium text-gray-900">
|
|
2461
|
+
AG-UI Event
|
|
2462
|
+
</th>
|
|
2463
|
+
</tr>
|
|
2464
|
+
</thead>
|
|
2465
|
+
<tbody>
|
|
2466
|
+
${filteredEvents.map((event, index) => {
|
|
2467
|
+
const rowBg = index % 2 === 0 ? "bg-white" : "bg-gray-50/50";
|
|
2468
|
+
const badgeClasses = this.getEventBadgeClasses(event.type);
|
|
2469
|
+
const extractedEvent = this.extractEventFromPayload(event.payload);
|
|
2470
|
+
const inlineEvent = this.stringifyPayload(extractedEvent, false) || "—";
|
|
2471
|
+
const prettyEvent = this.stringifyPayload(extractedEvent, true) || inlineEvent;
|
|
2472
|
+
const isExpanded = this.expandedRows.has(event.id);
|
|
2473
|
+
|
|
2474
|
+
return html`
|
|
2475
|
+
<tr
|
|
2476
|
+
class="${rowBg} cursor-pointer transition hover:bg-blue-50/50"
|
|
2477
|
+
@click=${() => this.toggleRowExpansion(event.id)}
|
|
2478
|
+
>
|
|
2479
|
+
<td class="border-l border-r border-b border-gray-200 px-3 py-2">
|
|
2480
|
+
<span class="font-mono text-[11px] text-gray-600">${event.agentId}</span>
|
|
2481
|
+
</td>
|
|
2482
|
+
<td class="border-r border-b border-gray-200 px-3 py-2 font-mono text-[11px] text-gray-600">
|
|
2483
|
+
<span title=${new Date(event.timestamp).toLocaleString()}>
|
|
2484
|
+
${new Date(event.timestamp).toLocaleTimeString()}
|
|
2485
|
+
</span>
|
|
2486
|
+
</td>
|
|
2487
|
+
<td class="border-r border-b border-gray-200 px-3 py-2">
|
|
2488
|
+
<span class=${badgeClasses}>${event.type}</span>
|
|
2489
|
+
</td>
|
|
2490
|
+
<td class="border-r border-b border-gray-200 px-3 py-2 font-mono text-[10px] text-gray-600 ${isExpanded ? '' : 'truncate max-w-xs'}">
|
|
2491
|
+
${isExpanded
|
|
2492
|
+
? html`
|
|
2493
|
+
<div class="group relative">
|
|
2494
|
+
<pre class="m-0 whitespace-pre-wrap break-words text-[10px] font-mono text-gray-600">${prettyEvent}</pre>
|
|
2495
|
+
<button
|
|
2496
|
+
class="absolute right-0 top-0 cursor-pointer rounded px-2 py-1 text-[10px] opacity-0 transition group-hover:opacity-100 ${
|
|
2497
|
+
this.copiedEvents.has(event.id)
|
|
2498
|
+
? 'bg-green-100 text-green-700'
|
|
2499
|
+
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 hover:text-gray-900'
|
|
2500
|
+
}"
|
|
2501
|
+
@click=${(e: Event) => {
|
|
2502
|
+
e.stopPropagation();
|
|
2503
|
+
this.copyToClipboard(prettyEvent, event.id);
|
|
2504
|
+
}}
|
|
2505
|
+
>
|
|
2506
|
+
${this.copiedEvents.has(event.id)
|
|
2507
|
+
? html`<span>✓ Copied</span>`
|
|
2508
|
+
: html`<span>Copy</span>`}
|
|
2509
|
+
</button>
|
|
2510
|
+
</div>
|
|
2511
|
+
`
|
|
2512
|
+
: inlineEvent}
|
|
2513
|
+
</td>
|
|
2514
|
+
</tr>
|
|
2515
|
+
`;
|
|
2516
|
+
})}
|
|
2517
|
+
</tbody>
|
|
2518
|
+
</table>
|
|
2519
|
+
</div>
|
|
1911
2520
|
</div>
|
|
1912
2521
|
`;
|
|
1913
2522
|
}
|
|
1914
2523
|
|
|
2524
|
+
private handleEventFilterInput(event: Event): void {
|
|
2525
|
+
const target = event.target as HTMLInputElement | null;
|
|
2526
|
+
this.eventFilterText = target?.value ?? "";
|
|
2527
|
+
this.requestUpdate();
|
|
2528
|
+
}
|
|
2529
|
+
|
|
2530
|
+
private handleEventTypeChange(event: Event): void {
|
|
2531
|
+
const target = event.target as HTMLSelectElement | null;
|
|
2532
|
+
const value = target?.value as InspectorAgentEventType | "all" | undefined;
|
|
2533
|
+
if (!value) {
|
|
2534
|
+
return;
|
|
2535
|
+
}
|
|
2536
|
+
this.eventTypeFilter = value;
|
|
2537
|
+
this.requestUpdate();
|
|
2538
|
+
}
|
|
2539
|
+
|
|
2540
|
+
private resetEventFilters(): void {
|
|
2541
|
+
this.eventFilterText = "";
|
|
2542
|
+
this.eventTypeFilter = "all";
|
|
2543
|
+
this.requestUpdate();
|
|
2544
|
+
}
|
|
2545
|
+
|
|
2546
|
+
private handleClearEvents = (): void => {
|
|
2547
|
+
if (this.selectedContext === "all-agents") {
|
|
2548
|
+
this.agentEvents.clear();
|
|
2549
|
+
this.flattenedEvents = [];
|
|
2550
|
+
} else {
|
|
2551
|
+
this.agentEvents.delete(this.selectedContext);
|
|
2552
|
+
this.flattenedEvents = this.flattenedEvents.filter((event) => event.agentId !== this.selectedContext);
|
|
2553
|
+
}
|
|
2554
|
+
|
|
2555
|
+
this.expandedRows.clear();
|
|
2556
|
+
this.copiedEvents.clear();
|
|
2557
|
+
this.requestUpdate();
|
|
2558
|
+
};
|
|
2559
|
+
|
|
2560
|
+
private exportEvents(events: InspectorEvent[]): void {
|
|
2561
|
+
try {
|
|
2562
|
+
const payload = JSON.stringify(events, null, 2);
|
|
2563
|
+
const blob = new Blob([payload], { type: "application/json" });
|
|
2564
|
+
const url = URL.createObjectURL(blob);
|
|
2565
|
+
const anchor = document.createElement("a");
|
|
2566
|
+
anchor.href = url;
|
|
2567
|
+
anchor.download = `copilotkit-events-${Date.now()}.json`;
|
|
2568
|
+
anchor.click();
|
|
2569
|
+
URL.revokeObjectURL(url);
|
|
2570
|
+
} catch (error) {
|
|
2571
|
+
console.error("Failed to export events", error);
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2574
|
+
|
|
1915
2575
|
private renderAgentsView() {
|
|
1916
2576
|
// Show message if "all-agents" is selected or no agents available
|
|
1917
2577
|
if (this.selectedContext === "all-agents") {
|
|
@@ -2013,7 +2673,7 @@ export class WebInspectorElement extends LitElement {
|
|
|
2013
2673
|
<h4 class="text-sm font-semibold text-gray-900">Current Messages</h4>
|
|
2014
2674
|
</div>
|
|
2015
2675
|
<div class="overflow-auto">
|
|
2016
|
-
${messages &&
|
|
2676
|
+
${messages && messages.length > 0
|
|
2017
2677
|
? html`
|
|
2018
2678
|
<table class="w-full text-xs">
|
|
2019
2679
|
<thead class="bg-gray-50">
|
|
@@ -2023,26 +2683,20 @@ export class WebInspectorElement extends LitElement {
|
|
|
2023
2683
|
</tr>
|
|
2024
2684
|
</thead>
|
|
2025
2685
|
<tbody class="divide-y divide-gray-200">
|
|
2026
|
-
${messages.map((msg
|
|
2027
|
-
const role = msg
|
|
2686
|
+
${messages.map((msg) => {
|
|
2687
|
+
const role = msg.role || "unknown";
|
|
2028
2688
|
const roleColors: Record<string, string> = {
|
|
2029
2689
|
user: "bg-blue-100 text-blue-800",
|
|
2030
2690
|
assistant: "bg-green-100 text-green-800",
|
|
2031
2691
|
system: "bg-gray-100 text-gray-800",
|
|
2692
|
+
tool: "bg-amber-100 text-amber-800",
|
|
2032
2693
|
unknown: "bg-gray-100 text-gray-600",
|
|
2033
2694
|
};
|
|
2034
2695
|
|
|
2035
|
-
const rawContent =
|
|
2036
|
-
|
|
2037
|
-
: msg?.content != null
|
|
2038
|
-
? JSON.stringify(msg.content)
|
|
2039
|
-
: "";
|
|
2040
|
-
|
|
2041
|
-
const toolCalls = Array.isArray(msg?.toolCalls) ? msg.toolCalls : [];
|
|
2696
|
+
const rawContent = msg.contentText ?? "";
|
|
2697
|
+
const toolCalls = msg.toolCalls ?? [];
|
|
2042
2698
|
const hasContent = rawContent.trim().length > 0;
|
|
2043
|
-
const contentFallback = toolCalls.length > 0
|
|
2044
|
-
? "Invoked tool call"
|
|
2045
|
-
: "—";
|
|
2699
|
+
const contentFallback = toolCalls.length > 0 ? "Invoked tool call" : "—";
|
|
2046
2700
|
|
|
2047
2701
|
return html`
|
|
2048
2702
|
<tr>
|
|
@@ -2080,15 +2734,6 @@ export class WebInspectorElement extends LitElement {
|
|
|
2080
2734
|
}
|
|
2081
2735
|
|
|
2082
2736
|
private renderContextDropdown() {
|
|
2083
|
-
// Agent Context doesn't use the dropdown - it's global
|
|
2084
|
-
if (this.selectedMenu === "agent-context") {
|
|
2085
|
-
return nothing;
|
|
2086
|
-
}
|
|
2087
|
-
|
|
2088
|
-
if (this.selectedMenu !== "ag-ui-events" && this.selectedMenu !== "agents" && this.selectedMenu !== "frontend-tools") {
|
|
2089
|
-
return nothing;
|
|
2090
|
-
}
|
|
2091
|
-
|
|
2092
2737
|
// Filter out "all-agents" when in agents view
|
|
2093
2738
|
const filteredOptions = this.selectedMenu === "agents"
|
|
2094
2739
|
? this.contextOptions.filter((opt) => opt.key !== "all-agents")
|
|
@@ -2097,10 +2742,10 @@ export class WebInspectorElement extends LitElement {
|
|
|
2097
2742
|
const selectedLabel = filteredOptions.find((opt) => opt.key === this.selectedContext)?.label ?? "";
|
|
2098
2743
|
|
|
2099
2744
|
return html`
|
|
2100
|
-
<div class="relative min-w-0 flex-1" data-context-dropdown-root="true">
|
|
2745
|
+
<div class="relative z-40 min-w-0 flex-1" data-context-dropdown-root="true">
|
|
2101
2746
|
<button
|
|
2102
2747
|
type="button"
|
|
2103
|
-
class="flex w-full min-w-0 max-w-[
|
|
2748
|
+
class="relative z-40 flex w-full min-w-0 max-w-[240px] items-center gap-1.5 rounded-md border border-gray-200 px-2 py-1 text-xs font-medium text-gray-700 transition hover:border-gray-300 hover:bg-gray-50"
|
|
2104
2749
|
@pointerdown=${this.handleContextDropdownToggle}
|
|
2105
2750
|
>
|
|
2106
2751
|
<span class="truncate flex-1 text-left">${selectedLabel}</span>
|
|
@@ -2187,7 +2832,8 @@ export class WebInspectorElement extends LitElement {
|
|
|
2187
2832
|
`;
|
|
2188
2833
|
}
|
|
2189
2834
|
|
|
2190
|
-
|
|
2835
|
+
this.refreshToolsSnapshot();
|
|
2836
|
+
const allTools = this.cachedTools;
|
|
2191
2837
|
|
|
2192
2838
|
if (allTools.length === 0) {
|
|
2193
2839
|
return html`
|
|
@@ -2206,7 +2852,7 @@ export class WebInspectorElement extends LitElement {
|
|
|
2206
2852
|
// Filter tools by selected agent
|
|
2207
2853
|
const filteredTools = this.selectedContext === "all-agents"
|
|
2208
2854
|
? allTools
|
|
2209
|
-
: allTools.filter(tool => tool.agentId === this.selectedContext);
|
|
2855
|
+
: allTools.filter((tool) => !tool.agentId || tool.agentId === this.selectedContext);
|
|
2210
2856
|
|
|
2211
2857
|
return html`
|
|
2212
2858
|
<div class="flex h-full flex-col overflow-hidden">
|
|
@@ -2219,39 +2865,43 @@ export class WebInspectorElement extends LitElement {
|
|
|
2219
2865
|
`;
|
|
2220
2866
|
}
|
|
2221
2867
|
|
|
2222
|
-
private extractToolsFromAgents():
|
|
2223
|
-
agentId: string;
|
|
2224
|
-
name: string;
|
|
2225
|
-
description?: string;
|
|
2226
|
-
parameters?: unknown;
|
|
2227
|
-
type: 'handler' | 'renderer';
|
|
2228
|
-
}> {
|
|
2868
|
+
private extractToolsFromAgents(): InspectorToolDefinition[] {
|
|
2229
2869
|
if (!this._core) {
|
|
2230
2870
|
return [];
|
|
2231
2871
|
}
|
|
2232
2872
|
|
|
2233
|
-
const tools:
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2873
|
+
const tools: InspectorToolDefinition[] = [];
|
|
2874
|
+
|
|
2875
|
+
// Start with tools registered on the core (frontend tools / HIL)
|
|
2876
|
+
for (const coreTool of this._core.tools ?? []) {
|
|
2877
|
+
tools.push({
|
|
2878
|
+
agentId: coreTool.agentId ?? "",
|
|
2879
|
+
name: coreTool.name,
|
|
2880
|
+
description: coreTool.description,
|
|
2881
|
+
parameters: coreTool.parameters,
|
|
2882
|
+
type: 'handler',
|
|
2883
|
+
});
|
|
2884
|
+
}
|
|
2240
2885
|
|
|
2886
|
+
// Augment with agent-level tool handlers/renderers
|
|
2241
2887
|
for (const [agentId, agent] of Object.entries(this._core.agents)) {
|
|
2242
2888
|
if (!agent) continue;
|
|
2243
2889
|
|
|
2244
2890
|
// Try to extract tool handlers
|
|
2245
|
-
const handlers = (agent as
|
|
2891
|
+
const handlers = (agent as { toolHandlers?: Record<string, unknown> }).toolHandlers;
|
|
2246
2892
|
if (handlers && typeof handlers === 'object') {
|
|
2247
2893
|
for (const [toolName, handler] of Object.entries(handlers)) {
|
|
2248
2894
|
if (handler && typeof handler === 'object') {
|
|
2249
|
-
const handlerObj = handler as
|
|
2895
|
+
const handlerObj = handler as Record<string, unknown>;
|
|
2250
2896
|
tools.push({
|
|
2251
2897
|
agentId,
|
|
2252
2898
|
name: toolName,
|
|
2253
|
-
description:
|
|
2254
|
-
|
|
2899
|
+
description:
|
|
2900
|
+
(typeof handlerObj.description === "string" && handlerObj.description) ||
|
|
2901
|
+
(handlerObj.tool as { description?: string } | undefined)?.description,
|
|
2902
|
+
parameters:
|
|
2903
|
+
handlerObj.parameters ??
|
|
2904
|
+
(handlerObj.tool as { parameters?: unknown } | undefined)?.parameters,
|
|
2255
2905
|
type: 'handler',
|
|
2256
2906
|
});
|
|
2257
2907
|
}
|
|
@@ -2259,18 +2909,22 @@ export class WebInspectorElement extends LitElement {
|
|
|
2259
2909
|
}
|
|
2260
2910
|
|
|
2261
2911
|
// Try to extract tool renderers
|
|
2262
|
-
const renderers = (agent as
|
|
2912
|
+
const renderers = (agent as { toolRenderers?: Record<string, unknown> }).toolRenderers;
|
|
2263
2913
|
if (renderers && typeof renderers === 'object') {
|
|
2264
2914
|
for (const [toolName, renderer] of Object.entries(renderers)) {
|
|
2265
2915
|
// Don't duplicate if we already have it as a handler
|
|
2266
2916
|
if (!tools.some(t => t.agentId === agentId && t.name === toolName)) {
|
|
2267
2917
|
if (renderer && typeof renderer === 'object') {
|
|
2268
|
-
const rendererObj = renderer as
|
|
2918
|
+
const rendererObj = renderer as Record<string, unknown>;
|
|
2269
2919
|
tools.push({
|
|
2270
2920
|
agentId,
|
|
2271
2921
|
name: toolName,
|
|
2272
|
-
description:
|
|
2273
|
-
|
|
2922
|
+
description:
|
|
2923
|
+
(typeof rendererObj.description === "string" && rendererObj.description) ||
|
|
2924
|
+
(rendererObj.tool as { description?: string } | undefined)?.description,
|
|
2925
|
+
parameters:
|
|
2926
|
+
rendererObj.parameters ??
|
|
2927
|
+
(rendererObj.tool as { parameters?: unknown } | undefined)?.parameters,
|
|
2274
2928
|
type: 'renderer',
|
|
2275
2929
|
});
|
|
2276
2930
|
}
|
|
@@ -2286,13 +2940,7 @@ export class WebInspectorElement extends LitElement {
|
|
|
2286
2940
|
});
|
|
2287
2941
|
}
|
|
2288
2942
|
|
|
2289
|
-
private renderToolCard(tool: {
|
|
2290
|
-
agentId: string;
|
|
2291
|
-
name: string;
|
|
2292
|
-
description?: string;
|
|
2293
|
-
parameters?: unknown;
|
|
2294
|
-
type: 'handler' | 'renderer';
|
|
2295
|
-
}) {
|
|
2943
|
+
private renderToolCard(tool: InspectorToolDefinition) {
|
|
2296
2944
|
const isExpanded = this.expandedTools.has(`${tool.agentId}:${tool.name}`);
|
|
2297
2945
|
const schema = this.extractSchemaInfo(tool.parameters);
|
|
2298
2946
|
|
|
@@ -2423,18 +3071,27 @@ export class WebInspectorElement extends LitElement {
|
|
|
2423
3071
|
}
|
|
2424
3072
|
|
|
2425
3073
|
// Try Zod schema introspection
|
|
2426
|
-
const zodDef = (parameters as
|
|
2427
|
-
if (zodDef) {
|
|
3074
|
+
const zodDef = (parameters as { _def?: Record<string, unknown> })._def;
|
|
3075
|
+
if (zodDef && typeof zodDef === "object") {
|
|
2428
3076
|
// Handle Zod object schema
|
|
2429
3077
|
if (zodDef.typeName === 'ZodObject') {
|
|
2430
|
-
const
|
|
3078
|
+
const rawShape = zodDef.shape;
|
|
3079
|
+
const shape =
|
|
3080
|
+
typeof rawShape === "function"
|
|
3081
|
+
? (rawShape as () => Record<string, unknown>)()
|
|
3082
|
+
: (rawShape as Record<string, unknown> | undefined);
|
|
3083
|
+
|
|
3084
|
+
if (!shape || typeof shape !== "object") {
|
|
3085
|
+
return result;
|
|
3086
|
+
}
|
|
2431
3087
|
const requiredKeys = new Set<string>();
|
|
2432
3088
|
|
|
2433
3089
|
// Get required fields
|
|
2434
3090
|
if (zodDef.unknownKeys === 'strict' || !zodDef.catchall) {
|
|
2435
|
-
Object.keys(shape || {}).forEach(key => {
|
|
2436
|
-
const
|
|
2437
|
-
|
|
3091
|
+
Object.keys(shape || {}).forEach((key) => {
|
|
3092
|
+
const candidate = (shape as Record<string, unknown>)[key];
|
|
3093
|
+
const fieldDef = (candidate as { _def?: Record<string, unknown> } | undefined)?._def;
|
|
3094
|
+
if (fieldDef && !this.isZodOptional(candidate)) {
|
|
2438
3095
|
requiredKeys.add(key);
|
|
2439
3096
|
}
|
|
2440
3097
|
});
|
|
@@ -2453,20 +3110,27 @@ export class WebInspectorElement extends LitElement {
|
|
|
2453
3110
|
});
|
|
2454
3111
|
}
|
|
2455
3112
|
}
|
|
2456
|
-
} else if (
|
|
3113
|
+
} else if (
|
|
3114
|
+
(parameters as { type?: string; properties?: Record<string, unknown> }).type === 'object' &&
|
|
3115
|
+
(parameters as { properties?: Record<string, unknown> }).properties
|
|
3116
|
+
) {
|
|
2457
3117
|
// Handle JSON Schema format
|
|
2458
|
-
const props = (parameters as
|
|
2459
|
-
const required = new Set(
|
|
3118
|
+
const props = (parameters as { properties?: Record<string, unknown> }).properties;
|
|
3119
|
+
const required = new Set(
|
|
3120
|
+
Array.isArray((parameters as { required?: string[] }).required)
|
|
3121
|
+
? (parameters as { required?: string[] }).required
|
|
3122
|
+
: [],
|
|
3123
|
+
);
|
|
2460
3124
|
|
|
2461
|
-
for (const [key, value] of Object.entries(props)) {
|
|
2462
|
-
const prop = value as
|
|
3125
|
+
for (const [key, value] of Object.entries(props ?? {})) {
|
|
3126
|
+
const prop = value as Record<string, unknown>;
|
|
2463
3127
|
result.properties.push({
|
|
2464
3128
|
name: key,
|
|
2465
|
-
type: prop.type,
|
|
2466
|
-
description: prop.description,
|
|
3129
|
+
type: prop.type as string | undefined,
|
|
3130
|
+
description: typeof prop.description === "string" ? prop.description : undefined,
|
|
2467
3131
|
required: required.has(key),
|
|
2468
3132
|
defaultValue: prop.default,
|
|
2469
|
-
enum: prop.enum,
|
|
3133
|
+
enum: Array.isArray(prop.enum) ? prop.enum : undefined,
|
|
2470
3134
|
});
|
|
2471
3135
|
}
|
|
2472
3136
|
}
|
|
@@ -2474,10 +3138,11 @@ export class WebInspectorElement extends LitElement {
|
|
|
2474
3138
|
return result;
|
|
2475
3139
|
}
|
|
2476
3140
|
|
|
2477
|
-
private isZodOptional(zodSchema:
|
|
2478
|
-
|
|
3141
|
+
private isZodOptional(zodSchema: unknown): boolean {
|
|
3142
|
+
const schema = zodSchema as { _def?: Record<string, unknown> };
|
|
3143
|
+
if (!schema?._def) return false;
|
|
2479
3144
|
|
|
2480
|
-
const def =
|
|
3145
|
+
const def = schema._def;
|
|
2481
3146
|
|
|
2482
3147
|
// Check if it's explicitly optional or nullable
|
|
2483
3148
|
if (def.typeName === 'ZodOptional' || def.typeName === 'ZodNullable') {
|
|
@@ -2492,7 +3157,7 @@ export class WebInspectorElement extends LitElement {
|
|
|
2492
3157
|
return false;
|
|
2493
3158
|
}
|
|
2494
3159
|
|
|
2495
|
-
private extractZodFieldInfo(zodSchema:
|
|
3160
|
+
private extractZodFieldInfo(zodSchema: unknown): {
|
|
2496
3161
|
type?: string;
|
|
2497
3162
|
description?: string;
|
|
2498
3163
|
defaultValue?: unknown;
|
|
@@ -2505,23 +3170,26 @@ export class WebInspectorElement extends LitElement {
|
|
|
2505
3170
|
enum?: unknown[];
|
|
2506
3171
|
} = {};
|
|
2507
3172
|
|
|
2508
|
-
|
|
3173
|
+
const schema = zodSchema as { _def?: Record<string, unknown> };
|
|
3174
|
+
if (!schema?._def) return info;
|
|
2509
3175
|
|
|
2510
|
-
let currentSchema =
|
|
2511
|
-
let def = currentSchema._def
|
|
3176
|
+
let currentSchema = schema as { _def?: Record<string, unknown> };
|
|
3177
|
+
let def = currentSchema._def as Record<string, unknown>;
|
|
2512
3178
|
|
|
2513
3179
|
// Unwrap optional/nullable
|
|
2514
3180
|
while (def.typeName === 'ZodOptional' || def.typeName === 'ZodNullable' || def.typeName === 'ZodDefault') {
|
|
2515
3181
|
if (def.typeName === 'ZodDefault' && def.defaultValue !== undefined) {
|
|
2516
3182
|
info.defaultValue = typeof def.defaultValue === 'function' ? def.defaultValue() : def.defaultValue;
|
|
2517
3183
|
}
|
|
2518
|
-
currentSchema = def.innerType;
|
|
3184
|
+
currentSchema = (def.innerType as { _def?: Record<string, unknown> }) ?? currentSchema;
|
|
2519
3185
|
if (!currentSchema?._def) break;
|
|
2520
|
-
def = currentSchema._def
|
|
3186
|
+
def = currentSchema._def as Record<string, unknown>;
|
|
2521
3187
|
}
|
|
2522
3188
|
|
|
2523
3189
|
// Extract description
|
|
2524
|
-
info.description = def.description;
|
|
3190
|
+
info.description = typeof def.description === "string" ? def.description : undefined;
|
|
3191
|
+
|
|
3192
|
+
const typeName = typeof def.typeName === "string" ? def.typeName : undefined;
|
|
2525
3193
|
|
|
2526
3194
|
// Extract type
|
|
2527
3195
|
const typeMap: Record<string, string> = {
|
|
@@ -2536,12 +3204,12 @@ export class WebInspectorElement extends LitElement {
|
|
|
2536
3204
|
ZodAny: 'any',
|
|
2537
3205
|
ZodUnknown: 'unknown',
|
|
2538
3206
|
};
|
|
2539
|
-
info.type = typeMap[
|
|
3207
|
+
info.type = typeName ? typeMap[typeName] || typeName.replace('Zod', '').toLowerCase() : undefined;
|
|
2540
3208
|
|
|
2541
3209
|
// Extract enum values
|
|
2542
|
-
if (
|
|
2543
|
-
info.enum = def.values;
|
|
2544
|
-
} else if (
|
|
3210
|
+
if (typeName === 'ZodEnum' && Array.isArray(def.values)) {
|
|
3211
|
+
info.enum = def.values as unknown[];
|
|
3212
|
+
} else if (typeName === 'ZodLiteral' && def.value !== undefined) {
|
|
2545
3213
|
info.enum = [def.value];
|
|
2546
3214
|
}
|
|
2547
3215
|
|
|
@@ -2589,6 +3257,7 @@ export class WebInspectorElement extends LitElement {
|
|
|
2589
3257
|
const isExpanded = this.expandedContextItems.has(id);
|
|
2590
3258
|
const valuePreview = this.getContextValuePreview(context.value);
|
|
2591
3259
|
const hasValue = context.value !== undefined && context.value !== null;
|
|
3260
|
+
const title = context.description?.trim() || id;
|
|
2592
3261
|
|
|
2593
3262
|
return html`
|
|
2594
3263
|
<div class="rounded-lg border border-gray-200 bg-white overflow-hidden">
|
|
@@ -2599,11 +3268,9 @@ export class WebInspectorElement extends LitElement {
|
|
|
2599
3268
|
>
|
|
2600
3269
|
<div class="flex items-start justify-between gap-3">
|
|
2601
3270
|
<div class="flex-1 min-w-0">
|
|
2602
|
-
|
|
2603
|
-
? html`<p class="text-sm font-medium text-gray-900 mb-1">${context.description}</p>`
|
|
2604
|
-
: html`<p class="text-sm font-medium text-gray-500 italic mb-1">No description</p>`}
|
|
3271
|
+
<p class="text-sm font-medium text-gray-900 mb-1">${title}</p>
|
|
2605
3272
|
<div class="flex items-center gap-2 text-xs text-gray-500">
|
|
2606
|
-
<span class="font-mono">${id
|
|
3273
|
+
<span class="font-mono truncate inline-block align-middle" style="max-width: 180px;">${id}</span>
|
|
2607
3274
|
${hasValue
|
|
2608
3275
|
? html`
|
|
2609
3276
|
<span class="text-gray-300">•</span>
|
|
@@ -2627,7 +3294,19 @@ export class WebInspectorElement extends LitElement {
|
|
|
2627
3294
|
</div>
|
|
2628
3295
|
${hasValue
|
|
2629
3296
|
? html`
|
|
2630
|
-
<
|
|
3297
|
+
<div class="mb-2 flex items-center justify-between gap-2">
|
|
3298
|
+
<h5 class="text-xs font-semibold text-gray-700">Value</h5>
|
|
3299
|
+
<button
|
|
3300
|
+
class="flex items-center gap-1 rounded-md border border-gray-200 bg-white px-2 py-1 text-[10px] font-medium text-gray-700 transition hover:bg-gray-50"
|
|
3301
|
+
type="button"
|
|
3302
|
+
@click=${(e: Event) => {
|
|
3303
|
+
e.stopPropagation();
|
|
3304
|
+
void this.copyContextValue(context.value, id);
|
|
3305
|
+
}}
|
|
3306
|
+
>
|
|
3307
|
+
${this.copiedContextItems.has(id) ? "Copied" : "Copy JSON"}
|
|
3308
|
+
</button>
|
|
3309
|
+
</div>
|
|
2631
3310
|
<div class="rounded-md border border-gray-200 bg-white p-3">
|
|
2632
3311
|
<pre class="overflow-auto text-xs text-gray-800 max-h-96"><code>${this.formatContextValue(context.value)}</code></pre>
|
|
2633
3312
|
</div>
|
|
@@ -2688,11 +3367,31 @@ export class WebInspectorElement extends LitElement {
|
|
|
2688
3367
|
|
|
2689
3368
|
try {
|
|
2690
3369
|
return JSON.stringify(value, null, 2);
|
|
2691
|
-
} catch
|
|
3370
|
+
} catch {
|
|
2692
3371
|
return String(value);
|
|
2693
3372
|
}
|
|
2694
3373
|
}
|
|
2695
3374
|
|
|
3375
|
+
private async copyContextValue(value: unknown, contextId: string): Promise<void> {
|
|
3376
|
+
if (typeof navigator === "undefined" || !navigator.clipboard?.writeText) {
|
|
3377
|
+
console.warn("Clipboard API is not available in this environment.");
|
|
3378
|
+
return;
|
|
3379
|
+
}
|
|
3380
|
+
|
|
3381
|
+
const serialized = this.formatContextValue(value);
|
|
3382
|
+
try {
|
|
3383
|
+
await navigator.clipboard.writeText(serialized);
|
|
3384
|
+
this.copiedContextItems.add(contextId);
|
|
3385
|
+
this.requestUpdate();
|
|
3386
|
+
setTimeout(() => {
|
|
3387
|
+
this.copiedContextItems.delete(contextId);
|
|
3388
|
+
this.requestUpdate();
|
|
3389
|
+
}, 1500);
|
|
3390
|
+
} catch (error) {
|
|
3391
|
+
console.error("Failed to copy context value:", error);
|
|
3392
|
+
}
|
|
3393
|
+
}
|
|
3394
|
+
|
|
2696
3395
|
private toggleContextExpansion(contextId: string): void {
|
|
2697
3396
|
if (this.expandedContextItems.has(contextId)) {
|
|
2698
3397
|
this.expandedContextItems.delete(contextId);
|
|
@@ -2731,6 +3430,218 @@ export class WebInspectorElement extends LitElement {
|
|
|
2731
3430
|
}
|
|
2732
3431
|
this.requestUpdate();
|
|
2733
3432
|
}
|
|
3433
|
+
|
|
3434
|
+
private renderAnnouncementPanel() {
|
|
3435
|
+
if (!this.isOpen) {
|
|
3436
|
+
return nothing;
|
|
3437
|
+
}
|
|
3438
|
+
|
|
3439
|
+
// Ensure loading is triggered even if we mounted in an already-open state
|
|
3440
|
+
this.ensureAnnouncementLoading();
|
|
3441
|
+
|
|
3442
|
+
if (!this.hasUnseenAnnouncement) {
|
|
3443
|
+
return nothing;
|
|
3444
|
+
}
|
|
3445
|
+
|
|
3446
|
+
if (!this.announcementLoaded && !this.announcementMarkdown) {
|
|
3447
|
+
return html`<div class="mx-4 my-3 rounded-xl border border-slate-200 bg-white px-4 py-3 text-sm text-slate-800 shadow-[0_12px_30px_rgba(15,23,42,0.12)]">
|
|
3448
|
+
<div class="flex items-center gap-2 font-semibold">
|
|
3449
|
+
<span class="inline-flex h-6 w-6 items-center justify-center rounded-md bg-slate-900 text-white shadow-sm">
|
|
3450
|
+
${this.renderIcon("Megaphone")}
|
|
3451
|
+
</span>
|
|
3452
|
+
<span>Loading latest announcement…</span>
|
|
3453
|
+
</div>
|
|
3454
|
+
</div>`;
|
|
3455
|
+
}
|
|
3456
|
+
|
|
3457
|
+
if (this.announcementLoadError) {
|
|
3458
|
+
return html`<div class="mx-4 my-3 rounded-xl border border-rose-200 bg-rose-50 px-4 py-3 text-sm text-rose-900 shadow-[0_12px_30px_rgba(15,23,42,0.12)]">
|
|
3459
|
+
<div class="flex items-center gap-2 font-semibold">
|
|
3460
|
+
<span class="inline-flex h-6 w-6 items-center justify-center rounded-md bg-rose-600 text-white shadow-sm">
|
|
3461
|
+
${this.renderIcon("Megaphone")}
|
|
3462
|
+
</span>
|
|
3463
|
+
<span>Announcement unavailable</span>
|
|
3464
|
+
</div>
|
|
3465
|
+
<p class="mt-2 text-xs text-rose-800">We couldn’t load the latest notice. Please try opening the inspector again.</p>
|
|
3466
|
+
</div>`;
|
|
3467
|
+
}
|
|
3468
|
+
|
|
3469
|
+
if (!this.announcementMarkdown) {
|
|
3470
|
+
return nothing;
|
|
3471
|
+
}
|
|
3472
|
+
|
|
3473
|
+
const content = this.announcementHtml
|
|
3474
|
+
? unsafeHTML(this.announcementHtml)
|
|
3475
|
+
: html`<pre class="whitespace-pre-wrap text-sm text-gray-900">${this.announcementMarkdown}</pre>`;
|
|
3476
|
+
|
|
3477
|
+
return html`<div class="mx-4 my-3 rounded-xl border border-slate-200 bg-white px-4 py-4 shadow-[0_12px_30px_rgba(15,23,42,0.12)]">
|
|
3478
|
+
<div class="mb-3 flex items-center gap-2 text-sm font-semibold text-slate-900">
|
|
3479
|
+
<span class="inline-flex h-7 w-7 items-center justify-center rounded-md bg-slate-900 text-white shadow-sm">
|
|
3480
|
+
${this.renderIcon("Megaphone")}
|
|
3481
|
+
</span>
|
|
3482
|
+
<span>Announcement</span>
|
|
3483
|
+
<button class="announcement-dismiss ml-auto" type="button" @click=${this.handleDismissAnnouncement} aria-label="Dismiss announcement">
|
|
3484
|
+
Dismiss
|
|
3485
|
+
</button>
|
|
3486
|
+
</div>
|
|
3487
|
+
<div class="announcement-content text-sm leading-relaxed text-gray-900">${content}</div>
|
|
3488
|
+
</div>`;
|
|
3489
|
+
}
|
|
3490
|
+
|
|
3491
|
+
private ensureAnnouncementLoading(): void {
|
|
3492
|
+
if (this.announcementPromise || typeof window === "undefined" || typeof fetch === "undefined") {
|
|
3493
|
+
return;
|
|
3494
|
+
}
|
|
3495
|
+
this.announcementPromise = this.fetchAnnouncement();
|
|
3496
|
+
}
|
|
3497
|
+
|
|
3498
|
+
private renderAnnouncementPreview() {
|
|
3499
|
+
if (!this.hasUnseenAnnouncement || !this.showAnnouncementPreview || !this.announcementPreviewText) {
|
|
3500
|
+
return nothing;
|
|
3501
|
+
}
|
|
3502
|
+
|
|
3503
|
+
const side = this.contextState.button.anchor.horizontal === "left" ? "right" : "left";
|
|
3504
|
+
|
|
3505
|
+
return html`<div
|
|
3506
|
+
class="announcement-preview"
|
|
3507
|
+
data-side=${side}
|
|
3508
|
+
role="note"
|
|
3509
|
+
@click=${() => this.handleAnnouncementPreviewClick()}
|
|
3510
|
+
>
|
|
3511
|
+
<span>${this.announcementPreviewText}</span>
|
|
3512
|
+
<span class="announcement-preview__arrow"></span>
|
|
3513
|
+
</div>`;
|
|
3514
|
+
}
|
|
3515
|
+
|
|
3516
|
+
private handleAnnouncementPreviewClick(): void {
|
|
3517
|
+
this.showAnnouncementPreview = false;
|
|
3518
|
+
this.openInspector();
|
|
3519
|
+
}
|
|
3520
|
+
|
|
3521
|
+
private handleDismissAnnouncement = (): void => {
|
|
3522
|
+
this.markAnnouncementSeen();
|
|
3523
|
+
};
|
|
3524
|
+
|
|
3525
|
+
private async fetchAnnouncement(): Promise<void> {
|
|
3526
|
+
try {
|
|
3527
|
+
const response = await fetch(ANNOUNCEMENT_URL, { cache: "no-cache" });
|
|
3528
|
+
if (!response.ok) {
|
|
3529
|
+
throw new Error(`Failed to load announcement (${response.status})`);
|
|
3530
|
+
}
|
|
3531
|
+
|
|
3532
|
+
const data = (await response.json()) as {
|
|
3533
|
+
timestamp?: unknown;
|
|
3534
|
+
previewText?: unknown;
|
|
3535
|
+
announcement?: unknown;
|
|
3536
|
+
};
|
|
3537
|
+
|
|
3538
|
+
const timestamp = typeof data?.timestamp === "string" ? data.timestamp : null;
|
|
3539
|
+
const previewText = typeof data?.previewText === "string" ? data.previewText : null;
|
|
3540
|
+
const markdown = typeof data?.announcement === "string" ? data.announcement : null;
|
|
3541
|
+
|
|
3542
|
+
if (!timestamp || !markdown) {
|
|
3543
|
+
throw new Error("Malformed announcement payload");
|
|
3544
|
+
}
|
|
3545
|
+
|
|
3546
|
+
const storedTimestamp = this.loadStoredAnnouncementTimestamp();
|
|
3547
|
+
|
|
3548
|
+
this.announcementTimestamp = timestamp;
|
|
3549
|
+
this.announcementPreviewText = previewText ?? "";
|
|
3550
|
+
this.announcementMarkdown = markdown;
|
|
3551
|
+
this.hasUnseenAnnouncement = (!storedTimestamp || storedTimestamp !== timestamp) && !!this.announcementPreviewText;
|
|
3552
|
+
this.showAnnouncementPreview = this.hasUnseenAnnouncement;
|
|
3553
|
+
this.announcementHtml = await this.convertMarkdownToHtml(markdown);
|
|
3554
|
+
this.announcementLoaded = true;
|
|
3555
|
+
|
|
3556
|
+
this.requestUpdate();
|
|
3557
|
+
} catch (error) {
|
|
3558
|
+
this.announcementLoadError = error;
|
|
3559
|
+
this.announcementLoaded = true;
|
|
3560
|
+
this.requestUpdate();
|
|
3561
|
+
}
|
|
3562
|
+
}
|
|
3563
|
+
|
|
3564
|
+
private async convertMarkdownToHtml(markdown: string): Promise<string | null> {
|
|
3565
|
+
const renderer = new marked.Renderer();
|
|
3566
|
+
renderer.link = (href, title, text) => {
|
|
3567
|
+
const safeHref = this.escapeHtmlAttr(this.appendRefParam(href ?? ""));
|
|
3568
|
+
const titleAttr = title ? ` title="${this.escapeHtmlAttr(title)}"` : "";
|
|
3569
|
+
return `<a href="${safeHref}" target="_blank" rel="noopener"${titleAttr}>${text}</a>`;
|
|
3570
|
+
};
|
|
3571
|
+
return marked.parse(markdown, { renderer });
|
|
3572
|
+
}
|
|
3573
|
+
|
|
3574
|
+
private appendRefParam(href: string): string {
|
|
3575
|
+
try {
|
|
3576
|
+
const url = new URL(href, typeof window !== "undefined" ? window.location.href : "https://copilotkit.ai");
|
|
3577
|
+
if (!url.searchParams.has("ref")) {
|
|
3578
|
+
url.searchParams.append("ref", "cpk-inspector");
|
|
3579
|
+
}
|
|
3580
|
+
return url.toString();
|
|
3581
|
+
} catch {
|
|
3582
|
+
return href;
|
|
3583
|
+
}
|
|
3584
|
+
}
|
|
3585
|
+
|
|
3586
|
+
private escapeHtmlAttr(value: string): string {
|
|
3587
|
+
return value
|
|
3588
|
+
.replace(/&/g, "&")
|
|
3589
|
+
.replace(/</g, "<")
|
|
3590
|
+
.replace(/>/g, ">")
|
|
3591
|
+
.replace(/\"/g, """)
|
|
3592
|
+
.replace(/'/g, "'");
|
|
3593
|
+
}
|
|
3594
|
+
|
|
3595
|
+
private loadStoredAnnouncementTimestamp(): string | null {
|
|
3596
|
+
if (typeof window === "undefined" || !window.localStorage) {
|
|
3597
|
+
return null;
|
|
3598
|
+
}
|
|
3599
|
+
try {
|
|
3600
|
+
const raw = window.localStorage.getItem(ANNOUNCEMENT_STORAGE_KEY);
|
|
3601
|
+
if (!raw) {
|
|
3602
|
+
return null;
|
|
3603
|
+
}
|
|
3604
|
+
const parsed = JSON.parse(raw);
|
|
3605
|
+
if (parsed && typeof parsed.timestamp === "string") {
|
|
3606
|
+
return parsed.timestamp;
|
|
3607
|
+
}
|
|
3608
|
+
// Backward compatibility: previous shape { hash }
|
|
3609
|
+
return null;
|
|
3610
|
+
} catch {
|
|
3611
|
+
// ignore malformed storage
|
|
3612
|
+
}
|
|
3613
|
+
return null;
|
|
3614
|
+
}
|
|
3615
|
+
|
|
3616
|
+
private persistAnnouncementTimestamp(timestamp: string): void {
|
|
3617
|
+
if (typeof window === "undefined" || !window.localStorage) {
|
|
3618
|
+
return;
|
|
3619
|
+
}
|
|
3620
|
+
try {
|
|
3621
|
+
const payload = JSON.stringify({ timestamp });
|
|
3622
|
+
window.localStorage.setItem(ANNOUNCEMENT_STORAGE_KEY, payload);
|
|
3623
|
+
} catch {
|
|
3624
|
+
// Non-fatal if storage is unavailable
|
|
3625
|
+
}
|
|
3626
|
+
}
|
|
3627
|
+
|
|
3628
|
+
private markAnnouncementSeen(): void {
|
|
3629
|
+
// Clear badge only when explicitly dismissed
|
|
3630
|
+
this.hasUnseenAnnouncement = false;
|
|
3631
|
+
this.showAnnouncementPreview = false;
|
|
3632
|
+
|
|
3633
|
+
if (!this.announcementTimestamp) {
|
|
3634
|
+
// If still loading, attempt once more after promise resolves; avoid infinite requeues
|
|
3635
|
+
if (this.announcementPromise && !this.announcementLoaded) {
|
|
3636
|
+
void this.announcementPromise.then(() => this.markAnnouncementSeen()).catch(() => undefined);
|
|
3637
|
+
}
|
|
3638
|
+
this.requestUpdate();
|
|
3639
|
+
return;
|
|
3640
|
+
}
|
|
3641
|
+
|
|
3642
|
+
this.persistAnnouncementTimestamp(this.announcementTimestamp);
|
|
3643
|
+
this.requestUpdate();
|
|
3644
|
+
}
|
|
2734
3645
|
}
|
|
2735
3646
|
|
|
2736
3647
|
export function defineWebInspector(): void {
|
|
@@ -2743,6 +3654,6 @@ defineWebInspector();
|
|
|
2743
3654
|
|
|
2744
3655
|
declare global {
|
|
2745
3656
|
interface HTMLElementTagNameMap {
|
|
2746
|
-
"web-inspector": WebInspectorElement;
|
|
3657
|
+
"cpk-web-inspector": WebInspectorElement;
|
|
2747
3658
|
}
|
|
2748
3659
|
}
|