mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-04-28 03:30:40 +00:00
fix: address audit findings across status-line and verbose-mode features
- useStatusLine: clamp used/remaining percentage to [0,100], track totalLinesRemoved as trigger, clean up debounceRef on unmount - AppContainer: use drainQueue from useMessageQueue instead of manual messageQueueRef to avoid stale-ref reads between renders - builtin-agents: add WRITE_FILE tool to statusline-setup agent, improve PS1 parsing instructions (unquoted assignments, \[/\]/\e escapes), strip ANSI colors, remove unreachable symlink instruction - CompactToolGroupDisplay: fix misleading hint "show full tool output" to "toggle verbose mode" across all 6 locales - AppContainer.test: add missing drainQueue mock
This commit is contained in:
parent
c36953816c
commit
520ed4e040
11 changed files with 69 additions and 39 deletions
|
|
@ -1971,6 +1971,6 @@ export default {
|
|||
verbose: 'ausführlich',
|
||||
'Show full tool output and thinking in verbose mode (toggle with Ctrl+O).':
|
||||
'Vollständige Tool-Ausgabe und Denkprozess im ausführlichen Modus anzeigen (mit Strg+O umschalten).',
|
||||
'Press Ctrl+O to show full tool output':
|
||||
'Strg+O für vollständige Tool-Ausgabe drücken',
|
||||
'Press Ctrl+O to toggle verbose mode':
|
||||
'Strg+O zum Umschalten des ausführlichen Modus drücken',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2011,6 +2011,5 @@ export default {
|
|||
verbose: 'verbose',
|
||||
'Show full tool output and thinking in verbose mode (toggle with Ctrl+O).':
|
||||
'Show full tool output and thinking in verbose mode (toggle with Ctrl+O).',
|
||||
'Press Ctrl+O to show full tool output':
|
||||
'Press Ctrl+O to show full tool output',
|
||||
'Press Ctrl+O to toggle verbose mode': 'Press Ctrl+O to toggle verbose mode',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1463,5 +1463,5 @@ export default {
|
|||
verbose: '詳細',
|
||||
'Show full tool output and thinking in verbose mode (toggle with Ctrl+O).':
|
||||
'詳細モードで完全なツール出力と思考を表示します(Ctrl+O で切り替え)。',
|
||||
'Press Ctrl+O to show full tool output': 'Ctrl+O で完全なツール出力を表示',
|
||||
'Press Ctrl+O to toggle verbose mode': 'Ctrl+O で詳細モードを切り替え',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1961,6 +1961,6 @@ export default {
|
|||
verbose: 'detalhado',
|
||||
'Show full tool output and thinking in verbose mode (toggle with Ctrl+O).':
|
||||
'Mostrar saída completa da ferramenta e raciocínio no modo detalhado (alternar com Ctrl+O).',
|
||||
'Press Ctrl+O to show full tool output':
|
||||
'Pressione Ctrl+O para exibir a saída completa da ferramenta',
|
||||
'Press Ctrl+O to toggle verbose mode':
|
||||
'Pressione Ctrl+O para alternar o modo detalhado',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1968,6 +1968,6 @@ export default {
|
|||
verbose: 'подробный',
|
||||
'Show full tool output and thinking in verbose mode (toggle with Ctrl+O).':
|
||||
'Показывать полный вывод инструментов и процесс рассуждений в подробном режиме (переключить с помощью Ctrl+O).',
|
||||
'Press Ctrl+O to show full tool output':
|
||||
'Нажмите Ctrl+O для показа полного вывода инструментов',
|
||||
'Press Ctrl+O to toggle verbose mode':
|
||||
'Нажмите Ctrl+O для переключения подробного режима',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1816,5 +1816,5 @@ export default {
|
|||
verbose: '详细',
|
||||
'Show full tool output and thinking in verbose mode (toggle with Ctrl+O).':
|
||||
'详细模式下显示完整工具输出和思考过程(Ctrl+O 切换)。',
|
||||
'Press Ctrl+O to show full tool output': '按 Ctrl+O 查看详细工具调用结果',
|
||||
'Press Ctrl+O to toggle verbose mode': '按 Ctrl+O 切换详细模式',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -243,6 +243,7 @@ describe('AppContainer State Management', () => {
|
|||
addMessage: vi.fn(),
|
||||
clearQueue: vi.fn(),
|
||||
getQueuedMessagesText: vi.fn().mockReturnValue(''),
|
||||
drainQueue: vi.fn().mockReturnValue([]),
|
||||
});
|
||||
mockedUseAutoAcceptIndicator.mockReturnValue(false);
|
||||
mockedUseGitBranchName.mockReturnValue('main');
|
||||
|
|
@ -455,6 +456,7 @@ describe('AppContainer State Management', () => {
|
|||
addMessage: mockQueueMessage,
|
||||
clearQueue: vi.fn(),
|
||||
getQueuedMessagesText: vi.fn().mockReturnValue(''),
|
||||
drainQueue: vi.fn().mockReturnValue([]),
|
||||
});
|
||||
|
||||
render(
|
||||
|
|
|
|||
|
|
@ -776,24 +776,22 @@ export const AppContainer = (props: AppContainerProps) => {
|
|||
disabled: agentViewState.activeView !== 'main',
|
||||
});
|
||||
|
||||
const { messageQueue, addMessage, clearQueue, getQueuedMessagesText } =
|
||||
useMessageQueue({
|
||||
isConfigInitialized,
|
||||
streamingState,
|
||||
submitQuery,
|
||||
});
|
||||
const {
|
||||
messageQueue,
|
||||
addMessage,
|
||||
clearQueue,
|
||||
getQueuedMessagesText,
|
||||
drainQueue,
|
||||
} = useMessageQueue({
|
||||
isConfigInitialized,
|
||||
streamingState,
|
||||
submitQuery,
|
||||
});
|
||||
|
||||
// Bridge message queue to mid-turn drain via ref.
|
||||
// Sync ref on every render so the drain callback always reads latest state.
|
||||
const messageQueueRef = useRef(messageQueue);
|
||||
messageQueueRef.current = messageQueue;
|
||||
midTurnDrainRef.current = () => {
|
||||
const queue = messageQueueRef.current;
|
||||
if (queue.length === 0) return [];
|
||||
messageQueueRef.current = [];
|
||||
clearQueue();
|
||||
return [...queue];
|
||||
};
|
||||
// drainQueue reads from the synchronous queueRef inside useMessageQueue,
|
||||
// so it always sees the latest state even between renders.
|
||||
midTurnDrainRef.current = drainQueue;
|
||||
|
||||
// Callback for handling final submit (must be after addMessage from useMessageQueue)
|
||||
const handleFinalSubmit = useCallback(
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ export const CompactToolGroupDisplay: React.FC<
|
|||
|
||||
{/* Hint line */}
|
||||
<Text color={theme.text.secondary}>
|
||||
{t('Press Ctrl+O to show full tool output')}
|
||||
{t('Press Ctrl+O to toggle verbose mode')}
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -175,6 +175,8 @@ export function useStatusLine(): {
|
|||
const { currentModel, branchName } = uiState;
|
||||
const totalToolCalls = uiState.sessionStats.metrics.tools.totalCalls;
|
||||
const totalLinesAdded = uiState.sessionStats.metrics.files.totalLinesAdded;
|
||||
const totalLinesRemoved =
|
||||
uiState.sessionStats.metrics.files.totalLinesRemoved;
|
||||
const effectiveVim = vimEnabled ? vimMode : undefined;
|
||||
const prevStateRef = useRef<{
|
||||
promptTokenCount: number;
|
||||
|
|
@ -183,6 +185,7 @@ export function useStatusLine(): {
|
|||
branchName: string | undefined;
|
||||
totalToolCalls: number;
|
||||
totalLinesAdded: number;
|
||||
totalLinesRemoved: number;
|
||||
}>({
|
||||
promptTokenCount: lastPromptTokenCount,
|
||||
currentModel,
|
||||
|
|
@ -190,6 +193,7 @@ export function useStatusLine(): {
|
|||
branchName,
|
||||
totalToolCalls,
|
||||
totalLinesAdded,
|
||||
totalLinesRemoved,
|
||||
});
|
||||
|
||||
// Guard: when true, the mount effect has already called doUpdate so the
|
||||
|
|
@ -216,8 +220,15 @@ export function useStatusLine(): {
|
|||
cfg.getContentGeneratorConfig()?.contextWindowSize || 0;
|
||||
const usedPercentage =
|
||||
contextWindowSize > 0
|
||||
? Math.round((stats.lastPromptTokenCount / contextWindowSize) * 1000) /
|
||||
10
|
||||
? Math.min(
|
||||
100,
|
||||
Math.max(
|
||||
0,
|
||||
Math.round(
|
||||
(stats.lastPromptTokenCount / contextWindowSize) * 1000,
|
||||
) / 10,
|
||||
),
|
||||
)
|
||||
: 0;
|
||||
|
||||
let totalInputTokens = 0;
|
||||
|
|
@ -238,9 +249,15 @@ export function useStatusLine(): {
|
|||
used_percentage: usedPercentage,
|
||||
remaining_percentage:
|
||||
contextWindowSize > 0
|
||||
? Math.round(
|
||||
(1 - stats.lastPromptTokenCount / contextWindowSize) * 1000,
|
||||
) / 10
|
||||
? Math.min(
|
||||
100,
|
||||
Math.max(
|
||||
0,
|
||||
Math.round(
|
||||
(1 - stats.lastPromptTokenCount / contextWindowSize) * 1000,
|
||||
) / 10,
|
||||
),
|
||||
)
|
||||
: 100,
|
||||
current_usage: stats.lastPromptTokenCount,
|
||||
total_input_tokens: totalInputTokens,
|
||||
|
|
@ -256,7 +273,7 @@ export function useStatusLine(): {
|
|||
}),
|
||||
metrics: buildMetricsPayload(m),
|
||||
...(vimEnabledRef.current && {
|
||||
vim: { mode: vimModeRef.current ?? 'INSERT' },
|
||||
vim: { mode: vimModeRef.current },
|
||||
}),
|
||||
};
|
||||
|
||||
|
|
@ -328,7 +345,8 @@ export function useStatusLine(): {
|
|||
effectiveVim !== prev.effectiveVim ||
|
||||
branchName !== prev.branchName ||
|
||||
totalToolCalls !== prev.totalToolCalls ||
|
||||
totalLinesAdded !== prev.totalLinesAdded
|
||||
totalLinesAdded !== prev.totalLinesAdded ||
|
||||
totalLinesRemoved !== prev.totalLinesRemoved
|
||||
) {
|
||||
prev.promptTokenCount = lastPromptTokenCount;
|
||||
prev.currentModel = currentModel;
|
||||
|
|
@ -336,6 +354,7 @@ export function useStatusLine(): {
|
|||
prev.branchName = branchName;
|
||||
prev.totalToolCalls = totalToolCalls;
|
||||
prev.totalLinesAdded = totalLinesAdded;
|
||||
prev.totalLinesRemoved = totalLinesRemoved;
|
||||
scheduleUpdate();
|
||||
}
|
||||
}, [
|
||||
|
|
@ -346,6 +365,7 @@ export function useStatusLine(): {
|
|||
branchName,
|
||||
totalToolCalls,
|
||||
totalLinesAdded,
|
||||
totalLinesRemoved,
|
||||
scheduleUpdate,
|
||||
]);
|
||||
|
||||
|
|
@ -379,6 +399,7 @@ export function useStatusLine(): {
|
|||
genRef.current++;
|
||||
if (debounceRef.current !== undefined) {
|
||||
clearTimeout(debounceRef.current);
|
||||
debounceRef.current = undefined;
|
||||
}
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
|
|
|||
|
|
@ -104,7 +104,12 @@ Notes:
|
|||
name: 'statusline-setup',
|
||||
description:
|
||||
"Use this agent to configure the user's Qwen Code status line setting.",
|
||||
tools: [ToolNames.READ_FILE, ToolNames.EDIT, ToolNames.ASK_USER_QUESTION],
|
||||
tools: [
|
||||
ToolNames.READ_FILE,
|
||||
ToolNames.WRITE_FILE,
|
||||
ToolNames.EDIT,
|
||||
ToolNames.ASK_USER_QUESTION,
|
||||
],
|
||||
color: 'orange',
|
||||
systemPrompt: `You are a status line setup agent for Qwen Code. Your job is to create or update the statusLine command in the user's Qwen Code settings.
|
||||
|
||||
|
|
@ -115,7 +120,12 @@ When asked to convert the user's shell PS1 configuration, follow these steps:
|
|||
- ~/.bash_profile
|
||||
- ~/.profile
|
||||
|
||||
2. Extract the PS1 value using this regex pattern: /(?:^|\\n)\\s*(?:export\\s+)?PS1\\s*=\\s*["']([^"']+)["']/m
|
||||
2. Look for PS1 assignments. PS1 may be quoted or unquoted, e.g.:
|
||||
- PS1="\\u@\\h:\\w\\$ "
|
||||
- PS1='\\u@\\h:\\w\\$ '
|
||||
- PS1=\\u@\\h:\\w\\$
|
||||
- export PS1="..."
|
||||
If there are multiple PS1 assignments, use the last one (it takes effect).
|
||||
|
||||
3. Convert PS1 escape sequences to shell commands:
|
||||
- \\u → $(whoami)
|
||||
|
|
@ -130,8 +140,10 @@ When asked to convert the user's shell PS1 configuration, follow these steps:
|
|||
- \\@ → $(date +%I:%M%p)
|
||||
- \\# → #
|
||||
- \\! → !
|
||||
- \\[ and \\] → (remove — these are readline non-printing markers, not needed in the status line)
|
||||
- \\e or \\033 → (ANSI escape — strip the entire color sequence including \\e[...m)
|
||||
|
||||
4. When using ANSI color codes, be sure to use \`printf\`. Do not remove colors. Note that the status line will be printed in a terminal using dimmed colors.
|
||||
4. Strip ANSI color/escape sequences from the PS1 output. The status line already renders in dimmed color, so PS1 colors are not useful and can produce garbled output.
|
||||
|
||||
5. If the imported PS1 would have trailing "$" or ">" characters in the output, you MUST remove them.
|
||||
|
||||
|
|
@ -198,8 +210,6 @@ How to use the statusLine command:
|
|||
}
|
||||
Make sure to preserve any existing "ui" settings (theme, etc.) when updating.
|
||||
|
||||
4. If ~/.qwen/settings.json is a symlink, update the target file instead.
|
||||
|
||||
Guidelines:
|
||||
- The status line only displays the first line of stdout — ensure commands produce exactly one line of output
|
||||
- Preserve existing settings when updating
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue