@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,65 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useState } from "react";
|
|
4
|
+
import type { Memory } from "@stigmer/protos/ai/stigmer/agentic/memory/v1/api_pb";
|
|
5
|
+
import { useStigmer } from "../hooks.js";
|
|
6
|
+
import { toError } from "../internal/toError.js";
|
|
7
|
+
|
|
8
|
+
/** Return value of {@link useRejectMemory}. */
|
|
9
|
+
export interface UseRejectMemoryReturn {
|
|
10
|
+
/** Reject a proposed memory by its resource ID. Resolves with the rejected record. */
|
|
11
|
+
readonly rejectMemory: (id: string) => Promise<Memory>;
|
|
12
|
+
/** `true` while the reject request is in flight. */
|
|
13
|
+
readonly isRejecting: boolean;
|
|
14
|
+
/** Error from the last failed reject, or `null` when healthy. */
|
|
15
|
+
readonly error: Error | null;
|
|
16
|
+
/** Reset `error` to `null`. */
|
|
17
|
+
readonly clearError: () => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Behavior hook that wraps `memory.reject()`. A rejected memory is never
|
|
22
|
+
* recalled; the record is kept as an audit trail until deleted.
|
|
23
|
+
*
|
|
24
|
+
* Rejecting an already-rejected memory succeeds and changes nothing.
|
|
25
|
+
* Rejecting a confirmed memory fails with FAILED_PRECONDITION —
|
|
26
|
+
* deleting a confirmed memory is how it is revoked.
|
|
27
|
+
*
|
|
28
|
+
* Rejection is deliberately one click on every surface, with no
|
|
29
|
+
* confirmation dialog (DD-005 D4): expensive review teaches users to
|
|
30
|
+
* ignore the proposal queue.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```tsx
|
|
34
|
+
* const { rejectMemory, isRejecting } = useRejectMemory();
|
|
35
|
+
*
|
|
36
|
+
* await rejectMemory(memory.metadata?.id ?? "");
|
|
37
|
+
* refetch(); // refresh the list after the decision
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export function useRejectMemory(): UseRejectMemoryReturn {
|
|
41
|
+
const stigmer = useStigmer();
|
|
42
|
+
const [isRejecting, setIsRejecting] = useState(false);
|
|
43
|
+
const [error, setError] = useState<Error | null>(null);
|
|
44
|
+
|
|
45
|
+
const clearError = useCallback(() => setError(null), []);
|
|
46
|
+
|
|
47
|
+
const rejectMemory = useCallback(
|
|
48
|
+
async (id: string): Promise<Memory> => {
|
|
49
|
+
setIsRejecting(true);
|
|
50
|
+
setError(null);
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
return await stigmer.memory.reject(id);
|
|
54
|
+
} catch (err) {
|
|
55
|
+
setError(toError(err));
|
|
56
|
+
throw err;
|
|
57
|
+
} finally {
|
|
58
|
+
setIsRejecting(false);
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
[stigmer],
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
return { rejectMemory, isRejecting, error, clearError };
|
|
65
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useState } from "react";
|
|
4
|
+
import type { Memory } from "@stigmer/protos/ai/stigmer/agentic/memory/v1/api_pb";
|
|
5
|
+
import { toMemoryUpdateInput } from "@stigmer/sdk";
|
|
6
|
+
import { useStigmer } from "../hooks.js";
|
|
7
|
+
import { toError } from "../internal/toError.js";
|
|
8
|
+
|
|
9
|
+
/** Return value of {@link useUpdateMemoryContent}. */
|
|
10
|
+
export interface UseUpdateMemoryContentReturn {
|
|
11
|
+
/** Replace a memory's fact text. Resolves with the updated record. */
|
|
12
|
+
readonly updateContent: (memory: Memory, content: string) => Promise<Memory>;
|
|
13
|
+
/** `true` while the update request is in flight. */
|
|
14
|
+
readonly isUpdating: boolean;
|
|
15
|
+
/** Error from the last failed update, or `null` when healthy. */
|
|
16
|
+
readonly error: Error | null;
|
|
17
|
+
/** Reset `error` to `null`. */
|
|
18
|
+
readonly clearError: () => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Behavior hook that edits a memory's fact text — the one field the
|
|
23
|
+
* subject owns after capture (DD-004).
|
|
24
|
+
*
|
|
25
|
+
* The update RPC replaces the entire spec, so the loaded record is
|
|
26
|
+
* mapped through the generated `toMemoryUpdateInput` and only `content`
|
|
27
|
+
* is overridden — the wipe-safe editing rule. Subject and provenance
|
|
28
|
+
* ride along verbatim (they are update-immutable server-side), and the
|
|
29
|
+
* consent lifecycle is never touched by updates.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```tsx
|
|
33
|
+
* const { updateContent, isUpdating } = useUpdateMemoryContent();
|
|
34
|
+
*
|
|
35
|
+
* await updateContent(memory, editedText);
|
|
36
|
+
* refetch(); // refresh the list after the edit
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export function useUpdateMemoryContent(): UseUpdateMemoryContentReturn {
|
|
40
|
+
const stigmer = useStigmer();
|
|
41
|
+
const [isUpdating, setIsUpdating] = useState(false);
|
|
42
|
+
const [error, setError] = useState<Error | null>(null);
|
|
43
|
+
|
|
44
|
+
const clearError = useCallback(() => setError(null), []);
|
|
45
|
+
|
|
46
|
+
const updateContent = useCallback(
|
|
47
|
+
async (memory: Memory, content: string): Promise<Memory> => {
|
|
48
|
+
setIsUpdating(true);
|
|
49
|
+
setError(null);
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
return await stigmer.memory.update({
|
|
53
|
+
...toMemoryUpdateInput(memory),
|
|
54
|
+
content,
|
|
55
|
+
});
|
|
56
|
+
} catch (err) {
|
|
57
|
+
setError(toError(err));
|
|
58
|
+
throw err;
|
|
59
|
+
} finally {
|
|
60
|
+
setIsUpdating(false);
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
[stigmer],
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
return { updateContent, isUpdating, error, clearError };
|
|
67
|
+
}
|
|
@@ -7,6 +7,7 @@ import type { Organization } from "@stigmer/protos/ai/stigmer/tenancy/organizati
|
|
|
7
7
|
import { useOrganization } from "./useOrganization.js";
|
|
8
8
|
import { useUpdateOrganization } from "./useUpdateOrganization.js";
|
|
9
9
|
import { useCheckPermission } from "../iam-policy/useCheckPermission.js";
|
|
10
|
+
import { MemoryEnabledRow } from "../internal/MemoryEnabledRow.js";
|
|
10
11
|
import { StandingContextField } from "../internal/StandingContextField.js";
|
|
11
12
|
import { SpinnerIcon } from "../internal/SpinnerIcon.js";
|
|
12
13
|
|
|
@@ -60,6 +61,14 @@ export function OrgPreferencesPanel({
|
|
|
60
61
|
clearError,
|
|
61
62
|
} = useUpdateOrganization();
|
|
62
63
|
|
|
64
|
+
// The memory toggle saves instantly (its own hook instance, so a flip's
|
|
65
|
+
// in-flight/error state never bleeds into the form's Save button).
|
|
66
|
+
const {
|
|
67
|
+
update: updateMemoryFlag,
|
|
68
|
+
isUpdating: isSavingMemoryFlag,
|
|
69
|
+
error: memoryFlagError,
|
|
70
|
+
} = useUpdateOrganization();
|
|
71
|
+
|
|
63
72
|
// Fail-open: OSS local mode has no IAM service, so editing stays
|
|
64
73
|
// available there; cloud gets a genuine server verdict.
|
|
65
74
|
const { allowed: canEdit } = useCheckPermission(
|
|
@@ -100,10 +109,16 @@ export function OrgPreferencesPanel({
|
|
|
100
109
|
clearError();
|
|
101
110
|
try {
|
|
102
111
|
// update() is a full-spec replace: spread the complete mapped input
|
|
103
|
-
// so unedited spec fields survive
|
|
112
|
+
// so unedited spec fields survive. `preferences` is a nested message,
|
|
113
|
+
// so the override spreads the mapper's COMPLETE preferences too —
|
|
114
|
+
// fields this form does not own (memory_enabled) survive the save.
|
|
115
|
+
const mapped = toOrganizationUpdateInput(organization);
|
|
104
116
|
const updated = await update({
|
|
105
|
-
...
|
|
106
|
-
preferences: {
|
|
117
|
+
...mapped,
|
|
118
|
+
preferences: {
|
|
119
|
+
...mapped.preferences,
|
|
120
|
+
standingContext: standingContext.trim() || undefined,
|
|
121
|
+
},
|
|
107
122
|
});
|
|
108
123
|
refetch();
|
|
109
124
|
onUpdated?.(updated);
|
|
@@ -114,6 +129,30 @@ export function OrgPreferencesPanel({
|
|
|
114
129
|
[canSubmit, organization, standingContext, update, clearError, refetch, onUpdated],
|
|
115
130
|
);
|
|
116
131
|
|
|
132
|
+
// The memory consent flag applies instantly (the UX-checkpoint decision):
|
|
133
|
+
// a consent bit flipped-but-unsaved that silently reverts on navigation is
|
|
134
|
+
// the failure consent UX must not have. Same wipe-safe double spread.
|
|
135
|
+
const handleMemoryToggle = useCallback(
|
|
136
|
+
async (next: boolean) => {
|
|
137
|
+
if (!organization) return;
|
|
138
|
+
try {
|
|
139
|
+
const mapped = toOrganizationUpdateInput(organization);
|
|
140
|
+
const updated = await updateMemoryFlag({
|
|
141
|
+
...mapped,
|
|
142
|
+
preferences: {
|
|
143
|
+
...mapped.preferences,
|
|
144
|
+
memoryEnabled: next || undefined,
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
refetch();
|
|
148
|
+
onUpdated?.(updated);
|
|
149
|
+
} catch {
|
|
150
|
+
// error state is managed by the toggle's own hook instance
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
[organization, updateMemoryFlag, refetch, onUpdated],
|
|
154
|
+
);
|
|
155
|
+
|
|
117
156
|
// -----------------------------------------------------------------------
|
|
118
157
|
// Loading state
|
|
119
158
|
// -----------------------------------------------------------------------
|
|
@@ -175,6 +214,16 @@ export function OrgPreferencesPanel({
|
|
|
175
214
|
helperText="Shared with agents as background context — not instructions. Applies to executions started by signed-in members of this organization and is visible on their execution records."
|
|
176
215
|
/>
|
|
177
216
|
|
|
217
|
+
<MemoryEnabledRow
|
|
218
|
+
id={`${baseId}-memory-enabled`}
|
|
219
|
+
checked={organization.spec?.preferences?.memoryEnabled ?? false}
|
|
220
|
+
onToggle={(next) => void handleMemoryToggle(next)}
|
|
221
|
+
saving={isSavingMemoryFlag}
|
|
222
|
+
readOnly={!canEdit}
|
|
223
|
+
error={memoryFlagError}
|
|
224
|
+
helperText="Allow agents to remember confirmed facts about members of this organization. Each member must also turn memory on in their own account preferences. Changes apply immediately."
|
|
225
|
+
/>
|
|
226
|
+
|
|
178
227
|
{!canEdit && (
|
|
179
228
|
<p className="stg:text-[0.65rem] stg:text-muted-foreground">
|
|
180
229
|
Only organization admins can edit these preferences.
|
|
@@ -125,6 +125,71 @@ describe("OrgPreferencesPanel", () => {
|
|
|
125
125
|
expect(screen.queryByRole("button", { name: "Save changes" })).toBeNull();
|
|
126
126
|
});
|
|
127
127
|
|
|
128
|
+
it("memory toggle applies instantly with the full mapped input (wipe-safe)", async () => {
|
|
129
|
+
const update = vi.fn(async (_input: OrganizationInput) => ORG);
|
|
130
|
+
renderPanel(createMockStigmer({ update }));
|
|
131
|
+
await findSyncedField("We deploy to us-east-1.");
|
|
132
|
+
|
|
133
|
+
// No Save click: consent applies the moment it is flipped (the
|
|
134
|
+
// UX-checkpoint decision — a flipped-but-unsaved consent bit that
|
|
135
|
+
// silently reverts on navigation is the failure this prevents).
|
|
136
|
+
fireEvent.click(screen.getByRole("switch", { name: "Memory" }));
|
|
137
|
+
|
|
138
|
+
await waitFor(() => expect(update).toHaveBeenCalledTimes(1));
|
|
139
|
+
const input = update.mock.calls[0]![0];
|
|
140
|
+
expect(input.preferences?.memoryEnabled).toBe(true);
|
|
141
|
+
// The double spread: the flip must not wipe the sibling preference…
|
|
142
|
+
expect(input.preferences?.standingContext).toBe("We deploy to us-east-1.");
|
|
143
|
+
// …nor any profile field this row never renders.
|
|
144
|
+
expect(input.name).toBe("Acme Corp");
|
|
145
|
+
expect(input.description).toBe("We make everything.");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("saving standing context preserves memory_enabled (the wipe-hazard regression)", async () => {
|
|
149
|
+
const orgWithMemoryOn = create(OrganizationSchema, {
|
|
150
|
+
metadata: { id: "acme", name: "Acme Corp", slug: "acme", org: "acme" },
|
|
151
|
+
spec: {
|
|
152
|
+
description: "We make everything.",
|
|
153
|
+
logoUrl: "https://acme.example/logo.png",
|
|
154
|
+
preferences: {
|
|
155
|
+
standingContext: "We deploy to us-east-1.",
|
|
156
|
+
memoryEnabled: true,
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
const update = vi.fn(async (_input: OrganizationInput) => orgWithMemoryOn);
|
|
161
|
+
const client = {
|
|
162
|
+
organization: {
|
|
163
|
+
get: vi.fn(async () => orgWithMemoryOn),
|
|
164
|
+
update,
|
|
165
|
+
},
|
|
166
|
+
iamPolicy: {
|
|
167
|
+
checkMyPermission: vi.fn(async () => ({ isAuthorized: true })),
|
|
168
|
+
},
|
|
169
|
+
} as never;
|
|
170
|
+
renderPanel(client);
|
|
171
|
+
|
|
172
|
+
const field = await findSyncedField("We deploy to us-east-1.");
|
|
173
|
+
fireEvent.change(field, { target: { value: "Prefer terse answers." } });
|
|
174
|
+
fireEvent.click(screen.getByRole("button", { name: "Save changes" }));
|
|
175
|
+
|
|
176
|
+
await waitFor(() => expect(update).toHaveBeenCalledTimes(1));
|
|
177
|
+
// Update is a full-spec replace: without the nested preferences
|
|
178
|
+
// spread, this save would silently switch the org's memory OFF.
|
|
179
|
+
expect(update.mock.calls[0]![0].preferences?.memoryEnabled).toBe(true);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("memory toggle is read-only without can_edit", async () => {
|
|
183
|
+
renderPanel(createMockStigmer({ canEdit: false }));
|
|
184
|
+
await screen.findByLabelText("Standing context");
|
|
185
|
+
|
|
186
|
+
await waitFor(() =>
|
|
187
|
+
expect(
|
|
188
|
+
screen.getByRole("switch", { name: "Memory" }).hasAttribute("disabled"),
|
|
189
|
+
).toBe(true),
|
|
190
|
+
);
|
|
191
|
+
});
|
|
192
|
+
|
|
128
193
|
it("shows the fetch error with a retry action when loading fails", async () => {
|
|
129
194
|
const client = {
|
|
130
195
|
organization: {
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Armed-only indicator for the session-scoped auto-approve state, docked
|
|
5
|
+
* above the composer. Renders nothing about approvals until auto-approve is
|
|
6
|
+
* actually ON — the composer stays clean by default — and while ON it is the
|
|
7
|
+
* always-visible safety surface: the user can never be silently
|
|
8
|
+
* auto-approving without seeing this strip and its one-click "Turn off".
|
|
9
|
+
*
|
|
10
|
+
* The way ON lives elsewhere (the Config facet's Run Config switch, the
|
|
11
|
+
* gate-time "Approve & don't ask again", the account's `default_auto_approve`
|
|
12
|
+
* preference, the host's `approvalDefaults`); this strip is deliberately only
|
|
13
|
+
* the way OFF plus the disclosure. Shared by `SessionViewer` and
|
|
14
|
+
* `NewSessionViewer` (DD-016) — a persisted account preference can arm the
|
|
15
|
+
* launcher before the first message, so both surfaces need the disclosure.
|
|
16
|
+
*/
|
|
17
|
+
export function AutoApproveIndicator({ onTurnOff }: { readonly onTurnOff: () => void }) {
|
|
18
|
+
return (
|
|
19
|
+
<div
|
|
20
|
+
role="status"
|
|
21
|
+
className="stg:flex stg:items-center stg:gap-2 stg:border-t stg:border-border-muted stg:px-4 stg:py-1.5 stg:text-xs stg:text-muted-foreground"
|
|
22
|
+
>
|
|
23
|
+
<ShieldCheckIcon />
|
|
24
|
+
<span className="stg:min-w-0 stg:flex-1 stg:truncate">
|
|
25
|
+
Auto-approving tool calls for this session
|
|
26
|
+
</span>
|
|
27
|
+
<button
|
|
28
|
+
type="button"
|
|
29
|
+
onClick={onTurnOff}
|
|
30
|
+
className="stg:shrink-0 stg:rounded stg:font-medium stg:text-foreground stg:underline-offset-2 stg:hover:underline stg:focus-visible:outline-none stg:focus-visible:ring-2 stg:focus-visible:ring-ring"
|
|
31
|
+
>
|
|
32
|
+
Turn off
|
|
33
|
+
</button>
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function ShieldCheckIcon() {
|
|
39
|
+
return (
|
|
40
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="stg:shrink-0 stg:text-success" aria-hidden="true">
|
|
41
|
+
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
|
|
42
|
+
<path d="m9 12 2 2 4-4" />
|
|
43
|
+
</svg>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
@@ -18,6 +18,7 @@ import { useNewSessionFlow } from "./useNewSessionFlow.js";
|
|
|
18
18
|
import { useSessionPanel } from "./useSessionPanel.js";
|
|
19
19
|
import { useSessionRailViews } from "./useSessionRailViews.js";
|
|
20
20
|
import { SessionPanelChip } from "./SessionPanelChip.js";
|
|
21
|
+
import { AutoApproveIndicator } from "./AutoApproveIndicator.js";
|
|
21
22
|
import type { RuntimeEnvProvider } from "./runtime-env.js";
|
|
22
23
|
import type { SessionAudience, SessionPanelMode } from "./audience.js";
|
|
23
24
|
import type { SessionRunConfig } from "./run-config.js";
|
|
@@ -374,6 +375,13 @@ export function NewSessionViewer({
|
|
|
374
375
|
harness: flow.harness,
|
|
375
376
|
executionTarget: undefined,
|
|
376
377
|
modelId: flow.modelId,
|
|
378
|
+
// Pre-session arming (#816): the switch covers the session this
|
|
379
|
+
// surface will create — the bootstrap execution carries
|
|
380
|
+
// spec.auto_approve_all. Guests never get it (they never render the
|
|
381
|
+
// panel either — belt and braces for the operator-consent withhold).
|
|
382
|
+
autoApprove: isGuest
|
|
383
|
+
? undefined
|
|
384
|
+
: { armed: flow.autoApproveAll, onChange: flow.setAutoApproveAll },
|
|
377
385
|
// Curated audiences see the configuration but cannot strip it — the
|
|
378
386
|
// Setup tab renders read-only without mutation callbacks (DD-011).
|
|
379
387
|
mutations: isCurated
|
|
@@ -387,6 +395,7 @@ export function NewSessionViewer({
|
|
|
387
395
|
[
|
|
388
396
|
flow.agentRef, flow.mcpServerUsages, flow.skillRefs,
|
|
389
397
|
flow.sessionVariables, flow.harness, flow.modelId,
|
|
398
|
+
flow.autoApproveAll, flow.setAutoApproveAll, isGuest,
|
|
390
399
|
isCurated, handleRemoveAgent, handleRemoveMcp, handleRemoveSkill,
|
|
391
400
|
],
|
|
392
401
|
);
|
|
@@ -461,6 +470,14 @@ export function NewSessionViewer({
|
|
|
461
470
|
ariaLabel="Start a new session"
|
|
462
471
|
/>
|
|
463
472
|
|
|
473
|
+
{/* Armed-only disclosure (#816 rework): a persisted account preference
|
|
474
|
+
or the host's approvalDefaults can pre-arm the session this surface
|
|
475
|
+
will create — the user must see that before the first send, with
|
|
476
|
+
the one-click way off. Guests never inherit either seed. */}
|
|
477
|
+
{!isGuest && flow.autoApproveAll && (
|
|
478
|
+
<AutoApproveIndicator onTurnOff={() => flow.setAutoApproveAll(false)} />
|
|
479
|
+
)}
|
|
480
|
+
|
|
464
481
|
{footerContent}
|
|
465
482
|
|
|
466
483
|
<p className="stg:text-center stg:text-[0.65rem] stg:text-muted-foreground">
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
type SurfaceVirtualDocument,
|
|
19
19
|
} from "../workspace/WorkspaceSurface.js";
|
|
20
20
|
import type { InteractionModeOption, SessionComposerHandle, SessionComposerSubmitContext } from "../composer/index.js";
|
|
21
|
+
import type { AccountExecutionDefaults } from "../identity-account/useAccountExecutionDefaults.js";
|
|
21
22
|
import type { ApplyResourceResult } from "../library/useApplyResource.js";
|
|
22
23
|
import { SessionViewerLayout } from "./SessionViewerLayout.js";
|
|
23
24
|
import { useWorkspaceEditors, isVirtualEntryId } from "../internal/store/index.js";
|
|
@@ -51,6 +52,8 @@ import { ArtifactDocument } from "../execution/ArtifactDocument.js";
|
|
|
51
52
|
import { useSessionRailViews } from "./useSessionRailViews.js";
|
|
52
53
|
import { useSessionPanel, type SessionPanelController } from "./useSessionPanel.js";
|
|
53
54
|
import { SessionPanelChip } from "./SessionPanelChip.js";
|
|
55
|
+
import { TranscriptExportMenu } from "./TranscriptExportMenu.js";
|
|
56
|
+
import { AutoApproveIndicator } from "./AutoApproveIndicator.js";
|
|
54
57
|
import { useSessionWriteBacks } from "./useSessionWriteBacks.js";
|
|
55
58
|
import { useWorkspaceReadRefs } from "./useWorkspaceReadRefs.js";
|
|
56
59
|
import {
|
|
@@ -239,6 +242,17 @@ export interface SessionViewerProps {
|
|
|
239
242
|
* server-side share policy owns. See {@link SessionRunConfig}.
|
|
240
243
|
*/
|
|
241
244
|
readonly runConfig?: SessionRunConfig;
|
|
245
|
+
/**
|
|
246
|
+
* The user's account-level execution defaults, typically from
|
|
247
|
+
* `useAccountExecutionDefaults()` — same seam as
|
|
248
|
+
* `NewSessionViewer.accountDefaults` (DD-016). On the session page only
|
|
249
|
+
* the `autoApprove` default applies: it seeds the session-scoped
|
|
250
|
+
* auto-approve state so a declared `default_auto_approve` preference
|
|
251
|
+
* covers follow-ups on existing sessions too. An explicit flip of the
|
|
252
|
+
* Config facet's switch wins for this session. Ignored for the
|
|
253
|
+
* `"guest"` audience.
|
|
254
|
+
*/
|
|
255
|
+
readonly accountDefaults?: AccountExecutionDefaults;
|
|
242
256
|
/**
|
|
243
257
|
* Whether the follow-up composer offers the model picker. `false`
|
|
244
258
|
* hides the picker while keeping the SDK's model sourcing (persisted
|
|
@@ -293,6 +307,19 @@ export interface SessionViewerProps {
|
|
|
293
307
|
* unopinionated about Console auth (DD-004).
|
|
294
308
|
*/
|
|
295
309
|
readonly headerActions?: ReactNode;
|
|
310
|
+
/**
|
|
311
|
+
* Whether the viewer offers its built-in whole-conversation export
|
|
312
|
+
* (stigmer/stigmer#814). Placement is panel-aware: viewers with the
|
|
313
|
+
* session panel export from the Config facet's Transcript section, and
|
|
314
|
+
* panel-less viewers (guests, `panel="none"`) render the header
|
|
315
|
+
* {@link TranscriptExportMenu} instead — so wherever a conversation can
|
|
316
|
+
* be viewed, its transcript stays reachable. The export carries only
|
|
317
|
+
* what the current caller can already read (`can_view`-scoped). Hosts
|
|
318
|
+
* that own their export surface (or inert demo scenes) opt out.
|
|
319
|
+
*
|
|
320
|
+
* @default true
|
|
321
|
+
*/
|
|
322
|
+
readonly transcriptExport?: boolean;
|
|
296
323
|
/**
|
|
297
324
|
* Host-injected access management control (e.g. the Console's
|
|
298
325
|
* `ManageAccessButton` with its own permission gating). Rendered inside
|
|
@@ -368,12 +395,14 @@ export function SessionViewer({
|
|
|
368
395
|
getRuntimeEnv,
|
|
369
396
|
audience = "integrator",
|
|
370
397
|
runConfig,
|
|
398
|
+
accountDefaults,
|
|
371
399
|
showModelSelector = true,
|
|
372
400
|
panel: panelMode = "auto",
|
|
373
401
|
defaultPanelOpen,
|
|
374
402
|
panelOpen,
|
|
375
403
|
onPanelOpenChange,
|
|
376
404
|
headerActions,
|
|
405
|
+
transcriptExport = true,
|
|
377
406
|
accessSlot,
|
|
378
407
|
onApplied,
|
|
379
408
|
threadSlots,
|
|
@@ -385,6 +414,7 @@ export function SessionViewer({
|
|
|
385
414
|
getRuntimeEnv,
|
|
386
415
|
audience,
|
|
387
416
|
runConfig,
|
|
417
|
+
accountDefaults,
|
|
388
418
|
});
|
|
389
419
|
const { conv } = flow;
|
|
390
420
|
const isGuest = audience === "guest";
|
|
@@ -650,9 +680,22 @@ export function SessionViewer({
|
|
|
650
680
|
// panel's always-mounted toggle; while collapsed it carries only the
|
|
651
681
|
// pending-item count. Execution status is never surfaced as header
|
|
652
682
|
// chrome — the thread itself communicates run state. A viewer without
|
|
653
|
-
// a panel (guests — session configuration is not a visitor's business
|
|
654
|
-
//
|
|
655
|
-
|
|
683
|
+
// a panel (guests — session configuration is not a visitor's business —
|
|
684
|
+
// or a host's panel="none") has no toggle: it is simply absent.
|
|
685
|
+
//
|
|
686
|
+
// Transcript export lives in the panel's Config facet; the header menu
|
|
687
|
+
// renders ONLY for panel-less viewers, so no audience loses export
|
|
688
|
+
// (guests and panel="none" hosts have no facet to reach it through).
|
|
689
|
+
headerActions={
|
|
690
|
+
transcriptExport && !panelEnabled ? (
|
|
691
|
+
<>
|
|
692
|
+
{headerActions}
|
|
693
|
+
<TranscriptExportMenu sessionId={sessionId} />
|
|
694
|
+
</>
|
|
695
|
+
) : (
|
|
696
|
+
headerActions
|
|
697
|
+
)
|
|
698
|
+
}
|
|
656
699
|
chip={
|
|
657
700
|
panelEnabled ? (
|
|
658
701
|
<SessionPanelChip
|
|
@@ -720,6 +763,10 @@ export function SessionViewer({
|
|
|
720
763
|
workspaceFileReader={workspaceFileReader}
|
|
721
764
|
workspaceContentSearcher={workspaceContentSearcher}
|
|
722
765
|
isCurated={isCurated}
|
|
766
|
+
isObserver={isObserver}
|
|
767
|
+
// Drives the Config facet's Transcript section only — a host
|
|
768
|
+
// that opted out of export gets no facet section either.
|
|
769
|
+
exportSessionId={transcriptExport ? sessionId : null}
|
|
723
770
|
/>
|
|
724
771
|
) : null
|
|
725
772
|
}
|
|
@@ -948,6 +995,10 @@ const ConversationColumn = memo(function ConversationColumn({
|
|
|
948
995
|
/>
|
|
949
996
|
)}
|
|
950
997
|
{!isObserver && sendError && <SendErrorBanner error={sendError} />}
|
|
998
|
+
{/* Armed-only disclosure (#816 rework): auto-approve's way ON lives in
|
|
999
|
+
the Config facet and the account preference — this strip is the
|
|
1000
|
+
always-visible proof it is on, plus the one-click way off. Never
|
|
1001
|
+
shown to observers (they cannot arm or disarm anything). */}
|
|
951
1002
|
{!isObserver && flow.autoApproveAll && (
|
|
952
1003
|
<AutoApproveIndicator onTurnOff={() => flow.setAutoApproveAll(false)} />
|
|
953
1004
|
)}
|
|
@@ -1068,6 +1119,17 @@ interface SessionPanelRegionProps {
|
|
|
1068
1119
|
readonly workspaceContentSearcher?: WorkspaceContentSearcher;
|
|
1069
1120
|
/** Curated audience (endUser or guest): the Setup facet renders read-only. */
|
|
1070
1121
|
readonly isCurated: boolean;
|
|
1122
|
+
/**
|
|
1123
|
+
* Observer audience (incl. channel-origin sessions): read-only viewers who
|
|
1124
|
+
* must never receive the Config facet's auto-approve switch — the same
|
|
1125
|
+
* withhold as approval submission. (Guests never render the panel at all.)
|
|
1126
|
+
*/
|
|
1127
|
+
readonly isObserver: boolean;
|
|
1128
|
+
/**
|
|
1129
|
+
* Session id for the Config facet's Transcript export section, or `null`
|
|
1130
|
+
* when the host opted out of export (`transcriptExport={false}`).
|
|
1131
|
+
*/
|
|
1132
|
+
readonly exportSessionId: string | null;
|
|
1071
1133
|
}
|
|
1072
1134
|
|
|
1073
1135
|
function SessionPanelRegion({
|
|
@@ -1091,6 +1153,8 @@ function SessionPanelRegion({
|
|
|
1091
1153
|
workspaceFileReader,
|
|
1092
1154
|
workspaceContentSearcher,
|
|
1093
1155
|
isCurated,
|
|
1156
|
+
isObserver,
|
|
1157
|
+
exportSessionId,
|
|
1094
1158
|
}: SessionPanelRegionProps) {
|
|
1095
1159
|
const { editors, activeKey, activeFile, reveal } = useWorkspaceEditors(
|
|
1096
1160
|
panel.editorsStore,
|
|
@@ -1258,6 +1322,16 @@ function SessionPanelRegion({
|
|
|
1258
1322
|
harness: flow.harness,
|
|
1259
1323
|
executionTarget: flow.executionTarget,
|
|
1260
1324
|
modelId: flow.model[0],
|
|
1325
|
+
// Arming auto-approve is an operator decision — withheld from
|
|
1326
|
+
// observers with the same gate as approval submission (guests never
|
|
1327
|
+
// render the panel). The switch stays interactive mid-run: flipping
|
|
1328
|
+
// it ON releases the in-flight run's gates via the flow's standing
|
|
1329
|
+
// responder (#816).
|
|
1330
|
+
autoApprove: isObserver
|
|
1331
|
+
? undefined
|
|
1332
|
+
: { armed: flow.autoApproveAll, onChange: flow.setAutoApproveAll },
|
|
1333
|
+
// Transcript export is view-scoped, so observers keep it (#814).
|
|
1334
|
+
sessionId: exportSessionId,
|
|
1261
1335
|
// Curated audiences see the configuration but cannot strip it — the
|
|
1262
1336
|
// Config facet renders read-only without mutation callbacks (DD-011).
|
|
1263
1337
|
mutations: isCurated
|
|
@@ -1272,6 +1346,7 @@ function SessionPanelRegion({
|
|
|
1272
1346
|
[
|
|
1273
1347
|
flow.agentRef, flow.isDefaultAgent, flow.mcpServerUsages, flow.skillRefs,
|
|
1274
1348
|
flow.sessionVariables, flow.harness, flow.executionTarget, flow.model,
|
|
1349
|
+
flow.autoApproveAll, flow.setAutoApproveAll, isObserver, exportSessionId,
|
|
1275
1350
|
isCurated, handleRemoveAgent, handleRemoveMcp, handleRemoveSkill,
|
|
1276
1351
|
accessSlot,
|
|
1277
1352
|
],
|
|
@@ -1386,36 +1461,6 @@ function SessionStarting() {
|
|
|
1386
1461
|
);
|
|
1387
1462
|
}
|
|
1388
1463
|
|
|
1389
|
-
/**
|
|
1390
|
-
* Low-weight, always-visible indicator shown while the session-scoped
|
|
1391
|
-
* auto-approve preference is active. The "Turn off" control reverts the
|
|
1392
|
-
* preference in one click — the safety affordance for "Approve & don't ask
|
|
1393
|
-
* again". Visible from the first render when the host pre-armed the
|
|
1394
|
-
* preference via `StigmerProvider`'s `approvalDefaults` (#302); otherwise
|
|
1395
|
-
* nothing about approvals appears until the user opts in at a gate. Either
|
|
1396
|
-
* way the user keeps the last word.
|
|
1397
|
-
*/
|
|
1398
|
-
function AutoApproveIndicator({ onTurnOff }: { onTurnOff: () => void }) {
|
|
1399
|
-
return (
|
|
1400
|
-
<div
|
|
1401
|
-
role="status"
|
|
1402
|
-
className="stg:flex stg:items-center stg:gap-2 stg:border-t stg:border-border-muted stg:px-4 stg:py-1.5 stg:text-xs stg:text-muted-foreground"
|
|
1403
|
-
>
|
|
1404
|
-
<ShieldCheckIcon />
|
|
1405
|
-
<span className="stg:min-w-0 stg:flex-1 stg:truncate">
|
|
1406
|
-
Auto-approving tool calls for this session
|
|
1407
|
-
</span>
|
|
1408
|
-
<button
|
|
1409
|
-
type="button"
|
|
1410
|
-
onClick={onTurnOff}
|
|
1411
|
-
className="stg:shrink-0 stg:rounded stg:font-medium stg:text-foreground stg:underline-offset-2 stg:hover:underline stg:focus-visible:outline-none stg:focus-visible:ring-2 stg:focus-visible:ring-ring"
|
|
1412
|
-
>
|
|
1413
|
-
Turn off
|
|
1414
|
-
</button>
|
|
1415
|
-
</div>
|
|
1416
|
-
);
|
|
1417
|
-
}
|
|
1418
|
-
|
|
1419
1464
|
/**
|
|
1420
1465
|
* The plan document tab's empty state — reachable only when a streaming plan
|
|
1421
1466
|
* auto-opened the tab and its turn then ended without publishing (stopped or
|
|
@@ -1616,11 +1661,3 @@ function LoaderIcon() {
|
|
|
1616
1661
|
);
|
|
1617
1662
|
}
|
|
1618
1663
|
|
|
1619
|
-
function ShieldCheckIcon() {
|
|
1620
|
-
return (
|
|
1621
|
-
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="stg:shrink-0 stg:text-success" aria-hidden="true">
|
|
1622
|
-
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
|
|
1623
|
-
<path d="m9 12 2 2 4-4" />
|
|
1624
|
-
</svg>
|
|
1625
|
-
);
|
|
1626
|
-
}
|