mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-06-13 21:07:00 -07:00
Merge branch 'main' into webfetch-stage-1
This commit is contained in:
@@ -347,6 +347,36 @@ async function run() {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Remove status/need-triage from maintainer-only issues since they
|
||||
// don't need community triage. We always attempt removal rather than
|
||||
// checking the (potentially stale) label snapshot, because the
|
||||
// issue-opened-labeler workflow runs concurrently and may add the
|
||||
// label after our snapshot was taken.
|
||||
if (isDryRun) {
|
||||
console.log(
|
||||
`[DRY RUN] Would remove status/need-triage from ${issueKey}`,
|
||||
);
|
||||
} else {
|
||||
try {
|
||||
await octokit.rest.issues.removeLabel({
|
||||
owner: issueInfo.owner,
|
||||
repo: issueInfo.repo,
|
||||
issue_number: issueInfo.number,
|
||||
name: 'status/need-triage',
|
||||
});
|
||||
console.log(`Removed status/need-triage from ${issueKey}`);
|
||||
} catch (removeError) {
|
||||
// 404 means the label wasn't present — that's fine.
|
||||
if (removeError.status === 404) {
|
||||
console.log(
|
||||
`status/need-triage not present on ${issueKey}, skipping.`,
|
||||
);
|
||||
} else {
|
||||
throw removeError;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error processing label for ${issueKey}: ${error.message}`);
|
||||
}
|
||||
|
||||
@@ -95,6 +95,8 @@ jobs:
|
||||
This PR contains the auto-generated changelog for the ${{ steps.release_info.outputs.VERSION }} release.
|
||||
|
||||
Please review and merge.
|
||||
|
||||
Related to #18505
|
||||
branch: 'changelog-${{ steps.release_info.outputs.VERSION }}'
|
||||
base: 'main'
|
||||
team-reviewers: 'gemini-cli-docs, gemini-cli-maintainers'
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
# Model steering (experimental)
|
||||
|
||||
Model steering lets you provide real-time guidance and feedback to Gemini CLI
|
||||
while it is actively executing a task. This lets you correct course, add missing
|
||||
context, or skip unnecessary steps without having to stop and restart the agent.
|
||||
|
||||
> **Note:** This is a preview feature under active development. Preview features
|
||||
> may only be available in the **Preview** channel or may need to be enabled
|
||||
> under `/settings`.
|
||||
|
||||
Model steering is particularly useful during complex [Plan Mode](./plan-mode.md)
|
||||
workflows or long-running subagent executions where you want to ensure the agent
|
||||
stays on the right track.
|
||||
|
||||
## Enabling model steering
|
||||
|
||||
Model steering is an experimental feature and is disabled by default. You can
|
||||
enable it using the `/settings` command or by updating your `settings.json`
|
||||
file.
|
||||
|
||||
1. Type `/settings` in the Gemini CLI.
|
||||
2. Search for **Model Steering**.
|
||||
3. Set the value to **true**.
|
||||
|
||||
Alternatively, add the following to your `settings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"experimental": {
|
||||
"modelSteering": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Using model steering
|
||||
|
||||
When model steering is enabled, Gemini CLI treats any text you type while the
|
||||
agent is working as a steering hint.
|
||||
|
||||
1. Start a task (for example, "Refactor the database service").
|
||||
2. While the agent is working (the spinner is visible), type your feedback in
|
||||
the input box.
|
||||
3. Press **Enter**.
|
||||
|
||||
Gemini CLI acknowledges your hint with a brief message and injects it directly
|
||||
into the model's context for the very next turn. The model then re-evaluates its
|
||||
current plan and adjusts its actions accordingly.
|
||||
|
||||
### Common use cases
|
||||
|
||||
You can use steering hints to guide the model in several ways:
|
||||
|
||||
- **Correcting a path:** "Actually, the utilities are in `src/common/utils`."
|
||||
- **Skipping a step:** "Skip the unit tests for now and just focus on the
|
||||
implementation."
|
||||
- **Adding context:** "The `User` type is defined in `packages/core/types.ts`."
|
||||
- **Redirecting the effort:** "Stop searching the codebase and start drafting
|
||||
the plan now."
|
||||
- **Handling ambiguity:** "Use the existing `Logger` class instead of creating a
|
||||
new one."
|
||||
|
||||
## How it works
|
||||
|
||||
When you submit a steering hint, Gemini CLI performs the following actions:
|
||||
|
||||
1. **Immediate acknowledgment:** It uses a small, fast model to generate a
|
||||
one-sentence acknowledgment so you know your hint was received.
|
||||
2. **Context injection:** It prepends an internal instruction to your hint that
|
||||
tells the main agent to:
|
||||
- Re-evaluate the active plan.
|
||||
- Classify the update (for example, as a new task or extra context).
|
||||
- Apply minimal-diff changes to affected tasks.
|
||||
3. **Real-time update:** The hint is delivered to the agent at the beginning of
|
||||
its next turn, ensuring the most immediate course correction possible.
|
||||
|
||||
## Next steps
|
||||
|
||||
- Tackle complex tasks with [Plan Mode](./plan-mode.md).
|
||||
- Build custom [Agent Skills](./skills.md).
|
||||
+28
-4
@@ -61,20 +61,44 @@ Gemini CLI takes action.
|
||||
[`ask_user`](../tools/ask-user.md). Provide your preferences to help guide
|
||||
the design.
|
||||
3. **Review the plan:** Once Gemini CLI has a proposed strategy, it creates a
|
||||
detailed implementation plan as a Markdown file in your plans directory. You
|
||||
can open and read this file to understand the proposed changes.
|
||||
detailed implementation plan as a Markdown file in your plans directory.
|
||||
- **View:** You can open and read this file to understand the proposed
|
||||
changes.
|
||||
- **Edit:** Press `Ctrl+X` to open the plan directly in your configured
|
||||
external editor.
|
||||
|
||||
4. **Approve or iterate:** Gemini CLI will present the finalized plan for your
|
||||
approval.
|
||||
- **Approve:** If you're satisfied with the plan, approve it to start the
|
||||
implementation immediately: **Yes, automatically accept edits** or **Yes,
|
||||
manually accept edits**.
|
||||
- **Iterate:** If the plan needs adjustments, provide feedback. Gemini CLI
|
||||
will refine the strategy and update the plan.
|
||||
- **Iterate:** If the plan needs adjustments, provide feedback in the input
|
||||
box or [edit the plan file directly](#collaborative-plan-editing). Gemini
|
||||
CLI will refine the strategy and update the plan.
|
||||
- **Cancel:** You can cancel your plan with `Esc`.
|
||||
|
||||
For more complex or specialized planning tasks, you can
|
||||
[customize the planning workflow with skills](#custom-planning-with-skills).
|
||||
|
||||
### Collaborative plan editing
|
||||
|
||||
You can collaborate with Gemini CLI by making direct changes or leaving comments
|
||||
in the implementation plan. This is often faster and more precise than
|
||||
describing complex changes in natural language.
|
||||
|
||||
1. **Open the plan:** Press `Ctrl+X` when Gemini CLI presents a plan for
|
||||
review.
|
||||
2. **Edit or comment:** The plan opens in your configured external editor (for
|
||||
example, VS Code or Vim). You can:
|
||||
- **Modify steps:** Directly reorder, delete, or rewrite implementation
|
||||
steps.
|
||||
- **Leave comments:** Add inline questions or feedback (for example, "Wait,
|
||||
shouldn't we use the existing `Logger` class here?").
|
||||
3. **Save and close:** Save your changes and close the editor.
|
||||
4. **Review and refine:** Gemini CLI automatically detects the changes, reviews
|
||||
your comments, and adjusts the implementation strategy. It then presents the
|
||||
refined plan for your final approval.
|
||||
|
||||
## How to exit Plan Mode
|
||||
|
||||
You can exit Plan Mode at any time, whether you have finalized a plan or want to
|
||||
|
||||
@@ -45,6 +45,7 @@ Environment variables can override these settings.
|
||||
| `logPrompts` | `GEMINI_TELEMETRY_LOG_PROMPTS` | Include prompts in telemetry logs | `true`/`false` | `true` |
|
||||
| `useCollector` | `GEMINI_TELEMETRY_USE_COLLECTOR` | Use external OTLP collector (advanced) | `true`/`false` | `false` |
|
||||
| `useCliAuth` | `GEMINI_TELEMETRY_USE_CLI_AUTH` | Use CLI credentials for telemetry (GCP target only) | `true`/`false` | `false` |
|
||||
| - | `GEMINI_CLI_SURFACE` | Optional custom label for traffic reporting | string | - |
|
||||
|
||||
**Note on boolean environment variables:** For boolean settings like `enabled`,
|
||||
setting the environment variable to `true` or `1` enables the feature.
|
||||
@@ -216,6 +217,50 @@ recommend using file-based output for local development.
|
||||
For advanced local telemetry setups (such as Jaeger or Genkit), see the
|
||||
[Local development guide](../local-development.md#viewing-traces).
|
||||
|
||||
## Client identification
|
||||
|
||||
Gemini CLI includes identifiers in its `User-Agent` header to help you
|
||||
differentiate and report on API traffic from different environments (for
|
||||
example, identifying calls from Gemini Code Assist versus a standard terminal).
|
||||
|
||||
### Automatic identification
|
||||
|
||||
Most integrated environments are identified automatically without additional
|
||||
configuration. The identifier is included as a prefix to the `User-Agent` and as
|
||||
a "surface" tag in the parenthetical metadata.
|
||||
|
||||
| Environment | User-Agent Prefix | Surface Tag |
|
||||
| :---------------------------------- | :--------------------------- | :---------- |
|
||||
| **Gemini Code Assist (Agent Mode)** | `GeminiCLI-a2a-server` | `vscode` |
|
||||
| **Zed (via ACP)** | `GeminiCLI-acp-zed` | `zed` |
|
||||
| **XCode (via ACP)** | `GeminiCLI-acp-xcode` | `xcode` |
|
||||
| **IntelliJ IDEA (via ACP)** | `GeminiCLI-acp-intellijidea` | `jetbrains` |
|
||||
| **Standard Terminal** | `GeminiCLI` | `terminal` |
|
||||
|
||||
**Example User-Agent:**
|
||||
`GeminiCLI-a2a-server/0.34.0/gemini-pro (linux; x64; vscode)`
|
||||
|
||||
### Custom identification
|
||||
|
||||
You can provide a custom identifier for your own scripts or automation by
|
||||
setting the `GEMINI_CLI_SURFACE` environment variable. This is useful for
|
||||
tracking specific internal tools or distribution channels in your GCP logs.
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
export GEMINI_CLI_SURFACE="my-custom-tool"
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
$env:GEMINI_CLI_SURFACE="my-custom-tool"
|
||||
```
|
||||
|
||||
When set, the value appears at the end of the `User-Agent` parenthetical:
|
||||
`GeminiCLI/0.34.0/gemini-pro (linux; x64; my-custom-tool)`
|
||||
|
||||
## Logs, metrics, and traces
|
||||
|
||||
This section describes the structure of logs, metrics, and traces generated by
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
# Use Plan Mode with model steering for complex tasks
|
||||
|
||||
Architecting a complex solution requires precision. By combining Plan Mode's
|
||||
structured environment with model steering's real-time feedback, you can guide
|
||||
Gemini CLI through the research and design phases to ensure the final
|
||||
implementation plan is exactly what you need.
|
||||
|
||||
> **Note:** This is a preview feature under active development. Preview features
|
||||
> may only be available in the **Preview** channel or may need to be enabled
|
||||
> under `/settings`.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Gemini CLI installed and authenticated.
|
||||
- [Plan Mode](../plan-mode.md) enabled in your settings.
|
||||
- [Model steering](../model-steering.md) enabled in your settings.
|
||||
|
||||
## Why combine Plan Mode and model steering?
|
||||
|
||||
[Plan Mode](../plan-mode.md) typically follows a linear path: research, propose,
|
||||
and draft. Adding model steering lets you:
|
||||
|
||||
1. **Direct the research:** Correct the agent if it's looking in the wrong
|
||||
directory or missing a key dependency.
|
||||
2. **Iterate mid-draft:** Suggest a different architectural pattern while the
|
||||
agent is still writing the plan.
|
||||
3. **Speed up the loop:** Avoid waiting for a full research turn to finish
|
||||
before providing critical context.
|
||||
|
||||
## Step 1: Start a complex task
|
||||
|
||||
Enter Plan Mode and start a task that requires research.
|
||||
|
||||
**Prompt:** `/plan I want to implement a new notification service using Redis.`
|
||||
|
||||
Gemini CLI enters Plan Mode and starts researching your existing codebase to
|
||||
identify where the new service should live.
|
||||
|
||||
## Step 2: Steer the research phase
|
||||
|
||||
As you see the agent calling tools like `list_directory` or `grep_search`, you
|
||||
might realize it's missing the relevant context.
|
||||
|
||||
**Action:** While the spinner is active, type your hint:
|
||||
`"Don't forget to check packages/common/queues for the existing Redis config."`
|
||||
|
||||
**Result:** Gemini CLI acknowledges your hint and immediately incorporates it
|
||||
into its research. You'll see it start exploring the directory you suggested in
|
||||
its very next turn.
|
||||
|
||||
## Step 3: Refine the design mid-turn
|
||||
|
||||
After research, the agent starts drafting the implementation plan. If you notice
|
||||
it's proposing a design that doesn't align with your goals, steer it.
|
||||
|
||||
**Action:** Type:
|
||||
`"Actually, let's use a Publisher/Subscriber pattern instead of a simple queue for this service."`
|
||||
|
||||
**Result:** The agent stops drafting the current version of the plan,
|
||||
re-evaluates the design based on your feedback, and starts a new draft that uses
|
||||
the Pub/Sub pattern.
|
||||
|
||||
## Step 4: Approve and implement
|
||||
|
||||
Once the agent has used your hints to craft the perfect plan, review the final
|
||||
`.md` file.
|
||||
|
||||
**Action:** Type: `"Looks perfect. Let's start the implementation."`
|
||||
|
||||
Gemini CLI exits Plan Mode and transitions to the implementation phase. Because
|
||||
the plan was refined in real-time with your feedback, the agent can now execute
|
||||
each step with higher confidence and fewer errors.
|
||||
|
||||
## Tips for effective steering
|
||||
|
||||
- **Be specific:** Instead of "do it differently," try "use the existing
|
||||
`Logger` class in `src/utils`."
|
||||
- **Steer early:** Providing feedback during the research phase is more
|
||||
efficient than waiting for the final plan to be drafted.
|
||||
- **Use for context:** Steering is a great way to provide knowledge that might
|
||||
not be obvious from reading the code (e.g., "We are planning to deprecate this
|
||||
module next month").
|
||||
|
||||
## Next steps
|
||||
|
||||
- Explore [Agent Skills](../skills.md) to add specialized expertise to your
|
||||
planning turns.
|
||||
- See the [Model steering reference](../model-steering.md) for technical
|
||||
details.
|
||||
@@ -701,6 +701,10 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
- **Default:** `undefined`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`agents.browser.disableUserInput`** (boolean):
|
||||
- **Description:** Disable user input on browser window during automation.
|
||||
- **Default:** `true`
|
||||
|
||||
#### `context`
|
||||
|
||||
- **`context.fileName`** (string | string[]):
|
||||
@@ -763,7 +767,7 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
|
||||
#### `tools`
|
||||
|
||||
- **`tools.sandbox`** (boolean | string):
|
||||
- **`tools.sandbox`** (string):
|
||||
- **Description:** Sandbox execution environment. Set to a boolean to enable
|
||||
or disable the sandbox, provide a string path to a sandbox profile, or
|
||||
specify an explicit sandbox command (e.g., "docker", "podman", "lxc").
|
||||
@@ -1384,6 +1388,13 @@ the `advanced.excludedEnvVars` setting in your `settings.json` file.
|
||||
- Useful for shared compute environments or keeping CLI state isolated.
|
||||
- Example: `export GEMINI_CLI_HOME="/path/to/user/config"` (Windows
|
||||
PowerShell: `$env:GEMINI_CLI_HOME="C:\path\to\user\config"`)
|
||||
- **`GEMINI_CLI_SURFACE`**:
|
||||
- Specifies a custom label to include in the `User-Agent` header for API
|
||||
traffic reporting.
|
||||
- This is useful for tracking specific internal tools or distribution
|
||||
channels.
|
||||
- Example: `export GEMINI_CLI_SURFACE="my-custom-tool"` (Windows PowerShell:
|
||||
`$env:GEMINI_CLI_SURFACE="my-custom-tool"`)
|
||||
- **`GOOGLE_API_KEY`**:
|
||||
- Your Google Cloud API key.
|
||||
- Required for using Vertex AI in express mode.
|
||||
|
||||
@@ -229,6 +229,9 @@ a `key` combination.
|
||||
the numbered radio option and confirm when the full number is entered.
|
||||
- `Ctrl + O`: Expand or collapse paste placeholders (`[Pasted Text: X lines]`)
|
||||
inline when the cursor is over the placeholder.
|
||||
- `Ctrl + X` (while a plan is presented): Open the plan in an external editor to
|
||||
[collaboratively edit or comment](../cli/plan-mode.md#collaborative-plan-editing)
|
||||
on the implementation strategy.
|
||||
- `Double-click` on a paste placeholder (alternate buffer mode only): Expand to
|
||||
view full content inline. Double-click again to collapse.
|
||||
|
||||
|
||||
@@ -124,6 +124,21 @@ topics on:
|
||||
`advanced.excludedEnvVars` setting in your `settings.json` to exclude fewer
|
||||
variables.
|
||||
|
||||
- **Warning: `npm WARN deprecated node-domexception@1.0.0` or
|
||||
`npm WARN deprecated glob` during install/update**
|
||||
- **Issue:** When installing or updating the Gemini CLI globally via
|
||||
`npm install -g @google/gemini-cli` or `npm update -g @google/gemini-cli`,
|
||||
you might see deprecation warnings regarding `node-domexception` or old
|
||||
versions of `glob`.
|
||||
- **Cause:** These warnings occur because some dependencies (or their
|
||||
sub-dependencies, like `google-auth-library`) rely on older package
|
||||
versions. Since Gemini CLI requires Node.js 20 or higher, the platform's
|
||||
native features (like the native `DOMException`) are used, making these
|
||||
warnings purely informational.
|
||||
- **Solution:** These warnings are harmless and can be safely ignored. Your
|
||||
installation or update will complete successfully and function properly
|
||||
without any action required.
|
||||
|
||||
## Exit codes
|
||||
|
||||
The Gemini CLI uses specific exit codes to indicate the reason for termination.
|
||||
|
||||
@@ -47,6 +47,11 @@
|
||||
"label": "Plan tasks with todos",
|
||||
"slug": "docs/cli/tutorials/task-planning"
|
||||
},
|
||||
{
|
||||
"label": "Use Plan Mode with model steering",
|
||||
"badge": "🔬",
|
||||
"slug": "docs/cli/tutorials/plan-mode-steering"
|
||||
},
|
||||
{
|
||||
"label": "Web search and fetch",
|
||||
"slug": "docs/cli/tutorials/web-tools"
|
||||
@@ -106,6 +111,11 @@
|
||||
{ "label": "MCP servers", "slug": "docs/tools/mcp-server" },
|
||||
{ "label": "Model routing", "slug": "docs/cli/model-routing" },
|
||||
{ "label": "Model selection", "slug": "docs/cli/model" },
|
||||
{
|
||||
"label": "Model steering",
|
||||
"badge": "🔬",
|
||||
"slug": "docs/cli/model-steering"
|
||||
},
|
||||
{
|
||||
"label": "Notifications",
|
||||
"badge": "🔬",
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, beforeEach, afterEach } from 'vitest';
|
||||
import { TestRig } from './test-helper.js';
|
||||
|
||||
/**
|
||||
* integration test to ensure no node.js deprecation warnings are emitted.
|
||||
* must run for all supported node versions as warnings may vary by version.
|
||||
*/
|
||||
describe('deprecation-warnings', () => {
|
||||
let rig: TestRig;
|
||||
|
||||
beforeEach(() => {
|
||||
rig = new TestRig();
|
||||
});
|
||||
|
||||
afterEach(async () => await rig.cleanup());
|
||||
|
||||
it.each([
|
||||
{ command: '--version', description: 'running --version' },
|
||||
{ command: '--help', description: 'running with --help' },
|
||||
])(
|
||||
'should not emit any deprecation warnings when $description',
|
||||
async ({ command, description }) => {
|
||||
await rig.setup(
|
||||
`should not emit any deprecation warnings when ${description}`,
|
||||
);
|
||||
|
||||
const { stderr, exitCode } = await rig.runWithStreams([command]);
|
||||
|
||||
// node.js deprecation warnings: (node:12345) [DEP0040] DeprecationWarning: ...
|
||||
const deprecationWarningPattern = /\[DEP\d+\].*DeprecationWarning/i;
|
||||
const hasDeprecationWarning = deprecationWarningPattern.test(stderr);
|
||||
|
||||
if (hasDeprecationWarning) {
|
||||
const deprecationMatches = stderr.match(
|
||||
/\[DEP\d+\].*DeprecationWarning:.*/gi,
|
||||
);
|
||||
const warnings = deprecationMatches
|
||||
? deprecationMatches.map((m) => m.trim()).join('\n')
|
||||
: 'Unknown deprecation warning format';
|
||||
|
||||
throw new Error(
|
||||
`Deprecation warnings detected in CLI output:\n${warnings}\n\n` +
|
||||
`Full stderr:\n${stderr}\n\n` +
|
||||
`This test ensures no deprecated Node.js modules are used. ` +
|
||||
`Please update dependencies to use non-deprecated alternatives.`,
|
||||
);
|
||||
}
|
||||
|
||||
// only check exit code if no deprecation warnings found
|
||||
if (exitCode !== 0) {
|
||||
throw new Error(
|
||||
`CLI exited with code ${exitCode} (expected 0). This may indicate a setup issue.\n` +
|
||||
`Stderr: ${stderr}`,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,2 @@
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"run_shell_command","args":{"command":"ls -F"}}}]},"finishReason":"STOP","index":0}]},{"candidates":[{"content":{"parts":[{"text":"I ran ls -F"}]},"finishReason":"STOP","index":0}]}]}
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I ran ls -F"}]},"finishReason":"STOP","index":0}]}]}
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { join } from 'node:path';
|
||||
import { TestRig, GEMINI_DIR } from './test-helper.js';
|
||||
import fs from 'node:fs';
|
||||
|
||||
describe('User Policy Regression Repro', () => {
|
||||
let rig: TestRig;
|
||||
|
||||
beforeEach(() => {
|
||||
rig = new TestRig();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (rig) {
|
||||
await rig.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
it('should respect policies in ~/.gemini/policies/allowed-tools.toml', async () => {
|
||||
rig.setup('user-policy-test', {
|
||||
fakeResponsesPath: join(import.meta.dirname, 'user-policy.responses'),
|
||||
});
|
||||
|
||||
// Create ~/.gemini/policies/allowed-tools.toml
|
||||
const userPoliciesDir = join(rig.homeDir!, GEMINI_DIR, 'policies');
|
||||
fs.mkdirSync(userPoliciesDir, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
join(userPoliciesDir, 'allowed-tools.toml'),
|
||||
`
|
||||
[[rule]]
|
||||
toolName = "run_shell_command"
|
||||
commandPrefix = "ls -F"
|
||||
decision = "allow"
|
||||
priority = 100
|
||||
`,
|
||||
);
|
||||
|
||||
// Run gemini with a prompt that triggers ls -F
|
||||
// approvalMode: 'default' in headless mode will DENY if it hits ASK_USER
|
||||
const result = await rig.run({
|
||||
args: ['-p', 'Run ls -F', '--model', 'gemini-3.1-pro-preview'],
|
||||
approvalMode: 'default',
|
||||
});
|
||||
|
||||
expect(result).toContain('I ran ls -F');
|
||||
expect(result).not.toContain('Tool execution denied by policy');
|
||||
expect(result).not.toContain('Tool "run_shell_command" not found');
|
||||
|
||||
const toolLogs = rig.readToolLogs();
|
||||
const lsLog = toolLogs.find(
|
||||
(l) =>
|
||||
l.toolRequest.name === 'run_shell_command' &&
|
||||
l.toolRequest.args.includes('ls -F'),
|
||||
);
|
||||
expect(lsLog).toBeDefined();
|
||||
expect(lsLog?.toolRequest.success).toBe(true);
|
||||
});
|
||||
|
||||
it('should FAIL if policy is not present (sanity check)', async () => {
|
||||
rig.setup('user-policy-sanity-check', {
|
||||
fakeResponsesPath: join(import.meta.dirname, 'user-policy.responses'),
|
||||
});
|
||||
|
||||
// DO NOT create the policy file here
|
||||
|
||||
// Run gemini with a prompt that triggers ls -F
|
||||
const result = await rig.run({
|
||||
args: ['-p', 'Run ls -F', '--model', 'gemini-3.1-pro-preview'],
|
||||
approvalMode: 'default',
|
||||
});
|
||||
|
||||
// In non-interactive mode, it should be denied
|
||||
expect(result).toContain('Tool "run_shell_command" not found');
|
||||
});
|
||||
});
|
||||
Generated
+225
-168
@@ -1318,9 +1318,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",
|
||||
@@ -1329,7 +1329,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",
|
||||
@@ -1516,9 +1516,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@hono/node-server": {
|
||||
"version": "1.19.9",
|
||||
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz",
|
||||
"integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==",
|
||||
"version": "1.19.11",
|
||||
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz",
|
||||
"integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.14.1"
|
||||
@@ -2089,9 +2089,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",
|
||||
@@ -2195,7 +2195,6 @@
|
||||
"integrity": "sha512-t54CUOsFMappY1Jbzb7fetWeO0n6K0k/4+/ZpkS+3Joz8I4VcvY9OiEBFRYISqaI2fq5sCiPtAjRDOzVYG8m+Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@octokit/auth-token": "^6.0.0",
|
||||
"@octokit/graphql": "^9.0.2",
|
||||
@@ -2376,7 +2375,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
|
||||
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
@@ -2426,7 +2424,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz",
|
||||
"integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@opentelemetry/semantic-conventions": "^1.29.0"
|
||||
},
|
||||
@@ -2801,7 +2798,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.5.0.tgz",
|
||||
"integrity": "sha512-F8W52ApePshpoSrfsSk1H2yJn9aKjCrbpQF1M9Qii0GHzbfVeFUB+rc3X4aggyZD8x9Gu3Slua+s6krmq6Dt8g==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@opentelemetry/core": "2.5.0",
|
||||
"@opentelemetry/semantic-conventions": "^1.29.0"
|
||||
@@ -2835,7 +2831,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.5.0.tgz",
|
||||
"integrity": "sha512-BeJLtU+f5Gf905cJX9vXFQorAr6TAfK3SPvTFqP+scfIpDQEJfRaGJWta7sJgP+m4dNtBf9y3yvBKVAZZtJQVA==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@opentelemetry/core": "2.5.0",
|
||||
"@opentelemetry/resources": "2.5.0"
|
||||
@@ -2890,7 +2885,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.5.0.tgz",
|
||||
"integrity": "sha512-VzRf8LzotASEyNDUxTdaJ9IRJ1/h692WyArDBInf5puLCjxbICD6XkHgpuudis56EndyS7LYFmtTMny6UABNdQ==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@opentelemetry/core": "2.5.0",
|
||||
"@opentelemetry/resources": "2.5.0",
|
||||
@@ -3045,9 +3039,9 @@
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.53.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.2.tgz",
|
||||
"integrity": "sha512-yDPzwsgiFO26RJA4nZo8I+xqzh7sJTZIWQOxn+/XOdPE31lAvLIYCKqjV+lNH/vxE2L2iH3plKxDCRK6i+CwhA==",
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
|
||||
"integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -3058,9 +3052,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.53.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.2.tgz",
|
||||
"integrity": "sha512-k8FontTxIE7b0/OGKeSN5B6j25EuppBcWM33Z19JoVT7UTXFSo3D9CdU39wGTeb29NO3XxpMNauh09B+Ibw+9g==",
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz",
|
||||
"integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -3071,9 +3065,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.53.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.2.tgz",
|
||||
"integrity": "sha512-A6s4gJpomNBtJ2yioj8bflM2oogDwzUiMl2yNJ2v9E7++sHrSrsQ29fOfn5DM/iCzpWcebNYEdXpaK4tr2RhfQ==",
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz",
|
||||
"integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -3084,9 +3078,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.53.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.2.tgz",
|
||||
"integrity": "sha512-e6XqVmXlHrBlG56obu9gDRPW3O3hLxpwHpLsBJvuI8qqnsrtSZ9ERoWUXtPOkY8c78WghyPHZdmPhHLWNdAGEw==",
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz",
|
||||
"integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3097,9 +3091,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||
"version": "4.53.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.2.tgz",
|
||||
"integrity": "sha512-v0E9lJW8VsrwPux5Qe5CwmH/CF/2mQs6xU1MF3nmUxmZUCHazCjLgYvToOk+YuuUqLQBio1qkkREhxhc656ViA==",
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz",
|
||||
"integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -3110,9 +3104,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||
"version": "4.53.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.2.tgz",
|
||||
"integrity": "sha512-ClAmAPx3ZCHtp6ysl4XEhWU69GUB1D+s7G9YjHGhIGCSrsg00nEGRRZHmINYxkdoJehde8VIsDC5t9C0gb6yqA==",
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz",
|
||||
"integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3123,9 +3117,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.53.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.2.tgz",
|
||||
"integrity": "sha512-EPlb95nUsz6Dd9Qy13fI5kUPXNSljaG9FiJ4YUGU1O/Q77i5DYFW5KR8g1OzTcdZUqQQ1KdDqsTohdFVwCwjqg==",
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz",
|
||||
"integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -3136,9 +3130,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||
"version": "4.53.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.2.tgz",
|
||||
"integrity": "sha512-BOmnVW+khAUX+YZvNfa0tGTEMVVEerOxN0pDk2E6N6DsEIa2Ctj48FOMfNDdrwinocKaC7YXUZ1pHlKpnkja/Q==",
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz",
|
||||
"integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -3149,9 +3143,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.53.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.2.tgz",
|
||||
"integrity": "sha512-Xt2byDZ+6OVNuREgBXr4+CZDJtrVso5woFtpKdGPhpTPHcNG7D8YXeQzpNbFRxzTVqJf7kvPMCub/pcGUWgBjA==",
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz",
|
||||
"integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -3162,9 +3156,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.53.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.2.tgz",
|
||||
"integrity": "sha512-+LdZSldy/I9N8+klim/Y1HsKbJ3BbInHav5qE9Iy77dtHC/pibw1SR/fXlWyAk0ThnpRKoODwnAuSjqxFRDHUQ==",
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz",
|
||||
"integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -3175,9 +3169,22 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
||||
"version": "4.53.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.2.tgz",
|
||||
"integrity": "sha512-8ms8sjmyc1jWJS6WdNSA23rEfdjWB30LH8Wqj0Cqvv7qSHnvw6kgMMXRdop6hkmGPlyYBdRPkjJnj3KCUHV/uQ==",
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz",
|
||||
"integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-loong64-musl": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz",
|
||||
"integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
@@ -3188,9 +3195,22 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
||||
"version": "4.53.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.2.tgz",
|
||||
"integrity": "sha512-3HRQLUQbpBDMmzoxPJYd3W6vrVHOo2cVW8RUo87Xz0JPJcBLBr5kZ1pGcQAhdZgX9VV7NbGNipah1omKKe23/g==",
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz",
|
||||
"integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-ppc64-musl": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz",
|
||||
"integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -3201,9 +3221,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.53.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.2.tgz",
|
||||
"integrity": "sha512-fMjKi+ojnmIvhk34gZP94vjogXNNUKMEYs+EDaB/5TG/wUkoeua7p7VCHnE6T2Tx+iaghAqQX8teQzcvrYpaQA==",
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz",
|
||||
"integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -3214,9 +3234,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
||||
"version": "4.53.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.2.tgz",
|
||||
"integrity": "sha512-XuGFGU+VwUUV5kLvoAdi0Wz5Xbh2SrjIxCtZj6Wq8MDp4bflb/+ThZsVxokM7n0pcbkEr2h5/pzqzDYI7cCgLQ==",
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz",
|
||||
"integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -3227,9 +3247,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||
"version": "4.53.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.2.tgz",
|
||||
"integrity": "sha512-w6yjZF0P+NGzWR3AXWX9zc0DNEGdtvykB03uhonSHMRa+oWA6novflo2WaJr6JZakG2ucsyb+rvhrKac6NIy+w==",
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz",
|
||||
"integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -3240,9 +3260,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.53.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.2.tgz",
|
||||
"integrity": "sha512-yo8d6tdfdeBArzC7T/PnHd7OypfI9cbuZzPnzLJIyKYFhAQ8SvlkKtKBMbXDxe1h03Rcr7u++nFS7tqXz87Gtw==",
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz",
|
||||
"integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3253,9 +3273,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.53.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.2.tgz",
|
||||
"integrity": "sha512-ah59c1YkCxKExPP8O9PwOvs+XRLKwh/mV+3YdKqQ5AMQ0r4M4ZDuOrpWkUaqO7fzAHdINzV9tEVu8vNw48z0lA==",
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz",
|
||||
"integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3265,10 +3285,23 @@
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-openbsd-x64": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz",
|
||||
"integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-openharmony-arm64": {
|
||||
"version": "4.53.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.2.tgz",
|
||||
"integrity": "sha512-4VEd19Wmhr+Zy7hbUsFZ6YXEiP48hE//KPLCSVNY5RMGX2/7HZ+QkN55a3atM1C/BZCGIgqN+xrVgtdak2S9+A==",
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz",
|
||||
"integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -3279,9 +3312,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.53.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.2.tgz",
|
||||
"integrity": "sha512-IlbHFYc/pQCgew/d5fslcy1KEaYVCJ44G8pajugd8VoOEI8ODhtb/j8XMhLpwHCMB3yk2J07ctup10gpw2nyMA==",
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz",
|
||||
"integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -3292,9 +3325,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.53.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.2.tgz",
|
||||
"integrity": "sha512-lNlPEGgdUfSzdCWU176ku/dQRnA7W+Gp8d+cWv73jYrb8uT7HTVVxq62DUYxjbaByuf1Yk0RIIAbDzp+CnOTFg==",
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz",
|
||||
"integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -3305,9 +3338,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
||||
"version": "4.53.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.2.tgz",
|
||||
"integrity": "sha512-S6YojNVrHybQis2lYov1sd+uj7K0Q05NxHcGktuMMdIQ2VixGwAfbJ23NnlvvVV1bdpR2m5MsNBViHJKcA4ADw==",
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz",
|
||||
"integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3318,9 +3351,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.53.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.2.tgz",
|
||||
"integrity": "sha512-k+/Rkcyx//P6fetPoLMb8pBeqJBNGx81uuf7iljX9++yNBVRDQgD04L+SVXmXmh5ZP4/WOp4mWF0kmi06PW2tA==",
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz",
|
||||
"integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3375,9 +3408,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": {
|
||||
@@ -4054,7 +4087,6 @@
|
||||
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
@@ -4329,7 +4361,6 @@
|
||||
"integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.35.0",
|
||||
"@typescript-eslint/types": "8.35.0",
|
||||
@@ -5203,7 +5234,6 @@
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -5240,9 +5270,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": {
|
||||
@@ -5274,9 +5304,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",
|
||||
@@ -7735,7 +7765,6 @@
|
||||
"integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
@@ -8246,7 +8275,6 @@
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
|
||||
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"accepts": "^2.0.0",
|
||||
"body-parser": "^2.2.1",
|
||||
@@ -8286,12 +8314,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/express-rate-limit": {
|
||||
"version": "8.2.1",
|
||||
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz",
|
||||
"integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==",
|
||||
"version": "8.3.1",
|
||||
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.1.tgz",
|
||||
"integrity": "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ip-address": "10.0.1"
|
||||
"ip-address": "10.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
@@ -8433,10 +8461,10 @@
|
||||
],
|
||||
"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==",
|
||||
"node_modules/fast-xml-builder": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.2.tgz",
|
||||
"integrity": "sha512-NJAmiuVaJEjVa7TjLZKlYd7RqmzOC91EtPFXHvlTcqBVo50Qh7XV5IwvXi1c7NRz2Q/majGX9YLcwJtWgHjtkA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -8445,7 +8473,24 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"strnum": "^1.1.1"
|
||||
"path-expression-matcher": "^1.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-xml-parser": {
|
||||
"version": "5.5.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.3.tgz",
|
||||
"integrity": "sha512-Ymnuefk6VzAhT3SxLzVUw+nMio/wB1NGypHkgetwtXcK1JfryaHk4DWQFGVwQ9XgzyS5iRZ7C2ZGI4AMsdMZ6A==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-xml-builder": "^1.1.2",
|
||||
"path-expression-matcher": "^1.1.3",
|
||||
"strnum": "^2.1.2"
|
||||
},
|
||||
"bin": {
|
||||
"fxparser": "src/cli/cli.js"
|
||||
@@ -9510,11 +9555,10 @@
|
||||
}
|
||||
},
|
||||
"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.7",
|
||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.7.tgz",
|
||||
"integrity": "sha512-jq9l1DM0zVIvsm3lv9Nw9nlJnMNPOcAtsbsgiUhWcFzPE99Gvo6yRTlszSLLYacMeQ6quHD6hMfId8crVHvexw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=16.9.0"
|
||||
}
|
||||
@@ -9794,7 +9838,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@jrichman/ink/-/ink-6.4.11.tgz",
|
||||
"integrity": "sha512-93LQlzT7vvZ1XJcmOMwN4s+6W334QegendeHOMnEJBlhnpIzr8bws6/aOEHG8ZCuVD/vNeeea5m1msHIdAY6ig==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@alcalzone/ansi-tokenize": "^0.2.1",
|
||||
"ansi-escapes": "^7.0.0",
|
||||
@@ -9963,9 +10006,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ip-address": {
|
||||
"version": "10.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz",
|
||||
"integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==",
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz",
|
||||
"integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
@@ -10749,6 +10792,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": {
|
||||
@@ -12827,6 +12871,21 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/path-expression-matcher": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.1.3.tgz",
|
||||
"integrity": "sha512-qdVgY8KXmVdJZRSS1JdEPOKPdTiEK/pi0RkcT2sw1RhXxohdujUlJFPuS1TSkevZ9vzd3ZlL7ULl1MHGTApKzQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/path-key": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
@@ -13381,7 +13440,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
|
||||
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -13392,7 +13450,6 @@
|
||||
"integrity": "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"shell-quote": "^1.6.1",
|
||||
"ws": "^7"
|
||||
@@ -13862,9 +13919,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.53.2",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.2.tgz",
|
||||
"integrity": "sha512-MHngMYwGJVi6Fmnk6ISmnk7JAHRNF0UkuucA0CUW3N3a4KnONPEZz+vUanQP/ZC/iY1Qkf3bwPWzyY84wEks1g==",
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz",
|
||||
"integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.8"
|
||||
@@ -13877,28 +13934,31 @@
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.53.2",
|
||||
"@rollup/rollup-android-arm64": "4.53.2",
|
||||
"@rollup/rollup-darwin-arm64": "4.53.2",
|
||||
"@rollup/rollup-darwin-x64": "4.53.2",
|
||||
"@rollup/rollup-freebsd-arm64": "4.53.2",
|
||||
"@rollup/rollup-freebsd-x64": "4.53.2",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.53.2",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.53.2",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.53.2",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.53.2",
|
||||
"@rollup/rollup-linux-loong64-gnu": "4.53.2",
|
||||
"@rollup/rollup-linux-ppc64-gnu": "4.53.2",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.53.2",
|
||||
"@rollup/rollup-linux-riscv64-musl": "4.53.2",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.53.2",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.53.2",
|
||||
"@rollup/rollup-linux-x64-musl": "4.53.2",
|
||||
"@rollup/rollup-openharmony-arm64": "4.53.2",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.53.2",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.53.2",
|
||||
"@rollup/rollup-win32-x64-gnu": "4.53.2",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.53.2",
|
||||
"@rollup/rollup-android-arm-eabi": "4.59.0",
|
||||
"@rollup/rollup-android-arm64": "4.59.0",
|
||||
"@rollup/rollup-darwin-arm64": "4.59.0",
|
||||
"@rollup/rollup-darwin-x64": "4.59.0",
|
||||
"@rollup/rollup-freebsd-arm64": "4.59.0",
|
||||
"@rollup/rollup-freebsd-x64": "4.59.0",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.59.0",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.59.0",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.59.0",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.59.0",
|
||||
"@rollup/rollup-linux-loong64-gnu": "4.59.0",
|
||||
"@rollup/rollup-linux-loong64-musl": "4.59.0",
|
||||
"@rollup/rollup-linux-ppc64-gnu": "4.59.0",
|
||||
"@rollup/rollup-linux-ppc64-musl": "4.59.0",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.59.0",
|
||||
"@rollup/rollup-linux-riscv64-musl": "4.59.0",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.59.0",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.59.0",
|
||||
"@rollup/rollup-linux-x64-musl": "4.59.0",
|
||||
"@rollup/rollup-openbsd-x64": "4.59.0",
|
||||
"@rollup/rollup-openharmony-arm64": "4.59.0",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.59.0",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.59.0",
|
||||
"@rollup/rollup-win32-x64-gnu": "4.59.0",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.59.0",
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
@@ -14432,9 +14492,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/simple-git": {
|
||||
"version": "3.28.0",
|
||||
"resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.28.0.tgz",
|
||||
"integrity": "sha512-Rs/vQRwsn1ILH1oBUy8NucJlXmnnLeLCfcvbSehkPzbv3wwoFWIdtfd6Ndo6ZPhlPsCZ60CPI4rxurnwAa+a2w==",
|
||||
"version": "3.33.0",
|
||||
"resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.33.0.tgz",
|
||||
"integrity": "sha512-D4V/tGC2sjsoNhoMybKyGoE+v8A60hRawKQ1iFRA1zwuDgGZCBJ4ByOzZ5J8joBbi4Oam0qiPH+GhzmSBwbJng==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@kwsites/file-exists": "^1.1.1",
|
||||
@@ -14937,9 +14997,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.2.0",
|
||||
"resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.0.tgz",
|
||||
"integrity": "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -15119,9 +15179,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.4",
|
||||
"resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.31.4.tgz",
|
||||
"integrity": "sha512-lZppDyQx91VdS5zJvAyGkmwe+Mq6xY978BDUG2wRkWE+jkmUF5ti8cvOovFQoN5bvSFKCXVkyKEaU5ec3SJiRg==",
|
||||
"license": "MIT",
|
||||
"os": [
|
||||
"darwin",
|
||||
@@ -15162,9 +15222,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": {
|
||||
@@ -15437,7 +15497,6 @@
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -15661,8 +15720,7 @@
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"dev": true,
|
||||
"license": "0BSD",
|
||||
"peer": true
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/tsx": {
|
||||
"version": "4.20.3",
|
||||
@@ -15670,7 +15728,6 @@
|
||||
"integrity": "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "~0.25.0",
|
||||
"get-tsconfig": "^4.7.5"
|
||||
@@ -15830,7 +15887,6 @@
|
||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -15889,9 +15945,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/underscore": {
|
||||
"version": "1.13.7",
|
||||
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz",
|
||||
"integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==",
|
||||
"version": "1.13.8",
|
||||
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz",
|
||||
"integrity": "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -16053,7 +16109,6 @@
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.2.2.tgz",
|
||||
"integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.5.0",
|
||||
@@ -16167,7 +16222,6 @@
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -16180,7 +16234,6 @@
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz",
|
||||
"integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/chai": "^5.2.2",
|
||||
"@vitest/expect": "3.2.4",
|
||||
@@ -16822,7 +16875,6 @@
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
@@ -17289,9 +17341,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",
|
||||
@@ -17339,6 +17391,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",
|
||||
@@ -17359,7 +17417,6 @@
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
|
||||
@@ -91,6 +91,15 @@ describe('loadConfig', () => {
|
||||
expect(fetchAdminControlsOnce).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should pass clientName as a2a-server to Config', async () => {
|
||||
await loadConfig(mockSettings, mockExtensionLoader, taskId);
|
||||
expect(Config).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
clientName: 'a2a-server',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
describe('when admin controls experiment is enabled', () => {
|
||||
beforeEach(() => {
|
||||
// We need to cast to any here to modify the mock implementation
|
||||
|
||||
@@ -62,6 +62,7 @@ export async function loadConfig(
|
||||
|
||||
const configParams: ConfigParameters = {
|
||||
sessionId: taskId,
|
||||
clientName: 'a2a-server',
|
||||
model: PREVIEW_GEMINI_MODEL,
|
||||
embeddingModel: DEFAULT_GEMINI_EMBEDDING_MODEL,
|
||||
sandbox: undefined, // Sandbox might not be relevant for a server-side agent
|
||||
|
||||
@@ -1773,7 +1773,7 @@ describe('loadCliConfig model selection', () => {
|
||||
});
|
||||
|
||||
it('always prefers model from argv', async () => {
|
||||
process.argv = ['node', 'script.js', '--model', 'gemini-2.5-flash-preview'];
|
||||
process.argv = ['node', 'script.js', '--model', 'gemini-2.5-flash'];
|
||||
const argv = await parseArguments(createTestMergedSettings());
|
||||
const config = await loadCliConfig(
|
||||
createTestMergedSettings({
|
||||
@@ -1785,11 +1785,11 @@ describe('loadCliConfig model selection', () => {
|
||||
argv,
|
||||
);
|
||||
|
||||
expect(config.getModel()).toBe('gemini-2.5-flash-preview');
|
||||
expect(config.getModel()).toBe('gemini-2.5-flash');
|
||||
});
|
||||
|
||||
it('selects the model from argv if provided', async () => {
|
||||
process.argv = ['node', 'script.js', '--model', 'gemini-2.5-flash-preview'];
|
||||
process.argv = ['node', 'script.js', '--model', 'gemini-2.5-flash'];
|
||||
const argv = await parseArguments(createTestMergedSettings());
|
||||
const config = await loadCliConfig(
|
||||
createTestMergedSettings({
|
||||
@@ -1799,7 +1799,7 @@ describe('loadCliConfig model selection', () => {
|
||||
argv,
|
||||
);
|
||||
|
||||
expect(config.getModel()).toBe('gemini-2.5-flash-preview');
|
||||
expect(config.getModel()).toBe('gemini-2.5-flash');
|
||||
});
|
||||
|
||||
it('selects the default auto model if provided via auto alias', async () => {
|
||||
@@ -3616,3 +3616,58 @@ describe('loadCliConfig mcpEnabled', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadCliConfig acpMode and clientName', () => {
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
vi.mocked(os.homedir).mockReturnValue('/mock/home/user');
|
||||
vi.stubEnv('GEMINI_API_KEY', 'test-api-key');
|
||||
vi.spyOn(ExtensionManager.prototype, 'getExtensions').mockReturnValue([]);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
|
||||
it('should set acpMode to true and detect clientName when --acp flag is used', async () => {
|
||||
process.argv = ['node', 'script.js', '--acp'];
|
||||
vi.stubEnv('TERM_PROGRAM', 'vscode');
|
||||
vi.stubEnv('VSCODE_GIT_ASKPASS_MAIN', '');
|
||||
vi.stubEnv('ANTIGRAVITY_CLI_ALIAS', '');
|
||||
const argv = await parseArguments(createTestMergedSettings());
|
||||
const config = await loadCliConfig(
|
||||
createTestMergedSettings(),
|
||||
'test-session',
|
||||
argv,
|
||||
);
|
||||
expect(config.getAcpMode()).toBe(true);
|
||||
expect(config.getClientName()).toBe('acp-vscode');
|
||||
});
|
||||
|
||||
it('should set acpMode to true but leave clientName undefined for generic terminals', async () => {
|
||||
process.argv = ['node', 'script.js', '--acp'];
|
||||
vi.stubEnv('TERM_PROGRAM', 'iTerm.app'); // Generic terminal
|
||||
vi.stubEnv('VSCODE_GIT_ASKPASS_MAIN', '');
|
||||
vi.stubEnv('ANTIGRAVITY_CLI_ALIAS', '');
|
||||
const argv = await parseArguments(createTestMergedSettings());
|
||||
const config = await loadCliConfig(
|
||||
createTestMergedSettings(),
|
||||
'test-session',
|
||||
argv,
|
||||
);
|
||||
expect(config.getAcpMode()).toBe(true);
|
||||
expect(config.getClientName()).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should set acpMode to false and clientName to undefined by default', async () => {
|
||||
process.argv = ['node', 'script.js'];
|
||||
const argv = await parseArguments(createTestMergedSettings());
|
||||
const config = await loadCliConfig(
|
||||
createTestMergedSettings(),
|
||||
'test-session',
|
||||
argv,
|
||||
);
|
||||
expect(config.getAcpMode()).toBe(false);
|
||||
expect(config.getClientName()).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -31,6 +31,8 @@ import {
|
||||
type HierarchicalMemory,
|
||||
coreEvents,
|
||||
GEMINI_MODEL_ALIAS_AUTO,
|
||||
isValidModelOrAlias,
|
||||
getValidModelsAndAliases,
|
||||
getAdminErrorMessage,
|
||||
isHeadlessMode,
|
||||
Config,
|
||||
@@ -40,6 +42,7 @@ import {
|
||||
type HookDefinition,
|
||||
type HookEventName,
|
||||
type OutputFormat,
|
||||
detectIdeFromEnv,
|
||||
} from '@google/gemini-cli-core';
|
||||
import {
|
||||
type Settings,
|
||||
@@ -670,6 +673,18 @@ export async function loadCliConfig(
|
||||
const specifiedModel =
|
||||
argv.model || process.env['GEMINI_MODEL'] || settings.model?.name;
|
||||
|
||||
// Validate the model if one was explicitly specified
|
||||
if (specifiedModel && specifiedModel !== GEMINI_MODEL_ALIAS_AUTO) {
|
||||
if (!isValidModelOrAlias(specifiedModel)) {
|
||||
const validModels = getValidModelsAndAliases();
|
||||
|
||||
throw new FatalConfigError(
|
||||
`Invalid model: "${specifiedModel}"\n\n` +
|
||||
`Valid models and aliases:\n${validModels.map((m) => ` - ${m}`).join('\n')}\n\n` +
|
||||
`Use /model to switch models interactively.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
const resolvedModel =
|
||||
specifiedModel === GEMINI_MODEL_ALIAS_AUTO
|
||||
? defaultModel
|
||||
@@ -710,8 +725,21 @@ export async function loadCliConfig(
|
||||
}
|
||||
}
|
||||
|
||||
const isAcpMode = !!argv.acp || !!argv.experimentalAcp;
|
||||
let clientName: string | undefined = undefined;
|
||||
if (isAcpMode) {
|
||||
const ide = detectIdeFromEnv();
|
||||
if (
|
||||
ide &&
|
||||
(ide.name !== 'vscode' || process.env['TERM_PROGRAM'] === 'vscode')
|
||||
) {
|
||||
clientName = `acp-${ide.name}`;
|
||||
}
|
||||
}
|
||||
|
||||
return new Config({
|
||||
acpMode: !!argv.acp || !!argv.experimentalAcp,
|
||||
acpMode: isAcpMode,
|
||||
clientName,
|
||||
sessionId,
|
||||
clientVersion: await getVersion(),
|
||||
embeddingModel: DEFAULT_GEMINI_EMBEDDING_MODEL,
|
||||
|
||||
@@ -12,12 +12,13 @@ import { ExtensionManager } from './extension-manager.js';
|
||||
import { createTestMergedSettings, type MergedSettings } from './settings.js';
|
||||
import { createExtension } from '../test-utils/createExtension.js';
|
||||
import { EXTENSIONS_DIRECTORY_NAME } from './extensions/variables.js';
|
||||
import { themeManager } from '../ui/themes/theme-manager.js';
|
||||
import {
|
||||
TrustLevel,
|
||||
loadTrustedFolders,
|
||||
isWorkspaceTrusted,
|
||||
} from './trustedFolders.js';
|
||||
import { getRealPath } from '@google/gemini-cli-core';
|
||||
import { getRealPath, type CustomTheme } from '@google/gemini-cli-core';
|
||||
|
||||
const mockHomedir = vi.hoisted(() => vi.fn(() => '/tmp/mock-home'));
|
||||
|
||||
@@ -38,6 +39,26 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
};
|
||||
});
|
||||
|
||||
const testTheme: CustomTheme = {
|
||||
type: 'custom',
|
||||
name: 'MyTheme',
|
||||
background: {
|
||||
primary: '#282828',
|
||||
diff: { added: '#2b3312', removed: '#341212' },
|
||||
},
|
||||
text: {
|
||||
primary: '#ebdbb2',
|
||||
secondary: '#a89984',
|
||||
link: '#83a598',
|
||||
accent: '#d3869b',
|
||||
},
|
||||
status: {
|
||||
success: '#b8bb26',
|
||||
warning: '#fabd2f',
|
||||
error: '#fb4934',
|
||||
},
|
||||
};
|
||||
|
||||
describe('ExtensionManager', () => {
|
||||
let tempHomeDir: string;
|
||||
let tempWorkspaceDir: string;
|
||||
@@ -65,6 +86,7 @@ describe('ExtensionManager', () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
themeManager.clearExtensionThemes();
|
||||
try {
|
||||
fs.rmSync(tempHomeDir, { recursive: true, force: true });
|
||||
} catch (_e) {
|
||||
@@ -484,4 +506,45 @@ describe('ExtensionManager', () => {
|
||||
).rejects.toThrow(/already installed/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('early theme registration', () => {
|
||||
it('should register themes with ThemeManager during loadExtensions for active extensions', async () => {
|
||||
createExtension({
|
||||
extensionsDir: userExtensionsDir,
|
||||
name: 'themed-ext',
|
||||
version: '1.0.0',
|
||||
themes: [testTheme],
|
||||
});
|
||||
|
||||
await extensionManager.loadExtensions();
|
||||
|
||||
expect(themeManager.getCustomThemeNames()).toContain(
|
||||
'MyTheme (themed-ext)',
|
||||
);
|
||||
});
|
||||
|
||||
it('should not register themes for inactive extensions', async () => {
|
||||
createExtension({
|
||||
extensionsDir: userExtensionsDir,
|
||||
name: 'disabled-ext',
|
||||
version: '1.0.0',
|
||||
themes: [testTheme],
|
||||
});
|
||||
|
||||
// Disable the extension by creating an enablement override
|
||||
const manager = new ExtensionManager({
|
||||
enabledExtensionOverrides: ['none'],
|
||||
settings: createTestMergedSettings(),
|
||||
workspaceDir: tempWorkspaceDir,
|
||||
requestConsent: vi.fn().mockResolvedValue(true),
|
||||
requestSetting: null,
|
||||
});
|
||||
|
||||
await manager.loadExtensions();
|
||||
|
||||
expect(themeManager.getCustomThemeNames()).not.toContain(
|
||||
'MyTheme (disabled-ext)',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -564,7 +564,7 @@ Would you like to attempt to install via "git clone" instead?`,
|
||||
|
||||
protected override async startExtension(extension: GeminiCLIExtension) {
|
||||
await super.startExtension(extension);
|
||||
if (extension.themes) {
|
||||
if (extension.themes && !themeManager.hasExtensionThemes(extension.name)) {
|
||||
themeManager.registerExtensionThemes(extension.name, extension.themes);
|
||||
}
|
||||
}
|
||||
@@ -624,6 +624,13 @@ Would you like to attempt to install via "git clone" instead?`,
|
||||
|
||||
this.loadedExtensions = builtExtensions;
|
||||
|
||||
// Register extension themes early so they're available at startup.
|
||||
for (const ext of this.loadedExtensions) {
|
||||
if (ext.isActive && ext.themes) {
|
||||
themeManager.registerExtensionThemes(ext.name, ext.themes);
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
this.loadedExtensions.map((ext) => this.maybeStartExtension(ext)),
|
||||
);
|
||||
|
||||
@@ -90,7 +90,13 @@ describe('loadSandboxConfig', () => {
|
||||
process.env['GEMINI_SANDBOX'] = 'docker';
|
||||
mockedCommandExistsSync.mockReturnValue(true);
|
||||
const config = await loadSandboxConfig({}, {});
|
||||
expect(config).toEqual({ command: 'docker', image: 'default/image' });
|
||||
expect(config).toEqual({
|
||||
enabled: true,
|
||||
allowedPaths: [],
|
||||
networkAccess: false,
|
||||
command: 'docker',
|
||||
image: 'default/image',
|
||||
});
|
||||
expect(mockedCommandExistsSync).toHaveBeenCalledWith('docker');
|
||||
});
|
||||
|
||||
@@ -113,7 +119,13 @@ describe('loadSandboxConfig', () => {
|
||||
process.env['GEMINI_SANDBOX'] = 'lxc';
|
||||
mockedCommandExistsSync.mockReturnValue(true);
|
||||
const config = await loadSandboxConfig({}, {});
|
||||
expect(config).toEqual({ command: 'lxc', image: 'default/image' });
|
||||
expect(config).toEqual({
|
||||
enabled: true,
|
||||
allowedPaths: [],
|
||||
networkAccess: false,
|
||||
command: 'lxc',
|
||||
image: 'default/image',
|
||||
});
|
||||
expect(mockedCommandExistsSync).toHaveBeenCalledWith('lxc');
|
||||
});
|
||||
|
||||
@@ -134,6 +146,9 @@ describe('loadSandboxConfig', () => {
|
||||
);
|
||||
const config = await loadSandboxConfig({}, { sandbox: true });
|
||||
expect(config).toEqual({
|
||||
enabled: true,
|
||||
allowedPaths: [],
|
||||
networkAccess: false,
|
||||
command: 'sandbox-exec',
|
||||
image: 'default/image',
|
||||
});
|
||||
@@ -144,6 +159,9 @@ describe('loadSandboxConfig', () => {
|
||||
mockedCommandExistsSync.mockReturnValue(true); // all commands exist
|
||||
const config = await loadSandboxConfig({}, { sandbox: true });
|
||||
expect(config).toEqual({
|
||||
enabled: true,
|
||||
allowedPaths: [],
|
||||
networkAccess: false,
|
||||
command: 'sandbox-exec',
|
||||
image: 'default/image',
|
||||
});
|
||||
@@ -153,14 +171,26 @@ describe('loadSandboxConfig', () => {
|
||||
mockedOsPlatform.mockReturnValue('linux');
|
||||
mockedCommandExistsSync.mockImplementation((cmd) => cmd === 'docker');
|
||||
const config = await loadSandboxConfig({ tools: { sandbox: true } }, {});
|
||||
expect(config).toEqual({ command: 'docker', image: 'default/image' });
|
||||
expect(config).toEqual({
|
||||
enabled: true,
|
||||
allowedPaths: [],
|
||||
networkAccess: false,
|
||||
command: 'docker',
|
||||
image: 'default/image',
|
||||
});
|
||||
});
|
||||
|
||||
it('should use podman if available and docker is not', async () => {
|
||||
mockedOsPlatform.mockReturnValue('linux');
|
||||
mockedCommandExistsSync.mockImplementation((cmd) => cmd === 'podman');
|
||||
const config = await loadSandboxConfig({}, { sandbox: true });
|
||||
expect(config).toEqual({ command: 'podman', image: 'default/image' });
|
||||
expect(config).toEqual({
|
||||
enabled: true,
|
||||
allowedPaths: [],
|
||||
networkAccess: false,
|
||||
command: 'podman',
|
||||
image: 'default/image',
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw if sandbox: true but no command is found', async () => {
|
||||
@@ -177,7 +207,13 @@ describe('loadSandboxConfig', () => {
|
||||
it('should use the specified command if it exists', async () => {
|
||||
mockedCommandExistsSync.mockReturnValue(true);
|
||||
const config = await loadSandboxConfig({}, { sandbox: 'podman' });
|
||||
expect(config).toEqual({ command: 'podman', image: 'default/image' });
|
||||
expect(config).toEqual({
|
||||
enabled: true,
|
||||
allowedPaths: [],
|
||||
networkAccess: false,
|
||||
command: 'podman',
|
||||
image: 'default/image',
|
||||
});
|
||||
expect(mockedCommandExistsSync).toHaveBeenCalledWith('podman');
|
||||
});
|
||||
|
||||
@@ -205,14 +241,26 @@ describe('loadSandboxConfig', () => {
|
||||
process.env['GEMINI_SANDBOX'] = 'docker';
|
||||
mockedCommandExistsSync.mockReturnValue(true);
|
||||
const config = await loadSandboxConfig({}, {});
|
||||
expect(config).toEqual({ command: 'docker', image: 'env/image' });
|
||||
expect(config).toEqual({
|
||||
enabled: true,
|
||||
allowedPaths: [],
|
||||
networkAccess: false,
|
||||
command: 'docker',
|
||||
image: 'env/image',
|
||||
});
|
||||
});
|
||||
|
||||
it('should use image from package.json if env var is not set', async () => {
|
||||
process.env['GEMINI_SANDBOX'] = 'docker';
|
||||
mockedCommandExistsSync.mockReturnValue(true);
|
||||
const config = await loadSandboxConfig({}, {});
|
||||
expect(config).toEqual({ command: 'docker', image: 'default/image' });
|
||||
expect(config).toEqual({
|
||||
enabled: true,
|
||||
allowedPaths: [],
|
||||
networkAccess: false,
|
||||
command: 'docker',
|
||||
image: 'default/image',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return undefined if command is found but no image is configured', async () => {
|
||||
@@ -234,20 +282,115 @@ describe('loadSandboxConfig', () => {
|
||||
'should enable sandbox for value: %s',
|
||||
async (value) => {
|
||||
const config = await loadSandboxConfig({}, { sandbox: value });
|
||||
expect(config).toEqual({ command: 'docker', image: 'default/image' });
|
||||
expect(config).toEqual({
|
||||
enabled: true,
|
||||
allowedPaths: [],
|
||||
networkAccess: false,
|
||||
command: 'docker',
|
||||
image: 'default/image',
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it.each([false, 'false', '0', undefined, null, ''])(
|
||||
'should disable sandbox for value: %s',
|
||||
async (value) => {
|
||||
// \`null\` is not a valid type for the arg, but good to test falsiness
|
||||
// `null` is not a valid type for the arg, but good to test falsiness
|
||||
const config = await loadSandboxConfig({}, { sandbox: value });
|
||||
expect(config).toBeUndefined();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('with SandboxConfig object in settings', () => {
|
||||
beforeEach(() => {
|
||||
mockedOsPlatform.mockReturnValue('linux');
|
||||
mockedCommandExistsSync.mockImplementation((cmd) => cmd === 'docker');
|
||||
});
|
||||
|
||||
it('should support object structure with enabled: true', async () => {
|
||||
const config = await loadSandboxConfig(
|
||||
{
|
||||
tools: {
|
||||
sandbox: {
|
||||
enabled: true,
|
||||
allowedPaths: ['/tmp'],
|
||||
networkAccess: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{},
|
||||
);
|
||||
expect(config).toEqual({
|
||||
enabled: true,
|
||||
allowedPaths: ['/tmp'],
|
||||
networkAccess: true,
|
||||
command: 'docker',
|
||||
image: 'default/image',
|
||||
});
|
||||
});
|
||||
|
||||
it('should support object structure with explicit command', async () => {
|
||||
mockedCommandExistsSync.mockImplementation((cmd) => cmd === 'podman');
|
||||
const config = await loadSandboxConfig(
|
||||
{
|
||||
tools: {
|
||||
sandbox: {
|
||||
enabled: true,
|
||||
command: 'podman',
|
||||
},
|
||||
},
|
||||
},
|
||||
{},
|
||||
);
|
||||
expect(config?.command).toBe('podman');
|
||||
});
|
||||
|
||||
it('should support object structure with custom image', async () => {
|
||||
const config = await loadSandboxConfig(
|
||||
{
|
||||
tools: {
|
||||
sandbox: {
|
||||
enabled: true,
|
||||
image: 'custom/image',
|
||||
},
|
||||
},
|
||||
},
|
||||
{},
|
||||
);
|
||||
expect(config?.image).toBe('custom/image');
|
||||
});
|
||||
|
||||
it('should return undefined if enabled is false in object', async () => {
|
||||
const config = await loadSandboxConfig(
|
||||
{
|
||||
tools: {
|
||||
sandbox: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{},
|
||||
);
|
||||
expect(config).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should prioritize CLI flag over settings object', async () => {
|
||||
const config = await loadSandboxConfig(
|
||||
{
|
||||
tools: {
|
||||
sandbox: {
|
||||
enabled: true,
|
||||
allowedPaths: ['/settings-path'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{ sandbox: false },
|
||||
);
|
||||
expect(config).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('with sandbox: runsc (gVisor)', () => {
|
||||
beforeEach(() => {
|
||||
mockedOsPlatform.mockReturnValue('linux');
|
||||
@@ -257,7 +400,13 @@ describe('loadSandboxConfig', () => {
|
||||
it('should use runsc via CLI argument on Linux', async () => {
|
||||
const config = await loadSandboxConfig({}, { sandbox: 'runsc' });
|
||||
|
||||
expect(config).toEqual({ command: 'runsc', image: 'default/image' });
|
||||
expect(config).toEqual({
|
||||
enabled: true,
|
||||
allowedPaths: [],
|
||||
networkAccess: false,
|
||||
command: 'runsc',
|
||||
image: 'default/image',
|
||||
});
|
||||
expect(mockedCommandExistsSync).toHaveBeenCalledWith('runsc');
|
||||
expect(mockedCommandExistsSync).toHaveBeenCalledWith('docker');
|
||||
});
|
||||
@@ -266,7 +415,13 @@ describe('loadSandboxConfig', () => {
|
||||
process.env['GEMINI_SANDBOX'] = 'runsc';
|
||||
const config = await loadSandboxConfig({}, {});
|
||||
|
||||
expect(config).toEqual({ command: 'runsc', image: 'default/image' });
|
||||
expect(config).toEqual({
|
||||
enabled: true,
|
||||
allowedPaths: [],
|
||||
networkAccess: false,
|
||||
command: 'runsc',
|
||||
image: 'default/image',
|
||||
});
|
||||
expect(mockedCommandExistsSync).toHaveBeenCalledWith('runsc');
|
||||
expect(mockedCommandExistsSync).toHaveBeenCalledWith('docker');
|
||||
});
|
||||
@@ -277,7 +432,13 @@ describe('loadSandboxConfig', () => {
|
||||
{},
|
||||
);
|
||||
|
||||
expect(config).toEqual({ command: 'runsc', image: 'default/image' });
|
||||
expect(config).toEqual({
|
||||
enabled: true,
|
||||
allowedPaths: [],
|
||||
networkAccess: false,
|
||||
command: 'runsc',
|
||||
image: 'default/image',
|
||||
});
|
||||
expect(mockedCommandExistsSync).toHaveBeenCalledWith('runsc');
|
||||
expect(mockedCommandExistsSync).toHaveBeenCalledWith('docker');
|
||||
});
|
||||
@@ -289,7 +450,13 @@ describe('loadSandboxConfig', () => {
|
||||
{ sandbox: 'podman' },
|
||||
);
|
||||
|
||||
expect(config).toEqual({ command: 'runsc', image: 'default/image' });
|
||||
expect(config).toEqual({
|
||||
enabled: true,
|
||||
allowedPaths: [],
|
||||
networkAccess: false,
|
||||
command: 'runsc',
|
||||
image: 'default/image',
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject runsc on macOS (Linux-only)', async () => {
|
||||
|
||||
@@ -23,7 +23,7 @@ const __dirname = path.dirname(__filename);
|
||||
interface SandboxCliArgs {
|
||||
sandbox?: boolean | string | null;
|
||||
}
|
||||
const VALID_SANDBOX_COMMANDS: ReadonlyArray<SandboxConfig['command']> = [
|
||||
const VALID_SANDBOX_COMMANDS = [
|
||||
'docker',
|
||||
'podman',
|
||||
'sandbox-exec',
|
||||
@@ -31,8 +31,10 @@ const VALID_SANDBOX_COMMANDS: ReadonlyArray<SandboxConfig['command']> = [
|
||||
'lxc',
|
||||
];
|
||||
|
||||
function isSandboxCommand(value: string): value is SandboxConfig['command'] {
|
||||
return (VALID_SANDBOX_COMMANDS as readonly string[]).includes(value);
|
||||
function isSandboxCommand(
|
||||
value: string,
|
||||
): value is Exclude<SandboxConfig['command'], undefined> {
|
||||
return VALID_SANDBOX_COMMANDS.includes(value);
|
||||
}
|
||||
|
||||
function getSandboxCommand(
|
||||
@@ -116,13 +118,36 @@ export async function loadSandboxConfig(
|
||||
argv: SandboxCliArgs,
|
||||
): Promise<SandboxConfig | undefined> {
|
||||
const sandboxOption = argv.sandbox ?? settings.tools?.sandbox;
|
||||
const command = getSandboxCommand(sandboxOption);
|
||||
|
||||
let sandboxValue: boolean | string | null | undefined;
|
||||
let allowedPaths: string[] = [];
|
||||
let networkAccess = false;
|
||||
let customImage: string | undefined;
|
||||
|
||||
if (
|
||||
typeof sandboxOption === 'object' &&
|
||||
sandboxOption !== null &&
|
||||
!Array.isArray(sandboxOption)
|
||||
) {
|
||||
const config = sandboxOption;
|
||||
sandboxValue = config.enabled ? (config.command ?? true) : false;
|
||||
allowedPaths = config.allowedPaths ?? [];
|
||||
networkAccess = config.networkAccess ?? false;
|
||||
customImage = config.image;
|
||||
} else if (typeof sandboxOption !== 'object' || sandboxOption === null) {
|
||||
sandboxValue = sandboxOption;
|
||||
}
|
||||
|
||||
const command = getSandboxCommand(sandboxValue);
|
||||
|
||||
const packageJson = await getPackageJson(__dirname);
|
||||
const image =
|
||||
process.env['GEMINI_SANDBOX_IMAGE'] ??
|
||||
process.env['GEMINI_SANDBOX_IMAGE_DEFAULT'] ??
|
||||
customImage ??
|
||||
packageJson?.config?.sandboxImageUri;
|
||||
|
||||
return command && image ? { command, image } : undefined;
|
||||
return command && image
|
||||
? { enabled: true, allowedPaths, networkAccess, command, image }
|
||||
: undefined;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
type AuthType,
|
||||
type AgentOverride,
|
||||
type CustomTheme,
|
||||
type SandboxConfig,
|
||||
} from '@google/gemini-cli-core';
|
||||
import type { SessionRetentionSettings } from './settings.js';
|
||||
import { DEFAULT_MIN_RETENTION } from '../utils/sessionCleanup.js';
|
||||
@@ -1106,6 +1107,16 @@ const SETTINGS_SCHEMA = {
|
||||
description: 'Model override for the visual agent.',
|
||||
showInDialog: false,
|
||||
},
|
||||
disableUserInput: {
|
||||
type: 'boolean',
|
||||
label: 'Disable User Input',
|
||||
category: 'Advanced',
|
||||
requiresRestart: false,
|
||||
default: true,
|
||||
description:
|
||||
'Disable user input on browser window during automation.',
|
||||
showInDialog: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -1263,8 +1274,8 @@ const SETTINGS_SCHEMA = {
|
||||
label: 'Sandbox',
|
||||
category: 'Tools',
|
||||
requiresRestart: true,
|
||||
default: undefined as boolean | string | undefined,
|
||||
ref: 'BooleanOrString',
|
||||
default: undefined as boolean | string | SandboxConfig | undefined,
|
||||
ref: 'BooleanOrStringOrObject',
|
||||
description: oneLine`
|
||||
Sandbox execution environment.
|
||||
Set to a boolean to enable or disable the sandbox, provide a string path to a sandbox profile,
|
||||
@@ -2618,9 +2629,44 @@ export const SETTINGS_SCHEMA_DEFINITIONS: Record<
|
||||
description: 'Accepts either a single string or an array of strings.',
|
||||
anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }],
|
||||
},
|
||||
BooleanOrString: {
|
||||
description: 'Accepts either a boolean flag or a string command name.',
|
||||
anyOf: [{ type: 'boolean' }, { type: 'string' }],
|
||||
BooleanOrStringOrObject: {
|
||||
description:
|
||||
'Accepts either a boolean flag, a string command name, or a configuration object.',
|
||||
anyOf: [
|
||||
{ type: 'boolean' },
|
||||
{ type: 'string' },
|
||||
{
|
||||
type: 'object',
|
||||
description: 'Sandbox configuration object.',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
enabled: {
|
||||
type: 'boolean',
|
||||
description: 'Enables or disables the sandbox.',
|
||||
},
|
||||
command: {
|
||||
type: 'string',
|
||||
description:
|
||||
'The sandbox command to use (docker, podman, sandbox-exec, runsc, lxc).',
|
||||
enum: ['docker', 'podman', 'sandbox-exec', 'runsc', 'lxc'],
|
||||
},
|
||||
image: {
|
||||
type: 'string',
|
||||
description: 'The sandbox image to use.',
|
||||
},
|
||||
allowedPaths: {
|
||||
type: 'array',
|
||||
description:
|
||||
'A list of absolute host paths that should be accessible within the sandbox.',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
networkAccess: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the sandbox should have internet access.',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
HookDefinitionArray: {
|
||||
type: 'array',
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
type CliArgs,
|
||||
} from './config/config.js';
|
||||
import { loadSandboxConfig } from './config/sandboxConfig.js';
|
||||
import { createMockSandboxConfig } from '@google/gemini-cli-test-utils';
|
||||
import { terminalCapabilityManager } from './ui/utils/terminalCapabilityManager.js';
|
||||
import { start_sandbox } from './utils/sandbox.js';
|
||||
import { validateNonInteractiveAuth } from './validateNonInterActiveAuth.js';
|
||||
@@ -192,12 +193,19 @@ vi.mock('./ui/utils/terminalCapabilityManager.js', () => ({
|
||||
|
||||
vi.mock('./config/config.js', () => ({
|
||||
loadCliConfig: vi.fn().mockImplementation(async () => createMockConfig()),
|
||||
parseArguments: vi.fn().mockResolvedValue({}),
|
||||
parseArguments: vi.fn().mockResolvedValue({
|
||||
enabled: true,
|
||||
allowedPaths: [],
|
||||
networkAccess: false,
|
||||
}),
|
||||
isDebugMode: vi.fn(() => false),
|
||||
}));
|
||||
|
||||
vi.mock('read-package-up', () => ({
|
||||
readPackageUp: vi.fn().mockResolvedValue({
|
||||
enabled: true,
|
||||
allowedPaths: [],
|
||||
networkAccess: false,
|
||||
packageJson: { name: 'test-pkg', version: 'test-version' },
|
||||
path: '/fake/path/package.json',
|
||||
}),
|
||||
@@ -235,6 +243,9 @@ vi.mock('./utils/relaunch.js', () => ({
|
||||
|
||||
vi.mock('./config/sandboxConfig.js', () => ({
|
||||
loadSandboxConfig: vi.fn().mockResolvedValue({
|
||||
enabled: true,
|
||||
allowedPaths: [],
|
||||
networkAccess: false,
|
||||
command: 'docker',
|
||||
image: 'test-image',
|
||||
}),
|
||||
@@ -540,6 +551,9 @@ describe('gemini.tsx main function kitty protocol', () => {
|
||||
);
|
||||
|
||||
vi.mocked(parseArguments).mockResolvedValue({
|
||||
enabled: true,
|
||||
allowedPaths: [],
|
||||
networkAccess: false,
|
||||
promptInteractive: false,
|
||||
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
|
||||
@@ -603,6 +617,9 @@ describe('gemini.tsx main function kitty protocol', () => {
|
||||
});
|
||||
|
||||
vi.mocked(parseArguments).mockResolvedValue({
|
||||
enabled: true,
|
||||
allowedPaths: [],
|
||||
networkAccess: false,
|
||||
promptInteractive: false,
|
||||
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
|
||||
@@ -622,14 +639,17 @@ describe('gemini.tsx main function kitty protocol', () => {
|
||||
const mockConfig = createMockConfig({
|
||||
isInteractive: () => false,
|
||||
getQuestion: () => '',
|
||||
getSandbox: () => ({ command: 'docker', image: 'test-image' }),
|
||||
getSandbox: () =>
|
||||
createMockSandboxConfig({ command: 'docker', image: 'test-image' }),
|
||||
});
|
||||
|
||||
vi.mocked(loadCliConfig).mockResolvedValue(mockConfig);
|
||||
vi.mocked(loadSandboxConfig).mockResolvedValue({
|
||||
command: 'docker',
|
||||
image: 'test-image',
|
||||
});
|
||||
vi.mocked(loadSandboxConfig).mockResolvedValue(
|
||||
createMockSandboxConfig({
|
||||
command: 'docker',
|
||||
image: 'test-image',
|
||||
}),
|
||||
);
|
||||
|
||||
process.env['GEMINI_API_KEY'] = 'test-key';
|
||||
try {
|
||||
@@ -670,6 +690,9 @@ describe('gemini.tsx main function kitty protocol', () => {
|
||||
);
|
||||
|
||||
vi.mocked(parseArguments).mockResolvedValue({
|
||||
enabled: true,
|
||||
allowedPaths: [],
|
||||
networkAccess: false,
|
||||
promptInteractive: false,
|
||||
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
vi.mocked(loadCliConfig).mockResolvedValue(
|
||||
@@ -725,6 +748,9 @@ describe('gemini.tsx main function kitty protocol', () => {
|
||||
);
|
||||
|
||||
vi.mocked(parseArguments).mockResolvedValue({
|
||||
enabled: true,
|
||||
allowedPaths: [],
|
||||
networkAccess: false,
|
||||
promptInteractive: false,
|
||||
resume: 'session-id',
|
||||
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
@@ -781,6 +807,9 @@ describe('gemini.tsx main function kitty protocol', () => {
|
||||
);
|
||||
|
||||
vi.mocked(parseArguments).mockResolvedValue({
|
||||
enabled: true,
|
||||
allowedPaths: [],
|
||||
networkAccess: false,
|
||||
promptInteractive: false,
|
||||
resume: 'latest',
|
||||
} as unknown as CliArgs);
|
||||
@@ -831,6 +860,9 @@ describe('gemini.tsx main function kitty protocol', () => {
|
||||
);
|
||||
|
||||
vi.mocked(parseArguments).mockResolvedValue({
|
||||
enabled: true,
|
||||
allowedPaths: [],
|
||||
networkAccess: false,
|
||||
promptInteractive: false,
|
||||
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
vi.mocked(loadCliConfig).mockResolvedValue(
|
||||
@@ -881,6 +913,9 @@ describe('gemini.tsx main function kitty protocol', () => {
|
||||
);
|
||||
|
||||
vi.mocked(parseArguments).mockResolvedValue({
|
||||
enabled: true,
|
||||
allowedPaths: [],
|
||||
networkAccess: false,
|
||||
promptInteractive: false,
|
||||
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
vi.mocked(loadCliConfig).mockResolvedValue(
|
||||
@@ -955,6 +990,9 @@ describe('gemini.tsx main function exit codes', () => {
|
||||
}),
|
||||
);
|
||||
vi.mocked(parseArguments).mockResolvedValue({
|
||||
enabled: true,
|
||||
allowedPaths: [],
|
||||
networkAccess: false,
|
||||
promptInteractive: true,
|
||||
} as unknown as CliArgs);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@@ -971,10 +1009,12 @@ describe('gemini.tsx main function exit codes', () => {
|
||||
|
||||
it('should exit with 41 for auth failure during sandbox setup', async () => {
|
||||
vi.stubEnv('SANDBOX', '');
|
||||
vi.mocked(loadSandboxConfig).mockResolvedValue({
|
||||
command: 'docker',
|
||||
image: 'test-image',
|
||||
});
|
||||
vi.mocked(loadSandboxConfig).mockResolvedValue(
|
||||
createMockSandboxConfig({
|
||||
command: 'docker',
|
||||
image: 'test-image',
|
||||
}),
|
||||
);
|
||||
vi.mocked(loadCliConfig).mockResolvedValue(
|
||||
createMockConfig({
|
||||
refreshAuth: vi.fn().mockRejectedValue(new Error('Auth failed')),
|
||||
@@ -1014,6 +1054,9 @@ describe('gemini.tsx main function exit codes', () => {
|
||||
}),
|
||||
);
|
||||
vi.mocked(parseArguments).mockResolvedValue({
|
||||
enabled: true,
|
||||
allowedPaths: [],
|
||||
networkAccess: false,
|
||||
resume: 'invalid-session',
|
||||
} as unknown as CliArgs);
|
||||
|
||||
@@ -1055,7 +1098,11 @@ describe('gemini.tsx main function exit codes', () => {
|
||||
merged: { security: { auth: {} }, ui: {} },
|
||||
}),
|
||||
);
|
||||
vi.mocked(parseArguments).mockResolvedValue({} as unknown as CliArgs);
|
||||
vi.mocked(parseArguments).mockResolvedValue({
|
||||
enabled: true,
|
||||
allowedPaths: [],
|
||||
networkAccess: false,
|
||||
} as unknown as CliArgs);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(process.stdin as any).isTTY = true;
|
||||
|
||||
@@ -1090,7 +1137,11 @@ describe('gemini.tsx main function exit codes', () => {
|
||||
merged: { security: { auth: { selectedType: undefined } }, ui: {} },
|
||||
}),
|
||||
);
|
||||
vi.mocked(parseArguments).mockResolvedValue({} as unknown as CliArgs);
|
||||
vi.mocked(parseArguments).mockResolvedValue({
|
||||
enabled: true,
|
||||
allowedPaths: [],
|
||||
networkAccess: false,
|
||||
} as unknown as CliArgs);
|
||||
|
||||
runNonInteractiveSpy.mockImplementation(() => Promise.resolve());
|
||||
|
||||
@@ -1160,7 +1211,12 @@ describe('project hooks loading based on trust', () => {
|
||||
const configModule = await import('./config/config.js');
|
||||
loadCliConfig = vi.mocked(configModule.loadCliConfig);
|
||||
parseArguments = vi.mocked(configModule.parseArguments);
|
||||
parseArguments.mockResolvedValue({ startupMessages: [] });
|
||||
parseArguments.mockResolvedValue({
|
||||
enabled: true,
|
||||
allowedPaths: [],
|
||||
networkAccess: false,
|
||||
startupMessages: [],
|
||||
});
|
||||
|
||||
const settingsModule = await import('./config/settings.js');
|
||||
loadSettings = vi.mocked(settingsModule.loadSettings);
|
||||
|
||||
@@ -162,6 +162,7 @@ import {
|
||||
import { LoginWithGoogleRestartDialog } from './auth/LoginWithGoogleRestartDialog.js';
|
||||
import { NewAgentsChoice } from './components/NewAgentsNotification.js';
|
||||
import { isSlashCommand } from './utils/commandUtils.js';
|
||||
import { parseSlashCommand } from '../utils/commands.js';
|
||||
import { useTerminalTheme } from './hooks/useTerminalTheme.js';
|
||||
import { useTimedMessage } from './hooks/useTimedMessage.js';
|
||||
import { useIsHelpDismissKey } from './utils/shortcutsHelp.js';
|
||||
@@ -1289,6 +1290,18 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
...pendingGeminiHistoryItems,
|
||||
]);
|
||||
|
||||
if (isSlash && isAgentRunning) {
|
||||
const { commandToExecute } = parseSlashCommand(
|
||||
submittedValue,
|
||||
slashCommands ?? [],
|
||||
);
|
||||
if (commandToExecute?.isSafeConcurrent) {
|
||||
void handleSlashCommand(submittedValue);
|
||||
addInput(submittedValue);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (config.isModelSteeringEnabled() && isAgentRunning && !isSlash) {
|
||||
handleHintSubmit(submittedValue);
|
||||
addInput(submittedValue);
|
||||
@@ -1332,6 +1345,8 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
addMessage,
|
||||
addInput,
|
||||
submitQuery,
|
||||
handleSlashCommand,
|
||||
slashCommands,
|
||||
isMcpReady,
|
||||
streamingState,
|
||||
messageQueue.length,
|
||||
|
||||
@@ -6,8 +6,10 @@
|
||||
|
||||
import type { IdeInfo } from '@google/gemini-cli-core';
|
||||
import { Box, Text } from 'ink';
|
||||
import type { RadioSelectItem } from './components/shared/RadioButtonSelect.js';
|
||||
import { RadioButtonSelect } from './components/shared/RadioButtonSelect.js';
|
||||
import {
|
||||
RadioButtonSelect,
|
||||
type RadioSelectItem,
|
||||
} from './components/shared/RadioButtonSelect.js';
|
||||
import { useKeypress } from './hooks/useKeypress.js';
|
||||
import { theme } from './semantic-colors.js';
|
||||
|
||||
|
||||
@@ -9,11 +9,11 @@ import { useCallback, useState } from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import { RadioButtonSelect } from '../components/shared/RadioButtonSelect.js';
|
||||
import type {
|
||||
LoadableSettingScope,
|
||||
LoadedSettings,
|
||||
import {
|
||||
SettingScope,
|
||||
type LoadableSettingScope,
|
||||
type LoadedSettings,
|
||||
} from '../../config/settings.js';
|
||||
import { SettingScope } from '../../config/settings.js';
|
||||
import {
|
||||
AuthType,
|
||||
clearCachedCredentialFile,
|
||||
|
||||
@@ -23,6 +23,7 @@ export const aboutCommand: SlashCommand = {
|
||||
description: 'Show version info',
|
||||
kind: CommandKind.BUILT_IN,
|
||||
autoExecute: true,
|
||||
isSafeConcurrent: true,
|
||||
action: async (context) => {
|
||||
const osVersion = process.platform;
|
||||
let sandboxEnv = 'no sandbox';
|
||||
|
||||
@@ -15,6 +15,7 @@ export const settingsCommand: SlashCommand = {
|
||||
description: 'View and edit Gemini CLI settings',
|
||||
kind: CommandKind.BUILT_IN,
|
||||
autoExecute: true,
|
||||
isSafeConcurrent: true,
|
||||
action: (_context, _args): OpenDialogActionReturn => ({
|
||||
type: 'dialog',
|
||||
dialog: 'settings',
|
||||
|
||||
@@ -84,6 +84,7 @@ export const statsCommand: SlashCommand = {
|
||||
description: 'Check session stats. Usage: /stats [session|model|tools]',
|
||||
kind: CommandKind.BUILT_IN,
|
||||
autoExecute: false,
|
||||
isSafeConcurrent: true,
|
||||
action: async (context: CommandContext) => {
|
||||
await defaultSessionView(context);
|
||||
},
|
||||
@@ -93,6 +94,7 @@ export const statsCommand: SlashCommand = {
|
||||
description: 'Show session-specific usage statistics',
|
||||
kind: CommandKind.BUILT_IN,
|
||||
autoExecute: true,
|
||||
isSafeConcurrent: true,
|
||||
action: async (context: CommandContext) => {
|
||||
await defaultSessionView(context);
|
||||
},
|
||||
@@ -102,6 +104,7 @@ export const statsCommand: SlashCommand = {
|
||||
description: 'Show model-specific usage statistics',
|
||||
kind: CommandKind.BUILT_IN,
|
||||
autoExecute: true,
|
||||
isSafeConcurrent: true,
|
||||
action: (context: CommandContext) => {
|
||||
const { selectedAuthType, userEmail, tier } = getUserIdentity(context);
|
||||
const currentModel = context.services.config?.getModel();
|
||||
@@ -125,6 +128,7 @@ export const statsCommand: SlashCommand = {
|
||||
description: 'Show tool-specific usage statistics',
|
||||
kind: CommandKind.BUILT_IN,
|
||||
autoExecute: true,
|
||||
isSafeConcurrent: true,
|
||||
action: (context: CommandContext) => {
|
||||
context.ui.addItem({
|
||||
type: MessageType.TOOL_STATS,
|
||||
|
||||
@@ -207,6 +207,11 @@ export interface SlashCommand {
|
||||
*/
|
||||
autoExecute?: boolean;
|
||||
|
||||
/**
|
||||
* Whether this command can be safely executed while the agent is busy (e.g. streaming a response).
|
||||
*/
|
||||
isSafeConcurrent?: boolean;
|
||||
|
||||
// Optional metadata for extension commands
|
||||
extensionName?: string;
|
||||
extensionId?: string;
|
||||
|
||||
@@ -37,6 +37,7 @@ describe('upgradeCommand', () => {
|
||||
getContentGeneratorConfig: vi.fn().mockReturnValue({
|
||||
authType: AuthType.LOGIN_WITH_GOOGLE,
|
||||
}),
|
||||
getUserTierName: vi.fn().mockReturnValue(undefined),
|
||||
},
|
||||
},
|
||||
} as unknown as CommandContext);
|
||||
@@ -115,4 +116,23 @@ describe('upgradeCommand', () => {
|
||||
});
|
||||
expect(openBrowserSecurely).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return info message for ultra tiers', async () => {
|
||||
vi.mocked(mockContext.services.config!.getUserTierName).mockReturnValue(
|
||||
'Advanced Ultra',
|
||||
);
|
||||
|
||||
if (!upgradeCommand.action) {
|
||||
throw new Error('The upgrade command must have an action.');
|
||||
}
|
||||
|
||||
const result = await upgradeCommand.action(mockContext, '');
|
||||
|
||||
expect(result).toEqual({
|
||||
type: 'message',
|
||||
messageType: 'info',
|
||||
content: 'You are already on the highest tier: Advanced Ultra.',
|
||||
});
|
||||
expect(openBrowserSecurely).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
shouldLaunchBrowser,
|
||||
UPGRADE_URL_PAGE,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { isUltraTier } from '../../utils/tierUtils.js';
|
||||
import { CommandKind, type SlashCommand } from './types.js';
|
||||
|
||||
/**
|
||||
@@ -35,6 +36,15 @@ export const upgradeCommand: SlashCommand = {
|
||||
};
|
||||
}
|
||||
|
||||
const tierName = context.services.config?.getUserTierName();
|
||||
if (isUltraTier(tierName)) {
|
||||
return {
|
||||
type: 'message',
|
||||
messageType: 'info',
|
||||
content: `You are already on the highest tier: ${tierName}.`,
|
||||
};
|
||||
}
|
||||
|
||||
if (!shouldLaunchBrowser()) {
|
||||
return {
|
||||
type: 'message',
|
||||
|
||||
@@ -11,6 +11,7 @@ export const vimCommand: SlashCommand = {
|
||||
description: 'Toggle vim mode on/off',
|
||||
kind: CommandKind.BUILT_IN,
|
||||
autoExecute: true,
|
||||
isSafeConcurrent: true,
|
||||
action: async (context, _args) => {
|
||||
const newVimState = await context.ui.toggleVimEnabled();
|
||||
|
||||
|
||||
@@ -8,11 +8,11 @@ import type React from 'react';
|
||||
import { useState, useEffect, useMemo, useCallback } from 'react';
|
||||
import { Text } from 'ink';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import type {
|
||||
LoadableSettingScope,
|
||||
LoadedSettings,
|
||||
import {
|
||||
SettingScope,
|
||||
type LoadableSettingScope,
|
||||
type LoadedSettings,
|
||||
} from '../../config/settings.js';
|
||||
import { SettingScope } from '../../config/settings.js';
|
||||
import type { AgentDefinition, AgentOverride } from '@google/gemini-cli-core';
|
||||
import { getCachedStringWidth } from '../utils/textUtils.js';
|
||||
import {
|
||||
|
||||
@@ -15,13 +15,12 @@ import {
|
||||
} from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import type { Question } from '@google/gemini-cli-core';
|
||||
import { checkExhaustive, type Question } from '@google/gemini-cli-core';
|
||||
import { BaseSelectionList } from './shared/BaseSelectionList.js';
|
||||
import type { SelectionListItem } from '../hooks/useSelectionList.js';
|
||||
import { TabHeader, type Tab } from './shared/TabHeader.js';
|
||||
import { useKeypress, type Key } from '../hooks/useKeypress.js';
|
||||
import { Command } from '../key/keyMatchers.js';
|
||||
import { checkExhaustive } from '@google/gemini-cli-core';
|
||||
import { TextInput } from './shared/TextInput.js';
|
||||
import { formatCommand } from '../key/keybindingUtils.js';
|
||||
import {
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import { useMemo } from 'react';
|
||||
import { ChecklistItem, type ChecklistItemData } from './ChecklistItem.js';
|
||||
|
||||
export interface ChecklistProps {
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { useRef, useCallback } from 'react';
|
||||
import type React from 'react';
|
||||
import { useRef, useCallback } from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import type { ConsoleMessageItem } from '../types.js';
|
||||
|
||||
@@ -87,6 +87,7 @@ export const DialogManager = ({
|
||||
!!uiState.quota.proQuotaRequest.isModelNotFoundError
|
||||
}
|
||||
authType={uiState.quota.proQuotaRequest.authType}
|
||||
tierName={config?.getUserTierName()}
|
||||
onChoice={uiActions.handleProQuotaChoice}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
import { render } from '../../test-utils/render.js';
|
||||
import { EditorSettingsDialog } from './EditorSettingsDialog.js';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { SettingScope } from '../../config/settings.js';
|
||||
import type { LoadedSettings } from '../../config/settings.js';
|
||||
import { SettingScope, type LoadedSettings } from '../../config/settings.js';
|
||||
import { KeypressProvider } from '../contexts/KeypressContext.js';
|
||||
import { act } from 'react';
|
||||
import { waitFor } from '../../test-utils/async.js';
|
||||
|
||||
@@ -13,18 +13,18 @@ import {
|
||||
type EditorDisplay,
|
||||
} from '../editors/editorSettingsManager.js';
|
||||
import { RadioButtonSelect } from './shared/RadioButtonSelect.js';
|
||||
import type {
|
||||
LoadableSettingScope,
|
||||
LoadedSettings,
|
||||
import {
|
||||
SettingScope,
|
||||
type LoadableSettingScope,
|
||||
type LoadedSettings,
|
||||
} from '../../config/settings.js';
|
||||
import { SettingScope } from '../../config/settings.js';
|
||||
import {
|
||||
type EditorType,
|
||||
isEditorAvailable,
|
||||
EDITOR_DISPLAY_NAMES,
|
||||
coreEvents,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { useKeypress } from '../hooks/useKeypress.js';
|
||||
import { coreEvents } from '@google/gemini-cli-core';
|
||||
|
||||
interface EditorDialogProps {
|
||||
onSelect: (
|
||||
|
||||
@@ -9,8 +9,10 @@ import type React from 'react';
|
||||
import { useEffect, useState, useCallback } from 'react';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import stripAnsi from 'strip-ansi';
|
||||
import type { RadioSelectItem } from './shared/RadioButtonSelect.js';
|
||||
import { RadioButtonSelect } from './shared/RadioButtonSelect.js';
|
||||
import {
|
||||
RadioButtonSelect,
|
||||
type RadioSelectItem,
|
||||
} from './shared/RadioButtonSelect.js';
|
||||
import { MaxSizedBox } from './shared/MaxSizedBox.js';
|
||||
import { Scrollable } from './shared/Scrollable.js';
|
||||
import { useKeypress } from '../hooks/useKeypress.js';
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { renderWithProviders } from '../../test-utils/render.js';
|
||||
import * as SessionContext from '../contexts/SessionContext.js';
|
||||
import type { SessionStatsState } from '../contexts/SessionContext.js';
|
||||
import { type SessionStatsState } from '../contexts/SessionContext.js';
|
||||
import { Banner } from './Banner.js';
|
||||
import { Footer } from './Footer.js';
|
||||
import { Header } from './Header.js';
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
import { render } from '../../test-utils/render.js';
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { Help } from './Help.js';
|
||||
import type { SlashCommand } from '../commands/types.js';
|
||||
import { CommandKind } from '../commands/types.js';
|
||||
import { CommandKind, type SlashCommand } from '../commands/types.js';
|
||||
|
||||
const mockCommands: readonly SlashCommand[] = [
|
||||
{
|
||||
|
||||
@@ -6,13 +6,12 @@
|
||||
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { HistoryItemDisplay } from './HistoryItemDisplay.js';
|
||||
import { type HistoryItem } from '../types.js';
|
||||
import { MessageType } from '../types.js';
|
||||
import { MessageType, type HistoryItem } from '../types.js';
|
||||
import { SessionStatsProvider } from '../contexts/SessionContext.js';
|
||||
import {
|
||||
CoreToolCallStatus,
|
||||
type Config,
|
||||
type ToolExecuteConfirmationDetails,
|
||||
CoreToolCallStatus,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { ToolGroupMessage } from './messages/ToolGroupMessage.js';
|
||||
import { renderWithProviders } from '../../test-utils/render.js';
|
||||
|
||||
@@ -8,31 +8,46 @@ import { renderWithProviders } from '../../test-utils/render.js';
|
||||
import { createMockSettings } from '../../test-utils/settings.js';
|
||||
import { waitFor } from '../../test-utils/async.js';
|
||||
import { act, useState } from 'react';
|
||||
import type { InputPromptProps } from './InputPrompt.js';
|
||||
import { InputPrompt, tryTogglePasteExpansion } from './InputPrompt.js';
|
||||
import type { TextBuffer } from './shared/text-buffer.js';
|
||||
import {
|
||||
InputPrompt,
|
||||
tryTogglePasteExpansion,
|
||||
type InputPromptProps,
|
||||
} from './InputPrompt.js';
|
||||
import {
|
||||
calculateTransformationsForLine,
|
||||
calculateTransformedLine,
|
||||
type TextBuffer,
|
||||
} from './shared/text-buffer.js';
|
||||
import type { Config } from '@google/gemini-cli-core';
|
||||
import { ApprovalMode, debugLogger } from '@google/gemini-cli-core';
|
||||
import {
|
||||
ApprovalMode,
|
||||
debugLogger,
|
||||
type Config,
|
||||
} from '@google/gemini-cli-core';
|
||||
import * as path from 'node:path';
|
||||
import type { CommandContext, SlashCommand } from '../commands/types.js';
|
||||
import { CommandKind } from '../commands/types.js';
|
||||
import {
|
||||
CommandKind,
|
||||
type CommandContext,
|
||||
type SlashCommand,
|
||||
} from '../commands/types.js';
|
||||
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
||||
import { Text } from 'ink';
|
||||
import type { UseShellHistoryReturn } from '../hooks/useShellHistory.js';
|
||||
import { useShellHistory } from '../hooks/useShellHistory.js';
|
||||
import type { UseCommandCompletionReturn } from '../hooks/useCommandCompletion.js';
|
||||
import {
|
||||
useShellHistory,
|
||||
type UseShellHistoryReturn,
|
||||
} from '../hooks/useShellHistory.js';
|
||||
import {
|
||||
useCommandCompletion,
|
||||
CompletionMode,
|
||||
type UseCommandCompletionReturn,
|
||||
} from '../hooks/useCommandCompletion.js';
|
||||
import type { UseInputHistoryReturn } from '../hooks/useInputHistory.js';
|
||||
import { useInputHistory } from '../hooks/useInputHistory.js';
|
||||
import type { UseReverseSearchCompletionReturn } from '../hooks/useReverseSearchCompletion.js';
|
||||
import { useReverseSearchCompletion } from '../hooks/useReverseSearchCompletion.js';
|
||||
import {
|
||||
useInputHistory,
|
||||
type UseInputHistoryReturn,
|
||||
} from '../hooks/useInputHistory.js';
|
||||
import {
|
||||
useReverseSearchCompletion,
|
||||
type UseReverseSearchCompletionReturn,
|
||||
} from '../hooks/useReverseSearchCompletion.js';
|
||||
import clipboardy from 'clipboardy';
|
||||
import * as clipboardUtils from '../utils/clipboardUtils.js';
|
||||
import { useKittyKeyboardProtocol } from '../hooks/useKittyKeyboardProtocol.js';
|
||||
@@ -79,6 +94,12 @@ afterEach(() => {
|
||||
});
|
||||
|
||||
const mockSlashCommands: SlashCommand[] = [
|
||||
{
|
||||
name: 'stats',
|
||||
description: 'Check stats',
|
||||
kind: CommandKind.BUILT_IN,
|
||||
isSafeConcurrent: true,
|
||||
},
|
||||
{
|
||||
name: 'clear',
|
||||
kind: CommandKind.BUILT_IN,
|
||||
@@ -3861,6 +3882,13 @@ describe('InputPrompt', () => {
|
||||
shouldSubmit: false,
|
||||
errorMessage: 'Slash commands cannot be queued',
|
||||
},
|
||||
{
|
||||
name: 'should allow concurrent-safe slash commands',
|
||||
bufferText: '/stats',
|
||||
shellMode: false,
|
||||
shouldSubmit: true,
|
||||
errorMessage: null,
|
||||
},
|
||||
{
|
||||
name: 'should prevent shell commands',
|
||||
bufferText: 'ls',
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import clipboardy from 'clipboardy';
|
||||
import { useCallback, useEffect, useState, useRef, useMemo } from 'react';
|
||||
import clipboardy from 'clipboardy';
|
||||
import { Box, Text, useStdout, type DOMElement } from 'ink';
|
||||
import { SuggestionsDisplay, MAX_WIDTH } from './SuggestionsDisplay.js';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
@@ -34,13 +34,16 @@ import {
|
||||
useCommandCompletion,
|
||||
CompletionMode,
|
||||
} from '../hooks/useCommandCompletion.js';
|
||||
import type { Key } from '../hooks/useKeypress.js';
|
||||
import { useKeypress } from '../hooks/useKeypress.js';
|
||||
import { useKeypress, type Key } from '../hooks/useKeypress.js';
|
||||
import { Command } from '../key/keyMatchers.js';
|
||||
import { formatCommand } from '../key/keybindingUtils.js';
|
||||
import type { CommandContext, SlashCommand } from '../commands/types.js';
|
||||
import type { Config } from '@google/gemini-cli-core';
|
||||
import { ApprovalMode, coreEvents, debugLogger } from '@google/gemini-cli-core';
|
||||
import {
|
||||
ApprovalMode,
|
||||
coreEvents,
|
||||
debugLogger,
|
||||
type Config,
|
||||
} from '@google/gemini-cli-core';
|
||||
import {
|
||||
parseInputForHighlighting,
|
||||
parseSegmentsFromTokens,
|
||||
@@ -55,6 +58,7 @@ import {
|
||||
isAutoExecutableCommand,
|
||||
isSlashCommand,
|
||||
} from '../utils/commandUtils.js';
|
||||
import { parseSlashCommand } from '../../utils/commands.js';
|
||||
import * as path from 'node:path';
|
||||
import { SCREEN_READER_USER_PREFIX } from '../textConstants.js';
|
||||
import { getSafeLowColorBackground } from '../themes/color-utils.js';
|
||||
@@ -405,6 +409,17 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
(isSlash || isShell) &&
|
||||
streamingState === StreamingState.Responding
|
||||
) {
|
||||
if (isSlash) {
|
||||
const { commandToExecute } = parseSlashCommand(
|
||||
trimmedMessage,
|
||||
slashCommands,
|
||||
);
|
||||
if (commandToExecute?.isSafeConcurrent) {
|
||||
inputHistory.handleSubmit(trimmedMessage);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setQueueErrorMessage(
|
||||
`${isShell ? 'Shell' : 'Slash'} commands cannot be queued`,
|
||||
);
|
||||
@@ -412,7 +427,13 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
}
|
||||
inputHistory.handleSubmit(trimmedMessage);
|
||||
},
|
||||
[inputHistory, shellModeActive, streamingState, setQueueErrorMessage],
|
||||
[
|
||||
inputHistory,
|
||||
shellModeActive,
|
||||
streamingState,
|
||||
setQueueErrorMessage,
|
||||
slashCommands,
|
||||
],
|
||||
);
|
||||
|
||||
// Effect to reset completion if history navigation just occurred and set the text
|
||||
|
||||
@@ -7,8 +7,10 @@
|
||||
import { Box, Text } from 'ink';
|
||||
import type React from 'react';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import type { RadioSelectItem } from './shared/RadioButtonSelect.js';
|
||||
import { RadioButtonSelect } from './shared/RadioButtonSelect.js';
|
||||
import {
|
||||
RadioButtonSelect,
|
||||
type RadioSelectItem,
|
||||
} from './shared/RadioButtonSelect.js';
|
||||
import { useKeypress } from '../hooks/useKeypress.js';
|
||||
|
||||
export enum LogoutChoice {
|
||||
|
||||
@@ -5,8 +5,10 @@
|
||||
*/
|
||||
|
||||
import { Box, Text } from 'ink';
|
||||
import type { RadioSelectItem } from './shared/RadioButtonSelect.js';
|
||||
import { RadioButtonSelect } from './shared/RadioButtonSelect.js';
|
||||
import {
|
||||
RadioButtonSelect,
|
||||
type RadioSelectItem,
|
||||
} from './shared/RadioButtonSelect.js';
|
||||
import { useKeypress } from '../hooks/useKeypress.js';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@ import { describe, it, expect, vi, beforeAll, afterAll } from 'vitest';
|
||||
import { ModelStatsDisplay } from './ModelStatsDisplay.js';
|
||||
import * as SessionContext from '../contexts/SessionContext.js';
|
||||
import * as SettingsContext from '../contexts/SettingsContext.js';
|
||||
import type { LoadedSettings } from '../../config/settings.js';
|
||||
import type { SessionMetrics } from '../contexts/SessionContext.js';
|
||||
import { type LoadedSettings } from '../../config/settings.js';
|
||||
import { type SessionMetrics } from '../contexts/SessionContext.js';
|
||||
import { ToolCallDecision, LlmRole } from '@google/gemini-cli-core';
|
||||
|
||||
// Mock the context to provide controlled data for testing
|
||||
|
||||
@@ -8,14 +8,16 @@ import { Box, Text } from 'ink';
|
||||
import type React from 'react';
|
||||
import { useState } from 'react';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import type { RadioSelectItem } from './shared/RadioButtonSelect.js';
|
||||
import { RadioButtonSelect } from './shared/RadioButtonSelect.js';
|
||||
import {
|
||||
RadioButtonSelect,
|
||||
type RadioSelectItem,
|
||||
} from './shared/RadioButtonSelect.js';
|
||||
import { useKeypress } from '../hooks/useKeypress.js';
|
||||
import { loadTrustedFolders, TrustLevel } from '../../config/trustedFolders.js';
|
||||
import { expandHomeDir } from '../utils/directoryUtils.js';
|
||||
import * as path from 'node:path';
|
||||
import { MessageType, type HistoryItem } from '../types.js';
|
||||
import type { Config } from '@google/gemini-cli-core';
|
||||
import { type Config } from '@google/gemini-cli-core';
|
||||
|
||||
export enum MultiFolderTrustChoice {
|
||||
YES,
|
||||
|
||||
@@ -4,8 +4,15 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import type { Mock } from 'vitest';
|
||||
import {
|
||||
describe,
|
||||
it,
|
||||
expect,
|
||||
vi,
|
||||
beforeEach,
|
||||
afterEach,
|
||||
type Mock,
|
||||
} from 'vitest';
|
||||
import { renderWithProviders } from '../../test-utils/render.js';
|
||||
import { waitFor } from '../../test-utils/async.js';
|
||||
import { PermissionsModifyTrustDialog } from './PermissionsModifyTrustDialog.js';
|
||||
|
||||
@@ -5,16 +5,18 @@
|
||||
*/
|
||||
|
||||
import { Box, Text } from 'ink';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import type React from 'react';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import {
|
||||
PolicyIntegrityManager,
|
||||
type Config,
|
||||
type PolicyUpdateConfirmationRequest,
|
||||
PolicyIntegrityManager,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import type { RadioSelectItem } from './shared/RadioButtonSelect.js';
|
||||
import { RadioButtonSelect } from './shared/RadioButtonSelect.js';
|
||||
import {
|
||||
RadioButtonSelect,
|
||||
type RadioSelectItem,
|
||||
} from './shared/RadioButtonSelect.js';
|
||||
import { useKeypress } from '../hooks/useKeypress.js';
|
||||
import { Command } from '../key/keyMatchers.js';
|
||||
import { useKeyMatchers } from '../hooks/useKeyMatchers.js';
|
||||
|
||||
@@ -202,6 +202,40 @@ describe('ProQuotaDialog', () => {
|
||||
);
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should NOT render upgrade option for LOGIN_WITH_GOOGLE if tier is Ultra', () => {
|
||||
const { unmount } = render(
|
||||
<ProQuotaDialog
|
||||
failedModel="gemini-2.5-pro"
|
||||
fallbackModel="gemini-2.5-flash"
|
||||
message="free tier quota error"
|
||||
isTerminalQuotaError={true}
|
||||
isModelNotFoundError={false}
|
||||
authType={AuthType.LOGIN_WITH_GOOGLE}
|
||||
tierName="Gemini Advanced Ultra"
|
||||
onChoice={mockOnChoice}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(RadioButtonSelect).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
items: [
|
||||
{
|
||||
label: 'Switch to gemini-2.5-flash',
|
||||
value: 'retry_always',
|
||||
key: 'retry_always',
|
||||
},
|
||||
{
|
||||
label: 'Stop',
|
||||
value: 'retry_later',
|
||||
key: 'retry_later',
|
||||
},
|
||||
],
|
||||
}),
|
||||
undefined,
|
||||
);
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when it is a capacity error', () => {
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Box, Text } from 'ink';
|
||||
import { RadioButtonSelect } from './shared/RadioButtonSelect.js';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import { AuthType } from '@google/gemini-cli-core';
|
||||
import { isUltraTier } from '../../utils/tierUtils.js';
|
||||
|
||||
interface ProQuotaDialogProps {
|
||||
failedModel: string;
|
||||
@@ -17,6 +18,7 @@ interface ProQuotaDialogProps {
|
||||
isTerminalQuotaError: boolean;
|
||||
isModelNotFoundError?: boolean;
|
||||
authType?: AuthType;
|
||||
tierName?: string;
|
||||
onChoice: (
|
||||
choice: 'retry_later' | 'retry_once' | 'retry_always' | 'upgrade',
|
||||
) => void;
|
||||
@@ -29,6 +31,7 @@ export function ProQuotaDialog({
|
||||
isTerminalQuotaError,
|
||||
isModelNotFoundError,
|
||||
authType,
|
||||
tierName,
|
||||
onChoice,
|
||||
}: ProQuotaDialogProps): React.JSX.Element {
|
||||
let items;
|
||||
@@ -47,6 +50,8 @@ export function ProQuotaDialog({
|
||||
},
|
||||
];
|
||||
} else if (isModelNotFoundError || isTerminalQuotaError) {
|
||||
const isUltra = isUltraTier(tierName);
|
||||
|
||||
// free users and out of quota users on G1 pro and Cloud Console gets an option to upgrade
|
||||
items = [
|
||||
{
|
||||
@@ -54,7 +59,7 @@ export function ProQuotaDialog({
|
||||
value: 'retry_always' as const,
|
||||
key: 'retry_always',
|
||||
},
|
||||
...(authType === AuthType.LOGIN_WITH_GOOGLE
|
||||
...(authType === AuthType.LOGIN_WITH_GOOGLE && !isUltra
|
||||
? [
|
||||
{
|
||||
label: 'Upgrade for higher limits',
|
||||
|
||||
@@ -8,8 +8,10 @@ import { Box, Text, useIsScreenReaderEnabled } from 'ink';
|
||||
import type React from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import { RadioButtonSelect } from './shared/RadioButtonSelect.js';
|
||||
import type { RadioSelectItem } from './shared/RadioButtonSelect.js';
|
||||
import {
|
||||
RadioButtonSelect,
|
||||
type RadioSelectItem,
|
||||
} from './shared/RadioButtonSelect.js';
|
||||
import type { FileChangeStats } from '../utils/rewindFileOps.js';
|
||||
import { useKeypress } from '../hooks/useKeypress.js';
|
||||
import { formatTimeAgo } from '../utils/formatters.js';
|
||||
|
||||
@@ -8,10 +8,9 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { act } from 'react';
|
||||
import { render } from '../../test-utils/render.js';
|
||||
import { waitFor } from '../../test-utils/async.js';
|
||||
import type { Config } from '@google/gemini-cli-core';
|
||||
import { SessionBrowser } from './SessionBrowser.js';
|
||||
import type { SessionBrowserProps } from './SessionBrowser.js';
|
||||
import type { SessionInfo } from '../../utils/sessionUtils.js';
|
||||
import { type Config } from '@google/gemini-cli-core';
|
||||
import { SessionBrowser, type SessionBrowserProps } from './SessionBrowser.js';
|
||||
import { type SessionInfo } from '../../utils/sessionUtils.js';
|
||||
|
||||
// Collect key handlers registered via useKeypress so tests can
|
||||
// simulate input without going through the full stdin pipeline.
|
||||
|
||||
@@ -8,7 +8,7 @@ import { renderWithProviders } from '../../test-utils/render.js';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { SessionSummaryDisplay } from './SessionSummaryDisplay.js';
|
||||
import * as SessionContext from '../contexts/SessionContext.js';
|
||||
import type { SessionMetrics } from '../contexts/SessionContext.js';
|
||||
import { type SessionMetrics } from '../contexts/SessionContext.js';
|
||||
import {
|
||||
ToolCallDecision,
|
||||
getShellConfiguration,
|
||||
|
||||
@@ -4,14 +4,17 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import { useState, useMemo, useCallback, useEffect } from 'react';
|
||||
import type React from 'react';
|
||||
import { Text } from 'ink';
|
||||
import { AsyncFzf } from 'fzf';
|
||||
import type { Key } from '../hooks/useKeypress.js';
|
||||
import { type Key } from '../hooks/useKeypress.js';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import type { LoadableSettingScope, Settings } from '../../config/settings.js';
|
||||
import { SettingScope } from '../../config/settings.js';
|
||||
import {
|
||||
SettingScope,
|
||||
type LoadableSettingScope,
|
||||
type Settings,
|
||||
} from '../../config/settings.js';
|
||||
import { getScopeMessageForSetting } from '../../utils/dialogScopeUtils.js';
|
||||
import {
|
||||
getDialogSettingKeys,
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { useCallback } from 'react';
|
||||
import type React from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import { useKeypress } from '../hooks/useKeypress.js';
|
||||
import { ShellExecutionService } from '@google/gemini-cli-core';
|
||||
import { keyToAnsi, type Key } from '../key/keyToAnsi.js';
|
||||
|
||||
@@ -8,7 +8,7 @@ import { renderWithProviders } from '../../test-utils/render.js';
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { StatsDisplay } from './StatsDisplay.js';
|
||||
import * as SessionContext from '../contexts/SessionContext.js';
|
||||
import type { SessionMetrics } from '../contexts/SessionContext.js';
|
||||
import { type SessionMetrics } from '../contexts/SessionContext.js';
|
||||
import {
|
||||
ToolCallDecision,
|
||||
type RetrieveUserQuotaResponse,
|
||||
|
||||
@@ -9,8 +9,10 @@ import { Box, Text, useStdout } from 'ink';
|
||||
import { ThemedGradient } from './ThemedGradient.js';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import { formatDuration, formatResetTime } from '../utils/formatters.js';
|
||||
import type { ModelMetrics } from '../contexts/SessionContext.js';
|
||||
import { useSessionStats } from '../contexts/SessionContext.js';
|
||||
import {
|
||||
useSessionStats,
|
||||
type ModelMetrics,
|
||||
} from '../contexts/SessionContext.js';
|
||||
import {
|
||||
getStatusColor,
|
||||
TOOL_SUCCESS_RATE_HIGH,
|
||||
|
||||
@@ -8,7 +8,7 @@ import { render } from '../../test-utils/render.js';
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { ToolStatsDisplay } from './ToolStatsDisplay.js';
|
||||
import * as SessionContext from '../contexts/SessionContext.js';
|
||||
import type { SessionMetrics } from '../contexts/SessionContext.js';
|
||||
import { type SessionMetrics } from '../contexts/SessionContext.js';
|
||||
import { ToolCallDecision } from '@google/gemini-cli-core';
|
||||
|
||||
// Mock the context to provide controlled data for testing
|
||||
|
||||
@@ -182,4 +182,23 @@ describe('<UserIdentity />', () => {
|
||||
expect(output).toContain('/upgrade');
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should not render /upgrade indicator for ultra tiers', async () => {
|
||||
const mockConfig = makeFakeConfig();
|
||||
vi.spyOn(mockConfig, 'getContentGeneratorConfig').mockReturnValue({
|
||||
authType: AuthType.LOGIN_WITH_GOOGLE,
|
||||
model: 'gemini-pro',
|
||||
} as unknown as ContentGeneratorConfig);
|
||||
vi.spyOn(mockConfig, 'getUserTierName').mockReturnValue('Advanced Ultra');
|
||||
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
<UserIdentity config={mockConfig} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
|
||||
const output = lastFrame();
|
||||
expect(output).toContain('Plan: Advanced Ultra');
|
||||
expect(output).not.toContain('/upgrade');
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
UserAccountManager,
|
||||
AuthType,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { isUltraTier } from '../../utils/tierUtils.js';
|
||||
|
||||
interface UserIdentityProps {
|
||||
config: Config;
|
||||
@@ -33,6 +34,8 @@ export const UserIdentity: React.FC<UserIdentityProps> = ({ config }) => {
|
||||
[config, authType],
|
||||
);
|
||||
|
||||
const isUltra = useMemo(() => isUltraTier(tierName), [tierName]);
|
||||
|
||||
if (!authType) {
|
||||
return null;
|
||||
}
|
||||
@@ -60,7 +63,7 @@ export const UserIdentity: React.FC<UserIdentityProps> = ({ config }) => {
|
||||
<Text color={theme.text.primary} wrap="truncate-end">
|
||||
<Text bold>Plan:</Text> {tierName}
|
||||
</Text>
|
||||
<Text color={theme.text.secondary}> /upgrade</Text>
|
||||
{!isUltra && <Text color={theme.text.secondary}> /upgrade</Text>}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
+12
@@ -13,6 +13,10 @@ Tips for getting started:
|
||||
2. /help for more information
|
||||
3. Ask coding questions, edit code or run commands
|
||||
4. Be specific for the best results
|
||||
╭──────────────────────────────────────────────────────────────────────────╮
|
||||
│ ? confirming_tool Confirming tool description │
|
||||
│ │
|
||||
╰──────────────────────────────────────────────────────────────────────────╯
|
||||
|
||||
Action Required (was prompted):
|
||||
|
||||
@@ -41,6 +45,10 @@ Tips for getting started:
|
||||
│ ✓ tool2 Description for tool 2 │
|
||||
│ │
|
||||
╰──────────────────────────────────────────────────────────────────────────╯
|
||||
╭──────────────────────────────────────────────────────────────────────────╮
|
||||
│ o tool3 Description for tool 3 │
|
||||
│ │
|
||||
╰──────────────────────────────────────────────────────────────────────────╯
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -97,6 +105,10 @@ Tips for getting started:
|
||||
2. /help for more information
|
||||
3. Ask coding questions, edit code or run commands
|
||||
4. Be specific for the best results
|
||||
╭──────────────────────────────────────────────────────────────────────────╮
|
||||
│ o tool3 Description for tool 3 │
|
||||
│ │
|
||||
╰──────────────────────────────────────────────────────────────────────────╯
|
||||
"
|
||||
`;
|
||||
|
||||
|
||||
@@ -5,10 +5,12 @@
|
||||
*/
|
||||
|
||||
import { renderWithProviders } from '../../../test-utils/render.js';
|
||||
import type { CompressionDisplayProps } from './CompressionMessage.js';
|
||||
import { CompressionMessage } from './CompressionMessage.js';
|
||||
import {
|
||||
CompressionMessage,
|
||||
type CompressionDisplayProps,
|
||||
} from './CompressionMessage.js';
|
||||
import { CompressionStatus } from '@google/gemini-cli-core';
|
||||
import type { CompressionProps } from '../../types.js';
|
||||
import { type CompressionProps } from '../../types.js';
|
||||
import { describe, it, expect } from 'vitest';
|
||||
|
||||
describe('<CompressionMessage />', () => {
|
||||
|
||||
@@ -159,4 +159,22 @@ describe('ThinkingMessage', () => {
|
||||
await expect(renderResult).toMatchSvgSnapshot();
|
||||
renderResult.unmount();
|
||||
});
|
||||
|
||||
it('filters out progress dots and empty lines', async () => {
|
||||
const renderResult = renderWithProviders(
|
||||
<ThinkingMessage
|
||||
thought={{ subject: '...', description: 'Thinking\n.\n..\n...\nDone' }}
|
||||
terminalWidth={80}
|
||||
isFirstThinking={true}
|
||||
/>,
|
||||
);
|
||||
await renderResult.waitUntilReady();
|
||||
|
||||
const output = renderResult.lastFrame();
|
||||
expect(output).toContain('Thinking');
|
||||
expect(output).toContain('Done');
|
||||
expect(renderResult.lastFrame()).toMatchSnapshot();
|
||||
await expect(renderResult).toMatchSvgSnapshot();
|
||||
renderResult.unmount();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,20 +23,26 @@ function normalizeThoughtLines(thought: ThoughtSummary): string[] {
|
||||
const subject = normalizeEscapedNewlines(thought.subject).trim();
|
||||
const description = normalizeEscapedNewlines(thought.description).trim();
|
||||
|
||||
if (!subject && !description) {
|
||||
return [];
|
||||
const isNoise = (text: string) => {
|
||||
const trimmed = text.trim();
|
||||
return !trimmed || /^\.+$/.test(trimmed);
|
||||
};
|
||||
|
||||
const lines: string[] = [];
|
||||
|
||||
if (subject && !isNoise(subject)) {
|
||||
lines.push(subject);
|
||||
}
|
||||
|
||||
if (!subject) {
|
||||
return description.split('\n');
|
||||
if (description) {
|
||||
const descriptionLines = description
|
||||
.split('\n')
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => !isNoise(line));
|
||||
lines.push(...descriptionLines);
|
||||
}
|
||||
|
||||
if (!description) {
|
||||
return [subject];
|
||||
}
|
||||
|
||||
const bodyLines = description.split('\n');
|
||||
return [subject, ...bodyLines];
|
||||
return lines;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,11 +8,9 @@ import { render } from '../../../test-utils/render.js';
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { Box } from 'ink';
|
||||
import { TodoTray } from './Todo.js';
|
||||
import type { Todo } from '@google/gemini-cli-core';
|
||||
import type { UIState } from '../../contexts/UIStateContext.js';
|
||||
import { UIStateContext } from '../../contexts/UIStateContext.js';
|
||||
import type { HistoryItem } from '../../types.js';
|
||||
import { CoreToolCallStatus } from '@google/gemini-cli-core';
|
||||
import { CoreToolCallStatus, type Todo } from '@google/gemini-cli-core';
|
||||
import { UIStateContext, type UIState } from '../../contexts/UIStateContext.js';
|
||||
import { type HistoryItem } from '../../types.js';
|
||||
|
||||
const createTodoHistoryItem = (todos: Todo[]): HistoryItem =>
|
||||
({
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { type TodoList } from '@google/gemini-cli-core';
|
||||
import { useUIState } from '../../contexts/UIStateContext.js';
|
||||
import { useMemo } from 'react';
|
||||
import type { HistoryItemToolGroup } from '../../types.js';
|
||||
import { Checklist } from '../Checklist.js';
|
||||
import type { ChecklistItemData } from '../ChecklistItem.js';
|
||||
|
||||
@@ -18,9 +18,11 @@ import {
|
||||
hasRedirection,
|
||||
debugLogger,
|
||||
} from '@google/gemini-cli-core';
|
||||
import type { RadioSelectItem } from '../shared/RadioButtonSelect.js';
|
||||
import { useToolActions } from '../../contexts/ToolActionsContext.js';
|
||||
import { RadioButtonSelect } from '../shared/RadioButtonSelect.js';
|
||||
import {
|
||||
RadioButtonSelect,
|
||||
type RadioSelectItem,
|
||||
} from '../shared/RadioButtonSelect.js';
|
||||
import { MaxSizedBox, MINIMUM_MAX_HEIGHT } from '../shared/MaxSizedBox.js';
|
||||
import {
|
||||
sanitizeForDisplay,
|
||||
|
||||
@@ -118,9 +118,10 @@ describe('<ToolGroupMessage />', () => {
|
||||
{ config: baseMockConfig, settings: fullVerbositySettings },
|
||||
);
|
||||
|
||||
// Should render nothing because all tools in the group are confirming
|
||||
// Should now render confirming tools
|
||||
await waitUntilReady();
|
||||
expect(lastFrame({ allowEmpty: true })).toBe('');
|
||||
const output = lastFrame();
|
||||
expect(output).toContain('test-tool');
|
||||
unmount();
|
||||
});
|
||||
|
||||
@@ -162,11 +163,11 @@ describe('<ToolGroupMessage />', () => {
|
||||
},
|
||||
},
|
||||
);
|
||||
// pending-tool should be hidden
|
||||
// pending-tool should now be visible
|
||||
await waitUntilReady();
|
||||
const output = lastFrame();
|
||||
expect(output).toContain('successful-tool');
|
||||
expect(output).not.toContain('pending-tool');
|
||||
expect(output).toContain('pending-tool');
|
||||
expect(output).toContain('error-tool');
|
||||
expect(output).toMatchSnapshot();
|
||||
unmount();
|
||||
@@ -280,12 +281,12 @@ describe('<ToolGroupMessage />', () => {
|
||||
},
|
||||
},
|
||||
);
|
||||
// write_file (Pending) should be hidden
|
||||
// write_file (Pending) should now be visible
|
||||
await waitUntilReady();
|
||||
const output = lastFrame();
|
||||
expect(output).toContain('read_file');
|
||||
expect(output).toContain('run_shell_command');
|
||||
expect(output).not.toContain('write_file');
|
||||
expect(output).toContain('write_file');
|
||||
expect(output).toMatchSnapshot();
|
||||
unmount();
|
||||
});
|
||||
@@ -841,7 +842,7 @@ describe('<ToolGroupMessage />', () => {
|
||||
);
|
||||
|
||||
await waitUntilReady();
|
||||
expect(lastFrame({ allowEmpty: true })).toBe('');
|
||||
expect(lastFrame({ allowEmpty: true })).not.toBe('');
|
||||
unmount();
|
||||
});
|
||||
|
||||
|
||||
@@ -110,10 +110,11 @@ export const ToolGroupMessage: React.FC<ToolGroupMessageProps> = ({
|
||||
() =>
|
||||
toolCalls.filter((t) => {
|
||||
const displayStatus = mapCoreStatusToDisplayStatus(t.status);
|
||||
return (
|
||||
displayStatus !== ToolCallStatus.Pending &&
|
||||
displayStatus !== ToolCallStatus.Confirming
|
||||
);
|
||||
// We used to filter out Pending and Confirming statuses here to avoid
|
||||
// duplication with the Global Queue, but this causes tools to appear to
|
||||
// "vanish" from the context after approval.
|
||||
// We now allow them to be visible here as well.
|
||||
return displayStatus !== ToolCallStatus.Canceled;
|
||||
}),
|
||||
|
||||
[toolCalls],
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import type { ToolMessageProps } from './ToolMessage.js';
|
||||
import { ToolMessage } from './ToolMessage.js';
|
||||
import { ToolMessage, type ToolMessageProps } from './ToolMessage.js';
|
||||
import { StreamingState } from '../../types.js';
|
||||
import { StreamingContext } from '../../contexts/StreamingContext.js';
|
||||
import { renderWithProviders } from '../../../test-utils/render.js';
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="88" viewBox="0 0 920 88">
|
||||
<style>
|
||||
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
|
||||
</style>
|
||||
<rect width="920" height="88" fill="#000000" />
|
||||
<g transform="translate(10, 10)">
|
||||
<text x="0" y="2" fill="#ffffff" textLength="117" lengthAdjust="spacingAndGlyphs" font-style="italic"> Thinking... </text>
|
||||
<text x="9" y="19" fill="#afafaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="9" y="36" fill="#afafaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="27" y="36" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs" font-weight="bold" font-style="italic">Thinking</text>
|
||||
<text x="9" y="53" fill="#afafaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="27" y="53" fill="#afafaf" textLength="36" lengthAdjust="spacingAndGlyphs" font-style="italic">Done</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1016 B |
@@ -1,5 +1,20 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`ThinkingMessage > filters out progress dots and empty lines 1`] = `
|
||||
" Thinking...
|
||||
│
|
||||
│ Thinking
|
||||
│ Done
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ThinkingMessage > filters out progress dots and empty lines 2`] = `
|
||||
" Thinking...
|
||||
│
|
||||
│ Thinking
|
||||
│ Done"
|
||||
`;
|
||||
|
||||
exports[`ThinkingMessage > normalizes escaped newline tokens 1`] = `
|
||||
" Thinking...
|
||||
│
|
||||
|
||||
@@ -74,6 +74,10 @@ exports[`<ToolGroupMessage /> > Golden Snapshots > renders mixed tool calls incl
|
||||
│ ⊶ run_shell_command Run command │
|
||||
│ │
|
||||
│ Test result │
|
||||
│ │
|
||||
│ o write_file Write to file │
|
||||
│ │
|
||||
│ Test result │
|
||||
╰──────────────────────────────────────────────────────────────────────────╯
|
||||
"
|
||||
`;
|
||||
@@ -84,6 +88,10 @@ exports[`<ToolGroupMessage /> > Golden Snapshots > renders multiple tool calls w
|
||||
│ │
|
||||
│ Test result │
|
||||
│ │
|
||||
│ o pending-tool This tool is pending │
|
||||
│ │
|
||||
│ Test result │
|
||||
│ │
|
||||
│ x error-tool This tool failed │
|
||||
│ │
|
||||
│ Test result │
|
||||
|
||||
@@ -8,9 +8,10 @@ import type React from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Text, Box } from 'ink';
|
||||
import { theme } from '../../semantic-colors.js';
|
||||
import { useSelectionList } from '../../hooks/useSelectionList.js';
|
||||
|
||||
import type { SelectionListItem } from '../../hooks/useSelectionList.js';
|
||||
import {
|
||||
useSelectionList,
|
||||
type SelectionListItem,
|
||||
} from '../../hooks/useSelectionList.js';
|
||||
|
||||
export interface RenderItemContext {
|
||||
isSelected: boolean;
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import type React from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { Colors } from '../../colors.js';
|
||||
import type { SettingEnumOption } from '../../../config/settingsSchema.js';
|
||||
|
||||
@@ -6,9 +6,8 @@
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { renderWithProviders } from '../../../test-utils/render.js';
|
||||
import type { Text } from 'ink';
|
||||
import { Box } from 'ink';
|
||||
import type React from 'react';
|
||||
import { Box, type Text } from 'ink';
|
||||
import {
|
||||
RadioButtonSelect,
|
||||
type RadioSelectItem,
|
||||
|
||||
@@ -6,13 +6,11 @@
|
||||
|
||||
import type React from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import type { Key } from '../../hooks/useKeypress.js';
|
||||
import { Text, Box } from 'ink';
|
||||
import { useKeypress } from '../../hooks/useKeypress.js';
|
||||
import { useKeypress, type Key } from '../../hooks/useKeypress.js';
|
||||
import chalk from 'chalk';
|
||||
import { theme } from '../../semantic-colors.js';
|
||||
import type { TextBuffer } from './text-buffer.js';
|
||||
import { expandPastePlaceholders } from './text-buffer.js';
|
||||
import { expandPastePlaceholders, type TextBuffer } from './text-buffer.js';
|
||||
import { cpSlice, cpIndexToOffset } from '../../utils/textUtils.js';
|
||||
import { Command } from '../../key/keyMatchers.js';
|
||||
import { useKeyMatchers } from '../../hooks/useKeyMatchers.js';
|
||||
|
||||
@@ -7,8 +7,12 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import Spinner from 'ink-spinner';
|
||||
import type { Config } from '@google/gemini-cli-core';
|
||||
import { debugLogger, spawnAsync, LlmRole } from '@google/gemini-cli-core';
|
||||
import {
|
||||
debugLogger,
|
||||
spawnAsync,
|
||||
LlmRole,
|
||||
type Config,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { useKeypress } from '../../hooks/useKeypress.js';
|
||||
import { Command } from '../../key/keyMatchers.js';
|
||||
import { useKeyMatchers } from '../../hooks/useKeyMatchers.js';
|
||||
|
||||
@@ -7,8 +7,12 @@
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import Spinner from 'ink-spinner';
|
||||
import type { Config } from '@google/gemini-cli-core';
|
||||
import { debugLogger, spawnAsync, LlmRole } from '@google/gemini-cli-core';
|
||||
import {
|
||||
debugLogger,
|
||||
spawnAsync,
|
||||
LlmRole,
|
||||
type Config,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { useKeypress } from '../../hooks/useKeypress.js';
|
||||
import { Command } from '../../key/keyMatchers.js';
|
||||
import { TextInput } from '../shared/TextInput.js';
|
||||
|
||||
@@ -8,7 +8,6 @@ import type React from 'react';
|
||||
import { useMemo, useCallback, useState } from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import type { RegistryExtension } from '../../../config/extensionRegistryClient.js';
|
||||
|
||||
import {
|
||||
SearchableList,
|
||||
type GenericListItem,
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type { MCPServerConfig } from '@google/gemini-cli-core';
|
||||
import { MCPServerStatus } from '@google/gemini-cli-core';
|
||||
import { MCPServerStatus, type MCPServerConfig } from '@google/gemini-cli-core';
|
||||
import { Box, Text } from 'ink';
|
||||
import type React from 'react';
|
||||
import { MAX_MCP_RESOURCES_TO_SHOW } from '../../constants.js';
|
||||
|
||||
@@ -9,14 +9,13 @@ import type React from 'react';
|
||||
import { act } from 'react';
|
||||
import { renderHook } from '../../test-utils/render.js';
|
||||
import { waitFor } from '../../test-utils/async.js';
|
||||
import type { Mock } from 'vitest';
|
||||
import { vi, afterAll, beforeAll } from 'vitest';
|
||||
import type { Key } from './KeypressContext.js';
|
||||
import { vi, afterAll, beforeAll, type Mock } from 'vitest';
|
||||
import {
|
||||
KeypressProvider,
|
||||
useKeypressContext,
|
||||
ESC_TIMEOUT,
|
||||
FAST_RETURN_TIMEOUT,
|
||||
type Key,
|
||||
} from './KeypressContext.js';
|
||||
import { terminalCapabilityManager } from '../utils/terminalCapabilityManager.js';
|
||||
import { useStdin } from 'ink';
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
*/
|
||||
|
||||
import { renderHook } from '../../test-utils/render.js';
|
||||
import type React from 'react';
|
||||
import { act } from 'react';
|
||||
import { MouseProvider, useMouseContext, useMouse } from './MouseContext.js';
|
||||
import { vi, type Mock } from 'vitest';
|
||||
import type React from 'react';
|
||||
import { useStdin } from 'ink';
|
||||
import { EventEmitter } from 'node:events';
|
||||
import { appEvents, AppEvent } from '../../utils/events.js';
|
||||
|
||||
@@ -4,12 +4,13 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { type MutableRefObject, Component, type ReactNode } from 'react';
|
||||
import { type MutableRefObject, Component, type ReactNode, act } from 'react';
|
||||
import { render } from '../../test-utils/render.js';
|
||||
|
||||
import { act } from 'react';
|
||||
import type { SessionMetrics } from './SessionContext.js';
|
||||
import { SessionStatsProvider, useSessionStats } from './SessionContext.js';
|
||||
import {
|
||||
SessionStatsProvider,
|
||||
useSessionStats,
|
||||
type SessionMetrics,
|
||||
} from './SessionContext.js';
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { uiTelemetryService } from '@google/gemini-cli-core';
|
||||
|
||||
|
||||
@@ -5,17 +5,16 @@
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import { Component, type ReactNode } from 'react';
|
||||
import { Component, type ReactNode, act } from 'react';
|
||||
import { renderHook, render } from '../../test-utils/render.js';
|
||||
import { act } from 'react';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { SettingsContext, useSettingsStore } from './SettingsContext.js';
|
||||
import {
|
||||
type LoadedSettings,
|
||||
SettingScope,
|
||||
createTestMergedSettings,
|
||||
type LoadedSettings,
|
||||
type LoadedSettingsSnapshot,
|
||||
type SettingsFile,
|
||||
createTestMergedSettings,
|
||||
} from '../../config/settings.js';
|
||||
|
||||
const createMockSettingsFile = (path: string): SettingsFile => ({
|
||||
|
||||
@@ -4,10 +4,16 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type { Mock } from 'vitest';
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import {
|
||||
describe,
|
||||
it,
|
||||
expect,
|
||||
vi,
|
||||
beforeEach,
|
||||
afterEach,
|
||||
type Mock,
|
||||
} from 'vitest';
|
||||
import { handleAtCommand } from './atCommandProcessor.js';
|
||||
import type { Config, DiscoveredMCPResource } from '@google/gemini-cli-core';
|
||||
import {
|
||||
FileDiscoveryService,
|
||||
GlobTool,
|
||||
@@ -18,6 +24,8 @@ import {
|
||||
GEMINI_IGNORE_FILE_NAME,
|
||||
// DEFAULT_FILE_EXCLUDES,
|
||||
CoreToolCallStatus,
|
||||
type Config,
|
||||
type DiscoveredMCPResource,
|
||||
} from '@google/gemini-cli-core';
|
||||
import * as core from '@google/gemini-cli-core';
|
||||
import * as os from 'node:os';
|
||||
|
||||
@@ -80,7 +80,7 @@ export const useShellCommandProcessor = (
|
||||
setShellInputFocused: (value: boolean) => void,
|
||||
terminalWidth?: number,
|
||||
terminalHeight?: number,
|
||||
activeToolPtyId?: number,
|
||||
activeBackgroundExecutionId?: number,
|
||||
isWaitingForConfirmation?: boolean,
|
||||
) => {
|
||||
const [state, dispatch] = useReducer(shellReducer, initialState);
|
||||
@@ -103,7 +103,8 @@ export const useShellCommandProcessor = (
|
||||
}
|
||||
const m = manager.current;
|
||||
|
||||
const activePtyId = state.activeShellPtyId || activeToolPtyId;
|
||||
const activePtyId =
|
||||
state.activeShellPtyId ?? activeBackgroundExecutionId ?? undefined;
|
||||
|
||||
useEffect(() => {
|
||||
const isForegroundActive = !!activePtyId || !!isWaitingForConfirmation;
|
||||
@@ -191,7 +192,8 @@ export const useShellCommandProcessor = (
|
||||
]);
|
||||
|
||||
const backgroundCurrentShell = useCallback(() => {
|
||||
const pidToBackground = state.activeShellPtyId || activeToolPtyId;
|
||||
const pidToBackground =
|
||||
state.activeShellPtyId ?? activeBackgroundExecutionId;
|
||||
if (pidToBackground) {
|
||||
ShellExecutionService.background(pidToBackground);
|
||||
m.backgroundedPids.add(pidToBackground);
|
||||
@@ -202,7 +204,7 @@ export const useShellCommandProcessor = (
|
||||
m.restoreTimeout = null;
|
||||
}
|
||||
}
|
||||
}, [state.activeShellPtyId, activeToolPtyId, m]);
|
||||
}, [state.activeShellPtyId, activeBackgroundExecutionId, m]);
|
||||
|
||||
const dismissBackgroundShell = useCallback(
|
||||
async (pid: number) => {
|
||||
|
||||
@@ -9,19 +9,18 @@ import { act } from 'react';
|
||||
import { renderHook } from '../../test-utils/render.js';
|
||||
import { waitFor } from '../../test-utils/async.js';
|
||||
import { useSlashCommandProcessor } from './slashCommandProcessor.js';
|
||||
import type { SlashCommand } from '../commands/types.js';
|
||||
import { CommandKind } from '../commands/types.js';
|
||||
import { CommandKind, type SlashCommand } from '../commands/types.js';
|
||||
import type { LoadedSettings } from '../../config/settings.js';
|
||||
import { MessageType } from '../types.js';
|
||||
import { BuiltinCommandLoader } from '../../services/BuiltinCommandLoader.js';
|
||||
import { FileCommandLoader } from '../../services/FileCommandLoader.js';
|
||||
import { McpPromptLoader } from '../../services/McpPromptLoader.js';
|
||||
import {
|
||||
type GeminiClient,
|
||||
SlashCommandStatus,
|
||||
MCPDiscoveryState,
|
||||
makeFakeConfig,
|
||||
coreEvents,
|
||||
type GeminiClient,
|
||||
} from '@google/gemini-cli-core';
|
||||
|
||||
const {
|
||||
|
||||
@@ -17,10 +17,12 @@ import { act } from 'react';
|
||||
import { renderHook } from '../../test-utils/render.js';
|
||||
import { useApprovalModeIndicator } from './useApprovalModeIndicator.js';
|
||||
|
||||
import { Config, ApprovalMode } from '@google/gemini-cli-core';
|
||||
import type { Config as ActualConfigType } from '@google/gemini-cli-core';
|
||||
import type { Key } from './useKeypress.js';
|
||||
import { useKeypress } from './useKeypress.js';
|
||||
import {
|
||||
Config,
|
||||
ApprovalMode,
|
||||
type Config as ActualConfigType,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { useKeypress, type Key } from './useKeypress.js';
|
||||
import { MessageType } from '../types.js';
|
||||
|
||||
vi.mock('./useKeypress.js');
|
||||
|
||||
@@ -13,8 +13,7 @@ import {
|
||||
import { useKeypress } from './useKeypress.js';
|
||||
import { Command } from '../key/keyMatchers.js';
|
||||
import { useKeyMatchers } from './useKeyMatchers.js';
|
||||
import type { HistoryItemWithoutId } from '../types.js';
|
||||
import { MessageType } from '../types.js';
|
||||
import { MessageType, type HistoryItemWithoutId } from '../types.js';
|
||||
|
||||
export interface UseApprovalModeIndicatorArgs {
|
||||
config: Config;
|
||||
|
||||
@@ -10,14 +10,18 @@ import * as path from 'node:path';
|
||||
import { renderHook } from '../../test-utils/render.js';
|
||||
import { waitFor } from '../../test-utils/async.js';
|
||||
import { useAtCompletion } from './useAtCompletion.js';
|
||||
import type { Config, FileSearch } from '@google/gemini-cli-core';
|
||||
import {
|
||||
FileSearchFactory,
|
||||
FileDiscoveryService,
|
||||
escapePath,
|
||||
type Config,
|
||||
type FileSearch,
|
||||
} from '@google/gemini-cli-core';
|
||||
import type { FileSystemStructure } from '@google/gemini-cli-test-utils';
|
||||
import { createTmpDir, cleanupTmpDir } from '@google/gemini-cli-test-utils';
|
||||
import {
|
||||
createTmpDir,
|
||||
cleanupTmpDir,
|
||||
type FileSystemStructure,
|
||||
} from '@google/gemini-cli-test-utils';
|
||||
import type { Suggestion } from '../components/SuggestionsDisplay.js';
|
||||
|
||||
// Test harness to capture the state from the hook's callbacks.
|
||||
|
||||
@@ -7,14 +7,17 @@
|
||||
import { useEffect, useReducer, useRef } from 'react';
|
||||
import { setTimeout as setTimeoutPromise } from 'node:timers/promises';
|
||||
import * as path from 'node:path';
|
||||
import type { Config, FileSearch } from '@google/gemini-cli-core';
|
||||
import {
|
||||
FileSearchFactory,
|
||||
escapePath,
|
||||
FileDiscoveryService,
|
||||
type Config,
|
||||
type FileSearch,
|
||||
} from '@google/gemini-cli-core';
|
||||
import type { Suggestion } from '../components/SuggestionsDisplay.js';
|
||||
import { MAX_SUGGESTIONS_TO_SHOW } from '../components/SuggestionsDisplay.js';
|
||||
import {
|
||||
MAX_SUGGESTIONS_TO_SHOW,
|
||||
type Suggestion,
|
||||
} from '../components/SuggestionsDisplay.js';
|
||||
import { CommandKind } from '../commands/types.js';
|
||||
import { AsyncFzf } from 'fzf';
|
||||
|
||||
|
||||
@@ -24,10 +24,14 @@ import type { CommandContext } from '../commands/types.js';
|
||||
import type { Config } from '@google/gemini-cli-core';
|
||||
import { useTextBuffer } from '../components/shared/text-buffer.js';
|
||||
import type { Suggestion } from '../components/SuggestionsDisplay.js';
|
||||
import type { UseAtCompletionProps } from './useAtCompletion.js';
|
||||
import { useAtCompletion } from './useAtCompletion.js';
|
||||
import type { UseSlashCompletionProps } from './useSlashCompletion.js';
|
||||
import { useSlashCompletion } from './useSlashCompletion.js';
|
||||
import {
|
||||
useAtCompletion,
|
||||
type UseAtCompletionProps,
|
||||
} from './useAtCompletion.js';
|
||||
import {
|
||||
useSlashCompletion,
|
||||
type UseSlashCompletionProps,
|
||||
} from './useSlashCompletion.js';
|
||||
import { useShellCompletion } from './useShellCompletion.js';
|
||||
|
||||
vi.mock('./useAtCompletion', () => ({
|
||||
|
||||
@@ -14,10 +14,10 @@ import { toCodePoints } from '../utils/textUtils.js';
|
||||
import { useAtCompletion } from './useAtCompletion.js';
|
||||
import { useSlashCompletion } from './useSlashCompletion.js';
|
||||
import { useShellCompletion } from './useShellCompletion.js';
|
||||
import type { PromptCompletion } from './usePromptCompletion.js';
|
||||
import {
|
||||
usePromptCompletion,
|
||||
PROMPT_COMPLETION_MIN_LENGTH,
|
||||
type PromptCompletion,
|
||||
} from './usePromptCompletion.js';
|
||||
import type { Config } from '@google/gemini-cli-core';
|
||||
import { useCompletion } from './useCompletion.js';
|
||||
|
||||
@@ -6,8 +6,10 @@
|
||||
|
||||
import { useState, useCallback } from 'react';
|
||||
|
||||
import type { Suggestion } from '../components/SuggestionsDisplay.js';
|
||||
import { MAX_SUGGESTIONS_TO_SHOW } from '../components/SuggestionsDisplay.js';
|
||||
import {
|
||||
MAX_SUGGESTIONS_TO_SHOW,
|
||||
type Suggestion,
|
||||
} from '../components/SuggestionsDisplay.js';
|
||||
|
||||
export interface UseCompletionReturn {
|
||||
suggestions: Suggestion[];
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user