@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.
- package/composer/ComposerToolbar.d.ts.map +1 -1
- package/composer/ComposerToolbar.js.map +1 -1
- package/execution/MessageThread.d.ts.map +1 -1
- package/execution/MessageThread.js +8 -11
- package/execution/MessageThread.js.map +1 -1
- package/execution/ResultView.d.ts.map +1 -1
- package/execution/ResultView.js +6 -0
- package/execution/ResultView.js.map +1 -1
- package/execution/ToolCallItem.d.ts.map +1 -1
- package/execution/ToolCallItem.js +13 -2
- package/execution/ToolCallItem.js.map +1 -1
- package/execution/tool-categories.d.ts +1 -1
- package/execution/tool-categories.d.ts.map +1 -1
- package/execution/tool-categories.js +6 -0
- package/execution/tool-categories.js.map +1 -1
- package/execution/useFileChangeContent.d.ts +2 -6
- package/execution/useFileChangeContent.d.ts.map +1 -1
- package/execution/useFileChangeContent.js +4 -12
- package/execution/useFileChangeContent.js.map +1 -1
- package/identity-account/AccountPreferencesPanel.d.ts +2 -1
- package/identity-account/AccountPreferencesPanel.d.ts.map +1 -1
- package/identity-account/AccountPreferencesPanel.js +41 -3
- package/identity-account/AccountPreferencesPanel.js.map +1 -1
- package/identity-account/useAccountExecutionDefaults.d.ts +7 -0
- package/identity-account/useAccountExecutionDefaults.d.ts.map +1 -1
- package/identity-account/useAccountExecutionDefaults.js +5 -2
- package/identity-account/useAccountExecutionDefaults.js.map +1 -1
- package/index.d.ts +5 -2
- package/index.d.ts.map +1 -1
- package/index.js +5 -1
- package/index.js.map +1 -1
- package/internal/MemoryEnabledRow.d.ts +37 -0
- package/internal/MemoryEnabledRow.d.ts.map +1 -0
- package/internal/MemoryEnabledRow.js +25 -0
- package/internal/MemoryEnabledRow.js.map +1 -0
- package/internal/download.d.ts +8 -0
- package/internal/download.d.ts.map +1 -0
- package/internal/download.js +25 -0
- package/internal/download.js.map +1 -0
- package/library/useExportResource.d.ts.map +1 -1
- package/library/useExportResource.js +3 -17
- package/library/useExportResource.js.map +1 -1
- package/memory/MemoryListPanel.d.ts +35 -0
- package/memory/MemoryListPanel.d.ts.map +1 -0
- package/memory/MemoryListPanel.js +141 -0
- package/memory/MemoryListPanel.js.map +1 -0
- package/memory/MemoryProposalCard.d.ts +35 -0
- package/memory/MemoryProposalCard.d.ts.map +1 -0
- package/memory/MemoryProposalCard.js +73 -0
- package/memory/MemoryProposalCard.js.map +1 -0
- package/memory/index.d.ts +10 -0
- package/memory/index.d.ts.map +1 -0
- package/memory/index.js +14 -0
- package/memory/index.js.map +1 -0
- package/memory/memoryGroups.d.ts +31 -0
- package/memory/memoryGroups.d.ts.map +1 -0
- package/memory/memoryGroups.js +52 -0
- package/memory/memoryGroups.js.map +1 -0
- package/memory/useConfirmMemory.d.ts +31 -0
- package/memory/useConfirmMemory.d.ts.map +1 -0
- package/memory/useConfirmMemory.js +43 -0
- package/memory/useConfirmMemory.js.map +1 -0
- package/memory/useDeleteMemory.d.ts +30 -0
- package/memory/useDeleteMemory.d.ts.map +1 -0
- package/memory/useDeleteMemory.js +42 -0
- package/memory/useDeleteMemory.js.map +1 -0
- package/memory/useMemories.d.ts +33 -0
- package/memory/useMemories.d.ts.map +1 -0
- package/memory/useMemories.js +42 -0
- package/memory/useMemories.js.map +1 -0
- package/memory/useMemory.d.ts +39 -0
- package/memory/useMemory.d.ts.map +1 -0
- package/memory/useMemory.js +43 -0
- package/memory/useMemory.js.map +1 -0
- package/memory/useRejectMemory.d.ts +34 -0
- package/memory/useRejectMemory.d.ts.map +1 -0
- package/memory/useRejectMemory.js +46 -0
- package/memory/useRejectMemory.js.map +1 -0
- package/memory/useUpdateMemoryContent.d.ts +32 -0
- package/memory/useUpdateMemoryContent.d.ts.map +1 -0
- package/memory/useUpdateMemoryContent.js +48 -0
- package/memory/useUpdateMemoryContent.js.map +1 -0
- package/organization/OrgPreferencesPanel.d.ts.map +1 -1
- package/organization/OrgPreferencesPanel.js +36 -4
- package/organization/OrgPreferencesPanel.js.map +1 -1
- package/package.json +4 -4
- package/session/AutoApproveIndicator.d.ts +18 -0
- package/session/AutoApproveIndicator.d.ts.map +1 -0
- package/session/AutoApproveIndicator.js +23 -0
- package/session/AutoApproveIndicator.js.map +1 -0
- package/session/NewSessionViewer.d.ts.map +1 -1
- package/session/NewSessionViewer.js +10 -1
- package/session/NewSessionViewer.js.map +1 -1
- package/session/SessionViewer.d.ts +26 -1
- package/session/SessionViewer.d.ts.map +1 -1
- package/session/SessionViewer.js +28 -22
- package/session/SessionViewer.js.map +1 -1
- package/session/TranscriptExportMenu.d.ts +24 -0
- package/session/TranscriptExportMenu.d.ts.map +1 -0
- package/session/TranscriptExportMenu.js +26 -0
- package/session/TranscriptExportMenu.js.map +1 -0
- package/session/facets/SetupTab.d.ts +43 -6
- package/session/facets/SetupTab.d.ts.map +1 -1
- package/session/facets/SetupTab.js +32 -36
- package/session/facets/SetupTab.js.map +1 -1
- package/session/facets/UsageTab.d.ts +2 -1
- package/session/facets/UsageTab.d.ts.map +1 -1
- package/session/facets/UsageTab.js +11 -5
- package/session/facets/UsageTab.js.map +1 -1
- package/session/facets/index.d.ts +1 -1
- package/session/facets/index.d.ts.map +1 -1
- package/session/facets/primitives.d.ts +58 -0
- package/session/facets/primitives.d.ts.map +1 -0
- package/session/facets/primitives.js +59 -0
- package/session/facets/primitives.js.map +1 -0
- package/session/index.d.ts +5 -1
- package/session/index.d.ts.map +1 -1
- package/session/index.js +2 -0
- package/session/index.js.map +1 -1
- package/session/useExportTranscript.d.ts +44 -0
- package/session/useExportTranscript.d.ts.map +1 -0
- package/session/useExportTranscript.js +109 -0
- package/session/useExportTranscript.js.map +1 -0
- package/session/useNewSessionFlow.d.ts +16 -0
- package/session/useNewSessionFlow.d.ts.map +1 -1
- package/session/useNewSessionFlow.js +25 -6
- package/session/useNewSessionFlow.js.map +1 -1
- package/session/useSessionConversation.d.ts.map +1 -1
- package/session/useSessionConversation.js +7 -19
- package/session/useSessionConversation.js.map +1 -1
- package/session/useSessionExecutions.d.ts +2 -14
- package/session/useSessionExecutions.d.ts.map +1 -1
- package/session/useSessionExecutions.js +4 -26
- package/session/useSessionExecutions.js.map +1 -1
- package/session/useSessionPageFlow.d.ts +41 -10
- package/session/useSessionPageFlow.d.ts.map +1 -1
- package/session/useSessionPageFlow.js +67 -9
- package/session/useSessionPageFlow.js.map +1 -1
- package/settings/MemorySection.d.ts +12 -0
- package/settings/MemorySection.d.ts.map +1 -0
- package/settings/MemorySection.js +21 -0
- package/settings/MemorySection.js.map +1 -0
- package/settings/index.d.ts +1 -0
- package/settings/index.d.ts.map +1 -1
- package/settings/index.js +1 -0
- package/settings/index.js.map +1 -1
- package/settings/settings-nav.d.ts.map +1 -1
- package/settings/settings-nav.js +2 -1
- package/settings/settings-nav.js.map +1 -1
- package/src/__tests__/public-api.test.ts +21 -0
- package/src/attachment/__tests__/prepare-image.test.ts +12 -6
- package/src/composer/ComposerToolbar.tsx +1 -0
- package/src/execution/MessageThread.tsx +8 -11
- package/src/execution/ResultView.tsx +6 -0
- package/src/execution/ToolCallItem.tsx +18 -1
- package/src/execution/tool-categories.ts +7 -0
- package/src/execution/useFileChangeContent.ts +5 -12
- package/src/identity-account/AccountPreferencesPanel.tsx +89 -2
- package/src/identity-account/__tests__/AccountPreferencesPanel.test.tsx +112 -0
- package/src/identity-account/__tests__/useAccountExecutionDefaults.test.tsx +34 -1
- package/src/identity-account/useAccountExecutionDefaults.ts +12 -2
- package/src/index.ts +38 -0
- package/src/internal/MemoryEnabledRow.tsx +92 -0
- package/src/internal/download.ts +29 -0
- package/src/library/useExportResource.ts +3 -19
- package/src/memory/MemoryListPanel.tsx +532 -0
- package/src/memory/MemoryProposalCard.tsx +181 -0
- package/src/memory/__tests__/MemoryListPanel.test.tsx +259 -0
- package/src/memory/__tests__/MemoryProposalCard.test.tsx +236 -0
- package/src/memory/__tests__/memoryHooks.test.tsx +283 -0
- package/src/memory/index.ts +24 -0
- package/src/memory/memoryGroups.ts +64 -0
- package/src/memory/useConfirmMemory.ts +62 -0
- package/src/memory/useDeleteMemory.ts +61 -0
- package/src/memory/useMemories.ts +66 -0
- package/src/memory/useMemory.ts +80 -0
- package/src/memory/useRejectMemory.ts +65 -0
- package/src/memory/useUpdateMemoryContent.ts +67 -0
- package/src/organization/OrgPreferencesPanel.tsx +52 -3
- package/src/organization/__tests__/OrgPreferencesPanel.test.tsx +65 -0
- package/src/session/AutoApproveIndicator.tsx +45 -0
- package/src/session/NewSessionViewer.tsx +17 -0
- package/src/session/SessionViewer.tsx +78 -41
- package/src/session/TranscriptExportMenu.tsx +78 -0
- package/src/session/__tests__/NewSessionViewer.test.tsx +32 -0
- package/src/session/__tests__/SessionViewer-autoApprove.test.tsx +244 -0
- package/src/session/__tests__/SessionViewer-transcriptExport.test.tsx +189 -0
- package/src/session/__tests__/TranscriptExportMenu.test.tsx +97 -0
- package/src/session/__tests__/useExportTranscript.test.tsx +187 -0
- package/src/session/__tests__/useNewSessionFlow.test.tsx +89 -0
- package/src/session/__tests__/useSessionPageFlow.autoApprove.test.tsx +263 -2
- package/src/session/facets/SetupTab.tsx +193 -157
- package/src/session/facets/UsageTab.tsx +38 -38
- package/src/session/facets/index.ts +1 -0
- package/src/session/facets/primitives.tsx +169 -0
- package/src/session/index.ts +10 -0
- package/src/session/useExportTranscript.ts +171 -0
- package/src/session/useNewSessionFlow.ts +42 -6
- package/src/session/useSessionConversation.ts +10 -16
- package/src/session/useSessionExecutions.ts +5 -26
- package/src/session/useSessionPageFlow.ts +108 -21
- package/src/settings/MemorySection.tsx +45 -0
- package/src/settings/index.ts +1 -0
- package/src/settings/settings-nav.ts +2 -0
- package/src/switch/Switch.tsx +4 -0
- package/src/usage/useExportCSV.ts +2 -14
- package/src/workflow/__tests__/useWorkflowCanvas.dirty.test.tsx +7 -2
- package/src/workflow/__tests__/useWorkflowCanvas.drag.test.tsx +7 -2
- package/src/workspace/WorkspaceSurface.tsx +26 -3
- package/styles.css +1 -1
- package/switch/Switch.d.ts +3 -1
- package/switch/Switch.d.ts.map +1 -1
- package/switch/Switch.js +2 -2
- package/switch/Switch.js.map +1 -1
- package/usage/useExportCSV.d.ts.map +1 -1
- package/usage/useExportCSV.js +2 -13
- package/usage/useExportCSV.js.map +1 -1
- package/workspace/WorkspaceSurface.js +11 -2
- 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
|
+
}
|
package/src/session/index.ts
CHANGED
|
@@ -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
|
-
//
|
|
315
|
-
//
|
|
316
|
-
//
|
|
317
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
548
|
-
//
|
|
549
|
-
//
|
|
550
|
-
//
|
|
551
|
-
//
|
|
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
|
-
|
|
563
|
-
|
|
564
|
-
|
|
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,
|