@stigmer/react 3.12.4 → 3.12.6

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 (369) hide show
  1. package/agent/AgentDetailView.js +2 -2
  2. package/agent/AgentDetailView.js.map +1 -1
  3. package/agent/index.d.ts +0 -1
  4. package/agent/index.d.ts.map +1 -1
  5. package/agent/index.js +0 -1
  6. package/agent/index.js.map +1 -1
  7. package/agent/useUpdateAgent.d.ts +3 -3
  8. package/agent/useUpdateAgent.js +3 -3
  9. package/agent-instance/AgentInstanceDetailPanel.d.ts.map +1 -1
  10. package/agent-instance/AgentInstanceDetailPanel.js +5 -7
  11. package/agent-instance/AgentInstanceDetailPanel.js.map +1 -1
  12. package/agent-instance/useUpdateAgentInstance.d.ts +1 -1
  13. package/agent-instance/useUpdateAgentInstance.js +1 -1
  14. package/attachment/AttachmentChipList.js +16 -6
  15. package/attachment/AttachmentChipList.js.map +1 -1
  16. package/attachment/index.d.ts +1 -1
  17. package/attachment/index.d.ts.map +1 -1
  18. package/attachment/index.js.map +1 -1
  19. package/attachment/useAttachments.d.ts +51 -8
  20. package/attachment/useAttachments.d.ts.map +1 -1
  21. package/attachment/useAttachments.js +74 -3
  22. package/attachment/useAttachments.js.map +1 -1
  23. package/channel/AgentChannelsPanel.d.ts.map +1 -1
  24. package/channel/AgentChannelsPanel.js +10 -5
  25. package/channel/AgentChannelsPanel.js.map +1 -1
  26. package/channel/ChannelRunConfigDialog.d.ts +51 -0
  27. package/channel/ChannelRunConfigDialog.d.ts.map +1 -0
  28. package/channel/ChannelRunConfigDialog.js +121 -0
  29. package/channel/ChannelRunConfigDialog.js.map +1 -0
  30. package/channel/index.d.ts +2 -0
  31. package/channel/index.d.ts.map +1 -1
  32. package/channel/index.js +1 -0
  33. package/channel/index.js.map +1 -1
  34. package/channel/useSaveAgentChannel.d.ts.map +1 -1
  35. package/channel/useSaveAgentChannel.js +6 -3
  36. package/channel/useSaveAgentChannel.js.map +1 -1
  37. package/channel-app/ChannelAppDetailPanel.js +9 -9
  38. package/channel-app/ChannelAppDetailPanel.js.map +1 -1
  39. package/composer/ComposerToolbar.d.ts +6 -1
  40. package/composer/ComposerToolbar.d.ts.map +1 -1
  41. package/composer/ComposerToolbar.js +2 -2
  42. package/composer/ComposerToolbar.js.map +1 -1
  43. package/composer/SessionComposer.d.ts +9 -0
  44. package/composer/SessionComposer.d.ts.map +1 -1
  45. package/composer/SessionComposer.js +39 -13
  46. package/composer/SessionComposer.js.map +1 -1
  47. package/conversation/ConversationComposer.d.ts +19 -3
  48. package/conversation/ConversationComposer.d.ts.map +1 -1
  49. package/conversation/ConversationComposer.js +4 -4
  50. package/conversation/ConversationComposer.js.map +1 -1
  51. package/conversation/ConversationTemplatePickerDialog.d.ts +63 -0
  52. package/conversation/ConversationTemplatePickerDialog.d.ts.map +1 -0
  53. package/conversation/ConversationTemplatePickerDialog.js +151 -0
  54. package/conversation/ConversationTemplatePickerDialog.js.map +1 -0
  55. package/conversation/ConversationsWorkbench.d.ts.map +1 -1
  56. package/conversation/ConversationsWorkbench.js +16 -5
  57. package/conversation/ConversationsWorkbench.js.map +1 -1
  58. package/conversation/index.d.ts +3 -1
  59. package/conversation/index.d.ts.map +1 -1
  60. package/conversation/index.js +1 -0
  61. package/conversation/index.js.map +1 -1
  62. package/conversation/useConversationParticipation.d.ts +41 -4
  63. package/conversation/useConversationParticipation.d.ts.map +1 -1
  64. package/conversation/useConversationParticipation.js +13 -2
  65. package/conversation/useConversationParticipation.js.map +1 -1
  66. package/cursor-accounts/CursorAccountsConsole.d.ts.map +1 -1
  67. package/cursor-accounts/CursorAccountsConsole.js +12 -3
  68. package/cursor-accounts/CursorAccountsConsole.js.map +1 -1
  69. package/cursor-accounts/cursor-account-coverage.d.ts +30 -1
  70. package/cursor-accounts/cursor-account-coverage.d.ts.map +1 -1
  71. package/cursor-accounts/cursor-account-coverage.js +21 -0
  72. package/cursor-accounts/cursor-account-coverage.js.map +1 -1
  73. package/execution/useCreateAgentExecution.d.ts +15 -0
  74. package/execution/useCreateAgentExecution.d.ts.map +1 -1
  75. package/execution/useCreateAgentExecution.js +5 -0
  76. package/execution/useCreateAgentExecution.js.map +1 -1
  77. package/identity-account/AccountPreferencesPanel.d.ts +36 -0
  78. package/identity-account/AccountPreferencesPanel.d.ts.map +1 -0
  79. package/identity-account/AccountPreferencesPanel.js +202 -0
  80. package/identity-account/AccountPreferencesPanel.js.map +1 -0
  81. package/identity-account/index.d.ts +8 -0
  82. package/identity-account/index.d.ts.map +1 -1
  83. package/identity-account/index.js +4 -0
  84. package/identity-account/index.js.map +1 -1
  85. package/identity-account/useAccountExecutionDefaults.d.ts +36 -0
  86. package/identity-account/useAccountExecutionDefaults.d.ts.map +1 -0
  87. package/identity-account/useAccountExecutionDefaults.js +46 -0
  88. package/identity-account/useAccountExecutionDefaults.js.map +1 -0
  89. package/identity-account/useMyIdentityAccount.d.ts +55 -0
  90. package/identity-account/useMyIdentityAccount.d.ts.map +1 -0
  91. package/identity-account/useMyIdentityAccount.js +37 -0
  92. package/identity-account/useMyIdentityAccount.js.map +1 -0
  93. package/identity-account/useUpdateIdentityAccount.d.ts +42 -0
  94. package/identity-account/useUpdateIdentityAccount.d.ts.map +1 -0
  95. package/identity-account/useUpdateIdentityAccount.js +53 -0
  96. package/identity-account/useUpdateIdentityAccount.js.map +1 -0
  97. package/identity-provider/IdentityProviderDetailPanel.d.ts.map +1 -1
  98. package/identity-provider/IdentityProviderDetailPanel.js +16 -15
  99. package/identity-provider/IdentityProviderDetailPanel.js.map +1 -1
  100. package/index.d.ts +15 -13
  101. package/index.d.ts.map +1 -1
  102. package/index.js +11 -8
  103. package/index.js.map +1 -1
  104. package/internal/StandingContextField.d.ts +37 -0
  105. package/internal/StandingContextField.d.ts.map +1 -0
  106. package/internal/StandingContextField.js +24 -0
  107. package/internal/StandingContextField.js.map +1 -0
  108. package/library/InstanceVisibilitySelector.d.ts.map +1 -1
  109. package/library/InstanceVisibilitySelector.js +9 -2
  110. package/library/InstanceVisibilitySelector.js.map +1 -1
  111. package/library/ResourceVisibilityControl.d.ts +13 -3
  112. package/library/ResourceVisibilityControl.d.ts.map +1 -1
  113. package/library/ResourceVisibilityControl.js +33 -5
  114. package/library/ResourceVisibilityControl.js.map +1 -1
  115. package/library/VisibilityOptionRow.d.ts +6 -0
  116. package/library/VisibilityOptionRow.d.ts.map +1 -1
  117. package/library/VisibilityOptionRow.js +11 -1
  118. package/library/VisibilityOptionRow.js.map +1 -1
  119. package/library/VisibilitySelector.d.ts.map +1 -1
  120. package/library/VisibilitySelector.js +14 -2
  121. package/library/VisibilitySelector.js.map +1 -1
  122. package/library/index.d.ts +4 -2
  123. package/library/index.d.ts.map +1 -1
  124. package/library/index.js +2 -1
  125. package/library/index.js.map +1 -1
  126. package/library/useCanSetPublicVisibility.d.ts +30 -0
  127. package/library/useCanSetPublicVisibility.d.ts.map +1 -0
  128. package/library/useCanSetPublicVisibility.js +41 -0
  129. package/library/useCanSetPublicVisibility.js.map +1 -0
  130. package/library/visibilityLevels.d.ts +32 -1
  131. package/library/visibilityLevels.d.ts.map +1 -1
  132. package/library/visibilityLevels.js +34 -14
  133. package/library/visibilityLevels.js.map +1 -1
  134. package/mcp-server/McpServerDetailView.js +2 -2
  135. package/mcp-server/McpServerDetailView.js.map +1 -1
  136. package/mcp-server/index.d.ts +0 -1
  137. package/mcp-server/index.d.ts.map +1 -1
  138. package/mcp-server/index.js +0 -1
  139. package/mcp-server/index.js.map +1 -1
  140. package/mcp-server/internal/definitionDrift.d.ts +14 -12
  141. package/mcp-server/internal/definitionDrift.d.ts.map +1 -1
  142. package/mcp-server/internal/definitionDrift.js +22 -19
  143. package/mcp-server/internal/definitionDrift.js.map +1 -1
  144. package/mcp-server/useMcpServerConnect.d.ts +8 -5
  145. package/mcp-server/useMcpServerConnect.d.ts.map +1 -1
  146. package/mcp-server/useMcpServerConnect.js +2 -1
  147. package/mcp-server/useMcpServerConnect.js.map +1 -1
  148. package/mcp-server/useMcpServerOAuthConnect.d.ts.map +1 -1
  149. package/mcp-server/useMcpServerOAuthConnect.js +7 -2
  150. package/mcp-server/useMcpServerOAuthConnect.js.map +1 -1
  151. package/mcp-server/useUpdateMcpServer.d.ts +3 -3
  152. package/mcp-server/useUpdateMcpServer.js +3 -3
  153. package/models/ModelSelector.d.ts +33 -1
  154. package/models/ModelSelector.d.ts.map +1 -1
  155. package/models/ModelSelector.js +37 -4
  156. package/models/ModelSelector.js.map +1 -1
  157. package/models/index.d.ts +2 -0
  158. package/models/index.d.ts.map +1 -1
  159. package/models/index.js +1 -0
  160. package/models/index.js.map +1 -1
  161. package/models/registry.d.ts +16 -0
  162. package/models/registry.d.ts.map +1 -1
  163. package/models/registry.js +13 -0
  164. package/models/registry.js.map +1 -1
  165. package/models/thinking-mode.d.ts +27 -0
  166. package/models/thinking-mode.d.ts.map +1 -0
  167. package/models/thinking-mode.js +32 -0
  168. package/models/thinking-mode.js.map +1 -0
  169. package/oauth-app/CreateOAuthAppForm.d.ts +2 -2
  170. package/oauth-app/CreateOAuthAppForm.d.ts.map +1 -1
  171. package/oauth-app/CreateOAuthAppForm.js +13 -4
  172. package/oauth-app/CreateOAuthAppForm.js.map +1 -1
  173. package/oauth-app/OAuthAppDetailPanel.d.ts.map +1 -1
  174. package/oauth-app/OAuthAppDetailPanel.js +50 -22
  175. package/oauth-app/OAuthAppDetailPanel.js.map +1 -1
  176. package/organization/OrgPreferencesPanel.d.ts +32 -0
  177. package/organization/OrgPreferencesPanel.d.ts.map +1 -0
  178. package/organization/OrgPreferencesPanel.js +90 -0
  179. package/organization/OrgPreferencesPanel.js.map +1 -0
  180. package/organization/OrgProfilePanel.d.ts.map +1 -1
  181. package/organization/OrgProfilePanel.js +5 -4
  182. package/organization/OrgProfilePanel.js.map +1 -1
  183. package/organization/index.d.ts +2 -0
  184. package/organization/index.d.ts.map +1 -1
  185. package/organization/index.js +1 -0
  186. package/organization/index.js.map +1 -1
  187. package/package.json +4 -4
  188. package/platform-client/PlatformClientDetailPanel.d.ts.map +1 -1
  189. package/platform-client/PlatformClientDetailPanel.js +12 -13
  190. package/platform-client/PlatformClientDetailPanel.js.map +1 -1
  191. package/schedule/ScheduleDetailView.d.ts.map +1 -1
  192. package/schedule/ScheduleDetailView.js +21 -10
  193. package/schedule/ScheduleDetailView.js.map +1 -1
  194. package/schedule/ScheduleForm.d.ts.map +1 -1
  195. package/schedule/ScheduleForm.js +13 -3
  196. package/schedule/ScheduleForm.js.map +1 -1
  197. package/session/NewSessionViewer.d.ts +10 -1
  198. package/session/NewSessionViewer.d.ts.map +1 -1
  199. package/session/NewSessionViewer.js +2 -1
  200. package/session/NewSessionViewer.js.map +1 -1
  201. package/session/run-config.d.ts +9 -0
  202. package/session/run-config.d.ts.map +1 -1
  203. package/session/run-config.js +5 -0
  204. package/session/run-config.js.map +1 -1
  205. package/session/useNewSessionFlow.d.ts +27 -1
  206. package/session/useNewSessionFlow.d.ts.map +1 -1
  207. package/session/useNewSessionFlow.js +51 -8
  208. package/session/useNewSessionFlow.js.map +1 -1
  209. package/session/useSessionConversation.d.ts +12 -1
  210. package/session/useSessionConversation.d.ts.map +1 -1
  211. package/session/useSessionConversation.js +15 -24
  212. package/session/useSessionConversation.js.map +1 -1
  213. package/session/useSessionPageFlow.d.ts.map +1 -1
  214. package/session/useSessionPageFlow.js +14 -10
  215. package/session/useSessionPageFlow.js.map +1 -1
  216. package/session/useUpdateSession.d.ts +18 -24
  217. package/session/useUpdateSession.d.ts.map +1 -1
  218. package/session/useUpdateSession.js +18 -24
  219. package/session/useUpdateSession.js.map +1 -1
  220. package/settings/AccountPreferencesSection.d.ts +3 -0
  221. package/settings/AccountPreferencesSection.d.ts.map +1 -0
  222. package/settings/AccountPreferencesSection.js +10 -0
  223. package/settings/AccountPreferencesSection.js.map +1 -0
  224. package/settings/OrgPreferencesSection.d.ts +3 -0
  225. package/settings/OrgPreferencesSection.d.ts.map +1 -0
  226. package/settings/OrgPreferencesSection.js +13 -0
  227. package/settings/OrgPreferencesSection.js.map +1 -0
  228. package/settings/index.d.ts +2 -0
  229. package/settings/index.d.ts.map +1 -1
  230. package/settings/index.js +2 -0
  231. package/settings/index.js.map +1 -1
  232. package/settings/settings-nav.d.ts.map +1 -1
  233. package/settings/settings-nav.js +17 -1
  234. package/settings/settings-nav.js.map +1 -1
  235. package/src/agent/AgentDetailView.tsx +2 -2
  236. package/src/agent/index.ts +0 -2
  237. package/src/agent/useUpdateAgent.ts +3 -3
  238. package/src/agent-instance/AgentInstanceDetailPanel.tsx +5 -7
  239. package/src/agent-instance/__tests__/AgentInstanceDetailPanel.test.tsx +75 -0
  240. package/src/agent-instance/useUpdateAgentInstance.ts +1 -1
  241. package/src/attachment/AttachmentChipList.tsx +19 -8
  242. package/src/attachment/__tests__/AttachmentChipList.test.tsx +12 -0
  243. package/src/attachment/__tests__/useAttachments.test.ts +293 -0
  244. package/src/attachment/index.ts +1 -0
  245. package/src/attachment/useAttachments.ts +143 -11
  246. package/src/channel/AgentChannelsPanel.tsx +26 -1
  247. package/src/channel/ChannelRunConfigDialog.tsx +351 -0
  248. package/src/channel/__tests__/ChannelRunConfigDialog.test.tsx +210 -0
  249. package/src/channel/index.ts +2 -0
  250. package/src/channel/useSaveAgentChannel.ts +6 -3
  251. package/src/channel-app/ChannelAppDetailPanel.tsx +9 -9
  252. package/src/channel-app/__tests__/channelAppWhatsApp.test.tsx +2 -0
  253. package/src/channel-app/__tests__/channelAppWipeSafety.test.tsx +87 -0
  254. package/src/composer/ComposerToolbar.tsx +9 -0
  255. package/src/composer/SessionComposer.tsx +59 -16
  256. package/src/composer/__tests__/SessionComposer-thinkingMode.test.tsx +176 -0
  257. package/src/conversation/ConversationComposer.tsx +38 -4
  258. package/src/conversation/ConversationTemplatePickerDialog.tsx +441 -0
  259. package/src/conversation/ConversationsWorkbench.tsx +40 -4
  260. package/src/conversation/__tests__/ConversationComposer.test.tsx +56 -1
  261. package/src/conversation/__tests__/ConversationTemplatePickerDialog.test.tsx +224 -0
  262. package/src/conversation/__tests__/useConversationParticipation.test.tsx +45 -4
  263. package/src/conversation/index.ts +4 -0
  264. package/src/conversation/useConversationParticipation.ts +58 -6
  265. package/src/cursor-accounts/CursorAccountsConsole.tsx +40 -5
  266. package/src/cursor-accounts/__tests__/CursorAccountsConsole.test.tsx +32 -0
  267. package/src/cursor-accounts/__tests__/cursor-account-coverage.test.ts +58 -1
  268. package/src/cursor-accounts/cursor-account-coverage.ts +55 -0
  269. package/src/execution/useCreateAgentExecution.ts +19 -0
  270. package/src/identity-account/AccountPreferencesPanel.tsx +532 -0
  271. package/src/identity-account/__tests__/AccountPreferencesPanel.test.tsx +267 -0
  272. package/src/identity-account/__tests__/useAccountExecutionDefaults.test.tsx +116 -0
  273. package/src/identity-account/__tests__/useMyIdentityAccount.test.tsx +121 -0
  274. package/src/identity-account/index.ts +11 -0
  275. package/src/identity-account/useAccountExecutionDefaults.ts +63 -0
  276. package/src/identity-account/useMyIdentityAccount.ts +75 -0
  277. package/src/identity-account/useUpdateIdentityAccount.ts +73 -0
  278. package/src/identity-provider/IdentityProviderDetailPanel.tsx +18 -15
  279. package/src/identity-provider/__tests__/IdentityProviderDetailPanel.test.tsx +80 -0
  280. package/src/index.ts +26 -4
  281. package/src/internal/StandingContextField.tsx +92 -0
  282. package/src/library/InstanceVisibilitySelector.tsx +9 -2
  283. package/src/library/ResourceVisibilityControl.tsx +46 -13
  284. package/src/library/VisibilityOptionRow.tsx +28 -6
  285. package/src/library/VisibilitySelector.tsx +14 -2
  286. package/src/library/__tests__/VisibilitySelector.test.tsx +48 -1
  287. package/src/library/__tests__/visibilityLevels.test.ts +67 -1
  288. package/src/library/index.ts +6 -1
  289. package/src/library/useCanSetPublicVisibility.ts +56 -0
  290. package/src/library/visibilityLevels.ts +65 -14
  291. package/src/mcp-server/McpServerDetailView.tsx +2 -2
  292. package/src/mcp-server/__tests__/useMcpServerConnect.asyncLane.test.tsx +117 -0
  293. package/src/mcp-server/index.ts +0 -2
  294. package/src/mcp-server/internal/definitionDrift.ts +22 -20
  295. package/src/mcp-server/useMcpServerConnect.ts +9 -6
  296. package/src/mcp-server/useMcpServerOAuthConnect.ts +7 -2
  297. package/src/mcp-server/useUpdateMcpServer.ts +3 -3
  298. package/src/models/ModelSelector.tsx +94 -1
  299. package/src/models/index.ts +2 -0
  300. package/src/models/registry.ts +29 -0
  301. package/src/models/thinking-mode.ts +48 -0
  302. package/src/oauth-app/CreateOAuthAppForm.tsx +58 -3
  303. package/src/oauth-app/OAuthAppDetailPanel.tsx +103 -20
  304. package/src/oauth-app/__tests__/OAuthAppDetailPanel.test.tsx +94 -0
  305. package/src/organization/OrgPreferencesPanel.tsx +221 -0
  306. package/src/organization/OrgProfilePanel.tsx +5 -4
  307. package/src/organization/__tests__/OrgPreferencesPanel.test.tsx +145 -0
  308. package/src/organization/__tests__/OrgProfilePanel.test.tsx +108 -0
  309. package/src/organization/index.ts +2 -0
  310. package/src/platform-client/PlatformClientDetailPanel.tsx +14 -13
  311. package/src/platform-client/__tests__/PlatformClientDetailPanel.test.tsx +82 -0
  312. package/src/schedule/ScheduleDetailView.tsx +29 -4
  313. package/src/schedule/ScheduleForm.tsx +14 -1
  314. package/src/session/NewSessionViewer.tsx +12 -0
  315. package/src/session/__tests__/useNewSessionFlow.test.tsx +151 -0
  316. package/src/session/run-config.ts +16 -0
  317. package/src/session/useNewSessionFlow.ts +82 -9
  318. package/src/session/useSessionConversation.ts +32 -40
  319. package/src/session/useSessionPageFlow.ts +14 -11
  320. package/src/session/useUpdateSession.ts +18 -24
  321. package/src/settings/AccountPreferencesSection.tsx +25 -0
  322. package/src/settings/OrgPreferencesSection.tsx +35 -0
  323. package/src/settings/__tests__/settings-nav.test.ts +27 -0
  324. package/src/settings/index.ts +2 -0
  325. package/src/settings/settings-nav.ts +18 -0
  326. package/src/workflow/ApprovalFormBuilder.tsx +20 -27
  327. package/src/workflow/WorkflowDetailView.tsx +2 -2
  328. package/src/workflow/__tests__/WorkflowInstanceDetailPanel.test.tsx +83 -0
  329. package/src/workflow/index.ts +0 -2
  330. package/src/workflow/instance/WorkflowInstanceDetailPanel.tsx +6 -7
  331. package/src/workflow/useUpdateWorkflow.ts +3 -3
  332. package/styles.css +1 -1
  333. package/workflow/ApprovalFormBuilder.d.ts.map +1 -1
  334. package/workflow/ApprovalFormBuilder.js +10 -5
  335. package/workflow/ApprovalFormBuilder.js.map +1 -1
  336. package/workflow/WorkflowDetailView.js +2 -2
  337. package/workflow/WorkflowDetailView.js.map +1 -1
  338. package/workflow/index.d.ts +0 -1
  339. package/workflow/index.d.ts.map +1 -1
  340. package/workflow/index.js +0 -1
  341. package/workflow/index.js.map +1 -1
  342. package/workflow/instance/WorkflowInstanceDetailPanel.d.ts.map +1 -1
  343. package/workflow/instance/WorkflowInstanceDetailPanel.js +6 -7
  344. package/workflow/instance/WorkflowInstanceDetailPanel.js.map +1 -1
  345. package/workflow/useUpdateWorkflow.d.ts +3 -3
  346. package/workflow/useUpdateWorkflow.js +3 -3
  347. package/agent/internal/agentToInput.d.ts +0 -15
  348. package/agent/internal/agentToInput.d.ts.map +0 -1
  349. package/agent/internal/agentToInput.js +0 -84
  350. package/agent/internal/agentToInput.js.map +0 -1
  351. package/mcp-server/internal/mcpServerToInput.d.ts +0 -12
  352. package/mcp-server/internal/mcpServerToInput.d.ts.map +0 -1
  353. package/mcp-server/internal/mcpServerToInput.js +0 -84
  354. package/mcp-server/internal/mcpServerToInput.js.map +0 -1
  355. package/session/session-spec-converters.d.ts +0 -24
  356. package/session/session-spec-converters.d.ts.map +0 -1
  357. package/session/session-spec-converters.js +0 -72
  358. package/session/session-spec-converters.js.map +0 -1
  359. package/src/agent/internal/__tests__/agentToInput.test.ts +0 -145
  360. package/src/agent/internal/agentToInput.ts +0 -102
  361. package/src/mcp-server/__tests__/mcpServerToInput.test.ts +0 -201
  362. package/src/mcp-server/internal/mcpServerToInput.ts +0 -98
  363. package/src/session/__tests__/session-spec-converters.test.ts +0 -183
  364. package/src/session/session-spec-converters.ts +0 -86
  365. package/src/workflow/internal/workflowToInput.ts +0 -96
  366. package/workflow/internal/workflowToInput.d.ts +0 -12
  367. package/workflow/internal/workflowToInput.d.ts.map +0 -1
  368. package/workflow/internal/workflowToInput.js +0 -73
  369. package/workflow/internal/workflowToInput.js.map +0 -1
@@ -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,
@@ -0,0 +1,145 @@
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
+ OrganizationSchema,
6
+ type Organization,
7
+ } from "@stigmer/protos/ai/stigmer/tenancy/organization/v1/api_pb";
8
+ import type { OrganizationInput } from "@stigmer/sdk";
9
+ import { StigmerContext } from "../../context";
10
+ import { DeploymentModeContext } from "../../deployment-mode";
11
+ import { OrgPreferencesPanel } from "../OrgPreferencesPanel";
12
+
13
+ const ORG: Organization = create(OrganizationSchema, {
14
+ metadata: { id: "acme", name: "Acme Corp", slug: "acme", org: "acme" },
15
+ spec: {
16
+ description: "We make everything.",
17
+ logoUrl: "https://acme.example/logo.png",
18
+ preferences: { standingContext: "We deploy to us-east-1." },
19
+ },
20
+ });
21
+
22
+ function createMockStigmer(overrides?: {
23
+ update?: ReturnType<typeof vi.fn>;
24
+ canEdit?: boolean;
25
+ }) {
26
+ return {
27
+ organization: {
28
+ get: vi.fn(async () => ORG),
29
+ update: overrides?.update ?? vi.fn(async () => ORG),
30
+ },
31
+ iamPolicy: {
32
+ checkMyPermission: vi.fn(async () => ({
33
+ isAuthorized: overrides?.canEdit ?? true,
34
+ })),
35
+ },
36
+ } as never;
37
+ }
38
+
39
+ function renderPanel(client: unknown) {
40
+ return render(
41
+ <StigmerContext.Provider value={client as never}>
42
+ <DeploymentModeContext.Provider value="local">
43
+ <OrgPreferencesPanel orgId="acme" />
44
+ </DeploymentModeContext.Provider>
45
+ </StigmerContext.Provider>,
46
+ );
47
+ }
48
+
49
+ afterEach(cleanup);
50
+
51
+ /**
52
+ * Waits until the form has synced from server data (the panel copies the
53
+ * fetched value into local state in a passive effect, one flush after the
54
+ * field first renders) — interacting earlier races the sync.
55
+ */
56
+ async function findSyncedField(value: string) {
57
+ const field = await screen.findByLabelText("Standing context");
58
+ await waitFor(() => expect(field).toHaveProperty("value", value));
59
+ return field;
60
+ }
61
+
62
+ describe("OrgPreferencesPanel", () => {
63
+ it("loads and displays the declared standing context", async () => {
64
+ renderPanel(createMockStigmer());
65
+
66
+ await findSyncedField("We deploy to us-east-1.");
67
+ });
68
+
69
+ it("saves the full mapped input — unedited profile fields survive (wipe-bug guard)", async () => {
70
+ const update = vi.fn(async (_input: OrganizationInput) => ORG);
71
+ renderPanel(createMockStigmer({ update }));
72
+
73
+ const field = await findSyncedField("We deploy to us-east-1.");
74
+ fireEvent.change(field, { target: { value: "Prefer terse answers." } });
75
+ fireEvent.click(screen.getByRole("button", { name: "Save changes" }));
76
+
77
+ await waitFor(() => expect(update).toHaveBeenCalledTimes(1));
78
+ const input = update.mock.calls[0]![0];
79
+
80
+ expect(input.preferences).toEqual({
81
+ standingContext: "Prefer terse answers.",
82
+ });
83
+ // Fields this form never renders must round-trip untouched.
84
+ expect(input.name).toBe("Acme Corp");
85
+ expect(input.description).toBe("We make everything.");
86
+ expect(input.logoUrl).toBe("https://acme.example/logo.png");
87
+ expect(input.org).toBe("acme");
88
+ expect(input.slug).toBe("acme");
89
+ });
90
+
91
+ it("clears the standing context when the text is emptied", async () => {
92
+ const update = vi.fn(async (_input: OrganizationInput) => ORG);
93
+ renderPanel(createMockStigmer({ update }));
94
+
95
+ const field = await findSyncedField("We deploy to us-east-1.");
96
+ fireEvent.change(field, { target: { value: "" } });
97
+ fireEvent.click(screen.getByRole("button", { name: "Save changes" }));
98
+
99
+ await waitFor(() => expect(update).toHaveBeenCalledTimes(1));
100
+ expect(update.mock.calls[0]![0].preferences).toEqual({
101
+ standingContext: undefined,
102
+ });
103
+ });
104
+
105
+ it("discard resets the field to the server value", async () => {
106
+ renderPanel(createMockStigmer());
107
+
108
+ const field = await findSyncedField("We deploy to us-east-1.");
109
+ fireEvent.change(field, { target: { value: "Edited but regretted" } });
110
+ fireEvent.click(screen.getByRole("button", { name: "Discard" }));
111
+
112
+ expect(field).toHaveProperty("value", "We deploy to us-east-1.");
113
+ });
114
+
115
+ it("renders read-only with an explanation when the caller lacks can_edit", async () => {
116
+ renderPanel(createMockStigmer({ canEdit: false }));
117
+
118
+ const field = await screen.findByLabelText("Standing context");
119
+ await waitFor(() =>
120
+ expect(field).toHaveProperty("readOnly", true),
121
+ );
122
+ expect(
123
+ screen.getByText("Only organization admins can edit these preferences."),
124
+ ).toBeTruthy();
125
+ expect(screen.queryByRole("button", { name: "Save changes" })).toBeNull();
126
+ });
127
+
128
+ it("shows the fetch error with a retry action when loading fails", async () => {
129
+ const client = {
130
+ organization: {
131
+ get: vi.fn(async () => {
132
+ throw new Error("boom");
133
+ }),
134
+ update: vi.fn(),
135
+ },
136
+ iamPolicy: {
137
+ checkMyPermission: vi.fn(async () => ({ isAuthorized: true })),
138
+ },
139
+ } as never;
140
+ renderPanel(client);
141
+
142
+ expect(await screen.findByRole("alert")).toBeTruthy();
143
+ expect(screen.getByRole("button", { name: "Retry" })).toBeTruthy();
144
+ });
145
+ });
@@ -0,0 +1,108 @@
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
+ OrganizationSchema,
6
+ type Organization,
7
+ } from "@stigmer/protos/ai/stigmer/tenancy/organization/v1/api_pb";
8
+ import type { OrganizationInput } from "@stigmer/sdk";
9
+ import { StigmerContext } from "../../context";
10
+ import { DeploymentModeContext } from "../../deployment-mode";
11
+ import { OrgProfilePanel } from "../OrgProfilePanel";
12
+
13
+ /**
14
+ * Regression suite for the full-spec-replace wipe bug (oss#293 Phase 1):
15
+ * `organization.update()` wholesale replaces the stored spec, so a profile
16
+ * save that sends only the edited fields silently wipes every other mutable
17
+ * spec field — most visibly `spec.preferences.standing_context` set via the
18
+ * CLI or the preferences page. The panel must spread the complete mapped
19
+ * input and override only what it edits.
20
+ */
21
+
22
+ const ORG: Organization = create(OrganizationSchema, {
23
+ metadata: {
24
+ id: "acme",
25
+ name: "Acme Corp",
26
+ slug: "acme",
27
+ org: "acme",
28
+ },
29
+ spec: {
30
+ description: "We make everything.",
31
+ logoUrl: "https://acme.example/logo.png",
32
+ preferences: { standingContext: "We deploy to us-east-1." },
33
+ },
34
+ });
35
+
36
+ function createMockStigmer(overrides?: {
37
+ update?: ReturnType<typeof vi.fn>;
38
+ }) {
39
+ return {
40
+ organization: {
41
+ get: vi.fn(async () => ORG),
42
+ update: overrides?.update ?? vi.fn(async () => ORG),
43
+ },
44
+ } as never;
45
+ }
46
+
47
+ // Deployment mode "local" keeps the IdentityProvidersSummary sub-panel
48
+ // inert (identity_provider is cloud-only), so no extra client stubs are
49
+ // needed — the suite tests the form, not the summary.
50
+ function renderPanel(client: unknown) {
51
+ return render(
52
+ <StigmerContext.Provider value={client as never}>
53
+ <DeploymentModeContext.Provider value="local">
54
+ <OrgProfilePanel orgId="acme" />
55
+ </DeploymentModeContext.Provider>
56
+ </StigmerContext.Provider>,
57
+ );
58
+ }
59
+
60
+ afterEach(cleanup);
61
+
62
+ describe("OrgProfilePanel save payload", () => {
63
+ it("round-trips unedited spec fields — preferences survive a profile save", async () => {
64
+ const update = vi.fn(async (_input: OrganizationInput) => ORG);
65
+ const client = createMockStigmer({ update });
66
+ renderPanel(client);
67
+
68
+ // Wait for the server-sync effect to fill the form before editing —
69
+ // interacting on first appearance races the sync.
70
+ const description = await screen.findByLabelText("Description");
71
+ await waitFor(() =>
72
+ expect(description).toHaveProperty("value", "We make everything."),
73
+ );
74
+ fireEvent.change(description, { target: { value: "New description" } });
75
+ fireEvent.click(screen.getByRole("button", { name: "Save changes" }));
76
+
77
+ await waitFor(() => expect(update).toHaveBeenCalledTimes(1));
78
+ const input = update.mock.calls[0]![0];
79
+
80
+ // The edited field.
81
+ expect(input.description).toBe("New description");
82
+ // The unedited fields the form does not render — the wipe-bug guard.
83
+ expect(input.preferences).toEqual({
84
+ standingContext: "We deploy to us-east-1.",
85
+ });
86
+ expect(input.logoUrl).toBe("https://acme.example/logo.png");
87
+ // Addressing fields for the update pipeline's org+slug lookup.
88
+ expect(input.org).toBe("acme");
89
+ expect(input.slug).toBe("acme");
90
+ expect(input.name).toBe("Acme Corp");
91
+ });
92
+
93
+ it("sends the edited name and keeps discard/dirty semantics intact", async () => {
94
+ const update = vi.fn(async (_input: OrganizationInput) => ORG);
95
+ renderPanel(createMockStigmer({ update }));
96
+
97
+ const name = await screen.findByLabelText("Name");
98
+ await waitFor(() => expect(name).toHaveProperty("value", "Acme Corp"));
99
+ fireEvent.change(name, { target: { value: "Acme Corporation" } });
100
+ fireEvent.click(screen.getByRole("button", { name: "Save changes" }));
101
+
102
+ await waitFor(() => expect(update).toHaveBeenCalledTimes(1));
103
+ expect(update.mock.calls[0]![0].name).toBe("Acme Corporation");
104
+ expect(update.mock.calls[0]![0].preferences).toEqual({
105
+ standingContext: "We deploy to us-east-1.",
106
+ });
107
+ });
108
+ });
@@ -16,5 +16,7 @@ export { CreateOrganizationForm } from "./CreateOrganizationForm.js";
16
16
  export type { CreateOrganizationFormProps } from "./CreateOrganizationForm.js";
17
17
  export { OrgProfilePanel } from "./OrgProfilePanel.js";
18
18
  export type { OrgProfilePanelProps } from "./OrgProfilePanel.js";
19
+ export { OrgPreferencesPanel } from "./OrgPreferencesPanel.js";
20
+ export type { OrgPreferencesPanelProps } from "./OrgPreferencesPanel.js";
19
21
  export { OrgSwitcher } from "./OrgSwitcher.js";
20
22
  export type { OrgSwitcherProps } from "./OrgSwitcher.js";
@@ -3,7 +3,7 @@
3
3
  import { type FormEvent, type KeyboardEvent, useCallback, useId, useState } from "react";
4
4
  import { cn } from "@stigmer/theme";
5
5
  import { UNSTYLED_FIELDSET } from "../internal/element-resets.js";
6
- import { getUserMessage } from "@stigmer/sdk";
6
+ import { getUserMessage, toPlatformClientUpdateInput } from "@stigmer/sdk";
7
7
  import { IamRole } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
8
8
  import { timestampDate, type Timestamp } from "@bufbuild/protobuf/wkt";
9
9
  import type { PlatformClient } from "@stigmer/protos/ai/stigmer/iam/platformclient/v1/api_pb";
@@ -176,21 +176,22 @@ export function PlatformClientDetailPanel({
176
176
  e.preventDefault();
177
177
  clearUpdateError();
178
178
  try {
179
+ // Full-spec-replace safety: spread the complete mapped input and
180
+ // override only the edited fields, so unlisted spec fields (e.g.
181
+ // environment_refs, the embedded-assistant credential binding)
182
+ // survive the save. Cleared fields are set to undefined
183
+ // explicitly — omitting them would carry the stale mapped value.
179
184
  const updated = await update({
180
- name: meta?.name ?? "",
181
- slug: meta?.slug,
182
- org: meta?.org ?? "",
185
+ ...toPlatformClientUpdateInput(platformClient),
183
186
  neverExpires,
184
- ...(!neverExpires &&
185
- expiresAt && {
186
- expiresAt: new Date(expiresAt).toISOString(),
187
- }),
187
+ expiresAt:
188
+ !neverExpires && expiresAt ? new Date(expiresAt) : undefined,
188
189
  autoProvisionAccounts: autoProvision,
189
190
  autoGrantOnOrg: autoGrant,
190
- ...(autoGrant &&
191
- autoGrantRole !== IamRole.iam_role_unspecified && {
192
- autoGrantRole,
193
- }),
191
+ autoGrantRole:
192
+ autoGrant && autoGrantRole !== IamRole.iam_role_unspecified
193
+ ? autoGrantRole
194
+ : undefined,
194
195
  allowedOrigins: origins,
195
196
  });
196
197
  setMode("view");
@@ -200,7 +201,7 @@ export function PlatformClientDetailPanel({
200
201
  }
201
202
  },
202
203
  [
203
- meta,
204
+ platformClient,
204
205
  neverExpires,
205
206
  expiresAt,
206
207
  autoProvision,
@@ -0,0 +1,82 @@
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 { timestampFromDate } from "@bufbuild/protobuf/wkt";
5
+ import {
6
+ PlatformClientSchema,
7
+ type PlatformClient,
8
+ } from "@stigmer/protos/ai/stigmer/iam/platformclient/v1/api_pb";
9
+ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
10
+ import { IamRole } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
11
+ import type { PlatformClientInput } from "@stigmer/sdk";
12
+ import { StigmerContext } from "../../context";
13
+ import { PlatformClientDetailPanel } from "../PlatformClientDetailPanel";
14
+
15
+ /**
16
+ * Regression suite for the full-spec-replace wipe bug: before the
17
+ * generated-mapper migration, this panel hand-built its update input and
18
+ * NEVER sent `environment_refs` — saving any edit silently wiped the
19
+ * client's credential-delivery environment bindings. The panel must
20
+ * spread `toPlatformClientUpdateInput` and override only what it edits.
21
+ */
22
+
23
+ const PLATFORM_CLIENT: PlatformClient = create(PlatformClientSchema, {
24
+ metadata: {
25
+ id: "pc-1",
26
+ name: "Embed Client",
27
+ slug: "embed-client",
28
+ org: "acme",
29
+ },
30
+ spec: {
31
+ clientId: "client-abc",
32
+ expiresAt: timestampFromDate(new Date("2027-06-01T00:00:00Z")),
33
+ neverExpires: false,
34
+ autoProvisionAccounts: true,
35
+ autoGrantOnOrg: true,
36
+ autoGrantRole: IamRole.admin,
37
+ allowedOrigins: ["https://embed.acme.example"],
38
+ environmentRefs: [
39
+ { org: "acme", slug: "prod", kind: ApiResourceKind.environment },
40
+ ],
41
+ },
42
+ });
43
+
44
+ function renderPanel(update: ReturnType<typeof vi.fn>) {
45
+ const client = {
46
+ platformclient: { update },
47
+ } as never;
48
+ return render(
49
+ <StigmerContext.Provider value={client}>
50
+ <PlatformClientDetailPanel platformClient={PLATFORM_CLIENT} />
51
+ </StigmerContext.Provider>,
52
+ );
53
+ }
54
+
55
+ afterEach(cleanup);
56
+
57
+ describe("PlatformClientDetailPanel save payload", () => {
58
+ it("round-trips environment_refs on an origins-only edit (the wipe bug)", async () => {
59
+ const update = vi.fn(async (_input: PlatformClientInput) => PLATFORM_CLIENT);
60
+ renderPanel(update);
61
+
62
+ fireEvent.click(screen.getByRole("button", { name: "Edit" }));
63
+ fireEvent.click(screen.getByRole("button", { name: "Save changes" }));
64
+
65
+ await waitFor(() => expect(update).toHaveBeenCalledTimes(1));
66
+ const input = update.mock.calls[0]![0];
67
+
68
+ // The wipe-bug guard: the form does not render environment bindings,
69
+ // yet they must survive the save.
70
+ expect(input.environmentRefs).toEqual([
71
+ { org: "acme", slug: "prod", kind: ApiResourceKind.environment },
72
+ ]);
73
+ // Form-owned fields round-trip from the edit state.
74
+ expect(input.allowedOrigins).toEqual(["https://embed.acme.example"]);
75
+ expect(input.autoGrantRole).toBe(IamRole.admin);
76
+ expect(input.expiresAt).toBeInstanceOf(Date);
77
+ // Addressing fields for the update pipeline's org+slug lookup.
78
+ expect(input.org).toBe("acme");
79
+ expect(input.slug).toBe("embed-client");
80
+ expect(input.name).toBe("Embed Client");
81
+ });
82
+ });