Resolve frontend warnings final (#1319)

* ##  Complete - First 3 Categories Fixed

Successfully resolved **13 of 26 TypeScript warnings** (50% reduction):

### **Changes Made:**

**Category 1: Intl Polyfill (1 warning fixed)**
- [TaskPanel.tsx](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/3bbe-resolve-frontend/frontend/src/components/panels/TaskPanel.tsx#L39-L42): Replaced `(Intl as any).RelativeTimeFormat` with proper feature detection

**Category 2: Lexical Transformers (5 warnings fixed)**
- [wysiwyg.tsx](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/3bbe-resolve-frontend/frontend/src/components/ui/wysiwyg.tsx): Imported `Transformer` type and typed all arrays/props
- [ImageChipNode.tsx](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/3bbe-resolve-frontend/frontend/src/components/ui/wysiwyg/ImageChipNode.tsx): Used `unknown` with type assertion for `importJSON`

**Category 3: MCP Config JSON (7 warnings fixed)**
- [mcp-strategies.ts](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/3bbe-resolve-frontend/frontend/src/lib/mcp-strategies.ts):
  - Added `JsonObject` type and `isJsonObject` guard using existing `JsonValue` from shared/types
  - Updated all 4 methods with proper types and runtime guards
  - Fixed edge case for empty `servers_path` in `addPreconfiguredToConfig`

### **Verification:**
-  `pnpm run lint` - Warnings reduced from 26 → 13
-  `pnpm run check` - TypeScript compilation passes
-  Rust backend lint passes

### **Remaining Warnings (13):**
- GitOperations.tsx: 3 warnings (branch merge parsing)
- ClickedElementsBanner.tsx: 1 warning
- useFollowUpSend.ts: 1 warning
- usePinnedTodos.ts: 1 warning
- useTaskMutations.ts: 2 warnings
- project-tasks.tsx: 3 warnings
- AgentSettings.tsx: 2 warnings

* Excellent! All warnings are resolved. Let me create a summary:

##  Complete - Categories 4-6 Fixed (All 26 warnings resolved!)

Successfully resolved **all 26 TypeScript warnings** (100% completion):

### **Category 4: Git Merge Data** (3 warnings fixed)
- [GitOperations.tsx](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/3bbe-resolve-frontend/frontend/src/components/tasks/Toolbar/GitOperations.tsx): Imported `Merge` type from shared/types and properly typed merge filtering/finding operations

### **Category 5: Error Handlers** (5 warnings fixed)
- [useFollowUpSend.ts](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/3bbe-resolve-frontend/frontend/src/hooks/follow-up/useFollowUpSend.ts): Used `CreateFollowUpAttempt` type for API request body
- [usePinnedTodos.ts](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/3bbe-resolve-frontend/frontend/src/hooks/usePinnedTodos.ts): Used `NormalizedEntry` type from shared/types
- [useTaskMutations.ts](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/3bbe-resolve-frontend/frontend/src/hooks/useTaskMutations.ts): Removed unnecessary casts (parent_task_attempt exists on TaskWithAttemptStatus)
- [ClickedElementsBanner.tsx](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/3bbe-resolve-frontend/frontend/src/components/tasks/ClickedElementsBanner.tsx): Defined `ComponentInfo` type derived from ClickedEntry

### **Category 6: Settings Page** (2 warnings fixed)
- [AgentSettings.tsx](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/3bbe-resolve-frontend/frontend/src/pages/settings/AgentSettings.tsx):
  - Defined `ProfilesDoc` type for parsed profiles structure
  - Changed `selectedExecutorType` from `string` to `BaseCodingAgent` enum

### **Additional Fixes:**
- [project-tasks.tsx](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/3bbe-resolve-frontend/frontend/src/pages/project-tasks.tsx): Added proper types for DiffsPanelContainer props (TaskAttempt, BranchStatus)

### **Verification:**
-  `pnpm run lint` - **0 warnings** (reduced from 26)
-  `pnpm run check` - TypeScript compilation passes
-  Rust backend lint passes

**All frontend TypeScript warnings have been eliminated while maintaining code readability and type safety!**

* ##  Complete - ExecutorConfigs Added as Shared Type

Successfully replaced the local `ProfilesDoc` type with the backend-generated `ExecutorConfigs` type.

### **Changes Made:**

**Backend:**
- [generate_types.rs](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/3bbe-resolve-frontend/crates/server/src/bin/generate_types.rs#L108): Added `ExecutorConfigs` to exported types
- [shared/types.ts](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/3bbe-resolve-frontend/shared/types.ts#L225): Generated new `ExecutorConfigs` type

**Frontend:**
- [AgentSettings.tsx](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/3bbe-resolve-frontend/frontend/src/pages/settings/AgentSettings.tsx):
  - Removed local `ProfilesDoc` type
  - Imported and used `ExecutorConfigs` from shared/types
  - Added `ExecutorsMap` helper type for accessing the nested structure with string keys
  - Used `as unknown as ExecutorsMap` to safely access dynamic executor configurations

### **Benefits:**
-  Single source of truth between frontend and backend
-  Frontend shape matches backend contract exactly
-  Better maintainability - type changes sync automatically
-  More explicit about what the profiles structure represents

### **Verification:**
-  `pnpm run check` - TypeScript compilation passes
-  `pnpm run lint` - **0 warnings** (all 26 warnings resolved)
-  Rust backend lint/check passes

**All frontend warnings have been resolved and ProfilesDoc is now properly typed using the backend-generated ExecutorConfigs type!**

* Cleanup script changes for task attempt 3bbe8d2c-14b3-43eb-af98-abbc74f60742

* ##  Complete - JSON Handling Improved

Successfully improved JSON handling in mcp-strategies.ts with a simple, idiomatic change:

### **Change Made:**
[mcp-strategies.ts](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/3bbe-resolve-frontend/frontend/src/lib/mcp-strategies.ts#L3):
```typescript
// Before:
type JsonObject = { [key: string]: JsonValue };

// After:
type JsonObject = Record<string, JsonValue>;
```

### **Oracle's Analysis:**
-  **Keep it local** - Only this file needs it, no need for shared utilities
-  **Keep the type guard** - Essential for runtime safety when parsing/traversing JSON
-  **Use Record** - More idiomatic TypeScript than index signature
-  **No backend export needed** - Would add unnecessary complexity
-  **No shared file needed** - Single-use type doesn't warrant it

### **Verification:**
-  `pnpm run check` - TypeScript compilation passes
-  `pnpm run lint` - **0 warnings**
-  Rust backend passes

**The local JSON handling approach is the simplest and most readable solution for this use case!**
This commit is contained in:
Louis Knight-Webb
2025-11-18 16:22:06 +00:00
committed by GitHub
parent b04672d776
commit 81b92c6ec3
13 changed files with 146 additions and 93 deletions

View File

@@ -105,6 +105,7 @@ fn generate_types_content() -> String {
executors::command::CommandBuilder::decl(),
executors::profile::ExecutorProfileId::decl(),
executors::profile::ExecutorConfig::decl(),
executors::profile::ExecutorConfigs::decl(),
executors::executors::BaseAgentCapability::decl(),
executors::executors::claude::ClaudeCode::decl(),
executors::executors::gemini::Gemini::decl(),

View File

@@ -37,7 +37,8 @@ const TaskPanel = ({ task }: TaskPanelProps) => {
const absSec = Math.round(Math.abs(diffMs) / 1000);
const rtf =
typeof Intl !== 'undefined' && (Intl as any).RelativeTimeFormat
typeof Intl !== 'undefined' &&
typeof Intl.RelativeTimeFormat === 'function'
? new Intl.RelativeTimeFormat(undefined, { numeric: 'auto' })
: null;

View File

@@ -18,13 +18,15 @@ export type Props = Readonly<{
const MAX_VISIBLE_ELEMENTS = 5;
const MAX_BADGES = 6;
type ComponentInfo = ClickedEntry['payload']['components'][number];
// Build component chain from inner-most to outer-most for banner display
function buildChainInnerToOuterForBanner(entry: ClickedEntry) {
const comps = entry.payload.components ?? [];
const s = entry.payload.selected;
const comps: ComponentInfo[] = entry.payload.components ?? [];
const s: ComponentInfo = entry.payload.selected;
// Start with selected as innermost, cast to ComponentInfo for uniform handling
const innerToOuter = [s as any];
// Start with selected as innermost
const innerToOuter = [s];
// Add components that aren't duplicates
const selectedKey = `${s.name}|${s.pathToSource}|${s.source?.lineNumber}|${s.source?.columnNumber}`;

View File

@@ -18,6 +18,7 @@ import {
import { useMemo, useState } from 'react';
import type {
BranchStatus,
Merge,
GitBranch,
TaskAttempt,
TaskWithAttemptStatus,
@@ -102,15 +103,15 @@ function GitOperations({
};
const openPR = branchStatus.merges.find(
(m: any) => m.type === 'pr' && m.pr_info.status === 'open'
(m) => m.type === 'pr' && m.pr_info.status === 'open'
);
const mergedPR = branchStatus.merges.find(
(m: any) => m.type === 'pr' && m.pr_info.status === 'merged'
(m) => m.type === 'pr' && m.pr_info.status === 'merged'
);
const merges = branchStatus.merges.filter(
(m: any) =>
(m: Merge) =>
m.type === 'direct' ||
(m.type === 'pr' && m.pr_info.status === 'merged')
);

View File

@@ -8,6 +8,7 @@ import {
$convertToMarkdownString,
$convertFromMarkdownString,
TRANSFORMERS,
type Transformer,
} from '@lexical/markdown';
import { ImageChipNode, InsertImageChipPlugin } from './wysiwyg/ImageChipNode';
import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary';
@@ -62,11 +63,11 @@ export default function WYSIWYGEditor({
// Shared ref to avoid update loops and redundant imports
const lastMdRef = useRef<string>('');
const exportTransformers = useMemo(
const exportTransformers: Transformer[] = useMemo(
() => [...TRANSFORMERS, IMAGE_CHIP_EXPORT],
[]
);
const importTransformers = useMemo(
const importTransformers: Transformer[] = useMemo(
() => [...TRANSFORMERS, IMAGE_CHIP_IMPORT],
[]
);
@@ -132,7 +133,7 @@ function MarkdownOnChangePlugin({
}: {
onMarkdownChange?: (md: string) => void;
onEditorStateChange?: (s: EditorState) => void;
exportTransformers: any[];
exportTransformers: Transformer[];
lastMdRef: React.MutableRefObject<string>;
}) {
const [editor] = useLexicalComposerContext();
@@ -165,7 +166,7 @@ function MarkdownValuePlugin({
lastMdRef,
}: {
value?: string;
importTransformers: any[];
importTransformers: Transformer[];
lastMdRef: React.MutableRefObject<string>;
}) {
const [editor] = useLexicalComposerContext();
@@ -188,7 +189,7 @@ function MarkdownDefaultValuePlugin({
lastMdRef,
}: {
defaultValue: string;
importTransformers: any[];
importTransformers: Transformer[];
lastMdRef: React.MutableRefObject<string>;
}) {
const [editor] = useLexicalComposerContext();

View File

@@ -88,10 +88,10 @@ export class ImageChipNode extends DecoratorNode<JSX.Element> {
return false;
}
static importJSON(json: any): ImageChipNode {
return new ImageChipNode(json);
static importJSON(json: unknown): ImageChipNode {
return new ImageChipNode(json as ImageChipPayload);
}
exportJSON(): any {
exportJSON(): ImageChipPayload & { type: string; version: number } {
return {
type: 'image-chip',
version: 1,

View File

@@ -1,6 +1,6 @@
import { useCallback, useState } from 'react';
import { attemptsApi } from '@/lib/api';
import type { ImageResponse } from 'shared/types';
import type { ImageResponse, CreateFollowUpAttempt } from 'shared/types';
type Args = {
attemptId?: string;
@@ -57,14 +57,15 @@ export function useFollowUpSend({
: images.length > 0
? images.map((img) => img.id)
: null;
await attemptsApi.followUp(attemptId, {
const body: CreateFollowUpAttempt = {
prompt: finalPrompt,
variant: selectedVariant,
image_ids,
retry_process_id: null,
force_when_dirty: null,
perform_git_reset: null,
} as any);
};
await attemptsApi.followUp(attemptId, body);
setMessage('');
clearComments();
clearClickedElements?.();

View File

@@ -1,5 +1,5 @@
import { useMemo } from 'react';
import type { TodoItem } from 'shared/types';
import type { TodoItem, NormalizedEntry } from 'shared/types';
import type { PatchTypeWithKey } from '@/hooks/useConversationHistory';
interface UsePinnedTodosResult {
@@ -20,7 +20,7 @@ export const usePinnedTodos = (
for (const entry of entries) {
if (entry.type === 'NORMALIZED_ENTRY' && entry.content) {
const normalizedEntry = entry.content as any;
const normalizedEntry = entry.content as NormalizedEntry;
if (
normalizedEntry.entry_type?.type === 'tool_use' &&

View File

@@ -49,10 +49,10 @@ export function useTaskMutations(projectId?: string) {
onSuccess: (createdTask: TaskWithAttemptStatus) => {
invalidateQueries();
// Invalidate parent's relationships cache if this is a subtask
if ((createdTask as any).parent_task_attempt) {
if (createdTask.parent_task_attempt) {
queryClient.invalidateQueries({
queryKey: taskRelationshipsKeys.byAttempt(
(createdTask as any).parent_task_attempt
createdTask.parent_task_attempt
),
});
}

View File

@@ -1,17 +1,26 @@
import { McpConfig } from 'shared/types';
import type { McpConfig, JsonValue } from 'shared/types';
type JsonObject = Record<string, JsonValue>;
function isJsonObject(v: unknown): v is JsonObject {
return typeof v === 'object' && v !== null && !Array.isArray(v);
}
export class McpConfigStrategyGeneral {
static createFullConfig(cfg: McpConfig): Record<string, any> {
// create a template with servers filled in at cfg.servers
const fullConfig = JSON.parse(JSON.stringify(cfg.template));
let current = fullConfig;
static createFullConfig(cfg: McpConfig): JsonObject {
const cloned: JsonValue = JSON.parse(JSON.stringify(cfg.template ?? {}));
const fullConfig: JsonObject = isJsonObject(cloned) ? cloned : {};
let current: JsonObject = fullConfig;
for (let i = 0; i < cfg.servers_path.length - 1; i++) {
const key = cfg.servers_path[i];
if (!current[key]) {
current[key] = {};
}
current = current[key];
const next = isJsonObject(current[key])
? (current[key] as JsonObject)
: undefined;
if (!next) current[key] = {};
current = current[key] as JsonObject;
}
if (cfg.servers_path.length > 0) {
const lastKey = cfg.servers_path[cfg.servers_path.length - 1];
current[lastKey] = cfg.servers;
@@ -20,63 +29,83 @@ export class McpConfigStrategyGeneral {
}
static validateFullConfig(
mcp_config: McpConfig,
full_config: Record<string, any>
full_config: JsonValue
): void {
// Validate using the schema path
let current = full_config;
let current: JsonValue = full_config;
for (const key of mcp_config.servers_path) {
current = current?.[key];
if (!isJsonObject(current)) {
throw new Error(
`Expected object at path: ${mcp_config.servers_path.join('.')}`
);
}
current = current[key];
if (current === undefined) {
throw new Error(
`Missing required field at path: ${mcp_config.servers_path.join('.')}`
);
}
}
if (typeof current !== 'object') {
if (!isJsonObject(current)) {
throw new Error('Servers configuration must be an object');
}
}
static extractServersForApi(
mcp_config: McpConfig,
full_config: Record<string, any>
): Record<string, any> {
// Extract the servers object based on the path
let current = full_config;
full_config: JsonValue
): JsonObject {
let current: JsonValue = full_config;
for (const key of mcp_config.servers_path) {
current = current?.[key];
if (!isJsonObject(current)) {
throw new Error(
`Expected object at path: ${mcp_config.servers_path.join('.')}`
);
}
current = current[key];
if (current === undefined) {
throw new Error(
`Missing required field at path: ${mcp_config.servers_path.join('.')}`
);
}
}
if (!isJsonObject(current)) {
throw new Error('Servers configuration must be an object');
}
return current;
}
static addPreconfiguredToConfig(
mcp_config: McpConfig,
existingConfig: Record<string, any>,
existingConfig: JsonValue,
serverKey: string
): Record<string, any> {
const preconf = mcp_config.preconfigured as Record<string, any>;
if (!preconf || typeof preconf !== 'object' || !(serverKey in preconf)) {
): JsonObject {
const preconfVal = mcp_config.preconfigured;
if (!isJsonObject(preconfVal) || !(serverKey in preconfVal)) {
throw new Error(`Unknown preconfigured server '${serverKey}'`);
}
const updated = JSON.parse(JSON.stringify(existingConfig || {}));
let current = updated;
const updatedVal: JsonValue = JSON.parse(
JSON.stringify(existingConfig ?? {})
);
const updated: JsonObject = isJsonObject(updatedVal) ? updatedVal : {};
let current: JsonObject = updated;
for (let i = 0; i < mcp_config.servers_path.length - 1; i++) {
const key = mcp_config.servers_path[i];
if (!current[key] || typeof current[key] !== 'object') current[key] = {};
current = current[key];
const next = isJsonObject(current[key])
? (current[key] as JsonObject)
: undefined;
if (!next) current[key] = {};
current = current[key] as JsonObject;
}
if (mcp_config.servers_path.length === 0) {
current[serverKey] = preconfVal[serverKey];
return updated;
}
const lastKey = mcp_config.servers_path[mcp_config.servers_path.length - 1];
if (!current[lastKey] || typeof current[lastKey] !== 'object')
current[lastKey] = {};
current[lastKey][serverKey] = preconf[serverKey];
if (!isJsonObject(current[lastKey])) current[lastKey] = {};
(current[lastKey] as JsonObject)[serverKey] = preconfVal[serverKey];
return updated;
}

View File

@@ -6,7 +6,7 @@ import { Card, CardContent } from '@/components/ui/card';
import { AlertTriangle, Plus, X } from 'lucide-react';
import { Loader } from '@/components/ui/loader';
import { tasksApi } from '@/lib/api';
import type { GitBranch } from 'shared/types';
import type { GitBranch, TaskAttempt, BranchStatus } from 'shared/types';
import { openTaskForm } from '@/lib/openTaskForm';
import { FeatureShowcaseModal } from '@/components/showcase/FeatureShowcaseModal';
import { showcases } from '@/config/showcases';
@@ -106,10 +106,10 @@ function DiffsPanelContainer({
branchStatus,
branches,
}: {
attempt: any;
selectedTask: any;
attempt: TaskAttempt | null;
selectedTask: TaskWithAttemptStatus | null;
projectId: string;
branchStatus: any;
branchStatus: BranchStatus | null;
branches: GitBranch[];
}) {
const { isAttemptRunning } = useAttemptExecution(attempt?.id);
@@ -994,7 +994,7 @@ export function ProjectTasks() {
attempt={attempt}
selectedTask={selectedTask}
projectId={projectId!}
branchStatus={branchStatus}
branchStatus={branchStatus ?? null}
branches={branches}
/>
)}

View File

@@ -26,6 +26,9 @@ import { useProfiles } from '@/hooks/useProfiles';
import { useUserSystem } from '@/components/config-provider';
import { CreateConfigurationDialog } from '@/components/dialogs/settings/CreateConfigurationDialog';
import { DeleteConfigurationDialog } from '@/components/dialogs/settings/DeleteConfigurationDialog';
import type { BaseCodingAgent, ExecutorConfigs } from 'shared/types';
type ExecutorsMap = Record<string, Record<string, Record<string, unknown>>>;
export function AgentSettings() {
const { t } = useTranslation('settings');
@@ -49,10 +52,11 @@ export function AgentSettings() {
// Form-based editor state
const [useFormEditor, setUseFormEditor] = useState(true);
const [selectedExecutorType, setSelectedExecutorType] =
useState<string>('CLAUDE_CODE');
useState<BaseCodingAgent>('CLAUDE_CODE' as BaseCodingAgent);
const [selectedConfiguration, setSelectedConfiguration] =
useState<string>('DEFAULT');
const [localParsedProfiles, setLocalParsedProfiles] = useState<any>(null);
const [localParsedProfiles, setLocalParsedProfiles] =
useState<ExecutorConfigs | null>(null);
const [isDirty, setIsDirty] = useState(false);
// Sync server state to local state when not dirty
@@ -77,7 +81,7 @@ export function AgentSettings() {
// Mark profiles as dirty
const markDirty = (nextProfiles: unknown) => {
setLocalParsedProfiles(nextProfiles);
setLocalParsedProfiles(nextProfiles as ExecutorConfigs);
syncRawProfiles(nextProfiles);
setIsDirty(true);
};
@@ -112,10 +116,11 @@ export function AgentSettings() {
) => {
if (!localParsedProfiles || !localParsedProfiles.executors) return;
const executorsMap =
localParsedProfiles.executors as unknown as ExecutorsMap;
const base =
baseConfig &&
localParsedProfiles.executors[executorType]?.[baseConfig]?.[executorType]
? localParsedProfiles.executors[executorType][baseConfig][executorType]
baseConfig && executorsMap[executorType]?.[baseConfig]?.[executorType]
? executorsMap[executorType][baseConfig][executorType]
: {};
const updatedProfiles = {
@@ -123,7 +128,7 @@ export function AgentSettings() {
executors: {
...localParsedProfiles.executors,
[executorType]: {
...localParsedProfiles.executors[executorType],
...executorsMap[executorType],
[configName]: {
[executorType]: base,
},
@@ -190,9 +195,10 @@ export function AgentSettings() {
},
};
const executorsMap = updatedProfiles.executors as unknown as ExecutorsMap;
// If no configurations left, create a blank DEFAULT (should not happen due to check above)
if (Object.keys(remainingConfigs).length === 0) {
updatedProfiles.executors[selectedExecutorType] = {
executorsMap[selectedExecutorType] = {
DEFAULT: { [selectedExecutorType]: {} },
};
}
@@ -208,7 +214,7 @@ export function AgentSettings() {
// Select the next available configuration
const nextConfigs = Object.keys(
updatedProfiles.executors[selectedExecutorType]
executorsMap[selectedExecutorType] || {}
);
const nextSelected = nextConfigs[0] || 'DEFAULT';
setSelectedConfiguration(nextSelected);
@@ -279,13 +285,15 @@ export function AgentSettings() {
) => {
if (!localParsedProfiles || !localParsedProfiles.executors) return;
const executorsMap =
localParsedProfiles.executors as unknown as ExecutorsMap;
// Update the parsed profiles with the new config
const updatedProfiles = {
...localParsedProfiles,
executors: {
...localParsedProfiles.executors,
[executorType]: {
...localParsedProfiles.executors[executorType],
...executorsMap[executorType],
[configuration]: {
[executorType]: formData,
},
@@ -406,7 +414,7 @@ export function AgentSettings() {
<Select
value={selectedExecutorType}
onValueChange={(value) => {
setSelectedExecutorType(value);
setSelectedExecutorType(value as BaseCodingAgent);
// Reset configuration selection when executor type changes
setSelectedConfiguration('DEFAULT');
}}
@@ -499,15 +507,20 @@ export function AgentSettings() {
</div>
</div>
{localParsedProfiles.executors[selectedExecutorType]?.[
{(() => {
const executorsMap =
localParsedProfiles.executors as unknown as ExecutorsMap;
return (
!!executorsMap[selectedExecutorType]?.[
selectedConfiguration
]?.[selectedExecutorType] && (
<ExecutorConfigForm
executor={selectedExecutorType as any}
executor={selectedExecutorType}
value={
localParsedProfiles.executors[selectedExecutorType][
(executorsMap[selectedExecutorType][
selectedConfiguration
][selectedExecutorType] || {}
][selectedExecutorType] as Record<string, unknown>) ||
{}
}
onChange={(formData) =>
handleExecutorConfigChange(
@@ -521,7 +534,9 @@ export function AgentSettings() {
isSaving={profilesSaving}
isDirty={isDirty}
/>
)}
)
);
})()}
</div>
) : (
// Raw JSON editor

View File

@@ -222,6 +222,8 @@ variant: string | null, };
export type ExecutorConfig = { [key in string]?: { "CLAUDE_CODE": ClaudeCode } | { "AMP": Amp } | { "GEMINI": Gemini } | { "CODEX": Codex } | { "OPENCODE": Opencode } | { "CURSOR_AGENT": CursorAgent } | { "QWEN_CODE": QwenCode } | { "COPILOT": Copilot } };
export type ExecutorConfigs = { executors: { [key in BaseCodingAgent]?: ExecutorConfig }, };
export enum BaseAgentCapability { SESSION_FORK = "SESSION_FORK", SETUP_HELPER = "SETUP_HELPER" }
export type ClaudeCode = { append_prompt: AppendPrompt, claude_code_router?: boolean | null, plan?: boolean | null, approvals?: boolean | null, model?: string | null, dangerously_skip_permissions?: boolean | null, base_command_override?: string | null, additional_params?: Array<string> | null, };