diff --git a/apps/backend/cmd/kandev/adapters.go b/apps/backend/cmd/kandev/adapters.go index 03f4171df0..d92a62056e 100644 --- a/apps/backend/cmd/kandev/adapters.go +++ b/apps/backend/cmd/kandev/adapters.go @@ -208,7 +208,6 @@ func (a *lifecycleAdapter) ResolveAgentProfile(ctx context.Context, profileID st Model: info.Model, AutoApprove: info.AutoApprove, DangerouslySkipPermissions: info.DangerouslySkipPermissions, - Plan: info.Plan, CLIPassthrough: info.CLIPassthrough, }, nil } diff --git a/apps/backend/internal/agent/lifecycle/manager.go b/apps/backend/internal/agent/lifecycle/manager.go index 147ed7c862..d4ae19d236 100644 --- a/apps/backend/internal/agent/lifecycle/manager.go +++ b/apps/backend/internal/agent/lifecycle/manager.go @@ -737,9 +737,6 @@ func (m *Manager) Launch(ctx context.Context, req *LaunchRequest) (*AgentExecuti if profileInfo.AutoApprove { reqWithWorktree.Env["AGENTCTL_AUTO_APPROVE_PERMISSIONS"] = "true" } - if profileInfo.Plan != "" { - reqWithWorktree.Env["AGENT_PLAN"] = profileInfo.Plan - } } // 7. Launch via runtime - creates agentctl instance with workspace access only diff --git a/apps/backend/internal/agent/lifecycle/profile_resolver.go b/apps/backend/internal/agent/lifecycle/profile_resolver.go index 730705eb61..7f2c9de93a 100644 --- a/apps/backend/internal/agent/lifecycle/profile_resolver.go +++ b/apps/backend/internal/agent/lifecycle/profile_resolver.go @@ -53,6 +53,5 @@ func (r *StoreProfileResolver) ResolveProfile(ctx context.Context, profileID str DangerouslySkipPermissions: profile.DangerouslySkipPermissions, AllowIndexing: profile.AllowIndexing, CLIPassthrough: profile.CLIPassthrough, - Plan: profile.Plan, }, nil } diff --git a/apps/backend/internal/agent/lifecycle/profile_resolver_test.go b/apps/backend/internal/agent/lifecycle/profile_resolver_test.go index f0871e6bae..d93cd15206 100644 --- a/apps/backend/internal/agent/lifecycle/profile_resolver_test.go +++ b/apps/backend/internal/agent/lifecycle/profile_resolver_test.go @@ -116,7 +116,6 @@ func TestStoreProfileResolver_ResolveProfile_Success(t *testing.T) { Model: "claude-3.5-sonnet", AutoApprove: true, DangerouslySkipPermissions: false, - Plan: "pro", }, nil }, GetAgentFn: func(ctx context.Context, id string) (*models.Agent, error) { @@ -159,9 +158,6 @@ func TestStoreProfileResolver_ResolveProfile_Success(t *testing.T) { if info.DangerouslySkipPermissions != false { t.Error("expected DangerouslySkipPermissions to be false") } - if info.Plan != "pro" { - t.Errorf("expected Plan 'pro', got '%s'", info.Plan) - } } func TestStoreProfileResolver_ResolveProfile_ProfileNotFound(t *testing.T) { diff --git a/apps/backend/internal/agent/lifecycle/types.go b/apps/backend/internal/agent/lifecycle/types.go index 8aeb0c6405..568239c13f 100644 --- a/apps/backend/internal/agent/lifecycle/types.go +++ b/apps/backend/internal/agent/lifecycle/types.go @@ -155,7 +155,6 @@ type AgentProfileInfo struct { DangerouslySkipPermissions bool AllowIndexing bool CLIPassthrough bool - Plan string } // ProfileResolver resolves agent profile IDs to profile information diff --git a/apps/backend/internal/agent/settings/controller/controller.go b/apps/backend/internal/agent/settings/controller/controller.go index 924c1ea151..c9322be056 100644 --- a/apps/backend/internal/agent/settings/controller/controller.go +++ b/apps/backend/internal/agent/settings/controller/controller.go @@ -317,7 +317,6 @@ func (c *Controller) EnsureInitialAgentProfiles(ctx context.Context) error { AgentID: agent.ID, Name: defaultAgentProfileName, Model: defaultModel, - Plan: "", AgentDisplayName: displayName, AutoApprove: autoApprove, AllowIndexing: allowIndexing, @@ -405,7 +404,6 @@ type CreateAgentProfileRequest struct { Model string AutoApprove bool DangerouslySkipPermissions bool - Plan string } func (c *Controller) CreateAgent(ctx context.Context, req CreateAgentRequest) (*dto.AgentDTO, error) { @@ -458,7 +456,6 @@ func (c *Controller) CreateAgent(ctx context.Context, req CreateAgentRequest) (* Model: profileReq.Model, AutoApprove: profileReq.AutoApprove, DangerouslySkipPermissions: profileReq.DangerouslySkipPermissions, - Plan: profileReq.Plan, } if err := c.repo.CreateAgentProfile(ctx, profile); err != nil { return nil, err @@ -519,7 +516,6 @@ type CreateProfileRequest struct { DangerouslySkipPermissions bool AllowIndexing bool CLIPassthrough bool - Plan string } func (c *Controller) CreateProfile(ctx context.Context, req CreateProfileRequest) (*dto.AgentProfileDTO, error) { @@ -544,7 +540,6 @@ func (c *Controller) CreateProfile(ctx context.Context, req CreateProfileRequest DangerouslySkipPermissions: req.DangerouslySkipPermissions, AllowIndexing: req.AllowIndexing, CLIPassthrough: req.CLIPassthrough, - Plan: req.Plan, } if err := c.repo.CreateAgentProfile(ctx, profile); err != nil { return nil, err @@ -561,7 +556,6 @@ type UpdateProfileRequest struct { DangerouslySkipPermissions *bool AllowIndexing *bool CLIPassthrough *bool - Plan *string } func (c *Controller) UpdateProfile(ctx context.Context, req UpdateProfileRequest) (*dto.AgentProfileDTO, error) { @@ -591,9 +585,6 @@ func (c *Controller) UpdateProfile(ctx context.Context, req UpdateProfileRequest if req.CLIPassthrough != nil { profile.CLIPassthrough = *req.CLIPassthrough } - if req.Plan != nil { - profile.Plan = *req.Plan - } if err := c.repo.UpdateAgentProfile(ctx, profile); err != nil { return nil, err } @@ -656,7 +647,6 @@ func toProfileDTO(profile *models.AgentProfile) dto.AgentProfileDTO { DangerouslySkipPermissions: profile.DangerouslySkipPermissions, AllowIndexing: profile.AllowIndexing, CLIPassthrough: profile.CLIPassthrough, - Plan: profile.Plan, CreatedAt: profile.CreatedAt, UpdatedAt: profile.UpdatedAt, } diff --git a/apps/backend/internal/agent/settings/dto/dto.go b/apps/backend/internal/agent/settings/dto/dto.go index 60dd835c17..36ed52c392 100644 --- a/apps/backend/internal/agent/settings/dto/dto.go +++ b/apps/backend/internal/agent/settings/dto/dto.go @@ -16,7 +16,6 @@ type AgentProfileDTO struct { DangerouslySkipPermissions bool `json:"dangerously_skip_permissions"` AllowIndexing bool `json:"allow_indexing"` CLIPassthrough bool `json:"cli_passthrough"` - Plan string `json:"plan"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } diff --git a/apps/backend/internal/agent/settings/handlers/handlers.go b/apps/backend/internal/agent/settings/handlers/handlers.go index 69d1146a73..816689a7c5 100644 --- a/apps/backend/internal/agent/settings/handlers/handlers.go +++ b/apps/backend/internal/agent/settings/handlers/handlers.go @@ -106,7 +106,6 @@ type createAgentProfileRequest struct { Model string `json:"model"` AutoApprove bool `json:"auto_approve"` DangerouslySkipPermissions bool `json:"dangerously_skip_permissions"` - Plan string `json:"plan"` } func (h *Handlers) httpCreateAgent(c *gin.Context) { @@ -130,7 +129,6 @@ func (h *Handlers) httpCreateAgent(c *gin.Context) { Model: profile.Model, AutoApprove: profile.AutoApprove, DangerouslySkipPermissions: profile.DangerouslySkipPermissions, - Plan: profile.Plan, }) } resp, err := h.controller.CreateAgent(c.Request.Context(), controller.CreateAgentRequest{ @@ -286,7 +284,6 @@ type createProfileRequest struct { DangerouslySkipPermissions bool `json:"dangerously_skip_permissions"` AllowIndexing bool `json:"allow_indexing"` CLIPassthrough bool `json:"cli_passthrough"` - Plan string `json:"plan"` } func (h *Handlers) httpCreateProfile(c *gin.Context) { @@ -307,7 +304,6 @@ func (h *Handlers) httpCreateProfile(c *gin.Context) { DangerouslySkipPermissions: body.DangerouslySkipPermissions, AllowIndexing: body.AllowIndexing, CLIPassthrough: body.CLIPassthrough, - Plan: body.Plan, }) if err != nil { h.logger.Error("failed to create profile", zap.Error(err)) @@ -330,7 +326,6 @@ type updateProfileRequest struct { DangerouslySkipPermissions *bool `json:"dangerously_skip_permissions,omitempty"` AllowIndexing *bool `json:"allow_indexing,omitempty"` CLIPassthrough *bool `json:"cli_passthrough,omitempty"` - Plan *string `json:"plan,omitempty"` } func (h *Handlers) httpUpdateProfile(c *gin.Context) { @@ -351,7 +346,6 @@ func (h *Handlers) httpUpdateProfile(c *gin.Context) { DangerouslySkipPermissions: body.DangerouslySkipPermissions, AllowIndexing: body.AllowIndexing, CLIPassthrough: body.CLIPassthrough, - Plan: body.Plan, }) if err != nil { if err == controller.ErrAgentProfileNotFound { diff --git a/apps/backend/internal/agent/settings/models/models.go b/apps/backend/internal/agent/settings/models/models.go index 373b12740a..5faa4038d9 100644 --- a/apps/backend/internal/agent/settings/models/models.go +++ b/apps/backend/internal/agent/settings/models/models.go @@ -22,7 +22,6 @@ type AgentProfile struct { DangerouslySkipPermissions bool `json:"dangerously_skip_permissions"` AllowIndexing bool `json:"allow_indexing"` CLIPassthrough bool `json:"cli_passthrough"` - Plan string `json:"plan"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` DeletedAt *time.Time `json:"deleted_at,omitempty"` diff --git a/apps/backend/internal/agent/settings/store/sqlite.go b/apps/backend/internal/agent/settings/store/sqlite.go index 51bf432179..4894b465de 100644 --- a/apps/backend/internal/agent/settings/store/sqlite.go +++ b/apps/backend/internal/agent/settings/store/sqlite.go @@ -253,9 +253,9 @@ func (r *sqliteRepository) CreateAgentProfile(ctx context.Context, profile *mode profile.UpdatedAt = now _, err := r.db.ExecContext(ctx, ` INSERT INTO agent_profiles (id, agent_id, name, agent_display_name, model, auto_approve, dangerously_skip_permissions, allow_indexing, cli_passthrough, plan, created_at, updated_at, deleted_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, '', ?, ?, ?) `, profile.ID, profile.AgentID, profile.Name, profile.AgentDisplayName, profile.Model, sqlite.BoolToInt(profile.AutoApprove), - sqlite.BoolToInt(profile.DangerouslySkipPermissions), sqlite.BoolToInt(profile.AllowIndexing), sqlite.BoolToInt(profile.CLIPassthrough), profile.Plan, profile.CreatedAt, profile.UpdatedAt, profile.DeletedAt) + sqlite.BoolToInt(profile.DangerouslySkipPermissions), sqlite.BoolToInt(profile.AllowIndexing), sqlite.BoolToInt(profile.CLIPassthrough), profile.CreatedAt, profile.UpdatedAt, profile.DeletedAt) return err } @@ -263,10 +263,10 @@ func (r *sqliteRepository) UpdateAgentProfile(ctx context.Context, profile *mode profile.UpdatedAt = time.Now().UTC() result, err := r.db.ExecContext(ctx, ` UPDATE agent_profiles - SET name = ?, agent_display_name = ?, model = ?, auto_approve = ?, dangerously_skip_permissions = ?, allow_indexing = ?, cli_passthrough = ?, plan = ?, updated_at = ? + SET name = ?, agent_display_name = ?, model = ?, auto_approve = ?, dangerously_skip_permissions = ?, allow_indexing = ?, cli_passthrough = ?, updated_at = ? WHERE id = ? AND deleted_at IS NULL `, profile.Name, profile.AgentDisplayName, profile.Model, sqlite.BoolToInt(profile.AutoApprove), - sqlite.BoolToInt(profile.DangerouslySkipPermissions), sqlite.BoolToInt(profile.AllowIndexing), sqlite.BoolToInt(profile.CLIPassthrough), profile.Plan, profile.UpdatedAt, profile.ID) + sqlite.BoolToInt(profile.DangerouslySkipPermissions), sqlite.BoolToInt(profile.AllowIndexing), sqlite.BoolToInt(profile.CLIPassthrough), profile.UpdatedAt, profile.ID) if err != nil { return err } @@ -355,6 +355,7 @@ func scanAgentProfile(scanner interface { var skipPermissions int var allowIndexing int var cliPassthrough int + var plan string // unused, kept for backwards compatibility if err := scanner.Scan( &profile.ID, &profile.AgentID, @@ -365,7 +366,7 @@ func scanAgentProfile(scanner interface { &skipPermissions, &allowIndexing, &cliPassthrough, - &profile.Plan, + &plan, &profile.CreatedAt, &profile.UpdatedAt, &profile.DeletedAt, diff --git a/apps/backend/internal/orchestrator/executor/executor.go b/apps/backend/internal/orchestrator/executor/executor.go index f818c4ca6c..a052c72fbe 100644 --- a/apps/backend/internal/orchestrator/executor/executor.go +++ b/apps/backend/internal/orchestrator/executor/executor.go @@ -69,7 +69,6 @@ type AgentProfileInfo struct { Model string AutoApprove bool DangerouslySkipPermissions bool - Plan string CLIPassthrough bool } @@ -261,7 +260,6 @@ func (e *Executor) PrepareSession(ctx context.Context, task *v1.Task, agentProfi "model": profileInfo.Model, "auto_approve": profileInfo.AutoApprove, "dangerously_skip_permissions": profileInfo.DangerouslySkipPermissions, - "plan": profileInfo.Plan, "cli_passthrough": profileInfo.CLIPassthrough, } } else { diff --git a/apps/web/app/actions/agents.ts b/apps/web/app/actions/agents.ts index fcbd3ed97b..2f86f5ac48 100644 --- a/apps/web/app/actions/agents.ts +++ b/apps/web/app/actions/agents.ts @@ -51,7 +51,7 @@ export async function createAgentAction(payload: { auto_approve: boolean; dangerously_skip_permissions: boolean; allow_indexing: boolean; - plan: string; + cli_passthrough: boolean; }>; }): Promise { return fetchJson(`${apiBaseUrl}/api/v1/agents`, { @@ -83,7 +83,6 @@ export async function createAgentProfileAction( dangerously_skip_permissions: boolean; allow_indexing: boolean; cli_passthrough: boolean; - plan: string; } ): Promise { return fetchJson(`${apiBaseUrl}/api/v1/agents/${agentId}/profiles`, { @@ -94,7 +93,7 @@ export async function createAgentProfileAction( export async function updateAgentProfileAction( id: string, - payload: Partial> + payload: Partial> ): Promise { return fetchJson(`${apiBaseUrl}/api/v1/agent-profiles/${id}`, { method: 'PATCH', diff --git a/apps/web/app/settings/agents/[agentId]/page.tsx b/apps/web/app/settings/agents/[agentId]/page.tsx index 95c6c2fbc1..86411149f3 100644 --- a/apps/web/app/settings/agents/[agentId]/page.tsx +++ b/apps/web/app/settings/agents/[agentId]/page.tsx @@ -2,24 +2,14 @@ import { useEffect, useMemo, useState } from 'react'; import Link from 'next/link'; -import { useParams, useRouter } from 'next/navigation'; +import { useParams, useRouter, useSearchParams } from 'next/navigation'; import { IconPlus } from '@tabler/icons-react'; import { Button } from '@kandev/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@kandev/ui/card'; -import { Input } from '@kandev/ui/input'; -import { Label } from '@kandev/ui/label'; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@kandev/ui/select'; import { Separator } from '@kandev/ui/separator'; -import { Switch } from '@kandev/ui/switch'; -import { Textarea } from '@kandev/ui/textarea'; import { useToast } from '@/components/toast-provider'; import { UnsavedChangesBadge, UnsavedSaveButton } from '@/components/settings/unsaved-indicator'; +import { ProfileFormFields } from '@/components/settings/profile-form-fields'; import { createAgentAction, createAgentProfileAction, @@ -58,6 +48,7 @@ type AgentSetupFormProps = { savedAgent: Agent | null; discoveryAgent: AgentDiscovery | undefined; onToastError: (error: unknown) => void; + isCreateMode?: boolean; }; const createDraftProfile = ( @@ -75,7 +66,6 @@ const createDraftProfile = ( dangerously_skip_permissions: permissionSettings?.dangerously_skip_permissions?.default ?? false, allow_indexing: permissionSettings?.allow_indexing?.default ?? false, cli_passthrough: false, - plan: '', created_at: new Date().toISOString(), updated_at: new Date().toISOString(), isNew: true, @@ -109,6 +99,7 @@ function AgentSetupForm({ savedAgent, discoveryAgent, onToastError, + isCreateMode = false, }: AgentSetupFormProps) { const router = useRouter(); const settingsAgents = useAppStore((state) => state.settingsAgents.items); @@ -130,13 +121,17 @@ function AgentSetupForm({ }, [availableAgents, draftAgent.name]); const currentAgentModelConfig = useMemo(() => { - return currentAvailableAgent?.model_config ?? { default_model: '', available_models: [] }; + return currentAvailableAgent?.model_config ?? { default_model: '', available_models: [], supports_dynamic_models: false }; }, [currentAvailableAgent]); const permissionSettings = useMemo(() => { return currentAvailableAgent?.permission_settings ?? {}; }, [currentAvailableAgent]); + const passthroughConfig = useMemo(() => { + return currentAvailableAgent?.passthrough_config ?? null; + }, [currentAvailableAgent]); + const isProfileDirty = (draft: DraftProfile, saved?: AgentProfile) => { if (!saved) return true; return ( @@ -145,7 +140,7 @@ function AgentSetupForm({ draft.auto_approve !== saved.auto_approve || draft.dangerously_skip_permissions !== saved.dangerously_skip_permissions || draft.allow_indexing !== saved.allow_indexing || - draft.plan !== saved.plan + draft.cli_passthrough !== saved.cli_passthrough ); }; @@ -285,7 +280,7 @@ function AgentSetupForm({ auto_approve: profile.auto_approve, dangerously_skip_permissions: profile.dangerously_skip_permissions, allow_indexing: profile.allow_indexing ?? false, - plan: profile.plan, + cli_passthrough: profile.cli_passthrough ?? false, })), }); if (created.profiles.length === draftAgent.profiles.length) { @@ -353,7 +348,8 @@ function AgentSetupForm({ } const savedProfilesById = new Map(savedAgent.profiles.map((profile) => [profile.id, profile])); - const nextProfiles: AgentProfile[] = []; + // In create mode, preserve existing profiles; otherwise start fresh + const nextProfiles: AgentProfile[] = isCreateMode ? [...savedAgent.profiles] : []; for (const profile of draftAgent.profiles) { const savedProfile = savedProfilesById.get(profile.id); if (!savedProfile) { @@ -364,7 +360,6 @@ function AgentSetupForm({ dangerously_skip_permissions: profile.dangerously_skip_permissions, allow_indexing: profile.allow_indexing ?? false, cli_passthrough: profile.cli_passthrough ?? false, - plan: profile.plan, }); if (profile.mcp_config && profile.mcp_config.dirty && profile.mcp_config.servers.trim()) { try { @@ -387,17 +382,19 @@ function AgentSetupForm({ auto_approve: profile.auto_approve, dangerously_skip_permissions: profile.dangerously_skip_permissions, allow_indexing: profile.allow_indexing ?? false, - plan: profile.plan, }); nextProfiles.push(updatedProfile); continue; } nextProfiles.push(savedProfile); } - for (const savedProfile of savedAgent.profiles) { - const stillExists = draftAgent.profiles.some((profile) => profile.id === savedProfile.id); - if (!stillExists) { - await deleteAgentProfileAction(savedProfile.id); + // In create mode, don't delete existing profiles - we're only adding new ones + if (!isCreateMode) { + for (const savedProfile of savedAgent.profiles) { + const stillExists = draftAgent.profiles.some((profile) => profile.id === savedProfile.id); + if (!stillExists) { + await deleteAgentProfileAction(savedProfile.id); + } } } @@ -409,6 +406,10 @@ function AgentSetupForm({ }; upsertAgent(nextAgent); setDraftAgent(ensureProfiles(cloneAgent(nextAgent), resolveDisplayName(nextAgent.name), currentAgentModelConfig.default_model, permissionSettings)); + // In create mode, redirect to manage mode after successful save + if (isCreateMode) { + router.replace(`/settings/agents/${encodeURIComponent(savedAgent.name)}`); + } } setSaveStatus('success'); } catch (error) { @@ -430,7 +431,7 @@ function AgentSetupForm({

- Configure profiles and defaults for this agent. + {isCreateMode ? 'Create a new profile for this agent.' : 'Configure profiles and defaults for this agent.'}

@@ -440,7 +441,11 @@ function AgentSetupForm({
- {draftAgent.profiles[0]?.agent_display_name ?? draftAgent.name} Profiles + + {isCreateMode + ? `Create ${draftAgent.profiles[0]?.agent_display_name ?? draftAgent.name} Profile` + : `${draftAgent.profiles[0]?.agent_display_name ?? draftAgent.name} Profiles`} + {isAgentDirty && }
- )} - - -
- - {currentAgentModelConfig.available_models.length > 0 ? ( - - ) : ( - - handleProfileChange(profile.id, { model: event.target.value }) - } - placeholder="model-id" - /> - )} -
- -
- -