diff --git a/.github/scripts/pr-triage.sh b/.github/scripts/pr-triage.sh index e6521376ce..92200ee4d2 100755 --- a/.github/scripts/pr-triage.sh +++ b/.github/scripts/pr-triage.sh @@ -22,7 +22,7 @@ get_issue_labels() { # Check cache case "${ISSUE_LABELS_CACHE_FLAT}" in *"|${ISSUE_NUM}:"*) - local suffix="${ISSUE_LABELS_CACHE_FLAT#*|${ISSUE_NUM}:}" + local suffix="${ISSUE_LABELS_CACHE_FLAT#*|"${ISSUE_NUM}":}" echo "${suffix%%|*}" return ;; diff --git a/.github/workflows/chained_e2e.yml b/.github/workflows/chained_e2e.yml index 487225d452..4b37d0e109 100644 --- a/.github/workflows/chained_e2e.yml +++ b/.github/workflows/chained_e2e.yml @@ -224,8 +224,6 @@ jobs: if: | always() && (needs.merge_queue_skipper.result !='success' || needs.merge_queue_skipper.outputs.skip != 'true') runs-on: 'gemini-cli-windows-16-core' - continue-on-error: true - steps: - name: 'Checkout' uses: 'actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955' # ratchet:actions/checkout@v5 @@ -315,6 +313,7 @@ jobs: needs: - 'e2e_linux' - 'e2e_mac' + - 'e2e_windows' - 'evals' - 'merge_queue_skipper' runs-on: 'gemini-cli-ubuntu-16-core' @@ -323,6 +322,7 @@ jobs: run: | if [[ ${{ needs.e2e_linux.result }} != 'success' || \ ${{ needs.e2e_mac.result }} != 'success' || \ + ${{ needs.e2e_windows.result }} != 'success' || \ ${{ needs.evals.result }} != 'success' ]]; then echo "One or more E2E jobs failed." exit 1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0f9714df99..dd7288cde5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -360,7 +360,6 @@ jobs: runs-on: 'gemini-cli-windows-16-core' needs: 'merge_queue_skipper' if: "${{needs.merge_queue_skipper.outputs.skip == 'false'}}" - continue-on-error: true timeout-minutes: 60 strategy: matrix: @@ -458,6 +457,7 @@ jobs: - 'link_checker' - 'test_linux' - 'test_mac' + - 'test_windows' - 'codeql' - 'bundle_size' runs-on: 'gemini-cli-ubuntu-16-core' @@ -468,6 +468,7 @@ jobs: (${{ needs.link_checker.result }} != 'success' && ${{ needs.link_checker.result }} != 'skipped') || \ (${{ needs.test_linux.result }} != 'success' && ${{ needs.test_linux.result }} != 'skipped') || \ (${{ needs.test_mac.result }} != 'success' && ${{ needs.test_mac.result }} != 'skipped') || \ + (${{ needs.test_windows.result }} != 'success' && ${{ needs.test_windows.result }} != 'skipped') || \ (${{ needs.codeql.result }} != 'success' && ${{ needs.codeql.result }} != 'skipped') || \ (${{ needs.bundle_size.result }} != 'success' && ${{ needs.bundle_size.result }} != 'skipped') ]]; then echo "One or more CI jobs failed." diff --git a/.github/workflows/evals-nightly.yml b/.github/workflows/evals-nightly.yml index b7a375d836..6f6767ebfe 100644 --- a/.github/workflows/evals-nightly.yml +++ b/.github/workflows/evals-nightly.yml @@ -27,6 +27,7 @@ jobs: fail-fast: false matrix: model: + - 'gemini-3.1-pro-preview-customtools' - 'gemini-3-pro-preview' - 'gemini-3-flash-preview' - 'gemini-2.5-pro' diff --git a/.github/workflows/pr-rate-limiter.yaml b/.github/workflows/pr-rate-limiter.yaml new file mode 100644 index 0000000000..c703279532 --- /dev/null +++ b/.github/workflows/pr-rate-limiter.yaml @@ -0,0 +1,29 @@ +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json + +name: 'PR rate limiter' + +permissions: {} + +on: + pull_request_target: + types: + - 'opened' + - 'reopened' + +jobs: + limit: + runs-on: 'gemini-cli-ubuntu-16-core' + permissions: + contents: 'read' + pull-requests: 'write' + steps: + - name: 'Limit open pull requests per user' + uses: 'Homebrew/actions/limit-pull-requests@9ceb7934560eb61d131dde205a6c2d77b2e1529d' # master + with: + except-author-associations: 'MEMBER,OWNER,COLLABORATOR' + comment-limit: 8 + comment: > + You already have 7 pull requests open. Please work on getting + existing PRs merged before opening more. + close-limit: 8 + close: true diff --git a/docs/cli/settings.md b/docs/cli/settings.md index 111728ea59..0b20ce31f2 100644 --- a/docs/cli/settings.md +++ b/docs/cli/settings.md @@ -29,6 +29,7 @@ they appear in the UI. | Enable Auto Update | `general.enableAutoUpdate` | Enable automatic updates. | `true` | | Enable Notifications | `general.enableNotifications` | Enable run-event notifications for action-required prompts and session completion. Currently macOS only. | `false` | | Plan Directory | `general.plan.directory` | The directory where planning artifacts are stored. If not specified, defaults to the system temporary directory. | `undefined` | +| Max Chat Model Attempts | `general.maxAttempts` | Maximum number of attempts for requests to the main chat model. Cannot exceed 10. | `10` | | Debug Keystroke Logging | `general.debugKeystrokeLogging` | Enable debug logging of keystrokes to the console. | `false` | | Enable Session Cleanup | `general.sessionRetention.enabled` | Enable automatic session cleanup | `false` | | Keep chat history | `general.sessionRetention.maxAge` | Automatically delete chats older than this time period (e.g., "30d", "7d", "24h", "1w") | `undefined` | @@ -111,14 +112,15 @@ they appear in the UI. ### Security -| UI Label | Setting | Description | Default | -| ------------------------------------- | ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -| Disable YOLO Mode | `security.disableYoloMode` | Disable YOLO mode, even if enabled by a flag. | `false` | -| Allow Permanent Tool Approval | `security.enablePermanentToolApproval` | Enable the "Allow for all future sessions" option in tool confirmation dialogs. | `false` | -| Blocks extensions from Git | `security.blockGitExtensions` | Blocks installing and loading extensions from Git. | `false` | -| Extension Source Regex Allowlist | `security.allowedExtensions` | List of Regex patterns for allowed extensions. If nonempty, only extensions that match the patterns in this list are allowed. Overrides the blockGitExtensions setting. | `[]` | -| Folder Trust | `security.folderTrust.enabled` | Setting to track whether Folder trust is enabled. | `true` | -| Enable Environment Variable Redaction | `security.environmentVariableRedaction.enabled` | Enable redaction of environment variables that may contain secrets. | `false` | +| UI Label | Setting | Description | Default | +| ------------------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------- | +| Disable YOLO Mode | `security.disableYoloMode` | Disable YOLO mode, even if enabled by a flag. | `false` | +| Allow Permanent Tool Approval | `security.enablePermanentToolApproval` | Enable the "Allow for all future sessions" option in tool confirmation dialogs. | `false` | +| Blocks extensions from Git | `security.blockGitExtensions` | Blocks installing and loading extensions from Git. | `false` | +| Extension Source Regex Allowlist | `security.allowedExtensions` | List of Regex patterns for allowed extensions. If nonempty, only extensions that match the patterns in this list are allowed. Overrides the blockGitExtensions setting. | `[]` | +| Folder Trust | `security.folderTrust.enabled` | Setting to track whether Folder trust is enabled. | `true` | +| Enable Environment Variable Redaction | `security.environmentVariableRedaction.enabled` | Enable redaction of environment variables that may contain secrets. | `false` | +| Enable Context-Aware Security | `security.enableConseca` | Enable the context-aware security checker. This feature uses an LLM to dynamically generate and enforce security policies for tool use based on your prompt, providing an additional layer of protection against unintended actions. | `false` | ### Advanced diff --git a/docs/extensions/reference.md b/docs/extensions/reference.md index b4a0df7336..d36df94d78 100644 --- a/docs/extensions/reference.md +++ b/docs/extensions/reference.md @@ -116,7 +116,9 @@ The manifest file defines the extension's behavior and configuration. "description": "My awesome extension", "mcpServers": { "my-server": { - "command": "node my-server.js" + "command": "node", + "args": ["${extensionPath}/my-server.js"], + "cwd": "${extensionPath}" } }, "contextFileName": "GEMINI.md", @@ -124,19 +126,41 @@ The manifest file defines the extension's behavior and configuration. } ``` -- `name`: A unique identifier for the extension. Use lowercase letters, numbers, - and dashes. This name must match the extension's directory name. -- `version`: The current version of the extension. -- `description`: A short summary shown in the extension gallery. -- `mcpServers`: A map of Model Context Protocol (MCP) - servers. Extension servers follow the same format as standard - [CLI configuration](../reference/configuration.md). -- `contextFileName`: The name of the context file (defaults to `GEMINI.md`). Can - also be an array of strings to load multiple context files. -- `excludeTools`: An array of tools to block from the model. You can restrict - specific arguments, such as `run_shell_command(rm -rf)`. -- `themes`: An optional list of themes provided by the extension. See - [Themes](../cli/themes.md) for more information. +- `name`: The name of the extension. This is used to uniquely identify the + extension and for conflict resolution when extension commands have the same + name as user or project commands. The name should be lowercase or numbers and + use dashes instead of underscores or spaces. This is how users will refer to + your extension in the CLI. Note that we expect this name to match the + extension directory name. +- `version`: The version of the extension. +- `description`: A short description of the extension. This will be displayed on + [geminicli.com/extensions](https://geminicli.com/extensions). +- `mcpServers`: A map of MCP servers to settings. The key is the name of the + server, and the value is the server configuration. These servers will be + loaded on startup just like MCP servers defined in a + [`settings.json` file](../reference/configuration.md). If both an extension + and a `settings.json` file define an MCP server with the same name, the server + defined in the `settings.json` file takes precedence. + - Note that all MCP server configuration options are supported except for + `trust`. + - For portability, you should use `${extensionPath}` to refer to files within + your extension directory. + - Separate your executable and its arguments using `command` and `args` + instead of putting them both in `command`. +- `contextFileName`: The name of the file that contains the context for the + extension. This will be used to load the context from the extension directory. + If this property is not used but a `GEMINI.md` file is present in your + extension directory, then that file will be loaded. +- `excludeTools`: An array of tool names to exclude from the model. You can also + specify command-specific restrictions for tools that support it, like the + `run_shell_command` tool. For example, + `"excludeTools": ["run_shell_command(rm -rf)"]` will block the `rm -rf` + command. Note that this differs from the MCP server `excludeTools` + functionality, which can be listed in the MCP server config. + +When Gemini CLI starts, it loads all the extensions and merges their +configurations. If there are any conflicts, the workspace configuration takes +precedence. ### Extension settings diff --git a/docs/hooks/reference.md b/docs/hooks/reference.md index 452edb378d..9b7226ac05 100644 --- a/docs/hooks/reference.md +++ b/docs/hooks/reference.md @@ -98,6 +98,8 @@ and parameter rewriting. - `tool_name`: (`string`) The name of the tool being called. - `tool_input`: (`object`) The raw arguments generated by the model. - `mcp_context`: (`object`) Optional metadata for MCP-based tools. + - `original_request_name`: (`string`) The original name of the tool being + called, if this is a tail tool call. - **Relevant Output Fields**: - `decision`: Set to `"deny"` (or `"block"`) to prevent the tool from executing. @@ -120,12 +122,18 @@ hiding sensitive output from the agent. - `tool_response`: (`object`) The result containing `llmContent`, `returnDisplay`, and optional `error`. - `mcp_context`: (`object`) + - `original_request_name`: (`string`) The original name of the tool being + called, if this is a tail tool call. - **Relevant Output Fields**: - `decision`: Set to `"deny"` to hide the real tool output from the agent. - `reason`: Required if denied. This text **replaces** the tool result sent back to the model. - `hookSpecificOutput.additionalContext`: Text that is **appended** to the tool result for the agent. + - `hookSpecificOutput.tailToolCallRequest`: (`{ name: string, args: object }`) + A request to execute another tool immediately after this one. The result of + this "tail call" will replace the original tool's response. Ideal for + programmatic tool routing. - `continue`: Set to `false` to **kill the entire agent loop** immediately. - **Exit Code 2 (Block Result)**: Hides the tool result. Uses `stderr` as the replacement content sent to the agent. **The turn continues.** diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index b069b03fc2..ba22eb802f 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -142,6 +142,11 @@ their corresponding top-level category object in your `settings.json` file. request" errors. - **Default:** `false` +- **`general.maxAttempts`** (number): + - **Description:** Maximum number of attempts for requests to the main chat + model. Cannot exceed 10. + - **Default:** `10` + - **`general.debugKeystrokeLogging`** (boolean): - **Description:** Enable debug logging of keystrokes to the console. - **Default:** `false` @@ -868,6 +873,14 @@ their corresponding top-level category object in your `settings.json` file. - **Default:** `undefined` - **Requires restart:** Yes +- **`security.enableConseca`** (boolean): + - **Description:** Enable the context-aware security checker. This feature + uses an LLM to dynamically generate and enforce security policies for tool + use based on your prompt, providing an additional layer of protection + against unintended actions. + - **Default:** `false` + - **Requires restart:** Yes + #### `advanced` - **`advanced.autoConfigureMemory`** (boolean): diff --git a/docs/tools/mcp-server.md b/docs/tools/mcp-server.md index 09726432fd..22ce748918 100644 --- a/docs/tools/mcp-server.md +++ b/docs/tools/mcp-server.md @@ -163,7 +163,8 @@ Each server configuration supports the following properties: - **`args`** (string[]): Command-line arguments for Stdio transport - **`headers`** (object): Custom HTTP headers when using `url` or `httpUrl` - **`env`** (object): Environment variables for the server process. Values can - reference environment variables using `$VAR_NAME` or `${VAR_NAME}` syntax + reference environment variables using `$VAR_NAME` or `${VAR_NAME}` syntax (all + platforms), or `%VAR_NAME%` (Windows only). - **`cwd`** (string): Working directory for Stdio transport - **`timeout`** (number): Request timeout in milliseconds (default: 600,000ms = 10 minutes) @@ -184,6 +185,63 @@ Each server configuration supports the following properties: Service Account to impersonate. Used with `authProviderType: 'service_account_impersonation'`. +### Environment variable expansion + +Gemini CLI automatically expands environment variables in the `env` block of +your MCP server configuration. This allows you to securely reference variables +defined in your shell or environment without hardcoding sensitive information +directly in your `settings.json` file. + +The expansion utility supports: + +- **POSIX/Bash syntax:** `$VARIABLE_NAME` or `${VARIABLE_NAME}` (supported on + all platforms) +- **Windows syntax:** `%VARIABLE_NAME%` (supported only when running on Windows) + +If a variable is not defined in the current environment, it resolves to an empty +string. + +**Example:** + +```json +"env": { + "API_KEY": "$MY_EXTERNAL_TOKEN", + "LOG_LEVEL": "$LOG_LEVEL", + "TEMP_DIR": "%TEMP%" +} +``` + +### Security and environment sanitization + +To protect your credentials, Gemini CLI performs environment sanitization when +spawning MCP server processes. + +#### Automatic redaction + +By default, the CLI redacts sensitive environment variables from the base +environment (inherited from the host process) to prevent unintended exposure to +third-party MCP servers. This includes: + +- Core project keys: `GEMINI_API_KEY`, `GOOGLE_API_KEY`, etc. +- Variables matching sensitive patterns: `*TOKEN*`, `*SECRET*`, `*PASSWORD*`, + `*KEY*`, `*AUTH*`, `*CREDENTIAL*`. +- Certificates and private key patterns. + +#### Explicit overrides + +If an environment variable must be passed to an MCP server, you must explicitly +state it in the `env` property of the server configuration in `settings.json`. +Explicitly defined variables (including those from extensions) are trusted and +are **not** subjected to the automatic redaction process. + +This follows the security principle that if a variable is explicitly configured +by the user for a specific server, it constitutes informed consent to share that +specific data with that server. + +> **Note:** Even when explicitly defined, you should avoid hardcoding secrets. +> Instead, use environment variable expansion (e.g., `"MY_KEY": "$MY_KEY"`) to +> securely pull the value from your host environment at runtime. + ### OAuth support for remote MCP servers The Gemini CLI supports OAuth 2.0 authentication for remote MCP servers using @@ -738,7 +796,9 @@ The MCP integration tracks several states: - **Trust settings:** The `trust` option bypasses all confirmation dialogs. Use cautiously and only for servers you completely control - **Access tokens:** Be security-aware when configuring environment variables - containing API keys or tokens + containing API keys or tokens. See + [Security and environment sanitization](#security-and-environment-sanitization) + for details on how Gemini CLI protects your credentials. - **Sandbox compatibility:** When using sandboxing, ensure MCP servers are available within the sandbox environment - **Private data:** Using broadly scoped personal access tokens can lead to diff --git a/evals/frugalReads.eval.ts b/evals/frugalReads.eval.ts index 55a73f85e2..47578039a6 100644 --- a/evals/frugalReads.eval.ts +++ b/evals/frugalReads.eval.ts @@ -78,22 +78,23 @@ describe('Frugal reads eval', () => { ).toBe(true); let totalLinesRead = 0; - const readRanges: { offset: number; limit: number }[] = []; + const readRanges: { start_line: number; end_line: number }[] = []; for (const call of targetFileReads) { const args = JSON.parse(call.toolRequest.args); expect( - args.limit, - 'Agent read the entire file (missing limit) instead of using ranged read', + args.end_line, + 'Agent read the entire file (missing end_line) instead of using ranged read', ).toBeDefined(); - const limit = args.limit; - const offset = args.offset ?? 0; - totalLinesRead += limit; - readRanges.push({ offset, limit }); + const end_line = args.end_line; + const start_line = args.start_line ?? 1; + const linesRead = end_line - start_line + 1; + totalLinesRead += linesRead; + readRanges.push({ start_line, end_line }); - expect(args.limit, 'Agent read too many lines at once').toBeLessThan( + expect(linesRead, 'Agent read too many lines at once').toBeLessThan( 1001, ); } @@ -108,7 +109,7 @@ describe('Frugal reads eval', () => { const errorLines = [500, 510, 520]; for (const line of errorLines) { const covered = readRanges.some( - (range) => line >= range.offset && line < range.offset + range.limit, + (range) => line >= range.start_line && line <= range.end_line, ); expect(covered, `Agent should have read around line ${line}`).toBe( true, @@ -191,8 +192,8 @@ describe('Frugal reads eval', () => { for (const call of targetFileReads) { const args = JSON.parse(call.toolRequest.args); expect( - args.limit, - 'Agent should have used ranged read (limit) to save tokens', + args.end_line, + 'Agent should have used ranged read (end_line) to save tokens', ).toBeDefined(); } }, @@ -253,7 +254,7 @@ describe('Frugal reads eval', () => { // and just read the whole file to be efficient with tool calls. const readEntireFile = targetFileReads.some((call) => { const args = JSON.parse(call.toolRequest.args); - return args.limit === undefined; + return args.end_line === undefined; }); expect( diff --git a/evals/frugalSearch.eval.ts b/evals/frugalSearch.eval.ts index 8805a6a8ed..1c49fc2ed4 100644 --- a/evals/frugalSearch.eval.ts +++ b/evals/frugalSearch.eval.ts @@ -68,7 +68,7 @@ describe('Frugal Search', () => { const args = getParams(call); return ( args.file_path === 'src/legacy_processor.ts' && - (args.limit === undefined || args.limit === null) + (args.end_line === undefined || args.end_line === null) ); }); @@ -87,7 +87,7 @@ describe('Frugal Search', () => { if ( call.toolRequest.name === 'read_file' && args.file_path === 'src/legacy_processor.ts' && - args.limit !== undefined + args.end_line !== undefined ) { return true; } diff --git a/evals/interactive-hang.eval.ts b/evals/interactive-hang.eval.ts index 43b49759bb..0cf56acf98 100644 --- a/evals/interactive-hang.eval.ts +++ b/evals/interactive-hang.eval.ts @@ -56,7 +56,7 @@ describe('interactive_commands', () => { const scaffoldCall = logs.find( (l) => l.toolRequest.name === 'run_shell_command' && - /npm (init|create)|npx create-|yarn create|pnpm create/.test( + /npm (init|create)|npx (.*)?create-|yarn create|pnpm create/.test( l.toolRequest.args, ), ); diff --git a/integration-tests/hooks-system.tail-tool-call.responses b/integration-tests/hooks-system.tail-tool-call.responses new file mode 100644 index 0000000000..13dc3fde4d --- /dev/null +++ b/integration-tests/hooks-system.tail-tool-call.responses @@ -0,0 +1,2 @@ +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"read_file","args":{"file_path":"original.txt"}}}],"role":"model"},"finishReason":"STOP","index":0}]}]} +{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Tail call completed successfully."}],"role":"model"},"finishReason":"STOP","index":0}]}]} \ No newline at end of file diff --git a/integration-tests/hooks-system.test.ts b/integration-tests/hooks-system.test.ts index 2db1019c5f..479851957b 100644 --- a/integration-tests/hooks-system.test.ts +++ b/integration-tests/hooks-system.test.ts @@ -286,6 +286,113 @@ describe('Hooks System Integration', () => { }); }); + describe('Command Hooks - Tail Tool Calls', () => { + it('should execute a tail tool call from AfterTool hooks and replace original response', async () => { + // Create a script that acts as the hook. + // It will trigger on "read_file" and issue a tail call to "write_file". + rig.setup('should execute a tail tool call from AfterTool hooks', { + fakeResponsesPath: join( + import.meta.dirname, + 'hooks-system.tail-tool-call.responses', + ), + }); + + const hookOutput = { + decision: 'allow', + hookSpecificOutput: { + hookEventName: 'AfterTool', + tailToolCallRequest: { + name: 'write_file', + args: { + file_path: 'tail-called-file.txt', + content: 'Content from tail call', + }, + }, + }, + }; + + const hookScript = `console.log(JSON.stringify(${JSON.stringify( + hookOutput, + )})); process.exit(0);`; + + const scriptPath = join(rig.testDir!, 'tail_call_hook.js'); + writeFileSync(scriptPath, hookScript); + const commandPath = scriptPath.replace(/\\/g, '/'); + + rig.setup('should execute a tail tool call from AfterTool hooks', { + fakeResponsesPath: join( + import.meta.dirname, + 'hooks-system.tail-tool-call.responses', + ), + settings: { + hooksConfig: { + enabled: true, + }, + hooks: { + AfterTool: [ + { + matcher: 'read_file', + hooks: [ + { + type: 'command', + command: `node "${commandPath}"`, + timeout: 5000, + }, + ], + }, + ], + }, + }, + }); + + // Create a test file to trigger the read_file tool + rig.createFile('original.txt', 'Original content'); + + const cliOutput = await rig.run({ + args: 'Read original.txt', // Fake responses should trigger read_file on this + }); + + // 1. Verify that write_file was called (as a tail call replacing read_file) + // Since read_file was replaced before finalizing, it will not appear in the tool logs. + const foundWriteFile = await rig.waitForToolCall('write_file'); + expect(foundWriteFile).toBeTruthy(); + + // Ensure hook logs are flushed and the final LLM response is received. + // The mock LLM is configured to respond with "Tail call completed successfully." + expect(cliOutput).toContain('Tail call completed successfully.'); + + // Ensure telemetry is written to disk + await rig.waitForTelemetryReady(); + + // Read hook logs to debug + const hookLogs = rig.readHookLogs(); + const relevantHookLog = hookLogs.find( + (l) => l.hookCall.hook_event_name === 'AfterTool', + ); + + expect(relevantHookLog).toBeDefined(); + + // 2. Verify write_file was executed. + // In non-interactive mode, the CLI deduplicates tool execution logs by callId. + // Since a tail call reuses the original callId, "Tool: write_file" is not printed. + // Instead, we verify the side-effect (file creation) and the telemetry log. + + // 3. Verify the tail-called tool actually wrote the file + const modifiedContent = rig.readFile('tail-called-file.txt'); + expect(modifiedContent).toBe('Content from tail call'); + + // 4. Verify telemetry for the final tool call. + // The original 'read_file' call is replaced, so only 'write_file' is finalized and logged. + const toolLogs = rig.readToolLogs(); + const successfulTools = toolLogs.filter((t) => t.toolRequest.success); + expect( + successfulTools.some((t) => t.toolRequest.name === 'write_file'), + ).toBeTruthy(); + // The original request name should be preserved in the log payload if possible, + // but the executed tool name is 'write_file'. + }); + }); + describe('BeforeModel Hooks - LLM Request Modification', () => { it('should modify LLM requests with BeforeModel hooks', async () => { // Create a hook script that replaces the LLM request with a modified version diff --git a/package-lock.json b/package-lock.json index 0bfce7daa0..f58bb26483 100644 --- a/package-lock.json +++ b/package-lock.json @@ -997,9 +997,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", - "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1029,9 +1029,9 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, "license": "MIT", "engines": { @@ -1039,13 +1039,13 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.1.tgz", - "integrity": "sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==", + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.6", + "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" }, @@ -1054,19 +1054,22 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.3.tgz", - "integrity": "sha512-u180qk2Um1le4yf0ruXH3PYFeEZeYC3p/4wCTKrr2U1CmGdzGi3KtY0nuPDH48UJxlKCC5RDzbcbh4X0XlqgHg==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/core": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz", - "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1077,20 +1080,20 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.4.tgz", + "integrity": "sha512-4h4MVF8pmBsncB60r0wSJiIeUKTSD4m7FmTFThG8RHlsg9ajqckLm9OraguFGZE4vVdpiI1Q4+hFnisopmG6gQ==", "dev": true, "license": "MIT", "dependencies": { - "ajv": "^6.12.4", + "ajv": "^6.14.0", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.3", "strip-json-comments": "^3.1.1" }, "engines": { @@ -1114,9 +1117,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.29.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.29.0.tgz", - "integrity": "sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==", + "version": "9.39.3", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.3.tgz", + "integrity": "sha512-1B1VkCq6FuUNlQvlBYb+1jDu/gV297TIs/OeiaSR9l1H27SVW55ONE1e1Vp16NqP683+xEGzxYtv4XCiDPaQiw==", "dev": true, "license": "MIT", "engines": { @@ -1127,9 +1130,9 @@ } }, "node_modules/@eslint/object-schema": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", - "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1137,32 +1140,19 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", - "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.15.2", + "@eslint/core": "^0.17.0", "levn": "^0.4.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", - "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, "node_modules/@google-cloud/common": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-5.0.2.tgz", @@ -1343,9 +1333,9 @@ } }, "node_modules/@google-cloud/storage": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.17.0.tgz", - "integrity": "sha512-5m9GoZqKh52a1UqkxDBu/+WVFDALNtHg5up5gNmNbXQWBcV813tzJKsyDtKjOPrlR1em1TxtD7NSPCrObH7koQ==", + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.19.0.tgz", + "integrity": "sha512-n2FjE7NAOYyshogdc7KQOl/VZb4sneqPjWouSyia9CMDdMhRX5+RIbqalNmC7LOLzuLAN89VlF2HvG8na9G+zQ==", "license": "Apache-2.0", "dependencies": { "@google-cloud/paginator": "^5.0.0", @@ -1354,7 +1344,7 @@ "abort-controller": "^3.0.0", "async-retry": "^1.3.3", "duplexify": "^4.1.3", - "fast-xml-parser": "^4.4.1", + "fast-xml-parser": "^5.3.4", "gaxios": "^6.0.2", "google-auth-library": "^9.6.3", "html-entities": "^2.5.2", @@ -1761,27 +1751,6 @@ } } }, - "node_modules/@isaacs/balanced-match": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", - "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", - "license": "MIT", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@isaacs/brace-expansion": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz", - "integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==", - "license": "MIT", - "dependencies": { - "@isaacs/balanced-match": "^4.0.1" - }, - "engines": { - "node": "20 || >=22" - } - }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -2165,9 +2134,9 @@ } }, "node_modules/@modelcontextprotocol/sdk/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -3445,9 +3414,9 @@ } }, "node_modules/@secretlint/config-loader/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", "dependencies": { @@ -4353,21 +4322,20 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.35.0.tgz", - "integrity": "sha512-ijItUYaiWuce0N1SoSMrEd0b6b6lYkYt99pqCPfybd+HKVXtEvYhICfLdwp42MhiI5mp0oq7PKEL+g1cNiz/Eg==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.1.tgz", + "integrity": "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.35.0", - "@typescript-eslint/type-utils": "8.35.0", - "@typescript-eslint/utils": "8.35.0", - "@typescript-eslint/visitor-keys": "8.35.0", - "graphemer": "^1.4.0", - "ignore": "^7.0.0", + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/type-utils": "8.56.1", + "@typescript-eslint/utils": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "ignore": "^7.0.5", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.1.0" + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4377,9 +4345,9 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.35.0", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "@typescript-eslint/parser": "^8.56.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { @@ -4393,17 +4361,17 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.35.0.tgz", - "integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.1.tgz", + "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.35.0", - "@typescript-eslint/types": "8.35.0", - "@typescript-eslint/typescript-estree": "8.35.0", - "@typescript-eslint/visitor-keys": "8.35.0", - "debug": "^4.3.4" + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "debug": "^4.4.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4413,20 +4381,20 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.35.0.tgz", - "integrity": "sha512-41xatqRwWZuhUMF/aZm2fcUsOFKNcG28xqRSS6ZVr9BVJtGExosLAm5A1OxTjRMagx8nJqva+P5zNIGt8RIgbQ==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.1.tgz", + "integrity": "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.35.0", - "@typescript-eslint/types": "^8.35.0", - "debug": "^4.3.4" + "@typescript-eslint/tsconfig-utils": "^8.56.1", + "@typescript-eslint/types": "^8.56.1", + "debug": "^4.4.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4436,18 +4404,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.35.0.tgz", - "integrity": "sha512-+AgL5+mcoLxl1vGjwNfiWq5fLDZM1TmTPYs2UkyHfFhgERxBbqHlNjRzhThJqz+ktBqTChRYY6zwbMwy0591AA==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.1.tgz", + "integrity": "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.35.0", - "@typescript-eslint/visitor-keys": "8.35.0" + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4458,9 +4426,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.35.0.tgz", - "integrity": "sha512-04k/7247kZzFraweuEirmvUj+W3bJLI9fX6fbo1Qm2YykuBvEhRTPl8tcxlYO8kZZW+HIXfkZNoasVb8EV4jpA==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.1.tgz", + "integrity": "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==", "dev": true, "license": "MIT", "engines": { @@ -4471,20 +4439,21 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.35.0.tgz", - "integrity": "sha512-ceNNttjfmSEoM9PW87bWLDEIaLAyR+E6BoYJQ5PfaDau37UGca9Nyq3lBk8Bw2ad0AKvYabz6wxc7DMTO2jnNA==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.1.tgz", + "integrity": "sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.35.0", - "@typescript-eslint/utils": "8.35.0", - "debug": "^4.3.4", - "ts-api-utils": "^2.1.0" + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/utils": "8.56.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4494,14 +4463,14 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.35.0.tgz", - "integrity": "sha512-0mYH3emanku0vHw2aRLNGqe7EXh9WHEhi7kZzscrMDf6IIRUQ5Jk4wp1QrledE/36KtdZrVfKnE32eZCf/vaVQ==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz", + "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==", "dev": true, "license": "MIT", "engines": { @@ -4513,22 +4482,21 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.35.0.tgz", - "integrity": "sha512-F+BhnaBemgu1Qf8oHrxyw14wq6vbL8xwWKKMwTMwYIRmFFY/1n/9T/jpbobZL8vp7QyEUcC6xGrnAO4ua8Kp7w==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.1.tgz", + "integrity": "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.35.0", - "@typescript-eslint/tsconfig-utils": "8.35.0", - "@typescript-eslint/types": "8.35.0", - "@typescript-eslint/visitor-keys": "8.35.0", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^2.1.0" + "@typescript-eslint/project-service": "8.56.1", + "@typescript-eslint/tsconfig-utils": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4538,46 +4506,59 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", + "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.2" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@typescript-eslint/utils": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.35.0.tgz", - "integrity": "sha512-nqoMu7WWM7ki5tPgLVsmPM8CkqtoPUG6xXGeefM5t4x3XumOEKMoUZPdi+7F+/EotukN4R9OWdmDxN80fqoZeg==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.1.tgz", + "integrity": "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.35.0", - "@typescript-eslint/types": "8.35.0", - "@typescript-eslint/typescript-estree": "8.35.0" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4587,19 +4568,19 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.35.0.tgz", - "integrity": "sha512-zTh2+1Y8ZpmeQaQVIc/ZZxsx8UzgKJyNg1PTvjzC7WMhPSVS8bfDX34k1SrwOf016qd5RU3az2UxUNue3IfQ5g==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.1.tgz", + "integrity": "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.35.0", - "eslint-visitor-keys": "^4.2.1" + "@typescript-eslint/types": "8.56.1", + "eslint-visitor-keys": "^5.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4609,6 +4590,19 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@typespec/ts-http-runtime": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.0.tgz", @@ -4685,174 +4679,6 @@ } } }, - "node_modules/@vitest/eslint-plugin/node_modules/@typescript-eslint/project-service": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.47.0.tgz", - "integrity": "sha512-2X4BX8hUeB5JcA1TQJ7GjcgulXQ+5UkNb0DL8gHsHUHdFoiCTJoYLTpib3LtSDPZsRET5ygN4qqIWrHyYIKERA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.47.0", - "@typescript-eslint/types": "^8.47.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@vitest/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.47.0.tgz", - "integrity": "sha512-a0TTJk4HXMkfpFkL9/WaGTNuv7JWfFTQFJd6zS9dVAjKsojmv9HT55xzbEpnZoY+VUb+YXLMp+ihMLz/UlZfDg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.47.0", - "@typescript-eslint/visitor-keys": "8.47.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@vitest/eslint-plugin/node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.47.0.tgz", - "integrity": "sha512-ybUAvjy4ZCL11uryalkKxuT3w3sXJAuWhOoGS3T/Wu+iUu1tGJmk5ytSY8gbdACNARmcYEB0COksD2j6hfGK2g==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@vitest/eslint-plugin/node_modules/@typescript-eslint/types": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.47.0.tgz", - "integrity": "sha512-nHAE6bMKsizhA2uuYZbEbmp5z2UpffNrPEqiKIeN7VsV6UY/roxanWfoRrf6x/k9+Obf+GQdkm0nPU+vnMXo9A==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@vitest/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.47.0.tgz", - "integrity": "sha512-k6ti9UepJf5NpzCjH31hQNLHQWupTRPhZ+KFF8WtTuTpy7uHPfeg2NM7cP27aCGajoEplxJDFVCEm9TGPYyiVg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.47.0", - "@typescript-eslint/tsconfig-utils": "8.47.0", - "@typescript-eslint/types": "8.47.0", - "@typescript-eslint/visitor-keys": "8.47.0", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^2.1.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@vitest/eslint-plugin/node_modules/@typescript-eslint/utils": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.47.0.tgz", - "integrity": "sha512-g7XrNf25iL4TJOiPqatNuaChyqt49a/onq5YsJ9+hXeugK+41LVg7AxikMfM02PC6jbNtZLCJj6AUcQXJS/jGQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.47.0", - "@typescript-eslint/types": "8.47.0", - "@typescript-eslint/typescript-estree": "8.47.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@vitest/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.47.0.tgz", - "integrity": "sha512-SIV3/6eftCy1bNzCQoPmbWsRLujS8t5iDIZ4spZOBHqrM+yfX2ogg8Tt3PDTAVKw3sSCiUgg30uOAvK2r9zGjQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.47.0", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@vitest/eslint-plugin/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@vitest/eslint-plugin/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@vitest/expect": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", @@ -4962,17 +4788,17 @@ } }, "node_modules/@vscode/vsce": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-3.6.0.tgz", - "integrity": "sha512-u2ZoMfymRNJb14aHNawnXJtXHLXDVKc1oKZaH4VELKT/9iWKRVgtQOdwxCgtwSxJoqYvuK4hGlBWQJ05wxADhg==", + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-3.7.1.tgz", + "integrity": "sha512-OTm2XdMt2YkpSn2Nx7z2EJtSuhRHsTPYsSK59hr3v8jRArK+2UEoju4Jumn1CmpgoBLGI6ReHLJ/czYltNUW3g==", "dev": true, "license": "MIT", "dependencies": { "@azure/identity": "^4.1.0", - "@secretlint/node": "^10.1.1", - "@secretlint/secretlint-formatter-sarif": "^10.1.1", - "@secretlint/secretlint-rule-no-dotenv": "^10.1.1", - "@secretlint/secretlint-rule-preset-recommend": "^10.1.1", + "@secretlint/node": "^10.1.2", + "@secretlint/secretlint-formatter-sarif": "^10.1.2", + "@secretlint/secretlint-rule-no-dotenv": "^10.1.2", + "@secretlint/secretlint-rule-preset-recommend": "^10.1.2", "@vscode/vsce-sign": "^2.0.0", "azure-devops-node-api": "^12.5.0", "chalk": "^4.1.2", @@ -4989,7 +4815,7 @@ "minimatch": "^3.0.3", "parse-semver": "^1.1.1", "read": "^1.0.7", - "secretlint": "^10.1.1", + "secretlint": "^10.1.2", "semver": "^7.5.2", "tmp": "^0.2.3", "typed-rest-client": "^1.8.4", @@ -5153,6 +4979,70 @@ "win32" ] }, + "node_modules/@vscode/vsce/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@vscode/vsce/node_modules/brace-expansion": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@vscode/vsce/node_modules/glob": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", + "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@vscode/vsce/node_modules/glob/node_modules/minimatch": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", + "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@vscode/vsce/node_modules/hosted-git-info": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", @@ -5359,9 +5249,9 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", "dependencies": { @@ -5393,9 +5283,9 @@ } }, "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -7185,9 +7075,9 @@ } }, "node_modules/depcheck/node_modules/minimatch": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.6.tgz", - "integrity": "sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==", + "version": "7.4.7", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.7.tgz", + "integrity": "sha512-t3SrsBRdssa8F/nFEadAxveFpnbhlbq7FiizzOMqx69w9EbmNEzcKiPkc60udvrOkWsTMm6jmnQP1c5rbdVfSA==", "dev": true, "license": "ISC", "dependencies": { @@ -7432,9 +7322,36 @@ } }, "node_modules/dotenv": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.1.0.tgz", - "integrity": "sha512-tG9VUTJTuju6GcXgbdsOuRhupE8cb4mRgY5JLRCh4MtGoVo3/gfGUtOMwmProM6d0ba2mCFvv+WrpYJV6qgJXQ==", + "version": "17.2.4", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.4.tgz", + "integrity": "sha512-mudtfb4zRB4bVvdj0xRo+e6duH1csJRM8IukBqfTRvHotn9+LBXB8ynAidP9zHqoRC/fsllXgk4kCKlR21fIhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand": { + "version": "12.0.3", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-12.0.3.tgz", + "integrity": "sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA==", + "license": "BSD-2-Clause", + "dependencies": { + "dotenv": "^16.4.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand/node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -7858,25 +7775,24 @@ } }, "node_modules/eslint": { - "version": "9.29.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.29.0.tgz", - "integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==", + "version": "9.39.3", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.3.tgz", + "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.20.1", - "@eslint/config-helpers": "^0.2.1", - "@eslint/core": "^0.14.0", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.29.0", - "@eslint/plugin-kit": "^0.3.1", + "@eslint/js": "9.39.3", + "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", @@ -8570,9 +8486,9 @@ "license": "BSD-3-Clause" }, "node_modules/fast-xml-parser": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.3.tgz", - "integrity": "sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==", + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.7.tgz", + "integrity": "sha512-JzVLro9NQv92pOM/jTCR6mHlJh2FGwtomH8ZQjhFj/R29P2Fnj38OgPJVtcvYw6SuKClhgYuwUZf5b3rd8u2mA==", "funding": [ { "type": "github", @@ -8581,7 +8497,7 @@ ], "license": "MIT", "dependencies": { - "strnum": "^1.1.1" + "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" @@ -9195,16 +9111,37 @@ "tslib": "2" } }, - "node_modules/glob/node_modules/minimatch": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", - "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", - "license": "BlueOak-1.0.0", + "node_modules/glob/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "license": "MIT", "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" + "balanced-match": "^4.0.2" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", + "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -9516,13 +9453,6 @@ "node": ">=10" } }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" - }, "node_modules/graphql": { "version": "16.11.0", "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.11.0.tgz", @@ -9675,9 +9605,9 @@ } }, "node_modules/hono": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.9.tgz", - "integrity": "sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.2.tgz", + "integrity": "sha512-gJnaDHXKDayjt8ue0n8Gs0A007yKXj4Xzb8+cNjZeYsSzzwKc0Lr+OZgYwVfB0pHfUs17EPoLvrOsEaJ9mj+Tg==", "license": "MIT", "engines": { "node": ">=16.9.0" @@ -10945,6 +10875,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, "license": "MIT" }, "node_modules/json-schema-typed": { @@ -11838,9 +11769,9 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", + "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", "dev": true, "license": "ISC", "dependencies": { @@ -14385,9 +14316,9 @@ } }, "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -15179,9 +15110,9 @@ } }, "node_modules/strnum": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", - "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.2.tgz", + "integrity": "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==", "funding": [ { "type": "github", @@ -15333,9 +15264,9 @@ } }, "node_modules/systeminformation": { - "version": "5.30.2", - "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.30.2.tgz", - "integrity": "sha512-Rrt5oFTWluUVuPlbtn3o9ja+nvjdF3Um4DG0KxqfYvpzcx7Q9plZBTjJiJy9mAouua4+OI7IUGBaG9Zyt9NgxA==", + "version": "5.31.1", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.31.1.tgz", + "integrity": "sha512-6pRwxoGeV/roJYpsfcP6tN9mep6pPeCtXbUOCdVa0nme05Brwcwdge/fVNhIZn2wuUitAKZm4IYa7QjnRIa9zA==", "license": "MIT", "os": [ "darwin", @@ -15376,9 +15307,9 @@ } }, "node_modules/table/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", "dependencies": { @@ -15588,41 +15519,54 @@ } }, "node_modules/test-exclude": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", - "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.2.tgz", + "integrity": "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==", "dev": true, "license": "ISC", "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^10.4.1", - "minimatch": "^9.0.4" + "minimatch": "^10.2.2" }, "engines": { "node": ">=18" } }, + "node_modules/test-exclude/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/test-exclude/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", + "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.2" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -15871,9 +15815,9 @@ } }, "node_modules/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", "dev": true, "license": "MIT", "engines": { @@ -16130,15 +16074,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.35.0.tgz", - "integrity": "sha512-uEnz70b7kBz6eg/j0Czy6K5NivaYopgxRjsnAJ2Fx5oTLo3wefTHIbL7AkQr1+7tJCRVpTs/wiM8JR/11Loq9A==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.56.1.tgz", + "integrity": "sha512-U4lM6pjmBX7J5wk4szltF7I1cGBHXZopnAXCMXb3+fZ3B/0Z3hq3wS/CCUB2NZBNAExK92mCU2tEohWuwVMsDQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.35.0", - "@typescript-eslint/parser": "8.35.0", - "@typescript-eslint/utils": "8.35.0" + "@typescript-eslint/eslint-plugin": "8.56.1", + "@typescript-eslint/parser": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/utils": "8.56.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -16148,8 +16093,8 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/uc.micro": { @@ -17400,6 +17345,8 @@ "ajv-formats": "^3.0.0", "chardet": "^2.1.0", "diff": "^8.0.3", + "dotenv": "^17.2.4", + "dotenv-expand": "^12.0.3", "fast-levenshtein": "^2.0.6", "fdir": "^6.4.6", "fzf": "^0.5.2", @@ -17419,6 +17366,7 @@ "shell-quote": "^1.8.3", "simple-git": "^3.28.0", "strip-ansi": "^7.1.0", + "strip-json-comments": "^3.1.1", "systeminformation": "^5.25.11", "tree-sitter-bash": "^0.25.0", "undici": "^7.10.0", @@ -17474,9 +17422,9 @@ } }, "packages/core/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -17581,6 +17529,12 @@ "node": ">= 4" } }, + "packages/core/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, "packages/core/node_modules/mime": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/mime/-/mime-4.0.7.tgz", diff --git a/packages/a2a-server/src/agent/executor.ts b/packages/a2a-server/src/agent/executor.ts index b0522a945f..e2287a2562 100644 --- a/packages/a2a-server/src/agent/executor.ts +++ b/packages/a2a-server/src/agent/executor.ts @@ -29,6 +29,8 @@ import { CoderAgentEvent, getPersistedState, setPersistedState, + getContextIdFromMetadata, + getAgentSettingsFromMetadata, } from '../types.js'; import { loadConfig, loadEnvironment, setTargetDir } from '../config/config.js'; import { loadSettings } from '../config/settings.js'; @@ -117,8 +119,7 @@ export class CoderAgentExecutor implements AgentExecutor { const agentSettings = persistedState._agentSettings; const config = await this.getConfig(agentSettings, sdkTask.id); const contextId: string = - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion - (metadata['_contextId'] as string) || sdkTask.contextId; + getContextIdFromMetadata(metadata) || sdkTask.contextId; const runtimeTask = await Task.create( sdkTask.id, contextId, @@ -141,8 +142,10 @@ export class CoderAgentExecutor implements AgentExecutor { agentSettingsInput?: AgentSettings, eventBus?: ExecutionEventBus, ): Promise { - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion - const agentSettings = agentSettingsInput || ({} as AgentSettings); + const agentSettings: AgentSettings = agentSettingsInput || { + kind: CoderAgentEvent.StateAgentSettingsEvent, + workspacePath: process.cwd(), + }; const config = await this.getConfig(agentSettings, taskId); const runtimeTask = await Task.create( taskId, @@ -292,8 +295,7 @@ export class CoderAgentExecutor implements AgentExecutor { const contextId: string = userMessage.contextId || sdkTask?.contextId || - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion - (sdkTask?.metadata?.['_contextId'] as string) || + getContextIdFromMetadata(sdkTask?.metadata) || uuidv4(); logger.info( @@ -388,10 +390,7 @@ export class CoderAgentExecutor implements AgentExecutor { } } else { logger.info(`[CoderAgentExecutor] Creating new task ${taskId}.`); - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion - const agentSettings = userMessage.metadata?.[ - 'coderAgent' - ] as AgentSettings; + const agentSettings = getAgentSettingsFromMetadata(userMessage.metadata); try { wrapper = await this.createTask( taskId, diff --git a/packages/a2a-server/src/agent/task.test.ts b/packages/a2a-server/src/agent/task.test.ts index 39cfe5eb74..81987a780b 100644 --- a/packages/a2a-server/src/agent/task.test.ts +++ b/packages/a2a-server/src/agent/task.test.ts @@ -513,7 +513,10 @@ describe('Task', () => { { request: { callId: '1' }, status: 'awaiting_approval', - confirmationDetails: { onConfirm: onConfirmSpy }, + confirmationDetails: { + type: 'edit', + onConfirm: onConfirmSpy, + }, }, ] as unknown as ToolCall[]; @@ -533,7 +536,10 @@ describe('Task', () => { { request: { callId: '1' }, status: 'awaiting_approval', - confirmationDetails: { onConfirm: onConfirmSpy }, + confirmationDetails: { + type: 'edit', + onConfirm: onConfirmSpy, + }, }, ] as unknown as ToolCall[]; diff --git a/packages/a2a-server/src/agent/task.ts b/packages/a2a-server/src/agent/task.ts index bc8cd121a9..c91ef72781 100644 --- a/packages/a2a-server/src/agent/task.ts +++ b/packages/a2a-server/src/agent/task.ts @@ -59,6 +59,33 @@ import type { PartUnion, Part as genAiPart } from '@google/genai'; type UnionKeys = T extends T ? keyof T : never; +type ConfirmationType = ToolCallConfirmationDetails['type']; + +const VALID_CONFIRMATION_TYPES: readonly ConfirmationType[] = [ + 'edit', + 'exec', + 'mcp', + 'info', + 'ask_user', + 'exit_plan_mode', +] as const; + +function isToolCallConfirmationDetails( + value: unknown, +): value is ToolCallConfirmationDetails { + if ( + typeof value !== 'object' || + value === null || + !('onConfirm' in value) || + typeof value.onConfirm !== 'function' || + !('type' in value) || + typeof value.type !== 'string' + ) { + return false; + } + return (VALID_CONFIRMATION_TYPES as readonly string[]).includes(value.type); +} + export class Task { id: string; contextId: string; @@ -376,11 +403,10 @@ export class Task { } if (tc.status === 'awaiting_approval' && tc.confirmationDetails) { - this.pendingToolConfirmationDetails.set( - tc.request.callId, - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion - tc.confirmationDetails as ToolCallConfirmationDetails, - ); + const details = tc.confirmationDetails; + if (isToolCallConfirmationDetails(details)) { + this.pendingToolConfirmationDetails.set(tc.request.callId, details); + } } // Only send an update if the status has actually changed. @@ -412,11 +438,12 @@ export class Task { ); toolCalls.forEach((tc: ToolCall) => { if (tc.status === 'awaiting_approval' && tc.confirmationDetails) { - // eslint-disable-next-line @typescript-eslint/no-floating-promises, @typescript-eslint/no-unsafe-type-assertion - (tc.confirmationDetails as ToolCallConfirmationDetails).onConfirm( - ToolConfirmationOutcome.ProceedOnce, - ); - this.pendingToolConfirmationDetails.delete(tc.request.callId); + const details = tc.confirmationDetails; + if (isToolCallConfirmationDetails(details)) { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + details.onConfirm(ToolConfirmationOutcome.ProceedOnce); + this.pendingToolConfirmationDetails.delete(tc.request.callId); + } } }); return; @@ -466,15 +493,13 @@ export class Task { T extends ToolCall | AnyDeclarativeTool, K extends UnionKeys, >(from: T, ...fields: K[]): Partial { - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion - const ret = {} as Pick; + const ret: Partial = {}; for (const field of fields) { - if (field in from) { + if (field in from && from[field] !== undefined) { ret[field] = from[field]; } } - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion - return ret as Partial; + return ret; } private toolStatusMessage( @@ -485,8 +510,11 @@ export class Task { const messageParts: Part[] = []; // Create a serializable version of the ToolCall (pick necessary - // properties/avoid methods causing circular reference errors) - const serializableToolCall: Partial = this._pickFields( + // properties/avoid methods causing circular reference errors). + // Type allows tool to be Partial for serialization. + const serializableToolCall: Partial> & { + tool?: Partial; + } = this._pickFields( tc, 'request', 'status', @@ -496,8 +524,7 @@ export class Task { ); if (tc.tool) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion - serializableToolCall.tool = this._pickFields( + const toolFields = this._pickFields( tc.tool, 'name', 'displayName', @@ -507,7 +534,8 @@ export class Task { 'canUpdateOutput', 'schema', 'parameterSchema', - ) as AnyDeclarativeTool; + ); + serializableToolCall.tool = toolFields; } messageParts.push({ @@ -530,8 +558,15 @@ export class Task { old_string: string, new_string: string, ): Promise { + // Validate path to prevent path traversal vulnerabilities + const resolvedPath = path.resolve(this.config.getTargetDir(), file_path); + const pathError = this.config.validatePathAccess(resolvedPath, 'read'); + if (pathError) { + throw new Error(`Path validation failed: ${pathError}`); + } + try { - const currentContent = await fs.readFile(file_path, 'utf8'); + const currentContent = await fs.readFile(resolvedPath, 'utf8'); return this._applyReplacement( currentContent, old_string, @@ -625,15 +660,32 @@ export class Task { request.args['old_string'] && request.args['new_string'] ) { - const newContent = await this.getProposedContent( - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion - request.args['file_path'] as string, - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion - request.args['old_string'] as string, - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion - request.args['new_string'] as string, - ); - return { ...request, args: { ...request.args, newContent } }; + const filePath = request.args['file_path']; + const oldString = request.args['old_string']; + const newString = request.args['new_string']; + if ( + typeof filePath === 'string' && + typeof oldString === 'string' && + typeof newString === 'string' + ) { + // Resolve and validate path to prevent path traversal (user-controlled file_path). + const resolvedPath = path.resolve( + this.config.getTargetDir(), + filePath, + ); + const pathError = this.config.validatePathAccess( + resolvedPath, + 'read', + ); + if (!pathError) { + const newContent = await this.getProposedContent( + resolvedPath, + oldString, + newString, + ); + return { ...request, args: { ...request.args, newContent } }; + } + } } return request; }), @@ -725,10 +777,17 @@ export class Task { break; case GeminiEventType.Error: default: { - // Block scope for lexical declaration - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion - const errorEvent = event as ServerGeminiErrorEvent; // Type assertion - const errorMessage = errorEvent.value?.error + // Use type guard instead of unsafe type assertion + let errorEvent: ServerGeminiErrorEvent | undefined; + if ( + event.type === GeminiEventType.Error && + event.value && + typeof event.value === 'object' && + 'error' in event.value + ) { + errorEvent = event; + } + const errorMessage = errorEvent?.value?.error ? getErrorMessage(errorEvent.value.error) : 'Unknown error from LLM stream'; logger.error( @@ -737,7 +796,7 @@ export class Task { ); let errMessage = `Unknown error from LLM stream: ${JSON.stringify(event)}`; - if (errorEvent.value?.error) { + if (errorEvent?.value?.error) { errMessage = parseAndFormatApiError(errorEvent.value.error); } this.cancelPendingTools(`LLM stream error: ${errorMessage}`); @@ -814,12 +873,11 @@ export class Task { // If `edit` tool call, pass updated payload if presesent if (confirmationDetails.type === 'edit') { - const payload = part.data['newContent'] - ? ({ - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion - newContent: part.data['newContent'] as string, - } as ToolConfirmationPayload) - : undefined; + const newContent = part.data['newContent']; + const payload = + typeof newContent === 'string' + ? ({ newContent } as ToolConfirmationPayload) + : undefined; this.skipFinalTrueAfterInlineEdit = !!payload; try { await confirmationDetails.onConfirm(confirmationOutcome, payload); diff --git a/packages/a2a-server/src/types.ts b/packages/a2a-server/src/types.ts index 0ed6a67994..bce233c9dd 100644 --- a/packages/a2a-server/src/types.ts +++ b/packages/a2a-server/src/types.ts @@ -122,11 +122,60 @@ export type PersistedTaskMetadata = { [k: string]: unknown }; export const METADATA_KEY = '__persistedState'; +function isAgentSettings(value: unknown): value is AgentSettings { + return ( + typeof value === 'object' && + value !== null && + 'kind' in value && + value.kind === CoderAgentEvent.StateAgentSettingsEvent && + 'workspacePath' in value && + typeof value.workspacePath === 'string' + ); +} + +function isPersistedStateMetadata( + value: unknown, +): value is PersistedStateMetadata { + return ( + typeof value === 'object' && + value !== null && + '_agentSettings' in value && + '_taskState' in value && + isAgentSettings(value._agentSettings) + ); +} + export function getPersistedState( metadata: PersistedTaskMetadata, ): PersistedStateMetadata | undefined { - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion - return metadata?.[METADATA_KEY] as PersistedStateMetadata | undefined; + const state = metadata?.[METADATA_KEY]; + if (isPersistedStateMetadata(state)) { + return state; + } + return undefined; +} + +export function getContextIdFromMetadata( + metadata: PersistedTaskMetadata | undefined, +): string | undefined { + if (!metadata) { + return undefined; + } + const contextId = metadata['_contextId']; + return typeof contextId === 'string' ? contextId : undefined; +} + +export function getAgentSettingsFromMetadata( + metadata: PersistedTaskMetadata | undefined, +): AgentSettings | undefined { + if (!metadata) { + return undefined; + } + const coderAgent = metadata['coderAgent']; + if (isAgentSettings(coderAgent)) { + return coderAgent; + } + return undefined; } export function setPersistedState( diff --git a/packages/a2a-server/src/utils/testing_utils.ts b/packages/a2a-server/src/utils/testing_utils.ts index 86d0d4a4bd..9cb0657c7a 100644 --- a/packages/a2a-server/src/utils/testing_utils.ts +++ b/packages/a2a-server/src/utils/testing_utils.ts @@ -71,6 +71,7 @@ export function createMockConfig( getMcpServers: vi.fn().mockReturnValue({}), }), getGitService: vi.fn(), + validatePathAccess: vi.fn().mockReturnValue(undefined), ...overrides, } as unknown as Config; mockConfig.getMessageBus = vi.fn().mockReturnValue(createMockMessageBus()); diff --git a/packages/cli/src/config/config.ts b/packages/cli/src/config/config.ts index 50e0c2059d..3e0fd4b913 100755 --- a/packages/cli/src/config/config.ts +++ b/packages/cli/src/config/config.ts @@ -878,6 +878,7 @@ export async function loadCliConfig( agents: refreshedSettings.merged.agents, }; }, + enableConseca: settings.security?.enableConseca, }); } diff --git a/packages/cli/src/config/policy-engine.integration.test.ts b/packages/cli/src/config/policy-engine.integration.test.ts index 6847865434..1d7573337e 100644 --- a/packages/cli/src/config/policy-engine.integration.test.ts +++ b/packages/cli/src/config/policy-engine.integration.test.ts @@ -352,6 +352,38 @@ describe('Policy Engine Integration Tests', () => { ).toBe(PolicyDecision.DENY); }); + it('should correctly match tool annotations', async () => { + const settings: Settings = {}; + + const config = await createPolicyEngineConfig( + settings, + ApprovalMode.DEFAULT, + ); + + // Add a manual rule with annotations to the config + config.rules = config.rules || []; + config.rules.push({ + toolAnnotations: { readOnlyHint: true }, + decision: PolicyDecision.ALLOW, + priority: 10, + }); + + const engine = new PolicyEngine(config); + + // A tool with readOnlyHint=true should be ALLOWED + const roCall = { name: 'some_tool', args: {} }; + const roMeta = { readOnlyHint: true }; + expect((await engine.check(roCall, undefined, roMeta)).decision).toBe( + PolicyDecision.ALLOW, + ); + + // A tool without the hint (or with false) should follow default decision (ASK_USER) + const rwMeta = { readOnlyHint: false }; + expect((await engine.check(roCall, undefined, rwMeta)).decision).toBe( + PolicyDecision.ASK_USER, + ); + }); + describe.each(['write_file', 'replace'])( 'Plan Mode policy for %s', (toolName) => { diff --git a/packages/cli/src/config/policy.test.ts b/packages/cli/src/config/policy.test.ts index a0e687388d..1a773d56a7 100644 --- a/packages/cli/src/config/policy.test.ts +++ b/packages/cli/src/config/policy.test.ts @@ -142,4 +142,48 @@ describe('resolveWorkspacePolicyState', () => { expect.stringContaining('Automatically accepting and loading'), ); }); + + it('should not return workspace policies if cwd is the home directory', async () => { + const policiesDir = path.join(tempDir, '.gemini', 'policies'); + fs.mkdirSync(policiesDir, { recursive: true }); + fs.writeFileSync(path.join(policiesDir, 'policy.toml'), 'rules = []'); + + // Run from HOME directory (tempDir is mocked as HOME in beforeEach) + const result = await resolveWorkspacePolicyState({ + cwd: tempDir, + trustedFolder: true, + interactive: true, + }); + + expect(result.workspacePoliciesDir).toBeUndefined(); + expect(result.policyUpdateConfirmationRequest).toBeUndefined(); + }); + + it('should not return workspace policies if cwd is a symlink to the home directory', async () => { + const policiesDir = path.join(tempDir, '.gemini', 'policies'); + fs.mkdirSync(policiesDir, { recursive: true }); + fs.writeFileSync(path.join(policiesDir, 'policy.toml'), 'rules = []'); + + // Create a symlink to the home directory + const symlinkDir = path.join( + os.tmpdir(), + `gemini-cli-symlink-${Date.now()}`, + ); + fs.symlinkSync(tempDir, symlinkDir, 'dir'); + + try { + // Run from symlink to HOME directory + const result = await resolveWorkspacePolicyState({ + cwd: symlinkDir, + trustedFolder: true, + interactive: true, + }); + + expect(result.workspacePoliciesDir).toBeUndefined(); + expect(result.policyUpdateConfirmationRequest).toBeUndefined(); + } finally { + // Clean up symlink + fs.unlinkSync(symlinkDir); + } + }); }); diff --git a/packages/cli/src/config/policy.ts b/packages/cli/src/config/policy.ts index ef6164efb7..3b85d0b4b6 100644 --- a/packages/cli/src/config/policy.ts +++ b/packages/cli/src/config/policy.ts @@ -67,9 +67,15 @@ export async function resolveWorkspacePolicyState(options: { | undefined; if (trustedFolder) { - const potentialWorkspacePoliciesDir = new Storage( - cwd, - ).getWorkspacePoliciesDir(); + const storage = new Storage(cwd); + + // If we are in the home directory (or rather, our target Gemini dir is the global one), + // don't treat it as a workspace to avoid loading global policies twice. + if (storage.isWorkspaceHomeDir()) { + return { workspacePoliciesDir: undefined }; + } + + const potentialWorkspacePoliciesDir = storage.getWorkspacePoliciesDir(); const integrityManager = new PolicyIntegrityManager(); const integrityResult = await integrityManager.checkIntegrity( 'workspace', diff --git a/packages/cli/src/config/settings.test.ts b/packages/cli/src/config/settings.test.ts index 7b341b3ee0..6b2f18bb58 100644 --- a/packages/cli/src/config/settings.test.ts +++ b/packages/cli/src/config/settings.test.ts @@ -79,6 +79,7 @@ import { import { FatalConfigError, GEMINI_DIR, + Storage, type MCPServerConfig, } from '@google/gemini-cli-core'; import { updateSettingsFilePreservingFormat } from '../utils/commentJson.js'; @@ -126,6 +127,30 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => { const actual = await importOriginal(); const os = await import('node:os'); + const pathMod = await import('node:path'); + const fsMod = await import('node:fs'); + + // Helper to resolve paths using the test's mocked environment + const testResolve = (p: string | undefined) => { + if (!p) return ''; + try { + // Use the mocked fs.realpathSync if available, otherwise fallback + return fsMod.realpathSync(pathMod.resolve(p)); + } catch { + return pathMod.resolve(p); + } + }; + + // Create a smarter mock for isWorkspaceHomeDir + vi.spyOn(actual.Storage.prototype, 'isWorkspaceHomeDir').mockImplementation( + function (this: Storage) { + const target = testResolve(pathMod.dirname(this.getGeminiDir())); + // Pick up the mocked home directory specifically from the 'os' mock + const home = testResolve(os.homedir()); + return actual.normalizePath(target) === actual.normalizePath(home); + }, + ); + return { ...actual, coreEvents: mockCoreEvents, @@ -1491,20 +1516,29 @@ describe('Settings Loading and Merging', () => { return pStr; }); + // Force the storage check to return true for this specific test + const isWorkspaceHomeDirSpy = vi + .spyOn(Storage.prototype, 'isWorkspaceHomeDir') + .mockReturnValue(true); + (mockFsExistsSync as Mock).mockImplementation( (p: string) => // Only return true for workspace settings path to see if it gets loaded p === mockWorkspaceSettingsPath, ); - const settings = loadSettings(mockSymlinkDir); + try { + const settings = loadSettings(mockSymlinkDir); - // Verify that even though the file exists, it was NOT loaded because realpath matched home - expect(fs.readFileSync).not.toHaveBeenCalledWith( - mockWorkspaceSettingsPath, - 'utf-8', - ); - expect(settings.workspace.settings).toEqual({}); + // Verify that even though the file exists, it was NOT loaded because realpath matched home + expect(fs.readFileSync).not.toHaveBeenCalledWith( + mockWorkspaceSettingsPath, + 'utf-8', + ); + expect(settings.workspace.settings).toEqual({}); + } finally { + isWorkspaceHomeDirSpy.mockRestore(); + } }); }); diff --git a/packages/cli/src/config/settings.ts b/packages/cli/src/config/settings.ts index 2f6f2f7450..c3f7c447eb 100644 --- a/packages/cli/src/config/settings.ts +++ b/packages/cli/src/config/settings.ts @@ -637,24 +637,8 @@ export function loadSettings( const systemSettingsPath = getSystemSettingsPath(); const systemDefaultsPath = getSystemDefaultsPath(); - // Resolve paths to their canonical representation to handle symlinks - const resolvedWorkspaceDir = path.resolve(workspaceDir); - const resolvedHomeDir = path.resolve(homedir()); - - let realWorkspaceDir = resolvedWorkspaceDir; - try { - // fs.realpathSync gets the "true" path, resolving any symlinks - realWorkspaceDir = fs.realpathSync(resolvedWorkspaceDir); - } catch (_e) { - // This is okay. The path might not exist yet, and that's a valid state. - } - - // We expect homedir to always exist and be resolvable. - const realHomeDir = fs.realpathSync(resolvedHomeDir); - - const workspaceSettingsPath = new Storage( - workspaceDir, - ).getWorkspaceSettingsPath(); + const storage = new Storage(workspaceDir); + const workspaceSettingsPath = storage.getWorkspaceSettingsPath(); const load = (filePath: string): { settings: Settings; rawJson?: string } => { try { @@ -712,7 +696,7 @@ export function loadSettings( settings: {} as Settings, rawJson: undefined, }; - if (realWorkspaceDir !== realHomeDir) { + if (!storage.isWorkspaceHomeDir()) { workspaceResult = load(workspaceSettingsPath); } @@ -800,11 +784,11 @@ export function loadSettings( readOnly: false, }, { - path: realWorkspaceDir === realHomeDir ? '' : workspaceSettingsPath, + path: storage.isWorkspaceHomeDir() ? '' : workspaceSettingsPath, settings: workspaceSettings, originalSettings: workspaceOriginalSettings, rawJson: workspaceResult.rawJson, - readOnly: realWorkspaceDir === realHomeDir, + readOnly: storage.isWorkspaceHomeDir(), }, isTrusted, settingsErrors, diff --git a/packages/cli/src/config/settingsSchema.ts b/packages/cli/src/config/settingsSchema.ts index 17c51d4e21..5c04cea9b5 100644 --- a/packages/cli/src/config/settingsSchema.ts +++ b/packages/cli/src/config/settingsSchema.ts @@ -297,6 +297,16 @@ const SETTINGS_SCHEMA = { 'Retry on "exception TypeError: fetch failed sending request" errors.', showInDialog: false, }, + maxAttempts: { + type: 'number', + label: 'Max Chat Model Attempts', + category: 'General', + requiresRestart: false, + default: 10, + description: + 'Maximum number of attempts for requests to the main chat model. Cannot exceed 10.', + showInDialog: true, + }, debugKeystrokeLogging: { type: 'boolean', label: 'Debug Keystroke Logging', @@ -1483,6 +1493,16 @@ const SETTINGS_SCHEMA = { }, }, }, + enableConseca: { + type: 'boolean', + label: 'Enable Context-Aware Security', + category: 'Security', + requiresRestart: true, + default: false, + description: + 'Enable the context-aware security checker. This feature uses an LLM to dynamically generate and enforce security policies for tool use based on your prompt, providing an additional layer of protection against unintended actions.', + showInDialog: true, + }, }, }, diff --git a/packages/cli/src/ui/components/Footer.test.tsx b/packages/cli/src/ui/components/Footer.test.tsx index 143e8319a3..cc06cb141d 100644 --- a/packages/cli/src/ui/components/Footer.test.tsx +++ b/packages/cli/src/ui/components/Footer.test.tsx @@ -182,7 +182,7 @@ describe('