Disallow and suppress unsafe assignment (#19736)

This commit is contained in:
Christian Gunderman
2026-02-20 22:28:55 +00:00
committed by GitHub
parent b746524a1b
commit 58d637f919
71 changed files with 149 additions and 22 deletions
+1
View File
@@ -200,6 +200,7 @@ export default tseslint.config(
ignores: ['**/*.test.ts', '**/*.test.tsx'], ignores: ['**/*.test.ts', '**/*.test.tsx'],
rules: { rules: {
'@typescript-eslint/no-unsafe-type-assertion': 'error', '@typescript-eslint/no-unsafe-type-assertion': 'error',
'@typescript-eslint/no-unsafe-assignment': 'error',
}, },
}, },
{ {
@@ -69,6 +69,7 @@ export class RestoreCommand implements Command {
throw error; throw error;
} }
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const toolCallData = JSON.parse(data); const toolCallData = JSON.parse(data);
const ToolCallDataSchema = getToolCallDataSchema(); const ToolCallDataSchema = getToolCallDataSchema();
const parseResult = ToolCallDataSchema.safeParse(toolCallData); const parseResult = ToolCallDataSchema.safeParse(toolCallData);
+2 -1
View File
@@ -7,8 +7,8 @@
import * as fs from 'node:fs'; import * as fs from 'node:fs';
import * as path from 'node:path'; import * as path from 'node:path';
import type { MCPServerConfig } from '@google/gemini-cli-core';
import { import {
type MCPServerConfig,
debugLogger, debugLogger,
GEMINI_DIR, GEMINI_DIR,
getErrorMessage, getErrorMessage,
@@ -122,6 +122,7 @@ export function loadSettings(workspaceDir: string): Settings {
function resolveEnvVarsInString(value: string): string { function resolveEnvVarsInString(value: string): string {
const envVarRegex = /\$(?:(\w+)|{([^}]+)})/g; // Find $VAR_NAME or ${VAR_NAME} const envVarRegex = /\$(?:(\w+)|{([^}]+)})/g; // Find $VAR_NAME or ${VAR_NAME}
return value.replace(envVarRegex, (match, varName1, varName2) => { return value.replace(envVarRegex, (match, varName1, varName2) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const varName = varName1 || varName2; const varName = varName1 || varName2;
if (process && process.env && typeof process.env[varName] === 'string') { if (process && process.env && typeof process.env[varName] === 'string') {
return process.env[varName]; return process.env[varName];
+8 -3
View File
@@ -7,8 +7,8 @@
import express from 'express'; import express from 'express';
import type { AgentCard, Message } from '@a2a-js/sdk'; import type { AgentCard, Message } from '@a2a-js/sdk';
import type { TaskStore } from '@a2a-js/sdk/server';
import { import {
type TaskStore,
DefaultRequestHandler, DefaultRequestHandler,
InMemoryTaskStore, InMemoryTaskStore,
DefaultExecutionEventBus, DefaultExecutionEventBus,
@@ -25,9 +25,12 @@ import { loadConfig, loadEnvironment, setTargetDir } from '../config/config.js';
import { loadSettings } from '../config/settings.js'; import { loadSettings } from '../config/settings.js';
import { loadExtensions } from '../config/extension.js'; import { loadExtensions } from '../config/extension.js';
import { commandRegistry } from '../commands/command-registry.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 type { Command, CommandArgument } from '../commands/types.js';
import { GitService } from '@google/gemini-cli-core';
type CommandResponse = { type CommandResponse = {
name: string; name: string;
@@ -88,6 +91,7 @@ async function handleExecuteCommand(
}, },
) { ) {
logger.info('[CoreAgent] Received /executeCommand request: ', req.body); logger.info('[CoreAgent] Received /executeCommand request: ', req.body);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const { command, args } = req.body; const { command, args } = req.body;
try { try {
if (typeof command !== 'string') { if (typeof command !== 'string') {
@@ -211,6 +215,7 @@ export async function createApp() {
const agentSettings = req.body.agentSettings as const agentSettings = req.body.agentSettings as
| AgentSettings | AgentSettings
| undefined; | undefined;
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const contextId = req.body.contextId || uuidv4(); const contextId = req.body.contextId || uuidv4();
const wrapper = await agentExecutor.createTask( const wrapper = await agentExecutor.createTask(
taskId, taskId,
@@ -246,6 +246,7 @@ export class GCSTaskStore implements TaskStore {
} }
const [compressedMetadata] = await metadataFile.download(); const [compressedMetadata] = await metadataFile.download();
const jsonData = gunzipSync(compressedMetadata).toString(); const jsonData = gunzipSync(compressedMetadata).toString();
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const loadedMetadata = JSON.parse(jsonData); const loadedMetadata = JSON.parse(jsonData);
logger.info(`Task ${taskId} metadata loaded from GCS.`); logger.info(`Task ${taskId} metadata loaded from GCS.`);
@@ -282,12 +283,14 @@ export class GCSTaskStore implements TaskStore {
return { return {
id: taskId, id: taskId,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
contextId: loadedMetadata._contextId || uuidv4(), contextId: loadedMetadata._contextId || uuidv4(),
kind: 'task', kind: 'task',
status: { status: {
state: persistedState._taskState, state: persistedState._taskState,
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
}, },
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
metadata: loadedMetadata, metadata: loadedMetadata,
history: [], history: [],
artifacts: [], artifacts: [],
@@ -866,6 +866,7 @@ Would you like to attempt to install via "git clone" instead?`,
try { try {
const hooksContent = await fs.promises.readFile(hooksFilePath, 'utf-8'); const hooksContent = await fs.promises.readFile(hooksFilePath, 'utf-8');
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const rawHooks = JSON.parse(hooksContent); const rawHooks = JSON.parse(hooksContent);
if ( if (
@@ -81,6 +81,7 @@ export class ExtensionRegistryClient {
`${ext.extensionName} ${ext.extensionDescription} ${ext.fullName}`, `${ext.extensionName} ${ext.extensionDescription} ${ext.fullName}`,
fuzzy: true, fuzzy: true,
}); });
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const results = await fzf.find(query); const results = await fzf.find(query);
return results.map((r: { item: RegistryExtension }) => r.item); return results.map((r: { item: RegistryExtension }) => r.item);
} }
+2 -1
View File
@@ -217,13 +217,14 @@ export class AppRig {
} }
private stubRefreshAuth() { 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; const gcConfig = this.config as any;
gcConfig.refreshAuth = async (authMethod: AuthType) => { gcConfig.refreshAuth = async (authMethod: AuthType) => {
gcConfig.modelAvailabilityService.reset(); gcConfig.modelAvailabilityService.reset();
const newContentGeneratorConfig = { const newContentGeneratorConfig = {
authType: authMethod, authType: authMethod,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
proxy: gcConfig.getProxy(), proxy: gcConfig.getProxy(),
apiKey: process.env['GEMINI_API_KEY'] || 'test-api-key', 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]/; const invalidCharsRegex = /[\b\x1b]/;
function toHaveOnlyValidCharacters(this: Assertion, buffer: TextBuffer) { 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; const { isNot } = this as any;
let pass = true; let pass = true;
const invalidLines: Array<{ line: number; content: string }> = []; const invalidLines: Array<{ line: number; content: string }> = [];
@@ -45,7 +45,7 @@ export const createMockCommandContext = (
forScope: vi.fn().mockReturnValue({ settings: {} }), forScope: vi.fn().mockReturnValue({ settings: {} }),
} as unknown as LoadedSettings, } as unknown as LoadedSettings,
git: undefined as GitService | undefined, 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: { logger: {
log: vi.fn(), log: vi.fn(),
logMessage: vi.fn(), logMessage: vi.fn(),
@@ -54,7 +54,7 @@ export const createMockCommandContext = (
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any, // Cast because Logger is a class. } 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: { ui: {
addItem: vi.fn(), addItem: vi.fn(),
clear: vi.fn(), clear: vi.fn(),
@@ -94,11 +94,14 @@ export const createMockCommandContext = (
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const merge = (target: any, source: any): any => { const merge = (target: any, source: any): any => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const output = { ...target }; const output = { ...target };
for (const key in source) { for (const key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) { if (Object.prototype.hasOwnProperty.call(source, key)) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const sourceValue = source[key]; const sourceValue = source[key];
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const targetValue = output[key]; const targetValue = output[key];
if ( if (
@@ -106,9 +109,11 @@ export const createMockCommandContext = (
Object.prototype.toString.call(sourceValue) === '[object Object]' && Object.prototype.toString.call(sourceValue) === '[object Object]' &&
Object.prototype.toString.call(targetValue) === '[object Object]' Object.prototype.toString.call(targetValue) === '[object Object]'
) { ) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
output[key] = merge(targetValue, sourceValue); output[key] = merge(targetValue, sourceValue);
} else { } else {
// If not, we do a direct assignment. This preserves Date objects and others. // 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; output[key] = sourceValue;
} }
} }
+2 -1
View File
@@ -46,6 +46,7 @@ export const createMockSettings = (
workspace, workspace,
isTrusted, isTrusted,
errors, errors,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
merged: mergedOverride, merged: mergedOverride,
...settingsOverrides ...settingsOverrides
} = overrides; } = overrides;
@@ -75,7 +76,7 @@ export const createMockSettings = (
// Assign any function overrides (e.g., vi.fn() for methods) // Assign any function overrides (e.g., vi.fn() for methods)
for (const key in overrides) { for (const key in overrides) {
if (typeof overrides[key] === 'function') { 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]; (loaded as any)[key] = overrides[key];
} }
} }
@@ -115,6 +115,7 @@ export function SettingsDialog({
} }
const doSearch = async () => { const doSearch = async () => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const results = await fzfInstance.find(searchQuery); const results = await fzfInstance.find(searchQuery);
if (!active) return; if (!active) return;
@@ -451,6 +451,7 @@ Return a JSON object with:
'--limit', '--limit',
String(limit), String(limit),
]); ]);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const issues: Issue[] = JSON.parse(stdout); const issues: Issue[] = JSON.parse(stdout);
if (issues.length === 0) { if (issues.length === 0) {
setState((s) => ({ setState((s) => ({
@@ -137,6 +137,7 @@ export const TriageIssues = ({
'--limit', '--limit',
String(limit), String(limit),
]); ]);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const issues: Issue[] = JSON.parse(stdout); const issues: Issue[] = JSON.parse(stdout);
if (issues.length === 0) { if (issues.length === 0) {
setState((s) => ({ setState((s) => ({
@@ -166,6 +166,7 @@ async function searchResourceCandidates(
const fzf = new AsyncFzf(candidates, { const fzf = new AsyncFzf(candidates, {
selector: (candidate: ResourceSuggestionCandidate) => candidate.searchKey, selector: (candidate: ResourceSuggestionCandidate) => candidate.searchKey,
}); });
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const results = await fzf.find(normalizedPattern, { const results = await fzf.find(normalizedPattern, {
limit: MAX_SUGGESTIONS_TO_SHOW * 3, limit: MAX_SUGGESTIONS_TO_SHOW * 3,
}); });
@@ -188,6 +189,7 @@ async function searchAgentCandidates(
const fzf = new AsyncFzf(candidates, { const fzf = new AsyncFzf(candidates, {
selector: (s: Suggestion) => s.label, selector: (s: Suggestion) => s.label,
}); });
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const results = await fzf.find(normalizedPattern, { const results = await fzf.find(normalizedPattern, {
limit: MAX_SUGGESTIONS_TO_SHOW, limit: MAX_SUGGESTIONS_TO_SHOW,
}); });
@@ -57,6 +57,7 @@ export const useSessionBrowser = (
const originalFilePath = path.join(chatsDir, fileName); const originalFilePath = path.join(chatsDir, fileName);
// Load up the conversation. // Load up the conversation.
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const conversation: ConversationRecord = JSON.parse( const conversation: ConversationRecord = JSON.parse(
await fs.readFile(originalFilePath, 'utf8'), await fs.readFile(originalFilePath, 'utf8'),
); );
@@ -271,6 +271,7 @@ function useCommandSuggestions(
const fzfInstance = getFzfForCommands(commandsToSearch); const fzfInstance = getFzfForCommands(commandsToSearch);
if (fzfInstance) { if (fzfInstance) {
try { try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const fzfResults = await fzfInstance.fzf.find(partial); const fzfResults = await fzfInstance.fzf.find(partial);
if (signal.aborted) return; if (signal.aborted) return;
const uniqueCommands = new Set<SlashCommand>(); const uniqueCommands = new Set<SlashCommand>();
@@ -22,6 +22,7 @@ export const useStateAndRef = <
(newStateOrCallback) => { (newStateOrCallback) => {
let newValue: T; let newValue: T;
if (typeof newStateOrCallback === 'function') { if (typeof newStateOrCallback === 'function') {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
newValue = newStateOrCallback(ref.current); newValue = newStateOrCallback(ref.current);
} else { } else {
newValue = newStateOrCallback; newValue = newStateOrCallback;
@@ -243,6 +243,7 @@ export const TableRenderer: React.FC<TableRendererProps> = ({
isHeader = false, isHeader = false,
): React.ReactNode => { ): React.ReactNode => {
const renderedCells = cells.map((cell, index) => { const renderedCells = cells.map((cell, index) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const width = adjustedWidths[index] || 0; const width = adjustedWidths[index] || 0;
return renderCell(cell, width, isHeader); return renderCell(cell, width, isHeader);
}); });
@@ -179,6 +179,7 @@ async function configureVSCodeStyle(
await backupFile(keybindingsFile); await backupFile(keybindingsFile);
try { try {
const cleanContent = stripJsonComments(content); const cleanContent = stripJsonComments(content);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const parsedContent = JSON.parse(cleanContent); const parsedContent = JSON.parse(cleanContent);
if (!Array.isArray(parsedContent)) { if (!Array.isArray(parsedContent)) {
return { return {
+2
View File
@@ -233,7 +233,9 @@ export function escapeAnsiCtrlCodes<T>(obj: T): T {
let newArr: unknown[] | null = null; let newArr: unknown[] | null = null;
for (let i = 0; i < obj.length; i++) { for (let i = 0; i < obj.length; i++) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const value = obj[i]; const value = obj[i];
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const escapedValue = escapeAnsiCtrlCodes(value); const escapedValue = escapeAnsiCtrlCodes(value);
if (escapedValue !== value) { if (escapedValue !== value) {
if (newArr === null) { if (newArr === null) {
+1
View File
@@ -23,6 +23,7 @@ export function resolveEnvVarsInString(
): string { ): string {
const envVarRegex = /\$(?:(\w+)|{([^}]+)})/g; // Find $VAR_NAME or ${VAR_NAME} const envVarRegex = /\$(?:(\w+)|{([^}]+)})/g; // Find $VAR_NAME or ${VAR_NAME}
return value.replace(envVarRegex, (match, varName1, varName2) => { return value.replace(envVarRegex, (match, varName1, varName2) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const varName = varName1 || varName2; const varName = varName1 || varName2;
if (customEnv && typeof customEnv[varName] === 'string') { if (customEnv && typeof customEnv[varName] === 'string') {
return customEnv[varName]; return customEnv[varName];
+1
View File
@@ -78,6 +78,7 @@ export const getLatestGitHubRelease = async (
); );
} }
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const releaseTag = (await response.json()).tag_name; const releaseTag = (await response.json()).tag_name;
if (!releaseTag) { if (!releaseTag) {
throw new Error(`Response did not include tag_name field`); throw new Error(`Response did not include tag_name field`);
+1
View File
@@ -29,6 +29,7 @@ export function tryParseJSON(input: string): object | null {
if (!checkInput(input)) return null; if (!checkInput(input)) return null;
const trimmed = input.trim(); const trimmed = input.trim();
try { try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const parsed = JSON.parse(trimmed); const parsed = JSON.parse(trimmed);
if (parsed === null || typeof parsed !== 'object') { if (parsed === null || typeof parsed !== 'object') {
return null; return null;
@@ -38,6 +38,7 @@ export class PersistentState {
const filePath = this.getPath(); const filePath = this.getPath();
if (fs.existsSync(filePath)) { if (fs.existsSync(filePath)) {
const content = fs.readFileSync(filePath, 'utf-8'); const content = fs.readFileSync(filePath, 'utf-8');
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
this.cache = JSON.parse(content); this.cache = JSON.parse(content);
} else { } else {
this.cache = {}; this.cache = {};
+1
View File
@@ -23,6 +23,7 @@ export async function readStdin(): Promise<string> {
const onReadable = () => { const onReadable = () => {
let chunk; let chunk;
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
while ((chunk = process.stdin.read()) !== null) { while ((chunk = process.stdin.read()) !== null) {
if (pipedInputTimerId) { if (pipedInputTimerId) {
clearTimeout(pipedInputTimerId); clearTimeout(pipedInputTimerId);
+2
View File
@@ -254,6 +254,7 @@ export const getAllSessionFiles = async (
async (file): Promise<SessionFileEntry> => { async (file): Promise<SessionFileEntry> => {
const filePath = path.join(chatsDir, file); const filePath = path.join(chatsDir, file);
try { try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const content: ConversationRecord = JSON.parse( const content: ConversationRecord = JSON.parse(
await fs.readFile(filePath, 'utf8'), await fs.readFile(filePath, 'utf8'),
); );
@@ -498,6 +499,7 @@ export class SessionSelector {
const sessionPath = path.join(chatsDir, sessionInfo.fileName); const sessionPath = path.join(chatsDir, sessionInfo.fileName);
try { try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const sessionData: ConversationRecord = JSON.parse( const sessionData: ConversationRecord = JSON.parse(
await fs.readFile(sessionPath, 'utf8'), await fs.readFile(sessionPath, 'utf8'),
); );
+1
View File
@@ -371,6 +371,7 @@ export function setPendingSettingValue(
pendingSettings: Settings, pendingSettings: Settings,
): Settings { ): Settings {
const path = key.split('.'); const path = key.split('.');
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const newSettings = JSON.parse(JSON.stringify(pendingSettings)); const newSettings = JSON.parse(JSON.stringify(pendingSettings));
setNestedValue(newSettings, path, value); setNestedValue(newSettings, path, value);
return newSettings; return newSettings;
@@ -25,7 +25,9 @@ function extractRecursiveMessage(input: string): string {
(trimmed.startsWith('[') && trimmed.endsWith(']')) (trimmed.startsWith('[') && trimmed.endsWith(']'))
) { ) {
try { try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const parsed = JSON.parse(trimmed); const parsed = JSON.parse(trimmed);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const next = const next =
parsed?.error?.message || parsed?.error?.message ||
parsed?.[0]?.error?.message || parsed?.[0]?.error?.message ||
@@ -23,6 +23,7 @@ export class AcpFileSystemService implements FileSystemService {
return this.fallback.readTextFile(filePath); return this.fallback.readTextFile(filePath);
} }
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const response = await this.connection.readTextFile({ const response = await this.connection.readTextFile({
path: filePath, path: filePath,
sessionId: this.sessionId, 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 output = await this.connection.requestPermission(params);
const outcome = const outcome =
output.outcome.outcome === CoreToolCallStatus.Cancelled output.outcome.outcome === CoreToolCallStatus.Cancelled
@@ -27,6 +27,7 @@ export class AcknowledgedAgentsService {
const filePath = Storage.getAcknowledgedAgentsPath(); const filePath = Storage.getAcknowledgedAgentsPath();
try { try {
const content = await fs.readFile(filePath, 'utf-8'); const content = await fs.readFile(filePath, 'utf-8');
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
this.acknowledgedAgents = JSON.parse(content); this.acknowledgedAgents = JSON.parse(content);
} catch (error: unknown) { } catch (error: unknown) {
if (!isNodeError(error) || error.code !== 'ENOENT') { if (!isNodeError(error) || error.code !== 'ENOENT') {
@@ -54,6 +54,7 @@ export async function scheduleAgentTools(
} = options; } = options;
// Create a proxy/override of the config to provide the agent-specific tool registry. // 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); const agentConfig: Config = Object.create(config);
agentConfig.getToolRegistry = () => toolRegistry; agentConfig.getToolRegistry = () => toolRegistry;
+2 -1
View File
@@ -34,6 +34,7 @@ import {
AgentStartEvent, AgentStartEvent,
AgentFinishEvent, AgentFinishEvent,
RecoveryAttemptEvent, RecoveryAttemptEvent,
LlmRole,
} from '../telemetry/types.js'; } from '../telemetry/types.js';
import type { import type {
LocalAgentDefinition, LocalAgentDefinition,
@@ -59,7 +60,6 @@ import { getVersion } from '../utils/version.js';
import { getToolCallContext } from '../utils/toolCallContext.js'; import { getToolCallContext } from '../utils/toolCallContext.js';
import { scheduleAgentTools } from './agent-scheduler.js'; import { scheduleAgentTools } from './agent-scheduler.js';
import { DeadlineTimer } from '../utils/deadlineTimer.js'; import { DeadlineTimer } from '../utils/deadlineTimer.js';
import { LlmRole } from '../telemetry/types.js';
import { formatUserHintsForModel } from '../utils/fastAckHelper.js'; import { formatUserHintsForModel } from '../utils/fastAckHelper.js';
/** A callback function to report on agent activity. */ /** A callback function to report on agent activity. */
@@ -925,6 +925,7 @@ export class LocalAgentExecutor<TOutput extends z.ZodTypeAny> {
continue; continue;
} }
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const validatedOutput = validationResult.data; const validatedOutput = validationResult.data;
if (this.definition.processOutput) { if (this.definition.processOutput) {
submittedOutput = this.definition.processOutput(validatedOutput); submittedOutput = this.definition.processOutput(validatedOutput);
+1
View File
@@ -394,6 +394,7 @@ export class AgentRegistry {
} }
// Use Object.create to preserve lazy getters on the definition object // 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); const merged: LocalAgentDefinition<TOutput> = Object.create(definition);
if (overrides.runConfig) { if (overrides.runConfig) {
@@ -31,6 +31,7 @@ export function sanitizeAdminSettings(
if (sanitized.mcpSetting?.mcpConfigJson) { if (sanitized.mcpSetting?.mcpConfigJson) {
try { try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const parsed = JSON.parse(sanitized.mcpSetting.mcpConfigJson); const parsed = JSON.parse(sanitized.mcpSetting.mcpConfigJson);
const validationResult = McpConfigDefinitionSchema.safeParse(parsed); const validationResult = McpConfigDefinitionSchema.safeParse(parsed);
@@ -35,6 +35,7 @@ export async function getExperiments(
const expPath = process.env['GEMINI_EXP']; const expPath = process.env['GEMINI_EXP'];
debugLogger.debug('Reading experiments from', expPath); debugLogger.debug('Reading experiments from', expPath);
const content = await fs.promises.readFile(expPath, 'utf8'); const content = await fs.promises.readFile(expPath, 'utf8');
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const response: ListExperimentsResponse = JSON.parse(content); const response: ListExperimentsResponse = JSON.parse(content);
if ( if (
(response.flags && !Array.isArray(response.flags)) || (response.flags && !Array.isArray(response.flags)) ||
@@ -125,6 +125,7 @@ export class OAuthCredentialStorage {
throw error; throw error;
} }
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const credentials: Credentials = JSON.parse(credsJson); const credentials: Credentials = JSON.parse(credsJson);
// Save to new storage // Save to new storage
+1
View File
@@ -695,6 +695,7 @@ async function fetchAndCacheUserInfo(client: OAuth2Client): Promise<void> {
return; return;
} }
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const userInfo = await response.json(); const userInfo = await response.json();
await userAccountManager.cacheGoogleAccount(userInfo.email); await userAccountManager.cacheGoogleAccount(userInfo.email);
} catch (error) { } catch (error) {
+2
View File
@@ -88,6 +88,7 @@ export class Logger {
} }
try { try {
const fileContent = await fs.readFile(this.logFilePath, 'utf-8'); const fileContent = await fs.readFile(this.logFilePath, 'utf-8');
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const parsedLogs = JSON.parse(fileContent); const parsedLogs = JSON.parse(fileContent);
if (!Array.isArray(parsedLogs)) { if (!Array.isArray(parsedLogs)) {
debugLogger.debug( debugLogger.debug(
@@ -352,6 +353,7 @@ export class Logger {
const path = await this._getCheckpointPath(tag); const path = await this._getCheckpointPath(tag);
try { try {
const fileContent = await fs.readFile(path, 'utf-8'); const fileContent = await fs.readFile(path, 'utf-8');
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const parsedContent = JSON.parse(fileContent); const parsedContent = JSON.parse(fileContent);
// Handle legacy format (just an array of Content) // Handle legacy format (just an array of Content)
+5 -3
View File
@@ -5,10 +5,8 @@
*/ */
import { spawn } from 'node:child_process'; 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 { import type {
HookConfig,
HookInput, HookInput,
HookOutput, HookOutput,
HookExecutionResult, HookExecutionResult,
@@ -17,6 +15,8 @@ import type {
BeforeModelOutput, BeforeModelOutput,
BeforeToolInput, BeforeToolInput,
} from './types.js'; } from './types.js';
import { HookEventName, ConfigSource } from './types.js';
import type { Config } from '../config/config.js';
import type { LLMRequest } from './hookTranslator.js'; import type { LLMRequest } from './hookTranslator.js';
import { debugLogger } from '../utils/debugLogger.js'; import { debugLogger } from '../utils/debugLogger.js';
import { sanitizeEnvironment } from '../services/environmentSanitization.js'; import { sanitizeEnvironment } from '../services/environmentSanitization.js';
@@ -356,8 +356,10 @@ export class HookRunner {
const textToParse = stdout.trim() || stderr.trim(); const textToParse = stdout.trim() || stderr.trim();
if (textToParse) { if (textToParse) {
try { try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
let parsed = JSON.parse(textToParse); let parsed = JSON.parse(textToParse);
if (typeof parsed === 'string') { if (typeof parsed === 'string') {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
parsed = JSON.parse(parsed); parsed = JSON.parse(parsed);
} }
if (parsed && typeof parsed === 'object') { if (parsed && typeof parsed === 'object') {
+1
View File
@@ -34,6 +34,7 @@ export class TrustedHooksManager {
try { try {
if (fs.existsSync(this.configPath)) { if (fs.existsSync(this.configPath)) {
const content = fs.readFileSync(this.configPath, 'utf-8'); const content = fs.readFileSync(this.configPath, 'utf-8');
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
this.trustedHooks = JSON.parse(content); this.trustedHooks = JSON.parse(content);
} }
} catch (error) { } catch (error) {
+5 -2
View File
@@ -16,8 +16,10 @@ import { getIdeProcessInfo } from './process-utils.js';
import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'; import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js'; import {
import { ListToolsResultSchema } from '@modelcontextprotocol/sdk/types.js'; CallToolResultSchema,
ListToolsResultSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { IDE_REQUEST_TIMEOUT_MS } from './constants.js'; import { IDE_REQUEST_TIMEOUT_MS } from './constants.js';
import { debugLogger } from '../utils/debugLogger.js'; import { debugLogger } from '../utils/debugLogger.js';
import { import {
@@ -343,6 +345,7 @@ export class IdeClient {
if (textPart?.text) { if (textPart?.text) {
try { try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const parsedJson = JSON.parse(textPart.text); const parsedJson = JSON.parse(textPart.text);
if (parsedJson && typeof parsedJson.content === 'string') { if (parsedJson && typeof parsedJson.content === 'string') {
return parsedJson.content; return parsedJson.content;
@@ -89,8 +89,10 @@ export function getStdioConfigFromEnv(): StdioConfig | undefined {
let args: string[] = []; let args: string[] = [];
if (argsStr) { if (argsStr) {
try { try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const parsedArgs = JSON.parse(argsStr); const parsedArgs = JSON.parse(argsStr);
if (Array.isArray(parsedArgs)) { if (Array.isArray(parsedArgs)) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
args = parsedArgs; args = parsedArgs;
} else { } else {
logger.error( logger.error(
@@ -188,6 +190,7 @@ export async function getConnectionConfigFromFile(
} }
if (validWorkspaces.length === 1) { if (validWorkspaces.length === 1) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const selected = validWorkspaces[0]; const selected = validWorkspaces[0];
const fileIndex = parsedContents.indexOf(selected); const fileIndex = parsedContents.indexOf(selected);
if (fileIndex !== -1) { if (fileIndex !== -1) {
@@ -202,6 +205,7 @@ export async function getConnectionConfigFromFile(
(content) => String(content.port) === portFromEnv, (content) => String(content.port) === portFromEnv,
); );
if (matchingPortIndex !== -1) { if (matchingPortIndex !== -1) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const selected = validWorkspaces[matchingPortIndex]; const selected = validWorkspaces[matchingPortIndex];
const fileIndex = parsedContents.indexOf(selected); const fileIndex = parsedContents.indexOf(selected);
if (fileIndex !== -1) { 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 selected = validWorkspaces[0];
const fileIndex = parsedContents.indexOf(selected); const fileIndex = parsedContents.indexOf(selected);
if (fileIndex !== -1) { if (fileIndex !== -1) {
+1
View File
@@ -47,6 +47,7 @@ async function getProcessTableWindows(): Promise<Map<number, ProcessInfo>> {
let processes: RawProcessInfo | RawProcessInfo[]; let processes: RawProcessInfo | RawProcessInfo[];
try { try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
processes = JSON.parse(stdout); processes = JSON.parse(stdout);
} catch (_e) { } catch (_e) {
return processMap; return processMap;
+1 -2
View File
@@ -4,7 +4,7 @@
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
import { vi } from 'vitest'; import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
// Mock dependencies AT THE TOP // Mock dependencies AT THE TOP
const mockOpenBrowserSecurely = vi.hoisted(() => vi.fn()); 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 http from 'node:http';
import * as crypto from 'node:crypto'; import * as crypto from 'node:crypto';
import type { import type {
+1
View File
@@ -409,6 +409,7 @@ export class OAuthUtils {
*/ */
static parseTokenExpiry(idToken: string): number | undefined { static parseTokenExpiry(idToken: string): number | undefined {
try { try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const payload = JSON.parse( const payload = JSON.parse(
Buffer.from(idToken.split('.')[1], 'base64').toString(), Buffer.from(idToken.split('.')[1], 'base64').toString(),
); );
@@ -45,7 +45,9 @@ export class KeychainTokenStorage
try { try {
// Try to import keytar without any timeout - let the OS handle it // Try to import keytar without any timeout - let the OS handle it
const moduleName = 'keytar'; const moduleName = 'keytar';
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const module = await import(moduleName); const module = await import(moduleName);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
this.keytarModule = module.default || module; this.keytarModule = module.default || module;
} catch (_) { } catch (_) {
//Keytar is optional so we shouldn't raise an error of log anything. //Keytar is optional so we shouldn't raise an error of log anything.
@@ -229,6 +229,7 @@ export class CheckerRunner {
// Try to parse the output // Try to parse the output
try { try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const rawResult = JSON.parse(stdout); const rawResult = JSON.parse(stdout);
const result = SafetyCheckResultSchema.parse(rawResult); const result = SafetyCheckResultSchema.parse(rawResult);
@@ -26,6 +26,7 @@ async function generateAndSaveSummary(
): Promise<void> { ): Promise<void> {
// Read session file // Read session file
const content = await fs.readFile(sessionPath, 'utf-8'); const content = await fs.readFile(sessionPath, 'utf-8');
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const conversation: ConversationRecord = JSON.parse(content); const conversation: ConversationRecord = JSON.parse(content);
// Skip if summary already exists // Skip if summary already exists
@@ -69,6 +70,7 @@ async function generateAndSaveSummary(
// Re-read the file before writing to handle race conditions // Re-read the file before writing to handle race conditions
const freshContent = await fs.readFile(sessionPath, 'utf-8'); const freshContent = await fs.readFile(sessionPath, 'utf-8');
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const freshConversation: ConversationRecord = JSON.parse(freshContent); const freshConversation: ConversationRecord = JSON.parse(freshContent);
// Check if summary was added by another process // Check if summary was added by another process
@@ -127,6 +129,7 @@ export async function getPreviousSession(
try { try {
const content = await fs.readFile(filePath, 'utf-8'); const content = await fs.readFile(filePath, 'utf-8');
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const conversation: ConversationRecord = JSON.parse(content); const conversation: ConversationRecord = JSON.parse(content);
if (conversation.summary) { if (conversation.summary) {
@@ -568,6 +568,7 @@ export class ShellExecutionService {
const guardedCommand = ensurePromptvarsDisabled(commandToExecute, shell); const guardedCommand = ensurePromptvarsDisabled(commandToExecute, shell);
const args = [...argsPrefix, guardedCommand]; const args = [...argsPrefix, guardedCommand];
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const ptyProcess = ptyInfo.module.spawn(executable, args, { const ptyProcess = ptyInfo.module.spawn(executable, args, {
cwd, cwd,
name: 'xterm-256color', name: 'xterm-256color',
@@ -598,6 +599,7 @@ export class ShellExecutionService {
headlessTerminal.scrollToTop(); headlessTerminal.scrollToTop();
this.activePtys.set(ptyProcess.pid, { this.activePtys.set(ptyProcess.pid, {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
ptyProcess, ptyProcess,
headlessTerminal, headlessTerminal,
maxSerializedLines: shellExecutionConfig.maxSerializedLines, maxSerializedLines: shellExecutionConfig.maxSerializedLines,
@@ -831,6 +833,7 @@ export class ShellExecutionService {
signal: signal ?? null, signal: signal ?? null,
error, error,
aborted: abortSignal.aborted, aborted: abortSignal.aborted,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
pid: ptyProcess.pid, pid: ptyProcess.pid,
executionMethod: ptyInfo?.name ?? 'node-pty', executionMethod: ptyInfo?.name ?? 'node-pty',
}); });
@@ -862,9 +865,11 @@ export class ShellExecutionService {
const abortHandler = async () => { const abortHandler = async () => {
if (ptyProcess.pid && !exited) { if (ptyProcess.pid && !exited) {
await killProcessGroup({ await killProcessGroup({
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
pid: ptyProcess.pid, pid: ptyProcess.pid,
escalate: true, escalate: true,
isExited: () => exited, isExited: () => exited,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
pty: ptyProcess, pty: ptyProcess,
}); });
} }
@@ -873,6 +878,7 @@ export class ShellExecutionService {
abortSignal.addEventListener('abort', abortHandler, { once: true }); abortSignal.addEventListener('abort', abortHandler, { once: true });
}); });
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
return { pid: ptyProcess.pid, result }; return { pid: ptyProcess.pid, result };
} catch (e) { } catch (e) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion // 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.function_name === ASK_USER_TOOL_NAME &&
event.metadata['ask_user'] event.metadata['ask_user']
) { ) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const askUser = event.metadata['ask_user']; const askUser = event.metadata['ask_user'];
const askUserMapping: { [key: string]: EventMetadataKey } = { const askUserMapping: { [key: string]: EventMetadataKey } = {
question_types: EventMetadataKey.GEMINI_CLI_ASK_USER_QUESTION_TYPES, 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 // eslint-disable-next-line @typescript-eslint/no-explicit-any
const socketLike: any = { const socketLike: any = {
_httpMessage: { _httpMessage: {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
agent: proxyAgentLike, agent: proxyAgentLike,
socket: null, socket: null,
}, },
}; };
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
socketLike._httpMessage.socket = socketLike; // Create circular reference socketLike._httpMessage.socket = socketLike; // Create circular reference
proxyAgentLike.sockets['cloudcode-pa.googleapis.com:443'] = [socketLike]; proxyAgentLike.sockets['cloudcode-pa.googleapis.com:443'] = [socketLike];
@@ -49,6 +51,7 @@ describe('Circular Reference Integration Test', () => {
error: new Error('Network error'), error: new Error('Network error'),
function_args: { function_args: {
filePath: '/test/file.txt', filePath: '/test/file.txt',
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
httpAgent: proxyAgentLike, // This would cause the circular reference httpAgent: proxyAgentLike, // This would cause the circular reference
}, },
}; };
@@ -39,8 +39,10 @@ describe('Circular Reference Handling', () => {
sockets: {}, sockets: {},
agent: null, agent: null,
}; };
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
circularObject.agent = circularObject; // Create circular reference circularObject.agent = circularObject; // Create circular reference
circularObject.sockets['test-host'] = [ circularObject.sockets['test-host'] = [
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
{ _httpMessage: { agent: circularObject } }, { _httpMessage: { agent: circularObject } },
]; ];
@@ -48,6 +50,7 @@ describe('Circular Reference Handling', () => {
const mockRequest: ToolCallRequestInfo = { const mockRequest: ToolCallRequestInfo = {
callId: 'test-call-id', callId: 'test-call-id',
name: 'ReadFile', name: 'ReadFile',
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
args: circularObject, // This would cause the original error args: circularObject, // This would cause the original error
isClientInitiated: false, isClientInitiated: false,
prompt_id: 'test-prompt-id', prompt_id: 'test-prompt-id',
+2
View File
@@ -145,12 +145,14 @@ export function logToolCall(config: Config, event: ToolCallEvent): void {
}); });
if (event.metadata) { if (event.metadata) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const added = event.metadata['model_added_lines']; const added = event.metadata['model_added_lines'];
if (typeof added === 'number' && added > 0) { if (typeof added === 'number' && added > 0) {
recordLinesChanged(config, added, 'added', { recordLinesChanged(config, added, 'added', {
function_name: event.function_name, function_name: event.function_name,
}); });
} }
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const removed = event.metadata['model_removed_lines']; const removed = event.metadata['model_removed_lines'];
if (typeof removed === 'number' && removed > 0) { if (typeof removed === 'number' && removed > 0) {
recordLinesChanged(config, removed, 'removed', { recordLinesChanged(config, removed, 'removed', {
+3 -1
View File
@@ -22,6 +22,7 @@ import type {
Prompt, Prompt,
ReadResourceResult, ReadResourceResult,
Resource, Resource,
Tool as McpTool,
} from '@modelcontextprotocol/sdk/types.js'; } from '@modelcontextprotocol/sdk/types.js';
import { import {
ListResourcesResultSchema, ListResourcesResultSchema,
@@ -31,7 +32,6 @@ import {
ToolListChangedNotificationSchema, ToolListChangedNotificationSchema,
PromptListChangedNotificationSchema, PromptListChangedNotificationSchema,
ProgressNotificationSchema, ProgressNotificationSchema,
type Tool as McpTool,
} from '@modelcontextprotocol/sdk/types.js'; } from '@modelcontextprotocol/sdk/types.js';
import { ApprovalMode, PolicyDecision } from '../policy/types.js'; import { ApprovalMode, PolicyDecision } from '../policy/types.js';
import { parse } from 'shell-quote'; import { parse } from 'shell-quote';
@@ -1996,6 +1996,7 @@ export async function createTransport(
// The `XcodeMcpBridgeFixTransport` wrapper hides the underlying `StdioClientTransport`, // The `XcodeMcpBridgeFixTransport` wrapper hides the underlying `StdioClientTransport`,
// which exposes `stderr` for debug logging. We need to unwrap it to attach the listener. // 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 = const underlyingTransport =
transport instanceof XcodeMcpBridgeFixTransport transport instanceof XcodeMcpBridgeFixTransport
? // 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
@@ -2007,6 +2008,7 @@ export async function createTransport(
underlyingTransport.stderr underlyingTransport.stderr
) { ) {
underlyingTransport.stderr.on('data', (data) => { underlyingTransport.stderr.on('data', (data) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const stderrStr = data.toString().trim(); const stderrStr = data.toString().trim();
debugLogger.debug( debugLogger.debug(
`[DEBUG] [MCP STDERR (${mcpServerName})]: `, `[DEBUG] [MCP STDERR (${mcpServerName})]: `,
+4
View File
@@ -481,8 +481,10 @@ class GrepToolInvocation extends BaseToolInvocation<
basePath: string, basePath: string,
): GrepMatch | null { ): GrepMatch | null {
try { try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const json = JSON.parse(line); const json = JSON.parse(line);
if (json.type === 'match' || json.type === 'context') { if (json.type === 'match' || json.type === 'context') {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const data = json.data; const data = json.data;
// Defensive check: ensure text properties exist (skips binary/invalid encoding) // Defensive check: ensure text properties exist (skips binary/invalid encoding)
if (data.path?.text && data.lines?.text) { if (data.path?.text && data.lines?.text) {
@@ -500,7 +502,9 @@ class GrepToolInvocation extends BaseToolInvocation<
return { return {
filePath: relativeFilePath || path.basename(absoluteFilePath), filePath: relativeFilePath || path.basename(absoluteFilePath),
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
lineNumber: data.line_number, lineNumber: data.line_number,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
line: data.lines.text.trimEnd(), line: data.lines.text.trimEnd(),
isContext: json.type === 'context', isContext: json.type === 'context',
}; };
+1
View File
@@ -390,6 +390,7 @@ export class ToolRegistry {
// execute discovery command and extract function declarations (w/ or w/o "tool" wrappers) // execute discovery command and extract function declarations (w/ or w/o "tool" wrappers)
const functions: FunctionDeclaration[] = []; const functions: FunctionDeclaration[] = [];
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const discoveredItems = JSON.parse(stdout.trim()); const discoveredItems = JSON.parse(stdout.trim());
if (!discoveredItems || !Array.isArray(discoveredItems)) { if (!discoveredItems || !Array.isArray(discoveredItems)) {
@@ -75,7 +75,7 @@ export class XcodeMcpBridgeFixTransport
// We can cast because we verified 'result' is in response, // We can cast because we verified 'result' is in response,
// but TS might still be picky if the type is a strict union. // but TS might still be picky if the type is a strict union.
// Let's treat it safely. // 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; const result = response.result as any;
// Check if we have content but missing structuredContent // Check if we have content but missing structuredContent
@@ -85,12 +85,15 @@ export class XcodeMcpBridgeFixTransport
result.content.length > 0 && result.content.length > 0 &&
!result.structuredContent !result.structuredContent
) { ) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const firstItem = result.content[0]; const firstItem = result.content[0];
if (firstItem.type === 'text' && typeof firstItem.text === 'string') { if (firstItem.type === 'text' && typeof firstItem.text === 'string') {
try { try {
// Attempt to parse the text as JSON // Attempt to parse the text as JSON
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const parsed = JSON.parse(firstItem.text); const parsed = JSON.parse(firstItem.text);
// If successful, populate structuredContent // If successful, populate structuredContent
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
result.structuredContent = parsed; result.structuredContent = parsed;
} catch (_) { } catch (_) {
// Ignored: Content is likely plain text, not JSON. // Ignored: Content is likely plain text, not JSON.
@@ -145,6 +145,7 @@ class RecursiveFileSearch implements FileSearch {
if (pattern.includes('*') || !this.fzf) { if (pattern.includes('*') || !this.fzf) {
filteredCandidates = await filter(candidates, pattern, options.signal); filteredCandidates = await filter(candidates, pattern, options.signal);
} else { } else {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
filteredCandidates = await this.fzf filteredCandidates = await this.fzf
.find(pattern) .find(pattern)
.then((results: Array<FzfResultItem<string>>) => .then((results: Array<FzfResultItem<string>>) =>
+4
View File
@@ -23,12 +23,16 @@ export const getPty = async (): Promise<PtyImplementation> => {
} }
try { try {
const lydell = '@lydell/node-pty'; const lydell = '@lydell/node-pty';
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const module = await import(lydell); const module = await import(lydell);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
return { module, name: 'lydell-node-pty' }; return { module, name: 'lydell-node-pty' };
} catch (_e) { } catch (_e) {
try { try {
const nodePty = 'node-pty'; const nodePty = 'node-pty';
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const module = await import(nodePty); const module = await import(nodePty);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
return { module, name: 'node-pty' }; return { module, name: 'node-pty' };
} catch (_e2) { } catch (_e2) {
return null; return null;
+7
View File
@@ -166,10 +166,12 @@ export function parseGoogleApiError(error: unknown): GoogleApiError | null {
depth < maxDepth depth < maxDepth
) { ) {
try { try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const parsedMessage = JSON.parse( const parsedMessage = JSON.parse(
currentError.message.replace(/\u00A0/g, '').replace(/\n/g, ' '), currentError.message.replace(/\u00A0/g, '').replace(/\n/g, ' '),
); );
if (parsedMessage.error) { if (parsedMessage.error) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
currentError = parsedMessage.error; currentError = parsedMessage.error;
depth++; depth++;
} else { } else {
@@ -243,6 +245,7 @@ function fromGaxiosError(errorObj: object): ErrorShape | undefined {
if (typeof data === 'string') { if (typeof data === 'string') {
try { try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
data = JSON.parse(data); data = JSON.parse(data);
} catch (_) { } catch (_) {
// Not a JSON string, can't parse. // Not a JSON string, can't parse.
@@ -250,6 +253,7 @@ function fromGaxiosError(errorObj: object): ErrorShape | undefined {
} }
if (Array.isArray(data) && data.length > 0) { if (Array.isArray(data) && data.length > 0) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
data = data[0]; data = data[0];
} }
@@ -288,6 +292,7 @@ function fromApiError(errorObj: object): ErrorShape | undefined {
if (typeof data === 'string') { if (typeof data === 'string') {
try { try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
data = JSON.parse(data); data = JSON.parse(data);
} catch (_) { } catch (_) {
// Not a JSON string, can't parse. // Not a JSON string, can't parse.
@@ -297,6 +302,7 @@ function fromApiError(errorObj: object): ErrorShape | undefined {
const lastBrace = data.lastIndexOf('}'); const lastBrace = data.lastIndexOf('}');
if (firstBrace !== -1 && lastBrace !== -1 && lastBrace > firstBrace) { if (firstBrace !== -1 && lastBrace !== -1 && lastBrace > firstBrace) {
try { try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
data = JSON.parse(data.substring(firstBrace, lastBrace + 1)); data = JSON.parse(data.substring(firstBrace, lastBrace + 1));
} catch (__) { } catch (__) {
// Still failed // Still failed
@@ -307,6 +313,7 @@ function fromApiError(errorObj: object): ErrorShape | undefined {
} }
if (Array.isArray(data) && data.length > 0) { if (Array.isArray(data) && data.length > 0) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
data = data[0]; data = data[0];
} }
@@ -127,6 +127,7 @@ async function getGeminiMdFilePathsInternal(
result.value.global.forEach((p) => globalPaths.add(p)); result.value.global.forEach((p) => globalPaths.add(p));
result.value.project.forEach((p) => projectPaths.add(p)); result.value.project.forEach((p) => projectPaths.add(p));
} else { } else {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const error = result.reason; const error = result.reason;
const message = error instanceof Error ? error.message : String(error); const message = error instanceof Error ? error.message : String(error);
logger.error(`Error discovering files in directory: ${message}`); logger.error(`Error discovering files in directory: ${message}`);
@@ -299,6 +300,7 @@ export async function readGeminiMdFiles(
} else { } else {
// This case shouldn't happen since we catch all errors above, // This case shouldn't happen since we catch all errors above,
// but handle it for completeness // but handle it for completeness
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const error = result.reason; const error = result.reason;
const message = error instanceof Error ? error.message : String(error); const message = error instanceof Error ? error.message : String(error);
logger.error(`Unexpected error processing file: ${message}`); logger.error(`Unexpected error processing file: ${message}`);
@@ -37,6 +37,7 @@ export function safeJsonStringify(
function removeEmptyObjects(data: any): object { function removeEmptyObjects(data: any): object {
const cleanedObject: { [key: string]: unknown } = {}; const cleanedObject: { [key: string]: unknown } = {};
for (const k in data) { for (const k in data) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const v = data[k]; const v = data[k];
if (v !== null && v !== undefined && typeof v === 'boolean') { if (v !== null && v !== undefined && typeof v === 'boolean') {
cleanedObject[k] = v; cleanedObject[k] = v;
+5 -3
View File
@@ -12,9 +12,9 @@ import * as addFormats from 'ajv-formats';
import { debugLogger } from './debugLogger.js'; import { debugLogger } from './debugLogger.js';
// Ajv's ESM/CJS interop: use 'any' for compatibility as recommended by Ajv docs // 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; 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 Ajv2020Class = (Ajv2020Pkg as any).default || Ajv2020Pkg;
const ajvOptions = { const ajvOptions = {
@@ -29,12 +29,14 @@ const ajvOptions = {
}; };
// Draft-07 validator (default) // Draft-07 validator (default)
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const ajvDefault: Ajv = new AjvClass(ajvOptions); const ajvDefault: Ajv = new AjvClass(ajvOptions);
// Draft-2020-12 validator for MCP servers using rmcp // Draft-2020-12 validator for MCP servers using rmcp
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const ajv2020: Ajv = new Ajv2020Class(ajvOptions); 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; const addFormatsFunc = (addFormats as any).default || addFormats;
addFormatsFunc(ajvDefault); addFormatsFunc(ajvDefault);
addFormatsFunc(ajv2020); addFormatsFunc(ajv2020);
+1
View File
@@ -479,6 +479,7 @@ function parsePowerShellCommandDetails(
hasRedirection?: boolean; hasRedirection?: boolean;
} | null = null; } | null = null;
try { try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
parsed = JSON.parse(output); parsed = JSON.parse(output);
} catch { } catch {
return { details: [], hasError: true }; return { details: [], hasError: true };
+2
View File
@@ -88,6 +88,7 @@ export function createWorkingStdio() {
if (prop === 'write') { if (prop === 'write') {
return writeToStdout; return writeToStdout;
} }
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const value = Reflect.get(target, prop, receiver); const value = Reflect.get(target, prop, receiver);
if (typeof value === 'function') { if (typeof value === 'function') {
return value.bind(target); return value.bind(target);
@@ -101,6 +102,7 @@ export function createWorkingStdio() {
if (prop === 'write') { if (prop === 'write') {
return writeToStderr; return writeToStderr;
} }
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const value = Reflect.get(target, prop, receiver); const value = Reflect.get(target, prop, receiver);
if (typeof value === 'function') { if (typeof value === 'function') {
return value.bind(target); return value.bind(target);
@@ -30,6 +30,7 @@ export class UserAccountManager {
return defaultState; return defaultState;
} }
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const parsed = JSON.parse(content); const parsed = JSON.parse(content);
// Inlined validation logic // Inlined validation logic
@@ -50,7 +51,9 @@ export class UserAccountManager {
} }
return { return {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
active: parsed.active ?? null, active: parsed.active ?? null,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
old: parsed.old ?? [], old: parsed.old ?? [],
}; };
} }
+1
View File
@@ -260,6 +260,7 @@ export class DevTools extends EventEmitter {
ws.on('message', (data: Buffer) => { ws.on('message', (data: Buffer) => {
try { try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const message = JSON.parse(data.toString()); const message = JSON.parse(data.toString());
// Handle registration first // Handle registration first
+2
View File
@@ -210,6 +210,7 @@ export class GeminiCliSession {
const toolCall = event.value; const toolCall = event.value;
let args = toolCall.args; let args = toolCall.args;
if (typeof args === 'string') { if (typeof args === 'string') {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
args = JSON.parse(args); args = JSON.parse(args);
} }
toolCallsToSchedule.push({ toolCallsToSchedule.push({
@@ -238,6 +239,7 @@ export class GeminiCliSession {
}; };
const originalRegistry = this.config.getToolRegistry(); const originalRegistry = this.config.getToolRegistry();
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const scopedRegistry: ToolRegistry = Object.create(originalRegistry); const scopedRegistry: ToolRegistry = Object.create(originalRegistry);
scopedRegistry.getTool = (name: string) => { scopedRegistry.getTool = (name: string) => {
const tool = originalRegistry.getTool(name); const tool = originalRegistry.getTool(name);
@@ -38,6 +38,7 @@ async function checkForUpdates(
isManagedExtensionSurface: boolean, isManagedExtensionSurface: boolean,
) { ) {
try { try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const currentVersion = context.extension.packageJSON.version; const currentVersion = context.extension.packageJSON.version;
// Fetch extension details from the VSCode Marketplace. // Fetch extension details from the VSCode Marketplace.
@@ -75,9 +76,12 @@ async function checkForUpdates(
return; return;
} }
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const data = await response.json(); const data = await response.json();
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const extension = data?.results?.[0]?.extensions?.[0]; const extension = data?.results?.[0]?.extensions?.[0];
// The versions are sorted by date, so the first one is the latest. // 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; const latestVersion = extension?.versions?.[0]?.version;
if ( if (