@copilotkit/react-core 1.56.4-canary.1777529757 → 1.56.4-canary.1777531098

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@copilotkit/react-core",
3
- "version": "1.56.4-canary.1777529757",
3
+ "version": "1.56.4-canary.1777531098",
4
4
  "private": false,
5
5
  "keywords": [
6
6
  "ai",
@@ -73,11 +73,11 @@
73
73
  "untruncate-json": "^0.0.1",
74
74
  "use-stick-to-bottom": "^1.1.1",
75
75
  "zod-to-json-schema": "^3.24.5",
76
- "@copilotkit/a2ui-renderer": "1.56.4-canary.1777529757",
77
- "@copilotkit/shared": "1.56.4-canary.1777529757",
78
- "@copilotkit/web-inspector": "1.56.4-canary.1777529757",
79
- "@copilotkit/runtime-client-gql": "1.56.4-canary.1777529757",
80
- "@copilotkit/core": "1.56.4-canary.1777529757"
76
+ "@copilotkit/a2ui-renderer": "1.56.4-canary.1777531098",
77
+ "@copilotkit/web-inspector": "1.56.4-canary.1777531098",
78
+ "@copilotkit/shared": "1.56.4-canary.1777531098",
79
+ "@copilotkit/runtime-client-gql": "1.56.4-canary.1777531098",
80
+ "@copilotkit/core": "1.56.4-canary.1777531098"
81
81
  },
82
82
  "devDependencies": {
83
83
  "@tailwindcss/cli": "^4.1.11",
@@ -264,14 +264,20 @@ export function CopilotChatView({
264
264
  ...(disclaimer !== undefined ? { disclaimer } : {}),
265
265
  } as CopilotChatInputProps);
266
266
 
267
- // Hide suggestions while a thread is connecting (mid-replay would render
268
- // against a still-assembling message tree and visibly jump as each final
269
- // text chunk reflows the layout). Run-in-flight is handled by the
270
- // SuggestionEngine: at run start, non-"always" suggestions are cleared and
271
- // only "always" ones are restored — so a non-empty `suggestions` array
272
- // here already means "this is something the user opted to keep visible."
267
+ // Hide suggestions while a thread is connecting or a run is in flight.
268
+ // Otherwise, mid-replay (bootstrap stream from /connect) or mid-run, the
269
+ // suggestions would render against a still-assembling message tree and
270
+ // visibly jump as each final text chunk reflows the layout.
271
+ //
272
+ // `available: "always"` controls *eligibility windows* (welcome screen vs
273
+ // after first message), not whether to render through these transitions —
274
+ // we still wait for the connect/run to settle and the end-of-run reload
275
+ // to repopulate against the new context.
273
276
  const hasSuggestions =
274
- !isConnecting && Array.isArray(suggestions) && suggestions.length > 0;
277
+ !isConnecting &&
278
+ !isRunning &&
279
+ Array.isArray(suggestions) &&
280
+ suggestions.length > 0;
275
281
  const BoundSuggestionView = hasSuggestions
276
282
  ? renderSlot(suggestionView, CopilotChatSuggestionView, {
277
283
  suggestions,
@@ -87,7 +87,7 @@ describe("CopilotChat - static suggestions with available:'always'", () => {
87
87
  });
88
88
  });
89
89
 
90
- it("should keep suggestions visible during a run", async () => {
90
+ it("should hide suggestions during a run and restore them after", async () => {
91
91
  const agent = new MockStepwiseAgent();
92
92
  renderChat({ agent, consumerAgentId: "default" });
93
93
 
@@ -111,14 +111,13 @@ describe("CopilotChat - static suggestions with available:'always'", () => {
111
111
  agent.emit(runStartedEvent());
112
112
  agent.emit(textChunkEvent(messageId, "Hello! How can I help?"));
113
113
 
114
- // Suggestions must remain visible even while the run is still in progress
115
- await waitFor(
116
- () => {
117
- expect(screen.getByText("Say hello")).toBeDefined();
118
- expect(screen.getByText("Get help")).toBeDefined();
119
- },
120
- { timeout: 3000 },
121
- );
114
+ // While the run is in flight, suggestions should be hidden every run
115
+ // changes the conversation context, so we wait for the end-of-run reload
116
+ // before showing them again.
117
+ await waitFor(() => {
118
+ expect(screen.queryByText("Say hello")).toBeNull();
119
+ expect(screen.queryByText("Get help")).toBeNull();
120
+ });
122
121
 
123
122
  agent.emit(runFinishedEvent());
124
123
  agent.complete();
@@ -127,7 +126,7 @@ describe("CopilotChat - static suggestions with available:'always'", () => {
127
126
  expect(screen.getByText("Hello! How can I help?")).toBeDefined();
128
127
  });
129
128
 
130
- // And still visible after the run completes
129
+ // After the run, the static "always" config repopulates them.
131
130
  await waitFor(
132
131
  () => {
133
132
  expect(screen.getByText("Say hello")).toBeDefined();
@@ -137,7 +136,7 @@ describe("CopilotChat - static suggestions with available:'always'", () => {
137
136
  );
138
137
  });
139
138
 
140
- it("should keep suggestions visible during a run in pin-to-send mode", async () => {
139
+ it("should hide suggestions during a run in pin-to-send mode", async () => {
141
140
  const agent = new MockStepwiseAgent();
142
141
  renderChat({ agent, autoScroll: "pin-to-send" });
143
142
 
@@ -161,14 +160,10 @@ describe("CopilotChat - static suggestions with available:'always'", () => {
161
160
  agent.emit(runStartedEvent());
162
161
  agent.emit(textChunkEvent(messageId, "Hello! How can I help?"));
163
162
 
164
- // Suggestions must remain visible even while the run is still in progress
165
- await waitFor(
166
- () => {
167
- expect(screen.getByText("Say hello")).toBeDefined();
168
- expect(screen.getByText("Get help")).toBeDefined();
169
- },
170
- { timeout: 3000 },
171
- );
163
+ await waitFor(() => {
164
+ expect(screen.queryByText("Say hello")).toBeNull();
165
+ expect(screen.queryByText("Get help")).toBeNull();
166
+ });
172
167
 
173
168
  agent.emit(runFinishedEvent());
174
169
  agent.complete();
@@ -177,7 +172,6 @@ describe("CopilotChat - static suggestions with available:'always'", () => {
177
172
  expect(screen.getByText("Hello! How can I help?")).toBeDefined();
178
173
  });
179
174
 
180
- // And still visible after the run completes
181
175
  await waitFor(
182
176
  () => {
183
177
  expect(screen.getByText("Say hello")).toBeDefined();
@@ -106,22 +106,37 @@ export function useConfigureSuggestions(
106
106
  const isGlobalConfig =
107
107
  rawConsumerAgentId === undefined || rawConsumerAgentId === "*";
108
108
 
109
+ const isDynamicConfigType = useMemo(
110
+ () => !!normalizedConfig && "instructions" in normalizedConfig,
111
+ [normalizedConfig],
112
+ );
113
+
109
114
  const requestReload = useCallback(() => {
110
115
  if (!normalizedConfig) {
111
116
  return;
112
117
  }
113
118
 
114
119
  if (isGlobalConfig) {
120
+ const seen = new Set<string>();
115
121
  const agents = Object.values(copilotkit.agents ?? {});
116
122
  for (const entry of agents) {
117
123
  const agentId = entry.agentId;
118
124
  if (!agentId) {
119
125
  continue;
120
126
  }
127
+ seen.add(agentId);
121
128
  if (!entry.isRunning) {
122
129
  copilotkit.reloadSuggestions(agentId);
123
130
  }
124
131
  }
132
+ // Also reload for the chat's resolved consumer agent. The registry can
133
+ // be empty at this point (e.g. runtime info still loading), in which
134
+ // case the loop above wouldn't have fired for the agent the user is
135
+ // actually chatting with — and the welcome screen would render with
136
+ // no suggestions until they navigate away and back.
137
+ if (targetAgentId && !seen.has(targetAgentId)) {
138
+ copilotkit.reloadSuggestions(targetAgentId);
139
+ }
125
140
  return;
126
141
  }
127
142
 
@@ -169,6 +184,34 @@ export function useConfigureSuggestions(
169
184
  }
170
185
  requestReload();
171
186
  }, [extraDeps.length, normalizedConfig, requestReload, ...extraDeps]);
187
+
188
+ // When agents arrive after the initial render (runtime info just landed),
189
+ // re-request a reload so dynamic configs that need a real agent can finally
190
+ // generate. Skip for static configs — they don't need an agent and the
191
+ // initial mount reload already handled them. Skip when the target agent
192
+ // is already in the registry — the initial reload already covered it, and
193
+ // re-firing on every subsequent `onAgentsChanged` (e.g. dev-mode hot
194
+ // reloads, sibling chat configs mounting) would stack overlapping
195
+ // generations.
196
+ useEffect(() => {
197
+ if (!normalizedConfig || !isDynamicConfigType) return;
198
+ if (!targetAgentId) return;
199
+
200
+ const initiallyPresent = !!copilotkit.getAgent(targetAgentId);
201
+ if (initiallyPresent) return;
202
+
203
+ const subscription = copilotkit.subscribe({
204
+ onAgentsChanged: () => {
205
+ if (copilotkit.getAgent(targetAgentId)) {
206
+ requestReload();
207
+ subscription.unsubscribe();
208
+ }
209
+ },
210
+ });
211
+ return () => {
212
+ subscription.unsubscribe();
213
+ };
214
+ }, [copilotkit, normalizedConfig, isDynamicConfigType, targetAgentId, requestReload]);
172
215
  }
173
216
 
174
217
  function isDynamicConfig(