@stigmer/react 3.12.6 → 3.12.8

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 (219) hide show
  1. package/composer/ComposerToolbar.d.ts.map +1 -1
  2. package/composer/ComposerToolbar.js.map +1 -1
  3. package/execution/MessageThread.d.ts.map +1 -1
  4. package/execution/MessageThread.js +8 -11
  5. package/execution/MessageThread.js.map +1 -1
  6. package/execution/ResultView.d.ts.map +1 -1
  7. package/execution/ResultView.js +6 -0
  8. package/execution/ResultView.js.map +1 -1
  9. package/execution/ToolCallItem.d.ts.map +1 -1
  10. package/execution/ToolCallItem.js +13 -2
  11. package/execution/ToolCallItem.js.map +1 -1
  12. package/execution/tool-categories.d.ts +1 -1
  13. package/execution/tool-categories.d.ts.map +1 -1
  14. package/execution/tool-categories.js +6 -0
  15. package/execution/tool-categories.js.map +1 -1
  16. package/execution/useFileChangeContent.d.ts +2 -6
  17. package/execution/useFileChangeContent.d.ts.map +1 -1
  18. package/execution/useFileChangeContent.js +4 -12
  19. package/execution/useFileChangeContent.js.map +1 -1
  20. package/identity-account/AccountPreferencesPanel.d.ts +2 -1
  21. package/identity-account/AccountPreferencesPanel.d.ts.map +1 -1
  22. package/identity-account/AccountPreferencesPanel.js +41 -3
  23. package/identity-account/AccountPreferencesPanel.js.map +1 -1
  24. package/identity-account/useAccountExecutionDefaults.d.ts +7 -0
  25. package/identity-account/useAccountExecutionDefaults.d.ts.map +1 -1
  26. package/identity-account/useAccountExecutionDefaults.js +5 -2
  27. package/identity-account/useAccountExecutionDefaults.js.map +1 -1
  28. package/index.d.ts +5 -2
  29. package/index.d.ts.map +1 -1
  30. package/index.js +5 -1
  31. package/index.js.map +1 -1
  32. package/internal/MemoryEnabledRow.d.ts +37 -0
  33. package/internal/MemoryEnabledRow.d.ts.map +1 -0
  34. package/internal/MemoryEnabledRow.js +25 -0
  35. package/internal/MemoryEnabledRow.js.map +1 -0
  36. package/internal/download.d.ts +8 -0
  37. package/internal/download.d.ts.map +1 -0
  38. package/internal/download.js +25 -0
  39. package/internal/download.js.map +1 -0
  40. package/library/useExportResource.d.ts.map +1 -1
  41. package/library/useExportResource.js +3 -17
  42. package/library/useExportResource.js.map +1 -1
  43. package/memory/MemoryListPanel.d.ts +35 -0
  44. package/memory/MemoryListPanel.d.ts.map +1 -0
  45. package/memory/MemoryListPanel.js +141 -0
  46. package/memory/MemoryListPanel.js.map +1 -0
  47. package/memory/MemoryProposalCard.d.ts +35 -0
  48. package/memory/MemoryProposalCard.d.ts.map +1 -0
  49. package/memory/MemoryProposalCard.js +73 -0
  50. package/memory/MemoryProposalCard.js.map +1 -0
  51. package/memory/index.d.ts +10 -0
  52. package/memory/index.d.ts.map +1 -0
  53. package/memory/index.js +14 -0
  54. package/memory/index.js.map +1 -0
  55. package/memory/memoryGroups.d.ts +31 -0
  56. package/memory/memoryGroups.d.ts.map +1 -0
  57. package/memory/memoryGroups.js +52 -0
  58. package/memory/memoryGroups.js.map +1 -0
  59. package/memory/useConfirmMemory.d.ts +31 -0
  60. package/memory/useConfirmMemory.d.ts.map +1 -0
  61. package/memory/useConfirmMemory.js +43 -0
  62. package/memory/useConfirmMemory.js.map +1 -0
  63. package/memory/useDeleteMemory.d.ts +30 -0
  64. package/memory/useDeleteMemory.d.ts.map +1 -0
  65. package/memory/useDeleteMemory.js +42 -0
  66. package/memory/useDeleteMemory.js.map +1 -0
  67. package/memory/useMemories.d.ts +33 -0
  68. package/memory/useMemories.d.ts.map +1 -0
  69. package/memory/useMemories.js +42 -0
  70. package/memory/useMemories.js.map +1 -0
  71. package/memory/useMemory.d.ts +39 -0
  72. package/memory/useMemory.d.ts.map +1 -0
  73. package/memory/useMemory.js +43 -0
  74. package/memory/useMemory.js.map +1 -0
  75. package/memory/useRejectMemory.d.ts +34 -0
  76. package/memory/useRejectMemory.d.ts.map +1 -0
  77. package/memory/useRejectMemory.js +46 -0
  78. package/memory/useRejectMemory.js.map +1 -0
  79. package/memory/useUpdateMemoryContent.d.ts +32 -0
  80. package/memory/useUpdateMemoryContent.d.ts.map +1 -0
  81. package/memory/useUpdateMemoryContent.js +48 -0
  82. package/memory/useUpdateMemoryContent.js.map +1 -0
  83. package/organization/OrgPreferencesPanel.d.ts.map +1 -1
  84. package/organization/OrgPreferencesPanel.js +36 -4
  85. package/organization/OrgPreferencesPanel.js.map +1 -1
  86. package/package.json +4 -4
  87. package/session/AutoApproveIndicator.d.ts +18 -0
  88. package/session/AutoApproveIndicator.d.ts.map +1 -0
  89. package/session/AutoApproveIndicator.js +23 -0
  90. package/session/AutoApproveIndicator.js.map +1 -0
  91. package/session/NewSessionViewer.d.ts.map +1 -1
  92. package/session/NewSessionViewer.js +10 -1
  93. package/session/NewSessionViewer.js.map +1 -1
  94. package/session/SessionViewer.d.ts +26 -1
  95. package/session/SessionViewer.d.ts.map +1 -1
  96. package/session/SessionViewer.js +28 -22
  97. package/session/SessionViewer.js.map +1 -1
  98. package/session/TranscriptExportMenu.d.ts +24 -0
  99. package/session/TranscriptExportMenu.d.ts.map +1 -0
  100. package/session/TranscriptExportMenu.js +26 -0
  101. package/session/TranscriptExportMenu.js.map +1 -0
  102. package/session/facets/SetupTab.d.ts +43 -6
  103. package/session/facets/SetupTab.d.ts.map +1 -1
  104. package/session/facets/SetupTab.js +32 -36
  105. package/session/facets/SetupTab.js.map +1 -1
  106. package/session/facets/UsageTab.d.ts +2 -1
  107. package/session/facets/UsageTab.d.ts.map +1 -1
  108. package/session/facets/UsageTab.js +11 -5
  109. package/session/facets/UsageTab.js.map +1 -1
  110. package/session/facets/index.d.ts +1 -1
  111. package/session/facets/index.d.ts.map +1 -1
  112. package/session/facets/primitives.d.ts +58 -0
  113. package/session/facets/primitives.d.ts.map +1 -0
  114. package/session/facets/primitives.js +59 -0
  115. package/session/facets/primitives.js.map +1 -0
  116. package/session/index.d.ts +5 -1
  117. package/session/index.d.ts.map +1 -1
  118. package/session/index.js +2 -0
  119. package/session/index.js.map +1 -1
  120. package/session/useExportTranscript.d.ts +44 -0
  121. package/session/useExportTranscript.d.ts.map +1 -0
  122. package/session/useExportTranscript.js +109 -0
  123. package/session/useExportTranscript.js.map +1 -0
  124. package/session/useNewSessionFlow.d.ts +16 -0
  125. package/session/useNewSessionFlow.d.ts.map +1 -1
  126. package/session/useNewSessionFlow.js +25 -6
  127. package/session/useNewSessionFlow.js.map +1 -1
  128. package/session/useSessionConversation.d.ts.map +1 -1
  129. package/session/useSessionConversation.js +7 -19
  130. package/session/useSessionConversation.js.map +1 -1
  131. package/session/useSessionExecutions.d.ts +2 -14
  132. package/session/useSessionExecutions.d.ts.map +1 -1
  133. package/session/useSessionExecutions.js +4 -26
  134. package/session/useSessionExecutions.js.map +1 -1
  135. package/session/useSessionPageFlow.d.ts +41 -10
  136. package/session/useSessionPageFlow.d.ts.map +1 -1
  137. package/session/useSessionPageFlow.js +67 -9
  138. package/session/useSessionPageFlow.js.map +1 -1
  139. package/settings/MemorySection.d.ts +12 -0
  140. package/settings/MemorySection.d.ts.map +1 -0
  141. package/settings/MemorySection.js +21 -0
  142. package/settings/MemorySection.js.map +1 -0
  143. package/settings/index.d.ts +1 -0
  144. package/settings/index.d.ts.map +1 -1
  145. package/settings/index.js +1 -0
  146. package/settings/index.js.map +1 -1
  147. package/settings/settings-nav.d.ts.map +1 -1
  148. package/settings/settings-nav.js +2 -1
  149. package/settings/settings-nav.js.map +1 -1
  150. package/src/__tests__/public-api.test.ts +21 -0
  151. package/src/attachment/__tests__/prepare-image.test.ts +12 -6
  152. package/src/composer/ComposerToolbar.tsx +1 -0
  153. package/src/execution/MessageThread.tsx +8 -11
  154. package/src/execution/ResultView.tsx +6 -0
  155. package/src/execution/ToolCallItem.tsx +18 -1
  156. package/src/execution/tool-categories.ts +7 -0
  157. package/src/execution/useFileChangeContent.ts +5 -12
  158. package/src/identity-account/AccountPreferencesPanel.tsx +89 -2
  159. package/src/identity-account/__tests__/AccountPreferencesPanel.test.tsx +112 -0
  160. package/src/identity-account/__tests__/useAccountExecutionDefaults.test.tsx +34 -1
  161. package/src/identity-account/useAccountExecutionDefaults.ts +12 -2
  162. package/src/index.ts +38 -0
  163. package/src/internal/MemoryEnabledRow.tsx +92 -0
  164. package/src/internal/download.ts +29 -0
  165. package/src/library/useExportResource.ts +3 -19
  166. package/src/memory/MemoryListPanel.tsx +532 -0
  167. package/src/memory/MemoryProposalCard.tsx +181 -0
  168. package/src/memory/__tests__/MemoryListPanel.test.tsx +259 -0
  169. package/src/memory/__tests__/MemoryProposalCard.test.tsx +236 -0
  170. package/src/memory/__tests__/memoryHooks.test.tsx +283 -0
  171. package/src/memory/index.ts +24 -0
  172. package/src/memory/memoryGroups.ts +64 -0
  173. package/src/memory/useConfirmMemory.ts +62 -0
  174. package/src/memory/useDeleteMemory.ts +61 -0
  175. package/src/memory/useMemories.ts +66 -0
  176. package/src/memory/useMemory.ts +80 -0
  177. package/src/memory/useRejectMemory.ts +65 -0
  178. package/src/memory/useUpdateMemoryContent.ts +67 -0
  179. package/src/organization/OrgPreferencesPanel.tsx +52 -3
  180. package/src/organization/__tests__/OrgPreferencesPanel.test.tsx +65 -0
  181. package/src/session/AutoApproveIndicator.tsx +45 -0
  182. package/src/session/NewSessionViewer.tsx +17 -0
  183. package/src/session/SessionViewer.tsx +78 -41
  184. package/src/session/TranscriptExportMenu.tsx +78 -0
  185. package/src/session/__tests__/NewSessionViewer.test.tsx +32 -0
  186. package/src/session/__tests__/SessionViewer-autoApprove.test.tsx +244 -0
  187. package/src/session/__tests__/SessionViewer-transcriptExport.test.tsx +189 -0
  188. package/src/session/__tests__/TranscriptExportMenu.test.tsx +97 -0
  189. package/src/session/__tests__/useExportTranscript.test.tsx +187 -0
  190. package/src/session/__tests__/useNewSessionFlow.test.tsx +89 -0
  191. package/src/session/__tests__/useSessionPageFlow.autoApprove.test.tsx +263 -2
  192. package/src/session/facets/SetupTab.tsx +193 -157
  193. package/src/session/facets/UsageTab.tsx +38 -38
  194. package/src/session/facets/index.ts +1 -0
  195. package/src/session/facets/primitives.tsx +169 -0
  196. package/src/session/index.ts +10 -0
  197. package/src/session/useExportTranscript.ts +171 -0
  198. package/src/session/useNewSessionFlow.ts +42 -6
  199. package/src/session/useSessionConversation.ts +10 -16
  200. package/src/session/useSessionExecutions.ts +5 -26
  201. package/src/session/useSessionPageFlow.ts +108 -21
  202. package/src/settings/MemorySection.tsx +45 -0
  203. package/src/settings/index.ts +1 -0
  204. package/src/settings/settings-nav.ts +2 -0
  205. package/src/switch/Switch.tsx +4 -0
  206. package/src/usage/useExportCSV.ts +2 -14
  207. package/src/workflow/__tests__/useWorkflowCanvas.dirty.test.tsx +7 -2
  208. package/src/workflow/__tests__/useWorkflowCanvas.drag.test.tsx +7 -2
  209. package/src/workspace/WorkspaceSurface.tsx +26 -3
  210. package/styles.css +1 -1
  211. package/switch/Switch.d.ts +3 -1
  212. package/switch/Switch.d.ts.map +1 -1
  213. package/switch/Switch.js +2 -2
  214. package/switch/Switch.js.map +1 -1
  215. package/usage/useExportCSV.d.ts.map +1 -1
  216. package/usage/useExportCSV.js +2 -13
  217. package/usage/useExportCSV.js.map +1 -1
  218. package/workspace/WorkspaceSurface.js +11 -2
  219. package/workspace/WorkspaceSurface.js.map +1 -1
@@ -32,6 +32,7 @@ export type ToolCategory =
32
32
  | "sub-agent"
33
33
  | "internal"
34
34
  | "mcp"
35
+ | "memory"
35
36
  | "unknown";
36
37
 
37
38
  /**
@@ -122,6 +123,11 @@ const PREVIEW_CATEGORIES: ReadonlySet<ToolCategory> = new Set<ToolCategory>([
122
123
  "mcp",
123
124
  "web-search",
124
125
  "fetch",
126
+ // A memory proposal is a decision surface: the verbatim fact and the
127
+ // Confirm/Reject actions ARE the content — never fold it behind a chevron
128
+ // (DD-005 D4: a proposal the user scrolled past is caught by the memory
129
+ // page, but the chip must not hide itself while in view).
130
+ "memory",
125
131
  "unknown",
126
132
  ]);
127
133
 
@@ -232,6 +238,7 @@ const KIND_DISPLAY: Partial<Record<ToolKind, KindDisplayEntry>> = {
232
238
  [ToolKind.THINK]: { category: "think", label: "Thinking", primaryField: "thought" },
233
239
  [ToolKind.SUBAGENT]: { category: "sub-agent", label: "Sub-agent", primaryField: "description", fallbackFields: ["prompt"] },
234
240
  [ToolKind.TODO]: { category: "internal", label: "Todos", primaryField: "todos" },
241
+ [ToolKind.MEMORY]: { category: "memory", label: "Remember", primaryField: "fact" },
235
242
  };
236
243
 
237
244
  /**
@@ -5,9 +5,14 @@ import type {
5
5
  FileChange,
6
6
  FileContent,
7
7
  } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
8
+ import { execIdFromStorageKey } from "@stigmer/sdk";
8
9
  import { useArtifactContent } from "./useArtifactContent.js";
9
10
  import { useArtifactDownloadUrl } from "./useArtifactDownloadUrl.js";
10
11
 
12
+ // The key-parsing rule lives in @stigmer/sdk with the other shared
13
+ // conversation-assembly rules; re-exported here for existing consumers.
14
+ export { execIdFromStorageKey };
15
+
11
16
  /** Return value of {@link useFileChangeContent}. */
12
17
  export interface UseFileChangeContentReturn {
13
18
  /**
@@ -135,15 +140,3 @@ function sideText(
135
140
  return fetched;
136
141
  }
137
142
 
138
- /**
139
- * Extracts the execution id from an artifact storage key of the form
140
- * `artifacts/{executionId}/...`. Returns `null` for an unexpected shape so the
141
- * caller skips the fetch rather than issuing a request the server would reject.
142
- */
143
- export function execIdFromStorageKey(storageKey: string): string | null {
144
- const parts = storageKey.split("/");
145
- if (parts.length >= 3 && parts[0] === "artifacts" && parts[1]) {
146
- return parts[1];
147
- }
148
- return null;
149
- }
@@ -18,8 +18,10 @@ import { useResourceAvailable, ApiResourceKind } from "../deployment-mode.js";
18
18
  import { useModelRegistry } from "../models/index.js";
19
19
  import { HARNESS_META, type HarnessOption } from "../models/harness.js";
20
20
  import { CloudFeatureNotice } from "../internal/CloudFeatureNotice.js";
21
+ import { MemoryEnabledRow } from "../internal/MemoryEnabledRow.js";
21
22
  import { StandingContextField } from "../internal/StandingContextField.js";
22
23
  import { SpinnerIcon } from "../internal/SpinnerIcon.js";
24
+ import { Switch } from "../switch/Switch.js";
23
25
 
24
26
  /** Props for {@link AccountPreferencesPanel}. */
25
27
  export interface AccountPreferencesPanelProps {
@@ -32,7 +34,8 @@ export interface AccountPreferencesPanelProps {
32
34
  /**
33
35
  * Self-contained editor for the current user's declared preferences
34
36
  * (`IdentityAccountSpec.preferences`): standing context plus the
35
- * execution defaults (default harness and per-harness default models).
37
+ * execution defaults (default harness, per-harness default models, and
38
+ * the auto-approve default).
36
39
  *
37
40
  * The declared text is snapshotted into the user's own eligible agent
38
41
  * executions and delivered to the agent as background context.
@@ -108,16 +111,26 @@ function AccountPreferencesForm({
108
111
  clearError,
109
112
  } = useUpdateIdentityAccount();
110
113
 
114
+ // The memory toggle saves instantly (its own hook instance, so a flip's
115
+ // in-flight/error state never bleeds into the form's Save button).
116
+ const {
117
+ update: updateMemoryFlag,
118
+ isUpdating: isSavingMemoryFlag,
119
+ error: memoryFlagError,
120
+ } = useUpdateIdentityAccount();
121
+
111
122
  const [standingContext, setStandingContext] = useState("");
112
123
  const [defaultHarness, setDefaultHarness] = useState("");
113
124
  const [defaultNativeModel, setDefaultNativeModel] = useState("");
114
125
  const [defaultCursorModel, setDefaultCursorModel] = useState("");
126
+ const [defaultAutoApprove, setDefaultAutoApprove] = useState(false);
115
127
 
116
128
  const serverPreferences = account?.spec?.preferences;
117
129
  const serverStandingContext = serverPreferences?.standingContext ?? "";
118
130
  const serverDefaultHarness = serverPreferences?.defaultHarness ?? "";
119
131
  const serverDefaultNativeModel = serverPreferences?.defaultNativeModel ?? "";
120
132
  const serverDefaultCursorModel = serverPreferences?.defaultCursorModel ?? "";
133
+ const serverDefaultAutoApprove = serverPreferences?.defaultAutoApprove ?? false;
121
134
 
122
135
  // Sync the form fields when server data changes.
123
136
  useEffect(() => {
@@ -127,6 +140,7 @@ function AccountPreferencesForm({
127
140
  setDefaultHarness(prefs?.defaultHarness ?? "");
128
141
  setDefaultNativeModel(prefs?.defaultNativeModel ?? "");
129
142
  setDefaultCursorModel(prefs?.defaultCursorModel ?? "");
143
+ setDefaultAutoApprove(prefs?.defaultAutoApprove ?? false);
130
144
  }, [account]);
131
145
 
132
146
  const hasChanges = useMemo(
@@ -134,16 +148,19 @@ function AccountPreferencesForm({
134
148
  standingContext.trim() !== serverStandingContext ||
135
149
  defaultHarness !== serverDefaultHarness ||
136
150
  defaultNativeModel !== serverDefaultNativeModel ||
137
- defaultCursorModel !== serverDefaultCursorModel,
151
+ defaultCursorModel !== serverDefaultCursorModel ||
152
+ defaultAutoApprove !== serverDefaultAutoApprove,
138
153
  [
139
154
  standingContext,
140
155
  defaultHarness,
141
156
  defaultNativeModel,
142
157
  defaultCursorModel,
158
+ defaultAutoApprove,
143
159
  serverStandingContext,
144
160
  serverDefaultHarness,
145
161
  serverDefaultNativeModel,
146
162
  serverDefaultCursorModel,
163
+ serverDefaultAutoApprove,
147
164
  ],
148
165
  );
149
166
 
@@ -154,12 +171,14 @@ function AccountPreferencesForm({
154
171
  setDefaultHarness(serverDefaultHarness);
155
172
  setDefaultNativeModel(serverDefaultNativeModel);
156
173
  setDefaultCursorModel(serverDefaultCursorModel);
174
+ setDefaultAutoApprove(serverDefaultAutoApprove);
157
175
  clearError();
158
176
  }, [
159
177
  serverStandingContext,
160
178
  serverDefaultHarness,
161
179
  serverDefaultNativeModel,
162
180
  serverDefaultCursorModel,
181
+ serverDefaultAutoApprove,
163
182
  clearError,
164
183
  ]);
165
184
 
@@ -183,6 +202,7 @@ function AccountPreferencesForm({
183
202
  defaultHarness: defaultHarness || undefined,
184
203
  defaultNativeModel: defaultNativeModel || undefined,
185
204
  defaultCursorModel: defaultCursorModel || undefined,
205
+ defaultAutoApprove: defaultAutoApprove || undefined,
186
206
  },
187
207
  });
188
208
  refetch();
@@ -198,6 +218,7 @@ function AccountPreferencesForm({
198
218
  defaultHarness,
199
219
  defaultNativeModel,
200
220
  defaultCursorModel,
221
+ defaultAutoApprove,
201
222
  update,
202
223
  clearError,
203
224
  refetch,
@@ -205,6 +226,30 @@ function AccountPreferencesForm({
205
226
  ],
206
227
  );
207
228
 
229
+ // The memory consent flag applies instantly (the UX-checkpoint decision):
230
+ // a consent bit flipped-but-unsaved that silently reverts on navigation is
231
+ // the failure consent UX must not have. Same wipe-safe double spread.
232
+ const handleMemoryToggle = useCallback(
233
+ async (next: boolean) => {
234
+ if (!account) return;
235
+ try {
236
+ const mapped = toIdentityAccountUpdateInput(account);
237
+ const updated = await updateMemoryFlag({
238
+ ...mapped,
239
+ preferences: {
240
+ ...mapped.preferences,
241
+ memoryEnabled: next || undefined,
242
+ },
243
+ });
244
+ refetch();
245
+ onUpdated?.(updated);
246
+ } catch {
247
+ // error state is managed by the toggle's own hook instance
248
+ }
249
+ },
250
+ [account, updateMemoryFlag, refetch, onUpdated],
251
+ );
252
+
208
253
  // -----------------------------------------------------------------------
209
254
  // Loading state
210
255
  // -----------------------------------------------------------------------
@@ -330,8 +375,50 @@ function AccountPreferencesForm({
330
375
  on that harness — even when it isn&apos;t your default.
331
376
  </p>
332
377
  </fieldset>
378
+
379
+ {/* Auto-approve default (stigmer/stigmer#816): a client-read seed
380
+ like the harness/model defaults above — the session panel's
381
+ Config switch beats it per conversation, and armed sessions
382
+ always show the "Auto-approving tool calls" strip by the
383
+ composer. Never applied to guest or observer surfaces. */}
384
+ <div
385
+ className={cn(
386
+ "stg:flex stg:items-start stg:justify-between stg:gap-3 stg:rounded-md stg:border stg:border-input stg:px-3 stg:py-2.5",
387
+ isUpdating && "stg:opacity-50",
388
+ )}
389
+ >
390
+ <div className="stg:min-w-0">
391
+ <span
392
+ id={`${baseId}-default-auto-approve-title`}
393
+ className="stg:block stg:text-xs stg:font-medium stg:text-foreground"
394
+ >
395
+ Auto-approve tool calls
396
+ </span>
397
+ <p className="stg:text-[0.65rem] stg:text-muted-foreground">
398
+ Start your sessions with tool calls running unattended — no
399
+ approval prompts. Each conversation can turn this off (or on)
400
+ in its session panel.
401
+ </p>
402
+ </div>
403
+ <Switch
404
+ checked={defaultAutoApprove}
405
+ onCheckedChange={setDefaultAutoApprove}
406
+ disabled={isUpdating}
407
+ aria-labelledby={`${baseId}-default-auto-approve-title`}
408
+ className="stg:mt-0.5"
409
+ />
410
+ </div>
333
411
  </div>
334
412
 
413
+ <MemoryEnabledRow
414
+ id={`${baseId}-memory-enabled`}
415
+ checked={account.spec?.preferences?.memoryEnabled ?? false}
416
+ onToggle={(next) => void handleMemoryToggle(next)}
417
+ saving={isSavingMemoryFlag}
418
+ error={memoryFlagError}
419
+ helperText="When on, agents may propose facts to remember about you; only facts you confirm are stored. Confirmed memories are shared with your future sessions and appear on those executions' records. Requires your organization to have memory enabled. Changes apply immediately."
420
+ />
421
+
335
422
  {updateError && (
336
423
  <p className="stg:text-destructive stg:text-[0.65rem]" role="alert">
337
424
  {getUserMessage(updateError)}
@@ -115,6 +115,52 @@ describe("AccountPreferencesPanel", () => {
115
115
  expect(input.slug).toBe("ada");
116
116
  });
117
117
 
118
+ it("saves the auto-approve default when the toggle is flipped on", async () => {
119
+ const update = vi.fn(async (_input: IdentityAccountInput) => ACCOUNT);
120
+ renderPanel(createMockStigmer({ update }));
121
+
122
+ await findSyncedField("Keep answers terse.");
123
+ const toggle = screen.getByRole("switch", { name: "Auto-approve tool calls" });
124
+ expect(toggle.getAttribute("aria-checked")).toBe("false");
125
+
126
+ fireEvent.click(toggle);
127
+ fireEvent.click(screen.getByRole("button", { name: "Save changes" }));
128
+
129
+ await waitFor(() => expect(update).toHaveBeenCalledTimes(1));
130
+ const input = update.mock.calls[0]![0];
131
+ expect(input.preferences?.defaultAutoApprove).toBe(true);
132
+ });
133
+
134
+ it("reflects a declared auto-approve default and omits it when turned off", async () => {
135
+ const armedAccount = create(IdentityAccountSchema, {
136
+ metadata: { id: "ia-1", name: "Ada Lovelace", slug: "ada", org: "acme" },
137
+ spec: {
138
+ idpId: "auth0|abc",
139
+ email: "ada@acme.example",
140
+ preferences: { defaultAutoApprove: true },
141
+ },
142
+ });
143
+ const update = vi.fn(async (_input: IdentityAccountInput) => armedAccount);
144
+ renderPanel(
145
+ createMockStigmer({ whoAmI: vi.fn(async () => armedAccount), update }),
146
+ );
147
+
148
+ const toggle = await screen.findByRole("switch", {
149
+ name: "Auto-approve tool calls",
150
+ });
151
+ await waitFor(() =>
152
+ expect(toggle.getAttribute("aria-checked")).toBe("true"),
153
+ );
154
+
155
+ fireEvent.click(toggle);
156
+ fireEvent.click(screen.getByRole("button", { name: "Save changes" }));
157
+
158
+ await waitFor(() => expect(update).toHaveBeenCalledTimes(1));
159
+ // Only a declared true travels — off is the absent zero value.
160
+ const input = update.mock.calls[0]![0];
161
+ expect(input.preferences?.defaultAutoApprove).toBeUndefined();
162
+ });
163
+
118
164
  it("refetches the account after a successful save", async () => {
119
165
  const whoAmI = vi.fn(async () => ACCOUNT);
120
166
  renderPanel(createMockStigmer({ whoAmI }));
@@ -137,6 +183,72 @@ describe("AccountPreferencesPanel", () => {
137
183
  expect(screen.getByRole("button", { name: "Retry" })).toBeTruthy();
138
184
  });
139
185
 
186
+ describe("memory consent (oss#293 Phase 2 Stage 3)", () => {
187
+ it("memory toggle applies instantly with the full mapped input (wipe-safe)", async () => {
188
+ const update = vi.fn(async (_input: IdentityAccountInput) => ACCOUNT);
189
+ renderPanel(createMockStigmer({ update }));
190
+ await findSyncedField("Keep answers terse.");
191
+
192
+ // No Save click: consent applies the moment it is flipped (the
193
+ // UX-checkpoint decision).
194
+ fireEvent.click(screen.getByRole("switch", { name: "Memory" }));
195
+
196
+ await waitFor(() => expect(update).toHaveBeenCalledTimes(1));
197
+ const input = update.mock.calls[0]![0];
198
+ expect(input.preferences?.memoryEnabled).toBe(true);
199
+ // The double spread: the flip preserves every sibling preference…
200
+ expect(input.preferences?.standingContext).toBe("Keep answers terse.");
201
+ // …and the identity fields the row never renders.
202
+ expect(input.id).toBe("ia-1");
203
+ expect(input.email).toBe("ada@acme.example");
204
+ });
205
+
206
+ it("saving the form preserves memory_enabled (the wipe-hazard regression)", async () => {
207
+ const accountWithMemoryOn = create(IdentityAccountSchema, {
208
+ metadata: { id: "ia-1", name: "Ada Lovelace", slug: "ada", org: "acme" },
209
+ spec: {
210
+ idpId: "auth0|abc",
211
+ email: "ada@acme.example",
212
+ preferences: {
213
+ standingContext: "Keep answers terse.",
214
+ memoryEnabled: true,
215
+ },
216
+ },
217
+ });
218
+ const update = vi.fn(async (_input: IdentityAccountInput) => accountWithMemoryOn);
219
+ renderPanel(
220
+ createMockStigmer({ whoAmI: vi.fn(async () => accountWithMemoryOn), update }),
221
+ );
222
+
223
+ const field = await findSyncedField("Keep answers terse.");
224
+ fireEvent.change(field, { target: { value: "Terser still." } });
225
+ fireEvent.click(screen.getByRole("button", { name: "Save changes" }));
226
+
227
+ await waitFor(() => expect(update).toHaveBeenCalledTimes(1));
228
+ // Update is a full-spec replace: without the nested preferences
229
+ // spread, this save would silently revoke the memory consent.
230
+ expect(update.mock.calls[0]![0].preferences?.memoryEnabled).toBe(true);
231
+ });
232
+
233
+ it("reflects the stored flag as the switch state", async () => {
234
+ const accountWithMemoryOn = create(IdentityAccountSchema, {
235
+ metadata: { id: "ia-1", name: "Ada", slug: "ada", org: "acme" },
236
+ spec: {
237
+ idpId: "auth0|abc",
238
+ preferences: { memoryEnabled: true },
239
+ },
240
+ });
241
+ renderPanel(createMockStigmer({ whoAmI: vi.fn(async () => accountWithMemoryOn) }));
242
+ await findSyncedField("");
243
+
244
+ await waitFor(() =>
245
+ expect(
246
+ screen.getByRole("switch", { name: "Memory" }).getAttribute("aria-checked"),
247
+ ).toBe("true"),
248
+ );
249
+ });
250
+ });
251
+
140
252
  describe("execution defaults (oss#293 Phase 1.5)", () => {
141
253
  const ACCOUNT_WITH_DEFAULTS: IdentityAccount = create(IdentityAccountSchema, {
142
254
  metadata: { id: "ia-1", name: "Ada Lovelace", slug: "ada", org: "acme" },
@@ -12,7 +12,7 @@ import { DeploymentModeContext } from "../../deployment-mode";
12
12
  import { useAccountExecutionDefaults } from "../useAccountExecutionDefaults";
13
13
 
14
14
  function accountWithPreferences(
15
- preferences: Record<string, string>,
15
+ preferences: Record<string, string | boolean>,
16
16
  ): IdentityAccount {
17
17
  return create(IdentityAccountSchema, {
18
18
  metadata: { id: "ia-1", name: "Ada", slug: "ada", org: "acme" },
@@ -102,6 +102,39 @@ describe("useAccountExecutionDefaults", () => {
102
102
  );
103
103
  });
104
104
 
105
+ it("a declared auto-approve default alone counts as a declared default", async () => {
106
+ const whoAmI = vi.fn(async () =>
107
+ accountWithPreferences({ defaultAutoApprove: true }),
108
+ );
109
+ const { result } = renderHook(() => useAccountExecutionDefaults(), {
110
+ wrapper: createWrapper(whoAmI),
111
+ });
112
+
113
+ await waitFor(() =>
114
+ expect(result.current).toEqual({
115
+ harness: undefined,
116
+ nativeModel: undefined,
117
+ cursorModel: undefined,
118
+ autoApprove: true,
119
+ }),
120
+ );
121
+ });
122
+
123
+ it("an unset auto-approve bool is no preference, not an explicit false", async () => {
124
+ const whoAmI = vi.fn(async () =>
125
+ accountWithPreferences({
126
+ defaultHarness: "native",
127
+ defaultAutoApprove: false,
128
+ }),
129
+ );
130
+ const { result } = renderHook(() => useAccountExecutionDefaults(), {
131
+ wrapper: createWrapper(whoAmI),
132
+ });
133
+
134
+ await waitFor(() => expect(result.current?.harness).toBe("native"));
135
+ expect(result.current?.autoApprove).toBeUndefined();
136
+ });
137
+
105
138
  it("returns undefined on a whoAmI failure — degrades to platform defaults", async () => {
106
139
  const whoAmI = vi.fn(async () => {
107
140
  throw new Error("boom");
@@ -16,6 +16,13 @@ export interface AccountExecutionDefaults {
16
16
  readonly nativeModel?: string;
17
17
  /** Preferred model for cursor-harness sessions, when declared. */
18
18
  readonly cursorModel?: string;
19
+ /**
20
+ * Start sessions with "auto-approve tool calls" armed
21
+ * (`default_auto_approve`, stigmer/stigmer#816). A seed like every other
22
+ * field here: an explicit in-session flip beats it for that conversation,
23
+ * and it is never applied to guest/observer surfaces.
24
+ */
25
+ readonly autoApprove?: boolean;
19
26
  }
20
27
 
21
28
  /**
@@ -56,8 +63,11 @@ export function useAccountExecutionDefaults(): AccountExecutionDefaults | undefi
56
63
  : undefined;
57
64
  const nativeModel = prefs.defaultNativeModel || undefined;
58
65
  const cursorModel = prefs.defaultCursorModel || undefined;
66
+ // Only a declared `true` travels — an unset bool is "no preference",
67
+ // matching the empty-string convention of the fields above.
68
+ const autoApprove = prefs.defaultAutoApprove || undefined;
59
69
 
60
- if (!harness && !nativeModel && !cursorModel) return undefined;
61
- return { harness, nativeModel, cursorModel };
70
+ if (!harness && !nativeModel && !cursorModel && !autoApprove) return undefined;
71
+ return { harness, nativeModel, cursorModel, autoApprove };
62
72
  }, [account]);
63
73
  }
package/src/index.ts CHANGED
@@ -139,6 +139,8 @@ export {
139
139
  useSessionList,
140
140
  useSessionExecutions,
141
141
  useSessionConversation,
142
+ useExportTranscript,
143
+ TranscriptExportMenu,
142
144
  useSessionArtifacts,
143
145
  artifactKey,
144
146
  useSessionWriteBacks,
@@ -189,6 +191,9 @@ export type {
189
191
  UseSessionExecutionsReturn,
190
192
  SendFollowUpOptions,
191
193
  UseSessionConversationReturn,
194
+ UseExportTranscriptOptions,
195
+ UseExportTranscriptReturn,
196
+ TranscriptExportMenuProps,
192
197
  SessionArtifactEntry,
193
198
  UseSessionArtifactsReturn,
194
199
  SessionWriteBackEntry,
@@ -222,8 +227,14 @@ export type {
222
227
  RuntimeEnvProvider,
223
228
  SessionAudience,
224
229
  SessionPanelMode,
230
+ // The #664 pinning seam's contract type: SessionViewer/NewSessionViewer/
231
+ // useNewSessionFlow all accept it, and the docs name it — embedders must
232
+ // be able to import it from the root (oss#802; the exports map exposes no
233
+ // ./session subpath on purpose).
234
+ SessionRunConfig,
225
235
  SetupTabProps,
226
236
  SetupTabMutationCallbacks,
237
+ SetupTabAutoApprove,
227
238
  } from "./session/index.js";
228
239
 
229
240
  // Activity — unified recent activity (sessions + workflow executions)
@@ -851,6 +862,32 @@ export type {
851
862
  AccountPreferencesPanelProps,
852
863
  } from "./identity-account/index.js";
853
864
 
865
+ // Memory — agent-proposed, user-confirmed facts: data hook, decision
866
+ // hooks (confirm/reject/delete/edit), grouping helpers, and the list panel
867
+ export {
868
+ useMemories,
869
+ useMemory,
870
+ useConfirmMemory,
871
+ useRejectMemory,
872
+ useDeleteMemory,
873
+ useUpdateMemoryContent,
874
+ groupMemoriesByLifecycle,
875
+ formatMemoryProvenance,
876
+ MemoryListPanel,
877
+ MemoryProposalCardBody,
878
+ } from "./memory/index.js";
879
+ export type {
880
+ UseMemoriesReturn,
881
+ UseMemoryReturn,
882
+ UseConfirmMemoryReturn,
883
+ UseRejectMemoryReturn,
884
+ UseDeleteMemoryReturn,
885
+ UseUpdateMemoryContentReturn,
886
+ MemoryGroups,
887
+ MemoryListPanelProps,
888
+ MemoryProposalCardBodyProps,
889
+ } from "./memory/index.js";
890
+
854
891
  // IAM Policy — data hooks, behavior hooks, headless hook, and styled components for access management
855
892
  export {
856
893
  useGrantableRoles,
@@ -1078,6 +1115,7 @@ export { MembersSection } from "./settings/index.js";
1078
1115
  export { OrgProfileSection } from "./settings/index.js";
1079
1116
  export { OrgPreferencesSection } from "./settings/index.js";
1080
1117
  export { AccountPreferencesSection } from "./settings/index.js";
1118
+ export { MemorySection } from "./settings/index.js";
1081
1119
  export { EnvironmentsSection } from "./settings/index.js";
1082
1120
  export { InvitationsSection } from "./settings/index.js";
1083
1121
  export { IdentityProvidersSection } from "./settings/index.js";
@@ -0,0 +1,92 @@
1
+ "use client";
2
+
3
+ import { cn } from "@stigmer/theme";
4
+ import { getUserMessage } from "@stigmer/sdk";
5
+ import { Switch } from "../switch/Switch.js";
6
+ import { SpinnerIcon } from "./SpinnerIcon.js";
7
+
8
+ /** Props for {@link MemoryEnabledRow}. */
9
+ export interface MemoryEnabledRowProps {
10
+ /** Id for the switch control (aria-labelledby wiring). */
11
+ readonly id: string;
12
+ /** The stored flag value (server state, or an optimistic pending value). */
13
+ readonly checked: boolean;
14
+ /** Fired with the next value when the user flips the switch. */
15
+ readonly onToggle: (next: boolean) => void;
16
+ /** `true` while the flip is being saved (switch disabled, spinner shown). */
17
+ readonly saving: boolean;
18
+ /** `true` when the caller may not edit the flag (read-only rendering). */
19
+ readonly readOnly?: boolean;
20
+ /** Error from the last failed flip, or `null`. */
21
+ readonly error: Error | null;
22
+ /**
23
+ * The scope-specific helper copy (DD-006 D6's transparency statement
24
+ * belongs here). Rendered under the title, wired as the switch's
25
+ * accessible description.
26
+ */
27
+ readonly helperText: string;
28
+ }
29
+
30
+ /**
31
+ * The `memory_enabled` consent toggle — one row, shared verbatim by
32
+ * {@link OrgPreferencesPanel} and {@link AccountPreferencesPanel} so the
33
+ * two scopes of the double opt-in (DD-006 D1) present identically.
34
+ *
35
+ * INSTANT-APPLY by deliberate exception (UX checkpoint, owner-approved
36
+ * 2026-08-22): both host panels are save-button forms, but a consent bit
37
+ * flipped-and-unsaved that silently reverts on navigation is exactly the
38
+ * failure consent UX must not have — so this row saves the moment it is
39
+ * flipped, matching the Switch primitive's own contract ("instant binary
40
+ * changes") and every peer product's memory toggle. The helper copy
41
+ * states it. Hosts wire `onToggle` to their own full-spec-replace update
42
+ * and pass the in-flight/error state back down.
43
+ */
44
+ export function MemoryEnabledRow({
45
+ id,
46
+ checked,
47
+ onToggle,
48
+ saving,
49
+ readOnly = false,
50
+ error,
51
+ helperText,
52
+ }: MemoryEnabledRowProps) {
53
+ const titleId = `${id}-title`;
54
+ const descriptionId = `${id}-description`;
55
+
56
+ return (
57
+ <div className="stg:space-y-1">
58
+ <div className="stg:flex stg:items-start stg:justify-between stg:gap-3">
59
+ <div className="stg:min-w-0 stg:flex-1">
60
+ <span
61
+ id={titleId}
62
+ className="stg:block stg:text-xs stg:font-medium stg:text-foreground"
63
+ >
64
+ Memory
65
+ </span>
66
+ <p
67
+ id={descriptionId}
68
+ className="stg:text-[0.65rem] stg:leading-snug stg:text-muted-foreground"
69
+ >
70
+ {helperText}
71
+ </p>
72
+ </div>
73
+ <span className="stg:flex stg:shrink-0 stg:items-center stg:gap-1.5">
74
+ {saving && <SpinnerIcon size={12} />}
75
+ <Switch
76
+ id={id}
77
+ checked={checked}
78
+ onCheckedChange={onToggle}
79
+ disabled={saving || readOnly}
80
+ aria-labelledby={titleId}
81
+ aria-describedby={descriptionId}
82
+ />
83
+ </span>
84
+ </div>
85
+ {error && (
86
+ <p className="stg:text-[0.65rem] stg:text-destructive" role="alert">
87
+ {getUserMessage(error)}
88
+ </p>
89
+ )}
90
+ </div>
91
+ );
92
+ }
@@ -0,0 +1,29 @@
1
+ // Client-side file download via Blob + object URL + anchor click.
2
+ //
3
+ // The one home of this routine — useExportResource, useExportCSV, and
4
+ // useExportTranscript all download through it. The pattern is proven inside
5
+ // the desktop (Tauri) webview as well as browsers, so SDK components can rely
6
+ // on it without a per-host seam.
7
+
8
+ /**
9
+ * Triggers a browser download of `content` as a UTF-8 text file.
10
+ *
11
+ * @param mimeType - Bare MIME type (e.g. `"text/markdown"`); the UTF-8
12
+ * charset is appended here so callers cannot drift on it.
13
+ */
14
+ export function downloadTextFile(
15
+ content: string,
16
+ filename: string,
17
+ mimeType: string,
18
+ ): void {
19
+ const blob = new Blob([content], { type: `${mimeType};charset=utf-8;` });
20
+ const url = URL.createObjectURL(blob);
21
+ const link = document.createElement("a");
22
+ link.href = url;
23
+ link.download = filename;
24
+ link.style.display = "none";
25
+ document.body.appendChild(link);
26
+ link.click();
27
+ document.body.removeChild(link);
28
+ URL.revokeObjectURL(url);
29
+ }