@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
@@ -20,7 +20,9 @@ import { useAgentRefFromSession } from "./useAgentRefFromSession.js";
20
20
  import { usePersistedModel, type UsePersistedModelReturn } from "./usePersistedModel.js";
21
21
  import { toSessionUpdateInput } from "@stigmer/sdk";
22
22
  import type { SessionAudience } from "./audience.js";
23
+ import { isChannelOriginSession } from "./channelOrigin.js";
23
24
  import { assertValidRunConfig, type SessionRunConfig } from "./run-config.js";
25
+ import type { AccountExecutionDefaults } from "../identity-account/useAccountExecutionDefaults.js";
24
26
 
25
27
  /**
26
28
  * Well-known Daytona sandbox workspace root. Used as the SDK safety-net
@@ -68,6 +70,18 @@ export interface UseSessionPageFlowOptions {
68
70
  * config). See {@link SessionRunConfig}.
69
71
  */
70
72
  readonly runConfig?: SessionRunConfig;
73
+ /**
74
+ * The user's account-level execution defaults, typically from
75
+ * `useAccountExecutionDefaults()`. On the session page only
76
+ * `autoApprove` applies — it seeds the session-scoped auto-approve
77
+ * state so a user whose `default_auto_approve` preference is ON gets
78
+ * armed follow-ups on every session, not just newly created ones. An
79
+ * explicit in-session flip always wins for this session; the seed may
80
+ * arrive after mount (whoAmI resolves async) and still applies, being
81
+ * part of the derivation rather than an initializer. Ignored for the
82
+ * `"guest"` audience (guests inherit no identity-derived preference).
83
+ */
84
+ readonly accountDefaults?: AccountExecutionDefaults;
71
85
  }
72
86
 
73
87
  /** Return value of {@link useSessionPageFlow}. */
@@ -140,20 +154,38 @@ export interface UseSessionPageFlowReturn {
140
154
  readonly sessionVariables: UseSessionVariablesReturn;
141
155
 
142
156
  /**
143
- * Session-scoped "auto-approve tool calls" preference.
157
+ * Session-scoped "auto-approve tool calls" state (stigmer/stigmer#816).
158
+ *
159
+ * `false` by default. `true` when any of its sources arms it — the user's
160
+ * explicit choice always winning:
144
161
  *
145
- * `false` by default; a host can pre-arm it app-wide via
146
- * `StigmerProvider`'s `approvalDefaults` (#302, guests excluded). Flipped
147
- * to `true` when the user chooses "Approve & don't ask again" at an
148
- * approval gate (see {@link submitApproval}), and carried into every
149
- * subsequent follow-up via {@link handleSubmit}. Held in memory only —
150
- * reset on reload / new session back to the host default, never persisted
151
- * server-side.
162
+ * 1. the user's Config facet switch ({@link setAutoApproveAll});
163
+ * 2. "Approve & don't ask again" at an approval gate (see
164
+ * {@link submitApproval});
165
+ * 3. the account's `default_auto_approve` preference
166
+ * (`UseSessionPageFlowOptions.accountDefaults`, guests excluded);
167
+ * 4. the host's app-wide `StigmerProvider` `approvalDefaults` (#302,
168
+ * guests excluded);
169
+ * 5. the active in-flight execution having been created with
170
+ * `spec.auto_approve_all` (e.g. armed on the new-session surface), so
171
+ * the state survives the launcher → session-page handoff.
172
+ *
173
+ * While `true`, follow-ups carry `auto_approve_all` ({@link handleSubmit})
174
+ * and gates appearing in the in-flight run are auto-released (never for
175
+ * guest/observer/channel-origin surfaces). The explicit per-session flip is
176
+ * held in memory only — reset on reload back to the seeds; the account
177
+ * preference is the one persisted default, declared by the user in
178
+ * Account Preferences.
152
179
  */
153
180
  readonly autoApproveAll: boolean;
154
181
  /**
155
- * Toggle the session-scoped auto-approve preference. The reversible "Turn off"
156
- * control in the UI calls this with `false`.
182
+ * Set the user's explicit session-scoped auto-approve choice. Wired to the
183
+ * Config facet's switch; an explicit `false` wins over both seeds and over
184
+ * an armed in-flight run for everything the client controls (follow-up
185
+ * carry, gate auto-release) — an already-armed run's server-side bypass
186
+ * cannot be revoked mid-run, exactly as with today's gate-time "Approve &
187
+ * don't ask again". Never written back to the account preference: the
188
+ * flip is scoped to THIS conversation.
157
189
  */
158
190
  readonly setAutoApproveAll: (value: boolean) => void;
159
191
 
@@ -322,17 +354,40 @@ export function useSessionPageFlow(
322
354
  const [skillRefs, setSkillRefs] = useState<ResourceRef[]>([]);
323
355
  const initialSyncDone = useRef(false);
324
356
 
325
- // Session-scoped auto-approve. Armed either at the approval gate ("Approve &
326
- // don't ask again") or from the host's provider-level approvalDefaults
327
- // (#302) an app-level trust judgment, so it seeds the INITIAL state only;
328
- // the user's in-session "Turn off" always wins from then on. Guests never
329
- // inherit the host default (a share-link visitor is not the operator the
330
- // host's trust judgment covers). Lives only in memory for the life of this
331
- // page reset on reload re-applies the host default, never a user choice.
357
+ // Session-scoped auto-approve (#816). Derived like interactionMode above
358
+ // the user's explicit in-session decision wins, otherwise the truth is
359
+ // computed from its sources never an effect-synced copy:
360
+ //
361
+ // effective = userChoice ?? (accountSeed || hostSeed || runArmed)
362
+ //
363
+ // - `userChoice`: the Config facet's switch, or the gate-time "Approve &
364
+ // don't ask again" (which has always escalated the session preference).
365
+ // - `accountSeed`: the account's default_auto_approve preference — the
366
+ // user's own persisted walk-away default. Resolves async with whoAmI;
367
+ // being part of the derivation (not an initializer), a late arrival
368
+ // still applies unless the user has already flipped this session.
369
+ // - `hostSeed`: the host's provider-level approvalDefaults (#302) — an
370
+ // app-level trust judgment. Guests inherit neither seed (a share-link
371
+ // visitor is not the operator either trust judgment covers).
372
+ // - `runArmed`: the ACTIVE execution was created with
373
+ // spec.auto_approve_all, so the switch reflects an armed in-flight run —
374
+ // this is what carries the state across the new-session → session-page
375
+ // handoff. Deliberately derived from the active execution ONLY, never
376
+ // from history: deriving from past executions would silently survive a
377
+ // reload independent of the seeds above.
378
+ //
379
+ // The explicit choice lives only in memory for the life of this page —
380
+ // reset on reload re-applies the seeds (the account preference now being
381
+ // the persisted one the user actually declared), never a per-session flip.
332
382
  const approvalDefaults = useApprovalDefaults();
333
- const [autoApproveAll, setAutoApproveAll] = useState(
334
- () => !isGuest && (approvalDefaults?.autoApproveAll ?? false),
335
- );
383
+ const [autoApproveChoice, setAutoApproveChoice] = useState<boolean | null>(null);
384
+ const accountSeed = !isGuest && (options.accountDefaults?.autoApprove ?? false);
385
+ const hostSeed = !isGuest && (approvalDefaults?.autoApproveAll ?? false);
386
+ const runArmed = conv.activeStreamExecution?.spec?.autoApproveAll === true;
387
+ const autoApproveAll = autoApproveChoice ?? (accountSeed || hostSeed || runArmed);
388
+ const setAutoApproveAll = useCallback((value: boolean) => {
389
+ setAutoApproveChoice(value);
390
+ }, []);
336
391
 
337
392
  const submitApproval = useCallback<UseSessionConversationReturn["submitApproval"]>(
338
393
  async (toolCallId, action, comment) => {
@@ -341,13 +396,45 @@ export function useSessionPageFlow(
341
396
  // plane separately resolves the current execution's remaining gates and
342
397
  // the runner skips the gate for the rest of that run.
343
398
  if (action === ApprovalAction.APPROVE_ALL) {
344
- setAutoApproveAll(true);
399
+ setAutoApproveChoice(true);
345
400
  }
346
401
  await conv.submitApproval(toolCallId, action, comment);
347
402
  },
348
403
  [conv.submitApproval],
349
404
  );
350
405
 
406
+ // Armed responder (#816, the walk-away scenario): while auto-approve is ON,
407
+ // answer each approval gate as it appears in the in-flight run. This must be
408
+ // a standing responder, not a flip-time one-shot: a gate-time APPROVE_ALL
409
+ // grants a lease scoped to ONE tool class (see APPROVAL_ACTION_APPROVE_ALL
410
+ // in agentexecution enum.proto), so a later gate of a different class would
411
+ // still park the run — the responder covers each class as it surfaces, and
412
+ // every released call keeps an honest audit decision.
413
+ //
414
+ // Read-only surfaces must never auto-submit approvals on a viewer's behalf:
415
+ // the predicate mirrors SessionViewer's read-only derivation (guest audience,
416
+ // observer audience, channel-origin session) exactly.
417
+ const canRespondToGates =
418
+ !isGuest &&
419
+ options.audience !== "observer" &&
420
+ !isChannelOriginSession(conv.session);
421
+ const respondedToolCallIds = useRef<Set<string>>(new Set());
422
+ useEffect(() => {
423
+ if (!autoApproveAll || !canRespondToGates) return;
424
+ for (const approval of conv.pendingApprovals) {
425
+ const toolCallId = approval.toolCallId;
426
+ // Marked before the submit resolves so a re-render mid-flight cannot
427
+ // double-submit. A failed submission is deliberately NOT retried — the
428
+ // approval card stays for a manual decision instead of a retry storm.
429
+ if (!toolCallId || respondedToolCallIds.current.has(toolCallId)) continue;
430
+ respondedToolCallIds.current.add(toolCallId);
431
+ void conv.submitApproval(toolCallId, ApprovalAction.APPROVE_ALL).catch(() => {
432
+ // Swallowed by design (see above); the card's own error surface
433
+ // reports the failure where the user can act on it.
434
+ });
435
+ }
436
+ }, [autoApproveAll, canRespondToGates, conv.pendingApprovals, conv.submitApproval]);
437
+
351
438
  // -------------------------------------------------------------------------
352
439
  // Agent — derive from session, allow mid-session changes
353
440
  // -------------------------------------------------------------------------
@@ -0,0 +1,45 @@
1
+ "use client";
2
+
3
+ import { useId } from "react";
4
+ import { MemoryListPanel } from "../memory/MemoryListPanel.js";
5
+ import { useActiveOrgSlug } from "../organization/OrgProvider.js";
6
+
7
+ /**
8
+ * Settings section listing everything the platform remembers about the
9
+ * caller in the active organization.
10
+ *
11
+ * The helper copy carries the DD-006 D6 transparency statement: a
12
+ * confirmed memory is injected into the caller's future sessions and
13
+ * appears in those executions' records. Memory content everywhere else
14
+ * is subject-only — this page shows the caller their own records and
15
+ * nobody else's.
16
+ */
17
+ export function MemorySection() {
18
+ const headingId = useId();
19
+ const org = useActiveOrgSlug();
20
+
21
+ return (
22
+ <section aria-labelledby={headingId}>
23
+ <h2
24
+ id={headingId}
25
+ className="stg:text-foreground stg:mb-1 stg:text-sm stg:font-semibold"
26
+ >
27
+ Memory
28
+ </h2>
29
+ <p className="stg:text-muted-foreground stg:mb-6 stg:text-xs">
30
+ Facts agents proposed to remember about you in this organization.
31
+ Confirmed memories are shared with agents in your future sessions
32
+ and appear in those executions&apos; records. Only you can see
33
+ them here, and you can edit or delete any of them at any time.
34
+ </p>
35
+
36
+ {!org ? (
37
+ <p className="stg:text-muted-foreground stg:py-4 stg:text-center stg:text-xs">
38
+ Select an organization to view its memories.
39
+ </p>
40
+ ) : (
41
+ <MemoryListPanel org={org} />
42
+ )}
43
+ </section>
44
+ );
45
+ }
@@ -12,6 +12,7 @@ export { MembersSection } from "./MembersSection.js";
12
12
  export { OrgProfileSection } from "./OrgProfileSection.js";
13
13
  export { OrgPreferencesSection } from "./OrgPreferencesSection.js";
14
14
  export { AccountPreferencesSection } from "./AccountPreferencesSection.js";
15
+ export { MemorySection } from "./MemorySection.js";
15
16
  export { EnvironmentsSection } from "./EnvironmentsSection.js";
16
17
  export { InvitationsSection } from "./InvitationsSection.js";
17
18
  export { IdentityProvidersSection } from "./IdentityProvidersSection.js";
@@ -4,6 +4,7 @@ import {
4
4
  AppWindow,
5
5
  BarChart3,
6
6
  Box,
7
+ Brain,
7
8
  Building2,
8
9
  CreditCard,
9
10
  KeyRound,
@@ -108,6 +109,7 @@ export const SETTINGS_NAV_GROUPS: readonly SettingsNavGroup[] = [
108
109
  label: "Preferences",
109
110
  icon: UserCog,
110
111
  },
112
+ { href: "/settings/memory", label: "Memory", icon: Brain },
111
113
  ],
112
114
  },
113
115
  ];
@@ -17,6 +17,8 @@ export interface SwitchProps {
17
17
  readonly "aria-label"?: string;
18
18
  /** ID of the element that labels this switch. */
19
19
  readonly "aria-labelledby"?: string;
20
+ /** ID of the element that describes this switch (helper copy). */
21
+ readonly "aria-describedby"?: string;
20
22
  /** Element ID, for association with an external `<label htmlFor>`. */
21
23
  readonly id?: string;
22
24
  /** Additional CSS classes for the root button. */
@@ -54,6 +56,7 @@ export function Switch({
54
56
  disabled,
55
57
  "aria-label": ariaLabel,
56
58
  "aria-labelledby": ariaLabelledby,
59
+ "aria-describedby": ariaDescribedby,
57
60
  id,
58
61
  className,
59
62
  }: SwitchProps) {
@@ -65,6 +68,7 @@ export function Switch({
65
68
  aria-checked={checked}
66
69
  aria-label={ariaLabel}
67
70
  aria-labelledby={ariaLabelledby}
71
+ aria-describedby={ariaDescribedby}
68
72
  disabled={disabled}
69
73
  onClick={() => onCheckedChange(!checked)}
70
74
  className={cn(
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { useCallback, useState } from "react";
4
4
  import type { GetOrgUsageReportOutput } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/io_pb";
5
+ import { downloadTextFile } from "../internal/download.js";
5
6
 
6
7
  /** Export format for the CSV download. */
7
8
  export type ExportFormat = "daily_summary" | "model_breakdown";
@@ -41,7 +42,7 @@ export function useExportCSV(
41
42
  ? buildDailySummaryCSV(report, orgId)
42
43
  : buildModelBreakdownCSV(report, orgId);
43
44
 
44
- downloadCSV(csv, filename);
45
+ downloadTextFile(csv, filename, "text/csv");
45
46
  } finally {
46
47
  setIsExporting(false);
47
48
  }
@@ -100,16 +101,3 @@ function escapeCsvField(value: string): string {
100
101
  }
101
102
  return value;
102
103
  }
103
-
104
- function downloadCSV(csv: string, filename: string): void {
105
- const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
106
- const url = URL.createObjectURL(blob);
107
- const link = document.createElement("a");
108
- link.href = url;
109
- link.download = filename;
110
- link.style.display = "none";
111
- document.body.appendChild(link);
112
- link.click();
113
- document.body.removeChild(link);
114
- URL.revokeObjectURL(url);
115
- }
@@ -2,7 +2,7 @@ import { describe, it, expect } from "vitest";
2
2
  import { renderHook, act } from "@testing-library/react";
3
3
  import type { ReactNode } from "react";
4
4
  import { ReactFlowProvider } from "@xyflow/react";
5
- import type { Node } from "@xyflow/react";
5
+ import type { Node, OnNodeDrag } from "@xyflow/react";
6
6
  import { useWorkflowCanvas } from "../useWorkflowCanvas";
7
7
 
8
8
  // Regression tests for oss#609: isDirty was a model-reference comparison
@@ -54,7 +54,12 @@ function renderCanvas() {
54
54
 
55
55
  type CanvasResult = ReturnType<typeof renderCanvas>["result"];
56
56
 
57
- const DRAG_EVENT = {} as React.MouseEvent;
57
+ // The drag fixture derives its type from the handler itself, so it tracks
58
+ // whatever event type the installed @xyflow/react declares (the pinned
59
+ // 12.10.2 takes the React synthetic event; newer majors take the native
60
+ // event) — a dependency bump can never strand this cast again (#821's
61
+ // hand-written native-MouseEvent cast did not compile under npm ci).
62
+ const DRAG_EVENT = {} as Parameters<OnNodeDrag>[0];
58
63
 
59
64
  /** Drives one full drag gesture the way React Flow produces it. */
60
65
  function drag(result: CanvasResult, id: string, to: { x: number; y: number }) {
@@ -2,7 +2,7 @@ import { describe, it, expect } from "vitest";
2
2
  import { renderHook, act } from "@testing-library/react";
3
3
  import type { ReactNode } from "react";
4
4
  import { ReactFlowProvider } from "@xyflow/react";
5
- import type { Node } from "@xyflow/react";
5
+ import type { Node, OnNodeDrag } from "@xyflow/react";
6
6
  import { useWorkflowCanvas } from "../useWorkflowCanvas";
7
7
 
8
8
  // Regression tests for oss#602: node drags updated only React Flow's copy
@@ -51,7 +51,12 @@ function renderCanvas() {
51
51
 
52
52
  type CanvasResult = ReturnType<typeof renderCanvas>["result"];
53
53
 
54
- const DRAG_EVENT = {} as React.MouseEvent;
54
+ // The drag fixture derives its type from the handler itself, so it tracks
55
+ // whatever event type the installed @xyflow/react declares (the pinned
56
+ // 12.10.2 takes the React synthetic event; newer majors take the native
57
+ // event) — a dependency bump can never strand this cast again (#821's
58
+ // hand-written native-MouseEvent cast did not compile under npm ci).
59
+ const DRAG_EVENT = {} as Parameters<OnNodeDrag>[0];
55
60
 
56
61
  function canvasPosition(result: CanvasResult, id: string): { x: number; y: number } {
57
62
  const node = result.current.nodes.find((n) => n.id === id);
@@ -695,8 +695,16 @@ function EditorArea({
695
695
  <div className="stg:flex stg:h-full stg:min-h-0 stg:flex-col">
696
696
  {/* Collapse control + tab strip. The far right is kept clear (pr-24):
697
697
  the session viewer floats its top-right controls (host
698
- `headerActions` and the panel chip) over this region. */}
699
- <div className="stg:flex stg:shrink-0 stg:items-stretch stg:border-b stg:border-border stg:pr-24">
698
+ `headerActions` and the panel chip) over this region. Fixed to the
699
+ shared HEADER_ROW_HEIGHT so this row's bottom border stays flush
700
+ with the sidebar facet header's — and so an empty tab strip (no
701
+ open editors) cannot collapse the row. */}
702
+ <div
703
+ className={cn(
704
+ HEADER_ROW_HEIGHT,
705
+ "stg:flex stg:shrink-0 stg:items-stretch stg:border-b stg:border-border stg:pr-24",
706
+ )}
707
+ >
700
708
  <Tooltip>
701
709
  <TooltipTrigger
702
710
  render={
@@ -843,9 +851,24 @@ function Breadcrumbs({
843
851
  // Shared sidebar chrome
844
852
  // ---------------------------------------------------------------------------
845
853
 
854
+ /**
855
+ * One fixed height for every header row across the surface — the sidebar
856
+ * facet header AND the editor area's tab-strip row — so their bottom borders
857
+ * form a single continuous line across the panel. Fixed rather than
858
+ * padding-derived: the two rows have different content (micro-caps title vs
859
+ * text-xs tabs, sometimes no tabs at all), so padding math can never keep
860
+ * them aligned.
861
+ */
862
+ const HEADER_ROW_HEIGHT = "stg:h-8";
863
+
846
864
  function SidebarHeader({ title }: { readonly title: string }) {
847
865
  return (
848
- <div className="stg:shrink-0 stg:border-b stg:border-border stg:px-3 stg:py-2 stg:text-[0.65rem] stg:font-semibold stg:uppercase stg:tracking-wide stg:text-muted-foreground">
866
+ <div
867
+ className={cn(
868
+ HEADER_ROW_HEIGHT,
869
+ "stg:flex stg:shrink-0 stg:items-center stg:border-b stg:border-border stg:px-3 stg:text-[0.65rem] stg:font-semibold stg:uppercase stg:tracking-wide stg:text-muted-foreground",
870
+ )}
871
+ >
849
872
  {title}
850
873
  </div>
851
874
  );