mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-07-23 00:01:24 -07:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a09f6b40ff | |||
| 4e5dfd0cb7 | |||
| 524b1e39a5 | |||
| 32a123fc54 | |||
| 23264ced9a | |||
| 7de0616229 | |||
| 39d3b0e28c | |||
| 5acaacad96 | |||
| a921bcd9ef | |||
| e9171fd792 | |||
| 2009fbbd92 | |||
| 8db2948361 | |||
| 5fa14dbe42 | |||
| e6cd5d208c | |||
| c9d48026c4 |
@@ -58,6 +58,19 @@ your total token usage using the `/stats` command in Gemini CLI.
|
||||
|
||||
## Installation and updates
|
||||
|
||||
### How do I check which version of Gemini CLI I'm currently running?
|
||||
|
||||
You can check your current Gemini CLI version using one of these methods:
|
||||
|
||||
- Run `gemini --version` or `gemini -v` from your terminal
|
||||
- Check the globally installed version using your package manager:
|
||||
- npm: `npm list -g @google/gemini-cli`
|
||||
- pnpm: `pnpm list -g @google/gemini-cli`
|
||||
- yarn: `yarn global list @google/gemini-cli`
|
||||
- bun: `bun pm ls -g @google/gemini-cli`
|
||||
- homebrew: `brew list --versions gemini-cli`
|
||||
- Inside an active Gemini CLI session, use the `/about` command
|
||||
|
||||
### How do I update Gemini CLI to the latest version?
|
||||
|
||||
If you installed it globally via `npm`, update it using the command
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I'll help you with that."},{"functionCall":{"name":"browser_agent","args":{"task":"Open https://example.com and check if there is a heading"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":50,"totalTokenCount":150}}]}
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"new_page","args":{"url":"https://example.com"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":50,"totalTokenCount":150}}]}
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"take_snapshot","args":{}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":50,"totalTokenCount":150}}]}
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"complete_task","args":{"success":true,"summary":"SUCCESS_POLICY_TEST_COMPLETED"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":50,"totalTokenCount":150}}]}
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Task completed successfully. The page has the heading \"Example Domain\"."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":200,"candidatesTokenCount":50,"totalTokenCount":250}}]}
|
||||
@@ -0,0 +1,178 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { TestRig, poll } from './test-helper.js';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { execSync } from 'node:child_process';
|
||||
import { existsSync, writeFileSync, readFileSync, mkdirSync } from 'node:fs';
|
||||
import stripAnsi from 'strip-ansi';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
const chromeAvailable = (() => {
|
||||
try {
|
||||
if (process.platform === 'darwin') {
|
||||
execSync(
|
||||
'test -d "/Applications/Google Chrome.app" || test -d "/Applications/Chromium.app"',
|
||||
{
|
||||
stdio: 'ignore',
|
||||
},
|
||||
);
|
||||
} else if (process.platform === 'linux') {
|
||||
execSync(
|
||||
'which google-chrome || which chromium-browser || which chromium',
|
||||
{ stdio: 'ignore' },
|
||||
);
|
||||
} else if (process.platform === 'win32') {
|
||||
const chromePaths = [
|
||||
'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
|
||||
'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
|
||||
`${process.env['LOCALAPPDATA'] ?? ''}\\Google\\Chrome\\Application\\chrome.exe`,
|
||||
];
|
||||
const found = chromePaths.some((p) => existsSync(p));
|
||||
if (!found) {
|
||||
execSync('where chrome || where chromium', { stdio: 'ignore' });
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
})();
|
||||
|
||||
describe.skipIf(!chromeAvailable)('browser-policy', () => {
|
||||
let rig: TestRig;
|
||||
|
||||
beforeEach(() => {
|
||||
rig = new TestRig();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await rig.cleanup();
|
||||
});
|
||||
|
||||
it('should skip confirmation when "Allow all server tools for this session" is chosen', async () => {
|
||||
rig.setup('browser-policy-skip-confirmation', {
|
||||
fakeResponsesPath: join(__dirname, 'browser-policy.responses'),
|
||||
settings: {
|
||||
agents: {
|
||||
overrides: {
|
||||
browser_agent: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
browser: {
|
||||
headless: true,
|
||||
sessionMode: 'isolated',
|
||||
allowedDomains: ['example.com'],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Manually trust the folder to avoid the dialog and enable option 3
|
||||
const geminiDir = join(rig.homeDir!, '.gemini');
|
||||
mkdirSync(geminiDir, { recursive: true });
|
||||
|
||||
// Write to trustedFolders.json
|
||||
const trustedFoldersPath = join(geminiDir, 'trustedFolders.json');
|
||||
const trustedFolders = {
|
||||
[rig.testDir!]: 'TRUST_FOLDER',
|
||||
};
|
||||
writeFileSync(trustedFoldersPath, JSON.stringify(trustedFolders, null, 2));
|
||||
|
||||
// Force confirmation for browser agent.
|
||||
// NOTE: We don't force confirm browser tools here because "Allow all server tools"
|
||||
// adds a rule with ALWAYS_ALLOW_PRIORITY (3.9x) which would be overshadowed by
|
||||
// a rule in the user tier (4.x) like the one from this TOML.
|
||||
// By removing the explicit mcp rule, the first MCP tool will still prompt
|
||||
// due to default approvalMode = 'default', and then "Allow all" will correctly
|
||||
// bypass subsequent tools.
|
||||
const policyFile = join(rig.testDir!, 'force-confirm.toml');
|
||||
writeFileSync(
|
||||
policyFile,
|
||||
`
|
||||
[[rule]]
|
||||
name = "Force confirm browser_agent"
|
||||
toolName = "browser_agent"
|
||||
decision = "ask_user"
|
||||
priority = 200
|
||||
`,
|
||||
);
|
||||
|
||||
// Update settings.json in both project and home directories to point to the policy file
|
||||
for (const baseDir of [rig.testDir!, rig.homeDir!]) {
|
||||
const settingsPath = join(baseDir, '.gemini', 'settings.json');
|
||||
if (existsSync(settingsPath)) {
|
||||
const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
|
||||
settings.policyPaths = [policyFile];
|
||||
// Ensure folder trust is enabled
|
||||
settings.security = settings.security || {};
|
||||
settings.security.folderTrust = settings.security.folderTrust || {};
|
||||
settings.security.folderTrust.enabled = true;
|
||||
writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
||||
}
|
||||
}
|
||||
|
||||
const run = await rig.runInteractive({
|
||||
approvalMode: 'default',
|
||||
env: {
|
||||
GEMINI_CLI_INTEGRATION_TEST: 'true',
|
||||
},
|
||||
});
|
||||
|
||||
await run.sendKeys(
|
||||
'Open https://example.com and check if there is a heading\r',
|
||||
);
|
||||
await run.sendKeys('\r');
|
||||
|
||||
// Handle confirmations.
|
||||
// 1. Initial browser_agent delegation (likely only 3 options, so use option 1: Allow once)
|
||||
await poll(
|
||||
() => stripAnsi(run.output).toLowerCase().includes('action required'),
|
||||
60000,
|
||||
1000,
|
||||
);
|
||||
await run.sendKeys('1\r');
|
||||
await new Promise((r) => setTimeout(r, 2000));
|
||||
|
||||
// Handle privacy notice
|
||||
await poll(
|
||||
() => stripAnsi(run.output).toLowerCase().includes('privacy notice'),
|
||||
5000,
|
||||
100,
|
||||
);
|
||||
await run.sendKeys('1\r');
|
||||
await new Promise((r) => setTimeout(r, 5000));
|
||||
|
||||
// new_page (MCP tool, should have 4 options, use option 3: Allow all server tools)
|
||||
await poll(
|
||||
() => {
|
||||
const stripped = stripAnsi(run.output).toLowerCase();
|
||||
return (
|
||||
stripped.includes('new_page') &&
|
||||
stripped.includes('allow all server tools for this session')
|
||||
);
|
||||
},
|
||||
60000,
|
||||
1000,
|
||||
);
|
||||
|
||||
// Select "Allow all server tools for this session" (option 3)
|
||||
await run.sendKeys('3\r');
|
||||
await new Promise((r) => setTimeout(r, 30000));
|
||||
|
||||
const output = stripAnsi(run.output).toLowerCase();
|
||||
|
||||
expect(output).toContain('browser_agent');
|
||||
expect(output).toContain('completed successfully');
|
||||
});
|
||||
});
|
||||
@@ -177,6 +177,9 @@ describe('GeminiAgent', () => {
|
||||
getHasAccessToPreviewModel: vi.fn().mockReturnValue(false),
|
||||
getCheckpointingEnabled: vi.fn().mockReturnValue(false),
|
||||
getDisableAlwaysAllow: vi.fn().mockReturnValue(false),
|
||||
get config() {
|
||||
return this;
|
||||
},
|
||||
} as unknown as Mocked<Awaited<ReturnType<typeof loadCliConfig>>>;
|
||||
mockSettings = {
|
||||
merged: {
|
||||
@@ -656,6 +659,12 @@ describe('Session', () => {
|
||||
getGitService: vi.fn().mockResolvedValue({} as GitService),
|
||||
waitForMcpInit: vi.fn(),
|
||||
getDisableAlwaysAllow: vi.fn().mockReturnValue(false),
|
||||
get config() {
|
||||
return this;
|
||||
},
|
||||
get toolRegistry() {
|
||||
return mockToolRegistry;
|
||||
},
|
||||
} as unknown as Mocked<Config>;
|
||||
mockConnection = {
|
||||
sessionUpdate: vi.fn(),
|
||||
@@ -894,6 +903,9 @@ describe('Session', () => {
|
||||
update: expect.objectContaining({
|
||||
sessionUpdate: 'tool_call_update',
|
||||
status: 'completed',
|
||||
title: 'Test Tool',
|
||||
locations: [],
|
||||
kind: 'read',
|
||||
}),
|
||||
}),
|
||||
);
|
||||
@@ -1306,6 +1318,18 @@ describe('Session', () => {
|
||||
expect(path.resolve).toHaveBeenCalled();
|
||||
expect(fs.stat).toHaveBeenCalled();
|
||||
|
||||
expect(mockConnection.sessionUpdate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
update: expect.objectContaining({
|
||||
sessionUpdate: 'tool_call_update',
|
||||
status: 'completed',
|
||||
title: 'Read files',
|
||||
locations: [],
|
||||
kind: 'read',
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
// Verify ReadManyFilesTool was used (implicitly by checking if sendMessageStream was called with resolved content)
|
||||
// Since we mocked ReadManyFilesTool to return specific content, we can check the args passed to sendMessageStream
|
||||
expect(mockChat.sendMessageStream).toHaveBeenCalledWith(
|
||||
@@ -1321,6 +1345,65 @@ describe('Session', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle @path resolution error', async () => {
|
||||
(path.resolve as unknown as Mock).mockReturnValue('/tmp/error.txt');
|
||||
(fs.stat as unknown as Mock).mockResolvedValue({
|
||||
isDirectory: () => false,
|
||||
});
|
||||
(isWithinRoot as unknown as Mock).mockReturnValue(true);
|
||||
|
||||
const MockReadManyFilesTool = ReadManyFilesTool as unknown as Mock;
|
||||
MockReadManyFilesTool.mockImplementationOnce(() => ({
|
||||
name: 'read_many_files',
|
||||
kind: 'read',
|
||||
build: vi.fn().mockReturnValue({
|
||||
getDescription: () => 'Read files',
|
||||
toolLocations: () => [],
|
||||
execute: vi.fn().mockRejectedValue(new Error('File read failed')),
|
||||
}),
|
||||
}));
|
||||
|
||||
const stream = createMockStream([
|
||||
{
|
||||
type: StreamEventType.CHUNK,
|
||||
value: { candidates: [] },
|
||||
},
|
||||
]);
|
||||
mockChat.sendMessageStream.mockResolvedValue(stream);
|
||||
|
||||
await expect(
|
||||
session.prompt({
|
||||
sessionId: 'session-1',
|
||||
prompt: [
|
||||
{ type: 'text', text: 'Read' },
|
||||
{
|
||||
type: 'resource_link',
|
||||
uri: 'file://error.txt',
|
||||
mimeType: 'text/plain',
|
||||
name: 'error.txt',
|
||||
},
|
||||
],
|
||||
}),
|
||||
).rejects.toThrow('File read failed');
|
||||
|
||||
expect(mockConnection.sessionUpdate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
update: expect.objectContaining({
|
||||
sessionUpdate: 'tool_call_update',
|
||||
status: 'failed',
|
||||
content: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
content: expect.objectContaining({
|
||||
text: expect.stringMatching(/File read failed/),
|
||||
}),
|
||||
}),
|
||||
]),
|
||||
kind: 'read',
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle cancellation during prompt', async () => {
|
||||
let streamController: ReadableStreamDefaultController<unknown>;
|
||||
const stream = new ReadableStream({
|
||||
@@ -1434,6 +1517,7 @@ describe('Session', () => {
|
||||
content: expect.objectContaining({ text: 'Tool failed' }),
|
||||
}),
|
||||
]),
|
||||
kind: 'read',
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -47,6 +47,7 @@ import {
|
||||
DEFAULT_GEMINI_MODEL_AUTO,
|
||||
PREVIEW_GEMINI_MODEL_AUTO,
|
||||
getDisplayString,
|
||||
type AgentLoopContext,
|
||||
} from '@google/gemini-cli-core';
|
||||
import * as acp from '@agentclientprotocol/sdk';
|
||||
import { AcpFileSystemService } from './fileSystemService.js';
|
||||
@@ -104,7 +105,7 @@ export class GeminiAgent {
|
||||
private customHeaders: Record<string, string> | undefined;
|
||||
|
||||
constructor(
|
||||
private config: Config,
|
||||
private context: AgentLoopContext,
|
||||
private settings: LoadedSettings,
|
||||
private argv: CliArgs,
|
||||
private connection: acp.AgentSideConnection,
|
||||
@@ -148,7 +149,7 @@ export class GeminiAgent {
|
||||
},
|
||||
];
|
||||
|
||||
await this.config.initialize();
|
||||
await this.context.config.initialize();
|
||||
const version = await getVersion();
|
||||
return {
|
||||
protocolVersion: acp.PROTOCOL_VERSION,
|
||||
@@ -220,7 +221,7 @@ export class GeminiAgent {
|
||||
this.baseUrl = baseUrl;
|
||||
this.customHeaders = headers;
|
||||
|
||||
await this.config.refreshAuth(
|
||||
await this.context.config.refreshAuth(
|
||||
method,
|
||||
apiKey ?? this.apiKey,
|
||||
baseUrl,
|
||||
@@ -537,7 +538,7 @@ export class Session {
|
||||
constructor(
|
||||
private readonly id: string,
|
||||
private readonly chat: GeminiChat,
|
||||
private readonly config: Config,
|
||||
private readonly context: AgentLoopContext,
|
||||
private readonly connection: acp.AgentSideConnection,
|
||||
private readonly settings: LoadedSettings,
|
||||
) {}
|
||||
@@ -552,13 +553,15 @@ export class Session {
|
||||
}
|
||||
|
||||
setMode(modeId: acp.SessionModeId): acp.SetSessionModeResponse {
|
||||
const availableModes = buildAvailableModes(this.config.isPlanEnabled());
|
||||
const availableModes = buildAvailableModes(
|
||||
this.context.config.isPlanEnabled(),
|
||||
);
|
||||
const mode = availableModes.find((m) => m.id === modeId);
|
||||
if (!mode) {
|
||||
throw new Error(`Invalid or unavailable mode: ${modeId}`);
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
this.config.setApprovalMode(mode.id as ApprovalMode);
|
||||
this.context.config.setApprovalMode(mode.id as ApprovalMode);
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -579,7 +582,7 @@ export class Session {
|
||||
}
|
||||
|
||||
setModel(modelId: acp.ModelId): acp.SetSessionModelResponse {
|
||||
this.config.setModel(modelId);
|
||||
this.context.config.setModel(modelId);
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -634,7 +637,7 @@ export class Session {
|
||||
}
|
||||
}
|
||||
|
||||
const tool = this.config.getToolRegistry().getTool(toolCall.name);
|
||||
const tool = this.context.toolRegistry.getTool(toolCall.name);
|
||||
|
||||
await this.sendUpdate({
|
||||
sessionUpdate: 'tool_call',
|
||||
@@ -658,7 +661,7 @@ export class Session {
|
||||
const pendingSend = new AbortController();
|
||||
this.pendingPrompt = pendingSend;
|
||||
|
||||
await this.config.waitForMcpInit();
|
||||
await this.context.config.waitForMcpInit();
|
||||
|
||||
const promptId = Math.random().toString(16).slice(2);
|
||||
const chat = this.chat;
|
||||
@@ -712,8 +715,8 @@ export class Session {
|
||||
|
||||
try {
|
||||
const model = resolveModel(
|
||||
this.config.getModel(),
|
||||
(await this.config.getGemini31Launched?.()) ?? false,
|
||||
this.context.config.getModel(),
|
||||
(await this.context.config.getGemini31Launched?.()) ?? false,
|
||||
);
|
||||
const responseStream = await chat.sendMessageStream(
|
||||
{ model },
|
||||
@@ -804,9 +807,9 @@ export class Session {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
parts: Part[],
|
||||
): Promise<boolean> {
|
||||
const gitService = await this.config.getGitService();
|
||||
const gitService = await this.context.config.getGitService();
|
||||
const commandContext = {
|
||||
config: this.config,
|
||||
agentContext: this.context,
|
||||
settings: this.settings,
|
||||
git: gitService,
|
||||
sendMessage: async (text: string) => {
|
||||
@@ -842,7 +845,7 @@ export class Session {
|
||||
const errorResponse = (error: Error) => {
|
||||
const durationMs = Date.now() - startTime;
|
||||
logToolCall(
|
||||
this.config,
|
||||
this.context.config,
|
||||
new ToolCallEvent(
|
||||
undefined,
|
||||
fc.name ?? '',
|
||||
@@ -872,7 +875,7 @@ export class Session {
|
||||
return errorResponse(new Error('Missing function name'));
|
||||
}
|
||||
|
||||
const toolRegistry = this.config.getToolRegistry();
|
||||
const toolRegistry = this.context.toolRegistry;
|
||||
const tool = toolRegistry.getTool(fc.name);
|
||||
|
||||
if (!tool) {
|
||||
@@ -908,7 +911,10 @@ export class Session {
|
||||
|
||||
const params: acp.RequestPermissionRequest = {
|
||||
sessionId: this.id,
|
||||
options: toPermissionOptions(confirmationDetails, this.config),
|
||||
options: toPermissionOptions(
|
||||
confirmationDetails,
|
||||
this.context.config,
|
||||
),
|
||||
toolCall: {
|
||||
toolCallId: callId,
|
||||
status: 'pending',
|
||||
@@ -966,12 +972,15 @@ export class Session {
|
||||
sessionUpdate: 'tool_call_update',
|
||||
toolCallId: callId,
|
||||
status: 'completed',
|
||||
title: invocation.getDescription(),
|
||||
content: content ? [content] : [],
|
||||
locations: invocation.toolLocations(),
|
||||
kind: toAcpToolKind(tool.kind),
|
||||
});
|
||||
|
||||
const durationMs = Date.now() - startTime;
|
||||
logToolCall(
|
||||
this.config,
|
||||
this.context.config,
|
||||
new ToolCallEvent(
|
||||
undefined,
|
||||
fc.name ?? '',
|
||||
@@ -985,7 +994,7 @@ export class Session {
|
||||
),
|
||||
);
|
||||
|
||||
this.chat.recordCompletedToolCalls(this.config.getActiveModel(), [
|
||||
this.chat.recordCompletedToolCalls(this.context.config.getActiveModel(), [
|
||||
{
|
||||
status: CoreToolCallStatus.Success,
|
||||
request: {
|
||||
@@ -1003,8 +1012,8 @@ export class Session {
|
||||
fc.name,
|
||||
callId,
|
||||
toolResult.llmContent,
|
||||
this.config.getActiveModel(),
|
||||
this.config,
|
||||
this.context.config.getActiveModel(),
|
||||
this.context.config,
|
||||
),
|
||||
resultDisplay: toolResult.returnDisplay,
|
||||
error: undefined,
|
||||
@@ -1017,8 +1026,8 @@ export class Session {
|
||||
fc.name,
|
||||
callId,
|
||||
toolResult.llmContent,
|
||||
this.config.getActiveModel(),
|
||||
this.config,
|
||||
this.context.config.getActiveModel(),
|
||||
this.context.config,
|
||||
);
|
||||
} catch (e) {
|
||||
const error = e instanceof Error ? e : new Error(String(e));
|
||||
@@ -1030,9 +1039,10 @@ export class Session {
|
||||
content: [
|
||||
{ type: 'content', content: { type: 'text', text: error.message } },
|
||||
],
|
||||
kind: toAcpToolKind(tool.kind),
|
||||
});
|
||||
|
||||
this.chat.recordCompletedToolCalls(this.config.getActiveModel(), [
|
||||
this.chat.recordCompletedToolCalls(this.context.config.getActiveModel(), [
|
||||
{
|
||||
status: CoreToolCallStatus.Error,
|
||||
request: {
|
||||
@@ -1118,18 +1128,18 @@ export class Session {
|
||||
const atPathToResolvedSpecMap = new Map<string, string>();
|
||||
|
||||
// Get centralized file discovery service
|
||||
const fileDiscovery = this.config.getFileService();
|
||||
const fileDiscovery = this.context.config.getFileService();
|
||||
const fileFilteringOptions: FilterFilesOptions =
|
||||
this.config.getFileFilteringOptions();
|
||||
this.context.config.getFileFilteringOptions();
|
||||
|
||||
const pathSpecsToRead: string[] = [];
|
||||
const contentLabelsForDisplay: string[] = [];
|
||||
const ignoredPaths: string[] = [];
|
||||
|
||||
const toolRegistry = this.config.getToolRegistry();
|
||||
const toolRegistry = this.context.toolRegistry;
|
||||
const readManyFilesTool = new ReadManyFilesTool(
|
||||
this.config,
|
||||
this.config.getMessageBus(),
|
||||
this.context.config,
|
||||
this.context.messageBus,
|
||||
);
|
||||
const globTool = toolRegistry.getTool('glob');
|
||||
|
||||
@@ -1148,8 +1158,11 @@ export class Session {
|
||||
let currentPathSpec = pathName;
|
||||
let resolvedSuccessfully = false;
|
||||
try {
|
||||
const absolutePath = path.resolve(this.config.getTargetDir(), pathName);
|
||||
if (isWithinRoot(absolutePath, this.config.getTargetDir())) {
|
||||
const absolutePath = path.resolve(
|
||||
this.context.config.getTargetDir(),
|
||||
pathName,
|
||||
);
|
||||
if (isWithinRoot(absolutePath, this.context.config.getTargetDir())) {
|
||||
const stats = await fs.stat(absolutePath);
|
||||
if (stats.isDirectory()) {
|
||||
currentPathSpec = pathName.endsWith('/')
|
||||
@@ -1169,7 +1182,7 @@ export class Session {
|
||||
}
|
||||
} catch (error) {
|
||||
if (isNodeError(error) && error.code === 'ENOENT') {
|
||||
if (this.config.getEnableRecursiveFileSearch() && globTool) {
|
||||
if (this.context.config.getEnableRecursiveFileSearch() && globTool) {
|
||||
this.debug(
|
||||
`Path ${pathName} not found directly, attempting glob search.`,
|
||||
);
|
||||
@@ -1177,7 +1190,7 @@ export class Session {
|
||||
const globResult = await globTool.buildAndExecute(
|
||||
{
|
||||
pattern: `**/*${pathName}*`,
|
||||
path: this.config.getTargetDir(),
|
||||
path: this.context.config.getTargetDir(),
|
||||
},
|
||||
abortSignal,
|
||||
);
|
||||
@@ -1191,7 +1204,7 @@ export class Session {
|
||||
if (lines.length > 1 && lines[1]) {
|
||||
const firstMatchAbsolute = lines[1].trim();
|
||||
currentPathSpec = path.relative(
|
||||
this.config.getTargetDir(),
|
||||
this.context.config.getTargetDir(),
|
||||
firstMatchAbsolute,
|
||||
);
|
||||
this.debug(
|
||||
@@ -1324,7 +1337,10 @@ export class Session {
|
||||
sessionUpdate: 'tool_call_update',
|
||||
toolCallId: callId,
|
||||
status: 'completed',
|
||||
title: invocation.getDescription(),
|
||||
content: content ? [content] : [],
|
||||
locations: invocation.toolLocations(),
|
||||
kind: toAcpToolKind(readManyFilesTool.kind),
|
||||
});
|
||||
if (Array.isArray(result.llmContent)) {
|
||||
const fileContentRegex = /^--- (.*?) ---\n\n([\s\S]*?)\n\n$/;
|
||||
@@ -1368,6 +1384,7 @@ export class Session {
|
||||
},
|
||||
},
|
||||
],
|
||||
kind: toAcpToolKind(readManyFilesTool.kind),
|
||||
});
|
||||
|
||||
throw error;
|
||||
@@ -1402,7 +1419,7 @@ export class Session {
|
||||
}
|
||||
|
||||
debug(msg: string) {
|
||||
if (this.config.getDebugMode()) {
|
||||
if (this.context.config.getDebugMode()) {
|
||||
debugLogger.warn(msg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +97,9 @@ describe('GeminiAgent Session Resume', () => {
|
||||
getHasAccessToPreviewModel: vi.fn().mockReturnValue(false),
|
||||
getGemini31LaunchedSync: vi.fn().mockReturnValue(false),
|
||||
getCheckpointingEnabled: vi.fn().mockReturnValue(false),
|
||||
get config() {
|
||||
return this;
|
||||
},
|
||||
} as unknown as Mocked<Config>;
|
||||
mockSettings = {
|
||||
merged: {
|
||||
@@ -158,9 +161,10 @@ describe('GeminiAgent Session Resume', () => {
|
||||
],
|
||||
};
|
||||
|
||||
mockConfig.getToolRegistry = vi.fn().mockReturnValue({
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(mockConfig as any).toolRegistry = {
|
||||
getTool: vi.fn().mockReturnValue({ kind: 'read' }),
|
||||
});
|
||||
};
|
||||
|
||||
(SessionSelector as unknown as Mock).mockImplementation(() => ({
|
||||
resolveSession: vi.fn().mockResolvedValue({
|
||||
|
||||
@@ -53,7 +53,7 @@ export class ListExtensionsCommand implements Command {
|
||||
context: CommandContext,
|
||||
_: string[],
|
||||
): Promise<CommandExecutionResponse> {
|
||||
const extensions = listExtensions(context.config);
|
||||
const extensions = listExtensions(context.agentContext.config);
|
||||
const data = extensions.length ? extensions : 'No extensions installed.';
|
||||
|
||||
return { name: this.name, data };
|
||||
@@ -134,7 +134,7 @@ export class EnableExtensionCommand implements Command {
|
||||
args: string[],
|
||||
): Promise<CommandExecutionResponse> {
|
||||
const enableContext = getEnableDisableContext(
|
||||
context.config,
|
||||
context.agentContext.config,
|
||||
args,
|
||||
'enable',
|
||||
);
|
||||
@@ -156,7 +156,8 @@ export class EnableExtensionCommand implements Command {
|
||||
|
||||
if (extension?.mcpServers) {
|
||||
const mcpEnablementManager = McpServerEnablementManager.getInstance();
|
||||
const mcpClientManager = context.config.getMcpClientManager();
|
||||
const mcpClientManager =
|
||||
context.agentContext.config.getMcpClientManager();
|
||||
const enabledServers = await mcpEnablementManager.autoEnableServers(
|
||||
Object.keys(extension.mcpServers),
|
||||
);
|
||||
@@ -191,7 +192,7 @@ export class DisableExtensionCommand implements Command {
|
||||
args: string[],
|
||||
): Promise<CommandExecutionResponse> {
|
||||
const enableContext = getEnableDisableContext(
|
||||
context.config,
|
||||
context.agentContext.config,
|
||||
args,
|
||||
'disable',
|
||||
);
|
||||
@@ -223,7 +224,7 @@ export class InstallExtensionCommand implements Command {
|
||||
context: CommandContext,
|
||||
args: string[],
|
||||
): Promise<CommandExecutionResponse> {
|
||||
const extensionLoader = context.config.getExtensionLoader();
|
||||
const extensionLoader = context.agentContext.config.getExtensionLoader();
|
||||
if (!(extensionLoader instanceof ExtensionManager)) {
|
||||
return {
|
||||
name: this.name,
|
||||
@@ -268,7 +269,7 @@ export class LinkExtensionCommand implements Command {
|
||||
context: CommandContext,
|
||||
args: string[],
|
||||
): Promise<CommandExecutionResponse> {
|
||||
const extensionLoader = context.config.getExtensionLoader();
|
||||
const extensionLoader = context.agentContext.config.getExtensionLoader();
|
||||
if (!(extensionLoader instanceof ExtensionManager)) {
|
||||
return {
|
||||
name: this.name,
|
||||
@@ -313,7 +314,7 @@ export class UninstallExtensionCommand implements Command {
|
||||
context: CommandContext,
|
||||
args: string[],
|
||||
): Promise<CommandExecutionResponse> {
|
||||
const extensionLoader = context.config.getExtensionLoader();
|
||||
const extensionLoader = context.agentContext.config.getExtensionLoader();
|
||||
if (!(extensionLoader instanceof ExtensionManager)) {
|
||||
return {
|
||||
name: this.name,
|
||||
@@ -369,7 +370,7 @@ export class RestartExtensionCommand implements Command {
|
||||
context: CommandContext,
|
||||
args: string[],
|
||||
): Promise<CommandExecutionResponse> {
|
||||
const extensionLoader = context.config.getExtensionLoader();
|
||||
const extensionLoader = context.agentContext.config.getExtensionLoader();
|
||||
if (!(extensionLoader instanceof ExtensionManager)) {
|
||||
return { name: this.name, data: 'Cannot restart extensions.' };
|
||||
}
|
||||
@@ -424,7 +425,7 @@ export class UpdateExtensionCommand implements Command {
|
||||
context: CommandContext,
|
||||
args: string[],
|
||||
): Promise<CommandExecutionResponse> {
|
||||
const extensionLoader = context.config.getExtensionLoader();
|
||||
const extensionLoader = context.agentContext.config.getExtensionLoader();
|
||||
if (!(extensionLoader instanceof ExtensionManager)) {
|
||||
return { name: this.name, data: 'Cannot update extensions.' };
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ export class InitCommand implements Command {
|
||||
context: CommandContext,
|
||||
_args: string[] = [],
|
||||
): Promise<CommandExecutionResponse> {
|
||||
const targetDir = context.config.getTargetDir();
|
||||
const targetDir = context.agentContext.config.getTargetDir();
|
||||
if (!targetDir) {
|
||||
throw new Error('Command requires a workspace.');
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ export class ShowMemoryCommand implements Command {
|
||||
context: CommandContext,
|
||||
_: string[],
|
||||
): Promise<CommandExecutionResponse> {
|
||||
const result = showMemory(context.config);
|
||||
const result = showMemory(context.agentContext.config);
|
||||
return { name: this.name, data: result.content };
|
||||
}
|
||||
}
|
||||
@@ -63,7 +63,7 @@ export class RefreshMemoryCommand implements Command {
|
||||
context: CommandContext,
|
||||
_: string[],
|
||||
): Promise<CommandExecutionResponse> {
|
||||
const result = await refreshMemory(context.config);
|
||||
const result = await refreshMemory(context.agentContext.config);
|
||||
return { name: this.name, data: result.content };
|
||||
}
|
||||
}
|
||||
@@ -76,7 +76,7 @@ export class ListMemoryCommand implements Command {
|
||||
context: CommandContext,
|
||||
_: string[],
|
||||
): Promise<CommandExecutionResponse> {
|
||||
const result = listMemoryFiles(context.config);
|
||||
const result = listMemoryFiles(context.agentContext.config);
|
||||
return { name: this.name, data: result.content };
|
||||
}
|
||||
}
|
||||
@@ -95,7 +95,7 @@ export class AddMemoryCommand implements Command {
|
||||
return { name: this.name, data: result.content };
|
||||
}
|
||||
|
||||
const toolRegistry = context.config.getToolRegistry();
|
||||
const toolRegistry = context.agentContext.toolRegistry;
|
||||
const tool = toolRegistry.getTool(result.toolName);
|
||||
if (tool) {
|
||||
const abortController = new AbortController();
|
||||
@@ -106,10 +106,10 @@ export class AddMemoryCommand implements Command {
|
||||
await tool.buildAndExecute(result.toolArgs, signal, undefined, {
|
||||
shellExecutionConfig: {
|
||||
sanitizationConfig: DEFAULT_SANITIZATION_CONFIG,
|
||||
sandboxManager: context.config.sandboxManager,
|
||||
sandboxManager: context.agentContext.sandboxManager,
|
||||
},
|
||||
});
|
||||
await refreshMemory(context.config);
|
||||
await refreshMemory(context.agentContext.config);
|
||||
return {
|
||||
name: this.name,
|
||||
data: `Added memory: "${textToAdd}"`,
|
||||
|
||||
@@ -29,7 +29,8 @@ export class RestoreCommand implements Command {
|
||||
context: CommandContext,
|
||||
args: string[],
|
||||
): Promise<CommandExecutionResponse> {
|
||||
const { config, git: gitService } = context;
|
||||
const { agentContext: agentContext, git: gitService } = context;
|
||||
const { config } = agentContext;
|
||||
const argsStr = args.join(' ');
|
||||
|
||||
try {
|
||||
@@ -116,7 +117,7 @@ export class ListCheckpointsCommand implements Command {
|
||||
readonly description = 'Lists all available checkpoints.';
|
||||
|
||||
async execute(context: CommandContext): Promise<CommandExecutionResponse> {
|
||||
const { config } = context;
|
||||
const { config } = context.agentContext;
|
||||
|
||||
try {
|
||||
if (!config.getCheckpointingEnabled()) {
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type { Config, GitService } from '@google/gemini-cli-core';
|
||||
import type { AgentLoopContext, GitService } from '@google/gemini-cli-core';
|
||||
import type { LoadedSettings } from '../../config/settings.js';
|
||||
|
||||
export interface CommandContext {
|
||||
config: Config;
|
||||
agentContext: AgentLoopContext;
|
||||
settings: LoadedSettings;
|
||||
git?: GitService;
|
||||
sendMessage: (text: string) => Promise<void>;
|
||||
|
||||
@@ -419,6 +419,14 @@ export async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.merged.advanced.autoConfigureMemory) {
|
||||
const heapStats = v8.getHeapStatistics();
|
||||
const currentMaxOldSpaceSizeMb = Math.floor(
|
||||
heapStats.heap_size_limit / 1024 / 1024,
|
||||
);
|
||||
writeToStderr(`Allocated memory: ${currentMaxOldSpaceSizeMb} MB\n`);
|
||||
}
|
||||
|
||||
// We are now past the logic handling potentially launching a child process
|
||||
// to run Gemini CLI. It is now safe to perform expensive initialization that
|
||||
// may have side effects.
|
||||
|
||||
@@ -29,7 +29,7 @@ describe('Model Steering Integration', () => {
|
||||
configOverrides: { modelSteering: true },
|
||||
});
|
||||
await rig.initialize();
|
||||
rig.render();
|
||||
await rig.render();
|
||||
await rig.waitForIdle();
|
||||
|
||||
rig.setToolPolicy('list_directory', PolicyDecision.ASK_USER);
|
||||
|
||||
@@ -65,9 +65,9 @@ export const handleSlashCommand = async (
|
||||
|
||||
const logger = new Logger(config?.getSessionId() || '', config?.storage);
|
||||
|
||||
const context: CommandContext = {
|
||||
const commandContext: CommandContext = {
|
||||
services: {
|
||||
config,
|
||||
agentContext: config,
|
||||
settings,
|
||||
git: undefined,
|
||||
logger,
|
||||
@@ -84,7 +84,7 @@ export const handleSlashCommand = async (
|
||||
},
|
||||
};
|
||||
|
||||
const result = await commandToExecute.action(context, args);
|
||||
const result = await commandToExecute.action(commandContext, args);
|
||||
|
||||
if (result) {
|
||||
switch (result.type) {
|
||||
|
||||
@@ -31,11 +31,14 @@ describe('AtFileProcessor', () => {
|
||||
|
||||
mockConfig = {
|
||||
// The processor only passes the config through, so we don't need a full mock.
|
||||
get config() {
|
||||
return this;
|
||||
},
|
||||
} as unknown as Config;
|
||||
|
||||
context = createMockCommandContext({
|
||||
services: {
|
||||
config: mockConfig,
|
||||
agentContext: mockConfig,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -60,7 +63,7 @@ describe('AtFileProcessor', () => {
|
||||
const prompt: PartUnion[] = [{ text: 'Analyze @{file.txt}' }];
|
||||
const contextWithoutConfig = createMockCommandContext({
|
||||
services: {
|
||||
config: null,
|
||||
agentContext: null,
|
||||
},
|
||||
});
|
||||
const result = await processor.process(prompt, contextWithoutConfig);
|
||||
|
||||
@@ -25,7 +25,7 @@ export class AtFileProcessor implements IPromptProcessor {
|
||||
input: PromptPipelineContent,
|
||||
context: CommandContext,
|
||||
): Promise<PromptPipelineContent> {
|
||||
const config = context.services.config;
|
||||
const config = context.services.agentContext?.config;
|
||||
if (!config) {
|
||||
return input;
|
||||
}
|
||||
|
||||
@@ -89,6 +89,9 @@ describe('ShellProcessor', () => {
|
||||
getPolicyEngine: vi.fn().mockReturnValue({
|
||||
check: mockPolicyEngineCheck,
|
||||
}),
|
||||
get config() {
|
||||
return this as unknown as Config;
|
||||
},
|
||||
};
|
||||
|
||||
context = createMockCommandContext({
|
||||
@@ -98,7 +101,7 @@ describe('ShellProcessor', () => {
|
||||
args: 'default args',
|
||||
},
|
||||
services: {
|
||||
config: mockConfig as Config,
|
||||
agentContext: mockConfig as Config,
|
||||
},
|
||||
session: {
|
||||
sessionShellAllowlist: new Set(),
|
||||
@@ -120,7 +123,7 @@ describe('ShellProcessor', () => {
|
||||
const prompt: PromptPipelineContent = createPromptPipelineContent('!{ls}');
|
||||
const contextWithoutConfig = createMockCommandContext({
|
||||
services: {
|
||||
config: null,
|
||||
agentContext: null,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ export class ShellProcessor implements IPromptProcessor {
|
||||
];
|
||||
}
|
||||
|
||||
const config = context.services.config;
|
||||
const config = context.services.agentContext?.config;
|
||||
if (!config) {
|
||||
throw new Error(
|
||||
`Security configuration not loaded. Cannot verify shell command permissions for '${this.commandName}'. Aborting.`,
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
*/
|
||||
|
||||
import { describe, it, afterEach, expect } from 'vitest';
|
||||
import { act } from 'react';
|
||||
import { AppRig } from './AppRig.js';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
@@ -31,7 +30,7 @@ describe('AppRig', () => {
|
||||
configOverrides: { modelSteering: true },
|
||||
});
|
||||
await rig.initialize();
|
||||
rig.render();
|
||||
await rig.render();
|
||||
await rig.waitForIdle();
|
||||
|
||||
// Set breakpoints on the canonical tool names
|
||||
@@ -69,12 +68,7 @@ describe('AppRig', () => {
|
||||
);
|
||||
rig = new AppRig({ fakeResponsesPath });
|
||||
await rig.initialize();
|
||||
await act(async () => {
|
||||
rig!.render();
|
||||
// Allow async initializations (like banners) to settle within the act boundary
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
});
|
||||
|
||||
await rig.render();
|
||||
// Wait for initial render
|
||||
await rig.waitForIdle();
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs';
|
||||
import { AppContainer } from '../ui/AppContainer.js';
|
||||
import { renderWithProviders } from './render.js';
|
||||
import { renderWithProviders, type RenderInstance } from './render.js';
|
||||
import {
|
||||
makeFakeConfig,
|
||||
type Config,
|
||||
@@ -155,7 +155,7 @@ export interface PendingConfirmation {
|
||||
}
|
||||
|
||||
export class AppRig {
|
||||
private renderResult: ReturnType<typeof renderWithProviders> | undefined;
|
||||
private renderResult: RenderInstance | undefined;
|
||||
private config: Config | undefined;
|
||||
private settings: LoadedSettings | undefined;
|
||||
private testDir: string;
|
||||
@@ -393,12 +393,12 @@ export class AppRig {
|
||||
return isAnyToolActive || isAwaitingConfirmation;
|
||||
}
|
||||
|
||||
render() {
|
||||
async render() {
|
||||
if (!this.config || !this.settings)
|
||||
throw new Error('AppRig not initialized');
|
||||
|
||||
act(() => {
|
||||
this.renderResult = renderWithProviders(
|
||||
await act(async () => {
|
||||
this.renderResult = await renderWithProviders(
|
||||
<AppContainer
|
||||
config={this.config!}
|
||||
version="test-version"
|
||||
|
||||
@@ -46,15 +46,19 @@ describe('createMockCommandContext', () => {
|
||||
|
||||
const overrides = {
|
||||
services: {
|
||||
config: mockConfig,
|
||||
agentContext: { config: mockConfig },
|
||||
},
|
||||
};
|
||||
|
||||
const context = createMockCommandContext(overrides);
|
||||
|
||||
expect(context.services.config).toBeDefined();
|
||||
expect(context.services.config?.getModel()).toBe('gemini-pro');
|
||||
expect(context.services.config?.getProjectRoot()).toBe('/test/project');
|
||||
expect(context.services.agentContext).toBeDefined();
|
||||
expect(context.services.agentContext?.config?.getModel()).toBe(
|
||||
'gemini-pro',
|
||||
);
|
||||
expect(context.services.agentContext?.config?.getProjectRoot()).toBe(
|
||||
'/test/project',
|
||||
);
|
||||
|
||||
// Verify a default property on the same nested object is still there
|
||||
expect(context.services.logger).toBeDefined();
|
||||
|
||||
@@ -36,7 +36,7 @@ export const createMockCommandContext = (
|
||||
args: '',
|
||||
},
|
||||
services: {
|
||||
config: null,
|
||||
agentContext: null,
|
||||
|
||||
settings: {
|
||||
merged: defaultMergedSettings,
|
||||
|
||||
@@ -16,8 +16,6 @@ import { vi } from 'vitest';
|
||||
import stripAnsi from 'strip-ansi';
|
||||
import type React from 'react';
|
||||
import { act, useState } from 'react';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import type { LoadedSettings } from '../config/settings.js';
|
||||
import { KeypressProvider } from '../ui/contexts/KeypressContext.js';
|
||||
import { SettingsContext } from '../ui/contexts/SettingsContext.js';
|
||||
@@ -44,7 +42,7 @@ import {
|
||||
type OverflowState,
|
||||
} from '../ui/contexts/OverflowContext.js';
|
||||
|
||||
import { makeFakeConfig, type Config } from '@google/gemini-cli-core';
|
||||
import { type Config } from '@google/gemini-cli-core';
|
||||
import { FakePersistentState } from './persistentStateFake.js';
|
||||
import { AppContext, type AppState } from '../ui/contexts/AppContext.js';
|
||||
import { createMockSettings } from './settings.js';
|
||||
@@ -53,6 +51,7 @@ import { themeManager, DEFAULT_THEME } from '../ui/themes/theme-manager.js';
|
||||
import { DefaultLight } from '../ui/themes/builtin/light/default-light.js';
|
||||
import { pickDefaultThemeName } from '../ui/themes/theme.js';
|
||||
import { generateSvgForTerminal } from './svg.js';
|
||||
import { loadCliConfig, type CliArgs } from '../config/config.js';
|
||||
|
||||
export const persistentStateMock = new FakePersistentState();
|
||||
|
||||
@@ -66,7 +65,9 @@ if (process.env['NODE_ENV'] === 'test') {
|
||||
}
|
||||
|
||||
vi.mock('../utils/persistentState.js', () => ({
|
||||
persistentState: persistentStateMock,
|
||||
get persistentState() {
|
||||
return persistentStateMock;
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../ui/utils/terminalUtils.js', () => ({
|
||||
@@ -486,50 +487,6 @@ export const simulateClick = async (
|
||||
});
|
||||
};
|
||||
|
||||
let mockConfigInternal: Config | undefined;
|
||||
|
||||
const getMockConfigInternal = (): Config => {
|
||||
if (!mockConfigInternal) {
|
||||
mockConfigInternal = makeFakeConfig({
|
||||
targetDir: os.tmpdir(),
|
||||
enableEventDrivenScheduler: true,
|
||||
});
|
||||
}
|
||||
return mockConfigInternal;
|
||||
};
|
||||
|
||||
const configProxy = new Proxy({} as Config, {
|
||||
get(_target, prop) {
|
||||
if (prop === 'getTargetDir') {
|
||||
return () =>
|
||||
path.join(
|
||||
path.parse(process.cwd()).root,
|
||||
'Users',
|
||||
'test',
|
||||
'project',
|
||||
'foo',
|
||||
'bar',
|
||||
'and',
|
||||
'some',
|
||||
'more',
|
||||
'directories',
|
||||
'to',
|
||||
'make',
|
||||
'it',
|
||||
'long',
|
||||
);
|
||||
}
|
||||
if (prop === 'getUseBackgroundColor') {
|
||||
return () => true;
|
||||
}
|
||||
const internal = getMockConfigInternal();
|
||||
if (prop in internal) {
|
||||
return internal[prop as keyof typeof internal];
|
||||
}
|
||||
throw new Error(`mockConfig does not have property ${String(prop)}`);
|
||||
},
|
||||
});
|
||||
|
||||
export const mockSettings = createMockSettings();
|
||||
|
||||
// A minimal mock UIState to satisfy the context provider.
|
||||
@@ -639,7 +596,7 @@ const ContextCapture: React.FC<{ children: React.ReactNode }> = ({
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
export const renderWithProviders = (
|
||||
export const renderWithProviders = async (
|
||||
component: React.ReactElement,
|
||||
{
|
||||
shellFocus = true,
|
||||
@@ -647,8 +604,7 @@ export const renderWithProviders = (
|
||||
uiState: providedUiState,
|
||||
width,
|
||||
mouseEventsEnabled = false,
|
||||
|
||||
config = configProxy as unknown as Config,
|
||||
config,
|
||||
uiActions,
|
||||
persistentState,
|
||||
appState = mockAppState,
|
||||
@@ -666,13 +622,15 @@ export const renderWithProviders = (
|
||||
};
|
||||
appState?: AppState;
|
||||
} = {},
|
||||
): RenderInstance & {
|
||||
simulateClick: (
|
||||
col: number,
|
||||
row: number,
|
||||
button?: 0 | 1 | 2,
|
||||
) => Promise<void>;
|
||||
} => {
|
||||
): Promise<
|
||||
RenderInstance & {
|
||||
simulateClick: (
|
||||
col: number,
|
||||
row: number,
|
||||
button?: 0 | 1 | 2,
|
||||
) => Promise<void>;
|
||||
}
|
||||
> => {
|
||||
const baseState: UIState = new Proxy(
|
||||
{ ...baseMockUiState, ...providedUiState },
|
||||
{
|
||||
@@ -701,8 +659,15 @@ export const renderWithProviders = (
|
||||
persistentStateMock.mockClear();
|
||||
|
||||
const terminalWidth = width ?? baseState.terminalWidth;
|
||||
const finalSettings = settings;
|
||||
const finalConfig = config;
|
||||
|
||||
if (!config) {
|
||||
config = await loadCliConfig(
|
||||
settings.merged,
|
||||
'random-session-id',
|
||||
{} as unknown as CliArgs,
|
||||
{ cwd: '/' },
|
||||
);
|
||||
}
|
||||
|
||||
const mainAreaWidth = terminalWidth;
|
||||
|
||||
@@ -732,8 +697,8 @@ export const renderWithProviders = (
|
||||
|
||||
const wrapWithProviders = (comp: React.ReactElement) => (
|
||||
<AppContext.Provider value={appState}>
|
||||
<ConfigContext.Provider value={finalConfig}>
|
||||
<SettingsContext.Provider value={finalSettings}>
|
||||
<ConfigContext.Provider value={config}>
|
||||
<SettingsContext.Provider value={settings}>
|
||||
<UIStateContext.Provider value={finalUiState}>
|
||||
<VimModeProvider>
|
||||
<ShellFocusContext.Provider value={shellFocus}>
|
||||
@@ -744,7 +709,7 @@ export const renderWithProviders = (
|
||||
<UIActionsContext.Provider value={finalUIActions}>
|
||||
<OverflowProvider>
|
||||
<ToolActionsProvider
|
||||
config={finalConfig}
|
||||
config={config}
|
||||
toolCalls={allToolCalls}
|
||||
>
|
||||
<AskUserActionsProvider
|
||||
@@ -863,7 +828,7 @@ export function renderHook<Result, Props>(
|
||||
return { result, rerender, unmount, waitUntilReady, generateSvg };
|
||||
}
|
||||
|
||||
export function renderHookWithProviders<Result, Props>(
|
||||
export async function renderHookWithProviders<Result, Props>(
|
||||
renderCallback: (props: Props) => Result,
|
||||
options: {
|
||||
initialProps?: Props;
|
||||
@@ -876,13 +841,13 @@ export function renderHookWithProviders<Result, Props>(
|
||||
mouseEventsEnabled?: boolean;
|
||||
config?: Config;
|
||||
} = {},
|
||||
): {
|
||||
): Promise<{
|
||||
result: { current: Result };
|
||||
rerender: (props?: Props) => void;
|
||||
unmount: () => void;
|
||||
waitUntilReady: () => Promise<void>;
|
||||
generateSvg: () => string;
|
||||
} {
|
||||
}> {
|
||||
const result = { current: undefined as unknown as Result };
|
||||
|
||||
let setPropsFn: ((props: Props) => void) | undefined;
|
||||
@@ -901,8 +866,8 @@ export function renderHookWithProviders<Result, Props>(
|
||||
|
||||
let renderResult: ReturnType<typeof render>;
|
||||
|
||||
act(() => {
|
||||
renderResult = renderWithProviders(
|
||||
await act(async () => {
|
||||
renderResult = await renderWithProviders(
|
||||
<Wrapper>
|
||||
{}
|
||||
<TestComponent initialProps={options.initialProps as Props} />
|
||||
|
||||
@@ -94,11 +94,10 @@ describe('App', () => {
|
||||
};
|
||||
|
||||
it('should render main content and composer when not quitting', async () => {
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<App />,
|
||||
{
|
||||
uiState: mockUIState,
|
||||
config: makeFakeConfig({ useAlternateBuffer: false }),
|
||||
settings: createMockSettings({ ui: { useAlternateBuffer: false } }),
|
||||
},
|
||||
);
|
||||
@@ -116,11 +115,10 @@ describe('App', () => {
|
||||
quittingMessages: [{ id: 1, type: 'user', text: 'test' }],
|
||||
} as UIState;
|
||||
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<App />,
|
||||
{
|
||||
uiState: quittingUIState,
|
||||
config: makeFakeConfig({ useAlternateBuffer: false }),
|
||||
settings: createMockSettings({ ui: { useAlternateBuffer: false } }),
|
||||
},
|
||||
);
|
||||
@@ -138,11 +136,10 @@ describe('App', () => {
|
||||
pendingHistoryItems: [{ type: 'user', text: 'pending item' }],
|
||||
} as UIState;
|
||||
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<App />,
|
||||
{
|
||||
uiState: quittingUIState,
|
||||
config: makeFakeConfig({ useAlternateBuffer: true }),
|
||||
settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
|
||||
},
|
||||
);
|
||||
@@ -159,11 +156,10 @@ describe('App', () => {
|
||||
dialogsVisible: true,
|
||||
} as UIState;
|
||||
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<App />,
|
||||
{
|
||||
uiState: dialogUIState,
|
||||
config: makeFakeConfig({ useAlternateBuffer: true }),
|
||||
settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
|
||||
},
|
||||
);
|
||||
@@ -187,11 +183,10 @@ describe('App', () => {
|
||||
[stateKey]: true,
|
||||
} as UIState;
|
||||
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<App />,
|
||||
{
|
||||
uiState,
|
||||
config: makeFakeConfig({ useAlternateBuffer: true }),
|
||||
settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
|
||||
},
|
||||
);
|
||||
@@ -205,11 +200,10 @@ describe('App', () => {
|
||||
it('should render ScreenReaderAppLayout when screen reader is enabled', async () => {
|
||||
(useIsScreenReaderEnabled as Mock).mockReturnValue(true);
|
||||
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<App />,
|
||||
{
|
||||
uiState: mockUIState,
|
||||
config: makeFakeConfig({ useAlternateBuffer: true }),
|
||||
settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
|
||||
},
|
||||
);
|
||||
@@ -225,11 +219,10 @@ describe('App', () => {
|
||||
it('should render DefaultAppLayout when screen reader is not enabled', async () => {
|
||||
(useIsScreenReaderEnabled as Mock).mockReturnValue(false);
|
||||
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<App />,
|
||||
{
|
||||
uiState: mockUIState,
|
||||
config: makeFakeConfig({ useAlternateBuffer: true }),
|
||||
settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
|
||||
},
|
||||
);
|
||||
@@ -281,7 +274,7 @@ describe('App', () => {
|
||||
vi.spyOn(configWithExperiment, 'isTrustedFolder').mockReturnValue(true);
|
||||
vi.spyOn(configWithExperiment, 'getIdeMode').mockReturnValue(false);
|
||||
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<App />,
|
||||
{
|
||||
uiState: stateWithConfirmingTool,
|
||||
@@ -302,11 +295,10 @@ describe('App', () => {
|
||||
describe('Snapshots', () => {
|
||||
it('renders default layout correctly', async () => {
|
||||
(useIsScreenReaderEnabled as Mock).mockReturnValue(false);
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<App />,
|
||||
{
|
||||
uiState: mockUIState,
|
||||
config: makeFakeConfig({ useAlternateBuffer: true }),
|
||||
settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
|
||||
},
|
||||
);
|
||||
@@ -317,11 +309,10 @@ describe('App', () => {
|
||||
|
||||
it('renders screen reader layout correctly', async () => {
|
||||
(useIsScreenReaderEnabled as Mock).mockReturnValue(true);
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<App />,
|
||||
{
|
||||
uiState: mockUIState,
|
||||
config: makeFakeConfig({ useAlternateBuffer: true }),
|
||||
settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
|
||||
},
|
||||
);
|
||||
@@ -335,11 +326,10 @@ describe('App', () => {
|
||||
...mockUIState,
|
||||
dialogsVisible: true,
|
||||
} as UIState;
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<App />,
|
||||
{
|
||||
uiState: dialogUIState,
|
||||
config: makeFakeConfig({ useAlternateBuffer: true }),
|
||||
settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -212,7 +212,7 @@ import { useEditorSettings } from './hooks/useEditorSettings.js';
|
||||
import { useSettingsCommand } from './hooks/useSettingsCommand.js';
|
||||
import { useModelCommand } from './hooks/useModelCommand.js';
|
||||
import { useSlashCommandProcessor } from './hooks/slashCommandProcessor.js';
|
||||
import { useConsoleMessages } from './hooks/useConsoleMessages.js';
|
||||
import { useErrorCount } from './hooks/useConsoleMessages.js';
|
||||
import { useGeminiStream } from './hooks/useGeminiStream.js';
|
||||
import { useVim } from './hooks/vim.js';
|
||||
import { useFolderTrust } from './hooks/useFolderTrust.js';
|
||||
@@ -294,7 +294,7 @@ describe('AppContainer State Management', () => {
|
||||
const mockedUseSettingsCommand = useSettingsCommand as Mock;
|
||||
const mockedUseModelCommand = useModelCommand as Mock;
|
||||
const mockedUseSlashCommandProcessor = useSlashCommandProcessor as Mock;
|
||||
const mockedUseConsoleMessages = useConsoleMessages as Mock;
|
||||
const mockedUseConsoleMessages = useErrorCount as Mock;
|
||||
const mockedUseGeminiStream = useGeminiStream as Mock;
|
||||
const mockedUseVim = useVim as Mock;
|
||||
const mockedUseFolderTrust = useFolderTrust as Mock;
|
||||
@@ -396,9 +396,9 @@ describe('AppContainer State Management', () => {
|
||||
confirmationRequest: null,
|
||||
});
|
||||
mockedUseConsoleMessages.mockReturnValue({
|
||||
consoleMessages: [],
|
||||
errorCount: 0,
|
||||
handleNewMessage: vi.fn(),
|
||||
clearConsoleMessages: vi.fn(),
|
||||
clearErrorCount: vi.fn(),
|
||||
});
|
||||
mockedUseGeminiStream.mockReturnValue(DEFAULT_GEMINI_STREAM_MOCK);
|
||||
mockedUseVim.mockReturnValue({ handleInput: vi.fn() });
|
||||
|
||||
@@ -103,7 +103,7 @@ import {
|
||||
useOverflowActions,
|
||||
useOverflowState,
|
||||
} from './contexts/OverflowContext.js';
|
||||
import { useConsoleMessages } from './hooks/useConsoleMessages.js';
|
||||
import { useErrorCount } from './hooks/useConsoleMessages.js';
|
||||
import { useTerminalSize } from './hooks/useTerminalSize.js';
|
||||
import { calculatePromptWidths } from './components/InputPrompt.js';
|
||||
import { calculateMainAreaWidth } from './utils/ui-sizing.js';
|
||||
@@ -552,8 +552,7 @@ export const AppContainer = (props: AppContainerProps) => {
|
||||
};
|
||||
}, [settings]);
|
||||
|
||||
const { consoleMessages, clearConsoleMessages: clearConsoleMessagesState } =
|
||||
useConsoleMessages();
|
||||
const { errorCount, clearErrorCount } = useErrorCount();
|
||||
|
||||
const mainAreaWidth = calculateMainAreaWidth(terminalWidth, config);
|
||||
// Derive widths for InputPrompt using shared helper
|
||||
@@ -1372,11 +1371,11 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
// Explicitly hide the expansion hint and clear its x-second timer when clearing the screen.
|
||||
triggerExpandHint(null);
|
||||
historyManager.clearItems();
|
||||
clearConsoleMessagesState();
|
||||
clearErrorCount();
|
||||
refreshStatic();
|
||||
}, [
|
||||
historyManager,
|
||||
clearConsoleMessagesState,
|
||||
clearErrorCount,
|
||||
refreshStatic,
|
||||
reset,
|
||||
triggerExpandHint,
|
||||
@@ -1983,22 +1982,6 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
};
|
||||
}, [historyManager]);
|
||||
|
||||
const filteredConsoleMessages = useMemo(() => {
|
||||
if (config.getDebugMode()) {
|
||||
return consoleMessages;
|
||||
}
|
||||
return consoleMessages.filter((msg) => msg.type !== 'debug');
|
||||
}, [consoleMessages, config]);
|
||||
|
||||
// Computed values
|
||||
const errorCount = useMemo(
|
||||
() =>
|
||||
filteredConsoleMessages
|
||||
.filter((msg) => msg.type === 'error')
|
||||
.reduce((total, msg) => total + msg.count, 0),
|
||||
[filteredConsoleMessages],
|
||||
);
|
||||
|
||||
const nightly = props.version.includes('nightly');
|
||||
|
||||
const dialogsVisible =
|
||||
@@ -2233,7 +2216,6 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
constrainHeight,
|
||||
showErrorDetails,
|
||||
showFullTodos,
|
||||
filteredConsoleMessages,
|
||||
ideContextState,
|
||||
renderMarkdown,
|
||||
ctrlCPressedOnce: ctrlCPressCount >= 1,
|
||||
@@ -2361,7 +2343,6 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
constrainHeight,
|
||||
showErrorDetails,
|
||||
showFullTodos,
|
||||
filteredConsoleMessages,
|
||||
ideContextState,
|
||||
renderMarkdown,
|
||||
ctrlCPressCount,
|
||||
|
||||
@@ -53,7 +53,7 @@ describe('IdeIntegrationNudge', () => {
|
||||
});
|
||||
|
||||
it('renders correctly with default options', async () => {
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<IdeIntegrationNudge {...defaultProps} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
@@ -68,7 +68,7 @@ describe('IdeIntegrationNudge', () => {
|
||||
|
||||
it('handles "Yes" selection', async () => {
|
||||
const onComplete = vi.fn();
|
||||
const { stdin, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { stdin, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<IdeIntegrationNudge {...defaultProps} onComplete={onComplete} />,
|
||||
);
|
||||
|
||||
@@ -89,7 +89,7 @@ describe('IdeIntegrationNudge', () => {
|
||||
|
||||
it('handles "No" selection', async () => {
|
||||
const onComplete = vi.fn();
|
||||
const { stdin, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { stdin, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<IdeIntegrationNudge {...defaultProps} onComplete={onComplete} />,
|
||||
);
|
||||
|
||||
@@ -115,7 +115,7 @@ describe('IdeIntegrationNudge', () => {
|
||||
|
||||
it('handles "Dismiss" selection', async () => {
|
||||
const onComplete = vi.fn();
|
||||
const { stdin, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { stdin, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<IdeIntegrationNudge {...defaultProps} onComplete={onComplete} />,
|
||||
);
|
||||
|
||||
@@ -146,7 +146,7 @@ describe('IdeIntegrationNudge', () => {
|
||||
|
||||
it('handles Escape key press', async () => {
|
||||
const onComplete = vi.fn();
|
||||
const { stdin, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { stdin, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<IdeIntegrationNudge {...defaultProps} onComplete={onComplete} />,
|
||||
);
|
||||
|
||||
@@ -173,9 +173,10 @@ describe('IdeIntegrationNudge', () => {
|
||||
vi.stubEnv('GEMINI_CLI_IDE_WORKSPACE_PATH', '/tmp');
|
||||
|
||||
const onComplete = vi.fn();
|
||||
const { lastFrame, stdin, waitUntilReady, unmount } = renderWithProviders(
|
||||
<IdeIntegrationNudge {...defaultProps} onComplete={onComplete} />,
|
||||
);
|
||||
const { lastFrame, stdin, waitUntilReady, unmount } =
|
||||
await renderWithProviders(
|
||||
<IdeIntegrationNudge {...defaultProps} onComplete={onComplete} />,
|
||||
);
|
||||
|
||||
await waitUntilReady();
|
||||
|
||||
|
||||
@@ -143,7 +143,7 @@ describe('AuthDialog', () => {
|
||||
for (const [key, value] of Object.entries(env)) {
|
||||
vi.stubEnv(key, value as string);
|
||||
}
|
||||
const { waitUntilReady, unmount } = renderWithProviders(
|
||||
const { waitUntilReady, unmount } = await renderWithProviders(
|
||||
<AuthDialog {...props} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
@@ -161,7 +161,7 @@ describe('AuthDialog', () => {
|
||||
|
||||
it('filters auth types when enforcedType is set', async () => {
|
||||
props.settings.merged.security.auth.enforcedType = AuthType.USE_GEMINI;
|
||||
const { waitUntilReady, unmount } = renderWithProviders(
|
||||
const { waitUntilReady, unmount } = await renderWithProviders(
|
||||
<AuthDialog {...props} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
@@ -173,7 +173,7 @@ describe('AuthDialog', () => {
|
||||
|
||||
it('sets initial index to 0 when enforcedType is set', async () => {
|
||||
props.settings.merged.security.auth.enforcedType = AuthType.USE_GEMINI;
|
||||
const { waitUntilReady, unmount } = renderWithProviders(
|
||||
const { waitUntilReady, unmount } = await renderWithProviders(
|
||||
<AuthDialog {...props} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
@@ -213,7 +213,7 @@ describe('AuthDialog', () => {
|
||||
},
|
||||
])('selects initial auth type $desc', async ({ setup, expected }) => {
|
||||
setup();
|
||||
const { waitUntilReady, unmount } = renderWithProviders(
|
||||
const { waitUntilReady, unmount } = await renderWithProviders(
|
||||
<AuthDialog {...props} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
@@ -226,7 +226,7 @@ describe('AuthDialog', () => {
|
||||
describe('handleAuthSelect', () => {
|
||||
it('calls onAuthError if validation fails', async () => {
|
||||
mockedValidateAuthMethod.mockReturnValue('Invalid method');
|
||||
const { waitUntilReady, unmount } = renderWithProviders(
|
||||
const { waitUntilReady, unmount } = await renderWithProviders(
|
||||
<AuthDialog {...props} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
@@ -245,7 +245,7 @@ describe('AuthDialog', () => {
|
||||
|
||||
it('sets auth context with requiresRestart: true for LOGIN_WITH_GOOGLE', async () => {
|
||||
mockedValidateAuthMethod.mockReturnValue(null);
|
||||
const { waitUntilReady, unmount } = renderWithProviders(
|
||||
const { waitUntilReady, unmount } = await renderWithProviders(
|
||||
<AuthDialog {...props} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
@@ -261,7 +261,7 @@ describe('AuthDialog', () => {
|
||||
|
||||
it('sets auth context with empty object for other auth types', async () => {
|
||||
mockedValidateAuthMethod.mockReturnValue(null);
|
||||
const { waitUntilReady, unmount } = renderWithProviders(
|
||||
const { waitUntilReady, unmount } = await renderWithProviders(
|
||||
<AuthDialog {...props} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
@@ -278,7 +278,7 @@ describe('AuthDialog', () => {
|
||||
vi.stubEnv('GEMINI_API_KEY', 'test-key-from-env');
|
||||
// props.settings.merged.security.auth.selectedType is undefined here, simulating initial setup
|
||||
|
||||
const { waitUntilReady, unmount } = renderWithProviders(
|
||||
const { waitUntilReady, unmount } = await renderWithProviders(
|
||||
<AuthDialog {...props} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
@@ -297,7 +297,7 @@ describe('AuthDialog', () => {
|
||||
vi.stubEnv('GEMINI_API_KEY', ''); // Empty string
|
||||
// props.settings.merged.security.auth.selectedType is undefined here
|
||||
|
||||
const { waitUntilReady, unmount } = renderWithProviders(
|
||||
const { waitUntilReady, unmount } = await renderWithProviders(
|
||||
<AuthDialog {...props} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
@@ -316,7 +316,7 @@ describe('AuthDialog', () => {
|
||||
// process.env['GEMINI_API_KEY'] is not set
|
||||
// props.settings.merged.security.auth.selectedType is undefined here, simulating initial setup
|
||||
|
||||
const { waitUntilReady, unmount } = renderWithProviders(
|
||||
const { waitUntilReady, unmount } = await renderWithProviders(
|
||||
<AuthDialog {...props} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
@@ -337,7 +337,7 @@ describe('AuthDialog', () => {
|
||||
props.settings.merged.security.auth.selectedType =
|
||||
AuthType.LOGIN_WITH_GOOGLE;
|
||||
|
||||
const { waitUntilReady, unmount } = renderWithProviders(
|
||||
const { waitUntilReady, unmount } = await renderWithProviders(
|
||||
<AuthDialog {...props} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
@@ -360,7 +360,7 @@ describe('AuthDialog', () => {
|
||||
vi.mocked(props.config.isBrowserLaunchSuppressed).mockReturnValue(true);
|
||||
mockedValidateAuthMethod.mockReturnValue(null);
|
||||
|
||||
const { waitUntilReady, unmount } = renderWithProviders(
|
||||
const { waitUntilReady, unmount } = await renderWithProviders(
|
||||
<AuthDialog {...props} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
@@ -383,7 +383,7 @@ describe('AuthDialog', () => {
|
||||
|
||||
it('displays authError when provided', async () => {
|
||||
props.authError = 'Something went wrong';
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<AuthDialog {...props} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
@@ -429,7 +429,7 @@ describe('AuthDialog', () => {
|
||||
},
|
||||
])('$desc', async ({ setup, expectations }) => {
|
||||
setup();
|
||||
const { waitUntilReady, unmount } = renderWithProviders(
|
||||
const { waitUntilReady, unmount } = await renderWithProviders(
|
||||
<AuthDialog {...props} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
@@ -442,7 +442,7 @@ describe('AuthDialog', () => {
|
||||
|
||||
describe('Snapshots', () => {
|
||||
it('renders correctly with default props', async () => {
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<AuthDialog {...props} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
@@ -452,7 +452,7 @@ describe('AuthDialog', () => {
|
||||
|
||||
it('renders correctly with auth error', async () => {
|
||||
props.authError = 'Something went wrong';
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<AuthDialog {...props} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
@@ -462,7 +462,7 @@ describe('AuthDialog', () => {
|
||||
|
||||
it('renders correctly with enforced auth type', async () => {
|
||||
props.settings.merged.security.auth.enforcedType = AuthType.USE_GEMINI;
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<AuthDialog {...props} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
|
||||
@@ -73,7 +73,7 @@ describe('BannedAccountDialog', () => {
|
||||
});
|
||||
|
||||
it('renders the suspension message from accountSuspensionInfo', async () => {
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<BannedAccountDialog
|
||||
accountSuspensionInfo={DEFAULT_SUSPENSION_INFO}
|
||||
onExit={onExit}
|
||||
@@ -89,7 +89,7 @@ describe('BannedAccountDialog', () => {
|
||||
});
|
||||
|
||||
it('renders menu options with appeal link text from response', async () => {
|
||||
const { waitUntilReady, unmount } = renderWithProviders(
|
||||
const { waitUntilReady, unmount } = await renderWithProviders(
|
||||
<BannedAccountDialog
|
||||
accountSuspensionInfo={DEFAULT_SUSPENSION_INFO}
|
||||
onExit={onExit}
|
||||
@@ -109,7 +109,7 @@ describe('BannedAccountDialog', () => {
|
||||
const infoWithoutUrl: AccountSuspensionInfo = {
|
||||
message: 'Account suspended.',
|
||||
};
|
||||
const { waitUntilReady, unmount } = renderWithProviders(
|
||||
const { waitUntilReady, unmount } = await renderWithProviders(
|
||||
<BannedAccountDialog
|
||||
accountSuspensionInfo={infoWithoutUrl}
|
||||
onExit={onExit}
|
||||
@@ -129,7 +129,7 @@ describe('BannedAccountDialog', () => {
|
||||
message: 'Account suspended.',
|
||||
appealUrl: 'https://example.com/appeal',
|
||||
};
|
||||
const { waitUntilReady, unmount } = renderWithProviders(
|
||||
const { waitUntilReady, unmount } = await renderWithProviders(
|
||||
<BannedAccountDialog
|
||||
accountSuspensionInfo={infoWithoutLinkText}
|
||||
onExit={onExit}
|
||||
@@ -143,7 +143,7 @@ describe('BannedAccountDialog', () => {
|
||||
});
|
||||
|
||||
it('opens browser when appeal option is selected', async () => {
|
||||
const { waitUntilReady, unmount } = renderWithProviders(
|
||||
const { waitUntilReady, unmount } = await renderWithProviders(
|
||||
<BannedAccountDialog
|
||||
accountSuspensionInfo={DEFAULT_SUSPENSION_INFO}
|
||||
onExit={onExit}
|
||||
@@ -162,7 +162,7 @@ describe('BannedAccountDialog', () => {
|
||||
|
||||
it('shows URL when browser cannot be launched', async () => {
|
||||
mockedShouldLaunchBrowser.mockReturnValue(false);
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<BannedAccountDialog
|
||||
accountSuspensionInfo={DEFAULT_SUSPENSION_INFO}
|
||||
onExit={onExit}
|
||||
@@ -180,7 +180,7 @@ describe('BannedAccountDialog', () => {
|
||||
});
|
||||
|
||||
it('calls onExit when "Exit" is selected', async () => {
|
||||
const { waitUntilReady, unmount } = renderWithProviders(
|
||||
const { waitUntilReady, unmount } = await renderWithProviders(
|
||||
<BannedAccountDialog
|
||||
accountSuspensionInfo={DEFAULT_SUSPENSION_INFO}
|
||||
onExit={onExit}
|
||||
@@ -196,7 +196,7 @@ describe('BannedAccountDialog', () => {
|
||||
});
|
||||
|
||||
it('calls onChangeAuth when "Change authentication" is selected', async () => {
|
||||
const { waitUntilReady, unmount } = renderWithProviders(
|
||||
const { waitUntilReady, unmount } = await renderWithProviders(
|
||||
<BannedAccountDialog
|
||||
accountSuspensionInfo={DEFAULT_SUSPENSION_INFO}
|
||||
onExit={onExit}
|
||||
@@ -212,7 +212,7 @@ describe('BannedAccountDialog', () => {
|
||||
});
|
||||
|
||||
it('exits on escape key', async () => {
|
||||
const { waitUntilReady, unmount } = renderWithProviders(
|
||||
const { waitUntilReady, unmount } = await renderWithProviders(
|
||||
<BannedAccountDialog
|
||||
accountSuspensionInfo={DEFAULT_SUSPENSION_INFO}
|
||||
onExit={onExit}
|
||||
@@ -227,7 +227,7 @@ describe('BannedAccountDialog', () => {
|
||||
});
|
||||
|
||||
it('renders snapshot correctly', async () => {
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<BannedAccountDialog
|
||||
accountSuspensionInfo={DEFAULT_SUSPENSION_INFO}
|
||||
onExit={onExit}
|
||||
|
||||
@@ -36,10 +36,12 @@ describe('aboutCommand', () => {
|
||||
beforeEach(() => {
|
||||
mockContext = createMockCommandContext({
|
||||
services: {
|
||||
config: {
|
||||
getModel: vi.fn(),
|
||||
getIdeMode: vi.fn().mockReturnValue(true),
|
||||
getUserTierName: vi.fn().mockReturnValue(undefined),
|
||||
agentContext: {
|
||||
config: {
|
||||
getModel: vi.fn(),
|
||||
getIdeMode: vi.fn().mockReturnValue(true),
|
||||
getUserTierName: vi.fn().mockReturnValue(undefined),
|
||||
},
|
||||
},
|
||||
settings: {
|
||||
merged: {
|
||||
@@ -57,9 +59,10 @@ describe('aboutCommand', () => {
|
||||
} as unknown as CommandContext);
|
||||
|
||||
vi.mocked(getVersion).mockResolvedValue('test-version');
|
||||
vi.spyOn(mockContext.services.config!, 'getModel').mockReturnValue(
|
||||
'test-model',
|
||||
);
|
||||
vi.spyOn(
|
||||
mockContext.services.agentContext!.config,
|
||||
'getModel',
|
||||
).mockReturnValue('test-model');
|
||||
process.env['GOOGLE_CLOUD_PROJECT'] = 'test-gcp-project';
|
||||
Object.defineProperty(process, 'platform', {
|
||||
value: 'test-os',
|
||||
@@ -160,9 +163,9 @@ describe('aboutCommand', () => {
|
||||
});
|
||||
|
||||
it('should display the tier when getUserTierName returns a value', async () => {
|
||||
vi.mocked(mockContext.services.config!.getUserTierName).mockReturnValue(
|
||||
'Enterprise Tier',
|
||||
);
|
||||
vi.mocked(
|
||||
mockContext.services.agentContext!.config.getUserTierName,
|
||||
).mockReturnValue('Enterprise Tier');
|
||||
if (!aboutCommand.action) {
|
||||
throw new Error('The about command must have an action.');
|
||||
}
|
||||
|
||||
@@ -34,7 +34,8 @@ export const aboutCommand: SlashCommand = {
|
||||
process.env['SEATBELT_PROFILE'] || 'unknown'
|
||||
})`;
|
||||
}
|
||||
const modelVersion = context.services.config?.getModel() || 'Unknown';
|
||||
const modelVersion =
|
||||
context.services.agentContext?.config.getModel() || 'Unknown';
|
||||
const cliVersion = await getVersion();
|
||||
const selectedAuthType =
|
||||
context.services.settings.merged.security.auth.selectedType || '';
|
||||
@@ -48,7 +49,7 @@ export const aboutCommand: SlashCommand = {
|
||||
});
|
||||
const userEmail = cachedAccount ?? undefined;
|
||||
|
||||
const tier = context.services.config?.getUserTierName();
|
||||
const tier = context.services.agentContext?.config.getUserTierName();
|
||||
|
||||
const aboutItem: Omit<HistoryItemAbout, 'id'> = {
|
||||
type: MessageType.ABOUT,
|
||||
@@ -68,7 +69,7 @@ export const aboutCommand: SlashCommand = {
|
||||
};
|
||||
|
||||
async function getIdeClientName(context: CommandContext) {
|
||||
if (!context.services.config?.getIdeMode()) {
|
||||
if (!context.services.agentContext?.config.getIdeMode()) {
|
||||
return '';
|
||||
}
|
||||
const ideClient = await IdeClient.getInstance();
|
||||
|
||||
@@ -26,6 +26,7 @@ describe('agentsCommand', () => {
|
||||
let mockContext: ReturnType<typeof createMockCommandContext>;
|
||||
let mockConfig: {
|
||||
getAgentRegistry: ReturnType<typeof vi.fn>;
|
||||
config: Config;
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -37,11 +38,14 @@ describe('agentsCommand', () => {
|
||||
getAllAgentNames: vi.fn().mockReturnValue([]),
|
||||
reload: vi.fn(),
|
||||
}),
|
||||
get config() {
|
||||
return this as unknown as Config;
|
||||
},
|
||||
};
|
||||
|
||||
mockContext = createMockCommandContext({
|
||||
services: {
|
||||
config: mockConfig as unknown as Config,
|
||||
agentContext: mockConfig as unknown as Config,
|
||||
settings: {
|
||||
workspace: { path: '/mock/path' },
|
||||
merged: { agents: { overrides: {} } },
|
||||
@@ -53,7 +57,7 @@ describe('agentsCommand', () => {
|
||||
it('should show an error if config is not available', async () => {
|
||||
const contextWithoutConfig = createMockCommandContext({
|
||||
services: {
|
||||
config: null,
|
||||
agentContext: null,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -226,7 +230,7 @@ describe('agentsCommand', () => {
|
||||
|
||||
it('should show an error if config is not available for enable', async () => {
|
||||
const contextWithoutConfig = createMockCommandContext({
|
||||
services: { config: null },
|
||||
services: { agentContext: null },
|
||||
});
|
||||
const enableCommand = agentsCommand.subCommands?.find(
|
||||
(cmd) => cmd.name === 'enable',
|
||||
@@ -332,7 +336,7 @@ describe('agentsCommand', () => {
|
||||
|
||||
it('should show an error if config is not available for disable', async () => {
|
||||
const contextWithoutConfig = createMockCommandContext({
|
||||
services: { config: null },
|
||||
services: { agentContext: null },
|
||||
});
|
||||
const disableCommand = agentsCommand.subCommands?.find(
|
||||
(cmd) => cmd.name === 'disable',
|
||||
@@ -433,7 +437,7 @@ describe('agentsCommand', () => {
|
||||
|
||||
it('should show an error if config is not available', async () => {
|
||||
const contextWithoutConfig = createMockCommandContext({
|
||||
services: { config: null },
|
||||
services: { agentContext: null },
|
||||
});
|
||||
const configCommand = agentsCommand.subCommands?.find(
|
||||
(cmd) => cmd.name === 'config',
|
||||
|
||||
@@ -21,7 +21,7 @@ const agentsListCommand: SlashCommand = {
|
||||
kind: CommandKind.BUILT_IN,
|
||||
autoExecute: true,
|
||||
action: async (context: CommandContext) => {
|
||||
const { config } = context.services;
|
||||
const config = context.services.agentContext?.config;
|
||||
if (!config) {
|
||||
return {
|
||||
type: 'message',
|
||||
@@ -61,7 +61,8 @@ async function enableAction(
|
||||
context: CommandContext,
|
||||
args: string,
|
||||
): Promise<SlashCommandActionReturn | void> {
|
||||
const { config, settings } = context.services;
|
||||
const config = context.services.agentContext?.config;
|
||||
const { settings } = context.services;
|
||||
if (!config) {
|
||||
return {
|
||||
type: 'message',
|
||||
@@ -137,7 +138,8 @@ async function disableAction(
|
||||
context: CommandContext,
|
||||
args: string,
|
||||
): Promise<SlashCommandActionReturn | void> {
|
||||
const { config, settings } = context.services;
|
||||
const config = context.services.agentContext?.config;
|
||||
const { settings } = context.services;
|
||||
if (!config) {
|
||||
return {
|
||||
type: 'message',
|
||||
@@ -216,7 +218,7 @@ async function configAction(
|
||||
context: CommandContext,
|
||||
args: string,
|
||||
): Promise<SlashCommandActionReturn | void> {
|
||||
const { config } = context.services;
|
||||
const config = context.services.agentContext?.config;
|
||||
if (!config) {
|
||||
return {
|
||||
type: 'message',
|
||||
@@ -266,7 +268,8 @@ async function configAction(
|
||||
}
|
||||
|
||||
function completeAgentsToEnable(context: CommandContext, partialArg: string) {
|
||||
const { config, settings } = context.services;
|
||||
const config = context.services.agentContext?.config;
|
||||
const { settings } = context.services;
|
||||
if (!config) return [];
|
||||
|
||||
const overrides = settings.merged.agents.overrides;
|
||||
@@ -278,7 +281,7 @@ function completeAgentsToEnable(context: CommandContext, partialArg: string) {
|
||||
}
|
||||
|
||||
function completeAgentsToDisable(context: CommandContext, partialArg: string) {
|
||||
const { config } = context.services;
|
||||
const config = context.services.agentContext?.config;
|
||||
if (!config) return [];
|
||||
|
||||
const agentRegistry = config.getAgentRegistry();
|
||||
@@ -287,7 +290,7 @@ function completeAgentsToDisable(context: CommandContext, partialArg: string) {
|
||||
}
|
||||
|
||||
function completeAllAgents(context: CommandContext, partialArg: string) {
|
||||
const { config } = context.services;
|
||||
const config = context.services.agentContext?.config;
|
||||
if (!config) return [];
|
||||
|
||||
const agentRegistry = config.getAgentRegistry();
|
||||
@@ -328,7 +331,7 @@ const agentsReloadCommand: SlashCommand = {
|
||||
description: 'Reload the agent registry',
|
||||
kind: CommandKind.BUILT_IN,
|
||||
action: async (context: CommandContext) => {
|
||||
const { config } = context.services;
|
||||
const config = context.services.agentContext?.config;
|
||||
const agentRegistry = config?.getAgentRegistry();
|
||||
if (!agentRegistry) {
|
||||
return {
|
||||
|
||||
@@ -9,6 +9,7 @@ import { authCommand } from './authCommand.js';
|
||||
import { type CommandContext } from './types.js';
|
||||
import { createMockCommandContext } from '../../test-utils/mockCommandContext.js';
|
||||
import { SettingScope } from '../../config/settings.js';
|
||||
import type { GeminiClient } from '@google/gemini-cli-core';
|
||||
|
||||
vi.mock('@google/gemini-cli-core', async () => {
|
||||
const actual = await vi.importActual('@google/gemini-cli-core');
|
||||
@@ -24,8 +25,10 @@ describe('authCommand', () => {
|
||||
beforeEach(() => {
|
||||
mockContext = createMockCommandContext({
|
||||
services: {
|
||||
config: {
|
||||
getGeminiClient: vi.fn(),
|
||||
agentContext: {
|
||||
geminiClient: {
|
||||
stripThoughtsFromHistory: vi.fn(),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -101,17 +104,19 @@ describe('authCommand', () => {
|
||||
const mockStripThoughts = vi.fn();
|
||||
const mockClient = {
|
||||
stripThoughtsFromHistory: mockStripThoughts,
|
||||
} as unknown as ReturnType<
|
||||
NonNullable<typeof mockContext.services.config>['getGeminiClient']
|
||||
>;
|
||||
|
||||
if (mockContext.services.config) {
|
||||
mockContext.services.config.getGeminiClient = vi.fn(() => mockClient);
|
||||
} as unknown as GeminiClient;
|
||||
if (mockContext.services.agentContext?.config) {
|
||||
mockContext.services.agentContext.config.getGeminiClient = vi.fn(
|
||||
() => mockClient,
|
||||
);
|
||||
}
|
||||
|
||||
await logoutCommand!.action!(mockContext, '');
|
||||
|
||||
expect(mockStripThoughts).toHaveBeenCalled();
|
||||
expect(
|
||||
mockContext.services.agentContext?.geminiClient
|
||||
.stripThoughtsFromHistory,
|
||||
).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return logout action to signal explicit state change', async () => {
|
||||
@@ -123,7 +128,7 @@ describe('authCommand', () => {
|
||||
|
||||
it('should handle missing config gracefully', async () => {
|
||||
const logoutCommand = authCommand.subCommands?.[1];
|
||||
mockContext.services.config = null;
|
||||
mockContext.services.agentContext = null;
|
||||
|
||||
const result = await logoutCommand!.action!(mockContext, '');
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ const authLogoutCommand: SlashCommand = {
|
||||
undefined,
|
||||
);
|
||||
// Strip thoughts from history instead of clearing completely
|
||||
context.services.config?.getGeminiClient()?.stripThoughtsFromHistory();
|
||||
context.services.agentContext?.geminiClient.stripThoughtsFromHistory();
|
||||
// Return logout action to signal explicit state change
|
||||
return {
|
||||
type: 'logout',
|
||||
|
||||
@@ -83,16 +83,18 @@ describe('bugCommand', () => {
|
||||
it('should generate the default GitHub issue URL', async () => {
|
||||
const mockContext = createMockCommandContext({
|
||||
services: {
|
||||
config: {
|
||||
getModel: () => 'gemini-pro',
|
||||
getBugCommand: () => undefined,
|
||||
getIdeMode: () => true,
|
||||
getGeminiClient: () => ({
|
||||
agentContext: {
|
||||
config: {
|
||||
getModel: () => 'gemini-pro',
|
||||
getBugCommand: () => undefined,
|
||||
getIdeMode: () => true,
|
||||
getContentGeneratorConfig: () => ({ authType: 'oauth-personal' }),
|
||||
},
|
||||
geminiClient: {
|
||||
getChat: () => ({
|
||||
getHistory: () => [],
|
||||
}),
|
||||
}),
|
||||
getContentGeneratorConfig: () => ({ authType: 'oauth-personal' }),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -126,18 +128,20 @@ describe('bugCommand', () => {
|
||||
];
|
||||
const mockContext = createMockCommandContext({
|
||||
services: {
|
||||
config: {
|
||||
getModel: () => 'gemini-pro',
|
||||
getBugCommand: () => undefined,
|
||||
getIdeMode: () => true,
|
||||
getGeminiClient: () => ({
|
||||
agentContext: {
|
||||
config: {
|
||||
getModel: () => 'gemini-pro',
|
||||
getBugCommand: () => undefined,
|
||||
getIdeMode: () => true,
|
||||
getContentGeneratorConfig: () => ({ authType: 'vertex-ai' }),
|
||||
storage: {
|
||||
getProjectTempDir: () => '/tmp/gemini',
|
||||
},
|
||||
},
|
||||
geminiClient: {
|
||||
getChat: () => ({
|
||||
getHistory: () => history,
|
||||
}),
|
||||
}),
|
||||
getContentGeneratorConfig: () => ({ authType: 'vertex-ai' }),
|
||||
storage: {
|
||||
getProjectTempDir: () => '/tmp/gemini',
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -172,16 +176,18 @@ describe('bugCommand', () => {
|
||||
'https://internal.bug-tracker.com/new?desc={title}&details={info}';
|
||||
const mockContext = createMockCommandContext({
|
||||
services: {
|
||||
config: {
|
||||
getModel: () => 'gemini-pro',
|
||||
getBugCommand: () => ({ urlTemplate: customTemplate }),
|
||||
getIdeMode: () => true,
|
||||
getGeminiClient: () => ({
|
||||
agentContext: {
|
||||
config: {
|
||||
getModel: () => 'gemini-pro',
|
||||
getBugCommand: () => ({ urlTemplate: customTemplate }),
|
||||
getIdeMode: () => true,
|
||||
getContentGeneratorConfig: () => ({ authType: 'vertex-ai' }),
|
||||
},
|
||||
geminiClient: {
|
||||
getChat: () => ({
|
||||
getHistory: () => [],
|
||||
}),
|
||||
}),
|
||||
getContentGeneratorConfig: () => ({ authType: 'vertex-ai' }),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -32,8 +32,8 @@ export const bugCommand: SlashCommand = {
|
||||
autoExecute: false,
|
||||
action: async (context: CommandContext, args?: string): Promise<void> => {
|
||||
const bugDescription = (args || '').trim();
|
||||
const { config } = context.services;
|
||||
|
||||
const agentContext = context.services.agentContext;
|
||||
const config = agentContext?.config;
|
||||
const osVersion = `${process.platform} ${process.version}`;
|
||||
let sandboxEnv = 'no sandbox';
|
||||
if (process.env['SANDBOX'] && process.env['SANDBOX'] !== 'sandbox-exec') {
|
||||
@@ -73,7 +73,7 @@ export const bugCommand: SlashCommand = {
|
||||
info += `* **IDE Client:** ${ideClient}\n`;
|
||||
}
|
||||
|
||||
const chat = config?.getGeminiClient()?.getChat();
|
||||
const chat = agentContext?.geminiClient?.getChat();
|
||||
const history = chat?.getHistory() || [];
|
||||
let historyFileMessage = '';
|
||||
let problemValue = bugDescription;
|
||||
@@ -134,7 +134,7 @@ export const bugCommand: SlashCommand = {
|
||||
};
|
||||
|
||||
async function getIdeClientName(context: CommandContext) {
|
||||
if (!context.services.config?.getIdeMode()) {
|
||||
if (!context.services.agentContext?.config.getIdeMode()) {
|
||||
return '';
|
||||
}
|
||||
const ideClient = await IdeClient.getInstance();
|
||||
|
||||
@@ -70,18 +70,19 @@ describe('chatCommand', () => {
|
||||
|
||||
mockContext = createMockCommandContext({
|
||||
services: {
|
||||
config: {
|
||||
getProjectRoot: () => '/project/root',
|
||||
getGeminiClient: () =>
|
||||
({
|
||||
getChat: mockGetChat,
|
||||
}) as unknown as GeminiClient,
|
||||
storage: {
|
||||
getProjectTempDir: () => '/project/root/.gemini/tmp/mockhash',
|
||||
agentContext: {
|
||||
config: {
|
||||
getProjectRoot: () => '/project/root',
|
||||
getContentGeneratorConfig: () => ({
|
||||
authType: AuthType.LOGIN_WITH_GOOGLE,
|
||||
}),
|
||||
storage: {
|
||||
getProjectTempDir: () => '/project/root/.gemini/tmp/mockhash',
|
||||
},
|
||||
},
|
||||
getContentGeneratorConfig: () => ({
|
||||
authType: AuthType.LOGIN_WITH_GOOGLE,
|
||||
}),
|
||||
geminiClient: {
|
||||
getChat: mockGetChat,
|
||||
} as unknown as GeminiClient,
|
||||
},
|
||||
logger: {
|
||||
saveCheckpoint: mockSaveCheckpoint,
|
||||
@@ -698,7 +699,11 @@ Hi there!`;
|
||||
|
||||
beforeEach(() => {
|
||||
mockGetLatestApiRequest = vi.fn();
|
||||
mockContext.services.config!.getLatestApiRequest =
|
||||
if (!mockContext.services.agentContext!.config) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(mockContext.services.agentContext!.config as any) = {};
|
||||
}
|
||||
mockContext.services.agentContext!.config.getLatestApiRequest =
|
||||
mockGetLatestApiRequest;
|
||||
vi.spyOn(process, 'cwd').mockReturnValue('/project/root');
|
||||
vi.spyOn(Date, 'now').mockReturnValue(1234567890);
|
||||
|
||||
@@ -35,7 +35,7 @@ const getSavedChatTags = async (
|
||||
context: CommandContext,
|
||||
mtSortDesc: boolean,
|
||||
): Promise<ChatDetail[]> => {
|
||||
const cfg = context.services.config;
|
||||
const cfg = context.services.agentContext?.config;
|
||||
const geminiDir = cfg?.storage?.getProjectTempDir();
|
||||
if (!geminiDir) {
|
||||
return [];
|
||||
@@ -103,7 +103,8 @@ const saveCommand: SlashCommand = {
|
||||
};
|
||||
}
|
||||
|
||||
const { logger, config } = context.services;
|
||||
const { logger } = context.services;
|
||||
const config = context.services.agentContext?.config;
|
||||
await logger.initialize();
|
||||
|
||||
if (!context.overwriteConfirmed) {
|
||||
@@ -125,7 +126,7 @@ const saveCommand: SlashCommand = {
|
||||
}
|
||||
}
|
||||
|
||||
const chat = config?.getGeminiClient()?.getChat();
|
||||
const chat = context.services.agentContext?.geminiClient?.getChat();
|
||||
if (!chat) {
|
||||
return {
|
||||
type: 'message',
|
||||
@@ -172,7 +173,8 @@ const resumeCheckpointCommand: SlashCommand = {
|
||||
};
|
||||
}
|
||||
|
||||
const { logger, config } = context.services;
|
||||
const { logger } = context.services;
|
||||
const config = context.services.agentContext?.config;
|
||||
await logger.initialize();
|
||||
const checkpoint = await logger.loadCheckpoint(tag);
|
||||
const conversation = checkpoint.history;
|
||||
@@ -298,7 +300,7 @@ const shareCommand: SlashCommand = {
|
||||
};
|
||||
}
|
||||
|
||||
const chat = context.services.config?.getGeminiClient()?.getChat();
|
||||
const chat = context.services.agentContext?.geminiClient?.getChat();
|
||||
if (!chat) {
|
||||
return {
|
||||
type: 'message',
|
||||
@@ -344,7 +346,7 @@ export const debugCommand: SlashCommand = {
|
||||
kind: CommandKind.BUILT_IN,
|
||||
autoExecute: true,
|
||||
action: async (context): Promise<MessageActionReturn> => {
|
||||
const req = context.services.config?.getLatestApiRequest();
|
||||
const req = context.services.agentContext?.config.getLatestApiRequest();
|
||||
if (!req) {
|
||||
return {
|
||||
type: 'message',
|
||||
|
||||
@@ -36,24 +36,25 @@ describe('clearCommand', () => {
|
||||
|
||||
mockContext = createMockCommandContext({
|
||||
services: {
|
||||
config: {
|
||||
getGeminiClient: () =>
|
||||
({
|
||||
resetChat: mockResetChat,
|
||||
getChat: () => ({
|
||||
getChatRecordingService: mockGetChatRecordingService,
|
||||
}),
|
||||
}) as unknown as GeminiClient,
|
||||
setSessionId: vi.fn(),
|
||||
getEnableHooks: vi.fn().mockReturnValue(false),
|
||||
getMessageBus: vi.fn().mockReturnValue(undefined),
|
||||
getHookSystem: vi.fn().mockReturnValue({
|
||||
fireSessionEndEvent: vi.fn().mockResolvedValue(undefined),
|
||||
fireSessionStartEvent: vi.fn().mockResolvedValue(undefined),
|
||||
}),
|
||||
injectionService: {
|
||||
clear: mockHintClear,
|
||||
agentContext: {
|
||||
config: {
|
||||
getEnableHooks: vi.fn().mockReturnValue(false),
|
||||
setSessionId: vi.fn(),
|
||||
getMessageBus: vi.fn().mockReturnValue(undefined),
|
||||
getHookSystem: vi.fn().mockReturnValue({
|
||||
fireSessionEndEvent: vi.fn().mockResolvedValue(undefined),
|
||||
fireSessionStartEvent: vi.fn().mockResolvedValue(undefined),
|
||||
}),
|
||||
injectionService: {
|
||||
clear: mockHintClear,
|
||||
},
|
||||
},
|
||||
geminiClient: {
|
||||
resetChat: mockResetChat,
|
||||
getChat: () => ({
|
||||
getChatRecordingService: mockGetChatRecordingService,
|
||||
}),
|
||||
} as unknown as GeminiClient,
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -98,7 +99,7 @@ describe('clearCommand', () => {
|
||||
|
||||
const nullConfigContext = createMockCommandContext({
|
||||
services: {
|
||||
config: null,
|
||||
agentContext: null,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -20,8 +20,8 @@ export const clearCommand: SlashCommand = {
|
||||
kind: CommandKind.BUILT_IN,
|
||||
autoExecute: true,
|
||||
action: async (context, _args) => {
|
||||
const geminiClient = context.services.config?.getGeminiClient();
|
||||
const config = context.services.config;
|
||||
const geminiClient = context.services.agentContext?.geminiClient;
|
||||
const config = context.services.agentContext?.config;
|
||||
|
||||
// Fire SessionEnd hook before clearing
|
||||
const hookSystem = config?.getHookSystem();
|
||||
|
||||
@@ -22,11 +22,10 @@ describe('compressCommand', () => {
|
||||
mockTryCompressChat = vi.fn();
|
||||
context = createMockCommandContext({
|
||||
services: {
|
||||
config: {
|
||||
getGeminiClient: () =>
|
||||
({
|
||||
tryCompressChat: mockTryCompressChat,
|
||||
}) as unknown as GeminiClient,
|
||||
agentContext: {
|
||||
geminiClient: {
|
||||
tryCompressChat: mockTryCompressChat,
|
||||
} as unknown as GeminiClient,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -39,9 +39,11 @@ export const compressCommand: SlashCommand = {
|
||||
try {
|
||||
ui.setPendingItem(pendingMessage);
|
||||
const promptId = `compress-${Date.now()}`;
|
||||
const compressed = await context.services.config
|
||||
?.getGeminiClient()
|
||||
?.tryCompressChat(promptId, true);
|
||||
const compressed =
|
||||
await context.services.agentContext?.geminiClient?.tryCompressChat(
|
||||
promptId,
|
||||
true,
|
||||
);
|
||||
if (compressed) {
|
||||
ui.addItem(
|
||||
{
|
||||
|
||||
@@ -29,10 +29,10 @@ describe('copyCommand', () => {
|
||||
|
||||
mockContext = createMockCommandContext({
|
||||
services: {
|
||||
config: {
|
||||
getGeminiClient: () => ({
|
||||
agentContext: {
|
||||
geminiClient: {
|
||||
getChat: mockGetChat,
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -301,7 +301,7 @@ describe('copyCommand', () => {
|
||||
if (!copyCommand.action) throw new Error('Command has no action');
|
||||
|
||||
const nullConfigContext = createMockCommandContext({
|
||||
services: { config: null },
|
||||
services: { agentContext: null },
|
||||
});
|
||||
|
||||
const result = await copyCommand.action(nullConfigContext, '');
|
||||
|
||||
@@ -18,7 +18,7 @@ export const copyCommand: SlashCommand = {
|
||||
kind: CommandKind.BUILT_IN,
|
||||
autoExecute: true,
|
||||
action: async (context, _args): Promise<SlashCommandActionReturn | void> => {
|
||||
const chat = context.services.config?.getGeminiClient()?.getChat();
|
||||
const chat = context.services.agentContext?.geminiClient?.getChat();
|
||||
const history = chat?.getHistory();
|
||||
|
||||
// Get the last message from the AI (model role)
|
||||
|
||||
@@ -85,11 +85,14 @@ describe('directoryCommand', () => {
|
||||
getFileFilteringOptions: () => ({ ignore: [], include: [] }),
|
||||
setUserMemory: vi.fn(),
|
||||
setGeminiMdFileCount: vi.fn(),
|
||||
get config() {
|
||||
return this;
|
||||
},
|
||||
} as unknown as Config;
|
||||
|
||||
mockContext = {
|
||||
services: {
|
||||
config: mockConfig,
|
||||
agentContext: mockConfig,
|
||||
settings: {
|
||||
merged: {
|
||||
memoryDiscoveryMaxDirs: 1000,
|
||||
|
||||
@@ -60,7 +60,7 @@ async function finishAddingDirectories(
|
||||
}
|
||||
|
||||
if (added.length > 0) {
|
||||
const gemini = config.getGeminiClient();
|
||||
const gemini = config.geminiClient;
|
||||
if (gemini) {
|
||||
await gemini.addDirectoryContext();
|
||||
|
||||
@@ -110,9 +110,9 @@ export const directoryCommand: SlashCommand = {
|
||||
|
||||
// Filter out existing directories
|
||||
let filteredSuggestions = suggestions;
|
||||
if (context.services.config) {
|
||||
if (context.services.agentContext?.config) {
|
||||
const workspaceContext =
|
||||
context.services.config.getWorkspaceContext();
|
||||
context.services.agentContext.config.getWorkspaceContext();
|
||||
const existingDirs = new Set(
|
||||
workspaceContext.getDirectories().map((dir) => path.resolve(dir)),
|
||||
);
|
||||
@@ -144,11 +144,11 @@ export const directoryCommand: SlashCommand = {
|
||||
action: async (context: CommandContext, args: string) => {
|
||||
const {
|
||||
ui: { addItem },
|
||||
services: { config, settings },
|
||||
services: { agentContext, settings },
|
||||
} = context;
|
||||
const [...rest] = args.split(' ');
|
||||
|
||||
if (!config) {
|
||||
if (!agentContext) {
|
||||
addItem({
|
||||
type: MessageType.ERROR,
|
||||
text: 'Configuration is not available.',
|
||||
@@ -156,7 +156,7 @@ export const directoryCommand: SlashCommand = {
|
||||
return;
|
||||
}
|
||||
|
||||
if (config.isRestrictiveSandbox()) {
|
||||
if (agentContext.config.isRestrictiveSandbox()) {
|
||||
return {
|
||||
type: 'message' as const,
|
||||
messageType: 'error' as const,
|
||||
@@ -181,7 +181,7 @@ export const directoryCommand: SlashCommand = {
|
||||
const errors: string[] = [];
|
||||
const alreadyAdded: string[] = [];
|
||||
|
||||
const workspaceContext = config.getWorkspaceContext();
|
||||
const workspaceContext = agentContext.config.getWorkspaceContext();
|
||||
const currentWorkspaceDirs = workspaceContext.getDirectories();
|
||||
const pathsToProcess: string[] = [];
|
||||
|
||||
@@ -252,7 +252,7 @@ export const directoryCommand: SlashCommand = {
|
||||
trustedDirs={added}
|
||||
errors={errors}
|
||||
finishAddingDirectories={finishAddingDirectories}
|
||||
config={config}
|
||||
config={agentContext.config}
|
||||
addItem={addItem}
|
||||
/>
|
||||
),
|
||||
@@ -264,7 +264,12 @@ export const directoryCommand: SlashCommand = {
|
||||
errors.push(...result.errors);
|
||||
}
|
||||
|
||||
await finishAddingDirectories(config, addItem, added, errors);
|
||||
await finishAddingDirectories(
|
||||
agentContext.config,
|
||||
addItem,
|
||||
added,
|
||||
errors,
|
||||
);
|
||||
return;
|
||||
},
|
||||
},
|
||||
@@ -275,16 +280,16 @@ export const directoryCommand: SlashCommand = {
|
||||
action: async (context: CommandContext) => {
|
||||
const {
|
||||
ui: { addItem },
|
||||
services: { config },
|
||||
services: { agentContext },
|
||||
} = context;
|
||||
if (!config) {
|
||||
if (!agentContext) {
|
||||
addItem({
|
||||
type: MessageType.ERROR,
|
||||
text: 'Configuration is not available.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
const workspaceContext = config.getWorkspaceContext();
|
||||
const workspaceContext = agentContext.config.getWorkspaceContext();
|
||||
const directories = workspaceContext.getDirectories();
|
||||
const directoryList = directories.map((dir) => `- ${dir}`).join('\n');
|
||||
addItem({
|
||||
|
||||
@@ -161,14 +161,16 @@ describe('extensionsCommand', () => {
|
||||
|
||||
mockContext = createMockCommandContext({
|
||||
services: {
|
||||
config: {
|
||||
getExtensions: mockGetExtensions,
|
||||
getExtensionLoader: vi.fn().mockReturnValue(mockExtensionLoader),
|
||||
getWorkingDir: () => '/test/dir',
|
||||
reloadSkills: mockReloadSkills,
|
||||
getAgentRegistry: vi.fn().mockReturnValue({
|
||||
reload: mockReloadAgents,
|
||||
}),
|
||||
agentContext: {
|
||||
config: {
|
||||
getExtensions: mockGetExtensions,
|
||||
getExtensionLoader: vi.fn().mockReturnValue(mockExtensionLoader),
|
||||
getWorkingDir: () => '/test/dir',
|
||||
reloadSkills: mockReloadSkills,
|
||||
getAgentRegistry: vi.fn().mockReturnValue({
|
||||
reload: mockReloadAgents,
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
ui: {
|
||||
@@ -917,7 +919,7 @@ describe('extensionsCommand', () => {
|
||||
expect(restartAction).not.toBeNull();
|
||||
|
||||
mockRestartExtension = vi.fn();
|
||||
mockContext.services.config!.getExtensionLoader = vi
|
||||
mockContext.services.agentContext!.config.getExtensionLoader = vi
|
||||
.fn()
|
||||
.mockImplementation(() => ({
|
||||
getExtensions: mockGetExtensions,
|
||||
@@ -927,7 +929,7 @@ describe('extensionsCommand', () => {
|
||||
});
|
||||
|
||||
it('should show a message if no extensions are installed', async () => {
|
||||
mockContext.services.config!.getExtensionLoader = vi
|
||||
mockContext.services.agentContext!.config.getExtensionLoader = vi
|
||||
.fn()
|
||||
.mockImplementation(() => ({
|
||||
getExtensions: () => [],
|
||||
@@ -1017,7 +1019,7 @@ describe('extensionsCommand', () => {
|
||||
});
|
||||
|
||||
it('shows an error if no extension loader is available', async () => {
|
||||
mockContext.services.config!.getExtensionLoader = vi.fn();
|
||||
mockContext.services.agentContext!.config.getExtensionLoader = vi.fn();
|
||||
|
||||
await restartAction!(mockContext, '--all');
|
||||
|
||||
|
||||
@@ -54,8 +54,8 @@ function showMessageIfNoExtensions(
|
||||
}
|
||||
|
||||
async function listAction(context: CommandContext) {
|
||||
const extensions = context.services.config
|
||||
? listExtensions(context.services.config)
|
||||
const extensions = context.services.agentContext?.config
|
||||
? listExtensions(context.services.agentContext.config)
|
||||
: [];
|
||||
|
||||
if (showMessageIfNoExtensions(context, extensions)) {
|
||||
@@ -88,8 +88,8 @@ function updateAction(context: CommandContext, args: string): Promise<void> {
|
||||
(resolve) => (resolveUpdateComplete = resolve),
|
||||
);
|
||||
|
||||
const extensions = context.services.config
|
||||
? listExtensions(context.services.config)
|
||||
const extensions = context.services.agentContext?.config
|
||||
? listExtensions(context.services.agentContext.config)
|
||||
: [];
|
||||
|
||||
if (showMessageIfNoExtensions(context, extensions)) {
|
||||
@@ -128,7 +128,7 @@ function updateAction(context: CommandContext, args: string): Promise<void> {
|
||||
},
|
||||
});
|
||||
if (names?.length) {
|
||||
const extensions = listExtensions(context.services.config!);
|
||||
const extensions = listExtensions(context.services.agentContext!.config);
|
||||
for (const name of names) {
|
||||
const extension = extensions.find(
|
||||
(extension) => extension.name === name,
|
||||
@@ -156,7 +156,8 @@ async function restartAction(
|
||||
context: CommandContext,
|
||||
args: string,
|
||||
): Promise<void> {
|
||||
const extensionLoader = context.services.config?.getExtensionLoader();
|
||||
const extensionLoader =
|
||||
context.services.agentContext?.config.getExtensionLoader();
|
||||
if (!extensionLoader) {
|
||||
context.ui.addItem({
|
||||
type: MessageType.ERROR,
|
||||
@@ -235,8 +236,8 @@ async function restartAction(
|
||||
|
||||
if (failures.length < extensionsToRestart.length) {
|
||||
try {
|
||||
await context.services.config?.reloadSkills();
|
||||
await context.services.config?.getAgentRegistry()?.reload();
|
||||
await context.services.agentContext?.config.reloadSkills();
|
||||
await context.services.agentContext?.config.getAgentRegistry()?.reload();
|
||||
} catch (error) {
|
||||
context.ui.addItem({
|
||||
type: MessageType.ERROR,
|
||||
@@ -274,7 +275,8 @@ async function exploreAction(
|
||||
const useRegistryUI = settings.experimental?.extensionRegistry;
|
||||
|
||||
if (useRegistryUI) {
|
||||
const extensionManager = context.services.config?.getExtensionLoader();
|
||||
const extensionManager =
|
||||
context.services.agentContext?.config.getExtensionLoader();
|
||||
if (extensionManager instanceof ExtensionManager) {
|
||||
return {
|
||||
type: 'custom_dialog' as const,
|
||||
@@ -331,7 +333,8 @@ function getEnableDisableContext(
|
||||
names: string[];
|
||||
scope: SettingScope;
|
||||
} | null {
|
||||
const extensionLoader = context.services.config?.getExtensionLoader();
|
||||
const extensionLoader =
|
||||
context.services.agentContext?.config.getExtensionLoader();
|
||||
if (!(extensionLoader instanceof ExtensionManager)) {
|
||||
debugLogger.error(
|
||||
`Cannot ${context.invocation?.name} extensions in this environment`,
|
||||
@@ -431,7 +434,8 @@ async function enableAction(context: CommandContext, args: string) {
|
||||
|
||||
if (extension?.mcpServers) {
|
||||
const mcpEnablementManager = McpServerEnablementManager.getInstance();
|
||||
const mcpClientManager = context.services.config?.getMcpClientManager();
|
||||
const mcpClientManager =
|
||||
context.services.agentContext?.config.getMcpClientManager();
|
||||
const enabledServers = await mcpEnablementManager.autoEnableServers(
|
||||
Object.keys(extension.mcpServers ?? {}),
|
||||
);
|
||||
@@ -463,7 +467,8 @@ async function installAction(
|
||||
args: string,
|
||||
requestConsentOverride?: (consent: string) => Promise<boolean>,
|
||||
) {
|
||||
const extensionLoader = context.services.config?.getExtensionLoader();
|
||||
const extensionLoader =
|
||||
context.services.agentContext?.config.getExtensionLoader();
|
||||
if (!(extensionLoader instanceof ExtensionManager)) {
|
||||
debugLogger.error(
|
||||
`Cannot ${context.invocation?.name} extensions in this environment`,
|
||||
@@ -529,7 +534,8 @@ async function installAction(
|
||||
}
|
||||
|
||||
async function linkAction(context: CommandContext, args: string) {
|
||||
const extensionLoader = context.services.config?.getExtensionLoader();
|
||||
const extensionLoader =
|
||||
context.services.agentContext?.config.getExtensionLoader();
|
||||
if (!(extensionLoader instanceof ExtensionManager)) {
|
||||
debugLogger.error(
|
||||
`Cannot ${context.invocation?.name} extensions in this environment`,
|
||||
@@ -593,7 +599,8 @@ async function linkAction(context: CommandContext, args: string) {
|
||||
}
|
||||
|
||||
async function uninstallAction(context: CommandContext, args: string) {
|
||||
const extensionLoader = context.services.config?.getExtensionLoader();
|
||||
const extensionLoader =
|
||||
context.services.agentContext?.config.getExtensionLoader();
|
||||
if (!(extensionLoader instanceof ExtensionManager)) {
|
||||
debugLogger.error(
|
||||
`Cannot ${context.invocation?.name} extensions in this environment`,
|
||||
@@ -692,7 +699,8 @@ async function configAction(context: CommandContext, args: string) {
|
||||
}
|
||||
}
|
||||
|
||||
const extensionManager = context.services.config?.getExtensionLoader();
|
||||
const extensionManager =
|
||||
context.services.agentContext?.config.getExtensionLoader();
|
||||
if (!(extensionManager instanceof ExtensionManager)) {
|
||||
debugLogger.error(
|
||||
`Cannot ${context.invocation?.name} extensions in this environment`,
|
||||
@@ -729,7 +737,7 @@ export function completeExtensions(
|
||||
context: CommandContext,
|
||||
partialArg: string,
|
||||
) {
|
||||
let extensions = context.services.config?.getExtensions() ?? [];
|
||||
let extensions = context.services.agentContext?.config.getExtensions() ?? [];
|
||||
|
||||
if (context.invocation?.name === 'enable') {
|
||||
extensions = extensions.filter((ext) => !ext.isActive);
|
||||
|
||||
@@ -93,7 +93,7 @@ describe('hooksCommand', () => {
|
||||
// Create mock context with config and settings
|
||||
mockContext = createMockCommandContext({
|
||||
services: {
|
||||
config: mockConfig,
|
||||
agentContext: { config: mockConfig },
|
||||
settings: mockSettings,
|
||||
},
|
||||
});
|
||||
@@ -141,7 +141,7 @@ describe('hooksCommand', () => {
|
||||
it('should return error when config is not loaded', async () => {
|
||||
const contextWithoutConfig = createMockCommandContext({
|
||||
services: {
|
||||
config: null,
|
||||
agentContext: null,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -225,7 +225,7 @@ describe('hooksCommand', () => {
|
||||
it('should return error when config is not loaded', async () => {
|
||||
const contextWithoutConfig = createMockCommandContext({
|
||||
services: {
|
||||
config: null,
|
||||
agentContext: null,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -338,7 +338,7 @@ describe('hooksCommand', () => {
|
||||
it('should return error when config is not loaded', async () => {
|
||||
const contextWithoutConfig = createMockCommandContext({
|
||||
services: {
|
||||
config: null,
|
||||
agentContext: null,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -470,7 +470,7 @@ describe('hooksCommand', () => {
|
||||
it('should return empty array when config is not available', () => {
|
||||
const contextWithoutConfig = createMockCommandContext({
|
||||
services: {
|
||||
config: null,
|
||||
agentContext: null,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -567,7 +567,7 @@ describe('hooksCommand', () => {
|
||||
it('should return error when config is not loaded', async () => {
|
||||
const contextWithoutConfig = createMockCommandContext({
|
||||
services: {
|
||||
config: null,
|
||||
agentContext: null,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -691,7 +691,7 @@ describe('hooksCommand', () => {
|
||||
it('should return error when config is not loaded', async () => {
|
||||
const contextWithoutConfig = createMockCommandContext({
|
||||
services: {
|
||||
config: null,
|
||||
agentContext: null,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -27,7 +27,8 @@ import { HooksDialog } from '../components/HooksDialog.js';
|
||||
function panelAction(
|
||||
context: CommandContext,
|
||||
): MessageActionReturn | OpenCustomDialogActionReturn {
|
||||
const { config } = context.services;
|
||||
const agentContext = context.services.agentContext;
|
||||
const config = agentContext?.config;
|
||||
if (!config) {
|
||||
return {
|
||||
type: 'message',
|
||||
@@ -55,7 +56,8 @@ async function enableAction(
|
||||
context: CommandContext,
|
||||
args: string,
|
||||
): Promise<void | MessageActionReturn> {
|
||||
const { config } = context.services;
|
||||
const agentContext = context.services.agentContext;
|
||||
const config = agentContext?.config;
|
||||
if (!config) {
|
||||
return {
|
||||
type: 'message',
|
||||
@@ -108,7 +110,8 @@ async function disableAction(
|
||||
context: CommandContext,
|
||||
args: string,
|
||||
): Promise<void | MessageActionReturn> {
|
||||
const { config } = context.services;
|
||||
const agentContext = context.services.agentContext;
|
||||
const config = agentContext?.config;
|
||||
if (!config) {
|
||||
return {
|
||||
type: 'message',
|
||||
@@ -163,7 +166,8 @@ function completeEnabledHookNames(
|
||||
context: CommandContext,
|
||||
partialArg: string,
|
||||
): string[] {
|
||||
const { config } = context.services;
|
||||
const agentContext = context.services.agentContext;
|
||||
const config = agentContext?.config;
|
||||
if (!config) return [];
|
||||
|
||||
const hookSystem = config.getHookSystem();
|
||||
@@ -183,7 +187,8 @@ function completeDisabledHookNames(
|
||||
context: CommandContext,
|
||||
partialArg: string,
|
||||
): string[] {
|
||||
const { config } = context.services;
|
||||
const agentContext = context.services.agentContext;
|
||||
const config = agentContext?.config;
|
||||
if (!config) return [];
|
||||
|
||||
const hookSystem = config.getHookSystem();
|
||||
@@ -209,7 +214,8 @@ function getHookDisplayName(hook: HookRegistryEntry): string {
|
||||
async function enableAllAction(
|
||||
context: CommandContext,
|
||||
): Promise<void | MessageActionReturn> {
|
||||
const { config } = context.services;
|
||||
const agentContext = context.services.agentContext;
|
||||
const config = agentContext?.config;
|
||||
if (!config) {
|
||||
return {
|
||||
type: 'message',
|
||||
@@ -280,7 +286,8 @@ async function enableAllAction(
|
||||
async function disableAllAction(
|
||||
context: CommandContext,
|
||||
): Promise<void | MessageActionReturn> {
|
||||
const { config } = context.services;
|
||||
const agentContext = context.services.agentContext;
|
||||
const config = agentContext?.config;
|
||||
if (!config) {
|
||||
return {
|
||||
type: 'message',
|
||||
|
||||
@@ -60,10 +60,12 @@ describe('ideCommand', () => {
|
||||
settings: {
|
||||
setValue: vi.fn(),
|
||||
},
|
||||
config: {
|
||||
getIdeMode: vi.fn(),
|
||||
setIdeMode: vi.fn(),
|
||||
getUsageStatisticsEnabled: vi.fn().mockReturnValue(false),
|
||||
agentContext: {
|
||||
config: {
|
||||
getIdeMode: vi.fn(),
|
||||
setIdeMode: vi.fn(),
|
||||
getUsageStatisticsEnabled: vi.fn().mockReturnValue(false),
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as CommandContext;
|
||||
|
||||
@@ -217,9 +217,13 @@ export const ideCommand = async (): Promise<SlashCommand> => {
|
||||
);
|
||||
// Poll for up to 5 seconds for the extension to activate.
|
||||
for (let i = 0; i < 10; i++) {
|
||||
await setIdeModeAndSyncConnection(context.services.config!, true, {
|
||||
logToConsole: false,
|
||||
});
|
||||
await setIdeModeAndSyncConnection(
|
||||
context.services.agentContext!.config,
|
||||
true,
|
||||
{
|
||||
logToConsole: false,
|
||||
},
|
||||
);
|
||||
if (
|
||||
ideClient.getConnectionStatus().status ===
|
||||
IDEConnectionStatus.Connected
|
||||
@@ -262,7 +266,10 @@ export const ideCommand = async (): Promise<SlashCommand> => {
|
||||
'ide.enabled',
|
||||
true,
|
||||
);
|
||||
await setIdeModeAndSyncConnection(context.services.config!, true);
|
||||
await setIdeModeAndSyncConnection(
|
||||
context.services.agentContext!.config,
|
||||
true,
|
||||
);
|
||||
const { messageType, content } = getIdeStatusMessage(ideClient);
|
||||
context.ui.addItem(
|
||||
{
|
||||
@@ -285,7 +292,10 @@ export const ideCommand = async (): Promise<SlashCommand> => {
|
||||
'ide.enabled',
|
||||
false,
|
||||
);
|
||||
await setIdeModeAndSyncConnection(context.services.config!, false);
|
||||
await setIdeModeAndSyncConnection(
|
||||
context.services.agentContext!.config,
|
||||
false,
|
||||
);
|
||||
const { messageType, content } = getIdeStatusMessage(ideClient);
|
||||
context.ui.addItem(
|
||||
{
|
||||
|
||||
@@ -31,8 +31,10 @@ describe('initCommand', () => {
|
||||
// Create a fresh mock context for each test
|
||||
mockContext = createMockCommandContext({
|
||||
services: {
|
||||
config: {
|
||||
getTargetDir: () => targetDir,
|
||||
agentContext: {
|
||||
config: {
|
||||
getTargetDir: () => targetDir,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -94,7 +96,7 @@ describe('initCommand', () => {
|
||||
// Arrange: Create a context without config
|
||||
const noConfigContext = createMockCommandContext();
|
||||
if (noConfigContext.services) {
|
||||
noConfigContext.services.config = null;
|
||||
noConfigContext.services.agentContext = null;
|
||||
}
|
||||
|
||||
// Act: Run the command's action
|
||||
|
||||
@@ -23,14 +23,14 @@ export const initCommand: SlashCommand = {
|
||||
context: CommandContext,
|
||||
_args: string,
|
||||
): Promise<SlashCommandActionReturn> => {
|
||||
if (!context.services.config) {
|
||||
if (!context.services.agentContext?.config) {
|
||||
return {
|
||||
type: 'message',
|
||||
messageType: 'error',
|
||||
content: 'Configuration not available.',
|
||||
};
|
||||
}
|
||||
const targetDir = context.services.config.getTargetDir();
|
||||
const targetDir = context.services.agentContext.config.getTargetDir();
|
||||
const geminiMdPath = path.join(targetDir, 'GEMINI.md');
|
||||
|
||||
const result = performInit(fs.existsSync(geminiMdPath));
|
||||
|
||||
@@ -119,7 +119,10 @@ describe('mcpCommand', () => {
|
||||
|
||||
mockContext = createMockCommandContext({
|
||||
services: {
|
||||
config: mockConfig,
|
||||
agentContext: {
|
||||
config: mockConfig,
|
||||
toolRegistry: mockConfig.getToolRegistry(),
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -132,7 +135,7 @@ describe('mcpCommand', () => {
|
||||
it('should show an error if config is not available', async () => {
|
||||
const contextWithoutConfig = createMockCommandContext({
|
||||
services: {
|
||||
config: null,
|
||||
agentContext: null,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -146,7 +149,8 @@ describe('mcpCommand', () => {
|
||||
});
|
||||
|
||||
it('should show an error if tool registry is not available', async () => {
|
||||
mockConfig.getToolRegistry = vi.fn().mockReturnValue(undefined);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(mockContext.services.agentContext as any).toolRegistry = undefined;
|
||||
|
||||
const result = await mcpCommand.action!(mockContext, '');
|
||||
|
||||
@@ -196,9 +200,13 @@ describe('mcpCommand', () => {
|
||||
...mockServer3Tools,
|
||||
];
|
||||
|
||||
mockConfig.getToolRegistry = vi.fn().mockReturnValue({
|
||||
const mockToolRegistry = {
|
||||
getAllTools: vi.fn().mockReturnValue(allTools),
|
||||
});
|
||||
};
|
||||
mockConfig.getToolRegistry = vi.fn().mockReturnValue(mockToolRegistry);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(mockContext.services.agentContext as any).toolRegistry =
|
||||
mockToolRegistry;
|
||||
|
||||
const resourcesByServer: Record<
|
||||
string,
|
||||
|
||||
@@ -42,8 +42,8 @@ const authCommand: SlashCommand = {
|
||||
args: string,
|
||||
): Promise<MessageActionReturn> => {
|
||||
const serverName = args.trim();
|
||||
const { config } = context.services;
|
||||
|
||||
const agentContext = context.services.agentContext;
|
||||
const config = agentContext?.config;
|
||||
if (!config) {
|
||||
return {
|
||||
type: 'message',
|
||||
@@ -138,7 +138,7 @@ const authCommand: SlashCommand = {
|
||||
await mcpClientManager.restartServer(serverName);
|
||||
}
|
||||
// Update the client with the new tools
|
||||
const geminiClient = config.getGeminiClient();
|
||||
const geminiClient = context.services.agentContext?.geminiClient;
|
||||
if (geminiClient?.isInitialized()) {
|
||||
await geminiClient.setTools();
|
||||
}
|
||||
@@ -162,7 +162,8 @@ const authCommand: SlashCommand = {
|
||||
}
|
||||
},
|
||||
completion: async (context: CommandContext, partialArg: string) => {
|
||||
const { config } = context.services;
|
||||
const agentContext = context.services.agentContext;
|
||||
const config = agentContext?.config;
|
||||
if (!config) return [];
|
||||
|
||||
const mcpServers = config.getMcpClientManager()?.getMcpServers() || {};
|
||||
@@ -177,7 +178,8 @@ const listAction = async (
|
||||
showDescriptions = false,
|
||||
showSchema = false,
|
||||
): Promise<void | MessageActionReturn> => {
|
||||
const { config } = context.services;
|
||||
const agentContext = context.services.agentContext;
|
||||
const config = agentContext?.config;
|
||||
if (!config) {
|
||||
return {
|
||||
type: 'message',
|
||||
@@ -188,7 +190,7 @@ const listAction = async (
|
||||
|
||||
config.setUserInteractedWithMcp();
|
||||
|
||||
const toolRegistry = config.getToolRegistry();
|
||||
const toolRegistry = agentContext.toolRegistry;
|
||||
if (!toolRegistry) {
|
||||
return {
|
||||
type: 'message',
|
||||
@@ -334,7 +336,8 @@ const reloadCommand: SlashCommand = {
|
||||
action: async (
|
||||
context: CommandContext,
|
||||
): Promise<void | SlashCommandActionReturn> => {
|
||||
const { config } = context.services;
|
||||
const agentContext = context.services.agentContext;
|
||||
const config = agentContext?.config;
|
||||
if (!config) {
|
||||
return {
|
||||
type: 'message',
|
||||
@@ -360,7 +363,7 @@ const reloadCommand: SlashCommand = {
|
||||
await mcpClientManager.restart();
|
||||
|
||||
// Update the client with the new tools
|
||||
const geminiClient = config.getGeminiClient();
|
||||
const geminiClient = agentContext.geminiClient;
|
||||
if (geminiClient?.isInitialized()) {
|
||||
await geminiClient.setTools();
|
||||
}
|
||||
@@ -377,7 +380,8 @@ async function handleEnableDisable(
|
||||
args: string,
|
||||
enable: boolean,
|
||||
): Promise<MessageActionReturn> {
|
||||
const { config } = context.services;
|
||||
const agentContext = context.services.agentContext;
|
||||
const config = agentContext?.config;
|
||||
if (!config) {
|
||||
return {
|
||||
type: 'message',
|
||||
@@ -465,8 +469,8 @@ async function handleEnableDisable(
|
||||
);
|
||||
await mcpClientManager.restart();
|
||||
}
|
||||
if (config.getGeminiClient()?.isInitialized())
|
||||
await config.getGeminiClient().setTools();
|
||||
if (agentContext.geminiClient?.isInitialized())
|
||||
await agentContext.geminiClient.setTools();
|
||||
context.ui.reloadCommands();
|
||||
|
||||
return { type: 'message', messageType: 'info', content: msg };
|
||||
@@ -477,7 +481,8 @@ async function getEnablementCompletion(
|
||||
partialArg: string,
|
||||
showEnabled: boolean,
|
||||
): Promise<string[]> {
|
||||
const { config } = context.services;
|
||||
const agentContext = context.services.agentContext;
|
||||
const config = agentContext?.config;
|
||||
if (!config) return [];
|
||||
const servers = Object.keys(
|
||||
config.getMcpClientManager()?.getMcpServers() || {},
|
||||
|
||||
@@ -102,10 +102,12 @@ describe('memoryCommand', () => {
|
||||
|
||||
mockContext = createMockCommandContext({
|
||||
services: {
|
||||
config: {
|
||||
getUserMemory: mockGetUserMemory,
|
||||
getGeminiMdFileCount: mockGetGeminiMdFileCount,
|
||||
getExtensionLoader: () => new SimpleExtensionLoader([]),
|
||||
agentContext: {
|
||||
config: {
|
||||
getUserMemory: mockGetUserMemory,
|
||||
getGeminiMdFileCount: mockGetGeminiMdFileCount,
|
||||
getExtensionLoader: () => new SimpleExtensionLoader([]),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -250,7 +252,7 @@ describe('memoryCommand', () => {
|
||||
|
||||
mockContext = createMockCommandContext({
|
||||
services: {
|
||||
config: mockConfig,
|
||||
agentContext: { config: mockConfig },
|
||||
settings: {
|
||||
merged: {
|
||||
memoryDiscoveryMaxDirs: 1000,
|
||||
@@ -268,7 +270,7 @@ describe('memoryCommand', () => {
|
||||
if (!reloadCommand.action) throw new Error('Command has no action');
|
||||
|
||||
// Enable JIT in mock config
|
||||
const config = mockContext.services.config;
|
||||
const config = mockContext.services.agentContext?.config;
|
||||
if (!config) throw new Error('Config is undefined');
|
||||
|
||||
vi.mocked(config.isJitContextEnabled).mockReturnValue(true);
|
||||
@@ -370,7 +372,7 @@ describe('memoryCommand', () => {
|
||||
if (!reloadCommand.action) throw new Error('Command has no action');
|
||||
|
||||
const nullConfigContext = createMockCommandContext({
|
||||
services: { config: null },
|
||||
services: { agentContext: null },
|
||||
});
|
||||
|
||||
await expect(
|
||||
@@ -413,8 +415,10 @@ describe('memoryCommand', () => {
|
||||
});
|
||||
mockContext = createMockCommandContext({
|
||||
services: {
|
||||
config: {
|
||||
getGeminiMdFilePaths: mockGetGeminiMdfilePaths,
|
||||
agentContext: {
|
||||
config: {
|
||||
getGeminiMdFilePaths: mockGetGeminiMdfilePaths,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -29,7 +29,7 @@ export const memoryCommand: SlashCommand = {
|
||||
kind: CommandKind.BUILT_IN,
|
||||
autoExecute: true,
|
||||
action: async (context) => {
|
||||
const config = context.services.config;
|
||||
const config = context.services.agentContext?.config;
|
||||
if (!config) return;
|
||||
const result = showMemory(config);
|
||||
|
||||
@@ -81,7 +81,7 @@ export const memoryCommand: SlashCommand = {
|
||||
);
|
||||
|
||||
try {
|
||||
const config = context.services.config;
|
||||
const config = context.services.agentContext?.config;
|
||||
if (config) {
|
||||
const result = await refreshMemory(config);
|
||||
|
||||
@@ -111,7 +111,7 @@ export const memoryCommand: SlashCommand = {
|
||||
kind: CommandKind.BUILT_IN,
|
||||
autoExecute: true,
|
||||
action: async (context) => {
|
||||
const config = context.services.config;
|
||||
const config = context.services.agentContext?.config;
|
||||
if (!config) return;
|
||||
const result = listMemoryFiles(config);
|
||||
|
||||
|
||||
@@ -37,8 +37,11 @@ describe('modelCommand', () => {
|
||||
}
|
||||
|
||||
const mockRefreshUserQuota = vi.fn();
|
||||
mockContext.services.config = {
|
||||
mockContext.services.agentContext = {
|
||||
refreshUserQuota: mockRefreshUserQuota,
|
||||
get config() {
|
||||
return this;
|
||||
},
|
||||
} as unknown as Config;
|
||||
|
||||
await modelCommand.action(mockContext, '');
|
||||
@@ -66,8 +69,11 @@ describe('modelCommand', () => {
|
||||
(c) => c.name === 'manage',
|
||||
);
|
||||
const mockRefreshUserQuota = vi.fn();
|
||||
mockContext.services.config = {
|
||||
mockContext.services.agentContext = {
|
||||
refreshUserQuota: mockRefreshUserQuota,
|
||||
get config() {
|
||||
return this;
|
||||
},
|
||||
} as unknown as Config;
|
||||
|
||||
await manageCommand!.action!(mockContext, '');
|
||||
@@ -84,7 +90,7 @@ describe('modelCommand', () => {
|
||||
expect(setCommand).toBeDefined();
|
||||
|
||||
const mockSetModel = vi.fn();
|
||||
mockContext.services.config = {
|
||||
mockContext.services.agentContext = {
|
||||
setModel: mockSetModel,
|
||||
getHasAccessToPreviewModel: vi.fn().mockReturnValue(true),
|
||||
getUserId: vi.fn().mockReturnValue('test-user'),
|
||||
@@ -98,6 +104,9 @@ describe('modelCommand', () => {
|
||||
getPolicyEngine: vi.fn().mockReturnValue({
|
||||
getApprovalMode: vi.fn().mockReturnValue('auto'),
|
||||
}),
|
||||
get config() {
|
||||
return this;
|
||||
},
|
||||
} as unknown as Config;
|
||||
|
||||
await setCommand!.action!(mockContext, 'gemini-pro');
|
||||
@@ -116,7 +125,7 @@ describe('modelCommand', () => {
|
||||
(c) => c.name === 'set',
|
||||
);
|
||||
const mockSetModel = vi.fn();
|
||||
mockContext.services.config = {
|
||||
mockContext.services.agentContext = {
|
||||
setModel: mockSetModel,
|
||||
getHasAccessToPreviewModel: vi.fn().mockReturnValue(true),
|
||||
getUserId: vi.fn().mockReturnValue('test-user'),
|
||||
@@ -130,6 +139,9 @@ describe('modelCommand', () => {
|
||||
getPolicyEngine: vi.fn().mockReturnValue({
|
||||
getApprovalMode: vi.fn().mockReturnValue('auto'),
|
||||
}),
|
||||
get config() {
|
||||
return this;
|
||||
},
|
||||
} as unknown as Config;
|
||||
|
||||
await setCommand!.action!(mockContext, 'gemini-pro --persist');
|
||||
|
||||
@@ -34,10 +34,10 @@ const setModelCommand: SlashCommand = {
|
||||
const modelName = parts[0];
|
||||
const persist = parts.includes('--persist');
|
||||
|
||||
if (context.services.config) {
|
||||
context.services.config.setModel(modelName, !persist);
|
||||
if (context.services.agentContext?.config) {
|
||||
context.services.agentContext.config.setModel(modelName, !persist);
|
||||
const event = new ModelSlashCommandEvent(modelName);
|
||||
logModelSlashCommand(context.services.config, event);
|
||||
logModelSlashCommand(context.services.agentContext.config, event);
|
||||
|
||||
context.ui.addItem({
|
||||
type: MessageType.INFO,
|
||||
@@ -53,8 +53,8 @@ const manageModelCommand: SlashCommand = {
|
||||
kind: CommandKind.BUILT_IN,
|
||||
autoExecute: true,
|
||||
action: async (context: CommandContext) => {
|
||||
if (context.services.config) {
|
||||
await context.services.config.refreshUserQuota();
|
||||
if (context.services.agentContext?.config) {
|
||||
await context.services.agentContext.config.refreshUserQuota();
|
||||
}
|
||||
return {
|
||||
type: 'dialog',
|
||||
|
||||
@@ -24,7 +24,8 @@ export const oncallCommand: SlashCommand = {
|
||||
kind: CommandKind.BUILT_IN,
|
||||
autoExecute: true,
|
||||
action: async (context, args): Promise<OpenCustomDialogActionReturn> => {
|
||||
const { config } = context.services;
|
||||
const agentContext = context.services.agentContext;
|
||||
const config = agentContext?.config;
|
||||
if (!config) {
|
||||
throw new Error('Config not available');
|
||||
}
|
||||
@@ -56,7 +57,8 @@ export const oncallCommand: SlashCommand = {
|
||||
kind: CommandKind.BUILT_IN,
|
||||
autoExecute: true,
|
||||
action: async (context, args): Promise<OpenCustomDialogActionReturn> => {
|
||||
const { config } = context.services;
|
||||
const agentContext = context.services.agentContext;
|
||||
const config = agentContext?.config;
|
||||
if (!config) {
|
||||
throw new Error('Config not available');
|
||||
}
|
||||
|
||||
@@ -52,14 +52,16 @@ describe('planCommand', () => {
|
||||
beforeEach(() => {
|
||||
mockContext = createMockCommandContext({
|
||||
services: {
|
||||
config: {
|
||||
isPlanEnabled: vi.fn(),
|
||||
setApprovalMode: vi.fn(),
|
||||
getApprovedPlanPath: vi.fn(),
|
||||
getApprovalMode: vi.fn(),
|
||||
getFileSystemService: vi.fn(),
|
||||
storage: {
|
||||
getPlansDir: vi.fn().mockReturnValue('/mock/plans/dir'),
|
||||
agentContext: {
|
||||
config: {
|
||||
isPlanEnabled: vi.fn(),
|
||||
setApprovalMode: vi.fn(),
|
||||
getApprovedPlanPath: vi.fn(),
|
||||
getApprovalMode: vi.fn(),
|
||||
getFileSystemService: vi.fn(),
|
||||
storage: {
|
||||
getPlansDir: vi.fn().mockReturnValue('/mock/plans/dir'),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -83,17 +85,19 @@ describe('planCommand', () => {
|
||||
});
|
||||
|
||||
it('should switch to plan mode if enabled', async () => {
|
||||
vi.mocked(mockContext.services.config!.isPlanEnabled).mockReturnValue(true);
|
||||
vi.mocked(mockContext.services.config!.getApprovedPlanPath).mockReturnValue(
|
||||
undefined,
|
||||
);
|
||||
vi.mocked(
|
||||
mockContext.services.agentContext!.config.isPlanEnabled,
|
||||
).mockReturnValue(true);
|
||||
vi.mocked(
|
||||
mockContext.services.agentContext!.config.getApprovedPlanPath,
|
||||
).mockReturnValue(undefined);
|
||||
|
||||
if (!planCommand.action) throw new Error('Action missing');
|
||||
await planCommand.action(mockContext, '');
|
||||
|
||||
expect(mockContext.services.config!.setApprovalMode).toHaveBeenCalledWith(
|
||||
ApprovalMode.PLAN,
|
||||
);
|
||||
expect(
|
||||
mockContext.services.agentContext!.config.setApprovalMode,
|
||||
).toHaveBeenCalledWith(ApprovalMode.PLAN);
|
||||
expect(coreEvents.emitFeedback).toHaveBeenCalledWith(
|
||||
'info',
|
||||
'Switched to Plan Mode.',
|
||||
@@ -102,10 +106,12 @@ describe('planCommand', () => {
|
||||
|
||||
it('should display the approved plan from config', async () => {
|
||||
const mockPlanPath = '/mock/plans/dir/approved-plan.md';
|
||||
vi.mocked(mockContext.services.config!.isPlanEnabled).mockReturnValue(true);
|
||||
vi.mocked(mockContext.services.config!.getApprovedPlanPath).mockReturnValue(
|
||||
mockPlanPath,
|
||||
);
|
||||
vi.mocked(
|
||||
mockContext.services.agentContext!.config.isPlanEnabled,
|
||||
).mockReturnValue(true);
|
||||
vi.mocked(
|
||||
mockContext.services.agentContext!.config.getApprovedPlanPath,
|
||||
).mockReturnValue(mockPlanPath);
|
||||
vi.mocked(processSingleFileContent).mockResolvedValue({
|
||||
llmContent: '# Approved Plan Content',
|
||||
returnDisplay: '# Approved Plan Content',
|
||||
@@ -128,7 +134,7 @@ describe('planCommand', () => {
|
||||
it('should copy the approved plan to clipboard', async () => {
|
||||
const mockPlanPath = '/mock/plans/dir/approved-plan.md';
|
||||
vi.mocked(
|
||||
mockContext.services.config!.getApprovedPlanPath,
|
||||
mockContext.services.agentContext!.config.getApprovedPlanPath,
|
||||
).mockReturnValue(mockPlanPath);
|
||||
vi.mocked(readFileWithEncoding).mockResolvedValue('# Plan Content');
|
||||
|
||||
@@ -149,7 +155,7 @@ describe('planCommand', () => {
|
||||
|
||||
it('should warn if no approved plan is found', async () => {
|
||||
vi.mocked(
|
||||
mockContext.services.config!.getApprovedPlanPath,
|
||||
mockContext.services.agentContext!.config.getApprovedPlanPath,
|
||||
).mockReturnValue(undefined);
|
||||
|
||||
const copySubCommand = planCommand.subCommands?.find(
|
||||
|
||||
@@ -22,7 +22,7 @@ import * as path from 'node:path';
|
||||
import { copyToClipboard } from '../utils/commandUtils.js';
|
||||
|
||||
async function copyAction(context: CommandContext) {
|
||||
const config = context.services.config;
|
||||
const config = context.services.agentContext?.config;
|
||||
if (!config) {
|
||||
debugLogger.debug('Plan copy command: config is not available in context');
|
||||
return;
|
||||
@@ -53,7 +53,7 @@ export const planCommand: SlashCommand = {
|
||||
kind: CommandKind.BUILT_IN,
|
||||
autoExecute: false,
|
||||
action: async (context) => {
|
||||
const config = context.services.config;
|
||||
const config = context.services.agentContext?.config;
|
||||
if (!config) {
|
||||
debugLogger.debug('Plan command: config is not available in context');
|
||||
return;
|
||||
|
||||
@@ -32,7 +32,7 @@ describe('policiesCommand', () => {
|
||||
|
||||
describe('list subcommand', () => {
|
||||
it('should show error if config is missing', async () => {
|
||||
mockContext.services.config = null;
|
||||
mockContext.services.agentContext = null;
|
||||
const listCommand = policiesCommand.subCommands![0];
|
||||
|
||||
await listCommand.action!(mockContext, '');
|
||||
@@ -50,8 +50,11 @@ describe('policiesCommand', () => {
|
||||
const mockPolicyEngine = {
|
||||
getRules: vi.fn().mockReturnValue([]),
|
||||
};
|
||||
mockContext.services.config = {
|
||||
mockContext.services.agentContext = {
|
||||
getPolicyEngine: vi.fn().mockReturnValue(mockPolicyEngine),
|
||||
get config() {
|
||||
return this;
|
||||
},
|
||||
} as unknown as Config;
|
||||
|
||||
const listCommand = policiesCommand.subCommands![0];
|
||||
@@ -85,8 +88,11 @@ describe('policiesCommand', () => {
|
||||
const mockPolicyEngine = {
|
||||
getRules: vi.fn().mockReturnValue(mockRules),
|
||||
};
|
||||
mockContext.services.config = {
|
||||
mockContext.services.agentContext = {
|
||||
getPolicyEngine: vi.fn().mockReturnValue(mockPolicyEngine),
|
||||
get config() {
|
||||
return this;
|
||||
},
|
||||
} as unknown as Config;
|
||||
|
||||
const listCommand = policiesCommand.subCommands![0];
|
||||
@@ -142,8 +148,11 @@ describe('policiesCommand', () => {
|
||||
const mockPolicyEngine = {
|
||||
getRules: vi.fn().mockReturnValue(mockRules),
|
||||
};
|
||||
mockContext.services.config = {
|
||||
mockContext.services.agentContext = {
|
||||
getPolicyEngine: vi.fn().mockReturnValue(mockPolicyEngine),
|
||||
get config() {
|
||||
return this;
|
||||
},
|
||||
} as unknown as Config;
|
||||
|
||||
const listCommand = policiesCommand.subCommands![0];
|
||||
|
||||
@@ -51,7 +51,8 @@ const listPoliciesCommand: SlashCommand = {
|
||||
kind: CommandKind.BUILT_IN,
|
||||
autoExecute: true,
|
||||
action: async (context) => {
|
||||
const { config } = context.services;
|
||||
const agentContext = context.services.agentContext;
|
||||
const config = agentContext?.config;
|
||||
if (!config) {
|
||||
context.ui.addItem(
|
||||
{
|
||||
|
||||
@@ -47,14 +47,17 @@ describe('restoreCommand', () => {
|
||||
getProjectTempCheckpointsDir: vi.fn().mockReturnValue(checkpointsDir),
|
||||
getProjectTempDir: vi.fn().mockReturnValue(geminiTempDir),
|
||||
},
|
||||
getGeminiClient: vi.fn().mockReturnValue({
|
||||
geminiClient: {
|
||||
setHistory: mockSetHistory,
|
||||
}),
|
||||
},
|
||||
get config() {
|
||||
return this;
|
||||
},
|
||||
} as unknown as Config;
|
||||
|
||||
mockContext = createMockCommandContext({
|
||||
services: {
|
||||
config: mockConfig,
|
||||
agentContext: mockConfig,
|
||||
git: mockGitService,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -37,10 +37,11 @@ async function restoreAction(
|
||||
args: string,
|
||||
): Promise<void | SlashCommandActionReturn> {
|
||||
const { services, ui } = context;
|
||||
const { config, git: gitService } = services;
|
||||
const { agentContext, git: gitService } = services;
|
||||
const { addItem, loadHistory } = ui;
|
||||
|
||||
const checkpointDir = config?.storage.getProjectTempCheckpointsDir();
|
||||
const checkpointDir =
|
||||
agentContext?.config.storage.getProjectTempCheckpointsDir();
|
||||
|
||||
if (!checkpointDir) {
|
||||
return {
|
||||
@@ -116,7 +117,7 @@ async function restoreAction(
|
||||
} else if (action.type === 'load_history' && loadHistory) {
|
||||
loadHistory(action.history);
|
||||
if (action.clientHistory) {
|
||||
config?.getGeminiClient()?.setHistory(action.clientHistory);
|
||||
agentContext!.geminiClient?.setHistory(action.clientHistory);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,8 +141,9 @@ async function completion(
|
||||
_partialArg: string,
|
||||
): Promise<string[]> {
|
||||
const { services } = context;
|
||||
const { config } = services;
|
||||
const checkpointDir = config?.storage.getProjectTempCheckpointsDir();
|
||||
const { agentContext } = services;
|
||||
const checkpointDir =
|
||||
agentContext?.config.storage.getProjectTempCheckpointsDir();
|
||||
if (!checkpointDir) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -97,15 +97,17 @@ describe('rewindCommand', () => {
|
||||
|
||||
mockContext = createMockCommandContext({
|
||||
services: {
|
||||
config: {
|
||||
getGeminiClient: () => ({
|
||||
agentContext: {
|
||||
geminiClient: {
|
||||
getChatRecordingService: mockGetChatRecordingService,
|
||||
setHistory: mockSetHistory,
|
||||
sendMessageStream: mockSendMessageStream,
|
||||
}),
|
||||
getSessionId: () => 'test-session-id',
|
||||
getContextManager: () => ({ refresh: mockResetContext }),
|
||||
getProjectRoot: mockGetProjectRoot,
|
||||
},
|
||||
config: {
|
||||
getSessionId: () => 'test-session-id',
|
||||
getContextManager: () => ({ refresh: mockResetContext }),
|
||||
getProjectRoot: mockGetProjectRoot,
|
||||
},
|
||||
},
|
||||
},
|
||||
ui: {
|
||||
@@ -293,7 +295,12 @@ describe('rewindCommand', () => {
|
||||
it('should fail if client is not initialized', () => {
|
||||
const context = createMockCommandContext({
|
||||
services: {
|
||||
config: { getGeminiClient: () => undefined },
|
||||
agentContext: {
|
||||
geminiClient: undefined,
|
||||
get config() {
|
||||
return this;
|
||||
},
|
||||
},
|
||||
},
|
||||
}) as unknown as CommandContext;
|
||||
|
||||
@@ -309,8 +316,11 @@ describe('rewindCommand', () => {
|
||||
it('should fail if recording service is unavailable', () => {
|
||||
const context = createMockCommandContext({
|
||||
services: {
|
||||
config: {
|
||||
getGeminiClient: () => ({ getChatRecordingService: () => undefined }),
|
||||
agentContext: {
|
||||
geminiClient: { getChatRecordingService: () => undefined },
|
||||
get config() {
|
||||
return this;
|
||||
},
|
||||
},
|
||||
},
|
||||
}) as unknown as CommandContext;
|
||||
|
||||
@@ -61,7 +61,7 @@ async function rewindConversation(
|
||||
client.setHistory(clientHistory as Content[]);
|
||||
|
||||
// Reset context manager as we are rewinding history
|
||||
await context.services.config?.getContextManager()?.refresh();
|
||||
await context.services.agentContext?.config.getContextManager()?.refresh();
|
||||
|
||||
// Update UI History
|
||||
// We generate IDs based on index for the rewind history
|
||||
@@ -94,7 +94,8 @@ export const rewindCommand: SlashCommand = {
|
||||
description: 'Jump back to a specific message and restart the conversation',
|
||||
kind: CommandKind.BUILT_IN,
|
||||
action: (context) => {
|
||||
const config = context.services.config;
|
||||
const agentContext = context.services.agentContext;
|
||||
const config = agentContext?.config;
|
||||
if (!config)
|
||||
return {
|
||||
type: 'message',
|
||||
@@ -102,7 +103,7 @@ export const rewindCommand: SlashCommand = {
|
||||
content: 'Config not found',
|
||||
};
|
||||
|
||||
const client = config.getGeminiClient();
|
||||
const client = agentContext.geminiClient;
|
||||
if (!client)
|
||||
return {
|
||||
type: 'message',
|
||||
|
||||
@@ -230,7 +230,7 @@ export const setupGithubCommand: SlashCommand = {
|
||||
}
|
||||
|
||||
// Get the latest release tag from GitHub
|
||||
const proxy = context?.services?.config?.getProxy();
|
||||
const proxy = context?.services?.agentContext?.config.getProxy();
|
||||
const releaseTag = await getLatestGitHubRelease(proxy);
|
||||
const readmeUrl = `https://github.com/google-github-actions/run-gemini-cli/blob/${releaseTag}/README.md#quick-start`;
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ describe('skillsCommand', () => {
|
||||
];
|
||||
context = createMockCommandContext({
|
||||
services: {
|
||||
config: {
|
||||
agentContext: {
|
||||
getSkillManager: vi.fn().mockReturnValue({
|
||||
getAllSkills: vi.fn().mockReturnValue(skills),
|
||||
getSkills: vi.fn().mockReturnValue(skills),
|
||||
@@ -80,6 +80,9 @@ describe('skillsCommand', () => {
|
||||
),
|
||||
}),
|
||||
getContentGenerator: vi.fn(),
|
||||
get config() {
|
||||
return this;
|
||||
},
|
||||
} as unknown as Config,
|
||||
settings: {
|
||||
merged: createTestMergedSettings({ skills: { disabled: [] } }),
|
||||
@@ -162,7 +165,8 @@ describe('skillsCommand', () => {
|
||||
});
|
||||
|
||||
it('should filter built-in skills by default and show them with "all"', async () => {
|
||||
const skillManager = context.services.config!.getSkillManager();
|
||||
const skillManager =
|
||||
context.services.agentContext!.config.getSkillManager();
|
||||
const mockSkills = [
|
||||
{
|
||||
name: 'regular',
|
||||
@@ -452,7 +456,8 @@ describe('skillsCommand', () => {
|
||||
});
|
||||
|
||||
it('should show error if skills are disabled by admin during disable', async () => {
|
||||
const skillManager = context.services.config!.getSkillManager();
|
||||
const skillManager =
|
||||
context.services.agentContext!.config.getSkillManager();
|
||||
vi.mocked(skillManager.isAdminEnabled).mockReturnValue(false);
|
||||
|
||||
const disableCmd = skillsCommand.subCommands!.find(
|
||||
@@ -470,7 +475,8 @@ describe('skillsCommand', () => {
|
||||
});
|
||||
|
||||
it('should show error if skills are disabled by admin during enable', async () => {
|
||||
const skillManager = context.services.config!.getSkillManager();
|
||||
const skillManager =
|
||||
context.services.agentContext!.config.getSkillManager();
|
||||
vi.mocked(skillManager.isAdminEnabled).mockReturnValue(false);
|
||||
|
||||
const enableCmd = skillsCommand.subCommands!.find(
|
||||
@@ -497,8 +503,7 @@ describe('skillsCommand', () => {
|
||||
const reloadSkillsMock = vi.fn().mockImplementation(async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
});
|
||||
// @ts-expect-error Mocking reloadSkills
|
||||
context.services.config.reloadSkills = reloadSkillsMock;
|
||||
context.services.agentContext!.config.reloadSkills = reloadSkillsMock;
|
||||
|
||||
const actionPromise = reloadCmd.action!(context, '');
|
||||
|
||||
@@ -537,15 +542,15 @@ describe('skillsCommand', () => {
|
||||
(s) => s.name === 'reload',
|
||||
)!;
|
||||
const reloadSkillsMock = vi.fn().mockImplementation(async () => {
|
||||
const skillManager = context.services.config!.getSkillManager();
|
||||
const skillManager =
|
||||
context.services.agentContext!.config.getSkillManager();
|
||||
vi.mocked(skillManager.getSkills).mockReturnValue([
|
||||
{ name: 'skill1' },
|
||||
{ name: 'skill2' },
|
||||
{ name: 'skill3' },
|
||||
] as SkillDefinition[]);
|
||||
});
|
||||
// @ts-expect-error Mocking reloadSkills
|
||||
context.services.config.reloadSkills = reloadSkillsMock;
|
||||
context.services.agentContext!.config.reloadSkills = reloadSkillsMock;
|
||||
|
||||
await reloadCmd.action!(context, '');
|
||||
|
||||
@@ -562,13 +567,13 @@ describe('skillsCommand', () => {
|
||||
(s) => s.name === 'reload',
|
||||
)!;
|
||||
const reloadSkillsMock = vi.fn().mockImplementation(async () => {
|
||||
const skillManager = context.services.config!.getSkillManager();
|
||||
const skillManager =
|
||||
context.services.agentContext!.config.getSkillManager();
|
||||
vi.mocked(skillManager.getSkills).mockReturnValue([
|
||||
{ name: 'skill1' },
|
||||
] as SkillDefinition[]);
|
||||
});
|
||||
// @ts-expect-error Mocking reloadSkills
|
||||
context.services.config.reloadSkills = reloadSkillsMock;
|
||||
context.services.agentContext!.config.reloadSkills = reloadSkillsMock;
|
||||
|
||||
await reloadCmd.action!(context, '');
|
||||
|
||||
@@ -585,14 +590,14 @@ describe('skillsCommand', () => {
|
||||
(s) => s.name === 'reload',
|
||||
)!;
|
||||
const reloadSkillsMock = vi.fn().mockImplementation(async () => {
|
||||
const skillManager = context.services.config!.getSkillManager();
|
||||
const skillManager =
|
||||
context.services.agentContext!.config.getSkillManager();
|
||||
vi.mocked(skillManager.getSkills).mockReturnValue([
|
||||
{ name: 'skill2' }, // skill1 removed, skill3 added
|
||||
{ name: 'skill3' },
|
||||
] as SkillDefinition[]);
|
||||
});
|
||||
// @ts-expect-error Mocking reloadSkills
|
||||
context.services.config.reloadSkills = reloadSkillsMock;
|
||||
context.services.agentContext!.config.reloadSkills = reloadSkillsMock;
|
||||
|
||||
await reloadCmd.action!(context, '');
|
||||
|
||||
@@ -608,7 +613,7 @@ describe('skillsCommand', () => {
|
||||
const reloadCmd = skillsCommand.subCommands!.find(
|
||||
(s) => s.name === 'reload',
|
||||
)!;
|
||||
context.services.config = null;
|
||||
context.services.agentContext = null;
|
||||
|
||||
await reloadCmd.action!(context, '');
|
||||
|
||||
@@ -628,8 +633,7 @@ describe('skillsCommand', () => {
|
||||
const reloadSkillsMock = vi.fn().mockImplementation(async () => {
|
||||
await new Promise((_, reject) => setTimeout(() => reject(error), 200));
|
||||
});
|
||||
// @ts-expect-error Mocking reloadSkills
|
||||
context.services.config.reloadSkills = reloadSkillsMock;
|
||||
context.services.agentContext!.config.reloadSkills = reloadSkillsMock;
|
||||
|
||||
const actionPromise = reloadCmd.action!(context, '');
|
||||
await vi.advanceTimersByTimeAsync(100);
|
||||
@@ -651,7 +655,8 @@ describe('skillsCommand', () => {
|
||||
const disableCmd = skillsCommand.subCommands!.find(
|
||||
(s) => s.name === 'disable',
|
||||
)!;
|
||||
const skillManager = context.services.config!.getSkillManager();
|
||||
const skillManager =
|
||||
context.services.agentContext!.config.getSkillManager();
|
||||
const mockSkills = [
|
||||
{
|
||||
name: 'skill1',
|
||||
@@ -681,7 +686,8 @@ describe('skillsCommand', () => {
|
||||
const enableCmd = skillsCommand.subCommands!.find(
|
||||
(s) => s.name === 'enable',
|
||||
)!;
|
||||
const skillManager = context.services.config!.getSkillManager();
|
||||
const skillManager =
|
||||
context.services.agentContext!.config.getSkillManager();
|
||||
const mockSkills = [
|
||||
{
|
||||
name: 'skill1',
|
||||
|
||||
@@ -46,7 +46,7 @@ async function listAction(
|
||||
}
|
||||
}
|
||||
|
||||
const skillManager = context.services.config?.getSkillManager();
|
||||
const skillManager = context.services.agentContext?.config.getSkillManager();
|
||||
if (!skillManager) {
|
||||
context.ui.addItem({
|
||||
type: MessageType.ERROR,
|
||||
@@ -127,8 +127,8 @@ async function linkAction(
|
||||
text: `Successfully linked skills from "${sourcePath}" (${scope}).`,
|
||||
});
|
||||
|
||||
if (context.services.config) {
|
||||
await context.services.config.reloadSkills();
|
||||
if (context.services.agentContext?.config) {
|
||||
await context.services.agentContext.config.reloadSkills();
|
||||
}
|
||||
} catch (error) {
|
||||
context.ui.addItem({
|
||||
@@ -150,14 +150,14 @@ async function disableAction(
|
||||
});
|
||||
return;
|
||||
}
|
||||
const skillManager = context.services.config?.getSkillManager();
|
||||
const skillManager = context.services.agentContext?.config.getSkillManager();
|
||||
if (skillManager?.isAdminEnabled() === false) {
|
||||
context.ui.addItem(
|
||||
{
|
||||
type: MessageType.ERROR,
|
||||
text: getAdminErrorMessage(
|
||||
'Agent skills',
|
||||
context.services.config ?? undefined,
|
||||
context.services.agentContext?.config ?? undefined,
|
||||
),
|
||||
},
|
||||
Date.now(),
|
||||
@@ -211,14 +211,14 @@ async function enableAction(
|
||||
return;
|
||||
}
|
||||
|
||||
const skillManager = context.services.config?.getSkillManager();
|
||||
const skillManager = context.services.agentContext?.config.getSkillManager();
|
||||
if (skillManager?.isAdminEnabled() === false) {
|
||||
context.ui.addItem(
|
||||
{
|
||||
type: MessageType.ERROR,
|
||||
text: getAdminErrorMessage(
|
||||
'Agent skills',
|
||||
context.services.config ?? undefined,
|
||||
context.services.agentContext?.config ?? undefined,
|
||||
),
|
||||
},
|
||||
Date.now(),
|
||||
@@ -246,7 +246,7 @@ async function enableAction(
|
||||
async function reloadAction(
|
||||
context: CommandContext,
|
||||
): Promise<void | SlashCommandActionReturn> {
|
||||
const config = context.services.config;
|
||||
const config = context.services.agentContext?.config;
|
||||
if (!config) {
|
||||
context.ui.addItem({
|
||||
type: MessageType.ERROR,
|
||||
@@ -333,7 +333,7 @@ function disableCompletion(
|
||||
context: CommandContext,
|
||||
partialArg: string,
|
||||
): string[] {
|
||||
const skillManager = context.services.config?.getSkillManager();
|
||||
const skillManager = context.services.agentContext?.config.getSkillManager();
|
||||
if (!skillManager) {
|
||||
return [];
|
||||
}
|
||||
@@ -347,7 +347,7 @@ function enableCompletion(
|
||||
context: CommandContext,
|
||||
partialArg: string,
|
||||
): string[] {
|
||||
const skillManager = context.services.config?.getSkillManager();
|
||||
const skillManager = context.services.agentContext?.config.getSkillManager();
|
||||
if (!skillManager) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -43,12 +43,15 @@ describe('statsCommand', () => {
|
||||
it('should display general session stats when run with no subcommand', async () => {
|
||||
if (!statsCommand.action) throw new Error('Command has no action');
|
||||
|
||||
mockContext.services.config = {
|
||||
mockContext.services.agentContext = {
|
||||
refreshUserQuota: vi.fn(),
|
||||
refreshAvailableCredits: vi.fn(),
|
||||
getUserTierName: vi.fn(),
|
||||
getUserPaidTier: vi.fn(),
|
||||
getModel: vi.fn(),
|
||||
get config() {
|
||||
return this;
|
||||
},
|
||||
} as unknown as Config;
|
||||
|
||||
await statsCommand.action(mockContext, '');
|
||||
@@ -80,7 +83,7 @@ describe('statsCommand', () => {
|
||||
.fn()
|
||||
.mockReturnValue('2025-01-01T12:00:00Z');
|
||||
|
||||
mockContext.services.config = {
|
||||
mockContext.services.agentContext = {
|
||||
refreshUserQuota: mockRefreshUserQuota,
|
||||
getUserTierName: mockGetUserTierName,
|
||||
getModel: mockGetModel,
|
||||
@@ -89,6 +92,9 @@ describe('statsCommand', () => {
|
||||
getQuotaResetTime: mockGetQuotaResetTime,
|
||||
getUserPaidTier: vi.fn(),
|
||||
refreshAvailableCredits: vi.fn(),
|
||||
get config() {
|
||||
return this;
|
||||
},
|
||||
} as unknown as Config;
|
||||
|
||||
await statsCommand.action(mockContext, '');
|
||||
|
||||
@@ -29,8 +29,8 @@ function getUserIdentity(context: CommandContext) {
|
||||
const cachedAccount = userAccountManager.getCachedGoogleAccount();
|
||||
const userEmail = cachedAccount ?? undefined;
|
||||
|
||||
const tier = context.services.config?.getUserTierName();
|
||||
const paidTier = context.services.config?.getUserPaidTier();
|
||||
const tier = context.services.agentContext?.config.getUserTierName();
|
||||
const paidTier = context.services.agentContext?.config.getUserPaidTier();
|
||||
const creditBalance = getG1CreditBalance(paidTier) ?? undefined;
|
||||
|
||||
return { selectedAuthType, userEmail, tier, creditBalance };
|
||||
@@ -50,7 +50,7 @@ async function defaultSessionView(context: CommandContext) {
|
||||
|
||||
const { selectedAuthType, userEmail, tier, creditBalance } =
|
||||
getUserIdentity(context);
|
||||
const currentModel = context.services.config?.getModel();
|
||||
const currentModel = context.services.agentContext?.config.getModel();
|
||||
|
||||
const statsItem: HistoryItemStats = {
|
||||
type: MessageType.STATS,
|
||||
@@ -62,16 +62,19 @@ async function defaultSessionView(context: CommandContext) {
|
||||
creditBalance,
|
||||
};
|
||||
|
||||
if (context.services.config) {
|
||||
if (context.services.agentContext?.config) {
|
||||
const [quota] = await Promise.all([
|
||||
context.services.config.refreshUserQuota(),
|
||||
context.services.config.refreshAvailableCredits(),
|
||||
context.services.agentContext.config.refreshUserQuota(),
|
||||
context.services.agentContext.config.refreshAvailableCredits(),
|
||||
]);
|
||||
if (quota) {
|
||||
statsItem.quotas = quota;
|
||||
statsItem.pooledRemaining = context.services.config.getQuotaRemaining();
|
||||
statsItem.pooledLimit = context.services.config.getQuotaLimit();
|
||||
statsItem.pooledResetTime = context.services.config.getQuotaResetTime();
|
||||
statsItem.pooledRemaining =
|
||||
context.services.agentContext.config.getQuotaRemaining();
|
||||
statsItem.pooledLimit =
|
||||
context.services.agentContext.config.getQuotaLimit();
|
||||
statsItem.pooledResetTime =
|
||||
context.services.agentContext.config.getQuotaResetTime();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,10 +110,13 @@ export const statsCommand: SlashCommand = {
|
||||
isSafeConcurrent: true,
|
||||
action: (context: CommandContext) => {
|
||||
const { selectedAuthType, userEmail, tier } = getUserIdentity(context);
|
||||
const currentModel = context.services.config?.getModel();
|
||||
const pooledRemaining = context.services.config?.getQuotaRemaining();
|
||||
const pooledLimit = context.services.config?.getQuotaLimit();
|
||||
const pooledResetTime = context.services.config?.getQuotaResetTime();
|
||||
const currentModel = context.services.agentContext?.config.getModel();
|
||||
const pooledRemaining =
|
||||
context.services.agentContext?.config.getQuotaRemaining();
|
||||
const pooledLimit =
|
||||
context.services.agentContext?.config.getQuotaLimit();
|
||||
const pooledResetTime =
|
||||
context.services.agentContext?.config.getQuotaResetTime();
|
||||
context.ui.addItem({
|
||||
type: MessageType.MODEL_STATS,
|
||||
selectedAuthType,
|
||||
|
||||
@@ -30,8 +30,8 @@ describe('toolsCommand', () => {
|
||||
it('should display an error if the tool registry is unavailable', async () => {
|
||||
const mockContext = createMockCommandContext({
|
||||
services: {
|
||||
config: {
|
||||
getToolRegistry: () => undefined,
|
||||
agentContext: {
|
||||
toolRegistry: undefined,
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -48,10 +48,10 @@ describe('toolsCommand', () => {
|
||||
it('should display "No tools available" when none are found', async () => {
|
||||
const mockContext = createMockCommandContext({
|
||||
services: {
|
||||
config: {
|
||||
getToolRegistry: () => ({
|
||||
agentContext: {
|
||||
toolRegistry: {
|
||||
getAllTools: () => [] as Array<ToolBuilder<object, ToolResult>>,
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -69,8 +69,8 @@ describe('toolsCommand', () => {
|
||||
it('should list tools without descriptions by default (no args)', async () => {
|
||||
const mockContext = createMockCommandContext({
|
||||
services: {
|
||||
config: {
|
||||
getToolRegistry: () => ({ getAllTools: () => mockTools }),
|
||||
agentContext: {
|
||||
toolRegistry: { getAllTools: () => mockTools },
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -90,8 +90,8 @@ describe('toolsCommand', () => {
|
||||
it('should list tools without descriptions when "list" arg is passed', async () => {
|
||||
const mockContext = createMockCommandContext({
|
||||
services: {
|
||||
config: {
|
||||
getToolRegistry: () => ({ getAllTools: () => mockTools }),
|
||||
agentContext: {
|
||||
toolRegistry: { getAllTools: () => mockTools },
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -111,8 +111,8 @@ describe('toolsCommand', () => {
|
||||
it('should list tools with descriptions when "desc" arg is passed', async () => {
|
||||
const mockContext = createMockCommandContext({
|
||||
services: {
|
||||
config: {
|
||||
getToolRegistry: () => ({ getAllTools: () => mockTools }),
|
||||
agentContext: {
|
||||
toolRegistry: { getAllTools: () => mockTools },
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -144,8 +144,8 @@ describe('toolsCommand', () => {
|
||||
it('subcommand "list" should display tools without descriptions', async () => {
|
||||
const mockContext = createMockCommandContext({
|
||||
services: {
|
||||
config: {
|
||||
getToolRegistry: () => ({ getAllTools: () => mockTools }),
|
||||
agentContext: {
|
||||
toolRegistry: { getAllTools: () => mockTools },
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -165,8 +165,8 @@ describe('toolsCommand', () => {
|
||||
it('subcommand "desc" should display tools with descriptions', async () => {
|
||||
const mockContext = createMockCommandContext({
|
||||
services: {
|
||||
config: {
|
||||
getToolRegistry: () => ({ getAllTools: () => mockTools }),
|
||||
agentContext: {
|
||||
toolRegistry: { getAllTools: () => mockTools },
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -196,8 +196,8 @@ describe('toolsCommand', () => {
|
||||
|
||||
const mockContext = createMockCommandContext({
|
||||
services: {
|
||||
config: {
|
||||
getToolRegistry: () => ({ getAllTools: () => mockTools }),
|
||||
agentContext: {
|
||||
toolRegistry: { getAllTools: () => mockTools },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -15,7 +15,7 @@ async function listTools(
|
||||
context: CommandContext,
|
||||
showDescriptions: boolean,
|
||||
): Promise<void> {
|
||||
const toolRegistry = context.services.config?.getToolRegistry();
|
||||
const toolRegistry = context.services.agentContext?.toolRegistry;
|
||||
if (!toolRegistry) {
|
||||
context.ui.addItem({
|
||||
type: MessageType.ERROR,
|
||||
|
||||
@@ -11,11 +11,11 @@ import type {
|
||||
ConfirmationRequest,
|
||||
} from '../types.js';
|
||||
import type {
|
||||
Config,
|
||||
GitService,
|
||||
Logger,
|
||||
CommandActionReturn,
|
||||
AgentDefinition,
|
||||
AgentLoopContext,
|
||||
} from '@google/gemini-cli-core';
|
||||
import type { LoadedSettings } from '../../config/settings.js';
|
||||
import type { UseHistoryManagerReturn } from '../hooks/useHistoryManager.js';
|
||||
@@ -39,7 +39,7 @@ export interface CommandContext {
|
||||
// Core services and configuration
|
||||
services: {
|
||||
// TODO(abhipatel12): Ensure that config is never null.
|
||||
config: Config | null;
|
||||
agentContext: AgentLoopContext | null;
|
||||
settings: LoadedSettings;
|
||||
git: GitService | undefined;
|
||||
logger: Logger;
|
||||
|
||||
@@ -33,11 +33,13 @@ describe('upgradeCommand', () => {
|
||||
vi.clearAllMocks();
|
||||
mockContext = createMockCommandContext({
|
||||
services: {
|
||||
config: {
|
||||
getContentGeneratorConfig: vi.fn().mockReturnValue({
|
||||
authType: AuthType.LOGIN_WITH_GOOGLE,
|
||||
}),
|
||||
getUserTierName: vi.fn().mockReturnValue(undefined),
|
||||
agentContext: {
|
||||
config: {
|
||||
getContentGeneratorConfig: vi.fn().mockReturnValue({
|
||||
authType: AuthType.LOGIN_WITH_GOOGLE,
|
||||
}),
|
||||
getUserTierName: vi.fn().mockReturnValue(undefined),
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as CommandContext);
|
||||
@@ -62,7 +64,7 @@ describe('upgradeCommand', () => {
|
||||
|
||||
it('should return an error message when NOT logged in with Google', async () => {
|
||||
vi.mocked(
|
||||
mockContext.services.config!.getContentGeneratorConfig,
|
||||
mockContext.services.agentContext!.config.getContentGeneratorConfig,
|
||||
).mockReturnValue({
|
||||
authType: AuthType.USE_GEMINI,
|
||||
});
|
||||
@@ -118,9 +120,9 @@ describe('upgradeCommand', () => {
|
||||
});
|
||||
|
||||
it('should return info message for ultra tiers', async () => {
|
||||
vi.mocked(mockContext.services.config!.getUserTierName).mockReturnValue(
|
||||
'Advanced Ultra',
|
||||
);
|
||||
vi.mocked(
|
||||
mockContext.services.agentContext!.config.getUserTierName,
|
||||
).mockReturnValue('Advanced Ultra');
|
||||
|
||||
if (!upgradeCommand.action) {
|
||||
throw new Error('The upgrade command must have an action.');
|
||||
|
||||
@@ -23,8 +23,8 @@ export const upgradeCommand: SlashCommand = {
|
||||
description: 'Upgrade your Gemini Code Assist tier for higher limits',
|
||||
autoExecute: true,
|
||||
action: async (context) => {
|
||||
const authType =
|
||||
context.services.config?.getContentGeneratorConfig()?.authType;
|
||||
const config = context.services.agentContext?.config;
|
||||
const authType = config?.getContentGeneratorConfig()?.authType;
|
||||
if (authType !== AuthType.LOGIN_WITH_GOOGLE) {
|
||||
// This command should ideally be hidden if not logged in with Google,
|
||||
// but we add a safety check here just in case.
|
||||
@@ -36,7 +36,7 @@ export const upgradeCommand: SlashCommand = {
|
||||
};
|
||||
}
|
||||
|
||||
const tierName = context.services.config?.getUserTierName();
|
||||
const tierName = config?.getUserTierName();
|
||||
if (isUltraTier(tierName)) {
|
||||
return {
|
||||
type: 'message',
|
||||
|
||||
@@ -25,7 +25,7 @@ describe('AboutBox', () => {
|
||||
};
|
||||
|
||||
it('renders with required props', async () => {
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<AboutBox {...defaultProps} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
@@ -46,7 +46,7 @@ describe('AboutBox', () => {
|
||||
['tier', 'Enterprise', 'Tier'],
|
||||
])('renders optional prop %s', async (prop, value, label) => {
|
||||
const props = { ...defaultProps, [prop]: value };
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<AboutBox {...props} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
@@ -58,7 +58,7 @@ describe('AboutBox', () => {
|
||||
|
||||
it('renders Auth Method with email when userEmail is provided', async () => {
|
||||
const props = { ...defaultProps, userEmail: 'test@example.com' };
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<AboutBox {...props} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
@@ -69,7 +69,7 @@ describe('AboutBox', () => {
|
||||
|
||||
it('renders Auth Method correctly when not oauth', async () => {
|
||||
const props = { ...defaultProps, selectedAuthType: 'api-key' };
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<AboutBox {...props} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
|
||||
@@ -17,7 +17,7 @@ describe('AdminSettingsChangedDialog', () => {
|
||||
});
|
||||
|
||||
it('renders correctly', async () => {
|
||||
const { lastFrame, waitUntilReady } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady } = await renderWithProviders(
|
||||
<AdminSettingsChangedDialog />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
@@ -25,7 +25,7 @@ describe('AdminSettingsChangedDialog', () => {
|
||||
});
|
||||
|
||||
it('restarts on "r" key press', async () => {
|
||||
const { stdin, waitUntilReady } = renderWithProviders(
|
||||
const { stdin, waitUntilReady } = await renderWithProviders(
|
||||
<AdminSettingsChangedDialog />,
|
||||
{
|
||||
uiActions: {
|
||||
@@ -43,7 +43,7 @@ describe('AdminSettingsChangedDialog', () => {
|
||||
});
|
||||
|
||||
it.each(['r', 'R'])('restarts on "%s" key press', async (key) => {
|
||||
const { stdin, waitUntilReady } = renderWithProviders(
|
||||
const { stdin, waitUntilReady } = await renderWithProviders(
|
||||
<AdminSettingsChangedDialog />,
|
||||
{
|
||||
uiActions: {
|
||||
|
||||
@@ -115,7 +115,7 @@ describe('AgentConfigDialog', () => {
|
||||
settings: LoadedSettings,
|
||||
definition: AgentDefinition = createMockAgentDefinition(),
|
||||
) => {
|
||||
const result = renderWithProviders(
|
||||
const result = await renderWithProviders(
|
||||
<AgentConfigDialog
|
||||
agentName="test-agent"
|
||||
displayName="Test Agent"
|
||||
@@ -323,7 +323,7 @@ describe('AgentConfigDialog', () => {
|
||||
const settings = createMockSettings();
|
||||
// Agent config has about 6 base items + 2 per tool
|
||||
// Render with very small height (20)
|
||||
const { lastFrame, unmount } = renderWithProviders(
|
||||
const { lastFrame, unmount } = await renderWithProviders(
|
||||
<AgentConfigDialog
|
||||
agentName="test-agent"
|
||||
displayName="Test Agent"
|
||||
|
||||
@@ -108,7 +108,7 @@ describe('AlternateBufferQuittingDisplay', () => {
|
||||
|
||||
it('renders with active and pending tool messages', async () => {
|
||||
persistentStateMock.setData({ tipsShown: 0 });
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<AlternateBufferQuittingDisplay />,
|
||||
{
|
||||
uiState: {
|
||||
@@ -125,7 +125,7 @@ describe('AlternateBufferQuittingDisplay', () => {
|
||||
|
||||
it('renders with empty history and no pending items', async () => {
|
||||
persistentStateMock.setData({ tipsShown: 0 });
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<AlternateBufferQuittingDisplay />,
|
||||
{
|
||||
uiState: {
|
||||
@@ -142,7 +142,7 @@ describe('AlternateBufferQuittingDisplay', () => {
|
||||
|
||||
it('renders with history but no pending items', async () => {
|
||||
persistentStateMock.setData({ tipsShown: 0 });
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<AlternateBufferQuittingDisplay />,
|
||||
{
|
||||
uiState: {
|
||||
@@ -159,7 +159,7 @@ describe('AlternateBufferQuittingDisplay', () => {
|
||||
|
||||
it('renders with pending items but no history', async () => {
|
||||
persistentStateMock.setData({ tipsShown: 0 });
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<AlternateBufferQuittingDisplay />,
|
||||
{
|
||||
uiState: {
|
||||
@@ -195,7 +195,7 @@ describe('AlternateBufferQuittingDisplay', () => {
|
||||
],
|
||||
},
|
||||
];
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<AlternateBufferQuittingDisplay />,
|
||||
{
|
||||
uiState: {
|
||||
@@ -220,7 +220,7 @@ describe('AlternateBufferQuittingDisplay', () => {
|
||||
{ id: 1, type: 'user', text: 'Hello Gemini' },
|
||||
{ id: 2, type: 'gemini', text: 'Hello User!' },
|
||||
];
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<AlternateBufferQuittingDisplay />,
|
||||
{
|
||||
uiState: {
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
} from '../../test-utils/render.js';
|
||||
import { AppHeader } from './AppHeader.js';
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { makeFakeConfig } from '@google/gemini-cli-core';
|
||||
import crypto from 'node:crypto';
|
||||
|
||||
vi.mock('../utils/terminalSetup.js', () => ({
|
||||
@@ -19,7 +18,6 @@ vi.mock('../utils/terminalSetup.js', () => ({
|
||||
|
||||
describe('<AppHeader />', () => {
|
||||
it('should render the banner with default text', async () => {
|
||||
const mockConfig = makeFakeConfig();
|
||||
const uiState = {
|
||||
history: [],
|
||||
bannerData: {
|
||||
@@ -29,10 +27,9 @@ describe('<AppHeader />', () => {
|
||||
bannerVisible: true,
|
||||
};
|
||||
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<AppHeader version="1.0.0" />,
|
||||
{
|
||||
config: mockConfig,
|
||||
uiState,
|
||||
},
|
||||
);
|
||||
@@ -44,7 +41,6 @@ describe('<AppHeader />', () => {
|
||||
});
|
||||
|
||||
it('should render the banner with warning text', async () => {
|
||||
const mockConfig = makeFakeConfig();
|
||||
const uiState = {
|
||||
history: [],
|
||||
bannerData: {
|
||||
@@ -54,10 +50,9 @@ describe('<AppHeader />', () => {
|
||||
bannerVisible: true,
|
||||
};
|
||||
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<AppHeader version="1.0.0" />,
|
||||
{
|
||||
config: mockConfig,
|
||||
uiState,
|
||||
},
|
||||
);
|
||||
@@ -69,7 +64,6 @@ describe('<AppHeader />', () => {
|
||||
});
|
||||
|
||||
it('should not render the banner when no flags are set', async () => {
|
||||
const mockConfig = makeFakeConfig();
|
||||
const uiState = {
|
||||
history: [],
|
||||
bannerData: {
|
||||
@@ -78,10 +72,9 @@ describe('<AppHeader />', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<AppHeader version="1.0.0" />,
|
||||
{
|
||||
config: mockConfig,
|
||||
uiState,
|
||||
},
|
||||
);
|
||||
@@ -93,7 +86,6 @@ describe('<AppHeader />', () => {
|
||||
});
|
||||
|
||||
it('should not render the default banner if shown count is 5 or more', async () => {
|
||||
const mockConfig = makeFakeConfig();
|
||||
const uiState = {
|
||||
history: [],
|
||||
bannerData: {
|
||||
@@ -111,10 +103,9 @@ describe('<AppHeader />', () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<AppHeader version="1.0.0" />,
|
||||
{
|
||||
config: mockConfig,
|
||||
uiState,
|
||||
},
|
||||
);
|
||||
@@ -126,7 +117,6 @@ describe('<AppHeader />', () => {
|
||||
});
|
||||
|
||||
it('should increment the version count when default banner is displayed', async () => {
|
||||
const mockConfig = makeFakeConfig();
|
||||
const uiState = {
|
||||
history: [],
|
||||
bannerData: {
|
||||
@@ -139,10 +129,9 @@ describe('<AppHeader />', () => {
|
||||
// and interfering with the expected persistentState.set call.
|
||||
persistentStateMock.setData({ tipsShown: 10 });
|
||||
|
||||
const { waitUntilReady, unmount } = renderWithProviders(
|
||||
const { waitUntilReady, unmount } = await renderWithProviders(
|
||||
<AppHeader version="1.0.0" />,
|
||||
{
|
||||
config: mockConfig,
|
||||
uiState,
|
||||
},
|
||||
);
|
||||
@@ -161,7 +150,6 @@ describe('<AppHeader />', () => {
|
||||
});
|
||||
|
||||
it('should render banner text with unescaped newlines', async () => {
|
||||
const mockConfig = makeFakeConfig();
|
||||
const uiState = {
|
||||
history: [],
|
||||
bannerData: {
|
||||
@@ -171,10 +159,9 @@ describe('<AppHeader />', () => {
|
||||
bannerVisible: true,
|
||||
};
|
||||
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<AppHeader version="1.0.0" />,
|
||||
{
|
||||
config: mockConfig,
|
||||
uiState,
|
||||
},
|
||||
);
|
||||
@@ -185,7 +172,6 @@ describe('<AppHeader />', () => {
|
||||
});
|
||||
|
||||
it('should render Tips when tipsShown is less than 10', async () => {
|
||||
const mockConfig = makeFakeConfig();
|
||||
const uiState = {
|
||||
history: [],
|
||||
bannerData: {
|
||||
@@ -197,10 +183,9 @@ describe('<AppHeader />', () => {
|
||||
|
||||
persistentStateMock.setData({ tipsShown: 5 });
|
||||
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<AppHeader version="1.0.0" />,
|
||||
{
|
||||
config: mockConfig,
|
||||
uiState,
|
||||
},
|
||||
);
|
||||
@@ -212,7 +197,6 @@ describe('<AppHeader />', () => {
|
||||
});
|
||||
|
||||
it('should NOT render Tips when tipsShown is 10 or more', async () => {
|
||||
const mockConfig = makeFakeConfig();
|
||||
const uiState = {
|
||||
bannerData: {
|
||||
defaultText: '',
|
||||
@@ -222,10 +206,9 @@ describe('<AppHeader />', () => {
|
||||
|
||||
persistentStateMock.setData({ tipsShown: 10 });
|
||||
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<AppHeader version="1.0.0" />,
|
||||
{
|
||||
config: mockConfig,
|
||||
uiState,
|
||||
},
|
||||
);
|
||||
@@ -238,7 +221,6 @@ describe('<AppHeader />', () => {
|
||||
it('should show tips until they have been shown 10 times (persistence flow)', async () => {
|
||||
persistentStateMock.setData({ tipsShown: 9 });
|
||||
|
||||
const mockConfig = makeFakeConfig();
|
||||
const uiState = {
|
||||
history: [],
|
||||
bannerData: {
|
||||
@@ -249,8 +231,7 @@ describe('<AppHeader />', () => {
|
||||
};
|
||||
|
||||
// First session
|
||||
const session1 = renderWithProviders(<AppHeader version="1.0.0" />, {
|
||||
config: mockConfig,
|
||||
const session1 = await renderWithProviders(<AppHeader version="1.0.0" />, {
|
||||
uiState,
|
||||
});
|
||||
await session1.waitUntilReady();
|
||||
@@ -260,9 +241,10 @@ describe('<AppHeader />', () => {
|
||||
session1.unmount();
|
||||
|
||||
// Second session - state is persisted in the fake
|
||||
const session2 = renderWithProviders(<AppHeader version="1.0.0" />, {
|
||||
config: mockConfig,
|
||||
});
|
||||
const session2 = await renderWithProviders(
|
||||
<AppHeader version="1.0.0" />,
|
||||
{},
|
||||
);
|
||||
await session2.waitUntilReady();
|
||||
|
||||
expect(session2.lastFrame()).not.toContain('Tips');
|
||||
|
||||
@@ -32,7 +32,7 @@ describe('AppHeader Icon Rendering', () => {
|
||||
it('renders the default icon in standard terminals', async () => {
|
||||
vi.mocked(isAppleTerminal).mockReturnValue(false);
|
||||
|
||||
const result = renderWithProviders(<AppHeader version="1.0.0" />);
|
||||
const result = await renderWithProviders(<AppHeader version="1.0.0" />);
|
||||
await result.waitUntilReady();
|
||||
|
||||
await expect(result).toMatchSvgSnapshot();
|
||||
@@ -41,7 +41,7 @@ describe('AppHeader Icon Rendering', () => {
|
||||
it('renders the symmetric icon in Apple Terminal', async () => {
|
||||
vi.mocked(isAppleTerminal).mockReturnValue(true);
|
||||
|
||||
const result = renderWithProviders(<AppHeader version="1.0.0" />);
|
||||
const result = await renderWithProviders(<AppHeader version="1.0.0" />);
|
||||
await result.waitUntilReady();
|
||||
|
||||
await expect(result).toMatchSvgSnapshot();
|
||||
|
||||
@@ -48,7 +48,7 @@ describe('AskUserDialog', () => {
|
||||
];
|
||||
|
||||
it('renders question and options', async () => {
|
||||
const { lastFrame, waitUntilReady } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady } = await renderWithProviders(
|
||||
<AskUserDialog
|
||||
questions={authQuestion}
|
||||
onSubmit={vi.fn()}
|
||||
@@ -140,7 +140,7 @@ describe('AskUserDialog', () => {
|
||||
])('Submission: $name', ({ name, questions, actions, expectedSubmit }) => {
|
||||
it(`submits correct values for ${name}`, async () => {
|
||||
const onSubmit = vi.fn();
|
||||
const { stdin } = renderWithProviders(
|
||||
const { stdin } = await renderWithProviders(
|
||||
<AskUserDialog
|
||||
questions={questions}
|
||||
onSubmit={onSubmit}
|
||||
@@ -172,7 +172,7 @@ describe('AskUserDialog', () => {
|
||||
},
|
||||
] as Question[];
|
||||
|
||||
const { stdin, lastFrame, waitUntilReady } = renderWithProviders(
|
||||
const { stdin, lastFrame, waitUntilReady } = await renderWithProviders(
|
||||
<AskUserDialog
|
||||
questions={questions}
|
||||
onSubmit={vi.fn()}
|
||||
@@ -196,7 +196,7 @@ describe('AskUserDialog', () => {
|
||||
|
||||
it('handles custom option in single select with inline typing', async () => {
|
||||
const onSubmit = vi.fn();
|
||||
const { stdin, lastFrame, waitUntilReady } = renderWithProviders(
|
||||
const { stdin, lastFrame, waitUntilReady } = await renderWithProviders(
|
||||
<AskUserDialog
|
||||
questions={authQuestion}
|
||||
onSubmit={onSubmit}
|
||||
@@ -245,7 +245,7 @@ describe('AskUserDialog', () => {
|
||||
];
|
||||
|
||||
const onSubmit = vi.fn();
|
||||
const { stdin, lastFrame, waitUntilReady } = renderWithProviders(
|
||||
const { stdin, lastFrame, waitUntilReady } = await renderWithProviders(
|
||||
<AskUserDialog
|
||||
questions={authQuestionWithOther}
|
||||
onSubmit={onSubmit}
|
||||
@@ -307,7 +307,7 @@ describe('AskUserDialog', () => {
|
||||
},
|
||||
];
|
||||
|
||||
const { lastFrame, waitUntilReady } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady } = await renderWithProviders(
|
||||
<AskUserDialog
|
||||
questions={questions}
|
||||
onSubmit={vi.fn()}
|
||||
@@ -341,7 +341,7 @@ describe('AskUserDialog', () => {
|
||||
);
|
||||
|
||||
it('navigates to custom option when typing unbound characters (Type-to-Jump)', async () => {
|
||||
const { stdin, lastFrame, waitUntilReady } = renderWithProviders(
|
||||
const { stdin, lastFrame, waitUntilReady } = await renderWithProviders(
|
||||
<AskUserDialog
|
||||
questions={authQuestion}
|
||||
onSubmit={vi.fn()}
|
||||
@@ -397,7 +397,7 @@ describe('AskUserDialog', () => {
|
||||
},
|
||||
];
|
||||
|
||||
const { lastFrame, waitUntilReady } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady } = await renderWithProviders(
|
||||
<AskUserDialog
|
||||
questions={multiQuestions}
|
||||
onSubmit={vi.fn()}
|
||||
@@ -412,7 +412,7 @@ describe('AskUserDialog', () => {
|
||||
});
|
||||
|
||||
it('hides progress header for single question', async () => {
|
||||
const { lastFrame, waitUntilReady } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady } = await renderWithProviders(
|
||||
<AskUserDialog
|
||||
questions={authQuestion}
|
||||
onSubmit={vi.fn()}
|
||||
@@ -427,7 +427,7 @@ describe('AskUserDialog', () => {
|
||||
});
|
||||
|
||||
it('shows keyboard hints', async () => {
|
||||
const { lastFrame, waitUntilReady } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady } = await renderWithProviders(
|
||||
<AskUserDialog
|
||||
questions={authQuestion}
|
||||
onSubmit={vi.fn()}
|
||||
@@ -461,7 +461,7 @@ describe('AskUserDialog', () => {
|
||||
},
|
||||
];
|
||||
|
||||
const { stdin, lastFrame, waitUntilReady } = renderWithProviders(
|
||||
const { stdin, lastFrame, waitUntilReady } = await renderWithProviders(
|
||||
<AskUserDialog
|
||||
questions={multiQuestions}
|
||||
onSubmit={vi.fn()}
|
||||
@@ -508,7 +508,7 @@ describe('AskUserDialog', () => {
|
||||
];
|
||||
|
||||
const onSubmit = vi.fn();
|
||||
const { stdin, lastFrame, waitUntilReady } = renderWithProviders(
|
||||
const { stdin, lastFrame, waitUntilReady } = await renderWithProviders(
|
||||
<AskUserDialog
|
||||
questions={multiQuestions}
|
||||
onSubmit={onSubmit}
|
||||
@@ -582,7 +582,7 @@ describe('AskUserDialog', () => {
|
||||
},
|
||||
];
|
||||
|
||||
const { lastFrame, waitUntilReady } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady } = await renderWithProviders(
|
||||
<AskUserDialog
|
||||
questions={multiQuestions}
|
||||
onSubmit={vi.fn()}
|
||||
@@ -614,7 +614,7 @@ describe('AskUserDialog', () => {
|
||||
},
|
||||
];
|
||||
|
||||
const { stdin, lastFrame, waitUntilReady } = renderWithProviders(
|
||||
const { stdin, lastFrame, waitUntilReady } = await renderWithProviders(
|
||||
<AskUserDialog
|
||||
questions={multiQuestions}
|
||||
onSubmit={vi.fn()}
|
||||
@@ -664,7 +664,7 @@ describe('AskUserDialog', () => {
|
||||
},
|
||||
];
|
||||
|
||||
const { stdin, lastFrame, waitUntilReady } = renderWithProviders(
|
||||
const { stdin, lastFrame, waitUntilReady } = await renderWithProviders(
|
||||
<AskUserDialog
|
||||
questions={multiQuestions}
|
||||
onSubmit={vi.fn()}
|
||||
@@ -703,7 +703,7 @@ describe('AskUserDialog', () => {
|
||||
];
|
||||
|
||||
const onSubmit = vi.fn();
|
||||
const { stdin } = renderWithProviders(
|
||||
const { stdin } = await renderWithProviders(
|
||||
<AskUserDialog
|
||||
questions={multiQuestions}
|
||||
onSubmit={onSubmit}
|
||||
@@ -736,7 +736,7 @@ describe('AskUserDialog', () => {
|
||||
},
|
||||
];
|
||||
|
||||
const { lastFrame, waitUntilReady } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady } = await renderWithProviders(
|
||||
<AskUserDialog
|
||||
questions={textQuestion}
|
||||
onSubmit={vi.fn()}
|
||||
@@ -759,7 +759,7 @@ describe('AskUserDialog', () => {
|
||||
},
|
||||
];
|
||||
|
||||
const { lastFrame, waitUntilReady } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady } = await renderWithProviders(
|
||||
<AskUserDialog
|
||||
questions={textQuestion}
|
||||
onSubmit={vi.fn()}
|
||||
@@ -782,7 +782,7 @@ describe('AskUserDialog', () => {
|
||||
},
|
||||
];
|
||||
|
||||
const { stdin, lastFrame, waitUntilReady } = renderWithProviders(
|
||||
const { stdin, lastFrame, waitUntilReady } = await renderWithProviders(
|
||||
<AskUserDialog
|
||||
questions={textQuestion}
|
||||
onSubmit={vi.fn()}
|
||||
@@ -820,7 +820,7 @@ describe('AskUserDialog', () => {
|
||||
},
|
||||
];
|
||||
|
||||
const { lastFrame, waitUntilReady } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady } = await renderWithProviders(
|
||||
<AskUserDialog
|
||||
questions={textQuestion}
|
||||
onSubmit={vi.fn()}
|
||||
@@ -853,7 +853,7 @@ describe('AskUserDialog', () => {
|
||||
},
|
||||
];
|
||||
|
||||
const { stdin, lastFrame, waitUntilReady } = renderWithProviders(
|
||||
const { stdin, lastFrame, waitUntilReady } = await renderWithProviders(
|
||||
<AskUserDialog
|
||||
questions={mixedQuestions}
|
||||
onSubmit={vi.fn()}
|
||||
@@ -903,7 +903,7 @@ describe('AskUserDialog', () => {
|
||||
];
|
||||
|
||||
const onSubmit = vi.fn();
|
||||
const { stdin, lastFrame, waitUntilReady } = renderWithProviders(
|
||||
const { stdin, lastFrame, waitUntilReady } = await renderWithProviders(
|
||||
<AskUserDialog
|
||||
questions={mixedQuestions}
|
||||
onSubmit={onSubmit}
|
||||
@@ -959,7 +959,7 @@ describe('AskUserDialog', () => {
|
||||
];
|
||||
|
||||
const onSubmit = vi.fn();
|
||||
const { stdin } = renderWithProviders(
|
||||
const { stdin } = await renderWithProviders(
|
||||
<AskUserDialog
|
||||
questions={textQuestion}
|
||||
onSubmit={onSubmit}
|
||||
@@ -986,7 +986,7 @@ describe('AskUserDialog', () => {
|
||||
];
|
||||
|
||||
const onCancel = vi.fn();
|
||||
const { stdin, lastFrame, waitUntilReady } = renderWithProviders(
|
||||
const { stdin, lastFrame, waitUntilReady } = await renderWithProviders(
|
||||
<AskUserDialog
|
||||
questions={textQuestion}
|
||||
onSubmit={vi.fn()}
|
||||
@@ -1036,7 +1036,7 @@ describe('AskUserDialog', () => {
|
||||
},
|
||||
];
|
||||
|
||||
const { stdin, lastFrame, waitUntilReady } = renderWithProviders(
|
||||
const { stdin, lastFrame, waitUntilReady } = await renderWithProviders(
|
||||
<AskUserDialog
|
||||
questions={multiQuestions}
|
||||
onSubmit={vi.fn()}
|
||||
@@ -1102,7 +1102,7 @@ describe('AskUserDialog', () => {
|
||||
];
|
||||
|
||||
const onSubmit = vi.fn();
|
||||
const { stdin, lastFrame, waitUntilReady } = renderWithProviders(
|
||||
const { stdin, lastFrame, waitUntilReady } = await renderWithProviders(
|
||||
<AskUserDialog
|
||||
questions={multiQuestions}
|
||||
onSubmit={onSubmit}
|
||||
@@ -1154,7 +1154,7 @@ describe('AskUserDialog', () => {
|
||||
},
|
||||
];
|
||||
|
||||
const { lastFrame, waitUntilReady } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady } = await renderWithProviders(
|
||||
<AskUserDialog
|
||||
questions={questions}
|
||||
onSubmit={vi.fn()}
|
||||
@@ -1184,7 +1184,7 @@ describe('AskUserDialog', () => {
|
||||
},
|
||||
];
|
||||
|
||||
const { lastFrame, waitUntilReady } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady } = await renderWithProviders(
|
||||
<AskUserDialog
|
||||
questions={questions}
|
||||
onSubmit={vi.fn()}
|
||||
@@ -1217,7 +1217,7 @@ describe('AskUserDialog', () => {
|
||||
},
|
||||
];
|
||||
|
||||
const { lastFrame, waitUntilReady } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady } = await renderWithProviders(
|
||||
<AskUserDialog
|
||||
questions={questions}
|
||||
onSubmit={vi.fn()}
|
||||
@@ -1248,7 +1248,7 @@ describe('AskUserDialog', () => {
|
||||
},
|
||||
];
|
||||
|
||||
const { lastFrame, waitUntilReady } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady } = await renderWithProviders(
|
||||
<AskUserDialog
|
||||
questions={questions}
|
||||
onSubmit={vi.fn()}
|
||||
@@ -1287,7 +1287,7 @@ describe('AskUserDialog', () => {
|
||||
availableTerminalHeight: 5, // Small height to force scroll arrows
|
||||
} as UIState;
|
||||
|
||||
const { lastFrame, waitUntilReady } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady } = await renderWithProviders(
|
||||
<UIStateContext.Provider value={mockUIState}>
|
||||
<AskUserDialog
|
||||
questions={questions}
|
||||
@@ -1326,7 +1326,7 @@ describe('AskUserDialog', () => {
|
||||
availableTerminalHeight: 5,
|
||||
} as UIState;
|
||||
|
||||
const { lastFrame, waitUntilReady } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady } = await renderWithProviders(
|
||||
<UIStateContext.Provider value={mockUIState}>
|
||||
<AskUserDialog
|
||||
questions={questions}
|
||||
@@ -1365,7 +1365,7 @@ describe('AskUserDialog', () => {
|
||||
},
|
||||
];
|
||||
|
||||
const { stdin, lastFrame, waitUntilReady } = renderWithProviders(
|
||||
const { stdin, lastFrame, waitUntilReady } = await renderWithProviders(
|
||||
<AskUserDialog
|
||||
questions={questions}
|
||||
onSubmit={vi.fn()}
|
||||
@@ -1399,7 +1399,7 @@ describe('AskUserDialog', () => {
|
||||
},
|
||||
];
|
||||
|
||||
const { stdin, lastFrame, waitUntilReady } = renderWithProviders(
|
||||
const { stdin, lastFrame, waitUntilReady } = await renderWithProviders(
|
||||
<AskUserDialog
|
||||
questions={questions}
|
||||
onSubmit={vi.fn()}
|
||||
@@ -1432,7 +1432,7 @@ describe('AskUserDialog', () => {
|
||||
];
|
||||
|
||||
const onSubmit = vi.fn();
|
||||
const { stdin } = renderWithProviders(
|
||||
const { stdin } = await renderWithProviders(
|
||||
<AskUserDialog
|
||||
questions={questions}
|
||||
onSubmit={onSubmit}
|
||||
|
||||
@@ -14,7 +14,7 @@ describe('Banner', () => {
|
||||
['info mode', false, 'Info Message'],
|
||||
['multi-line warning', true, 'Title Line\\nBody Line 1\\nBody Line 2'],
|
||||
])('renders in %s', async (_, isWarning, text) => {
|
||||
const renderResult = renderWithProviders(
|
||||
const renderResult = await renderWithProviders(
|
||||
<Banner bannerText={text} isWarning={isWarning} width={80} />,
|
||||
);
|
||||
await renderResult.waitUntilReady();
|
||||
@@ -24,7 +24,7 @@ describe('Banner', () => {
|
||||
|
||||
it('handles newlines in text', async () => {
|
||||
const text = 'Line 1\\nLine 2';
|
||||
const renderResult = renderWithProviders(
|
||||
const renderResult = await renderWithProviders(
|
||||
<Banner bannerText={text} isWarning={false} width={80} />,
|
||||
);
|
||||
await renderResult.waitUntilReady();
|
||||
|
||||
@@ -30,7 +30,7 @@ describe('Key Bubbling Regression', () => {
|
||||
];
|
||||
|
||||
it('does not navigate when pressing "j" or "k" in a focused text input', async () => {
|
||||
const { stdin, lastFrame } = renderWithProviders(
|
||||
const { stdin, lastFrame } = await renderWithProviders(
|
||||
<AskUserDialog
|
||||
questions={choiceQuestion}
|
||||
onSubmit={vi.fn()}
|
||||
|
||||
@@ -17,7 +17,9 @@ describe('<CliSpinner />', () => {
|
||||
|
||||
it('should increment debugNumAnimatedComponents on mount and decrement on unmount', async () => {
|
||||
expect(debugState.debugNumAnimatedComponents).toBe(0);
|
||||
const { waitUntilReady, unmount } = renderWithProviders(<CliSpinner />);
|
||||
const { waitUntilReady, unmount } = await renderWithProviders(
|
||||
<CliSpinner />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
expect(debugState.debugNumAnimatedComponents).toBe(1);
|
||||
unmount();
|
||||
@@ -26,7 +28,7 @@ describe('<CliSpinner />', () => {
|
||||
|
||||
it('should not render when showSpinner is false', async () => {
|
||||
const settings = createMockSettings({ ui: { showSpinner: false } });
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<CliSpinner />,
|
||||
{ settings },
|
||||
);
|
||||
|
||||
@@ -96,7 +96,7 @@ describe('ColorsDisplay', () => {
|
||||
|
||||
it('renders correctly', async () => {
|
||||
const mockTheme = themeManager.getActiveTheme();
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<ColorsDisplay activeTheme={mockTheme} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
|
||||
@@ -183,7 +183,6 @@ const createMockUIState = (overrides: Partial<UIState> = {}): UIState =>
|
||||
ideContextState: null,
|
||||
geminiMdFileCount: 0,
|
||||
renderMarkdown: true,
|
||||
filteredConsoleMessages: [],
|
||||
history: [],
|
||||
sessionStats: {
|
||||
sessionId: 'test-session',
|
||||
@@ -757,13 +756,6 @@ describe('Composer', () => {
|
||||
it('shows DetailedMessagesDisplay when showErrorDetails is true', async () => {
|
||||
const uiState = createMockUIState({
|
||||
showErrorDetails: true,
|
||||
filteredConsoleMessages: [
|
||||
{
|
||||
type: 'error',
|
||||
content: 'Test error',
|
||||
count: 1,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const { lastFrame } = await renderComposer(uiState);
|
||||
|
||||
@@ -422,7 +422,6 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
||||
<OverflowProvider>
|
||||
<Box flexDirection="column">
|
||||
<DetailedMessagesDisplay
|
||||
messages={uiState.filteredConsoleMessages}
|
||||
maxHeight={
|
||||
uiState.constrainHeight ? debugConsoleMaxHeight : undefined
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ describe('ConfigInitDisplay', () => {
|
||||
});
|
||||
|
||||
it('renders initial state', async () => {
|
||||
const { lastFrame, waitUntilReady } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady } = await renderWithProviders(
|
||||
<ConfigInitDisplay />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
@@ -59,7 +59,7 @@ describe('ConfigInitDisplay', () => {
|
||||
return coreEvents;
|
||||
});
|
||||
|
||||
const { lastFrame } = renderWithProviders(<ConfigInitDisplay />);
|
||||
const { lastFrame } = await renderWithProviders(<ConfigInitDisplay />);
|
||||
|
||||
// Wait for listener to be registered
|
||||
await waitFor(() => {
|
||||
@@ -97,7 +97,7 @@ describe('ConfigInitDisplay', () => {
|
||||
return coreEvents;
|
||||
});
|
||||
|
||||
const { lastFrame } = renderWithProviders(<ConfigInitDisplay />);
|
||||
const { lastFrame } = await renderWithProviders(<ConfigInitDisplay />);
|
||||
|
||||
await waitFor(() => {
|
||||
if (!listener) throw new Error('Listener not registered yet');
|
||||
@@ -133,7 +133,7 @@ describe('ConfigInitDisplay', () => {
|
||||
return coreEvents;
|
||||
});
|
||||
|
||||
const { lastFrame } = renderWithProviders(<ConfigInitDisplay />);
|
||||
const { lastFrame } = await renderWithProviders(<ConfigInitDisplay />);
|
||||
|
||||
await waitFor(() => {
|
||||
if (!listener) throw new Error('Listener not registered yet');
|
||||
|
||||
@@ -19,7 +19,7 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
|
||||
describe('ContextUsageDisplay', () => {
|
||||
it('renders correct percentage used', async () => {
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<ContextUsageDisplay
|
||||
promptTokenCount={5000}
|
||||
model="gemini-pro"
|
||||
@@ -33,7 +33,7 @@ describe('ContextUsageDisplay', () => {
|
||||
});
|
||||
|
||||
it('renders correctly when usage is 0%', async () => {
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<ContextUsageDisplay
|
||||
promptTokenCount={0}
|
||||
model="gemini-pro"
|
||||
@@ -47,7 +47,7 @@ describe('ContextUsageDisplay', () => {
|
||||
});
|
||||
|
||||
it('renders abbreviated label when terminal width is small', async () => {
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<ContextUsageDisplay
|
||||
promptTokenCount={2000}
|
||||
model="gemini-pro"
|
||||
@@ -63,7 +63,7 @@ describe('ContextUsageDisplay', () => {
|
||||
});
|
||||
|
||||
it('renders 80% correctly', async () => {
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<ContextUsageDisplay
|
||||
promptTokenCount={8000}
|
||||
model="gemini-pro"
|
||||
@@ -77,7 +77,7 @@ describe('ContextUsageDisplay', () => {
|
||||
});
|
||||
|
||||
it('renders 100% when full', async () => {
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<ContextUsageDisplay
|
||||
promptTokenCount={10000}
|
||||
model="gemini-pro"
|
||||
|
||||
@@ -6,11 +6,16 @@
|
||||
|
||||
import { renderWithProviders } from '../../test-utils/render.js';
|
||||
import { DetailedMessagesDisplay } from './DetailedMessagesDisplay.js';
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import type { ConsoleMessageItem } from '../types.js';
|
||||
import { Box } from 'ink';
|
||||
import type React from 'react';
|
||||
import { createMockSettings } from '../../test-utils/settings.js';
|
||||
import { useConsoleMessages } from '../hooks/useConsoleMessages.js';
|
||||
|
||||
vi.mock('../hooks/useConsoleMessages.js', () => ({
|
||||
useConsoleMessages: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('./shared/ScrollableList.js', () => ({
|
||||
ScrollableList: ({
|
||||
@@ -29,14 +34,15 @@ vi.mock('./shared/ScrollableList.js', () => ({
|
||||
}));
|
||||
|
||||
describe('DetailedMessagesDisplay', () => {
|
||||
beforeEach(() => {
|
||||
vi.mocked(useConsoleMessages).mockReturnValue({
|
||||
consoleMessages: [],
|
||||
clearConsoleMessages: vi.fn(),
|
||||
});
|
||||
});
|
||||
it('renders nothing when messages are empty', async () => {
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
<DetailedMessagesDisplay
|
||||
messages={[]}
|
||||
maxHeight={10}
|
||||
width={80}
|
||||
hasFocus={false}
|
||||
/>,
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<DetailedMessagesDisplay maxHeight={10} width={80} hasFocus={false} />,
|
||||
{
|
||||
settings: createMockSettings({ ui: { errorVerbosity: 'full' } }),
|
||||
},
|
||||
@@ -53,14 +59,13 @@ describe('DetailedMessagesDisplay', () => {
|
||||
{ type: 'error', content: 'Error message', count: 1 },
|
||||
{ type: 'debug', content: 'Debug message', count: 1 },
|
||||
];
|
||||
vi.mocked(useConsoleMessages).mockReturnValue({
|
||||
consoleMessages: messages,
|
||||
clearConsoleMessages: vi.fn(),
|
||||
});
|
||||
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
<DetailedMessagesDisplay
|
||||
messages={messages}
|
||||
maxHeight={20}
|
||||
width={80}
|
||||
hasFocus={true}
|
||||
/>,
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<DetailedMessagesDisplay maxHeight={20} width={80} hasFocus={true} />,
|
||||
{
|
||||
settings: createMockSettings({ ui: { errorVerbosity: 'full' } }),
|
||||
},
|
||||
@@ -76,14 +81,13 @@ describe('DetailedMessagesDisplay', () => {
|
||||
const messages: ConsoleMessageItem[] = [
|
||||
{ type: 'error', content: 'Error message', count: 1 },
|
||||
];
|
||||
vi.mocked(useConsoleMessages).mockReturnValue({
|
||||
consoleMessages: messages,
|
||||
clearConsoleMessages: vi.fn(),
|
||||
});
|
||||
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
<DetailedMessagesDisplay
|
||||
messages={messages}
|
||||
maxHeight={20}
|
||||
width={80}
|
||||
hasFocus={true}
|
||||
/>,
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<DetailedMessagesDisplay maxHeight={20} width={80} hasFocus={true} />,
|
||||
{
|
||||
settings: createMockSettings({ ui: { errorVerbosity: 'low' } }),
|
||||
},
|
||||
@@ -97,14 +101,13 @@ describe('DetailedMessagesDisplay', () => {
|
||||
const messages: ConsoleMessageItem[] = [
|
||||
{ type: 'error', content: 'Error message', count: 1 },
|
||||
];
|
||||
vi.mocked(useConsoleMessages).mockReturnValue({
|
||||
consoleMessages: messages,
|
||||
clearConsoleMessages: vi.fn(),
|
||||
});
|
||||
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
<DetailedMessagesDisplay
|
||||
messages={messages}
|
||||
maxHeight={20}
|
||||
width={80}
|
||||
hasFocus={true}
|
||||
/>,
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<DetailedMessagesDisplay maxHeight={20} width={80} hasFocus={true} />,
|
||||
{
|
||||
settings: createMockSettings({ ui: { errorVerbosity: 'full' } }),
|
||||
},
|
||||
@@ -118,14 +121,13 @@ describe('DetailedMessagesDisplay', () => {
|
||||
const messages: ConsoleMessageItem[] = [
|
||||
{ type: 'log', content: 'Repeated message', count: 5 },
|
||||
];
|
||||
vi.mocked(useConsoleMessages).mockReturnValue({
|
||||
consoleMessages: messages,
|
||||
clearConsoleMessages: vi.fn(),
|
||||
});
|
||||
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
<DetailedMessagesDisplay
|
||||
messages={messages}
|
||||
maxHeight={10}
|
||||
width={80}
|
||||
hasFocus={false}
|
||||
/>,
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<DetailedMessagesDisplay maxHeight={10} width={80} hasFocus={false} />,
|
||||
{
|
||||
settings: createMockSettings({ ui: { errorVerbosity: 'full' } }),
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import { useRef, useCallback } from 'react';
|
||||
import { useRef, useCallback, useMemo } from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import type { ConsoleMessageItem } from '../types.js';
|
||||
@@ -13,9 +13,10 @@ import {
|
||||
ScrollableList,
|
||||
type ScrollableListRef,
|
||||
} from './shared/ScrollableList.js';
|
||||
import { useConsoleMessages } from '../hooks/useConsoleMessages.js';
|
||||
import { useConfig } from '../contexts/ConfigContext.js';
|
||||
|
||||
interface DetailedMessagesDisplayProps {
|
||||
messages: ConsoleMessageItem[];
|
||||
maxHeight: number | undefined;
|
||||
width: number;
|
||||
hasFocus: boolean;
|
||||
@@ -25,9 +26,19 @@ const iconBoxWidth = 3;
|
||||
|
||||
export const DetailedMessagesDisplay: React.FC<
|
||||
DetailedMessagesDisplayProps
|
||||
> = ({ messages, maxHeight, width, hasFocus }) => {
|
||||
> = ({ maxHeight, width, hasFocus }) => {
|
||||
const scrollableListRef = useRef<ScrollableListRef<ConsoleMessageItem>>(null);
|
||||
|
||||
const { consoleMessages } = useConsoleMessages();
|
||||
const config = useConfig();
|
||||
|
||||
const messages = useMemo(() => {
|
||||
if (config.getDebugMode()) {
|
||||
return consoleMessages;
|
||||
}
|
||||
return consoleMessages.filter((msg) => msg.type !== 'debug');
|
||||
}, [consoleMessages, config]);
|
||||
|
||||
const borderAndPadding = 3;
|
||||
|
||||
const estimatedItemHeight = useCallback(
|
||||
|
||||
@@ -104,7 +104,7 @@ describe('DialogManager', () => {
|
||||
};
|
||||
|
||||
it('renders nothing by default', async () => {
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<DialogManager {...defaultProps} />,
|
||||
{ uiState: baseUiState as Partial<UIState> as UIState },
|
||||
);
|
||||
@@ -197,7 +197,7 @@ describe('DialogManager', () => {
|
||||
it.each(testCases)(
|
||||
'renders %s when state is %o',
|
||||
async (uiStateOverride, expectedComponent) => {
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<DialogManager {...defaultProps} />,
|
||||
{
|
||||
uiState: {
|
||||
|
||||
@@ -51,11 +51,11 @@ describe('EditorSettingsDialog', () => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
const renderWithProvider = (ui: React.ReactElement) =>
|
||||
const renderWithProvider = async (ui: React.ReactElement) =>
|
||||
renderWithProviders(ui);
|
||||
|
||||
it('renders correctly', async () => {
|
||||
const { lastFrame, waitUntilReady } = renderWithProvider(
|
||||
const { lastFrame, waitUntilReady } = await renderWithProvider(
|
||||
<EditorSettingsDialog
|
||||
onSelect={vi.fn()}
|
||||
settings={mockSettings}
|
||||
@@ -68,7 +68,7 @@ describe('EditorSettingsDialog', () => {
|
||||
|
||||
it('calls onSelect when an editor is selected', async () => {
|
||||
const onSelect = vi.fn();
|
||||
const { lastFrame, waitUntilReady } = renderWithProvider(
|
||||
const { lastFrame, waitUntilReady } = await renderWithProvider(
|
||||
<EditorSettingsDialog
|
||||
onSelect={onSelect}
|
||||
settings={mockSettings}
|
||||
@@ -81,7 +81,7 @@ describe('EditorSettingsDialog', () => {
|
||||
});
|
||||
|
||||
it('switches focus between editor and scope sections on Tab', async () => {
|
||||
const { lastFrame, stdin, waitUntilReady } = renderWithProvider(
|
||||
const { lastFrame, stdin, waitUntilReady } = await renderWithProvider(
|
||||
<EditorSettingsDialog
|
||||
onSelect={vi.fn()}
|
||||
settings={mockSettings}
|
||||
@@ -127,7 +127,7 @@ describe('EditorSettingsDialog', () => {
|
||||
|
||||
it('calls onExit when Escape is pressed', async () => {
|
||||
const onExit = vi.fn();
|
||||
const { stdin, waitUntilReady } = renderWithProvider(
|
||||
const { stdin, waitUntilReady } = await renderWithProvider(
|
||||
<EditorSettingsDialog
|
||||
onSelect={vi.fn()}
|
||||
settings={mockSettings}
|
||||
@@ -162,7 +162,7 @@ describe('EditorSettingsDialog', () => {
|
||||
},
|
||||
} as unknown as LoadedSettings;
|
||||
|
||||
const { lastFrame, waitUntilReady } = renderWithProvider(
|
||||
const { lastFrame, waitUntilReady } = await renderWithProvider(
|
||||
<EditorSettingsDialog
|
||||
onSelect={vi.fn()}
|
||||
settings={settingsWithOtherScope}
|
||||
|
||||
@@ -30,7 +30,7 @@ describe('EmptyWalletDialog', () => {
|
||||
|
||||
describe('rendering', () => {
|
||||
it('should match snapshot with fallback available', async () => {
|
||||
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
|
||||
const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
|
||||
<EmptyWalletDialog
|
||||
failedModel="gemini-2.5-pro"
|
||||
fallbackModel="gemini-3-flash-preview"
|
||||
@@ -45,7 +45,7 @@ describe('EmptyWalletDialog', () => {
|
||||
});
|
||||
|
||||
it('should match snapshot without fallback', async () => {
|
||||
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
|
||||
const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
|
||||
<EmptyWalletDialog
|
||||
failedModel="gemini-2.5-pro"
|
||||
onChoice={mockOnChoice}
|
||||
@@ -58,7 +58,7 @@ describe('EmptyWalletDialog', () => {
|
||||
});
|
||||
|
||||
it('should display the model name and usage limit message', async () => {
|
||||
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
|
||||
const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
|
||||
<EmptyWalletDialog
|
||||
failedModel="gemini-2.5-pro"
|
||||
onChoice={mockOnChoice}
|
||||
@@ -73,7 +73,7 @@ describe('EmptyWalletDialog', () => {
|
||||
});
|
||||
|
||||
it('should display purchase prompt and credits update notice', async () => {
|
||||
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
|
||||
const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
|
||||
<EmptyWalletDialog
|
||||
failedModel="gemini-2.5-pro"
|
||||
onChoice={mockOnChoice}
|
||||
@@ -90,7 +90,7 @@ describe('EmptyWalletDialog', () => {
|
||||
});
|
||||
|
||||
it('should display reset time when provided', async () => {
|
||||
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
|
||||
const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
|
||||
<EmptyWalletDialog
|
||||
failedModel="gemini-2.5-pro"
|
||||
resetTime="3:45 PM"
|
||||
@@ -106,7 +106,7 @@ describe('EmptyWalletDialog', () => {
|
||||
});
|
||||
|
||||
it('should not display reset time when not provided', async () => {
|
||||
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
|
||||
const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
|
||||
<EmptyWalletDialog
|
||||
failedModel="gemini-2.5-pro"
|
||||
onChoice={mockOnChoice}
|
||||
@@ -120,7 +120,7 @@ describe('EmptyWalletDialog', () => {
|
||||
});
|
||||
|
||||
it('should display slash command hints', async () => {
|
||||
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
|
||||
const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
|
||||
<EmptyWalletDialog
|
||||
failedModel="gemini-2.5-pro"
|
||||
onChoice={mockOnChoice}
|
||||
@@ -139,7 +139,7 @@ describe('EmptyWalletDialog', () => {
|
||||
describe('onChoice handling', () => {
|
||||
it('should call onGetCredits and onChoice when get_credits is selected', async () => {
|
||||
// get_credits is the first item, so just press Enter
|
||||
const { unmount, stdin, waitUntilReady } = renderWithProviders(
|
||||
const { unmount, stdin, waitUntilReady } = await renderWithProviders(
|
||||
<EmptyWalletDialog
|
||||
failedModel="gemini-2.5-pro"
|
||||
onChoice={mockOnChoice}
|
||||
@@ -158,7 +158,7 @@ describe('EmptyWalletDialog', () => {
|
||||
});
|
||||
|
||||
it('should call onChoice without onGetCredits when onGetCredits is not provided', async () => {
|
||||
const { unmount, stdin, waitUntilReady } = renderWithProviders(
|
||||
const { unmount, stdin, waitUntilReady } = await renderWithProviders(
|
||||
<EmptyWalletDialog
|
||||
failedModel="gemini-2.5-pro"
|
||||
onChoice={mockOnChoice}
|
||||
@@ -177,7 +177,7 @@ describe('EmptyWalletDialog', () => {
|
||||
it('should call onChoice with use_fallback when selected', async () => {
|
||||
// With fallback: items are [get_credits, use_fallback, stop]
|
||||
// use_fallback is the second item: Down + Enter
|
||||
const { unmount, stdin, waitUntilReady } = renderWithProviders(
|
||||
const { unmount, stdin, waitUntilReady } = await renderWithProviders(
|
||||
<EmptyWalletDialog
|
||||
failedModel="gemini-2.5-pro"
|
||||
fallbackModel="gemini-3-flash-preview"
|
||||
@@ -198,7 +198,7 @@ describe('EmptyWalletDialog', () => {
|
||||
it('should call onChoice with stop when selected', async () => {
|
||||
// Without fallback: items are [get_credits, stop]
|
||||
// stop is the second item: Down + Enter
|
||||
const { unmount, stdin, waitUntilReady } = renderWithProviders(
|
||||
const { unmount, stdin, waitUntilReady } = await renderWithProviders(
|
||||
<EmptyWalletDialog
|
||||
failedModel="gemini-2.5-pro"
|
||||
onChoice={mockOnChoice}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user