@stigmer/react 3.1.13 → 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/agent/AgentDetailView.d.ts +7 -9
- package/agent/AgentDetailView.d.ts.map +1 -1
- package/agent/AgentDetailView.js +22 -30
- package/agent/AgentDetailView.js.map +1 -1
- package/index.d.ts +2 -2
- package/index.d.ts.map +1 -1
- package/index.js +2 -2
- package/index.js.map +1 -1
- package/package.json +4 -4
- package/sharing/AgentShareList.d.ts +44 -0
- package/sharing/AgentShareList.d.ts.map +1 -0
- package/sharing/AgentShareList.js +149 -0
- package/sharing/AgentShareList.js.map +1 -0
- package/sharing/ShareAgentDialog.d.ts +38 -25
- package/sharing/ShareAgentDialog.d.ts.map +1 -1
- package/sharing/ShareAgentDialog.js +131 -62
- package/sharing/ShareAgentDialog.js.map +1 -1
- package/sharing/index.d.ts +10 -8
- package/sharing/index.d.ts.map +1 -1
- package/sharing/index.js +5 -4
- package/sharing/index.js.map +1 -1
- package/sharing/useAgentShares.d.ts +42 -0
- package/sharing/useAgentShares.d.ts.map +1 -0
- package/sharing/useAgentShares.js +38 -0
- package/sharing/useAgentShares.js.map +1 -0
- package/sharing/useCanCreateAgentShare.d.ts +43 -0
- package/sharing/useCanCreateAgentShare.d.ts.map +1 -0
- package/sharing/useCanCreateAgentShare.js +44 -0
- package/sharing/useCanCreateAgentShare.js.map +1 -0
- package/sharing/useDeleteAgentShare.d.ts +42 -0
- package/sharing/useDeleteAgentShare.d.ts.map +1 -0
- package/sharing/useDeleteAgentShare.js +45 -0
- package/sharing/useDeleteAgentShare.js.map +1 -0
- package/sharing/useSaveAgentShare.d.ts +43 -18
- package/sharing/useSaveAgentShare.d.ts.map +1 -1
- package/sharing/useSaveAgentShare.js +55 -22
- package/sharing/useSaveAgentShare.js.map +1 -1
- package/src/agent/AgentDetailView.tsx +34 -43
- package/src/index.ts +11 -9
- package/src/sharing/AgentShareList.tsx +507 -0
- package/src/sharing/ShareAgentDialog.tsx +310 -112
- package/src/sharing/__tests__/AgentShareList.test.tsx +388 -0
- package/src/sharing/__tests__/ShareAgentDialog.test.tsx +311 -312
- package/src/sharing/__tests__/useAgentShares.test.tsx +145 -0
- package/src/sharing/__tests__/useCanCreateAgentShare.test.tsx +190 -0
- package/src/sharing/index.ts +10 -12
- package/src/sharing/useAgentShares.ts +68 -0
- package/src/sharing/useCanCreateAgentShare.ts +74 -0
- package/src/sharing/useDeleteAgentShare.ts +73 -0
- package/src/sharing/useSaveAgentShare.ts +73 -23
- package/styles.css +1 -1
- package/sharing/useAgentShare.d.ts +0 -49
- package/sharing/useAgentShare.d.ts.map +0 -1
- package/sharing/useAgentShare.js +0 -64
- package/sharing/useAgentShare.js.map +0 -1
- package/sharing/useCreateExternalShareLink.d.ts +0 -90
- package/sharing/useCreateExternalShareLink.d.ts.map +0 -1
- package/sharing/useCreateExternalShareLink.js +0 -65
- package/sharing/useCreateExternalShareLink.js.map +0 -1
- package/sharing/useShareAgent.d.ts +0 -69
- package/sharing/useShareAgent.d.ts.map +0 -1
- package/sharing/useShareAgent.js +0 -42
- package/sharing/useShareAgent.js.map +0 -1
- package/src/sharing/__tests__/useAgentShare.test.tsx +0 -210
- package/src/sharing/__tests__/useCreateExternalShareLink.test.tsx +0 -194
- package/src/sharing/__tests__/useShareAgent.test.tsx +0 -131
- package/src/sharing/useAgentShare.ts +0 -105
- package/src/sharing/useCreateExternalShareLink.tsx +0 -141
- package/src/sharing/useShareAgent.tsx +0 -107
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import type { Agent } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
|
|
2
|
-
import type { AgentShare } from "@stigmer/protos/ai/stigmer/agentic/agentshare/v1/api_pb";
|
|
3
|
-
/** Return value of {@link useAgentShare}. */
|
|
4
|
-
export interface UseAgentShareReturn {
|
|
5
|
-
/**
|
|
6
|
-
* The agent's canonical share, or `null` while loading, on error, or
|
|
7
|
-
* when the agent has never been shared. `share === null && !isLoading
|
|
8
|
-
* && !error` means "no share exists yet" — the first save creates it.
|
|
9
|
-
*/
|
|
10
|
-
readonly share: AgentShare | null;
|
|
11
|
-
/** `true` while the initial fetch or a refetch is in flight. */
|
|
12
|
-
readonly isLoading: boolean;
|
|
13
|
-
/** `true` while a background refetch is in flight and stale data is shown. */
|
|
14
|
-
readonly isRefetching: boolean;
|
|
15
|
-
/** Error from the last failed request, or `null` when healthy. */
|
|
16
|
-
readonly error: Error | null;
|
|
17
|
-
/** Discard cached data and re-fetch the share from the server. */
|
|
18
|
-
readonly refetch: () => void;
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Data hook that loads an agent's canonical {@link AgentShare} — the
|
|
22
|
-
* resource carrying the hosted-chat channel configuration (audience,
|
|
23
|
-
* allowed origins, visitor messages, tool credentials, link token).
|
|
24
|
-
*
|
|
25
|
-
* Sharing is channel configuration, not agent behavior (decision 011):
|
|
26
|
-
* it lives in its own resource, so reading the agent alone can never
|
|
27
|
-
* tell whether it is shared. This hook is how owner-side surfaces (the
|
|
28
|
-
* Share dialog) resolve that state.
|
|
29
|
-
*
|
|
30
|
-
* `shareOrg` scopes resolution to one sharing org's channel and defaults
|
|
31
|
-
* to the agent's own org (the owner's share). Pass the viewer's org to
|
|
32
|
-
* manage a **cross-org share** — the viewer's own channel of another
|
|
33
|
-
* org's marketplace-public agent (decision 013).
|
|
34
|
-
*
|
|
35
|
-
* Pass `null` for `agent` to skip fetching (stable no-op) — useful
|
|
36
|
-
* while the agent is still loading. A resolved `null` share means the
|
|
37
|
-
* agent has never been shared in `shareOrg`; the first save creates the
|
|
38
|
-
* share.
|
|
39
|
-
*
|
|
40
|
-
* @example
|
|
41
|
-
* ```tsx
|
|
42
|
-
* const { share, isLoading } = useAgentShare(agent);
|
|
43
|
-
*
|
|
44
|
-
* if (isLoading) return <Spinner />;
|
|
45
|
-
* const enabled = share?.spec?.enabled ?? false;
|
|
46
|
-
* ```
|
|
47
|
-
*/
|
|
48
|
-
export declare function useAgentShare(agent: Agent | null, shareOrg?: string): UseAgentShareReturn;
|
|
49
|
-
//# sourceMappingURL=useAgentShare.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useAgentShare.d.ts","sourceRoot":"","sources":["../../src/sharing/useAgentShare.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oDAAoD,CAAC;AAChF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yDAAyD,CAAC;AAK1F,6CAA6C;AAC7C,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IAClC,gEAAgE;IAChE,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,8EAA8E;IAC9E,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,kEAAkE;IAClE,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IAC7B,kEAAkE;IAClE,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;CAC9B;AAyBD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,KAAK,GAAG,IAAI,EACnB,QAAQ,CAAC,EAAE,MAAM,GAChB,mBAAmB,CAuBrB"}
|
package/sharing/useAgentShare.js
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { create } from "@bufbuild/protobuf";
|
|
3
|
-
import { GetAgentSharesByAgentRequestSchema } from "@stigmer/protos/ai/stigmer/agentic/agentshare/v1/io_pb";
|
|
4
|
-
import { useStigmer } from "../hooks.js";
|
|
5
|
-
import { useFetch } from "../internal/useFetch.js";
|
|
6
|
-
/**
|
|
7
|
-
* The canonical share among an agent's shares **within one sharing org**:
|
|
8
|
-
* the one whose slug equals the agent's slug (the server's default when a
|
|
9
|
-
* share is created without an explicit slug), falling back to the first
|
|
10
|
-
* entry in that org. The data model allows N shares per agent across N
|
|
11
|
-
* orgs (decision 011 D3 + decision 013), so the org filter is what keeps
|
|
12
|
-
* each org's dialog on its own channel — without it, an owner who can
|
|
13
|
-
* also see another org's share of the same agent would edit the wrong
|
|
14
|
-
* one. Extra shares created via manifests never confuse the dialog.
|
|
15
|
-
*/
|
|
16
|
-
function pickCanonicalShare(shares, shareOrg, agentSlug) {
|
|
17
|
-
const inOrg = shares.filter((share) => share.metadata?.org === shareOrg);
|
|
18
|
-
return (inOrg.find((share) => share.metadata?.slug === agentSlug) ??
|
|
19
|
-
inOrg[0] ??
|
|
20
|
-
null);
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Data hook that loads an agent's canonical {@link AgentShare} — the
|
|
24
|
-
* resource carrying the hosted-chat channel configuration (audience,
|
|
25
|
-
* allowed origins, visitor messages, tool credentials, link token).
|
|
26
|
-
*
|
|
27
|
-
* Sharing is channel configuration, not agent behavior (decision 011):
|
|
28
|
-
* it lives in its own resource, so reading the agent alone can never
|
|
29
|
-
* tell whether it is shared. This hook is how owner-side surfaces (the
|
|
30
|
-
* Share dialog) resolve that state.
|
|
31
|
-
*
|
|
32
|
-
* `shareOrg` scopes resolution to one sharing org's channel and defaults
|
|
33
|
-
* to the agent's own org (the owner's share). Pass the viewer's org to
|
|
34
|
-
* manage a **cross-org share** — the viewer's own channel of another
|
|
35
|
-
* org's marketplace-public agent (decision 013).
|
|
36
|
-
*
|
|
37
|
-
* Pass `null` for `agent` to skip fetching (stable no-op) — useful
|
|
38
|
-
* while the agent is still loading. A resolved `null` share means the
|
|
39
|
-
* agent has never been shared in `shareOrg`; the first save creates the
|
|
40
|
-
* share.
|
|
41
|
-
*
|
|
42
|
-
* @example
|
|
43
|
-
* ```tsx
|
|
44
|
-
* const { share, isLoading } = useAgentShare(agent);
|
|
45
|
-
*
|
|
46
|
-
* if (isLoading) return <Spinner />;
|
|
47
|
-
* const enabled = share?.spec?.enabled ?? false;
|
|
48
|
-
* ```
|
|
49
|
-
*/
|
|
50
|
-
export function useAgentShare(agent, shareOrg) {
|
|
51
|
-
const stigmer = useStigmer();
|
|
52
|
-
const agentId = agent?.metadata?.id ?? "";
|
|
53
|
-
const agentSlug = agent?.metadata?.slug ?? "";
|
|
54
|
-
const resolvedShareOrg = shareOrg || (agent?.metadata?.org ?? "");
|
|
55
|
-
const fetchFn = agentId
|
|
56
|
-
? async () => {
|
|
57
|
-
const result = await stigmer.agentShare.getByAgent(create(GetAgentSharesByAgentRequestSchema, { agentId }));
|
|
58
|
-
return pickCanonicalShare(result.items, resolvedShareOrg, agentSlug);
|
|
59
|
-
}
|
|
60
|
-
: null;
|
|
61
|
-
const { data: share, isLoading, isRefetching, error, refetch } = useFetch(fetchFn, [agentId, agentSlug, resolvedShareOrg, stigmer], null);
|
|
62
|
-
return { share, isLoading, isRefetching, error, refetch };
|
|
63
|
-
}
|
|
64
|
-
//# sourceMappingURL=useAgentShare.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useAgentShare.js","sourceRoot":"","sources":["../../src/sharing/useAgentShare.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAG5C,OAAO,EAAE,kCAAkC,EAAE,MAAM,wDAAwD,CAAC;AAC5G,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAoBnD;;;;;;;;;GASG;AACH,SAAS,kBAAkB,CACzB,MAA6B,EAC7B,QAAgB,EAChB,SAAiB;IAEjB,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,KAAK,QAAQ,CAAC,CAAC;IACzE,OAAO,CACL,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,KAAK,SAAS,CAAC;QACzD,KAAK,CAAC,CAAC,CAAC;QACR,IAAI,CACL,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,UAAU,aAAa,CAC3B,KAAmB,EACnB,QAAiB;IAEjB,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAE7B,MAAM,OAAO,GAAG,KAAK,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,CAAC;IAC1C,MAAM,SAAS,GAAG,KAAK,EAAE,QAAQ,EAAE,IAAI,IAAI,EAAE,CAAC;IAC9C,MAAM,gBAAgB,GAAG,QAAQ,IAAI,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,CAAC,CAAC;IAElE,MAAM,OAAO,GAAG,OAAO;QACrB,CAAC,CAAC,KAAK,IAAI,EAAE;YACT,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,UAAU,CAChD,MAAM,CAAC,kCAAkC,EAAE,EAAE,OAAO,EAAE,CAAC,CACxD,CAAC;YACF,OAAO,kBAAkB,CAAC,MAAM,CAAC,KAAK,EAAE,gBAAgB,EAAE,SAAS,CAAC,CAAC;QACvE,CAAC;QACH,CAAC,CAAC,IAAI,CAAC;IAET,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,QAAQ,CACvE,OAAO,EACP,CAAC,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,OAAO,CAAC,EAC/C,IAAyB,CAC1B,CAAC;IAEF,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;AAC5D,CAAC"}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import { type ReactNode } from "react";
|
|
2
|
-
import type { Agent } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
|
|
3
|
-
import type { DetailAction } from "../resource-detail/types.js";
|
|
4
|
-
/** Arguments for {@link useCreateExternalShareLink}. */
|
|
5
|
-
export interface UseCreateExternalShareLinkArgs {
|
|
6
|
-
/**
|
|
7
|
-
* The agent to share, or `null` while it is still loading. When
|
|
8
|
-
* `null`, {@link UseCreateExternalShareLinkReturn.action} is `null`
|
|
9
|
-
* and the dialog renders nothing — safe to call before the resource
|
|
10
|
-
* is ready.
|
|
11
|
-
*/
|
|
12
|
-
readonly agent: Agent | null;
|
|
13
|
-
/**
|
|
14
|
-
* The viewer's own organization — the org that would own the external
|
|
15
|
-
* share, pay for its traffic, and host its URL. When empty or equal to
|
|
16
|
-
* the agent's org, the action is `null` (the same-org Share entry from
|
|
17
|
-
* {@link useShareAgent} covers that case).
|
|
18
|
-
*/
|
|
19
|
-
readonly viewerOrg: string;
|
|
20
|
-
/**
|
|
21
|
-
* Builds the absolute public chat URL for the shared agent. Same
|
|
22
|
-
* contract as {@link useShareAgent}: the host application owns URL
|
|
23
|
-
* construction. When omitted, the dialog falls back to the relative
|
|
24
|
-
* `/chat/<org>/<slug>`.
|
|
25
|
-
*/
|
|
26
|
-
readonly buildShareUrl?: (org: string, slug: string) => string;
|
|
27
|
-
/** Called after any sharing change is persisted. */
|
|
28
|
-
readonly onSharingChanged?: () => void;
|
|
29
|
-
/** Menu-item label. @default "Create share link" */
|
|
30
|
-
readonly label?: string;
|
|
31
|
-
}
|
|
32
|
-
/** Return value of {@link useCreateExternalShareLink}. */
|
|
33
|
-
export interface UseCreateExternalShareLinkReturn {
|
|
34
|
-
/**
|
|
35
|
-
* A ready-to-spread {@link DetailAction} for a kebab/overflow menu, or
|
|
36
|
-
* `null` when the entry does not apply: agent still loading, same-org
|
|
37
|
-
* (or no) viewer org, agent not marketplace-public, or the viewer
|
|
38
|
-
* lacks `can_create_agent_share` in their own org. Lives in the
|
|
39
|
-
* `"sharing"` group, like its same-org sibling.
|
|
40
|
-
*/
|
|
41
|
-
readonly action: DetailAction | null;
|
|
42
|
-
/** The {@link ShareAgentDialog} node — render it once in the host tree. */
|
|
43
|
-
readonly dialog: ReactNode;
|
|
44
|
-
/** Imperatively open the dialog. */
|
|
45
|
-
readonly open: () => void;
|
|
46
|
-
/** Whether the dialog is currently open. */
|
|
47
|
-
readonly isOpen: boolean;
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* The marketplace entry for **cross-org shares** (decision 013): a
|
|
51
|
-
* "Create share link" action on another org's marketplace-public agent
|
|
52
|
-
* that opens the same {@link ShareAgentDialog} with the share org set to
|
|
53
|
-
* the viewer's own org. The resulting share is the viewer org's channel —
|
|
54
|
-
* its URL (`/chat/<viewer-org>/<slug>`), its billing, its credential
|
|
55
|
-
* bindings — while the agent blueprint stays live in its own org.
|
|
56
|
-
*
|
|
57
|
-
* The gate mirrors the server's create bar so the action never appears
|
|
58
|
-
* to a user whose create would be refused:
|
|
59
|
-
*
|
|
60
|
-
* - the agent lives in another org and is `visibility_public` — the
|
|
61
|
-
* origin org's implicit consent (D1); the agent-side `can_execute`
|
|
62
|
-
* half of the bar is granted to every authenticated account on public
|
|
63
|
-
* agents by the `allow_public` wildcard, so visibility is its exact
|
|
64
|
-
* client-side proxy;
|
|
65
|
-
* - the viewer holds `can_create_agent_share` in their own org (D2's
|
|
66
|
-
* org-side half — admin-level, since a public share spends that org's
|
|
67
|
-
* credits on the open internet). The org check uses the org slug as
|
|
68
|
-
* the resource id: an Organization's id equals its slug
|
|
69
|
-
* (ApiResourceMetadata.id).
|
|
70
|
-
*
|
|
71
|
-
* The same-org Share entry ({@link useShareAgent}, gated on agent
|
|
72
|
-
* `can_edit`) and this one are mutually exclusive by construction — at
|
|
73
|
-
* most one renders for any viewer/agent pair — so hosts can inject both
|
|
74
|
-
* unconditionally.
|
|
75
|
-
*
|
|
76
|
-
* @example
|
|
77
|
-
* ```tsx
|
|
78
|
-
* const externalShare = useCreateExternalShareLink({
|
|
79
|
-
* agent,
|
|
80
|
-
* viewerOrg: activeOrgSlug,
|
|
81
|
-
* buildShareUrl,
|
|
82
|
-
* onSharingChanged: refetch,
|
|
83
|
-
* });
|
|
84
|
-
* // ...
|
|
85
|
-
* <ResourceDetailShell actions={[...actions, externalShare.action].filter(Boolean)} ... />
|
|
86
|
-
* {externalShare.dialog}
|
|
87
|
-
* ```
|
|
88
|
-
*/
|
|
89
|
-
export declare function useCreateExternalShareLink({ agent, viewerOrg, buildShareUrl, onSharingChanged, label, }: UseCreateExternalShareLinkArgs): UseCreateExternalShareLinkReturn;
|
|
90
|
-
//# sourceMappingURL=useCreateExternalShareLink.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useCreateExternalShareLink.d.ts","sourceRoot":"","sources":["../../src/sharing/useCreateExternalShareLink.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAyB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAC9D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oDAAoD,CAAC;AAGhF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAGhE,wDAAwD;AACxD,MAAM,WAAW,8BAA8B;IAC7C;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IAC7B;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IAC/D,oDAAoD;IACpD,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IACvC,oDAAoD;IACpD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,0DAA0D;AAC1D,MAAM,WAAW,gCAAgC;IAC/C;;;;;;OAMG;IACH,QAAQ,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;IACrC,2EAA2E;IAC3E,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B,oCAAoC;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,IAAI,CAAC;IAC1B,4CAA4C;IAC5C,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,wBAAgB,0BAA0B,CAAC,EACzC,KAAK,EACL,SAAS,EACT,aAAa,EACb,gBAAgB,EAChB,KAA2B,GAC5B,EAAE,8BAA8B,GAAG,gCAAgC,CAsCnE"}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import { useCallback, useState } from "react";
|
|
4
|
-
import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
|
|
5
|
-
import { useCheckPermission } from "../iam-policy/useCheckPermission.js";
|
|
6
|
-
import { ShareAgentDialog } from "./ShareAgentDialog.js";
|
|
7
|
-
/**
|
|
8
|
-
* The marketplace entry for **cross-org shares** (decision 013): a
|
|
9
|
-
* "Create share link" action on another org's marketplace-public agent
|
|
10
|
-
* that opens the same {@link ShareAgentDialog} with the share org set to
|
|
11
|
-
* the viewer's own org. The resulting share is the viewer org's channel —
|
|
12
|
-
* its URL (`/chat/<viewer-org>/<slug>`), its billing, its credential
|
|
13
|
-
* bindings — while the agent blueprint stays live in its own org.
|
|
14
|
-
*
|
|
15
|
-
* The gate mirrors the server's create bar so the action never appears
|
|
16
|
-
* to a user whose create would be refused:
|
|
17
|
-
*
|
|
18
|
-
* - the agent lives in another org and is `visibility_public` — the
|
|
19
|
-
* origin org's implicit consent (D1); the agent-side `can_execute`
|
|
20
|
-
* half of the bar is granted to every authenticated account on public
|
|
21
|
-
* agents by the `allow_public` wildcard, so visibility is its exact
|
|
22
|
-
* client-side proxy;
|
|
23
|
-
* - the viewer holds `can_create_agent_share` in their own org (D2's
|
|
24
|
-
* org-side half — admin-level, since a public share spends that org's
|
|
25
|
-
* credits on the open internet). The org check uses the org slug as
|
|
26
|
-
* the resource id: an Organization's id equals its slug
|
|
27
|
-
* (ApiResourceMetadata.id).
|
|
28
|
-
*
|
|
29
|
-
* The same-org Share entry ({@link useShareAgent}, gated on agent
|
|
30
|
-
* `can_edit`) and this one are mutually exclusive by construction — at
|
|
31
|
-
* most one renders for any viewer/agent pair — so hosts can inject both
|
|
32
|
-
* unconditionally.
|
|
33
|
-
*
|
|
34
|
-
* @example
|
|
35
|
-
* ```tsx
|
|
36
|
-
* const externalShare = useCreateExternalShareLink({
|
|
37
|
-
* agent,
|
|
38
|
-
* viewerOrg: activeOrgSlug,
|
|
39
|
-
* buildShareUrl,
|
|
40
|
-
* onSharingChanged: refetch,
|
|
41
|
-
* });
|
|
42
|
-
* // ...
|
|
43
|
-
* <ResourceDetailShell actions={[...actions, externalShare.action].filter(Boolean)} ... />
|
|
44
|
-
* {externalShare.dialog}
|
|
45
|
-
* ```
|
|
46
|
-
*/
|
|
47
|
-
export function useCreateExternalShareLink({ agent, viewerOrg, buildShareUrl, onSharingChanged, label = "Create share link", }) {
|
|
48
|
-
const [isOpen, setIsOpen] = useState(false);
|
|
49
|
-
const agentOrg = agent?.metadata?.org ?? "";
|
|
50
|
-
const isCrossOrg = !!agent && viewerOrg !== "" && viewerOrg !== agentOrg;
|
|
51
|
-
const isPublic = agent?.metadata?.visibility === ApiResourceVisibility.visibility_public;
|
|
52
|
-
// The org-side bar is only worth asking about when the entry could
|
|
53
|
-
// apply at all — passing null skips the RPC entirely.
|
|
54
|
-
const { allowed: canCreateShare } = useCheckPermission(isCrossOrg && isPublic
|
|
55
|
-
? { kind: "organization", id: viewerOrg }
|
|
56
|
-
: null, "can_create_agent_share");
|
|
57
|
-
const open = useCallback(() => setIsOpen(true), []);
|
|
58
|
-
const applicable = isCrossOrg && isPublic && canCreateShare;
|
|
59
|
-
const action = applicable
|
|
60
|
-
? { id: "create-share-link", label, group: "sharing", onAction: open }
|
|
61
|
-
: null;
|
|
62
|
-
const dialog = agent && applicable ? (_jsx(ShareAgentDialog, { open: isOpen, onOpenChange: setIsOpen, agent: agent, shareOrg: viewerOrg, buildShareUrl: buildShareUrl, onSharingChanged: onSharingChanged })) : null;
|
|
63
|
-
return { action, dialog, open, isOpen };
|
|
64
|
-
}
|
|
65
|
-
//# sourceMappingURL=useCreateExternalShareLink.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useCreateExternalShareLink.js","sourceRoot":"","sources":["../../src/sharing/useCreateExternalShareLink.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAkB,MAAM,OAAO,CAAC;AAE9D,OAAO,EAAE,qBAAqB,EAAE,MAAM,wDAAwD,CAAC;AAC/F,OAAO,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AAEzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAiDzD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,MAAM,UAAU,0BAA0B,CAAC,EACzC,KAAK,EACL,SAAS,EACT,aAAa,EACb,gBAAgB,EAChB,KAAK,GAAG,mBAAmB,GACI;IAC/B,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAE5C,MAAM,QAAQ,GAAG,KAAK,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,CAAC;IAC5C,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,IAAI,SAAS,KAAK,EAAE,IAAI,SAAS,KAAK,QAAQ,CAAC;IACzE,MAAM,QAAQ,GACZ,KAAK,EAAE,QAAQ,EAAE,UAAU,KAAK,qBAAqB,CAAC,iBAAiB,CAAC;IAE1E,mEAAmE;IACnE,sDAAsD;IACtD,MAAM,EAAE,OAAO,EAAE,cAAc,EAAE,GAAG,kBAAkB,CACpD,UAAU,IAAI,QAAQ;QACpB,CAAC,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,EAAE,EAAE,SAAS,EAAE;QACzC,CAAC,CAAC,IAAI,EACR,wBAAwB,CACzB,CAAC;IAEF,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;IAEpD,MAAM,UAAU,GAAG,UAAU,IAAI,QAAQ,IAAI,cAAc,CAAC;IAE5D,MAAM,MAAM,GAAwB,UAAU;QAC5C,CAAC,CAAC,EAAE,EAAE,EAAE,mBAAmB,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE;QACtE,CAAC,CAAC,IAAI,CAAC;IAET,MAAM,MAAM,GACV,KAAK,IAAI,UAAU,CAAC,CAAC,CAAC,CACpB,KAAC,gBAAgB,IACf,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,SAAS,EACvB,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,SAAS,EACnB,aAAa,EAAE,aAAa,EAC5B,gBAAgB,EAAE,gBAAgB,GAClC,CACH,CAAC,CAAC,CAAC,IAAI,CAAC;IAEX,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AAC1C,CAAC"}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { type ReactNode } from "react";
|
|
2
|
-
import type { Agent } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
|
|
3
|
-
import type { DetailAction } from "../resource-detail/types.js";
|
|
4
|
-
/** Arguments for {@link useShareAgent}. */
|
|
5
|
-
export interface UseShareAgentArgs {
|
|
6
|
-
/**
|
|
7
|
-
* The agent whose sharing is managed, or `null` while it is still
|
|
8
|
-
* loading. When `null`, {@link UseShareAgentReturn.action} is `null`
|
|
9
|
-
* and the dialog renders nothing — safe to call before the resource
|
|
10
|
-
* is ready.
|
|
11
|
-
*/
|
|
12
|
-
readonly agent: Agent | null;
|
|
13
|
-
/**
|
|
14
|
-
* Builds the absolute public chat URL for the shared agent. The host
|
|
15
|
-
* application owns URL construction (its configured public origin may
|
|
16
|
-
* differ from the rendering origin — e.g. the desktop app). When
|
|
17
|
-
* omitted, the dialog falls back to the relative `/chat/<org>/<slug>`.
|
|
18
|
-
*/
|
|
19
|
-
readonly buildShareUrl?: (org: string, slug: string) => string;
|
|
20
|
-
/**
|
|
21
|
-
* Called after any sharing change is persisted. Hosts typically pass
|
|
22
|
-
* the agent data hook's `refetch`.
|
|
23
|
-
*/
|
|
24
|
-
readonly onSharingChanged?: () => void;
|
|
25
|
-
/** Menu-item label. @default "Share" */
|
|
26
|
-
readonly label?: string;
|
|
27
|
-
}
|
|
28
|
-
/** Return value of {@link useShareAgent}. */
|
|
29
|
-
export interface UseShareAgentReturn {
|
|
30
|
-
/**
|
|
31
|
-
* A ready-to-spread {@link DetailAction} for a kebab/overflow menu, or
|
|
32
|
-
* `null` when the agent is unavailable or the user lacks `can_edit`.
|
|
33
|
-
* Lives in the `"sharing"` group, beside "Manage access".
|
|
34
|
-
*/
|
|
35
|
-
readonly action: DetailAction | null;
|
|
36
|
-
/** The {@link ShareAgentDialog} node — render it once in the host tree. */
|
|
37
|
-
readonly dialog: ReactNode;
|
|
38
|
-
/** Imperatively open the dialog. */
|
|
39
|
-
readonly open: () => void;
|
|
40
|
-
/** Whether the dialog is currently open. */
|
|
41
|
-
readonly isOpen: boolean;
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Wires the Share dialog to a kebab/overflow menu — the same trigger
|
|
45
|
-
* shape as {@link useManageAccess}, its conceptual sibling: Manage access
|
|
46
|
-
* governs who can *read* the blueprint; Share governs who can *chat* with
|
|
47
|
-
* the running agent (billed to the owning org).
|
|
48
|
-
*
|
|
49
|
-
* Owns the open-state and the `can_edit` gate — the same permission the
|
|
50
|
-
* AgentShare create/apply handlers enforce on the referenced agent, so
|
|
51
|
-
* the action never appears to a user whose changes would be rejected.
|
|
52
|
-
* Returns a `null` action while the agent is loading or the user cannot
|
|
53
|
-
* edit, so the host can unconditionally fold `action` into its actions
|
|
54
|
-
* array.
|
|
55
|
-
*
|
|
56
|
-
* @example
|
|
57
|
-
* ```tsx
|
|
58
|
-
* const share = useShareAgent({
|
|
59
|
-
* agent,
|
|
60
|
-
* buildShareUrl: (org, slug) => `${appOrigin}/chat/${org}/${slug}`,
|
|
61
|
-
* onSharingChanged: refetch,
|
|
62
|
-
* });
|
|
63
|
-
* // ...
|
|
64
|
-
* <ResourceDetailShell actions={share.action ? [...actions, share.action] : actions} ... />
|
|
65
|
-
* {share.dialog}
|
|
66
|
-
* ```
|
|
67
|
-
*/
|
|
68
|
-
export declare function useShareAgent({ agent, buildShareUrl, onSharingChanged, label, }: UseShareAgentArgs): UseShareAgentReturn;
|
|
69
|
-
//# sourceMappingURL=useShareAgent.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useShareAgent.d.ts","sourceRoot":"","sources":["../../src/sharing/useShareAgent.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAyB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAC9D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oDAAoD,CAAC;AAEhF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAGhE,2CAA2C;AAC3C,MAAM,WAAW,iBAAiB;IAChC;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IAC7B;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IAC/D;;;OAGG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IACvC,wCAAwC;IACxC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,6CAA6C;AAC7C,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;IACrC,2EAA2E;IAC3E,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B,oCAAoC;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,IAAI,CAAC;IAC1B,4CAA4C;IAC5C,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,aAAa,EACb,gBAAgB,EAChB,KAAe,GAChB,EAAE,iBAAiB,GAAG,mBAAmB,CA2BzC"}
|
package/sharing/useShareAgent.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import { useCallback, useState } from "react";
|
|
4
|
-
import { useCheckPermission } from "../iam-policy/useCheckPermission.js";
|
|
5
|
-
import { ShareAgentDialog } from "./ShareAgentDialog.js";
|
|
6
|
-
/**
|
|
7
|
-
* Wires the Share dialog to a kebab/overflow menu — the same trigger
|
|
8
|
-
* shape as {@link useManageAccess}, its conceptual sibling: Manage access
|
|
9
|
-
* governs who can *read* the blueprint; Share governs who can *chat* with
|
|
10
|
-
* the running agent (billed to the owning org).
|
|
11
|
-
*
|
|
12
|
-
* Owns the open-state and the `can_edit` gate — the same permission the
|
|
13
|
-
* AgentShare create/apply handlers enforce on the referenced agent, so
|
|
14
|
-
* the action never appears to a user whose changes would be rejected.
|
|
15
|
-
* Returns a `null` action while the agent is loading or the user cannot
|
|
16
|
-
* edit, so the host can unconditionally fold `action` into its actions
|
|
17
|
-
* array.
|
|
18
|
-
*
|
|
19
|
-
* @example
|
|
20
|
-
* ```tsx
|
|
21
|
-
* const share = useShareAgent({
|
|
22
|
-
* agent,
|
|
23
|
-
* buildShareUrl: (org, slug) => `${appOrigin}/chat/${org}/${slug}`,
|
|
24
|
-
* onSharingChanged: refetch,
|
|
25
|
-
* });
|
|
26
|
-
* // ...
|
|
27
|
-
* <ResourceDetailShell actions={share.action ? [...actions, share.action] : actions} ... />
|
|
28
|
-
* {share.dialog}
|
|
29
|
-
* ```
|
|
30
|
-
*/
|
|
31
|
-
export function useShareAgent({ agent, buildShareUrl, onSharingChanged, label = "Share", }) {
|
|
32
|
-
const [isOpen, setIsOpen] = useState(false);
|
|
33
|
-
const agentId = agent?.metadata?.id ?? null;
|
|
34
|
-
const { allowed: canEdit } = useCheckPermission(agentId ? { kind: "agent", id: agentId } : null, "can_edit");
|
|
35
|
-
const open = useCallback(() => setIsOpen(true), []);
|
|
36
|
-
const action = agent && canEdit
|
|
37
|
-
? { id: "share", label, group: "sharing", onAction: open }
|
|
38
|
-
: null;
|
|
39
|
-
const dialog = agent ? (_jsx(ShareAgentDialog, { open: isOpen, onOpenChange: setIsOpen, agent: agent, buildShareUrl: buildShareUrl, onSharingChanged: onSharingChanged })) : null;
|
|
40
|
-
return { action, dialog, open, isOpen };
|
|
41
|
-
}
|
|
42
|
-
//# sourceMappingURL=useShareAgent.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useShareAgent.js","sourceRoot":"","sources":["../../src/sharing/useShareAgent.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAkB,MAAM,OAAO,CAAC;AAE9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AAEzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AA2CzD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,UAAU,aAAa,CAAC,EAC5B,KAAK,EACL,aAAa,EACb,gBAAgB,EAChB,KAAK,GAAG,OAAO,GACG;IAClB,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAE5C,MAAM,OAAO,GAAG,KAAK,EAAE,QAAQ,EAAE,EAAE,IAAI,IAAI,CAAC;IAC5C,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,kBAAkB,CAC7C,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,EAC/C,UAAU,CACX,CAAC;IAEF,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;IAEpD,MAAM,MAAM,GACV,KAAK,IAAI,OAAO;QACd,CAAC,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE;QAC1D,CAAC,CAAC,IAAI,CAAC;IAEX,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,CACrB,KAAC,gBAAgB,IACf,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,SAAS,EACvB,KAAK,EAAE,KAAK,EACZ,aAAa,EAAE,aAAa,EAC5B,gBAAgB,EAAE,gBAAgB,GAClC,CACH,CAAC,CAAC,CAAC,IAAI,CAAC;IAET,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AAC1C,CAAC"}
|
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
-
import { renderHook, waitFor } from "@testing-library/react";
|
|
3
|
-
import type { ReactNode } from "react";
|
|
4
|
-
import type { GetAgentSharesByAgentRequest } from "@stigmer/protos/ai/stigmer/agentic/agentshare/v1/io_pb";
|
|
5
|
-
import { StigmerContext } from "../../context";
|
|
6
|
-
import { FetchCacheContext } from "../../internal/FetchCacheProvider";
|
|
7
|
-
import { useAgentShare } from "../useAgentShare";
|
|
8
|
-
|
|
9
|
-
function createMockStigmer(overrides: {
|
|
10
|
-
getByAgent?: (input: GetAgentSharesByAgentRequest) => Promise<unknown>;
|
|
11
|
-
} = {}) {
|
|
12
|
-
return {
|
|
13
|
-
agentShare: {
|
|
14
|
-
getByAgent:
|
|
15
|
-
overrides.getByAgent ??
|
|
16
|
-
vi.fn().mockResolvedValue({ totalCount: 0, items: [] }),
|
|
17
|
-
},
|
|
18
|
-
} as never;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function wrapper(client: unknown) {
|
|
22
|
-
return function Wrapper({ children }: { children: ReactNode }) {
|
|
23
|
-
return (
|
|
24
|
-
<FetchCacheContext.Provider value={null}>
|
|
25
|
-
<StigmerContext.Provider value={client as never}>
|
|
26
|
-
{children}
|
|
27
|
-
</StigmerContext.Provider>
|
|
28
|
-
</FetchCacheContext.Provider>
|
|
29
|
-
);
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const AGENT = {
|
|
34
|
-
metadata: {
|
|
35
|
-
id: "agt_1",
|
|
36
|
-
org: "acme",
|
|
37
|
-
slug: "support-agent",
|
|
38
|
-
name: "Support Agent",
|
|
39
|
-
},
|
|
40
|
-
} as never;
|
|
41
|
-
|
|
42
|
-
function makeShare(slug: string, id = `ash_${slug}`) {
|
|
43
|
-
return {
|
|
44
|
-
metadata: { id, org: "acme", slug },
|
|
45
|
-
spec: { enabled: true },
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
describe("useAgentShare", () => {
|
|
50
|
-
beforeEach(() => {
|
|
51
|
-
vi.restoreAllMocks();
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it("loads the agent's shares by agent id", async () => {
|
|
55
|
-
const share = makeShare("support-agent");
|
|
56
|
-
const getByAgent = vi
|
|
57
|
-
.fn()
|
|
58
|
-
.mockResolvedValue({ totalCount: 1, items: [share] });
|
|
59
|
-
const client = createMockStigmer({ getByAgent });
|
|
60
|
-
|
|
61
|
-
const { result } = renderHook(() => useAgentShare(AGENT), {
|
|
62
|
-
wrapper: wrapper(client),
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
expect(result.current.isLoading).toBe(true);
|
|
66
|
-
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
67
|
-
|
|
68
|
-
expect(result.current.share).toBe(share);
|
|
69
|
-
expect(result.current.error).toBeNull();
|
|
70
|
-
expect(getByAgent).toHaveBeenCalledWith(
|
|
71
|
-
expect.objectContaining({ agentId: "agt_1" }),
|
|
72
|
-
);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it("resolves null (no share yet) when the agent has never been shared", async () => {
|
|
76
|
-
const client = createMockStigmer();
|
|
77
|
-
|
|
78
|
-
const { result } = renderHook(() => useAgentShare(AGENT), {
|
|
79
|
-
wrapper: wrapper(client),
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
83
|
-
|
|
84
|
-
// The no-share state is not an error: the first save creates the share.
|
|
85
|
-
expect(result.current.share).toBeNull();
|
|
86
|
-
expect(result.current.error).toBeNull();
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it("picks the slug-matching share as canonical when several exist", async () => {
|
|
90
|
-
const canonical = makeShare("support-agent");
|
|
91
|
-
const renamed = makeShare("support-help-desk");
|
|
92
|
-
const getByAgent = vi
|
|
93
|
-
.fn()
|
|
94
|
-
.mockResolvedValue({ totalCount: 2, items: [renamed, canonical] });
|
|
95
|
-
const client = createMockStigmer({ getByAgent });
|
|
96
|
-
|
|
97
|
-
const { result } = renderHook(() => useAgentShare(AGENT), {
|
|
98
|
-
wrapper: wrapper(client),
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
102
|
-
expect(result.current.share).toBe(canonical);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it("falls back to the first share when none matches the agent slug", async () => {
|
|
106
|
-
// A manifest-created share with a custom slug is still manageable —
|
|
107
|
-
// preferring it over "no share" keeps the dialog editing the real row.
|
|
108
|
-
const renamed = makeShare("support-help-desk");
|
|
109
|
-
const getByAgent = vi
|
|
110
|
-
.fn()
|
|
111
|
-
.mockResolvedValue({ totalCount: 1, items: [renamed] });
|
|
112
|
-
const client = createMockStigmer({ getByAgent });
|
|
113
|
-
|
|
114
|
-
const { result } = renderHook(() => useAgentShare(AGENT), {
|
|
115
|
-
wrapper: wrapper(client),
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
119
|
-
expect(result.current.share).toBe(renamed);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it("skips fetching while the agent is null (stable no-op)", () => {
|
|
123
|
-
const getByAgent = vi.fn();
|
|
124
|
-
const client = createMockStigmer({ getByAgent });
|
|
125
|
-
|
|
126
|
-
const { result } = renderHook(() => useAgentShare(null), {
|
|
127
|
-
wrapper: wrapper(client),
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
expect(result.current.isLoading).toBe(false);
|
|
131
|
-
expect(result.current.share).toBeNull();
|
|
132
|
-
expect(getByAgent).not.toHaveBeenCalled();
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
describe("cross-org share resolution (shareOrg — decision 013)", () => {
|
|
136
|
-
it("scopes the canonical pick to the sharing org's channel", async () => {
|
|
137
|
-
// The same agent shared in two orgs: the owner's share AND another
|
|
138
|
-
// org's external share. Each org's dialog must resolve its own row.
|
|
139
|
-
const ownerShare = makeShare("support-agent");
|
|
140
|
-
const externalShare = {
|
|
141
|
-
metadata: { id: "ash_ext", org: "consumer-org", slug: "support-agent" },
|
|
142
|
-
spec: { enabled: true },
|
|
143
|
-
};
|
|
144
|
-
const getByAgent = vi
|
|
145
|
-
.fn()
|
|
146
|
-
.mockResolvedValue({ totalCount: 2, items: [ownerShare, externalShare] });
|
|
147
|
-
const client = createMockStigmer({ getByAgent });
|
|
148
|
-
|
|
149
|
-
const { result } = renderHook(
|
|
150
|
-
() => useAgentShare(AGENT, "consumer-org"),
|
|
151
|
-
{ wrapper: wrapper(client) },
|
|
152
|
-
);
|
|
153
|
-
|
|
154
|
-
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
155
|
-
expect(result.current.share).toBe(externalShare);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
it("resolves null when only OTHER orgs' shares exist — never edits a foreign channel", async () => {
|
|
159
|
-
const ownerShare = makeShare("support-agent");
|
|
160
|
-
const getByAgent = vi
|
|
161
|
-
.fn()
|
|
162
|
-
.mockResolvedValue({ totalCount: 1, items: [ownerShare] });
|
|
163
|
-
const client = createMockStigmer({ getByAgent });
|
|
164
|
-
|
|
165
|
-
const { result } = renderHook(
|
|
166
|
-
() => useAgentShare(AGENT, "consumer-org"),
|
|
167
|
-
{ wrapper: wrapper(client) },
|
|
168
|
-
);
|
|
169
|
-
|
|
170
|
-
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
171
|
-
// Without the org filter this would fall back to the owner's share
|
|
172
|
-
// and a save would overwrite the wrong org's configuration.
|
|
173
|
-
expect(result.current.share).toBeNull();
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
it("defaults shareOrg to the agent's own org (Phase A behavior unchanged)", async () => {
|
|
177
|
-
const ownerShare = makeShare("support-agent");
|
|
178
|
-
const externalShare = {
|
|
179
|
-
metadata: { id: "ash_ext", org: "consumer-org", slug: "support-agent" },
|
|
180
|
-
spec: { enabled: true },
|
|
181
|
-
};
|
|
182
|
-
const getByAgent = vi
|
|
183
|
-
.fn()
|
|
184
|
-
.mockResolvedValue({ totalCount: 2, items: [externalShare, ownerShare] });
|
|
185
|
-
const client = createMockStigmer({ getByAgent });
|
|
186
|
-
|
|
187
|
-
const { result } = renderHook(() => useAgentShare(AGENT), {
|
|
188
|
-
wrapper: wrapper(client),
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
192
|
-
expect(result.current.share).toBe(ownerShare);
|
|
193
|
-
});
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
it("exposes fetch failures as errors", async () => {
|
|
197
|
-
const getByAgent = vi
|
|
198
|
-
.fn()
|
|
199
|
-
.mockRejectedValue(new Error("backend unavailable"));
|
|
200
|
-
const client = createMockStigmer({ getByAgent });
|
|
201
|
-
|
|
202
|
-
const { result } = renderHook(() => useAgentShare(AGENT), {
|
|
203
|
-
wrapper: wrapper(client),
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
207
|
-
expect(result.current.error).toBeTruthy();
|
|
208
|
-
expect(result.current.share).toBeNull();
|
|
209
|
-
});
|
|
210
|
-
});
|