@stigmer/react 3.12.7 → 3.12.9

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 (196) hide show
  1. package/README.md +1 -3
  2. package/composer/ComposerToolbar.d.ts +1 -19
  3. package/composer/ComposerToolbar.d.ts.map +1 -1
  4. package/composer/ComposerToolbar.js +2 -7
  5. package/composer/ComposerToolbar.js.map +1 -1
  6. package/composer/SessionComposer.d.ts +0 -10
  7. package/composer/SessionComposer.d.ts.map +1 -1
  8. package/composer/SessionComposer.js +2 -2
  9. package/composer/SessionComposer.js.map +1 -1
  10. package/composer/index.d.ts +0 -1
  11. package/composer/index.d.ts.map +1 -1
  12. package/composer/index.js.map +1 -1
  13. package/conversation/ConversationTimelineView.d.ts +15 -1
  14. package/conversation/ConversationTimelineView.d.ts.map +1 -1
  15. package/conversation/ConversationTimelineView.js +3 -2
  16. package/conversation/ConversationTimelineView.js.map +1 -1
  17. package/conversation/ConversationsWorkbench.d.ts +14 -1
  18. package/conversation/ConversationsWorkbench.d.ts.map +1 -1
  19. package/conversation/ConversationsWorkbench.js +11 -2
  20. package/conversation/ConversationsWorkbench.js.map +1 -1
  21. package/execution/MessageThread.d.ts +33 -2
  22. package/execution/MessageThread.d.ts.map +1 -1
  23. package/execution/MessageThread.js +44 -6
  24. package/execution/MessageThread.js.map +1 -1
  25. package/execution/RecalledMemoriesCard.d.ts +47 -0
  26. package/execution/RecalledMemoriesCard.d.ts.map +1 -0
  27. package/execution/RecalledMemoriesCard.js +58 -0
  28. package/execution/RecalledMemoriesCard.js.map +1 -0
  29. package/execution/ResultView.d.ts.map +1 -1
  30. package/execution/ResultView.js +6 -0
  31. package/execution/ResultView.js.map +1 -1
  32. package/execution/ToolCallItem.d.ts.map +1 -1
  33. package/execution/ToolCallItem.js +13 -2
  34. package/execution/ToolCallItem.js.map +1 -1
  35. package/execution/index.d.ts +2 -0
  36. package/execution/index.d.ts.map +1 -1
  37. package/execution/index.js +1 -0
  38. package/execution/index.js.map +1 -1
  39. package/execution/tool-categories.d.ts +1 -1
  40. package/execution/tool-categories.d.ts.map +1 -1
  41. package/execution/tool-categories.js +6 -0
  42. package/execution/tool-categories.js.map +1 -1
  43. package/identity-account/AccountPreferencesPanel.d.ts +2 -1
  44. package/identity-account/AccountPreferencesPanel.d.ts.map +1 -1
  45. package/identity-account/AccountPreferencesPanel.js +41 -3
  46. package/identity-account/AccountPreferencesPanel.js.map +1 -1
  47. package/identity-account/useAccountExecutionDefaults.d.ts +7 -0
  48. package/identity-account/useAccountExecutionDefaults.d.ts.map +1 -1
  49. package/identity-account/useAccountExecutionDefaults.js +5 -2
  50. package/identity-account/useAccountExecutionDefaults.js.map +1 -1
  51. package/index.d.ts +6 -6
  52. package/index.d.ts.map +1 -1
  53. package/index.js +2 -2
  54. package/index.js.map +1 -1
  55. package/internal/MemoryEnabledRow.d.ts +37 -0
  56. package/internal/MemoryEnabledRow.d.ts.map +1 -0
  57. package/internal/MemoryEnabledRow.js +25 -0
  58. package/internal/MemoryEnabledRow.js.map +1 -0
  59. package/internal/VirtualizedThread.d.ts +3 -1
  60. package/internal/VirtualizedThread.d.ts.map +1 -1
  61. package/internal/VirtualizedThread.js +3 -1
  62. package/internal/VirtualizedThread.js.map +1 -1
  63. package/internal/useAutoScroll.d.ts +18 -0
  64. package/internal/useAutoScroll.d.ts.map +1 -1
  65. package/internal/useAutoScroll.js +50 -0
  66. package/internal/useAutoScroll.js.map +1 -1
  67. package/memory/MemoryProposalCard.d.ts +35 -0
  68. package/memory/MemoryProposalCard.d.ts.map +1 -0
  69. package/memory/MemoryProposalCard.js +73 -0
  70. package/memory/MemoryProposalCard.js.map +1 -0
  71. package/memory/index.d.ts +2 -0
  72. package/memory/index.d.ts.map +1 -1
  73. package/memory/index.js +2 -0
  74. package/memory/index.js.map +1 -1
  75. package/memory/useMemory.d.ts +39 -0
  76. package/memory/useMemory.d.ts.map +1 -0
  77. package/memory/useMemory.js +43 -0
  78. package/memory/useMemory.js.map +1 -0
  79. package/organization/OrgPreferencesPanel.d.ts.map +1 -1
  80. package/organization/OrgPreferencesPanel.js +36 -4
  81. package/organization/OrgPreferencesPanel.js.map +1 -1
  82. package/package.json +4 -4
  83. package/session/AutoApproveIndicator.d.ts +18 -0
  84. package/session/AutoApproveIndicator.d.ts.map +1 -0
  85. package/session/AutoApproveIndicator.js +23 -0
  86. package/session/AutoApproveIndicator.js.map +1 -0
  87. package/session/NewSessionViewer.d.ts.map +1 -1
  88. package/session/NewSessionViewer.js +10 -9
  89. package/session/NewSessionViewer.js.map +1 -1
  90. package/session/SessionViewer.d.ts +21 -9
  91. package/session/SessionViewer.d.ts.map +1 -1
  92. package/session/SessionViewer.js +31 -21
  93. package/session/SessionViewer.js.map +1 -1
  94. package/session/facets/SetupTab.d.ts +43 -6
  95. package/session/facets/SetupTab.d.ts.map +1 -1
  96. package/session/facets/SetupTab.js +32 -36
  97. package/session/facets/SetupTab.js.map +1 -1
  98. package/session/facets/UsageTab.d.ts +2 -1
  99. package/session/facets/UsageTab.d.ts.map +1 -1
  100. package/session/facets/UsageTab.js +11 -5
  101. package/session/facets/UsageTab.js.map +1 -1
  102. package/session/facets/index.d.ts +1 -1
  103. package/session/facets/index.d.ts.map +1 -1
  104. package/session/facets/primitives.d.ts +58 -0
  105. package/session/facets/primitives.d.ts.map +1 -0
  106. package/session/facets/primitives.js +59 -0
  107. package/session/facets/primitives.js.map +1 -0
  108. package/session/index.d.ts +1 -1
  109. package/session/index.d.ts.map +1 -1
  110. package/session/index.js.map +1 -1
  111. package/session/useNewSessionFlow.d.ts +9 -4
  112. package/session/useNewSessionFlow.d.ts.map +1 -1
  113. package/session/useNewSessionFlow.js +20 -9
  114. package/session/useNewSessionFlow.js.map +1 -1
  115. package/session/useSessionPageFlow.d.ts +28 -10
  116. package/session/useSessionPageFlow.d.ts.map +1 -1
  117. package/session/useSessionPageFlow.js +16 -10
  118. package/session/useSessionPageFlow.js.map +1 -1
  119. package/settings/MemorySection.d.ts.map +1 -1
  120. package/settings/MemorySection.js +1 -1
  121. package/settings/MemorySection.js.map +1 -1
  122. package/src/attachment/__tests__/prepare-image.test.ts +12 -6
  123. package/src/composer/ComposerToolbar.tsx +0 -57
  124. package/src/composer/SessionComposer.tsx +1 -13
  125. package/src/composer/index.ts +0 -1
  126. package/src/conversation/ConversationTimelineView.tsx +17 -1
  127. package/src/conversation/ConversationsWorkbench.tsx +25 -0
  128. package/src/conversation/__tests__/ConversationTimelineView.layout.test.tsx +37 -1
  129. package/src/execution/MessageThread.tsx +75 -3
  130. package/src/execution/RecalledMemoriesCard.tsx +164 -0
  131. package/src/execution/ResultView.tsx +6 -0
  132. package/src/execution/ToolCallItem.tsx +18 -1
  133. package/src/execution/__tests__/RecalledMemoriesCard.test.tsx +123 -0
  134. package/src/execution/__tests__/message-thread-recalled-memories.test.tsx +216 -0
  135. package/src/execution/__tests__/message-thread-scroll-on-send.test.tsx +141 -0
  136. package/src/execution/__tests__/message-thread-slots.test.tsx +36 -0
  137. package/src/execution/index.ts +2 -0
  138. package/src/execution/tool-categories.ts +7 -0
  139. package/src/identity-account/AccountPreferencesPanel.tsx +89 -2
  140. package/src/identity-account/__tests__/AccountPreferencesPanel.test.tsx +112 -0
  141. package/src/identity-account/__tests__/useAccountExecutionDefaults.test.tsx +34 -1
  142. package/src/identity-account/useAccountExecutionDefaults.ts +12 -2
  143. package/src/index.ts +8 -1
  144. package/src/internal/MemoryEnabledRow.tsx +92 -0
  145. package/src/internal/VirtualizedThread.tsx +5 -0
  146. package/src/internal/__tests__/useAutoScroll.layout.test.tsx +105 -4
  147. package/src/internal/__tests__/useAutoScroll.test.tsx +41 -1
  148. package/src/internal/useAutoScroll.ts +54 -0
  149. package/src/memory/MemoryProposalCard.tsx +181 -0
  150. package/src/memory/__tests__/MemoryProposalCard.test.tsx +236 -0
  151. package/src/memory/__tests__/memoryHooks.test.tsx +63 -0
  152. package/src/memory/index.ts +5 -0
  153. package/src/memory/useMemory.ts +80 -0
  154. package/src/organization/OrgPreferencesPanel.tsx +52 -3
  155. package/src/organization/__tests__/OrgPreferencesPanel.test.tsx +65 -0
  156. package/src/session/AutoApproveIndicator.tsx +45 -0
  157. package/src/session/NewSessionViewer.tsx +18 -15
  158. package/src/session/SessionViewer.tsx +70 -32
  159. package/src/session/__tests__/NewSessionViewer.test.tsx +32 -0
  160. package/src/session/__tests__/SessionViewer-autoApprove.test.tsx +244 -0
  161. package/src/session/__tests__/SessionViewer-transcriptExport.test.tsx +60 -22
  162. package/src/session/__tests__/useNewSessionFlow.test.tsx +56 -0
  163. package/src/session/__tests__/useSessionPageFlow.autoApprove.test.tsx +87 -0
  164. package/src/session/facets/SetupTab.tsx +193 -157
  165. package/src/session/facets/UsageTab.tsx +38 -38
  166. package/src/session/facets/index.ts +1 -0
  167. package/src/session/facets/primitives.tsx +169 -0
  168. package/src/session/index.ts +1 -0
  169. package/src/session/useNewSessionFlow.ts +30 -15
  170. package/src/session/useSessionPageFlow.ts +44 -20
  171. package/src/settings/MemorySection.tsx +4 -2
  172. package/src/switch/Switch.tsx +4 -0
  173. package/src/workflow/__tests__/WorkflowExecutionViewer.approvals.test.tsx +26 -7
  174. package/src/workflow/__tests__/task-presentation.test.ts +49 -12
  175. package/src/workflow/__tests__/useWorkflowCanvas.dirty.test.tsx +7 -2
  176. package/src/workflow/__tests__/useWorkflowCanvas.drag.test.tsx +7 -2
  177. package/src/workflow/__tests__/workflow-thread-scroll-on-send.test.tsx +145 -0
  178. package/src/workflow/thread/WorkflowTaskThread.tsx +59 -32
  179. package/src/workflow/thread/task-presentation.ts +52 -7
  180. package/src/workspace/WorkspaceSurface.tsx +26 -3
  181. package/styles.css +1 -1
  182. package/switch/Switch.d.ts +3 -1
  183. package/switch/Switch.d.ts.map +1 -1
  184. package/switch/Switch.js +2 -2
  185. package/switch/Switch.js.map +1 -1
  186. package/workflow/thread/WorkflowTaskThread.d.ts +13 -0
  187. package/workflow/thread/WorkflowTaskThread.d.ts.map +1 -1
  188. package/workflow/thread/WorkflowTaskThread.js +33 -25
  189. package/workflow/thread/WorkflowTaskThread.js.map +1 -1
  190. package/workflow/thread/task-presentation.d.ts.map +1 -1
  191. package/workflow/thread/task-presentation.js +50 -7
  192. package/workflow/thread/task-presentation.js.map +1 -1
  193. package/workspace/WorkspaceSurface.js +11 -2
  194. package/workspace/WorkspaceSurface.js.map +1 -1
  195. package/src/composer/__tests__/ComposerToolbar-autoApprove.test.tsx +0 -80
  196. package/src/session/__tests__/SessionViewer-autoApproveToggle.test.tsx +0 -178
@@ -0,0 +1,169 @@
1
+ "use client";
2
+
3
+ // Shared visual vocabulary for the session-panel facets (Config / Changes /
4
+ // Artifacts / Usage): one dense, VS Code-style row-and-section language so the
5
+ // facets read as one surface instead of three eras of styling. The Artifacts
6
+ // facet's `ArtifactRowView` set the idiom — compact rows, quiet metadata,
7
+ // hover/focus-revealed actions — and these primitives carry it to the
8
+ // non-file-list facets. All visual properties flow through `--stgm-*`
9
+ // tokens (DD-005).
10
+
11
+ import { cn } from "@stigmer/theme";
12
+
13
+ /**
14
+ * A titled facet section: uppercase micro-heading over flush content.
15
+ * `count` renders the "(N)" suffix the retired inspector used; `annotation`
16
+ * renders a normal-case, muted trailing note (e.g. "next message only").
17
+ */
18
+ export function FacetSection({
19
+ heading,
20
+ count,
21
+ annotation,
22
+ children,
23
+ }: {
24
+ readonly heading: string;
25
+ readonly count?: number;
26
+ readonly annotation?: string;
27
+ readonly children: React.ReactNode;
28
+ }) {
29
+ return (
30
+ <section className="stg:flex stg:flex-col stg:gap-1">
31
+ <h3 className="stg:px-2 stg:text-[0.65rem] stg:font-semibold stg:uppercase stg:tracking-wider stg:text-muted-foreground">
32
+ {heading}
33
+ {count != null && count > 0 && (
34
+ <span className="stg:ml-1 stg:text-muted-foreground-faint">({count})</span>
35
+ )}
36
+ {annotation && (
37
+ <span className="stg:ml-1 stg:font-normal stg:normal-case stg:tracking-normal stg:text-muted-foreground-faint">
38
+ ({annotation})
39
+ </span>
40
+ )}
41
+ </h3>
42
+ {children}
43
+ </section>
44
+ );
45
+ }
46
+
47
+ /**
48
+ * One dense, non-interactive facet row: leading content, optional
49
+ * right-aligned metadata, and hover/focus-revealed trailing actions
50
+ * (the `group` the actions' reveal keys on). Interactive rows (whole-row
51
+ * buttons) build on {@link FACET_ROW_BUTTON} instead.
52
+ */
53
+ export function FacetRow({
54
+ children,
55
+ meta,
56
+ actions,
57
+ className,
58
+ }: {
59
+ readonly children: React.ReactNode;
60
+ /** Right-aligned quiet metadata (counts, sizes, values). */
61
+ readonly meta?: React.ReactNode;
62
+ /** Hover/focus-revealed trailing controls — siblings, never nested. */
63
+ readonly actions?: React.ReactNode;
64
+ readonly className?: string;
65
+ }) {
66
+ return (
67
+ <div
68
+ className={cn(
69
+ "stg:group stg:flex stg:items-center stg:gap-2 stg:px-2 stg:py-1 stg:text-xs",
70
+ className,
71
+ )}
72
+ >
73
+ <span className="stg:flex stg:min-w-0 stg:flex-1 stg:items-center stg:gap-2 stg:text-foreground">
74
+ {children}
75
+ </span>
76
+ {meta && (
77
+ <span className="stg:shrink-0 stg:text-[0.65rem] stg:text-muted-foreground">
78
+ {meta}
79
+ </span>
80
+ )}
81
+ {actions}
82
+ </div>
83
+ );
84
+ }
85
+
86
+ /**
87
+ * Class string for a whole-row interactive facet action (the
88
+ * `ArtifactRowView` open-button idiom): dense row metrics plus hover and
89
+ * focus-visible treatments. A string constant (not a component) so callers
90
+ * keep their own element type and extra classes.
91
+ */
92
+ export const FACET_ROW_BUTTON = cn(
93
+ "stg:flex stg:w-full stg:min-w-0 stg:items-center stg:gap-2 stg:px-2 stg:py-1 stg:text-left stg:text-xs stg:text-muted-foreground stg:transition-colors",
94
+ "stg:hover:bg-muted stg:hover:text-foreground",
95
+ "stg:focus-visible:outline-none stg:focus-visible:ring-2 stg:focus-visible:ring-inset stg:focus-visible:ring-ring",
96
+ "stg:disabled:pointer-events-none stg:disabled:opacity-50",
97
+ );
98
+
99
+ /**
100
+ * Hover/focus-revealed remove control for a {@link FacetRow} — the
101
+ * `ArtifactRowView` download-button reveal pattern applied to removal.
102
+ * Always reachable by keyboard (focus reveals it without hover).
103
+ */
104
+ export function FacetRemoveButton({
105
+ onClick,
106
+ label,
107
+ }: {
108
+ readonly onClick: () => void;
109
+ readonly label: string;
110
+ }) {
111
+ return (
112
+ <button
113
+ type="button"
114
+ onClick={onClick}
115
+ aria-label={label}
116
+ className={cn(
117
+ "stg:flex stg:shrink-0 stg:items-center stg:text-muted-foreground stg:opacity-0 stg:transition-opacity",
118
+ "stg:group-hover:opacity-100 stg:focus-visible:opacity-100",
119
+ "stg:hover:text-destructive",
120
+ "stg:focus-visible:outline-none stg:focus-visible:ring-2 stg:focus-visible:ring-inset stg:focus-visible:ring-ring",
121
+ )}
122
+ >
123
+ <FacetXIcon />
124
+ </button>
125
+ );
126
+ }
127
+
128
+ /** Muted empty-state line for a facet section with nothing to list. */
129
+ export function FacetEmptyHint({ children }: { readonly children: React.ReactNode }) {
130
+ return (
131
+ <p className="stg:px-2 stg:text-xs stg:text-muted-foreground-faint">{children}</p>
132
+ );
133
+ }
134
+
135
+ /**
136
+ * A key→value config row ("Harness Native"): muted label left, value
137
+ * right — the dense inverse of a definition list, sized like every other
138
+ * facet row. `children` is the value; pass a control (e.g. a Switch) for
139
+ * interactive config rows.
140
+ */
141
+ export function FacetKeyValueRow({
142
+ label,
143
+ labelId,
144
+ children,
145
+ }: {
146
+ readonly label: string;
147
+ /** Optional id for the label span — point a control's `aria-labelledby` here. */
148
+ readonly labelId?: string;
149
+ readonly children: React.ReactNode;
150
+ }) {
151
+ return (
152
+ <div className="stg:flex stg:items-center stg:justify-between stg:gap-2 stg:px-2 stg:py-1 stg:text-xs">
153
+ <span id={labelId} className="stg:shrink-0 stg:text-muted-foreground">
154
+ {label}
155
+ </span>
156
+ <span className="stg:flex stg:min-w-0 stg:items-center stg:gap-1.5 stg:truncate stg:text-foreground">
157
+ {children}
158
+ </span>
159
+ </div>
160
+ );
161
+ }
162
+
163
+ function FacetXIcon() {
164
+ return (
165
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
166
+ <path d="M4 4L10 10M10 4L4 10" />
167
+ </svg>
168
+ );
169
+ }
@@ -170,6 +170,7 @@ export { SetupTab } from "./facets/index.js";
170
170
  export type {
171
171
  SetupTabProps,
172
172
  SetupTabMutationCallbacks,
173
+ SetupTabAutoApprove,
173
174
  } from "./facets/index.js";
174
175
 
175
176
  // Session utilities (re-exported from @stigmer/sdk)
@@ -116,6 +116,9 @@ export interface UseNewSessionFlowOptions {
116
116
  * - model: stored per-harness choice > the account's model for the
117
117
  * active harness (validated against the registry; a stale model
118
118
  * silently falls through) > the harness default.
119
+ * - autoApprove: explicit in-session flip > the account's
120
+ * `default_auto_approve` > the host's `approvalDefaults` (derived,
121
+ * so a late-arriving value applies without a touched-ref effect).
119
122
  *
120
123
  * Seeded values are never persisted to localStorage — the account
121
124
  * preference keeps applying until the user decides. Ignored for the
@@ -221,10 +224,12 @@ export interface UseNewSessionFlowReturn {
221
224
  /**
222
225
  * Pre-arm "auto-approve tool calls" for the session this surface will
223
226
  * create (stigmer/stigmer#816, the walk-away scenario). Seeded from the
224
- * host's `StigmerProvider` `approvalDefaults` (#302, guests excluded);
225
- * the user's toggle wins from then on. Carried into the bootstrap create
226
- * as `spec.auto_approve_all` the whole-run server-side bypass so the
227
- * first run stays covered even if the user closes the tab.
227
+ * account's `default_auto_approve` preference and the host's
228
+ * `StigmerProvider` `approvalDefaults` (#302) guests inherit neither;
229
+ * the user's explicit flip (the Config facet switch) wins from then on.
230
+ * Carried into the bootstrap create as `spec.auto_approve_all` the
231
+ * whole-run server-side bypass — so the first run stays covered even if
232
+ * the user closes the tab.
228
233
  */
229
234
  readonly autoApproveAll: boolean;
230
235
  /** Set the user's explicit auto-approve choice for the created session. */
@@ -323,18 +328,28 @@ export function useNewSessionFlow(
323
328
  const contextTarget = useExecutionTarget();
324
329
  const executionTarget = options.executionTarget ?? contextTarget;
325
330
  const adapter = useRunnerAdapter();
326
- // Auto-approve for the session about to be created (#816): the host
327
- // approval default (#302) seeds the INITIAL state only; the user's
328
- // composer toggle wins from then on. Guests never inherit the default
329
- // and never see the toggle — a share-link visitor is not the operator
330
- // the host's trust judgment covers (the GUEST_HARNESS
331
- // fixed-platform-policy reasoning). Carried into the bootstrap create as
332
- // spec.auto_approve_all, the whole-run server-side bypass the created
333
- // run stays covered even if this tab dies before its first gate.
331
+ // Auto-approve for the session about to be created (#816): derived like
332
+ // useSessionPageFlow's the user's explicit flip wins, otherwise the
333
+ // truth is computed from its seeds:
334
+ //
335
+ // effective = userChoice ?? (accountSeed || hostSeed)
336
+ //
337
+ // Derived rather than useState-initialized because the account preference
338
+ // (default_auto_approve) resolves async with whoAmI a mount-time
339
+ // initializer would silently miss it (the harness seed's late-arrival
340
+ // problem, solved here by derivation instead of a touched-ref effect).
341
+ // Guests inherit neither seed — a share-link visitor is not the operator
342
+ // either trust judgment covers (the GUEST_HARNESS fixed-platform-policy
343
+ // reasoning). Carried into the bootstrap create as spec.auto_approve_all,
344
+ // the whole-run server-side bypass — the created run stays covered even
345
+ // if this tab dies before its first gate.
334
346
  const approvalDefaults = useApprovalDefaults();
335
- const [autoApproveAll, setAutoApproveAll] = useState(
336
- () => !isGuest && (approvalDefaults?.autoApproveAll ?? false),
337
- );
347
+ const [autoApproveChoice, setAutoApproveAll] = useState<boolean | null>(null);
348
+ const autoApproveAll =
349
+ autoApproveChoice ??
350
+ (!isGuest &&
351
+ ((accountDefaults?.autoApprove ?? false) ||
352
+ (approvalDefaults?.autoApproveAll ?? false)));
338
353
 
339
354
  const [harness, setHarnessRaw] = useState<HarnessOption>(() => {
340
355
  // Guests get the fixed platform policy (see GUEST_HARNESS) and never
@@ -22,6 +22,7 @@ import { toSessionUpdateInput } from "@stigmer/sdk";
22
22
  import type { SessionAudience } from "./audience.js";
23
23
  import { isChannelOriginSession } from "./channelOrigin.js";
24
24
  import { assertValidRunConfig, type SessionRunConfig } from "./run-config.js";
25
+ import type { AccountExecutionDefaults } from "../identity-account/useAccountExecutionDefaults.js";
25
26
 
26
27
  /**
27
28
  * Well-known Daytona sandbox workspace root. Used as the SDK safety-net
@@ -69,6 +70,18 @@ export interface UseSessionPageFlowOptions {
69
70
  * config). See {@link SessionRunConfig}.
70
71
  */
71
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;
72
85
  }
73
86
 
74
87
  /** Return value of {@link useSessionPageFlow}. */
@@ -146,28 +159,33 @@ export interface UseSessionPageFlowReturn {
146
159
  * `false` by default. `true` when any of its sources arms it — the user's
147
160
  * explicit choice always winning:
148
161
  *
149
- * 1. the user's composer toggle ({@link setAutoApproveAll});
162
+ * 1. the user's Config facet switch ({@link setAutoApproveAll});
150
163
  * 2. "Approve & don't ask again" at an approval gate (see
151
164
  * {@link submitApproval});
152
- * 3. the host's app-wide `StigmerProvider` `approvalDefaults` (#302,
165
+ * 3. the account's `default_auto_approve` preference
166
+ * (`UseSessionPageFlowOptions.accountDefaults`, guests excluded);
167
+ * 4. the host's app-wide `StigmerProvider` `approvalDefaults` (#302,
153
168
  * guests excluded);
154
- * 4. the active in-flight execution having been created with
169
+ * 5. the active in-flight execution having been created with
155
170
  * `spec.auto_approve_all` (e.g. armed on the new-session surface), so
156
171
  * the state survives the launcher → session-page handoff.
157
172
  *
158
173
  * While `true`, follow-ups carry `auto_approve_all` ({@link handleSubmit})
159
174
  * and gates appearing in the in-flight run are auto-released (never for
160
- * guest/observer/channel-origin surfaces). Held in memory only — reset on
161
- * reload back to the host default, never persisted server-side.
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.
162
179
  */
163
180
  readonly autoApproveAll: boolean;
164
181
  /**
165
182
  * Set the user's explicit session-scoped auto-approve choice. Wired to the
166
- * composer's always-visible toggle; an explicit `false` wins over the host
167
- * default and over an armed in-flight run for everything the client
168
- * controls (follow-up carry, gate auto-release) — an already-armed run's
169
- * server-side bypass cannot be revoked mid-run, exactly as with today's
170
- * gate-time "Approve & don't ask again".
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.
171
189
  */
172
190
  readonly setAutoApproveAll: (value: boolean) => void;
173
191
 
@@ -340,27 +358,33 @@ export function useSessionPageFlow(
340
358
  // the user's explicit in-session decision wins, otherwise the truth is
341
359
  // computed from its sources — never an effect-synced copy:
342
360
  //
343
- // effective = userChoice ?? (hostSeed || runArmed)
361
+ // effective = userChoice ?? (accountSeed || hostSeed || runArmed)
344
362
  //
345
- // - `userChoice`: the composer toggle, or the gate-time "Approve & don't
346
- // ask again" (which has always escalated the session preference).
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.
347
369
  // - `hostSeed`: the host's provider-level approvalDefaults (#302) — an
348
- // app-level trust judgment. Guests never inherit it (a share-link
349
- // visitor is not the operator the host's trust judgment covers).
370
+ // app-level trust judgment. Guests inherit neither seed (a share-link
371
+ // visitor is not the operator either trust judgment covers).
350
372
  // - `runArmed`: the ACTIVE execution was created with
351
- // spec.auto_approve_all, so the toggle reflects an armed in-flight run —
373
+ // spec.auto_approve_all, so the switch reflects an armed in-flight run —
352
374
  // this is what carries the state across the new-session → session-page
353
375
  // handoff. Deliberately derived from the active execution ONLY, never
354
376
  // from history: deriving from past executions would silently survive a
355
- // reload and change the reset-on-reload consent contract below.
377
+ // reload independent of the seeds above.
356
378
  //
357
- // Lives only in memory for the life of this page — reset on reload
358
- // re-applies the host default, never a user choice.
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.
359
382
  const approvalDefaults = useApprovalDefaults();
360
383
  const [autoApproveChoice, setAutoApproveChoice] = useState<boolean | null>(null);
384
+ const accountSeed = !isGuest && (options.accountDefaults?.autoApprove ?? false);
361
385
  const hostSeed = !isGuest && (approvalDefaults?.autoApproveAll ?? false);
362
386
  const runArmed = conv.activeStreamExecution?.spec?.autoApproveAll === true;
363
- const autoApproveAll = autoApproveChoice ?? (hostSeed || runArmed);
387
+ const autoApproveAll = autoApproveChoice ?? (accountSeed || hostSeed || runArmed);
364
388
  const setAutoApproveAll = useCallback((value: boolean) => {
365
389
  setAutoApproveChoice(value);
366
390
  }, []);
@@ -29,8 +29,10 @@ export function MemorySection() {
29
29
  <p className="stg:text-muted-foreground stg:mb-6 stg:text-xs">
30
30
  Facts agents proposed to remember about you in this organization.
31
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.
32
+ and appear in those executions&apos; records; once you have many,
33
+ each conversation recalls the most relevant ones, shown on the
34
+ execution. Only you can see them here, and you can edit or delete
35
+ any of them at any time.
34
36
  </p>
35
37
 
36
38
  {!org ? (
@@ -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(
@@ -31,7 +31,11 @@ import {
31
31
  } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/event_pb";
32
32
  import type { WorkflowExecutionEvent } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/event_pb";
33
33
  import { WorkflowTaskKind } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/enum_pb";
34
- import { ExecutionPhase as AgentExecutionPhase } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
34
+ import {
35
+ ApprovalAction,
36
+ ExecutionPhase as AgentExecutionPhase,
37
+ FileDecisionAction,
38
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
35
39
  import type { DerivedCostSummary } from "../../internal/store/workflow-execution-event-store";
36
40
  import { WorkflowExecutionEventStore } from "../../internal/store/workflow-execution-event-store";
37
41
  import { useWorkflowExecution } from "../useWorkflowExecution";
@@ -303,14 +307,29 @@ describe("WorkflowExecutionViewer in-thread HITL (S10/T06/T07)", () => {
303
307
  const actions = arrange();
304
308
  render(<WorkflowExecutionViewer executionId="wex_1" />);
305
309
 
306
- // Identity, not equivalence: the transcript submits through the SAME
307
- // workflow-level handlers every other surface uses, so in-flight and
308
- // error state can never fork — and never the child's agentExecution.*
309
- // path (checked exhaustively in WorkflowAgentCallTranscript.test.tsx).
310
+ // The transcript submits through the SAME workflow-level handlers
311
+ // every other surface uses, so in-flight and error state can never
312
+ // fork — and never the child's agentExecution.* path (checked
313
+ // exhaustively in WorkflowAgentCallTranscript.test.tsx). State fields
314
+ // pass by IDENTITY; the submit fns are asserted by DELEGATION since
315
+ // the thread's scroll-on-send wrapper (stigmer-cloud#267) pins the
316
+ // view before handing each call to the single instance — a per-card
317
+ // duplicate would still fail here, because the delegate IS the
318
+ // viewer's own spy.
319
+ expect(mockedTranscript.mock.calls.length).toBeGreaterThan(0);
310
320
  for (const call of mockedTranscript.mock.calls) {
311
321
  const hitl = call[0].hitl!;
312
- expect(hitl.submitApproval).toBe(actions.submitApproval);
313
- expect(hitl.submitFileDecision).toBe(actions.submitFileDecision);
322
+ hitl.submitApproval("tc-probe", ApprovalAction.APPROVE);
323
+ expect(actions.submitApproval).toHaveBeenLastCalledWith(
324
+ "tc-probe",
325
+ ApprovalAction.APPROVE,
326
+ );
327
+ hitl.submitFileDecision("agx-probe", "cs-probe", FileDecisionAction.APPROVE);
328
+ expect(actions.submitFileDecision).toHaveBeenLastCalledWith(
329
+ "agx-probe",
330
+ "cs-probe",
331
+ FileDecisionAction.APPROVE,
332
+ );
314
333
  expect(hitl.approvalSubmittingToolCallIds).toBe(
315
334
  actions.approvalSubmittingToolCallIds,
316
335
  );
@@ -286,21 +286,55 @@ describe("per-kind preview lines", () => {
286
286
  overrides: { agentSlug: "blog-writer", messagesCount: 12, toolCallsCount: 5 },
287
287
  expected: "blog-writer · 12 msgs · 5 tools",
288
288
  },
289
+ {
290
+ label: "switch_case settled on a target branch (R6-5)",
291
+ kind: WorkflowTaskKind.switch_case,
292
+ overrides: { outputSummary: { __flow_directive__: "approved-path" } },
293
+ expected: "→ approved-path",
294
+ },
295
+ {
296
+ label: "switch_case settled on a termination directive",
297
+ kind: WorkflowTaskKind.switch_case,
298
+ overrides: { outputSummary: { __flow_directive__: "exit" } },
299
+ expected: "→ exit",
300
+ },
301
+ {
302
+ label: "try_catch recovered via catch retry (R6-5)",
303
+ kind: WorkflowTaskKind.try_catch,
304
+ overrides: { attemptNumber: 3, outputSummary: { result: "ok" } },
305
+ expected: "recovered after 3 attempts",
306
+ },
307
+ {
308
+ label: "try_catch settled block result (first-attempt success)",
309
+ kind: WorkflowTaskKind.try_catch,
310
+ overrides: { outputSummary: { result: 42 } },
311
+ expected: "42",
312
+ },
289
313
  ])("$label", ({ kind, overrides, expected }) => {
290
314
  const { previewLine } = resolveTaskPreview(state({ taskKind: kind, ...overrides }));
291
315
  expect(previewLine).toBe(expected);
292
316
  });
293
317
 
294
- // Kinds that deliberately stay status-only: control flow (branch/fork
295
- // detail needs graph topology the thread lacks deferred), raise_error
296
- // (the failure precedence carries the message), the invocation kinds
297
- // (no verified output envelope — backend follow-up), and unspecified
298
- // (the snapshot fallback).
318
+ // switch_case degrades to status-only when no case matched: the executor
319
+ // returns no directive, so there is honestly nothing to say (R6-5).
320
+ it("switch_case yields an empty line without a flow directive", () => {
321
+ const { previewLine } = resolveTaskPreview(
322
+ state({
323
+ taskKind: WorkflowTaskKind.switch_case,
324
+ outputSummary: { some: "data" },
325
+ }),
326
+ );
327
+ expect(previewLine).toBe("");
328
+ });
329
+
330
+ // Kinds that deliberately stay status-only: remaining control flow
331
+ // (for/fork detail needs graph topology the thread lacks — deferred),
332
+ // raise_error (the failure precedence carries the message; its preview
333
+ // BODY carries the detail), the invocation kinds (no verified output
334
+ // envelope — backend follow-up), and unspecified (the snapshot fallback).
299
335
  it.each([
300
- WorkflowTaskKind.switch_case,
301
336
  WorkflowTaskKind.for_each,
302
337
  WorkflowTaskKind.fork,
303
- WorkflowTaskKind.try_catch,
304
338
  WorkflowTaskKind.raise_error,
305
339
  WorkflowTaskKind.http_call,
306
340
  WorkflowTaskKind.grpc_call,
@@ -396,9 +430,12 @@ type JsonValueLike = Parameters<typeof valueSnippet>[0];
396
430
  describe("defaultDisclosureForKind", () => {
397
431
  // T05 (DD-T05-5): every kind whose output CAN matter is a preview kind —
398
432
  // the showBody gate keeps output-less cards as one-line rows, so preview
399
- // disclosure costs nothing until the runner writes real output. Only
400
- // genuinely body-less kinds (control flow, wait/listen, unspecified)
401
- // remain summary.
433
+ // disclosure costs nothing until the runner writes real output. R6-5
434
+ // adds try_catch (its output is the block's settled result) and
435
+ // raise_error (always fails; the preview body IS the always-visible
436
+ // failure detail). Only genuinely body-less kinds remain summary —
437
+ // including switch_case DELIBERATELY: its whole content is the one-word
438
+ // directive on its preview line; a body would only repeat it.
402
439
  it.each([
403
440
  [WorkflowTaskKind.transform, "preview"],
404
441
  [WorkflowTaskKind.validate, "preview"],
@@ -413,13 +450,13 @@ describe("defaultDisclosureForKind", () => {
413
450
  [WorkflowTaskKind.grpc_call, "preview"],
414
451
  [WorkflowTaskKind.activity_call, "preview"],
415
452
  [WorkflowTaskKind.run_workflow, "preview"],
453
+ [WorkflowTaskKind.try_catch, "preview"],
454
+ [WorkflowTaskKind.raise_error, "preview"],
416
455
  [WorkflowTaskKind.wait, "summary"],
417
456
  [WorkflowTaskKind.switch_case, "summary"],
418
457
  [WorkflowTaskKind.fork, "summary"],
419
- [WorkflowTaskKind.try_catch, "summary"],
420
458
  [WorkflowTaskKind.for_each, "summary"],
421
459
  [WorkflowTaskKind.listen, "summary"],
422
- [WorkflowTaskKind.raise_error, "summary"],
423
460
  [WorkflowTaskKind.workflow_task_kind_unspecified, "summary"],
424
461
  ] as const)("kind %d defaults to %s", (kind, expected) => {
425
462
  expect(defaultDisclosureForKind(kind)).toBe(expected);
@@ -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);