@agent-native/core 0.15.4 → 0.15.6
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/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +3 -2
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +12 -10
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/components/CodeRequiredDialog.js +1 -1
- package/dist/client/components/CodeRequiredDialog.js.map +1 -1
- package/dist/client/settings/BackgroundAgentSection.d.ts.map +1 -1
- package/dist/client/settings/BackgroundAgentSection.js +2 -1
- package/dist/client/settings/BackgroundAgentSection.js.map +1 -1
- package/dist/client/settings/BrowserSection.d.ts.map +1 -1
- package/dist/client/settings/BrowserSection.js +3 -2
- package/dist/client/settings/BrowserSection.js.map +1 -1
- package/dist/client/settings/SettingsPanel.js +1 -1
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/settings/VoiceTranscriptionSection.d.ts.map +1 -1
- package/dist/client/settings/VoiceTranscriptionSection.js +1 -0
- package/dist/client/settings/VoiceTranscriptionSection.js.map +1 -1
- package/dist/client/settings/useBuilderStatus.d.ts +1 -0
- package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
- package/dist/client/settings/useBuilderStatus.js +53 -12
- package/dist/client/settings/useBuilderStatus.js.map +1 -1
- package/dist/client/settings/useBuilderStatus.spec.js +43 -7
- package/dist/client/settings/useBuilderStatus.spec.js.map +1 -1
- package/dist/client/transcription/BuilderTranscriptionCta.d.ts.map +1 -1
- package/dist/client/transcription/BuilderTranscriptionCta.js +7 -2
- package/dist/client/transcription/BuilderTranscriptionCta.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +3 -1
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +19 -2
- package/dist/server/auth.js.map +1 -1
- package/dist/server/builder-browser.d.ts +15 -0
- package/dist/server/builder-browser.d.ts.map +1 -1
- package/dist/server/builder-browser.js +80 -1
- package/dist/server/builder-browser.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +58 -18
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/package.json +1 -1
- package/dist/client/dev-mode.d.ts +0 -14
- package/dist/client/dev-mode.d.ts.map +0 -1
- package/dist/client/dev-mode.js +0 -14
- package/dist/client/dev-mode.js.map +0 -1
- package/dist/client/extensions/EmbeddedTool.d.ts +0 -20
- package/dist/client/extensions/EmbeddedTool.d.ts.map +0 -1
- package/dist/client/extensions/EmbeddedTool.js +0 -199
- package/dist/client/extensions/EmbeddedTool.js.map +0 -1
- package/dist/client/extensions/ToolEditor.d.ts +0 -5
- package/dist/client/extensions/ToolEditor.d.ts.map +0 -1
- package/dist/client/extensions/ToolEditor.js +0 -129
- package/dist/client/extensions/ToolEditor.js.map +0 -1
- package/dist/client/extensions/ToolViewer.d.ts +0 -5
- package/dist/client/extensions/ToolViewer.d.ts.map +0 -1
- package/dist/client/extensions/ToolViewer.js +0 -400
- package/dist/client/extensions/ToolViewer.js.map +0 -1
- package/dist/client/extensions/ToolViewerPage.d.ts +0 -2
- package/dist/client/extensions/ToolViewerPage.d.ts.map +0 -1
- package/dist/client/extensions/ToolViewerPage.js +0 -24
- package/dist/client/extensions/ToolViewerPage.js.map +0 -1
- package/dist/client/extensions/ToolsListPage.d.ts +0 -2
- package/dist/client/extensions/ToolsListPage.d.ts.map +0 -1
- package/dist/client/extensions/ToolsListPage.js +0 -67
- package/dist/client/extensions/ToolsListPage.js.map +0 -1
- package/dist/client/extensions/ToolsSidebarSection.d.ts +0 -2
- package/dist/client/extensions/ToolsSidebarSection.d.ts.map +0 -1
- package/dist/client/extensions/ToolsSidebarSection.js +0 -236
- package/dist/client/extensions/ToolsSidebarSection.js.map +0 -1
- package/dist/client/extensions/tool-order.d.ts +0 -7
- package/dist/client/extensions/tool-order.d.ts.map +0 -1
- package/dist/client/extensions/tool-order.js +0 -47
- package/dist/client/extensions/tool-order.js.map +0 -1
- package/dist/client/tools/EmbeddedTool.d.ts +0 -20
- package/dist/client/tools/EmbeddedTool.d.ts.map +0 -1
- package/dist/client/tools/EmbeddedTool.js +0 -199
- package/dist/client/tools/EmbeddedTool.js.map +0 -1
- package/dist/client/tools/ExtensionSlot.d.ts +0 -27
- package/dist/client/tools/ExtensionSlot.d.ts.map +0 -1
- package/dist/client/tools/ExtensionSlot.js +0 -96
- package/dist/client/tools/ExtensionSlot.js.map +0 -1
- package/dist/client/tools/ToolEditor.d.ts +0 -5
- package/dist/client/tools/ToolEditor.d.ts.map +0 -1
- package/dist/client/tools/ToolEditor.js +0 -129
- package/dist/client/tools/ToolEditor.js.map +0 -1
- package/dist/client/tools/ToolViewer.d.ts +0 -5
- package/dist/client/tools/ToolViewer.d.ts.map +0 -1
- package/dist/client/tools/ToolViewer.js +0 -400
- package/dist/client/tools/ToolViewer.js.map +0 -1
- package/dist/client/tools/ToolViewerPage.d.ts +0 -2
- package/dist/client/tools/ToolViewerPage.d.ts.map +0 -1
- package/dist/client/tools/ToolViewerPage.js +0 -24
- package/dist/client/tools/ToolViewerPage.js.map +0 -1
- package/dist/client/tools/ToolsListPage.d.ts +0 -2
- package/dist/client/tools/ToolsListPage.d.ts.map +0 -1
- package/dist/client/tools/ToolsListPage.js +0 -67
- package/dist/client/tools/ToolsListPage.js.map +0 -1
- package/dist/client/tools/ToolsSidebarSection.d.ts +0 -2
- package/dist/client/tools/ToolsSidebarSection.d.ts.map +0 -1
- package/dist/client/tools/ToolsSidebarSection.js +0 -236
- package/dist/client/tools/ToolsSidebarSection.js.map +0 -1
- package/dist/client/tools/iframe-bridge.d.ts +0 -38
- package/dist/client/tools/iframe-bridge.d.ts.map +0 -1
- package/dist/client/tools/iframe-bridge.js +0 -207
- package/dist/client/tools/iframe-bridge.js.map +0 -1
- package/dist/client/tools/index.d.ts +0 -8
- package/dist/client/tools/index.d.ts.map +0 -1
- package/dist/client/tools/index.js +0 -8
- package/dist/client/tools/index.js.map +0 -1
- package/dist/client/tools/tool-order.d.ts +0 -7
- package/dist/client/tools/tool-order.d.ts.map +0 -1
- package/dist/client/tools/tool-order.js +0 -47
- package/dist/client/tools/tool-order.js.map +0 -1
- package/dist/server/local-migration.d.ts +0 -41
- package/dist/server/local-migration.d.ts.map +0 -1
- package/dist/server/local-migration.js +0 -235
- package/dist/server/local-migration.js.map +0 -1
- package/dist/tools/actions.d.ts +0 -3
- package/dist/tools/actions.d.ts.map +0 -1
- package/dist/tools/actions.js +0 -272
- package/dist/tools/actions.js.map +0 -1
- package/dist/tools/fetch-tool.d.ts +0 -23
- package/dist/tools/fetch-tool.d.ts.map +0 -1
- package/dist/tools/fetch-tool.js +0 -178
- package/dist/tools/fetch-tool.js.map +0 -1
- package/dist/tools/html-shell.d.ts +0 -45
- package/dist/tools/html-shell.d.ts.map +0 -1
- package/dist/tools/html-shell.js +0 -514
- package/dist/tools/html-shell.js.map +0 -1
- package/dist/tools/proxy-security.d.ts +0 -12
- package/dist/tools/proxy-security.d.ts.map +0 -1
- package/dist/tools/proxy-security.js +0 -158
- package/dist/tools/proxy-security.js.map +0 -1
- package/dist/tools/routes.d.ts +0 -2
- package/dist/tools/routes.d.ts.map +0 -1
- package/dist/tools/routes.js +0 -627
- package/dist/tools/routes.js.map +0 -1
- package/dist/tools/schema.d.ts +0 -664
- package/dist/tools/schema.d.ts.map +0 -1
- package/dist/tools/schema.js +0 -146
- package/dist/tools/schema.js.map +0 -1
- package/dist/tools/slots/routes.d.ts +0 -15
- package/dist/tools/slots/routes.d.ts.map +0 -1
- package/dist/tools/slots/routes.js +0 -94
- package/dist/tools/slots/routes.js.map +0 -1
- package/dist/tools/slots/schema.d.ts +0 -303
- package/dist/tools/slots/schema.d.ts.map +0 -1
- package/dist/tools/slots/schema.js +0 -76
- package/dist/tools/slots/schema.js.map +0 -1
- package/dist/tools/slots/store.d.ts +0 -66
- package/dist/tools/slots/store.d.ts.map +0 -1
- package/dist/tools/slots/store.js +0 -227
- package/dist/tools/slots/store.js.map +0 -1
- package/dist/tools/store.d.ts +0 -40
- package/dist/tools/store.d.ts.map +0 -1
- package/dist/tools/store.js +0 -193
- package/dist/tools/store.js.map +0 -1
- package/dist/tools/theme.d.ts +0 -2
- package/dist/tools/theme.d.ts.map +0 -1
- package/dist/tools/theme.js +0 -67
- package/dist/tools/theme.js.map +0 -1
- package/dist/tools/url-safety.d.ts +0 -24
- package/dist/tools/url-safety.d.ts.map +0 -1
- package/dist/tools/url-safety.js +0 -224
- package/dist/tools/url-safety.js.map +0 -1
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { agentNativePath } from "../api-path.js";
|
|
3
|
-
import { useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
-
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
5
|
-
import { useNavigate } from "react-router";
|
|
6
|
-
import { IconDots, IconExternalLink, IconLayoutSidebarRightCollapse, IconTrash, } from "@tabler/icons-react";
|
|
7
|
-
import { Popover, PopoverContent, PopoverTrigger, } from "../components/ui/popover.js";
|
|
8
|
-
import { isAllowedToolPath, sanitizeToolRequestOptions, checkBridgePolicy, } from "./iframe-bridge.js";
|
|
9
|
-
/**
|
|
10
|
-
* Renders a tool inline as a small auto-sized iframe — for use inside an
|
|
11
|
-
* `<ExtensionSlot>`. Different from `<ToolViewer>` (which is full-page with a
|
|
12
|
-
* toolbar): no header, sized to content, receives a `slotContext`.
|
|
13
|
-
*/
|
|
14
|
-
export function EmbeddedTool({ toolId, slotId, context, className, initialHeight = 80, }) {
|
|
15
|
-
const iframeRef = useRef(null);
|
|
16
|
-
const [height, setHeight] = useState(initialHeight);
|
|
17
|
-
const [isDark, setIsDark] = useState(false);
|
|
18
|
-
// (audit H4) Mirror ToolViewer's role-aware gating; deny-by-default until
|
|
19
|
-
// the iframe's render binding announcement arrives.
|
|
20
|
-
const bridgeContextRef = useRef({
|
|
21
|
-
role: "viewer",
|
|
22
|
-
isAuthor: false,
|
|
23
|
-
});
|
|
24
|
-
useEffect(() => {
|
|
25
|
-
setIsDark(document.documentElement.classList.contains("dark"));
|
|
26
|
-
const observer = new MutationObserver(() => {
|
|
27
|
-
setIsDark(document.documentElement.classList.contains("dark"));
|
|
28
|
-
});
|
|
29
|
-
observer.observe(document.documentElement, {
|
|
30
|
-
attributes: true,
|
|
31
|
-
attributeFilter: ["class"],
|
|
32
|
-
});
|
|
33
|
-
return () => observer.disconnect();
|
|
34
|
-
}, []);
|
|
35
|
-
const { data: tool } = useQuery({
|
|
36
|
-
queryKey: ["tool", toolId],
|
|
37
|
-
queryFn: async () => {
|
|
38
|
-
const res = await fetch(agentNativePath(`/_agent-native/tools/${toolId}`));
|
|
39
|
-
if (!res.ok)
|
|
40
|
-
throw new Error("Failed to fetch tool");
|
|
41
|
-
return res.json();
|
|
42
|
-
},
|
|
43
|
-
});
|
|
44
|
-
const iframeSrc = useMemo(() => {
|
|
45
|
-
const v = encodeURIComponent(tool?.updatedAt ?? "");
|
|
46
|
-
return agentNativePath(`/_agent-native/tools/${toolId}/render?slot=${encodeURIComponent(slotId)}&dark=${isDark}&v=${v}`);
|
|
47
|
-
}, [toolId, slotId, isDark, tool?.updatedAt]);
|
|
48
|
-
// Forward slot context whenever it changes. The iframe's own load handler
|
|
49
|
-
// posts the initial value once it's ready; this effect handles updates.
|
|
50
|
-
const contextJson = JSON.stringify(context ?? {});
|
|
51
|
-
useEffect(() => {
|
|
52
|
-
const win = iframeRef.current?.contentWindow;
|
|
53
|
-
if (!win)
|
|
54
|
-
return;
|
|
55
|
-
win.postMessage({ type: "agent-native-slot-context", context: context ?? {} }, "*");
|
|
56
|
-
}, [contextJson]);
|
|
57
|
-
// Bridge tool requests + height reports.
|
|
58
|
-
useEffect(() => {
|
|
59
|
-
const handleMessage = async (event) => {
|
|
60
|
-
if (event.source !== iframeRef.current?.contentWindow)
|
|
61
|
-
return;
|
|
62
|
-
const message = event.data;
|
|
63
|
-
if (!message || typeof message !== "object")
|
|
64
|
-
return;
|
|
65
|
-
if (message.type === "agent-native-tool-binding") {
|
|
66
|
-
const binding = message.binding ?? {};
|
|
67
|
-
const role = binding.role === "owner" ||
|
|
68
|
-
binding.role === "admin" ||
|
|
69
|
-
binding.role === "editor" ||
|
|
70
|
-
binding.role === "viewer"
|
|
71
|
-
? binding.role
|
|
72
|
-
: "viewer";
|
|
73
|
-
bridgeContextRef.current = {
|
|
74
|
-
role,
|
|
75
|
-
isAuthor: !!binding.isAuthor,
|
|
76
|
-
};
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
if (message.type === "agent-native-tool-resize") {
|
|
80
|
-
const h = Number(message.height);
|
|
81
|
-
if (Number.isFinite(h) && h > 0) {
|
|
82
|
-
setHeight(Math.ceil(h));
|
|
83
|
-
}
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
if (message.type !== "agent-native-tool-request")
|
|
87
|
-
return;
|
|
88
|
-
const requestId = String(message.requestId ?? "");
|
|
89
|
-
const path = String(message.path ?? "");
|
|
90
|
-
const respond = (payload) => {
|
|
91
|
-
iframeRef.current?.contentWindow?.postMessage({ type: "agent-native-tool-response", requestId, ...payload }, "*");
|
|
92
|
-
};
|
|
93
|
-
if (!requestId || !isAllowedToolPath(path, toolId)) {
|
|
94
|
-
respond({ error: "Tool request path is not allowed" });
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
try {
|
|
98
|
-
const options = sanitizeToolRequestOptions(message.options);
|
|
99
|
-
// (audit H4) Role-aware gating: viewer-shared tools can read but not
|
|
100
|
-
// write. The bridge policy is decided here in the parent before the
|
|
101
|
-
// request leaves; the server enforces a second layer.
|
|
102
|
-
const policy = checkBridgePolicy(path, options.method ?? "GET", bridgeContextRef.current);
|
|
103
|
-
if (!policy.ok) {
|
|
104
|
-
respond({
|
|
105
|
-
response: {
|
|
106
|
-
ok: false,
|
|
107
|
-
status: 403,
|
|
108
|
-
statusText: "Forbidden",
|
|
109
|
-
body: { error: policy.error },
|
|
110
|
-
},
|
|
111
|
-
});
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
// (audit H5) Same tool-bridge tagging as <ToolViewer>. action-routes
|
|
115
|
-
// uses these headers to enforce per-action `toolCallable` opt-in.
|
|
116
|
-
const finalHeaders = new Headers(options.headers ?? undefined);
|
|
117
|
-
finalHeaders.set("X-Agent-Native-Tool-Bridge", "1");
|
|
118
|
-
finalHeaders.set("X-Agent-Native-Tool-Id", toolId);
|
|
119
|
-
const res = await fetch(agentNativePath(path), {
|
|
120
|
-
...options,
|
|
121
|
-
headers: finalHeaders,
|
|
122
|
-
credentials: "same-origin",
|
|
123
|
-
});
|
|
124
|
-
const text = await res.text();
|
|
125
|
-
let body = text;
|
|
126
|
-
if (text) {
|
|
127
|
-
try {
|
|
128
|
-
body = JSON.parse(text);
|
|
129
|
-
}
|
|
130
|
-
catch {
|
|
131
|
-
body = text;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
respond({
|
|
135
|
-
response: {
|
|
136
|
-
ok: res.ok,
|
|
137
|
-
status: res.status,
|
|
138
|
-
statusText: res.statusText,
|
|
139
|
-
body,
|
|
140
|
-
},
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
catch (err) {
|
|
144
|
-
respond({ error: err?.message ?? "Tool host request failed" });
|
|
145
|
-
}
|
|
146
|
-
};
|
|
147
|
-
window.addEventListener("message", handleMessage);
|
|
148
|
-
return () => window.removeEventListener("message", handleMessage);
|
|
149
|
-
}, [toolId]);
|
|
150
|
-
if (!tool) {
|
|
151
|
-
return (_jsx("div", { className: className, style: { height: initialHeight }, "aria-busy": "true" }));
|
|
152
|
-
}
|
|
153
|
-
return (_jsxs("div", { className: `relative group/embedded-tool ${className ?? ""}`, children: [_jsx("iframe", { ref: iframeRef, src: iframeSrc, title: tool.name, sandbox: "allow-scripts allow-forms", style: { width: "100%", border: 0, height, display: "block" }, onLoad: () => {
|
|
154
|
-
iframeRef.current?.contentWindow?.postMessage({ type: "agent-native-slot-context", context: context ?? {} }, "*");
|
|
155
|
-
} }, `${toolId}-${tool.updatedAt ?? ""}`), _jsx(EmbeddedToolMenu, { toolId: toolId, slotId: slotId, toolName: tool.name })] }));
|
|
156
|
-
}
|
|
157
|
-
function EmbeddedToolMenu({ toolId, slotId, toolName, }) {
|
|
158
|
-
const [open, setOpen] = useState(false);
|
|
159
|
-
const [confirmingDelete, setConfirmingDelete] = useState(false);
|
|
160
|
-
const queryClient = useQueryClient();
|
|
161
|
-
const navigate = useNavigate();
|
|
162
|
-
const closeMenu = () => {
|
|
163
|
-
setOpen(false);
|
|
164
|
-
setConfirmingDelete(false);
|
|
165
|
-
};
|
|
166
|
-
const removeFromSlot = async () => {
|
|
167
|
-
closeMenu();
|
|
168
|
-
queryClient.setQueryData(["slot-installs", slotId], (old) => (old ?? []).filter((i) => i.toolId !== toolId));
|
|
169
|
-
try {
|
|
170
|
-
await fetch(agentNativePath(`/_agent-native/slots/${encodeURIComponent(slotId)}/install/${encodeURIComponent(toolId)}`), { method: "DELETE" });
|
|
171
|
-
}
|
|
172
|
-
finally {
|
|
173
|
-
queryClient.invalidateQueries({ queryKey: ["slot-installs", slotId] });
|
|
174
|
-
}
|
|
175
|
-
};
|
|
176
|
-
const deleteTool = async () => {
|
|
177
|
-
closeMenu();
|
|
178
|
-
queryClient.setQueryData(["slot-installs", slotId], (old) => (old ?? []).filter((i) => i.toolId !== toolId));
|
|
179
|
-
try {
|
|
180
|
-
await fetch(agentNativePath(`/_agent-native/tools/${toolId}`), {
|
|
181
|
-
method: "DELETE",
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
finally {
|
|
185
|
-
queryClient.invalidateQueries({ queryKey: ["slot-installs", slotId] });
|
|
186
|
-
queryClient.invalidateQueries({ queryKey: ["tool", toolId] });
|
|
187
|
-
queryClient.invalidateQueries({ queryKey: ["tools"] });
|
|
188
|
-
}
|
|
189
|
-
};
|
|
190
|
-
return (_jsxs(Popover, { open: open, onOpenChange: (o) => {
|
|
191
|
-
setOpen(o);
|
|
192
|
-
if (!o)
|
|
193
|
-
setConfirmingDelete(false);
|
|
194
|
-
}, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx("button", { type: "button", className: "absolute top-1 right-1 flex h-6 w-6 items-center justify-center rounded-md bg-background/60 text-muted-foreground/60 opacity-0 hover:bg-accent hover:text-foreground hover:opacity-100 group-hover/embedded-tool:opacity-100 cursor-pointer transition-opacity", title: `${toolName} options`, "aria-label": `${toolName} options`, children: _jsx(IconDots, { className: "h-3.5 w-3.5" }) }) }), _jsx(PopoverContent, { align: "end", sideOffset: 4, className: "w-56 p-1", children: !confirmingDelete ? (_jsxs("div", { className: "flex flex-col", children: [_jsxs("button", { type: "button", onClick: () => {
|
|
195
|
-
closeMenu();
|
|
196
|
-
navigate(`/tools/${toolId}`);
|
|
197
|
-
}, className: "flex items-center gap-2 rounded-sm px-2 py-1.5 text-[12px] hover:bg-accent cursor-pointer text-left", children: [_jsx(IconExternalLink, { className: "h-3.5 w-3.5" }), _jsx("span", { children: "Open full view" })] }), _jsxs("button", { type: "button", onClick: removeFromSlot, className: "flex items-center gap-2 rounded-sm px-2 py-1.5 text-[12px] hover:bg-accent cursor-pointer text-left", children: [_jsx(IconLayoutSidebarRightCollapse, { className: "h-3.5 w-3.5" }), _jsx("span", { children: "Remove from this widget area" })] }), _jsx("div", { className: "my-1 h-px bg-border/40" }), _jsxs("button", { type: "button", onClick: () => setConfirmingDelete(true), className: "flex items-center gap-2 rounded-sm px-2 py-1.5 text-[12px] text-destructive hover:bg-destructive/10 cursor-pointer text-left", children: [_jsx(IconTrash, { className: "h-3.5 w-3.5" }), _jsx("span", { children: "Delete tool\u2026" })] })] })) : (_jsxs("div", { className: "flex flex-col gap-2 p-2", children: [_jsxs("p", { className: "text-[12px]", children: ["Delete ", _jsx("span", { className: "font-medium", children: toolName }), "? This removes the tool everywhere, for everyone it's shared with."] }), _jsxs("div", { className: "flex justify-end gap-1", children: [_jsx("button", { type: "button", onClick: () => setConfirmingDelete(false), className: "rounded-md px-2 py-1 text-[12px] hover:bg-accent cursor-pointer", children: "Cancel" }), _jsx("button", { type: "button", onClick: deleteTool, className: "rounded-md bg-destructive px-2 py-1 text-[12px] text-destructive-foreground hover:bg-destructive/90 cursor-pointer", children: "Delete" })] })] })) })] }));
|
|
198
|
-
}
|
|
199
|
-
//# sourceMappingURL=EmbeddedTool.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"EmbeddedTool.js","sourceRoot":"","sources":["../../../src/client/tools/EmbeddedTool.tsx"],"names":[],"mappings":"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","sourcesContent":["import { agentNativePath } from \"../api-path.js\";\nimport { useEffect, useMemo, useRef, useState } from \"react\";\nimport { useQuery, useQueryClient } from \"@tanstack/react-query\";\nimport { useNavigate } from \"react-router\";\nimport {\n IconDots,\n IconExternalLink,\n IconLayoutSidebarRightCollapse,\n IconTrash,\n} from \"@tabler/icons-react\";\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"../components/ui/popover.js\";\nimport {\n isAllowedToolPath,\n sanitizeToolRequestOptions,\n checkBridgePolicy,\n type ToolBridgeRole,\n} from \"./iframe-bridge.js\";\n\ninterface Tool {\n id: string;\n name: string;\n description?: string;\n content?: string;\n updatedAt?: string;\n}\n\nexport interface EmbeddedToolProps {\n toolId: string;\n /** Slot identifier passed via the iframe URL so the tool runtime knows it's\n * embedded and enables auto-resize. */\n slotId: string;\n /** Object pushed into the tool as `window.slotContext`. Re-posted whenever\n * the host re-renders with a new context. */\n context?: Record<string, unknown> | null;\n /** Optional className applied to the iframe container. */\n className?: string;\n /** Initial iframe height before content reports a real height. */\n initialHeight?: number;\n}\n\n/**\n * Renders a tool inline as a small auto-sized iframe — for use inside an\n * `<ExtensionSlot>`. Different from `<ToolViewer>` (which is full-page with a\n * toolbar): no header, sized to content, receives a `slotContext`.\n */\nexport function EmbeddedTool({\n toolId,\n slotId,\n context,\n className,\n initialHeight = 80,\n}: EmbeddedToolProps) {\n const iframeRef = useRef<HTMLIFrameElement | null>(null);\n const [height, setHeight] = useState<number>(initialHeight);\n const [isDark, setIsDark] = useState(false);\n // (audit H4) Mirror ToolViewer's role-aware gating; deny-by-default until\n // the iframe's render binding announcement arrives.\n const bridgeContextRef = useRef<{\n role: ToolBridgeRole;\n isAuthor: boolean;\n }>({\n role: \"viewer\",\n isAuthor: false,\n });\n\n useEffect(() => {\n setIsDark(document.documentElement.classList.contains(\"dark\"));\n const observer = new MutationObserver(() => {\n setIsDark(document.documentElement.classList.contains(\"dark\"));\n });\n observer.observe(document.documentElement, {\n attributes: true,\n attributeFilter: [\"class\"],\n });\n return () => observer.disconnect();\n }, []);\n\n const { data: tool } = useQuery<Tool>({\n queryKey: [\"tool\", toolId],\n queryFn: async () => {\n const res = await fetch(\n agentNativePath(`/_agent-native/tools/${toolId}`),\n );\n if (!res.ok) throw new Error(\"Failed to fetch tool\");\n return res.json();\n },\n });\n\n const iframeSrc = useMemo(() => {\n const v = encodeURIComponent(tool?.updatedAt ?? \"\");\n return agentNativePath(\n `/_agent-native/tools/${toolId}/render?slot=${encodeURIComponent(slotId)}&dark=${isDark}&v=${v}`,\n );\n }, [toolId, slotId, isDark, tool?.updatedAt]);\n\n // Forward slot context whenever it changes. The iframe's own load handler\n // posts the initial value once it's ready; this effect handles updates.\n const contextJson = JSON.stringify(context ?? {});\n useEffect(() => {\n const win = iframeRef.current?.contentWindow;\n if (!win) return;\n win.postMessage(\n { type: \"agent-native-slot-context\", context: context ?? {} },\n \"*\",\n );\n }, [contextJson]);\n\n // Bridge tool requests + height reports.\n useEffect(() => {\n const handleMessage = async (event: MessageEvent) => {\n if (event.source !== iframeRef.current?.contentWindow) return;\n const message = event.data;\n if (!message || typeof message !== \"object\") return;\n\n if (message.type === \"agent-native-tool-binding\") {\n const binding = (message as any).binding ?? {};\n const role: ToolBridgeRole =\n binding.role === \"owner\" ||\n binding.role === \"admin\" ||\n binding.role === \"editor\" ||\n binding.role === \"viewer\"\n ? binding.role\n : \"viewer\";\n bridgeContextRef.current = {\n role,\n isAuthor: !!binding.isAuthor,\n };\n return;\n }\n\n if (message.type === \"agent-native-tool-resize\") {\n const h = Number(message.height);\n if (Number.isFinite(h) && h > 0) {\n setHeight(Math.ceil(h));\n }\n return;\n }\n\n if (message.type !== \"agent-native-tool-request\") return;\n\n const requestId = String(message.requestId ?? \"\");\n const path = String(message.path ?? \"\");\n const respond = (payload: Record<string, unknown>) => {\n iframeRef.current?.contentWindow?.postMessage(\n { type: \"agent-native-tool-response\", requestId, ...payload },\n \"*\",\n );\n };\n\n if (!requestId || !isAllowedToolPath(path, toolId)) {\n respond({ error: \"Tool request path is not allowed\" });\n return;\n }\n\n try {\n const options = sanitizeToolRequestOptions(message.options);\n // (audit H4) Role-aware gating: viewer-shared tools can read but not\n // write. The bridge policy is decided here in the parent before the\n // request leaves; the server enforces a second layer.\n const policy = checkBridgePolicy(\n path,\n options.method ?? \"GET\",\n bridgeContextRef.current,\n );\n if (!policy.ok) {\n respond({\n response: {\n ok: false,\n status: 403,\n statusText: \"Forbidden\",\n body: { error: policy.error },\n },\n });\n return;\n }\n // (audit H5) Same tool-bridge tagging as <ToolViewer>. action-routes\n // uses these headers to enforce per-action `toolCallable` opt-in.\n const finalHeaders = new Headers(options.headers ?? undefined);\n finalHeaders.set(\"X-Agent-Native-Tool-Bridge\", \"1\");\n finalHeaders.set(\"X-Agent-Native-Tool-Id\", toolId);\n const res = await fetch(agentNativePath(path), {\n ...options,\n headers: finalHeaders,\n credentials: \"same-origin\",\n });\n const text = await res.text();\n let body: unknown = text;\n if (text) {\n try {\n body = JSON.parse(text);\n } catch {\n body = text;\n }\n }\n respond({\n response: {\n ok: res.ok,\n status: res.status,\n statusText: res.statusText,\n body,\n },\n });\n } catch (err: any) {\n respond({ error: err?.message ?? \"Tool host request failed\" });\n }\n };\n\n window.addEventListener(\"message\", handleMessage);\n return () => window.removeEventListener(\"message\", handleMessage);\n }, [toolId]);\n\n if (!tool) {\n return (\n <div\n className={className}\n style={{ height: initialHeight }}\n aria-busy=\"true\"\n />\n );\n }\n\n return (\n <div className={`relative group/embedded-tool ${className ?? \"\"}`}>\n <iframe\n ref={iframeRef}\n key={`${toolId}-${tool.updatedAt ?? \"\"}`}\n src={iframeSrc}\n title={tool.name}\n sandbox=\"allow-scripts allow-forms\"\n style={{ width: \"100%\", border: 0, height, display: \"block\" }}\n onLoad={() => {\n iframeRef.current?.contentWindow?.postMessage(\n { type: \"agent-native-slot-context\", context: context ?? {} },\n \"*\",\n );\n }}\n />\n <EmbeddedToolMenu toolId={toolId} slotId={slotId} toolName={tool.name} />\n </div>\n );\n}\n\nfunction EmbeddedToolMenu({\n toolId,\n slotId,\n toolName,\n}: {\n toolId: string;\n slotId: string;\n toolName: string;\n}) {\n const [open, setOpen] = useState(false);\n const [confirmingDelete, setConfirmingDelete] = useState(false);\n const queryClient = useQueryClient();\n const navigate = useNavigate();\n\n const closeMenu = () => {\n setOpen(false);\n setConfirmingDelete(false);\n };\n\n const removeFromSlot = async () => {\n closeMenu();\n queryClient.setQueryData<any[]>([\"slot-installs\", slotId], (old) =>\n (old ?? []).filter((i) => i.toolId !== toolId),\n );\n try {\n await fetch(\n agentNativePath(\n `/_agent-native/slots/${encodeURIComponent(slotId)}/install/${encodeURIComponent(toolId)}`,\n ),\n { method: \"DELETE\" },\n );\n } finally {\n queryClient.invalidateQueries({ queryKey: [\"slot-installs\", slotId] });\n }\n };\n\n const deleteTool = async () => {\n closeMenu();\n queryClient.setQueryData<any[]>([\"slot-installs\", slotId], (old) =>\n (old ?? []).filter((i) => i.toolId !== toolId),\n );\n try {\n await fetch(agentNativePath(`/_agent-native/tools/${toolId}`), {\n method: \"DELETE\",\n });\n } finally {\n queryClient.invalidateQueries({ queryKey: [\"slot-installs\", slotId] });\n queryClient.invalidateQueries({ queryKey: [\"tool\", toolId] });\n queryClient.invalidateQueries({ queryKey: [\"tools\"] });\n }\n };\n\n return (\n <Popover\n open={open}\n onOpenChange={(o) => {\n setOpen(o);\n if (!o) setConfirmingDelete(false);\n }}\n >\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n className=\"absolute top-1 right-1 flex h-6 w-6 items-center justify-center rounded-md bg-background/60 text-muted-foreground/60 opacity-0 hover:bg-accent hover:text-foreground hover:opacity-100 group-hover/embedded-tool:opacity-100 cursor-pointer transition-opacity\"\n title={`${toolName} options`}\n aria-label={`${toolName} options`}\n >\n <IconDots className=\"h-3.5 w-3.5\" />\n </button>\n </PopoverTrigger>\n <PopoverContent align=\"end\" sideOffset={4} className=\"w-56 p-1\">\n {!confirmingDelete ? (\n <div className=\"flex flex-col\">\n <button\n type=\"button\"\n onClick={() => {\n closeMenu();\n navigate(`/tools/${toolId}`);\n }}\n className=\"flex items-center gap-2 rounded-sm px-2 py-1.5 text-[12px] hover:bg-accent cursor-pointer text-left\"\n >\n <IconExternalLink className=\"h-3.5 w-3.5\" />\n <span>Open full view</span>\n </button>\n <button\n type=\"button\"\n onClick={removeFromSlot}\n className=\"flex items-center gap-2 rounded-sm px-2 py-1.5 text-[12px] hover:bg-accent cursor-pointer text-left\"\n >\n <IconLayoutSidebarRightCollapse className=\"h-3.5 w-3.5\" />\n <span>Remove from this widget area</span>\n </button>\n <div className=\"my-1 h-px bg-border/40\" />\n <button\n type=\"button\"\n onClick={() => setConfirmingDelete(true)}\n className=\"flex items-center gap-2 rounded-sm px-2 py-1.5 text-[12px] text-destructive hover:bg-destructive/10 cursor-pointer text-left\"\n >\n <IconTrash className=\"h-3.5 w-3.5\" />\n <span>Delete tool…</span>\n </button>\n </div>\n ) : (\n <div className=\"flex flex-col gap-2 p-2\">\n <p className=\"text-[12px]\">\n Delete <span className=\"font-medium\">{toolName}</span>? This\n removes the tool everywhere, for everyone it's shared with.\n </p>\n <div className=\"flex justify-end gap-1\">\n <button\n type=\"button\"\n onClick={() => setConfirmingDelete(false)}\n className=\"rounded-md px-2 py-1 text-[12px] hover:bg-accent cursor-pointer\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={deleteTool}\n className=\"rounded-md bg-destructive px-2 py-1 text-[12px] text-destructive-foreground hover:bg-destructive/90 cursor-pointer\"\n >\n Delete\n </button>\n </div>\n </div>\n )}\n </PopoverContent>\n </Popover>\n );\n}\n"]}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
export interface ExtensionSlotProps {
|
|
2
|
-
/** Stable slot identifier — convention: `<app>.<area>.<position>`. */
|
|
3
|
-
id: string;
|
|
4
|
-
/** Object pushed to each embedded tool as `slotContext`. */
|
|
5
|
-
context?: Record<string, unknown> | null;
|
|
6
|
-
/** Show a small "+" affordance when the slot has no installs. Default: false. */
|
|
7
|
-
showEmptyAffordance?: boolean;
|
|
8
|
-
/** Optional className applied to the wrapper. */
|
|
9
|
-
className?: string;
|
|
10
|
-
/** Optional className applied to each EmbeddedTool. */
|
|
11
|
-
toolClassName?: string;
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* A named UI slot that user-installed tools can render into. Apps drop this
|
|
15
|
-
* component wherever they want to allow extensions; the framework handles
|
|
16
|
-
* fetching, sandboxing, context delivery, and lifecycle.
|
|
17
|
-
*
|
|
18
|
-
* Example:
|
|
19
|
-
*
|
|
20
|
-
* <ExtensionSlot
|
|
21
|
-
* id="mail.contact-sidebar.bottom"
|
|
22
|
-
* context={{ contactEmail }}
|
|
23
|
-
* showEmptyAffordance
|
|
24
|
-
* />
|
|
25
|
-
*/
|
|
26
|
-
export declare function ExtensionSlot({ id, context, showEmptyAffordance, className, toolClassName, }: ExtensionSlotProps): import("react/jsx-runtime").JSX.Element;
|
|
27
|
-
//# sourceMappingURL=ExtensionSlot.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ExtensionSlot.d.ts","sourceRoot":"","sources":["../../../src/client/tools/ExtensionSlot.tsx"],"names":[],"mappings":"AA+BA,MAAM,WAAW,kBAAkB;IACjC,sEAAsE;IACtE,EAAE,EAAE,MAAM,CAAC;IACX,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACzC,iFAAiF;IACjF,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,iDAAiD;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,EAC5B,EAAE,EACF,OAAO,EACP,mBAAmB,EACnB,SAAS,EACT,aAAa,GACd,EAAE,kBAAkB,2CAwCpB"}
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { agentNativePath } from "../api-path.js";
|
|
3
|
-
import { useState } from "react";
|
|
4
|
-
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
5
|
-
import { IconPlus } from "@tabler/icons-react";
|
|
6
|
-
import { Popover, PopoverContent, PopoverTrigger, } from "../components/ui/popover.js";
|
|
7
|
-
import { sendToAgentChat } from "../agent-chat.js";
|
|
8
|
-
import { EmbeddedTool } from "./EmbeddedTool.js";
|
|
9
|
-
/**
|
|
10
|
-
* A named UI slot that user-installed tools can render into. Apps drop this
|
|
11
|
-
* component wherever they want to allow extensions; the framework handles
|
|
12
|
-
* fetching, sandboxing, context delivery, and lifecycle.
|
|
13
|
-
*
|
|
14
|
-
* Example:
|
|
15
|
-
*
|
|
16
|
-
* <ExtensionSlot
|
|
17
|
-
* id="mail.contact-sidebar.bottom"
|
|
18
|
-
* context={{ contactEmail }}
|
|
19
|
-
* showEmptyAffordance
|
|
20
|
-
* />
|
|
21
|
-
*/
|
|
22
|
-
export function ExtensionSlot({ id, context, showEmptyAffordance, className, toolClassName, }) {
|
|
23
|
-
const { data: installs = [], isLoading } = useQuery({
|
|
24
|
-
queryKey: ["slot-installs", id],
|
|
25
|
-
queryFn: async () => {
|
|
26
|
-
const res = await fetch(agentNativePath(`/_agent-native/slots/${encodeURIComponent(id)}/installs`));
|
|
27
|
-
if (!res.ok)
|
|
28
|
-
return [];
|
|
29
|
-
return res.json();
|
|
30
|
-
},
|
|
31
|
-
});
|
|
32
|
-
if (isLoading) {
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
35
|
-
if (installs.length === 0) {
|
|
36
|
-
if (!showEmptyAffordance)
|
|
37
|
-
return null;
|
|
38
|
-
return (_jsx("div", { className: className, children: _jsx(SlotEmptyAffordance, { slotId: id }) }));
|
|
39
|
-
}
|
|
40
|
-
return (_jsx("div", { className: className, children: installs.map((install) => (_jsx(EmbeddedTool, { toolId: install.toolId, slotId: id, context: context, className: toolClassName }, install.installId))) }));
|
|
41
|
-
}
|
|
42
|
-
function SlotEmptyAffordance({ slotId }) {
|
|
43
|
-
const [open, setOpen] = useState(false);
|
|
44
|
-
const { data: available = [], isLoading } = useQuery({
|
|
45
|
-
queryKey: ["slot-available", slotId],
|
|
46
|
-
queryFn: async () => {
|
|
47
|
-
const res = await fetch(agentNativePath(`/_agent-native/slots/${encodeURIComponent(slotId)}/available`));
|
|
48
|
-
if (!res.ok)
|
|
49
|
-
return [];
|
|
50
|
-
return res.json();
|
|
51
|
-
},
|
|
52
|
-
enabled: open,
|
|
53
|
-
});
|
|
54
|
-
const queryClient = useQueryClient();
|
|
55
|
-
const install = async (toolId) => {
|
|
56
|
-
queryClient.setQueryData(["slot-installs", slotId], (old) => {
|
|
57
|
-
const tool = available.find((t) => t.toolId === toolId);
|
|
58
|
-
if (!tool || !old)
|
|
59
|
-
return old;
|
|
60
|
-
return [
|
|
61
|
-
...old,
|
|
62
|
-
{
|
|
63
|
-
installId: `optimistic-${toolId}`,
|
|
64
|
-
toolId,
|
|
65
|
-
name: tool.name,
|
|
66
|
-
description: tool.description,
|
|
67
|
-
icon: tool.icon,
|
|
68
|
-
updatedAt: new Date().toISOString(),
|
|
69
|
-
position: old.length,
|
|
70
|
-
config: tool.config,
|
|
71
|
-
},
|
|
72
|
-
];
|
|
73
|
-
});
|
|
74
|
-
setOpen(false);
|
|
75
|
-
try {
|
|
76
|
-
await fetch(agentNativePath(`/_agent-native/slots/${encodeURIComponent(slotId)}/install`), {
|
|
77
|
-
method: "POST",
|
|
78
|
-
headers: { "Content-Type": "application/json" },
|
|
79
|
-
body: JSON.stringify({ toolId }),
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
finally {
|
|
83
|
-
queryClient.invalidateQueries({ queryKey: ["slot-installs", slotId] });
|
|
84
|
-
}
|
|
85
|
-
};
|
|
86
|
-
const requestNew = () => {
|
|
87
|
-
setOpen(false);
|
|
88
|
-
sendToAgentChat({
|
|
89
|
-
message: `Create a new widget that fits in slot "${slotId}". I'll describe what it should do next.`,
|
|
90
|
-
submit: false,
|
|
91
|
-
openSidebar: true,
|
|
92
|
-
});
|
|
93
|
-
};
|
|
94
|
-
return (_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: "flex w-full items-center gap-2 px-4 py-2 text-[11px] text-muted-foreground/60 hover:text-muted-foreground cursor-pointer", title: "Add a widget", children: [_jsx("div", { className: "h-5 w-5 rounded-md border border-dashed border-border/40 flex items-center justify-center shrink-0", children: _jsx(IconPlus, { className: "h-3 w-3" }) }), _jsx("span", { children: "Add widget" })] }) }), _jsxs(PopoverContent, { side: "left", align: "end", sideOffset: 8, className: "w-72 p-0 overflow-hidden", children: [_jsxs("div", { className: "px-3 py-2 border-b border-border/40", children: [_jsx("p", { className: "text-[12px] font-medium", children: "Add widget here" }), _jsx("p", { className: "text-[11px] text-muted-foreground/70", children: slotId })] }), _jsxs("div", { className: "max-h-72 overflow-y-auto py-1", children: [isLoading && (_jsx("div", { className: "px-3 py-3 text-[12px] text-muted-foreground/60", children: "Loading\u2026" })), !isLoading && available.length === 0 && (_jsx("div", { className: "px-3 py-3 text-[12px] text-muted-foreground/60", children: "No widgets available for this slot yet." })), available.map((tool) => (_jsx("button", { type: "button", onClick: () => install(tool.toolId), className: "flex w-full items-start gap-2 px-3 py-2 text-left hover:bg-accent cursor-pointer", children: _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("p", { className: "text-[12px] font-medium truncate", children: tool.name }), tool.description && (_jsx("p", { className: "text-[11px] text-muted-foreground/70 truncate", children: tool.description }))] }) }, tool.toolId)))] }), _jsx("div", { className: "border-t border-border/40 p-1", children: _jsxs("button", { type: "button", onClick: requestNew, className: "flex w-full items-center gap-2 rounded-md px-3 py-2 text-[12px] text-muted-foreground hover:bg-accent hover:text-foreground cursor-pointer", children: [_jsx(IconPlus, { className: "h-3.5 w-3.5" }), _jsx("span", { children: "Build a new widget" })] }) })] })] }));
|
|
95
|
-
}
|
|
96
|
-
//# sourceMappingURL=ExtensionSlot.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ExtensionSlot.js","sourceRoot":"","sources":["../../../src/client/tools/ExtensionSlot.tsx"],"names":[],"mappings":"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","sourcesContent":["import { agentNativePath } from \"../api-path.js\";\nimport { useState } from \"react\";\nimport { useQuery, useQueryClient } from \"@tanstack/react-query\";\nimport { IconPlus } from \"@tabler/icons-react\";\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"../components/ui/popover.js\";\nimport { sendToAgentChat } from \"../agent-chat.js\";\nimport { EmbeddedTool } from \"./EmbeddedTool.js\";\n\ninterface SlotInstall {\n installId: string;\n toolId: string;\n name: string;\n description: string;\n icon: string | null;\n updatedAt: string;\n position: number;\n config: string | null;\n}\n\ninterface AvailableTool {\n toolId: string;\n name: string;\n description: string;\n icon: string | null;\n config: string | null;\n}\n\nexport interface ExtensionSlotProps {\n /** Stable slot identifier — convention: `<app>.<area>.<position>`. */\n id: string;\n /** Object pushed to each embedded tool as `slotContext`. */\n context?: Record<string, unknown> | null;\n /** Show a small \"+\" affordance when the slot has no installs. Default: false. */\n showEmptyAffordance?: boolean;\n /** Optional className applied to the wrapper. */\n className?: string;\n /** Optional className applied to each EmbeddedTool. */\n toolClassName?: string;\n}\n\n/**\n * A named UI slot that user-installed tools can render into. Apps drop this\n * component wherever they want to allow extensions; the framework handles\n * fetching, sandboxing, context delivery, and lifecycle.\n *\n * Example:\n *\n * <ExtensionSlot\n * id=\"mail.contact-sidebar.bottom\"\n * context={{ contactEmail }}\n * showEmptyAffordance\n * />\n */\nexport function ExtensionSlot({\n id,\n context,\n showEmptyAffordance,\n className,\n toolClassName,\n}: ExtensionSlotProps) {\n const { data: installs = [], isLoading } = useQuery<SlotInstall[]>({\n queryKey: [\"slot-installs\", id],\n queryFn: async () => {\n const res = await fetch(\n agentNativePath(\n `/_agent-native/slots/${encodeURIComponent(id)}/installs`,\n ),\n );\n if (!res.ok) return [];\n return res.json();\n },\n });\n\n if (isLoading) {\n return null;\n }\n\n if (installs.length === 0) {\n if (!showEmptyAffordance) return null;\n return (\n <div className={className}>\n <SlotEmptyAffordance slotId={id} />\n </div>\n );\n }\n\n return (\n <div className={className}>\n {installs.map((install) => (\n <EmbeddedTool\n key={install.installId}\n toolId={install.toolId}\n slotId={id}\n context={context}\n className={toolClassName}\n />\n ))}\n </div>\n );\n}\n\nfunction SlotEmptyAffordance({ slotId }: { slotId: string }) {\n const [open, setOpen] = useState(false);\n const { data: available = [], isLoading } = useQuery<AvailableTool[]>({\n queryKey: [\"slot-available\", slotId],\n queryFn: async () => {\n const res = await fetch(\n agentNativePath(\n `/_agent-native/slots/${encodeURIComponent(slotId)}/available`,\n ),\n );\n if (!res.ok) return [];\n return res.json();\n },\n enabled: open,\n });\n const queryClient = useQueryClient();\n\n const install = async (toolId: string) => {\n queryClient.setQueryData<SlotInstall[]>(\n [\"slot-installs\", slotId],\n (old) => {\n const tool = available.find((t) => t.toolId === toolId);\n if (!tool || !old) return old;\n return [\n ...old,\n {\n installId: `optimistic-${toolId}`,\n toolId,\n name: tool.name,\n description: tool.description,\n icon: tool.icon,\n updatedAt: new Date().toISOString(),\n position: old.length,\n config: tool.config,\n },\n ];\n },\n );\n setOpen(false);\n try {\n await fetch(\n agentNativePath(\n `/_agent-native/slots/${encodeURIComponent(slotId)}/install`,\n ),\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ toolId }),\n },\n );\n } finally {\n queryClient.invalidateQueries({ queryKey: [\"slot-installs\", slotId] });\n }\n };\n\n const requestNew = () => {\n setOpen(false);\n sendToAgentChat({\n message: `Create a new widget that fits in slot \"${slotId}\". I'll describe what it should do next.`,\n submit: false,\n openSidebar: true,\n });\n };\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n className=\"flex w-full items-center gap-2 px-4 py-2 text-[11px] text-muted-foreground/60 hover:text-muted-foreground cursor-pointer\"\n title=\"Add a widget\"\n >\n <div className=\"h-5 w-5 rounded-md border border-dashed border-border/40 flex items-center justify-center shrink-0\">\n <IconPlus className=\"h-3 w-3\" />\n </div>\n <span>Add widget</span>\n </button>\n </PopoverTrigger>\n <PopoverContent\n side=\"left\"\n align=\"end\"\n sideOffset={8}\n className=\"w-72 p-0 overflow-hidden\"\n >\n <div className=\"px-3 py-2 border-b border-border/40\">\n <p className=\"text-[12px] font-medium\">Add widget here</p>\n <p className=\"text-[11px] text-muted-foreground/70\">{slotId}</p>\n </div>\n <div className=\"max-h-72 overflow-y-auto py-1\">\n {isLoading && (\n <div className=\"px-3 py-3 text-[12px] text-muted-foreground/60\">\n Loading…\n </div>\n )}\n {!isLoading && available.length === 0 && (\n <div className=\"px-3 py-3 text-[12px] text-muted-foreground/60\">\n No widgets available for this slot yet.\n </div>\n )}\n {available.map((tool) => (\n <button\n key={tool.toolId}\n type=\"button\"\n onClick={() => install(tool.toolId)}\n className=\"flex w-full items-start gap-2 px-3 py-2 text-left hover:bg-accent cursor-pointer\"\n >\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-[12px] font-medium truncate\">{tool.name}</p>\n {tool.description && (\n <p className=\"text-[11px] text-muted-foreground/70 truncate\">\n {tool.description}\n </p>\n )}\n </div>\n </button>\n ))}\n </div>\n <div className=\"border-t border-border/40 p-1\">\n <button\n type=\"button\"\n onClick={requestNew}\n className=\"flex w-full items-center gap-2 rounded-md px-3 py-2 text-[12px] text-muted-foreground hover:bg-accent hover:text-foreground cursor-pointer\"\n >\n <IconPlus className=\"h-3.5 w-3.5\" />\n <span>Build a new widget</span>\n </button>\n </div>\n </PopoverContent>\n </Popover>\n );\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ToolEditor.d.ts","sourceRoot":"","sources":["../../../src/client/tools/ToolEditor.tsx"],"names":[],"mappings":"AA+BA,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,UAAU,CAAC,EAAE,MAAM,EAAE,EAAE,eAAe,2CAmUrD"}
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { agentNativePath } from "../api-path.js";
|
|
3
|
-
import { useState, useEffect } from "react";
|
|
4
|
-
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
5
|
-
import { Link, useNavigate } from "react-router";
|
|
6
|
-
import { IconArrowLeft, IconDeviceFloppy, IconDots, IconTrash, IconX, } from "@tabler/icons-react";
|
|
7
|
-
import { Popover, PopoverContent, PopoverTrigger, } from "../components/ui/popover.js";
|
|
8
|
-
import { cn } from "../utils.js";
|
|
9
|
-
export function ToolEditor({ toolId }) {
|
|
10
|
-
const navigate = useNavigate();
|
|
11
|
-
const queryClient = useQueryClient();
|
|
12
|
-
const isEdit = !!toolId;
|
|
13
|
-
const [name, setName] = useState("");
|
|
14
|
-
const [description, setDescription] = useState("");
|
|
15
|
-
const [content, setContent] = useState("");
|
|
16
|
-
const [saving, setSaving] = useState(false);
|
|
17
|
-
const [deleting, setDeleting] = useState(false);
|
|
18
|
-
const [menuOpen, setMenuOpen] = useState(false);
|
|
19
|
-
const [confirmingDelete, setConfirmingDelete] = useState(false);
|
|
20
|
-
const { data: slots = [] } = useQuery({
|
|
21
|
-
queryKey: ["tool-slots", toolId],
|
|
22
|
-
queryFn: async () => {
|
|
23
|
-
const res = await fetch(agentNativePath(`/_agent-native/slots/tool/${toolId}`));
|
|
24
|
-
if (!res.ok)
|
|
25
|
-
return [];
|
|
26
|
-
return res.json();
|
|
27
|
-
},
|
|
28
|
-
enabled: isEdit && menuOpen,
|
|
29
|
-
});
|
|
30
|
-
const { data: existingTool } = useQuery({
|
|
31
|
-
queryKey: ["tool", toolId],
|
|
32
|
-
queryFn: async () => {
|
|
33
|
-
const res = await fetch(agentNativePath(`/_agent-native/tools/${toolId}`));
|
|
34
|
-
if (!res.ok)
|
|
35
|
-
throw new Error("Failed to fetch tool");
|
|
36
|
-
return res.json();
|
|
37
|
-
},
|
|
38
|
-
enabled: isEdit,
|
|
39
|
-
});
|
|
40
|
-
useEffect(() => {
|
|
41
|
-
if (existingTool) {
|
|
42
|
-
setName(existingTool.name ?? "");
|
|
43
|
-
setDescription(existingTool.description ?? "");
|
|
44
|
-
setContent(existingTool.content ?? "");
|
|
45
|
-
}
|
|
46
|
-
}, [existingTool]);
|
|
47
|
-
const handleSave = async () => {
|
|
48
|
-
if (!name.trim())
|
|
49
|
-
return;
|
|
50
|
-
setSaving(true);
|
|
51
|
-
try {
|
|
52
|
-
const body = JSON.stringify({
|
|
53
|
-
name: name.trim(),
|
|
54
|
-
description: description.trim() || undefined,
|
|
55
|
-
content,
|
|
56
|
-
});
|
|
57
|
-
if (isEdit) {
|
|
58
|
-
const res = await fetch(agentNativePath(`/_agent-native/tools/${toolId}`), {
|
|
59
|
-
method: "PUT",
|
|
60
|
-
headers: { "Content-Type": "application/json" },
|
|
61
|
-
body,
|
|
62
|
-
});
|
|
63
|
-
if (!res.ok)
|
|
64
|
-
throw new Error("Update failed");
|
|
65
|
-
queryClient.invalidateQueries({ queryKey: ["tool", toolId] });
|
|
66
|
-
queryClient.invalidateQueries({ queryKey: ["tools"] });
|
|
67
|
-
navigate(`/tools/${toolId}`);
|
|
68
|
-
}
|
|
69
|
-
else {
|
|
70
|
-
const res = await fetch(agentNativePath("/_agent-native/tools"), {
|
|
71
|
-
method: "POST",
|
|
72
|
-
headers: { "Content-Type": "application/json" },
|
|
73
|
-
body,
|
|
74
|
-
});
|
|
75
|
-
if (!res.ok)
|
|
76
|
-
throw new Error("Create failed");
|
|
77
|
-
const created = await res.json();
|
|
78
|
-
queryClient.invalidateQueries({ queryKey: ["tools"] });
|
|
79
|
-
navigate(`/tools/${created.id}`);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
finally {
|
|
83
|
-
setSaving(false);
|
|
84
|
-
}
|
|
85
|
-
};
|
|
86
|
-
const handleDelete = async () => {
|
|
87
|
-
if (!toolId)
|
|
88
|
-
return;
|
|
89
|
-
setDeleting(true);
|
|
90
|
-
try {
|
|
91
|
-
const prev = queryClient.getQueryData(["tools"]);
|
|
92
|
-
queryClient.setQueryData(["tools"], (old) => (old ?? []).filter((t) => t.id !== toolId));
|
|
93
|
-
const res = await fetch(agentNativePath(`/_agent-native/tools/${toolId}`), {
|
|
94
|
-
method: "DELETE",
|
|
95
|
-
});
|
|
96
|
-
if (!res.ok) {
|
|
97
|
-
if (prev)
|
|
98
|
-
queryClient.setQueryData(["tools"], prev);
|
|
99
|
-
throw new Error("Delete failed");
|
|
100
|
-
}
|
|
101
|
-
queryClient.invalidateQueries({ queryKey: ["tools"] });
|
|
102
|
-
slots.forEach((s) => queryClient.invalidateQueries({
|
|
103
|
-
queryKey: ["slot-installs", s.slotId],
|
|
104
|
-
}));
|
|
105
|
-
navigate("/tools");
|
|
106
|
-
}
|
|
107
|
-
finally {
|
|
108
|
-
setDeleting(false);
|
|
109
|
-
setConfirmingDelete(false);
|
|
110
|
-
setMenuOpen(false);
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
const handleRemoveFromSlot = async (slotId) => {
|
|
114
|
-
if (!toolId)
|
|
115
|
-
return;
|
|
116
|
-
try {
|
|
117
|
-
await fetch(agentNativePath(`/_agent-native/slots/${encodeURIComponent(slotId)}/install/${encodeURIComponent(toolId)}`), { method: "DELETE" });
|
|
118
|
-
}
|
|
119
|
-
finally {
|
|
120
|
-
queryClient.invalidateQueries({ queryKey: ["slot-installs", slotId] });
|
|
121
|
-
}
|
|
122
|
-
};
|
|
123
|
-
return (_jsxs("div", { className: "flex h-full flex-col", children: [_jsxs("header", { className: "flex items-center justify-between border-b px-4 py-3", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Link, { to: isEdit ? `/tools/${toolId}` : "/tools", className: "inline-flex cursor-pointer items-center justify-center rounded-md p-1 text-muted-foreground hover:bg-accent hover:text-accent-foreground", "aria-label": "Back", children: _jsx(IconArrowLeft, { className: "h-4 w-4" }) }), _jsx("h1", { className: "text-sm font-semibold", children: isEdit ? "Edit Tool" : "New Tool" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("button", { type: "button", onClick: handleSave, disabled: saving || !name.trim(), className: cn("inline-flex cursor-pointer items-center justify-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-sm text-primary-foreground hover:bg-primary/90", (saving || !name.trim()) && "opacity-60"), children: [_jsx(IconDeviceFloppy, { className: "h-3.5 w-3.5" }), saving ? "Saving..." : isEdit ? "Save" : "Create"] }), isEdit && (_jsxs(Popover, { open: menuOpen, onOpenChange: (o) => {
|
|
124
|
-
setMenuOpen(o);
|
|
125
|
-
if (!o)
|
|
126
|
-
setConfirmingDelete(false);
|
|
127
|
-
}, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx("button", { type: "button", className: "inline-flex h-8 w-8 cursor-pointer items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-accent-foreground", title: "More options", "aria-label": "More options", children: _jsx(IconDots, { className: "h-4 w-4" }) }) }), _jsx(PopoverContent, { align: "end", sideOffset: 4, className: "w-72 p-0", children: !confirmingDelete ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "px-3 py-2 border-b border-border/40", children: [_jsx("p", { className: "text-[12px] font-medium", children: "Appears in" }), slots.length === 0 ? (_jsx("p", { className: "text-[11px] text-muted-foreground/70 mt-0.5", children: "Not installed in any widget areas. Ask the agent to add it somewhere." })) : (_jsxs("p", { className: "text-[11px] text-muted-foreground/70 mt-0.5", children: ["This tool can render in ", slots.length, " widget area", slots.length === 1 ? "" : "s", "."] }))] }), slots.length > 0 && (_jsx("div", { className: "max-h-48 overflow-y-auto py-1", children: slots.map((s) => (_jsxs("div", { className: "flex items-center gap-2 px-3 py-1.5 text-[12px]", children: [_jsx("span", { className: "flex-1 truncate font-mono text-[11px] text-muted-foreground", children: s.slotId }), _jsx("button", { type: "button", onClick: () => handleRemoveFromSlot(s.slotId), className: "rounded p-1 text-muted-foreground/60 hover:bg-accent hover:text-foreground cursor-pointer", title: "Remove from this widget area (for me)", "aria-label": "Remove from this widget area", children: _jsx(IconX, { className: "h-3.5 w-3.5" }) })] }, s.id))) })), _jsx("div", { className: "border-t border-border/40 p-1", children: _jsxs("button", { type: "button", onClick: () => setConfirmingDelete(true), className: "flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-[12px] text-destructive hover:bg-destructive/10 cursor-pointer text-left", children: [_jsx(IconTrash, { className: "h-3.5 w-3.5" }), _jsx("span", { children: "Delete tool\u2026" })] }) })] })) : (_jsxs("div", { className: "flex flex-col gap-2 p-3", children: [_jsxs("p", { className: "text-[12px]", children: ["Delete ", _jsx("span", { className: "font-medium", children: name }), "? This removes the tool everywhere, for everyone it's shared with."] }), _jsxs("div", { className: "flex justify-end gap-1", children: [_jsx("button", { type: "button", onClick: () => setConfirmingDelete(false), className: "rounded-md px-2 py-1 text-[12px] hover:bg-accent cursor-pointer", children: "Cancel" }), _jsx("button", { type: "button", onClick: handleDelete, disabled: deleting, className: cn("rounded-md bg-destructive px-2 py-1 text-[12px] text-destructive-foreground hover:bg-destructive/90 cursor-pointer", deleting && "opacity-60"), children: deleting ? "Deleting…" : "Delete" })] })] })) })] }))] })] }), _jsxs("div", { className: "flex flex-1 overflow-hidden", children: [_jsxs("div", { className: "flex w-1/2 flex-col gap-4 overflow-auto border-r p-4", children: [_jsxs("div", { children: [_jsx("label", { className: "mb-1.5 block text-sm font-medium text-foreground", children: "Name" }), _jsx("input", { type: "text", value: name, onChange: (e) => setName(e.target.value), placeholder: "My Tool", className: "h-9 w-full rounded-md border border-input bg-background px-3 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:ring-offset-background" })] }), _jsxs("div", { children: [_jsx("label", { className: "mb-1.5 block text-sm font-medium text-foreground", children: "Description" }), _jsx("textarea", { value: description, onChange: (e) => setDescription(e.target.value), placeholder: "What does this tool do?", rows: 2, className: "w-full resize-none rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:ring-offset-background" })] }), _jsxs("div", { className: "flex flex-1 flex-col", children: [_jsx("label", { className: "mb-1.5 block text-sm font-medium text-foreground", children: "Content" }), _jsx("textarea", { value: content, onChange: (e) => setContent(e.target.value), placeholder: "<html>...</html>", className: "flex-1 resize-none rounded-md border border-input bg-background p-3 font-mono text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:ring-offset-background", spellCheck: false })] })] }), _jsx("div", { className: "w-1/2", children: content ? (_jsx("iframe", { srcDoc: content, className: "h-full w-full border-0", sandbox: "allow-scripts allow-forms", title: "Tool preview" })) : (_jsx("div", { className: "flex h-full items-center justify-center text-sm text-muted-foreground", children: "Preview will appear here" })) })] })] }));
|
|
128
|
-
}
|
|
129
|
-
//# sourceMappingURL=ToolEditor.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ToolEditor.js","sourceRoot":"","sources":["../../../src/client/tools/ToolEditor.tsx"],"names":[],"mappings":"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","sourcesContent":["import { agentNativePath } from \"../api-path.js\";\nimport { useState, useEffect } from \"react\";\nimport { useQuery, useQueryClient } from \"@tanstack/react-query\";\nimport { Link, useNavigate } from \"react-router\";\nimport {\n IconArrowLeft,\n IconDeviceFloppy,\n IconDots,\n IconTrash,\n IconX,\n} from \"@tabler/icons-react\";\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"../components/ui/popover.js\";\nimport { cn } from \"../utils.js\";\n\ninterface SlotDeclaration {\n id: string;\n toolId: string;\n slotId: string;\n}\n\ninterface Tool {\n id: string;\n name: string;\n description?: string;\n content?: string;\n}\n\nexport interface ToolEditorProps {\n toolId?: string;\n}\n\nexport function ToolEditor({ toolId }: ToolEditorProps) {\n const navigate = useNavigate();\n const queryClient = useQueryClient();\n const isEdit = !!toolId;\n\n const [name, setName] = useState(\"\");\n const [description, setDescription] = useState(\"\");\n const [content, setContent] = useState(\"\");\n const [saving, setSaving] = useState(false);\n const [deleting, setDeleting] = useState(false);\n const [menuOpen, setMenuOpen] = useState(false);\n const [confirmingDelete, setConfirmingDelete] = useState(false);\n\n const { data: slots = [] } = useQuery<SlotDeclaration[]>({\n queryKey: [\"tool-slots\", toolId],\n queryFn: async () => {\n const res = await fetch(\n agentNativePath(`/_agent-native/slots/tool/${toolId}`),\n );\n if (!res.ok) return [];\n return res.json();\n },\n enabled: isEdit && menuOpen,\n });\n\n const { data: existingTool } = useQuery<Tool>({\n queryKey: [\"tool\", toolId],\n queryFn: async () => {\n const res = await fetch(\n agentNativePath(`/_agent-native/tools/${toolId}`),\n );\n if (!res.ok) throw new Error(\"Failed to fetch tool\");\n return res.json();\n },\n enabled: isEdit,\n });\n\n useEffect(() => {\n if (existingTool) {\n setName(existingTool.name ?? \"\");\n setDescription(existingTool.description ?? \"\");\n setContent(existingTool.content ?? \"\");\n }\n }, [existingTool]);\n\n const handleSave = async () => {\n if (!name.trim()) return;\n setSaving(true);\n try {\n const body = JSON.stringify({\n name: name.trim(),\n description: description.trim() || undefined,\n content,\n });\n\n if (isEdit) {\n const res = await fetch(\n agentNativePath(`/_agent-native/tools/${toolId}`),\n {\n method: \"PUT\",\n headers: { \"Content-Type\": \"application/json\" },\n body,\n },\n );\n if (!res.ok) throw new Error(\"Update failed\");\n queryClient.invalidateQueries({ queryKey: [\"tool\", toolId] });\n queryClient.invalidateQueries({ queryKey: [\"tools\"] });\n navigate(`/tools/${toolId}`);\n } else {\n const res = await fetch(agentNativePath(\"/_agent-native/tools\"), {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body,\n });\n if (!res.ok) throw new Error(\"Create failed\");\n const created = await res.json();\n queryClient.invalidateQueries({ queryKey: [\"tools\"] });\n navigate(`/tools/${created.id}`);\n }\n } finally {\n setSaving(false);\n }\n };\n\n const handleDelete = async () => {\n if (!toolId) return;\n setDeleting(true);\n try {\n const prev = queryClient.getQueryData<Tool[]>([\"tools\"]);\n queryClient.setQueryData<Tool[]>([\"tools\"], (old) =>\n (old ?? []).filter((t) => t.id !== toolId),\n );\n\n const res = await fetch(\n agentNativePath(`/_agent-native/tools/${toolId}`),\n {\n method: \"DELETE\",\n },\n );\n if (!res.ok) {\n if (prev) queryClient.setQueryData([\"tools\"], prev);\n throw new Error(\"Delete failed\");\n }\n\n queryClient.invalidateQueries({ queryKey: [\"tools\"] });\n slots.forEach((s) =>\n queryClient.invalidateQueries({\n queryKey: [\"slot-installs\", s.slotId],\n }),\n );\n navigate(\"/tools\");\n } finally {\n setDeleting(false);\n setConfirmingDelete(false);\n setMenuOpen(false);\n }\n };\n\n const handleRemoveFromSlot = async (slotId: string) => {\n if (!toolId) return;\n try {\n await fetch(\n agentNativePath(\n `/_agent-native/slots/${encodeURIComponent(slotId)}/install/${encodeURIComponent(toolId)}`,\n ),\n { method: \"DELETE\" },\n );\n } finally {\n queryClient.invalidateQueries({ queryKey: [\"slot-installs\", slotId] });\n }\n };\n\n return (\n <div className=\"flex h-full flex-col\">\n <header className=\"flex items-center justify-between border-b px-4 py-3\">\n <div className=\"flex items-center gap-3\">\n <Link\n to={isEdit ? `/tools/${toolId}` : \"/tools\"}\n className=\"inline-flex cursor-pointer items-center justify-center rounded-md p-1 text-muted-foreground hover:bg-accent hover:text-accent-foreground\"\n aria-label=\"Back\"\n >\n <IconArrowLeft className=\"h-4 w-4\" />\n </Link>\n <h1 className=\"text-sm font-semibold\">\n {isEdit ? \"Edit Tool\" : \"New Tool\"}\n </h1>\n </div>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={handleSave}\n disabled={saving || !name.trim()}\n className={cn(\n \"inline-flex cursor-pointer items-center justify-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-sm text-primary-foreground hover:bg-primary/90\",\n (saving || !name.trim()) && \"opacity-60\",\n )}\n >\n <IconDeviceFloppy className=\"h-3.5 w-3.5\" />\n {saving ? \"Saving...\" : isEdit ? \"Save\" : \"Create\"}\n </button>\n {isEdit && (\n <Popover\n open={menuOpen}\n onOpenChange={(o) => {\n setMenuOpen(o);\n if (!o) setConfirmingDelete(false);\n }}\n >\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n className=\"inline-flex h-8 w-8 cursor-pointer items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-accent-foreground\"\n title=\"More options\"\n aria-label=\"More options\"\n >\n <IconDots className=\"h-4 w-4\" />\n </button>\n </PopoverTrigger>\n <PopoverContent align=\"end\" sideOffset={4} className=\"w-72 p-0\">\n {!confirmingDelete ? (\n <>\n <div className=\"px-3 py-2 border-b border-border/40\">\n <p className=\"text-[12px] font-medium\">Appears in</p>\n {slots.length === 0 ? (\n <p className=\"text-[11px] text-muted-foreground/70 mt-0.5\">\n Not installed in any widget areas. Ask the agent to\n add it somewhere.\n </p>\n ) : (\n <p className=\"text-[11px] text-muted-foreground/70 mt-0.5\">\n This tool can render in {slots.length} widget area\n {slots.length === 1 ? \"\" : \"s\"}.\n </p>\n )}\n </div>\n {slots.length > 0 && (\n <div className=\"max-h-48 overflow-y-auto py-1\">\n {slots.map((s) => (\n <div\n key={s.id}\n className=\"flex items-center gap-2 px-3 py-1.5 text-[12px]\"\n >\n <span className=\"flex-1 truncate font-mono text-[11px] text-muted-foreground\">\n {s.slotId}\n </span>\n <button\n type=\"button\"\n onClick={() => handleRemoveFromSlot(s.slotId)}\n className=\"rounded p-1 text-muted-foreground/60 hover:bg-accent hover:text-foreground cursor-pointer\"\n title=\"Remove from this widget area (for me)\"\n aria-label=\"Remove from this widget area\"\n >\n <IconX className=\"h-3.5 w-3.5\" />\n </button>\n </div>\n ))}\n </div>\n )}\n <div className=\"border-t border-border/40 p-1\">\n <button\n type=\"button\"\n onClick={() => setConfirmingDelete(true)}\n className=\"flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-[12px] text-destructive hover:bg-destructive/10 cursor-pointer text-left\"\n >\n <IconTrash className=\"h-3.5 w-3.5\" />\n <span>Delete tool…</span>\n </button>\n </div>\n </>\n ) : (\n <div className=\"flex flex-col gap-2 p-3\">\n <p className=\"text-[12px]\">\n Delete <span className=\"font-medium\">{name}</span>? This\n removes the tool everywhere, for everyone it's shared\n with.\n </p>\n <div className=\"flex justify-end gap-1\">\n <button\n type=\"button\"\n onClick={() => setConfirmingDelete(false)}\n className=\"rounded-md px-2 py-1 text-[12px] hover:bg-accent cursor-pointer\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={handleDelete}\n disabled={deleting}\n className={cn(\n \"rounded-md bg-destructive px-2 py-1 text-[12px] text-destructive-foreground hover:bg-destructive/90 cursor-pointer\",\n deleting && \"opacity-60\",\n )}\n >\n {deleting ? \"Deleting…\" : \"Delete\"}\n </button>\n </div>\n </div>\n )}\n </PopoverContent>\n </Popover>\n )}\n </div>\n </header>\n\n <div className=\"flex flex-1 overflow-hidden\">\n <div className=\"flex w-1/2 flex-col gap-4 overflow-auto border-r p-4\">\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-foreground\">\n Name\n </label>\n <input\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder=\"My Tool\"\n className=\"h-9 w-full rounded-md border border-input bg-background px-3 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:ring-offset-background\"\n />\n </div>\n\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-foreground\">\n Description\n </label>\n <textarea\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n placeholder=\"What does this tool do?\"\n rows={2}\n className=\"w-full resize-none rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:ring-offset-background\"\n />\n </div>\n\n <div className=\"flex flex-1 flex-col\">\n <label className=\"mb-1.5 block text-sm font-medium text-foreground\">\n Content\n </label>\n <textarea\n value={content}\n onChange={(e) => setContent(e.target.value)}\n placeholder=\"<html>...</html>\"\n className=\"flex-1 resize-none rounded-md border border-input bg-background p-3 font-mono text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:ring-offset-background\"\n spellCheck={false}\n />\n </div>\n </div>\n\n <div className=\"w-1/2\">\n {content ? (\n <iframe\n srcDoc={content}\n className=\"h-full w-full border-0\"\n sandbox=\"allow-scripts allow-forms\"\n title=\"Tool preview\"\n />\n ) : (\n <div className=\"flex h-full items-center justify-center text-sm text-muted-foreground\">\n Preview will appear here\n </div>\n )}\n </div>\n </div>\n </div>\n );\n}\n"]}
|