@copilotkit/react-core 1.55.0-next.7 → 1.55.0-next.9
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/CHANGELOG.md +23 -0
- package/dist/{copilotkit-B3Mb1yVE.cjs → copilotkit-BDNjFNmk.cjs} +156 -92
- package/dist/copilotkit-BDNjFNmk.cjs.map +1 -0
- package/dist/{copilotkit-Dy5w3qEV.d.mts → copilotkit-BqcyhQjT.d.mts} +3 -1
- package/dist/{copilotkit-Dy5w3qEV.d.mts.map → copilotkit-BqcyhQjT.d.mts.map} +1 -1
- package/dist/{copilotkit-DNYSFuz5.mjs → copilotkit-DeOzjPsb.mjs} +157 -93
- package/dist/copilotkit-DeOzjPsb.mjs.map +1 -0
- package/dist/{copilotkit-DBzgOMby.d.cts → copilotkit-l-IBF4Xp.d.cts} +3 -1
- package/dist/{copilotkit-DBzgOMby.d.cts.map → copilotkit-l-IBF4Xp.d.cts.map} +1 -1
- package/dist/index.cjs +9 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +9 -4
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +160 -94
- package/dist/index.umd.js.map +1 -1
- package/dist/v2/index.cjs +1 -1
- package/dist/v2/index.d.cts +1 -1
- package/dist/v2/index.d.mts +1 -1
- package/dist/v2/index.mjs +1 -1
- package/dist/v2/index.umd.js +160 -94
- package/dist/v2/index.umd.js.map +1 -1
- package/package.json +6 -6
- package/src/components/copilot-provider/__tests__/copilot-messages-key.test.tsx +92 -0
- package/src/components/copilot-provider/copilotkit.tsx +3 -3
- package/src/hooks/__tests__/use-copilot-chat-internal-connect.test.tsx +27 -16
- package/src/hooks/use-copilot-chat_internal.ts +15 -4
- package/src/v2/__tests__/utils/test-helpers.tsx +40 -7
- package/src/v2/components/chat/CopilotChat.tsx +4 -2
- package/src/v2/components/chat/CopilotChatMessageView.tsx +7 -2
- package/src/v2/components/chat/__tests__/CopilotChatToolRendering.e2e.test.tsx +5 -2
- package/src/v2/components/chat/__tests__/CopilotChatToolRerenders.e2e.test.tsx +5 -2
- package/src/v2/components/chat/__tests__/MCPAppsActivityRenderer.e2e.test.tsx +17 -1
- package/src/v2/hooks/__tests__/use-agent-context-timing.e2e.test.tsx +8 -0
- package/src/v2/hooks/__tests__/use-agent-thread-isolation.test.tsx +327 -0
- package/src/v2/hooks/__tests__/use-agent.e2e.test.tsx +13 -2
- package/src/v2/hooks/__tests__/use-frontend-tool.e2e.test.tsx +23 -4
- package/src/v2/hooks/use-agent.tsx +126 -6
- package/src/v2/hooks/use-render-custom-messages.tsx +6 -1
- package/dist/copilotkit-B3Mb1yVE.cjs.map +0 -1
- package/dist/copilotkit-DNYSFuz5.mjs.map +0 -1
package/package.json
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"publishConfig": {
|
|
11
11
|
"access": "public"
|
|
12
12
|
},
|
|
13
|
-
"version": "1.55.0-next.
|
|
13
|
+
"version": "1.55.0-next.9",
|
|
14
14
|
"sideEffects": [
|
|
15
15
|
"**/*.css"
|
|
16
16
|
],
|
|
@@ -86,11 +86,11 @@
|
|
|
86
86
|
"untruncate-json": "^0.0.1",
|
|
87
87
|
"use-stick-to-bottom": "^1.1.1",
|
|
88
88
|
"rxjs": "7.8.1",
|
|
89
|
-
"@copilotkit/a2ui-renderer": "1.55.0-next.
|
|
90
|
-
"@copilotkit/core": "1.55.0-next.
|
|
91
|
-
"@copilotkit/runtime-client-gql": "1.55.0-next.
|
|
92
|
-
"@copilotkit/shared": "1.55.0-next.
|
|
93
|
-
"@copilotkit/web-inspector": "1.55.0-next.
|
|
89
|
+
"@copilotkit/a2ui-renderer": "1.55.0-next.9",
|
|
90
|
+
"@copilotkit/core": "1.55.0-next.9",
|
|
91
|
+
"@copilotkit/runtime-client-gql": "1.55.0-next.9",
|
|
92
|
+
"@copilotkit/shared": "1.55.0-next.9",
|
|
93
|
+
"@copilotkit/web-inspector": "1.55.0-next.9"
|
|
94
94
|
},
|
|
95
95
|
"keywords": [
|
|
96
96
|
"copilotkit",
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { render } from "@testing-library/react";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Regression test: CopilotMessages children must have React keys.
|
|
7
|
+
*
|
|
8
|
+
* When CopilotMessages receives multiple children (e.g. memoizedChildren +
|
|
9
|
+
* RegisteredActionsRenderer), React treats them as a dynamic list and warns
|
|
10
|
+
* if they lack keys. This test verifies the fix by rendering a minimal
|
|
11
|
+
* reproduction using the same pattern as CopilotKitInternal.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
// Minimal stand-in for CopilotMessages – renders children inside a provider-like wrapper.
|
|
15
|
+
function CopilotMessages({ children }: { children: React.ReactNode }) {
|
|
16
|
+
const memoized = React.useMemo(() => children, [children]);
|
|
17
|
+
return <div data-testid="messages">{memoized}</div>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe("CopilotMessages children keys", () => {
|
|
21
|
+
let consoleErrorSpy: ReturnType<typeof vi.spyOn>;
|
|
22
|
+
const keyWarnings: string[] = [];
|
|
23
|
+
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
keyWarnings.length = 0;
|
|
26
|
+
consoleErrorSpy = vi
|
|
27
|
+
.spyOn(console, "error")
|
|
28
|
+
.mockImplementation((...args: any[]) => {
|
|
29
|
+
const msg = args.map(String).join(" ");
|
|
30
|
+
if (
|
|
31
|
+
msg.includes('unique "key" prop') ||
|
|
32
|
+
msg.includes("unique 'key' prop") ||
|
|
33
|
+
msg.includes("unique key")
|
|
34
|
+
) {
|
|
35
|
+
keyWarnings.push(msg);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
afterEach(() => {
|
|
41
|
+
consoleErrorSpy.mockRestore();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("warns about missing keys when children array lacks keys (pre-fix pattern)", () => {
|
|
45
|
+
const ChildA = () => <div>app content</div>;
|
|
46
|
+
const ChildB = () => <span>actions</span>;
|
|
47
|
+
|
|
48
|
+
// Passing an explicit array as children – this is what JSX compiles to
|
|
49
|
+
// when you write: <CopilotMessages>{memoizedChildren}<RegisteredActionsRenderer /></CopilotMessages>
|
|
50
|
+
// React sees: { children: [memoizedChildren, <RegisteredActionsRenderer />] }
|
|
51
|
+
render(<CopilotMessages>{[<ChildA />, <ChildB />]}</CopilotMessages>);
|
|
52
|
+
|
|
53
|
+
expect(keyWarnings.length).toBeGreaterThan(0);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("does NOT warn when children array elements have keys (post-fix pattern)", () => {
|
|
57
|
+
const ChildA = () => <div>app content</div>;
|
|
58
|
+
const ChildB = () => <span>actions</span>;
|
|
59
|
+
|
|
60
|
+
// The fix: wrap in keyed elements
|
|
61
|
+
render(
|
|
62
|
+
<CopilotMessages>
|
|
63
|
+
{[
|
|
64
|
+
<React.Fragment key="children">
|
|
65
|
+
<ChildA />
|
|
66
|
+
</React.Fragment>,
|
|
67
|
+
<ChildB key="actions" />,
|
|
68
|
+
]}
|
|
69
|
+
</CopilotMessages>,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
expect(keyWarnings).toHaveLength(0);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("does NOT warn with the actual fix pattern (keyed JSX children)", () => {
|
|
76
|
+
const MemoChildren = React.memo(() => <div>app content</div>);
|
|
77
|
+
MemoChildren.displayName = "MemoChildren";
|
|
78
|
+
const RegisteredActionsRenderer = React.memo(() => null);
|
|
79
|
+
RegisteredActionsRenderer.displayName = "RegisteredActionsRenderer";
|
|
80
|
+
|
|
81
|
+
render(
|
|
82
|
+
<CopilotMessages>
|
|
83
|
+
<React.Fragment key="children">
|
|
84
|
+
<MemoChildren />
|
|
85
|
+
</React.Fragment>
|
|
86
|
+
<RegisteredActionsRenderer key="actions" />
|
|
87
|
+
</CopilotMessages>,
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
expect(keyWarnings).toHaveLength(0);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* ```
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import {
|
|
17
|
+
import React, {
|
|
18
18
|
useCallback,
|
|
19
19
|
useEffect,
|
|
20
20
|
useMemo,
|
|
@@ -761,8 +761,8 @@ export function CopilotKitInternal(cpkProps: CopilotKitProps) {
|
|
|
761
761
|
<CoAgentStateRendersProvider>
|
|
762
762
|
<MessagesTapProvider>
|
|
763
763
|
<CopilotMessages>
|
|
764
|
-
{memoizedChildren}
|
|
765
|
-
<RegisteredActionsRenderer />
|
|
764
|
+
<React.Fragment key="children">{memoizedChildren}</React.Fragment>
|
|
765
|
+
<RegisteredActionsRenderer key="actions" />
|
|
766
766
|
</CopilotMessages>
|
|
767
767
|
</MessagesTapProvider>
|
|
768
768
|
{bannerError && showDevConsole && (
|
|
@@ -178,18 +178,32 @@ describe("useCopilotChatInternal – connectAgent guard", () => {
|
|
|
178
178
|
});
|
|
179
179
|
});
|
|
180
180
|
|
|
181
|
-
it("does not call connectAgent when threadId matches agent
|
|
181
|
+
it("does not call connectAgent when threadId matches (same agent instance, no re-render)", async () => {
|
|
182
|
+
// useAgent now returns a per-thread clone, so the wrapper guards via
|
|
183
|
+
// lastConnectedAgentRef: connect fires once per agent instance, not once
|
|
184
|
+
// per render. After the first connect, further re-renders with the same
|
|
185
|
+
// agent do not trigger another connect.
|
|
182
186
|
mockRuntimeConnectionStatus =
|
|
183
187
|
CopilotKitCoreRuntimeConnectionStatus.Connected;
|
|
184
|
-
mockAgent.threadId = "config-thread-id";
|
|
188
|
+
mockAgent.threadId = "config-thread-id";
|
|
185
189
|
applyMocks();
|
|
186
190
|
|
|
187
|
-
renderHook(() => useCopilotChatInternal(), {
|
|
191
|
+
const { rerender } = renderHook(() => useCopilotChatInternal(), {
|
|
192
|
+
wrapper: createWrapper(),
|
|
193
|
+
});
|
|
188
194
|
|
|
189
|
-
|
|
195
|
+
await vi.waitFor(() => {
|
|
196
|
+
expect(mockConnectAgent).toHaveBeenCalledTimes(1);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Re-render with same agent — should NOT connect again
|
|
200
|
+
rerender();
|
|
201
|
+
await vi.waitFor(() => {
|
|
202
|
+
expect(mockConnectAgent).toHaveBeenCalledTimes(1);
|
|
203
|
+
});
|
|
190
204
|
});
|
|
191
205
|
|
|
192
|
-
it("
|
|
206
|
+
it("calls connectAgent when config threadId is missing", async () => {
|
|
193
207
|
mockRuntimeConnectionStatus =
|
|
194
208
|
CopilotKitCoreRuntimeConnectionStatus.Connected;
|
|
195
209
|
mockConfigThreadId = undefined;
|
|
@@ -197,10 +211,12 @@ describe("useCopilotChatInternal – connectAgent guard", () => {
|
|
|
197
211
|
|
|
198
212
|
renderHook(() => useCopilotChatInternal(), { wrapper: createWrapper() });
|
|
199
213
|
|
|
200
|
-
|
|
214
|
+
await vi.waitFor(() => {
|
|
215
|
+
expect(mockConnectAgent).toHaveBeenCalledTimes(1);
|
|
216
|
+
});
|
|
201
217
|
});
|
|
202
218
|
|
|
203
|
-
it("calls connectAgent when
|
|
219
|
+
it("calls connectAgent when status is Connected and threadIds differ", async () => {
|
|
204
220
|
mockRuntimeConnectionStatus =
|
|
205
221
|
CopilotKitCoreRuntimeConnectionStatus.Connected;
|
|
206
222
|
mockAgent.threadId = "old-thread-id"; // differs from config
|
|
@@ -214,18 +230,13 @@ describe("useCopilotChatInternal – connectAgent guard", () => {
|
|
|
214
230
|
});
|
|
215
231
|
});
|
|
216
232
|
|
|
217
|
-
it("
|
|
218
|
-
mockRuntimeConnectionStatus =
|
|
219
|
-
CopilotKitCoreRuntimeConnectionStatus.Connected;
|
|
220
|
-
mockAgent.threadId = "old-thread-id";
|
|
233
|
+
it("passes config threadId to useAgent", () => {
|
|
221
234
|
applyMocks();
|
|
222
235
|
|
|
223
236
|
renderHook(() => useCopilotChatInternal(), { wrapper: createWrapper() });
|
|
224
237
|
|
|
225
|
-
|
|
226
|
-
expect
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
expect(mockAgent.threadId).toBe("config-thread-id");
|
|
238
|
+
expect(vi.mocked(useAgent)).toHaveBeenCalledWith(
|
|
239
|
+
expect.objectContaining({ threadId: "config-thread-id" }),
|
|
240
|
+
);
|
|
230
241
|
});
|
|
231
242
|
});
|
|
@@ -337,7 +337,17 @@ export function useCopilotChatInternal({
|
|
|
337
337
|
|
|
338
338
|
// Apply priority: props > existing config > defaults
|
|
339
339
|
const resolvedAgentId = existingConfig?.agentId ?? "default";
|
|
340
|
-
const { agent } = useAgent({
|
|
340
|
+
const { agent } = useAgent({
|
|
341
|
+
agentId: resolvedAgentId,
|
|
342
|
+
threadId: existingConfig?.threadId,
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
// Track the last agent instance we called connect() on. Without this,
|
|
346
|
+
// connect() fires on every render where status is Connected — including
|
|
347
|
+
// unrelated context re-renders and StrictMode double-invocations.
|
|
348
|
+
// The ref is reset in the cleanup so that remounts (StrictMode, real
|
|
349
|
+
// unmount/remount) always trigger a fresh connect.
|
|
350
|
+
const lastConnectedAgentRef = useRef<AbstractAgent | null>(null);
|
|
341
351
|
|
|
342
352
|
useEffect(() => {
|
|
343
353
|
let detached = false;
|
|
@@ -372,18 +382,19 @@ export function useCopilotChatInternal({
|
|
|
372
382
|
};
|
|
373
383
|
if (
|
|
374
384
|
agent &&
|
|
375
|
-
|
|
376
|
-
agent.threadId !== existingConfig.threadId &&
|
|
385
|
+
agent !== lastConnectedAgentRef.current &&
|
|
377
386
|
copilotkit.runtimeConnectionStatus ===
|
|
378
387
|
CopilotKitCoreRuntimeConnectionStatus.Connected
|
|
379
388
|
) {
|
|
380
|
-
|
|
389
|
+
lastConnectedAgentRef.current = agent;
|
|
381
390
|
connect(agent);
|
|
382
391
|
}
|
|
383
392
|
return () => {
|
|
384
393
|
// Abort the HTTP request and detach the active run.
|
|
385
394
|
// This is critical for React StrictMode which unmounts+remounts in dev,
|
|
386
395
|
// preventing duplicate /connect requests from reaching the server.
|
|
396
|
+
// Reset the ref so remounts always trigger a fresh connect.
|
|
397
|
+
lastConnectedAgentRef.current = null;
|
|
387
398
|
detached = true;
|
|
388
399
|
connectAbortController.abort();
|
|
389
400
|
agent?.detachActiveRun();
|
|
@@ -51,9 +51,18 @@ export class MockStepwiseAgent extends AbstractAgent {
|
|
|
51
51
|
});
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
clone():
|
|
55
|
-
//
|
|
56
|
-
|
|
54
|
+
clone(): this {
|
|
55
|
+
// Return a new instance that shares the same subject so tests can keep
|
|
56
|
+
// controlling events via the original reference while satisfying the
|
|
57
|
+
// clone() contract (must return a distinct object).
|
|
58
|
+
// Use the concrete constructor so subclasses (e.g. FailingConnectAgent)
|
|
59
|
+
// preserve their overridden methods.
|
|
60
|
+
const cloned = new (this
|
|
61
|
+
.constructor as new () => MockStepwiseAgent)() as this;
|
|
62
|
+
cloned.agentId = this.agentId;
|
|
63
|
+
(cloned as unknown as { subject: Subject<BaseEvent> }).subject =
|
|
64
|
+
this.subject;
|
|
65
|
+
return cloned;
|
|
57
66
|
}
|
|
58
67
|
|
|
59
68
|
// No-op: prevent the base class from tearing down the Subject
|
|
@@ -110,7 +119,21 @@ export class MockReconnectableAgent extends AbstractAgent {
|
|
|
110
119
|
}
|
|
111
120
|
|
|
112
121
|
clone(): MockReconnectableAgent {
|
|
113
|
-
|
|
122
|
+
const cloned = new MockReconnectableAgent();
|
|
123
|
+
cloned.agentId = this.agentId;
|
|
124
|
+
(
|
|
125
|
+
cloned as unknown as {
|
|
126
|
+
subject: Subject<BaseEvent>;
|
|
127
|
+
storedEvents: BaseEvent[];
|
|
128
|
+
}
|
|
129
|
+
).subject = this.subject;
|
|
130
|
+
(
|
|
131
|
+
cloned as unknown as {
|
|
132
|
+
subject: Subject<BaseEvent>;
|
|
133
|
+
storedEvents: BaseEvent[];
|
|
134
|
+
}
|
|
135
|
+
).storedEvents = this.storedEvents;
|
|
136
|
+
return cloned;
|
|
114
137
|
}
|
|
115
138
|
|
|
116
139
|
// No-op: prevent the base class from tearing down the Subject
|
|
@@ -440,10 +463,20 @@ export function emitSuggestionToolCall(
|
|
|
440
463
|
* A MockStepwiseAgent that emits suggestion events when run() is called
|
|
441
464
|
*/
|
|
442
465
|
export class SuggestionsProviderAgent extends MockStepwiseAgent {
|
|
443
|
-
|
|
466
|
+
// Shared via a container so clone() and original see the same value even
|
|
467
|
+
// when setSuggestions() is called after the clone is created.
|
|
468
|
+
private _shared: { suggestions: Array<{ title: string; message: string }> } =
|
|
469
|
+
{ suggestions: [] };
|
|
444
470
|
|
|
445
471
|
setSuggestions(suggestions: Array<{ title: string; message: string }>) {
|
|
446
|
-
this.
|
|
472
|
+
this._shared.suggestions = suggestions;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
clone(): this {
|
|
476
|
+
const cloned = super.clone();
|
|
477
|
+
(cloned as unknown as { _shared: typeof this._shared })._shared =
|
|
478
|
+
this._shared;
|
|
479
|
+
return cloned;
|
|
447
480
|
}
|
|
448
481
|
|
|
449
482
|
run(_input: RunAgentInput): Observable<BaseEvent> {
|
|
@@ -458,7 +491,7 @@ export class SuggestionsProviderAgent extends MockStepwiseAgent {
|
|
|
458
491
|
emitSuggestionToolCall(this, {
|
|
459
492
|
toolCallId: testId("tc"),
|
|
460
493
|
parentMessageId: messageId,
|
|
461
|
-
suggestions: this.
|
|
494
|
+
suggestions: this._shared.suggestions,
|
|
462
495
|
});
|
|
463
496
|
|
|
464
497
|
this.emit({ type: EventType.RUN_FINISHED } as BaseEvent);
|
|
@@ -71,7 +71,10 @@ export function CopilotChat({
|
|
|
71
71
|
[threadId, existingConfig?.threadId],
|
|
72
72
|
);
|
|
73
73
|
|
|
74
|
-
const { agent } = useAgent({
|
|
74
|
+
const { agent } = useAgent({
|
|
75
|
+
agentId: resolvedAgentId,
|
|
76
|
+
threadId: resolvedThreadId,
|
|
77
|
+
});
|
|
75
78
|
const { copilotkit } = useCopilotKit();
|
|
76
79
|
const { suggestions: autoSuggestions } = useSuggestions({
|
|
77
80
|
agentId: resolvedAgentId,
|
|
@@ -164,7 +167,6 @@ export function CopilotChat({
|
|
|
164
167
|
console.error("CopilotChat: connectAgent failed", error);
|
|
165
168
|
}
|
|
166
169
|
};
|
|
167
|
-
agent.threadId = resolvedThreadId;
|
|
168
170
|
connect(agent);
|
|
169
171
|
return () => {
|
|
170
172
|
// Abort the HTTP request and detach the active run.
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from "@ag-ui/core";
|
|
13
13
|
import { twMerge } from "tailwind-merge";
|
|
14
14
|
import { useRenderActivityMessage, useRenderCustomMessages } from "../../hooks";
|
|
15
|
+
import { getThreadClone } from "../../hooks/use-agent";
|
|
15
16
|
import { useCopilotKit } from "../../providers/CopilotKitProvider";
|
|
16
17
|
import { useCopilotChatConfiguration } from "../../providers/CopilotChatConfigurationProvider";
|
|
17
18
|
|
|
@@ -313,14 +314,18 @@ export function CopilotChatMessageView({
|
|
|
313
314
|
// Subscribe to state changes so custom message renderers re-render when state updates.
|
|
314
315
|
useEffect(() => {
|
|
315
316
|
if (!config?.agentId) return;
|
|
316
|
-
const
|
|
317
|
+
const registryAgent = copilotkit.getAgent(config.agentId);
|
|
318
|
+
// Prefer the per-thread clone so that state changes from the running agent
|
|
319
|
+
// (which is the clone, not the registry) trigger re-renders.
|
|
320
|
+
const agent =
|
|
321
|
+
getThreadClone(registryAgent, config.threadId) ?? registryAgent;
|
|
317
322
|
if (!agent) return;
|
|
318
323
|
|
|
319
324
|
const subscription = agent.subscribe({
|
|
320
325
|
onStateChanged: forceUpdate,
|
|
321
326
|
});
|
|
322
327
|
return () => subscription.unsubscribe();
|
|
323
|
-
}, [config?.agentId, copilotkit, forceUpdate]);
|
|
328
|
+
}, [config?.agentId, config?.threadId, copilotkit, forceUpdate]);
|
|
324
329
|
|
|
325
330
|
// Subscribe to interrupt element changes for in-chat rendering.
|
|
326
331
|
const [interruptElement, setInterruptElement] =
|
|
@@ -250,8 +250,11 @@ class MockStepwiseAgent extends AbstractAgent {
|
|
|
250
250
|
}
|
|
251
251
|
|
|
252
252
|
clone(): MockStepwiseAgent {
|
|
253
|
-
|
|
254
|
-
|
|
253
|
+
const cloned = new MockStepwiseAgent();
|
|
254
|
+
cloned.agentId = this.agentId;
|
|
255
|
+
(cloned as unknown as { subject: Subject<BaseEvent> }).subject =
|
|
256
|
+
this.subject;
|
|
257
|
+
return cloned;
|
|
255
258
|
}
|
|
256
259
|
|
|
257
260
|
async detachActiveRun(): Promise<void> {}
|
|
@@ -52,8 +52,11 @@ class MockStepwiseAgent extends AbstractAgent {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
clone(): MockStepwiseAgent {
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
const cloned = new MockStepwiseAgent();
|
|
56
|
+
cloned.agentId = this.agentId;
|
|
57
|
+
(cloned as unknown as { subject: Subject<BaseEvent> }).subject =
|
|
58
|
+
this.subject;
|
|
59
|
+
return cloned;
|
|
57
60
|
}
|
|
58
61
|
|
|
59
62
|
async detachActiveRun(): Promise<void> {}
|
|
@@ -78,7 +78,23 @@ class MockMCPProxyAgent extends AbstractAgent {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
clone(): MockMCPProxyAgent {
|
|
81
|
-
|
|
81
|
+
const cloned = new MockMCPProxyAgent();
|
|
82
|
+
cloned.agentId = this.agentId;
|
|
83
|
+
type Internal = {
|
|
84
|
+
subject: Subject<BaseEvent>;
|
|
85
|
+
runAgentCalls: Array<{ input: Partial<RunAgentInput> }>;
|
|
86
|
+
runAgentResponses: Map<string, unknown>;
|
|
87
|
+
};
|
|
88
|
+
(cloned as unknown as Internal).subject = (
|
|
89
|
+
this as unknown as Internal
|
|
90
|
+
).subject;
|
|
91
|
+
(cloned as unknown as Internal).runAgentCalls = (
|
|
92
|
+
this as unknown as Internal
|
|
93
|
+
).runAgentCalls;
|
|
94
|
+
(cloned as unknown as Internal).runAgentResponses = (
|
|
95
|
+
this as unknown as Internal
|
|
96
|
+
).runAgentResponses;
|
|
97
|
+
return cloned;
|
|
82
98
|
}
|
|
83
99
|
|
|
84
100
|
async detachActiveRun(): Promise<void> {}
|
|
@@ -42,8 +42,16 @@ describe("useAgentContext timing - follow-up run sees updated context", () => {
|
|
|
42
42
|
* with no new messages — which is fine; we only need to capture context.
|
|
43
43
|
*/
|
|
44
44
|
class ContextCapturingAgent extends MockStepwiseAgent {
|
|
45
|
+
// Shared so the clone and original both see the captured contexts
|
|
45
46
|
public contextPerRun: Context[][] = [];
|
|
46
47
|
|
|
48
|
+
clone(): this {
|
|
49
|
+
const cloned = super.clone();
|
|
50
|
+
(cloned as unknown as ContextCapturingAgent).contextPerRun =
|
|
51
|
+
this.contextPerRun;
|
|
52
|
+
return cloned;
|
|
53
|
+
}
|
|
54
|
+
|
|
47
55
|
async runAgent(
|
|
48
56
|
parameters?: RunAgentParameters,
|
|
49
57
|
subscriber?: AgentSubscriber,
|