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