@stigmer/react 3.1.12 → 3.1.14

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 (63) hide show
  1. package/agent/AgentDetailView.d.ts +11 -3
  2. package/agent/AgentDetailView.d.ts.map +1 -1
  3. package/agent/AgentDetailView.js +22 -17
  4. package/agent/AgentDetailView.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/AgentShareList.d.ts +44 -0
  11. package/sharing/AgentShareList.d.ts.map +1 -0
  12. package/sharing/AgentShareList.js +149 -0
  13. package/sharing/AgentShareList.js.map +1 -0
  14. package/sharing/ShareAgentDialog.d.ts +38 -15
  15. package/sharing/ShareAgentDialog.d.ts.map +1 -1
  16. package/sharing/ShareAgentDialog.js +135 -57
  17. package/sharing/ShareAgentDialog.js.map +1 -1
  18. package/sharing/index.d.ts +10 -6
  19. package/sharing/index.d.ts.map +1 -1
  20. package/sharing/index.js +5 -3
  21. package/sharing/index.js.map +1 -1
  22. package/sharing/useAgentShares.d.ts +42 -0
  23. package/sharing/useAgentShares.d.ts.map +1 -0
  24. package/sharing/useAgentShares.js +38 -0
  25. package/sharing/useAgentShares.js.map +1 -0
  26. package/sharing/useCanCreateAgentShare.d.ts +43 -0
  27. package/sharing/useCanCreateAgentShare.d.ts.map +1 -0
  28. package/sharing/useCanCreateAgentShare.js +44 -0
  29. package/sharing/useCanCreateAgentShare.js.map +1 -0
  30. package/sharing/useDeleteAgentShare.d.ts +42 -0
  31. package/sharing/useDeleteAgentShare.d.ts.map +1 -0
  32. package/sharing/useDeleteAgentShare.js +45 -0
  33. package/sharing/useDeleteAgentShare.js.map +1 -0
  34. package/sharing/useSaveAgentShare.d.ts +45 -14
  35. package/sharing/useSaveAgentShare.d.ts.map +1 -1
  36. package/sharing/useSaveAgentShare.js +59 -19
  37. package/sharing/useSaveAgentShare.js.map +1 -1
  38. package/src/agent/AgentDetailView.tsx +37 -20
  39. package/src/index.ts +11 -6
  40. package/src/sharing/AgentShareList.tsx +507 -0
  41. package/src/sharing/ShareAgentDialog.tsx +325 -95
  42. package/src/sharing/__tests__/AgentShareList.test.tsx +388 -0
  43. package/src/sharing/__tests__/ShareAgentDialog.test.tsx +333 -286
  44. package/src/sharing/__tests__/{useAgentShare.test.tsx → useAgentShares.test.tsx} +47 -51
  45. package/src/sharing/__tests__/useCanCreateAgentShare.test.tsx +190 -0
  46. package/src/sharing/__tests__/useSaveAgentShare.test.tsx +48 -0
  47. package/src/sharing/index.ts +10 -7
  48. package/src/sharing/useAgentShares.ts +68 -0
  49. package/src/sharing/useCanCreateAgentShare.ts +74 -0
  50. package/src/sharing/useDeleteAgentShare.ts +73 -0
  51. package/src/sharing/useSaveAgentShare.ts +77 -19
  52. package/styles.css +1 -1
  53. package/sharing/useAgentShare.d.ts +0 -43
  54. package/sharing/useAgentShare.d.ts.map +0 -1
  55. package/sharing/useAgentShare.js +0 -54
  56. package/sharing/useAgentShare.js.map +0 -1
  57. package/sharing/useShareAgent.d.ts +0 -69
  58. package/sharing/useShareAgent.d.ts.map +0 -1
  59. package/sharing/useShareAgent.js +0 -42
  60. package/sharing/useShareAgent.js.map +0 -1
  61. package/src/sharing/__tests__/useShareAgent.test.tsx +0 -131
  62. package/src/sharing/useAgentShare.ts +0 -91
  63. package/src/sharing/useShareAgent.tsx +0 -107
@@ -1,9 +1,10 @@
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 { Code } from "@connectrpc/connect";
4
5
  import { AgentShareAudience } from "@stigmer/protos/ai/stigmer/agentic/agentshare/v1/spec_pb";
5
6
  import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
6
- import type { AgentShareInput } from "@stigmer/sdk";
7
+ import { StigmerError, type AgentShareInput } from "@stigmer/sdk";
7
8
  import { StigmerContext } from "../../context";
8
9
  import { FetchCacheContext } from "../../internal/FetchCacheProvider";
9
10
  import { DeploymentModeContext } from "../../deployment-mode";
@@ -27,7 +28,6 @@ beforeAll(() => {
27
28
  afterEach(cleanup);
28
29
 
29
30
  interface MockOverrides {
30
- getByAgent?: (input: unknown) => Promise<unknown>;
31
31
  apply?: (input: AgentShareInput) => Promise<unknown>;
32
32
  rotateShareLink?: (input: unknown) => Promise<unknown>;
33
33
  getOrCreateBillingAccount?: (orgId: string) => Promise<unknown>;
@@ -37,9 +37,6 @@ interface MockOverrides {
37
37
  function createMockStigmer(overrides: MockOverrides = {}) {
38
38
  return {
39
39
  agentShare: {
40
- getByAgent:
41
- overrides.getByAgent ??
42
- vi.fn().mockResolvedValue({ totalCount: 0, items: [] }),
43
40
  apply: overrides.apply ?? vi.fn().mockResolvedValue({}),
44
41
  rotateShareLink:
45
42
  overrides.rotateShareLink ?? vi.fn().mockResolvedValue({}),
@@ -129,11 +126,7 @@ function makeShare(
129
126
  },
130
127
  spec: { agentRef: { org: "acme", slug: "support-agent" }, ...spec },
131
128
  ...(shareLinkToken !== undefined ? { status: { shareLinkToken } } : {}),
132
- };
133
- }
134
-
135
- function withShare(share: unknown) {
136
- return vi.fn().mockResolvedValue({ totalCount: 1, items: [share] });
129
+ } as never;
137
130
  }
138
131
 
139
132
  function orgSharedEnv(slug: string, name?: string) {
@@ -150,8 +143,8 @@ function orgSharedEnv(slug: string, name?: string) {
150
143
  const buildShareUrl = (org: string, slug: string) =>
151
144
  `https://app.example.com/chat/${org}/${slug}`;
152
145
 
153
- /** Render the open dialog and wait for the share load to settle. */
154
- async function renderOpenDialog(
146
+ /** Render the dialog open. Pass `share` for edit mode; omit for create mode. */
147
+ function renderOpenDialog(
155
148
  client: unknown,
156
149
  props?: Partial<Parameters<typeof ShareAgentDialog>[0]> & { mode?: "cloud" | "local" },
157
150
  ) {
@@ -167,96 +160,31 @@ async function renderOpenDialog(
167
160
  />
168
161
  </Providers>,
169
162
  );
170
- await waitFor(() =>
171
- expect(screen.queryByLabelText("Loading sharing settings")).toBeNull(),
172
- );
173
163
  }
174
164
 
175
165
  describe("ShareAgentDialog", () => {
176
- it("mounts no body while closed (share and billing fetches stay lazy)", () => {
177
- const getByAgent = vi.fn();
166
+ it("mounts no body while closed (billing fetch stays lazy)", () => {
178
167
  const getOrCreateBillingAccount = vi.fn();
179
168
  render(
180
- <Providers
181
- client={createMockStigmer({ getByAgent, getOrCreateBillingAccount })}
182
- >
169
+ <Providers client={createMockStigmer({ getOrCreateBillingAccount })}>
183
170
  <ShareAgentDialog
184
171
  open={false}
185
172
  onOpenChange={() => {}}
186
173
  agent={makeAgent()}
174
+ share={makeShare({ enabled: true })}
187
175
  buildShareUrl={buildShareUrl}
188
176
  />
189
177
  </Providers>,
190
178
  );
191
179
 
192
180
  expect(screen.queryByText("Share")).toBeNull();
193
- expect(getByAgent).not.toHaveBeenCalled();
194
181
  expect(getOrCreateBillingAccount).not.toHaveBeenCalled();
195
182
  });
196
183
 
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
- );
204
- render(
205
- <Providers client={createMockStigmer({ getByAgent })}>
206
- <ShareAgentDialog
207
- open
208
- onOpenChange={() => {}}
209
- agent={makeAgent()}
210
- buildShareUrl={buildShareUrl}
211
- />
212
- </Providers>,
213
- );
214
-
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
- );
222
- });
223
-
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] });
230
- render(
231
- <Providers client={createMockStigmer({ getByAgent })}>
232
- <ShareAgentDialog
233
- open
234
- onOpenChange={() => {}}
235
- agent={makeAgent()}
236
- buildShareUrl={buildShareUrl}
237
- />
238
- </Providers>,
239
- );
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 })),
184
+ it("renders header, toggle, and the share link from buildShareUrl", () => {
185
+ renderOpenDialog(createMockStigmer(), {
186
+ share: makeShare({ enabled: true }),
258
187
  });
259
- await renderOpenDialog(client);
260
188
 
261
189
  expect(screen.getByText("Share")).toBeTruthy();
262
190
  expect(screen.getByText("Support Agent")).toBeTruthy();
@@ -266,77 +194,190 @@ describe("ShareAgentDialog", () => {
266
194
  ).toBeTruthy();
267
195
  });
268
196
 
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.
197
+ it("builds the link from the SHARE's slug when it differs from the agent's", () => {
198
+ // A renamed share: the hosted URL lives at the share's slug.
271
199
  const renamed = {
272
- ...makeShare({ enabled: true }),
200
+ ...(makeShare({ enabled: true }) as Record<string, unknown>),
273
201
  metadata: { id: "ash_1", org: "acme", slug: "help-desk", name: "Help Desk" },
274
- };
275
- const client = createMockStigmer({ getByAgent: withShare(renamed) });
276
- await renderOpenDialog(client);
202
+ } as never;
203
+ renderOpenDialog(createMockStigmer(), { share: renamed });
277
204
 
278
205
  expect(
279
206
  screen.getByText("https://app.example.com/chat/acme/help-desk"),
280
207
  ).toBeTruthy();
281
208
  });
282
209
 
283
- it("falls back to the relative /chat path when buildShareUrl is omitted", async () => {
284
- const client = createMockStigmer({
285
- getByAgent: withShare(makeShare({ enabled: true })),
210
+ it("falls back to the relative /chat path when buildShareUrl is omitted", () => {
211
+ renderOpenDialog(createMockStigmer(), {
212
+ share: makeShare({ enabled: true }),
213
+ buildShareUrl: undefined,
286
214
  });
287
- await renderOpenDialog(client, { buildShareUrl: undefined });
288
215
 
289
216
  expect(screen.getByText("/chat/acme/support-agent")).toBeTruthy();
290
217
  });
291
218
 
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);
219
+ describe("create mode (no share prop)", () => {
220
+ it("renders the create step with identity prefilled from the agent", () => {
221
+ renderOpenDialog(createMockStigmer());
296
222
 
297
223
  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),
224
+ screen.getByRole("heading", { name: "Create share", hidden: true }),
307
225
  ).toBeTruthy();
226
+ expect(
227
+ (screen.getByLabelText("Name", { selector: "input" }) as HTMLInputElement).value,
228
+ ).toBe("Support Agent");
229
+ expect(
230
+ (screen.getByLabelText("Slug", { selector: "input" }) as HTMLInputElement).value,
231
+ ).toBe("support-agent");
232
+ // No channel exists yet — no switch, no link, and the footer offers
233
+ // Cancel rather than Done.
234
+ expect(screen.queryByRole("switch", { hidden: true })).toBeNull();
235
+ expect(screen.getByText("Cancel")).toBeTruthy();
308
236
  });
309
237
 
310
- it("first enable creates the canonical share via apply", async () => {
238
+ it("auto-derives the slug from the name until the slug is edited", () => {
239
+ renderOpenDialog(createMockStigmer());
240
+
241
+ const name = screen.getByLabelText("Name", { selector: "input" });
242
+ const slug = screen.getByLabelText("Slug", { selector: "input" }) as HTMLInputElement;
243
+
244
+ fireEvent.change(name, { target: { value: "Docs Site Widget" } });
245
+ expect(slug.value).toBe("docs-site-widget");
246
+
247
+ fireEvent.change(slug, { target: { value: "docs-widget" } });
248
+ fireEvent.change(name, { target: { value: "Renamed Again" } });
249
+ // Touched slug stays put.
250
+ expect(slug.value).toBe("docs-widget");
251
+ });
252
+
253
+ it("creates the share live with the chosen identity, then becomes its editor", async () => {
311
254
  const apply = vi.fn().mockResolvedValue(makeShare({ enabled: true }));
312
- const client = createMockStigmer({ apply });
313
- await renderOpenDialog(client);
255
+ renderOpenDialog(createMockStigmer({ apply }));
314
256
 
315
- fireEvent.click(screen.getByRole("switch", { hidden: true }));
257
+ fireEvent.click(screen.getByRole("button", { name: "Create share", hidden: true }));
316
258
 
317
259
  await waitFor(() => expect(apply).toHaveBeenCalledTimes(1));
318
260
  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
261
  expect(input.org).toBe("acme");
322
262
  expect(input.slug).toBe("support-agent");
263
+ expect(input.name).toBe("Support Agent");
323
264
  expect(input.agentRef).toEqual({ org: "acme", slug: "support-agent" });
265
+ // One intent-click yields a live link: created enabled, public.
324
266
  expect(input.enabled).toBe(true);
267
+ expect(input.audience).toBe(AgentShareAudience.public);
268
+
269
+ // The dialog transitions to the editor on the created share.
270
+ await waitFor(() =>
271
+ expect(
272
+ screen.getByText("https://app.example.com/chat/acme/support-agent"),
273
+ ).toBeTruthy(),
274
+ );
275
+ expect(screen.getByText("Done")).toBeTruthy();
276
+ });
277
+
278
+ it("pins an (org, slug) collision to the slug field with a pick-another-slug remedy", async () => {
279
+ const apply = vi
280
+ .fn()
281
+ .mockRejectedValue(
282
+ new StigmerError("already-exists", "duplicate slug", Code.AlreadyExists),
283
+ );
284
+ renderOpenDialog(createMockStigmer({ apply }));
285
+
286
+ fireEvent.click(screen.getByRole("button", { name: "Create share", hidden: true }));
287
+
288
+ expect(
289
+ await screen.findByText(/pick a different slug/i),
290
+ ).toBeTruthy();
291
+ // Still on the create step — nothing was created.
292
+ expect(screen.queryByRole("switch", { hidden: true })).toBeNull();
293
+
294
+ // Editing the slug clears the collision so the user can retry.
295
+ fireEvent.change(screen.getByLabelText("Slug", { selector: "input" }), {
296
+ target: { value: "support-agent-2" },
297
+ });
298
+ expect(screen.queryByText(/pick a different slug/i)).toBeNull();
299
+ });
300
+
301
+ it("surfaces other server refusals verbatim (e.g. non-public dependencies)", async () => {
302
+ const apply = vi
303
+ .fn()
304
+ .mockRejectedValue(
305
+ new StigmerError(
306
+ "failed-precondition",
307
+ "cannot share acme/support-agent across organizations: it references resources that are not public: skill acme/internal-kb",
308
+ Code.FailedPrecondition,
309
+ ),
310
+ );
311
+ renderOpenDialog(createMockStigmer({ apply }), { shareOrg: "consumer-org" });
312
+
313
+ fireEvent.click(screen.getByRole("button", { name: "Create share", hidden: true }));
314
+
315
+ expect(
316
+ await screen.findByText(/resources that are not public/i),
317
+ ).toBeTruthy();
318
+ });
319
+
320
+ it("cross-org create: qualifies the agent, names the paying org, creates in the viewer's org", async () => {
321
+ const apply = vi.fn().mockResolvedValue({
322
+ metadata: {
323
+ id: "ash_ext",
324
+ org: "consumer-org",
325
+ slug: "support-agent",
326
+ name: "Support Agent",
327
+ },
328
+ spec: {
329
+ agentRef: { org: "acme", slug: "support-agent" },
330
+ enabled: true,
331
+ },
332
+ });
333
+ renderOpenDialog(createMockStigmer({ apply }), { shareOrg: "consumer-org" });
325
334
 
335
+ // The header names whose blueprint this channel serves.
336
+ expect(screen.getByText("acme/support-agent")).toBeTruthy();
337
+ // The create copy names the org that owns and pays for the channel.
338
+ expect(screen.getByText(/credits/)).toBeTruthy();
339
+
340
+ fireEvent.click(screen.getByRole("button", { name: "Create share", hidden: true }));
341
+
342
+ await waitFor(() => expect(apply).toHaveBeenCalledTimes(1));
343
+ const input = apply.mock.calls[0][0] as AgentShareInput;
344
+ expect(input.org).toBe("consumer-org");
345
+ expect(input.agentRef).toEqual({ org: "acme", slug: "support-agent" });
346
+
347
+ // The editor shows the sharing org's URL; cross-org shares are
348
+ // public-audience only, so no audience selector is offered.
326
349
  await waitFor(() =>
327
350
  expect(
328
- screen.getByRole("switch", { hidden: true }).getAttribute("aria-checked"),
329
- ).toBe("true"),
351
+ screen.getByText("https://app.example.com/chat/consumer-org/support-agent"),
352
+ ).toBeTruthy(),
330
353
  );
354
+ expect(screen.queryByRole("radiogroup", { hidden: true })).toBeNull();
331
355
  });
332
356
  });
333
357
 
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 })),
358
+ describe("paused share (enabled: false)", () => {
359
+ it("renders the off state with disabled copy affordances", () => {
360
+ renderOpenDialog(createMockStigmer(), {
361
+ share: makeShare({ enabled: false }),
362
+ });
363
+
364
+ expect(
365
+ screen.getByRole("switch", { hidden: true }).getAttribute("aria-checked"),
366
+ ).toBe("false");
367
+ const copy = screen.getByRole("button", {
368
+ name: "Copy",
369
+ hidden: true,
370
+ }) as HTMLButtonElement;
371
+ expect(copy.disabled).toBe(true);
372
+ expect(
373
+ screen.getByText(/sharing is off, so this link doesn't work yet/i),
374
+ ).toBeTruthy();
375
+ });
376
+
377
+ it("shows the reason on the Embed tab too", () => {
378
+ renderOpenDialog(createMockStigmer(), {
379
+ share: makeShare({ enabled: false }),
338
380
  });
339
- await renderOpenDialog(client);
340
381
 
341
382
  fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
342
383
  expect(
@@ -344,11 +385,10 @@ describe("ShareAgentDialog", () => {
344
385
  ).toBeTruthy();
345
386
  });
346
387
 
347
- it("drops the hint and enables copy once sharing is on", async () => {
348
- const client = createMockStigmer({
349
- getByAgent: withShare(makeShare({ enabled: true })),
388
+ it("drops the hint and enables copy once sharing is on", () => {
389
+ renderOpenDialog(createMockStigmer(), {
390
+ share: makeShare({ enabled: true }),
350
391
  });
351
- await renderOpenDialog(client);
352
392
 
353
393
  const copy = screen.getByRole("button", {
354
394
  name: "Copy",
@@ -359,11 +399,10 @@ describe("ShareAgentDialog", () => {
359
399
  });
360
400
  });
361
401
 
362
- it("shows the indexability warning", async () => {
363
- const client = createMockStigmer({
364
- getByAgent: withShare(makeShare({ enabled: true })),
402
+ it("shows the indexability warning", () => {
403
+ renderOpenDialog(createMockStigmer(), {
404
+ share: makeShare({ enabled: true }),
365
405
  });
366
- await renderOpenDialog(client);
367
406
 
368
407
  expect(
369
408
  screen.getByText(/forwarded and indexed by search engines/),
@@ -373,17 +412,13 @@ describe("ShareAgentDialog", () => {
373
412
  describe("Audience", () => {
374
413
  it("defaults to Public link and switching to Org members applies the full spec", async () => {
375
414
  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
- ),
415
+ renderOpenDialog(createMockStigmer({ apply }), {
416
+ share: makeShare({
417
+ enabled: true,
418
+ allowedOrigins: ["https://example.com"],
419
+ messages: { rateLimited: "Easy there." },
420
+ }),
385
421
  });
386
- await renderOpenDialog(client);
387
422
 
388
423
  const publicOption = screen.getByRole("radio", {
389
424
  name: "Public link",
@@ -402,21 +437,25 @@ describe("ShareAgentDialog", () => {
402
437
  expect(input.enabled).toBe(true);
403
438
  expect(input.allowedOrigins).toEqual(["https://example.com"]);
404
439
  expect(input.messages?.rateLimited).toBe("Easy there.");
440
+ // Edit keys on the existing share's identity — never a new row.
441
+ expect(input.org).toBe("acme");
442
+ expect(input.slug).toBe("support-agent");
405
443
  });
406
444
 
407
445
  it("switching to Org members drops credential bindings (public-audience only)", async () => {
408
446
  const apply = vi.fn().mockResolvedValue({});
409
- const client = createMockStigmer({
410
- apply,
411
- getByAgent: withShare(
412
- makeShare({
447
+ renderOpenDialog(
448
+ createMockStigmer({
449
+ apply,
450
+ environments: [orgSharedEnv("github-creds")],
451
+ }),
452
+ {
453
+ share: makeShare({
413
454
  enabled: true,
414
455
  environmentRefs: [{ org: "acme", slug: "github-creds" }],
415
456
  }),
416
- ),
417
- environments: [orgSharedEnv("github-creds")],
418
- });
419
- await renderOpenDialog(client);
457
+ },
458
+ );
420
459
 
421
460
  fireEvent.click(
422
461
  screen.getByRole("radio", { name: "Org members", hidden: true }),
@@ -430,13 +469,10 @@ describe("ShareAgentDialog", () => {
430
469
  expect(input.environmentRefs).toEqual([]);
431
470
  });
432
471
 
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
- ),
472
+ it("renders org-audience copy: member link, revocation note, no indexability warning", () => {
473
+ renderOpenDialog(createMockStigmer(), {
474
+ share: makeShare({ enabled: true, audience: AgentShareAudience.org }),
438
475
  });
439
- await renderOpenDialog(client);
440
476
 
441
477
  expect(screen.getByText("Organization members can chat")).toBeTruthy();
442
478
  expect(screen.getByText("Member chat link")).toBeTruthy();
@@ -448,13 +484,10 @@ describe("ShareAgentDialog", () => {
448
484
  ).toBeNull();
449
485
  });
450
486
 
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
- ),
487
+ it("replaces the Embed tab with a public-only explanation for the org audience", () => {
488
+ renderOpenDialog(createMockStigmer(), {
489
+ share: makeShare({ enabled: true, audience: AgentShareAudience.org }),
456
490
  });
457
- await renderOpenDialog(client);
458
491
 
459
492
  fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
460
493
  expect(
@@ -464,20 +497,60 @@ describe("ShareAgentDialog", () => {
464
497
  });
465
498
  });
466
499
 
500
+ describe("cross-org edit mode (share org differs from the agent's — decision 013)", () => {
501
+ const externalShare = {
502
+ metadata: {
503
+ id: "ash_ext",
504
+ org: "consumer-org",
505
+ slug: "support-agent",
506
+ name: "Support Agent",
507
+ },
508
+ spec: {
509
+ agentRef: { org: "acme", slug: "support-agent" },
510
+ enabled: true,
511
+ },
512
+ } as never;
513
+
514
+ it("qualifies the agent in the header and hides the audience selector", () => {
515
+ renderOpenDialog(createMockStigmer(), { share: externalShare });
516
+
517
+ // The header names whose blueprint this channel serves.
518
+ expect(screen.getByText("acme/support-agent")).toBeTruthy();
519
+ // Cross-org shares are public-audience only — no choice to offer.
520
+ expect(screen.queryByRole("radiogroup", { hidden: true })).toBeNull();
521
+ });
522
+
523
+ it("builds the link and billing line from the sharing org", () => {
524
+ renderOpenDialog(createMockStigmer(), { share: externalShare });
525
+
526
+ expect(
527
+ screen.getByText("https://app.example.com/chat/consumer-org/support-agent"),
528
+ ).toBeTruthy();
529
+ // Who-pays names the sharing org, not the agent's.
530
+ expect(screen.getByText("consumer-org")).toBeTruthy();
531
+ });
532
+
533
+ it("same-org dialogs are unchanged when the share org equals the agent's", () => {
534
+ renderOpenDialog(createMockStigmer(), {
535
+ share: makeShare({ enabled: true }),
536
+ });
537
+
538
+ expect(screen.getByText("Support Agent")).toBeTruthy();
539
+ expect(screen.getByRole("radiogroup", { hidden: true })).toBeTruthy();
540
+ });
541
+ });
542
+
467
543
  it("enabling applies the complete spec and notifies the host", async () => {
468
544
  const apply = vi.fn().mockResolvedValue({});
469
545
  const onSharingChanged = vi.fn();
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
- ),
546
+ renderOpenDialog(createMockStigmer({ apply }), {
547
+ onSharingChanged,
548
+ share: makeShare({
549
+ enabled: false,
550
+ allowedOrigins: ["https://example.com"],
551
+ messages: { rateLimited: "Easy there." },
552
+ }),
479
553
  });
480
- await renderOpenDialog(client, { onSharingChanged });
481
554
 
482
555
  fireEvent.click(screen.getByRole("switch", { hidden: true }));
483
556
 
@@ -499,11 +572,9 @@ describe("ShareAgentDialog", () => {
499
572
  allowedOrigins: ["https://normalized.example.com"],
500
573
  }),
501
574
  );
502
- const client = createMockStigmer({
503
- apply,
504
- getByAgent: withShare(makeShare({ enabled: false })),
575
+ renderOpenDialog(createMockStigmer({ apply }), {
576
+ share: makeShare({ enabled: false }),
505
577
  });
506
- await renderOpenDialog(client);
507
578
 
508
579
  fireEvent.click(screen.getByRole("switch", { hidden: true }));
509
580
  await waitFor(() =>
@@ -517,11 +588,11 @@ describe("ShareAgentDialog", () => {
517
588
  });
518
589
 
519
590
  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 })),
591
+ it("warns needs-credentials for a tool-using share without bindings", () => {
592
+ renderOpenDialog(createMockStigmer(), {
593
+ agent: makeAgent({ mcpUsages: true }),
594
+ share: makeShare({ enabled: true }),
523
595
  });
524
- await renderOpenDialog(client, { agent: makeAgent({ mcpUsages: true }) });
525
596
 
526
597
  expect(
527
598
  screen.getByText(/no credentials are bound to this share/i),
@@ -530,12 +601,16 @@ describe("ShareAgentDialog", () => {
530
601
 
531
602
  it("binds an org-shared environment by applying the appended refs", async () => {
532
603
  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 }) });
604
+ renderOpenDialog(
605
+ createMockStigmer({
606
+ apply,
607
+ environments: [orgSharedEnv("github-creds", "GitHub Creds")],
608
+ }),
609
+ {
610
+ agent: makeAgent({ mcpUsages: true }),
611
+ share: makeShare({ enabled: true }),
612
+ },
613
+ );
539
614
 
540
615
  // The section is expanded by default for tool-using agents.
541
616
  const picker = await screen.findByLabelText("Add environment", {
@@ -562,11 +637,15 @@ describe("ShareAgentDialog", () => {
562
637
  },
563
638
  spec: { description: "" },
564
639
  };
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 }) });
640
+ renderOpenDialog(
641
+ createMockStigmer({
642
+ environments: [orgSharedEnv("github-creds", "GitHub Creds"), privateEnv],
643
+ }),
644
+ {
645
+ agent: makeAgent({ mcpUsages: true }),
646
+ share: makeShare({ enabled: true }),
647
+ },
648
+ );
570
649
 
571
650
  await screen.findByLabelText("Add environment", { selector: "select" });
572
651
  // Private environments are guest-unusable (the runtime merge skips
@@ -575,24 +654,21 @@ describe("ShareAgentDialog", () => {
575
654
  expect(screen.queryByText("Personal Creds")).toBeNull();
576
655
  });
577
656
 
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
- ),
657
+ it("hides the section entirely for org-audience shares", () => {
658
+ renderOpenDialog(createMockStigmer(), {
659
+ agent: makeAgent({ mcpUsages: true }),
660
+ share: makeShare({ enabled: true, audience: AgentShareAudience.org }),
583
661
  });
584
- await renderOpenDialog(client, { agent: makeAgent({ mcpUsages: true }) });
585
662
 
586
663
  expect(screen.queryByText("Tool credentials")).toBeNull();
587
664
  });
588
665
  });
589
666
 
590
667
  describe("Embed tab", () => {
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 })),
668
+ it("shows the one-line script snippet: loader from the app origin + <stigmer-agent>", () => {
669
+ renderOpenDialog(createMockStigmer(), {
670
+ share: makeShare({ enabled: true }),
594
671
  });
595
- await renderOpenDialog(client);
596
672
 
597
673
  fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
598
674
  // The loader must be served from the SAME origin as the share URL —
@@ -609,11 +685,10 @@ describe("ShareAgentDialog", () => {
609
685
  ).toBeTruthy();
610
686
  });
611
687
 
612
- it("keeps the iframe snippet available as the collapsed no-JavaScript alternative", async () => {
613
- const client = createMockStigmer({
614
- getByAgent: withShare(makeShare({ enabled: true })),
688
+ it("keeps the iframe snippet available as the collapsed no-JavaScript alternative", () => {
689
+ renderOpenDialog(createMockStigmer(), {
690
+ share: makeShare({ enabled: true }),
615
691
  });
616
- await renderOpenDialog(client);
617
692
 
618
693
  fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
619
694
  // Collapsed by default — the script snippet is the primary path.
@@ -634,11 +709,9 @@ describe("ShareAgentDialog", () => {
634
709
 
635
710
  it("rejects an invalid origin without calling the RPC", async () => {
636
711
  const apply = vi.fn();
637
- const client = createMockStigmer({
638
- apply,
639
- getByAgent: withShare(makeShare({ enabled: true })),
712
+ renderOpenDialog(createMockStigmer({ apply }), {
713
+ share: makeShare({ enabled: true }),
640
714
  });
641
- await renderOpenDialog(client);
642
715
 
643
716
  fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
644
717
  const input = screen.getByLabelText("Add allowed origin");
@@ -653,16 +726,12 @@ describe("ShareAgentDialog", () => {
653
726
 
654
727
  it("adds a valid origin by applying the appended list", async () => {
655
728
  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
- ),
729
+ renderOpenDialog(createMockStigmer({ apply }), {
730
+ share: makeShare({
731
+ enabled: true,
732
+ allowedOrigins: ["https://existing.example.com"],
733
+ }),
664
734
  });
665
- await renderOpenDialog(client);
666
735
 
667
736
  fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
668
737
  fireEvent.change(screen.getByLabelText("Add allowed origin"), {
@@ -682,16 +751,12 @@ describe("ShareAgentDialog", () => {
682
751
 
683
752
  it("rejects a duplicate origin", async () => {
684
753
  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
- ),
754
+ renderOpenDialog(createMockStigmer({ apply }), {
755
+ share: makeShare({
756
+ enabled: true,
757
+ allowedOrigins: ["https://example.com"],
758
+ }),
693
759
  });
694
- await renderOpenDialog(client);
695
760
 
696
761
  fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
697
762
  fireEvent.change(screen.getByLabelText("Add allowed origin"), {
@@ -705,16 +770,12 @@ describe("ShareAgentDialog", () => {
705
770
 
706
771
  it("removes an origin by applying the filtered list", async () => {
707
772
  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
- ),
773
+ renderOpenDialog(createMockStigmer({ apply }), {
774
+ share: makeShare({
775
+ enabled: true,
776
+ allowedOrigins: ["https://a.example.com", "https://b.example.com"],
777
+ }),
716
778
  });
717
- await renderOpenDialog(client);
718
779
 
719
780
  fireEvent.click(screen.getByRole("tab", { name: /Embed/, hidden: true }));
720
781
  fireEvent.click(screen.getByLabelText("Remove https://a.example.com"));
@@ -728,16 +789,12 @@ describe("ShareAgentDialog", () => {
728
789
  describe("visitor messages", () => {
729
790
  it("saves edited messages as part of the complete spec", async () => {
730
791
  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
- ),
792
+ renderOpenDialog(createMockStigmer({ apply }), {
793
+ share: makeShare({
794
+ enabled: true,
795
+ allowedOrigins: ["https://example.com"],
796
+ }),
739
797
  });
740
- await renderOpenDialog(client);
741
798
 
742
799
  fireEvent.click(screen.getByText("Customize visitor messages"));
743
800
  fireEvent.change(screen.getByLabelText(/Rate limited/), {
@@ -752,11 +809,10 @@ describe("ShareAgentDialog", () => {
752
809
  expect(input.allowedOrigins).toEqual(["https://example.com"]);
753
810
  });
754
811
 
755
- it("caps each message at 300 characters", async () => {
756
- const client = createMockStigmer({
757
- getByAgent: withShare(makeShare({ enabled: true })),
812
+ it("caps each message at 300 characters", () => {
813
+ renderOpenDialog(createMockStigmer(), {
814
+ share: makeShare({ enabled: true }),
758
815
  });
759
- await renderOpenDialog(client);
760
816
 
761
817
  fireEvent.click(screen.getByText("Customize visitor messages"));
762
818
  const field = screen.getByLabelText(/Rate limited/) as HTMLTextAreaElement;
@@ -770,11 +826,10 @@ describe("ShareAgentDialog", () => {
770
826
  const getOrCreateBillingAccount = vi.fn().mockResolvedValue({
771
827
  balance: { availableMicros: BigInt(12_500_000) },
772
828
  });
773
- const client = createMockStigmer({
774
- getOrCreateBillingAccount,
775
- getByAgent: withShare(makeShare({ enabled: true })),
829
+ renderOpenDialog(createMockStigmer({ getOrCreateBillingAccount }), {
830
+ mode: "cloud",
831
+ share: makeShare({ enabled: true }),
776
832
  });
777
- await renderOpenDialog(client, { mode: "cloud" });
778
833
 
779
834
  expect(screen.getByText(/Visitors chat on/)).toBeTruthy();
780
835
  await waitFor(() =>
@@ -783,13 +838,12 @@ describe("ShareAgentDialog", () => {
783
838
  expect(getOrCreateBillingAccount).toHaveBeenCalledWith("acme");
784
839
  });
785
840
 
786
- it("degrades to the who-pays line alone in local mode (no billing fetch)", async () => {
841
+ it("degrades to the who-pays line alone in local mode (no billing fetch)", () => {
787
842
  const getOrCreateBillingAccount = vi.fn();
788
- const client = createMockStigmer({
789
- getOrCreateBillingAccount,
790
- getByAgent: withShare(makeShare({ enabled: true })),
843
+ renderOpenDialog(createMockStigmer({ getOrCreateBillingAccount }), {
844
+ mode: "local",
845
+ share: makeShare({ enabled: true }),
791
846
  });
792
- await renderOpenDialog(client, { mode: "local" });
793
847
 
794
848
  expect(screen.getByText(/Visitors chat on/)).toBeTruthy();
795
849
  expect(screen.queryByText(/available/)).toBeNull();
@@ -800,11 +854,10 @@ describe("ShareAgentDialog", () => {
800
854
  const getOrCreateBillingAccount = vi
801
855
  .fn()
802
856
  .mockRejectedValue(new Error("billing unavailable"));
803
- const client = createMockStigmer({
804
- getOrCreateBillingAccount,
805
- getByAgent: withShare(makeShare({ enabled: true })),
857
+ renderOpenDialog(createMockStigmer({ getOrCreateBillingAccount }), {
858
+ mode: "cloud",
859
+ share: makeShare({ enabled: true }),
806
860
  });
807
- await renderOpenDialog(client, { mode: "cloud" });
808
861
 
809
862
  await waitFor(() => expect(getOrCreateBillingAccount).toHaveBeenCalled());
810
863
  expect(screen.getByText(/Visitors chat on/)).toBeTruthy();
@@ -814,11 +867,10 @@ describe("ShareAgentDialog", () => {
814
867
  });
815
868
 
816
869
  describe("Developer tab", () => {
817
- it("shows the platform client snippet and docs link", async () => {
818
- const client = createMockStigmer({
819
- getByAgent: withShare(makeShare({ enabled: true })),
870
+ it("shows the platform client snippet and docs link", () => {
871
+ renderOpenDialog(createMockStigmer(), {
872
+ share: makeShare({ enabled: true }),
820
873
  });
821
- await renderOpenDialog(client);
822
874
 
823
875
  fireEvent.click(screen.getByRole("tab", { name: /Developer/, hidden: true }));
824
876
  expect(screen.getByText(/createPlatformClientAuth/)).toBeTruthy();
@@ -827,24 +879,24 @@ describe("ShareAgentDialog", () => {
827
879
  });
828
880
  });
829
881
 
830
- it("renders in-flow without showModal when modal is false", async () => {
882
+ it("renders in-flow without showModal when modal is false", () => {
831
883
  const showModal = vi.spyOn(HTMLDialogElement.prototype, "showModal");
832
- const client = createMockStigmer({
833
- getByAgent: withShare(makeShare({ enabled: true })),
884
+ renderOpenDialog(createMockStigmer(), {
885
+ modal: false,
886
+ share: makeShare({ enabled: true }),
834
887
  });
835
- await renderOpenDialog(client, { modal: false });
836
888
 
837
889
  expect(screen.getByText("Share")).toBeTruthy();
838
890
  expect(showModal).not.toHaveBeenCalled();
839
891
  showModal.mockRestore();
840
892
  });
841
893
 
842
- it("requests close via Done and the close affordance", async () => {
894
+ it("requests close via Done and the close affordance", () => {
843
895
  const onOpenChange = vi.fn();
844
- const client = createMockStigmer({
845
- getByAgent: withShare(makeShare({ enabled: true })),
896
+ renderOpenDialog(createMockStigmer(), {
897
+ onOpenChange,
898
+ share: makeShare({ enabled: true }),
846
899
  });
847
- await renderOpenDialog(client, { onOpenChange });
848
900
 
849
901
  screen.getByText("Done").click();
850
902
  expect(onOpenChange).toHaveBeenCalledWith(false);
@@ -855,11 +907,10 @@ describe("ShareAgentDialog", () => {
855
907
  });
856
908
 
857
909
  describe("Reset link (rotatable share token)", () => {
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")),
910
+ it("appends the status token to the shown link and embed snippet", () => {
911
+ renderOpenDialog(createMockStigmer(), {
912
+ share: makeShare({ enabled: true }, "tok123"),
861
913
  });
862
- await renderOpenDialog(client);
863
914
 
864
915
  expect(
865
916
  screen.getByText("https://app.example.com/chat/acme/support-agent?k=tok123"),
@@ -874,11 +925,10 @@ describe("ShareAgentDialog", () => {
874
925
  .fn()
875
926
  .mockResolvedValue(makeShare({ enabled: true }, "fresh-token"));
876
927
  const onSharingChanged = vi.fn();
877
- const client = createMockStigmer({
878
- rotateShareLink,
879
- getByAgent: withShare(makeShare({ enabled: true })),
928
+ renderOpenDialog(createMockStigmer({ rotateShareLink }), {
929
+ onSharingChanged,
930
+ share: makeShare({ enabled: true }),
880
931
  });
881
- await renderOpenDialog(client, { onSharingChanged });
882
932
 
883
933
  // A plain link shows no token before the reset.
884
934
  expect(
@@ -895,23 +945,20 @@ describe("ShareAgentDialog", () => {
895
945
  ).toBeTruthy(),
896
946
  );
897
947
  expect(rotateShareLink).toHaveBeenCalledTimes(1);
898
- // The rotation targets the loaded share by its own id.
948
+ // The rotation targets the given share by its own id.
899
949
  expect(
900
950
  (rotateShareLink.mock.calls[0][0] as { resourceId: string }).resourceId,
901
951
  ).toBe("ash_1");
902
952
  expect(onSharingChanged).toHaveBeenCalled();
903
953
  });
904
954
 
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
- ),
955
+ it("hides the Reset control and the token for org-members-only shares", () => {
956
+ renderOpenDialog(createMockStigmer(), {
957
+ share: makeShare(
958
+ { enabled: true, audience: AgentShareAudience.org },
959
+ "tok123",
912
960
  ),
913
961
  });
914
- await renderOpenDialog(client);
915
962
 
916
963
  // Org access is gated by membership, not the link token: the member
917
964
  // link stays clean and the Reset lever is not offered.