@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.
Files changed (72) hide show
  1. package/environment/EnvironmentPicker.d.ts +17 -1
  2. package/environment/EnvironmentPicker.d.ts.map +1 -1
  3. package/environment/EnvironmentPicker.js +3 -2
  4. package/environment/EnvironmentPicker.js.map +1 -1
  5. package/index.d.ts +2 -2
  6. package/index.d.ts.map +1 -1
  7. package/index.js +2 -2
  8. package/index.js.map +1 -1
  9. package/package.json +4 -4
  10. package/sharing/ShareAgentDialog.d.ts +10 -8
  11. package/sharing/ShareAgentDialog.d.ts.map +1 -1
  12. package/sharing/ShareAgentDialog.js +139 -54
  13. package/sharing/ShareAgentDialog.js.map +1 -1
  14. package/sharing/SharedAgentChat.d.ts +1 -1
  15. package/sharing/SharedAgentChat.d.ts.map +1 -1
  16. package/sharing/index.d.ts +4 -2
  17. package/sharing/index.d.ts.map +1 -1
  18. package/sharing/index.js +2 -1
  19. package/sharing/index.js.map +1 -1
  20. package/sharing/useAgentShare.d.ts +43 -0
  21. package/sharing/useAgentShare.d.ts.map +1 -0
  22. package/sharing/useAgentShare.js +54 -0
  23. package/sharing/useAgentShare.js.map +1 -0
  24. package/sharing/useRotateShareLink.d.ts +22 -19
  25. package/sharing/useRotateShareLink.d.ts.map +1 -1
  26. package/sharing/useRotateShareLink.js +22 -19
  27. package/sharing/useRotateShareLink.js.map +1 -1
  28. package/sharing/useSaveAgentShare.d.ts +105 -0
  29. package/sharing/useSaveAgentShare.d.ts.map +1 -0
  30. package/sharing/useSaveAgentShare.js +97 -0
  31. package/sharing/useSaveAgentShare.js.map +1 -0
  32. package/sharing/useShareAgent.d.ts +5 -4
  33. package/sharing/useShareAgent.d.ts.map +1 -1
  34. package/sharing/useShareAgent.js +5 -4
  35. package/sharing/useShareAgent.js.map +1 -1
  36. package/sharing/useShareToolReadiness.d.ts +29 -25
  37. package/sharing/useShareToolReadiness.d.ts.map +1 -1
  38. package/sharing/useShareToolReadiness.js +47 -51
  39. package/sharing/useShareToolReadiness.js.map +1 -1
  40. package/sharing/useSharedAgentProfile.d.ts +5 -4
  41. package/sharing/useSharedAgentProfile.d.ts.map +1 -1
  42. package/sharing/useSharedAgentProfile.js +6 -5
  43. package/sharing/useSharedAgentProfile.js.map +1 -1
  44. package/src/environment/EnvironmentPicker.tsx +23 -2
  45. package/src/index.ts +8 -4
  46. package/src/sharing/ShareAgentDialog.tsx +353 -119
  47. package/src/sharing/SharedAgentChat.tsx +1 -1
  48. package/src/sharing/__tests__/ShareAgentDialog.test.tsx +550 -369
  49. package/src/sharing/__tests__/useAgentShare.test.tsx +149 -0
  50. package/src/sharing/__tests__/useRotateShareLink.test.tsx +8 -8
  51. package/src/sharing/__tests__/useSaveAgentShare.test.tsx +241 -0
  52. package/src/sharing/__tests__/useShareAgent.test.tsx +6 -2
  53. package/src/sharing/__tests__/useShareToolReadiness.test.tsx +84 -47
  54. package/src/sharing/__tests__/useSharedAgentProfile.test.tsx +1 -1
  55. package/src/sharing/index.ts +9 -7
  56. package/src/sharing/useAgentShare.ts +91 -0
  57. package/src/sharing/useRotateShareLink.ts +30 -25
  58. package/src/sharing/useSaveAgentShare.ts +186 -0
  59. package/src/sharing/useShareAgent.tsx +5 -4
  60. package/src/sharing/useShareToolReadiness.ts +62 -60
  61. package/src/sharing/useSharedAgentProfile.ts +7 -6
  62. package/src/switch/Switch.tsx +6 -1
  63. package/src/switch/__tests__/Switch.test.tsx +19 -0
  64. package/switch/Switch.d.ts.map +1 -1
  65. package/switch/Switch.js +6 -1
  66. package/switch/Switch.js.map +1 -1
  67. package/sharing/useUpdateAgentSharing.d.ts +0 -86
  68. package/sharing/useUpdateAgentSharing.d.ts.map +0 -1
  69. package/sharing/useUpdateAgentSharing.js +0 -81
  70. package/sharing/useUpdateAgentSharing.js.map +0 -1
  71. package/src/sharing/__tests__/useUpdateAgentSharing.test.tsx +0 -200
  72. package/src/sharing/useUpdateAgentSharing.ts +0 -151
@@ -1,8 +1,9 @@
1
1
  import { describe, it, expect, vi, beforeAll, afterEach } from "vitest";
2
2
  import { render, screen, fireEvent, waitFor, cleanup } from "@testing-library/react";
3
3
  import type { ReactNode } from "react";
4
- import type { UpdateAgentSharingInput } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/io_pb";
5
- import { AgentSharingAudience } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/spec_pb";
4
+ import { AgentShareAudience } from "@stigmer/protos/ai/stigmer/agentic/agentshare/v1/spec_pb";
5
+ import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
6
+ import type { AgentShareInput } from "@stigmer/sdk";
6
7
  import { StigmerContext } from "../../context";
7
8
  import { FetchCacheContext } from "../../internal/FetchCacheProvider";
8
9
  import { DeploymentModeContext } from "../../deployment-mode";
@@ -26,18 +27,41 @@ beforeAll(() => {
26
27
  afterEach(cleanup);
27
28
 
28
29
  interface MockOverrides {
29
- updateSharing?: (input: UpdateAgentSharingInput) => Promise<unknown>;
30
+ getByAgent?: (input: unknown) => Promise<unknown>;
31
+ apply?: (input: AgentShareInput) => Promise<unknown>;
30
32
  rotateShareLink?: (input: unknown) => Promise<unknown>;
31
33
  getOrCreateBillingAccount?: (orgId: string) => Promise<unknown>;
34
+ environments?: unknown[];
32
35
  }
33
36
 
34
37
  function createMockStigmer(overrides: MockOverrides = {}) {
35
38
  return {
36
- agent: {
37
- updateSharing: overrides.updateSharing ?? vi.fn().mockResolvedValue({}),
39
+ agentShare: {
40
+ getByAgent:
41
+ overrides.getByAgent ??
42
+ vi.fn().mockResolvedValue({ totalCount: 0, items: [] }),
43
+ apply: overrides.apply ?? vi.fn().mockResolvedValue({}),
38
44
  rotateShareLink:
39
45
  overrides.rotateShareLink ?? vi.fn().mockResolvedValue({}),
40
46
  },
47
+ environment: {
48
+ list: vi.fn().mockResolvedValue({
49
+ items: overrides.environments ?? [],
50
+ totalCount: overrides.environments?.length ?? 0,
51
+ }),
52
+ // The readiness hook checks bound refs' visibility; the picker's
53
+ // list above is the source of the fixtures, so resolve from it.
54
+ getByReference: vi
55
+ .fn()
56
+ .mockImplementation(({ slug }: { org: string; slug: string }) => {
57
+ const env = (overrides.environments ?? []).find(
58
+ (e) => (e as { metadata?: { slug?: string } }).metadata?.slug === slug,
59
+ );
60
+ return env
61
+ ? Promise.resolve(env)
62
+ : Promise.reject(new Error("not found"));
63
+ }),
64
+ },
41
65
  billing: {
42
66
  getOrCreateBillingAccount:
43
67
  overrides.getOrCreateBillingAccount ??
@@ -66,41 +90,96 @@ function Providers({
66
90
  );
67
91
  }
68
92
 
69
- function makeAgent(
70
- sharing?: {
93
+ function makeAgent(overrides?: { mcpUsages?: boolean }) {
94
+ return {
95
+ metadata: {
96
+ id: "agt_1",
97
+ org: "acme",
98
+ slug: "support-agent",
99
+ name: "Support Agent",
100
+ },
101
+ spec: {
102
+ mcpServerUsages: overrides?.mcpUsages
103
+ ? [{ mcpServerRef: { org: "acme", slug: "github" } }]
104
+ : [],
105
+ },
106
+ } as never;
107
+ }
108
+
109
+ function makeShare(
110
+ spec?: {
71
111
  enabled?: boolean;
72
- audience?: AgentSharingAudience;
112
+ audience?: AgentShareAudience;
73
113
  allowedOrigins?: string[];
74
114
  messages?: {
75
115
  rateLimited?: string;
76
116
  unavailable?: string;
77
117
  conversationEnded?: string;
78
118
  };
119
+ environmentRefs?: { org: string; slug: string }[];
79
120
  },
80
121
  shareLinkToken?: string,
81
122
  ) {
82
123
  return {
83
124
  metadata: {
84
- id: "agt_1",
125
+ id: "ash_1",
85
126
  org: "acme",
86
127
  slug: "support-agent",
87
128
  name: "Support Agent",
88
129
  },
89
- spec: { sharing },
90
- ...(shareLinkToken !== undefined
91
- ? { status: { shareLinkToken } }
92
- : {}),
93
- } as never;
130
+ spec: { agentRef: { org: "acme", slug: "support-agent" }, ...spec },
131
+ ...(shareLinkToken !== undefined ? { status: { shareLinkToken } } : {}),
132
+ };
133
+ }
134
+
135
+ function withShare(share: unknown) {
136
+ return vi.fn().mockResolvedValue({ totalCount: 1, items: [share] });
137
+ }
138
+
139
+ function orgSharedEnv(slug: string, name?: string) {
140
+ return {
141
+ metadata: {
142
+ slug,
143
+ name: name ?? slug,
144
+ visibility: ApiResourceVisibility.visibility_org,
145
+ },
146
+ spec: { description: "" },
147
+ };
94
148
  }
95
149
 
96
150
  const buildShareUrl = (org: string, slug: string) =>
97
151
  `https://app.example.com/chat/${org}/${slug}`;
98
152
 
153
+ /** Render the open dialog and wait for the share load to settle. */
154
+ async function renderOpenDialog(
155
+ client: unknown,
156
+ props?: Partial<Parameters<typeof ShareAgentDialog>[0]> & { mode?: "cloud" | "local" },
157
+ ) {
158
+ const { mode, ...dialogProps } = props ?? {};
159
+ render(
160
+ <Providers client={client} mode={mode}>
161
+ <ShareAgentDialog
162
+ open
163
+ onOpenChange={() => {}}
164
+ agent={makeAgent()}
165
+ buildShareUrl={buildShareUrl}
166
+ {...dialogProps}
167
+ />
168
+ </Providers>,
169
+ );
170
+ await waitFor(() =>
171
+ expect(screen.queryByLabelText("Loading sharing settings")).toBeNull(),
172
+ );
173
+ }
174
+
99
175
  describe("ShareAgentDialog", () => {
100
- it("mounts no body while closed (billing fetch stays lazy)", () => {
176
+ it("mounts no body while closed (share and billing fetches stay lazy)", () => {
177
+ const getByAgent = vi.fn();
101
178
  const getOrCreateBillingAccount = vi.fn();
102
179
  render(
103
- <Providers client={createMockStigmer({ getOrCreateBillingAccount })}>
180
+ <Providers
181
+ client={createMockStigmer({ getByAgent, getOrCreateBillingAccount })}
182
+ >
104
183
  <ShareAgentDialog
105
184
  open={false}
106
185
  onOpenChange={() => {}}
@@ -111,54 +190,180 @@ describe("ShareAgentDialog", () => {
111
190
  );
112
191
 
113
192
  expect(screen.queryByText("Share")).toBeNull();
193
+ expect(getByAgent).not.toHaveBeenCalled();
114
194
  expect(getOrCreateBillingAccount).not.toHaveBeenCalled();
115
195
  });
116
196
 
117
- it("renders header, toggle, and the share link from buildShareUrl", () => {
197
+ it("shows a loading state while the share resolves, then the form", async () => {
198
+ let resolveLoad: (v: unknown) => void = () => {};
199
+ const getByAgent = vi.fn().mockReturnValue(
200
+ new Promise((resolve) => {
201
+ resolveLoad = resolve;
202
+ }),
203
+ );
118
204
  render(
119
- <Providers client={createMockStigmer()}>
205
+ <Providers client={createMockStigmer({ getByAgent })}>
120
206
  <ShareAgentDialog
121
207
  open
122
208
  onOpenChange={() => {}}
123
- agent={makeAgent({ enabled: true })}
209
+ agent={makeAgent()}
124
210
  buildShareUrl={buildShareUrl}
125
211
  />
126
212
  </Providers>,
127
213
  );
128
214
 
129
- expect(screen.getByText("Share")).toBeTruthy();
130
- expect(screen.getByText("Support Agent")).toBeTruthy();
131
- expect(screen.getByRole("switch", { hidden: true }).getAttribute("aria-checked")).toBe("true");
132
- expect(
133
- screen.getByText("https://app.example.com/chat/acme/support-agent"),
134
- ).toBeTruthy();
215
+ expect(screen.getByLabelText("Loading sharing settings")).toBeTruthy();
216
+ resolveLoad({ totalCount: 1, items: [makeShare({ enabled: true })] });
217
+ await waitFor(() =>
218
+ expect(
219
+ screen.getByText("https://app.example.com/chat/acme/support-agent"),
220
+ ).toBeTruthy(),
221
+ );
135
222
  });
136
223
 
137
- it("falls back to the relative /chat path when buildShareUrl is omitted", () => {
224
+ it("offers retry when the share load fails", async () => {
225
+ const share = makeShare({ enabled: true });
226
+ const getByAgent = vi
227
+ .fn()
228
+ .mockRejectedValueOnce(new Error("backend unavailable"))
229
+ .mockResolvedValueOnce({ totalCount: 1, items: [share] });
138
230
  render(
139
- <Providers client={createMockStigmer()}>
231
+ <Providers client={createMockStigmer({ getByAgent })}>
140
232
  <ShareAgentDialog
141
233
  open
142
234
  onOpenChange={() => {}}
143
- agent={makeAgent({ enabled: true })}
235
+ agent={makeAgent()}
236
+ buildShareUrl={buildShareUrl}
144
237
  />
145
238
  </Providers>,
146
239
  );
147
240
 
241
+ // A failed load must never render a form that would create a share
242
+ // over an existing one the dialog couldn't see.
243
+ expect(
244
+ await screen.findByText(/couldn't load this agent's sharing settings/i),
245
+ ).toBeTruthy();
246
+
247
+ fireEvent.click(screen.getByRole("button", { name: "Try again", hidden: true }));
248
+ await waitFor(() =>
249
+ expect(
250
+ screen.getByText("https://app.example.com/chat/acme/support-agent"),
251
+ ).toBeTruthy(),
252
+ );
253
+ });
254
+
255
+ it("renders header, toggle, and the share link from buildShareUrl", async () => {
256
+ const client = createMockStigmer({
257
+ getByAgent: withShare(makeShare({ enabled: true })),
258
+ });
259
+ await renderOpenDialog(client);
260
+
261
+ expect(screen.getByText("Share")).toBeTruthy();
262
+ expect(screen.getByText("Support Agent")).toBeTruthy();
263
+ expect(screen.getByRole("switch", { hidden: true }).getAttribute("aria-checked")).toBe("true");
264
+ expect(
265
+ screen.getByText("https://app.example.com/chat/acme/support-agent"),
266
+ ).toBeTruthy();
267
+ });
268
+
269
+ it("builds the link from the SHARE's slug when it differs from the agent's", async () => {
270
+ // A manifest-renamed share: the hosted URL lives at the share's slug.
271
+ const renamed = {
272
+ ...makeShare({ enabled: true }),
273
+ metadata: { id: "ash_1", org: "acme", slug: "help-desk", name: "Help Desk" },
274
+ };
275
+ const client = createMockStigmer({ getByAgent: withShare(renamed) });
276
+ await renderOpenDialog(client);
277
+
278
+ expect(
279
+ screen.getByText("https://app.example.com/chat/acme/help-desk"),
280
+ ).toBeTruthy();
281
+ });
282
+
283
+ it("falls back to the relative /chat path when buildShareUrl is omitted", async () => {
284
+ const client = createMockStigmer({
285
+ getByAgent: withShare(makeShare({ enabled: true })),
286
+ });
287
+ await renderOpenDialog(client, { buildShareUrl: undefined });
288
+
148
289
  expect(screen.getByText("/chat/acme/support-agent")).toBeTruthy();
149
290
  });
150
291
 
151
- it("shows the indexability warning", () => {
152
- render(
153
- <Providers client={createMockStigmer()}>
154
- <ShareAgentDialog
155
- open
156
- onOpenChange={() => {}}
157
- agent={makeAgent({ enabled: true })}
158
- buildShareUrl={buildShareUrl}
159
- />
160
- </Providers>,
161
- );
292
+ describe("never-shared agent (no share yet)", () => {
293
+ it("renders the off state with disabled copy affordances", async () => {
294
+ const client = createMockStigmer();
295
+ await renderOpenDialog(client);
296
+
297
+ expect(
298
+ screen.getByRole("switch", { hidden: true }).getAttribute("aria-checked"),
299
+ ).toBe("false");
300
+ const copy = screen.getByRole("button", {
301
+ name: "Copy",
302
+ hidden: true,
303
+ }) as HTMLButtonElement;
304
+ expect(copy.disabled).toBe(true);
305
+ expect(
306
+ screen.getByText(/sharing is off, so this link doesn't work yet/i),
307
+ ).toBeTruthy();
308
+ });
309
+
310
+ it("first enable creates the canonical share via apply", async () => {
311
+ const apply = vi.fn().mockResolvedValue(makeShare({ enabled: true }));
312
+ const client = createMockStigmer({ apply });
313
+ await renderOpenDialog(client);
314
+
315
+ fireEvent.click(screen.getByRole("switch", { hidden: true }));
316
+
317
+ await waitFor(() => expect(apply).toHaveBeenCalledTimes(1));
318
+ const input = apply.mock.calls[0][0] as AgentShareInput;
319
+ // Create identity: the agent's own org/slug/name (the server's D2
320
+ // default made explicit) plus the agent reference.
321
+ expect(input.org).toBe("acme");
322
+ expect(input.slug).toBe("support-agent");
323
+ expect(input.agentRef).toEqual({ org: "acme", slug: "support-agent" });
324
+ expect(input.enabled).toBe(true);
325
+
326
+ await waitFor(() =>
327
+ expect(
328
+ screen.getByRole("switch", { hidden: true }).getAttribute("aria-checked"),
329
+ ).toBe("true"),
330
+ );
331
+ });
332
+ });
333
+
334
+ describe("Sharing-off state", () => {
335
+ it("shows the reason on the Embed tab too", async () => {
336
+ const client = createMockStigmer({
337
+ getByAgent: withShare(makeShare({ enabled: false })),
338
+ });
339
+ await renderOpenDialog(client);
340
+
341
+ fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
342
+ expect(
343
+ screen.getByText(/sharing is off, so this embed doesn't work yet/i),
344
+ ).toBeTruthy();
345
+ });
346
+
347
+ it("drops the hint and enables copy once sharing is on", async () => {
348
+ const client = createMockStigmer({
349
+ getByAgent: withShare(makeShare({ enabled: true })),
350
+ });
351
+ await renderOpenDialog(client);
352
+
353
+ const copy = screen.getByRole("button", {
354
+ name: "Copy",
355
+ hidden: true,
356
+ }) as HTMLButtonElement;
357
+ expect(copy.disabled).toBe(false);
358
+ expect(screen.queryByText(/sharing is off/i)).toBeNull();
359
+ });
360
+ });
361
+
362
+ it("shows the indexability warning", async () => {
363
+ const client = createMockStigmer({
364
+ getByAgent: withShare(makeShare({ enabled: true })),
365
+ });
366
+ await renderOpenDialog(client);
162
367
 
163
368
  expect(
164
369
  screen.getByText(/forwarded and indexed by search engines/),
@@ -166,22 +371,19 @@ describe("ShareAgentDialog", () => {
166
371
  });
167
372
 
168
373
  describe("Audience", () => {
169
- it("defaults to Public link and switching to Org members commits the full block", async () => {
170
- const updateSharing = vi.fn().mockResolvedValue({});
171
- render(
172
- <Providers client={createMockStigmer({ updateSharing })}>
173
- <ShareAgentDialog
174
- open
175
- onOpenChange={() => {}}
176
- agent={makeAgent({
177
- enabled: true,
178
- allowedOrigins: ["https://example.com"],
179
- messages: { rateLimited: "Easy there." },
180
- })}
181
- buildShareUrl={buildShareUrl}
182
- />
183
- </Providers>,
184
- );
374
+ it("defaults to Public link and switching to Org members applies the full spec", async () => {
375
+ const apply = vi.fn().mockResolvedValue({});
376
+ const client = createMockStigmer({
377
+ apply,
378
+ getByAgent: withShare(
379
+ makeShare({
380
+ enabled: true,
381
+ allowedOrigins: ["https://example.com"],
382
+ messages: { rateLimited: "Easy there." },
383
+ }),
384
+ ),
385
+ });
386
+ await renderOpenDialog(client);
185
387
 
186
388
  const publicOption = screen.getByRole("radio", {
187
389
  name: "Public link",
@@ -193,30 +395,49 @@ describe("ShareAgentDialog", () => {
193
395
  screen.getByRole("radio", { name: "Org members", hidden: true }),
194
396
  );
195
397
 
196
- await waitFor(() => expect(updateSharing).toHaveBeenCalledTimes(1));
197
- const input = updateSharing.mock.calls[0][0] as UpdateAgentSharingInput;
198
- expect(input.sharing?.audience).toBe(AgentSharingAudience.org);
199
- // Whole-block replace: the audience switch must not clobber the rest.
200
- expect(input.sharing?.enabled).toBe(true);
201
- expect(input.sharing?.allowedOrigins).toEqual(["https://example.com"]);
202
- expect(input.sharing?.messages?.rateLimited).toBe("Easy there.");
203
- });
204
-
205
- it("renders org-audience copy: member link, revocation note, no indexability warning", () => {
206
- render(
207
- <Providers client={createMockStigmer()}>
208
- <ShareAgentDialog
209
- open
210
- onOpenChange={() => {}}
211
- agent={makeAgent({
212
- enabled: true,
213
- audience: AgentSharingAudience.org,
214
- })}
215
- buildShareUrl={buildShareUrl}
216
- />
217
- </Providers>,
398
+ await waitFor(() => expect(apply).toHaveBeenCalledTimes(1));
399
+ const input = apply.mock.calls[0][0] as AgentShareInput;
400
+ expect(input.audience).toBe(AgentShareAudience.org);
401
+ // Whole-spec replace: the audience switch must not clobber the rest.
402
+ expect(input.enabled).toBe(true);
403
+ expect(input.allowedOrigins).toEqual(["https://example.com"]);
404
+ expect(input.messages?.rateLimited).toBe("Easy there.");
405
+ });
406
+
407
+ it("switching to Org members drops credential bindings (public-audience only)", async () => {
408
+ const apply = vi.fn().mockResolvedValue({});
409
+ const client = createMockStigmer({
410
+ apply,
411
+ getByAgent: withShare(
412
+ makeShare({
413
+ enabled: true,
414
+ environmentRefs: [{ org: "acme", slug: "github-creds" }],
415
+ }),
416
+ ),
417
+ environments: [orgSharedEnv("github-creds")],
418
+ });
419
+ await renderOpenDialog(client);
420
+
421
+ fireEvent.click(
422
+ screen.getByRole("radio", { name: "Org members", hidden: true }),
218
423
  );
219
424
 
425
+ // The proto CEL rule rejects environment_refs on org-audience
426
+ // shares — carrying them would fail the whole apply.
427
+ await waitFor(() => expect(apply).toHaveBeenCalledTimes(1));
428
+ const input = apply.mock.calls[0][0] as AgentShareInput;
429
+ expect(input.audience).toBe(AgentShareAudience.org);
430
+ expect(input.environmentRefs).toEqual([]);
431
+ });
432
+
433
+ it("renders org-audience copy: member link, revocation note, no indexability warning", async () => {
434
+ const client = createMockStigmer({
435
+ getByAgent: withShare(
436
+ makeShare({ enabled: true, audience: AgentShareAudience.org }),
437
+ ),
438
+ });
439
+ await renderOpenDialog(client);
440
+
220
441
  expect(screen.getByText("Organization members can chat")).toBeTruthy();
221
442
  expect(screen.getByText("Member chat link")).toBeTruthy();
222
443
  expect(
@@ -227,20 +448,13 @@ describe("ShareAgentDialog", () => {
227
448
  ).toBeNull();
228
449
  });
229
450
 
230
- it("replaces the Embed tab with a public-only explanation for the org audience", () => {
231
- render(
232
- <Providers client={createMockStigmer()}>
233
- <ShareAgentDialog
234
- open
235
- onOpenChange={() => {}}
236
- agent={makeAgent({
237
- enabled: true,
238
- audience: AgentSharingAudience.org,
239
- })}
240
- buildShareUrl={buildShareUrl}
241
- />
242
- </Providers>,
243
- );
451
+ it("replaces the Embed tab with a public-only explanation for the org audience", async () => {
452
+ const client = createMockStigmer({
453
+ getByAgent: withShare(
454
+ makeShare({ enabled: true, audience: AgentShareAudience.org }),
455
+ ),
456
+ });
457
+ await renderOpenDialog(client);
244
458
 
245
459
  fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
246
460
  expect(
@@ -250,56 +464,46 @@ describe("ShareAgentDialog", () => {
250
464
  });
251
465
  });
252
466
 
253
- it("enabling sends the complete sharing block and notifies the host", async () => {
254
- const updateSharing = vi.fn().mockResolvedValue({});
467
+ it("enabling applies the complete spec and notifies the host", async () => {
468
+ const apply = vi.fn().mockResolvedValue({});
255
469
  const onSharingChanged = vi.fn();
256
- render(
257
- <Providers client={createMockStigmer({ updateSharing })}>
258
- <ShareAgentDialog
259
- open
260
- onOpenChange={() => {}}
261
- agent={makeAgent({
262
- enabled: false,
263
- allowedOrigins: ["https://example.com"],
264
- messages: { rateLimited: "Easy there." },
265
- })}
266
- buildShareUrl={buildShareUrl}
267
- onSharingChanged={onSharingChanged}
268
- />
269
- </Providers>,
270
- );
470
+ const client = createMockStigmer({
471
+ apply,
472
+ getByAgent: withShare(
473
+ makeShare({
474
+ enabled: false,
475
+ allowedOrigins: ["https://example.com"],
476
+ messages: { rateLimited: "Easy there." },
477
+ }),
478
+ ),
479
+ });
480
+ await renderOpenDialog(client, { onSharingChanged });
271
481
 
272
482
  fireEvent.click(screen.getByRole("switch", { hidden: true }));
273
483
 
274
- await waitFor(() => expect(updateSharing).toHaveBeenCalledTimes(1));
275
- const input = updateSharing.mock.calls[0][0] as UpdateAgentSharingInput;
276
- expect(input.resourceId).toBe("agt_1");
277
- expect(input.sharing?.enabled).toBe(true);
278
- // The RPC replaces spec.sharing wholesale — the toggle must carry the
484
+ await waitFor(() => expect(apply).toHaveBeenCalledTimes(1));
485
+ const input = apply.mock.calls[0][0] as AgentShareInput;
486
+ expect(input.enabled).toBe(true);
487
+ // Apply replaces the spec wholesale — the toggle must carry the
279
488
  // existing origins and messages or it would silently erase them.
280
- expect(input.sharing?.allowedOrigins).toEqual(["https://example.com"]);
281
- expect(input.sharing?.messages?.rateLimited).toBe("Easy there.");
489
+ expect(input.allowedOrigins).toEqual(["https://example.com"]);
490
+ expect(input.messages?.rateLimited).toBe("Easy there.");
282
491
  await waitFor(() => expect(onSharingChanged).toHaveBeenCalled());
283
492
  });
284
493
 
285
- it("adopts the server's returned sharing state after a commit", async () => {
286
- // Server echoes the update but with a normalized origin list.
287
- const updateSharing = vi.fn().mockResolvedValue(
288
- makeAgent({
494
+ it("adopts the server's returned share state after a commit", async () => {
495
+ // Server echoes the apply but with a normalized origin list.
496
+ const apply = vi.fn().mockResolvedValue(
497
+ makeShare({
289
498
  enabled: true,
290
499
  allowedOrigins: ["https://normalized.example.com"],
291
500
  }),
292
501
  );
293
- render(
294
- <Providers client={createMockStigmer({ updateSharing })}>
295
- <ShareAgentDialog
296
- open
297
- onOpenChange={() => {}}
298
- agent={makeAgent({ enabled: false })}
299
- buildShareUrl={buildShareUrl}
300
- />
301
- </Providers>,
302
- );
502
+ const client = createMockStigmer({
503
+ apply,
504
+ getByAgent: withShare(makeShare({ enabled: false })),
505
+ });
506
+ await renderOpenDialog(client);
303
507
 
304
508
  fireEvent.click(screen.getByRole("switch", { hidden: true }));
305
509
  await waitFor(() =>
@@ -312,18 +516,83 @@ describe("ShareAgentDialog", () => {
312
516
  expect(screen.getByText("https://normalized.example.com")).toBeTruthy();
313
517
  });
314
518
 
519
+ describe("Tool credentials", () => {
520
+ it("warns needs-credentials for a tool-using share without bindings", async () => {
521
+ const client = createMockStigmer({
522
+ getByAgent: withShare(makeShare({ enabled: true })),
523
+ });
524
+ await renderOpenDialog(client, { agent: makeAgent({ mcpUsages: true }) });
525
+
526
+ expect(
527
+ screen.getByText(/no credentials are bound to this share/i),
528
+ ).toBeTruthy();
529
+ });
530
+
531
+ it("binds an org-shared environment by applying the appended refs", async () => {
532
+ const apply = vi.fn().mockResolvedValue({});
533
+ const client = createMockStigmer({
534
+ apply,
535
+ getByAgent: withShare(makeShare({ enabled: true })),
536
+ environments: [orgSharedEnv("github-creds", "GitHub Creds")],
537
+ });
538
+ await renderOpenDialog(client, { agent: makeAgent({ mcpUsages: true }) });
539
+
540
+ // The section is expanded by default for tool-using agents.
541
+ const picker = await screen.findByLabelText("Add environment", {
542
+ // The select is visually present inside the collapsible.
543
+ selector: "select",
544
+ });
545
+ fireEvent.change(picker, { target: { value: "github-creds" } });
546
+
547
+ await waitFor(() => expect(apply).toHaveBeenCalledTimes(1));
548
+ const input = apply.mock.calls[0][0] as AgentShareInput;
549
+ expect(input.environmentRefs).toEqual([
550
+ { org: "acme", slug: "github-creds" },
551
+ ]);
552
+ // The binding rides the full spec — nothing else changes.
553
+ expect(input.enabled).toBe(true);
554
+ });
555
+
556
+ it("offers only org-shared environments in the picker", async () => {
557
+ const privateEnv = {
558
+ metadata: {
559
+ slug: "personal-creds",
560
+ name: "Personal Creds",
561
+ visibility: ApiResourceVisibility.visibility_private,
562
+ },
563
+ spec: { description: "" },
564
+ };
565
+ const client = createMockStigmer({
566
+ getByAgent: withShare(makeShare({ enabled: true })),
567
+ environments: [orgSharedEnv("github-creds", "GitHub Creds"), privateEnv],
568
+ });
569
+ await renderOpenDialog(client, { agent: makeAgent({ mcpUsages: true }) });
570
+
571
+ await screen.findByLabelText("Add environment", { selector: "select" });
572
+ // Private environments are guest-unusable (the runtime merge skips
573
+ // them) — offering them would bind credentials that never apply.
574
+ expect(screen.getByText("GitHub Creds")).toBeTruthy();
575
+ expect(screen.queryByText("Personal Creds")).toBeNull();
576
+ });
577
+
578
+ it("hides the section entirely for org-audience shares", async () => {
579
+ const client = createMockStigmer({
580
+ getByAgent: withShare(
581
+ makeShare({ enabled: true, audience: AgentShareAudience.org }),
582
+ ),
583
+ });
584
+ await renderOpenDialog(client, { agent: makeAgent({ mcpUsages: true }) });
585
+
586
+ expect(screen.queryByText("Tool credentials")).toBeNull();
587
+ });
588
+ });
589
+
315
590
  describe("Embed tab", () => {
316
- it("shows the one-line script snippet: loader from the app origin + <stigmer-agent>", () => {
317
- render(
318
- <Providers client={createMockStigmer()}>
319
- <ShareAgentDialog
320
- open
321
- onOpenChange={() => {}}
322
- agent={makeAgent({ enabled: true })}
323
- buildShareUrl={buildShareUrl}
324
- />
325
- </Providers>,
326
- );
591
+ it("shows the one-line script snippet: loader from the app origin + <stigmer-agent>", async () => {
592
+ const client = createMockStigmer({
593
+ getByAgent: withShare(makeShare({ enabled: true })),
594
+ });
595
+ await renderOpenDialog(client);
327
596
 
328
597
  fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
329
598
  // The loader must be served from the SAME origin as the share URL —
@@ -340,17 +609,11 @@ describe("ShareAgentDialog", () => {
340
609
  ).toBeTruthy();
341
610
  });
342
611
 
343
- it("keeps the iframe snippet available as the collapsed no-JavaScript alternative", () => {
344
- render(
345
- <Providers client={createMockStigmer()}>
346
- <ShareAgentDialog
347
- open
348
- onOpenChange={() => {}}
349
- agent={makeAgent({ enabled: true })}
350
- buildShareUrl={buildShareUrl}
351
- />
352
- </Providers>,
353
- );
612
+ it("keeps the iframe snippet available as the collapsed no-JavaScript alternative", async () => {
613
+ const client = createMockStigmer({
614
+ getByAgent: withShare(makeShare({ enabled: true })),
615
+ });
616
+ await renderOpenDialog(client);
354
617
 
355
618
  fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
356
619
  // Collapsed by default — the script snippet is the primary path.
@@ -370,17 +633,12 @@ describe("ShareAgentDialog", () => {
370
633
  });
371
634
 
372
635
  it("rejects an invalid origin without calling the RPC", async () => {
373
- const updateSharing = vi.fn();
374
- render(
375
- <Providers client={createMockStigmer({ updateSharing })}>
376
- <ShareAgentDialog
377
- open
378
- onOpenChange={() => {}}
379
- agent={makeAgent({ enabled: true })}
380
- buildShareUrl={buildShareUrl}
381
- />
382
- </Providers>,
383
- );
636
+ const apply = vi.fn();
637
+ const client = createMockStigmer({
638
+ apply,
639
+ getByAgent: withShare(makeShare({ enabled: true })),
640
+ });
641
+ await renderOpenDialog(client);
384
642
 
385
643
  fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
386
644
  const input = screen.getByLabelText("Add allowed origin");
@@ -390,24 +648,21 @@ describe("ShareAgentDialog", () => {
390
648
  fireEvent.click(screen.getByText("Add"));
391
649
 
392
650
  expect(await screen.findByRole("alert", { hidden: true })).toBeTruthy();
393
- expect(updateSharing).not.toHaveBeenCalled();
394
- });
395
-
396
- it("adds a valid origin by committing the appended list", async () => {
397
- const updateSharing = vi.fn().mockResolvedValue({});
398
- render(
399
- <Providers client={createMockStigmer({ updateSharing })}>
400
- <ShareAgentDialog
401
- open
402
- onOpenChange={() => {}}
403
- agent={makeAgent({
404
- enabled: true,
405
- allowedOrigins: ["https://existing.example.com"],
406
- })}
407
- buildShareUrl={buildShareUrl}
408
- />
409
- </Providers>,
410
- );
651
+ expect(apply).not.toHaveBeenCalled();
652
+ });
653
+
654
+ it("adds a valid origin by applying the appended list", async () => {
655
+ const apply = vi.fn().mockResolvedValue({});
656
+ const client = createMockStigmer({
657
+ apply,
658
+ getByAgent: withShare(
659
+ makeShare({
660
+ enabled: true,
661
+ allowedOrigins: ["https://existing.example.com"],
662
+ }),
663
+ ),
664
+ });
665
+ await renderOpenDialog(client);
411
666
 
412
667
  fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
413
668
  fireEvent.change(screen.getByLabelText("Add allowed origin"), {
@@ -415,31 +670,28 @@ describe("ShareAgentDialog", () => {
415
670
  });
416
671
  fireEvent.click(screen.getByText("Add"));
417
672
 
418
- await waitFor(() => expect(updateSharing).toHaveBeenCalledTimes(1));
419
- const input = updateSharing.mock.calls[0][0] as UpdateAgentSharingInput;
420
- expect(input.sharing?.allowedOrigins).toEqual([
673
+ await waitFor(() => expect(apply).toHaveBeenCalledTimes(1));
674
+ const input = apply.mock.calls[0][0] as AgentShareInput;
675
+ expect(input.allowedOrigins).toEqual([
421
676
  "https://existing.example.com",
422
677
  "https://new.example.com",
423
678
  ]);
424
679
  // Enabled state rides along untouched.
425
- expect(input.sharing?.enabled).toBe(true);
680
+ expect(input.enabled).toBe(true);
426
681
  });
427
682
 
428
683
  it("rejects a duplicate origin", async () => {
429
- const updateSharing = vi.fn();
430
- render(
431
- <Providers client={createMockStigmer({ updateSharing })}>
432
- <ShareAgentDialog
433
- open
434
- onOpenChange={() => {}}
435
- agent={makeAgent({
436
- enabled: true,
437
- allowedOrigins: ["https://example.com"],
438
- })}
439
- buildShareUrl={buildShareUrl}
440
- />
441
- </Providers>,
442
- );
684
+ const apply = vi.fn();
685
+ const client = createMockStigmer({
686
+ apply,
687
+ getByAgent: withShare(
688
+ makeShare({
689
+ enabled: true,
690
+ allowedOrigins: ["https://example.com"],
691
+ }),
692
+ ),
693
+ });
694
+ await renderOpenDialog(client);
443
695
 
444
696
  fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
445
697
  fireEvent.change(screen.getByLabelText("Add allowed origin"), {
@@ -448,50 +700,44 @@ describe("ShareAgentDialog", () => {
448
700
  fireEvent.click(screen.getByText("Add"));
449
701
 
450
702
  expect(await screen.findByRole("alert", { hidden: true })).toBeTruthy();
451
- expect(updateSharing).not.toHaveBeenCalled();
452
- });
453
-
454
- it("removes an origin by committing the filtered list", async () => {
455
- const updateSharing = vi.fn().mockResolvedValue({});
456
- render(
457
- <Providers client={createMockStigmer({ updateSharing })}>
458
- <ShareAgentDialog
459
- open
460
- onOpenChange={() => {}}
461
- agent={makeAgent({
462
- enabled: true,
463
- allowedOrigins: ["https://a.example.com", "https://b.example.com"],
464
- })}
465
- buildShareUrl={buildShareUrl}
466
- />
467
- </Providers>,
468
- );
703
+ expect(apply).not.toHaveBeenCalled();
704
+ });
705
+
706
+ it("removes an origin by applying the filtered list", async () => {
707
+ const apply = vi.fn().mockResolvedValue({});
708
+ const client = createMockStigmer({
709
+ apply,
710
+ getByAgent: withShare(
711
+ makeShare({
712
+ enabled: true,
713
+ allowedOrigins: ["https://a.example.com", "https://b.example.com"],
714
+ }),
715
+ ),
716
+ });
717
+ await renderOpenDialog(client);
469
718
 
470
719
  fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
471
720
  fireEvent.click(screen.getByLabelText("Remove https://a.example.com"));
472
721
 
473
- await waitFor(() => expect(updateSharing).toHaveBeenCalledTimes(1));
474
- const input = updateSharing.mock.calls[0][0] as UpdateAgentSharingInput;
475
- expect(input.sharing?.allowedOrigins).toEqual(["https://b.example.com"]);
722
+ await waitFor(() => expect(apply).toHaveBeenCalledTimes(1));
723
+ const input = apply.mock.calls[0][0] as AgentShareInput;
724
+ expect(input.allowedOrigins).toEqual(["https://b.example.com"]);
476
725
  });
477
726
  });
478
727
 
479
728
  describe("visitor messages", () => {
480
- it("saves edited messages as part of the complete block", async () => {
481
- const updateSharing = vi.fn().mockResolvedValue({});
482
- render(
483
- <Providers client={createMockStigmer({ updateSharing })}>
484
- <ShareAgentDialog
485
- open
486
- onOpenChange={() => {}}
487
- agent={makeAgent({
488
- enabled: true,
489
- allowedOrigins: ["https://example.com"],
490
- })}
491
- buildShareUrl={buildShareUrl}
492
- />
493
- </Providers>,
494
- );
729
+ it("saves edited messages as part of the complete spec", async () => {
730
+ const apply = vi.fn().mockResolvedValue({});
731
+ const client = createMockStigmer({
732
+ apply,
733
+ getByAgent: withShare(
734
+ makeShare({
735
+ enabled: true,
736
+ allowedOrigins: ["https://example.com"],
737
+ }),
738
+ ),
739
+ });
740
+ await renderOpenDialog(client);
495
741
 
496
742
  fireEvent.click(screen.getByText("Customize visitor messages"));
497
743
  fireEvent.change(screen.getByLabelText(/Rate limited/), {
@@ -499,24 +745,18 @@ describe("ShareAgentDialog", () => {
499
745
  });
500
746
  fireEvent.click(screen.getByText("Save messages"));
501
747
 
502
- await waitFor(() => expect(updateSharing).toHaveBeenCalledTimes(1));
503
- const input = updateSharing.mock.calls[0][0] as UpdateAgentSharingInput;
504
- expect(input.sharing?.messages?.rateLimited).toBe("Please slow down.");
505
- expect(input.sharing?.enabled).toBe(true);
506
- expect(input.sharing?.allowedOrigins).toEqual(["https://example.com"]);
507
- });
508
-
509
- it("caps each message at 300 characters", () => {
510
- render(
511
- <Providers client={createMockStigmer()}>
512
- <ShareAgentDialog
513
- open
514
- onOpenChange={() => {}}
515
- agent={makeAgent({ enabled: true })}
516
- buildShareUrl={buildShareUrl}
517
- />
518
- </Providers>,
519
- );
748
+ await waitFor(() => expect(apply).toHaveBeenCalledTimes(1));
749
+ const input = apply.mock.calls[0][0] as AgentShareInput;
750
+ expect(input.messages?.rateLimited).toBe("Please slow down.");
751
+ expect(input.enabled).toBe(true);
752
+ expect(input.allowedOrigins).toEqual(["https://example.com"]);
753
+ });
754
+
755
+ it("caps each message at 300 characters", async () => {
756
+ const client = createMockStigmer({
757
+ getByAgent: withShare(makeShare({ enabled: true })),
758
+ });
759
+ await renderOpenDialog(client);
520
760
 
521
761
  fireEvent.click(screen.getByText("Customize visitor messages"));
522
762
  const field = screen.getByLabelText(/Rate limited/) as HTMLTextAreaElement;
@@ -530,19 +770,11 @@ describe("ShareAgentDialog", () => {
530
770
  const getOrCreateBillingAccount = vi.fn().mockResolvedValue({
531
771
  balance: { availableMicros: BigInt(12_500_000) },
532
772
  });
533
- render(
534
- <Providers
535
- client={createMockStigmer({ getOrCreateBillingAccount })}
536
- mode="cloud"
537
- >
538
- <ShareAgentDialog
539
- open
540
- onOpenChange={() => {}}
541
- agent={makeAgent({ enabled: true })}
542
- buildShareUrl={buildShareUrl}
543
- />
544
- </Providers>,
545
- );
773
+ const client = createMockStigmer({
774
+ getOrCreateBillingAccount,
775
+ getByAgent: withShare(makeShare({ enabled: true })),
776
+ });
777
+ await renderOpenDialog(client, { mode: "cloud" });
546
778
 
547
779
  expect(screen.getByText(/Visitors chat on/)).toBeTruthy();
548
780
  await waitFor(() =>
@@ -551,21 +783,13 @@ describe("ShareAgentDialog", () => {
551
783
  expect(getOrCreateBillingAccount).toHaveBeenCalledWith("acme");
552
784
  });
553
785
 
554
- it("degrades to the who-pays line alone in local mode (no billing fetch)", () => {
786
+ it("degrades to the who-pays line alone in local mode (no billing fetch)", async () => {
555
787
  const getOrCreateBillingAccount = vi.fn();
556
- render(
557
- <Providers
558
- client={createMockStigmer({ getOrCreateBillingAccount })}
559
- mode="local"
560
- >
561
- <ShareAgentDialog
562
- open
563
- onOpenChange={() => {}}
564
- agent={makeAgent({ enabled: true })}
565
- buildShareUrl={buildShareUrl}
566
- />
567
- </Providers>,
568
- );
788
+ const client = createMockStigmer({
789
+ getOrCreateBillingAccount,
790
+ getByAgent: withShare(makeShare({ enabled: true })),
791
+ });
792
+ await renderOpenDialog(client, { mode: "local" });
569
793
 
570
794
  expect(screen.getByText(/Visitors chat on/)).toBeTruthy();
571
795
  expect(screen.queryByText(/available/)).toBeNull();
@@ -576,19 +800,11 @@ describe("ShareAgentDialog", () => {
576
800
  const getOrCreateBillingAccount = vi
577
801
  .fn()
578
802
  .mockRejectedValue(new Error("billing unavailable"));
579
- render(
580
- <Providers
581
- client={createMockStigmer({ getOrCreateBillingAccount })}
582
- mode="cloud"
583
- >
584
- <ShareAgentDialog
585
- open
586
- onOpenChange={() => {}}
587
- agent={makeAgent({ enabled: true })}
588
- buildShareUrl={buildShareUrl}
589
- />
590
- </Providers>,
591
- );
803
+ const client = createMockStigmer({
804
+ getOrCreateBillingAccount,
805
+ getByAgent: withShare(makeShare({ enabled: true })),
806
+ });
807
+ await renderOpenDialog(client, { mode: "cloud" });
592
808
 
593
809
  await waitFor(() => expect(getOrCreateBillingAccount).toHaveBeenCalled());
594
810
  expect(screen.getByText(/Visitors chat on/)).toBeTruthy();
@@ -598,17 +814,11 @@ describe("ShareAgentDialog", () => {
598
814
  });
599
815
 
600
816
  describe("Developer tab", () => {
601
- it("shows the platform client snippet and docs link", () => {
602
- render(
603
- <Providers client={createMockStigmer()}>
604
- <ShareAgentDialog
605
- open
606
- onOpenChange={() => {}}
607
- agent={makeAgent({ enabled: true })}
608
- buildShareUrl={buildShareUrl}
609
- />
610
- </Providers>,
611
- );
817
+ it("shows the platform client snippet and docs link", async () => {
818
+ const client = createMockStigmer({
819
+ getByAgent: withShare(makeShare({ enabled: true })),
820
+ });
821
+ await renderOpenDialog(client);
612
822
 
613
823
  fireEvent.click(screen.getByRole("tab", { name: /Developer/, hidden: true }));
614
824
  expect(screen.getByText(/createPlatformClientAuth/)).toBeTruthy();
@@ -617,37 +827,24 @@ describe("ShareAgentDialog", () => {
617
827
  });
618
828
  });
619
829
 
620
- it("renders in-flow without showModal when modal is false", () => {
830
+ it("renders in-flow without showModal when modal is false", async () => {
621
831
  const showModal = vi.spyOn(HTMLDialogElement.prototype, "showModal");
622
- render(
623
- <Providers client={createMockStigmer()}>
624
- <ShareAgentDialog
625
- open
626
- onOpenChange={() => {}}
627
- agent={makeAgent({ enabled: true })}
628
- buildShareUrl={buildShareUrl}
629
- modal={false}
630
- />
631
- </Providers>,
632
- );
832
+ const client = createMockStigmer({
833
+ getByAgent: withShare(makeShare({ enabled: true })),
834
+ });
835
+ await renderOpenDialog(client, { modal: false });
633
836
 
634
837
  expect(screen.getByText("Share")).toBeTruthy();
635
838
  expect(showModal).not.toHaveBeenCalled();
636
839
  showModal.mockRestore();
637
840
  });
638
841
 
639
- it("requests close via Done and the close affordance", () => {
842
+ it("requests close via Done and the close affordance", async () => {
640
843
  const onOpenChange = vi.fn();
641
- render(
642
- <Providers client={createMockStigmer()}>
643
- <ShareAgentDialog
644
- open
645
- onOpenChange={onOpenChange}
646
- agent={makeAgent({ enabled: true })}
647
- buildShareUrl={buildShareUrl}
648
- />
649
- </Providers>,
650
- );
844
+ const client = createMockStigmer({
845
+ getByAgent: withShare(makeShare({ enabled: true })),
846
+ });
847
+ await renderOpenDialog(client, { onOpenChange });
651
848
 
652
849
  screen.getByText("Done").click();
653
850
  expect(onOpenChange).toHaveBeenCalledWith(false);
@@ -658,17 +855,11 @@ describe("ShareAgentDialog", () => {
658
855
  });
659
856
 
660
857
  describe("Reset link (rotatable share token)", () => {
661
- it("appends the status token to the shown link and embed snippet", () => {
662
- render(
663
- <Providers client={createMockStigmer()}>
664
- <ShareAgentDialog
665
- open
666
- onOpenChange={() => {}}
667
- agent={makeAgent({ enabled: true }, "tok123")}
668
- buildShareUrl={buildShareUrl}
669
- />
670
- </Providers>,
671
- );
858
+ it("appends the status token to the shown link and embed snippet", async () => {
859
+ const client = createMockStigmer({
860
+ getByAgent: withShare(makeShare({ enabled: true }, "tok123")),
861
+ });
862
+ await renderOpenDialog(client);
672
863
 
673
864
  expect(
674
865
  screen.getByText("https://app.example.com/chat/acme/support-agent?k=tok123"),
@@ -679,22 +870,15 @@ describe("ShareAgentDialog", () => {
679
870
  });
680
871
 
681
872
  it("rotates on Reset link, adopts the fresh token, and notifies the host", async () => {
682
- const rotateShareLink = vi.fn().mockResolvedValue({
683
- metadata: { id: "agt_1" },
684
- status: { shareLinkToken: "fresh-token" },
685
- });
873
+ const rotateShareLink = vi
874
+ .fn()
875
+ .mockResolvedValue(makeShare({ enabled: true }, "fresh-token"));
686
876
  const onSharingChanged = vi.fn();
687
- render(
688
- <Providers client={createMockStigmer({ rotateShareLink })}>
689
- <ShareAgentDialog
690
- open
691
- onOpenChange={() => {}}
692
- agent={makeAgent({ enabled: true })}
693
- buildShareUrl={buildShareUrl}
694
- onSharingChanged={onSharingChanged}
695
- />
696
- </Providers>,
697
- );
877
+ const client = createMockStigmer({
878
+ rotateShareLink,
879
+ getByAgent: withShare(makeShare({ enabled: true })),
880
+ });
881
+ await renderOpenDialog(client, { onSharingChanged });
698
882
 
699
883
  // A plain link shows no token before the reset.
700
884
  expect(
@@ -711,26 +895,23 @@ describe("ShareAgentDialog", () => {
711
895
  ).toBeTruthy(),
712
896
  );
713
897
  expect(rotateShareLink).toHaveBeenCalledTimes(1);
898
+ // The rotation targets the loaded share by its own id.
899
+ expect(
900
+ (rotateShareLink.mock.calls[0][0] as { resourceId: string }).resourceId,
901
+ ).toBe("ash_1");
714
902
  expect(onSharingChanged).toHaveBeenCalled();
715
903
  });
716
904
 
717
- it("hides the Reset control and the token for org-members-only shares", () => {
718
- render(
719
- <Providers client={createMockStigmer()}>
720
- <ShareAgentDialog
721
- open
722
- onOpenChange={() => {}}
723
- agent={makeAgent(
724
- {
725
- enabled: true,
726
- audience: AgentSharingAudience.org,
727
- },
728
- "tok123",
729
- )}
730
- buildShareUrl={buildShareUrl}
731
- />
732
- </Providers>,
733
- );
905
+ it("hides the Reset control and the token for org-members-only shares", async () => {
906
+ const client = createMockStigmer({
907
+ getByAgent: withShare(
908
+ makeShare(
909
+ { enabled: true, audience: AgentShareAudience.org },
910
+ "tok123",
911
+ ),
912
+ ),
913
+ });
914
+ await renderOpenDialog(client);
734
915
 
735
916
  // Org access is gated by membership, not the link token: the member
736
917
  // link stays clean and the Reset lever is not offered.