@copilotkit/react-core 1.59.3 → 1.59.5-canary.1781104893

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.
@@ -220,9 +220,9 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
220
220
 
221
221
  //#endregion
222
222
  //#region src/v2/lib/utils.ts
223
- const twMerge$7 = (0, tailwind_merge.extendTailwindMerge)({ prefix: "cpk" });
223
+ const twMerge$8 = (0, tailwind_merge.extendTailwindMerge)({ prefix: "cpk" });
224
224
  function cn(...inputs) {
225
- return twMerge$7((0, clsx.clsx)(inputs));
225
+ return twMerge$8((0, clsx.clsx)(inputs));
226
226
  }
227
227
 
228
228
  //#endregion
@@ -2011,7 +2011,7 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
2011
2011
  severity: "warning",
2012
2012
  message: `Your CopilotKit license expires in ${graceRemaining} day${graceRemaining !== 1 ? "s" : ""}. Please renew.`,
2013
2013
  actionLabel: "Renew",
2014
- actionUrl: "https://cloud.copilotkit.ai",
2014
+ actionUrl: "https://dashboard.operations.copilotkit.ai",
2015
2015
  onDismiss
2016
2016
  });
2017
2017
  case "expired": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BannerShell, {
@@ -2940,210 +2940,110 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
2940
2940
  };
2941
2941
 
2942
2942
  //#endregion
2943
- //#region src/v2/a2ui/A2UIMessageRenderer.tsx
2943
+ //#region src/v2/a2ui/A2UIRecoveryStates.tsx
2944
2944
  /**
2945
- * The container key used to wrap A2UI operations for explicit detection.
2946
- * Must match A2UI_OPERATIONS_KEY in @ag-ui/a2ui-middleware and copilotkit.a2ui (Python).
2945
+ * The pre-paint lifecycle fields the middleware stamps onto the `a2ui-surface`
2946
+ * activity content (alongside `a2ui_operations` on paint). `.passthrough()` keeps
2947
+ * `a2ui_operations` and any future fields intact.
2947
2948
  */
2948
- const A2UI_OPERATIONS_KEY = "a2ui_operations";
2949
- let initialized = false;
2950
- function ensureInitialized() {
2951
- if (!initialized) {
2952
- (0, _copilotkit_a2ui_renderer.initializeDefaultCatalog)();
2953
- (0, _copilotkit_a2ui_renderer.injectStyles)();
2954
- initialized = true;
2955
- }
2956
- }
2957
- function createA2UIMessageRenderer(options) {
2958
- const { theme, catalog, loadingComponent } = options;
2959
- return {
2960
- activityType: "a2ui-surface",
2961
- content: zod.z.any(),
2962
- render: ({ content, agent }) => {
2963
- ensureInitialized();
2964
- const [operations, setOperations] = (0, react.useState)([]);
2965
- const { copilotkit } = useCopilotKit();
2966
- const lastContentRef = (0, react.useRef)(null);
2967
- (0, react.useEffect)(() => {
2968
- if (content === lastContentRef.current) return;
2969
- lastContentRef.current = content;
2970
- const incoming = content?.[A2UI_OPERATIONS_KEY];
2971
- if (!content || !Array.isArray(incoming)) {
2972
- setOperations([]);
2973
- return;
2974
- }
2975
- setOperations(incoming);
2976
- }, [content]);
2977
- const groupedOperations = (0, react.useMemo)(() => {
2978
- const groups = /* @__PURE__ */ new Map();
2979
- for (const operation of operations) {
2980
- const surfaceId = getOperationSurfaceId(operation) ?? _copilotkit_a2ui_renderer.DEFAULT_SURFACE_ID;
2981
- if (!groups.has(surfaceId)) groups.set(surfaceId, []);
2982
- groups.get(surfaceId).push(operation);
2983
- }
2984
- return groups;
2985
- }, [operations]);
2986
- if (!groupedOperations.size) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(loadingComponent ?? DefaultA2UILoading, {});
2987
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2988
- className: "cpk:flex cpk:min-h-0 cpk:flex-1 cpk:flex-col cpk:gap-6 cpk:overflow-auto cpk:py-6",
2989
- children: Array.from(groupedOperations.entries()).map(([surfaceId, ops]) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReactSurfaceHost, {
2990
- surfaceId,
2991
- operations: ops,
2992
- theme,
2993
- agent,
2994
- copilotkit,
2995
- catalog
2996
- }, surfaceId))
2997
- });
2998
- }
2999
- };
3000
- }
3001
- /**
3002
- * Renders a single A2UI surface using the React renderer.
3003
- * Wraps A2UIProvider + A2UIRenderer and bridges actions back to CopilotKit.
3004
- */
3005
- function ReactSurfaceHost({ surfaceId, operations, theme, agent, copilotkit, catalog }) {
3006
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3007
- className: "cpk:flex cpk:w-full cpk:flex-none cpk:flex-col cpk:gap-4",
3008
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_copilotkit_a2ui_renderer.A2UIProvider, {
3009
- onAction: (0, react.useCallback)(async (message) => {
3010
- if (!agent) return;
3011
- message.userAction;
3012
- try {
3013
- copilotkit.setProperties({
3014
- ...copilotkit.properties,
3015
- a2uiAction: message
3016
- });
3017
- await copilotkit.runAgent({ agent });
3018
- } finally {
3019
- if (copilotkit.properties) {
3020
- const { a2uiAction, ...rest } = copilotkit.properties;
3021
- copilotkit.setProperties(rest);
3022
- }
3023
- }
3024
- }, [agent, copilotkit]),
3025
- theme,
3026
- catalog,
3027
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SurfaceMessageProcessor, {
3028
- surfaceId,
3029
- operations
3030
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(A2UISurfaceOrError, { surfaceId })]
3031
- })
3032
- });
2949
+ const A2UILifecycleFields = {
2950
+ status: zod.z.enum([
2951
+ "building",
2952
+ "retrying",
2953
+ "failed"
2954
+ ]).optional(),
2955
+ attempt: zod.z.number().optional(),
2956
+ maxAttempts: zod.z.number().optional(),
2957
+ progressTokens: zod.z.number().optional(),
2958
+ error: zod.z.string().optional(),
2959
+ errors: zod.z.array(zod.z.any()).optional(),
2960
+ attempts: zod.z.array(zod.z.any()).optional(),
2961
+ debugExposure: zod.z.enum([
2962
+ "hidden",
2963
+ "collapsed",
2964
+ "verbose"
2965
+ ]).optional()
2966
+ };
2967
+ /** Server-stamped debugExposure wins; else the client option; else "collapsed". */
2968
+ function resolveDebugExposure(content, optionDebugExposure) {
2969
+ return content?.debugExposure ?? optionDebugExposure;
3033
2970
  }
3034
- /**
3035
- * Renders the A2UI surface, or an error message if processing failed.
3036
- * Must be a child of A2UIProvider to access the error state.
3037
- */
3038
- function A2UISurfaceOrError({ surfaceId }) {
3039
- const error = (0, _copilotkit_a2ui_renderer.useA2UIError)();
3040
- if (error) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3041
- className: "cpk:rounded-lg cpk:border cpk:border-red-200 cpk:bg-red-50 cpk:p-3 cpk:text-sm cpk:text-red-700",
3042
- children: ["A2UI render error: ", error]
3043
- });
3044
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_copilotkit_a2ui_renderer.A2UIRenderer, {
3045
- surfaceId,
3046
- className: "cpk:flex cpk:flex-1"
2971
+ /** building: the generic skeleton + optional live token count. */
2972
+ function A2UIBuildingState({ content }) {
2973
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(A2UIGeneratingSkeleton, {
2974
+ label: "Building interface",
2975
+ tokens: typeof content?.progressTokens === "number" ? content.progressTokens : void 0
3047
2976
  });
3048
2977
  }
3049
2978
  /**
3050
- * Processes A2UI operations into the provider's message processor.
3051
- * Must be a child of A2UIProvider to access the actions context.
2979
+ * retrying: stays the generic skeleton through fast/transient retries; only once
2980
+ * the retry is perceptible (after `showAfterMs`, or once `attempt` crosses
2981
+ * `showAfterAttempts`) does the sub-label reveal "Retrying generation… (N/M)".
3052
2982
  */
3053
- function SurfaceMessageProcessor({ surfaceId, operations }) {
3054
- const { processMessages, getSurface } = (0, _copilotkit_a2ui_renderer.useA2UIActions)();
3055
- const lastHashRef = (0, react.useRef)("");
2983
+ function A2UIRetryingState({ content, showAfterMs, showAfterAttempts, debugExposure }) {
2984
+ const attempt = typeof content?.attempt === "number" ? content.attempt : void 0;
2985
+ const maxAttempts = typeof content?.maxAttempts === "number" ? content.maxAttempts : void 0;
2986
+ const immediate = attempt !== void 0 && attempt >= showAfterAttempts;
2987
+ const [revealed, setRevealed] = (0, react.useState)(immediate);
3056
2988
  (0, react.useEffect)(() => {
3057
- const hash = JSON.stringify(operations);
3058
- if (hash === lastHashRef.current) return;
3059
- lastHashRef.current = hash;
3060
- processMessages(getSurface(surfaceId) ? operations.filter((op) => !op?.createSurface) : operations);
3061
- }, [
3062
- processMessages,
3063
- getSurface,
3064
- surfaceId,
3065
- operations
3066
- ]);
3067
- return null;
2989
+ if (immediate) {
2990
+ setRevealed(true);
2991
+ return;
2992
+ }
2993
+ const timer = setTimeout(() => setRevealed(true), showAfterMs);
2994
+ return () => clearTimeout(timer);
2995
+ }, [immediate, showAfterMs]);
2996
+ const tokens = typeof content?.progressTokens === "number" ? content.progressTokens : void 0;
2997
+ if (!revealed) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(A2UIGeneratingSkeleton, {
2998
+ label: "Building interface",
2999
+ tokens
3000
+ });
3001
+ const label = attempt !== void 0 && maxAttempts !== void 0 ? `Retrying generation… (${attempt}/${maxAttempts} attempts)` : "Retrying generation…";
3002
+ const errors = Array.isArray(content?.errors) ? content.errors : [];
3003
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(A2UIGeneratingSkeleton, {
3004
+ label,
3005
+ tokens,
3006
+ children: debugExposure !== "hidden" && errors.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(A2UIDebugDetails, {
3007
+ label: "validation issues",
3008
+ open: debugExposure === "verbose",
3009
+ payload: {
3010
+ attempt: content?.attempt,
3011
+ errors
3012
+ }
3013
+ })
3014
+ });
3068
3015
  }
3069
- /**
3070
- * Default loading component shown while an A2UI surface is generating.
3071
- * Displays an animated shimmer skeleton.
3072
- */
3073
- function DefaultA2UILoading() {
3016
+ /** failed: a clean hard-failure card that replaces the skeleton in place. */
3017
+ function A2UIRecoveryFailure({ content, debugExposure }) {
3074
3018
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3075
- className: "cpk:flex cpk:flex-col cpk:gap-3 cpk:rounded-xl cpk:border cpk:border-gray-100 cpk:bg-gray-50/50 cpk:p-5",
3076
- style: { minHeight: 120 },
3019
+ className: "cpk:rounded-lg cpk:border cpk:border-amber-200 cpk:bg-amber-50 cpk:p-3 cpk:text-sm cpk:text-amber-800",
3077
3020
  children: [
3078
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3079
- className: "cpk:flex cpk:items-center cpk:gap-2",
3080
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3081
- className: "cpk:h-3 cpk:w-3 cpk:rounded-full cpk:bg-gray-200",
3082
- style: { animation: "cpk-a2ui-pulse 1.5s ease-in-out infinite" }
3083
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3084
- className: "cpk:text-xs cpk:font-medium cpk:text-gray-400",
3085
- children: "Generating UI..."
3086
- })]
3021
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3022
+ className: "cpk:font-medium",
3023
+ children: "Couldn't generate the UI"
3087
3024
  }),
3088
3025
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3089
- className: "cpk:flex cpk:flex-col cpk:gap-2",
3090
- children: [
3091
- .8,
3092
- .6,
3093
- .4
3094
- ].map((width, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3095
- className: "cpk:h-3 cpk:rounded cpk:bg-gray-200/70",
3096
- style: {
3097
- width: `${width * 100}%`,
3098
- animation: `cpk-a2ui-pulse 1.5s ease-in-out ${i * .15}s infinite`
3099
- }
3100
- }, i))
3026
+ className: "cpk:mt-1 cpk:text-xs cpk:text-amber-700",
3027
+ children: "Something went wrong rendering this. You can keep chatting and try again."
3101
3028
  }),
3102
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: `
3103
- @keyframes cpk-a2ui-pulse {
3104
- 0%, 100% { opacity: 0.4; }
3105
- 50% { opacity: 1; }
3106
- }
3107
- ` })
3029
+ debugExposure !== "hidden" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(A2UIDebugDetails, {
3030
+ label: "developer details",
3031
+ open: debugExposure === "verbose",
3032
+ payload: {
3033
+ error: content?.error,
3034
+ attempts: content?.attempts
3035
+ }
3036
+ })
3108
3037
  ]
3109
3038
  });
3110
3039
  }
3111
- function getOperationSurfaceId(operation) {
3112
- if (!operation || typeof operation !== "object") return null;
3113
- if (typeof operation.surfaceId === "string") return operation.surfaceId;
3114
- return operation?.createSurface?.surfaceId ?? operation?.updateComponents?.surfaceId ?? operation?.updateDataModel?.surfaceId ?? operation?.deleteSurface?.surfaceId ?? null;
3115
- }
3116
-
3117
- //#endregion
3118
- //#region src/v2/a2ui/A2UIToolCallRenderer.tsx
3119
- /**
3120
- * Tool name used by the dynamic A2UI generation secondary LLM.
3121
- * This renderer is auto-registered when A2UI is enabled.
3122
- */
3123
- const RENDER_A2UI_TOOL_NAME = "render_a2ui";
3124
3040
  /**
3125
- * Built-in progress indicator for dynamic A2UI generation.
3126
- * Shows a skeleton wireframe that progressively reveals as tokens stream in.
3127
- *
3128
- * Registered automatically when A2UI is enabled. Users can override by
3129
- * providing their own `useRenderTool({ name: "render_a2ui", ... })`.
3041
+ * Animated wireframe skeleton with a label, an optional live token count, and an
3042
+ * optional debug-detail slot below it. Pure CSS animation (no data dependency).
3043
+ * The `tokens` count drives a progressive reveal of skeleton rows.
3130
3044
  */
3131
- function A2UIProgressIndicator({ parameters }) {
3132
- const lastRef = (0, react.useRef)({
3133
- time: 0,
3134
- tokens: 0
3135
- });
3136
- const now = Date.now();
3137
- let { tokens } = lastRef.current;
3138
- if (now - lastRef.current.time > 200) {
3139
- const chars = JSON.stringify(parameters ?? {}).length;
3140
- tokens = Math.round(chars / 4);
3141
- lastRef.current = {
3142
- time: now,
3143
- tokens
3144
- };
3145
- }
3146
- const phase = tokens < 50 ? 0 : tokens < 200 ? 1 : tokens < 400 ? 2 : 3;
3045
+ function A2UIGeneratingSkeleton({ label, tokens, children }) {
3046
+ const phase = tokens == null ? 3 : tokens < 50 ? 0 : tokens < 200 ? 1 : tokens < 400 ? 2 : 3;
3147
3047
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3148
3048
  style: {
3149
3049
  margin: "12px 0",
@@ -3351,8 +3251,8 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
3351
3251
  color: "#a1a1aa",
3352
3252
  letterSpacing: "0.025em"
3353
3253
  },
3354
- children: "Building interface"
3355
- }), tokens > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3254
+ children: label
3255
+ }), typeof tokens === "number" && tokens > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3356
3256
  style: {
3357
3257
  fontSize: 11,
3358
3258
  color: "#d4d4d8",
@@ -3365,6 +3265,7 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
3365
3265
  ]
3366
3266
  })]
3367
3267
  }),
3268
+ children,
3368
3269
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: `
3369
3270
  @keyframes cpk-a2ui-fade {
3370
3271
  0%, 100% { opacity: 1; }
@@ -3378,6 +3279,20 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
3378
3279
  ]
3379
3280
  });
3380
3281
  }
3282
+ function A2UIDebugDetails({ label, open, payload }) {
3283
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
3284
+ open,
3285
+ className: "cpk:mt-2 cpk:text-xs",
3286
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", {
3287
+ className: "cpk:cursor-pointer cpk:text-gray-500",
3288
+ children: label
3289
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
3290
+ className: "cpk:mt-1 cpk:overflow-auto cpk:rounded cpk:bg-gray-100 cpk:p-2 cpk:text-gray-700",
3291
+ style: { fontSize: 11 },
3292
+ children: JSON.stringify(payload, null, 2)
3293
+ })]
3294
+ });
3295
+ }
3381
3296
  function Dot() {
3382
3297
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: {
3383
3298
  width: 7,
@@ -3413,13 +3328,242 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
3413
3328
  children
3414
3329
  });
3415
3330
  }
3331
+
3332
+ //#endregion
3333
+ //#region src/v2/a2ui/A2UIMessageRenderer.tsx
3334
+ /**
3335
+ * The container key used to wrap A2UI operations for explicit detection.
3336
+ * Must match A2UI_OPERATIONS_KEY in @ag-ui/a2ui-middleware and copilotkit.a2ui (Python).
3337
+ */
3338
+ const A2UI_OPERATIONS_KEY = "a2ui_operations";
3339
+ let initialized = false;
3340
+ function ensureInitialized() {
3341
+ if (!initialized) {
3342
+ (0, _copilotkit_a2ui_renderer.initializeDefaultCatalog)();
3343
+ (0, _copilotkit_a2ui_renderer.injectStyles)();
3344
+ initialized = true;
3345
+ }
3346
+ }
3347
+ /**
3348
+ * The `a2ui-surface` activity carries the WHOLE generative-UI lifecycle on one
3349
+ * stable messageId (OSS-162): pre-paint `status` ("building" | "retrying" |
3350
+ * "failed") with recovery detail, then `a2ui_operations` on paint. The states
3351
+ * swap in place, so the painted surface replaces the skeleton with no extra
3352
+ * coordination. `.passthrough()` preserves operations + any future fields.
3353
+ */
3354
+ const A2UISurfaceContentSchema = zod.z.object({
3355
+ a2ui_operations: zod.z.array(zod.z.any()).optional(),
3356
+ ...A2UILifecycleFields
3357
+ }).passthrough();
3358
+ function createA2UIMessageRenderer(options) {
3359
+ const { theme, catalog, loadingComponent, recovery } = options;
3360
+ const showAfterMs = recovery?.showAfterMs ?? 2e3;
3361
+ const showAfterAttempts = recovery?.showAfterAttempts ?? 2;
3362
+ const optionDebugExposure = recovery?.debugExposure ?? "collapsed";
3363
+ return {
3364
+ activityType: "a2ui-surface",
3365
+ content: A2UISurfaceContentSchema,
3366
+ render: ({ content, agent }) => {
3367
+ ensureInitialized();
3368
+ const [operations, setOperations] = (0, react.useState)([]);
3369
+ const { copilotkit } = useCopilotKit();
3370
+ const lastContentRef = (0, react.useRef)(null);
3371
+ (0, react.useEffect)(() => {
3372
+ if (content === lastContentRef.current) return;
3373
+ lastContentRef.current = content;
3374
+ const incoming = content?.[A2UI_OPERATIONS_KEY];
3375
+ if (!content || !Array.isArray(incoming)) {
3376
+ setOperations([]);
3377
+ return;
3378
+ }
3379
+ setOperations(incoming);
3380
+ }, [content]);
3381
+ const groupedOperations = (0, react.useMemo)(() => {
3382
+ const groups = /* @__PURE__ */ new Map();
3383
+ for (const operation of operations) {
3384
+ const surfaceId = getOperationSurfaceId(operation) ?? _copilotkit_a2ui_renderer.DEFAULT_SURFACE_ID;
3385
+ if (!groups.has(surfaceId)) groups.set(surfaceId, []);
3386
+ groups.get(surfaceId).push(operation);
3387
+ }
3388
+ return groups;
3389
+ }, [operations]);
3390
+ const hasOps = groupedOperations.size > 0;
3391
+ const renderLifecycle = (c) => {
3392
+ const status = c?.status;
3393
+ const debugExposure = resolveDebugExposure(c, optionDebugExposure);
3394
+ if (status === "failed") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(A2UIRecoveryFailure, {
3395
+ content: c,
3396
+ debugExposure
3397
+ });
3398
+ if (status === "retrying") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(A2UIRetryingState, {
3399
+ content: c,
3400
+ showAfterMs,
3401
+ showAfterAttempts,
3402
+ debugExposure
3403
+ });
3404
+ if (loadingComponent) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(loadingComponent, {});
3405
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(A2UIBuildingState, { content: c });
3406
+ };
3407
+ const lastLoaderContentRef = (0, react.useRef)(null);
3408
+ if (!(Array.isArray(content?.[A2UI_OPERATIONS_KEY]) && content[A2UI_OPERATIONS_KEY].length > 0)) lastLoaderContentRef.current = content;
3409
+ const [surfaceReady, setSurfaceReady] = (0, react.useState)(false);
3410
+ const readyRef = (0, react.useRef)(false);
3411
+ const markSurfaceReady = (0, react.useCallback)(() => {
3412
+ if (readyRef.current) return;
3413
+ readyRef.current = true;
3414
+ requestAnimationFrame(() => setSurfaceReady(true));
3415
+ }, []);
3416
+ (0, react.useEffect)(() => {
3417
+ if (!hasOps) {
3418
+ setSurfaceReady(false);
3419
+ readyRef.current = false;
3420
+ return;
3421
+ }
3422
+ const t = setTimeout(() => setSurfaceReady(true), 8e3);
3423
+ return () => clearTimeout(t);
3424
+ }, [hasOps]);
3425
+ if (!hasOps) return renderLifecycle(content);
3426
+ const surfaces = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3427
+ className: "cpk:flex cpk:min-h-0 cpk:flex-1 cpk:flex-col cpk:gap-6 cpk:overflow-auto cpk:py-6",
3428
+ children: Array.from(groupedOperations.entries()).map(([surfaceId, ops]) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReactSurfaceHost, {
3429
+ surfaceId,
3430
+ operations: ops,
3431
+ theme,
3432
+ agent,
3433
+ copilotkit,
3434
+ catalog,
3435
+ onReady: markSurfaceReady
3436
+ }, surfaceId))
3437
+ });
3438
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3439
+ style: { position: "relative" },
3440
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3441
+ "aria-hidden": !surfaceReady,
3442
+ style: surfaceReady ? void 0 : {
3443
+ position: "absolute",
3444
+ inset: 0,
3445
+ opacity: 0,
3446
+ pointerEvents: "none"
3447
+ },
3448
+ children: surfaces
3449
+ }), !surfaceReady && renderLifecycle(lastLoaderContentRef.current ?? content)]
3450
+ });
3451
+ }
3452
+ };
3453
+ }
3416
3454
  /**
3417
- * Registers the built-in `render_a2ui` tool call renderer via the props-based
3418
- * `setRenderToolCalls` mechanism (not `useRenderTool`).
3455
+ * Renders a single A2UI surface using the React renderer.
3456
+ * Wraps A2UIProvider + A2UIRenderer and bridges actions back to CopilotKit.
3457
+ */
3458
+ function ReactSurfaceHost({ surfaceId, operations, theme, agent, copilotkit, catalog, onReady }) {
3459
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3460
+ className: "cpk:flex cpk:w-full cpk:flex-none cpk:flex-col cpk:gap-4",
3461
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_copilotkit_a2ui_renderer.A2UIProvider, {
3462
+ onAction: (0, react.useCallback)(async (message) => {
3463
+ if (!agent) return;
3464
+ message.userAction;
3465
+ try {
3466
+ copilotkit.setProperties({
3467
+ ...copilotkit.properties,
3468
+ a2uiAction: message
3469
+ });
3470
+ await copilotkit.runAgent({ agent });
3471
+ } finally {
3472
+ if (copilotkit.properties) {
3473
+ const { a2uiAction, ...rest } = copilotkit.properties;
3474
+ copilotkit.setProperties(rest);
3475
+ }
3476
+ }
3477
+ }, [agent, copilotkit]),
3478
+ theme,
3479
+ catalog,
3480
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SurfaceMessageProcessor, {
3481
+ surfaceId,
3482
+ operations,
3483
+ onReady
3484
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(A2UISurfaceOrError, { surfaceId })]
3485
+ })
3486
+ });
3487
+ }
3488
+ /**
3489
+ * Renders the A2UI surface, or an error message if processing failed.
3490
+ * Must be a child of A2UIProvider to access the error state.
3491
+ */
3492
+ function A2UISurfaceOrError({ surfaceId }) {
3493
+ const error = (0, _copilotkit_a2ui_renderer.useA2UIError)();
3494
+ if (error) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3495
+ className: "cpk:rounded-lg cpk:border cpk:border-red-200 cpk:bg-red-50 cpk:p-3 cpk:text-sm cpk:text-red-700",
3496
+ children: ["A2UI render error: ", error]
3497
+ });
3498
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_copilotkit_a2ui_renderer.A2UIRenderer, {
3499
+ surfaceId,
3500
+ className: "cpk:flex cpk:flex-1"
3501
+ });
3502
+ }
3503
+ /**
3504
+ * Processes A2UI operations into the provider's message processor.
3505
+ * Must be a child of A2UIProvider to access the actions context.
3506
+ */
3507
+ function SurfaceMessageProcessor({ surfaceId, operations, onReady }) {
3508
+ const { processMessages, getSurface } = (0, _copilotkit_a2ui_renderer.useA2UIActions)();
3509
+ const lastHashRef = (0, react.useRef)("");
3510
+ (0, react.useEffect)(() => {
3511
+ const hash = JSON.stringify(operations);
3512
+ if (hash === lastHashRef.current) return;
3513
+ lastHashRef.current = hash;
3514
+ processMessages(getSurface(surfaceId) ? operations.filter((op) => !op?.createSurface) : operations);
3515
+ if (onReady && surfaceHasRenderableContent(operations)) onReady();
3516
+ }, [
3517
+ processMessages,
3518
+ getSurface,
3519
+ surfaceId,
3520
+ operations,
3521
+ onReady
3522
+ ]);
3523
+ return null;
3524
+ }
3525
+ /**
3526
+ * Whether the surface's operations are enough to paint a visible card yet.
3527
+ * A data-bound surface references its data via `path` and renders nothing until
3528
+ * the data model has ≥1 value; a static surface (no path refs) paints from its
3529
+ * components alone. Used to time the loader→surface cross-over to actual content
3530
+ * arrival rather than a fixed delay. (OSS-162)
3531
+ */
3532
+ function surfaceHasRenderableContent(operations) {
3533
+ const componentOps = operations.filter((o) => o?.updateComponents);
3534
+ if (!componentOps.length) return false;
3535
+ if (!JSON.stringify(componentOps).includes("\"path\"")) return true;
3536
+ return operations.some((o) => {
3537
+ const v = o?.updateDataModel?.value;
3538
+ if (!v || typeof v !== "object") return false;
3539
+ return Object.values(v).some((x) => Array.isArray(x) ? x.length > 0 : x !== null && x !== void 0 && x !== "");
3540
+ });
3541
+ }
3542
+ function getOperationSurfaceId(operation) {
3543
+ if (!operation || typeof operation !== "object") return null;
3544
+ if (typeof operation.surfaceId === "string") return operation.surfaceId;
3545
+ return operation?.createSurface?.surfaceId ?? operation?.updateComponents?.surfaceId ?? operation?.updateDataModel?.surfaceId ?? operation?.deleteSurface?.surfaceId ?? null;
3546
+ }
3547
+
3548
+ //#endregion
3549
+ //#region src/v2/a2ui/A2UIToolCallRenderer.tsx
3550
+ /**
3551
+ * Tool name used by the dynamic A2UI generation secondary LLM.
3552
+ */
3553
+ const RENDER_A2UI_TOOL_NAME = "render_a2ui";
3554
+ /**
3555
+ * Registers a no-op renderer for the `render_a2ui` tool call so its raw streamed
3556
+ * args are never surfaced in the transcript.
3557
+ *
3558
+ * The generation skeleton / retry / failure UX is NO LONGER owned here (OSS-162):
3559
+ * the A2UI middleware drives the whole lifecycle on the `a2ui-surface` activity
3560
+ * (one stable messageId, building → retrying → failed → painted), rendered in
3561
+ * place by `createA2UIMessageRenderer`. Owning a skeleton per tool call caused a
3562
+ * duplicate skeleton on retries / multi-call generations and a skeleton that
3563
+ * lingered after the surface painted — both fixed by retiring it here.
3419
3564
  *
3420
- * This ensures user-registered `useRenderTool({ name: "render_a2ui", ... })`
3421
- * hooks automatically override the built-in, since the merge logic in
3422
- * react-core.ts gives hook-based entries priority over prop-based entries.
3565
+ * Users can still override with their own `useRenderTool({ name: "render_a2ui" })`
3566
+ * (hook-based entries take priority over this prop-based registration).
3423
3567
  */
3424
3568
  function A2UIBuiltInToolCallRenderer() {
3425
3569
  const { copilotkit } = useCopilotKit();
@@ -3427,15 +3571,7 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
3427
3571
  const renderer = defineToolCallRenderer({
3428
3572
  name: RENDER_A2UI_TOOL_NAME,
3429
3573
  args: zod.z.any(),
3430
- render: ({ status, args: parameters }) => {
3431
- if (status === "complete") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, {});
3432
- const params = parameters;
3433
- const items = params?.items;
3434
- if (Array.isArray(items) && items.length > 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, {});
3435
- const components = params?.components;
3436
- if (Array.isArray(components) && components.length > 2) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, {});
3437
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(A2UIProgressIndicator, { parameters });
3438
- }
3574
+ render: () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, {})
3439
3575
  });
3440
3576
  const existing = copilotkit._renderToolCalls ?? [];
3441
3577
  copilotkit.setRenderToolCalls([...existing.filter((rc) => rc.name !== RENDER_A2UI_TOOL_NAME), renderer]);
@@ -3572,7 +3708,8 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
3572
3708
  if (runtimeA2UIEnabled) renderers.unshift(createA2UIMessageRenderer({
3573
3709
  theme: a2ui?.theme ?? _copilotkit_a2ui_renderer.viewerTheme,
3574
3710
  catalog: a2ui?.catalog,
3575
- loadingComponent: a2ui?.loadingComponent
3711
+ loadingComponent: a2ui?.loadingComponent,
3712
+ recovery: a2ui?.recovery
3576
3713
  }));
3577
3714
  return renderers;
3578
3715
  }, [
@@ -4797,6 +4934,175 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
4797
4934
  };
4798
4935
  }
4799
4936
 
4937
+ //#endregion
4938
+ //#region src/v2/lib/record-annotation.ts
4939
+ /**
4940
+ * Low-level function that posts an arbitrary annotation to the CopilotKit
4941
+ * runtime's general annotation endpoint (`POST /annotate`).
4942
+ *
4943
+ * This is the single transport entry point for all annotation types. Higher-
4944
+ * level hooks (e.g. `useLearnFromUserAction`) build the `type`/`payload` pair
4945
+ * for their specific annotation shape and delegate the HTTP call here.
4946
+ *
4947
+ * The function uses the same transport as `useLearnFromUserAction`:
4948
+ * - `runtimeUrl` from `copilotkit.runtimeUrl` (BFF proxies to the platform)
4949
+ * - `headers` from `copilotkit.headers` (customer auth forwarded to BFF)
4950
+ * - `clientEventId` auto-generated via `randomUUID()` when omitted
4951
+ * - `userId` is resolved server-side by the runtime; the client never sends it
4952
+ * - Errors propagate to the caller (fire-and-propagate, not fire-and-forget)
4953
+ *
4954
+ * @param args - Transport dependencies plus annotation fields.
4955
+ * @returns The platform result containing the annotation row `id` and a
4956
+ * `duplicate` flag.
4957
+ * @throws When the network request fails or the runtime returns a non-2xx
4958
+ * status. Callers that want fire-and-forget behavior should `.catch`
4959
+ * at the call site.
4960
+ */
4961
+ async function recordAnnotation(args) {
4962
+ const { runtimeUrl, headers, type, payload, threadId, occurredAt } = args;
4963
+ const body = {
4964
+ type,
4965
+ threadId,
4966
+ clientEventId: args.clientEventId ?? (0, _copilotkit_shared.randomUUID)(),
4967
+ ...payload !== void 0 ? { payload } : {},
4968
+ ...occurredAt !== void 0 ? { occurredAt } : {}
4969
+ };
4970
+ const response = await fetch(`${runtimeUrl}/annotate`, {
4971
+ method: "POST",
4972
+ headers: {
4973
+ "Content-Type": "application/json",
4974
+ ...headers
4975
+ },
4976
+ body: JSON.stringify(body)
4977
+ });
4978
+ if (!response.ok) {
4979
+ const text = await response.text().catch(() => "");
4980
+ throw new Error(`recordAnnotation: request failed (${response.status})${text ? `: ${text}` : ""}`);
4981
+ }
4982
+ const text = await response.text();
4983
+ if (!text) throw new Error(`recordAnnotation: runtime ${runtimeUrl}/annotate returned ${response.status} with an empty body`);
4984
+ try {
4985
+ return JSON.parse(text);
4986
+ } catch {
4987
+ throw new Error(`recordAnnotation: runtime ${runtimeUrl}/annotate returned a non-JSON body (status ${response.status})`);
4988
+ }
4989
+ }
4990
+
4991
+ //#endregion
4992
+ //#region src/v2/hooks/use-learn-from-user-action.tsx
4993
+ /**
4994
+ * Record a user UI interaction in the Intelligence platform's user-actions
4995
+ * stream. The platform's auto-curated knowledge base agent reads these
4996
+ * (alongside finished agent runs) and writes free-form Obsidian-flavored
4997
+ * markdown to `/project`, where any agent in the same project can later
4998
+ * read it via the `copilotkit_knowledge_base_shell` MCP tool.
4999
+ *
5000
+ * The hook returns a stable function. Calling it issues a request to the
5001
+ * customer's CopilotKit runtime (`POST ${runtimeUrl}/annotate`), which
5002
+ * resolves the Intel user from the BFF's auth and forwards to the
5003
+ * platform — the Intel API key never reaches the browser.
5004
+ *
5005
+ * If `clientEventId` is omitted `recordAnnotation` generates a UUID per call,
5006
+ * so a naive double-call (e.g. React 18 strict-mode double-mount, or a retry
5007
+ * after a network blip on a fresh Promise) is naturally safe. Supply your
5008
+ * own key when a single semantic event must remain idempotent across
5009
+ * multiple `learnFromUserAction(...)` calls.
5010
+ *
5011
+ * @example
5012
+ * ```tsx
5013
+ * import { useLearnFromUserAction } from "@copilotkit/react-core";
5014
+ *
5015
+ * function SettingsPage({ threadId }) {
5016
+ * const learnFromUserAction = useLearnFromUserAction();
5017
+ *
5018
+ * const onRename = (oldName: string, newName: string) => {
5019
+ * void learnFromUserAction({
5020
+ * threadId,
5021
+ * title: "Renamed project",
5022
+ * data: { previous: { name: oldName }, next: { name: newName } },
5023
+ * });
5024
+ * };
5025
+ * }
5026
+ * ```
5027
+ */
5028
+ function useLearnFromUserAction() {
5029
+ const { copilotkit } = useCopilotKit();
5030
+ return (0, react.useCallback)(async (input) => {
5031
+ const runtimeUrl = copilotkit.runtimeUrl;
5032
+ if (!runtimeUrl) throw new Error("useLearnFromUserAction: runtimeUrl is not configured. Set it on <CopilotKitProvider runtimeUrl=...>.");
5033
+ const payload = {
5034
+ ...input.title !== void 0 ? { title: input.title } : {},
5035
+ ...input.description !== void 0 ? { description: input.description } : {},
5036
+ ...input.data !== void 0 ? { data: input.data } : {}
5037
+ };
5038
+ return recordAnnotation({
5039
+ runtimeUrl,
5040
+ headers: copilotkit.headers ?? {},
5041
+ type: "user_action",
5042
+ payload: Object.keys(payload).length > 0 ? payload : void 0,
5043
+ threadId: input.threadId,
5044
+ clientEventId: input.clientEventId,
5045
+ occurredAt: input.occurredAt
5046
+ });
5047
+ }, [copilotkit]);
5048
+ }
5049
+
5050
+ //#endregion
5051
+ //#region src/v2/hooks/use-learn-from-user-action-in-current-thread.tsx
5052
+ /**
5053
+ * Record a user UI interaction against the **current chat's** thread. The
5054
+ * `threadId` is sourced from the surrounding
5055
+ * `<CopilotChatConfigurationProvider>` (the same provider `<CopilotChat>`,
5056
+ * `<CopilotSidebar>`, and friends set up), so callers in a chat-aware
5057
+ * subtree don't need to thread an id through manually.
5058
+ *
5059
+ * Throws on **call** (not on mount) when there is no chat-config provider
5060
+ * in scope — matches the "throw on call when runtimeUrl is missing"
5061
+ * behavior of {@link useLearnFromUserAction}. Mounting the hook in a branch
5062
+ * that never fires is harmless.
5063
+ *
5064
+ * The recorder does NOT accept a `threadId` override. If you need to
5065
+ * record against an explicit thread, use {@link useLearnFromUserAction}
5066
+ * directly — two hooks, two crisp contracts, no mode confusion.
5067
+ *
5068
+ * This hook always uses `config.threadId`, regardless of whether the
5069
+ * surrounding chat config minted it internally or received one from
5070
+ * the caller. Auto-minted threads simply mean the action lands under
5071
+ * a thread the platform never saw — the writer agent still distills
5072
+ * user-action-only threads (it does not require the thread to exist
5073
+ * in `cpki.threads`), so the loop keeps learning.
5074
+ *
5075
+ * @example
5076
+ * ```tsx
5077
+ * import { useLearnFromUserActionInCurrentThread } from "@copilotkit/react-core";
5078
+ *
5079
+ * function SettingsPanel() {
5080
+ * const learnFromUserAction = useLearnFromUserActionInCurrentThread();
5081
+ *
5082
+ * const onRename = (oldName: string, newName: string) => {
5083
+ * void learnFromUserAction({
5084
+ * title: "Renamed project",
5085
+ * data: { previous: { name: oldName }, next: { name: newName } },
5086
+ * });
5087
+ * };
5088
+ *
5089
+ * // ...
5090
+ * }
5091
+ * ```
5092
+ */
5093
+ function useLearnFromUserActionInCurrentThread() {
5094
+ const config = useCopilotChatConfiguration();
5095
+ const learnFromUserAction = useLearnFromUserAction();
5096
+ return (0, react.useCallback)(async (input) => {
5097
+ const threadId = config?.threadId;
5098
+ if (!threadId) throw new Error("useLearnFromUserActionInCurrentThread: no CopilotChatConfigurationProvider in scope. Wrap the call site in <CopilotChat>, <CopilotSidebar>, or <CopilotChatConfigurationProvider>, or use `useLearnFromUserAction()` and pass `threadId` explicitly.");
5099
+ return learnFromUserAction({
5100
+ ...input,
5101
+ threadId
5102
+ });
5103
+ }, [config?.threadId, learnFromUserAction]);
5104
+ }
5105
+
4800
5106
  //#endregion
4801
5107
  //#region src/v2/hooks/use-attachments.tsx
4802
5108
  /**
@@ -4955,6 +5261,153 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
4955
5261
  };
4956
5262
  }
4957
5263
 
5264
+ //#endregion
5265
+ //#region src/v2/hooks/use-learning-containers.tsx
5266
+ /** The default learning containers value. Matches the backend default. */
5267
+ const DEFAULT_CONTAINERS = ["project"];
5268
+ /**
5269
+ * Declaratively keeps a thread's learning containers in sync by emitting
5270
+ * `set_learning_containers` annotations via the CopilotKit runtime annotate
5271
+ * endpoint (`POST ${runtimeUrl}/annotate`).
5272
+ *
5273
+ * **Emit rules:**
5274
+ * - On mount with `["project"]` (the backend default) → does NOT emit.
5275
+ * Absence of an annotation equals the default, so the round-trip is skipped.
5276
+ * - On mount with any other value → emits immediately.
5277
+ * - On any subsequent content change (including a switch back to
5278
+ * `["project"]`) → emits (a deliberate switch is always recorded).
5279
+ * - On unmount or threadId change → emits a reset to `["project"]`
5280
+ * so the backend is left in a clean state for the next consumer.
5281
+ * Changing `learningContainers` within the same thread does NOT reset the
5282
+ * thread; only the new value is emitted.
5283
+ *
5284
+ * Content-equality is evaluated via `JSON.stringify` so a fresh array literal
5285
+ * with the same items does NOT trigger a redundant emit.
5286
+ *
5287
+ * If `runtimeUrl` is absent, all emits are silently skipped.
5288
+ *
5289
+ * @example
5290
+ * ```tsx
5291
+ * function ThreadPane({ threadId, userScope }: Props) {
5292
+ * useLearningContainers({
5293
+ * threadId,
5294
+ * learningContainers: [userScope],
5295
+ * });
5296
+ * // ...
5297
+ * }
5298
+ * ```
5299
+ */
5300
+ function useLearningContainers({ threadId, learningContainers }) {
5301
+ const { copilotkit } = useCopilotKit();
5302
+ /**
5303
+ * Tracks the last-synced container list so content-identical rerenders
5304
+ * (fresh array, same values) do not fire a redundant emit.
5305
+ * `null` = nothing synced yet (initial state or after a threadId reset).
5306
+ */
5307
+ const lastSyncedRef = (0, react.useRef)(null);
5308
+ /** Guards the missing-runtimeUrl warning so it fires at most once per hook instance. */
5309
+ const warnedMissingUrlRef = (0, react.useRef)(false);
5310
+ const runtimeUrlRef = (0, react.useRef)(copilotkit.runtimeUrl);
5311
+ const headersRef = (0, react.useRef)(copilotkit.headers ?? {});
5312
+ runtimeUrlRef.current = copilotkit.runtimeUrl;
5313
+ headersRef.current = copilotkit.headers ?? {};
5314
+ const key = JSON.stringify(learningContainers);
5315
+ const defaultKey = JSON.stringify(DEFAULT_CONTAINERS);
5316
+ (0, react.useEffect)(() => {
5317
+ const runtimeUrl = copilotkit.runtimeUrl;
5318
+ const headers = copilotkit.headers ?? {};
5319
+ /**
5320
+ * Fire-and-forget emit; errors must not surface in render.
5321
+ * Failures are logged as warnings so they are diagnosable without
5322
+ * propagating into the React render cycle.
5323
+ */
5324
+ const emit = (containers) => {
5325
+ if (!runtimeUrl) {
5326
+ if (!warnedMissingUrlRef.current) {
5327
+ warnedMissingUrlRef.current = true;
5328
+ console.warn("useLearningContainers: runtimeUrl not configured; learning-container sync disabled");
5329
+ }
5330
+ return;
5331
+ }
5332
+ recordAnnotation({
5333
+ runtimeUrl,
5334
+ headers,
5335
+ type: "set_learning_containers",
5336
+ payload: { containers },
5337
+ threadId
5338
+ }).catch((err) => {
5339
+ console.warn("useLearningContainers: failed to record set_learning_containers", err);
5340
+ });
5341
+ };
5342
+ if (lastSyncedRef.current === null) {
5343
+ if (key === defaultKey) {
5344
+ lastSyncedRef.current = learningContainers;
5345
+ return;
5346
+ }
5347
+ emit(learningContainers);
5348
+ lastSyncedRef.current = learningContainers;
5349
+ } else if (key !== JSON.stringify(lastSyncedRef.current)) {
5350
+ emit(learningContainers);
5351
+ lastSyncedRef.current = learningContainers;
5352
+ }
5353
+ }, [threadId, key]);
5354
+ (0, react.useEffect)(() => {
5355
+ const capturedThreadId = threadId;
5356
+ return () => {
5357
+ const capturedRuntimeUrl = runtimeUrlRef.current;
5358
+ const capturedHeaders = headersRef.current;
5359
+ if (capturedRuntimeUrl) recordAnnotation({
5360
+ runtimeUrl: capturedRuntimeUrl,
5361
+ headers: capturedHeaders,
5362
+ type: "set_learning_containers",
5363
+ payload: { containers: DEFAULT_CONTAINERS },
5364
+ threadId: capturedThreadId
5365
+ }).catch((err) => {
5366
+ console.warn("useLearningContainers: failed to record set_learning_containers", err);
5367
+ });
5368
+ lastSyncedRef.current = null;
5369
+ };
5370
+ }, [threadId]);
5371
+ }
5372
+
5373
+ //#endregion
5374
+ //#region src/v2/hooks/use-learning-containers-in-current-thread.tsx
5375
+ /**
5376
+ * Declaratively keeps the **current chat thread's** learning containers in
5377
+ * sync. The `threadId` is sourced from the surrounding
5378
+ * `<CopilotChatConfigurationProvider>` (the same provider `<CopilotChat>`,
5379
+ * `<CopilotSidebar>`, and friends set up), so callers in a chat-aware
5380
+ * subtree don't need to thread an id through manually.
5381
+ *
5382
+ * **Throws on render** when there is no chat-config provider in scope or
5383
+ * when the provider does not yet have an active `threadId`. Mount the hook
5384
+ * inside a subtree that is guaranteed to have a thread context.
5385
+ *
5386
+ * If you need to manage an explicit thread, use {@link useLearningContainers}
5387
+ * directly — two hooks, two crisp contracts, no mode confusion.
5388
+ *
5389
+ * @throws When no `CopilotChatConfigurationProvider` is in scope or when the
5390
+ * active `threadId` is absent/empty.
5391
+ *
5392
+ * @example
5393
+ * ```tsx
5394
+ * function ThreadPanel({ scope }: Props) {
5395
+ * useLearningContainersInCurrentThread({
5396
+ * learningContainers: [scope],
5397
+ * });
5398
+ * // ...
5399
+ * }
5400
+ * ```
5401
+ */
5402
+ function useLearningContainersInCurrentThread({ learningContainers }) {
5403
+ const threadId = useCopilotChatConfiguration()?.threadId;
5404
+ if (!threadId) throw new Error("useLearningContainersInCurrentThread must be used within a thread context (no active threadId). Wrap the component in <CopilotChat>, <CopilotSidebar>, or <CopilotChatConfigurationProvider>, or use `useLearningContainers()` and pass `threadId` explicitly.");
5405
+ useLearningContainers({
5406
+ threadId,
5407
+ learningContainers
5408
+ });
5409
+ }
5410
+
4958
5411
  //#endregion
4959
5412
  //#region src/v2/components/chat/CopilotChatToolCallsView.tsx
4960
5413
  function CopilotChatToolCallsView({ message, messages = [] }) {
@@ -5727,33 +6180,170 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
5727
6180
  */
5728
6181
  const ScrollElementContext = react.default.createContext(null);
5729
6182
 
6183
+ //#endregion
6184
+ //#region src/v2/components/intelligence-indicator/IntelligenceIndicatorView.tsx
6185
+ /**
6186
+ * The presentational "CopilotKit Intelligence" face — the default
6187
+ * rendered by the {@link IntelligenceIndicator} brain and the default
6188
+ * value for the `intelligenceIndicator` slot.
6189
+ *
6190
+ * Layout: a glassmorphism pill (the `__chrome` layer) wrapping an icon
6191
+ * and a label. The icon is two overlaid SVG paths — a spinner arc and a
6192
+ * checkmark — whose geometry lives in each path's `d` ATTRIBUTE so it
6193
+ * renders in every browser (the CSS `d:` property is Chrome-only).
6194
+ *
6195
+ * Two states, driven by the `data-status` attribute (see globals.css
6196
+ * for the exact timing):
6197
+ * 1. **In-progress.** The arc spins (CSS rotation) inside the pill and
6198
+ * the checkmark is hidden. Label + icon are a saturated purple.
6199
+ * 2. **Finished.** The arc fades out mid-spin while the checkmark draws
6200
+ * itself in upright (animated `stroke-dashoffset`); the pill chrome
6201
+ * fades away; and the label + icon settle from purple to a neutral
6202
+ * gray, with the label slanting slightly (a `transform: skewX`
6203
+ * faux-italic, so it interpolates with the color instead of snapping
6204
+ * and never reflows). The result reads as quiet "history metadata"
6205
+ * rather than an active spinner. The label text itself never changes
6206
+ * — the static check plus the color/slant shift carry the "done"
6207
+ * meaning, so no wording change is needed.
6208
+ *
6209
+ * All motion is gated behind `prefers-reduced-motion` (globals.css):
6210
+ * when reduced motion is requested the arc does not spin and the two
6211
+ * states swap instantly, without transitions.
6212
+ *
6213
+ * Customize via the `intelligenceIndicator` slot on `CopilotChat`:
6214
+ * a className string restyles the wrapper, a props object tweaks
6215
+ * the default (`{ label }`), and a component replaces it entirely
6216
+ * with full control over visuals and timing.
6217
+ */
6218
+ function IntelligenceIndicatorView({ message, status, label, className, ...rest }) {
6219
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6220
+ className: (0, tailwind_merge.twMerge)("cpk-intelligence-indicator", className),
6221
+ role: "status",
6222
+ "aria-live": "polite",
6223
+ "data-testid": `cpk-intelligence-indicator-${message.id}`,
6224
+ "data-status": status,
6225
+ title: label,
6226
+ ...rest,
6227
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6228
+ className: "cpk-intelligence-indicator__chrome",
6229
+ "aria-hidden": "true"
6230
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6231
+ className: "cpk-intelligence-indicator__content",
6232
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
6233
+ className: "cpk-intelligence-indicator__icon",
6234
+ viewBox: "0 0 24 24",
6235
+ width: "14",
6236
+ height: "14",
6237
+ "aria-hidden": "true",
6238
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
6239
+ className: "cpk-intelligence-indicator__icon-arc",
6240
+ pathLength: 1,
6241
+ d: "M 12 3 C 17 3 21 7 21 12 C 21 17 17 21 12 21 C 7 21 3 17 3 12 C 3 7 7 3 12 3"
6242
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
6243
+ className: "cpk-intelligence-indicator__icon-check",
6244
+ pathLength: 1,
6245
+ d: "M 5 12.5 L 9 16.5 L 19 6.5"
6246
+ })]
6247
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6248
+ className: "cpk-intelligence-indicator__label",
6249
+ children: label
6250
+ })]
6251
+ })]
6252
+ });
6253
+ }
6254
+
5730
6255
  //#endregion
5731
6256
  //#region src/v2/components/intelligence-indicator/IntelligenceIndicator.tsx
5732
6257
  /**
5733
6258
  * Grace window before showing the spinner. A matching tool call must
5734
6259
  * remain unresolved (no `tool`-role result message in `agent.messages`)
5735
- * for at least this long before the pill appears. This filters out
5736
- * history-replay flashes — during `connectAgent` replay, tool calls and
5737
- * their results arrive back-to-back in sub-millisecond bursts, so the
5738
- * timer is cancelled before it fires. Live runs cross the threshold
5739
- * easily because the tool actually has to execute.
6260
+ * for at least this long before the indicator transitions out of
6261
+ * `hidden`. This filters out history-replay flashes — during
6262
+ * `connectAgent` replay, tool calls and their results arrive
6263
+ * back-to-back in sub-millisecond bursts, so the timer is cancelled
6264
+ * before it fires. Live runs cross the threshold easily because the
6265
+ * tool actually has to execute.
5740
6266
  */
5741
6267
  const PENDING_THRESHOLD_MS = 100;
5742
- /** Hold the checkmark briefly before fading out. */
5743
- const CHECK_HOLD_MS = 800;
5744
6268
  /**
5745
- * Duration of the fade-out animation. Must match
5746
- * `cpk-intelligence-pill-fade-out` keyframes in `v2/styles/globals.css`.
6269
+ * Tool-name regex patterns that trigger the indicator. Matches any tool
6270
+ * name *containing* the Intelligence MCP server's canonical tool name, so
6271
+ * both the bare `copilotkit_knowledge_base_shell` and the namespaced
6272
+ * `mcp__<server>__copilotkit_knowledge_base_shell` form (emitted by
6273
+ * `@ag-ui/mcp-middleware`) light up the pill. If we add per-instance
6274
+ * customization later (e.g. a `CopilotKitProvider` prop or a runtime-info
6275
+ * field), this constant becomes the fallback.
5747
6276
  */
5748
- const FADE_OUT_ANIMATION_MS = 480;
6277
+ const DEFAULT_TOOL_PATTERNS = [/copilotkit_knowledge_base_shell/];
5749
6278
  /**
5750
- * Tool-name regex patterns that trigger the indicator. Currently
5751
- * hardcoded to the Intelligence MCP server's canonical tool name. If
5752
- * we add per-instance customization later (e.g. a `CopilotKitProvider`
5753
- * prop or a runtime-info field), this constant becomes the fallback.
6279
+ * Phase to start in when an indicator first mounts. A turn that is already
6280
+ * complete at mount jumps straight to `finished` no `hidden` flash, no
6281
+ * spinner blip — which is what makes scrolled-back / replayed history render
6282
+ * its indicators directly in the finished state.
6283
+ *
6284
+ * Pure and timing-free on purpose: the grace window ({@link
6285
+ * PENDING_THRESHOLD_MS}) only controls *when* the live transition is applied;
6286
+ * these functions decide *what* it resolves to, so the decision can be unit
6287
+ * tested deterministically without any timers.
5754
6288
  */
5755
- const DEFAULT_TOOL_PATTERNS = [/^copilotkit_knowledge_base_shell$/];
6289
+ function initialIndicatorPhase(turnComplete) {
6290
+ return turnComplete ? "finished" : "hidden";
6291
+ }
6292
+ /**
6293
+ * Phase the grace window resolves to once it elapses:
6294
+ * - completed turn → `finished` (replay-flash suppression: a tool whose
6295
+ * result lands within the window skips the spinner entirely),
6296
+ * - a still-pending matching tool call → `spinner`,
6297
+ * - otherwise stay `hidden` (the matching tool call hasn't landed yet).
6298
+ */
6299
+ function resolveGracePhase(turnComplete, hasPending) {
6300
+ if (turnComplete) return "finished";
6301
+ if (hasPending) return "spinner";
6302
+ return "hidden";
6303
+ }
5756
6304
  const isMatchingToolCallName = (name) => typeof name === "string" && DEFAULT_TOOL_PATTERNS.some((p) => p.test(name));
6305
+ const messageHasMatchingToolCall = (m) => {
6306
+ if (m.role !== "assistant") return false;
6307
+ return (Array.isArray(m.toolCalls) ? m.toolCalls : []).some((tc) => isMatchingToolCallName(tc?.function?.name));
6308
+ };
6309
+ /**
6310
+ * Stable turn id for the messages that precede the first user message (a turn
6311
+ * with no opening user message of its own). Used as the React key so the
6312
+ * indicator for that turn never collides with a real user-message id.
6313
+ */
6314
+ const INTELLIGENCE_TURN_HEAD = "__cpk_turn_head__";
6315
+ /**
6316
+ * Map each Intelligence-using turn to its anchor message — the FIRST bash-using
6317
+ * assistant message of the turn — and a stable turn id (the id of the user
6318
+ * message that opened the turn, or {@link INTELLIGENCE_TURN_HEAD} for the
6319
+ * pre-first-user turn). Returns `Map<anchorMessageId, turnId>`.
6320
+ *
6321
+ * Anchoring to the FIRST (not last) bash-using message keeps the indicator
6322
+ * fixed in place for the whole turn: later bash steps don't reposition it, so
6323
+ * the spinner never abruptly jumps mid-turn (bug 1). `CopilotChatMessageView`
6324
+ * emits exactly one `IntelligenceIndicator` per entry, keyed by the turn id and
6325
+ * positioned at the anchor; the per-turn key also lets every past turn keep its
6326
+ * own indicator in scroll-back.
6327
+ */
6328
+ function getIntelligenceTurnAnchors(messages) {
6329
+ const anchors = /* @__PURE__ */ new Map();
6330
+ let turnId = INTELLIGENCE_TURN_HEAD;
6331
+ let anchorId = null;
6332
+ const commit = () => {
6333
+ if (anchorId !== null) anchors.set(anchorId, turnId);
6334
+ anchorId = null;
6335
+ };
6336
+ for (const m of messages) {
6337
+ if (m.role === "user") {
6338
+ commit();
6339
+ turnId = m.id;
6340
+ continue;
6341
+ }
6342
+ if (anchorId === null && messageHasMatchingToolCall(m)) anchorId = m.id;
6343
+ }
6344
+ commit();
6345
+ return anchors;
6346
+ }
5757
6347
  /**
5758
6348
  * "Tool-call-like" messages do NOT count as a real follow-up: tool
5759
6349
  * result messages, assistant messages that carry tool calls, and
@@ -5772,45 +6362,51 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
5772
6362
  return false;
5773
6363
  };
5774
6364
  /**
5775
- * The "Using CopilotKit Intelligence" pill. Auto-mounted by
5776
- * `CopilotChatMessageView` for every message slot when
5777
- * `copilotkit.intelligence` is configured callers do not register
5778
- * this themselves. Self-gates so only the canonical message renders a
5779
- * pill.
6365
+ * The "Using CopilotKit Intelligence" indicator brain. Auto-mounted by
6366
+ * `CopilotChatMessageView` once per Intelligence-using turn, at that
6367
+ * turn's anchor message and keyed by the turn id (see
6368
+ * {@link getIntelligenceTurnAnchors}). Callers do not register this
6369
+ * themselves. It owns the run subscription and the phase machine and
6370
+ * renders its swappable face via the `intelligenceIndicator` slot.
6371
+ *
6372
+ * Placement (which message anchors the turn) is decided by the view, so
6373
+ * this component does not self-gate its own placement; it only derives
6374
+ * in-progress/finished for the turn it was mounted on.
5780
6375
  *
5781
6376
  * Render gates (all must hold):
5782
6377
  * 1. `copilotkit.intelligence !== undefined`
5783
- * 2. The message is an assistant message with at least one tool call
5784
- * whose name matches {@link DEFAULT_TOOL_PATTERNS}
5785
- * 3. The message is the *latest* such matching-assistant message in
5786
- * `agent.messages` — tool-result messages and prose-only assistant
5787
- * messages don't invalidate the slot, so the pill stays
5788
- * continuously through a multi-step tool chain.
5789
- * 4. The phase machine is past `idle` (the pending-grace timer fired)
5790
- * and not yet `hidden`.
6378
+ * 2. The (anchor) message is an assistant message with at least one
6379
+ * tool call whose name matches {@link DEFAULT_TOOL_PATTERNS}.
6380
+ * 3. The phase machine is past `hidden`.
6381
+ *
6382
+ * Because the view keys each indicator by its turn id, the instance moves
6383
+ * with the anchor across a hand-off (no remount, no spinner restart), and
6384
+ * every prior Intelligence-using turn keeps its own persistent indicator
6385
+ * in chat history.
5791
6386
  *
5792
6387
  * Phase machine (per-instance, all timers local):
5793
- * - Starts in `idle` nothing rendered.
5794
- * - `idle spinner` once a matching tool call has been pending
6388
+ * - Starts in `hidden`, unless the message mounts onto an
6389
+ * already-completed turn (no pending work, agent stopped or a
6390
+ * real follow-up already present), in which case the lazy
6391
+ * `useState` initializer starts directly in `finished`. This is
6392
+ * what avoids a "hidden flash" on history replay.
6393
+ * - `hidden → spinner` once a matching tool call has been pending
5795
6394
  * (no `tool`-role result with a matching `toolCallId`) for
5796
6395
  * {@link PENDING_THRESHOLD_MS}. Replay flashes (tool call + result
5797
6396
  * in the same tick) never cross this threshold.
5798
- * - `spinnercheck` as soon as EITHER `agent.isRunning` flips
6397
+ * - `hiddenfinished` if after the grace window the turn is
6398
+ * already complete (no pending work AND
6399
+ * `sawRealFollowup || !agent.isRunning`). Handles very fast tools
6400
+ * whose result lands within the grace window.
6401
+ * - `spinner → finished` as soon as EITHER `agent.isRunning` flips
5799
6402
  * false OR a non-tool-call-like message appears later in
5800
- * `agent.messages` (i.e. the agent has produced a "real"
5801
- * follow-up — prose answer or a new user turn).
5802
- * - `check fading` after {@link CHECK_HOLD_MS}.
5803
- * - `fading hidden` after {@link FADE_OUT_ANIMATION_MS}.
5804
- *
5805
- * Once `hidden`, the phase is sticky — a finished pill never re-spawns
5806
- * on the same message. New runs mount fresh indicator instances on
5807
- * their own assistant messages.
5808
- *
5809
- * The "exactly one pill at a time" guarantee is structural: only one
5810
- * message satisfies the latest-matching-assistant gate at any moment.
6403
+ * `agent.messages` (i.e. the agent produced a "real" follow-up —
6404
+ * prose answer or a new user turn).
6405
+ * - `finished` is terminal: the indicator settles into its
6406
+ * persistent tag form and stays mounted.
5811
6407
  */
5812
6408
  function IntelligenceIndicator(props) {
5813
- const { message, agentId, label = "Using CopilotKit Intelligence" } = props;
6409
+ const { message, agentId, label = "CopilotKit Intelligence", intelligenceIndicator } = props;
5814
6410
  const { copilotkit } = useCopilotKit();
5815
6411
  const config = useCopilotChatConfiguration();
5816
6412
  const { agent } = useAgent({
@@ -5830,90 +6426,95 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
5830
6426
  for (const m of agent.messages) if (m.role === "tool" && m.toolCallId) resolved.add(m.toolCallId);
5831
6427
  return matchingToolCallIds.some((id) => !resolved.has(id));
5832
6428
  }, [matchingToolCallIds, agent.messages]);
5833
- const sawRealFollowup = (0, react.useMemo)(() => {
6429
+ const turnComplete = (0, react.useMemo)(() => {
5834
6430
  const idx = agent.messages.findIndex((m) => m.id === message.id);
5835
6431
  if (idx < 0) return false;
5836
6432
  for (let i = idx + 1; i < agent.messages.length; i += 1) if (!isToolCallLikeMessage(agent.messages[i])) return true;
5837
6433
  return false;
5838
- }, [agent.messages, message.id]);
5839
- const [phase, setPhase] = (0, react.useState)("idle");
6434
+ }, [agent.messages, message.id]) || !agent.isRunning;
6435
+ const [phase, setPhase] = (0, react.useState)(() => initialIndicatorPhase(turnComplete));
5840
6436
  (0, react.useEffect)(() => {
5841
- if (phase !== "idle") return void 0;
5842
- if (!hasPending) return void 0;
5843
- const t = setTimeout(() => setPhase("spinner"), PENDING_THRESHOLD_MS);
6437
+ if (phase !== "hidden") return void 0;
6438
+ const t = setTimeout(() => {
6439
+ setPhase(resolveGracePhase(turnComplete, hasPending));
6440
+ }, PENDING_THRESHOLD_MS);
5844
6441
  return () => clearTimeout(t);
5845
- }, [phase, hasPending]);
5846
- (0, react.useEffect)(() => {
5847
- if (phase !== "spinner") return void 0;
5848
- if (!agent.isRunning || sawRealFollowup) setPhase("check");
5849
6442
  }, [
5850
6443
  phase,
5851
- agent.isRunning,
5852
- sawRealFollowup
6444
+ hasPending,
6445
+ turnComplete
5853
6446
  ]);
5854
6447
  (0, react.useEffect)(() => {
5855
- if (phase !== "check") return void 0;
5856
- const t = setTimeout(() => setPhase("fading"), CHECK_HOLD_MS);
5857
- return () => clearTimeout(t);
5858
- }, [phase]);
5859
- (0, react.useEffect)(() => {
5860
- if (phase !== "fading") return void 0;
5861
- const t = setTimeout(() => setPhase("hidden"), FADE_OUT_ANIMATION_MS);
5862
- return () => clearTimeout(t);
5863
- }, [phase]);
6448
+ if (phase !== "spinner") return void 0;
6449
+ if (turnComplete) setPhase("finished");
6450
+ }, [phase, turnComplete]);
5864
6451
  if (copilotkit.intelligence === void 0) return null;
5865
6452
  if (!config) return null;
5866
- if (phase === "idle" || phase === "hidden") return null;
6453
+ if (phase === "hidden") return null;
5867
6454
  if (message.role !== "assistant") return null;
5868
- if (!(Array.isArray(message.toolCalls) ? message.toolCalls : []).some((tc) => isMatchingToolCallName(tc?.function?.name))) return null;
5869
- let latestMatchingAssistantId;
5870
- for (let i = agent.messages.length - 1; i >= 0; i -= 1) {
5871
- const m = agent.messages[i];
5872
- if (m.role !== "assistant") continue;
5873
- if ((Array.isArray(m.toolCalls) ? m.toolCalls : []).some((tc) => isMatchingToolCallName(tc?.function?.name))) {
5874
- latestMatchingAssistantId = m.id;
5875
- break;
5876
- }
5877
- }
5878
- if (latestMatchingAssistantId !== message.id) return null;
5879
- const showSpinner = phase === "spinner";
5880
- const isFading = phase === "fading";
5881
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5882
- className: "cpk-intelligence-pill" + (isFading ? " cpk-intelligence-pill--fading" : ""),
5883
- role: "status",
5884
- "aria-live": "polite",
5885
- "aria-hidden": isFading || void 0,
5886
- "data-testid": `cpk-intelligence-pill-${message.id}`,
5887
- title: label,
5888
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
5889
- className: "cpk-intelligence-pill__icon",
5890
- viewBox: "0 0 24 24",
5891
- width: "14",
5892
- height: "14",
5893
- "aria-hidden": "true",
5894
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
5895
- cx: "12",
5896
- cy: "12",
5897
- r: "9",
5898
- fill: "none",
5899
- strokeWidth: "2.5",
5900
- strokeLinecap: "round",
5901
- className: "cpk-intelligence-pill__ring" + (showSpinner ? "" : " cpk-intelligence-pill__ring--done")
5902
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
5903
- d: "M8 12.5l3 3 5-6",
5904
- fill: "none",
5905
- strokeWidth: "2.5",
5906
- strokeLinecap: "round",
5907
- strokeLinejoin: "round",
5908
- className: "cpk-intelligence-pill__check" + (showSpinner ? "" : " cpk-intelligence-pill__check--shown")
5909
- })]
5910
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label })]
6455
+ if (!messageHasMatchingToolCall(message)) return null;
6456
+ return renderSlot(intelligenceIndicator, IntelligenceIndicatorView, {
6457
+ message,
6458
+ status: phase === "finished" ? "finished" : "in-progress",
6459
+ label
5911
6460
  });
5912
6461
  }
5913
6462
 
5914
6463
  //#endregion
5915
6464
  //#region src/v2/components/chat/CopilotChatMessageView.tsx
5916
6465
  /**
6466
+ * Builds a map of message.id → stable per-row React key for an entire message
6467
+ * list. A message's canonical `id` is not stable within a turn: some backends
6468
+ * re-key a message mid-stream (e.g. LangChain replaces its transient
6469
+ * `lc_run--…` streaming id with the provider's final `resp_…` id in the
6470
+ * MESSAGES_SNAPSHOT). Keying rows by `id` made React unmount/remount the row
6471
+ * on that swap — the visible HITL chat flash. Tool-call ids survive the
6472
+ * rename, so an assistant message anchored by a tool call is keyed by its
6473
+ * first tool-call id (`tc:<anchorToolCallId>`); everything else falls back to
6474
+ * `id`.
6475
+ *
6476
+ * Load-bearing assumption: this only helps when the first tool-call id itself
6477
+ * is stable across the rename; backends that re-key tool-call ids mid-stream
6478
+ * still remount.
6479
+ *
6480
+ * Collision rule: two distinct assistant messages can occasionally share a
6481
+ * first-tool-call id (e.g. due to upstream bugs or replayed state). First
6482
+ * occurrence (in list order) claims `tc:<id>`; later collisions fall back to
6483
+ * `message.id`. Every assigned key is checked against the claimed set, so
6484
+ * keys are unique even for pathological ids (e.g. a raw message id beginning
6485
+ * with "tc:"); collisions disambiguate with a deterministic numeric suffix.
6486
+ *
6487
+ * Precondition: callers must pass a deduplicated list (see
6488
+ * `deduplicateMessages`); duplicate message ids would silently overwrite map
6489
+ * entries.
6490
+ *
6491
+ * Order caveat: the collision rule is order-sensitive — if two messages
6492
+ * sharing a first tool-call id swap list positions across renders, the
6493
+ * claimant changes and both rows remount. Acceptable: that situation already
6494
+ * indicates an upstream id bug, and keys remain unique.
6495
+ */
6496
+ function buildRowRenderKeys(messages) {
6497
+ const keys = /* @__PURE__ */ new Map();
6498
+ const claimed = /* @__PURE__ */ new Set();
6499
+ for (const message of messages) {
6500
+ let candidate;
6501
+ if (message.role === "assistant") {
6502
+ const anchorToolCallId = message.toolCalls?.[0]?.id;
6503
+ if (anchorToolCallId) candidate = `tc:${anchorToolCallId}`;
6504
+ }
6505
+ let assigned = message.id;
6506
+ if (candidate && !claimed.has(candidate)) assigned = candidate;
6507
+ else if (claimed.has(assigned)) {
6508
+ let n = 2;
6509
+ while (claimed.has(`${assigned}:${n}`)) n += 1;
6510
+ assigned = `${assigned}:${n}`;
6511
+ }
6512
+ keys.set(message.id, assigned);
6513
+ claimed.add(assigned);
6514
+ }
6515
+ return keys;
6516
+ }
6517
+ /**
5917
6518
  * Resolves a slot value into a { Component, slotProps } pair, handling the three
5918
6519
  * slot forms: a component type, a className string, or a partial-props object.
5919
6520
  */
@@ -6060,7 +6661,7 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6060
6661
  return [...acc.values()];
6061
6662
  }
6062
6663
  const VIRTUALIZE_THRESHOLD = 50;
6063
- function CopilotChatMessageView({ messages = [], assistantMessage, userMessage, reasoningMessage, cursor, isRunning = false, children, className, ...props }) {
6664
+ function CopilotChatMessageView({ messages = [], assistantMessage, userMessage, reasoningMessage, cursor, intelligenceIndicator, isRunning = false, children, className, ...props }) {
6064
6665
  const renderCustomMessage = useRenderCustomMessages();
6065
6666
  const { renderActivityMessage } = useRenderActivityMessage();
6066
6667
  const { copilotkit } = useCopilotKit();
@@ -6092,6 +6693,7 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6092
6693
  return copilotkit.getStateByRun(config.agentId, config.threadId, resolvedRunId);
6093
6694
  };
6094
6695
  const deduplicatedMessages = (0, react.useMemo)(() => deduplicateMessages(messages), [messages]);
6696
+ const rowRenderKeys = (0, react.useMemo)(() => buildRowRenderKeys(deduplicatedMessages), [deduplicatedMessages]);
6095
6697
  if (process.env.NODE_ENV === "development" && deduplicatedMessages.length < messages.length) console.warn(`CopilotChatMessageView: Merged ${messages.length - deduplicatedMessages.length} message(s) with duplicate IDs.`);
6096
6698
  const { Component: AssistantComponent, slotProps: assistantSlotProps } = (0, react.useMemo)(() => resolveSlotComponent(assistantMessage, CopilotChatAssistantMessage_default), [assistantMessage]);
6097
6699
  const { Component: UserComponent, slotProps: userSlotProps } = (0, react.useMemo)(() => resolveSlotComponent(userMessage, CopilotChatUserMessage_default), [userMessage]);
@@ -6118,48 +6720,52 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6118
6720
  if (!shouldVirtualize || !deduplicatedMessages.length) return;
6119
6721
  virtualizer.scrollToIndex(deduplicatedMessages.length - 1, { align: "end" });
6120
6722
  }, [shouldVirtualize, firstMessageId]);
6723
+ const intelligenceTurnAnchors = (0, react.useMemo)(() => getIntelligenceTurnAnchors(deduplicatedMessages), [deduplicatedMessages]);
6121
6724
  const renderMessageBlock = (message) => {
6122
6725
  const elements = [];
6123
6726
  const stateSnapshot = getStateSnapshotForMessage(message.id);
6727
+ const rowKey = rowRenderKeys.get(message.id) ?? message.id;
6124
6728
  if (renderCustomMessage) elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedCustomMessage, {
6125
6729
  message,
6126
6730
  position: "before",
6127
6731
  renderCustomMessage,
6128
6732
  stateSnapshot
6129
- }, `${message.id}-custom-before`));
6733
+ }, `${rowKey}-custom-before`));
6130
6734
  if (message.role === "assistant") elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedAssistantMessage, {
6131
6735
  message,
6132
6736
  messages,
6133
6737
  isRunning,
6134
6738
  AssistantMessageComponent: AssistantComponent,
6135
6739
  slotProps: assistantSlotProps
6136
- }, message.id));
6740
+ }, rowKey));
6137
6741
  else if (message.role === "user") elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedUserMessage, {
6138
6742
  message,
6139
6743
  UserMessageComponent: UserComponent,
6140
6744
  slotProps: userSlotProps
6141
- }, message.id));
6745
+ }, rowKey));
6142
6746
  else if (message.role === "activity") elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedActivityMessage, {
6143
6747
  message,
6144
6748
  renderActivityMessage
6145
- }, message.id));
6749
+ }, rowKey));
6146
6750
  else if (message.role === "reasoning") elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedReasoningMessage, {
6147
6751
  message,
6148
6752
  messages,
6149
6753
  isRunning,
6150
6754
  ReasoningMessageComponent: ReasoningComponent,
6151
6755
  slotProps: reasoningSlotProps
6152
- }, message.id));
6756
+ }, rowKey));
6153
6757
  if (renderCustomMessage) elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedCustomMessage, {
6154
6758
  message,
6155
6759
  position: "after",
6156
6760
  renderCustomMessage,
6157
6761
  stateSnapshot
6158
- }, `${message.id}-custom-after`));
6159
- if (copilotkit.intelligence !== void 0 && message.role === "assistant") elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IntelligenceIndicator, {
6762
+ }, `${rowKey}-custom-after`));
6763
+ const intelligenceTurnId = intelligenceTurnAnchors.get(message.id);
6764
+ if (intelligenceTurnId !== void 0) elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IntelligenceIndicator, {
6160
6765
  message,
6161
- agentId: config?.agentId ?? _copilotkit_shared.DEFAULT_AGENT_ID
6162
- }, `${message.id}-intelligence`));
6766
+ agentId: config?.agentId ?? _copilotkit_shared.DEFAULT_AGENT_ID,
6767
+ intelligenceIndicator
6768
+ }, `intelligence-${intelligenceTurnId}`));
6163
6769
  return elements.filter(Boolean);
6164
6770
  };
6165
6771
  const messageElements = shouldVirtualize ? [] : deduplicatedMessages.flatMap(renderMessageBlock);
@@ -6199,7 +6805,7 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6199
6805
  transform: `translateY(${virtualItem.start}px)`
6200
6806
  },
6201
6807
  children: renderMessageBlock(message)
6202
- }, message.id);
6808
+ }, rowRenderKeys.get(message.id) ?? message.id);
6203
6809
  })
6204
6810
  }) : messageElements,
6205
6811
  interruptElement,
@@ -6596,7 +7202,7 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6596
7202
  })
6597
7203
  });
6598
7204
  }
6599
- function CopilotChatView({ messageView, input, scrollView, suggestionView, welcomeScreen, messages = [], autoScroll = true, isRunning = false, suggestions, suggestionLoadingIndexes, onSelectSuggestion, onSubmitMessage, onStop, inputMode, inputValue, onInputChange, onStartTranscribe, onCancelTranscribe, onFinishTranscribe, onFinishTranscribeWithAudio, attachments, onRemoveAttachment, onAddFile, dragOver, onDragOver, onDragLeave, onDrop, isConnecting = false, hasExplicitThreadId = false, disclaimer, children, className, ...props }) {
7205
+ function CopilotChatView({ messageView, input, scrollView, suggestionView, welcomeScreen, messages = [], autoScroll = true, isRunning = false, suggestions, suggestionLoadingIndexes, onSelectSuggestion, onSubmitMessage, onStop, inputMode, inputValue, onInputChange, onStartTranscribe, onCancelTranscribe, onFinishTranscribe, onFinishTranscribeWithAudio, attachments, onRemoveAttachment, onAddFile, dragOver, onDragOver, onDragLeave, onDrop, isConnecting = false, hasExplicitThreadId = false, disclaimer, intelligenceIndicator, children, className, ...props }) {
6600
7206
  const [inputContainerEl, setInputContainerEl] = (0, react.useState)(null);
6601
7207
  const [inputContainerHeight, setInputContainerHeight] = (0, react.useState)(0);
6602
7208
  const [isResizing, setIsResizing] = (0, react.useState)(false);
@@ -6633,7 +7239,8 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6633
7239
  }, [inputContainerEl]);
6634
7240
  const BoundMessageView = renderSlot(messageView, CopilotChatMessageView, {
6635
7241
  messages,
6636
- isRunning
7242
+ isRunning,
7243
+ intelligenceIndicator
6637
7244
  });
6638
7245
  const BoundInput = renderSlot(input, CopilotChatInput_default, {
6639
7246
  onSubmitMessage,
@@ -7178,6 +7785,13 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
7178
7785
  resolvedAgentId,
7179
7786
  hasExplicitThreadId
7180
7787
  ]);
7788
+ const waitForActiveRunToSettle = (0, react.useCallback)(async () => {
7789
+ if (agent.isRunning && (0, _copilotkit_core.isRunCompletionAware)(agent) && agent.activeRunCompletionPromise) try {
7790
+ await agent.activeRunCompletionPromise;
7791
+ } catch (error) {
7792
+ console.error("CopilotChat: in-flight run rejected while queuing send", error);
7793
+ }
7794
+ }, [agent]);
7181
7795
  const onSubmitInput = (0, react.useCallback)(async (value) => {
7182
7796
  if (selectedAttachmentsRef.current.some((a) => a.status === "uploading")) {
7183
7797
  console.error("[CopilotKit] Cannot send while attachments are uploading (pre-await guard)");
@@ -7185,11 +7799,7 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
7185
7799
  return;
7186
7800
  }
7187
7801
  setInputValue("");
7188
- if (agent.isRunning && "activeRunCompletionPromise" in agent && agent.activeRunCompletionPromise) try {
7189
- await agent.activeRunCompletionPromise;
7190
- } catch (error) {
7191
- console.error("CopilotChat: in-flight run rejected while queuing send", error);
7192
- }
7802
+ await waitForActiveRunToSettle();
7193
7803
  if (selectedAttachmentsRef.current.some((a) => a.status === "uploading")) {
7194
7804
  console.error("[CopilotKit] Cannot send while attachments are uploading (post-await re-check)");
7195
7805
  setTranscriptionError("Cannot send while attachments are uploading.");
@@ -7226,8 +7836,13 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
7226
7836
  } catch (error) {
7227
7837
  console.error("CopilotChat: runAgent failed", error);
7228
7838
  }
7229
- }, [agent, consumeAttachments]);
7839
+ }, [
7840
+ agent,
7841
+ consumeAttachments,
7842
+ waitForActiveRunToSettle
7843
+ ]);
7230
7844
  const handleSelectSuggestion = (0, react.useCallback)(async (suggestion) => {
7845
+ await waitForActiveRunToSettle();
7231
7846
  agent.addMessage({
7232
7847
  id: (0, _copilotkit_shared.randomUUID)(),
7233
7848
  role: "user",
@@ -7238,7 +7853,7 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
7238
7853
  } catch (error) {
7239
7854
  console.error("CopilotChat: runAgent failed after selecting suggestion", error);
7240
7855
  }
7241
- }, [agent]);
7856
+ }, [agent, waitForActiveRunToSettle]);
7242
7857
  const stopCurrentRun = (0, react.useCallback)(() => {
7243
7858
  try {
7244
7859
  copilotkit.stopAgent({ agent });
@@ -8902,7 +9517,7 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
8902
9517
  } };
8903
9518
  case _copilotkit_shared.CopilotKitErrorCode.UPGRADE_REQUIRED_ERROR: return { primary: {
8904
9519
  label: "Upgrade",
8905
- onClick: () => window.open("https://cloud.copilotkit.ai", "_blank", "noopener,noreferrer")
9520
+ onClick: () => window.open("https://dashboard.operations.copilotkit.ai", "_blank", "noopener,noreferrer")
8906
9521
  } };
8907
9522
  default: return;
8908
9523
  }
@@ -10047,7 +10662,9 @@ Object.defineProperty(exports, 'CopilotSidebarView', {
10047
10662
  return CopilotSidebarView;
10048
10663
  }
10049
10664
  });
10665
+ exports.INTELLIGENCE_TURN_HEAD = INTELLIGENCE_TURN_HEAD;
10050
10666
  exports.IntelligenceIndicator = IntelligenceIndicator;
10667
+ exports.IntelligenceIndicatorView = IntelligenceIndicatorView;
10051
10668
  exports.MCPAppsActivityContentSchema = MCPAppsActivityContentSchema;
10052
10669
  exports.MCPAppsActivityRenderer = MCPAppsActivityRenderer;
10053
10670
  exports.MCPAppsActivityType = MCPAppsActivityType;
@@ -10062,6 +10679,7 @@ Object.defineProperty(exports, 'a2uiDefaultTheme', {
10062
10679
  });
10063
10680
  exports.createA2UIMessageRenderer = createA2UIMessageRenderer;
10064
10681
  exports.defineToolCallRenderer = defineToolCallRenderer;
10682
+ exports.getIntelligenceTurnAnchors = getIntelligenceTurnAnchors;
10065
10683
  exports.useAgent = useAgent;
10066
10684
  exports.useAgentContext = useAgentContext;
10067
10685
  exports.useAttachments = useAttachments;
@@ -10074,6 +10692,10 @@ exports.useDefaultRenderTool = useDefaultRenderTool;
10074
10692
  exports.useFrontendTool = useFrontendTool;
10075
10693
  exports.useHumanInTheLoop = useHumanInTheLoop;
10076
10694
  exports.useInterrupt = useInterrupt;
10695
+ exports.useLearnFromUserAction = useLearnFromUserAction;
10696
+ exports.useLearnFromUserActionInCurrentThread = useLearnFromUserActionInCurrentThread;
10697
+ exports.useLearningContainers = useLearningContainers;
10698
+ exports.useLearningContainersInCurrentThread = useLearningContainersInCurrentThread;
10077
10699
  exports.useRenderActivityMessage = useRenderActivityMessage;
10078
10700
  exports.useRenderCustomMessages = useRenderCustomMessages;
10079
10701
  exports.useRenderTool = useRenderTool;