@agent-native/core 0.94.3 → 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 +16 -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/pr-visual-recap.mdx +20 -12
- package/corpus/core/docs/content/template-design.mdx +16 -0
- package/corpus/core/package.json +2 -2
- package/corpus/core/src/agent/engine/ai-sdk-engine.ts +3 -0
- package/corpus/core/src/agent/engine/registry.ts +6 -1
- package/corpus/core/src/agent/engine/types.ts +2 -0
- 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/cli/code-agent-executor.ts +61 -1
- package/corpus/core/src/cli/pr-visual-recap-workflow.ts +1 -1
- package/corpus/core/src/cli/recap.ts +262 -25
- 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/settings/UsageSection.tsx +26 -5
- 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/index.ts +2 -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/core/src/usage/store.ts +82 -20
- 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/actions/record-recap-usage-cost.ts +49 -0
- package/corpus/templates/plan/actions/record-recap-usage.ts +15 -22
- package/corpus/templates/plan/package.json +1 -1
- package/dist/agent/engine/ai-sdk-engine.d.ts.map +1 -1
- package/dist/agent/engine/ai-sdk-engine.js +3 -0
- package/dist/agent/engine/ai-sdk-engine.js.map +1 -1
- package/dist/agent/engine/registry.d.ts +1 -1
- package/dist/agent/engine/registry.d.ts.map +1 -1
- package/dist/agent/engine/registry.js +2 -0
- package/dist/agent/engine/registry.js.map +1 -1
- package/dist/agent/engine/types.d.ts +2 -0
- package/dist/agent/engine/types.d.ts.map +1 -1
- package/dist/agent/engine/types.js.map +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/cli/code-agent-executor.d.ts +2 -0
- package/dist/cli/code-agent-executor.d.ts.map +1 -1
- package/dist/cli/code-agent-executor.js +52 -3
- package/dist/cli/code-agent-executor.js.map +1 -1
- package/dist/cli/pr-visual-recap-workflow.d.ts +1 -1
- package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
- package/dist/cli/pr-visual-recap-workflow.js +1 -1
- package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
- package/dist/cli/recap.d.ts +22 -2
- package/dist/cli/recap.d.ts.map +1 -1
- package/dist/cli/recap.js +199 -22
- package/dist/cli/recap.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/settings/UsageSection.d.ts.map +1 -1
- package/dist/client/settings/UsageSection.js +12 -3
- package/dist/client/settings/UsageSection.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/collab/struct-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/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.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/dist/usage/store.d.ts +21 -0
- package/dist/usage/store.d.ts.map +1 -1
- package/dist/usage/store.js +54 -19
- package/dist/usage/store.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/pr-visual-recap.mdx +20 -12
- package/docs/content/template-design.mdx +16 -0
- package/package.json +2 -2
|
@@ -123,6 +123,7 @@ import {
|
|
|
123
123
|
type ApprovalContextValue,
|
|
124
124
|
ReconnectStreamMessage,
|
|
125
125
|
} from "./chat/tool-call-display.js";
|
|
126
|
+
import { useReconnectReaderOwner } from "./chat/use-reconnect-reader-owner.js";
|
|
126
127
|
import {
|
|
127
128
|
MessageScroller,
|
|
128
129
|
MessageScrollerButton,
|
|
@@ -764,6 +765,20 @@ function toolCallIdsRepresentSameLocalCall(
|
|
|
764
765
|
return renderedId === reconnectId || renderedId.endsWith(`:${reconnectId}`);
|
|
765
766
|
}
|
|
766
767
|
|
|
768
|
+
function toolCallIdIsReaderLocal(id: string): boolean {
|
|
769
|
+
return (
|
|
770
|
+
/^tc_\d+$/.test(id) ||
|
|
771
|
+
/:tc_\d+$/.test(id) ||
|
|
772
|
+
id.startsWith("reconnect-activity:")
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
function toolCallReaderLocalKey(id: string): string | null {
|
|
777
|
+
const counterMatch = id.match(/(?:^|:)(tc_\d+)$/);
|
|
778
|
+
if (counterMatch?.[1]) return counterMatch[1];
|
|
779
|
+
return id.startsWith("reconnect-activity:") ? id : null;
|
|
780
|
+
}
|
|
781
|
+
|
|
767
782
|
function collectRenderedToolCallStates(messages: readonly unknown[]): {
|
|
768
783
|
byId: Map<string, { rank: number }>;
|
|
769
784
|
latestAssistantByFingerprint: Map<string, { rank: number }>;
|
|
@@ -862,7 +877,14 @@ function latestRenderedAssistantText(messages: readonly unknown[]): string {
|
|
|
862
877
|
function dedupePendingToolCallReplaysWithinContent(
|
|
863
878
|
content: ContentPart[],
|
|
864
879
|
): ContentPart[] {
|
|
865
|
-
const
|
|
880
|
+
const laterStatesByFingerprint = new Map<
|
|
881
|
+
string,
|
|
882
|
+
{
|
|
883
|
+
ids: Set<string>;
|
|
884
|
+
readerLocalKeys: Set<string>;
|
|
885
|
+
hasStableId: boolean;
|
|
886
|
+
}
|
|
887
|
+
>();
|
|
866
888
|
let changed = false;
|
|
867
889
|
const nextReversed: ContentPart[] = [];
|
|
868
890
|
|
|
@@ -870,53 +892,153 @@ function dedupePendingToolCallReplaysWithinContent(
|
|
|
870
892
|
const part = content[i];
|
|
871
893
|
if (!part) continue;
|
|
872
894
|
const fingerprint = toolCallFingerprintFromContentPart(part);
|
|
873
|
-
|
|
874
|
-
fingerprint
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
)
|
|
895
|
+
const laterState = fingerprint
|
|
896
|
+
? laterStatesByFingerprint.get(fingerprint)
|
|
897
|
+
: undefined;
|
|
898
|
+
const id = toolCallIdFromContentPart(part);
|
|
899
|
+
const readerLocalKey = id ? toolCallReaderLocalKey(id) : null;
|
|
900
|
+
const isReaderReplay = Boolean(
|
|
901
|
+
id &&
|
|
902
|
+
laterState &&
|
|
903
|
+
(laterState.ids.has(id) ||
|
|
904
|
+
(readerLocalKey && laterState.readerLocalKeys.has(readerLocalKey)) ||
|
|
905
|
+
(toolCallIdIsReaderLocal(id) && laterState.hasStableId)),
|
|
906
|
+
);
|
|
907
|
+
if (fingerprint && !toolCallPartHasResult(part) && isReaderReplay) {
|
|
878
908
|
changed = true;
|
|
879
909
|
continue;
|
|
880
910
|
}
|
|
881
|
-
if (fingerprint)
|
|
911
|
+
if (fingerprint) {
|
|
912
|
+
const state = laterState ?? {
|
|
913
|
+
ids: new Set(),
|
|
914
|
+
readerLocalKeys: new Set(),
|
|
915
|
+
hasStableId: false,
|
|
916
|
+
};
|
|
917
|
+
if (id) {
|
|
918
|
+
state.ids.add(id);
|
|
919
|
+
const key = toolCallReaderLocalKey(id);
|
|
920
|
+
if (key) state.readerLocalKeys.add(key);
|
|
921
|
+
else state.hasStableId = true;
|
|
922
|
+
}
|
|
923
|
+
laterStatesByFingerprint.set(fingerprint, state);
|
|
924
|
+
}
|
|
882
925
|
nextReversed.push(part);
|
|
883
926
|
}
|
|
884
927
|
|
|
885
928
|
return changed ? nextReversed.reverse() : content;
|
|
886
929
|
}
|
|
887
930
|
|
|
931
|
+
function pendingToolCallCountsByName(
|
|
932
|
+
content: readonly ContentPart[],
|
|
933
|
+
): Map<string, number> {
|
|
934
|
+
const counts = new Map<string, number>();
|
|
935
|
+
for (const part of content) {
|
|
936
|
+
const name = toolCallNameFromContentPart(part);
|
|
937
|
+
if (!name || toolCallProgressRank(part) >= 4) continue;
|
|
938
|
+
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
939
|
+
}
|
|
940
|
+
return counts;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
/**
|
|
944
|
+
* Linear-time longest overlap where a suffix of `renderedText` equals a prefix
|
|
945
|
+
* of `reconnectText`. The reconnect overlay is recalculated during render on
|
|
946
|
+
* every stream tick, so a descending slice/endsWith scan becomes quadratic on
|
|
947
|
+
* large repeated output.
|
|
948
|
+
*/
|
|
949
|
+
function longestSuffixPrefixOverlap(
|
|
950
|
+
renderedText: string,
|
|
951
|
+
reconnectText: string,
|
|
952
|
+
): number {
|
|
953
|
+
const maxOverlap = Math.min(renderedText.length, reconnectText.length);
|
|
954
|
+
if (maxOverlap === 0) return 0;
|
|
955
|
+
|
|
956
|
+
const pattern = reconnectText.slice(0, maxOverlap);
|
|
957
|
+
const prefixLengths = new Array<number>(pattern.length).fill(0);
|
|
958
|
+
for (let index = 1; index < pattern.length; index += 1) {
|
|
959
|
+
let matched = prefixLengths[index - 1] ?? 0;
|
|
960
|
+
while (matched > 0 && pattern[index] !== pattern[matched]) {
|
|
961
|
+
matched = prefixLengths[matched - 1] ?? 0;
|
|
962
|
+
}
|
|
963
|
+
if (pattern[index] === pattern[matched]) matched += 1;
|
|
964
|
+
prefixLengths[index] = matched;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
let matched = 0;
|
|
968
|
+
const renderedStart = renderedText.length - maxOverlap;
|
|
969
|
+
for (let index = renderedStart; index < renderedText.length; index += 1) {
|
|
970
|
+
const character = renderedText[index];
|
|
971
|
+
while (matched > 0 && character !== pattern[matched]) {
|
|
972
|
+
matched = prefixLengths[matched - 1] ?? 0;
|
|
973
|
+
}
|
|
974
|
+
if (character === pattern[matched]) matched += 1;
|
|
975
|
+
if (matched === pattern.length && index < renderedText.length - 1) {
|
|
976
|
+
matched = prefixLengths[matched - 1] ?? 0;
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
return matched;
|
|
980
|
+
}
|
|
981
|
+
|
|
888
982
|
function trimReconnectTextAlreadyRendered(
|
|
889
983
|
content: ContentPart[],
|
|
890
984
|
renderedAssistantText: string,
|
|
985
|
+
options?: { trimTailOverlap?: boolean },
|
|
891
986
|
): ContentPart[] {
|
|
892
987
|
if (!renderedAssistantText) return content;
|
|
893
988
|
|
|
894
|
-
|
|
989
|
+
const reconnectText = assistantTextFromContent(content);
|
|
990
|
+
if (!reconnectText) return content;
|
|
991
|
+
|
|
992
|
+
let overlapLength = 0;
|
|
993
|
+
if (reconnectText.startsWith(renderedAssistantText)) {
|
|
994
|
+
overlapLength = renderedAssistantText.length;
|
|
995
|
+
} else if (renderedAssistantText.startsWith(reconnectText)) {
|
|
996
|
+
overlapLength = reconnectText.length;
|
|
997
|
+
} else if (options?.trimTailOverlap) {
|
|
998
|
+
const tailOverlap = longestSuffixPrefixOverlap(
|
|
999
|
+
renderedAssistantText,
|
|
1000
|
+
reconnectText,
|
|
1001
|
+
);
|
|
1002
|
+
if (tailOverlap > 0) {
|
|
1003
|
+
// Tail reconnects begin after the last remembered event, so their first
|
|
1004
|
+
// text can be the final suffix of a thread snapshot imported in parallel.
|
|
1005
|
+
// For a partial overlap, require a whole word/phrase boundary so a
|
|
1006
|
+
// coincidental shared character at the join cannot eat legitimate text.
|
|
1007
|
+
const renderedBeforeOverlap =
|
|
1008
|
+
renderedAssistantText[renderedAssistantText.length - tailOverlap - 1];
|
|
1009
|
+
const reconnectAfterOverlap = reconnectText[tailOverlap];
|
|
1010
|
+
const isWordCharacter = (value: string | undefined) =>
|
|
1011
|
+
value ? /[\p{L}\p{N}_]/u.test(value) : false;
|
|
1012
|
+
const isWholeBoundary =
|
|
1013
|
+
!isWordCharacter(renderedBeforeOverlap) &&
|
|
1014
|
+
!isWordCharacter(reconnectAfterOverlap);
|
|
1015
|
+
if (tailOverlap === reconnectText.length || isWholeBoundary) {
|
|
1016
|
+
overlapLength = tailOverlap;
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
if (overlapLength === 0) return content;
|
|
1022
|
+
|
|
1023
|
+
let remainingOverlap = overlapLength;
|
|
895
1024
|
let changed = false;
|
|
896
1025
|
const next: ContentPart[] = [];
|
|
897
1026
|
|
|
898
1027
|
for (const part of content) {
|
|
899
|
-
if (part.type !== "text" || remainingOverlap
|
|
1028
|
+
if (part.type !== "text" || remainingOverlap === 0) {
|
|
900
1029
|
next.push(part);
|
|
901
1030
|
continue;
|
|
902
1031
|
}
|
|
903
1032
|
|
|
904
|
-
if (
|
|
905
|
-
remainingOverlap
|
|
906
|
-
changed = true;
|
|
907
|
-
continue;
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
if (part.text.startsWith(remainingOverlap)) {
|
|
911
|
-
const text = part.text.slice(remainingOverlap.length);
|
|
912
|
-
remainingOverlap = "";
|
|
1033
|
+
if (part.text.length <= remainingOverlap) {
|
|
1034
|
+
remainingOverlap -= part.text.length;
|
|
913
1035
|
changed = true;
|
|
914
|
-
if (text) next.push({ ...part, text });
|
|
915
1036
|
continue;
|
|
916
1037
|
}
|
|
917
1038
|
|
|
918
|
-
remainingOverlap
|
|
919
|
-
|
|
1039
|
+
next.push({ ...part, text: part.text.slice(remainingOverlap) });
|
|
1040
|
+
remainingOverlap = 0;
|
|
1041
|
+
changed = true;
|
|
920
1042
|
}
|
|
921
1043
|
|
|
922
1044
|
return changed ? next : content;
|
|
@@ -925,10 +1047,14 @@ function trimReconnectTextAlreadyRendered(
|
|
|
925
1047
|
export function dedupeReconnectContentAgainstMessages(
|
|
926
1048
|
content: ContentPart[],
|
|
927
1049
|
messages: readonly unknown[],
|
|
928
|
-
options?: {
|
|
1050
|
+
options?: {
|
|
1051
|
+
suppressToolRepeats?: boolean;
|
|
1052
|
+
trimTailTextOverlap?: boolean;
|
|
1053
|
+
},
|
|
929
1054
|
): ContentPart[] {
|
|
930
1055
|
if (content.length === 0) return content;
|
|
931
1056
|
const snapshotDeduped = dedupePendingToolCallReplaysWithinContent(content);
|
|
1057
|
+
const reconnectPendingCounts = pendingToolCallCountsByName(snapshotDeduped);
|
|
932
1058
|
let changed = snapshotDeduped !== content;
|
|
933
1059
|
if (messages.length === 0) return changed ? snapshotDeduped : content;
|
|
934
1060
|
const { byId, latestAssistantByFingerprint, latestAssistantByName } =
|
|
@@ -1009,6 +1135,31 @@ export function dedupeReconnectContentAgainstMessages(
|
|
|
1009
1135
|
const latestByName = name
|
|
1010
1136
|
? latestAssistantByName.get(name)
|
|
1011
1137
|
: undefined;
|
|
1138
|
+
const reconnectPendingCount = name
|
|
1139
|
+
? (reconnectPendingCounts.get(name) ?? 0)
|
|
1140
|
+
: 0;
|
|
1141
|
+
const hasReaderLocalIdentity = Boolean(
|
|
1142
|
+
(id && toolCallIdIsReaderLocal(id)) ||
|
|
1143
|
+
(latestByName &&
|
|
1144
|
+
Array.from(latestByName.pendingIds).some(
|
|
1145
|
+
toolCallIdIsReaderLocal,
|
|
1146
|
+
)),
|
|
1147
|
+
);
|
|
1148
|
+
if (
|
|
1149
|
+
options?.suppressToolRepeats &&
|
|
1150
|
+
latestByName &&
|
|
1151
|
+
latestByName.pendingIds.size === 1 &&
|
|
1152
|
+
reconnectPendingCount === 1 &&
|
|
1153
|
+
hasReaderLocalIdentity &&
|
|
1154
|
+
latestByName.pendingRank >= reconnectRank
|
|
1155
|
+
) {
|
|
1156
|
+
// During reconnect -> live-adapter handoff both accumulators own
|
|
1157
|
+
// the same run. Their pre-tool-start activity cards can have
|
|
1158
|
+
// unrelated reader-local ids and no args fingerprint, so name +
|
|
1159
|
+
// pending progress is the only stable identity available.
|
|
1160
|
+
changed = true;
|
|
1161
|
+
return false;
|
|
1162
|
+
}
|
|
1012
1163
|
const matchesRenderedLocalId =
|
|
1013
1164
|
id && latestByName
|
|
1014
1165
|
? Array.from(latestByName.ids).some((renderedId) =>
|
|
@@ -1037,6 +1188,7 @@ export function dedupeReconnectContentAgainstMessages(
|
|
|
1037
1188
|
const textDeduped = trimReconnectTextAlreadyRendered(
|
|
1038
1189
|
filtered,
|
|
1039
1190
|
renderedAssistantText,
|
|
1191
|
+
{ trimTailOverlap: options?.trimTailTextOverlap },
|
|
1040
1192
|
);
|
|
1041
1193
|
if (textDeduped !== filtered) changed = true;
|
|
1042
1194
|
return changed ? textDeduped : content;
|
|
@@ -1151,19 +1303,40 @@ function contentHasVisibleReasoning(content: unknown): boolean {
|
|
|
1151
1303
|
});
|
|
1152
1304
|
}
|
|
1153
1305
|
|
|
1306
|
+
function contentHasUnresolvedToolCall(
|
|
1307
|
+
content: unknown,
|
|
1308
|
+
toolName?: string | null,
|
|
1309
|
+
): boolean {
|
|
1310
|
+
if (!Array.isArray(content)) return false;
|
|
1311
|
+
return content.some((part) => {
|
|
1312
|
+
if (!part || typeof part !== "object") return false;
|
|
1313
|
+
const candidate = part as {
|
|
1314
|
+
type?: unknown;
|
|
1315
|
+
toolName?: unknown;
|
|
1316
|
+
result?: unknown;
|
|
1317
|
+
};
|
|
1318
|
+
return (
|
|
1319
|
+
candidate.type === "tool-call" &&
|
|
1320
|
+
candidate.result === undefined &&
|
|
1321
|
+
(!toolName || candidate.toolName === toolName)
|
|
1322
|
+
);
|
|
1323
|
+
});
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1154
1326
|
export function shouldShowGlobalRunningStatus({
|
|
1155
1327
|
showRunningInUI,
|
|
1156
1328
|
runningActivityLabel,
|
|
1329
|
+
runningActivityTool,
|
|
1157
1330
|
latestMessage,
|
|
1158
1331
|
reconnectContent,
|
|
1159
1332
|
}: {
|
|
1160
1333
|
showRunningInUI: boolean;
|
|
1161
1334
|
runningActivityLabel: string | null | undefined;
|
|
1335
|
+
runningActivityTool?: string | null;
|
|
1162
1336
|
latestMessage: unknown;
|
|
1163
1337
|
reconnectContent: readonly ContentPart[];
|
|
1164
1338
|
}): boolean {
|
|
1165
1339
|
if (!showRunningInUI) return false;
|
|
1166
|
-
if (runningActivityLabel) return true;
|
|
1167
1340
|
|
|
1168
1341
|
const message =
|
|
1169
1342
|
latestMessage && typeof latestMessage === "object"
|
|
@@ -1172,6 +1345,30 @@ export function shouldShowGlobalRunningStatus({
|
|
|
1172
1345
|
const latestMessageHasReasoning =
|
|
1173
1346
|
message?.role === "assistant" &&
|
|
1174
1347
|
contentHasVisibleReasoning(message.content);
|
|
1348
|
+
const latestMessageHasUnresolvedTool =
|
|
1349
|
+
message?.role === "assistant" &&
|
|
1350
|
+
contentHasUnresolvedToolCall(message.content);
|
|
1351
|
+
const matchingActivityToolIsVisible = Boolean(
|
|
1352
|
+
runningActivityTool &&
|
|
1353
|
+
((message?.role === "assistant" &&
|
|
1354
|
+
contentHasUnresolvedToolCall(message.content, runningActivityTool)) ||
|
|
1355
|
+
contentHasUnresolvedToolCall(reconnectContent, runningActivityTool)),
|
|
1356
|
+
);
|
|
1357
|
+
|
|
1358
|
+
// A pending card for the same tool is already the running indicator.
|
|
1359
|
+
// Rendering its global activity label as well shows the logical call twice
|
|
1360
|
+
// (for example, `generate design` plus `Writing generate design...`).
|
|
1361
|
+
if (runningActivityLabel && matchingActivityToolIsVisible) {
|
|
1362
|
+
return false;
|
|
1363
|
+
}
|
|
1364
|
+
if (
|
|
1365
|
+
!runningActivityLabel &&
|
|
1366
|
+
(latestMessageHasUnresolvedTool ||
|
|
1367
|
+
contentHasUnresolvedToolCall(reconnectContent))
|
|
1368
|
+
) {
|
|
1369
|
+
return false;
|
|
1370
|
+
}
|
|
1371
|
+
if (runningActivityLabel) return true;
|
|
1175
1372
|
|
|
1176
1373
|
return (
|
|
1177
1374
|
!latestMessageHasReasoning && !contentHasVisibleReasoning(reconnectContent)
|
|
@@ -2041,6 +2238,10 @@ const AssistantChatInner = forwardRef<
|
|
|
2041
2238
|
const reconnectCanMaterializeRef = useRef(false);
|
|
2042
2239
|
const reconnectAbortRef = useRef<AbortController | null>(null);
|
|
2043
2240
|
const reconnectAutoRecoveryCountRef = useRef(0);
|
|
2241
|
+
const reconnectOwnerMountedRef = useReconnectReaderOwner(
|
|
2242
|
+
reconnectRunIdRef,
|
|
2243
|
+
reconnectAbortRef,
|
|
2244
|
+
);
|
|
2044
2245
|
const [pendingReconnectRecovery, setPendingReconnectRecovery] =
|
|
2045
2246
|
useState<PendingReconnectRecovery | null>(null);
|
|
2046
2247
|
// Nuclear stop: user clicked stop. Clears the stop button/indicator AND
|
|
@@ -2386,6 +2587,7 @@ const AssistantChatInner = forwardRef<
|
|
|
2386
2587
|
const startReconnectToRun = useCallback(
|
|
2387
2588
|
(runInfo: ActiveRunLookup): boolean => {
|
|
2388
2589
|
if (
|
|
2590
|
+
!reconnectOwnerMountedRef.current ||
|
|
2389
2591
|
!threadId ||
|
|
2390
2592
|
!runInfo.runId ||
|
|
2391
2593
|
(runInfo.status !== "running" && !isReplayableTerminalRun(runInfo))
|
|
@@ -2596,6 +2798,12 @@ const AssistantChatInner = forwardRef<
|
|
|
2596
2798
|
rafPending = true;
|
|
2597
2799
|
requestAnimationFrame(() => {
|
|
2598
2800
|
rafPending = false;
|
|
2801
|
+
if (
|
|
2802
|
+
!reconnectOwnerMountedRef.current ||
|
|
2803
|
+
reconnectRunIdRef.current !== runId
|
|
2804
|
+
) {
|
|
2805
|
+
return;
|
|
2806
|
+
}
|
|
2599
2807
|
setReconnectContent(latestSnapshot);
|
|
2600
2808
|
});
|
|
2601
2809
|
};
|
|
@@ -2665,6 +2873,11 @@ const AssistantChatInner = forwardRef<
|
|
|
2665
2873
|
clearInterval(idleCheck);
|
|
2666
2874
|
}
|
|
2667
2875
|
|
|
2876
|
+
// A newer reader, live adapter, stop action, or component unmount took
|
|
2877
|
+
// ownership while this async loop was unwinding. Do not let the stale
|
|
2878
|
+
// generation refresh/import or update reconnect state afterwards.
|
|
2879
|
+
if (reconnectRunIdRef.current !== runId) return;
|
|
2880
|
+
|
|
2668
2881
|
if (noProgressDuringReconnect && reconnectRunIdRef.current === runId) {
|
|
2669
2882
|
const reconnectErrorCode =
|
|
2670
2883
|
reconnectTerminalReason === "run_timeout"
|
|
@@ -3630,6 +3843,7 @@ const AssistantChatInner = forwardRef<
|
|
|
3630
3843
|
const stillNeeded =
|
|
3631
3844
|
dedupeReconnectContentAgainstMessages(reconnectContent, messages, {
|
|
3632
3845
|
suppressToolRepeats: true,
|
|
3846
|
+
trimTailTextOverlap: true,
|
|
3633
3847
|
}).length > 0;
|
|
3634
3848
|
if (!stillNeeded) {
|
|
3635
3849
|
setReconnectContent([]);
|
|
@@ -4236,7 +4450,11 @@ const AssistantChatInner = forwardRef<
|
|
|
4236
4450
|
const visibleReconnectContent = dedupeReconnectContentAgainstMessages(
|
|
4237
4451
|
reconnectContent,
|
|
4238
4452
|
messages,
|
|
4239
|
-
{
|
|
4453
|
+
{
|
|
4454
|
+
suppressToolRepeats: adapterHandoffPending,
|
|
4455
|
+
trimTailTextOverlap:
|
|
4456
|
+
adapterHandoffPending || reconnectTailOnlyRef.current,
|
|
4457
|
+
},
|
|
4240
4458
|
);
|
|
4241
4459
|
const chatScrollResetKey = `${tabId ?? ""}:${threadId ?? ""}`;
|
|
4242
4460
|
|
|
@@ -4262,11 +4480,18 @@ const AssistantChatInner = forwardRef<
|
|
|
4262
4480
|
);
|
|
4263
4481
|
const latestMessage = messages[messages.length - 1];
|
|
4264
4482
|
const latestMessageRole = latestMessage?.role;
|
|
4483
|
+
const reconnectStatusContent =
|
|
4484
|
+
visibleReconnectContent.length > 0
|
|
4485
|
+
? visibleReconnectContent
|
|
4486
|
+
: reconnectContent.length === 0
|
|
4487
|
+
? reconnectActivityContent
|
|
4488
|
+
: [];
|
|
4265
4489
|
const showGlobalRunningStatus = shouldShowGlobalRunningStatus({
|
|
4266
4490
|
showRunningInUI,
|
|
4267
4491
|
runningActivityLabel,
|
|
4492
|
+
runningActivityTool,
|
|
4268
4493
|
latestMessage,
|
|
4269
|
-
reconnectContent:
|
|
4494
|
+
reconnectContent: reconnectStatusContent,
|
|
4270
4495
|
});
|
|
4271
4496
|
const latestAssistantWasPlan =
|
|
4272
4497
|
latestMessageRole === "assistant" &&
|
|
@@ -976,7 +976,7 @@ export function ReasoningCell({
|
|
|
976
976
|
durationMs?: number | null;
|
|
977
977
|
}) {
|
|
978
978
|
const embeddedInWorkSummary = React.useContext(WorkSummaryContentContext);
|
|
979
|
-
const [open, setOpen] = useState(defaultOpen ??
|
|
979
|
+
const [open, setOpen] = useState(defaultOpen ?? true);
|
|
980
980
|
const trimmed = text.trim();
|
|
981
981
|
if (!trimmed && !isStreaming) return null;
|
|
982
982
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
|
|
3
|
+
type MutableRef<T> = { current: T };
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Owns the lifetime of AssistantChat's reconnect reader. The inner chat can be
|
|
7
|
+
* remounted while its outer runtime survives (error-boundary recovery, keyed
|
|
8
|
+
* thread changes), so unmount must explicitly retire the old reader.
|
|
9
|
+
*/
|
|
10
|
+
export function useReconnectReaderOwner(
|
|
11
|
+
reconnectRunIdRef: MutableRef<string | null>,
|
|
12
|
+
reconnectAbortRef: MutableRef<AbortController | null>,
|
|
13
|
+
): MutableRef<boolean> {
|
|
14
|
+
const mountedRef = useRef(false);
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
mountedRef.current = true;
|
|
18
|
+
return () => {
|
|
19
|
+
mountedRef.current = false;
|
|
20
|
+
const reconnectAbort = reconnectAbortRef.current;
|
|
21
|
+
reconnectRunIdRef.current = null;
|
|
22
|
+
reconnectAbortRef.current = null;
|
|
23
|
+
reconnectAbort?.abort();
|
|
24
|
+
};
|
|
25
|
+
}, [reconnectAbortRef, reconnectRunIdRef]);
|
|
26
|
+
|
|
27
|
+
return mountedRef;
|
|
28
|
+
}
|
|
@@ -4,7 +4,11 @@ import {
|
|
|
4
4
|
IconUpload,
|
|
5
5
|
IconX,
|
|
6
6
|
} from "@tabler/icons-react";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
keepPreviousData,
|
|
9
|
+
useQuery,
|
|
10
|
+
useQueryClient,
|
|
11
|
+
} from "@tanstack/react-query";
|
|
8
12
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
9
13
|
|
|
10
14
|
import { sendToAgentChat } from "./agent-chat.js";
|
|
@@ -1051,6 +1055,10 @@ export function useGuidedQuestionFlow({
|
|
|
1051
1055
|
},
|
|
1052
1056
|
refetchInterval: resolvedRefetchInterval,
|
|
1053
1057
|
structuralSharing: false,
|
|
1058
|
+
// A matching app-state event changes the query key. Preserve the existing
|
|
1059
|
+
// payload while the replacement read is in flight so full-canvas question
|
|
1060
|
+
// forms do not unmount and flash during routine sync updates.
|
|
1061
|
+
placeholderData: keepPreviousData,
|
|
1054
1062
|
});
|
|
1055
1063
|
|
|
1056
1064
|
useEffect(() => {
|
|
@@ -43,16 +43,15 @@ interface PlatformInfo {
|
|
|
43
43
|
const PLATFORMS: PlatformInfo[] = [
|
|
44
44
|
{
|
|
45
45
|
id: "slack",
|
|
46
|
-
label: "Slack",
|
|
46
|
+
label: "Slack (legacy)",
|
|
47
47
|
icon: IconBrandSlack,
|
|
48
|
-
description:
|
|
48
|
+
description:
|
|
49
|
+
"Legacy single-workspace setup. Use Settings → Messaging for new Slack connections.",
|
|
49
50
|
envVars: ["SLACK_BOT_TOKEN", "SLACK_SIGNING_SECRET"],
|
|
50
51
|
setupSteps: [
|
|
51
|
-
"
|
|
52
|
-
|
|
53
|
-
"
|
|
54
|
-
"Install the app to your workspace",
|
|
55
|
-
"Copy the Bot Token and Signing Secret into your environment",
|
|
52
|
+
"Open Settings → Messaging for the supported managed Slack setup",
|
|
53
|
+
"Use this legacy setup only for an existing single-workspace installation",
|
|
54
|
+
"Managed OAuth stores workspace bot tokens automatically; do not add SLACK_BOT_TOKEN for new connections",
|
|
56
55
|
],
|
|
57
56
|
docsUrl: "https://api.slack.com/apps",
|
|
58
57
|
},
|
|
@@ -6,6 +6,7 @@ import { agentNativePath } from "../api-path.js";
|
|
|
6
6
|
interface UsageBucket {
|
|
7
7
|
key: string;
|
|
8
8
|
cents: number;
|
|
9
|
+
cost: UsageCostAggregate;
|
|
9
10
|
calls: number;
|
|
10
11
|
inputTokens: number;
|
|
11
12
|
outputTokens: number;
|
|
@@ -16,6 +17,7 @@ interface UsageBucket {
|
|
|
16
17
|
interface DailyBucket {
|
|
17
18
|
date: string;
|
|
18
19
|
cents: number;
|
|
20
|
+
cost: UsageCostAggregate;
|
|
19
21
|
calls: number;
|
|
20
22
|
}
|
|
21
23
|
|
|
@@ -30,8 +32,14 @@ interface UsageRecentEntry {
|
|
|
30
32
|
cacheReadTokens: number;
|
|
31
33
|
cacheWriteTokens: number;
|
|
32
34
|
cents: number;
|
|
35
|
+
costSource: "reported" | "estimated" | "unavailable";
|
|
33
36
|
}
|
|
34
37
|
|
|
38
|
+
type UsageCostAggregate =
|
|
39
|
+
| { status: "known"; knownCents: number; unavailableCalls: 0 }
|
|
40
|
+
| { status: "partial"; knownCents: number; unavailableCalls: number }
|
|
41
|
+
| { status: "unavailable"; knownCents: 0; unavailableCalls: number };
|
|
42
|
+
|
|
35
43
|
interface UsageBillingMode {
|
|
36
44
|
unit: "usd" | "builder-credits";
|
|
37
45
|
label: string;
|
|
@@ -44,6 +52,7 @@ interface UsageBillingMode {
|
|
|
44
52
|
interface UsageSummary {
|
|
45
53
|
billing?: UsageBillingMode;
|
|
46
54
|
totalCents: number;
|
|
55
|
+
totalCost: UsageCostAggregate;
|
|
47
56
|
totalCalls: number;
|
|
48
57
|
totalInputTokens: number;
|
|
49
58
|
totalOutputTokens: number;
|
|
@@ -103,6 +112,15 @@ function formatSpend(cents: number, billing: UsageBillingMode): string {
|
|
|
103
112
|
return `$${(cents / 100).toFixed(2)}`;
|
|
104
113
|
}
|
|
105
114
|
|
|
115
|
+
function formatAggregateCost(
|
|
116
|
+
cost: UsageCostAggregate,
|
|
117
|
+
billing: UsageBillingMode,
|
|
118
|
+
): string {
|
|
119
|
+
if (cost.status === "known") return formatSpend(cost.knownCents, billing);
|
|
120
|
+
if (cost.status === "unavailable") return "Unknown";
|
|
121
|
+
return `${formatSpend(cost.knownCents, billing)} + ${cost.unavailableCalls} unpriced`;
|
|
122
|
+
}
|
|
123
|
+
|
|
106
124
|
function formatTokens(n: number): string {
|
|
107
125
|
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
|
|
108
126
|
if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`;
|
|
@@ -139,7 +157,7 @@ function BucketBars({
|
|
|
139
157
|
{b.key || "(none)"}
|
|
140
158
|
</span>
|
|
141
159
|
<span className="shrink-0 text-muted-foreground tabular-nums">
|
|
142
|
-
{
|
|
160
|
+
{formatAggregateCost(b.cost, billing)}
|
|
143
161
|
<span className="ms-1 opacity-60">
|
|
144
162
|
· {formatTokens(b.inputTokens + b.outputTokens)} tok
|
|
145
163
|
</span>
|
|
@@ -183,7 +201,7 @@ function DailySparkline({
|
|
|
183
201
|
(displayAmountFromCostCents(d.cents, billing) / max) * 100,
|
|
184
202
|
)}%`,
|
|
185
203
|
}}
|
|
186
|
-
title={`${d.date}: ${
|
|
204
|
+
title={`${d.date}: ${formatAggregateCost(d.cost, billing)} (${d.calls} calls)`}
|
|
187
205
|
/>
|
|
188
206
|
))}
|
|
189
207
|
</div>
|
|
@@ -266,7 +284,7 @@ export function UsageSection() {
|
|
|
266
284
|
: "Total spend"}
|
|
267
285
|
</div>
|
|
268
286
|
<div className="text-[18px] font-semibold tabular-nums">
|
|
269
|
-
{
|
|
287
|
+
{formatAggregateCost(data.totalCost, billing)}
|
|
270
288
|
</div>
|
|
271
289
|
</div>
|
|
272
290
|
<div className="text-end">
|
|
@@ -352,7 +370,9 @@ export function UsageSection() {
|
|
|
352
370
|
</div>
|
|
353
371
|
</div>
|
|
354
372
|
<div className="shrink-0 text-end tabular-nums text-muted-foreground">
|
|
355
|
-
{
|
|
373
|
+
{r.costSource === "unavailable"
|
|
374
|
+
? "Unknown"
|
|
375
|
+
: formatSpend(r.cents, billing)}
|
|
356
376
|
</div>
|
|
357
377
|
</div>
|
|
358
378
|
))}
|
|
@@ -370,7 +390,8 @@ export function UsageSection() {
|
|
|
370
390
|
<p className="text-[10px] text-muted-foreground">
|
|
371
391
|
Spend is estimated from published Anthropic pricing and your own
|
|
372
392
|
recorded token counts. Cached input is priced at ~10% of regular
|
|
373
|
-
input.
|
|
393
|
+
input. Calls without provider pricing are marked unknown and
|
|
394
|
+
excluded from known-cost subtotals.
|
|
374
395
|
</p>
|
|
375
396
|
)}
|
|
376
397
|
</>
|