@stigmer/react 3.12.5 → 3.12.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (329) hide show
  1. package/agent/AgentDetailView.js +2 -2
  2. package/agent/AgentDetailView.js.map +1 -1
  3. package/agent/index.d.ts +0 -1
  4. package/agent/index.d.ts.map +1 -1
  5. package/agent/index.js +0 -1
  6. package/agent/index.js.map +1 -1
  7. package/agent/useUpdateAgent.d.ts +3 -3
  8. package/agent/useUpdateAgent.js +3 -3
  9. package/agent-instance/AgentInstanceDetailPanel.d.ts.map +1 -1
  10. package/agent-instance/AgentInstanceDetailPanel.js +5 -7
  11. package/agent-instance/AgentInstanceDetailPanel.js.map +1 -1
  12. package/agent-instance/useUpdateAgentInstance.d.ts +1 -1
  13. package/agent-instance/useUpdateAgentInstance.js +1 -1
  14. package/channel/AgentChannelsPanel.d.ts.map +1 -1
  15. package/channel/AgentChannelsPanel.js +10 -5
  16. package/channel/AgentChannelsPanel.js.map +1 -1
  17. package/channel/ChannelRunConfigDialog.d.ts +51 -0
  18. package/channel/ChannelRunConfigDialog.d.ts.map +1 -0
  19. package/channel/ChannelRunConfigDialog.js +121 -0
  20. package/channel/ChannelRunConfigDialog.js.map +1 -0
  21. package/channel/index.d.ts +2 -0
  22. package/channel/index.d.ts.map +1 -1
  23. package/channel/index.js +1 -0
  24. package/channel/index.js.map +1 -1
  25. package/channel/useSaveAgentChannel.d.ts.map +1 -1
  26. package/channel/useSaveAgentChannel.js +6 -3
  27. package/channel/useSaveAgentChannel.js.map +1 -1
  28. package/channel-app/ChannelAppDetailPanel.js +9 -9
  29. package/channel-app/ChannelAppDetailPanel.js.map +1 -1
  30. package/composer/ComposerToolbar.d.ts +24 -1
  31. package/composer/ComposerToolbar.d.ts.map +1 -1
  32. package/composer/ComposerToolbar.js +7 -2
  33. package/composer/ComposerToolbar.js.map +1 -1
  34. package/composer/SessionComposer.d.ts +19 -0
  35. package/composer/SessionComposer.d.ts.map +1 -1
  36. package/composer/SessionComposer.js +24 -5
  37. package/composer/SessionComposer.js.map +1 -1
  38. package/composer/index.d.ts +1 -0
  39. package/composer/index.d.ts.map +1 -1
  40. package/composer/index.js.map +1 -1
  41. package/cursor-accounts/CursorAccountsConsole.d.ts.map +1 -1
  42. package/cursor-accounts/CursorAccountsConsole.js +12 -3
  43. package/cursor-accounts/CursorAccountsConsole.js.map +1 -1
  44. package/cursor-accounts/cursor-account-coverage.d.ts +30 -1
  45. package/cursor-accounts/cursor-account-coverage.d.ts.map +1 -1
  46. package/cursor-accounts/cursor-account-coverage.js +21 -0
  47. package/cursor-accounts/cursor-account-coverage.js.map +1 -1
  48. package/execution/MessageThread.d.ts.map +1 -1
  49. package/execution/MessageThread.js +8 -11
  50. package/execution/MessageThread.js.map +1 -1
  51. package/execution/useCreateAgentExecution.d.ts +15 -0
  52. package/execution/useCreateAgentExecution.d.ts.map +1 -1
  53. package/execution/useCreateAgentExecution.js +5 -0
  54. package/execution/useCreateAgentExecution.js.map +1 -1
  55. package/execution/useFileChangeContent.d.ts +2 -6
  56. package/execution/useFileChangeContent.d.ts.map +1 -1
  57. package/execution/useFileChangeContent.js +4 -12
  58. package/execution/useFileChangeContent.js.map +1 -1
  59. package/identity-account/AccountPreferencesPanel.d.ts +2 -1
  60. package/identity-account/AccountPreferencesPanel.d.ts.map +1 -1
  61. package/identity-account/AccountPreferencesPanel.js +33 -18
  62. package/identity-account/AccountPreferencesPanel.js.map +1 -1
  63. package/identity-provider/IdentityProviderDetailPanel.d.ts.map +1 -1
  64. package/identity-provider/IdentityProviderDetailPanel.js +16 -15
  65. package/identity-provider/IdentityProviderDetailPanel.js.map +1 -1
  66. package/index.d.ts +10 -7
  67. package/index.d.ts.map +1 -1
  68. package/index.js +8 -4
  69. package/index.js.map +1 -1
  70. package/internal/download.d.ts +8 -0
  71. package/internal/download.d.ts.map +1 -0
  72. package/internal/download.js +25 -0
  73. package/internal/download.js.map +1 -0
  74. package/library/useExportResource.d.ts.map +1 -1
  75. package/library/useExportResource.js +3 -17
  76. package/library/useExportResource.js.map +1 -1
  77. package/mcp-server/McpServerDetailView.js +2 -2
  78. package/mcp-server/McpServerDetailView.js.map +1 -1
  79. package/mcp-server/index.d.ts +0 -1
  80. package/mcp-server/index.d.ts.map +1 -1
  81. package/mcp-server/index.js +0 -1
  82. package/mcp-server/index.js.map +1 -1
  83. package/mcp-server/internal/definitionDrift.d.ts +14 -12
  84. package/mcp-server/internal/definitionDrift.d.ts.map +1 -1
  85. package/mcp-server/internal/definitionDrift.js +22 -19
  86. package/mcp-server/internal/definitionDrift.js.map +1 -1
  87. package/mcp-server/useUpdateMcpServer.d.ts +3 -3
  88. package/mcp-server/useUpdateMcpServer.js +3 -3
  89. package/memory/MemoryListPanel.d.ts +35 -0
  90. package/memory/MemoryListPanel.d.ts.map +1 -0
  91. package/memory/MemoryListPanel.js +141 -0
  92. package/memory/MemoryListPanel.js.map +1 -0
  93. package/memory/index.d.ts +8 -0
  94. package/memory/index.d.ts.map +1 -0
  95. package/memory/index.js +12 -0
  96. package/memory/index.js.map +1 -0
  97. package/memory/memoryGroups.d.ts +31 -0
  98. package/memory/memoryGroups.d.ts.map +1 -0
  99. package/memory/memoryGroups.js +52 -0
  100. package/memory/memoryGroups.js.map +1 -0
  101. package/memory/useConfirmMemory.d.ts +31 -0
  102. package/memory/useConfirmMemory.d.ts.map +1 -0
  103. package/memory/useConfirmMemory.js +43 -0
  104. package/memory/useConfirmMemory.js.map +1 -0
  105. package/memory/useDeleteMemory.d.ts +30 -0
  106. package/memory/useDeleteMemory.d.ts.map +1 -0
  107. package/memory/useDeleteMemory.js +42 -0
  108. package/memory/useDeleteMemory.js.map +1 -0
  109. package/memory/useMemories.d.ts +33 -0
  110. package/memory/useMemories.d.ts.map +1 -0
  111. package/memory/useMemories.js +42 -0
  112. package/memory/useMemories.js.map +1 -0
  113. package/memory/useRejectMemory.d.ts +34 -0
  114. package/memory/useRejectMemory.d.ts.map +1 -0
  115. package/memory/useRejectMemory.js +46 -0
  116. package/memory/useRejectMemory.js.map +1 -0
  117. package/memory/useUpdateMemoryContent.d.ts +32 -0
  118. package/memory/useUpdateMemoryContent.d.ts.map +1 -0
  119. package/memory/useUpdateMemoryContent.js +48 -0
  120. package/memory/useUpdateMemoryContent.js.map +1 -0
  121. package/models/ModelSelector.d.ts +33 -1
  122. package/models/ModelSelector.d.ts.map +1 -1
  123. package/models/ModelSelector.js +37 -4
  124. package/models/ModelSelector.js.map +1 -1
  125. package/models/index.d.ts +2 -0
  126. package/models/index.d.ts.map +1 -1
  127. package/models/index.js +1 -0
  128. package/models/index.js.map +1 -1
  129. package/models/registry.d.ts +16 -0
  130. package/models/registry.d.ts.map +1 -1
  131. package/models/registry.js +13 -0
  132. package/models/registry.js.map +1 -1
  133. package/models/thinking-mode.d.ts +27 -0
  134. package/models/thinking-mode.d.ts.map +1 -0
  135. package/models/thinking-mode.js +32 -0
  136. package/models/thinking-mode.js.map +1 -0
  137. package/oauth-app/OAuthAppDetailPanel.d.ts.map +1 -1
  138. package/oauth-app/OAuthAppDetailPanel.js +21 -20
  139. package/oauth-app/OAuthAppDetailPanel.js.map +1 -1
  140. package/package.json +4 -4
  141. package/platform-client/PlatformClientDetailPanel.d.ts.map +1 -1
  142. package/platform-client/PlatformClientDetailPanel.js +12 -13
  143. package/platform-client/PlatformClientDetailPanel.js.map +1 -1
  144. package/schedule/ScheduleDetailView.d.ts.map +1 -1
  145. package/schedule/ScheduleDetailView.js +21 -10
  146. package/schedule/ScheduleDetailView.js.map +1 -1
  147. package/schedule/ScheduleForm.d.ts.map +1 -1
  148. package/schedule/ScheduleForm.js +13 -3
  149. package/schedule/ScheduleForm.js.map +1 -1
  150. package/session/NewSessionViewer.d.ts.map +1 -1
  151. package/session/NewSessionViewer.js +9 -1
  152. package/session/NewSessionViewer.js.map +1 -1
  153. package/session/SessionViewer.d.ts +14 -1
  154. package/session/SessionViewer.d.ts.map +1 -1
  155. package/session/SessionViewer.js +21 -25
  156. package/session/SessionViewer.js.map +1 -1
  157. package/session/TranscriptExportMenu.d.ts +24 -0
  158. package/session/TranscriptExportMenu.d.ts.map +1 -0
  159. package/session/TranscriptExportMenu.js +26 -0
  160. package/session/TranscriptExportMenu.js.map +1 -0
  161. package/session/index.d.ts +4 -0
  162. package/session/index.d.ts.map +1 -1
  163. package/session/index.js +2 -0
  164. package/session/index.js.map +1 -1
  165. package/session/run-config.d.ts +9 -0
  166. package/session/run-config.d.ts.map +1 -1
  167. package/session/run-config.js +5 -0
  168. package/session/run-config.js.map +1 -1
  169. package/session/useExportTranscript.d.ts +44 -0
  170. package/session/useExportTranscript.d.ts.map +1 -0
  171. package/session/useExportTranscript.js +109 -0
  172. package/session/useExportTranscript.js.map +1 -0
  173. package/session/useNewSessionFlow.d.ts +11 -0
  174. package/session/useNewSessionFlow.d.ts.map +1 -1
  175. package/session/useNewSessionFlow.js +20 -6
  176. package/session/useNewSessionFlow.js.map +1 -1
  177. package/session/useSessionConversation.d.ts +12 -1
  178. package/session/useSessionConversation.d.ts.map +1 -1
  179. package/session/useSessionConversation.js +21 -42
  180. package/session/useSessionConversation.js.map +1 -1
  181. package/session/useSessionExecutions.d.ts +2 -14
  182. package/session/useSessionExecutions.d.ts.map +1 -1
  183. package/session/useSessionExecutions.js +4 -26
  184. package/session/useSessionExecutions.js.map +1 -1
  185. package/session/useSessionPageFlow.d.ts +23 -10
  186. package/session/useSessionPageFlow.d.ts.map +1 -1
  187. package/session/useSessionPageFlow.js +75 -19
  188. package/session/useSessionPageFlow.js.map +1 -1
  189. package/session/useUpdateSession.d.ts +18 -24
  190. package/session/useUpdateSession.d.ts.map +1 -1
  191. package/session/useUpdateSession.js +18 -24
  192. package/session/useUpdateSession.js.map +1 -1
  193. package/settings/MemorySection.d.ts +12 -0
  194. package/settings/MemorySection.d.ts.map +1 -0
  195. package/settings/MemorySection.js +21 -0
  196. package/settings/MemorySection.js.map +1 -0
  197. package/settings/index.d.ts +1 -0
  198. package/settings/index.d.ts.map +1 -1
  199. package/settings/index.js +1 -0
  200. package/settings/index.js.map +1 -1
  201. package/settings/settings-nav.d.ts.map +1 -1
  202. package/settings/settings-nav.js +2 -1
  203. package/settings/settings-nav.js.map +1 -1
  204. package/src/__tests__/public-api.test.ts +21 -0
  205. package/src/agent/AgentDetailView.tsx +2 -2
  206. package/src/agent/index.ts +0 -2
  207. package/src/agent/useUpdateAgent.ts +3 -3
  208. package/src/agent-instance/AgentInstanceDetailPanel.tsx +5 -7
  209. package/src/agent-instance/__tests__/AgentInstanceDetailPanel.test.tsx +75 -0
  210. package/src/agent-instance/useUpdateAgentInstance.ts +1 -1
  211. package/src/channel/AgentChannelsPanel.tsx +26 -1
  212. package/src/channel/ChannelRunConfigDialog.tsx +351 -0
  213. package/src/channel/__tests__/ChannelRunConfigDialog.test.tsx +210 -0
  214. package/src/channel/index.ts +2 -0
  215. package/src/channel/useSaveAgentChannel.ts +6 -3
  216. package/src/channel-app/ChannelAppDetailPanel.tsx +9 -9
  217. package/src/channel-app/__tests__/channelAppWhatsApp.test.tsx +2 -0
  218. package/src/channel-app/__tests__/channelAppWipeSafety.test.tsx +87 -0
  219. package/src/composer/ComposerToolbar.tsx +67 -0
  220. package/src/composer/SessionComposer.tsx +49 -3
  221. package/src/composer/__tests__/ComposerToolbar-autoApprove.test.tsx +80 -0
  222. package/src/composer/__tests__/SessionComposer-thinkingMode.test.tsx +176 -0
  223. package/src/composer/index.ts +1 -0
  224. package/src/cursor-accounts/CursorAccountsConsole.tsx +40 -5
  225. package/src/cursor-accounts/__tests__/CursorAccountsConsole.test.tsx +32 -0
  226. package/src/cursor-accounts/__tests__/cursor-account-coverage.test.ts +58 -1
  227. package/src/cursor-accounts/cursor-account-coverage.ts +55 -0
  228. package/src/execution/MessageThread.tsx +8 -11
  229. package/src/execution/useCreateAgentExecution.ts +19 -0
  230. package/src/execution/useFileChangeContent.ts +5 -12
  231. package/src/identity-account/AccountPreferencesPanel.tsx +154 -68
  232. package/src/identity-account/__tests__/AccountPreferencesPanel.test.tsx +36 -2
  233. package/src/identity-provider/IdentityProviderDetailPanel.tsx +18 -15
  234. package/src/identity-provider/__tests__/IdentityProviderDetailPanel.test.tsx +80 -0
  235. package/src/index.ts +36 -2
  236. package/src/internal/download.ts +29 -0
  237. package/src/library/useExportResource.ts +3 -19
  238. package/src/mcp-server/McpServerDetailView.tsx +2 -2
  239. package/src/mcp-server/index.ts +0 -2
  240. package/src/mcp-server/internal/definitionDrift.ts +22 -20
  241. package/src/mcp-server/useUpdateMcpServer.ts +3 -3
  242. package/src/memory/MemoryListPanel.tsx +532 -0
  243. package/src/memory/__tests__/MemoryListPanel.test.tsx +259 -0
  244. package/src/memory/__tests__/memoryHooks.test.tsx +220 -0
  245. package/src/memory/index.ts +19 -0
  246. package/src/memory/memoryGroups.ts +64 -0
  247. package/src/memory/useConfirmMemory.ts +62 -0
  248. package/src/memory/useDeleteMemory.ts +61 -0
  249. package/src/memory/useMemories.ts +66 -0
  250. package/src/memory/useRejectMemory.ts +65 -0
  251. package/src/memory/useUpdateMemoryContent.ts +67 -0
  252. package/src/models/ModelSelector.tsx +94 -1
  253. package/src/models/index.ts +2 -0
  254. package/src/models/registry.ts +29 -0
  255. package/src/models/thinking-mode.ts +48 -0
  256. package/src/oauth-app/OAuthAppDetailPanel.tsx +23 -21
  257. package/src/oauth-app/__tests__/OAuthAppDetailPanel.test.tsx +94 -0
  258. package/src/platform-client/PlatformClientDetailPanel.tsx +14 -13
  259. package/src/platform-client/__tests__/PlatformClientDetailPanel.test.tsx +82 -0
  260. package/src/schedule/ScheduleDetailView.tsx +29 -4
  261. package/src/schedule/ScheduleForm.tsx +14 -1
  262. package/src/session/NewSessionViewer.tsx +15 -1
  263. package/src/session/SessionViewer.tsx +48 -49
  264. package/src/session/TranscriptExportMenu.tsx +78 -0
  265. package/src/session/__tests__/SessionViewer-autoApproveToggle.test.tsx +178 -0
  266. package/src/session/__tests__/SessionViewer-transcriptExport.test.tsx +151 -0
  267. package/src/session/__tests__/TranscriptExportMenu.test.tsx +97 -0
  268. package/src/session/__tests__/useExportTranscript.test.tsx +187 -0
  269. package/src/session/__tests__/useNewSessionFlow.test.tsx +33 -0
  270. package/src/session/__tests__/useSessionPageFlow.autoApprove.test.tsx +176 -2
  271. package/src/session/index.ts +9 -0
  272. package/src/session/run-config.ts +16 -0
  273. package/src/session/useExportTranscript.ts +171 -0
  274. package/src/session/useNewSessionFlow.ts +34 -7
  275. package/src/session/useSessionConversation.ts +42 -56
  276. package/src/session/useSessionExecutions.ts +5 -26
  277. package/src/session/useSessionPageFlow.ts +98 -32
  278. package/src/session/useUpdateSession.ts +18 -24
  279. package/src/settings/MemorySection.tsx +45 -0
  280. package/src/settings/index.ts +1 -0
  281. package/src/settings/settings-nav.ts +2 -0
  282. package/src/usage/useExportCSV.ts +2 -14
  283. package/src/workflow/ApprovalFormBuilder.tsx +20 -5
  284. package/src/workflow/WorkflowDetailView.tsx +2 -2
  285. package/src/workflow/__tests__/WorkflowInstanceDetailPanel.test.tsx +83 -0
  286. package/src/workflow/index.ts +0 -2
  287. package/src/workflow/instance/WorkflowInstanceDetailPanel.tsx +6 -7
  288. package/src/workflow/useUpdateWorkflow.ts +3 -3
  289. package/styles.css +1 -1
  290. package/usage/useExportCSV.d.ts.map +1 -1
  291. package/usage/useExportCSV.js +2 -13
  292. package/usage/useExportCSV.js.map +1 -1
  293. package/workflow/ApprovalFormBuilder.d.ts.map +1 -1
  294. package/workflow/ApprovalFormBuilder.js +10 -8
  295. package/workflow/ApprovalFormBuilder.js.map +1 -1
  296. package/workflow/WorkflowDetailView.js +2 -2
  297. package/workflow/WorkflowDetailView.js.map +1 -1
  298. package/workflow/index.d.ts +0 -1
  299. package/workflow/index.d.ts.map +1 -1
  300. package/workflow/index.js +0 -1
  301. package/workflow/index.js.map +1 -1
  302. package/workflow/instance/WorkflowInstanceDetailPanel.d.ts.map +1 -1
  303. package/workflow/instance/WorkflowInstanceDetailPanel.js +6 -7
  304. package/workflow/instance/WorkflowInstanceDetailPanel.js.map +1 -1
  305. package/workflow/useUpdateWorkflow.d.ts +3 -3
  306. package/workflow/useUpdateWorkflow.js +3 -3
  307. package/agent/internal/agentToInput.d.ts +0 -15
  308. package/agent/internal/agentToInput.d.ts.map +0 -1
  309. package/agent/internal/agentToInput.js +0 -84
  310. package/agent/internal/agentToInput.js.map +0 -1
  311. package/mcp-server/internal/mcpServerToInput.d.ts +0 -12
  312. package/mcp-server/internal/mcpServerToInput.d.ts.map +0 -1
  313. package/mcp-server/internal/mcpServerToInput.js +0 -84
  314. package/mcp-server/internal/mcpServerToInput.js.map +0 -1
  315. package/session/session-spec-converters.d.ts +0 -24
  316. package/session/session-spec-converters.d.ts.map +0 -1
  317. package/session/session-spec-converters.js +0 -72
  318. package/session/session-spec-converters.js.map +0 -1
  319. package/src/agent/internal/__tests__/agentToInput.test.ts +0 -145
  320. package/src/agent/internal/agentToInput.ts +0 -102
  321. package/src/mcp-server/__tests__/mcpServerToInput.test.ts +0 -201
  322. package/src/mcp-server/internal/mcpServerToInput.ts +0 -98
  323. package/src/session/__tests__/session-spec-converters.test.ts +0 -183
  324. package/src/session/session-spec-converters.ts +0 -86
  325. package/src/workflow/internal/workflowToInput.ts +0 -96
  326. package/workflow/internal/workflowToInput.d.ts +0 -12
  327. package/workflow/internal/workflowToInput.d.ts.map +0 -1
  328. package/workflow/internal/workflowToInput.js +0 -73
  329. package/workflow/internal/workflowToInput.js.map +0 -1
@@ -3,7 +3,7 @@
3
3
  import { type FormEvent, type KeyboardEvent, useCallback, useId, useState } from "react";
4
4
  import { cn } from "@stigmer/theme";
5
5
  import { UNSTYLED_FIELDSET } from "../internal/element-resets.js";
6
- import { getUserMessage } from "@stigmer/sdk";
6
+ import { getUserMessage, toPlatformClientUpdateInput } from "@stigmer/sdk";
7
7
  import { IamRole } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
8
8
  import { timestampDate, type Timestamp } from "@bufbuild/protobuf/wkt";
9
9
  import type { PlatformClient } from "@stigmer/protos/ai/stigmer/iam/platformclient/v1/api_pb";
@@ -176,21 +176,22 @@ export function PlatformClientDetailPanel({
176
176
  e.preventDefault();
177
177
  clearUpdateError();
178
178
  try {
179
+ // Full-spec-replace safety: spread the complete mapped input and
180
+ // override only the edited fields, so unlisted spec fields (e.g.
181
+ // environment_refs, the embedded-assistant credential binding)
182
+ // survive the save. Cleared fields are set to undefined
183
+ // explicitly — omitting them would carry the stale mapped value.
179
184
  const updated = await update({
180
- name: meta?.name ?? "",
181
- slug: meta?.slug,
182
- org: meta?.org ?? "",
185
+ ...toPlatformClientUpdateInput(platformClient),
183
186
  neverExpires,
184
- ...(!neverExpires &&
185
- expiresAt && {
186
- expiresAt: new Date(expiresAt).toISOString(),
187
- }),
187
+ expiresAt:
188
+ !neverExpires && expiresAt ? new Date(expiresAt) : undefined,
188
189
  autoProvisionAccounts: autoProvision,
189
190
  autoGrantOnOrg: autoGrant,
190
- ...(autoGrant &&
191
- autoGrantRole !== IamRole.iam_role_unspecified && {
192
- autoGrantRole,
193
- }),
191
+ autoGrantRole:
192
+ autoGrant && autoGrantRole !== IamRole.iam_role_unspecified
193
+ ? autoGrantRole
194
+ : undefined,
194
195
  allowedOrigins: origins,
195
196
  });
196
197
  setMode("view");
@@ -200,7 +201,7 @@ export function PlatformClientDetailPanel({
200
201
  }
201
202
  },
202
203
  [
203
- meta,
204
+ platformClient,
204
205
  neverExpires,
205
206
  expiresAt,
206
207
  autoProvision,
@@ -0,0 +1,82 @@
1
+ import { describe, it, expect, vi, afterEach } from "vitest";
2
+ import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react";
3
+ import { create } from "@bufbuild/protobuf";
4
+ import { timestampFromDate } from "@bufbuild/protobuf/wkt";
5
+ import {
6
+ PlatformClientSchema,
7
+ type PlatformClient,
8
+ } from "@stigmer/protos/ai/stigmer/iam/platformclient/v1/api_pb";
9
+ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
10
+ import { IamRole } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
11
+ import type { PlatformClientInput } from "@stigmer/sdk";
12
+ import { StigmerContext } from "../../context";
13
+ import { PlatformClientDetailPanel } from "../PlatformClientDetailPanel";
14
+
15
+ /**
16
+ * Regression suite for the full-spec-replace wipe bug: before the
17
+ * generated-mapper migration, this panel hand-built its update input and
18
+ * NEVER sent `environment_refs` — saving any edit silently wiped the
19
+ * client's credential-delivery environment bindings. The panel must
20
+ * spread `toPlatformClientUpdateInput` and override only what it edits.
21
+ */
22
+
23
+ const PLATFORM_CLIENT: PlatformClient = create(PlatformClientSchema, {
24
+ metadata: {
25
+ id: "pc-1",
26
+ name: "Embed Client",
27
+ slug: "embed-client",
28
+ org: "acme",
29
+ },
30
+ spec: {
31
+ clientId: "client-abc",
32
+ expiresAt: timestampFromDate(new Date("2027-06-01T00:00:00Z")),
33
+ neverExpires: false,
34
+ autoProvisionAccounts: true,
35
+ autoGrantOnOrg: true,
36
+ autoGrantRole: IamRole.admin,
37
+ allowedOrigins: ["https://embed.acme.example"],
38
+ environmentRefs: [
39
+ { org: "acme", slug: "prod", kind: ApiResourceKind.environment },
40
+ ],
41
+ },
42
+ });
43
+
44
+ function renderPanel(update: ReturnType<typeof vi.fn>) {
45
+ const client = {
46
+ platformclient: { update },
47
+ } as never;
48
+ return render(
49
+ <StigmerContext.Provider value={client}>
50
+ <PlatformClientDetailPanel platformClient={PLATFORM_CLIENT} />
51
+ </StigmerContext.Provider>,
52
+ );
53
+ }
54
+
55
+ afterEach(cleanup);
56
+
57
+ describe("PlatformClientDetailPanel save payload", () => {
58
+ it("round-trips environment_refs on an origins-only edit (the wipe bug)", async () => {
59
+ const update = vi.fn(async (_input: PlatformClientInput) => PLATFORM_CLIENT);
60
+ renderPanel(update);
61
+
62
+ fireEvent.click(screen.getByRole("button", { name: "Edit" }));
63
+ fireEvent.click(screen.getByRole("button", { name: "Save changes" }));
64
+
65
+ await waitFor(() => expect(update).toHaveBeenCalledTimes(1));
66
+ const input = update.mock.calls[0]![0];
67
+
68
+ // The wipe-bug guard: the form does not render environment bindings,
69
+ // yet they must survive the save.
70
+ expect(input.environmentRefs).toEqual([
71
+ { org: "acme", slug: "prod", kind: ApiResourceKind.environment },
72
+ ]);
73
+ // Form-owned fields round-trip from the edit state.
74
+ expect(input.allowedOrigins).toEqual(["https://embed.acme.example"]);
75
+ expect(input.autoGrantRole).toBe(IamRole.admin);
76
+ expect(input.expiresAt).toBeInstanceOf(Date);
77
+ // Addressing fields for the update pipeline's org+slug lookup.
78
+ expect(input.org).toBe("acme");
79
+ expect(input.slug).toBe("embed-client");
80
+ expect(input.name).toBe("Embed Client");
81
+ });
82
+ });
@@ -37,7 +37,7 @@ import {
37
37
  type RunConfig,
38
38
  } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/invocation_pb";
39
39
  import { Harness } from "@stigmer/protos/ai/stigmer/agentic/session/v1/enum_pb";
40
- import { ServiceTier } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
40
+ import { ServiceTier, ThinkingMode } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
41
41
  import type { WorkspaceEntry } from "@stigmer/protos/ai/stigmer/agentic/session/v1/workspace_pb";
42
42
  import {
43
43
  cadenceToCron,
@@ -60,6 +60,11 @@ import {
60
60
  toProtoServiceTier,
61
61
  type ServiceTierOption,
62
62
  } from "../models/service-tier.js";
63
+ import {
64
+ fromProtoThinkingMode,
65
+ toProtoThinkingMode,
66
+ type ThinkingModeOption,
67
+ } from "../models/thinking-mode.js";
63
68
  import { deriveScheduleState, formatNextFire } from "./scheduleState.js";
64
69
  import {
65
70
  ScheduleRunsCompactList,
@@ -599,10 +604,10 @@ export function ScheduleDetailView({
599
604
  {editable && target ? (
600
605
  <EngineModelInlineEditor
601
606
  invocation={target}
602
- onSave={(harness, modelName, serviceTier) =>
607
+ onSave={(harness, modelName, serviceTier, thinkingMode) =>
603
608
  saveSpecField("engine-model", (s) => {
604
609
  if (s.target.case === "agent") {
605
- applyEngineModel(s.target.value, harness, modelName, serviceTier);
610
+ applyEngineModel(s.target.value, harness, modelName, serviceTier, thinkingMode);
606
611
  }
607
612
  })
608
613
  }
@@ -1016,6 +1021,7 @@ function EngineModelInlineEditor({
1016
1021
  harness: Harness,
1017
1022
  modelName: string,
1018
1023
  serviceTier: ServiceTierOption,
1024
+ thinkingMode: ThinkingModeOption,
1019
1025
  ) => Promise<boolean>;
1020
1026
  readonly isSaving: boolean;
1021
1027
  readonly error?: string;
@@ -1024,6 +1030,7 @@ function EngineModelInlineEditor({
1024
1030
  const [modelName, setModelName] = useState("");
1025
1031
  const [harness, setHarness] = useState<HarnessOption>("cursor");
1026
1032
  const [serviceTier, setServiceTier] = useState<ServiceTierOption>("standard");
1033
+ const [thinkingMode, setThinkingMode] = useState<ThinkingModeOption>("disabled");
1027
1034
 
1028
1035
  const startEdit = () => {
1029
1036
  setModelName(invocation.runConfig?.modelName ?? "");
@@ -1035,6 +1042,9 @@ function EngineModelInlineEditor({
1035
1042
  setServiceTier(
1036
1043
  fromProtoServiceTier(invocation.runConfig?.serviceTier) ?? "standard",
1037
1044
  );
1045
+ setThinkingMode(
1046
+ fromProtoThinkingMode(invocation.runConfig?.thinkingMode) ?? "disabled",
1047
+ );
1038
1048
  setIsEditing(true);
1039
1049
  };
1040
1050
 
@@ -1056,6 +1066,8 @@ function EngineModelInlineEditor({
1056
1066
  onHarnessChange={setHarness}
1057
1067
  serviceTier={serviceTier}
1058
1068
  onServiceTierChange={setServiceTier}
1069
+ thinkingMode={thinkingMode}
1070
+ onThinkingModeChange={setThinkingMode}
1059
1071
  placeholderLabel="Platform default"
1060
1072
  disabled={isSaving}
1061
1073
  />
@@ -1065,6 +1077,7 @@ function EngineModelInlineEditor({
1065
1077
  onClick={() => {
1066
1078
  setModelName("");
1067
1079
  setServiceTier("standard");
1080
+ setThinkingMode("disabled");
1068
1081
  }}
1069
1082
  disabled={isSaving}
1070
1083
  className="stg:rounded-md stg:px-2 stg:py-1 stg:text-[0.65rem] stg:text-muted-foreground stg:hover:text-foreground stg:focus-visible:outline-none stg:focus-visible:ring-2 stg:focus-visible:ring-ring stg:disabled:pointer-events-none stg:disabled:opacity-50"
@@ -1084,6 +1097,7 @@ function EngineModelInlineEditor({
1084
1097
  modelName !== "" ? toProtoHarness(harness) : Harness.UNSPECIFIED,
1085
1098
  modelName,
1086
1099
  serviceTier,
1100
+ thinkingMode,
1087
1101
  );
1088
1102
  if (ok) setIsEditing(false);
1089
1103
  }}
@@ -1169,14 +1183,17 @@ function applyEngineModel(
1169
1183
  harness: Harness,
1170
1184
  modelName: string,
1171
1185
  serviceTier: ServiceTierOption,
1186
+ thinkingMode: ThinkingModeOption,
1172
1187
  ): void {
1173
1188
  invocation.harness = harness;
1174
1189
  invocation.runConfig = normalizeRunConfig(
1175
1190
  modelName,
1176
1191
  invocation.runConfig?.maxCostUsd ?? 0,
1177
1192
  invocation.runConfig?.maxToolRounds ?? 0,
1178
- // The tier rides the model choice: no model, no tier (#357).
1193
+ // The variant attributes ride the model choice: no model, no tier and
1194
+ // no thinking (#357/#772).
1179
1195
  modelName.trim() !== "" ? serviceTier : "standard",
1196
+ modelName.trim() !== "" ? thinkingMode : "disabled",
1180
1197
  );
1181
1198
  }
1182
1199
 
@@ -1190,6 +1207,7 @@ function applyBudget(
1190
1207
  maxCostUsd ?? 0,
1191
1208
  invocation.runConfig?.maxToolRounds ?? 0,
1192
1209
  fromProtoServiceTier(invocation.runConfig?.serviceTier) ?? "standard",
1210
+ fromProtoThinkingMode(invocation.runConfig?.thinkingMode) ?? "disabled",
1193
1211
  );
1194
1212
  }
1195
1213
 
@@ -1198,12 +1216,14 @@ function normalizeRunConfig(
1198
1216
  maxCostUsd: number,
1199
1217
  maxToolRounds: number,
1200
1218
  serviceTier: ServiceTierOption,
1219
+ thinkingMode: ThinkingModeOption,
1201
1220
  ): RunConfig | undefined {
1202
1221
  const fields: {
1203
1222
  modelName?: string;
1204
1223
  maxCostUsd?: number;
1205
1224
  maxToolRounds?: number;
1206
1225
  serviceTier?: ServiceTier;
1226
+ thinkingMode?: ThinkingMode;
1207
1227
  } = {};
1208
1228
  if (modelName.trim() !== "") fields.modelName = modelName.trim();
1209
1229
  if (maxCostUsd > 0) fields.maxCostUsd = maxCostUsd;
@@ -1211,6 +1231,8 @@ function normalizeRunConfig(
1211
1231
  // Only an active fast choice is carried — an untouched tier stays
1212
1232
  // absent, preserving the unspecified-vs-explicit ledger distinction (#357).
1213
1233
  if (serviceTier === "fast") fields.serviceTier = toProtoServiceTier(serviceTier);
1234
+ // The tier's #772 twin: only an active enabled choice is carried.
1235
+ if (thinkingMode === "enabled") fields.thinkingMode = toProtoThinkingMode(thinkingMode);
1214
1236
 
1215
1237
  return Object.keys(fields).length > 0
1216
1238
  ? create(RunConfigSchema, fields)
@@ -1429,6 +1451,9 @@ function EngineModelSummary({
1429
1451
  if (invocation?.runConfig?.serviceTier === ServiceTier.FAST) {
1430
1452
  parts.push("Fast tier");
1431
1453
  }
1454
+ if (invocation?.runConfig?.thinkingMode === ThinkingMode.ENABLED) {
1455
+ parts.push("Thinking");
1456
+ }
1432
1457
  return <span className="stg:text-sm stg:text-foreground">{parts.join(" · ")}</span>;
1433
1458
  }
1434
1459
 
@@ -16,6 +16,7 @@ import { useGitHubConnection } from "../github/useGitHubConnection.js";
16
16
  import { ModelSelector } from "../models/ModelSelector.js";
17
17
  import { toProtoHarness, type HarnessOption } from "../models/harness.js";
18
18
  import { toProtoServiceTier, type ServiceTierOption } from "../models/service-tier.js";
19
+ import { toProtoThinkingMode, type ThinkingModeOption } from "../models/thinking-mode.js";
19
20
  import { Switch } from "../switch/Switch.js";
20
21
  import { useStigmerPortalContainer } from "../portal-container.js";
21
22
  import { useWorkspaceEntries } from "../workspace/useWorkspaceEntries.js";
@@ -116,6 +117,8 @@ export function ScheduleForm({
116
117
  // is picked. No model pinned = no tier pinned (fast requires a model,
117
118
  // and the form's reset button clears both).
118
119
  const [serviceTier, setServiceTier] = useState<ServiceTierOption>("standard");
120
+ // Thinking mode: the tier's #772 twin, capability-scoped the same way.
121
+ const [thinkingMode, setThinkingMode] = useState<ThinkingModeOption>("disabled");
119
122
 
120
123
  const [budgetUsd, setBudgetUsd] = useState("");
121
124
 
@@ -152,7 +155,7 @@ export function ScheduleForm({
152
155
  if (!canSubmit || !agentRef) return;
153
156
 
154
157
  clearError();
155
- const runConfig = buildRunConfig(modelName, budgetUsd, serviceTier);
158
+ const runConfig = buildRunConfig(modelName, budgetUsd, serviceTier, thinkingMode);
156
159
  try {
157
160
  const schedule = await create({
158
161
  name: trimmedName,
@@ -195,6 +198,7 @@ export function ScheduleForm({
195
198
  modelName,
196
199
  modelHarness,
197
200
  serviceTier,
201
+ thinkingMode,
198
202
  budgetUsd,
199
203
  workspace,
200
204
  onComplete,
@@ -362,6 +366,8 @@ export function ScheduleForm({
362
366
  onHarnessChange={setModelHarness}
363
367
  serviceTier={serviceTier}
364
368
  onServiceTierChange={setServiceTier}
369
+ thinkingMode={thinkingMode}
370
+ onThinkingModeChange={setThinkingMode}
365
371
  placeholderLabel="Platform default"
366
372
  disabled={isCreating}
367
373
  />
@@ -371,6 +377,7 @@ export function ScheduleForm({
371
377
  onClick={() => {
372
378
  setModelName("");
373
379
  setServiceTier("standard");
380
+ setThinkingMode("disabled");
374
381
  }}
375
382
  disabled={isCreating}
376
383
  className={cn(
@@ -514,6 +521,7 @@ function buildRunConfig(
514
521
  modelName: string,
515
522
  budgetUsd: string,
516
523
  serviceTier: ServiceTierOption,
524
+ thinkingMode: ThinkingModeOption,
517
525
  ): RunConfigInput | undefined {
518
526
  const model = modelName.trim();
519
527
  const cost = Number.parseFloat(budgetUsd);
@@ -528,6 +536,11 @@ function buildRunConfig(
528
536
  if (serviceTier === "fast" && model !== "") {
529
537
  config.serviceTier = toProtoServiceTier(serviceTier);
530
538
  }
539
+ // The tier's #772 twin: thinking is a per-model capability, same
540
+ // only-explicit-enabled + requires-a-model contract.
541
+ if (thinkingMode === "enabled" && model !== "") {
542
+ config.thinkingMode = toProtoThinkingMode(thinkingMode);
543
+ }
531
544
 
532
545
  return Object.keys(config).length > 0 ? config : undefined;
533
546
  }
@@ -6,7 +6,7 @@ import type { UseGitHubConnectionReturn } from "../github/useGitHubConnection.js
6
6
  import type { WorkspaceFileLister } from "../workspace/WorkspaceFileLister.js";
7
7
  import type { WorkspaceFileReader } from "../workspace/WorkspaceFileReader.js";
8
8
  import type { WorkspaceContentSearcher } from "../workspace/WorkspaceContentSearcher.js";
9
- import type { InteractionModeOption } from "../composer/index.js";
9
+ import type { ComposerAutoApproveProps, InteractionModeOption } from "../composer/index.js";
10
10
  import { SessionComposer } from "../composer/index.js";
11
11
  import type { HarnessOption } from "../models/harness.js";
12
12
  import type { AccountExecutionDefaults } from "../identity-account/useAccountExecutionDefaults.js";
@@ -299,6 +299,19 @@ export function NewSessionViewer({
299
299
  // SessionViewer derives (DD-016), gating the chip and the region together.
300
300
  const panelEnabled = panelMode !== "none" && !isGuest;
301
301
 
302
+ // Always-visible auto-approve toggle (#816), present before the first
303
+ // message is ever typed — the walk-away user arms it here and the create
304
+ // carries spec.auto_approve_all server-side. Guests never get it (the
305
+ // operator-consent reasoning as the #302 host default). Memoized per
306
+ // DD-010 — the composer is memo'd and an inline object would defeat it.
307
+ const autoApprove = useMemo<ComposerAutoApproveProps | undefined>(
308
+ () =>
309
+ isGuest
310
+ ? undefined
311
+ : { armed: flow.autoApproveAll, onChange: flow.setAutoApproveAll },
312
+ [isGuest, flow.autoApproveAll, flow.setAutoApproveAll],
313
+ );
314
+
302
315
  // Guest agent binding is host configuration, not a picker interaction:
303
316
  // the composer's agent machinery (picker, env-collection, personal
304
317
  // environments — all org reads a guest token cannot make) stays fully
@@ -452,6 +465,7 @@ export function NewSessionViewer({
452
465
  onInteractionModeChange={setInteractionMode}
453
466
  showInteractionModePicker={!isGuest}
454
467
  showModelSelector={modelSelectorVisible}
468
+ autoApprove={autoApprove}
455
469
  enableAttachments={!isGuest}
456
470
  defaultModelId={flow.modelId}
457
471
  onModelChange={flow.setModelId}
@@ -17,7 +17,7 @@ import {
17
17
  WorkspaceSurface,
18
18
  type SurfaceVirtualDocument,
19
19
  } from "../workspace/WorkspaceSurface.js";
20
- import type { InteractionModeOption, SessionComposerHandle, SessionComposerSubmitContext } from "../composer/index.js";
20
+ import type { ComposerAutoApproveProps, InteractionModeOption, SessionComposerHandle, SessionComposerSubmitContext } from "../composer/index.js";
21
21
  import type { ApplyResourceResult } from "../library/useApplyResource.js";
22
22
  import { SessionViewerLayout } from "./SessionViewerLayout.js";
23
23
  import { useWorkspaceEditors, isVirtualEntryId } from "../internal/store/index.js";
@@ -51,6 +51,7 @@ import { ArtifactDocument } from "../execution/ArtifactDocument.js";
51
51
  import { useSessionRailViews } from "./useSessionRailViews.js";
52
52
  import { useSessionPanel, type SessionPanelController } from "./useSessionPanel.js";
53
53
  import { SessionPanelChip } from "./SessionPanelChip.js";
54
+ import { TranscriptExportMenu } from "./TranscriptExportMenu.js";
54
55
  import { useSessionWriteBacks } from "./useSessionWriteBacks.js";
55
56
  import { useWorkspaceReadRefs } from "./useWorkspaceReadRefs.js";
56
57
  import {
@@ -293,6 +294,19 @@ export interface SessionViewerProps {
293
294
  * unopinionated about Console auth (DD-004).
294
295
  */
295
296
  readonly headerActions?: ReactNode;
297
+ /**
298
+ * Whether the viewer renders its built-in whole-conversation export
299
+ * control ({@link TranscriptExportMenu}) in the header corner
300
+ * (stigmer/stigmer#814). Default ON — a deliberate DD-011 divergence,
301
+ * owner-ratified: wherever a conversation can be viewed its transcript
302
+ * should be one click away, the control is an additive header button (no
303
+ * rendering-strategy or DOM-structure change to existing content), and
304
+ * the export carries only what the current caller can already read.
305
+ * Hosts that own their export surface (or inert demo scenes) opt out.
306
+ *
307
+ * @default true
308
+ */
309
+ readonly transcriptExport?: boolean;
296
310
  /**
297
311
  * Host-injected access management control (e.g. the Console's
298
312
  * `ManageAccessButton` with its own permission gating). Rendered inside
@@ -374,6 +388,7 @@ export function SessionViewer({
374
388
  panelOpen,
375
389
  onPanelOpenChange,
376
390
  headerActions,
391
+ transcriptExport = true,
377
392
  accessSlot,
378
393
  onApplied,
379
394
  threadSlots,
@@ -646,13 +661,23 @@ export function SessionViewer({
646
661
  return (
647
662
  <SessionViewerLayout
648
663
  className={className}
649
- // Top-right controls: host actions + the panel chip. The chip is the
650
- // panel's always-mounted toggle; while collapsed it carries only the
651
- // pending-item count. Execution status is never surfaced as header
652
- // chrome — the thread itself communicates run state. A viewer without
653
- // a panel (guests — session configuration is not a visitor's business
654
- // — or a host's panel="none") has no toggle: it is simply absent.
655
- headerActions={headerActions}
664
+ // Top-right controls: host actions + the built-in transcript export +
665
+ // the panel chip. The chip is the panel's always-mounted toggle; while
666
+ // collapsed it carries only the pending-item count. Execution status
667
+ // is never surfaced as header chrome — the thread itself communicates
668
+ // run state. A viewer without a panel (guests — session configuration
669
+ // is not a visitor's business — or a host's panel="none") has no
670
+ // toggle: it is simply absent.
671
+ headerActions={
672
+ transcriptExport ? (
673
+ <>
674
+ {headerActions}
675
+ <TranscriptExportMenu sessionId={sessionId} />
676
+ </>
677
+ ) : (
678
+ headerActions
679
+ )
680
+ }
656
681
  chip={
657
682
  panelEnabled ? (
658
683
  <SessionPanelChip
@@ -824,6 +849,20 @@ const ConversationColumn = memo(function ConversationColumn({
824
849
  void conv.stop();
825
850
  }, [conv.stop]);
826
851
 
852
+ // Always-visible auto-approve toggle (#816), replacing the old armed-only
853
+ // indicator banner: the walk-away user needs a way ON before any gate
854
+ // exists, not just a way off after one. Guests never get it (they don't
855
+ // inherit the host default and this is the operator's consent surface);
856
+ // observers have no composer at all. Memoized per DD-010 — the composer
857
+ // is memo'd and an inline object would defeat it.
858
+ const autoApprove = useMemo<ComposerAutoApproveProps | undefined>(
859
+ () =>
860
+ isGuest
861
+ ? undefined
862
+ : { armed: flow.autoApproveAll, onChange: flow.setAutoApproveAll },
863
+ [isGuest, flow.autoApproveAll, flow.setAutoApproveAll],
864
+ );
865
+
827
866
  // Edit-and-resubmit: stop the in-flight turn, pre-fill the composer with
828
867
  // the original text, and remember which execution is being edited. The
829
868
  // append-only execution log is never rewritten — submitting while editing
@@ -948,9 +987,6 @@ const ConversationColumn = memo(function ConversationColumn({
948
987
  />
949
988
  )}
950
989
  {!isObserver && sendError && <SendErrorBanner error={sendError} />}
951
- {!isObserver && flow.autoApproveAll && (
952
- <AutoApproveIndicator onTurnOff={() => flow.setAutoApproveAll(false)} />
953
- )}
954
990
  {/* Pending file reviews dock here — pinned above the composer so the
955
991
  decision the agent is blocked on can never scroll out of view. The
956
992
  thread renders only observational rows (badges) and read-only
@@ -992,6 +1028,7 @@ const ConversationColumn = memo(function ConversationColumn({
992
1028
  onInteractionModeChange={setInteractionMode}
993
1029
  showInteractionModePicker={!isGuest}
994
1030
  showModelSelector={modelSelectorVisible}
1031
+ autoApprove={autoApprove}
995
1032
  enableAttachments={!isGuest}
996
1033
  workspace={isGuest ? undefined : flow.workspace}
997
1034
  gitHubConnection={isGuest ? undefined : gitHubConnection}
@@ -1386,36 +1423,6 @@ function SessionStarting() {
1386
1423
  );
1387
1424
  }
1388
1425
 
1389
- /**
1390
- * Low-weight, always-visible indicator shown while the session-scoped
1391
- * auto-approve preference is active. The "Turn off" control reverts the
1392
- * preference in one click — the safety affordance for "Approve & don't ask
1393
- * again". Visible from the first render when the host pre-armed the
1394
- * preference via `StigmerProvider`'s `approvalDefaults` (#302); otherwise
1395
- * nothing about approvals appears until the user opts in at a gate. Either
1396
- * way the user keeps the last word.
1397
- */
1398
- function AutoApproveIndicator({ onTurnOff }: { onTurnOff: () => void }) {
1399
- return (
1400
- <div
1401
- role="status"
1402
- className="stg:flex stg:items-center stg:gap-2 stg:border-t stg:border-border-muted stg:px-4 stg:py-1.5 stg:text-xs stg:text-muted-foreground"
1403
- >
1404
- <ShieldCheckIcon />
1405
- <span className="stg:min-w-0 stg:flex-1 stg:truncate">
1406
- Auto-approving tool calls for this session
1407
- </span>
1408
- <button
1409
- type="button"
1410
- onClick={onTurnOff}
1411
- className="stg:shrink-0 stg:rounded stg:font-medium stg:text-foreground stg:underline-offset-2 stg:hover:underline stg:focus-visible:outline-none stg:focus-visible:ring-2 stg:focus-visible:ring-ring"
1412
- >
1413
- Turn off
1414
- </button>
1415
- </div>
1416
- );
1417
- }
1418
-
1419
1426
  /**
1420
1427
  * The plan document tab's empty state — reachable only when a streaming plan
1421
1428
  * auto-opened the tab and its turn then ended without publishing (stopped or
@@ -1616,11 +1623,3 @@ function LoaderIcon() {
1616
1623
  );
1617
1624
  }
1618
1625
 
1619
- function ShieldCheckIcon() {
1620
- return (
1621
- <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="stg:shrink-0 stg:text-success" aria-hidden="true">
1622
- <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
1623
- <path d="m9 12 2 2 4-4" />
1624
- </svg>
1625
- );
1626
- }
@@ -0,0 +1,78 @@
1
+ "use client";
2
+
3
+ import { Copy, Download, FileJson, FileText } from "lucide-react";
4
+ import { cn } from "@stigmer/theme";
5
+ import { ActionMenu } from "../action-menu/ActionMenu.js";
6
+ import { SpinnerIcon } from "../internal/SpinnerIcon.js";
7
+ import {
8
+ useExportTranscript,
9
+ type UseExportTranscriptOptions,
10
+ } from "./useExportTranscript.js";
11
+
12
+ /** Props for {@link TranscriptExportMenu}. */
13
+ export interface TranscriptExportMenuProps extends UseExportTranscriptOptions {
14
+ /** The session whose conversation is exported. */
15
+ readonly sessionId: string | null;
16
+ /** Additional CSS classes for the trigger button. */
17
+ readonly className?: string;
18
+ }
19
+
20
+ /**
21
+ * The whole-conversation export control (stigmer/stigmer#814): a quiet
22
+ * icon-button menu offering **Copy transcript**, **Download Markdown**, and
23
+ * **Download JSON**, powered by {@link useExportTranscript} — the canonical
24
+ * transcript assembly, thinking and resolved tool outputs included.
25
+ *
26
+ * `SessionViewer` renders it in its header corner by default; it is exported
27
+ * so hosts can mount the same control on any other surface a conversation is
28
+ * reachable from (a session list's row menu, a custom header).
29
+ *
30
+ * The export serializes exactly what the viewer already shows the current
31
+ * caller — the RPCs behind it enforce the same `can_view` permission — so
32
+ * offering it to every audience adds convenience, not exposure.
33
+ */
34
+ export function TranscriptExportMenu({
35
+ sessionId,
36
+ includeSuperseded,
37
+ className,
38
+ }: TranscriptExportMenuProps) {
39
+ const exporter = useExportTranscript(sessionId, { includeSuperseded });
40
+
41
+ return (
42
+ <ActionMenu>
43
+ <ActionMenu.Trigger
44
+ aria-label="Export transcript"
45
+ className={cn("stg:size-7", className)}
46
+ >
47
+ {exporter.isExporting ? (
48
+ <SpinnerIcon size={14} />
49
+ ) : (
50
+ <Download className="stg:size-3.5" aria-hidden="true" />
51
+ )}
52
+ </ActionMenu.Trigger>
53
+ <ActionMenu.Content>
54
+ <ActionMenu.Item
55
+ icon={<Copy className="stg:size-3.5" aria-hidden="true" />}
56
+ onSelect={() => void exporter.copyMarkdown()}
57
+ disabled={exporter.isExporting}
58
+ >
59
+ Copy transcript
60
+ </ActionMenu.Item>
61
+ <ActionMenu.Item
62
+ icon={<FileText className="stg:size-3.5" aria-hidden="true" />}
63
+ onSelect={() => void exporter.downloadMarkdown()}
64
+ disabled={exporter.isExporting}
65
+ >
66
+ Download Markdown
67
+ </ActionMenu.Item>
68
+ <ActionMenu.Item
69
+ icon={<FileJson className="stg:size-3.5" aria-hidden="true" />}
70
+ onSelect={() => void exporter.downloadJson()}
71
+ disabled={exporter.isExporting}
72
+ >
73
+ Download JSON
74
+ </ActionMenu.Item>
75
+ </ActionMenu.Content>
76
+ </ActionMenu>
77
+ );
78
+ }