@stigmer/react 3.12.5 → 3.12.7

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 (329) 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/channel/AgentChannelsPanel.d.ts.map +1 -1
  15. package/channel/AgentChannelsPanel.js +10 -5
  16. package/channel/AgentChannelsPanel.js.map +1 -1
  17. package/channel/ChannelRunConfigDialog.d.ts +51 -0
  18. package/channel/ChannelRunConfigDialog.d.ts.map +1 -0
  19. package/channel/ChannelRunConfigDialog.js +121 -0
  20. package/channel/ChannelRunConfigDialog.js.map +1 -0
  21. package/channel/index.d.ts +2 -0
  22. package/channel/index.d.ts.map +1 -1
  23. package/channel/index.js +1 -0
  24. package/channel/index.js.map +1 -1
  25. package/channel/useSaveAgentChannel.d.ts.map +1 -1
  26. package/channel/useSaveAgentChannel.js +6 -3
  27. package/channel/useSaveAgentChannel.js.map +1 -1
  28. package/channel-app/ChannelAppDetailPanel.js +9 -9
  29. package/channel-app/ChannelAppDetailPanel.js.map +1 -1
  30. package/composer/ComposerToolbar.d.ts +24 -1
  31. package/composer/ComposerToolbar.d.ts.map +1 -1
  32. package/composer/ComposerToolbar.js +7 -2
  33. package/composer/ComposerToolbar.js.map +1 -1
  34. package/composer/SessionComposer.d.ts +19 -0
  35. package/composer/SessionComposer.d.ts.map +1 -1
  36. package/composer/SessionComposer.js +24 -5
  37. package/composer/SessionComposer.js.map +1 -1
  38. package/composer/index.d.ts +1 -0
  39. package/composer/index.d.ts.map +1 -1
  40. package/composer/index.js.map +1 -1
  41. package/cursor-accounts/CursorAccountsConsole.d.ts.map +1 -1
  42. package/cursor-accounts/CursorAccountsConsole.js +12 -3
  43. package/cursor-accounts/CursorAccountsConsole.js.map +1 -1
  44. package/cursor-accounts/cursor-account-coverage.d.ts +30 -1
  45. package/cursor-accounts/cursor-account-coverage.d.ts.map +1 -1
  46. package/cursor-accounts/cursor-account-coverage.js +21 -0
  47. package/cursor-accounts/cursor-account-coverage.js.map +1 -1
  48. package/execution/MessageThread.d.ts.map +1 -1
  49. package/execution/MessageThread.js +8 -11
  50. package/execution/MessageThread.js.map +1 -1
  51. package/execution/useCreateAgentExecution.d.ts +15 -0
  52. package/execution/useCreateAgentExecution.d.ts.map +1 -1
  53. package/execution/useCreateAgentExecution.js +5 -0
  54. package/execution/useCreateAgentExecution.js.map +1 -1
  55. package/execution/useFileChangeContent.d.ts +2 -6
  56. package/execution/useFileChangeContent.d.ts.map +1 -1
  57. package/execution/useFileChangeContent.js +4 -12
  58. package/execution/useFileChangeContent.js.map +1 -1
  59. package/identity-account/AccountPreferencesPanel.d.ts +2 -1
  60. package/identity-account/AccountPreferencesPanel.d.ts.map +1 -1
  61. package/identity-account/AccountPreferencesPanel.js +33 -18
  62. package/identity-account/AccountPreferencesPanel.js.map +1 -1
  63. package/identity-provider/IdentityProviderDetailPanel.d.ts.map +1 -1
  64. package/identity-provider/IdentityProviderDetailPanel.js +16 -15
  65. package/identity-provider/IdentityProviderDetailPanel.js.map +1 -1
  66. package/index.d.ts +10 -7
  67. package/index.d.ts.map +1 -1
  68. package/index.js +8 -4
  69. package/index.js.map +1 -1
  70. package/internal/download.d.ts +8 -0
  71. package/internal/download.d.ts.map +1 -0
  72. package/internal/download.js +25 -0
  73. package/internal/download.js.map +1 -0
  74. package/library/useExportResource.d.ts.map +1 -1
  75. package/library/useExportResource.js +3 -17
  76. package/library/useExportResource.js.map +1 -1
  77. package/mcp-server/McpServerDetailView.js +2 -2
  78. package/mcp-server/McpServerDetailView.js.map +1 -1
  79. package/mcp-server/index.d.ts +0 -1
  80. package/mcp-server/index.d.ts.map +1 -1
  81. package/mcp-server/index.js +0 -1
  82. package/mcp-server/index.js.map +1 -1
  83. package/mcp-server/internal/definitionDrift.d.ts +14 -12
  84. package/mcp-server/internal/definitionDrift.d.ts.map +1 -1
  85. package/mcp-server/internal/definitionDrift.js +22 -19
  86. package/mcp-server/internal/definitionDrift.js.map +1 -1
  87. package/mcp-server/useUpdateMcpServer.d.ts +3 -3
  88. package/mcp-server/useUpdateMcpServer.js +3 -3
  89. package/memory/MemoryListPanel.d.ts +35 -0
  90. package/memory/MemoryListPanel.d.ts.map +1 -0
  91. package/memory/MemoryListPanel.js +141 -0
  92. package/memory/MemoryListPanel.js.map +1 -0
  93. package/memory/index.d.ts +8 -0
  94. package/memory/index.d.ts.map +1 -0
  95. package/memory/index.js +12 -0
  96. package/memory/index.js.map +1 -0
  97. package/memory/memoryGroups.d.ts +31 -0
  98. package/memory/memoryGroups.d.ts.map +1 -0
  99. package/memory/memoryGroups.js +52 -0
  100. package/memory/memoryGroups.js.map +1 -0
  101. package/memory/useConfirmMemory.d.ts +31 -0
  102. package/memory/useConfirmMemory.d.ts.map +1 -0
  103. package/memory/useConfirmMemory.js +43 -0
  104. package/memory/useConfirmMemory.js.map +1 -0
  105. package/memory/useDeleteMemory.d.ts +30 -0
  106. package/memory/useDeleteMemory.d.ts.map +1 -0
  107. package/memory/useDeleteMemory.js +42 -0
  108. package/memory/useDeleteMemory.js.map +1 -0
  109. package/memory/useMemories.d.ts +33 -0
  110. package/memory/useMemories.d.ts.map +1 -0
  111. package/memory/useMemories.js +42 -0
  112. package/memory/useMemories.js.map +1 -0
  113. package/memory/useRejectMemory.d.ts +34 -0
  114. package/memory/useRejectMemory.d.ts.map +1 -0
  115. package/memory/useRejectMemory.js +46 -0
  116. package/memory/useRejectMemory.js.map +1 -0
  117. package/memory/useUpdateMemoryContent.d.ts +32 -0
  118. package/memory/useUpdateMemoryContent.d.ts.map +1 -0
  119. package/memory/useUpdateMemoryContent.js +48 -0
  120. package/memory/useUpdateMemoryContent.js.map +1 -0
  121. package/models/ModelSelector.d.ts +33 -1
  122. package/models/ModelSelector.d.ts.map +1 -1
  123. package/models/ModelSelector.js +37 -4
  124. package/models/ModelSelector.js.map +1 -1
  125. package/models/index.d.ts +2 -0
  126. package/models/index.d.ts.map +1 -1
  127. package/models/index.js +1 -0
  128. package/models/index.js.map +1 -1
  129. package/models/registry.d.ts +16 -0
  130. package/models/registry.d.ts.map +1 -1
  131. package/models/registry.js +13 -0
  132. package/models/registry.js.map +1 -1
  133. package/models/thinking-mode.d.ts +27 -0
  134. package/models/thinking-mode.d.ts.map +1 -0
  135. package/models/thinking-mode.js +32 -0
  136. package/models/thinking-mode.js.map +1 -0
  137. package/oauth-app/OAuthAppDetailPanel.d.ts.map +1 -1
  138. package/oauth-app/OAuthAppDetailPanel.js +21 -20
  139. package/oauth-app/OAuthAppDetailPanel.js.map +1 -1
  140. package/package.json +4 -4
  141. package/platform-client/PlatformClientDetailPanel.d.ts.map +1 -1
  142. package/platform-client/PlatformClientDetailPanel.js +12 -13
  143. package/platform-client/PlatformClientDetailPanel.js.map +1 -1
  144. package/schedule/ScheduleDetailView.d.ts.map +1 -1
  145. package/schedule/ScheduleDetailView.js +21 -10
  146. package/schedule/ScheduleDetailView.js.map +1 -1
  147. package/schedule/ScheduleForm.d.ts.map +1 -1
  148. package/schedule/ScheduleForm.js +13 -3
  149. package/schedule/ScheduleForm.js.map +1 -1
  150. package/session/NewSessionViewer.d.ts.map +1 -1
  151. package/session/NewSessionViewer.js +9 -1
  152. package/session/NewSessionViewer.js.map +1 -1
  153. package/session/SessionViewer.d.ts +14 -1
  154. package/session/SessionViewer.d.ts.map +1 -1
  155. package/session/SessionViewer.js +21 -25
  156. package/session/SessionViewer.js.map +1 -1
  157. package/session/TranscriptExportMenu.d.ts +24 -0
  158. package/session/TranscriptExportMenu.d.ts.map +1 -0
  159. package/session/TranscriptExportMenu.js +26 -0
  160. package/session/TranscriptExportMenu.js.map +1 -0
  161. package/session/index.d.ts +4 -0
  162. package/session/index.d.ts.map +1 -1
  163. package/session/index.js +2 -0
  164. package/session/index.js.map +1 -1
  165. package/session/run-config.d.ts +9 -0
  166. package/session/run-config.d.ts.map +1 -1
  167. package/session/run-config.js +5 -0
  168. package/session/run-config.js.map +1 -1
  169. package/session/useExportTranscript.d.ts +44 -0
  170. package/session/useExportTranscript.d.ts.map +1 -0
  171. package/session/useExportTranscript.js +109 -0
  172. package/session/useExportTranscript.js.map +1 -0
  173. package/session/useNewSessionFlow.d.ts +11 -0
  174. package/session/useNewSessionFlow.d.ts.map +1 -1
  175. package/session/useNewSessionFlow.js +20 -6
  176. package/session/useNewSessionFlow.js.map +1 -1
  177. package/session/useSessionConversation.d.ts +12 -1
  178. package/session/useSessionConversation.d.ts.map +1 -1
  179. package/session/useSessionConversation.js +21 -42
  180. package/session/useSessionConversation.js.map +1 -1
  181. package/session/useSessionExecutions.d.ts +2 -14
  182. package/session/useSessionExecutions.d.ts.map +1 -1
  183. package/session/useSessionExecutions.js +4 -26
  184. package/session/useSessionExecutions.js.map +1 -1
  185. package/session/useSessionPageFlow.d.ts +23 -10
  186. package/session/useSessionPageFlow.d.ts.map +1 -1
  187. package/session/useSessionPageFlow.js +75 -19
  188. package/session/useSessionPageFlow.js.map +1 -1
  189. package/session/useUpdateSession.d.ts +18 -24
  190. package/session/useUpdateSession.d.ts.map +1 -1
  191. package/session/useUpdateSession.js +18 -24
  192. package/session/useUpdateSession.js.map +1 -1
  193. package/settings/MemorySection.d.ts +12 -0
  194. package/settings/MemorySection.d.ts.map +1 -0
  195. package/settings/MemorySection.js +21 -0
  196. package/settings/MemorySection.js.map +1 -0
  197. package/settings/index.d.ts +1 -0
  198. package/settings/index.d.ts.map +1 -1
  199. package/settings/index.js +1 -0
  200. package/settings/index.js.map +1 -1
  201. package/settings/settings-nav.d.ts.map +1 -1
  202. package/settings/settings-nav.js +2 -1
  203. package/settings/settings-nav.js.map +1 -1
  204. package/src/__tests__/public-api.test.ts +21 -0
  205. package/src/agent/AgentDetailView.tsx +2 -2
  206. package/src/agent/index.ts +0 -2
  207. package/src/agent/useUpdateAgent.ts +3 -3
  208. package/src/agent-instance/AgentInstanceDetailPanel.tsx +5 -7
  209. package/src/agent-instance/__tests__/AgentInstanceDetailPanel.test.tsx +75 -0
  210. package/src/agent-instance/useUpdateAgentInstance.ts +1 -1
  211. package/src/channel/AgentChannelsPanel.tsx +26 -1
  212. package/src/channel/ChannelRunConfigDialog.tsx +351 -0
  213. package/src/channel/__tests__/ChannelRunConfigDialog.test.tsx +210 -0
  214. package/src/channel/index.ts +2 -0
  215. package/src/channel/useSaveAgentChannel.ts +6 -3
  216. package/src/channel-app/ChannelAppDetailPanel.tsx +9 -9
  217. package/src/channel-app/__tests__/channelAppWhatsApp.test.tsx +2 -0
  218. package/src/channel-app/__tests__/channelAppWipeSafety.test.tsx +87 -0
  219. package/src/composer/ComposerToolbar.tsx +67 -0
  220. package/src/composer/SessionComposer.tsx +49 -3
  221. package/src/composer/__tests__/ComposerToolbar-autoApprove.test.tsx +80 -0
  222. package/src/composer/__tests__/SessionComposer-thinkingMode.test.tsx +176 -0
  223. package/src/composer/index.ts +1 -0
  224. package/src/cursor-accounts/CursorAccountsConsole.tsx +40 -5
  225. package/src/cursor-accounts/__tests__/CursorAccountsConsole.test.tsx +32 -0
  226. package/src/cursor-accounts/__tests__/cursor-account-coverage.test.ts +58 -1
  227. package/src/cursor-accounts/cursor-account-coverage.ts +55 -0
  228. package/src/execution/MessageThread.tsx +8 -11
  229. package/src/execution/useCreateAgentExecution.ts +19 -0
  230. package/src/execution/useFileChangeContent.ts +5 -12
  231. package/src/identity-account/AccountPreferencesPanel.tsx +154 -68
  232. package/src/identity-account/__tests__/AccountPreferencesPanel.test.tsx +36 -2
  233. package/src/identity-provider/IdentityProviderDetailPanel.tsx +18 -15
  234. package/src/identity-provider/__tests__/IdentityProviderDetailPanel.test.tsx +80 -0
  235. package/src/index.ts +36 -2
  236. package/src/internal/download.ts +29 -0
  237. package/src/library/useExportResource.ts +3 -19
  238. package/src/mcp-server/McpServerDetailView.tsx +2 -2
  239. package/src/mcp-server/index.ts +0 -2
  240. package/src/mcp-server/internal/definitionDrift.ts +22 -20
  241. package/src/mcp-server/useUpdateMcpServer.ts +3 -3
  242. package/src/memory/MemoryListPanel.tsx +532 -0
  243. package/src/memory/__tests__/MemoryListPanel.test.tsx +259 -0
  244. package/src/memory/__tests__/memoryHooks.test.tsx +220 -0
  245. package/src/memory/index.ts +19 -0
  246. package/src/memory/memoryGroups.ts +64 -0
  247. package/src/memory/useConfirmMemory.ts +62 -0
  248. package/src/memory/useDeleteMemory.ts +61 -0
  249. package/src/memory/useMemories.ts +66 -0
  250. package/src/memory/useRejectMemory.ts +65 -0
  251. package/src/memory/useUpdateMemoryContent.ts +67 -0
  252. package/src/models/ModelSelector.tsx +94 -1
  253. package/src/models/index.ts +2 -0
  254. package/src/models/registry.ts +29 -0
  255. package/src/models/thinking-mode.ts +48 -0
  256. package/src/oauth-app/OAuthAppDetailPanel.tsx +23 -21
  257. package/src/oauth-app/__tests__/OAuthAppDetailPanel.test.tsx +94 -0
  258. package/src/platform-client/PlatformClientDetailPanel.tsx +14 -13
  259. package/src/platform-client/__tests__/PlatformClientDetailPanel.test.tsx +82 -0
  260. package/src/schedule/ScheduleDetailView.tsx +29 -4
  261. package/src/schedule/ScheduleForm.tsx +14 -1
  262. package/src/session/NewSessionViewer.tsx +15 -1
  263. package/src/session/SessionViewer.tsx +48 -49
  264. package/src/session/TranscriptExportMenu.tsx +78 -0
  265. package/src/session/__tests__/SessionViewer-autoApproveToggle.test.tsx +178 -0
  266. package/src/session/__tests__/SessionViewer-transcriptExport.test.tsx +151 -0
  267. package/src/session/__tests__/TranscriptExportMenu.test.tsx +97 -0
  268. package/src/session/__tests__/useExportTranscript.test.tsx +187 -0
  269. package/src/session/__tests__/useNewSessionFlow.test.tsx +33 -0
  270. package/src/session/__tests__/useSessionPageFlow.autoApprove.test.tsx +176 -2
  271. package/src/session/index.ts +9 -0
  272. package/src/session/run-config.ts +16 -0
  273. package/src/session/useExportTranscript.ts +171 -0
  274. package/src/session/useNewSessionFlow.ts +34 -7
  275. package/src/session/useSessionConversation.ts +42 -56
  276. package/src/session/useSessionExecutions.ts +5 -26
  277. package/src/session/useSessionPageFlow.ts +98 -32
  278. package/src/session/useUpdateSession.ts +18 -24
  279. package/src/settings/MemorySection.tsx +45 -0
  280. package/src/settings/index.ts +1 -0
  281. package/src/settings/settings-nav.ts +2 -0
  282. package/src/usage/useExportCSV.ts +2 -14
  283. package/src/workflow/ApprovalFormBuilder.tsx +20 -5
  284. package/src/workflow/WorkflowDetailView.tsx +2 -2
  285. package/src/workflow/__tests__/WorkflowInstanceDetailPanel.test.tsx +83 -0
  286. package/src/workflow/index.ts +0 -2
  287. package/src/workflow/instance/WorkflowInstanceDetailPanel.tsx +6 -7
  288. package/src/workflow/useUpdateWorkflow.ts +3 -3
  289. package/styles.css +1 -1
  290. package/usage/useExportCSV.d.ts.map +1 -1
  291. package/usage/useExportCSV.js +2 -13
  292. package/usage/useExportCSV.js.map +1 -1
  293. package/workflow/ApprovalFormBuilder.d.ts.map +1 -1
  294. package/workflow/ApprovalFormBuilder.js +10 -8
  295. package/workflow/ApprovalFormBuilder.js.map +1 -1
  296. package/workflow/WorkflowDetailView.js +2 -2
  297. package/workflow/WorkflowDetailView.js.map +1 -1
  298. package/workflow/index.d.ts +0 -1
  299. package/workflow/index.d.ts.map +1 -1
  300. package/workflow/index.js +0 -1
  301. package/workflow/index.js.map +1 -1
  302. package/workflow/instance/WorkflowInstanceDetailPanel.d.ts.map +1 -1
  303. package/workflow/instance/WorkflowInstanceDetailPanel.js +6 -7
  304. package/workflow/instance/WorkflowInstanceDetailPanel.js.map +1 -1
  305. package/workflow/useUpdateWorkflow.d.ts +3 -3
  306. package/workflow/useUpdateWorkflow.js +3 -3
  307. package/agent/internal/agentToInput.d.ts +0 -15
  308. package/agent/internal/agentToInput.d.ts.map +0 -1
  309. package/agent/internal/agentToInput.js +0 -84
  310. package/agent/internal/agentToInput.js.map +0 -1
  311. package/mcp-server/internal/mcpServerToInput.d.ts +0 -12
  312. package/mcp-server/internal/mcpServerToInput.d.ts.map +0 -1
  313. package/mcp-server/internal/mcpServerToInput.js +0 -84
  314. package/mcp-server/internal/mcpServerToInput.js.map +0 -1
  315. package/session/session-spec-converters.d.ts +0 -24
  316. package/session/session-spec-converters.d.ts.map +0 -1
  317. package/session/session-spec-converters.js +0 -72
  318. package/session/session-spec-converters.js.map +0 -1
  319. package/src/agent/internal/__tests__/agentToInput.test.ts +0 -145
  320. package/src/agent/internal/agentToInput.ts +0 -102
  321. package/src/mcp-server/__tests__/mcpServerToInput.test.ts +0 -201
  322. package/src/mcp-server/internal/mcpServerToInput.ts +0 -98
  323. package/src/session/__tests__/session-spec-converters.test.ts +0 -183
  324. package/src/session/session-spec-converters.ts +0 -86
  325. package/src/workflow/internal/workflowToInput.ts +0 -96
  326. package/workflow/internal/workflowToInput.d.ts +0 -12
  327. package/workflow/internal/workflowToInput.d.ts.map +0 -1
  328. package/workflow/internal/workflowToInput.js +0 -73
  329. package/workflow/internal/workflowToInput.js.map +0 -1
@@ -3,9 +3,14 @@
3
3
  import { create } from "@bufbuild/protobuf";
4
4
  import type { AgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
5
5
  import { ListAgentExecutionsBySessionRequestSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/io_pb";
6
+ import { sortChronologically } from "@stigmer/sdk";
6
7
  import { useStigmer } from "../hooks.js";
7
8
  import { useFetch } from "../internal/useFetch.js";
8
9
 
10
+ // The ordering rule lives in @stigmer/sdk with the other shared
11
+ // conversation-assembly rules; re-exported here for existing consumers.
12
+ export { sortChronologically };
13
+
9
14
  /** Options for {@link useSessionExecutions}. */
10
15
  export interface UseSessionExecutionsOptions {
11
16
  /**
@@ -71,32 +76,6 @@ export interface UseSessionExecutionsReturn {
71
76
  * const { executions } = useSessionExecutions(sessionId ?? null);
72
77
  * ```
73
78
  */
74
- /**
75
- * Returns the executions in chronological (oldest-first) order — the order
76
- * the conversation thread renders top-to-bottom.
77
- *
78
- * Defense-in-depth against an unordered list response: the executions ARE the
79
- * transcript, so a scrambled order drops the newest turns out of view (they no
80
- * longer sort to the bottom). The server orders this list, but the thread must
81
- * never depend on that alone. Resource ids are time-sortable ULIDs
82
- * (`aex_01k…`), so an ascending id sort is creation order without parsing
83
- * timestamps; entries missing an id sort last but keep a stable relative order.
84
- *
85
- * @internal Exported for testing — not part of the public API.
86
- */
87
- export function sortChronologically(
88
- executions: readonly AgentExecution[],
89
- ): AgentExecution[] {
90
- return [...executions].sort((a, b) => {
91
- const aId = a.metadata?.id ?? "";
92
- const bId = b.metadata?.id ?? "";
93
- if (aId === bId) return 0;
94
- if (!aId) return 1;
95
- if (!bId) return -1;
96
- return aId < bId ? -1 : 1;
97
- });
98
- }
99
-
100
79
  export function useSessionExecutions(
101
80
  sessionId: string | null,
102
81
  options?: UseSessionExecutionsOptions,
@@ -18,8 +18,9 @@ import { useSessionConversation, type UseSessionConversationReturn } from "./use
18
18
  import { resolveExecutionRuntimeEnv, type RuntimeEnvProvider } from "./runtime-env.js";
19
19
  import { useAgentRefFromSession } from "./useAgentRefFromSession.js";
20
20
  import { usePersistedModel, type UsePersistedModelReturn } from "./usePersistedModel.js";
21
- import { specMcpUsagesToInput, specSkillRefsToInput } from "./session-spec-converters.js";
21
+ import { toSessionUpdateInput } from "@stigmer/sdk";
22
22
  import type { SessionAudience } from "./audience.js";
23
+ import { isChannelOriginSession } from "./channelOrigin.js";
23
24
  import { assertValidRunConfig, type SessionRunConfig } from "./run-config.js";
24
25
 
25
26
  /**
@@ -140,20 +141,33 @@ export interface UseSessionPageFlowReturn {
140
141
  readonly sessionVariables: UseSessionVariablesReturn;
141
142
 
142
143
  /**
143
- * Session-scoped "auto-approve tool calls" preference.
144
+ * Session-scoped "auto-approve tool calls" state (stigmer/stigmer#816).
144
145
  *
145
- * `false` by default; a host can pre-arm it app-wide via
146
- * `StigmerProvider`'s `approvalDefaults` (#302, guests excluded). Flipped
147
- * to `true` when the user chooses "Approve & don't ask again" at an
148
- * approval gate (see {@link submitApproval}), and carried into every
149
- * subsequent follow-up via {@link handleSubmit}. Held in memory only
150
- * reset on reload / new session back to the host default, never persisted
151
- * server-side.
146
+ * `false` by default. `true` when any of its sources arms it the user's
147
+ * explicit choice always winning:
148
+ *
149
+ * 1. the user's composer toggle ({@link setAutoApproveAll});
150
+ * 2. "Approve & don't ask again" at an approval gate (see
151
+ * {@link submitApproval});
152
+ * 3. the host's app-wide `StigmerProvider` `approvalDefaults` (#302,
153
+ * guests excluded);
154
+ * 4. the active in-flight execution having been created with
155
+ * `spec.auto_approve_all` (e.g. armed on the new-session surface), so
156
+ * the state survives the launcher → session-page handoff.
157
+ *
158
+ * While `true`, follow-ups carry `auto_approve_all` ({@link handleSubmit})
159
+ * and gates appearing in the in-flight run are auto-released (never for
160
+ * guest/observer/channel-origin surfaces). Held in memory only — reset on
161
+ * reload back to the host default, never persisted server-side.
152
162
  */
153
163
  readonly autoApproveAll: boolean;
154
164
  /**
155
- * Toggle the session-scoped auto-approve preference. The reversible "Turn off"
156
- * control in the UI calls this with `false`.
165
+ * Set the user's explicit session-scoped auto-approve choice. Wired to the
166
+ * composer's always-visible toggle; an explicit `false` wins over the host
167
+ * default and over an armed in-flight run for everything the client
168
+ * controls (follow-up carry, gate auto-release) — an already-armed run's
169
+ * server-side bypass cannot be revoked mid-run, exactly as with today's
170
+ * gate-time "Approve & don't ask again".
157
171
  */
158
172
  readonly setAutoApproveAll: (value: boolean) => void;
159
173
 
@@ -267,6 +281,7 @@ export function useSessionPageFlow(
267
281
  // callback's identity must not churn on every render (DD-010).
268
282
  const pinnedModelName = runConfig?.modelName;
269
283
  const pinnedServiceTier = runConfig?.serviceTier;
284
+ const pinnedThinkingMode = runConfig?.thinkingMode;
270
285
 
271
286
  const stigmer = useStigmer();
272
287
  const conv = useSessionConversation(sessionId, org);
@@ -321,17 +336,34 @@ export function useSessionPageFlow(
321
336
  const [skillRefs, setSkillRefs] = useState<ResourceRef[]>([]);
322
337
  const initialSyncDone = useRef(false);
323
338
 
324
- // Session-scoped auto-approve. Armed either at the approval gate ("Approve &
325
- // don't ask again") or from the host's provider-level approvalDefaults
326
- // (#302) an app-level trust judgment, so it seeds the INITIAL state only;
327
- // the user's in-session "Turn off" always wins from then on. Guests never
328
- // inherit the host default (a share-link visitor is not the operator the
329
- // host's trust judgment covers). Lives only in memory for the life of this
330
- // page reset on reload re-applies the host default, never a user choice.
339
+ // Session-scoped auto-approve (#816). Derived like interactionMode above
340
+ // the user's explicit in-session decision wins, otherwise the truth is
341
+ // computed from its sources never an effect-synced copy:
342
+ //
343
+ // effective = userChoice ?? (hostSeed || runArmed)
344
+ //
345
+ // - `userChoice`: the composer toggle, or the gate-time "Approve & don't
346
+ // ask again" (which has always escalated the session preference).
347
+ // - `hostSeed`: the host's provider-level approvalDefaults (#302) — an
348
+ // app-level trust judgment. Guests never inherit it (a share-link
349
+ // visitor is not the operator the host's trust judgment covers).
350
+ // - `runArmed`: the ACTIVE execution was created with
351
+ // spec.auto_approve_all, so the toggle reflects an armed in-flight run —
352
+ // this is what carries the state across the new-session → session-page
353
+ // handoff. Deliberately derived from the active execution ONLY, never
354
+ // from history: deriving from past executions would silently survive a
355
+ // reload and change the reset-on-reload consent contract below.
356
+ //
357
+ // Lives only in memory for the life of this page — reset on reload
358
+ // re-applies the host default, never a user choice.
331
359
  const approvalDefaults = useApprovalDefaults();
332
- const [autoApproveAll, setAutoApproveAll] = useState(
333
- () => !isGuest && (approvalDefaults?.autoApproveAll ?? false),
334
- );
360
+ const [autoApproveChoice, setAutoApproveChoice] = useState<boolean | null>(null);
361
+ const hostSeed = !isGuest && (approvalDefaults?.autoApproveAll ?? false);
362
+ const runArmed = conv.activeStreamExecution?.spec?.autoApproveAll === true;
363
+ const autoApproveAll = autoApproveChoice ?? (hostSeed || runArmed);
364
+ const setAutoApproveAll = useCallback((value: boolean) => {
365
+ setAutoApproveChoice(value);
366
+ }, []);
335
367
 
336
368
  const submitApproval = useCallback<UseSessionConversationReturn["submitApproval"]>(
337
369
  async (toolCallId, action, comment) => {
@@ -340,13 +372,45 @@ export function useSessionPageFlow(
340
372
  // plane separately resolves the current execution's remaining gates and
341
373
  // the runner skips the gate for the rest of that run.
342
374
  if (action === ApprovalAction.APPROVE_ALL) {
343
- setAutoApproveAll(true);
375
+ setAutoApproveChoice(true);
344
376
  }
345
377
  await conv.submitApproval(toolCallId, action, comment);
346
378
  },
347
379
  [conv.submitApproval],
348
380
  );
349
381
 
382
+ // Armed responder (#816, the walk-away scenario): while auto-approve is ON,
383
+ // answer each approval gate as it appears in the in-flight run. This must be
384
+ // a standing responder, not a flip-time one-shot: a gate-time APPROVE_ALL
385
+ // grants a lease scoped to ONE tool class (see APPROVAL_ACTION_APPROVE_ALL
386
+ // in agentexecution enum.proto), so a later gate of a different class would
387
+ // still park the run — the responder covers each class as it surfaces, and
388
+ // every released call keeps an honest audit decision.
389
+ //
390
+ // Read-only surfaces must never auto-submit approvals on a viewer's behalf:
391
+ // the predicate mirrors SessionViewer's read-only derivation (guest audience,
392
+ // observer audience, channel-origin session) exactly.
393
+ const canRespondToGates =
394
+ !isGuest &&
395
+ options.audience !== "observer" &&
396
+ !isChannelOriginSession(conv.session);
397
+ const respondedToolCallIds = useRef<Set<string>>(new Set());
398
+ useEffect(() => {
399
+ if (!autoApproveAll || !canRespondToGates) return;
400
+ for (const approval of conv.pendingApprovals) {
401
+ const toolCallId = approval.toolCallId;
402
+ // Marked before the submit resolves so a re-render mid-flight cannot
403
+ // double-submit. A failed submission is deliberately NOT retried — the
404
+ // approval card stays for a manual decision instead of a retry storm.
405
+ if (!toolCallId || respondedToolCallIds.current.has(toolCallId)) continue;
406
+ respondedToolCallIds.current.add(toolCallId);
407
+ void conv.submitApproval(toolCallId, ApprovalAction.APPROVE_ALL).catch(() => {
408
+ // Swallowed by design (see above); the card's own error surface
409
+ // reports the failure where the user can act on it.
410
+ });
411
+ }
412
+ }, [autoApproveAll, canRespondToGates, conv.pendingApprovals, conv.submitApproval]);
413
+
350
414
  // -------------------------------------------------------------------------
351
415
  // Agent — derive from session, allow mid-session changes
352
416
  // -------------------------------------------------------------------------
@@ -401,16 +465,14 @@ export function useSessionPageFlow(
401
465
  }
402
466
  }
403
467
 
404
- // MCP server usages
405
- const mcpInputs = specMcpUsagesToInput(spec);
406
- if (mcpInputs?.length) {
407
- setMcpServerUsages(mcpInputs);
468
+ // MCP server usages and skill references, hydrated through the SDK's
469
+ // complete update-input mapper (the canonical proto → input lens).
470
+ const mapped = toSessionUpdateInput(conv.session);
471
+ if (mapped.mcpServerUsages?.length) {
472
+ setMcpServerUsages(mapped.mcpServerUsages);
408
473
  }
409
-
410
- // Skill references
411
- const skillInputs = specSkillRefsToInput(spec);
412
- if (skillInputs?.length) {
413
- setSkillRefs(skillInputs);
474
+ if (mapped.skillRefs?.length) {
475
+ setSkillRefs(mapped.skillRefs);
414
476
  }
415
477
  }, [conv.session, workspace]);
416
478
 
@@ -480,6 +542,10 @@ export function useSessionPageFlow(
480
542
  serviceTier: pinnedModelName
481
543
  ? (pinnedServiceTier === "fast" ? "fast" : undefined)
482
544
  : context?.serviceTier,
545
+ // The tier's #772 twin, same pin-wins + only-explicit-enabled rule.
546
+ thinkingMode: pinnedModelName
547
+ ? (pinnedThinkingMode === "enabled" ? "enabled" : undefined)
548
+ : context?.thinkingMode,
483
549
  buildFromPlan: context?.buildFromPlan,
484
550
  // Sourced from the session-scoped preference set at the approval gate,
485
551
  // not from the composer (the pre-arm toggle was removed).
@@ -490,7 +556,7 @@ export function useSessionPageFlow(
490
556
 
491
557
  sessionVariables.clear();
492
558
  },
493
- [conv.sendFollowUp, modelId, pinnedModelName, pinnedServiceTier, workspace, mcpServerUsages, skillRefs, sessionVariables.clear, resolution, agentRef, sessionInstanceId, stigmer, autoApproveAll, getRuntimeEnv],
559
+ [conv.sendFollowUp, modelId, pinnedModelName, pinnedServiceTier, pinnedThinkingMode, workspace, mcpServerUsages, skillRefs, sessionVariables.clear, resolution, agentRef, sessionInstanceId, stigmer, autoApproveAll, getRuntimeEnv],
494
560
  );
495
561
 
496
562
  // -------------------------------------------------------------------------
@@ -25,40 +25,34 @@ export interface UseUpdateSessionReturn {
25
25
  * by the backend to identify the resource) and all spec fields.
26
26
  *
27
27
  * **Full-replace semantics.** The backend replaces the entire spec with
28
- * what you send — there is no field mask. Round-trip every spec field
29
- * you do not intend to change, *including `metadata`*: an update built
30
- * from a reconstructed partial spec silently wipes the fields it omits
31
- * (workspace entries, MCP servers, and `stigmer.ai/*` metadata keys such
32
- * as the embedder session context). To change only the subject, prefer
33
- * the field-level `session.updateSubject()` RPC instead of this hook.
28
+ * what you send — there is no field mask. Spread the SDK's complete
29
+ * `toSessionUpdateInput()` mapper and override only the fields you
30
+ * change: an update built from a reconstructed partial spec silently
31
+ * wipes the fields it omits (workspace entries, MCP servers, and
32
+ * `stigmer.ai/*` metadata keys such as the embedder session context).
33
+ * To change only the subject, prefer the field-level
34
+ * `session.updateSubject()` RPC instead of this hook.
34
35
  *
35
36
  * Typically composed into higher-level hooks like
36
- * {@link useSessionConversation} (whose update path round-trips the
37
- * fetched spec) rather than used directly. Platform builders who need
38
- * direct session mutation can use this hook standalone.
37
+ * {@link useSessionConversation} (whose update path spreads the mapper
38
+ * over a freshly fetched session) rather than used directly. Platform
39
+ * builders who need direct session mutation can use this hook
40
+ * standalone.
39
41
  *
40
42
  * @example
41
43
  * ```tsx
44
+ * import { toSessionUpdateInput } from "@stigmer/sdk";
45
+ *
42
46
  * function TagSession({ session }: { session: Session }) {
43
47
  * const { update, isUpdating } = useUpdateSession();
44
48
  *
45
49
  * async function handleTag() {
46
- * const spec = session.spec;
50
+ * const mapped = toSessionUpdateInput(session);
47
51
  * await update({
48
- * name: session.metadata!.name,
49
- * org: session.metadata!.org,
50
- * // Full replace: carry over every fetched spec field...
51
- * agentInstanceId: spec?.agentInstanceId,
52
- * subject: spec?.subject,
53
- * harnessStateId: spec?.harnessStateId,
54
- * harness: spec?.harness,
55
- * cursorMode: spec?.cursorMode,
56
- * executionTarget: spec?.executionTarget,
57
- * // ...including the spec lists (workspaceEntries, mcpServerUsages,
58
- * // skillRefs), converted from the fetched proto exactly as
59
- * // useSessionConversation's update path does...
60
- * // ...then apply the one change on top of the carried metadata.
61
- * metadata: { ...spec?.metadata, "acme/reviewed": "true" },
52
+ * ...mapped,
53
+ * // Nested values follow the same rule: spread the mapper's
54
+ * // complete value, then apply the one change on top.
55
+ * metadata: { ...mapped.metadata, "acme/reviewed": "true" },
62
56
  * });
63
57
  * }
64
58
  *
@@ -0,0 +1,45 @@
1
+ "use client";
2
+
3
+ import { useId } from "react";
4
+ import { MemoryListPanel } from "../memory/MemoryListPanel.js";
5
+ import { useActiveOrgSlug } from "../organization/OrgProvider.js";
6
+
7
+ /**
8
+ * Settings section listing everything the platform remembers about the
9
+ * caller in the active organization.
10
+ *
11
+ * The helper copy carries the DD-006 D6 transparency statement: a
12
+ * confirmed memory is injected into the caller's future sessions and
13
+ * appears in those executions' records. Memory content everywhere else
14
+ * is subject-only — this page shows the caller their own records and
15
+ * nobody else's.
16
+ */
17
+ export function MemorySection() {
18
+ const headingId = useId();
19
+ const org = useActiveOrgSlug();
20
+
21
+ return (
22
+ <section aria-labelledby={headingId}>
23
+ <h2
24
+ id={headingId}
25
+ className="stg:text-foreground stg:mb-1 stg:text-sm stg:font-semibold"
26
+ >
27
+ Memory
28
+ </h2>
29
+ <p className="stg:text-muted-foreground stg:mb-6 stg:text-xs">
30
+ Facts agents proposed to remember about you in this organization.
31
+ Confirmed memories are shared with agents in your future sessions
32
+ and appear in those executions&apos; records. Only you can see
33
+ them here, and you can edit or delete any of them at any time.
34
+ </p>
35
+
36
+ {!org ? (
37
+ <p className="stg:text-muted-foreground stg:py-4 stg:text-center stg:text-xs">
38
+ Select an organization to view its memories.
39
+ </p>
40
+ ) : (
41
+ <MemoryListPanel org={org} />
42
+ )}
43
+ </section>
44
+ );
45
+ }
@@ -12,6 +12,7 @@ export { MembersSection } from "./MembersSection.js";
12
12
  export { OrgProfileSection } from "./OrgProfileSection.js";
13
13
  export { OrgPreferencesSection } from "./OrgPreferencesSection.js";
14
14
  export { AccountPreferencesSection } from "./AccountPreferencesSection.js";
15
+ export { MemorySection } from "./MemorySection.js";
15
16
  export { EnvironmentsSection } from "./EnvironmentsSection.js";
16
17
  export { InvitationsSection } from "./InvitationsSection.js";
17
18
  export { IdentityProvidersSection } from "./IdentityProvidersSection.js";
@@ -4,6 +4,7 @@ import {
4
4
  AppWindow,
5
5
  BarChart3,
6
6
  Box,
7
+ Brain,
7
8
  Building2,
8
9
  CreditCard,
9
10
  KeyRound,
@@ -108,6 +109,7 @@ export const SETTINGS_NAV_GROUPS: readonly SettingsNavGroup[] = [
108
109
  label: "Preferences",
109
110
  icon: UserCog,
110
111
  },
112
+ { href: "/settings/memory", label: "Memory", icon: Brain },
111
113
  ],
112
114
  },
113
115
  ];
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { useCallback, useState } from "react";
4
4
  import type { GetOrgUsageReportOutput } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/io_pb";
5
+ import { downloadTextFile } from "../internal/download.js";
5
6
 
6
7
  /** Export format for the CSV download. */
7
8
  export type ExportFormat = "daily_summary" | "model_breakdown";
@@ -41,7 +42,7 @@ export function useExportCSV(
41
42
  ? buildDailySummaryCSV(report, orgId)
42
43
  : buildModelBreakdownCSV(report, orgId);
43
44
 
44
- downloadCSV(csv, filename);
45
+ downloadTextFile(csv, filename, "text/csv");
45
46
  } finally {
46
47
  setIsExporting(false);
47
48
  }
@@ -100,16 +101,3 @@ function escapeCsvField(value: string): string {
100
101
  }
101
102
  return value;
102
103
  }
103
-
104
- function downloadCSV(csv: string, filename: string): void {
105
- const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
106
- const url = URL.createObjectURL(blob);
107
- const link = document.createElement("a");
108
- link.href = url;
109
- link.download = filename;
110
- link.style.display = "none";
111
- document.body.appendChild(link);
112
- link.click();
113
- document.body.removeChild(link);
114
- URL.revokeObjectURL(url);
115
- }
@@ -24,15 +24,17 @@ interface FormFieldEntry {
24
24
  enumValues: string;
25
25
  }
26
26
 
27
- // HUMAN_INPUT_TIMEOUT_ESCALATE is deliberately absent: the proto declares it
28
- // but no runtime exists, and the server validator refuses it at apply
29
- // (stigmer/stigmer#779). The old option also wrote an `escalation_task` field
30
- // that HumanInputTaskConfig never had, so every config it produced failed
31
- // the server's strict decode.
27
+ // ESCALATE carries the outcome-by-name contract (stigmer/stigmer#781): the
28
+ // timeout resolves to the outcome NAMED "escalate" and follows its routing,
29
+ // so the gate must declare that outcome no extra config fields (the
30
+ // pre-#779 option wrote an `escalation_task` field the proto never had).
31
+ // The builder hints when the outcome is missing; the server validator is
32
+ // the enforcement.
32
33
  const TIMEOUT_POLICY_OPTIONS = [
33
34
  { value: "HUMAN_INPUT_TIMEOUT_FAIL", label: "Fail — task errors on timeout" },
34
35
  { value: "HUMAN_INPUT_TIMEOUT_APPROVE", label: "Auto-approve — proceed as approved" },
35
36
  { value: "HUMAN_INPUT_TIMEOUT_DENY", label: "Auto-deny — proceed as denied" },
37
+ { value: "HUMAN_INPUT_TIMEOUT_ESCALATE", label: "Escalate — route via the 'escalate' outcome" },
36
38
  ] as const;
37
39
 
38
40
  const TIMEOUT_UNITS = [
@@ -111,6 +113,7 @@ export const ApprovalFormBuilder = memo(function ApprovalFormBuilder({
111
113
  <TimeoutSection
112
114
  timeout={typeof cfg.timeout === "number" ? cfg.timeout : 0}
113
115
  onTimeout={typeof cfg.on_timeout === "string" ? cfg.on_timeout : ""}
116
+ hasEscalateOutcome={extractOutcomes(cfg).some((o) => o.name === "escalate")}
114
117
  onUpdateConfig={onUpdateConfig}
115
118
  />
116
119
  <StringListSection
@@ -621,10 +624,12 @@ function FormFieldRow({
621
624
  function TimeoutSection({
622
625
  timeout,
623
626
  onTimeout,
627
+ hasEscalateOutcome,
624
628
  onUpdateConfig,
625
629
  }: {
626
630
  timeout: number;
627
631
  onTimeout: string;
632
+ hasEscalateOutcome: boolean;
628
633
  onUpdateConfig: (fieldPath: string, value: unknown) => void;
629
634
  }) {
630
635
  const bestUnit = useMemo(() => {
@@ -703,6 +708,16 @@ function TimeoutSection({
703
708
  <option key={opt.value} value={opt.value}>{opt.label}</option>
704
709
  ))}
705
710
  </select>
711
+ {onTimeout === "HUMAN_INPUT_TIMEOUT_ESCALATE" && !hasEscalateOutcome && (
712
+ <p
713
+ role="alert"
714
+ className="stg:text-[10px] stg:text-[var(--stgm-warning,#d97706)]"
715
+ >
716
+ Escalate needs an outcome named &quot;escalate&quot; with routing —
717
+ the timeout resolves to it. Add the outcome above, or the server
718
+ will reject this workflow.
719
+ </p>
720
+ )}
706
721
  </div>
707
722
  )}
708
723
 
@@ -9,7 +9,7 @@ import { ValidationState } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/
9
9
  import type { WorkflowInput } from "@stigmer/sdk";
10
10
  import { useWorkflow } from "./useWorkflow.js";
11
11
  import { useUpdateWorkflow } from "./useUpdateWorkflow.js";
12
- import { workflowToInput } from "./internal/workflowToInput.js";
12
+ import { toWorkflowUpdateInput } from "@stigmer/sdk";
13
13
  import { useWorkflowExecutionList } from "./useWorkflowExecutionList.js";
14
14
  import { useWorkflowDashboardSummary } from "./useWorkflowDashboardSummary.js";
15
15
  import { WorkflowOverviewGraph } from "./WorkflowOverviewGraph.js";
@@ -189,7 +189,7 @@ export function WorkflowDetailView({
189
189
  value: WorkflowInput[K],
190
190
  ): Promise<boolean> => {
191
191
  if (!workflow) return false;
192
- const input = workflowToInput(workflow);
192
+ const input = toWorkflowUpdateInput(workflow);
193
193
  (input as unknown as Record<string, unknown>)[field] = value;
194
194
  try {
195
195
  const updated = await update(input);
@@ -0,0 +1,83 @@
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
+ WorkflowInstanceSchema,
6
+ type WorkflowInstance,
7
+ } from "@stigmer/protos/ai/stigmer/agentic/workflowinstance/v1/api_pb";
8
+ import { WorkflowExecutionVisibility } from "@stigmer/protos/ai/stigmer/agentic/workflowinstance/v1/spec_pb";
9
+ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
10
+ import type { WorkflowInstanceInput } from "@stigmer/sdk";
11
+ import { StigmerContext } from "../../context";
12
+ import { WorkflowInstanceDetailPanel } from "../instance/WorkflowInstanceDetailPanel";
13
+
14
+ /**
15
+ * Regression suite for the full-spec-replace wipe bug: before the
16
+ * generated-mapper migration, saving environment edits in this panel
17
+ * silently reset `execution_visibility` (run observability, which has its
18
+ * own dedicated updateExecutionVisibility RPC and control). The panel
19
+ * must spread `toWorkflowInstanceUpdateInput` and override only the
20
+ * environment refs.
21
+ */
22
+
23
+ const INSTANCE: WorkflowInstance = create(WorkflowInstanceSchema, {
24
+ metadata: {
25
+ id: "wfi-1",
26
+ name: "Nightly Triage",
27
+ slug: "nightly-triage",
28
+ org: "acme",
29
+ },
30
+ spec: {
31
+ workflowId: "wf-1",
32
+ description: "Runs every night.",
33
+ environmentRefs: [
34
+ { org: "acme", slug: "prod", kind: ApiResourceKind.environment },
35
+ ],
36
+ executionVisibility: WorkflowExecutionVisibility.organization,
37
+ },
38
+ });
39
+
40
+ function renderPanel(update: ReturnType<typeof vi.fn>) {
41
+ const client = {
42
+ workflowInstance: { update },
43
+ iamPolicy: {
44
+ checkMyPermission: vi.fn(async () => ({ isAuthorized: true })),
45
+ },
46
+ environment: {
47
+ list: vi.fn(async () => ({ items: [], totalCount: 0 })),
48
+ },
49
+ } as never;
50
+ return render(
51
+ <StigmerContext.Provider value={client}>
52
+ <WorkflowInstanceDetailPanel instance={INSTANCE} org="acme" onClose={() => {}} />
53
+ </StigmerContext.Provider>,
54
+ );
55
+ }
56
+
57
+ afterEach(cleanup);
58
+
59
+ describe("WorkflowInstanceDetailPanel save payload", () => {
60
+ it("round-trips execution_visibility on an environments-only save (the wipe bug)", async () => {
61
+ const update = vi.fn(async (_input: WorkflowInstanceInput) => INSTANCE);
62
+ renderPanel(update);
63
+
64
+ fireEvent.click(await screen.findByRole("button", { name: "Edit" }));
65
+ fireEvent.click(screen.getByRole("button", { name: "Save" }));
66
+
67
+ await waitFor(() => expect(update).toHaveBeenCalledTimes(1));
68
+ const input = update.mock.calls[0]![0];
69
+
70
+ // The wipe-bug guard: run visibility is edited elsewhere and must
71
+ // survive an environment save untouched.
72
+ expect(input.executionVisibility).toBe(
73
+ WorkflowExecutionVisibility.organization,
74
+ );
75
+ expect(input.workflowId).toBe("wf-1");
76
+ expect(input.description).toBe("Runs every night.");
77
+ // The form-owned field.
78
+ expect(input.environmentRefs).toEqual([{ org: "acme", slug: "prod" }]);
79
+ // Addressing fields.
80
+ expect(input.org).toBe("acme");
81
+ expect(input.slug).toBe("nightly-triage");
82
+ });
83
+ });
@@ -481,8 +481,6 @@ export {
481
481
  type UseUpdateWorkflowReturn,
482
482
  } from "./useUpdateWorkflow.js";
483
483
 
484
- export { workflowToInput } from "./internal/workflowToInput.js";
485
-
486
484
  // Starter YAML template for new workflow creation
487
485
  export { STARTER_WORKFLOW_YAML } from "./starter-workflow-yaml.js";
488
486
 
@@ -9,7 +9,7 @@ import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apires
9
9
  import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
10
10
  import { WorkflowExecutionVisibility } from "@stigmer/protos/ai/stigmer/agentic/workflowinstance/v1/spec_pb";
11
11
  import type { ResourceRef } from "@stigmer/sdk";
12
- import { getUserMessage } from "@stigmer/sdk";
12
+ import { getUserMessage, toWorkflowInstanceUpdateInput } from "@stigmer/sdk";
13
13
  import { useUpdateWorkflowInstance } from "./useUpdateWorkflowInstance.js";
14
14
  import { useDeleteWorkflowInstance } from "./useDeleteWorkflowInstance.js";
15
15
  import { RunVisibilityControl } from "./RunVisibilityControl.js";
@@ -104,20 +104,19 @@ export function WorkflowInstanceDetailPanel({
104
104
 
105
105
  const handleSaveEnvs = useCallback(async () => {
106
106
  try {
107
+ // Full-spec-replace safety: spread the complete mapped input so
108
+ // unlisted spec fields (e.g. execution_visibility, which has its
109
+ // own dedicated control above) survive an environment-only save.
107
110
  await update({
108
- name: meta?.name ?? "",
109
- org: meta?.org ?? org,
110
- workflowId: spec?.workflowId ?? "",
111
- description: spec?.description,
111
+ ...toWorkflowInstanceUpdateInput(instance),
112
112
  environmentRefs: editEnvRefs,
113
- visibility: meta?.visibility,
114
113
  });
115
114
  setIsEditingEnvs(false);
116
115
  onUpdated?.();
117
116
  } catch {
118
117
  // error displayed by hook
119
118
  }
120
- }, [update, meta, spec, org, editEnvRefs, onUpdated]);
119
+ }, [update, instance, editEnvRefs, onUpdated]);
121
120
 
122
121
  const handleDelete = useCallback(async () => {
123
122
  setDeleteError(null);
@@ -23,9 +23,9 @@ export interface UseUpdateWorkflowReturn {
23
23
  *
24
24
  * Wraps `stigmer.workflow.update(input)` with loading and error state.
25
25
  * The caller must provide a **complete** `WorkflowInput` -- the backend
26
- * performs full spec replacement. Use `workflowToInput()` to reconstruct
27
- * the input from a fetched workflow, modify the desired field, and pass
28
- * the result here.
26
+ * performs full spec replacement. Use `toWorkflowUpdateInput()` (from
27
+ * `@stigmer/sdk`) to reconstruct the input from a fetched workflow,
28
+ * modify the desired field, and pass the result here.
29
29
  */
30
30
  export function useUpdateWorkflow(): UseUpdateWorkflowReturn {
31
31
  const stigmer = useStigmer();