Skip to content

Commit 99ea372

Browse files
authored
Merge pull request #588 from wgqqqqq/codex/fix-acp-file-confirm-actions
fix(web-ui): restore inline ACP file approval actions
2 parents d2332d8 + 8019cb7 commit 99ea372

2 files changed

Lines changed: 113 additions & 9 deletions

File tree

src/web-ui/src/flow_chat/tool-cards/FileOperationToolCard.scss

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,4 +347,33 @@
347347
color: var(--color-text-muted);
348348
white-space: pre-wrap;
349349
word-break: break-word;
350-
}
350+
}
351+
352+
.file-op-header-actions {
353+
gap: 4px;
354+
}
355+
356+
.file-op-header-action.icon-btn--xs {
357+
width: 18px;
358+
height: 18px;
359+
}
360+
361+
.file-op-confirm-btn {
362+
color: var(--color-success);
363+
364+
&:hover:not(:disabled) {
365+
background: var(--color-success-bg);
366+
color: var(--color-success);
367+
box-shadow: 0 2px 8px var(--color-success-bg);
368+
}
369+
}
370+
371+
.file-op-reject-btn {
372+
color: var(--color-error);
373+
374+
&:hover:not(:disabled) {
375+
background: var(--color-error-bg);
376+
color: var(--color-error);
377+
box-shadow: 0 2px 8px var(--color-error-bg);
378+
}
379+
}

src/web-ui/src/flow_chat/tool-cards/FileOperationToolCard.tsx

Lines changed: 83 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@ import {
2626
Loader2,
2727
Clock,
2828
Check,
29+
X,
2930
} from 'lucide-react';
30-
import { CubeLoading } from '../../component-library';
31+
import { CubeLoading, IconButton } from '../../component-library';
3132
import type { ToolCardProps } from '../types/flow-chat';
3233
import { BaseToolCard, ToolCardHeader } from './BaseToolCard';
3334
import { useSnapshotState } from '../../tools/snapshot_system/hooks/useSnapshotState';
@@ -45,6 +46,7 @@ import { useToolCardHeightContract } from './useToolCardHeightContract';
4546
import { hasNonFileUriScheme } from '@/shared/utils/pathUtils';
4647
import { notificationService } from '@/shared/notification-system';
4748
import { useGitState } from '@/tools/git/hooks/useGitState';
49+
import { ToolCardHeaderActions } from './ToolCardHeaderActions';
4850
import './FileOperationToolCard.scss';
4951

5052
const log = createLogger('FileOperationToolCard');
@@ -59,10 +61,20 @@ export const FileOperationToolCard: React.FC<FileOperationToolCardProps> = ({
5961
toolItem,
6062
config,
6163
sessionId,
62-
onOpenInEditor
64+
onOpenInEditor,
65+
onConfirm,
66+
onReject,
6367
}) => {
6468
const { t } = useTranslation('flow-chat');
65-
const { toolCall, toolResult, status, isParamsStreaming, partialParams } = toolItem;
69+
const {
70+
toolCall,
71+
toolResult,
72+
status,
73+
isParamsStreaming,
74+
partialParams,
75+
requiresConfirmation,
76+
userConfirmed,
77+
} = toolItem;
6678
const toolId = toolItem.id ?? toolCall?.id;
6779

6880
const [isErrorExpanded, setIsErrorExpanded] = useState(false);
@@ -128,6 +140,13 @@ export const FileOperationToolCard: React.FC<FileOperationToolCardProps> = ({
128140
const contentPreview = getContent();
129141

130142
const isFailed = status === 'error' || (toolResult && 'success' in toolResult && !toolResult.success);
143+
const showConfirmationActions = Boolean(
144+
requiresConfirmation &&
145+
!userConfirmed &&
146+
status !== 'completed' &&
147+
status !== 'cancelled' &&
148+
status !== 'error'
149+
);
131150

132151
const fileName = currentFilePath ?
133152
(currentFilePath.split(/[/\\]/).pop() || t('context.file')) :
@@ -443,7 +462,7 @@ export const FileOperationToolCard: React.FC<FileOperationToolCardProps> = ({
443462

444463
if (
445464
(e.target as HTMLElement).closest(
446-
'.file-op-diff-pill, .file-op-open-full-button',
465+
'.file-op-diff-pill, .file-op-open-full-button, .tool-card-header-actions',
447466
)
448467
) {
449468
return;
@@ -468,6 +487,18 @@ export const FileOperationToolCard: React.FC<FileOperationToolCardProps> = ({
468487
toolItem.toolName,
469488
]);
470489

490+
const handleConfirmClick = useCallback((e: React.MouseEvent<HTMLButtonElement>) => {
491+
e.preventDefault();
492+
e.stopPropagation();
493+
onConfirm?.(toolCall?.input);
494+
}, [onConfirm, toolCall?.input]);
495+
496+
const handleRejectClick = useCallback((e: React.MouseEvent<HTMLButtonElement>) => {
497+
e.preventDefault();
498+
e.stopPropagation();
499+
onReject?.();
500+
}, [onReject]);
501+
471502
const handleOpenFullCodeClick = useCallback((e: React.MouseEvent) => {
472503
e.preventDefault();
473504
e.stopPropagation();
@@ -720,12 +751,11 @@ export const FileOperationToolCard: React.FC<FileOperationToolCardProps> = ({
720751
const expandedContent = renderExpandedContent();
721752
const hasExpandableContent =
722753
!isFailed &&
723-
!isDeleteTool &&
724754
Boolean(expandedContent);
725755

726756
const isCardContentExpanded =
727-
!isDeleteTool &&
728757
!isFailed &&
758+
!isDeleteTool &&
729759
isContentExpanded;
730760

731761
const renderHeader = () => {
@@ -793,12 +823,34 @@ export const FileOperationToolCard: React.FC<FileOperationToolCardProps> = ({
793823
)
794824
}
795825
extra={
796-
<>
826+
<ToolCardHeaderActions className="file-op-header-actions">
797827
{isParamsStreaming && (status === 'preparing' || status === 'streaming') && (
798828
<span className="params-streaming-indicator">
799829
{currentFilePath ? t('toolCards.file.receivingParams') : t('toolCards.file.analyzing')}
800830
</span>
801831
)}
832+
{showConfirmationActions && (
833+
<>
834+
<IconButton
835+
className="tool-card-header-action file-op-header-action file-op-confirm-btn"
836+
variant="success"
837+
size="xs"
838+
onClick={handleConfirmClick}
839+
tooltip={t('toolCards.mcp.confirmExecute')}
840+
>
841+
<Check size={12} />
842+
</IconButton>
843+
<IconButton
844+
className="tool-card-header-action file-op-header-action file-op-reject-btn"
845+
variant="danger"
846+
size="xs"
847+
onClick={handleRejectClick}
848+
tooltip={t('toolCards.mcp.cancel')}
849+
>
850+
<X size={12} />
851+
</IconButton>
852+
</>
853+
)}
802854
{canOpenFullCode && (
803855
<Tooltip content={t('toolCards.file.openFullCodeHint')} placement="top">
804856
<button
@@ -811,7 +863,7 @@ export const FileOperationToolCard: React.FC<FileOperationToolCardProps> = ({
811863
</button>
812864
</Tooltip>
813865
)}
814-
</>
866+
</ToolCardHeaderActions>
815867
}
816868
statusIcon={isDeleteTool ? null : renderStatusIcon()}
817869
/>
@@ -829,6 +881,28 @@ export const FileOperationToolCard: React.FC<FileOperationToolCardProps> = ({
829881
<CompactToolCardHeader
830882
icon={getDeleteStatusIcon()}
831883
content={renderDeleteContent()}
884+
extra={showConfirmationActions ? (
885+
<ToolCardHeaderActions className="file-op-header-actions">
886+
<IconButton
887+
className="tool-card-header-action file-op-header-action file-op-confirm-btn"
888+
variant="success"
889+
size="xs"
890+
onClick={handleConfirmClick}
891+
tooltip={t('toolCards.mcp.confirmExecute')}
892+
>
893+
<Check size={12} />
894+
</IconButton>
895+
<IconButton
896+
className="tool-card-header-action file-op-header-action file-op-reject-btn"
897+
variant="danger"
898+
size="xs"
899+
onClick={handleRejectClick}
900+
tooltip={t('toolCards.mcp.cancel')}
901+
>
902+
<X size={12} />
903+
</IconButton>
904+
</ToolCardHeaderActions>
905+
) : undefined}
832906
/>
833907
}
834908
/>
@@ -846,6 +920,7 @@ export const FileOperationToolCard: React.FC<FileOperationToolCardProps> = ({
846920
expandedContent={expandedContent}
847921
errorContent={isFailed && isErrorExpanded ? renderErrorContent() : null}
848922
isFailed={isFailed}
923+
requiresConfirmation={showConfirmationActions}
849924
headerExpandAffordance={hasExpandableContent}
850925
headerAffordanceKind="expand"
851926
/>

0 commit comments

Comments
 (0)