Merge branch 'main' into mb/beforemodel-context

This commit is contained in:
Michael Bleigh
2026-04-07 10:14:44 -07:00
committed by GitHub
127 changed files with 6402 additions and 3014 deletions
@@ -32,11 +32,11 @@ import { createAnalyzeScreenshotTool } from './analyzeScreenshot.js';
import { injectAutomationOverlay } from './automationOverlay.js';
import { injectInputBlocker } from './inputBlocker.js';
import { debugLogger } from '../../utils/debugLogger.js';
import { recordBrowserAgentToolDiscovery } from '../../telemetry/metrics.js';
import {
recordBrowserAgentToolDiscovery,
recordBrowserAgentVisionStatus,
recordBrowserAgentCleanup,
} from '../../telemetry/metrics.js';
logBrowserAgentVisionStatus,
logBrowserAgentCleanup,
} from '../../telemetry/loggers.js';
import {
PolicyDecision,
PRIORITY_SUBAGENT_TOOL,
@@ -248,7 +248,7 @@ export async function createBrowserAgentDefinition(
const allTools: AnyDeclarativeTool[] = [...mcpTools];
const visionDisabledReason = getVisionDisabledReason();
recordBrowserAgentVisionStatus(config, {
logBrowserAgentVisionStatus(config, {
enabled: !visionDisabledReason,
disabled_reason: visionDisabledReason?.code,
});
@@ -299,13 +299,13 @@ export async function cleanupBrowserAgent(
const startMs = Date.now();
try {
await browserManager.close();
recordBrowserAgentCleanup(config, Date.now() - startMs, {
logBrowserAgentCleanup(config, Date.now() - startMs, {
session_mode: sessionMode,
success: true,
});
debugLogger.log('Browser agent cleanup complete');
} catch (error) {
recordBrowserAgentCleanup(config, Date.now() - startMs, {
logBrowserAgentCleanup(config, Date.now() - startMs, {
session_mode: sessionMode,
success: false,
});
@@ -742,7 +742,7 @@ describe('BrowserAgentInvocation', () => {
);
});
it('should call cleanupBrowserAgent with correct params', async () => {
it('should not call cleanupBrowserAgent (cleanup is handled by BrowserManager.resetAll)', async () => {
const invocation = new BrowserAgentInvocation(
mockConfig,
mockParams,
@@ -750,11 +750,7 @@ describe('BrowserAgentInvocation', () => {
);
await invocation.execute(new AbortController().signal, vi.fn());
expect(cleanupBrowserAgent).toHaveBeenCalledWith(
expect.anything(),
mockConfig,
'persistent',
);
expect(cleanupBrowserAgent).not.toHaveBeenCalled();
});
});
@@ -34,12 +34,9 @@ import {
isToolActivityError,
} from '../types.js';
import type { MessageBus } from '../../confirmation-bus/message-bus.js';
import {
createBrowserAgentDefinition,
cleanupBrowserAgent,
} from './browserAgentFactory.js';
import { createBrowserAgentDefinition } from './browserAgentFactory.js';
import { removeInputBlocker } from './inputBlocker.js';
import { recordBrowserAgentTaskOutcome } from '../../telemetry/metrics.js';
import { logBrowserAgentTaskOutcome } from '../../telemetry/loggers.js';
import {
sanitizeThoughtContent,
sanitizeToolArgs,
@@ -400,7 +397,7 @@ ${output.result}`;
},
};
} finally {
recordBrowserAgentTaskOutcome(this.config, {
logBrowserAgentTaskOutcome(this.config, {
success: taskSuccess,
session_mode: sessionMode,
vision_enabled: visionEnabled,
@@ -444,7 +441,6 @@ ${output.result}`;
} catch {
// Ignore errors for removing the overlays.
}
await cleanupBrowserAgent(browserManager, this.config, sessionMode);
}
}
}
@@ -373,6 +373,7 @@ describe('BrowserManager', () => {
session_mode: 'persistent',
headless: false,
success: true,
tool_count: 4,
},
);
});
@@ -30,7 +30,7 @@ import * as path from 'node:path';
import * as fs from 'node:fs';
import { fileURLToPath } from 'node:url';
import { injectAutomationOverlay } from './automationOverlay.js';
import { recordBrowserAgentConnection } from '../../telemetry/metrics.js';
import { logBrowserAgentConnection } from '../../telemetry/loggers.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@@ -563,7 +563,9 @@ export class BrowserManager {
// Add optional settings from config.
// Force headless in seatbelt sandbox since Chrome profile/display access
// may be restricted, and the user is running in a sandboxed environment.
if (browserConfig.customConfig.headless || isSeatbeltSandbox) {
const effectiveHeadless =
!!browserConfig.customConfig.headless || isSeatbeltSandbox;
if (effectiveHeadless) {
mcpArgs.push('--headless');
}
if (browserConfig.customConfig.profilePath) {
@@ -667,15 +669,12 @@ export class BrowserManager {
// clear the action counter for each connection
this.actionCounter = 0;
recordBrowserAgentConnection(
this.config,
Date.now() - connectStartMs,
{
session_mode: sessionMode,
headless: !!browserConfig.customConfig.headless,
success: true,
},
);
logBrowserAgentConnection(this.config, Date.now() - connectStartMs, {
session_mode: sessionMode,
headless: effectiveHeadless,
success: true,
tool_count: this.discoveredTools.length,
});
})(),
new Promise<never>((_, reject) => {
timeoutId = setTimeout(
@@ -696,9 +695,9 @@ export class BrowserManager {
error instanceof Error ? error.message : String(error);
const errorType = BrowserManager.classifyConnectionError(rawErrorMessage);
recordBrowserAgentConnection(this.config, Date.now() - connectStartMs, {
logBrowserAgentConnection(this.config, Date.now() - connectStartMs, {
session_mode: sessionMode,
headless: !!browserConfig.customConfig.headless,
headless: effectiveHeadless,
success: false,
error_type: errorType,
});
+20 -2
View File
@@ -30,7 +30,7 @@ import { CompressionStatus } from '../core/turn.js';
import { type ToolCallRequestInfo } from '../scheduler/types.js';
import { ChatCompressionService } from '../context/chatCompressionService.js';
import { getDirectoryContextString } from '../utils/environmentContext.js';
import { renderUserMemory } from '../prompts/snippets.js';
import { renderUserMemory, renderAgentSkills } from '../prompts/snippets.js';
import { promptIdContext } from '../utils/promptIdContext.js';
import {
logAgentStart,
@@ -78,7 +78,10 @@ import {
runWithScopedWorkspaceContext,
} from '../config/scoped-config.js';
import { CompleteTaskTool } from '../tools/complete-task.js';
import { COMPLETE_TASK_TOOL_NAME } from '../tools/definitions/base-declarations.js';
import {
COMPLETE_TASK_TOOL_NAME,
ACTIVATE_SKILL_TOOL_NAME,
} from '../tools/definitions/base-declarations.js';
/** A callback function to report on agent activity. */
export type ActivityCallback = (activity: SubagentActivityEvent) => void;
@@ -1318,6 +1321,21 @@ export class LocalAgentExecutor<TOutput extends z.ZodTypeAny> {
// Inject user inputs into the prompt template.
let finalPrompt = templateString(promptConfig.systemPrompt, inputs);
// Inject skill SI if ACTIVATE_SKILL_TOOL_NAME is available to this agent.
if (this.toolRegistry.getTool(ACTIVATE_SKILL_TOOL_NAME) !== undefined) {
const skills = this.context.config.getSkillManager().getSkills();
if (skills.length > 0) {
const skillsPrompt = renderAgentSkills(
skills.map((s) => ({
name: s.name,
description: s.description,
location: s.location,
})),
);
finalPrompt += `\n\n${skillsPrompt}`;
}
}
// Append memory context if available.
const systemMemory = this.context.config.getSystemInstructionMemory();
if (systemMemory) {
+12 -3
View File
@@ -1579,7 +1579,9 @@ describe('Server Config (config.ts)', () => {
});
expect(config.getSandboxEnabled()).toBe(false);
expect(config.getSandboxAllowedPaths()).toEqual([]);
expect(config.getSandboxAllowedPaths()).toEqual([
Storage.getGlobalTempDir(),
]);
expect(config.getSandboxNetworkAccess()).toBe(false);
});
@@ -1597,7 +1599,11 @@ describe('Server Config (config.ts)', () => {
});
expect(config.getSandboxEnabled()).toBe(true);
expect(config.getSandboxAllowedPaths()).toEqual(['/tmp/foo', '/var/bar']);
expect(config.getSandboxAllowedPaths()).toEqual([
'/tmp/foo',
'/var/bar',
Storage.getGlobalTempDir(),
]);
expect(config.getSandboxNetworkAccess()).toBe(true);
expect(config.getSandbox()?.command).toBe('docker');
expect(config.getSandbox()?.image).toBe('my-image');
@@ -1614,7 +1620,10 @@ describe('Server Config (config.ts)', () => {
});
expect(config.getSandboxEnabled()).toBe(true);
expect(config.getSandboxAllowedPaths()).toEqual(['/only/this']);
expect(config.getSandboxAllowedPaths()).toEqual([
'/only/this',
Storage.getGlobalTempDir(),
]);
expect(config.getSandboxNetworkAccess()).toBe(false);
});
+23 -4
View File
@@ -508,6 +508,7 @@ export enum AuthProviderType {
export interface SandboxConfig {
enabled: boolean;
allowedPaths?: string[];
includeDirectories?: string[];
networkAccess?: boolean;
command?:
| 'docker'
@@ -524,6 +525,7 @@ export const ConfigSchema = z.object({
.object({
enabled: z.boolean().default(false),
allowedPaths: z.array(z.string()).default([]),
includeDirectories: z.array(z.string()).default([]),
networkAccess: z.boolean().default(false),
command: z
.enum([
@@ -965,6 +967,11 @@ export class Config implements McpContext, AgentLoopContext {
? {
enabled: params.sandbox.enabled || params.toolSandboxing || false,
allowedPaths: params.sandbox.allowedPaths ?? [],
includeDirectories: [
...(params.sandbox.includeDirectories ?? []),
...(params.sandbox.allowedPaths ?? []),
Storage.getGlobalTempDir(),
],
networkAccess: params.sandbox.networkAccess ?? false,
command: params.sandbox.command,
image: params.sandbox.image,
@@ -972,6 +979,7 @@ export class Config implements McpContext, AgentLoopContext {
: {
enabled: params.toolSandboxing || false,
allowedPaths: [],
includeDirectories: [Storage.getGlobalTempDir()],
networkAccess: false,
};
@@ -994,7 +1002,10 @@ export class Config implements McpContext, AgentLoopContext {
{
workspace: this.targetDir,
forbiddenPaths: this.getSandboxForbiddenPaths.bind(this),
includeDirectories: this.pendingIncludeDirectories,
includeDirectories: [
...this.pendingIncludeDirectories,
Storage.getGlobalTempDir(),
],
policyManager: this._sandboxPolicyManager,
},
initialApprovalMode,
@@ -1002,7 +1013,7 @@ export class Config implements McpContext, AgentLoopContext {
if (
!(this._sandboxManager instanceof NoopSandboxManager) &&
this.sandbox.enabled
this.sandbox?.enabled
) {
this.fileSystemService = new SandboxedFileSystemService(
this._sandboxManager,
@@ -1702,7 +1713,10 @@ export class Config implements McpContext, AgentLoopContext {
{
workspace: this.targetDir,
forbiddenPaths: this.getSandboxForbiddenPaths.bind(this),
includeDirectories: this.pendingIncludeDirectories,
includeDirectories: [
...this.pendingIncludeDirectories,
Storage.getGlobalTempDir(),
],
policyManager: this._sandboxPolicyManager,
},
this.getApprovalMode(),
@@ -1981,7 +1995,12 @@ export class Config implements McpContext, AgentLoopContext {
}
getSandboxAllowedPaths(): string[] {
return this.sandbox?.allowedPaths ?? [];
const paths = [...(this.sandbox?.allowedPaths ?? [])];
const globalTempDir = Storage.getGlobalTempDir();
if (!paths.includes(globalTempDir)) {
paths.push(globalTempDir);
}
return paths;
}
getSandboxNetworkAccess(): boolean {
@@ -42,7 +42,8 @@ Use the following guidelines to optimize your search and read patterns.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
- **Conventions & Style:** Rigorously adhere to existing workspace conventions, architectural patterns, and style (naming, formatting, typing, commenting). During the research phase, analyze surrounding files, tests, and configuration to ensure your changes are seamless, idiomatic, and consistent with the local context. Never compromise idiomatic quality or completeness (e.g., proper declarations, type safety, documentation) to minimize tool calls; all supporting changes required by local conventions are part of a surgical update.
- **Types, warnings and linters:** NEVER use hacks like disabling or suppressing warnings or bypassing the type system (i.e.: casts in TypeScript) unless explicitly instructed to by the user. Instead, use idiomatic language features (e.g.: type guard functions).
- **Types, warnings and linters:** NEVER use hacks like disabling or suppressing warnings, bypassing the type system (e.g.: casts in TypeScript), or employing "hidden" logic (e.g.: reflection, prototype manipulation) unless explicitly instructed to by the user. Instead, use explicit and idiomatic language features (e.g.: type guards, explicit class instantiation, or object spread) that maintain structural integrity and type safety.
- **Design Patterns:** Prioritize explicit composition and delegation (e.g.: wrapper classes, proxies, or factory functions) over complex inheritance or prototype-based cloning. When extending or modifying existing classes, prefer patterns that are easily traceable and type-safe.
- **Libraries/Frameworks:** NEVER assume a library/framework is available. Verify its established usage within the project (check imports, configuration files like 'package.json', 'Cargo.toml', 'requirements.txt', etc.) before employing it.
- **Technical Integrity:** You are responsible for the entire lifecycle: implementation, testing, and validation. Within the scope of your changes, prioritize readability and long-term maintainability by consolidating logic into clean abstractions rather than threading state across unrelated layers. Align strictly with the requested architectural direction, ensuring the final implementation is focused and free of redundant "just-in-case" alternatives. Validation is not merely running tests; it is the exhaustive process of ensuring that every aspect of your change—behavioral, structural, and stylistic—is correct and fully compatible with the broader project. For bug fixes, you must empirically reproduce the failure with a new test case or reproduction script before applying the fix.
- **Expertise & Intent Alignment:** Provide proactive technical opinions grounded in research while strictly adhering to the user's intended workflow. Distinguish between **Directives** (unambiguous requests for action or implementation) and **Inquiries** (requests for analysis, advice, or observations). Assume all requests are Inquiries unless they contain an explicit instruction to perform a task. For Inquiries, your scope is strictly limited to research and analysis; you may propose a solution or strategy, but you MUST NOT modify files until a corresponding Directive is issued. Do not initiate implementation based on observations of bugs or statements of fact. Once an Inquiry is resolved, or while waiting for a Directive, stop and wait for the next user instruction. For Directives, only clarify if critically underspecified; otherwise, work autonomously. You should only seek user intervention if you have exhausted all possible routes or if a proposed solution would take the workspace in a significantly different architectural direction.
@@ -219,7 +220,8 @@ Use the following guidelines to optimize your search and read patterns.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
- **Conventions & Style:** Rigorously adhere to existing workspace conventions, architectural patterns, and style (naming, formatting, typing, commenting). During the research phase, analyze surrounding files, tests, and configuration to ensure your changes are seamless, idiomatic, and consistent with the local context. Never compromise idiomatic quality or completeness (e.g., proper declarations, type safety, documentation) to minimize tool calls; all supporting changes required by local conventions are part of a surgical update.
- **Types, warnings and linters:** NEVER use hacks like disabling or suppressing warnings or bypassing the type system (i.e.: casts in TypeScript) unless explicitly instructed to by the user. Instead, use idiomatic language features (e.g.: type guard functions).
- **Types, warnings and linters:** NEVER use hacks like disabling or suppressing warnings, bypassing the type system (e.g.: casts in TypeScript), or employing "hidden" logic (e.g.: reflection, prototype manipulation) unless explicitly instructed to by the user. Instead, use explicit and idiomatic language features (e.g.: type guards, explicit class instantiation, or object spread) that maintain structural integrity and type safety.
- **Design Patterns:** Prioritize explicit composition and delegation (e.g.: wrapper classes, proxies, or factory functions) over complex inheritance or prototype-based cloning. When extending or modifying existing classes, prefer patterns that are easily traceable and type-safe.
- **Libraries/Frameworks:** NEVER assume a library/framework is available. Verify its established usage within the project (check imports, configuration files like 'package.json', 'Cargo.toml', 'requirements.txt', etc.) before employing it.
- **Technical Integrity:** You are responsible for the entire lifecycle: implementation, testing, and validation. Within the scope of your changes, prioritize readability and long-term maintainability by consolidating logic into clean abstractions rather than threading state across unrelated layers. Align strictly with the requested architectural direction, ensuring the final implementation is focused and free of redundant "just-in-case" alternatives. Validation is not merely running tests; it is the exhaustive process of ensuring that every aspect of your change—behavioral, structural, and stylistic—is correct and fully compatible with the broader project. For bug fixes, you must empirically reproduce the failure with a new test case or reproduction script before applying the fix.
- **Expertise & Intent Alignment:** Provide proactive technical opinions grounded in research while strictly adhering to the user's intended workflow. Distinguish between **Directives** (unambiguous requests for action or implementation) and **Inquiries** (requests for analysis, advice, or observations). Assume all requests are Inquiries unless they contain an explicit instruction to perform a task. For Inquiries, your scope is strictly limited to research and analysis; you may propose a solution or strategy, but you MUST NOT modify files until a corresponding Directive is issued. Do not initiate implementation based on observations of bugs or statements of fact. Once an Inquiry is resolved, or while waiting for a Directive, stop and wait for the next user instruction. For Directives, only clarify if critically underspecified; otherwise, work autonomously. You should only seek user intervention if you have exhausted all possible routes or if a proposed solution would take the workspace in a significantly different architectural direction.
@@ -369,6 +371,8 @@ exports[`Core System Prompt (prompts.ts) > ApprovalMode in System Prompt > shoul
- **Libraries/Frameworks:** NEVER assume a library/framework is available or appropriate. Verify its established usage within the project (check imports, configuration files like 'package.json', 'Cargo.toml', 'requirements.txt', 'build.gradle', etc., or observe neighboring files) before employing it.
- **Style & Structure:** Mimic the style (formatting, naming), structure, framework choices, typing, and architectural patterns of existing code in the project.
- **Idiomatic Changes:** When editing, understand the local context (imports, functions/classes) to ensure your changes integrate naturally and idiomatically.
- **Types, Warnings & Linters:** NEVER use hacks like disabling or suppressing warnings, bypassing the type system (e.g.: casts in TypeScript), or employing "hidden" logic (e.g.: reflection, prototype manipulation) unless explicitly instructed to by the user. Instead, use explicit and idiomatic language features (e.g.: type guards, explicit class instantiation, or object spread) that maintain structural integrity and type safety.
- **Design Patterns:** Prioritize explicit composition and delegation (e.g.: wrapper classes, proxies, or factory functions) over complex inheritance or prototype-based cloning. When extending or modifying existing classes, prefer patterns that are easily traceable and type-safe.
- **Comments:** Add code comments sparingly. Focus on *why* something is done, especially for complex logic, rather than *what* is done. Only add high-value comments if necessary for clarity or if requested by the user. Do not edit comments that are separate from the code you are changing. *NEVER* talk to the user or describe your changes through comments.
- **Proactiveness:** Fulfill the user's request thoroughly. When adding features or fixing bugs, this includes adding tests to ensure quality. Consider all created files, especially tests, to be permanent artifacts unless the user says otherwise.
- **User Hints:** During execution, the user may provide real-time hints (marked as "User hint:" or "User hints:"). Treat these as high-priority but scope-preserving course corrections: apply the minimal plan change needed, keep unaffected user tasks active, and never cancel/skip tasks unless cancellation is explicit for those tasks. Hints may add new tasks, modify one or more tasks, cancel specific tasks, or provide extra context only. If scope is ambiguous, ask for clarification before dropping work.
@@ -515,7 +519,8 @@ Use the following guidelines to optimize your search and read patterns.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
- **Conventions & Style:** Rigorously adhere to existing workspace conventions, architectural patterns, and style (naming, formatting, typing, commenting). During the research phase, analyze surrounding files, tests, and configuration to ensure your changes are seamless, idiomatic, and consistent with the local context. Never compromise idiomatic quality or completeness (e.g., proper declarations, type safety, documentation) to minimize tool calls; all supporting changes required by local conventions are part of a surgical update.
- **Types, warnings and linters:** NEVER use hacks like disabling or suppressing warnings or bypassing the type system (i.e.: casts in TypeScript) unless explicitly instructed to by the user. Instead, use idiomatic language features (e.g.: type guard functions).
- **Types, warnings and linters:** NEVER use hacks like disabling or suppressing warnings, bypassing the type system (e.g.: casts in TypeScript), or employing "hidden" logic (e.g.: reflection, prototype manipulation) unless explicitly instructed to by the user. Instead, use explicit and idiomatic language features (e.g.: type guards, explicit class instantiation, or object spread) that maintain structural integrity and type safety.
- **Design Patterns:** Prioritize explicit composition and delegation (e.g.: wrapper classes, proxies, or factory functions) over complex inheritance or prototype-based cloning. When extending or modifying existing classes, prefer patterns that are easily traceable and type-safe.
- **Libraries/Frameworks:** NEVER assume a library/framework is available. Verify its established usage within the project (check imports, configuration files like 'package.json', 'Cargo.toml', 'requirements.txt', etc.) before employing it.
- **Technical Integrity:** You are responsible for the entire lifecycle: implementation, testing, and validation. Within the scope of your changes, prioritize readability and long-term maintainability by consolidating logic into clean abstractions rather than threading state across unrelated layers. Align strictly with the requested architectural direction, ensuring the final implementation is focused and free of redundant "just-in-case" alternatives. Validation is not merely running tests; it is the exhaustive process of ensuring that every aspect of your change—behavioral, structural, and stylistic—is correct and fully compatible with the broader project. For bug fixes, you must empirically reproduce the failure with a new test case or reproduction script before applying the fix.
- **Expertise & Intent Alignment:** Provide proactive technical opinions grounded in research while strictly adhering to the user's intended workflow. Distinguish between **Directives** (unambiguous requests for action or implementation) and **Inquiries** (requests for analysis, advice, or observations). Assume all requests are Inquiries unless they contain an explicit instruction to perform a task. For Inquiries, your scope is strictly limited to research and analysis; you may propose a solution or strategy, but you MUST NOT modify files until a corresponding Directive is issued. Do not initiate implementation based on observations of bugs or statements of fact. Once an Inquiry is resolved, or while waiting for a Directive, stop and wait for the next user instruction. For Directives, only clarify if critically underspecified; otherwise, work autonomously. You should only seek user intervention if you have exhausted all possible routes or if a proposed solution would take the workspace in a significantly different architectural direction.
@@ -692,7 +697,8 @@ Use the following guidelines to optimize your search and read patterns.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
- **Conventions & Style:** Rigorously adhere to existing workspace conventions, architectural patterns, and style (naming, formatting, typing, commenting). During the research phase, analyze surrounding files, tests, and configuration to ensure your changes are seamless, idiomatic, and consistent with the local context. Never compromise idiomatic quality or completeness (e.g., proper declarations, type safety, documentation) to minimize tool calls; all supporting changes required by local conventions are part of a surgical update.
- **Types, warnings and linters:** NEVER use hacks like disabling or suppressing warnings or bypassing the type system (i.e.: casts in TypeScript) unless explicitly instructed to by the user. Instead, use idiomatic language features (e.g.: type guard functions).
- **Types, warnings and linters:** NEVER use hacks like disabling or suppressing warnings, bypassing the type system (e.g.: casts in TypeScript), or employing "hidden" logic (e.g.: reflection, prototype manipulation) unless explicitly instructed to by the user. Instead, use explicit and idiomatic language features (e.g.: type guards, explicit class instantiation, or object spread) that maintain structural integrity and type safety.
- **Design Patterns:** Prioritize explicit composition and delegation (e.g.: wrapper classes, proxies, or factory functions) over complex inheritance or prototype-based cloning. When extending or modifying existing classes, prefer patterns that are easily traceable and type-safe.
- **Libraries/Frameworks:** NEVER assume a library/framework is available. Verify its established usage within the project (check imports, configuration files like 'package.json', 'Cargo.toml', 'requirements.txt', etc.) before employing it.
- **Technical Integrity:** You are responsible for the entire lifecycle: implementation, testing, and validation. Within the scope of your changes, prioritize readability and long-term maintainability by consolidating logic into clean abstractions rather than threading state across unrelated layers. Align strictly with the requested architectural direction, ensuring the final implementation is focused and free of redundant "just-in-case" alternatives. Validation is not merely running tests; it is the exhaustive process of ensuring that every aspect of your change—behavioral, structural, and stylistic—is correct and fully compatible with the broader project. For bug fixes, you must empirically reproduce the failure with a new test case or reproduction script before applying the fix.
- **Expertise & Intent Alignment:** Provide proactive technical opinions grounded in research while strictly adhering to the user's intended workflow. Distinguish between **Directives** (unambiguous requests for action or implementation) and **Inquiries** (requests for analysis, advice, or observations). Assume all requests are Inquiries unless they contain an explicit instruction to perform a task. For Inquiries, your scope is strictly limited to research and analysis; you may propose a solution or strategy, but you MUST NOT modify files until a corresponding Directive is issued. Do not initiate implementation based on observations of bugs or statements of fact. Once an Inquiry is resolved, or while waiting for a Directive, stop and wait for the next user instruction. For Directives, only clarify if critically underspecified; otherwise, work autonomously. You should only seek user intervention if you have exhausted all possible routes or if a proposed solution would take the workspace in a significantly different architectural direction.
@@ -867,7 +873,8 @@ Use the following guidelines to optimize your search and read patterns.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
- **Conventions & Style:** Rigorously adhere to existing workspace conventions, architectural patterns, and style (naming, formatting, typing, commenting). During the research phase, analyze surrounding files, tests, and configuration to ensure your changes are seamless, idiomatic, and consistent with the local context. Never compromise idiomatic quality or completeness (e.g., proper declarations, type safety, documentation) to minimize tool calls; all supporting changes required by local conventions are part of a surgical update.
- **Types, warnings and linters:** NEVER use hacks like disabling or suppressing warnings or bypassing the type system (i.e.: casts in TypeScript) unless explicitly instructed to by the user. Instead, use idiomatic language features (e.g.: type guard functions).
- **Types, warnings and linters:** NEVER use hacks like disabling or suppressing warnings, bypassing the type system (e.g.: casts in TypeScript), or employing "hidden" logic (e.g.: reflection, prototype manipulation) unless explicitly instructed to by the user. Instead, use explicit and idiomatic language features (e.g.: type guards, explicit class instantiation, or object spread) that maintain structural integrity and type safety.
- **Design Patterns:** Prioritize explicit composition and delegation (e.g.: wrapper classes, proxies, or factory functions) over complex inheritance or prototype-based cloning. When extending or modifying existing classes, prefer patterns that are easily traceable and type-safe.
- **Libraries/Frameworks:** NEVER assume a library/framework is available. Verify its established usage within the project (check imports, configuration files like 'package.json', 'Cargo.toml', 'requirements.txt', etc.) before employing it.
- **Technical Integrity:** You are responsible for the entire lifecycle: implementation, testing, and validation. Within the scope of your changes, prioritize readability and long-term maintainability by consolidating logic into clean abstractions rather than threading state across unrelated layers. Align strictly with the requested architectural direction, ensuring the final implementation is focused and free of redundant "just-in-case" alternatives. Validation is not merely running tests; it is the exhaustive process of ensuring that every aspect of your change—behavioral, structural, and stylistic—is correct and fully compatible with the broader project. For bug fixes, you must empirically reproduce the failure with a new test case or reproduction script before applying the fix.
- **Expertise & Intent Alignment:** Provide proactive technical opinions grounded in research while strictly adhering to the user's intended workflow. Distinguish between **Directives** (unambiguous requests for action or implementation) and **Inquiries** (requests for analysis, advice, or observations). Assume all requests are Inquiries unless they contain an explicit instruction to perform a task. For Inquiries, your scope is strictly limited to research and analysis; you may propose a solution or strategy, but you MUST NOT modify files until a corresponding Directive is issued. Do not initiate implementation based on observations of bugs or statements of fact. Once an Inquiry is resolved, or while waiting for a Directive, stop and wait for the next user instruction. For Directives, you must work autonomously as no further user input is available. You should only seek user intervention if you have exhausted all possible routes or if a proposed solution would take the workspace in a significantly different architectural direction.
@@ -994,7 +1001,8 @@ Use the following guidelines to optimize your search and read patterns.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
- **Conventions & Style:** Rigorously adhere to existing workspace conventions, architectural patterns, and style (naming, formatting, typing, commenting). During the research phase, analyze surrounding files, tests, and configuration to ensure your changes are seamless, idiomatic, and consistent with the local context. Never compromise idiomatic quality or completeness (e.g., proper declarations, type safety, documentation) to minimize tool calls; all supporting changes required by local conventions are part of a surgical update.
- **Types, warnings and linters:** NEVER use hacks like disabling or suppressing warnings or bypassing the type system (i.e.: casts in TypeScript) unless explicitly instructed to by the user. Instead, use idiomatic language features (e.g.: type guard functions).
- **Types, warnings and linters:** NEVER use hacks like disabling or suppressing warnings, bypassing the type system (e.g.: casts in TypeScript), or employing "hidden" logic (e.g.: reflection, prototype manipulation) unless explicitly instructed to by the user. Instead, use explicit and idiomatic language features (e.g.: type guards, explicit class instantiation, or object spread) that maintain structural integrity and type safety.
- **Design Patterns:** Prioritize explicit composition and delegation (e.g.: wrapper classes, proxies, or factory functions) over complex inheritance or prototype-based cloning. When extending or modifying existing classes, prefer patterns that are easily traceable and type-safe.
- **Libraries/Frameworks:** NEVER assume a library/framework is available. Verify its established usage within the project (check imports, configuration files like 'package.json', 'Cargo.toml', 'requirements.txt', etc.) before employing it.
- **Technical Integrity:** You are responsible for the entire lifecycle: implementation, testing, and validation. Within the scope of your changes, prioritize readability and long-term maintainability by consolidating logic into clean abstractions rather than threading state across unrelated layers. Align strictly with the requested architectural direction, ensuring the final implementation is focused and free of redundant "just-in-case" alternatives. Validation is not merely running tests; it is the exhaustive process of ensuring that every aspect of your change—behavioral, structural, and stylistic—is correct and fully compatible with the broader project. For bug fixes, you must empirically reproduce the failure with a new test case or reproduction script before applying the fix.
- **Expertise & Intent Alignment:** Provide proactive technical opinions grounded in research while strictly adhering to the user's intended workflow. Distinguish between **Directives** (unambiguous requests for action or implementation) and **Inquiries** (requests for analysis, advice, or observations). Assume all requests are Inquiries unless they contain an explicit instruction to perform a task. For Inquiries, your scope is strictly limited to research and analysis; you may propose a solution or strategy, but you MUST NOT modify files until a corresponding Directive is issued. Do not initiate implementation based on observations of bugs or statements of fact. Once an Inquiry is resolved, or while waiting for a Directive, stop and wait for the next user instruction. For Directives, you must work autonomously as no further user input is available. You should only seek user intervention if you have exhausted all possible routes or if a proposed solution would take the workspace in a significantly different architectural direction.
@@ -1088,6 +1096,8 @@ exports[`Core System Prompt (prompts.ts) > should handle git instructions when i
- **Libraries/Frameworks:** NEVER assume a library/framework is available or appropriate. Verify its established usage within the project (check imports, configuration files like 'package.json', 'Cargo.toml', 'requirements.txt', 'build.gradle', etc., or observe neighboring files) before employing it.
- **Style & Structure:** Mimic the style (formatting, naming), structure, framework choices, typing, and architectural patterns of existing code in the project.
- **Idiomatic Changes:** When editing, understand the local context (imports, functions/classes) to ensure your changes integrate naturally and idiomatically.
- **Types, Warnings & Linters:** NEVER use hacks like disabling or suppressing warnings, bypassing the type system (e.g.: casts in TypeScript), or employing "hidden" logic (e.g.: reflection, prototype manipulation) unless explicitly instructed to by the user. Instead, use explicit and idiomatic language features (e.g.: type guards, explicit class instantiation, or object spread) that maintain structural integrity and type safety.
- **Design Patterns:** Prioritize explicit composition and delegation (e.g.: wrapper classes, proxies, or factory functions) over complex inheritance or prototype-based cloning. When extending or modifying existing classes, prefer patterns that are easily traceable and type-safe.
- **Comments:** Add code comments sparingly. Focus on *why* something is done, especially for complex logic, rather than *what* is done. Only add high-value comments if necessary for clarity or if requested by the user. Do not edit comments that are separate from the code you are changing. *NEVER* talk to the user or describe your changes through comments.
- **Proactiveness:** Fulfill the user's request thoroughly. When adding features or fixing bugs, this includes adding tests to ensure quality. Consider all created files, especially tests, to be permanent artifacts unless the user says otherwise.
- **User Hints:** During execution, the user may provide real-time hints (marked as "User hint:" or "User hints:"). Treat these as high-priority but scope-preserving course corrections: apply the minimal plan change needed, keep unaffected user tasks active, and never cancel/skip tasks unless cancellation is explicit for those tasks. Hints may add new tasks, modify one or more tasks, cancel specific tasks, or provide extra context only. If scope is ambiguous, ask for clarification before dropping work.
@@ -1201,6 +1211,8 @@ exports[`Core System Prompt (prompts.ts) > should handle git instructions when i
- **Libraries/Frameworks:** NEVER assume a library/framework is available or appropriate. Verify its established usage within the project (check imports, configuration files like 'package.json', 'Cargo.toml', 'requirements.txt', 'build.gradle', etc., or observe neighboring files) before employing it.
- **Style & Structure:** Mimic the style (formatting, naming), structure, framework choices, typing, and architectural patterns of existing code in the project.
- **Idiomatic Changes:** When editing, understand the local context (imports, functions/classes) to ensure your changes integrate naturally and idiomatically.
- **Types, Warnings & Linters:** NEVER use hacks like disabling or suppressing warnings, bypassing the type system (e.g.: casts in TypeScript), or employing "hidden" logic (e.g.: reflection, prototype manipulation) unless explicitly instructed to by the user. Instead, use explicit and idiomatic language features (e.g.: type guards, explicit class instantiation, or object spread) that maintain structural integrity and type safety.
- **Design Patterns:** Prioritize explicit composition and delegation (e.g.: wrapper classes, proxies, or factory functions) over complex inheritance or prototype-based cloning. When extending or modifying existing classes, prefer patterns that are easily traceable and type-safe.
- **Comments:** Add code comments sparingly. Focus on *why* something is done, especially for complex logic, rather than *what* is done. Only add high-value comments if necessary for clarity or if requested by the user. Do not edit comments that are separate from the code you are changing. *NEVER* talk to the user or describe your changes through comments.
- **Proactiveness:** Fulfill the user's request thoroughly. When adding features or fixing bugs, this includes adding tests to ensure quality. Consider all created files, especially tests, to be permanent artifacts unless the user says otherwise.
- **User Hints:** During execution, the user may provide real-time hints (marked as "User hint:" or "User hints:"). Treat these as high-priority but scope-preserving course corrections: apply the minimal plan change needed, keep unaffected user tasks active, and never cancel/skip tasks unless cancellation is explicit for those tasks. Hints may add new tasks, modify one or more tasks, cancel specific tasks, or provide extra context only. If scope is ambiguous, ask for clarification before dropping work.
@@ -1332,6 +1344,8 @@ exports[`Core System Prompt (prompts.ts) > should include approved plan instruct
- **Libraries/Frameworks:** NEVER assume a library/framework is available or appropriate. Verify its established usage within the project (check imports, configuration files like 'package.json', 'Cargo.toml', 'requirements.txt', 'build.gradle', etc., or observe neighboring files) before employing it.
- **Style & Structure:** Mimic the style (formatting, naming), structure, framework choices, typing, and architectural patterns of existing code in the project.
- **Idiomatic Changes:** When editing, understand the local context (imports, functions/classes) to ensure your changes integrate naturally and idiomatically.
- **Types, Warnings & Linters:** NEVER use hacks like disabling or suppressing warnings, bypassing the type system (e.g.: casts in TypeScript), or employing "hidden" logic (e.g.: reflection, prototype manipulation) unless explicitly instructed to by the user. Instead, use explicit and idiomatic language features (e.g.: type guards, explicit class instantiation, or object spread) that maintain structural integrity and type safety.
- **Design Patterns:** Prioritize explicit composition and delegation (e.g.: wrapper classes, proxies, or factory functions) over complex inheritance or prototype-based cloning. When extending or modifying existing classes, prefer patterns that are easily traceable and type-safe.
- **Comments:** Add code comments sparingly. Focus on *why* something is done, especially for complex logic, rather than *what* is done. Only add high-value comments if necessary for clarity or if requested by the user. Do not edit comments that are separate from the code you are changing. *NEVER* talk to the user or describe your changes through comments.
- **Proactiveness:** Fulfill the user's request thoroughly. When adding features or fixing bugs, this includes adding tests to ensure quality. Consider all created files, especially tests, to be permanent artifacts unless the user says otherwise.
- **User Hints:** During execution, the user may provide real-time hints (marked as "User hint:" or "User hints:"). Treat these as high-priority but scope-preserving course corrections: apply the minimal plan change needed, keep unaffected user tasks active, and never cancel/skip tasks unless cancellation is explicit for those tasks. Hints may add new tasks, modify one or more tasks, cancel specific tasks, or provide extra context only. If scope is ambiguous, ask for clarification before dropping work.
@@ -1435,6 +1449,8 @@ exports[`Core System Prompt (prompts.ts) > should include available_skills when
- **Libraries/Frameworks:** NEVER assume a library/framework is available or appropriate. Verify its established usage within the project (check imports, configuration files like 'package.json', 'Cargo.toml', 'requirements.txt', 'build.gradle', etc., or observe neighboring files) before employing it.
- **Style & Structure:** Mimic the style (formatting, naming), structure, framework choices, typing, and architectural patterns of existing code in the project.
- **Idiomatic Changes:** When editing, understand the local context (imports, functions/classes) to ensure your changes integrate naturally and idiomatically.
- **Types, Warnings & Linters:** NEVER use hacks like disabling or suppressing warnings, bypassing the type system (e.g.: casts in TypeScript), or employing "hidden" logic (e.g.: reflection, prototype manipulation) unless explicitly instructed to by the user. Instead, use explicit and idiomatic language features (e.g.: type guards, explicit class instantiation, or object spread) that maintain structural integrity and type safety.
- **Design Patterns:** Prioritize explicit composition and delegation (e.g.: wrapper classes, proxies, or factory functions) over complex inheritance or prototype-based cloning. When extending or modifying existing classes, prefer patterns that are easily traceable and type-safe.
- **Comments:** Add code comments sparingly. Focus on *why* something is done, especially for complex logic, rather than *what* is done. Only add high-value comments if necessary for clarity or if requested by the user. Do not edit comments that are separate from the code you are changing. *NEVER* talk to the user or describe your changes through comments.
- **Proactiveness:** Fulfill the user's request thoroughly. When adding features or fixing bugs, this includes adding tests to ensure quality. Consider all created files, especially tests, to be permanent artifacts unless the user says otherwise.
- **User Hints:** During execution, the user may provide real-time hints (marked as "User hint:" or "User hints:"). Treat these as high-priority but scope-preserving course corrections: apply the minimal plan change needed, keep unaffected user tasks active, and never cancel/skip tasks unless cancellation is explicit for those tasks. Hints may add new tasks, modify one or more tasks, cancel specific tasks, or provide extra context only. If scope is ambiguous, ask for clarification before dropping work.
@@ -1594,7 +1610,8 @@ Use the following guidelines to optimize your search and read patterns.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
- **Conventions & Style:** Rigorously adhere to existing workspace conventions, architectural patterns, and style (naming, formatting, typing, commenting). During the research phase, analyze surrounding files, tests, and configuration to ensure your changes are seamless, idiomatic, and consistent with the local context. Never compromise idiomatic quality or completeness (e.g., proper declarations, type safety, documentation) to minimize tool calls; all supporting changes required by local conventions are part of a surgical update.
- **Types, warnings and linters:** NEVER use hacks like disabling or suppressing warnings or bypassing the type system (i.e.: casts in TypeScript) unless explicitly instructed to by the user. Instead, use idiomatic language features (e.g.: type guard functions).
- **Types, warnings and linters:** NEVER use hacks like disabling or suppressing warnings, bypassing the type system (e.g.: casts in TypeScript), or employing "hidden" logic (e.g.: reflection, prototype manipulation) unless explicitly instructed to by the user. Instead, use explicit and idiomatic language features (e.g.: type guards, explicit class instantiation, or object spread) that maintain structural integrity and type safety.
- **Design Patterns:** Prioritize explicit composition and delegation (e.g.: wrapper classes, proxies, or factory functions) over complex inheritance or prototype-based cloning. When extending or modifying existing classes, prefer patterns that are easily traceable and type-safe.
- **Libraries/Frameworks:** NEVER assume a library/framework is available. Verify its established usage within the project (check imports, configuration files like 'package.json', 'Cargo.toml', 'requirements.txt', etc.) before employing it.
- **Technical Integrity:** You are responsible for the entire lifecycle: implementation, testing, and validation. Within the scope of your changes, prioritize readability and long-term maintainability by consolidating logic into clean abstractions rather than threading state across unrelated layers. Align strictly with the requested architectural direction, ensuring the final implementation is focused and free of redundant "just-in-case" alternatives. Validation is not merely running tests; it is the exhaustive process of ensuring that every aspect of your change—behavioral, structural, and stylistic—is correct and fully compatible with the broader project. For bug fixes, you must empirically reproduce the failure with a new test case or reproduction script before applying the fix.
- **Expertise & Intent Alignment:** Provide proactive technical opinions grounded in research while strictly adhering to the user's intended workflow. Distinguish between **Directives** (unambiguous requests for action or implementation) and **Inquiries** (requests for analysis, advice, or observations). Assume all requests are Inquiries unless they contain an explicit instruction to perform a task. For Inquiries, your scope is strictly limited to research and analysis; you may propose a solution or strategy, but you MUST NOT modify files until a corresponding Directive is issued. Do not initiate implementation based on observations of bugs or statements of fact. Once an Inquiry is resolved, or while waiting for a Directive, stop and wait for the next user instruction. For Directives, only clarify if critically underspecified; otherwise, work autonomously. You should only seek user intervention if you have exhausted all possible routes or if a proposed solution would take the workspace in a significantly different architectural direction.
@@ -1765,7 +1782,8 @@ Use the following guidelines to optimize your search and read patterns.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
- **Conventions & Style:** Rigorously adhere to existing workspace conventions, architectural patterns, and style (naming, formatting, typing, commenting). During the research phase, analyze surrounding files, tests, and configuration to ensure your changes are seamless, idiomatic, and consistent with the local context. Never compromise idiomatic quality or completeness (e.g., proper declarations, type safety, documentation) to minimize tool calls; all supporting changes required by local conventions are part of a surgical update.
- **Types, warnings and linters:** NEVER use hacks like disabling or suppressing warnings or bypassing the type system (i.e.: casts in TypeScript) unless explicitly instructed to by the user. Instead, use idiomatic language features (e.g.: type guard functions).
- **Types, warnings and linters:** NEVER use hacks like disabling or suppressing warnings, bypassing the type system (e.g.: casts in TypeScript), or employing "hidden" logic (e.g.: reflection, prototype manipulation) unless explicitly instructed to by the user. Instead, use explicit and idiomatic language features (e.g.: type guards, explicit class instantiation, or object spread) that maintain structural integrity and type safety.
- **Design Patterns:** Prioritize explicit composition and delegation (e.g.: wrapper classes, proxies, or factory functions) over complex inheritance or prototype-based cloning. When extending or modifying existing classes, prefer patterns that are easily traceable and type-safe.
- **Libraries/Frameworks:** NEVER assume a library/framework is available. Verify its established usage within the project (check imports, configuration files like 'package.json', 'Cargo.toml', 'requirements.txt', etc.) before employing it.
- **Technical Integrity:** You are responsible for the entire lifecycle: implementation, testing, and validation. Within the scope of your changes, prioritize readability and long-term maintainability by consolidating logic into clean abstractions rather than threading state across unrelated layers. Align strictly with the requested architectural direction, ensuring the final implementation is focused and free of redundant "just-in-case" alternatives. Validation is not merely running tests; it is the exhaustive process of ensuring that every aspect of your change—behavioral, structural, and stylistic—is correct and fully compatible with the broader project. For bug fixes, you must empirically reproduce the failure with a new test case or reproduction script before applying the fix.
- **Expertise & Intent Alignment:** Provide proactive technical opinions grounded in research while strictly adhering to the user's intended workflow. Distinguish between **Directives** (unambiguous requests for action or implementation) and **Inquiries** (requests for analysis, advice, or observations). Assume all requests are Inquiries unless they contain an explicit instruction to perform a task. For Inquiries, your scope is strictly limited to research and analysis; you may propose a solution or strategy, but you MUST NOT modify files until a corresponding Directive is issued. Do not initiate implementation based on observations of bugs or statements of fact. Once an Inquiry is resolved, or while waiting for a Directive, stop and wait for the next user instruction. For Directives, only clarify if critically underspecified; otherwise, work autonomously. You should only seek user intervention if you have exhausted all possible routes or if a proposed solution would take the workspace in a significantly different architectural direction.
@@ -1927,7 +1945,8 @@ Use the following guidelines to optimize your search and read patterns.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
- **Conventions & Style:** Rigorously adhere to existing workspace conventions, architectural patterns, and style (naming, formatting, typing, commenting). During the research phase, analyze surrounding files, tests, and configuration to ensure your changes are seamless, idiomatic, and consistent with the local context. Never compromise idiomatic quality or completeness (e.g., proper declarations, type safety, documentation) to minimize tool calls; all supporting changes required by local conventions are part of a surgical update.
- **Types, warnings and linters:** NEVER use hacks like disabling or suppressing warnings or bypassing the type system (i.e.: casts in TypeScript) unless explicitly instructed to by the user. Instead, use idiomatic language features (e.g.: type guard functions).
- **Types, warnings and linters:** NEVER use hacks like disabling or suppressing warnings, bypassing the type system (e.g.: casts in TypeScript), or employing "hidden" logic (e.g.: reflection, prototype manipulation) unless explicitly instructed to by the user. Instead, use explicit and idiomatic language features (e.g.: type guards, explicit class instantiation, or object spread) that maintain structural integrity and type safety.
- **Design Patterns:** Prioritize explicit composition and delegation (e.g.: wrapper classes, proxies, or factory functions) over complex inheritance or prototype-based cloning. When extending or modifying existing classes, prefer patterns that are easily traceable and type-safe.
- **Libraries/Frameworks:** NEVER assume a library/framework is available. Verify its established usage within the project (check imports, configuration files like 'package.json', 'Cargo.toml', 'requirements.txt', etc.) before employing it.
- **Technical Integrity:** You are responsible for the entire lifecycle: implementation, testing, and validation. Within the scope of your changes, prioritize readability and long-term maintainability by consolidating logic into clean abstractions rather than threading state across unrelated layers. Align strictly with the requested architectural direction, ensuring the final implementation is focused and free of redundant "just-in-case" alternatives. Validation is not merely running tests; it is the exhaustive process of ensuring that every aspect of your change—behavioral, structural, and stylistic—is correct and fully compatible with the broader project. For bug fixes, you must empirically reproduce the failure with a new test case or reproduction script before applying the fix.
- **Expertise & Intent Alignment:** Provide proactive technical opinions grounded in research while strictly adhering to the user's intended workflow. Distinguish between **Directives** (unambiguous requests for action or implementation) and **Inquiries** (requests for analysis, advice, or observations). Assume all requests are Inquiries unless they contain an explicit instruction to perform a task. For Inquiries, your scope is strictly limited to research and analysis; you may propose a solution or strategy, but you MUST NOT modify files until a corresponding Directive is issued. Do not initiate implementation based on observations of bugs or statements of fact. Once an Inquiry is resolved, or while waiting for a Directive, stop and wait for the next user instruction. For Directives, only clarify if critically underspecified; otherwise, work autonomously. You should only seek user intervention if you have exhausted all possible routes or if a proposed solution would take the workspace in a significantly different architectural direction.
@@ -2089,7 +2108,8 @@ Use the following guidelines to optimize your search and read patterns.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
- **Conventions & Style:** Rigorously adhere to existing workspace conventions, architectural patterns, and style (naming, formatting, typing, commenting). During the research phase, analyze surrounding files, tests, and configuration to ensure your changes are seamless, idiomatic, and consistent with the local context. Never compromise idiomatic quality or completeness (e.g., proper declarations, type safety, documentation) to minimize tool calls; all supporting changes required by local conventions are part of a surgical update.
- **Types, warnings and linters:** NEVER use hacks like disabling or suppressing warnings or bypassing the type system (i.e.: casts in TypeScript) unless explicitly instructed to by the user. Instead, use idiomatic language features (e.g.: type guard functions).
- **Types, warnings and linters:** NEVER use hacks like disabling or suppressing warnings, bypassing the type system (e.g.: casts in TypeScript), or employing "hidden" logic (e.g.: reflection, prototype manipulation) unless explicitly instructed to by the user. Instead, use explicit and idiomatic language features (e.g.: type guards, explicit class instantiation, or object spread) that maintain structural integrity and type safety.
- **Design Patterns:** Prioritize explicit composition and delegation (e.g.: wrapper classes, proxies, or factory functions) over complex inheritance or prototype-based cloning. When extending or modifying existing classes, prefer patterns that are easily traceable and type-safe.
- **Libraries/Frameworks:** NEVER assume a library/framework is available. Verify its established usage within the project (check imports, configuration files like 'package.json', 'Cargo.toml', 'requirements.txt', etc.) before employing it.
- **Technical Integrity:** You are responsible for the entire lifecycle: implementation, testing, and validation. Within the scope of your changes, prioritize readability and long-term maintainability by consolidating logic into clean abstractions rather than threading state across unrelated layers. Align strictly with the requested architectural direction, ensuring the final implementation is focused and free of redundant "just-in-case" alternatives. Validation is not merely running tests; it is the exhaustive process of ensuring that every aspect of your change—behavioral, structural, and stylistic—is correct and fully compatible with the broader project. For bug fixes, you must empirically reproduce the failure with a new test case or reproduction script before applying the fix.
- **Expertise & Intent Alignment:** Provide proactive technical opinions grounded in research while strictly adhering to the user's intended workflow. Distinguish between **Directives** (unambiguous requests for action or implementation) and **Inquiries** (requests for analysis, advice, or observations). Assume all requests are Inquiries unless they contain an explicit instruction to perform a task. For Inquiries, your scope is strictly limited to research and analysis; you may propose a solution or strategy, but you MUST NOT modify files until a corresponding Directive is issued. Do not initiate implementation based on observations of bugs or statements of fact. Once an Inquiry is resolved, or while waiting for a Directive, stop and wait for the next user instruction. For Directives, only clarify if critically underspecified; otherwise, work autonomously. You should only seek user intervention if you have exhausted all possible routes or if a proposed solution would take the workspace in a significantly different architectural direction.
@@ -2247,7 +2267,8 @@ Use the following guidelines to optimize your search and read patterns.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
- **Conventions & Style:** Rigorously adhere to existing workspace conventions, architectural patterns, and style (naming, formatting, typing, commenting). During the research phase, analyze surrounding files, tests, and configuration to ensure your changes are seamless, idiomatic, and consistent with the local context. Never compromise idiomatic quality or completeness (e.g., proper declarations, type safety, documentation) to minimize tool calls; all supporting changes required by local conventions are part of a surgical update.
- **Types, warnings and linters:** NEVER use hacks like disabling or suppressing warnings or bypassing the type system (i.e.: casts in TypeScript) unless explicitly instructed to by the user. Instead, use idiomatic language features (e.g.: type guard functions).
- **Types, warnings and linters:** NEVER use hacks like disabling or suppressing warnings, bypassing the type system (e.g.: casts in TypeScript), or employing "hidden" logic (e.g.: reflection, prototype manipulation) unless explicitly instructed to by the user. Instead, use explicit and idiomatic language features (e.g.: type guards, explicit class instantiation, or object spread) that maintain structural integrity and type safety.
- **Design Patterns:** Prioritize explicit composition and delegation (e.g.: wrapper classes, proxies, or factory functions) over complex inheritance or prototype-based cloning. When extending or modifying existing classes, prefer patterns that are easily traceable and type-safe.
- **Libraries/Frameworks:** NEVER assume a library/framework is available. Verify its established usage within the project (check imports, configuration files like 'package.json', 'Cargo.toml', 'requirements.txt', etc.) before employing it.
- **Technical Integrity:** You are responsible for the entire lifecycle: implementation, testing, and validation. Within the scope of your changes, prioritize readability and long-term maintainability by consolidating logic into clean abstractions rather than threading state across unrelated layers. Align strictly with the requested architectural direction, ensuring the final implementation is focused and free of redundant "just-in-case" alternatives. Validation is not merely running tests; it is the exhaustive process of ensuring that every aspect of your change—behavioral, structural, and stylistic—is correct and fully compatible with the broader project. For bug fixes, you must empirically reproduce the failure with a new test case or reproduction script before applying the fix.
- **Expertise & Intent Alignment:** Provide proactive technical opinions grounded in research while strictly adhering to the user's intended workflow. Distinguish between **Directives** (unambiguous requests for action or implementation) and **Inquiries** (requests for analysis, advice, or observations). Assume all requests are Inquiries unless they contain an explicit instruction to perform a task. For Inquiries, your scope is strictly limited to research and analysis; you may propose a solution or strategy, but you MUST NOT modify files until a corresponding Directive is issued. Do not initiate implementation based on observations of bugs or statements of fact. Once an Inquiry is resolved, or while waiting for a Directive, stop and wait for the next user instruction. For Directives, only clarify if critically underspecified; otherwise, work autonomously. You should only seek user intervention if you have exhausted all possible routes or if a proposed solution would take the workspace in a significantly different architectural direction.
@@ -2405,7 +2426,8 @@ Use the following guidelines to optimize your search and read patterns.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
- **Conventions & Style:** Rigorously adhere to existing workspace conventions, architectural patterns, and style (naming, formatting, typing, commenting). During the research phase, analyze surrounding files, tests, and configuration to ensure your changes are seamless, idiomatic, and consistent with the local context. Never compromise idiomatic quality or completeness (e.g., proper declarations, type safety, documentation) to minimize tool calls; all supporting changes required by local conventions are part of a surgical update.
- **Types, warnings and linters:** NEVER use hacks like disabling or suppressing warnings or bypassing the type system (i.e.: casts in TypeScript) unless explicitly instructed to by the user. Instead, use idiomatic language features (e.g.: type guard functions).
- **Types, warnings and linters:** NEVER use hacks like disabling or suppressing warnings, bypassing the type system (e.g.: casts in TypeScript), or employing "hidden" logic (e.g.: reflection, prototype manipulation) unless explicitly instructed to by the user. Instead, use explicit and idiomatic language features (e.g.: type guards, explicit class instantiation, or object spread) that maintain structural integrity and type safety.
- **Design Patterns:** Prioritize explicit composition and delegation (e.g.: wrapper classes, proxies, or factory functions) over complex inheritance or prototype-based cloning. When extending or modifying existing classes, prefer patterns that are easily traceable and type-safe.
- **Libraries/Frameworks:** NEVER assume a library/framework is available. Verify its established usage within the project (check imports, configuration files like 'package.json', 'Cargo.toml', 'requirements.txt', etc.) before employing it.
- **Technical Integrity:** You are responsible for the entire lifecycle: implementation, testing, and validation. Within the scope of your changes, prioritize readability and long-term maintainability by consolidating logic into clean abstractions rather than threading state across unrelated layers. Align strictly with the requested architectural direction, ensuring the final implementation is focused and free of redundant "just-in-case" alternatives. Validation is not merely running tests; it is the exhaustive process of ensuring that every aspect of your change—behavioral, structural, and stylistic—is correct and fully compatible with the broader project. For bug fixes, you must empirically reproduce the failure with a new test case or reproduction script before applying the fix.
- **Expertise & Intent Alignment:** Provide proactive technical opinions grounded in research while strictly adhering to the user's intended workflow. Distinguish between **Directives** (unambiguous requests for action or implementation) and **Inquiries** (requests for analysis, advice, or observations). Assume all requests are Inquiries unless they contain an explicit instruction to perform a task. For Inquiries, your scope is strictly limited to research and analysis; you may propose a solution or strategy, but you MUST NOT modify files until a corresponding Directive is issued. Do not initiate implementation based on observations of bugs or statements of fact. Once an Inquiry is resolved, or while waiting for a Directive, stop and wait for the next user instruction. For Directives, only clarify if critically underspecified; otherwise, work autonomously. You should only seek user intervention if you have exhausted all possible routes or if a proposed solution would take the workspace in a significantly different architectural direction.
@@ -2557,7 +2579,8 @@ Use the following guidelines to optimize your search and read patterns.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
- **Conventions & Style:** Rigorously adhere to existing workspace conventions, architectural patterns, and style (naming, formatting, typing, commenting). During the research phase, analyze surrounding files, tests, and configuration to ensure your changes are seamless, idiomatic, and consistent with the local context. Never compromise idiomatic quality or completeness (e.g., proper declarations, type safety, documentation) to minimize tool calls; all supporting changes required by local conventions are part of a surgical update.
- **Types, warnings and linters:** NEVER use hacks like disabling or suppressing warnings or bypassing the type system (i.e.: casts in TypeScript) unless explicitly instructed to by the user. Instead, use idiomatic language features (e.g.: type guard functions).
- **Types, warnings and linters:** NEVER use hacks like disabling or suppressing warnings, bypassing the type system (e.g.: casts in TypeScript), or employing "hidden" logic (e.g.: reflection, prototype manipulation) unless explicitly instructed to by the user. Instead, use explicit and idiomatic language features (e.g.: type guards, explicit class instantiation, or object spread) that maintain structural integrity and type safety.
- **Design Patterns:** Prioritize explicit composition and delegation (e.g.: wrapper classes, proxies, or factory functions) over complex inheritance or prototype-based cloning. When extending or modifying existing classes, prefer patterns that are easily traceable and type-safe.
- **Libraries/Frameworks:** NEVER assume a library/framework is available. Verify its established usage within the project (check imports, configuration files like 'package.json', 'Cargo.toml', 'requirements.txt', etc.) before employing it.
- **Technical Integrity:** You are responsible for the entire lifecycle: implementation, testing, and validation. Within the scope of your changes, prioritize readability and long-term maintainability by consolidating logic into clean abstractions rather than threading state across unrelated layers. Align strictly with the requested architectural direction, ensuring the final implementation is focused and free of redundant "just-in-case" alternatives. Validation is not merely running tests; it is the exhaustive process of ensuring that every aspect of your change—behavioral, structural, and stylistic—is correct and fully compatible with the broader project. For bug fixes, you must empirically reproduce the failure with a new test case or reproduction script before applying the fix.
- **Expertise & Intent Alignment:** Provide proactive technical opinions grounded in research while strictly adhering to the user's intended workflow. Distinguish between **Directives** (unambiguous requests for action or implementation) and **Inquiries** (requests for analysis, advice, or observations). Assume all requests are Inquiries unless they contain an explicit instruction to perform a task. For Inquiries, your scope is strictly limited to research and analysis; you may propose a solution or strategy, but you MUST NOT modify files until a corresponding Directive is issued. Do not initiate implementation based on observations of bugs or statements of fact. Once an Inquiry is resolved, or while waiting for a Directive, stop and wait for the next user instruction. For Directives, only clarify if critically underspecified; otherwise, work autonomously. You should only seek user intervention if you have exhausted all possible routes or if a proposed solution would take the workspace in a significantly different architectural direction.
@@ -2714,7 +2737,8 @@ Use the following guidelines to optimize your search and read patterns.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
- **Conventions & Style:** Rigorously adhere to existing workspace conventions, architectural patterns, and style (naming, formatting, typing, commenting). During the research phase, analyze surrounding files, tests, and configuration to ensure your changes are seamless, idiomatic, and consistent with the local context. Never compromise idiomatic quality or completeness (e.g., proper declarations, type safety, documentation) to minimize tool calls; all supporting changes required by local conventions are part of a surgical update.
- **Types, warnings and linters:** NEVER use hacks like disabling or suppressing warnings or bypassing the type system (i.e.: casts in TypeScript) unless explicitly instructed to by the user. Instead, use idiomatic language features (e.g.: type guard functions).
- **Types, warnings and linters:** NEVER use hacks like disabling or suppressing warnings, bypassing the type system (e.g.: casts in TypeScript), or employing "hidden" logic (e.g.: reflection, prototype manipulation) unless explicitly instructed to by the user. Instead, use explicit and idiomatic language features (e.g.: type guards, explicit class instantiation, or object spread) that maintain structural integrity and type safety.
- **Design Patterns:** Prioritize explicit composition and delegation (e.g.: wrapper classes, proxies, or factory functions) over complex inheritance or prototype-based cloning. When extending or modifying existing classes, prefer patterns that are easily traceable and type-safe.
- **Libraries/Frameworks:** NEVER assume a library/framework is available. Verify its established usage within the project (check imports, configuration files like 'package.json', 'Cargo.toml', 'requirements.txt', etc.) before employing it.
- **Technical Integrity:** You are responsible for the entire lifecycle: implementation, testing, and validation. Within the scope of your changes, prioritize readability and long-term maintainability by consolidating logic into clean abstractions rather than threading state across unrelated layers. Align strictly with the requested architectural direction, ensuring the final implementation is focused and free of redundant "just-in-case" alternatives. Validation is not merely running tests; it is the exhaustive process of ensuring that every aspect of your change—behavioral, structural, and stylistic—is correct and fully compatible with the broader project. For bug fixes, you must empirically reproduce the failure with a new test case or reproduction script before applying the fix.
- **Expertise & Intent Alignment:** Provide proactive technical opinions grounded in research while strictly adhering to the user's intended workflow. Distinguish between **Directives** (unambiguous requests for action or implementation) and **Inquiries** (requests for analysis, advice, or observations). Assume all requests are Inquiries unless they contain an explicit instruction to perform a task. For Inquiries, your scope is strictly limited to research and analysis; you may propose a solution or strategy, but you MUST NOT modify files until a corresponding Directive is issued. Do not initiate implementation based on observations of bugs or statements of fact. Once an Inquiry is resolved, or while waiting for a Directive, stop and wait for the next user instruction. For Directives, only clarify if critically underspecified; otherwise, work autonomously. You should only seek user intervention if you have exhausted all possible routes or if a proposed solution would take the workspace in a significantly different architectural direction.
@@ -2839,6 +2863,8 @@ exports[`Core System Prompt (prompts.ts) > should include the TASK MANAGEMENT PR
- **Libraries/Frameworks:** NEVER assume a library/framework is available or appropriate. Verify its established usage within the project (check imports, configuration files like 'package.json', 'Cargo.toml', 'requirements.txt', 'build.gradle', etc., or observe neighboring files) before employing it.
- **Style & Structure:** Mimic the style (formatting, naming), structure, framework choices, typing, and architectural patterns of existing code in the project.
- **Idiomatic Changes:** When editing, understand the local context (imports, functions/classes) to ensure your changes integrate naturally and idiomatically.
- **Types, Warnings & Linters:** NEVER use hacks like disabling or suppressing warnings, bypassing the type system (e.g.: casts in TypeScript), or employing "hidden" logic (e.g.: reflection, prototype manipulation) unless explicitly instructed to by the user. Instead, use explicit and idiomatic language features (e.g.: type guards, explicit class instantiation, or object spread) that maintain structural integrity and type safety.
- **Design Patterns:** Prioritize explicit composition and delegation (e.g.: wrapper classes, proxies, or factory functions) over complex inheritance or prototype-based cloning. When extending or modifying existing classes, prefer patterns that are easily traceable and type-safe.
- **Comments:** Add code comments sparingly. Focus on *why* something is done, especially for complex logic, rather than *what* is done. Only add high-value comments if necessary for clarity or if requested by the user. Do not edit comments that are separate from the code you are changing. *NEVER* talk to the user or describe your changes through comments.
- **Proactiveness:** Fulfill the user's request thoroughly. When adding features or fixing bugs, this includes adding tests to ensure quality. Consider all created files, especially tests, to be permanent artifacts unless the user says otherwise.
- **User Hints:** During execution, the user may provide real-time hints (marked as "User hint:" or "User hints:"). Treat these as high-priority but scope-preserving course corrections: apply the minimal plan change needed, keep unaffected user tasks active, and never cancel/skip tasks unless cancellation is explicit for those tasks. Hints may add new tasks, modify one or more tasks, cancel specific tasks, or provide extra context only. If scope is ambiguous, ask for clarification before dropping work.
@@ -2997,7 +3023,8 @@ Use the following guidelines to optimize your search and read patterns.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
- **Conventions & Style:** Rigorously adhere to existing workspace conventions, architectural patterns, and style (naming, formatting, typing, commenting). During the research phase, analyze surrounding files, tests, and configuration to ensure your changes are seamless, idiomatic, and consistent with the local context. Never compromise idiomatic quality or completeness (e.g., proper declarations, type safety, documentation) to minimize tool calls; all supporting changes required by local conventions are part of a surgical update.
- **Types, warnings and linters:** NEVER use hacks like disabling or suppressing warnings or bypassing the type system (i.e.: casts in TypeScript) unless explicitly instructed to by the user. Instead, use idiomatic language features (e.g.: type guard functions).
- **Types, warnings and linters:** NEVER use hacks like disabling or suppressing warnings, bypassing the type system (e.g.: casts in TypeScript), or employing "hidden" logic (e.g.: reflection, prototype manipulation) unless explicitly instructed to by the user. Instead, use explicit and idiomatic language features (e.g.: type guards, explicit class instantiation, or object spread) that maintain structural integrity and type safety.
- **Design Patterns:** Prioritize explicit composition and delegation (e.g.: wrapper classes, proxies, or factory functions) over complex inheritance or prototype-based cloning. When extending or modifying existing classes, prefer patterns that are easily traceable and type-safe.
- **Libraries/Frameworks:** NEVER assume a library/framework is available. Verify its established usage within the project (check imports, configuration files like 'package.json', 'Cargo.toml', 'requirements.txt', etc.) before employing it.
- **Technical Integrity:** You are responsible for the entire lifecycle: implementation, testing, and validation. Within the scope of your changes, prioritize readability and long-term maintainability by consolidating logic into clean abstractions rather than threading state across unrelated layers. Align strictly with the requested architectural direction, ensuring the final implementation is focused and free of redundant "just-in-case" alternatives. Validation is not merely running tests; it is the exhaustive process of ensuring that every aspect of your change—behavioral, structural, and stylistic—is correct and fully compatible with the broader project. For bug fixes, you must empirically reproduce the failure with a new test case or reproduction script before applying the fix.
- **Expertise & Intent Alignment:** Provide proactive technical opinions grounded in research while strictly adhering to the user's intended workflow. Distinguish between **Directives** (unambiguous requests for action or implementation) and **Inquiries** (requests for analysis, advice, or observations). Assume all requests are Inquiries unless they contain an explicit instruction to perform a task. For Inquiries, your scope is strictly limited to research and analysis; you may propose a solution or strategy, but you MUST NOT modify files until a corresponding Directive is issued. Do not initiate implementation based on observations of bugs or statements of fact. Once an Inquiry is resolved, or while waiting for a Directive, stop and wait for the next user instruction. For Directives, only clarify if critically underspecified; otherwise, work autonomously. You should only seek user intervention if you have exhausted all possible routes or if a proposed solution would take the workspace in a significantly different architectural direction.
@@ -3134,6 +3161,8 @@ exports[`Core System Prompt (prompts.ts) > should match snapshot on Windows 1`]
- **Libraries/Frameworks:** NEVER assume a library/framework is available or appropriate. Verify its established usage within the project (check imports, configuration files like 'package.json', 'Cargo.toml', 'requirements.txt', 'build.gradle', etc., or observe neighboring files) before employing it.
- **Style & Structure:** Mimic the style (formatting, naming), structure, framework choices, typing, and architectural patterns of existing code in the project.
- **Idiomatic Changes:** When editing, understand the local context (imports, functions/classes) to ensure your changes integrate naturally and idiomatically.
- **Types, Warnings & Linters:** NEVER use hacks like disabling or suppressing warnings, bypassing the type system (e.g.: casts in TypeScript), or employing "hidden" logic (e.g.: reflection, prototype manipulation) unless explicitly instructed to by the user. Instead, use explicit and idiomatic language features (e.g.: type guards, explicit class instantiation, or object spread) that maintain structural integrity and type safety.
- **Design Patterns:** Prioritize explicit composition and delegation (e.g.: wrapper classes, proxies, or factory functions) over complex inheritance or prototype-based cloning. When extending or modifying existing classes, prefer patterns that are easily traceable and type-safe.
- **Comments:** Add code comments sparingly. Focus on *why* something is done, especially for complex logic, rather than *what* is done. Only add high-value comments if necessary for clarity or if requested by the user. Do not edit comments that are separate from the code you are changing. *NEVER* talk to the user or describe your changes through comments.
- **Proactiveness:** Fulfill the user's request thoroughly. When adding features or fixing bugs, this includes adding tests to ensure quality. Consider all created files, especially tests, to be permanent artifacts unless the user says otherwise.
- **User Hints:** During execution, the user may provide real-time hints (marked as "User hint:" or "User hints:"). Treat these as high-priority but scope-preserving course corrections: apply the minimal plan change needed, keep unaffected user tasks active, and never cancel/skip tasks unless cancellation is explicit for those tasks. Hints may add new tasks, modify one or more tasks, cancel specific tasks, or provide extra context only. If scope is ambiguous, ask for clarification before dropping work.
@@ -3247,6 +3276,8 @@ exports[`Core System Prompt (prompts.ts) > should render hierarchical memory wit
- **Libraries/Frameworks:** NEVER assume a library/framework is available or appropriate. Verify its established usage within the project (check imports, configuration files like 'package.json', 'Cargo.toml', 'requirements.txt', 'build.gradle', etc., or observe neighboring files) before employing it.
- **Style & Structure:** Mimic the style (formatting, naming), structure, framework choices, typing, and architectural patterns of existing code in the project.
- **Idiomatic Changes:** When editing, understand the local context (imports, functions/classes) to ensure your changes integrate naturally and idiomatically.
- **Types, Warnings & Linters:** NEVER use hacks like disabling or suppressing warnings, bypassing the type system (e.g.: casts in TypeScript), or employing "hidden" logic (e.g.: reflection, prototype manipulation) unless explicitly instructed to by the user. Instead, use explicit and idiomatic language features (e.g.: type guards, explicit class instantiation, or object spread) that maintain structural integrity and type safety.
- **Design Patterns:** Prioritize explicit composition and delegation (e.g.: wrapper classes, proxies, or factory functions) over complex inheritance or prototype-based cloning. When extending or modifying existing classes, prefer patterns that are easily traceable and type-safe.
- **Comments:** Add code comments sparingly. Focus on *why* something is done, especially for complex logic, rather than *what* is done. Only add high-value comments if necessary for clarity or if requested by the user. Do not edit comments that are separate from the code you are changing. *NEVER* talk to the user or describe your changes through comments.
- **Proactiveness:** Fulfill the user's request thoroughly. When adding features or fixing bugs, this includes adding tests to ensure quality. Consider all created files, especially tests, to be permanent artifacts unless the user says otherwise.
- **Conflict Resolution:** Instructions are provided in hierarchical context tags: \`<global_context>\`, \`<extension_context>\`, and \`<project_context>\`. In case of contradictory instructions, follow this priority: \`<project_context>\` (highest) > \`<extension_context>\` > \`<global_context>\` (lowest).
@@ -3408,7 +3439,8 @@ Use the following guidelines to optimize your search and read patterns.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
- **Conventions & Style:** Rigorously adhere to existing workspace conventions, architectural patterns, and style (naming, formatting, typing, commenting). During the research phase, analyze surrounding files, tests, and configuration to ensure your changes are seamless, idiomatic, and consistent with the local context. Never compromise idiomatic quality or completeness (e.g., proper declarations, type safety, documentation) to minimize tool calls; all supporting changes required by local conventions are part of a surgical update.
- **Types, warnings and linters:** NEVER use hacks like disabling or suppressing warnings or bypassing the type system (i.e.: casts in TypeScript) unless explicitly instructed to by the user. Instead, use idiomatic language features (e.g.: type guard functions).
- **Types, warnings and linters:** NEVER use hacks like disabling or suppressing warnings, bypassing the type system (e.g.: casts in TypeScript), or employing "hidden" logic (e.g.: reflection, prototype manipulation) unless explicitly instructed to by the user. Instead, use explicit and idiomatic language features (e.g.: type guards, explicit class instantiation, or object spread) that maintain structural integrity and type safety.
- **Design Patterns:** Prioritize explicit composition and delegation (e.g.: wrapper classes, proxies, or factory functions) over complex inheritance or prototype-based cloning. When extending or modifying existing classes, prefer patterns that are easily traceable and type-safe.
- **Libraries/Frameworks:** NEVER assume a library/framework is available. Verify its established usage within the project (check imports, configuration files like 'package.json', 'Cargo.toml', 'requirements.txt', etc.) before employing it.
- **Technical Integrity:** You are responsible for the entire lifecycle: implementation, testing, and validation. Within the scope of your changes, prioritize readability and long-term maintainability by consolidating logic into clean abstractions rather than threading state across unrelated layers. Align strictly with the requested architectural direction, ensuring the final implementation is focused and free of redundant "just-in-case" alternatives. Validation is not merely running tests; it is the exhaustive process of ensuring that every aspect of your change—behavioral, structural, and stylistic—is correct and fully compatible with the broader project. For bug fixes, you must empirically reproduce the failure with a new test case or reproduction script before applying the fix.
- **Expertise & Intent Alignment:** Provide proactive technical opinions grounded in research while strictly adhering to the user's intended workflow. Distinguish between **Directives** (unambiguous requests for action or implementation) and **Inquiries** (requests for analysis, advice, or observations). Assume all requests are Inquiries unless they contain an explicit instruction to perform a task. For Inquiries, your scope is strictly limited to research and analysis; you may propose a solution or strategy, but you MUST NOT modify files until a corresponding Directive is issued. Do not initiate implementation based on observations of bugs or statements of fact. Once an Inquiry is resolved, or while waiting for a Directive, stop and wait for the next user instruction. For Directives, only clarify if critically underspecified; otherwise, work autonomously. You should only seek user intervention if you have exhausted all possible routes or if a proposed solution would take the workspace in a significantly different architectural direction.
@@ -3566,7 +3598,8 @@ Use the following guidelines to optimize your search and read patterns.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
- **Conventions & Style:** Rigorously adhere to existing workspace conventions, architectural patterns, and style (naming, formatting, typing, commenting). During the research phase, analyze surrounding files, tests, and configuration to ensure your changes are seamless, idiomatic, and consistent with the local context. Never compromise idiomatic quality or completeness (e.g., proper declarations, type safety, documentation) to minimize tool calls; all supporting changes required by local conventions are part of a surgical update.
- **Types, warnings and linters:** NEVER use hacks like disabling or suppressing warnings or bypassing the type system (i.e.: casts in TypeScript) unless explicitly instructed to by the user. Instead, use idiomatic language features (e.g.: type guard functions).
- **Types, warnings and linters:** NEVER use hacks like disabling or suppressing warnings, bypassing the type system (e.g.: casts in TypeScript), or employing "hidden" logic (e.g.: reflection, prototype manipulation) unless explicitly instructed to by the user. Instead, use explicit and idiomatic language features (e.g.: type guards, explicit class instantiation, or object spread) that maintain structural integrity and type safety.
- **Design Patterns:** Prioritize explicit composition and delegation (e.g.: wrapper classes, proxies, or factory functions) over complex inheritance or prototype-based cloning. When extending or modifying existing classes, prefer patterns that are easily traceable and type-safe.
- **Libraries/Frameworks:** NEVER assume a library/framework is available. Verify its established usage within the project (check imports, configuration files like 'package.json', 'Cargo.toml', 'requirements.txt', etc.) before employing it.
- **Technical Integrity:** You are responsible for the entire lifecycle: implementation, testing, and validation. Within the scope of your changes, prioritize readability and long-term maintainability by consolidating logic into clean abstractions rather than threading state across unrelated layers. Align strictly with the requested architectural direction, ensuring the final implementation is focused and free of redundant "just-in-case" alternatives. Validation is not merely running tests; it is the exhaustive process of ensuring that every aspect of your change—behavioral, structural, and stylistic—is correct and fully compatible with the broader project. For bug fixes, you must empirically reproduce the failure with a new test case or reproduction script before applying the fix.
- **Expertise & Intent Alignment:** Provide proactive technical opinions grounded in research while strictly adhering to the user's intended workflow. Distinguish between **Directives** (unambiguous requests for action or implementation) and **Inquiries** (requests for analysis, advice, or observations). Assume all requests are Inquiries unless they contain an explicit instruction to perform a task. For Inquiries, your scope is strictly limited to research and analysis; you may propose a solution or strategy, but you MUST NOT modify files until a corresponding Directive is issued. Do not initiate implementation based on observations of bugs or statements of fact. Once an Inquiry is resolved, or while waiting for a Directive, stop and wait for the next user instruction. For Directives, only clarify if critically underspecified; otherwise, work autonomously. You should only seek user intervention if you have exhausted all possible routes or if a proposed solution would take the workspace in a significantly different architectural direction.
@@ -3691,6 +3724,8 @@ exports[`Core System Prompt (prompts.ts) > should return the interactive avoidan
- **Libraries/Frameworks:** NEVER assume a library/framework is available or appropriate. Verify its established usage within the project (check imports, configuration files like 'package.json', 'Cargo.toml', 'requirements.txt', 'build.gradle', etc., or observe neighboring files) before employing it.
- **Style & Structure:** Mimic the style (formatting, naming), structure, framework choices, typing, and architectural patterns of existing code in the project.
- **Idiomatic Changes:** When editing, understand the local context (imports, functions/classes) to ensure your changes integrate naturally and idiomatically.
- **Types, Warnings & Linters:** NEVER use hacks like disabling or suppressing warnings, bypassing the type system (e.g.: casts in TypeScript), or employing "hidden" logic (e.g.: reflection, prototype manipulation) unless explicitly instructed to by the user. Instead, use explicit and idiomatic language features (e.g.: type guards, explicit class instantiation, or object spread) that maintain structural integrity and type safety.
- **Design Patterns:** Prioritize explicit composition and delegation (e.g.: wrapper classes, proxies, or factory functions) over complex inheritance or prototype-based cloning. When extending or modifying existing classes, prefer patterns that are easily traceable and type-safe.
- **Comments:** Add code comments sparingly. Focus on *why* something is done, especially for complex logic, rather than *what* is done. Only add high-value comments if necessary for clarity or if requested by the user. Do not edit comments that are separate from the code you are changing. *NEVER* talk to the user or describe your changes through comments.
- **Proactiveness:** Fulfill the user's request thoroughly. When adding features or fixing bugs, this includes adding tests to ensure quality. Consider all created files, especially tests, to be permanent artifacts unless the user says otherwise.
- **User Hints:** During execution, the user may provide real-time hints (marked as "User hint:" or "User hints:"). Treat these as high-priority but scope-preserving course corrections: apply the minimal plan change needed, keep unaffected user tasks active, and never cancel/skip tasks unless cancellation is explicit for those tasks. Hints may add new tasks, modify one or more tasks, cancel specific tasks, or provide extra context only. If scope is ambiguous, ask for clarification before dropping work.
@@ -3836,7 +3871,8 @@ Use the following guidelines to optimize your search and read patterns.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
- **Conventions & Style:** Rigorously adhere to existing workspace conventions, architectural patterns, and style (naming, formatting, typing, commenting). During the research phase, analyze surrounding files, tests, and configuration to ensure your changes are seamless, idiomatic, and consistent with the local context. Never compromise idiomatic quality or completeness (e.g., proper declarations, type safety, documentation) to minimize tool calls; all supporting changes required by local conventions are part of a surgical update.
- **Types, warnings and linters:** NEVER use hacks like disabling or suppressing warnings or bypassing the type system (i.e.: casts in TypeScript) unless explicitly instructed to by the user. Instead, use idiomatic language features (e.g.: type guard functions).
- **Types, warnings and linters:** NEVER use hacks like disabling or suppressing warnings, bypassing the type system (e.g.: casts in TypeScript), or employing "hidden" logic (e.g.: reflection, prototype manipulation) unless explicitly instructed to by the user. Instead, use explicit and idiomatic language features (e.g.: type guards, explicit class instantiation, or object spread) that maintain structural integrity and type safety.
- **Design Patterns:** Prioritize explicit composition and delegation (e.g.: wrapper classes, proxies, or factory functions) over complex inheritance or prototype-based cloning. When extending or modifying existing classes, prefer patterns that are easily traceable and type-safe.
- **Libraries/Frameworks:** NEVER assume a library/framework is available. Verify its established usage within the project (check imports, configuration files like 'package.json', 'Cargo.toml', 'requirements.txt', etc.) before employing it.
- **Technical Integrity:** You are responsible for the entire lifecycle: implementation, testing, and validation. Within the scope of your changes, prioritize readability and long-term maintainability by consolidating logic into clean abstractions rather than threading state across unrelated layers. Align strictly with the requested architectural direction, ensuring the final implementation is focused and free of redundant "just-in-case" alternatives. Validation is not merely running tests; it is the exhaustive process of ensuring that every aspect of your change—behavioral, structural, and stylistic—is correct and fully compatible with the broader project. For bug fixes, you must empirically reproduce the failure with a new test case or reproduction script before applying the fix.
- **Expertise & Intent Alignment:** Provide proactive technical opinions grounded in research while strictly adhering to the user's intended workflow. Distinguish between **Directives** (unambiguous requests for action or implementation) and **Inquiries** (requests for analysis, advice, or observations). Assume all requests are Inquiries unless they contain an explicit instruction to perform a task. For Inquiries, your scope is strictly limited to research and analysis; you may propose a solution or strategy, but you MUST NOT modify files until a corresponding Directive is issued. Do not initiate implementation based on observations of bugs or statements of fact. Once an Inquiry is resolved, or while waiting for a Directive, stop and wait for the next user instruction. For Directives, only clarify if critically underspecified; otherwise, work autonomously. You should only seek user intervention if you have exhausted all possible routes or if a proposed solution would take the workspace in a significantly different architectural direction.
@@ -3994,7 +4030,8 @@ Use the following guidelines to optimize your search and read patterns.
## Engineering Standards
- **Contextual Precedence:** Instructions found in \`GEMINI.md\` files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
- **Conventions & Style:** Rigorously adhere to existing workspace conventions, architectural patterns, and style (naming, formatting, typing, commenting). During the research phase, analyze surrounding files, tests, and configuration to ensure your changes are seamless, idiomatic, and consistent with the local context. Never compromise idiomatic quality or completeness (e.g., proper declarations, type safety, documentation) to minimize tool calls; all supporting changes required by local conventions are part of a surgical update.
- **Types, warnings and linters:** NEVER use hacks like disabling or suppressing warnings or bypassing the type system (i.e.: casts in TypeScript) unless explicitly instructed to by the user. Instead, use idiomatic language features (e.g.: type guard functions).
- **Types, warnings and linters:** NEVER use hacks like disabling or suppressing warnings, bypassing the type system (e.g.: casts in TypeScript), or employing "hidden" logic (e.g.: reflection, prototype manipulation) unless explicitly instructed to by the user. Instead, use explicit and idiomatic language features (e.g.: type guards, explicit class instantiation, or object spread) that maintain structural integrity and type safety.
- **Design Patterns:** Prioritize explicit composition and delegation (e.g.: wrapper classes, proxies, or factory functions) over complex inheritance or prototype-based cloning. When extending or modifying existing classes, prefer patterns that are easily traceable and type-safe.
- **Libraries/Frameworks:** NEVER assume a library/framework is available. Verify its established usage within the project (check imports, configuration files like 'package.json', 'Cargo.toml', 'requirements.txt', etc.) before employing it.
- **Technical Integrity:** You are responsible for the entire lifecycle: implementation, testing, and validation. Within the scope of your changes, prioritize readability and long-term maintainability by consolidating logic into clean abstractions rather than threading state across unrelated layers. Align strictly with the requested architectural direction, ensuring the final implementation is focused and free of redundant "just-in-case" alternatives. Validation is not merely running tests; it is the exhaustive process of ensuring that every aspect of your change—behavioral, structural, and stylistic—is correct and fully compatible with the broader project. For bug fixes, you must empirically reproduce the failure with a new test case or reproduction script before applying the fix.
- **Expertise & Intent Alignment:** Provide proactive technical opinions grounded in research while strictly adhering to the user's intended workflow. Distinguish between **Directives** (unambiguous requests for action or implementation) and **Inquiries** (requests for analysis, advice, or observations). Assume all requests are Inquiries unless they contain an explicit instruction to perform a task. For Inquiries, your scope is strictly limited to research and analysis; you may propose a solution or strategy, but you MUST NOT modify files until a corresponding Directive is issued. Do not initiate implementation based on observations of bugs or statements of fact. Once an Inquiry is resolved, or while waiting for a Directive, stop and wait for the next user instruction. For Directives, only clarify if critically underspecified; otherwise, work autonomously. You should only seek user intervention if you have exhausted all possible routes or if a proposed solution would take the workspace in a significantly different architectural direction.
@@ -4119,6 +4156,8 @@ exports[`Core System Prompt (prompts.ts) > should use legacy system prompt for n
- **Libraries/Frameworks:** NEVER assume a library/framework is available or appropriate. Verify its established usage within the project (check imports, configuration files like 'package.json', 'Cargo.toml', 'requirements.txt', 'build.gradle', etc., or observe neighboring files) before employing it.
- **Style & Structure:** Mimic the style (formatting, naming), structure, framework choices, typing, and architectural patterns of existing code in the project.
- **Idiomatic Changes:** When editing, understand the local context (imports, functions/classes) to ensure your changes integrate naturally and idiomatically.
- **Types, Warnings & Linters:** NEVER use hacks like disabling or suppressing warnings, bypassing the type system (e.g.: casts in TypeScript), or employing "hidden" logic (e.g.: reflection, prototype manipulation) unless explicitly instructed to by the user. Instead, use explicit and idiomatic language features (e.g.: type guards, explicit class instantiation, or object spread) that maintain structural integrity and type safety.
- **Design Patterns:** Prioritize explicit composition and delegation (e.g.: wrapper classes, proxies, or factory functions) over complex inheritance or prototype-based cloning. When extending or modifying existing classes, prefer patterns that are easily traceable and type-safe.
- **Comments:** Add code comments sparingly. Focus on *why* something is done, especially for complex logic, rather than *what* is done. Only add high-value comments if necessary for clarity or if requested by the user. Do not edit comments that are separate from the code you are changing. *NEVER* talk to the user or describe your changes through comments.
- **Proactiveness:** Fulfill the user's request thoroughly. When adding features or fixing bugs, this includes adding tests to ensure quality. Consider all created files, especially tests, to be permanent artifacts unless the user says otherwise.
- **User Hints:** During execution, the user may provide real-time hints (marked as "User hint:" or "User hints:"). Treat these as high-priority but scope-preserving course corrections: apply the minimal plan change needed, keep unaffected user tasks active, and never cancel/skip tasks unless cancellation is explicit for those tasks. Hints may add new tasks, modify one or more tasks, cancel specific tasks, or provide extra context only. If scope is ambiguous, ask for clarification before dropping work.
@@ -121,6 +121,56 @@ describe('HookTranslator', () => {
},
]);
});
it('should apply model override when hook returns only model field', () => {
const baseRequest: GenerateContentParameters = {
model: 'gemini-2.5-flash-lite',
contents: [
{
role: 'user',
parts: [{ text: 'Hello' }],
},
],
} as unknown as GenerateContentParameters;
// Simulate a hook that only overrides the model — no messages field
const hookRequest = {
model: 'gemini-2.5-flash',
} as unknown as LLMRequest;
const sdkRequest = translator.fromHookLLMRequest(
hookRequest,
baseRequest,
);
// Model should be overridden
expect(sdkRequest.model).toBe('gemini-2.5-flash');
// Original conversation contents should be preserved
expect(sdkRequest.contents).toEqual(baseRequest.contents);
});
it('should preserve base request contents when hook messages is undefined', () => {
const baseRequest: GenerateContentParameters = {
model: 'gemini-1.5-flash',
contents: [
{ role: 'user', parts: [{ text: 'original message' }] },
{ role: 'model', parts: [{ text: 'original reply' }] },
],
} as unknown as GenerateContentParameters;
const hookRequest = {
model: 'gemini-1.5-pro',
// messages intentionally omitted
} as unknown as LLMRequest;
const sdkRequest = translator.fromHookLLMRequest(
hookRequest,
baseRequest,
);
expect(sdkRequest.model).toBe('gemini-1.5-pro');
expect(sdkRequest.contents).toEqual(baseRequest.contents);
});
});
describe('LLM Response Translation', () => {
+21 -14
View File
@@ -225,23 +225,30 @@ export class HookTranslatorGenAIv1 extends HookTranslator {
hookRequest: LLMRequest,
baseRequest?: GenerateContentParameters,
): GenerateContentParameters {
// Convert hook messages back to SDK Content format
const contents = hookRequest.messages.map((message) => ({
role: message.role === 'model' ? 'model' : message.role,
parts: [
{
text:
typeof message.content === 'string'
? message.content
: String(message.content),
},
],
}));
// Convert hook messages back to SDK Content format.
// If the hook returned a partial request without messages (e.g. only
// overriding `model`), fall back to the base request's contents so the
// conversation is preserved.
const contents = hookRequest.messages
? hookRequest.messages.map((message) => ({
role: message.role === 'model' ? 'model' : message.role,
parts: [
{
text:
typeof message.content === 'string'
? message.content
: String(message.content),
},
],
}))
: (baseRequest?.contents ?? []);
// Build the result with proper typing
// Build the result with proper typing.
// Use nullish coalescing so a hook that only sets `model` still works --
// fall back to the base request's model rather than overwriting with undefined.
const result: GenerateContentParameters = {
...baseRequest,
model: hookRequest.model,
model: hookRequest.model ?? baseRequest?.model ?? '',
contents,
};
+2 -1
View File
@@ -107,7 +107,8 @@ toolName = [
"activate_skill",
"codebase_investigator",
"cli_help",
"get_internal_docs"
"get_internal_docs",
"complete_task"
]
decision = "allow"
priority = 70
@@ -2,7 +2,7 @@
network = false
readonly = true
approvedTools = []
allowOverrides = false
allowOverrides = true
[modes.default]
network = false
@@ -17,4 +17,3 @@ approvedTools = ['sed', 'grep', 'awk', 'perl', 'cat', 'echo', 'Add-Content', 'Se
allowOverrides = true
[commands]
@@ -0,0 +1,72 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { SandboxPolicyManager } from './sandboxPolicyManager.js';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
describe('SandboxPolicyManager', () => {
const tempDir = path.join(os.tmpdir(), 'gemini-test-sandbox-policy');
const configPath = path.join(tempDir, 'sandbox.toml');
beforeEach(() => {
if (!fs.existsSync(tempDir)) {
fs.mkdirSync(tempDir, { recursive: true });
}
});
afterEach(() => {
if (fs.existsSync(tempDir)) {
fs.rmSync(tempDir, { recursive: true, force: true });
}
});
it('should add and retrieve session approvals', () => {
const manager = new SandboxPolicyManager(configPath);
manager.addSessionApproval('ls', {
fileSystem: { read: ['/tmp'], write: [] },
network: false,
});
const perms = manager.getCommandPermissions('ls');
expect(perms.fileSystem?.read).toContain('/tmp');
});
it('should protect against prototype pollution (session)', () => {
const manager = new SandboxPolicyManager(configPath);
manager.addSessionApproval('__proto__', {
fileSystem: { read: ['/POLLUTED'], write: [] },
network: true,
});
const perms = manager.getCommandPermissions('any-command');
expect(perms.fileSystem?.read).not.toContain('/POLLUTED');
});
it('should protect against prototype pollution (persistent)', () => {
const manager = new SandboxPolicyManager(configPath);
manager.addPersistentApproval('constructor', {
fileSystem: { read: ['/POLLUTED_PERSISTENT'], write: [] },
network: true,
});
const perms = manager.getCommandPermissions('constructor');
expect(perms.fileSystem?.read).not.toContain('/POLLUTED_PERSISTENT');
});
it('should lowercase command names for normalization', () => {
const manager = new SandboxPolicyManager(configPath);
manager.addSessionApproval('NPM', {
fileSystem: { read: ['/node_modules'], write: [] },
network: true,
});
const perms = manager.getCommandPermissions('npm');
expect(perms.fileSystem?.read).toContain('/node_modules');
});
});
@@ -13,6 +13,7 @@ import { fileURLToPath } from 'node:url';
import { debugLogger } from '../utils/debugLogger.js';
import { type SandboxPermissions } from '../services/sandboxManager.js';
import { sanitizePaths } from '../services/sandboxManager.js';
import { normalizeCommand } from '../utils/shell-utils.js';
export const SandboxModeConfigSchema = z.object({
network: z.boolean(),
@@ -63,7 +64,7 @@ export class SandboxPolicyManager {
network: false,
readonly: true,
approvedTools: [],
allowOverrides: false,
allowOverrides: true,
},
default: {
network: false,
@@ -104,6 +105,10 @@ export class SandboxPolicyManager {
this.config = this.loadConfig();
}
private isProtectedKey(key: string): boolean {
return key === '__proto__' || key === 'constructor' || key === 'prototype';
}
private loadConfig(): SandboxTomlSchemaType {
if (!fs.existsSync(this.configPath)) {
return SandboxPolicyManager.DEFAULT_CONFIG;
@@ -154,8 +159,15 @@ export class SandboxPolicyManager {
}
getCommandPermissions(commandName: string): SandboxPermissions {
const persistent = this.config.commands[commandName];
const session = this.sessionApprovals[commandName];
const normalized = normalizeCommand(commandName);
if (this.isProtectedKey(normalized)) {
return {
fileSystem: { read: [], write: [] },
network: false,
};
}
const persistent = this.config.commands[normalized];
const session = this.sessionApprovals[normalized];
return {
fileSystem: {
@@ -176,25 +188,25 @@ export class SandboxPolicyManager {
commandName: string,
permissions: SandboxPermissions,
): void {
const existing = this.sessionApprovals[commandName] || {
const normalized = normalizeCommand(commandName);
if (this.isProtectedKey(normalized)) {
return;
}
const existing = this.sessionApprovals[normalized] || {
fileSystem: { read: [], write: [] },
network: false,
};
this.sessionApprovals[commandName] = {
this.sessionApprovals[normalized] = {
fileSystem: {
read: Array.from(
new Set([
...(existing.fileSystem?.read ?? []),
...(permissions.fileSystem?.read ?? []),
]),
),
write: Array.from(
new Set([
...(existing.fileSystem?.write ?? []),
...(permissions.fileSystem?.write ?? []),
]),
),
read: sanitizePaths([
...(existing.fileSystem?.read ?? []),
...(permissions.fileSystem?.read ?? []),
]),
write: sanitizePaths([
...(existing.fileSystem?.write ?? []),
...(permissions.fileSystem?.write ?? []),
]),
},
network: existing.network || permissions.network || false,
};
@@ -204,7 +216,11 @@ export class SandboxPolicyManager {
commandName: string,
permissions: SandboxPermissions,
): void {
const existing = this.config.commands[commandName] || {
const normalized = normalizeCommand(commandName);
if (this.isProtectedKey(normalized)) {
return;
}
const existing = this.config.commands[normalized] || {
allowed_paths: [],
allow_network: false,
};
@@ -216,7 +232,7 @@ export class SandboxPolicyManager {
];
const newPaths = new Set(sanitizePaths(newPathsArray));
this.config.commands[commandName] = {
this.config.commands[normalized] = {
allowed_paths: Array.from(newPaths),
allow_network: existing.allow_network || permissions.network || false,
};
+6 -2
View File
@@ -177,6 +177,8 @@ export function renderCoreMandates(options?: CoreMandatesOptions): string {
- **Libraries/Frameworks:** NEVER assume a library/framework is available or appropriate. Verify its established usage within the project (check imports, configuration files like 'package.json', 'Cargo.toml', 'requirements.txt', 'build.gradle', etc., or observe neighboring files) before employing it.
- **Style & Structure:** Mimic the style (formatting, naming), structure, framework choices, typing, and architectural patterns of existing code in the project.
- **Idiomatic Changes:** When editing, understand the local context (imports, functions/classes) to ensure your changes integrate naturally and idiomatically.
- **Types, Warnings & Linters:** NEVER use hacks like disabling or suppressing warnings, bypassing the type system (e.g.: casts in TypeScript), or employing "hidden" logic (e.g.: reflection, prototype manipulation) unless explicitly instructed to by the user. Instead, use explicit and idiomatic language features (e.g.: type guards, explicit class instantiation, or object spread) that maintain structural integrity and type safety.
- **Design Patterns:** Prioritize explicit composition and delegation (e.g.: wrapper classes, proxies, or factory functions) over complex inheritance or prototype-based cloning. When extending or modifying existing classes, prefer patterns that are easily traceable and type-safe.
- **Comments:** Add code comments sparingly. Focus on *why* something is done, especially for complex logic, rather than *what* is done. Only add high-value comments if necessary for clarity or if requested by the user. Do not edit comments that are separate from the code you are changing. *NEVER* talk to the user or describe your changes through comments.
- **Proactiveness:** Fulfill the user's request thoroughly. When adding features or fixing bugs, this includes adding tests to ensure quality. Consider all created files, especially tests, to be permanent artifacts unless the user says otherwise.${mandateConflictResolution(options.hasHierarchicalMemory)}
- **User Hints:** During execution, the user may provide real-time hints (marked as "User hint:" or "User hints:"). Treat these as high-priority but scope-preserving course corrections: apply the minimal plan change needed, keep unaffected user tasks active, and never cancel/skip tasks unless cancellation is explicit for those tasks. Hints may add new tasks, modify one or more tasks, cancel specific tasks, or provide extra context only. If scope is ambiguous, ask for clarification before dropping work.
@@ -518,10 +520,12 @@ function mandateTopicUpdateModel(): string {
## Topic Updates
As you work, the user follows along by reading topic updates that you publish with ${UPDATE_TOPIC_TOOL_NAME}. Keep them informed by doing the following:
- Always call ${UPDATE_TOPIC_TOOL_NAME} in your first and last turn. The final turn should always recap what was done.
- Usage Exception: NEVER use ${UPDATE_TOPIC_TOOL_NAME} for answering questions, providing explanations, or performing isolated lookup tasks (e.g. reading a single file, running a quick search, or checking a version). It is STRICTLY for orchestrating multi-step codebase modifications or complex investigations involving 3 or more tool calls.
- Always call ${UPDATE_TOPIC_TOOL_NAME} in your first turn.
- For tasks taking multiple turns, also call ${UPDATE_TOPIC_TOOL_NAME} in your last turn to recap what was done.
- Each topic update should give a concise description of what you are doing for the next few turns in the \`${TOPIC_PARAM_SUMMARY}\` parameter.
- Provide topic updates whenever you change "topics". A topic is typically a discrete subgoal and will be every 3 to 10 turns. Do not use ${UPDATE_TOPIC_TOOL_NAME} on every turn.
- The typical user message should call ${UPDATE_TOPIC_TOOL_NAME} 3 or more times. Each corresponds to a distinct phase of the task, such as "Researching X", "Researching Y", "Implementing Z with X", and "Testing Z".
- The typical complex user message should call ${UPDATE_TOPIC_TOOL_NAME} 3 or more times. Each corresponds to a distinct phase of the task, such as "Researching X", "Researching Y", "Implementing Z with X", and "Testing Z".
- Remember to call ${UPDATE_TOPIC_TOOL_NAME} when you experience an unexpected event (e.g., a test failure, compilation error, environment issue, or unexpected learning) that requires a strategic detour.
- **Examples:**
- ${UPDATE_TOPIC_TOOL_NAME}(${TOPIC_PARAM_TITLE}="Researching Parser", ${TOPIC_PARAM_SUMMARY}="I am starting an investigation into the parser timeout bug. My goal is to first understand the current test coverage and then attempt to reproduce the failure. This phase will focus on identifying the bottleneck in the main loop before we move to implementation.")
+6 -3
View File
@@ -229,7 +229,8 @@ Use the following guidelines to optimize your search and read patterns.
## Engineering Standards
- **Contextual Precedence:** Instructions found in ${formattedFilenames} files are foundational mandates. They take absolute precedence over the general workflows and tool defaults described in this system prompt.
- **Conventions & Style:** Rigorously adhere to existing workspace conventions, architectural patterns, and style (naming, formatting, typing, commenting). During the research phase, analyze surrounding files, tests, and configuration to ensure your changes are seamless, idiomatic, and consistent with the local context. Never compromise idiomatic quality or completeness (e.g., proper declarations, type safety, documentation) to minimize tool calls; all supporting changes required by local conventions are part of a surgical update.
- **Types, warnings and linters:** NEVER use hacks like disabling or suppressing warnings or bypassing the type system (i.e.: casts in TypeScript) unless explicitly instructed to by the user. Instead, use idiomatic language features (e.g.: type guard functions).
- **Types, warnings and linters:** NEVER use hacks like disabling or suppressing warnings, bypassing the type system (e.g.: casts in TypeScript), or employing "hidden" logic (e.g.: reflection, prototype manipulation) unless explicitly instructed to by the user. Instead, use explicit and idiomatic language features (e.g.: type guards, explicit class instantiation, or object spread) that maintain structural integrity and type safety.
- **Design Patterns:** Prioritize explicit composition and delegation (e.g.: wrapper classes, proxies, or factory functions) over complex inheritance or prototype-based cloning. When extending or modifying existing classes, prefer patterns that are easily traceable and type-safe.
- **Libraries/Frameworks:** NEVER assume a library/framework is available. Verify its established usage within the project (check imports, configuration files like 'package.json', 'Cargo.toml', 'requirements.txt', etc.) before employing it.
- **Technical Integrity:** You are responsible for the entire lifecycle: implementation, testing, and validation. Within the scope of your changes, prioritize readability and long-term maintainability by consolidating logic into clean abstractions rather than threading state across unrelated layers. Align strictly with the requested architectural direction, ensuring the final implementation is focused and free of redundant "just-in-case" alternatives. Validation is not merely running tests; it is the exhaustive process of ensuring that every aspect of your changebehavioral, structural, and stylisticis correct and fully compatible with the broader project. For bug fixes, you must empirically reproduce the failure with a new test case or reproduction script before applying the fix.
- **Expertise & Intent Alignment:** Provide proactive technical opinions grounded in research while strictly adhering to the user's intended workflow. Distinguish between **Directives** (unambiguous requests for action or implementation) and **Inquiries** (requests for analysis, advice, or observations). Assume all requests are Inquiries unless they contain an explicit instruction to perform a task. For Inquiries, your scope is strictly limited to research and analysis; you may propose a solution or strategy, but you MUST NOT modify files until a corresponding Directive is issued. Do not initiate implementation based on observations of bugs or statements of fact. Once an Inquiry is resolved, or while waiting for a Directive, stop and wait for the next user instruction. ${options.interactive ? 'For Directives, only clarify if critically underspecified; otherwise, work autonomously.' : 'For Directives, you must work autonomously as no further user input is available.'} You should only seek user intervention if you have exhausted all possible routes or if a proposed solution would take the workspace in a significantly different architectural direction.
@@ -629,10 +630,12 @@ function mandateTopicUpdateModel(): string {
## Topic Updates
As you work, the user follows along by reading topic updates that you publish with ${UPDATE_TOPIC_TOOL_NAME}. Keep them informed by doing the following:
- Always call ${UPDATE_TOPIC_TOOL_NAME} in your first and last turn. The final turn should always recap what was done.
- Usage Exception: NEVER use ${UPDATE_TOPIC_TOOL_NAME} for answering questions, providing explanations, or performing isolated lookup tasks (e.g. reading a single file, running a quick search, or checking a version). It is STRICTLY for orchestrating multi-step codebase modifications or complex investigations involving 3 or more tool calls.
- Always call ${UPDATE_TOPIC_TOOL_NAME} in your first turn.
- For tasks taking multiple turns, also call ${UPDATE_TOPIC_TOOL_NAME} in your last turn to recap what was done.
- Each topic update should give a concise description of what you are doing for the next few turns in the \`${TOPIC_PARAM_SUMMARY}\` parameter.
- Provide topic updates whenever you change "topics". A topic is typically a discrete subgoal and will be every 3 to 10 turns. Do not use ${UPDATE_TOPIC_TOOL_NAME} on every turn.
- The typical user message should call ${UPDATE_TOPIC_TOOL_NAME} 3 or more times. Each corresponds to a distinct phase of the task, such as "Researching X", "Researching Y", "Implementing Z with X", and "Testing Z".
- The typical complex user message should call ${UPDATE_TOPIC_TOOL_NAME} 3 or more times. Each corresponds to a distinct phase of the task, such as "Researching X", "Researching Y", "Implementing Z with X", and "Testing Z".
- Remember to call ${UPDATE_TOPIC_TOOL_NAME} when you experience an unexpected event (e.g., a test failure, compilation error, environment issue, or unexpected learning) that requires a strategic detour.
- **Examples:**
- \`update_topic(${TOPIC_PARAM_TITLE}="Researching Parser", ${TOPIC_PARAM_SUMMARY}="I am starting an investigation into the parser timeout bug. My goal is to first understand the current test coverage and then attempt to reproduce the failure. This phase will focus on identifying the bottleneck in the main loop before we move to implementation.")\`
@@ -148,6 +148,10 @@ export class LinuxSandboxManager implements SandboxManager {
return this.options.workspace;
}
getOptions(): GlobalSandboxOptions {
return this.options;
}
private getMaskFilePath(): string {
if (
LinuxSandboxManager.maskFilePath &&
@@ -59,6 +59,10 @@ export class MacOsSandboxManager implements SandboxManager {
return this.options.workspace;
}
getOptions(): GlobalSandboxOptions {
return this.options;
}
async prepareCommand(req: SandboxRequest): Promise<SandboxedCommand> {
await initializeShellParsers();
const sanitizationConfig = getSecureSanitizationConfig(
@@ -8,6 +8,7 @@ import os from 'node:os';
import path from 'node:path';
import fs from 'node:fs';
import { type SandboxPermissions } from '../../services/sandboxManager.js';
import { normalizeCommand } from '../../utils/shell-utils.js';
const NETWORK_RELIANT_TOOLS = new Set([
'npm',
@@ -45,7 +46,7 @@ export function isNetworkReliantCommand(
commandName: string,
subCommand?: string,
): boolean {
const normalizedCommand = commandName.toLowerCase().replace(/\.exe$/, '');
const normalizedCommand = normalizeCommand(commandName);
if (!NETWORK_RELIANT_TOOLS.has(normalizedCommand)) {
return false;
}
@@ -82,7 +83,7 @@ export function isNetworkReliantCommand(
export async function getProactiveToolSuggestions(
commandName: string,
): Promise<SandboxPermissions | undefined> {
const normalizedCommand = commandName.toLowerCase().replace(/\.exe$/, '');
const normalizedCommand = normalizeCommand(commandName);
if (!NETWORK_RELIANT_TOOLS.has(normalizedCommand)) {
return undefined;
}
@@ -21,6 +21,8 @@ using System.Text;
*/
public class GeminiSandbox {
// P/Invoke constants and structures
private const int JobObjectExtendedLimitInformation = 9;
private const int JobObjectNetRateControlInformation = 32;
private const uint JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x00002000;
private const uint JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION = 0x00000400;
private const uint JOB_OBJECT_LIMIT_ACTIVE_PROCESS = 0x00000008;
@@ -74,6 +76,9 @@ public class GeminiSandbox {
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool AssignProcessToJobObject(IntPtr hJob, IntPtr hProcess);
[DllImport("kernel32.dll", SetLastError = true)]
static extern uint ResumeThread(IntPtr hThread);
[DllImport("advapi32.dll", SetLastError = true)]
static extern bool OpenProcessToken(IntPtr ProcessHandle, uint DesiredAccess, out IntPtr TokenHandle);
@@ -191,7 +196,8 @@ public class GeminiSandbox {
IntPtr hToken = IntPtr.Zero;
IntPtr hRestrictedToken = IntPtr.Zero;
IntPtr lowIntegritySid = IntPtr.Zero;
IntPtr hJob = IntPtr.Zero;
PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
try {
// 1. Duplicate Primary Token
@@ -208,6 +214,7 @@ public class GeminiSandbox {
// 2. Lower Integrity Level to Low
// S-1-16-4096 is the SID for "Low Mandatory Level"
IntPtr lowIntegritySid = IntPtr.Zero;
if (ConvertStringSidToSid("S-1-16-4096", out lowIntegritySid)) {
TOKEN_MANDATORY_LABEL tml = new TOKEN_MANDATORY_LABEL();
tml.Label.Sid = lowIntegritySid;
@@ -226,25 +233,42 @@ public class GeminiSandbox {
}
// 3. Setup Job Object for cleanup
IntPtr hJob = CreateJobObject(IntPtr.Zero, null);
hJob = CreateJobObject(IntPtr.Zero, null);
if (hJob == IntPtr.Zero) {
Console.Error.WriteLine("Error: CreateJobObject failed (" + Marshal.GetLastWin32Error() + ")");
return 1;
}
JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobLimits = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
jobLimits.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION;
IntPtr lpJobLimits = Marshal.AllocHGlobal(Marshal.SizeOf(jobLimits));
Marshal.StructureToPtr(jobLimits, lpJobLimits, false);
SetInformationJobObject(hJob, 9 /* JobObjectExtendedLimitInformation */, lpJobLimits, (uint)Marshal.SizeOf(jobLimits));
Marshal.FreeHGlobal(lpJobLimits);
try {
Marshal.StructureToPtr(jobLimits, lpJobLimits, false);
if (!SetInformationJobObject(hJob, JobObjectExtendedLimitInformation, lpJobLimits, (uint)Marshal.SizeOf(jobLimits))) {
Console.Error.WriteLine("Error: SetInformationJobObject(Limits) failed (" + Marshal.GetLastWin32Error() + ")");
return 1;
}
} finally {
Marshal.FreeHGlobal(lpJobLimits);
}
if (!networkAccess) {
JOBOBJECT_NET_RATE_CONTROL_INFORMATION netLimits = new JOBOBJECT_NET_RATE_CONTROL_INFORMATION();
netLimits.MaxBandwidth = 1;
netLimits.ControlFlags = 0x1 | 0x2; // ENABLE | MAX_BANDWIDTH
netLimits.DscpTag = 0;
IntPtr lpNetLimits = Marshal.AllocHGlobal(Marshal.SizeOf(netLimits));
Marshal.StructureToPtr(netLimits, lpNetLimits, false);
SetInformationJobObject(hJob, 32 /* JobObjectNetRateControlInformation */, lpNetLimits, (uint)Marshal.SizeOf(netLimits));
Marshal.FreeHGlobal(lpNetLimits);
try {
Marshal.StructureToPtr(netLimits, lpNetLimits, false);
if (!SetInformationJobObject(hJob, JobObjectNetRateControlInformation, lpNetLimits, (uint)Marshal.SizeOf(netLimits))) {
// Some versions of Windows might not support network rate control, but we should know if it fails.
Console.Error.WriteLine("Warning: SetInformationJobObject(NetRate) failed (" + Marshal.GetLastWin32Error() + "). Network might not be throttled.");
}
} finally {
Marshal.FreeHGlobal(lpNetLimits);
}
}
// 4. Handle Internal Commands or External Process
@@ -310,32 +334,49 @@ public class GeminiSandbox {
commandLine += QuoteArgument(args[i]);
}
PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
// Creation Flags: 0x04000000 (CREATE_BREAKAWAY_FROM_JOB) to allow job assignment if parent is in job
uint creationFlags = 0;
// Creation Flags: 0x01000000 (CREATE_BREAKAWAY_FROM_JOB) to allow job assignment if parent is in job
// 0x00000004 (CREATE_SUSPENDED) to prevent the process from executing before being placed in the job
uint creationFlags = 0x01000000 | 0x00000004;
if (!CreateProcessAsUser(hRestrictedToken, null, commandLine, IntPtr.Zero, IntPtr.Zero, true, creationFlags, IntPtr.Zero, cwd, ref si, out pi)) {
Console.WriteLine("Error: CreateProcessAsUser failed (" + Marshal.GetLastWin32Error() + ") Command: " + commandLine);
int err = Marshal.GetLastWin32Error();
Console.Error.WriteLine("Error: CreateProcessAsUser failed (" + err + ") Command: " + commandLine);
return 1;
}
AssignProcessToJobObject(hJob, pi.hProcess);
// Wait for exit
uint waitResult = WaitForSingleObject(pi.hProcess, 0xFFFFFFFF);
uint exitCode = 0;
GetExitCodeProcess(pi.hProcess, out exitCode);
if (!AssignProcessToJobObject(hJob, pi.hProcess)) {
int err = Marshal.GetLastWin32Error();
Console.Error.WriteLine("Error: AssignProcessToJobObject failed (" + err + ") Command: " + commandLine);
TerminateProcess(pi.hProcess, 1);
return 1;
}
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
CloseHandle(hJob);
ResumeThread(pi.hThread);
if (WaitForSingleObject(pi.hProcess, 0xFFFFFFFF) == 0xFFFFFFFF) {
int err = Marshal.GetLastWin32Error();
Console.Error.WriteLine("Error: WaitForSingleObject failed (" + err + ")");
}
uint exitCode = 0;
if (!GetExitCodeProcess(pi.hProcess, out exitCode)) {
int err = Marshal.GetLastWin32Error();
Console.Error.WriteLine("Error: GetExitCodeProcess failed (" + err + ")");
return 1;
}
return (int)exitCode;
} finally {
if (hToken != IntPtr.Zero) CloseHandle(hToken);
if (hRestrictedToken != IntPtr.Zero) CloseHandle(hRestrictedToken);
if (hJob != IntPtr.Zero) CloseHandle(hJob);
if (pi.hProcess != IntPtr.Zero) CloseHandle(pi.hProcess);
if (pi.hThread != IntPtr.Zero) CloseHandle(pi.hThread);
}
}
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool TerminateProcess(IntPtr hProcess, uint uExitCode);
[DllImport("kernel32.dll", SetLastError = true)]
static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);
@@ -25,17 +25,40 @@ vi.mock('../../utils/shell-utils.js', async (importOriginal) => {
};
});
// TODO: reenable once test is fixed
describe.skipIf(os.platform() === 'win32')('WindowsSandboxManager', () => {
describe('WindowsSandboxManager', () => {
let manager: WindowsSandboxManager;
let testCwd: string;
/**
* Creates a temporary directory and returns its canonical real path.
*/
function createTempDir(name: string, parent = os.tmpdir()): string {
const rawPath = fs.mkdtempSync(path.join(parent, `gemini-test-${name}-`));
return fs.realpathSync(rawPath);
}
const helperExePath = path.resolve(
__dirname,
WindowsSandboxManager.HELPER_EXE,
);
beforeEach(() => {
vi.spyOn(os, 'platform').mockReturnValue('win32');
vi.spyOn(sandboxManager, 'tryRealpath').mockImplementation(async (p) =>
p.toString(),
);
testCwd = fs.mkdtempSync(path.join(os.tmpdir(), 'gemini-cli-test-'));
// Mock existsSync to skip the csc.exe auto-compilation of helper during unit tests.
const originalExistsSync = fs.existsSync;
vi.spyOn(fs, 'existsSync').mockImplementation((p) => {
if (typeof p === 'string' && path.resolve(p) === helperExePath) {
return true;
}
return originalExistsSync(p);
});
testCwd = createTempDir('cwd');
manager = new WindowsSandboxManager({
workspace: testCwd,
modeConfig: { readonly: false, allowOverrides: true },
@@ -45,7 +68,9 @@ describe.skipIf(os.platform() === 'win32')('WindowsSandboxManager', () => {
afterEach(() => {
vi.restoreAllMocks();
fs.rmSync(testCwd, { recursive: true, force: true });
if (testCwd && fs.existsSync(testCwd)) {
fs.rmSync(testCwd, { recursive: true, force: true });
}
});
it('should prepare a GeminiSandbox.exe command', async () => {
@@ -155,8 +180,7 @@ describe.skipIf(os.platform() === 'win32')('WindowsSandboxManager', () => {
});
it('should handle persistent permissions from policyManager', async () => {
const persistentPath = path.join(testCwd, 'persistent_path');
fs.mkdirSync(persistentPath, { recursive: true });
const persistentPath = createTempDir('persistent', testCwd);
const mockPolicyManager = {
getCommandPermissions: vi.fn().mockReturnValue({
@@ -189,6 +213,8 @@ describe.skipIf(os.platform() === 'win32')('WindowsSandboxManager', () => {
expect(icaclsArgs).toContainEqual([
persistentPath,
'/grant',
'*S-1-16-4096:(OI)(CI)(M)',
'/setintegritylevel',
'(OI)(CI)Low',
]);
@@ -234,10 +260,7 @@ describe.skipIf(os.platform() === 'win32')('WindowsSandboxManager', () => {
});
it('should grant Low Integrity access to the workspace and allowed paths', async () => {
const allowedPath = path.join(os.tmpdir(), 'gemini-cli-test-allowed');
if (!fs.existsSync(allowedPath)) {
fs.mkdirSync(allowedPath);
}
const allowedPath = createTempDir('allowed');
try {
const req: SandboxRequest = {
command: 'test',
@@ -257,13 +280,17 @@ describe.skipIf(os.platform() === 'win32')('WindowsSandboxManager', () => {
.map((c) => c[1]);
expect(icaclsArgs).toContainEqual([
path.resolve(testCwd),
testCwd,
'/grant',
'*S-1-16-4096:(OI)(CI)(M)',
'/setintegritylevel',
'(OI)(CI)Low',
]);
expect(icaclsArgs).toContainEqual([
path.resolve(allowedPath),
allowedPath,
'/grant',
'*S-1-16-4096:(OI)(CI)(M)',
'/setintegritylevel',
'(OI)(CI)Low',
]);
@@ -273,13 +300,7 @@ describe.skipIf(os.platform() === 'win32')('WindowsSandboxManager', () => {
});
it('should grant Low Integrity access to additional write paths', async () => {
const extraWritePath = path.join(
os.tmpdir(),
'gemini-cli-test-extra-write',
);
if (!fs.existsSync(extraWritePath)) {
fs.mkdirSync(extraWritePath);
}
const extraWritePath = createTempDir('extra-write');
try {
const req: SandboxRequest = {
command: 'test',
@@ -303,7 +324,9 @@ describe.skipIf(os.platform() === 'win32')('WindowsSandboxManager', () => {
.map((c) => c[1]);
expect(icaclsArgs).toContainEqual([
path.resolve(extraWritePath),
extraWritePath,
'/grant',
'*S-1-16-4096:(OI)(CI)(M)',
'/setintegritylevel',
'(OI)(CI)Low',
]);
@@ -330,26 +353,26 @@ describe.skipIf(os.platform() === 'win32')('WindowsSandboxManager', () => {
},
};
await manager.prepareCommand(req);
// Rejected because it's an unreachable/invalid UNC path or it doesn't exist
await expect(manager.prepareCommand(req)).rejects.toThrow();
const icaclsArgs = vi
.mocked(spawnAsync)
.mock.calls.filter((c) => c[0] === 'icacls')
.map((c) => c[1]);
expect(icaclsArgs).not.toContainEqual([
uncPath,
'/setintegritylevel',
'(OI)(CI)Low',
]);
expect(icaclsArgs).not.toContainEqual(expect.arrayContaining([uncPath]));
},
);
it.runIf(process.platform === 'win32')(
'should allow extended-length and local device paths',
async () => {
const longPath = '\\\\?\\C:\\very\\long\\path';
const devicePath = '\\\\.\\PhysicalDrive0';
// Create actual files for inheritance/existence checks
const longPath = path.join(testCwd, 'very_long_path.txt');
const devicePath = path.join(testCwd, 'device_path.txt');
fs.writeFileSync(longPath, '');
fs.writeFileSync(devicePath, '');
const req: SandboxRequest = {
command: 'test',
@@ -373,12 +396,16 @@ describe.skipIf(os.platform() === 'win32')('WindowsSandboxManager', () => {
.map((c) => c[1]);
expect(icaclsArgs).toContainEqual([
longPath,
path.resolve(longPath),
'/grant',
'*S-1-16-4096:(OI)(CI)(M)',
'/setintegritylevel',
'(OI)(CI)Low',
]);
expect(icaclsArgs).toContainEqual([
devicePath,
path.resolve(devicePath),
'/grant',
'*S-1-16-4096:(OI)(CI)(M)',
'/setintegritylevel',
'(OI)(CI)Low',
]);
@@ -420,10 +447,7 @@ describe.skipIf(os.platform() === 'win32')('WindowsSandboxManager', () => {
});
it('should deny Low Integrity access to forbidden paths', async () => {
const forbiddenPath = path.join(os.tmpdir(), 'gemini-cli-test-forbidden');
if (!fs.existsSync(forbiddenPath)) {
fs.mkdirSync(forbiddenPath);
}
const forbiddenPath = createTempDir('forbidden');
try {
const managerWithForbidden = new WindowsSandboxManager({
workspace: testCwd,
@@ -440,7 +464,7 @@ describe.skipIf(os.platform() === 'win32')('WindowsSandboxManager', () => {
await managerWithForbidden.prepareCommand(req);
expect(spawnAsync).toHaveBeenCalledWith('icacls', [
path.resolve(forbiddenPath),
forbiddenPath,
'/deny',
'*S-1-16-4096:(OI)(CI)(F)',
]);
@@ -450,10 +474,7 @@ describe.skipIf(os.platform() === 'win32')('WindowsSandboxManager', () => {
});
it('should override allowed paths if a path is also in forbidden paths', async () => {
const conflictPath = path.join(os.tmpdir(), 'gemini-cli-test-conflict');
if (!fs.existsSync(conflictPath)) {
fs.mkdirSync(conflictPath);
}
const conflictPath = createTempDir('conflict');
try {
const managerWithForbidden = new WindowsSandboxManager({
workspace: testCwd,
@@ -478,14 +499,14 @@ describe.skipIf(os.platform() === 'win32')('WindowsSandboxManager', () => {
call[1] &&
call[1].includes('/setintegritylevel') &&
call[0] === 'icacls' &&
call[1][0] === path.resolve(conflictPath),
call[1][0] === conflictPath,
);
const denyCallIndex = spawnMock.mock.calls.findIndex(
(call) =>
call[1] &&
call[1].includes('/deny') &&
call[0] === 'icacls' &&
call[1][0] === path.resolve(conflictPath),
call[1][0] === conflictPath,
);
// Conflict should have been filtered out of allow calls
@@ -513,8 +534,8 @@ describe.skipIf(os.platform() === 'win32')('WindowsSandboxManager', () => {
expect(result.args[5]).toBe(filePath);
});
it('should safely handle special characters in __write path', async () => {
const maliciousPath = path.join(testCwd, 'foo"; echo bar; ".txt');
it('should safely handle special characters in __write path using environment variables', async () => {
const maliciousPath = path.join(testCwd, 'foo & echo bar; ! .txt');
fs.writeFileSync(maliciousPath, '');
const req: SandboxRequest = {
command: '__write',
@@ -545,4 +566,23 @@ describe.skipIf(os.platform() === 'win32')('WindowsSandboxManager', () => {
expect(result.args[4]).toBe('__read');
expect(result.args[5]).toBe(filePath);
});
it('should return a cleanup function that deletes the temporary manifest', async () => {
const req: SandboxRequest = {
command: 'test',
args: [],
cwd: testCwd,
env: {},
};
const result = await manager.prepareCommand(req);
const manifestPath = result.args[3];
expect(fs.existsSync(manifestPath)).toBe(true);
expect(result.cleanup).toBeDefined();
result.cleanup?.();
expect(fs.existsSync(manifestPath)).toBe(false);
expect(fs.existsSync(path.dirname(manifestPath))).toBe(false);
});
});
@@ -16,7 +16,6 @@ import {
findSecretFiles,
type GlobalSandboxOptions,
sanitizePaths,
tryRealpath,
type SandboxPermissions,
type ParsedSandboxDenial,
resolveSandboxPaths,
@@ -36,23 +35,28 @@ import {
} from './commandSafety.js';
import { verifySandboxOverrides } from '../utils/commandUtils.js';
import { parseWindowsSandboxDenials } from './windowsSandboxDenialUtils.js';
import { isSubpath, resolveToRealPath } from '../../utils/paths.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// S-1-16-4096 is the SID for "Low Mandatory Level" (Low Integrity)
const LOW_INTEGRITY_SID = '*S-1-16-4096';
/**
* A SandboxManager implementation for Windows that uses Restricted Tokens,
* Job Objects, and Low Integrity levels for process isolation.
* Uses a native C# helper to bypass PowerShell restrictions.
*/
export class WindowsSandboxManager implements SandboxManager {
static readonly HELPER_EXE = 'GeminiSandbox.exe';
private readonly helperPath: string;
private initialized = false;
private readonly allowedCache = new Set<string>();
private readonly deniedCache = new Set<string>();
constructor(private readonly options: GlobalSandboxOptions) {
this.helperPath = path.resolve(__dirname, 'GeminiSandbox.exe');
this.helperPath = path.resolve(__dirname, WindowsSandboxManager.HELPER_EXE);
}
isKnownSafeCommand(args: string[]): boolean {
@@ -76,6 +80,10 @@ export class WindowsSandboxManager implements SandboxManager {
return this.options.workspace;
}
getOptions(): GlobalSandboxOptions {
return this.options;
}
/**
* Ensures a file or directory exists.
*/
@@ -259,9 +267,14 @@ export class WindowsSandboxManager implements SandboxManager {
this.options.modeConfig?.network ?? req.policy?.networkAccess ?? false;
const networkAccess = defaultNetwork || mergedAdditional.network;
// 1. Handle filesystem permissions for Low Integrity
// Grant "Low Mandatory Level" write access to the workspace.
// If not in readonly mode OR it's a strictly approved pipeline, allow workspace writes
const { allowed: allowedPaths, forbidden: forbiddenPaths } =
await resolveSandboxPaths(this.options, req);
// Track all roots where Low Integrity write access has been granted.
// New files created within these roots will inherit the Low label.
const writableRoots: string[] = [];
// 1. Workspace access
const isApproved = allowOverrides
? await isStrictlyApproved(
command,
@@ -272,20 +285,19 @@ export class WindowsSandboxManager implements SandboxManager {
if (!isReadonlyMode || isApproved) {
await this.grantLowIntegrityAccess(this.options.workspace);
writableRoots.push(this.options.workspace);
}
const { allowed: allowedPaths, forbidden: forbiddenPaths } =
await resolveSandboxPaths(this.options, req);
// Grant "Low Mandatory Level" access to includeDirectories.
// 2. Globally included directories
const includeDirs = sanitizePaths(this.options.includeDirectories);
for (const includeDir of includeDirs) {
await this.grantLowIntegrityAccess(includeDir);
writableRoots.push(includeDir);
}
// Grant "Low Mandatory Level" read/write access to allowedPaths.
// 3. Explicitly allowed paths from the request policy
for (const allowedPath of allowedPaths) {
const resolved = await tryRealpath(allowedPath);
const resolved = resolveToRealPath(allowedPath);
try {
await fs.promises.access(resolved, fs.constants.F_OK);
} catch {
@@ -295,23 +307,32 @@ export class WindowsSandboxManager implements SandboxManager {
);
}
await this.grantLowIntegrityAccess(resolved);
writableRoots.push(resolved);
}
// Grant "Low Mandatory Level" write access to additional permissions write paths.
// 4. Additional write paths (e.g. from internal __write command)
const additionalWritePaths = sanitizePaths(
mergedAdditional.fileSystem?.write,
);
for (const writePath of additionalWritePaths) {
const resolved = await tryRealpath(writePath);
const resolved = resolveToRealPath(writePath);
try {
await fs.promises.access(resolved, fs.constants.F_OK);
await this.grantLowIntegrityAccess(resolved);
continue;
} catch {
throw new Error(
`Sandbox request rejected: Additional write path does not exist: ${resolved}. ` +
'On Windows, granular sandbox access can only be granted to existing paths to avoid broad parent directory permissions.',
// If the file doesn't exist, it's only allowed if it resides within a granted root.
const isInherited = writableRoots.some((root) =>
isSubpath(root, resolved),
);
if (!isInherited) {
throw new Error(
`Sandbox request rejected: Additional write path does not exist and its parent directory is not allowed: ${resolved}. ` +
'On Windows, granular sandbox access can only be granted to existing paths to avoid broad parent directory permissions.',
);
}
}
await this.grantLowIntegrityAccess(resolved);
}
// 2. Collect secret files and apply protective ACLs
@@ -382,15 +403,6 @@ export class WindowsSandboxManager implements SandboxManager {
const manifestPath = path.join(tempDir, 'manifest.txt');
fs.writeFileSync(manifestPath, allForbidden.join('\n'));
// Cleanup on exit
process.on('exit', () => {
try {
fs.rmSync(tempDir, { recursive: true, force: true });
} catch {
// Ignore errors
}
});
// 5. Construct the helper command
// GeminiSandbox.exe <network:0|1> <cwd> --forbidden-manifest <path> <command> [args...]
const program = this.helperPath;
@@ -411,6 +423,13 @@ export class WindowsSandboxManager implements SandboxManager {
args: finalArgs,
env: finalEnv,
cwd: req.cwd,
cleanup: () => {
try {
fs.rmSync(tempDir, { recursive: true, force: true });
} catch {
// Ignore errors
}
},
};
}
@@ -422,7 +441,7 @@ export class WindowsSandboxManager implements SandboxManager {
return;
}
const resolvedPath = await tryRealpath(targetPath);
const resolvedPath = resolveToRealPath(targetPath);
if (this.allowedCache.has(resolvedPath)) {
return;
}
@@ -446,8 +465,12 @@ export class WindowsSandboxManager implements SandboxManager {
}
try {
// 1. Grant explicit Modify access to the Low Integrity SID
// 2. Set the Mandatory Label to Low to allow "Write Up" from Low processes
await spawnAsync('icacls', [
resolvedPath,
'/grant',
`${LOW_INTEGRITY_SID}:(OI)(CI)(M)`,
'/setintegritylevel',
'(OI)(CI)Low',
]);
@@ -469,7 +492,7 @@ export class WindowsSandboxManager implements SandboxManager {
return;
}
const resolvedPath = await tryRealpath(targetPath);
const resolvedPath = resolveToRealPath(targetPath);
if (this.deniedCache.has(resolvedPath)) {
return;
}
@@ -479,9 +502,6 @@ export class WindowsSandboxManager implements SandboxManager {
return;
}
// S-1-16-4096 is the SID for "Low Mandatory Level" (Low Integrity)
const LOW_INTEGRITY_SID = '*S-1-16-4096';
// icacls flags: (OI) Object Inherit, (CI) Container Inherit, (F) Full Access Deny.
// Omit /T (recursive) for performance; (OI)(CI) ensures inheritance for new items.
// Windows dynamically evaluates existing items, though deep explicit Allow ACEs
@@ -28,7 +28,14 @@ const Platform = {
/** Returns a command to create an empty file. */
touch(filePath: string) {
return this.isWindows
? { command: 'cmd.exe', args: ['/c', `type nul > "${filePath}"`] }
? {
command: 'powershell.exe',
args: [
'-NoProfile',
'-Command',
`New-Item -Path "${filePath}" -ItemType File -Force`,
],
}
: { command: 'touch', args: [filePath] };
},
@@ -48,18 +55,13 @@ const Platform = {
/** Returns a command to perform a network request. */
curl(url: string) {
return this.isWindows
? {
command: 'powershell.exe',
args: ['-Command', `Invoke-WebRequest -Uri ${url} -TimeoutSec 1`],
}
: { command: 'curl', args: ['-s', '--connect-timeout', '1', url] };
return { command: 'curl', args: ['-s', '--connect-timeout', '1', url] };
},
/** Returns a command that checks if the current terminal is interactive. */
isPty() {
return this.isWindows
? 'cmd.exe /c echo True'
? 'powershell.exe -NoProfile -Command "echo True"'
: 'bash -c "if [ -t 1 ]; then echo True; else echo False; fi"';
},
@@ -103,7 +105,7 @@ function ensureSandboxAvailable(): boolean {
if (platform === 'win32') {
// Windows sandboxing relies on icacls, which is a core system utility and
// always available.
// TODO: reenable once test is fixed
// TODO: reenable once flakiness is addressed
return false;
}
@@ -167,23 +169,28 @@ describe('SandboxManager Integration', () => {
expect(result.stdout.trim()).toBe('sandbox test');
});
it('supports interactive pseudo-terminals (node-pty)', async () => {
const handle = await ShellExecutionService.execute(
Platform.isPty(),
workspace,
() => {},
new AbortController().signal,
true,
{
sanitizationConfig: getSecureSanitizationConfig(),
sandboxManager: manager,
},
);
// The Windows sandbox wrapper (GeminiSandbox.exe) uses standard pipes
// for I/O interception, which breaks ConPTY pseudo-terminal inheritance.
it.skipIf(Platform.isWindows)(
'supports interactive pseudo-terminals (node-pty)',
async () => {
const handle = await ShellExecutionService.execute(
Platform.isPty(),
workspace,
() => {},
new AbortController().signal,
true,
{
sanitizationConfig: getSecureSanitizationConfig(),
sandboxManager: manager,
},
);
const result = await handle.result;
expect(result.exitCode).toBe(0);
expect(result.output).toContain('True');
});
const result = await handle.result;
expect(result.exitCode).toBe(0);
expect(result.output).toContain('True');
},
);
});
describe('File System Access', () => {
@@ -511,18 +518,23 @@ describe('SandboxManager Integration', () => {
if (server) await new Promise<void>((res) => server.close(() => res()));
});
it('blocks network access by default', async () => {
const { command, args } = Platform.curl(url);
const sandboxed = await manager.prepareCommand({
command,
args,
cwd: workspace,
env: process.env,
});
// Windows Job Object rate limits exempt loopback (127.0.0.1) traffic,
// so this test cannot verify loopback blocking on Windows.
it.skipIf(Platform.isWindows)(
'blocks network access by default',
async () => {
const { command, args } = Platform.curl(url);
const sandboxed = await manager.prepareCommand({
command,
args,
cwd: workspace,
env: process.env,
});
const result = await runCommand(sandboxed);
expect(result.status).not.toBe(0);
});
const result = await runCommand(sandboxed);
expect(result.status).not.toBe(0);
},
);
it('grants network access when explicitly allowed', async () => {
const { command, args } = Platform.curl(url);
@@ -146,6 +146,11 @@ export interface SandboxManager {
* Returns the primary workspace directory for this sandbox.
*/
getWorkspace(): string;
/**
* Returns the global sandbox options for this sandbox.
*/
getOptions(): GlobalSandboxOptions | undefined;
}
/**
@@ -283,6 +288,10 @@ export class NoopSandboxManager implements SandboxManager {
getWorkspace(): string {
return this.options?.workspace ?? process.cwd();
}
getOptions(): GlobalSandboxOptions | undefined {
return this.options;
}
}
/**
@@ -310,6 +319,10 @@ export class LocalSandboxManager implements SandboxManager {
getWorkspace(): string {
return this.options?.workspace ?? process.cwd();
}
getOptions(): GlobalSandboxOptions | undefined {
return this.options;
}
}
/**
@@ -18,6 +18,7 @@ import type {
SandboxManager,
SandboxRequest,
SandboxedCommand,
GlobalSandboxOptions,
} from './sandboxManager.js';
import { spawn, type ChildProcess } from 'node:child_process';
import { EventEmitter } from 'node:events';
@@ -52,6 +53,13 @@ class MockSandboxManager implements SandboxManager {
getWorkspace(): string {
return path.resolve('/workspace');
}
getOptions(): GlobalSandboxOptions | undefined {
return {
workspace: path.resolve('/workspace'),
includeDirectories: [path.resolve('/test/cwd')],
};
}
}
describe('SandboxedFileSystemService', () => {
@@ -22,12 +22,29 @@ export class SandboxedFileSystemService implements FileSystemService {
private sanitizeAndValidatePath(filePath: string): string {
const resolvedPath = resolveToRealPath(filePath);
if (!isSubpath(this.cwd, resolvedPath) && this.cwd !== resolvedPath) {
throw new Error(
`Access denied: Path '${filePath}' is outside the workspace.`,
);
const workspace = resolveToRealPath(this.sandboxManager.getWorkspace());
if (isSubpath(workspace, resolvedPath) || workspace === resolvedPath) {
return resolvedPath;
}
return resolvedPath;
// Check if the path is explicitly allowed by the sandbox manager
const options = this.sandboxManager.getOptions();
const allowedPaths = options?.includeDirectories ?? [];
for (const allowed of allowedPaths) {
const resolvedAllowed = resolveToRealPath(allowed);
if (
isSubpath(resolvedAllowed, resolvedPath) ||
resolvedAllowed === resolvedPath
) {
return resolvedPath;
}
}
throw new Error(
`Access denied: Path '${filePath}' is outside the workspace and not in allowed paths.`,
);
}
async readTextFile(filePath: string): Promise<string> {
@@ -155,6 +155,7 @@ const createMockSerializeTerminalToObjectReturnValue = (
underline: false,
dim: false,
inverse: false,
isUninitialized: false,
fg: '#ffffff',
bg: '#000000',
},
@@ -173,6 +174,7 @@ const createExpectedAnsiOutput = (text: string | string[]): AnsiOutput => {
underline: false,
dim: false,
inverse: false,
isUninitialized: false,
fg: '',
bg: '',
} as AnsiToken,
@@ -2015,6 +2017,7 @@ describe('ShellExecutionService environment variables', () => {
isDangerousCommand: vi.fn().mockReturnValue(false),
parseDenials: vi.fn().mockReturnValue(undefined),
getWorkspace: vi.fn().mockReturnValue('/workspace'),
getOptions: vi.fn().mockReturnValue(undefined),
};
const configWithSandbox: ShellExecutionConfig = {
@@ -1692,4 +1692,187 @@ describe('ClearcutLogger', () => {
]);
});
});
describe('logBrowserAgentConnectionEvent', () => {
it('logs a successful connection event', () => {
const { logger } = setup();
logger?.logBrowserAgentConnectionEvent({
session_mode: 'isolated',
headless: true,
success: true,
duration_ms: 1500,
});
const events = getEvents(logger!);
expect(events.length).toBe(1);
expect(events[0]).toHaveEventName(EventNames.BROWSER_AGENT_CONNECTION);
expect(events[0]).toHaveMetadataValue([
EventMetadataKey.GEMINI_CLI_BROWSER_AGENT_SESSION_MODE,
'isolated',
]);
expect(events[0]).toHaveMetadataValue([
EventMetadataKey.GEMINI_CLI_BROWSER_AGENT_HEADLESS,
'true',
]);
expect(events[0]).toHaveMetadataValue([
EventMetadataKey.GEMINI_CLI_BROWSER_AGENT_SUCCESS,
'true',
]);
expect(events[0]).toHaveMetadataValue([
EventMetadataKey.GEMINI_CLI_BROWSER_AGENT_DURATION_MS,
'1500',
]);
});
it('logs a failed connection event with error_type', () => {
const { logger } = setup();
logger?.logBrowserAgentConnectionEvent({
session_mode: 'persistent',
headless: false,
success: false,
duration_ms: 30000,
error_type: 'timeout',
});
const events = getEvents(logger!);
expect(events.length).toBe(1);
expect(events[0]).toHaveMetadataValue([
EventMetadataKey.GEMINI_CLI_BROWSER_AGENT_SUCCESS,
'false',
]);
expect(events[0]).toHaveMetadataValue([
EventMetadataKey.GEMINI_CLI_BROWSER_AGENT_ERROR_TYPE,
'timeout',
]);
});
it('logs tool_count when provided', () => {
const { logger } = setup();
logger?.logBrowserAgentConnectionEvent({
session_mode: 'existing',
headless: true,
success: true,
duration_ms: 800,
tool_count: 12,
});
const events = getEvents(logger!);
expect(events[0]).toHaveMetadataValue([
EventMetadataKey.GEMINI_CLI_BROWSER_AGENT_TOOL_COUNT,
'12',
]);
});
});
describe('logBrowserAgentVisionStatusEvent', () => {
it('logs vision enabled', () => {
const { logger } = setup();
logger?.logBrowserAgentVisionStatusEvent({ enabled: true });
const events = getEvents(logger!);
expect(events.length).toBe(1);
expect(events[0]).toHaveEventName(EventNames.BROWSER_AGENT_VISION_STATUS);
expect(events[0]).toHaveMetadataValue([
EventMetadataKey.GEMINI_CLI_BROWSER_AGENT_VISION_ENABLED,
'true',
]);
});
it('logs vision disabled with reason', () => {
const { logger } = setup();
logger?.logBrowserAgentVisionStatusEvent({
enabled: false,
disabled_reason: 'no_visual_model',
});
const events = getEvents(logger!);
expect(events[0]).toHaveMetadataValue([
EventMetadataKey.GEMINI_CLI_BROWSER_AGENT_VISION_ENABLED,
'false',
]);
expect(events[0]).toHaveMetadataValue([
EventMetadataKey.GEMINI_CLI_BROWSER_AGENT_VISION_DISABLED_REASON,
'no_visual_model',
]);
});
});
describe('logBrowserAgentTaskOutcomeEvent', () => {
it('logs a task outcome event with all attributes', () => {
const { logger } = setup();
logger?.logBrowserAgentTaskOutcomeEvent({
success: true,
session_mode: 'isolated',
vision_enabled: true,
headless: true,
duration_ms: 5000,
});
const events = getEvents(logger!);
expect(events.length).toBe(1);
expect(events[0]).toHaveEventName(EventNames.BROWSER_AGENT_TASK_OUTCOME);
expect(events[0]).toHaveMetadataValue([
EventMetadataKey.GEMINI_CLI_BROWSER_AGENT_SUCCESS,
'true',
]);
expect(events[0]).toHaveMetadataValue([
EventMetadataKey.GEMINI_CLI_BROWSER_AGENT_SESSION_MODE,
'isolated',
]);
expect(events[0]).toHaveMetadataValue([
EventMetadataKey.GEMINI_CLI_BROWSER_AGENT_VISION_ENABLED,
'true',
]);
expect(events[0]).toHaveMetadataValue([
EventMetadataKey.GEMINI_CLI_BROWSER_AGENT_HEADLESS,
'true',
]);
expect(events[0]).toHaveMetadataValue([
EventMetadataKey.GEMINI_CLI_BROWSER_AGENT_DURATION_MS,
'5000',
]);
});
});
describe('logBrowserAgentCleanupEvent', () => {
it('logs a cleanup event with all attributes', () => {
const { logger } = setup();
logger?.logBrowserAgentCleanupEvent({
session_mode: 'isolated',
success: true,
duration_ms: 200,
});
const events = getEvents(logger!);
expect(events.length).toBe(1);
expect(events[0]).toHaveEventName(EventNames.BROWSER_AGENT_CLEANUP);
expect(events[0]).toHaveMetadataValue([
EventMetadataKey.GEMINI_CLI_BROWSER_AGENT_SESSION_MODE,
'isolated',
]);
expect(events[0]).toHaveMetadataValue([
EventMetadataKey.GEMINI_CLI_BROWSER_AGENT_SUCCESS,
'true',
]);
expect(events[0]).toHaveMetadataValue([
EventMetadataKey.GEMINI_CLI_BROWSER_AGENT_DURATION_MS,
'200',
]);
});
it('logs a failed cleanup event', () => {
const { logger } = setup();
logger?.logBrowserAgentCleanupEvent({
session_mode: 'persistent',
success: false,
duration_ms: 5000,
});
const events = getEvents(logger!);
expect(events[0]).toHaveMetadataValue([
EventMetadataKey.GEMINI_CLI_BROWSER_AGENT_SUCCESS,
'false',
]);
});
});
});
@@ -135,6 +135,10 @@ export enum EventNames {
OVERAGE_OPTION_SELECTED = 'overage_option_selected',
EMPTY_WALLET_MENU_SHOWN = 'empty_wallet_menu_shown',
CREDIT_PURCHASE_CLICK = 'credit_purchase_click',
BROWSER_AGENT_CONNECTION = 'browser_agent_connection',
BROWSER_AGENT_VISION_STATUS = 'browser_agent_vision_status',
BROWSER_AGENT_TASK_OUTCOME = 'browser_agent_task_outcome',
BROWSER_AGENT_CLEANUP = 'browser_agent_cleanup',
}
export interface LogResponse {
@@ -1935,6 +1939,146 @@ export class ClearcutLogger {
this.flushIfNeeded();
}
// ==========================================================================
// Browser Agent Events
// ==========================================================================
logBrowserAgentConnectionEvent(attrs: {
session_mode: string;
headless: boolean;
success: boolean;
duration_ms: number;
error_type?: string;
tool_count?: number;
}): void {
const data: EventValue[] = [
{
gemini_cli_key: EventMetadataKey.GEMINI_CLI_BROWSER_AGENT_SESSION_MODE,
value: attrs.session_mode,
},
{
gemini_cli_key: EventMetadataKey.GEMINI_CLI_BROWSER_AGENT_HEADLESS,
value: attrs.headless.toString(),
},
{
gemini_cli_key: EventMetadataKey.GEMINI_CLI_BROWSER_AGENT_SUCCESS,
value: attrs.success.toString(),
},
{
gemini_cli_key: EventMetadataKey.GEMINI_CLI_BROWSER_AGENT_DURATION_MS,
value: attrs.duration_ms.toString(),
},
];
if (attrs.error_type) {
data.push({
gemini_cli_key: EventMetadataKey.GEMINI_CLI_BROWSER_AGENT_ERROR_TYPE,
value: attrs.error_type,
});
}
if (attrs.tool_count !== undefined) {
data.push({
gemini_cli_key: EventMetadataKey.GEMINI_CLI_BROWSER_AGENT_TOOL_COUNT,
value: attrs.tool_count.toString(),
});
}
this.enqueueLogEvent(
this.createLogEvent(EventNames.BROWSER_AGENT_CONNECTION, data),
);
this.flushIfNeeded();
}
logBrowserAgentVisionStatusEvent(attrs: {
enabled: boolean;
disabled_reason?: string;
}): void {
const data: EventValue[] = [
{
gemini_cli_key:
EventMetadataKey.GEMINI_CLI_BROWSER_AGENT_VISION_ENABLED,
value: attrs.enabled.toString(),
},
];
if (attrs.disabled_reason) {
data.push({
gemini_cli_key:
EventMetadataKey.GEMINI_CLI_BROWSER_AGENT_VISION_DISABLED_REASON,
value: attrs.disabled_reason,
});
}
this.enqueueLogEvent(
this.createLogEvent(EventNames.BROWSER_AGENT_VISION_STATUS, data),
);
this.flushIfNeeded();
}
logBrowserAgentTaskOutcomeEvent(attrs: {
success: boolean;
session_mode: string;
vision_enabled: boolean;
headless: boolean;
duration_ms: number;
}): void {
const data: EventValue[] = [
{
gemini_cli_key: EventMetadataKey.GEMINI_CLI_BROWSER_AGENT_SUCCESS,
value: attrs.success.toString(),
},
{
gemini_cli_key: EventMetadataKey.GEMINI_CLI_BROWSER_AGENT_SESSION_MODE,
value: attrs.session_mode,
},
{
gemini_cli_key:
EventMetadataKey.GEMINI_CLI_BROWSER_AGENT_VISION_ENABLED,
value: attrs.vision_enabled.toString(),
},
{
gemini_cli_key: EventMetadataKey.GEMINI_CLI_BROWSER_AGENT_HEADLESS,
value: attrs.headless.toString(),
},
{
gemini_cli_key: EventMetadataKey.GEMINI_CLI_BROWSER_AGENT_DURATION_MS,
value: attrs.duration_ms.toString(),
},
];
this.enqueueLogEvent(
this.createLogEvent(EventNames.BROWSER_AGENT_TASK_OUTCOME, data),
);
this.flushIfNeeded();
}
logBrowserAgentCleanupEvent(attrs: {
session_mode: string;
success: boolean;
duration_ms: number;
}): void {
const data: EventValue[] = [
{
gemini_cli_key: EventMetadataKey.GEMINI_CLI_BROWSER_AGENT_SESSION_MODE,
value: attrs.session_mode,
},
{
gemini_cli_key: EventMetadataKey.GEMINI_CLI_BROWSER_AGENT_SUCCESS,
value: attrs.success.toString(),
},
{
gemini_cli_key: EventMetadataKey.GEMINI_CLI_BROWSER_AGENT_DURATION_MS,
value: attrs.duration_ms.toString(),
},
];
this.enqueueLogEvent(
this.createLogEvent(EventNames.BROWSER_AGENT_CLEANUP, data),
);
this.flushIfNeeded();
}
/**
* Adds default fields to data, and returns a new data array. This fields
* should exist on all log events.
@@ -7,7 +7,7 @@
// Defines valid event metadata keys for Clearcut logging.
export enum EventMetadataKey {
// Deleted enums: 24
// Next ID: 195
// Next ID: 203
GEMINI_CLI_KEY_UNKNOWN = 0,
@@ -725,4 +725,32 @@ export enum EventMetadataKey {
// Logs the duration of the onboarding process in milliseconds.
GEMINI_CLI_ONBOARDING_DURATION_MS = 194,
// ==========================================================================
// Browser Agent Event Keys
// ==========================================================================
// Logs the browser agent session mode (persistent, isolated, existing).
GEMINI_CLI_BROWSER_AGENT_SESSION_MODE = 195,
// Logs whether the browser agent ran in headless mode.
GEMINI_CLI_BROWSER_AGENT_HEADLESS = 196,
// Logs whether the browser agent operation was successful.
GEMINI_CLI_BROWSER_AGENT_SUCCESS = 197,
// Logs the error type for a browser agent connection failure.
GEMINI_CLI_BROWSER_AGENT_ERROR_TYPE = 198,
// Logs the duration in milliseconds for a browser agent operation.
GEMINI_CLI_BROWSER_AGENT_DURATION_MS = 199,
// Logs whether vision mode was enabled for the browser agent.
GEMINI_CLI_BROWSER_AGENT_VISION_ENABLED = 200,
// Logs the reason vision mode was disabled for the browser agent.
GEMINI_CLI_BROWSER_AGENT_VISION_DISABLED_REASON = 201,
// Logs the number of tools discovered from the MCP server.
GEMINI_CLI_BROWSER_AGENT_TOOL_COUNT = 202,
}
+91
View File
@@ -83,6 +83,10 @@ import {
recordInvalidChunk,
recordOnboardingStart,
recordOnboardingSuccess,
recordBrowserAgentConnection,
recordBrowserAgentVisionStatus,
recordBrowserAgentTaskOutcome,
recordBrowserAgentCleanup,
} from './metrics.js';
import { bufferTelemetryEvent } from './sdk.js';
import { uiTelemetryService, type UiEvent } from './uiTelemetry.js';
@@ -939,3 +943,90 @@ export function logBillingEvent(
}
}
}
// ==========================================================================
// Browser Agent Events
// ==========================================================================
export function logBrowserAgentConnection(
config: Config,
durationMs: number,
attributes: {
session_mode: 'persistent' | 'isolated' | 'existing';
headless: boolean;
success: boolean;
error_type?:
| 'profile_locked'
| 'timeout'
| 'connection_refused'
| 'unknown';
tool_count?: number;
},
): void {
ClearcutLogger.getInstance(config)?.logBrowserAgentConnectionEvent({
session_mode: attributes.session_mode,
headless: attributes.headless,
success: attributes.success,
duration_ms: durationMs,
error_type: attributes.error_type,
tool_count: attributes.tool_count,
});
recordBrowserAgentConnection(config, durationMs, attributes);
}
export function logBrowserAgentVisionStatus(
config: Config,
attributes: {
enabled: boolean;
disabled_reason?:
| 'no_visual_model'
| 'missing_visual_tools'
| 'blocked_auth_type';
},
): void {
ClearcutLogger.getInstance(config)?.logBrowserAgentVisionStatusEvent({
enabled: attributes.enabled,
disabled_reason: attributes.disabled_reason,
});
recordBrowserAgentVisionStatus(config, attributes);
}
export function logBrowserAgentTaskOutcome(
config: Config,
attributes: {
success: boolean;
session_mode: 'persistent' | 'isolated' | 'existing';
vision_enabled: boolean;
headless: boolean;
duration_ms: number;
},
): void {
ClearcutLogger.getInstance(config)?.logBrowserAgentTaskOutcomeEvent({
success: attributes.success,
session_mode: attributes.session_mode,
vision_enabled: attributes.vision_enabled,
headless: attributes.headless,
duration_ms: attributes.duration_ms,
});
recordBrowserAgentTaskOutcome(config, attributes);
}
export function logBrowserAgentCleanup(
config: Config,
durationMs: number,
attributes: {
session_mode: 'persistent' | 'isolated' | 'existing';
success: boolean;
},
): void {
ClearcutLogger.getInstance(config)?.logBrowserAgentCleanupEvent({
session_mode: attributes.session_mode,
success: attributes.success,
duration_ms: durationMs,
});
recordBrowserAgentCleanup(config, durationMs, attributes);
}
@@ -1687,6 +1687,29 @@ describe('Telemetry Metrics', () => {
expect(mockCounterAddFn).not.toHaveBeenCalled();
});
it('records tool_count on success when provided', () => {
initializeMetricsModule(mockConfig);
mockCounterAddFn.mockClear();
mockHistogramRecordFn.mockClear();
recordBrowserAgentConnectionModule(mockConfig, 1200, {
session_mode: 'isolated',
headless: false,
success: true,
tool_count: 5,
});
expect(mockHistogramRecordFn).toHaveBeenCalledWith(1200, {
'session.id': 'test-session-id',
'installation.id': 'test-installation-id',
'user.email': 'test@example.com',
session_mode: 'isolated',
headless: false,
success: true,
tool_count: 5,
});
});
it('records connection duration and failure counter on error', () => {
initializeMetricsModule(mockConfig);
mockCounterAddFn.mockClear();
+2
View File
@@ -1624,6 +1624,7 @@ export function recordBrowserAgentConnection(
| 'timeout'
| 'connection_refused'
| 'unknown';
tool_count?: number;
},
): void {
if (!isMetricsInitialized) return;
@@ -1635,6 +1636,7 @@ export function recordBrowserAgentConnection(
session_mode: attributes.session_mode,
headless: attributes.headless,
success: attributes.success,
tool_count: attributes.tool_count,
});
if (!attributes.success && browserAgentConnectionFailureCounter) {
+42 -1
View File
@@ -154,7 +154,11 @@ describe('ShellTool', () => {
return mockSandboxManager;
},
sandboxPolicyManager: {
getCommandPermissions: vi.fn().mockReturnValue(undefined),
getCommandPermissions: vi.fn().mockReturnValue({
fileSystem: { read: [], write: [] },
network: false,
}),
getModeConfig: vi.fn().mockReturnValue({ readonly: false }),
addPersistentApproval: vi.fn(),
addSessionApproval: vi.fn(),
@@ -708,6 +712,39 @@ describe('ShellTool', () => {
it('should throw an error if validation fails', () => {
expect(() => shellTool.build({ command: '' })).toThrow();
});
it('should NOT return a sandbox expansion prompt for npm install when sandboxing is disabled', async () => {
const bus = (shellTool as unknown as { messageBus: MessageBus })
.messageBus;
const mockBus = getMockMessageBusInstance(
bus,
) as unknown as TestableMockMessageBus;
mockBus.defaultToolDecision = 'allow';
vi.mocked(mockConfig.getSandboxEnabled).mockReturnValue(false);
const params = { command: 'npm install' };
const invocation = shellTool.build(params);
const confirmation = await invocation.shouldConfirmExecute(
new AbortController().signal,
);
// Should be false because standard confirm mode is 'allow'
expect(confirmation).toBe(false);
});
it('should return a sandbox expansion prompt for npm install when sandboxing is enabled', async () => {
vi.mocked(mockConfig.getSandboxEnabled).mockReturnValue(true);
const params = { command: 'npm install' };
const invocation = shellTool.build(params);
const confirmation = await invocation.shouldConfirmExecute(
new AbortController().signal,
);
expect(confirmation).not.toBe(false);
expect(confirmation && confirmation.type).toBe('sandbox_expansion');
});
});
describe('getDescription', () => {
@@ -950,6 +987,10 @@ describe('ShellTool', () => {
describe('sandbox heuristics', () => {
const mockAbortSignal = new AbortController().signal;
beforeEach(() => {
vi.mocked(mockConfig.getSandboxEnabled).mockReturnValue(true);
});
it('should suggest proactive permissions for npm commands', async () => {
const homeDir = path.join(tempRootDir, 'home');
fs.mkdirSync(homeDir);
+109 -77
View File
@@ -10,7 +10,10 @@ import path from 'node:path';
import os from 'node:os';
import crypto from 'node:crypto';
import { debugLogger } from '../index.js';
import type { SandboxPermissions } from '../services/sandboxManager.js';
import {
type SandboxPermissions,
getPathIdentity,
} from '../services/sandboxManager.js';
import { ToolErrorType } from './tool-error.js';
import {
BaseDeclarativeTool,
@@ -42,6 +45,7 @@ import {
stripShellWrapper,
parseCommandDetails,
hasRedirection,
normalizeCommand,
} from '../utils/shell-utils.js';
import { SHELL_TOOL_NAME } from './tool-names.js';
import { PARAM_ADDITIONAL_PERMISSIONS } from './definitions/base-declarations.js';
@@ -49,7 +53,7 @@ import type { MessageBus } from '../confirmation-bus/message-bus.js';
import { getShellDefinition } from './definitions/coreTools.js';
import { resolveToolDeclaration } from './definitions/resolver.js';
import type { AgentLoopContext } from '../config/agent-loop-context.js';
import { isSubpath } from '../utils/paths.js';
import { isSubpath, resolveToRealPath } from '../utils/paths.js';
import {
getProactiveToolSuggestions,
isNetworkReliantCommand,
@@ -132,7 +136,7 @@ export class ShellToolInvocation extends BaseToolInvocation<
}
getDescription(): string {
return `${this.params.command} ${this.getContextualDetails()}`;
return this.params.description || '';
}
private simplifyPaths(paths: Set<string>): string[] {
@@ -247,77 +251,103 @@ export class ShellToolInvocation extends BaseToolInvocation<
return this.getConfirmationDetails(abortSignal);
}
// Proactively suggest expansion for known network-heavy Node.js ecosystem tools
// (npm install, etc.) to avoid hangs when network is restricted by default.
// We do this even if the command is "allowed" by policy because the DEFAULT
// permissions are usually insufficient for these commands.
const command = stripShellWrapper(this.params.command);
const rootCommands = getCommandRoots(command);
const rootCommand = rootCommands[0];
if (this.context.config.getSandboxEnabled()) {
const command = stripShellWrapper(this.params.command);
const rootCommands = getCommandRoots(command);
const rawRootCommand = rootCommands[0];
if (rootCommand) {
const proactive = await getProactiveToolSuggestions(rootCommand);
if (proactive) {
const approved =
this.context.config.sandboxPolicyManager.getCommandPermissions(
rootCommand,
);
const missingNetwork = !!proactive.network && !approved?.network;
// Detect commands or sub-commands that definitely need network
const parsed = parseCommandDetails(command);
const subCommand = parsed?.details[0]?.args?.[0];
const needsNetwork = isNetworkReliantCommand(rootCommand, subCommand);
if (needsNetwork) {
// Add write permission to the current directory if we are in readonly mode
if (rawRootCommand) {
const rootCommand = normalizeCommand(rawRootCommand);
const proactive = await getProactiveToolSuggestions(rootCommand);
if (proactive) {
const mode = this.context.config.getApprovalMode();
const isReadonlyMode =
this.context.config.sandboxPolicyManager.getModeConfig(mode)
?.readonly ?? false;
const modeConfig =
this.context.config.sandboxPolicyManager.getModeConfig(mode);
const approved =
this.context.config.sandboxPolicyManager.getCommandPermissions(
rootCommand,
);
if (isReadonlyMode) {
const cwd =
this.params.dir_path || this.context.config.getTargetDir();
proactive.fileSystem = proactive.fileSystem || {
read: [],
write: [],
};
proactive.fileSystem.write = proactive.fileSystem.write || [];
if (!proactive.fileSystem.write.includes(cwd)) {
proactive.fileSystem.write.push(cwd);
proactive.fileSystem.read = proactive.fileSystem.read || [];
if (!proactive.fileSystem.read.includes(cwd)) {
proactive.fileSystem.read.push(cwd);
const hasNetwork = modeConfig.network || approved.network;
const missingNetwork = !!proactive.network && !hasNetwork;
// Detect commands or sub-commands that definitely need network
const parsed = parseCommandDetails(command);
const subCommand = parsed?.details[0]?.args?.[0];
const needsNetwork = isNetworkReliantCommand(rootCommand, subCommand);
if (needsNetwork) {
// Add write permission to the current directory if we are in readonly mode
const isReadonlyMode = modeConfig.readonly ?? false;
if (isReadonlyMode) {
const cwd =
this.params.dir_path || this.context.config.getTargetDir();
proactive.fileSystem = proactive.fileSystem || {
read: [],
write: [],
};
proactive.fileSystem.write = proactive.fileSystem.write || [];
if (!proactive.fileSystem.write.includes(cwd)) {
proactive.fileSystem.write.push(cwd);
proactive.fileSystem.read = proactive.fileSystem.read || [];
if (!proactive.fileSystem.read.includes(cwd)) {
proactive.fileSystem.read.push(cwd);
}
}
}
}
const missingRead = (proactive.fileSystem?.read || []).filter(
(p) => !approved?.fileSystem?.read?.includes(p),
);
const missingWrite = (proactive.fileSystem?.write || []).filter(
(p) => !approved?.fileSystem?.write?.includes(p),
);
const isApproved = (
requestedPath: string,
approvedPaths?: string[],
): boolean => {
if (!approvedPaths || approvedPaths.length === 0) return false;
const requestedRealIdentity = getPathIdentity(
resolveToRealPath(requestedPath),
);
const needsExpansion =
missingRead.length > 0 || missingWrite.length > 0 || missingNetwork;
// Identity check is fast, subpath check is slower
return approvedPaths.some((p) => {
const approvedRealIdentity = getPathIdentity(
resolveToRealPath(p),
);
return (
requestedRealIdentity === approvedRealIdentity ||
isSubpath(approvedRealIdentity, requestedRealIdentity)
);
});
};
if (needsExpansion) {
const details = await this.getConfirmationDetails(
abortSignal,
proactive,
const missingRead = (proactive.fileSystem?.read || []).filter(
(p) => !isApproved(p, approved.fileSystem?.read),
);
if (details && details.type === 'sandbox_expansion') {
const originalOnConfirm = details.onConfirm;
details.onConfirm = async (outcome: ToolConfirmationOutcome) => {
await originalOnConfirm(outcome);
if (outcome !== ToolConfirmationOutcome.Cancel) {
this.proactivePermissionsConfirmed = proactive;
}
};
const missingWrite = (proactive.fileSystem?.write || []).filter(
(p) => !isApproved(p, approved.fileSystem?.write),
);
const needsExpansion =
missingRead.length > 0 ||
missingWrite.length > 0 ||
missingNetwork;
if (needsExpansion) {
const details = await this.getConfirmationDetails(
abortSignal,
proactive,
);
if (details && details.type === 'sandbox_expansion') {
const originalOnConfirm = details.onConfirm;
details.onConfirm = async (
outcome: ToolConfirmationOutcome,
) => {
await originalOnConfirm(outcome);
if (outcome !== ToolConfirmationOutcome.Cancel) {
this.proactivePermissionsConfirmed = proactive;
}
};
}
return details;
}
return details;
}
}
}
@@ -742,20 +772,22 @@ export class ShellToolInvocation extends BaseToolInvocation<
);
// Proactive permission suggestions for Node ecosystem tools
const proactive =
await getProactiveToolSuggestions(rootCommandDisplay);
if (proactive) {
if (proactive.network) {
sandboxDenial.network = true;
}
if (proactive.fileSystem?.read) {
for (const p of proactive.fileSystem.read) {
readPaths.add(p);
if (this.context.config.getSandboxEnabled()) {
const proactive =
await getProactiveToolSuggestions(rootCommandDisplay);
if (proactive) {
if (proactive.network) {
sandboxDenial.network = true;
}
}
if (proactive.fileSystem?.write) {
for (const p of proactive.fileSystem.write) {
writePaths.add(p);
if (proactive.fileSystem?.read) {
for (const p of proactive.fileSystem.read) {
readPaths.add(p);
}
}
if (proactive.fileSystem?.write) {
for (const p of proactive.fileSystem.write) {
writePaths.add(p);
}
}
}
}
@@ -0,0 +1,180 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import {
vi,
describe,
it,
expect,
beforeEach,
beforeAll,
afterEach,
} from 'vitest';
import os from 'node:os';
import type _fs from 'node:fs';
import { ShellTool } from './shell.js';
import { type Config } from '../config/config.js';
import { createMockMessageBus } from '../test-utils/mock-message-bus.js';
import * as proactivePermissions from '../sandbox/utils/proactivePermissions.js';
import { initializeShellParsers } from '../utils/shell-utils.js';
vi.mock('node:fs', async (importOriginal) => {
const original = await importOriginal<typeof import('node:fs')>();
return {
...original,
default: {
...original,
realpathSync: vi.fn((p) => p),
},
realpathSync: vi.fn((p) => p),
};
});
vi.mock('../sandbox/utils/proactivePermissions.js', () => ({
getProactiveToolSuggestions: vi.fn(),
isNetworkReliantCommand: vi.fn(),
}));
const mockPlatform = (platform: string) => {
vi.stubGlobal(
'process',
Object.create(process, {
platform: {
get: () => platform,
},
}),
);
vi.spyOn(os, 'platform').mockReturnValue(platform as NodeJS.Platform);
};
describe('ShellTool Proactive Expansion', () => {
let mockConfig: Config;
let shellTool: ShellTool;
beforeAll(async () => {
await initializeShellParsers();
});
afterEach(() => {
vi.unstubAllGlobals();
vi.restoreAllMocks();
});
beforeEach(() => {
vi.clearAllMocks();
mockPlatform('darwin');
mockConfig = {
get config() {
return this;
},
getSandboxEnabled: vi.fn().mockReturnValue(false),
getTargetDir: vi.fn().mockReturnValue('/tmp'),
getApprovalMode: vi.fn().mockReturnValue('strict'),
sandboxPolicyManager: {
getCommandPermissions: vi.fn().mockReturnValue({
fileSystem: { read: [], write: [] },
network: false,
}),
getModeConfig: vi.fn().mockReturnValue({ readonly: false }),
},
getEnableInteractiveShell: vi.fn().mockReturnValue(false),
getEnableShellOutputEfficiency: vi.fn().mockReturnValue(true),
getShellToolInactivityTimeout: vi.fn().mockReturnValue(1000),
} as unknown as Config;
const bus = createMockMessageBus();
shellTool = new ShellTool(mockConfig, bus);
});
it('should NOT call getProactiveToolSuggestions when sandboxing is disabled', async () => {
const invocation = shellTool.build({ command: 'npm install' });
const abortSignal = new AbortController().signal;
await invocation.shouldConfirmExecute(abortSignal);
expect(
proactivePermissions.getProactiveToolSuggestions,
).not.toHaveBeenCalled();
});
it('should call getProactiveToolSuggestions when sandboxing is enabled', async () => {
vi.mocked(mockConfig.getSandboxEnabled).mockReturnValue(true);
vi.mocked(
proactivePermissions.getProactiveToolSuggestions,
).mockResolvedValue({
network: true,
});
vi.mocked(proactivePermissions.isNetworkReliantCommand).mockReturnValue(
true,
);
const invocation = shellTool.build({ command: 'npm install' });
const abortSignal = new AbortController().signal;
await invocation.shouldConfirmExecute(abortSignal);
expect(
proactivePermissions.getProactiveToolSuggestions,
).toHaveBeenCalledWith('npm');
});
it('should normalize command names (lowercase and strip .exe) when sandboxing is enabled', async () => {
vi.mocked(mockConfig.getSandboxEnabled).mockReturnValue(true);
vi.mocked(
proactivePermissions.getProactiveToolSuggestions,
).mockResolvedValue({
network: true,
});
vi.mocked(proactivePermissions.isNetworkReliantCommand).mockReturnValue(
true,
);
const invocation = shellTool.build({ command: 'NPM.EXE install' });
const abortSignal = new AbortController().signal;
await invocation.shouldConfirmExecute(abortSignal);
expect(
proactivePermissions.getProactiveToolSuggestions,
).toHaveBeenCalledWith('npm');
});
it('should NOT request expansion if paths are already approved (case-insensitive subpath)', async () => {
// This test assumes Darwin or Windows for case-insensitivity
vi.mocked(mockConfig.getSandboxEnabled).mockReturnValue(true);
vi.mocked(
proactivePermissions.getProactiveToolSuggestions,
).mockResolvedValue({
fileSystem: { read: ['/project/src'], write: [] },
});
vi.mocked(proactivePermissions.isNetworkReliantCommand).mockReturnValue(
true,
);
// Current approval is for the parent dir, with different casing
vi.mocked(
mockConfig.sandboxPolicyManager.getCommandPermissions,
).mockReturnValue({
fileSystem: { read: ['/PROJECT'], write: [] },
network: false,
});
const invocation = shellTool.build({ command: 'npm install' });
const result = await invocation.shouldConfirmExecute(
new AbortController().signal,
);
// If it's correctly approved, result should be false (no expansion needed)
// or a normal 'exec' confirmation, but NOT 'sandbox_expansion'.
if (result) {
expect(result.type).not.toBe('sandbox_expansion');
} else {
expect(result).toBe(false);
}
});
});
+77 -2
View File
@@ -15,6 +15,7 @@ import {
shortenPath,
normalizePath,
resolveToRealPath,
makeRelative,
} from './paths.js';
vi.mock('node:fs', async (importOriginal) => {
@@ -215,7 +216,7 @@ describe('isSubpath', () => {
});
});
describe('isSubpath on Windows', () => {
describe.skipIf(process.platform !== 'win32')('isSubpath on Windows', () => {
afterEach(() => vi.unstubAllGlobals());
beforeEach(() => mockPlatform('win32'));
@@ -268,6 +269,20 @@ describe('isSubpath on Windows', () => {
});
});
describe.skipIf(process.platform !== 'darwin')('isSubpath on Darwin', () => {
afterEach(() => vi.unstubAllGlobals());
beforeEach(() => mockPlatform('darwin'));
it('should be case-insensitive for path components on Darwin', () => {
expect(isSubpath('/PROJECT', '/project/src')).toBe(true);
});
it('should return true for a direct subpath on Darwin', () => {
expect(isSubpath('/Users/Test', '/Users/Test/file.txt')).toBe(true);
});
});
describe('shortenPath', () => {
describe.skipIf(process.platform === 'win32')('on POSIX', () => {
it('should not shorten a path that is shorter than maxLen', () => {
@@ -586,6 +601,54 @@ describe('resolveToRealPath', () => {
});
});
describe('makeRelative', () => {
describe.skipIf(process.platform === 'win32')('on POSIX', () => {
it('should return relative path if targetPath is already relative', () => {
expect(makeRelative('foo/bar', '/root')).toBe('foo/bar');
});
it('should return relative path from root to target', () => {
const root = '/Users/test/project';
const target = '/Users/test/project/src/file.ts';
expect(makeRelative(target, root)).toBe('src/file.ts');
});
it('should return "." if target and root are the same', () => {
const root = '/Users/test/project';
expect(makeRelative(root, root)).toBe('.');
});
it('should handle parent directories with ..', () => {
const root = '/Users/test/project/src';
const target = '/Users/test/project/docs/readme.md';
expect(makeRelative(target, root)).toBe('../docs/readme.md');
});
});
describe.skipIf(process.platform !== 'win32')('on Windows', () => {
it('should return relative path if targetPath is already relative', () => {
expect(makeRelative('foo/bar', 'C:\\root')).toBe('foo/bar');
});
it('should return relative path from root to target', () => {
const root = 'C:\\Users\\test\\project';
const target = 'C:\\Users\\test\\project\\src\\file.ts';
expect(makeRelative(target, root)).toBe('src\\file.ts');
});
it('should return "." if target and root are the same', () => {
const root = 'C:\\Users\\test\\project';
expect(makeRelative(root, root)).toBe('.');
});
it('should handle parent directories with ..', () => {
const root = 'C:\\Users\\test\\project\\src';
const target = 'C:\\Users\\test\\project\\docs\\readme.md';
expect(makeRelative(target, root)).toBe('..\\docs\\readme.md');
});
});
});
describe('normalizePath', () => {
it('should resolve a relative path to an absolute path', () => {
const result = normalizePath('some/relative/path');
@@ -615,7 +678,19 @@ describe('normalizePath', () => {
});
});
describe.skipIf(process.platform === 'win32')('on POSIX', () => {
describe.skipIf(process.platform !== 'darwin')('on Darwin', () => {
beforeEach(() => mockPlatform('darwin'));
afterEach(() => vi.unstubAllGlobals());
it('should lowercase the entire path', () => {
const result = normalizePath('/Users/TEST');
expect(result).toBe('/users/test');
});
});
describe.skipIf(
process.platform === 'win32' || process.platform === 'darwin',
)('on Linux', () => {
it('should preserve case', () => {
const result = normalizePath('/usr/Local/Bin');
expect(result).toContain('Local');
+24 -5
View File
@@ -325,9 +325,14 @@ export function getProjectHash(projectRoot: string): string {
* - On Windows, converts to lowercase for case-insensitivity.
*/
export function normalizePath(p: string): string {
const resolved = path.resolve(p);
const platform = process.platform;
const isWindows = platform === 'win32';
const pathModule = isWindows ? path.win32 : path;
const resolved = pathModule.resolve(p);
const normalized = resolved.replace(/\\/g, '/');
return process.platform === 'win32' ? normalized.toLowerCase() : normalized;
const isCaseInsensitive = isWindows || platform === 'darwin';
return isCaseInsensitive ? normalized.toLowerCase() : normalized;
}
/**
@@ -337,11 +342,25 @@ export function normalizePath(p: string): string {
* @returns True if childPath is a subpath of parentPath, false otherwise.
*/
export function isSubpath(parentPath: string, childPath: string): boolean {
const isWindows = process.platform === 'win32';
const platform = process.platform;
const isWindows = platform === 'win32';
const isDarwin = platform === 'darwin';
const pathModule = isWindows ? path.win32 : path;
// On Windows, path.relative is case-insensitive. On POSIX, it's case-sensitive.
const relative = pathModule.relative(parentPath, childPath);
// Resolve both paths to absolute to ensure consistent comparison,
// especially when mixing relative and absolute paths or when casing differs.
let p = pathModule.resolve(parentPath);
let c = pathModule.resolve(childPath);
// On Windows, path.relative is case-insensitive.
// On POSIX (including Darwin), path.relative is case-sensitive.
// We want it to be case-insensitive on Darwin to match user expectation and sandbox policy.
if (isDarwin) {
p = p.toLowerCase();
c = c.toLowerCase();
}
const relative = pathModule.relative(p, c);
return (
!relative.startsWith(`..${pathModule.sep}`) &&
@@ -21,6 +21,7 @@ import {
parseCommandDetails,
splitCommands,
stripShellWrapper,
normalizeCommand,
hasRedirection,
resolveExecutable,
} from './shell-utils.js';
@@ -115,6 +116,23 @@ const mockPowerShellResult = (
});
};
describe('normalizeCommand', () => {
it('should lowercase the command', () => {
expect(normalizeCommand('NPM')).toBe('npm');
});
it('should remove .exe extension', () => {
expect(normalizeCommand('node.exe')).toBe('node');
});
it('should handle absolute paths', () => {
expect(normalizeCommand('/usr/bin/npm')).toBe('npm');
expect(normalizeCommand('C:\\Program Files\\nodejs\\node.exe')).toBe(
'node',
);
});
});
describe('getCommandRoots', () => {
it('should return a single command', () => {
expect(getCommandRoots('ls -l')).toEqual(['ls']);
+14
View File
@@ -310,6 +310,20 @@ function normalizeCommandName(raw: string): string {
return raw.trim();
}
/**
* Normalizes a command name for sandbox policy lookups.
* Converts to lowercase and removes the .exe extension for cross-platform consistency.
*
* @param commandName - The command name to normalize.
* @returns The normalized command name.
*/
export function normalizeCommand(commandName: string): string {
// Split by both separators and get the last non-empty part
const parts = commandName.split(/[\\/]/).filter(Boolean);
const base = parts.length > 0 ? parts[parts.length - 1] : '';
return base.toLowerCase().replace(/\.exe$/, '');
}
function extractNameFromNode(node: Node): string | null {
switch (node.type) {
case 'command': {
@@ -30,11 +30,12 @@ describe('terminalSerializer', () => {
allowProposedApi: true,
});
const result = serializeTerminalToObject(terminal);
expect(result).toHaveLength(24);
expect(result).toHaveLength(1);
result.forEach((line) => {
// Expect each line to be either empty or contain a single token with spaces
// Actually, the first cell will have inverse: true (cursor), so it will have multiple tokens
if (line.length > 0) {
expect(line[0].text.trim()).toBe('');
expect(line[line.length - 1].text.trim()).toBe('');
}
});
});
+34 -7
View File
@@ -12,6 +12,7 @@ export interface AnsiToken {
underline: boolean;
dim: boolean;
inverse: boolean;
isUninitialized: boolean;
fg: string;
bg: string;
}
@@ -126,6 +127,12 @@ class Cell {
return this.cell?.getChars() || ' ';
}
isUninitialized(): boolean {
return this.cell
? this.cell.getCode() === 0 && this.cell.isAttributeDefault()
: true;
}
isAttribute(attribute: Attribute): boolean {
return (this.attributes & attribute) !== 0;
}
@@ -137,7 +144,8 @@ class Cell {
this.bg === other.bg &&
this.fgColorMode === other.fgColorMode &&
this.bgColorMode === other.bgColorMode &&
this.isCursor() === other.isCursor()
this.isCursor() === other.isCursor() &&
this.isUninitialized() === other.isUninitialized()
);
}
}
@@ -149,15 +157,15 @@ export function serializeTerminalToObject(
): AnsiOutput {
const buffer = terminal.buffer.active;
const cursorX = buffer.cursorX;
const cursorY = buffer.cursorY;
const absoluteCursorY = buffer.baseY + buffer.cursorY;
const defaultFg = '';
const defaultBg = '';
const result: AnsiOutput = [];
// Reuse cell instances
const lastCell = new Cell(null, -1, -1, cursorX, cursorY);
const currentCell = new Cell(null, -1, -1, cursorX, cursorY);
const lastCell = new Cell(null, -1, -1, cursorX, absoluteCursorY);
const currentCell = new Cell(null, -1, -1, cursorX, absoluteCursorY);
const effectiveStart = startLine ?? buffer.viewportY;
const effectiveEnd = endLine ?? buffer.viewportY + terminal.rows;
@@ -173,12 +181,12 @@ export function serializeTerminalToObject(
}
// Reset lastCell for new line
lastCell.update(null, -1, -1, cursorX, cursorY);
lastCell.update(null, -1, -1, cursorX, absoluteCursorY);
let currentText = '';
for (let x = 0; x < terminal.cols; x++) {
const cellData = line.getCell(x, cellBuffer);
currentCell.update(cellData || null, x, y, cursorX, cursorY);
currentCell.update(cellData || null, x, y, cursorX, absoluteCursorY);
if (x > 0 && !currentCell.equals(lastCell)) {
if (currentText) {
@@ -190,6 +198,7 @@ export function serializeTerminalToObject(
dim: lastCell.isAttribute(Attribute.dim),
inverse:
lastCell.isAttribute(Attribute.inverse) || lastCell.isCursor(),
isUninitialized: lastCell.isUninitialized(),
fg: convertColorToHex(lastCell.fg, lastCell.fgColorMode, defaultFg),
bg: convertColorToHex(lastCell.bg, lastCell.bgColorMode, defaultBg),
};
@@ -200,7 +209,7 @@ export function serializeTerminalToObject(
currentText += currentCell.getChars();
// Copy state from currentCell to lastCell. Since we can't easily deep copy
// without allocating, we just update lastCell with the same data.
lastCell.update(cellData || null, x, y, cursorX, cursorY);
lastCell.update(cellData || null, x, y, cursorX, absoluteCursorY);
}
if (currentText) {
@@ -211,6 +220,7 @@ export function serializeTerminalToObject(
underline: lastCell.isAttribute(Attribute.underline),
dim: lastCell.isAttribute(Attribute.dim),
inverse: lastCell.isAttribute(Attribute.inverse) || lastCell.isCursor(),
isUninitialized: lastCell.isUninitialized(),
fg: convertColorToHex(lastCell.fg, lastCell.fgColorMode, defaultFg),
bg: convertColorToHex(lastCell.bg, lastCell.bgColorMode, defaultBg),
};
@@ -220,6 +230,23 @@ export function serializeTerminalToObject(
result.push(currentLine);
}
// Remove trailing empty lines
while (result.length > 0) {
const lastLine = result[result.length - 1];
const lineY = effectiveStart + result.length - 1;
// A line is empty if all its tokens are marked as uninitialized and it has no cursor
const isEmpty =
lastLine.every((token) => token.isUninitialized && !token.inverse) &&
lineY !== absoluteCursorY;
if (isEmpty) {
result.pop();
} else {
break;
}
}
return result;
}