mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-04-30 20:50:34 +00:00
feat(arena): improve cancellation handling and simplify to in-process mode
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> - Track user-initiated cancellation separately from failures - Cancel round immediately when user denies a tool call - Add message queue to handle input during streaming - Add info messages during Arena operations (apply, stop, cleanup) - Disable tmux/iTerm2 backends (only in-process mode supported) - Polish UI: green tool count, updated warning prefix This improves the Arena UX by providing clearer feedback and properly handling user cancellations without treating them as failures.
This commit is contained in:
parent
3233d16b5c
commit
4ee94715df
13 changed files with 153 additions and 65 deletions
|
|
@ -249,10 +249,7 @@ function executeArenaCommand(
|
|||
} else if (event.type === 'info') {
|
||||
addAndRecordArenaMessage(MessageType.INFO, event.message);
|
||||
} else {
|
||||
addAndRecordArenaMessage(
|
||||
MessageType.WARNING,
|
||||
`Arena warning: ${event.message}`,
|
||||
);
|
||||
addAndRecordArenaMessage(MessageType.WARNING, event.message);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -18,9 +18,10 @@
|
|||
*/
|
||||
|
||||
import { Box, Text, useStdin } from 'ink';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
AgentStatus,
|
||||
isTerminalStatus,
|
||||
ApprovalMode,
|
||||
APPROVAL_MODES,
|
||||
} from '@qwen-code/qwen-code-core';
|
||||
|
|
@ -38,6 +39,7 @@ import { useTextBuffer } from '../shared/text-buffer.js';
|
|||
import { calculatePromptWidths } from '../../utils/layoutUtils.js';
|
||||
import { BaseTextInput } from '../BaseTextInput.js';
|
||||
import { LoadingIndicator } from '../LoadingIndicator.js';
|
||||
import { QueuedMessageDisplay } from '../QueuedMessageDisplay.js';
|
||||
import { AgentFooter } from './AgentFooter.js';
|
||||
import { keyMatchers, Command } from '../../keyMatchers.js';
|
||||
import { theme } from '../../semantic-colors.js';
|
||||
|
|
@ -182,13 +184,35 @@ export const AgentComposer: React.FC<AgentComposerProps> = ({ agentId }) => {
|
|||
[buffer, agentTabBarFocused, setAgentTabBarFocused],
|
||||
);
|
||||
|
||||
// ── Message queue (accumulate while streaming, flush as one prompt on idle) ──
|
||||
|
||||
const [messageQueue, setMessageQueue] = useState<string[]>([]);
|
||||
|
||||
// When agent becomes idle (and not terminal), flush queued messages.
|
||||
useEffect(() => {
|
||||
if (
|
||||
streamingState === StreamingState.Idle &&
|
||||
messageQueue.length > 0 &&
|
||||
status !== undefined &&
|
||||
!isTerminalStatus(status)
|
||||
) {
|
||||
const combined = messageQueue.join('\n');
|
||||
setMessageQueue([]);
|
||||
interactiveAgent?.enqueueMessage(combined);
|
||||
}
|
||||
}, [streamingState, messageQueue, interactiveAgent, status]);
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
(text: string) => {
|
||||
const trimmed = text.trim();
|
||||
if (!trimmed || !interactiveAgent) return;
|
||||
interactiveAgent.enqueueMessage(trimmed);
|
||||
if (streamingState === StreamingState.Idle) {
|
||||
interactiveAgent.enqueueMessage(trimmed);
|
||||
} else {
|
||||
setMessageQueue((prev) => [...prev, trimmed]);
|
||||
}
|
||||
},
|
||||
[interactiveAgent],
|
||||
[interactiveAgent, streamingState],
|
||||
);
|
||||
|
||||
// ── Render ──
|
||||
|
|
@ -255,6 +279,8 @@ export const AgentComposer: React.FC<AgentComposerProps> = ({ agentId }) => {
|
|||
</Box>
|
||||
)}
|
||||
|
||||
<QueuedMessageDisplay messageQueue={messageQueue} />
|
||||
|
||||
{/* Input prompt — always visible, like the main Composer */}
|
||||
<BaseTextInput
|
||||
buffer={buffer}
|
||||
|
|
|
|||
|
|
@ -74,6 +74,10 @@ export function ArenaSelectDialog({
|
|||
mgr.getAgentStates().find((item) => item.agentId === agentId);
|
||||
const label = agent?.model.modelId || agentId;
|
||||
|
||||
pushMessage({
|
||||
messageType: 'info',
|
||||
content: `Applying changes from ${label}…`,
|
||||
});
|
||||
const result = await mgr.applyAgentResult(agentId);
|
||||
if (!result.success) {
|
||||
pushMessage({
|
||||
|
|
@ -111,6 +115,10 @@ export function ArenaSelectDialog({
|
|||
}
|
||||
|
||||
try {
|
||||
pushMessage({
|
||||
messageType: 'info',
|
||||
content: 'Discarding Arena results and cleaning up…',
|
||||
});
|
||||
await config.cleanupArenaRuntime(true);
|
||||
pushMessage({
|
||||
messageType: 'info',
|
||||
|
|
|
|||
|
|
@ -264,7 +264,11 @@ export function ArenaStatusDialog({
|
|||
<Text color={theme.status.error}>{failedToolCalls}</Text>
|
||||
</Text>
|
||||
) : (
|
||||
<Text color={theme.text.primary}>
|
||||
<Text
|
||||
color={
|
||||
toolCalls > 0 ? theme.status.success : theme.text.primary
|
||||
}
|
||||
>
|
||||
{pad(String(toolCalls), colTools - 1, 'right')}
|
||||
</Text>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -80,9 +80,17 @@ export function ArenaStopDialog({
|
|||
sessionStatus === ArenaSessionStatus.RUNNING ||
|
||||
sessionStatus === ArenaSessionStatus.INITIALIZING
|
||||
) {
|
||||
pushMessage({
|
||||
messageType: 'info',
|
||||
content: 'Stopping Arena agents…',
|
||||
});
|
||||
await mgr.cancel();
|
||||
}
|
||||
await mgr.waitForSettled();
|
||||
pushMessage({
|
||||
messageType: 'info',
|
||||
content: 'Cleaning up Arena resources…',
|
||||
});
|
||||
|
||||
if (action === 'preserve') {
|
||||
await mgr.cleanupRuntime();
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ export const SuccessMessage: React.FC<StatusTextProps> = ({ text }) => (
|
|||
export const WarningMessage: React.FC<StatusTextProps> = ({ text }) => (
|
||||
<StatusMessage
|
||||
text={text}
|
||||
prefix="⚠"
|
||||
prefix="△"
|
||||
prefixColor={theme.status.warning}
|
||||
textColor={theme.status.warning}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -124,7 +124,8 @@ export function useAgentStreamingState(
|
|||
}, [status, hasPendingApprovals]);
|
||||
|
||||
const isInputActive =
|
||||
streamingState === StreamingState.Idle &&
|
||||
(streamingState === StreamingState.Idle ||
|
||||
streamingState === StreamingState.Responding) &&
|
||||
status !== undefined &&
|
||||
!isTerminalStatus(status);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue