@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
@@ -11,11 +11,20 @@ import { ApprovalAction } from "@stigmer/protos/ai/stigmer/agentic/agentexecutio
11
11
  const mockSubmitApproval = vi.fn<(t: string, a: ApprovalAction, c?: string) => Promise<void>>();
12
12
  const mockSendFollowUp = vi.fn();
13
13
 
14
+ /** Loosely-typed conversation stub — individual tests mutate the arming and
15
+ * gate fields, and restore them in their afterEach. */
14
16
  const mockConv = {
15
- session: { spec: {} },
17
+ session: { spec: {} } as {
18
+ spec: object;
19
+ metadata?: { labels?: Record<string, string> };
20
+ },
16
21
  isLoading: false,
17
22
  completedExecutions: [] as unknown[],
18
- activeStreamExecution: null,
23
+ activeStreamExecution: null as {
24
+ spec?: { autoApproveAll?: boolean };
25
+ status?: object;
26
+ } | null,
27
+ pendingApprovals: [] as { toolCallId: string }[],
19
28
  workspaceEntries: [] as unknown[],
20
29
  submitApproval: mockSubmitApproval,
21
30
  sendFollowUp: mockSendFollowUp,
@@ -221,3 +230,168 @@ describe("useSessionPageFlow — host-set approval default (#302)", () => {
221
230
  expect(result.current.autoApproveAll).toBe(false);
222
231
  });
223
232
  });
233
+
234
+ describe("useSessionPageFlow — arming derived from the in-flight run (#816)", () => {
235
+ afterEach(() => {
236
+ mockConv.activeStreamExecution = null;
237
+ mockConv.completedExecutions = [];
238
+ vi.clearAllMocks();
239
+ });
240
+
241
+ it("reflects an active execution armed at create (launcher handoff)", () => {
242
+ mockConv.activeStreamExecution = { spec: { autoApproveAll: true }, status: {} };
243
+ const { result } = renderHook(() => useSessionPageFlow(OPTS));
244
+ expect(result.current.autoApproveAll).toBe(true);
245
+ });
246
+
247
+ it("the user's explicit OFF beats the armed run for follow-up carry", async () => {
248
+ mockConv.activeStreamExecution = { spec: { autoApproveAll: true }, status: {} };
249
+ const { result } = renderHook(() => useSessionPageFlow(OPTS));
250
+
251
+ act(() => {
252
+ result.current.setAutoApproveAll(false);
253
+ });
254
+ expect(result.current.autoApproveAll).toBe(false);
255
+
256
+ await act(async () => {
257
+ await result.current.handleSubmit("after explicit off");
258
+ });
259
+ const followUpOpts = mockSendFollowUp.mock.calls.at(-1)?.[1];
260
+ expect(followUpOpts.autoApproveAll).toBeUndefined();
261
+ });
262
+
263
+ it("never derives from PAST executions (the reset-on-reload consent contract)", () => {
264
+ // A reloaded page whose history contains an armed run must come up at
265
+ // the host default — deriving from history would make the consent
266
+ // silently survive reloads.
267
+ mockConv.completedExecutions = [{ spec: { autoApproveAll: true }, status: {} }];
268
+ const { result } = renderHook(() => useSessionPageFlow(OPTS));
269
+ expect(result.current.autoApproveAll).toBe(false);
270
+ });
271
+ });
272
+
273
+ describe("useSessionPageFlow — armed responder (#816, the walk-away scenario)", () => {
274
+ beforeEach(() => {
275
+ mockSubmitApproval.mockResolvedValue(undefined);
276
+ });
277
+ afterEach(() => {
278
+ mockConv.pendingApprovals = [];
279
+ mockConv.activeStreamExecution = null;
280
+ mockConv.session = { spec: {} };
281
+ vi.clearAllMocks();
282
+ });
283
+
284
+ it("releases a gate that appears while armed", async () => {
285
+ const { rerender } = renderHook(() => useSessionPageFlow(OPTS), {
286
+ wrapper: hostDefaultWrapper(true),
287
+ });
288
+
289
+ mockConv.pendingApprovals = [{ toolCallId: "tc1" }];
290
+ await act(async () => rerender());
291
+
292
+ expect(mockSubmitApproval).toHaveBeenCalledExactlyOnceWith(
293
+ "tc1",
294
+ ApprovalAction.APPROVE_ALL,
295
+ );
296
+ });
297
+
298
+ it("releases a gate already waiting when the toggle flips ON", async () => {
299
+ mockConv.pendingApprovals = [{ toolCallId: "tc9" }];
300
+ const { result } = renderHook(() => useSessionPageFlow(OPTS));
301
+ expect(mockSubmitApproval).not.toHaveBeenCalled();
302
+
303
+ await act(async () => {
304
+ result.current.setAutoApproveAll(true);
305
+ });
306
+
307
+ expect(mockSubmitApproval).toHaveBeenCalledExactlyOnceWith(
308
+ "tc9",
309
+ ApprovalAction.APPROVE_ALL,
310
+ );
311
+ });
312
+
313
+ it("submits once per gate across re-renders", async () => {
314
+ const { rerender } = renderHook(() => useSessionPageFlow(OPTS), {
315
+ wrapper: hostDefaultWrapper(true),
316
+ });
317
+
318
+ mockConv.pendingApprovals = [{ toolCallId: "tc1" }];
319
+ await act(async () => rerender());
320
+ await act(async () => rerender());
321
+
322
+ expect(mockSubmitApproval).toHaveBeenCalledTimes(1);
323
+ });
324
+
325
+ it("covers a LATER gate of a different class (lease scope contract)", async () => {
326
+ // A gate-time APPROVE_ALL grants a class-scoped lease, so a second gate
327
+ // of another class still parks — the responder must answer it too.
328
+ const { rerender } = renderHook(() => useSessionPageFlow(OPTS), {
329
+ wrapper: hostDefaultWrapper(true),
330
+ });
331
+
332
+ mockConv.pendingApprovals = [{ toolCallId: "tc-shell" }];
333
+ await act(async () => rerender());
334
+ mockConv.pendingApprovals = [{ toolCallId: "tc-write" }];
335
+ await act(async () => rerender());
336
+
337
+ expect(mockSubmitApproval).toHaveBeenCalledTimes(2);
338
+ expect(mockSubmitApproval).toHaveBeenLastCalledWith(
339
+ "tc-write",
340
+ ApprovalAction.APPROVE_ALL,
341
+ );
342
+ });
343
+
344
+ it("does not retry a failed submission (the card stays for manual action)", async () => {
345
+ mockSubmitApproval.mockRejectedValue(new Error("boom"));
346
+ const { rerender } = renderHook(() => useSessionPageFlow(OPTS), {
347
+ wrapper: hostDefaultWrapper(true),
348
+ });
349
+
350
+ mockConv.pendingApprovals = [{ toolCallId: "tc1" }];
351
+ await act(async () => rerender());
352
+ await act(async () => rerender());
353
+
354
+ expect(mockSubmitApproval).toHaveBeenCalledTimes(1);
355
+ });
356
+
357
+ it("stays inert while OFF", async () => {
358
+ const { rerender } = renderHook(() => useSessionPageFlow(OPTS));
359
+
360
+ mockConv.pendingApprovals = [{ toolCallId: "tc1" }];
361
+ await act(async () => rerender());
362
+
363
+ expect(mockSubmitApproval).not.toHaveBeenCalled();
364
+ });
365
+
366
+ it("never fires for a guest, even when the run itself is armed", async () => {
367
+ mockConv.activeStreamExecution = { spec: { autoApproveAll: true }, status: {} };
368
+ mockConv.pendingApprovals = [{ toolCallId: "tc1" }];
369
+ renderHook(() => useSessionPageFlow({ ...OPTS, audience: "guest" }), {
370
+ wrapper: hostDefaultWrapper(true),
371
+ });
372
+
373
+ expect(mockSubmitApproval).not.toHaveBeenCalled();
374
+ });
375
+
376
+ it("never fires for an observer", async () => {
377
+ mockConv.pendingApprovals = [{ toolCallId: "tc1" }];
378
+ renderHook(() => useSessionPageFlow({ ...OPTS, audience: "observer" }), {
379
+ wrapper: hostDefaultWrapper(true),
380
+ });
381
+
382
+ expect(mockSubmitApproval).not.toHaveBeenCalled();
383
+ });
384
+
385
+ it("never fires for a channel-origin session (read-only by construction)", async () => {
386
+ mockConv.session = {
387
+ spec: {},
388
+ metadata: { labels: { "stigmer.ai/channel-id": "ch_1" } },
389
+ };
390
+ mockConv.pendingApprovals = [{ toolCallId: "tc1" }];
391
+ renderHook(() => useSessionPageFlow(OPTS), {
392
+ wrapper: hostDefaultWrapper(true),
393
+ });
394
+
395
+ expect(mockSubmitApproval).not.toHaveBeenCalled();
396
+ });
397
+ });
@@ -39,6 +39,15 @@ export type {
39
39
  UseSessionConversationReturn,
40
40
  } from "./useSessionConversation.js";
41
41
 
42
+ export { useExportTranscript } from "./useExportTranscript.js";
43
+ export type {
44
+ UseExportTranscriptOptions,
45
+ UseExportTranscriptReturn,
46
+ } from "./useExportTranscript.js";
47
+
48
+ export { TranscriptExportMenu } from "./TranscriptExportMenu.js";
49
+ export type { TranscriptExportMenuProps } from "./TranscriptExportMenu.js";
50
+
42
51
  export { useSessionArtifacts, artifactKey } from "./useSessionArtifacts.js";
43
52
  export type {
44
53
  SessionArtifactEntry,
@@ -1,4 +1,5 @@
1
1
  import type { ServiceTierOption } from "../models/service-tier.js";
2
+ import type { ThinkingModeOption } from "../models/thinking-mode.js";
2
3
 
3
4
  /**
4
5
  * Owner-pinned execution config for a session surface
@@ -39,6 +40,14 @@ export interface SessionRunConfig {
39
40
  * pin is a host configuration error and throws at submit.
40
41
  */
41
42
  readonly serviceTier?: ServiceTierOption;
43
+ /**
44
+ * Thinking mode for every execution (stigmer/stigmer#772).
45
+ * `"enabled"` requires {@link modelName} under the same contract as
46
+ * {@link serviceTier}: thinking is a per-model capability (the server
47
+ * refuses it fail-closed for Auto), so a mode pin without a model pin
48
+ * is a host configuration error and throws at submit.
49
+ */
50
+ readonly thinkingMode?: ThinkingModeOption;
42
51
  }
43
52
 
44
53
  /**
@@ -56,4 +65,11 @@ export function assertValidRunConfig(runConfig: SessionRunConfig): void {
56
65
  + "dimension. Pin the model the surface should run.",
57
66
  );
58
67
  }
68
+ if (runConfig.thinkingMode === "enabled" && !runConfig.modelName) {
69
+ throw new Error(
70
+ "SessionRunConfig: thinkingMode \"enabled\" requires modelName — "
71
+ + "thinking is a per-model capability and Auto (no pinned model) has "
72
+ + "no variant dimensions. Pin the model the surface should run.",
73
+ );
74
+ }
59
75
  }
@@ -0,0 +1,171 @@
1
+ "use client";
2
+
3
+ import { useCallback, useMemo, useState } from "react";
4
+ import {
5
+ fetchSessionTranscript,
6
+ resolvedSubject,
7
+ transcriptToJson,
8
+ transcriptToMarkdown,
9
+ type SessionTranscript,
10
+ } from "@stigmer/sdk";
11
+ import { toast } from "../feedback/toast.js";
12
+ import { useStigmer } from "../hooks.js";
13
+ import { downloadTextFile } from "../internal/download.js";
14
+ import { generateSlug } from "../internal/slug.js";
15
+ import { toError } from "../internal/toError.js";
16
+
17
+ /** Options for {@link useExportTranscript}. */
18
+ export interface UseExportTranscriptOptions {
19
+ /**
20
+ * Keep turns replaced by edit-and-resubmit in the export, marked as
21
+ * superseded. Defaults to `false` — the conversation as the viewer
22
+ * shows it.
23
+ */
24
+ readonly includeSuperseded?: boolean;
25
+ }
26
+
27
+ /** Return value of {@link useExportTranscript}. */
28
+ export interface UseExportTranscriptReturn {
29
+ /** Copy the whole conversation as Markdown to the clipboard. */
30
+ readonly copyMarkdown: () => Promise<void>;
31
+ /** Download the whole conversation as a Markdown file. */
32
+ readonly downloadMarkdown: () => Promise<void>;
33
+ /** Download the whole conversation as a structured JSON file. */
34
+ readonly downloadJson: () => Promise<void>;
35
+ /** `true` while a transcript fetch (and offload resolution) is in flight. */
36
+ readonly isExporting: boolean;
37
+ /** The last export failure, or `null`. Cleared when a new export starts. */
38
+ readonly error: Error | null;
39
+ }
40
+
41
+ /**
42
+ * Behavior hook that exports a session's whole conversation — thinking,
43
+ * tool calls with offloaded outputs resolved, sub-agent turns, timestamps —
44
+ * as Markdown (copy or download) or structured JSON (stigmer/stigmer#814).
45
+ *
46
+ * Lazy: nothing is fetched until an action is invoked. Each action fetches
47
+ * the canonical transcript via `fetchSessionTranscript` (the SDK's one
48
+ * authoritative assembly), so every export surface produces identical
49
+ * content. Feedback follows the `useExportResource` convention: toasts for
50
+ * success/failure, plus a typed `error` for hosts that render their own.
51
+ *
52
+ * @example
53
+ * ```tsx
54
+ * const exporter = useExportTranscript(sessionId);
55
+ *
56
+ * <ActionMenu.Item onSelect={exporter.copyMarkdown}>Copy transcript</ActionMenu.Item>
57
+ * <ActionMenu.Item onSelect={exporter.downloadMarkdown}>Download .md</ActionMenu.Item>
58
+ * <ActionMenu.Item onSelect={exporter.downloadJson}>Download .json</ActionMenu.Item>
59
+ * ```
60
+ */
61
+ export function useExportTranscript(
62
+ sessionId: string | null,
63
+ options?: UseExportTranscriptOptions,
64
+ ): UseExportTranscriptReturn {
65
+ const stigmer = useStigmer();
66
+ const includeSuperseded = options?.includeSuperseded === true;
67
+ const [isExporting, setIsExporting] = useState(false);
68
+ const [error, setError] = useState<Error | null>(null);
69
+
70
+ const fetchTranscript = useCallback((): Promise<SessionTranscript> => {
71
+ if (!sessionId) {
72
+ return Promise.reject(
73
+ new Error(
74
+ "Cannot export a transcript without a session id. Pass the session's " +
75
+ "id to useExportTranscript once the session exists.",
76
+ ),
77
+ );
78
+ }
79
+ return fetchSessionTranscript(stigmer, sessionId, { includeSuperseded });
80
+ }, [stigmer, sessionId, includeSuperseded]);
81
+
82
+ const copyMarkdown = useCallback(async (): Promise<void> => {
83
+ setError(null);
84
+ setIsExporting(true);
85
+ const markdown = fetchTranscript().then(
86
+ (t) => transcriptToMarkdown(t, { generatedAt: new Date().toISOString() }),
87
+ );
88
+ try {
89
+ // The transcript is fetched before it can be copied, and WebKit (the
90
+ // desktop app's webview) only honors clipboard writes inside the user
91
+ // gesture's task — a plain writeText after the await is rejected
92
+ // there. ClipboardItem accepts a promise, letting the write start
93
+ // synchronously in the gesture while the content resolves.
94
+ if (typeof ClipboardItem !== "undefined" && navigator.clipboard?.write) {
95
+ await navigator.clipboard.write([
96
+ new ClipboardItem({
97
+ "text/plain": markdown.then(
98
+ (text) => new Blob([text], { type: "text/plain" }),
99
+ ),
100
+ }),
101
+ ]);
102
+ } else {
103
+ await navigator.clipboard.writeText(await markdown);
104
+ }
105
+ toast.success("Transcript copied to clipboard");
106
+ } catch (e) {
107
+ const err = toError(e);
108
+ setError(err);
109
+ toast.error(`Couldn't copy the transcript: ${err.message}`);
110
+ } finally {
111
+ setIsExporting(false);
112
+ }
113
+ }, [fetchTranscript]);
114
+
115
+ const download = useCallback(
116
+ async (serialize: (t: SessionTranscript) => { content: string; extension: string; mimeType: string }): Promise<void> => {
117
+ setError(null);
118
+ setIsExporting(true);
119
+ try {
120
+ const transcript = await fetchTranscript();
121
+ const { content, extension, mimeType } = serialize(transcript);
122
+ downloadTextFile(
123
+ content,
124
+ `${transcriptFilename(transcript)}.${extension}`,
125
+ mimeType,
126
+ );
127
+ } catch (e) {
128
+ const err = toError(e);
129
+ setError(err);
130
+ toast.error(`Couldn't export the transcript: ${err.message}`);
131
+ } finally {
132
+ setIsExporting(false);
133
+ }
134
+ },
135
+ [fetchTranscript],
136
+ );
137
+
138
+ const downloadMarkdown = useCallback(
139
+ () =>
140
+ download((t) => ({
141
+ content: transcriptToMarkdown(t, {
142
+ generatedAt: new Date().toISOString(),
143
+ }),
144
+ extension: "md",
145
+ mimeType: "text/markdown",
146
+ })),
147
+ [download],
148
+ );
149
+
150
+ const downloadJson = useCallback(
151
+ () =>
152
+ download((t) => ({
153
+ content: JSON.stringify(transcriptToJson(t), null, 2),
154
+ extension: "json",
155
+ mimeType: "application/json",
156
+ })),
157
+ [download],
158
+ );
159
+
160
+ return useMemo(
161
+ () => ({ copyMarkdown, downloadMarkdown, downloadJson, isExporting, error }),
162
+ [copyMarkdown, downloadMarkdown, downloadJson, isExporting, error],
163
+ );
164
+ }
165
+
166
+ /** `<subject-slug>-transcript`, falling back to the session id. */
167
+ function transcriptFilename(transcript: SessionTranscript): string {
168
+ const subject = resolvedSubject(transcript.session.spec?.subject);
169
+ const slug = subject ? generateSlug(subject).slice(0, 60) : "";
170
+ return `${slug || transcript.session.metadata?.id || "session"}-transcript`;
171
+ }
@@ -218,6 +218,18 @@ export interface UseNewSessionFlowReturn {
218
218
  /** Session variables (per-execution secrets) manager. */
219
219
  readonly sessionVariables: UseSessionVariablesReturn;
220
220
 
221
+ /**
222
+ * Pre-arm "auto-approve tool calls" for the session this surface will
223
+ * create (stigmer/stigmer#816, the walk-away scenario). Seeded from the
224
+ * host's `StigmerProvider` `approvalDefaults` (#302, guests excluded);
225
+ * the user's toggle wins from then on. Carried into the bootstrap create
226
+ * as `spec.auto_approve_all` — the whole-run server-side bypass — so the
227
+ * first run stays covered even if the user closes the tab.
228
+ */
229
+ readonly autoApproveAll: boolean;
230
+ /** Set the user's explicit auto-approve choice for the created session. */
231
+ readonly setAutoApproveAll: (value: boolean) => void;
232
+
221
233
  /** `true` while the create session + execution flow is in flight. */
222
234
  readonly isSubmitting: boolean;
223
235
  /** Human-readable error from the last failed submission, or `null`. */
@@ -307,16 +319,22 @@ export function useNewSessionFlow(
307
319
  if (runConfig) assertValidRunConfig(runConfig);
308
320
  const pinnedModelName = runConfig?.modelName;
309
321
  const pinnedServiceTier = runConfig?.serviceTier;
322
+ const pinnedThinkingMode = runConfig?.thinkingMode;
310
323
  const contextTarget = useExecutionTarget();
311
324
  const executionTarget = options.executionTarget ?? contextTarget;
312
325
  const adapter = useRunnerAdapter();
313
- // Host approval default (#302): pre-arm auto_approve_all on the bootstrap
314
- // create when the provider says so. Guests never inherit it — a share-link
315
- // visitor is not the operator the host's trust judgment covers (the
316
- // GUEST_HARNESS fixed-platform-policy reasoning).
326
+ // Auto-approve for the session about to be created (#816): the host
327
+ // approval default (#302) seeds the INITIAL state only; the user's
328
+ // composer toggle wins from then on. Guests never inherit the default
329
+ // and never see the toggle — a share-link visitor is not the operator
330
+ // the host's trust judgment covers (the GUEST_HARNESS
331
+ // fixed-platform-policy reasoning). Carried into the bootstrap create as
332
+ // spec.auto_approve_all, the whole-run server-side bypass — the created
333
+ // run stays covered even if this tab dies before its first gate.
317
334
  const approvalDefaults = useApprovalDefaults();
318
- const autoApproveAll =
319
- (!isGuest && approvalDefaults?.autoApproveAll) || undefined;
335
+ const [autoApproveAll, setAutoApproveAll] = useState(
336
+ () => !isGuest && (approvalDefaults?.autoApproveAll ?? false),
337
+ );
320
338
 
321
339
  const [harness, setHarnessRaw] = useState<HarnessOption>(() => {
322
340
  // Guests get the fixed platform policy (see GUEST_HARNESS) and never
@@ -490,8 +508,14 @@ export function useNewSessionFlow(
490
508
  serviceTier: pinnedModelName
491
509
  ? (pinnedServiceTier === "fast" ? "fast" : undefined)
492
510
  : context?.serviceTier,
511
+ // The tier's #772 twin, same pin-wins + only-explicit-enabled rule.
512
+ thinkingMode: pinnedModelName
513
+ ? (pinnedThinkingMode === "enabled" ? "enabled" : undefined)
514
+ : context?.thinkingMode,
493
515
  workspaceFileRefs: context?.workspaceFileRefs,
494
- autoApproveAll,
516
+ // Only an armed state travels; false stays off the wire (the
517
+ // serviceTier/thinkingMode only-explicit discipline).
518
+ autoApproveAll: autoApproveAll || undefined,
495
519
  };
496
520
 
497
521
  // Resolve which agent the bootstrapped session runs against: an
@@ -584,6 +608,7 @@ export function useNewSessionFlow(
584
608
  effectiveModelId,
585
609
  pinnedModelName,
586
610
  pinnedServiceTier,
611
+ pinnedThinkingMode,
587
612
  workspace,
588
613
  mcpServerUsages,
589
614
  skillRefs,
@@ -618,6 +643,8 @@ export function useNewSessionFlow(
618
643
  setSkillRefs,
619
644
  workspace,
620
645
  sessionVariables,
646
+ autoApproveAll,
647
+ setAutoApproveAll,
621
648
  isSubmitting,
622
649
  submitError,
623
650
  submit,
@@ -9,15 +9,18 @@ import type { Session } from "@stigmer/protos/ai/stigmer/agentic/session/v1/api_
9
9
  import type { McpServerUsage as ProtoMcpServerUsage } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/spec_pb";
10
10
  import type { WorkspaceEntry as ProtoWorkspaceEntry } from "@stigmer/protos/ai/stigmer/agentic/session/v1/workspace_pb";
11
11
  import type { ApiResourceReference as ProtoApiResourceReference } from "@stigmer/protos/ai/stigmer/commons/apiresource/io_pb";
12
- import type {
13
- AttachmentInput,
14
- EnvVarInput,
15
- McpServerUsageInput,
16
- ResourceRef,
17
- WorkspaceEntryInput,
12
+ import {
13
+ supersededExecutionIds,
14
+ toSessionUpdateInput,
15
+ type AttachmentInput,
16
+ type EnvVarInput,
17
+ type McpServerUsageInput,
18
+ type ResourceRef,
19
+ type WorkspaceEntryInput,
18
20
  } from "@stigmer/sdk";
19
21
  import { isTerminalPhase } from "../execution/execution-phases.js";
20
22
  import type { ServiceTierOption } from "../models/service-tier.js";
23
+ import type { ThinkingModeOption } from "../models/thinking-mode.js";
21
24
  import { useStigmer } from "../hooks.js";
22
25
  import { toError } from "../internal/toError.js";
23
26
  import { useConversationStoreRef } from "../internal/store/index.js";
@@ -32,11 +35,6 @@ import { useSession } from "./useSession.js";
32
35
  import { useSessionExecutions } from "./useSessionExecutions.js";
33
36
  import { useUpdateSession } from "./useUpdateSession.js";
34
37
  import { useLocalSessionWorker } from "./useLocalSessionWorker.js";
35
- import {
36
- specWorkspaceToInput,
37
- specMcpUsagesToInput,
38
- specSkillRefsToInput,
39
- } from "./session-spec-converters.js";
40
38
 
41
39
  /**
42
40
  * Cadence for re-discovering the session's executions while the live stream
@@ -111,6 +109,16 @@ export interface SendFollowUpOptions {
111
109
  * @see {@link SharedAgentExecutionFields.serviceTier}
112
110
  */
113
111
  readonly serviceTier?: ServiceTierOption;
112
+ /**
113
+ * Thinking mode for this execution's model calls (stigmer/stigmer#772).
114
+ *
115
+ * Only set when the user actively selected `"enabled"`; `undefined`
116
+ * preserves the unspecified-vs-explicit distinction (unset resolves to
117
+ * disabled on the platform side — never the provider account default).
118
+ *
119
+ * @see {@link SharedAgentExecutionFields.thinkingMode}
120
+ */
121
+ readonly thinkingMode?: ThinkingModeOption;
114
122
  /**
115
123
  * Marks this execution as a Build-from-plan turn.
116
124
  *
@@ -537,13 +545,11 @@ export function useSessionConversation(
537
545
  }
538
546
  }, [activeExecutionId, stream.phase, refetch]);
539
547
 
540
- // Executions replaced via edit-and-resubmit. The successor carries
541
- // `spec.supersedes_execution_id`; hiding the superseded turn makes the
542
- // edited message read as a single corrected exchange (in-place replace).
543
- // The link must also be read off the live stream copy — right after a
544
- // resubmit, the successor streams before the list refetch delivers it.
545
- // Display-only: the raw `executions` list (and therefore active-id
546
- // resolution above) is never filtered.
548
+ // Executions replaced via edit-and-resubmit (the shared conversation rule —
549
+ // see supersededExecutionIds in @stigmer/sdk for the why). The extra id is
550
+ // the live stream copy's link: right after a resubmit, the successor streams
551
+ // before the list refetch delivers it. Display-only: the raw `executions`
552
+ // list (and therefore active-id resolution above) is never filtered.
547
553
  //
548
554
  // Dep is the scalar link, NOT stream.execution: the stream object changes
549
555
  // reference every frame, and rebuilding the Set per frame would hand
@@ -551,15 +557,10 @@ export function useSessionConversation(
551
557
  // memoization (DD-010).
552
558
  const streamSupersededId =
553
559
  stream.execution?.spec?.supersedesExecutionId || null;
554
- const supersededIds = useMemo(() => {
555
- const ids = new Set<string>();
556
- for (const e of executions) {
557
- const superseded = e.spec?.supersedesExecutionId;
558
- if (superseded) ids.add(superseded);
559
- }
560
- if (streamSupersededId) ids.add(streamSupersededId);
561
- return ids;
562
- }, [executions, streamSupersededId]);
560
+ const supersededIds = useMemo(
561
+ () => supersededExecutionIds(executions, streamSupersededId),
562
+ [executions, streamSupersededId],
563
+ );
563
564
 
564
565
  const completedExecutions = useMemo(() => {
565
566
  return executions.filter((e) => {
@@ -674,6 +675,7 @@ export function useSessionConversation(
674
675
  attachments: options?.attachments,
675
676
  interactionMode: options?.interactionMode,
676
677
  serviceTier: options?.serviceTier,
678
+ thinkingMode: options?.thinkingMode,
677
679
  buildFromPlan: options?.buildFromPlan,
678
680
  autoApproveAll: options?.autoApproveAll,
679
681
  workspaceFileRefs: options?.workspaceFileRefs,
@@ -818,13 +820,13 @@ export function useSessionConversation(
818
820
  // ---------------------------------------------------------------------------
819
821
 
820
822
  /**
821
- * Converts the existing Session proto into a SessionInput suitable for
822
- * the update RPC (which uses replace semantics — the full spec is sent).
823
- *
824
- * For each session-level collection (workspace, MCP servers, skills):
825
- * if an override is provided, it replaces the existing value; otherwise
826
- * the current session value is preserved by converting it back to input
827
- * format.
823
+ * Builds the SessionInput for the update RPC (which uses replace
824
+ * semantics — the full spec is sent) by spreading the SDK's complete
825
+ * generated mapper and overriding only the session-level collections the
826
+ * caller provided. An override replaces the existing value; an omitted
827
+ * field keeps the fetched session's value (an empty-array override
828
+ * clears the collection normalized to absent, the mappers' canonical
829
+ * shape).
828
830
  */
829
831
  function buildUpdateInput(
830
832
  session: Session,
@@ -835,30 +837,14 @@ function buildUpdateInput(
835
837
  skillRefs?: ResourceRef[];
836
838
  },
837
839
  ) {
838
- const meta = session.metadata!;
839
- const spec = session.spec;
840
-
841
- const workspaceEntries =
842
- overrides.workspaceEntries ?? specWorkspaceToInput(spec);
843
-
844
- const mcpServerUsages =
845
- overrides.mcpServerUsages ?? specMcpUsagesToInput(spec);
846
-
847
- const skillRefs = overrides.skillRefs ?? specSkillRefsToInput(spec);
840
+ const mapped = toSessionUpdateInput(session);
841
+ const workspaceEntries = overrides.workspaceEntries ?? mapped.workspaceEntries;
842
+ const mcpServerUsages = overrides.mcpServerUsages ?? mapped.mcpServerUsages;
843
+ const skillRefs = overrides.skillRefs ?? mapped.skillRefs;
848
844
 
849
845
  return {
850
- name: meta.name,
851
- org: meta.org,
852
- agentInstanceId: overrides.agentInstanceId ?? (spec?.agentInstanceId || undefined),
853
- subject: spec?.subject || undefined,
854
- harnessStateId: spec?.harnessStateId || undefined,
855
- harness: spec?.harness,
856
- cursorMode: spec?.cursorMode,
857
- executionTarget: spec?.executionTarget,
858
- metadata:
859
- spec?.metadata && Object.keys(spec.metadata).length > 0
860
- ? { ...spec.metadata }
861
- : undefined,
846
+ ...mapped,
847
+ agentInstanceId: overrides.agentInstanceId ?? mapped.agentInstanceId,
862
848
  workspaceEntries: workspaceEntries?.length ? workspaceEntries : undefined,
863
849
  mcpServerUsages: mcpServerUsages?.length ? mcpServerUsages : undefined,
864
850
  skillRefs: skillRefs?.length ? skillRefs : undefined,