@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,210 @@
1
+ import { describe, it, expect, vi, beforeAll, afterEach } from "vitest";
2
+ import { render, screen, fireEvent, waitFor, cleanup } from "@testing-library/react";
3
+ import type { ReactNode } from "react";
4
+ import { ServiceTier, ThinkingMode } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
5
+ import type { AgentChannelInput } from "@stigmer/sdk";
6
+ import { StigmerContext } from "../../context";
7
+ import { ModelRegistryContext } from "../../models/ModelRegistryContext";
8
+ import type { ModelInfo } from "../../models/registry";
9
+ import { ChannelRunConfigDialog } from "../ChannelRunConfigDialog";
10
+
11
+ // happy-dom does not implement the native dialog show/close methods.
12
+ beforeAll(() => {
13
+ HTMLDialogElement.prototype.showModal = function showModal() {
14
+ this.open = true;
15
+ };
16
+ HTMLDialogElement.prototype.close = function close() {
17
+ this.open = false;
18
+ };
19
+ if (!("ResizeObserver" in globalThis)) {
20
+ (globalThis as unknown as { ResizeObserver: unknown }).ResizeObserver =
21
+ class {
22
+ observe() {}
23
+ unobserve() {}
24
+ disconnect() {}
25
+ };
26
+ }
27
+ });
28
+
29
+ afterEach(cleanup);
30
+
31
+ /**
32
+ * The console's first run_config editor (stigmer/stigmer#792). The pinned
33
+ * behaviors are the edit-surface contracts the rest of the platform relies
34
+ * on: `max_tool_rounds` (the operator knob this editor never renders)
35
+ * survives every save verbatim, an all-empty draft clears the block
36
+ * ("empty = inherit"), and untouched variant switches stay off the wire
37
+ * (the #357/#772 unspecified-vs-explicit ledger distinction).
38
+ */
39
+
40
+ const MODELS: ModelInfo[] = [
41
+ {
42
+ modelId: "claude-haiku-4-5",
43
+ provider: "anthropic",
44
+ displayName: "Haiku 4.5",
45
+ shortDescription: "Fast Claude with optional thinking",
46
+ speedTier: "fast",
47
+ costTier: "economy",
48
+ harness: "cursor",
49
+ featured: true,
50
+ serviceTiers: [],
51
+ thinkingCapable: true,
52
+ },
53
+ ];
54
+
55
+ function makeChannel(runConfig?: {
56
+ modelName?: string;
57
+ maxCostUsd?: number;
58
+ maxToolRounds?: number;
59
+ serviceTier?: ServiceTier;
60
+ thinkingMode?: ThinkingMode;
61
+ }) {
62
+ return {
63
+ metadata: {
64
+ id: "ach_1",
65
+ name: "Support Slack",
66
+ slug: "support-slack",
67
+ org: "acme",
68
+ labels: {},
69
+ },
70
+ spec: {
71
+ agentRef: { org: "acme", slug: "support-agent" },
72
+ enabled: true,
73
+ providerConfig: { case: "slack", value: {} },
74
+ environmentRefs: [],
75
+ ...(runConfig
76
+ ? {
77
+ runConfig: {
78
+ modelName: runConfig.modelName ?? "",
79
+ maxCostUsd: runConfig.maxCostUsd ?? 0,
80
+ maxToolRounds: runConfig.maxToolRounds ?? 0,
81
+ serviceTier: runConfig.serviceTier ?? ServiceTier.UNSPECIFIED,
82
+ thinkingMode: runConfig.thinkingMode ?? ThinkingMode.UNSPECIFIED,
83
+ },
84
+ }
85
+ : {}),
86
+ },
87
+ status: { installState: 2 },
88
+ } as never;
89
+ }
90
+
91
+ function createMockStigmer() {
92
+ const apply = vi.fn().mockResolvedValue({});
93
+ const client = { agentChannel: { apply } } as never;
94
+ return { client, apply };
95
+ }
96
+
97
+ function Providers({
98
+ client,
99
+ children,
100
+ }: {
101
+ client: unknown;
102
+ children: ReactNode;
103
+ }) {
104
+ return (
105
+ <StigmerContext.Provider value={client as never}>
106
+ <ModelRegistryContext.Provider
107
+ value={{ models: MODELS, isLoading: false, error: null, refetch: vi.fn() }}
108
+ >
109
+ {children}
110
+ </ModelRegistryContext.Provider>
111
+ </StigmerContext.Provider>
112
+ );
113
+ }
114
+
115
+ function renderDialog(channel: unknown) {
116
+ const { client, apply } = createMockStigmer();
117
+ const onSaved = vi.fn();
118
+ render(
119
+ <Providers client={client}>
120
+ <ChannelRunConfigDialog
121
+ open
122
+ onOpenChange={vi.fn()}
123
+ channel={channel as never}
124
+ onSaved={onSaved}
125
+ />
126
+ </Providers>,
127
+ );
128
+ return { apply, onSaved };
129
+ }
130
+
131
+ async function save() {
132
+ fireEvent.click(screen.getByRole("button", { name: "Save" }));
133
+ }
134
+
135
+ describe("ChannelRunConfigDialog", () => {
136
+ it("preserves max_tool_rounds verbatim even though it never renders", async () => {
137
+ const { apply } = renderDialog(
138
+ makeChannel({ modelName: "claude-haiku-4-5", maxToolRounds: 25 }),
139
+ );
140
+
141
+ expect(screen.queryByText(/tool round/i)).toBeNull();
142
+ await save();
143
+
144
+ await waitFor(() => expect(apply).toHaveBeenCalledOnce());
145
+ const input = apply.mock.calls[0][0] as AgentChannelInput;
146
+ expect(input.runConfig?.maxToolRounds).toBe(25);
147
+ expect(input.runConfig?.modelName).toBe("claude-haiku-4-5");
148
+ });
149
+
150
+ it("clears run_config entirely when the draft is emptied (empty = inherit)", async () => {
151
+ const { apply } = renderDialog(
152
+ makeChannel({ modelName: "claude-haiku-4-5", maxCostUsd: 2 }),
153
+ );
154
+
155
+ fireEvent.click(
156
+ screen.getByRole("button", { name: "Reset to platform default" }),
157
+ );
158
+ fireEvent.change(screen.getByLabelText(/Budget per run/), {
159
+ target: { value: "" },
160
+ });
161
+ await save();
162
+
163
+ await waitFor(() => expect(apply).toHaveBeenCalledOnce());
164
+ const input = apply.mock.calls[0][0] as AgentChannelInput;
165
+ expect(input.runConfig).toBeUndefined();
166
+ });
167
+
168
+ it("carries an actively enabled thinking mode, and only then", async () => {
169
+ const { apply } = renderDialog(makeChannel({ modelName: "claude-haiku-4-5" }));
170
+
171
+ fireEvent.click(screen.getByRole("button", { name: /Haiku 4\.5/ }));
172
+ fireEvent.click(await screen.findByRole("switch", { name: "Thinking" }));
173
+ fireEvent.keyDown(document.activeElement ?? document.body, { key: "Escape" });
174
+ await save();
175
+
176
+ await waitFor(() => expect(apply).toHaveBeenCalledOnce());
177
+ const input = apply.mock.calls[0][0] as AgentChannelInput;
178
+ expect(input.runConfig?.thinkingMode).toBe(ThinkingMode.ENABLED);
179
+ // The untouched tier stays absent — never stamped as an explicit
180
+ // STANDARD by a save (#357 ledger distinction).
181
+ expect(input.runConfig?.serviceTier).toBeUndefined();
182
+ });
183
+
184
+ it("round-trips a stored thinking pin into the switch state", async () => {
185
+ renderDialog(
186
+ makeChannel({
187
+ modelName: "claude-haiku-4-5",
188
+ thinkingMode: ThinkingMode.ENABLED,
189
+ }),
190
+ );
191
+
192
+ fireEvent.click(screen.getByRole("button", { name: /Haiku 4\.5/ }));
193
+ const toggle = await screen.findByRole("switch", { name: "Thinking" });
194
+ expect(toggle.getAttribute("aria-checked")).toBe("true");
195
+ });
196
+
197
+ it("collects a budget without requiring a model pin", async () => {
198
+ const { apply } = renderDialog(makeChannel());
199
+
200
+ fireEvent.change(screen.getByLabelText(/Budget per run/), {
201
+ target: { value: "1.5" },
202
+ });
203
+ await save();
204
+
205
+ await waitFor(() => expect(apply).toHaveBeenCalledOnce());
206
+ const input = apply.mock.calls[0][0] as AgentChannelInput;
207
+ expect(input.runConfig?.maxCostUsd).toBe(1.5);
208
+ expect(input.runConfig?.modelName).toBeUndefined();
209
+ });
210
+ });
@@ -69,6 +69,8 @@ export type { ConnectWhatsAppDialogProps } from "./ConnectWhatsAppDialog.js";
69
69
 
70
70
  export { ChannelCredentialsDialog } from "./ChannelCredentialsDialog.js";
71
71
  export type { ChannelCredentialsDialogProps } from "./ChannelCredentialsDialog.js";
72
+ export { ChannelRunConfigDialog } from "./ChannelRunConfigDialog.js";
73
+ export type { ChannelRunConfigDialogProps } from "./ChannelRunConfigDialog.js";
72
74
 
73
75
  export { ChannelToolCredentials } from "./ChannelToolCredentials.js";
74
76
  export type { ChannelToolCredentialsProps } from "./ChannelToolCredentials.js";
@@ -66,9 +66,11 @@ export function agentChannelToInput(channel: AgentChannel): AgentChannelInput {
66
66
  })),
67
67
  }
68
68
  : {}),
69
- // The console does not edit run_config (yet) it rides through
70
- // opaquely so a settings save or pause/resume toggle never wipes an
71
- // override set via the CLI or API.
69
+ // run_config rides through verbatim so a settings save or
70
+ // pause/resume toggle never wipes an override set elsewhere (the
71
+ // ChannelRunConfigDialog the one surface that edits it, #792 —
72
+ // overrides this copy with its own draft; CLI/API overrides survive
73
+ // every other save).
72
74
  ...(spec?.runConfig
73
75
  ? {
74
76
  runConfig: {
@@ -76,6 +78,7 @@ export function agentChannelToInput(channel: AgentChannel): AgentChannelInput {
76
78
  maxCostUsd: spec.runConfig.maxCostUsd,
77
79
  maxToolRounds: spec.runConfig.maxToolRounds,
78
80
  serviceTier: spec.runConfig.serviceTier,
81
+ thinkingMode: spec.runConfig.thinkingMode,
79
82
  },
80
83
  }
81
84
  : {}),
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { type FormEvent, useCallback, useId, useMemo, useState } from "react";
4
4
  import { cn } from "@stigmer/theme";
5
- import { getUserMessage } from "@stigmer/sdk";
5
+ import { getUserMessage, toChannelAppUpdateInput } from "@stigmer/sdk";
6
6
  import type { ChannelApp } from "@stigmer/protos/ai/stigmer/agentic/channelapp/v1/api_pb";
7
7
  import { useStigmer } from "../hooks.js";
8
8
  import { useUpdateChannelApp } from "./useUpdateChannelApp.js";
@@ -229,10 +229,10 @@ function SlackAppDetail({
229
229
 
230
230
  clearError();
231
231
  try {
232
+ // Full-spec-replace safety: spread the complete mapped input and
233
+ // override only the slack arm this form owns.
232
234
  const updated = await update({
233
- name,
234
- org: channelApp.metadata?.org ?? "",
235
- ...(channelApp.metadata?.slug ? { slug: channelApp.metadata.slug } : {}),
235
+ ...toChannelAppUpdateInput(channelApp),
236
236
  slack: {
237
237
  clientId: clientId.trim(),
238
238
  // The redaction marker means "keep the stored value" — the
@@ -246,7 +246,7 @@ function SlackAppDetail({
246
246
  // error state is managed by useUpdateChannelApp
247
247
  }
248
248
  },
249
- [canSave, clearError, update, name, channelApp.metadata, clientId, clientSecret, signingSecret, onUpdated],
249
+ [canSave, clearError, update, channelApp, clientId, clientSecret, signingSecret, onUpdated],
250
250
  );
251
251
 
252
252
  return (
@@ -379,10 +379,10 @@ function WhatsAppAppDetail({
379
379
 
380
380
  clearError();
381
381
  try {
382
+ // Full-spec-replace safety: spread the complete mapped input and
383
+ // override only the whatsapp arm this form owns.
382
384
  const updated = await update({
383
- name,
384
- org: channelApp.metadata?.org ?? "",
385
- ...(channelApp.metadata?.slug ? { slug: channelApp.metadata.slug } : {}),
385
+ ...toChannelAppUpdateInput(channelApp),
386
386
  whatsapp: {
387
387
  appId: metaAppId.trim(),
388
388
  // The redaction marker means "keep the stored value" — the
@@ -397,7 +397,7 @@ function WhatsAppAppDetail({
397
397
  // error state is managed by useUpdateChannelApp
398
398
  }
399
399
  },
400
- [canSave, clearError, update, name, channelApp.metadata, metaAppId, appSecret, accessToken, verifyToken, onUpdated],
400
+ [canSave, clearError, update, channelApp, metaAppId, appSecret, accessToken, verifyToken, onUpdated],
401
401
  );
402
402
 
403
403
  return (
@@ -228,6 +228,8 @@ describe("ChannelAppDetailPanel — WhatsApp branch", () => {
228
228
 
229
229
  await waitFor(() => expect(update).toHaveBeenCalled());
230
230
  expect(update).toHaveBeenCalledWith({
231
+ // The generated mapper carries metadata.id for exact update addressing.
232
+ id: "chapp_1",
231
233
  name: "Acme WhatsApp",
232
234
  org: "acme",
233
235
  slug: "acme-whatsapp",
@@ -0,0 +1,87 @@
1
+ import { describe, it, expect, vi, afterEach } from "vitest";
2
+ import { render, screen, fireEvent, waitFor, cleanup } from "@testing-library/react";
3
+ import { create } from "@bufbuild/protobuf";
4
+ import {
5
+ ChannelAppSchema,
6
+ type ChannelApp,
7
+ } from "@stigmer/protos/ai/stigmer/agentic/channelapp/v1/api_pb";
8
+ import type { ChannelAppInput } from "@stigmer/sdk";
9
+ import { StigmerContext } from "../../context";
10
+ import { ChannelAppDetailPanel } from "../ChannelAppDetailPanel";
11
+
12
+ /**
13
+ * Regression suite for the full-spec-replace wipe bug: both detail forms
14
+ * must spread `toChannelAppUpdateInput` and override only their provider
15
+ * arm, so identity fields and any future spec field survive the save.
16
+ * Secrets follow the redaction contract: the form is seeded with the
17
+ * server's redaction marker, and sending the marker back means "keep the
18
+ * stored value" (per-field, verified by the backend's
19
+ * TestRedactAndEncryptCoverEveryProviderArm).
20
+ */
21
+
22
+ const REDACTED = "***REDACTED***";
23
+
24
+ const SLACK_APP: ChannelApp = create(ChannelAppSchema, {
25
+ metadata: {
26
+ id: "chapp_1",
27
+ name: "Acme Slack",
28
+ slug: "acme-slack",
29
+ org: "acme",
30
+ labels: { team: "support" },
31
+ },
32
+ spec: {
33
+ providerConfig: {
34
+ case: "slack",
35
+ value: {
36
+ clientId: "1234.5678",
37
+ clientSecret: REDACTED,
38
+ signingSecret: REDACTED,
39
+ },
40
+ },
41
+ },
42
+ });
43
+
44
+ function renderPanel(update: ReturnType<typeof vi.fn>) {
45
+ const client = {
46
+ baseUrl: "https://api.stigmer.ai",
47
+ channelapp: { update, delete: vi.fn(async () => ({})) },
48
+ } as never;
49
+ return render(
50
+ <StigmerContext.Provider value={client}>
51
+ <ChannelAppDetailPanel
52
+ channelApp={SLACK_APP}
53
+ consoleOrigin="https://console.acme.example"
54
+ />
55
+ </StigmerContext.Provider>,
56
+ );
57
+ }
58
+
59
+ afterEach(cleanup);
60
+
61
+ describe("ChannelAppDetailPanel save payload (slack arm)", () => {
62
+ it("spreads the complete mapped input and overrides only the slack config", async () => {
63
+ const update = vi.fn(async (_input: ChannelAppInput) => SLACK_APP);
64
+ renderPanel(update);
65
+
66
+ const clientId = await screen.findByLabelText(/client id/i);
67
+ fireEvent.change(clientId, { target: { value: "9999.0000" } });
68
+ fireEvent.click(screen.getByRole("button", { name: /save credentials/i }));
69
+
70
+ await waitFor(() => expect(update).toHaveBeenCalledTimes(1));
71
+ const input = update.mock.calls[0]![0];
72
+
73
+ // The edited field, plus per-field keep-markers for untouched secrets.
74
+ expect(input.slack).toEqual({
75
+ clientId: "9999.0000",
76
+ clientSecret: REDACTED,
77
+ signingSecret: REDACTED,
78
+ });
79
+ // Identity and unrendered metadata survive via the mapper spread.
80
+ expect(input.name).toBe("Acme Slack");
81
+ expect(input.org).toBe("acme");
82
+ expect(input.slug).toBe("acme-slack");
83
+ expect(input.labels).toEqual({ team: "support" });
84
+ // The other provider arm stays absent — the oneof is preserved.
85
+ expect(input.whatsapp).toBeUndefined();
86
+ });
87
+ });
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
 
3
+ import { useId } from "react";
3
4
  import { cn } from "@stigmer/theme";
4
5
  import {
5
6
  Tooltip,
@@ -7,12 +8,14 @@ import {
7
8
  TooltipProvider,
8
9
  TooltipTrigger,
9
10
  } from "../internal/tooltip.js";
11
+ import { Switch } from "../switch/Switch.js";
10
12
  import { ContextPopover } from "./ContextPopover.js";
11
13
  import { ConfigureMenu, type ConfigureMenuItem } from "./ConfigureMenu.js";
12
14
  import { ModelSelector } from "../models/ModelSelector.js";
13
15
  import { HarnessSelector } from "../models/HarnessSelector.js";
14
16
  import type { HarnessOption } from "../models/harness.js";
15
17
  import type { ServiceTierOption } from "../models/service-tier.js";
18
+ import type { ThinkingModeOption } from "../models/thinking-mode.js";
16
19
  import { InteractionModePicker, type InteractionModeOption } from "./InteractionModePicker.js";
17
20
  import {
18
21
  PaperclipIcon,
@@ -22,6 +25,18 @@ import {
22
25
  } from "./icons.js";
23
26
  import { SpinnerIcon } from "../internal/SpinnerIcon.js";
24
27
 
28
+ /**
29
+ * Wiring for the always-available "auto-approve tool calls" toggle
30
+ * (stigmer/stigmer#816). Presence renders the control; omission keeps the
31
+ * toolbar byte-identical for consumers that don't wire it (DD-011).
32
+ */
33
+ export interface ComposerAutoApproveProps {
34
+ /** Whether auto-approve is currently armed for this conversation. */
35
+ readonly armed: boolean;
36
+ /** Called with the next value when the user flips the toggle. */
37
+ readonly onChange: (armed: boolean) => void;
38
+ }
39
+
25
40
  export interface ComposerToolbarProps {
26
41
  readonly disabled: boolean;
27
42
  readonly isSubmitting: boolean;
@@ -46,6 +61,14 @@ export interface ComposerToolbarProps {
46
61
  readonly interactionMode?: InteractionModeOption;
47
62
  readonly onInteractionModeChange: (mode: InteractionModeOption) => void;
48
63
 
64
+ /**
65
+ * Always-available auto-approve toggle (#816). Deliberately NOT bound to
66
+ * {@link disabled}: the walk-away user flips it exactly while a turn
67
+ * streams, so it stays interactive under the composer lock — the same
68
+ * exemption the Stop button carries.
69
+ */
70
+ readonly autoApprove?: ComposerAutoApproveProps;
71
+
49
72
  readonly showModelSelector: boolean;
50
73
  readonly modelId?: string;
51
74
  readonly onModelChange: (id: string) => void;
@@ -53,6 +76,10 @@ export interface ComposerToolbarProps {
53
76
  readonly serviceTier?: ServiceTierOption;
54
77
  /** Enables the fast-tier switch inside the model selector's options area. */
55
78
  readonly onServiceTierChange?: (tier: ServiceTierOption) => void;
79
+ /** Current thinking mode for the selected model (#772). */
80
+ readonly thinkingMode?: ThinkingModeOption;
81
+ /** Enables the thinking switch inside the model selector's options area. */
82
+ readonly onThinkingModeChange?: (mode: ThinkingModeOption) => void;
56
83
 
57
84
  // -- Right group: Secondary actions (icon-only) ---------------------------
58
85
 
@@ -123,13 +150,17 @@ export function ComposerToolbar({
123
150
  showInteractionModePicker,
124
151
  interactionMode,
125
152
  onInteractionModeChange,
153
+ autoApprove,
126
154
  showModelSelector,
127
155
  modelId,
128
156
  onModelChange,
129
157
  serviceTier,
130
158
  onServiceTierChange,
159
+ thinkingMode,
160
+ onThinkingModeChange,
131
161
  }: ComposerToolbarProps) {
132
162
  const showHarnessSeparate = showHarnessSelector && !showModelSelector;
163
+ const autoApproveLabelId = useId();
133
164
 
134
165
  return (
135
166
  <div className="stg:flex stg:items-center stg:justify-between stg:gap-2 stg:border-t stg:border-border-muted stg:px-3 stg:py-2">
@@ -161,9 +192,45 @@ export function ComposerToolbar({
161
192
  onHarnessChange={showHarnessSelector ? onHarnessChange : undefined}
162
193
  serviceTier={serviceTier}
163
194
  onServiceTierChange={onServiceTierChange}
195
+ thinkingMode={thinkingMode}
196
+ onThinkingModeChange={onThinkingModeChange}
164
197
  disabled={disabled}
165
198
  />
166
199
  )}
200
+
201
+ {/* Auto-approve (#816): primary state like Mode and Model — armed
202
+ vs gated is something the user glances at before walking away.
203
+ Never bound to `disabled` (see ComposerAutoApproveProps): the
204
+ whole point is flipping it while a turn streams. Scoped provider:
205
+ this tooltip does not share the icon cluster's hover-delay group. */}
206
+ {autoApprove && (
207
+ <TooltipProvider>
208
+ <Tooltip>
209
+ <TooltipTrigger render={<span className="stg:inline-flex" />}>
210
+ <span className="stg:flex stg:shrink-0 stg:items-center stg:gap-1.5 stg:px-1">
211
+ <label
212
+ id={autoApproveLabelId}
213
+ htmlFor={`${autoApproveLabelId}-switch`}
214
+ className="stg:cursor-pointer stg:select-none stg:text-xs stg:text-muted-foreground"
215
+ >
216
+ Auto-approve
217
+ </label>
218
+ <Switch
219
+ id={`${autoApproveLabelId}-switch`}
220
+ checked={autoApprove.armed}
221
+ onCheckedChange={autoApprove.onChange}
222
+ aria-labelledby={autoApproveLabelId}
223
+ />
224
+ </span>
225
+ </TooltipTrigger>
226
+ <TooltipContent side="top">
227
+ {autoApprove.armed
228
+ ? "Tool calls run without asking — this conversation only. Turn off to be asked again."
229
+ : "Run this conversation's tool calls without asking for approval, including the run in progress."}
230
+ </TooltipContent>
231
+ </Tooltip>
232
+ </TooltipProvider>
233
+ )}
167
234
  </div>
168
235
 
169
236
  {/* ---- Right group: Secondary actions (icon-only) + Send ----
@@ -4,10 +4,11 @@ import { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo,
4
4
  import { cn } from "@stigmer/theme";
5
5
  import { getUserMessage, type AttachmentInput, type EnvVarInput, type McpServerUsageInput, type ResourceRef } from "@stigmer/sdk";
6
6
  import { useComposer } from "./useComposer.js";
7
- import { ComposerToolbar } from "./ComposerToolbar.js";
7
+ import { ComposerToolbar, type ComposerAutoApproveProps } from "./ComposerToolbar.js";
8
8
  import { type ConfigureMenuItem } from "./ConfigureMenu.js";
9
9
  import type { HarnessOption } from "../models/harness.js";
10
10
  import { FAST_SERVICE_TIER, type ServiceTierOption } from "../models/service-tier.js";
11
+ import type { ThinkingModeOption } from "../models/thinking-mode.js";
11
12
  import type { InteractionModeOption } from "./InteractionModePicker.js";
12
13
  import { parseModelKey } from "../models/registry.js";
13
14
  import { useModelRegistry } from "../models/useModelRegistry.js";
@@ -164,6 +165,14 @@ export interface SessionComposerSubmitContext {
164
165
  * Pass to execution creation as `execution_config.service_tier`.
165
166
  */
166
167
  readonly serviceTier?: ServiceTierOption;
168
+ /**
169
+ * Thinking mode the user actively selected for this execution
170
+ * (stigmer/stigmer#772). Only ever `"enabled"` — an untouched mode means
171
+ * "platform default" and is carried as `undefined`, the same
172
+ * unspecified-vs-explicit contract as {@link serviceTier}.
173
+ * Pass to execution creation as `execution_config.thinking_mode`.
174
+ */
175
+ readonly thinkingMode?: ThinkingModeOption;
167
176
  /**
168
177
  * Workspace-relative file paths the user referenced via drag-to-reference.
169
178
  *
@@ -291,6 +300,16 @@ export interface SessionComposerProps {
291
300
  /** Show the model selector. @default true */
292
301
  readonly showModelSelector?: boolean;
293
302
 
303
+ /**
304
+ * Always-visible "auto-approve tool calls" toggle in the toolbar
305
+ * (stigmer/stigmer#816). Presence renders it; omission keeps the composer
306
+ * byte-identical (DD-011). Stays interactive while the composer is
307
+ * `disabled` — the walk-away user flips it exactly while a turn streams —
308
+ * so consumers must only wire it on surfaces whose viewer may submit
309
+ * approvals (never guest/observer). See {@link ComposerAutoApproveProps}.
310
+ */
311
+ readonly autoApprove?: ComposerAutoApproveProps;
312
+
294
313
  /**
295
314
  * Workspace state managed by {@link useWorkspaceEntries}.
296
315
  * When provided, renders a workspace trigger in the toolbar
@@ -608,6 +627,7 @@ const SessionComposerInner = forwardRef<SessionComposerHandle, SessionComposerPr
608
627
  defaultModelId,
609
628
  onModelChange,
610
629
  showModelSelector = true,
630
+ autoApprove,
611
631
  workspace,
612
632
  gitHubConnection,
613
633
  enableGitHub = true,
@@ -663,6 +683,9 @@ const SessionComposerInner = forwardRef<SessionComposerHandle, SessionComposerPr
663
683
  // selection below (#663) — state here records the user's intent only.
664
684
  const [serviceTier, setServiceTier] = useState<ServiceTierOption>("standard");
665
685
 
686
+ // Thinking mode (#772): the tier's twin, same intent-vs-effective split.
687
+ const [thinkingMode, setThinkingMode] = useState<ThinkingModeOption>("disabled");
688
+
666
689
  // Active harness mirror: controlled hosts (both viewers) keep the
667
690
  // `harness` prop current, but with `showHarnessSelector` the dropdown
668
691
  // lives inside ModelSelector and an uncontrolled host would leave the
@@ -837,10 +860,26 @@ const SessionComposerInner = forwardRef<SessionComposerHandle, SessionComposerPr
837
860
  ? "fast"
838
861
  : "standard";
839
862
 
840
- return { modelId: effectiveModelId, model: effectiveModel, serviceTier: effectiveServiceTier };
863
+ // "enabled" rides under the identical rule, keyed on the capability
864
+ // (cursor-harness models only — v1 has no native thinking mapping, #772).
865
+ const effectiveThinkingMode: ThinkingModeOption =
866
+ thinkingMode === "enabled"
867
+ && effectiveModel !== undefined
868
+ && effectiveModel.harness === "cursor"
869
+ && effectiveModel.thinkingCapable === true
870
+ ? "enabled"
871
+ : "disabled";
872
+
873
+ return {
874
+ modelId: effectiveModelId,
875
+ model: effectiveModel,
876
+ serviceTier: effectiveServiceTier,
877
+ thinkingMode: effectiveThinkingMode,
878
+ };
841
879
  }, [
842
880
  modelId,
843
881
  serviceTier,
882
+ thinkingMode,
844
883
  showModelSelector,
845
884
  isRegistryLoading,
846
885
  registryGetByKey,
@@ -1054,15 +1093,19 @@ const SessionComposerInner = forwardRef<SessionComposerHandle, SessionComposerPr
1054
1093
  // explicit distinction is load-bearing telemetry (#357).
1055
1094
  const submitServiceTier =
1056
1095
  effective.serviceTier === "fast" ? effective.serviceTier : undefined;
1096
+ // The tier's #772 twin: only an in-effect "enabled" is carried.
1097
+ const submitThinkingMode =
1098
+ effective.thinkingMode === "enabled" ? effective.thinkingMode : undefined;
1057
1099
 
1058
1100
  const context: SessionComposerSubmitContext | undefined =
1059
1101
  hasEnv || hasAttachments || effectiveMode || hasFileRefs || buildFromPlan
1060
- || submitServiceTier
1102
+ || submitServiceTier || submitThinkingMode
1061
1103
  ? {
1062
1104
  runtimeEnv: hasEnv ? env : undefined,
1063
1105
  attachments: hasAttachments ? attachmentInputs : undefined,
1064
1106
  interactionMode: effectiveMode,
1065
1107
  serviceTier: submitServiceTier,
1108
+ thinkingMode: submitThinkingMode,
1066
1109
  workspaceFileRefs: hasFileRefs ? [...fileRefs.refs] : undefined,
1067
1110
  buildFromPlan,
1068
1111
  }
@@ -1888,12 +1931,15 @@ const SessionComposerInner = forwardRef<SessionComposerHandle, SessionComposerPr
1888
1931
  interactionMode={interactionMode}
1889
1932
  onInteractionModeChange={handleInteractionModeChange}
1890
1933
  showModelSelector={showModelSelector}
1934
+ autoApprove={autoApprove}
1891
1935
  // The pill renders the effective selection — the same value the
1892
1936
  // submit payload carries (#663), never a fallback of its own.
1893
1937
  modelId={effective.modelId}
1894
1938
  onModelChange={handleModelChange}
1895
1939
  serviceTier={serviceTier}
1896
1940
  onServiceTierChange={setServiceTier}
1941
+ thinkingMode={thinkingMode}
1942
+ onThinkingModeChange={setThinkingMode}
1897
1943
  />
1898
1944
  </div>
1899
1945
  </div>