@agent-native/core 0.126.0 → 0.127.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +71 -0
- package/corpus/core/docs/content/external-agents-catalog.mdx +14 -11
- package/corpus/core/docs/content/mcp-protocol.mdx +9 -4
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/index.ts +2 -0
- package/corpus/core/src/agent/production-agent.ts +6 -1
- package/corpus/core/src/agent/run-manager.ts +37 -0
- package/corpus/core/src/agent/run-store.ts +18 -0
- package/corpus/core/src/client/MultiTabAssistantChat.tsx +75 -43
- package/corpus/core/src/client/agent-chat.ts +19 -1
- package/corpus/core/src/client/chat/tool-call-display.tsx +25 -0
- package/corpus/core/src/client/resources/mcp-integration-catalog.ts +23 -0
- package/corpus/core/src/client/settings/AgentsSection.tsx +565 -71
- package/corpus/core/src/client/settings/SecretsSection.tsx +71 -36
- package/corpus/core/src/client/use-chat-models.ts +54 -37
- package/corpus/core/src/localization/default-messages.ts +11 -0
- package/corpus/core/src/mcp/ask-app-task-handle.ts +183 -0
- package/corpus/core/src/mcp/builtin-tools.ts +146 -34
- package/corpus/core/src/server/agent-capabilities.ts +8 -1
- package/corpus/core/src/server/agent-chat-plugin.ts +17 -0
- package/corpus/core/src/server/agent-discovery.ts +1 -1
- package/corpus/core/src/server/agent-peer-probe.ts +133 -0
- package/corpus/core/src/server/core-routes-plugin.ts +67 -0
- package/corpus/core/src/shared/mcp-embed-headers.ts +4 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +159 -119
- package/corpus/templates/design/app/hooks/use-question-flow.ts +21 -1
- package/corpus/templates/design/changelog/2026-07-28-the-model-you-pick-when-starting-a-new-design-is-now-used-fo.md +6 -0
- package/corpus/templates/dispatch/changelog/2026-07-28-pending-apps-are-hidden-by-default-and-app-ownership-details.md +6 -0
- package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +5 -1
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +5 -0
- package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +31 -8
- package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +15 -0
- package/corpus/templates/slides/app/components/presentation/PresentationView.tsx +78 -8
- package/corpus/templates/slides/app/components/presentation/PresenterView.tsx +179 -0
- package/corpus/templates/slides/app/components/presentation/present-channel.ts +15 -0
- package/corpus/templates/slides/app/i18n/en-US.ts +10 -1
- package/corpus/templates/slides/app/lib/export-google-slides-client.ts +67 -0
- package/corpus/templates/slides/app/lib/export-pptx-client.ts +16 -3
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +11 -0
- package/corpus/templates/slides/app/pages/Presentation.tsx +11 -3
- package/corpus/templates/slides/changelog/2026-07-28-export-to-google-slides-now-creates-the-deck-directly-in-you.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-28-opening-a-presentation-link-directly-no-longer-bounces-back-.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-28-presenter-view-open-a-second-window-while-presenting-to-see-.md +6 -0
- package/corpus/templates/slides/server/routes/api/exports/google-slides.post.ts +90 -0
- package/corpus/toolkit/CHANGELOG.md +6 -0
- package/corpus/toolkit/package.json +1 -1
- package/corpus/toolkit/src/composer/TiptapComposer.tsx +143 -111
- package/corpus/toolkit/src/composer/runtime-adapters.tsx +2 -0
- package/dist/a2a/index.d.ts +2 -0
- package/dist/a2a/index.d.ts.map +1 -1
- package/dist/a2a/index.js +1 -0
- package/dist/a2a/index.js.map +1 -1
- package/dist/agent/production-agent.d.ts +2 -2
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +6 -3
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-manager.d.ts +11 -0
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +34 -1
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/run-store.d.ts +8 -0
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +16 -0
- package/dist/agent/run-store.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +60 -31
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/agent-chat.d.ts +6 -0
- package/dist/client/agent-chat.d.ts.map +1 -1
- package/dist/client/agent-chat.js +13 -1
- package/dist/client/agent-chat.js.map +1 -1
- package/dist/client/chat/tool-call-display.d.ts.map +1 -1
- package/dist/client/chat/tool-call-display.js +14 -0
- package/dist/client/chat/tool-call-display.js.map +1 -1
- package/dist/client/resources/mcp-integration-catalog.d.ts +6 -0
- package/dist/client/resources/mcp-integration-catalog.d.ts.map +1 -1
- package/dist/client/resources/mcp-integration-catalog.js +16 -0
- package/dist/client/resources/mcp-integration-catalog.js.map +1 -1
- package/dist/client/settings/AgentsSection.d.ts.map +1 -1
- package/dist/client/settings/AgentsSection.js +250 -30
- package/dist/client/settings/AgentsSection.js.map +1 -1
- package/dist/client/settings/SecretsSection.d.ts.map +1 -1
- package/dist/client/settings/SecretsSection.js +16 -2
- package/dist/client/settings/SecretsSection.js.map +1 -1
- package/dist/client/use-chat-models.d.ts.map +1 -1
- package/dist/client/use-chat-models.js +46 -27
- package/dist/client/use-chat-models.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/localization/default-messages.d.ts +9 -0
- package/dist/localization/default-messages.d.ts.map +1 -1
- package/dist/localization/default-messages.js +9 -0
- package/dist/localization/default-messages.js.map +1 -1
- package/dist/mcp/ask-app-task-handle.d.ts +25 -0
- package/dist/mcp/ask-app-task-handle.d.ts.map +1 -0
- package/dist/mcp/ask-app-task-handle.js +136 -0
- package/dist/mcp/ask-app-task-handle.js.map +1 -0
- package/dist/mcp/builtin-tools.d.ts +3 -3
- package/dist/mcp/builtin-tools.d.ts.map +1 -1
- package/dist/mcp/builtin-tools.js +115 -38
- package/dist/mcp/builtin-tools.js.map +1 -1
- package/dist/observability/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +4 -4
- package/dist/provider-api/actions/provider-api.d.ts +8 -8
- package/dist/server/agent-capabilities.d.ts +7 -1
- package/dist/server/agent-capabilities.d.ts.map +1 -1
- package/dist/server/agent-capabilities.js +1 -0
- package/dist/server/agent-capabilities.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts +7 -0
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +16 -1
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-discovery.d.ts +1 -0
- package/dist/server/agent-discovery.d.ts.map +1 -1
- package/dist/server/agent-discovery.js +1 -1
- package/dist/server/agent-discovery.js.map +1 -1
- package/dist/server/agent-peer-probe.d.ts +37 -0
- package/dist/server/agent-peer-probe.d.ts.map +1 -0
- package/dist/server/agent-peer-probe.js +90 -0
- package/dist/server/agent-peer-probe.js.map +1 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +53 -0
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/shared/mcp-embed-headers.d.ts +1 -0
- package/dist/shared/mcp-embed-headers.d.ts.map +1 -1
- package/dist/shared/mcp-embed-headers.js +4 -0
- package/dist/shared/mcp-embed-headers.js.map +1 -1
- package/dist/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +159 -119
- package/docs/content/external-agents-catalog.mdx +14 -11
- package/docs/content/mcp-protocol.mdx +9 -4
- package/package.json +2 -2
- package/src/a2a/index.ts +2 -0
- package/src/agent/production-agent.ts +6 -1
- package/src/agent/run-manager.ts +37 -0
- package/src/agent/run-store.ts +18 -0
- package/src/client/MultiTabAssistantChat.tsx +75 -43
- package/src/client/agent-chat.ts +19 -1
- package/src/client/chat/tool-call-display.tsx +25 -0
- package/src/client/resources/mcp-integration-catalog.ts +23 -0
- package/src/client/settings/AgentsSection.tsx +565 -71
- package/src/client/settings/SecretsSection.tsx +71 -36
- package/src/client/use-chat-models.ts +54 -37
- package/src/localization/default-messages.ts +11 -0
- package/src/mcp/ask-app-task-handle.ts +183 -0
- package/src/mcp/builtin-tools.ts +146 -34
- package/src/server/agent-capabilities.ts +8 -1
- package/src/server/agent-chat-plugin.ts +17 -0
- package/src/server/agent-discovery.ts +1 -1
- package/src/server/agent-peer-probe.ts +133 -0
- package/src/server/core-routes-plugin.ts +67 -0
- package/src/shared/mcp-embed-headers.ts +4 -0
- package/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +159 -119
|
@@ -1,17 +1,35 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ButtonBase as ToolkitButtonBase } from "@agent-native/toolkit/ui/button";
|
|
2
|
+
import {
|
|
3
|
+
IconPlus,
|
|
4
|
+
IconPencil,
|
|
5
|
+
IconTrash,
|
|
6
|
+
IconX,
|
|
7
|
+
IconCheck,
|
|
8
|
+
IconLoader2,
|
|
9
|
+
IconAlertTriangle,
|
|
10
|
+
IconExternalLink,
|
|
11
|
+
IconRefresh,
|
|
12
|
+
} from "@tabler/icons-react";
|
|
2
13
|
import { useState, useEffect, useRef, useCallback } from "react";
|
|
3
14
|
|
|
15
|
+
import {
|
|
16
|
+
buildOpenRoutePath,
|
|
17
|
+
buildSettingsRoute,
|
|
18
|
+
STANDARD_SETTINGS_TABS,
|
|
19
|
+
} from "../../navigation/index.js";
|
|
4
20
|
import {
|
|
5
21
|
getRemoteAgentIdFromPath,
|
|
6
22
|
isRemoteAgentPath,
|
|
23
|
+
REMOTE_AGENT_RESOURCE_PREFIX,
|
|
7
24
|
remoteAgentResourcePath,
|
|
8
25
|
} from "../../resources/metadata.js";
|
|
9
|
-
import { agentNativePath } from "../api-path.js";
|
|
26
|
+
import { agentNativePath, appBasePath } from "../api-path.js";
|
|
10
27
|
import {
|
|
11
28
|
Tooltip,
|
|
12
29
|
TooltipContent,
|
|
13
30
|
TooltipTrigger,
|
|
14
31
|
} from "../components/ui/tooltip.js";
|
|
32
|
+
import { useOrg, useSyncA2ASecret } from "../org/hooks.js";
|
|
15
33
|
|
|
16
34
|
interface AgentInfo {
|
|
17
35
|
id: string;
|
|
@@ -21,6 +39,61 @@ interface AgentInfo {
|
|
|
21
39
|
description?: string;
|
|
22
40
|
}
|
|
23
41
|
|
|
42
|
+
/** Wire shape of `GET /_agent-native/agents/probe` (single or batched result). */
|
|
43
|
+
interface AgentProbeResult {
|
|
44
|
+
url: string;
|
|
45
|
+
reachable: boolean;
|
|
46
|
+
name?: string;
|
|
47
|
+
description?: string;
|
|
48
|
+
securitySchemes?: string[];
|
|
49
|
+
/** Absent (not false) whenever the auth check never ran or never resolved. */
|
|
50
|
+
authorized?: boolean;
|
|
51
|
+
authError?: string;
|
|
52
|
+
publicSkills?: number;
|
|
53
|
+
error?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function describeSkills(publicSkills: number | undefined): string | null {
|
|
57
|
+
if (publicSkills === undefined) return null;
|
|
58
|
+
// An empty public skill list only means the card advertises no anonymous-
|
|
59
|
+
// safe actions — the peer still has authenticated reads/writes. Saying so
|
|
60
|
+
// plainly avoids reading as "this agent can't do anything."
|
|
61
|
+
if (publicSkills === 0) return "reads require auth";
|
|
62
|
+
return `${publicSkills} public skill${publicSkills === 1 ? "" : "s"}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** One-line status for the row dot tooltip. */
|
|
66
|
+
function describeProbeTooltip(result: AgentProbeResult): string {
|
|
67
|
+
if (!result.reachable) {
|
|
68
|
+
return `Unreachable${result.error ? `: ${result.error}` : ""}`;
|
|
69
|
+
}
|
|
70
|
+
if (result.authorized === false) {
|
|
71
|
+
return "Reachable, but the peer rejected our token — calls will 401 in production";
|
|
72
|
+
}
|
|
73
|
+
if (result.authorized === undefined) {
|
|
74
|
+
return "Reachable; couldn't verify our token";
|
|
75
|
+
}
|
|
76
|
+
return "Reachable and authorized";
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Multi-clause status line for the Add popover's Check result. */
|
|
80
|
+
function describeCheckResult(result: AgentProbeResult): string {
|
|
81
|
+
if (!result.reachable) {
|
|
82
|
+
return result.error ?? "Not reachable";
|
|
83
|
+
}
|
|
84
|
+
const scheme = result.securitySchemes?.length
|
|
85
|
+
? result.securitySchemes.join(", ")
|
|
86
|
+
: "no auth scheme advertised";
|
|
87
|
+
const authText =
|
|
88
|
+
result.authorized === false
|
|
89
|
+
? "the peer rejected our token — calls will 401 in production"
|
|
90
|
+
: result.authorized === undefined
|
|
91
|
+
? `couldn't verify our token${result.authError ? ` (${result.authError})` : ""}`
|
|
92
|
+
: "our token works";
|
|
93
|
+
const skills = describeSkills(result.publicSkills);
|
|
94
|
+
return [`Live · ${scheme}`, authText, skills].filter(Boolean).join(" · ");
|
|
95
|
+
}
|
|
96
|
+
|
|
24
97
|
function AgentEditPopover({
|
|
25
98
|
agent,
|
|
26
99
|
onSave,
|
|
@@ -125,21 +198,68 @@ function AgentEditPopover({
|
|
|
125
198
|
);
|
|
126
199
|
}
|
|
127
200
|
|
|
201
|
+
type CheckState =
|
|
202
|
+
| { status: "idle" }
|
|
203
|
+
| { status: "checking" }
|
|
204
|
+
| { status: "done"; result: AgentProbeResult }
|
|
205
|
+
| { status: "error"; message: string };
|
|
206
|
+
|
|
207
|
+
interface AddedAgentInfo {
|
|
208
|
+
name: string;
|
|
209
|
+
url: string;
|
|
210
|
+
description: string;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** Builds an absolute deep link into a peer's own Settings > Agents Add
|
|
214
|
+
* popover, prefilled with THIS app's own name/url/description, via the
|
|
215
|
+
* existing `/_agent-native/open` route's `f_*` filter-forwarding (the only
|
|
216
|
+
* non-reserved params the open route echoes onto the redirect URL instead of
|
|
217
|
+
* only stashing them server-side for `navigate` polling). No new endpoint. */
|
|
218
|
+
function buildPeerRegisterBackLink(peerUrl: string): string {
|
|
219
|
+
const selfUrl = `${window.location.origin}${appBasePath()}`;
|
|
220
|
+
const selfName = document.title.trim() || window.location.hostname;
|
|
221
|
+
const openPath = buildOpenRoutePath({
|
|
222
|
+
view: "settings",
|
|
223
|
+
to: buildSettingsRoute(STANDARD_SETTINGS_TABS.agent),
|
|
224
|
+
params: {
|
|
225
|
+
f_agentName: selfName,
|
|
226
|
+
f_agentUrl: selfUrl,
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
return `${peerUrl.replace(/\/+$/, "")}${openPath}`;
|
|
230
|
+
}
|
|
231
|
+
|
|
128
232
|
function AgentAddPopover({
|
|
233
|
+
initialName = "",
|
|
234
|
+
initialUrl = "",
|
|
235
|
+
initialDescription = "",
|
|
236
|
+
secretSet,
|
|
237
|
+
syncSecret,
|
|
129
238
|
onAdd,
|
|
130
239
|
onClose,
|
|
131
240
|
}: {
|
|
132
|
-
|
|
241
|
+
initialName?: string;
|
|
242
|
+
initialUrl?: string;
|
|
243
|
+
initialDescription?: string;
|
|
244
|
+
secretSet: boolean | undefined;
|
|
245
|
+
syncSecret: ReturnType<typeof useSyncA2ASecret>;
|
|
246
|
+
onAdd: (name: string, url: string, description: string) => Promise<boolean>;
|
|
133
247
|
onClose: () => void;
|
|
134
248
|
}) {
|
|
135
|
-
const [name, setName] = useState(
|
|
136
|
-
const [url, setUrl] = useState(
|
|
137
|
-
const [description, setDescription] = useState(
|
|
249
|
+
const [name, setName] = useState(initialName);
|
|
250
|
+
const [url, setUrl] = useState(initialUrl);
|
|
251
|
+
const [description, setDescription] = useState(initialDescription);
|
|
252
|
+
const [check, setCheck] = useState<CheckState>({ status: "idle" });
|
|
253
|
+
const [added, setAdded] = useState<AddedAgentInfo | null>(null);
|
|
138
254
|
const nameRef = useRef<HTMLInputElement>(null);
|
|
255
|
+
const urlRef = useRef<HTMLInputElement>(null);
|
|
139
256
|
const popoverRef = useRef<HTMLDivElement>(null);
|
|
140
257
|
|
|
141
258
|
useEffect(() => {
|
|
142
|
-
const t = setTimeout(
|
|
259
|
+
const t = setTimeout(
|
|
260
|
+
() => (urlRef.current?.value ? nameRef : urlRef).current?.focus(),
|
|
261
|
+
50,
|
|
262
|
+
);
|
|
143
263
|
return () => clearTimeout(t);
|
|
144
264
|
}, []);
|
|
145
265
|
|
|
@@ -156,17 +276,182 @@ function AgentAddPopover({
|
|
|
156
276
|
return () => document.removeEventListener("mousedown", handleClick);
|
|
157
277
|
}, [onClose]);
|
|
158
278
|
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
279
|
+
const handleCheck = useCallback(async () => {
|
|
280
|
+
const trimmedUrl = url.trim();
|
|
281
|
+
if (!trimmedUrl) return;
|
|
282
|
+
setCheck({ status: "checking" });
|
|
283
|
+
try {
|
|
284
|
+
const res = await fetch(
|
|
285
|
+
agentNativePath(
|
|
286
|
+
`/_agent-native/agents/probe?url=${encodeURIComponent(trimmedUrl)}`,
|
|
287
|
+
),
|
|
288
|
+
);
|
|
289
|
+
const body = await res.json().catch(() => null);
|
|
290
|
+
if (!res.ok) {
|
|
291
|
+
setCheck({
|
|
292
|
+
status: "error",
|
|
293
|
+
message: body?.error ?? `Check failed (${res.status})`,
|
|
294
|
+
});
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
const result = body as AgentProbeResult;
|
|
298
|
+
setCheck({ status: "done", result });
|
|
299
|
+
if (result.reachable) {
|
|
300
|
+
if (!name.trim() && result.name) setName(result.name);
|
|
301
|
+
if (!description.trim() && result.description) {
|
|
302
|
+
setDescription(result.description);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
} catch (err: any) {
|
|
306
|
+
setCheck({ status: "error", message: err?.message ?? "Check failed" });
|
|
307
|
+
}
|
|
308
|
+
}, [url, name, description]);
|
|
309
|
+
|
|
310
|
+
const handleAdd = async () => {
|
|
311
|
+
const trimmedName = name.trim();
|
|
312
|
+
const trimmedUrl = url.trim();
|
|
313
|
+
if (!trimmedName || !trimmedUrl) return;
|
|
314
|
+
const trimmedDescription = description.trim();
|
|
315
|
+
const ok = await onAdd(trimmedName, trimmedUrl, trimmedDescription);
|
|
316
|
+
if (ok) {
|
|
317
|
+
setAdded({
|
|
318
|
+
name: trimmedName,
|
|
319
|
+
url: trimmedUrl,
|
|
320
|
+
description: trimmedDescription,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
162
323
|
};
|
|
163
324
|
|
|
325
|
+
if (added) {
|
|
326
|
+
return (
|
|
327
|
+
<div
|
|
328
|
+
ref={popoverRef}
|
|
329
|
+
className="absolute end-0 top-full z-50 mt-1 w-72 rounded-lg border border-border bg-popover p-2.5 shadow-lg"
|
|
330
|
+
>
|
|
331
|
+
<div className="flex flex-col gap-1.5">
|
|
332
|
+
<p className="text-[10px] leading-relaxed text-muted-foreground">
|
|
333
|
+
Added {added.name} on your side only — registration is one-way, so
|
|
334
|
+
it won't know about this app until it's added there too.
|
|
335
|
+
</p>
|
|
336
|
+
<a
|
|
337
|
+
href={buildPeerRegisterBackLink(added.url)}
|
|
338
|
+
target="_blank"
|
|
339
|
+
rel="noopener noreferrer"
|
|
340
|
+
className="inline-flex cursor-pointer items-center justify-center gap-1 rounded bg-accent px-2 py-1 text-[10px] font-medium text-foreground no-underline hover:bg-accent/80"
|
|
341
|
+
>
|
|
342
|
+
<IconExternalLink size={10} />
|
|
343
|
+
Open {added.name}'s settings
|
|
344
|
+
</a>
|
|
345
|
+
<button
|
|
346
|
+
onClick={onClose}
|
|
347
|
+
className="cursor-pointer rounded px-2 py-0.5 text-[10px] text-muted-foreground hover:text-foreground"
|
|
348
|
+
>
|
|
349
|
+
Dismiss
|
|
350
|
+
</button>
|
|
351
|
+
</div>
|
|
352
|
+
</div>
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const result = check.status === "done" ? check.result : null;
|
|
357
|
+
const unreachable = result ? !result.reachable : false;
|
|
358
|
+
const unauthorized = result ? result.authorized === false : false;
|
|
359
|
+
|
|
164
360
|
return (
|
|
165
361
|
<div
|
|
166
362
|
ref={popoverRef}
|
|
167
|
-
className="absolute end-0 top-full z-50 mt-1 w-
|
|
363
|
+
className="absolute end-0 top-full z-50 mt-1 w-72 rounded-lg border border-border bg-popover p-2.5 shadow-lg"
|
|
168
364
|
>
|
|
169
365
|
<div className="flex flex-col gap-1.5">
|
|
366
|
+
<div className="flex gap-1">
|
|
367
|
+
<input
|
|
368
|
+
ref={urlRef}
|
|
369
|
+
value={url}
|
|
370
|
+
onChange={(e) => {
|
|
371
|
+
setUrl(e.target.value);
|
|
372
|
+
setCheck({ status: "idle" });
|
|
373
|
+
}}
|
|
374
|
+
onKeyDown={(e) => {
|
|
375
|
+
if (e.key === "Enter") handleCheck();
|
|
376
|
+
if (e.key === "Escape") onClose();
|
|
377
|
+
}}
|
|
378
|
+
className="w-full flex-1 rounded border border-border bg-background px-2 py-1 text-[11px] text-foreground outline-none placeholder:text-muted-foreground/50 focus:ring-1 focus:ring-accent"
|
|
379
|
+
placeholder="URL (e.g. http://localhost:8085)"
|
|
380
|
+
/>
|
|
381
|
+
<ToolkitButtonBase
|
|
382
|
+
type="button"
|
|
383
|
+
variant="outline"
|
|
384
|
+
onClick={handleCheck}
|
|
385
|
+
disabled={!url.trim() || check.status === "checking"}
|
|
386
|
+
className="inline-flex shrink-0 cursor-pointer items-center gap-1 rounded border border-border px-2 py-1 text-[10px] text-muted-foreground hover:bg-accent/40 hover:text-foreground disabled:opacity-40"
|
|
387
|
+
>
|
|
388
|
+
{check.status === "checking" ? (
|
|
389
|
+
<IconLoader2 size={10} className="animate-spin" />
|
|
390
|
+
) : (
|
|
391
|
+
"Check"
|
|
392
|
+
)}
|
|
393
|
+
</ToolkitButtonBase>
|
|
394
|
+
</div>
|
|
395
|
+
|
|
396
|
+
{check.status === "error" && (
|
|
397
|
+
<p className="flex items-start gap-1 text-[10px] text-red-500">
|
|
398
|
+
<IconAlertTriangle size={11} className="mt-px shrink-0" />
|
|
399
|
+
{check.message}
|
|
400
|
+
</p>
|
|
401
|
+
)}
|
|
402
|
+
{result && (
|
|
403
|
+
<div
|
|
404
|
+
className={`flex items-start gap-1 text-[10px] ${
|
|
405
|
+
unreachable || unauthorized
|
|
406
|
+
? "text-amber-600 dark:text-amber-400"
|
|
407
|
+
: "text-green-600 dark:text-green-500"
|
|
408
|
+
}`}
|
|
409
|
+
>
|
|
410
|
+
{unreachable || unauthorized ? (
|
|
411
|
+
<IconAlertTriangle size={11} className="mt-px shrink-0" />
|
|
412
|
+
) : (
|
|
413
|
+
<IconCheck size={11} className="mt-px shrink-0" />
|
|
414
|
+
)}
|
|
415
|
+
<span className="leading-relaxed">
|
|
416
|
+
{describeCheckResult(result)}
|
|
417
|
+
{unreachable &&
|
|
418
|
+
" — the app may just not be running yet; you can still add it."}
|
|
419
|
+
</span>
|
|
420
|
+
</div>
|
|
421
|
+
)}
|
|
422
|
+
{unauthorized &&
|
|
423
|
+
(secretSet === true ? (
|
|
424
|
+
<button
|
|
425
|
+
onClick={() => syncSecret.mutate(undefined)}
|
|
426
|
+
disabled={syncSecret.isPending}
|
|
427
|
+
className="inline-flex cursor-pointer items-center gap-1 self-start rounded border border-border px-1.5 py-0.5 text-[10px] text-muted-foreground hover:text-foreground disabled:opacity-40"
|
|
428
|
+
>
|
|
429
|
+
{syncSecret.isPending ? (
|
|
430
|
+
<IconLoader2 size={10} className="animate-spin" />
|
|
431
|
+
) : (
|
|
432
|
+
<IconRefresh size={10} />
|
|
433
|
+
)}
|
|
434
|
+
Sync secret to apps
|
|
435
|
+
</button>
|
|
436
|
+
) : (
|
|
437
|
+
<p className="text-[10px] text-muted-foreground">
|
|
438
|
+
{secretSet === false ? (
|
|
439
|
+
<>
|
|
440
|
+
No shared secret set yet —{" "}
|
|
441
|
+
<a
|
|
442
|
+
href={buildSettingsRoute(STANDARD_SETTINGS_TABS.team)}
|
|
443
|
+
className="underline underline-offset-2 hover:text-foreground"
|
|
444
|
+
>
|
|
445
|
+
set one on the Team page
|
|
446
|
+
</a>{" "}
|
|
447
|
+
first.
|
|
448
|
+
</>
|
|
449
|
+
) : (
|
|
450
|
+
"Ask your workspace owner to sync the shared secret."
|
|
451
|
+
)}
|
|
452
|
+
</p>
|
|
453
|
+
))}
|
|
454
|
+
|
|
170
455
|
<input
|
|
171
456
|
ref={nameRef}
|
|
172
457
|
value={name}
|
|
@@ -178,16 +463,6 @@ function AgentAddPopover({
|
|
|
178
463
|
className="w-full rounded border border-border bg-background px-2 py-1 text-[11px] text-foreground outline-none placeholder:text-muted-foreground/50 focus:ring-1 focus:ring-accent"
|
|
179
464
|
placeholder="Name"
|
|
180
465
|
/>
|
|
181
|
-
<input
|
|
182
|
-
value={url}
|
|
183
|
-
onChange={(e) => setUrl(e.target.value)}
|
|
184
|
-
onKeyDown={(e) => {
|
|
185
|
-
if (e.key === "Enter") handleAdd();
|
|
186
|
-
if (e.key === "Escape") onClose();
|
|
187
|
-
}}
|
|
188
|
-
className="w-full rounded border border-border bg-background px-2 py-1 text-[11px] text-foreground outline-none placeholder:text-muted-foreground/50 focus:ring-1 focus:ring-accent"
|
|
189
|
-
placeholder="URL (e.g. http://localhost:8085)"
|
|
190
|
-
/>
|
|
191
466
|
<input
|
|
192
467
|
value={description}
|
|
193
468
|
onChange={(e) => setDescription(e.target.value)}
|
|
@@ -201,16 +476,16 @@ function AgentAddPopover({
|
|
|
201
476
|
<div className="flex justify-end gap-1 pt-0.5">
|
|
202
477
|
<button
|
|
203
478
|
onClick={onClose}
|
|
204
|
-
className="rounded px-2 py-0.5 text-[10px] text-muted-foreground hover:text-foreground"
|
|
479
|
+
className="cursor-pointer rounded px-2 py-0.5 text-[10px] text-muted-foreground hover:text-foreground"
|
|
205
480
|
>
|
|
206
481
|
Cancel
|
|
207
482
|
</button>
|
|
208
483
|
<button
|
|
209
484
|
onClick={handleAdd}
|
|
210
485
|
disabled={!name.trim() || !url.trim()}
|
|
211
|
-
className="rounded bg-accent px-2 py-0.5 text-[10px] font-medium text-foreground hover:bg-accent/80 disabled:opacity-40"
|
|
486
|
+
className="cursor-pointer rounded bg-accent px-2 py-0.5 text-[10px] font-medium text-foreground hover:bg-accent/80 disabled:opacity-40"
|
|
212
487
|
>
|
|
213
|
-
Add
|
|
488
|
+
{unreachable ? "Add anyway" : "Add"}
|
|
214
489
|
</button>
|
|
215
490
|
</div>
|
|
216
491
|
</div>
|
|
@@ -218,11 +493,174 @@ function AgentAddPopover({
|
|
|
218
493
|
);
|
|
219
494
|
}
|
|
220
495
|
|
|
496
|
+
function A2ASecretStatusRow({
|
|
497
|
+
org,
|
|
498
|
+
syncSecret,
|
|
499
|
+
}: {
|
|
500
|
+
org: { a2aSecretSet?: boolean; allowedDomain: string | null } | undefined;
|
|
501
|
+
syncSecret: ReturnType<typeof useSyncA2ASecret>;
|
|
502
|
+
}) {
|
|
503
|
+
if (!org) return null;
|
|
504
|
+
const secretSet = org.a2aSecretSet;
|
|
505
|
+
|
|
506
|
+
if (secretSet === undefined) {
|
|
507
|
+
// Not an owner/admin — the server omits `a2aSecretSet` entirely for this
|
|
508
|
+
// role rather than reporting `false`, so this must read as "can't see
|
|
509
|
+
// it," never as "not set" (a claim we have no basis for).
|
|
510
|
+
return (
|
|
511
|
+
<div className="mb-2 rounded-md border border-border/60 bg-accent/20 px-2 py-1.5 text-[10px] text-muted-foreground">
|
|
512
|
+
Shared secret is managed by your workspace owner.
|
|
513
|
+
</div>
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
if (!secretSet) {
|
|
518
|
+
return (
|
|
519
|
+
<div className="mb-2 rounded-md border border-amber-500/30 bg-amber-500/10 px-2 py-1.5 text-[10px] text-amber-600 dark:text-amber-400">
|
|
520
|
+
No shared secret set — connected apps will reject calls in production.{" "}
|
|
521
|
+
<a
|
|
522
|
+
href={buildSettingsRoute(STANDARD_SETTINGS_TABS.team)}
|
|
523
|
+
className="underline underline-offset-2 hover:text-amber-500"
|
|
524
|
+
>
|
|
525
|
+
Set one on the Team page
|
|
526
|
+
</a>
|
|
527
|
+
</div>
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const noDomain = syncSecret.error?.message?.toLowerCase().includes("domain");
|
|
532
|
+
const failures = syncSecret.data?.results.filter((r) => !r.ok) ?? [];
|
|
533
|
+
|
|
534
|
+
return (
|
|
535
|
+
<div className="mb-2 flex flex-col gap-1 rounded-md border border-border/60 bg-accent/20 px-2 py-1.5 text-[10px]">
|
|
536
|
+
<div className="flex items-center justify-between gap-2">
|
|
537
|
+
<span className="text-muted-foreground">
|
|
538
|
+
Shared secret set
|
|
539
|
+
{org.allowedDomain ? ` for ${org.allowedDomain}` : ""}
|
|
540
|
+
</span>
|
|
541
|
+
<button
|
|
542
|
+
onClick={() => syncSecret.mutate(undefined)}
|
|
543
|
+
disabled={syncSecret.isPending}
|
|
544
|
+
className="inline-flex shrink-0 cursor-pointer items-center gap-1 rounded border border-border px-1.5 py-0.5 text-[10px] text-muted-foreground hover:text-foreground disabled:opacity-40"
|
|
545
|
+
>
|
|
546
|
+
{syncSecret.isPending ? (
|
|
547
|
+
<IconLoader2 size={10} className="animate-spin" />
|
|
548
|
+
) : (
|
|
549
|
+
<IconRefresh size={10} />
|
|
550
|
+
)}
|
|
551
|
+
Sync to apps
|
|
552
|
+
</button>
|
|
553
|
+
</div>
|
|
554
|
+
{syncSecret.data && !syncSecret.isPending && (
|
|
555
|
+
<div className="text-muted-foreground">
|
|
556
|
+
Synced to {syncSecret.data.succeeded}/{syncSecret.data.total} app
|
|
557
|
+
{syncSecret.data.total === 1 ? "" : "s"}
|
|
558
|
+
{syncSecret.data.failed > 0
|
|
559
|
+
? ` (${syncSecret.data.failed} failed)`
|
|
560
|
+
: ""}
|
|
561
|
+
.
|
|
562
|
+
{failures.length > 0 && (
|
|
563
|
+
<ul className="mt-0.5 list-disc ps-3 text-red-500">
|
|
564
|
+
{failures.map((r) => (
|
|
565
|
+
<li key={r.id}>
|
|
566
|
+
{r.name}: {r.error ?? `HTTP ${r.status ?? "?"}`}
|
|
567
|
+
</li>
|
|
568
|
+
))}
|
|
569
|
+
</ul>
|
|
570
|
+
)}
|
|
571
|
+
</div>
|
|
572
|
+
)}
|
|
573
|
+
{syncSecret.error && (
|
|
574
|
+
<p className="text-red-500">
|
|
575
|
+
{syncSecret.error.message}
|
|
576
|
+
{noDomain && (
|
|
577
|
+
<>
|
|
578
|
+
{" "}
|
|
579
|
+
<a
|
|
580
|
+
href={buildSettingsRoute(STANDARD_SETTINGS_TABS.team)}
|
|
581
|
+
className="underline underline-offset-2"
|
|
582
|
+
>
|
|
583
|
+
Set the domain
|
|
584
|
+
</a>
|
|
585
|
+
</>
|
|
586
|
+
)}
|
|
587
|
+
</p>
|
|
588
|
+
)}
|
|
589
|
+
</div>
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/** Query params `/_agent-native/open` forwards onto the redirect target. */
|
|
594
|
+
const PREFILL_PARAMS = [
|
|
595
|
+
"f_agentName",
|
|
596
|
+
"f_agentUrl",
|
|
597
|
+
"f_agentDescription",
|
|
598
|
+
] as const;
|
|
599
|
+
|
|
221
600
|
export function AgentsSection() {
|
|
222
601
|
const [agents, setAgents] = useState<AgentInfo[]>([]);
|
|
223
602
|
const [loading, setLoading] = useState(true);
|
|
224
603
|
const [editingAgent, setEditingAgent] = useState<string | null>(null);
|
|
225
604
|
const [showAdd, setShowAdd] = useState(false);
|
|
605
|
+
const [prefill, setPrefill] = useState<{
|
|
606
|
+
name: string;
|
|
607
|
+
url: string;
|
|
608
|
+
description: string;
|
|
609
|
+
} | null>(null);
|
|
610
|
+
const [probeById, setProbeById] = useState<Map<
|
|
611
|
+
string,
|
|
612
|
+
AgentProbeResult
|
|
613
|
+
> | null>(null);
|
|
614
|
+
|
|
615
|
+
const { data: org } = useOrg();
|
|
616
|
+
const syncSecret = useSyncA2ASecret();
|
|
617
|
+
|
|
618
|
+
// Landing from a peer's "register back" deep link (see
|
|
619
|
+
// buildPeerRegisterBackLink): the open route only echoes `f_*` params onto
|
|
620
|
+
// the redirect URL, so read those here and open the Add popover prefilled.
|
|
621
|
+
useEffect(() => {
|
|
622
|
+
if (typeof window === "undefined") return;
|
|
623
|
+
const params = new URLSearchParams(window.location.search);
|
|
624
|
+
const url = params.get("f_agentUrl");
|
|
625
|
+
if (!url) return;
|
|
626
|
+
setPrefill({
|
|
627
|
+
name: params.get("f_agentName") ?? "",
|
|
628
|
+
url,
|
|
629
|
+
description: params.get("f_agentDescription") ?? "",
|
|
630
|
+
});
|
|
631
|
+
setShowAdd(true);
|
|
632
|
+
for (const key of PREFILL_PARAMS) params.delete(key);
|
|
633
|
+
const query = params.toString();
|
|
634
|
+
window.history.replaceState(
|
|
635
|
+
null,
|
|
636
|
+
"",
|
|
637
|
+
`${window.location.pathname}${query ? `?${query}` : ""}${window.location.hash}`,
|
|
638
|
+
);
|
|
639
|
+
}, []);
|
|
640
|
+
|
|
641
|
+
// One batched probe for the whole list — cheap liveness dots, not one
|
|
642
|
+
// request per row. A row absent from the results (never returned) stays
|
|
643
|
+
// dot-less rather than defaulting to a color that isn't backed by data.
|
|
644
|
+
useEffect(() => {
|
|
645
|
+
let cancelled = false;
|
|
646
|
+
fetch(agentNativePath("/_agent-native/agents/probe"))
|
|
647
|
+
.then((res) => (res.ok ? res.json() : null))
|
|
648
|
+
.then((data) => {
|
|
649
|
+
if (cancelled || !data) return;
|
|
650
|
+
const results = Array.isArray(data.results)
|
|
651
|
+
? (data.results as Array<AgentProbeResult & { id: string }>)
|
|
652
|
+
: [];
|
|
653
|
+
const map = new Map<string, AgentProbeResult>();
|
|
654
|
+
for (const result of results) map.set(result.id.toLowerCase(), result);
|
|
655
|
+
setProbeById(map);
|
|
656
|
+
})
|
|
657
|
+
.catch(() => {
|
|
658
|
+
if (!cancelled) setProbeById(new Map());
|
|
659
|
+
});
|
|
660
|
+
return () => {
|
|
661
|
+
cancelled = true;
|
|
662
|
+
};
|
|
663
|
+
}, []);
|
|
226
664
|
|
|
227
665
|
const fetchAgents = useCallback(async () => {
|
|
228
666
|
try {
|
|
@@ -231,11 +669,24 @@ export function AgentsSection() {
|
|
|
231
669
|
);
|
|
232
670
|
if (!res.ok) return;
|
|
233
671
|
const data = await res.json();
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
672
|
+
// Migrating a remote agent to the canonical `remote-agents/` prefix
|
|
673
|
+
// leaves the legacy `agents/` row in place (resources/store.ts), so
|
|
674
|
+
// every migrated agent has two resources pointing at one URL. Collapse
|
|
675
|
+
// them the way discoverAgents does, canonical winning.
|
|
676
|
+
const byAgentId = new Map<string, { id: string; path: string }>();
|
|
677
|
+
for (const resource of (data.resources ?? []) as Array<{
|
|
678
|
+
id: string;
|
|
679
|
+
path: string;
|
|
680
|
+
}>) {
|
|
681
|
+
if (!isRemoteAgentPath(resource.path)) continue;
|
|
682
|
+
const agentId = getRemoteAgentIdFromPath(resource.path);
|
|
683
|
+
const existing = byAgentId.get(agentId);
|
|
684
|
+
if (existing?.path.startsWith(REMOTE_AGENT_RESOURCE_PREFIX)) continue;
|
|
685
|
+
byAgentId.set(agentId, resource);
|
|
686
|
+
}
|
|
687
|
+
const agentResources = [...byAgentId.values()];
|
|
237
688
|
const parsed = await Promise.all(
|
|
238
|
-
agentResources.map(async (r:
|
|
689
|
+
agentResources.map(async (r): Promise<AgentInfo | null> => {
|
|
239
690
|
try {
|
|
240
691
|
const detail = await fetch(
|
|
241
692
|
agentNativePath(`/_agent-native/resources/${r.id}`),
|
|
@@ -255,7 +706,7 @@ export function AgentsSection() {
|
|
|
255
706
|
}
|
|
256
707
|
}),
|
|
257
708
|
);
|
|
258
|
-
setAgents(parsed.filter(
|
|
709
|
+
setAgents(parsed.filter((agent): agent is AgentInfo => agent !== null));
|
|
259
710
|
} finally {
|
|
260
711
|
setLoading(false);
|
|
261
712
|
}
|
|
@@ -265,7 +716,11 @@ export function AgentsSection() {
|
|
|
265
716
|
fetchAgents();
|
|
266
717
|
}, [fetchAgents]);
|
|
267
718
|
|
|
268
|
-
const handleAdd = async (
|
|
719
|
+
const handleAdd = async (
|
|
720
|
+
name: string,
|
|
721
|
+
url: string,
|
|
722
|
+
description: string,
|
|
723
|
+
): Promise<boolean> => {
|
|
269
724
|
const id = name.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
270
725
|
const agentJson = JSON.stringify(
|
|
271
726
|
{
|
|
@@ -289,11 +744,14 @@ export function AgentsSection() {
|
|
|
289
744
|
shared: true,
|
|
290
745
|
}),
|
|
291
746
|
});
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
747
|
+
// Deliberately don't close the popover here — a successful add shows a
|
|
748
|
+
// follow-up state (registration is one-way; the peer doesn't know
|
|
749
|
+
// about us yet) that the user dismisses explicitly.
|
|
750
|
+
if (res.ok) fetchAgents();
|
|
751
|
+
return res.ok;
|
|
752
|
+
} catch {
|
|
753
|
+
return false;
|
|
754
|
+
}
|
|
297
755
|
};
|
|
298
756
|
|
|
299
757
|
const handleSave = async (agent: AgentInfo) => {
|
|
@@ -365,13 +823,23 @@ export function AgentsSection() {
|
|
|
365
823
|
</Tooltip>
|
|
366
824
|
{showAdd && (
|
|
367
825
|
<AgentAddPopover
|
|
826
|
+
initialName={prefill?.name}
|
|
827
|
+
initialUrl={prefill?.url}
|
|
828
|
+
initialDescription={prefill?.description}
|
|
829
|
+
secretSet={org?.a2aSecretSet}
|
|
830
|
+
syncSecret={syncSecret}
|
|
368
831
|
onAdd={handleAdd}
|
|
369
|
-
onClose={() =>
|
|
832
|
+
onClose={() => {
|
|
833
|
+
setShowAdd(false);
|
|
834
|
+
setPrefill(null);
|
|
835
|
+
}}
|
|
370
836
|
/>
|
|
371
837
|
)}
|
|
372
838
|
</div>
|
|
373
839
|
</div>
|
|
374
840
|
|
|
841
|
+
<A2ASecretStatusRow org={org} syncSecret={syncSecret} />
|
|
842
|
+
|
|
375
843
|
{/* Agent list */}
|
|
376
844
|
{loading ? (
|
|
377
845
|
<div className="space-y-1.5">
|
|
@@ -388,42 +856,68 @@ export function AgentsSection() {
|
|
|
388
856
|
</button>
|
|
389
857
|
) : (
|
|
390
858
|
<div className="flex flex-col gap-0.5">
|
|
391
|
-
{agents.map((agent) =>
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
859
|
+
{agents.map((agent) => {
|
|
860
|
+
const probe = probeById?.get(
|
|
861
|
+
getRemoteAgentIdFromPath(agent.path).toLowerCase(),
|
|
862
|
+
);
|
|
863
|
+
const dotState: "ok" | "warn" | null = !probe
|
|
864
|
+
? null
|
|
865
|
+
: probe.reachable && probe.authorized !== false
|
|
866
|
+
? "ok"
|
|
867
|
+
: "warn";
|
|
868
|
+
return (
|
|
869
|
+
<div key={agent.id} className="group relative">
|
|
870
|
+
<div className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 hover:bg-accent/30">
|
|
871
|
+
{dotState ? (
|
|
872
|
+
<Tooltip>
|
|
873
|
+
<TooltipTrigger asChild>
|
|
874
|
+
<span
|
|
875
|
+
className={`h-1.5 w-1.5 shrink-0 rounded-full ${
|
|
876
|
+
dotState === "ok" ? "bg-green-500" : "bg-amber-500"
|
|
877
|
+
}`}
|
|
878
|
+
/>
|
|
879
|
+
</TooltipTrigger>
|
|
880
|
+
<TooltipContent>
|
|
881
|
+
{probe && describeProbeTooltip(probe)}
|
|
882
|
+
</TooltipContent>
|
|
883
|
+
</Tooltip>
|
|
884
|
+
) : (
|
|
885
|
+
<span className="h-1.5 w-1.5 shrink-0 rounded-full bg-transparent" />
|
|
886
|
+
)}
|
|
887
|
+
<span className="text-[11px] font-medium text-foreground truncate shrink-0">
|
|
888
|
+
{agent.name}
|
|
889
|
+
</span>
|
|
890
|
+
<span className="flex-1 text-[10px] text-muted-foreground/60 truncate text-end">
|
|
891
|
+
{agent.url}
|
|
892
|
+
</span>
|
|
893
|
+
<Tooltip>
|
|
894
|
+
<TooltipTrigger asChild>
|
|
895
|
+
<button
|
|
896
|
+
onClick={() => {
|
|
897
|
+
setEditingAgent(
|
|
898
|
+
editingAgent === agent.id ? null : agent.id,
|
|
899
|
+
);
|
|
900
|
+
setShowAdd(false);
|
|
901
|
+
}}
|
|
902
|
+
className="shrink-0 rounded p-0.5 text-muted-foreground opacity-0 group-hover:opacity-100 hover:text-foreground hover:bg-accent/50"
|
|
903
|
+
>
|
|
904
|
+
<IconPencil size={11} />
|
|
905
|
+
</button>
|
|
906
|
+
</TooltipTrigger>
|
|
907
|
+
<TooltipContent>Edit agent</TooltipContent>
|
|
908
|
+
</Tooltip>
|
|
909
|
+
</div>
|
|
910
|
+
{editingAgent === agent.id && (
|
|
911
|
+
<AgentEditPopover
|
|
912
|
+
agent={agent}
|
|
913
|
+
onSave={handleSave}
|
|
914
|
+
onDelete={handleDelete}
|
|
915
|
+
onClose={() => setEditingAgent(null)}
|
|
916
|
+
/>
|
|
917
|
+
)}
|
|
416
918
|
</div>
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
agent={agent}
|
|
420
|
-
onSave={handleSave}
|
|
421
|
-
onDelete={handleDelete}
|
|
422
|
-
onClose={() => setEditingAgent(null)}
|
|
423
|
-
/>
|
|
424
|
-
)}
|
|
425
|
-
</div>
|
|
426
|
-
))}
|
|
919
|
+
);
|
|
920
|
+
})}
|
|
427
921
|
<button
|
|
428
922
|
onClick={() => {
|
|
429
923
|
setShowAdd(true);
|