mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-11 22:00:41 -07:00
[feat]: Add /extensions restart command (#12739)
This commit is contained in:
@@ -7,7 +7,11 @@
|
||||
import { debugLogger, listExtensions } from '@google/gemini-cli-core';
|
||||
import type { ExtensionUpdateInfo } from '../../config/extension.js';
|
||||
import { getErrorMessage } from '../../utils/errors.js';
|
||||
import { MessageType, type HistoryItemExtensionsList } from '../types.js';
|
||||
import {
|
||||
MessageType,
|
||||
type HistoryItemExtensionsList,
|
||||
type HistoryItemInfo,
|
||||
} from '../types.js';
|
||||
import {
|
||||
type CommandContext,
|
||||
type SlashCommand,
|
||||
@@ -17,6 +21,7 @@ import open from 'open';
|
||||
import process from 'node:process';
|
||||
import { ExtensionManager } from '../../config/extension-manager.js';
|
||||
import { SettingScope } from '../../config/settings.js';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
|
||||
async function listAction(context: CommandContext) {
|
||||
const historyItem: HistoryItemExtensionsList = {
|
||||
@@ -116,6 +121,118 @@ function updateAction(context: CommandContext, args: string): Promise<void> {
|
||||
return updateComplete.then((_) => {});
|
||||
}
|
||||
|
||||
async function restartAction(
|
||||
context: CommandContext,
|
||||
args: string,
|
||||
): Promise<void> {
|
||||
const extensionLoader = context.services.config?.getExtensionLoader();
|
||||
if (!extensionLoader) {
|
||||
context.ui.addItem(
|
||||
{
|
||||
type: MessageType.ERROR,
|
||||
text: "Extensions are not yet loaded, can't restart yet",
|
||||
},
|
||||
Date.now(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const restartArgs = args.split(' ').filter((value) => value.length > 0);
|
||||
const all = restartArgs.length === 1 && restartArgs[0] === '--all';
|
||||
const names = all ? null : restartArgs;
|
||||
if (!all && names?.length === 0) {
|
||||
context.ui.addItem(
|
||||
{
|
||||
type: MessageType.ERROR,
|
||||
text: 'Usage: /extensions restart <extension-names>|--all',
|
||||
},
|
||||
Date.now(),
|
||||
);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
let extensionsToRestart = extensionLoader
|
||||
.getExtensions()
|
||||
.filter((extension) => extension.isActive);
|
||||
if (names) {
|
||||
extensionsToRestart = extensionsToRestart.filter((extension) =>
|
||||
names.includes(extension.name),
|
||||
);
|
||||
if (names.length !== extensionsToRestart.length) {
|
||||
const notFound = names.filter(
|
||||
(name) =>
|
||||
!extensionsToRestart.some((extension) => extension.name === name),
|
||||
);
|
||||
if (notFound.length > 0) {
|
||||
context.ui.addItem(
|
||||
{
|
||||
type: MessageType.WARNING,
|
||||
text: `Extension(s) not found or not active: ${notFound.join(
|
||||
', ',
|
||||
)}`,
|
||||
},
|
||||
Date.now(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (extensionsToRestart.length === 0) {
|
||||
// We will have logged a different message above already.
|
||||
return;
|
||||
}
|
||||
|
||||
const s = extensionsToRestart.length > 1 ? 's' : '';
|
||||
|
||||
const restartingMessage = {
|
||||
type: MessageType.INFO,
|
||||
text: `Restarting ${extensionsToRestart.length} extension${s}...`,
|
||||
color: theme.text.primary,
|
||||
};
|
||||
context.ui.addItem(restartingMessage, Date.now());
|
||||
|
||||
const results = await Promise.allSettled(
|
||||
extensionsToRestart.map(async (extension) => {
|
||||
if (extension.isActive) {
|
||||
await extensionLoader.restartExtension(extension);
|
||||
context.ui.dispatchExtensionStateUpdate({
|
||||
type: 'RESTARTED',
|
||||
payload: {
|
||||
name: extension.name,
|
||||
},
|
||||
});
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
const failures = results.filter(
|
||||
(result): result is PromiseRejectedResult => result.status === 'rejected',
|
||||
);
|
||||
|
||||
if (failures.length > 0) {
|
||||
const errorMessages = failures
|
||||
.map((failure, index) => {
|
||||
const extensionName = extensionsToRestart[index].name;
|
||||
return `${extensionName}: ${getErrorMessage(failure.reason)}`;
|
||||
})
|
||||
.join('\n ');
|
||||
context.ui.addItem(
|
||||
{
|
||||
type: MessageType.ERROR,
|
||||
text: `Failed to restart some extensions:\n ${errorMessages}`,
|
||||
},
|
||||
Date.now(),
|
||||
);
|
||||
} else {
|
||||
const infoItem: HistoryItemInfo = {
|
||||
type: MessageType.INFO,
|
||||
text: `${extensionsToRestart.length} extension${s} restarted successfully.`,
|
||||
icon: ' ',
|
||||
color: theme.text.primary,
|
||||
};
|
||||
context.ui.addItem(infoItem, Date.now());
|
||||
}
|
||||
}
|
||||
|
||||
async function exploreAction(context: CommandContext) {
|
||||
const extensionsUrl = 'https://geminicli.com/extensions/';
|
||||
|
||||
@@ -284,10 +401,14 @@ export function completeExtensions(
|
||||
partialArg: string,
|
||||
) {
|
||||
let extensions = context.services.config?.getExtensions() ?? [];
|
||||
|
||||
if (context.invocation?.name === 'enable') {
|
||||
extensions = extensions.filter((ext) => !ext.isActive);
|
||||
}
|
||||
if (context.invocation?.name === 'disable') {
|
||||
if (
|
||||
context.invocation?.name === 'disable' ||
|
||||
context.invocation?.name === 'restart'
|
||||
) {
|
||||
extensions = extensions.filter((ext) => ext.isActive);
|
||||
}
|
||||
const extensionNames = extensions.map((ext) => ext.name);
|
||||
@@ -351,6 +472,14 @@ const exploreExtensionsCommand: SlashCommand = {
|
||||
action: exploreAction,
|
||||
};
|
||||
|
||||
const restartCommand: SlashCommand = {
|
||||
name: 'restart',
|
||||
description: 'Restart all extensions',
|
||||
kind: CommandKind.BUILT_IN,
|
||||
action: restartAction,
|
||||
completion: completeExtensions,
|
||||
};
|
||||
|
||||
export function extensionsCommand(
|
||||
enableExtensionReloading?: boolean,
|
||||
): SlashCommand {
|
||||
@@ -365,6 +494,7 @@ export function extensionsCommand(
|
||||
listExtensionsCommand,
|
||||
updateExtensionsCommand,
|
||||
exploreExtensionsCommand,
|
||||
restartCommand,
|
||||
...conditionalCommands,
|
||||
],
|
||||
action: (context, args) =>
|
||||
|
||||
Reference in New Issue
Block a user