@agent-native/core 0.95.0 → 0.95.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/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +10 -0
- package/corpus/core/docs/content/locales/ar-SA/template-design.mdx +13 -0
- package/corpus/core/docs/content/locales/de-DE/template-design.mdx +18 -0
- package/corpus/core/docs/content/locales/es-ES/template-design.mdx +16 -0
- package/corpus/core/docs/content/locales/fr-FR/template-design.mdx +17 -0
- package/corpus/core/docs/content/locales/hi-IN/template-design.mdx +16 -0
- package/corpus/core/docs/content/locales/ja-JP/template-design.mdx +17 -0
- package/corpus/core/docs/content/locales/ko-KR/template-design.mdx +15 -0
- package/corpus/core/docs/content/locales/pt-BR/template-design.mdx +16 -0
- package/corpus/core/docs/content/locales/zh-CN/template-design.mdx +11 -0
- package/corpus/core/docs/content/locales/zh-TW/template-design.mdx +12 -0
- package/corpus/core/docs/content/template-design.mdx +16 -0
- package/corpus/core/package.json +2 -2
- package/corpus/core/src/agent/production-agent.ts +20 -0
- package/corpus/core/src/agent/thread-data-builder.ts +41 -17
- package/corpus/core/src/client/AssistantChat.tsx +250 -25
- package/corpus/core/src/client/chat/tool-call-display.tsx +1 -1
- package/corpus/core/src/client/chat/use-reconnect-reader-owner.ts +28 -0
- package/corpus/core/src/client/guided-questions.tsx +9 -1
- package/corpus/core/src/client/integrations/IntegrationsPanel.tsx +6 -7
- package/corpus/core/src/client/sse-event-processor.ts +90 -21
- package/corpus/core/src/connections/catalog.ts +2 -2
- package/corpus/core/src/deploy/build.ts +3 -0
- package/corpus/core/src/integrations/catalog.ts +2 -2
- package/corpus/core/src/server/auth.ts +1 -1
- package/corpus/core/src/server/credential-provider.ts +7 -3
- package/corpus/core/src/shared/cache-control.ts +10 -4
- package/corpus/templates/analytics/app/lib/data-sources.ts +5 -4
- package/corpus/templates/analytics/changelog/2026-07-10-slack-token-setup-marked-legacy.md +6 -0
- package/corpus/templates/analytics/server/lib/credential-keys.ts +6 -2
- package/corpus/templates/brain/changelog/2026-07-10-slack-backfill-token-marked-legacy.md +6 -0
- package/corpus/templates/brain/server/register-secrets.ts +2 -2
- package/corpus/templates/clips/app/components/library/library-layout.tsx +7 -1
- package/corpus/templates/clips/app/components/library/page-header.tsx +19 -3
- package/corpus/templates/clips/app/routes/_app.library._index.tsx +16 -12
- package/corpus/templates/clips/changelog/2026-07-10-new-recording-appears-once-when-the-library-sidebar-is-open.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-10-slack-token-fallback-guidance.md +6 -0
- package/corpus/templates/clips/server/register-secrets.ts +1 -1
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +0 -66
- package/corpus/templates/content/app/routes/_app.local-files.tsx +7 -47
- package/corpus/templates/content/changelog/2026-07-10-local-folders-now-wait-for-an-explicit-pull-before-content-r.md +6 -0
- package/corpus/templates/content/package.json +1 -1
- package/corpus/templates/design/actions/generate-design.ts +128 -31
- package/corpus/templates/design/actions/present-design-variants.ts +37 -9
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +9 -3
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +32 -10
- package/corpus/templates/design/app/components/design/QuestionFlow.tsx +2 -2
- package/corpus/templates/design/app/components/design/TweaksPanel.tsx +9 -1
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +7 -5
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +3 -0
- package/corpus/templates/design/app/global.css +10 -0
- package/corpus/templates/design/app/hooks/use-question-flow.ts +9 -2
- package/corpus/templates/design/app/i18n/zh-TW.ts +1 -0
- package/corpus/templates/design/app/i18n-data.ts +21 -11
- package/corpus/templates/design/changelog/2026-07-10-editor-panels-are-easier-to-distinguish-and-design-intake-qu.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-interact-mode-now-stays-on-the-full-canvas-matches-edit-visu.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-new-web-designs-open-at-desktop-size-and-adapt-across-mobile.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-tweaks-now-link-to-full-documentation.md +6 -0
- package/corpus/templates/design/server/plugins/agent-chat.ts +2 -0
- package/corpus/templates/mail/app/i18n/ar-SA.ts +3 -3
- package/corpus/templates/mail/app/i18n/de-DE.ts +3 -3
- package/corpus/templates/mail/app/i18n/en-US.ts +4 -3
- package/corpus/templates/mail/app/i18n/es-ES.ts +3 -3
- package/corpus/templates/mail/app/i18n/fr-FR.ts +3 -3
- package/corpus/templates/mail/app/i18n/hi-IN.ts +3 -3
- package/corpus/templates/mail/app/i18n/ja-JP.ts +3 -3
- package/corpus/templates/mail/app/i18n/ko-KR.ts +3 -3
- package/corpus/templates/mail/app/i18n/pt-BR.ts +3 -3
- package/corpus/templates/mail/app/i18n/zh-CN.ts +3 -3
- package/corpus/templates/mail/app/i18n/zh-TW.ts +3 -3
- package/corpus/templates/mail/changelog/2026-07-10-slack-intake-marked-legacy.md +6 -0
- package/corpus/templates/mail/server/lib/env-config.ts +5 -4
- package/corpus/templates/plan/package.json +1 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +20 -0
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/thread-data-builder.d.ts.map +1 -1
- package/dist/agent/thread-data-builder.js +36 -18
- package/dist/agent/thread-data-builder.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +3 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +200 -28
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/chat/tool-call-display.js +1 -1
- package/dist/client/chat/tool-call-display.js.map +1 -1
- package/dist/client/chat/use-reconnect-reader-owner.d.ts +11 -0
- package/dist/client/chat/use-reconnect-reader-owner.d.ts.map +1 -0
- package/dist/client/chat/use-reconnect-reader-owner.js +21 -0
- package/dist/client/chat/use-reconnect-reader-owner.js.map +1 -0
- package/dist/client/guided-questions.d.ts.map +1 -1
- package/dist/client/guided-questions.js +5 -1
- package/dist/client/guided-questions.js.map +1 -1
- package/dist/client/integrations/IntegrationsPanel.d.ts.map +1 -1
- package/dist/client/integrations/IntegrationsPanel.js +5 -7
- package/dist/client/integrations/IntegrationsPanel.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +67 -20
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/connections/catalog.d.ts +2 -2
- package/dist/connections/catalog.js +2 -2
- package/dist/connections/catalog.js.map +1 -1
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +3 -1
- package/dist/deploy/build.js.map +1 -1
- package/dist/integrations/catalog.js +2 -2
- package/dist/integrations/catalog.js.map +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +1 -1
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/auth.js +1 -1
- package/dist/server/auth.js.map +1 -1
- package/dist/server/credential-provider.d.ts +5 -3
- package/dist/server/credential-provider.d.ts.map +1 -1
- package/dist/server/credential-provider.js +7 -3
- package/dist/server/credential-provider.js.map +1 -1
- package/dist/shared/cache-control.d.ts +8 -6
- package/dist/shared/cache-control.d.ts.map +1 -1
- package/dist/shared/cache-control.js +8 -3
- package/dist/shared/cache-control.js.map +1 -1
- package/docs/content/locales/ar-SA/template-design.mdx +13 -0
- package/docs/content/locales/de-DE/template-design.mdx +18 -0
- package/docs/content/locales/es-ES/template-design.mdx +16 -0
- package/docs/content/locales/fr-FR/template-design.mdx +17 -0
- package/docs/content/locales/hi-IN/template-design.mdx +16 -0
- package/docs/content/locales/ja-JP/template-design.mdx +17 -0
- package/docs/content/locales/ko-KR/template-design.mdx +15 -0
- package/docs/content/locales/pt-BR/template-design.mdx +16 -0
- package/docs/content/locales/zh-CN/template-design.mdx +11 -0
- package/docs/content/locales/zh-TW/template-design.mdx +12 -0
- package/docs/content/template-design.mdx +16 -0
- package/package.json +2 -2
|
@@ -18,6 +18,7 @@ import { repoHasAssistantMessage, getRepoMessages, getRepoMessage, shouldImportS
|
|
|
18
18
|
import { BuilderSetupContent, LoopLimitContinueCard, RunErrorRecoveryCard, PlanModeCallout, getLoopLimitMetadata, getRunErrorMetadata, getRequestModeMetadata, } from "./chat/run-recovery.js";
|
|
19
19
|
import { createAgentChatRuntimeAdapter, } from "./chat/runtime.js";
|
|
20
20
|
import { ChatRunningContext, ApprovalContext, ReconnectStreamMessage, } from "./chat/tool-call-display.js";
|
|
21
|
+
import { useReconnectReaderOwner } from "./chat/use-reconnect-reader-owner.js";
|
|
21
22
|
import { MessageScroller, MessageScrollerButton, MessageScrollerContent, MessageScrollerItem, MessageScrollerProvider, MessageScrollerViewport, } from "./components/ui/message-scroller.js";
|
|
22
23
|
import { Popover, PopoverContent, PopoverTrigger, } from "./components/ui/popover.js";
|
|
23
24
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "./components/ui/tooltip.js";
|
|
@@ -396,6 +397,17 @@ function toolCallNameFromContentPart(part) {
|
|
|
396
397
|
function toolCallIdsRepresentSameLocalCall(renderedId, reconnectId) {
|
|
397
398
|
return renderedId === reconnectId || renderedId.endsWith(`:${reconnectId}`);
|
|
398
399
|
}
|
|
400
|
+
function toolCallIdIsReaderLocal(id) {
|
|
401
|
+
return (/^tc_\d+$/.test(id) ||
|
|
402
|
+
/:tc_\d+$/.test(id) ||
|
|
403
|
+
id.startsWith("reconnect-activity:"));
|
|
404
|
+
}
|
|
405
|
+
function toolCallReaderLocalKey(id) {
|
|
406
|
+
const counterMatch = id.match(/(?:^|:)(tc_\d+)$/);
|
|
407
|
+
if (counterMatch?.[1])
|
|
408
|
+
return counterMatch[1];
|
|
409
|
+
return id.startsWith("reconnect-activity:") ? id : null;
|
|
410
|
+
}
|
|
399
411
|
function collectRenderedToolCallStates(messages) {
|
|
400
412
|
const byId = new Map();
|
|
401
413
|
for (const message of messages) {
|
|
@@ -470,7 +482,7 @@ function latestRenderedAssistantText(messages) {
|
|
|
470
482
|
return assistantTextFromContent(latestMessage.content);
|
|
471
483
|
}
|
|
472
484
|
function dedupePendingToolCallReplaysWithinContent(content) {
|
|
473
|
-
const
|
|
485
|
+
const laterStatesByFingerprint = new Map();
|
|
474
486
|
let changed = false;
|
|
475
487
|
const nextReversed = [];
|
|
476
488
|
for (let i = content.length - 1; i >= 0; i -= 1) {
|
|
@@ -478,44 +490,134 @@ function dedupePendingToolCallReplaysWithinContent(content) {
|
|
|
478
490
|
if (!part)
|
|
479
491
|
continue;
|
|
480
492
|
const fingerprint = toolCallFingerprintFromContentPart(part);
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
493
|
+
const laterState = fingerprint
|
|
494
|
+
? laterStatesByFingerprint.get(fingerprint)
|
|
495
|
+
: undefined;
|
|
496
|
+
const id = toolCallIdFromContentPart(part);
|
|
497
|
+
const readerLocalKey = id ? toolCallReaderLocalKey(id) : null;
|
|
498
|
+
const isReaderReplay = Boolean(id &&
|
|
499
|
+
laterState &&
|
|
500
|
+
(laterState.ids.has(id) ||
|
|
501
|
+
(readerLocalKey && laterState.readerLocalKeys.has(readerLocalKey)) ||
|
|
502
|
+
(toolCallIdIsReaderLocal(id) && laterState.hasStableId)));
|
|
503
|
+
if (fingerprint && !toolCallPartHasResult(part) && isReaderReplay) {
|
|
484
504
|
changed = true;
|
|
485
505
|
continue;
|
|
486
506
|
}
|
|
487
|
-
if (fingerprint)
|
|
488
|
-
|
|
507
|
+
if (fingerprint) {
|
|
508
|
+
const state = laterState ?? {
|
|
509
|
+
ids: new Set(),
|
|
510
|
+
readerLocalKeys: new Set(),
|
|
511
|
+
hasStableId: false,
|
|
512
|
+
};
|
|
513
|
+
if (id) {
|
|
514
|
+
state.ids.add(id);
|
|
515
|
+
const key = toolCallReaderLocalKey(id);
|
|
516
|
+
if (key)
|
|
517
|
+
state.readerLocalKeys.add(key);
|
|
518
|
+
else
|
|
519
|
+
state.hasStableId = true;
|
|
520
|
+
}
|
|
521
|
+
laterStatesByFingerprint.set(fingerprint, state);
|
|
522
|
+
}
|
|
489
523
|
nextReversed.push(part);
|
|
490
524
|
}
|
|
491
525
|
return changed ? nextReversed.reverse() : content;
|
|
492
526
|
}
|
|
493
|
-
function
|
|
527
|
+
function pendingToolCallCountsByName(content) {
|
|
528
|
+
const counts = new Map();
|
|
529
|
+
for (const part of content) {
|
|
530
|
+
const name = toolCallNameFromContentPart(part);
|
|
531
|
+
if (!name || toolCallProgressRank(part) >= 4)
|
|
532
|
+
continue;
|
|
533
|
+
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
534
|
+
}
|
|
535
|
+
return counts;
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Linear-time longest overlap where a suffix of `renderedText` equals a prefix
|
|
539
|
+
* of `reconnectText`. The reconnect overlay is recalculated during render on
|
|
540
|
+
* every stream tick, so a descending slice/endsWith scan becomes quadratic on
|
|
541
|
+
* large repeated output.
|
|
542
|
+
*/
|
|
543
|
+
function longestSuffixPrefixOverlap(renderedText, reconnectText) {
|
|
544
|
+
const maxOverlap = Math.min(renderedText.length, reconnectText.length);
|
|
545
|
+
if (maxOverlap === 0)
|
|
546
|
+
return 0;
|
|
547
|
+
const pattern = reconnectText.slice(0, maxOverlap);
|
|
548
|
+
const prefixLengths = new Array(pattern.length).fill(0);
|
|
549
|
+
for (let index = 1; index < pattern.length; index += 1) {
|
|
550
|
+
let matched = prefixLengths[index - 1] ?? 0;
|
|
551
|
+
while (matched > 0 && pattern[index] !== pattern[matched]) {
|
|
552
|
+
matched = prefixLengths[matched - 1] ?? 0;
|
|
553
|
+
}
|
|
554
|
+
if (pattern[index] === pattern[matched])
|
|
555
|
+
matched += 1;
|
|
556
|
+
prefixLengths[index] = matched;
|
|
557
|
+
}
|
|
558
|
+
let matched = 0;
|
|
559
|
+
const renderedStart = renderedText.length - maxOverlap;
|
|
560
|
+
for (let index = renderedStart; index < renderedText.length; index += 1) {
|
|
561
|
+
const character = renderedText[index];
|
|
562
|
+
while (matched > 0 && character !== pattern[matched]) {
|
|
563
|
+
matched = prefixLengths[matched - 1] ?? 0;
|
|
564
|
+
}
|
|
565
|
+
if (character === pattern[matched])
|
|
566
|
+
matched += 1;
|
|
567
|
+
if (matched === pattern.length && index < renderedText.length - 1) {
|
|
568
|
+
matched = prefixLengths[matched - 1] ?? 0;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
return matched;
|
|
572
|
+
}
|
|
573
|
+
function trimReconnectTextAlreadyRendered(content, renderedAssistantText, options) {
|
|
494
574
|
if (!renderedAssistantText)
|
|
495
575
|
return content;
|
|
496
|
-
|
|
576
|
+
const reconnectText = assistantTextFromContent(content);
|
|
577
|
+
if (!reconnectText)
|
|
578
|
+
return content;
|
|
579
|
+
let overlapLength = 0;
|
|
580
|
+
if (reconnectText.startsWith(renderedAssistantText)) {
|
|
581
|
+
overlapLength = renderedAssistantText.length;
|
|
582
|
+
}
|
|
583
|
+
else if (renderedAssistantText.startsWith(reconnectText)) {
|
|
584
|
+
overlapLength = reconnectText.length;
|
|
585
|
+
}
|
|
586
|
+
else if (options?.trimTailOverlap) {
|
|
587
|
+
const tailOverlap = longestSuffixPrefixOverlap(renderedAssistantText, reconnectText);
|
|
588
|
+
if (tailOverlap > 0) {
|
|
589
|
+
// Tail reconnects begin after the last remembered event, so their first
|
|
590
|
+
// text can be the final suffix of a thread snapshot imported in parallel.
|
|
591
|
+
// For a partial overlap, require a whole word/phrase boundary so a
|
|
592
|
+
// coincidental shared character at the join cannot eat legitimate text.
|
|
593
|
+
const renderedBeforeOverlap = renderedAssistantText[renderedAssistantText.length - tailOverlap - 1];
|
|
594
|
+
const reconnectAfterOverlap = reconnectText[tailOverlap];
|
|
595
|
+
const isWordCharacter = (value) => value ? /[\p{L}\p{N}_]/u.test(value) : false;
|
|
596
|
+
const isWholeBoundary = !isWordCharacter(renderedBeforeOverlap) &&
|
|
597
|
+
!isWordCharacter(reconnectAfterOverlap);
|
|
598
|
+
if (tailOverlap === reconnectText.length || isWholeBoundary) {
|
|
599
|
+
overlapLength = tailOverlap;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
if (overlapLength === 0)
|
|
604
|
+
return content;
|
|
605
|
+
let remainingOverlap = overlapLength;
|
|
497
606
|
let changed = false;
|
|
498
607
|
const next = [];
|
|
499
608
|
for (const part of content) {
|
|
500
|
-
if (part.type !== "text" || remainingOverlap
|
|
609
|
+
if (part.type !== "text" || remainingOverlap === 0) {
|
|
501
610
|
next.push(part);
|
|
502
611
|
continue;
|
|
503
612
|
}
|
|
504
|
-
if (
|
|
505
|
-
remainingOverlap
|
|
506
|
-
changed = true;
|
|
507
|
-
continue;
|
|
508
|
-
}
|
|
509
|
-
if (part.text.startsWith(remainingOverlap)) {
|
|
510
|
-
const text = part.text.slice(remainingOverlap.length);
|
|
511
|
-
remainingOverlap = "";
|
|
613
|
+
if (part.text.length <= remainingOverlap) {
|
|
614
|
+
remainingOverlap -= part.text.length;
|
|
512
615
|
changed = true;
|
|
513
|
-
if (text)
|
|
514
|
-
next.push({ ...part, text });
|
|
515
616
|
continue;
|
|
516
617
|
}
|
|
517
|
-
remainingOverlap
|
|
518
|
-
|
|
618
|
+
next.push({ ...part, text: part.text.slice(remainingOverlap) });
|
|
619
|
+
remainingOverlap = 0;
|
|
620
|
+
changed = true;
|
|
519
621
|
}
|
|
520
622
|
return changed ? next : content;
|
|
521
623
|
}
|
|
@@ -523,6 +625,7 @@ export function dedupeReconnectContentAgainstMessages(content, messages, options
|
|
|
523
625
|
if (content.length === 0)
|
|
524
626
|
return content;
|
|
525
627
|
const snapshotDeduped = dedupePendingToolCallReplaysWithinContent(content);
|
|
628
|
+
const reconnectPendingCounts = pendingToolCallCountsByName(snapshotDeduped);
|
|
526
629
|
let changed = snapshotDeduped !== content;
|
|
527
630
|
if (messages.length === 0)
|
|
528
631
|
return changed ? snapshotDeduped : content;
|
|
@@ -595,6 +698,25 @@ export function dedupeReconnectContentAgainstMessages(content, messages, options
|
|
|
595
698
|
const latestByName = name
|
|
596
699
|
? latestAssistantByName.get(name)
|
|
597
700
|
: undefined;
|
|
701
|
+
const reconnectPendingCount = name
|
|
702
|
+
? (reconnectPendingCounts.get(name) ?? 0)
|
|
703
|
+
: 0;
|
|
704
|
+
const hasReaderLocalIdentity = Boolean((id && toolCallIdIsReaderLocal(id)) ||
|
|
705
|
+
(latestByName &&
|
|
706
|
+
Array.from(latestByName.pendingIds).some(toolCallIdIsReaderLocal)));
|
|
707
|
+
if (options?.suppressToolRepeats &&
|
|
708
|
+
latestByName &&
|
|
709
|
+
latestByName.pendingIds.size === 1 &&
|
|
710
|
+
reconnectPendingCount === 1 &&
|
|
711
|
+
hasReaderLocalIdentity &&
|
|
712
|
+
latestByName.pendingRank >= reconnectRank) {
|
|
713
|
+
// During reconnect -> live-adapter handoff both accumulators own
|
|
714
|
+
// the same run. Their pre-tool-start activity cards can have
|
|
715
|
+
// unrelated reader-local ids and no args fingerprint, so name +
|
|
716
|
+
// pending progress is the only stable identity available.
|
|
717
|
+
changed = true;
|
|
718
|
+
return false;
|
|
719
|
+
}
|
|
598
720
|
const matchesRenderedLocalId = id && latestByName
|
|
599
721
|
? Array.from(latestByName.ids).some((renderedId) => toolCallIdsRepresentSameLocalCall(renderedId, id))
|
|
600
722
|
: false;
|
|
@@ -612,7 +734,7 @@ export function dedupeReconnectContentAgainstMessages(content, messages, options
|
|
|
612
734
|
return true;
|
|
613
735
|
})
|
|
614
736
|
: snapshotDeduped;
|
|
615
|
-
const textDeduped = trimReconnectTextAlreadyRendered(filtered, renderedAssistantText);
|
|
737
|
+
const textDeduped = trimReconnectTextAlreadyRendered(filtered, renderedAssistantText, { trimTailOverlap: options?.trimTailTextOverlap });
|
|
616
738
|
if (textDeduped !== filtered)
|
|
617
739
|
changed = true;
|
|
618
740
|
return changed ? textDeduped : content;
|
|
@@ -687,16 +809,45 @@ function contentHasVisibleReasoning(content) {
|
|
|
687
809
|
candidate.text.trim().length > 0);
|
|
688
810
|
});
|
|
689
811
|
}
|
|
690
|
-
|
|
812
|
+
function contentHasUnresolvedToolCall(content, toolName) {
|
|
813
|
+
if (!Array.isArray(content))
|
|
814
|
+
return false;
|
|
815
|
+
return content.some((part) => {
|
|
816
|
+
if (!part || typeof part !== "object")
|
|
817
|
+
return false;
|
|
818
|
+
const candidate = part;
|
|
819
|
+
return (candidate.type === "tool-call" &&
|
|
820
|
+
candidate.result === undefined &&
|
|
821
|
+
(!toolName || candidate.toolName === toolName));
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
export function shouldShowGlobalRunningStatus({ showRunningInUI, runningActivityLabel, runningActivityTool, latestMessage, reconnectContent, }) {
|
|
691
825
|
if (!showRunningInUI)
|
|
692
826
|
return false;
|
|
693
|
-
if (runningActivityLabel)
|
|
694
|
-
return true;
|
|
695
827
|
const message = latestMessage && typeof latestMessage === "object"
|
|
696
828
|
? latestMessage
|
|
697
829
|
: null;
|
|
698
830
|
const latestMessageHasReasoning = message?.role === "assistant" &&
|
|
699
831
|
contentHasVisibleReasoning(message.content);
|
|
832
|
+
const latestMessageHasUnresolvedTool = message?.role === "assistant" &&
|
|
833
|
+
contentHasUnresolvedToolCall(message.content);
|
|
834
|
+
const matchingActivityToolIsVisible = Boolean(runningActivityTool &&
|
|
835
|
+
((message?.role === "assistant" &&
|
|
836
|
+
contentHasUnresolvedToolCall(message.content, runningActivityTool)) ||
|
|
837
|
+
contentHasUnresolvedToolCall(reconnectContent, runningActivityTool)));
|
|
838
|
+
// A pending card for the same tool is already the running indicator.
|
|
839
|
+
// Rendering its global activity label as well shows the logical call twice
|
|
840
|
+
// (for example, `generate design` plus `Writing generate design...`).
|
|
841
|
+
if (runningActivityLabel && matchingActivityToolIsVisible) {
|
|
842
|
+
return false;
|
|
843
|
+
}
|
|
844
|
+
if (!runningActivityLabel &&
|
|
845
|
+
(latestMessageHasUnresolvedTool ||
|
|
846
|
+
contentHasUnresolvedToolCall(reconnectContent))) {
|
|
847
|
+
return false;
|
|
848
|
+
}
|
|
849
|
+
if (runningActivityLabel)
|
|
850
|
+
return true;
|
|
700
851
|
return (!latestMessageHasReasoning && !contentHasVisibleReasoning(reconnectContent));
|
|
701
852
|
}
|
|
702
853
|
function AssistantChatUserMessageItem() {
|
|
@@ -1198,6 +1349,7 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
1198
1349
|
const reconnectCanMaterializeRef = useRef(false);
|
|
1199
1350
|
const reconnectAbortRef = useRef(null);
|
|
1200
1351
|
const reconnectAutoRecoveryCountRef = useRef(0);
|
|
1352
|
+
const reconnectOwnerMountedRef = useReconnectReaderOwner(reconnectRunIdRef, reconnectAbortRef);
|
|
1201
1353
|
const [pendingReconnectRecovery, setPendingReconnectRecovery] = useState(null);
|
|
1202
1354
|
// Nuclear stop: user clicked stop. Clears the stop button/indicator AND
|
|
1203
1355
|
// lets new submissions go through immediately — prevents the "stuck
|
|
@@ -1494,7 +1646,8 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
1494
1646
|
(!stopped.runId || !runId || stopped.runId === runId));
|
|
1495
1647
|
}, []);
|
|
1496
1648
|
const startReconnectToRun = useCallback((runInfo) => {
|
|
1497
|
-
if (!
|
|
1649
|
+
if (!reconnectOwnerMountedRef.current ||
|
|
1650
|
+
!threadId ||
|
|
1498
1651
|
!runInfo.runId ||
|
|
1499
1652
|
(runInfo.status !== "running" && !isReplayableTerminalRun(runInfo))) {
|
|
1500
1653
|
return false;
|
|
@@ -1687,6 +1840,10 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
1687
1840
|
rafPending = true;
|
|
1688
1841
|
requestAnimationFrame(() => {
|
|
1689
1842
|
rafPending = false;
|
|
1843
|
+
if (!reconnectOwnerMountedRef.current ||
|
|
1844
|
+
reconnectRunIdRef.current !== runId) {
|
|
1845
|
+
return;
|
|
1846
|
+
}
|
|
1690
1847
|
setReconnectContent(latestSnapshot);
|
|
1691
1848
|
});
|
|
1692
1849
|
};
|
|
@@ -1745,6 +1902,11 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
1745
1902
|
clearInterval(watchdog);
|
|
1746
1903
|
clearInterval(idleCheck);
|
|
1747
1904
|
}
|
|
1905
|
+
// A newer reader, live adapter, stop action, or component unmount took
|
|
1906
|
+
// ownership while this async loop was unwinding. Do not let the stale
|
|
1907
|
+
// generation refresh/import or update reconnect state afterwards.
|
|
1908
|
+
if (reconnectRunIdRef.current !== runId)
|
|
1909
|
+
return;
|
|
1748
1910
|
if (noProgressDuringReconnect && reconnectRunIdRef.current === runId) {
|
|
1749
1911
|
const reconnectErrorCode = reconnectTerminalReason === "run_timeout"
|
|
1750
1912
|
? "run_timeout"
|
|
@@ -2656,6 +2818,7 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
2656
2818
|
}
|
|
2657
2819
|
const stillNeeded = dedupeReconnectContentAgainstMessages(reconnectContent, messages, {
|
|
2658
2820
|
suppressToolRepeats: true,
|
|
2821
|
+
trimTailTextOverlap: true,
|
|
2659
2822
|
}).length > 0;
|
|
2660
2823
|
if (!stillNeeded) {
|
|
2661
2824
|
setReconnectContent([]);
|
|
@@ -3139,7 +3302,10 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
3139
3302
|
// Do not memoize this on `messages` identity. assistant-ui can update the
|
|
3140
3303
|
// live assistant message content in place while streaming, and the reconnect
|
|
3141
3304
|
// overlay must hide as soon as that live message has caught up.
|
|
3142
|
-
const visibleReconnectContent = dedupeReconnectContentAgainstMessages(reconnectContent, messages, {
|
|
3305
|
+
const visibleReconnectContent = dedupeReconnectContentAgainstMessages(reconnectContent, messages, {
|
|
3306
|
+
suppressToolRepeats: adapterHandoffPending,
|
|
3307
|
+
trimTailTextOverlap: adapterHandoffPending || reconnectTailOnlyRef.current,
|
|
3308
|
+
});
|
|
3143
3309
|
const chatScrollResetKey = `${tabId ?? ""}:${threadId ?? ""}`;
|
|
3144
3310
|
const { isDevMode: cpDevMode } = useDevMode(apiUrl);
|
|
3145
3311
|
const checkpointCtx = useMemo(() => ({ apiUrl, devMode: cpDevMode, threadId }), [apiUrl, cpDevMode, threadId]);
|
|
@@ -3159,11 +3325,17 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
3159
3325
|
const lastUserText = useMemo(() => latestNonRecoveryUserMessageText(messages), [messages]);
|
|
3160
3326
|
const latestMessage = messages[messages.length - 1];
|
|
3161
3327
|
const latestMessageRole = latestMessage?.role;
|
|
3328
|
+
const reconnectStatusContent = visibleReconnectContent.length > 0
|
|
3329
|
+
? visibleReconnectContent
|
|
3330
|
+
: reconnectContent.length === 0
|
|
3331
|
+
? reconnectActivityContent
|
|
3332
|
+
: [];
|
|
3162
3333
|
const showGlobalRunningStatus = shouldShowGlobalRunningStatus({
|
|
3163
3334
|
showRunningInUI,
|
|
3164
3335
|
runningActivityLabel,
|
|
3336
|
+
runningActivityTool,
|
|
3165
3337
|
latestMessage,
|
|
3166
|
-
reconnectContent:
|
|
3338
|
+
reconnectContent: reconnectStatusContent,
|
|
3167
3339
|
});
|
|
3168
3340
|
const latestAssistantWasPlan = latestMessageRole === "assistant" &&
|
|
3169
3341
|
getRequestModeMetadata(latestMessage) === "plan";
|