@stigmer/react 3.40.0 → 3.41.0

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 (279) hide show
  1. package/README.md +1 -0
  2. package/access/ManageAccessDialog.d.ts +1 -1
  3. package/access/ManageAccessDialog.js +1 -1
  4. package/access/types.d.ts +1 -1
  5. package/billing/ChangePlanDialog.d.ts.map +1 -1
  6. package/billing/ChangePlanDialog.js +13 -1
  7. package/billing/ChangePlanDialog.js.map +1 -1
  8. package/billing/ProviderKeysSection.d.ts +33 -0
  9. package/billing/ProviderKeysSection.d.ts.map +1 -0
  10. package/billing/ProviderKeysSection.js +143 -0
  11. package/billing/ProviderKeysSection.js.map +1 -0
  12. package/billing/UpgradeNotice.d.ts.map +1 -1
  13. package/billing/UpgradeNotice.js +1 -0
  14. package/billing/UpgradeNotice.js.map +1 -1
  15. package/billing/index.d.ts +4 -0
  16. package/billing/index.d.ts.map +1 -1
  17. package/billing/index.js +2 -0
  18. package/billing/index.js.map +1 -1
  19. package/billing/plan-features.d.ts +3 -2
  20. package/billing/plan-features.d.ts.map +1 -1
  21. package/billing/plan-features.js +4 -3
  22. package/billing/plan-features.js.map +1 -1
  23. package/billing/useProviderKeys.d.ts +67 -0
  24. package/billing/useProviderKeys.d.ts.map +1 -0
  25. package/billing/useProviderKeys.js +61 -0
  26. package/billing/useProviderKeys.js.map +1 -0
  27. package/channel/AgentChannelsPanel.d.ts.map +1 -1
  28. package/channel/AgentChannelsPanel.js +11 -9
  29. package/channel/AgentChannelsPanel.js.map +1 -1
  30. package/channel/connect/ServingAppSection.js +1 -1
  31. package/channel/connect/ServingAppSection.js.map +1 -1
  32. package/channel-app/ChannelAppDetailPanel.d.ts +5 -0
  33. package/channel-app/ChannelAppDetailPanel.d.ts.map +1 -1
  34. package/channel-app/ChannelAppDetailPanel.js +22 -9
  35. package/channel-app/ChannelAppDetailPanel.js.map +1 -1
  36. package/channel-app/ChannelAppListPanel.d.ts +13 -4
  37. package/channel-app/ChannelAppListPanel.d.ts.map +1 -1
  38. package/channel-app/ChannelAppListPanel.js +5 -5
  39. package/channel-app/ChannelAppListPanel.js.map +1 -1
  40. package/channel-app/CreateChannelAppForm.js +1 -1
  41. package/channel-app/CreateChannelAppForm.js.map +1 -1
  42. package/channel-app/copy.d.ts +15 -0
  43. package/channel-app/copy.d.ts.map +1 -0
  44. package/channel-app/copy.js +15 -0
  45. package/channel-app/copy.js.map +1 -0
  46. package/channel-app/internal.d.ts +9 -0
  47. package/channel-app/internal.d.ts.map +1 -1
  48. package/channel-app/internal.js +9 -0
  49. package/channel-app/internal.js.map +1 -1
  50. package/channel-app/slackAppSetup.d.ts +3 -1
  51. package/channel-app/slackAppSetup.d.ts.map +1 -1
  52. package/channel-app/slackAppSetup.js +3 -1
  53. package/channel-app/slackAppSetup.js.map +1 -1
  54. package/channel-app/whatsappAppSetup.d.ts +3 -1
  55. package/channel-app/whatsappAppSetup.d.ts.map +1 -1
  56. package/channel-app/whatsappAppSetup.js +3 -1
  57. package/channel-app/whatsappAppSetup.js.map +1 -1
  58. package/composer/SessionComposer.d.ts.map +1 -1
  59. package/composer/SessionComposer.js +5 -9
  60. package/composer/SessionComposer.js.map +1 -1
  61. package/environment/EnvironmentVariableEditor.js +1 -1
  62. package/environment/EnvironmentVariableEditor.js.map +1 -1
  63. package/environment/index.d.ts +1 -1
  64. package/environment/index.d.ts.map +1 -1
  65. package/environment/index.js +1 -1
  66. package/environment/index.js.map +1 -1
  67. package/environment/systemEnvVars.d.ts +4 -63
  68. package/environment/systemEnvVars.d.ts.map +1 -1
  69. package/environment/systemEnvVars.js +21 -113
  70. package/environment/systemEnvVars.js.map +1 -1
  71. package/execution/RecalledMemoriesCard.js +1 -1
  72. package/iam-policy/OrgMembersPanel.js +1 -1
  73. package/iam-policy/OrgMembersPanel.js.map +1 -1
  74. package/iam-policy/useCheckPermission.d.ts +4 -1
  75. package/iam-policy/useCheckPermission.d.ts.map +1 -1
  76. package/iam-policy/useCheckPermission.js +4 -1
  77. package/iam-policy/useCheckPermission.js.map +1 -1
  78. package/iam-policy/useGrantableRoles.d.ts +1 -1
  79. package/iam-policy/useGrantableRoles.js +1 -1
  80. package/iam-policy/useRevokeOrgAccess.d.ts +10 -8
  81. package/iam-policy/useRevokeOrgAccess.d.ts.map +1 -1
  82. package/iam-policy/useRevokeOrgAccess.js +4 -4
  83. package/iam-policy/useRevokeOrgAccess.js.map +1 -1
  84. package/identity-account/AccountPreferencesPanel.d.ts +6 -0
  85. package/identity-account/AccountPreferencesPanel.d.ts.map +1 -1
  86. package/identity-account/AccountPreferencesPanel.js +33 -2
  87. package/identity-account/AccountPreferencesPanel.js.map +1 -1
  88. package/identity-account/useAccountExecutionDefaults.d.ts +1 -1
  89. package/identity-account/useAccountExecutionDefaults.d.ts.map +1 -1
  90. package/identity-account/useAccountExecutionDefaults.js +2 -3
  91. package/identity-account/useAccountExecutionDefaults.js.map +1 -1
  92. package/index.d.ts +3 -3
  93. package/index.d.ts.map +1 -1
  94. package/index.js +3 -3
  95. package/index.js.map +1 -1
  96. package/internal/UnknownPublicAddressNotice.d.ts +20 -0
  97. package/internal/UnknownPublicAddressNotice.d.ts.map +1 -0
  98. package/internal/UnknownPublicAddressNotice.js +19 -0
  99. package/internal/UnknownPublicAddressNotice.js.map +1 -0
  100. package/internal/features.js +2 -2
  101. package/internal/features.js.map +1 -1
  102. package/library/InstanceVisibilitySelector.d.ts +1 -1
  103. package/library/InstanceVisibilitySelector.d.ts.map +1 -1
  104. package/library/ResourceVisibilityControl.d.ts +6 -3
  105. package/library/ResourceVisibilityControl.d.ts.map +1 -1
  106. package/library/ResourceVisibilityControl.js +7 -4
  107. package/library/ResourceVisibilityControl.js.map +1 -1
  108. package/library/VisibilitySelector.d.ts +2 -2
  109. package/library/VisibilitySelector.d.ts.map +1 -1
  110. package/library/VisibilitySelector.js +1 -1
  111. package/licenses/LicenseIssueForm.js +1 -1
  112. package/licenses/LicenseIssueForm.js.map +1 -1
  113. package/licenses/license-format.d.ts +7 -5
  114. package/licenses/license-format.d.ts.map +1 -1
  115. package/licenses/license-format.js +1 -3
  116. package/licenses/license-format.js.map +1 -1
  117. package/mcp-server/McpServerConnectDialog.js +4 -5
  118. package/mcp-server/McpServerConnectDialog.js.map +1 -1
  119. package/mcp-server/McpServerDetailView.d.ts.map +1 -1
  120. package/mcp-server/McpServerDetailView.js +3 -6
  121. package/mcp-server/McpServerDetailView.js.map +1 -1
  122. package/mcp-server/McpServerPicker.d.ts.map +1 -1
  123. package/mcp-server/McpServerPicker.js +2 -6
  124. package/mcp-server/McpServerPicker.js.map +1 -1
  125. package/mcp-server/McpToolSelector.js +1 -1
  126. package/mcp-server/McpToolSelector.js.map +1 -1
  127. package/mcp-server/useMcpServerConnect.d.ts +14 -25
  128. package/mcp-server/useMcpServerConnect.d.ts.map +1 -1
  129. package/mcp-server/useMcpServerConnect.js +7 -19
  130. package/mcp-server/useMcpServerConnect.js.map +1 -1
  131. package/mcp-server/useMcpServerOAuthConnect.d.ts +1 -3
  132. package/mcp-server/useMcpServerOAuthConnect.d.ts.map +1 -1
  133. package/mcp-server/useMcpServerOAuthConnect.js +6 -19
  134. package/mcp-server/useMcpServerOAuthConnect.js.map +1 -1
  135. package/models/HarnessSelector.d.ts.map +1 -1
  136. package/models/HarnessSelector.js +1 -2
  137. package/models/HarnessSelector.js.map +1 -1
  138. package/models/ModelSelector.d.ts +1 -1
  139. package/models/ModelSelector.d.ts.map +1 -1
  140. package/models/ModelSelector.js +1 -7
  141. package/models/ModelSelector.js.map +1 -1
  142. package/models/harness.d.ts +10 -6
  143. package/models/harness.d.ts.map +1 -1
  144. package/models/harness.js +35 -28
  145. package/models/harness.js.map +1 -1
  146. package/models/index.d.ts +1 -1
  147. package/models/index.d.ts.map +1 -1
  148. package/models/index.js +1 -1
  149. package/models/index.js.map +1 -1
  150. package/organization/OrgPreferencesPanel.d.ts.map +1 -1
  151. package/organization/OrgPreferencesPanel.js +13 -1
  152. package/organization/OrgPreferencesPanel.js.map +1 -1
  153. package/package.json +5 -5
  154. package/plugin/useMcpServerReadiness.js +2 -2
  155. package/plugin/useMcpServerReadiness.js.map +1 -1
  156. package/provider-standing/ProviderStandingConsole.js +1 -1
  157. package/provider.d.ts +27 -1
  158. package/provider.d.ts.map +1 -1
  159. package/provider.js +3 -2
  160. package/provider.js.map +1 -1
  161. package/public-base-url-context.d.ts +35 -0
  162. package/public-base-url-context.d.ts.map +1 -0
  163. package/public-base-url-context.js +62 -0
  164. package/public-base-url-context.js.map +1 -0
  165. package/resource-workbench/components/SelectionCheckbox.js +1 -1
  166. package/resource-workbench/components/SelectionCheckbox.js.map +1 -1
  167. package/session/useLocalSessionWorker.d.ts +19 -3
  168. package/session/useLocalSessionWorker.d.ts.map +1 -1
  169. package/session/useLocalSessionWorker.js +37 -4
  170. package/session/useLocalSessionWorker.js.map +1 -1
  171. package/settings/ChannelAppsSection.d.ts +7 -0
  172. package/settings/ChannelAppsSection.d.ts.map +1 -1
  173. package/settings/ChannelAppsSection.js +15 -2
  174. package/settings/ChannelAppsSection.js.map +1 -1
  175. package/settings/ProviderKeysSection.d.ts +3 -0
  176. package/settings/ProviderKeysSection.d.ts.map +1 -0
  177. package/settings/ProviderKeysSection.js +3 -0
  178. package/settings/ProviderKeysSection.js.map +1 -0
  179. package/settings/index.d.ts +2 -0
  180. package/settings/index.d.ts.map +1 -1
  181. package/settings/index.js +1 -0
  182. package/settings/index.js.map +1 -1
  183. package/settings/settings-nav.d.ts.map +1 -1
  184. package/settings/settings-nav.js +2 -1
  185. package/settings/settings-nav.js.map +1 -1
  186. package/sharing/ShareAgentDialog.d.ts.map +1 -1
  187. package/sharing/ShareAgentDialog.js +12 -8
  188. package/sharing/ShareAgentDialog.js.map +1 -1
  189. package/sharing/useCanCreateAgentShare.d.ts +3 -1
  190. package/sharing/useCanCreateAgentShare.d.ts.map +1 -1
  191. package/sharing/useCanCreateAgentShare.js +5 -3
  192. package/sharing/useCanCreateAgentShare.js.map +1 -1
  193. package/sharing/useSaveAgentShare.d.ts +1 -1
  194. package/sharing/useSaveAgentShare.js +1 -1
  195. package/src/access/ManageAccessDialog.tsx +1 -1
  196. package/src/access/types.ts +1 -1
  197. package/src/billing/ChangePlanDialog.tsx +14 -2
  198. package/src/billing/ProviderKeysSection.tsx +324 -0
  199. package/src/billing/UpgradeNotice.tsx +1 -0
  200. package/src/billing/index.ts +9 -0
  201. package/src/billing/plan-features.ts +4 -3
  202. package/src/billing/useProviderKeys.ts +129 -0
  203. package/src/channel/AgentChannelsPanel.tsx +11 -9
  204. package/src/channel/connect/ServingAppSection.tsx +1 -1
  205. package/src/channel-app/ChannelAppDetailPanel.tsx +214 -176
  206. package/src/channel-app/ChannelAppListPanel.tsx +14 -4
  207. package/src/channel-app/CreateChannelAppForm.tsx +0 -1
  208. package/src/channel-app/copy.ts +16 -0
  209. package/src/channel-app/internal.tsx +21 -0
  210. package/src/channel-app/slackAppSetup.ts +3 -1
  211. package/src/channel-app/whatsappAppSetup.ts +3 -1
  212. package/src/composer/SessionComposer.tsx +5 -14
  213. package/src/environment/EnvironmentVariableEditor.tsx +1 -1
  214. package/src/environment/index.ts +1 -7
  215. package/src/environment/systemEnvVars.ts +21 -133
  216. package/src/execution/RecalledMemoriesCard.tsx +1 -1
  217. package/src/iam-policy/OrgMembersPanel.tsx +3 -1
  218. package/src/iam-policy/useCheckPermission.ts +4 -1
  219. package/src/iam-policy/useGrantableRoles.ts +1 -1
  220. package/src/iam-policy/useRevokeOrgAccess.ts +10 -8
  221. package/src/identity-account/AccountPreferencesPanel.tsx +56 -9
  222. package/src/identity-account/useAccountExecutionDefaults.ts +2 -5
  223. package/src/index.ts +9 -5
  224. package/src/internal/UnknownPublicAddressNotice.tsx +31 -0
  225. package/src/internal/features.ts +2 -2
  226. package/src/library/InstanceVisibilitySelector.tsx +1 -1
  227. package/src/library/ResourceVisibilityControl.tsx +7 -4
  228. package/src/library/VisibilitySelector.tsx +2 -2
  229. package/src/licenses/LicenseIssueForm.tsx +1 -1
  230. package/src/licenses/license-format.ts +11 -7
  231. package/src/mcp-server/McpServerConnectDialog.tsx +4 -6
  232. package/src/mcp-server/McpServerDetailView.tsx +3 -6
  233. package/src/mcp-server/McpServerPicker.tsx +2 -12
  234. package/src/mcp-server/McpToolSelector.tsx +1 -1
  235. package/src/mcp-server/useMcpServerConnect.ts +14 -33
  236. package/src/mcp-server/useMcpServerOAuthConnect.ts +7 -23
  237. package/src/models/HarnessSelector.tsx +1 -3
  238. package/src/models/ModelSelector.tsx +2 -7
  239. package/src/models/harness.ts +41 -39
  240. package/src/models/index.ts +1 -1
  241. package/src/organization/OrgPreferencesPanel.tsx +19 -1
  242. package/src/plugin/useMcpServerReadiness.ts +2 -2
  243. package/src/provider-standing/ProviderStandingConsole.tsx +1 -1
  244. package/src/provider.tsx +56 -26
  245. package/src/public-base-url-context.ts +70 -0
  246. package/src/resource-workbench/components/SelectionCheckbox.tsx +1 -1
  247. package/src/session/useLocalSessionWorker.ts +35 -4
  248. package/src/settings/ChannelAppsSection.tsx +19 -2
  249. package/src/settings/ProviderKeysSection.tsx +4 -0
  250. package/src/settings/index.ts +2 -0
  251. package/src/settings/settings-nav.ts +2 -0
  252. package/src/sharing/ShareAgentDialog.tsx +30 -14
  253. package/src/sharing/useCanCreateAgentShare.ts +6 -4
  254. package/src/sharing/useSaveAgentShare.ts +1 -1
  255. package/src/styles.css +14 -4
  256. package/src/workflow/WorkflowTaskApprovalSummary.tsx +14 -7
  257. package/src/workflow/inspector/tabs/BranchesTab.tsx +2 -2
  258. package/src/workflow/inspector/tabs/CatchTab.tsx +1 -1
  259. package/src/workflow/inspector/tabs/IterationTab.tsx +1 -1
  260. package/src/workflow/instance/WorkflowInstanceDetailPanel.tsx +7 -6
  261. package/src/workflow/task-detail/task-approval.ts +20 -13
  262. package/styles.css +1 -1
  263. package/workflow/WorkflowTaskApprovalSummary.d.ts +7 -4
  264. package/workflow/WorkflowTaskApprovalSummary.d.ts.map +1 -1
  265. package/workflow/WorkflowTaskApprovalSummary.js +8 -3
  266. package/workflow/WorkflowTaskApprovalSummary.js.map +1 -1
  267. package/workflow/inspector/tabs/BranchesTab.js +1 -1
  268. package/workflow/inspector/tabs/BranchesTab.js.map +1 -1
  269. package/workflow/inspector/tabs/CatchTab.js +1 -1
  270. package/workflow/inspector/tabs/CatchTab.js.map +1 -1
  271. package/workflow/inspector/tabs/IterationTab.js +1 -1
  272. package/workflow/inspector/tabs/IterationTab.js.map +1 -1
  273. package/workflow/instance/WorkflowInstanceDetailPanel.d.ts.map +1 -1
  274. package/workflow/instance/WorkflowInstanceDetailPanel.js +6 -5
  275. package/workflow/instance/WorkflowInstanceDetailPanel.js.map +1 -1
  276. package/workflow/task-detail/task-approval.d.ts +13 -11
  277. package/workflow/task-detail/task-approval.d.ts.map +1 -1
  278. package/workflow/task-detail/task-approval.js +8 -4
  279. package/workflow/task-detail/task-approval.js.map +1 -1
@@ -4,7 +4,7 @@ import { type FormEvent, useCallback, useId, useMemo, useState } from "react";
4
4
  import { cn } from "@stigmer/theme";
5
5
  import { getUserMessage, toChannelAppUpdateInput } from "@stigmer/sdk";
6
6
  import type { ChannelApp } from "@stigmer/protos/ai/stigmer/agentic/channelapp/v1/api_pb";
7
- import { useStigmer } from "../hooks.js";
7
+ import { usePublicBaseUrl } from "../public-base-url-context.js";
8
8
  import { useUpdateChannelApp } from "./useUpdateChannelApp.js";
9
9
  import { useDeleteChannelApp } from "./useDeleteChannelApp.js";
10
10
  import type { ChannelAppCreateHandoff } from "./CreateChannelAppForm.js";
@@ -17,7 +17,8 @@ import {
17
17
  WHATSAPP_CHANNEL_APP_WEBHOOK_FIELDS,
18
18
  whatsappChannelAppWebhookUrl,
19
19
  } from "./whatsappAppSetup.js";
20
- import { CopyBlock, CopyRow, FormField } from "./internal.js";
20
+ import { CopyBlock, CopyRow, FormField, UnknownAddressRow } from "./internal.js";
21
+ import { PermissionGate } from "../iam-policy/PermissionGate.js";
21
22
  import { SpinnerIcon } from "../internal/SpinnerIcon.js";
22
23
 
23
24
  // ---------------------------------------------------------------------------
@@ -66,6 +67,11 @@ export interface ChannelAppDetailPanelProps {
66
67
  * untouched preserves the stored values (rotate one secret without
67
68
  * re-entering the others). Deletion is refused server-side while any
68
69
  * agent channel still installs through this app.
70
+ *
71
+ * The credentials form is offered to a caller who may edit the app
72
+ * (`can_edit`) and the deletion section to one who may delete it
73
+ * (`can_delete`): the organization's admins. Someone granted viewer on an
74
+ * app sees its setup values with no actions.
69
75
  */
70
76
  export function ChannelAppDetailPanel({
71
77
  channelApp,
@@ -127,50 +133,55 @@ export function ChannelAppDetailPanel({
127
133
  ) : null}
128
134
 
129
135
  {/* Deletion */}
130
- <section className="stg:border-t stg:border-border stg:pt-3" aria-label="Danger zone">
131
- {deleteError && (
132
- <p className="stg:text-destructive stg:mb-2 stg:text-[0.65rem]" role="alert">
133
- {getUserMessage(deleteError)}
134
- </p>
135
- )}
136
- {confirmingDelete ? (
137
- <div className="stg:flex stg:items-center stg:gap-2">
138
- <p className="stg:text-xs stg:text-foreground">
139
- Delete this channel app? Channels installing through it must be
140
- disconnected first.
136
+ <PermissionGate
137
+ resource={{ kind: "channel_app", id: appId }}
138
+ relation="can_delete"
139
+ >
140
+ <section className="stg:border-t stg:border-border stg:pt-3" aria-label="Danger zone">
141
+ {deleteError && (
142
+ <p className="stg:text-destructive stg:mb-2 stg:text-[0.65rem]" role="alert">
143
+ {getUserMessage(deleteError)}
141
144
  </p>
145
+ )}
146
+ {confirmingDelete ? (
147
+ <div className="stg:flex stg:items-center stg:gap-2">
148
+ <p className="stg:text-xs stg:text-foreground">
149
+ Delete this channel app? Channels installing through it must be
150
+ disconnected first.
151
+ </p>
152
+ <button
153
+ type="button"
154
+ onClick={handleDelete}
155
+ disabled={isDeleting}
156
+ className={cn(
157
+ "stg:inline-flex stg:items-center stg:gap-1.5 stg:rounded-md stg:px-2.5 stg:py-1 stg:text-xs stg:font-medium",
158
+ "stg:bg-destructive stg:text-destructive-foreground stg:hover:opacity-90",
159
+ "stg:disabled:pointer-events-none stg:disabled:opacity-40",
160
+ )}
161
+ >
162
+ {isDeleting && <SpinnerIcon size={12} />}
163
+ Delete
164
+ </button>
165
+ <button
166
+ type="button"
167
+ onClick={() => setConfirmingDelete(false)}
168
+ disabled={isDeleting}
169
+ className="stg:text-muted-foreground stg:hover:text-foreground stg:text-xs stg:transition-colors"
170
+ >
171
+ Keep
172
+ </button>
173
+ </div>
174
+ ) : (
142
175
  <button
143
176
  type="button"
144
- onClick={handleDelete}
145
- disabled={isDeleting}
146
- className={cn(
147
- "stg:inline-flex stg:items-center stg:gap-1.5 stg:rounded-md stg:px-2.5 stg:py-1 stg:text-xs stg:font-medium",
148
- "stg:bg-destructive stg:text-destructive-foreground stg:hover:opacity-90",
149
- "stg:disabled:pointer-events-none stg:disabled:opacity-40",
150
- )}
151
- >
152
- {isDeleting && <SpinnerIcon size={12} />}
153
- Delete
154
- </button>
155
- <button
156
- type="button"
157
- onClick={() => setConfirmingDelete(false)}
158
- disabled={isDeleting}
159
- className="stg:text-muted-foreground stg:hover:text-foreground stg:text-xs stg:transition-colors"
177
+ onClick={() => setConfirmingDelete(true)}
178
+ className="stg:text-destructive stg:text-xs stg:font-medium stg:hover:opacity-80"
160
179
  >
161
- Keep
180
+ Delete channel app
162
181
  </button>
163
- </div>
164
- ) : (
165
- <button
166
- type="button"
167
- onClick={() => setConfirmingDelete(true)}
168
- className="stg:text-destructive stg:text-xs stg:font-medium stg:hover:opacity-80"
169
- >
170
- Delete channel app
171
- </button>
172
- )}
173
- </section>
182
+ )}
183
+ </section>
184
+ </PermissionGate>
174
185
  </div>
175
186
  );
176
187
  }
@@ -189,7 +200,7 @@ function SlackAppDetail({
189
200
  readonly onUpdated?: (app: ChannelApp) => void;
190
201
  }) {
191
202
  const baseId = useId();
192
- const stigmer = useStigmer();
203
+ const publicBaseUrl = usePublicBaseUrl();
193
204
  const { update, isUpdating, error: updateError, clearError } = useUpdateChannelApp();
194
205
 
195
206
  const slack = channelApp.spec?.providerConfig?.case === "slack"
@@ -203,9 +214,14 @@ function SlackAppDetail({
203
214
  const appId = channelApp.metadata?.id ?? "";
204
215
  const name = channelApp.metadata?.name ?? "";
205
216
 
217
+ // `undefined` while the server's public address is unknown: the row
218
+ // says so, and the manifest leaves out its event subscriptions.
206
219
  const webhookUrl = useMemo(
207
- () => slackChannelAppWebhookUrl(stigmer.baseUrl, appId),
208
- [stigmer.baseUrl, appId],
220
+ () =>
221
+ publicBaseUrl === null
222
+ ? undefined
223
+ : slackChannelAppWebhookUrl(publicBaseUrl, appId),
224
+ [publicBaseUrl, appId],
209
225
  );
210
226
  const redirectUrl = useMemo(
211
227
  () => slackChannelAppRedirectUrl(consoleOrigin),
@@ -260,73 +276,82 @@ function SlackAppDetail({
260
276
  below (Slack verifies it immediately). The completed manifest is
261
277
  also available for review or re-paste.
262
278
  </p>
263
- <CopyRow
264
- label="Events request URL"
265
- value={webhookUrl}
266
- copyTargetId="stgm-chapp-webhook-copy"
267
- />
279
+ {webhookUrl !== undefined ? (
280
+ <CopyRow
281
+ label="Events request URL"
282
+ value={webhookUrl}
283
+ copyTargetId="stgm-chapp-webhook-copy"
284
+ />
285
+ ) : (
286
+ <UnknownAddressRow label="Events request URL" subject="the events request URL" />
287
+ )}
268
288
  <CopyRow label="OAuth redirect URL" value={redirectUrl} />
269
289
  <CopyBlock label="Completed app manifest" value={manifest} />
270
290
  </section>
271
291
 
272
292
  {/* Credential rotation */}
273
- <form onSubmit={handleSave} className="stg:space-y-3" aria-label="Credentials">
274
- <p className="stg:text-xs stg:font-medium stg:text-foreground">Credentials</p>
275
- <p className="stg:-mt-2 stg:text-[0.65rem] stg:text-muted-foreground">
276
- Secrets show as <code className="stg:font-mono">***REDACTED***</code>;
277
- leave a field untouched to keep its stored value, or paste a new
278
- one to rotate it.
279
- </p>
280
-
281
- <FormField
282
- id={`${baseId}-client-id`}
283
- label="Client ID"
284
- value={clientId}
285
- onChange={setClientId}
286
- placeholder="1234567890.0987654321"
287
- disabled={isUpdating}
288
- required
289
- />
290
- <FormField
291
- id={`${baseId}-client-secret`}
292
- label="Client secret"
293
- value={clientSecret}
294
- onChange={setClientSecret}
295
- placeholder="Client secret"
296
- type="password"
297
- disabled={isUpdating}
298
- required
299
- />
300
- <FormField
301
- id={`${baseId}-signing-secret`}
302
- label="Signing secret"
303
- value={signingSecret}
304
- onChange={setSigningSecret}
305
- placeholder="Signing secret"
306
- type="password"
307
- disabled={isUpdating}
308
- required
309
- />
310
-
311
- {updateError && (
312
- <p className="stg:text-destructive stg:text-[0.65rem]" role="alert">
313
- {getUserMessage(updateError)}
293
+ <PermissionGate
294
+ resource={{ kind: "channel_app", id: appId }}
295
+ relation="can_edit"
296
+ >
297
+ <form onSubmit={handleSave} className="stg:space-y-3" aria-label="Credentials">
298
+ <p className="stg:text-xs stg:font-medium stg:text-foreground">Credentials</p>
299
+ <p className="stg:-mt-2 stg:text-[0.65rem] stg:text-muted-foreground">
300
+ Secrets show as <code className="stg:font-mono">***REDACTED***</code>;
301
+ leave a field untouched to keep its stored value, or paste a new
302
+ one to rotate it.
314
303
  </p>
315
- )}
316
304
 
317
- <button
318
- type="submit"
319
- disabled={!canSave}
320
- className={cn(
321
- "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",
322
- "stg:bg-primary stg:text-primary-foreground stg:hover:bg-primary-hover",
323
- "stg:disabled:pointer-events-none stg:disabled:opacity-40",
305
+ <FormField
306
+ id={`${baseId}-client-id`}
307
+ label="Client ID"
308
+ value={clientId}
309
+ onChange={setClientId}
310
+ placeholder="1234567890.0987654321"
311
+ disabled={isUpdating}
312
+ required
313
+ />
314
+ <FormField
315
+ id={`${baseId}-client-secret`}
316
+ label="Client secret"
317
+ value={clientSecret}
318
+ onChange={setClientSecret}
319
+ placeholder="Client secret"
320
+ type="password"
321
+ disabled={isUpdating}
322
+ required
323
+ />
324
+ <FormField
325
+ id={`${baseId}-signing-secret`}
326
+ label="Signing secret"
327
+ value={signingSecret}
328
+ onChange={setSigningSecret}
329
+ placeholder="Signing secret"
330
+ type="password"
331
+ disabled={isUpdating}
332
+ required
333
+ />
334
+
335
+ {updateError && (
336
+ <p className="stg:text-destructive stg:text-[0.65rem]" role="alert">
337
+ {getUserMessage(updateError)}
338
+ </p>
324
339
  )}
325
- >
326
- {isUpdating && <SpinnerIcon size={12} />}
327
- Save credentials
328
- </button>
329
- </form>
340
+
341
+ <button
342
+ type="submit"
343
+ disabled={!canSave}
344
+ className={cn(
345
+ "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",
346
+ "stg:bg-primary stg:text-primary-foreground stg:hover:bg-primary-hover",
347
+ "stg:disabled:pointer-events-none stg:disabled:opacity-40",
348
+ )}
349
+ >
350
+ {isUpdating && <SpinnerIcon size={12} />}
351
+ Save credentials
352
+ </button>
353
+ </form>
354
+ </PermissionGate>
330
355
  </>
331
356
  );
332
357
  }
@@ -345,7 +370,7 @@ function WhatsAppAppDetail({
345
370
  readonly onUpdated?: (app: ChannelApp) => void;
346
371
  }) {
347
372
  const baseId = useId();
348
- const stigmer = useStigmer();
373
+ const publicBaseUrl = usePublicBaseUrl();
349
374
  const { update, isUpdating, error: updateError, clearError } = useUpdateChannelApp();
350
375
 
351
376
  const whatsapp = channelApp.spec?.providerConfig?.case === "whatsapp"
@@ -360,9 +385,13 @@ function WhatsAppAppDetail({
360
385
  const appId = channelApp.metadata?.id ?? "";
361
386
  const name = channelApp.metadata?.name ?? "";
362
387
 
388
+ // `undefined` while the server's public address is unknown: the row says so.
363
389
  const webhookUrl = useMemo(
364
- () => whatsappChannelAppWebhookUrl(stigmer.baseUrl, appId),
365
- [stigmer.baseUrl, appId],
390
+ () =>
391
+ publicBaseUrl === null
392
+ ? undefined
393
+ : whatsappChannelAppWebhookUrl(publicBaseUrl, appId),
394
+ [publicBaseUrl, appId],
366
395
  );
367
396
 
368
397
  const canSave =
@@ -416,11 +445,15 @@ function WhatsAppAppDetail({
416
445
  </span>{" "}
417
446
  webhook field.
418
447
  </p>
419
- <CopyRow
420
- label="Callback URL"
421
- value={webhookUrl}
422
- copyTargetId="stgm-chapp-webhook-copy"
423
- />
448
+ {webhookUrl !== undefined ? (
449
+ <CopyRow
450
+ label="Callback URL"
451
+ value={webhookUrl}
452
+ copyTargetId="stgm-chapp-webhook-copy"
453
+ />
454
+ ) : (
455
+ <UnknownAddressRow label="Callback URL" subject="the callback URL" />
456
+ )}
424
457
  {createHandoff?.verifyToken ? (
425
458
  <CopyRow
426
459
  label="Verify token"
@@ -440,74 +473,79 @@ function WhatsAppAppDetail({
440
473
  </section>
441
474
 
442
475
  {/* Credential rotation */}
443
- <form onSubmit={handleSave} className="stg:space-y-3" aria-label="Credentials">
444
- <p className="stg:text-xs stg:font-medium stg:text-foreground">Credentials</p>
445
- <p className="stg:-mt-2 stg:text-[0.65rem] stg:text-muted-foreground">
446
- Secrets show as <code className="stg:font-mono">***REDACTED***</code>;
447
- leave a field untouched to keep its stored value, or paste a new
448
- one to rotate it.
449
- </p>
450
-
451
- <FormField
452
- id={`${baseId}-app-id`}
453
- label="App ID"
454
- value={metaAppId}
455
- onChange={setMetaAppId}
456
- placeholder="1234567890123456"
457
- disabled={isUpdating}
458
- required
459
- />
460
- <FormField
461
- id={`${baseId}-app-secret`}
462
- label="App secret"
463
- value={appSecret}
464
- onChange={setAppSecret}
465
- placeholder="App secret"
466
- type="password"
467
- disabled={isUpdating}
468
- required
469
- />
470
- <FormField
471
- id={`${baseId}-access-token`}
472
- label="Access token"
473
- value={accessToken}
474
- onChange={setAccessToken}
475
- placeholder="Long-lived system-user access token"
476
- type="password"
477
- disabled={isUpdating}
478
- required
479
- />
480
- <FormField
481
- id={`${baseId}-verify-token`}
482
- label="Verify token"
483
- value={verifyToken}
484
- onChange={setVerifyToken}
485
- placeholder="Verify token"
486
- type="password"
487
- hint="Rotating it? Update Meta's webhook configuration to the same value."
488
- disabled={isUpdating}
489
- required
490
- />
491
-
492
- {updateError && (
493
- <p className="stg:text-destructive stg:text-[0.65rem]" role="alert">
494
- {getUserMessage(updateError)}
476
+ <PermissionGate
477
+ resource={{ kind: "channel_app", id: appId }}
478
+ relation="can_edit"
479
+ >
480
+ <form onSubmit={handleSave} className="stg:space-y-3" aria-label="Credentials">
481
+ <p className="stg:text-xs stg:font-medium stg:text-foreground">Credentials</p>
482
+ <p className="stg:-mt-2 stg:text-[0.65rem] stg:text-muted-foreground">
483
+ Secrets show as <code className="stg:font-mono">***REDACTED***</code>;
484
+ leave a field untouched to keep its stored value, or paste a new
485
+ one to rotate it.
495
486
  </p>
496
- )}
497
487
 
498
- <button
499
- type="submit"
500
- disabled={!canSave}
501
- className={cn(
502
- "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",
503
- "stg:bg-primary stg:text-primary-foreground stg:hover:bg-primary-hover",
504
- "stg:disabled:pointer-events-none stg:disabled:opacity-40",
488
+ <FormField
489
+ id={`${baseId}-app-id`}
490
+ label="App ID"
491
+ value={metaAppId}
492
+ onChange={setMetaAppId}
493
+ placeholder="1234567890123456"
494
+ disabled={isUpdating}
495
+ required
496
+ />
497
+ <FormField
498
+ id={`${baseId}-app-secret`}
499
+ label="App secret"
500
+ value={appSecret}
501
+ onChange={setAppSecret}
502
+ placeholder="App secret"
503
+ type="password"
504
+ disabled={isUpdating}
505
+ required
506
+ />
507
+ <FormField
508
+ id={`${baseId}-access-token`}
509
+ label="Access token"
510
+ value={accessToken}
511
+ onChange={setAccessToken}
512
+ placeholder="Long-lived system-user access token"
513
+ type="password"
514
+ disabled={isUpdating}
515
+ required
516
+ />
517
+ <FormField
518
+ id={`${baseId}-verify-token`}
519
+ label="Verify token"
520
+ value={verifyToken}
521
+ onChange={setVerifyToken}
522
+ placeholder="Verify token"
523
+ type="password"
524
+ hint="Rotating it? Update Meta's webhook configuration to the same value."
525
+ disabled={isUpdating}
526
+ required
527
+ />
528
+
529
+ {updateError && (
530
+ <p className="stg:text-destructive stg:text-[0.65rem]" role="alert">
531
+ {getUserMessage(updateError)}
532
+ </p>
505
533
  )}
506
- >
507
- {isUpdating && <SpinnerIcon size={12} />}
508
- Save credentials
509
- </button>
510
- </form>
534
+
535
+ <button
536
+ type="submit"
537
+ disabled={!canSave}
538
+ className={cn(
539
+ "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",
540
+ "stg:bg-primary stg:text-primary-foreground stg:hover:bg-primary-hover",
541
+ "stg:disabled:pointer-events-none stg:disabled:opacity-40",
542
+ )}
543
+ >
544
+ {isUpdating && <SpinnerIcon size={12} />}
545
+ Save credentials
546
+ </button>
547
+ </form>
548
+ </PermissionGate>
511
549
  </>
512
550
  );
513
551
  }
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
 
3
+ import type { ReactNode } from "react";
3
4
  import { cn } from "@stigmer/theme";
4
5
  import { getUserMessage } from "@stigmer/sdk";
5
6
  import { timestampDate } from "@bufbuild/protobuf/wkt";
@@ -22,14 +23,22 @@ export interface ChannelAppListPanelProps {
22
23
  readonly onEdit?: (app: ChannelApp) => void;
23
24
  /** Re-expose refetch so parents can trigger a list refresh. */
24
25
  readonly onRefetchRef?: (refetch: () => void) => void;
26
+ /**
27
+ * Shown when the list is empty. Defaults to "No channel apps registered
28
+ * yet." The list holds only the apps the caller may view (the
29
+ * organization's admins, and anyone granted viewer on an app), so a host
30
+ * that knows the caller may not manage channel apps should say who does
31
+ * instead.
32
+ */
33
+ readonly emptyState?: ReactNode;
25
34
  /** Additional CSS class names for the root container. */
26
35
  readonly className?: string;
27
36
  }
28
37
 
29
38
  /**
30
- * Displays the {@link ChannelApp} resources owned by an organization —
31
- * the customer's own provider apps (Slack, Meta/WhatsApp) that agent
32
- * channels can install through.
39
+ * Displays the {@link ChannelApp} resources of an organization that the
40
+ * caller may view — the customer's own provider apps (Slack,
41
+ * Meta/WhatsApp) that agent channels can install through.
33
42
  *
34
43
  * Each row shows the app name, provider, the provider's non-secret app
35
44
  * identifier, and creation date. Secret fields never appear (they
@@ -49,6 +58,7 @@ export function ChannelAppListPanel({
49
58
  org,
50
59
  onEdit,
51
60
  onRefetchRef,
61
+ emptyState = "No channel apps registered yet.",
52
62
  className,
53
63
  }: ChannelAppListPanelProps) {
54
64
  const { channelApps, isLoading, error, refetch } = useChannelAppList(org);
@@ -87,7 +97,7 @@ export function ChannelAppListPanel({
87
97
  className,
88
98
  )}
89
99
  >
90
- No channel apps registered yet.
100
+ {emptyState}
91
101
  </p>
92
102
  );
93
103
  }
@@ -311,7 +311,6 @@ function ProviderPicker({
311
311
  checked={checked}
312
312
  onChange={() => onChange(p.id)}
313
313
  disabled={disabled}
314
- className="stg:accent-current"
315
314
  />
316
315
  <p.Icon className="stg:size-3.5 stg:shrink-0 stg:text-foreground" />
317
316
  <span className="stg:text-xs stg:font-medium stg:text-foreground">{p.label}</span>
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Channel-app copy shared by more than one surface, so the settings section
3
+ * and anything else that lists an organization's channel apps say the same
4
+ * thing.
5
+ *
6
+ * A channel app carries its organization's Slack or Meta app secrets, so the
7
+ * model shows it to the organization's admins and anyone granted viewer on
8
+ * it, and plain members and viewers may not see it at all
9
+ * (stigmer/stigmer#1384, #1409). An empty list is therefore not evidence that none
10
+ * is registered, so a caller who may not create channel apps is told who
11
+ * manages them instead of being told that nothing exists.
12
+ */
13
+
14
+ /** Shown in place of the empty state to a caller who may not create channel apps. */
15
+ export const CHANNEL_APPS_MANAGED_BY_ADMINS =
16
+ "Channel apps are managed by your organization's admins.";
@@ -3,6 +3,7 @@
3
3
  import { useCallback } from "react";
4
4
  import { cn } from "@stigmer/theme";
5
5
  import { TruncatedText } from "../internal/truncated-text.js";
6
+ import { UnknownPublicAddressNotice } from "../internal/UnknownPublicAddressNotice.js";
6
7
  import { useCopyFeedback } from "../internal/useCopyFeedback.js";
7
8
 
8
9
  /**
@@ -89,6 +90,26 @@ export function CopyRow({
89
90
  );
90
91
  }
91
92
 
93
+ /**
94
+ * The place of a {@link CopyRow} whose URL cannot be built because the
95
+ * server's public address is unknown: the same label, then the shared
96
+ * notice in place of the value and its copy button.
97
+ */
98
+ export function UnknownAddressRow({
99
+ label,
100
+ subject,
101
+ }: {
102
+ label: string;
103
+ subject: string;
104
+ }) {
105
+ return (
106
+ <div className="stg:space-y-1">
107
+ <p className="stg:text-xs stg:font-medium stg:text-foreground">{label}</p>
108
+ <UnknownPublicAddressNotice subject={subject} />
109
+ </div>
110
+ );
111
+ }
112
+
92
113
  /** A multi-line copyable value (the app manifest) in a scrollable block. */
93
114
  export function CopyBlock({
94
115
  label,
@@ -53,7 +53,9 @@ export const SLACK_CHANNEL_APP_BOT_EVENTS = [
53
53
  * The events webhook URL for one ChannelApp: the per-app route the
54
54
  * receiver resolves the app's signing secret by.
55
55
  *
56
- * @param apiBaseUrl the Stigmer API origin (`stigmer.baseUrl`)
56
+ * @param apiBaseUrl the server's public base URL, absolute (the one
57
+ * `StigmerProvider`'s `publicBaseUrl` names, or the
58
+ * client's `baseUrl` when that is absolute)
57
59
  * @param channelAppId the ChannelApp resource id (`chapp_...`)
58
60
  */
59
61
  export function slackChannelAppWebhookUrl(
@@ -36,7 +36,9 @@ export const WHATSAPP_CHANNEL_APP_WEBHOOK_FIELDS = ["messages"] as const;
36
36
  * The events webhook URL for one ChannelApp: the per-app route the
37
37
  * receiver resolves the app's secret and verify token by.
38
38
  *
39
- * @param apiBaseUrl the Stigmer API origin (`stigmer.baseUrl`)
39
+ * @param apiBaseUrl the server's public base URL, absolute (the one
40
+ * `StigmerProvider`'s `publicBaseUrl` names, or the
41
+ * client's `baseUrl` when that is absolute)
40
42
  * @param channelAppId the ChannelApp resource id (`chapp_...`)
41
43
  */
42
44
  export function whatsappChannelAppWebhookUrl(
@@ -39,11 +39,7 @@ import { FileReferenceChipList } from "../file-reference/FileReferenceChipList.j
39
39
  import { FILE_REF_MIME } from "../internal/file-tree/index.js";
40
40
  import { useSessionEnvPool } from "../environment/useSessionEnvPool.js";
41
41
  import { usePersonalEnvironment } from "../environment/usePersonalEnvironment.js";
42
- import { useStigmer } from "../hooks.js";
43
- import {
44
- SYSTEM_ENV_VAR_KEYS,
45
- resolveSystemEnvVarValues,
46
- } from "../environment/systemEnvVars.js";
42
+ import { SYSTEM_ENV_VAR_KEYS } from "../environment/systemEnvVars.js";
47
43
  import { useRenderTracer } from "../internal/dev/index.js";
48
44
  import { Tooltip, TooltipContent, TooltipTrigger } from "../internal/tooltip.js";
49
45
  import {
@@ -714,8 +710,6 @@ const SessionComposerInner = forwardRef<SessionComposerHandle, SessionComposerPr
714
710
  [personalEnv.environment],
715
711
  );
716
712
 
717
- const stigmer = useStigmer();
718
-
719
713
  const pool = useSessionEnvPool({
720
714
  personalEnvKeys,
721
715
  manualSecrets: sessionVariables?.entries,
@@ -1025,14 +1019,11 @@ const SessionComposerInner = forwardRef<SessionComposerHandle, SessionComposerPr
1025
1019
  }
1026
1020
  }
1027
1021
 
1022
+ // Only what the page collected for this run: the platform's own keys
1023
+ // are filled in the runner, below every value a user saved
1024
+ // (systemEnvVars.ts header, stigmer/stigmer#1446).
1028
1025
  const env: Record<string, EnvVarInput> = {};
1029
1026
 
1030
- // System env vars first (lowest priority) — auto-resolved from
1031
- // the Stigmer client's connection context so MCP servers and
1032
- // agents can reach the backend without manual user input.
1033
- const systemVars = await resolveSystemEnvVarValues(stigmer);
1034
- Object.assign(env, systemVars);
1035
-
1036
1027
  if (
1037
1028
  agentSetup.state.status === "ready" &&
1038
1029
  agentSetup.state.resolution.mode === "oneTime"
@@ -1108,7 +1099,7 @@ const SessionComposerInner = forwardRef<SessionComposerHandle, SessionComposerPr
1108
1099
  fileRefs.clear();
1109
1100
  }
1110
1101
  },
1111
- [onSubmit, effective, stigmer, agentSetup.state, mcpSetup.pendingRuntimeEnv, sessionVariables, enableAttachments, attachments, personalEnv, showInteractionModePicker, interactionMode],
1102
+ [onSubmit, effective, agentSetup.state, mcpSetup.pendingRuntimeEnv, sessionVariables, enableAttachments, attachments, personalEnv, showInteractionModePicker, interactionMode],
1112
1103
  );
1113
1104
 
1114
1105
  const composer = useComposer({