@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
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// Scroll-on-send wiring pins for MessageThread (stigmer-cloud#267): the
|
|
2
|
+
// send moment is the optimistic message's empty→present transition, and it
|
|
3
|
+
// must pin the thread exactly once — default-ON, opt-out via
|
|
4
|
+
// `scrollOnSend={false}` (the ratified DD-011 divergence). The REAL scroll
|
|
5
|
+
// mechanics (pin + follow re-engagement under real layout) are pinned in
|
|
6
|
+
// `internal/__tests__/useAutoScroll.layout.test.tsx`; this file pins the
|
|
7
|
+
// surface's signal derivation through a spied `jumpToLatest`, with the real
|
|
8
|
+
// `usePinToLatestOnSignal` connecting them.
|
|
9
|
+
|
|
10
|
+
import { describe, it, expect, vi, afterEach } from "vitest";
|
|
11
|
+
import { render, cleanup } from "@testing-library/react";
|
|
12
|
+
import { create } from "@bufbuild/protobuf";
|
|
13
|
+
import {
|
|
14
|
+
AgentExecutionSchema,
|
|
15
|
+
type AgentExecution,
|
|
16
|
+
} from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
17
|
+
import { AgentExecutionSpecSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/spec_pb";
|
|
18
|
+
import { ApiResourceMetadataSchema } from "@stigmer/protos/ai/stigmer/commons/apiresource/metadata_pb";
|
|
19
|
+
|
|
20
|
+
const jumpToLatestSpy = vi.fn();
|
|
21
|
+
|
|
22
|
+
// The fake replaces only the scroll machine (browser observers happy-dom
|
|
23
|
+
// lacks); `usePinToLatestOnSignal` stays REAL, so these tests exercise the
|
|
24
|
+
// actual signal contract between MessageThread and its render paths.
|
|
25
|
+
vi.mock("../../internal/useAutoScroll", async (importOriginal) => {
|
|
26
|
+
const original =
|
|
27
|
+
await importOriginal<typeof import("../../internal/useAutoScroll")>();
|
|
28
|
+
return {
|
|
29
|
+
...original,
|
|
30
|
+
useAutoScroll: () => ({
|
|
31
|
+
scrollRef: { current: null },
|
|
32
|
+
sentinelRef: { current: null },
|
|
33
|
+
contentRef: () => {},
|
|
34
|
+
isFollowing: true,
|
|
35
|
+
jumpToLatest: jumpToLatestSpy,
|
|
36
|
+
}),
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
import { MessageThread } from "../MessageThread";
|
|
41
|
+
|
|
42
|
+
afterEach(() => {
|
|
43
|
+
cleanup();
|
|
44
|
+
jumpToLatestSpy.mockClear();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
function makeExecution(id: string, message: string): AgentExecution {
|
|
48
|
+
const exec = create(AgentExecutionSchema);
|
|
49
|
+
const meta = create(ApiResourceMetadataSchema);
|
|
50
|
+
meta.id = id;
|
|
51
|
+
exec.metadata = meta;
|
|
52
|
+
const spec = create(AgentExecutionSpecSchema);
|
|
53
|
+
spec.message = message;
|
|
54
|
+
exec.spec = spec;
|
|
55
|
+
return exec;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
describe("MessageThread — scroll-on-send (stigmer-cloud#267)", () => {
|
|
59
|
+
it("pins the thread exactly once when the optimistic message appears (default-on)", () => {
|
|
60
|
+
const executions = [makeExecution("aex_1", "earlier turn")];
|
|
61
|
+
const { rerender } = render(
|
|
62
|
+
<MessageThread executions={executions} pendingUserMessage={null} />,
|
|
63
|
+
);
|
|
64
|
+
expect(jumpToLatestSpy).not.toHaveBeenCalled();
|
|
65
|
+
|
|
66
|
+
// The send: the composer sets the optimistic message.
|
|
67
|
+
rerender(
|
|
68
|
+
<MessageThread executions={executions} pendingUserMessage="do the thing" />,
|
|
69
|
+
);
|
|
70
|
+
expect(jumpToLatestSpy).toHaveBeenCalledTimes(1);
|
|
71
|
+
|
|
72
|
+
// Re-renders while the message stays pending must not re-pin — the
|
|
73
|
+
// reader may have scrolled up again to read history mid-turn.
|
|
74
|
+
rerender(
|
|
75
|
+
<MessageThread executions={executions} pendingUserMessage="do the thing" />,
|
|
76
|
+
);
|
|
77
|
+
expect(jumpToLatestSpy).toHaveBeenCalledTimes(1);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("pins again on the NEXT send after the pending message clears", () => {
|
|
81
|
+
const executions = [makeExecution("aex_1", "earlier turn")];
|
|
82
|
+
const { rerender } = render(
|
|
83
|
+
<MessageThread executions={executions} pendingUserMessage={null} />,
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
rerender(
|
|
87
|
+
<MessageThread executions={executions} pendingUserMessage="first send" />,
|
|
88
|
+
);
|
|
89
|
+
rerender(<MessageThread executions={executions} pendingUserMessage={null} />);
|
|
90
|
+
rerender(
|
|
91
|
+
<MessageThread executions={executions} pendingUserMessage="second send" />,
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
expect(jumpToLatestSpy).toHaveBeenCalledTimes(2);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("does not re-pin on a failed send's retry — pending stays present throughout, and the reader was already brought to it", () => {
|
|
98
|
+
const executions = [makeExecution("aex_1", "earlier turn")];
|
|
99
|
+
const { rerender } = render(
|
|
100
|
+
<MessageThread executions={executions} pendingUserMessage={null} />,
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
rerender(
|
|
104
|
+
<MessageThread executions={executions} pendingUserMessage="flaky send" />,
|
|
105
|
+
);
|
|
106
|
+
rerender(
|
|
107
|
+
<MessageThread
|
|
108
|
+
executions={executions}
|
|
109
|
+
pendingUserMessage="flaky send"
|
|
110
|
+
pendingMessageFailed
|
|
111
|
+
onRetrySend={() => {}}
|
|
112
|
+
/>,
|
|
113
|
+
);
|
|
114
|
+
rerender(
|
|
115
|
+
<MessageThread executions={executions} pendingUserMessage="flaky send" />,
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
expect(jumpToLatestSpy).toHaveBeenCalledTimes(1);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("never pins with scrollOnSend={false} — the opt-out preserves today's leave-the-reader-alone behavior", () => {
|
|
122
|
+
const executions = [makeExecution("aex_1", "earlier turn")];
|
|
123
|
+
const { rerender } = render(
|
|
124
|
+
<MessageThread
|
|
125
|
+
executions={executions}
|
|
126
|
+
pendingUserMessage={null}
|
|
127
|
+
scrollOnSend={false}
|
|
128
|
+
/>,
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
rerender(
|
|
132
|
+
<MessageThread
|
|
133
|
+
executions={executions}
|
|
134
|
+
pendingUserMessage="do the thing"
|
|
135
|
+
scrollOnSend={false}
|
|
136
|
+
/>,
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
expect(jumpToLatestSpy).not.toHaveBeenCalled();
|
|
140
|
+
});
|
|
141
|
+
});
|
|
@@ -5,11 +5,13 @@ import { create } from "@bufbuild/protobuf";
|
|
|
5
5
|
import {
|
|
6
6
|
AgentExecutionSchema,
|
|
7
7
|
AgentExecutionStatusSchema,
|
|
8
|
+
RecalledMemoriesReportSchema,
|
|
8
9
|
type AgentExecution,
|
|
9
10
|
} from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
10
11
|
import {
|
|
11
12
|
AgentExecutionSpecSchema,
|
|
12
13
|
ExecutionConfigSchema,
|
|
14
|
+
RecalledMemoriesSchema,
|
|
13
15
|
} from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/spec_pb";
|
|
14
16
|
import { ApiResourceMetadataSchema } from "@stigmer/protos/ai/stigmer/commons/apiresource/metadata_pb";
|
|
15
17
|
import { AgentMessageSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
@@ -32,6 +34,7 @@ import type { ExecutionErrorNoticeProps } from "../ExecutionErrorNotice";
|
|
|
32
34
|
import type { TodoCardProps } from "../TodoCard";
|
|
33
35
|
import type { TodoRowProps } from "../TodoList";
|
|
34
36
|
import type { SetupProgressProps } from "../SetupProgress";
|
|
37
|
+
import type { RecalledMemoriesCardProps } from "../RecalledMemoriesCard";
|
|
35
38
|
import type { LivenessStatusLineProps } from "../LivenessStatusLine";
|
|
36
39
|
import type { PlanCompletionCardProps } from "../PlanCompletionCard";
|
|
37
40
|
import type { PlanArtifactCardProps } from "../PlanArtifactCard";
|
|
@@ -318,6 +321,39 @@ describe("MessageThread slots", () => {
|
|
|
318
321
|
expect(screen.getByTestId("custom-setup").textContent).toBe("waiting");
|
|
319
322
|
});
|
|
320
323
|
|
|
324
|
+
it("renders the RecalledMemoriesCard slot for a selection-active execution", () => {
|
|
325
|
+
const CustomRecalled = ({ report, facts }: RecalledMemoriesCardProps) => (
|
|
326
|
+
<div data-testid="custom-recalled">
|
|
327
|
+
{report.injectedMemoryIds.length} of {facts.length}
|
|
328
|
+
</div>
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
const exec = makeExecution({ id: "e1" });
|
|
332
|
+
exec.spec!.recalledMemories = create(RecalledMemoriesSchema, {
|
|
333
|
+
enabled: true,
|
|
334
|
+
facts: [
|
|
335
|
+
{ memoryId: "mem_a", content: "Prefers concise answers." },
|
|
336
|
+
{ memoryId: "mem_b", content: "Deploys with Bazel." },
|
|
337
|
+
],
|
|
338
|
+
});
|
|
339
|
+
exec.status!.recalledMemoriesReport = create(RecalledMemoriesReportSchema, {
|
|
340
|
+
selectionActive: true,
|
|
341
|
+
injectedMemoryIds: ["mem_a"],
|
|
342
|
+
embeddingModel: "text-embedding-3-small",
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
render(
|
|
346
|
+
<MessageThread
|
|
347
|
+
executions={[exec]}
|
|
348
|
+
slots={{ RecalledMemoriesCard: CustomRecalled }}
|
|
349
|
+
/>,
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
expect(screen.getByTestId("custom-recalled").textContent).toBe("1 of 2");
|
|
353
|
+
// The built-in card must not render alongside the override.
|
|
354
|
+
expect(screen.queryByRole("status", { name: /Recalled/ })).toBeNull();
|
|
355
|
+
});
|
|
356
|
+
|
|
321
357
|
it("renders the LivenessStatusLine slot while the execution is live between events", () => {
|
|
322
358
|
const CustomLiveness = ({ label }: LivenessStatusLineProps) => (
|
|
323
359
|
<div data-testid="custom-liveness">{label ?? "custom-live"}</div>
|
package/src/execution/index.ts
CHANGED
|
@@ -42,6 +42,8 @@ export type { SummarizationBadgeProps } from "./SummarizationBadge.js";
|
|
|
42
42
|
|
|
43
43
|
export { SummarizationCard } from "./SummarizationCard.js";
|
|
44
44
|
export type { SummarizationCardProps } from "./SummarizationCard.js";
|
|
45
|
+
export { RecalledMemoriesCard, resolveInjectedFacts } from "./RecalledMemoriesCard.js";
|
|
46
|
+
export type { RecalledMemoriesCardProps } from "./RecalledMemoriesCard.js";
|
|
45
47
|
|
|
46
48
|
export { PlanCompletionCard } from "./PlanCompletionCard.js";
|
|
47
49
|
export type { PlanCompletionCardProps } from "./PlanCompletionCard.js";
|
|
@@ -32,6 +32,7 @@ export type ToolCategory =
|
|
|
32
32
|
| "sub-agent"
|
|
33
33
|
| "internal"
|
|
34
34
|
| "mcp"
|
|
35
|
+
| "memory"
|
|
35
36
|
| "unknown";
|
|
36
37
|
|
|
37
38
|
/**
|
|
@@ -122,6 +123,11 @@ const PREVIEW_CATEGORIES: ReadonlySet<ToolCategory> = new Set<ToolCategory>([
|
|
|
122
123
|
"mcp",
|
|
123
124
|
"web-search",
|
|
124
125
|
"fetch",
|
|
126
|
+
// A memory proposal is a decision surface: the verbatim fact and the
|
|
127
|
+
// Confirm/Reject actions ARE the content — never fold it behind a chevron
|
|
128
|
+
// (DD-005 D4: a proposal the user scrolled past is caught by the memory
|
|
129
|
+
// page, but the chip must not hide itself while in view).
|
|
130
|
+
"memory",
|
|
125
131
|
"unknown",
|
|
126
132
|
]);
|
|
127
133
|
|
|
@@ -232,6 +238,7 @@ const KIND_DISPLAY: Partial<Record<ToolKind, KindDisplayEntry>> = {
|
|
|
232
238
|
[ToolKind.THINK]: { category: "think", label: "Thinking", primaryField: "thought" },
|
|
233
239
|
[ToolKind.SUBAGENT]: { category: "sub-agent", label: "Sub-agent", primaryField: "description", fallbackFields: ["prompt"] },
|
|
234
240
|
[ToolKind.TODO]: { category: "internal", label: "Todos", primaryField: "todos" },
|
|
241
|
+
[ToolKind.MEMORY]: { category: "memory", label: "Remember", primaryField: "fact" },
|
|
235
242
|
};
|
|
236
243
|
|
|
237
244
|
/**
|
|
@@ -18,8 +18,10 @@ import { useResourceAvailable, ApiResourceKind } from "../deployment-mode.js";
|
|
|
18
18
|
import { useModelRegistry } from "../models/index.js";
|
|
19
19
|
import { HARNESS_META, type HarnessOption } from "../models/harness.js";
|
|
20
20
|
import { CloudFeatureNotice } from "../internal/CloudFeatureNotice.js";
|
|
21
|
+
import { MemoryEnabledRow } from "../internal/MemoryEnabledRow.js";
|
|
21
22
|
import { StandingContextField } from "../internal/StandingContextField.js";
|
|
22
23
|
import { SpinnerIcon } from "../internal/SpinnerIcon.js";
|
|
24
|
+
import { Switch } from "../switch/Switch.js";
|
|
23
25
|
|
|
24
26
|
/** Props for {@link AccountPreferencesPanel}. */
|
|
25
27
|
export interface AccountPreferencesPanelProps {
|
|
@@ -32,7 +34,8 @@ export interface AccountPreferencesPanelProps {
|
|
|
32
34
|
/**
|
|
33
35
|
* Self-contained editor for the current user's declared preferences
|
|
34
36
|
* (`IdentityAccountSpec.preferences`): standing context plus the
|
|
35
|
-
* execution defaults (default harness
|
|
37
|
+
* execution defaults (default harness, per-harness default models, and
|
|
38
|
+
* the auto-approve default).
|
|
36
39
|
*
|
|
37
40
|
* The declared text is snapshotted into the user's own eligible agent
|
|
38
41
|
* executions and delivered to the agent as background context.
|
|
@@ -108,16 +111,26 @@ function AccountPreferencesForm({
|
|
|
108
111
|
clearError,
|
|
109
112
|
} = useUpdateIdentityAccount();
|
|
110
113
|
|
|
114
|
+
// The memory toggle saves instantly (its own hook instance, so a flip's
|
|
115
|
+
// in-flight/error state never bleeds into the form's Save button).
|
|
116
|
+
const {
|
|
117
|
+
update: updateMemoryFlag,
|
|
118
|
+
isUpdating: isSavingMemoryFlag,
|
|
119
|
+
error: memoryFlagError,
|
|
120
|
+
} = useUpdateIdentityAccount();
|
|
121
|
+
|
|
111
122
|
const [standingContext, setStandingContext] = useState("");
|
|
112
123
|
const [defaultHarness, setDefaultHarness] = useState("");
|
|
113
124
|
const [defaultNativeModel, setDefaultNativeModel] = useState("");
|
|
114
125
|
const [defaultCursorModel, setDefaultCursorModel] = useState("");
|
|
126
|
+
const [defaultAutoApprove, setDefaultAutoApprove] = useState(false);
|
|
115
127
|
|
|
116
128
|
const serverPreferences = account?.spec?.preferences;
|
|
117
129
|
const serverStandingContext = serverPreferences?.standingContext ?? "";
|
|
118
130
|
const serverDefaultHarness = serverPreferences?.defaultHarness ?? "";
|
|
119
131
|
const serverDefaultNativeModel = serverPreferences?.defaultNativeModel ?? "";
|
|
120
132
|
const serverDefaultCursorModel = serverPreferences?.defaultCursorModel ?? "";
|
|
133
|
+
const serverDefaultAutoApprove = serverPreferences?.defaultAutoApprove ?? false;
|
|
121
134
|
|
|
122
135
|
// Sync the form fields when server data changes.
|
|
123
136
|
useEffect(() => {
|
|
@@ -127,6 +140,7 @@ function AccountPreferencesForm({
|
|
|
127
140
|
setDefaultHarness(prefs?.defaultHarness ?? "");
|
|
128
141
|
setDefaultNativeModel(prefs?.defaultNativeModel ?? "");
|
|
129
142
|
setDefaultCursorModel(prefs?.defaultCursorModel ?? "");
|
|
143
|
+
setDefaultAutoApprove(prefs?.defaultAutoApprove ?? false);
|
|
130
144
|
}, [account]);
|
|
131
145
|
|
|
132
146
|
const hasChanges = useMemo(
|
|
@@ -134,16 +148,19 @@ function AccountPreferencesForm({
|
|
|
134
148
|
standingContext.trim() !== serverStandingContext ||
|
|
135
149
|
defaultHarness !== serverDefaultHarness ||
|
|
136
150
|
defaultNativeModel !== serverDefaultNativeModel ||
|
|
137
|
-
defaultCursorModel !== serverDefaultCursorModel
|
|
151
|
+
defaultCursorModel !== serverDefaultCursorModel ||
|
|
152
|
+
defaultAutoApprove !== serverDefaultAutoApprove,
|
|
138
153
|
[
|
|
139
154
|
standingContext,
|
|
140
155
|
defaultHarness,
|
|
141
156
|
defaultNativeModel,
|
|
142
157
|
defaultCursorModel,
|
|
158
|
+
defaultAutoApprove,
|
|
143
159
|
serverStandingContext,
|
|
144
160
|
serverDefaultHarness,
|
|
145
161
|
serverDefaultNativeModel,
|
|
146
162
|
serverDefaultCursorModel,
|
|
163
|
+
serverDefaultAutoApprove,
|
|
147
164
|
],
|
|
148
165
|
);
|
|
149
166
|
|
|
@@ -154,12 +171,14 @@ function AccountPreferencesForm({
|
|
|
154
171
|
setDefaultHarness(serverDefaultHarness);
|
|
155
172
|
setDefaultNativeModel(serverDefaultNativeModel);
|
|
156
173
|
setDefaultCursorModel(serverDefaultCursorModel);
|
|
174
|
+
setDefaultAutoApprove(serverDefaultAutoApprove);
|
|
157
175
|
clearError();
|
|
158
176
|
}, [
|
|
159
177
|
serverStandingContext,
|
|
160
178
|
serverDefaultHarness,
|
|
161
179
|
serverDefaultNativeModel,
|
|
162
180
|
serverDefaultCursorModel,
|
|
181
|
+
serverDefaultAutoApprove,
|
|
163
182
|
clearError,
|
|
164
183
|
]);
|
|
165
184
|
|
|
@@ -183,6 +202,7 @@ function AccountPreferencesForm({
|
|
|
183
202
|
defaultHarness: defaultHarness || undefined,
|
|
184
203
|
defaultNativeModel: defaultNativeModel || undefined,
|
|
185
204
|
defaultCursorModel: defaultCursorModel || undefined,
|
|
205
|
+
defaultAutoApprove: defaultAutoApprove || undefined,
|
|
186
206
|
},
|
|
187
207
|
});
|
|
188
208
|
refetch();
|
|
@@ -198,6 +218,7 @@ function AccountPreferencesForm({
|
|
|
198
218
|
defaultHarness,
|
|
199
219
|
defaultNativeModel,
|
|
200
220
|
defaultCursorModel,
|
|
221
|
+
defaultAutoApprove,
|
|
201
222
|
update,
|
|
202
223
|
clearError,
|
|
203
224
|
refetch,
|
|
@@ -205,6 +226,30 @@ function AccountPreferencesForm({
|
|
|
205
226
|
],
|
|
206
227
|
);
|
|
207
228
|
|
|
229
|
+
// The memory consent flag applies instantly (the UX-checkpoint decision):
|
|
230
|
+
// a consent bit flipped-but-unsaved that silently reverts on navigation is
|
|
231
|
+
// the failure consent UX must not have. Same wipe-safe double spread.
|
|
232
|
+
const handleMemoryToggle = useCallback(
|
|
233
|
+
async (next: boolean) => {
|
|
234
|
+
if (!account) return;
|
|
235
|
+
try {
|
|
236
|
+
const mapped = toIdentityAccountUpdateInput(account);
|
|
237
|
+
const updated = await updateMemoryFlag({
|
|
238
|
+
...mapped,
|
|
239
|
+
preferences: {
|
|
240
|
+
...mapped.preferences,
|
|
241
|
+
memoryEnabled: next || undefined,
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
refetch();
|
|
245
|
+
onUpdated?.(updated);
|
|
246
|
+
} catch {
|
|
247
|
+
// error state is managed by the toggle's own hook instance
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
[account, updateMemoryFlag, refetch, onUpdated],
|
|
251
|
+
);
|
|
252
|
+
|
|
208
253
|
// -----------------------------------------------------------------------
|
|
209
254
|
// Loading state
|
|
210
255
|
// -----------------------------------------------------------------------
|
|
@@ -330,8 +375,50 @@ function AccountPreferencesForm({
|
|
|
330
375
|
on that harness — even when it isn't your default.
|
|
331
376
|
</p>
|
|
332
377
|
</fieldset>
|
|
378
|
+
|
|
379
|
+
{/* Auto-approve default (stigmer/stigmer#816): a client-read seed
|
|
380
|
+
like the harness/model defaults above — the session panel's
|
|
381
|
+
Config switch beats it per conversation, and armed sessions
|
|
382
|
+
always show the "Auto-approving tool calls" strip by the
|
|
383
|
+
composer. Never applied to guest or observer surfaces. */}
|
|
384
|
+
<div
|
|
385
|
+
className={cn(
|
|
386
|
+
"stg:flex stg:items-start stg:justify-between stg:gap-3 stg:rounded-md stg:border stg:border-input stg:px-3 stg:py-2.5",
|
|
387
|
+
isUpdating && "stg:opacity-50",
|
|
388
|
+
)}
|
|
389
|
+
>
|
|
390
|
+
<div className="stg:min-w-0">
|
|
391
|
+
<span
|
|
392
|
+
id={`${baseId}-default-auto-approve-title`}
|
|
393
|
+
className="stg:block stg:text-xs stg:font-medium stg:text-foreground"
|
|
394
|
+
>
|
|
395
|
+
Auto-approve tool calls
|
|
396
|
+
</span>
|
|
397
|
+
<p className="stg:text-[0.65rem] stg:text-muted-foreground">
|
|
398
|
+
Start your sessions with tool calls running unattended — no
|
|
399
|
+
approval prompts. Each conversation can turn this off (or on)
|
|
400
|
+
in its session panel.
|
|
401
|
+
</p>
|
|
402
|
+
</div>
|
|
403
|
+
<Switch
|
|
404
|
+
checked={defaultAutoApprove}
|
|
405
|
+
onCheckedChange={setDefaultAutoApprove}
|
|
406
|
+
disabled={isUpdating}
|
|
407
|
+
aria-labelledby={`${baseId}-default-auto-approve-title`}
|
|
408
|
+
className="stg:mt-0.5"
|
|
409
|
+
/>
|
|
410
|
+
</div>
|
|
333
411
|
</div>
|
|
334
412
|
|
|
413
|
+
<MemoryEnabledRow
|
|
414
|
+
id={`${baseId}-memory-enabled`}
|
|
415
|
+
checked={account.spec?.preferences?.memoryEnabled ?? false}
|
|
416
|
+
onToggle={(next) => void handleMemoryToggle(next)}
|
|
417
|
+
saving={isSavingMemoryFlag}
|
|
418
|
+
error={memoryFlagError}
|
|
419
|
+
helperText="When on, agents may propose facts to remember about you; only facts you confirm are stored. Confirmed memories are shared with your future sessions and appear on those executions' records; once you have many, each conversation recalls the most relevant ones, shown on the execution. Requires your organization to have memory enabled. Changes apply immediately."
|
|
420
|
+
/>
|
|
421
|
+
|
|
335
422
|
{updateError && (
|
|
336
423
|
<p className="stg:text-destructive stg:text-[0.65rem]" role="alert">
|
|
337
424
|
{getUserMessage(updateError)}
|
|
@@ -115,6 +115,52 @@ describe("AccountPreferencesPanel", () => {
|
|
|
115
115
|
expect(input.slug).toBe("ada");
|
|
116
116
|
});
|
|
117
117
|
|
|
118
|
+
it("saves the auto-approve default when the toggle is flipped on", async () => {
|
|
119
|
+
const update = vi.fn(async (_input: IdentityAccountInput) => ACCOUNT);
|
|
120
|
+
renderPanel(createMockStigmer({ update }));
|
|
121
|
+
|
|
122
|
+
await findSyncedField("Keep answers terse.");
|
|
123
|
+
const toggle = screen.getByRole("switch", { name: "Auto-approve tool calls" });
|
|
124
|
+
expect(toggle.getAttribute("aria-checked")).toBe("false");
|
|
125
|
+
|
|
126
|
+
fireEvent.click(toggle);
|
|
127
|
+
fireEvent.click(screen.getByRole("button", { name: "Save changes" }));
|
|
128
|
+
|
|
129
|
+
await waitFor(() => expect(update).toHaveBeenCalledTimes(1));
|
|
130
|
+
const input = update.mock.calls[0]![0];
|
|
131
|
+
expect(input.preferences?.defaultAutoApprove).toBe(true);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("reflects a declared auto-approve default and omits it when turned off", async () => {
|
|
135
|
+
const armedAccount = create(IdentityAccountSchema, {
|
|
136
|
+
metadata: { id: "ia-1", name: "Ada Lovelace", slug: "ada", org: "acme" },
|
|
137
|
+
spec: {
|
|
138
|
+
idpId: "auth0|abc",
|
|
139
|
+
email: "ada@acme.example",
|
|
140
|
+
preferences: { defaultAutoApprove: true },
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
const update = vi.fn(async (_input: IdentityAccountInput) => armedAccount);
|
|
144
|
+
renderPanel(
|
|
145
|
+
createMockStigmer({ whoAmI: vi.fn(async () => armedAccount), update }),
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
const toggle = await screen.findByRole("switch", {
|
|
149
|
+
name: "Auto-approve tool calls",
|
|
150
|
+
});
|
|
151
|
+
await waitFor(() =>
|
|
152
|
+
expect(toggle.getAttribute("aria-checked")).toBe("true"),
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
fireEvent.click(toggle);
|
|
156
|
+
fireEvent.click(screen.getByRole("button", { name: "Save changes" }));
|
|
157
|
+
|
|
158
|
+
await waitFor(() => expect(update).toHaveBeenCalledTimes(1));
|
|
159
|
+
// Only a declared true travels — off is the absent zero value.
|
|
160
|
+
const input = update.mock.calls[0]![0];
|
|
161
|
+
expect(input.preferences?.defaultAutoApprove).toBeUndefined();
|
|
162
|
+
});
|
|
163
|
+
|
|
118
164
|
it("refetches the account after a successful save", async () => {
|
|
119
165
|
const whoAmI = vi.fn(async () => ACCOUNT);
|
|
120
166
|
renderPanel(createMockStigmer({ whoAmI }));
|
|
@@ -137,6 +183,72 @@ describe("AccountPreferencesPanel", () => {
|
|
|
137
183
|
expect(screen.getByRole("button", { name: "Retry" })).toBeTruthy();
|
|
138
184
|
});
|
|
139
185
|
|
|
186
|
+
describe("memory consent (oss#293 Phase 2 Stage 3)", () => {
|
|
187
|
+
it("memory toggle applies instantly with the full mapped input (wipe-safe)", async () => {
|
|
188
|
+
const update = vi.fn(async (_input: IdentityAccountInput) => ACCOUNT);
|
|
189
|
+
renderPanel(createMockStigmer({ update }));
|
|
190
|
+
await findSyncedField("Keep answers terse.");
|
|
191
|
+
|
|
192
|
+
// No Save click: consent applies the moment it is flipped (the
|
|
193
|
+
// UX-checkpoint decision).
|
|
194
|
+
fireEvent.click(screen.getByRole("switch", { name: "Memory" }));
|
|
195
|
+
|
|
196
|
+
await waitFor(() => expect(update).toHaveBeenCalledTimes(1));
|
|
197
|
+
const input = update.mock.calls[0]![0];
|
|
198
|
+
expect(input.preferences?.memoryEnabled).toBe(true);
|
|
199
|
+
// The double spread: the flip preserves every sibling preference…
|
|
200
|
+
expect(input.preferences?.standingContext).toBe("Keep answers terse.");
|
|
201
|
+
// …and the identity fields the row never renders.
|
|
202
|
+
expect(input.id).toBe("ia-1");
|
|
203
|
+
expect(input.email).toBe("ada@acme.example");
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("saving the form preserves memory_enabled (the wipe-hazard regression)", async () => {
|
|
207
|
+
const accountWithMemoryOn = create(IdentityAccountSchema, {
|
|
208
|
+
metadata: { id: "ia-1", name: "Ada Lovelace", slug: "ada", org: "acme" },
|
|
209
|
+
spec: {
|
|
210
|
+
idpId: "auth0|abc",
|
|
211
|
+
email: "ada@acme.example",
|
|
212
|
+
preferences: {
|
|
213
|
+
standingContext: "Keep answers terse.",
|
|
214
|
+
memoryEnabled: true,
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
const update = vi.fn(async (_input: IdentityAccountInput) => accountWithMemoryOn);
|
|
219
|
+
renderPanel(
|
|
220
|
+
createMockStigmer({ whoAmI: vi.fn(async () => accountWithMemoryOn), update }),
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
const field = await findSyncedField("Keep answers terse.");
|
|
224
|
+
fireEvent.change(field, { target: { value: "Terser still." } });
|
|
225
|
+
fireEvent.click(screen.getByRole("button", { name: "Save changes" }));
|
|
226
|
+
|
|
227
|
+
await waitFor(() => expect(update).toHaveBeenCalledTimes(1));
|
|
228
|
+
// Update is a full-spec replace: without the nested preferences
|
|
229
|
+
// spread, this save would silently revoke the memory consent.
|
|
230
|
+
expect(update.mock.calls[0]![0].preferences?.memoryEnabled).toBe(true);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("reflects the stored flag as the switch state", async () => {
|
|
234
|
+
const accountWithMemoryOn = create(IdentityAccountSchema, {
|
|
235
|
+
metadata: { id: "ia-1", name: "Ada", slug: "ada", org: "acme" },
|
|
236
|
+
spec: {
|
|
237
|
+
idpId: "auth0|abc",
|
|
238
|
+
preferences: { memoryEnabled: true },
|
|
239
|
+
},
|
|
240
|
+
});
|
|
241
|
+
renderPanel(createMockStigmer({ whoAmI: vi.fn(async () => accountWithMemoryOn) }));
|
|
242
|
+
await findSyncedField("");
|
|
243
|
+
|
|
244
|
+
await waitFor(() =>
|
|
245
|
+
expect(
|
|
246
|
+
screen.getByRole("switch", { name: "Memory" }).getAttribute("aria-checked"),
|
|
247
|
+
).toBe("true"),
|
|
248
|
+
);
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
140
252
|
describe("execution defaults (oss#293 Phase 1.5)", () => {
|
|
141
253
|
const ACCOUNT_WITH_DEFAULTS: IdentityAccount = create(IdentityAccountSchema, {
|
|
142
254
|
metadata: { id: "ia-1", name: "Ada Lovelace", slug: "ada", org: "acme" },
|
|
@@ -12,7 +12,7 @@ import { DeploymentModeContext } from "../../deployment-mode";
|
|
|
12
12
|
import { useAccountExecutionDefaults } from "../useAccountExecutionDefaults";
|
|
13
13
|
|
|
14
14
|
function accountWithPreferences(
|
|
15
|
-
preferences: Record<string, string>,
|
|
15
|
+
preferences: Record<string, string | boolean>,
|
|
16
16
|
): IdentityAccount {
|
|
17
17
|
return create(IdentityAccountSchema, {
|
|
18
18
|
metadata: { id: "ia-1", name: "Ada", slug: "ada", org: "acme" },
|
|
@@ -102,6 +102,39 @@ describe("useAccountExecutionDefaults", () => {
|
|
|
102
102
|
);
|
|
103
103
|
});
|
|
104
104
|
|
|
105
|
+
it("a declared auto-approve default alone counts as a declared default", async () => {
|
|
106
|
+
const whoAmI = vi.fn(async () =>
|
|
107
|
+
accountWithPreferences({ defaultAutoApprove: true }),
|
|
108
|
+
);
|
|
109
|
+
const { result } = renderHook(() => useAccountExecutionDefaults(), {
|
|
110
|
+
wrapper: createWrapper(whoAmI),
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
await waitFor(() =>
|
|
114
|
+
expect(result.current).toEqual({
|
|
115
|
+
harness: undefined,
|
|
116
|
+
nativeModel: undefined,
|
|
117
|
+
cursorModel: undefined,
|
|
118
|
+
autoApprove: true,
|
|
119
|
+
}),
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("an unset auto-approve bool is no preference, not an explicit false", async () => {
|
|
124
|
+
const whoAmI = vi.fn(async () =>
|
|
125
|
+
accountWithPreferences({
|
|
126
|
+
defaultHarness: "native",
|
|
127
|
+
defaultAutoApprove: false,
|
|
128
|
+
}),
|
|
129
|
+
);
|
|
130
|
+
const { result } = renderHook(() => useAccountExecutionDefaults(), {
|
|
131
|
+
wrapper: createWrapper(whoAmI),
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
await waitFor(() => expect(result.current?.harness).toBe("native"));
|
|
135
|
+
expect(result.current?.autoApprove).toBeUndefined();
|
|
136
|
+
});
|
|
137
|
+
|
|
105
138
|
it("returns undefined on a whoAmI failure — degrades to platform defaults", async () => {
|
|
106
139
|
const whoAmI = vi.fn(async () => {
|
|
107
140
|
throw new Error("boom");
|
|
@@ -16,6 +16,13 @@ export interface AccountExecutionDefaults {
|
|
|
16
16
|
readonly nativeModel?: string;
|
|
17
17
|
/** Preferred model for cursor-harness sessions, when declared. */
|
|
18
18
|
readonly cursorModel?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Start sessions with "auto-approve tool calls" armed
|
|
21
|
+
* (`default_auto_approve`, stigmer/stigmer#816). A seed like every other
|
|
22
|
+
* field here: an explicit in-session flip beats it for that conversation,
|
|
23
|
+
* and it is never applied to guest/observer surfaces.
|
|
24
|
+
*/
|
|
25
|
+
readonly autoApprove?: boolean;
|
|
19
26
|
}
|
|
20
27
|
|
|
21
28
|
/**
|
|
@@ -56,8 +63,11 @@ export function useAccountExecutionDefaults(): AccountExecutionDefaults | undefi
|
|
|
56
63
|
: undefined;
|
|
57
64
|
const nativeModel = prefs.defaultNativeModel || undefined;
|
|
58
65
|
const cursorModel = prefs.defaultCursorModel || undefined;
|
|
66
|
+
// Only a declared `true` travels — an unset bool is "no preference",
|
|
67
|
+
// matching the empty-string convention of the fields above.
|
|
68
|
+
const autoApprove = prefs.defaultAutoApprove || undefined;
|
|
59
69
|
|
|
60
|
-
if (!harness && !nativeModel && !cursorModel) return undefined;
|
|
61
|
-
return { harness, nativeModel, cursorModel };
|
|
70
|
+
if (!harness && !nativeModel && !cursorModel && !autoApprove) return undefined;
|
|
71
|
+
return { harness, nativeModel, cursorModel, autoApprove };
|
|
62
72
|
}, [account]);
|
|
63
73
|
}
|