@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
@@ -0,0 +1,80 @@
1
+ import { describe, it, expect, vi, afterEach } from "vitest";
2
+ import { render, screen, cleanup, fireEvent } from "@testing-library/react";
3
+ import { ComposerToolbar, type ComposerToolbarProps } from "../ComposerToolbar";
4
+
5
+ afterEach(cleanup);
6
+
7
+ function baseProps(overrides: Partial<ComposerToolbarProps> = {}): ComposerToolbarProps {
8
+ return {
9
+ disabled: false,
10
+ isSubmitting: false,
11
+ canSend: true,
12
+ onSend: vi.fn(),
13
+ showHarnessSelector: false,
14
+ harness: "native",
15
+ onHarnessChange: vi.fn(),
16
+ showInteractionModePicker: false,
17
+ interactionMode: "agent",
18
+ onInteractionModeChange: vi.fn(),
19
+ showModelSelector: false,
20
+ modelId: undefined,
21
+ onModelChange: vi.fn(),
22
+ showWorkspace: false,
23
+ workspaceCount: 0,
24
+ workspaceContent: null,
25
+ showAttach: false,
26
+ attachmentCount: 0,
27
+ onAttachClick: vi.fn(),
28
+ configureItems: [],
29
+ configOpen: false,
30
+ onConfigOpenChange: vi.fn(),
31
+ configActivePanel: null,
32
+ onConfigActivePanelChange: vi.fn(),
33
+ renderConfigPanel: () => null,
34
+ ...overrides,
35
+ };
36
+ }
37
+
38
+ describe("ComposerToolbar auto-approve toggle (#816)", () => {
39
+ it("renders nothing when the prop is omitted (DD-011 opt-in)", () => {
40
+ render(<ComposerToolbar {...baseProps()} />);
41
+ expect(screen.queryByRole("switch")).toBeNull();
42
+ expect(screen.queryByText("Auto-approve")).toBeNull();
43
+ });
44
+
45
+ it("renders a labeled switch reflecting the armed state", () => {
46
+ render(
47
+ <ComposerToolbar
48
+ {...baseProps({ autoApprove: { armed: true, onChange: vi.fn() } })}
49
+ />,
50
+ );
51
+ const toggle = screen.getByRole("switch", { name: "Auto-approve" });
52
+ expect(toggle.getAttribute("aria-checked")).toBe("true");
53
+ });
54
+
55
+ it("flipping calls onChange with the next value", () => {
56
+ const onChange = vi.fn();
57
+ render(
58
+ <ComposerToolbar
59
+ {...baseProps({ autoApprove: { armed: false, onChange } })}
60
+ />,
61
+ );
62
+ fireEvent.click(screen.getByRole("switch", { name: "Auto-approve" }));
63
+ expect(onChange).toHaveBeenCalledExactlyOnceWith(true);
64
+ });
65
+
66
+ it("stays interactive while the toolbar is disabled (the walk-away flip)", () => {
67
+ // The whole point of the toggle is arming DURING a streaming turn, when
68
+ // the rest of the composer is locked — the Stop-button exemption.
69
+ const onChange = vi.fn();
70
+ render(
71
+ <ComposerToolbar
72
+ {...baseProps({ disabled: true, autoApprove: { armed: false, onChange } })}
73
+ />,
74
+ );
75
+ const toggle = screen.getByRole("switch", { name: "Auto-approve" });
76
+ expect(toggle.hasAttribute("disabled")).toBe(false);
77
+ fireEvent.click(toggle);
78
+ expect(onChange).toHaveBeenCalledExactlyOnceWith(true);
79
+ });
80
+ });
@@ -0,0 +1,176 @@
1
+ import { describe, it, expect, vi, afterEach, beforeAll } from "vitest";
2
+ import { render, screen, cleanup, fireEvent, waitFor } from "@testing-library/react";
3
+ import { type ReactNode } from "react";
4
+ import type { Stigmer } from "@stigmer/sdk";
5
+ import { StigmerContext } from "../../context";
6
+ import { ModelRegistryContext } from "../../models/ModelRegistryContext";
7
+ import type { ModelInfo } from "../../models/registry";
8
+ import { SessionComposer } from "../SessionComposer";
9
+
10
+ // Shim ResizeObserver for Base UI's positioner (the
11
+ // scheduleCreation.test.tsx pattern).
12
+ beforeAll(() => {
13
+ if (!("ResizeObserver" in globalThis)) {
14
+ (globalThis as unknown as { ResizeObserver: unknown }).ResizeObserver =
15
+ class {
16
+ observe() {}
17
+ unobserve() {}
18
+ disconnect() {}
19
+ };
20
+ }
21
+ });
22
+
23
+ /**
24
+ * End-to-end composer wiring for the thinking mode (stigmer/stigmer#772) —
25
+ * the SessionComposer-serviceTier twin. The toolbar's ModelSelector toggle
26
+ * must reach composer state, and an active enabled selection must ride the
27
+ * submit context to execution create. This seam (SessionComposer →
28
+ * ComposerToolbar props → submit context) is exactly where the #357 tier
29
+ * toggle originally shipped dead while every layer unit-tested green.
30
+ */
31
+
32
+ const THINKING_CAPABLE: ModelInfo = {
33
+ modelId: "claude-haiku-4-5",
34
+ provider: "anthropic",
35
+ displayName: "Haiku 4.5",
36
+ shortDescription: "Fast Claude with optional thinking",
37
+ speedTier: "fast",
38
+ costTier: "economy",
39
+ harness: "cursor",
40
+ featured: true,
41
+ serviceTiers: [],
42
+ thinkingCapable: true,
43
+ };
44
+
45
+ const NOT_CAPABLE: ModelInfo = {
46
+ modelId: "composer-2.5",
47
+ provider: "cursor",
48
+ displayName: "Composer 2.5",
49
+ shortDescription: "Fast agentic model",
50
+ speedTier: "fastest",
51
+ costTier: "economy",
52
+ harness: "cursor",
53
+ featured: true,
54
+ serviceTiers: ["fast"],
55
+ thinkingCapable: false,
56
+ };
57
+
58
+ function createMinimalStigmerMock(): Stigmer {
59
+ return {
60
+ agentExecution: { uploadAttachment: vi.fn() },
61
+ environment: { getPersonal: vi.fn().mockResolvedValue(null) },
62
+ baseUrl: "http://localhost:8080",
63
+ getAuthCredential: vi.fn().mockResolvedValue("test-token"),
64
+ config: {
65
+ baseUrl: "http://localhost:8080",
66
+ getAccessToken: vi.fn().mockResolvedValue(""),
67
+ },
68
+ } as unknown as Stigmer;
69
+ }
70
+
71
+ function createWrapper(client: Stigmer) {
72
+ return function Wrapper({ children }: { children: ReactNode }) {
73
+ return (
74
+ <StigmerContext.Provider value={client}>
75
+ <ModelRegistryContext.Provider
76
+ value={{
77
+ models: [THINKING_CAPABLE, NOT_CAPABLE],
78
+ isLoading: false,
79
+ error: null,
80
+ refetch: vi.fn(),
81
+ }}
82
+ >
83
+ {children}
84
+ </ModelRegistryContext.Provider>
85
+ </StigmerContext.Provider>
86
+ );
87
+ };
88
+ }
89
+
90
+ function renderComposer(defaultModelId = "claude-haiku-4-5") {
91
+ const client = createMinimalStigmerMock();
92
+ const onSubmit = vi.fn();
93
+
94
+ const result = render(
95
+ <SessionComposer
96
+ onSubmit={onSubmit}
97
+ harness="cursor"
98
+ defaultModelId={defaultModelId}
99
+ />,
100
+ { wrapper: createWrapper(client) },
101
+ );
102
+
103
+ return { ...result, onSubmit };
104
+ }
105
+
106
+ function submitMessage(message: string) {
107
+ const textarea = screen.getByRole("textbox");
108
+ fireEvent.change(textarea, { target: { value: message } });
109
+ fireEvent.keyDown(textarea, { key: "Enter", shiftKey: false });
110
+ }
111
+
112
+ /**
113
+ * Open the model selector popover (Base UI portals mount asynchronously in
114
+ * jsdom — the switch is awaited) and flip the thinking switch in the
115
+ * options area, then close the popover (toggling keeps it open).
116
+ */
117
+ async function toggleThinking(triggerName: RegExp = /Haiku 4\.5/) {
118
+ fireEvent.click(screen.getByRole("button", { name: triggerName }));
119
+ const toggle = await screen.findByRole("switch", { name: "Thinking" });
120
+ fireEvent.click(toggle);
121
+ fireEvent.keyDown(document.activeElement ?? document.body, { key: "Escape" });
122
+ }
123
+
124
+ afterEach(cleanup);
125
+
126
+ describe("SessionComposer — thinking mode submit contract", () => {
127
+ it("renders the thinking switch through the toolbar for a capable model", async () => {
128
+ renderComposer();
129
+
130
+ fireEvent.click(screen.getByRole("button", { name: /Haiku 4\.5/ }));
131
+ expect(await screen.findByRole("switch", { name: "Thinking" })).toBeTruthy();
132
+ });
133
+
134
+ it("renders no thinking switch for a model without the capability", async () => {
135
+ renderComposer("composer-2.5");
136
+
137
+ fireEvent.click(screen.getByRole("button", { name: /Composer 2\.5/ }));
138
+ // The fast switch proves the options area itself rendered — only the
139
+ // thinking row must be absent (no dead controls).
140
+ await screen.findByRole("switch", { name: "Fast tier" });
141
+ expect(screen.queryByRole("switch", { name: "Thinking" })).toBeNull();
142
+ });
143
+
144
+ it("carries thinkingMode 'enabled' on the submit context after toggling", async () => {
145
+ const { onSubmit } = renderComposer();
146
+
147
+ await toggleThinking();
148
+ submitMessage("Think hard about this");
149
+
150
+ await waitFor(() => expect(onSubmit).toHaveBeenCalledOnce());
151
+ const context = onSubmit.mock.calls[0][2];
152
+ expect(context?.thinkingMode).toBe("enabled");
153
+ });
154
+
155
+ it("leaves thinkingMode undefined when the switch is untouched", async () => {
156
+ const { onSubmit } = renderComposer();
157
+
158
+ submitMessage("Just answer");
159
+
160
+ await waitFor(() => expect(onSubmit).toHaveBeenCalledOnce());
161
+ const context = onSubmit.mock.calls[0][2];
162
+ expect(context?.thinkingMode).toBeUndefined();
163
+ });
164
+
165
+ it("leaves thinkingMode undefined after toggling on and back off", async () => {
166
+ const { onSubmit } = renderComposer();
167
+
168
+ await toggleThinking();
169
+ await toggleThinking();
170
+ submitMessage("Changed my mind");
171
+
172
+ await waitFor(() => expect(onSubmit).toHaveBeenCalledOnce());
173
+ const context = onSubmit.mock.calls[0][2];
174
+ expect(context?.thinkingMode).toBeUndefined();
175
+ });
176
+ });
@@ -7,6 +7,7 @@ export type {
7
7
  SessionComposerProps,
8
8
  SessionComposerSubmitContext,
9
9
  } from "./SessionComposer.js";
10
+ export type { ComposerAutoApproveProps } from "./ComposerToolbar.js";
10
11
 
11
12
  export { InteractionModePicker } from "./InteractionModePicker.js";
12
13
  export type {
@@ -10,7 +10,7 @@ import { Button } from "../button/index.js";
10
10
  import { StateBadge } from "./badges.js";
11
11
  import { CursorAccountEditor } from "./CursorAccountEditor.js";
12
12
  import { CursorAccountsAccessNotice } from "./CursorAccountsAccessNotice.js";
13
- import { deriveCoverage } from "./cursor-account-coverage.js";
13
+ import { deriveCoverage, derivePoolHealth } from "./cursor-account-coverage.js";
14
14
  import { formatSyncTime } from "./cursor-account-format.js";
15
15
  import { MemberKeysPanel } from "./MemberKeysPanel.js";
16
16
  import { useCursorAccounts } from "./useCursorAccounts.js";
@@ -119,11 +119,38 @@ export function CursorAccountsConsole({ className }: CursorAccountsConsoleProps)
119
119
  }
120
120
 
121
121
  const accounts = list.accounts?.accounts ?? [];
122
+ const health = derivePoolHealth(accounts);
122
123
 
123
124
  return (
124
125
  <div className={cn("stg:space-y-3", className)}>
125
126
  <div className="stg:flex stg:items-center stg:justify-between stg:gap-3">
126
- <h3 className="stg:text-sm stg:font-semibold stg:text-foreground">Cursor accounts</h3>
127
+ <div>
128
+ <h3 className="stg:text-sm stg:font-semibold stg:text-foreground">Cursor accounts</h3>
129
+ {/* Fleet health in one line (server-computed routability): the
130
+ 2026-08-15 drain hid behind per-account enabled counts — this
131
+ is the fact the operator needed on THIS screen. */}
132
+ {accounts.length > 0 && (
133
+ <p
134
+ className={cn(
135
+ "stg:text-[11px]",
136
+ health.totalRoutableKeys === 0
137
+ ? "stg:text-destructive"
138
+ : "stg:text-muted-foreground",
139
+ )}
140
+ role={health.totalRoutableKeys === 0 ? "alert" : undefined}
141
+ >
142
+ {health.totalRoutableKeys === 0
143
+ ? "No routable execution key anywhere - executions on API-pool models are failing"
144
+ : `${health.totalRoutableKeys} routable ${health.totalRoutableKeys === 1 ? "key" : "keys"} across ${health.totalAccounts} ${health.totalAccounts === 1 ? "account" : "accounts"}`}
145
+ {health.drainedAccounts > 0
146
+ ? ` · ${health.drainedAccounts} ${health.drainedAccounts === 1 ? "account" : "accounts"} drained`
147
+ : ""}
148
+ {health.guardTrippedKeys > 0
149
+ ? ` · ${health.guardTrippedKeys} ${health.guardTrippedKeys === 1 ? "key" : "keys"} usage-drained`
150
+ : ""}
151
+ </p>
152
+ )}
153
+ </div>
127
154
  <Button size="sm" onClick={() => setFlow({ phase: "create" })}>
128
155
  Add account
129
156
  </Button>
@@ -137,7 +164,7 @@ export function CursorAccountsConsole({ className }: CursorAccountsConsoleProps)
137
164
  <span>Account</span>
138
165
  <span>Orgs</span>
139
166
  <span>Status</span>
140
- <span>Execution keys</span>
167
+ <span>Routable keys</span>
141
168
  <span className="stg:text-right">Last synced</span>
142
169
  </div>
143
170
  {accounts.length === 0 ? (
@@ -214,8 +241,16 @@ function AccountRow({
214
241
  />
215
242
  </span>
216
243
  <span>
217
- {summary.enabledKeyCount > 0 ? (
218
- <span className="stg:text-muted-foreground">{summary.enabledKeyCount}</span>
244
+ {/* Routability, not enabled-ness: what key selection would DO.
245
+ "Drained" (usage guard) is recoverable at the cycle reset;
246
+ "Not routable" (no enabled key, or owners removed) needs
247
+ operator action. */}
248
+ {summary.routableKeyCount > 0 ? (
249
+ <span className="stg:text-muted-foreground">
250
+ {summary.routableKeyCount} of {summary.enabledKeyCount}
251
+ </span>
252
+ ) : summary.guardTrippedKeyCount > 0 ? (
253
+ <StateBadge tone="warn" label="Drained" />
219
254
  ) : (
220
255
  <StateBadge tone="warn" label="Not routable" />
221
256
  )}
@@ -86,6 +86,7 @@ function scenarSummary() {
86
86
  return create(CursorAccountSummarySchema, {
87
87
  account: scenarAccount(),
88
88
  enabledKeyCount: 1,
89
+ routableKeyCount: 1,
89
90
  lastSyncedAt: timestampFromDate(new Date("2026-07-22T12:00:00Z")),
90
91
  });
91
92
  }
@@ -194,6 +195,37 @@ describe("CursorAccountsConsole", () => {
194
195
  expect(screen.getByText("empty team")).toBeTruthy();
195
196
  expect(screen.getByText("Not routable")).toBeTruthy();
196
197
  expect(screen.getByText("shared pool")).toBeTruthy();
198
+ // Routable count renders as "N of M" (server-computed routability),
199
+ // and the fleet-health line aggregates it.
200
+ expect(screen.getByText("1 of 1")).toBeTruthy();
201
+ expect(screen.getByText(/1 routable key across 2 accounts/)).toBeTruthy();
202
+ });
203
+
204
+ it("surfaces a fully guard-drained account and the fleet drain alert", async () => {
205
+ // The 2026-08-15 shape: enabled keys exist, none routable — the one
206
+ // key left is usage-guard-tripped. The row must read "Drained" (a
207
+ // cycle-reset condition), not the misleading enabled count, and with
208
+ // zero routable keys fleet-wide the health line must alarm.
209
+ const drained = create(CursorAccountSummarySchema, {
210
+ account: scenarAccount({
211
+ accountId: "acc-3",
212
+ displayName: "drained team",
213
+ orgIds: [],
214
+ }),
215
+ enabledKeyCount: 1,
216
+ routableKeyCount: 0,
217
+ guardTrippedKeyCount: 1,
218
+ });
219
+ const client = createMockStigmer({
220
+ listAccounts: vi.fn().mockResolvedValue({ accounts: [drained] }),
221
+ });
222
+ render(<CursorAccountsConsole />, { wrapper: wrapper(client) });
223
+
224
+ await waitFor(() => expect(screen.getByText("drained team")).toBeTruthy());
225
+ expect(screen.getByText("Drained")).toBeTruthy();
226
+ expect(
227
+ screen.getByText(/No routable execution key anywhere/),
228
+ ).toBeTruthy();
197
229
  });
198
230
 
199
231
  it("shows the designed access notice on PERMISSION_DENIED", async () => {
@@ -7,12 +7,14 @@ import {
7
7
  CursorTeamMemberSchema,
8
8
  } from "@stigmer/protos/ai/stigmer/platform/cursoraccount/v1/cursor_account_pb";
9
9
  import {
10
+ CursorAccountSummarySchema,
10
11
  CursorAccountViewSchema,
11
12
  CursorMemberKeyState,
12
13
  CursorMemberKeyViewSchema,
13
14
  CursorTeamMemberViewSchema,
14
15
  } from "@stigmer/protos/ai/stigmer/platform/cursoraccount/v1/io_pb";
15
- import { deriveCoverage } from "../cursor-account-coverage";
16
+ import { CursorAccountSchema } from "@stigmer/protos/ai/stigmer/platform/cursoraccount/v1/cursor_account_pb";
17
+ import { deriveCoverage, derivePoolHealth } from "../cursor-account-coverage";
16
18
 
17
19
  function keyView(keyId: string, state: CursorMemberKeyState) {
18
20
  return create(CursorMemberKeyViewSchema, {
@@ -71,3 +73,58 @@ describe("deriveCoverage", () => {
71
73
  expect(coverage.onTeamWithKey).toEqual([]);
72
74
  });
73
75
  });
76
+
77
+ describe("derivePoolHealth", () => {
78
+ function summary(opts: {
79
+ enabled?: boolean;
80
+ enabledKeyCount: number;
81
+ routableKeyCount: number;
82
+ guardTrippedKeyCount?: number;
83
+ }) {
84
+ return create(CursorAccountSummarySchema, {
85
+ account: create(CursorAccountSchema, {
86
+ accountId: "acc",
87
+ enabled: opts.enabled ?? true,
88
+ }),
89
+ enabledKeyCount: opts.enabledKeyCount,
90
+ routableKeyCount: opts.routableKeyCount,
91
+ guardTrippedKeyCount: opts.guardTrippedKeyCount ?? 0,
92
+ });
93
+ }
94
+
95
+ it("reproduces the 2026-08-15 drain as a one-line fact", () => {
96
+ // Four accounts with enabled keys but zero routable (guard-tripped or
97
+ // owner-removed), one fresh account with a single routable key — the
98
+ // fleet state the per-account enabled counts hid.
99
+ const health = derivePoolHealth([
100
+ summary({ enabledKeyCount: 1, routableKeyCount: 0, guardTrippedKeyCount: 1 }),
101
+ summary({ enabledKeyCount: 10, routableKeyCount: 0 }),
102
+ summary({ enabledKeyCount: 1, routableKeyCount: 0 }),
103
+ summary({ enabledKeyCount: 0, routableKeyCount: 0 }),
104
+ summary({ enabledKeyCount: 2, routableKeyCount: 1, guardTrippedKeyCount: 0 }),
105
+ ]);
106
+
107
+ expect(health.totalAccounts).toBe(5);
108
+ expect(health.totalRoutableKeys).toBe(1);
109
+ // The zero-key account is not "drained" — it never had capacity.
110
+ expect(health.drainedAccounts).toBe(3);
111
+ expect(health.guardTrippedKeys).toBe(1);
112
+ });
113
+
114
+ it("does not count disabled accounts as drained (disable means drain-by-choice)", () => {
115
+ const health = derivePoolHealth([
116
+ summary({ enabled: false, enabledKeyCount: 3, routableKeyCount: 0 }),
117
+ ]);
118
+
119
+ expect(health.drainedAccounts).toBe(0);
120
+ });
121
+
122
+ it("is all zeros for an empty list", () => {
123
+ const health = derivePoolHealth([]);
124
+
125
+ expect(health.totalAccounts).toBe(0);
126
+ expect(health.totalRoutableKeys).toBe(0);
127
+ expect(health.drainedAccounts).toBe(0);
128
+ expect(health.guardTrippedKeys).toBe(0);
129
+ });
130
+ });
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  CursorMemberKeyState,
3
+ type CursorAccountSummary,
3
4
  type CursorAccountView,
4
5
  type CursorMemberKeyView,
5
6
  type CursorTeamMemberView,
@@ -69,3 +70,57 @@ export function deriveCoverage(view: CursorAccountView): CursorAccountCoverage {
69
70
  unclassified: [],
70
71
  };
71
72
  }
73
+
74
+ /**
75
+ * Fleet-wide health rolled up from the list summaries — the console's
76
+ * answer to "can the platform serve right now, and from where?". Pure
77
+ * aggregation of the server-computed routable/guard counts (the same
78
+ * routability rule key selection uses); this module must never
79
+ * re-derive routability client-side.
80
+ *
81
+ * <p>Why this exists: in the 2026-08-15 pool drain, the list showed
82
+ * five accounts with healthy-looking enabled-key counts while exactly
83
+ * one guard-tripped key could serve — the operator had to open every
84
+ * account detail to see it. The rollup makes fleet exhaustion a
85
+ * one-line fact.
86
+ */
87
+ export interface CursorPoolHealth {
88
+ /** Total accounts in the list. */
89
+ readonly totalAccounts: number;
90
+ /** Keys the routability rule would select for a new session, fleet-wide. */
91
+ readonly totalRoutableKeys: number;
92
+ /**
93
+ * Enabled accounts with enabled keys but zero routable ones — every
94
+ * key is dead (owner removed) or usage-drained. These are the rows
95
+ * that look healthy at a glance and are not.
96
+ */
97
+ readonly drainedAccounts: number;
98
+ /** Enabled keys excluded only by the usage guard, fleet-wide. */
99
+ readonly guardTrippedKeys: number;
100
+ }
101
+
102
+ /** Aggregate the server-computed summary counts across the account list. */
103
+ export function derivePoolHealth(
104
+ summaries: readonly CursorAccountSummary[],
105
+ ): CursorPoolHealth {
106
+ let totalRoutableKeys = 0;
107
+ let drainedAccounts = 0;
108
+ let guardTrippedKeys = 0;
109
+ for (const summary of summaries) {
110
+ totalRoutableKeys += summary.routableKeyCount;
111
+ guardTrippedKeys += summary.guardTrippedKeyCount;
112
+ if (
113
+ summary.account?.enabled === true
114
+ && summary.enabledKeyCount > 0
115
+ && summary.routableKeyCount === 0
116
+ ) {
117
+ drainedAccounts += 1;
118
+ }
119
+ }
120
+ return {
121
+ totalAccounts: summaries.length,
122
+ totalRoutableKeys,
123
+ drainedAccounts,
124
+ guardTrippedKeys,
125
+ };
126
+ }
@@ -20,7 +20,7 @@ import {
20
20
  ToolCallStatus,
21
21
  } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
22
22
  import type { WorkspaceEntry } from "@stigmer/protos/ai/stigmer/agentic/session/v1/workspace_pb";
23
- import { displayFileChangeSets } from "@stigmer/sdk";
23
+ import { displayFileChangeSets, syntheticUserPrompt } from "@stigmer/sdk";
24
24
  import { cn } from "@stigmer/theme";
25
25
  import { isTerminalPhase } from "./execution-phases.js";
26
26
  import { MessageEntry, type MessageEntryProps } from "./MessageEntry.js";
@@ -764,17 +764,14 @@ export function buildThreadItems(
764
764
  });
765
765
  }
766
766
 
767
- const specMessage = exec.spec?.message;
768
- // A Build-from-plan turn synthesizes NO prompt item: its message is a
769
- // machine-written label ("Build from plan"), not user prose — the real
770
- // instruction is runner-injected from the same flag. The plan card
771
- // directly above the turn is the visible cause; rendering the label as a
772
- // user bubble would attribute words to the user they never typed.
773
- const isBuildTurn = exec.spec?.executionConfig?.buildFromPlan === true;
774
- if (specMessage && specMessage !== "execute" && !isBuildTurn) {
767
+ // The user-turn synthesis rule (empty / "execute" placeholder /
768
+ // Build-from-plan skips) is the shared conversation rule see
769
+ // syntheticUserPrompt in @stigmer/sdk for the why of each skip.
770
+ const promptText = syntheticUserPrompt(exec);
771
+ if (promptText) {
775
772
  const syntheticHumanMsg = create(AgentMessageSchema);
776
773
  syntheticHumanMsg.type = MessageType.MESSAGE_HUMAN;
777
- syntheticHumanMsg.content = specMessage;
774
+ syntheticHumanMsg.content = promptText;
778
775
 
779
776
  // When the active stream execution's spec message matches the
780
777
  // pending user message, use a shared bridging key so React
@@ -782,7 +779,7 @@ export function buildThreadItems(
782
779
  const bridgePending =
783
780
  isActiveStreamExec &&
784
781
  pendingUserMessage != null &&
785
- specMessage === pendingUserMessage;
782
+ promptText === pendingUserMessage;
786
783
 
787
784
  // The turn's submitted files (spec.attachments, by reference — the spec
788
785
  // never mutates and structural sharing keeps the ref stable, so the
@@ -14,6 +14,7 @@ import { useStigmer } from "../hooks.js";
14
14
  import { toError } from "../internal/toError.js";
15
15
  import { toProtoInteractionMode } from "../composer/interaction-mode.js";
16
16
  import { toProtoServiceTier, type ServiceTierOption } from "../models/service-tier.js";
17
+ import { toProtoThinkingMode, type ThinkingModeOption } from "../models/thinking-mode.js";
17
18
  import { toProtoHarness, type HarnessOption } from "../models/harness.js";
18
19
  import {
19
20
  toProtoExecutionTarget,
@@ -138,6 +139,20 @@ export interface SharedAgentExecutionFields {
138
139
  * Maps to `ExecutionConfig.service_tier` in the proto.
139
140
  */
140
141
  readonly serviceTier?: ServiceTierOption;
142
+ /**
143
+ * Thinking mode for this execution's model calls (stigmer/stigmer#772).
144
+ *
145
+ * - `"disabled"` (default): the model's base variant, pinned explicitly —
146
+ * never the provider account default.
147
+ * - `"enabled"`: the model's extended-reasoning variant, billed at base
148
+ * per-token rates (reasoning tokens bill as output). Valid only for
149
+ * cursor-harness models whose registry entry declares the thinking
150
+ * capability — the backend refuses the create otherwise, so gate the
151
+ * option on `ModelInfo.thinkingCapable`.
152
+ *
153
+ * Maps to `ExecutionConfig.thinking_mode` in the proto.
154
+ */
155
+ readonly thinkingMode?: ThinkingModeOption;
141
156
  /**
142
157
  * Marks this execution as a Build-from-plan turn: the user approved a
143
158
  * plan from a prior Plan-mode execution and asked the agent to implement
@@ -318,6 +333,7 @@ export function useCreateAgentExecution(): UseCreateAgentExecutionReturn {
318
333
  input.modelName ||
319
334
  input.interactionMode ||
320
335
  input.serviceTier ||
336
+ input.thinkingMode ||
321
337
  input.structuredOutputSchema ||
322
338
  input.buildFromPlan;
323
339
  const executionConfig = hasConfig
@@ -329,6 +345,9 @@ export function useCreateAgentExecution(): UseCreateAgentExecutionReturn {
329
345
  ...(input.serviceTier
330
346
  ? { serviceTier: toProtoServiceTier(input.serviceTier) }
331
347
  : {}),
348
+ ...(input.thinkingMode
349
+ ? { thinkingMode: toProtoThinkingMode(input.thinkingMode) }
350
+ : {}),
332
351
  ...(input.structuredOutputSchema
333
352
  ? { structuredOutputSchema: input.structuredOutputSchema }
334
353
  : {}),
@@ -5,9 +5,14 @@ import type {
5
5
  FileChange,
6
6
  FileContent,
7
7
  } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
8
+ import { execIdFromStorageKey } from "@stigmer/sdk";
8
9
  import { useArtifactContent } from "./useArtifactContent.js";
9
10
  import { useArtifactDownloadUrl } from "./useArtifactDownloadUrl.js";
10
11
 
12
+ // The key-parsing rule lives in @stigmer/sdk with the other shared
13
+ // conversation-assembly rules; re-exported here for existing consumers.
14
+ export { execIdFromStorageKey };
15
+
11
16
  /** Return value of {@link useFileChangeContent}. */
12
17
  export interface UseFileChangeContentReturn {
13
18
  /**
@@ -135,15 +140,3 @@ function sideText(
135
140
  return fetched;
136
141
  }
137
142
 
138
- /**
139
- * Extracts the execution id from an artifact storage key of the form
140
- * `artifacts/{executionId}/...`. Returns `null` for an unexpected shape so the
141
- * caller skips the fetch rather than issuing a request the server would reject.
142
- */
143
- export function execIdFromStorageKey(storageKey: string): string | null {
144
- const parts = storageKey.split("/");
145
- if (parts.length >= 3 && parts[0] === "artifacts" && parts[1]) {
146
- return parts[1];
147
- }
148
- return null;
149
- }