@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
@@ -4,7 +4,7 @@ import { useCallback, useState } from "react";
4
4
  import { create } from "@bufbuild/protobuf";
5
5
  import type { McpServer } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
6
6
  import { ConnectInputSchema } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/io_pb";
7
- import type { EnvVarInput } from "@stigmer/sdk";
7
+ import { connectAndWait, type EnvVarInput } from "@stigmer/sdk";
8
8
  import { useStigmer } from "../hooks.js";
9
9
  import { resolveDeclaredSystemEnvVars } from "../environment/systemEnvVars.js";
10
10
  import { toError } from "../internal/toError.js";
@@ -15,9 +15,12 @@ export interface UseMcpServerConnectReturn {
15
15
  * Connect to an MCP server: discover its tools, resource templates,
16
16
  * and classify tool approval policies via a lightweight LLM call.
17
17
  *
18
- * Calls the `connect` RPC which starts a Temporal workflow on the
19
- * runner. The RPC blocks until the workflow completes
20
- * (typically 5-15 seconds, ~30s timeout).
18
+ * Uses the async connect lane (`startConnect` + polling via the SDK's
19
+ * `connectAndWait`, stigmer/stigmer#425): the promise still resolves
20
+ * when the operation settles — legitimately tens of seconds to minutes
21
+ * for heavy servers — but no RPC stays open that long, so the wait
22
+ * survives browser transport limits. Backends without the lane fall
23
+ * back to the blocking `connect` RPC transparently.
21
24
  *
22
25
  * Platform system env vars (`STIGMER_SERVER_ADDRESS`,
23
26
  * `STIGMER_API_KEY`) are injected only when the MCP server
@@ -45,7 +48,7 @@ export interface UseMcpServerConnectReturn {
45
48
  runtimeEnv?: Record<string, EnvVarInput>,
46
49
  declaredEnvKeys?: readonly string[],
47
50
  ) => Promise<McpServer>;
48
- /** `true` while the connect RPC is in flight. */
51
+ /** `true` while the connect operation is in flight (start through settle). */
49
52
  readonly isConnecting: boolean;
50
53
  /** Error from the most recent failed connect, or `null`. */
51
54
  readonly error: Error | null;
@@ -129,7 +132,7 @@ export function useMcpServerConnect(): UseMcpServerConnectReturn {
129
132
  : {}),
130
133
  });
131
134
 
132
- return await stigmer.mcpServer.connect(input);
135
+ return await connectAndWait(stigmer.mcpServer, input);
133
136
  } catch (err) {
134
137
  const wrapped = toError(err);
135
138
  setError(wrapped);
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { useCallback, useEffect, useRef, useState } from "react";
4
4
  import { create } from "@bufbuild/protobuf";
5
- import { getUserMessage } from "@stigmer/sdk";
5
+ import { connectAndWait, getUserMessage } from "@stigmer/sdk";
6
6
  import type { McpServer } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
7
7
  import {
8
8
  InitiateOAuthConnectInputSchema,
@@ -248,7 +248,12 @@ export function useMcpServerOAuthConnect(): UseMcpServerOAuthConnectReturn {
248
248
  : {}),
249
249
  });
250
250
 
251
- const server = await stigmer.mcpServer.connect(input);
251
+ // Async connect lane (stigmer/stigmer#425): startConnect + poll via
252
+ // the SDK's shared protocol, so the discovery wait can outlive the
253
+ // browser's ~300s no-bytes unary limit that used to fail this phase
254
+ // spuriously. Old backends without the lane fall back to the
255
+ // blocking RPC inside connectAndWait.
256
+ const server = await connectAndWait(stigmer.mcpServer, input);
252
257
 
253
258
  advancePhase("done");
254
259
  return server;
@@ -4,7 +4,10 @@ import { type FormEvent, useCallback, useId, useState } from "react";
4
4
  import { cn } from "@stigmer/theme";
5
5
  import { getUserMessage } from "@stigmer/sdk";
6
6
  import type { OAuthApp } from "@stigmer/protos/ai/stigmer/iam/oauthapp/v1/api_pb";
7
- import { VendorApprovalStatus } from "@stigmer/protos/ai/stigmer/iam/oauthapp/v1/spec_pb";
7
+ import {
8
+ TokenEndpointAuthMethod,
9
+ VendorApprovalStatus,
10
+ } from "@stigmer/protos/ai/stigmer/iam/oauthapp/v1/spec_pb";
8
11
  import { useCreateOAuthApp } from "./useCreateOAuthApp.js";
9
12
  import { SpinnerIcon } from "../internal/SpinnerIcon.js";
10
13
 
@@ -30,8 +33,8 @@ export interface CreateOAuthAppFormProps {
30
33
  * Collects the required OAuth configuration: **name**, **provider**,
31
34
  * **client ID**, **client secret**, **authorization URL**, and
32
35
  * **token URL**. An expandable "Advanced" section provides optional
33
- * fields for scopes, userinfo URL, scope parameter name, and vendor
34
- * approval settings.
36
+ * fields for scopes, userinfo URL, scope parameter name, token endpoint
37
+ * auth method, and vendor approval settings.
35
38
  *
36
39
  * This is a pure presentational component with no dialog wrapper
37
40
  * (headless-first). The parent is responsible for rendering it inside
@@ -71,6 +74,9 @@ export function CreateOAuthAppForm({
71
74
  const [scopes, setScopes] = useState("");
72
75
  const [userinfoUrl, setUserinfoUrl] = useState("");
73
76
  const [scopeParameterName, setScopeParameterName] = useState("");
77
+ const [tokenEndpointAuthMethod, setTokenEndpointAuthMethod] = useState<
78
+ "unspecified" | "client_secret_basic" | "client_secret_post"
79
+ >("unspecified");
74
80
  const [vendorApprovalStatus, setVendorApprovalStatus] = useState<
75
81
  "unspecified" | "pending" | "approved" | "rejected"
76
82
  >("unspecified");
@@ -117,6 +123,10 @@ export function CreateOAuthAppForm({
117
123
  ...(scopeParameterName.trim() && {
118
124
  scopeParameterName: scopeParameterName.trim(),
119
125
  }),
126
+ ...(tokenEndpointAuthMethod !== "unspecified" && {
127
+ tokenEndpointAuthMethod:
128
+ TOKEN_AUTH_METHOD_MAP[tokenEndpointAuthMethod],
129
+ }),
120
130
  ...(vendorApprovalStatus !== "unspecified" && {
121
131
  vendorApprovalStatus: APPROVAL_STATUS_MAP[vendorApprovalStatus],
122
132
  }),
@@ -141,6 +151,7 @@ export function CreateOAuthAppForm({
141
151
  scopes,
142
152
  userinfoUrl,
143
153
  scopeParameterName,
154
+ tokenEndpointAuthMethod,
144
155
  vendorApprovalStatus,
145
156
  vendorApprovalDocsUrl,
146
157
  create,
@@ -259,6 +270,45 @@ export function CreateOAuthAppForm({
259
270
  disabled={isCreating}
260
271
  />
261
272
 
273
+ <div className="stg:space-y-1">
274
+ <label
275
+ htmlFor={`${baseId}-token-auth-method`}
276
+ className="stg:text-xs stg:font-medium stg:text-foreground"
277
+ >
278
+ Token endpoint auth method
279
+ </label>
280
+ <select
281
+ id={`${baseId}-token-auth-method`}
282
+ value={tokenEndpointAuthMethod}
283
+ onChange={(e) =>
284
+ setTokenEndpointAuthMethod(
285
+ e.target.value as typeof tokenEndpointAuthMethod,
286
+ )
287
+ }
288
+ disabled={isCreating}
289
+ className={cn(
290
+ "stg:w-full 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",
291
+ "stg:focus-visible:outline-none stg:focus-visible:ring-1 stg:focus-visible:ring-ring",
292
+ "stg:disabled:pointer-events-none stg:disabled:opacity-50",
293
+ )}
294
+ >
295
+ <option value="unspecified">
296
+ Unspecified (treated as HTTP Basic)
297
+ </option>
298
+ <option value="client_secret_basic">
299
+ client_secret_basic (HTTP Basic header)
300
+ </option>
301
+ <option value="client_secret_post">
302
+ client_secret_post (form body)
303
+ </option>
304
+ </select>
305
+ <p className="stg:text-[0.65rem] stg:text-muted-foreground">
306
+ How the client secret is sent to the vendor&apos;s token
307
+ endpoint. Most vendors accept HTTP Basic; some (e.g.
308
+ HubSpot) require client_secret_post.
309
+ </p>
310
+ </div>
311
+
262
312
  <div className="stg:space-y-1">
263
313
  <label
264
314
  htmlFor={`${baseId}-approval-status`}
@@ -354,6 +404,11 @@ const APPROVAL_STATUS_MAP = {
354
404
  rejected: VendorApprovalStatus.REJECTED,
355
405
  } as const;
356
406
 
407
+ const TOKEN_AUTH_METHOD_MAP = {
408
+ client_secret_basic: TokenEndpointAuthMethod.CLIENT_SECRET_BASIC,
409
+ client_secret_post: TokenEndpointAuthMethod.CLIENT_SECRET_POST,
410
+ } as const;
411
+
357
412
  // ---------------------------------------------------------------------------
358
413
  // FormField (internal)
359
414
  // ---------------------------------------------------------------------------
@@ -4,7 +4,10 @@ import { type FormEvent, useCallback, useId, useState } from "react";
4
4
  import { cn } from "@stigmer/theme";
5
5
  import { getUserMessage } from "@stigmer/sdk";
6
6
  import type { OAuthApp } from "@stigmer/protos/ai/stigmer/iam/oauthapp/v1/api_pb";
7
- import { VendorApprovalStatus } from "@stigmer/protos/ai/stigmer/iam/oauthapp/v1/spec_pb";
7
+ import {
8
+ TokenEndpointAuthMethod,
9
+ VendorApprovalStatus,
10
+ } from "@stigmer/protos/ai/stigmer/iam/oauthapp/v1/spec_pb";
8
11
  import { timestampDate, type Timestamp } from "@bufbuild/protobuf/wkt";
9
12
  import { useUpdateOAuthApp } from "./useUpdateOAuthApp.js";
10
13
  import { useDeleteOAuthApp } from "./useDeleteOAuthApp.js";
@@ -87,6 +90,9 @@ export function OAuthAppDetailPanel({
87
90
  const [scopeParameterName, setScopeParameterName] = useState(
88
91
  spec?.scopeParameterName ?? "",
89
92
  );
93
+ const [tokenEndpointAuthMethod, setTokenEndpointAuthMethod] = useState(
94
+ tokenAuthMethodToKey(spec?.tokenEndpointAuthMethod),
95
+ );
90
96
  const [vendorApprovalStatus, setVendorApprovalStatus] = useState(
91
97
  approvalStatusToKey(spec?.vendorApprovalStatus),
92
98
  );
@@ -103,6 +109,7 @@ export function OAuthAppDetailPanel({
103
109
  setScopes(spec?.scopes.join(", ") ?? "");
104
110
  setUserinfoUrl(spec?.userinfoUrl ?? "");
105
111
  setScopeParameterName(spec?.scopeParameterName ?? "");
112
+ setTokenEndpointAuthMethod(tokenAuthMethodToKey(spec?.tokenEndpointAuthMethod));
106
113
  setVendorApprovalStatus(approvalStatusToKey(spec?.vendorApprovalStatus));
107
114
  setVendorApprovalDocsUrl(spec?.vendorApprovalDocsUrl ?? "");
108
115
  clearUpdateError();
@@ -139,6 +146,10 @@ export function OAuthAppDetailPanel({
139
146
  ...(scopeParameterName.trim() && {
140
147
  scopeParameterName: scopeParameterName.trim(),
141
148
  }),
149
+ ...(tokenEndpointAuthMethod !== "unspecified" && {
150
+ tokenEndpointAuthMethod:
151
+ TOKEN_AUTH_METHOD_MAP[tokenEndpointAuthMethod],
152
+ }),
142
153
  ...(vendorApprovalStatus !== "unspecified" && {
143
154
  vendorApprovalStatus: APPROVAL_STATUS_MAP[vendorApprovalStatus],
144
155
  }),
@@ -154,8 +165,9 @@ export function OAuthAppDetailPanel({
154
165
  },
155
166
  [
156
167
  meta, provider, clientId, clientSecret, authorizationUrl, tokenUrl,
157
- scopes, userinfoUrl, scopeParameterName, vendorApprovalStatus,
158
- vendorApprovalDocsUrl, update, clearUpdateError, onUpdated,
168
+ scopes, userinfoUrl, scopeParameterName, tokenEndpointAuthMethod,
169
+ vendorApprovalStatus, vendorApprovalDocsUrl, update, clearUpdateError,
170
+ onUpdated,
159
171
  ],
160
172
  );
161
173
 
@@ -372,6 +384,40 @@ export function OAuthAppDetailPanel({
372
384
  disabled={isUpdating}
373
385
  />
374
386
 
387
+ <div className="stg:space-y-1">
388
+ <label
389
+ htmlFor={`${baseId}-token-auth-method`}
390
+ className="stg:text-xs stg:font-medium stg:text-foreground"
391
+ >
392
+ Token endpoint auth method
393
+ </label>
394
+ <select
395
+ id={`${baseId}-token-auth-method`}
396
+ value={tokenEndpointAuthMethod}
397
+ onChange={(e) =>
398
+ setTokenEndpointAuthMethod(
399
+ e.target.value as typeof tokenEndpointAuthMethod,
400
+ )
401
+ }
402
+ disabled={isUpdating}
403
+ className={cn(
404
+ "stg:w-full 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",
405
+ "stg:focus-visible:outline-none stg:focus-visible:ring-1 stg:focus-visible:ring-ring",
406
+ "stg:disabled:pointer-events-none stg:disabled:opacity-50",
407
+ )}
408
+ >
409
+ <option value="unspecified">
410
+ Unspecified (treated as HTTP Basic)
411
+ </option>
412
+ <option value="client_secret_basic">
413
+ client_secret_basic (HTTP Basic header)
414
+ </option>
415
+ <option value="client_secret_post">
416
+ client_secret_post (form body)
417
+ </option>
418
+ </select>
419
+ </div>
420
+
375
421
  <div className="stg:space-y-1">
376
422
  <label
377
423
  htmlFor={`${baseId}-approval-status`}
@@ -481,6 +527,29 @@ function approvalStatusToKey(
481
527
  }
482
528
  }
483
529
 
530
+ const TOKEN_AUTH_METHOD_MAP = {
531
+ client_secret_basic: TokenEndpointAuthMethod.CLIENT_SECRET_BASIC,
532
+ client_secret_post: TokenEndpointAuthMethod.CLIENT_SECRET_POST,
533
+ } as const;
534
+
535
+ const TOKEN_AUTH_METHOD_LABELS: Record<number, string> = {
536
+ [TokenEndpointAuthMethod.CLIENT_SECRET_BASIC]: "client_secret_basic",
537
+ [TokenEndpointAuthMethod.CLIENT_SECRET_POST]: "client_secret_post",
538
+ };
539
+
540
+ function tokenAuthMethodToKey(
541
+ method?: TokenEndpointAuthMethod,
542
+ ): "unspecified" | "client_secret_basic" | "client_secret_post" {
543
+ switch (method) {
544
+ case TokenEndpointAuthMethod.CLIENT_SECRET_BASIC:
545
+ return "client_secret_basic";
546
+ case TokenEndpointAuthMethod.CLIENT_SECRET_POST:
547
+ return "client_secret_post";
548
+ default:
549
+ return "unspecified";
550
+ }
551
+ }
552
+
484
553
  // ---------------------------------------------------------------------------
485
554
  // View mode
486
555
  // ---------------------------------------------------------------------------
@@ -514,6 +583,18 @@ function ViewMode({
514
583
  mono
515
584
  />
516
585
  )}
586
+ {spec?.tokenEndpointAuthMethod !== undefined &&
587
+ spec.tokenEndpointAuthMethod !==
588
+ TokenEndpointAuthMethod.UNSPECIFIED && (
589
+ <Field
590
+ label="Token endpoint auth method"
591
+ value={
592
+ TOKEN_AUTH_METHOD_LABELS[spec.tokenEndpointAuthMethod] ??
593
+ "Unknown"
594
+ }
595
+ mono
596
+ />
597
+ )}
517
598
  {spec?.vendorApprovalStatus !== undefined &&
518
599
  spec.vendorApprovalStatus !== VendorApprovalStatus.UNSPECIFIED && (
519
600
  <Field
@@ -0,0 +1,221 @@
1
+ "use client";
2
+
3
+ import { type FormEvent, useCallback, useEffect, useId, useMemo, useState } from "react";
4
+ import { cn } from "@stigmer/theme";
5
+ import { getUserMessage, toOrganizationUpdateInput } from "@stigmer/sdk";
6
+ import type { Organization } from "@stigmer/protos/ai/stigmer/tenancy/organization/v1/api_pb";
7
+ import { useOrganization } from "./useOrganization.js";
8
+ import { useUpdateOrganization } from "./useUpdateOrganization.js";
9
+ import { useCheckPermission } from "../iam-policy/useCheckPermission.js";
10
+ import { StandingContextField } from "../internal/StandingContextField.js";
11
+ import { SpinnerIcon } from "../internal/SpinnerIcon.js";
12
+
13
+ /** Props for {@link OrgPreferencesPanel}. */
14
+ export interface OrgPreferencesPanelProps {
15
+ /** The ID of the organization whose preferences to display and edit. */
16
+ readonly orgId: string;
17
+ /** Fired with the updated resource after a successful save. */
18
+ readonly onUpdated?: (org: Organization) => void;
19
+ /** Additional CSS class names for the root container. */
20
+ readonly className?: string;
21
+ }
22
+
23
+ /**
24
+ * Self-contained editor for an {@link Organization}'s declared
25
+ * preferences (`spec.preferences.standing_context`).
26
+ *
27
+ * The declared text is snapshotted into every eligible agent execution
28
+ * run by the organization's members (first-party human operators only)
29
+ * and delivered to the agent as background context. Editing requires
30
+ * `can_edit` on the organization; viewers without it get a read-only
31
+ * rendering. On save, calls `organization.update()` with the complete
32
+ * mapped input (full-spec-replace safety) and fires `onUpdated`.
33
+ *
34
+ * All visual properties flow through `--stgm-*` design tokens. Zero
35
+ * dependencies on Console routing, auth context, or layout — platform
36
+ * builders can embed it directly:
37
+ *
38
+ * @example
39
+ * ```tsx
40
+ * <OrgPreferencesPanel orgId="org-id-123" />
41
+ * ```
42
+ */
43
+ export function OrgPreferencesPanel({
44
+ orgId,
45
+ onUpdated,
46
+ className,
47
+ }: OrgPreferencesPanelProps) {
48
+ const baseId = useId();
49
+ const {
50
+ organization,
51
+ isLoading: isFetching,
52
+ error: fetchError,
53
+ refetch,
54
+ } = useOrganization(orgId || null);
55
+
56
+ const {
57
+ update,
58
+ isUpdating,
59
+ error: updateError,
60
+ clearError,
61
+ } = useUpdateOrganization();
62
+
63
+ // Fail-open: OSS local mode has no IAM service, so editing stays
64
+ // available there; cloud gets a genuine server verdict.
65
+ const { allowed: canEdit } = useCheckPermission(
66
+ orgId ? { kind: "organization", id: orgId } : null,
67
+ "can_edit",
68
+ );
69
+
70
+ const [standingContext, setStandingContext] = useState("");
71
+
72
+ const serverStandingContext =
73
+ organization?.spec?.preferences?.standingContext ?? "";
74
+
75
+ // Sync the form field when server data changes.
76
+ useEffect(() => {
77
+ if (!organization) return;
78
+ setStandingContext(
79
+ organization.spec?.preferences?.standingContext ?? "",
80
+ );
81
+ }, [organization]);
82
+
83
+ const hasChanges = useMemo(
84
+ () => standingContext.trim() !== serverStandingContext,
85
+ [standingContext, serverStandingContext],
86
+ );
87
+
88
+ const canSubmit = canEdit && hasChanges && !isUpdating;
89
+
90
+ const handleDiscard = useCallback(() => {
91
+ setStandingContext(serverStandingContext);
92
+ clearError();
93
+ }, [serverStandingContext, clearError]);
94
+
95
+ const handleSubmit = useCallback(
96
+ async (e: FormEvent) => {
97
+ e.preventDefault();
98
+ if (!canSubmit || !organization) return;
99
+
100
+ clearError();
101
+ try {
102
+ // update() is a full-spec replace: spread the complete mapped input
103
+ // so unedited spec fields survive, and override only preferences.
104
+ const updated = await update({
105
+ ...toOrganizationUpdateInput(organization),
106
+ preferences: { standingContext: standingContext.trim() || undefined },
107
+ });
108
+ refetch();
109
+ onUpdated?.(updated);
110
+ } catch {
111
+ // error state is managed by useUpdateOrganization
112
+ }
113
+ },
114
+ [canSubmit, organization, standingContext, update, clearError, refetch, onUpdated],
115
+ );
116
+
117
+ // -----------------------------------------------------------------------
118
+ // Loading state
119
+ // -----------------------------------------------------------------------
120
+
121
+ if (isFetching && !organization) {
122
+ return (
123
+ <div
124
+ className={cn("stg:space-y-4", className)}
125
+ aria-busy="true"
126
+ aria-label="Loading organization preferences"
127
+ >
128
+ <div className="stg:bg-muted-subtle stg:h-28 stg:animate-pulse stg:rounded" />
129
+ <div className="stg:bg-muted-subtle stg:h-8 stg:w-32 stg:animate-pulse stg:rounded" />
130
+ </div>
131
+ );
132
+ }
133
+
134
+ // -----------------------------------------------------------------------
135
+ // Fetch error
136
+ // -----------------------------------------------------------------------
137
+
138
+ if (fetchError) {
139
+ return (
140
+ <div className={cn("stg:space-y-3", className)} role="alert">
141
+ <p className="stg:text-destructive stg:text-sm">
142
+ {getUserMessage(fetchError)}
143
+ </p>
144
+ <button
145
+ type="button"
146
+ onClick={refetch}
147
+ className={cn(
148
+ "stg:rounded-md stg:px-3 stg:py-1.5 stg:text-xs stg:font-medium",
149
+ "stg:bg-primary stg:text-primary-foreground stg:hover:bg-primary-hover",
150
+ )}
151
+ >
152
+ Retry
153
+ </button>
154
+ </div>
155
+ );
156
+ }
157
+
158
+ if (!organization) return null;
159
+
160
+ // -----------------------------------------------------------------------
161
+ // Render
162
+ // -----------------------------------------------------------------------
163
+
164
+ return (
165
+ <form onSubmit={handleSubmit} className={cn("stg:space-y-4", className)}>
166
+ <StandingContextField
167
+ id={`${baseId}-standing-context`}
168
+ value={standingContext}
169
+ onChange={setStandingContext}
170
+ disabled={isUpdating}
171
+ readOnly={!canEdit}
172
+ placeholder={
173
+ "e.g. We deploy to us-east-1. Our stack is Go and TypeScript. Prefer concise answers."
174
+ }
175
+ helperText="Shared with agents as background context — not instructions. Applies to executions started by signed-in members of this organization and is visible on their execution records."
176
+ />
177
+
178
+ {!canEdit && (
179
+ <p className="stg:text-[0.65rem] stg:text-muted-foreground">
180
+ Only organization admins can edit these preferences.
181
+ </p>
182
+ )}
183
+
184
+ {updateError && (
185
+ <p className="stg:text-destructive stg:text-[0.65rem]" role="alert">
186
+ {getUserMessage(updateError)}
187
+ </p>
188
+ )}
189
+
190
+ {canEdit && (
191
+ <div className="stg:flex stg:items-center stg:gap-2">
192
+ <button
193
+ type="submit"
194
+ disabled={!canSubmit}
195
+ className={cn(
196
+ "stg:inline-flex stg:items-center stg:gap-1.5 stg:rounded-md stg:px-3 stg:py-1.5 stg:text-xs stg:font-medium",
197
+ "stg:bg-primary stg:text-primary-foreground stg:hover:bg-primary-hover",
198
+ "stg:disabled:pointer-events-none stg:disabled:opacity-40",
199
+ )}
200
+ >
201
+ {isUpdating && <SpinnerIcon size={12} />}
202
+ Save changes
203
+ </button>
204
+
205
+ {hasChanges && !isUpdating && (
206
+ <button
207
+ type="button"
208
+ onClick={handleDiscard}
209
+ className={cn(
210
+ "stg:rounded-md stg:px-3 stg:py-1.5 stg:text-xs",
211
+ "stg:text-muted-foreground stg:hover:text-foreground stg:hover:bg-accent-hover",
212
+ )}
213
+ >
214
+ Discard
215
+ </button>
216
+ )}
217
+ </div>
218
+ )}
219
+ </form>
220
+ );
221
+ }
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { type FormEvent, useCallback, useEffect, useId, useMemo, useState } from "react";
4
4
  import { cn } from "@stigmer/theme";
5
- import { getUserMessage } from "@stigmer/sdk";
5
+ import { getUserMessage, toOrganizationUpdateInput } from "@stigmer/sdk";
6
6
  import type { Organization } from "@stigmer/protos/ai/stigmer/tenancy/organization/v1/api_pb";
7
7
  import { useOrganization } from "./useOrganization.js";
8
8
  import { useUpdateOrganization } from "./useUpdateOrganization.js";
@@ -116,10 +116,12 @@ export function OrgProfilePanel({
116
116
 
117
117
  clearError();
118
118
  try {
119
+ // update() is a full-spec replace: spread the complete mapped input
120
+ // so unedited spec fields (e.g. preferences) survive the save, and
121
+ // override only the fields this form edits.
119
122
  const updated = await update({
123
+ ...toOrganizationUpdateInput(organization),
120
124
  name: name.trim(),
121
- slug: serverSlug,
122
- org: serverSlug,
123
125
  description: description.trim() || undefined,
124
126
  logoUrl: logoUrl.trim() || undefined,
125
127
  });
@@ -135,7 +137,6 @@ export function OrgProfilePanel({
135
137
  name,
136
138
  description,
137
139
  logoUrl,
138
- serverSlug,
139
140
  update,
140
141
  clearError,
141
142
  refetch,