@stigmer/react 3.12.6 → 3.12.7
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 +19 -1
- package/composer/ComposerToolbar.d.ts.map +1 -1
- package/composer/ComposerToolbar.js +7 -2
- package/composer/ComposerToolbar.js.map +1 -1
- package/composer/SessionComposer.d.ts +10 -0
- package/composer/SessionComposer.d.ts.map +1 -1
- package/composer/SessionComposer.js +2 -2
- package/composer/SessionComposer.js.map +1 -1
- package/composer/index.d.ts +1 -0
- package/composer/index.d.ts.map +1 -1
- package/composer/index.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/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/index.d.ts +6 -3
- package/index.d.ts.map +1 -1
- package/index.js +5 -1
- package/index.js.map +1 -1
- 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/index.d.ts +8 -0
- package/memory/index.d.ts.map +1 -0
- package/memory/index.js +12 -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/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/package.json +4 -4
- package/session/NewSessionViewer.d.ts.map +1 -1
- package/session/NewSessionViewer.js +9 -1
- package/session/NewSessionViewer.js.map +1 -1
- package/session/SessionViewer.d.ts +14 -1
- package/session/SessionViewer.d.ts.map +1 -1
- package/session/SessionViewer.js +21 -25
- 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/index.d.ts +4 -0
- 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 +11 -0
- package/session/useNewSessionFlow.d.ts.map +1 -1
- package/session/useNewSessionFlow.js +14 -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 +23 -10
- package/session/useSessionPageFlow.d.ts.map +1 -1
- package/session/useSessionPageFlow.js +61 -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/composer/ComposerToolbar.tsx +58 -0
- package/src/composer/SessionComposer.tsx +13 -1
- package/src/composer/__tests__/ComposerToolbar-autoApprove.test.tsx +80 -0
- package/src/composer/index.ts +1 -0
- package/src/execution/MessageThread.tsx +8 -11
- package/src/execution/useFileChangeContent.ts +5 -12
- package/src/index.ts +34 -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/__tests__/MemoryListPanel.test.tsx +259 -0
- package/src/memory/__tests__/memoryHooks.test.tsx +220 -0
- package/src/memory/index.ts +19 -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/useRejectMemory.ts +65 -0
- package/src/memory/useUpdateMemoryContent.ts +67 -0
- package/src/session/NewSessionViewer.tsx +15 -1
- package/src/session/SessionViewer.tsx +48 -49
- package/src/session/TranscriptExportMenu.tsx +78 -0
- package/src/session/__tests__/SessionViewer-autoApproveToggle.test.tsx +178 -0
- package/src/session/__tests__/SessionViewer-transcriptExport.test.tsx +151 -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 +33 -0
- package/src/session/__tests__/useSessionPageFlow.autoApprove.test.tsx +176 -2
- package/src/session/index.ts +9 -0
- package/src/session/useExportTranscript.ts +171 -0
- package/src/session/useNewSessionFlow.ts +28 -7
- package/src/session/useSessionConversation.ts +10 -16
- package/src/session/useSessionExecutions.ts +5 -26
- package/src/session/useSessionPageFlow.ts +84 -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/usage/useExportCSV.ts +2 -14
- package/usage/useExportCSV.d.ts.map +1 -1
- package/usage/useExportCSV.js +2 -13
- package/usage/useExportCSV.js.map +1 -1
|
@@ -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
|
+
}
|
|
@@ -218,6 +218,18 @@ export interface UseNewSessionFlowReturn {
|
|
|
218
218
|
/** Session variables (per-execution secrets) manager. */
|
|
219
219
|
readonly sessionVariables: UseSessionVariablesReturn;
|
|
220
220
|
|
|
221
|
+
/**
|
|
222
|
+
* Pre-arm "auto-approve tool calls" for the session this surface will
|
|
223
|
+
* 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.
|
|
228
|
+
*/
|
|
229
|
+
readonly autoApproveAll: boolean;
|
|
230
|
+
/** Set the user's explicit auto-approve choice for the created session. */
|
|
231
|
+
readonly setAutoApproveAll: (value: boolean) => void;
|
|
232
|
+
|
|
221
233
|
/** `true` while the create session + execution flow is in flight. */
|
|
222
234
|
readonly isSubmitting: boolean;
|
|
223
235
|
/** Human-readable error from the last failed submission, or `null`. */
|
|
@@ -311,13 +323,18 @@ export function useNewSessionFlow(
|
|
|
311
323
|
const contextTarget = useExecutionTarget();
|
|
312
324
|
const executionTarget = options.executionTarget ?? contextTarget;
|
|
313
325
|
const adapter = useRunnerAdapter();
|
|
314
|
-
//
|
|
315
|
-
//
|
|
316
|
-
//
|
|
317
|
-
//
|
|
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.
|
|
318
334
|
const approvalDefaults = useApprovalDefaults();
|
|
319
|
-
const autoApproveAll =
|
|
320
|
-
(!isGuest && approvalDefaults?.autoApproveAll
|
|
335
|
+
const [autoApproveAll, setAutoApproveAll] = useState(
|
|
336
|
+
() => !isGuest && (approvalDefaults?.autoApproveAll ?? false),
|
|
337
|
+
);
|
|
321
338
|
|
|
322
339
|
const [harness, setHarnessRaw] = useState<HarnessOption>(() => {
|
|
323
340
|
// Guests get the fixed platform policy (see GUEST_HARNESS) and never
|
|
@@ -496,7 +513,9 @@ export function useNewSessionFlow(
|
|
|
496
513
|
? (pinnedThinkingMode === "enabled" ? "enabled" : undefined)
|
|
497
514
|
: context?.thinkingMode,
|
|
498
515
|
workspaceFileRefs: context?.workspaceFileRefs,
|
|
499
|
-
|
|
516
|
+
// Only an armed state travels; false stays off the wire (the
|
|
517
|
+
// serviceTier/thinkingMode only-explicit discipline).
|
|
518
|
+
autoApproveAll: autoApproveAll || undefined,
|
|
500
519
|
};
|
|
501
520
|
|
|
502
521
|
// Resolve which agent the bootstrapped session runs against: an
|
|
@@ -624,6 +643,8 @@ export function useNewSessionFlow(
|
|
|
624
643
|
setSkillRefs,
|
|
625
644
|
workspace,
|
|
626
645
|
sessionVariables,
|
|
646
|
+
autoApproveAll,
|
|
647
|
+
setAutoApproveAll,
|
|
627
648
|
isSubmitting,
|
|
628
649
|
submitError,
|
|
629
650
|
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,
|
|
@@ -20,6 +20,7 @@ import { useAgentRefFromSession } from "./useAgentRefFromSession.js";
|
|
|
20
20
|
import { usePersistedModel, type UsePersistedModelReturn } from "./usePersistedModel.js";
|
|
21
21
|
import { toSessionUpdateInput } from "@stigmer/sdk";
|
|
22
22
|
import type { SessionAudience } from "./audience.js";
|
|
23
|
+
import { isChannelOriginSession } from "./channelOrigin.js";
|
|
23
24
|
import { assertValidRunConfig, type SessionRunConfig } from "./run-config.js";
|
|
24
25
|
|
|
25
26
|
/**
|
|
@@ -140,20 +141,33 @@ export interface UseSessionPageFlowReturn {
|
|
|
140
141
|
readonly sessionVariables: UseSessionVariablesReturn;
|
|
141
142
|
|
|
142
143
|
/**
|
|
143
|
-
* Session-scoped "auto-approve tool calls"
|
|
144
|
+
* Session-scoped "auto-approve tool calls" state (stigmer/stigmer#816).
|
|
144
145
|
*
|
|
145
|
-
* `false` by default
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
146
|
+
* `false` by default. `true` when any of its sources arms it — the user's
|
|
147
|
+
* explicit choice always winning:
|
|
148
|
+
*
|
|
149
|
+
* 1. the user's composer toggle ({@link setAutoApproveAll});
|
|
150
|
+
* 2. "Approve & don't ask again" at an approval gate (see
|
|
151
|
+
* {@link submitApproval});
|
|
152
|
+
* 3. the host's app-wide `StigmerProvider` `approvalDefaults` (#302,
|
|
153
|
+
* guests excluded);
|
|
154
|
+
* 4. the active in-flight execution having been created with
|
|
155
|
+
* `spec.auto_approve_all` (e.g. armed on the new-session surface), so
|
|
156
|
+
* the state survives the launcher → session-page handoff.
|
|
157
|
+
*
|
|
158
|
+
* While `true`, follow-ups carry `auto_approve_all` ({@link handleSubmit})
|
|
159
|
+
* 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.
|
|
152
162
|
*/
|
|
153
163
|
readonly autoApproveAll: boolean;
|
|
154
164
|
/**
|
|
155
|
-
*
|
|
156
|
-
*
|
|
165
|
+
* 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".
|
|
157
171
|
*/
|
|
158
172
|
readonly setAutoApproveAll: (value: boolean) => void;
|
|
159
173
|
|
|
@@ -322,17 +336,34 @@ export function useSessionPageFlow(
|
|
|
322
336
|
const [skillRefs, setSkillRefs] = useState<ResourceRef[]>([]);
|
|
323
337
|
const initialSyncDone = useRef(false);
|
|
324
338
|
|
|
325
|
-
// Session-scoped auto-approve.
|
|
326
|
-
//
|
|
327
|
-
//
|
|
328
|
-
//
|
|
329
|
-
//
|
|
330
|
-
//
|
|
331
|
-
//
|
|
339
|
+
// Session-scoped auto-approve (#816). Derived like interactionMode above —
|
|
340
|
+
// the user's explicit in-session decision wins, otherwise the truth is
|
|
341
|
+
// computed from its sources — never an effect-synced copy:
|
|
342
|
+
//
|
|
343
|
+
// effective = userChoice ?? (hostSeed || runArmed)
|
|
344
|
+
//
|
|
345
|
+
// - `userChoice`: the composer toggle, or the gate-time "Approve & don't
|
|
346
|
+
// ask again" (which has always escalated the session preference).
|
|
347
|
+
// - `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).
|
|
350
|
+
// - `runArmed`: the ACTIVE execution was created with
|
|
351
|
+
// spec.auto_approve_all, so the toggle reflects an armed in-flight run —
|
|
352
|
+
// this is what carries the state across the new-session → session-page
|
|
353
|
+
// handoff. Deliberately derived from the active execution ONLY, never
|
|
354
|
+
// from history: deriving from past executions would silently survive a
|
|
355
|
+
// reload and change the reset-on-reload consent contract below.
|
|
356
|
+
//
|
|
357
|
+
// Lives only in memory for the life of this page — reset on reload
|
|
358
|
+
// re-applies the host default, never a user choice.
|
|
332
359
|
const approvalDefaults = useApprovalDefaults();
|
|
333
|
-
const [
|
|
334
|
-
|
|
335
|
-
|
|
360
|
+
const [autoApproveChoice, setAutoApproveChoice] = useState<boolean | null>(null);
|
|
361
|
+
const hostSeed = !isGuest && (approvalDefaults?.autoApproveAll ?? false);
|
|
362
|
+
const runArmed = conv.activeStreamExecution?.spec?.autoApproveAll === true;
|
|
363
|
+
const autoApproveAll = autoApproveChoice ?? (hostSeed || runArmed);
|
|
364
|
+
const setAutoApproveAll = useCallback((value: boolean) => {
|
|
365
|
+
setAutoApproveChoice(value);
|
|
366
|
+
}, []);
|
|
336
367
|
|
|
337
368
|
const submitApproval = useCallback<UseSessionConversationReturn["submitApproval"]>(
|
|
338
369
|
async (toolCallId, action, comment) => {
|
|
@@ -341,13 +372,45 @@ export function useSessionPageFlow(
|
|
|
341
372
|
// plane separately resolves the current execution's remaining gates and
|
|
342
373
|
// the runner skips the gate for the rest of that run.
|
|
343
374
|
if (action === ApprovalAction.APPROVE_ALL) {
|
|
344
|
-
|
|
375
|
+
setAutoApproveChoice(true);
|
|
345
376
|
}
|
|
346
377
|
await conv.submitApproval(toolCallId, action, comment);
|
|
347
378
|
},
|
|
348
379
|
[conv.submitApproval],
|
|
349
380
|
);
|
|
350
381
|
|
|
382
|
+
// Armed responder (#816, the walk-away scenario): while auto-approve is ON,
|
|
383
|
+
// answer each approval gate as it appears in the in-flight run. This must be
|
|
384
|
+
// a standing responder, not a flip-time one-shot: a gate-time APPROVE_ALL
|
|
385
|
+
// grants a lease scoped to ONE tool class (see APPROVAL_ACTION_APPROVE_ALL
|
|
386
|
+
// in agentexecution enum.proto), so a later gate of a different class would
|
|
387
|
+
// still park the run — the responder covers each class as it surfaces, and
|
|
388
|
+
// every released call keeps an honest audit decision.
|
|
389
|
+
//
|
|
390
|
+
// Read-only surfaces must never auto-submit approvals on a viewer's behalf:
|
|
391
|
+
// the predicate mirrors SessionViewer's read-only derivation (guest audience,
|
|
392
|
+
// observer audience, channel-origin session) exactly.
|
|
393
|
+
const canRespondToGates =
|
|
394
|
+
!isGuest &&
|
|
395
|
+
options.audience !== "observer" &&
|
|
396
|
+
!isChannelOriginSession(conv.session);
|
|
397
|
+
const respondedToolCallIds = useRef<Set<string>>(new Set());
|
|
398
|
+
useEffect(() => {
|
|
399
|
+
if (!autoApproveAll || !canRespondToGates) return;
|
|
400
|
+
for (const approval of conv.pendingApprovals) {
|
|
401
|
+
const toolCallId = approval.toolCallId;
|
|
402
|
+
// Marked before the submit resolves so a re-render mid-flight cannot
|
|
403
|
+
// double-submit. A failed submission is deliberately NOT retried — the
|
|
404
|
+
// approval card stays for a manual decision instead of a retry storm.
|
|
405
|
+
if (!toolCallId || respondedToolCallIds.current.has(toolCallId)) continue;
|
|
406
|
+
respondedToolCallIds.current.add(toolCallId);
|
|
407
|
+
void conv.submitApproval(toolCallId, ApprovalAction.APPROVE_ALL).catch(() => {
|
|
408
|
+
// Swallowed by design (see above); the card's own error surface
|
|
409
|
+
// reports the failure where the user can act on it.
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
}, [autoApproveAll, canRespondToGates, conv.pendingApprovals, conv.submitApproval]);
|
|
413
|
+
|
|
351
414
|
// -------------------------------------------------------------------------
|
|
352
415
|
// Agent — derive from session, allow mid-session changes
|
|
353
416
|
// -------------------------------------------------------------------------
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useId } from "react";
|
|
4
|
+
import { MemoryListPanel } from "../memory/MemoryListPanel.js";
|
|
5
|
+
import { useActiveOrgSlug } from "../organization/OrgProvider.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Settings section listing everything the platform remembers about the
|
|
9
|
+
* caller in the active organization.
|
|
10
|
+
*
|
|
11
|
+
* The helper copy carries the DD-006 D6 transparency statement: a
|
|
12
|
+
* confirmed memory is injected into the caller's future sessions and
|
|
13
|
+
* appears in those executions' records. Memory content everywhere else
|
|
14
|
+
* is subject-only — this page shows the caller their own records and
|
|
15
|
+
* nobody else's.
|
|
16
|
+
*/
|
|
17
|
+
export function MemorySection() {
|
|
18
|
+
const headingId = useId();
|
|
19
|
+
const org = useActiveOrgSlug();
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<section aria-labelledby={headingId}>
|
|
23
|
+
<h2
|
|
24
|
+
id={headingId}
|
|
25
|
+
className="stg:text-foreground stg:mb-1 stg:text-sm stg:font-semibold"
|
|
26
|
+
>
|
|
27
|
+
Memory
|
|
28
|
+
</h2>
|
|
29
|
+
<p className="stg:text-muted-foreground stg:mb-6 stg:text-xs">
|
|
30
|
+
Facts agents proposed to remember about you in this organization.
|
|
31
|
+
Confirmed memories are shared with agents in your future sessions
|
|
32
|
+
and appear in those executions' records. Only you can see
|
|
33
|
+
them here, and you can edit or delete any of them at any time.
|
|
34
|
+
</p>
|
|
35
|
+
|
|
36
|
+
{!org ? (
|
|
37
|
+
<p className="stg:text-muted-foreground stg:py-4 stg:text-center stg:text-xs">
|
|
38
|
+
Select an organization to view its memories.
|
|
39
|
+
</p>
|
|
40
|
+
) : (
|
|
41
|
+
<MemoryListPanel org={org} />
|
|
42
|
+
)}
|
|
43
|
+
</section>
|
|
44
|
+
);
|
|
45
|
+
}
|
package/src/settings/index.ts
CHANGED
|
@@ -12,6 +12,7 @@ export { MembersSection } from "./MembersSection.js";
|
|
|
12
12
|
export { OrgProfileSection } from "./OrgProfileSection.js";
|
|
13
13
|
export { OrgPreferencesSection } from "./OrgPreferencesSection.js";
|
|
14
14
|
export { AccountPreferencesSection } from "./AccountPreferencesSection.js";
|
|
15
|
+
export { MemorySection } from "./MemorySection.js";
|
|
15
16
|
export { EnvironmentsSection } from "./EnvironmentsSection.js";
|
|
16
17
|
export { InvitationsSection } from "./InvitationsSection.js";
|
|
17
18
|
export { IdentityProvidersSection } from "./IdentityProvidersSection.js";
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
AppWindow,
|
|
5
5
|
BarChart3,
|
|
6
6
|
Box,
|
|
7
|
+
Brain,
|
|
7
8
|
Building2,
|
|
8
9
|
CreditCard,
|
|
9
10
|
KeyRound,
|
|
@@ -108,6 +109,7 @@ export const SETTINGS_NAV_GROUPS: readonly SettingsNavGroup[] = [
|
|
|
108
109
|
label: "Preferences",
|
|
109
110
|
icon: UserCog,
|
|
110
111
|
},
|
|
112
|
+
{ href: "/settings/memory", label: "Memory", icon: Brain },
|
|
111
113
|
],
|
|
112
114
|
},
|
|
113
115
|
];
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useCallback, useState } from "react";
|
|
4
4
|
import type { GetOrgUsageReportOutput } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/io_pb";
|
|
5
|
+
import { downloadTextFile } from "../internal/download.js";
|
|
5
6
|
|
|
6
7
|
/** Export format for the CSV download. */
|
|
7
8
|
export type ExportFormat = "daily_summary" | "model_breakdown";
|
|
@@ -41,7 +42,7 @@ export function useExportCSV(
|
|
|
41
42
|
? buildDailySummaryCSV(report, orgId)
|
|
42
43
|
: buildModelBreakdownCSV(report, orgId);
|
|
43
44
|
|
|
44
|
-
|
|
45
|
+
downloadTextFile(csv, filename, "text/csv");
|
|
45
46
|
} finally {
|
|
46
47
|
setIsExporting(false);
|
|
47
48
|
}
|
|
@@ -100,16 +101,3 @@ function escapeCsvField(value: string): string {
|
|
|
100
101
|
}
|
|
101
102
|
return value;
|
|
102
103
|
}
|
|
103
|
-
|
|
104
|
-
function downloadCSV(csv: string, filename: string): void {
|
|
105
|
-
const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
|
|
106
|
-
const url = URL.createObjectURL(blob);
|
|
107
|
-
const link = document.createElement("a");
|
|
108
|
-
link.href = url;
|
|
109
|
-
link.download = filename;
|
|
110
|
-
link.style.display = "none";
|
|
111
|
-
document.body.appendChild(link);
|
|
112
|
-
link.click();
|
|
113
|
-
document.body.removeChild(link);
|
|
114
|
-
URL.revokeObjectURL(url);
|
|
115
|
-
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useExportCSV.d.ts","sourceRoot":"","sources":["../../src/usage/useExportCSV.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,4DAA4D,CAAC;
|
|
1
|
+
{"version":3,"file":"useExportCSV.d.ts","sourceRoot":"","sources":["../../src/usage/useExportCSV.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,4DAA4D,CAAC;AAG1G,0CAA0C;AAC1C,MAAM,MAAM,YAAY,GAAG,eAAe,GAAG,iBAAiB,CAAC;AAE/D,4CAA4C;AAC5C,MAAM,WAAW,kBAAkB;IACjC,mDAAmD;IACnD,QAAQ,CAAC,SAAS,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,CAAC;IACnD,4CAA4C;IAC5C,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;CAC/B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,uBAAuB,GAAG,IAAI,EACtC,KAAK,EAAE,MAAM,GACZ,kBAAkB,CAsBpB"}
|
package/usage/useExportCSV.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useCallback, useState } from "react";
|
|
3
|
+
import { downloadTextFile } from "../internal/download.js";
|
|
3
4
|
/**
|
|
4
5
|
* Behavior hook that generates and downloads usage data as CSV.
|
|
5
6
|
*
|
|
@@ -21,7 +22,7 @@ export function useExportCSV(report, orgId) {
|
|
|
21
22
|
const { csv, filename } = format === "daily_summary"
|
|
22
23
|
? buildDailySummaryCSV(report, orgId)
|
|
23
24
|
: buildModelBreakdownCSV(report, orgId);
|
|
24
|
-
|
|
25
|
+
downloadTextFile(csv, filename, "text/csv");
|
|
25
26
|
}
|
|
26
27
|
finally {
|
|
27
28
|
setIsExporting(false);
|
|
@@ -66,16 +67,4 @@ function escapeCsvField(value) {
|
|
|
66
67
|
}
|
|
67
68
|
return value;
|
|
68
69
|
}
|
|
69
|
-
function downloadCSV(csv, filename) {
|
|
70
|
-
const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
|
|
71
|
-
const url = URL.createObjectURL(blob);
|
|
72
|
-
const link = document.createElement("a");
|
|
73
|
-
link.href = url;
|
|
74
|
-
link.download = filename;
|
|
75
|
-
link.style.display = "none";
|
|
76
|
-
document.body.appendChild(link);
|
|
77
|
-
link.click();
|
|
78
|
-
document.body.removeChild(link);
|
|
79
|
-
URL.revokeObjectURL(url);
|
|
80
|
-
}
|
|
81
70
|
//# sourceMappingURL=useExportCSV.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useExportCSV.js","sourceRoot":"","sources":["../../src/usage/useExportCSV.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"useExportCSV.js","sourceRoot":"","sources":["../../src/usage/useExportCSV.ts"],"names":[],"mappings":"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"}
|