@copilotkit/web-inspector 1.56.4 → 1.56.5-canary.1777671752
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/dist/index.cjs +2968 -197
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +273 -10
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +273 -10
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2969 -199
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +3053 -259
- package/dist/index.umd.js.map +1 -1
- package/dist/styles/generated.cjs +1 -1
- package/dist/styles/generated.cjs.map +1 -1
- package/dist/styles/generated.mjs +1 -1
- package/dist/styles/generated.mjs.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/web-inspector.spec.ts +179 -1
- package/src/index.ts +3681 -277
- package/src/styles/generated.css +1 -1
package/src/index.ts
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
import { LitElement, css, html, nothing, unsafeCSS } from "lit";
|
|
2
|
+
import type { TemplateResult } from "lit";
|
|
3
|
+
import { marked } from "marked";
|
|
2
4
|
import { styleMap } from "lit/directives/style-map.js";
|
|
3
5
|
import tailwindStyles from "./styles/generated.css";
|
|
4
6
|
import inspectorLogoUrl from "./assets/inspector-logo.svg";
|
|
5
7
|
import inspectorLogoIconUrl from "./assets/inspector-logo-icon.svg";
|
|
6
8
|
import { unsafeHTML } from "lit/directives/unsafe-html.js";
|
|
7
|
-
import { marked } from "marked";
|
|
8
9
|
import { icons } from "lucide";
|
|
10
|
+
import type { CopilotKitCore } from "@copilotkit/core";
|
|
9
11
|
import {
|
|
10
|
-
CopilotKitCore,
|
|
11
12
|
CopilotKitCoreRuntimeConnectionStatus,
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
ɵselectThreads,
|
|
14
|
+
ɵselectThreadsError,
|
|
15
|
+
ɵcreateThreadStore,
|
|
16
|
+
} from "@copilotkit/core";
|
|
17
|
+
import type {
|
|
18
|
+
CopilotKitCoreSubscriber,
|
|
19
|
+
CopilotKitCoreErrorCode,
|
|
20
|
+
ɵThreadStore,
|
|
21
|
+
ɵThread,
|
|
14
22
|
} from "@copilotkit/core";
|
|
15
23
|
import type { AbstractAgent, AgentSubscriber } from "@ag-ui/client";
|
|
16
24
|
import type {
|
|
@@ -33,18 +41,23 @@ import {
|
|
|
33
41
|
import {
|
|
34
42
|
loadInspectorState,
|
|
35
43
|
saveInspectorState,
|
|
36
|
-
type PersistedState,
|
|
37
44
|
isValidAnchor,
|
|
38
45
|
isValidPosition,
|
|
39
46
|
isValidSize,
|
|
40
47
|
isValidDockMode,
|
|
41
48
|
} from "./lib/persistence";
|
|
49
|
+
import type { PersistedState } from "./lib/persistence";
|
|
42
50
|
|
|
43
51
|
export const WEB_INSPECTOR_TAG = "cpk-web-inspector" as const;
|
|
44
52
|
|
|
45
53
|
type LucideIconName = keyof typeof icons;
|
|
46
54
|
|
|
47
|
-
type MenuKey =
|
|
55
|
+
type MenuKey =
|
|
56
|
+
| "ag-ui-events"
|
|
57
|
+
| "agents"
|
|
58
|
+
| "frontend-tools"
|
|
59
|
+
| "agent-context"
|
|
60
|
+
| "threads";
|
|
48
61
|
|
|
49
62
|
type MenuItem = {
|
|
50
63
|
key: MenuKey;
|
|
@@ -61,7 +74,7 @@ const INSPECTOR_STORAGE_KEY = "cpk:inspector:state";
|
|
|
61
74
|
const ANNOUNCEMENT_STORAGE_KEY = "cpk:inspector:announcements";
|
|
62
75
|
const ANNOUNCEMENT_URL = "https://cdn.copilotkit.ai/announcements.json";
|
|
63
76
|
const DEFAULT_BUTTON_SIZE: Size = { width: 48, height: 48 };
|
|
64
|
-
const DEFAULT_WINDOW_SIZE: Size = { width: 840, height:
|
|
77
|
+
const DEFAULT_WINDOW_SIZE: Size = { width: 840, height: 700 };
|
|
65
78
|
const DOCKED_LEFT_WIDTH = 500; // Sensible width for left dock with collapsed sidebar
|
|
66
79
|
const MAX_AGENT_EVENTS = 200;
|
|
67
80
|
const MAX_TOTAL_EVENTS = 500;
|
|
@@ -87,7 +100,9 @@ type InspectorAgentEventType =
|
|
|
87
100
|
| "REASONING_MESSAGE_CONTENT"
|
|
88
101
|
| "REASONING_MESSAGE_END"
|
|
89
102
|
| "REASONING_END"
|
|
90
|
-
| "REASONING_ENCRYPTED_VALUE"
|
|
103
|
+
| "REASONING_ENCRYPTED_VALUE"
|
|
104
|
+
| "ACTIVITY_SNAPSHOT"
|
|
105
|
+
| "ACTIVITY_DELTA";
|
|
91
106
|
|
|
92
107
|
const AGENT_EVENT_TYPES: readonly InspectorAgentEventType[] = [
|
|
93
108
|
"RUN_STARTED",
|
|
@@ -111,49 +126,2206 @@ const AGENT_EVENT_TYPES: readonly InspectorAgentEventType[] = [
|
|
|
111
126
|
"REASONING_MESSAGE_END",
|
|
112
127
|
"REASONING_END",
|
|
113
128
|
"REASONING_ENCRYPTED_VALUE",
|
|
129
|
+
"ACTIVITY_SNAPSHOT",
|
|
130
|
+
"ACTIVITY_DELTA",
|
|
114
131
|
] as const;
|
|
115
132
|
|
|
116
|
-
type SanitizedValue =
|
|
117
|
-
| string
|
|
118
|
-
| number
|
|
119
|
-
| boolean
|
|
120
|
-
| null
|
|
121
|
-
| SanitizedValue[]
|
|
122
|
-
| { [key: string]: SanitizedValue };
|
|
133
|
+
type SanitizedValue =
|
|
134
|
+
| string
|
|
135
|
+
| number
|
|
136
|
+
| boolean
|
|
137
|
+
| null
|
|
138
|
+
| SanitizedValue[]
|
|
139
|
+
| { [key: string]: SanitizedValue };
|
|
140
|
+
|
|
141
|
+
type InspectorToolCall = {
|
|
142
|
+
id?: string;
|
|
143
|
+
function?: {
|
|
144
|
+
name?: string;
|
|
145
|
+
arguments?: SanitizedValue | string;
|
|
146
|
+
};
|
|
147
|
+
toolName?: string;
|
|
148
|
+
status?: string;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
type InspectorMessage = {
|
|
152
|
+
id?: string;
|
|
153
|
+
role: string;
|
|
154
|
+
contentText: string;
|
|
155
|
+
contentRaw?: SanitizedValue;
|
|
156
|
+
toolCalls: InspectorToolCall[];
|
|
157
|
+
/** Populated for role="activity" messages (Generative UI). */
|
|
158
|
+
activityType?: string;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
type InspectorToolDefinition = {
|
|
162
|
+
agentId: string;
|
|
163
|
+
name: string;
|
|
164
|
+
description?: string;
|
|
165
|
+
parameters?: unknown;
|
|
166
|
+
type: "handler" | "renderer";
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
type InspectorEvent = {
|
|
170
|
+
id: string;
|
|
171
|
+
agentId: string;
|
|
172
|
+
type: InspectorAgentEventType;
|
|
173
|
+
timestamp: number;
|
|
174
|
+
payload: SanitizedValue;
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
// ─── Thread details types ────────────────────────────────────────────────────
|
|
178
|
+
|
|
179
|
+
interface ApiThreadMessage {
|
|
180
|
+
id: string;
|
|
181
|
+
role: string;
|
|
182
|
+
content?: string;
|
|
183
|
+
toolCalls?: Array<{ id: string; name: string; args: string }>;
|
|
184
|
+
toolCallId?: string;
|
|
185
|
+
/** Present when role === "activity" (Generative UI output). */
|
|
186
|
+
activityType?: string;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
interface ConversationUser {
|
|
190
|
+
id: string;
|
|
191
|
+
type: "user";
|
|
192
|
+
content: string;
|
|
193
|
+
createdAt: string;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
interface ConversationAssistant {
|
|
197
|
+
id: string;
|
|
198
|
+
type: "assistant";
|
|
199
|
+
content: string;
|
|
200
|
+
createdAt: string;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
interface ConversationToolCall {
|
|
204
|
+
id: string;
|
|
205
|
+
type: "tool_call";
|
|
206
|
+
toolName: string;
|
|
207
|
+
toolCallId: string;
|
|
208
|
+
arguments: Record<string, unknown>;
|
|
209
|
+
result: Record<string, unknown> | null;
|
|
210
|
+
createdAt: string;
|
|
211
|
+
groupId?: string;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
interface ConversationReasoning {
|
|
215
|
+
id: string;
|
|
216
|
+
type: "reasoning";
|
|
217
|
+
duration: string;
|
|
218
|
+
createdAt: string;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
interface ConversationStateUpdate {
|
|
222
|
+
id: string;
|
|
223
|
+
type: "state_update";
|
|
224
|
+
createdAt: string;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
interface ConversationAgentResponded {
|
|
228
|
+
id: string;
|
|
229
|
+
type: "agent_responded";
|
|
230
|
+
createdAt: string;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
interface ConversationGenerativeUIItem {
|
|
234
|
+
id: string;
|
|
235
|
+
type: "generative-ui";
|
|
236
|
+
activityType: string;
|
|
237
|
+
createdAt: string;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
interface ToolCallGroup {
|
|
241
|
+
type: "tool_call_group";
|
|
242
|
+
id: string;
|
|
243
|
+
items: ConversationToolCall[];
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
type ConversationItem =
|
|
247
|
+
| ConversationUser
|
|
248
|
+
| ConversationAssistant
|
|
249
|
+
| ConversationToolCall
|
|
250
|
+
| ConversationReasoning
|
|
251
|
+
| ConversationStateUpdate
|
|
252
|
+
| ConversationAgentResponded
|
|
253
|
+
| ConversationGenerativeUIItem;
|
|
254
|
+
|
|
255
|
+
type RenderItem = ConversationItem | ToolCallGroup;
|
|
256
|
+
|
|
257
|
+
interface ApiAgentEvent {
|
|
258
|
+
type: string;
|
|
259
|
+
timestamp: string | number;
|
|
260
|
+
payload: Record<string, unknown>;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
type ThreadDetailsTab = "conversation" | "agent-state" | "ag-ui-events";
|
|
264
|
+
|
|
265
|
+
// ─── JSON syntax highlighter ─────────────────────────────────────────────────
|
|
266
|
+
// Inline-styled so shadow DOM encapsulation preserves colors when the output
|
|
267
|
+
// is injected via unsafeHTML. Only for structured data — never raw user HTML.
|
|
268
|
+
|
|
269
|
+
function escapeHtml(s: string): string {
|
|
270
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Memoize highlight output by payload reference. Tab switches cause Lit to
|
|
274
|
+
// re-render the active panel from scratch, and the JSON.stringify + regex
|
|
275
|
+
// pass below is by far the most expensive thing in the events / state
|
|
276
|
+
// panels (potentially MB of agent state). Caching by object reference
|
|
277
|
+
// turns subsequent renders of an unchanged event list into near-zero JS work.
|
|
278
|
+
const highlightedJsonCache = new WeakMap<object, string>();
|
|
279
|
+
|
|
280
|
+
function highlightedJson(obj: unknown): string {
|
|
281
|
+
if (typeof obj === "object" && obj !== null) {
|
|
282
|
+
const cached = highlightedJsonCache.get(obj);
|
|
283
|
+
if (cached !== undefined) return cached;
|
|
284
|
+
}
|
|
285
|
+
const colors = {
|
|
286
|
+
key: "#5558B2",
|
|
287
|
+
str: "#189370",
|
|
288
|
+
num: "#996300",
|
|
289
|
+
bool: "#c0333a",
|
|
290
|
+
nil: "#838389",
|
|
291
|
+
};
|
|
292
|
+
const json = JSON.stringify(obj, null, 2);
|
|
293
|
+
if (!json) return "";
|
|
294
|
+
const parts: string[] = [];
|
|
295
|
+
let lastIndex = 0;
|
|
296
|
+
const re =
|
|
297
|
+
/("(?:\\u[a-fA-F0-9]{4}|\\[^u]|[^\\"])*"(?:\s*:)?|\b(?:true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+-]?\d+)?)/g;
|
|
298
|
+
let match: RegExpExecArray | null;
|
|
299
|
+
while ((match = re.exec(json)) !== null) {
|
|
300
|
+
parts.push(escapeHtml(json.slice(lastIndex, match.index)));
|
|
301
|
+
const m = match[0];
|
|
302
|
+
let color = colors.num;
|
|
303
|
+
if (m.startsWith('"')) {
|
|
304
|
+
color = m.trimEnd().endsWith(":") ? colors.key : colors.str;
|
|
305
|
+
} else if (m === "true" || m === "false") {
|
|
306
|
+
color = colors.bool;
|
|
307
|
+
} else if (m === "null") {
|
|
308
|
+
color = colors.nil;
|
|
309
|
+
}
|
|
310
|
+
parts.push(`<span style="color:${color}">${escapeHtml(m)}</span>`);
|
|
311
|
+
lastIndex = match.index + m.length;
|
|
312
|
+
}
|
|
313
|
+
parts.push(escapeHtml(json.slice(lastIndex)));
|
|
314
|
+
const result = parts.join("");
|
|
315
|
+
if (typeof obj === "object" && obj !== null) {
|
|
316
|
+
highlightedJsonCache.set(obj, result);
|
|
317
|
+
}
|
|
318
|
+
return result;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function eventColors(type: string): { bg: string; fg: string } {
|
|
322
|
+
if (type.startsWith("TEXT_MESSAGE")) return { bg: "#EEE6FE", fg: "#57575B" };
|
|
323
|
+
if (type.startsWith("TOOL_CALL"))
|
|
324
|
+
return { bg: "rgba(133,236,206,0.15)", fg: "#189370" };
|
|
325
|
+
if (type.startsWith("STATE"))
|
|
326
|
+
return { bg: "rgba(190,194,255,0.102)", fg: "#5558B2" };
|
|
327
|
+
if (type.startsWith("RUN_") || type.startsWith("STEP_"))
|
|
328
|
+
return { bg: "rgba(255,172,77,0.2)", fg: "#996300" };
|
|
329
|
+
if (type === "ERROR") return { bg: "rgba(250,95,103,0.13)", fg: "#c0333a" };
|
|
330
|
+
return { bg: "#F7F7F9", fg: "#838389" };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function formatTimestamp(ts: string | number): string {
|
|
334
|
+
const date = typeof ts === "number" ? new Date(ts) : new Date(ts);
|
|
335
|
+
if (Number.isNaN(date.getTime())) return "";
|
|
336
|
+
const ms = date.getMilliseconds().toString().padStart(3, "0");
|
|
337
|
+
return (
|
|
338
|
+
date.toLocaleTimeString("en-US", {
|
|
339
|
+
hour: "2-digit",
|
|
340
|
+
minute: "2-digit",
|
|
341
|
+
second: "2-digit",
|
|
342
|
+
hour12: false,
|
|
343
|
+
}) +
|
|
344
|
+
"." +
|
|
345
|
+
ms
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// ─── cpk-thread-list ────────────────────────────────────────────────────────
|
|
350
|
+
|
|
351
|
+
class CpkThreadList extends LitElement {
|
|
352
|
+
static properties = {
|
|
353
|
+
threads: { attribute: false },
|
|
354
|
+
selectedThreadId: { attribute: false },
|
|
355
|
+
errorMessage: { attribute: false },
|
|
356
|
+
_query: { state: true },
|
|
357
|
+
};
|
|
358
|
+
threads: ɵThread[] = [];
|
|
359
|
+
selectedThreadId: string | null = null;
|
|
360
|
+
/**
|
|
361
|
+
* Non-null when the underlying thread store reported a load error
|
|
362
|
+
* (REST list rejection, Phoenix subscribe failure, retry exhaustion).
|
|
363
|
+
* Surfaced inline so users see a real error state instead of stale or
|
|
364
|
+
* empty data with no indication of what went wrong.
|
|
365
|
+
*/
|
|
366
|
+
errorMessage: string | null = null;
|
|
367
|
+
private _query = "";
|
|
368
|
+
|
|
369
|
+
static styles = css`
|
|
370
|
+
@import url("https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600&family=Spline+Sans+Mono:wght@400;500&display=swap");
|
|
371
|
+
|
|
372
|
+
:host {
|
|
373
|
+
display: flex;
|
|
374
|
+
flex-direction: column;
|
|
375
|
+
height: 100%;
|
|
376
|
+
overflow: hidden;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
.cpk-tl {
|
|
380
|
+
font-family: "Plus Jakarta Sans", sans-serif;
|
|
381
|
+
display: flex;
|
|
382
|
+
flex-direction: column;
|
|
383
|
+
height: 100%;
|
|
384
|
+
overflow: hidden;
|
|
385
|
+
background: #f7f7f9;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/* ── Search ── */
|
|
389
|
+
.cpk-tl__search {
|
|
390
|
+
padding: 10px 12px;
|
|
391
|
+
border-bottom: 1px solid #dbdbe5;
|
|
392
|
+
flex-shrink: 0;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.cpk-tl__search-input {
|
|
396
|
+
width: 100%;
|
|
397
|
+
box-sizing: border-box;
|
|
398
|
+
font-family: "Plus Jakarta Sans", sans-serif;
|
|
399
|
+
font-size: 12px;
|
|
400
|
+
padding: 7px 10px;
|
|
401
|
+
border-radius: 6px;
|
|
402
|
+
border: 1px solid #dbdbe5;
|
|
403
|
+
background: #ffffff;
|
|
404
|
+
color: #010507;
|
|
405
|
+
outline: none;
|
|
406
|
+
transition: border-color 0.15s;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
.cpk-tl__search-input:focus {
|
|
410
|
+
border-color: #bec2ff;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/* ── List ── */
|
|
414
|
+
.cpk-tl__list {
|
|
415
|
+
flex: 1;
|
|
416
|
+
overflow-y: auto;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/* ── Thread item ── */
|
|
420
|
+
.cpk-tl__item {
|
|
421
|
+
padding: 11px 13px;
|
|
422
|
+
cursor: pointer;
|
|
423
|
+
border-bottom: 1px solid #e9e9ef;
|
|
424
|
+
border-left: 3px solid transparent;
|
|
425
|
+
transition: background 0.1s;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
.cpk-tl__item:hover {
|
|
429
|
+
background: #ffffff;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
.cpk-tl__item--active {
|
|
433
|
+
background: #bec2ff1a;
|
|
434
|
+
border-left-color: #bec2ff;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
.cpk-tl__item--active:hover {
|
|
438
|
+
background: #bec2ff33;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
.cpk-tl__row1 {
|
|
442
|
+
display: flex;
|
|
443
|
+
align-items: center;
|
|
444
|
+
gap: 8px;
|
|
445
|
+
margin-bottom: 3px;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
.cpk-tl__name {
|
|
449
|
+
font-size: 12px;
|
|
450
|
+
font-weight: 500;
|
|
451
|
+
color: #010507;
|
|
452
|
+
flex: 1;
|
|
453
|
+
overflow: hidden;
|
|
454
|
+
text-overflow: ellipsis;
|
|
455
|
+
white-space: nowrap;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
.cpk-tl__name--unnamed {
|
|
459
|
+
color: #838389;
|
|
460
|
+
font-style: italic;
|
|
461
|
+
font-weight: 400;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
.cpk-tl__time {
|
|
465
|
+
font-family: "Spline Sans Mono", monospace;
|
|
466
|
+
font-size: 10px;
|
|
467
|
+
color: #838389;
|
|
468
|
+
flex-shrink: 0;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
.cpk-tl__meta {
|
|
472
|
+
display: flex;
|
|
473
|
+
gap: 6px;
|
|
474
|
+
align-items: center;
|
|
475
|
+
flex-wrap: wrap;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
.cpk-tl__pill {
|
|
479
|
+
font-family: "Spline Sans Mono", monospace;
|
|
480
|
+
font-size: 9px;
|
|
481
|
+
padding: 1px 7px;
|
|
482
|
+
border-radius: 4px;
|
|
483
|
+
text-transform: uppercase;
|
|
484
|
+
font-weight: 500;
|
|
485
|
+
white-space: nowrap;
|
|
486
|
+
background: #eee6fe;
|
|
487
|
+
color: #57575b;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/* ── Empty state ── */
|
|
491
|
+
.cpk-tl__empty {
|
|
492
|
+
padding: 32px 16px;
|
|
493
|
+
text-align: center;
|
|
494
|
+
color: #838389;
|
|
495
|
+
font-size: 12px;
|
|
496
|
+
display: flex;
|
|
497
|
+
flex-direction: column;
|
|
498
|
+
align-items: center;
|
|
499
|
+
gap: 8px;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
.cpk-tl__empty-icon {
|
|
503
|
+
color: #c0c0c8;
|
|
504
|
+
}
|
|
505
|
+
`;
|
|
506
|
+
|
|
507
|
+
private relativeTime(dateStr: string): string {
|
|
508
|
+
const date = new Date(dateStr);
|
|
509
|
+
const diffMs = Date.now() - date.getTime();
|
|
510
|
+
const diffSec = Math.floor(diffMs / 1000);
|
|
511
|
+
if (diffSec < 60) return `${diffSec}s ago`;
|
|
512
|
+
const diffMin = Math.floor(diffSec / 60);
|
|
513
|
+
if (diffMin < 60) return `${diffMin}m ago`;
|
|
514
|
+
const diffH = Math.floor(diffMin / 60);
|
|
515
|
+
if (diffH < 24) return `${diffH}h ago`;
|
|
516
|
+
const diffD = Math.floor(diffH / 24);
|
|
517
|
+
return `${diffD}d ago`;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
private get filtered(): ɵThread[] {
|
|
521
|
+
const q = this._query.toLowerCase();
|
|
522
|
+
if (!q) return this.threads;
|
|
523
|
+
return this.threads.filter(
|
|
524
|
+
(t) =>
|
|
525
|
+
(t.name?.toLowerCase().includes(q) ?? false) ||
|
|
526
|
+
t.agentId.toLowerCase().includes(q) ||
|
|
527
|
+
t.id.toLowerCase().includes(q),
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
private onThreadClick(threadId: string): void {
|
|
532
|
+
this.dispatchEvent(
|
|
533
|
+
new CustomEvent("threadSelected", {
|
|
534
|
+
detail: threadId,
|
|
535
|
+
bubbles: true,
|
|
536
|
+
composed: true,
|
|
537
|
+
}),
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
private onSearchInput = (event: Event): void => {
|
|
542
|
+
this._query = (event.target as HTMLInputElement).value;
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
render() {
|
|
546
|
+
const filtered = this.filtered;
|
|
547
|
+
return html`
|
|
548
|
+
<div class="cpk-tl">
|
|
549
|
+
<!-- Search -->
|
|
550
|
+
<div class="cpk-tl__search">
|
|
551
|
+
<input
|
|
552
|
+
type="text"
|
|
553
|
+
placeholder="Search threads…"
|
|
554
|
+
.value=${this._query}
|
|
555
|
+
@input=${this.onSearchInput}
|
|
556
|
+
class="cpk-tl__search-input"
|
|
557
|
+
/>
|
|
558
|
+
</div>
|
|
559
|
+
|
|
560
|
+
<!-- Thread list -->
|
|
561
|
+
<div class="cpk-tl__list">
|
|
562
|
+
${filtered.map(
|
|
563
|
+
(thread) => html`
|
|
564
|
+
<div
|
|
565
|
+
class="cpk-tl__item ${
|
|
566
|
+
this.selectedThreadId === thread.id
|
|
567
|
+
? "cpk-tl__item--active"
|
|
568
|
+
: ""
|
|
569
|
+
}"
|
|
570
|
+
@click=${() => this.onThreadClick(thread.id)}
|
|
571
|
+
>
|
|
572
|
+
<div class="cpk-tl__row1">
|
|
573
|
+
<span
|
|
574
|
+
class="cpk-tl__name ${
|
|
575
|
+
!thread.name ? "cpk-tl__name--unnamed" : ""
|
|
576
|
+
}"
|
|
577
|
+
>${thread.name ?? "Untitled"}</span
|
|
578
|
+
>
|
|
579
|
+
<span class="cpk-tl__time"
|
|
580
|
+
>${this.relativeTime(thread.updatedAt)}</span
|
|
581
|
+
>
|
|
582
|
+
</div>
|
|
583
|
+
<div class="cpk-tl__meta">
|
|
584
|
+
<span class="cpk-tl__pill">${thread.agentId}</span>
|
|
585
|
+
</div>
|
|
586
|
+
</div>
|
|
587
|
+
`,
|
|
588
|
+
)}
|
|
589
|
+
${
|
|
590
|
+
filtered.length === 0
|
|
591
|
+
? html`
|
|
592
|
+
<div class="cpk-tl__empty">
|
|
593
|
+
${
|
|
594
|
+
this.errorMessage
|
|
595
|
+
? html`
|
|
596
|
+
<svg
|
|
597
|
+
width="24"
|
|
598
|
+
height="24"
|
|
599
|
+
viewBox="0 0 24 24"
|
|
600
|
+
fill="none"
|
|
601
|
+
stroke="currentColor"
|
|
602
|
+
stroke-width="1.5"
|
|
603
|
+
stroke-linecap="round"
|
|
604
|
+
stroke-linejoin="round"
|
|
605
|
+
class="cpk-tl__empty-icon"
|
|
606
|
+
>
|
|
607
|
+
<circle cx="12" cy="12" r="10" />
|
|
608
|
+
<line x1="12" y1="8" x2="12" y2="12" />
|
|
609
|
+
<line x1="12" y1="16" x2="12.01" y2="16" />
|
|
610
|
+
</svg>
|
|
611
|
+
<div>
|
|
612
|
+
Failed to load threads
|
|
613
|
+
<div style="font-size:11px;margin-top:4px;color:#c0333a;">
|
|
614
|
+
${this.errorMessage}
|
|
615
|
+
</div>
|
|
616
|
+
</div>
|
|
617
|
+
`
|
|
618
|
+
: this.threads.length === 0
|
|
619
|
+
? html`
|
|
620
|
+
<svg
|
|
621
|
+
width="24"
|
|
622
|
+
height="24"
|
|
623
|
+
viewBox="0 0 24 24"
|
|
624
|
+
fill="none"
|
|
625
|
+
stroke="currentColor"
|
|
626
|
+
stroke-width="1.5"
|
|
627
|
+
stroke-linecap="round"
|
|
628
|
+
stroke-linejoin="round"
|
|
629
|
+
class="cpk-tl__empty-icon"
|
|
630
|
+
>
|
|
631
|
+
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
|
|
632
|
+
</svg>
|
|
633
|
+
No threads yet
|
|
634
|
+
`
|
|
635
|
+
: html`
|
|
636
|
+
No threads match your search.
|
|
637
|
+
`
|
|
638
|
+
}
|
|
639
|
+
</div>
|
|
640
|
+
`
|
|
641
|
+
: nothing
|
|
642
|
+
}
|
|
643
|
+
</div>
|
|
644
|
+
</div>
|
|
645
|
+
`;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// ─── cpk-thread-details ──────────────────────────────────────────────────────
|
|
650
|
+
// Renders the selected thread's conversation, agent state, and AG-UI events.
|
|
651
|
+
// Conversation comes from the runtime's `/threads/:id/messages` endpoint
|
|
652
|
+
// (always thread-accurate). Agent state and AG-UI events accept live inputs
|
|
653
|
+
// (`agentStateInput`, `agentEventsInput`) from the parent inspector's ongoing
|
|
654
|
+
// agent subscriptions; when those are absent we fall back to the per-thread
|
|
655
|
+
// fetched data via `/threads/:id/{events,state}`.
|
|
656
|
+
|
|
657
|
+
// Exported (with the underscore-prefixed name signalling internal/test-only)
|
|
658
|
+
// so unit tests can pin down the per-panel template-cache invariants without
|
|
659
|
+
// reaching through `customElements`. Production consumers continue to use the
|
|
660
|
+
// `cpk-thread-details` custom element registered below.
|
|
661
|
+
export class ɵCpkThreadDetails extends LitElement {
|
|
662
|
+
static properties = {
|
|
663
|
+
threadId: { attribute: false },
|
|
664
|
+
thread: { attribute: false },
|
|
665
|
+
runtimeUrl: { attribute: false },
|
|
666
|
+
headers: { attribute: false },
|
|
667
|
+
agentStateInput: { attribute: false },
|
|
668
|
+
agentEventsInput: { attribute: false },
|
|
669
|
+
liveMessageVersion: { attribute: false },
|
|
670
|
+
_tab: { state: true },
|
|
671
|
+
_conversation: { state: true },
|
|
672
|
+
_fetchedEvents: { state: true },
|
|
673
|
+
_fetchedState: { state: true },
|
|
674
|
+
_loadingMessages: { state: true },
|
|
675
|
+
_loadingEvents: { state: true },
|
|
676
|
+
_loadingState: { state: true },
|
|
677
|
+
_messagesError: { state: true },
|
|
678
|
+
_eventsError: { state: true },
|
|
679
|
+
_stateError: { state: true },
|
|
680
|
+
_expandedTools: { state: true },
|
|
681
|
+
_expandedMessages: { state: true },
|
|
682
|
+
_showDetailPanel: { state: true },
|
|
683
|
+
_detailPanelWidth: { state: true },
|
|
684
|
+
_eventsNotAvailable: { state: true },
|
|
685
|
+
_stateNotAvailable: { state: true },
|
|
686
|
+
_panelInitializing: { state: true },
|
|
687
|
+
_activatedTabs: { state: true },
|
|
688
|
+
};
|
|
689
|
+
|
|
690
|
+
threadId: string | null = null;
|
|
691
|
+
thread: ɵThread | null = null;
|
|
692
|
+
runtimeUrl = "";
|
|
693
|
+
headers: Record<string, string> = {};
|
|
694
|
+
agentStateInput: Record<string, unknown> | null = null;
|
|
695
|
+
agentEventsInput: ApiAgentEvent[] = [];
|
|
696
|
+
/**
|
|
697
|
+
* Monotonic per-thread counter the parent inspector ticks every time the
|
|
698
|
+
* agent currently running on this thread emits a message change. When this
|
|
699
|
+
* prop changes for the same `threadId`, we re-fetch `/threads/:id/messages`
|
|
700
|
+
* so the conversation view reflects live streaming output.
|
|
701
|
+
*/
|
|
702
|
+
liveMessageVersion = 0;
|
|
703
|
+
|
|
704
|
+
private _tab: ThreadDetailsTab = "conversation";
|
|
705
|
+
private _conversation: ConversationItem[] = [];
|
|
706
|
+
private _fetchedEvents: ApiAgentEvent[] | null = null;
|
|
707
|
+
private _fetchedState: Record<string, unknown> | null = null;
|
|
708
|
+
private _loadingMessages = false;
|
|
709
|
+
private _loadingEvents = false;
|
|
710
|
+
private _loadingState = false;
|
|
711
|
+
private _messagesError: string | null = null;
|
|
712
|
+
private _eventsError: string | null = null;
|
|
713
|
+
private _stateError: string | null = null;
|
|
714
|
+
private _expandedTools = new Set<string>();
|
|
715
|
+
private _expandedMessages = new Set<string>();
|
|
716
|
+
private _showDetailPanel = false;
|
|
717
|
+
private _detailPanelWidth = 250;
|
|
718
|
+
/** True when the /events endpoint returned 501 — don't fall back to live data. */
|
|
719
|
+
private _eventsNotAvailable = false;
|
|
720
|
+
/** True when the /state endpoint returned 501 — don't fall back to live data. */
|
|
721
|
+
private _stateNotAvailable = false;
|
|
722
|
+
/**
|
|
723
|
+
* Briefly true after a tab switch so the active-tab highlight + a generic
|
|
724
|
+
* "Loading…" placeholder paint before the heavy per-tab render runs. Without
|
|
725
|
+
* this, large event/conversation lists block the next paint and the user
|
|
726
|
+
* sees the click as unresponsive for seconds.
|
|
727
|
+
*/
|
|
728
|
+
private _panelInitializing = false;
|
|
729
|
+
/**
|
|
730
|
+
* Tabs that have been opened at least once for the current thread. Once a
|
|
731
|
+
* tab is activated, its rendered DOM stays mounted (we hide inactive tabs
|
|
732
|
+
* via display:none) so flipping back to it is just a CSS swap rather than
|
|
733
|
+
* tearing down and rebuilding the entire panel from scratch. Without this,
|
|
734
|
+
* switching back to AG-UI Events on a thread with hundreds of events
|
|
735
|
+
* triggers a multi-second DOM-creation pass each time.
|
|
736
|
+
*
|
|
737
|
+
* Reset to {"conversation"} when the selected thread changes.
|
|
738
|
+
*/
|
|
739
|
+
private _activatedTabs: Set<ThreadDetailsTab> = new Set(["conversation"]);
|
|
740
|
+
/**
|
|
741
|
+
* Memoized per-panel templates keyed by the inputs they render from.
|
|
742
|
+
* When the underlying data hasn't changed (same `_conversation` /
|
|
743
|
+
* `_fetchedState` / events array reference, plus expand-state for the
|
|
744
|
+
* conversation panel), we return the previously built TemplateResult.
|
|
745
|
+
* Lit then sees "same template, same values" and skips the diff entirely,
|
|
746
|
+
* so re-rendering on tab switch is near-zero work even when the panel
|
|
747
|
+
* content is large. The key is an opaque tuple compared element-wise by
|
|
748
|
+
* reference; if any element flips, the cache misses and rebuilds.
|
|
749
|
+
*/
|
|
750
|
+
private _panelTplCache: Map<
|
|
751
|
+
ThreadDetailsTab,
|
|
752
|
+
{ key: readonly unknown[]; tpl: TemplateResult }
|
|
753
|
+
> = new Map();
|
|
754
|
+
/**
|
|
755
|
+
* Tracks whether we've fetched events for the current thread yet. Events
|
|
756
|
+
* fetch lazily on first sub-tab click so a large response's JSON.parse
|
|
757
|
+
* doesn't block the main thread when the user only ever cares about the
|
|
758
|
+
* conversation.
|
|
759
|
+
*/
|
|
760
|
+
private _eventsFetched = false;
|
|
761
|
+
/**
|
|
762
|
+
* Tracks whether we've fetched state for the current thread yet. Same
|
|
763
|
+
* lazy-load reasoning as `_eventsFetched`.
|
|
764
|
+
*/
|
|
765
|
+
private _stateFetched = false;
|
|
766
|
+
private _lastFetchedThreadId: string | null = null;
|
|
767
|
+
private _lastSeenLiveMessageVersion = 0;
|
|
768
|
+
private _messagesAbort: AbortController | null = null;
|
|
769
|
+
private _eventsAbort: AbortController | null = null;
|
|
770
|
+
private _stateAbort: AbortController | null = null;
|
|
771
|
+
private _dividerResizing = false;
|
|
772
|
+
private _dividerPointerId = -1;
|
|
773
|
+
private _dividerStartX = 0;
|
|
774
|
+
private _dividerStartWidth = 0;
|
|
775
|
+
|
|
776
|
+
static readonly COLLAPSE_THRESHOLD = 800;
|
|
777
|
+
private static readonly TAB_LIST: ReadonlyArray<{
|
|
778
|
+
id: ThreadDetailsTab;
|
|
779
|
+
label: string;
|
|
780
|
+
}> = [
|
|
781
|
+
{ id: "conversation", label: "Conversation" },
|
|
782
|
+
{ id: "agent-state", label: "Agent State" },
|
|
783
|
+
{ id: "ag-ui-events", label: "AG-UI Events" },
|
|
784
|
+
];
|
|
785
|
+
|
|
786
|
+
private renderTabContent(id: ThreadDetailsTab): TemplateResult {
|
|
787
|
+
if (id === "conversation") return this.renderConversation();
|
|
788
|
+
if (id === "agent-state") return this.renderState();
|
|
789
|
+
return this.renderEvents();
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
private activateTab(id: ThreadDetailsTab): void {
|
|
793
|
+
if (this._tab === id) return;
|
|
794
|
+
const isFirstActivation = !this._activatedTabs.has(id);
|
|
795
|
+
this._tab = id;
|
|
796
|
+
if (isFirstActivation) {
|
|
797
|
+
// First time opening this tab: paint a "Loading…" overlay for one
|
|
798
|
+
// frame so the tab highlight + spinner appear before the heavy
|
|
799
|
+
// per-tab render runs (events list, state JSON). The rAF batches
|
|
800
|
+
// mounting the panel into `_activatedTabs` and clearing the spinner
|
|
801
|
+
// into a single subsequent paint. Subsequent activations are pure
|
|
802
|
+
// CSS toggles via display:none on the already-mounted panel — no
|
|
803
|
+
// re-render required.
|
|
804
|
+
this._panelInitializing = true;
|
|
805
|
+
requestAnimationFrame(() => {
|
|
806
|
+
this._activatedTabs = new Set([...this._activatedTabs, id]);
|
|
807
|
+
this._panelInitializing = false;
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
this.maybeFetchTabData(id);
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
private maybeFetchTabData(id: ThreadDetailsTab): void {
|
|
814
|
+
// Lazy-trigger the events / state fetches so their (potentially huge)
|
|
815
|
+
// JSON.parse only blocks the main thread after the user has shown
|
|
816
|
+
// intent to view that sub-tab. Without lazy-load, the eager fetch runs
|
|
817
|
+
// as soon as the thread opens and a single large response can stall
|
|
818
|
+
// the entire panel for seconds — including making the tab buttons
|
|
819
|
+
// themselves feel unresponsive.
|
|
820
|
+
if (!this.threadId) return;
|
|
821
|
+
if (id === "ag-ui-events" && !this._eventsFetched) {
|
|
822
|
+
this._eventsFetched = true;
|
|
823
|
+
void this.fetchEvents(this.threadId);
|
|
824
|
+
} else if (id === "agent-state" && !this._stateFetched) {
|
|
825
|
+
this._stateFetched = true;
|
|
826
|
+
void this.fetchState(this.threadId);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
static styles = css`
|
|
831
|
+
@import url("https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600&family=Spline+Sans+Mono:wght@400;500&display=swap");
|
|
832
|
+
|
|
833
|
+
/* ── Root ────────────────────────────────────────────────────────── */
|
|
834
|
+
:host {
|
|
835
|
+
display: flex;
|
|
836
|
+
flex-direction: row;
|
|
837
|
+
overflow: hidden;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
.cpk-td {
|
|
841
|
+
font-family: "Plus Jakarta Sans", sans-serif;
|
|
842
|
+
font-size: 13px;
|
|
843
|
+
display: flex;
|
|
844
|
+
flex-direction: row;
|
|
845
|
+
width: 100%;
|
|
846
|
+
height: 100%;
|
|
847
|
+
overflow: hidden;
|
|
848
|
+
background: #ffffff;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
/* ── Left area ───────────────────────────────────────────────────── */
|
|
852
|
+
.cpk-td__left {
|
|
853
|
+
flex: 1;
|
|
854
|
+
min-width: 0;
|
|
855
|
+
display: flex;
|
|
856
|
+
flex-direction: column;
|
|
857
|
+
overflow: hidden;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
/* ── Tab bar header ──────────────────────────────────────────────── */
|
|
861
|
+
.cpk-td__tabs-header {
|
|
862
|
+
/* No top/right padding so tabs and toggle sit flush against the
|
|
863
|
+
top and right edges of the inspector. */
|
|
864
|
+
padding: 0 0 0 12px;
|
|
865
|
+
border-bottom: 1px solid #dbdbe5;
|
|
866
|
+
flex-shrink: 0;
|
|
867
|
+
display: flex;
|
|
868
|
+
align-items: stretch;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
.cpk-td__tab-group {
|
|
872
|
+
display: flex;
|
|
873
|
+
gap: 0;
|
|
874
|
+
margin-bottom: -1px;
|
|
875
|
+
/* Allow the tab list to shrink rather than pushing the panel-toggle
|
|
876
|
+
button past the right edge of the inspector when horizontal space
|
|
877
|
+
gets tight (the drawer being open eats noticeably into width). */
|
|
878
|
+
min-width: 0;
|
|
879
|
+
flex-shrink: 1;
|
|
880
|
+
overflow: hidden;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
.cpk-td__tab {
|
|
884
|
+
font-family: "Plus Jakarta Sans", sans-serif;
|
|
885
|
+
font-size: 11px;
|
|
886
|
+
font-weight: 500;
|
|
887
|
+
padding: 10px 12px;
|
|
888
|
+
border: none;
|
|
889
|
+
border-bottom: 2px solid transparent;
|
|
890
|
+
cursor: pointer;
|
|
891
|
+
background: transparent;
|
|
892
|
+
color: #838389;
|
|
893
|
+
transition:
|
|
894
|
+
color 0.12s,
|
|
895
|
+
border-color 0.12s;
|
|
896
|
+
white-space: nowrap;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
.cpk-td__tab:hover {
|
|
900
|
+
color: #010507;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
.cpk-td__tab--active {
|
|
904
|
+
color: #010507;
|
|
905
|
+
border-bottom-color: #bec2ff;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
/* Toggle is a separate control, not a tab — so it does NOT use the
|
|
909
|
+
tabs' bottom-border active indicator. Instead, a subtle filled
|
|
910
|
+
state communicates "the drawer is open," and a vertical separator
|
|
911
|
+
on the left visually divorces it from the tab group. */
|
|
912
|
+
.cpk-td__panel-toggle {
|
|
913
|
+
margin-left: auto;
|
|
914
|
+
align-self: stretch;
|
|
915
|
+
display: flex;
|
|
916
|
+
align-items: center;
|
|
917
|
+
justify-content: center;
|
|
918
|
+
padding: 0 12px;
|
|
919
|
+
border: none;
|
|
920
|
+
border-left: 1px solid #dbdbe5;
|
|
921
|
+
background: transparent;
|
|
922
|
+
color: #838389;
|
|
923
|
+
cursor: pointer;
|
|
924
|
+
flex-shrink: 0;
|
|
925
|
+
transition:
|
|
926
|
+
color 0.12s,
|
|
927
|
+
background 0.12s;
|
|
928
|
+
}
|
|
929
|
+
.cpk-td__panel-toggle:hover {
|
|
930
|
+
color: #010507;
|
|
931
|
+
background: #f4f4f9;
|
|
932
|
+
}
|
|
933
|
+
.cpk-td__panel-toggle--active {
|
|
934
|
+
color: #5558b2;
|
|
935
|
+
background: #eee6fe;
|
|
936
|
+
}
|
|
937
|
+
.cpk-td__panel-toggle--active:hover {
|
|
938
|
+
background: #e4d8fc;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
/* ── Scrollable content ──────────────────────────────────────────── */
|
|
942
|
+
.cpk-td__content {
|
|
943
|
+
flex: 1;
|
|
944
|
+
overflow-y: auto;
|
|
945
|
+
padding: 16px;
|
|
946
|
+
display: flex;
|
|
947
|
+
flex-direction: column;
|
|
948
|
+
gap: 8px;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
/* Pin direct children so expanded tool bodies don't get flex-shrunk. */
|
|
952
|
+
.cpk-td__content > * {
|
|
953
|
+
flex-shrink: 0;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
/*
|
|
957
|
+
* Each tab's content is wrapped in this panel so the keep-mounted
|
|
958
|
+
* inactive panels can be hidden via display:none without disturbing
|
|
959
|
+
* the gap between visible siblings. The flex column + gap gives each
|
|
960
|
+
* conversation item / event row breathing room (the cpk-td__content
|
|
961
|
+
* rule above no longer reaches them now that they are nested inside
|
|
962
|
+
* the per-panel wrapper).
|
|
963
|
+
*/
|
|
964
|
+
.cpk-td__panel {
|
|
965
|
+
display: flex;
|
|
966
|
+
flex-direction: column;
|
|
967
|
+
gap: 12px;
|
|
968
|
+
}
|
|
969
|
+
.cpk-td__panel > * {
|
|
970
|
+
flex-shrink: 0;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
/* ── Empty state ─────────────────────────────────────────────────── */
|
|
974
|
+
.cpk-td__empty-state {
|
|
975
|
+
flex: 1;
|
|
976
|
+
display: flex;
|
|
977
|
+
flex-direction: column;
|
|
978
|
+
align-items: center;
|
|
979
|
+
justify-content: center;
|
|
980
|
+
gap: 8px;
|
|
981
|
+
color: #838389;
|
|
982
|
+
font-size: 13px;
|
|
983
|
+
padding: 40px 0;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
.cpk-td__empty-hint {
|
|
987
|
+
font-size: 11px;
|
|
988
|
+
color: #838389;
|
|
989
|
+
text-align: center;
|
|
990
|
+
max-width: 220px;
|
|
991
|
+
line-height: 1.5;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
/* ── Status messages ─────────────────────────────────────────────── */
|
|
995
|
+
.cpk-td__status {
|
|
996
|
+
padding: 16px;
|
|
997
|
+
font-size: 12px;
|
|
998
|
+
color: #838389;
|
|
999
|
+
text-align: center;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
.cpk-td__status--error {
|
|
1003
|
+
color: #c0333a;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
/* ── Conversation bubbles ────────────────────────────────────────── */
|
|
1007
|
+
.cpk-td__bubble {
|
|
1008
|
+
display: flex;
|
|
1009
|
+
margin-bottom: 2px;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
.cpk-td__bubble--user {
|
|
1013
|
+
justify-content: flex-end;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
.cpk-td__bubble--assistant {
|
|
1017
|
+
justify-content: flex-start;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
.cpk-td__bubble-inner {
|
|
1021
|
+
padding: 9px 14px;
|
|
1022
|
+
max-width: 75%;
|
|
1023
|
+
font-size: 13px;
|
|
1024
|
+
line-height: 1.55;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
.cpk-td__bubble-inner--user {
|
|
1028
|
+
background: #eee6fe;
|
|
1029
|
+
color: #57575b;
|
|
1030
|
+
border-radius: 10px 10px 3px 10px;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
.cpk-td__show-more {
|
|
1034
|
+
display: inline-block;
|
|
1035
|
+
margin-top: 4px;
|
|
1036
|
+
font-size: 11px;
|
|
1037
|
+
font-weight: 500;
|
|
1038
|
+
color: #57575b;
|
|
1039
|
+
cursor: pointer;
|
|
1040
|
+
text-decoration: underline;
|
|
1041
|
+
text-underline-offset: 2px;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
.cpk-td__bubble-inner--assistant {
|
|
1045
|
+
background: #f7f7f9;
|
|
1046
|
+
color: #010507;
|
|
1047
|
+
border-radius: 10px 10px 10px 3px;
|
|
1048
|
+
border: 1px solid #e9e9ef;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
/* ── Tool call blocks ────────────────────────────────────────────── */
|
|
1052
|
+
.cpk-td__tool-block {
|
|
1053
|
+
border: 1px solid #e9e9ef;
|
|
1054
|
+
border-radius: 6px;
|
|
1055
|
+
overflow: hidden;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
.cpk-td__tool-header {
|
|
1059
|
+
display: flex;
|
|
1060
|
+
align-items: center;
|
|
1061
|
+
gap: 6px;
|
|
1062
|
+
padding: 6px 10px;
|
|
1063
|
+
background: rgba(133, 236, 206, 0.15);
|
|
1064
|
+
cursor: pointer;
|
|
1065
|
+
font-size: 11px;
|
|
1066
|
+
user-select: none;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
.cpk-td__tool-header:hover {
|
|
1070
|
+
background: rgba(133, 236, 206, 0.22);
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
.cpk-td__tool-name {
|
|
1074
|
+
font-family: "Spline Sans Mono", monospace;
|
|
1075
|
+
font-size: 10px;
|
|
1076
|
+
font-weight: 500;
|
|
1077
|
+
color: #189370;
|
|
1078
|
+
text-transform: uppercase;
|
|
1079
|
+
flex: 1;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
.cpk-td__tool-status {
|
|
1083
|
+
font-family: "Spline Sans Mono", monospace;
|
|
1084
|
+
font-size: 9px;
|
|
1085
|
+
text-transform: uppercase;
|
|
1086
|
+
color: #189370;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
.cpk-td__tool-status--pending {
|
|
1090
|
+
color: #996300;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
.cpk-td__tool-chevron {
|
|
1094
|
+
color: #838389;
|
|
1095
|
+
font-size: 10px;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
.cpk-td__tool-body {
|
|
1099
|
+
padding: 8px 10px;
|
|
1100
|
+
border-top: 1px solid #e9e9ef;
|
|
1101
|
+
background: #ffffff;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
.cpk-td__tool-section-label {
|
|
1105
|
+
font-family: "Spline Sans Mono", monospace;
|
|
1106
|
+
font-size: 9px;
|
|
1107
|
+
font-weight: 500;
|
|
1108
|
+
color: #838389;
|
|
1109
|
+
text-transform: uppercase;
|
|
1110
|
+
margin-bottom: 4px;
|
|
1111
|
+
letter-spacing: 0.3px;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
.cpk-td__tool-pre {
|
|
1115
|
+
margin: 0;
|
|
1116
|
+
font-family: "Spline Sans Mono", monospace;
|
|
1117
|
+
font-size: 10px;
|
|
1118
|
+
background: #f7f7f9;
|
|
1119
|
+
padding: 6px 8px;
|
|
1120
|
+
border-radius: 4px;
|
|
1121
|
+
overflow-x: auto;
|
|
1122
|
+
white-space: pre-wrap;
|
|
1123
|
+
word-break: break-all;
|
|
1124
|
+
color: #010507;
|
|
1125
|
+
line-height: 1.6;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
/* ── Tool call group ─────────────────────────────────────────────── */
|
|
1129
|
+
.cpk-td__tool-group {
|
|
1130
|
+
border: 1px solid #e9e9ef;
|
|
1131
|
+
border-radius: 6px;
|
|
1132
|
+
overflow: hidden;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
.cpk-td__tool-group-header {
|
|
1136
|
+
padding: 5px 10px;
|
|
1137
|
+
background: rgba(133, 236, 206, 0.15);
|
|
1138
|
+
font-family: "Spline Sans Mono", monospace;
|
|
1139
|
+
font-size: 10px;
|
|
1140
|
+
color: #189370;
|
|
1141
|
+
text-transform: uppercase;
|
|
1142
|
+
font-weight: 500;
|
|
1143
|
+
border-bottom: 1px solid #e9e9ef;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
.cpk-td__tool-group .cpk-td__tool-block {
|
|
1147
|
+
border: none;
|
|
1148
|
+
border-bottom: 1px solid #e9e9ef;
|
|
1149
|
+
border-radius: 0;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
.cpk-td__tool-group .cpk-td__tool-block:last-child {
|
|
1153
|
+
border-bottom: none;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
/* ── Inline chips (reasoning / state update) ─────────────────────── */
|
|
1157
|
+
.cpk-td__inline-chip {
|
|
1158
|
+
display: flex;
|
|
1159
|
+
align-items: center;
|
|
1160
|
+
gap: 8px;
|
|
1161
|
+
padding: 5px 0;
|
|
1162
|
+
color: #838389;
|
|
1163
|
+
font-family: "Spline Sans Mono", monospace;
|
|
1164
|
+
font-size: 9px;
|
|
1165
|
+
text-transform: uppercase;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
.cpk-td__inline-chip::before,
|
|
1169
|
+
.cpk-td__inline-chip::after {
|
|
1170
|
+
content: "";
|
|
1171
|
+
flex: 1;
|
|
1172
|
+
height: 1px;
|
|
1173
|
+
background: #e9e9ef;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
/* ── Generative UI ──────────────────────────────────────────────── */
|
|
1177
|
+
@keyframes cpk-genui-enter {
|
|
1178
|
+
from {
|
|
1179
|
+
opacity: 0;
|
|
1180
|
+
transform: translateY(8px);
|
|
1181
|
+
}
|
|
1182
|
+
to {
|
|
1183
|
+
opacity: 1;
|
|
1184
|
+
transform: translateY(0);
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
.cpk-td__genui {
|
|
1189
|
+
display: flex;
|
|
1190
|
+
flex-direction: column;
|
|
1191
|
+
gap: 6px;
|
|
1192
|
+
padding: 4px 16px 8px;
|
|
1193
|
+
animation: cpk-genui-enter 0.25s cubic-bezier(0.16, 1, 0.3, 1) both;
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
.cpk-td__genui-badge {
|
|
1197
|
+
display: inline-flex;
|
|
1198
|
+
align-items: center;
|
|
1199
|
+
gap: 4px;
|
|
1200
|
+
padding: 2px 8px;
|
|
1201
|
+
border-radius: 4px;
|
|
1202
|
+
background: #eee6fe;
|
|
1203
|
+
color: #57575b;
|
|
1204
|
+
font-size: 10px;
|
|
1205
|
+
font-weight: 600;
|
|
1206
|
+
align-self: flex-start;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
.cpk-td__genui-card {
|
|
1210
|
+
overflow: hidden;
|
|
1211
|
+
border-radius: 12px;
|
|
1212
|
+
border: 1px solid #e2e8f0;
|
|
1213
|
+
background: #fff;
|
|
1214
|
+
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.08);
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
.cpk-td__genui-placeholder {
|
|
1218
|
+
padding: 8px 12px;
|
|
1219
|
+
border-radius: 8px;
|
|
1220
|
+
border: 1px solid #ede9fe;
|
|
1221
|
+
background: #f5f3ff;
|
|
1222
|
+
color: #7c3aed;
|
|
1223
|
+
font-size: 11px;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
/* ── AG-UI Events ────────────────────────────────────────────────── */
|
|
1227
|
+
.cpk-td__event {
|
|
1228
|
+
flex-shrink: 0;
|
|
1229
|
+
border: 1px solid #e9e9ef;
|
|
1230
|
+
border-radius: 6px;
|
|
1231
|
+
overflow: hidden;
|
|
1232
|
+
/*
|
|
1233
|
+
* content-visibility: auto lets the browser skip layout + paint for
|
|
1234
|
+
* off-screen events while keeping them in the DOM (so scroll size
|
|
1235
|
+
* stays correct). Without this, switching back to AG-UI Events on a
|
|
1236
|
+
* thread with hundreds of events triggers a full layout pass over
|
|
1237
|
+
* every event row, which on Martha's intelligence-backed example
|
|
1238
|
+
* shows up as a multi-second freeze each time the panel becomes
|
|
1239
|
+
* visible. The intrinsic-size hint avoids the visible jump as the
|
|
1240
|
+
* browser swaps in real heights when items scroll into view.
|
|
1241
|
+
*/
|
|
1242
|
+
content-visibility: auto;
|
|
1243
|
+
contain-intrinsic-size: 0 80px;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
.cpk-td__event-header {
|
|
1247
|
+
display: flex;
|
|
1248
|
+
justify-content: space-between;
|
|
1249
|
+
align-items: center;
|
|
1250
|
+
padding: 5px 10px;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
.cpk-td__event-type {
|
|
1254
|
+
font-family: "Spline Sans Mono", monospace;
|
|
1255
|
+
font-size: 9px;
|
|
1256
|
+
font-weight: 500;
|
|
1257
|
+
text-transform: uppercase;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
.cpk-td__event-time {
|
|
1261
|
+
font-family: "Spline Sans Mono", monospace;
|
|
1262
|
+
font-size: 9px;
|
|
1263
|
+
color: #838389;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
.cpk-td__event-payload {
|
|
1267
|
+
margin: 0;
|
|
1268
|
+
font-family: "Spline Sans Mono", monospace;
|
|
1269
|
+
font-size: 10px;
|
|
1270
|
+
line-height: 1.6;
|
|
1271
|
+
white-space: pre-wrap;
|
|
1272
|
+
word-break: break-all;
|
|
1273
|
+
color: #57575b;
|
|
1274
|
+
padding: 8px 10px;
|
|
1275
|
+
border-top: 1px solid #e9e9ef;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
/* ── JSON block (agent state) ────────────────────────────────────── */
|
|
1279
|
+
.cpk-td__json-block {
|
|
1280
|
+
margin: 0;
|
|
1281
|
+
font-family: "Spline Sans Mono", monospace;
|
|
1282
|
+
font-size: 11px;
|
|
1283
|
+
line-height: 1.8;
|
|
1284
|
+
white-space: pre-wrap;
|
|
1285
|
+
word-break: break-all;
|
|
1286
|
+
color: #57575b;
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
/* ── Resize divider ──────────────────────────────────────────────── */
|
|
1290
|
+
/* Floats over the drawer's left edge so the toggle and the drawer
|
|
1291
|
+
touch directly without a 4px flex-gap between them. The hit zone
|
|
1292
|
+
is wider than its visual hint to make it easy to grab. */
|
|
1293
|
+
.cpk-td__detail-divider {
|
|
1294
|
+
position: absolute;
|
|
1295
|
+
top: 0;
|
|
1296
|
+
bottom: 0;
|
|
1297
|
+
left: -3px;
|
|
1298
|
+
width: 7px;
|
|
1299
|
+
cursor: col-resize;
|
|
1300
|
+
background: transparent;
|
|
1301
|
+
z-index: 5;
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
.cpk-td__detail-divider:hover {
|
|
1305
|
+
background: rgba(190, 194, 255, 0.3);
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
/* ── Right detail panel ──────────────────────────────────────────── */
|
|
1309
|
+
.cpk-td__detail {
|
|
1310
|
+
flex-shrink: 0;
|
|
1311
|
+
overflow: hidden;
|
|
1312
|
+
background: #f7f7f9;
|
|
1313
|
+
display: flex;
|
|
1314
|
+
flex-direction: column;
|
|
1315
|
+
gap: 0;
|
|
1316
|
+
padding: 0;
|
|
1317
|
+
box-sizing: border-box;
|
|
1318
|
+
position: relative;
|
|
1319
|
+
/* Slide open/closed via width + padding transition. When closed,
|
|
1320
|
+
width and padding are 0 so the drawer fully collapses. */
|
|
1321
|
+
transition:
|
|
1322
|
+
width 220ms cubic-bezier(0.4, 0, 0.2, 1),
|
|
1323
|
+
padding 220ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
.cpk-td__detail[data-open="true"] {
|
|
1327
|
+
overflow-y: auto;
|
|
1328
|
+
padding: 16px;
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
.cpk-tdp__section-title {
|
|
1332
|
+
font-family: "Spline Sans Mono", monospace;
|
|
1333
|
+
font-size: 10px;
|
|
1334
|
+
font-weight: 500;
|
|
1335
|
+
color: #838389;
|
|
1336
|
+
text-transform: uppercase;
|
|
1337
|
+
letter-spacing: 0.6px;
|
|
1338
|
+
margin-bottom: 8px;
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
.cpk-tdp__divider {
|
|
1342
|
+
height: 1px;
|
|
1343
|
+
background: #dbdbe5;
|
|
1344
|
+
margin: 14px 0;
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
.cpk-tdp__row {
|
|
1348
|
+
display: flex;
|
|
1349
|
+
justify-content: space-between;
|
|
1350
|
+
align-items: flex-start;
|
|
1351
|
+
padding: 3px 0;
|
|
1352
|
+
gap: 8px;
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
.cpk-tdp__label {
|
|
1356
|
+
color: #838389;
|
|
1357
|
+
font-size: 11px;
|
|
1358
|
+
white-space: nowrap;
|
|
1359
|
+
flex-shrink: 0;
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
.cpk-tdp__value {
|
|
1363
|
+
color: #010507;
|
|
1364
|
+
font-family: "Spline Sans Mono", monospace;
|
|
1365
|
+
font-size: 11px;
|
|
1366
|
+
text-align: right;
|
|
1367
|
+
min-width: 0;
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
.cpk-tdp__value--truncate {
|
|
1371
|
+
overflow: hidden;
|
|
1372
|
+
text-overflow: ellipsis;
|
|
1373
|
+
white-space: nowrap;
|
|
1374
|
+
max-width: 130px;
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
.cpk-tdp__value--wrap {
|
|
1378
|
+
white-space: normal;
|
|
1379
|
+
word-break: break-all;
|
|
1380
|
+
text-align: right;
|
|
1381
|
+
}
|
|
1382
|
+
`;
|
|
1383
|
+
|
|
1384
|
+
updated(_changed: Map<string, unknown>): void {
|
|
1385
|
+
if (this.threadId !== this._lastFetchedThreadId) {
|
|
1386
|
+
this._lastFetchedThreadId = this.threadId;
|
|
1387
|
+
this._lastSeenLiveMessageVersion = this.liveMessageVersion;
|
|
1388
|
+
this._tab = "conversation";
|
|
1389
|
+
this._activatedTabs = new Set(["conversation"]);
|
|
1390
|
+
this._panelTplCache = new Map();
|
|
1391
|
+
this._expandedTools = new Set();
|
|
1392
|
+
this._expandedMessages = new Set();
|
|
1393
|
+
this._messagesAbort?.abort();
|
|
1394
|
+
this._messagesAbort = null;
|
|
1395
|
+
this._eventsAbort?.abort();
|
|
1396
|
+
this._eventsAbort = null;
|
|
1397
|
+
this._stateAbort?.abort();
|
|
1398
|
+
this._stateAbort = null;
|
|
1399
|
+
// Reset cleared so the next click into events/state triggers a fresh
|
|
1400
|
+
// fetch. Eagerly clear `_fetchedEvents` / `_fetchedState` so the empty
|
|
1401
|
+
// state doesn't briefly show last thread's data.
|
|
1402
|
+
this._eventsFetched = false;
|
|
1403
|
+
this._stateFetched = false;
|
|
1404
|
+
this._fetchedEvents = null;
|
|
1405
|
+
this._fetchedState = null;
|
|
1406
|
+
|
|
1407
|
+
if (this.threadId) {
|
|
1408
|
+
// Conversation is the default tab and shows immediately on thread
|
|
1409
|
+
// open, so fetch eagerly. Events and state are only visible once the
|
|
1410
|
+
// user clicks their sub-tab; deferring those fetches prevents a long
|
|
1411
|
+
// JSON.parse of a large events payload from blocking the main thread
|
|
1412
|
+
// before the user has even shown intent to view them.
|
|
1413
|
+
void this.fetchMessages(this.threadId);
|
|
1414
|
+
} else {
|
|
1415
|
+
this._conversation = [];
|
|
1416
|
+
}
|
|
1417
|
+
} else if (
|
|
1418
|
+
this.threadId &&
|
|
1419
|
+
this.liveMessageVersion !== this._lastSeenLiveMessageVersion
|
|
1420
|
+
) {
|
|
1421
|
+
// Same thread, but the parent inspector signalled new agent-emitted
|
|
1422
|
+
// messages on this thread (via `liveMessageVersion`). Re-fetch the
|
|
1423
|
+
// canonical conversation from the runtime so streaming output flows
|
|
1424
|
+
// into the view without us reimplementing AG-UI → ConversationItem
|
|
1425
|
+
// mapping in the parent. `silent: true` so the loading-state indicator
|
|
1426
|
+
// doesn't flash between every streaming chunk and we keep the
|
|
1427
|
+
// last-good view on transient fetch errors.
|
|
1428
|
+
this._lastSeenLiveMessageVersion = this.liveMessageVersion;
|
|
1429
|
+
this._messagesAbort?.abort();
|
|
1430
|
+
this._messagesAbort = null;
|
|
1431
|
+
void this.fetchMessages(this.threadId, true);
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
/**
|
|
1436
|
+
* Fetch the canonical conversation for `threadId` from the runtime.
|
|
1437
|
+
*
|
|
1438
|
+
* `silent` is true for live re-fetches triggered by `liveMessageVersion`
|
|
1439
|
+
* bumps during streaming. In that mode we never toggle the loading state
|
|
1440
|
+
* (which would flash "Loading messages…" between every message) and we
|
|
1441
|
+
* keep the previous conversation on transient errors instead of blanking
|
|
1442
|
+
* it. Initial threadId-change fetches use the default (`silent=false`)
|
|
1443
|
+
* so users see an explicit loading indicator on first load.
|
|
1444
|
+
*/
|
|
1445
|
+
private async fetchMessages(
|
|
1446
|
+
threadId: string,
|
|
1447
|
+
silent: boolean = false,
|
|
1448
|
+
): Promise<void> {
|
|
1449
|
+
if (!this.runtimeUrl) {
|
|
1450
|
+
if (!silent) this._conversation = [];
|
|
1451
|
+
return;
|
|
1452
|
+
}
|
|
1453
|
+
const controller = new AbortController();
|
|
1454
|
+
this._messagesAbort = controller;
|
|
1455
|
+
if (!silent) {
|
|
1456
|
+
this._loadingMessages = true;
|
|
1457
|
+
this._messagesError = null;
|
|
1458
|
+
}
|
|
1459
|
+
try {
|
|
1460
|
+
const res = await fetch(
|
|
1461
|
+
`${this.runtimeUrl}/threads/${encodeURIComponent(threadId)}/messages`,
|
|
1462
|
+
{ headers: { ...this.headers }, signal: controller.signal },
|
|
1463
|
+
);
|
|
1464
|
+
if (controller.signal.aborted || this.threadId !== threadId) return;
|
|
1465
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
1466
|
+
const data = (await res.json()) as { messages: ApiThreadMessage[] };
|
|
1467
|
+
if (controller.signal.aborted || this.threadId !== threadId) return;
|
|
1468
|
+
this._conversation = this.mapMessages(data.messages);
|
|
1469
|
+
} catch (err) {
|
|
1470
|
+
if (err instanceof Error && err.name === "AbortError") return;
|
|
1471
|
+
if (!silent) {
|
|
1472
|
+
this._messagesError =
|
|
1473
|
+
err instanceof Error ? err.message : "Failed to load messages";
|
|
1474
|
+
this._conversation = [];
|
|
1475
|
+
}
|
|
1476
|
+
// Silent mode: keep last-good conversation, don't surface the error.
|
|
1477
|
+
// The next successful live re-fetch will recover automatically.
|
|
1478
|
+
} finally {
|
|
1479
|
+
if (!silent && !controller.signal.aborted) {
|
|
1480
|
+
this._loadingMessages = false;
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
private async fetchEvents(threadId: string): Promise<void> {
|
|
1486
|
+
this._eventsNotAvailable = false;
|
|
1487
|
+
if (!this.runtimeUrl) {
|
|
1488
|
+
this._fetchedEvents = null;
|
|
1489
|
+
return;
|
|
1490
|
+
}
|
|
1491
|
+
const controller = new AbortController();
|
|
1492
|
+
this._eventsAbort = controller;
|
|
1493
|
+
this._loadingEvents = true;
|
|
1494
|
+
this._eventsError = null;
|
|
1495
|
+
try {
|
|
1496
|
+
const res = await fetch(
|
|
1497
|
+
`${this.runtimeUrl}/threads/${encodeURIComponent(threadId)}/events`,
|
|
1498
|
+
{ headers: { ...this.headers }, signal: controller.signal },
|
|
1499
|
+
);
|
|
1500
|
+
// Drop results if a newer fetch superseded this one (thread switched
|
|
1501
|
+
// mid-flight). Without this, switching A→B can leave thread B's view
|
|
1502
|
+
// showing thread A's events when A's request resolves last.
|
|
1503
|
+
if (controller.signal.aborted || this.threadId !== threadId) return;
|
|
1504
|
+
if (res.status === 501) {
|
|
1505
|
+
// Endpoint not supported on this runtime (e.g. Intelligence platform).
|
|
1506
|
+
// Mark unavailable so we don't misleadingly fall back to the parent's
|
|
1507
|
+
// live agent events — those are agent-keyed, not thread-keyed, and
|
|
1508
|
+
// would render identical across every thread on the same agent.
|
|
1509
|
+
this._eventsNotAvailable = true;
|
|
1510
|
+
this._fetchedEvents = null;
|
|
1511
|
+
return;
|
|
1512
|
+
}
|
|
1513
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
1514
|
+
const data = (await res.json()) as {
|
|
1515
|
+
events: Array<Record<string, unknown>>;
|
|
1516
|
+
};
|
|
1517
|
+
if (controller.signal.aborted || this.threadId !== threadId) return;
|
|
1518
|
+
this._fetchedEvents = this.mapApiEvents(data.events);
|
|
1519
|
+
} catch (err) {
|
|
1520
|
+
if (err instanceof Error && err.name === "AbortError") return;
|
|
1521
|
+
if (this.threadId !== threadId) return;
|
|
1522
|
+
this._eventsError =
|
|
1523
|
+
err instanceof Error ? err.message : "Failed to load events";
|
|
1524
|
+
this._fetchedEvents = [];
|
|
1525
|
+
} finally {
|
|
1526
|
+
if (!controller.signal.aborted && this.threadId === threadId) {
|
|
1527
|
+
this._loadingEvents = false;
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
private async fetchState(threadId: string): Promise<void> {
|
|
1533
|
+
this._stateNotAvailable = false;
|
|
1534
|
+
if (!this.runtimeUrl) {
|
|
1535
|
+
this._fetchedState = null;
|
|
1536
|
+
return;
|
|
1537
|
+
}
|
|
1538
|
+
const controller = new AbortController();
|
|
1539
|
+
this._stateAbort = controller;
|
|
1540
|
+
this._loadingState = true;
|
|
1541
|
+
this._stateError = null;
|
|
1542
|
+
try {
|
|
1543
|
+
const res = await fetch(
|
|
1544
|
+
`${this.runtimeUrl}/threads/${encodeURIComponent(threadId)}/state`,
|
|
1545
|
+
{ headers: { ...this.headers }, signal: controller.signal },
|
|
1546
|
+
);
|
|
1547
|
+
if (controller.signal.aborted || this.threadId !== threadId) return;
|
|
1548
|
+
if (res.status === 501) {
|
|
1549
|
+
this._stateNotAvailable = true;
|
|
1550
|
+
this._fetchedState = null;
|
|
1551
|
+
return;
|
|
1552
|
+
}
|
|
1553
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
1554
|
+
const data = (await res.json()) as {
|
|
1555
|
+
state: Record<string, unknown> | null;
|
|
1556
|
+
};
|
|
1557
|
+
if (controller.signal.aborted || this.threadId !== threadId) return;
|
|
1558
|
+
this._fetchedState = data.state ?? null;
|
|
1559
|
+
} catch (err) {
|
|
1560
|
+
if (err instanceof Error && err.name === "AbortError") return;
|
|
1561
|
+
if (this.threadId !== threadId) return;
|
|
1562
|
+
this._stateError =
|
|
1563
|
+
err instanceof Error ? err.message : "Failed to load state";
|
|
1564
|
+
this._fetchedState = null;
|
|
1565
|
+
} finally {
|
|
1566
|
+
if (!controller.signal.aborted && this.threadId === threadId) {
|
|
1567
|
+
this._loadingState = false;
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
private mapMessages(messages: ApiThreadMessage[]): ConversationItem[] {
|
|
1573
|
+
const items: ConversationItem[] = [];
|
|
1574
|
+
const toolCallMap = new Map<string, ConversationToolCall>();
|
|
1575
|
+
for (const msg of messages) {
|
|
1576
|
+
if (msg.role === "user" && msg.content) {
|
|
1577
|
+
items.push({
|
|
1578
|
+
id: msg.id,
|
|
1579
|
+
type: "user",
|
|
1580
|
+
content: msg.content,
|
|
1581
|
+
createdAt: "",
|
|
1582
|
+
});
|
|
1583
|
+
} else if (msg.role === "assistant") {
|
|
1584
|
+
if (msg.toolCalls?.length) {
|
|
1585
|
+
for (const tc of msg.toolCalls) {
|
|
1586
|
+
let args: Record<string, unknown> = {};
|
|
1587
|
+
try {
|
|
1588
|
+
args = JSON.parse(tc.args) as Record<string, unknown>;
|
|
1589
|
+
} catch (err) {
|
|
1590
|
+
// Inspector is a debugging surface — surface malformed payloads
|
|
1591
|
+
// instead of silently substituting `{}`. The sentinel lets the
|
|
1592
|
+
// renderer flag "raw arguments — failed to parse" if/when it
|
|
1593
|
+
// grows that branch; the console.error gives anyone with the
|
|
1594
|
+
// devtools open immediate visibility into the offending blob.
|
|
1595
|
+
console.error(
|
|
1596
|
+
"[CopilotKit Inspector] Failed to parse tool-call arguments",
|
|
1597
|
+
{ toolCallId: tc.id, raw: tc.args, error: err },
|
|
1598
|
+
);
|
|
1599
|
+
args = { __parseError: true, __raw: tc.args };
|
|
1600
|
+
}
|
|
1601
|
+
const item: ConversationToolCall = {
|
|
1602
|
+
id: tc.id,
|
|
1603
|
+
type: "tool_call",
|
|
1604
|
+
toolName: tc.name,
|
|
1605
|
+
toolCallId: tc.id,
|
|
1606
|
+
arguments: args,
|
|
1607
|
+
result: null,
|
|
1608
|
+
createdAt: "",
|
|
1609
|
+
};
|
|
1610
|
+
toolCallMap.set(tc.id, item);
|
|
1611
|
+
items.push(item);
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
if (msg.content) {
|
|
1615
|
+
items.push({
|
|
1616
|
+
id: msg.id,
|
|
1617
|
+
type: "assistant",
|
|
1618
|
+
content: msg.content,
|
|
1619
|
+
createdAt: "",
|
|
1620
|
+
});
|
|
1621
|
+
}
|
|
1622
|
+
} else if (msg.role === "activity") {
|
|
1623
|
+
items.push({
|
|
1624
|
+
id: msg.id,
|
|
1625
|
+
type: "generative-ui",
|
|
1626
|
+
activityType: msg.activityType ?? "unknown",
|
|
1627
|
+
createdAt: "",
|
|
1628
|
+
});
|
|
1629
|
+
} else if (msg.role === "tool" && msg.toolCallId) {
|
|
1630
|
+
const tc = toolCallMap.get(msg.toolCallId);
|
|
1631
|
+
if (tc) {
|
|
1632
|
+
try {
|
|
1633
|
+
tc.result = JSON.parse(msg.content ?? "{}") as Record<
|
|
1634
|
+
string,
|
|
1635
|
+
unknown
|
|
1636
|
+
>;
|
|
1637
|
+
} catch (err) {
|
|
1638
|
+
// See the comment on the assistant tool-call args parse above —
|
|
1639
|
+
// same rationale, same sentinel shape so the renderer can treat
|
|
1640
|
+
// both consistently.
|
|
1641
|
+
console.error(
|
|
1642
|
+
"[CopilotKit Inspector] Failed to parse tool-call result content",
|
|
1643
|
+
{ toolCallId: msg.toolCallId, raw: msg.content, error: err },
|
|
1644
|
+
);
|
|
1645
|
+
tc.result = { __parseError: true, __raw: msg.content ?? null };
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
return items;
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
private mapApiEvents(
|
|
1654
|
+
events: Array<Record<string, unknown>>,
|
|
1655
|
+
): ApiAgentEvent[] {
|
|
1656
|
+
return events.map((event) => {
|
|
1657
|
+
const { type, timestamp, ...rest } = event;
|
|
1658
|
+
return {
|
|
1659
|
+
type: typeof type === "string" ? type : "UNKNOWN",
|
|
1660
|
+
timestamp:
|
|
1661
|
+
typeof timestamp === "string" || typeof timestamp === "number"
|
|
1662
|
+
? timestamp
|
|
1663
|
+
: Date.now(),
|
|
1664
|
+
payload: rest,
|
|
1665
|
+
};
|
|
1666
|
+
});
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
private get renderItems(): RenderItem[] {
|
|
1670
|
+
const items = this._conversation;
|
|
1671
|
+
const result: RenderItem[] = [];
|
|
1672
|
+
const seen = new Set<string>();
|
|
1673
|
+
for (const item of items) {
|
|
1674
|
+
if (item.type === "agent_responded") continue;
|
|
1675
|
+
if (item.type !== "tool_call" || !item.groupId) {
|
|
1676
|
+
result.push(item);
|
|
1677
|
+
continue;
|
|
1678
|
+
}
|
|
1679
|
+
if (seen.has(item.groupId)) continue;
|
|
1680
|
+
seen.add(item.groupId);
|
|
1681
|
+
const group: ToolCallGroup = {
|
|
1682
|
+
type: "tool_call_group",
|
|
1683
|
+
id: item.groupId,
|
|
1684
|
+
items: items.filter(
|
|
1685
|
+
(i): i is ConversationToolCall =>
|
|
1686
|
+
i.type === "tool_call" && i.groupId === item.groupId,
|
|
1687
|
+
),
|
|
1688
|
+
};
|
|
1689
|
+
result.push(group);
|
|
1690
|
+
}
|
|
1691
|
+
return result;
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
private get activityCounts(): {
|
|
1695
|
+
messages: number;
|
|
1696
|
+
toolCalls: number;
|
|
1697
|
+
generativeUi: number;
|
|
1698
|
+
} {
|
|
1699
|
+
let messages = 0;
|
|
1700
|
+
let toolCalls = 0;
|
|
1701
|
+
let generativeUi = 0;
|
|
1702
|
+
for (const item of this._conversation) {
|
|
1703
|
+
if (item.type === "user" || item.type === "assistant") messages++;
|
|
1704
|
+
if (item.type === "tool_call") toolCalls++;
|
|
1705
|
+
if (item.type === "generative-ui") generativeUi++;
|
|
1706
|
+
}
|
|
1707
|
+
return { messages, toolCalls, generativeUi };
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
private get duration(): string {
|
|
1711
|
+
const t = this.thread;
|
|
1712
|
+
if (!t?.createdAt || !t?.updatedAt) return "—";
|
|
1713
|
+
const ms =
|
|
1714
|
+
new Date(t.updatedAt).getTime() - new Date(t.createdAt).getTime();
|
|
1715
|
+
if (ms < 0) return "—";
|
|
1716
|
+
if (ms < 1000) return `${ms}ms`;
|
|
1717
|
+
const s = Math.floor(ms / 1000);
|
|
1718
|
+
if (s < 60) return `${s}s`;
|
|
1719
|
+
const m = Math.floor(s / 60);
|
|
1720
|
+
const rs = s % 60;
|
|
1721
|
+
return `${m}m ${rs}s`;
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
private toggleToolExpand(id: string): void {
|
|
1725
|
+
const next = new Set(this._expandedTools);
|
|
1726
|
+
if (next.has(id)) next.delete(id);
|
|
1727
|
+
else next.add(id);
|
|
1728
|
+
this._expandedTools = next;
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
private toggleMessageExpand(id: string): void {
|
|
1732
|
+
const next = new Set(this._expandedMessages);
|
|
1733
|
+
if (next.has(id)) next.delete(id);
|
|
1734
|
+
else next.add(id);
|
|
1735
|
+
this._expandedMessages = next;
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
private get activeEvents(): ApiAgentEvent[] {
|
|
1739
|
+
// When the endpoint explicitly returned 501 we report no events rather
|
|
1740
|
+
// than leaking the parent's agent-keyed live events across historical
|
|
1741
|
+
// threads (those would render identically for every thread on the same
|
|
1742
|
+
// agent and mislead the reader).
|
|
1743
|
+
if (this._eventsNotAvailable) return [];
|
|
1744
|
+
return this._fetchedEvents ?? this.agentEventsInput ?? [];
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
private get activeState(): Record<string, unknown> | null {
|
|
1748
|
+
if (this._stateNotAvailable) return null;
|
|
1749
|
+
return this._fetchedState ?? this.agentStateInput ?? null;
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
private hasRenderableState(): boolean {
|
|
1753
|
+
const s = this.activeState;
|
|
1754
|
+
return !!s && typeof s === "object" && Object.keys(s).length > 0;
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
private shortId(id: string | null | undefined): string {
|
|
1758
|
+
if (!id) return "—";
|
|
1759
|
+
return id.length > 20 ? id.slice(0, 8) + "…" : id;
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
private fmtTime(dateStr: string | null | undefined): string {
|
|
1763
|
+
if (!dateStr) return "—";
|
|
1764
|
+
const d = new Date(dateStr);
|
|
1765
|
+
if (Number.isNaN(d.getTime())) return "—";
|
|
1766
|
+
return d.toLocaleTimeString("en-US", {
|
|
1767
|
+
hour: "2-digit",
|
|
1768
|
+
minute: "2-digit",
|
|
1769
|
+
second: "2-digit",
|
|
1770
|
+
hour12: false,
|
|
1771
|
+
});
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
private onDetailDividerDown = (event: PointerEvent): void => {
|
|
1775
|
+
this._dividerResizing = true;
|
|
1776
|
+
this._dividerPointerId = event.pointerId;
|
|
1777
|
+
this._dividerStartX = event.clientX;
|
|
1778
|
+
this._dividerStartWidth = this._detailPanelWidth;
|
|
1779
|
+
(event.currentTarget as HTMLElement).setPointerCapture(event.pointerId);
|
|
1780
|
+
event.preventDefault();
|
|
1781
|
+
};
|
|
1782
|
+
|
|
1783
|
+
private onDetailDividerMove = (event: PointerEvent): void => {
|
|
1784
|
+
if (!this._dividerResizing || this._dividerPointerId !== event.pointerId)
|
|
1785
|
+
return;
|
|
1786
|
+
const delta = this._dividerStartX - event.clientX;
|
|
1787
|
+
this._detailPanelWidth = Math.max(
|
|
1788
|
+
160,
|
|
1789
|
+
Math.min(400, this._dividerStartWidth + delta),
|
|
1790
|
+
);
|
|
1791
|
+
};
|
|
1792
|
+
|
|
1793
|
+
private onDetailDividerUp = (event: PointerEvent): void => {
|
|
1794
|
+
if (this._dividerPointerId !== event.pointerId) return;
|
|
1795
|
+
const target = event.currentTarget as HTMLElement;
|
|
1796
|
+
if (target.hasPointerCapture(this._dividerPointerId)) {
|
|
1797
|
+
target.releasePointerCapture(this._dividerPointerId);
|
|
1798
|
+
}
|
|
1799
|
+
this._dividerResizing = false;
|
|
1800
|
+
};
|
|
1801
|
+
|
|
1802
|
+
render() {
|
|
1803
|
+
return html`
|
|
1804
|
+
<div class="cpk-td">
|
|
1805
|
+
<!-- ── Left area: tabs + content ─────────────────────────────────── -->
|
|
1806
|
+
<div class="cpk-td__left">
|
|
1807
|
+
<!-- Tab bar -->
|
|
1808
|
+
<div class="cpk-td__tabs-header">
|
|
1809
|
+
<div class="cpk-td__tab-group" role="tablist">
|
|
1810
|
+
${ɵCpkThreadDetails.TAB_LIST.map(
|
|
1811
|
+
(tab) => html`
|
|
1812
|
+
<button
|
|
1813
|
+
role="tab"
|
|
1814
|
+
class="cpk-td__tab ${
|
|
1815
|
+
this._tab === tab.id ? "cpk-td__tab--active" : ""
|
|
1816
|
+
}"
|
|
1817
|
+
@click=${() => this.activateTab(tab.id)}
|
|
1818
|
+
>
|
|
1819
|
+
${tab.label}
|
|
1820
|
+
</button>
|
|
1821
|
+
`,
|
|
1822
|
+
)}
|
|
1823
|
+
</div>
|
|
1824
|
+
${this.renderPanelToggle()}
|
|
1825
|
+
</div>
|
|
1826
|
+
|
|
1827
|
+
<!-- Scrollable content -->
|
|
1828
|
+
<div class="cpk-td__content">
|
|
1829
|
+
${
|
|
1830
|
+
this._panelInitializing
|
|
1831
|
+
? html`
|
|
1832
|
+
<div class="cpk-td__status">Loading…</div>
|
|
1833
|
+
`
|
|
1834
|
+
: nothing
|
|
1835
|
+
}
|
|
1836
|
+
${ɵCpkThreadDetails.TAB_LIST.map((tab) =>
|
|
1837
|
+
this._activatedTabs.has(tab.id)
|
|
1838
|
+
? html`<div
|
|
1839
|
+
class="cpk-td__panel"
|
|
1840
|
+
style=${
|
|
1841
|
+
this._tab === tab.id && !this._panelInitializing
|
|
1842
|
+
? ""
|
|
1843
|
+
: "display:none"
|
|
1844
|
+
}
|
|
1845
|
+
>
|
|
1846
|
+
${this.renderTabContent(tab.id)}
|
|
1847
|
+
</div>`
|
|
1848
|
+
: nothing,
|
|
1849
|
+
)}
|
|
1850
|
+
</div>
|
|
1851
|
+
</div>
|
|
1852
|
+
|
|
1853
|
+
<!--
|
|
1854
|
+
Drawer always rendered so width animates between 0 and its
|
|
1855
|
+
target. Divider lives INSIDE the drawer and is absolutely
|
|
1856
|
+
positioned over its left edge so the toggle (rightmost of the
|
|
1857
|
+
tab row) and the drawer touch with no flex-gap between them.
|
|
1858
|
+
-->
|
|
1859
|
+
<div
|
|
1860
|
+
class="cpk-td__detail"
|
|
1861
|
+
data-open=${this._showDetailPanel ? "true" : "false"}
|
|
1862
|
+
style="width:${this._showDetailPanel ? this._detailPanelWidth : 0}px"
|
|
1863
|
+
aria-hidden=${this._showDetailPanel ? "false" : "true"}
|
|
1864
|
+
>
|
|
1865
|
+
${
|
|
1866
|
+
this._showDetailPanel
|
|
1867
|
+
? html`
|
|
1868
|
+
<div
|
|
1869
|
+
class="cpk-td__detail-divider"
|
|
1870
|
+
@pointerdown=${this.onDetailDividerDown}
|
|
1871
|
+
@pointermove=${this.onDetailDividerMove}
|
|
1872
|
+
@pointerup=${this.onDetailDividerUp}
|
|
1873
|
+
@pointercancel=${this.onDetailDividerUp}
|
|
1874
|
+
></div>
|
|
1875
|
+
`
|
|
1876
|
+
: nothing
|
|
1877
|
+
}
|
|
1878
|
+
${this.renderDetailPanel()}
|
|
1879
|
+
</div>
|
|
1880
|
+
</div>
|
|
1881
|
+
`;
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
private renderConversation() {
|
|
1885
|
+
if (this._loadingMessages) {
|
|
1886
|
+
return html`
|
|
1887
|
+
<div class="cpk-td__status">Loading messages…</div>
|
|
1888
|
+
`;
|
|
1889
|
+
}
|
|
1890
|
+
if (this._messagesError) {
|
|
1891
|
+
return html`<div class="cpk-td__status cpk-td__status--error">
|
|
1892
|
+
${this._messagesError}
|
|
1893
|
+
</div>`;
|
|
1894
|
+
}
|
|
1895
|
+
if (this._conversation.length === 0) {
|
|
1896
|
+
return html`
|
|
1897
|
+
<div class="cpk-td__empty-state">
|
|
1898
|
+
<svg
|
|
1899
|
+
width="28"
|
|
1900
|
+
height="28"
|
|
1901
|
+
viewBox="0 0 24 24"
|
|
1902
|
+
fill="none"
|
|
1903
|
+
stroke="currentColor"
|
|
1904
|
+
stroke-width="1.5"
|
|
1905
|
+
stroke-linecap="round"
|
|
1906
|
+
stroke-linejoin="round"
|
|
1907
|
+
>
|
|
1908
|
+
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
|
|
1909
|
+
</svg>
|
|
1910
|
+
<span>No messages yet</span>
|
|
1911
|
+
</div>
|
|
1912
|
+
`;
|
|
1913
|
+
}
|
|
1914
|
+
// Expand state is part of the cache key because clicking a tool-call
|
|
1915
|
+
// header or the "Show more" button on a long message replaces
|
|
1916
|
+
// `_expandedTools` / `_expandedMessages` without touching
|
|
1917
|
+
// `_conversation` — without those keys the cache returns the
|
|
1918
|
+
// pre-toggle template and the disclosure appears broken.
|
|
1919
|
+
return this.cachedPanelTpl(
|
|
1920
|
+
"conversation",
|
|
1921
|
+
[this._conversation, this._expandedTools, this._expandedMessages],
|
|
1922
|
+
() => {
|
|
1923
|
+
const items = this.renderItems;
|
|
1924
|
+
return html`${items.map((item) => this.renderRenderItem(item))}`;
|
|
1925
|
+
},
|
|
1926
|
+
);
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
/**
|
|
1930
|
+
* Memoize the rendered TemplateResult for `slot` keyed by tuple
|
|
1931
|
+
* element-wise reference equality. The hot path for tab switches: when
|
|
1932
|
+
* the underlying data hasn't changed, return the previously built
|
|
1933
|
+
* TemplateResult so Lit's diff short-circuits. Each panel's `key` is
|
|
1934
|
+
* the tuple of inputs the template reads — pass everything the template
|
|
1935
|
+
* depends on, or the cache will return stale output when those inputs
|
|
1936
|
+
* change without the listed key flipping.
|
|
1937
|
+
*/
|
|
1938
|
+
private cachedPanelTpl(
|
|
1939
|
+
slot: ThreadDetailsTab,
|
|
1940
|
+
key: readonly unknown[],
|
|
1941
|
+
build: () => TemplateResult,
|
|
1942
|
+
): TemplateResult {
|
|
1943
|
+
const cached = this._panelTplCache.get(slot);
|
|
1944
|
+
if (
|
|
1945
|
+
cached &&
|
|
1946
|
+
cached.key.length === key.length &&
|
|
1947
|
+
cached.key.every((v, i) => v === key[i])
|
|
1948
|
+
) {
|
|
1949
|
+
return cached.tpl;
|
|
1950
|
+
}
|
|
1951
|
+
const tpl = build();
|
|
1952
|
+
this._panelTplCache.set(slot, { key, tpl });
|
|
1953
|
+
return tpl;
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1956
|
+
private renderRenderItem(item: RenderItem) {
|
|
1957
|
+
switch (item.type) {
|
|
1958
|
+
case "user":
|
|
1959
|
+
case "assistant":
|
|
1960
|
+
return this.renderBubble(item);
|
|
1961
|
+
case "tool_call":
|
|
1962
|
+
return this.renderToolBlock(item);
|
|
1963
|
+
case "tool_call_group":
|
|
1964
|
+
return this.renderToolGroup(item);
|
|
1965
|
+
case "reasoning":
|
|
1966
|
+
return html`<div class="cpk-td__inline-chip">
|
|
1967
|
+
<span>Reasoned for ${item.duration}</span>
|
|
1968
|
+
</div>`;
|
|
1969
|
+
case "state_update":
|
|
1970
|
+
return html`
|
|
1971
|
+
<div class="cpk-td__inline-chip">
|
|
1972
|
+
<span>Updated agent state</span>
|
|
1973
|
+
</div>
|
|
1974
|
+
`;
|
|
1975
|
+
case "generative-ui":
|
|
1976
|
+
return this.renderGenerativeUI(item);
|
|
1977
|
+
case "agent_responded":
|
|
1978
|
+
return nothing;
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
private renderBubble(item: ConversationUser | ConversationAssistant) {
|
|
1983
|
+
const isUser = item.type === "user";
|
|
1984
|
+
const threshold = ɵCpkThreadDetails.COLLAPSE_THRESHOLD;
|
|
1985
|
+
const expanded = this._expandedMessages.has(item.id);
|
|
1986
|
+
const tooLong = item.content.length > threshold;
|
|
1987
|
+
const shown =
|
|
1988
|
+
tooLong && !expanded
|
|
1989
|
+
? item.content.slice(0, threshold) + "…"
|
|
1990
|
+
: item.content;
|
|
1991
|
+
return html`
|
|
1992
|
+
<div
|
|
1993
|
+
class="cpk-td__bubble ${
|
|
1994
|
+
isUser ? "cpk-td__bubble--user" : "cpk-td__bubble--assistant"
|
|
1995
|
+
}"
|
|
1996
|
+
>
|
|
1997
|
+
<div
|
|
1998
|
+
class="cpk-td__bubble-inner ${
|
|
1999
|
+
isUser
|
|
2000
|
+
? "cpk-td__bubble-inner--user"
|
|
2001
|
+
: "cpk-td__bubble-inner--assistant"
|
|
2002
|
+
}"
|
|
2003
|
+
>
|
|
2004
|
+
${shown}
|
|
2005
|
+
${
|
|
2006
|
+
tooLong
|
|
2007
|
+
? html`<span
|
|
2008
|
+
class="cpk-td__show-more"
|
|
2009
|
+
@click=${() => this.toggleMessageExpand(item.id)}
|
|
2010
|
+
>${expanded ? "Show less" : "Show more"}</span
|
|
2011
|
+
>`
|
|
2012
|
+
: nothing
|
|
2013
|
+
}
|
|
2014
|
+
</div>
|
|
2015
|
+
</div>
|
|
2016
|
+
`;
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
private renderToolBlock(item: ConversationToolCall) {
|
|
2020
|
+
const expanded = this._expandedTools.has(item.id);
|
|
2021
|
+
return html`
|
|
2022
|
+
<div class="cpk-td__tool-block">
|
|
2023
|
+
<div
|
|
2024
|
+
class="cpk-td__tool-header"
|
|
2025
|
+
@click=${() => this.toggleToolExpand(item.id)}
|
|
2026
|
+
>
|
|
2027
|
+
<svg width="10" height="10" viewBox="0 0 10 10" fill="none">
|
|
2028
|
+
<path
|
|
2029
|
+
d="M1 9C1 9 2 7 5 7C8 7 9 9 9 9M5 1C5 1 7 2.5 7 4.5C7 6.5 5 7 5 7C5 7 3 6.5 3 4.5C3 2.5 5 1 5 1Z"
|
|
2030
|
+
stroke="#189370"
|
|
2031
|
+
stroke-width="1.2"
|
|
2032
|
+
stroke-linecap="round"
|
|
2033
|
+
stroke-linejoin="round"
|
|
2034
|
+
/>
|
|
2035
|
+
</svg>
|
|
2036
|
+
<span class="cpk-td__tool-name">${item.toolName}</span>
|
|
2037
|
+
${
|
|
2038
|
+
item.result || Object.keys(item.arguments).length > 0
|
|
2039
|
+
? html`
|
|
2040
|
+
<span class="cpk-td__tool-status">DONE</span>
|
|
2041
|
+
`
|
|
2042
|
+
: html`
|
|
2043
|
+
<span class="cpk-td__tool-status cpk-td__tool-status--pending">PENDING</span>
|
|
2044
|
+
`
|
|
2045
|
+
}
|
|
2046
|
+
<span class="cpk-td__tool-chevron">${expanded ? "▾" : "▸"}</span>
|
|
2047
|
+
</div>
|
|
2048
|
+
${
|
|
2049
|
+
expanded
|
|
2050
|
+
? html`
|
|
2051
|
+
<div class="cpk-td__tool-body">
|
|
2052
|
+
<div class="cpk-td__tool-section-label">Arguments</div>
|
|
2053
|
+
<pre class="cpk-td__tool-pre">
|
|
2054
|
+
${unsafeHTML(highlightedJson(item.arguments))}</pre
|
|
2055
|
+
>
|
|
2056
|
+
${
|
|
2057
|
+
item.result
|
|
2058
|
+
? html`
|
|
2059
|
+
<div
|
|
2060
|
+
class="cpk-td__tool-section-label"
|
|
2061
|
+
style="margin-top:8px"
|
|
2062
|
+
>
|
|
2063
|
+
Result
|
|
2064
|
+
</div>
|
|
2065
|
+
<pre class="cpk-td__tool-pre">
|
|
2066
|
+
${unsafeHTML(highlightedJson(item.result))}</pre
|
|
2067
|
+
>
|
|
2068
|
+
`
|
|
2069
|
+
: nothing
|
|
2070
|
+
}
|
|
2071
|
+
</div>
|
|
2072
|
+
`
|
|
2073
|
+
: nothing
|
|
2074
|
+
}
|
|
2075
|
+
</div>
|
|
2076
|
+
`;
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
private renderToolGroup(group: ToolCallGroup) {
|
|
2080
|
+
return html`
|
|
2081
|
+
<div class="cpk-td__tool-group">
|
|
2082
|
+
<div class="cpk-td__tool-group-header">
|
|
2083
|
+
${group.items.length} tool call${group.items.length !== 1 ? "s" : ""}
|
|
2084
|
+
</div>
|
|
2085
|
+
${group.items.map((tc) => this.renderToolBlock(tc))}
|
|
2086
|
+
</div>
|
|
2087
|
+
`;
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
private renderGenerativeUI(item: ConversationGenerativeUIItem) {
|
|
2091
|
+
return html`
|
|
2092
|
+
<div class="cpk-td__genui">
|
|
2093
|
+
<div class="cpk-td__genui-badge">
|
|
2094
|
+
<svg width="9" height="9" viewBox="0 0 24 24" fill="currentColor">
|
|
2095
|
+
<path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" />
|
|
2096
|
+
</svg>
|
|
2097
|
+
Generative UI
|
|
2098
|
+
</div>
|
|
2099
|
+
<div class="cpk-td__genui-placeholder">
|
|
2100
|
+
${item.activityType} — rendered in chat
|
|
2101
|
+
</div>
|
|
2102
|
+
</div>
|
|
2103
|
+
`;
|
|
2104
|
+
}
|
|
2105
|
+
|
|
2106
|
+
private renderState() {
|
|
2107
|
+
if (this._loadingState) {
|
|
2108
|
+
return html`
|
|
2109
|
+
<div class="cpk-td__status">Loading state…</div>
|
|
2110
|
+
`;
|
|
2111
|
+
}
|
|
2112
|
+
if (this._stateError) {
|
|
2113
|
+
return html`<div class="cpk-td__status cpk-td__status--error">
|
|
2114
|
+
${this._stateError}
|
|
2115
|
+
</div>`;
|
|
2116
|
+
}
|
|
2117
|
+
if (this._stateNotAvailable) {
|
|
2118
|
+
return html`
|
|
2119
|
+
<div class="cpk-td__empty-state">
|
|
2120
|
+
<svg
|
|
2121
|
+
width="28"
|
|
2122
|
+
height="28"
|
|
2123
|
+
viewBox="0 0 24 24"
|
|
2124
|
+
fill="none"
|
|
2125
|
+
stroke="currentColor"
|
|
2126
|
+
stroke-width="1.5"
|
|
2127
|
+
stroke-linecap="round"
|
|
2128
|
+
stroke-linejoin="round"
|
|
2129
|
+
>
|
|
2130
|
+
<ellipse cx="12" cy="5" rx="9" ry="3" />
|
|
2131
|
+
<path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3" />
|
|
2132
|
+
<path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5" />
|
|
2133
|
+
</svg>
|
|
2134
|
+
<span>State history not available</span>
|
|
2135
|
+
<span class="cpk-td__empty-hint"
|
|
2136
|
+
>This runtime doesn't yet expose per-thread agent state. Available when
|
|
2137
|
+
running against the in-memory runner.</span
|
|
2138
|
+
>
|
|
2139
|
+
</div>
|
|
2140
|
+
`;
|
|
2141
|
+
}
|
|
2142
|
+
if (!this.hasRenderableState()) {
|
|
2143
|
+
return html`
|
|
2144
|
+
<div class="cpk-td__empty-state">
|
|
2145
|
+
<svg
|
|
2146
|
+
width="28"
|
|
2147
|
+
height="28"
|
|
2148
|
+
viewBox="0 0 24 24"
|
|
2149
|
+
fill="none"
|
|
2150
|
+
stroke="currentColor"
|
|
2151
|
+
stroke-width="1.5"
|
|
2152
|
+
stroke-linecap="round"
|
|
2153
|
+
stroke-linejoin="round"
|
|
2154
|
+
>
|
|
2155
|
+
<ellipse cx="12" cy="5" rx="9" ry="3" />
|
|
2156
|
+
<path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3" />
|
|
2157
|
+
<path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5" />
|
|
2158
|
+
</svg>
|
|
2159
|
+
<span>No state captured</span>
|
|
2160
|
+
<span class="cpk-td__empty-hint"
|
|
2161
|
+
>Emitted live from STATE_SNAPSHOT events.</span
|
|
2162
|
+
>
|
|
2163
|
+
</div>
|
|
2164
|
+
`;
|
|
2165
|
+
}
|
|
2166
|
+
const stateValue = this.activeState;
|
|
2167
|
+
return this.cachedPanelTpl("agent-state", [stateValue], () => {
|
|
2168
|
+
return html`<pre class="cpk-td__json-block">
|
|
2169
|
+
${unsafeHTML(highlightedJson(stateValue))}</pre
|
|
2170
|
+
>`;
|
|
2171
|
+
});
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
private renderEvents() {
|
|
2175
|
+
if (this._loadingEvents) {
|
|
2176
|
+
return html`
|
|
2177
|
+
<div class="cpk-td__status">Loading events…</div>
|
|
2178
|
+
`;
|
|
2179
|
+
}
|
|
2180
|
+
if (this._eventsError) {
|
|
2181
|
+
return html`<div class="cpk-td__status cpk-td__status--error">
|
|
2182
|
+
${this._eventsError}
|
|
2183
|
+
</div>`;
|
|
2184
|
+
}
|
|
2185
|
+
if (this._eventsNotAvailable) {
|
|
2186
|
+
return html`
|
|
2187
|
+
<div class="cpk-td__empty-state">
|
|
2188
|
+
<span>Event history not available</span>
|
|
2189
|
+
<span class="cpk-td__empty-hint"
|
|
2190
|
+
>This runtime doesn't yet expose per-thread AG-UI events. Available when
|
|
2191
|
+
running against the in-memory runner.</span
|
|
2192
|
+
>
|
|
2193
|
+
</div>
|
|
2194
|
+
`;
|
|
2195
|
+
}
|
|
2196
|
+
const events = this.activeEvents;
|
|
2197
|
+
if (events.length === 0) {
|
|
2198
|
+
return html`
|
|
2199
|
+
<div class="cpk-td__empty-state">
|
|
2200
|
+
<span>No events captured</span>
|
|
2201
|
+
<span class="cpk-td__empty-hint"
|
|
2202
|
+
>Events are recorded live. Run the agent to see them here.</span
|
|
2203
|
+
>
|
|
2204
|
+
</div>
|
|
2205
|
+
`;
|
|
2206
|
+
}
|
|
2207
|
+
return this.cachedPanelTpl("ag-ui-events", [events], () => {
|
|
2208
|
+
return html`${events.map((event) => {
|
|
2209
|
+
const { bg, fg } = eventColors(event.type);
|
|
2210
|
+
return html`
|
|
2211
|
+
<div class="cpk-td__event">
|
|
2212
|
+
<div class="cpk-td__event-header" style="background:${bg}">
|
|
2213
|
+
<span class="cpk-td__event-type" style="color:${fg}"
|
|
2214
|
+
>${event.type}</span
|
|
2215
|
+
>
|
|
2216
|
+
<span class="cpk-td__event-time"
|
|
2217
|
+
>${formatTimestamp(event.timestamp)}</span
|
|
2218
|
+
>
|
|
2219
|
+
</div>
|
|
2220
|
+
<pre class="cpk-td__event-payload">
|
|
2221
|
+
${unsafeHTML(highlightedJson(event.payload))}</pre
|
|
2222
|
+
>
|
|
2223
|
+
</div>
|
|
2224
|
+
`;
|
|
2225
|
+
})}`;
|
|
2226
|
+
});
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2229
|
+
private renderPanelToggle() {
|
|
2230
|
+
return html`
|
|
2231
|
+
<button
|
|
2232
|
+
class="cpk-td__panel-toggle ${
|
|
2233
|
+
this._showDetailPanel ? "cpk-td__panel-toggle--active" : ""
|
|
2234
|
+
}"
|
|
2235
|
+
@click=${() => {
|
|
2236
|
+
this._showDetailPanel = !this._showDetailPanel;
|
|
2237
|
+
}}
|
|
2238
|
+
title="Toggle thread details"
|
|
2239
|
+
type="button"
|
|
2240
|
+
>
|
|
2241
|
+
<svg
|
|
2242
|
+
width="14"
|
|
2243
|
+
height="14"
|
|
2244
|
+
viewBox="0 0 24 24"
|
|
2245
|
+
fill="none"
|
|
2246
|
+
stroke="currentColor"
|
|
2247
|
+
stroke-width="2"
|
|
2248
|
+
stroke-linecap="round"
|
|
2249
|
+
stroke-linejoin="round"
|
|
2250
|
+
>
|
|
2251
|
+
<rect x="3" y="3" width="18" height="18" rx="2" />
|
|
2252
|
+
<line x1="15" y1="3" x2="15" y2="21" />
|
|
2253
|
+
</svg>
|
|
2254
|
+
</button>
|
|
2255
|
+
`;
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2258
|
+
private renderDetailPanel() {
|
|
2259
|
+
const counts = this.activityCounts;
|
|
2260
|
+
return html`
|
|
2261
|
+
<!-- Thread -->
|
|
2262
|
+
<div class="cpk-tdp__section-title">Thread</div>
|
|
2263
|
+
<div class="cpk-tdp__row">
|
|
2264
|
+
<span class="cpk-tdp__label">ID</span>
|
|
2265
|
+
<span class="cpk-tdp__value cpk-tdp__value--wrap"
|
|
2266
|
+
>${this.shortId(this.thread?.id)}</span
|
|
2267
|
+
>
|
|
2268
|
+
</div>
|
|
2269
|
+
<div class="cpk-tdp__row">
|
|
2270
|
+
<span class="cpk-tdp__label">Name</span>
|
|
2271
|
+
<span class="cpk-tdp__value">${this.thread?.name ?? "—"}</span>
|
|
2272
|
+
</div>
|
|
2273
|
+
<div class="cpk-tdp__row">
|
|
2274
|
+
<span class="cpk-tdp__label">Agent</span>
|
|
2275
|
+
<span class="cpk-tdp__value cpk-tdp__value--truncate"
|
|
2276
|
+
>${this.thread?.agentId ?? "—"}</span
|
|
2277
|
+
>
|
|
2278
|
+
</div>
|
|
2279
|
+
<div class="cpk-tdp__row">
|
|
2280
|
+
<span class="cpk-tdp__label">Created by</span>
|
|
2281
|
+
<span class="cpk-tdp__value cpk-tdp__value--truncate"
|
|
2282
|
+
>${this.thread?.createdById ?? "—"}</span
|
|
2283
|
+
>
|
|
2284
|
+
</div>
|
|
2285
|
+
|
|
2286
|
+
<div class="cpk-tdp__divider"></div>
|
|
123
2287
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
2288
|
+
<!-- Timestamps -->
|
|
2289
|
+
<div class="cpk-tdp__section-title">Timestamps</div>
|
|
2290
|
+
<div class="cpk-tdp__row">
|
|
2291
|
+
<span class="cpk-tdp__label">Created</span>
|
|
2292
|
+
<span class="cpk-tdp__value">${this.fmtTime(this.thread?.createdAt)}</span>
|
|
2293
|
+
</div>
|
|
2294
|
+
<div class="cpk-tdp__row">
|
|
2295
|
+
<span class="cpk-tdp__label">Updated</span>
|
|
2296
|
+
<span class="cpk-tdp__value">${this.fmtTime(this.thread?.updatedAt)}</span>
|
|
2297
|
+
</div>
|
|
2298
|
+
<div class="cpk-tdp__row">
|
|
2299
|
+
<span class="cpk-tdp__label">Duration</span>
|
|
2300
|
+
<span class="cpk-tdp__value">${this.duration}</span>
|
|
2301
|
+
</div>
|
|
133
2302
|
|
|
134
|
-
|
|
135
|
-
id?: string;
|
|
136
|
-
role: string;
|
|
137
|
-
contentText: string;
|
|
138
|
-
contentRaw?: SanitizedValue;
|
|
139
|
-
toolCalls: InspectorToolCall[];
|
|
140
|
-
};
|
|
2303
|
+
<div class="cpk-tdp__divider"></div>
|
|
141
2304
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
2305
|
+
<!-- Activity -->
|
|
2306
|
+
<div class="cpk-tdp__section-title">Activity</div>
|
|
2307
|
+
<div class="cpk-tdp__row">
|
|
2308
|
+
<span class="cpk-tdp__label">Messages</span>
|
|
2309
|
+
<span class="cpk-tdp__value">${counts.messages}</span>
|
|
2310
|
+
</div>
|
|
2311
|
+
<div class="cpk-tdp__row">
|
|
2312
|
+
<span class="cpk-tdp__label">Tool calls</span>
|
|
2313
|
+
<span class="cpk-tdp__value">${counts.toolCalls}</span>
|
|
2314
|
+
</div>
|
|
2315
|
+
<div class="cpk-tdp__row">
|
|
2316
|
+
<span class="cpk-tdp__label">AG-UI events</span>
|
|
2317
|
+
<span class="cpk-tdp__value">${this.activeEvents.length}</span>
|
|
2318
|
+
</div>
|
|
2319
|
+
`;
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
149
2322
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
};
|
|
2323
|
+
if (!customElements.get("cpk-thread-list")) {
|
|
2324
|
+
customElements.define("cpk-thread-list", CpkThreadList);
|
|
2325
|
+
}
|
|
2326
|
+
if (!customElements.get("cpk-thread-details")) {
|
|
2327
|
+
customElements.define("cpk-thread-details", ɵCpkThreadDetails);
|
|
2328
|
+
}
|
|
157
2329
|
|
|
158
2330
|
export class WebInspectorElement extends LitElement {
|
|
159
2331
|
static properties = {
|
|
@@ -173,6 +2345,17 @@ export class WebInspectorElement extends LitElement {
|
|
|
173
2345
|
private agentSubscriptions: Map<string, () => void> = new Map();
|
|
174
2346
|
private agentEvents: Map<string, InspectorEvent[]> = new Map();
|
|
175
2347
|
private agentMessages: Map<string, InspectorMessage[]> = new Map();
|
|
2348
|
+
// Tracks which thread each agent is currently running on. Populated from the
|
|
2349
|
+
// agent instance handed to `onAgentRunStarted` so that, when that agent
|
|
2350
|
+
// subsequently emits message changes, we can bump the per-thread live
|
|
2351
|
+
// version (below) only for the thread the messages actually belong to.
|
|
2352
|
+
private agentRunThreadId: Map<string, string> = new Map();
|
|
2353
|
+
// Per-thread monotonic version that ticks every time an agent currently
|
|
2354
|
+
// running on that thread emits a message change. `cpk-thread-details`
|
|
2355
|
+
// watches this prop and re-fetches `/threads/:id/messages` when it changes,
|
|
2356
|
+
// which is how live updates flow into the conversation view without
|
|
2357
|
+
// duplicating the runtime's message-shape conversion in the inspector.
|
|
2358
|
+
private liveMessageVersion: Map<string, number> = new Map();
|
|
176
2359
|
private agentStates: Map<string, SanitizedValue> = new Map();
|
|
177
2360
|
private flattenedEvents: InspectorEvent[] = [];
|
|
178
2361
|
private eventCounter = 0;
|
|
@@ -190,6 +2373,22 @@ export class WebInspectorElement extends LitElement {
|
|
|
190
2373
|
private draggedDuringInteraction = false;
|
|
191
2374
|
private ignoreNextButtonClick = false;
|
|
192
2375
|
private selectedMenu: MenuKey = "ag-ui-events";
|
|
2376
|
+
private selectedThreadId: string | null = null;
|
|
2377
|
+
private threadListWidth = 290;
|
|
2378
|
+
private threadDividerResizing = false;
|
|
2379
|
+
private threadDividerPointerId = -1;
|
|
2380
|
+
private threadDividerStartX = 0;
|
|
2381
|
+
private threadDividerStartWidth = 0;
|
|
2382
|
+
private _threads: ɵThread[] = [];
|
|
2383
|
+
private _threadStoreSubscriptions: Map<string, () => void> = new Map();
|
|
2384
|
+
private _threadsByAgent: Map<string, ɵThread[]> = new Map();
|
|
2385
|
+
// Error from each agent's thread store (REST list rejection, Phoenix
|
|
2386
|
+
// subscribe failure, retry exhaustion). When non-empty for the active
|
|
2387
|
+
// selection, the threads view renders an error state instead of stale
|
|
2388
|
+
// data with no indication.
|
|
2389
|
+
private _threadsErrorByAgent: Map<string, Error> = new Map();
|
|
2390
|
+
// Thread stores created and owned by the inspector (keyed by agentId)
|
|
2391
|
+
private _ownedThreadStores: Map<string, ɵThreadStore> = new Map();
|
|
193
2392
|
private contextMenuOpen = false;
|
|
194
2393
|
private dockMode: DockMode = "floating";
|
|
195
2394
|
private previousBodyMargins: { left: string; bottom: string } | null = null;
|
|
@@ -203,16 +2402,29 @@ export class WebInspectorElement extends LitElement {
|
|
|
203
2402
|
private toolSignature = "";
|
|
204
2403
|
private eventFilterText = "";
|
|
205
2404
|
private eventTypeFilter: InspectorAgentEventType | "all" = "all";
|
|
2405
|
+
// Column widths for the AG-UI events table (agent, time, event-type; last col is auto)
|
|
2406
|
+
private evtColWidths = [100, 80, 150];
|
|
2407
|
+
private _evtColResize: {
|
|
2408
|
+
col: number;
|
|
2409
|
+
startX: number;
|
|
2410
|
+
startW: number;
|
|
2411
|
+
} | null = null;
|
|
2412
|
+
|
|
2413
|
+
private _threadsUnlocked = false;
|
|
2414
|
+
private _threadsUnlocking = false;
|
|
2415
|
+
private _threadsGateError: string | null = null;
|
|
2416
|
+
private _threadsGateCodeInvalid = false;
|
|
2417
|
+
private _threadsGateInvalidTimer: ReturnType<typeof setTimeout> | null = null;
|
|
2418
|
+
private _threadsUnlockingTimer: ReturnType<typeof setTimeout> | null = null;
|
|
206
2419
|
|
|
207
|
-
private announcementMarkdown: string | null = null;
|
|
208
2420
|
private announcementHtml: string | null = null;
|
|
209
2421
|
private announcementTimestamp: string | null = null;
|
|
210
2422
|
private announcementPreviewText: string | null = null;
|
|
211
2423
|
private hasUnseenAnnouncement = false;
|
|
212
2424
|
private announcementLoaded = false;
|
|
213
|
-
private announcementLoadError: unknown = null;
|
|
214
2425
|
private announcementPromise: Promise<void> | null = null;
|
|
215
2426
|
private showAnnouncementPreview = true;
|
|
2427
|
+
private announcementExpanded = false;
|
|
216
2428
|
|
|
217
2429
|
get core(): CopilotKitCore | null {
|
|
218
2430
|
return this._core;
|
|
@@ -259,12 +2471,140 @@ export class WebInspectorElement extends LitElement {
|
|
|
259
2471
|
private resizeInitialSize: { width: number; height: number } | null = null;
|
|
260
2472
|
private isResizing = false;
|
|
261
2473
|
|
|
262
|
-
private readonly
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
2474
|
+
private readonly customTabIcons: Record<string, string> = {
|
|
2475
|
+
threads: `<svg class="h-3.5 w-3.5" width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M9.04167 15C8.29167 15 7.65972 14.7431 7.14583 14.2292C6.63194 13.7153 6.375 13.0972 6.375 12.375C6.375 11.3194 6.80208 10.3646 7.65625 9.51042C8.51042 8.65625 9.57639 8.125 10.8542 7.91667C10.8125 7.41667 10.6875 7.03819 10.4792 6.78125C10.2708 6.52431 9.98611 6.39583 9.625 6.39583C9.20833 6.39583 8.75694 6.56944 8.27083 6.91667C7.78472 7.26389 7.20833 7.83333 6.54167 8.625C5.45833 9.91667 4.66319 10.7569 4.15625 11.1458C3.64931 11.5347 3.10417 11.7292 2.52083 11.7292C1.8125 11.7292 1.21528 11.4653 0.729167 10.9375C0.243056 10.4097 0 9.77083 0 9.02083C0 8.27083 0.163194 7.50347 0.489583 6.71875C0.815972 5.93403 1.36806 4.99306 2.14583 3.89583C2.40972 3.53472 2.60417 3.22917 2.72917 2.97917C2.85417 2.72917 2.91667 2.52778 2.91667 2.375C2.91667 2.27778 2.89931 2.20486 2.86458 2.15625C2.82986 2.10764 2.77778 2.08333 2.70833 2.08333C2.56944 2.08333 2.39583 2.17014 2.1875 2.34375C1.97917 2.51736 1.73611 2.78472 1.45833 3.14583L0 1.66667C0.444444 1.125 0.895833 0.711806 1.35417 0.427083C1.8125 0.142361 2.26389 0 2.70833 0C3.34722 0 3.88889 0.222222 4.33333 0.666667C4.77778 1.11111 5 1.66667 5 2.33333C5 2.73611 4.89583 3.18056 4.6875 3.66667C4.47917 4.15278 4.13194 4.73611 3.64583 5.41667C3.11806 6.16667 2.72569 6.82639 2.46875 7.39583C2.21181 7.96528 2.08333 8.46528 2.08333 8.89583C2.08333 9.13194 2.12153 9.31597 2.19792 9.44792C2.27431 9.57986 2.38194 9.64583 2.52083 9.64583C2.65972 9.64583 2.78125 9.60764 2.88542 9.53125C2.98958 9.45486 3.18056 9.27083 3.45833 8.97917C3.63889 8.78472 3.85417 8.54514 4.10417 8.26042C4.35417 7.97569 4.65972 7.625 5.02083 7.20833C5.89583 6.16667 6.6875 5.42361 7.39583 4.97917C8.10417 4.53472 8.84722 4.3125 9.625 4.3125C10.5556 4.3125 11.3194 4.625 11.9167 5.25C12.5139 5.875 12.8542 6.72917 12.9375 7.8125H15V9.89583H12.9375C12.8264 11.4514 12.4201 12.691 11.7188 13.6146C11.0174 14.5382 10.125 15 9.04167 15ZM9.08333 12.9167C9.52778 12.9167 9.90278 12.6632 10.2083 12.1562C10.5139 11.6493 10.7222 10.9444 10.8333 10.0417C10.1944 10.1944 9.63889 10.4965 9.16667 10.9479C8.69444 11.3993 8.45833 11.8472 8.45833 12.2917C8.45833 12.4861 8.51389 12.6389 8.625 12.75C8.73611 12.8611 8.88889 12.9167 9.08333 12.9167Z" fill="currentColor"/></svg>`,
|
|
2476
|
+
};
|
|
2477
|
+
|
|
2478
|
+
private get menuItems(): MenuItem[] {
|
|
2479
|
+
const hasFrontendTools = (this._core?.tools?.length ?? 0) > 0;
|
|
2480
|
+
return [
|
|
2481
|
+
{
|
|
2482
|
+
key: "ag-ui-events",
|
|
2483
|
+
label: "AG-UI Events",
|
|
2484
|
+
icon: "Zap" as LucideIconName,
|
|
2485
|
+
},
|
|
2486
|
+
{ key: "agents", label: "Agent", icon: "Bot" as LucideIconName },
|
|
2487
|
+
...(hasFrontendTools
|
|
2488
|
+
? [
|
|
2489
|
+
{
|
|
2490
|
+
key: "frontend-tools" as const,
|
|
2491
|
+
label: "Frontend Tools",
|
|
2492
|
+
icon: "Hammer" as LucideIconName,
|
|
2493
|
+
},
|
|
2494
|
+
]
|
|
2495
|
+
: []),
|
|
2496
|
+
{
|
|
2497
|
+
key: "agent-context",
|
|
2498
|
+
label: "Context",
|
|
2499
|
+
icon: "FileText" as LucideIconName,
|
|
2500
|
+
},
|
|
2501
|
+
{
|
|
2502
|
+
key: "threads",
|
|
2503
|
+
label: "Threads",
|
|
2504
|
+
icon: "MessageSquare" as LucideIconName,
|
|
2505
|
+
},
|
|
2506
|
+
];
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2509
|
+
private subscribeToThreadStore(agentId: string, store: ɵThreadStore): void {
|
|
2510
|
+
if (this._threadStoreSubscriptions.has(agentId)) return;
|
|
2511
|
+
const threadsSub = store.select(ɵselectThreads).subscribe((threads) => {
|
|
2512
|
+
this._threadsByAgent.set(agentId, threads as ɵThread[]);
|
|
2513
|
+
this._threads = Array.from(this._threadsByAgent.values()).flat();
|
|
2514
|
+
this.autoSelectLatestThread();
|
|
2515
|
+
this.requestUpdate();
|
|
2516
|
+
});
|
|
2517
|
+
const errorSub = store.select(ɵselectThreadsError).subscribe((error) => {
|
|
2518
|
+
if (error) {
|
|
2519
|
+
this._threadsErrorByAgent.set(agentId, error);
|
|
2520
|
+
} else {
|
|
2521
|
+
this._threadsErrorByAgent.delete(agentId);
|
|
2522
|
+
}
|
|
2523
|
+
this.requestUpdate();
|
|
2524
|
+
});
|
|
2525
|
+
this._threadStoreSubscriptions.set(agentId, () => {
|
|
2526
|
+
threadsSub.unsubscribe();
|
|
2527
|
+
errorSub.unsubscribe();
|
|
2528
|
+
});
|
|
2529
|
+
// Populate immediately from current state
|
|
2530
|
+
const initialState = store.getState();
|
|
2531
|
+
this._threadsByAgent.set(agentId, ɵselectThreads(initialState));
|
|
2532
|
+
const initialError = ɵselectThreadsError(initialState);
|
|
2533
|
+
if (initialError) {
|
|
2534
|
+
this._threadsErrorByAgent.set(agentId, initialError);
|
|
2535
|
+
} else {
|
|
2536
|
+
this._threadsErrorByAgent.delete(agentId);
|
|
2537
|
+
}
|
|
2538
|
+
this._threads = Array.from(this._threadsByAgent.values()).flat();
|
|
2539
|
+
this.autoSelectLatestThread();
|
|
2540
|
+
}
|
|
2541
|
+
|
|
2542
|
+
private autoSelectLatestThread(): void {
|
|
2543
|
+
if (this._threads.length === 0) return;
|
|
2544
|
+
const stillValid =
|
|
2545
|
+
this.selectedThreadId != null &&
|
|
2546
|
+
this._threads.some((t) => t.id === this.selectedThreadId);
|
|
2547
|
+
if (!stillValid) {
|
|
2548
|
+
// Threads are sorted most-recently-updated first
|
|
2549
|
+
this.selectedThreadId = this._threads[0]!.id;
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
|
|
2553
|
+
private teardownThreadStoreSubscriptions(): void {
|
|
2554
|
+
for (const unsub of this._threadStoreSubscriptions.values()) {
|
|
2555
|
+
unsub();
|
|
2556
|
+
}
|
|
2557
|
+
this._threadStoreSubscriptions.clear();
|
|
2558
|
+
this._threadsByAgent.clear();
|
|
2559
|
+
this._threadsErrorByAgent.clear();
|
|
2560
|
+
this._threads = [];
|
|
2561
|
+
}
|
|
2562
|
+
|
|
2563
|
+
private ensureOwnedThreadStore(agentId: string): void {
|
|
2564
|
+
if (this._ownedThreadStores.has(agentId)) return;
|
|
2565
|
+
// Don't overwrite a store already registered by useThreads() or another external caller
|
|
2566
|
+
if (this.core?.getThreadStore(agentId)) return;
|
|
2567
|
+
const core = this.core;
|
|
2568
|
+
if (!core?.runtimeUrl) return;
|
|
2569
|
+
|
|
2570
|
+
const store = ɵcreateThreadStore({ fetch: globalThis.fetch });
|
|
2571
|
+
store.start();
|
|
2572
|
+
store.setContext({
|
|
2573
|
+
runtimeUrl: core.runtimeUrl,
|
|
2574
|
+
headers: {},
|
|
2575
|
+
agentId,
|
|
2576
|
+
});
|
|
2577
|
+
this._ownedThreadStores.set(agentId, store);
|
|
2578
|
+
// Subscribe directly so threads render even before the registry callback
|
|
2579
|
+
// fires (some published-core code paths land on the subscriber after
|
|
2580
|
+
// registerThreadStore returns).
|
|
2581
|
+
this.subscribeToThreadStore(agentId, store);
|
|
2582
|
+
core.registerThreadStore(agentId, store);
|
|
2583
|
+
}
|
|
2584
|
+
|
|
2585
|
+
private refreshOwnedThreadStore(agentId: string): void {
|
|
2586
|
+
const store = this._ownedThreadStores.get(agentId);
|
|
2587
|
+
if (!store) return;
|
|
2588
|
+
// refresh() re-fetches without resetting threads to [] first, so the list
|
|
2589
|
+
// stays visible while new data loads and survives transient fetch failures.
|
|
2590
|
+
store.refresh();
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2593
|
+
private removeOwnedThreadStore(agentId: string): void {
|
|
2594
|
+
const store = this._ownedThreadStores.get(agentId);
|
|
2595
|
+
if (!store) return;
|
|
2596
|
+
store.stop();
|
|
2597
|
+
this.core?.unregisterThreadStore(agentId);
|
|
2598
|
+
this._ownedThreadStores.delete(agentId);
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
private teardownOwnedThreadStores(): void {
|
|
2602
|
+
for (const [agentId, store] of this._ownedThreadStores) {
|
|
2603
|
+
store.stop();
|
|
2604
|
+
this.core?.unregisterThreadStore(agentId);
|
|
2605
|
+
}
|
|
2606
|
+
this._ownedThreadStores.clear();
|
|
2607
|
+
}
|
|
268
2608
|
|
|
269
2609
|
private attachToCore(core: CopilotKitCore): void {
|
|
270
2610
|
this.runtimeStatus = core.runtimeConnectionStatus;
|
|
@@ -274,6 +2614,15 @@ export class WebInspectorElement extends LitElement {
|
|
|
274
2614
|
this.coreSubscriber = {
|
|
275
2615
|
onRuntimeConnectionStatusChanged: ({ status }) => {
|
|
276
2616
|
this.runtimeStatus = status;
|
|
2617
|
+
if (status === "connected") {
|
|
2618
|
+
for (const agentId of this._ownedThreadStores.keys()) {
|
|
2619
|
+
this.refreshOwnedThreadStore(agentId);
|
|
2620
|
+
}
|
|
2621
|
+
} else {
|
|
2622
|
+
// Clear stale thread data immediately when the server goes away
|
|
2623
|
+
this._threadsByAgent.clear();
|
|
2624
|
+
this._threads = [];
|
|
2625
|
+
}
|
|
277
2626
|
this.requestUpdate();
|
|
278
2627
|
},
|
|
279
2628
|
onPropertiesChanged: ({ properties }) => {
|
|
@@ -287,23 +2636,52 @@ export class WebInspectorElement extends LitElement {
|
|
|
287
2636
|
onAgentsChanged: ({ agents }) => {
|
|
288
2637
|
this.processAgentsChanged(agents);
|
|
289
2638
|
},
|
|
290
|
-
onAgentRunStarted: ({ agent }) => {
|
|
291
|
-
// Per-thread clones are not in the agent registry, so
|
|
292
|
-
// onAgentsChanged never fires for them. Subscribe here so
|
|
293
|
-
// the inspector captures their AG-UI events.
|
|
294
|
-
if (agent?.agentId) {
|
|
295
|
-
this.subscribeToAgent(agent);
|
|
296
|
-
}
|
|
297
|
-
},
|
|
298
2639
|
onContextChanged: ({ context }) => {
|
|
299
2640
|
this.contextStore = this.normalizeContextStore(context);
|
|
300
2641
|
this.requestUpdate();
|
|
301
2642
|
},
|
|
2643
|
+
onThreadStoreRegistered: ({ agentId, store }) => {
|
|
2644
|
+
this.subscribeToThreadStore(agentId, store);
|
|
2645
|
+
this.requestUpdate();
|
|
2646
|
+
},
|
|
2647
|
+
onThreadStoreUnregistered: ({ agentId }) => {
|
|
2648
|
+
const unsub = this._threadStoreSubscriptions.get(agentId);
|
|
2649
|
+
if (unsub) {
|
|
2650
|
+
unsub();
|
|
2651
|
+
this._threadStoreSubscriptions.delete(agentId);
|
|
2652
|
+
}
|
|
2653
|
+
this._threadsByAgent.delete(agentId);
|
|
2654
|
+
this._threadsErrorByAgent.delete(agentId);
|
|
2655
|
+
this._threads = Array.from(this._threadsByAgent.values()).flat();
|
|
2656
|
+
this.requestUpdate();
|
|
2657
|
+
},
|
|
2658
|
+
onAgentRunStarted: ({ agent }) => {
|
|
2659
|
+
// Subscribe to the concrete agent instance about to run. This handles
|
|
2660
|
+
// per-thread clones that are not in core.agents and therefore not
|
|
2661
|
+
// reachable via onAgentsChanged. Replacing an existing subscription for
|
|
2662
|
+
// the same agentId is safe: the previous instance emits no more events
|
|
2663
|
+
// once a new run starts on a fresh clone.
|
|
2664
|
+
this.subscribeToAgent(agent);
|
|
2665
|
+
const runThreadId = (agent as { threadId?: string }).threadId;
|
|
2666
|
+
if (agent.agentId && runThreadId) {
|
|
2667
|
+
this.agentRunThreadId.set(agent.agentId, runThreadId);
|
|
2668
|
+
}
|
|
2669
|
+
this.requestUpdate();
|
|
2670
|
+
},
|
|
302
2671
|
} satisfies CopilotKitCoreSubscriber;
|
|
303
2672
|
|
|
304
2673
|
this.coreUnsubscribe = core.subscribe(this.coreSubscriber).unsubscribe;
|
|
305
2674
|
this.processAgentsChanged(core.agents);
|
|
306
2675
|
|
|
2676
|
+
// Subscribe to any already-registered thread stores. `getThreadStores` was
|
|
2677
|
+
// added in the same release as this inspector; guard so consumers still on
|
|
2678
|
+
// an older @copilotkit/core don't throw when assigning `inspector.core`.
|
|
2679
|
+
const threadStores =
|
|
2680
|
+
typeof core.getThreadStores === "function" ? core.getThreadStores() : {};
|
|
2681
|
+
for (const [agentId, store] of Object.entries(threadStores)) {
|
|
2682
|
+
this.subscribeToThreadStore(agentId, store);
|
|
2683
|
+
}
|
|
2684
|
+
|
|
307
2685
|
// Initialize context from core
|
|
308
2686
|
if (core.context) {
|
|
309
2687
|
this.contextStore = this.normalizeContextStore(core.context);
|
|
@@ -322,6 +2700,8 @@ export class WebInspectorElement extends LitElement {
|
|
|
322
2700
|
this.cachedTools = [];
|
|
323
2701
|
this.toolSignature = "";
|
|
324
2702
|
this.teardownAgentSubscriptions();
|
|
2703
|
+
this.teardownThreadStoreSubscriptions();
|
|
2704
|
+
this.teardownOwnedThreadStores();
|
|
325
2705
|
}
|
|
326
2706
|
|
|
327
2707
|
private teardownAgentSubscriptions(): void {
|
|
@@ -347,6 +2727,7 @@ export class WebInspectorElement extends LitElement {
|
|
|
347
2727
|
}
|
|
348
2728
|
seenAgentIds.add(agent.agentId);
|
|
349
2729
|
this.subscribeToAgent(agent);
|
|
2730
|
+
this.ensureOwnedThreadStore(agent.agentId);
|
|
350
2731
|
}
|
|
351
2732
|
|
|
352
2733
|
for (const agentId of Array.from(this.agentSubscriptions.keys())) {
|
|
@@ -355,6 +2736,10 @@ export class WebInspectorElement extends LitElement {
|
|
|
355
2736
|
this.agentEvents.delete(agentId);
|
|
356
2737
|
this.agentMessages.delete(agentId);
|
|
357
2738
|
this.agentStates.delete(agentId);
|
|
2739
|
+
// Do NOT remove owned thread stores here — they are independent of
|
|
2740
|
+
// whether the agent appears in core.agents (published cores discover
|
|
2741
|
+
// agents asynchronously so agents may be empty on first fire). Stores
|
|
2742
|
+
// are torn down in teardownOwnedThreadStores() when the core detaches.
|
|
358
2743
|
}
|
|
359
2744
|
}
|
|
360
2745
|
|
|
@@ -436,6 +2821,7 @@ export class WebInspectorElement extends LitElement {
|
|
|
436
2821
|
},
|
|
437
2822
|
onRunFinishedEvent: ({ event, result }) => {
|
|
438
2823
|
this.recordAgentEvent(agentId, "RUN_FINISHED", { event, result });
|
|
2824
|
+
this.refreshOwnedThreadStore(agentId);
|
|
439
2825
|
},
|
|
440
2826
|
onRunErrorEvent: ({ event }) => {
|
|
441
2827
|
this.recordAgentEvent(agentId, "RUN_ERROR", event);
|
|
@@ -529,6 +2915,14 @@ export class WebInspectorElement extends LitElement {
|
|
|
529
2915
|
onReasoningEncryptedValueEvent: ({ event }) => {
|
|
530
2916
|
this.recordAgentEvent(agentId, "REASONING_ENCRYPTED_VALUE", event);
|
|
531
2917
|
},
|
|
2918
|
+
onActivitySnapshotEvent: ({ event }) => {
|
|
2919
|
+
this.recordAgentEvent(agentId, "ACTIVITY_SNAPSHOT", event);
|
|
2920
|
+
this.syncAgentMessages(agent);
|
|
2921
|
+
},
|
|
2922
|
+
onActivityDeltaEvent: ({ event }) => {
|
|
2923
|
+
this.recordAgentEvent(agentId, "ACTIVITY_DELTA", event);
|
|
2924
|
+
this.syncAgentMessages(agent);
|
|
2925
|
+
},
|
|
532
2926
|
};
|
|
533
2927
|
|
|
534
2928
|
const { unsubscribe } = agent.subscribe(subscriber);
|
|
@@ -549,6 +2943,32 @@ export class WebInspectorElement extends LitElement {
|
|
|
549
2943
|
}
|
|
550
2944
|
}
|
|
551
2945
|
|
|
2946
|
+
private mapMessagesToConversation(
|
|
2947
|
+
messages: InspectorMessage[] | null,
|
|
2948
|
+
): { id: string; type: string; content: string; createdAt: string }[] | null {
|
|
2949
|
+
if (!messages) return null;
|
|
2950
|
+
return messages
|
|
2951
|
+
.filter(
|
|
2952
|
+
(m) =>
|
|
2953
|
+
m.role === "user" || m.role === "assistant" || m.role === "activity",
|
|
2954
|
+
)
|
|
2955
|
+
.map((m, i) => ({
|
|
2956
|
+
id: m.id ?? `msg-${i}`,
|
|
2957
|
+
type:
|
|
2958
|
+
m.role === "user"
|
|
2959
|
+
? "user"
|
|
2960
|
+
: m.role === "activity"
|
|
2961
|
+
? "generative-ui"
|
|
2962
|
+
: "assistant",
|
|
2963
|
+
// For activity messages, store the activityType as a label so the
|
|
2964
|
+
// renderer has something meaningful to display.
|
|
2965
|
+
// TODO: render activity payload once available.
|
|
2966
|
+
content:
|
|
2967
|
+
m.role === "activity" ? (m.activityType ?? "unknown") : m.contentText,
|
|
2968
|
+
createdAt: "",
|
|
2969
|
+
}));
|
|
2970
|
+
}
|
|
2971
|
+
|
|
552
2972
|
private recordAgentEvent(
|
|
553
2973
|
agentId: string,
|
|
554
2974
|
type: InspectorAgentEventType,
|
|
@@ -594,6 +3014,19 @@ export class WebInspectorElement extends LitElement {
|
|
|
594
3014
|
this.agentMessages.delete(agent.agentId);
|
|
595
3015
|
}
|
|
596
3016
|
|
|
3017
|
+
// Bump the live-message version for whichever thread this agent is
|
|
3018
|
+
// currently running on. cpk-thread-details watches this for the
|
|
3019
|
+
// selected thread and re-fetches `/threads/:id/messages` when it ticks,
|
|
3020
|
+
// so the conversation view stays in sync with the streaming agent
|
|
3021
|
+
// without the parent re-implementing AG-UI → ConversationItem mapping.
|
|
3022
|
+
const runThreadId = this.agentRunThreadId.get(agent.agentId);
|
|
3023
|
+
if (runThreadId) {
|
|
3024
|
+
this.liveMessageVersion.set(
|
|
3025
|
+
runThreadId,
|
|
3026
|
+
(this.liveMessageVersion.get(runThreadId) ?? 0) + 1,
|
|
3027
|
+
);
|
|
3028
|
+
}
|
|
3029
|
+
|
|
597
3030
|
this.requestUpdate();
|
|
598
3031
|
} catch (error) {
|
|
599
3032
|
console.error(
|
|
@@ -648,14 +3081,25 @@ export class WebInspectorElement extends LitElement {
|
|
|
648
3081
|
if (pendingContext) {
|
|
649
3082
|
const isPendingAvailable =
|
|
650
3083
|
pendingContext === "all-agents" || agentIds.has(pendingContext);
|
|
651
|
-
|
|
3084
|
+
// Only restore a specific-agent selection when there is exactly one
|
|
3085
|
+
// agent registered. With multiple agents, fall back to "all-agents" so
|
|
3086
|
+
// events from any agent are visible regardless of what was persisted.
|
|
3087
|
+
const shouldRestore =
|
|
3088
|
+
isPendingAvailable &&
|
|
3089
|
+
(pendingContext === "all-agents" || agentIds.size === 1);
|
|
3090
|
+
if (shouldRestore) {
|
|
652
3091
|
if (this.selectedContext !== pendingContext) {
|
|
653
3092
|
this.selectedContext = pendingContext;
|
|
654
3093
|
this.expandedRows.clear();
|
|
655
3094
|
}
|
|
656
3095
|
this.pendingSelectedContext = null;
|
|
657
3096
|
} else if (agentIds.size > 0) {
|
|
658
|
-
//
|
|
3097
|
+
// Persisted selection is unavailable or inappropriate for multiple
|
|
3098
|
+
// agents — reset to "all-agents" so nothing is silently filtered.
|
|
3099
|
+
if (this.selectedContext !== "all-agents") {
|
|
3100
|
+
this.selectedContext = "all-agents";
|
|
3101
|
+
this.expandedRows.clear();
|
|
3102
|
+
}
|
|
659
3103
|
this.pendingSelectedContext = null;
|
|
660
3104
|
}
|
|
661
3105
|
}
|
|
@@ -665,15 +3109,13 @@ export class WebInspectorElement extends LitElement {
|
|
|
665
3109
|
);
|
|
666
3110
|
|
|
667
3111
|
if (!hasSelectedContext && this.pendingSelectedContext === null) {
|
|
668
|
-
//
|
|
3112
|
+
// When there is exactly one agent, auto-select it so the view is
|
|
3113
|
+
// immediately focused. When multiple agents are registered (e.g. "default"
|
|
3114
|
+
// + "openai"), keep "all-agents" so events from any agent are visible.
|
|
669
3115
|
let nextSelected: string = "all-agents";
|
|
670
3116
|
|
|
671
|
-
if (agentIds.
|
|
672
|
-
nextSelected =
|
|
673
|
-
} else if (agentIds.size > 0) {
|
|
674
|
-
nextSelected = Array.from(agentIds).sort((a, b) =>
|
|
675
|
-
a.localeCompare(b),
|
|
676
|
-
)[0]!;
|
|
3117
|
+
if (agentIds.size === 1) {
|
|
3118
|
+
nextSelected = Array.from(agentIds)[0]!;
|
|
677
3119
|
}
|
|
678
3120
|
|
|
679
3121
|
if (this.selectedContext !== nextSelected) {
|
|
@@ -1003,6 +3445,7 @@ ${argsString}</pre
|
|
|
1003
3445
|
z-index: 2147483646;
|
|
1004
3446
|
display: block;
|
|
1005
3447
|
will-change: transform;
|
|
3448
|
+
font-family: "Plus Jakarta Sans", system-ui, sans-serif;
|
|
1006
3449
|
}
|
|
1007
3450
|
|
|
1008
3451
|
:host([data-transitioning="true"]) {
|
|
@@ -1058,13 +3501,14 @@ ${argsString}</pre
|
|
|
1058
3501
|
left: 50%;
|
|
1059
3502
|
transform: translateX(-50%) translateY(-4px);
|
|
1060
3503
|
white-space: nowrap;
|
|
1061
|
-
background: rgba(
|
|
3504
|
+
background: rgba(1, 5, 7, 0.95);
|
|
1062
3505
|
color: white;
|
|
1063
3506
|
padding: 4px 8px;
|
|
1064
3507
|
border-radius: 6px;
|
|
1065
3508
|
font-size: 10px;
|
|
3509
|
+
font-family: "Plus Jakarta Sans", system-ui, sans-serif;
|
|
1066
3510
|
line-height: 1.2;
|
|
1067
|
-
box-shadow: 0 4px 10px rgba(
|
|
3511
|
+
box-shadow: 0 4px 10px rgba(1, 5, 7, 0.18);
|
|
1068
3512
|
opacity: 0;
|
|
1069
3513
|
pointer-events: none;
|
|
1070
3514
|
transition:
|
|
@@ -1085,18 +3529,19 @@ ${argsString}</pre
|
|
|
1085
3529
|
min-width: 300px;
|
|
1086
3530
|
max-width: 300px;
|
|
1087
3531
|
background: white;
|
|
1088
|
-
color: #
|
|
3532
|
+
color: #010507;
|
|
1089
3533
|
font-size: 13px;
|
|
3534
|
+
font-family: "Plus Jakarta Sans", system-ui, sans-serif;
|
|
1090
3535
|
line-height: 1.4;
|
|
1091
3536
|
border-radius: 12px;
|
|
1092
|
-
box-shadow: 0 12px 28px rgba(
|
|
3537
|
+
box-shadow: 0 12px 28px rgba(1, 5, 7, 0.12);
|
|
1093
3538
|
padding: 10px 12px;
|
|
1094
3539
|
display: inline-flex;
|
|
1095
3540
|
align-items: flex-start;
|
|
1096
3541
|
gap: 8px;
|
|
1097
3542
|
z-index: 4500;
|
|
1098
3543
|
animation: fade-slide-in 160ms ease;
|
|
1099
|
-
border: 1px solid rgba(
|
|
3544
|
+
border: 1px solid rgba(219, 219, 229, 0.4);
|
|
1100
3545
|
white-space: normal;
|
|
1101
3546
|
word-break: break-word;
|
|
1102
3547
|
text-align: left;
|
|
@@ -1117,7 +3562,7 @@ ${argsString}</pre
|
|
|
1117
3562
|
width: 10px;
|
|
1118
3563
|
height: 10px;
|
|
1119
3564
|
background: white;
|
|
1120
|
-
border: 1px solid rgba(
|
|
3565
|
+
border: 1px solid rgba(219, 219, 229, 0.4);
|
|
1121
3566
|
transform: rotate(45deg);
|
|
1122
3567
|
top: 50%;
|
|
1123
3568
|
margin-top: -5px;
|
|
@@ -1126,75 +3571,449 @@ ${argsString}</pre
|
|
|
1126
3571
|
|
|
1127
3572
|
.announcement-preview[data-side="left"] .announcement-preview__arrow {
|
|
1128
3573
|
right: -5px;
|
|
1129
|
-
box-shadow: 6px -6px 10px rgba(
|
|
3574
|
+
box-shadow: 6px -6px 10px rgba(1, 5, 7, 0.08);
|
|
1130
3575
|
}
|
|
1131
3576
|
|
|
1132
3577
|
.announcement-preview[data-side="right"] .announcement-preview__arrow {
|
|
1133
3578
|
left: -5px;
|
|
1134
|
-
box-shadow: -6px 6px 10px rgba(
|
|
3579
|
+
box-shadow: -6px 6px 10px rgba(1, 5, 7, 0.08);
|
|
1135
3580
|
}
|
|
1136
3581
|
|
|
1137
3582
|
.announcement-dismiss {
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
3583
|
+
background: none;
|
|
3584
|
+
border: none;
|
|
3585
|
+
cursor: pointer;
|
|
3586
|
+
color: #838389;
|
|
3587
|
+
width: 28px;
|
|
3588
|
+
height: 28px;
|
|
3589
|
+
display: flex;
|
|
3590
|
+
align-items: center;
|
|
3591
|
+
justify-content: center;
|
|
3592
|
+
border-radius: 6px;
|
|
3593
|
+
padding: 0;
|
|
1144
3594
|
transition:
|
|
1145
3595
|
background 120ms ease,
|
|
1146
3596
|
color 120ms ease;
|
|
1147
3597
|
}
|
|
1148
|
-
|
|
1149
|
-
.announcement-dismiss:hover {
|
|
1150
|
-
background: rgba(
|
|
1151
|
-
color: #
|
|
3598
|
+
|
|
3599
|
+
.announcement-dismiss:hover {
|
|
3600
|
+
background: rgba(0, 0, 0, 0.06);
|
|
3601
|
+
color: #010507;
|
|
3602
|
+
}
|
|
3603
|
+
|
|
3604
|
+
/* ── Agent tab section cards ─────────────────────────────────────── */
|
|
3605
|
+
.cpk-section-card {
|
|
3606
|
+
border-radius: 8px;
|
|
3607
|
+
background: #ffffff;
|
|
3608
|
+
overflow: hidden;
|
|
3609
|
+
}
|
|
3610
|
+
|
|
3611
|
+
/* ── Agent icon bubble ───────────────────────────────────────────── */
|
|
3612
|
+
.cpk-agent-icon {
|
|
3613
|
+
background-color: #f0f0f4 !important;
|
|
3614
|
+
color: #57575b !important;
|
|
3615
|
+
}
|
|
3616
|
+
|
|
3617
|
+
/* ── Agent stat cards ────────────────────────────────────────────── */
|
|
3618
|
+
.cpk-stat-card {
|
|
3619
|
+
background-color: #ffffff !important;
|
|
3620
|
+
border: 1px solid #dbdbe5 !important;
|
|
3621
|
+
}
|
|
3622
|
+
button.cpk-stat-card:hover {
|
|
3623
|
+
background-color: #f7f7f9 !important;
|
|
3624
|
+
}
|
|
3625
|
+
|
|
3626
|
+
/* ── Circle chevron (Frontend Tools + Context) ──────────────────── */
|
|
3627
|
+
.cpk-chevron-circle {
|
|
3628
|
+
display: inline-flex;
|
|
3629
|
+
align-items: center;
|
|
3630
|
+
justify-content: center;
|
|
3631
|
+
width: 24px;
|
|
3632
|
+
height: 24px;
|
|
3633
|
+
border-radius: 50%;
|
|
3634
|
+
background-color: #f0f0f4;
|
|
3635
|
+
color: #838389;
|
|
3636
|
+
flex-shrink: 0;
|
|
3637
|
+
transition: transform 0.2s;
|
|
3638
|
+
}
|
|
3639
|
+
.cpk-chevron-circle svg {
|
|
3640
|
+
width: 14px !important;
|
|
3641
|
+
height: 14px !important;
|
|
3642
|
+
}
|
|
3643
|
+
.cpk-chevron-circle--open {
|
|
3644
|
+
transform: rotate(180deg);
|
|
3645
|
+
}
|
|
3646
|
+
|
|
3647
|
+
/* ── Inline copy button ─────────────────────────────────────────── */
|
|
3648
|
+
.cpk-copy-btn {
|
|
3649
|
+
font-size: 10px;
|
|
3650
|
+
font-weight: 500;
|
|
3651
|
+
color: #57575b;
|
|
3652
|
+
background: #ffffff;
|
|
3653
|
+
border: 1px solid #dbdbe5;
|
|
3654
|
+
cursor: pointer;
|
|
3655
|
+
padding: 2px 8px;
|
|
3656
|
+
border-radius: 4px;
|
|
3657
|
+
flex-shrink: 0;
|
|
3658
|
+
transition:
|
|
3659
|
+
background-color 0.15s,
|
|
3660
|
+
border-color 0.15s;
|
|
3661
|
+
}
|
|
3662
|
+
.cpk-copy-btn:hover {
|
|
3663
|
+
background-color: #f0f0f4;
|
|
3664
|
+
border-color: #afafb7;
|
|
3665
|
+
}
|
|
3666
|
+
|
|
3667
|
+
.cpk-section-header {
|
|
3668
|
+
background: #e8edf5;
|
|
3669
|
+
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
|
3670
|
+
padding: 10px 16px;
|
|
3671
|
+
}
|
|
3672
|
+
.cpk-section-header h4 {
|
|
3673
|
+
font-size: 11px;
|
|
3674
|
+
font-weight: 600;
|
|
3675
|
+
color: #181c1f;
|
|
3676
|
+
margin: 0;
|
|
3677
|
+
}
|
|
3678
|
+
|
|
3679
|
+
/* Inputs/selects inside the lavender header need an explicit white bg */
|
|
3680
|
+
.cpk-section-header input,
|
|
3681
|
+
.cpk-section-header select {
|
|
3682
|
+
background-color: #ffffff !important;
|
|
3683
|
+
box-shadow: none !important;
|
|
3684
|
+
}
|
|
3685
|
+
.cpk-section-header select {
|
|
3686
|
+
padding-right: 24px !important;
|
|
3687
|
+
}
|
|
3688
|
+
/* Events table column headers */
|
|
3689
|
+
table thead th {
|
|
3690
|
+
font-weight: 600 !important;
|
|
3691
|
+
}
|
|
3692
|
+
|
|
3693
|
+
.announcement-content {
|
|
3694
|
+
color: #010507;
|
|
3695
|
+
font-size: 12px;
|
|
3696
|
+
font-family: "Plus Jakarta Sans", system-ui, sans-serif;
|
|
3697
|
+
line-height: 1.5;
|
|
3698
|
+
}
|
|
3699
|
+
|
|
3700
|
+
.announcement-content h1,
|
|
3701
|
+
.announcement-content h2,
|
|
3702
|
+
.announcement-content h3 {
|
|
3703
|
+
font-weight: 700;
|
|
3704
|
+
margin: 0.4rem 0 0.2rem;
|
|
3705
|
+
}
|
|
3706
|
+
|
|
3707
|
+
.announcement-content h1 {
|
|
3708
|
+
font-size: 0.75rem;
|
|
3709
|
+
}
|
|
3710
|
+
.announcement-content h2 {
|
|
3711
|
+
font-size: 0.8rem;
|
|
3712
|
+
}
|
|
3713
|
+
.announcement-content h3 {
|
|
3714
|
+
font-size: 0.75rem;
|
|
3715
|
+
}
|
|
3716
|
+
|
|
3717
|
+
.announcement-content p {
|
|
3718
|
+
margin: 0.2rem 0;
|
|
3719
|
+
}
|
|
3720
|
+
|
|
3721
|
+
.announcement-content ul {
|
|
3722
|
+
list-style: disc;
|
|
3723
|
+
padding-left: 1.25rem;
|
|
3724
|
+
margin: 0.2rem 0;
|
|
3725
|
+
}
|
|
3726
|
+
|
|
3727
|
+
.announcement-content ol {
|
|
3728
|
+
list-style: decimal;
|
|
3729
|
+
padding-left: 1.25rem;
|
|
3730
|
+
margin: 0.2rem 0;
|
|
3731
|
+
}
|
|
3732
|
+
|
|
3733
|
+
.announcement-content a {
|
|
3734
|
+
color: #757cf2;
|
|
3735
|
+
text-decoration: underline;
|
|
3736
|
+
}
|
|
3737
|
+
|
|
3738
|
+
.announcement-body {
|
|
3739
|
+
position: relative;
|
|
3740
|
+
overflow: hidden;
|
|
3741
|
+
transition: max-height 0.25s ease;
|
|
3742
|
+
}
|
|
3743
|
+
.announcement-body--collapsed {
|
|
3744
|
+
max-height: 72px;
|
|
3745
|
+
}
|
|
3746
|
+
.announcement-body--expanded {
|
|
3747
|
+
max-height: 2000px;
|
|
3748
|
+
}
|
|
3749
|
+
.announcement-fade {
|
|
3750
|
+
position: absolute;
|
|
3751
|
+
bottom: 0;
|
|
3752
|
+
left: 0;
|
|
3753
|
+
right: 0;
|
|
3754
|
+
height: 48px;
|
|
3755
|
+
background: linear-gradient(to bottom, transparent, #ffffff);
|
|
3756
|
+
pointer-events: none;
|
|
3757
|
+
}
|
|
3758
|
+
.announcement-toggle {
|
|
3759
|
+
display: block;
|
|
3760
|
+
width: 100%;
|
|
3761
|
+
margin-top: 6px;
|
|
3762
|
+
padding: 0;
|
|
3763
|
+
background: none;
|
|
3764
|
+
border: none;
|
|
3765
|
+
font-family: "Plus Jakarta Sans", system-ui, sans-serif;
|
|
3766
|
+
font-size: 12px;
|
|
3767
|
+
font-weight: 500;
|
|
3768
|
+
color: #757cf2;
|
|
3769
|
+
cursor: pointer;
|
|
3770
|
+
text-align: center;
|
|
3771
|
+
}
|
|
3772
|
+
.announcement-toggle:hover {
|
|
3773
|
+
color: #6430ab;
|
|
3774
|
+
}
|
|
3775
|
+
|
|
3776
|
+
/* ── Brand typography ────────────────────────────────────────── */
|
|
3777
|
+
/* Override Tailwind font-mono stack → Spline Sans Mono */
|
|
3778
|
+
.font-mono,
|
|
3779
|
+
pre,
|
|
3780
|
+
code {
|
|
3781
|
+
font-family: "Spline Sans Mono", ui-monospace, "Cascadia Code", monospace;
|
|
3782
|
+
}
|
|
3783
|
+
|
|
3784
|
+
/* ── Floating button ─────────────────────────────────────────── */
|
|
3785
|
+
.console-button {
|
|
3786
|
+
background-color: rgba(1, 5, 7, 0.95) !important;
|
|
3787
|
+
border-color: rgba(190, 194, 255, 0.25) !important;
|
|
3788
|
+
box-shadow:
|
|
3789
|
+
0 0 0 1px rgba(190, 194, 255, 0.15),
|
|
3790
|
+
0 4px 14px rgba(1, 5, 7, 0.28) !important;
|
|
3791
|
+
}
|
|
3792
|
+
.console-button:hover {
|
|
3793
|
+
background-color: rgba(1, 5, 7, 1) !important;
|
|
3794
|
+
border-color: rgba(190, 194, 255, 0.45) !important;
|
|
3795
|
+
}
|
|
3796
|
+
.console-button:focus-visible {
|
|
3797
|
+
outline-color: #bec2ff !important;
|
|
3798
|
+
}
|
|
3799
|
+
|
|
3800
|
+
/* ── Inspector window ────────────────────────────────────────── */
|
|
3801
|
+
.inspector-window {
|
|
3802
|
+
border-color: #dbdbe5 !important;
|
|
3803
|
+
box-shadow:
|
|
3804
|
+
0 8px 32px rgba(1, 5, 7, 0.1),
|
|
3805
|
+
0 2px 8px rgba(1, 5, 7, 0.06) !important;
|
|
3806
|
+
}
|
|
3807
|
+
|
|
3808
|
+
/* ── Header drag area ────────────────────────────────────────── */
|
|
3809
|
+
.drag-handle {
|
|
3810
|
+
border-bottom-color: #dbdbe5 !important;
|
|
3811
|
+
/* Subtle pale lavender gradient — brand "light, spacious" surface */
|
|
3812
|
+
background: linear-gradient(180deg, #f4f4fd 0%, #ffffff 100%) !important;
|
|
3813
|
+
}
|
|
3814
|
+
|
|
3815
|
+
/* Tab strip row: soft off-white, separated from content */
|
|
3816
|
+
.drag-handle > div:last-child {
|
|
3817
|
+
border-top-color: #e2e2ea !important;
|
|
3818
|
+
background-color: #fafafc !important;
|
|
3819
|
+
}
|
|
3820
|
+
|
|
3821
|
+
/* ── Tab buttons ─────────────────────────────────────────────── */
|
|
3822
|
+
/*
|
|
3823
|
+
* Named classes owned by this component — no Tailwind conflict.
|
|
3824
|
+
* Active: brand surface/surfaceContainerActive (lilac tint) +
|
|
3825
|
+
* border/borderActionEnabled underline.
|
|
3826
|
+
* Dark fill is for primary action buttons only, not nav tabs.
|
|
3827
|
+
*/
|
|
3828
|
+
.cpk-tab-active {
|
|
3829
|
+
background-color: rgba(190, 194, 255, 0.18);
|
|
3830
|
+
color: #010507;
|
|
3831
|
+
font-weight: 600;
|
|
3832
|
+
}
|
|
3833
|
+
.cpk-tab-active .cpk-tab-icon {
|
|
3834
|
+
color: #757cf2;
|
|
3835
|
+
}
|
|
3836
|
+
.cpk-tab-inactive {
|
|
3837
|
+
background-color: transparent;
|
|
3838
|
+
color: #2b2b2b;
|
|
3839
|
+
}
|
|
3840
|
+
.cpk-tab-inactive .cpk-tab-icon {
|
|
3841
|
+
color: #838389;
|
|
3842
|
+
}
|
|
3843
|
+
.cpk-tab-inactive:hover {
|
|
3844
|
+
background-color: rgba(190, 194, 255, 0.08);
|
|
3845
|
+
color: #010507;
|
|
3846
|
+
cursor: pointer;
|
|
3847
|
+
}
|
|
3848
|
+
.cpk-tab-active {
|
|
3849
|
+
cursor: pointer;
|
|
3850
|
+
}
|
|
3851
|
+
|
|
3852
|
+
/* ── Header control buttons (dock, close) — first row only ───── */
|
|
3853
|
+
.drag-handle > div:first-child button {
|
|
3854
|
+
color: #838389 !important;
|
|
3855
|
+
}
|
|
3856
|
+
.drag-handle > div:first-child button:hover {
|
|
3857
|
+
background-color: #f0f0f4 !important;
|
|
3858
|
+
color: #57575b !important;
|
|
3859
|
+
}
|
|
3860
|
+
.drag-handle > div:first-child button:focus-visible {
|
|
3861
|
+
outline-color: #bec2ff !important;
|
|
3862
|
+
}
|
|
3863
|
+
|
|
3864
|
+
/* ── Agent/context dropdown ──────────────────────────────────── */
|
|
3865
|
+
[data-context-dropdown-root="true"] > button {
|
|
3866
|
+
border-color: #dbdbe5 !important;
|
|
3867
|
+
color: #010507 !important;
|
|
3868
|
+
}
|
|
3869
|
+
[data-context-dropdown-root="true"] > button:hover {
|
|
3870
|
+
border-color: #bec2ff !important;
|
|
3871
|
+
background-color: #f7f7f9 !important;
|
|
3872
|
+
}
|
|
3873
|
+
[data-context-dropdown-root="true"] > button > span:last-child {
|
|
3874
|
+
color: #838389 !important;
|
|
3875
|
+
}
|
|
3876
|
+
[data-context-dropdown-root="true"] > div {
|
|
3877
|
+
border-color: #dbdbe5 !important;
|
|
3878
|
+
box-shadow: 0 4px 12px rgba(1, 5, 7, 0.08) !important;
|
|
3879
|
+
}
|
|
3880
|
+
[data-context-dropdown-root="true"] > div button:hover,
|
|
3881
|
+
[data-context-dropdown-root="true"] > div button:focus {
|
|
3882
|
+
background-color: #f7f7f9 !important;
|
|
3883
|
+
}
|
|
3884
|
+
|
|
3885
|
+
/* ── Status bar (bottom chrome) ──────────────────────────────── */
|
|
3886
|
+
.inspector-window > div > div:last-child {
|
|
3887
|
+
border-top-color: #dbdbe5 !important;
|
|
3888
|
+
background-color: #f7f7f9 !important;
|
|
3889
|
+
}
|
|
3890
|
+
|
|
3891
|
+
/* ── Resize handle ───────────────────────────────────────────── */
|
|
3892
|
+
.resize-handle {
|
|
3893
|
+
color: #838389 !important;
|
|
3894
|
+
}
|
|
3895
|
+
.resize-handle:hover {
|
|
3896
|
+
color: #57575b !important;
|
|
3897
|
+
}
|
|
3898
|
+
|
|
3899
|
+
/* ── AG-UI Events tab ────────────────────────────────────────── */
|
|
3900
|
+
/* Row hover: replace blue tint with brand lilac */
|
|
3901
|
+
tr:hover td {
|
|
3902
|
+
background-color: rgba(190, 194, 255, 0.08) !important;
|
|
3903
|
+
}
|
|
3904
|
+
/* Reset/dark action button */
|
|
3905
|
+
button[class*="bg-gray-900"] {
|
|
3906
|
+
background-color: #010507 !important;
|
|
3907
|
+
}
|
|
3908
|
+
button[class*="bg-gray-800"] {
|
|
3909
|
+
background-color: #2b2b2b !important;
|
|
3910
|
+
}
|
|
3911
|
+
/* Copy "copied" state: generic green → brand mint */
|
|
3912
|
+
button[class*="bg-green-100"] {
|
|
3913
|
+
background-color: rgba(133, 236, 206, 0.2) !important;
|
|
3914
|
+
color: #189370 !important;
|
|
3915
|
+
}
|
|
3916
|
+
|
|
3917
|
+
/* ── Agents tab ──────────────────────────────────────────────── */
|
|
3918
|
+
/* Agent icon bubble: blue → lilac */
|
|
3919
|
+
span[class*="bg-blue-100"]:not([class*="text-blue-800"]) {
|
|
3920
|
+
background-color: rgba(190, 194, 255, 0.15) !important;
|
|
3921
|
+
}
|
|
3922
|
+
span[class*="text-blue-600"] {
|
|
3923
|
+
color: #757cf2 !important;
|
|
3924
|
+
}
|
|
3925
|
+
/* Running badge: emerald → mint */
|
|
3926
|
+
span[class*="bg-emerald-50"] {
|
|
3927
|
+
background-color: rgba(133, 236, 206, 0.15) !important;
|
|
3928
|
+
}
|
|
3929
|
+
span[class*="text-emerald-700"] {
|
|
3930
|
+
color: #189370 !important;
|
|
3931
|
+
}
|
|
3932
|
+
/* Running status dot */
|
|
3933
|
+
span[class*="bg-emerald-500"] {
|
|
3934
|
+
background-color: #85ecce !important;
|
|
3935
|
+
}
|
|
3936
|
+
/* Idle dot */
|
|
3937
|
+
span[class*="bg-gray-400"] {
|
|
3938
|
+
background-color: #afafb7 !important;
|
|
3939
|
+
}
|
|
3940
|
+
/* User role badge (blue → lilac) */
|
|
3941
|
+
span[class*="bg-blue-100"][class*="text-blue-800"] {
|
|
3942
|
+
background-color: rgba(190, 194, 255, 0.22) !important;
|
|
3943
|
+
border: 1px solid rgba(190, 194, 255, 0.45) !important;
|
|
3944
|
+
color: #57575b !important;
|
|
1152
3945
|
}
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
color:
|
|
1156
|
-
|
|
1157
|
-
|
|
3946
|
+
/* Assistant role badge (green → mint) */
|
|
3947
|
+
span[class*="bg-green-100"][class*="text-green-800"] {
|
|
3948
|
+
background-color: rgba(133, 236, 206, 0.18) !important;
|
|
3949
|
+
border: 1px solid rgba(133, 236, 206, 0.4) !important;
|
|
3950
|
+
color: #189370 !important;
|
|
1158
3951
|
}
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
font-weight: 700;
|
|
1164
|
-
margin: 0.4rem 0 0.2rem;
|
|
3952
|
+
/* Tool role badge (amber → orange brand) */
|
|
3953
|
+
span[class*="bg-amber-100"][class*="text-amber-800"] {
|
|
3954
|
+
background-color: rgba(255, 172, 77, 0.15) !important;
|
|
3955
|
+
color: #57575b !important;
|
|
1165
3956
|
}
|
|
1166
3957
|
|
|
1167
|
-
|
|
1168
|
-
|
|
3958
|
+
/* ── Frontend Tools tab ──────────────────────────────────────── */
|
|
3959
|
+
/* Handler badge (blue → lilac) */
|
|
3960
|
+
span[class*="bg-blue-50"][class*="text-blue-700"] {
|
|
3961
|
+
background-color: rgba(190, 194, 255, 0.12) !important;
|
|
3962
|
+
border-color: rgba(190, 194, 255, 0.3) !important;
|
|
3963
|
+
color: #010507 !important;
|
|
1169
3964
|
}
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
3965
|
+
/* Renderer badge (purple → lilac-adjacent) */
|
|
3966
|
+
span[class*="bg-purple-50"][class*="text-purple-700"] {
|
|
3967
|
+
background-color: rgba(190, 194, 255, 0.12) !important;
|
|
3968
|
+
border-color: rgba(190, 194, 255, 0.3) !important;
|
|
3969
|
+
color: #57575b !important;
|
|
1173
3970
|
}
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
3971
|
+
/* Required badge (rose → brand red) */
|
|
3972
|
+
span[class*="bg-rose-50"][class*="text-rose-700"] {
|
|
3973
|
+
background-color: rgba(250, 95, 103, 0.1) !important;
|
|
3974
|
+
border-color: rgba(250, 95, 103, 0.25) !important;
|
|
3975
|
+
color: #fa5f67 !important;
|
|
1177
3976
|
}
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
3977
|
+
/* Code/default value blocks */
|
|
3978
|
+
code[class*="bg-gray-100"],
|
|
3979
|
+
span[class*="bg-gray-100"] {
|
|
3980
|
+
background-color: #f0f0f4 !important;
|
|
1181
3981
|
}
|
|
1182
3982
|
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
3983
|
+
/* ── Connected status bar: match threads header mint (#5BE4BB) ──── */
|
|
3984
|
+
/* Outer strip bg + top border + text when connected badge is present */
|
|
3985
|
+
.inspector-window
|
|
3986
|
+
> div
|
|
3987
|
+
> div:last-child
|
|
3988
|
+
> div:last-child:has(div[class*="bg-emerald-50"]) {
|
|
3989
|
+
background-color: rgba(91, 228, 187, 0.08) !important;
|
|
3990
|
+
border-top-color: rgba(91, 228, 187, 0.3) !important;
|
|
3991
|
+
color: #189370 !important;
|
|
1187
3992
|
}
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
3993
|
+
/* Inner badge — slightly more opaque on the mint bg */
|
|
3994
|
+
div[class*="bg-emerald-50"][class*="border-emerald-200"] {
|
|
3995
|
+
background-color: rgba(91, 228, 187, 0.12) !important;
|
|
3996
|
+
border-color: rgba(91, 228, 187, 0.4) !important;
|
|
3997
|
+
color: #189370 !important;
|
|
3998
|
+
}
|
|
3999
|
+
/* Icon bubble inside connected badge → mint tint */
|
|
4000
|
+
div[class*="bg-emerald-50"] span[class*="bg-white"] {
|
|
4001
|
+
background-color: rgba(91, 228, 187, 0.3) !important;
|
|
1193
4002
|
}
|
|
1194
4003
|
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
4004
|
+
/* ── Announcement panel ──────────────────────────────────────── */
|
|
4005
|
+
div[class*="border-slate-200"][class*="bg-white"] {
|
|
4006
|
+
border-color: #dbdbe5 !important;
|
|
4007
|
+
}
|
|
4008
|
+
/* Announcement icon bubble: black → brand light lavender + lilac icon */
|
|
4009
|
+
span[class*="bg-slate-900"],
|
|
4010
|
+
div[class*="bg-slate-900"] {
|
|
4011
|
+
background-color: #eee6fe !important;
|
|
4012
|
+
color: #757cf2 !important;
|
|
4013
|
+
}
|
|
4014
|
+
span[class*="text-slate-800"],
|
|
4015
|
+
div[class*="text-slate-800"] {
|
|
4016
|
+
color: #010507 !important;
|
|
1198
4017
|
}
|
|
1199
4018
|
`,
|
|
1200
4019
|
];
|
|
@@ -1202,6 +4021,7 @@ ${argsString}</pre
|
|
|
1202
4021
|
connectedCallback(): void {
|
|
1203
4022
|
super.connectedCallback();
|
|
1204
4023
|
if (typeof window !== "undefined") {
|
|
4024
|
+
this.ensureBrandFonts();
|
|
1205
4025
|
window.addEventListener("resize", this.handleResize);
|
|
1206
4026
|
window.addEventListener(
|
|
1207
4027
|
"pointerdown",
|
|
@@ -1215,6 +4035,17 @@ ${argsString}</pre
|
|
|
1215
4035
|
}
|
|
1216
4036
|
}
|
|
1217
4037
|
|
|
4038
|
+
private ensureBrandFonts(): void {
|
|
4039
|
+
const FONT_LINK_ID = "cpk-inspector-brand-fonts";
|
|
4040
|
+
if (document.getElementById(FONT_LINK_ID)) return;
|
|
4041
|
+
const link = document.createElement("link");
|
|
4042
|
+
link.id = FONT_LINK_ID;
|
|
4043
|
+
link.rel = "stylesheet";
|
|
4044
|
+
link.href =
|
|
4045
|
+
"https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;600&family=Spline+Sans+Mono:wght@600&display=swap";
|
|
4046
|
+
document.head.appendChild(link);
|
|
4047
|
+
}
|
|
4048
|
+
|
|
1218
4049
|
disconnectedCallback(): void {
|
|
1219
4050
|
super.disconnectedCallback();
|
|
1220
4051
|
if (typeof window !== "undefined") {
|
|
@@ -1309,7 +4140,7 @@ ${argsString}</pre
|
|
|
1309
4140
|
"focus-visible:outline",
|
|
1310
4141
|
"focus-visible:outline-2",
|
|
1311
4142
|
"focus-visible:outline-offset-2",
|
|
1312
|
-
"focus-visible:outline-
|
|
4143
|
+
"focus-visible:outline-[#BEC2FF]",
|
|
1313
4144
|
"touch-none",
|
|
1314
4145
|
"select-none",
|
|
1315
4146
|
this.isDragging ? "cursor-grabbing" : "cursor-grab",
|
|
@@ -1435,6 +4266,7 @@ ${argsString}</pre
|
|
|
1435
4266
|
</div>
|
|
1436
4267
|
</div>
|
|
1437
4268
|
</div>
|
|
4269
|
+
${this.renderAnnouncementBanner()}
|
|
1438
4270
|
<div
|
|
1439
4271
|
class="flex flex-wrap items-center gap-2 border-t border-gray-100 px-3 py-2 text-xs"
|
|
1440
4272
|
>
|
|
@@ -1442,9 +4274,7 @@ ${argsString}</pre
|
|
|
1442
4274
|
const isSelected = this.selectedMenu === key;
|
|
1443
4275
|
const tabClasses = [
|
|
1444
4276
|
"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",
|
|
1445
|
-
isSelected
|
|
1446
|
-
? "bg-gray-900 text-white shadow-sm"
|
|
1447
|
-
: "text-gray-600 hover:bg-gray-100 hover:text-gray-900",
|
|
4277
|
+
isSelected ? "cpk-tab-active" : "cpk-tab-inactive",
|
|
1448
4278
|
].join(" ");
|
|
1449
4279
|
|
|
1450
4280
|
return html`
|
|
@@ -1454,10 +4284,12 @@ ${argsString}</pre
|
|
|
1454
4284
|
aria-pressed=${isSelected}
|
|
1455
4285
|
@click=${() => this.handleMenuSelect(key)}
|
|
1456
4286
|
>
|
|
1457
|
-
<span
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
4287
|
+
<span class="cpk-tab-icon">
|
|
4288
|
+
${
|
|
4289
|
+
key in this.customTabIcons
|
|
4290
|
+
? unsafeHTML(this.customTabIcons[key])
|
|
4291
|
+
: this.renderIcon(icon)
|
|
4292
|
+
}
|
|
1461
4293
|
</span>
|
|
1462
4294
|
<span>${label}</span>
|
|
1463
4295
|
</button>
|
|
@@ -1466,8 +4298,7 @@ ${argsString}</pre
|
|
|
1466
4298
|
</div>
|
|
1467
4299
|
</div>
|
|
1468
4300
|
<div class="flex flex-1 flex-col overflow-hidden">
|
|
1469
|
-
<div class="flex-1 overflow-auto">
|
|
1470
|
-
${this.renderAnnouncementPanel()}
|
|
4301
|
+
<div id="cpk-main-scroll" class="flex-1 overflow-auto">
|
|
1471
4302
|
${this.renderCoreWarningBanner()} ${this.renderMainContent()}
|
|
1472
4303
|
<slot></slot>
|
|
1473
4304
|
</div>
|
|
@@ -1519,6 +4350,11 @@ ${argsString}</pre
|
|
|
1519
4350
|
return;
|
|
1520
4351
|
}
|
|
1521
4352
|
|
|
4353
|
+
// Restore early-access unlock from cookie set by _submitThreadsCode
|
|
4354
|
+
if (document.cookie.includes("cpk_threads_access=1")) {
|
|
4355
|
+
this._threadsUnlocked = true;
|
|
4356
|
+
}
|
|
4357
|
+
|
|
1522
4358
|
const persisted = loadInspectorState(INSPECTOR_STORAGE_KEY);
|
|
1523
4359
|
if (!persisted) {
|
|
1524
4360
|
return;
|
|
@@ -2531,6 +5367,8 @@ ${argsString}</pre
|
|
|
2531
5367
|
? this.sanitizeForLogging(raw.content)
|
|
2532
5368
|
: undefined,
|
|
2533
5369
|
toolCalls,
|
|
5370
|
+
activityType:
|
|
5371
|
+
typeof raw.activityType === "string" ? raw.activityType : undefined,
|
|
2534
5372
|
};
|
|
2535
5373
|
}
|
|
2536
5374
|
|
|
@@ -2587,11 +5425,6 @@ ${argsString}</pre
|
|
|
2587
5425
|
private expandedContextItems: Set<string> = new Set();
|
|
2588
5426
|
private copiedContextItems: Set<string> = new Set();
|
|
2589
5427
|
|
|
2590
|
-
private getSelectedMenu(): MenuItem {
|
|
2591
|
-
const found = this.menuItems.find((item) => item.key === this.selectedMenu);
|
|
2592
|
-
return found ?? this.menuItems[0]!;
|
|
2593
|
-
}
|
|
2594
|
-
|
|
2595
5428
|
private renderCoreWarningBanner() {
|
|
2596
5429
|
if (this._core) {
|
|
2597
5430
|
return nothing;
|
|
@@ -2686,9 +5519,547 @@ ${argsString}</pre
|
|
|
2686
5519
|
return this.renderContextView();
|
|
2687
5520
|
}
|
|
2688
5521
|
|
|
5522
|
+
if (this.selectedMenu === "threads") {
|
|
5523
|
+
return this.renderThreadsView();
|
|
5524
|
+
}
|
|
5525
|
+
|
|
2689
5526
|
return nothing;
|
|
2690
5527
|
}
|
|
2691
5528
|
|
|
5529
|
+
private handleThreadDividerPointerDown = (event: PointerEvent) => {
|
|
5530
|
+
this.threadDividerResizing = true;
|
|
5531
|
+
this.threadDividerPointerId = event.pointerId;
|
|
5532
|
+
this.threadDividerStartX = event.clientX;
|
|
5533
|
+
this.threadDividerStartWidth = this.threadListWidth;
|
|
5534
|
+
(event.currentTarget as HTMLElement).setPointerCapture(event.pointerId);
|
|
5535
|
+
event.preventDefault();
|
|
5536
|
+
};
|
|
5537
|
+
|
|
5538
|
+
private handleThreadDividerPointerMove = (event: PointerEvent) => {
|
|
5539
|
+
if (
|
|
5540
|
+
!this.threadDividerResizing ||
|
|
5541
|
+
this.threadDividerPointerId !== event.pointerId
|
|
5542
|
+
)
|
|
5543
|
+
return;
|
|
5544
|
+
const delta = event.clientX - this.threadDividerStartX;
|
|
5545
|
+
this.threadListWidth = Math.max(
|
|
5546
|
+
180,
|
|
5547
|
+
Math.min(480, this.threadDividerStartWidth + delta),
|
|
5548
|
+
);
|
|
5549
|
+
this.requestUpdate();
|
|
5550
|
+
};
|
|
5551
|
+
|
|
5552
|
+
private handleThreadDividerPointerUp = (event: PointerEvent) => {
|
|
5553
|
+
if (this.threadDividerPointerId !== event.pointerId) return;
|
|
5554
|
+
const target = event.currentTarget as HTMLElement;
|
|
5555
|
+
if (target.hasPointerCapture(this.threadDividerPointerId)) {
|
|
5556
|
+
target.releasePointerCapture(this.threadDividerPointerId);
|
|
5557
|
+
}
|
|
5558
|
+
this.threadDividerResizing = false;
|
|
5559
|
+
};
|
|
5560
|
+
|
|
5561
|
+
private renderThreadsGate() {
|
|
5562
|
+
return html`
|
|
5563
|
+
<div style="
|
|
5564
|
+
position:relative;
|
|
5565
|
+
display:flex;
|
|
5566
|
+
flex-direction:column;
|
|
5567
|
+
align-items:center;
|
|
5568
|
+
justify-content:center;
|
|
5569
|
+
padding:40px 24px;
|
|
5570
|
+
min-height:100%;
|
|
5571
|
+
text-align:center;
|
|
5572
|
+
background:linear-gradient(135deg,#f5f4ff 0%,#ede9fe 100%);
|
|
5573
|
+
overflow:hidden;
|
|
5574
|
+
">
|
|
5575
|
+
<!-- Blurred ellipses from Figma/storybook -->
|
|
5576
|
+
<div style="position:absolute;width:570px;height:570px;border-radius:50%;top:-80px;left:-120px;opacity:0.25;background:#757CF2;filter:blur(120px);pointer-events:none;"></div>
|
|
5577
|
+
<div style="position:absolute;width:570px;height:570px;border-radius:50%;bottom:-100px;right:-80px;opacity:0.2;background:#FFAC4D;filter:blur(120px);pointer-events:none;"></div>
|
|
5578
|
+
<div style="position:absolute;width:400px;height:400px;border-radius:50%;bottom:20px;left:-60px;opacity:0.15;background:#FFAC4D;filter:blur(100px);pointer-events:none;"></div>
|
|
5579
|
+
|
|
5580
|
+
${
|
|
5581
|
+
this._threadsUnlocking
|
|
5582
|
+
? this._renderUnlockingCard()
|
|
5583
|
+
: this._renderEarlyAccessCard()
|
|
5584
|
+
}
|
|
5585
|
+
</div>
|
|
5586
|
+
`;
|
|
5587
|
+
}
|
|
5588
|
+
|
|
5589
|
+
/** Hosted invite-form URL — used by the "Request early access" CTA. */
|
|
5590
|
+
private static readonly THREADS_REQUEST_URL =
|
|
5591
|
+
"https://r3x69.share-na2.hsforms.com/2uiZg8EkiT7a_KykeXV1ajQ";
|
|
5592
|
+
|
|
5593
|
+
private _renderEarlyAccessCard() {
|
|
5594
|
+
const invalid = this._threadsGateCodeInvalid;
|
|
5595
|
+
return html`
|
|
5596
|
+
<div
|
|
5597
|
+
style="
|
|
5598
|
+
position:relative;
|
|
5599
|
+
z-index:1;
|
|
5600
|
+
background:#ffffff;
|
|
5601
|
+
border:1px solid #E5E5EA;
|
|
5602
|
+
border-radius:20px;
|
|
5603
|
+
box-shadow:0 16px 48px rgba(1,5,7,0.12),0 2px 6px rgba(1,5,7,0.05);
|
|
5604
|
+
padding:28px;
|
|
5605
|
+
width:400px;
|
|
5606
|
+
max-width:100%;
|
|
5607
|
+
display:flex;
|
|
5608
|
+
flex-direction:column;
|
|
5609
|
+
gap:18px;
|
|
5610
|
+
text-align:left;
|
|
5611
|
+
font-family:'Plus Jakarta Sans', system-ui, sans-serif;
|
|
5612
|
+
"
|
|
5613
|
+
>
|
|
5614
|
+
<!-- Kicker pill -->
|
|
5615
|
+
<div>
|
|
5616
|
+
<span
|
|
5617
|
+
style="
|
|
5618
|
+
display:inline-flex;
|
|
5619
|
+
align-items:center;
|
|
5620
|
+
gap:4px;
|
|
5621
|
+
padding:4px 10px;
|
|
5622
|
+
border-radius:999px;
|
|
5623
|
+
background:#F3F3FC;
|
|
5624
|
+
color:#757CF2;
|
|
5625
|
+
font-family:'Spline Sans Mono', ui-monospace, monospace;
|
|
5626
|
+
font-size:10px;
|
|
5627
|
+
font-weight:500;
|
|
5628
|
+
letter-spacing:0.08em;
|
|
5629
|
+
text-transform:uppercase;
|
|
5630
|
+
"
|
|
5631
|
+
>Early Access</span
|
|
5632
|
+
>
|
|
5633
|
+
</div>
|
|
5634
|
+
|
|
5635
|
+
<!-- Title + description -->
|
|
5636
|
+
<div style="display:flex;flex-direction:column;gap:8px;">
|
|
5637
|
+
<h2
|
|
5638
|
+
style="
|
|
5639
|
+
font-family:'Plus Jakarta Sans', system-ui, sans-serif;
|
|
5640
|
+
font-size:24px;
|
|
5641
|
+
font-weight:700;
|
|
5642
|
+
color:#010507;
|
|
5643
|
+
line-height:1.2;
|
|
5644
|
+
letter-spacing:-0.015em;
|
|
5645
|
+
margin:0;
|
|
5646
|
+
"
|
|
5647
|
+
>
|
|
5648
|
+
<span
|
|
5649
|
+
style="
|
|
5650
|
+
background:linear-gradient(90deg, #757CF2 0%, #5AE4BB 100%);
|
|
5651
|
+
-webkit-background-clip:text;
|
|
5652
|
+
background-clip:text;
|
|
5653
|
+
color:transparent;
|
|
5654
|
+
-webkit-text-fill-color:transparent;
|
|
5655
|
+
"
|
|
5656
|
+
>Threads</span
|
|
5657
|
+
>
|
|
5658
|
+
are in private beta
|
|
5659
|
+
</h2>
|
|
5660
|
+
<p
|
|
5661
|
+
style="
|
|
5662
|
+
font-size:14px;
|
|
5663
|
+
font-weight:500;
|
|
5664
|
+
color:#5C5C66;
|
|
5665
|
+
line-height:1.55;
|
|
5666
|
+
margin:0;
|
|
5667
|
+
"
|
|
5668
|
+
>
|
|
5669
|
+
Spin up separate conversations with your agent, one per task, bug,
|
|
5670
|
+
or feature, and jump back into any of them without losing context.
|
|
5671
|
+
</p>
|
|
5672
|
+
</div>
|
|
5673
|
+
|
|
5674
|
+
<!-- Bullets -->
|
|
5675
|
+
<div
|
|
5676
|
+
style="display:flex;flex-direction:column;gap:8px;padding:4px 0;"
|
|
5677
|
+
>
|
|
5678
|
+
${[
|
|
5679
|
+
"One agent, many conversations",
|
|
5680
|
+
"Persistent history across sessions",
|
|
5681
|
+
"Jump between threads in a click",
|
|
5682
|
+
].map(
|
|
5683
|
+
(label) => html`
|
|
5684
|
+
<div style="display:flex;align-items:center;gap:10px;">
|
|
5685
|
+
<svg
|
|
5686
|
+
width="14"
|
|
5687
|
+
height="14"
|
|
5688
|
+
viewBox="0 0 24 24"
|
|
5689
|
+
fill="none"
|
|
5690
|
+
stroke="#010507"
|
|
5691
|
+
stroke-width="2.5"
|
|
5692
|
+
stroke-linecap="round"
|
|
5693
|
+
stroke-linejoin="round"
|
|
5694
|
+
style="flex-shrink:0;"
|
|
5695
|
+
>
|
|
5696
|
+
<polyline points="20 6 9 17 4 12"></polyline>
|
|
5697
|
+
</svg>
|
|
5698
|
+
<span style="font-size:13px;font-weight:500;color:#010507;"
|
|
5699
|
+
>${label}</span
|
|
5700
|
+
>
|
|
5701
|
+
</div>
|
|
5702
|
+
`,
|
|
5703
|
+
)}
|
|
5704
|
+
</div>
|
|
5705
|
+
|
|
5706
|
+
<!-- Primary CTA: dark MonoPillButton with adjacent arrow circle -->
|
|
5707
|
+
<div>
|
|
5708
|
+
<a
|
|
5709
|
+
href=${WebInspectorElement.THREADS_REQUEST_URL}
|
|
5710
|
+
target="_blank"
|
|
5711
|
+
rel="noopener noreferrer"
|
|
5712
|
+
style="
|
|
5713
|
+
display:inline-flex;
|
|
5714
|
+
align-items:center;
|
|
5715
|
+
gap:8px;
|
|
5716
|
+
text-decoration:none;
|
|
5717
|
+
cursor:pointer;
|
|
5718
|
+
"
|
|
5719
|
+
>
|
|
5720
|
+
<span
|
|
5721
|
+
style="
|
|
5722
|
+
display:inline-flex;
|
|
5723
|
+
align-items:center;
|
|
5724
|
+
justify-content:center;
|
|
5725
|
+
background:#010507;
|
|
5726
|
+
color:#ffffff;
|
|
5727
|
+
font-family:'Spline Sans Mono', ui-monospace, monospace;
|
|
5728
|
+
font-size:13px;
|
|
5729
|
+
font-weight:500;
|
|
5730
|
+
letter-spacing:0.06em;
|
|
5731
|
+
text-transform:uppercase;
|
|
5732
|
+
padding:14px 22px;
|
|
5733
|
+
border-radius:999px;
|
|
5734
|
+
box-shadow:0 4px 12px rgba(1,5,7,0.18);
|
|
5735
|
+
"
|
|
5736
|
+
>Request Early Access</span
|
|
5737
|
+
>
|
|
5738
|
+
<span
|
|
5739
|
+
style="
|
|
5740
|
+
display:inline-flex;
|
|
5741
|
+
align-items:center;
|
|
5742
|
+
justify-content:center;
|
|
5743
|
+
width:36px;
|
|
5744
|
+
height:36px;
|
|
5745
|
+
border-radius:999px;
|
|
5746
|
+
background:#010507;
|
|
5747
|
+
color:#ffffff;
|
|
5748
|
+
box-shadow:0 4px 12px rgba(1,5,7,0.18);
|
|
5749
|
+
"
|
|
5750
|
+
>
|
|
5751
|
+
<svg
|
|
5752
|
+
width="14"
|
|
5753
|
+
height="14"
|
|
5754
|
+
viewBox="0 0 24 24"
|
|
5755
|
+
fill="none"
|
|
5756
|
+
stroke="currentColor"
|
|
5757
|
+
stroke-width="2"
|
|
5758
|
+
stroke-linecap="round"
|
|
5759
|
+
stroke-linejoin="round"
|
|
5760
|
+
>
|
|
5761
|
+
<line x1="5" y1="12" x2="19" y2="12"></line>
|
|
5762
|
+
<polyline points="12 5 19 12 12 19"></polyline>
|
|
5763
|
+
</svg>
|
|
5764
|
+
</span>
|
|
5765
|
+
</a>
|
|
5766
|
+
</div>
|
|
5767
|
+
|
|
5768
|
+
<!-- Divider + invite-code section -->
|
|
5769
|
+
<div
|
|
5770
|
+
style="
|
|
5771
|
+
display:flex;
|
|
5772
|
+
flex-direction:column;
|
|
5773
|
+
gap:8px;
|
|
5774
|
+
padding-top:14px;
|
|
5775
|
+
border-top:1px dashed #E5E5EA;
|
|
5776
|
+
"
|
|
5777
|
+
>
|
|
5778
|
+
<span style="font-size:12px;font-weight:500;color:#8A8A94;"
|
|
5779
|
+
>Have an invite code?</span
|
|
5780
|
+
>
|
|
5781
|
+
<div style="display:flex;gap:8px;">
|
|
5782
|
+
<div
|
|
5783
|
+
style="
|
|
5784
|
+
flex:1;
|
|
5785
|
+
background:#ffffff;
|
|
5786
|
+
border:1px solid ${invalid ? "#FA5F67" : "#E5E5EA"};
|
|
5787
|
+
border-radius:10px;
|
|
5788
|
+
padding:2px 4px 2px 12px;
|
|
5789
|
+
transition:border-color 150ms ease;
|
|
5790
|
+
"
|
|
5791
|
+
>
|
|
5792
|
+
<input
|
|
5793
|
+
id="cpk-gate-input"
|
|
5794
|
+
type="text"
|
|
5795
|
+
placeholder="Enter access code"
|
|
5796
|
+
style="
|
|
5797
|
+
width:100%;
|
|
5798
|
+
padding:10px 0;
|
|
5799
|
+
font-family:'Plus Jakarta Sans', system-ui, sans-serif;
|
|
5800
|
+
font-size:13px;
|
|
5801
|
+
font-weight:500;
|
|
5802
|
+
color:#010507;
|
|
5803
|
+
background:transparent;
|
|
5804
|
+
border:none;
|
|
5805
|
+
outline:none;
|
|
5806
|
+
"
|
|
5807
|
+
@keydown=${(e: KeyboardEvent) => {
|
|
5808
|
+
if (e.key === "Enter") {
|
|
5809
|
+
this._submitThreadsCode(
|
|
5810
|
+
(e.currentTarget as HTMLInputElement).value,
|
|
5811
|
+
);
|
|
5812
|
+
}
|
|
5813
|
+
}}
|
|
5814
|
+
/>
|
|
5815
|
+
</div>
|
|
5816
|
+
<button
|
|
5817
|
+
style="
|
|
5818
|
+
background:#010507;
|
|
5819
|
+
color:#ffffff;
|
|
5820
|
+
border:none;
|
|
5821
|
+
border-radius:10px;
|
|
5822
|
+
padding:0 16px;
|
|
5823
|
+
font-family:'Spline Sans Mono', ui-monospace, monospace;
|
|
5824
|
+
font-size:11px;
|
|
5825
|
+
font-weight:500;
|
|
5826
|
+
letter-spacing:0.06em;
|
|
5827
|
+
text-transform:uppercase;
|
|
5828
|
+
cursor:pointer;
|
|
5829
|
+
white-space:nowrap;
|
|
5830
|
+
"
|
|
5831
|
+
@click=${() => {
|
|
5832
|
+
const input = this.shadowRoot?.getElementById(
|
|
5833
|
+
"cpk-gate-input",
|
|
5834
|
+
) as HTMLInputElement | null;
|
|
5835
|
+
if (input) this._submitThreadsCode(input.value);
|
|
5836
|
+
}}
|
|
5837
|
+
>
|
|
5838
|
+
Unlock
|
|
5839
|
+
</button>
|
|
5840
|
+
</div>
|
|
5841
|
+
${
|
|
5842
|
+
invalid
|
|
5843
|
+
? html`
|
|
5844
|
+
<div style="font-size: 11px; font-weight: 500; color: #fa5f67">
|
|
5845
|
+
That code isn't valid. Double-check your invite email.
|
|
5846
|
+
</div>
|
|
5847
|
+
`
|
|
5848
|
+
: nothing
|
|
5849
|
+
}
|
|
5850
|
+
</div>
|
|
5851
|
+
</div>
|
|
5852
|
+
`;
|
|
5853
|
+
}
|
|
5854
|
+
|
|
5855
|
+
private _renderUnlockingCard() {
|
|
5856
|
+
return html`
|
|
5857
|
+
<div
|
|
5858
|
+
style="
|
|
5859
|
+
position: relative;
|
|
5860
|
+
z-index: 1;
|
|
5861
|
+
background: #ffffff;
|
|
5862
|
+
border: 1px solid #e5e5ea;
|
|
5863
|
+
border-radius: 20px;
|
|
5864
|
+
box-shadow:
|
|
5865
|
+
0 16px 48px rgba(1, 5, 7, 0.12),
|
|
5866
|
+
0 2px 6px rgba(1, 5, 7, 0.05);
|
|
5867
|
+
padding: 32px;
|
|
5868
|
+
width: 340px;
|
|
5869
|
+
max-width: 100%;
|
|
5870
|
+
display: flex;
|
|
5871
|
+
flex-direction: column;
|
|
5872
|
+
align-items: center;
|
|
5873
|
+
gap: 16px;
|
|
5874
|
+
text-align: center;
|
|
5875
|
+
font-family: "Plus Jakarta Sans", system-ui, sans-serif;
|
|
5876
|
+
"
|
|
5877
|
+
>
|
|
5878
|
+
<div
|
|
5879
|
+
style="
|
|
5880
|
+
width: 56px;
|
|
5881
|
+
height: 56px;
|
|
5882
|
+
border-radius: 999px;
|
|
5883
|
+
background: linear-gradient(135deg, #bec2ff 0%, #85ecce 100%);
|
|
5884
|
+
display: flex;
|
|
5885
|
+
align-items: center;
|
|
5886
|
+
justify-content: center;
|
|
5887
|
+
"
|
|
5888
|
+
>
|
|
5889
|
+
<svg
|
|
5890
|
+
width="24"
|
|
5891
|
+
height="24"
|
|
5892
|
+
viewBox="0 0 24 24"
|
|
5893
|
+
fill="none"
|
|
5894
|
+
stroke="#010507"
|
|
5895
|
+
stroke-width="2.5"
|
|
5896
|
+
stroke-linecap="round"
|
|
5897
|
+
stroke-linejoin="round"
|
|
5898
|
+
>
|
|
5899
|
+
<polyline points="20 6 9 17 4 12"></polyline>
|
|
5900
|
+
</svg>
|
|
5901
|
+
</div>
|
|
5902
|
+
<div style="font-size: 18px; font-weight: 700; color: #010507">
|
|
5903
|
+
Welcome to Threads
|
|
5904
|
+
</div>
|
|
5905
|
+
<div style="font-size: 13px; color: #5c5c66; line-height: 1.5">
|
|
5906
|
+
Loading your conversations…
|
|
5907
|
+
</div>
|
|
5908
|
+
</div>
|
|
5909
|
+
`;
|
|
5910
|
+
}
|
|
5911
|
+
|
|
5912
|
+
private _submitThreadsCode(value: string): void {
|
|
5913
|
+
if (value.trim().toLowerCase() === "earlyaccess") {
|
|
5914
|
+
// Persist the unlock so subsequent loads bypass the gate, then show
|
|
5915
|
+
// the brief "Welcome to Threads" confirmation before swapping to the
|
|
5916
|
+
// real Threads UI ~2s later.
|
|
5917
|
+
document.cookie =
|
|
5918
|
+
"cpk_threads_access=1; path=/; max-age=31536000; SameSite=Lax";
|
|
5919
|
+
this._threadsGateError = null;
|
|
5920
|
+
this._threadsGateCodeInvalid = false;
|
|
5921
|
+
this._threadsUnlocking = true;
|
|
5922
|
+
if (this._threadsUnlockingTimer !== null) {
|
|
5923
|
+
clearTimeout(this._threadsUnlockingTimer);
|
|
5924
|
+
}
|
|
5925
|
+
this._threadsUnlockingTimer = setTimeout(() => {
|
|
5926
|
+
this._threadsUnlocking = false;
|
|
5927
|
+
this._threadsUnlocked = true;
|
|
5928
|
+
this._threadsUnlockingTimer = null;
|
|
5929
|
+
this.requestUpdate();
|
|
5930
|
+
}, 2000);
|
|
5931
|
+
} else {
|
|
5932
|
+
// Invalid: flash the input border + error copy, auto-clear after
|
|
5933
|
+
// 1600ms (matches the design's invalid-code window).
|
|
5934
|
+
this._threadsGateCodeInvalid = true;
|
|
5935
|
+
this._threadsGateError = null;
|
|
5936
|
+
if (this._threadsGateInvalidTimer !== null) {
|
|
5937
|
+
clearTimeout(this._threadsGateInvalidTimer);
|
|
5938
|
+
}
|
|
5939
|
+
this._threadsGateInvalidTimer = setTimeout(() => {
|
|
5940
|
+
this._threadsGateCodeInvalid = false;
|
|
5941
|
+
this._threadsGateInvalidTimer = null;
|
|
5942
|
+
this.requestUpdate();
|
|
5943
|
+
}, 1600);
|
|
5944
|
+
}
|
|
5945
|
+
this.requestUpdate();
|
|
5946
|
+
}
|
|
5947
|
+
|
|
5948
|
+
private renderThreadsView() {
|
|
5949
|
+
if (!this._threadsUnlocked) {
|
|
5950
|
+
return this.renderThreadsGate();
|
|
5951
|
+
}
|
|
5952
|
+
|
|
5953
|
+
const displayThreads =
|
|
5954
|
+
this.selectedContext === "all-agents"
|
|
5955
|
+
? this._threads
|
|
5956
|
+
: (this._threadsByAgent.get(this.selectedContext) ?? []);
|
|
5957
|
+
|
|
5958
|
+
// Surface a thread-store load error inline. For "all-agents" we report
|
|
5959
|
+
// the first error encountered across all agents (good enough for a
|
|
5960
|
+
// debugging surface — the per-agent context filter narrows down the
|
|
5961
|
+
// culprit). For a specific agent we use that agent's error directly.
|
|
5962
|
+
let threadsErrorMessage: string | null = null;
|
|
5963
|
+
if (this.selectedContext === "all-agents") {
|
|
5964
|
+
const firstError = this._threadsErrorByAgent.values().next().value;
|
|
5965
|
+
threadsErrorMessage = firstError?.message ?? null;
|
|
5966
|
+
} else {
|
|
5967
|
+
threadsErrorMessage =
|
|
5968
|
+
this._threadsErrorByAgent.get(this.selectedContext)?.message ?? null;
|
|
5969
|
+
}
|
|
5970
|
+
|
|
5971
|
+
const selectedThread =
|
|
5972
|
+
this.selectedThreadId != null
|
|
5973
|
+
? (displayThreads.find((t) => t.id === this.selectedThreadId) ?? null)
|
|
5974
|
+
: null;
|
|
5975
|
+
|
|
5976
|
+
return html`
|
|
5977
|
+
<div style="display:flex;height:100%;overflow:hidden;">
|
|
5978
|
+
<!-- Left sidebar: thread list -->
|
|
5979
|
+
<div
|
|
5980
|
+
style="width:${this.threadListWidth}px;flex-shrink:0;overflow:hidden;display:flex;flex-direction:column;border-right:1px solid #DBDBE5;"
|
|
5981
|
+
>
|
|
5982
|
+
<cpk-thread-list
|
|
5983
|
+
style="height:100%;"
|
|
5984
|
+
.threads=${displayThreads}
|
|
5985
|
+
.selectedThreadId=${this.selectedThreadId}
|
|
5986
|
+
.errorMessage=${threadsErrorMessage}
|
|
5987
|
+
@threadSelected=${(e: CustomEvent<string>) => {
|
|
5988
|
+
this.selectedThreadId = e.detail;
|
|
5989
|
+
this.requestUpdate();
|
|
5990
|
+
}}
|
|
5991
|
+
></cpk-thread-list>
|
|
5992
|
+
</div>
|
|
5993
|
+
|
|
5994
|
+
<!-- Resize divider -->
|
|
5995
|
+
<div
|
|
5996
|
+
style="width:4px;flex-shrink:0;cursor:col-resize;background:transparent;position:relative;z-index:1;"
|
|
5997
|
+
@pointerdown=${this.handleThreadDividerPointerDown}
|
|
5998
|
+
@pointermove=${this.handleThreadDividerPointerMove}
|
|
5999
|
+
@pointerup=${this.handleThreadDividerPointerUp}
|
|
6000
|
+
@pointercancel=${this.handleThreadDividerPointerUp}
|
|
6001
|
+
></div>
|
|
6002
|
+
|
|
6003
|
+
<!-- Center + right: thread details or empty state -->
|
|
6004
|
+
<div style="flex:1;min-width:0;overflow:hidden;display:flex;">
|
|
6005
|
+
${
|
|
6006
|
+
this.selectedThreadId
|
|
6007
|
+
? html`<cpk-thread-details
|
|
6008
|
+
style="flex:1;min-width:0;"
|
|
6009
|
+
.threadId=${this.selectedThreadId}
|
|
6010
|
+
.thread=${selectedThread}
|
|
6011
|
+
.runtimeUrl=${this._core?.runtimeUrl ?? ""}
|
|
6012
|
+
.headers=${this._core?.headers ?? {}}
|
|
6013
|
+
.liveMessageVersion=${
|
|
6014
|
+
this.selectedThreadId
|
|
6015
|
+
? (this.liveMessageVersion.get(this.selectedThreadId) ??
|
|
6016
|
+
0)
|
|
6017
|
+
: 0
|
|
6018
|
+
}
|
|
6019
|
+
.agentStateInput=${
|
|
6020
|
+
selectedThread
|
|
6021
|
+
? this.getLatestStateForAgent(selectedThread.agentId)
|
|
6022
|
+
: null
|
|
6023
|
+
}
|
|
6024
|
+
.agentEventsInput=${
|
|
6025
|
+
selectedThread
|
|
6026
|
+
? (this.agentEvents.get(selectedThread.agentId) ?? [])
|
|
6027
|
+
: []
|
|
6028
|
+
}
|
|
6029
|
+
></cpk-thread-details>`
|
|
6030
|
+
: html`
|
|
6031
|
+
<div
|
|
6032
|
+
style="
|
|
6033
|
+
flex: 1;
|
|
6034
|
+
display: flex;
|
|
6035
|
+
flex-direction: column;
|
|
6036
|
+
align-items: center;
|
|
6037
|
+
justify-content: center;
|
|
6038
|
+
gap: 8px;
|
|
6039
|
+
color: #838389;
|
|
6040
|
+
"
|
|
6041
|
+
>
|
|
6042
|
+
<svg
|
|
6043
|
+
width="32"
|
|
6044
|
+
height="32"
|
|
6045
|
+
viewBox="0 0 24 24"
|
|
6046
|
+
fill="none"
|
|
6047
|
+
stroke="#c0c0c8"
|
|
6048
|
+
stroke-width="1.5"
|
|
6049
|
+
stroke-linecap="round"
|
|
6050
|
+
stroke-linejoin="round"
|
|
6051
|
+
>
|
|
6052
|
+
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
|
|
6053
|
+
</svg>
|
|
6054
|
+
<span style="font-size: 13px">${displayThreads.length === 0 ? "No threads yet" : "Select a thread to inspect"}</span>
|
|
6055
|
+
</div>
|
|
6056
|
+
`
|
|
6057
|
+
}
|
|
6058
|
+
</div>
|
|
6059
|
+
</div>
|
|
6060
|
+
`;
|
|
6061
|
+
}
|
|
6062
|
+
|
|
2692
6063
|
private renderEventsTable() {
|
|
2693
6064
|
const events = this.getEventsForSelectedContext();
|
|
2694
6065
|
const filteredEvents = this.filterEvents(events);
|
|
@@ -2700,19 +6071,15 @@ ${argsString}</pre
|
|
|
2700
6071
|
if (events.length === 0) {
|
|
2701
6072
|
return html`
|
|
2702
6073
|
<div
|
|
2703
|
-
class="flex h-full items-center justify-center px-4 py-
|
|
6074
|
+
class="flex h-full flex-col items-center justify-center gap-2 px-4 py-10 text-center"
|
|
2704
6075
|
>
|
|
2705
|
-
<div class="
|
|
2706
|
-
|
|
2707
|
-
class="mb-3 flex justify-center text-gray-300 [&>svg]:!h-8 [&>svg]:!w-8"
|
|
2708
|
-
>
|
|
2709
|
-
${this.renderIcon("Zap")}
|
|
2710
|
-
</div>
|
|
2711
|
-
<p class="text-sm text-gray-600">No events yet</p>
|
|
2712
|
-
<p class="mt-2 text-xs text-gray-500">
|
|
2713
|
-
Trigger an agent run to see live activity.
|
|
2714
|
-
</p>
|
|
6076
|
+
<div class="text-gray-300 [&>svg]:!h-8 [&>svg]:!w-8">
|
|
6077
|
+
${this.renderIcon("Zap")}
|
|
2715
6078
|
</div>
|
|
6079
|
+
<span class="text-sm text-gray-600">No events yet</span>
|
|
6080
|
+
<span class="max-w-[240px] text-xs leading-snug text-gray-400"
|
|
6081
|
+
>Events are recorded live. Run the agent to see them here.</span
|
|
6082
|
+
>
|
|
2716
6083
|
</div>
|
|
2717
6084
|
`;
|
|
2718
6085
|
}
|
|
@@ -2823,23 +6190,30 @@ ${argsString}</pre
|
|
|
2823
6190
|
</div>
|
|
2824
6191
|
<div class="relative h-full w-full overflow-y-auto overflow-x-hidden">
|
|
2825
6192
|
<table class="w-full table-fixed border-collapse text-xs box-border">
|
|
6193
|
+
<colgroup>
|
|
6194
|
+
<col style="width:${this.evtColWidths[0]}px">
|
|
6195
|
+
<col style="width:${this.evtColWidths[1]}px">
|
|
6196
|
+
<col style="width:${this.evtColWidths[2]}px">
|
|
6197
|
+
<col>
|
|
6198
|
+
</colgroup>
|
|
2826
6199
|
<thead class="sticky top-0 z-10">
|
|
2827
6200
|
<tr class="bg-white">
|
|
6201
|
+
${["Agent", "Time", "Event Type"].map(
|
|
6202
|
+
(label, col) => html`
|
|
2828
6203
|
<th
|
|
2829
6204
|
class="border-b border-gray-200 bg-white px-3 py-2 text-left font-medium text-gray-900"
|
|
6205
|
+
style="position:relative;overflow:hidden;"
|
|
2830
6206
|
>
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
Event Type
|
|
2842
|
-
</th>
|
|
6207
|
+
${label}
|
|
6208
|
+
<div
|
|
6209
|
+
style="position:absolute;top:0;right:0;width:5px;height:100%;cursor:col-resize;user-select:none;background:transparent;"
|
|
6210
|
+
@pointerdown=${(e: PointerEvent) => this._onEvtColResizeStart(e, col)}
|
|
6211
|
+
@pointermove=${(e: PointerEvent) => this._onEvtColResizeMove(e)}
|
|
6212
|
+
@pointerup=${() => this._onEvtColResizeEnd()}
|
|
6213
|
+
@pointercancel=${() => this._onEvtColResizeEnd()}
|
|
6214
|
+
></div>
|
|
6215
|
+
</th>`,
|
|
6216
|
+
)}
|
|
2843
6217
|
<th
|
|
2844
6218
|
class="border-b border-gray-200 bg-white px-3 py-2 text-left font-medium text-gray-900"
|
|
2845
6219
|
>
|
|
@@ -2954,6 +6328,30 @@ ${prettyEvent}</pre
|
|
|
2954
6328
|
this.requestUpdate();
|
|
2955
6329
|
}
|
|
2956
6330
|
|
|
6331
|
+
private _onEvtColResizeStart(e: PointerEvent, col: number): void {
|
|
6332
|
+
e.preventDefault();
|
|
6333
|
+
e.stopPropagation();
|
|
6334
|
+
(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);
|
|
6335
|
+
this._evtColResize = {
|
|
6336
|
+
col,
|
|
6337
|
+
startX: e.clientX,
|
|
6338
|
+
startW: this.evtColWidths[col] ?? 0,
|
|
6339
|
+
};
|
|
6340
|
+
}
|
|
6341
|
+
|
|
6342
|
+
private _onEvtColResizeMove(e: PointerEvent): void {
|
|
6343
|
+
if (!this._evtColResize) return;
|
|
6344
|
+
const { col, startX, startW } = this._evtColResize;
|
|
6345
|
+
this.evtColWidths = this.evtColWidths.map((w, i) =>
|
|
6346
|
+
i === col ? Math.max(40, startW + (e.clientX - startX)) : w,
|
|
6347
|
+
);
|
|
6348
|
+
this.requestUpdate();
|
|
6349
|
+
}
|
|
6350
|
+
|
|
6351
|
+
private _onEvtColResizeEnd(): void {
|
|
6352
|
+
this._evtColResize = null;
|
|
6353
|
+
}
|
|
6354
|
+
|
|
2957
6355
|
private handleClearEvents = (): void => {
|
|
2958
6356
|
if (this.selectedContext === "all-agents") {
|
|
2959
6357
|
this.agentEvents.clear();
|
|
@@ -3026,7 +6424,7 @@ ${prettyEvent}</pre
|
|
|
3026
6424
|
<div class="flex items-start justify-between mb-4">
|
|
3027
6425
|
<div class="flex items-center gap-3">
|
|
3028
6426
|
<div
|
|
3029
|
-
class="flex h-10 w-10 items-center justify-center rounded-lg bg-blue-100 text-blue-600"
|
|
6427
|
+
class="flex h-10 w-10 items-center justify-center rounded-lg bg-blue-100 text-blue-600 cpk-agent-icon"
|
|
3030
6428
|
>
|
|
3031
6429
|
${this.renderIcon("Bot")}
|
|
3032
6430
|
</div>
|
|
@@ -3062,7 +6460,7 @@ ${prettyEvent}</pre
|
|
|
3062
6460
|
<div class="grid grid-cols-2 gap-4 md:grid-cols-4">
|
|
3063
6461
|
<button
|
|
3064
6462
|
type="button"
|
|
3065
|
-
class="rounded-md bg-gray-50 px-3 py-2 text-left transition hover:bg-gray-100 cursor-pointer overflow-hidden"
|
|
6463
|
+
class="rounded-md bg-gray-50 px-3 py-2 text-left transition hover:bg-gray-100 cursor-pointer overflow-hidden cpk-stat-card"
|
|
3066
6464
|
@click=${() => this.handleMenuSelect("ag-ui-events")}
|
|
3067
6465
|
title="View all events in AG-UI Events"
|
|
3068
6466
|
>
|
|
@@ -3073,7 +6471,9 @@ ${prettyEvent}</pre
|
|
|
3073
6471
|
${stats.totalEvents}
|
|
3074
6472
|
</div>
|
|
3075
6473
|
</button>
|
|
3076
|
-
<div
|
|
6474
|
+
<div
|
|
6475
|
+
class="rounded-md bg-gray-50 px-3 py-2 overflow-hidden cpk-stat-card"
|
|
6476
|
+
>
|
|
3077
6477
|
<div class="truncate whitespace-nowrap text-xs text-gray-600">
|
|
3078
6478
|
Messages
|
|
3079
6479
|
</div>
|
|
@@ -3081,7 +6481,9 @@ ${prettyEvent}</pre
|
|
|
3081
6481
|
${stats.messages}
|
|
3082
6482
|
</div>
|
|
3083
6483
|
</div>
|
|
3084
|
-
<div
|
|
6484
|
+
<div
|
|
6485
|
+
class="rounded-md bg-gray-50 px-3 py-2 overflow-hidden cpk-stat-card"
|
|
6486
|
+
>
|
|
3085
6487
|
<div class="truncate whitespace-nowrap text-xs text-gray-600">
|
|
3086
6488
|
Tool Calls
|
|
3087
6489
|
</div>
|
|
@@ -3089,7 +6491,9 @@ ${prettyEvent}</pre
|
|
|
3089
6491
|
${stats.toolCalls}
|
|
3090
6492
|
</div>
|
|
3091
6493
|
</div>
|
|
3092
|
-
<div
|
|
6494
|
+
<div
|
|
6495
|
+
class="rounded-md bg-gray-50 px-3 py-2 overflow-hidden cpk-stat-card"
|
|
6496
|
+
>
|
|
3093
6497
|
<div class="truncate whitespace-nowrap text-xs text-gray-600">
|
|
3094
6498
|
Errors
|
|
3095
6499
|
</div>
|
|
@@ -3101,9 +6505,9 @@ ${prettyEvent}</pre
|
|
|
3101
6505
|
</div>
|
|
3102
6506
|
|
|
3103
6507
|
<!-- Current State Section -->
|
|
3104
|
-
<div class="
|
|
3105
|
-
<div class="
|
|
3106
|
-
<h4
|
|
6508
|
+
<div class="cpk-section-card">
|
|
6509
|
+
<div class="cpk-section-header">
|
|
6510
|
+
<h4>Current State</h4>
|
|
3107
6511
|
</div>
|
|
3108
6512
|
<div class="overflow-auto p-4">
|
|
3109
6513
|
${
|
|
@@ -3115,7 +6519,7 @@ ${prettyEvent}</pre
|
|
|
3115
6519
|
`
|
|
3116
6520
|
: html`
|
|
3117
6521
|
<div
|
|
3118
|
-
class="flex h-
|
|
6522
|
+
class="flex h-12 items-center justify-center text-xs text-gray-500"
|
|
3119
6523
|
>
|
|
3120
6524
|
<div class="flex items-center gap-2 text-gray-500">
|
|
3121
6525
|
<span class="text-lg text-gray-400"
|
|
@@ -3130,32 +6534,20 @@ ${prettyEvent}</pre
|
|
|
3130
6534
|
</div>
|
|
3131
6535
|
|
|
3132
6536
|
<!-- Current Messages Section -->
|
|
3133
|
-
<div class="
|
|
3134
|
-
<div class="
|
|
3135
|
-
<h4
|
|
3136
|
-
Current Messages
|
|
3137
|
-
</h4>
|
|
6537
|
+
<div class="cpk-section-card">
|
|
6538
|
+
<div class="cpk-section-header">
|
|
6539
|
+
<h4>Current Messages</h4>
|
|
3138
6540
|
</div>
|
|
3139
6541
|
<div class="overflow-auto">
|
|
3140
6542
|
${
|
|
3141
6543
|
messages && messages.length > 0
|
|
3142
6544
|
? html`
|
|
3143
|
-
<
|
|
3144
|
-
<
|
|
3145
|
-
<
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
Role
|
|
3150
|
-
</th>
|
|
3151
|
-
<th
|
|
3152
|
-
class="px-4 py-2 text-left font-medium text-gray-700"
|
|
3153
|
-
>
|
|
3154
|
-
Content
|
|
3155
|
-
</th>
|
|
3156
|
-
</tr>
|
|
3157
|
-
</thead>
|
|
3158
|
-
<tbody class="divide-y divide-gray-200">
|
|
6545
|
+
<div class="w-full text-xs">
|
|
6546
|
+
<div class="flex bg-gray-50">
|
|
6547
|
+
<div class="w-40 shrink-0 px-4 py-2 font-medium text-gray-700">Role</div>
|
|
6548
|
+
<div class="flex-1 px-4 py-2 font-medium text-gray-700">Content</div>
|
|
6549
|
+
</div>
|
|
6550
|
+
<div class="divide-y divide-gray-200">
|
|
3159
6551
|
${messages.map((msg) => {
|
|
3160
6552
|
const role = msg.role || "unknown";
|
|
3161
6553
|
const roleColors: Record<string, string> = {
|
|
@@ -3173,27 +6565,23 @@ ${prettyEvent}</pre
|
|
|
3173
6565
|
toolCalls.length > 0 ? "Invoked tool call" : "—";
|
|
3174
6566
|
|
|
3175
6567
|
return html`
|
|
3176
|
-
<
|
|
3177
|
-
<
|
|
6568
|
+
<div class="flex items-start">
|
|
6569
|
+
<div class="w-40 shrink-0 px-4 py-2">
|
|
3178
6570
|
<span
|
|
3179
|
-
class="inline-flex rounded px-2 py-0.5 text-[10px] font-medium ${
|
|
3180
|
-
roleColors[role] || roleColors.unknown
|
|
3181
|
-
}"
|
|
6571
|
+
class="inline-flex rounded px-2 py-0.5 text-[10px] font-medium ${roleColors[role] || roleColors.unknown}"
|
|
3182
6572
|
>
|
|
3183
6573
|
${role}
|
|
3184
6574
|
</span>
|
|
3185
|
-
</
|
|
3186
|
-
<
|
|
6575
|
+
</div>
|
|
6576
|
+
<div class="flex-1 px-4 py-2">
|
|
3187
6577
|
${
|
|
3188
6578
|
hasContent
|
|
3189
6579
|
? html`<div
|
|
3190
|
-
class="
|
|
6580
|
+
class="whitespace-pre-line break-words text-gray-700"
|
|
3191
6581
|
>
|
|
3192
6582
|
${rawContent}
|
|
3193
6583
|
</div>`
|
|
3194
|
-
: html`<div
|
|
3195
|
-
class="text-xs italic text-gray-400"
|
|
3196
|
-
>
|
|
6584
|
+
: html`<div class="italic text-gray-400">
|
|
3197
6585
|
${contentFallback}
|
|
3198
6586
|
</div>`
|
|
3199
6587
|
}
|
|
@@ -3202,16 +6590,16 @@ ${prettyEvent}</pre
|
|
|
3202
6590
|
? this.renderToolCallDetails(toolCalls)
|
|
3203
6591
|
: nothing
|
|
3204
6592
|
}
|
|
3205
|
-
</
|
|
3206
|
-
</
|
|
6593
|
+
</div>
|
|
6594
|
+
</div>
|
|
3207
6595
|
`;
|
|
3208
6596
|
})}
|
|
3209
|
-
</
|
|
3210
|
-
</
|
|
6597
|
+
</div>
|
|
6598
|
+
</div>
|
|
3211
6599
|
`
|
|
3212
6600
|
: html`
|
|
3213
6601
|
<div
|
|
3214
|
-
class="flex h-
|
|
6602
|
+
class="flex h-12 items-center justify-center text-xs text-gray-500"
|
|
3215
6603
|
>
|
|
3216
6604
|
<div class="flex items-center gap-2 text-gray-500">
|
|
3217
6605
|
<span class="text-lg text-gray-400"
|
|
@@ -3300,22 +6688,51 @@ ${prettyEvent}</pre
|
|
|
3300
6688
|
return;
|
|
3301
6689
|
}
|
|
3302
6690
|
|
|
6691
|
+
const previousMenu = this.selectedMenu;
|
|
3303
6692
|
this.selectedMenu = key;
|
|
3304
6693
|
|
|
3305
|
-
// If switching to agents view and "all-agents" is selected, switch to
|
|
6694
|
+
// If switching to agents view and "all-agents" is selected, switch to the most recently active agent
|
|
3306
6695
|
if (key === "agents" && this.selectedContext === "all-agents") {
|
|
3307
6696
|
const agentOptions = this.contextOptions.filter(
|
|
3308
6697
|
(opt) => opt.key !== "all-agents",
|
|
3309
6698
|
);
|
|
3310
6699
|
if (agentOptions.length > 0) {
|
|
3311
|
-
//
|
|
3312
|
-
const
|
|
3313
|
-
|
|
3314
|
-
|
|
6700
|
+
// Pick the agent with the most recent activity; fall back to first
|
|
6701
|
+
const mostRecent = agentOptions.reduce<{
|
|
6702
|
+
key: string;
|
|
6703
|
+
ts: number;
|
|
6704
|
+
} | null>((best, opt) => {
|
|
6705
|
+
const ts = this.getAgentStats(opt.key).lastActivity ?? -1;
|
|
6706
|
+
return best === null || ts > best.ts ? { key: opt.key, ts } : best;
|
|
6707
|
+
}, null);
|
|
6708
|
+
this.selectedContext = mostRecent
|
|
6709
|
+
? mostRecent.key
|
|
3315
6710
|
: agentOptions[0]!.key;
|
|
3316
6711
|
}
|
|
3317
6712
|
}
|
|
3318
6713
|
|
|
6714
|
+
// If leaving the agents view with multiple agents registered, restore
|
|
6715
|
+
// "all-agents" so the Events tab isn't silently filtered to one agent.
|
|
6716
|
+
if (previousMenu === "agents" && key !== "agents") {
|
|
6717
|
+
const agentCount = this.contextOptions.filter(
|
|
6718
|
+
(opt) => opt.key !== "all-agents",
|
|
6719
|
+
).length;
|
|
6720
|
+
if (agentCount > 1) {
|
|
6721
|
+
this.selectedContext = "all-agents";
|
|
6722
|
+
}
|
|
6723
|
+
}
|
|
6724
|
+
|
|
6725
|
+
if (key === "threads") {
|
|
6726
|
+
this.autoSelectLatestThread();
|
|
6727
|
+
}
|
|
6728
|
+
|
|
6729
|
+
if (key === "ag-ui-events" || key === "agents") {
|
|
6730
|
+
requestAnimationFrame(() => {
|
|
6731
|
+
const scroller = this.shadowRoot?.getElementById("cpk-main-scroll");
|
|
6732
|
+
if (scroller) scroller.scrollTop = 0;
|
|
6733
|
+
});
|
|
6734
|
+
}
|
|
6735
|
+
|
|
3319
6736
|
this.contextMenuOpen = false;
|
|
3320
6737
|
this.persistState();
|
|
3321
6738
|
this.requestUpdate();
|
|
@@ -3948,9 +7365,19 @@ ${prettyEvent}</pre
|
|
|
3948
7365
|
<div class="mb-3">
|
|
3949
7366
|
<h5 class="mb-1 text-xs font-semibold text-gray-700">ID</h5>
|
|
3950
7367
|
<code
|
|
3951
|
-
class="
|
|
7368
|
+
class="font-mono text-xs font-medium text-gray-800 flex-1 truncate min-w-0"
|
|
3952
7369
|
>${id}</code
|
|
3953
7370
|
>
|
|
7371
|
+
<button
|
|
7372
|
+
type="button"
|
|
7373
|
+
class="cpk-copy-btn"
|
|
7374
|
+
@click=${(e: Event) => {
|
|
7375
|
+
e.stopPropagation();
|
|
7376
|
+
void this.copyContextValue(id, `${id}:id`);
|
|
7377
|
+
}}
|
|
7378
|
+
>
|
|
7379
|
+
${this.copiedContextItems.has(`${id}:id`) ? "✓" : "Copy"}
|
|
7380
|
+
</button>
|
|
3954
7381
|
</div>
|
|
3955
7382
|
${
|
|
3956
7383
|
hasValue
|
|
@@ -3960,8 +7387,8 @@ ${prettyEvent}</pre
|
|
|
3960
7387
|
Value
|
|
3961
7388
|
</h5>
|
|
3962
7389
|
<button
|
|
3963
|
-
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"
|
|
3964
7390
|
type="button"
|
|
7391
|
+
class="cpk-copy-btn"
|
|
3965
7392
|
@click=${(e: Event) => {
|
|
3966
7393
|
e.stopPropagation();
|
|
3967
7394
|
void this.copyContextValue(context.value, id);
|
|
@@ -3974,15 +7401,6 @@ ${prettyEvent}</pre
|
|
|
3974
7401
|
}
|
|
3975
7402
|
</button>
|
|
3976
7403
|
</div>
|
|
3977
|
-
<div
|
|
3978
|
-
class="rounded-md border border-gray-200 bg-white p-3"
|
|
3979
|
-
>
|
|
3980
|
-
<pre
|
|
3981
|
-
class="overflow-auto text-xs text-gray-800 max-h-96"
|
|
3982
|
-
><code>${this.formatContextValue(
|
|
3983
|
-
context.value,
|
|
3984
|
-
)}</code></pre>
|
|
3985
|
-
</div>
|
|
3986
7404
|
`
|
|
3987
7405
|
: html`
|
|
3988
7406
|
<div class="flex items-center justify-center py-4 text-xs text-gray-500">
|
|
@@ -4004,7 +7422,7 @@ ${prettyEvent}</pre
|
|
|
4004
7422
|
}
|
|
4005
7423
|
|
|
4006
7424
|
if (typeof value === "string") {
|
|
4007
|
-
return value.length > 50 ? `${value.
|
|
7425
|
+
return value.length > 50 ? `${value.slice(0, 50)}...` : value;
|
|
4008
7426
|
}
|
|
4009
7427
|
|
|
4010
7428
|
if (typeof value === "number" || typeof value === "boolean") {
|
|
@@ -4112,70 +7530,34 @@ ${prettyEvent}</pre
|
|
|
4112
7530
|
this.requestUpdate();
|
|
4113
7531
|
}
|
|
4114
7532
|
|
|
4115
|
-
private
|
|
4116
|
-
if (!this.isOpen) {
|
|
4117
|
-
return nothing;
|
|
4118
|
-
}
|
|
4119
|
-
|
|
4120
|
-
// Ensure loading is triggered even if we mounted in an already-open state
|
|
4121
|
-
this.ensureAnnouncementLoading();
|
|
4122
|
-
|
|
7533
|
+
private renderAnnouncementBanner() {
|
|
4123
7534
|
if (!this.hasUnseenAnnouncement) {
|
|
4124
7535
|
return nothing;
|
|
4125
7536
|
}
|
|
4126
7537
|
|
|
4127
|
-
if (!this.announcementLoaded && !this.
|
|
4128
|
-
return html`<div
|
|
4129
|
-
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)]"
|
|
4130
|
-
>
|
|
4131
|
-
<div class="flex items-center gap-2 font-semibold">
|
|
4132
|
-
<span
|
|
4133
|
-
class="inline-flex h-6 w-6 items-center justify-center rounded-md bg-slate-900 text-white shadow-sm"
|
|
4134
|
-
>
|
|
4135
|
-
${this.renderIcon("Megaphone")}
|
|
4136
|
-
</span>
|
|
4137
|
-
<span>Loading latest announcement…</span>
|
|
4138
|
-
</div>
|
|
4139
|
-
</div>`;
|
|
4140
|
-
}
|
|
4141
|
-
|
|
4142
|
-
if (this.announcementLoadError) {
|
|
7538
|
+
if (!this.announcementLoaded && !this.announcementHtml) {
|
|
4143
7539
|
return html`<div
|
|
4144
|
-
class="
|
|
7540
|
+
class="flex items-center gap-2 px-4 py-3 text-sm font-semibold text-slate-800"
|
|
4145
7541
|
>
|
|
4146
|
-
<
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
<span>Announcement unavailable</span>
|
|
4153
|
-
</div>
|
|
4154
|
-
<p class="mt-2 text-xs text-rose-800">
|
|
4155
|
-
We couldn’t load the latest notice. Please try opening the inspector
|
|
4156
|
-
again.
|
|
4157
|
-
</p>
|
|
7542
|
+
<span
|
|
7543
|
+
class="inline-flex h-6 w-6 items-center justify-center rounded-md bg-slate-900 text-white shadow-sm"
|
|
7544
|
+
>
|
|
7545
|
+
${this.renderIcon("Megaphone")}
|
|
7546
|
+
</span>
|
|
7547
|
+
<span>Loading latest announcement…</span>
|
|
4158
7548
|
</div>`;
|
|
4159
7549
|
}
|
|
4160
7550
|
|
|
4161
|
-
if (!this.
|
|
7551
|
+
if (!this.announcementHtml) {
|
|
4162
7552
|
return nothing;
|
|
4163
7553
|
}
|
|
4164
7554
|
|
|
4165
|
-
|
|
4166
|
-
? unsafeHTML(this.announcementHtml)
|
|
4167
|
-
: html`<pre class="whitespace-pre-wrap text-sm text-gray-900">
|
|
4168
|
-
${this.announcementMarkdown}</pre
|
|
4169
|
-
>`;
|
|
4170
|
-
|
|
4171
|
-
return html`<div
|
|
4172
|
-
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)]"
|
|
4173
|
-
>
|
|
7555
|
+
return html`<div class="mx-4 mb-3 rounded-xl border border-slate-200 bg-white px-4 py-3">
|
|
4174
7556
|
<div
|
|
4175
|
-
class="mb-
|
|
7557
|
+
class="mb-2 flex items-center gap-2 text-xs font-semibold text-slate-900"
|
|
4176
7558
|
>
|
|
4177
7559
|
<span
|
|
4178
|
-
class="inline-flex h-
|
|
7560
|
+
class="inline-flex h-5 w-5 items-center justify-center rounded-md bg-slate-900 text-white shadow-sm"
|
|
4179
7561
|
>
|
|
4180
7562
|
${this.renderIcon("Megaphone")}
|
|
4181
7563
|
</span>
|
|
@@ -4186,12 +7568,31 @@ ${this.announcementMarkdown}</pre
|
|
|
4186
7568
|
@click=${this.handleDismissAnnouncement}
|
|
4187
7569
|
aria-label="Dismiss announcement"
|
|
4188
7570
|
>
|
|
4189
|
-
|
|
7571
|
+
${this.renderIcon("X")}
|
|
4190
7572
|
</button>
|
|
4191
7573
|
</div>
|
|
4192
|
-
<div class="announcement-
|
|
4193
|
-
|
|
7574
|
+
<div class="announcement-body ${this.announcementExpanded ? "announcement-body--expanded" : "announcement-body--collapsed"}">
|
|
7575
|
+
<div class="announcement-content">
|
|
7576
|
+
${unsafeHTML(this.announcementHtml)}
|
|
7577
|
+
</div>
|
|
7578
|
+
${
|
|
7579
|
+
!this.announcementExpanded
|
|
7580
|
+
? html`
|
|
7581
|
+
<div class="announcement-fade"></div>
|
|
7582
|
+
`
|
|
7583
|
+
: nothing
|
|
7584
|
+
}
|
|
4194
7585
|
</div>
|
|
7586
|
+
<button
|
|
7587
|
+
class="announcement-toggle"
|
|
7588
|
+
type="button"
|
|
7589
|
+
@click=${() => {
|
|
7590
|
+
this.announcementExpanded = !this.announcementExpanded;
|
|
7591
|
+
this.requestUpdate();
|
|
7592
|
+
}}
|
|
7593
|
+
>
|
|
7594
|
+
${this.announcementExpanded ? "Show less ↑" : "Show more ↓"}
|
|
7595
|
+
</button>
|
|
4195
7596
|
</div>`;
|
|
4196
7597
|
}
|
|
4197
7598
|
|
|
@@ -4266,7 +7667,6 @@ ${this.announcementMarkdown}</pre
|
|
|
4266
7667
|
|
|
4267
7668
|
this.announcementTimestamp = timestamp;
|
|
4268
7669
|
this.announcementPreviewText = previewText ?? "";
|
|
4269
|
-
this.announcementMarkdown = markdown;
|
|
4270
7670
|
this.hasUnseenAnnouncement =
|
|
4271
7671
|
(!storedTimestamp || storedTimestamp !== timestamp) &&
|
|
4272
7672
|
!!this.announcementPreviewText;
|
|
@@ -4276,7 +7676,11 @@ ${this.announcementMarkdown}</pre
|
|
|
4276
7676
|
|
|
4277
7677
|
this.requestUpdate();
|
|
4278
7678
|
} catch (error) {
|
|
4279
|
-
|
|
7679
|
+
// Swallowing here would hide non-network failures (malformed JSON, the
|
|
7680
|
+
// explicit "Malformed announcement payload" throw above, exceptions
|
|
7681
|
+
// from `convertMarkdownToHtml`). At minimum, surface in the console so
|
|
7682
|
+
// a stale announcement is debuggable.
|
|
7683
|
+
console.warn("[CopilotKit Inspector] Failed to load announcement", error);
|
|
4280
7684
|
this.announcementLoaded = true;
|
|
4281
7685
|
this.requestUpdate();
|
|
4282
7686
|
}
|