@stigmer/react 3.1.13 → 3.1.15

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 (139) hide show
  1. package/agent/AgentDetailView.d.ts +9 -9
  2. package/agent/AgentDetailView.d.ts.map +1 -1
  3. package/agent/AgentDetailView.js +23 -31
  4. package/agent/AgentDetailView.js.map +1 -1
  5. package/agent-instance/AgentInstanceEmptyState.d.ts +4 -1
  6. package/agent-instance/AgentInstanceEmptyState.d.ts.map +1 -1
  7. package/agent-instance/AgentInstanceEmptyState.js +12 -5
  8. package/agent-instance/AgentInstanceEmptyState.js.map +1 -1
  9. package/agent-instance/AgentInstanceList.d.ts +13 -2
  10. package/agent-instance/AgentInstanceList.d.ts.map +1 -1
  11. package/agent-instance/AgentInstanceList.js +8 -4
  12. package/agent-instance/AgentInstanceList.js.map +1 -1
  13. package/agent-instance/useAgentInstances.d.ts +10 -3
  14. package/agent-instance/useAgentInstances.d.ts.map +1 -1
  15. package/agent-instance/useAgentInstances.js +12 -4
  16. package/agent-instance/useAgentInstances.js.map +1 -1
  17. package/button/Button.d.ts +54 -0
  18. package/button/Button.d.ts.map +1 -0
  19. package/button/Button.js +42 -0
  20. package/button/Button.js.map +1 -0
  21. package/button/index.d.ts +3 -0
  22. package/button/index.d.ts.map +1 -0
  23. package/button/index.js +2 -0
  24. package/button/index.js.map +1 -0
  25. package/empty-state/EmptyState.d.ts.map +1 -1
  26. package/empty-state/EmptyState.js +8 -3
  27. package/empty-state/EmptyState.js.map +1 -1
  28. package/empty-state/types.d.ts +2 -0
  29. package/empty-state/types.d.ts.map +1 -1
  30. package/execution/index.d.ts +1 -1
  31. package/execution/index.d.ts.map +1 -1
  32. package/execution/index.js.map +1 -1
  33. package/execution/useCreateAgentExecution.d.ts +81 -9
  34. package/execution/useCreateAgentExecution.d.ts.map +1 -1
  35. package/execution/useCreateAgentExecution.js +42 -4
  36. package/execution/useCreateAgentExecution.js.map +1 -1
  37. package/index.d.ts +5 -3
  38. package/index.d.ts.map +1 -1
  39. package/index.js +4 -2
  40. package/index.js.map +1 -1
  41. package/package.json +4 -4
  42. package/session/useNewSessionFlow.d.ts +6 -3
  43. package/session/useNewSessionFlow.d.ts.map +1 -1
  44. package/session/useNewSessionFlow.js +34 -35
  45. package/session/useNewSessionFlow.js.map +1 -1
  46. package/sharing/AgentShareList.d.ts +46 -0
  47. package/sharing/AgentShareList.d.ts.map +1 -0
  48. package/sharing/AgentShareList.js +159 -0
  49. package/sharing/AgentShareList.js.map +1 -0
  50. package/sharing/ShareAgentDialog.d.ts +38 -25
  51. package/sharing/ShareAgentDialog.d.ts.map +1 -1
  52. package/sharing/ShareAgentDialog.js +131 -62
  53. package/sharing/ShareAgentDialog.js.map +1 -1
  54. package/sharing/index.d.ts +10 -8
  55. package/sharing/index.d.ts.map +1 -1
  56. package/sharing/index.js +5 -4
  57. package/sharing/index.js.map +1 -1
  58. package/sharing/useAgentShares.d.ts +48 -0
  59. package/sharing/useAgentShares.d.ts.map +1 -0
  60. package/sharing/useAgentShares.js +44 -0
  61. package/sharing/useAgentShares.js.map +1 -0
  62. package/sharing/useCanCreateAgentShare.d.ts +43 -0
  63. package/sharing/useCanCreateAgentShare.d.ts.map +1 -0
  64. package/sharing/useCanCreateAgentShare.js +44 -0
  65. package/sharing/useCanCreateAgentShare.js.map +1 -0
  66. package/sharing/useDeleteAgentShare.d.ts +42 -0
  67. package/sharing/useDeleteAgentShare.d.ts.map +1 -0
  68. package/sharing/useDeleteAgentShare.js +45 -0
  69. package/sharing/useDeleteAgentShare.js.map +1 -0
  70. package/sharing/useSaveAgentShare.d.ts +43 -18
  71. package/sharing/useSaveAgentShare.d.ts.map +1 -1
  72. package/sharing/useSaveAgentShare.js +55 -22
  73. package/sharing/useSaveAgentShare.js.map +1 -1
  74. package/src/agent/AgentDetailView.tsx +37 -43
  75. package/src/agent-instance/AgentInstanceEmptyState.tsx +24 -43
  76. package/src/agent-instance/AgentInstanceList.tsx +29 -16
  77. package/src/agent-instance/__tests__/agent-instance-hooks.test.tsx +19 -0
  78. package/src/agent-instance/useAgentInstances.ts +12 -3
  79. package/src/button/Button.tsx +104 -0
  80. package/src/button/__tests__/Button.test.tsx +82 -0
  81. package/src/button/index.ts +2 -0
  82. package/src/empty-state/EmptyState.tsx +8 -10
  83. package/src/empty-state/types.ts +2 -0
  84. package/src/execution/__tests__/useCreateAgentExecution.test.tsx +92 -0
  85. package/src/execution/index.ts +2 -0
  86. package/src/execution/useCreateAgentExecution.ts +116 -10
  87. package/src/index.ts +17 -9
  88. package/src/session/__tests__/useNewSessionFlow.test.tsx +121 -52
  89. package/src/session/useNewSessionFlow.ts +38 -37
  90. package/src/sharing/AgentShareList.tsx +502 -0
  91. package/src/sharing/ShareAgentDialog.tsx +310 -112
  92. package/src/sharing/__tests__/AgentShareList.test.tsx +388 -0
  93. package/src/sharing/__tests__/ShareAgentDialog.test.tsx +311 -312
  94. package/src/sharing/__tests__/useAgentShares.test.tsx +186 -0
  95. package/src/sharing/__tests__/useCanCreateAgentShare.test.tsx +190 -0
  96. package/src/sharing/index.ts +10 -12
  97. package/src/sharing/useAgentShares.ts +74 -0
  98. package/src/sharing/useCanCreateAgentShare.ts +74 -0
  99. package/src/sharing/useDeleteAgentShare.ts +73 -0
  100. package/src/sharing/useSaveAgentShare.ts +73 -23
  101. package/src/workflow/WorkflowDetailView.tsx +9 -0
  102. package/src/workflow/instance/WorkflowInstanceEmptyState.tsx +24 -43
  103. package/src/workflow/instance/WorkflowInstanceList.tsx +29 -16
  104. package/src/workflow/useWorkflowInstances.ts +10 -3
  105. package/styles.css +1 -1
  106. package/workflow/WorkflowDetailView.d.ts +8 -1
  107. package/workflow/WorkflowDetailView.d.ts.map +1 -1
  108. package/workflow/WorkflowDetailView.js +2 -2
  109. package/workflow/WorkflowDetailView.js.map +1 -1
  110. package/workflow/instance/WorkflowInstanceEmptyState.d.ts +4 -1
  111. package/workflow/instance/WorkflowInstanceEmptyState.d.ts.map +1 -1
  112. package/workflow/instance/WorkflowInstanceEmptyState.js +12 -5
  113. package/workflow/instance/WorkflowInstanceEmptyState.js.map +1 -1
  114. package/workflow/instance/WorkflowInstanceList.d.ts +13 -2
  115. package/workflow/instance/WorkflowInstanceList.d.ts.map +1 -1
  116. package/workflow/instance/WorkflowInstanceList.js +8 -4
  117. package/workflow/instance/WorkflowInstanceList.js.map +1 -1
  118. package/workflow/useWorkflowInstances.d.ts +8 -3
  119. package/workflow/useWorkflowInstances.d.ts.map +1 -1
  120. package/workflow/useWorkflowInstances.js +10 -4
  121. package/workflow/useWorkflowInstances.js.map +1 -1
  122. package/sharing/useAgentShare.d.ts +0 -49
  123. package/sharing/useAgentShare.d.ts.map +0 -1
  124. package/sharing/useAgentShare.js +0 -64
  125. package/sharing/useAgentShare.js.map +0 -1
  126. package/sharing/useCreateExternalShareLink.d.ts +0 -90
  127. package/sharing/useCreateExternalShareLink.d.ts.map +0 -1
  128. package/sharing/useCreateExternalShareLink.js +0 -65
  129. package/sharing/useCreateExternalShareLink.js.map +0 -1
  130. package/sharing/useShareAgent.d.ts +0 -69
  131. package/sharing/useShareAgent.d.ts.map +0 -1
  132. package/sharing/useShareAgent.js +0 -42
  133. package/sharing/useShareAgent.js.map +0 -1
  134. package/src/sharing/__tests__/useAgentShare.test.tsx +0 -210
  135. package/src/sharing/__tests__/useCreateExternalShareLink.test.tsx +0 -194
  136. package/src/sharing/__tests__/useShareAgent.test.tsx +0 -131
  137. package/src/sharing/useAgentShare.ts +0 -105
  138. package/src/sharing/useCreateExternalShareLink.tsx +0 -141
  139. package/src/sharing/useShareAgent.tsx +0 -107
@@ -1,194 +0,0 @@
1
- import { describe, it, expect, vi, beforeAll, afterEach } from "vitest";
2
- import { renderHook, waitFor, cleanup } from "@testing-library/react";
3
- import type { ReactNode } from "react";
4
- import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
5
- import { StigmerContext } from "../../context";
6
- import { FetchCacheContext } from "../../internal/FetchCacheProvider";
7
- import { useCreateExternalShareLink } from "../useCreateExternalShareLink";
8
-
9
- // happy-dom does not implement the native dialog show/close methods.
10
- beforeAll(() => {
11
- HTMLDialogElement.prototype.showModal = function showModal() {
12
- this.open = true;
13
- };
14
- HTMLDialogElement.prototype.close = function close() {
15
- this.open = false;
16
- };
17
- });
18
-
19
- afterEach(cleanup);
20
-
21
- function createMockStigmer(overrides: {
22
- isAuthorized?: boolean;
23
- checkMyPermission?: (...args: unknown[]) => Promise<unknown>;
24
- } = {}) {
25
- return {
26
- iamPolicy: {
27
- checkMyPermission:
28
- overrides.checkMyPermission ??
29
- vi.fn().mockResolvedValue({
30
- isAuthorized: overrides.isAuthorized ?? true,
31
- }),
32
- },
33
- agentShare: {
34
- getByAgent: vi.fn().mockResolvedValue({ totalCount: 0, items: [] }),
35
- apply: vi.fn().mockResolvedValue({}),
36
- rotateShareLink: vi.fn().mockResolvedValue({}),
37
- },
38
- billing: { getOrCreateBillingAccount: vi.fn().mockResolvedValue(null) },
39
- } as never;
40
- }
41
-
42
- function wrapper(client: unknown) {
43
- return function Wrapper({ children }: { children: ReactNode }) {
44
- return (
45
- <FetchCacheContext.Provider value={null}>
46
- <StigmerContext.Provider value={client as never}>
47
- {children}
48
- </StigmerContext.Provider>
49
- </FetchCacheContext.Provider>
50
- );
51
- };
52
- }
53
-
54
- function makeAgent(visibility: ApiResourceVisibility) {
55
- return {
56
- metadata: {
57
- id: "agt_1",
58
- org: "provider-org",
59
- slug: "public-helper",
60
- name: "Public Helper",
61
- visibility,
62
- },
63
- spec: {},
64
- } as never;
65
- }
66
-
67
- const PUBLIC_AGENT = makeAgent(ApiResourceVisibility.visibility_public);
68
- const PRIVATE_AGENT = makeAgent(ApiResourceVisibility.visibility_private);
69
-
70
- describe("useCreateExternalShareLink", () => {
71
- it("offers the action on another org's public agent when the viewer holds the org bar", async () => {
72
- const { result } = renderHook(
73
- () =>
74
- useCreateExternalShareLink({
75
- agent: PUBLIC_AGENT,
76
- viewerOrg: "consumer-org",
77
- }),
78
- { wrapper: wrapper(createMockStigmer({ isAuthorized: true })) },
79
- );
80
-
81
- await waitFor(() => expect(result.current.action).not.toBeNull());
82
- expect(result.current.action?.id).toBe("create-share-link");
83
- expect(result.current.action?.label).toBe("Create share link");
84
- expect(result.current.action?.group).toBe("sharing");
85
- });
86
-
87
- it("checks can_create_agent_share on the VIEWER's org (id = slug)", async () => {
88
- const client = createMockStigmer({ isAuthorized: true });
89
- renderHook(
90
- () =>
91
- useCreateExternalShareLink({
92
- agent: PUBLIC_AGENT,
93
- viewerOrg: "consumer-org",
94
- }),
95
- { wrapper: wrapper(client) },
96
- );
97
-
98
- const check = (
99
- client as { iamPolicy: { checkMyPermission: ReturnType<typeof vi.fn> } }
100
- ).iamPolicy.checkMyPermission;
101
- await waitFor(() => expect(check).toHaveBeenCalled());
102
- const input = check.mock.calls[0][0] as {
103
- resource?: { kind: string; id: string };
104
- relation: string;
105
- };
106
- expect(input.relation).toBe("can_create_agent_share");
107
- expect(input.resource?.kind).toBe("organization");
108
- expect(input.resource?.id).toBe("consumer-org");
109
- });
110
-
111
- it("stays null on the viewer's OWN agents — the same-org Share entry covers those", () => {
112
- const checkMyPermission = vi.fn();
113
- const { result } = renderHook(
114
- () =>
115
- useCreateExternalShareLink({
116
- agent: PUBLIC_AGENT,
117
- viewerOrg: "provider-org",
118
- }),
119
- { wrapper: wrapper(createMockStigmer({ checkMyPermission })) },
120
- );
121
-
122
- expect(result.current.action).toBeNull();
123
- expect(result.current.dialog).toBeNull();
124
- // Not applicable structurally: no permission round-trip is spent.
125
- expect(checkMyPermission).not.toHaveBeenCalled();
126
- });
127
-
128
- it("stays null on a non-public agent — visibility is the origin org's consent", () => {
129
- const checkMyPermission = vi.fn();
130
- const { result } = renderHook(
131
- () =>
132
- useCreateExternalShareLink({
133
- agent: PRIVATE_AGENT,
134
- viewerOrg: "consumer-org",
135
- }),
136
- { wrapper: wrapper(createMockStigmer({ checkMyPermission })) },
137
- );
138
-
139
- expect(result.current.action).toBeNull();
140
- expect(checkMyPermission).not.toHaveBeenCalled();
141
- });
142
-
143
- it("stays null while the agent is loading or the viewer org is unknown", () => {
144
- const client = createMockStigmer();
145
- const { result: loading } = renderHook(
146
- () => useCreateExternalShareLink({ agent: null, viewerOrg: "consumer-org" }),
147
- { wrapper: wrapper(client) },
148
- );
149
- const { result: orgless } = renderHook(
150
- () => useCreateExternalShareLink({ agent: PUBLIC_AGENT, viewerOrg: "" }),
151
- { wrapper: wrapper(client) },
152
- );
153
-
154
- expect(loading.current.action).toBeNull();
155
- expect(orgless.current.action).toBeNull();
156
- });
157
-
158
- it("hides the action when the viewer lacks the org-side permission", async () => {
159
- const client = createMockStigmer({ isAuthorized: false });
160
- const { result } = renderHook(
161
- () =>
162
- useCreateExternalShareLink({
163
- agent: PUBLIC_AGENT,
164
- viewerOrg: "consumer-org",
165
- }),
166
- { wrapper: wrapper(client) },
167
- );
168
-
169
- await waitFor(() =>
170
- expect(
171
- (client as { iamPolicy: { checkMyPermission: ReturnType<typeof vi.fn> } })
172
- .iamPolicy.checkMyPermission,
173
- ).toHaveBeenCalled(),
174
- );
175
- await waitFor(() => expect(result.current.action).toBeNull());
176
- });
177
-
178
- it("opens the cross-org dialog via the action", async () => {
179
- const { result } = renderHook(
180
- () =>
181
- useCreateExternalShareLink({
182
- agent: PUBLIC_AGENT,
183
- viewerOrg: "consumer-org",
184
- }),
185
- { wrapper: wrapper(createMockStigmer({ isAuthorized: true })) },
186
- );
187
-
188
- await waitFor(() => expect(result.current.action).not.toBeNull());
189
- expect(result.current.isOpen).toBe(false);
190
-
191
- result.current.action?.onAction();
192
- await waitFor(() => expect(result.current.isOpen).toBe(true));
193
- });
194
- });
@@ -1,131 +0,0 @@
1
- import { describe, it, expect, vi, beforeAll, afterEach } from "vitest";
2
- import { renderHook, waitFor, cleanup } from "@testing-library/react";
3
- import type { ReactNode } from "react";
4
- import { StigmerContext } from "../../context";
5
- import { FetchCacheContext } from "../../internal/FetchCacheProvider";
6
- import { useShareAgent } from "../useShareAgent";
7
-
8
- // happy-dom does not implement the native dialog show/close methods.
9
- beforeAll(() => {
10
- HTMLDialogElement.prototype.showModal = function showModal() {
11
- this.open = true;
12
- };
13
- HTMLDialogElement.prototype.close = function close() {
14
- this.open = false;
15
- };
16
- });
17
-
18
- afterEach(cleanup);
19
-
20
- function createMockStigmer(overrides: {
21
- isAuthorized?: boolean;
22
- checkMyPermission?: (...args: unknown[]) => Promise<unknown>;
23
- } = {}) {
24
- return {
25
- iamPolicy: {
26
- checkMyPermission:
27
- overrides.checkMyPermission ??
28
- vi.fn().mockResolvedValue({
29
- isAuthorized: overrides.isAuthorized ?? true,
30
- }),
31
- },
32
- agentShare: {
33
- getByAgent: vi.fn().mockResolvedValue({ totalCount: 0, items: [] }),
34
- apply: vi.fn().mockResolvedValue({}),
35
- rotateShareLink: vi.fn().mockResolvedValue({}),
36
- },
37
- billing: { getOrCreateBillingAccount: vi.fn().mockResolvedValue(null) },
38
- } as never;
39
- }
40
-
41
- function wrapper(client: unknown) {
42
- return function Wrapper({ children }: { children: ReactNode }) {
43
- return (
44
- <FetchCacheContext.Provider value={null}>
45
- <StigmerContext.Provider value={client as never}>
46
- {children}
47
- </StigmerContext.Provider>
48
- </FetchCacheContext.Provider>
49
- );
50
- };
51
- }
52
-
53
- const AGENT = {
54
- metadata: {
55
- id: "agt_1",
56
- org: "acme",
57
- slug: "support-agent",
58
- name: "Support Agent",
59
- },
60
- spec: {},
61
- } as never;
62
-
63
- describe("useShareAgent", () => {
64
- it("returns a Share action in the sharing group when the user can edit", async () => {
65
- const { result } = renderHook(() => useShareAgent({ agent: AGENT }), {
66
- wrapper: wrapper(createMockStigmer({ isAuthorized: true })),
67
- });
68
-
69
- await waitFor(() => expect(result.current.action).not.toBeNull());
70
- expect(result.current.action?.id).toBe("share");
71
- expect(result.current.action?.label).toBe("Share");
72
- expect(result.current.action?.group).toBe("sharing");
73
- });
74
-
75
- it("returns a null action while the agent is loading", () => {
76
- const checkMyPermission = vi.fn();
77
- const { result } = renderHook(() => useShareAgent({ agent: null }), {
78
- wrapper: wrapper(createMockStigmer({ checkMyPermission })),
79
- });
80
-
81
- expect(result.current.action).toBeNull();
82
- expect(result.current.dialog).toBeNull();
83
- expect(checkMyPermission).not.toHaveBeenCalled();
84
- });
85
-
86
- it("returns a null action when the user lacks can_edit", async () => {
87
- const client = createMockStigmer({ isAuthorized: false });
88
- const { result } = renderHook(() => useShareAgent({ agent: AGENT }), {
89
- wrapper: wrapper(client),
90
- });
91
-
92
- await waitFor(() =>
93
- expect(
94
- (client as { iamPolicy: { checkMyPermission: ReturnType<typeof vi.fn> } })
95
- .iamPolicy.checkMyPermission,
96
- ).toHaveBeenCalled(),
97
- );
98
- await waitFor(() => expect(result.current.action).toBeNull());
99
- });
100
-
101
- it("checks the can_edit relation on the agent", async () => {
102
- const client = createMockStigmer({ isAuthorized: true });
103
- renderHook(() => useShareAgent({ agent: AGENT }), {
104
- wrapper: wrapper(client),
105
- });
106
-
107
- const check = (
108
- client as { iamPolicy: { checkMyPermission: ReturnType<typeof vi.fn> } }
109
- ).iamPolicy.checkMyPermission;
110
- await waitFor(() => expect(check).toHaveBeenCalled());
111
- const input = check.mock.calls[0][0] as {
112
- resource?: { kind: string; id: string };
113
- relation: string;
114
- };
115
- expect(input.relation).toBe("can_edit");
116
- expect(input.resource?.kind).toBe("agent");
117
- expect(input.resource?.id).toBe("agt_1");
118
- });
119
-
120
- it("opens the dialog via the action", async () => {
121
- const { result } = renderHook(() => useShareAgent({ agent: AGENT }), {
122
- wrapper: wrapper(createMockStigmer({ isAuthorized: true })),
123
- });
124
-
125
- await waitFor(() => expect(result.current.action).not.toBeNull());
126
- expect(result.current.isOpen).toBe(false);
127
-
128
- result.current.action?.onAction();
129
- await waitFor(() => expect(result.current.isOpen).toBe(true));
130
- });
131
- });
@@ -1,105 +0,0 @@
1
- "use client";
2
-
3
- import { create } from "@bufbuild/protobuf";
4
- import type { Agent } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
5
- import type { AgentShare } from "@stigmer/protos/ai/stigmer/agentic/agentshare/v1/api_pb";
6
- import { GetAgentSharesByAgentRequestSchema } from "@stigmer/protos/ai/stigmer/agentic/agentshare/v1/io_pb";
7
- import { useStigmer } from "../hooks.js";
8
- import { useFetch } from "../internal/useFetch.js";
9
-
10
- /** Return value of {@link useAgentShare}. */
11
- export interface UseAgentShareReturn {
12
- /**
13
- * The agent's canonical share, or `null` while loading, on error, or
14
- * when the agent has never been shared. `share === null && !isLoading
15
- * && !error` means "no share exists yet" — the first save creates it.
16
- */
17
- readonly share: AgentShare | null;
18
- /** `true` while the initial fetch or a refetch is in flight. */
19
- readonly isLoading: boolean;
20
- /** `true` while a background refetch is in flight and stale data is shown. */
21
- readonly isRefetching: boolean;
22
- /** Error from the last failed request, or `null` when healthy. */
23
- readonly error: Error | null;
24
- /** Discard cached data and re-fetch the share from the server. */
25
- readonly refetch: () => void;
26
- }
27
-
28
- /**
29
- * The canonical share among an agent's shares **within one sharing org**:
30
- * the one whose slug equals the agent's slug (the server's default when a
31
- * share is created without an explicit slug), falling back to the first
32
- * entry in that org. The data model allows N shares per agent across N
33
- * orgs (decision 011 D3 + decision 013), so the org filter is what keeps
34
- * each org's dialog on its own channel — without it, an owner who can
35
- * also see another org's share of the same agent would edit the wrong
36
- * one. Extra shares created via manifests never confuse the dialog.
37
- */
38
- function pickCanonicalShare(
39
- shares: readonly AgentShare[],
40
- shareOrg: string,
41
- agentSlug: string,
42
- ): AgentShare | null {
43
- const inOrg = shares.filter((share) => share.metadata?.org === shareOrg);
44
- return (
45
- inOrg.find((share) => share.metadata?.slug === agentSlug) ??
46
- inOrg[0] ??
47
- null
48
- );
49
- }
50
-
51
- /**
52
- * Data hook that loads an agent's canonical {@link AgentShare} — the
53
- * resource carrying the hosted-chat channel configuration (audience,
54
- * allowed origins, visitor messages, tool credentials, link token).
55
- *
56
- * Sharing is channel configuration, not agent behavior (decision 011):
57
- * it lives in its own resource, so reading the agent alone can never
58
- * tell whether it is shared. This hook is how owner-side surfaces (the
59
- * Share dialog) resolve that state.
60
- *
61
- * `shareOrg` scopes resolution to one sharing org's channel and defaults
62
- * to the agent's own org (the owner's share). Pass the viewer's org to
63
- * manage a **cross-org share** — the viewer's own channel of another
64
- * org's marketplace-public agent (decision 013).
65
- *
66
- * Pass `null` for `agent` to skip fetching (stable no-op) — useful
67
- * while the agent is still loading. A resolved `null` share means the
68
- * agent has never been shared in `shareOrg`; the first save creates the
69
- * share.
70
- *
71
- * @example
72
- * ```tsx
73
- * const { share, isLoading } = useAgentShare(agent);
74
- *
75
- * if (isLoading) return <Spinner />;
76
- * const enabled = share?.spec?.enabled ?? false;
77
- * ```
78
- */
79
- export function useAgentShare(
80
- agent: Agent | null,
81
- shareOrg?: string,
82
- ): UseAgentShareReturn {
83
- const stigmer = useStigmer();
84
-
85
- const agentId = agent?.metadata?.id ?? "";
86
- const agentSlug = agent?.metadata?.slug ?? "";
87
- const resolvedShareOrg = shareOrg || (agent?.metadata?.org ?? "");
88
-
89
- const fetchFn = agentId
90
- ? async () => {
91
- const result = await stigmer.agentShare.getByAgent(
92
- create(GetAgentSharesByAgentRequestSchema, { agentId }),
93
- );
94
- return pickCanonicalShare(result.items, resolvedShareOrg, agentSlug);
95
- }
96
- : null;
97
-
98
- const { data: share, isLoading, isRefetching, error, refetch } = useFetch(
99
- fetchFn,
100
- [agentId, agentSlug, resolvedShareOrg, stigmer],
101
- null as AgentShare | null,
102
- );
103
-
104
- return { share, isLoading, isRefetching, error, refetch };
105
- }
@@ -1,141 +0,0 @@
1
- "use client";
2
-
3
- import { useCallback, useState, type ReactNode } from "react";
4
- import type { Agent } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
5
- import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
6
- import { useCheckPermission } from "../iam-policy/useCheckPermission.js";
7
- import type { DetailAction } from "../resource-detail/types.js";
8
- import { ShareAgentDialog } from "./ShareAgentDialog.js";
9
-
10
- /** Arguments for {@link useCreateExternalShareLink}. */
11
- export interface UseCreateExternalShareLinkArgs {
12
- /**
13
- * The agent to share, or `null` while it is still loading. When
14
- * `null`, {@link UseCreateExternalShareLinkReturn.action} is `null`
15
- * and the dialog renders nothing — safe to call before the resource
16
- * is ready.
17
- */
18
- readonly agent: Agent | null;
19
- /**
20
- * The viewer's own organization — the org that would own the external
21
- * share, pay for its traffic, and host its URL. When empty or equal to
22
- * the agent's org, the action is `null` (the same-org Share entry from
23
- * {@link useShareAgent} covers that case).
24
- */
25
- readonly viewerOrg: string;
26
- /**
27
- * Builds the absolute public chat URL for the shared agent. Same
28
- * contract as {@link useShareAgent}: the host application owns URL
29
- * construction. When omitted, the dialog falls back to the relative
30
- * `/chat/<org>/<slug>`.
31
- */
32
- readonly buildShareUrl?: (org: string, slug: string) => string;
33
- /** Called after any sharing change is persisted. */
34
- readonly onSharingChanged?: () => void;
35
- /** Menu-item label. @default "Create share link" */
36
- readonly label?: string;
37
- }
38
-
39
- /** Return value of {@link useCreateExternalShareLink}. */
40
- export interface UseCreateExternalShareLinkReturn {
41
- /**
42
- * A ready-to-spread {@link DetailAction} for a kebab/overflow menu, or
43
- * `null` when the entry does not apply: agent still loading, same-org
44
- * (or no) viewer org, agent not marketplace-public, or the viewer
45
- * lacks `can_create_agent_share` in their own org. Lives in the
46
- * `"sharing"` group, like its same-org sibling.
47
- */
48
- readonly action: DetailAction | null;
49
- /** The {@link ShareAgentDialog} node — render it once in the host tree. */
50
- readonly dialog: ReactNode;
51
- /** Imperatively open the dialog. */
52
- readonly open: () => void;
53
- /** Whether the dialog is currently open. */
54
- readonly isOpen: boolean;
55
- }
56
-
57
- /**
58
- * The marketplace entry for **cross-org shares** (decision 013): a
59
- * "Create share link" action on another org's marketplace-public agent
60
- * that opens the same {@link ShareAgentDialog} with the share org set to
61
- * the viewer's own org. The resulting share is the viewer org's channel —
62
- * its URL (`/chat/<viewer-org>/<slug>`), its billing, its credential
63
- * bindings — while the agent blueprint stays live in its own org.
64
- *
65
- * The gate mirrors the server's create bar so the action never appears
66
- * to a user whose create would be refused:
67
- *
68
- * - the agent lives in another org and is `visibility_public` — the
69
- * origin org's implicit consent (D1); the agent-side `can_execute`
70
- * half of the bar is granted to every authenticated account on public
71
- * agents by the `allow_public` wildcard, so visibility is its exact
72
- * client-side proxy;
73
- * - the viewer holds `can_create_agent_share` in their own org (D2's
74
- * org-side half — admin-level, since a public share spends that org's
75
- * credits on the open internet). The org check uses the org slug as
76
- * the resource id: an Organization's id equals its slug
77
- * (ApiResourceMetadata.id).
78
- *
79
- * The same-org Share entry ({@link useShareAgent}, gated on agent
80
- * `can_edit`) and this one are mutually exclusive by construction — at
81
- * most one renders for any viewer/agent pair — so hosts can inject both
82
- * unconditionally.
83
- *
84
- * @example
85
- * ```tsx
86
- * const externalShare = useCreateExternalShareLink({
87
- * agent,
88
- * viewerOrg: activeOrgSlug,
89
- * buildShareUrl,
90
- * onSharingChanged: refetch,
91
- * });
92
- * // ...
93
- * <ResourceDetailShell actions={[...actions, externalShare.action].filter(Boolean)} ... />
94
- * {externalShare.dialog}
95
- * ```
96
- */
97
- export function useCreateExternalShareLink({
98
- agent,
99
- viewerOrg,
100
- buildShareUrl,
101
- onSharingChanged,
102
- label = "Create share link",
103
- }: UseCreateExternalShareLinkArgs): UseCreateExternalShareLinkReturn {
104
- const [isOpen, setIsOpen] = useState(false);
105
-
106
- const agentOrg = agent?.metadata?.org ?? "";
107
- const isCrossOrg = !!agent && viewerOrg !== "" && viewerOrg !== agentOrg;
108
- const isPublic =
109
- agent?.metadata?.visibility === ApiResourceVisibility.visibility_public;
110
-
111
- // The org-side bar is only worth asking about when the entry could
112
- // apply at all — passing null skips the RPC entirely.
113
- const { allowed: canCreateShare } = useCheckPermission(
114
- isCrossOrg && isPublic
115
- ? { kind: "organization", id: viewerOrg }
116
- : null,
117
- "can_create_agent_share",
118
- );
119
-
120
- const open = useCallback(() => setIsOpen(true), []);
121
-
122
- const applicable = isCrossOrg && isPublic && canCreateShare;
123
-
124
- const action: DetailAction | null = applicable
125
- ? { id: "create-share-link", label, group: "sharing", onAction: open }
126
- : null;
127
-
128
- const dialog =
129
- agent && applicable ? (
130
- <ShareAgentDialog
131
- open={isOpen}
132
- onOpenChange={setIsOpen}
133
- agent={agent}
134
- shareOrg={viewerOrg}
135
- buildShareUrl={buildShareUrl}
136
- onSharingChanged={onSharingChanged}
137
- />
138
- ) : null;
139
-
140
- return { action, dialog, open, isOpen };
141
- }
@@ -1,107 +0,0 @@
1
- "use client";
2
-
3
- import { useCallback, useState, type ReactNode } from "react";
4
- import type { Agent } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
5
- import { useCheckPermission } from "../iam-policy/useCheckPermission.js";
6
- import type { DetailAction } from "../resource-detail/types.js";
7
- import { ShareAgentDialog } from "./ShareAgentDialog.js";
8
-
9
- /** Arguments for {@link useShareAgent}. */
10
- export interface UseShareAgentArgs {
11
- /**
12
- * The agent whose sharing is managed, or `null` while it is still
13
- * loading. When `null`, {@link UseShareAgentReturn.action} is `null`
14
- * and the dialog renders nothing — safe to call before the resource
15
- * is ready.
16
- */
17
- readonly agent: Agent | null;
18
- /**
19
- * Builds the absolute public chat URL for the shared agent. The host
20
- * application owns URL construction (its configured public origin may
21
- * differ from the rendering origin — e.g. the desktop app). When
22
- * omitted, the dialog falls back to the relative `/chat/<org>/<slug>`.
23
- */
24
- readonly buildShareUrl?: (org: string, slug: string) => string;
25
- /**
26
- * Called after any sharing change is persisted. Hosts typically pass
27
- * the agent data hook's `refetch`.
28
- */
29
- readonly onSharingChanged?: () => void;
30
- /** Menu-item label. @default "Share" */
31
- readonly label?: string;
32
- }
33
-
34
- /** Return value of {@link useShareAgent}. */
35
- export interface UseShareAgentReturn {
36
- /**
37
- * A ready-to-spread {@link DetailAction} for a kebab/overflow menu, or
38
- * `null` when the agent is unavailable or the user lacks `can_edit`.
39
- * Lives in the `"sharing"` group, beside "Manage access".
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
- /**
51
- * Wires the Share dialog to a kebab/overflow menu — the same trigger
52
- * shape as {@link useManageAccess}, its conceptual sibling: Manage access
53
- * governs who can *read* the blueprint; Share governs who can *chat* with
54
- * the running agent (billed to the owning org).
55
- *
56
- * Owns the open-state and the `can_edit` gate — the same permission the
57
- * AgentShare create/apply handlers enforce on the referenced agent, so
58
- * the action never appears to a user whose changes would be rejected.
59
- * Returns a `null` action while the agent is loading or the user cannot
60
- * edit, so the host can unconditionally fold `action` into its actions
61
- * array.
62
- *
63
- * @example
64
- * ```tsx
65
- * const share = useShareAgent({
66
- * agent,
67
- * buildShareUrl: (org, slug) => `${appOrigin}/chat/${org}/${slug}`,
68
- * onSharingChanged: refetch,
69
- * });
70
- * // ...
71
- * <ResourceDetailShell actions={share.action ? [...actions, share.action] : actions} ... />
72
- * {share.dialog}
73
- * ```
74
- */
75
- export function useShareAgent({
76
- agent,
77
- buildShareUrl,
78
- onSharingChanged,
79
- label = "Share",
80
- }: UseShareAgentArgs): UseShareAgentReturn {
81
- const [isOpen, setIsOpen] = useState(false);
82
-
83
- const agentId = agent?.metadata?.id ?? null;
84
- const { allowed: canEdit } = useCheckPermission(
85
- agentId ? { kind: "agent", id: agentId } : null,
86
- "can_edit",
87
- );
88
-
89
- const open = useCallback(() => setIsOpen(true), []);
90
-
91
- const action: DetailAction | null =
92
- agent && canEdit
93
- ? { id: "share", label, group: "sharing", onAction: open }
94
- : null;
95
-
96
- const dialog = agent ? (
97
- <ShareAgentDialog
98
- open={isOpen}
99
- onOpenChange={setIsOpen}
100
- agent={agent}
101
- buildShareUrl={buildShareUrl}
102
- onSharingChanged={onSharingChanged}
103
- />
104
- ) : null;
105
-
106
- return { action, dialog, open, isOpen };
107
- }