@stigmer/cli 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.
@@ -1,13 +1,12 @@
1
1
  // Unit tests for the share-agent resource layer. The critical contract is
2
- // merge-preservation: `updateSharing` replaces `spec.sharing` wholesale, so a
3
- // CLI toggle must never wipe console-configured origins or visitor messages.
2
+ // merge-preservation: `agentShare.apply` replaces the share's spec wholesale,
3
+ // so a CLI toggle must never wipe console-configured origins, visitor
4
+ // messages, credential bindings, or the audience.
4
5
 
5
- import { clone, create } from "@bufbuild/protobuf";
6
+ import { create } from "@bufbuild/protobuf";
6
7
  import { AgentSchema, type Agent } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
7
- import { AgentStatusSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/status_pb";
8
- import type { UpdateAgentSharingInput } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/io_pb";
9
- import { AgentSharingAudience } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/spec_pb";
10
- import { buildEmbedSnippet, type Stigmer } from "@stigmer/sdk";
8
+ import { AgentShareAudience } from "@stigmer/protos/ai/stigmer/agentic/agentshare/v1/spec_pb";
9
+ import { buildEmbedSnippet, type AgentShareInput, type Stigmer } from "@stigmer/sdk";
11
10
  import { describe, expect, it } from "vitest";
12
11
  import { classify, ExitCode, UsageError } from "../errors/index.js";
13
12
  import { shareAgent } from "./share.js";
@@ -15,32 +14,55 @@ import { shareAgent } from "./share.js";
15
14
  const CLOUD = { appOrigin: "https://app.stigmer.ai", isLocal: false };
16
15
  const LOCAL = { appOrigin: "http://localhost:8234", isLocal: true };
17
16
 
18
- function makeAgent(
19
- sharing?: {
20
- enabled?: boolean;
21
- audience?: AgentSharingAudience;
22
- allowedOrigins?: string[];
23
- messages?: { rateLimited?: string; unavailable?: string; conversationEnded?: string };
24
- },
25
- shareLinkToken?: string,
26
- ): Agent {
17
+ function makeAgent(): Agent {
27
18
  return create(AgentSchema, {
28
19
  metadata: { id: "agt_01ARZ3NDEKTSV4RRFFQ69G5FAV", org: "acme", slug: "support-agent", name: "Support Agent" },
29
- spec: sharing !== undefined ? { sharing } : {},
30
- ...(shareLinkToken !== undefined ? { status: { shareLinkToken } } : {}),
20
+ spec: {},
31
21
  });
32
22
  }
33
23
 
34
- // A minimal fake of the SDK surface shareAgent touches: agent.getByReference /
35
- // agent.get + call-recording agent.updateSharing / agent.rotateShareLink
36
- // (skill.test.ts convention). Rotation answers with the agent re-stamped
37
- // with `rotatedToken`, mirroring the server's fresh-entropy response.
24
+ interface ShareFixture {
25
+ slug?: string;
26
+ enabled?: boolean;
27
+ audience?: AgentShareAudience;
28
+ allowedOrigins?: string[];
29
+ messages?: { rateLimited?: string; unavailable?: string; conversationEnded?: string };
30
+ environmentRefs?: { org: string; slug: string }[];
31
+ shareLinkToken?: string;
32
+ }
33
+
34
+ // Plain-object AgentShare fixture: the CLI reads it structurally (metadata /
35
+ // spec / status), so proto construction adds nothing here.
36
+ function makeShare(fixture: ShareFixture = {}) {
37
+ const { shareLinkToken, slug, ...spec } = fixture;
38
+ return {
39
+ metadata: {
40
+ id: "ash_01ARZ3NDEKTSV4RRFFQ69G5FAV",
41
+ org: "acme",
42
+ slug: slug ?? "support-agent",
43
+ name: "Support Agent",
44
+ },
45
+ spec: {
46
+ agentRef: { org: "acme", slug: "support-agent" },
47
+ ...spec,
48
+ },
49
+ ...(shareLinkToken !== undefined ? { status: { shareLinkToken } } : {}),
50
+ };
51
+ }
52
+
53
+ // A minimal fake of the SDK surface shareAgent touches: agent resolution +
54
+ // call-recording agentShare getByAgent/apply/rotateShareLink (skill.test.ts
55
+ // convention). Apply answers with the applied input merged over the existing
56
+ // share (id assigned), mirroring the server's upsert. Rotation answers with
57
+ // the share re-stamped with `rotatedToken`, mirroring fresh entropy.
38
58
  function fakeClient(
39
59
  agent: Agent,
60
+ share: ReturnType<typeof makeShare> | null,
40
61
  opts: { failWith?: Error; rotatedToken?: string } = {},
41
62
  ) {
42
- const calls: UpdateAgentSharingInput[] = [];
63
+ const applies: AgentShareInput[] = [];
43
64
  let rotations = 0;
65
+ let currentShare = share;
44
66
  const client = {
45
67
  agent: {
46
68
  async get() {
@@ -49,99 +71,147 @@ function fakeClient(
49
71
  async getByReference() {
50
72
  return agent;
51
73
  },
52
- async updateSharing(input: UpdateAgentSharingInput) {
74
+ },
75
+ agentShare: {
76
+ async getByAgent() {
77
+ return {
78
+ totalCount: currentShare ? 1 : 0,
79
+ items: currentShare ? [currentShare] : [],
80
+ };
81
+ },
82
+ async apply(input: AgentShareInput) {
53
83
  if (opts.failWith) throw opts.failWith;
54
- calls.push(input);
55
- return agent;
84
+ applies.push(input);
85
+ currentShare = {
86
+ metadata: {
87
+ id: currentShare?.metadata.id ?? "ash_01ARZ3NDEKTSV4RRFFQ69G5FAV",
88
+ org: input.org,
89
+ slug: input.slug ?? "",
90
+ name: input.name,
91
+ },
92
+ spec: {
93
+ agentRef: { org: input.agentRef.org, slug: input.agentRef.slug },
94
+ enabled: input.enabled ?? false,
95
+ audience: input.audience ?? AgentShareAudience.unspecified,
96
+ allowedOrigins: input.allowedOrigins ?? [],
97
+ messages: input.messages as never,
98
+ environmentRefs: input.environmentRefs ?? [],
99
+ },
100
+ ...(currentShare?.status ? { status: currentShare.status } : {}),
101
+ } as ReturnType<typeof makeShare>;
102
+ return currentShare;
56
103
  },
57
104
  async rotateShareLink() {
58
105
  rotations += 1;
59
- const rotated = clone(AgentSchema, agent);
60
- rotated.status = create(AgentStatusSchema, {
61
- shareLinkToken: opts.rotatedToken ?? "fresh-token",
62
- });
63
- return rotated;
106
+ currentShare = {
107
+ ...(currentShare as ReturnType<typeof makeShare>),
108
+ status: { shareLinkToken: opts.rotatedToken ?? "fresh-token" },
109
+ };
110
+ return currentShare;
64
111
  },
65
112
  },
66
113
  } as unknown as Stigmer;
67
- return { client, calls, rotationCount: () => rotations };
114
+ return { client, applies, rotationCount: () => rotations };
68
115
  }
69
116
 
70
117
  describe("shareAgent merge-preservation (fails closed)", () => {
71
- it("enabling preserves console-configured origins and messages verbatim", async () => {
72
- const agent = makeAgent({
118
+ it("enabling preserves console-configured origins, messages, and bindings verbatim", async () => {
119
+ const share = makeShare({
73
120
  enabled: false,
74
121
  allowedOrigins: ["https://example.com", "https://docs.example.com"],
75
122
  messages: { rateLimited: "Slow down!", unavailable: "Back soon.", conversationEnded: "Bye!" },
123
+ environmentRefs: [{ org: "acme", slug: "github-org-shared" }],
76
124
  });
77
- const { client, calls } = fakeClient(agent);
125
+ const { client, applies } = fakeClient(makeAgent(), share);
78
126
 
79
127
  await shareAgent(client, "acme/support-agent", "acme", { enabled: true, ...CLOUD });
80
128
 
81
- expect(calls).toHaveLength(1);
82
- expect(calls[0].resourceId).toBe("agt_01ARZ3NDEKTSV4RRFFQ69G5FAV");
83
- expect(calls[0].sharing?.enabled).toBe(true);
84
- expect(calls[0].sharing?.allowedOrigins).toEqual(["https://example.com", "https://docs.example.com"]);
85
- expect(calls[0].sharing?.messages?.rateLimited).toBe("Slow down!");
86
- expect(calls[0].sharing?.messages?.unavailable).toBe("Back soon.");
87
- expect(calls[0].sharing?.messages?.conversationEnded).toBe("Bye!");
129
+ expect(applies).toHaveLength(1);
130
+ expect(applies[0].org).toBe("acme");
131
+ expect(applies[0].slug).toBe("support-agent");
132
+ expect(applies[0].agentRef).toEqual({ org: "acme", slug: "support-agent" });
133
+ expect(applies[0].enabled).toBe(true);
134
+ expect(applies[0].allowedOrigins).toEqual(["https://example.com", "https://docs.example.com"]);
135
+ expect(applies[0].messages?.rateLimited).toBe("Slow down!");
136
+ expect(applies[0].messages?.unavailable).toBe("Back soon.");
137
+ expect(applies[0].messages?.conversationEnded).toBe("Bye!");
138
+ expect(applies[0].environmentRefs).toEqual([{ org: "acme", slug: "github-org-shared" }]);
88
139
  });
89
140
 
90
141
  it("disabling preserves origins and messages, flipping only enabled", async () => {
91
- const agent = makeAgent({
142
+ const share = makeShare({
92
143
  enabled: true,
93
144
  allowedOrigins: ["https://example.com"],
94
145
  messages: { rateLimited: "Slow down!" },
95
146
  });
96
- const { client, calls } = fakeClient(agent);
147
+ const { client, applies } = fakeClient(makeAgent(), share);
97
148
 
98
149
  const result = await shareAgent(client, "acme/support-agent", "acme", { enabled: false, ...CLOUD });
99
150
 
100
- expect(calls).toHaveLength(1);
101
- expect(calls[0].sharing?.enabled).toBe(false);
102
- expect(calls[0].sharing?.allowedOrigins).toEqual(["https://example.com"]);
103
- expect(calls[0].sharing?.messages?.rateLimited).toBe("Slow down!");
151
+ expect(applies).toHaveLength(1);
152
+ expect(applies[0].enabled).toBe(false);
153
+ expect(applies[0].allowedOrigins).toEqual(["https://example.com"]);
154
+ expect(applies[0].messages?.rateLimited).toBe("Slow down!");
104
155
  expect(result.message).toContain("Sharing disabled");
105
156
  // No link/snippet on disable — the URL no longer works.
106
157
  expect(result.sections).toHaveLength(0);
107
158
  });
108
159
 
109
- it("enables cleanly for an agent that has never been shared (no spec.sharing)", async () => {
110
- const { client, calls } = fakeClient(makeAgent(undefined));
160
+ it("enabling a never-shared agent creates the canonical share", async () => {
161
+ const { client, applies } = fakeClient(makeAgent(), null);
111
162
 
112
163
  await shareAgent(client, "acme/support-agent", "acme", { enabled: true, ...CLOUD });
113
164
 
114
- expect(calls).toHaveLength(1);
115
- expect(calls[0].sharing?.enabled).toBe(true);
116
- expect(calls[0].sharing?.allowedOrigins).toEqual([]);
117
- expect(calls[0].sharing?.messages?.rateLimited).toBe("");
165
+ expect(applies).toHaveLength(1);
166
+ // Create identity: the agent's own org/slug (the server's default,
167
+ // made explicit) with the agent reference and empty config.
168
+ expect(applies[0].slug).toBe("support-agent");
169
+ expect(applies[0].agentRef).toEqual({ org: "acme", slug: "support-agent" });
170
+ expect(applies[0].enabled).toBe(true);
171
+ expect(applies[0].allowedOrigins).toEqual([]);
172
+ expect(applies[0].messages?.rateLimited).toBe("");
173
+ expect(applies[0].environmentRefs).toEqual([]);
174
+ });
175
+
176
+ it("edits a renamed share under its own slug (never forks a second share)", async () => {
177
+ const share = makeShare({ slug: "help-desk", enabled: false });
178
+ const { client, applies } = fakeClient(makeAgent(), share);
179
+
180
+ const result = await shareAgent(client, "acme/support-agent", "acme", { enabled: true, ...CLOUD });
181
+
182
+ expect(applies).toHaveLength(1);
183
+ // Applying with the agent's slug would CREATE a second share; the
184
+ // toggle must target the existing row's identity.
185
+ expect(applies[0].slug).toBe("help-desk");
186
+ const link = result.sections.find((s) => s.title === "Public chat link");
187
+ expect(link?.items).toEqual(["https://app.stigmer.ai/chat/acme/help-desk"]);
118
188
  });
119
189
 
120
190
  it("a plain toggle preserves an org-members-only audience (never reverts to public)", async () => {
121
- const agent = makeAgent({
191
+ const share = makeShare({
122
192
  enabled: false,
123
- audience: AgentSharingAudience.org,
193
+ audience: AgentShareAudience.org,
124
194
  allowedOrigins: [],
125
195
  });
126
- const { client, calls } = fakeClient(agent);
196
+ const { client, applies } = fakeClient(makeAgent(), share);
127
197
 
128
198
  await shareAgent(client, "acme/support-agent", "acme", { enabled: true, ...CLOUD });
129
199
 
130
- expect(calls).toHaveLength(1);
200
+ expect(applies).toHaveLength(1);
131
201
  // A re-enable without --audience must keep the org restriction: writing
132
202
  // public here would silently expose an internal agent to the internet.
133
- expect(calls[0].sharing?.audience).toBe(AgentSharingAudience.org);
203
+ expect(applies[0].audience).toBe(AgentShareAudience.org);
134
204
  });
135
205
  });
136
206
 
137
207
  describe("shareAgent audience", () => {
138
- it("--audience org sets the audience and writes the full block", async () => {
139
- const agent = makeAgent({
208
+ it("--audience org sets the audience and applies the full spec", async () => {
209
+ const share = makeShare({
140
210
  enabled: true,
141
211
  allowedOrigins: ["https://example.com"],
142
212
  messages: { rateLimited: "Slow down!" },
143
213
  });
144
- const { client, calls } = fakeClient(agent);
214
+ const { client, applies } = fakeClient(makeAgent(), share);
145
215
 
146
216
  const result = await shareAgent(client, "acme/support-agent", "acme", {
147
217
  enabled: true,
@@ -149,11 +219,11 @@ describe("shareAgent audience", () => {
149
219
  ...CLOUD,
150
220
  });
151
221
 
152
- expect(calls).toHaveLength(1);
153
- expect(calls[0].sharing?.audience).toBe(AgentSharingAudience.org);
154
- expect(calls[0].sharing?.enabled).toBe(true);
155
- expect(calls[0].sharing?.allowedOrigins).toEqual(["https://example.com"]);
156
- expect(calls[0].sharing?.messages?.rateLimited).toBe("Slow down!");
222
+ expect(applies).toHaveLength(1);
223
+ expect(applies[0].audience).toBe(AgentShareAudience.org);
224
+ expect(applies[0].enabled).toBe(true);
225
+ expect(applies[0].allowedOrigins).toEqual(["https://example.com"]);
226
+ expect(applies[0].messages?.rateLimited).toBe("Slow down!");
157
227
 
158
228
  // Org shares print the member link and NO embed snippet (embeds serve
159
229
  // anonymous guests, which org shares refuse).
@@ -165,8 +235,8 @@ describe("shareAgent audience", () => {
165
235
  });
166
236
 
167
237
  it("--audience public converts an org share back to an explicit public one", async () => {
168
- const agent = makeAgent({ enabled: true, audience: AgentSharingAudience.org });
169
- const { client, calls } = fakeClient(agent);
238
+ const share = makeShare({ enabled: true, audience: AgentShareAudience.org });
239
+ const { client, applies } = fakeClient(makeAgent(), share);
170
240
 
171
241
  await shareAgent(client, "acme/support-agent", "acme", {
172
242
  enabled: true,
@@ -174,14 +244,14 @@ describe("shareAgent audience", () => {
174
244
  ...CLOUD,
175
245
  });
176
246
 
177
- expect(calls).toHaveLength(1);
247
+ expect(applies).toHaveLength(1);
178
248
  // Written as the explicit enum value, never left unspecified.
179
- expect(calls[0].sharing?.audience).toBe(AgentSharingAudience.public);
249
+ expect(applies[0].audience).toBe(AgentShareAudience.public);
180
250
  });
181
251
 
182
252
  it("an audience change alone triggers a write even when enabled matches", async () => {
183
- const agent = makeAgent({ enabled: true });
184
- const { client, calls } = fakeClient(agent);
253
+ const share = makeShare({ enabled: true });
254
+ const { client, applies } = fakeClient(makeAgent(), share);
185
255
 
186
256
  const result = await shareAgent(client, "acme/support-agent", "acme", {
187
257
  enabled: true,
@@ -189,13 +259,13 @@ describe("shareAgent audience", () => {
189
259
  ...CLOUD,
190
260
  });
191
261
 
192
- expect(calls).toHaveLength(1);
262
+ expect(applies).toHaveLength(1);
193
263
  expect(result.message).toContain("Sharing enabled");
194
264
  });
195
265
 
196
266
  it("matching enabled AND audience stays idempotent (no write)", async () => {
197
- const agent = makeAgent({ enabled: true, audience: AgentSharingAudience.org });
198
- const { client, calls } = fakeClient(agent);
267
+ const share = makeShare({ enabled: true, audience: AgentShareAudience.org });
268
+ const { client, applies } = fakeClient(makeAgent(), share);
199
269
 
200
270
  const result = await shareAgent(client, "acme/support-agent", "acme", {
201
271
  enabled: true,
@@ -203,36 +273,47 @@ describe("shareAgent audience", () => {
203
273
  ...CLOUD,
204
274
  });
205
275
 
206
- expect(calls).toHaveLength(0);
276
+ expect(applies).toHaveLength(0);
207
277
  expect(result.message).toContain("already on");
208
278
  });
209
279
  });
210
280
 
211
281
  describe("shareAgent idempotency", () => {
212
282
  it("skips the write when sharing is already enabled, still reporting the link", async () => {
213
- const { client, calls } = fakeClient(makeAgent({ enabled: true }));
283
+ const { client, applies } = fakeClient(makeAgent(), makeShare({ enabled: true }));
214
284
 
215
285
  const result = await shareAgent(client, "acme/support-agent", "acme", { enabled: true, ...CLOUD });
216
286
 
217
- expect(calls).toHaveLength(0);
287
+ expect(applies).toHaveLength(0);
218
288
  expect(result.message).toContain("already on");
219
289
  const link = result.sections.find((s) => s.title === "Public chat link");
220
290
  expect(link?.items).toEqual(["https://app.stigmer.ai/chat/acme/support-agent"]);
221
291
  });
222
292
 
223
293
  it("skips the write when sharing is already disabled", async () => {
224
- const { client, calls } = fakeClient(makeAgent({ enabled: false }));
294
+ const { client, applies } = fakeClient(makeAgent(), makeShare({ enabled: false }));
295
+
296
+ const result = await shareAgent(client, "acme/support-agent", "acme", { enabled: false, ...CLOUD });
297
+
298
+ expect(applies).toHaveLength(0);
299
+ expect(result.message).toContain("already off");
300
+ });
301
+
302
+ it("disabling a never-shared agent creates nothing", async () => {
303
+ const { client, applies } = fakeClient(makeAgent(), null);
225
304
 
226
305
  const result = await shareAgent(client, "acme/support-agent", "acme", { enabled: false, ...CLOUD });
227
306
 
228
- expect(calls).toHaveLength(0);
307
+ // Materializing a share row just to mark it disabled would create a
308
+ // resource the owner never asked for.
309
+ expect(applies).toHaveLength(0);
229
310
  expect(result.message).toContain("already off");
230
311
  });
231
312
  });
232
313
 
233
314
  describe("shareAgent output", () => {
234
- it("builds the link and snippet from the RESOLVED agent's org/slug (ID refs included)", async () => {
235
- const { client } = fakeClient(makeAgent({ enabled: false }));
315
+ it("builds the link and snippet from the RESOLVED share's org/slug (ID refs included)", async () => {
316
+ const { client } = fakeClient(makeAgent(), makeShare({ enabled: false }));
236
317
 
237
318
  const result = await shareAgent(client, "agt_01ARZ3NDEKTSV4RRFFQ69G5FAV", "", { enabled: true, ...CLOUD });
238
319
 
@@ -241,7 +322,7 @@ describe("shareAgent output", () => {
241
322
  });
242
323
 
243
324
  it("emits the exact embed snippet the web share dialog emits", async () => {
244
- const { client } = fakeClient(makeAgent({ enabled: false }));
325
+ const { client } = fakeClient(makeAgent(), makeShare({ enabled: false }));
245
326
 
246
327
  const result = await shareAgent(client, "acme/support-agent", "acme", { enabled: true, ...CLOUD });
247
328
 
@@ -250,7 +331,7 @@ describe("shareAgent output", () => {
250
331
  });
251
332
 
252
333
  it("includes the who-pays and indexability hints", async () => {
253
- const { client } = fakeClient(makeAgent({ enabled: false }));
334
+ const { client } = fakeClient(makeAgent(), makeShare({ enabled: false }));
254
335
 
255
336
  const result = await shareAgent(client, "acme/support-agent", "acme", { enabled: true, ...CLOUD });
256
337
 
@@ -259,7 +340,7 @@ describe("shareAgent output", () => {
259
340
  });
260
341
 
261
342
  it("warns that a local link won't serve visitors (guest chat is Cloud-only)", async () => {
262
- const { client } = fakeClient(makeAgent({ enabled: false }));
343
+ const { client } = fakeClient(makeAgent(), makeShare({ enabled: false }));
263
344
 
264
345
  const result = await shareAgent(client, "acme/support-agent", "acme", { enabled: true, ...LOCAL });
265
346
 
@@ -269,7 +350,7 @@ describe("shareAgent output", () => {
269
350
  });
270
351
 
271
352
  it("omits the local warning on a cloud backend", async () => {
272
- const { client } = fakeClient(makeAgent({ enabled: false }));
353
+ const { client } = fakeClient(makeAgent(), makeShare({ enabled: false }));
273
354
 
274
355
  const result = await shareAgent(client, "acme/support-agent", "acme", { enabled: true, ...CLOUD });
275
356
 
@@ -279,8 +360,9 @@ describe("shareAgent output", () => {
279
360
 
280
361
  describe("shareAgent --reset-link (rotatable share token)", () => {
281
362
  it("rotates and prints the fresh tokened link and snippet", async () => {
282
- const { client, calls, rotationCount } = fakeClient(
283
- makeAgent({ enabled: true }),
363
+ const { client, applies, rotationCount } = fakeClient(
364
+ makeAgent(),
365
+ makeShare({ enabled: true }),
284
366
  { rotatedToken: "fresh-token" },
285
367
  );
286
368
 
@@ -290,8 +372,8 @@ describe("shareAgent --reset-link (rotatable share token)", () => {
290
372
  ...CLOUD,
291
373
  });
292
374
 
293
- // Already enabled: no sharing write, exactly one rotation.
294
- expect(calls).toHaveLength(0);
375
+ // Already enabled: no spec write, exactly one rotation.
376
+ expect(applies).toHaveLength(0);
295
377
  expect(rotationCount()).toBe(1);
296
378
  expect(result.message).toContain("Share link reset");
297
379
 
@@ -306,9 +388,30 @@ describe("shareAgent --reset-link (rotatable share token)", () => {
306
388
  expect(result.hints.some((h) => h.includes("Re-share the new link"))).toBe(true);
307
389
  });
308
390
 
391
+ it("enable + reset on a never-shared agent creates the share, then rotates it", async () => {
392
+ const { client, applies, rotationCount } = fakeClient(makeAgent(), null, {
393
+ rotatedToken: "fresh-token",
394
+ });
395
+
396
+ const result = await shareAgent(client, "acme/support-agent", "acme", {
397
+ enabled: true,
398
+ resetLink: true,
399
+ ...CLOUD,
400
+ });
401
+
402
+ // The rotation needs a share id, which only exists after the create.
403
+ expect(applies).toHaveLength(1);
404
+ expect(rotationCount()).toBe(1);
405
+ const link = result.sections.find((s) => s.title === "Public chat link");
406
+ expect(link?.items).toEqual([
407
+ "https://app.stigmer.ai/chat/acme/support-agent?k=fresh-token",
408
+ ]);
409
+ });
410
+
309
411
  it("a plain toggle on a locked link keeps printing the existing tokened URL", async () => {
310
412
  const { client, rotationCount } = fakeClient(
311
- makeAgent({ enabled: true }, "existing-token"),
413
+ makeAgent(),
414
+ makeShare({ enabled: true, shareLinkToken: "existing-token" }),
312
415
  );
313
416
 
314
417
  const result = await shareAgent(client, "acme/support-agent", "acme", {
@@ -327,7 +430,8 @@ describe("shareAgent --reset-link (rotatable share token)", () => {
327
430
 
328
431
  it("refuses --reset-link for an org-members-only share with actionable guidance", async () => {
329
432
  const { client, rotationCount } = fakeClient(
330
- makeAgent({ enabled: true, audience: AgentSharingAudience.org }),
433
+ makeAgent(),
434
+ makeShare({ enabled: true, audience: AgentShareAudience.org }),
331
435
  );
332
436
 
333
437
  const err = await shareAgent(client, "acme/support-agent", "acme", {
@@ -345,7 +449,12 @@ describe("shareAgent --reset-link (rotatable share token)", () => {
345
449
 
346
450
  it("the member link never carries a token, even when one exists in status", async () => {
347
451
  const { client } = fakeClient(
348
- makeAgent({ enabled: true, audience: AgentSharingAudience.org }, "existing-token"),
452
+ makeAgent(),
453
+ makeShare({
454
+ enabled: true,
455
+ audience: AgentShareAudience.org,
456
+ shareLinkToken: "existing-token",
457
+ }),
349
458
  );
350
459
 
351
460
  const result = await shareAgent(client, "acme/support-agent", "acme", {
@@ -360,27 +469,27 @@ describe("shareAgent --reset-link (rotatable share token)", () => {
360
469
 
361
470
  describe("shareAgent errors", () => {
362
471
  it("fails fast with org guidance for a bare slug and no org", async () => {
363
- const { client, calls } = fakeClient(makeAgent());
472
+ const { client, applies } = fakeClient(makeAgent(), null);
364
473
 
365
474
  const err = await shareAgent(client, "support-agent", "", { enabled: true, ...CLOUD }).catch((e: unknown) => e);
366
475
 
367
476
  expect(err).toBeInstanceOf(UsageError);
368
477
  expect((err as Error).message).toContain("organization not set");
369
478
  expect(classify(err)?.exitCode).toBe(ExitCode.Usage);
370
- expect(calls).toHaveLength(0);
479
+ expect(applies).toHaveLength(0);
371
480
  });
372
481
 
373
482
  it("does not apply the org guard to an ID reference", async () => {
374
- const { client, calls } = fakeClient(makeAgent({ enabled: false }));
483
+ const { client, applies } = fakeClient(makeAgent(), makeShare({ enabled: false }));
375
484
 
376
485
  await shareAgent(client, "agt_01ARZ3NDEKTSV4RRFFQ69G5FAV", "", { enabled: true, ...CLOUD });
377
486
 
378
- expect(calls).toHaveLength(1);
487
+ expect(applies).toHaveLength(1);
379
488
  });
380
489
 
381
490
  it("propagates server rejections (permission denied) unchanged", async () => {
382
491
  const denied = new Error("permission denied: can_edit required");
383
- const { client } = fakeClient(makeAgent({ enabled: false }), { failWith: denied });
492
+ const { client } = fakeClient(makeAgent(), makeShare({ enabled: false }), { failWith: denied });
384
493
 
385
494
  await expect(shareAgent(client, "acme/support-agent", "acme", { enabled: true, ...CLOUD })).rejects.toThrow(
386
495
  /permission denied/,