mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-15 06:12:50 -07:00
fix 1566
This commit is contained in:
@@ -178,9 +178,10 @@ describe('GeminiAgent', () => {
|
||||
isPlanEnabled: vi.fn().mockReturnValue(true),
|
||||
getGemini31LaunchedSync: vi.fn().mockReturnValue(false),
|
||||
getHasAccessToPreviewModel: vi.fn().mockReturnValue(false),
|
||||
getCheckpointingEnabled: vi.fn().mockReturnValue(false),
|
||||
getDisableAlwaysAllow: vi.fn().mockReturnValue(false),
|
||||
validatePathAccess: vi.fn().mockReturnValue(null),
|
||||
getExtensions: vi.fn().mockReturnValue([]),
|
||||
isTrustedFolder: vi.fn().mockReturnValue(true),
|
||||
getWorkspaceContext: vi.fn().mockReturnValue({
|
||||
addReadOnlyPath: vi.fn(),
|
||||
}),
|
||||
@@ -198,6 +199,9 @@ describe('GeminiAgent', () => {
|
||||
setClientName: vi.fn(),
|
||||
},
|
||||
setClientName: vi.fn(),
|
||||
getSkillManager: vi.fn().mockReturnValue({
|
||||
discoverSkills: vi.fn().mockResolvedValue(undefined),
|
||||
}),
|
||||
get config() {
|
||||
return this;
|
||||
},
|
||||
@@ -510,6 +514,27 @@ describe('GeminiAgent', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should create a new session with additional roots', async () => {
|
||||
const _meta = { additionalRoots: ['/extra/path'] };
|
||||
|
||||
await agent.newSession({
|
||||
cwd: '/tmp',
|
||||
mcpServers: [],
|
||||
_meta,
|
||||
} as acp.NewSessionRequest & { _meta?: Record<string, unknown> });
|
||||
|
||||
expect(loadCliConfig).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
context: expect.objectContaining({
|
||||
includeDirectories: expect.arrayContaining(['/extra/path']),
|
||||
}),
|
||||
}),
|
||||
'test-session-id',
|
||||
mockArgv,
|
||||
{ cwd: '/tmp' },
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle authentication failure gracefully', async () => {
|
||||
mockConfig.refreshAuth.mockRejectedValue(new Error('Auth failed'));
|
||||
const debugSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
@@ -579,6 +604,51 @@ describe('GeminiAgent', () => {
|
||||
expect(result).toMatchObject({ stopReason: 'end_turn' });
|
||||
});
|
||||
|
||||
it('should support additional roots mid-session in prompt', async () => {
|
||||
const _meta = { additionalRoots: ['/extra/path'] };
|
||||
|
||||
// Mock chat.sendMessageStream to avoid crash
|
||||
const mockChatInTest = {
|
||||
sendMessageStream: vi
|
||||
.fn()
|
||||
.mockResolvedValue(
|
||||
createMockStream([
|
||||
{
|
||||
type: StreamEventType.CHUNK,
|
||||
value: { candidates: [{ content: { parts: [{ text: 'Hi' }] } }] },
|
||||
},
|
||||
]),
|
||||
),
|
||||
};
|
||||
(
|
||||
mockConfig.getGeminiClient().startChat as unknown as import('vitest').Mock
|
||||
).mockResolvedValue(mockChatInTest);
|
||||
|
||||
await agent.newSession({ cwd: '/tmp', mcpServers: [] });
|
||||
const session = (
|
||||
agent as unknown as { sessions: Map<string, Session> }
|
||||
).sessions.get('test-session-id');
|
||||
if (!session) throw new Error('Session not found');
|
||||
|
||||
// Stub addDirectories and getDirectories on workspaceContext
|
||||
const mockWorkspaceContext = mockConfig.getWorkspaceContext();
|
||||
mockWorkspaceContext.addDirectories = vi.fn();
|
||||
mockWorkspaceContext.getDirectories = vi
|
||||
.fn()
|
||||
.mockReturnValue(['/tmp', '/extra/path']);
|
||||
|
||||
await agent.prompt({
|
||||
sessionId: 'test-session-id',
|
||||
prompt: [],
|
||||
_meta,
|
||||
} as acp.PromptRequest & { _meta?: Record<string, unknown> });
|
||||
|
||||
expect(mockWorkspaceContext.addDirectories).toHaveBeenCalledWith([
|
||||
'/extra/path',
|
||||
]);
|
||||
expect(mockConfig.getSkillManager().discoverSkills).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should delegate setMode to session', async () => {
|
||||
await agent.newSession({ cwd: '/tmp', mcpServers: [] });
|
||||
const session = (
|
||||
|
||||
@@ -259,10 +259,21 @@ export class GeminiAgent {
|
||||
);
|
||||
}
|
||||
|
||||
async newSession({
|
||||
cwd,
|
||||
mcpServers,
|
||||
}: acp.NewSessionRequest): Promise<acp.NewSessionResponse> {
|
||||
async newSession(
|
||||
req: acp.NewSessionRequest,
|
||||
): Promise<acp.NewSessionResponse> {
|
||||
const { cwd, mcpServers } = req;
|
||||
const meta = hasMeta(req) ? req._meta : undefined;
|
||||
const additionalRootsRaw = meta?.['additionalRoots'];
|
||||
const additionalRoots: string[] = [];
|
||||
if (Array.isArray(additionalRootsRaw)) {
|
||||
for (const root of additionalRootsRaw) {
|
||||
if (typeof root === 'string') {
|
||||
additionalRoots.push(root);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const sessionId = randomUUID();
|
||||
const loadedSettings = loadSettings(cwd);
|
||||
const config = await this.newSessionConfig(
|
||||
@@ -270,6 +281,7 @@ export class GeminiAgent {
|
||||
cwd,
|
||||
mcpServers,
|
||||
loadedSettings,
|
||||
additionalRoots,
|
||||
);
|
||||
|
||||
const authType =
|
||||
@@ -473,6 +485,7 @@ export class GeminiAgent {
|
||||
cwd: string,
|
||||
mcpServers: acp.McpServer[],
|
||||
loadedSettings?: LoadedSettings,
|
||||
additionalRoots: string[] = [],
|
||||
): Promise<Config> {
|
||||
const currentSettings = loadedSettings || this.settings;
|
||||
const mergedMcpServers = { ...currentSettings.merged.mcpServers };
|
||||
@@ -513,6 +526,13 @@ export class GeminiAgent {
|
||||
const settings = {
|
||||
...currentSettings.merged,
|
||||
mcpServers: mergedMcpServers,
|
||||
context: {
|
||||
...currentSettings.merged.context,
|
||||
includeDirectories: [
|
||||
...(currentSettings.merged.context?.includeDirectories || []),
|
||||
...additionalRoots,
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const config = await loadCliConfig(settings, sessionId, this.argv, { cwd });
|
||||
@@ -693,6 +713,29 @@ export class Session {
|
||||
const pendingSend = new AbortController();
|
||||
this.pendingPrompt = pendingSend;
|
||||
|
||||
const meta = hasMeta(params) ? params._meta : undefined;
|
||||
const additionalRootsRaw = meta?.['additionalRoots'];
|
||||
const additionalRoots: string[] = [];
|
||||
if (Array.isArray(additionalRootsRaw)) {
|
||||
for (const root of additionalRootsRaw) {
|
||||
if (typeof root === 'string') {
|
||||
additionalRoots.push(root);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (additionalRoots.length > 0) {
|
||||
this.context.config.getWorkspaceContext().addDirectories(additionalRoots);
|
||||
await this.context.config
|
||||
.getSkillManager()
|
||||
.discoverSkills(
|
||||
this.context.config.storage,
|
||||
this.context.config.getExtensions(),
|
||||
this.context.config.isTrustedFolder(),
|
||||
[...this.context.config.getWorkspaceContext().getDirectories()],
|
||||
);
|
||||
}
|
||||
|
||||
await this.context.config.waitForMcpInit();
|
||||
|
||||
const promptId = Math.random().toString(16).slice(2);
|
||||
|
||||
@@ -1413,6 +1413,7 @@ export class Config implements McpContext, AgentLoopContext {
|
||||
this.storage,
|
||||
this.getExtensions(),
|
||||
this.isTrustedFolder(),
|
||||
[...this.workspaceContext.getDirectories()],
|
||||
);
|
||||
this.getSkillManager().setDisabledSkills(this.disabledSkills);
|
||||
|
||||
@@ -3111,6 +3112,7 @@ export class Config implements McpContext, AgentLoopContext {
|
||||
this.storage,
|
||||
this.getExtensions(),
|
||||
this.isTrustedFolder(),
|
||||
[...this.workspaceContext.getDirectories()],
|
||||
);
|
||||
this.getSkillManager().setDisabledSkills(this.disabledSkills);
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ export class SkillManager {
|
||||
storage: Storage,
|
||||
extensions: GeminiCLIExtension[] = [],
|
||||
isTrusted: boolean = false,
|
||||
additionalRoots: string[] = [],
|
||||
): Promise<void> {
|
||||
this.clearSkills();
|
||||
|
||||
@@ -89,6 +90,26 @@ export class SkillManager {
|
||||
storage.getProjectAgentSkillsDir(),
|
||||
);
|
||||
this.addSkillsWithPrecedence(projectAgentSkills);
|
||||
|
||||
// 5. Additional roots (e.g. from includeDirectories)
|
||||
if (additionalRoots && additionalRoots.length > 0) {
|
||||
for (const root of additionalRoots) {
|
||||
// Direct search (looks for SKILL.md and */SKILL.md)
|
||||
const rootSkills = await loadSkillsFromDir(root);
|
||||
this.addSkillsWithPrecedence(rootSkills);
|
||||
|
||||
// Search in standard subdirectories inside the root
|
||||
const agentSkills = await loadSkillsFromDir(
|
||||
path.join(root, '.agents', 'skills'),
|
||||
);
|
||||
this.addSkillsWithPrecedence(agentSkills);
|
||||
|
||||
const geminiSkills = await loadSkillsFromDir(
|
||||
path.join(root, '.gemini', 'skills'),
|
||||
);
|
||||
this.addSkillsWithPrecedence(geminiSkills);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user