@stigmer/react 3.12.7 → 3.12.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -3
- package/composer/ComposerToolbar.d.ts +1 -19
- package/composer/ComposerToolbar.d.ts.map +1 -1
- package/composer/ComposerToolbar.js +2 -7
- package/composer/ComposerToolbar.js.map +1 -1
- package/composer/SessionComposer.d.ts +0 -10
- 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 +0 -1
- package/composer/index.d.ts.map +1 -1
- package/composer/index.js.map +1 -1
- package/conversation/ConversationTimelineView.d.ts +15 -1
- package/conversation/ConversationTimelineView.d.ts.map +1 -1
- package/conversation/ConversationTimelineView.js +3 -2
- package/conversation/ConversationTimelineView.js.map +1 -1
- package/conversation/ConversationsWorkbench.d.ts +14 -1
- package/conversation/ConversationsWorkbench.d.ts.map +1 -1
- package/conversation/ConversationsWorkbench.js +11 -2
- package/conversation/ConversationsWorkbench.js.map +1 -1
- package/execution/MessageThread.d.ts +33 -2
- package/execution/MessageThread.d.ts.map +1 -1
- package/execution/MessageThread.js +44 -6
- package/execution/MessageThread.js.map +1 -1
- package/execution/RecalledMemoriesCard.d.ts +47 -0
- package/execution/RecalledMemoriesCard.d.ts.map +1 -0
- package/execution/RecalledMemoriesCard.js +58 -0
- package/execution/RecalledMemoriesCard.js.map +1 -0
- 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/index.d.ts +2 -0
- package/execution/index.d.ts.map +1 -1
- package/execution/index.js +1 -0
- package/execution/index.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/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 +6 -6
- package/index.d.ts.map +1 -1
- package/index.js +2 -2
- 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/VirtualizedThread.d.ts +3 -1
- package/internal/VirtualizedThread.d.ts.map +1 -1
- package/internal/VirtualizedThread.js +3 -1
- package/internal/VirtualizedThread.js.map +1 -1
- package/internal/useAutoScroll.d.ts +18 -0
- package/internal/useAutoScroll.d.ts.map +1 -1
- package/internal/useAutoScroll.js +50 -0
- package/internal/useAutoScroll.js.map +1 -1
- 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 +2 -0
- package/memory/index.d.ts.map +1 -1
- package/memory/index.js +2 -0
- package/memory/index.js.map +1 -1
- 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/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 -9
- package/session/NewSessionViewer.js.map +1 -1
- package/session/SessionViewer.d.ts +21 -9
- package/session/SessionViewer.d.ts.map +1 -1
- package/session/SessionViewer.js +31 -21
- package/session/SessionViewer.js.map +1 -1
- 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 +1 -1
- package/session/index.d.ts.map +1 -1
- package/session/index.js.map +1 -1
- package/session/useNewSessionFlow.d.ts +9 -4
- package/session/useNewSessionFlow.d.ts.map +1 -1
- package/session/useNewSessionFlow.js +20 -9
- package/session/useNewSessionFlow.js.map +1 -1
- package/session/useSessionPageFlow.d.ts +28 -10
- package/session/useSessionPageFlow.d.ts.map +1 -1
- package/session/useSessionPageFlow.js +16 -10
- package/session/useSessionPageFlow.js.map +1 -1
- package/settings/MemorySection.d.ts.map +1 -1
- package/settings/MemorySection.js +1 -1
- package/settings/MemorySection.js.map +1 -1
- package/src/attachment/__tests__/prepare-image.test.ts +12 -6
- package/src/composer/ComposerToolbar.tsx +0 -57
- package/src/composer/SessionComposer.tsx +1 -13
- package/src/composer/index.ts +0 -1
- package/src/conversation/ConversationTimelineView.tsx +17 -1
- package/src/conversation/ConversationsWorkbench.tsx +25 -0
- package/src/conversation/__tests__/ConversationTimelineView.layout.test.tsx +37 -1
- package/src/execution/MessageThread.tsx +75 -3
- package/src/execution/RecalledMemoriesCard.tsx +164 -0
- package/src/execution/ResultView.tsx +6 -0
- package/src/execution/ToolCallItem.tsx +18 -1
- package/src/execution/__tests__/RecalledMemoriesCard.test.tsx +123 -0
- package/src/execution/__tests__/message-thread-recalled-memories.test.tsx +216 -0
- package/src/execution/__tests__/message-thread-scroll-on-send.test.tsx +141 -0
- package/src/execution/__tests__/message-thread-slots.test.tsx +36 -0
- package/src/execution/index.ts +2 -0
- package/src/execution/tool-categories.ts +7 -0
- 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 +8 -1
- package/src/internal/MemoryEnabledRow.tsx +92 -0
- package/src/internal/VirtualizedThread.tsx +5 -0
- package/src/internal/__tests__/useAutoScroll.layout.test.tsx +105 -4
- package/src/internal/__tests__/useAutoScroll.test.tsx +41 -1
- package/src/internal/useAutoScroll.ts +54 -0
- package/src/memory/MemoryProposalCard.tsx +181 -0
- package/src/memory/__tests__/MemoryProposalCard.test.tsx +236 -0
- package/src/memory/__tests__/memoryHooks.test.tsx +63 -0
- package/src/memory/index.ts +5 -0
- package/src/memory/useMemory.ts +80 -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 +18 -15
- package/src/session/SessionViewer.tsx +70 -32
- 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 +60 -22
- package/src/session/__tests__/useNewSessionFlow.test.tsx +56 -0
- package/src/session/__tests__/useSessionPageFlow.autoApprove.test.tsx +87 -0
- 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 +1 -0
- package/src/session/useNewSessionFlow.ts +30 -15
- package/src/session/useSessionPageFlow.ts +44 -20
- package/src/settings/MemorySection.tsx +4 -2
- package/src/switch/Switch.tsx +4 -0
- package/src/workflow/__tests__/WorkflowExecutionViewer.approvals.test.tsx +26 -7
- package/src/workflow/__tests__/task-presentation.test.ts +49 -12
- package/src/workflow/__tests__/useWorkflowCanvas.dirty.test.tsx +7 -2
- package/src/workflow/__tests__/useWorkflowCanvas.drag.test.tsx +7 -2
- package/src/workflow/__tests__/workflow-thread-scroll-on-send.test.tsx +145 -0
- package/src/workflow/thread/WorkflowTaskThread.tsx +59 -32
- package/src/workflow/thread/task-presentation.ts +52 -7
- 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/workflow/thread/WorkflowTaskThread.d.ts +13 -0
- package/workflow/thread/WorkflowTaskThread.d.ts.map +1 -1
- package/workflow/thread/WorkflowTaskThread.js +33 -25
- package/workflow/thread/WorkflowTaskThread.js.map +1 -1
- package/workflow/thread/task-presentation.d.ts.map +1 -1
- package/workflow/thread/task-presentation.js +50 -7
- package/workflow/thread/task-presentation.js.map +1 -1
- package/workspace/WorkspaceSurface.js +11 -2
- package/workspace/WorkspaceSurface.js.map +1 -1
- package/src/composer/__tests__/ComposerToolbar-autoApprove.test.tsx +0 -80
- package/src/session/__tests__/SessionViewer-autoApproveToggle.test.tsx +0 -178
|
@@ -6,7 +6,7 @@ import type { UseGitHubConnectionReturn } from "../github/useGitHubConnection.js
|
|
|
6
6
|
import type { WorkspaceFileLister } from "../workspace/WorkspaceFileLister.js";
|
|
7
7
|
import type { WorkspaceFileReader } from "../workspace/WorkspaceFileReader.js";
|
|
8
8
|
import type { WorkspaceContentSearcher } from "../workspace/WorkspaceContentSearcher.js";
|
|
9
|
-
import type {
|
|
9
|
+
import type { InteractionModeOption } from "../composer/index.js";
|
|
10
10
|
import { SessionComposer } from "../composer/index.js";
|
|
11
11
|
import type { HarnessOption } from "../models/harness.js";
|
|
12
12
|
import type { AccountExecutionDefaults } from "../identity-account/useAccountExecutionDefaults.js";
|
|
@@ -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";
|
|
@@ -299,19 +300,6 @@ export function NewSessionViewer({
|
|
|
299
300
|
// SessionViewer derives (DD-016), gating the chip and the region together.
|
|
300
301
|
const panelEnabled = panelMode !== "none" && !isGuest;
|
|
301
302
|
|
|
302
|
-
// Always-visible auto-approve toggle (#816), present before the first
|
|
303
|
-
// message is ever typed — the walk-away user arms it here and the create
|
|
304
|
-
// carries spec.auto_approve_all server-side. Guests never get it (the
|
|
305
|
-
// operator-consent reasoning as the #302 host default). Memoized per
|
|
306
|
-
// DD-010 — the composer is memo'd and an inline object would defeat it.
|
|
307
|
-
const autoApprove = useMemo<ComposerAutoApproveProps | undefined>(
|
|
308
|
-
() =>
|
|
309
|
-
isGuest
|
|
310
|
-
? undefined
|
|
311
|
-
: { armed: flow.autoApproveAll, onChange: flow.setAutoApproveAll },
|
|
312
|
-
[isGuest, flow.autoApproveAll, flow.setAutoApproveAll],
|
|
313
|
-
);
|
|
314
|
-
|
|
315
303
|
// Guest agent binding is host configuration, not a picker interaction:
|
|
316
304
|
// the composer's agent machinery (picker, env-collection, personal
|
|
317
305
|
// environments — all org reads a guest token cannot make) stays fully
|
|
@@ -387,6 +375,13 @@ export function NewSessionViewer({
|
|
|
387
375
|
harness: flow.harness,
|
|
388
376
|
executionTarget: undefined,
|
|
389
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 },
|
|
390
385
|
// Curated audiences see the configuration but cannot strip it — the
|
|
391
386
|
// Setup tab renders read-only without mutation callbacks (DD-011).
|
|
392
387
|
mutations: isCurated
|
|
@@ -400,6 +395,7 @@ export function NewSessionViewer({
|
|
|
400
395
|
[
|
|
401
396
|
flow.agentRef, flow.mcpServerUsages, flow.skillRefs,
|
|
402
397
|
flow.sessionVariables, flow.harness, flow.modelId,
|
|
398
|
+
flow.autoApproveAll, flow.setAutoApproveAll, isGuest,
|
|
403
399
|
isCurated, handleRemoveAgent, handleRemoveMcp, handleRemoveSkill,
|
|
404
400
|
],
|
|
405
401
|
);
|
|
@@ -465,7 +461,6 @@ export function NewSessionViewer({
|
|
|
465
461
|
onInteractionModeChange={setInteractionMode}
|
|
466
462
|
showInteractionModePicker={!isGuest}
|
|
467
463
|
showModelSelector={modelSelectorVisible}
|
|
468
|
-
autoApprove={autoApprove}
|
|
469
464
|
enableAttachments={!isGuest}
|
|
470
465
|
defaultModelId={flow.modelId}
|
|
471
466
|
onModelChange={flow.setModelId}
|
|
@@ -475,6 +470,14 @@ export function NewSessionViewer({
|
|
|
475
470
|
ariaLabel="Start a new session"
|
|
476
471
|
/>
|
|
477
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
|
+
|
|
478
481
|
{footerContent}
|
|
479
482
|
|
|
480
483
|
<p className="stg:text-center stg:text-[0.65rem] stg:text-muted-foreground">
|
|
@@ -17,7 +17,8 @@ import {
|
|
|
17
17
|
WorkspaceSurface,
|
|
18
18
|
type SurfaceVirtualDocument,
|
|
19
19
|
} from "../workspace/WorkspaceSurface.js";
|
|
20
|
-
import type {
|
|
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";
|
|
@@ -52,6 +53,7 @@ import { useSessionRailViews } from "./useSessionRailViews.js";
|
|
|
52
53
|
import { useSessionPanel, type SessionPanelController } from "./useSessionPanel.js";
|
|
53
54
|
import { SessionPanelChip } from "./SessionPanelChip.js";
|
|
54
55
|
import { TranscriptExportMenu } from "./TranscriptExportMenu.js";
|
|
56
|
+
import { AutoApproveIndicator } from "./AutoApproveIndicator.js";
|
|
55
57
|
import { useSessionWriteBacks } from "./useSessionWriteBacks.js";
|
|
56
58
|
import { useWorkspaceReadRefs } from "./useWorkspaceReadRefs.js";
|
|
57
59
|
import {
|
|
@@ -240,6 +242,17 @@ export interface SessionViewerProps {
|
|
|
240
242
|
* server-side share policy owns. See {@link SessionRunConfig}.
|
|
241
243
|
*/
|
|
242
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;
|
|
243
256
|
/**
|
|
244
257
|
* Whether the follow-up composer offers the model picker. `false`
|
|
245
258
|
* hides the picker while keeping the SDK's model sourcing (persisted
|
|
@@ -295,14 +308,14 @@ export interface SessionViewerProps {
|
|
|
295
308
|
*/
|
|
296
309
|
readonly headerActions?: ReactNode;
|
|
297
310
|
/**
|
|
298
|
-
* Whether the viewer
|
|
299
|
-
*
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
-
*
|
|
303
|
-
*
|
|
304
|
-
*
|
|
305
|
-
*
|
|
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.
|
|
306
319
|
*
|
|
307
320
|
* @default true
|
|
308
321
|
*/
|
|
@@ -382,6 +395,7 @@ export function SessionViewer({
|
|
|
382
395
|
getRuntimeEnv,
|
|
383
396
|
audience = "integrator",
|
|
384
397
|
runConfig,
|
|
398
|
+
accountDefaults,
|
|
385
399
|
showModelSelector = true,
|
|
386
400
|
panel: panelMode = "auto",
|
|
387
401
|
defaultPanelOpen,
|
|
@@ -400,6 +414,7 @@ export function SessionViewer({
|
|
|
400
414
|
getRuntimeEnv,
|
|
401
415
|
audience,
|
|
402
416
|
runConfig,
|
|
417
|
+
accountDefaults,
|
|
403
418
|
});
|
|
404
419
|
const { conv } = flow;
|
|
405
420
|
const isGuest = audience === "guest";
|
|
@@ -661,15 +676,18 @@ export function SessionViewer({
|
|
|
661
676
|
return (
|
|
662
677
|
<SessionViewerLayout
|
|
663
678
|
className={className}
|
|
664
|
-
// Top-right controls: host actions + the
|
|
665
|
-
//
|
|
666
|
-
//
|
|
667
|
-
//
|
|
668
|
-
//
|
|
669
|
-
//
|
|
670
|
-
//
|
|
679
|
+
// Top-right controls: host actions + the panel chip. The chip is the
|
|
680
|
+
// panel's always-mounted toggle; while collapsed it carries only the
|
|
681
|
+
// pending-item count. Execution status is never surfaced as header
|
|
682
|
+
// chrome — the thread itself communicates run state. A viewer without
|
|
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).
|
|
671
689
|
headerActions={
|
|
672
|
-
transcriptExport ? (
|
|
690
|
+
transcriptExport && !panelEnabled ? (
|
|
673
691
|
<>
|
|
674
692
|
{headerActions}
|
|
675
693
|
<TranscriptExportMenu sessionId={sessionId} />
|
|
@@ -745,6 +763,10 @@ export function SessionViewer({
|
|
|
745
763
|
workspaceFileReader={workspaceFileReader}
|
|
746
764
|
workspaceContentSearcher={workspaceContentSearcher}
|
|
747
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}
|
|
748
770
|
/>
|
|
749
771
|
) : null
|
|
750
772
|
}
|
|
@@ -849,20 +871,6 @@ const ConversationColumn = memo(function ConversationColumn({
|
|
|
849
871
|
void conv.stop();
|
|
850
872
|
}, [conv.stop]);
|
|
851
873
|
|
|
852
|
-
// Always-visible auto-approve toggle (#816), replacing the old armed-only
|
|
853
|
-
// indicator banner: the walk-away user needs a way ON before any gate
|
|
854
|
-
// exists, not just a way off after one. Guests never get it (they don't
|
|
855
|
-
// inherit the host default and this is the operator's consent surface);
|
|
856
|
-
// observers have no composer at all. Memoized per DD-010 — the composer
|
|
857
|
-
// is memo'd and an inline object would defeat it.
|
|
858
|
-
const autoApprove = useMemo<ComposerAutoApproveProps | undefined>(
|
|
859
|
-
() =>
|
|
860
|
-
isGuest
|
|
861
|
-
? undefined
|
|
862
|
-
: { armed: flow.autoApproveAll, onChange: flow.setAutoApproveAll },
|
|
863
|
-
[isGuest, flow.autoApproveAll, flow.setAutoApproveAll],
|
|
864
|
-
);
|
|
865
|
-
|
|
866
874
|
// Edit-and-resubmit: stop the in-flight turn, pre-fill the composer with
|
|
867
875
|
// the original text, and remember which execution is being edited. The
|
|
868
876
|
// append-only execution log is never rewritten — submitting while editing
|
|
@@ -987,6 +995,13 @@ const ConversationColumn = memo(function ConversationColumn({
|
|
|
987
995
|
/>
|
|
988
996
|
)}
|
|
989
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). */}
|
|
1002
|
+
{!isObserver && flow.autoApproveAll && (
|
|
1003
|
+
<AutoApproveIndicator onTurnOff={() => flow.setAutoApproveAll(false)} />
|
|
1004
|
+
)}
|
|
990
1005
|
{/* Pending file reviews dock here — pinned above the composer so the
|
|
991
1006
|
decision the agent is blocked on can never scroll out of view. The
|
|
992
1007
|
thread renders only observational rows (badges) and read-only
|
|
@@ -1028,7 +1043,6 @@ const ConversationColumn = memo(function ConversationColumn({
|
|
|
1028
1043
|
onInteractionModeChange={setInteractionMode}
|
|
1029
1044
|
showInteractionModePicker={!isGuest}
|
|
1030
1045
|
showModelSelector={modelSelectorVisible}
|
|
1031
|
-
autoApprove={autoApprove}
|
|
1032
1046
|
enableAttachments={!isGuest}
|
|
1033
1047
|
workspace={isGuest ? undefined : flow.workspace}
|
|
1034
1048
|
gitHubConnection={isGuest ? undefined : gitHubConnection}
|
|
@@ -1105,6 +1119,17 @@ interface SessionPanelRegionProps {
|
|
|
1105
1119
|
readonly workspaceContentSearcher?: WorkspaceContentSearcher;
|
|
1106
1120
|
/** Curated audience (endUser or guest): the Setup facet renders read-only. */
|
|
1107
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;
|
|
1108
1133
|
}
|
|
1109
1134
|
|
|
1110
1135
|
function SessionPanelRegion({
|
|
@@ -1128,6 +1153,8 @@ function SessionPanelRegion({
|
|
|
1128
1153
|
workspaceFileReader,
|
|
1129
1154
|
workspaceContentSearcher,
|
|
1130
1155
|
isCurated,
|
|
1156
|
+
isObserver,
|
|
1157
|
+
exportSessionId,
|
|
1131
1158
|
}: SessionPanelRegionProps) {
|
|
1132
1159
|
const { editors, activeKey, activeFile, reveal } = useWorkspaceEditors(
|
|
1133
1160
|
panel.editorsStore,
|
|
@@ -1295,6 +1322,16 @@ function SessionPanelRegion({
|
|
|
1295
1322
|
harness: flow.harness,
|
|
1296
1323
|
executionTarget: flow.executionTarget,
|
|
1297
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,
|
|
1298
1335
|
// Curated audiences see the configuration but cannot strip it — the
|
|
1299
1336
|
// Config facet renders read-only without mutation callbacks (DD-011).
|
|
1300
1337
|
mutations: isCurated
|
|
@@ -1309,6 +1346,7 @@ function SessionPanelRegion({
|
|
|
1309
1346
|
[
|
|
1310
1347
|
flow.agentRef, flow.isDefaultAgent, flow.mcpServerUsages, flow.skillRefs,
|
|
1311
1348
|
flow.sessionVariables, flow.harness, flow.executionTarget, flow.model,
|
|
1349
|
+
flow.autoApproveAll, flow.setAutoApproveAll, isObserver, exportSessionId,
|
|
1312
1350
|
isCurated, handleRemoveAgent, handleRemoveMcp, handleRemoveSkill,
|
|
1313
1351
|
accessSlot,
|
|
1314
1352
|
],
|
|
@@ -33,6 +33,7 @@ const emptyWorkspace = {
|
|
|
33
33
|
clear: vi.fn(),
|
|
34
34
|
};
|
|
35
35
|
|
|
36
|
+
const setAutoApproveAll = vi.fn();
|
|
36
37
|
const stubEmptyFlow = {
|
|
37
38
|
harness: "native" as const,
|
|
38
39
|
setHarness: vi.fn(),
|
|
@@ -48,6 +49,8 @@ const stubEmptyFlow = {
|
|
|
48
49
|
setSkillRefs: vi.fn(),
|
|
49
50
|
workspace: emptyWorkspace,
|
|
50
51
|
sessionVariables: { entries: [], isEmpty: true, clear: vi.fn() },
|
|
52
|
+
autoApproveAll: false,
|
|
53
|
+
setAutoApproveAll,
|
|
51
54
|
isSubmitting: false,
|
|
52
55
|
submitError: null,
|
|
53
56
|
submit: vi.fn(),
|
|
@@ -125,6 +128,35 @@ describe("NewSessionViewer — persistent panel chip", () => {
|
|
|
125
128
|
});
|
|
126
129
|
});
|
|
127
130
|
|
|
131
|
+
describe("NewSessionViewer — auto-approve placement (#816 rework)", () => {
|
|
132
|
+
const INDICATOR_TEXT = "Auto-approving tool calls for this session";
|
|
133
|
+
|
|
134
|
+
it("shows no indicator while auto-approve is off — the launcher stays clean", () => {
|
|
135
|
+
render(<NewSessionViewer org="acme" onSessionCreated={vi.fn()} />);
|
|
136
|
+
expect(screen.queryByText(INDICATOR_TEXT)).toBeNull();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("shows the armed-only indicator when a seed pre-arms the session, with Turn off", () => {
|
|
140
|
+
mockFlow = { ...stubEmptyFlow, autoApproveAll: true };
|
|
141
|
+
render(<NewSessionViewer org="acme" onSessionCreated={vi.fn()} />);
|
|
142
|
+
|
|
143
|
+
expect(screen.getByText(INDICATOR_TEXT)).toBeTruthy();
|
|
144
|
+
fireEvent.click(screen.getByRole("button", { name: "Turn off" }));
|
|
145
|
+
expect(setAutoApproveAll).toHaveBeenCalledExactlyOnceWith(false);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("offers the auto-approve switch in the Config facet, wired to the flow", () => {
|
|
149
|
+
render(<NewSessionViewer org="acme" onSessionCreated={vi.fn()} />);
|
|
150
|
+
|
|
151
|
+
fireEvent.click(screen.getByRole("button", { name: "Show panel" }));
|
|
152
|
+
const toggle = screen.getByRole("switch", { name: "Auto-approve" });
|
|
153
|
+
expect(toggle.getAttribute("aria-checked")).toBe("false");
|
|
154
|
+
|
|
155
|
+
fireEvent.click(toggle);
|
|
156
|
+
expect(setAutoApproveAll).toHaveBeenCalledExactlyOnceWith(true);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
128
160
|
describe("NewSessionViewer — composer stays centered", () => {
|
|
129
161
|
// The composer's wrapper is the probe's parent (wrapper > h1 + composer +
|
|
130
162
|
// footer). `my-auto` is unconditional safe-centering; the old `my-6`
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { render, screen, cleanup, fireEvent } from "@testing-library/react";
|
|
3
|
+
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Wiring-contract tests for auto-approve placement (the #816 rework):
|
|
6
|
+
//
|
|
7
|
+
// - The composer carries NO auto-approve control — the session-level switch
|
|
8
|
+
// lives in the Config facet (SetupTab receives the `autoApprove` group for
|
|
9
|
+
// every audience that may submit approvals, never observers; guests never
|
|
10
|
+
// render the panel at all).
|
|
11
|
+
// - The armed-only AutoApproveIndicator above the composer is the
|
|
12
|
+
// always-visible disclosure: present exactly while the state is armed,
|
|
13
|
+
// with a one-click "Turn off"; never rendered for observers.
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
type CapturedProps = Record<string, unknown>;
|
|
17
|
+
|
|
18
|
+
const composerProps: CapturedProps[] = [];
|
|
19
|
+
vi.mock("../../composer", async (importOriginal) => {
|
|
20
|
+
const actual = await importOriginal<typeof import("../../composer")>();
|
|
21
|
+
return {
|
|
22
|
+
...actual,
|
|
23
|
+
SessionComposer: (props: CapturedProps) => {
|
|
24
|
+
composerProps.push(props);
|
|
25
|
+
return <div data-testid="composer-probe" />;
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
vi.mock("../../execution/MessageThread", () => ({
|
|
31
|
+
MessageThread: () => <div data-testid="thread-probe" />,
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
vi.mock("../../execution/FileReviewDock", () => ({
|
|
35
|
+
FileReviewDock: () => <div data-testid="file-review-dock-probe" />,
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
// SetupTab renders for real: the facet switch is asserted through its
|
|
39
|
+
// actual DOM (a switch labeled "Auto-approve" in the Run Config section),
|
|
40
|
+
// reached by opening the panel and selecting the Config rail view.
|
|
41
|
+
|
|
42
|
+
const stubWorkspace = {
|
|
43
|
+
entries: [],
|
|
44
|
+
hasEntries: false,
|
|
45
|
+
toInput: vi.fn().mockReturnValue([]),
|
|
46
|
+
addGitRepo: vi.fn(),
|
|
47
|
+
addLocalPath: vi.fn(),
|
|
48
|
+
removeEntry: vi.fn(),
|
|
49
|
+
clear: vi.fn(),
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const stubConv = {
|
|
53
|
+
session: { spec: {} } as Record<string, unknown>,
|
|
54
|
+
isLoading: false,
|
|
55
|
+
loadError: null,
|
|
56
|
+
completedExecutions: [],
|
|
57
|
+
activeStreamExecution: null,
|
|
58
|
+
activePhase: null,
|
|
59
|
+
isStreaming: false,
|
|
60
|
+
isConnecting: false,
|
|
61
|
+
sendFollowUp: vi.fn(),
|
|
62
|
+
canSendFollowUp: true,
|
|
63
|
+
isSending: false,
|
|
64
|
+
sendError: null,
|
|
65
|
+
clearSendError: vi.fn(),
|
|
66
|
+
pendingUserMessage: null,
|
|
67
|
+
workspaceEntries: [],
|
|
68
|
+
mcpServerUsages: [],
|
|
69
|
+
skillRefs: [],
|
|
70
|
+
pendingApprovals: [],
|
|
71
|
+
submitApproval: vi.fn(),
|
|
72
|
+
submittingApprovalIds: new Set<string>(),
|
|
73
|
+
fileChangeSets: [],
|
|
74
|
+
submitFileDecision: vi.fn(),
|
|
75
|
+
submittingFileDecisionKeys: new Set<string>(),
|
|
76
|
+
fileDecisionErrors: new Map<string, Error>(),
|
|
77
|
+
streamError: null,
|
|
78
|
+
reconnectStream: vi.fn(),
|
|
79
|
+
approvalError: null,
|
|
80
|
+
retryLastSend: vi.fn(),
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const setAutoApproveAll = vi.fn();
|
|
84
|
+
const stubSessionPageFlow = {
|
|
85
|
+
conv: stubConv,
|
|
86
|
+
harness: "native" as const,
|
|
87
|
+
executionTarget: undefined,
|
|
88
|
+
model: [undefined, vi.fn()] as const,
|
|
89
|
+
interactionMode: ["agent" as const, vi.fn()] as const,
|
|
90
|
+
agentRef: { org: "acme", slug: "support-bot" },
|
|
91
|
+
setAgentRef: vi.fn(),
|
|
92
|
+
resolution: null,
|
|
93
|
+
setResolution: vi.fn(),
|
|
94
|
+
isDefaultAgent: false,
|
|
95
|
+
mcpServerUsages: [],
|
|
96
|
+
setMcpServerUsages: vi.fn(),
|
|
97
|
+
skillRefs: [],
|
|
98
|
+
setSkillRefs: vi.fn(),
|
|
99
|
+
workspace: stubWorkspace,
|
|
100
|
+
sessionVariables: { entries: [], isEmpty: true, clear: vi.fn() },
|
|
101
|
+
autoApproveAll: false,
|
|
102
|
+
setAutoApproveAll,
|
|
103
|
+
submitApproval: vi.fn(),
|
|
104
|
+
handleSubmit: vi.fn(),
|
|
105
|
+
submitError: null as Error | null,
|
|
106
|
+
displayExecution: null,
|
|
107
|
+
allExecutions: [],
|
|
108
|
+
sandboxWorkspaceRoot: undefined,
|
|
109
|
+
};
|
|
110
|
+
vi.mock("../useSessionPageFlow", () => ({
|
|
111
|
+
useSessionPageFlow: () => stubSessionPageFlow,
|
|
112
|
+
}));
|
|
113
|
+
|
|
114
|
+
vi.mock("../../hooks", () => ({
|
|
115
|
+
useStigmer: () => ({
|
|
116
|
+
agentExecution: {
|
|
117
|
+
uploadAttachment: vi.fn(),
|
|
118
|
+
getArtifactContent: vi.fn(),
|
|
119
|
+
},
|
|
120
|
+
}),
|
|
121
|
+
}));
|
|
122
|
+
|
|
123
|
+
import { SessionViewer } from "../SessionViewer";
|
|
124
|
+
|
|
125
|
+
const INDICATOR_TEXT = "Auto-approving tool calls for this session";
|
|
126
|
+
|
|
127
|
+
function lastComposerProps(): CapturedProps {
|
|
128
|
+
expect(composerProps.length).toBeGreaterThan(0);
|
|
129
|
+
return composerProps.at(-1)!;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Selects the panel's Config facet (the rail's radio). */
|
|
133
|
+
function openConfigFacet() {
|
|
134
|
+
fireEvent.click(screen.getByRole("radio", { name: "Config" }));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function facetSwitch(): HTMLElement | null {
|
|
138
|
+
return screen.queryByRole("switch", { name: "Auto-approve" });
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
beforeEach(() => {
|
|
142
|
+
composerProps.length = 0;
|
|
143
|
+
stubConv.session = { spec: {} };
|
|
144
|
+
stubSessionPageFlow.autoApproveAll = false;
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
afterEach(() => {
|
|
148
|
+
cleanup();
|
|
149
|
+
vi.clearAllMocks();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe("SessionViewer — composer stays clean (#816 rework)", () => {
|
|
153
|
+
it("hands the composer no auto-approve control at all", () => {
|
|
154
|
+
render(<SessionViewer sessionId="ses_1" org="acme" />);
|
|
155
|
+
|
|
156
|
+
expect("autoApprove" in lastComposerProps()).toBe(false);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe("SessionViewer — Config facet auto-approve switch (#816 rework)", () => {
|
|
161
|
+
it("renders the switch reflecting the flow state, and flipping calls the flow (integrator)", () => {
|
|
162
|
+
render(<SessionViewer sessionId="ses_1" org="acme" defaultPanelOpen />);
|
|
163
|
+
openConfigFacet();
|
|
164
|
+
|
|
165
|
+
const toggle = facetSwitch();
|
|
166
|
+
expect(toggle).not.toBeNull();
|
|
167
|
+
expect(toggle!.getAttribute("aria-checked")).toBe("false");
|
|
168
|
+
fireEvent.click(toggle!);
|
|
169
|
+
expect(setAutoApproveAll).toHaveBeenCalledExactlyOnceWith(true);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("mirrors an armed flow state into the switch", () => {
|
|
173
|
+
stubSessionPageFlow.autoApproveAll = true;
|
|
174
|
+
render(<SessionViewer sessionId="ses_1" org="acme" defaultPanelOpen />);
|
|
175
|
+
openConfigFacet();
|
|
176
|
+
|
|
177
|
+
expect(facetSwitch()!.getAttribute("aria-checked")).toBe("true");
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("endUser gets the switch — the walk-away persona is an end user", () => {
|
|
181
|
+
render(
|
|
182
|
+
<SessionViewer
|
|
183
|
+
sessionId="ses_1"
|
|
184
|
+
org="acme"
|
|
185
|
+
audience="endUser"
|
|
186
|
+
defaultPanelOpen
|
|
187
|
+
/>,
|
|
188
|
+
);
|
|
189
|
+
openConfigFacet();
|
|
190
|
+
|
|
191
|
+
expect(facetSwitch()).not.toBeNull();
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("observers never get the switch — the approval-submission withhold", () => {
|
|
195
|
+
render(
|
|
196
|
+
<SessionViewer
|
|
197
|
+
sessionId="ses_1"
|
|
198
|
+
org="acme"
|
|
199
|
+
audience="observer"
|
|
200
|
+
defaultPanelOpen
|
|
201
|
+
/>,
|
|
202
|
+
);
|
|
203
|
+
openConfigFacet();
|
|
204
|
+
|
|
205
|
+
expect(facetSwitch()).toBeNull();
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it("channel-origin sessions never get the switch (observer by construction)", () => {
|
|
209
|
+
stubConv.session = {
|
|
210
|
+
spec: {},
|
|
211
|
+
metadata: { labels: { "stigmer.ai/channel-id": "chn_1" } },
|
|
212
|
+
};
|
|
213
|
+
render(<SessionViewer sessionId="ses_1" org="acme" defaultPanelOpen />);
|
|
214
|
+
openConfigFacet();
|
|
215
|
+
|
|
216
|
+
expect(facetSwitch()).toBeNull();
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
describe("SessionViewer — armed-only indicator (#816 rework)", () => {
|
|
221
|
+
it("absent while auto-approve is off — the composer stays clean", () => {
|
|
222
|
+
render(<SessionViewer sessionId="ses_1" org="acme" />);
|
|
223
|
+
|
|
224
|
+
expect(screen.queryByText(INDICATOR_TEXT)).toBeNull();
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it("renders while armed, and Turn off flips the flow state", () => {
|
|
228
|
+
stubSessionPageFlow.autoApproveAll = true;
|
|
229
|
+
render(<SessionViewer sessionId="ses_1" org="acme" />);
|
|
230
|
+
|
|
231
|
+
expect(screen.getByText(INDICATOR_TEXT)).toBeTruthy();
|
|
232
|
+
fireEvent.click(screen.getByRole("button", { name: "Turn off" }));
|
|
233
|
+
expect(setAutoApproveAll).toHaveBeenCalledExactlyOnceWith(false);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it("never renders for observers — they cannot arm or disarm anything", () => {
|
|
237
|
+
stubSessionPageFlow.autoApproveAll = true;
|
|
238
|
+
render(
|
|
239
|
+
<SessionViewer sessionId="ses_1" org="acme" audience="observer" />,
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
expect(screen.queryByText(INDICATOR_TEXT)).toBeNull();
|
|
243
|
+
});
|
|
244
|
+
});
|