@webless/agent 0.2.10 → 0.2.12
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/{chunk-DSNTEDXA.js → chunk-K52TJUV6.js} +329 -94
- package/dist/chunk-K52TJUV6.js.map +1 -0
- package/dist/embed.cjs +331 -93
- package/dist/embed.cjs.map +1 -1
- package/dist/embed.js +1 -1
- package/dist/index.cjs +149 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +149 -27
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +328 -93
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-DSNTEDXA.js.map +0 -1
package/dist/react.cjs
CHANGED
|
@@ -80,7 +80,8 @@ function createAgentRuntimeCapability(options) {
|
|
|
80
80
|
const response = await fetchImplementation(`${options.runtimeOrigin}/webless/v1/bootstrap`, {
|
|
81
81
|
body: JSON.stringify({
|
|
82
82
|
clientSessionId: options.visitorSessionId,
|
|
83
|
-
indexId: options.indexId
|
|
83
|
+
indexId: options.indexId,
|
|
84
|
+
version: options.version
|
|
84
85
|
}),
|
|
85
86
|
headers: { "content-type": "application/json" },
|
|
86
87
|
method: "POST"
|
|
@@ -221,6 +222,54 @@ function clearPersistedAgentSession(visitorSessionId, options) {
|
|
|
221
222
|
}
|
|
222
223
|
|
|
223
224
|
// src/runtime/client.ts
|
|
225
|
+
function isTurnBoundary(event) {
|
|
226
|
+
return event.type === "session.waiting" || event.type === "session.completed" || event.type === "session.failed";
|
|
227
|
+
}
|
|
228
|
+
function applyMessageEvent(event, rendered, handlers) {
|
|
229
|
+
const step = mapStepLabel(event);
|
|
230
|
+
if (step) handlers.onStep?.(step.label, step.detail);
|
|
231
|
+
if (event.type === "session.failed") {
|
|
232
|
+
throw new Error(event.data.message || event.data.code);
|
|
233
|
+
}
|
|
234
|
+
if (event.type === "message.completed") {
|
|
235
|
+
handlers.onComplete?.();
|
|
236
|
+
}
|
|
237
|
+
if (event.type !== "message.appended") return rendered;
|
|
238
|
+
const { messageDelta, messageSoFar } = event.data;
|
|
239
|
+
let delta = messageDelta;
|
|
240
|
+
let next = rendered;
|
|
241
|
+
if (messageSoFar.startsWith(rendered)) {
|
|
242
|
+
delta = messageSoFar.slice(rendered.length);
|
|
243
|
+
next = messageSoFar;
|
|
244
|
+
} else if (messageDelta) {
|
|
245
|
+
next += messageDelta;
|
|
246
|
+
}
|
|
247
|
+
if (delta) handlers.onDelta(delta);
|
|
248
|
+
return next;
|
|
249
|
+
}
|
|
250
|
+
function latestTurnEvents(events) {
|
|
251
|
+
let startIndex = -1;
|
|
252
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
253
|
+
if (events[index]?.type === "message.received") {
|
|
254
|
+
startIndex = index;
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return startIndex >= 0 ? events.slice(startIndex) : [];
|
|
259
|
+
}
|
|
260
|
+
function renderTurn(events) {
|
|
261
|
+
let rendered = "";
|
|
262
|
+
for (const event of events) {
|
|
263
|
+
if (event.type !== "message.appended") continue;
|
|
264
|
+
const { messageDelta, messageSoFar } = event.data;
|
|
265
|
+
if (messageSoFar.startsWith(rendered)) {
|
|
266
|
+
rendered = messageSoFar;
|
|
267
|
+
} else if (messageDelta) {
|
|
268
|
+
rendered += messageDelta;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return rendered;
|
|
272
|
+
}
|
|
224
273
|
function mapStepLabel(event) {
|
|
225
274
|
if (event.type !== "step.started") return null;
|
|
226
275
|
const stepIndex = event.data.stepIndex;
|
|
@@ -239,6 +288,7 @@ var AgentSession = class {
|
|
|
239
288
|
this.capability = createAgentRuntimeCapability({
|
|
240
289
|
indexId,
|
|
241
290
|
runtimeOrigin,
|
|
291
|
+
version,
|
|
242
292
|
visitorSessionId
|
|
243
293
|
});
|
|
244
294
|
}
|
|
@@ -256,8 +306,13 @@ var AgentSession = class {
|
|
|
256
306
|
return this.session?.state.sessionId ?? loadPersistedAgentSession(this.visitorSessionId, this.storeOptions)?.sessionId;
|
|
257
307
|
}
|
|
258
308
|
reset() {
|
|
259
|
-
|
|
260
|
-
|
|
309
|
+
if (this.activeResponse) {
|
|
310
|
+
void this.activeResponse.cancel().catch(() => {
|
|
311
|
+
});
|
|
312
|
+
} else {
|
|
313
|
+
void this.session?.cancel().catch(() => {
|
|
314
|
+
});
|
|
315
|
+
}
|
|
261
316
|
this.activeResponse = void 0;
|
|
262
317
|
this.session = void 0;
|
|
263
318
|
clearPersistedAgentSession(this.visitorSessionId, this.storeOptions);
|
|
@@ -282,7 +337,8 @@ var AgentSession = class {
|
|
|
282
337
|
if (this.client && this.clientHost === config.host) {
|
|
283
338
|
return this.client;
|
|
284
339
|
}
|
|
285
|
-
this.
|
|
340
|
+
this.activeResponse = void 0;
|
|
341
|
+
this.session = void 0;
|
|
286
342
|
this.client = new import_client2.Client({
|
|
287
343
|
auth: { bearer: () => this.capability.getAccessToken() },
|
|
288
344
|
host: config.host,
|
|
@@ -331,28 +387,20 @@ var AgentSession = class {
|
|
|
331
387
|
this.persistSessionCursor(session);
|
|
332
388
|
}
|
|
333
389
|
this.activeResponse = response;
|
|
390
|
+
let streamIndex = session?.state.streamIndex ?? 0;
|
|
334
391
|
let rendered = "";
|
|
335
392
|
try {
|
|
336
393
|
for await (const event of response) {
|
|
337
394
|
if (signal.aborted) break;
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
if (
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
rendered += messageDelta;
|
|
348
|
-
}
|
|
349
|
-
if (delta) handlers.onDelta(delta);
|
|
350
|
-
}
|
|
351
|
-
if (event.type === "message.completed") {
|
|
352
|
-
handlers.onComplete?.();
|
|
353
|
-
}
|
|
354
|
-
if (event.type === "session.failed") {
|
|
355
|
-
throw new Error(event.data.message || event.data.code);
|
|
395
|
+
rendered = applyMessageEvent(event, rendered, handlers);
|
|
396
|
+
streamIndex += 1;
|
|
397
|
+
if (session) {
|
|
398
|
+
savePersistedAgentSession(
|
|
399
|
+
this.visitorSessionId,
|
|
400
|
+
session.state.sessionId,
|
|
401
|
+
streamIndex,
|
|
402
|
+
this.storeOptions
|
|
403
|
+
);
|
|
356
404
|
}
|
|
357
405
|
}
|
|
358
406
|
} finally {
|
|
@@ -364,15 +412,83 @@ var AgentSession = class {
|
|
|
364
412
|
if (!rendered.trim() && !signal.aborted) {
|
|
365
413
|
throw new Error("Empty response from runtime");
|
|
366
414
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
415
|
+
return rendered.trim();
|
|
416
|
+
}
|
|
417
|
+
async resumeTurn(message, signal, handlers, initialText = "") {
|
|
418
|
+
const persisted = loadPersistedAgentSession(this.visitorSessionId, this.storeOptions);
|
|
419
|
+
if (!persisted) return null;
|
|
420
|
+
const client = this.ensureClient();
|
|
421
|
+
const attached = client.sessions.attach(persisted.sessionId, {
|
|
422
|
+
streamIndex: persisted.streamIndex
|
|
423
|
+
});
|
|
424
|
+
const snapshot = await withCapabilityRefresh(
|
|
425
|
+
this.capability,
|
|
426
|
+
() => attached.snapshot({ signal })
|
|
427
|
+
);
|
|
428
|
+
const turnEvents = latestTurnEvents(snapshot.events);
|
|
429
|
+
const received = turnEvents[0];
|
|
430
|
+
if (received?.type !== "message.received" || received.data.message !== message) {
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
433
|
+
let rendered = renderTurn(turnEvents);
|
|
434
|
+
if (rendered.startsWith(initialText)) {
|
|
435
|
+
const missedText = rendered.slice(initialText.length);
|
|
436
|
+
if (missedText) handlers.onDelta(missedText);
|
|
437
|
+
} else if (initialText.startsWith(rendered)) {
|
|
438
|
+
rendered = initialText;
|
|
439
|
+
} else if (!initialText.startsWith(rendered)) {
|
|
440
|
+
rendered = initialText + rendered;
|
|
441
|
+
}
|
|
442
|
+
let session = client.sessions.attach(snapshot.session.sessionId, {
|
|
443
|
+
streamIndex: snapshot.session.streamIndex
|
|
444
|
+
});
|
|
445
|
+
this.session = session;
|
|
446
|
+
this.persistSessionCursor(session);
|
|
447
|
+
let snapshotBoundary;
|
|
448
|
+
for (let index = turnEvents.length - 1; index >= 0; index -= 1) {
|
|
449
|
+
const event = turnEvents[index];
|
|
450
|
+
if (event && isTurnBoundary(event)) {
|
|
451
|
+
snapshotBoundary = event;
|
|
452
|
+
break;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
if (snapshotBoundary) {
|
|
456
|
+
if (snapshotBoundary.type === "session.failed") {
|
|
457
|
+
throw new Error(snapshotBoundary.data.message || snapshotBoundary.data.code);
|
|
458
|
+
}
|
|
459
|
+
handlers.onComplete?.();
|
|
460
|
+
if (!rendered.trim()) throw new Error("Empty response from runtime");
|
|
461
|
+
return rendered.trim();
|
|
462
|
+
}
|
|
463
|
+
let streamIndex = snapshot.session.streamIndex;
|
|
464
|
+
for await (const event of session.stream({ signal })) {
|
|
465
|
+
if (signal.aborted) break;
|
|
466
|
+
rendered = applyMessageEvent(event, rendered, handlers);
|
|
467
|
+
streamIndex += 1;
|
|
468
|
+
savePersistedAgentSession(
|
|
469
|
+
this.visitorSessionId,
|
|
470
|
+
session.state.sessionId,
|
|
471
|
+
streamIndex,
|
|
472
|
+
this.storeOptions
|
|
473
|
+
);
|
|
474
|
+
if (isTurnBoundary(event)) break;
|
|
475
|
+
}
|
|
476
|
+
session = client.sessions.attach(session.state.sessionId, { streamIndex });
|
|
477
|
+
this.session = session;
|
|
478
|
+
this.persistSessionCursor(session);
|
|
479
|
+
if (!rendered.trim() && !signal.aborted) {
|
|
480
|
+
throw new Error("Empty response from runtime");
|
|
370
481
|
}
|
|
371
482
|
return rendered.trim();
|
|
372
483
|
}
|
|
373
484
|
cancelActive() {
|
|
374
|
-
this.activeResponse
|
|
375
|
-
|
|
485
|
+
if (this.activeResponse) {
|
|
486
|
+
this.activeResponse.cancel().catch(() => {
|
|
487
|
+
});
|
|
488
|
+
} else {
|
|
489
|
+
this.session?.cancel().catch(() => {
|
|
490
|
+
});
|
|
491
|
+
}
|
|
376
492
|
}
|
|
377
493
|
};
|
|
378
494
|
function createAgentClient(options) {
|
|
@@ -406,6 +522,12 @@ function createAgentClient(options) {
|
|
|
406
522
|
sendOptions.signal ?? new AbortController().signal,
|
|
407
523
|
sendOptions.handlers
|
|
408
524
|
),
|
|
525
|
+
resumeTurn: (resumeOptions) => session.resumeTurn(
|
|
526
|
+
resumeOptions.message,
|
|
527
|
+
resumeOptions.signal ?? new AbortController().signal,
|
|
528
|
+
resumeOptions.handlers,
|
|
529
|
+
resumeOptions.initialText
|
|
530
|
+
),
|
|
409
531
|
reset: () => session.reset(),
|
|
410
532
|
cancelActive: () => session.cancelActive(),
|
|
411
533
|
getActiveSessionId: () => session.getActiveSessionId()
|
|
@@ -434,6 +556,58 @@ function formatAgentError(error) {
|
|
|
434
556
|
return "Runtime request failed";
|
|
435
557
|
}
|
|
436
558
|
|
|
559
|
+
// src/react/persisted-conversation.ts
|
|
560
|
+
var CONVERSATION_VERSION = 1;
|
|
561
|
+
function conversationKey(storageKeyPrefix, visitorSessionId) {
|
|
562
|
+
return `${storageKeyPrefix}:conversation:${visitorSessionId}`;
|
|
563
|
+
}
|
|
564
|
+
function parseMessage(value) {
|
|
565
|
+
if (typeof value !== "object" || value === null) return null;
|
|
566
|
+
const record = value;
|
|
567
|
+
if (typeof record.id !== "string" || record.role !== "agent" && record.role !== "visitor" || typeof record.text !== "string" || typeof record.createdAt !== "number" || !Number.isFinite(record.createdAt)) {
|
|
568
|
+
return null;
|
|
569
|
+
}
|
|
570
|
+
return {
|
|
571
|
+
id: record.id,
|
|
572
|
+
role: record.role,
|
|
573
|
+
text: record.text,
|
|
574
|
+
createdAt: record.createdAt
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
function loadPersistedAgentConversation(storageKeyPrefix, visitorSessionId) {
|
|
578
|
+
if (typeof sessionStorage === "undefined") return null;
|
|
579
|
+
const raw = sessionStorage.getItem(conversationKey(storageKeyPrefix, visitorSessionId));
|
|
580
|
+
if (!raw) return null;
|
|
581
|
+
try {
|
|
582
|
+
const value = JSON.parse(raw);
|
|
583
|
+
if (typeof value !== "object" || value === null) return null;
|
|
584
|
+
const record = value;
|
|
585
|
+
if (record.version !== CONVERSATION_VERSION || !Array.isArray(record.messages) || typeof record.pending !== "boolean" || typeof record.streamingText !== "string") {
|
|
586
|
+
return null;
|
|
587
|
+
}
|
|
588
|
+
const messages = record.messages.map(parseMessage);
|
|
589
|
+
if (messages.some((message) => message === null)) return null;
|
|
590
|
+
return {
|
|
591
|
+
messages: messages.filter((message) => message !== null),
|
|
592
|
+
pending: record.pending,
|
|
593
|
+
streamingText: record.streamingText
|
|
594
|
+
};
|
|
595
|
+
} catch {
|
|
596
|
+
return null;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
function savePersistedAgentConversation(storageKeyPrefix, visitorSessionId, conversation) {
|
|
600
|
+
if (typeof sessionStorage === "undefined") return;
|
|
601
|
+
sessionStorage.setItem(
|
|
602
|
+
conversationKey(storageKeyPrefix, visitorSessionId),
|
|
603
|
+
JSON.stringify({ version: CONVERSATION_VERSION, ...conversation })
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
function clearPersistedAgentConversation(storageKeyPrefix, visitorSessionId) {
|
|
607
|
+
if (typeof sessionStorage === "undefined") return;
|
|
608
|
+
sessionStorage.removeItem(conversationKey(storageKeyPrefix, visitorSessionId));
|
|
609
|
+
}
|
|
610
|
+
|
|
437
611
|
// src/react/hooks/useAgentChat.ts
|
|
438
612
|
var GREETING_MESSAGE = {
|
|
439
613
|
id: "greeting",
|
|
@@ -450,6 +624,15 @@ var INITIAL_STATE = {
|
|
|
450
624
|
streamingText: "",
|
|
451
625
|
error: null
|
|
452
626
|
};
|
|
627
|
+
function stateFromConversation(conversation) {
|
|
628
|
+
if (!conversation || conversation.messages.length === 0) return INITIAL_STATE;
|
|
629
|
+
return {
|
|
630
|
+
...INITIAL_STATE,
|
|
631
|
+
messages: conversation.messages,
|
|
632
|
+
phase: conversation.pending ? conversation.streamingText ? "streaming" : "thinking" : "complete",
|
|
633
|
+
streamingText: conversation.streamingText
|
|
634
|
+
};
|
|
635
|
+
}
|
|
453
636
|
var STATUS_SEQUENCE = [
|
|
454
637
|
{ id: "s1", label: "Starting Eve session", ms: 400 },
|
|
455
638
|
{ id: "s2", label: "Connecting to runtime", ms: 500 }
|
|
@@ -481,8 +664,6 @@ function useAgentChat({
|
|
|
481
664
|
visitorSessionId,
|
|
482
665
|
storageKeyPrefix
|
|
483
666
|
}) {
|
|
484
|
-
const [state, setState] = (0, import_react.useState)(INITIAL_STATE);
|
|
485
|
-
const runRef = (0, import_react.useRef)(null);
|
|
486
667
|
const resolvedStorageKeyPrefix = (0, import_react.useMemo)(
|
|
487
668
|
() => storageKeyPrefix?.trim() || buildAgentStorageKeyPrefix({ customerId, indexId, version, runtimeOrigin }),
|
|
488
669
|
[customerId, indexId, runtimeOrigin, storageKeyPrefix, version]
|
|
@@ -491,6 +672,12 @@ function useAgentChat({
|
|
|
491
672
|
() => visitorSessionId?.trim() || getOrCreateVisitorSessionId({ storageKeyPrefix: resolvedStorageKeyPrefix }),
|
|
492
673
|
[resolvedStorageKeyPrefix, visitorSessionId]
|
|
493
674
|
);
|
|
675
|
+
const [state, setState] = (0, import_react.useState)(
|
|
676
|
+
() => stateFromConversation(
|
|
677
|
+
loadPersistedAgentConversation(resolvedStorageKeyPrefix, visitorId)
|
|
678
|
+
)
|
|
679
|
+
);
|
|
680
|
+
const runRef = (0, import_react.useRef)(null);
|
|
494
681
|
const clientRef = (0, import_react.useRef)(
|
|
495
682
|
createAgentClient({
|
|
496
683
|
customerId,
|
|
@@ -501,20 +688,15 @@ function useAgentChat({
|
|
|
501
688
|
storageKeyPrefix: resolvedStorageKeyPrefix
|
|
502
689
|
})
|
|
503
690
|
);
|
|
504
|
-
const identityRef = (0, import_react.useRef)(null);
|
|
505
691
|
const identityKey = `${customerId ?? ""}|${indexId}|${version ?? "published"}|${runtimeOrigin ?? ""}|${resolvedStorageKeyPrefix}|${visitorId}`;
|
|
692
|
+
const identityRef = (0, import_react.useRef)(identityKey);
|
|
506
693
|
(0, import_react.useEffect)(() => {
|
|
507
|
-
if (identityRef.current === null) {
|
|
508
|
-
identityRef.current = identityKey;
|
|
509
|
-
return;
|
|
510
|
-
}
|
|
511
694
|
if (identityRef.current === identityKey) {
|
|
512
695
|
return;
|
|
513
696
|
}
|
|
514
697
|
identityRef.current = identityKey;
|
|
515
698
|
runRef.current?.abort();
|
|
516
699
|
runRef.current = null;
|
|
517
|
-
clientRef.current.reset();
|
|
518
700
|
clientRef.current = createAgentClient({
|
|
519
701
|
customerId,
|
|
520
702
|
indexId,
|
|
@@ -523,81 +705,81 @@ function useAgentChat({
|
|
|
523
705
|
visitorSessionId: visitorId,
|
|
524
706
|
storageKeyPrefix: resolvedStorageKeyPrefix
|
|
525
707
|
});
|
|
526
|
-
setState(
|
|
708
|
+
setState(
|
|
709
|
+
stateFromConversation(
|
|
710
|
+
loadPersistedAgentConversation(resolvedStorageKeyPrefix, visitorId)
|
|
711
|
+
)
|
|
712
|
+
);
|
|
527
713
|
}, [customerId, identityKey, indexId, runtimeOrigin, resolvedStorageKeyPrefix, version, visitorId]);
|
|
714
|
+
(0, import_react.useEffect)(() => {
|
|
715
|
+
if (!hasVisitorMessages(state.messages)) return;
|
|
716
|
+
savePersistedAgentConversation(resolvedStorageKeyPrefix, visitorId, {
|
|
717
|
+
messages: state.messages,
|
|
718
|
+
pending: isAgentBusy(state.phase),
|
|
719
|
+
streamingText: state.streamingText
|
|
720
|
+
});
|
|
721
|
+
}, [resolvedStorageKeyPrefix, state.messages, state.phase, state.streamingText, visitorId]);
|
|
528
722
|
const reset = (0, import_react.useCallback)(() => {
|
|
529
723
|
runRef.current?.abort();
|
|
530
724
|
runRef.current = null;
|
|
531
725
|
clientRef.current.reset();
|
|
726
|
+
clearPersistedAgentConversation(resolvedStorageKeyPrefix, visitorId);
|
|
532
727
|
setState(INITIAL_STATE);
|
|
533
|
-
}, []);
|
|
534
|
-
const
|
|
535
|
-
async (
|
|
536
|
-
|
|
537
|
-
clientRef.current.cancelActive();
|
|
538
|
-
const controller = new AbortController();
|
|
539
|
-
runRef.current = controller;
|
|
728
|
+
}, [resolvedStorageKeyPrefix, visitorId]);
|
|
729
|
+
const runTurn = (0, import_react.useCallback)(
|
|
730
|
+
async (input) => {
|
|
731
|
+
const { controller, initialText = "", resume, visitorText } = input;
|
|
540
732
|
const { signal } = controller;
|
|
541
733
|
const isActiveRun = () => runRef.current === controller && !signal.aborted;
|
|
542
|
-
const visitorMessage = {
|
|
543
|
-
id: `visitor-${Date.now()}`,
|
|
544
|
-
role: "visitor",
|
|
545
|
-
text: visitorText,
|
|
546
|
-
createdAt: Date.now()
|
|
547
|
-
};
|
|
548
|
-
setState((prev) => ({
|
|
549
|
-
...prev,
|
|
550
|
-
phase: "thinking",
|
|
551
|
-
messages: [...prev.messages, visitorMessage],
|
|
552
|
-
toolSteps: [{ id: "s1", label: "Starting Eve session", state: "active" }],
|
|
553
|
-
journey: null,
|
|
554
|
-
followUps: [],
|
|
555
|
-
streamingText: "",
|
|
556
|
-
error: null
|
|
557
|
-
}));
|
|
558
734
|
try {
|
|
559
|
-
let streamStarted =
|
|
560
|
-
|
|
735
|
+
let streamStarted = Boolean(initialText);
|
|
736
|
+
let streamed = initialText;
|
|
737
|
+
const planningPromise = resume ? Promise.resolve() : runStatusSequence(signal, (step) => {
|
|
561
738
|
if (streamStarted || !isActiveRun()) return;
|
|
562
739
|
setState((prev) => ({ ...prev, phase: "running-tools", toolSteps: [step] }));
|
|
563
740
|
});
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
741
|
+
const handlers = {
|
|
742
|
+
onStep: (label, detail) => {
|
|
743
|
+
if (streamStarted || !isActiveRun()) return;
|
|
744
|
+
setState((prev) => ({
|
|
745
|
+
...prev,
|
|
746
|
+
phase: "running-tools",
|
|
747
|
+
toolSteps: [{ id: `step-${label}`, label, detail, state: "active" }]
|
|
748
|
+
}));
|
|
749
|
+
},
|
|
750
|
+
onDelta: (delta) => {
|
|
751
|
+
if (!isActiveRun()) return;
|
|
752
|
+
void planningPromise.catch(() => {
|
|
753
|
+
});
|
|
754
|
+
if (!streamStarted) {
|
|
755
|
+
streamStarted = true;
|
|
570
756
|
setState((prev) => ({
|
|
571
757
|
...prev,
|
|
572
|
-
phase: "
|
|
573
|
-
toolSteps: [
|
|
758
|
+
phase: "streaming",
|
|
759
|
+
toolSteps: [],
|
|
760
|
+
streamingText: ""
|
|
574
761
|
}));
|
|
575
|
-
},
|
|
576
|
-
onDelta: (delta) => {
|
|
577
|
-
if (!isActiveRun()) return;
|
|
578
|
-
void planningPromise.catch(() => {
|
|
579
|
-
});
|
|
580
|
-
if (!streamStarted) {
|
|
581
|
-
streamStarted = true;
|
|
582
|
-
setState((prev) => ({
|
|
583
|
-
...prev,
|
|
584
|
-
phase: "streaming",
|
|
585
|
-
toolSteps: [],
|
|
586
|
-
streamingText: ""
|
|
587
|
-
}));
|
|
588
|
-
}
|
|
589
|
-
streamed += delta;
|
|
590
|
-
setState((prev) => ({ ...prev, phase: "streaming", streamingText: streamed }));
|
|
591
|
-
},
|
|
592
|
-
onComplete: () => {
|
|
593
|
-
if (!isActiveRun()) return;
|
|
594
|
-
streamStarted = true;
|
|
595
762
|
}
|
|
763
|
+
streamed += delta;
|
|
764
|
+
setState((prev) => ({ ...prev, phase: "streaming", streamingText: streamed }));
|
|
765
|
+
},
|
|
766
|
+
onComplete: () => {
|
|
767
|
+
if (!isActiveRun()) return;
|
|
768
|
+
streamStarted = true;
|
|
596
769
|
}
|
|
597
|
-
}
|
|
770
|
+
};
|
|
771
|
+
let finalText = resume ? await clientRef.current.resumeTurn({
|
|
772
|
+
handlers,
|
|
773
|
+
initialText,
|
|
774
|
+
message: visitorText,
|
|
775
|
+
signal
|
|
776
|
+
}) : await clientRef.current.sendTurn(visitorText, { handlers, signal });
|
|
777
|
+
if (resume && finalText === null) {
|
|
778
|
+
finalText = await clientRef.current.sendTurn(visitorText, { handlers, signal });
|
|
779
|
+
}
|
|
598
780
|
await planningPromise.catch(() => {
|
|
599
781
|
});
|
|
600
|
-
if (!isActiveRun()) return;
|
|
782
|
+
if (!isActiveRun() || finalText === null) return;
|
|
601
783
|
const agentMessage = {
|
|
602
784
|
id: `agent-${Date.now()}`,
|
|
603
785
|
role: "agent",
|
|
@@ -613,6 +795,7 @@ function useAgentChat({
|
|
|
613
795
|
followUps: [],
|
|
614
796
|
journey: null
|
|
615
797
|
}));
|
|
798
|
+
runRef.current = null;
|
|
616
799
|
} catch (error) {
|
|
617
800
|
if (error instanceof DOMException && error.name === "AbortError") return;
|
|
618
801
|
if (!isActiveRun()) return;
|
|
@@ -625,14 +808,66 @@ function useAgentChat({
|
|
|
625
808
|
streamingText: "",
|
|
626
809
|
error: message
|
|
627
810
|
}));
|
|
811
|
+
runRef.current = null;
|
|
628
812
|
}
|
|
629
813
|
},
|
|
630
|
-
[
|
|
814
|
+
[]
|
|
631
815
|
);
|
|
816
|
+
const submit = (0, import_react.useCallback)(
|
|
817
|
+
async (visitorText) => {
|
|
818
|
+
if (runRef.current) {
|
|
819
|
+
runRef.current.abort();
|
|
820
|
+
clientRef.current.cancelActive();
|
|
821
|
+
}
|
|
822
|
+
const controller = new AbortController();
|
|
823
|
+
runRef.current = controller;
|
|
824
|
+
const visitorMessage = {
|
|
825
|
+
id: `visitor-${Date.now()}`,
|
|
826
|
+
role: "visitor",
|
|
827
|
+
text: visitorText,
|
|
828
|
+
createdAt: Date.now()
|
|
829
|
+
};
|
|
830
|
+
setState((prev) => ({
|
|
831
|
+
...prev,
|
|
832
|
+
phase: "thinking",
|
|
833
|
+
messages: [...prev.messages, visitorMessage],
|
|
834
|
+
toolSteps: [{ id: "s1", label: "Starting Eve session", state: "active" }],
|
|
835
|
+
journey: null,
|
|
836
|
+
followUps: [],
|
|
837
|
+
streamingText: "",
|
|
838
|
+
error: null
|
|
839
|
+
}));
|
|
840
|
+
await runTurn({ controller, resume: false, visitorText });
|
|
841
|
+
},
|
|
842
|
+
[runTurn]
|
|
843
|
+
);
|
|
844
|
+
(0, import_react.useEffect)(() => {
|
|
845
|
+
const conversation = loadPersistedAgentConversation(
|
|
846
|
+
resolvedStorageKeyPrefix,
|
|
847
|
+
visitorId
|
|
848
|
+
);
|
|
849
|
+
if (!conversation?.pending) return;
|
|
850
|
+
const visitorMessage = [...conversation.messages].reverse().find((message) => message.role === "visitor");
|
|
851
|
+
if (!visitorMessage) return;
|
|
852
|
+
const controller = new AbortController();
|
|
853
|
+
runRef.current = controller;
|
|
854
|
+
void runTurn({
|
|
855
|
+
controller,
|
|
856
|
+
initialText: conversation.streamingText,
|
|
857
|
+
resume: true,
|
|
858
|
+
visitorText: visitorMessage.text
|
|
859
|
+
});
|
|
860
|
+
return () => {
|
|
861
|
+
if (runRef.current === controller) {
|
|
862
|
+
runRef.current = null;
|
|
863
|
+
}
|
|
864
|
+
controller.abort();
|
|
865
|
+
};
|
|
866
|
+
}, [identityKey, resolvedStorageKeyPrefix, runTurn, visitorId]);
|
|
632
867
|
(0, import_react.useEffect)(() => {
|
|
633
868
|
return () => {
|
|
634
869
|
runRef.current?.abort();
|
|
635
|
-
|
|
870
|
+
runRef.current = null;
|
|
636
871
|
};
|
|
637
872
|
}, []);
|
|
638
873
|
return {
|