@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,75 @@
|
|
|
1
|
+
import { describe, it, expect, vi, afterEach } from "vitest";
|
|
2
|
+
import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
3
|
+
import { create } from "@bufbuild/protobuf";
|
|
4
|
+
import {
|
|
5
|
+
AgentInstanceSchema,
|
|
6
|
+
type AgentInstance,
|
|
7
|
+
} from "@stigmer/protos/ai/stigmer/agentic/agentinstance/v1/api_pb";
|
|
8
|
+
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
9
|
+
import type { AgentInstanceInput } from "@stigmer/sdk";
|
|
10
|
+
import { StigmerContext } from "../../context";
|
|
11
|
+
import { AgentInstanceDetailPanel } from "../AgentInstanceDetailPanel";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Regression suite for the full-spec-replace wipe bug: the environment
|
|
15
|
+
* editor must spread `toAgentInstanceUpdateInput` and override only the
|
|
16
|
+
* environment refs, so agent_id/description (and any future spec field)
|
|
17
|
+
* survive an environments-only save.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const INSTANCE: AgentInstance = create(AgentInstanceSchema, {
|
|
21
|
+
metadata: {
|
|
22
|
+
id: "inst-1",
|
|
23
|
+
name: "Prod Support Bot",
|
|
24
|
+
slug: "prod-support-bot",
|
|
25
|
+
org: "acme",
|
|
26
|
+
},
|
|
27
|
+
spec: {
|
|
28
|
+
agentId: "agent-1",
|
|
29
|
+
description: "Production instance.",
|
|
30
|
+
environmentRefs: [
|
|
31
|
+
{ org: "acme", slug: "prod", kind: ApiResourceKind.environment },
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
function renderPanel(update: ReturnType<typeof vi.fn>) {
|
|
37
|
+
const client = {
|
|
38
|
+
agentInstance: { update },
|
|
39
|
+
iamPolicy: {
|
|
40
|
+
checkMyPermission: vi.fn(async () => ({ isAuthorized: true })),
|
|
41
|
+
},
|
|
42
|
+
environment: {
|
|
43
|
+
list: vi.fn(async () => ({ items: [], totalCount: 0 })),
|
|
44
|
+
},
|
|
45
|
+
} as never;
|
|
46
|
+
return render(
|
|
47
|
+
<StigmerContext.Provider value={client}>
|
|
48
|
+
<AgentInstanceDetailPanel instance={INSTANCE} org="acme" onClose={() => {}} />
|
|
49
|
+
</StigmerContext.Provider>,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
afterEach(cleanup);
|
|
54
|
+
|
|
55
|
+
describe("AgentInstanceDetailPanel save payload", () => {
|
|
56
|
+
it("round-trips agent_id and description on an environments-only save", async () => {
|
|
57
|
+
const update = vi.fn(async (_input: AgentInstanceInput) => INSTANCE);
|
|
58
|
+
renderPanel(update);
|
|
59
|
+
|
|
60
|
+
fireEvent.click(await screen.findByRole("button", { name: "Edit" }));
|
|
61
|
+
fireEvent.click(screen.getByRole("button", { name: "Save" }));
|
|
62
|
+
|
|
63
|
+
await waitFor(() => expect(update).toHaveBeenCalledTimes(1));
|
|
64
|
+
const input = update.mock.calls[0]![0];
|
|
65
|
+
|
|
66
|
+
// The wipe-bug guard: fields the environment editor does not render.
|
|
67
|
+
expect(input.agentId).toBe("agent-1");
|
|
68
|
+
expect(input.description).toBe("Production instance.");
|
|
69
|
+
// The form-owned field.
|
|
70
|
+
expect(input.environmentRefs).toEqual([{ org: "acme", slug: "prod" }]);
|
|
71
|
+
// Addressing fields.
|
|
72
|
+
expect(input.org).toBe("acme");
|
|
73
|
+
expect(input.slug).toBe("prod-support-bot");
|
|
74
|
+
});
|
|
75
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useCallback, useState } from "react";
|
|
4
|
-
import { FileCode2, KeyRound, LayoutTemplate, MessageSquare, MoreHorizontal, Share2, Trash2 } from "lucide-react";
|
|
4
|
+
import { FileCode2, KeyRound, LayoutTemplate, MessageSquare, MoreHorizontal, Share2, SlidersHorizontal, Trash2 } from "lucide-react";
|
|
5
5
|
import { cn } from "@stigmer/theme";
|
|
6
6
|
import { getUserMessage } from "@stigmer/sdk";
|
|
7
7
|
import type { Agent } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
|
|
@@ -25,6 +25,7 @@ import { CloudFeatureNotice } from "../internal/CloudFeatureNotice.js";
|
|
|
25
25
|
import { TruncatedText } from "../internal/truncated-text.js";
|
|
26
26
|
import { ChannelConversationsDialog } from "./ChannelConversationsDialog.js";
|
|
27
27
|
import { ChannelCredentialsDialog } from "./ChannelCredentialsDialog.js";
|
|
28
|
+
import { ChannelRunConfigDialog } from "./ChannelRunConfigDialog.js";
|
|
28
29
|
import { ChannelTemplatesDialog } from "./ChannelTemplatesDialog.js";
|
|
29
30
|
import { ConnectSlackDialog } from "./ConnectSlackDialog.js";
|
|
30
31
|
import { ConnectWhatsAppDialog } from "./ConnectWhatsAppDialog.js";
|
|
@@ -160,6 +161,7 @@ export function AgentChannelsPanel({
|
|
|
160
161
|
|
|
161
162
|
// The channel being edited as YAML (the kind-agnostic manifest flow).
|
|
162
163
|
const [editingYaml, setEditingYaml] = useState<AgentChannel | null>(null);
|
|
164
|
+
const [editingRunConfig, setEditingRunConfig] = useState<AgentChannel | null>(null);
|
|
163
165
|
|
|
164
166
|
const handleConnect = useCallback(
|
|
165
167
|
(channel: AgentChannel | null, provider: ChannelProviderDescriptor) => {
|
|
@@ -305,6 +307,7 @@ export function AgentChannelsPanel({
|
|
|
305
307
|
onViewConversations={() => setViewingConversations(channel)}
|
|
306
308
|
onViewTemplates={() => setViewingTemplates(channel)}
|
|
307
309
|
onEditYaml={() => setEditingYaml(channel)}
|
|
310
|
+
onEditRunConfig={() => setEditingRunConfig(channel)}
|
|
308
311
|
refetch={refetch}
|
|
309
312
|
/>
|
|
310
313
|
);
|
|
@@ -390,6 +393,17 @@ export function AgentChannelsPanel({
|
|
|
390
393
|
/>
|
|
391
394
|
)}
|
|
392
395
|
|
|
396
|
+
{editingRunConfig && (
|
|
397
|
+
<ChannelRunConfigDialog
|
|
398
|
+
open
|
|
399
|
+
onOpenChange={(open) => {
|
|
400
|
+
if (!open) setEditingRunConfig(null);
|
|
401
|
+
}}
|
|
402
|
+
channel={editingRunConfig}
|
|
403
|
+
onSaved={refetch}
|
|
404
|
+
/>
|
|
405
|
+
)}
|
|
406
|
+
|
|
393
407
|
<ConfirmDialog
|
|
394
408
|
state={confirmState}
|
|
395
409
|
onConfirm={handleConfirm}
|
|
@@ -415,6 +429,7 @@ interface ChannelCardProps {
|
|
|
415
429
|
readonly onViewConversations: () => void;
|
|
416
430
|
readonly onViewTemplates: () => void;
|
|
417
431
|
readonly onEditYaml: () => void;
|
|
432
|
+
readonly onEditRunConfig: () => void;
|
|
418
433
|
readonly refetch: () => void;
|
|
419
434
|
}
|
|
420
435
|
|
|
@@ -429,6 +444,7 @@ function ChannelCard({
|
|
|
429
444
|
onViewConversations,
|
|
430
445
|
onViewTemplates,
|
|
431
446
|
onEditYaml,
|
|
447
|
+
onEditRunConfig,
|
|
432
448
|
refetch,
|
|
433
449
|
}: ChannelCardProps) {
|
|
434
450
|
const meta = channel.metadata;
|
|
@@ -584,6 +600,15 @@ function ChannelCard({
|
|
|
584
600
|
Templates
|
|
585
601
|
</ActionMenu.Item>
|
|
586
602
|
)}
|
|
603
|
+
{canEdit && (
|
|
604
|
+
<ActionMenu.Item
|
|
605
|
+
icon={<SlidersHorizontal />}
|
|
606
|
+
onSelect={onEditRunConfig}
|
|
607
|
+
data-cursor-target="channel-run-config"
|
|
608
|
+
>
|
|
609
|
+
Run settings
|
|
610
|
+
</ActionMenu.Item>
|
|
611
|
+
)}
|
|
587
612
|
{canEdit && (
|
|
588
613
|
<ActionMenu.Item
|
|
589
614
|
icon={<KeyRound />}
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useId, useState } from "react";
|
|
4
|
+
import { cn } from "@stigmer/theme";
|
|
5
|
+
import { getUserMessage, type RunConfigInput } from "@stigmer/sdk";
|
|
6
|
+
import type { AgentChannel } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/api_pb";
|
|
7
|
+
import { Button } from "../button/Button.js";
|
|
8
|
+
import { DialogShell } from "../internal/DialogShell.js";
|
|
9
|
+
import { ModelSelector } from "../models/ModelSelector.js";
|
|
10
|
+
import type { HarnessOption } from "../models/harness.js";
|
|
11
|
+
import {
|
|
12
|
+
fromProtoServiceTier,
|
|
13
|
+
toProtoServiceTier,
|
|
14
|
+
type ServiceTierOption,
|
|
15
|
+
} from "../models/service-tier.js";
|
|
16
|
+
import {
|
|
17
|
+
fromProtoThinkingMode,
|
|
18
|
+
toProtoThinkingMode,
|
|
19
|
+
type ThinkingModeOption,
|
|
20
|
+
} from "../models/thinking-mode.js";
|
|
21
|
+
import { agentChannelToInput, useSaveAgentChannel } from "./useSaveAgentChannel.js";
|
|
22
|
+
|
|
23
|
+
/** Props for {@link ChannelRunConfigDialog}. */
|
|
24
|
+
export interface ChannelRunConfigDialogProps {
|
|
25
|
+
/** Whether the dialog is open. */
|
|
26
|
+
readonly open: boolean;
|
|
27
|
+
/** Called when the dialog requests an open-state change. */
|
|
28
|
+
readonly onOpenChange: (open: boolean) => void;
|
|
29
|
+
/** The channel whose run settings are edited. */
|
|
30
|
+
readonly channel: AgentChannel;
|
|
31
|
+
/** Called after a successful save (hosts typically pass `refetch`). */
|
|
32
|
+
readonly onSaved?: () => void;
|
|
33
|
+
/**
|
|
34
|
+
* When `false`, renders as an in-flow open dialog instead of a
|
|
35
|
+
* top-layer modal — for embedding in constrained surfaces
|
|
36
|
+
* (documentation demos, visual tests). Interactive hosts keep the
|
|
37
|
+
* default.
|
|
38
|
+
* @default true
|
|
39
|
+
*/
|
|
40
|
+
readonly modal?: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Edits an installed channel's run settings (`AgentChannelSpec.run_config`)
|
|
45
|
+
* — the console's first run_config editor (stigmer/stigmer#792), replacing
|
|
46
|
+
* the Edit-YAML detour for the model pin the write-time validation guards
|
|
47
|
+
* (stigmer/stigmer#774).
|
|
48
|
+
*
|
|
49
|
+
* Speaks the schedule form's vocabulary (DD-018 D-5): the model pin uses
|
|
50
|
+
* the composer's own {@link ModelSelector} (with the fast-tier and
|
|
51
|
+
* thinking switches, #357/#772) and the budget is the one run bound a
|
|
52
|
+
* user owns; `max_tool_rounds` is an operator knob, API-reachable only —
|
|
53
|
+
* preserved verbatim on save, never rendered.
|
|
54
|
+
*
|
|
55
|
+
* The harness dropdown is a browsing scope for the picker, initialized to
|
|
56
|
+
* cursor (the platform's channel-serving default): channels carry no
|
|
57
|
+
* per-channel harness — the serving harness is a platform property — and
|
|
58
|
+
* the write-time model-pin validation judges the pin against any harness
|
|
59
|
+
* section, so pinning under either scope is legitimate.
|
|
60
|
+
*
|
|
61
|
+
* Saves are full-input applies via {@link agentChannelToInput}, so the
|
|
62
|
+
* agent reference, provider marker, credential bindings, and install
|
|
63
|
+
* status all survive; clearing the model with no budget set is an
|
|
64
|
+
* explicit reset to the platform defaults (`run_config` absent). Untouched
|
|
65
|
+
* variant switches stay off the wire — unspecified-vs-explicit is a
|
|
66
|
+
* load-bearing ledger distinction (#357).
|
|
67
|
+
*
|
|
68
|
+
* Built on the native `<dialog>` element, matching the SDK's modal
|
|
69
|
+
* convention. Most hosts mount it via {@link AgentChannelsPanel}'s
|
|
70
|
+
* channel card action.
|
|
71
|
+
*/
|
|
72
|
+
export function ChannelRunConfigDialog({
|
|
73
|
+
open,
|
|
74
|
+
onOpenChange,
|
|
75
|
+
channel,
|
|
76
|
+
onSaved,
|
|
77
|
+
modal = true,
|
|
78
|
+
}: ChannelRunConfigDialogProps) {
|
|
79
|
+
// Instance-scoped title id (oss#593): hosts legitimately mount this
|
|
80
|
+
// dialog more than once per page; duplicate DOM ids would break the
|
|
81
|
+
// aria-labelledby association for every copy after the first.
|
|
82
|
+
const titleId = useId();
|
|
83
|
+
|
|
84
|
+
const handleClose = useCallback(() => {
|
|
85
|
+
onOpenChange(false);
|
|
86
|
+
}, [onOpenChange]);
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<DialogShell
|
|
90
|
+
open={open}
|
|
91
|
+
onOpenChange={onOpenChange}
|
|
92
|
+
modal={modal}
|
|
93
|
+
width="md"
|
|
94
|
+
aria-labelledby={titleId}
|
|
95
|
+
>
|
|
96
|
+
{/* Body mounts only while open so its draft resets per session —
|
|
97
|
+
reopening never resumes stale, unsaved edits. */}
|
|
98
|
+
{open && (
|
|
99
|
+
<ChannelRunConfigDialogBody
|
|
100
|
+
channel={channel}
|
|
101
|
+
onSaved={onSaved}
|
|
102
|
+
onClose={handleClose}
|
|
103
|
+
titleId={titleId}
|
|
104
|
+
/>
|
|
105
|
+
)}
|
|
106
|
+
</DialogShell>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function ChannelRunConfigDialogBody({
|
|
111
|
+
channel,
|
|
112
|
+
onSaved,
|
|
113
|
+
onClose,
|
|
114
|
+
titleId,
|
|
115
|
+
}: {
|
|
116
|
+
readonly channel: AgentChannel;
|
|
117
|
+
readonly onSaved?: () => void;
|
|
118
|
+
readonly onClose: () => void;
|
|
119
|
+
/** Heading id minted by the outer dialog for its aria-labelledby. */
|
|
120
|
+
readonly titleId: string;
|
|
121
|
+
}) {
|
|
122
|
+
const channelName =
|
|
123
|
+
channel.metadata?.name || channel.metadata?.slug || "this channel";
|
|
124
|
+
const stored = channel.spec?.runConfig;
|
|
125
|
+
|
|
126
|
+
const { save, isPending, error, clearError } = useSaveAgentChannel();
|
|
127
|
+
|
|
128
|
+
const [modelName, setModelName] = useState(stored?.modelName ?? "");
|
|
129
|
+
const [harness, setHarness] = useState<HarnessOption>("cursor");
|
|
130
|
+
const [serviceTier, setServiceTier] = useState<ServiceTierOption>(
|
|
131
|
+
fromProtoServiceTier(stored?.serviceTier) ?? "standard",
|
|
132
|
+
);
|
|
133
|
+
const [thinkingMode, setThinkingMode] = useState<ThinkingModeOption>(
|
|
134
|
+
fromProtoThinkingMode(stored?.thinkingMode) ?? "disabled",
|
|
135
|
+
);
|
|
136
|
+
const [budgetUsd, setBudgetUsd] = useState(
|
|
137
|
+
stored && stored.maxCostUsd > 0 ? String(stored.maxCostUsd) : "",
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
const handleSave = useCallback(async () => {
|
|
141
|
+
try {
|
|
142
|
+
// Full-input apply: only run_config changes. An all-empty draft
|
|
143
|
+
// clears the block entirely — the proto's "empty = inherit" contract
|
|
144
|
+
// (DD-018 D-2) — while `max_tool_rounds`, which this editor never
|
|
145
|
+
// renders, survives verbatim (operator knob, DD-018 D-5).
|
|
146
|
+
await save({
|
|
147
|
+
...agentChannelToInput(channel),
|
|
148
|
+
runConfig: buildRunConfig(
|
|
149
|
+
modelName,
|
|
150
|
+
budgetUsd,
|
|
151
|
+
serviceTier,
|
|
152
|
+
thinkingMode,
|
|
153
|
+
stored?.maxToolRounds ?? 0,
|
|
154
|
+
),
|
|
155
|
+
});
|
|
156
|
+
onSaved?.();
|
|
157
|
+
onClose();
|
|
158
|
+
} catch {
|
|
159
|
+
// Surfaced through the hook's error state below.
|
|
160
|
+
}
|
|
161
|
+
}, [budgetUsd, channel, modelName, onClose, onSaved, save, serviceTier, stored, thinkingMode]);
|
|
162
|
+
|
|
163
|
+
return (
|
|
164
|
+
<div className="stg:flex stg:flex-col">
|
|
165
|
+
<div className="stg:flex stg:items-start stg:justify-between stg:gap-3 stg:border-b stg:border-border stg:px-5 stg:py-4">
|
|
166
|
+
<div className="stg:min-w-0">
|
|
167
|
+
<h2
|
|
168
|
+
id={titleId}
|
|
169
|
+
className="stg:text-sm stg:font-semibold stg:text-foreground"
|
|
170
|
+
>
|
|
171
|
+
Run settings
|
|
172
|
+
</h2>
|
|
173
|
+
<p className="stg:mt-0.5 stg:truncate stg:text-xs stg:text-muted-foreground">
|
|
174
|
+
{channelName}
|
|
175
|
+
</p>
|
|
176
|
+
</div>
|
|
177
|
+
<button
|
|
178
|
+
type="button"
|
|
179
|
+
onClick={onClose}
|
|
180
|
+
aria-label="Close dialog"
|
|
181
|
+
className={cn(
|
|
182
|
+
"stg:rounded stg:p-1 stg:text-muted-foreground",
|
|
183
|
+
"stg:hover:bg-accent-hover stg:hover:text-foreground",
|
|
184
|
+
"stg:focus:outline-none stg:focus:ring-1 stg:focus:ring-ring",
|
|
185
|
+
)}
|
|
186
|
+
>
|
|
187
|
+
<CloseIcon />
|
|
188
|
+
</button>
|
|
189
|
+
</div>
|
|
190
|
+
|
|
191
|
+
<div className="stg:space-y-4 stg:px-5 stg:py-4">
|
|
192
|
+
{/* Engine & model — the composer's own picker (DD-018 D-5), the
|
|
193
|
+
same shape as the schedule form's. Nothing is pinned until a
|
|
194
|
+
model is picked. */}
|
|
195
|
+
<div className="stg:space-y-1">
|
|
196
|
+
<span className={labelClasses}>
|
|
197
|
+
Model{" "}
|
|
198
|
+
<span className="stg:font-normal stg:text-muted-foreground">(optional)</span>
|
|
199
|
+
</span>
|
|
200
|
+
<div className="stg:flex stg:items-center stg:gap-2">
|
|
201
|
+
<ModelSelector
|
|
202
|
+
value={modelName}
|
|
203
|
+
onValueChange={setModelName}
|
|
204
|
+
initialHarness={harness}
|
|
205
|
+
onHarnessChange={setHarness}
|
|
206
|
+
serviceTier={serviceTier}
|
|
207
|
+
onServiceTierChange={setServiceTier}
|
|
208
|
+
thinkingMode={thinkingMode}
|
|
209
|
+
onThinkingModeChange={setThinkingMode}
|
|
210
|
+
placeholderLabel="Platform default"
|
|
211
|
+
disabled={isPending}
|
|
212
|
+
/>
|
|
213
|
+
{modelName !== "" && (
|
|
214
|
+
<button
|
|
215
|
+
type="button"
|
|
216
|
+
onClick={() => {
|
|
217
|
+
setModelName("");
|
|
218
|
+
setServiceTier("standard");
|
|
219
|
+
setThinkingMode("disabled");
|
|
220
|
+
}}
|
|
221
|
+
disabled={isPending}
|
|
222
|
+
className={cn(
|
|
223
|
+
"stg:rounded-md stg:px-2 stg:py-1 stg:text-[0.65rem] stg:text-muted-foreground",
|
|
224
|
+
"stg:hover:text-foreground stg:focus-visible:outline-none stg:focus-visible:ring-2 stg:focus-visible:ring-ring",
|
|
225
|
+
"stg:disabled:pointer-events-none stg:disabled:opacity-50",
|
|
226
|
+
)}
|
|
227
|
+
>
|
|
228
|
+
Reset to platform default
|
|
229
|
+
</button>
|
|
230
|
+
)}
|
|
231
|
+
</div>
|
|
232
|
+
<p className={hintClasses}>
|
|
233
|
+
Every conversation turn on this channel runs the pinned model.
|
|
234
|
+
Unpinned, the platform’s default engine and model apply.
|
|
235
|
+
</p>
|
|
236
|
+
</div>
|
|
237
|
+
|
|
238
|
+
{/* Budget — the one run bound that matters for an unattended
|
|
239
|
+
surface (DD-018 D-5). Clamped by the platform profile;
|
|
240
|
+
tool-round bounds stay API-only. */}
|
|
241
|
+
<div className="stg:space-y-1">
|
|
242
|
+
<label htmlFor={`${titleId}-budget`} className={labelClasses}>
|
|
243
|
+
Budget per run (USD){" "}
|
|
244
|
+
<span className="stg:font-normal stg:text-muted-foreground">(optional)</span>
|
|
245
|
+
</label>
|
|
246
|
+
<input
|
|
247
|
+
id={`${titleId}-budget`}
|
|
248
|
+
type="number"
|
|
249
|
+
min="0"
|
|
250
|
+
step="0.5"
|
|
251
|
+
inputMode="decimal"
|
|
252
|
+
value={budgetUsd}
|
|
253
|
+
onChange={(e) => setBudgetUsd(e.target.value)}
|
|
254
|
+
placeholder="Platform default"
|
|
255
|
+
disabled={isPending}
|
|
256
|
+
className={cn(inputClasses, "stg:max-w-40")}
|
|
257
|
+
/>
|
|
258
|
+
<p className={hintClasses}>
|
|
259
|
+
Caps each turn’s estimated spend. The platform profile
|
|
260
|
+
bounds this value; the lower cap wins.
|
|
261
|
+
</p>
|
|
262
|
+
</div>
|
|
263
|
+
|
|
264
|
+
{error && (
|
|
265
|
+
<div
|
|
266
|
+
role="alert"
|
|
267
|
+
className="stg:rounded-md stg:border stg:border-destructive/30 stg:bg-destructive-subtle stg:px-3 stg:py-2 stg:text-xs stg:text-destructive"
|
|
268
|
+
>
|
|
269
|
+
{getUserMessage(error)}
|
|
270
|
+
</div>
|
|
271
|
+
)}
|
|
272
|
+
</div>
|
|
273
|
+
|
|
274
|
+
<div className="stg:flex stg:justify-end stg:gap-2 stg:border-t stg:border-border stg:px-5 stg:py-3">
|
|
275
|
+
<Button
|
|
276
|
+
variant="ghost"
|
|
277
|
+
size="sm"
|
|
278
|
+
onClick={() => {
|
|
279
|
+
clearError();
|
|
280
|
+
onClose();
|
|
281
|
+
}}
|
|
282
|
+
disabled={isPending}
|
|
283
|
+
>
|
|
284
|
+
Cancel
|
|
285
|
+
</Button>
|
|
286
|
+
<Button
|
|
287
|
+
variant="primary"
|
|
288
|
+
size="sm"
|
|
289
|
+
onClick={() => void handleSave()}
|
|
290
|
+
disabled={isPending}
|
|
291
|
+
>
|
|
292
|
+
Save
|
|
293
|
+
</Button>
|
|
294
|
+
</div>
|
|
295
|
+
</div>
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Assemble the run_config the save writes. The schedule form's
|
|
301
|
+
* `buildRunConfig` contract (#357/#772) plus the edit-surface rule from
|
|
302
|
+
* the schedule detail view's `normalizeRunConfig`: fields this editor
|
|
303
|
+
* does not own (`max_tool_rounds`) are preserved verbatim, and an
|
|
304
|
+
* all-empty result clears the block ("empty = inherit").
|
|
305
|
+
*/
|
|
306
|
+
function buildRunConfig(
|
|
307
|
+
modelName: string,
|
|
308
|
+
budgetUsd: string,
|
|
309
|
+
serviceTier: ServiceTierOption,
|
|
310
|
+
thinkingMode: ThinkingModeOption,
|
|
311
|
+
storedMaxToolRounds: number,
|
|
312
|
+
): RunConfigInput | undefined {
|
|
313
|
+
const model = modelName.trim();
|
|
314
|
+
const cost = Number.parseFloat(budgetUsd);
|
|
315
|
+
|
|
316
|
+
const config: RunConfigInput = {};
|
|
317
|
+
if (model !== "") config.modelName = model;
|
|
318
|
+
if (Number.isFinite(cost) && cost > 0) config.maxCostUsd = cost;
|
|
319
|
+
if (storedMaxToolRounds > 0) config.maxToolRounds = storedMaxToolRounds;
|
|
320
|
+
// Carried only when the user actively chose the variant AND pinned a
|
|
321
|
+
// model (both are per-model; the server refuses them without one). An
|
|
322
|
+
// untouched switch stays absent — unspecified-vs-explicit is a
|
|
323
|
+
// load-bearing ledger distinction (#357/#772).
|
|
324
|
+
if (serviceTier === "fast" && model !== "") {
|
|
325
|
+
config.serviceTier = toProtoServiceTier(serviceTier);
|
|
326
|
+
}
|
|
327
|
+
if (thinkingMode === "enabled" && model !== "") {
|
|
328
|
+
config.thinkingMode = toProtoThinkingMode(thinkingMode);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return Object.keys(config).length > 0 ? config : undefined;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const labelClasses = "stg:block stg:text-xs stg:font-medium stg:text-foreground";
|
|
335
|
+
|
|
336
|
+
const hintClasses = "stg:text-[0.65rem] stg:text-muted-foreground";
|
|
337
|
+
|
|
338
|
+
const inputClasses = cn(
|
|
339
|
+
"stg:w-full stg:rounded-md stg:border stg:border-input stg:bg-background stg:px-2.5 stg:py-1.5 stg:text-xs stg:text-foreground",
|
|
340
|
+
"stg:placeholder:text-muted-foreground",
|
|
341
|
+
"stg:focus-visible:outline-none stg:focus-visible:ring-1 stg:focus-visible:ring-ring",
|
|
342
|
+
"stg:disabled:pointer-events-none stg:disabled:opacity-50",
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
function CloseIcon() {
|
|
346
|
+
return (
|
|
347
|
+
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" aria-hidden="true">
|
|
348
|
+
<path d="m3 3 8 8M11 3l-8 8" />
|
|
349
|
+
</svg>
|
|
350
|
+
);
|
|
351
|
+
}
|