@stigmer/cli 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.
- package/package.json +5 -5
- package/resources/share.d.ts.map +1 -1
- package/resources/share.js +37 -15
- package/resources/share.js.map +1 -1
- package/src/resources/share.test.ts +73 -0
- package/src/resources/share.ts +42 -13
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stigmer/cli",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.14",
|
|
4
4
|
"description": "Stigmer command-line interface — manage agents, workflows, MCP servers, skills, and executions from the terminal",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -35,10 +35,10 @@
|
|
|
35
35
|
"@connectrpc/connect": "^2.0.0",
|
|
36
36
|
"@connectrpc/connect-node": "^2.1.1",
|
|
37
37
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
38
|
-
"@stigmer/ink": "3.1.
|
|
39
|
-
"@stigmer/mcp-server": "3.1.
|
|
40
|
-
"@stigmer/protos": "3.1.
|
|
41
|
-
"@stigmer/sdk": "3.1.
|
|
38
|
+
"@stigmer/ink": "3.1.14",
|
|
39
|
+
"@stigmer/mcp-server": "3.1.14",
|
|
40
|
+
"@stigmer/protos": "3.1.14",
|
|
41
|
+
"@stigmer/sdk": "3.1.14",
|
|
42
42
|
"commander": "^12.1.0",
|
|
43
43
|
"fflate": "^0.8.3",
|
|
44
44
|
"ink": "^7.0.0",
|
package/resources/share.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"share.d.ts","sourceRoot":"","sources":["../../src/resources/share.ts"],"names":[],"mappings":"AA8BA,OAAO,EAIL,KAAK,OAAO,EACb,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAInD,4EAA4E;AAC5E,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,KAAK,CAAC;AAE7C,iEAAiE;AACjE,MAAM,WAAW,iBAAiB;IAChC,mEAAmE;IACnE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,aAAa,CAAC;IAClC;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,kEAAkE;IAClE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAsB,UAAU,CAC9B,MAAM,EAAE,OAAO,EACf,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,aAAa,CAAC,
|
|
1
|
+
{"version":3,"file":"share.d.ts","sourceRoot":"","sources":["../../src/resources/share.ts"],"names":[],"mappings":"AA8BA,OAAO,EAIL,KAAK,OAAO,EACb,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAInD,4EAA4E;AAC5E,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,KAAK,CAAC;AAE7C,iEAAiE;AACjE,MAAM,WAAW,iBAAiB;IAChC,mEAAmE;IACnE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,aAAa,CAAC;IAClC;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,kEAAkE;IAClE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAsB,UAAU,CAC9B,MAAM,EAAE,OAAO,EACf,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,aAAa,CAAC,CA4FxB"}
|
package/resources/share.js
CHANGED
|
@@ -43,10 +43,28 @@ export async function shareAgent(client, ref, org, options) {
|
|
|
43
43
|
` stigmer share agent ${ref} --org <org>`);
|
|
44
44
|
}
|
|
45
45
|
const agent = await resolveAgentRef(client, ref, org);
|
|
46
|
-
|
|
46
|
+
const agentOrg = agent.metadata?.org ?? "";
|
|
47
|
+
// The share lands in the CALLER's resolved org (decision 013 D8): when
|
|
48
|
+
// it differs from the agent's, this is a cross-org share — the caller
|
|
49
|
+
// org's own channel (URL, billing, credentials) of another org's
|
|
50
|
+
// marketplace-public agent. An empty org (ID ref without context)
|
|
51
|
+
// falls back to the agent's own — the Phase A same-org form.
|
|
52
|
+
const shareOrg = org || agentOrg;
|
|
53
|
+
const isCrossOrg = shareOrg !== agentOrg;
|
|
54
|
+
let share = await resolveCanonicalShare(client, agent, shareOrg);
|
|
47
55
|
const current = share?.spec;
|
|
48
56
|
const currentAudience = audienceFromProto(current?.audience);
|
|
49
57
|
const targetAudience = options.audience ?? currentAudience;
|
|
58
|
+
// Cross-org shares are public-audience only (decision 013 D3). The
|
|
59
|
+
// server refuses this too; refusing here names the remedy instead of
|
|
60
|
+
// surfacing a bare FAILED_PRECONDITION — and mirrors the share dialog,
|
|
61
|
+
// which hides the audience selector in cross-org mode.
|
|
62
|
+
if (isCrossOrg && targetAudience === "org") {
|
|
63
|
+
throw new UsageError("--audience org is not available for another organization's agent\n\n" +
|
|
64
|
+
`A share of ${agentOrg}/${agent.metadata?.slug ?? ""} in your organization\n` +
|
|
65
|
+
"(" + shareOrg + ") serves anyone with the link. Org-members-only sharing\n" +
|
|
66
|
+
"is limited to the agent's own organization.");
|
|
67
|
+
}
|
|
50
68
|
// Resetting the link is meaningless for org-members-only shares: their
|
|
51
69
|
// access is gated by live membership, and the member link never carries
|
|
52
70
|
// the token. Refuse rather than silently rotating an unused secret —
|
|
@@ -71,7 +89,7 @@ export async function shareAgent(client, ref, org, options) {
|
|
|
71
89
|
// Apply the COMPLETE spec with only the requested fields changed, so a
|
|
72
90
|
// CLI toggle can never wipe console-configured origins, visitor
|
|
73
91
|
// messages, credential bindings, or an org-members-only audience.
|
|
74
|
-
share = await client.agentShare.apply(preservingShareInput(agent, share, options.enabled, targetAudience));
|
|
92
|
+
share = await client.agentShare.apply(preservingShareInput(agent, share, shareOrg, options.enabled, targetAudience));
|
|
75
93
|
}
|
|
76
94
|
// The rotation is a separate targeted RPC (the token is server-owned
|
|
77
95
|
// status, not part of the spec apply merges). The returned share carries
|
|
@@ -84,19 +102,22 @@ export async function shareAgent(client, ref, org, options) {
|
|
|
84
102
|
return describeOutcome(agent, share, options, targetAudience, alreadyInState && options.resetLink !== true);
|
|
85
103
|
}
|
|
86
104
|
/**
|
|
87
|
-
* The agent's canonical share
|
|
88
|
-
* server's default on create), else the
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
105
|
+
* The agent's canonical share **within one sharing org**: the one whose
|
|
106
|
+
* slug equals the agent's (the server's default on create), else the
|
|
107
|
+
* first entry in that org, else null when the agent has never been shared
|
|
108
|
+
* there. Mirrors the web dialog's selection so both surfaces manage the
|
|
109
|
+
* same row. The org filter is what keeps each org on its own channel
|
|
110
|
+
* (decision 013): an agent may be shared in N orgs, and toggling here
|
|
111
|
+
* must never edit another org's share.
|
|
92
112
|
*/
|
|
93
|
-
async function resolveCanonicalShare(client, agent) {
|
|
113
|
+
async function resolveCanonicalShare(client, agent, shareOrg) {
|
|
94
114
|
const result = await client.agentShare.getByAgent(create(GetAgentSharesByAgentRequestSchema, {
|
|
95
115
|
agentId: agent.metadata?.id ?? "",
|
|
96
116
|
}));
|
|
97
117
|
const agentSlug = agent.metadata?.slug ?? "";
|
|
98
|
-
|
|
99
|
-
|
|
118
|
+
const inOrg = result.items.filter((share) => share.metadata?.org === shareOrg);
|
|
119
|
+
return (inOrg.find((share) => share.metadata?.slug === agentSlug) ??
|
|
120
|
+
inOrg[0] ??
|
|
100
121
|
null);
|
|
101
122
|
}
|
|
102
123
|
// Unspecified means public by contract (a share created without an explicit
|
|
@@ -109,15 +130,16 @@ function audienceFromProto(audience) {
|
|
|
109
130
|
// was never shared before). Identity comes from the existing share when one
|
|
110
131
|
// exists — a manifest-created share may carry a non-default slug, and
|
|
111
132
|
// applying with the agent's slug would create a SECOND share — and from the
|
|
112
|
-
// agent otherwise (the server's own D2 default,
|
|
113
|
-
//
|
|
114
|
-
//
|
|
115
|
-
|
|
133
|
+
// sharing org + the agent's slug otherwise (the server's own D2 default,
|
|
134
|
+
// made explicit; for a cross-org share the org is the CALLER's, not the
|
|
135
|
+
// agent's — decision 013). The audience is written explicitly — never left
|
|
136
|
+
// unspecified — so a console-managed org share can't drift back to public.
|
|
137
|
+
function preservingShareInput(agent, share, shareOrg, enabled, audience) {
|
|
116
138
|
const agentOrg = agent.metadata?.org ?? "";
|
|
117
139
|
const agentSlug = agent.metadata?.slug ?? "";
|
|
118
140
|
const current = share?.spec;
|
|
119
141
|
return {
|
|
120
|
-
org: share?.metadata?.org ||
|
|
142
|
+
org: share?.metadata?.org || shareOrg,
|
|
121
143
|
slug: share?.metadata?.slug || agentSlug,
|
|
122
144
|
name: share?.metadata?.name || agent.metadata?.name || agentSlug,
|
|
123
145
|
agentRef: { org: agentOrg, slug: agentSlug },
|
package/resources/share.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"share.js","sourceRoot":"","sources":["../../src/resources/share.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,wCAAwC;AACxC,EAAE;AACF,6EAA6E;AAC7E,6EAA6E;AAC7E,4EAA4E;AAC5E,8EAA8E;AAC9E,oCAAoC;AACpC,EAAE;AACF,sEAAsE;AACtE,4EAA4E;AAC5E,6EAA6E;AAC7E,6EAA6E;AAC7E,2EAA2E;AAC3E,4EAA4E;AAC5E,2EAA2E;AAC3E,EAAE;AACF,+EAA+E;AAC/E,uEAAuE;AACvE,+EAA+E;AAC/E,4EAA4E;AAE5E,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAG5C,OAAO,EACL,kCAAkC,EAClC,0BAA0B,GAC3B,MAAM,wDAAwD,CAAC;AAChE,OAAO,EAAE,kBAAkB,EAAE,MAAM,0DAA0D,CAAC;AAC9F,OAAO,EACL,YAAY,EACZ,iBAAiB,GAGlB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAgCnD;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,MAAe,EACf,GAAW,EACX,GAAW,EACX,OAA0B;IAE1B,sEAAsE;IACtE,yEAAyE;IACzE,IAAI,GAAG,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACxD,MAAM,IAAI,UAAU,CAClB,0BAA0B;YACxB,gBAAgB;YAChB,4CAA4C;YAC5C,yBAAyB,GAAG,cAAc,CAC7C,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,eAAe,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;IACtD,IAAI,KAAK,GAAG,MAAM,qBAAqB,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"share.js","sourceRoot":"","sources":["../../src/resources/share.ts"],"names":[],"mappings":"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"}
|
|
@@ -467,6 +467,79 @@ describe("shareAgent --reset-link (rotatable share token)", () => {
|
|
|
467
467
|
});
|
|
468
468
|
});
|
|
469
469
|
|
|
470
|
+
describe("shareAgent cross-org (decision 013)", () => {
|
|
471
|
+
it("lands the share in the CALLER's org, agent_ref keeps the provider's", async () => {
|
|
472
|
+
const { client, applies } = fakeClient(makeAgent(), null);
|
|
473
|
+
|
|
474
|
+
const result = await shareAgent(client, "acme/support-agent", "consumer-org", {
|
|
475
|
+
enabled: true,
|
|
476
|
+
...CLOUD,
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
expect(applies).toHaveLength(1);
|
|
480
|
+
// The share is the caller org's channel: its URL, billing, and
|
|
481
|
+
// credentials — while the blueprint reference stays the provider's.
|
|
482
|
+
expect(applies[0].org).toBe("consumer-org");
|
|
483
|
+
expect(applies[0].slug).toBe("support-agent");
|
|
484
|
+
expect(applies[0].agentRef).toEqual({ org: "acme", slug: "support-agent" });
|
|
485
|
+
|
|
486
|
+
const link = result.sections.find((s) => s.title === "Public chat link");
|
|
487
|
+
expect(link?.items).toEqual(["https://app.stigmer.ai/chat/consumer-org/support-agent"]);
|
|
488
|
+
expect(result.hints.some((h) => h.includes("consumer-org's credits"))).toBe(true);
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
it("never edits the provider org's own share (canonical resolution is org-scoped)", async () => {
|
|
492
|
+
// The provider already shares this agent with its own config; the
|
|
493
|
+
// caller's toggle must create the CALLER org's share, not flip (or
|
|
494
|
+
// inherit the config of) the provider's row.
|
|
495
|
+
const providerShare = makeShare({
|
|
496
|
+
enabled: true,
|
|
497
|
+
allowedOrigins: ["https://provider.example.com"],
|
|
498
|
+
});
|
|
499
|
+
const { client, applies } = fakeClient(makeAgent(), providerShare);
|
|
500
|
+
|
|
501
|
+
await shareAgent(client, "acme/support-agent", "consumer-org", {
|
|
502
|
+
enabled: true,
|
|
503
|
+
...CLOUD,
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
expect(applies).toHaveLength(1);
|
|
507
|
+
expect(applies[0].org).toBe("consumer-org");
|
|
508
|
+
// Fresh channel, fresh config: the provider's origins must not leak
|
|
509
|
+
// into the caller org's share.
|
|
510
|
+
expect(applies[0].allowedOrigins).toEqual([]);
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
it("refuses --audience org with guidance (cross-org shares are public only)", async () => {
|
|
514
|
+
const { client, applies } = fakeClient(makeAgent(), null);
|
|
515
|
+
|
|
516
|
+
const err = await shareAgent(client, "acme/support-agent", "consumer-org", {
|
|
517
|
+
enabled: true,
|
|
518
|
+
audience: "org",
|
|
519
|
+
...CLOUD,
|
|
520
|
+
}).catch((e: unknown) => e);
|
|
521
|
+
|
|
522
|
+
expect(err).toBeInstanceOf(UsageError);
|
|
523
|
+
expect((err as Error).message).toContain("agent's own organization");
|
|
524
|
+
expect(classify(err)?.exitCode).toBe(ExitCode.Usage);
|
|
525
|
+
expect(applies).toHaveLength(0);
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
it("passes the server's fail-loud dependency refusal through unchanged", async () => {
|
|
529
|
+
// The D5 sweep names every non-public dependency; the CLI must
|
|
530
|
+
// surface that message verbatim — it tells the caller exactly what
|
|
531
|
+
// to ask the provider org to publish.
|
|
532
|
+
const refusal = new Error(
|
|
533
|
+
"cannot share acme/support-agent across organizations: it references resources that are not public: skill acme/private-skill",
|
|
534
|
+
);
|
|
535
|
+
const { client } = fakeClient(makeAgent(), null, { failWith: refusal });
|
|
536
|
+
|
|
537
|
+
await expect(
|
|
538
|
+
shareAgent(client, "acme/support-agent", "consumer-org", { enabled: true, ...CLOUD }),
|
|
539
|
+
).rejects.toThrow(/skill acme\/private-skill/);
|
|
540
|
+
});
|
|
541
|
+
});
|
|
542
|
+
|
|
470
543
|
describe("shareAgent errors", () => {
|
|
471
544
|
it("fails fast with org guidance for a bare slug and no org", async () => {
|
|
472
545
|
const { client, applies } = fakeClient(makeAgent(), null);
|
package/src/resources/share.ts
CHANGED
|
@@ -93,12 +93,33 @@ export async function shareAgent(
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
const agent = await resolveAgentRef(client, ref, org);
|
|
96
|
-
|
|
96
|
+
const agentOrg = agent.metadata?.org ?? "";
|
|
97
|
+
// The share lands in the CALLER's resolved org (decision 013 D8): when
|
|
98
|
+
// it differs from the agent's, this is a cross-org share — the caller
|
|
99
|
+
// org's own channel (URL, billing, credentials) of another org's
|
|
100
|
+
// marketplace-public agent. An empty org (ID ref without context)
|
|
101
|
+
// falls back to the agent's own — the Phase A same-org form.
|
|
102
|
+
const shareOrg = org || agentOrg;
|
|
103
|
+
const isCrossOrg = shareOrg !== agentOrg;
|
|
104
|
+
let share = await resolveCanonicalShare(client, agent, shareOrg);
|
|
97
105
|
const current = share?.spec;
|
|
98
106
|
|
|
99
107
|
const currentAudience = audienceFromProto(current?.audience);
|
|
100
108
|
const targetAudience = options.audience ?? currentAudience;
|
|
101
109
|
|
|
110
|
+
// Cross-org shares are public-audience only (decision 013 D3). The
|
|
111
|
+
// server refuses this too; refusing here names the remedy instead of
|
|
112
|
+
// surfacing a bare FAILED_PRECONDITION — and mirrors the share dialog,
|
|
113
|
+
// which hides the audience selector in cross-org mode.
|
|
114
|
+
if (isCrossOrg && targetAudience === "org") {
|
|
115
|
+
throw new UsageError(
|
|
116
|
+
"--audience org is not available for another organization's agent\n\n" +
|
|
117
|
+
`A share of ${agentOrg}/${agent.metadata?.slug ?? ""} in your organization\n` +
|
|
118
|
+
"(" + shareOrg + ") serves anyone with the link. Org-members-only sharing\n" +
|
|
119
|
+
"is limited to the agent's own organization.",
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
102
123
|
// Resetting the link is meaningless for org-members-only shares: their
|
|
103
124
|
// access is gated by live membership, and the member link never carries
|
|
104
125
|
// the token. Refuse rather than silently rotating an unused secret —
|
|
@@ -129,7 +150,7 @@ export async function shareAgent(
|
|
|
129
150
|
// CLI toggle can never wipe console-configured origins, visitor
|
|
130
151
|
// messages, credential bindings, or an org-members-only audience.
|
|
131
152
|
share = await client.agentShare.apply(
|
|
132
|
-
preservingShareInput(agent, share, options.enabled, targetAudience),
|
|
153
|
+
preservingShareInput(agent, share, shareOrg, options.enabled, targetAudience),
|
|
133
154
|
);
|
|
134
155
|
}
|
|
135
156
|
|
|
@@ -154,15 +175,18 @@ export async function shareAgent(
|
|
|
154
175
|
}
|
|
155
176
|
|
|
156
177
|
/**
|
|
157
|
-
* The agent's canonical share
|
|
158
|
-
* server's default on create), else the
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
178
|
+
* The agent's canonical share **within one sharing org**: the one whose
|
|
179
|
+
* slug equals the agent's (the server's default on create), else the
|
|
180
|
+
* first entry in that org, else null when the agent has never been shared
|
|
181
|
+
* there. Mirrors the web dialog's selection so both surfaces manage the
|
|
182
|
+
* same row. The org filter is what keeps each org on its own channel
|
|
183
|
+
* (decision 013): an agent may be shared in N orgs, and toggling here
|
|
184
|
+
* must never edit another org's share.
|
|
162
185
|
*/
|
|
163
186
|
async function resolveCanonicalShare(
|
|
164
187
|
client: Stigmer,
|
|
165
188
|
agent: Agent,
|
|
189
|
+
shareOrg: string,
|
|
166
190
|
): Promise<AgentShare | null> {
|
|
167
191
|
const result = await client.agentShare.getByAgent(
|
|
168
192
|
create(GetAgentSharesByAgentRequestSchema, {
|
|
@@ -170,9 +194,12 @@ async function resolveCanonicalShare(
|
|
|
170
194
|
}),
|
|
171
195
|
);
|
|
172
196
|
const agentSlug = agent.metadata?.slug ?? "";
|
|
197
|
+
const inOrg = result.items.filter(
|
|
198
|
+
(share) => share.metadata?.org === shareOrg,
|
|
199
|
+
);
|
|
173
200
|
return (
|
|
174
|
-
|
|
175
|
-
|
|
201
|
+
inOrg.find((share) => share.metadata?.slug === agentSlug) ??
|
|
202
|
+
inOrg[0] ??
|
|
176
203
|
null
|
|
177
204
|
);
|
|
178
205
|
}
|
|
@@ -188,12 +215,14 @@ function audienceFromProto(audience: AgentShareAudience | undefined): ShareAudie
|
|
|
188
215
|
// was never shared before). Identity comes from the existing share when one
|
|
189
216
|
// exists — a manifest-created share may carry a non-default slug, and
|
|
190
217
|
// applying with the agent's slug would create a SECOND share — and from the
|
|
191
|
-
// agent otherwise (the server's own D2 default,
|
|
192
|
-
//
|
|
193
|
-
//
|
|
218
|
+
// sharing org + the agent's slug otherwise (the server's own D2 default,
|
|
219
|
+
// made explicit; for a cross-org share the org is the CALLER's, not the
|
|
220
|
+
// agent's — decision 013). The audience is written explicitly — never left
|
|
221
|
+
// unspecified — so a console-managed org share can't drift back to public.
|
|
194
222
|
function preservingShareInput(
|
|
195
223
|
agent: Agent,
|
|
196
224
|
share: AgentShare | null,
|
|
225
|
+
shareOrg: string,
|
|
197
226
|
enabled: boolean,
|
|
198
227
|
audience: ShareAudience,
|
|
199
228
|
): AgentShareInput {
|
|
@@ -201,7 +230,7 @@ function preservingShareInput(
|
|
|
201
230
|
const agentSlug = agent.metadata?.slug ?? "";
|
|
202
231
|
const current = share?.spec;
|
|
203
232
|
return {
|
|
204
|
-
org: share?.metadata?.org ||
|
|
233
|
+
org: share?.metadata?.org || shareOrg,
|
|
205
234
|
slug: share?.metadata?.slug || agentSlug,
|
|
206
235
|
name: share?.metadata?.name || agent.metadata?.name || agentSlug,
|
|
207
236
|
agentRef: { org: agentOrg, slug: agentSlug },
|