@assistant-ui/eve 0.0.24 → 0.0.26
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/dist/convertEveMessages.d.ts +40 -51
- package/dist/convertEveMessages.d.ts.map +1 -1
- package/dist/convertEveMessages.js +146 -117
- package/dist/convertEveMessages.js.map +1 -1
- package/dist/deriveCreatedAt.d.ts +9 -9
- package/dist/deriveCreatedAt.d.ts.map +1 -1
- package/dist/deriveCreatedAt.js +25 -34
- package/dist/deriveCreatedAt.js.map +1 -1
- package/dist/eveCloudSessions.js +1 -2
- package/dist/eveExtras.d.ts +4 -2
- package/dist/eveExtras.d.ts.map +1 -1
- package/dist/eveExtras.js +1 -2
- package/dist/eveExtras.js.map +1 -1
- package/dist/hooks.d.ts +4 -3
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +5 -5
- package/dist/hooks.js.map +1 -1
- package/dist/sdkIdentity.js +2 -3
- package/dist/useEveAgentRuntime.d.ts +3 -5
- package/dist/useEveAgentRuntime.d.ts.map +1 -1
- package/dist/useEveAgentRuntime.js +34 -23
- package/dist/useEveAgentRuntime.js.map +1 -1
- package/package.json +10 -10
- package/src/convertEveMessages.test.ts +925 -729
- package/src/convertEveMessages.ts +282 -227
- package/src/deriveCreatedAt.test.ts +90 -88
- package/src/deriveCreatedAt.ts +32 -36
- package/src/eveExtras.ts +4 -4
- package/src/hooks.ts +4 -3
- package/src/testUtils.ts +189 -8
- package/src/useEveAgentRuntime.cloud.test.tsx +105 -136
- package/src/useEveAgentRuntime.eveResume.test.tsx +22 -104
- package/src/useEveAgentRuntime.fast-refresh.test.tsx +195 -37
- package/src/useEveAgentRuntime.interruptedTurn.test.tsx +86 -40
- package/src/useEveAgentRuntime.refetch.test.tsx +19 -58
- package/src/useEveAgentRuntime.test.tsx +372 -291
- package/src/useEveAgentRuntime.ts +100 -113
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
// @vitest-environment jsdom
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
act,
|
|
5
|
+
render,
|
|
6
|
+
renderHook,
|
|
7
|
+
waitFor as waitForAssertion,
|
|
8
|
+
} from "@testing-library/react";
|
|
4
9
|
import {
|
|
5
10
|
startTransition,
|
|
6
11
|
Suspense,
|
|
@@ -18,12 +23,24 @@ vi.mock("eve/react", async (importOriginal) => ({
|
|
|
18
23
|
useEveAgent: mockUseEveAgent,
|
|
19
24
|
}));
|
|
20
25
|
|
|
21
|
-
import type {
|
|
22
|
-
import
|
|
26
|
+
import type { SessionEvent, SessionStreamEvent } from "eve/client";
|
|
27
|
+
import { activeTurn, emptySessionView, foldEvents, idle } from "eve/events";
|
|
28
|
+
import type { EveMessage } from "eve/react";
|
|
29
|
+
import {
|
|
30
|
+
createEveDelivery,
|
|
31
|
+
createEveDeliveryAfter,
|
|
32
|
+
createEveEvents,
|
|
33
|
+
} from "./testUtils";
|
|
34
|
+
|
|
23
35
|
import { useEveAgentRuntime } from "./useEveAgentRuntime";
|
|
24
36
|
import { eveExtras } from "./eveExtras";
|
|
25
37
|
|
|
26
|
-
|
|
38
|
+
type MessageFixture = { messages: readonly EveMessage[] };
|
|
39
|
+
|
|
40
|
+
const waitFor = (assertion: () => void) =>
|
|
41
|
+
waitForAssertion(assertion, { interval: 10 });
|
|
42
|
+
|
|
43
|
+
const stuckStreamingData: MessageFixture = {
|
|
27
44
|
messages: [
|
|
28
45
|
{ id: "u1", role: "user", parts: [{ type: "text", text: "hi" }] },
|
|
29
46
|
{
|
|
@@ -35,18 +52,34 @@ const stuckStreamingData: EveMessageData = {
|
|
|
35
52
|
],
|
|
36
53
|
};
|
|
37
54
|
|
|
38
|
-
const createAgent = (overrides: Record<string, unknown>) =>
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
55
|
+
const createAgent = (overrides: Record<string, unknown>) => {
|
|
56
|
+
const { send, respond, ...other } = overrides;
|
|
57
|
+
const invoke = (handler: unknown, args: unknown[]) => {
|
|
58
|
+
if (typeof handler !== "function")
|
|
59
|
+
return Promise.resolve(createEveDelivery());
|
|
60
|
+
try {
|
|
61
|
+
return Promise.resolve(
|
|
62
|
+
createEveDeliveryAfter(Promise.resolve(handler(...args))),
|
|
63
|
+
);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
return Promise.reject(error);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
return {
|
|
69
|
+
messages: stuckStreamingData.messages,
|
|
70
|
+
view: emptySessionView(),
|
|
71
|
+
error: undefined,
|
|
72
|
+
events: [],
|
|
73
|
+
session: undefined,
|
|
74
|
+
status: "idle",
|
|
75
|
+
cancel: vi.fn(async () => createEveDelivery()),
|
|
76
|
+
newSession: vi.fn(),
|
|
77
|
+
resume: vi.fn(async () => {}),
|
|
78
|
+
...other,
|
|
79
|
+
send: vi.fn((...args: unknown[]) => invoke(send, args)),
|
|
80
|
+
respond: vi.fn((...args: unknown[]) => invoke(respond, args)),
|
|
81
|
+
};
|
|
82
|
+
};
|
|
50
83
|
|
|
51
84
|
afterEach(() => {
|
|
52
85
|
vi.restoreAllMocks();
|
|
@@ -68,7 +101,7 @@ describe("useEveAgentRuntime status forwarding", () => {
|
|
|
68
101
|
if (failureMode === "throws") throw callbackError;
|
|
69
102
|
return Promise.reject(callbackError);
|
|
70
103
|
});
|
|
71
|
-
const agent = createAgent({
|
|
104
|
+
const agent = createAgent({ messages: [] });
|
|
72
105
|
let capturedOptions: Record<
|
|
73
106
|
string,
|
|
74
107
|
((value: unknown) => void) | undefined
|
|
@@ -87,7 +120,7 @@ describe("useEveAgentRuntime status forwarding", () => {
|
|
|
87
120
|
|
|
88
121
|
const value =
|
|
89
122
|
callbackName === "onFinish"
|
|
90
|
-
? { status: "
|
|
123
|
+
? { status: "idle" }
|
|
91
124
|
: callbackName === "onError"
|
|
92
125
|
? new Error("run failed")
|
|
93
126
|
: {};
|
|
@@ -104,7 +137,7 @@ describe("useEveAgentRuntime status forwarding", () => {
|
|
|
104
137
|
|
|
105
138
|
it("maps the session error onto the interrupted assistant message", () => {
|
|
106
139
|
mockUseEveAgent.mockReturnValue(
|
|
107
|
-
createAgent({ status: "
|
|
140
|
+
createAgent({ status: "idle", error: new Error("boom") }) as never,
|
|
108
141
|
);
|
|
109
142
|
|
|
110
143
|
const { result } = renderHook(() => useEveAgentRuntime());
|
|
@@ -117,7 +150,7 @@ describe("useEveAgentRuntime status forwarding", () => {
|
|
|
117
150
|
});
|
|
118
151
|
|
|
119
152
|
it("settles an aborted turn to cancelled once the agent is idle", () => {
|
|
120
|
-
mockUseEveAgent.mockReturnValue(createAgent({ status: "
|
|
153
|
+
mockUseEveAgent.mockReturnValue(createAgent({ status: "idle" }) as never);
|
|
121
154
|
|
|
122
155
|
const { result } = renderHook(() => useEveAgentRuntime());
|
|
123
156
|
|
|
@@ -128,7 +161,7 @@ describe("useEveAgentRuntime status forwarding", () => {
|
|
|
128
161
|
});
|
|
129
162
|
|
|
130
163
|
it("recomputes statuses when only the session error changes", () => {
|
|
131
|
-
const idle = createAgent({ status: "
|
|
164
|
+
const idle = createAgent({ status: "idle" });
|
|
132
165
|
mockUseEveAgent.mockReturnValue(idle as never);
|
|
133
166
|
|
|
134
167
|
const { result, rerender } = renderHook(() => useEveAgentRuntime());
|
|
@@ -140,7 +173,7 @@ describe("useEveAgentRuntime status forwarding", () => {
|
|
|
140
173
|
|
|
141
174
|
mockUseEveAgent.mockReturnValue({
|
|
142
175
|
...idle,
|
|
143
|
-
status: "
|
|
176
|
+
status: "idle",
|
|
144
177
|
error: new Error("boom"),
|
|
145
178
|
} as never);
|
|
146
179
|
rerender();
|
|
@@ -154,7 +187,7 @@ describe("useEveAgentRuntime status forwarding", () => {
|
|
|
154
187
|
|
|
155
188
|
it("forwards runConfig to eve as one-turn client context when sending", async () => {
|
|
156
189
|
const agent = createAgent({
|
|
157
|
-
|
|
190
|
+
messages: [],
|
|
158
191
|
});
|
|
159
192
|
mockUseEveAgent.mockReturnValue(agent as never);
|
|
160
193
|
|
|
@@ -177,7 +210,7 @@ describe("useEveAgentRuntime status forwarding", () => {
|
|
|
177
210
|
|
|
178
211
|
it("omits clientContext when no runConfig is provided", async () => {
|
|
179
212
|
const agent = createAgent({
|
|
180
|
-
|
|
213
|
+
messages: [],
|
|
181
214
|
});
|
|
182
215
|
mockUseEveAgent.mockReturnValue(agent as never);
|
|
183
216
|
|
|
@@ -197,7 +230,7 @@ describe("useEveAgentRuntime status forwarding", () => {
|
|
|
197
230
|
|
|
198
231
|
it("omits clientContext when runConfig.custom is empty", async () => {
|
|
199
232
|
const agent = createAgent({
|
|
200
|
-
|
|
233
|
+
messages: [],
|
|
201
234
|
});
|
|
202
235
|
mockUseEveAgent.mockReturnValue(agent as never);
|
|
203
236
|
|
|
@@ -218,7 +251,7 @@ describe("useEveAgentRuntime status forwarding", () => {
|
|
|
218
251
|
|
|
219
252
|
it("prefers the reload-time runConfig over the staged one", async () => {
|
|
220
253
|
const agent = createAgent({
|
|
221
|
-
|
|
254
|
+
messages: [],
|
|
222
255
|
});
|
|
223
256
|
mockUseEveAgent.mockReturnValue(agent as never);
|
|
224
257
|
|
|
@@ -254,7 +287,7 @@ describe("useEveAgentRuntime status forwarding", () => {
|
|
|
254
287
|
|
|
255
288
|
it("falls back to the staged runConfig when reload passes none", async () => {
|
|
256
289
|
const agent = createAgent({
|
|
257
|
-
|
|
290
|
+
messages: [],
|
|
258
291
|
});
|
|
259
292
|
mockUseEveAgent.mockReturnValue(agent as never);
|
|
260
293
|
|
|
@@ -291,7 +324,7 @@ describe("useEveAgentRuntime status forwarding", () => {
|
|
|
291
324
|
// the adapter and cannot clear the staged context.
|
|
292
325
|
it("cannot clear the staged runConfig with an explicit empty reload config", async () => {
|
|
293
326
|
const agent = createAgent({
|
|
294
|
-
|
|
327
|
+
messages: [],
|
|
295
328
|
});
|
|
296
329
|
mockUseEveAgent.mockReturnValue(agent as never);
|
|
297
330
|
|
|
@@ -323,9 +356,7 @@ describe("useEveAgentRuntime status forwarding", () => {
|
|
|
323
356
|
});
|
|
324
357
|
|
|
325
358
|
it("keeps the last assistant message running while streaming", () => {
|
|
326
|
-
mockUseEveAgent.mockReturnValue(
|
|
327
|
-
createAgent({ status: "streaming" }) as never,
|
|
328
|
-
);
|
|
359
|
+
mockUseEveAgent.mockReturnValue(createAgent({ status: "busy" }) as never);
|
|
329
360
|
|
|
330
361
|
const { result } = renderHook(() => useEveAgentRuntime());
|
|
331
362
|
|
|
@@ -336,7 +367,7 @@ describe("useEveAgentRuntime status forwarding", () => {
|
|
|
336
367
|
});
|
|
337
368
|
|
|
338
369
|
describe("useEveAgentRuntime tool approval responses", () => {
|
|
339
|
-
const textRequestData:
|
|
370
|
+
const textRequestData: MessageFixture = {
|
|
340
371
|
messages: [
|
|
341
372
|
{ id: "u1", role: "user", parts: [{ type: "text", text: "hi" }] },
|
|
342
373
|
{
|
|
@@ -344,22 +375,23 @@ describe("useEveAgentRuntime tool approval responses", () => {
|
|
|
344
375
|
role: "assistant",
|
|
345
376
|
parts: [
|
|
346
377
|
{
|
|
347
|
-
type: "
|
|
348
|
-
state: "approval
|
|
349
|
-
|
|
350
|
-
|
|
378
|
+
type: "tool",
|
|
379
|
+
state: "approval",
|
|
380
|
+
callId: "call_1",
|
|
381
|
+
stepIndex: 0,
|
|
382
|
+
kind: "tool",
|
|
383
|
+
name: "ask_question",
|
|
384
|
+
label: "ask_question",
|
|
351
385
|
input: {},
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
display: "text",
|
|
362
|
-
},
|
|
386
|
+
interaction: {
|
|
387
|
+
interactionId: "req_1",
|
|
388
|
+
turnId: "t1",
|
|
389
|
+
stepIndex: 0,
|
|
390
|
+
status: "open",
|
|
391
|
+
request: {
|
|
392
|
+
prompt: "What should the subject line be?",
|
|
393
|
+
kind: "question",
|
|
394
|
+
display: "text",
|
|
363
395
|
},
|
|
364
396
|
},
|
|
365
397
|
},
|
|
@@ -371,11 +403,6 @@ describe("useEveAgentRuntime tool approval responses", () => {
|
|
|
371
403
|
const flushMicrotasks = () =>
|
|
372
404
|
new Promise((resolve) => setTimeout(resolve, 0));
|
|
373
405
|
|
|
374
|
-
const processEvents = process as unknown as {
|
|
375
|
-
on(event: "unhandledRejection", listener: (reason: unknown) => void): void;
|
|
376
|
-
off(event: "unhandledRejection", listener: (reason: unknown) => void): void;
|
|
377
|
-
};
|
|
378
|
-
|
|
379
406
|
const respondToTextRequest = (
|
|
380
407
|
result: { current: ReturnType<typeof useEveAgentRuntime> },
|
|
381
408
|
response: { approved: boolean; reason?: string; optionId?: string },
|
|
@@ -386,14 +413,85 @@ describe("useEveAgentRuntime tool approval responses", () => {
|
|
|
386
413
|
.respondToToolApproval(response);
|
|
387
414
|
|
|
388
415
|
it("keeps an unanswered free-form request pending through the default controls", async () => {
|
|
416
|
+
const pausedEvents: readonly SessionEvent[] = [
|
|
417
|
+
{ type: "session.started", data: {} },
|
|
418
|
+
{ type: "delivery.admitted", data: { deliveryId: "d1" } },
|
|
419
|
+
{
|
|
420
|
+
type: "turn.started",
|
|
421
|
+
data: {
|
|
422
|
+
turnId: "t1",
|
|
423
|
+
cause: { deliveryId: "d1" },
|
|
424
|
+
follows: null,
|
|
425
|
+
},
|
|
426
|
+
},
|
|
427
|
+
{
|
|
428
|
+
type: "delivery.consumed",
|
|
429
|
+
data: {
|
|
430
|
+
deliveryId: "d1",
|
|
431
|
+
turnId: "t1",
|
|
432
|
+
parts: [{ kind: "text", text: "hi" }],
|
|
433
|
+
},
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
type: "interaction.opened",
|
|
437
|
+
data: {
|
|
438
|
+
interactionId: "req_1",
|
|
439
|
+
subject: { turnId: "t1" },
|
|
440
|
+
request: {
|
|
441
|
+
kind: "question",
|
|
442
|
+
prompt: "What should the subject line be?",
|
|
443
|
+
display: "text",
|
|
444
|
+
},
|
|
445
|
+
},
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
type: "turn.paused",
|
|
449
|
+
data: { turnId: "t1", awaiting: [{ interactionId: "req_1" }] },
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
type: "delivery.settled",
|
|
453
|
+
data: {
|
|
454
|
+
deliveryId: "d1",
|
|
455
|
+
turnId: "t1",
|
|
456
|
+
outcome: "awaiting-input",
|
|
457
|
+
},
|
|
458
|
+
},
|
|
459
|
+
];
|
|
460
|
+
const view = foldEvents(
|
|
461
|
+
emptySessionView(),
|
|
462
|
+
createEveEvents(
|
|
463
|
+
pausedEvents.map((event) => ({
|
|
464
|
+
at: "2026-01-02T10:00:00.000Z",
|
|
465
|
+
event,
|
|
466
|
+
})),
|
|
467
|
+
),
|
|
468
|
+
);
|
|
469
|
+
expect(activeTurn(view)?.turnId).toBe("t1");
|
|
470
|
+
expect(idle(view)).toBe(true);
|
|
389
471
|
const rejections: unknown[] = [];
|
|
390
472
|
const onUnhandledRejection = (reason: unknown) => rejections.push(reason);
|
|
473
|
+
const processEvents = process as {
|
|
474
|
+
env: typeof process.env;
|
|
475
|
+
on(
|
|
476
|
+
event: "unhandledRejection",
|
|
477
|
+
listener: (reason: unknown) => void,
|
|
478
|
+
): void;
|
|
479
|
+
off(
|
|
480
|
+
event: "unhandledRejection",
|
|
481
|
+
listener: (reason: unknown) => void,
|
|
482
|
+
): void;
|
|
483
|
+
};
|
|
391
484
|
processEvents.on("unhandledRejection", onUnhandledRejection);
|
|
392
|
-
const agent = createAgent({
|
|
485
|
+
const agent = createAgent({ messages: textRequestData.messages, view });
|
|
393
486
|
mockUseEveAgent.mockReturnValue(agent as never);
|
|
394
487
|
|
|
395
488
|
try {
|
|
396
489
|
const { result } = renderHook(() => useEveAgentRuntime());
|
|
490
|
+
expect(result.current.thread.getState().isRunning).toBe(false);
|
|
491
|
+
expect(result.current.thread.getState().messages.at(-1)?.status).toEqual({
|
|
492
|
+
type: "requires-action",
|
|
493
|
+
reason: "tool-calls",
|
|
494
|
+
});
|
|
397
495
|
|
|
398
496
|
await expect(
|
|
399
497
|
respondToTextRequest(result, { approved: true }),
|
|
@@ -410,89 +508,53 @@ describe("useEveAgentRuntime tool approval responses", () => {
|
|
|
410
508
|
}
|
|
411
509
|
});
|
|
412
510
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
messages: [
|
|
417
|
-
{ id: "u1", role: "user", parts: [{ type: "text", text: "hi" }] },
|
|
418
|
-
{
|
|
419
|
-
id: "a1",
|
|
420
|
-
role: "assistant",
|
|
421
|
-
parts: [
|
|
422
|
-
{
|
|
423
|
-
type: "dynamic-tool",
|
|
424
|
-
state: "approval-requested",
|
|
425
|
-
toolCallId: "call_1",
|
|
426
|
-
toolName: "delete_file",
|
|
427
|
-
input: {},
|
|
428
|
-
approval: { id: "req_1" },
|
|
429
|
-
...(toolMetadata && { toolMetadata }),
|
|
430
|
-
},
|
|
431
|
-
],
|
|
432
|
-
},
|
|
433
|
-
],
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
it.each([
|
|
437
|
-
["no eve tool metadata at all", undefined],
|
|
438
|
-
[
|
|
439
|
-
"eve metadata carrying no input request",
|
|
440
|
-
{ eve: { kind: "tool-call", name: "delete_file" } },
|
|
441
|
-
],
|
|
442
|
-
])(
|
|
443
|
-
"still answers an ordinary approval when the part has %s",
|
|
444
|
-
async (_label, toolMetadata) => {
|
|
445
|
-
const agent = createAgent({
|
|
446
|
-
data: bareApprovalData(toolMetadata as never),
|
|
447
|
-
});
|
|
448
|
-
mockUseEveAgent.mockReturnValue(agent as never);
|
|
511
|
+
it("submits a free-form answer as text without an option id", async () => {
|
|
512
|
+
const agent = createAgent({ messages: textRequestData.messages });
|
|
513
|
+
mockUseEveAgent.mockReturnValue(agent as never);
|
|
449
514
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
515
|
+
const { result } = renderHook(() => useEveAgentRuntime());
|
|
516
|
+
respondToTextRequest(result, {
|
|
517
|
+
approved: true,
|
|
518
|
+
reason: "Quarterly results",
|
|
519
|
+
});
|
|
454
520
|
|
|
455
|
-
|
|
521
|
+
await flushMicrotasks();
|
|
456
522
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
);
|
|
523
|
+
expect(agent.respond).toHaveBeenCalledWith([
|
|
524
|
+
{ interactionId: "req_1", text: "Quarterly results" },
|
|
525
|
+
]);
|
|
526
|
+
});
|
|
462
527
|
|
|
463
|
-
it("
|
|
464
|
-
const agent = createAgent({
|
|
528
|
+
it("submits an approval using the interaction's option id", async () => {
|
|
529
|
+
const agent = createAgent({ messages: approvalData.messages });
|
|
465
530
|
mockUseEveAgent.mockReturnValue(agent as never);
|
|
466
531
|
|
|
467
532
|
const { result } = renderHook(() => useEveAgentRuntime());
|
|
468
|
-
respondToTextRequest(result, { approved:
|
|
533
|
+
respondToTextRequest(result, { approved: true });
|
|
469
534
|
|
|
470
535
|
await flushMicrotasks();
|
|
471
536
|
|
|
472
537
|
expect(agent.respond).toHaveBeenCalledWith([
|
|
473
|
-
{
|
|
538
|
+
{ interactionId: "req_1", optionId: "approve" },
|
|
474
539
|
]);
|
|
475
540
|
});
|
|
476
541
|
|
|
477
|
-
it("
|
|
478
|
-
const agent = createAgent({
|
|
542
|
+
it("maps a refusal to eve's deny option", async () => {
|
|
543
|
+
const agent = createAgent({ messages: approvalData.messages });
|
|
479
544
|
mockUseEveAgent.mockReturnValue(agent as never);
|
|
480
545
|
|
|
481
546
|
const { result } = renderHook(() => useEveAgentRuntime());
|
|
482
|
-
respondToTextRequest(result, {
|
|
483
|
-
approved: true,
|
|
484
|
-
reason: "Quarterly results",
|
|
485
|
-
});
|
|
547
|
+
respondToTextRequest(result, { approved: false, reason: "not safe" });
|
|
486
548
|
|
|
487
549
|
await flushMicrotasks();
|
|
488
550
|
|
|
489
551
|
expect(agent.respond).toHaveBeenCalledWith([
|
|
490
|
-
{
|
|
552
|
+
{ interactionId: "req_1", optionId: "deny", text: "not safe" },
|
|
491
553
|
]);
|
|
492
554
|
});
|
|
493
555
|
});
|
|
494
556
|
|
|
495
|
-
const settledData:
|
|
557
|
+
const settledData: MessageFixture = {
|
|
496
558
|
messages: [
|
|
497
559
|
{ id: "u1", role: "user", parts: [{ type: "text", text: "earlier" }] },
|
|
498
560
|
{
|
|
@@ -503,7 +565,7 @@ const settledData: EveMessageData = {
|
|
|
503
565
|
],
|
|
504
566
|
};
|
|
505
567
|
|
|
506
|
-
const executingToolData:
|
|
568
|
+
const executingToolData: MessageFixture = {
|
|
507
569
|
messages: [
|
|
508
570
|
{ id: "u1", role: "user", parts: [{ type: "text", text: "run it" }] },
|
|
509
571
|
{
|
|
@@ -511,10 +573,13 @@ const executingToolData: EveMessageData = {
|
|
|
511
573
|
role: "assistant",
|
|
512
574
|
parts: [
|
|
513
575
|
{
|
|
514
|
-
type: "
|
|
515
|
-
state: "
|
|
516
|
-
|
|
517
|
-
|
|
576
|
+
type: "tool",
|
|
577
|
+
state: "running",
|
|
578
|
+
callId: "call_slow",
|
|
579
|
+
stepIndex: 0,
|
|
580
|
+
kind: "tool",
|
|
581
|
+
name: "slow_tool",
|
|
582
|
+
label: "slow_tool",
|
|
518
583
|
input: {},
|
|
519
584
|
},
|
|
520
585
|
],
|
|
@@ -522,7 +587,7 @@ const executingToolData: EveMessageData = {
|
|
|
522
587
|
],
|
|
523
588
|
};
|
|
524
589
|
|
|
525
|
-
const twoExecutingToolsData:
|
|
590
|
+
const twoExecutingToolsData: MessageFixture = {
|
|
526
591
|
messages: [
|
|
527
592
|
{ id: "u1", role: "user", parts: [{ type: "text", text: "run them" }] },
|
|
528
593
|
{
|
|
@@ -530,17 +595,23 @@ const twoExecutingToolsData: EveMessageData = {
|
|
|
530
595
|
role: "assistant",
|
|
531
596
|
parts: [
|
|
532
597
|
{
|
|
533
|
-
type: "
|
|
534
|
-
state: "
|
|
535
|
-
|
|
536
|
-
|
|
598
|
+
type: "tool",
|
|
599
|
+
state: "running",
|
|
600
|
+
callId: "call_slow_a",
|
|
601
|
+
stepIndex: 0,
|
|
602
|
+
kind: "tool",
|
|
603
|
+
name: "slow_tool",
|
|
604
|
+
label: "slow_tool",
|
|
537
605
|
input: {},
|
|
538
606
|
},
|
|
539
607
|
{
|
|
540
|
-
type: "
|
|
541
|
-
state: "
|
|
542
|
-
|
|
543
|
-
|
|
608
|
+
type: "tool",
|
|
609
|
+
state: "running",
|
|
610
|
+
callId: "call_slow_b",
|
|
611
|
+
stepIndex: 0,
|
|
612
|
+
kind: "tool",
|
|
613
|
+
name: "slow_tool",
|
|
614
|
+
label: "slow_tool",
|
|
544
615
|
input: {},
|
|
545
616
|
},
|
|
546
617
|
],
|
|
@@ -572,7 +643,7 @@ const stageMessage = async (
|
|
|
572
643
|
|
|
573
644
|
describe("useEveAgentRuntime staged messages", () => {
|
|
574
645
|
it("stages a user message without submitting when startRun is false", async () => {
|
|
575
|
-
const agent = createAgent({
|
|
646
|
+
const agent = createAgent({ messages: settledData.messages });
|
|
576
647
|
mockUseEveAgent.mockReturnValue(agent as never);
|
|
577
648
|
const { result } = renderHook(() => useEveAgentRuntime());
|
|
578
649
|
|
|
@@ -589,7 +660,7 @@ describe("useEveAgentRuntime staged messages", () => {
|
|
|
589
660
|
});
|
|
590
661
|
|
|
591
662
|
it("renders new turns and refloats staged drafts after a normal send", async () => {
|
|
592
|
-
const agent = createAgent({
|
|
663
|
+
const agent = createAgent({ messages: settledData.messages });
|
|
593
664
|
mockUseEveAgent.mockReturnValue(agent as never);
|
|
594
665
|
const { result, rerender } = renderHook(() => useEveAgentRuntime());
|
|
595
666
|
|
|
@@ -605,21 +676,19 @@ describe("useEveAgentRuntime staged messages", () => {
|
|
|
605
676
|
|
|
606
677
|
mockUseEveAgent.mockReturnValue(
|
|
607
678
|
createAgent({
|
|
608
|
-
|
|
609
|
-
messages
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
],
|
|
622
|
-
},
|
|
679
|
+
messages: [
|
|
680
|
+
...settledData.messages,
|
|
681
|
+
{
|
|
682
|
+
id: "u2",
|
|
683
|
+
role: "user",
|
|
684
|
+
parts: [{ type: "text", text: "hello" }],
|
|
685
|
+
},
|
|
686
|
+
{
|
|
687
|
+
id: "a2",
|
|
688
|
+
role: "assistant",
|
|
689
|
+
parts: [{ type: "text", text: "hi there" }],
|
|
690
|
+
},
|
|
691
|
+
],
|
|
623
692
|
send: agent.send,
|
|
624
693
|
}) as never,
|
|
625
694
|
);
|
|
@@ -637,7 +706,7 @@ describe("useEveAgentRuntime staged messages", () => {
|
|
|
637
706
|
});
|
|
638
707
|
|
|
639
708
|
it("keeps both drafts when two appends stage in the same batch", async () => {
|
|
640
|
-
const agent = createAgent({
|
|
709
|
+
const agent = createAgent({ messages: settledData.messages });
|
|
641
710
|
mockUseEveAgent.mockReturnValue(agent as never);
|
|
642
711
|
const { result } = renderHook(() => useEveAgentRuntime());
|
|
643
712
|
|
|
@@ -672,7 +741,7 @@ describe("useEveAgentRuntime staged messages", () => {
|
|
|
672
741
|
new Error("eve session is already processing a turn."),
|
|
673
742
|
)
|
|
674
743
|
.mockResolvedValue(undefined);
|
|
675
|
-
const agent = createAgent({
|
|
744
|
+
const agent = createAgent({ messages: settledData.messages, send });
|
|
676
745
|
mockUseEveAgent.mockReturnValue(agent as never);
|
|
677
746
|
const { result } = renderHook(() => useEveAgentRuntime());
|
|
678
747
|
|
|
@@ -717,7 +786,7 @@ describe("useEveAgentRuntime staged messages", () => {
|
|
|
717
786
|
});
|
|
718
787
|
|
|
719
788
|
it("applies the reload runConfig only to the message being reloaded", async () => {
|
|
720
|
-
const agent = createAgent({
|
|
789
|
+
const agent = createAgent({ messages: settledData.messages });
|
|
721
790
|
mockUseEveAgent.mockReturnValue(agent as never);
|
|
722
791
|
const { result } = renderHook(() => useEveAgentRuntime());
|
|
723
792
|
|
|
@@ -755,7 +824,7 @@ describe("useEveAgentRuntime staged messages", () => {
|
|
|
755
824
|
});
|
|
756
825
|
|
|
757
826
|
it("keeps later staged messages visible after promoting one staged parent", async () => {
|
|
758
|
-
const agent = createAgent({
|
|
827
|
+
const agent = createAgent({ messages: settledData.messages });
|
|
759
828
|
mockUseEveAgent.mockReturnValue(agent as never);
|
|
760
829
|
const { result } = renderHook(() => useEveAgentRuntime());
|
|
761
830
|
|
|
@@ -793,7 +862,7 @@ describe("useEveAgentRuntime staged messages", () => {
|
|
|
793
862
|
}),
|
|
794
863
|
)
|
|
795
864
|
.mockResolvedValue(undefined);
|
|
796
|
-
const agent = createAgent({
|
|
865
|
+
const agent = createAgent({ messages: settledData.messages, send });
|
|
797
866
|
mockUseEveAgent.mockReturnValue(agent as never);
|
|
798
867
|
const { result } = renderHook(() => useEveAgentRuntime());
|
|
799
868
|
|
|
@@ -835,14 +904,14 @@ describe("useEveAgentRuntime staged messages", () => {
|
|
|
835
904
|
});
|
|
836
905
|
|
|
837
906
|
it("keeps staged drafts when the session collapses to empty", async () => {
|
|
838
|
-
const agent = createAgent({
|
|
907
|
+
const agent = createAgent({ messages: settledData.messages });
|
|
839
908
|
mockUseEveAgent.mockReturnValue(agent as never);
|
|
840
909
|
const { result, rerender } = renderHook(() => useEveAgentRuntime());
|
|
841
910
|
|
|
842
911
|
await stageMessage(result.current, "staged draft");
|
|
843
912
|
|
|
844
913
|
mockUseEveAgent.mockReturnValue(
|
|
845
|
-
createAgent({
|
|
914
|
+
createAgent({ messages: [], send: agent.send }) as never,
|
|
846
915
|
);
|
|
847
916
|
rerender();
|
|
848
917
|
|
|
@@ -852,17 +921,9 @@ describe("useEveAgentRuntime staged messages", () => {
|
|
|
852
921
|
});
|
|
853
922
|
|
|
854
923
|
it("stops promoting once a promoted turn parks with an error status", async () => {
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
const send = vi.fn().mockImplementation(async () => {
|
|
859
|
-
capturedOptions.onFinish?.({ status: "error" });
|
|
860
|
-
});
|
|
861
|
-
const agent = createAgent({ data: settledData, send });
|
|
862
|
-
mockUseEveAgent.mockImplementation((options) => {
|
|
863
|
-
capturedOptions = options as typeof capturedOptions;
|
|
864
|
-
return agent as never;
|
|
865
|
-
});
|
|
924
|
+
const send = vi.fn().mockResolvedValue(createEveDelivery("failed"));
|
|
925
|
+
const agent = createAgent({ messages: settledData.messages, send });
|
|
926
|
+
mockUseEveAgent.mockReturnValue(agent as never);
|
|
866
927
|
const { result } = renderHook(() => useEveAgentRuntime());
|
|
867
928
|
|
|
868
929
|
await stageMessage(result.current, "first staged");
|
|
@@ -899,7 +960,7 @@ describe("useEveAgentRuntime staged messages", () => {
|
|
|
899
960
|
}),
|
|
900
961
|
)
|
|
901
962
|
.mockResolvedValue(undefined);
|
|
902
|
-
const agent = createAgent({
|
|
963
|
+
const agent = createAgent({ messages: settledData.messages, send });
|
|
903
964
|
mockUseEveAgent.mockReturnValue(agent as never);
|
|
904
965
|
const { result } = renderHook(() => useEveAgentRuntime());
|
|
905
966
|
|
|
@@ -934,7 +995,7 @@ describe("useEveAgentRuntime staged messages", () => {
|
|
|
934
995
|
});
|
|
935
996
|
|
|
936
997
|
it("still rejects reloading a non-staged message", async () => {
|
|
937
|
-
const agent = createAgent({
|
|
998
|
+
const agent = createAgent({ messages: settledData.messages });
|
|
938
999
|
mockUseEveAgent.mockReturnValue(agent as never);
|
|
939
1000
|
const { result } = renderHook(() => useEveAgentRuntime());
|
|
940
1001
|
|
|
@@ -959,7 +1020,7 @@ describe("useEveAgentRuntime extras wiring", () => {
|
|
|
959
1020
|
const events = [{ type: "session.started" }];
|
|
960
1021
|
const session = { sessionId: "s1" };
|
|
961
1022
|
mockUseEveAgent.mockReturnValue(
|
|
962
|
-
createAgent({ status: "
|
|
1023
|
+
createAgent({ status: "idle", error, events, session }) as never,
|
|
963
1024
|
);
|
|
964
1025
|
|
|
965
1026
|
const { result } = renderHook(() => useEveAgentRuntime());
|
|
@@ -970,7 +1031,7 @@ describe("useEveAgentRuntime extras wiring", () => {
|
|
|
970
1031
|
});
|
|
971
1032
|
|
|
972
1033
|
it("discards staged inputs when reset is invoked", async () => {
|
|
973
|
-
const agent = createAgent({
|
|
1034
|
+
const agent = createAgent({ messages: settledData.messages });
|
|
974
1035
|
mockUseEveAgent.mockReturnValue(agent as never);
|
|
975
1036
|
const { result } = renderHook(() => useEveAgentRuntime());
|
|
976
1037
|
|
|
@@ -998,11 +1059,11 @@ describe("useEveAgentRuntime extras wiring", () => {
|
|
|
998
1059
|
),
|
|
999
1060
|
).rejects.toThrow("Runtime does not support reloading messages.");
|
|
1000
1061
|
expect(agent.send).not.toHaveBeenCalled();
|
|
1001
|
-
expect(agent.
|
|
1062
|
+
expect(agent.newSession).toHaveBeenCalledTimes(1);
|
|
1002
1063
|
});
|
|
1003
1064
|
|
|
1004
1065
|
it("clears executing tool state when reset is invoked", async () => {
|
|
1005
|
-
const agent = createAgent({
|
|
1066
|
+
const agent = createAgent({ messages: settledData.messages });
|
|
1006
1067
|
mockUseEveAgent.mockReturnValue(agent as never);
|
|
1007
1068
|
const { result, rerender } = renderHook(() => useEveAgentRuntime());
|
|
1008
1069
|
|
|
@@ -1022,7 +1083,10 @@ describe("useEveAgentRuntime extras wiring", () => {
|
|
|
1022
1083
|
// The tracker treats its first snapshot as historical, so the tool call
|
|
1023
1084
|
// has to arrive on a later one to actually execute.
|
|
1024
1085
|
mockUseEveAgent.mockReturnValue(
|
|
1025
|
-
createAgent({
|
|
1086
|
+
createAgent({
|
|
1087
|
+
messages: executingToolData.messages,
|
|
1088
|
+
newSession: agent.newSession,
|
|
1089
|
+
}) as never,
|
|
1026
1090
|
);
|
|
1027
1091
|
rerender();
|
|
1028
1092
|
|
|
@@ -1037,7 +1101,7 @@ describe("useEveAgentRuntime extras wiring", () => {
|
|
|
1037
1101
|
await waitFor(() => {
|
|
1038
1102
|
expect(result.current.thread.getState().isRunning).toBe(false);
|
|
1039
1103
|
});
|
|
1040
|
-
expect(agent.
|
|
1104
|
+
expect(agent.newSession).toHaveBeenCalledTimes(1);
|
|
1041
1105
|
});
|
|
1042
1106
|
|
|
1043
1107
|
it("drops a send queued behind an active turn when reset is invoked", async () => {
|
|
@@ -1051,7 +1115,7 @@ describe("useEveAgentRuntime extras wiring", () => {
|
|
|
1051
1115
|
}),
|
|
1052
1116
|
)
|
|
1053
1117
|
.mockResolvedValue(undefined);
|
|
1054
|
-
const agent = createAgent({
|
|
1118
|
+
const agent = createAgent({ messages: settledData.messages, send });
|
|
1055
1119
|
mockUseEveAgent.mockReturnValue(agent as never);
|
|
1056
1120
|
const { result } = renderHook(() => useEveAgentRuntime());
|
|
1057
1121
|
|
|
@@ -1104,7 +1168,7 @@ describe("useEveAgentRuntime extras wiring", () => {
|
|
|
1104
1168
|
}),
|
|
1105
1169
|
)
|
|
1106
1170
|
.mockResolvedValue(undefined);
|
|
1107
|
-
const agent = createAgent({
|
|
1171
|
+
const agent = createAgent({ messages: settledData.messages, send });
|
|
1108
1172
|
mockUseEveAgent.mockReturnValue(agent as never);
|
|
1109
1173
|
const { result } = renderHook(() => useEveAgentRuntime());
|
|
1110
1174
|
|
|
@@ -1140,7 +1204,7 @@ describe("useEveAgentRuntime extras wiring", () => {
|
|
|
1140
1204
|
});
|
|
1141
1205
|
|
|
1142
1206
|
expect(send).toHaveBeenCalledTimes(1);
|
|
1143
|
-
expect(agent.
|
|
1207
|
+
expect(agent.cancel).toHaveBeenCalledTimes(1);
|
|
1144
1208
|
await waitFor(() => {
|
|
1145
1209
|
expect(getText(result.current)).toEqual([
|
|
1146
1210
|
"earlier",
|
|
@@ -1153,10 +1217,13 @@ describe("useEveAgentRuntime extras wiring", () => {
|
|
|
1153
1217
|
it("aborts client tool executions when reset is invoked", async () => {
|
|
1154
1218
|
const agentReset = vi.fn(() => {
|
|
1155
1219
|
mockUseEveAgent.mockReturnValue(
|
|
1156
|
-
createAgent({
|
|
1220
|
+
createAgent({ messages: [], newSession: agentReset }) as never,
|
|
1157
1221
|
);
|
|
1158
1222
|
});
|
|
1159
|
-
const agent = createAgent({
|
|
1223
|
+
const agent = createAgent({
|
|
1224
|
+
messages: settledData.messages,
|
|
1225
|
+
newSession: agentReset,
|
|
1226
|
+
});
|
|
1160
1227
|
mockUseEveAgent.mockReturnValue(agent as never);
|
|
1161
1228
|
const { result, rerender } = renderHook(() => useEveAgentRuntime());
|
|
1162
1229
|
|
|
@@ -1183,7 +1250,10 @@ describe("useEveAgentRuntime extras wiring", () => {
|
|
|
1183
1250
|
});
|
|
1184
1251
|
|
|
1185
1252
|
mockUseEveAgent.mockReturnValue(
|
|
1186
|
-
createAgent({
|
|
1253
|
+
createAgent({
|
|
1254
|
+
messages: twoExecutingToolsData.messages,
|
|
1255
|
+
newSession: agentReset,
|
|
1256
|
+
}) as never,
|
|
1187
1257
|
);
|
|
1188
1258
|
rerender();
|
|
1189
1259
|
|
|
@@ -1205,10 +1275,13 @@ describe("useEveAgentRuntime extras wiring", () => {
|
|
|
1205
1275
|
it("rejects a pending human-input request when reset is invoked", async () => {
|
|
1206
1276
|
const agentReset = vi.fn(() => {
|
|
1207
1277
|
mockUseEveAgent.mockReturnValue(
|
|
1208
|
-
createAgent({
|
|
1278
|
+
createAgent({ messages: [], newSession: agentReset }) as never,
|
|
1209
1279
|
);
|
|
1210
1280
|
});
|
|
1211
|
-
const agent = createAgent({
|
|
1281
|
+
const agent = createAgent({
|
|
1282
|
+
messages: settledData.messages,
|
|
1283
|
+
newSession: agentReset,
|
|
1284
|
+
});
|
|
1212
1285
|
mockUseEveAgent.mockReturnValue(agent as never);
|
|
1213
1286
|
const { result, rerender } = renderHook(() => useEveAgentRuntime());
|
|
1214
1287
|
|
|
@@ -1239,7 +1312,10 @@ describe("useEveAgentRuntime extras wiring", () => {
|
|
|
1239
1312
|
});
|
|
1240
1313
|
|
|
1241
1314
|
mockUseEveAgent.mockReturnValue(
|
|
1242
|
-
createAgent({
|
|
1315
|
+
createAgent({
|
|
1316
|
+
messages: twoExecutingToolsData.messages,
|
|
1317
|
+
newSession: agentReset,
|
|
1318
|
+
}) as never,
|
|
1243
1319
|
);
|
|
1244
1320
|
rerender();
|
|
1245
1321
|
|
|
@@ -1264,10 +1340,13 @@ describe("useEveAgentRuntime extras wiring", () => {
|
|
|
1264
1340
|
const releases: Record<string, () => void> = {};
|
|
1265
1341
|
const agentReset = vi.fn(() => {
|
|
1266
1342
|
mockUseEveAgent.mockReturnValue(
|
|
1267
|
-
createAgent({
|
|
1343
|
+
createAgent({ messages: [], newSession: agentReset }) as never,
|
|
1268
1344
|
);
|
|
1269
1345
|
});
|
|
1270
|
-
const agent = createAgent({
|
|
1346
|
+
const agent = createAgent({
|
|
1347
|
+
messages: settledData.messages,
|
|
1348
|
+
newSession: agentReset,
|
|
1349
|
+
});
|
|
1271
1350
|
mockUseEveAgent.mockReturnValue(agent as never);
|
|
1272
1351
|
const { result, rerender } = renderHook(() => useEveAgentRuntime());
|
|
1273
1352
|
|
|
@@ -1290,8 +1369,8 @@ describe("useEveAgentRuntime extras wiring", () => {
|
|
|
1290
1369
|
|
|
1291
1370
|
mockUseEveAgent.mockReturnValue(
|
|
1292
1371
|
createAgent({
|
|
1293
|
-
|
|
1294
|
-
|
|
1372
|
+
messages: twoExecutingToolsData.messages,
|
|
1373
|
+
newSession: agentReset,
|
|
1295
1374
|
}) as never,
|
|
1296
1375
|
);
|
|
1297
1376
|
rerender();
|
|
@@ -1321,21 +1400,24 @@ describe("useEveAgentRuntime extras wiring", () => {
|
|
|
1321
1400
|
});
|
|
1322
1401
|
|
|
1323
1402
|
it("promotes every staged draft after reset when the discarded run failed", async () => {
|
|
1324
|
-
const send = vi
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
});
|
|
1403
|
+
const send = vi
|
|
1404
|
+
.fn()
|
|
1405
|
+
.mockResolvedValueOnce(createEveDelivery("failed"))
|
|
1406
|
+
.mockResolvedValue(createEveDelivery());
|
|
1407
|
+
const agent = createAgent({ messages: settledData.messages, send });
|
|
1408
|
+
mockUseEveAgent.mockReturnValue(agent as never);
|
|
1331
1409
|
const { result } = renderHook(() => useEveAgentRuntime());
|
|
1332
1410
|
|
|
1333
|
-
|
|
1334
|
-
|
|
1411
|
+
await stageMessage(result.current, "discarded draft");
|
|
1412
|
+
const discardedId = result.current.thread.getState().messages[2]!.id;
|
|
1413
|
+
await act(async () => {
|
|
1414
|
+
await result.current.thread.startRun({ parentId: discardedId });
|
|
1335
1415
|
});
|
|
1416
|
+
expect(send).toHaveBeenCalledTimes(1);
|
|
1336
1417
|
act(() => {
|
|
1337
1418
|
eveExtras.tryGet(result.current.thread.getState().extras)!.reset();
|
|
1338
1419
|
});
|
|
1420
|
+
send.mockClear();
|
|
1339
1421
|
|
|
1340
1422
|
await stageMessage(result.current, "first staged");
|
|
1341
1423
|
await stageMessage(result.current, "second staged");
|
|
@@ -1355,7 +1437,7 @@ describe("useEveAgentRuntime extras wiring", () => {
|
|
|
1355
1437
|
});
|
|
1356
1438
|
});
|
|
1357
1439
|
|
|
1358
|
-
const approvalData:
|
|
1440
|
+
const approvalData: MessageFixture = {
|
|
1359
1441
|
messages: [
|
|
1360
1442
|
{ id: "u1", role: "user", parts: [{ type: "text", text: "send it" }] },
|
|
1361
1443
|
{
|
|
@@ -1363,26 +1445,27 @@ const approvalData: EveMessageData = {
|
|
|
1363
1445
|
role: "assistant",
|
|
1364
1446
|
parts: [
|
|
1365
1447
|
{
|
|
1366
|
-
type: "
|
|
1367
|
-
state: "approval
|
|
1368
|
-
|
|
1369
|
-
|
|
1448
|
+
type: "tool",
|
|
1449
|
+
state: "approval",
|
|
1450
|
+
callId: "call_1",
|
|
1451
|
+
stepIndex: 0,
|
|
1452
|
+
kind: "tool",
|
|
1453
|
+
name: "send_email",
|
|
1454
|
+
label: "send_email",
|
|
1370
1455
|
input: { to: "dev@example.com" },
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
],
|
|
1385
|
-
},
|
|
1456
|
+
interaction: {
|
|
1457
|
+
interactionId: "req_1",
|
|
1458
|
+
turnId: "t1",
|
|
1459
|
+
stepIndex: 0,
|
|
1460
|
+
status: "open",
|
|
1461
|
+
request: {
|
|
1462
|
+
kind: "approval",
|
|
1463
|
+
prompt: "Send the email?",
|
|
1464
|
+
display: "confirmation",
|
|
1465
|
+
options: [
|
|
1466
|
+
{ id: "approve", label: "Approve" },
|
|
1467
|
+
{ id: "deny", label: "Deny" },
|
|
1468
|
+
],
|
|
1386
1469
|
},
|
|
1387
1470
|
},
|
|
1388
1471
|
},
|
|
@@ -1401,7 +1484,11 @@ describe("useEveAgentRuntime concurrent sends", () => {
|
|
|
1401
1484
|
}),
|
|
1402
1485
|
);
|
|
1403
1486
|
const respond = vi.fn().mockResolvedValue(undefined);
|
|
1404
|
-
const agent = createAgent({
|
|
1487
|
+
const agent = createAgent({
|
|
1488
|
+
messages: approvalData.messages,
|
|
1489
|
+
send,
|
|
1490
|
+
respond,
|
|
1491
|
+
});
|
|
1405
1492
|
mockUseEveAgent.mockReturnValue(agent as never);
|
|
1406
1493
|
const { result } = renderHook(() => useEveAgentRuntime());
|
|
1407
1494
|
|
|
@@ -1426,7 +1513,7 @@ describe("useEveAgentRuntime concurrent sends", () => {
|
|
|
1426
1513
|
});
|
|
1427
1514
|
await waitFor(() => expect(respond).toHaveBeenCalledTimes(1));
|
|
1428
1515
|
expect(respond).toHaveBeenCalledWith([
|
|
1429
|
-
{
|
|
1516
|
+
{ interactionId: "req_1", optionId: "approve" },
|
|
1430
1517
|
]);
|
|
1431
1518
|
});
|
|
1432
1519
|
|
|
@@ -1441,7 +1528,7 @@ describe("useEveAgentRuntime concurrent sends", () => {
|
|
|
1441
1528
|
}),
|
|
1442
1529
|
)
|
|
1443
1530
|
.mockResolvedValue(undefined);
|
|
1444
|
-
const agent = createAgent({
|
|
1531
|
+
const agent = createAgent({ messages: settledData.messages, send });
|
|
1445
1532
|
mockUseEveAgent.mockReturnValue(agent as never);
|
|
1446
1533
|
const { result } = renderHook(() => useEveAgentRuntime());
|
|
1447
1534
|
|
|
@@ -1480,7 +1567,7 @@ describe("useEveAgentRuntime concurrent sends", () => {
|
|
|
1480
1567
|
}),
|
|
1481
1568
|
)
|
|
1482
1569
|
.mockResolvedValue(undefined);
|
|
1483
|
-
const agent = createAgent({
|
|
1570
|
+
const agent = createAgent({ messages: settledData.messages, send });
|
|
1484
1571
|
mockUseEveAgent.mockReturnValue(agent as never);
|
|
1485
1572
|
const { result } = renderHook(() => useEveAgentRuntime());
|
|
1486
1573
|
|
|
@@ -1501,7 +1588,7 @@ describe("useEveAgentRuntime concurrent sends", () => {
|
|
|
1501
1588
|
act(() => {
|
|
1502
1589
|
result.current.thread.cancelRun();
|
|
1503
1590
|
});
|
|
1504
|
-
expect(agent.
|
|
1591
|
+
expect(agent.cancel).toHaveBeenCalledTimes(1);
|
|
1505
1592
|
|
|
1506
1593
|
await act(async () => {
|
|
1507
1594
|
resolveFirstSend();
|
|
@@ -1520,7 +1607,7 @@ describe("useEveAgentRuntime concurrent sends", () => {
|
|
|
1520
1607
|
}),
|
|
1521
1608
|
)
|
|
1522
1609
|
.mockResolvedValue(undefined);
|
|
1523
|
-
const agent = createAgent({
|
|
1610
|
+
const agent = createAgent({ messages: settledData.messages, send });
|
|
1524
1611
|
mockUseEveAgent.mockReturnValue(agent as never);
|
|
1525
1612
|
const { result } = renderHook(() => useEveAgentRuntime());
|
|
1526
1613
|
|
|
@@ -1566,13 +1653,11 @@ describe("useEveAgentRuntime concurrent sends", () => {
|
|
|
1566
1653
|
// Before the turn streams, the dispatched message is the thread's trailing
|
|
1567
1654
|
// user leaf. Eve owns that message and cannot remove it on cancel.
|
|
1568
1655
|
const agent = createAgent({
|
|
1569
|
-
|
|
1570
|
-
messages
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
} satisfies EveMessageData,
|
|
1575
|
-
status: "submitted",
|
|
1656
|
+
messages: [
|
|
1657
|
+
...settledData.messages,
|
|
1658
|
+
{ id: "u2", role: "user", parts: [{ type: "text", text: "first" }] },
|
|
1659
|
+
],
|
|
1660
|
+
status: "idle",
|
|
1576
1661
|
send,
|
|
1577
1662
|
});
|
|
1578
1663
|
mockUseEveAgent.mockReturnValue(agent as never);
|
|
@@ -1612,7 +1697,11 @@ describe("useEveAgentRuntime concurrent sends", () => {
|
|
|
1612
1697
|
}),
|
|
1613
1698
|
);
|
|
1614
1699
|
const respond = vi.fn().mockResolvedValue(undefined);
|
|
1615
|
-
const agent = createAgent({
|
|
1700
|
+
const agent = createAgent({
|
|
1701
|
+
messages: approvalData.messages,
|
|
1702
|
+
send,
|
|
1703
|
+
respond,
|
|
1704
|
+
});
|
|
1616
1705
|
mockUseEveAgent.mockReturnValue(agent as never);
|
|
1617
1706
|
const { result } = renderHook(() => useEveAgentRuntime());
|
|
1618
1707
|
const before = getText(result.current);
|
|
@@ -1655,7 +1744,7 @@ describe("useEveAgentRuntime concurrent sends", () => {
|
|
|
1655
1744
|
}),
|
|
1656
1745
|
)
|
|
1657
1746
|
.mockResolvedValue(undefined);
|
|
1658
|
-
const agent = createAgent({
|
|
1747
|
+
const agent = createAgent({ messages: settledData.messages, send });
|
|
1659
1748
|
mockUseEveAgent.mockReturnValue(agent as never);
|
|
1660
1749
|
const { result, unmount } = renderHook(() => useEveAgentRuntime());
|
|
1661
1750
|
|
|
@@ -1685,7 +1774,7 @@ describe("useEveAgentRuntime concurrent sends", () => {
|
|
|
1685
1774
|
let capturedOptions: {
|
|
1686
1775
|
onFinish?: (snapshot: { status: string }) => void;
|
|
1687
1776
|
} = {};
|
|
1688
|
-
const agent = createAgent({
|
|
1777
|
+
const agent = createAgent({ messages: settledData.messages });
|
|
1689
1778
|
mockUseEveAgent.mockImplementation((options) => {
|
|
1690
1779
|
capturedOptions = options as typeof capturedOptions;
|
|
1691
1780
|
return agent as never;
|
|
@@ -1693,9 +1782,9 @@ describe("useEveAgentRuntime concurrent sends", () => {
|
|
|
1693
1782
|
const onFinish = vi.fn();
|
|
1694
1783
|
renderHook(() => useEveAgentRuntime({ onFinish }));
|
|
1695
1784
|
|
|
1696
|
-
capturedOptions.onFinish?.({ status: "
|
|
1785
|
+
capturedOptions.onFinish?.({ status: "idle" });
|
|
1697
1786
|
|
|
1698
|
-
expect(onFinish).toHaveBeenCalledWith({ status: "
|
|
1787
|
+
expect(onFinish).toHaveBeenCalledWith({ status: "idle" });
|
|
1699
1788
|
});
|
|
1700
1789
|
|
|
1701
1790
|
it("orders a send issued mid-promotion after the in-flight staged draft", async () => {
|
|
@@ -1709,7 +1798,7 @@ describe("useEveAgentRuntime concurrent sends", () => {
|
|
|
1709
1798
|
}),
|
|
1710
1799
|
)
|
|
1711
1800
|
.mockResolvedValue(undefined);
|
|
1712
|
-
const agent = createAgent({
|
|
1801
|
+
const agent = createAgent({ messages: settledData.messages, send });
|
|
1713
1802
|
mockUseEveAgent.mockReturnValue(agent as never);
|
|
1714
1803
|
const { result } = renderHook(() => useEveAgentRuntime());
|
|
1715
1804
|
|
|
@@ -1748,9 +1837,9 @@ describe("useEveAgentRuntime concurrent sends", () => {
|
|
|
1748
1837
|
});
|
|
1749
1838
|
|
|
1750
1839
|
describe("useEveAgentRuntime cancel binding", () => {
|
|
1751
|
-
it("cancels through the eve
|
|
1840
|
+
it("cancels through the eve cancel binding", async () => {
|
|
1752
1841
|
const cancel = vi.fn().mockResolvedValue({ status: "cancelled" });
|
|
1753
|
-
const agent = createAgent({ status: "
|
|
1842
|
+
const agent = createAgent({ status: "busy", cancel });
|
|
1754
1843
|
mockUseEveAgent.mockReturnValue(agent as never);
|
|
1755
1844
|
const { result } = renderHook(() => useEveAgentRuntime());
|
|
1756
1845
|
|
|
@@ -1759,19 +1848,6 @@ describe("useEveAgentRuntime cancel binding", () => {
|
|
|
1759
1848
|
});
|
|
1760
1849
|
|
|
1761
1850
|
expect(cancel).toHaveBeenCalledTimes(1);
|
|
1762
|
-
expect(agent.stop).not.toHaveBeenCalled();
|
|
1763
|
-
});
|
|
1764
|
-
|
|
1765
|
-
it("falls back to the pre-0.38 stop binding when cancel is absent", async () => {
|
|
1766
|
-
const agent = createAgent({ status: "streaming" });
|
|
1767
|
-
mockUseEveAgent.mockReturnValue(agent as never);
|
|
1768
|
-
const { result } = renderHook(() => useEveAgentRuntime());
|
|
1769
|
-
|
|
1770
|
-
await act(async () => {
|
|
1771
|
-
result.current.thread.cancelRun();
|
|
1772
|
-
});
|
|
1773
|
-
|
|
1774
|
-
expect(agent.stop).toHaveBeenCalledTimes(1);
|
|
1775
1851
|
});
|
|
1776
1852
|
|
|
1777
1853
|
it("drops a queued send when the durable cancel settles late", async () => {
|
|
@@ -1792,7 +1868,7 @@ describe("useEveAgentRuntime cancel binding", () => {
|
|
|
1792
1868
|
resolveCancel = () => resolve({ status: "cancelled" });
|
|
1793
1869
|
}),
|
|
1794
1870
|
);
|
|
1795
|
-
const agent = createAgent({
|
|
1871
|
+
const agent = createAgent({ messages: settledData.messages, send, cancel });
|
|
1796
1872
|
mockUseEveAgent.mockReturnValue(agent as never);
|
|
1797
1873
|
const { result } = renderHook(() => useEveAgentRuntime());
|
|
1798
1874
|
|
|
@@ -1830,28 +1906,24 @@ describe("useEveAgentRuntime thread refetch", () => {
|
|
|
1830
1906
|
const resumeA = vi.fn().mockResolvedValue(undefined);
|
|
1831
1907
|
const resumeB = vi.fn().mockResolvedValue(undefined);
|
|
1832
1908
|
const agentA = createAgent({
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
],
|
|
1841
|
-
},
|
|
1909
|
+
messages: [
|
|
1910
|
+
{
|
|
1911
|
+
id: "workspace-a",
|
|
1912
|
+
role: "user",
|
|
1913
|
+
parts: [{ type: "text", text: "workspace A" }],
|
|
1914
|
+
},
|
|
1915
|
+
],
|
|
1842
1916
|
session,
|
|
1843
1917
|
resume: resumeA,
|
|
1844
1918
|
});
|
|
1845
1919
|
const agentB = createAgent({
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
],
|
|
1854
|
-
},
|
|
1920
|
+
messages: [
|
|
1921
|
+
{
|
|
1922
|
+
id: "workspace-b",
|
|
1923
|
+
role: "user",
|
|
1924
|
+
parts: [{ type: "text", text: "workspace B" }],
|
|
1925
|
+
},
|
|
1926
|
+
],
|
|
1855
1927
|
session,
|
|
1856
1928
|
resume: resumeB,
|
|
1857
1929
|
});
|
|
@@ -2139,19 +2211,6 @@ describe("useEveAgentRuntime thread refetch", () => {
|
|
|
2139
2211
|
await waitFor(() => expect(settled).toBe(true));
|
|
2140
2212
|
expect(resume).not.toHaveBeenCalled();
|
|
2141
2213
|
});
|
|
2142
|
-
|
|
2143
|
-
it("leaves the capability absent when the installed eve has no resume", async () => {
|
|
2144
|
-
const agent = createAgent({ session });
|
|
2145
|
-
mockUseEveAgent.mockReturnValue(agent as never);
|
|
2146
|
-
const { result } = renderHook(() => useEveAgentRuntime());
|
|
2147
|
-
|
|
2148
|
-
expect(result.current.thread.getState().capabilities.refetchThread).toBe(
|
|
2149
|
-
false,
|
|
2150
|
-
);
|
|
2151
|
-
await act(async () => {
|
|
2152
|
-
await result.current.threads.reloadMainThread();
|
|
2153
|
-
});
|
|
2154
|
-
});
|
|
2155
2214
|
});
|
|
2156
2215
|
|
|
2157
2216
|
describe("useEveAgentRuntime createdAt derivation", () => {
|
|
@@ -2162,22 +2221,34 @@ describe("useEveAgentRuntime createdAt derivation", () => {
|
|
|
2162
2221
|
const turnEvents = [
|
|
2163
2222
|
{
|
|
2164
2223
|
type: "turn.started",
|
|
2165
|
-
data: {
|
|
2166
|
-
|
|
2224
|
+
data: {
|
|
2225
|
+
turnId: TURN,
|
|
2226
|
+
cause: { deliveryId: "delivery_1" },
|
|
2227
|
+
follows: null,
|
|
2228
|
+
},
|
|
2229
|
+
meta: {
|
|
2230
|
+
at: "2026-01-02T10:00:00.000Z",
|
|
2231
|
+
position: { line: 0, index: 0 },
|
|
2232
|
+
},
|
|
2167
2233
|
},
|
|
2168
2234
|
{
|
|
2169
|
-
type: "
|
|
2170
|
-
data: {
|
|
2171
|
-
|
|
2235
|
+
type: "delivery.consumed",
|
|
2236
|
+
data: {
|
|
2237
|
+
deliveryId: "delivery_1",
|
|
2238
|
+
turnId: TURN,
|
|
2239
|
+
parts: [{ kind: "text", text: "hi" }],
|
|
2240
|
+
},
|
|
2241
|
+
meta: { at: USER_AT, position: { line: 1, index: 0 } },
|
|
2172
2242
|
},
|
|
2173
2243
|
{
|
|
2174
|
-
type: "
|
|
2175
|
-
data: {
|
|
2176
|
-
|
|
2244
|
+
type: "model.requested",
|
|
2245
|
+
data: { runId: "run_1", owner: { turnId: TURN } },
|
|
2246
|
+
scope: { turnId: TURN },
|
|
2247
|
+
meta: { at: ASSISTANT_AT, position: { line: 2, index: 0 } },
|
|
2177
2248
|
},
|
|
2178
|
-
] satisfies readonly
|
|
2249
|
+
] satisfies readonly SessionStreamEvent[];
|
|
2179
2250
|
|
|
2180
|
-
const turnData:
|
|
2251
|
+
const turnData: MessageFixture = {
|
|
2181
2252
|
messages: [
|
|
2182
2253
|
{
|
|
2183
2254
|
id: `${TURN}:user`,
|
|
@@ -2196,7 +2267,7 @@ describe("useEveAgentRuntime createdAt derivation", () => {
|
|
|
2196
2267
|
|
|
2197
2268
|
it("stamps a message with the wall clock when no event covers its turn, and keeps that stamp across renders", () => {
|
|
2198
2269
|
const before = Date.now();
|
|
2199
|
-
const agent = createAgent({
|
|
2270
|
+
const agent = createAgent({ messages: turnData.messages, events: [] });
|
|
2200
2271
|
mockUseEveAgent.mockReturnValue(agent as never);
|
|
2201
2272
|
|
|
2202
2273
|
const { result, rerender } = renderHook(() => useEveAgentRuntime());
|
|
@@ -2206,7 +2277,7 @@ describe("useEveAgentRuntime createdAt derivation", () => {
|
|
|
2206
2277
|
|
|
2207
2278
|
mockUseEveAgent.mockReturnValue({
|
|
2208
2279
|
...agent,
|
|
2209
|
-
|
|
2280
|
+
messages: [...turnData.messages],
|
|
2210
2281
|
} as never);
|
|
2211
2282
|
rerender();
|
|
2212
2283
|
|
|
@@ -2216,7 +2287,10 @@ describe("useEveAgentRuntime createdAt derivation", () => {
|
|
|
2216
2287
|
});
|
|
2217
2288
|
|
|
2218
2289
|
it("keeps the message list identity when appended events stamp no message", () => {
|
|
2219
|
-
const agent = createAgent({
|
|
2290
|
+
const agent = createAgent({
|
|
2291
|
+
messages: turnData.messages,
|
|
2292
|
+
events: turnEvents,
|
|
2293
|
+
});
|
|
2220
2294
|
mockUseEveAgent.mockReturnValue(agent as never);
|
|
2221
2295
|
|
|
2222
2296
|
const { result, rerender } = renderHook(() => useEveAgentRuntime());
|
|
@@ -2228,8 +2302,15 @@ describe("useEveAgentRuntime createdAt derivation", () => {
|
|
|
2228
2302
|
...turnEvents,
|
|
2229
2303
|
{
|
|
2230
2304
|
type: "turn.started",
|
|
2231
|
-
data: {
|
|
2232
|
-
|
|
2305
|
+
data: {
|
|
2306
|
+
turnId: "turn_next",
|
|
2307
|
+
cause: { deliveryId: "delivery_2" },
|
|
2308
|
+
follows: TURN,
|
|
2309
|
+
},
|
|
2310
|
+
meta: {
|
|
2311
|
+
at: "2026-01-02T10:05:00.000Z",
|
|
2312
|
+
position: { line: 3, index: 0 },
|
|
2313
|
+
},
|
|
2233
2314
|
},
|
|
2234
2315
|
],
|
|
2235
2316
|
} as never);
|