@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
@@ -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
+ }
@@ -39,6 +39,15 @@ export type {
39
39
  UseSessionConversationReturn,
40
40
  } from "./useSessionConversation.js";
41
41
 
42
+ export { useExportTranscript } from "./useExportTranscript.js";
43
+ export type {
44
+ UseExportTranscriptOptions,
45
+ UseExportTranscriptReturn,
46
+ } from "./useExportTranscript.js";
47
+
48
+ export { TranscriptExportMenu } from "./TranscriptExportMenu.js";
49
+ export type { TranscriptExportMenuProps } from "./TranscriptExportMenu.js";
50
+
42
51
  export { useSessionArtifacts, artifactKey } from "./useSessionArtifacts.js";
43
52
  export type {
44
53
  SessionArtifactEntry,
@@ -161,6 +170,7 @@ export { SetupTab } from "./facets/index.js";
161
170
  export type {
162
171
  SetupTabProps,
163
172
  SetupTabMutationCallbacks,
173
+ SetupTabAutoApprove,
164
174
  } from "./facets/index.js";
165
175
 
166
176
  // Session utilities (re-exported from @stigmer/sdk)
@@ -0,0 +1,171 @@
1
+ "use client";
2
+
3
+ import { useCallback, useMemo, useState } from "react";
4
+ import {
5
+ fetchSessionTranscript,
6
+ resolvedSubject,
7
+ transcriptToJson,
8
+ transcriptToMarkdown,
9
+ type SessionTranscript,
10
+ } from "@stigmer/sdk";
11
+ import { toast } from "../feedback/toast.js";
12
+ import { useStigmer } from "../hooks.js";
13
+ import { downloadTextFile } from "../internal/download.js";
14
+ import { generateSlug } from "../internal/slug.js";
15
+ import { toError } from "../internal/toError.js";
16
+
17
+ /** Options for {@link useExportTranscript}. */
18
+ export interface UseExportTranscriptOptions {
19
+ /**
20
+ * Keep turns replaced by edit-and-resubmit in the export, marked as
21
+ * superseded. Defaults to `false` — the conversation as the viewer
22
+ * shows it.
23
+ */
24
+ readonly includeSuperseded?: boolean;
25
+ }
26
+
27
+ /** Return value of {@link useExportTranscript}. */
28
+ export interface UseExportTranscriptReturn {
29
+ /** Copy the whole conversation as Markdown to the clipboard. */
30
+ readonly copyMarkdown: () => Promise<void>;
31
+ /** Download the whole conversation as a Markdown file. */
32
+ readonly downloadMarkdown: () => Promise<void>;
33
+ /** Download the whole conversation as a structured JSON file. */
34
+ readonly downloadJson: () => Promise<void>;
35
+ /** `true` while a transcript fetch (and offload resolution) is in flight. */
36
+ readonly isExporting: boolean;
37
+ /** The last export failure, or `null`. Cleared when a new export starts. */
38
+ readonly error: Error | null;
39
+ }
40
+
41
+ /**
42
+ * Behavior hook that exports a session's whole conversation — thinking,
43
+ * tool calls with offloaded outputs resolved, sub-agent turns, timestamps —
44
+ * as Markdown (copy or download) or structured JSON (stigmer/stigmer#814).
45
+ *
46
+ * Lazy: nothing is fetched until an action is invoked. Each action fetches
47
+ * the canonical transcript via `fetchSessionTranscript` (the SDK's one
48
+ * authoritative assembly), so every export surface produces identical
49
+ * content. Feedback follows the `useExportResource` convention: toasts for
50
+ * success/failure, plus a typed `error` for hosts that render their own.
51
+ *
52
+ * @example
53
+ * ```tsx
54
+ * const exporter = useExportTranscript(sessionId);
55
+ *
56
+ * <ActionMenu.Item onSelect={exporter.copyMarkdown}>Copy transcript</ActionMenu.Item>
57
+ * <ActionMenu.Item onSelect={exporter.downloadMarkdown}>Download .md</ActionMenu.Item>
58
+ * <ActionMenu.Item onSelect={exporter.downloadJson}>Download .json</ActionMenu.Item>
59
+ * ```
60
+ */
61
+ export function useExportTranscript(
62
+ sessionId: string | null,
63
+ options?: UseExportTranscriptOptions,
64
+ ): UseExportTranscriptReturn {
65
+ const stigmer = useStigmer();
66
+ const includeSuperseded = options?.includeSuperseded === true;
67
+ const [isExporting, setIsExporting] = useState(false);
68
+ const [error, setError] = useState<Error | null>(null);
69
+
70
+ const fetchTranscript = useCallback((): Promise<SessionTranscript> => {
71
+ if (!sessionId) {
72
+ return Promise.reject(
73
+ new Error(
74
+ "Cannot export a transcript without a session id. Pass the session's " +
75
+ "id to useExportTranscript once the session exists.",
76
+ ),
77
+ );
78
+ }
79
+ return fetchSessionTranscript(stigmer, sessionId, { includeSuperseded });
80
+ }, [stigmer, sessionId, includeSuperseded]);
81
+
82
+ const copyMarkdown = useCallback(async (): Promise<void> => {
83
+ setError(null);
84
+ setIsExporting(true);
85
+ const markdown = fetchTranscript().then(
86
+ (t) => transcriptToMarkdown(t, { generatedAt: new Date().toISOString() }),
87
+ );
88
+ try {
89
+ // The transcript is fetched before it can be copied, and WebKit (the
90
+ // desktop app's webview) only honors clipboard writes inside the user
91
+ // gesture's task — a plain writeText after the await is rejected
92
+ // there. ClipboardItem accepts a promise, letting the write start
93
+ // synchronously in the gesture while the content resolves.
94
+ if (typeof ClipboardItem !== "undefined" && navigator.clipboard?.write) {
95
+ await navigator.clipboard.write([
96
+ new ClipboardItem({
97
+ "text/plain": markdown.then(
98
+ (text) => new Blob([text], { type: "text/plain" }),
99
+ ),
100
+ }),
101
+ ]);
102
+ } else {
103
+ await navigator.clipboard.writeText(await markdown);
104
+ }
105
+ toast.success("Transcript copied to clipboard");
106
+ } catch (e) {
107
+ const err = toError(e);
108
+ setError(err);
109
+ toast.error(`Couldn't copy the transcript: ${err.message}`);
110
+ } finally {
111
+ setIsExporting(false);
112
+ }
113
+ }, [fetchTranscript]);
114
+
115
+ const download = useCallback(
116
+ async (serialize: (t: SessionTranscript) => { content: string; extension: string; mimeType: string }): Promise<void> => {
117
+ setError(null);
118
+ setIsExporting(true);
119
+ try {
120
+ const transcript = await fetchTranscript();
121
+ const { content, extension, mimeType } = serialize(transcript);
122
+ downloadTextFile(
123
+ content,
124
+ `${transcriptFilename(transcript)}.${extension}`,
125
+ mimeType,
126
+ );
127
+ } catch (e) {
128
+ const err = toError(e);
129
+ setError(err);
130
+ toast.error(`Couldn't export the transcript: ${err.message}`);
131
+ } finally {
132
+ setIsExporting(false);
133
+ }
134
+ },
135
+ [fetchTranscript],
136
+ );
137
+
138
+ const downloadMarkdown = useCallback(
139
+ () =>
140
+ download((t) => ({
141
+ content: transcriptToMarkdown(t, {
142
+ generatedAt: new Date().toISOString(),
143
+ }),
144
+ extension: "md",
145
+ mimeType: "text/markdown",
146
+ })),
147
+ [download],
148
+ );
149
+
150
+ const downloadJson = useCallback(
151
+ () =>
152
+ download((t) => ({
153
+ content: JSON.stringify(transcriptToJson(t), null, 2),
154
+ extension: "json",
155
+ mimeType: "application/json",
156
+ })),
157
+ [download],
158
+ );
159
+
160
+ return useMemo(
161
+ () => ({ copyMarkdown, downloadMarkdown, downloadJson, isExporting, error }),
162
+ [copyMarkdown, downloadMarkdown, downloadJson, isExporting, error],
163
+ );
164
+ }
165
+
166
+ /** `<subject-slug>-transcript`, falling back to the session id. */
167
+ function transcriptFilename(transcript: SessionTranscript): string {
168
+ const subject = resolvedSubject(transcript.session.spec?.subject);
169
+ const slug = subject ? generateSlug(subject).slice(0, 60) : "";
170
+ return `${slug || transcript.session.metadata?.id || "session"}-transcript`;
171
+ }
@@ -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
@@ -218,6 +221,20 @@ export interface UseNewSessionFlowReturn {
218
221
  /** Session variables (per-execution secrets) manager. */
219
222
  readonly sessionVariables: UseSessionVariablesReturn;
220
223
 
224
+ /**
225
+ * Pre-arm "auto-approve tool calls" for the session this surface will
226
+ * create (stigmer/stigmer#816, the walk-away scenario). Seeded from the
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.
233
+ */
234
+ readonly autoApproveAll: boolean;
235
+ /** Set the user's explicit auto-approve choice for the created session. */
236
+ readonly setAutoApproveAll: (value: boolean) => void;
237
+
221
238
  /** `true` while the create session + execution flow is in flight. */
222
239
  readonly isSubmitting: boolean;
223
240
  /** Human-readable error from the last failed submission, or `null`. */
@@ -311,13 +328,28 @@ export function useNewSessionFlow(
311
328
  const contextTarget = useExecutionTarget();
312
329
  const executionTarget = options.executionTarget ?? contextTarget;
313
330
  const adapter = useRunnerAdapter();
314
- // Host approval default (#302): pre-arm auto_approve_all on the bootstrap
315
- // create when the provider says so. Guests never inherit it — a share-link
316
- // visitor is not the operator the host's trust judgment covers (the
317
- // GUEST_HARNESS fixed-platform-policy reasoning).
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.
318
346
  const approvalDefaults = useApprovalDefaults();
347
+ const [autoApproveChoice, setAutoApproveAll] = useState<boolean | null>(null);
319
348
  const autoApproveAll =
320
- (!isGuest && approvalDefaults?.autoApproveAll) || undefined;
349
+ autoApproveChoice ??
350
+ (!isGuest &&
351
+ ((accountDefaults?.autoApprove ?? false) ||
352
+ (approvalDefaults?.autoApproveAll ?? false)));
321
353
 
322
354
  const [harness, setHarnessRaw] = useState<HarnessOption>(() => {
323
355
  // Guests get the fixed platform policy (see GUEST_HARNESS) and never
@@ -496,7 +528,9 @@ export function useNewSessionFlow(
496
528
  ? (pinnedThinkingMode === "enabled" ? "enabled" : undefined)
497
529
  : context?.thinkingMode,
498
530
  workspaceFileRefs: context?.workspaceFileRefs,
499
- autoApproveAll,
531
+ // Only an armed state travels; false stays off the wire (the
532
+ // serviceTier/thinkingMode only-explicit discipline).
533
+ autoApproveAll: autoApproveAll || undefined,
500
534
  };
501
535
 
502
536
  // Resolve which agent the bootstrapped session runs against: an
@@ -624,6 +658,8 @@ export function useNewSessionFlow(
624
658
  setSkillRefs,
625
659
  workspace,
626
660
  sessionVariables,
661
+ autoApproveAll,
662
+ setAutoApproveAll,
627
663
  isSubmitting,
628
664
  submitError,
629
665
  submit,
@@ -10,6 +10,7 @@ import type { McpServerUsage as ProtoMcpServerUsage } from "@stigmer/protos/ai/s
10
10
  import type { WorkspaceEntry as ProtoWorkspaceEntry } from "@stigmer/protos/ai/stigmer/agentic/session/v1/workspace_pb";
11
11
  import type { ApiResourceReference as ProtoApiResourceReference } from "@stigmer/protos/ai/stigmer/commons/apiresource/io_pb";
12
12
  import {
13
+ supersededExecutionIds,
13
14
  toSessionUpdateInput,
14
15
  type AttachmentInput,
15
16
  type EnvVarInput,
@@ -544,13 +545,11 @@ export function useSessionConversation(
544
545
  }
545
546
  }, [activeExecutionId, stream.phase, refetch]);
546
547
 
547
- // Executions replaced via edit-and-resubmit. The successor carries
548
- // `spec.supersedes_execution_id`; hiding the superseded turn makes the
549
- // edited message read as a single corrected exchange (in-place replace).
550
- // The link must also be read off the live stream copy — right after a
551
- // resubmit, the successor streams before the list refetch delivers it.
552
- // Display-only: the raw `executions` list (and therefore active-id
553
- // resolution above) is never filtered.
548
+ // Executions replaced via edit-and-resubmit (the shared conversation rule —
549
+ // see supersededExecutionIds in @stigmer/sdk for the why). The extra id is
550
+ // the live stream copy's link: right after a resubmit, the successor streams
551
+ // before the list refetch delivers it. Display-only: the raw `executions`
552
+ // list (and therefore active-id resolution above) is never filtered.
554
553
  //
555
554
  // Dep is the scalar link, NOT stream.execution: the stream object changes
556
555
  // reference every frame, and rebuilding the Set per frame would hand
@@ -558,15 +557,10 @@ export function useSessionConversation(
558
557
  // memoization (DD-010).
559
558
  const streamSupersededId =
560
559
  stream.execution?.spec?.supersedesExecutionId || null;
561
- const supersededIds = useMemo(() => {
562
- const ids = new Set<string>();
563
- for (const e of executions) {
564
- const superseded = e.spec?.supersedesExecutionId;
565
- if (superseded) ids.add(superseded);
566
- }
567
- if (streamSupersededId) ids.add(streamSupersededId);
568
- return ids;
569
- }, [executions, streamSupersededId]);
560
+ const supersededIds = useMemo(
561
+ () => supersededExecutionIds(executions, streamSupersededId),
562
+ [executions, streamSupersededId],
563
+ );
570
564
 
571
565
  const completedExecutions = useMemo(() => {
572
566
  return executions.filter((e) => {
@@ -3,9 +3,14 @@
3
3
  import { create } from "@bufbuild/protobuf";
4
4
  import type { AgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
5
5
  import { ListAgentExecutionsBySessionRequestSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/io_pb";
6
+ import { sortChronologically } from "@stigmer/sdk";
6
7
  import { useStigmer } from "../hooks.js";
7
8
  import { useFetch } from "../internal/useFetch.js";
8
9
 
10
+ // The ordering rule lives in @stigmer/sdk with the other shared
11
+ // conversation-assembly rules; re-exported here for existing consumers.
12
+ export { sortChronologically };
13
+
9
14
  /** Options for {@link useSessionExecutions}. */
10
15
  export interface UseSessionExecutionsOptions {
11
16
  /**
@@ -71,32 +76,6 @@ export interface UseSessionExecutionsReturn {
71
76
  * const { executions } = useSessionExecutions(sessionId ?? null);
72
77
  * ```
73
78
  */
74
- /**
75
- * Returns the executions in chronological (oldest-first) order — the order
76
- * the conversation thread renders top-to-bottom.
77
- *
78
- * Defense-in-depth against an unordered list response: the executions ARE the
79
- * transcript, so a scrambled order drops the newest turns out of view (they no
80
- * longer sort to the bottom). The server orders this list, but the thread must
81
- * never depend on that alone. Resource ids are time-sortable ULIDs
82
- * (`aex_01k…`), so an ascending id sort is creation order without parsing
83
- * timestamps; entries missing an id sort last but keep a stable relative order.
84
- *
85
- * @internal Exported for testing — not part of the public API.
86
- */
87
- export function sortChronologically(
88
- executions: readonly AgentExecution[],
89
- ): AgentExecution[] {
90
- return [...executions].sort((a, b) => {
91
- const aId = a.metadata?.id ?? "";
92
- const bId = b.metadata?.id ?? "";
93
- if (aId === bId) return 0;
94
- if (!aId) return 1;
95
- if (!bId) return -1;
96
- return aId < bId ? -1 : 1;
97
- });
98
- }
99
-
100
79
  export function useSessionExecutions(
101
80
  sessionId: string | null,
102
81
  options?: UseSessionExecutionsOptions,