@agent-native/core 0.84.56 → 0.84.58
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 +1 -1
- package/corpus/core/CHANGELOG.md +14 -0
- package/corpus/core/docs/content/locales/ar-SA/template-analytics.mdx +2 -0
- package/corpus/core/docs/content/locales/ar-SA/template-clips.mdx +2 -0
- package/corpus/core/docs/content/locales/de-DE/template-analytics.mdx +2 -0
- package/corpus/core/docs/content/locales/de-DE/template-clips.mdx +2 -0
- package/corpus/core/docs/content/locales/es-ES/template-analytics.mdx +2 -0
- package/corpus/core/docs/content/locales/es-ES/template-clips.mdx +2 -0
- package/corpus/core/docs/content/locales/fr-FR/template-analytics.mdx +2 -0
- package/corpus/core/docs/content/locales/fr-FR/template-clips.mdx +2 -0
- package/corpus/core/docs/content/locales/hi-IN/template-analytics.mdx +2 -0
- package/corpus/core/docs/content/locales/hi-IN/template-clips.mdx +2 -0
- package/corpus/core/docs/content/locales/ja-JP/template-analytics.mdx +2 -0
- package/corpus/core/docs/content/locales/ja-JP/template-clips.mdx +2 -0
- package/corpus/core/docs/content/locales/ko-KR/template-analytics.mdx +2 -0
- package/corpus/core/docs/content/locales/ko-KR/template-clips.mdx +2 -0
- package/corpus/core/docs/content/locales/pt-BR/template-analytics.mdx +2 -0
- package/corpus/core/docs/content/locales/pt-BR/template-clips.mdx +2 -0
- package/corpus/core/docs/content/locales/zh-CN/template-analytics.mdx +2 -0
- package/corpus/core/docs/content/locales/zh-CN/template-clips.mdx +2 -0
- package/corpus/core/docs/content/locales/zh-TW/template-analytics.mdx +2 -0
- package/corpus/core/docs/content/locales/zh-TW/template-clips.mdx +2 -0
- package/corpus/core/docs/content/template-analytics.mdx +2 -0
- package/corpus/core/docs/content/template-clips.mdx +22 -10
- package/corpus/core/docs/content/tracking.mdx +2 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/action.ts +11 -0
- package/corpus/core/src/agent/production-agent.ts +24 -46
- package/corpus/core/src/client/AssistantChat.tsx +69 -12
- package/corpus/core/src/client/MultiTabAssistantChat.tsx +1 -1
- package/corpus/core/src/client/conversation/use-near-bottom-autoscroll.ts +45 -2
- package/corpus/core/src/coding-tools/run-code.ts +1 -0
- package/corpus/core/src/integrations/webhook-handler.ts +10 -9
- package/corpus/core/src/server/action-discovery.ts +3 -0
- package/corpus/core/src/server/credential-provider.ts +11 -6
- package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +8 -0
- package/corpus/templates/analytics/AGENTS.md +5 -0
- package/corpus/templates/analytics/actions/create-session-replay-agent-link.ts +31 -0
- package/corpus/templates/analytics/app/i18n/zh-TW.ts +2 -0
- package/corpus/templates/analytics/app/i18n-data.ts +20 -0
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +65 -3
- package/corpus/templates/analytics/changelog/2026-07-02-session-replays-can-be-copied-as-temporary-private-links-for-agents.md +6 -0
- package/corpus/templates/analytics/server/handlers/session-replay.ts +78 -0
- package/corpus/templates/analytics/server/lib/agent-chat-plan-mode.ts +29 -0
- package/corpus/templates/analytics/server/lib/session-replay-agent-context.ts +313 -0
- package/corpus/templates/analytics/server/lib/session-replay.ts +198 -3
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +9 -1
- package/corpus/templates/analytics/server/routes/[...page].get.ts +104 -1
- package/corpus/templates/analytics/server/routes/api/session-replay/agent-context.json.get.ts +48 -0
- package/corpus/templates/analytics/server/routes/api/session-replay/agent-events.json.get.ts +63 -0
- package/corpus/templates/analytics/shared/session-replay-agent-access.ts +10 -0
- package/corpus/templates/clips/.agents/skills/video-sharing/SKILL.md +14 -8
- package/corpus/templates/clips/AGENTS.md +3 -2
- package/corpus/templates/clips/actions/create-recording-agent-link.ts +90 -0
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +43 -46
- package/corpus/templates/clips/app/components/sharing/share-ui.tsx +5 -3
- package/corpus/templates/clips/app/i18n/ar-SA.ts +1 -1
- package/corpus/templates/clips/app/i18n/de-DE.ts +1 -1
- package/corpus/templates/clips/app/i18n/en-US.ts +1 -1
- package/corpus/templates/clips/app/i18n/es-ES.ts +1 -1
- package/corpus/templates/clips/app/i18n/fr-FR.ts +1 -1
- package/corpus/templates/clips/app/i18n/hi-IN.ts +1 -1
- package/corpus/templates/clips/app/i18n/ja-JP.ts +1 -1
- package/corpus/templates/clips/app/i18n/ko-KR.ts +1 -1
- package/corpus/templates/clips/app/i18n/pt-BR.ts +1 -1
- package/corpus/templates/clips/app/i18n/zh-CN.ts +1 -1
- package/corpus/templates/clips/app/i18n/zh-TW.ts +1 -1
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +100 -46
- package/corpus/templates/clips/changelog/2026-07-02-private-recordings-can-be-shared-with-agents-through-temporary-links-without-making-them-public.md +6 -0
- package/corpus/templates/clips/server/lib/public-agent-context.ts +26 -13
- package/corpus/templates/clips/server/routes/api/agent-context.json.get.ts +2 -1
- package/corpus/templates/clips/server/routes/api/agent-frame.jpg.get.ts +2 -1
- package/corpus/templates/clips/server/routes/api/agent-transcript.json.get.ts +2 -1
- package/corpus/templates/clips/server/routes/api/public-recording.get.ts +7 -2
- package/corpus/templates/clips/shared/agent-context.ts +5 -0
- package/corpus/templates/design/actions/list-design-extensions.ts +2 -2
- package/corpus/templates/design/app/components/design/CodeWorkbenchHost.tsx +11 -89
- package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +314 -181
- package/corpus/templates/design/app/components/design/DesignImportPanel.tsx +92 -134
- package/corpus/templates/design/app/components/design/EditPanel.tsx +93 -19
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +18 -26
- package/corpus/templates/design/app/components/design/TokensPanel.tsx +220 -192
- package/corpus/templates/design/app/components/design/code-workbench-theme.ts +150 -0
- package/corpus/templates/design/app/i18n/zh-TW.ts +5 -7
- package/corpus/templates/design/app/i18n-data.ts +61 -77
- package/corpus/templates/design/app/lib/design-import.ts +4 -1
- package/corpus/templates/design/app/pages/DesignEditor.tsx +97 -48
- package/corpus/templates/design/changelog/2026-07-02-design-inspector-and-canvas-controls-stay-consistent-when-se.md +6 -0
- package/corpus/templates/design/changelog/2026-07-02-fixed-a-crash-when-opening-the-code-editor.md +6 -0
- package/corpus/templates/design/changelog/2026-07-02-fixed-motion-drawer-open-transition.md +6 -0
- package/corpus/templates/design/changelog/2026-07-02-improved-design-assets-panel.md +6 -0
- package/dist/action.d.ts +8 -0
- package/dist/action.d.ts.map +1 -1
- package/dist/action.js +3 -0
- package/dist/action.js.map +1 -1
- package/dist/agent/production-agent.d.ts +10 -11
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +21 -44
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +1 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +61 -11
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +1 -1
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/conversation/use-near-bottom-autoscroll.d.ts.map +1 -1
- package/dist/client/conversation/use-near-bottom-autoscroll.js +43 -2
- package/dist/client/conversation/use-near-bottom-autoscroll.js.map +1 -1
- package/dist/coding-tools/run-code.d.ts.map +1 -1
- package/dist/coding-tools/run-code.js +1 -0
- package/dist/coding-tools/run-code.js.map +1 -1
- package/dist/collab/routes.d.ts +2 -2
- package/dist/integrations/webhook-handler.d.ts +1 -0
- package/dist/integrations/webhook-handler.d.ts.map +1 -1
- package/dist/integrations/webhook-handler.js +9 -9
- package/dist/integrations/webhook-handler.js.map +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +3 -3
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/server/action-discovery.d.ts.map +1 -1
- package/dist/server/action-discovery.js +3 -0
- package/dist/server/action-discovery.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/credential-provider.d.ts +4 -0
- package/dist/server/credential-provider.d.ts.map +1 -1
- package/dist/server/credential-provider.js +13 -7
- package/dist/server/credential-provider.js.map +1 -1
- package/docs/content/locales/ar-SA/template-analytics.mdx +2 -0
- package/docs/content/locales/ar-SA/template-clips.mdx +2 -0
- package/docs/content/locales/de-DE/template-analytics.mdx +2 -0
- package/docs/content/locales/de-DE/template-clips.mdx +2 -0
- package/docs/content/locales/es-ES/template-analytics.mdx +2 -0
- package/docs/content/locales/es-ES/template-clips.mdx +2 -0
- package/docs/content/locales/fr-FR/template-analytics.mdx +2 -0
- package/docs/content/locales/fr-FR/template-clips.mdx +2 -0
- package/docs/content/locales/hi-IN/template-analytics.mdx +2 -0
- package/docs/content/locales/hi-IN/template-clips.mdx +2 -0
- package/docs/content/locales/ja-JP/template-analytics.mdx +2 -0
- package/docs/content/locales/ja-JP/template-clips.mdx +2 -0
- package/docs/content/locales/ko-KR/template-analytics.mdx +2 -0
- package/docs/content/locales/ko-KR/template-clips.mdx +2 -0
- package/docs/content/locales/pt-BR/template-analytics.mdx +2 -0
- package/docs/content/locales/pt-BR/template-clips.mdx +2 -0
- package/docs/content/locales/zh-CN/template-analytics.mdx +2 -0
- package/docs/content/locales/zh-CN/template-clips.mdx +2 -0
- package/docs/content/locales/zh-TW/template-analytics.mdx +2 -0
- package/docs/content/locales/zh-TW/template-clips.mdx +2 -0
- package/docs/content/template-analytics.mdx +2 -0
- package/docs/content/template-clips.mdx +22 -10
- package/docs/content/tracking.mdx +2 -0
- package/package.json +1 -1
|
@@ -588,6 +588,61 @@ export function reconnectActivityFallbackContent(
|
|
|
588
588
|
];
|
|
589
589
|
}
|
|
590
590
|
|
|
591
|
+
function toolCallIdFromContentPart(part: unknown): string | null {
|
|
592
|
+
if (!part || typeof part !== "object") return null;
|
|
593
|
+
const candidate = part as { type?: unknown; toolCallId?: unknown };
|
|
594
|
+
if (candidate.type !== "tool-call") return null;
|
|
595
|
+
return typeof candidate.toolCallId === "string" && candidate.toolCallId
|
|
596
|
+
? candidate.toolCallId
|
|
597
|
+
: null;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
function toolCallPartHasResult(part: unknown): boolean {
|
|
601
|
+
if (!part || typeof part !== "object") return false;
|
|
602
|
+
const candidate = part as { type?: unknown; result?: unknown };
|
|
603
|
+
return candidate.type === "tool-call" && "result" in candidate;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function collectRenderedToolCallStates(
|
|
607
|
+
messages: readonly unknown[],
|
|
608
|
+
): Map<string, { hasResult: boolean }> {
|
|
609
|
+
const states = new Map<string, { hasResult: boolean }>();
|
|
610
|
+
for (const message of messages) {
|
|
611
|
+
const msg = (message as { message?: unknown })?.message ?? message;
|
|
612
|
+
const content = (msg as { content?: unknown })?.content;
|
|
613
|
+
if (!Array.isArray(content)) continue;
|
|
614
|
+
for (const part of content) {
|
|
615
|
+
const id = toolCallIdFromContentPart(part);
|
|
616
|
+
if (!id) continue;
|
|
617
|
+
const existing = states.get(id);
|
|
618
|
+
states.set(id, {
|
|
619
|
+
hasResult: Boolean(existing?.hasResult || toolCallPartHasResult(part)),
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
return states;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
export function dedupeReconnectContentAgainstMessages(
|
|
627
|
+
content: ContentPart[],
|
|
628
|
+
messages: readonly unknown[],
|
|
629
|
+
): ContentPart[] {
|
|
630
|
+
if (content.length === 0 || messages.length === 0) return content;
|
|
631
|
+
const renderedToolCallStates = collectRenderedToolCallStates(messages);
|
|
632
|
+
if (renderedToolCallStates.size === 0) return content;
|
|
633
|
+
|
|
634
|
+
let changed = false;
|
|
635
|
+
const filtered = content.filter((part) => {
|
|
636
|
+
const id = toolCallIdFromContentPart(part);
|
|
637
|
+
const existing = id ? renderedToolCallStates.get(id) : undefined;
|
|
638
|
+
if (!id || !existing) return true;
|
|
639
|
+
if (toolCallPartHasResult(part) && !existing.hasResult) return true;
|
|
640
|
+
changed = true;
|
|
641
|
+
return false;
|
|
642
|
+
});
|
|
643
|
+
return changed ? filtered : content;
|
|
644
|
+
}
|
|
645
|
+
|
|
591
646
|
const RECOVERY_USER_MESSAGE_PREFIXES = [
|
|
592
647
|
"Continue from where you left off",
|
|
593
648
|
"Continue from where you stopped",
|
|
@@ -1540,15 +1595,10 @@ const AssistantChatInner = forwardRef<
|
|
|
1540
1595
|
});
|
|
1541
1596
|
const reconnectActivityContent = useMemo(
|
|
1542
1597
|
() =>
|
|
1543
|
-
|
|
1598
|
+
isReconnecting || reconnectFrozen
|
|
1544
1599
|
? reconnectActivityFallbackContent(runningActivityTool)
|
|
1545
1600
|
: [],
|
|
1546
|
-
[
|
|
1547
|
-
isReconnecting,
|
|
1548
|
-
reconnectFrozen,
|
|
1549
|
-
reconnectContent.length,
|
|
1550
|
-
runningActivityTool,
|
|
1551
|
-
],
|
|
1601
|
+
[isReconnecting, reconnectFrozen, runningActivityTool],
|
|
1552
1602
|
);
|
|
1553
1603
|
const lastBroadcastRunningRef = useRef(isRunning);
|
|
1554
1604
|
const tiptapRef = useRef<TiptapComposerHandle>(null);
|
|
@@ -3287,14 +3337,19 @@ const AssistantChatInner = forwardRef<
|
|
|
3287
3337
|
],
|
|
3288
3338
|
);
|
|
3289
3339
|
|
|
3340
|
+
const visibleReconnectContent = useMemo(
|
|
3341
|
+
() => dedupeReconnectContentAgainstMessages(reconnectContent, messages),
|
|
3342
|
+
[messages, reconnectContent],
|
|
3343
|
+
);
|
|
3344
|
+
|
|
3290
3345
|
const autoscrollFollowKey = useMemo(
|
|
3291
3346
|
() =>
|
|
3292
3347
|
[
|
|
3293
3348
|
messages.map(messageFollowKey).join(";"),
|
|
3294
3349
|
`q:${queuedMessages.map(queuedMessageFollowKey).join("|")}`,
|
|
3295
|
-
`r:${reconnectContentFollowKey(
|
|
3350
|
+
`r:${reconnectContentFollowKey(visibleReconnectContent)}`,
|
|
3296
3351
|
].join(";;"),
|
|
3297
|
-
[messages, queuedMessages,
|
|
3352
|
+
[messages, queuedMessages, visibleReconnectContent],
|
|
3298
3353
|
);
|
|
3299
3354
|
|
|
3300
3355
|
const {
|
|
@@ -3802,11 +3857,13 @@ const AssistantChatInner = forwardRef<
|
|
|
3802
3857
|
/>
|
|
3803
3858
|
)}
|
|
3804
3859
|
{(isReconnecting || reconnectFrozen) &&
|
|
3805
|
-
|
|
3806
|
-
<ReconnectStreamMessage
|
|
3860
|
+
visibleReconnectContent.length > 0 && (
|
|
3861
|
+
<ReconnectStreamMessage
|
|
3862
|
+
content={visibleReconnectContent}
|
|
3863
|
+
/>
|
|
3807
3864
|
)}
|
|
3808
3865
|
{(isReconnecting || reconnectFrozen) &&
|
|
3809
|
-
|
|
3866
|
+
visibleReconnectContent.length === 0 &&
|
|
3810
3867
|
reconnectActivityContent.length > 0 && (
|
|
3811
3868
|
<ReconnectStreamMessage
|
|
3812
3869
|
content={reconnectActivityContent}
|
|
@@ -283,7 +283,7 @@ function ScopeBadge({
|
|
|
283
283
|
<PopoverTrigger asChild>
|
|
284
284
|
<button
|
|
285
285
|
type="button"
|
|
286
|
-
className="inline-flex h-7 min-w-0 max-w-full cursor-pointer items-center gap-1.5 rounded-t-lg border border-b-0 border-input bg-background px-3 text-muted-foreground
|
|
286
|
+
className="inline-flex h-7 min-w-0 max-w-full cursor-pointer items-center gap-1.5 rounded-t-lg border border-b-0 border-input bg-background px-3 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground sm:max-w-72"
|
|
287
287
|
aria-label={heading}
|
|
288
288
|
>
|
|
289
289
|
<IconLink size={11} className="shrink-0 opacity-70" />
|
|
@@ -7,6 +7,41 @@ export interface UseNearBottomAutoscrollOptions {
|
|
|
7
7
|
enabled?: boolean;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
function eventElementTarget(event: Event): HTMLElement | null {
|
|
11
|
+
const target = event.target;
|
|
12
|
+
if (!(target instanceof Node)) return null;
|
|
13
|
+
if (target instanceof HTMLElement) return target;
|
|
14
|
+
return target.parentElement;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function canScrollElementVertically(
|
|
18
|
+
element: HTMLElement,
|
|
19
|
+
deltaY: number,
|
|
20
|
+
): boolean {
|
|
21
|
+
if (element.scrollHeight <= element.clientHeight) return false;
|
|
22
|
+
const style = window.getComputedStyle(element);
|
|
23
|
+
const overflowY = style.overflowY || style.overflow;
|
|
24
|
+
if (!/(auto|scroll|overlay)/.test(overflowY)) return false;
|
|
25
|
+
if (deltaY < 0) return element.scrollTop > 0;
|
|
26
|
+
if (deltaY > 0) {
|
|
27
|
+
return element.scrollTop + element.clientHeight < element.scrollHeight - 1;
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function nestedScrollableConsumesVerticalIntent(
|
|
33
|
+
event: Event,
|
|
34
|
+
root: HTMLElement,
|
|
35
|
+
deltaY: number,
|
|
36
|
+
): boolean {
|
|
37
|
+
let element = eventElementTarget(event);
|
|
38
|
+
while (element && element !== root) {
|
|
39
|
+
if (canScrollElementVertically(element, deltaY)) return true;
|
|
40
|
+
element = element.parentElement;
|
|
41
|
+
}
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
|
|
10
45
|
export function useNearBottomAutoscroll<TElement extends HTMLElement>({
|
|
11
46
|
followKey,
|
|
12
47
|
streaming = false,
|
|
@@ -83,6 +118,9 @@ export function useNearBottomAutoscroll<TElement extends HTMLElement>({
|
|
|
83
118
|
let lastScrollHeight = el.scrollHeight;
|
|
84
119
|
|
|
85
120
|
const onWheel = (event: WheelEvent) => {
|
|
121
|
+
if (nestedScrollableConsumesVerticalIntent(event, el, event.deltaY)) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
86
124
|
if (event.deltaY < 0) detachFromBottom();
|
|
87
125
|
};
|
|
88
126
|
|
|
@@ -95,7 +133,10 @@ export function useNearBottomAutoscroll<TElement extends HTMLElement>({
|
|
|
95
133
|
if (nextTouchY == null) return;
|
|
96
134
|
const lastTouchY = lastTouchYRef.current;
|
|
97
135
|
if (lastTouchY != null && nextTouchY > lastTouchY) {
|
|
98
|
-
|
|
136
|
+
const deltaY = lastTouchY - nextTouchY;
|
|
137
|
+
if (!nestedScrollableConsumesVerticalIntent(event, el, deltaY)) {
|
|
138
|
+
detachFromBottom();
|
|
139
|
+
}
|
|
99
140
|
}
|
|
100
141
|
lastTouchYRef.current = nextTouchY;
|
|
101
142
|
};
|
|
@@ -104,7 +145,8 @@ export function useNearBottomAutoscroll<TElement extends HTMLElement>({
|
|
|
104
145
|
lastTouchYRef.current = null;
|
|
105
146
|
};
|
|
106
147
|
|
|
107
|
-
const onScroll = () => {
|
|
148
|
+
const onScroll = (event: Event) => {
|
|
149
|
+
if (event.target !== el) return;
|
|
108
150
|
const previousScrollTop = lastScrollTopRef.current;
|
|
109
151
|
const nextScrollTop = el.scrollTop;
|
|
110
152
|
const nextScrollHeight = el.scrollHeight;
|
|
@@ -133,6 +175,7 @@ export function useNearBottomAutoscroll<TElement extends HTMLElement>({
|
|
|
133
175
|
event.key === "Home" ||
|
|
134
176
|
(event.key === " " && event.shiftKey)
|
|
135
177
|
) {
|
|
178
|
+
if (nestedScrollableConsumesVerticalIntent(event, el, -1)) return;
|
|
136
179
|
detachFromBottom();
|
|
137
180
|
}
|
|
138
181
|
};
|
|
@@ -36,7 +36,10 @@ import { isLocalDatabase } from "../db/client.js";
|
|
|
36
36
|
import { resolveOrgIdForEmail } from "../org/context.js";
|
|
37
37
|
import { withConfiguredAppBasePath } from "../server/app-base-path.js";
|
|
38
38
|
import { FRAMEWORK_ROUTE_PREFIX } from "../server/core-routes-plugin.js";
|
|
39
|
-
import {
|
|
39
|
+
import {
|
|
40
|
+
canUseDeployCredentialFallbackForRequest,
|
|
41
|
+
readDeployCredentialEnv,
|
|
42
|
+
} from "../server/credential-provider.js";
|
|
40
43
|
import { runWithRequestContext } from "../server/request-context.js";
|
|
41
44
|
import { A2A_CONTINUATION_QUEUED_MARKER } from "./a2a-continuation-marker.js";
|
|
42
45
|
import { signInternalToken } from "./internal-token.js";
|
|
@@ -128,11 +131,6 @@ function explicitEngineName(
|
|
|
128
131
|
return undefined;
|
|
129
132
|
}
|
|
130
133
|
|
|
131
|
-
function isMultiTenantDeploy(): boolean {
|
|
132
|
-
if (process.env.NODE_ENV !== "production") return false;
|
|
133
|
-
return !isLocalDatabase();
|
|
134
|
-
}
|
|
135
|
-
|
|
136
134
|
function collectToolResultSummaries(
|
|
137
135
|
completedRun: ActiveRun,
|
|
138
136
|
): A2AToolResultSummary[] {
|
|
@@ -142,23 +140,26 @@ function collectToolResultSummaries(
|
|
|
142
140
|
.map((event) => ({ tool: event.tool, result: event.result }));
|
|
143
141
|
}
|
|
144
142
|
|
|
145
|
-
async function resolveIntegrationApiKey(
|
|
143
|
+
export async function resolveIntegrationApiKey(
|
|
146
144
|
engineOption: WebhookHandlerOptions["engine"],
|
|
147
145
|
ownerEmail: string,
|
|
148
146
|
fallbackApiKey: string,
|
|
149
147
|
): Promise<string | undefined> {
|
|
148
|
+
const canUseDeployFallback = canUseDeployCredentialFallbackForRequest();
|
|
150
149
|
const engineName = explicitEngineName(engineOption);
|
|
151
150
|
if (engineName) {
|
|
152
151
|
const provider = engineToProvider(engineName);
|
|
153
152
|
const userApiKey = await getOwnerApiKey(provider, ownerEmail);
|
|
154
|
-
if (userApiKey
|
|
153
|
+
if (userApiKey) return userApiKey;
|
|
154
|
+
if (!canUseDeployFallback) return undefined;
|
|
155
155
|
const envVar = PROVIDER_TO_ENV[provider];
|
|
156
156
|
const providerEnvKey = envVar ? readDeployCredentialEnv(envVar) : undefined;
|
|
157
157
|
return providerEnvKey || fallbackApiKey.trim() || undefined;
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
const userApiKey = await getOwnerActiveApiKey(ownerEmail);
|
|
161
|
-
if (userApiKey
|
|
161
|
+
if (userApiKey) return userApiKey;
|
|
162
|
+
if (!canUseDeployFallback) return undefined;
|
|
162
163
|
return fallbackApiKey.trim() || undefined;
|
|
163
164
|
}
|
|
164
165
|
|
|
@@ -186,6 +186,9 @@ function preserveActionFlags(entry: Record<string, any>): Partial<ActionEntry> {
|
|
|
186
186
|
out.requiresAuth = entry.requiresAuth;
|
|
187
187
|
}
|
|
188
188
|
if (typeof entry.readOnly === "boolean") out.readOnly = entry.readOnly;
|
|
189
|
+
if (typeof entry.allowInPlanMode === "boolean") {
|
|
190
|
+
out.allowInPlanMode = entry.allowInPlanMode;
|
|
191
|
+
}
|
|
189
192
|
if (typeof entry.parallelSafe === "boolean") {
|
|
190
193
|
out.parallelSafe = entry.parallelSafe;
|
|
191
194
|
}
|
|
@@ -78,6 +78,10 @@ export function readDeployCredentialEnv(key: string): string | undefined {
|
|
|
78
78
|
* single-tenant contexts. In hosted production with a shared database, every
|
|
79
79
|
* signed-in user needs their own user/org/workspace credential so one deploy
|
|
80
80
|
* key does not silently power another tenant's chat.
|
|
81
|
+
*
|
|
82
|
+
* @deprecated Use `canUseDeployCredentialFallbackForRequest()` for generic
|
|
83
|
+
* provider secrets. This stricter helper remains for legacy call sites with
|
|
84
|
+
* identity-bearing deploy credentials.
|
|
81
85
|
*/
|
|
82
86
|
export function isDeployCredentialFallbackAllowed(): boolean {
|
|
83
87
|
if (!isProductionLikeRuntime()) return true;
|
|
@@ -86,9 +90,10 @@ export function isDeployCredentialFallbackAllowed(): boolean {
|
|
|
86
90
|
|
|
87
91
|
export function canUseDeployCredentialFallbackForRequest(): boolean {
|
|
88
92
|
const email = getRequestUserEmail();
|
|
89
|
-
if (email && isHostedWorkspaceRuntime()) return false;
|
|
90
93
|
if (!email) return true;
|
|
91
|
-
|
|
94
|
+
if (isHostedWorkspaceRuntime()) return false;
|
|
95
|
+
if (!isProductionLikeRuntime()) return true;
|
|
96
|
+
return isLocalDatabase();
|
|
92
97
|
}
|
|
93
98
|
|
|
94
99
|
const BUILDER_CREDENTIAL_KEYS = [
|
|
@@ -962,10 +967,10 @@ export async function resolveSecret(key: string): Promise<string | null> {
|
|
|
962
967
|
}
|
|
963
968
|
// Secrets table not ready — treat as missing.
|
|
964
969
|
}
|
|
965
|
-
//
|
|
966
|
-
//
|
|
967
|
-
//
|
|
968
|
-
//
|
|
970
|
+
// Read deployment-provided env values as fallbacks; framework code must not
|
|
971
|
+
// write to `process.env`, but keys supplied by the host remain valid config.
|
|
972
|
+
// Builder credentials keep a narrower path below because those keys carry a
|
|
973
|
+
// Builder identity rather than just enabling a provider call.
|
|
969
974
|
const envFallback = (
|
|
970
975
|
isBuilderCredentialKey(key)
|
|
971
976
|
? canUseBuilderDeployCredentialFallbackForRequest()
|
|
@@ -28,6 +28,14 @@ agent answers about browser recordings in the Analytics template.
|
|
|
28
28
|
`session-recording` access before reading private blob refs.
|
|
29
29
|
- SQL inline chunks are a local/dev fallback only; production should use
|
|
30
30
|
private or encrypted blob storage.
|
|
31
|
+
- When sharing a replay with an external agent, use
|
|
32
|
+
`create-session-replay-agent-link`. It mints a two-hour `agent_access` URL
|
|
33
|
+
scoped to the recording, embeds a small SSR discovery payload on
|
|
34
|
+
`/sessions/:recordingId`, and advertises
|
|
35
|
+
`/api/session-replay/agent-context.json` plus bounded
|
|
36
|
+
`/api/session-replay/agent-events.json` reads.
|
|
37
|
+
- Do not make session recordings public just so an agent can inspect them.
|
|
38
|
+
Tokenized agent links are the intended handoff path.
|
|
31
39
|
|
|
32
40
|
## Capture Defaults
|
|
33
41
|
|
|
@@ -148,6 +148,11 @@ details live in `.agents/skills/`.
|
|
|
148
148
|
`get-session-replay-summary` and fetches rrweb payloads only through scoped
|
|
149
149
|
chunk routes. Do not expose storage/provider URLs, raw chunk table access, or
|
|
150
150
|
unscoped replay blobs in UI, actions, dashboards, docs, or prompts.
|
|
151
|
+
- The session detail page can mint a temporary `Copy for agent` link through
|
|
152
|
+
`create-session-replay-agent-link`. That URL carries an `agent_access` token
|
|
153
|
+
scoped to one recording for two hours; SSR embeds an agent discovery payload
|
|
154
|
+
and the JSON APIs expose only summary/timeline metadata plus bounded event
|
|
155
|
+
reads.
|
|
151
156
|
- Dashboard rows that include `recording_id` should link to
|
|
152
157
|
`/sessions/:recordingId`; rows that only include `session_id` can link to a
|
|
153
158
|
filtered `/sessions` search.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import {
|
|
3
|
+
getRequestContext,
|
|
4
|
+
getRequestOrgId,
|
|
5
|
+
getRequestUserEmail,
|
|
6
|
+
} from "@agent-native/core/server";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
|
|
9
|
+
import { createSessionReplayAgentLink } from "../server/lib/session-replay-agent-context.js";
|
|
10
|
+
|
|
11
|
+
function resolveScope() {
|
|
12
|
+
const userEmail = getRequestUserEmail();
|
|
13
|
+
if (!userEmail) throw new Error("no authenticated user");
|
|
14
|
+
return { userEmail, orgId: getRequestOrgId() || null };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default defineAction({
|
|
18
|
+
description:
|
|
19
|
+
"Create a temporary private agent-readable link for one Analytics session replay recording. The URL is scoped to that recording and expires after two hours.",
|
|
20
|
+
schema: z.object({
|
|
21
|
+
recordingId: z.string().describe("The session_recordings id"),
|
|
22
|
+
}),
|
|
23
|
+
readOnly: true,
|
|
24
|
+
run: async (args) => {
|
|
25
|
+
return createSessionReplayAgentLink({
|
|
26
|
+
recordingId: args.recordingId,
|
|
27
|
+
scope: resolveScope(),
|
|
28
|
+
origin: getRequestContext()?.requestOrigin,
|
|
29
|
+
});
|
|
30
|
+
},
|
|
31
|
+
});
|
|
@@ -803,6 +803,8 @@ const messages = {
|
|
|
803
803
|
emptyStepVisit: "存取應用",
|
|
804
804
|
installSnippetTitle: "新增工作階段回放",
|
|
805
805
|
backToSessions: "返回工作階段",
|
|
806
|
+
copyForAgent: "複製給 Agent",
|
|
807
|
+
copiedForAgent: "已複製",
|
|
806
808
|
askAgent: "詢問代理此工作階段",
|
|
807
809
|
askSessionTitle: "詢問此工作階段",
|
|
808
810
|
askSessionDescription: "將此回放作為脈絡,向 Analytics 代理傳送聚焦提示。",
|
|
@@ -841,6 +841,8 @@ const enUS = {
|
|
|
841
841
|
emptyStepVisit: "Visit app",
|
|
842
842
|
installSnippetTitle: "Add session replay",
|
|
843
843
|
backToSessions: "Back to sessions",
|
|
844
|
+
copyForAgent: "Copy for agent",
|
|
845
|
+
copiedForAgent: "Copied",
|
|
844
846
|
askAgent: "Ask agent about this session",
|
|
845
847
|
askSessionTitle: "Ask about this session",
|
|
846
848
|
askSessionDescription:
|
|
@@ -6839,6 +6841,8 @@ const translatedAnalyticsDebtCorrections = {
|
|
|
6839
6841
|
anyActivity: "أي نشاط",
|
|
6840
6842
|
eventCountCompact: "{{count}} حدث",
|
|
6841
6843
|
pageCountCompact: "{{count}} صفحة",
|
|
6844
|
+
copyForAgent: "نسخ للوكيل",
|
|
6845
|
+
copiedForAgent: "تم النسخ",
|
|
6842
6846
|
askSessionTitle: "اسأل عن هذه الجلسة",
|
|
6843
6847
|
askSessionDescription:
|
|
6844
6848
|
"أرسل مطالبة مركزة إلى وكيل Analytics مع إرفاق هذا التسجيل كسياق.",
|
|
@@ -6869,6 +6873,8 @@ const translatedAnalyticsDebtCorrections = {
|
|
|
6869
6873
|
anyActivity: "Beliebige Aktivität",
|
|
6870
6874
|
eventCountCompact: "{{count}} Ereignisse",
|
|
6871
6875
|
pageCountCompact: "{{count}} Seiten",
|
|
6876
|
+
copyForAgent: "Für Agent kopieren",
|
|
6877
|
+
copiedForAgent: "Kopiert",
|
|
6872
6878
|
askSessionTitle: "Zu dieser Sitzung fragen",
|
|
6873
6879
|
askSessionDescription:
|
|
6874
6880
|
"Sende dem Analytics-Agenten eine gezielte Eingabe mit dieser Wiedergabe als Kontext.",
|
|
@@ -6894,6 +6900,8 @@ const translatedAnalyticsDebtCorrections = {
|
|
|
6894
6900
|
anyActivity: "Cualquier actividad",
|
|
6895
6901
|
eventCountCompact: "{{count}} eventos",
|
|
6896
6902
|
pageCountCompact: "{{count}} páginas",
|
|
6903
|
+
copyForAgent: "Copiar para agente",
|
|
6904
|
+
copiedForAgent: "Copiado",
|
|
6897
6905
|
askSessionTitle: "Preguntar sobre esta sesión",
|
|
6898
6906
|
askSessionDescription:
|
|
6899
6907
|
"Envía una pregunta enfocada al agente de Analytics con esta reproducción como contexto.",
|
|
@@ -6922,6 +6930,8 @@ const translatedAnalyticsDebtCorrections = {
|
|
|
6922
6930
|
anyActivity: "Toute activité",
|
|
6923
6931
|
eventCountCompact: "{{count}} événements",
|
|
6924
6932
|
pageCountCompact: "{{count}} pages",
|
|
6933
|
+
copyForAgent: "Copier pour l'agent",
|
|
6934
|
+
copiedForAgent: "Copié",
|
|
6925
6935
|
askSessionTitle: "Poser une question sur cette session",
|
|
6926
6936
|
askSessionDescription:
|
|
6927
6937
|
"Envoyez une question ciblée à l'agent Analytics avec cette relecture en contexte.",
|
|
@@ -6948,6 +6958,8 @@ const translatedAnalyticsDebtCorrections = {
|
|
|
6948
6958
|
anyActivity: "कोई भी गतिविधि",
|
|
6949
6959
|
eventCountCompact: "{{count}} इवेंट",
|
|
6950
6960
|
pageCountCompact: "{{count}} पेज",
|
|
6961
|
+
copyForAgent: "एजेंट के लिए कॉपी करें",
|
|
6962
|
+
copiedForAgent: "कॉपी हो गया",
|
|
6951
6963
|
askSessionTitle: "इस सत्र के बारे में पूछें",
|
|
6952
6964
|
askSessionDescription:
|
|
6953
6965
|
"इस रीप्ले को संदर्भ के रूप में जोड़कर Analytics एजेंट को केंद्रित प्रॉम्प्ट भेजें।",
|
|
@@ -6973,6 +6985,8 @@ const translatedAnalyticsDebtCorrections = {
|
|
|
6973
6985
|
anyActivity: "任意のアクティビティ",
|
|
6974
6986
|
eventCountCompact: "{{count}} 件のイベント",
|
|
6975
6987
|
pageCountCompact: "{{count}} ページ",
|
|
6988
|
+
copyForAgent: "エージェント用にコピー",
|
|
6989
|
+
copiedForAgent: "コピーしました",
|
|
6976
6990
|
askSessionTitle: "このセッションについて質問",
|
|
6977
6991
|
askSessionDescription:
|
|
6978
6992
|
"このリプレイをコンテキストとして添えて、Analytics エージェントに絞り込んだプロンプトを送信します。",
|
|
@@ -6998,6 +7012,8 @@ const translatedAnalyticsDebtCorrections = {
|
|
|
6998
7012
|
anyActivity: "모든 활동",
|
|
6999
7013
|
eventCountCompact: "{{count}}개 이벤트",
|
|
7000
7014
|
pageCountCompact: "{{count}}개 페이지",
|
|
7015
|
+
copyForAgent: "에이전트용으로 복사",
|
|
7016
|
+
copiedForAgent: "복사됨",
|
|
7001
7017
|
askSessionTitle: "이 세션에 대해 질문",
|
|
7002
7018
|
askSessionDescription:
|
|
7003
7019
|
"이 리플레이를 컨텍스트로 첨부해 Analytics 에이전트에 집중된 프롬프트를 보냅니다.",
|
|
@@ -7023,6 +7039,8 @@ const translatedAnalyticsDebtCorrections = {
|
|
|
7023
7039
|
anyActivity: "Qualquer atividade",
|
|
7024
7040
|
eventCountCompact: "{{count}} eventos",
|
|
7025
7041
|
pageCountCompact: "{{count}} páginas",
|
|
7042
|
+
copyForAgent: "Copiar para agente",
|
|
7043
|
+
copiedForAgent: "Copiado",
|
|
7026
7044
|
askSessionTitle: "Perguntar sobre esta sessão",
|
|
7027
7045
|
askSessionDescription:
|
|
7028
7046
|
"Envie um prompt focado ao agente de Analytics com este replay como contexto.",
|
|
@@ -7048,6 +7066,8 @@ const translatedAnalyticsDebtCorrections = {
|
|
|
7048
7066
|
anyActivity: "任意活动",
|
|
7049
7067
|
eventCountCompact: "{{count}} 个事件",
|
|
7050
7068
|
pageCountCompact: "{{count}} 个页面",
|
|
7069
|
+
copyForAgent: "复制给代理",
|
|
7070
|
+
copiedForAgent: "已复制",
|
|
7051
7071
|
askSessionTitle: "询问此会话",
|
|
7052
7072
|
askSessionDescription:
|
|
7053
7073
|
"将此回放作为上下文,向 Analytics 代理发送聚焦提示。",
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
appApiPath,
|
|
3
3
|
PromptComposer,
|
|
4
|
+
useActionMutation,
|
|
5
|
+
useActionQuery,
|
|
4
6
|
useSendToAgentChat,
|
|
5
7
|
useT,
|
|
6
8
|
} from "@agent-native/core/client";
|
|
9
|
+
import { SESSION_REPLAY_AGENT_ACCESS_PARAM } from "@shared/session-replay-agent-access";
|
|
7
10
|
import {
|
|
8
11
|
IconArrowLeft,
|
|
12
|
+
IconCheck,
|
|
13
|
+
IconCopy,
|
|
9
14
|
IconExclamationCircle,
|
|
10
15
|
IconKeyboard,
|
|
11
16
|
IconMessageCircle,
|
|
@@ -201,7 +206,12 @@ export default function SessionDetailPage() {
|
|
|
201
206
|
</div>
|
|
202
207
|
) : null}
|
|
203
208
|
</div>
|
|
204
|
-
{recording ?
|
|
209
|
+
{recording ? (
|
|
210
|
+
<div className="flex shrink-0 items-center gap-2">
|
|
211
|
+
<CopySessionForAgentButton recordingId={recording.id} />
|
|
212
|
+
<AskSessionPopover recording={recording} />
|
|
213
|
+
</div>
|
|
214
|
+
) : null}
|
|
205
215
|
</div>
|
|
206
216
|
|
|
207
217
|
{error ? (
|
|
@@ -224,6 +234,41 @@ export default function SessionDetailPage() {
|
|
|
224
234
|
);
|
|
225
235
|
}
|
|
226
236
|
|
|
237
|
+
function CopySessionForAgentButton({ recordingId }: { recordingId: string }) {
|
|
238
|
+
const t = useT();
|
|
239
|
+
const [copied, setCopied] = useState(false);
|
|
240
|
+
const createLink = useActionMutation(
|
|
241
|
+
"create-session-replay-agent-link" as any,
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
async function handleCopy() {
|
|
245
|
+
if (createLink.isPending) return;
|
|
246
|
+
const result = (await createLink.mutateAsync({
|
|
247
|
+
recordingId,
|
|
248
|
+
})) as { url?: string };
|
|
249
|
+
if (!result?.url) return;
|
|
250
|
+
await navigator.clipboard.writeText(result.url).catch(() => {});
|
|
251
|
+
setCopied(true);
|
|
252
|
+
setTimeout(() => setCopied(false), 1400);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return (
|
|
256
|
+
<Button
|
|
257
|
+
type="button"
|
|
258
|
+
variant="outline"
|
|
259
|
+
onClick={() => void handleCopy()}
|
|
260
|
+
disabled={createLink.isPending}
|
|
261
|
+
>
|
|
262
|
+
{copied ? (
|
|
263
|
+
<IconCheck className="h-4 w-4" />
|
|
264
|
+
) : (
|
|
265
|
+
<IconCopy className="h-4 w-4" />
|
|
266
|
+
)}
|
|
267
|
+
{copied ? t("sessions.copiedForAgent") : t("sessions.copyForAgent")}
|
|
268
|
+
</Button>
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
227
272
|
function AskSessionPopover({
|
|
228
273
|
recording,
|
|
229
274
|
}: {
|
|
@@ -976,8 +1021,9 @@ function DetailSkeleton() {
|
|
|
976
1021
|
}
|
|
977
1022
|
|
|
978
1023
|
function useSessionReplayPlayback(recordingId: string) {
|
|
1024
|
+
const agentAccessToken = currentSessionReplayAgentAccessToken();
|
|
979
1025
|
return useQuery({
|
|
980
|
-
queryKey: ["session-replay-playback", recordingId],
|
|
1026
|
+
queryKey: ["session-replay-playback", recordingId, agentAccessToken],
|
|
981
1027
|
enabled: Boolean(recordingId),
|
|
982
1028
|
staleTime: 30_000,
|
|
983
1029
|
gcTime: 60_000,
|
|
@@ -1080,9 +1126,25 @@ function replayUnavailableChunk(
|
|
|
1080
1126
|
};
|
|
1081
1127
|
}
|
|
1082
1128
|
|
|
1129
|
+
function currentSessionReplayAgentAccessToken(): string {
|
|
1130
|
+
const browserSearch =
|
|
1131
|
+
typeof window === "undefined" ? "" : window.location.search;
|
|
1132
|
+
return (
|
|
1133
|
+
new URLSearchParams(browserSearch).get(SESSION_REPLAY_AGENT_ACCESS_PARAM) ??
|
|
1134
|
+
""
|
|
1135
|
+
);
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1083
1138
|
async function fetchReplayApi(path: string): Promise<Response> {
|
|
1084
1139
|
const token = await getIdToken();
|
|
1085
|
-
|
|
1140
|
+
const browserOrigin =
|
|
1141
|
+
typeof window === "undefined" ? "http://localhost" : window.location.origin;
|
|
1142
|
+
const url = new URL(appApiPath(path), browserOrigin);
|
|
1143
|
+
const agentAccessToken = currentSessionReplayAgentAccessToken();
|
|
1144
|
+
if (agentAccessToken) {
|
|
1145
|
+
url.searchParams.set(SESSION_REPLAY_AGENT_ACCESS_PARAM, agentAccessToken);
|
|
1146
|
+
}
|
|
1147
|
+
return fetch(`${url.pathname}${url.search}${url.hash}`, {
|
|
1086
1148
|
headers: {
|
|
1087
1149
|
Accept: "application/json",
|
|
1088
1150
|
...(token ? { Authorization: `Bearer ${token}` } : {}),
|