@stigmer/react 3.12.4 → 3.12.5

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 (190) hide show
  1. package/attachment/AttachmentChipList.js +16 -6
  2. package/attachment/AttachmentChipList.js.map +1 -1
  3. package/attachment/index.d.ts +1 -1
  4. package/attachment/index.d.ts.map +1 -1
  5. package/attachment/index.js.map +1 -1
  6. package/attachment/useAttachments.d.ts +51 -8
  7. package/attachment/useAttachments.d.ts.map +1 -1
  8. package/attachment/useAttachments.js +74 -3
  9. package/attachment/useAttachments.js.map +1 -1
  10. package/composer/SessionComposer.d.ts.map +1 -1
  11. package/composer/SessionComposer.js +17 -10
  12. package/composer/SessionComposer.js.map +1 -1
  13. package/conversation/ConversationComposer.d.ts +19 -3
  14. package/conversation/ConversationComposer.d.ts.map +1 -1
  15. package/conversation/ConversationComposer.js +4 -4
  16. package/conversation/ConversationComposer.js.map +1 -1
  17. package/conversation/ConversationTemplatePickerDialog.d.ts +63 -0
  18. package/conversation/ConversationTemplatePickerDialog.d.ts.map +1 -0
  19. package/conversation/ConversationTemplatePickerDialog.js +151 -0
  20. package/conversation/ConversationTemplatePickerDialog.js.map +1 -0
  21. package/conversation/ConversationsWorkbench.d.ts.map +1 -1
  22. package/conversation/ConversationsWorkbench.js +16 -5
  23. package/conversation/ConversationsWorkbench.js.map +1 -1
  24. package/conversation/index.d.ts +3 -1
  25. package/conversation/index.d.ts.map +1 -1
  26. package/conversation/index.js +1 -0
  27. package/conversation/index.js.map +1 -1
  28. package/conversation/useConversationParticipation.d.ts +41 -4
  29. package/conversation/useConversationParticipation.d.ts.map +1 -1
  30. package/conversation/useConversationParticipation.js +13 -2
  31. package/conversation/useConversationParticipation.js.map +1 -1
  32. package/identity-account/AccountPreferencesPanel.d.ts +35 -0
  33. package/identity-account/AccountPreferencesPanel.d.ts.map +1 -0
  34. package/identity-account/AccountPreferencesPanel.js +187 -0
  35. package/identity-account/AccountPreferencesPanel.js.map +1 -0
  36. package/identity-account/index.d.ts +8 -0
  37. package/identity-account/index.d.ts.map +1 -1
  38. package/identity-account/index.js +4 -0
  39. package/identity-account/index.js.map +1 -1
  40. package/identity-account/useAccountExecutionDefaults.d.ts +36 -0
  41. package/identity-account/useAccountExecutionDefaults.d.ts.map +1 -0
  42. package/identity-account/useAccountExecutionDefaults.js +46 -0
  43. package/identity-account/useAccountExecutionDefaults.js.map +1 -0
  44. package/identity-account/useMyIdentityAccount.d.ts +55 -0
  45. package/identity-account/useMyIdentityAccount.d.ts.map +1 -0
  46. package/identity-account/useMyIdentityAccount.js +37 -0
  47. package/identity-account/useMyIdentityAccount.js.map +1 -0
  48. package/identity-account/useUpdateIdentityAccount.d.ts +42 -0
  49. package/identity-account/useUpdateIdentityAccount.d.ts.map +1 -0
  50. package/identity-account/useUpdateIdentityAccount.js +53 -0
  51. package/identity-account/useUpdateIdentityAccount.js.map +1 -0
  52. package/index.d.ts +11 -9
  53. package/index.d.ts.map +1 -1
  54. package/index.js +8 -5
  55. package/index.js.map +1 -1
  56. package/internal/StandingContextField.d.ts +37 -0
  57. package/internal/StandingContextField.d.ts.map +1 -0
  58. package/internal/StandingContextField.js +24 -0
  59. package/internal/StandingContextField.js.map +1 -0
  60. package/library/InstanceVisibilitySelector.d.ts.map +1 -1
  61. package/library/InstanceVisibilitySelector.js +9 -2
  62. package/library/InstanceVisibilitySelector.js.map +1 -1
  63. package/library/ResourceVisibilityControl.d.ts +13 -3
  64. package/library/ResourceVisibilityControl.d.ts.map +1 -1
  65. package/library/ResourceVisibilityControl.js +33 -5
  66. package/library/ResourceVisibilityControl.js.map +1 -1
  67. package/library/VisibilityOptionRow.d.ts +6 -0
  68. package/library/VisibilityOptionRow.d.ts.map +1 -1
  69. package/library/VisibilityOptionRow.js +11 -1
  70. package/library/VisibilityOptionRow.js.map +1 -1
  71. package/library/VisibilitySelector.d.ts.map +1 -1
  72. package/library/VisibilitySelector.js +14 -2
  73. package/library/VisibilitySelector.js.map +1 -1
  74. package/library/index.d.ts +4 -2
  75. package/library/index.d.ts.map +1 -1
  76. package/library/index.js +2 -1
  77. package/library/index.js.map +1 -1
  78. package/library/useCanSetPublicVisibility.d.ts +30 -0
  79. package/library/useCanSetPublicVisibility.d.ts.map +1 -0
  80. package/library/useCanSetPublicVisibility.js +41 -0
  81. package/library/useCanSetPublicVisibility.js.map +1 -0
  82. package/library/visibilityLevels.d.ts +32 -1
  83. package/library/visibilityLevels.d.ts.map +1 -1
  84. package/library/visibilityLevels.js +34 -14
  85. package/library/visibilityLevels.js.map +1 -1
  86. package/mcp-server/useMcpServerConnect.d.ts +8 -5
  87. package/mcp-server/useMcpServerConnect.d.ts.map +1 -1
  88. package/mcp-server/useMcpServerConnect.js +2 -1
  89. package/mcp-server/useMcpServerConnect.js.map +1 -1
  90. package/mcp-server/useMcpServerOAuthConnect.d.ts.map +1 -1
  91. package/mcp-server/useMcpServerOAuthConnect.js +7 -2
  92. package/mcp-server/useMcpServerOAuthConnect.js.map +1 -1
  93. package/oauth-app/CreateOAuthAppForm.d.ts +2 -2
  94. package/oauth-app/CreateOAuthAppForm.d.ts.map +1 -1
  95. package/oauth-app/CreateOAuthAppForm.js +13 -4
  96. package/oauth-app/CreateOAuthAppForm.js.map +1 -1
  97. package/oauth-app/OAuthAppDetailPanel.d.ts.map +1 -1
  98. package/oauth-app/OAuthAppDetailPanel.js +32 -5
  99. package/oauth-app/OAuthAppDetailPanel.js.map +1 -1
  100. package/organization/OrgPreferencesPanel.d.ts +32 -0
  101. package/organization/OrgPreferencesPanel.d.ts.map +1 -0
  102. package/organization/OrgPreferencesPanel.js +90 -0
  103. package/organization/OrgPreferencesPanel.js.map +1 -0
  104. package/organization/OrgProfilePanel.d.ts.map +1 -1
  105. package/organization/OrgProfilePanel.js +5 -4
  106. package/organization/OrgProfilePanel.js.map +1 -1
  107. package/organization/index.d.ts +2 -0
  108. package/organization/index.d.ts.map +1 -1
  109. package/organization/index.js +1 -0
  110. package/organization/index.js.map +1 -1
  111. package/package.json +4 -4
  112. package/session/NewSessionViewer.d.ts +10 -1
  113. package/session/NewSessionViewer.d.ts.map +1 -1
  114. package/session/NewSessionViewer.js +2 -1
  115. package/session/NewSessionViewer.js.map +1 -1
  116. package/session/useNewSessionFlow.d.ts +27 -1
  117. package/session/useNewSessionFlow.d.ts.map +1 -1
  118. package/session/useNewSessionFlow.js +45 -8
  119. package/session/useNewSessionFlow.js.map +1 -1
  120. package/settings/AccountPreferencesSection.d.ts +3 -0
  121. package/settings/AccountPreferencesSection.d.ts.map +1 -0
  122. package/settings/AccountPreferencesSection.js +10 -0
  123. package/settings/AccountPreferencesSection.js.map +1 -0
  124. package/settings/OrgPreferencesSection.d.ts +3 -0
  125. package/settings/OrgPreferencesSection.d.ts.map +1 -0
  126. package/settings/OrgPreferencesSection.js +13 -0
  127. package/settings/OrgPreferencesSection.js.map +1 -0
  128. package/settings/index.d.ts +2 -0
  129. package/settings/index.d.ts.map +1 -1
  130. package/settings/index.js +2 -0
  131. package/settings/index.js.map +1 -1
  132. package/settings/settings-nav.d.ts.map +1 -1
  133. package/settings/settings-nav.js +17 -1
  134. package/settings/settings-nav.js.map +1 -1
  135. package/src/attachment/AttachmentChipList.tsx +19 -8
  136. package/src/attachment/__tests__/AttachmentChipList.test.tsx +12 -0
  137. package/src/attachment/__tests__/useAttachments.test.ts +293 -0
  138. package/src/attachment/index.ts +1 -0
  139. package/src/attachment/useAttachments.ts +143 -11
  140. package/src/composer/SessionComposer.tsx +23 -14
  141. package/src/conversation/ConversationComposer.tsx +38 -4
  142. package/src/conversation/ConversationTemplatePickerDialog.tsx +441 -0
  143. package/src/conversation/ConversationsWorkbench.tsx +40 -4
  144. package/src/conversation/__tests__/ConversationComposer.test.tsx +56 -1
  145. package/src/conversation/__tests__/ConversationTemplatePickerDialog.test.tsx +224 -0
  146. package/src/conversation/__tests__/useConversationParticipation.test.tsx +45 -4
  147. package/src/conversation/index.ts +4 -0
  148. package/src/conversation/useConversationParticipation.ts +58 -6
  149. package/src/identity-account/AccountPreferencesPanel.tsx +446 -0
  150. package/src/identity-account/__tests__/AccountPreferencesPanel.test.tsx +233 -0
  151. package/src/identity-account/__tests__/useAccountExecutionDefaults.test.tsx +116 -0
  152. package/src/identity-account/__tests__/useMyIdentityAccount.test.tsx +121 -0
  153. package/src/identity-account/index.ts +11 -0
  154. package/src/identity-account/useAccountExecutionDefaults.ts +63 -0
  155. package/src/identity-account/useMyIdentityAccount.ts +75 -0
  156. package/src/identity-account/useUpdateIdentityAccount.ts +73 -0
  157. package/src/index.ts +24 -2
  158. package/src/internal/StandingContextField.tsx +92 -0
  159. package/src/library/InstanceVisibilitySelector.tsx +9 -2
  160. package/src/library/ResourceVisibilityControl.tsx +46 -13
  161. package/src/library/VisibilityOptionRow.tsx +28 -6
  162. package/src/library/VisibilitySelector.tsx +14 -2
  163. package/src/library/__tests__/VisibilitySelector.test.tsx +48 -1
  164. package/src/library/__tests__/visibilityLevels.test.ts +67 -1
  165. package/src/library/index.ts +6 -1
  166. package/src/library/useCanSetPublicVisibility.ts +56 -0
  167. package/src/library/visibilityLevels.ts +65 -14
  168. package/src/mcp-server/__tests__/useMcpServerConnect.asyncLane.test.tsx +117 -0
  169. package/src/mcp-server/useMcpServerConnect.ts +9 -6
  170. package/src/mcp-server/useMcpServerOAuthConnect.ts +7 -2
  171. package/src/oauth-app/CreateOAuthAppForm.tsx +58 -3
  172. package/src/oauth-app/OAuthAppDetailPanel.tsx +84 -3
  173. package/src/organization/OrgPreferencesPanel.tsx +221 -0
  174. package/src/organization/OrgProfilePanel.tsx +5 -4
  175. package/src/organization/__tests__/OrgPreferencesPanel.test.tsx +145 -0
  176. package/src/organization/__tests__/OrgProfilePanel.test.tsx +108 -0
  177. package/src/organization/index.ts +2 -0
  178. package/src/session/NewSessionViewer.tsx +12 -0
  179. package/src/session/__tests__/useNewSessionFlow.test.tsx +151 -0
  180. package/src/session/useNewSessionFlow.ts +76 -9
  181. package/src/settings/AccountPreferencesSection.tsx +25 -0
  182. package/src/settings/OrgPreferencesSection.tsx +35 -0
  183. package/src/settings/__tests__/settings-nav.test.ts +27 -0
  184. package/src/settings/index.ts +2 -0
  185. package/src/settings/settings-nav.ts +18 -0
  186. package/src/workflow/ApprovalFormBuilder.tsx +5 -27
  187. package/styles.css +1 -1
  188. package/workflow/ApprovalFormBuilder.d.ts.map +1 -1
  189. package/workflow/ApprovalFormBuilder.js +8 -5
  190. package/workflow/ApprovalFormBuilder.js.map +1 -1
@@ -0,0 +1,116 @@
1
+ import { describe, it, expect, vi, afterEach } from "vitest";
2
+ import { cleanup, renderHook, waitFor } from "@testing-library/react";
3
+ import type { ReactNode } from "react";
4
+ import { create } from "@bufbuild/protobuf";
5
+ import {
6
+ IdentityAccountSchema,
7
+ type IdentityAccount,
8
+ } from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/api_pb";
9
+ import type { DeploymentMode } from "@stigmer/sdk";
10
+ import { StigmerContext } from "../../context";
11
+ import { DeploymentModeContext } from "../../deployment-mode";
12
+ import { useAccountExecutionDefaults } from "../useAccountExecutionDefaults";
13
+
14
+ function accountWithPreferences(
15
+ preferences: Record<string, string>,
16
+ ): IdentityAccount {
17
+ return create(IdentityAccountSchema, {
18
+ metadata: { id: "ia-1", name: "Ada", slug: "ada", org: "acme" },
19
+ spec: { idpId: "auth0|abc", preferences },
20
+ });
21
+ }
22
+
23
+ function createWrapper(whoAmI: ReturnType<typeof vi.fn>, mode: DeploymentMode = "cloud") {
24
+ const client = { identityAccount: { whoAmI } } as never;
25
+ return function Wrapper({ children }: { children: ReactNode }) {
26
+ return (
27
+ <StigmerContext.Provider value={client}>
28
+ <DeploymentModeContext.Provider value={mode}>
29
+ {children}
30
+ </DeploymentModeContext.Provider>
31
+ </StigmerContext.Provider>
32
+ );
33
+ };
34
+ }
35
+
36
+ afterEach(cleanup);
37
+
38
+ describe("useAccountExecutionDefaults", () => {
39
+ it("returns undefined in local mode without issuing any RPC", async () => {
40
+ const whoAmI = vi.fn();
41
+ const { result } = renderHook(() => useAccountExecutionDefaults(), {
42
+ wrapper: createWrapper(whoAmI, "local"),
43
+ });
44
+
45
+ // Settle any pending effects before asserting the no-RPC invariant.
46
+ await waitFor(() => expect(result.current).toBeUndefined());
47
+ expect(whoAmI).not.toHaveBeenCalled();
48
+ });
49
+
50
+ it("shapes declared defaults for the launcher's accountDefaults prop", async () => {
51
+ const whoAmI = vi.fn(async () =>
52
+ accountWithPreferences({
53
+ defaultHarness: "cursor",
54
+ defaultNativeModel: "claude-sonnet-4.6",
55
+ defaultCursorModel: "composer-2.5",
56
+ }),
57
+ );
58
+ const { result } = renderHook(() => useAccountExecutionDefaults(), {
59
+ wrapper: createWrapper(whoAmI),
60
+ });
61
+
62
+ await waitFor(() =>
63
+ expect(result.current).toEqual({
64
+ harness: "cursor",
65
+ nativeModel: "claude-sonnet-4.6",
66
+ cursorModel: "composer-2.5",
67
+ }),
68
+ );
69
+ });
70
+
71
+ it("returns undefined while loading and when nothing is declared", async () => {
72
+ const whoAmI = vi.fn(async () =>
73
+ accountWithPreferences({ standingContext: "Keep answers terse." }),
74
+ );
75
+ const { result } = renderHook(() => useAccountExecutionDefaults(), {
76
+ wrapper: createWrapper(whoAmI),
77
+ });
78
+
79
+ expect(result.current).toBeUndefined();
80
+ await waitFor(() => expect(whoAmI).toHaveBeenCalled());
81
+ // standing_context alone declares no execution default.
82
+ expect(result.current).toBeUndefined();
83
+ });
84
+
85
+ it("treats an unknown persisted harness value as undeclared", async () => {
86
+ const whoAmI = vi.fn(async () =>
87
+ accountWithPreferences({
88
+ defaultHarness: "devin",
89
+ defaultNativeModel: "claude-sonnet-4.6",
90
+ }),
91
+ );
92
+ const { result } = renderHook(() => useAccountExecutionDefaults(), {
93
+ wrapper: createWrapper(whoAmI),
94
+ });
95
+
96
+ await waitFor(() =>
97
+ expect(result.current).toEqual({
98
+ harness: undefined,
99
+ nativeModel: "claude-sonnet-4.6",
100
+ cursorModel: undefined,
101
+ }),
102
+ );
103
+ });
104
+
105
+ it("returns undefined on a whoAmI failure — degrades to platform defaults", async () => {
106
+ const whoAmI = vi.fn(async () => {
107
+ throw new Error("boom");
108
+ });
109
+ const { result } = renderHook(() => useAccountExecutionDefaults(), {
110
+ wrapper: createWrapper(whoAmI),
111
+ });
112
+
113
+ await waitFor(() => expect(whoAmI).toHaveBeenCalled());
114
+ expect(result.current).toBeUndefined();
115
+ });
116
+ });
@@ -0,0 +1,121 @@
1
+ import { describe, it, expect, vi, afterEach } from "vitest";
2
+ import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react";
3
+ import { create } from "@bufbuild/protobuf";
4
+ import {
5
+ IdentityAccountSchema,
6
+ type IdentityAccount,
7
+ } from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/api_pb";
8
+ import type { IdentityAccountInput } from "@stigmer/sdk";
9
+ import { StigmerContext } from "../../context";
10
+ import { useMyIdentityAccount } from "../useMyIdentityAccount";
11
+ import { useUpdateIdentityAccount } from "../useUpdateIdentityAccount";
12
+
13
+ const ACCOUNT: IdentityAccount = create(IdentityAccountSchema, {
14
+ metadata: { id: "ia-1", name: "Ada", slug: "ada", org: "acme" },
15
+ spec: { preferences: { standingContext: "Keep answers terse." } },
16
+ });
17
+
18
+ /** Renders the data hook and exposes its state via data attributes. */
19
+ function FetchProbe() {
20
+ const { account, isLoading, error, refetch } = useMyIdentityAccount();
21
+ return (
22
+ <button
23
+ type="button"
24
+ data-testid="probe"
25
+ data-loading={String(isLoading)}
26
+ data-error={String(error !== null)}
27
+ data-context={account?.spec?.preferences?.standingContext ?? ""}
28
+ onClick={refetch}
29
+ />
30
+ );
31
+ }
32
+
33
+ function UpdateProbe({ input }: { input: IdentityAccountInput }) {
34
+ const { update, isUpdating, error } = useUpdateIdentityAccount();
35
+ return (
36
+ <button
37
+ type="button"
38
+ data-testid="probe"
39
+ data-updating={String(isUpdating)}
40
+ data-error={String(error !== null)}
41
+ onClick={() => void update(input).catch(() => undefined)}
42
+ />
43
+ );
44
+ }
45
+
46
+ function withClient(client: unknown, ui: React.ReactNode) {
47
+ return render(
48
+ <StigmerContext.Provider value={client as never}>
49
+ {ui}
50
+ </StigmerContext.Provider>,
51
+ );
52
+ }
53
+
54
+ afterEach(cleanup);
55
+
56
+ describe("useMyIdentityAccount", () => {
57
+ it("fetches the caller's account via whoAmI and supports refetch", async () => {
58
+ const whoAmI = vi.fn(async () => ACCOUNT);
59
+ withClient({ identityAccount: { whoAmI } }, <FetchProbe />);
60
+
61
+ await waitFor(() =>
62
+ expect(screen.getByTestId("probe").getAttribute("data-context")).toBe(
63
+ "Keep answers terse.",
64
+ ),
65
+ );
66
+ expect(whoAmI).toHaveBeenCalledTimes(1);
67
+
68
+ fireEvent.click(screen.getByTestId("probe"));
69
+ await waitFor(() => expect(whoAmI).toHaveBeenCalledTimes(2));
70
+ });
71
+
72
+ it("surfaces errors as state", async () => {
73
+ const whoAmI = vi.fn(async () => {
74
+ throw new Error("boom");
75
+ });
76
+ withClient({ identityAccount: { whoAmI } }, <FetchProbe />);
77
+
78
+ await waitFor(() =>
79
+ expect(screen.getByTestId("probe").getAttribute("data-error")).toBe(
80
+ "true",
81
+ ),
82
+ );
83
+ });
84
+ });
85
+
86
+ describe("useUpdateIdentityAccount", () => {
87
+ it("submits the input through identityAccount.update", async () => {
88
+ const update = vi.fn(async (_input: IdentityAccountInput) => ACCOUNT);
89
+ const input: IdentityAccountInput = {
90
+ name: "Ada",
91
+ org: "acme",
92
+ slug: "ada",
93
+ idpId: "auth0|abc",
94
+ preferences: { standingContext: "New" },
95
+ };
96
+ withClient({ identityAccount: { update } }, <UpdateProbe input={input} />);
97
+
98
+ fireEvent.click(screen.getByTestId("probe"));
99
+
100
+ await waitFor(() => expect(update).toHaveBeenCalledTimes(1));
101
+ expect(update.mock.calls[0]![0]).toEqual(input);
102
+ });
103
+
104
+ it("captures update failures in error state", async () => {
105
+ const update = vi.fn(async () => {
106
+ throw new Error("denied");
107
+ });
108
+ withClient(
109
+ { identityAccount: { update } },
110
+ <UpdateProbe input={{ name: "Ada", org: "acme", idpId: "x" }} />,
111
+ );
112
+
113
+ fireEvent.click(screen.getByTestId("probe"));
114
+
115
+ await waitFor(() =>
116
+ expect(screen.getByTestId("probe").getAttribute("data-error")).toBe(
117
+ "true",
118
+ ),
119
+ );
120
+ });
121
+ });
@@ -3,3 +3,14 @@ export {
3
3
  type IdentityAccountGateState,
4
4
  type UseIdentityAccountGateReturn,
5
5
  } from "./useIdentityAccountGate.js";
6
+ export { useMyIdentityAccount } from "./useMyIdentityAccount.js";
7
+ export type {
8
+ UseMyIdentityAccountOptions,
9
+ UseMyIdentityAccountReturn,
10
+ } from "./useMyIdentityAccount.js";
11
+ export { useAccountExecutionDefaults } from "./useAccountExecutionDefaults.js";
12
+ export type { AccountExecutionDefaults } from "./useAccountExecutionDefaults.js";
13
+ export { useUpdateIdentityAccount } from "./useUpdateIdentityAccount.js";
14
+ export type { UseUpdateIdentityAccountReturn } from "./useUpdateIdentityAccount.js";
15
+ export { AccountPreferencesPanel } from "./AccountPreferencesPanel.js";
16
+ export type { AccountPreferencesPanelProps } from "./AccountPreferencesPanel.js";
@@ -0,0 +1,63 @@
1
+ "use client";
2
+
3
+ import { useMemo } from "react";
4
+ import { useMyIdentityAccount } from "./useMyIdentityAccount.js";
5
+ import { useResourceAvailable, ApiResourceKind } from "../deployment-mode.js";
6
+ import type { HarnessOption } from "../models/harness.js";
7
+
8
+ /**
9
+ * The current user's account-level execution defaults, shaped for the
10
+ * session launcher's `accountDefaults` prop.
11
+ */
12
+ export interface AccountExecutionDefaults {
13
+ /** Preferred harness for new sessions, when declared. */
14
+ readonly harness?: HarnessOption;
15
+ /** Preferred model for native-harness sessions, when declared. */
16
+ readonly nativeModel?: string;
17
+ /** Preferred model for cursor-harness sessions, when declared. */
18
+ readonly cursorModel?: string;
19
+ }
20
+
21
+ /**
22
+ * Data hook that reads the current user's execution defaults
23
+ * (`IdentityAccountSpec.preferences.default_*`) for seeding new-session
24
+ * composers.
25
+ *
26
+ * Returns `undefined` in local mode (no IdentityAccount), while loading,
27
+ * on error, and when no default is declared — every one of those cases
28
+ * degrades to the platform's existing defaults, so consumers wire the
29
+ * result straight through:
30
+ *
31
+ * @example
32
+ * ```tsx
33
+ * <NewSessionViewer org={org} accountDefaults={useAccountExecutionDefaults()} />
34
+ * ```
35
+ *
36
+ * The defaults are a SEED, not an override: an explicit device-local pick
37
+ * outranks them (the layered precedence in `useNewSessionFlow`), and the
38
+ * chosen values ride the execution spec explicitly. Rides
39
+ * `useMyIdentityAccount`'s cross-mount cache, so the seed is available
40
+ * synchronously on every visit after the first.
41
+ */
42
+ export function useAccountExecutionDefaults(): AccountExecutionDefaults | undefined {
43
+ const available = useResourceAvailable(ApiResourceKind.identity_account);
44
+ const { account } = useMyIdentityAccount({ enabled: available });
45
+
46
+ return useMemo(() => {
47
+ const prefs = account?.spec?.preferences;
48
+ if (!prefs) return undefined;
49
+
50
+ // The proto validates default_harness to the shipped set, but a client
51
+ // must not trust persisted data it did not write — unknown values are
52
+ // treated as undeclared.
53
+ const harness =
54
+ prefs.defaultHarness === "native" || prefs.defaultHarness === "cursor"
55
+ ? (prefs.defaultHarness satisfies HarnessOption)
56
+ : undefined;
57
+ const nativeModel = prefs.defaultNativeModel || undefined;
58
+ const cursorModel = prefs.defaultCursorModel || undefined;
59
+
60
+ if (!harness && !nativeModel && !cursorModel) return undefined;
61
+ return { harness, nativeModel, cursorModel };
62
+ }, [account]);
63
+ }
@@ -0,0 +1,75 @@
1
+ "use client";
2
+
3
+ import type { IdentityAccount } from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/api_pb";
4
+ import { useStigmer } from "../hooks.js";
5
+ import { useFetch } from "../internal/useFetch.js";
6
+
7
+ /** Options for {@link useMyIdentityAccount}. */
8
+ export interface UseMyIdentityAccountOptions {
9
+ /**
10
+ * When `false`, the hook is idle: no RPC is issued and `account` stays
11
+ * `null`. Lets always-called hooks (React's rules) skip the doomed
12
+ * whoAmI against a local server — pass
13
+ * `useResourceAvailable(ApiResourceKind.identity_account)` here.
14
+ *
15
+ * @default true
16
+ */
17
+ readonly enabled?: boolean;
18
+ }
19
+
20
+ /** Return value of {@link useMyIdentityAccount}. */
21
+ export interface UseMyIdentityAccountReturn {
22
+ /** The authenticated user's identity account, or `null` while loading / on error. */
23
+ readonly account: IdentityAccount | null;
24
+ /** `true` while the initial fetch is in flight. */
25
+ readonly isLoading: boolean;
26
+ /** `true` while a background refetch is in flight and stale data is shown. */
27
+ readonly isRefetching: boolean;
28
+ /** Error from the last failed request, or `null` when healthy. */
29
+ readonly error: Error | null;
30
+ /** Re-fetch the account from the server (e.g. after an update). */
31
+ readonly refetch: () => void;
32
+ }
33
+
34
+ /**
35
+ * Data hook that fetches the current authenticated user's full
36
+ * {@link IdentityAccount} via `identityAccount.whoAmI()`.
37
+ *
38
+ * Unlike {@link useWhoAmI} (a cached identity lookup for "is this me?"
39
+ * checks), this hook is a refetchable data source for editors of the
40
+ * caller's own account — after a mutation, call `refetch()` to re-sync.
41
+ * `whoAmI()` returns the complete resource, so no follow-up `get()` is
42
+ * needed.
43
+ *
44
+ * Cloud-only by nature: the OSS local server does not implement
45
+ * IdentityAccount. Gate consumers with
46
+ * `useResourceAvailable(ApiResourceKind.identity_account)` — either by
47
+ * conditional mounting or via {@link UseMyIdentityAccountOptions.enabled}.
48
+ *
49
+ * Cached across mounts under a {@link FetchCacheProvider} (DD-014): a
50
+ * revisit renders the previous result immediately and refetches in the
51
+ * background, so consumers that seed UI from the account (e.g. composer
52
+ * defaults) settle synchronously after the first visit.
53
+ *
54
+ * @example
55
+ * ```tsx
56
+ * const { account, refetch } = useMyIdentityAccount();
57
+ * const standingContext =
58
+ * account?.spec?.preferences?.standingContext ?? "";
59
+ * ```
60
+ */
61
+ export function useMyIdentityAccount(
62
+ options?: UseMyIdentityAccountOptions,
63
+ ): UseMyIdentityAccountReturn {
64
+ const stigmer = useStigmer();
65
+ const enabled = options?.enabled ?? true;
66
+
67
+ const { data: account, isLoading, isRefetching, error, refetch } = useFetch(
68
+ enabled ? () => stigmer.identityAccount.whoAmI() : null,
69
+ [stigmer, enabled],
70
+ null as IdentityAccount | null,
71
+ { cacheKey: enabled ? "identity-account:me" : undefined },
72
+ );
73
+
74
+ return { account, isLoading, isRefetching, error, refetch };
75
+ }
@@ -0,0 +1,73 @@
1
+ "use client";
2
+
3
+ import { useCallback, useState } from "react";
4
+ import type { IdentityAccountInput } from "@stigmer/sdk";
5
+ import type { IdentityAccount } from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/api_pb";
6
+ import { useStigmer } from "../hooks.js";
7
+ import { toError } from "../internal/toError.js";
8
+
9
+ /** Return value of {@link useUpdateIdentityAccount}. */
10
+ export interface UseUpdateIdentityAccountReturn {
11
+ /** Submit an {@link IdentityAccountInput} to update an identity account. Resolves with the updated resource. */
12
+ readonly update: (input: IdentityAccountInput) => Promise<IdentityAccount>;
13
+ /** `true` while the update request is in flight. */
14
+ readonly isUpdating: boolean;
15
+ /** Error from the last failed update, or `null` when healthy. */
16
+ readonly error: Error | null;
17
+ /** Reset `error` to `null`. */
18
+ readonly clearError: () => void;
19
+ }
20
+
21
+ /**
22
+ * Mutation hook that wraps `identityAccount.update()` with loading and
23
+ * error state.
24
+ *
25
+ * The update RPC is a full-spec replacement addressed by `org` + `slug`
26
+ * (the generated input carries no id). Build the input from the loaded
27
+ * resource with `toIdentityAccountUpdateInput` from `@stigmer/sdk` and
28
+ * override only the fields being edited — sending a partial input wipes
29
+ * the unsent spec fields. The same rule applies INSIDE nested messages:
30
+ * override `preferences` by spreading the mapper's complete value first,
31
+ * or the untouched preference fields wipe.
32
+ *
33
+ * Self-service: a user can always update their own account (`can_edit`
34
+ * via the self-ownership relationship written at account creation).
35
+ *
36
+ * @example
37
+ * ```tsx
38
+ * const { update, isUpdating, error } = useUpdateIdentityAccount();
39
+ *
40
+ * const mapped = toIdentityAccountUpdateInput(account);
41
+ * await update({
42
+ * ...mapped,
43
+ * preferences: { ...mapped.preferences, standingContext: "Keep answers terse." },
44
+ * });
45
+ * refetch(); // re-sync the editor
46
+ * ```
47
+ */
48
+ export function useUpdateIdentityAccount(): UseUpdateIdentityAccountReturn {
49
+ const stigmer = useStigmer();
50
+ const [isUpdating, setIsUpdating] = useState(false);
51
+ const [error, setError] = useState<Error | null>(null);
52
+
53
+ const clearError = useCallback(() => setError(null), []);
54
+
55
+ const update = useCallback(
56
+ async (input: IdentityAccountInput): Promise<IdentityAccount> => {
57
+ setIsUpdating(true);
58
+ setError(null);
59
+
60
+ try {
61
+ return await stigmer.identityAccount.update(input);
62
+ } catch (err) {
63
+ setError(toError(err));
64
+ throw err;
65
+ } finally {
66
+ setIsUpdating(false);
67
+ }
68
+ },
69
+ [stigmer],
70
+ );
71
+
72
+ return { update, isUpdating, error, clearError };
73
+ }
package/src/index.ts CHANGED
@@ -502,6 +502,7 @@ export {
502
502
  visionPreflightMessage,
503
503
  } from "./attachment/index.js";
504
504
  export type {
505
+ AddFilesOptions,
505
506
  AttachmentPhase,
506
507
  AttachmentEntry,
507
508
  AssessVisionPreflightOptions,
@@ -833,13 +834,23 @@ export type {
833
834
  ToolCredentialsReadiness,
834
835
  } from "./environment/index.js";
835
836
 
836
- // Identity Account — gate hook for ensuring the caller's identity account exists before app render
837
+ // Identity Account — gate hook, self-account data/mutation hooks, the
838
+ // account preferences editor, and the composer-seeding execution defaults
837
839
  export {
838
840
  useIdentityAccountGate,
841
+ useMyIdentityAccount,
842
+ useAccountExecutionDefaults,
843
+ useUpdateIdentityAccount,
844
+ AccountPreferencesPanel,
839
845
  } from "./identity-account/index.js";
840
846
  export type {
841
847
  IdentityAccountGateState,
842
848
  UseIdentityAccountGateReturn,
849
+ UseMyIdentityAccountOptions,
850
+ UseMyIdentityAccountReturn,
851
+ AccountExecutionDefaults,
852
+ UseUpdateIdentityAccountReturn,
853
+ AccountPreferencesPanelProps,
843
854
  } from "./identity-account/index.js";
844
855
 
845
856
  // IAM Policy — data hooks, behavior hooks, headless hook, and styled components for access management
@@ -921,6 +932,7 @@ export {
921
932
  useUpdateOrganization,
922
933
  CreateOrganizationForm,
923
934
  OrgProfilePanel,
935
+ OrgPreferencesPanel,
924
936
  OrgSwitcher,
925
937
  } from "./organization/index.js";
926
938
  export type {
@@ -933,6 +945,7 @@ export type {
933
945
  UseUpdateOrganizationReturn,
934
946
  CreateOrganizationFormProps,
935
947
  OrgProfilePanelProps,
948
+ OrgPreferencesPanelProps,
936
949
  OrgSwitcherProps,
937
950
  } from "./organization/index.js";
938
951
 
@@ -1065,6 +1078,8 @@ export type { SettingsNavItem, SettingsNavGroup } from "./settings/index.js";
1065
1078
  export { ApiKeysSection } from "./settings/index.js";
1066
1079
  export { MembersSection } from "./settings/index.js";
1067
1080
  export { OrgProfileSection } from "./settings/index.js";
1081
+ export { OrgPreferencesSection } from "./settings/index.js";
1082
+ export { AccountPreferencesSection } from "./settings/index.js";
1068
1083
  export { EnvironmentsSection } from "./settings/index.js";
1069
1084
  export { InvitationsSection } from "./settings/index.js";
1070
1085
  export { IdentityProvidersSection } from "./settings/index.js";
@@ -1335,6 +1350,7 @@ export {
1335
1350
  ConversationControlBanner,
1336
1351
  ConversationAttentionBanner,
1337
1352
  ConversationComposer,
1353
+ ConversationTemplatePickerDialog,
1338
1354
  useConversation,
1339
1355
  useConversationList,
1340
1356
  useConversationMediaUrl,
@@ -1364,6 +1380,8 @@ export type {
1364
1380
  ConversationControlBannerProps,
1365
1381
  ConversationAttentionBannerProps,
1366
1382
  ConversationComposerProps,
1383
+ ConversationTemplatePickerDialogProps,
1384
+ ConversationReplyPayload,
1367
1385
  UseConversationOptions,
1368
1386
  UseConversationReturn,
1369
1387
  UseConversationListOptions,
@@ -1422,9 +1440,11 @@ export {
1422
1440
  VisibilitySelector,
1423
1441
  VisibilityBadge,
1424
1442
  blueprintVisibilityLevels,
1425
- INSTANCE_VISIBILITY_LEVELS,
1443
+ instanceVisibilityLevels,
1444
+ PUBLIC_LOCKED_REASON,
1426
1445
  visibilityLabel,
1427
1446
  useUpdateVisibility,
1447
+ useCanSetPublicVisibility,
1428
1448
  } from "./library/index.js";
1429
1449
  export type {
1430
1450
  ScopeToggleProps,
@@ -1444,8 +1464,10 @@ export type {
1444
1464
  VisibilityBadgeProps,
1445
1465
  VisibilityLevelOption,
1446
1466
  BlueprintVisibilityLevelsContext,
1467
+ InstanceVisibilityLevelsContext,
1447
1468
  VisibilityResourceKind,
1448
1469
  UseUpdateVisibilityReturn,
1470
+ UseCanSetPublicVisibilityReturn,
1449
1471
  } from "./library/index.js";
1450
1472
 
1451
1473
  // Manifest — kind-agnostic YAML edit/apply (editor, hooks, dialogs)
@@ -0,0 +1,92 @@
1
+ "use client";
2
+
3
+ import type { ReactNode } from "react";
4
+ import { cn } from "@stigmer/theme";
5
+
6
+ /**
7
+ * Both `OrganizationPreferences.standing_context` and
8
+ * `IdentityAccountPreferences.standing_context` pin `max_len = 2000`
9
+ * (buf.validate, spec.proto in each package).
10
+ */
11
+ export const STANDING_CONTEXT_MAX_LEN = 2000;
12
+ /** Show the remaining-characters counter once within this margin. */
13
+ const COUNTER_THRESHOLD = 200;
14
+
15
+ /** Props for {@link StandingContextField}. */
16
+ export interface StandingContextFieldProps {
17
+ /** DOM id for the textarea (label association). */
18
+ readonly id: string;
19
+ /** Current field value. */
20
+ readonly value: string;
21
+ /** Fired with the new value on every edit. */
22
+ readonly onChange: (value: string) => void;
23
+ /** Disables editing while a save is in flight. */
24
+ readonly disabled?: boolean;
25
+ /**
26
+ * Renders the field as read-only (still selectable/copyable) for
27
+ * viewers without edit permission.
28
+ */
29
+ readonly readOnly?: boolean;
30
+ /** Example text shown when the field is empty. */
31
+ readonly placeholder?: string;
32
+ /** Helper line rendered under the textarea. */
33
+ readonly helperText?: ReactNode;
34
+ }
35
+
36
+ /**
37
+ * Shared textarea for declared-preference standing context — the one
38
+ * free-text field both the organization and account preference editors
39
+ * render. Owns the proto length cap and the remaining-characters
40
+ * counter; the parent panel owns label, load/save, and dirty state.
41
+ *
42
+ * Internal to `@stigmer/react` — the public surface is the panels.
43
+ */
44
+ export function StandingContextField({
45
+ id,
46
+ value,
47
+ onChange,
48
+ disabled,
49
+ readOnly,
50
+ placeholder,
51
+ helperText,
52
+ }: StandingContextFieldProps) {
53
+ const remaining = STANDING_CONTEXT_MAX_LEN - value.length;
54
+
55
+ return (
56
+ <div className="stg:space-y-1">
57
+ <label
58
+ htmlFor={id}
59
+ className="stg:text-xs stg:font-medium stg:text-foreground"
60
+ >
61
+ Standing context
62
+ </label>
63
+ <textarea
64
+ id={id}
65
+ value={value}
66
+ onChange={(e) => onChange(e.target.value)}
67
+ maxLength={STANDING_CONTEXT_MAX_LEN}
68
+ rows={6}
69
+ disabled={disabled}
70
+ readOnly={readOnly}
71
+ placeholder={readOnly ? undefined : placeholder}
72
+ className={cn(
73
+ "stg:w-full stg:resize-y stg:rounded-md stg:border stg:border-input stg:bg-background stg:px-2.5 stg:py-1.5 stg:text-xs stg:text-foreground",
74
+ "stg:placeholder:text-muted-foreground",
75
+ "stg:focus-visible:outline-none stg:focus-visible:ring-1 stg:focus-visible:ring-ring",
76
+ "stg:disabled:pointer-events-none stg:disabled:opacity-50",
77
+ readOnly && "stg:bg-muted-subtle stg:text-muted-foreground",
78
+ )}
79
+ />
80
+ {helperText && (
81
+ <p className="stg:text-[0.65rem] stg:text-muted-foreground">
82
+ {helperText}
83
+ </p>
84
+ )}
85
+ {!readOnly && remaining <= COUNTER_THRESHOLD && (
86
+ <p className="stg:text-[0.65rem] stg:text-muted-foreground">
87
+ {remaining} characters left
88
+ </p>
89
+ )}
90
+ </div>
91
+ );
92
+ }
@@ -5,7 +5,8 @@ import {
5
5
  VisibilitySelector,
6
6
  type VisibilitySelectorMode,
7
7
  } from "./VisibilitySelector.js";
8
- import { INSTANCE_VISIBILITY_LEVELS } from "./visibilityLevels.js";
8
+ import { useCanSetPublicVisibility } from "./useCanSetPublicVisibility.js";
9
+ import { instanceVisibilityLevels } from "./visibilityLevels.js";
9
10
 
10
11
  /** Props for {@link InstanceVisibilitySelector}. */
11
12
  export interface InstanceVisibilitySelectorProps {
@@ -69,10 +70,16 @@ export function InstanceVisibilitySelector({
69
70
  disabled = false,
70
71
  className,
71
72
  }: InstanceVisibilitySelectorProps) {
73
+ // Public is operator-granted in the cloud edition; the hook resolves the
74
+ // caller's grant here so every consumer (create dialogs, list rows) gets
75
+ // the locked Public row without wiring anything.
76
+ const canSetPublic = useCanSetPublicVisibility();
72
77
  return (
73
78
  <VisibilitySelector
74
79
  visibility={visibility}
75
- options={INSTANCE_VISIBILITY_LEVELS}
80
+ options={instanceVisibilityLevels({
81
+ canSetPublicVisibility: canSetPublic.allowed,
82
+ })}
76
83
  onVisibilityChange={onVisibilityChange}
77
84
  mode={mode}
78
85
  isPending={isPending}