mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-08-10 00:47:16 -07:00
Merge branch 'main' into feat/stats-quota-display-improvements
This commit is contained in:
@@ -69,6 +69,7 @@ export class RestoreCommand implements Command {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const toolCallData = JSON.parse(data);
|
||||
const ToolCallDataSchema = getToolCallDataSchema();
|
||||
const parseResult = ToolCallDataSchema.safeParse(toolCallData);
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
|
||||
import type { MCPServerConfig } from '@google/gemini-cli-core';
|
||||
import {
|
||||
type MCPServerConfig,
|
||||
debugLogger,
|
||||
GEMINI_DIR,
|
||||
getErrorMessage,
|
||||
@@ -122,6 +122,7 @@ export function loadSettings(workspaceDir: string): Settings {
|
||||
function resolveEnvVarsInString(value: string): string {
|
||||
const envVarRegex = /\$(?:(\w+)|{([^}]+)})/g; // Find $VAR_NAME or ${VAR_NAME}
|
||||
return value.replace(envVarRegex, (match, varName1, varName2) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const varName = varName1 || varName2;
|
||||
if (process && process.env && typeof process.env[varName] === 'string') {
|
||||
return process.env[varName];
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
import express from 'express';
|
||||
|
||||
import type { AgentCard, Message } from '@a2a-js/sdk';
|
||||
import type { TaskStore } from '@a2a-js/sdk/server';
|
||||
import {
|
||||
type TaskStore,
|
||||
DefaultRequestHandler,
|
||||
InMemoryTaskStore,
|
||||
DefaultExecutionEventBus,
|
||||
@@ -25,9 +25,12 @@ import { loadConfig, loadEnvironment, setTargetDir } from '../config/config.js';
|
||||
import { loadSettings } from '../config/settings.js';
|
||||
import { loadExtensions } from '../config/extension.js';
|
||||
import { commandRegistry } from '../commands/command-registry.js';
|
||||
import { debugLogger, SimpleExtensionLoader } from '@google/gemini-cli-core';
|
||||
import {
|
||||
debugLogger,
|
||||
SimpleExtensionLoader,
|
||||
GitService,
|
||||
} from '@google/gemini-cli-core';
|
||||
import type { Command, CommandArgument } from '../commands/types.js';
|
||||
import { GitService } from '@google/gemini-cli-core';
|
||||
|
||||
type CommandResponse = {
|
||||
name: string;
|
||||
@@ -88,6 +91,7 @@ async function handleExecuteCommand(
|
||||
},
|
||||
) {
|
||||
logger.info('[CoreAgent] Received /executeCommand request: ', req.body);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const { command, args } = req.body;
|
||||
try {
|
||||
if (typeof command !== 'string') {
|
||||
@@ -211,6 +215,7 @@ export async function createApp() {
|
||||
const agentSettings = req.body.agentSettings as
|
||||
| AgentSettings
|
||||
| undefined;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const contextId = req.body.contextId || uuidv4();
|
||||
const wrapper = await agentExecutor.createTask(
|
||||
taskId,
|
||||
|
||||
@@ -246,6 +246,7 @@ export class GCSTaskStore implements TaskStore {
|
||||
}
|
||||
const [compressedMetadata] = await metadataFile.download();
|
||||
const jsonData = gunzipSync(compressedMetadata).toString();
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const loadedMetadata = JSON.parse(jsonData);
|
||||
logger.info(`Task ${taskId} metadata loaded from GCS.`);
|
||||
|
||||
@@ -282,12 +283,14 @@ export class GCSTaskStore implements TaskStore {
|
||||
|
||||
return {
|
||||
id: taskId,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
contextId: loadedMetadata._contextId || uuidv4(),
|
||||
kind: 'task',
|
||||
status: {
|
||||
state: persistedState._taskState,
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
metadata: loadedMetadata,
|
||||
history: [],
|
||||
artifacts: [],
|
||||
|
||||
@@ -866,6 +866,7 @@ Would you like to attempt to install via "git clone" instead?`,
|
||||
|
||||
try {
|
||||
const hooksContent = await fs.promises.readFile(hooksFilePath, 'utf-8');
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const rawHooks = JSON.parse(hooksContent);
|
||||
|
||||
if (
|
||||
|
||||
@@ -81,6 +81,7 @@ export class ExtensionRegistryClient {
|
||||
`${ext.extensionName} ${ext.extensionDescription} ${ext.fullName}`,
|
||||
fuzzy: true,
|
||||
});
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const results = await fzf.find(query);
|
||||
return results.map((r: { item: RegistryExtension }) => r.item);
|
||||
}
|
||||
|
||||
@@ -41,8 +41,9 @@ export async function createPolicyEngineConfig(
|
||||
export function createPolicyUpdater(
|
||||
policyEngine: PolicyEngine,
|
||||
messageBus: MessageBus,
|
||||
storage: Storage,
|
||||
) {
|
||||
return createCorePolicyUpdater(policyEngine, messageBus);
|
||||
return createCorePolicyUpdater(policyEngine, messageBus, storage);
|
||||
}
|
||||
|
||||
export interface WorkspacePolicyState {
|
||||
|
||||
@@ -583,7 +583,7 @@ export async function main() {
|
||||
|
||||
const policyEngine = config.getPolicyEngine();
|
||||
const messageBus = config.getMessageBus();
|
||||
createPolicyUpdater(policyEngine, messageBus);
|
||||
createPolicyUpdater(policyEngine, messageBus, config.storage);
|
||||
|
||||
// Register SessionEnd hook to fire on graceful exit
|
||||
// This runs before telemetry shutdown in runExitCleanup()
|
||||
|
||||
@@ -217,13 +217,14 @@ export class AppRig {
|
||||
}
|
||||
|
||||
private stubRefreshAuth() {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion, @typescript-eslint/no-explicit-any
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment
|
||||
const gcConfig = this.config as any;
|
||||
gcConfig.refreshAuth = async (authMethod: AuthType) => {
|
||||
gcConfig.modelAvailabilityService.reset();
|
||||
|
||||
const newContentGeneratorConfig = {
|
||||
authType: authMethod,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
proxy: gcConfig.getProxy(),
|
||||
apiKey: process.env['GEMINI_API_KEY'] || 'test-api-key',
|
||||
};
|
||||
|
||||
@@ -21,7 +21,7 @@ import type { TextBuffer } from '../ui/components/shared/text-buffer.js';
|
||||
const invalidCharsRegex = /[\b\x1b]/;
|
||||
|
||||
function toHaveOnlyValidCharacters(this: Assertion, buffer: TextBuffer) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-type-assertion
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-type-assertion, @typescript-eslint/no-unsafe-assignment
|
||||
const { isNot } = this as any;
|
||||
let pass = true;
|
||||
const invalidLines: Array<{ line: number; content: string }> = [];
|
||||
|
||||
@@ -45,7 +45,7 @@ export const createMockCommandContext = (
|
||||
forScope: vi.fn().mockReturnValue({ settings: {} }),
|
||||
} as unknown as LoadedSettings,
|
||||
git: undefined as GitService | undefined,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion, @typescript-eslint/no-unsafe-assignment
|
||||
logger: {
|
||||
log: vi.fn(),
|
||||
logMessage: vi.fn(),
|
||||
@@ -54,7 +54,7 @@ export const createMockCommandContext = (
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} as any, // Cast because Logger is a class.
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion, @typescript-eslint/no-unsafe-assignment
|
||||
ui: {
|
||||
addItem: vi.fn(),
|
||||
clear: vi.fn(),
|
||||
@@ -94,11 +94,14 @@ export const createMockCommandContext = (
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const merge = (target: any, source: any): any => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const output = { ...target };
|
||||
|
||||
for (const key in source) {
|
||||
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const sourceValue = source[key];
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const targetValue = output[key];
|
||||
|
||||
if (
|
||||
@@ -106,9 +109,11 @@ export const createMockCommandContext = (
|
||||
Object.prototype.toString.call(sourceValue) === '[object Object]' &&
|
||||
Object.prototype.toString.call(targetValue) === '[object Object]'
|
||||
) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
output[key] = merge(targetValue, sourceValue);
|
||||
} else {
|
||||
// If not, we do a direct assignment. This preserves Date objects and others.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
output[key] = sourceValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ export const createMockSettings = (
|
||||
workspace,
|
||||
isTrusted,
|
||||
errors,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
merged: mergedOverride,
|
||||
...settingsOverrides
|
||||
} = overrides;
|
||||
@@ -75,7 +76,7 @@ export const createMockSettings = (
|
||||
// Assign any function overrides (e.g., vi.fn() for methods)
|
||||
for (const key in overrides) {
|
||||
if (typeof overrides[key] === 'function') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion, @typescript-eslint/no-unsafe-assignment
|
||||
(loaded as any)[key] = overrides[key];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2078,8 +2078,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
|
||||
const fetchBannerTexts = async () => {
|
||||
const [defaultBanner, warningBanner] = await Promise.all([
|
||||
// TODO: temporarily disabling the banner, it will be re-added.
|
||||
'',
|
||||
config.getBannerTextNoCapacityIssues(),
|
||||
config.getBannerTextCapacityIssues(),
|
||||
]);
|
||||
|
||||
|
||||
@@ -115,6 +115,7 @@ export function SettingsDialog({
|
||||
}
|
||||
|
||||
const doSearch = async () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const results = await fzfInstance.find(searchQuery);
|
||||
|
||||
if (!active) return;
|
||||
|
||||
@@ -451,6 +451,7 @@ Return a JSON object with:
|
||||
'--limit',
|
||||
String(limit),
|
||||
]);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const issues: Issue[] = JSON.parse(stdout);
|
||||
if (issues.length === 0) {
|
||||
setState((s) => ({
|
||||
|
||||
@@ -137,6 +137,7 @@ export const TriageIssues = ({
|
||||
'--limit',
|
||||
String(limit),
|
||||
]);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const issues: Issue[] = JSON.parse(stdout);
|
||||
if (issues.length === 0) {
|
||||
setState((s) => ({
|
||||
|
||||
@@ -166,6 +166,7 @@ async function searchResourceCandidates(
|
||||
const fzf = new AsyncFzf(candidates, {
|
||||
selector: (candidate: ResourceSuggestionCandidate) => candidate.searchKey,
|
||||
});
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const results = await fzf.find(normalizedPattern, {
|
||||
limit: MAX_SUGGESTIONS_TO_SHOW * 3,
|
||||
});
|
||||
@@ -188,6 +189,7 @@ async function searchAgentCandidates(
|
||||
const fzf = new AsyncFzf(candidates, {
|
||||
selector: (s: Suggestion) => s.label,
|
||||
});
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const results = await fzf.find(normalizedPattern, {
|
||||
limit: MAX_SUGGESTIONS_TO_SHOW,
|
||||
});
|
||||
|
||||
@@ -57,6 +57,7 @@ export const useSessionBrowser = (
|
||||
const originalFilePath = path.join(chatsDir, fileName);
|
||||
|
||||
// Load up the conversation.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const conversation: ConversationRecord = JSON.parse(
|
||||
await fs.readFile(originalFilePath, 'utf8'),
|
||||
);
|
||||
|
||||
@@ -271,6 +271,7 @@ function useCommandSuggestions(
|
||||
const fzfInstance = getFzfForCommands(commandsToSearch);
|
||||
if (fzfInstance) {
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const fzfResults = await fzfInstance.fzf.find(partial);
|
||||
if (signal.aborted) return;
|
||||
const uniqueCommands = new Set<SlashCommand>();
|
||||
|
||||
@@ -22,6 +22,7 @@ export const useStateAndRef = <
|
||||
(newStateOrCallback) => {
|
||||
let newValue: T;
|
||||
if (typeof newStateOrCallback === 'function') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
newValue = newStateOrCallback(ref.current);
|
||||
} else {
|
||||
newValue = newStateOrCallback;
|
||||
|
||||
@@ -243,6 +243,7 @@ export const TableRenderer: React.FC<TableRendererProps> = ({
|
||||
isHeader = false,
|
||||
): React.ReactNode => {
|
||||
const renderedCells = cells.map((cell, index) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const width = adjustedWidths[index] || 0;
|
||||
return renderCell(cell, width, isHeader);
|
||||
});
|
||||
|
||||
@@ -179,6 +179,7 @@ async function configureVSCodeStyle(
|
||||
await backupFile(keybindingsFile);
|
||||
try {
|
||||
const cleanContent = stripJsonComments(content);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const parsedContent = JSON.parse(cleanContent);
|
||||
if (!Array.isArray(parsedContent)) {
|
||||
return {
|
||||
|
||||
@@ -233,7 +233,9 @@ export function escapeAnsiCtrlCodes<T>(obj: T): T {
|
||||
let newArr: unknown[] | null = null;
|
||||
|
||||
for (let i = 0; i < obj.length; i++) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const value = obj[i];
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const escapedValue = escapeAnsiCtrlCodes(value);
|
||||
if (escapedValue !== value) {
|
||||
if (newArr === null) {
|
||||
|
||||
@@ -23,6 +23,7 @@ export function resolveEnvVarsInString(
|
||||
): string {
|
||||
const envVarRegex = /\$(?:(\w+)|{([^}]+)})/g; // Find $VAR_NAME or ${VAR_NAME}
|
||||
return value.replace(envVarRegex, (match, varName1, varName2) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const varName = varName1 || varName2;
|
||||
if (customEnv && typeof customEnv[varName] === 'string') {
|
||||
return customEnv[varName];
|
||||
|
||||
@@ -78,6 +78,7 @@ export const getLatestGitHubRelease = async (
|
||||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const releaseTag = (await response.json()).tag_name;
|
||||
if (!releaseTag) {
|
||||
throw new Error(`Response did not include tag_name field`);
|
||||
|
||||
@@ -29,6 +29,7 @@ export function tryParseJSON(input: string): object | null {
|
||||
if (!checkInput(input)) return null;
|
||||
const trimmed = input.trim();
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const parsed = JSON.parse(trimmed);
|
||||
if (parsed === null || typeof parsed !== 'object') {
|
||||
return null;
|
||||
|
||||
@@ -38,6 +38,7 @@ export class PersistentState {
|
||||
const filePath = this.getPath();
|
||||
if (fs.existsSync(filePath)) {
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
this.cache = JSON.parse(content);
|
||||
} else {
|
||||
this.cache = {};
|
||||
|
||||
@@ -23,6 +23,7 @@ export async function readStdin(): Promise<string> {
|
||||
|
||||
const onReadable = () => {
|
||||
let chunk;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
while ((chunk = process.stdin.read()) !== null) {
|
||||
if (pipedInputTimerId) {
|
||||
clearTimeout(pipedInputTimerId);
|
||||
|
||||
@@ -254,6 +254,7 @@ export const getAllSessionFiles = async (
|
||||
async (file): Promise<SessionFileEntry> => {
|
||||
const filePath = path.join(chatsDir, file);
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const content: ConversationRecord = JSON.parse(
|
||||
await fs.readFile(filePath, 'utf8'),
|
||||
);
|
||||
@@ -498,6 +499,7 @@ export class SessionSelector {
|
||||
const sessionPath = path.join(chatsDir, sessionInfo.fileName);
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const sessionData: ConversationRecord = JSON.parse(
|
||||
await fs.readFile(sessionPath, 'utf8'),
|
||||
);
|
||||
|
||||
@@ -371,6 +371,7 @@ export function setPendingSettingValue(
|
||||
pendingSettings: Settings,
|
||||
): Settings {
|
||||
const path = key.split('.');
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const newSettings = JSON.parse(JSON.stringify(pendingSettings));
|
||||
setNestedValue(newSettings, path, value);
|
||||
return newSettings;
|
||||
|
||||
@@ -25,7 +25,9 @@ function extractRecursiveMessage(input: string): string {
|
||||
(trimmed.startsWith('[') && trimmed.endsWith(']'))
|
||||
) {
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const parsed = JSON.parse(trimmed);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const next =
|
||||
parsed?.error?.message ||
|
||||
parsed?.[0]?.error?.message ||
|
||||
|
||||
@@ -23,6 +23,7 @@ export class AcpFileSystemService implements FileSystemService {
|
||||
return this.fallback.readTextFile(filePath);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const response = await this.connection.readTextFile({
|
||||
path: filePath,
|
||||
sessionId: this.sessionId,
|
||||
|
||||
@@ -702,6 +702,7 @@ export class Session {
|
||||
},
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const output = await this.connection.requestPermission(params);
|
||||
const outcome =
|
||||
output.outcome.outcome === CoreToolCallStatus.Cancelled
|
||||
|
||||
@@ -27,6 +27,7 @@ export class AcknowledgedAgentsService {
|
||||
const filePath = Storage.getAcknowledgedAgentsPath();
|
||||
try {
|
||||
const content = await fs.readFile(filePath, 'utf-8');
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
this.acknowledgedAgents = JSON.parse(content);
|
||||
} catch (error: unknown) {
|
||||
if (!isNodeError(error) || error.code !== 'ENOENT') {
|
||||
|
||||
@@ -54,6 +54,7 @@ export async function scheduleAgentTools(
|
||||
} = options;
|
||||
|
||||
// Create a proxy/override of the config to provide the agent-specific tool registry.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const agentConfig: Config = Object.create(config);
|
||||
agentConfig.getToolRegistry = () => toolRegistry;
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
AgentStartEvent,
|
||||
AgentFinishEvent,
|
||||
RecoveryAttemptEvent,
|
||||
LlmRole,
|
||||
} from '../telemetry/types.js';
|
||||
import type {
|
||||
LocalAgentDefinition,
|
||||
@@ -59,7 +60,6 @@ import { getVersion } from '../utils/version.js';
|
||||
import { getToolCallContext } from '../utils/toolCallContext.js';
|
||||
import { scheduleAgentTools } from './agent-scheduler.js';
|
||||
import { DeadlineTimer } from '../utils/deadlineTimer.js';
|
||||
import { LlmRole } from '../telemetry/types.js';
|
||||
import { formatUserHintsForModel } from '../utils/fastAckHelper.js';
|
||||
|
||||
/** A callback function to report on agent activity. */
|
||||
@@ -925,6 +925,7 @@ export class LocalAgentExecutor<TOutput extends z.ZodTypeAny> {
|
||||
continue;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const validatedOutput = validationResult.data;
|
||||
if (this.definition.processOutput) {
|
||||
submittedOutput = this.definition.processOutput(validatedOutput);
|
||||
|
||||
@@ -394,6 +394,7 @@ export class AgentRegistry {
|
||||
}
|
||||
|
||||
// Use Object.create to preserve lazy getters on the definition object
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const merged: LocalAgentDefinition<TOutput> = Object.create(definition);
|
||||
|
||||
if (overrides.runConfig) {
|
||||
|
||||
@@ -31,6 +31,7 @@ export function sanitizeAdminSettings(
|
||||
|
||||
if (sanitized.mcpSetting?.mcpConfigJson) {
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const parsed = JSON.parse(sanitized.mcpSetting.mcpConfigJson);
|
||||
const validationResult = McpConfigDefinitionSchema.safeParse(parsed);
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ export async function getExperiments(
|
||||
const expPath = process.env['GEMINI_EXP'];
|
||||
debugLogger.debug('Reading experiments from', expPath);
|
||||
const content = await fs.promises.readFile(expPath, 'utf8');
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const response: ListExperimentsResponse = JSON.parse(content);
|
||||
if (
|
||||
(response.flags && !Array.isArray(response.flags)) ||
|
||||
|
||||
@@ -125,6 +125,7 @@ export class OAuthCredentialStorage {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const credentials: Credentials = JSON.parse(credsJson);
|
||||
|
||||
// Save to new storage
|
||||
|
||||
@@ -695,6 +695,7 @@ async function fetchAndCacheUserInfo(client: OAuth2Client): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const userInfo = await response.json();
|
||||
await userAccountManager.cacheGoogleAccount(userInfo.email);
|
||||
} catch (error) {
|
||||
|
||||
@@ -23,6 +23,8 @@ const TMP_DIR_NAME = 'tmp';
|
||||
const BIN_DIR_NAME = 'bin';
|
||||
const AGENTS_DIR_NAME = '.agents';
|
||||
|
||||
export const AUTO_SAVED_POLICY_FILENAME = 'auto-saved.toml';
|
||||
|
||||
export class Storage {
|
||||
private readonly targetDir: string;
|
||||
private readonly sessionId: string | undefined;
|
||||
@@ -154,6 +156,13 @@ export class Storage {
|
||||
return path.join(this.getGeminiDir(), 'policies');
|
||||
}
|
||||
|
||||
getAutoSavedPolicyPath(): string {
|
||||
return path.join(
|
||||
this.getWorkspacePoliciesDir(),
|
||||
AUTO_SAVED_POLICY_FILENAME,
|
||||
);
|
||||
}
|
||||
|
||||
ensureProjectTempDirExists(): void {
|
||||
fs.mkdirSync(this.getProjectTempDir(), { recursive: true });
|
||||
}
|
||||
|
||||
@@ -88,6 +88,7 @@ export class Logger {
|
||||
}
|
||||
try {
|
||||
const fileContent = await fs.readFile(this.logFilePath, 'utf-8');
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const parsedLogs = JSON.parse(fileContent);
|
||||
if (!Array.isArray(parsedLogs)) {
|
||||
debugLogger.debug(
|
||||
@@ -352,6 +353,7 @@ export class Logger {
|
||||
const path = await this._getCheckpointPath(tag);
|
||||
try {
|
||||
const fileContent = await fs.readFile(path, 'utf-8');
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const parsedContent = JSON.parse(fileContent);
|
||||
|
||||
// Handle legacy format (just an array of Content)
|
||||
|
||||
@@ -5,10 +5,8 @@
|
||||
*/
|
||||
|
||||
import { spawn } from 'node:child_process';
|
||||
import type { HookConfig } from './types.js';
|
||||
import { HookEventName, ConfigSource } from './types.js';
|
||||
import type { Config } from '../config/config.js';
|
||||
import type {
|
||||
HookConfig,
|
||||
HookInput,
|
||||
HookOutput,
|
||||
HookExecutionResult,
|
||||
@@ -17,6 +15,8 @@ import type {
|
||||
BeforeModelOutput,
|
||||
BeforeToolInput,
|
||||
} from './types.js';
|
||||
import { HookEventName, ConfigSource } from './types.js';
|
||||
import type { Config } from '../config/config.js';
|
||||
import type { LLMRequest } from './hookTranslator.js';
|
||||
import { debugLogger } from '../utils/debugLogger.js';
|
||||
import { sanitizeEnvironment } from '../services/environmentSanitization.js';
|
||||
@@ -356,8 +356,10 @@ export class HookRunner {
|
||||
const textToParse = stdout.trim() || stderr.trim();
|
||||
if (textToParse) {
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
let parsed = JSON.parse(textToParse);
|
||||
if (typeof parsed === 'string') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
parsed = JSON.parse(parsed);
|
||||
}
|
||||
if (parsed && typeof parsed === 'object') {
|
||||
|
||||
@@ -34,6 +34,7 @@ export class TrustedHooksManager {
|
||||
try {
|
||||
if (fs.existsSync(this.configPath)) {
|
||||
const content = fs.readFileSync(this.configPath, 'utf-8');
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
this.trustedHooks = JSON.parse(content);
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -16,8 +16,10 @@ import { getIdeProcessInfo } from './process-utils.js';
|
||||
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
||||
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
||||
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
||||
import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';
|
||||
import { ListToolsResultSchema } from '@modelcontextprotocol/sdk/types.js';
|
||||
import {
|
||||
CallToolResultSchema,
|
||||
ListToolsResultSchema,
|
||||
} from '@modelcontextprotocol/sdk/types.js';
|
||||
import { IDE_REQUEST_TIMEOUT_MS } from './constants.js';
|
||||
import { debugLogger } from '../utils/debugLogger.js';
|
||||
import {
|
||||
@@ -343,6 +345,7 @@ export class IdeClient {
|
||||
|
||||
if (textPart?.text) {
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const parsedJson = JSON.parse(textPart.text);
|
||||
if (parsedJson && typeof parsedJson.content === 'string') {
|
||||
return parsedJson.content;
|
||||
|
||||
@@ -89,8 +89,10 @@ export function getStdioConfigFromEnv(): StdioConfig | undefined {
|
||||
let args: string[] = [];
|
||||
if (argsStr) {
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const parsedArgs = JSON.parse(argsStr);
|
||||
if (Array.isArray(parsedArgs)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
args = parsedArgs;
|
||||
} else {
|
||||
logger.error(
|
||||
@@ -188,6 +190,7 @@ export async function getConnectionConfigFromFile(
|
||||
}
|
||||
|
||||
if (validWorkspaces.length === 1) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const selected = validWorkspaces[0];
|
||||
const fileIndex = parsedContents.indexOf(selected);
|
||||
if (fileIndex !== -1) {
|
||||
@@ -202,6 +205,7 @@ export async function getConnectionConfigFromFile(
|
||||
(content) => String(content.port) === portFromEnv,
|
||||
);
|
||||
if (matchingPortIndex !== -1) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const selected = validWorkspaces[matchingPortIndex];
|
||||
const fileIndex = parsedContents.indexOf(selected);
|
||||
if (fileIndex !== -1) {
|
||||
@@ -213,6 +217,7 @@ export async function getConnectionConfigFromFile(
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const selected = validWorkspaces[0];
|
||||
const fileIndex = parsedContents.indexOf(selected);
|
||||
if (fileIndex !== -1) {
|
||||
|
||||
@@ -47,6 +47,7 @@ async function getProcessTableWindows(): Promise<Map<number, ProcessInfo>> {
|
||||
|
||||
let processes: RawProcessInfo | RawProcessInfo[];
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
processes = JSON.parse(stdout);
|
||||
} catch (_e) {
|
||||
return processMap;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { vi } from 'vitest';
|
||||
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
|
||||
// Mock dependencies AT THE TOP
|
||||
const mockOpenBrowserSecurely = vi.hoisted(() => vi.fn());
|
||||
@@ -54,7 +54,6 @@ vi.mock('node:readline', () => ({
|
||||
})),
|
||||
}));
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import * as http from 'node:http';
|
||||
import * as crypto from 'node:crypto';
|
||||
import type {
|
||||
|
||||
@@ -409,6 +409,7 @@ export class OAuthUtils {
|
||||
*/
|
||||
static parseTokenExpiry(idToken: string): number | undefined {
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const payload = JSON.parse(
|
||||
Buffer.from(idToken.split('.')[1], 'base64').toString(),
|
||||
);
|
||||
|
||||
@@ -45,7 +45,9 @@ export class KeychainTokenStorage
|
||||
try {
|
||||
// Try to import keytar without any timeout - let the OS handle it
|
||||
const moduleName = 'keytar';
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const module = await import(moduleName);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
this.keytarModule = module.default || module;
|
||||
} catch (_) {
|
||||
//Keytar is optional so we shouldn't raise an error of log anything.
|
||||
|
||||
@@ -43,6 +43,20 @@ export const WORKSPACE_POLICY_TIER = 2;
|
||||
export const USER_POLICY_TIER = 3;
|
||||
export const ADMIN_POLICY_TIER = 4;
|
||||
|
||||
// Specific priority offsets and derived priorities for dynamic/settings rules.
|
||||
// These are added to the tier base (e.g., USER_POLICY_TIER).
|
||||
|
||||
// Workspace tier (2) + high priority (950/1000) = ALWAYS_ALLOW_PRIORITY
|
||||
// This ensures user "always allow" selections are high priority
|
||||
// within the workspace tier but still lose to user/admin policies.
|
||||
export const ALWAYS_ALLOW_PRIORITY = WORKSPACE_POLICY_TIER + 0.95;
|
||||
|
||||
export const MCP_EXCLUDED_PRIORITY = USER_POLICY_TIER + 0.9;
|
||||
export const EXCLUDE_TOOLS_FLAG_PRIORITY = USER_POLICY_TIER + 0.4;
|
||||
export const ALLOWED_TOOLS_FLAG_PRIORITY = USER_POLICY_TIER + 0.3;
|
||||
export const TRUSTED_MCP_SERVER_PRIORITY = USER_POLICY_TIER + 0.2;
|
||||
export const ALLOWED_MCP_SERVER_PRIORITY = USER_POLICY_TIER + 0.1;
|
||||
|
||||
/**
|
||||
* Gets the list of directories to search for policy files, in order of increasing priority
|
||||
* (Default -> User -> Project -> Admin).
|
||||
@@ -233,13 +247,14 @@ export async function createPolicyEngineConfig(
|
||||
// This ensures Admin > User > Workspace > Default hierarchy is always preserved,
|
||||
// while allowing user-specified priorities to work within each tier.
|
||||
//
|
||||
// Settings-based and dynamic rules (all in user tier 3.x):
|
||||
// 3.95: Tools that the user has selected as "Always Allow" in the interactive UI
|
||||
// 3.9: MCP servers excluded list (security: persistent server blocks)
|
||||
// 3.4: Command line flag --exclude-tools (explicit temporary blocks)
|
||||
// 3.3: Command line flag --allowed-tools (explicit temporary allows)
|
||||
// 3.2: MCP servers with trust=true (persistent trusted servers)
|
||||
// 3.1: MCP servers allowed list (persistent general server allows)
|
||||
// Settings-based and dynamic rules (mixed tiers):
|
||||
// MCP_EXCLUDED_PRIORITY: MCP servers excluded list (security: persistent server blocks)
|
||||
// EXCLUDE_TOOLS_FLAG_PRIORITY: Command line flag --exclude-tools (explicit temporary blocks)
|
||||
// ALLOWED_TOOLS_FLAG_PRIORITY: Command line flag --allowed-tools (explicit temporary allows)
|
||||
// TRUSTED_MCP_SERVER_PRIORITY: MCP servers with trust=true (persistent trusted servers)
|
||||
// ALLOWED_MCP_SERVER_PRIORITY: MCP servers allowed list (persistent general server allows)
|
||||
// ALWAYS_ALLOW_PRIORITY: Tools that the user has selected as "Always Allow" in the interactive UI
|
||||
// (Workspace tier 2.x - scoped to the project)
|
||||
//
|
||||
// TOML policy priorities (before transformation):
|
||||
// 10: Write tools default to ASK_USER (becomes 1.010 in default tier)
|
||||
@@ -250,33 +265,33 @@ export async function createPolicyEngineConfig(
|
||||
// 999: YOLO mode allow-all (becomes 1.999 in default tier)
|
||||
|
||||
// MCP servers that are explicitly excluded in settings.mcp.excluded
|
||||
// Priority: 3.9 (highest in user tier for security - persistent server blocks)
|
||||
// Priority: MCP_EXCLUDED_PRIORITY (highest in user tier for security - persistent server blocks)
|
||||
if (settings.mcp?.excluded) {
|
||||
for (const serverName of settings.mcp.excluded) {
|
||||
rules.push({
|
||||
toolName: `${serverName}__*`,
|
||||
decision: PolicyDecision.DENY,
|
||||
priority: 3.9,
|
||||
priority: MCP_EXCLUDED_PRIORITY,
|
||||
source: 'Settings (MCP Excluded)',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Tools that are explicitly excluded in the settings.
|
||||
// Priority: 3.4 (user tier - explicit temporary blocks)
|
||||
// Priority: EXCLUDE_TOOLS_FLAG_PRIORITY (user tier - explicit temporary blocks)
|
||||
if (settings.tools?.exclude) {
|
||||
for (const tool of settings.tools.exclude) {
|
||||
rules.push({
|
||||
toolName: tool,
|
||||
decision: PolicyDecision.DENY,
|
||||
priority: 3.4,
|
||||
priority: EXCLUDE_TOOLS_FLAG_PRIORITY,
|
||||
source: 'Settings (Tools Excluded)',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Tools that are explicitly allowed in the settings.
|
||||
// Priority: 3.3 (user tier - explicit temporary allows)
|
||||
// Priority: ALLOWED_TOOLS_FLAG_PRIORITY (user tier - explicit temporary allows)
|
||||
if (settings.tools?.allowed) {
|
||||
for (const tool of settings.tools.allowed) {
|
||||
// Check for legacy format: toolName(args)
|
||||
@@ -296,7 +311,7 @@ export async function createPolicyEngineConfig(
|
||||
rules.push({
|
||||
toolName,
|
||||
decision: PolicyDecision.ALLOW,
|
||||
priority: 3.3,
|
||||
priority: ALLOWED_TOOLS_FLAG_PRIORITY,
|
||||
argsPattern: new RegExp(pattern),
|
||||
source: 'Settings (Tools Allowed)',
|
||||
});
|
||||
@@ -308,7 +323,7 @@ export async function createPolicyEngineConfig(
|
||||
rules.push({
|
||||
toolName,
|
||||
decision: PolicyDecision.ALLOW,
|
||||
priority: 3.3,
|
||||
priority: ALLOWED_TOOLS_FLAG_PRIORITY,
|
||||
source: 'Settings (Tools Allowed)',
|
||||
});
|
||||
}
|
||||
@@ -320,7 +335,7 @@ export async function createPolicyEngineConfig(
|
||||
rules.push({
|
||||
toolName,
|
||||
decision: PolicyDecision.ALLOW,
|
||||
priority: 3.3,
|
||||
priority: ALLOWED_TOOLS_FLAG_PRIORITY,
|
||||
source: 'Settings (Tools Allowed)',
|
||||
});
|
||||
}
|
||||
@@ -328,7 +343,7 @@ export async function createPolicyEngineConfig(
|
||||
}
|
||||
|
||||
// MCP servers that are trusted in the settings.
|
||||
// Priority: 3.2 (user tier - persistent trusted servers)
|
||||
// Priority: TRUSTED_MCP_SERVER_PRIORITY (user tier - persistent trusted servers)
|
||||
if (settings.mcpServers) {
|
||||
for (const [serverName, serverConfig] of Object.entries(
|
||||
settings.mcpServers,
|
||||
@@ -339,7 +354,7 @@ export async function createPolicyEngineConfig(
|
||||
rules.push({
|
||||
toolName: `${serverName}__*`,
|
||||
decision: PolicyDecision.ALLOW,
|
||||
priority: 3.2,
|
||||
priority: TRUSTED_MCP_SERVER_PRIORITY,
|
||||
source: 'Settings (MCP Trusted)',
|
||||
});
|
||||
}
|
||||
@@ -347,13 +362,13 @@ export async function createPolicyEngineConfig(
|
||||
}
|
||||
|
||||
// MCP servers that are explicitly allowed in settings.mcp.allowed
|
||||
// Priority: 3.1 (user tier - persistent general server allows)
|
||||
// Priority: ALLOWED_MCP_SERVER_PRIORITY (user tier - persistent general server allows)
|
||||
if (settings.mcp?.allowed) {
|
||||
for (const serverName of settings.mcp.allowed) {
|
||||
rules.push({
|
||||
toolName: `${serverName}__*`,
|
||||
decision: PolicyDecision.ALLOW,
|
||||
priority: 3.1,
|
||||
priority: ALLOWED_MCP_SERVER_PRIORITY,
|
||||
source: 'Settings (MCP Allowed)',
|
||||
});
|
||||
}
|
||||
@@ -381,6 +396,7 @@ interface TomlRule {
|
||||
export function createPolicyUpdater(
|
||||
policyEngine: PolicyEngine,
|
||||
messageBus: MessageBus,
|
||||
storage: Storage,
|
||||
) {
|
||||
// Use a sequential queue for persistence to avoid lost updates from concurrent events.
|
||||
let persistenceQueue = Promise.resolve();
|
||||
@@ -400,10 +416,7 @@ export function createPolicyUpdater(
|
||||
policyEngine.addRule({
|
||||
toolName,
|
||||
decision: PolicyDecision.ALLOW,
|
||||
// User tier (3) + high priority (950/1000) = 3.95
|
||||
// This ensures user "always allow" selections are high priority
|
||||
// but still lose to admin policies (4.xxx) and settings excludes (300)
|
||||
priority: 3.95,
|
||||
priority: ALWAYS_ALLOW_PRIORITY,
|
||||
argsPattern: new RegExp(pattern),
|
||||
source: 'Dynamic (Confirmed)',
|
||||
});
|
||||
@@ -425,10 +438,7 @@ export function createPolicyUpdater(
|
||||
policyEngine.addRule({
|
||||
toolName,
|
||||
decision: PolicyDecision.ALLOW,
|
||||
// User tier (3) + high priority (950/1000) = 3.95
|
||||
// This ensures user "always allow" selections are high priority
|
||||
// but still lose to admin policies (4.xxx) and settings excludes (300)
|
||||
priority: 3.95,
|
||||
priority: ALWAYS_ALLOW_PRIORITY,
|
||||
argsPattern,
|
||||
source: 'Dynamic (Confirmed)',
|
||||
});
|
||||
@@ -437,9 +447,9 @@ export function createPolicyUpdater(
|
||||
if (message.persist) {
|
||||
persistenceQueue = persistenceQueue.then(async () => {
|
||||
try {
|
||||
const userPoliciesDir = Storage.getUserPoliciesDir();
|
||||
await fs.mkdir(userPoliciesDir, { recursive: true });
|
||||
const policyFile = path.join(userPoliciesDir, 'auto-saved.toml');
|
||||
const workspacePoliciesDir = storage.getWorkspacePoliciesDir();
|
||||
await fs.mkdir(workspacePoliciesDir, { recursive: true });
|
||||
const policyFile = storage.getAutoSavedPolicyPath();
|
||||
|
||||
// Read existing file
|
||||
let existingData: { rule?: TomlRule[] } = {};
|
||||
|
||||
@@ -15,11 +15,11 @@ import {
|
||||
} from 'vitest';
|
||||
import * as fs from 'node:fs/promises';
|
||||
import * as path from 'node:path';
|
||||
import { createPolicyUpdater } from './config.js';
|
||||
import { createPolicyUpdater, ALWAYS_ALLOW_PRIORITY } from './config.js';
|
||||
import { PolicyEngine } from './policy-engine.js';
|
||||
import { MessageBus } from '../confirmation-bus/message-bus.js';
|
||||
import { MessageBusType } from '../confirmation-bus/types.js';
|
||||
import { Storage } from '../config/storage.js';
|
||||
import { Storage, AUTO_SAVED_POLICY_FILENAME } from '../config/storage.js';
|
||||
import { ApprovalMode } from './types.js';
|
||||
|
||||
vi.mock('node:fs/promises');
|
||||
@@ -28,6 +28,7 @@ vi.mock('../config/storage.js');
|
||||
describe('createPolicyUpdater', () => {
|
||||
let policyEngine: PolicyEngine;
|
||||
let messageBus: MessageBus;
|
||||
let mockStorage: Storage;
|
||||
|
||||
beforeEach(() => {
|
||||
policyEngine = new PolicyEngine({
|
||||
@@ -36,6 +37,7 @@ describe('createPolicyUpdater', () => {
|
||||
approvalMode: ApprovalMode.DEFAULT,
|
||||
});
|
||||
messageBus = new MessageBus(policyEngine);
|
||||
mockStorage = new Storage('/mock/project');
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
@@ -44,10 +46,17 @@ describe('createPolicyUpdater', () => {
|
||||
});
|
||||
|
||||
it('should persist policy when persist flag is true', async () => {
|
||||
createPolicyUpdater(policyEngine, messageBus);
|
||||
createPolicyUpdater(policyEngine, messageBus, mockStorage);
|
||||
|
||||
const userPoliciesDir = '/mock/user/policies';
|
||||
vi.spyOn(Storage, 'getUserPoliciesDir').mockReturnValue(userPoliciesDir);
|
||||
const workspacePoliciesDir = '/mock/project/.gemini/policies';
|
||||
const policyFile = path.join(
|
||||
workspacePoliciesDir,
|
||||
AUTO_SAVED_POLICY_FILENAME,
|
||||
);
|
||||
vi.spyOn(mockStorage, 'getWorkspacePoliciesDir').mockReturnValue(
|
||||
workspacePoliciesDir,
|
||||
);
|
||||
vi.spyOn(mockStorage, 'getAutoSavedPolicyPath').mockReturnValue(policyFile);
|
||||
(fs.mkdir as unknown as Mock).mockResolvedValue(undefined);
|
||||
(fs.readFile as unknown as Mock).mockRejectedValue(
|
||||
new Error('File not found'),
|
||||
@@ -70,8 +79,8 @@ describe('createPolicyUpdater', () => {
|
||||
// Wait for async operations (microtasks)
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
|
||||
expect(Storage.getUserPoliciesDir).toHaveBeenCalled();
|
||||
expect(fs.mkdir).toHaveBeenCalledWith(userPoliciesDir, {
|
||||
expect(mockStorage.getWorkspacePoliciesDir).toHaveBeenCalled();
|
||||
expect(fs.mkdir).toHaveBeenCalledWith(workspacePoliciesDir, {
|
||||
recursive: true,
|
||||
});
|
||||
|
||||
@@ -85,12 +94,12 @@ describe('createPolicyUpdater', () => {
|
||||
);
|
||||
expect(fs.rename).toHaveBeenCalledWith(
|
||||
expect.stringMatching(/\.tmp$/),
|
||||
path.join(userPoliciesDir, 'auto-saved.toml'),
|
||||
policyFile,
|
||||
);
|
||||
});
|
||||
|
||||
it('should not persist policy when persist flag is false or undefined', async () => {
|
||||
createPolicyUpdater(policyEngine, messageBus);
|
||||
createPolicyUpdater(policyEngine, messageBus, mockStorage);
|
||||
|
||||
await messageBus.publish({
|
||||
type: MessageBusType.UPDATE_POLICY,
|
||||
@@ -104,10 +113,17 @@ describe('createPolicyUpdater', () => {
|
||||
});
|
||||
|
||||
it('should persist policy with commandPrefix when provided', async () => {
|
||||
createPolicyUpdater(policyEngine, messageBus);
|
||||
createPolicyUpdater(policyEngine, messageBus, mockStorage);
|
||||
|
||||
const userPoliciesDir = '/mock/user/policies';
|
||||
vi.spyOn(Storage, 'getUserPoliciesDir').mockReturnValue(userPoliciesDir);
|
||||
const workspacePoliciesDir = '/mock/project/.gemini/policies';
|
||||
const policyFile = path.join(
|
||||
workspacePoliciesDir,
|
||||
AUTO_SAVED_POLICY_FILENAME,
|
||||
);
|
||||
vi.spyOn(mockStorage, 'getWorkspacePoliciesDir').mockReturnValue(
|
||||
workspacePoliciesDir,
|
||||
);
|
||||
vi.spyOn(mockStorage, 'getAutoSavedPolicyPath').mockReturnValue(policyFile);
|
||||
(fs.mkdir as unknown as Mock).mockResolvedValue(undefined);
|
||||
(fs.readFile as unknown as Mock).mockRejectedValue(
|
||||
new Error('File not found'),
|
||||
@@ -136,7 +152,7 @@ describe('createPolicyUpdater', () => {
|
||||
const rules = policyEngine.getRules();
|
||||
const addedRule = rules.find((r) => r.toolName === toolName);
|
||||
expect(addedRule).toBeDefined();
|
||||
expect(addedRule?.priority).toBe(3.95);
|
||||
expect(addedRule?.priority).toBe(ALWAYS_ALLOW_PRIORITY);
|
||||
expect(addedRule?.argsPattern).toEqual(
|
||||
new RegExp(`"command":"git\\ status(?:[\\s"]|\\\\")`),
|
||||
);
|
||||
@@ -150,10 +166,17 @@ describe('createPolicyUpdater', () => {
|
||||
});
|
||||
|
||||
it('should persist policy with mcpName and toolName when provided', async () => {
|
||||
createPolicyUpdater(policyEngine, messageBus);
|
||||
createPolicyUpdater(policyEngine, messageBus, mockStorage);
|
||||
|
||||
const userPoliciesDir = '/mock/user/policies';
|
||||
vi.spyOn(Storage, 'getUserPoliciesDir').mockReturnValue(userPoliciesDir);
|
||||
const workspacePoliciesDir = '/mock/project/.gemini/policies';
|
||||
const policyFile = path.join(
|
||||
workspacePoliciesDir,
|
||||
AUTO_SAVED_POLICY_FILENAME,
|
||||
);
|
||||
vi.spyOn(mockStorage, 'getWorkspacePoliciesDir').mockReturnValue(
|
||||
workspacePoliciesDir,
|
||||
);
|
||||
vi.spyOn(mockStorage, 'getAutoSavedPolicyPath').mockReturnValue(policyFile);
|
||||
(fs.mkdir as unknown as Mock).mockResolvedValue(undefined);
|
||||
(fs.readFile as unknown as Mock).mockRejectedValue(
|
||||
new Error('File not found'),
|
||||
@@ -189,10 +212,17 @@ describe('createPolicyUpdater', () => {
|
||||
});
|
||||
|
||||
it('should escape special characters in toolName and mcpName', async () => {
|
||||
createPolicyUpdater(policyEngine, messageBus);
|
||||
createPolicyUpdater(policyEngine, messageBus, mockStorage);
|
||||
|
||||
const userPoliciesDir = '/mock/user/policies';
|
||||
vi.spyOn(Storage, 'getUserPoliciesDir').mockReturnValue(userPoliciesDir);
|
||||
const workspacePoliciesDir = '/mock/project/.gemini/policies';
|
||||
const policyFile = path.join(
|
||||
workspacePoliciesDir,
|
||||
AUTO_SAVED_POLICY_FILENAME,
|
||||
);
|
||||
vi.spyOn(mockStorage, 'getWorkspacePoliciesDir').mockReturnValue(
|
||||
workspacePoliciesDir,
|
||||
);
|
||||
vi.spyOn(mockStorage, 'getAutoSavedPolicyPath').mockReturnValue(policyFile);
|
||||
(fs.mkdir as unknown as Mock).mockResolvedValue(undefined);
|
||||
(fs.readFile as unknown as Mock).mockRejectedValue(
|
||||
new Error('File not found'),
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import * as fs from 'node:fs/promises';
|
||||
import { createPolicyUpdater } from './config.js';
|
||||
import { createPolicyUpdater, ALWAYS_ALLOW_PRIORITY } from './config.js';
|
||||
import { PolicyEngine } from './policy-engine.js';
|
||||
import { MessageBus } from '../confirmation-bus/message-bus.js';
|
||||
import { MessageBusType } from '../confirmation-bus/types.js';
|
||||
@@ -41,6 +41,7 @@ interface TestableShellToolInvocation {
|
||||
describe('createPolicyUpdater', () => {
|
||||
let policyEngine: PolicyEngine;
|
||||
let messageBus: MessageBus;
|
||||
let mockStorage: Storage;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
@@ -48,8 +49,9 @@ describe('createPolicyUpdater', () => {
|
||||
vi.spyOn(policyEngine, 'addRule');
|
||||
|
||||
messageBus = new MessageBus(policyEngine);
|
||||
vi.spyOn(Storage, 'getUserPoliciesDir').mockReturnValue(
|
||||
'/mock/user/policies',
|
||||
mockStorage = new Storage('/mock/project');
|
||||
vi.spyOn(mockStorage, 'getWorkspacePoliciesDir').mockReturnValue(
|
||||
'/mock/project/.gemini/policies',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -58,7 +60,7 @@ describe('createPolicyUpdater', () => {
|
||||
});
|
||||
|
||||
it('should add multiple rules when commandPrefix is an array', async () => {
|
||||
createPolicyUpdater(policyEngine, messageBus);
|
||||
createPolicyUpdater(policyEngine, messageBus, mockStorage);
|
||||
|
||||
await messageBus.publish({
|
||||
type: MessageBusType.UPDATE_POLICY,
|
||||
@@ -72,6 +74,7 @@ describe('createPolicyUpdater', () => {
|
||||
1,
|
||||
expect.objectContaining({
|
||||
toolName: 'run_shell_command',
|
||||
priority: ALWAYS_ALLOW_PRIORITY,
|
||||
argsPattern: new RegExp('"command":"echo(?:[\\s"]|\\\\")'),
|
||||
}),
|
||||
);
|
||||
@@ -79,13 +82,14 @@ describe('createPolicyUpdater', () => {
|
||||
2,
|
||||
expect.objectContaining({
|
||||
toolName: 'run_shell_command',
|
||||
priority: ALWAYS_ALLOW_PRIORITY,
|
||||
argsPattern: new RegExp('"command":"ls(?:[\\s"]|\\\\")'),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should add a single rule when commandPrefix is a string', async () => {
|
||||
createPolicyUpdater(policyEngine, messageBus);
|
||||
createPolicyUpdater(policyEngine, messageBus, mockStorage);
|
||||
|
||||
await messageBus.publish({
|
||||
type: MessageBusType.UPDATE_POLICY,
|
||||
@@ -98,13 +102,14 @@ describe('createPolicyUpdater', () => {
|
||||
expect(policyEngine.addRule).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
toolName: 'run_shell_command',
|
||||
priority: ALWAYS_ALLOW_PRIORITY,
|
||||
argsPattern: new RegExp('"command":"git(?:[\\s"]|\\\\")'),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should persist multiple rules correctly to TOML', async () => {
|
||||
createPolicyUpdater(policyEngine, messageBus);
|
||||
createPolicyUpdater(policyEngine, messageBus, mockStorage);
|
||||
vi.mocked(fs.readFile).mockRejectedValue({ code: 'ENOENT' });
|
||||
vi.mocked(fs.mkdir).mockResolvedValue(undefined);
|
||||
|
||||
@@ -139,7 +144,7 @@ describe('createPolicyUpdater', () => {
|
||||
});
|
||||
|
||||
it('should reject unsafe regex patterns', async () => {
|
||||
createPolicyUpdater(policyEngine, messageBus);
|
||||
createPolicyUpdater(policyEngine, messageBus, mockStorage);
|
||||
|
||||
await messageBus.publish({
|
||||
type: MessageBusType.UPDATE_POLICY,
|
||||
|
||||
@@ -229,6 +229,7 @@ export class CheckerRunner {
|
||||
|
||||
// Try to parse the output
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const rawResult = JSON.parse(stdout);
|
||||
const result = SafetyCheckResultSchema.parse(rawResult);
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ async function generateAndSaveSummary(
|
||||
): Promise<void> {
|
||||
// Read session file
|
||||
const content = await fs.readFile(sessionPath, 'utf-8');
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const conversation: ConversationRecord = JSON.parse(content);
|
||||
|
||||
// Skip if summary already exists
|
||||
@@ -69,6 +70,7 @@ async function generateAndSaveSummary(
|
||||
|
||||
// Re-read the file before writing to handle race conditions
|
||||
const freshContent = await fs.readFile(sessionPath, 'utf-8');
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const freshConversation: ConversationRecord = JSON.parse(freshContent);
|
||||
|
||||
// Check if summary was added by another process
|
||||
@@ -127,6 +129,7 @@ export async function getPreviousSession(
|
||||
|
||||
try {
|
||||
const content = await fs.readFile(filePath, 'utf-8');
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const conversation: ConversationRecord = JSON.parse(content);
|
||||
|
||||
if (conversation.summary) {
|
||||
|
||||
@@ -568,6 +568,7 @@ export class ShellExecutionService {
|
||||
const guardedCommand = ensurePromptvarsDisabled(commandToExecute, shell);
|
||||
const args = [...argsPrefix, guardedCommand];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const ptyProcess = ptyInfo.module.spawn(executable, args, {
|
||||
cwd,
|
||||
name: 'xterm-256color',
|
||||
@@ -598,6 +599,7 @@ export class ShellExecutionService {
|
||||
headlessTerminal.scrollToTop();
|
||||
|
||||
this.activePtys.set(ptyProcess.pid, {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
ptyProcess,
|
||||
headlessTerminal,
|
||||
maxSerializedLines: shellExecutionConfig.maxSerializedLines,
|
||||
@@ -831,6 +833,7 @@ export class ShellExecutionService {
|
||||
signal: signal ?? null,
|
||||
error,
|
||||
aborted: abortSignal.aborted,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
pid: ptyProcess.pid,
|
||||
executionMethod: ptyInfo?.name ?? 'node-pty',
|
||||
});
|
||||
@@ -862,9 +865,11 @@ export class ShellExecutionService {
|
||||
const abortHandler = async () => {
|
||||
if (ptyProcess.pid && !exited) {
|
||||
await killProcessGroup({
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
pid: ptyProcess.pid,
|
||||
escalate: true,
|
||||
isExited: () => exited,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
pty: ptyProcess,
|
||||
});
|
||||
}
|
||||
@@ -873,6 +878,7 @@ export class ShellExecutionService {
|
||||
abortSignal.addEventListener('abort', abortHandler, { once: true });
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
return { pid: ptyProcess.pid, result };
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
|
||||
@@ -716,6 +716,7 @@ export class ClearcutLogger {
|
||||
event.function_name === ASK_USER_TOOL_NAME &&
|
||||
event.metadata['ask_user']
|
||||
) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const askUser = event.metadata['ask_user'];
|
||||
const askUserMapping: { [key: string]: EventMetadataKey } = {
|
||||
question_types: EventMetadataKey.GEMINI_CLI_ASK_USER_QUESTION_TYPES,
|
||||
|
||||
@@ -36,11 +36,13 @@ describe('Circular Reference Integration Test', () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const socketLike: any = {
|
||||
_httpMessage: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
agent: proxyAgentLike,
|
||||
socket: null,
|
||||
},
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
socketLike._httpMessage.socket = socketLike; // Create circular reference
|
||||
proxyAgentLike.sockets['cloudcode-pa.googleapis.com:443'] = [socketLike];
|
||||
|
||||
@@ -49,6 +51,7 @@ describe('Circular Reference Integration Test', () => {
|
||||
error: new Error('Network error'),
|
||||
function_args: {
|
||||
filePath: '/test/file.txt',
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
httpAgent: proxyAgentLike, // This would cause the circular reference
|
||||
},
|
||||
};
|
||||
|
||||
@@ -39,8 +39,10 @@ describe('Circular Reference Handling', () => {
|
||||
sockets: {},
|
||||
agent: null,
|
||||
};
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
circularObject.agent = circularObject; // Create circular reference
|
||||
circularObject.sockets['test-host'] = [
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
{ _httpMessage: { agent: circularObject } },
|
||||
];
|
||||
|
||||
@@ -48,6 +50,7 @@ describe('Circular Reference Handling', () => {
|
||||
const mockRequest: ToolCallRequestInfo = {
|
||||
callId: 'test-call-id',
|
||||
name: 'ReadFile',
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
args: circularObject, // This would cause the original error
|
||||
isClientInitiated: false,
|
||||
prompt_id: 'test-prompt-id',
|
||||
|
||||
@@ -145,12 +145,14 @@ export function logToolCall(config: Config, event: ToolCallEvent): void {
|
||||
});
|
||||
|
||||
if (event.metadata) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const added = event.metadata['model_added_lines'];
|
||||
if (typeof added === 'number' && added > 0) {
|
||||
recordLinesChanged(config, added, 'added', {
|
||||
function_name: event.function_name,
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const removed = event.metadata['model_removed_lines'];
|
||||
if (typeof removed === 'number' && removed > 0) {
|
||||
recordLinesChanged(config, removed, 'removed', {
|
||||
|
||||
@@ -22,6 +22,7 @@ import type {
|
||||
Prompt,
|
||||
ReadResourceResult,
|
||||
Resource,
|
||||
Tool as McpTool,
|
||||
} from '@modelcontextprotocol/sdk/types.js';
|
||||
import {
|
||||
ListResourcesResultSchema,
|
||||
@@ -31,7 +32,6 @@ import {
|
||||
ToolListChangedNotificationSchema,
|
||||
PromptListChangedNotificationSchema,
|
||||
ProgressNotificationSchema,
|
||||
type Tool as McpTool,
|
||||
} from '@modelcontextprotocol/sdk/types.js';
|
||||
import { ApprovalMode, PolicyDecision } from '../policy/types.js';
|
||||
import { parse } from 'shell-quote';
|
||||
@@ -1996,6 +1996,7 @@ export async function createTransport(
|
||||
// The `XcodeMcpBridgeFixTransport` wrapper hides the underlying `StdioClientTransport`,
|
||||
// which exposes `stderr` for debug logging. We need to unwrap it to attach the listener.
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const underlyingTransport =
|
||||
transport instanceof XcodeMcpBridgeFixTransport
|
||||
? // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-type-assertion
|
||||
@@ -2007,6 +2008,7 @@ export async function createTransport(
|
||||
underlyingTransport.stderr
|
||||
) {
|
||||
underlyingTransport.stderr.on('data', (data) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const stderrStr = data.toString().trim();
|
||||
debugLogger.debug(
|
||||
`[DEBUG] [MCP STDERR (${mcpServerName})]: `,
|
||||
|
||||
@@ -481,8 +481,10 @@ class GrepToolInvocation extends BaseToolInvocation<
|
||||
basePath: string,
|
||||
): GrepMatch | null {
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const json = JSON.parse(line);
|
||||
if (json.type === 'match' || json.type === 'context') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const data = json.data;
|
||||
// Defensive check: ensure text properties exist (skips binary/invalid encoding)
|
||||
if (data.path?.text && data.lines?.text) {
|
||||
@@ -500,7 +502,9 @@ class GrepToolInvocation extends BaseToolInvocation<
|
||||
|
||||
return {
|
||||
filePath: relativeFilePath || path.basename(absoluteFilePath),
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
lineNumber: data.line_number,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
line: data.lines.text.trimEnd(),
|
||||
isContext: json.type === 'context',
|
||||
};
|
||||
|
||||
@@ -390,6 +390,7 @@ export class ToolRegistry {
|
||||
|
||||
// execute discovery command and extract function declarations (w/ or w/o "tool" wrappers)
|
||||
const functions: FunctionDeclaration[] = [];
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const discoveredItems = JSON.parse(stdout.trim());
|
||||
|
||||
if (!discoveredItems || !Array.isArray(discoveredItems)) {
|
||||
|
||||
@@ -75,7 +75,7 @@ export class XcodeMcpBridgeFixTransport
|
||||
// We can cast because we verified 'result' is in response,
|
||||
// but TS might still be picky if the type is a strict union.
|
||||
// Let's treat it safely.
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-type-assertion
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-type-assertion, @typescript-eslint/no-unsafe-assignment
|
||||
const result = response.result as any;
|
||||
|
||||
// Check if we have content but missing structuredContent
|
||||
@@ -85,12 +85,15 @@ export class XcodeMcpBridgeFixTransport
|
||||
result.content.length > 0 &&
|
||||
!result.structuredContent
|
||||
) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const firstItem = result.content[0];
|
||||
if (firstItem.type === 'text' && typeof firstItem.text === 'string') {
|
||||
try {
|
||||
// Attempt to parse the text as JSON
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const parsed = JSON.parse(firstItem.text);
|
||||
// If successful, populate structuredContent
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
result.structuredContent = parsed;
|
||||
} catch (_) {
|
||||
// Ignored: Content is likely plain text, not JSON.
|
||||
|
||||
@@ -145,6 +145,7 @@ class RecursiveFileSearch implements FileSearch {
|
||||
if (pattern.includes('*') || !this.fzf) {
|
||||
filteredCandidates = await filter(candidates, pattern, options.signal);
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
filteredCandidates = await this.fzf
|
||||
.find(pattern)
|
||||
.then((results: Array<FzfResultItem<string>>) =>
|
||||
|
||||
@@ -23,12 +23,16 @@ export const getPty = async (): Promise<PtyImplementation> => {
|
||||
}
|
||||
try {
|
||||
const lydell = '@lydell/node-pty';
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const module = await import(lydell);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
return { module, name: 'lydell-node-pty' };
|
||||
} catch (_e) {
|
||||
try {
|
||||
const nodePty = 'node-pty';
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const module = await import(nodePty);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
return { module, name: 'node-pty' };
|
||||
} catch (_e2) {
|
||||
return null;
|
||||
|
||||
@@ -166,10 +166,12 @@ export function parseGoogleApiError(error: unknown): GoogleApiError | null {
|
||||
depth < maxDepth
|
||||
) {
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const parsedMessage = JSON.parse(
|
||||
currentError.message.replace(/\u00A0/g, '').replace(/\n/g, ' '),
|
||||
);
|
||||
if (parsedMessage.error) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
currentError = parsedMessage.error;
|
||||
depth++;
|
||||
} else {
|
||||
@@ -243,6 +245,7 @@ function fromGaxiosError(errorObj: object): ErrorShape | undefined {
|
||||
|
||||
if (typeof data === 'string') {
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
data = JSON.parse(data);
|
||||
} catch (_) {
|
||||
// Not a JSON string, can't parse.
|
||||
@@ -250,6 +253,7 @@ function fromGaxiosError(errorObj: object): ErrorShape | undefined {
|
||||
}
|
||||
|
||||
if (Array.isArray(data) && data.length > 0) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
data = data[0];
|
||||
}
|
||||
|
||||
@@ -288,6 +292,7 @@ function fromApiError(errorObj: object): ErrorShape | undefined {
|
||||
|
||||
if (typeof data === 'string') {
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
data = JSON.parse(data);
|
||||
} catch (_) {
|
||||
// Not a JSON string, can't parse.
|
||||
@@ -297,6 +302,7 @@ function fromApiError(errorObj: object): ErrorShape | undefined {
|
||||
const lastBrace = data.lastIndexOf('}');
|
||||
if (firstBrace !== -1 && lastBrace !== -1 && lastBrace > firstBrace) {
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
data = JSON.parse(data.substring(firstBrace, lastBrace + 1));
|
||||
} catch (__) {
|
||||
// Still failed
|
||||
@@ -307,6 +313,7 @@ function fromApiError(errorObj: object): ErrorShape | undefined {
|
||||
}
|
||||
|
||||
if (Array.isArray(data) && data.length > 0) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
data = data[0];
|
||||
}
|
||||
|
||||
|
||||
@@ -127,6 +127,7 @@ async function getGeminiMdFilePathsInternal(
|
||||
result.value.global.forEach((p) => globalPaths.add(p));
|
||||
result.value.project.forEach((p) => projectPaths.add(p));
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const error = result.reason;
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
logger.error(`Error discovering files in directory: ${message}`);
|
||||
@@ -299,6 +300,7 @@ export async function readGeminiMdFiles(
|
||||
} else {
|
||||
// This case shouldn't happen since we catch all errors above,
|
||||
// but handle it for completeness
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const error = result.reason;
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
logger.error(`Unexpected error processing file: ${message}`);
|
||||
|
||||
@@ -37,6 +37,7 @@ export function safeJsonStringify(
|
||||
function removeEmptyObjects(data: any): object {
|
||||
const cleanedObject: { [key: string]: unknown } = {};
|
||||
for (const k in data) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const v = data[k];
|
||||
if (v !== null && v !== undefined && typeof v === 'boolean') {
|
||||
cleanedObject[k] = v;
|
||||
|
||||
@@ -12,9 +12,9 @@ import * as addFormats from 'ajv-formats';
|
||||
import { debugLogger } from './debugLogger.js';
|
||||
|
||||
// Ajv's ESM/CJS interop: use 'any' for compatibility as recommended by Ajv docs
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-type-assertion
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-type-assertion, @typescript-eslint/no-unsafe-assignment
|
||||
const AjvClass = (AjvPkg as any).default || AjvPkg;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-type-assertion
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-type-assertion, @typescript-eslint/no-unsafe-assignment
|
||||
const Ajv2020Class = (Ajv2020Pkg as any).default || Ajv2020Pkg;
|
||||
|
||||
const ajvOptions = {
|
||||
@@ -29,12 +29,14 @@ const ajvOptions = {
|
||||
};
|
||||
|
||||
// Draft-07 validator (default)
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const ajvDefault: Ajv = new AjvClass(ajvOptions);
|
||||
|
||||
// Draft-2020-12 validator for MCP servers using rmcp
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const ajv2020: Ajv = new Ajv2020Class(ajvOptions);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-type-assertion
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-type-assertion, @typescript-eslint/no-unsafe-assignment
|
||||
const addFormatsFunc = (addFormats as any).default || addFormats;
|
||||
addFormatsFunc(ajvDefault);
|
||||
addFormatsFunc(ajv2020);
|
||||
|
||||
@@ -479,6 +479,7 @@ function parsePowerShellCommandDetails(
|
||||
hasRedirection?: boolean;
|
||||
} | null = null;
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
parsed = JSON.parse(output);
|
||||
} catch {
|
||||
return { details: [], hasError: true };
|
||||
|
||||
@@ -88,6 +88,7 @@ export function createWorkingStdio() {
|
||||
if (prop === 'write') {
|
||||
return writeToStdout;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const value = Reflect.get(target, prop, receiver);
|
||||
if (typeof value === 'function') {
|
||||
return value.bind(target);
|
||||
@@ -101,6 +102,7 @@ export function createWorkingStdio() {
|
||||
if (prop === 'write') {
|
||||
return writeToStderr;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const value = Reflect.get(target, prop, receiver);
|
||||
if (typeof value === 'function') {
|
||||
return value.bind(target);
|
||||
|
||||
@@ -30,6 +30,7 @@ export class UserAccountManager {
|
||||
return defaultState;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const parsed = JSON.parse(content);
|
||||
|
||||
// Inlined validation logic
|
||||
@@ -50,7 +51,9 @@ export class UserAccountManager {
|
||||
}
|
||||
|
||||
return {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
active: parsed.active ?? null,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
old: parsed.old ?? [],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -260,6 +260,7 @@ export class DevTools extends EventEmitter {
|
||||
|
||||
ws.on('message', (data: Buffer) => {
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const message = JSON.parse(data.toString());
|
||||
|
||||
// Handle registration first
|
||||
|
||||
@@ -210,6 +210,7 @@ export class GeminiCliSession {
|
||||
const toolCall = event.value;
|
||||
let args = toolCall.args;
|
||||
if (typeof args === 'string') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
args = JSON.parse(args);
|
||||
}
|
||||
toolCallsToSchedule.push({
|
||||
@@ -238,6 +239,7 @@ export class GeminiCliSession {
|
||||
};
|
||||
|
||||
const originalRegistry = this.config.getToolRegistry();
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const scopedRegistry: ToolRegistry = Object.create(originalRegistry);
|
||||
scopedRegistry.getTool = (name: string) => {
|
||||
const tool = originalRegistry.getTool(name);
|
||||
|
||||
@@ -38,6 +38,7 @@ async function checkForUpdates(
|
||||
isManagedExtensionSurface: boolean,
|
||||
) {
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const currentVersion = context.extension.packageJSON.version;
|
||||
|
||||
// Fetch extension details from the VSCode Marketplace.
|
||||
@@ -75,9 +76,12 @@ async function checkForUpdates(
|
||||
return;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const data = await response.json();
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const extension = data?.results?.[0]?.extensions?.[0];
|
||||
// The versions are sorted by date, so the first one is the latest.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const latestVersion = extension?.versions?.[0]?.version;
|
||||
|
||||
if (
|
||||
|
||||
Reference in New Issue
Block a user