@stigmer/react 3.12.5 → 3.12.6
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/agent/AgentDetailView.js +2 -2
- package/agent/AgentDetailView.js.map +1 -1
- package/agent/index.d.ts +0 -1
- package/agent/index.d.ts.map +1 -1
- package/agent/index.js +0 -1
- package/agent/index.js.map +1 -1
- package/agent/useUpdateAgent.d.ts +3 -3
- package/agent/useUpdateAgent.js +3 -3
- package/agent-instance/AgentInstanceDetailPanel.d.ts.map +1 -1
- package/agent-instance/AgentInstanceDetailPanel.js +5 -7
- package/agent-instance/AgentInstanceDetailPanel.js.map +1 -1
- package/agent-instance/useUpdateAgentInstance.d.ts +1 -1
- package/agent-instance/useUpdateAgentInstance.js +1 -1
- package/channel/AgentChannelsPanel.d.ts.map +1 -1
- package/channel/AgentChannelsPanel.js +10 -5
- package/channel/AgentChannelsPanel.js.map +1 -1
- package/channel/ChannelRunConfigDialog.d.ts +51 -0
- package/channel/ChannelRunConfigDialog.d.ts.map +1 -0
- package/channel/ChannelRunConfigDialog.js +121 -0
- package/channel/ChannelRunConfigDialog.js.map +1 -0
- package/channel/index.d.ts +2 -0
- package/channel/index.d.ts.map +1 -1
- package/channel/index.js +1 -0
- package/channel/index.js.map +1 -1
- package/channel/useSaveAgentChannel.d.ts.map +1 -1
- package/channel/useSaveAgentChannel.js +6 -3
- package/channel/useSaveAgentChannel.js.map +1 -1
- package/channel-app/ChannelAppDetailPanel.js +9 -9
- package/channel-app/ChannelAppDetailPanel.js.map +1 -1
- package/composer/ComposerToolbar.d.ts +6 -1
- package/composer/ComposerToolbar.d.ts.map +1 -1
- package/composer/ComposerToolbar.js +2 -2
- package/composer/ComposerToolbar.js.map +1 -1
- package/composer/SessionComposer.d.ts +9 -0
- package/composer/SessionComposer.d.ts.map +1 -1
- package/composer/SessionComposer.js +22 -3
- package/composer/SessionComposer.js.map +1 -1
- package/cursor-accounts/CursorAccountsConsole.d.ts.map +1 -1
- package/cursor-accounts/CursorAccountsConsole.js +12 -3
- package/cursor-accounts/CursorAccountsConsole.js.map +1 -1
- package/cursor-accounts/cursor-account-coverage.d.ts +30 -1
- package/cursor-accounts/cursor-account-coverage.d.ts.map +1 -1
- package/cursor-accounts/cursor-account-coverage.js +21 -0
- package/cursor-accounts/cursor-account-coverage.js.map +1 -1
- package/execution/useCreateAgentExecution.d.ts +15 -0
- package/execution/useCreateAgentExecution.d.ts.map +1 -1
- package/execution/useCreateAgentExecution.js +5 -0
- package/execution/useCreateAgentExecution.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 +33 -18
- package/identity-account/AccountPreferencesPanel.js.map +1 -1
- package/identity-provider/IdentityProviderDetailPanel.d.ts.map +1 -1
- package/identity-provider/IdentityProviderDetailPanel.js +16 -15
- package/identity-provider/IdentityProviderDetailPanel.js.map +1 -1
- package/index.d.ts +4 -4
- package/index.d.ts.map +1 -1
- package/index.js +3 -3
- package/index.js.map +1 -1
- package/mcp-server/McpServerDetailView.js +2 -2
- package/mcp-server/McpServerDetailView.js.map +1 -1
- package/mcp-server/index.d.ts +0 -1
- package/mcp-server/index.d.ts.map +1 -1
- package/mcp-server/index.js +0 -1
- package/mcp-server/index.js.map +1 -1
- package/mcp-server/internal/definitionDrift.d.ts +14 -12
- package/mcp-server/internal/definitionDrift.d.ts.map +1 -1
- package/mcp-server/internal/definitionDrift.js +22 -19
- package/mcp-server/internal/definitionDrift.js.map +1 -1
- package/mcp-server/useUpdateMcpServer.d.ts +3 -3
- package/mcp-server/useUpdateMcpServer.js +3 -3
- package/models/ModelSelector.d.ts +33 -1
- package/models/ModelSelector.d.ts.map +1 -1
- package/models/ModelSelector.js +37 -4
- package/models/ModelSelector.js.map +1 -1
- package/models/index.d.ts +2 -0
- package/models/index.d.ts.map +1 -1
- package/models/index.js +1 -0
- package/models/index.js.map +1 -1
- package/models/registry.d.ts +16 -0
- package/models/registry.d.ts.map +1 -1
- package/models/registry.js +13 -0
- package/models/registry.js.map +1 -1
- package/models/thinking-mode.d.ts +27 -0
- package/models/thinking-mode.d.ts.map +1 -0
- package/models/thinking-mode.js +32 -0
- package/models/thinking-mode.js.map +1 -0
- package/oauth-app/OAuthAppDetailPanel.d.ts.map +1 -1
- package/oauth-app/OAuthAppDetailPanel.js +21 -20
- package/oauth-app/OAuthAppDetailPanel.js.map +1 -1
- package/package.json +4 -4
- package/platform-client/PlatformClientDetailPanel.d.ts.map +1 -1
- package/platform-client/PlatformClientDetailPanel.js +12 -13
- package/platform-client/PlatformClientDetailPanel.js.map +1 -1
- package/schedule/ScheduleDetailView.d.ts.map +1 -1
- package/schedule/ScheduleDetailView.js +21 -10
- package/schedule/ScheduleDetailView.js.map +1 -1
- package/schedule/ScheduleForm.d.ts.map +1 -1
- package/schedule/ScheduleForm.js +13 -3
- package/schedule/ScheduleForm.js.map +1 -1
- package/session/run-config.d.ts +9 -0
- package/session/run-config.d.ts.map +1 -1
- package/session/run-config.js +5 -0
- package/session/run-config.js.map +1 -1
- package/session/useNewSessionFlow.d.ts.map +1 -1
- package/session/useNewSessionFlow.js +6 -0
- package/session/useNewSessionFlow.js.map +1 -1
- package/session/useSessionConversation.d.ts +12 -1
- package/session/useSessionConversation.d.ts.map +1 -1
- package/session/useSessionConversation.js +15 -24
- package/session/useSessionConversation.js.map +1 -1
- package/session/useSessionPageFlow.d.ts.map +1 -1
- package/session/useSessionPageFlow.js +14 -10
- package/session/useSessionPageFlow.js.map +1 -1
- package/session/useUpdateSession.d.ts +18 -24
- package/session/useUpdateSession.d.ts.map +1 -1
- package/session/useUpdateSession.js +18 -24
- package/session/useUpdateSession.js.map +1 -1
- package/src/agent/AgentDetailView.tsx +2 -2
- package/src/agent/index.ts +0 -2
- package/src/agent/useUpdateAgent.ts +3 -3
- package/src/agent-instance/AgentInstanceDetailPanel.tsx +5 -7
- package/src/agent-instance/__tests__/AgentInstanceDetailPanel.test.tsx +75 -0
- package/src/agent-instance/useUpdateAgentInstance.ts +1 -1
- package/src/channel/AgentChannelsPanel.tsx +26 -1
- package/src/channel/ChannelRunConfigDialog.tsx +351 -0
- package/src/channel/__tests__/ChannelRunConfigDialog.test.tsx +210 -0
- package/src/channel/index.ts +2 -0
- package/src/channel/useSaveAgentChannel.ts +6 -3
- package/src/channel-app/ChannelAppDetailPanel.tsx +9 -9
- package/src/channel-app/__tests__/channelAppWhatsApp.test.tsx +2 -0
- package/src/channel-app/__tests__/channelAppWipeSafety.test.tsx +87 -0
- package/src/composer/ComposerToolbar.tsx +9 -0
- package/src/composer/SessionComposer.tsx +36 -2
- package/src/composer/__tests__/SessionComposer-thinkingMode.test.tsx +176 -0
- package/src/cursor-accounts/CursorAccountsConsole.tsx +40 -5
- package/src/cursor-accounts/__tests__/CursorAccountsConsole.test.tsx +32 -0
- package/src/cursor-accounts/__tests__/cursor-account-coverage.test.ts +58 -1
- package/src/cursor-accounts/cursor-account-coverage.ts +55 -0
- package/src/execution/useCreateAgentExecution.ts +19 -0
- package/src/identity-account/AccountPreferencesPanel.tsx +154 -68
- package/src/identity-account/__tests__/AccountPreferencesPanel.test.tsx +36 -2
- package/src/identity-provider/IdentityProviderDetailPanel.tsx +18 -15
- package/src/identity-provider/__tests__/IdentityProviderDetailPanel.test.tsx +80 -0
- package/src/index.ts +2 -2
- package/src/mcp-server/McpServerDetailView.tsx +2 -2
- package/src/mcp-server/index.ts +0 -2
- package/src/mcp-server/internal/definitionDrift.ts +22 -20
- package/src/mcp-server/useUpdateMcpServer.ts +3 -3
- package/src/models/ModelSelector.tsx +94 -1
- package/src/models/index.ts +2 -0
- package/src/models/registry.ts +29 -0
- package/src/models/thinking-mode.ts +48 -0
- package/src/oauth-app/OAuthAppDetailPanel.tsx +23 -21
- package/src/oauth-app/__tests__/OAuthAppDetailPanel.test.tsx +94 -0
- package/src/platform-client/PlatformClientDetailPanel.tsx +14 -13
- package/src/platform-client/__tests__/PlatformClientDetailPanel.test.tsx +82 -0
- package/src/schedule/ScheduleDetailView.tsx +29 -4
- package/src/schedule/ScheduleForm.tsx +14 -1
- package/src/session/run-config.ts +16 -0
- package/src/session/useNewSessionFlow.ts +6 -0
- package/src/session/useSessionConversation.ts +32 -40
- package/src/session/useSessionPageFlow.ts +14 -11
- package/src/session/useUpdateSession.ts +18 -24
- package/src/workflow/ApprovalFormBuilder.tsx +20 -5
- package/src/workflow/WorkflowDetailView.tsx +2 -2
- package/src/workflow/__tests__/WorkflowInstanceDetailPanel.test.tsx +83 -0
- package/src/workflow/index.ts +0 -2
- package/src/workflow/instance/WorkflowInstanceDetailPanel.tsx +6 -7
- package/src/workflow/useUpdateWorkflow.ts +3 -3
- package/styles.css +1 -1
- package/workflow/ApprovalFormBuilder.d.ts.map +1 -1
- package/workflow/ApprovalFormBuilder.js +10 -8
- package/workflow/ApprovalFormBuilder.js.map +1 -1
- package/workflow/WorkflowDetailView.js +2 -2
- package/workflow/WorkflowDetailView.js.map +1 -1
- package/workflow/index.d.ts +0 -1
- package/workflow/index.d.ts.map +1 -1
- package/workflow/index.js +0 -1
- package/workflow/index.js.map +1 -1
- package/workflow/instance/WorkflowInstanceDetailPanel.d.ts.map +1 -1
- package/workflow/instance/WorkflowInstanceDetailPanel.js +6 -7
- package/workflow/instance/WorkflowInstanceDetailPanel.js.map +1 -1
- package/workflow/useUpdateWorkflow.d.ts +3 -3
- package/workflow/useUpdateWorkflow.js +3 -3
- package/agent/internal/agentToInput.d.ts +0 -15
- package/agent/internal/agentToInput.d.ts.map +0 -1
- package/agent/internal/agentToInput.js +0 -84
- package/agent/internal/agentToInput.js.map +0 -1
- package/mcp-server/internal/mcpServerToInput.d.ts +0 -12
- package/mcp-server/internal/mcpServerToInput.d.ts.map +0 -1
- package/mcp-server/internal/mcpServerToInput.js +0 -84
- package/mcp-server/internal/mcpServerToInput.js.map +0 -1
- package/session/session-spec-converters.d.ts +0 -24
- package/session/session-spec-converters.d.ts.map +0 -1
- package/session/session-spec-converters.js +0 -72
- package/session/session-spec-converters.js.map +0 -1
- package/src/agent/internal/__tests__/agentToInput.test.ts +0 -145
- package/src/agent/internal/agentToInput.ts +0 -102
- package/src/mcp-server/__tests__/mcpServerToInput.test.ts +0 -201
- package/src/mcp-server/internal/mcpServerToInput.ts +0 -98
- package/src/session/__tests__/session-spec-converters.test.ts +0 -183
- package/src/session/session-spec-converters.ts +0 -86
- package/src/workflow/internal/workflowToInput.ts +0 -96
- package/workflow/internal/workflowToInput.d.ts +0 -12
- package/workflow/internal/workflowToInput.d.ts.map +0 -1
- package/workflow/internal/workflowToInput.js +0 -73
- package/workflow/internal/workflowToInput.js.map +0 -1
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeAll, afterEach } from "vitest";
|
|
2
|
+
import { render, screen, fireEvent, waitFor, cleanup } from "@testing-library/react";
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
import { ServiceTier, ThinkingMode } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
5
|
+
import type { AgentChannelInput } from "@stigmer/sdk";
|
|
6
|
+
import { StigmerContext } from "../../context";
|
|
7
|
+
import { ModelRegistryContext } from "../../models/ModelRegistryContext";
|
|
8
|
+
import type { ModelInfo } from "../../models/registry";
|
|
9
|
+
import { ChannelRunConfigDialog } from "../ChannelRunConfigDialog";
|
|
10
|
+
|
|
11
|
+
// happy-dom does not implement the native dialog show/close methods.
|
|
12
|
+
beforeAll(() => {
|
|
13
|
+
HTMLDialogElement.prototype.showModal = function showModal() {
|
|
14
|
+
this.open = true;
|
|
15
|
+
};
|
|
16
|
+
HTMLDialogElement.prototype.close = function close() {
|
|
17
|
+
this.open = false;
|
|
18
|
+
};
|
|
19
|
+
if (!("ResizeObserver" in globalThis)) {
|
|
20
|
+
(globalThis as unknown as { ResizeObserver: unknown }).ResizeObserver =
|
|
21
|
+
class {
|
|
22
|
+
observe() {}
|
|
23
|
+
unobserve() {}
|
|
24
|
+
disconnect() {}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
afterEach(cleanup);
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The console's first run_config editor (stigmer/stigmer#792). The pinned
|
|
33
|
+
* behaviors are the edit-surface contracts the rest of the platform relies
|
|
34
|
+
* on: `max_tool_rounds` (the operator knob this editor never renders)
|
|
35
|
+
* survives every save verbatim, an all-empty draft clears the block
|
|
36
|
+
* ("empty = inherit"), and untouched variant switches stay off the wire
|
|
37
|
+
* (the #357/#772 unspecified-vs-explicit ledger distinction).
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
const MODELS: ModelInfo[] = [
|
|
41
|
+
{
|
|
42
|
+
modelId: "claude-haiku-4-5",
|
|
43
|
+
provider: "anthropic",
|
|
44
|
+
displayName: "Haiku 4.5",
|
|
45
|
+
shortDescription: "Fast Claude with optional thinking",
|
|
46
|
+
speedTier: "fast",
|
|
47
|
+
costTier: "economy",
|
|
48
|
+
harness: "cursor",
|
|
49
|
+
featured: true,
|
|
50
|
+
serviceTiers: [],
|
|
51
|
+
thinkingCapable: true,
|
|
52
|
+
},
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
function makeChannel(runConfig?: {
|
|
56
|
+
modelName?: string;
|
|
57
|
+
maxCostUsd?: number;
|
|
58
|
+
maxToolRounds?: number;
|
|
59
|
+
serviceTier?: ServiceTier;
|
|
60
|
+
thinkingMode?: ThinkingMode;
|
|
61
|
+
}) {
|
|
62
|
+
return {
|
|
63
|
+
metadata: {
|
|
64
|
+
id: "ach_1",
|
|
65
|
+
name: "Support Slack",
|
|
66
|
+
slug: "support-slack",
|
|
67
|
+
org: "acme",
|
|
68
|
+
labels: {},
|
|
69
|
+
},
|
|
70
|
+
spec: {
|
|
71
|
+
agentRef: { org: "acme", slug: "support-agent" },
|
|
72
|
+
enabled: true,
|
|
73
|
+
providerConfig: { case: "slack", value: {} },
|
|
74
|
+
environmentRefs: [],
|
|
75
|
+
...(runConfig
|
|
76
|
+
? {
|
|
77
|
+
runConfig: {
|
|
78
|
+
modelName: runConfig.modelName ?? "",
|
|
79
|
+
maxCostUsd: runConfig.maxCostUsd ?? 0,
|
|
80
|
+
maxToolRounds: runConfig.maxToolRounds ?? 0,
|
|
81
|
+
serviceTier: runConfig.serviceTier ?? ServiceTier.UNSPECIFIED,
|
|
82
|
+
thinkingMode: runConfig.thinkingMode ?? ThinkingMode.UNSPECIFIED,
|
|
83
|
+
},
|
|
84
|
+
}
|
|
85
|
+
: {}),
|
|
86
|
+
},
|
|
87
|
+
status: { installState: 2 },
|
|
88
|
+
} as never;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function createMockStigmer() {
|
|
92
|
+
const apply = vi.fn().mockResolvedValue({});
|
|
93
|
+
const client = { agentChannel: { apply } } as never;
|
|
94
|
+
return { client, apply };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function Providers({
|
|
98
|
+
client,
|
|
99
|
+
children,
|
|
100
|
+
}: {
|
|
101
|
+
client: unknown;
|
|
102
|
+
children: ReactNode;
|
|
103
|
+
}) {
|
|
104
|
+
return (
|
|
105
|
+
<StigmerContext.Provider value={client as never}>
|
|
106
|
+
<ModelRegistryContext.Provider
|
|
107
|
+
value={{ models: MODELS, isLoading: false, error: null, refetch: vi.fn() }}
|
|
108
|
+
>
|
|
109
|
+
{children}
|
|
110
|
+
</ModelRegistryContext.Provider>
|
|
111
|
+
</StigmerContext.Provider>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function renderDialog(channel: unknown) {
|
|
116
|
+
const { client, apply } = createMockStigmer();
|
|
117
|
+
const onSaved = vi.fn();
|
|
118
|
+
render(
|
|
119
|
+
<Providers client={client}>
|
|
120
|
+
<ChannelRunConfigDialog
|
|
121
|
+
open
|
|
122
|
+
onOpenChange={vi.fn()}
|
|
123
|
+
channel={channel as never}
|
|
124
|
+
onSaved={onSaved}
|
|
125
|
+
/>
|
|
126
|
+
</Providers>,
|
|
127
|
+
);
|
|
128
|
+
return { apply, onSaved };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function save() {
|
|
132
|
+
fireEvent.click(screen.getByRole("button", { name: "Save" }));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
describe("ChannelRunConfigDialog", () => {
|
|
136
|
+
it("preserves max_tool_rounds verbatim even though it never renders", async () => {
|
|
137
|
+
const { apply } = renderDialog(
|
|
138
|
+
makeChannel({ modelName: "claude-haiku-4-5", maxToolRounds: 25 }),
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
expect(screen.queryByText(/tool round/i)).toBeNull();
|
|
142
|
+
await save();
|
|
143
|
+
|
|
144
|
+
await waitFor(() => expect(apply).toHaveBeenCalledOnce());
|
|
145
|
+
const input = apply.mock.calls[0][0] as AgentChannelInput;
|
|
146
|
+
expect(input.runConfig?.maxToolRounds).toBe(25);
|
|
147
|
+
expect(input.runConfig?.modelName).toBe("claude-haiku-4-5");
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("clears run_config entirely when the draft is emptied (empty = inherit)", async () => {
|
|
151
|
+
const { apply } = renderDialog(
|
|
152
|
+
makeChannel({ modelName: "claude-haiku-4-5", maxCostUsd: 2 }),
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
fireEvent.click(
|
|
156
|
+
screen.getByRole("button", { name: "Reset to platform default" }),
|
|
157
|
+
);
|
|
158
|
+
fireEvent.change(screen.getByLabelText(/Budget per run/), {
|
|
159
|
+
target: { value: "" },
|
|
160
|
+
});
|
|
161
|
+
await save();
|
|
162
|
+
|
|
163
|
+
await waitFor(() => expect(apply).toHaveBeenCalledOnce());
|
|
164
|
+
const input = apply.mock.calls[0][0] as AgentChannelInput;
|
|
165
|
+
expect(input.runConfig).toBeUndefined();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("carries an actively enabled thinking mode, and only then", async () => {
|
|
169
|
+
const { apply } = renderDialog(makeChannel({ modelName: "claude-haiku-4-5" }));
|
|
170
|
+
|
|
171
|
+
fireEvent.click(screen.getByRole("button", { name: /Haiku 4\.5/ }));
|
|
172
|
+
fireEvent.click(await screen.findByRole("switch", { name: "Thinking" }));
|
|
173
|
+
fireEvent.keyDown(document.activeElement ?? document.body, { key: "Escape" });
|
|
174
|
+
await save();
|
|
175
|
+
|
|
176
|
+
await waitFor(() => expect(apply).toHaveBeenCalledOnce());
|
|
177
|
+
const input = apply.mock.calls[0][0] as AgentChannelInput;
|
|
178
|
+
expect(input.runConfig?.thinkingMode).toBe(ThinkingMode.ENABLED);
|
|
179
|
+
// The untouched tier stays absent — never stamped as an explicit
|
|
180
|
+
// STANDARD by a save (#357 ledger distinction).
|
|
181
|
+
expect(input.runConfig?.serviceTier).toBeUndefined();
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("round-trips a stored thinking pin into the switch state", async () => {
|
|
185
|
+
renderDialog(
|
|
186
|
+
makeChannel({
|
|
187
|
+
modelName: "claude-haiku-4-5",
|
|
188
|
+
thinkingMode: ThinkingMode.ENABLED,
|
|
189
|
+
}),
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
fireEvent.click(screen.getByRole("button", { name: /Haiku 4\.5/ }));
|
|
193
|
+
const toggle = await screen.findByRole("switch", { name: "Thinking" });
|
|
194
|
+
expect(toggle.getAttribute("aria-checked")).toBe("true");
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("collects a budget without requiring a model pin", async () => {
|
|
198
|
+
const { apply } = renderDialog(makeChannel());
|
|
199
|
+
|
|
200
|
+
fireEvent.change(screen.getByLabelText(/Budget per run/), {
|
|
201
|
+
target: { value: "1.5" },
|
|
202
|
+
});
|
|
203
|
+
await save();
|
|
204
|
+
|
|
205
|
+
await waitFor(() => expect(apply).toHaveBeenCalledOnce());
|
|
206
|
+
const input = apply.mock.calls[0][0] as AgentChannelInput;
|
|
207
|
+
expect(input.runConfig?.maxCostUsd).toBe(1.5);
|
|
208
|
+
expect(input.runConfig?.modelName).toBeUndefined();
|
|
209
|
+
});
|
|
210
|
+
});
|
package/src/channel/index.ts
CHANGED
|
@@ -69,6 +69,8 @@ export type { ConnectWhatsAppDialogProps } from "./ConnectWhatsAppDialog.js";
|
|
|
69
69
|
|
|
70
70
|
export { ChannelCredentialsDialog } from "./ChannelCredentialsDialog.js";
|
|
71
71
|
export type { ChannelCredentialsDialogProps } from "./ChannelCredentialsDialog.js";
|
|
72
|
+
export { ChannelRunConfigDialog } from "./ChannelRunConfigDialog.js";
|
|
73
|
+
export type { ChannelRunConfigDialogProps } from "./ChannelRunConfigDialog.js";
|
|
72
74
|
|
|
73
75
|
export { ChannelToolCredentials } from "./ChannelToolCredentials.js";
|
|
74
76
|
export type { ChannelToolCredentialsProps } from "./ChannelToolCredentials.js";
|
|
@@ -66,9 +66,11 @@ export function agentChannelToInput(channel: AgentChannel): AgentChannelInput {
|
|
|
66
66
|
})),
|
|
67
67
|
}
|
|
68
68
|
: {}),
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
//
|
|
69
|
+
// run_config rides through verbatim so a settings save or
|
|
70
|
+
// pause/resume toggle never wipes an override set elsewhere (the
|
|
71
|
+
// ChannelRunConfigDialog — the one surface that edits it, #792 —
|
|
72
|
+
// overrides this copy with its own draft; CLI/API overrides survive
|
|
73
|
+
// every other save).
|
|
72
74
|
...(spec?.runConfig
|
|
73
75
|
? {
|
|
74
76
|
runConfig: {
|
|
@@ -76,6 +78,7 @@ export function agentChannelToInput(channel: AgentChannel): AgentChannelInput {
|
|
|
76
78
|
maxCostUsd: spec.runConfig.maxCostUsd,
|
|
77
79
|
maxToolRounds: spec.runConfig.maxToolRounds,
|
|
78
80
|
serviceTier: spec.runConfig.serviceTier,
|
|
81
|
+
thinkingMode: spec.runConfig.thinkingMode,
|
|
79
82
|
},
|
|
80
83
|
}
|
|
81
84
|
: {}),
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { type FormEvent, useCallback, useId, useMemo, useState } from "react";
|
|
4
4
|
import { cn } from "@stigmer/theme";
|
|
5
|
-
import { getUserMessage } from "@stigmer/sdk";
|
|
5
|
+
import { getUserMessage, toChannelAppUpdateInput } from "@stigmer/sdk";
|
|
6
6
|
import type { ChannelApp } from "@stigmer/protos/ai/stigmer/agentic/channelapp/v1/api_pb";
|
|
7
7
|
import { useStigmer } from "../hooks.js";
|
|
8
8
|
import { useUpdateChannelApp } from "./useUpdateChannelApp.js";
|
|
@@ -229,10 +229,10 @@ function SlackAppDetail({
|
|
|
229
229
|
|
|
230
230
|
clearError();
|
|
231
231
|
try {
|
|
232
|
+
// Full-spec-replace safety: spread the complete mapped input and
|
|
233
|
+
// override only the slack arm this form owns.
|
|
232
234
|
const updated = await update({
|
|
233
|
-
|
|
234
|
-
org: channelApp.metadata?.org ?? "",
|
|
235
|
-
...(channelApp.metadata?.slug ? { slug: channelApp.metadata.slug } : {}),
|
|
235
|
+
...toChannelAppUpdateInput(channelApp),
|
|
236
236
|
slack: {
|
|
237
237
|
clientId: clientId.trim(),
|
|
238
238
|
// The redaction marker means "keep the stored value" — the
|
|
@@ -246,7 +246,7 @@ function SlackAppDetail({
|
|
|
246
246
|
// error state is managed by useUpdateChannelApp
|
|
247
247
|
}
|
|
248
248
|
},
|
|
249
|
-
[canSave, clearError, update,
|
|
249
|
+
[canSave, clearError, update, channelApp, clientId, clientSecret, signingSecret, onUpdated],
|
|
250
250
|
);
|
|
251
251
|
|
|
252
252
|
return (
|
|
@@ -379,10 +379,10 @@ function WhatsAppAppDetail({
|
|
|
379
379
|
|
|
380
380
|
clearError();
|
|
381
381
|
try {
|
|
382
|
+
// Full-spec-replace safety: spread the complete mapped input and
|
|
383
|
+
// override only the whatsapp arm this form owns.
|
|
382
384
|
const updated = await update({
|
|
383
|
-
|
|
384
|
-
org: channelApp.metadata?.org ?? "",
|
|
385
|
-
...(channelApp.metadata?.slug ? { slug: channelApp.metadata.slug } : {}),
|
|
385
|
+
...toChannelAppUpdateInput(channelApp),
|
|
386
386
|
whatsapp: {
|
|
387
387
|
appId: metaAppId.trim(),
|
|
388
388
|
// The redaction marker means "keep the stored value" — the
|
|
@@ -397,7 +397,7 @@ function WhatsAppAppDetail({
|
|
|
397
397
|
// error state is managed by useUpdateChannelApp
|
|
398
398
|
}
|
|
399
399
|
},
|
|
400
|
-
[canSave, clearError, update,
|
|
400
|
+
[canSave, clearError, update, channelApp, metaAppId, appSecret, accessToken, verifyToken, onUpdated],
|
|
401
401
|
);
|
|
402
402
|
|
|
403
403
|
return (
|
|
@@ -228,6 +228,8 @@ describe("ChannelAppDetailPanel — WhatsApp branch", () => {
|
|
|
228
228
|
|
|
229
229
|
await waitFor(() => expect(update).toHaveBeenCalled());
|
|
230
230
|
expect(update).toHaveBeenCalledWith({
|
|
231
|
+
// The generated mapper carries metadata.id for exact update addressing.
|
|
232
|
+
id: "chapp_1",
|
|
231
233
|
name: "Acme WhatsApp",
|
|
232
234
|
org: "acme",
|
|
233
235
|
slug: "acme-whatsapp",
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { describe, it, expect, vi, afterEach } from "vitest";
|
|
2
|
+
import { render, screen, fireEvent, waitFor, cleanup } from "@testing-library/react";
|
|
3
|
+
import { create } from "@bufbuild/protobuf";
|
|
4
|
+
import {
|
|
5
|
+
ChannelAppSchema,
|
|
6
|
+
type ChannelApp,
|
|
7
|
+
} from "@stigmer/protos/ai/stigmer/agentic/channelapp/v1/api_pb";
|
|
8
|
+
import type { ChannelAppInput } from "@stigmer/sdk";
|
|
9
|
+
import { StigmerContext } from "../../context";
|
|
10
|
+
import { ChannelAppDetailPanel } from "../ChannelAppDetailPanel";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Regression suite for the full-spec-replace wipe bug: both detail forms
|
|
14
|
+
* must spread `toChannelAppUpdateInput` and override only their provider
|
|
15
|
+
* arm, so identity fields and any future spec field survive the save.
|
|
16
|
+
* Secrets follow the redaction contract: the form is seeded with the
|
|
17
|
+
* server's redaction marker, and sending the marker back means "keep the
|
|
18
|
+
* stored value" (per-field, verified by the backend's
|
|
19
|
+
* TestRedactAndEncryptCoverEveryProviderArm).
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const REDACTED = "***REDACTED***";
|
|
23
|
+
|
|
24
|
+
const SLACK_APP: ChannelApp = create(ChannelAppSchema, {
|
|
25
|
+
metadata: {
|
|
26
|
+
id: "chapp_1",
|
|
27
|
+
name: "Acme Slack",
|
|
28
|
+
slug: "acme-slack",
|
|
29
|
+
org: "acme",
|
|
30
|
+
labels: { team: "support" },
|
|
31
|
+
},
|
|
32
|
+
spec: {
|
|
33
|
+
providerConfig: {
|
|
34
|
+
case: "slack",
|
|
35
|
+
value: {
|
|
36
|
+
clientId: "1234.5678",
|
|
37
|
+
clientSecret: REDACTED,
|
|
38
|
+
signingSecret: REDACTED,
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
function renderPanel(update: ReturnType<typeof vi.fn>) {
|
|
45
|
+
const client = {
|
|
46
|
+
baseUrl: "https://api.stigmer.ai",
|
|
47
|
+
channelapp: { update, delete: vi.fn(async () => ({})) },
|
|
48
|
+
} as never;
|
|
49
|
+
return render(
|
|
50
|
+
<StigmerContext.Provider value={client}>
|
|
51
|
+
<ChannelAppDetailPanel
|
|
52
|
+
channelApp={SLACK_APP}
|
|
53
|
+
consoleOrigin="https://console.acme.example"
|
|
54
|
+
/>
|
|
55
|
+
</StigmerContext.Provider>,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
afterEach(cleanup);
|
|
60
|
+
|
|
61
|
+
describe("ChannelAppDetailPanel save payload (slack arm)", () => {
|
|
62
|
+
it("spreads the complete mapped input and overrides only the slack config", async () => {
|
|
63
|
+
const update = vi.fn(async (_input: ChannelAppInput) => SLACK_APP);
|
|
64
|
+
renderPanel(update);
|
|
65
|
+
|
|
66
|
+
const clientId = await screen.findByLabelText(/client id/i);
|
|
67
|
+
fireEvent.change(clientId, { target: { value: "9999.0000" } });
|
|
68
|
+
fireEvent.click(screen.getByRole("button", { name: /save credentials/i }));
|
|
69
|
+
|
|
70
|
+
await waitFor(() => expect(update).toHaveBeenCalledTimes(1));
|
|
71
|
+
const input = update.mock.calls[0]![0];
|
|
72
|
+
|
|
73
|
+
// The edited field, plus per-field keep-markers for untouched secrets.
|
|
74
|
+
expect(input.slack).toEqual({
|
|
75
|
+
clientId: "9999.0000",
|
|
76
|
+
clientSecret: REDACTED,
|
|
77
|
+
signingSecret: REDACTED,
|
|
78
|
+
});
|
|
79
|
+
// Identity and unrendered metadata survive via the mapper spread.
|
|
80
|
+
expect(input.name).toBe("Acme Slack");
|
|
81
|
+
expect(input.org).toBe("acme");
|
|
82
|
+
expect(input.slug).toBe("acme-slack");
|
|
83
|
+
expect(input.labels).toEqual({ team: "support" });
|
|
84
|
+
// The other provider arm stays absent — the oneof is preserved.
|
|
85
|
+
expect(input.whatsapp).toBeUndefined();
|
|
86
|
+
});
|
|
87
|
+
});
|
|
@@ -13,6 +13,7 @@ import { ModelSelector } from "../models/ModelSelector.js";
|
|
|
13
13
|
import { HarnessSelector } from "../models/HarnessSelector.js";
|
|
14
14
|
import type { HarnessOption } from "../models/harness.js";
|
|
15
15
|
import type { ServiceTierOption } from "../models/service-tier.js";
|
|
16
|
+
import type { ThinkingModeOption } from "../models/thinking-mode.js";
|
|
16
17
|
import { InteractionModePicker, type InteractionModeOption } from "./InteractionModePicker.js";
|
|
17
18
|
import {
|
|
18
19
|
PaperclipIcon,
|
|
@@ -53,6 +54,10 @@ export interface ComposerToolbarProps {
|
|
|
53
54
|
readonly serviceTier?: ServiceTierOption;
|
|
54
55
|
/** Enables the fast-tier switch inside the model selector's options area. */
|
|
55
56
|
readonly onServiceTierChange?: (tier: ServiceTierOption) => void;
|
|
57
|
+
/** Current thinking mode for the selected model (#772). */
|
|
58
|
+
readonly thinkingMode?: ThinkingModeOption;
|
|
59
|
+
/** Enables the thinking switch inside the model selector's options area. */
|
|
60
|
+
readonly onThinkingModeChange?: (mode: ThinkingModeOption) => void;
|
|
56
61
|
|
|
57
62
|
// -- Right group: Secondary actions (icon-only) ---------------------------
|
|
58
63
|
|
|
@@ -128,6 +133,8 @@ export function ComposerToolbar({
|
|
|
128
133
|
onModelChange,
|
|
129
134
|
serviceTier,
|
|
130
135
|
onServiceTierChange,
|
|
136
|
+
thinkingMode,
|
|
137
|
+
onThinkingModeChange,
|
|
131
138
|
}: ComposerToolbarProps) {
|
|
132
139
|
const showHarnessSeparate = showHarnessSelector && !showModelSelector;
|
|
133
140
|
|
|
@@ -161,6 +168,8 @@ export function ComposerToolbar({
|
|
|
161
168
|
onHarnessChange={showHarnessSelector ? onHarnessChange : undefined}
|
|
162
169
|
serviceTier={serviceTier}
|
|
163
170
|
onServiceTierChange={onServiceTierChange}
|
|
171
|
+
thinkingMode={thinkingMode}
|
|
172
|
+
onThinkingModeChange={onThinkingModeChange}
|
|
164
173
|
disabled={disabled}
|
|
165
174
|
/>
|
|
166
175
|
)}
|
|
@@ -8,6 +8,7 @@ import { ComposerToolbar } from "./ComposerToolbar.js";
|
|
|
8
8
|
import { type ConfigureMenuItem } from "./ConfigureMenu.js";
|
|
9
9
|
import type { HarnessOption } from "../models/harness.js";
|
|
10
10
|
import { FAST_SERVICE_TIER, type ServiceTierOption } from "../models/service-tier.js";
|
|
11
|
+
import type { ThinkingModeOption } from "../models/thinking-mode.js";
|
|
11
12
|
import type { InteractionModeOption } from "./InteractionModePicker.js";
|
|
12
13
|
import { parseModelKey } from "../models/registry.js";
|
|
13
14
|
import { useModelRegistry } from "../models/useModelRegistry.js";
|
|
@@ -164,6 +165,14 @@ export interface SessionComposerSubmitContext {
|
|
|
164
165
|
* Pass to execution creation as `execution_config.service_tier`.
|
|
165
166
|
*/
|
|
166
167
|
readonly serviceTier?: ServiceTierOption;
|
|
168
|
+
/**
|
|
169
|
+
* Thinking mode the user actively selected for this execution
|
|
170
|
+
* (stigmer/stigmer#772). Only ever `"enabled"` — an untouched mode means
|
|
171
|
+
* "platform default" and is carried as `undefined`, the same
|
|
172
|
+
* unspecified-vs-explicit contract as {@link serviceTier}.
|
|
173
|
+
* Pass to execution creation as `execution_config.thinking_mode`.
|
|
174
|
+
*/
|
|
175
|
+
readonly thinkingMode?: ThinkingModeOption;
|
|
167
176
|
/**
|
|
168
177
|
* Workspace-relative file paths the user referenced via drag-to-reference.
|
|
169
178
|
*
|
|
@@ -663,6 +672,9 @@ const SessionComposerInner = forwardRef<SessionComposerHandle, SessionComposerPr
|
|
|
663
672
|
// selection below (#663) — state here records the user's intent only.
|
|
664
673
|
const [serviceTier, setServiceTier] = useState<ServiceTierOption>("standard");
|
|
665
674
|
|
|
675
|
+
// Thinking mode (#772): the tier's twin, same intent-vs-effective split.
|
|
676
|
+
const [thinkingMode, setThinkingMode] = useState<ThinkingModeOption>("disabled");
|
|
677
|
+
|
|
666
678
|
// Active harness mirror: controlled hosts (both viewers) keep the
|
|
667
679
|
// `harness` prop current, but with `showHarnessSelector` the dropdown
|
|
668
680
|
// lives inside ModelSelector and an uncontrolled host would leave the
|
|
@@ -837,10 +849,26 @@ const SessionComposerInner = forwardRef<SessionComposerHandle, SessionComposerPr
|
|
|
837
849
|
? "fast"
|
|
838
850
|
: "standard";
|
|
839
851
|
|
|
840
|
-
|
|
852
|
+
// "enabled" rides under the identical rule, keyed on the capability
|
|
853
|
+
// (cursor-harness models only — v1 has no native thinking mapping, #772).
|
|
854
|
+
const effectiveThinkingMode: ThinkingModeOption =
|
|
855
|
+
thinkingMode === "enabled"
|
|
856
|
+
&& effectiveModel !== undefined
|
|
857
|
+
&& effectiveModel.harness === "cursor"
|
|
858
|
+
&& effectiveModel.thinkingCapable === true
|
|
859
|
+
? "enabled"
|
|
860
|
+
: "disabled";
|
|
861
|
+
|
|
862
|
+
return {
|
|
863
|
+
modelId: effectiveModelId,
|
|
864
|
+
model: effectiveModel,
|
|
865
|
+
serviceTier: effectiveServiceTier,
|
|
866
|
+
thinkingMode: effectiveThinkingMode,
|
|
867
|
+
};
|
|
841
868
|
}, [
|
|
842
869
|
modelId,
|
|
843
870
|
serviceTier,
|
|
871
|
+
thinkingMode,
|
|
844
872
|
showModelSelector,
|
|
845
873
|
isRegistryLoading,
|
|
846
874
|
registryGetByKey,
|
|
@@ -1054,15 +1082,19 @@ const SessionComposerInner = forwardRef<SessionComposerHandle, SessionComposerPr
|
|
|
1054
1082
|
// explicit distinction is load-bearing telemetry (#357).
|
|
1055
1083
|
const submitServiceTier =
|
|
1056
1084
|
effective.serviceTier === "fast" ? effective.serviceTier : undefined;
|
|
1085
|
+
// The tier's #772 twin: only an in-effect "enabled" is carried.
|
|
1086
|
+
const submitThinkingMode =
|
|
1087
|
+
effective.thinkingMode === "enabled" ? effective.thinkingMode : undefined;
|
|
1057
1088
|
|
|
1058
1089
|
const context: SessionComposerSubmitContext | undefined =
|
|
1059
1090
|
hasEnv || hasAttachments || effectiveMode || hasFileRefs || buildFromPlan
|
|
1060
|
-
|| submitServiceTier
|
|
1091
|
+
|| submitServiceTier || submitThinkingMode
|
|
1061
1092
|
? {
|
|
1062
1093
|
runtimeEnv: hasEnv ? env : undefined,
|
|
1063
1094
|
attachments: hasAttachments ? attachmentInputs : undefined,
|
|
1064
1095
|
interactionMode: effectiveMode,
|
|
1065
1096
|
serviceTier: submitServiceTier,
|
|
1097
|
+
thinkingMode: submitThinkingMode,
|
|
1066
1098
|
workspaceFileRefs: hasFileRefs ? [...fileRefs.refs] : undefined,
|
|
1067
1099
|
buildFromPlan,
|
|
1068
1100
|
}
|
|
@@ -1894,6 +1926,8 @@ const SessionComposerInner = forwardRef<SessionComposerHandle, SessionComposerPr
|
|
|
1894
1926
|
onModelChange={handleModelChange}
|
|
1895
1927
|
serviceTier={serviceTier}
|
|
1896
1928
|
onServiceTierChange={setServiceTier}
|
|
1929
|
+
thinkingMode={thinkingMode}
|
|
1930
|
+
onThinkingModeChange={setThinkingMode}
|
|
1897
1931
|
/>
|
|
1898
1932
|
</div>
|
|
1899
1933
|
</div>
|