Batch A: Fix high-priority 'any' types in core files (vibe-kanban) (#1310)
* Perfect! All type checks pass. Here's a summary of the completed fixes: ## ✅ Batch A Complete: Fixed all 'any' types in 7 core files ### Summary of Changes **1. useLogStream.ts** - Typed WebSocket patches - `patches.forEach((patch: any)` → `const patches = data.JsonPatch as Array<{ value?: PatchType }>` **2. api.ts** - Typed request body - `const requestBody: any = {}` → `const requestBody: { editor_type?: EditorType } = {}` **3. NextActionCard.tsx** - Used proper domain type - `task?: any` → `task?: TaskWithAttemptStatus` **4. PendingApprovalEntry.tsx** - Safe error handling - `catch (e: any)` → `catch (e: unknown)` with proper narrowing **5. useProfiles.ts** - Typed JSON values - `parsedProfiles: any | null` → `parsedProfiles: JsonValue | null` - `queryClient.setQueryData(['profiles'], (old: any)` → typed with `<{ content: string; path: string }>` **6. useProcessRetry.ts** - Typed executor actions - `const typ: any = details?.executor_action?.typ as any` → `const typ: ExecutorActionType | undefined` - Removed unnecessary `as any` cast on version field **7. DisplayConversationEntry.tsx** - Created ActionLike helper type - Removed local `type JsonValue = any` - Imported `JsonValue` and `TaskWithAttemptStatus` from shared/types - Created minimal `ActionLike` type for ToolCallCard compatibility - Typed all component props and variables properly All changes passed frontend and backend type checks ✓ * Cleanup script changes for task attempt 1b9207c2-6291-4bea-b06d-b4702f99f9ee * Perfect! Updated the `ActionLike` type to use `CommandRunResult` from shared types, which is cleaner and more aligned with the domain model. All type checks pass ✓ * Excellent! All checks pass. Let me create a summary of what we accomplished: ## ✅ Refactor Complete: Simplified ToolCallCard by Passing Full Entry ### Summary of Changes **Removed the ActionLike workaround** and made the code significantly simpler and more readable: ### Before (Complex with ActionLike bridge): ```typescript // Helper type to bridge ExecutorAction and ActionType type ActionLike = Partial<{ action: string; arguments: JsonValue; result: CommandRunResult; tool_name: string; message: string; summary: string; }>; // Multiple props with conversions at call sites <ToolCallCard entryType={toolEntry} entryContent={entry.content} defaultExpanded={defaultExpanded} statusAppearance={statusAppearance} forceExpanded={isPendingApproval} linkifyUrls={isGithubCliSetup} /> ``` ### After (Simple with unified entry): ```typescript // Just pass the entry - ToolCallCard handles everything <ToolCallCard entry={entry} expansionKey={expansionKey} forceExpanded={isPendingApproval} /> ``` ### Key Improvements 1. **Simpler Props**: Changed from 8+ props to just 3 (`entry`, `expansionKey`, `forceExpanded`) 2. **Removed ActionLike**: Eliminated the bridge type that was papering over type differences 3. **Centralized Logic**: All normalization logic now lives inside ToolCallCard where it belongs 4. **Type Safety**: Uses proper type narrowing (`'entry_type' in entry`) instead of type casts 5. **Better Readability**: Call sites are now trivial - just pass the entry ### Files Changed - `DisplayConversationEntry.tsx`: - Removed ActionLike helper type - Updated ToolCallCard component to accept unified entry prop - Simplified both call sites (ProcessStartPayload and NormalizedEntry) - Moved linkifyUrls, defaultExpanded logic into ToolCallCard All type checks pass ✓ * Cleanup script changes for task attempt 1b9207c2-6291-4bea-b06d-b4702f99f9ee
This commit is contained in:
committed by
GitHub
parent
5e7742da2a
commit
870a166c38
@@ -6,6 +6,8 @@ import {
|
|||||||
TaskAttempt,
|
TaskAttempt,
|
||||||
ToolStatus,
|
ToolStatus,
|
||||||
type NormalizedEntryType,
|
type NormalizedEntryType,
|
||||||
|
type TaskWithAttemptStatus,
|
||||||
|
type JsonValue,
|
||||||
} from 'shared/types.ts';
|
} from 'shared/types.ts';
|
||||||
import type { ProcessStartPayload } from '@/types/logs';
|
import type { ProcessStartPayload } from '@/types/logs';
|
||||||
import FileChangeRenderer from './FileChangeRenderer';
|
import FileChangeRenderer from './FileChangeRenderer';
|
||||||
@@ -39,11 +41,10 @@ type Props = {
|
|||||||
diffDeletable?: boolean;
|
diffDeletable?: boolean;
|
||||||
executionProcessId?: string;
|
executionProcessId?: string;
|
||||||
taskAttempt?: TaskAttempt;
|
taskAttempt?: TaskAttempt;
|
||||||
task?: any;
|
task?: TaskWithAttemptStatus;
|
||||||
};
|
};
|
||||||
|
|
||||||
type FileEditAction = Extract<ActionType, { action: 'file_edit' }>;
|
type FileEditAction = Extract<ActionType, { action: 'file_edit' }>;
|
||||||
type JsonValue = any;
|
|
||||||
|
|
||||||
const renderJson = (v: JsonValue) => (
|
const renderJson = (v: JsonValue) => (
|
||||||
<pre className="whitespace-pre-wrap">{JSON.stringify(v, null, 2)}</pre>
|
<pre className="whitespace-pre-wrap">{JSON.stringify(v, null, 2)}</pre>
|
||||||
@@ -419,62 +420,56 @@ const PlanPresentationCard: React.FC<{
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ToolCallCard: React.FC<{
|
const ToolCallCard: React.FC<{
|
||||||
entryType?: Extract<NormalizedEntryType, { type: 'tool_use' }>;
|
entry: NormalizedEntry | ProcessStartPayload;
|
||||||
action?: any;
|
|
||||||
expansionKey: string;
|
expansionKey: string;
|
||||||
content?: string;
|
|
||||||
entryContent?: string;
|
|
||||||
highlighted?: boolean;
|
|
||||||
defaultExpanded?: boolean;
|
|
||||||
statusAppearance?: ToolStatusAppearance;
|
|
||||||
forceExpanded?: boolean;
|
forceExpanded?: boolean;
|
||||||
linkifyUrls?: boolean;
|
}> = ({ entry, expansionKey, forceExpanded = false }) => {
|
||||||
}> = ({
|
|
||||||
entryType,
|
|
||||||
action,
|
|
||||||
expansionKey,
|
|
||||||
content,
|
|
||||||
entryContent,
|
|
||||||
defaultExpanded = false,
|
|
||||||
forceExpanded = false,
|
|
||||||
linkifyUrls = false,
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslation('common');
|
const { t } = useTranslation('common');
|
||||||
const at: any = entryType?.action_type || action;
|
|
||||||
|
// Determine if this is a NormalizedEntry with tool_use
|
||||||
|
const isNormalizedEntry = 'entry_type' in entry;
|
||||||
|
const entryType =
|
||||||
|
isNormalizedEntry && entry.entry_type.type === 'tool_use'
|
||||||
|
? entry.entry_type
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
// Compute defaults from entry
|
||||||
|
const linkifyUrls = entryType?.tool_name === 'GitHub CLI Setup Script';
|
||||||
|
const defaultExpanded = linkifyUrls;
|
||||||
|
|
||||||
const [expanded, toggle] = useExpandable(
|
const [expanded, toggle] = useExpandable(
|
||||||
`tool-entry:${expansionKey}`,
|
`tool-entry:${expansionKey}`,
|
||||||
defaultExpanded
|
defaultExpanded
|
||||||
);
|
);
|
||||||
const effectiveExpanded = forceExpanded || expanded;
|
const effectiveExpanded = forceExpanded || expanded;
|
||||||
|
|
||||||
const label =
|
// Extract action details
|
||||||
at?.action === 'command_run'
|
const actionType = entryType?.action_type;
|
||||||
? 'Ran'
|
const isCommand = actionType?.action === 'command_run';
|
||||||
: entryType?.tool_name || at?.tool_name || 'Tool';
|
const isTool = actionType?.action === 'tool';
|
||||||
|
|
||||||
const isCommand = at?.action === 'command_run';
|
// Label and content
|
||||||
|
const label = isCommand ? 'Ran' : entryType?.tool_name || 'Tool';
|
||||||
|
|
||||||
const inlineText = (entryContent || content || '').trim();
|
const inlineText = isNormalizedEntry ? entry.content.trim() : '';
|
||||||
const isSingleLine = inlineText !== '' && !/\r?\n/.test(inlineText);
|
const isSingleLine = inlineText !== '' && !/\r?\n/.test(inlineText);
|
||||||
const showInlineSummary = isSingleLine;
|
const showInlineSummary = isSingleLine;
|
||||||
|
|
||||||
const hasArgs = at?.action === 'tool' && !!at?.arguments;
|
// Command details
|
||||||
const hasResult = at?.action === 'tool' && !!at?.result;
|
const commandResult = isCommand ? actionType.result : null;
|
||||||
|
const output = commandResult?.output ?? null;
|
||||||
const output: string | null = isCommand ? (at?.result?.output ?? null) : null;
|
|
||||||
let argsText: string | null = null;
|
let argsText: string | null = null;
|
||||||
if (isCommand) {
|
if (isCommand) {
|
||||||
const fromArgs =
|
const fromArgs =
|
||||||
typeof at?.arguments === 'string'
|
typeof actionType.command === 'string' ? actionType.command : '';
|
||||||
? at.arguments
|
const fallback = inlineText;
|
||||||
: at?.arguments != null
|
|
||||||
? JSON.stringify(at.arguments, null, 2)
|
|
||||||
: '';
|
|
||||||
|
|
||||||
const fallback = (entryContent || content || '').trim();
|
|
||||||
argsText = (fromArgs || fallback).trim();
|
argsText = (fromArgs || fallback).trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tool details
|
||||||
|
const hasArgs = isTool && !!actionType.arguments;
|
||||||
|
const hasResult = isTool && !!actionType.result;
|
||||||
|
|
||||||
const hasExpandableDetails = isCommand
|
const hasExpandableDetails = isCommand
|
||||||
? Boolean(argsText) || Boolean(output)
|
? Boolean(argsText) || Boolean(output)
|
||||||
: hasArgs || hasResult;
|
: hasArgs || hasResult;
|
||||||
@@ -497,6 +492,7 @@ const ToolCallCard: React.FC<{
|
|||||||
const headerClassName = cn(
|
const headerClassName = cn(
|
||||||
'w-full flex items-center gap-1.5 text-left text-secondary-foreground'
|
'w-full flex items-center gap-1.5 text-left text-secondary-foreground'
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="inline-block w-full flex flex-col gap-4">
|
<div className="inline-block w-full flex flex-col gap-4">
|
||||||
<HeaderWrapper {...headerProps} className={headerClassName}>
|
<HeaderWrapper {...headerProps} className={headerClassName}>
|
||||||
@@ -539,26 +535,26 @@ const ToolCallCard: React.FC<{
|
|||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{entryType?.action_type.action === 'tool' && (
|
{isTool && actionType && (
|
||||||
<>
|
<>
|
||||||
<div className="font-normal uppercase bg-background border-b border-dashed px-2 py-1">
|
<div className="font-normal uppercase bg-background border-b border-dashed px-2 py-1">
|
||||||
{t('conversation.args')}
|
{t('conversation.args')}
|
||||||
</div>
|
</div>
|
||||||
<div className="px-2 py-1">
|
<div className="px-2 py-1">
|
||||||
{renderJson(entryType.action_type.arguments)}
|
{renderJson(actionType.arguments)}
|
||||||
</div>
|
</div>
|
||||||
<div className="font-normal uppercase bg-background border-y border-dashed px-2 py-1">
|
<div className="font-normal uppercase bg-background border-y border-dashed px-2 py-1">
|
||||||
{t('conversation.result')}
|
{t('conversation.result')}
|
||||||
</div>
|
</div>
|
||||||
<div className="px-2 py-1">
|
<div className="px-2 py-1">
|
||||||
{entryType.action_type.result?.type.type === 'markdown' &&
|
{actionType.result?.type.type === 'markdown' &&
|
||||||
entryType.action_type.result.value && (
|
actionType.result.value && (
|
||||||
<MarkdownRenderer
|
<MarkdownRenderer
|
||||||
content={entryType.action_type.result.value?.toString()}
|
content={actionType.result.value?.toString()}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{entryType.action_type.result?.type.type === 'json' &&
|
{actionType.result?.type.type === 'json' &&
|
||||||
renderJson(entryType.action_type.result.value)}
|
renderJson(actionType.result.value)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -624,14 +620,9 @@ function DisplayConversationEntry({
|
|||||||
const greyed = isProcessGreyed(executionProcessId);
|
const greyed = isProcessGreyed(executionProcessId);
|
||||||
|
|
||||||
if (isProcessStart(entry)) {
|
if (isProcessStart(entry)) {
|
||||||
const toolAction: any = entry.action ?? null;
|
|
||||||
return (
|
return (
|
||||||
<div className={greyed ? 'opacity-50 pointer-events-none' : undefined}>
|
<div className={greyed ? 'opacity-50 pointer-events-none' : undefined}>
|
||||||
<ToolCallCard
|
<ToolCallCard entry={entry} expansionKey={expansionKey} />
|
||||||
action={toolAction}
|
|
||||||
expansionKey={expansionKey}
|
|
||||||
content={toolAction?.message ?? toolAction?.summary ?? undefined}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -691,9 +682,7 @@ function DisplayConversationEntry({
|
|||||||
const isPlanPresentation =
|
const isPlanPresentation =
|
||||||
toolEntry.action_type.action === 'plan_presentation';
|
toolEntry.action_type.action === 'plan_presentation';
|
||||||
const isPendingApproval = status.status === 'pending_approval';
|
const isPendingApproval = status.status === 'pending_approval';
|
||||||
const isGithubCliSetup = toolEntry.tool_name === 'GitHub CLI Setup Script';
|
const defaultExpanded = isPendingApproval || isPlanPresentation;
|
||||||
const defaultExpanded =
|
|
||||||
isPendingApproval || isPlanPresentation || isGithubCliSetup;
|
|
||||||
|
|
||||||
const body = (() => {
|
const body = (() => {
|
||||||
if (isFileEdit(toolEntry.action_type)) {
|
if (isFileEdit(toolEntry.action_type)) {
|
||||||
@@ -728,13 +717,9 @@ function DisplayConversationEntry({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ToolCallCard
|
<ToolCallCard
|
||||||
entryType={toolEntry}
|
entry={entry}
|
||||||
expansionKey={expansionKey}
|
expansionKey={expansionKey}
|
||||||
entryContent={entry.content}
|
|
||||||
defaultExpanded={defaultExpanded}
|
|
||||||
statusAppearance={statusAppearance}
|
|
||||||
forceExpanded={isPendingApproval}
|
forceExpanded={isPendingApproval}
|
||||||
linkifyUrls={isGithubCliSetup}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -24,7 +24,11 @@ import { getIdeName } from '@/components/ide/IdeIcon';
|
|||||||
import { useProject } from '@/contexts/project-context';
|
import { useProject } from '@/contexts/project-context';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { attemptsApi } from '@/lib/api';
|
import { attemptsApi } from '@/lib/api';
|
||||||
import { BaseAgentCapability, type BaseCodingAgent } from 'shared/types';
|
import {
|
||||||
|
BaseAgentCapability,
|
||||||
|
type BaseCodingAgent,
|
||||||
|
type TaskWithAttemptStatus,
|
||||||
|
} from 'shared/types';
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
@@ -37,7 +41,7 @@ type NextActionCardProps = {
|
|||||||
containerRef?: string | null;
|
containerRef?: string | null;
|
||||||
failed: boolean;
|
failed: boolean;
|
||||||
execution_processes: number;
|
execution_processes: number;
|
||||||
task?: any;
|
task?: TaskWithAttemptStatus;
|
||||||
needsSetup?: boolean;
|
needsSetup?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -265,9 +265,11 @@ const PendingApprovalEntry = ({
|
|||||||
});
|
});
|
||||||
setHasResponded(true);
|
setHasResponded(true);
|
||||||
clear();
|
clear();
|
||||||
} catch (e: any) {
|
} catch (e: unknown) {
|
||||||
console.error('Approval respond failed:', e);
|
console.error('Approval respond failed:', e);
|
||||||
setError(e?.message || 'Failed to send response');
|
const errorMessage =
|
||||||
|
e instanceof Error ? e.message : 'Failed to send response';
|
||||||
|
setError(errorMessage);
|
||||||
} finally {
|
} finally {
|
||||||
setIsResponding(false);
|
setIsResponding(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,8 +52,8 @@ export const useLogStream = (processId: string): UseLogStreamResult => {
|
|||||||
|
|
||||||
// Handle different message types based on LogMsg enum
|
// Handle different message types based on LogMsg enum
|
||||||
if ('JsonPatch' in data) {
|
if ('JsonPatch' in data) {
|
||||||
const patches = data.JsonPatch;
|
const patches = data.JsonPatch as Array<{ value?: PatchType }>;
|
||||||
patches.forEach((patch: any) => {
|
patches.forEach((patch) => {
|
||||||
const value = patch?.value;
|
const value = patch?.value;
|
||||||
if (!value || !value.type) return;
|
if (!value || !value.type) return;
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,11 @@ import { useCallback, useState } from 'react';
|
|||||||
import { useAttemptExecution } from '@/hooks/useAttemptExecution';
|
import { useAttemptExecution } from '@/hooks/useAttemptExecution';
|
||||||
import { useBranchStatus } from '@/hooks/useBranchStatus';
|
import { useBranchStatus } from '@/hooks/useBranchStatus';
|
||||||
import { attemptsApi, executionProcessesApi } from '@/lib/api';
|
import { attemptsApi, executionProcessesApi } from '@/lib/api';
|
||||||
import type { ExecutionProcess, TaskAttempt } from 'shared/types';
|
import type {
|
||||||
|
ExecutionProcess,
|
||||||
|
TaskAttempt,
|
||||||
|
ExecutorActionType,
|
||||||
|
} from 'shared/types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reusable hook to retry a process given its executionProcessId and a new prompt.
|
* Reusable hook to retry a process given its executionProcessId and a new prompt.
|
||||||
@@ -64,13 +68,14 @@ export function useProcessRetry(attempt: TaskAttempt | undefined) {
|
|||||||
try {
|
try {
|
||||||
const details =
|
const details =
|
||||||
await executionProcessesApi.getDetails(executionProcessId);
|
await executionProcessesApi.getDetails(executionProcessId);
|
||||||
const typ: any = details?.executor_action?.typ as any;
|
const typ: ExecutorActionType | undefined =
|
||||||
|
details?.executor_action?.typ;
|
||||||
if (
|
if (
|
||||||
typ &&
|
typ &&
|
||||||
(typ.type === 'CodingAgentInitialRequest' ||
|
(typ.type === 'CodingAgentInitialRequest' ||
|
||||||
typ.type === 'CodingAgentFollowUpRequest')
|
typ.type === 'CodingAgentFollowUpRequest')
|
||||||
) {
|
) {
|
||||||
variant = (typ.executor_profile_id?.variant as string | null) ?? null;
|
variant = typ.executor_profile_id?.variant ?? null;
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
/* ignore */
|
/* ignore */
|
||||||
@@ -83,7 +88,7 @@ export function useProcessRetry(attempt: TaskAttempt | undefined) {
|
|||||||
prompt: newPrompt,
|
prompt: newPrompt,
|
||||||
variant,
|
variant,
|
||||||
image_ids: [],
|
image_ids: [],
|
||||||
version: null as any,
|
version: null,
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setBusy(false);
|
setBusy(false);
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { profilesApi } from '@/lib/api';
|
import { profilesApi } from '@/lib/api';
|
||||||
|
import type { JsonValue } from 'shared/types';
|
||||||
|
|
||||||
export type UseProfilesReturn = {
|
export type UseProfilesReturn = {
|
||||||
// data
|
// data
|
||||||
profilesContent: string;
|
profilesContent: string;
|
||||||
parsedProfiles: any | null;
|
parsedProfiles: JsonValue | null;
|
||||||
profilesPath: string;
|
profilesPath: string;
|
||||||
|
|
||||||
// status
|
// status
|
||||||
@@ -33,8 +34,9 @@ export function useProfiles(): UseProfilesReturn {
|
|||||||
mutationFn: (content: string) => profilesApi.save(content),
|
mutationFn: (content: string) => profilesApi.save(content),
|
||||||
onSuccess: (_, content) => {
|
onSuccess: (_, content) => {
|
||||||
// Optimistically update cache with new content
|
// Optimistically update cache with new content
|
||||||
queryClient.setQueryData(['profiles'], (old: any) =>
|
queryClient.setQueryData<{ content: string; path: string }>(
|
||||||
old ? { ...old, content } : old
|
['profiles'],
|
||||||
|
(old) => (old ? { ...old, content } : old)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -274,7 +274,7 @@ export const projectsApi = {
|
|||||||
id: string,
|
id: string,
|
||||||
editorType?: EditorType
|
editorType?: EditorType
|
||||||
): Promise<OpenEditorResponse> => {
|
): Promise<OpenEditorResponse> => {
|
||||||
const requestBody: any = {};
|
const requestBody: { editor_type?: EditorType } = {};
|
||||||
if (editorType) requestBody.editor_type = editorType;
|
if (editorType) requestBody.editor_type = editorType;
|
||||||
|
|
||||||
const response = await makeRequest(`/api/projects/${id}/open-editor`, {
|
const response = await makeRequest(`/api/projects/${id}/open-editor`, {
|
||||||
|
|||||||
Reference in New Issue
Block a user