@stigmer/react 3.1.8 → 3.1.9
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/environment/EnvironmentPicker.d.ts +17 -1
- package/environment/EnvironmentPicker.d.ts.map +1 -1
- package/environment/EnvironmentPicker.js +3 -2
- package/environment/EnvironmentPicker.js.map +1 -1
- package/index.d.ts +2 -2
- package/index.d.ts.map +1 -1
- package/index.js +2 -2
- package/index.js.map +1 -1
- package/package.json +4 -4
- package/sharing/ShareAgentDialog.d.ts +10 -8
- package/sharing/ShareAgentDialog.d.ts.map +1 -1
- package/sharing/ShareAgentDialog.js +139 -54
- package/sharing/ShareAgentDialog.js.map +1 -1
- package/sharing/SharedAgentChat.d.ts +1 -1
- package/sharing/SharedAgentChat.d.ts.map +1 -1
- package/sharing/index.d.ts +4 -2
- package/sharing/index.d.ts.map +1 -1
- package/sharing/index.js +2 -1
- package/sharing/index.js.map +1 -1
- package/sharing/useAgentShare.d.ts +43 -0
- package/sharing/useAgentShare.d.ts.map +1 -0
- package/sharing/useAgentShare.js +54 -0
- package/sharing/useAgentShare.js.map +1 -0
- package/sharing/useRotateShareLink.d.ts +22 -19
- package/sharing/useRotateShareLink.d.ts.map +1 -1
- package/sharing/useRotateShareLink.js +22 -19
- package/sharing/useRotateShareLink.js.map +1 -1
- package/sharing/useSaveAgentShare.d.ts +105 -0
- package/sharing/useSaveAgentShare.d.ts.map +1 -0
- package/sharing/useSaveAgentShare.js +97 -0
- package/sharing/useSaveAgentShare.js.map +1 -0
- package/sharing/useShareAgent.d.ts +5 -4
- package/sharing/useShareAgent.d.ts.map +1 -1
- package/sharing/useShareAgent.js +5 -4
- package/sharing/useShareAgent.js.map +1 -1
- package/sharing/useShareToolReadiness.d.ts +29 -25
- package/sharing/useShareToolReadiness.d.ts.map +1 -1
- package/sharing/useShareToolReadiness.js +47 -51
- package/sharing/useShareToolReadiness.js.map +1 -1
- package/sharing/useSharedAgentProfile.d.ts +5 -4
- package/sharing/useSharedAgentProfile.d.ts.map +1 -1
- package/sharing/useSharedAgentProfile.js +6 -5
- package/sharing/useSharedAgentProfile.js.map +1 -1
- package/src/environment/EnvironmentPicker.tsx +23 -2
- package/src/index.ts +8 -4
- package/src/sharing/ShareAgentDialog.tsx +353 -119
- package/src/sharing/SharedAgentChat.tsx +1 -1
- package/src/sharing/__tests__/ShareAgentDialog.test.tsx +550 -369
- package/src/sharing/__tests__/useAgentShare.test.tsx +149 -0
- package/src/sharing/__tests__/useRotateShareLink.test.tsx +8 -8
- package/src/sharing/__tests__/useSaveAgentShare.test.tsx +241 -0
- package/src/sharing/__tests__/useShareAgent.test.tsx +6 -2
- package/src/sharing/__tests__/useShareToolReadiness.test.tsx +84 -47
- package/src/sharing/__tests__/useSharedAgentProfile.test.tsx +1 -1
- package/src/sharing/index.ts +9 -7
- package/src/sharing/useAgentShare.ts +91 -0
- package/src/sharing/useRotateShareLink.ts +30 -25
- package/src/sharing/useSaveAgentShare.ts +186 -0
- package/src/sharing/useShareAgent.tsx +5 -4
- package/src/sharing/useShareToolReadiness.ts +62 -60
- package/src/sharing/useSharedAgentProfile.ts +7 -6
- package/src/switch/Switch.tsx +6 -1
- package/src/switch/__tests__/Switch.test.tsx +19 -0
- package/switch/Switch.d.ts.map +1 -1
- package/switch/Switch.js +6 -1
- package/switch/Switch.js.map +1 -1
- package/sharing/useUpdateAgentSharing.d.ts +0 -86
- package/sharing/useUpdateAgentSharing.d.ts.map +0 -1
- package/sharing/useUpdateAgentSharing.js +0 -81
- package/sharing/useUpdateAgentSharing.js.map +0 -1
- package/src/sharing/__tests__/useUpdateAgentSharing.test.tsx +0 -200
- package/src/sharing/useUpdateAgentSharing.ts +0 -151
|
@@ -9,8 +9,12 @@ import {
|
|
|
9
9
|
chatPath,
|
|
10
10
|
getUserMessage,
|
|
11
11
|
validateOrigin,
|
|
12
|
+
type ResourceRef,
|
|
12
13
|
} from "@stigmer/sdk";
|
|
13
14
|
import type { Agent } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
|
|
15
|
+
import type { AgentShare } from "@stigmer/protos/ai/stigmer/agentic/agentshare/v1/api_pb";
|
|
16
|
+
import type { Environment } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/api_pb";
|
|
17
|
+
import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
|
|
14
18
|
import { Switch } from "../switch/Switch.js";
|
|
15
19
|
import { Tabs, type TabItem } from "../tabs/Tabs.js";
|
|
16
20
|
import { toast } from "../feedback/toast.js";
|
|
@@ -18,12 +22,14 @@ import { useCopyResource } from "../resource-detail/useCopyResource.js";
|
|
|
18
22
|
import { useDeploymentMode } from "../deployment-mode.js";
|
|
19
23
|
import { useBillingAccount } from "../billing/useBillingAccount.js";
|
|
20
24
|
import { formatCreditBalance } from "../billing/format.js";
|
|
25
|
+
import { EnvironmentPicker } from "../environment/EnvironmentPicker.js";
|
|
26
|
+
import { useAgentShare } from "./useAgentShare.js";
|
|
21
27
|
import {
|
|
22
28
|
sharingAudienceFromProto,
|
|
23
|
-
|
|
24
|
-
type
|
|
29
|
+
useSaveAgentShare,
|
|
30
|
+
type AgentShareDraft,
|
|
25
31
|
type SharingAudience,
|
|
26
|
-
} from "./
|
|
32
|
+
} from "./useSaveAgentShare.js";
|
|
27
33
|
import { useRotateShareLink } from "./useRotateShareLink.js";
|
|
28
34
|
import { useShareToolReadiness } from "./useShareToolReadiness.js";
|
|
29
35
|
|
|
@@ -70,20 +76,22 @@ export interface ShareAgentDialogProps {
|
|
|
70
76
|
}
|
|
71
77
|
|
|
72
78
|
/**
|
|
73
|
-
* The Share dialog for an agent: toggle
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* PlatformClient SDK path.
|
|
79
|
+
* The Share dialog for an agent: toggle sharing, copy the hosted chat
|
|
80
|
+
* link, copy an embeddable snippet, manage allowed embed origins, bind
|
|
81
|
+
* tool credentials for visitors, customize visitor refusal messages,
|
|
82
|
+
* and discover the PlatformClient SDK path.
|
|
77
83
|
*
|
|
78
84
|
* Sharing is a distinct consent from marketplace visibility: visibility
|
|
79
85
|
* governs who can *read* the blueprint; sharing governs who can *chat*
|
|
80
86
|
* with the running agent — billed to the owning org. The dialog states
|
|
81
87
|
* who pays next to the toggle so enabling sharing never surprises.
|
|
82
88
|
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
89
|
+
* Sharing lives in its own **AgentShare resource** (decision 011), so
|
|
90
|
+
* the dialog loads the agent's canonical share when it opens and every
|
|
91
|
+
* save is an idempotent `apply` of the complete configuration — the
|
|
92
|
+
* first enable creates the share, later edits update it, one code path
|
|
93
|
+
* (see {@link useSaveAgentShare}). The local draft is refreshed from
|
|
94
|
+
* every returned share, so the dialog never drifts from the server.
|
|
87
95
|
*
|
|
88
96
|
* Built on the native `<dialog>` element for focus trapping and escape
|
|
89
97
|
* handling, matching the SDK's modal convention ({@link ManageAccessDialog}).
|
|
@@ -135,8 +143,8 @@ export function ShareAgentDialog({
|
|
|
135
143
|
)}
|
|
136
144
|
aria-labelledby="share-agent-title"
|
|
137
145
|
>
|
|
138
|
-
{/* Body mounts only while open so the
|
|
139
|
-
the
|
|
146
|
+
{/* Body mounts only while open so the share is re-read per session
|
|
147
|
+
(the draft resets with it) and no fetch fires on a closed dialog. */}
|
|
140
148
|
{open && (
|
|
141
149
|
<ShareAgentDialogBody
|
|
142
150
|
agent={agent}
|
|
@@ -150,65 +158,186 @@ export function ShareAgentDialog({
|
|
|
150
158
|
}
|
|
151
159
|
|
|
152
160
|
// ---------------------------------------------------------------------------
|
|
153
|
-
// Dialog body —
|
|
161
|
+
// Dialog body — resolves the canonical share, then hands off to the form
|
|
154
162
|
// ---------------------------------------------------------------------------
|
|
155
163
|
|
|
156
|
-
|
|
157
|
-
|
|
164
|
+
/**
|
|
165
|
+
* Loads the agent's canonical share and mounts the form once resolved.
|
|
166
|
+
* The split keeps the form's draft seeding synchronous (`useState`
|
|
167
|
+
* initializer from the loaded share) — no hydrate-on-effect, no window
|
|
168
|
+
* where the switch shows a state the server never had.
|
|
169
|
+
*/
|
|
170
|
+
function ShareAgentDialogBody({
|
|
171
|
+
agent,
|
|
172
|
+
buildShareUrl,
|
|
173
|
+
onSharingChanged,
|
|
174
|
+
onClose,
|
|
175
|
+
}: {
|
|
176
|
+
readonly agent: Agent;
|
|
177
|
+
readonly buildShareUrl?: (org: string, slug: string) => string;
|
|
178
|
+
readonly onSharingChanged?: () => void;
|
|
179
|
+
readonly onClose: () => void;
|
|
180
|
+
}) {
|
|
181
|
+
const { share, isLoading, error, refetch } = useAgentShare(agent);
|
|
182
|
+
|
|
183
|
+
return (
|
|
184
|
+
<div className="flex flex-col">
|
|
185
|
+
{/* Header */}
|
|
186
|
+
<div className="flex items-start justify-between border-b border-border px-6 py-4">
|
|
187
|
+
<div className="min-w-0">
|
|
188
|
+
<h2
|
|
189
|
+
id="share-agent-title"
|
|
190
|
+
className="text-base font-semibold text-foreground"
|
|
191
|
+
>
|
|
192
|
+
Share
|
|
193
|
+
</h2>
|
|
194
|
+
<p className="mt-0.5 truncate text-xs text-muted-foreground">
|
|
195
|
+
{agent.metadata?.name || agent.metadata?.slug}
|
|
196
|
+
</p>
|
|
197
|
+
</div>
|
|
198
|
+
<button
|
|
199
|
+
type="button"
|
|
200
|
+
onClick={onClose}
|
|
201
|
+
aria-label="Close"
|
|
202
|
+
className={cn(
|
|
203
|
+
"rounded-md p-1 text-muted-foreground",
|
|
204
|
+
"hover:text-foreground hover:bg-accent-hover",
|
|
205
|
+
"focus:outline-none focus:ring-2 focus:ring-ring",
|
|
206
|
+
)}
|
|
207
|
+
>
|
|
208
|
+
<CloseIcon />
|
|
209
|
+
</button>
|
|
210
|
+
</div>
|
|
211
|
+
|
|
212
|
+
{isLoading ? (
|
|
213
|
+
<div
|
|
214
|
+
className="px-6 py-10 text-center"
|
|
215
|
+
aria-busy="true"
|
|
216
|
+
aria-label="Loading sharing settings"
|
|
217
|
+
>
|
|
218
|
+
<div className="mx-auto h-4 w-2/3 animate-pulse rounded bg-muted" />
|
|
219
|
+
<div className="mx-auto mt-2 h-3 w-1/2 animate-pulse rounded bg-muted" />
|
|
220
|
+
</div>
|
|
221
|
+
) : error ? (
|
|
222
|
+
<div className="px-6 py-8 text-center" role="alert">
|
|
223
|
+
<p className="text-sm text-foreground">
|
|
224
|
+
Couldn't load this agent's sharing settings.
|
|
225
|
+
</p>
|
|
226
|
+
<p className="mt-1 text-xs text-muted-foreground">
|
|
227
|
+
{getUserMessage(error)}
|
|
228
|
+
</p>
|
|
229
|
+
<button
|
|
230
|
+
type="button"
|
|
231
|
+
onClick={refetch}
|
|
232
|
+
className={cn(
|
|
233
|
+
"mt-3 rounded-md px-3 py-1.5 text-xs font-medium",
|
|
234
|
+
"border border-border text-foreground hover:bg-accent-hover",
|
|
235
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
236
|
+
)}
|
|
237
|
+
>
|
|
238
|
+
Try again
|
|
239
|
+
</button>
|
|
240
|
+
</div>
|
|
241
|
+
) : (
|
|
242
|
+
<ShareAgentForm
|
|
243
|
+
agent={agent}
|
|
244
|
+
initialShare={share}
|
|
245
|
+
buildShareUrl={buildShareUrl}
|
|
246
|
+
onSharingChanged={onSharingChanged}
|
|
247
|
+
/>
|
|
248
|
+
)}
|
|
249
|
+
|
|
250
|
+
{/* Footer */}
|
|
251
|
+
<div className="flex items-center justify-end border-t border-border px-6 py-3">
|
|
252
|
+
<button
|
|
253
|
+
type="button"
|
|
254
|
+
onClick={onClose}
|
|
255
|
+
className={cn(
|
|
256
|
+
"rounded-md px-3 py-1.5 text-sm font-medium",
|
|
257
|
+
"bg-primary text-primary-foreground hover:bg-primary-hover",
|
|
258
|
+
"focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
|
259
|
+
)}
|
|
260
|
+
>
|
|
261
|
+
Done
|
|
262
|
+
</button>
|
|
263
|
+
</div>
|
|
264
|
+
</div>
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// ---------------------------------------------------------------------------
|
|
269
|
+
// Form — owns the share draft for the session
|
|
270
|
+
// ---------------------------------------------------------------------------
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* The dialog's editable projection of a share. `null` (never shared)
|
|
274
|
+
* seeds the same defaults the server would apply on first create, so
|
|
275
|
+
* the form needs no "no share yet" special case beyond the disabled
|
|
276
|
+
* copy fields that `enabled: false` already produces.
|
|
277
|
+
*/
|
|
278
|
+
function draftFromShare(share: AgentShare | null): AgentShareDraft {
|
|
279
|
+
const spec = share?.spec;
|
|
158
280
|
return {
|
|
159
|
-
enabled:
|
|
160
|
-
audience: sharingAudienceFromProto(
|
|
161
|
-
allowedOrigins:
|
|
281
|
+
enabled: spec?.enabled ?? false,
|
|
282
|
+
audience: sharingAudienceFromProto(spec?.audience),
|
|
283
|
+
allowedOrigins: spec?.allowedOrigins ?? [],
|
|
162
284
|
messages: {
|
|
163
|
-
rateLimited:
|
|
164
|
-
unavailable:
|
|
165
|
-
conversationEnded:
|
|
285
|
+
rateLimited: spec?.messages?.rateLimited ?? "",
|
|
286
|
+
unavailable: spec?.messages?.unavailable ?? "",
|
|
287
|
+
conversationEnded: spec?.messages?.conversationEnded ?? "",
|
|
166
288
|
},
|
|
289
|
+
environmentRefs: (spec?.environmentRefs ?? []).map((ref) => ({
|
|
290
|
+
org: ref.org,
|
|
291
|
+
slug: ref.slug,
|
|
292
|
+
})),
|
|
167
293
|
};
|
|
168
294
|
}
|
|
169
295
|
|
|
170
|
-
function
|
|
296
|
+
function ShareAgentForm({
|
|
171
297
|
agent,
|
|
298
|
+
initialShare,
|
|
172
299
|
buildShareUrl,
|
|
173
300
|
onSharingChanged,
|
|
174
|
-
onClose,
|
|
175
301
|
}: {
|
|
176
302
|
readonly agent: Agent;
|
|
303
|
+
readonly initialShare: AgentShare | null;
|
|
177
304
|
readonly buildShareUrl?: (org: string, slug: string) => string;
|
|
178
305
|
readonly onSharingChanged?: () => void;
|
|
179
|
-
readonly onClose: () => void;
|
|
180
306
|
}) {
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
)
|
|
188
|
-
|
|
189
|
-
// draft — rotateShareLink is its sole writer); track it separately and
|
|
190
|
-
// adopt it from every returned agent so the shown link never goes stale.
|
|
191
|
-
const [linkToken, setLinkToken] = useState(
|
|
192
|
-
() => agent.status?.shareLinkToken ?? "",
|
|
307
|
+
const agentName = agent.metadata?.name || (agent.metadata?.slug ?? "");
|
|
308
|
+
|
|
309
|
+
// The latest server share is the single baseline: the draft, the link
|
|
310
|
+
// token, and the share id for rotation all derive from it. `null`
|
|
311
|
+
// until the first save creates the share.
|
|
312
|
+
const [share, setShare] = useState<AgentShare | null>(initialShare);
|
|
313
|
+
const [draft, setDraft] = useState<AgentShareDraft>(() =>
|
|
314
|
+
draftFromShare(initialShare),
|
|
193
315
|
);
|
|
194
316
|
const [activeTab, setActiveTab] = useState("link");
|
|
195
317
|
|
|
196
|
-
const {
|
|
197
|
-
agent.metadata?.id ?? null,
|
|
198
|
-
);
|
|
318
|
+
const { save, isPending } = useSaveAgentShare(agent);
|
|
199
319
|
const { rotateShareLink, isPending: isRotating } = useRotateShareLink(
|
|
200
|
-
|
|
320
|
+
share?.metadata?.id ?? null,
|
|
201
321
|
);
|
|
202
322
|
|
|
203
|
-
//
|
|
204
|
-
//
|
|
323
|
+
// The share's own org/slug form the hosted URL. Before the first save
|
|
324
|
+
// the agent's stand in — exactly the identity the server will assign
|
|
325
|
+
// on create (D2: share slug defaults to the agent's).
|
|
326
|
+
const org = share?.metadata?.org || (agent.metadata?.org ?? "");
|
|
327
|
+
const slug = share?.metadata?.slug || (agent.metadata?.slug ?? "");
|
|
328
|
+
const linkToken = share?.status?.shareLinkToken ?? "";
|
|
329
|
+
|
|
330
|
+
// Single commit path: apply the complete draft, adopt the server's
|
|
331
|
+
// returned share as the new baseline, notify the host.
|
|
205
332
|
const commit = useCallback(
|
|
206
|
-
async (next:
|
|
333
|
+
async (next: AgentShareDraft, successMessage: string): Promise<boolean> => {
|
|
207
334
|
try {
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
335
|
+
const persisted = await save(next, share);
|
|
336
|
+
if (persisted) {
|
|
337
|
+
setShare(persisted);
|
|
338
|
+
setDraft(draftFromShare(persisted));
|
|
339
|
+
} else {
|
|
340
|
+
setDraft(next);
|
|
212
341
|
}
|
|
213
342
|
toast.success(successMessage);
|
|
214
343
|
onSharingChanged?.();
|
|
@@ -218,14 +347,14 @@ function ShareAgentDialogBody({
|
|
|
218
347
|
return false;
|
|
219
348
|
}
|
|
220
349
|
},
|
|
221
|
-
[
|
|
350
|
+
[save, share, onSharingChanged],
|
|
222
351
|
);
|
|
223
352
|
|
|
224
353
|
const handleRotateLink = useCallback(async () => {
|
|
225
354
|
try {
|
|
226
355
|
const updated = await rotateShareLink();
|
|
227
356
|
if (updated) {
|
|
228
|
-
|
|
357
|
+
setShare(updated);
|
|
229
358
|
}
|
|
230
359
|
toast.success("Link reset — the old link no longer works");
|
|
231
360
|
onSharingChanged?.();
|
|
@@ -247,10 +376,22 @@ function ShareAgentDialogBody({
|
|
|
247
376
|
const handleAudienceChange = useCallback(
|
|
248
377
|
(audience: SharingAudience) => {
|
|
249
378
|
if (audience === draft.audience) return;
|
|
379
|
+
// Credential bindings are public-audience only (the proto CEL rule
|
|
380
|
+
// rejects them on org shares — decision 011 addendum), so switching
|
|
381
|
+
// to org drops them, and the toast says so: silent config loss is
|
|
382
|
+
// worse than a wordier confirmation.
|
|
383
|
+
const dropsBindings =
|
|
384
|
+
audience === "org" && draft.environmentRefs.length > 0;
|
|
250
385
|
void commit(
|
|
251
|
-
{
|
|
386
|
+
{
|
|
387
|
+
...draft,
|
|
388
|
+
audience,
|
|
389
|
+
environmentRefs: dropsBindings ? [] : draft.environmentRefs,
|
|
390
|
+
},
|
|
252
391
|
audience === "org"
|
|
253
|
-
?
|
|
392
|
+
? dropsBindings
|
|
393
|
+
? "Only organization members can chat now — tool credential bindings were removed (they apply to public links only)"
|
|
394
|
+
: "Only organization members can chat now"
|
|
254
395
|
: "Anyone with the link can chat now",
|
|
255
396
|
);
|
|
256
397
|
},
|
|
@@ -268,34 +409,7 @@ function ShareAgentDialogBody({
|
|
|
268
409
|
: appendLinkToken(baseShareUrl, linkToken);
|
|
269
410
|
|
|
270
411
|
return (
|
|
271
|
-
|
|
272
|
-
{/* Header */}
|
|
273
|
-
<div className="flex items-start justify-between border-b border-border px-6 py-4">
|
|
274
|
-
<div className="min-w-0">
|
|
275
|
-
<h2
|
|
276
|
-
id="share-agent-title"
|
|
277
|
-
className="text-base font-semibold text-foreground"
|
|
278
|
-
>
|
|
279
|
-
Share
|
|
280
|
-
</h2>
|
|
281
|
-
<p className="mt-0.5 truncate text-xs text-muted-foreground">
|
|
282
|
-
{agentName}
|
|
283
|
-
</p>
|
|
284
|
-
</div>
|
|
285
|
-
<button
|
|
286
|
-
type="button"
|
|
287
|
-
onClick={onClose}
|
|
288
|
-
aria-label="Close"
|
|
289
|
-
className={cn(
|
|
290
|
-
"rounded-md p-1 text-muted-foreground",
|
|
291
|
-
"hover:text-foreground hover:bg-accent-hover",
|
|
292
|
-
"focus:outline-none focus:ring-2 focus:ring-ring",
|
|
293
|
-
)}
|
|
294
|
-
>
|
|
295
|
-
<CloseIcon />
|
|
296
|
-
</button>
|
|
297
|
-
</div>
|
|
298
|
-
|
|
412
|
+
<>
|
|
299
413
|
{/* Sharing master switch — governs every tab, so it sits above them. */}
|
|
300
414
|
<div className="border-b border-border px-6 py-4">
|
|
301
415
|
<div className="flex items-start justify-between gap-4">
|
|
@@ -322,7 +436,7 @@ function ShareAgentDialogBody({
|
|
|
322
436
|
onChange={handleAudienceChange}
|
|
323
437
|
disabled={isPending}
|
|
324
438
|
/>
|
|
325
|
-
<ToolReadinessHint agent={agent}
|
|
439
|
+
<ToolReadinessHint agent={agent} draft={draft} />
|
|
326
440
|
</div>
|
|
327
441
|
|
|
328
442
|
{/* Tabs */}
|
|
@@ -338,6 +452,7 @@ function ShareAgentDialogBody({
|
|
|
338
452
|
<LinkTab
|
|
339
453
|
shareUrl={shareUrl}
|
|
340
454
|
org={org}
|
|
455
|
+
agent={agent}
|
|
341
456
|
enabled={draft.enabled}
|
|
342
457
|
draft={draft}
|
|
343
458
|
isPending={isPending}
|
|
@@ -364,44 +479,41 @@ function ShareAgentDialogBody({
|
|
|
364
479
|
</div>
|
|
365
480
|
</Tabs>
|
|
366
481
|
</div>
|
|
367
|
-
|
|
368
|
-
{/* Footer */}
|
|
369
|
-
<div className="flex items-center justify-end border-t border-border px-6 py-3">
|
|
370
|
-
<button
|
|
371
|
-
type="button"
|
|
372
|
-
onClick={onClose}
|
|
373
|
-
className={cn(
|
|
374
|
-
"rounded-md px-3 py-1.5 text-sm font-medium",
|
|
375
|
-
"bg-primary text-primary-foreground hover:bg-primary-hover",
|
|
376
|
-
"focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
|
377
|
-
)}
|
|
378
|
-
>
|
|
379
|
-
Done
|
|
380
|
-
</button>
|
|
381
|
-
</div>
|
|
382
|
-
</div>
|
|
482
|
+
</>
|
|
383
483
|
);
|
|
384
484
|
}
|
|
385
485
|
|
|
386
486
|
// ---------------------------------------------------------------------------
|
|
387
|
-
// Tool readiness —
|
|
487
|
+
// Tool readiness — visitors' tool use needs credentials bound to the share
|
|
388
488
|
// ---------------------------------------------------------------------------
|
|
389
489
|
|
|
390
490
|
/**
|
|
391
|
-
* Pre-flight hint for tool-using agents: visitors' chats
|
|
392
|
-
*
|
|
393
|
-
*
|
|
394
|
-
*
|
|
395
|
-
*
|
|
491
|
+
* Pre-flight hint for tool-using agents: visitors' chats receive
|
|
492
|
+
* credentials only from the share's own environment bindings, so a
|
|
493
|
+
* tool-using agent with no bindings (`needs-credentials`) or a binding
|
|
494
|
+
* that is still private (`blocked`) will fail at the visitor's first
|
|
495
|
+
* message. Renders nothing when there is nothing to fix — sharing stays
|
|
496
|
+
* one toggle; this only catches the misconfiguration at share time.
|
|
396
497
|
*/
|
|
397
498
|
function ToolReadinessHint({
|
|
398
499
|
agent,
|
|
399
|
-
|
|
500
|
+
draft,
|
|
400
501
|
}: {
|
|
401
502
|
readonly agent: Agent;
|
|
402
|
-
readonly
|
|
503
|
+
readonly draft: AgentShareDraft;
|
|
403
504
|
}) {
|
|
404
|
-
const readiness = useShareToolReadiness(agent,
|
|
505
|
+
const readiness = useShareToolReadiness(agent, draft);
|
|
506
|
+
|
|
507
|
+
if (readiness.status === "needs-credentials") {
|
|
508
|
+
return (
|
|
509
|
+
<p className="mt-2 text-xs text-warning" role="status">
|
|
510
|
+
Visitors' chats can't use this agent's tools yet: no
|
|
511
|
+
credentials are bound to this share. Bind an org-shared environment
|
|
512
|
+
under <span className="font-medium">Tool credentials</span> in the
|
|
513
|
+
Link tab below.
|
|
514
|
+
</p>
|
|
515
|
+
);
|
|
516
|
+
}
|
|
405
517
|
|
|
406
518
|
if (readiness.status !== "blocked") {
|
|
407
519
|
return null;
|
|
@@ -415,8 +527,8 @@ function ToolReadinessHint({
|
|
|
415
527
|
Visitors' chats can't use this agent's tools yet: the
|
|
416
528
|
environment{plural ? "s" : ""} <span className="font-medium">{envList}</span>{" "}
|
|
417
529
|
{plural ? "are" : "is"} private. Share {plural ? "them" : "it"} with your
|
|
418
|
-
organization (Settings → Environments) so visitor
|
|
419
|
-
|
|
530
|
+
organization (Settings → Environments) so visitor runs can use the
|
|
531
|
+
credentials. Secret values stay hidden either way.
|
|
420
532
|
</p>
|
|
421
533
|
);
|
|
422
534
|
}
|
|
@@ -493,7 +605,7 @@ function WhoPaysLine({
|
|
|
493
605
|
}) {
|
|
494
606
|
const mode = useDeploymentMode();
|
|
495
607
|
// An Organization's id equals its slug (see ApiResourceMetadata.id), so
|
|
496
|
-
// the
|
|
608
|
+
// the share's org reference is directly usable as the billing org id.
|
|
497
609
|
// Cloud-only: local mode has no billing accounts.
|
|
498
610
|
const { account } = useBillingAccount(mode === "cloud" ? org : null);
|
|
499
611
|
|
|
@@ -515,9 +627,25 @@ function WhoPaysLine({
|
|
|
515
627
|
// Link tab
|
|
516
628
|
// ---------------------------------------------------------------------------
|
|
517
629
|
|
|
630
|
+
/**
|
|
631
|
+
* Names the reason the copy fields above are grayed out: sharing is off, so
|
|
632
|
+
* the link/embed would lead to NOT_FOUND (the fields stay disabled — handing
|
|
633
|
+
* out a dead link is worse than a disabled button). Points at the master
|
|
634
|
+
* switch as the remedy.
|
|
635
|
+
*/
|
|
636
|
+
function SharingOffHint({ subject }: { readonly subject: string }) {
|
|
637
|
+
return (
|
|
638
|
+
<p className="text-xs text-muted-foreground" role="status">
|
|
639
|
+
Sharing is off, so {subject} doesn't work yet — turn on the switch
|
|
640
|
+
above to activate it.
|
|
641
|
+
</p>
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
|
|
518
645
|
function LinkTab({
|
|
519
646
|
shareUrl,
|
|
520
647
|
org,
|
|
648
|
+
agent,
|
|
521
649
|
enabled,
|
|
522
650
|
draft,
|
|
523
651
|
isPending,
|
|
@@ -528,11 +656,12 @@ function LinkTab({
|
|
|
528
656
|
}: {
|
|
529
657
|
readonly shareUrl: string;
|
|
530
658
|
readonly org: string;
|
|
659
|
+
readonly agent: Agent;
|
|
531
660
|
readonly enabled: boolean;
|
|
532
|
-
readonly draft:
|
|
661
|
+
readonly draft: AgentShareDraft;
|
|
533
662
|
readonly isPending: boolean;
|
|
534
663
|
readonly commit: (
|
|
535
|
-
next:
|
|
664
|
+
next: AgentShareDraft,
|
|
536
665
|
successMessage: string,
|
|
537
666
|
) => Promise<boolean>;
|
|
538
667
|
readonly hasLinkToken: boolean;
|
|
@@ -550,6 +679,7 @@ function LinkTab({
|
|
|
550
679
|
disabled={!enabled}
|
|
551
680
|
openHref={enabled ? shareUrl : undefined}
|
|
552
681
|
/>
|
|
682
|
+
{!enabled && <SharingOffHint subject="this link" />}
|
|
553
683
|
|
|
554
684
|
{isOrgAudience ? (
|
|
555
685
|
<p className="text-xs text-muted-foreground">
|
|
@@ -575,6 +705,16 @@ function LinkTab({
|
|
|
575
705
|
/>
|
|
576
706
|
)}
|
|
577
707
|
|
|
708
|
+
{!isOrgAudience && (
|
|
709
|
+
<ToolCredentialsSection
|
|
710
|
+
org={org}
|
|
711
|
+
agent={agent}
|
|
712
|
+
draft={draft}
|
|
713
|
+
isPending={isPending}
|
|
714
|
+
commit={commit}
|
|
715
|
+
/>
|
|
716
|
+
)}
|
|
717
|
+
|
|
578
718
|
<MessagesEditor draft={draft} isPending={isPending} commit={commit} />
|
|
579
719
|
</div>
|
|
580
720
|
);
|
|
@@ -627,6 +767,101 @@ function ResetLinkControl({
|
|
|
627
767
|
);
|
|
628
768
|
}
|
|
629
769
|
|
|
770
|
+
// ---------------------------------------------------------------------------
|
|
771
|
+
// Tool credentials — org-shared environments bound to the share for visitors
|
|
772
|
+
// ---------------------------------------------------------------------------
|
|
773
|
+
|
|
774
|
+
/**
|
|
775
|
+
* Binds org-shared environments to the share's `environment_refs` — the
|
|
776
|
+
* consent act that makes a tool-using agent work for visitors (decision
|
|
777
|
+
* 011: credentials belong to the channel, never to the agent's pristine
|
|
778
|
+
* default instance). Public audience only; the section disappears for
|
|
779
|
+
* org shares, whose member sessions carry no share linkage in Phase A.
|
|
780
|
+
*
|
|
781
|
+
* Expanded by default when the agent uses MCP tools — for those agents
|
|
782
|
+
* this is essential configuration, not an advanced option.
|
|
783
|
+
*/
|
|
784
|
+
function ToolCredentialsSection({
|
|
785
|
+
org,
|
|
786
|
+
agent,
|
|
787
|
+
draft,
|
|
788
|
+
isPending,
|
|
789
|
+
commit,
|
|
790
|
+
}: {
|
|
791
|
+
readonly org: string;
|
|
792
|
+
readonly agent: Agent;
|
|
793
|
+
readonly draft: AgentShareDraft;
|
|
794
|
+
readonly isPending: boolean;
|
|
795
|
+
readonly commit: (
|
|
796
|
+
next: AgentShareDraft,
|
|
797
|
+
successMessage: string,
|
|
798
|
+
) => Promise<boolean>;
|
|
799
|
+
}) {
|
|
800
|
+
const hasMcpTools = (agent.spec?.mcpServerUsages?.length ?? 0) > 0;
|
|
801
|
+
const [expanded, setExpanded] = useState(
|
|
802
|
+
hasMcpTools || draft.environmentRefs.length > 0,
|
|
803
|
+
);
|
|
804
|
+
|
|
805
|
+
const handleChange = useCallback(
|
|
806
|
+
(refs: ResourceRef[]) => {
|
|
807
|
+
const added = refs.length > draft.environmentRefs.length;
|
|
808
|
+
void commit(
|
|
809
|
+
{ ...draft, environmentRefs: refs },
|
|
810
|
+
added ? "Credentials bound" : "Credential bindings updated",
|
|
811
|
+
);
|
|
812
|
+
},
|
|
813
|
+
[commit, draft],
|
|
814
|
+
);
|
|
815
|
+
|
|
816
|
+
// Only org-shared environments are guest-usable (the runtime merge
|
|
817
|
+
// skips private ones — decision 006), so offering others would bind
|
|
818
|
+
// credentials that silently never apply.
|
|
819
|
+
const onlyOrgShared = useCallback(
|
|
820
|
+
(env: Environment) =>
|
|
821
|
+
env.metadata?.visibility === ApiResourceVisibility.visibility_org,
|
|
822
|
+
[],
|
|
823
|
+
);
|
|
824
|
+
|
|
825
|
+
return (
|
|
826
|
+
<section>
|
|
827
|
+
<button
|
|
828
|
+
type="button"
|
|
829
|
+
onClick={() => setExpanded((v) => !v)}
|
|
830
|
+
aria-expanded={expanded}
|
|
831
|
+
className={cn(
|
|
832
|
+
"inline-flex items-center gap-1 text-xs font-medium text-muted-foreground",
|
|
833
|
+
"hover:text-foreground",
|
|
834
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded",
|
|
835
|
+
)}
|
|
836
|
+
>
|
|
837
|
+
<ChevronIcon
|
|
838
|
+
className={cn("size-3 transition-transform", expanded && "rotate-90")}
|
|
839
|
+
/>
|
|
840
|
+
Tool credentials
|
|
841
|
+
</button>
|
|
842
|
+
|
|
843
|
+
{expanded && (
|
|
844
|
+
<div className="mt-2 flex flex-col gap-2">
|
|
845
|
+
<p className="text-[0.65rem] text-muted-foreground">
|
|
846
|
+
Environments whose values visitors' chats can use — bind one
|
|
847
|
+
holding the credentials this agent's tools need (a read-only
|
|
848
|
+
token is safest). Only environments shared with your organization
|
|
849
|
+
can be bound; share one first in Settings → Environments.
|
|
850
|
+
Secret values stay hidden from visitors either way.
|
|
851
|
+
</p>
|
|
852
|
+
<EnvironmentPicker
|
|
853
|
+
org={org}
|
|
854
|
+
value={draft.environmentRefs}
|
|
855
|
+
onChange={handleChange}
|
|
856
|
+
disabled={isPending}
|
|
857
|
+
filterEnvironment={onlyOrgShared}
|
|
858
|
+
/>
|
|
859
|
+
</div>
|
|
860
|
+
)}
|
|
861
|
+
</section>
|
|
862
|
+
);
|
|
863
|
+
}
|
|
864
|
+
|
|
630
865
|
// ---------------------------------------------------------------------------
|
|
631
866
|
// Embed tab
|
|
632
867
|
// ---------------------------------------------------------------------------
|
|
@@ -675,10 +910,10 @@ function EmbedTab({
|
|
|
675
910
|
readonly agentName: string;
|
|
676
911
|
readonly linkToken: string;
|
|
677
912
|
readonly enabled: boolean;
|
|
678
|
-
readonly draft:
|
|
913
|
+
readonly draft: AgentShareDraft;
|
|
679
914
|
readonly isPending: boolean;
|
|
680
915
|
readonly commit: (
|
|
681
|
-
next:
|
|
916
|
+
next: AgentShareDraft,
|
|
682
917
|
successMessage: string,
|
|
683
918
|
) => Promise<boolean>;
|
|
684
919
|
}) {
|
|
@@ -713,6 +948,7 @@ function EmbedTab({
|
|
|
713
948
|
disabled={!enabled}
|
|
714
949
|
multiline
|
|
715
950
|
/>
|
|
951
|
+
{!enabled && <SharingOffHint subject="this embed" />}
|
|
716
952
|
<p className="text-xs text-muted-foreground">
|
|
717
953
|
The widget hides itself on sites that aren't allowed to embed
|
|
718
954
|
this agent. Free embeds show a "Powered by Stigmer" badge.
|
|
@@ -795,10 +1031,10 @@ function OriginsEditor({
|
|
|
795
1031
|
isPending,
|
|
796
1032
|
commit,
|
|
797
1033
|
}: {
|
|
798
|
-
readonly draft:
|
|
1034
|
+
readonly draft: AgentShareDraft;
|
|
799
1035
|
readonly isPending: boolean;
|
|
800
1036
|
readonly commit: (
|
|
801
|
-
next:
|
|
1037
|
+
next: AgentShareDraft,
|
|
802
1038
|
successMessage: string,
|
|
803
1039
|
) => Promise<boolean>;
|
|
804
1040
|
}) {
|
|
@@ -950,17 +1186,15 @@ const MESSAGE_FIELDS = [
|
|
|
950
1186
|
},
|
|
951
1187
|
] as const;
|
|
952
1188
|
|
|
953
|
-
type MessageKey = (typeof MESSAGE_FIELDS)[number]["key"];
|
|
954
|
-
|
|
955
1189
|
function MessagesEditor({
|
|
956
1190
|
draft,
|
|
957
1191
|
isPending,
|
|
958
1192
|
commit,
|
|
959
1193
|
}: {
|
|
960
|
-
readonly draft:
|
|
1194
|
+
readonly draft: AgentShareDraft;
|
|
961
1195
|
readonly isPending: boolean;
|
|
962
1196
|
readonly commit: (
|
|
963
|
-
next:
|
|
1197
|
+
next: AgentShareDraft,
|
|
964
1198
|
successMessage: string,
|
|
965
1199
|
) => Promise<boolean>;
|
|
966
1200
|
}) {
|