mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-05-05 15:31:27 +00:00
Merge branch 'main' into feat/support-permission
This commit is contained in:
commit
f9d9a985ce
249 changed files with 26635 additions and 2729 deletions
308
packages/cli/src/ui/contexts/AgentViewContext.tsx
Normal file
308
packages/cli/src/ui/contexts/AgentViewContext.tsx
Normal file
|
|
@ -0,0 +1,308 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview AgentViewContext — React context for in-process agent view switching.
|
||||
*
|
||||
* Tracks which view is active (main or an agent tab) and the set of registered
|
||||
* AgentInteractive instances. Consumed by AgentTabBar, AgentChatView, and
|
||||
* DefaultAppLayout to implement tab-based agent navigation.
|
||||
*
|
||||
* Kept separate from UIStateContext to avoid bloating the main state with
|
||||
* in-process-only concerns and to make the feature self-contained.
|
||||
*/
|
||||
|
||||
import {
|
||||
createContext,
|
||||
useContext,
|
||||
useCallback,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import {
|
||||
type AgentInteractive,
|
||||
type ApprovalMode,
|
||||
type Config,
|
||||
} from '@qwen-code/qwen-code-core';
|
||||
import { useArenaInProcess } from '../hooks/useArenaInProcess.js';
|
||||
|
||||
// ─── Types ──────────────────────────────────────────────────
|
||||
|
||||
export interface RegisteredAgent {
|
||||
interactiveAgent: AgentInteractive;
|
||||
/** Model identifier shown in tabs and paths (e.g. "glm-5"). */
|
||||
modelId: string;
|
||||
/** Human-friendly model name (e.g. "GLM 5"). */
|
||||
modelName?: string;
|
||||
color: string;
|
||||
}
|
||||
|
||||
export interface AgentViewState {
|
||||
/** 'main' or an agentId */
|
||||
activeView: string;
|
||||
/** Registered in-process agents keyed by agentId */
|
||||
agents: ReadonlyMap<string, RegisteredAgent>;
|
||||
/** Whether any agent tab's embedded shell currently has input focus. */
|
||||
agentShellFocused: boolean;
|
||||
/** Current text in the active agent tab's input buffer (empty when on main). */
|
||||
agentInputBufferText: string;
|
||||
/** Whether the tab bar has keyboard focus (vs the agent input). */
|
||||
agentTabBarFocused: boolean;
|
||||
/** Per-agent approval modes (keyed by agentId). */
|
||||
agentApprovalModes: ReadonlyMap<string, ApprovalMode>;
|
||||
}
|
||||
|
||||
export interface AgentViewActions {
|
||||
switchToMain(): void;
|
||||
switchToAgent(agentId: string): void;
|
||||
switchToNext(): void;
|
||||
switchToPrevious(): void;
|
||||
registerAgent(
|
||||
agentId: string,
|
||||
interactiveAgent: AgentInteractive,
|
||||
modelId: string,
|
||||
color: string,
|
||||
modelName?: string,
|
||||
): void;
|
||||
unregisterAgent(agentId: string): void;
|
||||
unregisterAll(): void;
|
||||
setAgentShellFocused(focused: boolean): void;
|
||||
setAgentInputBufferText(text: string): void;
|
||||
setAgentTabBarFocused(focused: boolean): void;
|
||||
setAgentApprovalMode(agentId: string, mode: ApprovalMode): void;
|
||||
}
|
||||
|
||||
// ─── Context ────────────────────────────────────────────────
|
||||
|
||||
const AgentViewStateContext = createContext<AgentViewState | null>(null);
|
||||
const AgentViewActionsContext = createContext<AgentViewActions | null>(null);
|
||||
|
||||
// ─── Defaults (used when no provider is mounted) ────────────
|
||||
|
||||
const DEFAULT_STATE: AgentViewState = {
|
||||
activeView: 'main',
|
||||
agents: new Map(),
|
||||
agentShellFocused: false,
|
||||
agentInputBufferText: '',
|
||||
agentTabBarFocused: false,
|
||||
agentApprovalModes: new Map(),
|
||||
};
|
||||
|
||||
const noop = () => {};
|
||||
|
||||
const DEFAULT_ACTIONS: AgentViewActions = {
|
||||
switchToMain: noop,
|
||||
switchToAgent: noop,
|
||||
switchToNext: noop,
|
||||
switchToPrevious: noop,
|
||||
registerAgent: noop,
|
||||
unregisterAgent: noop,
|
||||
unregisterAll: noop,
|
||||
setAgentShellFocused: noop,
|
||||
setAgentInputBufferText: noop,
|
||||
setAgentTabBarFocused: noop,
|
||||
setAgentApprovalMode: noop,
|
||||
};
|
||||
|
||||
// ─── Hook: useAgentViewState ────────────────────────────────
|
||||
|
||||
export function useAgentViewState(): AgentViewState {
|
||||
return useContext(AgentViewStateContext) ?? DEFAULT_STATE;
|
||||
}
|
||||
|
||||
// ─── Hook: useAgentViewActions ──────────────────────────────
|
||||
|
||||
export function useAgentViewActions(): AgentViewActions {
|
||||
return useContext(AgentViewActionsContext) ?? DEFAULT_ACTIONS;
|
||||
}
|
||||
|
||||
// ─── Provider ───────────────────────────────────────────────
|
||||
|
||||
interface AgentViewProviderProps {
|
||||
config?: Config;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function AgentViewProvider({
|
||||
config,
|
||||
children,
|
||||
}: AgentViewProviderProps) {
|
||||
const [activeView, setActiveView] = useState<string>('main');
|
||||
const [agents, setAgents] = useState<Map<string, RegisteredAgent>>(
|
||||
() => new Map(),
|
||||
);
|
||||
const [agentShellFocused, setAgentShellFocused] = useState(false);
|
||||
const [agentInputBufferText, setAgentInputBufferText] = useState('');
|
||||
const [agentTabBarFocused, setAgentTabBarFocused] = useState(false);
|
||||
const [agentApprovalModes, setAgentApprovalModes] = useState<
|
||||
Map<string, ApprovalMode>
|
||||
>(() => new Map());
|
||||
|
||||
// ── Navigation ──
|
||||
|
||||
const switchToMain = useCallback(() => {
|
||||
setActiveView('main');
|
||||
setAgentTabBarFocused(false);
|
||||
}, []);
|
||||
|
||||
const switchToAgent = useCallback(
|
||||
(agentId: string) => {
|
||||
if (agents.has(agentId)) {
|
||||
setActiveView(agentId);
|
||||
}
|
||||
},
|
||||
[agents],
|
||||
);
|
||||
|
||||
const switchToNext = useCallback(() => {
|
||||
const ids = ['main', ...agents.keys()];
|
||||
const currentIndex = ids.indexOf(activeView);
|
||||
const nextIndex = (currentIndex + 1) % ids.length;
|
||||
setActiveView(ids[nextIndex]!);
|
||||
}, [agents, activeView]);
|
||||
|
||||
const switchToPrevious = useCallback(() => {
|
||||
const ids = ['main', ...agents.keys()];
|
||||
const currentIndex = ids.indexOf(activeView);
|
||||
const prevIndex = (currentIndex - 1 + ids.length) % ids.length;
|
||||
setActiveView(ids[prevIndex]!);
|
||||
}, [agents, activeView]);
|
||||
|
||||
// ── Registration ──
|
||||
|
||||
const registerAgent = useCallback(
|
||||
(
|
||||
agentId: string,
|
||||
interactiveAgent: AgentInteractive,
|
||||
modelId: string,
|
||||
color: string,
|
||||
modelName?: string,
|
||||
) => {
|
||||
setAgents((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(agentId, {
|
||||
interactiveAgent,
|
||||
modelId,
|
||||
color,
|
||||
modelName,
|
||||
});
|
||||
return next;
|
||||
});
|
||||
// Seed approval mode from the agent's own config
|
||||
const mode = interactiveAgent.getCore().runtimeContext.getApprovalMode();
|
||||
setAgentApprovalModes((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(agentId, mode);
|
||||
return next;
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const unregisterAgent = useCallback((agentId: string) => {
|
||||
setAgents((prev) => {
|
||||
if (!prev.has(agentId)) return prev;
|
||||
const next = new Map(prev);
|
||||
next.delete(agentId);
|
||||
return next;
|
||||
});
|
||||
setAgentApprovalModes((prev) => {
|
||||
if (!prev.has(agentId)) return prev;
|
||||
const next = new Map(prev);
|
||||
next.delete(agentId);
|
||||
return next;
|
||||
});
|
||||
setActiveView((current) => (current === agentId ? 'main' : current));
|
||||
}, []);
|
||||
|
||||
const unregisterAll = useCallback(() => {
|
||||
setAgents(new Map());
|
||||
setAgentApprovalModes(new Map());
|
||||
setActiveView('main');
|
||||
setAgentTabBarFocused(false);
|
||||
}, []);
|
||||
|
||||
const setAgentApprovalMode = useCallback(
|
||||
(agentId: string, mode: ApprovalMode) => {
|
||||
// Update the agent's runtime config so tool scheduling picks it up
|
||||
const agent = agents.get(agentId);
|
||||
if (agent) {
|
||||
agent.interactiveAgent.getCore().runtimeContext.setApprovalMode(mode);
|
||||
}
|
||||
// Update UI state
|
||||
setAgentApprovalModes((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(agentId, mode);
|
||||
return next;
|
||||
});
|
||||
},
|
||||
[agents],
|
||||
);
|
||||
|
||||
// ── Memoized values ──
|
||||
|
||||
const state: AgentViewState = useMemo(
|
||||
() => ({
|
||||
activeView,
|
||||
agents,
|
||||
agentShellFocused,
|
||||
agentInputBufferText,
|
||||
agentTabBarFocused,
|
||||
agentApprovalModes,
|
||||
}),
|
||||
[
|
||||
activeView,
|
||||
agents,
|
||||
agentShellFocused,
|
||||
agentInputBufferText,
|
||||
agentTabBarFocused,
|
||||
agentApprovalModes,
|
||||
],
|
||||
);
|
||||
|
||||
const actions: AgentViewActions = useMemo(
|
||||
() => ({
|
||||
switchToMain,
|
||||
switchToAgent,
|
||||
switchToNext,
|
||||
switchToPrevious,
|
||||
registerAgent,
|
||||
unregisterAgent,
|
||||
unregisterAll,
|
||||
setAgentShellFocused,
|
||||
setAgentInputBufferText,
|
||||
setAgentTabBarFocused,
|
||||
setAgentApprovalMode,
|
||||
}),
|
||||
[
|
||||
switchToMain,
|
||||
switchToAgent,
|
||||
switchToNext,
|
||||
switchToPrevious,
|
||||
registerAgent,
|
||||
unregisterAgent,
|
||||
unregisterAll,
|
||||
setAgentShellFocused,
|
||||
setAgentInputBufferText,
|
||||
setAgentTabBarFocused,
|
||||
setAgentApprovalMode,
|
||||
],
|
||||
);
|
||||
|
||||
// ── Arena in-process bridge ──
|
||||
// Bridge arena manager events to agent registration. The hook is kept
|
||||
// in its own file for separation of concerns; it's called here so the
|
||||
// provider is the single owner of agent tab lifecycle.
|
||||
useArenaInProcess(config ?? null, actions);
|
||||
|
||||
return (
|
||||
<AgentViewStateContext.Provider value={state}>
|
||||
<AgentViewActionsContext.Provider value={actions}>
|
||||
{children}
|
||||
</AgentViewActionsContext.Provider>
|
||||
</AgentViewStateContext.Provider>
|
||||
);
|
||||
}
|
||||
|
|
@ -1367,6 +1367,75 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('drops unsupported Kitty CSI-u keys without blocking later input', () => {
|
||||
const keyHandler = vi.fn();
|
||||
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
||||
act(() => result.current.subscribe(keyHandler));
|
||||
|
||||
act(() => stdin.sendKittySequence(`\x1b[57358u`)); // CAPS_LOCK
|
||||
act(() =>
|
||||
stdin.pressKey({
|
||||
name: 'a',
|
||||
ctrl: false,
|
||||
meta: false,
|
||||
shift: false,
|
||||
paste: false,
|
||||
sequence: 'a',
|
||||
}),
|
||||
);
|
||||
|
||||
expect(keyHandler).toHaveBeenCalledTimes(1);
|
||||
expect(keyHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
name: 'a',
|
||||
sequence: 'a',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('recovers plain text that arrives in the same chunk after an unsupported CSI-u key', () => {
|
||||
const keyHandler = vi.fn();
|
||||
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
||||
act(() => result.current.subscribe(keyHandler));
|
||||
|
||||
act(() =>
|
||||
stdin.pressKey({
|
||||
name: '',
|
||||
ctrl: false,
|
||||
meta: false,
|
||||
shift: false,
|
||||
paste: false,
|
||||
sequence: '\x1b[57358ua',
|
||||
}),
|
||||
);
|
||||
|
||||
expect(keyHandler).toHaveBeenCalledTimes(1);
|
||||
expect(keyHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
name: 'a',
|
||||
sequence: 'a',
|
||||
kittyProtocol: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('drops unsupported CSI-u variants with event metadata and keeps parsing', () => {
|
||||
const keyHandler = vi.fn();
|
||||
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
||||
act(() => result.current.subscribe(keyHandler));
|
||||
|
||||
act(() => stdin.sendKittySequence(`\x1b[57358;1:1u\x1b[100u`));
|
||||
|
||||
expect(keyHandler).toHaveBeenCalledTimes(1);
|
||||
expect(keyHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
name: 'd',
|
||||
sequence: 'd',
|
||||
kittyProtocol: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Kitty keypad private-use keys', () => {
|
||||
|
|
|
|||
|
|
@ -178,6 +178,25 @@ export function KeypressProvider({
|
|||
let rawDataBuffer = Buffer.alloc(0);
|
||||
let rawFlushTimeout: NodeJS.Timeout | null = null;
|
||||
|
||||
const createPrintableKey = (char: string): Key => {
|
||||
const printableName =
|
||||
char === ' '
|
||||
? 'space'
|
||||
: /^[A-Za-z]$/.test(char)
|
||||
? char.toLowerCase()
|
||||
: char;
|
||||
|
||||
return {
|
||||
name: printableName,
|
||||
ctrl: false,
|
||||
meta: false,
|
||||
shift: false,
|
||||
paste: false,
|
||||
sequence: char,
|
||||
kittyProtocol: true,
|
||||
};
|
||||
};
|
||||
|
||||
// Parse a single complete kitty sequence from the start (prefix) of the
|
||||
// buffer and return both the Key and the number of characters consumed.
|
||||
// This lets us "peel off" one complete event when multiple sequences arrive
|
||||
|
|
@ -415,22 +434,11 @@ export function KeypressProvider({
|
|||
keyCode <= 0x10ffff &&
|
||||
!(keyCode >= 0xe000 && keyCode <= 0xf8ff)
|
||||
) {
|
||||
const char = String.fromCodePoint(keyCode);
|
||||
const printableName =
|
||||
char === ' '
|
||||
? 'space'
|
||||
: /^[A-Za-z]$/.test(char)
|
||||
? char.toLowerCase()
|
||||
: char;
|
||||
return {
|
||||
key: {
|
||||
name: printableName,
|
||||
ctrl: false,
|
||||
...createPrintableKey(String.fromCodePoint(keyCode)),
|
||||
meta: alt,
|
||||
shift,
|
||||
paste: false,
|
||||
sequence: char,
|
||||
kittyProtocol: true,
|
||||
},
|
||||
length: m[0].length,
|
||||
};
|
||||
|
|
@ -490,6 +498,42 @@ export function KeypressProvider({
|
|||
return null;
|
||||
};
|
||||
|
||||
const getCompleteCsiSequenceLength = (buffer: string): number | null => {
|
||||
if (!buffer.startsWith(`${ESC}[`)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (let i = 2; i < buffer.length; i++) {
|
||||
const code = buffer.charCodeAt(i);
|
||||
if (code >= 0x40 && code <= 0x7e) {
|
||||
return i + 1;
|
||||
}
|
||||
if (code < 0x20 || code > 0x3f) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const parsePlainTextPrefix = (
|
||||
buffer: string,
|
||||
): { key: Key; length: number } | null => {
|
||||
if (!buffer || buffer.startsWith(ESC)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [char] = Array.from(buffer);
|
||||
if (!char) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
key: createPrintableKey(char),
|
||||
length: char.length,
|
||||
};
|
||||
};
|
||||
|
||||
const broadcast = (key: Key) => {
|
||||
for (const handler of subscribers) {
|
||||
handler(key);
|
||||
|
|
@ -653,47 +697,82 @@ export function KeypressProvider({
|
|||
// start of the buffer. This handles batched inputs cleanly. If the
|
||||
// prefix is incomplete or invalid, skip to the next CSI introducer
|
||||
// (ESC[) so that a following valid sequence can still be parsed.
|
||||
let parsedAny = false;
|
||||
let bufferedInputHandled = false;
|
||||
while (kittySequenceBuffer) {
|
||||
const parsed = parseKittyPrefix(kittySequenceBuffer);
|
||||
if (!parsed) {
|
||||
// Look for the next potential CSI start beyond index 0
|
||||
const nextStart = kittySequenceBuffer.indexOf(`${ESC}[`, 1);
|
||||
if (nextStart > 0) {
|
||||
if (debugKeystrokeLogging) {
|
||||
if (parsed) {
|
||||
if (debugKeystrokeLogging) {
|
||||
const parsedSequence = kittySequenceBuffer.slice(
|
||||
0,
|
||||
parsed.length,
|
||||
);
|
||||
if (kittySequenceBuffer.length > parsed.length) {
|
||||
debugLogger.debug(
|
||||
'[DEBUG] Skipping incomplete/invalid CSI prefix:',
|
||||
kittySequenceBuffer.slice(0, nextStart),
|
||||
'[DEBUG] Kitty sequence parsed successfully (prefix):',
|
||||
parsedSequence,
|
||||
);
|
||||
} else {
|
||||
debugLogger.debug(
|
||||
'[DEBUG] Kitty sequence parsed successfully:',
|
||||
parsedSequence,
|
||||
);
|
||||
}
|
||||
kittySequenceBuffer = kittySequenceBuffer.slice(nextStart);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
// Consume the parsed prefix and broadcast it.
|
||||
kittySequenceBuffer = kittySequenceBuffer.slice(parsed.length);
|
||||
broadcast(parsed.key);
|
||||
bufferedInputHandled = true;
|
||||
continue;
|
||||
}
|
||||
if (debugKeystrokeLogging) {
|
||||
const parsedSequence = kittySequenceBuffer.slice(
|
||||
0,
|
||||
parsed.length,
|
||||
|
||||
const completeUnsupportedCsiLength =
|
||||
getCompleteCsiSequenceLength(kittySequenceBuffer);
|
||||
if (completeUnsupportedCsiLength) {
|
||||
if (debugKeystrokeLogging) {
|
||||
debugLogger.debug(
|
||||
'[DEBUG] Dropping unsupported complete CSI sequence:',
|
||||
kittySequenceBuffer.slice(0, completeUnsupportedCsiLength),
|
||||
);
|
||||
}
|
||||
kittySequenceBuffer = kittySequenceBuffer.slice(
|
||||
completeUnsupportedCsiLength,
|
||||
);
|
||||
if (kittySequenceBuffer.length > parsed.length) {
|
||||
bufferedInputHandled = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
const plainTextPrefix = parsePlainTextPrefix(kittySequenceBuffer);
|
||||
if (plainTextPrefix) {
|
||||
if (debugKeystrokeLogging) {
|
||||
debugLogger.debug(
|
||||
'[DEBUG] Kitty sequence parsed successfully (prefix):',
|
||||
parsedSequence,
|
||||
);
|
||||
} else {
|
||||
debugLogger.debug(
|
||||
'[DEBUG] Kitty sequence parsed successfully:',
|
||||
parsedSequence,
|
||||
'[DEBUG] Recovered plain text after kitty sequence:',
|
||||
plainTextPrefix.key.sequence,
|
||||
);
|
||||
}
|
||||
kittySequenceBuffer = kittySequenceBuffer.slice(
|
||||
plainTextPrefix.length,
|
||||
);
|
||||
broadcast(plainTextPrefix.key);
|
||||
bufferedInputHandled = true;
|
||||
continue;
|
||||
}
|
||||
// Consume the parsed prefix and broadcast it.
|
||||
kittySequenceBuffer = kittySequenceBuffer.slice(parsed.length);
|
||||
broadcast(parsed.key);
|
||||
parsedAny = true;
|
||||
|
||||
// Look for the next potential CSI start beyond index 0
|
||||
const nextStart = kittySequenceBuffer.indexOf(`${ESC}[`, 1);
|
||||
if (nextStart > 0) {
|
||||
if (debugKeystrokeLogging) {
|
||||
debugLogger.debug(
|
||||
'[DEBUG] Skipping incomplete/invalid CSI prefix:',
|
||||
kittySequenceBuffer.slice(0, nextStart),
|
||||
);
|
||||
}
|
||||
kittySequenceBuffer = kittySequenceBuffer.slice(nextStart);
|
||||
bufferedInputHandled = true;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (parsedAny) return;
|
||||
if (bufferedInputHandled) return;
|
||||
|
||||
if (config?.getDebugMode() || debugKeystrokeLogging) {
|
||||
const codes = Array.from(kittySequenceBuffer).map((ch) =>
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import {
|
|||
import { type SettingScope } from '../../config/settings.js';
|
||||
import { type CodingPlanRegion } from '../../constants/codingPlan.js';
|
||||
import type { AuthState } from '../types.js';
|
||||
import { type ArenaDialogType } from '../hooks/useArenaCommand.js';
|
||||
// OpenAICredentials type (previously imported from OpenAIKeyPrompt)
|
||||
export interface OpenAICredentials {
|
||||
apiKey: string;
|
||||
|
|
@ -54,6 +55,9 @@ export interface UIActions {
|
|||
exitEditorDialog: () => void;
|
||||
closeSettingsDialog: () => void;
|
||||
closeModelDialog: () => void;
|
||||
openArenaDialog: (type: Exclude<ArenaDialogType, null>) => void;
|
||||
closeArenaDialog: () => void;
|
||||
handleArenaModelsSelected?: (models: string[]) => void;
|
||||
dismissCodingPlanUpdate: () => void;
|
||||
closeTrustDialog: () => void;
|
||||
closePermissionsDialog: () => void;
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import type { UpdateObject } from '../utils/updateCheck.js';
|
|||
import { type UseHistoryManagerReturn } from '../hooks/useHistoryManager.js';
|
||||
import { type RestartReason } from '../hooks/useIdeTrustListener.js';
|
||||
import { type CodingPlanUpdateRequest } from '../hooks/useCodingPlanUpdates.js';
|
||||
import { type ArenaDialogType } from '../hooks/useArenaCommand.js';
|
||||
|
||||
export interface UIState {
|
||||
history: HistoryItem[];
|
||||
|
|
@ -53,6 +54,7 @@ export interface UIState {
|
|||
isSettingsDialogOpen: boolean;
|
||||
isModelDialogOpen: boolean;
|
||||
isTrustDialogOpen: boolean;
|
||||
activeArenaDialog: ArenaDialogType;
|
||||
isPermissionsDialogOpen: boolean;
|
||||
isApprovalModeDialogOpen: boolean;
|
||||
isResumeDialogOpen: boolean;
|
||||
|
|
@ -132,6 +134,8 @@ export interface UIState {
|
|||
isMcpDialogOpen: boolean;
|
||||
// Feedback dialog
|
||||
isFeedbackDialogOpen: boolean;
|
||||
// Per-task token tracking
|
||||
taskStartTokens: number;
|
||||
}
|
||||
|
||||
export const UIStateContext = createContext<UIState | null>(null);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue