@copilotkit/web-inspector 1.69.0 → 1.69.1

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.
Files changed (43) hide show
  1. package/dist/index.cjs +2418 -152
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +372 -6
  4. package/dist/index.d.cts.map +1 -1
  5. package/dist/index.d.mts +372 -6
  6. package/dist/index.d.mts.map +1 -1
  7. package/dist/index.mjs +2419 -156
  8. package/dist/index.mjs.map +1 -1
  9. package/dist/index.umd.js +3134 -236
  10. package/dist/index.umd.js.map +1 -1
  11. package/dist/lib/event-snippets.cjs +559 -0
  12. package/dist/lib/event-snippets.cjs.map +1 -0
  13. package/dist/lib/event-snippets.d.cts +61 -0
  14. package/dist/lib/event-snippets.d.cts.map +1 -0
  15. package/dist/lib/event-snippets.d.mts +61 -0
  16. package/dist/lib/event-snippets.d.mts.map +1 -0
  17. package/dist/lib/event-snippets.mjs +537 -0
  18. package/dist/lib/event-snippets.mjs.map +1 -0
  19. package/dist/lib/home-briefing.cjs +23 -3
  20. package/dist/lib/home-briefing.cjs.map +1 -1
  21. package/dist/lib/home-briefing.mjs +23 -4
  22. package/dist/lib/home-briefing.mjs.map +1 -1
  23. package/dist/lib/inspector-nav.cjs +1 -0
  24. package/dist/lib/inspector-nav.cjs.map +1 -1
  25. package/dist/lib/inspector-nav.d.cts +11 -0
  26. package/dist/lib/inspector-nav.d.cts.map +1 -0
  27. package/dist/lib/inspector-nav.d.mts +11 -0
  28. package/dist/lib/inspector-nav.d.mts.map +1 -0
  29. package/dist/lib/inspector-nav.mjs +1 -0
  30. package/dist/lib/inspector-nav.mjs.map +1 -1
  31. package/dist/lib/telemetry.cjs +46 -1
  32. package/dist/lib/telemetry.cjs.map +1 -1
  33. package/dist/lib/telemetry.d.cts.map +1 -1
  34. package/dist/lib/telemetry.d.mts.map +1 -1
  35. package/dist/lib/telemetry.mjs +44 -2
  36. package/dist/lib/telemetry.mjs.map +1 -1
  37. package/dist/package.cjs +1 -1
  38. package/dist/package.mjs +1 -1
  39. package/dist/styles/generated.cjs +1 -1
  40. package/dist/styles/generated.cjs.map +1 -1
  41. package/dist/styles/generated.mjs +1 -1
  42. package/dist/styles/generated.mjs.map +1 -1
  43. package/package.json +3 -3
package/dist/index.mjs CHANGED
@@ -5,16 +5,17 @@ import { applyAnchorPosition, centerContext, clampSize, constrainToViewport, kee
5
5
  import { clearLegacyAnnouncementReadState, isValidAnchor, isValidDockMode, isValidPosition, isValidSize, loadAnnouncementPulsedTimestamp, loadAnnouncementReadTimestamp, loadInspectorState, saveAnnouncementPulsedTimestamp, saveAnnouncementReadTimestamp, saveInspectorState } from "./lib/persistence.mjs";
6
6
  import { buildPopOutFeatures, ensureBrandFont, openPopOutWindow } from "./lib/pop-out.mjs";
7
7
  import { projectInspectorMetadata } from "./lib/inspector-metadata.mjs";
8
- import { buildHomeModel } from "./lib/home-briefing.mjs";
8
+ import { buildHomeModel, runtimeConnectionNeedsAttention } from "./lib/home-briefing.mjs";
9
9
  import { INSPECTOR_GROUPS, INSPECTOR_NAV_SECTIONS, getGroupForMenu, isInspectorMenuKey, shouldUseIconRail } from "./lib/inspector-nav.mjs";
10
10
  import { selectVisibleRealThreadId } from "./lib/thread-selection.mjs";
11
- import { TELEMETRY_DOCS_URL, ensureTelemetryDistinctId, getRuntimeUrlType, getTelemetryDistinctIdForUrl, maybeShowDisclosure, trackHomeCtaClicked, trackHomeViewed, trackInspectorOpened, trackMemoriesTabClicked, trackMetadataActionClicked, trackMetadataModuleViewed, trackTalkToEngineerClicked, trackThreadsEmptyEnabledViewed, trackThreadsEnabledViewed, trackThreadsExampleSelected, trackThreadsExampleTourCompleted, trackThreadsExampleTourDismissed, trackThreadsExampleTourReopened, trackThreadsExampleTourStarted, trackThreadsExampleTourStepViewed, trackThreadsExampleViewed, trackThreadsIntelligenceSignupClicked, trackThreadsLockedViewed, trackThreadsTabClicked, trackThreadsTalkToEngineerClicked, trackWhatsNewClicked, trackWhatsNewSignalViewed, trackWhatsNewViewed } from "./lib/telemetry.mjs";
11
+ import { ACTIVITY_STARTERS, compileActivityRecipe, compileChatSnippet, compileFromActivityMessage, compileReasoningRecipe, compileTextRecipe, compileToolCallRecipe, createSnippetId, deleteEventSnippet, editorStateFromSnippet, expandSnippetEventsForRun, exportEventSnippetsJson, groupEventSnippets, importEventSnippets, loadEventSnippets, parseSnippetEvents, recipeIconName, recipeIconWrapClass, recipeLabel, snippetContainsToolCall, snippetJsonIsRunnable, upsertEventSnippet } from "./lib/event-snippets.mjs";
12
+ import { TELEMETRY_DOCS_URL, ensureTelemetryDistinctId, getRuntimeUrlType, getTelemetryDistinctIdForUrl, maybeShowDisclosure, trackErrorSignalViewed, trackEventSnippetsRun, trackEventSnippetsSaved, trackHomeCtaClicked, trackHomeViewed, trackInspectorOpened, trackMemoriesTabClicked, trackMetadataActionClicked, trackMetadataModuleViewed, trackTalkToEngineerClicked, trackThreadsEmptyEnabledViewed, trackThreadsEnabledViewed, trackThreadsExampleSelected, trackThreadsExampleTourCompleted, trackThreadsExampleTourDismissed, trackThreadsExampleTourReopened, trackThreadsExampleTourStarted, trackThreadsExampleTourStepViewed, trackThreadsExampleViewed, trackThreadsIntelligenceSignupClicked, trackThreadsLockedViewed, trackThreadsTabClicked, trackThreadsTalkToEngineerClicked, trackWhatsNewClicked, trackWhatsNewSignalViewed, trackWhatsNewViewed } from "./lib/telemetry.mjs";
12
13
  import { LitElement, css, html, nothing, render, unsafeCSS } from "lit";
13
14
  import { marked } from "marked";
14
15
  import { styleMap } from "lit/directives/style-map.js";
15
16
  import { unsafeHTML } from "lit/directives/unsafe-html.js";
16
17
  import { icons } from "lucide";
17
- import { CopilotKitCoreRuntimeConnectionStatus, ɵcreateThreadStore, ɵselectMemories, ɵselectMemoriesAvailable, ɵselectMemoriesError, ɵselectMemoriesIsLoading, ɵselectMemoriesRealtimeStatus, ɵselectThreads, ɵselectThreadsError, ɵselectThreadsIsLoading } from "@copilotkit/core";
18
+ import { CopilotKitCoreErrorCode, CopilotKitCoreRuntimeConnectionStatus, createInspectorThreadRequestId, emitInspectorStopViewing, emitInspectorViewThread, isInspectorThreadBridgeEnabled, onInspectorActiveThread, onInspectorViewThreadResult, ɵcreateThreadStore, ɵinjectInspectorEvents, ɵresetInspectorInject, ɵselectMemories, ɵselectMemoriesAvailable, ɵselectMemoriesError, ɵselectMemoriesIsLoading, ɵselectMemoriesRealtimeStatus, ɵselectThreads, ɵselectThreadsError, ɵselectThreadsIsLoading } from "@copilotkit/core";
18
19
 
19
20
  //#region src/index.ts
20
21
  const WEB_INSPECTOR_TAG = "cpk-web-inspector";
@@ -33,11 +34,215 @@ const WHATS_NEW_VIEW_LABEL = "What's new";
33
34
  /** Menu key of the What's new leaf — the news signal's destination. */
34
35
  const WHATS_NEW_MENU_KEY = "whats-new";
35
36
  const NEWS_SIGNAL_ID = "whats-new";
36
- const NEWS_SIGNAL_TONE = "news";
37
- const NEWS_SIGNAL_COLOR = "#A78BFA";
38
- const NEWS_SIGNAL_CADENCE_MS = 2100;
39
- const NEWS_SIGNAL_DESTINATION = WHATS_NEW_MENU_KEY;
37
+ const LAUNCHER_SIGNAL_COLORS = {
38
+ news: "#A78BFA",
39
+ error: "#F87171"
40
+ };
41
+ /**
42
+ * The failure gesture, four phases in series:
43
+ *
44
+ * ```
45
+ * beat 400ms → open 250ms → hold 2500ms → close 250ms (3400ms total)
46
+ * ```
47
+ *
48
+ * Sequential rather than simultaneous, deliberately: the beat says *here*, the
49
+ * pill says *this*. The beat is short so the words arrive quickly.
50
+ *
51
+ * **All four durations live here and nowhere else.** The stylesheet reads the
52
+ * two animated phases as custom properties injected from these numbers rather
53
+ * than restating them, because they are taste and will be tuned by eye after
54
+ * the first live look — tuning the feel must be a number, not a refactor.
55
+ */
56
+ const ERROR_GESTURE_MS = {
57
+ beat: 400,
58
+ open: 250,
59
+ hold: 2500,
60
+ close: 250
61
+ };
62
+ /**
63
+ * The words the pill carries, which are the words the panel already carries.
64
+ *
65
+ * `Runtime error` is the System Health runtime tile's own label, and
66
+ * `Failed to load threads` is the Threads view's own heading. The outside and
67
+ * the inside must agree, so these are shared rather than paraphrased.
68
+ */
69
+ const RUNTIME_ERROR_LABEL = "Runtime error";
70
+ const THREADS_LOAD_ERROR_LABEL = "Failed to load threads";
71
+ const AGENT_RUN_FAILED_LABEL = "Agent run failed";
72
+ const TOOL_ERROR_LABEL = "Tool error";
73
+ const MEMORY_LOAD_ERROR_LABEL = "Failed to load learning data";
74
+ /**
75
+ * Copy on the landing view. Titles match the pill.
76
+ *
77
+ * The two fields differ in what they can promise. `advice` is about the
78
+ * reader's next move and is always true. `highlight` is a claim about *this
79
+ * view* — that the failed item is visible below — and the error carries no
80
+ * guarantee of that: a code mapped to `run` can arrive with no run in the
81
+ * buffer at all, and a tool error can arrive without the call id the
82
+ * highlight needs. So it is rendered only once the item is actually there.
83
+ * A card pointing at something the reader cannot find is worse than a card
84
+ * that stays quiet, because it sends them looking.
85
+ */
86
+ const EVENT_ERROR_GUIDANCE = {
87
+ run: {
88
+ title: AGENT_RUN_FAILED_LABEL,
89
+ highlight: "The failed run event is highlighted below."
90
+ },
91
+ tool: {
92
+ title: TOOL_ERROR_LABEL,
93
+ highlight: "The failed tool call is highlighted below."
94
+ },
95
+ memory: {
96
+ title: MEMORY_LOAD_ERROR_LABEL,
97
+ advice: "Confirm CopilotKit Intelligence is connected, then retry Learning."
98
+ }
99
+ };
100
+ /**
101
+ * The pill's second line, shared by every subject that carries a pill.
102
+ *
103
+ * **This is the one string in the feature that exists nowhere else in the
104
+ * product.** Every other word the launcher shows is word-identical to the
105
+ * panel, which is the standing rule; this line is a deliberate, owner-approved
106
+ * exception to it, because the pill became clickable and an invitation that
107
+ * says nothing is not an invitation.
108
+ *
109
+ * It is shown, never spoken: the polite live region carries the failure class
110
+ * alone. A screen-reader user cannot act on an instruction delivered through
111
+ * an announcement, and it would double the spoken length.
112
+ */
113
+ const PILL_SUBLINE_LABEL = "Open Inspector for details";
114
+ const HUD_OPEN_INSPECTOR_LABEL = "Open Inspector";
115
+ const HUD_THREADS_OFF_LABEL = "Turn on Threads";
116
+ const HUD_THREADS_ON_LABEL = "Threads on";
117
+ const HUD_INTELLIGENCE_OFF_LABEL = "Turn on Intelligence";
118
+ const HUD_INTELLIGENCE_ON_LABEL = "Intelligence connected";
119
+ const HUD_THREADS_OFF_DETAIL = "Inspect conversations from this app.";
120
+ const HUD_THREADS_ON_DETAIL = "Threads is on. Opens the Threads view.";
121
+ const HUD_INTELLIGENCE_OFF_DETAIL = "Connect Intelligence to use Threads and Learning.";
122
+ const HUD_INTELLIGENCE_ON_DETAIL = "Intelligence is connected. Opens Home.";
123
+ const HUD_LEARNING_OFF_LABEL = "Turn on Learning";
124
+ const HUD_LEARNING_ON_LABEL = "Learning on";
125
+ const HUD_LEARNING_OFF_DETAIL = "Connect Intelligence to use Learning.";
126
+ const HUD_LEARNING_ON_DETAIL = "Learning is on. Opens the Learning view.";
127
+ const HUD_OPEN_INSPECTOR_DETAIL = "Same as clicking the circle. Opens the full Inspector.";
128
+ const LAUNCHER_SIGNALS = {
129
+ "whats-new": {
130
+ tone: "news",
131
+ markerTarget: WHATS_NEW_MENU_KEY,
132
+ landingTarget: "home",
133
+ cadence: 2100,
134
+ priority: 0,
135
+ accessibleLabel: "new content"
136
+ },
137
+ connection: {
138
+ tone: "error",
139
+ markerTarget: "home",
140
+ landingTarget: "home",
141
+ cadence: ERROR_GESTURE_MS.beat,
142
+ priority: 5,
143
+ accessibleLabel: "runtime error",
144
+ pillLabel: RUNTIME_ERROR_LABEL
145
+ },
146
+ threads: {
147
+ tone: "error",
148
+ markerTarget: "threads",
149
+ landingTarget: "threads",
150
+ cadence: ERROR_GESTURE_MS.beat,
151
+ priority: 4,
152
+ accessibleLabel: "thread loading error",
153
+ pillLabel: THREADS_LOAD_ERROR_LABEL
154
+ },
155
+ run: {
156
+ tone: "error",
157
+ markerTarget: "ag-ui-events",
158
+ landingTarget: "ag-ui-events",
159
+ cadence: ERROR_GESTURE_MS.beat,
160
+ priority: 3,
161
+ accessibleLabel: "agent run failed",
162
+ pillLabel: AGENT_RUN_FAILED_LABEL
163
+ },
164
+ tool: {
165
+ tone: "error",
166
+ markerTarget: "agents",
167
+ landingTarget: "agents",
168
+ cadence: ERROR_GESTURE_MS.beat,
169
+ priority: 2,
170
+ accessibleLabel: "tool error",
171
+ pillLabel: TOOL_ERROR_LABEL
172
+ },
173
+ memory: {
174
+ tone: "error",
175
+ markerTarget: "memories",
176
+ landingTarget: "memories",
177
+ cadence: ERROR_GESTURE_MS.beat,
178
+ priority: 1,
179
+ accessibleLabel: "learning error",
180
+ pillLabel: MEMORY_LOAD_ERROR_LABEL
181
+ }
182
+ };
183
+ /** Highest priority first, so the winner of the single dot is the head. */
184
+ const LAUNCHER_SIGNAL_PRIORITY_ORDER = Object.keys(LAUNCHER_SIGNALS).sort((a, b) => LAUNCHER_SIGNALS[b].priority - LAUNCHER_SIGNALS[a].priority);
185
+ /** Wiring *state* — red until the problem heals. */
186
+ const WIRING_ERROR_KEYS = ["connection", "threads"];
187
+ /** Unread *events* — red until the landing view is read. */
188
+ const EVENT_ERROR_KEYS = [
189
+ "run",
190
+ "tool",
191
+ "memory"
192
+ ];
193
+ function isWiringErrorKey(key) {
194
+ return WIRING_ERROR_KEYS.includes(key);
195
+ }
196
+ /**
197
+ * Takes a plain string rather than a `LauncherSignalKey`, because one caller
198
+ * reads the subject back out of a `data-` attribute, where the DOM can only
199
+ * offer `string | undefined`. Narrowing untrusted input is what a guard is
200
+ * for; `LauncherSignalKey` still satisfies the parameter, so the callers that
201
+ * already hold one are unaffected.
202
+ */
203
+ function isEventErrorKey(key) {
204
+ return EVENT_ERROR_KEYS.includes(key);
205
+ }
206
+ /** Narrows a signal key to an error source, excluding the announcement. */
207
+ function isErrorSignalKey(key) {
208
+ return isWiringErrorKey(key) || isEventErrorKey(key);
209
+ }
210
+ /**
211
+ * The control range is the point, not an oversight: an attribute selector has
212
+ * to escape those characters too, and CSS.escape — which the fallback below
213
+ * stands in for — escapes them as well. Hoisted so the suppression can sit on
214
+ * the pattern rather than three lines above it.
215
+ */
216
+ const SELECTOR_ESCAPE_PATTERN = /[\0-\x1f\x7f!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g;
217
+ /** Attribute selector value. jsdom does not implement CSS.escape. */
218
+ function escapeSelectorValue(value) {
219
+ if (typeof CSS !== "undefined" && typeof CSS.escape === "function") return CSS.escape(value);
220
+ return value.replace(SELECTOR_ESCAPE_PATTERN, "\\$&");
221
+ }
222
+ function eventErrorKeyForCode(code) {
223
+ switch (code) {
224
+ case CopilotKitCoreErrorCode.TOOL_NOT_FOUND:
225
+ case CopilotKitCoreErrorCode.TOOL_HANDLER_FAILED:
226
+ case CopilotKitCoreErrorCode.TOOL_ARGUMENT_PARSE_FAILED:
227
+ case CopilotKitCoreErrorCode.AGENT_NOT_FOUND: return "tool";
228
+ case CopilotKitCoreErrorCode.AGENT_CONNECT_FAILED:
229
+ case CopilotKitCoreErrorCode.AGENT_RUN_FAILED:
230
+ case CopilotKitCoreErrorCode.AGENT_RUN_FAILED_EVENT:
231
+ case CopilotKitCoreErrorCode.AGENT_RUN_ERROR_EVENT: return "run";
232
+ default: return null;
233
+ }
234
+ }
235
+ /**
236
+ * Coalesce inspector-owned GET /threads sends. The first refresh goes out
237
+ * at once. Further calls in this window share one trailing request, so a
238
+ * flaky network does not fire a burst of list fetches and error cards.
239
+ */
240
+ const THREAD_LIST_DEBOUNCE_MS = 300;
241
+ /** The launcher's accessible name with nothing wrong. */
242
+ const LAUNCHER_BASE_LABEL = "Web Inspector";
40
243
  const EDGE_MARGIN = 16;
244
+ /** HUD card plus the hover bridge. Used to pick left vs right. */
245
+ const LAUNCHER_HUD_WIDTH = 248;
41
246
  const DRAG_THRESHOLD = 6;
42
247
  const MIN_WINDOW_WIDTH = 880;
43
248
  const MIN_WINDOW_WIDTH_DOCKED_LEFT = 640;
@@ -513,6 +718,7 @@ var CpkThreadList = class extends PortableLitElement {
513
718
  super(..._args);
514
719
  this.threads = [];
515
720
  this.selectedThreadId = null;
721
+ this.inAppThreadId = null;
516
722
  this.errorMessage = null;
517
723
  this.suppressEmptyState = false;
518
724
  this._query = "";
@@ -524,6 +730,7 @@ var CpkThreadList = class extends PortableLitElement {
524
730
  this.properties = {
525
731
  threads: { attribute: false },
526
732
  selectedThreadId: { attribute: false },
733
+ inAppThreadId: { attribute: false },
527
734
  errorMessage: { attribute: false },
528
735
  suppressEmptyState: { attribute: false },
529
736
  _query: { state: true }
@@ -674,6 +881,11 @@ var CpkThreadList = class extends PortableLitElement {
674
881
  color: #087653;
675
882
  }
676
883
 
884
+ .cpk-tl__pill--in-app {
885
+ background: #bec2ff;
886
+ color: #010507;
887
+ }
888
+
677
889
  /* ── Empty state ── */
678
890
  .cpk-tl__empty {
679
891
  padding: 32px 16px;
@@ -797,6 +1009,9 @@ var CpkThreadList = class extends PortableLitElement {
797
1009
  ${thread.isExample ? html`
798
1010
  <span class="cpk-tl__pill cpk-tl__pill--example">Example</span>
799
1011
  ` : nothing}
1012
+ ${this.inAppThreadId === thread.id ? html`
1013
+ <span class="cpk-tl__pill cpk-tl__pill--in-app">In app</span>
1014
+ ` : nothing}
800
1015
  </span>
801
1016
  </button>
802
1017
  `)}
@@ -863,6 +1078,8 @@ var CpkThreadInspector = class CpkThreadInspector extends PortableLitElement {
863
1078
  this.agentStateInput = null;
864
1079
  this.agentEventsInput = [];
865
1080
  this.liveMessageVersion = 0;
1081
+ this.viewInAppMode = "hidden";
1082
+ this.viewInAppError = null;
866
1083
  this.focusMessageId = null;
867
1084
  this.focusRequestId = 0;
868
1085
  this._tab = "timeline";
@@ -936,6 +1153,8 @@ var CpkThreadInspector = class CpkThreadInspector extends PortableLitElement {
936
1153
  agentStateInput: { attribute: false },
937
1154
  agentEventsInput: { attribute: false },
938
1155
  liveMessageVersion: { attribute: false },
1156
+ viewInAppMode: { attribute: false },
1157
+ viewInAppError: { attribute: false },
939
1158
  focusMessageId: { attribute: false },
940
1159
  focusRequestId: { attribute: false },
941
1160
  _tab: { state: true },
@@ -1258,6 +1477,37 @@ var CpkThreadInspector = class CpkThreadInspector extends PortableLitElement {
1258
1477
  overflow-wrap: anywhere;
1259
1478
  }
1260
1479
 
1480
+ .cpk-td__view-in-app {
1481
+ appearance: none;
1482
+ flex-shrink: 0;
1483
+ margin: 0;
1484
+ border: 1px solid #5558b2;
1485
+ border-radius: 6px;
1486
+ background: #5558b2;
1487
+ color: #ffffff;
1488
+ font-family: "Plus Jakarta Sans", sans-serif;
1489
+ font-size: 11px;
1490
+ font-weight: 600;
1491
+ padding: 5px 10px;
1492
+ cursor: pointer;
1493
+ }
1494
+
1495
+ .cpk-td__view-in-app:focus-visible {
1496
+ outline: 2px solid #010507;
1497
+ outline-offset: 2px;
1498
+ }
1499
+
1500
+ .cpk-td__view-in-app--stop {
1501
+ background: #ffffff;
1502
+ color: #5558b2;
1503
+ }
1504
+
1505
+ .cpk-td__view-in-app-error {
1506
+ flex-basis: 100%;
1507
+ color: #c0333a;
1508
+ font-size: 11px;
1509
+ }
1510
+
1261
1511
  /*
1262
1512
  * Each tab's content is wrapped in this panel so the keep-mounted
1263
1513
  * inactive panels can be hidden via display:none without disturbing
@@ -2850,8 +3100,32 @@ var CpkThreadInspector = class CpkThreadInspector extends PortableLitElement {
2850
3100
  </span>
2851
3101
  `)}
2852
3102
  </div>
3103
+ ${this.renderViewInAppAction()}
2853
3104
  ${bulkControls}
2854
3105
  </div>
3106
+ `;
3107
+ }
3108
+ renderViewInAppAction() {
3109
+ if (this.viewInAppMode === "hidden") return nothing;
3110
+ const isStop = this.viewInAppMode === "stop";
3111
+ return html`
3112
+ <button
3113
+ type="button"
3114
+ class="cpk-td__view-in-app ${isStop ? "cpk-td__view-in-app--stop" : ""}"
3115
+ data-testid="cpk-inspector-view-in-app"
3116
+ aria-label=${isStop ? "Stop viewing this thread in the app" : "View this thread in your app"}
3117
+ @click=${() => {
3118
+ this.dispatchEvent(new CustomEvent(isStop ? "stopViewing" : "viewInApp", {
3119
+ bubbles: true,
3120
+ composed: true
3121
+ }));
3122
+ }}
3123
+ >
3124
+ ${isStop ? "Stop viewing" : "View in your app"}
3125
+ </button>
3126
+ ${this.viewInAppError ? html`<span class="cpk-td__view-in-app-error" role="alert"
3127
+ >${this.viewInAppError}</span
3128
+ >` : nothing}
2855
3129
  `;
2856
3130
  }
2857
3131
  renderActiveBulkControls() {
@@ -4131,7 +4405,33 @@ var WebInspectorElement = class WebInspectorElement extends LitElement {
4131
4405
  inspect: "ag-ui-events"
4132
4406
  };
4133
4407
  this.lastScrolledAgentNavigationLayout = null;
4408
+ this.eventSnippets = [];
4409
+ this.selectedSnippetId = null;
4410
+ this.snippetRecipe = "tool-call";
4411
+ this.snippetName = "";
4412
+ this.snippetJson = "[]";
4413
+ this.snippetToolName = "";
4414
+ this.snippetToolArgs = "{}";
4415
+ this.snippetReasoningText = "";
4416
+ this.snippetTextContent = "";
4417
+ this.snippetActivityType = "a2ui-surface";
4418
+ this.snippetActivityContent = "{}";
4419
+ this.snippetError = null;
4420
+ this.snippetBanner = null;
4421
+ this.snippetConfirmOpen = false;
4422
+ this.lastInject = null;
4423
+ this.snippetListWidth = 200;
4424
+ this.snippetDividerResizing = false;
4425
+ this.snippetDividerPointerId = -1;
4426
+ this.snippetDividerStartX = 0;
4427
+ this.snippetDividerStartWidth = 0;
4134
4428
  this.selectedThreadId = null;
4429
+ this.inAppThreadId = null;
4430
+ this.inAppAgentId = null;
4431
+ this.inAppSource = null;
4432
+ this.activeViewInAppRequestId = null;
4433
+ this.viewInAppError = null;
4434
+ this.inspectorBridgeUnsubscribers = [];
4135
4435
  this.selectedRealThreadIsExplicit = false;
4136
4436
  this.selectedLocalExampleThreadId = null;
4137
4437
  this.requestedThreadId = null;
@@ -4195,9 +4495,39 @@ var WebInspectorElement = class WebInspectorElement extends LitElement {
4195
4495
  this.announcementLoaded = false;
4196
4496
  this.announcementPromise = null;
4197
4497
  this.newsSignalArmed = false;
4198
- this.newsSignalPulsing = false;
4199
- this.newsSignalPulsePending = false;
4498
+ this.pulsingSignal = null;
4499
+ this.pendingPulseSignal = null;
4200
4500
  this.pulseTimeoutId = null;
4501
+ this.errorSignalArmed = {
4502
+ connection: false,
4503
+ threads: false
4504
+ };
4505
+ this.eventErrorArmed = {
4506
+ run: false,
4507
+ tool: false,
4508
+ memory: false
4509
+ };
4510
+ this.eventErrorDetails = {
4511
+ run: null,
4512
+ tool: null,
4513
+ memory: null
4514
+ };
4515
+ this.pendingScrollToEventId = null;
4516
+ this.pendingScrollToToolCallId = null;
4517
+ this.threadRefreshLastSentAt = /* @__PURE__ */ new Map();
4518
+ this.threadRefreshTrailingTimers = /* @__PURE__ */ new Map();
4519
+ this.errorBeatSpent = false;
4520
+ this.errorSignalViewedSources = /* @__PURE__ */ new Set();
4521
+ this.gestureSignal = null;
4522
+ this.pillPhase = null;
4523
+ this.pillDirection = null;
4524
+ this.pillTimeoutId = null;
4525
+ this.launcherHudOpen = false;
4526
+ this.launcherHudSide = "left";
4527
+ this.launcherHudHelp = null;
4528
+ this.launcherHudCloseTimer = null;
4529
+ this.hudLandingMenu = null;
4530
+ this.pillOutcome = null;
4201
4531
  this.viewedNewsSignalIds = /* @__PURE__ */ new Set();
4202
4532
  this.pendingNewsSignalViewed = null;
4203
4533
  this.viewedBannerSurfaces = /* @__PURE__ */ new Set();
@@ -4271,9 +4601,59 @@ var WebInspectorElement = class WebInspectorElement extends LitElement {
4271
4601
  this.customTabIcons = { 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>` };
4272
4602
  this.handleDocumentVisibilityChange = () => {
4273
4603
  this.accountCtaMotionPaused = document.visibilityState !== "visible";
4274
- if (document.visibilityState === "visible" && this.newsSignalPulsePending && !this.isOpen) this.startNewsSignalPulse();
4604
+ if (document.visibilityState === "visible" && !this.isOpen) this.flushPendingSignalPulse();
4605
+ this.requestUpdate();
4606
+ };
4607
+ this.handlePillClick = (event) => {
4608
+ event.preventDefault();
4609
+ event.stopPropagation();
4610
+ this.openInspector("floating_button");
4611
+ };
4612
+ this.handleLauncherHudEnter = () => {
4613
+ this.openLauncherHud();
4614
+ };
4615
+ this.handleLauncherHudLeave = () => {
4616
+ if (this.launcherHudCloseTimer !== null) clearTimeout(this.launcherHudCloseTimer);
4617
+ this.launcherHudCloseTimer = setTimeout(() => {
4618
+ this.launcherHudCloseTimer = null;
4619
+ this.closeLauncherHud();
4620
+ }, 160);
4621
+ };
4622
+ this.handleLauncherHudFocusIn = () => {
4623
+ this.openLauncherHud();
4624
+ };
4625
+ this.handleLauncherHudFocusOut = (event) => {
4626
+ const next = event.relatedTarget;
4627
+ const wrapper = event.currentTarget;
4628
+ if (next instanceof Node && wrapper instanceof Node && wrapper.contains(next)) return;
4629
+ this.closeLauncherHud();
4630
+ };
4631
+ this.handleLauncherHudKeydown = (event) => {
4632
+ if (event.key !== "Escape") return;
4633
+ if (!this.launcherHudOpen) return;
4634
+ event.preventDefault();
4635
+ event.stopPropagation();
4636
+ this.closeLauncherHud();
4637
+ this.activeRoot.querySelector(".console-button")?.focus();
4638
+ };
4639
+ this.handleHudActionClick = (event, row) => {
4640
+ event.preventDefault();
4641
+ event.stopPropagation();
4642
+ this.hudLandingMenu = row === "inspector" ? null : row === "threads" ? "threads" : row === "learning" ? "memories" : "home";
4643
+ this.closeLauncherHud();
4644
+ this.openInspector("floating_button");
4645
+ };
4646
+ this.handleHudHelpClick = (event, row) => {
4647
+ event.preventDefault();
4648
+ event.stopPropagation();
4649
+ this.launcherHudHelp = this.launcherHudHelp === row ? null : row;
4275
4650
  this.requestUpdate();
4276
4651
  };
4652
+ this.handleHudRowClick = (event, row) => {
4653
+ const target = event.target;
4654
+ if (target instanceof Element && target.closest(".cpk-launcher-hud__help, [data-cpk-hud-action]")) return;
4655
+ this.handleHudActionClick(event, row);
4656
+ };
4277
4657
  this.handleSidebarToggle = () => {
4278
4658
  this.sidebarRailTooltip = null;
4279
4659
  this.sidebarCollapsed = !this.sidebarCollapsed;
@@ -4305,6 +4685,17 @@ var WebInspectorElement = class WebInspectorElement extends LitElement {
4305
4685
  this.persistState();
4306
4686
  this.requestUpdate();
4307
4687
  };
4688
+ this.handleEventErrorBannerKeydown = (event) => {
4689
+ if (event.key !== "Enter" && event.key !== " ") return;
4690
+ event.preventDefault();
4691
+ this.refocusEventErrorLanding(event);
4692
+ };
4693
+ this.refocusEventErrorLanding = (event) => {
4694
+ const key = event.currentTarget?.dataset.cpkEventError;
4695
+ if (!key || !isEventErrorKey(key)) return;
4696
+ this.applyEventErrorLanding(key);
4697
+ this.requestUpdate();
4698
+ };
4308
4699
  this.handleSystemColorSchemeChange = (event) => {
4309
4700
  if (this.hasExplicitColorScheme) return;
4310
4701
  this.colorScheme = event.matches ? "dark" : "light";
@@ -4634,6 +5025,19 @@ var WebInspectorElement = class WebInspectorElement extends LitElement {
4634
5025
  this.handlePlaygroundStop = () => {
4635
5026
  this.playgroundAgent?.abortRun();
4636
5027
  };
5028
+ this.handleSnippetImportChange = async (event) => {
5029
+ const input = event.target;
5030
+ const file = input.files?.[0];
5031
+ input.value = "";
5032
+ if (!file) return;
5033
+ try {
5034
+ this.eventSnippets = importEventSnippets(await file.text());
5035
+ this.snippetError = null;
5036
+ } catch (error) {
5037
+ this.snippetError = error instanceof Error ? error.message : "Import failed.";
5038
+ }
5039
+ this.requestUpdate();
5040
+ };
4637
5041
  this.handleInspectorMetadataActionClick = (action, placement) => {
4638
5042
  if (!this.isOpen || this.runtimeStatus !== CopilotKitCoreRuntimeConnectionStatus.Connected || this.core?.telemetryDisabled) return;
4639
5043
  if (action.kind === "enable_intelligence") {
@@ -4661,6 +5065,26 @@ var WebInspectorElement = class WebInspectorElement extends LitElement {
4661
5065
  if (this.core?.telemetryDisabled) return;
4662
5066
  trackThreadsTalkToEngineerClicked(this.getThreadsCtaTelemetryProps("talk_to_engineer", "threads_locked"));
4663
5067
  };
5068
+ this.handleSnippetListDividerPointerDown = (event) => {
5069
+ this.snippetDividerResizing = true;
5070
+ this.snippetDividerPointerId = event.pointerId;
5071
+ this.snippetDividerStartX = event.clientX;
5072
+ this.snippetDividerStartWidth = this.snippetListWidth;
5073
+ event.currentTarget.setPointerCapture(event.pointerId);
5074
+ event.preventDefault();
5075
+ };
5076
+ this.handleSnippetListDividerPointerMove = (event) => {
5077
+ if (!this.snippetDividerResizing || this.snippetDividerPointerId !== event.pointerId) return;
5078
+ const delta = event.clientX - this.snippetDividerStartX;
5079
+ this.snippetListWidth = Math.max(160, Math.min(360, this.snippetDividerStartWidth + delta));
5080
+ this.requestUpdate();
5081
+ };
5082
+ this.handleSnippetListDividerPointerUp = (event) => {
5083
+ if (this.snippetDividerPointerId !== event.pointerId) return;
5084
+ const target = event.currentTarget;
5085
+ if (target.hasPointerCapture(this.snippetDividerPointerId)) target.releasePointerCapture(this.snippetDividerPointerId);
5086
+ this.snippetDividerResizing = false;
5087
+ };
4664
5088
  this.handleThreadDividerPointerDown = (event) => {
4665
5089
  this.threadDividerResizing = true;
4666
5090
  this.threadDividerPointerId = event.pointerId;
@@ -4681,6 +5105,40 @@ var WebInspectorElement = class WebInspectorElement extends LitElement {
4681
5105
  if (target.hasPointerCapture(this.threadDividerPointerId)) target.releasePointerCapture(this.threadDividerPointerId);
4682
5106
  this.threadDividerResizing = false;
4683
5107
  };
5108
+ this.handleViewInApp = () => {
5109
+ const thread = this.getSelectedRealThread();
5110
+ if (!thread) return;
5111
+ if (this.activeViewInAppRequestId && this.inAppAgentId) emitInspectorStopViewing({
5112
+ requestId: this.activeViewInAppRequestId,
5113
+ agentId: this.inAppAgentId
5114
+ });
5115
+ this.viewInAppError = null;
5116
+ const requestId = createInspectorThreadRequestId();
5117
+ this.activeViewInAppRequestId = requestId;
5118
+ if (!emitInspectorViewThread({
5119
+ requestId,
5120
+ threadId: thread.id,
5121
+ agentId: thread.agentId
5122
+ })) {
5123
+ this.activeViewInAppRequestId = null;
5124
+ this.inAppThreadId = null;
5125
+ this.inAppAgentId = null;
5126
+ this.inAppSource = null;
5127
+ this.viewInAppError = "No official chat for this agent is on the page.";
5128
+ }
5129
+ this.requestUpdate();
5130
+ };
5131
+ this.handleStopViewing = () => {
5132
+ const requestId = this.activeViewInAppRequestId;
5133
+ const agentId = this.inAppAgentId;
5134
+ if (!requestId || !agentId) return;
5135
+ this.viewInAppError = null;
5136
+ emitInspectorStopViewing({
5137
+ requestId,
5138
+ agentId
5139
+ });
5140
+ this.requestUpdate();
5141
+ };
4684
5142
  this.handleThreadsExampleOverviewVideoControl = () => {
4685
5143
  const video = this.threadsExampleOverviewVideoElement;
4686
5144
  if (!video) return;
@@ -4883,6 +5341,11 @@ var WebInspectorElement = class WebInspectorElement extends LitElement {
4883
5341
  label: "AG-UI Events",
4884
5342
  icon: "Zap"
4885
5343
  },
5344
+ {
5345
+ key: "event-snippets",
5346
+ label: "Event Snippets",
5347
+ icon: "Code"
5348
+ },
4886
5349
  {
4887
5350
  key: "agents",
4888
5351
  label: "Agent",
@@ -5101,7 +5564,7 @@ var WebInspectorElement = class WebInspectorElement extends LitElement {
5101
5564
  const statusSub = store.select(createThreadStoreStatusSelector()).subscribe(({ error, isLoading }) => {
5102
5565
  if (capabilityGeneration !== this.threadCapabilityGeneration || !this.areThreadEndpointsAvailable()) return;
5103
5566
  if (error) this._threadsErrorByAgent.set(agentId, error);
5104
- else this._threadsErrorByAgent.delete(agentId);
5567
+ else if (!isLoading) this._threadsErrorByAgent.delete(agentId);
5105
5568
  this._threadsLoadingByAgent.set(agentId, isLoading);
5106
5569
  this.requestUpdate();
5107
5570
  });
@@ -5115,7 +5578,7 @@ var WebInspectorElement = class WebInspectorElement extends LitElement {
5115
5578
  this._threadsLoadingByAgent.set(agentId, ɵselectThreadsIsLoading(initialState));
5116
5579
  const initialError = ɵselectThreadsError(initialState);
5117
5580
  if (initialError) this._threadsErrorByAgent.set(agentId, initialError);
5118
- else this._threadsErrorByAgent.delete(agentId);
5581
+ else if (!ɵselectThreadsIsLoading(initialState)) this._threadsErrorByAgent.delete(agentId);
5119
5582
  this.rebuildFlattenedThreads();
5120
5583
  this.autoSelectLatestThread();
5121
5584
  }
@@ -5196,8 +5659,28 @@ var WebInspectorElement = class WebInspectorElement extends LitElement {
5196
5659
  if (!this.areThreadEndpointsAvailable()) return;
5197
5660
  const store = this._ownedThreadStores.get(agentId);
5198
5661
  if (!store) return;
5662
+ const now = Date.now();
5663
+ const waitMs = THREAD_LIST_DEBOUNCE_MS - (now - (this.threadRefreshLastSentAt.get(agentId) ?? 0));
5664
+ if (waitMs <= 0) {
5665
+ this.sendOwnedThreadRefresh(agentId, store, now);
5666
+ return;
5667
+ }
5668
+ if (this.threadRefreshTrailingTimers.has(agentId)) return;
5669
+ this.threadRefreshTrailingTimers.set(agentId, setTimeout(() => {
5670
+ this.threadRefreshTrailingTimers.delete(agentId);
5671
+ const current = this._ownedThreadStores.get(agentId);
5672
+ if (!current) return;
5673
+ this.sendOwnedThreadRefresh(agentId, current, Date.now());
5674
+ }, waitMs));
5675
+ }
5676
+ sendOwnedThreadRefresh(agentId, store, sentAt) {
5677
+ this.threadRefreshLastSentAt.set(agentId, sentAt);
5199
5678
  store.refresh();
5200
5679
  }
5680
+ cancelThreadRefreshDebounce() {
5681
+ for (const timer of this.threadRefreshTrailingTimers.values()) clearTimeout(timer);
5682
+ this.threadRefreshTrailingTimers.clear();
5683
+ }
5201
5684
  updateOwnedThreadStoreHeaders(headers) {
5202
5685
  if (!this.areThreadEndpointsAvailable()) return;
5203
5686
  const core = this.core;
@@ -5253,6 +5736,7 @@ var WebInspectorElement = class WebInspectorElement extends LitElement {
5253
5736
  this.runtimeStatus = core.runtimeConnectionStatus;
5254
5737
  this.coreProperties = core.properties;
5255
5738
  this.lastCoreError = null;
5739
+ this.clearAllEventErrors();
5256
5740
  const supportsInspectorMetadata = this.coreSupportsInspectorMetadata(core);
5257
5741
  this.updateInspectorMetadataProjection(this.readCoreInspectorMetadata(core));
5258
5742
  this.coreSubscriber = {
@@ -5288,11 +5772,12 @@ var WebInspectorElement = class WebInspectorElement extends LitElement {
5288
5772
  this.updateInspectorMetadataProjection(inspectorMetadata);
5289
5773
  this.requestUpdate();
5290
5774
  } } : {},
5291
- onError: ({ code, error }) => {
5775
+ onError: ({ code, error, context }) => {
5292
5776
  this.lastCoreError = {
5293
5777
  code,
5294
5778
  message: error.message
5295
5779
  };
5780
+ this.armEventErrorFromCode(code, error.message, context);
5296
5781
  this.requestUpdate();
5297
5782
  },
5298
5783
  onAgentsChanged: ({ agents }) => {
@@ -5385,6 +5870,7 @@ var WebInspectorElement = class WebInspectorElement extends LitElement {
5385
5870
  }),
5386
5871
  memoryStore.select(ɵselectMemoriesError).subscribe((v) => {
5387
5872
  this._memoriesError = v;
5873
+ if (v) this.armEventError("memory", v.message);
5388
5874
  this.requestUpdate();
5389
5875
  }),
5390
5876
  memoryStore.select(ɵselectMemoriesAvailable).subscribe((v) => {
@@ -5471,10 +5957,12 @@ var WebInspectorElement = class WebInspectorElement extends LitElement {
5471
5957
  this._recallQuery = "";
5472
5958
  this.coreSubscriber = null;
5473
5959
  this.runtimeStatus = null;
5960
+ this.cancelThreadRefreshDebounce();
5474
5961
  this.inspectorMetadataValue = void 0;
5475
5962
  this.inspectorMetadataProjection = projectInspectorMetadata(void 0, void 0);
5476
5963
  this.metadataTelemetryFingerprints.clear();
5477
5964
  this.lastCoreError = null;
5965
+ this.clearAllEventErrors();
5478
5966
  this.coreProperties = {};
5479
5967
  this.cachedTools = [];
5480
5968
  this.toolSignature = "";
@@ -5788,6 +6276,19 @@ var WebInspectorElement = class WebInspectorElement extends LitElement {
5788
6276
  this.persistState();
5789
6277
  this.requestUpdate();
5790
6278
  }
6279
+ focusEventSnippets(options) {
6280
+ this.pendingPersistedMenu = null;
6281
+ this.selectedMenu = "event-snippets";
6282
+ this.settingsOpen = false;
6283
+ this.lastSelectedMenuByGroup.inspect = "event-snippets";
6284
+ this.contextMenuOpen = false;
6285
+ this.layoutMenuOpen = false;
6286
+ this.reloadEventSnippets();
6287
+ if (options.snippetId) this.selectEventSnippet(options.snippetId);
6288
+ if (options.agentId && this.contextOptions.some((option) => option.key === options.agentId)) this.selectedContext = options.agentId;
6289
+ this.persistState();
6290
+ this.requestUpdate();
6291
+ }
5791
6292
  filterEvents(events) {
5792
6293
  const query = this.eventFilterText.trim().toLowerCase();
5793
6294
  return events.filter((event) => {
@@ -5833,22 +6334,28 @@ var WebInspectorElement = class WebInspectorElement extends LitElement {
5833
6334
  }
5834
6335
  renderToolCallDetails(toolCalls) {
5835
6336
  if (!Array.isArray(toolCalls) || toolCalls.length === 0) return nothing;
6337
+ const toolError = this.eventErrorDetails.tool;
5836
6338
  return html`
5837
6339
  <div class="mt-2 space-y-2">
5838
6340
  ${toolCalls.map((call, index) => {
5839
6341
  const functionName = call.function?.name ?? call.toolName ?? "Unknown function";
5840
6342
  const callId = typeof call?.id === "string" ? call.id : `tool-call-${index + 1}`;
5841
6343
  const argsString = this.formatToolCallArguments(call.function?.arguments);
6344
+ const isFailedCall = toolError?.toolCallId !== void 0 && toolError.toolCallId === callId;
5842
6345
  return html`
5843
6346
  <div
5844
- class="rounded-md border border-gray-200 bg-gray-50 p-3 text-xs text-gray-700"
6347
+ class=${isFailedCall ? "rounded-md border border-rose-300 bg-rose-50 p-3 text-xs text-gray-900" : "rounded-md border border-gray-200 bg-gray-50 p-3 text-xs text-gray-700"}
6348
+ data-cpk-failed-tool-call=${isFailedCall ? callId : void 0}
5845
6349
  >
5846
6350
  <div
5847
6351
  class="flex flex-wrap items-center justify-between gap-1 font-medium text-gray-900"
5848
6352
  >
5849
- <span>${functionName}</span>
5850
- <span class="text-[10px] text-gray-500">ID: ${callId}</span>
6353
+ <span>${functionName}${isFailedCall ? " failed" : ""}</span>
6354
+ <span class="text-[10px] text-gray-600">ID: ${callId}</span>
5851
6355
  </div>
6356
+ ${isFailedCall && toolError?.message ? html`<p class="mt-2 break-words leading-relaxed text-gray-800">
6357
+ ${toolError.message}
6358
+ </p>` : nothing}
5852
6359
  ${argsString ? html`<pre
5853
6360
  class="mt-2 overflow-auto rounded bg-white p-2 text-[11px] leading-relaxed text-gray-800"
5854
6361
  >
@@ -6000,18 +6507,28 @@ ${argsString}</pre
6000
6507
  .console-button-wrapper {
6001
6508
  position: relative;
6002
6509
  display: inline-flex;
6003
- }
6004
-
6005
- .console-button {
6510
+ /* The launcher's surface and edge, shared by the button and the pill so
6511
+ the two cannot drift apart. A dark grey rather than near-black: the
6512
+ launcher sits on a customer's page, and 1,5,7 against white is a
6513
+ harder edge than this surface needs. */
6514
+ --cpk-launcher-face: rgba(28, 31, 36, 0.95);
6515
+ --cpk-launcher-face-solid: rgb(28, 31, 36);
6516
+ --cpk-launcher-edge: rgba(190, 194, 255, 0.25);
6006
6517
  /* The launcher's own size, exposed so the signal dot can be placed
6007
6518
  against the OUTER rim with a length rather than a percentage.
6008
6519
  Percentages resolve against the padding box, which the 1px border
6009
- insets, and the dot would land inside the rim. */
6520
+ insets, and the dot would land inside the rim.
6521
+
6522
+ Declared on the wrapper rather than on the button so the pill, which
6523
+ is the button's sibling, can clear the mark by the same length. */
6010
6524
  --cpk-launcher-size: clamp(
6011
6525
  ${LAUNCHER_MIN_SIZE}px,
6012
6526
  7vw,
6013
6527
  ${LAUNCHER_MAX_SIZE}px
6014
6528
  );
6529
+ }
6530
+
6531
+ .console-button {
6015
6532
  width: var(--cpk-launcher-size);
6016
6533
  height: var(--cpk-launcher-size);
6017
6534
  /* Keep the 1px border inside the declared outer size. */
@@ -6413,14 +6930,14 @@ ${argsString}</pre
6413
6930
 
6414
6931
  /* ── Floating button ─────────────────────────────────────────── */
6415
6932
  .console-button {
6416
- background-color: rgba(1, 5, 7, 0.95) !important;
6417
- border-color: rgba(190, 194, 255, 0.25) !important;
6933
+ background-color: var(--cpk-launcher-face) !important;
6934
+ border-color: var(--cpk-launcher-edge) !important;
6418
6935
  box-shadow:
6419
6936
  0 0 0 1px rgba(190, 194, 255, 0.15),
6420
6937
  0 4px 14px rgba(1, 5, 7, 0.28) !important;
6421
6938
  }
6422
6939
  .console-button:hover {
6423
- background-color: rgba(1, 5, 7, 1) !important;
6940
+ background-color: var(--cpk-launcher-face-solid) !important;
6424
6941
  border-color: rgba(190, 194, 255, 0.45) !important;
6425
6942
  }
6426
6943
  .console-button:focus-visible {
@@ -6438,7 +6955,6 @@ ${argsString}</pre
6438
6955
  * that forces a repaint every frame is not acceptable.
6439
6956
  */
6440
6957
  .console-button[data-cpk-signal] {
6441
- --cpk-launcher-face: rgba(1, 5, 7, 0.95);
6442
6958
  isolation: isolate;
6443
6959
  }
6444
6960
 
@@ -6548,11 +7064,197 @@ ${argsString}</pre
6548
7064
  box-shadow: 0 0 0 1.5px var(--cpk-launcher-face);
6549
7065
  }
6550
7066
 
7067
+ /* ── Launcher pill: the launcher opens sideways and says what ─── */
7068
+ /*
7069
+ * The pill is laid out at its FULL width from the first frame and
7070
+ * revealed by animating a rectangular clip. Nothing is scaled and
7071
+ * nothing is resized.
7072
+ *
7073
+ * That is not a stylistic choice. This component is permanently mounted
7074
+ * on top of a customer's application, so no property that forces a
7075
+ * layout on every frame is acceptable — and animating "width" does
7076
+ * exactly that, sixty times a second, on someone else's page. A clip
7077
+ * leaves the element's geometry constant and changes only the visible
7078
+ * region, which the compositor handles. Animating a horizontal scale
7079
+ * was the other candidate and squashes the mark itself, not merely the
7080
+ * rounded end, so the logo would need counter-scaling and the dot and
7081
+ * halo would become ellipses.
7082
+ *
7083
+ * The launcher's own face and border are repeated here so the two form
7084
+ * one capsule: the button paints last and therefore on top, with no
7085
+ * z-index needed. The mark's own ring and shadow are deliberately left
7086
+ * alone for the whole gesture — the circle's outline staying visible
7087
+ * inside the open pill was looked at against the alternative and kept.
7088
+ *
7089
+ * A column, not a row: the pill carries a heading and a subline stacked,
7090
+ * centred against a height that does not change. "justify-content"
7091
+ * centres the pair vertically and "align-items" keeps both lines flush
7092
+ * left, so the pill never grows taller than the launcher it opens from.
7093
+ */
7094
+ .cpk-launcher-pill {
7095
+ position: absolute;
7096
+ top: 50%;
7097
+ margin-top: calc(var(--cpk-launcher-size) / -2);
7098
+ height: var(--cpk-launcher-size);
7099
+ display: inline-flex;
7100
+ flex-direction: column;
7101
+ justify-content: center;
7102
+ align-items: flex-start;
7103
+ gap: 1px;
7104
+ box-sizing: border-box;
7105
+ border-radius: 999px;
7106
+ border: 1px solid var(--cpk-launcher-edge);
7107
+ background: var(--cpk-launcher-face);
7108
+ color: #ffffff;
7109
+ white-space: nowrap;
7110
+ pointer-events: none;
7111
+ opacity: 0;
7112
+ }
7113
+
7114
+ /* The failure class, word-identical to the panel's own wording. */
7115
+ .cpk-launcher-pill__heading {
7116
+ font-size: 12px;
7117
+ font-weight: 600;
7118
+ line-height: 1.2;
7119
+ }
7120
+
7121
+ /*
7122
+ * The one line of copy in this feature that exists nowhere else in the
7123
+ * product. The heading above is word-identical to the panel, which is
7124
+ * the standing rule; this line is a deliberate, owner-approved exception
7125
+ * to it, because the pill is now clickable and has to say so.
7126
+ *
7127
+ * It is NOT spoken. A screen-reader user cannot act on an instruction
7128
+ * delivered through an announcement, and it would double the spoken
7129
+ * length — so the live region carries the failure class alone.
7130
+ */
7131
+ .cpk-launcher-pill__subline {
7132
+ font-size: 10.5px;
7133
+ font-weight: 500;
7134
+ line-height: 1.2;
7135
+ opacity: 0.72;
7136
+ }
7137
+
7138
+ /*
7139
+ * Two directions, one animation with the inset on the other side. The
7140
+ * padding on the launcher's side clears the mark, so the words never sit
7141
+ * under it. The text-side padding is derived from the capsule's radius
7142
+ * (half the launcher size), NOT a bare literal: padding is measured from
7143
+ * the bounding box, but the first half-height of that side is the rounded
7144
+ * cap. Half the size lands the text exactly where the cap ends and the
7145
+ * straight edge begins. A literal 14px put it 16px inside the curve at the
7146
+ * production launcher size, which is itself a clamp on the viewport.
7147
+ */
7148
+ .cpk-launcher-pill[data-cpk-pill-direction="left"] {
7149
+ right: 0;
7150
+ padding: 0 calc(var(--cpk-launcher-size) + 12px) 0
7151
+ calc(var(--cpk-launcher-size) / 2);
7152
+ clip-path: inset(0 0 0 calc(100% - var(--cpk-launcher-size)));
7153
+ }
7154
+ .cpk-launcher-pill[data-cpk-pill-direction="right"] {
7155
+ left: 0;
7156
+ padding: 0 calc(var(--cpk-launcher-size) / 2) 0
7157
+ calc(var(--cpk-launcher-size) + 12px);
7158
+ clip-path: inset(0 calc(100% - var(--cpk-launcher-size)) 0 0);
7159
+ }
7160
+
7161
+ /*
7162
+ * "round" on both stops, so the revealing edge is the capsule's own
7163
+ * rounded end travelling sideways rather than a straight vertical line
7164
+ * wiping across it. An unrounded inset reads as a wipe; this reads as an
7165
+ * opening. It adds no animated property: the clip is still the clip.
7166
+ */
7167
+ @keyframes cpk-launcher-pill-left {
7168
+ 0% {
7169
+ opacity: 0;
7170
+ clip-path: inset(
7171
+ 0 0 0 calc(100% - var(--cpk-launcher-size)) round 999px
7172
+ );
7173
+ }
7174
+ 100% {
7175
+ opacity: 1;
7176
+ clip-path: inset(0 0 0 0 round 999px);
7177
+ }
7178
+ }
7179
+
7180
+ @keyframes cpk-launcher-pill-right {
7181
+ 0% {
7182
+ opacity: 0;
7183
+ clip-path: inset(
7184
+ 0 calc(100% - var(--cpk-launcher-size)) 0 0 round 999px
7185
+ );
7186
+ }
7187
+ 100% {
7188
+ opacity: 1;
7189
+ clip-path: inset(0 0 0 0 round 999px);
7190
+ }
7191
+ }
7192
+
7193
+ /*
7194
+ * The pill takes the pointer exactly while it is on screen, so the
7195
+ * instruction it now carries is honest: a click on it opens the
7196
+ * Inspector, the same action as pressing the mark. During the beat the
7197
+ * clip covers only the mark itself, and a click target nobody can see
7198
+ * over someone else's page is not something to ship — so the base rule
7199
+ * keeps "pointer-events: none" and only the three visible phases take it
7200
+ * back.
7201
+ * The button paints last and therefore wins the pointer where the two
7202
+ * overlap, so dragging the launcher is unaffected throughout.
7203
+ */
7204
+ .cpk-launcher-pill[data-cpk-pill-phase="opening"],
7205
+ .cpk-launcher-pill[data-cpk-pill-phase="holding"],
7206
+ .cpk-launcher-pill[data-cpk-pill-phase="closing"] {
7207
+ pointer-events: auto;
7208
+ cursor: pointer;
7209
+ }
7210
+
7211
+ /* Closing is the same animation played backwards, so the two phases can
7212
+ never drift apart. */
7213
+ .cpk-launcher-pill[data-cpk-pill-phase="opening"],
7214
+ .cpk-launcher-pill[data-cpk-pill-phase="closing"] {
7215
+ animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
7216
+ animation-iteration-count: 1;
7217
+ animation-fill-mode: forwards;
7218
+ }
7219
+ .cpk-launcher-pill[data-cpk-pill-phase="opening"] {
7220
+ animation-duration: var(--cpk-launcher-pill-open);
7221
+ }
7222
+ .cpk-launcher-pill[data-cpk-pill-phase="closing"] {
7223
+ animation-duration: var(--cpk-launcher-pill-close);
7224
+ animation-direction: reverse;
7225
+ }
7226
+ .cpk-launcher-pill[data-cpk-pill-phase="opening"][data-cpk-pill-direction="left"],
7227
+ .cpk-launcher-pill[data-cpk-pill-phase="closing"][data-cpk-pill-direction="left"] {
7228
+ animation-name: cpk-launcher-pill-left;
7229
+ }
7230
+ .cpk-launcher-pill[data-cpk-pill-phase="opening"][data-cpk-pill-direction="right"],
7231
+ .cpk-launcher-pill[data-cpk-pill-phase="closing"][data-cpk-pill-direction="right"] {
7232
+ animation-name: cpk-launcher-pill-right;
7233
+ }
7234
+
7235
+ /* The hold is the end state of the reveal, held. */
7236
+ .cpk-launcher-pill[data-cpk-pill-phase="holding"] {
7237
+ opacity: 1;
7238
+ clip-path: inset(0 0 0 0);
7239
+ }
7240
+
6551
7241
  /*
6552
7242
  * Reduced motion: the halo is held statically rather than animated, so
6553
7243
  * the information arrives without the movement.
6554
7244
  */
6555
7245
  @media (prefers-reduced-motion: reduce) {
7246
+ /*
7247
+ * The pill is shown by opacity alone, with no clip animation and the
7248
+ * same hold. The instruction is to reduce motion, not to withhold
7249
+ * information, and this reader needs the label as much as anyone.
7250
+ */
7251
+ .cpk-launcher-pill[data-cpk-pill-phase="opening"],
7252
+ .cpk-launcher-pill[data-cpk-pill-phase="holding"],
7253
+ .cpk-launcher-pill[data-cpk-pill-phase="closing"] {
7254
+ animation: none !important;
7255
+ opacity: 1;
7256
+ clip-path: inset(0 0 0 0);
7257
+ }
6556
7258
  .cpk-launcher-signal-wash {
6557
7259
  opacity: 0.85;
6558
7260
  }
@@ -6572,58 +7274,275 @@ ${argsString}</pre
6572
7274
  }
6573
7275
  }
6574
7276
 
6575
- /*
6576
- * Unread marker on the navigation entry, which is what keeps the signal
6577
- * alive once the panel is open and the launcher is hidden. Static by
6578
- * design: the pulse belongs to the launcher, and movement here would
6579
- * compete with the live event stream a developer is actually watching.
6580
- */
6581
- .inspector-nav-signal-dot {
6582
- display: inline-block;
6583
- flex: none;
6584
- width: 6px;
6585
- height: 6px;
6586
- border-radius: 50%;
6587
- background: ${unsafeCSS(NEWS_SIGNAL_COLOR)};
7277
+ /* ── Launcher HUD: hover menu, quieter than the error island ── */
7278
+ .console-button-wrapper[data-cpk-hud="open"] .cpk-launcher-hud {
7279
+ pointer-events: auto;
7280
+ opacity: 1;
7281
+ transform: none;
7282
+ visibility: visible;
6588
7283
  }
6589
7284
 
6590
- /* ── Inspector window ────────────────────────────────────────── */
6591
- .inspector-window {
6592
- border: 1px solid #d8d8e8 !important;
6593
- border-radius: 5px !important;
6594
- box-shadow: none !important;
7285
+ .cpk-launcher-hud {
7286
+ position: absolute;
7287
+ top: 0;
7288
+ z-index: 4;
7289
+ padding-right: 14px;
7290
+ pointer-events: none;
7291
+ opacity: 0;
7292
+ visibility: hidden;
7293
+ transform: translateX(8px);
7294
+ transition:
7295
+ opacity 160ms ease,
7296
+ transform 200ms cubic-bezier(0.16, 1, 0.3, 1);
6595
7297
  }
6596
7298
 
6597
- /* ── Header drag area ────────────────────────────────────────── */
6598
- .drag-handle {
6599
- border-bottom-color: #d8d8e8 !important;
6600
- background-color: #f7f6fd !important;
7299
+ .cpk-launcher-hud[data-cpk-hud-side="left"] {
7300
+ right: 100%;
7301
+ padding-right: 14px;
7302
+ padding-left: 0;
6601
7303
  }
6602
7304
 
6603
- .inspector-account-strip {
6604
- background: linear-gradient(
6605
- 90deg,
6606
- #ffffff 0%,
6607
- #f3f1ff 58%,
6608
- #eefbf7 100%
6609
- ) !important;
6610
- color: #010507 !important;
7305
+ .cpk-launcher-hud[data-cpk-hud-side="right"] {
7306
+ left: 100%;
7307
+ right: auto;
7308
+ padding-right: 0;
7309
+ padding-left: 14px;
7310
+ transform: translateX(-8px);
6611
7311
  }
6612
7312
 
6613
- /* ── Tab buttons ─────────────────────────────────────────────── */
6614
- /*
6615
- * Named classes owned by this component — no Tailwind conflict.
6616
- * Active: brand surface/surfaceContainerActive (lilac tint) +
6617
- * border/borderActionEnabled underline.
6618
- * Dark fill is for primary action buttons only, not nav tabs.
6619
- */
6620
- .cpk-tab-active {
6621
- background-color: rgba(190, 194, 255, 0.18);
6622
- color: #010507;
6623
- font-weight: 600;
7313
+ .console-button-wrapper[data-cpk-hud="open"]
7314
+ .cpk-launcher-hud[data-cpk-hud-side="right"] {
7315
+ transform: none;
6624
7316
  }
6625
- .cpk-tab-icon {
6626
- display: inline-flex;
7317
+
7318
+ .cpk-launcher-hud__card {
7319
+ --hud-fill: rgb(28 31 36 / 0.88);
7320
+ --hud-line: rgb(190 194 255 / 0.5);
7321
+ position: relative;
7322
+ width: 228px;
7323
+ padding: 4px;
7324
+ border: 1px dotted var(--hud-line);
7325
+ border-radius: 10px;
7326
+ background: var(--hud-fill);
7327
+ color: #fff;
7328
+ backdrop-filter: blur(12px) saturate(1.2);
7329
+ box-shadow: 0 8px 20px rgb(1 5 7 / 0.18);
7330
+ }
7331
+
7332
+ .cpk-launcher-hud__arrow {
7333
+ position: absolute;
7334
+ top: calc(var(--cpk-launcher-size) / 2);
7335
+ width: 10px;
7336
+ height: 10px;
7337
+ background: var(--hud-fill);
7338
+ transform: translateY(-50%) rotate(45deg);
7339
+ }
7340
+
7341
+ .cpk-launcher-hud[data-cpk-hud-side="left"] .cpk-launcher-hud__arrow {
7342
+ right: -5px;
7343
+ border-top: 1px solid var(--hud-line);
7344
+ border-right: 1px solid var(--hud-line);
7345
+ }
7346
+
7347
+ .cpk-launcher-hud[data-cpk-hud-side="right"] .cpk-launcher-hud__arrow {
7348
+ left: -5px;
7349
+ border-bottom: 1px solid var(--hud-line);
7350
+ border-left: 1px solid var(--hud-line);
7351
+ }
7352
+
7353
+ .cpk-launcher-hud__list {
7354
+ margin: 0;
7355
+ padding: 0;
7356
+ list-style: none;
7357
+ }
7358
+
7359
+ .cpk-launcher-hud__list + .cpk-launcher-hud__list {
7360
+ margin-top: 4px;
7361
+ padding-top: 4px;
7362
+ border-top: 1px dotted var(--hud-line);
7363
+ }
7364
+
7365
+ .cpk-launcher-hud__row {
7366
+ position: relative;
7367
+ display: grid;
7368
+ grid-template-columns: 1fr 28px;
7369
+ align-items: start;
7370
+ border-radius: 7px;
7371
+ cursor: pointer;
7372
+ }
7373
+
7374
+ .cpk-launcher-hud__row + .cpk-launcher-hud__row {
7375
+ margin-top: 1px;
7376
+ }
7377
+
7378
+ .cpk-launcher-hud__row:hover,
7379
+ .cpk-launcher-hud__row:focus-within,
7380
+ .cpk-launcher-hud__row[data-cpk-hud-help="open"] {
7381
+ background: rgb(255 255 255 / 0.06);
7382
+ }
7383
+
7384
+ .cpk-launcher-hud__action {
7385
+ display: flex;
7386
+ gap: 8px;
7387
+ min-height: 32px;
7388
+ align-items: center;
7389
+ padding: 6px 8px;
7390
+ border: 0;
7391
+ border-radius: 7px;
7392
+ background: transparent;
7393
+ color: #fff;
7394
+ font-family: inherit;
7395
+ font-size: 12px;
7396
+ font-weight: 600;
7397
+ text-align: start;
7398
+ cursor: pointer;
7399
+ }
7400
+
7401
+ /* Stretch the row action over the whole tab, including the detail
7402
+ copy. The help mark sits above this layer. */
7403
+ .cpk-launcher-hud__action::after {
7404
+ content: "";
7405
+ position: absolute;
7406
+ inset: 0;
7407
+ }
7408
+
7409
+ .cpk-launcher-hud__check {
7410
+ flex: none;
7411
+ width: 14px;
7412
+ height: 14px;
7413
+ color: #34d399;
7414
+ }
7415
+
7416
+ .cpk-launcher-hud__help {
7417
+ position: relative;
7418
+ z-index: 1;
7419
+ display: inline-flex;
7420
+ width: 28px;
7421
+ height: 32px;
7422
+ align-items: center;
7423
+ justify-content: center;
7424
+ padding: 0;
7425
+ border: 0;
7426
+ background: transparent;
7427
+ color: rgb(255 255 255 / 0.78);
7428
+ font-size: 12px;
7429
+ font-weight: 600;
7430
+ cursor: pointer;
7431
+ }
7432
+
7433
+ .cpk-launcher-hud__help span {
7434
+ display: inline-flex;
7435
+ width: 16px;
7436
+ height: 16px;
7437
+ align-items: center;
7438
+ justify-content: center;
7439
+ border: 1px dotted rgb(190 194 255 / 0.55);
7440
+ border-radius: 50%;
7441
+ line-height: 1;
7442
+ }
7443
+
7444
+ .cpk-launcher-hud__help:focus-visible,
7445
+ .cpk-launcher-hud__action:focus-visible {
7446
+ outline: 2px solid #bec2ff;
7447
+ outline-offset: 1px;
7448
+ }
7449
+
7450
+ .cpk-launcher-hud__detail {
7451
+ grid-column: 1 / -1;
7452
+ max-height: 0;
7453
+ margin: 0;
7454
+ padding: 0 8px;
7455
+ overflow: hidden;
7456
+ color: rgb(255 255 255 / 0.78);
7457
+ font-size: 11px;
7458
+ font-weight: 400;
7459
+ line-height: 1.4;
7460
+ opacity: 0;
7461
+ pointer-events: none;
7462
+ transform: translateY(-6px);
7463
+ transition:
7464
+ max-height 200ms cubic-bezier(0.16, 1, 0.3, 1),
7465
+ opacity 150ms ease-out,
7466
+ transform 200ms cubic-bezier(0.16, 1, 0.3, 1),
7467
+ padding-bottom 200ms cubic-bezier(0.16, 1, 0.3, 1);
7468
+ }
7469
+
7470
+ .cpk-launcher-hud__row:hover .cpk-launcher-hud__detail,
7471
+ .cpk-launcher-hud__row:focus-within .cpk-launcher-hud__detail,
7472
+ .cpk-launcher-hud__row[data-cpk-hud-help="open"] .cpk-launcher-hud__detail {
7473
+ max-height: 72px;
7474
+ padding: 0 8px 7px;
7475
+ opacity: 1;
7476
+ transform: none;
7477
+ }
7478
+
7479
+ @media (prefers-reduced-motion: reduce) {
7480
+ .cpk-launcher-hud,
7481
+ .cpk-launcher-hud__detail {
7482
+ transition: none;
7483
+ }
7484
+ }
7485
+
7486
+ /*
7487
+ * Marker on the navigation entry, which is what keeps a signal alive
7488
+ * once the panel is open and the launcher is hidden. Static by design:
7489
+ * the beat belongs to the launcher, and movement here would compete with
7490
+ * the live event stream a developer is actually watching.
7491
+ *
7492
+ * Tone-selected rather than tone-agnostic, because the marker has to
7493
+ * agree with the dot that sent the reader here. Same shape, same
7494
+ * placement, one declaration different — as on the launcher, where the
7495
+ * treatment is shared and only the injected colour changes.
7496
+ */
7497
+ .inspector-nav-signal-dot {
7498
+ display: inline-block;
7499
+ flex: none;
7500
+ width: 6px;
7501
+ height: 6px;
7502
+ border-radius: 50%;
7503
+ background: ${unsafeCSS(LAUNCHER_SIGNAL_COLORS.news)};
7504
+ }
7505
+ .inspector-nav-signal-dot[data-cpk-signal-tone="error"] {
7506
+ background: ${unsafeCSS(LAUNCHER_SIGNAL_COLORS.error)};
7507
+ }
7508
+
7509
+ /* ── Inspector window ────────────────────────────────────────── */
7510
+ .inspector-window {
7511
+ border: 1px solid #d8d8e8 !important;
7512
+ border-radius: 5px !important;
7513
+ box-shadow: none !important;
7514
+ }
7515
+
7516
+ /* ── Header drag area ────────────────────────────────────────── */
7517
+ .drag-handle {
7518
+ border-bottom-color: #d8d8e8 !important;
7519
+ background-color: #f7f6fd !important;
7520
+ }
7521
+
7522
+ .inspector-account-strip {
7523
+ background: linear-gradient(
7524
+ 90deg,
7525
+ #ffffff 0%,
7526
+ #f3f1ff 58%,
7527
+ #eefbf7 100%
7528
+ ) !important;
7529
+ color: #010507 !important;
7530
+ }
7531
+
7532
+ /* ── Tab buttons ─────────────────────────────────────────────── */
7533
+ /*
7534
+ * Named classes owned by this component — no Tailwind conflict.
7535
+ * Active: brand surface/surfaceContainerActive (lilac tint) +
7536
+ * border/borderActionEnabled underline.
7537
+ * Dark fill is for primary action buttons only, not nav tabs.
7538
+ */
7539
+ .cpk-tab-active {
7540
+ background-color: rgba(190, 194, 255, 0.18);
7541
+ color: #010507;
7542
+ font-weight: 600;
7543
+ }
7544
+ .cpk-tab-icon {
7545
+ display: inline-flex;
6627
7546
  flex-shrink: 0;
6628
7547
  align-items: center;
6629
7548
  }
@@ -6916,6 +7835,7 @@ ${argsString}</pre
6916
7835
  clearLegacyAnnouncementReadState();
6917
7836
  this.tryAutoAttachCore();
6918
7837
  this.ensureAnnouncementLoading();
7838
+ this.subscribeToInspectorThreadBridge();
6919
7839
  }
6920
7840
  this.requestUpdate();
6921
7841
  }
@@ -6938,13 +7858,16 @@ ${argsString}</pre
6938
7858
  clearTimeout(this.transitionTimeoutId);
6939
7859
  this.transitionTimeoutId = null;
6940
7860
  }
7861
+ this.unsubscribeFromInspectorThreadBridge();
6941
7862
  this.threadsSetupPromptCopyGeneration += 1;
6942
7863
  if (this.threadsSetupPromptCopyResetTimeoutId !== null) {
6943
7864
  window.clearTimeout(this.threadsSetupPromptCopyResetTimeoutId);
6944
7865
  this.threadsSetupPromptCopyResetTimeoutId = null;
6945
7866
  }
6946
7867
  this.threadsSetupPromptCopyState = "idle";
6947
- this.stopNewsSignalPulse();
7868
+ this.stopSignalPulse();
7869
+ this.cancelGestureTail();
7870
+ this.cancelThreadRefreshDebounce();
6948
7871
  this.clearInspectorUsageRefresh();
6949
7872
  this.cleanupThreadsExampleOverviewVideo();
6950
7873
  this.removeDockStyles(true);
@@ -6987,6 +7910,7 @@ ${argsString}</pre
6987
7910
  ` : this.renderButton();
6988
7911
  }
6989
7912
  willUpdate() {
7913
+ this.evaluateErrorSignals();
6990
7914
  this.reconcileSelectedMenuVisibility();
6991
7915
  if (this.isOpen && this.dockMode === "docked-left") this.setAttribute("data-docked", "true");
6992
7916
  else this.removeAttribute("data-docked");
@@ -6996,7 +7920,11 @@ ${argsString}</pre
6996
7920
  this.syncThreadsExampleOverviewVideo();
6997
7921
  this.maybeTrackInspectorMetadataViews();
6998
7922
  this.maybeTrackNewsSignalViewed();
7923
+ this.resolvePillDirection();
7924
+ this.maybeTrackErrorSignalViewed();
6999
7925
  this.maybeCompleteWhatsNewView();
7926
+ this.maybeCompleteEventErrorView();
7927
+ this.flushErrorLandingScroll();
7000
7928
  this.maybeTrackHomeViewed();
7001
7929
  if (!this.isOpen) {
7002
7930
  this.lastScrolledAgentNavigationLayout = null;
@@ -7054,20 +7982,33 @@ ${argsString}</pre
7054
7982
  "select-none",
7055
7983
  this.isDragging ? "cursor-grabbing" : "cursor-pointer"
7056
7984
  ].join(" ");
7057
- const signalStyles = this.newsSignalArmed ? {
7058
- "--cpk-launcher-signal": NEWS_SIGNAL_COLOR,
7059
- "--cpk-launcher-cadence": `${NEWS_SIGNAL_CADENCE_MS}ms`
7985
+ const activeSignal = this.getActiveLauncherSignal();
7986
+ const signal = activeSignal ? LAUNCHER_SIGNALS[activeSignal] : null;
7987
+ const signalStyles = signal ? {
7988
+ "--cpk-launcher-signal": LAUNCHER_SIGNAL_COLORS[signal.tone],
7989
+ "--cpk-launcher-cadence": `${signal.cadence}ms`
7060
7990
  } : {};
7061
7991
  return html`
7062
- <div class="console-button-wrapper">
7992
+ <div
7993
+ class="console-button-wrapper"
7994
+ data-cpk-hud=${this.launcherHudOpen ? "open" : "closed"}
7995
+ @pointerenter=${this.handleLauncherHudEnter}
7996
+ @pointerleave=${this.handleLauncherHudLeave}
7997
+ @focusin=${this.handleLauncherHudFocusIn}
7998
+ @focusout=${this.handleLauncherHudFocusOut}
7999
+ @keydown=${this.handleLauncherHudKeydown}
8000
+ >
8001
+ ${this.renderLauncherPill()}
7063
8002
  <button
7064
8003
  class=${buttonClasses}
7065
8004
  type="button"
7066
- aria-label="Web Inspector"
7067
- title=${this.newsSignalArmed ? `${WHATS_NEW_VIEW_LABEL} — unread` : nothing}
8005
+ aria-expanded=${this.launcherHudOpen ? "true" : "false"}
8006
+ aria-controls=${this.launcherHudOpen ? "cpk-launcher-hud" : nothing}
8007
+ aria-label=${signal && signal.tone === "error" ? `${LAUNCHER_BASE_LABEL}, ${signal.accessibleLabel}` : LAUNCHER_BASE_LABEL}
8008
+ title=${activeSignal === NEWS_SIGNAL_ID ? `${WHATS_NEW_VIEW_LABEL} — unread` : nothing}
7068
8009
  data-drag-context="button"
7069
- data-cpk-signal=${this.newsSignalArmed ? NEWS_SIGNAL_TONE : nothing}
7070
- data-cpk-signal-pulsing=${this.newsSignalArmed && this.newsSignalPulsing ? "true" : nothing}
8010
+ data-cpk-signal=${signal ? signal.tone : nothing}
8011
+ data-cpk-signal-pulsing=${activeSignal !== null && this.pulsingSignal === activeSignal ? "true" : nothing}
7071
8012
  style=${styleMap(signalStyles)}
7072
8013
  data-dragging=${this.isDragging && this.pointerContext === "button" ? "true" : "false"}
7073
8014
  @pointerdown=${this.handlePointerDown}
@@ -7082,16 +8023,210 @@ ${argsString}</pre
7082
8023
  class="cpk-launcher-mark h-6 w-auto"
7083
8024
  loading="lazy"
7084
8025
  />
7085
- ${this.newsSignalArmed ? html`<span
8026
+ ${activeSignal !== null ? html`<span
7086
8027
  class="cpk-launcher-signal-wash"
7087
8028
  aria-hidden="true"
7088
8029
  ></span>
7089
8030
  <span
7090
8031
  class="cpk-launcher-signal-dot"
7091
- data-cpk-signal-dot=${NEWS_SIGNAL_ID}
8032
+ data-cpk-signal-dot=${activeSignal}
7092
8033
  aria-hidden="true"
7093
8034
  ></span>` : nothing}
7094
8035
  </button>
8036
+ ${html`<span
8037
+ class="sr-only"
8038
+ data-cpk-launcher-announcement
8039
+ role="status"
8040
+ aria-live="polite"
8041
+ >${this.getGestureLabel() ?? ""}</span
8042
+ >`}
8043
+ ${this.renderLauncherHud()}
8044
+ </div>
8045
+ `;
8046
+ }
8047
+ /**
8048
+ * The words the running gesture carries, or null when the launcher is quiet.
8049
+ *
8050
+ * Read from the signal's own description rather than from a condition on the
8051
+ * tone, so a third signal can carry a pill by declaring a label.
8052
+ */
8053
+ getGestureLabel() {
8054
+ if (this.gestureSignal === null) return null;
8055
+ return LAUNCHER_SIGNALS[this.gestureSignal].pillLabel ?? null;
8056
+ }
8057
+ /**
8058
+ * The pill, laid out at its full width and clipped, for the whole gesture.
8059
+ *
8060
+ * It renders from the first frame of the beat — clipped to nothing, so it
8061
+ * shows nothing — because the room it needs cannot be measured until it has
8062
+ * been laid out, and the direction is decided at gesture start.
8063
+ */
8064
+ renderLauncherPill() {
8065
+ const key = this.gestureSignal;
8066
+ if (key === null || this.pillPhase === null) return nothing;
8067
+ const signal = LAUNCHER_SIGNALS[key];
8068
+ const label = signal.pillLabel;
8069
+ if (label === void 0) return nothing;
8070
+ return html`
8071
+ <span
8072
+ class="cpk-launcher-pill"
8073
+ data-cpk-launcher-pill=${key}
8074
+ data-cpk-pill-phase=${this.pillPhase}
8075
+ data-cpk-pill-direction=${this.pillDirection ?? "left"}
8076
+ style=${styleMap({
8077
+ "--cpk-launcher-signal": LAUNCHER_SIGNAL_COLORS[signal.tone],
8078
+ "--cpk-launcher-pill-open": `${ERROR_GESTURE_MS.open}ms`,
8079
+ "--cpk-launcher-pill-close": `${ERROR_GESTURE_MS.close}ms`
8080
+ })}
8081
+ aria-hidden="true"
8082
+ @click=${this.handlePillClick}
8083
+ >
8084
+ <span class="cpk-launcher-pill__heading" data-cpk-pill-heading
8085
+ >${label}</span
8086
+ >
8087
+ <span class="cpk-launcher-pill__subline" data-cpk-pill-subline
8088
+ >${PILL_SUBLINE_LABEL}</span
8089
+ >
8090
+ </span>
8091
+ `;
8092
+ }
8093
+ isLauncherHudBlocked() {
8094
+ return this.gestureSignal !== null;
8095
+ }
8096
+ resolveLauncherHudSide() {
8097
+ if (typeof window === "undefined") {
8098
+ this.launcherHudSide = "left";
8099
+ return;
8100
+ }
8101
+ const button = this.activeRoot.querySelector(".console-button");
8102
+ if (!button) {
8103
+ this.launcherHudSide = "left";
8104
+ return;
8105
+ }
8106
+ if (button.getBoundingClientRect().left - LAUNCHER_HUD_WIDTH >= EDGE_MARGIN) {
8107
+ this.launcherHudSide = "left";
8108
+ return;
8109
+ }
8110
+ this.launcherHudSide = "right";
8111
+ }
8112
+ openLauncherHud() {
8113
+ if (this.isLauncherHudBlocked() || this.isOpen) return;
8114
+ this.resolveLauncherHudSide();
8115
+ if (this.launcherHudCloseTimer !== null) {
8116
+ clearTimeout(this.launcherHudCloseTimer);
8117
+ this.launcherHudCloseTimer = null;
8118
+ }
8119
+ if (this.launcherHudOpen) return;
8120
+ this.launcherHudOpen = true;
8121
+ this.requestUpdate();
8122
+ }
8123
+ closeLauncherHud() {
8124
+ if (this.launcherHudCloseTimer !== null) {
8125
+ clearTimeout(this.launcherHudCloseTimer);
8126
+ this.launcherHudCloseTimer = null;
8127
+ }
8128
+ if (!this.launcherHudOpen && this.launcherHudHelp === null) return;
8129
+ this.launcherHudOpen = false;
8130
+ this.launcherHudHelp = null;
8131
+ this.requestUpdate();
8132
+ }
8133
+ renderHudCheck() {
8134
+ return html`
8135
+ <svg
8136
+ class="cpk-launcher-hud__check"
8137
+ viewBox="0 0 16 16"
8138
+ aria-hidden="true"
8139
+ focusable="false"
8140
+ data-cpk-hud-check
8141
+ >
8142
+ <path
8143
+ fill="none"
8144
+ stroke="currentColor"
8145
+ stroke-width="2"
8146
+ stroke-linecap="round"
8147
+ stroke-linejoin="round"
8148
+ d="M3 8.5 6.5 12 13 4.5"
8149
+ />
8150
+ </svg>
8151
+ `;
8152
+ }
8153
+ renderHudRow(args) {
8154
+ const helpOpen = this.launcherHudHelp === args.id;
8155
+ const detailId = `cpk-hud-detail-${args.id}`;
8156
+ return html`
8157
+ <li
8158
+ class="cpk-launcher-hud__row"
8159
+ data-cpk-hud-row=${args.id}
8160
+ data-cpk-hud-help=${helpOpen ? "open" : nothing}
8161
+ @click=${(event) => this.handleHudRowClick(event, args.id)}
8162
+ >
8163
+ <button
8164
+ type="button"
8165
+ class="cpk-launcher-hud__action"
8166
+ data-cpk-hud-action
8167
+ aria-describedby=${detailId}
8168
+ @click=${(event) => this.handleHudActionClick(event, args.id)}
8169
+ @pointerdown=${(event) => event.stopPropagation()}
8170
+ >
8171
+ ${args.connected ? this.renderHudCheck() : nothing}${args.label}
8172
+ </button>
8173
+ <button
8174
+ type="button"
8175
+ class="cpk-launcher-hud__help"
8176
+ aria-expanded=${helpOpen ? "true" : "false"}
8177
+ aria-controls=${detailId}
8178
+ aria-label=${`About ${args.label}`}
8179
+ @click=${(event) => this.handleHudHelpClick(event, args.id)}
8180
+ @pointerdown=${(event) => event.stopPropagation()}
8181
+ >
8182
+ <span aria-hidden="true">?</span>
8183
+ </button>
8184
+ <p class="cpk-launcher-hud__detail" id=${detailId}>${args.detail}</p>
8185
+ </li>
8186
+ `;
8187
+ }
8188
+ renderLauncherHud() {
8189
+ if (!this.launcherHudOpen) return nothing;
8190
+ const threadsOn = this.areThreadEndpointsAvailable();
8191
+ const intelligenceOn = Boolean(this._core?.intelligence);
8192
+ const learningOn = intelligenceOn && this._memoriesAvailable;
8193
+ return html`
8194
+ <div
8195
+ class="cpk-launcher-hud"
8196
+ id="cpk-launcher-hud"
8197
+ data-cpk-launcher-hud
8198
+ data-cpk-hud-side=${this.launcherHudSide}
8199
+ >
8200
+ <div class="cpk-launcher-hud__card">
8201
+ <span class="cpk-launcher-hud__arrow" aria-hidden="true"></span>
8202
+ <ul class="cpk-launcher-hud__list" role="list">
8203
+ ${this.renderHudRow({
8204
+ id: "inspector",
8205
+ label: HUD_OPEN_INSPECTOR_LABEL,
8206
+ detail: HUD_OPEN_INSPECTOR_DETAIL
8207
+ })}
8208
+ </ul>
8209
+ <ul class="cpk-launcher-hud__list" role="list">
8210
+ ${this.renderHudRow({
8211
+ id: "threads",
8212
+ label: threadsOn ? HUD_THREADS_ON_LABEL : HUD_THREADS_OFF_LABEL,
8213
+ detail: threadsOn ? HUD_THREADS_ON_DETAIL : HUD_THREADS_OFF_DETAIL,
8214
+ connected: threadsOn
8215
+ })}
8216
+ ${this.renderHudRow({
8217
+ id: "intelligence",
8218
+ label: intelligenceOn ? HUD_INTELLIGENCE_ON_LABEL : HUD_INTELLIGENCE_OFF_LABEL,
8219
+ detail: intelligenceOn ? HUD_INTELLIGENCE_ON_DETAIL : HUD_INTELLIGENCE_OFF_DETAIL,
8220
+ connected: intelligenceOn
8221
+ })}
8222
+ ${this.renderHudRow({
8223
+ id: "learning",
8224
+ label: learningOn ? HUD_LEARNING_ON_LABEL : HUD_LEARNING_OFF_LABEL,
8225
+ detail: learningOn ? HUD_LEARNING_ON_DETAIL : HUD_LEARNING_OFF_DETAIL,
8226
+ connected: learningOn
8227
+ })}
8228
+ </ul>
8229
+ </div>
7095
8230
  </div>
7096
8231
  `;
7097
8232
  }
@@ -7113,7 +8248,6 @@ ${argsString}</pre
7113
8248
  `;
7114
8249
  }
7115
8250
  renderInspectorSidebar(iconRail, automaticallyCollapsed, agentSelector) {
7116
- const unread = this.newsSignalArmed && this.announcementLoaded;
7117
8251
  const homeModel = this.getHomeModel();
7118
8252
  return html`
7119
8253
  <aside
@@ -7155,7 +8289,7 @@ ${argsString}</pre
7155
8289
  </button>` : nothing}
7156
8290
  ${items.map((item) => {
7157
8291
  const isSelected = this.selectedMenu === item.key;
7158
- const showBadge = item.key === NEWS_SIGNAL_DESTINATION && unread;
8292
+ const marker = this.getNavigationSignalFor(item.key);
7159
8293
  return html`
7160
8294
  <button
7161
8295
  type="button"
@@ -7163,7 +8297,7 @@ ${argsString}</pre
7163
8297
  data-inspector-group=${group}
7164
8298
  data-inspector-menu-key=${item.key}
7165
8299
  aria-current=${isSelected ? "page" : nothing}
7166
- aria-label=${showBadge ? `${item.label}, new content` : item.label}
8300
+ aria-label=${marker ? `${item.label}, ${marker.accessibleLabel}` : item.label}
7167
8301
  data-inspector-tooltip=${item.label}
7168
8302
  title=${iconRail ? nothing : item.label}
7169
8303
  style=${INTERACTIVE_FOCUS_BASE_STYLE}
@@ -7177,8 +8311,12 @@ ${argsString}</pre
7177
8311
  ${item.key === "threads" ? unsafeHTML(this.customTabIcons.threads) : this.renderIcon(item.icon)}
7178
8312
  </span>
7179
8313
  <span class="inspector-nav-label">${item.label}</span>
7180
- ${showBadge ? html`
7181
- <span class="inspector-nav-signal-dot" aria-hidden="true"></span>
8314
+ ${marker ? html`
8315
+ <span
8316
+ class="inspector-nav-signal-dot"
8317
+ data-cpk-signal-tone=${marker.tone}
8318
+ aria-hidden="true"
8319
+ ></span>
7182
8320
  ` : nothing}
7183
8321
  </button>
7184
8322
  `;
@@ -7652,6 +8790,31 @@ ${argsString}</pre
7652
8790
  `)}
7653
8791
  </dl>
7654
8792
  </section>
8793
+ `;
8794
+ }
8795
+ renderEventErrorBanner(key) {
8796
+ const error = this.eventErrorDetails[key];
8797
+ if (!error) return nothing;
8798
+ const guide = EVENT_ERROR_GUIDANCE[key];
8799
+ return html`
8800
+ <div
8801
+ class="mx-3 mt-3 flex cursor-pointer items-start gap-2 rounded-lg border border-rose-200 bg-rose-50 px-3 py-2 text-left text-[11px] text-rose-950"
8802
+ role="alert"
8803
+ tabindex="0"
8804
+ data-cpk-event-error=${key}
8805
+ @click=${this.refocusEventErrorLanding}
8806
+ @keydown=${this.handleEventErrorBannerKeydown}
8807
+ >
8808
+ <span class="mt-0.5 shrink-0">${this.renderIcon("TriangleAlert")}</span>
8809
+ <div class="min-w-0 flex-1 space-y-1">
8810
+ <p class="font-semibold">${guide.title}</p>
8811
+ ${error.agentId ? html`<p>Agent: ${error.agentId}</p>` : nothing}
8812
+ ${error.toolName ? html`<p>Tool: ${error.toolName}</p>` : nothing}
8813
+ <p class="break-words leading-relaxed">${error.message}</p>
8814
+ ${guide.advice ? html`<p class="leading-relaxed">${guide.advice}</p>` : nothing}
8815
+ ${guide.highlight && this.hasEventErrorHighlight(key) ? html`<p class="leading-relaxed">${guide.highlight}</p>` : nothing}
8816
+ </div>
8817
+ </div>
7655
8818
  `;
7656
8819
  }
7657
8820
  handleHomeLastEventSelect(eventId, agentId) {
@@ -8370,21 +9533,34 @@ ${argsString}</pre
8370
9533
  this.draggedDuringInteraction = false;
8371
9534
  }
8372
9535
  openInspector(source, options = {}) {
8373
- if (options.threadId) this.focusThread(options);
9536
+ if (options.snippetId || options.menu === "event-snippets") this.focusEventSnippets(options);
9537
+ else if (options.threadId) this.focusThread(options);
8374
9538
  if (this.isPoppedOut) return;
8375
9539
  if (this.isOpen) return;
8376
9540
  const hadUnseenAnnouncement = this.newsSignalArmed;
9541
+ const activeSignalAtOpen = this.getActiveLauncherSignal();
8377
9542
  const firstOpen = !this.hasOpenedInspector;
8378
9543
  this.hasOpenedInspector = true;
8379
9544
  this.homeViewedThisOpen = false;
8380
- if (this.newsSignalArmed && source === "floating_button") {
8381
- this.selectedMenu = "home";
8382
- this.lastSelectedMenuByGroup.home = "home";
9545
+ this.closeLauncherHud();
9546
+ const hudMenu = this.hudLandingMenu;
9547
+ this.hudLandingMenu = null;
9548
+ if (hudMenu) {
9549
+ this.selectedMenu = hudMenu;
9550
+ this.lastSelectedMenuByGroup[getGroupForMenu(hudMenu)] = hudMenu;
9551
+ } else if (activeSignalAtOpen !== null && source === "floating_button") {
9552
+ const landing = LAUNCHER_SIGNALS[activeSignalAtOpen].landingTarget;
9553
+ this.selectedMenu = landing;
9554
+ this.lastSelectedMenuByGroup[getGroupForMenu(landing)] = landing;
9555
+ if (landing === "agents" || landing === "ag-ui-events") {
9556
+ if (isEventErrorKey(activeSignalAtOpen)) this.applyEventErrorLanding(activeSignalAtOpen);
9557
+ }
8383
9558
  }
8384
9559
  this.ensureAnnouncementLoading();
8385
9560
  this.isOpen = true;
9561
+ this.cancelGestureTail();
8386
9562
  this.persistState();
8387
- this.trackOpened(source, hadUnseenAnnouncement, firstOpen);
9563
+ this.trackOpened(source, hadUnseenAnnouncement, firstOpen, activeSignalAtOpen);
8388
9564
  if (this.dockMode !== "floating") this.applyDockStyles();
8389
9565
  this.ensureWindowPlacement();
8390
9566
  this.requestUpdate();
@@ -8399,7 +9575,6 @@ ${argsString}</pre
8399
9575
  if (this.isPoppedOut) return;
8400
9576
  if (!this.isOpen) return;
8401
9577
  this.isOpen = false;
8402
- if (this.newsSignalPulsePending) this.startNewsSignalPulse();
8403
9578
  if (this.dockMode !== "floating") this.removeDockStyles();
8404
9579
  this.persistState();
8405
9580
  this.updateHostTransform("button");
@@ -8407,6 +9582,7 @@ ${argsString}</pre
8407
9582
  this.updateComplete.then(() => {
8408
9583
  this.measureContext("button");
8409
9584
  this.applyAnchorPosition("button");
9585
+ this.flushPendingSignalPulse();
8410
9586
  });
8411
9587
  }
8412
9588
  renderIcon(name) {
@@ -8587,6 +9763,7 @@ ${argsString}</pre
8587
9763
  contentText,
8588
9764
  contentRaw: raw.content !== void 0 ? this.sanitizeForLogging(raw.content) : void 0,
8589
9765
  toolCalls,
9766
+ toolCallId: typeof raw.toolCallId === "string" ? raw.toolCallId : void 0,
8590
9767
  activityType: typeof raw.activityType === "string" ? raw.activityType : void 0
8591
9768
  };
8592
9769
  }
@@ -8769,16 +9946,16 @@ ${argsString}</pre
8769
9946
  @submit=${this.handlePlaygroundSubmit}
8770
9947
  >
8771
9948
  ${this.playgroundError ? html`<div
8772
- class="mx-auto mb-2 flex max-w-3xl items-start gap-2 rounded-lg border border-rose-200 bg-rose-50 px-2.5 py-2 text-[10px] text-rose-800"
9949
+ class="mx-auto mb-2 flex max-w-3xl items-start gap-2 rounded-lg border border-rose-200 bg-rose-50 px-2.5 py-2 text-[10px] text-rose-950"
8773
9950
  role="alert"
8774
9951
  data-playground-error
8775
9952
  >
8776
- <span class="mt-0.5 shrink-0 text-rose-600"
9953
+ <span class="mt-0.5 shrink-0"
8777
9954
  >${this.renderIcon("TriangleAlert")}</span
8778
9955
  >
8779
9956
  <div class="min-w-0 flex-1">
8780
9957
  <p class="font-semibold">Agent run failed</p>
8781
- <p class="mt-0.5 break-words leading-relaxed text-rose-700">
9958
+ <p class="mt-0.5 break-words leading-relaxed">
8782
9959
  ${this.playgroundError}
8783
9960
  </p>
8784
9961
  </div>
@@ -8813,23 +9990,574 @@ ${argsString}</pre
8813
9990
  ?disabled=${sendDisabled}
8814
9991
  @click=${this.playgroundIsRunning ? this.handlePlaygroundStop : nothing}
8815
9992
  >
8816
- ${this.renderIcon(this.playgroundIsRunning ? "Square" : "ArrowUp")}
8817
- </button>
9993
+ ${this.renderIcon(this.playgroundIsRunning ? "Square" : "ArrowUp")}
9994
+ </button>
9995
+ </div>
9996
+ <p
9997
+ class="mx-auto max-w-3xl px-3 py-2 text-center text-[10px] leading-4 text-gray-500"
9998
+ >
9999
+ AI can make mistakes. Please verify important information.
10000
+ </p>
10001
+ </form>
10002
+ `;
10003
+ }
10004
+ reloadEventSnippets() {
10005
+ this.eventSnippets = loadEventSnippets();
10006
+ }
10007
+ renderSnippetImportControl() {
10008
+ return html`
10009
+ <label
10010
+ class="inline-flex shrink-0 cursor-pointer items-center rounded-md border border-gray-300 bg-white px-3 py-1.5 text-[11px] text-gray-700"
10011
+ data-testid="cpk-snippet-import"
10012
+ >
10013
+ Import
10014
+ <input
10015
+ type="file"
10016
+ accept="application/json"
10017
+ class="hidden"
10018
+ @change=${this.handleSnippetImportChange}
10019
+ />
10020
+ </label>
10021
+ `;
10022
+ }
10023
+ selectEventSnippet(id) {
10024
+ const snippet = this.eventSnippets.find((item) => item.id === id);
10025
+ this.selectedSnippetId = id;
10026
+ if (!snippet) {
10027
+ this.requestUpdate();
10028
+ return;
10029
+ }
10030
+ const editor = editorStateFromSnippet(snippet);
10031
+ this.snippetRecipe = editor.recipe;
10032
+ this.snippetName = editor.name;
10033
+ this.snippetJson = editor.json;
10034
+ this.snippetToolName = editor.draft.toolName;
10035
+ this.snippetToolArgs = editor.draft.toolArgs;
10036
+ this.snippetReasoningText = editor.draft.reasoningText;
10037
+ this.snippetTextContent = editor.draft.textContent;
10038
+ this.snippetActivityType = editor.draft.activityType;
10039
+ this.snippetActivityContent = editor.draft.activityContent;
10040
+ this.snippetError = null;
10041
+ this.requestUpdate();
10042
+ }
10043
+ compileSnippetDraft() {
10044
+ switch (this.snippetRecipe) {
10045
+ case "tool-call": return compileToolCallRecipe({
10046
+ toolName: this.snippetToolName,
10047
+ argsJson: this.snippetToolArgs,
10048
+ threadId: this.getSnippetThreadId(),
10049
+ runId: this.getSnippetRunId()
10050
+ });
10051
+ case "reasoning": return compileReasoningRecipe({
10052
+ text: this.snippetReasoningText,
10053
+ threadId: this.getSnippetThreadId(),
10054
+ runId: this.getSnippetRunId()
10055
+ });
10056
+ case "text": return compileTextRecipe({
10057
+ text: this.snippetTextContent,
10058
+ threadId: this.getSnippetThreadId(),
10059
+ runId: this.getSnippetRunId()
10060
+ });
10061
+ case "activity": return compileActivityRecipe({
10062
+ activityType: this.snippetActivityType,
10063
+ contentJson: this.snippetActivityContent,
10064
+ threadId: this.getSnippetThreadId(),
10065
+ runId: this.getSnippetRunId()
10066
+ });
10067
+ case "raw": return parseSnippetEvents(this.snippetJson);
10068
+ }
10069
+ }
10070
+ getSnippetThreadId() {
10071
+ return this.selectedThreadId ?? "inspector-snippet";
10072
+ }
10073
+ getSnippetRunId() {
10074
+ return `inspector-snippet-${Date.now()}`;
10075
+ }
10076
+ getSnippetTargetAgent() {
10077
+ const core = this._core;
10078
+ if (!core) return null;
10079
+ const selected = this.selectedContext !== "all-agents" ? this.selectedContext : null;
10080
+ if (selected) return core.getAgent(selected) ?? null;
10081
+ const first = this.contextOptions.find((option) => option.key !== "all-agents");
10082
+ return first ? core.getAgent(first.key) ?? null : null;
10083
+ }
10084
+ applyRecipeToEditor() {
10085
+ try {
10086
+ const events = this.compileSnippetDraft();
10087
+ this.snippetJson = JSON.stringify(events, null, 2);
10088
+ this.snippetError = null;
10089
+ } catch (error) {
10090
+ this.snippetError = error instanceof Error ? error.message : "Could not compile recipe.";
10091
+ }
10092
+ this.requestUpdate();
10093
+ }
10094
+ handleSnippetRecipeChange(event) {
10095
+ const value = event.target.value;
10096
+ if (value === "tool-call" || value === "reasoning" || value === "text" || value === "activity" || value === "raw") {
10097
+ this.snippetRecipe = value;
10098
+ if (value !== "raw") this.applyRecipeToEditor();
10099
+ else this.requestUpdate();
10100
+ }
10101
+ }
10102
+ saveCurrentSnippet(source) {
10103
+ try {
10104
+ const events = parseSnippetEvents(this.snippetJson);
10105
+ const now = (/* @__PURE__ */ new Date()).toISOString();
10106
+ const snippet = {
10107
+ id: this.selectedSnippetId ?? createSnippetId(),
10108
+ name: this.snippetName.trim() || recipeLabel(this.snippetRecipe),
10109
+ recipe: this.snippetRecipe,
10110
+ events,
10111
+ createdAt: this.eventSnippets.find((item) => item.id === this.selectedSnippetId)?.createdAt ?? now,
10112
+ updatedAt: now
10113
+ };
10114
+ this.eventSnippets = upsertEventSnippet(snippet);
10115
+ this.selectedSnippetId = snippet.id;
10116
+ this.snippetBanner = "Snippet saved.";
10117
+ this.snippetError = null;
10118
+ if (!this.core?.telemetryDisabled) trackEventSnippetsSaved({
10119
+ recipe: this.snippetRecipe,
10120
+ source,
10121
+ success: true
10122
+ });
10123
+ } catch (error) {
10124
+ this.snippetError = error instanceof Error ? error.message : "Could not save snippet.";
10125
+ if (!this.core?.telemetryDisabled) trackEventSnippetsSaved({
10126
+ recipe: this.snippetRecipe,
10127
+ source,
10128
+ success: false
10129
+ });
10130
+ }
10131
+ this.requestUpdate();
10132
+ }
10133
+ async runCurrentSnippet() {
10134
+ this.snippetError = null;
10135
+ this.snippetBanner = null;
10136
+ let events;
10137
+ try {
10138
+ events = expandSnippetEventsForRun(parseSnippetEvents(this.snippetJson));
10139
+ } catch (error) {
10140
+ this.snippetError = error instanceof Error ? error.message : "Snippet JSON is invalid.";
10141
+ this.requestUpdate();
10142
+ return;
10143
+ }
10144
+ if (snippetContainsToolCall(events) && !this.snippetConfirmOpen) {
10145
+ this.snippetConfirmOpen = true;
10146
+ this.requestUpdate();
10147
+ return;
10148
+ }
10149
+ this.snippetConfirmOpen = false;
10150
+ const core = this._core;
10151
+ const agent = this.getSnippetTargetAgent();
10152
+ if (!core || !agent) {
10153
+ this.snippetError = "No agent is available to inject into.";
10154
+ this.requestUpdate();
10155
+ return;
10156
+ }
10157
+ if (agent.isRunning) {
10158
+ this.snippetError = "The agent is running. Wait for the current run to end.";
10159
+ this.requestUpdate();
10160
+ return;
10161
+ }
10162
+ try {
10163
+ const result = await ɵinjectInspectorEvents({
10164
+ core,
10165
+ agent,
10166
+ events
10167
+ });
10168
+ this.lastInject = {
10169
+ snippetId: this.selectedSnippetId ?? "unsaved",
10170
+ agentId: agent.agentId ?? "default",
10171
+ runId: this.getSnippetRunId(),
10172
+ messageIds: result.messageIds
10173
+ };
10174
+ this.snippetBanner = "Inspector injected these events into the live thread.";
10175
+ if (!this.core?.telemetryDisabled) trackEventSnippetsRun({
10176
+ recipe: this.snippetRecipe,
10177
+ source: "pane",
10178
+ success: true
10179
+ });
10180
+ } catch (error) {
10181
+ this.snippetError = error instanceof Error ? error.message : "Could not run snippet.";
10182
+ if (!this.core?.telemetryDisabled) trackEventSnippetsRun({
10183
+ recipe: this.snippetRecipe,
10184
+ source: "pane",
10185
+ success: false
10186
+ });
10187
+ }
10188
+ this.requestUpdate();
10189
+ }
10190
+ resetLastSnippetRun() {
10191
+ const last = this.lastInject;
10192
+ const core = this._core;
10193
+ if (!last || !core) return;
10194
+ const agent = core.getAgent(last.agentId);
10195
+ if (!agent) {
10196
+ this.snippetError = "The injected agent is no longer available.";
10197
+ this.requestUpdate();
10198
+ return;
10199
+ }
10200
+ ɵresetInspectorInject({
10201
+ agent,
10202
+ messageIds: last.messageIds
10203
+ });
10204
+ this.lastInject = null;
10205
+ this.snippetBanner = "Last inject was removed from the thread. App state from a tool handler was not undone.";
10206
+ this.requestUpdate();
10207
+ }
10208
+ renderEventSnippetsView() {
10209
+ if (this.eventSnippets.length === 0 && this.selectedSnippetId === null) this.reloadEventSnippets();
10210
+ const agent = this.getSnippetTargetAgent();
10211
+ const runBlocked = agent?.isRunning === true;
10212
+ const canRunJson = snippetJsonIsRunnable(this.snippetJson);
10213
+ const canRun = canRunJson && !!agent && !runBlocked;
10214
+ const canSave = canRunJson;
10215
+ const canDelete = this.selectedSnippetId !== null;
10216
+ const canExport = this.eventSnippets.length > 0;
10217
+ const canReset = this.lastInject !== null;
10218
+ const snippetGroups = groupEventSnippets(this.eventSnippets);
10219
+ const tools = this._core?.tools ?? [];
10220
+ return html`
10221
+ <div class="flex h-full min-h-0 flex-col bg-white">
10222
+ <div
10223
+ class="flex items-center justify-between gap-4 border-b border-gray-200 px-4 py-3"
10224
+ >
10225
+ <div class="min-w-0">
10226
+ <h2 class="text-sm font-semibold text-gray-900">Event Snippets</h2>
10227
+ <p class="mt-1 text-[11px] text-gray-500">
10228
+ Compile AG-UI events, run them on the live agent, and save them
10229
+ for later. Tool-call runs use the real frontend-tool handler.
10230
+ </p>
10231
+ </div>
10232
+ ${this.renderSnippetImportControl()}
10233
+ </div>
10234
+ ${this.snippetBanner ? html`<div
10235
+ class="border-b border-emerald-200 bg-emerald-50 px-4 py-2 text-[11px] text-emerald-800"
10236
+ role="status"
10237
+ >
10238
+ ${this.snippetBanner}
10239
+ </div>` : nothing}
10240
+ ${this.snippetError ? html`<div
10241
+ class="border-b border-rose-200 bg-rose-50 px-4 py-2 text-[11px] text-rose-800"
10242
+ role="alert"
10243
+ >
10244
+ ${this.snippetError}
10245
+ </div>` : nothing}
10246
+ ${this.snippetConfirmOpen ? html`<div
10247
+ class="border-b border-amber-200 bg-amber-50 px-4 py-2 text-[11px] text-amber-900"
10248
+ role="alertdialog"
10249
+ aria-label="Confirm tool-call run"
10250
+ >
10251
+ <p>
10252
+ This snippet contains a tool call. Run will execute the real
10253
+ handler on the live thread.
10254
+ </p>
10255
+ <div class="mt-2 flex gap-2">
10256
+ <button
10257
+ type="button"
10258
+ class="rounded-md bg-gray-900 px-2 py-1 text-[11px] text-white"
10259
+ data-testid="cpk-snippet-run-handler"
10260
+ @click=${() => {
10261
+ this.snippetConfirmOpen = true;
10262
+ this.runCurrentSnippet();
10263
+ }}
10264
+ >
10265
+ Run handler
10266
+ </button>
10267
+ <button
10268
+ type="button"
10269
+ class="rounded-md border border-gray-300 bg-white px-2 py-1 text-[11px]"
10270
+ @click=${() => {
10271
+ this.snippetConfirmOpen = false;
10272
+ this.requestUpdate();
10273
+ }}
10274
+ >
10275
+ Cancel
10276
+ </button>
10277
+ </div>
10278
+ </div>` : nothing}
10279
+ <div class="flex min-h-0 flex-1">
10280
+ <div
10281
+ class="inspector-snippet-sidebar shrink-0"
10282
+ style="width:${this.snippetListWidth}px"
10283
+ data-testid="cpk-snippet-list"
10284
+ >
10285
+ ${snippetGroups.length === 0 ? html`
10286
+ <p class="px-2 py-2 text-[11px] text-gray-500">No saved snippets yet.</p>
10287
+ ` : html`
10288
+ <nav class="inspector-sidebar-nav" aria-label="Saved snippets">
10289
+ ${snippetGroups.map((group) => html`
10290
+ <div class="inspector-sidebar-section">
10291
+ <div
10292
+ class="inspector-sidebar-label"
10293
+ data-testid="cpk-snippet-category"
10294
+ data-recipe=${group.recipe}
10295
+ >
10296
+ ${group.label}
10297
+ </div>
10298
+ ${group.snippets.map((snippet) => {
10299
+ const isSelected = snippet.id === this.selectedSnippetId;
10300
+ return html`
10301
+ <button
10302
+ type="button"
10303
+ class="inspector-nav-control inspector-sidebar-control ${isSelected ? "inspector-nav-control-active" : ""}"
10304
+ data-testid="cpk-snippet-item"
10305
+ data-recipe=${snippet.recipe}
10306
+ aria-current=${isSelected ? "page" : nothing}
10307
+ aria-label="${recipeLabel(snippet.recipe)}: ${snippet.name}"
10308
+ style=${INTERACTIVE_FOCUS_BASE_STYLE}
10309
+ @click=${() => this.selectEventSnippet(snippet.id)}
10310
+ >
10311
+ <span
10312
+ class="inspector-nav-icon"
10313
+ aria-hidden="true"
10314
+ >
10315
+ <span
10316
+ class="flex h-6 w-6 items-center justify-center rounded-md ${recipeIconWrapClass(snippet.recipe)}"
10317
+ >
10318
+ ${this.renderIcon(recipeIconName(snippet.recipe))}
10319
+ </span>
10320
+ </span>
10321
+ <span class="inspector-nav-label"
10322
+ >${snippet.name}</span
10323
+ >
10324
+ </button>
10325
+ `;
10326
+ })}
10327
+ </div>
10328
+ `)}
10329
+ </nav>
10330
+ `}
10331
+ </div>
10332
+ <div
10333
+ class="w-1.5 shrink-0 cursor-col-resize bg-gray-200 hover:bg-gray-400"
10334
+ role="separator"
10335
+ aria-orientation="vertical"
10336
+ aria-label="Resize snippet list"
10337
+ title="Drag to resize"
10338
+ data-testid="cpk-snippet-list-resize"
10339
+ style="touch-action:none"
10340
+ @pointerdown=${this.handleSnippetListDividerPointerDown}
10341
+ @pointermove=${this.handleSnippetListDividerPointerMove}
10342
+ @pointerup=${this.handleSnippetListDividerPointerUp}
10343
+ @pointercancel=${this.handleSnippetListDividerPointerUp}
10344
+ ></div>
10345
+ <div class="flex min-h-0 min-w-0 flex-1 flex-col gap-2 overflow-auto p-4">
10346
+ <label class="text-[11px] text-gray-600">
10347
+ Name
10348
+ <input
10349
+ class="mt-1 w-full rounded-md border border-gray-200 px-2 py-1 text-[11px]"
10350
+ data-testid="cpk-snippet-name"
10351
+ .value=${this.snippetName}
10352
+ @input=${(event) => {
10353
+ this.snippetName = event.target.value;
10354
+ }}
10355
+ />
10356
+ </label>
10357
+ <label class="text-[11px] text-gray-600">
10358
+ Recipe
10359
+ <select
10360
+ class="mt-1 w-full rounded-md border border-gray-200 px-2 py-1 text-[11px]"
10361
+ data-testid="cpk-snippet-recipe"
10362
+ .value=${this.snippetRecipe}
10363
+ @change=${this.handleSnippetRecipeChange}
10364
+ >
10365
+ ${[
10366
+ "tool-call",
10367
+ "reasoning",
10368
+ "text",
10369
+ "activity",
10370
+ "raw"
10371
+ ].map((recipe) => html`<option
10372
+ value=${recipe}
10373
+ ?selected=${recipe === this.snippetRecipe}
10374
+ >
10375
+ ${recipeLabel(recipe)}
10376
+ </option>`)}
10377
+ </select>
10378
+ </label>
10379
+ ${this.snippetRecipe === "tool-call" ? html`
10380
+ <label class="text-[11px] text-gray-600">
10381
+ Tool
10382
+ <input
10383
+ list="cpk-snippet-tools"
10384
+ class="mt-1 w-full rounded-md border border-gray-200 px-2 py-1 text-[11px]"
10385
+ data-testid="cpk-snippet-tool-name"
10386
+ .value=${this.snippetToolName}
10387
+ @input=${(event) => {
10388
+ this.snippetToolName = event.target.value;
10389
+ this.applyRecipeToEditor();
10390
+ }}
10391
+ />
10392
+ <datalist id="cpk-snippet-tools">
10393
+ ${tools.map((tool) => html`<option value=${tool.name}></option>`)}
10394
+ </datalist>
10395
+ </label>
10396
+ <label class="text-[11px] text-gray-600">
10397
+ Args JSON
10398
+ <textarea
10399
+ class="mt-1 h-20 w-full rounded-md border border-gray-200 px-2 py-1 font-mono text-[11px]"
10400
+ data-testid="cpk-snippet-tool-args"
10401
+ .value=${this.snippetToolArgs}
10402
+ @input=${(event) => {
10403
+ this.snippetToolArgs = event.target.value;
10404
+ this.applyRecipeToEditor();
10405
+ }}
10406
+ ></textarea>
10407
+ </label>
10408
+ ` : nothing}
10409
+ ${this.snippetRecipe === "reasoning" ? html`<label class="text-[11px] text-gray-600">
10410
+ Reasoning
10411
+ <textarea
10412
+ class="mt-1 h-20 w-full rounded-md border border-gray-200 px-2 py-1 text-[11px]"
10413
+ data-testid="cpk-snippet-reasoning"
10414
+ .value=${this.snippetReasoningText}
10415
+ @input=${(event) => {
10416
+ this.snippetReasoningText = event.target.value;
10417
+ this.applyRecipeToEditor();
10418
+ }}
10419
+ ></textarea>
10420
+ </label>` : nothing}
10421
+ ${this.snippetRecipe === "text" ? html`<label class="text-[11px] text-gray-600">
10422
+ Assistant text
10423
+ <textarea
10424
+ class="mt-1 h-20 w-full rounded-md border border-gray-200 px-2 py-1 text-[11px]"
10425
+ data-testid="cpk-snippet-text"
10426
+ .value=${this.snippetTextContent}
10427
+ @input=${(event) => {
10428
+ this.snippetTextContent = event.target.value;
10429
+ this.applyRecipeToEditor();
10430
+ }}
10431
+ ></textarea>
10432
+ </label>` : nothing}
10433
+ ${this.snippetRecipe === "activity" ? html`
10434
+ <label class="text-[11px] text-gray-600">
10435
+ Activity type
10436
+ <input
10437
+ list="cpk-snippet-activity"
10438
+ class="mt-1 w-full rounded-md border border-gray-200 px-2 py-1 text-[11px]"
10439
+ data-testid="cpk-snippet-activity-type"
10440
+ .value=${this.snippetActivityType}
10441
+ @input=${(event) => {
10442
+ this.snippetActivityType = event.target.value;
10443
+ this.applyRecipeToEditor();
10444
+ }}
10445
+ />
10446
+ <datalist id="cpk-snippet-activity">
10447
+ ${ACTIVITY_STARTERS.map((type) => html`<option value=${type}></option>`)}
10448
+ </datalist>
10449
+ </label>
10450
+ <label class="text-[11px] text-gray-600">
10451
+ Content JSON
10452
+ <textarea
10453
+ class="mt-1 h-24 w-full rounded-md border border-gray-200 px-2 py-1 font-mono text-[11px]"
10454
+ data-testid="cpk-snippet-activity-content"
10455
+ .value=${this.snippetActivityContent}
10456
+ @input=${(event) => {
10457
+ this.snippetActivityContent = event.target.value;
10458
+ this.applyRecipeToEditor();
10459
+ }}
10460
+ ></textarea>
10461
+ </label>
10462
+ ` : nothing}
10463
+ <label class="text-[11px] text-gray-600">
10464
+ Events JSON
10465
+ <textarea
10466
+ class="mt-1 h-40 w-full rounded-md border border-gray-200 px-2 py-1 font-mono text-[11px]"
10467
+ data-testid="cpk-snippet-json"
10468
+ .value=${this.snippetJson}
10469
+ @input=${(event) => {
10470
+ this.snippetJson = event.target.value;
10471
+ this.requestUpdate();
10472
+ }}
10473
+ ></textarea>
10474
+ </label>
10475
+ <div class="flex flex-wrap gap-2">
10476
+ <button
10477
+ type="button"
10478
+ class="rounded-md bg-gray-900 px-3 py-1.5 text-[11px] text-white disabled:opacity-50"
10479
+ data-testid="cpk-snippet-run"
10480
+ ?disabled=${!canRun}
10481
+ title=${runBlocked ? "The agent is running. Wait for the current run to end." : !agent ? "No agent is available to inject into." : !canRunJson ? "Events JSON must be a non-empty event array." : "Run snippet"}
10482
+ @click=${() => void this.runCurrentSnippet()}
10483
+ >
10484
+ ${runBlocked ? "Agent running" : "Run"}
10485
+ </button>
10486
+ <button
10487
+ type="button"
10488
+ class="rounded-md border border-gray-300 bg-white px-3 py-1.5 text-[11px] disabled:opacity-50"
10489
+ data-testid="cpk-snippet-save"
10490
+ ?disabled=${!canSave}
10491
+ title=${canSave ? "Save snippet" : "Events JSON must be a non-empty event array."}
10492
+ @click=${() => this.saveCurrentSnippet("pane")}
10493
+ >
10494
+ Save
10495
+ </button>
10496
+ <button
10497
+ type="button"
10498
+ class="rounded-md border border-gray-300 bg-white px-3 py-1.5 text-[11px] disabled:opacity-50"
10499
+ data-testid="cpk-snippet-reset"
10500
+ ?disabled=${!canReset}
10501
+ title=${canReset ? "Remove the last inject from the thread" : "No Inspector inject to reset."}
10502
+ @click=${() => this.resetLastSnippetRun()}
10503
+ >
10504
+ Reset last run
10505
+ </button>
10506
+ <button
10507
+ type="button"
10508
+ class="rounded-md border border-gray-300 bg-white px-3 py-1.5 text-[11px] disabled:opacity-50"
10509
+ data-testid="cpk-snippet-delete"
10510
+ ?disabled=${!canDelete}
10511
+ title=${canDelete ? "Delete this snippet" : "Select a snippet to delete."}
10512
+ @click=${() => {
10513
+ if (!this.selectedSnippetId) return;
10514
+ if (!window.confirm("Delete this snippet?")) return;
10515
+ this.eventSnippets = deleteEventSnippet(this.selectedSnippetId);
10516
+ this.selectedSnippetId = null;
10517
+ this.requestUpdate();
10518
+ }}
10519
+ >
10520
+ Delete
10521
+ </button>
10522
+ <button
10523
+ type="button"
10524
+ class="rounded-md border border-gray-300 bg-white px-3 py-1.5 text-[11px] disabled:opacity-50"
10525
+ data-testid="cpk-snippet-export"
10526
+ ?disabled=${!canExport}
10527
+ title=${canExport ? "Export snippets" : "Save a snippet before you export."}
10528
+ @click=${() => {
10529
+ if (!canExport) return;
10530
+ const blob = new Blob([exportEventSnippetsJson(this.eventSnippets)], { type: "application/json" });
10531
+ const url = URL.createObjectURL(blob);
10532
+ const link = document.createElement("a");
10533
+ link.href = url;
10534
+ link.download = "event-snippets.json";
10535
+ link.click();
10536
+ URL.revokeObjectURL(url);
10537
+ }}
10538
+ >
10539
+ Export
10540
+ </button>
10541
+ </div>
10542
+ </div>
8818
10543
  </div>
8819
- <p
8820
- class="mx-auto max-w-3xl px-3 py-2 text-center text-[10px] leading-4 text-gray-500"
8821
- >
8822
- AI can make mistakes. Please verify important information.
8823
- </p>
8824
- </form>
10544
+ </div>
8825
10545
  `;
8826
10546
  }
8827
10547
  renderMainContent() {
8828
10548
  if (this.settingsOpen) return this.renderSettingsPanel();
8829
10549
  if (this.selectedMenu === "home") return this.renderHomeView();
8830
10550
  if (this.selectedMenu === WHATS_NEW_MENU_KEY) return this.renderWhatsNewView();
8831
- if (this.selectedMenu === "ag-ui-events") return this.renderEventsTable();
10551
+ if (this.selectedMenu === "ag-ui-events") return html`
10552
+ <div class="flex h-full min-h-0 flex-col">
10553
+ ${this.renderEventErrorBanner("run")}
10554
+ <div class="min-h-0 flex-1 overflow-hidden">
10555
+ ${this.renderEventsTable()}
10556
+ </div>
10557
+ </div>
10558
+ `;
8832
10559
  if (this.selectedMenu === "playground") return this.renderPlaygroundView();
10560
+ if (this.selectedMenu === "event-snippets") return this.renderEventSnippetsView();
8833
10561
  if (this.selectedMenu === "agents") return this.renderAgentsView();
8834
10562
  if (this.selectedMenu === "frontend-tools") return this.renderToolsView();
8835
10563
  if (this.selectedMenu === "capabilities") return this.renderCapabilitiesView();
@@ -9213,8 +10941,9 @@ ${argsString}</pre
9213
10941
  </div>
9214
10942
  `;
9215
10943
  }
9216
- trackOpened(source, hadUnseenAnnouncement, firstOpen = false) {
10944
+ trackOpened(source, hadUnseenAnnouncement, firstOpen = false, activeSignal = null) {
9217
10945
  if (this.core?.telemetryDisabled) return;
10946
+ const errorSignal = activeSignal !== null && isErrorSignalKey(activeSignal) ? activeSignal : null;
9218
10947
  trackInspectorOpened({
9219
10948
  open_source: source,
9220
10949
  ...this.runtimeStatus === "connected" ? {
@@ -9223,6 +10952,8 @@ ${argsString}</pre
9223
10952
  } : {},
9224
10953
  runtime_url_type: getRuntimeUrlType(this.core?.runtimeUrl),
9225
10954
  has_unseen_announcement: hadUnseenAnnouncement,
10955
+ has_error_signal: errorSignal !== null,
10956
+ ...errorSignal === null ? {} : { error_signal_source: errorSignal },
9226
10957
  first_open: firstOpen
9227
10958
  });
9228
10959
  }
@@ -9386,6 +11117,51 @@ ${argsString}</pre
9386
11117
  example_kind: exampleKind
9387
11118
  });
9388
11119
  }
11120
+ subscribeToInspectorThreadBridge() {
11121
+ this.unsubscribeFromInspectorThreadBridge();
11122
+ if (!isInspectorThreadBridgeEnabled()) return;
11123
+ this.inspectorBridgeUnsubscribers.push(onInspectorActiveThread((payload) => {
11124
+ if (payload.requestId !== this.activeViewInAppRequestId) return;
11125
+ this.inAppThreadId = payload.threadId;
11126
+ this.inAppAgentId = payload.agentId;
11127
+ this.inAppSource = payload.source;
11128
+ if (payload.source === "app") {
11129
+ this.activeViewInAppRequestId = null;
11130
+ this.viewInAppError = null;
11131
+ }
11132
+ this.requestUpdate();
11133
+ }), onInspectorViewThreadResult((payload) => {
11134
+ if (payload.requestId !== this.activeViewInAppRequestId) return;
11135
+ if (payload.ok) {
11136
+ this.viewInAppError = null;
11137
+ this.inAppThreadId = payload.threadId;
11138
+ this.inAppAgentId = payload.agentId;
11139
+ this.inAppSource = "override";
11140
+ } else {
11141
+ this.activeViewInAppRequestId = null;
11142
+ this.inAppThreadId = null;
11143
+ this.inAppAgentId = null;
11144
+ this.inAppSource = null;
11145
+ this.viewInAppError = "The app could not load that thread. The previous chat is back.";
11146
+ }
11147
+ this.requestUpdate();
11148
+ }));
11149
+ }
11150
+ unsubscribeFromInspectorThreadBridge() {
11151
+ for (const unsubscribe of this.inspectorBridgeUnsubscribers) unsubscribe();
11152
+ this.inspectorBridgeUnsubscribers = [];
11153
+ }
11154
+ getViewInAppMode(thread, isExample) {
11155
+ if (!isInspectorThreadBridgeEnabled()) return "hidden";
11156
+ if (!thread || isExample) return "hidden";
11157
+ if (this.activeViewInAppRequestId && this.inAppSource === "override" && this.inAppThreadId === thread.id) return "stop";
11158
+ return "view";
11159
+ }
11160
+ getSelectedRealThread() {
11161
+ if (!this.selectedThreadId) return null;
11162
+ if (this.selectedThreadId === this.selectedLocalExampleThreadId) return null;
11163
+ return this.getActiveThreadsState().displayThreads.find((thread) => thread.id === this.selectedThreadId) ?? null;
11164
+ }
9389
11165
  getCurrentExampleTourProps() {
9390
11166
  const exampleKind = this.selectedThreadId ? getExampleKind(this.selectedThreadId) : void 0;
9391
11167
  const tourPair = getExampleTourTelemetryPair(this.exampleTourStep);
@@ -10005,7 +11781,7 @@ ${argsString}</pre
10005
11781
  <line x1="12" y1="16" x2="12.01" y2="16" />
10006
11782
  </svg>
10007
11783
  <span style="font-size: 13px; color: #c0333a;">
10008
- Failed to load learning data
11784
+ ${MEMORY_LOAD_ERROR_LABEL}
10009
11785
  </span>
10010
11786
  <span
10011
11787
  style="
@@ -10018,6 +11794,17 @@ ${argsString}</pre
10018
11794
  >
10019
11795
  ${this._memoriesError.message}
10020
11796
  </span>
11797
+ <span
11798
+ style="
11799
+ max-width: 320px;
11800
+ text-align: center;
11801
+ font-size: 11px;
11802
+ line-height: 1.5;
11803
+ color: #c0333a;
11804
+ "
11805
+ >
11806
+ ${EVENT_ERROR_GUIDANCE.memory.advice}
11807
+ </span>
10021
11808
  </div>
10022
11809
  `;
10023
11810
  if (this._memoriesLoading && this._memories.length === 0) return html`
@@ -10201,6 +11988,7 @@ ${argsString}</pre
10201
11988
  data-color-scheme=${this.colorScheme}
10202
11989
  .threads=${visibleThreads}
10203
11990
  .selectedThreadId=${this.selectedThreadId}
11991
+ .inAppThreadId=${this.inAppThreadId}
10204
11992
  .errorMessage=${threadsErrorMessage}
10205
11993
  .suppressEmptyState=${loadingWithoutRows}
10206
11994
  @threadSelected=${(e) => {
@@ -10270,6 +12058,10 @@ ${argsString}</pre
10270
12058
  .headers=${this._core?.headers ?? {}}
10271
12059
  .threadInspectionAvailable=${selectedThreadIsLocalExample || this.areThreadEndpointsAvailable() && this._core?.threadEndpoints?.inspect !== false}
10272
12060
  .liveMessageVersion=${this.liveMessageVersion.get(selectedThread.id) ?? 0}
12061
+ .viewInAppMode=${this.getViewInAppMode(selectedThread, selectedThreadIsLocalExample)}
12062
+ .viewInAppError=${this.viewInAppError}
12063
+ @viewInApp=${this.handleViewInApp}
12064
+ @stopViewing=${this.handleStopViewing}
10273
12065
  .focusMessageId=${this.focusedThreadMessageId}
10274
12066
  .focusRequestId=${this.threadFocusRequestId}
10275
12067
  .agentStateInput=${this.getLatestStateForAgent(selectedThread.agentId)}
@@ -10438,7 +12230,10 @@ ${argsString}</pre
10438
12230
  </div>
10439
12231
  </div>
10440
12232
  `;
10441
- else body = html`
12233
+ else {
12234
+ const runError = this.eventErrorDetails.run;
12235
+ const failedRunEventId = runError ? this.findLatestRunErrorEvent(runError.agentId)?.id : void 0;
12236
+ body = html`
10442
12237
  <div class="relative h-full w-full overflow-y-auto overflow-x-hidden">
10443
12238
  <table class="w-full table-fixed border-collapse text-xs box-border">
10444
12239
  <colgroup>
@@ -10450,10 +12245,10 @@ ${argsString}</pre
10450
12245
  <thead class="sticky top-0 z-10">
10451
12246
  <tr class="bg-white">
10452
12247
  ${[
10453
- "Agent",
10454
- "Time",
10455
- "Event Type"
10456
- ].map((label, col) => html` <th
12248
+ "Agent",
12249
+ "Time",
12250
+ "Event Type"
12251
+ ].map((label, col) => html` <th
10457
12252
  class="border-b border-gray-200 bg-white px-3 py-2 text-left font-medium text-gray-900"
10458
12253
  style="position:relative;overflow:hidden;"
10459
12254
  >
@@ -10475,16 +12270,18 @@ ${argsString}</pre
10475
12270
  </thead>
10476
12271
  <tbody>
10477
12272
  ${filteredEvents.map((event, index) => {
10478
- const rowBg = index % 2 === 0 ? "bg-white" : "bg-gray-50/50";
10479
- const badgeClasses = this.getEventBadgeClasses(event.type);
10480
- const extractedEvent = this.extractEventFromPayload(event.payload);
10481
- const inlineEvent = this.stringifyPayload(extractedEvent, false) || "—";
10482
- const prettyEvent = this.stringifyPayload(extractedEvent, true) || inlineEvent;
10483
- const isExpanded = this.expandedRows.has(event.id);
10484
- return html`
12273
+ const isFailedRunEvent = failedRunEventId !== void 0 && event.id === failedRunEventId;
12274
+ const rowBg = isFailedRunEvent ? "bg-rose-50" : index % 2 === 0 ? "bg-white" : "bg-gray-50/50";
12275
+ const badgeClasses = this.getEventBadgeClasses(event.type);
12276
+ const extractedEvent = this.extractEventFromPayload(event.payload);
12277
+ const inlineEvent = this.stringifyPayload(extractedEvent, false) || "—";
12278
+ const prettyEvent = this.stringifyPayload(extractedEvent, true) || inlineEvent;
12279
+ const isExpanded = this.expandedRows.has(event.id);
12280
+ return html`
10485
12281
  <tr
10486
12282
  class="${rowBg} cursor-pointer transition hover:bg-blue-50/50"
10487
12283
  data-inspector-event-id=${event.id}
12284
+ data-cpk-failed-run-event=${isFailedRunEvent ? event.id : void 0}
10488
12285
  @click=${(clickEvent) => this.toggleRowExpansion(event.id, clickEvent)}
10489
12286
  >
10490
12287
  <td
@@ -10517,9 +12314,9 @@ ${prettyEvent}</pre
10517
12314
  <button
10518
12315
  class="absolute right-0 top-0 cursor-pointer rounded px-2 py-1 text-[10px] opacity-0 transition group-hover:opacity-100 ${this.copiedEvents.has(event.id) ? "bg-green-100 text-green-700" : "bg-gray-100 text-gray-600 hover:bg-gray-200 hover:text-gray-900"}"
10519
12316
  @click=${(e) => {
10520
- e.stopPropagation();
10521
- this.copyToClipboard(prettyEvent, event.id, e);
10522
- }}
12317
+ e.stopPropagation();
12318
+ this.copyToClipboard(prettyEvent, event.id, e);
12319
+ }}
10523
12320
  >
10524
12321
  ${this.copiedEvents.has(event.id) ? html`
10525
12322
  <span>✓ Copied</span>
@@ -10532,11 +12329,12 @@ ${prettyEvent}</pre
10532
12329
  </td>
10533
12330
  </tr>
10534
12331
  `;
10535
- })}
12332
+ })}
10536
12333
  </tbody>
10537
12334
  </table>
10538
12335
  </div>
10539
12336
  `;
12337
+ }
10540
12338
  return html`
10541
12339
  <div
10542
12340
  class="${embedded ? "flex h-[28rem] min-h-[20rem] flex-col" : "flex h-full flex-col"}"
@@ -10601,6 +12399,7 @@ ${prettyEvent}</pre
10601
12399
  }
10602
12400
  renderAgentsView() {
10603
12401
  if (this.selectedContext === "all-agents") return html`
12402
+ ${this.renderEventErrorBanner("tool")}
10604
12403
  <div
10605
12404
  class="flex h-full items-center justify-center px-4 py-8 text-center"
10606
12405
  >
@@ -10624,6 +12423,7 @@ ${prettyEvent}</pre
10624
12423
  const messages = this.getLatestMessagesForAgent(agentId);
10625
12424
  return html`
10626
12425
  <div class="cpk-agent-view flex flex-col gap-4 p-4 overflow-auto">
12426
+ ${this.renderEventErrorBanner("tool")}
10627
12427
  <!-- Agent Overview Card -->
10628
12428
  <div
10629
12429
  class="cpk-agent-overview rounded-lg border border-gray-200 bg-white p-4"
@@ -10756,8 +12556,13 @@ ${prettyEvent}</pre
10756
12556
  const toolCalls = msg.toolCalls ?? [];
10757
12557
  const hasContent = rawContent.trim().length > 0;
10758
12558
  const contentFallback = toolCalls.length > 0 ? "Invoked tool call" : "—";
12559
+ const toolError = this.eventErrorDetails.tool;
12560
+ const isFailedResult = role === "tool" && toolError?.toolCallId !== void 0 && toolError.toolCallId === msg.toolCallId;
10759
12561
  return html`
10760
- <div class="flex items-start">
12562
+ <div
12563
+ class=${isFailedResult ? "flex items-start bg-rose-50" : "flex items-start"}
12564
+ data-cpk-failed-tool-result=${isFailedResult ? msg.toolCallId : nothing}
12565
+ >
10761
12566
  <div class="w-40 shrink-0 px-4 py-2">
10762
12567
  <span
10763
12568
  class="inline-flex rounded px-2 py-0.5 text-[10px] font-medium ${roleColors[role] || roleColors.unknown}"
@@ -10863,6 +12668,81 @@ ${prettyEvent}</pre
10863
12668
  </div>
10864
12669
  `;
10865
12670
  }
12671
+ /**
12672
+ * The Agent view is empty on "All Agents". Pick the agent that just failed
12673
+ * a tool, or the one with the most recent activity.
12674
+ */
12675
+ applyEventErrorLanding(key) {
12676
+ const error = this.eventErrorDetails[key];
12677
+ if (!error) {
12678
+ if (this.selectedMenu === "agents") this.focusAgentForView();
12679
+ return;
12680
+ }
12681
+ if (error.agentId && this.contextOptions.some((option) => option.key === error.agentId)) this.selectedContext = error.agentId;
12682
+ else if (this.selectedMenu === "agents") this.focusAgentForView(error);
12683
+ if (key === "tool" && error.toolCallId) {
12684
+ this.pendingScrollToToolCallId = error.toolCallId;
12685
+ return;
12686
+ }
12687
+ if (key === "run") {
12688
+ this.eventFilterText = "";
12689
+ this.eventTypeFilter = "all";
12690
+ const event = this.findLatestRunErrorEvent(error.agentId);
12691
+ if (!event) return;
12692
+ this.expandedRows.clear();
12693
+ this.expandedRows.add(event.id);
12694
+ this.pendingScrollToEventId = event.id;
12695
+ }
12696
+ }
12697
+ /**
12698
+ * Whether the landing view really carries the item the card points at.
12699
+ * Mirrors the two branches of `applyEventErrorLanding` that can bail out.
12700
+ */
12701
+ hasEventErrorHighlight(key) {
12702
+ const error = this.eventErrorDetails[key];
12703
+ if (!error) return false;
12704
+ if (key === "tool") return error.toolCallId !== void 0;
12705
+ if (key === "run") return this.findLatestRunErrorEvent(error.agentId) !== void 0;
12706
+ return false;
12707
+ }
12708
+ findLatestRunErrorEvent(agentId) {
12709
+ return (agentId && agentId !== "all-agents" ? this.agentEvents.get(agentId) ?? [] : this.flattenedEvents).find((event) => event.type === "RUN_ERROR");
12710
+ }
12711
+ flushErrorLandingScroll() {
12712
+ const eventId = this.pendingScrollToEventId;
12713
+ if (eventId) {
12714
+ this.pendingScrollToEventId = null;
12715
+ Array.from(this.activeRoot.querySelectorAll("[data-inspector-event-id]")).find((candidate) => candidate.dataset.inspectorEventId === eventId)?.scrollIntoView?.({ block: "center" });
12716
+ }
12717
+ const toolCallId = this.pendingScrollToToolCallId;
12718
+ if (toolCallId) {
12719
+ this.pendingScrollToToolCallId = null;
12720
+ const escaped = escapeSelectorValue(toolCallId);
12721
+ (this.activeRoot.querySelector(`[data-cpk-failed-tool-call="${escaped}"]`) ?? this.activeRoot.querySelector(`[data-cpk-failed-tool-result="${escaped}"]`))?.scrollIntoView?.({ block: "center" });
12722
+ }
12723
+ }
12724
+ /**
12725
+ * The Agent view is empty on "All Agents". Pick the agent that just failed
12726
+ * a tool, or the one with the most recent activity.
12727
+ */
12728
+ focusAgentForView(error) {
12729
+ const agentOptions = this.contextOptions.filter((opt) => opt.key !== "all-agents");
12730
+ if (agentOptions.length === 0) return;
12731
+ const errorAgentId = error?.agentId;
12732
+ if (errorAgentId && agentOptions.some((option) => option.key === errorAgentId)) {
12733
+ this.selectedContext = errorAgentId;
12734
+ return;
12735
+ }
12736
+ if (this.selectedContext !== "all-agents") return;
12737
+ const mostRecent = agentOptions.reduce((best, opt) => {
12738
+ const ts = this.getAgentStats(opt.key).lastActivity ?? -1;
12739
+ return best === null || ts > best.ts ? {
12740
+ key: opt.key,
12741
+ ts
12742
+ } : best;
12743
+ }, null);
12744
+ this.selectedContext = mostRecent ? mostRecent.key : agentOptions[0].key;
12745
+ }
10866
12746
  handleMenuSelect(key) {
10867
12747
  if (!this.menuItems.some((item) => item.key === key)) return;
10868
12748
  const previousMenu = this.selectedMenu;
@@ -10871,19 +12751,6 @@ ${prettyEvent}</pre
10871
12751
  this.selectedMenu = key;
10872
12752
  this.settingsOpen = false;
10873
12753
  this.lastSelectedMenuByGroup[getGroupForMenu(key)] = key;
10874
- if (key === "agents" && this.selectedContext === "all-agents") {
10875
- const agentOptions = this.contextOptions.filter((opt) => opt.key !== "all-agents");
10876
- if (agentOptions.length > 0) {
10877
- const mostRecent = agentOptions.reduce((best, opt) => {
10878
- const ts = this.getAgentStats(opt.key).lastActivity ?? -1;
10879
- return best === null || ts > best.ts ? {
10880
- key: opt.key,
10881
- ts
10882
- } : best;
10883
- }, null);
10884
- this.selectedContext = mostRecent ? mostRecent.key : agentOptions[0].key;
10885
- }
10886
- }
10887
12754
  if (previousMenu === "agents" && key !== "agents") {
10888
12755
  if (this.contextOptions.filter((opt) => opt.key !== "all-agents").length > 1) this.selectedContext = "all-agents";
10889
12756
  }
@@ -10897,10 +12764,13 @@ ${prettyEvent}</pre
10897
12764
  if (previousMenu !== "memories" && !this.core?.telemetryDisabled) trackMemoriesTabClicked(this.getMemoriesTelemetryProps());
10898
12765
  }
10899
12766
  if (key === "home" && previousMenu !== "home") this.homeViewedThisOpen = false;
10900
- if (key === "ag-ui-events" || key === "agents") requestAnimationFrame(() => {
10901
- const scroller = this.activeRoot.querySelector("#cpk-main-scroll");
10902
- if (scroller) scroller.scrollTop = 0;
10903
- });
12767
+ if (key === "event-snippets") this.reloadEventSnippets();
12768
+ if (key === "ag-ui-events" || key === "agents") {
12769
+ if (!(this.pendingScrollToEventId !== null || this.pendingScrollToToolCallId !== null)) requestAnimationFrame(() => {
12770
+ const scroller = this.activeRoot.querySelector("#cpk-main-scroll");
12771
+ if (scroller) scroller.scrollTop = 0;
12772
+ });
12773
+ }
10904
12774
  this.contextMenuOpen = false;
10905
12775
  this.layoutMenuOpen = false;
10906
12776
  this.persistState();
@@ -11536,50 +13406,443 @@ ${prettyEvent}</pre
11536
13406
  else this.expandedRows.add(eventId);
11537
13407
  this.requestUpdate();
11538
13408
  }
13409
+ /** Whether a given subject currently has something to say. */
13410
+ isSignalArmed(key) {
13411
+ if (isWiringErrorKey(key)) return this.errorSignalArmed[key];
13412
+ if (isEventErrorKey(key)) return this.eventErrorArmed[key];
13413
+ return this.newsSignalArmed;
13414
+ }
13415
+ /**
13416
+ * The signal that owns the single launcher dot, or null when the launcher is
13417
+ * quiet. Precedence rather than replacement: a suppressed signal stays armed
13418
+ * and takes the dot as soon as the higher-priority one clears, and its own
13419
+ * navigation marker is visible the whole time.
13420
+ */
13421
+ getActiveLauncherSignal() {
13422
+ for (const key of LAUNCHER_SIGNAL_PRIORITY_ORDER) if (this.isSignalArmed(key)) return key;
13423
+ return null;
13424
+ }
13425
+ /**
13426
+ * The marker a navigation entry carries, or null for an unmarked entry.
13427
+ *
13428
+ * Markers are independent of the launcher's single dot, so a suppressed
13429
+ * signal is never actually hidden once the panel is open. They also render
13430
+ * on the entry that is *currently selected*: for the news signal that never
13431
+ * mattered, because its marker clears as soon as the view renders, but a
13432
+ * state mirror stays true while it is being read and suppressing it on the
13433
+ * active entry would make it reappear on navigating away.
13434
+ */
13435
+ getNavigationSignalFor(key) {
13436
+ for (const signalKey of LAUNCHER_SIGNAL_PRIORITY_ORDER) {
13437
+ const signal = LAUNCHER_SIGNALS[signalKey];
13438
+ if (signal.markerTarget !== key) continue;
13439
+ if (!this.isSignalArmed(signalKey)) continue;
13440
+ if (signalKey === NEWS_SIGNAL_ID && !this.announcementLoaded) continue;
13441
+ return signal;
13442
+ }
13443
+ return null;
13444
+ }
13445
+ /** Whether a wiring error source is currently red. */
13446
+ hasArmedErrorSignal() {
13447
+ return WIRING_ERROR_KEYS.some((source) => this.errorSignalArmed[source]);
13448
+ }
13449
+ armEventErrorFromCode(code, message, context) {
13450
+ const key = eventErrorKeyForCode(code);
13451
+ if (key === null) return;
13452
+ const agentId = typeof context?.agentId === "string" && context.agentId.length > 0 ? context.agentId : void 0;
13453
+ const toolName = typeof context?.toolName === "string" && context.toolName.length > 0 ? context.toolName : void 0;
13454
+ const toolCallId = typeof context?.toolCallId === "string" && context.toolCallId.length > 0 ? context.toolCallId : void 0;
13455
+ this.armEventError(key, message, {
13456
+ agentId,
13457
+ toolName,
13458
+ toolCallId
13459
+ });
13460
+ }
13461
+ armEventError(key, message, extras = {}) {
13462
+ this.eventErrorDetails[key] = {
13463
+ message,
13464
+ ...extras
13465
+ };
13466
+ const wasArmed = this.eventErrorArmed[key];
13467
+ this.eventErrorArmed[key] = true;
13468
+ if (!wasArmed) this.startSignalPulse(key);
13469
+ if (this.isOpen && !this.settingsOpen && this.selectedMenu === LAUNCHER_SIGNALS[key].landingTarget) this.applyEventErrorLanding(key);
13470
+ this.requestUpdate();
13471
+ }
13472
+ clearEventError(key) {
13473
+ if (!this.eventErrorArmed[key]) return;
13474
+ this.eventErrorArmed[key] = false;
13475
+ this.errorSignalViewedSources.delete(key);
13476
+ this.retireSignal(key);
13477
+ this.requestUpdate();
13478
+ }
13479
+ clearAllEventErrors() {
13480
+ for (const key of EVENT_ERROR_KEYS) {
13481
+ this.eventErrorDetails[key] = null;
13482
+ if (!this.eventErrorArmed[key]) continue;
13483
+ this.eventErrorArmed[key] = false;
13484
+ this.retireSignal(key);
13485
+ }
13486
+ }
13487
+ /**
13488
+ * An event error is unread until its landing view is actually on screen.
13489
+ * Opening the Inspector for a different leaf must not burn it.
13490
+ */
13491
+ maybeCompleteEventErrorView() {
13492
+ if (!this.isOpen || this.settingsOpen) return;
13493
+ for (const key of EVENT_ERROR_KEYS) {
13494
+ if (!this.eventErrorArmed[key]) continue;
13495
+ if (this.selectedMenu !== LAUNCHER_SIGNALS[key].landingTarget) continue;
13496
+ this.clearEventError(key);
13497
+ }
13498
+ }
13499
+ isReducedMotionPreferred() {
13500
+ return typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches === true;
13501
+ }
11539
13502
  armNewsSignal(options) {
11540
13503
  const wasArmed = this.newsSignalArmed;
11541
13504
  this.newsSignalArmed = true;
11542
- if (options.pulse && this.isOpen) {
11543
- this.newsSignalPulsePending = true;
11544
- if (!wasArmed) this.requestUpdate();
11545
- } else if (options.pulse) this.startNewsSignalPulse();
13505
+ if (options.pulse) this.startSignalPulse(NEWS_SIGNAL_ID);
11546
13506
  else if (!wasArmed) this.requestUpdate();
11547
13507
  }
11548
13508
  clearNewsSignal() {
11549
13509
  if (!this.newsSignalArmed) return;
11550
13510
  this.newsSignalArmed = false;
11551
- this.newsSignalPulsePending = false;
11552
13511
  if (this.announcementTimestamp) saveAnnouncementReadTimestamp(this.announcementTimestamp);
11553
- if (this.newsSignalPulsing) this.stopNewsSignalPulse();
13512
+ this.retireSignal(NEWS_SIGNAL_ID);
11554
13513
  this.requestUpdate();
11555
13514
  }
11556
- startNewsSignalPulse() {
11557
- this.stopNewsSignalPulse();
11558
- if (typeof document !== "undefined" && document.visibilityState !== "visible") {
11559
- this.newsSignalPulsePending = true;
13515
+ /**
13516
+ * Requests one beat for a signal, running it now or deferring it.
13517
+ *
13518
+ * There is a single pending slot and four reasons a beat cannot land. All
13519
+ * four are the same situation — "cannot land now, run later" — and treating
13520
+ * them alike is the point: three separate behaviours for one situation would
13521
+ * not survive a third signal.
13522
+ *
13523
+ * Reason 3 is not cosmetic. Starting a beat while one runs does not restart
13524
+ * the animation, because the attribute it binds to does not change value and
13525
+ * the pseudo-element selectors match on attribute *presence*; the running
13526
+ * beat would merely change colour mid-flight. A failure that arms during an
13527
+ * announcement beat must wait for that beat to end, or it would run only as
13528
+ * its final fraction.
13529
+ *
13530
+ * A failure's beat is followed by a pill, and the whole 3.4-second gesture
13531
+ * holds this one slot for its full duration. That is not a second scheduling
13532
+ * concept: reason 3 already says "another beat is running", and a gesture is
13533
+ * simply a longer beat.
13534
+ */
13535
+ startSignalPulse(key) {
13536
+ if (this.isOpen || typeof document !== "undefined" && document.visibilityState !== "visible" || this.gestureSlotSignal !== null && this.gestureSlotSignal !== key || this.getActiveLauncherSignal() !== key) {
13537
+ const pending = this.pendingPulseSignal;
13538
+ if (pending === null || LAUNCHER_SIGNALS[key].priority >= LAUNCHER_SIGNALS[pending].priority) this.pendingPulseSignal = key;
11560
13539
  this.requestUpdate();
11561
13540
  return;
11562
13541
  }
11563
- this.newsSignalPulsePending = false;
11564
- this.newsSignalPulsing = true;
11565
- if (this.announcementTimestamp) saveAnnouncementPulsedTimestamp(this.announcementTimestamp);
13542
+ this.stopSignalPulse();
13543
+ this.pendingPulseSignal = null;
13544
+ this.pulsingSignal = key;
13545
+ if (isWiringErrorKey(key)) this.errorBeatSpent = true;
13546
+ else if (this.announcementTimestamp && key === NEWS_SIGNAL_ID) saveAnnouncementPulsedTimestamp(this.announcementTimestamp);
13547
+ this.beginGestureTail(key);
11566
13548
  this.requestUpdate();
11567
13549
  if (typeof window === "undefined") return;
11568
13550
  this.pulseTimeoutId = setTimeout(() => {
11569
13551
  this.pulseTimeoutId = null;
11570
- this.newsSignalPulsing = false;
13552
+ this.pulsingSignal = null;
11571
13553
  this.requestUpdate();
11572
- }, NEWS_SIGNAL_CADENCE_MS);
13554
+ if (this.gestureSignal === key && this.pillPhase === "closed") {
13555
+ this.openPill();
13556
+ return;
13557
+ }
13558
+ if (this.gestureSignal === key) {
13559
+ this.endGesture();
13560
+ return;
13561
+ }
13562
+ this.flushPendingSignalPulse();
13563
+ }, LAUNCHER_SIGNALS[key].cadence);
11573
13564
  }
11574
- stopNewsSignalPulse() {
13565
+ stopSignalPulse() {
11575
13566
  if (this.pulseTimeoutId !== null) {
11576
13567
  clearTimeout(this.pulseTimeoutId);
11577
13568
  this.pulseTimeoutId = null;
11578
13569
  }
11579
- this.newsSignalPulsing = false;
13570
+ this.pulsingSignal = null;
13571
+ }
13572
+ /**
13573
+ * Runs a deferred beat if it can land now, and drops it if its reason to
13574
+ * exist has gone: a nudge about a problem that no longer exists is worse
13575
+ * than no nudge at all.
13576
+ */
13577
+ flushPendingSignalPulse() {
13578
+ const key = this.pendingPulseSignal;
13579
+ if (key === null) return;
13580
+ if (!this.isSignalArmed(key)) {
13581
+ this.pendingPulseSignal = null;
13582
+ this.requestUpdate();
13583
+ return;
13584
+ }
13585
+ this.startSignalPulse(key);
13586
+ }
13587
+ /**
13588
+ * Drops a signal's gesture, pending or running, when the signal goes quiet.
13589
+ *
13590
+ * The pill closes early: it states a condition, and the condition has
13591
+ * stopped being true. The beat is left to finish, because a beat asserts
13592
+ * nothing — it says *here*, and "here" is still true.
13593
+ */
13594
+ retireSignal(key) {
13595
+ if (this.pendingPulseSignal === key) this.pendingPulseSignal = null;
13596
+ if (this.gestureSignal === key) this.closePillEarly();
13597
+ this.flushPendingSignalPulse();
13598
+ }
13599
+ /**
13600
+ * The signal holding the single gesture slot: a beat in flight, or the pill
13601
+ * and spoken sentence that follow it. One slot, not two — see reason 3 in
13602
+ * `startSignalPulse`.
13603
+ */
13604
+ get gestureSlotSignal() {
13605
+ return this.pulsingSignal ?? this.gestureSignal;
13606
+ }
13607
+ /**
13608
+ * Opens the gesture's tail alongside the beat, for a signal that carries a
13609
+ * pill. The pill is rendered immediately — clipped to nothing, so it shows
13610
+ * nothing — because its full width has to be on the page before the room
13611
+ * either side of the launcher can be measured.
13612
+ *
13613
+ * A signal with no pill label gets no tail at all, so the announcement's
13614
+ * gesture is exactly the beat it has always been.
13615
+ */
13616
+ beginGestureTail(key) {
13617
+ this.cancelPillTimeout();
13618
+ if (LAUNCHER_SIGNALS[key].pillLabel === void 0) {
13619
+ this.gestureSignal = null;
13620
+ this.pillPhase = null;
13621
+ this.pillDirection = null;
13622
+ return;
13623
+ }
13624
+ this.gestureSignal = key;
13625
+ this.pillPhase = "closed";
13626
+ this.pillDirection = null;
13627
+ this.closeLauncherHud();
13628
+ }
13629
+ /**
13630
+ * Chooses the side, or suppresses the pill, from the room actually available
13631
+ * at gesture start. Measured once, from the DOM, because the launcher is
13632
+ * draggable and its position persists: a reader who parked it near the left
13633
+ * edge would otherwise get a permanently truncated pill.
13634
+ *
13635
+ * Where neither side has room there is no pill at all rather than a cut-off
13636
+ * one — the dot and the beat still fire, so the signal is intact and only
13637
+ * the label is lost. Constraining where the reader may drag the control to
13638
+ * protect this animation was considered and rejected: the page is theirs.
13639
+ */
13640
+ resolvePillDirection() {
13641
+ if (this.pillDirection !== null || this.pillPhase === null) return;
13642
+ const wrapper = this.activeRoot.querySelector(".console-button-wrapper");
13643
+ const button = wrapper?.querySelector(".console-button");
13644
+ const pill = wrapper?.querySelector(".cpk-launcher-pill");
13645
+ if (!button || !pill || typeof window === "undefined") return;
13646
+ const mark = button.getBoundingClientRect();
13647
+ const overhang = Math.max(0, pill.getBoundingClientRect().width - mark.width);
13648
+ const viewportWidth = window.innerWidth;
13649
+ if (overhang === 0) {
13650
+ this.setPillOutcome("left");
13651
+ return;
13652
+ }
13653
+ if (mark.left - overhang >= EDGE_MARGIN) {
13654
+ this.setPillOutcome("left");
13655
+ return;
13656
+ }
13657
+ if (mark.right + overhang <= viewportWidth - EDGE_MARGIN) {
13658
+ this.setPillOutcome("right");
13659
+ return;
13660
+ }
13661
+ this.setPillOutcome(null);
13662
+ }
13663
+ /** Records the measurement's verdict, and drops the pill when it is null. */
13664
+ setPillOutcome(direction) {
13665
+ if (direction === null) {
13666
+ this.pillPhase = null;
13667
+ this.pillDirection = null;
13668
+ this.pillOutcome = "suppressed";
13669
+ this.requestUpdate();
13670
+ return;
13671
+ }
13672
+ this.pillDirection = direction;
13673
+ this.pillOutcome = "shown";
13674
+ this.requestUpdate();
13675
+ }
13676
+ /** Runs the pill's three phases in series once the beat has finished. */
13677
+ openPill() {
13678
+ this.resolvePillDirection();
13679
+ if (this.pillPhase === null) {
13680
+ this.endGesture();
13681
+ return;
13682
+ }
13683
+ this.advancePill("opening", ERROR_GESTURE_MS.open, () => {
13684
+ this.advancePill("holding", ERROR_GESTURE_MS.hold, () => {
13685
+ this.advancePill("closing", ERROR_GESTURE_MS.close, () => {
13686
+ this.endGesture();
13687
+ });
13688
+ });
13689
+ });
13690
+ }
13691
+ advancePill(phase, duration, next) {
13692
+ this.cancelPillTimeout();
13693
+ this.pillPhase = phase;
13694
+ this.requestUpdate();
13695
+ if (typeof window === "undefined") return;
13696
+ this.pillTimeoutId = setTimeout(() => {
13697
+ this.pillTimeoutId = null;
13698
+ next();
13699
+ }, duration);
13700
+ }
13701
+ /**
13702
+ * Closes the pill before its hold is out, because the failure it names has
13703
+ * been fixed. A pill that has not opened yet is simply dropped; the beat is
13704
+ * never cut short.
13705
+ */
13706
+ closePillEarly() {
13707
+ if (this.pillPhase === null) {
13708
+ this.endGesture();
13709
+ return;
13710
+ }
13711
+ if (this.pillPhase === "closing") return;
13712
+ if (this.pillPhase === "closed") {
13713
+ this.pillPhase = null;
13714
+ this.requestUpdate();
13715
+ return;
13716
+ }
13717
+ this.advancePill("closing", ERROR_GESTURE_MS.close, () => {
13718
+ this.endGesture();
13719
+ });
13720
+ }
13721
+ /** Releases the slot and leaves the plain mark with its dot behind. */
13722
+ endGesture() {
13723
+ this.cancelGestureTail();
13724
+ this.requestUpdate();
13725
+ this.flushPendingSignalPulse();
13726
+ }
13727
+ /**
13728
+ * Drops the gesture's tail outright, with no closing animation, for the
13729
+ * cases where the launcher itself has gone: the panel opened over it, or the
13730
+ * element was removed from the page.
13731
+ */
13732
+ cancelGestureTail() {
13733
+ this.cancelPillTimeout();
13734
+ this.gestureSignal = null;
13735
+ this.pillPhase = null;
13736
+ this.pillDirection = null;
13737
+ }
13738
+ cancelPillTimeout() {
13739
+ if (this.pillTimeoutId !== null) {
13740
+ clearTimeout(this.pillTimeoutId);
13741
+ this.pillTimeoutId = null;
13742
+ }
13743
+ }
13744
+ /**
13745
+ * Whether each error source is currently broken.
13746
+ *
13747
+ * Only two *wiring* conditions qualify. App errors (runs, tools, memory)
13748
+ * are unread events on a different latch — they name themselves on the pill
13749
+ * and clear when their landing view is read. Notably absent from *this*
13750
+ * latch:
13751
+ *
13752
+ * - **A failed agent run.** A run is an event. It arms `run`, not this
13753
+ * state. The resting wiring dot must not stay red for the rest of a debug
13754
+ * hour.
13755
+ * - **The core error channel as a wiring source.** Handshake failure already
13756
+ * sets the connection state. Other codes arm `run` or `tool`.
13757
+ * - **Memory failures before Learning is live.** The memory store is lazy
13758
+ * because creating it opens a realtime connection. Once it exists, a load
13759
+ * failure arms `memory`.
13760
+ * - **Product states.** An unconfigured Intelligence and an unentitled
13761
+ * Memory plan are not defects. The signal fires only where wiring is
13762
+ * present and the call still fails — which for threads is guaranteed by
13763
+ * `_threadsErrorByAgent` only ever being written while the thread
13764
+ * endpoints are available.
13765
+ *
13766
+ * Known limitation: the runtime handshake runs once, on connect. A server
13767
+ * that dies *after* the page loaded leaves the connection state at connected
13768
+ * and raises nothing; the next page load re-runs the handshake and the
13769
+ * signal appears then. The signal reports the wiring state as last
13770
+ * established. Closing that gap means a re-probe in the core, which is a
13771
+ * runtime concern.
13772
+ */
13773
+ isErrorSourceBroken(source) {
13774
+ if (source === "connection") {
13775
+ const state = this.getCoreStatusSummary().state;
13776
+ if (runtimeConnectionNeedsAttention(state)) return true;
13777
+ return this.errorSignalArmed.connection && state === "connecting";
13778
+ }
13779
+ return this._threadsErrorByAgent.size > 0;
13780
+ }
13781
+ /**
13782
+ * Mirrors both error latches onto the live state. Called from `willUpdate`,
13783
+ * because every mutation of the underlying state already requests an update,
13784
+ * so the resting dot follows the state within the same render.
13785
+ */
13786
+ evaluateErrorSignals() {
13787
+ const wasArmed = this.hasArmedErrorSignal();
13788
+ for (const source of WIRING_ERROR_KEYS) {
13789
+ if (this.isErrorSourceBroken(source)) {
13790
+ if (this.errorSignalArmed[source]) continue;
13791
+ this.errorSignalArmed[source] = true;
13792
+ continue;
13793
+ }
13794
+ if (!this.errorSignalArmed[source]) continue;
13795
+ this.errorSignalArmed[source] = false;
13796
+ this.errorSignalViewedSources.delete(source);
13797
+ this.retireSignal(source);
13798
+ }
13799
+ this.onErrorSignalsChanged(wasArmed);
13800
+ }
13801
+ /**
13802
+ * Applies the rising-edge rule after a latch changed.
13803
+ *
13804
+ * The beat fires on the transition from "no failure" to "at least one
13805
+ * failure", evaluated globally across the sources and never again while
13806
+ * anything is red — one root cause, one nudge.
13807
+ */
13808
+ onErrorSignalsChanged(wasArmed) {
13809
+ if (!this.hasArmedErrorSignal()) {
13810
+ this.errorBeatSpent = false;
13811
+ this.pillOutcome = null;
13812
+ return;
13813
+ }
13814
+ if (wasArmed || this.errorBeatSpent) return;
13815
+ const active = this.getActiveLauncherSignal();
13816
+ if (active !== null && isErrorSignalKey(active)) this.startSignalPulse(active);
13817
+ }
13818
+ /**
13819
+ * Records `oss.inspector.error_signal_viewed` once per source per outage,
13820
+ * when the dot is actually on screen.
13821
+ *
13822
+ * Unlike the announcement's launcher event this fires immediately rather
13823
+ * than waiting for the runtime handshake to report `telemetryDisabled`.
13824
+ * The held-queue would never drain for the connection source — a connection
13825
+ * failure means the handshake did not complete — so queuing would guarantee
13826
+ * zero data for the case this event exists to measure. `trackOpened` already
13827
+ * sends on the same terms. Both opt-out layers still gate it: the local
13828
+ * opt-out inside `track`, and the runtime's flag once it is known.
13829
+ */
13830
+ maybeTrackErrorSignalViewed() {
13831
+ if (this.isOpen || typeof document === "undefined" || document.visibilityState !== "visible") return;
13832
+ const active = this.getActiveLauncherSignal();
13833
+ if (active === null || !isErrorSignalKey(active)) return;
13834
+ if (this.errorSignalViewedSources.has(active)) return;
13835
+ if (this.pillOutcome === null && LAUNCHER_SIGNALS[active].pillLabel !== void 0) return;
13836
+ if (this.core?.telemetryDisabled) return;
13837
+ this.errorSignalViewedSources.add(active);
13838
+ trackErrorSignalViewed({
13839
+ source: active,
13840
+ presentation: this.isReducedMotionPreferred() ? "reduced_motion" : "animated",
13841
+ label: this.pillOutcome ?? "suppressed"
13842
+ });
11580
13843
  }
11581
13844
  maybeTrackNewsSignalViewed() {
11582
- if (!this.newsSignalArmed || !this.newsSignalPulsing || this.isOpen || typeof document === "undefined" || document.visibilityState !== "visible") return;
13845
+ if (!this.newsSignalArmed || this.pulsingSignal !== NEWS_SIGNAL_ID || this.isOpen || typeof document === "undefined" || document.visibilityState !== "visible") return;
11583
13846
  const id = this.announcementTimestamp;
11584
13847
  if (!id || this.viewedNewsSignalIds.has(id)) return;
11585
13848
  this.viewedNewsSignalIds.add(id);
@@ -11762,5 +14025,5 @@ function defineWebInspector(registry = globalThis.customElements) {
11762
14025
  defineWebInspector();
11763
14026
 
11764
14027
  //#endregion
11765
- export { CpkThreadInspector, THREAD_INSPECTOR_TAG, WEB_INSPECTOR_TAG, WebInspectorElement, defineWebInspector, ɵCpkThreadDetails, buildCapabilityRows as ɵbuildCapabilityRows, maxRecallScore as ɵmaxRecallScore, normalizeRelevance as ɵnormalizeRelevance, relevanceBarWidth as ɵrelevanceBarWidth };
14028
+ export { CpkThreadInspector, THREAD_INSPECTOR_TAG, WEB_INSPECTOR_TAG, WebInspectorElement, compileChatSnippet, compileFromActivityMessage, defineWebInspector, upsertEventSnippet, ɵCpkThreadDetails, buildCapabilityRows as ɵbuildCapabilityRows, maxRecallScore as ɵmaxRecallScore, normalizeRelevance as ɵnormalizeRelevance, relevanceBarWidth as ɵrelevanceBarWidth };
11766
14029
  //# sourceMappingURL=index.mjs.map