@alia.onl/sdk 22.1.0 → 22.1.1
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 +8 -0
- package/package.json +1 -1
- package/src/runtime/use-conversation-stream.ts +37 -25
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @alia.onl/sdk
|
|
2
2
|
|
|
3
|
+
## 22.1.1
|
|
4
|
+
|
|
5
|
+
- A delayed Stop acknowledgement cannot idle a newer turn, replace its sidebar
|
|
6
|
+
preview, or notify a different conversation. Thread/account changes and unmounts
|
|
7
|
+
invalidate old completion callbacks while the original acknowledgement drains.
|
|
8
|
+
- Keep repeated Stop requests deduplicated during that drain; errors from an old
|
|
9
|
+
Stop cannot appear in a replacement thread. Public APIs and peer ranges are unchanged.
|
|
10
|
+
|
|
3
11
|
## 22.1.0
|
|
4
12
|
|
|
5
13
|
- Stop explicitly cancels the authenticated foreground turn across API replicas;
|
package/package.json
CHANGED
|
@@ -216,6 +216,8 @@ export function useConversationStream({
|
|
|
216
216
|
const queryClient = useQueryClient();
|
|
217
217
|
const abortControllerRef = useRef<AbortController | null>(null);
|
|
218
218
|
const turnControlRef = useRef<ReturnType<typeof createChatTurnControl> | null>(null);
|
|
219
|
+
// A Stop acknowledgement can outlive its stream, thread, or the next send.
|
|
220
|
+
const turnGenerationRef = useRef(0);
|
|
219
221
|
|
|
220
222
|
// Batching refs: accumulate streaming text and flush at ~20fps instead of per-chunk
|
|
221
223
|
const pendingContentRef = useRef('');
|
|
@@ -279,6 +281,8 @@ export function useConversationStream({
|
|
|
279
281
|
mountedRef.current = true;
|
|
280
282
|
return () => {
|
|
281
283
|
mountedRef.current = false;
|
|
284
|
+
turnGenerationRef.current++;
|
|
285
|
+
turnControlRef.current = null;
|
|
282
286
|
flushPendingUpdates();
|
|
283
287
|
if (flushTimerRef.current) {
|
|
284
288
|
clearTimeout(flushTimerRef.current);
|
|
@@ -324,6 +328,8 @@ export function useConversationStream({
|
|
|
324
328
|
const was = conversationRef.current;
|
|
325
329
|
conversationRef.current = conversationId;
|
|
326
330
|
if (was !== undefined && was !== conversationId) {
|
|
331
|
+
turnGenerationRef.current++;
|
|
332
|
+
turnControlRef.current = null;
|
|
327
333
|
hostOutcomesRef.current.clear();
|
|
328
334
|
// The reply still streaming belongs to the thread that was left: cut it, drop what it had
|
|
329
335
|
// batched (it would land on this thread's last message) and let the screen be idle.
|
|
@@ -350,6 +356,8 @@ export function useConversationStream({
|
|
|
350
356
|
const was = accountRef.current;
|
|
351
357
|
accountRef.current = accountId;
|
|
352
358
|
if (was === null || was === accountId) return;
|
|
359
|
+
turnGenerationRef.current++;
|
|
360
|
+
turnControlRef.current = null;
|
|
353
361
|
abortControllerRef.current?.abort();
|
|
354
362
|
abortControllerRef.current = null;
|
|
355
363
|
dropPendingUpdates();
|
|
@@ -367,6 +375,7 @@ export function useConversationStream({
|
|
|
367
375
|
|
|
368
376
|
const append = useCallback(
|
|
369
377
|
async (message: Omit<Message, 'id'>, options?: SendOptions): Promise<SendOutcome> => {
|
|
378
|
+
const turnGeneration = ++turnGenerationRef.current;
|
|
370
379
|
setIsLoading(true);
|
|
371
380
|
setError(null);
|
|
372
381
|
// A new send answers the old failure, whether it is the retry or a fresh
|
|
@@ -1376,33 +1385,31 @@ export function useConversationStream({
|
|
|
1376
1385
|
setError(finalError);
|
|
1377
1386
|
return keepFailedTurn(true, finalError.message);
|
|
1378
1387
|
} finally {
|
|
1379
|
-
//
|
|
1380
|
-
|
|
1381
|
-
if (
|
|
1382
|
-
|
|
1383
|
-
flushTimerRef.current
|
|
1388
|
+
// Batches belong to the current turn. An older finally must not flush or clear
|
|
1389
|
+
// the next turn's shared timer, even when its controller ref has been cleared.
|
|
1390
|
+
if (turnGenerationRef.current === turnGeneration) {
|
|
1391
|
+
flushPendingUpdates();
|
|
1392
|
+
if (flushTimerRef.current) {
|
|
1393
|
+
clearTimeout(flushTimerRef.current);
|
|
1394
|
+
flushTimerRef.current = null;
|
|
1395
|
+
}
|
|
1384
1396
|
}
|
|
1385
|
-
//
|
|
1386
|
-
//
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
if (turnControlRef.current === turnControl) turnControlRef.current = null;
|
|
1397
|
+
// Settle only this turn's message ids, including when a new send has already
|
|
1398
|
+
// started in the same conversation. Error paths may have settled them first.
|
|
1399
|
+
settleAssistant(controller?.signal.aborted === true ? 'cancelled' : 'completed');
|
|
1400
|
+
// Retain this control while its ACK drains so repeated Stop remains deduplicated.
|
|
1401
|
+
// Local transport cleanup is required even after the thread has been replaced.
|
|
1391
1402
|
if (turnControl) await turnControl.finish();
|
|
1392
1403
|
else linked?.dispose();
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
//
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
*/
|
|
1403
|
-
if (replyText !== '') host.onTurnPreview?.(agentId, replyText);
|
|
1404
|
-
// Every way a turn stops streaming — finished, stopped, failed — passes here once.
|
|
1405
|
-
if (mountedRef.current) host.onTurnEnd?.();
|
|
1404
|
+
if (abortControllerRef.current === controller) abortControllerRef.current = null;
|
|
1405
|
+
if (turnControlRef.current === turnControl) turnControlRef.current = null;
|
|
1406
|
+
// Recheck after the await: another turn, thread, account or mount may now own UI.
|
|
1407
|
+
if (mountedRef.current && turnGenerationRef.current === turnGeneration) {
|
|
1408
|
+
setIsLoading(false);
|
|
1409
|
+
// The sidebar preview and completion notification belong to this visible turn.
|
|
1410
|
+
if (replyText !== '') host.onTurnPreview?.(agentId, replyText);
|
|
1411
|
+
host.onTurnEnd?.();
|
|
1412
|
+
}
|
|
1406
1413
|
}
|
|
1407
1414
|
},
|
|
1408
1415
|
[
|
|
@@ -1434,9 +1441,14 @@ export function useConversationStream({
|
|
|
1434
1441
|
|
|
1435
1442
|
const stop = useCallback(() => {
|
|
1436
1443
|
const turn = turnControlRef.current;
|
|
1444
|
+
const turnGeneration = turnGenerationRef.current;
|
|
1437
1445
|
if (!turn) return;
|
|
1438
1446
|
void turn.stop().catch((error: unknown) => {
|
|
1439
|
-
if (
|
|
1447
|
+
if (
|
|
1448
|
+
turnControlRef.current === turn &&
|
|
1449
|
+
turnGenerationRef.current === turnGeneration &&
|
|
1450
|
+
mountedRef.current
|
|
1451
|
+
) {
|
|
1440
1452
|
setError(error instanceof Error ? error : new Error('Generation could not be stopped.'));
|
|
1441
1453
|
}
|
|
1442
1454
|
});
|