mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-20 10:10:56 -07:00
feat(extensions): add support for plan directory in extension manifest (#20354)
Co-authored-by: Mahima Shanware <mshanware@google.com> Co-authored-by: Jerop Kipruto <jerop@google.com>
This commit is contained in:
@@ -19,6 +19,8 @@ import {
|
||||
debugLogger,
|
||||
ApprovalMode,
|
||||
type MCPServerConfig,
|
||||
type GeminiCLIExtension,
|
||||
Storage,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { loadCliConfig, parseArguments, type CliArgs } from './config.js';
|
||||
import {
|
||||
@@ -3524,4 +3526,101 @@ describe('loadCliConfig mcpEnabled', () => {
|
||||
expect(config.getAllowedMcpServers()).toEqual(['serverA']);
|
||||
expect(config.getBlockedMcpServers()).toEqual(['serverB']);
|
||||
});
|
||||
|
||||
describe('extension plan settings', () => {
|
||||
beforeEach(() => {
|
||||
vi.spyOn(Storage.prototype, 'getProjectTempDir').mockReturnValue(
|
||||
'/mock/home/user/.gemini/tmp/test-project',
|
||||
);
|
||||
});
|
||||
|
||||
it('should use plan directory from active extension when user has not specified one', async () => {
|
||||
process.argv = ['node', 'script.js'];
|
||||
const settings = createTestMergedSettings({
|
||||
experimental: { plan: true },
|
||||
});
|
||||
const argv = await parseArguments(settings);
|
||||
|
||||
vi.spyOn(ExtensionManager.prototype, 'getExtensions').mockReturnValue([
|
||||
{
|
||||
name: 'ext-plan',
|
||||
isActive: true,
|
||||
plan: { directory: 'ext-plans-dir' },
|
||||
} as unknown as GeminiCLIExtension,
|
||||
]);
|
||||
|
||||
const config = await loadCliConfig(settings, 'test-session', argv);
|
||||
expect(config.storage.getPlansDir()).toContain('ext-plans-dir');
|
||||
});
|
||||
|
||||
it('should NOT use plan directory from active extension when user has specified one', async () => {
|
||||
process.argv = ['node', 'script.js'];
|
||||
const settings = createTestMergedSettings({
|
||||
experimental: { plan: true },
|
||||
general: {
|
||||
plan: { directory: 'user-plans-dir' },
|
||||
},
|
||||
});
|
||||
const argv = await parseArguments(settings);
|
||||
|
||||
vi.spyOn(ExtensionManager.prototype, 'getExtensions').mockReturnValue([
|
||||
{
|
||||
name: 'ext-plan',
|
||||
isActive: true,
|
||||
plan: { directory: 'ext-plans-dir' },
|
||||
} as unknown as GeminiCLIExtension,
|
||||
]);
|
||||
|
||||
const config = await loadCliConfig(settings, 'test-session', argv);
|
||||
expect(config.storage.getPlansDir()).toContain('user-plans-dir');
|
||||
expect(config.storage.getPlansDir()).not.toContain('ext-plans-dir');
|
||||
});
|
||||
|
||||
it('should NOT use plan directory from inactive extension', async () => {
|
||||
process.argv = ['node', 'script.js'];
|
||||
const settings = createTestMergedSettings({
|
||||
experimental: { plan: true },
|
||||
});
|
||||
const argv = await parseArguments(settings);
|
||||
|
||||
vi.spyOn(ExtensionManager.prototype, 'getExtensions').mockReturnValue([
|
||||
{
|
||||
name: 'ext-plan',
|
||||
isActive: false,
|
||||
plan: { directory: 'ext-plans-dir-inactive' },
|
||||
} as unknown as GeminiCLIExtension,
|
||||
]);
|
||||
|
||||
const config = await loadCliConfig(settings, 'test-session', argv);
|
||||
expect(config.storage.getPlansDir()).not.toContain(
|
||||
'ext-plans-dir-inactive',
|
||||
);
|
||||
});
|
||||
|
||||
it('should use default path if neither user nor extension settings provide a plan directory', async () => {
|
||||
process.argv = ['node', 'script.js'];
|
||||
const settings = createTestMergedSettings({
|
||||
experimental: { plan: true },
|
||||
});
|
||||
const argv = await parseArguments(settings);
|
||||
|
||||
// No extensions providing plan directory
|
||||
vi.spyOn(ExtensionManager.prototype, 'getExtensions').mockReturnValue([]);
|
||||
|
||||
const config = await loadCliConfig(settings, 'test-session', argv);
|
||||
// Should return the default managed temp directory path
|
||||
expect(config.storage.getPlansDir()).toBe(
|
||||
path.join(
|
||||
'/mock',
|
||||
'home',
|
||||
'user',
|
||||
'.gemini',
|
||||
'tmp',
|
||||
'test-project',
|
||||
'test-session',
|
||||
'plans',
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -511,6 +511,10 @@ export async function loadCliConfig(
|
||||
});
|
||||
await extensionManager.loadExtensions();
|
||||
|
||||
const extensionPlanSettings = extensionManager
|
||||
.getExtensions()
|
||||
.find((ext) => ext.isActive && ext.plan?.directory)?.plan;
|
||||
|
||||
const experimentalJitContext = settings.experimental?.jitContext ?? false;
|
||||
|
||||
let memoryContent: string | HierarchicalMemory = '';
|
||||
@@ -827,7 +831,9 @@ export async function loadCliConfig(
|
||||
enableAgents: settings.experimental?.enableAgents,
|
||||
plan: settings.experimental?.plan,
|
||||
directWebFetch: settings.experimental?.directWebFetch,
|
||||
planSettings: settings.general?.plan,
|
||||
planSettings: settings.general?.plan?.directory
|
||||
? settings.general.plan
|
||||
: (extensionPlanSettings ?? settings.general?.plan),
|
||||
enableEventDrivenScheduler: true,
|
||||
skillsSupport: settings.skills?.enabled ?? true,
|
||||
disabledSkills: settings.skills?.disabled,
|
||||
|
||||
@@ -886,6 +886,7 @@ Would you like to attempt to install via "git clone" instead?`,
|
||||
themes: config.themes,
|
||||
rules,
|
||||
checkers,
|
||||
plan: config.plan,
|
||||
};
|
||||
} catch (e) {
|
||||
debugLogger.error(
|
||||
|
||||
@@ -33,6 +33,15 @@ export interface ExtensionConfig {
|
||||
* These themes will be registered when the extension is activated.
|
||||
*/
|
||||
themes?: CustomTheme[];
|
||||
/**
|
||||
* Planning features configuration contributed by this extension.
|
||||
*/
|
||||
plan?: {
|
||||
/**
|
||||
* The directory where planning artifacts are stored.
|
||||
*/
|
||||
directory?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ExtensionUpdateInfo {
|
||||
|
||||
Reference in New Issue
Block a user