@agent-native/core 0.84.25 → 0.84.27
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 +12 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/production-agent.ts +4 -2
- package/corpus/core/src/agent/run-manager.ts +4 -3
- package/corpus/core/src/agent/types.ts +9 -2
- package/corpus/core/src/client/AssistantChat.tsx +15 -1
- package/corpus/core/src/client/FeedbackButton.tsx +2 -2
- package/corpus/core/src/client/components/PresenceBar.tsx +7 -2
- package/corpus/core/src/client/composer/ComposerPlusMenu.tsx +23 -9
- package/corpus/core/src/client/sharing/ShareButton.tsx +22 -3
- package/corpus/core/src/client/sharing/ShareDialog.tsx +13 -1
- package/corpus/core/src/client/sse-event-processor.ts +49 -20
- package/corpus/core/src/sharing/actions/set-resource-visibility.ts +3 -3
- package/corpus/templates/design/actions/insert-asset.ts +14 -2
- package/corpus/templates/design/actions/insert-design-native-asset.ts +16 -4
- package/corpus/templates/design/actions/insert-figma-library-asset.ts +14 -2
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +14 -0
- package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +54 -10
- package/corpus/templates/design/app/components/design/EditPanel.tsx +23 -16
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +7 -2
- package/corpus/templates/design/app/components/design/inspector/InspectorAiActions.tsx +9 -5
- package/corpus/templates/design/app/components/layout/Header.tsx +44 -1
- package/corpus/templates/design/app/pages/DesignEditor.tsx +267 -105
- package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +15 -1
- package/corpus/templates/design/app/pages/Index.tsx +20 -9
- package/corpus/templates/design/app/routes/settings.tsx +1 -1
- package/corpus/templates/design/changelog/2026-07-01-design-editor-polish-makes-zoom-assets-sharing-and-agent-handoffs-clearer.md +6 -0
- package/corpus/templates/design/changelog/2026-07-01-design-qa-fixes-make-exports-permissions-and-tools-clearer.md +6 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +4 -3
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +4 -4
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/types.d.ts +3 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +16 -1
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/FeedbackButton.js +2 -2
- package/dist/client/FeedbackButton.js.map +1 -1
- package/dist/client/components/PresenceBar.d.ts.map +1 -1
- package/dist/client/components/PresenceBar.js +6 -2
- package/dist/client/components/PresenceBar.js.map +1 -1
- package/dist/client/composer/ComposerPlusMenu.d.ts.map +1 -1
- package/dist/client/composer/ComposerPlusMenu.js +15 -7
- package/dist/client/composer/ComposerPlusMenu.js.map +1 -1
- package/dist/client/sharing/ShareButton.js +15 -2
- package/dist/client/sharing/ShareButton.js.map +1 -1
- package/dist/client/sharing/ShareDialog.js +16 -1
- package/dist/client/sharing/ShareDialog.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +42 -16
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +6 -6
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/sharing/actions/set-resource-visibility.js +3 -3
- package/dist/sharing/actions/set-resource-visibility.js.map +1 -1
- package/package.json +1 -1
package/corpus/README.md
CHANGED
package/corpus/core/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @agent-native/core
|
|
2
2
|
|
|
3
|
+
## 0.84.27
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 6fb954b: Require admin access for shareable resource visibility changes.
|
|
8
|
+
|
|
9
|
+
## 0.84.26
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- f041345: Track action-preparation progress by streamed tool-input id so parallel same-name tool calls do not hide real progress.
|
|
14
|
+
|
|
3
15
|
## 0.84.25
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/corpus/core/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.84.
|
|
3
|
+
"version": "0.84.27",
|
|
4
4
|
"description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -2795,6 +2795,7 @@ export async function runAgentLoop(opts: {
|
|
|
2795
2795
|
let lastToolInputActivityAt = 0;
|
|
2796
2796
|
const sendToolInputActivity = (
|
|
2797
2797
|
toolName: string | undefined,
|
|
2798
|
+
toolInputId?: string,
|
|
2798
2799
|
progressBytes?: number,
|
|
2799
2800
|
force = false,
|
|
2800
2801
|
) => {
|
|
@@ -2810,6 +2811,7 @@ export async function runAgentLoop(opts: {
|
|
|
2810
2811
|
type: "activity",
|
|
2811
2812
|
label: toolInputActivityLabel(toolName),
|
|
2812
2813
|
...(toolName ? { tool: toolName } : {}),
|
|
2814
|
+
...(toolInputId ? { id: toolInputId } : {}),
|
|
2813
2815
|
...(typeof progressBytes === "number" ? { progressBytes } : {}),
|
|
2814
2816
|
});
|
|
2815
2817
|
};
|
|
@@ -2848,7 +2850,7 @@ export async function runAgentLoop(opts: {
|
|
|
2848
2850
|
toolInputNames.set(key, event.name);
|
|
2849
2851
|
toolInputBytes.set(key, 0);
|
|
2850
2852
|
}
|
|
2851
|
-
sendToolInputActivity(event.name, undefined, true);
|
|
2853
|
+
sendToolInputActivity(event.name, key, undefined, true);
|
|
2852
2854
|
} else if (event.type === "tool-input-delta") {
|
|
2853
2855
|
const key = event.id ?? event.name;
|
|
2854
2856
|
const toolName =
|
|
@@ -2862,7 +2864,7 @@ export async function runAgentLoop(opts: {
|
|
|
2862
2864
|
new TextEncoder().encode(event.text ?? "").byteLength;
|
|
2863
2865
|
toolInputBytes.set(key, progressBytes);
|
|
2864
2866
|
}
|
|
2865
|
-
sendToolInputActivity(toolName, progressBytes);
|
|
2867
|
+
sendToolInputActivity(toolName, key, progressBytes);
|
|
2866
2868
|
} else if (event.type === "gateway-heartbeat") {
|
|
2867
2869
|
send({ type: "stream_keepalive" });
|
|
2868
2870
|
} else if (event.type === "tool-call") {
|
|
@@ -441,7 +441,8 @@ export function startRun(
|
|
|
441
441
|
const shouldBumpProgressForEvent = (event: AgentChatEvent): boolean => {
|
|
442
442
|
if (event.type === "stream_keepalive") return false;
|
|
443
443
|
if (event.type === "activity" && isPreparingActionActivityEvent(event)) {
|
|
444
|
-
const toolKey =
|
|
444
|
+
const toolKey =
|
|
445
|
+
event.id?.trim() || event.tool?.trim() || event.label.trim();
|
|
445
446
|
const progressBytes =
|
|
446
447
|
typeof event.progressBytes === "number" &&
|
|
447
448
|
Number.isFinite(event.progressBytes) &&
|
|
@@ -449,6 +450,7 @@ export function startRun(
|
|
|
449
450
|
? Math.floor(event.progressBytes)
|
|
450
451
|
: undefined;
|
|
451
452
|
if (progressBytes === undefined) return false;
|
|
453
|
+
if (!event.id?.trim()) return progressBytes > 0;
|
|
452
454
|
const previousBytes = preparingActivityBytes.get(toolKey) ?? 0;
|
|
453
455
|
if (progressBytes <= previousBytes) {
|
|
454
456
|
preparingActivityBytes.set(
|
|
@@ -461,8 +463,7 @@ export function startRun(
|
|
|
461
463
|
return true;
|
|
462
464
|
}
|
|
463
465
|
if (event.type === "tool_start" || event.type === "tool_done") {
|
|
464
|
-
|
|
465
|
-
if (tool) preparingActivityBytes.delete(tool);
|
|
466
|
+
preparingActivityBytes.clear();
|
|
466
467
|
}
|
|
467
468
|
if (
|
|
468
469
|
event.type === "clear" ||
|
|
@@ -212,12 +212,19 @@ export type AgentToolInput = Record<string, unknown>;
|
|
|
212
212
|
export type AgentChatEvent =
|
|
213
213
|
| { type: "text"; text: string }
|
|
214
214
|
| { type: "thinking"; text: string }
|
|
215
|
-
| {
|
|
215
|
+
| {
|
|
216
|
+
type: "activity";
|
|
217
|
+
label: string;
|
|
218
|
+
tool?: string;
|
|
219
|
+
id?: string;
|
|
220
|
+
progressBytes?: number;
|
|
221
|
+
}
|
|
216
222
|
| { type: "stream_keepalive" }
|
|
217
|
-
| { type: "tool_start"; tool: string; input: AgentToolInput }
|
|
223
|
+
| { type: "tool_start"; tool: string; id?: string; input: AgentToolInput }
|
|
218
224
|
| {
|
|
219
225
|
type: "tool_done";
|
|
220
226
|
tool: string;
|
|
227
|
+
id?: string;
|
|
221
228
|
input?: AgentToolInput;
|
|
222
229
|
result: string;
|
|
223
230
|
isError?: boolean;
|
|
@@ -1456,6 +1456,7 @@ const AssistantChatInner = forwardRef<
|
|
|
1456
1456
|
// When stop is clicked during reconnect, keep content visible (don't wipe it)
|
|
1457
1457
|
const [reconnectFrozen, setReconnectFrozen] = useState(false);
|
|
1458
1458
|
const reconnectRunIdRef = useRef<string | null>(null);
|
|
1459
|
+
const reconnectTailOnlyRef = useRef(false);
|
|
1459
1460
|
const reconnectCanMaterializeRef = useRef(false);
|
|
1460
1461
|
const reconnectAbortRef = useRef<AbortController | null>(null);
|
|
1461
1462
|
// Nuclear stop: user clicked stop. Clears the stop button/indicator AND
|
|
@@ -1775,6 +1776,7 @@ const AssistantChatInner = forwardRef<
|
|
|
1775
1776
|
|
|
1776
1777
|
reconnectRunIdRef.current = runId;
|
|
1777
1778
|
const afterSeq = resolveReconnectAfterSeq(threadId, runId);
|
|
1779
|
+
reconnectTailOnlyRef.current = afterSeq > 0;
|
|
1778
1780
|
reconnectCanMaterializeRef.current = afterSeq === 0;
|
|
1779
1781
|
const storedActivityTool = getActiveRunActivityTool(threadId, runId);
|
|
1780
1782
|
setRunningActivityTool(storedActivityTool);
|
|
@@ -1941,6 +1943,7 @@ const AssistantChatInner = forwardRef<
|
|
|
1941
1943
|
await refreshThreadFromServer();
|
|
1942
1944
|
setReconnectContent([]);
|
|
1943
1945
|
setReconnectFrozen(false);
|
|
1946
|
+
reconnectTailOnlyRef.current = false;
|
|
1944
1947
|
} else {
|
|
1945
1948
|
settleInterruptedToolCalls(latestContent);
|
|
1946
1949
|
setReconnectContent([...latestContent]);
|
|
@@ -1959,6 +1962,7 @@ const AssistantChatInner = forwardRef<
|
|
|
1959
1962
|
reconnectAbortRef.current = null;
|
|
1960
1963
|
setIsReconnecting(false);
|
|
1961
1964
|
reconnectRunIdRef.current = null;
|
|
1965
|
+
reconnectTailOnlyRef.current = false;
|
|
1962
1966
|
if (afterSeq > 0) {
|
|
1963
1967
|
reconnectCanMaterializeRef.current = false;
|
|
1964
1968
|
}
|
|
@@ -1994,6 +1998,7 @@ const AssistantChatInner = forwardRef<
|
|
|
1994
1998
|
reconnectAbortRef.current = null;
|
|
1995
1999
|
setIsReconnecting(false);
|
|
1996
2000
|
reconnectRunIdRef.current = null;
|
|
2001
|
+
reconnectTailOnlyRef.current = false;
|
|
1997
2002
|
if (loaded || afterSeq > 0 || latestContent.length === 0) {
|
|
1998
2003
|
reconnectCanMaterializeRef.current = false;
|
|
1999
2004
|
}
|
|
@@ -2023,6 +2028,7 @@ const AssistantChatInner = forwardRef<
|
|
|
2023
2028
|
useCallback(async (): Promise<boolean> => {
|
|
2024
2029
|
if (!threadId) return false;
|
|
2025
2030
|
try {
|
|
2031
|
+
const storedActiveRun = getActiveRun();
|
|
2026
2032
|
const runRes = await fetch(
|
|
2027
2033
|
`${apiUrl}/runs/active?threadId=${encodeURIComponent(threadId)}`,
|
|
2028
2034
|
);
|
|
@@ -2033,6 +2039,11 @@ const AssistantChatInner = forwardRef<
|
|
|
2033
2039
|
runInfo.status !== "running" ||
|
|
2034
2040
|
activeRunLooksStale(runInfo)
|
|
2035
2041
|
) {
|
|
2042
|
+
if (storedActiveRun?.threadId === threadId) {
|
|
2043
|
+
clearActiveRunIfMatches(threadId, storedActiveRun.runId);
|
|
2044
|
+
} else if (runInfo.runId) {
|
|
2045
|
+
clearActiveRunIfMatches(threadId, String(runInfo.runId));
|
|
2046
|
+
}
|
|
2036
2047
|
await refreshThreadFromServer();
|
|
2037
2048
|
return false;
|
|
2038
2049
|
}
|
|
@@ -2819,7 +2830,9 @@ const AssistantChatInner = forwardRef<
|
|
|
2819
2830
|
reconnectRunIdRef.current = null;
|
|
2820
2831
|
setIsReconnecting(false);
|
|
2821
2832
|
const shouldFreezeReconnectContent =
|
|
2822
|
-
|
|
2833
|
+
!reconnectTailOnlyRef.current &&
|
|
2834
|
+
reconnectCanMaterializeRef.current &&
|
|
2835
|
+
reconnectContent.length > 0;
|
|
2823
2836
|
if (shouldFreezeReconnectContent) {
|
|
2824
2837
|
setReconnectFrozen(true);
|
|
2825
2838
|
} else {
|
|
@@ -2827,6 +2840,7 @@ const AssistantChatInner = forwardRef<
|
|
|
2827
2840
|
setReconnectContent([]);
|
|
2828
2841
|
reconnectCanMaterializeRef.current = false;
|
|
2829
2842
|
}
|
|
2843
|
+
reconnectTailOnlyRef.current = false;
|
|
2830
2844
|
}
|
|
2831
2845
|
threadRuntime.cancelRun();
|
|
2832
2846
|
if (typeof window !== "undefined") {
|
|
@@ -429,7 +429,7 @@ export function FeedbackButton({
|
|
|
429
429
|
<TooltipPrimitive.Portal>
|
|
430
430
|
<TooltipPrimitive.Content
|
|
431
431
|
sideOffset={6}
|
|
432
|
-
className="z-[
|
|
432
|
+
className="z-[100040] overflow-hidden rounded-md border border-border bg-popover px-2 py-1 text-[11px] text-foreground shadow-md animate-in fade-in-0 zoom-in-95"
|
|
433
433
|
>
|
|
434
434
|
{resolvedLabel}
|
|
435
435
|
</TooltipPrimitive.Content>
|
|
@@ -482,7 +482,7 @@ export function FeedbackButton({
|
|
|
482
482
|
align={align}
|
|
483
483
|
sideOffset={8}
|
|
484
484
|
collisionPadding={16}
|
|
485
|
-
className="z-[
|
|
485
|
+
className="z-[100040] overflow-hidden rounded-lg border border-border bg-popover shadow-xl outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2"
|
|
486
486
|
style={surfaceStyle}
|
|
487
487
|
>
|
|
488
488
|
{submitted ? (
|
|
@@ -113,7 +113,8 @@ function UserAvatar({
|
|
|
113
113
|
: `0 0 0 2px #fff`,
|
|
114
114
|
}}
|
|
115
115
|
aria-label={`${name} (${user.email})${isFollowing ? " — following" : ""}`}
|
|
116
|
-
tabIndex={0}
|
|
116
|
+
tabIndex={onClick ? 0 : undefined}
|
|
117
|
+
role={onClick ? "button" : undefined}
|
|
117
118
|
onClick={onClick}
|
|
118
119
|
onKeyDown={(e) => {
|
|
119
120
|
if (e.key === "Enter" || e.key === " ") onClick?.();
|
|
@@ -123,7 +124,11 @@ function UserAvatar({
|
|
|
123
124
|
</div>
|
|
124
125
|
</TooltipTrigger>
|
|
125
126
|
<TooltipContent side="bottom">
|
|
126
|
-
{isFollowing
|
|
127
|
+
{isFollowing
|
|
128
|
+
? `Following ${name} — click to stop`
|
|
129
|
+
: onClick
|
|
130
|
+
? `${name} — click to follow`
|
|
131
|
+
: user.email}
|
|
127
132
|
</TooltipContent>
|
|
128
133
|
</Tooltip>
|
|
129
134
|
);
|
|
@@ -12,7 +12,13 @@ import {
|
|
|
12
12
|
IconArrowLeft,
|
|
13
13
|
IconX,
|
|
14
14
|
} from "@tabler/icons-react";
|
|
15
|
-
import React, {
|
|
15
|
+
import React, {
|
|
16
|
+
useState,
|
|
17
|
+
useRef,
|
|
18
|
+
useEffect,
|
|
19
|
+
useMemo,
|
|
20
|
+
useCallback,
|
|
21
|
+
} from "react";
|
|
16
22
|
import { createPortal } from "react-dom";
|
|
17
23
|
|
|
18
24
|
import { setAgentChatContextItem } from "../agent-chat.js";
|
|
@@ -740,6 +746,16 @@ function AssetsPickerModal({
|
|
|
740
746
|
const sourceUrl = useMemo(() => assetPickerUrl(), []);
|
|
741
747
|
const iframeUrl = useMemo(() => withEmbeddedParams(sourceUrl), [sourceUrl]);
|
|
742
748
|
const targetOrigin = useMemo(() => assetPickerOrigin(iframeUrl), [iframeUrl]);
|
|
749
|
+
const configurePicker = useCallback(() => {
|
|
750
|
+
if (!targetOrigin) return;
|
|
751
|
+
iframeRef.current?.contentWindow?.postMessage(
|
|
752
|
+
embedEnvelope("message", {
|
|
753
|
+
name: "configure",
|
|
754
|
+
payload: { mediaType: "image", count: 3 },
|
|
755
|
+
}),
|
|
756
|
+
targetOrigin,
|
|
757
|
+
);
|
|
758
|
+
}, [targetOrigin]);
|
|
743
759
|
|
|
744
760
|
useEffect(() => {
|
|
745
761
|
if (open) setPickerReady(false);
|
|
@@ -755,13 +771,7 @@ function AssetsPickerModal({
|
|
|
755
771
|
|
|
756
772
|
if (event.data.type === "ready") {
|
|
757
773
|
setPickerReady(true);
|
|
758
|
-
|
|
759
|
-
embedEnvelope("message", {
|
|
760
|
-
name: "configure",
|
|
761
|
-
payload: { mediaType: "image", count: 3 },
|
|
762
|
-
}),
|
|
763
|
-
targetOrigin,
|
|
764
|
-
);
|
|
774
|
+
configurePicker();
|
|
765
775
|
return;
|
|
766
776
|
}
|
|
767
777
|
|
|
@@ -801,7 +811,7 @@ function AssetsPickerModal({
|
|
|
801
811
|
window.removeEventListener("message", handleMessage);
|
|
802
812
|
window.removeEventListener("keydown", handleKey);
|
|
803
813
|
};
|
|
804
|
-
}, [onOpenChange, open, targetOrigin]);
|
|
814
|
+
}, [configurePicker, onOpenChange, open, targetOrigin]);
|
|
805
815
|
|
|
806
816
|
if (!open || typeof document === "undefined") return null;
|
|
807
817
|
|
|
@@ -846,6 +856,10 @@ function AssetsPickerModal({
|
|
|
846
856
|
sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-downloads"
|
|
847
857
|
allow="clipboard-read; clipboard-write; microphone; fullscreen"
|
|
848
858
|
referrerPolicy="strict-origin-when-cross-origin"
|
|
859
|
+
onLoad={() => {
|
|
860
|
+
configurePicker();
|
|
861
|
+
setPickerReady(true);
|
|
862
|
+
}}
|
|
849
863
|
/>
|
|
850
864
|
</div>
|
|
851
865
|
) : (
|
|
@@ -693,6 +693,10 @@ function SharePanel(
|
|
|
693
693
|
|
|
694
694
|
const handleVisibility = (next: Visibility) => {
|
|
695
695
|
if (next === visibility) return;
|
|
696
|
+
if (!canManage) {
|
|
697
|
+
setShareError("Only owners and admins can change access.");
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
696
700
|
setShareError(null);
|
|
697
701
|
void onVisibilityChange(next).catch((err) => {
|
|
698
702
|
setShareError(extractShareErrorMessage(err));
|
|
@@ -1025,6 +1029,15 @@ function SharePanel(
|
|
|
1025
1029
|
</div>
|
|
1026
1030
|
</div>
|
|
1027
1031
|
|
|
1032
|
+
{shareError && !canManage ? (
|
|
1033
|
+
<div
|
|
1034
|
+
role="alert"
|
|
1035
|
+
className="mb-4 rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-xs text-destructive"
|
|
1036
|
+
>
|
|
1037
|
+
{shareError}
|
|
1038
|
+
</div>
|
|
1039
|
+
) : null}
|
|
1040
|
+
|
|
1028
1041
|
{props.accessNote ? (
|
|
1029
1042
|
<div className="mb-4 rounded-md border border-border bg-muted/35 p-3 text-xs text-muted-foreground">
|
|
1030
1043
|
{props.accessNote}
|
|
@@ -1589,14 +1602,20 @@ function VisibilitySelect(props: {
|
|
|
1589
1602
|
onChange: (v: Visibility) => void;
|
|
1590
1603
|
disabled?: boolean;
|
|
1591
1604
|
visibilityCopy?: ShareButtonProps["visibilityCopy"];
|
|
1605
|
+
/** When false, the "Private" option is omitted unless currently selected. */
|
|
1606
|
+
allowPrivate?: boolean;
|
|
1592
1607
|
/** When false, the "Public" option is omitted. Default: true. */
|
|
1593
1608
|
allowPublic?: boolean;
|
|
1594
1609
|
}) {
|
|
1610
|
+
const allowPrivate = props.allowPrivate !== false;
|
|
1595
1611
|
const allowPublic = props.allowPublic !== false;
|
|
1596
1612
|
const current = visibilityMeta(props.value, props.visibilityCopy);
|
|
1597
|
-
const options = (Object.keys(VIS_META) as Visibility[]).filter(
|
|
1598
|
-
(k
|
|
1599
|
-
|
|
1613
|
+
const options = (Object.keys(VIS_META) as Visibility[]).filter((k) => {
|
|
1614
|
+
if (k === props.value) return true;
|
|
1615
|
+
if (k === "private" && !allowPrivate) return false;
|
|
1616
|
+
if (k === "public" && !allowPublic) return false;
|
|
1617
|
+
return true;
|
|
1618
|
+
});
|
|
1600
1619
|
return (
|
|
1601
1620
|
<Select.Root
|
|
1602
1621
|
value={props.value}
|
|
@@ -376,6 +376,7 @@ function LinkTab(props: {
|
|
|
376
376
|
|
|
377
377
|
const handleVisibility = (next: Visibility) => {
|
|
378
378
|
if (next === visibility) return;
|
|
379
|
+
if (!canManage) return;
|
|
379
380
|
setVisibility.mutate(
|
|
380
381
|
{ resourceType, resourceId, visibility: next } as any,
|
|
381
382
|
{ onSuccess: () => sharesQuery.refetch() },
|
|
@@ -490,6 +491,7 @@ function InviteTab(props: {
|
|
|
490
491
|
|
|
491
492
|
const handleVisibility = (next: Visibility) => {
|
|
492
493
|
if (next === visibility) return;
|
|
494
|
+
if (!canManage) return;
|
|
493
495
|
setVisibility.mutate(
|
|
494
496
|
{ resourceType, resourceId, visibility: next } as any,
|
|
495
497
|
{ onSuccess: () => sharesQuery.refetch() },
|
|
@@ -762,10 +764,20 @@ function VisibilitySelect(props: {
|
|
|
762
764
|
value: Visibility;
|
|
763
765
|
onChange: (v: Visibility) => void;
|
|
764
766
|
disabled?: boolean;
|
|
767
|
+
allowPrivate?: boolean;
|
|
768
|
+
allowPublic?: boolean;
|
|
765
769
|
}) {
|
|
766
770
|
const t = useT();
|
|
767
771
|
const visibilityMeta = useVisibilityMeta();
|
|
768
772
|
const current = visibilityMeta[props.value];
|
|
773
|
+
const allowPrivate = props.allowPrivate !== false;
|
|
774
|
+
const allowPublic = props.allowPublic !== false;
|
|
775
|
+
const options = (Object.keys(VIS_ICONS) as Visibility[]).filter((k) => {
|
|
776
|
+
if (k === props.value) return true;
|
|
777
|
+
if (k === "private" && !allowPrivate) return false;
|
|
778
|
+
if (k === "public" && !allowPublic) return false;
|
|
779
|
+
return true;
|
|
780
|
+
});
|
|
769
781
|
return (
|
|
770
782
|
<Select.Root
|
|
771
783
|
value={props.value}
|
|
@@ -792,7 +804,7 @@ function VisibilitySelect(props: {
|
|
|
792
804
|
>
|
|
793
805
|
<Select.Viewport>
|
|
794
806
|
<SelectItems
|
|
795
|
-
items={
|
|
807
|
+
items={options.map((k) => ({
|
|
796
808
|
value: k,
|
|
797
809
|
label: visibilityMeta[k].label,
|
|
798
810
|
description: visibilityMeta[k].description,
|
|
@@ -150,8 +150,8 @@ export const SSE_ACTION_PREPARATION_STALL_TIMEOUT_MS = 90_000;
|
|
|
150
150
|
|
|
151
151
|
type ActivityTrailEntry = AgentActivityTrailEntry;
|
|
152
152
|
|
|
153
|
-
type
|
|
154
|
-
tool
|
|
153
|
+
type PreparingActionEntry = {
|
|
154
|
+
tool: string;
|
|
155
155
|
startedAt?: number;
|
|
156
156
|
lastProgressBytes?: number;
|
|
157
157
|
/**
|
|
@@ -166,6 +166,10 @@ type PreparingActionState = {
|
|
|
166
166
|
lastProgressAt?: number;
|
|
167
167
|
};
|
|
168
168
|
|
|
169
|
+
type PreparingActionState = {
|
|
170
|
+
entries?: Map<string, PreparingActionEntry>;
|
|
171
|
+
};
|
|
172
|
+
|
|
169
173
|
function formatProgressBytes(bytes: number): string {
|
|
170
174
|
if (bytes < 1024) return `${bytes} B`;
|
|
171
175
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
@@ -270,21 +274,35 @@ function updatePreparingActionState(
|
|
|
270
274
|
if (ev.type === "activity" && isPreparingActionActivity(ev)) {
|
|
271
275
|
const tool = ev.tool?.trim() || undefined;
|
|
272
276
|
if (!tool) return false;
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
277
|
+
const key = ev.id?.trim() || tool;
|
|
278
|
+
const entries = state.entries ?? new Map<string, PreparingActionEntry>();
|
|
279
|
+
state.entries = entries;
|
|
280
|
+
let entry = entries.get(key);
|
|
281
|
+
if (!entry) {
|
|
282
|
+
entry = {
|
|
283
|
+
tool,
|
|
284
|
+
startedAt: now,
|
|
285
|
+
lastProgressAt: undefined,
|
|
286
|
+
lastProgressBytes: undefined,
|
|
287
|
+
};
|
|
288
|
+
entries.set(key, entry);
|
|
278
289
|
}
|
|
279
290
|
const progressBytes = activityProgressBytes(ev);
|
|
280
|
-
const previousBytes =
|
|
291
|
+
const previousBytes = entry.lastProgressBytes ?? 0;
|
|
281
292
|
if (progressBytes !== undefined) {
|
|
282
|
-
|
|
293
|
+
entry.lastProgressBytes = Math.max(previousBytes, progressBytes);
|
|
294
|
+
}
|
|
295
|
+
if (!ev.id?.trim()) {
|
|
296
|
+
if (progressBytes !== undefined && progressBytes > 0) {
|
|
297
|
+
entry.lastProgressAt = now;
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
return false;
|
|
283
301
|
}
|
|
284
302
|
if (progressBytes !== undefined && progressBytes > previousBytes) {
|
|
285
303
|
// A byte increase is proof the model is still streaming this action's
|
|
286
304
|
// argument. Repeated zero-byte prep activity is only a heartbeat.
|
|
287
|
-
|
|
305
|
+
entry.lastProgressAt = now;
|
|
288
306
|
return true;
|
|
289
307
|
}
|
|
290
308
|
return false;
|
|
@@ -299,10 +317,17 @@ function updatePreparingActionState(
|
|
|
299
317
|
ev.type === "error" ||
|
|
300
318
|
ev.type === "missing_api_key"
|
|
301
319
|
) {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
320
|
+
if (ev.type === "tool_start" || ev.type === "tool_done") {
|
|
321
|
+
const tool = ev.tool?.trim();
|
|
322
|
+
const id = ev.id?.trim();
|
|
323
|
+
for (const [key, entry] of state.entries ?? []) {
|
|
324
|
+
if ((id && key === id) || (!id && entry.tool === tool)) {
|
|
325
|
+
state.entries?.delete(key);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
} else {
|
|
329
|
+
state.entries?.clear();
|
|
330
|
+
}
|
|
306
331
|
}
|
|
307
332
|
return undefined;
|
|
308
333
|
}
|
|
@@ -313,12 +338,16 @@ function hasStalledPreparingAction(state: PreparingActionState, now: number) {
|
|
|
313
338
|
// streaming for a long time. `lastProgressAt` advances on every delta
|
|
314
339
|
// heartbeat, so an actively-streaming large output keeps resetting this and
|
|
315
340
|
// survives; a genuinely stuck prep (keepalive-only, no deltas) trips it.
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
341
|
+
for (const entry of state.entries?.values() ?? []) {
|
|
342
|
+
if (
|
|
343
|
+
entry.startedAt !== undefined &&
|
|
344
|
+
now - (entry.lastProgressAt ?? entry.startedAt) >=
|
|
345
|
+
SSE_ACTION_PREPARATION_STALL_TIMEOUT_MS
|
|
346
|
+
) {
|
|
347
|
+
return true;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return false;
|
|
322
351
|
}
|
|
323
352
|
|
|
324
353
|
async function readChunkWithProgressTimeout(
|
|
@@ -16,9 +16,9 @@ import {
|
|
|
16
16
|
|
|
17
17
|
export default defineAction({
|
|
18
18
|
description:
|
|
19
|
-
"Change the coarse visibility of a shareable resource: 'private' keeps it owner-only, 'org' shares it with all members of the owner's organization, 'public' makes it accessible to anyone with the link.
|
|
20
|
-
// (audit H5) Visibility changes
|
|
21
|
-
//
|
|
19
|
+
"Change the coarse visibility of a shareable resource: 'private' keeps it owner-only, 'org' shares it with all members of the owner's organization, 'public' makes it accessible to anyone with the link. Visibility changes require owner or admin role.",
|
|
20
|
+
// (audit H5) Visibility changes can flip a private resource org-wide or
|
|
21
|
+
// public. Refuse from the tools iframe bridge.
|
|
22
22
|
toolCallable: false,
|
|
23
23
|
mcpApp: {
|
|
24
24
|
compactCatalog: true,
|
|
@@ -57,6 +57,14 @@ function escapeHtml(value: string): string {
|
|
|
57
57
|
.replace(/"/g, """);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
function createInsertedNodeId(prefix: string): string {
|
|
61
|
+
const random =
|
|
62
|
+
typeof crypto !== "undefined" && typeof crypto.randomUUID === "function"
|
|
63
|
+
? crypto.randomUUID().replace(/-/g, "").slice(0, 12)
|
|
64
|
+
: Math.random().toString(36).slice(2, 14);
|
|
65
|
+
return `inserted-${prefix}-${random}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
60
68
|
function insertBeforeClosingTag(
|
|
61
69
|
html: string,
|
|
62
70
|
closingTag: "main" | "body",
|
|
@@ -70,6 +78,7 @@ function insertBeforeClosingTag(
|
|
|
70
78
|
function appendAssetMarkup(
|
|
71
79
|
html: string,
|
|
72
80
|
args: z.infer<typeof schemaInput>,
|
|
81
|
+
nodeId: string,
|
|
73
82
|
): string {
|
|
74
83
|
const label = args.title?.trim() || args.altText?.trim() || "Generated asset";
|
|
75
84
|
const escapedUrl = escapeHtml(args.assetUrl);
|
|
@@ -82,7 +91,7 @@ function appendAssetMarkup(
|
|
|
82
91
|
? `<video src="${escapedUrl}" controls class="w-full rounded-xl object-cover"></video>`
|
|
83
92
|
: `<img src="${escapedUrl}" alt="${escapeHtml(args.altText?.trim() || label)}" class="w-full rounded-xl object-cover" />`;
|
|
84
93
|
const snippet = `
|
|
85
|
-
<section class="mx-auto my-8 max-w-5xl px-4" data-agent-native-asset${assetIdAttr}>
|
|
94
|
+
<section class="mx-auto my-8 max-w-5xl px-4" data-agent-native-asset data-agent-native-node-id="${escapeHtml(nodeId)}" data-agent-native-layer-name="${escapedLabel}"${assetIdAttr}>
|
|
86
95
|
<figure class="overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm">
|
|
87
96
|
${media}
|
|
88
97
|
<figcaption class="px-4 py-3 text-sm text-slate-600">${escapedLabel}</figcaption>
|
|
@@ -191,7 +200,8 @@ export default defineAction({
|
|
|
191
200
|
// Collab read is best-effort; fall back to stored content.
|
|
192
201
|
}
|
|
193
202
|
|
|
194
|
-
const
|
|
203
|
+
const insertedNodeId = createInsertedNodeId("asset");
|
|
204
|
+
const content = appendAssetMarkup(base, args, insertedNodeId);
|
|
195
205
|
const now = new Date().toISOString();
|
|
196
206
|
await db
|
|
197
207
|
.update(schema.designFiles)
|
|
@@ -213,6 +223,8 @@ export default defineAction({
|
|
|
213
223
|
fileId: file.id,
|
|
214
224
|
filename: file.filename,
|
|
215
225
|
inserted: true,
|
|
226
|
+
insertedNodeId,
|
|
227
|
+
insertedSelector: `[data-agent-native-node-id="${insertedNodeId}"]`,
|
|
216
228
|
assetId: args.assetId ?? null,
|
|
217
229
|
assetUrl: args.assetUrl,
|
|
218
230
|
};
|
|
@@ -50,9 +50,17 @@ function insertBeforeClosingTag(
|
|
|
50
50
|
return html.replace(pattern, `${snippet}\n</${closingTag}>`);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
function
|
|
53
|
+
function createInsertedNodeId(prefix: string): string {
|
|
54
|
+
const random =
|
|
55
|
+
typeof crypto !== "undefined" && typeof crypto.randomUUID === "function"
|
|
56
|
+
? crypto.randomUUID().replace(/-/g, "").slice(0, 12)
|
|
57
|
+
: Math.random().toString(36).slice(2, 14);
|
|
58
|
+
return `inserted-${prefix}-${random}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function nativeSnippet(kind: DesignNativeAssetKind, nodeId: string): string {
|
|
54
62
|
const attrs = (componentName: string) =>
|
|
55
|
-
`data-agent-native-native-asset data-agent-native-component="${componentName}" data-agent-native-layer-name="${componentName}"`;
|
|
63
|
+
`data-agent-native-native-asset data-agent-native-node-id="${nodeId}" data-agent-native-component="${componentName}" data-agent-native-layer-name="${componentName}"`;
|
|
56
64
|
switch (kind) {
|
|
57
65
|
case "section-frame":
|
|
58
66
|
return `
|
|
@@ -137,8 +145,9 @@ function nativeSnippet(kind: DesignNativeAssetKind): string {
|
|
|
137
145
|
function appendNativeAssetMarkup(
|
|
138
146
|
html: string,
|
|
139
147
|
kind: DesignNativeAssetKind,
|
|
148
|
+
nodeId: string,
|
|
140
149
|
): string {
|
|
141
|
-
const snippet = nativeSnippet(kind);
|
|
150
|
+
const snippet = nativeSnippet(kind, nodeId);
|
|
142
151
|
return (
|
|
143
152
|
insertBeforeClosingTag(html, "main", snippet) ??
|
|
144
153
|
insertBeforeClosingTag(html, "body", snippet) ??
|
|
@@ -239,7 +248,8 @@ export default defineAction({
|
|
|
239
248
|
// Collab read is best-effort; fall back to stored content.
|
|
240
249
|
}
|
|
241
250
|
|
|
242
|
-
const
|
|
251
|
+
const insertedNodeId = createInsertedNodeId("native");
|
|
252
|
+
const content = appendNativeAssetMarkup(base, args.kind, insertedNodeId);
|
|
243
253
|
const now = new Date().toISOString();
|
|
244
254
|
await db
|
|
245
255
|
.update(schema.designFiles)
|
|
@@ -261,6 +271,8 @@ export default defineAction({
|
|
|
261
271
|
fileId: file.id,
|
|
262
272
|
filename: file.filename,
|
|
263
273
|
inserted: true,
|
|
274
|
+
insertedNodeId,
|
|
275
|
+
insertedSelector: `[data-agent-native-node-id="${insertedNodeId}"]`,
|
|
264
276
|
source: "design-native",
|
|
265
277
|
kind: args.kind,
|
|
266
278
|
};
|