@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,176 @@
|
|
|
1
|
+
import { describe, it, expect, vi, afterEach, beforeAll } from "vitest";
|
|
2
|
+
import { render, screen, cleanup, fireEvent, waitFor } from "@testing-library/react";
|
|
3
|
+
import { type ReactNode } from "react";
|
|
4
|
+
import type { Stigmer } from "@stigmer/sdk";
|
|
5
|
+
import { StigmerContext } from "../../context";
|
|
6
|
+
import { ModelRegistryContext } from "../../models/ModelRegistryContext";
|
|
7
|
+
import type { ModelInfo } from "../../models/registry";
|
|
8
|
+
import { SessionComposer } from "../SessionComposer";
|
|
9
|
+
|
|
10
|
+
// Shim ResizeObserver for Base UI's positioner (the
|
|
11
|
+
// scheduleCreation.test.tsx pattern).
|
|
12
|
+
beforeAll(() => {
|
|
13
|
+
if (!("ResizeObserver" in globalThis)) {
|
|
14
|
+
(globalThis as unknown as { ResizeObserver: unknown }).ResizeObserver =
|
|
15
|
+
class {
|
|
16
|
+
observe() {}
|
|
17
|
+
unobserve() {}
|
|
18
|
+
disconnect() {}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* End-to-end composer wiring for the thinking mode (stigmer/stigmer#772) —
|
|
25
|
+
* the SessionComposer-serviceTier twin. The toolbar's ModelSelector toggle
|
|
26
|
+
* must reach composer state, and an active enabled selection must ride the
|
|
27
|
+
* submit context to execution create. This seam (SessionComposer →
|
|
28
|
+
* ComposerToolbar props → submit context) is exactly where the #357 tier
|
|
29
|
+
* toggle originally shipped dead while every layer unit-tested green.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
const THINKING_CAPABLE: ModelInfo = {
|
|
33
|
+
modelId: "claude-haiku-4-5",
|
|
34
|
+
provider: "anthropic",
|
|
35
|
+
displayName: "Haiku 4.5",
|
|
36
|
+
shortDescription: "Fast Claude with optional thinking",
|
|
37
|
+
speedTier: "fast",
|
|
38
|
+
costTier: "economy",
|
|
39
|
+
harness: "cursor",
|
|
40
|
+
featured: true,
|
|
41
|
+
serviceTiers: [],
|
|
42
|
+
thinkingCapable: true,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const NOT_CAPABLE: ModelInfo = {
|
|
46
|
+
modelId: "composer-2.5",
|
|
47
|
+
provider: "cursor",
|
|
48
|
+
displayName: "Composer 2.5",
|
|
49
|
+
shortDescription: "Fast agentic model",
|
|
50
|
+
speedTier: "fastest",
|
|
51
|
+
costTier: "economy",
|
|
52
|
+
harness: "cursor",
|
|
53
|
+
featured: true,
|
|
54
|
+
serviceTiers: ["fast"],
|
|
55
|
+
thinkingCapable: false,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
function createMinimalStigmerMock(): Stigmer {
|
|
59
|
+
return {
|
|
60
|
+
agentExecution: { uploadAttachment: vi.fn() },
|
|
61
|
+
environment: { getPersonal: vi.fn().mockResolvedValue(null) },
|
|
62
|
+
baseUrl: "http://localhost:8080",
|
|
63
|
+
getAuthCredential: vi.fn().mockResolvedValue("test-token"),
|
|
64
|
+
config: {
|
|
65
|
+
baseUrl: "http://localhost:8080",
|
|
66
|
+
getAccessToken: vi.fn().mockResolvedValue(""),
|
|
67
|
+
},
|
|
68
|
+
} as unknown as Stigmer;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function createWrapper(client: Stigmer) {
|
|
72
|
+
return function Wrapper({ children }: { children: ReactNode }) {
|
|
73
|
+
return (
|
|
74
|
+
<StigmerContext.Provider value={client}>
|
|
75
|
+
<ModelRegistryContext.Provider
|
|
76
|
+
value={{
|
|
77
|
+
models: [THINKING_CAPABLE, NOT_CAPABLE],
|
|
78
|
+
isLoading: false,
|
|
79
|
+
error: null,
|
|
80
|
+
refetch: vi.fn(),
|
|
81
|
+
}}
|
|
82
|
+
>
|
|
83
|
+
{children}
|
|
84
|
+
</ModelRegistryContext.Provider>
|
|
85
|
+
</StigmerContext.Provider>
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function renderComposer(defaultModelId = "claude-haiku-4-5") {
|
|
91
|
+
const client = createMinimalStigmerMock();
|
|
92
|
+
const onSubmit = vi.fn();
|
|
93
|
+
|
|
94
|
+
const result = render(
|
|
95
|
+
<SessionComposer
|
|
96
|
+
onSubmit={onSubmit}
|
|
97
|
+
harness="cursor"
|
|
98
|
+
defaultModelId={defaultModelId}
|
|
99
|
+
/>,
|
|
100
|
+
{ wrapper: createWrapper(client) },
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
return { ...result, onSubmit };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function submitMessage(message: string) {
|
|
107
|
+
const textarea = screen.getByRole("textbox");
|
|
108
|
+
fireEvent.change(textarea, { target: { value: message } });
|
|
109
|
+
fireEvent.keyDown(textarea, { key: "Enter", shiftKey: false });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Open the model selector popover (Base UI portals mount asynchronously in
|
|
114
|
+
* jsdom — the switch is awaited) and flip the thinking switch in the
|
|
115
|
+
* options area, then close the popover (toggling keeps it open).
|
|
116
|
+
*/
|
|
117
|
+
async function toggleThinking(triggerName: RegExp = /Haiku 4\.5/) {
|
|
118
|
+
fireEvent.click(screen.getByRole("button", { name: triggerName }));
|
|
119
|
+
const toggle = await screen.findByRole("switch", { name: "Thinking" });
|
|
120
|
+
fireEvent.click(toggle);
|
|
121
|
+
fireEvent.keyDown(document.activeElement ?? document.body, { key: "Escape" });
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
afterEach(cleanup);
|
|
125
|
+
|
|
126
|
+
describe("SessionComposer — thinking mode submit contract", () => {
|
|
127
|
+
it("renders the thinking switch through the toolbar for a capable model", async () => {
|
|
128
|
+
renderComposer();
|
|
129
|
+
|
|
130
|
+
fireEvent.click(screen.getByRole("button", { name: /Haiku 4\.5/ }));
|
|
131
|
+
expect(await screen.findByRole("switch", { name: "Thinking" })).toBeTruthy();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("renders no thinking switch for a model without the capability", async () => {
|
|
135
|
+
renderComposer("composer-2.5");
|
|
136
|
+
|
|
137
|
+
fireEvent.click(screen.getByRole("button", { name: /Composer 2\.5/ }));
|
|
138
|
+
// The fast switch proves the options area itself rendered — only the
|
|
139
|
+
// thinking row must be absent (no dead controls).
|
|
140
|
+
await screen.findByRole("switch", { name: "Fast tier" });
|
|
141
|
+
expect(screen.queryByRole("switch", { name: "Thinking" })).toBeNull();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("carries thinkingMode 'enabled' on the submit context after toggling", async () => {
|
|
145
|
+
const { onSubmit } = renderComposer();
|
|
146
|
+
|
|
147
|
+
await toggleThinking();
|
|
148
|
+
submitMessage("Think hard about this");
|
|
149
|
+
|
|
150
|
+
await waitFor(() => expect(onSubmit).toHaveBeenCalledOnce());
|
|
151
|
+
const context = onSubmit.mock.calls[0][2];
|
|
152
|
+
expect(context?.thinkingMode).toBe("enabled");
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("leaves thinkingMode undefined when the switch is untouched", async () => {
|
|
156
|
+
const { onSubmit } = renderComposer();
|
|
157
|
+
|
|
158
|
+
submitMessage("Just answer");
|
|
159
|
+
|
|
160
|
+
await waitFor(() => expect(onSubmit).toHaveBeenCalledOnce());
|
|
161
|
+
const context = onSubmit.mock.calls[0][2];
|
|
162
|
+
expect(context?.thinkingMode).toBeUndefined();
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("leaves thinkingMode undefined after toggling on and back off", async () => {
|
|
166
|
+
const { onSubmit } = renderComposer();
|
|
167
|
+
|
|
168
|
+
await toggleThinking();
|
|
169
|
+
await toggleThinking();
|
|
170
|
+
submitMessage("Changed my mind");
|
|
171
|
+
|
|
172
|
+
await waitFor(() => expect(onSubmit).toHaveBeenCalledOnce());
|
|
173
|
+
const context = onSubmit.mock.calls[0][2];
|
|
174
|
+
expect(context?.thinkingMode).toBeUndefined();
|
|
175
|
+
});
|
|
176
|
+
});
|
|
@@ -10,7 +10,7 @@ import { Button } from "../button/index.js";
|
|
|
10
10
|
import { StateBadge } from "./badges.js";
|
|
11
11
|
import { CursorAccountEditor } from "./CursorAccountEditor.js";
|
|
12
12
|
import { CursorAccountsAccessNotice } from "./CursorAccountsAccessNotice.js";
|
|
13
|
-
import { deriveCoverage } from "./cursor-account-coverage.js";
|
|
13
|
+
import { deriveCoverage, derivePoolHealth } from "./cursor-account-coverage.js";
|
|
14
14
|
import { formatSyncTime } from "./cursor-account-format.js";
|
|
15
15
|
import { MemberKeysPanel } from "./MemberKeysPanel.js";
|
|
16
16
|
import { useCursorAccounts } from "./useCursorAccounts.js";
|
|
@@ -119,11 +119,38 @@ export function CursorAccountsConsole({ className }: CursorAccountsConsoleProps)
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
const accounts = list.accounts?.accounts ?? [];
|
|
122
|
+
const health = derivePoolHealth(accounts);
|
|
122
123
|
|
|
123
124
|
return (
|
|
124
125
|
<div className={cn("stg:space-y-3", className)}>
|
|
125
126
|
<div className="stg:flex stg:items-center stg:justify-between stg:gap-3">
|
|
126
|
-
<
|
|
127
|
+
<div>
|
|
128
|
+
<h3 className="stg:text-sm stg:font-semibold stg:text-foreground">Cursor accounts</h3>
|
|
129
|
+
{/* Fleet health in one line (server-computed routability): the
|
|
130
|
+
2026-08-15 drain hid behind per-account enabled counts — this
|
|
131
|
+
is the fact the operator needed on THIS screen. */}
|
|
132
|
+
{accounts.length > 0 && (
|
|
133
|
+
<p
|
|
134
|
+
className={cn(
|
|
135
|
+
"stg:text-[11px]",
|
|
136
|
+
health.totalRoutableKeys === 0
|
|
137
|
+
? "stg:text-destructive"
|
|
138
|
+
: "stg:text-muted-foreground",
|
|
139
|
+
)}
|
|
140
|
+
role={health.totalRoutableKeys === 0 ? "alert" : undefined}
|
|
141
|
+
>
|
|
142
|
+
{health.totalRoutableKeys === 0
|
|
143
|
+
? "No routable execution key anywhere - executions on API-pool models are failing"
|
|
144
|
+
: `${health.totalRoutableKeys} routable ${health.totalRoutableKeys === 1 ? "key" : "keys"} across ${health.totalAccounts} ${health.totalAccounts === 1 ? "account" : "accounts"}`}
|
|
145
|
+
{health.drainedAccounts > 0
|
|
146
|
+
? ` · ${health.drainedAccounts} ${health.drainedAccounts === 1 ? "account" : "accounts"} drained`
|
|
147
|
+
: ""}
|
|
148
|
+
{health.guardTrippedKeys > 0
|
|
149
|
+
? ` · ${health.guardTrippedKeys} ${health.guardTrippedKeys === 1 ? "key" : "keys"} usage-drained`
|
|
150
|
+
: ""}
|
|
151
|
+
</p>
|
|
152
|
+
)}
|
|
153
|
+
</div>
|
|
127
154
|
<Button size="sm" onClick={() => setFlow({ phase: "create" })}>
|
|
128
155
|
Add account
|
|
129
156
|
</Button>
|
|
@@ -137,7 +164,7 @@ export function CursorAccountsConsole({ className }: CursorAccountsConsoleProps)
|
|
|
137
164
|
<span>Account</span>
|
|
138
165
|
<span>Orgs</span>
|
|
139
166
|
<span>Status</span>
|
|
140
|
-
<span>
|
|
167
|
+
<span>Routable keys</span>
|
|
141
168
|
<span className="stg:text-right">Last synced</span>
|
|
142
169
|
</div>
|
|
143
170
|
{accounts.length === 0 ? (
|
|
@@ -214,8 +241,16 @@ function AccountRow({
|
|
|
214
241
|
/>
|
|
215
242
|
</span>
|
|
216
243
|
<span>
|
|
217
|
-
{
|
|
218
|
-
|
|
244
|
+
{/* Routability, not enabled-ness: what key selection would DO.
|
|
245
|
+
"Drained" (usage guard) is recoverable at the cycle reset;
|
|
246
|
+
"Not routable" (no enabled key, or owners removed) needs
|
|
247
|
+
operator action. */}
|
|
248
|
+
{summary.routableKeyCount > 0 ? (
|
|
249
|
+
<span className="stg:text-muted-foreground">
|
|
250
|
+
{summary.routableKeyCount} of {summary.enabledKeyCount}
|
|
251
|
+
</span>
|
|
252
|
+
) : summary.guardTrippedKeyCount > 0 ? (
|
|
253
|
+
<StateBadge tone="warn" label="Drained" />
|
|
219
254
|
) : (
|
|
220
255
|
<StateBadge tone="warn" label="Not routable" />
|
|
221
256
|
)}
|
|
@@ -86,6 +86,7 @@ function scenarSummary() {
|
|
|
86
86
|
return create(CursorAccountSummarySchema, {
|
|
87
87
|
account: scenarAccount(),
|
|
88
88
|
enabledKeyCount: 1,
|
|
89
|
+
routableKeyCount: 1,
|
|
89
90
|
lastSyncedAt: timestampFromDate(new Date("2026-07-22T12:00:00Z")),
|
|
90
91
|
});
|
|
91
92
|
}
|
|
@@ -194,6 +195,37 @@ describe("CursorAccountsConsole", () => {
|
|
|
194
195
|
expect(screen.getByText("empty team")).toBeTruthy();
|
|
195
196
|
expect(screen.getByText("Not routable")).toBeTruthy();
|
|
196
197
|
expect(screen.getByText("shared pool")).toBeTruthy();
|
|
198
|
+
// Routable count renders as "N of M" (server-computed routability),
|
|
199
|
+
// and the fleet-health line aggregates it.
|
|
200
|
+
expect(screen.getByText("1 of 1")).toBeTruthy();
|
|
201
|
+
expect(screen.getByText(/1 routable key across 2 accounts/)).toBeTruthy();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("surfaces a fully guard-drained account and the fleet drain alert", async () => {
|
|
205
|
+
// The 2026-08-15 shape: enabled keys exist, none routable — the one
|
|
206
|
+
// key left is usage-guard-tripped. The row must read "Drained" (a
|
|
207
|
+
// cycle-reset condition), not the misleading enabled count, and with
|
|
208
|
+
// zero routable keys fleet-wide the health line must alarm.
|
|
209
|
+
const drained = create(CursorAccountSummarySchema, {
|
|
210
|
+
account: scenarAccount({
|
|
211
|
+
accountId: "acc-3",
|
|
212
|
+
displayName: "drained team",
|
|
213
|
+
orgIds: [],
|
|
214
|
+
}),
|
|
215
|
+
enabledKeyCount: 1,
|
|
216
|
+
routableKeyCount: 0,
|
|
217
|
+
guardTrippedKeyCount: 1,
|
|
218
|
+
});
|
|
219
|
+
const client = createMockStigmer({
|
|
220
|
+
listAccounts: vi.fn().mockResolvedValue({ accounts: [drained] }),
|
|
221
|
+
});
|
|
222
|
+
render(<CursorAccountsConsole />, { wrapper: wrapper(client) });
|
|
223
|
+
|
|
224
|
+
await waitFor(() => expect(screen.getByText("drained team")).toBeTruthy());
|
|
225
|
+
expect(screen.getByText("Drained")).toBeTruthy();
|
|
226
|
+
expect(
|
|
227
|
+
screen.getByText(/No routable execution key anywhere/),
|
|
228
|
+
).toBeTruthy();
|
|
197
229
|
});
|
|
198
230
|
|
|
199
231
|
it("shows the designed access notice on PERMISSION_DENIED", async () => {
|
|
@@ -7,12 +7,14 @@ import {
|
|
|
7
7
|
CursorTeamMemberSchema,
|
|
8
8
|
} from "@stigmer/protos/ai/stigmer/platform/cursoraccount/v1/cursor_account_pb";
|
|
9
9
|
import {
|
|
10
|
+
CursorAccountSummarySchema,
|
|
10
11
|
CursorAccountViewSchema,
|
|
11
12
|
CursorMemberKeyState,
|
|
12
13
|
CursorMemberKeyViewSchema,
|
|
13
14
|
CursorTeamMemberViewSchema,
|
|
14
15
|
} from "@stigmer/protos/ai/stigmer/platform/cursoraccount/v1/io_pb";
|
|
15
|
-
import {
|
|
16
|
+
import { CursorAccountSchema } from "@stigmer/protos/ai/stigmer/platform/cursoraccount/v1/cursor_account_pb";
|
|
17
|
+
import { deriveCoverage, derivePoolHealth } from "../cursor-account-coverage";
|
|
16
18
|
|
|
17
19
|
function keyView(keyId: string, state: CursorMemberKeyState) {
|
|
18
20
|
return create(CursorMemberKeyViewSchema, {
|
|
@@ -71,3 +73,58 @@ describe("deriveCoverage", () => {
|
|
|
71
73
|
expect(coverage.onTeamWithKey).toEqual([]);
|
|
72
74
|
});
|
|
73
75
|
});
|
|
76
|
+
|
|
77
|
+
describe("derivePoolHealth", () => {
|
|
78
|
+
function summary(opts: {
|
|
79
|
+
enabled?: boolean;
|
|
80
|
+
enabledKeyCount: number;
|
|
81
|
+
routableKeyCount: number;
|
|
82
|
+
guardTrippedKeyCount?: number;
|
|
83
|
+
}) {
|
|
84
|
+
return create(CursorAccountSummarySchema, {
|
|
85
|
+
account: create(CursorAccountSchema, {
|
|
86
|
+
accountId: "acc",
|
|
87
|
+
enabled: opts.enabled ?? true,
|
|
88
|
+
}),
|
|
89
|
+
enabledKeyCount: opts.enabledKeyCount,
|
|
90
|
+
routableKeyCount: opts.routableKeyCount,
|
|
91
|
+
guardTrippedKeyCount: opts.guardTrippedKeyCount ?? 0,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
it("reproduces the 2026-08-15 drain as a one-line fact", () => {
|
|
96
|
+
// Four accounts with enabled keys but zero routable (guard-tripped or
|
|
97
|
+
// owner-removed), one fresh account with a single routable key — the
|
|
98
|
+
// fleet state the per-account enabled counts hid.
|
|
99
|
+
const health = derivePoolHealth([
|
|
100
|
+
summary({ enabledKeyCount: 1, routableKeyCount: 0, guardTrippedKeyCount: 1 }),
|
|
101
|
+
summary({ enabledKeyCount: 10, routableKeyCount: 0 }),
|
|
102
|
+
summary({ enabledKeyCount: 1, routableKeyCount: 0 }),
|
|
103
|
+
summary({ enabledKeyCount: 0, routableKeyCount: 0 }),
|
|
104
|
+
summary({ enabledKeyCount: 2, routableKeyCount: 1, guardTrippedKeyCount: 0 }),
|
|
105
|
+
]);
|
|
106
|
+
|
|
107
|
+
expect(health.totalAccounts).toBe(5);
|
|
108
|
+
expect(health.totalRoutableKeys).toBe(1);
|
|
109
|
+
// The zero-key account is not "drained" — it never had capacity.
|
|
110
|
+
expect(health.drainedAccounts).toBe(3);
|
|
111
|
+
expect(health.guardTrippedKeys).toBe(1);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("does not count disabled accounts as drained (disable means drain-by-choice)", () => {
|
|
115
|
+
const health = derivePoolHealth([
|
|
116
|
+
summary({ enabled: false, enabledKeyCount: 3, routableKeyCount: 0 }),
|
|
117
|
+
]);
|
|
118
|
+
|
|
119
|
+
expect(health.drainedAccounts).toBe(0);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("is all zeros for an empty list", () => {
|
|
123
|
+
const health = derivePoolHealth([]);
|
|
124
|
+
|
|
125
|
+
expect(health.totalAccounts).toBe(0);
|
|
126
|
+
expect(health.totalRoutableKeys).toBe(0);
|
|
127
|
+
expect(health.drainedAccounts).toBe(0);
|
|
128
|
+
expect(health.guardTrippedKeys).toBe(0);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CursorMemberKeyState,
|
|
3
|
+
type CursorAccountSummary,
|
|
3
4
|
type CursorAccountView,
|
|
4
5
|
type CursorMemberKeyView,
|
|
5
6
|
type CursorTeamMemberView,
|
|
@@ -69,3 +70,57 @@ export function deriveCoverage(view: CursorAccountView): CursorAccountCoverage {
|
|
|
69
70
|
unclassified: [],
|
|
70
71
|
};
|
|
71
72
|
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Fleet-wide health rolled up from the list summaries — the console's
|
|
76
|
+
* answer to "can the platform serve right now, and from where?". Pure
|
|
77
|
+
* aggregation of the server-computed routable/guard counts (the same
|
|
78
|
+
* routability rule key selection uses); this module must never
|
|
79
|
+
* re-derive routability client-side.
|
|
80
|
+
*
|
|
81
|
+
* <p>Why this exists: in the 2026-08-15 pool drain, the list showed
|
|
82
|
+
* five accounts with healthy-looking enabled-key counts while exactly
|
|
83
|
+
* one guard-tripped key could serve — the operator had to open every
|
|
84
|
+
* account detail to see it. The rollup makes fleet exhaustion a
|
|
85
|
+
* one-line fact.
|
|
86
|
+
*/
|
|
87
|
+
export interface CursorPoolHealth {
|
|
88
|
+
/** Total accounts in the list. */
|
|
89
|
+
readonly totalAccounts: number;
|
|
90
|
+
/** Keys the routability rule would select for a new session, fleet-wide. */
|
|
91
|
+
readonly totalRoutableKeys: number;
|
|
92
|
+
/**
|
|
93
|
+
* Enabled accounts with enabled keys but zero routable ones — every
|
|
94
|
+
* key is dead (owner removed) or usage-drained. These are the rows
|
|
95
|
+
* that look healthy at a glance and are not.
|
|
96
|
+
*/
|
|
97
|
+
readonly drainedAccounts: number;
|
|
98
|
+
/** Enabled keys excluded only by the usage guard, fleet-wide. */
|
|
99
|
+
readonly guardTrippedKeys: number;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Aggregate the server-computed summary counts across the account list. */
|
|
103
|
+
export function derivePoolHealth(
|
|
104
|
+
summaries: readonly CursorAccountSummary[],
|
|
105
|
+
): CursorPoolHealth {
|
|
106
|
+
let totalRoutableKeys = 0;
|
|
107
|
+
let drainedAccounts = 0;
|
|
108
|
+
let guardTrippedKeys = 0;
|
|
109
|
+
for (const summary of summaries) {
|
|
110
|
+
totalRoutableKeys += summary.routableKeyCount;
|
|
111
|
+
guardTrippedKeys += summary.guardTrippedKeyCount;
|
|
112
|
+
if (
|
|
113
|
+
summary.account?.enabled === true
|
|
114
|
+
&& summary.enabledKeyCount > 0
|
|
115
|
+
&& summary.routableKeyCount === 0
|
|
116
|
+
) {
|
|
117
|
+
drainedAccounts += 1;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
totalAccounts: summaries.length,
|
|
122
|
+
totalRoutableKeys,
|
|
123
|
+
drainedAccounts,
|
|
124
|
+
guardTrippedKeys,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
@@ -14,6 +14,7 @@ import { useStigmer } from "../hooks.js";
|
|
|
14
14
|
import { toError } from "../internal/toError.js";
|
|
15
15
|
import { toProtoInteractionMode } from "../composer/interaction-mode.js";
|
|
16
16
|
import { toProtoServiceTier, type ServiceTierOption } from "../models/service-tier.js";
|
|
17
|
+
import { toProtoThinkingMode, type ThinkingModeOption } from "../models/thinking-mode.js";
|
|
17
18
|
import { toProtoHarness, type HarnessOption } from "../models/harness.js";
|
|
18
19
|
import {
|
|
19
20
|
toProtoExecutionTarget,
|
|
@@ -138,6 +139,20 @@ export interface SharedAgentExecutionFields {
|
|
|
138
139
|
* Maps to `ExecutionConfig.service_tier` in the proto.
|
|
139
140
|
*/
|
|
140
141
|
readonly serviceTier?: ServiceTierOption;
|
|
142
|
+
/**
|
|
143
|
+
* Thinking mode for this execution's model calls (stigmer/stigmer#772).
|
|
144
|
+
*
|
|
145
|
+
* - `"disabled"` (default): the model's base variant, pinned explicitly —
|
|
146
|
+
* never the provider account default.
|
|
147
|
+
* - `"enabled"`: the model's extended-reasoning variant, billed at base
|
|
148
|
+
* per-token rates (reasoning tokens bill as output). Valid only for
|
|
149
|
+
* cursor-harness models whose registry entry declares the thinking
|
|
150
|
+
* capability — the backend refuses the create otherwise, so gate the
|
|
151
|
+
* option on `ModelInfo.thinkingCapable`.
|
|
152
|
+
*
|
|
153
|
+
* Maps to `ExecutionConfig.thinking_mode` in the proto.
|
|
154
|
+
*/
|
|
155
|
+
readonly thinkingMode?: ThinkingModeOption;
|
|
141
156
|
/**
|
|
142
157
|
* Marks this execution as a Build-from-plan turn: the user approved a
|
|
143
158
|
* plan from a prior Plan-mode execution and asked the agent to implement
|
|
@@ -318,6 +333,7 @@ export function useCreateAgentExecution(): UseCreateAgentExecutionReturn {
|
|
|
318
333
|
input.modelName ||
|
|
319
334
|
input.interactionMode ||
|
|
320
335
|
input.serviceTier ||
|
|
336
|
+
input.thinkingMode ||
|
|
321
337
|
input.structuredOutputSchema ||
|
|
322
338
|
input.buildFromPlan;
|
|
323
339
|
const executionConfig = hasConfig
|
|
@@ -329,6 +345,9 @@ export function useCreateAgentExecution(): UseCreateAgentExecutionReturn {
|
|
|
329
345
|
...(input.serviceTier
|
|
330
346
|
? { serviceTier: toProtoServiceTier(input.serviceTier) }
|
|
331
347
|
: {}),
|
|
348
|
+
...(input.thinkingMode
|
|
349
|
+
? { thinkingMode: toProtoThinkingMode(input.thinkingMode) }
|
|
350
|
+
: {}),
|
|
332
351
|
...(input.structuredOutputSchema
|
|
333
352
|
? { structuredOutputSchema: input.structuredOutputSchema }
|
|
334
353
|
: {}),
|