@stigmer/react 3.1.7 → 3.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. package/agent/AgentDetailView.d.ts +9 -1
  2. package/agent/AgentDetailView.d.ts.map +1 -1
  3. package/agent/AgentDetailView.js +15 -4
  4. package/agent/AgentDetailView.js.map +1 -1
  5. package/environment/EnvironmentListPanel.d.ts.map +1 -1
  6. package/environment/EnvironmentListPanel.js +11 -3
  7. package/environment/EnvironmentListPanel.js.map +1 -1
  8. package/environment/EnvironmentPicker.d.ts +17 -1
  9. package/environment/EnvironmentPicker.d.ts.map +1 -1
  10. package/environment/EnvironmentPicker.js +3 -2
  11. package/environment/EnvironmentPicker.js.map +1 -1
  12. package/environment/EnvironmentVariableEditor.d.ts.map +1 -1
  13. package/environment/EnvironmentVariableEditor.js +9 -3
  14. package/environment/EnvironmentVariableEditor.js.map +1 -1
  15. package/environment/index.d.ts +1 -0
  16. package/environment/index.d.ts.map +1 -1
  17. package/environment/index.js +1 -0
  18. package/environment/index.js.map +1 -1
  19. package/environment/shareRestriction.d.ts +13 -0
  20. package/environment/shareRestriction.d.ts.map +1 -0
  21. package/environment/shareRestriction.js +15 -0
  22. package/environment/shareRestriction.js.map +1 -0
  23. package/execution/ToolCallItem.d.ts.map +1 -1
  24. package/execution/ToolCallItem.js +12 -0
  25. package/execution/ToolCallItem.js.map +1 -1
  26. package/execution/ToolRunGroup.js +4 -0
  27. package/execution/ToolRunGroup.js.map +1 -1
  28. package/execution/WriteBackCard.d.ts +14 -5
  29. package/execution/WriteBackCard.d.ts.map +1 -1
  30. package/execution/WriteBackCard.js +103 -32
  31. package/execution/WriteBackCard.js.map +1 -1
  32. package/execution/WriteBacksWidget.d.ts +1 -1
  33. package/execution/WriteBacksWidget.js +2 -2
  34. package/execution/WriteBacksWidget.js.map +1 -1
  35. package/execution/index.d.ts +2 -0
  36. package/execution/index.d.ts.map +1 -1
  37. package/execution/index.js +1 -0
  38. package/execution/index.js.map +1 -1
  39. package/execution/write-back-utils.d.ts +57 -0
  40. package/execution/write-back-utils.d.ts.map +1 -0
  41. package/execution/write-back-utils.js +81 -0
  42. package/execution/write-back-utils.js.map +1 -0
  43. package/index.d.ts +6 -2
  44. package/index.d.ts.map +1 -1
  45. package/index.js +6 -1
  46. package/index.js.map +1 -1
  47. package/internal/dev/dom-counter.d.ts.map +1 -1
  48. package/internal/dev/dom-counter.js +2 -2
  49. package/internal/dev/dom-counter.js.map +1 -1
  50. package/internal/dev/enabled.d.ts +7 -0
  51. package/internal/dev/enabled.d.ts.map +1 -0
  52. package/internal/dev/enabled.js +38 -0
  53. package/internal/dev/enabled.js.map +1 -0
  54. package/internal/dev/profiler-wrapper.d.ts +3 -3
  55. package/internal/dev/profiler-wrapper.d.ts.map +1 -1
  56. package/internal/dev/profiler-wrapper.js +5 -5
  57. package/internal/dev/profiler-wrapper.js.map +1 -1
  58. package/internal/dev/use-key-stability.d.ts.map +1 -1
  59. package/internal/dev/use-key-stability.js +2 -2
  60. package/internal/dev/use-key-stability.js.map +1 -1
  61. package/internal/dev/use-render-tracer.d.ts +2 -2
  62. package/internal/dev/use-render-tracer.d.ts.map +1 -1
  63. package/internal/dev/use-render-tracer.js +4 -4
  64. package/internal/dev/use-render-tracer.js.map +1 -1
  65. package/internal/dev/use-stream-rate.d.ts +2 -1
  66. package/internal/dev/use-stream-rate.d.ts.map +1 -1
  67. package/internal/dev/use-stream-rate.js +4 -3
  68. package/internal/dev/use-stream-rate.js.map +1 -1
  69. package/library/ResourceVisibilityControl.d.ts +3 -0
  70. package/library/ResourceVisibilityControl.d.ts.map +1 -1
  71. package/library/ResourceVisibilityControl.js +22 -8
  72. package/library/ResourceVisibilityControl.js.map +1 -1
  73. package/library/useUpdateVisibility.d.ts +5 -3
  74. package/library/useUpdateVisibility.d.ts.map +1 -1
  75. package/library/useUpdateVisibility.js +7 -2
  76. package/library/useUpdateVisibility.js.map +1 -1
  77. package/library/visibilityLevels.d.ts +17 -0
  78. package/library/visibilityLevels.d.ts.map +1 -1
  79. package/library/visibilityLevels.js +32 -0
  80. package/library/visibilityLevels.js.map +1 -1
  81. package/package.json +4 -4
  82. package/session/NewSessionViewer.d.ts +11 -1
  83. package/session/NewSessionViewer.d.ts.map +1 -1
  84. package/session/NewSessionViewer.js +28 -8
  85. package/session/NewSessionViewer.js.map +1 -1
  86. package/session/SessionViewer.d.ts +9 -2
  87. package/session/SessionViewer.d.ts.map +1 -1
  88. package/session/SessionViewer.js +26 -12
  89. package/session/SessionViewer.js.map +1 -1
  90. package/session/audience.d.ts +12 -4
  91. package/session/audience.d.ts.map +1 -1
  92. package/session/facets/ChangesTab.d.ts.map +1 -1
  93. package/session/facets/ChangesTab.js +3 -1
  94. package/session/facets/ChangesTab.js.map +1 -1
  95. package/session/useNewSessionFlow.d.ts +12 -0
  96. package/session/useNewSessionFlow.d.ts.map +1 -1
  97. package/session/useNewSessionFlow.js +13 -1
  98. package/session/useNewSessionFlow.js.map +1 -1
  99. package/session/useSessionPageFlow.d.ts +13 -0
  100. package/session/useSessionPageFlow.d.ts.map +1 -1
  101. package/session/useSessionPageFlow.js +7 -2
  102. package/session/useSessionPageFlow.js.map +1 -1
  103. package/sharing/ShareAgentDialog.d.ts +57 -0
  104. package/sharing/ShareAgentDialog.d.ts.map +1 -0
  105. package/sharing/ShareAgentDialog.js +478 -0
  106. package/sharing/ShareAgentDialog.js.map +1 -0
  107. package/sharing/SharedAgentChat.d.ts +84 -0
  108. package/sharing/SharedAgentChat.d.ts.map +1 -0
  109. package/sharing/SharedAgentChat.js +80 -0
  110. package/sharing/SharedAgentChat.js.map +1 -0
  111. package/sharing/index.d.ts +18 -0
  112. package/sharing/index.d.ts.map +1 -0
  113. package/sharing/index.js +12 -0
  114. package/sharing/index.js.map +1 -0
  115. package/sharing/useAgentShare.d.ts +43 -0
  116. package/sharing/useAgentShare.d.ts.map +1 -0
  117. package/sharing/useAgentShare.js +54 -0
  118. package/sharing/useAgentShare.js.map +1 -0
  119. package/sharing/useRotateShareLink.d.ts +45 -0
  120. package/sharing/useRotateShareLink.d.ts.map +1 -0
  121. package/sharing/useRotateShareLink.js +58 -0
  122. package/sharing/useRotateShareLink.js.map +1 -0
  123. package/sharing/useSaveAgentShare.d.ts +105 -0
  124. package/sharing/useSaveAgentShare.d.ts.map +1 -0
  125. package/sharing/useSaveAgentShare.js +97 -0
  126. package/sharing/useSaveAgentShare.js.map +1 -0
  127. package/sharing/useShareAgent.d.ts +69 -0
  128. package/sharing/useShareAgent.d.ts.map +1 -0
  129. package/sharing/useShareAgent.js +42 -0
  130. package/sharing/useShareAgent.js.map +1 -0
  131. package/sharing/useShareToolReadiness.d.ts +52 -0
  132. package/sharing/useShareToolReadiness.d.ts.map +1 -0
  133. package/sharing/useShareToolReadiness.js +89 -0
  134. package/sharing/useShareToolReadiness.js.map +1 -0
  135. package/sharing/useSharedAgentProfile.d.ts +85 -0
  136. package/sharing/useSharedAgentProfile.d.ts.map +1 -0
  137. package/sharing/useSharedAgentProfile.js +73 -0
  138. package/sharing/useSharedAgentProfile.js.map +1 -0
  139. package/src/agent/AgentDetailView.tsx +28 -3
  140. package/src/environment/EnvironmentListPanel.tsx +47 -18
  141. package/src/environment/EnvironmentPicker.tsx +23 -2
  142. package/src/environment/EnvironmentVariableEditor.tsx +27 -17
  143. package/src/environment/__tests__/shareRestriction.test.ts +35 -0
  144. package/src/environment/index.ts +5 -0
  145. package/src/environment/shareRestriction.ts +22 -0
  146. package/src/execution/ToolCallItem.tsx +19 -1
  147. package/src/execution/ToolRunGroup.tsx +4 -0
  148. package/src/execution/WriteBackCard.tsx +285 -88
  149. package/src/execution/WriteBacksWidget.tsx +5 -5
  150. package/src/execution/__tests__/ToolCallItem.test.tsx +51 -0
  151. package/src/execution/__tests__/ToolRunGroup.test.tsx +22 -0
  152. package/src/execution/__tests__/WriteBackCard.test.tsx +64 -10
  153. package/src/execution/__tests__/write-back-utils.test.ts +135 -0
  154. package/src/execution/index.ts +7 -0
  155. package/src/execution/write-back-utils.ts +107 -0
  156. package/src/index.ts +38 -0
  157. package/src/internal/dev/__tests__/use-key-stability.test.ts +4 -0
  158. package/src/internal/dev/__tests__/use-render-tracer.test.ts +4 -0
  159. package/src/internal/dev/dom-counter.ts +2 -3
  160. package/src/internal/dev/enabled.ts +39 -0
  161. package/src/internal/dev/profiler-wrapper.tsx +5 -6
  162. package/src/internal/dev/use-key-stability.ts +2 -3
  163. package/src/internal/dev/use-render-tracer.ts +4 -5
  164. package/src/internal/dev/use-stream-rate.ts +4 -4
  165. package/src/library/ResourceVisibilityControl.tsx +23 -7
  166. package/src/library/__tests__/visibilityLevels.test.ts +33 -0
  167. package/src/library/useUpdateVisibility.ts +9 -3
  168. package/src/library/visibilityLevels.ts +36 -0
  169. package/src/session/NewSessionViewer.tsx +67 -31
  170. package/src/session/SessionViewer.tsx +67 -36
  171. package/src/session/__tests__/audienceWiring.test.tsx +99 -0
  172. package/src/session/__tests__/useNewSessionFlow.test.tsx +75 -1
  173. package/src/session/__tests__/useSessionPageFlow.guest.test.tsx +112 -0
  174. package/src/session/audience.ts +12 -4
  175. package/src/session/facets/ChangesTab.tsx +6 -4
  176. package/src/session/useNewSessionFlow.ts +26 -1
  177. package/src/session/useSessionPageFlow.ts +24 -2
  178. package/src/sharing/ShareAgentDialog.tsx +1489 -0
  179. package/src/sharing/SharedAgentChat.tsx +283 -0
  180. package/src/sharing/__tests__/ShareAgentDialog.test.tsx +926 -0
  181. package/src/sharing/__tests__/SharedAgentChat.test.tsx +222 -0
  182. package/src/sharing/__tests__/useAgentShare.test.tsx +149 -0
  183. package/src/sharing/__tests__/useRotateShareLink.test.tsx +96 -0
  184. package/src/sharing/__tests__/useSaveAgentShare.test.tsx +241 -0
  185. package/src/sharing/__tests__/useShareAgent.test.tsx +131 -0
  186. package/src/sharing/__tests__/useShareToolReadiness.test.tsx +219 -0
  187. package/src/sharing/__tests__/useSharedAgentProfile.test.tsx +201 -0
  188. package/src/sharing/index.ts +32 -0
  189. package/src/sharing/useAgentShare.ts +91 -0
  190. package/src/sharing/useRotateShareLink.ts +81 -0
  191. package/src/sharing/useSaveAgentShare.ts +186 -0
  192. package/src/sharing/useShareAgent.tsx +107 -0
  193. package/src/sharing/useShareToolReadiness.ts +131 -0
  194. package/src/sharing/useSharedAgentProfile.ts +132 -0
  195. package/src/switch/Switch.tsx +97 -0
  196. package/src/switch/__tests__/Switch.test.tsx +107 -0
  197. package/src/switch/index.ts +2 -0
  198. package/styles.css +1 -1
  199. package/switch/Switch.d.ts +47 -0
  200. package/switch/Switch.d.ts.map +1 -0
  201. package/switch/Switch.js +41 -0
  202. package/switch/Switch.js.map +1 -0
  203. package/switch/index.d.ts +3 -0
  204. package/switch/index.d.ts.map +1 -0
  205. package/switch/index.js +2 -0
  206. package/switch/index.js.map +1 -0
@@ -0,0 +1,201 @@
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 { Code } from "@connectrpc/connect";
5
+ import { StigmerError } from "@stigmer/sdk";
6
+ import { StigmerContext } from "../../context";
7
+ import { FetchCacheContext } from "../../internal/FetchCacheProvider";
8
+ import { useSharedAgentProfile } from "../useSharedAgentProfile";
9
+
10
+ function createMockStigmer(overrides: {
11
+ getSharedProfile?: (...args: unknown[]) => Promise<unknown>;
12
+ getSharedProfileForMember?: (...args: unknown[]) => Promise<unknown>;
13
+ } = {}) {
14
+ return {
15
+ agentShare: {
16
+ getSharedProfile:
17
+ overrides.getSharedProfile ?? vi.fn().mockResolvedValue(null),
18
+ getSharedProfileForMember:
19
+ overrides.getSharedProfileForMember ?? vi.fn().mockResolvedValue(null),
20
+ },
21
+ } as never;
22
+ }
23
+
24
+ function wrapper(client: unknown) {
25
+ return function Wrapper({ children }: { children: ReactNode }) {
26
+ return (
27
+ <FetchCacheContext.Provider value={null}>
28
+ <StigmerContext.Provider value={client as never}>
29
+ {children}
30
+ </StigmerContext.Provider>
31
+ </FetchCacheContext.Provider>
32
+ );
33
+ };
34
+ }
35
+
36
+ const PROFILE = {
37
+ org: "acme",
38
+ slug: "support-agent",
39
+ name: "Support Agent",
40
+ description: "Answers support questions",
41
+ iconUrl: "",
42
+ defaultInstanceId: "inst_1",
43
+ };
44
+
45
+ describe("useSharedAgentProfile", () => {
46
+ beforeEach(() => {
47
+ vi.restoreAllMocks();
48
+ });
49
+
50
+ it("fetches the profile by org and slug", async () => {
51
+ const getSharedProfile = vi.fn().mockResolvedValue(PROFILE);
52
+ const client = createMockStigmer({ getSharedProfile });
53
+
54
+ const { result } = renderHook(
55
+ () => useSharedAgentProfile("acme", "support-agent"),
56
+ { wrapper: wrapper(client) },
57
+ );
58
+
59
+ expect(result.current.isLoading).toBe(true);
60
+
61
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
62
+
63
+ expect(result.current.profile).toBe(PROFILE);
64
+ expect(result.current.error).toBeNull();
65
+ // The hook builds a GetSharedProfileRequest message; a plain link
66
+ // carries an empty link token.
67
+ expect(getSharedProfile).toHaveBeenCalledWith(
68
+ expect.objectContaining({
69
+ org: "acme",
70
+ slug: "support-agent",
71
+ linkToken: "",
72
+ }),
73
+ );
74
+ });
75
+
76
+ it("threads the linkToken option into the public request", async () => {
77
+ const getSharedProfile = vi.fn().mockResolvedValue(PROFILE);
78
+ const client = createMockStigmer({ getSharedProfile });
79
+
80
+ const { result } = renderHook(
81
+ () =>
82
+ useSharedAgentProfile("acme", "support-agent", { linkToken: "tok123" }),
83
+ { wrapper: wrapper(client) },
84
+ );
85
+
86
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
87
+
88
+ expect(getSharedProfile).toHaveBeenCalledWith(
89
+ expect.objectContaining({ linkToken: "tok123" }),
90
+ );
91
+ });
92
+
93
+ it("resolves through getSharedProfileForMember for the org audience", async () => {
94
+ const getSharedProfile = vi.fn();
95
+ const getSharedProfileForMember = vi.fn().mockResolvedValue(PROFILE);
96
+ const client = createMockStigmer({
97
+ getSharedProfile,
98
+ getSharedProfileForMember,
99
+ });
100
+
101
+ const { result } = renderHook(
102
+ () =>
103
+ useSharedAgentProfile("acme", "support-agent", { audience: "org" }),
104
+ { wrapper: wrapper(client) },
105
+ );
106
+
107
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
108
+
109
+ expect(result.current.profile).toBe(PROFILE);
110
+ expect(getSharedProfileForMember).toHaveBeenCalledWith({
111
+ org: "acme",
112
+ slug: "support-agent",
113
+ });
114
+ // The anonymous path returns NOT_FOUND for org shares by design; the
115
+ // hook must never fall back to it in org mode.
116
+ expect(getSharedProfile).not.toHaveBeenCalled();
117
+ });
118
+
119
+ it("maps a member-path NOT_FOUND (non-member) to profile === null", async () => {
120
+ const getSharedProfileForMember = vi.fn().mockRejectedValue(
121
+ new StigmerError("not-found", "agent not found", Code.NotFound),
122
+ );
123
+ const client = createMockStigmer({ getSharedProfileForMember });
124
+
125
+ const { result } = renderHook(
126
+ () =>
127
+ useSharedAgentProfile("acme", "support-agent", { audience: "org" }),
128
+ { wrapper: wrapper(client) },
129
+ );
130
+
131
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
132
+
133
+ expect(result.current.profile).toBeNull();
134
+ expect(result.current.error).toBeNull();
135
+ });
136
+
137
+ it("skips fetching when org is null", () => {
138
+ const getSharedProfile = vi.fn();
139
+ const client = createMockStigmer({ getSharedProfile });
140
+
141
+ const { result } = renderHook(
142
+ () => useSharedAgentProfile(null, "support-agent"),
143
+ { wrapper: wrapper(client) },
144
+ );
145
+
146
+ expect(result.current.isLoading).toBe(false);
147
+ expect(result.current.profile).toBeNull();
148
+ expect(getSharedProfile).not.toHaveBeenCalled();
149
+ });
150
+
151
+ it("skips fetching when slug is null", () => {
152
+ const getSharedProfile = vi.fn();
153
+ const client = createMockStigmer({ getSharedProfile });
154
+
155
+ const { result } = renderHook(() => useSharedAgentProfile("acme", null), {
156
+ wrapper: wrapper(client),
157
+ });
158
+
159
+ expect(result.current.isLoading).toBe(false);
160
+ expect(result.current.profile).toBeNull();
161
+ expect(getSharedProfile).not.toHaveBeenCalled();
162
+ });
163
+
164
+ it("maps NOT_FOUND (unshared or nonexistent) to null without error", async () => {
165
+ // The generated client rethrows every failure wrapped as a
166
+ // StigmerError — reject with the same shape the hook really sees.
167
+ const getSharedProfile = vi
168
+ .fn()
169
+ .mockRejectedValue(
170
+ new StigmerError("not-found", "agent not found", Code.NotFound),
171
+ );
172
+ const client = createMockStigmer({ getSharedProfile });
173
+
174
+ const { result } = renderHook(
175
+ () => useSharedAgentProfile("acme", "revoked-agent"),
176
+ { wrapper: wrapper(client) },
177
+ );
178
+
179
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
180
+
181
+ expect(result.current.profile).toBeNull();
182
+ expect(result.current.error).toBeNull();
183
+ });
184
+
185
+ it("exposes non-404 failures as errors", async () => {
186
+ const getSharedProfile = vi
187
+ .fn()
188
+ .mockRejectedValue(new Error("Internal server error"));
189
+ const client = createMockStigmer({ getSharedProfile });
190
+
191
+ const { result } = renderHook(
192
+ () => useSharedAgentProfile("acme", "support-agent"),
193
+ { wrapper: wrapper(client) },
194
+ );
195
+
196
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
197
+
198
+ expect(result.current.error).toBeTruthy();
199
+ expect(result.current.profile).toBeNull();
200
+ });
201
+ });
@@ -0,0 +1,32 @@
1
+ export { useSharedAgentProfile } from "./useSharedAgentProfile.js";
2
+ export type {
3
+ UseSharedAgentProfileOptions,
4
+ UseSharedAgentProfileReturn,
5
+ } from "./useSharedAgentProfile.js";
6
+ export { SharedAgentChat } from "./SharedAgentChat.js";
7
+ export type { SharedAgentChatProps } from "./SharedAgentChat.js";
8
+ export { useAgentShare } from "./useAgentShare.js";
9
+ export type { UseAgentShareReturn } from "./useAgentShare.js";
10
+ export {
11
+ sharingAudienceFromProto,
12
+ useSaveAgentShare,
13
+ } from "./useSaveAgentShare.js";
14
+ export type {
15
+ AgentShareDraft,
16
+ SharingAudience,
17
+ UseSaveAgentShareReturn,
18
+ } from "./useSaveAgentShare.js";
19
+ export { useRotateShareLink } from "./useRotateShareLink.js";
20
+ export type { UseRotateShareLinkReturn } from "./useRotateShareLink.js";
21
+ export { ShareAgentDialog } from "./ShareAgentDialog.js";
22
+ export type { ShareAgentDialogProps } from "./ShareAgentDialog.js";
23
+ export { useShareAgent } from "./useShareAgent.js";
24
+ export type {
25
+ UseShareAgentArgs,
26
+ UseShareAgentReturn,
27
+ } from "./useShareAgent.js";
28
+ export { useShareToolReadiness } from "./useShareToolReadiness.js";
29
+ export type { ShareToolReadiness } from "./useShareToolReadiness.js";
30
+ // Origin validation moved to @stigmer/sdk (framework-free, shared with the
31
+ // CLI); re-exported here so existing @stigmer/react importers keep working.
32
+ export { validateOrigin, MAX_ALLOWED_ORIGINS } from "@stigmer/sdk";
@@ -0,0 +1,91 @@
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: the one whose slug equals
30
+ * the agent's slug (the server's default when a share is created without
31
+ * an explicit slug), falling back to the first entry. The data model
32
+ * allows N shares per agent (decision 011 D3); the console manages the
33
+ * canonical one in Phase A, so extra shares created via manifests never
34
+ * confuse the dialog.
35
+ */
36
+ function pickCanonicalShare(
37
+ shares: readonly AgentShare[],
38
+ agentSlug: string,
39
+ ): AgentShare | null {
40
+ return (
41
+ shares.find((share) => share.metadata?.slug === agentSlug) ??
42
+ shares[0] ??
43
+ null
44
+ );
45
+ }
46
+
47
+ /**
48
+ * Data hook that loads an agent's canonical {@link AgentShare} — the
49
+ * resource carrying the hosted-chat channel configuration (audience,
50
+ * allowed origins, visitor messages, tool credentials, link token).
51
+ *
52
+ * Sharing is channel configuration, not agent behavior (decision 011):
53
+ * it lives in its own resource, so reading the agent alone can never
54
+ * tell whether it is shared. This hook is how owner-side surfaces (the
55
+ * Share dialog) resolve that state.
56
+ *
57
+ * Pass `null` for `agent` to skip fetching (stable no-op) — useful
58
+ * while the agent is still loading. A resolved `null` share means the
59
+ * agent has never been shared; the first save creates the share.
60
+ *
61
+ * @example
62
+ * ```tsx
63
+ * const { share, isLoading } = useAgentShare(agent);
64
+ *
65
+ * if (isLoading) return <Spinner />;
66
+ * const enabled = share?.spec?.enabled ?? false;
67
+ * ```
68
+ */
69
+ export function useAgentShare(agent: Agent | null): UseAgentShareReturn {
70
+ const stigmer = useStigmer();
71
+
72
+ const agentId = agent?.metadata?.id ?? "";
73
+ const agentSlug = agent?.metadata?.slug ?? "";
74
+
75
+ const fetchFn = agentId
76
+ ? async () => {
77
+ const result = await stigmer.agentShare.getByAgent(
78
+ create(GetAgentSharesByAgentRequestSchema, { agentId }),
79
+ );
80
+ return pickCanonicalShare(result.items, agentSlug);
81
+ }
82
+ : null;
83
+
84
+ const { data: share, isLoading, isRefetching, error, refetch } = useFetch(
85
+ fetchFn,
86
+ [agentId, agentSlug, stigmer],
87
+ null as AgentShare | null,
88
+ );
89
+
90
+ return { share, isLoading, isRefetching, error, refetch };
91
+ }
@@ -0,0 +1,81 @@
1
+ "use client";
2
+
3
+ import { useCallback, useState } from "react";
4
+ import { create } from "@bufbuild/protobuf";
5
+ import type { AgentShare } from "@stigmer/protos/ai/stigmer/agentic/agentshare/v1/api_pb";
6
+ import { RotateShareLinkInputSchema } from "@stigmer/protos/ai/stigmer/agentic/agentshare/v1/io_pb";
7
+ import { useStigmer } from "../hooks.js";
8
+ import { toError } from "../internal/toError.js";
9
+
10
+ /** Return value of {@link useRotateShareLink}. */
11
+ export interface UseRotateShareLinkReturn {
12
+ /**
13
+ * Rotate the share's link token via the `rotateShareLink` RPC.
14
+ * Resolves with the updated share, whose `status.shareLinkToken`
15
+ * carries the new token for building the fresh share URL.
16
+ */
17
+ readonly rotateShareLink: () => Promise<AgentShare | undefined>;
18
+ /** `true` while the RPC is in flight. */
19
+ readonly isPending: boolean;
20
+ /** The last error from the RPC, or `null`. */
21
+ readonly error: Error | null;
22
+ }
23
+
24
+ /**
25
+ * Behavior hook that rotates a share's link token.
26
+ *
27
+ * Wraps the `stigmer.agentShare.rotateShareLink()` RPC (authorized by
28
+ * the share's `can_edit`) with loading and error state management. The
29
+ * server generates the token — there is nothing to supply — and the
30
+ * previous link stops working the moment the call resolves, including
31
+ * for visitors mid-conversation. The token lives in
32
+ * `status.shareLinkToken` (server-owned; this RPC is its sole writer),
33
+ * so it is never part of a share draft and survives every apply.
34
+ *
35
+ * The hook is stateless with respect to the share — adopt the returned
36
+ * share (or refetch) so the UI shows the new tokened link, matching the
37
+ * {@link useSaveAgentShare} convention.
38
+ *
39
+ * Pass `null` for `shareId` to produce a stable no-op (useful while the
40
+ * share is still loading, or before the agent has ever been shared —
41
+ * there is no token to rotate until a share exists).
42
+ *
43
+ * @example
44
+ * ```tsx
45
+ * const { rotateShareLink, isPending } = useRotateShareLink(
46
+ * share?.metadata?.id ?? null,
47
+ * );
48
+ *
49
+ * const updated = await rotateShareLink();
50
+ * // updated.status.shareLinkToken is the new token
51
+ * ```
52
+ */
53
+ export function useRotateShareLink(
54
+ shareId: string | null,
55
+ ): UseRotateShareLinkReturn {
56
+ const stigmer = useStigmer();
57
+ const [isPending, setIsPending] = useState(false);
58
+ const [error, setError] = useState<Error | null>(null);
59
+
60
+ const rotateShareLink = useCallback(async (): Promise<
61
+ AgentShare | undefined
62
+ > => {
63
+ if (!shareId) return undefined;
64
+
65
+ setIsPending(true);
66
+ setError(null);
67
+
68
+ try {
69
+ return await stigmer.agentShare.rotateShareLink(
70
+ create(RotateShareLinkInputSchema, { resourceId: shareId }),
71
+ );
72
+ } catch (err) {
73
+ setError(toError(err));
74
+ throw err;
75
+ } finally {
76
+ setIsPending(false);
77
+ }
78
+ }, [shareId, stigmer]);
79
+
80
+ return { rotateShareLink, isPending, error };
81
+ }
@@ -0,0 +1,186 @@
1
+ "use client";
2
+
3
+ import { useCallback, useState } from "react";
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 { AgentShareAudience } from "@stigmer/protos/ai/stigmer/agentic/agentshare/v1/spec_pb";
7
+ import type { ResourceRef } from "@stigmer/sdk";
8
+ import { useStigmer } from "../hooks.js";
9
+ import { toError } from "../internal/toError.js";
10
+
11
+ /**
12
+ * Who can chat with a shared agent over its hosted link.
13
+ *
14
+ * - `"public"` — anyone with the link; visitors are anonymous guests.
15
+ * - `"org"` — signed-in members of the owning organization only.
16
+ * Membership is checked on every conversation turn, so access ends the
17
+ * moment a member leaves the org.
18
+ */
19
+ export type SharingAudience = "public" | "org";
20
+
21
+ /**
22
+ * A complete share configuration to persist.
23
+ *
24
+ * Every field is required by design: saving **replaces the share's whole
25
+ * spec**, so a caller must always supply the full configuration. A
26
+ * partial type here would let a toggle change silently wipe
27
+ * `allowedOrigins`, `messages`, `environmentRefs`, or the `audience` —
28
+ * the required shape makes that mistake unrepresentable. (The rotatable
29
+ * link token is exempt: it is server-owned `status`, which survives
30
+ * every save verbatim.)
31
+ */
32
+ export interface AgentShareDraft {
33
+ /** Whether hosted-chat access for the configured audience is enabled. */
34
+ readonly enabled: boolean;
35
+ /** Who can chat: anyone with the link, or org members only. */
36
+ readonly audience: SharingAudience;
37
+ /**
38
+ * Exact web origins allowed to embed the shared agent
39
+ * (e.g. `https://example.com`). Empty list = embeddable anywhere.
40
+ * Embedding is public-audience only.
41
+ */
42
+ readonly allowedOrigins: readonly string[];
43
+ /** Owner-customized visitor refusal copy. Empty strings mean platform defaults. */
44
+ readonly messages: {
45
+ /** Shown when a visitor exceeds the message rate limit. */
46
+ readonly rateLimited: string;
47
+ /** Shown when the org's credits are exhausted (sharing fails closed). */
48
+ readonly unavailable: string;
49
+ /** Shown when a conversation hits its turn limit or inactivity timeout. */
50
+ readonly conversationEnded: string;
51
+ };
52
+ /**
53
+ * Org-shared environments whose values guest conversations receive —
54
+ * how a tool-using agent becomes chattable over a share link without
55
+ * touching its pristine default instance. Public-audience only (the
56
+ * proto CEL rule rejects bindings on org-audience shares, whose member
57
+ * sessions carry no share linkage in Phase A — decision 011 addendum).
58
+ */
59
+ readonly environmentRefs: readonly ResourceRef[];
60
+ }
61
+
62
+ /**
63
+ * Maps a proto {@link AgentShareAudience} to the SDK's string union.
64
+ * Unspecified means public by contract (a share created without an
65
+ * explicit audience is an anyone-with-link share).
66
+ */
67
+ export function sharingAudienceFromProto(
68
+ audience: AgentShareAudience | undefined,
69
+ ): SharingAudience {
70
+ return audience === AgentShareAudience.org ? "org" : "public";
71
+ }
72
+
73
+ function sharingAudienceToProto(audience: SharingAudience): AgentShareAudience {
74
+ return audience === "org"
75
+ ? AgentShareAudience.org
76
+ : AgentShareAudience.public;
77
+ }
78
+
79
+ /** Return value of {@link useSaveAgentShare}. */
80
+ export interface UseSaveAgentShareReturn {
81
+ /**
82
+ * Persist the complete share configuration. Resolves with the
83
+ * persisted {@link AgentShare} — adopt it as the new baseline (its
84
+ * `status.shareLinkToken` carries the live link token).
85
+ *
86
+ * `current` is the share being edited, or `null` when the agent has
87
+ * never been shared — the save then creates the canonical share.
88
+ */
89
+ readonly save: (
90
+ draft: AgentShareDraft,
91
+ current: AgentShare | null,
92
+ ) => Promise<AgentShare | undefined>;
93
+ /** `true` while the RPC is in flight. */
94
+ readonly isPending: boolean;
95
+ /** The last error from the RPC, or `null`. */
96
+ readonly error: Error | null;
97
+ }
98
+
99
+ /**
100
+ * Behavior hook that persists an agent's canonical share configuration.
101
+ *
102
+ * Commits via `stigmer.agentShare.apply()` — an idempotent upsert keyed
103
+ * on the share's `(org, slug)` identity — so one code path serves both
104
+ * the first enable (creates the share; the server authorizes on the
105
+ * referenced agent's `can_edit`) and every later edit. There is
106
+ * deliberately no create/update branching: the share slug is unique per
107
+ * org and defaults to the agent's slug, which makes apply-by-identity
108
+ * exact, and the generated `AgentShareInput` carries no `metadata.id`
109
+ * for an update to route by anyway.
110
+ *
111
+ * Disabling is a save with `enabled: false` — a config-preserving pause
112
+ * (decision 011 D1). Deleting the share is a separate, destructive
113
+ * operation this hook does not perform.
114
+ *
115
+ * Pass `null` for `agent` to produce a stable no-op (useful while the
116
+ * agent is still loading).
117
+ *
118
+ * @example
119
+ * ```tsx
120
+ * const { share } = useAgentShare(agent);
121
+ * const { save, isPending } = useSaveAgentShare(agent);
122
+ *
123
+ * const persisted = await save({ ...draft, enabled: true }, share);
124
+ * // persisted.status.shareLinkToken is the live link token
125
+ * ```
126
+ */
127
+ export function useSaveAgentShare(
128
+ agent: Agent | null,
129
+ ): UseSaveAgentShareReturn {
130
+ const stigmer = useStigmer();
131
+ const [isPending, setIsPending] = useState(false);
132
+ const [error, setError] = useState<Error | null>(null);
133
+
134
+ const agentOrg = agent?.metadata?.org ?? "";
135
+ const agentSlug = agent?.metadata?.slug ?? "";
136
+ const agentName = agent?.metadata?.name ?? "";
137
+
138
+ const save = useCallback(
139
+ async (
140
+ draft: AgentShareDraft,
141
+ current: AgentShare | null,
142
+ ): Promise<AgentShare | undefined> => {
143
+ if (!agentOrg || !agentSlug) return undefined;
144
+
145
+ setIsPending(true);
146
+ setError(null);
147
+
148
+ try {
149
+ return await stigmer.agentShare.apply({
150
+ // Identity: the existing share's org/slug when editing (a
151
+ // manifest-created share may carry a non-default slug — apply
152
+ // with the agent's slug would create a SECOND share), the
153
+ // agent's when creating (the server's own D2 default, made
154
+ // explicit).
155
+ org: current?.metadata?.org || agentOrg,
156
+ slug: current?.metadata?.slug || agentSlug,
157
+ name: current?.metadata?.name || agentName || agentSlug,
158
+ agentRef: { org: agentOrg, slug: agentSlug },
159
+ enabled: draft.enabled,
160
+ // Written as the explicit enum value, never left unspecified —
161
+ // a share persisted as "public" can't be silently downgraded
162
+ // by tooling that relies on the unspecified-means-public rule.
163
+ audience: sharingAudienceToProto(draft.audience),
164
+ allowedOrigins: [...draft.allowedOrigins],
165
+ messages: {
166
+ rateLimited: draft.messages.rateLimited,
167
+ unavailable: draft.messages.unavailable,
168
+ conversationEnded: draft.messages.conversationEnded,
169
+ },
170
+ environmentRefs: draft.environmentRefs.map((ref) => ({
171
+ org: ref.org,
172
+ slug: ref.slug,
173
+ })),
174
+ });
175
+ } catch (err) {
176
+ setError(toError(err));
177
+ throw err;
178
+ } finally {
179
+ setIsPending(false);
180
+ }
181
+ },
182
+ [agentOrg, agentSlug, agentName, stigmer],
183
+ );
184
+
185
+ return { save, isPending, error };
186
+ }