Compare commits
60 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 90fda1400e | |||
| 4ebc43bc66 | |||
| 34b4f1c6e4 | |||
| e77b22e638 | |||
| 1b3e7d674f | |||
| e7f8d9cf1a | |||
| 651ad63ed6 | |||
| cbacdc67d0 | |||
| 7e1938c1bc | |||
| b9f1d832c8 | |||
| 47c5d25d93 | |||
| 28efab483f | |||
| 9fd92c0eea | |||
| 16768c08f2 | |||
| 1aa798dd18 | |||
| f96d5f98fe | |||
| 3c5b5db034 | |||
| 986293bd38 | |||
| adf7b3b717 | |||
| 9637fb3990 | |||
| 06fcdc231c | |||
| d29da15427 | |||
| ab3075feb9 | |||
| 5588000e93 | |||
| 68fef8745e | |||
| e432f7c009 | |||
| 846051f716 | |||
| 1c22c5b37b | |||
| 1762c9c509 | |||
| 0025978d76 | |||
| 4c5e887732 | |||
| 83096c68b0 | |||
| d2b775f9a7 | |||
| 0a8da988ed | |||
| 984f02c180 | |||
| df67f973ed | |||
| 7872d6d7fe | |||
| e116aa34f4 | |||
| ad98294352 | |||
| 2353a6d253 | |||
| 8ac560d2c9 | |||
| c6a9d3de13 | |||
| 8f131ffef7 | |||
| 70f6d6a992 | |||
| c96cb09e09 | |||
| 15298b28c2 | |||
| 7311e242ec | |||
| 21a3925f99 | |||
| ec35ebbe57 | |||
| 65024d4538 | |||
| 4fb3790051 | |||
| 4a040931ef | |||
| e74efc5c0b | |||
| d5a5995281 | |||
| 893ae4d29a | |||
| 370c45de67 | |||
| a93a1ebd65 | |||
| 3f12c1d7c7 | |||
| 61719a3926 | |||
| beaa2a968b |
@@ -33,6 +33,35 @@ evaluation.
|
|||||||
- **Warning**: Do not lose test fidelity by making prompts too direct/easy.
|
- **Warning**: Do not lose test fidelity by making prompts too direct/easy.
|
||||||
- **Primary Fix Trigger**: Adjust tool descriptions, system prompts
|
- **Primary Fix Trigger**: Adjust tool descriptions, system prompts
|
||||||
(`snippets.ts`), or **modules that contribute to the prompt template**.
|
(`snippets.ts`), or **modules that contribute to the prompt template**.
|
||||||
|
- Fixes should generally try to improve the prompt
|
||||||
|
`@packages/core/src/prompts/snippets.ts` first.
|
||||||
|
- **Instructional Generality**: Changes to the system prompt should aim to
|
||||||
|
be as general as possible while still accomplishing the goal. Specificity
|
||||||
|
should be added only as needed.
|
||||||
|
- **Principle**: Instead of creating "forbidden lists" for specific syntax
|
||||||
|
(e.g., "Don't use `Object.create()`"), formulate a broader engineering
|
||||||
|
principle that covers the underlying issue (e.g., "Prioritize explicit
|
||||||
|
composition over hidden prototype manipulation"). This improves
|
||||||
|
steerability across a wider range of similar scenarios.
|
||||||
|
- _Low Specificity_: "Follow ecosystem best practices"
|
||||||
|
- _Medium Specificity_: "Utilize OOP and functional best practices, as
|
||||||
|
applicable"
|
||||||
|
- _High Specificity_: Provide ecosystem-specific hints as examples of a
|
||||||
|
broader principle rather than direct instructions. e.g., "NEVER use
|
||||||
|
hacks like bypassing the type system or employing 'hidden' logic (e.g.:
|
||||||
|
reflection, prototype manipulation). Instead, use explicit and idiomatic
|
||||||
|
language features (e.g.: type guards, explicit class instantiation, or
|
||||||
|
object spread) that maintain structural integrity."
|
||||||
|
- **Prompt Simplification**: Once the test is passing, use `ask_user` to
|
||||||
|
determine if prompt simplification is desired.
|
||||||
|
- **Criteria**: Simplification should be attempted only if there are
|
||||||
|
related clauses that can be de-duplicated or reparented under a single
|
||||||
|
heading.
|
||||||
|
- **Verification**: As part of simplification, you MUST identify and run
|
||||||
|
any behavioral eval tests that might be affected by the changes to
|
||||||
|
ensure no regressions are introduced.
|
||||||
|
- Test fixes should not "cheat" by changing a test's `GEMINI.md` file or by
|
||||||
|
updating the test's prompt to instruct it to not repro the bug.
|
||||||
- **Warning**: Prompts have multiple configurations; ensure your fix targets
|
- **Warning**: Prompts have multiple configurations; ensure your fix targets
|
||||||
the correct config for the model in question.
|
the correct config for the model in question.
|
||||||
4. **Architecture Options**: If prompt or instruction tuning triggers no
|
4. **Architecture Options**: If prompt or instruction tuning triggers no
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
name: 'Evals: PR Evaluation & Regression'
|
name: 'Evals: PR Evaluation & Regression'
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request_target:
|
||||||
types: ['opened', 'synchronize', 'reopened', 'ready_for_review']
|
types: ['opened', 'synchronize', 'reopened', 'ready_for_review']
|
||||||
paths:
|
paths:
|
||||||
- 'packages/core/src/prompts/**'
|
- 'packages/core/src/prompts/**'
|
||||||
@@ -23,13 +23,73 @@ permissions:
|
|||||||
actions: 'read'
|
actions: 'read'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
detect-changes:
|
||||||
|
name: 'Detect Steering Changes'
|
||||||
|
runs-on: 'gemini-cli-ubuntu-16-core'
|
||||||
|
# Security: pull_request_target allows secrets, so we must gate carefully.
|
||||||
|
# Detection should not run code from the fork.
|
||||||
|
if: "github.repository == 'google-gemini/gemini-cli' && github.event.pull_request.draft == false"
|
||||||
|
outputs:
|
||||||
|
SHOULD_RUN: '${{ steps.detect.outputs.SHOULD_RUN }}'
|
||||||
|
STEERING_DETECTED: '${{ steps.detect.outputs.STEERING_DETECTED }}'
|
||||||
|
steps:
|
||||||
|
- name: 'Checkout'
|
||||||
|
uses: 'actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955' # ratchet:actions/checkout@v5
|
||||||
|
with:
|
||||||
|
# Check out the trusted code from main for detection
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: 'Detect Steering Changes'
|
||||||
|
id: 'detect'
|
||||||
|
env:
|
||||||
|
# Use the PR's head SHA for comparison without checking it out
|
||||||
|
PR_HEAD_SHA: '${{ github.event.pull_request.head.sha }}'
|
||||||
|
run: |
|
||||||
|
# Fetch the fork's PR branch for analysis
|
||||||
|
git fetch origin pull/${{ github.event.pull_request.number }}/head:pr-head
|
||||||
|
|
||||||
|
# Run the trusted script from main
|
||||||
|
SHOULD_RUN=$(node scripts/changed_prompt.js)
|
||||||
|
STEERING_DETECTED=$(node scripts/changed_prompt.js --steering-only)
|
||||||
|
echo "SHOULD_RUN=$SHOULD_RUN" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "STEERING_DETECTED=$STEERING_DETECTED" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: 'Notify Approval Required'
|
||||||
|
if: "steps.detect.outputs.SHOULD_RUN == 'true'"
|
||||||
|
env:
|
||||||
|
GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
|
||||||
|
run: |
|
||||||
|
RUN_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||||
|
COMMENT_BODY="### 🛑 Action Required: Evaluation Approval
|
||||||
|
|
||||||
|
Steering changes have been detected in this PR. To prevent regressions, a maintainer must approve the evaluation run before this PR can be merged.
|
||||||
|
|
||||||
|
**Maintainers:**
|
||||||
|
1. Go to the [**Workflow Run Summary**]($RUN_URL).
|
||||||
|
2. Click the yellow **'Review deployments'** button.
|
||||||
|
3. Select the **'eval-gate'** environment and click **'Approve'**.
|
||||||
|
|
||||||
|
Once approved, the evaluation results will be posted here automatically.
|
||||||
|
|
||||||
|
<!-- eval-approval-notification -->"
|
||||||
|
|
||||||
|
# Check if comment already exists to avoid spamming
|
||||||
|
COMMENT_ID=$(gh pr view ${{ github.event.pull_request.number }} --json comments --jq '.comments[] | select(.body | contains("<!-- eval-approval-notification -->")) | .url' | grep -oE "[0-9]+$" | head -n 1)
|
||||||
|
|
||||||
|
if [ -z "$COMMENT_ID" ]; then
|
||||||
|
gh pr comment ${{ github.event.pull_request.number }} --body "$COMMENT_BODY"
|
||||||
|
else
|
||||||
|
echo "Updating existing notification comment $COMMENT_ID..."
|
||||||
|
gh api -X PATCH "repos/${{ github.repository }}/issues/comments/$COMMENT_ID" -F body="$COMMENT_BODY"
|
||||||
|
fi
|
||||||
|
|
||||||
pr-evaluation:
|
pr-evaluation:
|
||||||
name: 'Evaluate Steering & Regressions'
|
name: 'Evaluate Steering & Regressions'
|
||||||
|
needs: 'detect-changes'
|
||||||
|
if: "needs.detect-changes.outputs.SHOULD_RUN == 'true'"
|
||||||
|
# Manual approval gate via environment
|
||||||
|
environment: 'eval-gate'
|
||||||
runs-on: 'gemini-cli-ubuntu-16-core'
|
runs-on: 'gemini-cli-ubuntu-16-core'
|
||||||
if: "github.repository == 'google-gemini/gemini-cli' && (github.event_name != 'pull_request' || (github.event.pull_request.draft == false && github.event.pull_request.head.repo.full_name == github.repository))"
|
|
||||||
# External contributors' PRs will wait for approval in this environment
|
|
||||||
environment: |-
|
|
||||||
${{ (github.event.pull_request.head.repo.full_name == github.repository) && 'internal' || 'external-evals' }}
|
|
||||||
env:
|
env:
|
||||||
# CENTRALIZED MODEL LIST
|
# CENTRALIZED MODEL LIST
|
||||||
MODEL_LIST: 'gemini-3-flash-preview'
|
MODEL_LIST: 'gemini-3-flash-preview'
|
||||||
@@ -38,8 +98,26 @@ jobs:
|
|||||||
- name: 'Checkout'
|
- name: 'Checkout'
|
||||||
uses: 'actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955' # ratchet:actions/checkout@v5
|
uses: 'actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955' # ratchet:actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
|
# Check out the fork's PR code for the actual evaluation
|
||||||
|
# This only runs AFTER manual approval
|
||||||
|
ref: '${{ github.event.pull_request.head.sha }}'
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: 'Remove Approval Notification'
|
||||||
|
# Run even if other steps fail, to ensure we clean up the "Action Required" message
|
||||||
|
if: 'always()'
|
||||||
|
env:
|
||||||
|
GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
|
||||||
|
PR_NUMBER: '${{ github.event.pull_request.number }}'
|
||||||
|
run: |
|
||||||
|
echo "Debug: PR_NUMBER is '$PR_NUMBER'"
|
||||||
|
# Search for the notification comment by its hidden tag
|
||||||
|
COMMENT_ID=$(gh pr view "$PR_NUMBER" --json comments --jq '.comments[] | select(.body | contains("<!-- eval-approval-notification -->")) | .url' | grep -oE "[0-9]+$" | head -n 1)
|
||||||
|
if [ -n "$COMMENT_ID" ]; then
|
||||||
|
echo "Removing notification comment $COMMENT_ID now that run is approved..."
|
||||||
|
gh api -X DELETE "repos/${{ github.repository }}/issues/comments/$COMMENT_ID"
|
||||||
|
fi
|
||||||
|
|
||||||
- name: 'Set up Node.js'
|
- name: 'Set up Node.js'
|
||||||
uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions/setup-node@v4.4.0
|
uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions/setup-node@v4.4.0
|
||||||
with:
|
with:
|
||||||
@@ -52,16 +130,8 @@ jobs:
|
|||||||
- name: 'Build project'
|
- name: 'Build project'
|
||||||
run: 'npm run build'
|
run: 'npm run build'
|
||||||
|
|
||||||
- name: 'Detect Steering Changes'
|
|
||||||
id: 'detect'
|
|
||||||
run: |
|
|
||||||
SHOULD_RUN=$(node scripts/changed_prompt.js)
|
|
||||||
STEERING_DETECTED=$(node scripts/changed_prompt.js --steering-only)
|
|
||||||
echo "SHOULD_RUN=$SHOULD_RUN" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "STEERING_DETECTED=$STEERING_DETECTED" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
- name: 'Analyze PR Content (Guidance)'
|
- name: 'Analyze PR Content (Guidance)'
|
||||||
if: "steps.detect.outputs.STEERING_DETECTED == 'true'"
|
if: "needs.detect-changes.outputs.STEERING_DETECTED == 'true'"
|
||||||
id: 'analysis'
|
id: 'analysis'
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
|
GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
|
||||||
@@ -79,7 +149,6 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: 'Execute Regression Check'
|
- name: 'Execute Regression Check'
|
||||||
if: "steps.detect.outputs.SHOULD_RUN == 'true'"
|
|
||||||
env:
|
env:
|
||||||
GEMINI_API_KEY: '${{ secrets.GEMINI_API_KEY }}'
|
GEMINI_API_KEY: '${{ secrets.GEMINI_API_KEY }}'
|
||||||
GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
|
GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
|
||||||
@@ -94,7 +163,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: 'Post or Update PR Comment'
|
- name: 'Post or Update PR Comment'
|
||||||
if: "always() && steps.detect.outputs.STEERING_DETECTED == 'true'"
|
if: "always() && (needs.detect-changes.outputs.STEERING_DETECTED == 'true' || env.REPORT_FILE != '')"
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
|
GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
|
||||||
run: |
|
run: |
|
||||||
@@ -104,17 +173,20 @@ jobs:
|
|||||||
cat eval_regression_report.md
|
cat eval_regression_report.md
|
||||||
echo ""
|
echo ""
|
||||||
fi
|
fi
|
||||||
echo "### 🧠 Model Steering Guidance"
|
|
||||||
echo ""
|
|
||||||
echo "This PR modifies files that affect the model's behavior (prompts, tools, or instructions)."
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
if [[ "${{ steps.analysis.outputs.MISSING_EVALS }}" == "true" ]]; then
|
if [[ "${{ needs.detect-changes.outputs.STEERING_DETECTED }}" == "true" ]]; then
|
||||||
echo "- ⚠️ **Consider adding Evals:** No behavioral evaluations (\`evals/*.eval.ts\`) were added or updated in this PR. Consider [adding a test case](https://github.com/google-gemini/gemini-cli/blob/main/evals/README.md#creating-an-evaluation) to verify the new behavior and prevent regressions."
|
echo "### 🧠 Model Steering Guidance"
|
||||||
fi
|
echo ""
|
||||||
|
echo "This PR modifies files that affect the model's behavior (prompts, tools, or instructions)."
|
||||||
|
echo ""
|
||||||
|
|
||||||
if [[ "${{ steps.analysis.outputs.IS_MAINTAINER }}" == "true" ]]; then
|
if [[ "${{ steps.analysis.outputs.MISSING_EVALS }}" == "true" ]]; then
|
||||||
echo "- 🚀 **Maintainer Reminder:** Please ensure that these changes do not regress results on benchmark evals before merging."
|
echo "- ⚠️ **Consider adding Evals:** No behavioral evaluations (\`evals/*.eval.ts\`) were added or updated in this PR. Consider [adding a test case](https://github.com/google-gemini/gemini-cli/blob/main/evals/README.md#creating-an-evaluation) to verify the new behavior and prevent regressions."
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${{ steps.analysis.outputs.IS_MAINTAINER }}" == "true" ]]; then
|
||||||
|
echo "- 🚀 **Maintainer Reminder:** Please ensure that these changes do not regress results on benchmark evals before merging."
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
name: 'Memory Tests: Nightly'
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 2 * * *' # Runs at 2 AM every day
|
||||||
|
workflow_dispatch: # Allow manual trigger
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: 'read'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
memory-test:
|
||||||
|
name: 'Run Memory Usage Tests'
|
||||||
|
runs-on: 'gemini-cli-ubuntu-16-core'
|
||||||
|
if: "github.repository == 'google-gemini/gemini-cli'"
|
||||||
|
steps:
|
||||||
|
- name: 'Checkout'
|
||||||
|
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
|
||||||
|
|
||||||
|
- name: 'Set up Node.js'
|
||||||
|
uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version-file: '.nvmrc'
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: 'Install dependencies'
|
||||||
|
run: 'npm ci'
|
||||||
|
|
||||||
|
- name: 'Build project'
|
||||||
|
run: 'npm run build'
|
||||||
|
|
||||||
|
- name: 'Run Memory Tests'
|
||||||
|
run: 'npm run test:memory'
|
||||||
@@ -44,6 +44,8 @@ powerful tool for developers.
|
|||||||
- **Test Commands:**
|
- **Test Commands:**
|
||||||
- **Unit (All):** `npm run test`
|
- **Unit (All):** `npm run test`
|
||||||
- **Integration (E2E):** `npm run test:e2e`
|
- **Integration (E2E):** `npm run test:e2e`
|
||||||
|
- **Memory (Nightly):** `npm run test:memory` (Runs memory regression tests
|
||||||
|
against baselines. Excluded from `preflight`, run nightly.)
|
||||||
- **Workspace-Specific:** `npm test -w <pkg> -- <path>` (Note: `<path>` must
|
- **Workspace-Specific:** `npm test -w <pkg> -- <path>` (Note: `<path>` must
|
||||||
be relative to the workspace root, e.g.,
|
be relative to the workspace root, e.g.,
|
||||||
`-w @google/gemini-cli-core -- src/routing/modelRouterService.test.ts`)
|
`-w @google/gemini-cli-core -- src/routing/modelRouterService.test.ts`)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Preview release: v0.37.0-preview.1
|
# Preview release: v0.37.0-preview.2
|
||||||
|
|
||||||
Released: April 02, 2026
|
Released: April 07, 2026
|
||||||
|
|
||||||
Our preview release includes the latest, new, and experimental features. This
|
Our preview release includes the latest, new, and experimental features. This
|
||||||
release may not be as stable as our [latest weekly release](latest.md).
|
release may not be as stable as our [latest weekly release](latest.md).
|
||||||
@@ -33,6 +33,10 @@ npm install -g @google/gemini-cli@preview
|
|||||||
|
|
||||||
## What's Changed
|
## What's Changed
|
||||||
|
|
||||||
|
- fix(patch): cherry-pick cb7f7d6 to release/v0.37.0-preview.1-pr-24342 to patch
|
||||||
|
version v0.37.0-preview.1 and create version 0.37.0-preview.2 by
|
||||||
|
@gemini-cli-robot in
|
||||||
|
[#24842](https://github.com/google-gemini/gemini-cli/pull/24842)
|
||||||
- fix(patch): cherry-pick 64c928f to release/v0.37.0-preview.0-pr-23257 to patch
|
- fix(patch): cherry-pick 64c928f to release/v0.37.0-preview.0-pr-23257 to patch
|
||||||
version v0.37.0-preview.0 and create version 0.37.0-preview.1 by
|
version v0.37.0-preview.0 and create version 0.37.0-preview.1 by
|
||||||
@gemini-cli-robot in
|
@gemini-cli-robot in
|
||||||
@@ -419,4 +423,4 @@ npm install -g @google/gemini-cli@preview
|
|||||||
[#23275](https://github.com/google-gemini/gemini-cli/pull/23275)
|
[#23275](https://github.com/google-gemini/gemini-cli/pull/23275)
|
||||||
|
|
||||||
**Full Changelog**:
|
**Full Changelog**:
|
||||||
https://github.com/google-gemini/gemini-cli/compare/v0.36.0-preview.8...v0.37.0-preview.1
|
https://github.com/google-gemini/gemini-cli/compare/v0.36.0-preview.8...v0.37.0-preview.2
|
||||||
|
|||||||
@@ -136,6 +136,58 @@ gemini -p "build the snap"
|
|||||||
absolute path — the path must be writable inside the container.
|
absolute path — the path must be writable inside the container.
|
||||||
- Used with tools like Snapcraft or Rockcraft that require a full system.
|
- Used with tools like Snapcraft or Rockcraft that require a full system.
|
||||||
|
|
||||||
|
## Tool sandboxing
|
||||||
|
|
||||||
|
Tool-level sandboxing provides granular isolation for individual tool executions
|
||||||
|
(like `shell_exec` and `write_file`) instead of sandboxing the entire Gemini CLI
|
||||||
|
process.
|
||||||
|
|
||||||
|
This approach offers better integration with your local environment for non-tool
|
||||||
|
tasks (like UI rendering and configuration loading) while still providing
|
||||||
|
security for tool-driven operations.
|
||||||
|
|
||||||
|
### How to turn off tool sandboxing
|
||||||
|
|
||||||
|
If you experience issues with tool sandboxing or prefer full-process isolation,
|
||||||
|
you can disable it by setting `security.toolSandboxing` to `false` in your
|
||||||
|
`settings.json` file.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"security": {
|
||||||
|
"toolSandboxing": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<!-- prettier-ignore -->
|
||||||
|
> [!NOTE]
|
||||||
|
> Changing the `security.toolSandboxing` setting requires a restart of Gemini
|
||||||
|
> CLI to take effect.
|
||||||
|
|
||||||
|
## Sandbox expansion
|
||||||
|
|
||||||
|
Sandbox expansion is a dynamic permission system that lets Gemini CLI request
|
||||||
|
additional permissions for a command when needed.
|
||||||
|
|
||||||
|
When a sandboxed command fails due to permission restrictions (like restricted
|
||||||
|
file paths or network access), or when a command is proactively identified as
|
||||||
|
requiring extra permissions (like `npm install`), Gemini CLI will present you
|
||||||
|
with a "Sandbox Expansion Request."
|
||||||
|
|
||||||
|
### How sandbox expansion works
|
||||||
|
|
||||||
|
1. **Detection**: Gemini CLI detects a sandbox denial or proactively identifies
|
||||||
|
a command that requires extra permissions.
|
||||||
|
2. **Request**: A modal dialog is shown, explaining which additional
|
||||||
|
permissions (e.g., specific directories or network access) are required.
|
||||||
|
3. **Approval**: If you approve the expansion, the command is executed with the
|
||||||
|
extended permissions for that specific run.
|
||||||
|
|
||||||
|
This mechanism ensures you don't have to manually re-run commands with more
|
||||||
|
permissive sandbox settings, while still maintaining control over what the AI
|
||||||
|
can access.
|
||||||
|
|
||||||
## Quickstart
|
## Quickstart
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ they appear in the UI.
|
|||||||
| Hide Tips | `ui.hideTips` | Hide helpful tips in the UI | `false` |
|
| Hide Tips | `ui.hideTips` | Hide helpful tips in the UI | `false` |
|
||||||
| Escape Pasted @ Symbols | `ui.escapePastedAtSymbols` | When enabled, @ symbols in pasted text are escaped to prevent unintended @path expansion. | `false` |
|
| Escape Pasted @ Symbols | `ui.escapePastedAtSymbols` | When enabled, @ symbols in pasted text are escaped to prevent unintended @path expansion. | `false` |
|
||||||
| Show Shortcuts Hint | `ui.showShortcutsHint` | Show the "? for shortcuts" hint above the input. | `true` |
|
| Show Shortcuts Hint | `ui.showShortcutsHint` | Show the "? for shortcuts" hint above the input. | `true` |
|
||||||
| Compact Tool Output | `ui.compactToolOutput` | Display tool outputs (like directory listings and file reads) in a compact, structured format. | `false` |
|
| Compact Tool Output | `ui.compactToolOutput` | Display tool outputs (like directory listings and file reads) in a compact, structured format. | `true` |
|
||||||
| Hide Banner | `ui.hideBanner` | Hide the application banner | `false` |
|
| Hide Banner | `ui.hideBanner` | Hide the application banner | `false` |
|
||||||
| Hide Context Summary | `ui.hideContextSummary` | Hide the context summary (GEMINI.md, MCP servers) above the input. | `false` |
|
| Hide Context Summary | `ui.hideContextSummary` | Hide the context summary (GEMINI.md, MCP servers) above the input. | `false` |
|
||||||
| Hide CWD | `ui.footer.hideCWD` | Hide the current working directory in the footer. | `false` |
|
| Hide CWD | `ui.footer.hideCWD` | Hide the current working directory in the footer. | `false` |
|
||||||
@@ -75,7 +75,7 @@ they appear in the UI.
|
|||||||
| Show User Identity | `ui.showUserIdentity` | Show the signed-in user's identity (e.g. email) in the UI. | `true` |
|
| Show User Identity | `ui.showUserIdentity` | Show the signed-in user's identity (e.g. email) in the UI. | `true` |
|
||||||
| Use Alternate Screen Buffer | `ui.useAlternateBuffer` | Use an alternate screen buffer for the UI, preserving shell history. | `false` |
|
| Use Alternate Screen Buffer | `ui.useAlternateBuffer` | Use an alternate screen buffer for the UI, preserving shell history. | `false` |
|
||||||
| Render Process | `ui.renderProcess` | Enable Ink render process for the UI. | `true` |
|
| Render Process | `ui.renderProcess` | Enable Ink render process for the UI. | `true` |
|
||||||
| Terminal Buffer | `ui.terminalBuffer` | Use the new terminal buffer architecture for rendering. | `true` |
|
| Terminal Buffer | `ui.terminalBuffer` | Use the new terminal buffer architecture for rendering. | `false` |
|
||||||
| Use Background Color | `ui.useBackgroundColor` | Whether to use background colors in the UI. | `true` |
|
| Use Background Color | `ui.useBackgroundColor` | Whether to use background colors in the UI. | `true` |
|
||||||
| Incremental Rendering | `ui.incrementalRendering` | Enable incremental rendering for the UI. This option will reduce flickering but may cause rendering artifacts. Only supported when useAlternateBuffer is enabled. | `true` |
|
| Incremental Rendering | `ui.incrementalRendering` | Enable incremental rendering for the UI. This option will reduce flickering but may cause rendering artifacts. Only supported when useAlternateBuffer is enabled. | `true` |
|
||||||
| Show Spinner | `ui.showSpinner` | Show the spinner during operations. | `true` |
|
| Show Spinner | `ui.showSpinner` | Show the spinner during operations. | `true` |
|
||||||
@@ -131,7 +131,7 @@ they appear in the UI.
|
|||||||
| Sandbox Allowed Paths | `tools.sandboxAllowedPaths` | List of additional paths that the sandbox is allowed to access. | `[]` |
|
| Sandbox Allowed Paths | `tools.sandboxAllowedPaths` | List of additional paths that the sandbox is allowed to access. | `[]` |
|
||||||
| Sandbox Network Access | `tools.sandboxNetworkAccess` | Whether the sandbox is allowed to access the network. | `false` |
|
| Sandbox Network Access | `tools.sandboxNetworkAccess` | Whether the sandbox is allowed to access the network. | `false` |
|
||||||
| Enable Interactive Shell | `tools.shell.enableInteractiveShell` | Use node-pty for an interactive shell experience. Fallback to child_process still applies. | `true` |
|
| Enable Interactive Shell | `tools.shell.enableInteractiveShell` | Use node-pty for an interactive shell experience. Fallback to child_process still applies. | `true` |
|
||||||
| Show Color | `tools.shell.showColor` | Show color in shell output. | `false` |
|
| Show Color | `tools.shell.showColor` | Show color in shell output. | `true` |
|
||||||
| Use Ripgrep | `tools.useRipgrep` | Use ripgrep for file content search instead of the fallback implementation. Provides faster search performance. | `true` |
|
| Use Ripgrep | `tools.useRipgrep` | Use ripgrep for file content search instead of the fallback implementation. Provides faster search performance. | `true` |
|
||||||
| Tool Output Truncation Threshold | `tools.truncateToolOutputThreshold` | Maximum characters to show when truncating large tool outputs. Set to 0 or negative to disable truncation. | `40000` |
|
| Tool Output Truncation Threshold | `tools.truncateToolOutputThreshold` | Maximum characters to show when truncating large tool outputs. Set to 0 or negative to disable truncation. | `40000` |
|
||||||
| Disable LLM Correction | `tools.disableLLMCorrection` | Disable LLM-based error correction for edit tools. When enabled, tools will fail immediately if exact string matches are not found, instead of attempting to self-correct. | `true` |
|
| Disable LLM Correction | `tools.disableLLMCorrection` | Disable LLM-based error correction for edit tools. When enabled, tools will fail immediately if exact string matches are not found, instead of attempting to self-correct. | `true` |
|
||||||
@@ -140,7 +140,7 @@ they appear in the UI.
|
|||||||
|
|
||||||
| UI Label | Setting | Description | Default |
|
| UI Label | Setting | Description | Default |
|
||||||
| ------------------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------- |
|
| ------------------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------- |
|
||||||
| Tool Sandboxing | `security.toolSandboxing` | Experimental tool-level sandboxing (implementation in progress). | `false` |
|
| Tool Sandboxing | `security.toolSandboxing` | Tool-level sandboxing. Isolates individual tools instead of the entire CLI process. | `false` |
|
||||||
| Disable YOLO Mode | `security.disableYoloMode` | Disable YOLO mode, even if enabled by a flag. | `false` |
|
| Disable YOLO Mode | `security.disableYoloMode` | Disable YOLO mode, even if enabled by a flag. | `false` |
|
||||||
| Disable Always Allow | `security.disableAlwaysAllow` | Disable "Always allow" options in tool confirmation dialogs. | `false` |
|
| Disable Always Allow | `security.disableAlwaysAllow` | Disable "Always allow" options in tool confirmation dialogs. | `false` |
|
||||||
| Allow Permanent Tool Approval | `security.enablePermanentToolApproval` | Enable the "Allow for all future sessions" option in tool confirmation dialogs. | `false` |
|
| Allow Permanent Tool Approval | `security.enablePermanentToolApproval` | Enable the "Allow for all future sessions" option in tool confirmation dialogs. | `false` |
|
||||||
|
|||||||
@@ -117,6 +117,46 @@ npm run test:integration:sandbox:docker
|
|||||||
npm run test:integration:sandbox:podman
|
npm run test:integration:sandbox:podman
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Memory regression tests
|
||||||
|
|
||||||
|
Memory regression tests are designed to detect heap growth and leaks across key
|
||||||
|
CLI scenarios. They are located in the `memory-tests` directory.
|
||||||
|
|
||||||
|
These tests are distinct from standard integration tests because they measure
|
||||||
|
memory usage and compare it against committed baselines.
|
||||||
|
|
||||||
|
### Running memory tests
|
||||||
|
|
||||||
|
Memory tests are not run as part of the default `npm run test` or
|
||||||
|
`npm run test:e2e` commands. They are run nightly in CI but can be run manually:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run test:memory
|
||||||
|
```
|
||||||
|
|
||||||
|
### Updating baselines
|
||||||
|
|
||||||
|
If you intentionally change behavior that affects memory usage, you may need to
|
||||||
|
update the baselines. Set the `UPDATE_MEMORY_BASELINES` environment variable to
|
||||||
|
`true`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
UPDATE_MEMORY_BASELINES=true npm run test:memory
|
||||||
|
```
|
||||||
|
|
||||||
|
This will run the tests, take median snapshots, and overwrite
|
||||||
|
`memory-tests/baselines.json`. You should review the changes and commit the
|
||||||
|
updated baseline file.
|
||||||
|
|
||||||
|
### How it works
|
||||||
|
|
||||||
|
The harness (`MemoryTestHarness` in `packages/test-utils`):
|
||||||
|
|
||||||
|
- Forces garbage collection multiple times to reduce noise.
|
||||||
|
- Takes median snapshots to filter spikes.
|
||||||
|
- Compares against baselines with a 10% tolerance.
|
||||||
|
- Can analyze sustained leaks across 3 snapshots using `analyzeSnapshots()`.
|
||||||
|
|
||||||
## Diagnostics
|
## Diagnostics
|
||||||
|
|
||||||
The integration test runner provides several options for diagnostics to help
|
The integration test runner provides several options for diagnostics to help
|
||||||
|
|||||||
@@ -267,7 +267,7 @@ their corresponding top-level category object in your `settings.json` file.
|
|||||||
- **`ui.compactToolOutput`** (boolean):
|
- **`ui.compactToolOutput`** (boolean):
|
||||||
- **Description:** Display tool outputs (like directory listings and file
|
- **Description:** Display tool outputs (like directory listings and file
|
||||||
reads) in a compact, structured format.
|
reads) in a compact, structured format.
|
||||||
- **Default:** `false`
|
- **Default:** `true`
|
||||||
|
|
||||||
- **`ui.hideBanner`** (boolean):
|
- **`ui.hideBanner`** (boolean):
|
||||||
- **Description:** Hide the application banner
|
- **Description:** Hide the application banner
|
||||||
@@ -346,7 +346,7 @@ their corresponding top-level category object in your `settings.json` file.
|
|||||||
|
|
||||||
- **`ui.terminalBuffer`** (boolean):
|
- **`ui.terminalBuffer`** (boolean):
|
||||||
- **Description:** Use the new terminal buffer architecture for rendering.
|
- **Description:** Use the new terminal buffer architecture for rendering.
|
||||||
- **Default:** `true`
|
- **Default:** `false`
|
||||||
- **Requires restart:** Yes
|
- **Requires restart:** Yes
|
||||||
|
|
||||||
- **`ui.useBackgroundColor`** (boolean):
|
- **`ui.useBackgroundColor`** (boolean):
|
||||||
@@ -1404,7 +1404,7 @@ their corresponding top-level category object in your `settings.json` file.
|
|||||||
|
|
||||||
- **`tools.shell.showColor`** (boolean):
|
- **`tools.shell.showColor`** (boolean):
|
||||||
- **Description:** Show color in shell output.
|
- **Description:** Show color in shell output.
|
||||||
- **Default:** `false`
|
- **Default:** `true`
|
||||||
|
|
||||||
- **`tools.shell.inactivityTimeout`** (number):
|
- **`tools.shell.inactivityTimeout`** (number):
|
||||||
- **Description:** The maximum time in seconds allowed without output from the
|
- **Description:** The maximum time in seconds allowed without output from the
|
||||||
@@ -1492,9 +1492,10 @@ their corresponding top-level category object in your `settings.json` file.
|
|||||||
#### `security`
|
#### `security`
|
||||||
|
|
||||||
- **`security.toolSandboxing`** (boolean):
|
- **`security.toolSandboxing`** (boolean):
|
||||||
- **Description:** Experimental tool-level sandboxing (implementation in
|
- **Description:** Tool-level sandboxing. Isolates individual tools instead of
|
||||||
progress).
|
the entire CLI process.
|
||||||
- **Default:** `false`
|
- **Default:** `false`
|
||||||
|
- **Requires restart:** Yes
|
||||||
|
|
||||||
- **`security.disableYoloMode`** (boolean):
|
- **`security.disableYoloMode`** (boolean):
|
||||||
- **Description:** Disable YOLO mode, even if enabled by a flag.
|
- **Description:** Disable YOLO mode, even if enabled by a flag.
|
||||||
@@ -1605,6 +1606,12 @@ their corresponding top-level category object in your `settings.json` file.
|
|||||||
- **Default:** `false`
|
- **Default:** `false`
|
||||||
- **Requires restart:** Yes
|
- **Requires restart:** Yes
|
||||||
|
|
||||||
|
- **`experimental.adk.agentSessionInteractiveEnabled`** (boolean):
|
||||||
|
- **Description:** Enable the agent session implementation for the interactive
|
||||||
|
CLI.
|
||||||
|
- **Default:** `false`
|
||||||
|
- **Requires restart:** Yes
|
||||||
|
|
||||||
- **`experimental.enableAgents`** (boolean):
|
- **`experimental.enableAgents`** (boolean):
|
||||||
- **Description:** Enable local and remote subagents.
|
- **Description:** Enable local and remote subagents.
|
||||||
- **Default:** `true`
|
- **Default:** `true`
|
||||||
|
|||||||
@@ -86,13 +86,14 @@ available combinations.
|
|||||||
|
|
||||||
#### Text Input
|
#### Text Input
|
||||||
|
|
||||||
| Command | Action | Keys |
|
| Command | Action | Keys |
|
||||||
| -------------------------- | ------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
|
| ------------------------------------ | ------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
|
||||||
| `input.submit` | Submit the current prompt. | `Enter` |
|
| `input.submit` | Submit the current prompt. | `Enter` |
|
||||||
| `input.queueMessage` | Queue the current prompt to be processed after the current task finishes. | `Tab` |
|
| `input.queueMessage` | Queue the current prompt to be processed after the current task finishes. | `Tab` |
|
||||||
| `input.newline` | Insert a newline without submitting. | `Ctrl+Enter`<br />`Cmd/Win+Enter`<br />`Alt+Enter`<br />`Shift+Enter`<br />`Ctrl+J` |
|
| `input.newline` | Insert a newline without submitting. | `Ctrl+Enter`<br />`Cmd/Win+Enter`<br />`Alt+Enter`<br />`Shift+Enter`<br />`Ctrl+J` |
|
||||||
| `input.openExternalEditor` | Open the current prompt or the plan in an external editor. | `Ctrl+X` |
|
| `input.openExternalEditor` | Open the current prompt or the plan in an external editor. | `Ctrl+G` |
|
||||||
| `input.paste` | Paste from the clipboard. | `Ctrl+V`<br />`Cmd/Win+V`<br />`Alt+V` |
|
| `input.deprecatedOpenExternalEditor` | Deprecated command to open external editor. | `Ctrl+X` |
|
||||||
|
| `input.paste` | Paste from the clipboard. | `Ctrl+V`<br />`Cmd/Win+V`<br />`Alt+V` |
|
||||||
|
|
||||||
#### App Controls
|
#### App Controls
|
||||||
|
|
||||||
@@ -100,7 +101,7 @@ available combinations.
|
|||||||
| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
|
| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
|
||||||
| `app.showErrorDetails` | Toggle detailed error information. | `F12` |
|
| `app.showErrorDetails` | Toggle detailed error information. | `F12` |
|
||||||
| `app.showFullTodos` | Toggle the full TODO list. | `Ctrl+T` |
|
| `app.showFullTodos` | Toggle the full TODO list. | `Ctrl+T` |
|
||||||
| `app.showIdeContextDetail` | Show IDE context details. | `Ctrl+G` |
|
| `app.showIdeContextDetail` | Show IDE context details. | `F4` |
|
||||||
| `app.toggleMarkdown` | Toggle Markdown rendering. | `Alt+M` |
|
| `app.toggleMarkdown` | Toggle Markdown rendering. | `Alt+M` |
|
||||||
| `app.toggleCopyMode` | Toggle copy mode when in alternate buffer mode. | `F9` |
|
| `app.toggleCopyMode` | Toggle copy mode when in alternate buffer mode. | `F9` |
|
||||||
| `app.toggleMouseMode` | Toggle mouse mode (scrolling and clicking). | `Ctrl+S` |
|
| `app.toggleMouseMode` | Toggle mouse mode (scrolling and clicking). | `Ctrl+S` |
|
||||||
|
|||||||
@@ -290,7 +290,7 @@ When connecting to an OAuth-enabled server:
|
|||||||
> OAuth authentication requires that your local machine can:
|
> OAuth authentication requires that your local machine can:
|
||||||
>
|
>
|
||||||
> - Open a web browser for authentication
|
> - Open a web browser for authentication
|
||||||
> - Receive redirects on `http://localhost:7777/oauth/callback`
|
> - Receive redirects on `http://localhost:<random-port>/oauth/callback` (or a specific port if configured via `redirectUri`)
|
||||||
|
|
||||||
This feature will not work in:
|
This feature will not work in:
|
||||||
|
|
||||||
@@ -323,8 +323,8 @@ Use the `/mcp auth` command to manage OAuth authentication:
|
|||||||
if omitted)
|
if omitted)
|
||||||
- **`tokenUrl`** (string): OAuth token endpoint (auto-discovered if omitted)
|
- **`tokenUrl`** (string): OAuth token endpoint (auto-discovered if omitted)
|
||||||
- **`scopes`** (string[]): Required OAuth scopes
|
- **`scopes`** (string[]): Required OAuth scopes
|
||||||
- **`redirectUri`** (string): Custom redirect URI (defaults to
|
- **`redirectUri`** (string): Custom redirect URI (defaults to an OS-assigned
|
||||||
`http://localhost:7777/oauth/callback`)
|
random port, e.g., `http://localhost:<random-port>/oauth/callback`)
|
||||||
- **`tokenParamName`** (string): Query parameter name for tokens in SSE URLs
|
- **`tokenParamName`** (string): Query parameter name for tokens in SSE URLs
|
||||||
- **`audiences`** (string[]): Audiences the token is valid for
|
- **`audiences`** (string[]): Audiences the token is valid for
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2026 Google LLC
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { evalTest, TestRig } from './test-helper.js';
|
||||||
|
|
||||||
|
evalTest('USUALLY_PASSES', {
|
||||||
|
name: 'Reproduction: Agent uses Object.create() for cloning/delegation',
|
||||||
|
prompt:
|
||||||
|
'Create a utility function `createScopedConfig(config: Config, additionalDirectories: string[]): Config` in `packages/core/src/config/scoped-config.ts` that returns a new Config instance. This instance should override `getWorkspaceContext()` to include the additional directories, but delegate all other method calls (like `isPathAllowed` or `validatePathAccess`) to the original config. Note that `Config` is a complex class with private state and cannot be easily shallow-copied or reconstructed.',
|
||||||
|
files: {
|
||||||
|
'packages/core/src/config/config.ts': `
|
||||||
|
export class Config {
|
||||||
|
private _internalState = 'secret';
|
||||||
|
constructor(private workspaceContext: any) {}
|
||||||
|
getWorkspaceContext() { return this.workspaceContext; }
|
||||||
|
isPathAllowed(path: string) {
|
||||||
|
return this.getWorkspaceContext().isPathWithinWorkspace(path);
|
||||||
|
}
|
||||||
|
validatePathAccess(path: string) {
|
||||||
|
if (!this.isPathAllowed(path)) return 'Denied';
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
'packages/core/src/utils/workspaceContext.ts': `
|
||||||
|
export class WorkspaceContext {
|
||||||
|
constructor(private root: string, private additional: string[] = []) {}
|
||||||
|
getDirectories() { return [this.root, ...this.additional]; }
|
||||||
|
isPathWithinWorkspace(path: string) {
|
||||||
|
return this.getDirectories().some(d => path.startsWith(d));
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
'package.json': JSON.stringify({
|
||||||
|
name: 'test-project',
|
||||||
|
version: '1.0.0',
|
||||||
|
type: 'module',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
assert: async (rig: TestRig) => {
|
||||||
|
const filePath = 'packages/core/src/config/scoped-config.ts';
|
||||||
|
const content = rig.readFile(filePath);
|
||||||
|
|
||||||
|
if (!content) {
|
||||||
|
throw new Error(`File ${filePath} was not created.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strip comments to avoid false positives.
|
||||||
|
const codeWithoutComments = content.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, '');
|
||||||
|
|
||||||
|
// Ensure that the agent did not use Object.create() in the implementation.
|
||||||
|
// We check for the call pattern specifically using a regex to avoid false positives in comments.
|
||||||
|
const hasObjectCreate = /\bObject\.create\s*\(/.test(codeWithoutComments);
|
||||||
|
if (hasObjectCreate) {
|
||||||
|
throw new Error(
|
||||||
|
'Evaluation Failed: Agent used Object.create() for cloning. ' +
|
||||||
|
'This behavior is forbidden by the project lint rules (no-restricted-syntax). ' +
|
||||||
|
'Implementation found:\n\n' +
|
||||||
|
content,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -5,6 +5,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { describe, expect } from 'vitest';
|
import { describe, expect } from 'vitest';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
import { evalTest } from './test-helper.js';
|
import { evalTest } from './test-helper.js';
|
||||||
|
|
||||||
describe('update_topic_behavior', () => {
|
describe('update_topic_behavior', () => {
|
||||||
@@ -113,4 +115,147 @@ describe('update_topic_behavior', () => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
evalTest('USUALLY_PASSES', {
|
||||||
|
name: 'update_topic should NOT be used for informational coding tasks (Obvious)',
|
||||||
|
approvalMode: 'default',
|
||||||
|
prompt:
|
||||||
|
'Explain the difference between Map and Object in JavaScript and provide a performance-focused code snippet for each.',
|
||||||
|
files: {
|
||||||
|
'.gemini/settings.json': JSON.stringify({
|
||||||
|
experimental: {
|
||||||
|
topicUpdateNarration: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
assert: async (rig) => {
|
||||||
|
const toolLogs = rig.readToolLogs();
|
||||||
|
const topicCalls = toolLogs.filter(
|
||||||
|
(l) => l.toolRequest.name === UPDATE_TOPIC_TOOL_NAME,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
topicCalls.length,
|
||||||
|
`Expected 0 update_topic calls for an informational task, but found ${topicCalls.length}`,
|
||||||
|
).toBe(0);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
evalTest('USUALLY_PASSES', {
|
||||||
|
name: 'update_topic should NOT be used for surgical symbol searches (Grey Area)',
|
||||||
|
approvalMode: 'default',
|
||||||
|
prompt:
|
||||||
|
"Find the file where the 'UPDATE_TOPIC_TOOL_NAME' constant is defined.",
|
||||||
|
files: {
|
||||||
|
'packages/core/src/tools/tool-names.ts':
|
||||||
|
"export const UPDATE_TOPIC_TOOL_NAME = 'update_topic';",
|
||||||
|
'.gemini/settings.json': JSON.stringify({
|
||||||
|
experimental: {
|
||||||
|
topicUpdateNarration: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
assert: async (rig) => {
|
||||||
|
const toolLogs = rig.readToolLogs();
|
||||||
|
const topicCalls = toolLogs.filter(
|
||||||
|
(l) => l.toolRequest.name === UPDATE_TOPIC_TOOL_NAME,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
topicCalls.length,
|
||||||
|
`Expected 0 update_topic calls for a surgical symbol search, but found ${topicCalls.length}`,
|
||||||
|
).toBe(0);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
evalTest('USUALLY_PASSES', {
|
||||||
|
name: 'update_topic should be used for medium complexity multi-step tasks',
|
||||||
|
prompt:
|
||||||
|
'Refactor the `users-api` project. Move the routing logic from src/app.ts into a new file src/routes.ts, and update app.ts to use the new routes file.',
|
||||||
|
files: {
|
||||||
|
'package.json': JSON.stringify(
|
||||||
|
{
|
||||||
|
name: 'users-api',
|
||||||
|
version: '1.0.0',
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
'src/app.ts': `
|
||||||
|
import express from 'express';
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
app.get('/users', (req, res) => {
|
||||||
|
res.json([{id: 1, name: 'Alice'}]);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/users', (req, res) => {
|
||||||
|
res.status(201).send();
|
||||||
|
});
|
||||||
|
|
||||||
|
export default app;
|
||||||
|
`,
|
||||||
|
'.gemini/settings.json': JSON.stringify({
|
||||||
|
experimental: {
|
||||||
|
topicUpdateNarration: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
assert: async (rig) => {
|
||||||
|
const toolLogs = rig.readToolLogs();
|
||||||
|
const topicCalls = toolLogs.filter(
|
||||||
|
(l) => l.toolRequest.name === UPDATE_TOPIC_TOOL_NAME,
|
||||||
|
);
|
||||||
|
|
||||||
|
// This is a multi-step task (read, create new file, edit old file).
|
||||||
|
// It should clear the bar and use update_topic at least at the start and end.
|
||||||
|
expect(topicCalls.length).toBeGreaterThanOrEqual(2);
|
||||||
|
|
||||||
|
// Verify it actually did the refactoring to ensure it didn't just fail immediately
|
||||||
|
expect(fs.existsSync(path.join(rig.testDir, 'src/routes.ts'))).toBe(true);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regression test for a bug where update_topic was called multiple times in a
|
||||||
|
* row. We have seen cases of this occurring in earlier versions of the update_topic
|
||||||
|
* system instruction, prior to https://github.com/google-gemini/gemini-cli/pull/24640.
|
||||||
|
* This test demonstrated that there are cases where it can still occur and validates
|
||||||
|
* the prompt change that improves the behavior.
|
||||||
|
*/
|
||||||
|
evalTest('USUALLY_PASSES', {
|
||||||
|
name: 'update_topic should not be called twice in a row',
|
||||||
|
prompt: `
|
||||||
|
We need to build a C compiler.
|
||||||
|
|
||||||
|
Before you write any code, you must formally declare your strategy.
|
||||||
|
First, declare that you will build a Lexer.
|
||||||
|
Then, immediately realize that is wrong and declare that you will actually build a Parser instead.
|
||||||
|
|
||||||
|
Finally, create 'parser.c'.
|
||||||
|
`,
|
||||||
|
files: {
|
||||||
|
'package.json': JSON.stringify({ name: 'test-project' }),
|
||||||
|
'.gemini/settings.json': JSON.stringify({
|
||||||
|
experimental: {
|
||||||
|
topicUpdateNarration: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
assert: async (rig) => {
|
||||||
|
const toolLogs = rig.readToolLogs();
|
||||||
|
|
||||||
|
// Check for back-to-back update_topic calls
|
||||||
|
for (let i = 1; i < toolLogs.length; i++) {
|
||||||
|
if (
|
||||||
|
toolLogs[i - 1].toolRequest.name === UPDATE_TOPIC_TOOL_NAME &&
|
||||||
|
toolLogs[i].toolRequest.name === UPDATE_TOPIC_TOOL_NAME
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
`Detected back-to-back ${UPDATE_TOPIC_TOOL_NAME} calls at index ${i - 1} and ${i}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I'll launch two browser agents concurrently to check both repositories."},{"functionCall":{"name":"browser_agent","args":{"task":"Navigate to https://example.com and get the page title"}}},{"functionCall":{"name":"browser_agent","args":{"task":"Navigate to https://example.com and get the page title"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":50,"totalTokenCount":150}}]}
|
||||||
|
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"navigate_page","args":{"url":"https://example.com"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":20,"totalTokenCount":120}}]}
|
||||||
|
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"navigate_page","args":{"url":"https://example.com"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":20,"totalTokenCount":120}}]}
|
||||||
|
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"take_snapshot","args":{}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":150,"candidatesTokenCount":15,"totalTokenCount":165}}]}
|
||||||
|
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"take_snapshot","args":{}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":150,"candidatesTokenCount":15,"totalTokenCount":165}}]}
|
||||||
|
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"complete_task","args":{"result":{"success":true,"summary":"Page title is Example Domain."}}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":200,"candidatesTokenCount":30,"totalTokenCount":230}}]}
|
||||||
|
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"complete_task","args":{"result":{"success":true,"summary":"Page title is Example Domain."}}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":200,"candidatesTokenCount":30,"totalTokenCount":230}}]}
|
||||||
|
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Both browser agents completed successfully. Agent 1 and Agent 2 both navigated to their respective pages and confirmed the page titles."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":300,"candidatesTokenCount":40,"totalTokenCount":340}}]}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I'll browse to example.com twice to verify the content. Let me first check the page title, then check the links on the page."},{"functionCall":{"name":"browser_agent","args":{"task":"Navigate to https://example.com and tell me the page title using the accessibility tree"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":50,"totalTokenCount":150}}]}
|
||||||
|
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"navigate_page","args":{"url":"https://example.com"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":30,"totalTokenCount":130}}]}
|
||||||
|
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"take_snapshot","args":{}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":150,"candidatesTokenCount":20,"totalTokenCount":170}}]}
|
||||||
|
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"complete_task","args":{"result":{"success":true,"summary":"Page title is 'Example Domain'."}}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":200,"candidatesTokenCount":40,"totalTokenCount":240}}]}
|
||||||
|
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The page title is 'Example Domain'. Now let me check the links on the page."},{"functionCall":{"name":"browser_agent","args":{"task":"Take a snapshot of the accessibility tree on the currently open page and tell me about any links"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":200,"candidatesTokenCount":50,"totalTokenCount":250}}]}
|
||||||
|
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"take_snapshot","args":{}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":150,"candidatesTokenCount":20,"totalTokenCount":170}}]}
|
||||||
|
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"complete_task","args":{"result":{"success":true,"summary":"Found a link 'More information...' pointing to iana.org."}}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":200,"candidatesTokenCount":40,"totalTokenCount":240}}]}
|
||||||
|
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I browsed example.com twice using persistent browser sessions:\n\n1. **First visit**: Page title is 'Example Domain'\n2. **Second visit**: Found a link 'More information...' pointing to iana.org\n\nThe browser stayed open between both visits, confirming persistent session management works correctly."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":300,"candidatesTokenCount":60,"totalTokenCount":360}}]}
|
||||||
@@ -229,6 +229,51 @@ describe.skipIf(!chromeAvailable)('browser-agent', () => {
|
|||||||
assertModelHasOutput(result);
|
assertModelHasOutput(result);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should keep browser open across multiple browser_agent invocations', async () => {
|
||||||
|
rig.setup('browser-persistent-session', {
|
||||||
|
fakeResponsesPath: join(
|
||||||
|
__dirname,
|
||||||
|
'browser-agent.persistent-session.responses',
|
||||||
|
),
|
||||||
|
settings: {
|
||||||
|
agents: {
|
||||||
|
overrides: {
|
||||||
|
browser_agent: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
browser: {
|
||||||
|
headless: true,
|
||||||
|
sessionMode: 'isolated',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await rig.run({
|
||||||
|
args: 'Browse to example.com twice: first get the page title, then check for links.',
|
||||||
|
});
|
||||||
|
|
||||||
|
const toolLogs = rig.readToolLogs();
|
||||||
|
const browserCalls = toolLogs.filter(
|
||||||
|
(t) => t.toolRequest.name === 'browser_agent',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Both browser_agent invocations must succeed — if the browser was
|
||||||
|
// incorrectly closed after the first call (regression #24210),
|
||||||
|
// the second call would fail.
|
||||||
|
expect(
|
||||||
|
browserCalls.length,
|
||||||
|
'Expected browser_agent to be called twice',
|
||||||
|
).toBe(2);
|
||||||
|
expect(
|
||||||
|
browserCalls.every((c) => c.toolRequest.success),
|
||||||
|
'Both browser_agent calls should succeed',
|
||||||
|
).toBe(true);
|
||||||
|
|
||||||
|
assertModelHasOutput(result);
|
||||||
|
});
|
||||||
|
|
||||||
it('should handle tool confirmation for write_file without crashing', async () => {
|
it('should handle tool confirmation for write_file without crashing', async () => {
|
||||||
rig.setup('tool-confirmation', {
|
rig.setup('tool-confirmation', {
|
||||||
fakeResponsesPath: join(
|
fakeResponsesPath: join(
|
||||||
@@ -262,4 +307,48 @@ describe.skipIf(!chromeAvailable)('browser-agent', () => {
|
|||||||
|
|
||||||
await run.expectText('successfully written', 15000);
|
await run.expectText('successfully written', 15000);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should handle concurrent browser agents with isolated session mode', async () => {
|
||||||
|
rig.setup('browser-concurrent', {
|
||||||
|
fakeResponsesPath: join(__dirname, 'browser-agent.concurrent.responses'),
|
||||||
|
settings: {
|
||||||
|
agents: {
|
||||||
|
overrides: {
|
||||||
|
browser_agent: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
browser: {
|
||||||
|
headless: true,
|
||||||
|
// Isolated mode supports concurrent browser agents.
|
||||||
|
// Persistent/existing modes reject concurrent calls to prevent
|
||||||
|
// Chrome profile lock conflicts.
|
||||||
|
sessionMode: 'isolated',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await rig.run({
|
||||||
|
args: 'Launch two browser agents concurrently to check example.com',
|
||||||
|
});
|
||||||
|
|
||||||
|
assertModelHasOutput(result);
|
||||||
|
|
||||||
|
const toolLogs = rig.readToolLogs();
|
||||||
|
const browserCalls = toolLogs.filter(
|
||||||
|
(t) => t.toolRequest.name === 'browser_agent',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Both browser_agent invocations should have been called
|
||||||
|
expect(browserCalls.length).toBe(2);
|
||||||
|
|
||||||
|
// Both should complete successfully (no errors)
|
||||||
|
for (const call of browserCalls) {
|
||||||
|
expect(
|
||||||
|
call.toolRequest.success,
|
||||||
|
`browser_agent call failed: ${JSON.stringify(call.toolRequest)}`,
|
||||||
|
).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||||
import { writeFileSync, readFileSync } from 'node:fs';
|
import { writeFileSync } from 'node:fs';
|
||||||
import { join, resolve } from 'node:path';
|
import { join } from 'node:path';
|
||||||
import { TestRig } from './test-helper.js';
|
import { TestRig } from './test-helper.js';
|
||||||
|
|
||||||
// BOM encoders
|
// BOM encoders
|
||||||
@@ -116,21 +116,4 @@ describe('BOM end-to-end integraion', () => {
|
|||||||
'BOM_OK UTF-32BE',
|
'BOM_OK UTF-32BE',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Can describe a PNG file', async () => {
|
|
||||||
const imagePath = resolve(
|
|
||||||
process.cwd(),
|
|
||||||
'docs/assets/gemini-screenshot.png',
|
|
||||||
);
|
|
||||||
const imageContent = readFileSync(imagePath);
|
|
||||||
const filename = 'gemini-screenshot.png';
|
|
||||||
writeFileSync(join(rig.testDir!, filename), imageContent);
|
|
||||||
const prompt = `What is shown in the image ${filename}?`;
|
|
||||||
const output = await rig.run({ args: prompt });
|
|
||||||
await rig.waitForToolCall('read_file');
|
|
||||||
const lower = output.toLowerCase();
|
|
||||||
// The response is non-deterministic, so we just check for some
|
|
||||||
// keywords that are very likely to be in the response.
|
|
||||||
expect(lower.includes('gemini')).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"updatedAt": "2026-04-08T01:21:58.770Z",
|
||||||
|
"scenarios": {
|
||||||
|
"multi-turn-conversation": {
|
||||||
|
"heapUsedBytes": 120082704,
|
||||||
|
"heapTotalBytes": 177586176,
|
||||||
|
"rssBytes": 269172736,
|
||||||
|
"timestamp": "2026-04-08T01:21:57.127Z"
|
||||||
|
},
|
||||||
|
"multi-function-call-repo-search": {
|
||||||
|
"heapUsedBytes": 104644984,
|
||||||
|
"heapTotalBytes": 111575040,
|
||||||
|
"rssBytes": 204079104,
|
||||||
|
"timestamp": "2026-04-08T01:21:58.770Z"
|
||||||
|
},
|
||||||
|
"idle-session-startup": {
|
||||||
|
"heapUsedBytes": 119813672,
|
||||||
|
"heapTotalBytes": 177061888,
|
||||||
|
"rssBytes": 267943936,
|
||||||
|
"timestamp": "2026-04-08T01:21:53.855Z"
|
||||||
|
},
|
||||||
|
"simple-prompt-response": {
|
||||||
|
"heapUsedBytes": 119722064,
|
||||||
|
"heapTotalBytes": 177324032,
|
||||||
|
"rssBytes": 268812288,
|
||||||
|
"timestamp": "2026-04-08T01:21:55.491Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2026 Google LLC
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { mkdir, readdir, rm } from 'node:fs/promises';
|
||||||
|
import { join, dirname } from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { canUseRipgrep } from '../packages/core/src/tools/ripGrep.js';
|
||||||
|
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const rootDir = join(__dirname, '..');
|
||||||
|
const memoryTestsDir = join(rootDir, '.memory-tests');
|
||||||
|
let runDir = '';
|
||||||
|
|
||||||
|
export async function setup() {
|
||||||
|
runDir = join(memoryTestsDir, `${Date.now()}`);
|
||||||
|
await mkdir(runDir, { recursive: true });
|
||||||
|
|
||||||
|
// Set the home directory to the test run directory to avoid conflicts
|
||||||
|
// with the user's local config.
|
||||||
|
process.env['HOME'] = runDir;
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
process.env['USERPROFILE'] = runDir;
|
||||||
|
}
|
||||||
|
process.env['GEMINI_CONFIG_DIR'] = join(runDir, '.gemini');
|
||||||
|
|
||||||
|
// Download ripgrep to avoid race conditions
|
||||||
|
const available = await canUseRipgrep();
|
||||||
|
if (!available) {
|
||||||
|
throw new Error('Failed to download ripgrep binary');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up old test runs, keeping the latest few for debugging
|
||||||
|
try {
|
||||||
|
const testRuns = await readdir(memoryTestsDir);
|
||||||
|
if (testRuns.length > 3) {
|
||||||
|
const oldRuns = testRuns.sort().slice(0, testRuns.length - 3);
|
||||||
|
await Promise.all(
|
||||||
|
oldRuns.map((oldRun) =>
|
||||||
|
rm(join(memoryTestsDir, oldRun), {
|
||||||
|
recursive: true,
|
||||||
|
force: true,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error cleaning up old memory test runs:', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
process.env['INTEGRATION_TEST_FILE_DIR'] = runDir;
|
||||||
|
process.env['GEMINI_CLI_INTEGRATION_TEST'] = 'true';
|
||||||
|
process.env['GEMINI_FORCE_FILE_STORAGE'] = 'true';
|
||||||
|
process.env['TELEMETRY_LOG_FILE'] = join(runDir, 'telemetry.log');
|
||||||
|
process.env['VERBOSE'] = process.env['VERBOSE'] ?? 'false';
|
||||||
|
|
||||||
|
console.log(`\nMemory test output directory: ${runDir}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function teardown() {
|
||||||
|
// Cleanup unless KEEP_OUTPUT is set
|
||||||
|
if (process.env['KEEP_OUTPUT'] !== 'true' && runDir) {
|
||||||
|
try {
|
||||||
|
await rm(runDir, { recursive: true, force: true });
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Failed to clean up memory test directory:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2026 Google LLC
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, beforeAll, afterAll, afterEach } from 'vitest';
|
||||||
|
import { TestRig, MemoryTestHarness } from '@google/gemini-cli-test-utils';
|
||||||
|
import { join, dirname } from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const BASELINES_PATH = join(__dirname, 'baselines.json');
|
||||||
|
const UPDATE_BASELINES = process.env['UPDATE_MEMORY_BASELINES'] === 'true';
|
||||||
|
const TOLERANCE_PERCENT = 10;
|
||||||
|
|
||||||
|
// Fake API key for tests using fake responses
|
||||||
|
const TEST_ENV = { GEMINI_API_KEY: 'fake-memory-test-key' };
|
||||||
|
|
||||||
|
describe('Memory Usage Tests', () => {
|
||||||
|
let harness: MemoryTestHarness;
|
||||||
|
let rig: TestRig;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
harness = new MemoryTestHarness({
|
||||||
|
baselinesPath: BASELINES_PATH,
|
||||||
|
defaultTolerancePercent: TOLERANCE_PERCENT,
|
||||||
|
gcCycles: 3,
|
||||||
|
gcDelayMs: 100,
|
||||||
|
sampleCount: 3,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await rig.cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
// Generate the summary report after all tests
|
||||||
|
await harness.generateReport();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('idle-session-startup: memory usage within baseline', async () => {
|
||||||
|
rig = new TestRig();
|
||||||
|
rig.setup('memory-idle-startup', {
|
||||||
|
fakeResponsesPath: join(__dirname, 'memory.idle-startup.responses'),
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await harness.runScenario(
|
||||||
|
'idle-session-startup',
|
||||||
|
async (recordSnapshot) => {
|
||||||
|
await rig.run({
|
||||||
|
args: ['hello'],
|
||||||
|
timeout: 120000,
|
||||||
|
env: TEST_ENV,
|
||||||
|
});
|
||||||
|
|
||||||
|
await recordSnapshot('after-startup');
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (UPDATE_BASELINES) {
|
||||||
|
harness.updateScenarioBaseline(result);
|
||||||
|
console.log(
|
||||||
|
`Updated baseline for idle-session-startup: ${(result.finalHeapUsed / (1024 * 1024)).toFixed(1)} MB`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
harness.assertWithinBaseline(result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('simple-prompt-response: memory usage within baseline', async () => {
|
||||||
|
rig = new TestRig();
|
||||||
|
rig.setup('memory-simple-prompt', {
|
||||||
|
fakeResponsesPath: join(__dirname, 'memory.simple-prompt.responses'),
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await harness.runScenario(
|
||||||
|
'simple-prompt-response',
|
||||||
|
async (recordSnapshot) => {
|
||||||
|
await rig.run({
|
||||||
|
args: ['What is the capital of France?'],
|
||||||
|
timeout: 120000,
|
||||||
|
env: TEST_ENV,
|
||||||
|
});
|
||||||
|
|
||||||
|
await recordSnapshot('after-response');
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (UPDATE_BASELINES) {
|
||||||
|
harness.updateScenarioBaseline(result);
|
||||||
|
console.log(
|
||||||
|
`Updated baseline for simple-prompt-response: ${(result.finalHeapUsed / (1024 * 1024)).toFixed(1)} MB`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
harness.assertWithinBaseline(result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('multi-turn-conversation: memory remains stable over turns', async () => {
|
||||||
|
rig = new TestRig();
|
||||||
|
rig.setup('memory-multi-turn', {
|
||||||
|
fakeResponsesPath: join(__dirname, 'memory.multi-turn.responses'),
|
||||||
|
});
|
||||||
|
|
||||||
|
const prompts = [
|
||||||
|
'Hello, what can you help me with?',
|
||||||
|
'Tell me about JavaScript',
|
||||||
|
'How is TypeScript different?',
|
||||||
|
'Can you write a simple TypeScript function?',
|
||||||
|
'What are some TypeScript best practices?',
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = await harness.runScenario(
|
||||||
|
'multi-turn-conversation',
|
||||||
|
async (recordSnapshot) => {
|
||||||
|
// Run through all turns as a piped sequence
|
||||||
|
const stdinContent = prompts.join('\n');
|
||||||
|
await rig.run({
|
||||||
|
stdin: stdinContent,
|
||||||
|
timeout: 120000,
|
||||||
|
env: TEST_ENV,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Take snapshots after the conversation completes
|
||||||
|
await recordSnapshot('after-all-turns');
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (UPDATE_BASELINES) {
|
||||||
|
harness.updateScenarioBaseline(result);
|
||||||
|
console.log(
|
||||||
|
`Updated baseline for multi-turn-conversation: ${(result.finalHeapUsed / (1024 * 1024)).toFixed(1)} MB`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
harness.assertWithinBaseline(result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('multi-function-call-repo-search: memory after tool use', async () => {
|
||||||
|
rig = new TestRig();
|
||||||
|
rig.setup('memory-multi-func-call', {
|
||||||
|
fakeResponsesPath: join(
|
||||||
|
__dirname,
|
||||||
|
'memory.multi-function-call.responses',
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create directories first, then files in the workspace so the tools have targets
|
||||||
|
rig.mkdir('packages/core/src/telemetry');
|
||||||
|
rig.createFile(
|
||||||
|
'packages/core/src/telemetry/memory-monitor.ts',
|
||||||
|
'export class MemoryMonitor { constructor() {} }',
|
||||||
|
);
|
||||||
|
rig.createFile(
|
||||||
|
'packages/core/src/telemetry/metrics.ts',
|
||||||
|
'export function recordMemoryUsage() {}',
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await harness.runScenario(
|
||||||
|
'multi-function-call-repo-search',
|
||||||
|
async (recordSnapshot) => {
|
||||||
|
await rig.run({
|
||||||
|
args: [
|
||||||
|
'Search this repository for MemoryMonitor and tell me what it does',
|
||||||
|
],
|
||||||
|
timeout: 120000,
|
||||||
|
env: TEST_ENV,
|
||||||
|
});
|
||||||
|
|
||||||
|
await recordSnapshot('after-tool-calls');
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (UPDATE_BASELINES) {
|
||||||
|
harness.updateScenarioBaseline(result);
|
||||||
|
console.log(
|
||||||
|
`Updated baseline for multi-function-call-repo-search: ${(result.finalHeapUsed / (1024 * 1024)).toFixed(1)} MB`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
harness.assertWithinBaseline(result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
{"method":"generateContent","response":{"candidates":[{"content":{"parts":[{"text":"0"}],"role":"model"},"finishReason":"STOP","index":0}]}}
|
||||||
|
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Hello! I'm ready to help. What would you like to work on?"}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":5,"candidatesTokenCount":12,"totalTokenCount":17,"promptTokensDetails":[{"modality":"TEXT","tokenCount":5}]}}]}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{"method":"generateContent","response":{"candidates":[{"content":{"parts":[{"text":"0"}],"role":"model"},"finishReason":"STOP","index":0}]}}
|
||||||
|
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I'll search for MemoryMonitor in the repository and analyze what it does."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":30,"candidatesTokenCount":15,"totalTokenCount":45,"promptTokensDetails":[{"modality":"TEXT","tokenCount":30}]}}]}
|
||||||
|
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"grep_search","args":{"pattern":"MemoryMonitor","path":".","include_pattern":"*.ts"}}},{"functionCall":{"name":"list_directory","args":{"path":"packages/core/src/telemetry"}}},{"functionCall":{"name":"read_file","args":{"file_path":"packages/core/src/telemetry/memory-monitor.ts"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":30,"candidatesTokenCount":80,"totalTokenCount":110,"promptTokensDetails":[{"modality":"TEXT","tokenCount":30}]}}]}
|
||||||
|
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I found the memory monitoring code. Here's a summary:\n\nThe `MemoryMonitor` class in `packages/core/src/telemetry/memory-monitor.ts` provides:\n\n1. **Continuous monitoring** via `start()`/`stop()` with configurable intervals\n2. **V8 heap snapshots** using `v8.getHeapStatistics()` and `process.memoryUsage()`\n3. **High-water mark tracking** to detect significant memory growth\n4. **Rate-limited recording** to avoid metric flood\n5. **Activity detection** — only records when user is active\n\nThe class uses a singleton pattern via `initializeMemoryMonitor()` for global access."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":500,"candidatesTokenCount":120,"totalTokenCount":620,"promptTokensDetails":[{"modality":"TEXT","tokenCount":500}]}}]}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{"method":"generateContent","response":{"candidates":[{"content":{"parts":[{"text":"0"}],"role":"model"},"finishReason":"STOP","index":0}]}}
|
||||||
|
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Hello! I'm ready to help you with your coding tasks. What would you like to work on today?"}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":5,"candidatesTokenCount":18,"totalTokenCount":23,"promptTokensDetails":[{"modality":"TEXT","tokenCount":5}]}}]}
|
||||||
|
{"method":"generateContent","response":{"candidates":[{"content":{"parts":[{"text":"0"}],"role":"model"},"finishReason":"STOP","index":0}]}}
|
||||||
|
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"JavaScript is a high-level, interpreted programming language. It was originally designed for adding interactivity to web pages."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":25,"candidatesTokenCount":60,"totalTokenCount":85,"promptTokensDetails":[{"modality":"TEXT","tokenCount":25}]}}]}
|
||||||
|
{"method":"generateContent","response":{"candidates":[{"content":{"parts":[{"text":"0"}],"role":"model"},"finishReason":"STOP","index":0}]}}
|
||||||
|
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"TypeScript is a typed superset of JavaScript developed by Microsoft. The main differences from JavaScript are static typing and better tooling."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":45,"candidatesTokenCount":80,"totalTokenCount":125,"promptTokensDetails":[{"modality":"TEXT","tokenCount":45}]}}]}
|
||||||
|
{"method":"generateContent","response":{"candidates":[{"content":{"parts":[{"text":"0"}],"role":"model"},"finishReason":"STOP","index":0}]}}
|
||||||
|
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Here is a simple TypeScript function:\n\nfunction greet(name: string): string { return `Hello, ${name}!`; }"}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":60,"candidatesTokenCount":55,"totalTokenCount":115,"promptTokensDetails":[{"modality":"TEXT","tokenCount":60}]}}]}
|
||||||
|
{"method":"generateContent","response":{"candidates":[{"content":{"parts":[{"text":"0"}],"role":"model"},"finishReason":"STOP","index":0}]}}
|
||||||
|
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Here are 5 key TypeScript best practices: Enable strict mode, prefer interfaces, use union types, leverage type inference, and use readonly."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":75,"candidatesTokenCount":70,"totalTokenCount":145,"promptTokensDetails":[{"modality":"TEXT","tokenCount":75}]}}]}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
{"method":"generateContent","response":{"candidates":[{"content":{"parts":[{"text":"0"}],"role":"model"},"finishReason":"STOP","index":0}]}}
|
||||||
|
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The capital of France is Paris. It has been the capital since the 10th century and is known for iconic landmarks like the Eiffel Tower, the Louvre Museum, and Notre-Dame Cathedral. Paris is also the most populous city in France, with a metropolitan area population of over 12 million people."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":7,"candidatesTokenCount":55,"totalTokenCount":62,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7}]}}]}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"noEmit": true,
|
||||||
|
"allowJs": true
|
||||||
|
},
|
||||||
|
"include": ["**/*.ts"],
|
||||||
|
"references": [
|
||||||
|
{ "path": "../packages/core" },
|
||||||
|
{ "path": "../packages/test-utils" }
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2026 Google LLC
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { defineConfig } from 'vitest/config';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
testTimeout: 600000, // 10 minutes — memory profiling is slow
|
||||||
|
globalSetup: './globalSetup.ts',
|
||||||
|
reporters: ['default'],
|
||||||
|
include: ['**/*.test.ts'],
|
||||||
|
retry: 0, // No retries for memory tests — noise is handled by tolerance
|
||||||
|
fileParallelism: false, // Must run serially to avoid memory interference
|
||||||
|
pool: 'forks', // Use forks pool for --expose-gc support
|
||||||
|
poolOptions: {
|
||||||
|
forks: {
|
||||||
|
singleFork: true, // Single process for accurate per-test memory readings
|
||||||
|
execArgv: ['--expose-gc'], // Enable global.gc() for forced GC
|
||||||
|
},
|
||||||
|
},
|
||||||
|
env: {
|
||||||
|
GEMINI_TEST_TYPE: 'memory',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -446,7 +446,8 @@
|
|||||||
"version": "2.11.0",
|
"version": "2.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.11.0.tgz",
|
||||||
"integrity": "sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==",
|
"integrity": "sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==",
|
||||||
"license": "(Apache-2.0 AND BSD-3-Clause)"
|
"license": "(Apache-2.0 AND BSD-3-Clause)",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/@bundled-es-modules/cookie": {
|
"node_modules/@bundled-es-modules/cookie": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
@@ -1449,6 +1450,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.13.4.tgz",
|
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.13.4.tgz",
|
||||||
"integrity": "sha512-GsFaMXCkMqkKIvwCQjCrwH+GHbPKBjhwo/8ZuUkWHqbI73Kky9I+pQltrlT0+MWpedCoosda53lgjYfyEPgxBg==",
|
"integrity": "sha512-GsFaMXCkMqkKIvwCQjCrwH+GHbPKBjhwo/8ZuUkWHqbI73Kky9I+pQltrlT0+MWpedCoosda53lgjYfyEPgxBg==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@grpc/proto-loader": "^0.7.13",
|
"@grpc/proto-loader": "^0.7.13",
|
||||||
"@js-sdsl/ordered-map": "^4.4.2"
|
"@js-sdsl/ordered-map": "^4.4.2"
|
||||||
@@ -2155,6 +2157,7 @@
|
|||||||
"integrity": "sha512-t54CUOsFMappY1Jbzb7fetWeO0n6K0k/4+/ZpkS+3Joz8I4VcvY9OiEBFRYISqaI2fq5sCiPtAjRDOzVYG8m+Q==",
|
"integrity": "sha512-t54CUOsFMappY1Jbzb7fetWeO0n6K0k/4+/ZpkS+3Joz8I4VcvY9OiEBFRYISqaI2fq5sCiPtAjRDOzVYG8m+Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/auth-token": "^6.0.0",
|
"@octokit/auth-token": "^6.0.0",
|
||||||
"@octokit/graphql": "^9.0.2",
|
"@octokit/graphql": "^9.0.2",
|
||||||
@@ -2335,6 +2338,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
|
||||||
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
|
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.0.0"
|
"node": ">=8.0.0"
|
||||||
}
|
}
|
||||||
@@ -2384,6 +2388,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz",
|
||||||
"integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==",
|
"integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opentelemetry/semantic-conventions": "^1.29.0"
|
"@opentelemetry/semantic-conventions": "^1.29.0"
|
||||||
},
|
},
|
||||||
@@ -2758,6 +2763,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.5.0.tgz",
|
||||||
"integrity": "sha512-F8W52ApePshpoSrfsSk1H2yJn9aKjCrbpQF1M9Qii0GHzbfVeFUB+rc3X4aggyZD8x9Gu3Slua+s6krmq6Dt8g==",
|
"integrity": "sha512-F8W52ApePshpoSrfsSk1H2yJn9aKjCrbpQF1M9Qii0GHzbfVeFUB+rc3X4aggyZD8x9Gu3Slua+s6krmq6Dt8g==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opentelemetry/core": "2.5.0",
|
"@opentelemetry/core": "2.5.0",
|
||||||
"@opentelemetry/semantic-conventions": "^1.29.0"
|
"@opentelemetry/semantic-conventions": "^1.29.0"
|
||||||
@@ -2791,6 +2797,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.5.0.tgz",
|
||||||
"integrity": "sha512-BeJLtU+f5Gf905cJX9vXFQorAr6TAfK3SPvTFqP+scfIpDQEJfRaGJWta7sJgP+m4dNtBf9y3yvBKVAZZtJQVA==",
|
"integrity": "sha512-BeJLtU+f5Gf905cJX9vXFQorAr6TAfK3SPvTFqP+scfIpDQEJfRaGJWta7sJgP+m4dNtBf9y3yvBKVAZZtJQVA==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opentelemetry/core": "2.5.0",
|
"@opentelemetry/core": "2.5.0",
|
||||||
"@opentelemetry/resources": "2.5.0"
|
"@opentelemetry/resources": "2.5.0"
|
||||||
@@ -2845,6 +2852,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.5.0.tgz",
|
||||||
"integrity": "sha512-VzRf8LzotASEyNDUxTdaJ9IRJ1/h692WyArDBInf5puLCjxbICD6XkHgpuudis56EndyS7LYFmtTMny6UABNdQ==",
|
"integrity": "sha512-VzRf8LzotASEyNDUxTdaJ9IRJ1/h692WyArDBInf5puLCjxbICD6XkHgpuudis56EndyS7LYFmtTMny6UABNdQ==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opentelemetry/core": "2.5.0",
|
"@opentelemetry/core": "2.5.0",
|
||||||
"@opentelemetry/resources": "2.5.0",
|
"@opentelemetry/resources": "2.5.0",
|
||||||
@@ -4081,6 +4089,7 @@
|
|||||||
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
|
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
}
|
}
|
||||||
@@ -4355,6 +4364,7 @@
|
|||||||
"integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==",
|
"integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.35.0",
|
"@typescript-eslint/scope-manager": "8.35.0",
|
||||||
"@typescript-eslint/types": "8.35.0",
|
"@typescript-eslint/types": "8.35.0",
|
||||||
@@ -5228,6 +5238,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -5569,6 +5580,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/asciichart": {
|
||||||
|
"version": "1.5.25",
|
||||||
|
"resolved": "https://registry.npmjs.org/asciichart/-/asciichart-1.5.25.tgz",
|
||||||
|
"integrity": "sha512-PNxzXIPPOtWq8T7bgzBtk9cI2lgS4SJZthUHEiQ1aoIc3lNzGfUvIvo9LiAnq26TACo9t1/4qP6KTGAUbzX9Xg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/assertion-error": {
|
"node_modules/assertion-error": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
|
||||||
@@ -7362,7 +7379,8 @@
|
|||||||
"version": "0.0.1581282",
|
"version": "0.0.1581282",
|
||||||
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1581282.tgz",
|
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1581282.tgz",
|
||||||
"integrity": "sha512-nv7iKtNZQshSW2hKzYNr46nM/Cfh5SEvE2oV0/SEGgc9XupIY5ggf84Cz8eJIkBce7S3bmTAauFD6aysMpnqsQ==",
|
"integrity": "sha512-nv7iKtNZQshSW2hKzYNr46nM/Cfh5SEvE2oV0/SEGgc9XupIY5ggf84Cz8eJIkBce7S3bmTAauFD6aysMpnqsQ==",
|
||||||
"license": "BSD-3-Clause"
|
"license": "BSD-3-Clause",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/dezalgo": {
|
"node_modules/dezalgo": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
@@ -7946,6 +7964,7 @@
|
|||||||
"integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==",
|
"integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.2.0",
|
"@eslint-community/eslint-utils": "^4.2.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
@@ -8463,6 +8482,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
|
||||||
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
|
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"accepts": "^2.0.0",
|
"accepts": "^2.0.0",
|
||||||
"body-parser": "^2.2.1",
|
"body-parser": "^2.2.1",
|
||||||
@@ -9775,6 +9795,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.7.tgz",
|
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.7.tgz",
|
||||||
"integrity": "sha512-jq9l1DM0zVIvsm3lv9Nw9nlJnMNPOcAtsbsgiUhWcFzPE99Gvo6yRTlszSLLYacMeQ6quHD6hMfId8crVHvexw==",
|
"integrity": "sha512-jq9l1DM0zVIvsm3lv9Nw9nlJnMNPOcAtsbsgiUhWcFzPE99Gvo6yRTlszSLLYacMeQ6quHD6hMfId8crVHvexw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.9.0"
|
"node": ">=16.9.0"
|
||||||
}
|
}
|
||||||
@@ -10053,6 +10074,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@jrichman/ink/-/ink-6.6.7.tgz",
|
"resolved": "https://registry.npmjs.org/@jrichman/ink/-/ink-6.6.7.tgz",
|
||||||
"integrity": "sha512-bDzQLpLzK/dn9Ur/Ku88ZZR9totVcMGrGYAgPHidsAAbe9NKztU1fggj/iu0wRp5g1kBeALb3cfagFGdDxAU1w==",
|
"integrity": "sha512-bDzQLpLzK/dn9Ur/Ku88ZZR9totVcMGrGYAgPHidsAAbe9NKztU1fggj/iu0wRp5g1kBeALb3cfagFGdDxAU1w==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-escapes": "^7.0.0",
|
"ansi-escapes": "^7.0.0",
|
||||||
"ansi-styles": "^6.2.3",
|
"ansi-styles": "^6.2.3",
|
||||||
@@ -13826,6 +13848,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
|
||||||
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
|
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -13836,6 +13859,7 @@
|
|||||||
"integrity": "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==",
|
"integrity": "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"shell-quote": "^1.6.1",
|
"shell-quote": "^1.6.1",
|
||||||
"ws": "^7"
|
"ws": "^7"
|
||||||
@@ -15985,6 +16009,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -16207,7 +16232,8 @@
|
|||||||
"version": "2.8.1",
|
"version": "2.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
"license": "0BSD"
|
"license": "0BSD",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/tsx": {
|
"node_modules/tsx": {
|
||||||
"version": "4.20.3",
|
"version": "4.20.3",
|
||||||
@@ -16215,6 +16241,7 @@
|
|||||||
"integrity": "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==",
|
"integrity": "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "~0.25.0",
|
"esbuild": "~0.25.0",
|
||||||
"get-tsconfig": "^4.7.5"
|
"get-tsconfig": "^4.7.5"
|
||||||
@@ -16380,6 +16407,7 @@
|
|||||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@@ -16602,6 +16630,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-7.2.2.tgz",
|
||||||
"integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==",
|
"integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.0",
|
||||||
"fdir": "^6.5.0",
|
"fdir": "^6.5.0",
|
||||||
@@ -16715,6 +16744,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -16727,6 +16757,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz",
|
||||||
"integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
|
"integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/chai": "^5.2.2",
|
"@types/chai": "^5.2.2",
|
||||||
"@vitest/expect": "3.2.4",
|
"@vitest/expect": "3.2.4",
|
||||||
@@ -17374,6 +17405,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
@@ -17817,6 +17849,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz",
|
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz",
|
||||||
"integrity": "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==",
|
"integrity": "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@grpc/proto-loader": "^0.8.0",
|
"@grpc/proto-loader": "^0.8.0",
|
||||||
"@js-sdsl/ordered-map": "^4.4.2"
|
"@js-sdsl/ordered-map": "^4.4.2"
|
||||||
@@ -17920,6 +17953,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -17979,6 +18013,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@google/gemini-cli-core": "file:../core",
|
"@google/gemini-cli-core": "file:../core",
|
||||||
"@lydell/node-pty": "1.1.0",
|
"@lydell/node-pty": "1.1.0",
|
||||||
|
"asciichart": "^1.5.25",
|
||||||
"strip-ansi": "^7.1.2",
|
"strip-ansi": "^7.1.2",
|
||||||
"vitest": "^3.2.4"
|
"vitest": "^3.2.4"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -51,6 +51,8 @@
|
|||||||
"test:integration:all": "npm run test:integration:sandbox:none && npm run test:integration:sandbox:docker && npm run test:integration:sandbox:podman",
|
"test:integration:all": "npm run test:integration:sandbox:none && npm run test:integration:sandbox:docker && npm run test:integration:sandbox:podman",
|
||||||
"test:integration:flaky": "cross-env RUN_FLAKY_INTEGRATION=1 npm run test:integration:sandbox:none",
|
"test:integration:flaky": "cross-env RUN_FLAKY_INTEGRATION=1 npm run test:integration:sandbox:none",
|
||||||
"test:integration:sandbox:none": "cross-env GEMINI_SANDBOX=false vitest run --root ./integration-tests",
|
"test:integration:sandbox:none": "cross-env GEMINI_SANDBOX=false vitest run --root ./integration-tests",
|
||||||
|
"test:memory": "vitest run --root ./memory-tests",
|
||||||
|
"test:memory:update-baselines": "cross-env UPDATE_MEMORY_BASELINES=true vitest run --root ./memory-tests",
|
||||||
"test:integration:sandbox:docker": "cross-env GEMINI_SANDBOX=docker npm run build:sandbox && cross-env GEMINI_SANDBOX=docker vitest run --root ./integration-tests",
|
"test:integration:sandbox:docker": "cross-env GEMINI_SANDBOX=docker npm run build:sandbox && cross-env GEMINI_SANDBOX=docker vitest run --root ./integration-tests",
|
||||||
"test:integration:sandbox:podman": "cross-env GEMINI_SANDBOX=podman vitest run --root ./integration-tests",
|
"test:integration:sandbox:podman": "cross-env GEMINI_SANDBOX=podman vitest run --root ./integration-tests",
|
||||||
"lint": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" eslint . --cache --max-warnings 0",
|
"lint": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" eslint . --cache --max-warnings 0",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env -S node --no-warnings=DEP0040
|
#!/usr/bin/env node
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @license
|
* @license
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env -S node --no-warnings=DEP0040
|
#!/usr/bin/env node
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @license
|
* @license
|
||||||
|
|||||||
@@ -26,5 +26,11 @@ describe('CommandHandler', () => {
|
|||||||
|
|
||||||
const init = parse('/init');
|
const init = parse('/init');
|
||||||
expect(init.commandToExecute?.name).toBe('init');
|
expect(init.commandToExecute?.name).toBe('init');
|
||||||
|
|
||||||
|
const about = parse('/about');
|
||||||
|
expect(about.commandToExecute?.name).toBe('about');
|
||||||
|
|
||||||
|
const help = parse('/help');
|
||||||
|
expect(help.commandToExecute?.name).toBe('help');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import { MemoryCommand } from './commands/memory.js';
|
|||||||
import { ExtensionsCommand } from './commands/extensions.js';
|
import { ExtensionsCommand } from './commands/extensions.js';
|
||||||
import { InitCommand } from './commands/init.js';
|
import { InitCommand } from './commands/init.js';
|
||||||
import { RestoreCommand } from './commands/restore.js';
|
import { RestoreCommand } from './commands/restore.js';
|
||||||
|
import { AboutCommand } from './commands/about.js';
|
||||||
|
import { HelpCommand } from './commands/help.js';
|
||||||
|
|
||||||
export class CommandHandler {
|
export class CommandHandler {
|
||||||
private registry: CommandRegistry;
|
private registry: CommandRegistry;
|
||||||
@@ -24,6 +26,8 @@ export class CommandHandler {
|
|||||||
registry.register(new ExtensionsCommand());
|
registry.register(new ExtensionsCommand());
|
||||||
registry.register(new InitCommand());
|
registry.register(new InitCommand());
|
||||||
registry.register(new RestoreCommand());
|
registry.register(new RestoreCommand());
|
||||||
|
registry.register(new AboutCommand());
|
||||||
|
registry.register(new HelpCommand(registry));
|
||||||
return registry;
|
return registry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2026 Google LLC
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
IdeClient,
|
||||||
|
UserAccountManager,
|
||||||
|
getVersion,
|
||||||
|
} from '@google/gemini-cli-core';
|
||||||
|
import type {
|
||||||
|
Command,
|
||||||
|
CommandContext,
|
||||||
|
CommandExecutionResponse,
|
||||||
|
} from './types.js';
|
||||||
|
import process from 'node:process';
|
||||||
|
|
||||||
|
export class AboutCommand implements Command {
|
||||||
|
readonly name = 'about';
|
||||||
|
readonly description = 'Show version and environment info';
|
||||||
|
|
||||||
|
async execute(
|
||||||
|
context: CommandContext,
|
||||||
|
_args: string[] = [],
|
||||||
|
): Promise<CommandExecutionResponse> {
|
||||||
|
const osVersion = process.platform;
|
||||||
|
let sandboxEnv = 'no sandbox';
|
||||||
|
if (process.env['SANDBOX'] && process.env['SANDBOX'] !== 'sandbox-exec') {
|
||||||
|
sandboxEnv = process.env['SANDBOX'];
|
||||||
|
} else if (process.env['SANDBOX'] === 'sandbox-exec') {
|
||||||
|
sandboxEnv = `sandbox-exec (${
|
||||||
|
process.env['SEATBELT_PROFILE'] || 'unknown'
|
||||||
|
})`;
|
||||||
|
}
|
||||||
|
const modelVersion = context.agentContext.config.getModel() || 'Unknown';
|
||||||
|
const cliVersion = await getVersion();
|
||||||
|
const selectedAuthType =
|
||||||
|
context.settings.merged?.security?.auth?.selectedType ?? '';
|
||||||
|
const gcpProject = process.env['GOOGLE_CLOUD_PROJECT'] || '';
|
||||||
|
const ideClient = await getIdeClientName(context);
|
||||||
|
|
||||||
|
const userAccountManager = new UserAccountManager();
|
||||||
|
const cachedAccount = userAccountManager.getCachedGoogleAccount();
|
||||||
|
const userEmail = cachedAccount ?? 'Unknown';
|
||||||
|
|
||||||
|
const tier = context.agentContext.config.getUserTierName() || 'Unknown';
|
||||||
|
|
||||||
|
const info = [
|
||||||
|
`- Version: ${cliVersion}`,
|
||||||
|
`- OS: ${osVersion}`,
|
||||||
|
`- Sandbox: ${sandboxEnv}`,
|
||||||
|
`- Model: ${modelVersion}`,
|
||||||
|
`- Auth Type: ${selectedAuthType}`,
|
||||||
|
`- GCP Project: ${gcpProject}`,
|
||||||
|
`- IDE Client: ${ideClient}`,
|
||||||
|
`- User Email: ${userEmail}`,
|
||||||
|
`- Tier: ${tier}`,
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: this.name,
|
||||||
|
data: `Gemini CLI Info:\n${info}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getIdeClientName(context: CommandContext) {
|
||||||
|
if (!context.agentContext.config.getIdeMode()) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const ideClient = await IdeClient.getInstance();
|
||||||
|
return ideClient?.getDetectedIdeDisplayName() ?? '';
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2026 Google LLC
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { HelpCommand } from './help.js';
|
||||||
|
import { CommandRegistry } from './commandRegistry.js';
|
||||||
|
import type { Command, CommandContext } from './types.js';
|
||||||
|
|
||||||
|
describe('HelpCommand', () => {
|
||||||
|
it('returns formatted help text with sorted commands', async () => {
|
||||||
|
const registry = new CommandRegistry();
|
||||||
|
|
||||||
|
const cmdB: Command = {
|
||||||
|
name: 'bravo',
|
||||||
|
description: 'Bravo command',
|
||||||
|
execute: async () => ({ name: 'bravo', data: '' }),
|
||||||
|
};
|
||||||
|
|
||||||
|
const cmdA: Command = {
|
||||||
|
name: 'alpha',
|
||||||
|
description: 'Alpha command',
|
||||||
|
execute: async () => ({ name: 'alpha', data: '' }),
|
||||||
|
};
|
||||||
|
|
||||||
|
registry.register(cmdB);
|
||||||
|
registry.register(cmdA);
|
||||||
|
|
||||||
|
const helpCommand = new HelpCommand(registry);
|
||||||
|
|
||||||
|
const context = {} as CommandContext;
|
||||||
|
|
||||||
|
const response = await helpCommand.execute(context, []);
|
||||||
|
|
||||||
|
expect(response.name).toBe('help');
|
||||||
|
|
||||||
|
const data = response.data as string;
|
||||||
|
|
||||||
|
expect(data).toContain('Gemini CLI Help:');
|
||||||
|
expect(data).toContain('### Basics');
|
||||||
|
expect(data).toContain('### Commands');
|
||||||
|
|
||||||
|
const lines = data.split('\n');
|
||||||
|
const alphaIndex = lines.findIndex((l) => l.includes('/alpha'));
|
||||||
|
const bravoIndex = lines.findIndex((l) => l.includes('/bravo'));
|
||||||
|
|
||||||
|
expect(alphaIndex).toBeLessThan(bravoIndex);
|
||||||
|
expect(alphaIndex).not.toBe(-1);
|
||||||
|
expect(bravoIndex).not.toBe(-1);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2026 Google LLC
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type {
|
||||||
|
Command,
|
||||||
|
CommandContext,
|
||||||
|
CommandExecutionResponse,
|
||||||
|
} from './types.js';
|
||||||
|
import type { CommandRegistry } from './commandRegistry.js';
|
||||||
|
|
||||||
|
export class HelpCommand implements Command {
|
||||||
|
readonly name = 'help';
|
||||||
|
readonly description = 'Show available commands';
|
||||||
|
|
||||||
|
constructor(private registry: CommandRegistry) {}
|
||||||
|
|
||||||
|
async execute(
|
||||||
|
_context: CommandContext,
|
||||||
|
_args: string[] = [],
|
||||||
|
): Promise<CommandExecutionResponse> {
|
||||||
|
const commands = this.registry
|
||||||
|
.getAllCommands()
|
||||||
|
.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
|
||||||
|
const lines: string[] = [];
|
||||||
|
|
||||||
|
lines.push('Gemini CLI Help:');
|
||||||
|
lines.push('');
|
||||||
|
lines.push('### Basics');
|
||||||
|
lines.push(
|
||||||
|
'- **Add context**: Use `@` to specify files for context (e.g., `@src/myFile.ts`) to target specific files or folders.',
|
||||||
|
);
|
||||||
|
lines.push('');
|
||||||
|
|
||||||
|
lines.push('### Commands');
|
||||||
|
for (const cmd of commands) {
|
||||||
|
if (cmd.description) {
|
||||||
|
lines.push(`- **/${cmd.name}** - ${cmd.description}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: this.name,
|
||||||
|
data: lines.join('\n'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,8 +4,16 @@
|
|||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
|
import {
|
||||||
import { coreEvents, type Config } from '@google/gemini-cli-core';
|
vi,
|
||||||
|
describe,
|
||||||
|
it,
|
||||||
|
expect,
|
||||||
|
beforeEach,
|
||||||
|
afterEach,
|
||||||
|
type MockInstance,
|
||||||
|
} from 'vitest';
|
||||||
|
import { type Config } from '@google/gemini-cli-core';
|
||||||
import { handleList, listCommand } from './list.js';
|
import { handleList, listCommand } from './list.js';
|
||||||
import { loadSettings, type LoadedSettings } from '../../config/settings.js';
|
import { loadSettings, type LoadedSettings } from '../../config/settings.js';
|
||||||
import { loadCliConfig } from '../../config/config.js';
|
import { loadCliConfig } from '../../config/config.js';
|
||||||
@@ -32,12 +40,16 @@ vi.mock('../utils.js', () => ({
|
|||||||
describe('skills list command', () => {
|
describe('skills list command', () => {
|
||||||
const mockLoadSettings = vi.mocked(loadSettings);
|
const mockLoadSettings = vi.mocked(loadSettings);
|
||||||
const mockLoadCliConfig = vi.mocked(loadCliConfig);
|
const mockLoadCliConfig = vi.mocked(loadCliConfig);
|
||||||
|
let stdoutWriteSpy: MockInstance<typeof process.stdout.write>;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
mockLoadSettings.mockReturnValue({
|
mockLoadSettings.mockReturnValue({
|
||||||
merged: {},
|
merged: {},
|
||||||
} as unknown as LoadedSettings);
|
} as unknown as LoadedSettings);
|
||||||
|
stdoutWriteSpy = vi
|
||||||
|
.spyOn(process.stdout, 'write')
|
||||||
|
.mockImplementation(() => true);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -56,10 +68,7 @@ describe('skills list command', () => {
|
|||||||
|
|
||||||
await handleList({});
|
await handleList({});
|
||||||
|
|
||||||
expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith(
|
expect(stdoutWriteSpy).toHaveBeenCalledWith('No skills discovered.\n');
|
||||||
'log',
|
|
||||||
'No skills discovered.',
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should list all discovered skills', async () => {
|
it('should list all discovered skills', async () => {
|
||||||
@@ -87,24 +96,19 @@ describe('skills list command', () => {
|
|||||||
|
|
||||||
await handleList({});
|
await handleList({});
|
||||||
|
|
||||||
expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith(
|
expect(stdoutWriteSpy).toHaveBeenCalledWith(
|
||||||
'log',
|
chalk.bold('Discovered Agent Skills:') + '\n\n',
|
||||||
chalk.bold('Discovered Agent Skills:'),
|
|
||||||
);
|
);
|
||||||
expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith(
|
expect(stdoutWriteSpy).toHaveBeenCalledWith(
|
||||||
'log',
|
|
||||||
expect.stringContaining('skill1'),
|
expect.stringContaining('skill1'),
|
||||||
);
|
);
|
||||||
expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith(
|
expect(stdoutWriteSpy).toHaveBeenCalledWith(
|
||||||
'log',
|
|
||||||
expect.stringContaining(chalk.green('[Enabled]')),
|
expect.stringContaining(chalk.green('[Enabled]')),
|
||||||
);
|
);
|
||||||
expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith(
|
expect(stdoutWriteSpy).toHaveBeenCalledWith(
|
||||||
'log',
|
|
||||||
expect.stringContaining('skill2'),
|
expect.stringContaining('skill2'),
|
||||||
);
|
);
|
||||||
expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith(
|
expect(stdoutWriteSpy).toHaveBeenCalledWith(
|
||||||
'log',
|
|
||||||
expect.stringContaining(chalk.red('[Disabled]')),
|
expect.stringContaining(chalk.red('[Disabled]')),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -135,12 +139,10 @@ describe('skills list command', () => {
|
|||||||
|
|
||||||
// Default
|
// Default
|
||||||
await handleList({ all: false });
|
await handleList({ all: false });
|
||||||
expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith(
|
expect(stdoutWriteSpy).toHaveBeenCalledWith(
|
||||||
'log',
|
|
||||||
expect.stringContaining('regular'),
|
expect.stringContaining('regular'),
|
||||||
);
|
);
|
||||||
expect(coreEvents.emitConsoleLog).not.toHaveBeenCalledWith(
|
expect(stdoutWriteSpy).not.toHaveBeenCalledWith(
|
||||||
'log',
|
|
||||||
expect.stringContaining('builtin'),
|
expect.stringContaining('builtin'),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -148,16 +150,13 @@ describe('skills list command', () => {
|
|||||||
|
|
||||||
// With all: true
|
// With all: true
|
||||||
await handleList({ all: true });
|
await handleList({ all: true });
|
||||||
expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith(
|
expect(stdoutWriteSpy).toHaveBeenCalledWith(
|
||||||
'log',
|
|
||||||
expect.stringContaining('regular'),
|
expect.stringContaining('regular'),
|
||||||
);
|
);
|
||||||
expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith(
|
expect(stdoutWriteSpy).toHaveBeenCalledWith(
|
||||||
'log',
|
|
||||||
expect.stringContaining('builtin'),
|
expect.stringContaining('builtin'),
|
||||||
);
|
);
|
||||||
expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith(
|
expect(stdoutWriteSpy).toHaveBeenCalledWith(
|
||||||
'log',
|
|
||||||
expect.stringContaining(chalk.gray(' [Built-in]')),
|
expect.stringContaining(chalk.gray(' [Built-in]')),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { CommandModule } from 'yargs';
|
import type { CommandModule } from 'yargs';
|
||||||
import { debugLogger } from '@google/gemini-cli-core';
|
|
||||||
import { loadSettings } from '../../config/settings.js';
|
import { loadSettings } from '../../config/settings.js';
|
||||||
import { loadCliConfig, type CliArgs } from '../../config/config.js';
|
import { loadCliConfig, type CliArgs } from '../../config/config.js';
|
||||||
import { exitCli } from '../utils.js';
|
import { exitCli } from '../utils.js';
|
||||||
@@ -42,12 +41,11 @@ export async function handleList(args: { all?: boolean }) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (skills.length === 0) {
|
if (skills.length === 0) {
|
||||||
debugLogger.log('No skills discovered.');
|
process.stdout.write('No skills discovered.\n');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
debugLogger.log(chalk.bold('Discovered Agent Skills:'));
|
process.stdout.write(chalk.bold('Discovered Agent Skills:') + '\n\n');
|
||||||
debugLogger.log('');
|
|
||||||
|
|
||||||
for (const skill of skills) {
|
for (const skill of skills) {
|
||||||
const status = skill.disabled
|
const status = skill.disabled
|
||||||
@@ -56,10 +54,11 @@ export async function handleList(args: { all?: boolean }) {
|
|||||||
|
|
||||||
const builtinSuffix = skill.isBuiltin ? chalk.gray(' [Built-in]') : '';
|
const builtinSuffix = skill.isBuiltin ? chalk.gray(' [Built-in]') : '';
|
||||||
|
|
||||||
debugLogger.log(`${chalk.bold(skill.name)} ${status}${builtinSuffix}`);
|
process.stdout.write(
|
||||||
debugLogger.log(` Description: ${skill.description}`);
|
`${chalk.bold(skill.name)} ${status}${builtinSuffix}\n`,
|
||||||
debugLogger.log(` Location: ${skill.location}`);
|
);
|
||||||
debugLogger.log('');
|
process.stdout.write(` Description: ${skill.description}\n`);
|
||||||
|
process.stdout.write(` Location: ${skill.location}\n\n`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -938,6 +938,8 @@ export async function loadCliConfig(
|
|||||||
: undefined,
|
: undefined,
|
||||||
blockedEnvironmentVariables:
|
blockedEnvironmentVariables:
|
||||||
settings.security?.environmentVariableRedaction?.blocked,
|
settings.security?.environmentVariableRedaction?.blocked,
|
||||||
|
allowedEnvironmentVariables:
|
||||||
|
settings.security?.environmentVariableRedaction?.allowed,
|
||||||
enableEnvironmentVariableRedaction:
|
enableEnvironmentVariableRedaction:
|
||||||
settings.security?.environmentVariableRedaction?.enabled,
|
settings.security?.environmentVariableRedaction?.enabled,
|
||||||
userMemory: memoryContent,
|
userMemory: memoryContent,
|
||||||
|
|||||||
@@ -520,8 +520,8 @@ describe('Policy Engine Integration Tests', () => {
|
|||||||
const readOnlyToolRule = rules.find(
|
const readOnlyToolRule = rules.find(
|
||||||
(r) => r.toolName === 'glob' && !r.subagent,
|
(r) => r.toolName === 'glob' && !r.subagent,
|
||||||
);
|
);
|
||||||
// Priority 70 in default tier → 1.07 (Overriding Plan Mode Deny)
|
// Priority 50 in default tier → 1.05 (Overriding Plan Mode Deny)
|
||||||
expect(readOnlyToolRule?.priority).toBeCloseTo(1.07, 5);
|
expect(readOnlyToolRule?.priority).toBeCloseTo(1.05, 5);
|
||||||
|
|
||||||
// Verify the engine applies these priorities correctly
|
// Verify the engine applies these priorities correctly
|
||||||
expect(
|
expect(
|
||||||
@@ -677,8 +677,8 @@ describe('Policy Engine Integration Tests', () => {
|
|||||||
expect(server1Rule?.priority).toBe(4.1); // Allowed servers (user tier)
|
expect(server1Rule?.priority).toBe(4.1); // Allowed servers (user tier)
|
||||||
|
|
||||||
const globRule = rules.find((r) => r.toolName === 'glob' && !r.subagent);
|
const globRule = rules.find((r) => r.toolName === 'glob' && !r.subagent);
|
||||||
// Priority 70 in default tier → 1.07
|
// Priority 50 in default tier → 1.05
|
||||||
expect(globRule?.priority).toBeCloseTo(1.07, 5); // Auto-accept read-only
|
expect(globRule?.priority).toBeCloseTo(1.05, 5); // Auto-accept read-only
|
||||||
|
|
||||||
// The PolicyEngine will sort these by priority when it's created
|
// The PolicyEngine will sort these by priority when it's created
|
||||||
const engine = new PolicyEngine(config);
|
const engine = new PolicyEngine(config);
|
||||||
|
|||||||
@@ -576,7 +576,7 @@ const SETTINGS_SCHEMA = {
|
|||||||
label: 'Compact Tool Output',
|
label: 'Compact Tool Output',
|
||||||
category: 'UI',
|
category: 'UI',
|
||||||
requiresRestart: false,
|
requiresRestart: false,
|
||||||
default: false,
|
default: true,
|
||||||
description:
|
description:
|
||||||
'Display tool outputs (like directory listings and file reads) in a compact, structured format.',
|
'Display tool outputs (like directory listings and file reads) in a compact, structured format.',
|
||||||
showInDialog: true,
|
showInDialog: true,
|
||||||
@@ -757,7 +757,7 @@ const SETTINGS_SCHEMA = {
|
|||||||
label: 'Terminal Buffer',
|
label: 'Terminal Buffer',
|
||||||
category: 'UI',
|
category: 'UI',
|
||||||
requiresRestart: true,
|
requiresRestart: true,
|
||||||
default: true,
|
default: false,
|
||||||
description: 'Use the new terminal buffer architecture for rendering.',
|
description: 'Use the new terminal buffer architecture for rendering.',
|
||||||
showInDialog: true,
|
showInDialog: true,
|
||||||
},
|
},
|
||||||
@@ -1527,7 +1527,7 @@ const SETTINGS_SCHEMA = {
|
|||||||
label: 'Show Color',
|
label: 'Show Color',
|
||||||
category: 'Tools',
|
category: 'Tools',
|
||||||
requiresRestart: false,
|
requiresRestart: false,
|
||||||
default: false,
|
default: true,
|
||||||
description: 'Show color in shell output.',
|
description: 'Show color in shell output.',
|
||||||
showInDialog: true,
|
showInDialog: true,
|
||||||
},
|
},
|
||||||
@@ -1711,10 +1711,10 @@ const SETTINGS_SCHEMA = {
|
|||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
label: 'Tool Sandboxing',
|
label: 'Tool Sandboxing',
|
||||||
category: 'Security',
|
category: 'Security',
|
||||||
requiresRestart: false,
|
requiresRestart: true,
|
||||||
default: false,
|
default: false,
|
||||||
description:
|
description:
|
||||||
'Experimental tool-level sandboxing (implementation in progress).',
|
'Tool-level sandboxing. Isolates individual tools instead of the entire CLI process.',
|
||||||
showInDialog: true,
|
showInDialog: true,
|
||||||
},
|
},
|
||||||
disableYoloMode: {
|
disableYoloMode: {
|
||||||
@@ -1970,6 +1970,16 @@ const SETTINGS_SCHEMA = {
|
|||||||
description: 'Enable non-interactive agent sessions.',
|
description: 'Enable non-interactive agent sessions.',
|
||||||
showInDialog: false,
|
showInDialog: false,
|
||||||
},
|
},
|
||||||
|
agentSessionInteractiveEnabled: {
|
||||||
|
type: 'boolean',
|
||||||
|
label: 'Interactive Agent Session Enabled',
|
||||||
|
category: 'Experimental',
|
||||||
|
requiresRestart: true,
|
||||||
|
default: false,
|
||||||
|
description:
|
||||||
|
'Enable the agent session implementation for the interactive CLI.',
|
||||||
|
showInDialog: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
enableAgents: {
|
enableAgents: {
|
||||||
|
|||||||
@@ -379,15 +379,30 @@ describe('initializeOutputListenersAndFlush', () => {
|
|||||||
describe('getNodeMemoryArgs', () => {
|
describe('getNodeMemoryArgs', () => {
|
||||||
let osTotalMemSpy: MockInstance;
|
let osTotalMemSpy: MockInstance;
|
||||||
let v8GetHeapStatisticsSpy: MockInstance;
|
let v8GetHeapStatisticsSpy: MockInstance;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
let originalConfig: any;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
osTotalMemSpy = vi.spyOn(os, 'totalmem');
|
osTotalMemSpy = vi.spyOn(os, 'totalmem');
|
||||||
v8GetHeapStatisticsSpy = vi.spyOn(v8, 'getHeapStatistics');
|
v8GetHeapStatisticsSpy = vi.spyOn(v8, 'getHeapStatistics');
|
||||||
delete process.env['GEMINI_CLI_NO_RELAUNCH'];
|
delete process.env['GEMINI_CLI_NO_RELAUNCH'];
|
||||||
|
|
||||||
|
originalConfig = process.config;
|
||||||
|
Object.defineProperty(process, 'config', {
|
||||||
|
value: {
|
||||||
|
...originalConfig,
|
||||||
|
variables: { ...originalConfig?.variables, v8_enable_sandbox: 1 },
|
||||||
|
},
|
||||||
|
configurable: true,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
vi.restoreAllMocks();
|
vi.restoreAllMocks();
|
||||||
|
Object.defineProperty(process, 'config', {
|
||||||
|
value: originalConfig,
|
||||||
|
configurable: true,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return empty array if GEMINI_CLI_NO_RELAUNCH is set', () => {
|
it('should return empty array if GEMINI_CLI_NO_RELAUNCH is set', () => {
|
||||||
@@ -400,8 +415,10 @@ describe('getNodeMemoryArgs', () => {
|
|||||||
v8GetHeapStatisticsSpy.mockReturnValue({
|
v8GetHeapStatisticsSpy.mockReturnValue({
|
||||||
heap_size_limit: 8 * 1024 * 1024 * 1024, // 8GB
|
heap_size_limit: 8 * 1024 * 1024 * 1024, // 8GB
|
||||||
});
|
});
|
||||||
// Target is 50% of 16GB = 8GB. Current is 8GB. No relaunch needed.
|
// Target is 50% of 16GB = 8GB. Current is 8GB. Relaunch needed for EPT size only.
|
||||||
expect(getNodeMemoryArgs(false)).toEqual([]);
|
expect(getNodeMemoryArgs(false)).toEqual([
|
||||||
|
'--max-external-pointer-table-size=268435456',
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return memory args if current heap limit is insufficient', () => {
|
it('should return memory args if current heap limit is insufficient', () => {
|
||||||
@@ -409,8 +426,11 @@ describe('getNodeMemoryArgs', () => {
|
|||||||
v8GetHeapStatisticsSpy.mockReturnValue({
|
v8GetHeapStatisticsSpy.mockReturnValue({
|
||||||
heap_size_limit: 4 * 1024 * 1024 * 1024, // 4GB
|
heap_size_limit: 4 * 1024 * 1024 * 1024, // 4GB
|
||||||
});
|
});
|
||||||
// Target is 50% of 16GB = 8GB. Current is 4GB. Relaunch needed.
|
// Target is 50% of 16GB = 8GB. Current is 4GB. Relaunch needed for both.
|
||||||
expect(getNodeMemoryArgs(false)).toEqual(['--max-old-space-size=8192']);
|
expect(getNodeMemoryArgs(false)).toEqual([
|
||||||
|
'--max-external-pointer-table-size=268435456',
|
||||||
|
'--max-old-space-size=8192',
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should log debug info when isDebugMode is true', () => {
|
it('should log debug info when isDebugMode is true', () => {
|
||||||
|
|||||||
@@ -111,6 +111,8 @@ export function validateDnsResolutionOrder(
|
|||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DEFAULT_EPT_SIZE = (256 * 1024 * 1024).toString();
|
||||||
|
|
||||||
export function getNodeMemoryArgs(isDebugMode: boolean): string[] {
|
export function getNodeMemoryArgs(isDebugMode: boolean): string[] {
|
||||||
const totalMemoryMB = os.totalmem() / (1024 * 1024);
|
const totalMemoryMB = os.totalmem() / (1024 * 1024);
|
||||||
const heapStats = v8.getHeapStatistics();
|
const heapStats = v8.getHeapStatistics();
|
||||||
@@ -130,16 +132,35 @@ export function getNodeMemoryArgs(isDebugMode: boolean): string[] {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const args: string[] = [];
|
||||||
|
|
||||||
|
// Automatically expand the V8 External Pointer Table to 256MB to prevent
|
||||||
|
// out-of-memory crashes during high native-handle concurrency.
|
||||||
|
// Note: Only supported in specific Node.js versions compiled with V8 Sandbox enabled.
|
||||||
|
const eptFlag = `--max-external-pointer-table-size=${DEFAULT_EPT_SIZE}`;
|
||||||
|
const isV8SandboxEnabled =
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-type-assertion
|
||||||
|
(process.config?.variables as any)?.v8_enable_sandbox === 1;
|
||||||
|
|
||||||
|
if (
|
||||||
|
isV8SandboxEnabled &&
|
||||||
|
!process.execArgv.some((arg) =>
|
||||||
|
arg.startsWith('--max-external-pointer-table-size'),
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
args.push(eptFlag);
|
||||||
|
}
|
||||||
|
|
||||||
if (targetMaxOldSpaceSizeInMB > currentMaxOldSpaceSizeMb) {
|
if (targetMaxOldSpaceSizeInMB > currentMaxOldSpaceSizeMb) {
|
||||||
if (isDebugMode) {
|
if (isDebugMode) {
|
||||||
debugLogger.debug(
|
debugLogger.debug(
|
||||||
`Need to relaunch with more memory: ${targetMaxOldSpaceSizeInMB.toFixed(2)} MB`,
|
`Need to relaunch with more memory: ${targetMaxOldSpaceSizeInMB.toFixed(2)} MB`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return [`--max-old-space-size=${targetMaxOldSpaceSizeInMB}`];
|
args.push(`--max-old-space-size=${targetMaxOldSpaceSizeInMB}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return [];
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setupUnhandledRejectionHandler() {
|
export function setupUnhandledRejectionHandler() {
|
||||||
|
|||||||
@@ -156,8 +156,9 @@ export async function startInteractiveUI(
|
|||||||
useAlternateBuffer || config.getUseTerminalBuffer(),
|
useAlternateBuffer || config.getUseTerminalBuffer(),
|
||||||
patchConsole: false,
|
patchConsole: false,
|
||||||
alternateBuffer: useAlternateBuffer,
|
alternateBuffer: useAlternateBuffer,
|
||||||
renderProcess: config.getUseRenderProcess(),
|
|
||||||
terminalBuffer: config.getUseTerminalBuffer(),
|
terminalBuffer: config.getUseTerminalBuffer(),
|
||||||
|
renderProcess:
|
||||||
|
config.getUseRenderProcess() && config.getUseTerminalBuffer(),
|
||||||
incrementalRendering:
|
incrementalRendering:
|
||||||
settings.merged.ui.incrementalRendering !== false &&
|
settings.merged.ui.incrementalRendering !== false &&
|
||||||
useAlternateBuffer &&
|
useAlternateBuffer &&
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
|||||||
Scheduler: class {
|
Scheduler: class {
|
||||||
schedule = mockSchedulerSchedule;
|
schedule = mockSchedulerSchedule;
|
||||||
cancelAll = vi.fn();
|
cancelAll = vi.fn();
|
||||||
|
dispose = vi.fn();
|
||||||
},
|
},
|
||||||
isTelemetrySdkInitialized: vi.fn().mockReturnValue(true),
|
isTelemetrySdkInitialized: vi.fn().mockReturnValue(true),
|
||||||
ChatRecordingService: MockChatRecordingService,
|
ChatRecordingService: MockChatRecordingService,
|
||||||
|
|||||||
@@ -187,6 +187,7 @@ export async function runNonInteractive(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let errorToHandle: unknown | undefined;
|
let errorToHandle: unknown | undefined;
|
||||||
|
let scheduler: Scheduler | undefined;
|
||||||
try {
|
try {
|
||||||
consolePatcher.patch();
|
consolePatcher.patch();
|
||||||
|
|
||||||
@@ -215,7 +216,7 @@ export async function runNonInteractive(
|
|||||||
});
|
});
|
||||||
|
|
||||||
const geminiClient = config.getGeminiClient();
|
const geminiClient = config.getGeminiClient();
|
||||||
const scheduler = new Scheduler({
|
scheduler = new Scheduler({
|
||||||
context: config,
|
context: config,
|
||||||
messageBus: config.getMessageBus(),
|
messageBus: config.getMessageBus(),
|
||||||
getPreferredEditor: () => undefined,
|
getPreferredEditor: () => undefined,
|
||||||
@@ -528,6 +529,7 @@ export async function runNonInteractive(
|
|||||||
// Cleanup stdin cancellation before other cleanup
|
// Cleanup stdin cancellation before other cleanup
|
||||||
cleanupStdinCancellation();
|
cleanupStdinCancellation();
|
||||||
|
|
||||||
|
scheduler?.dispose();
|
||||||
consolePatcher.cleanup();
|
consolePatcher.cleanup();
|
||||||
coreEvents.off(CoreEvent.UserFeedback, handleUserFeedback);
|
coreEvents.off(CoreEvent.UserFeedback, handleUserFeedback);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
|||||||
Scheduler: class {
|
Scheduler: class {
|
||||||
schedule = mockSchedulerSchedule;
|
schedule = mockSchedulerSchedule;
|
||||||
cancelAll = vi.fn();
|
cancelAll = vi.fn();
|
||||||
|
dispose = vi.fn();
|
||||||
},
|
},
|
||||||
isTelemetrySdkInitialized: vi.fn().mockReturnValue(true),
|
isTelemetrySdkInitialized: vi.fn().mockReturnValue(true),
|
||||||
ChatRecordingService: MockChatRecordingService,
|
ChatRecordingService: MockChatRecordingService,
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import {
|
|||||||
LegacyAgentSession,
|
LegacyAgentSession,
|
||||||
ToolErrorType,
|
ToolErrorType,
|
||||||
geminiPartsToContentParts,
|
geminiPartsToContentParts,
|
||||||
|
debugLogger,
|
||||||
} from '@google/gemini-cli-core';
|
} from '@google/gemini-cli-core';
|
||||||
|
|
||||||
import type { Part } from '@google/genai';
|
import type { Part } from '@google/genai';
|
||||||
@@ -183,6 +184,7 @@ export async function runNonInteractive({
|
|||||||
};
|
};
|
||||||
|
|
||||||
let errorToHandle: unknown | undefined;
|
let errorToHandle: unknown | undefined;
|
||||||
|
let scheduler: Scheduler | undefined;
|
||||||
let abortSession = () => {};
|
let abortSession = () => {};
|
||||||
try {
|
try {
|
||||||
consolePatcher.patch();
|
consolePatcher.patch();
|
||||||
@@ -214,7 +216,7 @@ export async function runNonInteractive({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const geminiClient = config.getGeminiClient();
|
const geminiClient = config.getGeminiClient();
|
||||||
const scheduler = new Scheduler({
|
scheduler = new Scheduler({
|
||||||
context: config,
|
context: config,
|
||||||
messageBus: config.getMessageBus(),
|
messageBus: config.getMessageBus(),
|
||||||
getPreferredEditor: () => undefined,
|
getPreferredEditor: () => undefined,
|
||||||
@@ -599,6 +601,7 @@ export async function runNonInteractive({
|
|||||||
// Explicitly ignore these non-interactive events
|
// Explicitly ignore these non-interactive events
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
debugLogger.error('Unknown agent event type:', event);
|
||||||
event satisfies never;
|
event satisfies never;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -610,6 +613,7 @@ export async function runNonInteractive({
|
|||||||
cleanupStdinCancellation();
|
cleanupStdinCancellation();
|
||||||
abortController.signal.removeEventListener('abort', abortSession);
|
abortController.signal.removeEventListener('abort', abortSession);
|
||||||
|
|
||||||
|
scheduler?.dispose();
|
||||||
consolePatcher.cleanup();
|
consolePatcher.cleanup();
|
||||||
coreEvents.off(CoreEvent.UserFeedback, handleUserFeedback);
|
coreEvents.off(CoreEvent.UserFeedback, handleUserFeedback);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,6 +136,7 @@ export const createMockConfig = (overrides: Partial<Config> = {}): Config =>
|
|||||||
getRetryFetchErrors: vi.fn().mockReturnValue(true),
|
getRetryFetchErrors: vi.fn().mockReturnValue(true),
|
||||||
getEnableShellOutputEfficiency: vi.fn().mockReturnValue(true),
|
getEnableShellOutputEfficiency: vi.fn().mockReturnValue(true),
|
||||||
getShellToolInactivityTimeout: vi.fn().mockReturnValue(300000),
|
getShellToolInactivityTimeout: vi.fn().mockReturnValue(300000),
|
||||||
|
getRequestTimeoutMs: vi.fn().mockReturnValue(undefined),
|
||||||
getShellExecutionConfig: vi.fn().mockReturnValue({
|
getShellExecutionConfig: vi.fn().mockReturnValue({
|
||||||
sandboxManager: new NoopSandboxManager(),
|
sandboxManager: new NoopSandboxManager(),
|
||||||
sanitizationConfig: {
|
sanitizationConfig: {
|
||||||
|
|||||||
@@ -504,6 +504,8 @@ const baseMockUiState = {
|
|||||||
history: [],
|
history: [],
|
||||||
renderMarkdown: true,
|
renderMarkdown: true,
|
||||||
streamingState: StreamingState.Idle,
|
streamingState: StreamingState.Idle,
|
||||||
|
isConfigInitialized: true,
|
||||||
|
isAuthenticating: false,
|
||||||
terminalWidth: 100,
|
terminalWidth: 100,
|
||||||
terminalHeight: 40,
|
terminalHeight: 40,
|
||||||
currentModel: 'gemini-pro',
|
currentModel: 'gemini-pro',
|
||||||
@@ -598,6 +600,9 @@ const mockUIActions: UIActions = {
|
|||||||
clearAccountSuspension: vi.fn(),
|
clearAccountSuspension: vi.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
import { type TextBuffer } from '../ui/components/shared/text-buffer.js';
|
||||||
|
import { InputContext, type InputState } from '../ui/contexts/InputContext.js';
|
||||||
|
|
||||||
let capturedOverflowState: OverflowState | undefined;
|
let capturedOverflowState: OverflowState | undefined;
|
||||||
let capturedOverflowActions: OverflowActions | undefined;
|
let capturedOverflowActions: OverflowActions | undefined;
|
||||||
const ContextCapture: React.FC<{ children: React.ReactNode }> = ({
|
const ContextCapture: React.FC<{ children: React.ReactNode }> = ({
|
||||||
@@ -614,6 +619,7 @@ export const renderWithProviders = async (
|
|||||||
shellFocus = true,
|
shellFocus = true,
|
||||||
settings = mockSettings,
|
settings = mockSettings,
|
||||||
uiState: providedUiState,
|
uiState: providedUiState,
|
||||||
|
inputState: providedInputState,
|
||||||
width,
|
width,
|
||||||
mouseEventsEnabled = false,
|
mouseEventsEnabled = false,
|
||||||
config,
|
config,
|
||||||
@@ -625,6 +631,7 @@ export const renderWithProviders = async (
|
|||||||
shellFocus?: boolean;
|
shellFocus?: boolean;
|
||||||
settings?: LoadedSettings;
|
settings?: LoadedSettings;
|
||||||
uiState?: Partial<UIState>;
|
uiState?: Partial<UIState>;
|
||||||
|
inputState?: Partial<InputState>;
|
||||||
width?: number;
|
width?: number;
|
||||||
mouseEventsEnabled?: boolean;
|
mouseEventsEnabled?: boolean;
|
||||||
config?: Config;
|
config?: Config;
|
||||||
@@ -659,6 +666,18 @@ export const renderWithProviders = async (
|
|||||||
},
|
},
|
||||||
) as UIState;
|
) as UIState;
|
||||||
|
|
||||||
|
const inputState = {
|
||||||
|
buffer: { text: '' } as unknown as TextBuffer,
|
||||||
|
userMessages: [],
|
||||||
|
shellModeActive: false,
|
||||||
|
showEscapePrompt: false,
|
||||||
|
copyModeEnabled: false,
|
||||||
|
inputWidth: 80,
|
||||||
|
suggestionsWidth: 40,
|
||||||
|
...(providedUiState as unknown as Partial<InputState>),
|
||||||
|
...providedInputState,
|
||||||
|
};
|
||||||
|
|
||||||
if (persistentState?.get) {
|
if (persistentState?.get) {
|
||||||
persistentStateMock.get.mockImplementation(persistentState.get);
|
persistentStateMock.get.mockImplementation(persistentState.get);
|
||||||
}
|
}
|
||||||
@@ -708,63 +727,65 @@ export const renderWithProviders = async (
|
|||||||
<AppContext.Provider value={appState}>
|
<AppContext.Provider value={appState}>
|
||||||
<ConfigContext.Provider value={config}>
|
<ConfigContext.Provider value={config}>
|
||||||
<SettingsContext.Provider value={settings}>
|
<SettingsContext.Provider value={settings}>
|
||||||
<UIStateContext.Provider value={finalUiState}>
|
<InputContext.Provider value={inputState}>
|
||||||
<VimModeProvider>
|
<UIStateContext.Provider value={finalUiState}>
|
||||||
<ShellFocusContext.Provider value={shellFocus}>
|
<VimModeProvider>
|
||||||
<SessionStatsProvider>
|
<ShellFocusContext.Provider value={shellFocus}>
|
||||||
<StreamingContext.Provider
|
<SessionStatsProvider>
|
||||||
value={finalUiState.streamingState}
|
<StreamingContext.Provider
|
||||||
>
|
value={finalUiState.streamingState}
|
||||||
<UIActionsContext.Provider value={finalUIActions}>
|
>
|
||||||
<OverflowProvider>
|
<UIActionsContext.Provider value={finalUIActions}>
|
||||||
<ToolActionsProvider
|
<OverflowProvider>
|
||||||
config={config}
|
<ToolActionsProvider
|
||||||
toolCalls={allToolCalls}
|
config={config}
|
||||||
isExpanded={
|
toolCalls={allToolCalls}
|
||||||
toolActions?.isExpanded ??
|
isExpanded={
|
||||||
vi.fn().mockReturnValue(false)
|
toolActions?.isExpanded ??
|
||||||
}
|
vi.fn().mockReturnValue(false)
|
||||||
toggleExpansion={
|
}
|
||||||
toolActions?.toggleExpansion ?? vi.fn()
|
toggleExpansion={
|
||||||
}
|
toolActions?.toggleExpansion ?? vi.fn()
|
||||||
toggleAllExpansion={
|
}
|
||||||
toolActions?.toggleAllExpansion ?? vi.fn()
|
toggleAllExpansion={
|
||||||
}
|
toolActions?.toggleAllExpansion ?? vi.fn()
|
||||||
>
|
}
|
||||||
<AskUserActionsProvider
|
|
||||||
request={null}
|
|
||||||
onSubmit={vi.fn()}
|
|
||||||
onCancel={vi.fn()}
|
|
||||||
>
|
>
|
||||||
<KeypressProvider>
|
<AskUserActionsProvider
|
||||||
<MouseProvider
|
request={null}
|
||||||
mouseEventsEnabled={mouseEventsEnabled}
|
onSubmit={vi.fn()}
|
||||||
>
|
onCancel={vi.fn()}
|
||||||
<TerminalProvider>
|
>
|
||||||
<ScrollProvider>
|
<KeypressProvider>
|
||||||
<ContextCapture>
|
<MouseProvider
|
||||||
<Box
|
mouseEventsEnabled={mouseEventsEnabled}
|
||||||
width={terminalWidth}
|
>
|
||||||
flexShrink={0}
|
<TerminalProvider>
|
||||||
flexGrow={0}
|
<ScrollProvider>
|
||||||
flexDirection="column"
|
<ContextCapture>
|
||||||
>
|
<Box
|
||||||
{comp}
|
width={terminalWidth}
|
||||||
</Box>
|
flexShrink={0}
|
||||||
</ContextCapture>
|
flexGrow={0}
|
||||||
</ScrollProvider>
|
flexDirection="column"
|
||||||
</TerminalProvider>
|
>
|
||||||
</MouseProvider>
|
{comp}
|
||||||
</KeypressProvider>
|
</Box>
|
||||||
</AskUserActionsProvider>
|
</ContextCapture>
|
||||||
</ToolActionsProvider>
|
</ScrollProvider>
|
||||||
</OverflowProvider>
|
</TerminalProvider>
|
||||||
</UIActionsContext.Provider>
|
</MouseProvider>
|
||||||
</StreamingContext.Provider>
|
</KeypressProvider>
|
||||||
</SessionStatsProvider>
|
</AskUserActionsProvider>
|
||||||
</ShellFocusContext.Provider>
|
</ToolActionsProvider>
|
||||||
</VimModeProvider>
|
</OverflowProvider>
|
||||||
</UIStateContext.Provider>
|
</UIActionsContext.Provider>
|
||||||
|
</StreamingContext.Provider>
|
||||||
|
</SessionStatsProvider>
|
||||||
|
</ShellFocusContext.Provider>
|
||||||
|
</VimModeProvider>
|
||||||
|
</UIStateContext.Provider>
|
||||||
|
</InputContext.Provider>
|
||||||
</SettingsContext.Provider>
|
</SettingsContext.Provider>
|
||||||
</ConfigContext.Provider>
|
</ConfigContext.Provider>
|
||||||
</AppContext.Provider>
|
</AppContext.Provider>
|
||||||
|
|||||||
@@ -122,13 +122,17 @@ vi.mock('ink', async (importOriginal) => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
import { InputContext, type InputState } from './contexts/InputContext.js';
|
||||||
|
|
||||||
// Helper component will read the context values provided by AppContainer
|
// Helper component will read the context values provided by AppContainer
|
||||||
// so we can assert against them in our tests.
|
// so we can assert against them in our tests.
|
||||||
let capturedUIState: UIState;
|
let capturedUIState: UIState;
|
||||||
|
let capturedInputState: InputState;
|
||||||
let capturedUIActions: UIActions;
|
let capturedUIActions: UIActions;
|
||||||
let capturedOverflowActions: OverflowActions;
|
let capturedOverflowActions: OverflowActions;
|
||||||
function TestContextConsumer() {
|
function TestContextConsumer() {
|
||||||
capturedUIState = useContext(UIStateContext)!;
|
capturedUIState = useContext(UIStateContext)!;
|
||||||
|
capturedInputState = useContext(InputContext)!;
|
||||||
capturedUIActions = useContext(UIActionsContext)!;
|
capturedUIActions = useContext(UIActionsContext)!;
|
||||||
capturedOverflowActions = useOverflowActions()!;
|
capturedOverflowActions = useOverflowActions()!;
|
||||||
return null;
|
return null;
|
||||||
@@ -3036,7 +3040,7 @@ describe('AppContainer State Management', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { unmount } = await act(async () => renderAppContainer());
|
const { unmount } = await act(async () => renderAppContainer());
|
||||||
expect(capturedUIState.userMessages).toContain('previous message');
|
expect(capturedInputState.userMessages).toContain('previous message');
|
||||||
|
|
||||||
const { onCancelSubmit } = extractUseGeminiStreamArgs(
|
const { onCancelSubmit } = extractUseGeminiStreamArgs(
|
||||||
mockedUseGeminiStream.mock.lastCall!,
|
mockedUseGeminiStream.mock.lastCall!,
|
||||||
@@ -3064,8 +3068,8 @@ describe('AppContainer State Management', () => {
|
|||||||
const { rerender, unmount } = await act(async () => renderAppContainer());
|
const { rerender, unmount } = await act(async () => renderAppContainer());
|
||||||
|
|
||||||
// Verify userMessages is populated from inputHistory
|
// Verify userMessages is populated from inputHistory
|
||||||
expect(capturedUIState.userMessages).toContain('first prompt');
|
expect(capturedInputState.userMessages).toContain('first prompt');
|
||||||
expect(capturedUIState.userMessages).toContain('second prompt');
|
expect(capturedInputState.userMessages).toContain('second prompt');
|
||||||
|
|
||||||
// Clear the conversation history (simulating /clear command)
|
// Clear the conversation history (simulating /clear command)
|
||||||
const mockClearItems = vi.fn();
|
const mockClearItems = vi.fn();
|
||||||
@@ -3084,8 +3088,8 @@ describe('AppContainer State Management', () => {
|
|||||||
|
|
||||||
// Verify that userMessages still contains the input history
|
// Verify that userMessages still contains the input history
|
||||||
// (it should not be affected by clearing conversation history)
|
// (it should not be affected by clearing conversation history)
|
||||||
expect(capturedUIState.userMessages).toContain('first prompt');
|
expect(capturedInputState.userMessages).toContain('first prompt');
|
||||||
expect(capturedUIState.userMessages).toContain('second prompt');
|
expect(capturedInputState.userMessages).toContain('second prompt');
|
||||||
|
|
||||||
unmount();
|
unmount();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -36,9 +36,11 @@ import {
|
|||||||
type ConfirmationRequest,
|
type ConfirmationRequest,
|
||||||
type PermissionConfirmationRequest,
|
type PermissionConfirmationRequest,
|
||||||
type QuotaStats,
|
type QuotaStats,
|
||||||
|
MessageType,
|
||||||
|
StreamingState,
|
||||||
|
type HistoryItemInfo,
|
||||||
} from './types.js';
|
} from './types.js';
|
||||||
import { checkPermissions } from './hooks/atCommandProcessor.js';
|
import { checkPermissions } from './hooks/atCommandProcessor.js';
|
||||||
import { MessageType, StreamingState } from './types.js';
|
|
||||||
import { ToolActionsProvider } from './contexts/ToolActionsContext.js';
|
import { ToolActionsProvider } from './contexts/ToolActionsContext.js';
|
||||||
import { MouseProvider } from './contexts/MouseContext.js';
|
import { MouseProvider } from './contexts/MouseContext.js';
|
||||||
import { ScrollProvider } from './contexts/ScrollProvider.js';
|
import { ScrollProvider } from './contexts/ScrollProvider.js';
|
||||||
@@ -51,6 +53,7 @@ import {
|
|||||||
type UserTierId,
|
type UserTierId,
|
||||||
type GeminiUserTier,
|
type GeminiUserTier,
|
||||||
type UserFeedbackPayload,
|
type UserFeedbackPayload,
|
||||||
|
type HookSystemMessagePayload,
|
||||||
type AgentDefinition,
|
type AgentDefinition,
|
||||||
type ApprovalMode,
|
type ApprovalMode,
|
||||||
IdeClient,
|
IdeClient,
|
||||||
@@ -194,6 +197,8 @@ import {
|
|||||||
} from './hooks/useVisibilityToggle.js';
|
} from './hooks/useVisibilityToggle.js';
|
||||||
import { useKeyMatchers } from './hooks/useKeyMatchers.js';
|
import { useKeyMatchers } from './hooks/useKeyMatchers.js';
|
||||||
|
|
||||||
|
import { InputContext } from './contexts/InputContext.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The fraction of the terminal width to allocate to the shell.
|
* The fraction of the terminal width to allocate to the shell.
|
||||||
* This provides horizontal padding.
|
* This provides horizontal padding.
|
||||||
@@ -2109,7 +2114,19 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleHookSystemMessage = (payload: HookSystemMessagePayload) => {
|
||||||
|
historyManager.addItem(
|
||||||
|
{
|
||||||
|
type: MessageType.INFO,
|
||||||
|
text: payload.message,
|
||||||
|
source: payload.hookName,
|
||||||
|
} as HistoryItemInfo,
|
||||||
|
Date.now(),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
coreEvents.on(CoreEvent.UserFeedback, handleUserFeedback);
|
coreEvents.on(CoreEvent.UserFeedback, handleUserFeedback);
|
||||||
|
coreEvents.on(CoreEvent.HookSystemMessage, handleHookSystemMessage);
|
||||||
|
|
||||||
// Flush any messages that happened during startup before this component
|
// Flush any messages that happened during startup before this component
|
||||||
// mounted.
|
// mounted.
|
||||||
@@ -2117,6 +2134,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
|||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
coreEvents.off(CoreEvent.UserFeedback, handleUserFeedback);
|
coreEvents.off(CoreEvent.UserFeedback, handleUserFeedback);
|
||||||
|
coreEvents.off(CoreEvent.HookSystemMessage, handleHookSystemMessage);
|
||||||
};
|
};
|
||||||
}, [historyManager]);
|
}, [historyManager]);
|
||||||
|
|
||||||
@@ -2328,6 +2346,27 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
|||||||
};
|
};
|
||||||
}, [config, refreshStatic]);
|
}, [config, refreshStatic]);
|
||||||
|
|
||||||
|
const inputState = useMemo(
|
||||||
|
() => ({
|
||||||
|
buffer,
|
||||||
|
userMessages: inputHistory,
|
||||||
|
shellModeActive,
|
||||||
|
showEscapePrompt,
|
||||||
|
copyModeEnabled,
|
||||||
|
inputWidth,
|
||||||
|
suggestionsWidth,
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
buffer,
|
||||||
|
inputHistory,
|
||||||
|
shellModeActive,
|
||||||
|
showEscapePrompt,
|
||||||
|
copyModeEnabled,
|
||||||
|
inputWidth,
|
||||||
|
suggestionsWidth,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
const uiState: UIState = useMemo(
|
const uiState: UIState = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
history: historyManager.history,
|
history: historyManager.history,
|
||||||
@@ -2371,11 +2410,6 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
|||||||
initError,
|
initError,
|
||||||
pendingGeminiHistoryItems,
|
pendingGeminiHistoryItems,
|
||||||
thought,
|
thought,
|
||||||
shellModeActive,
|
|
||||||
userMessages: inputHistory,
|
|
||||||
buffer,
|
|
||||||
inputWidth,
|
|
||||||
suggestionsWidth,
|
|
||||||
isInputActive,
|
isInputActive,
|
||||||
isResuming,
|
isResuming,
|
||||||
shouldShowIdePrompt,
|
shouldShowIdePrompt,
|
||||||
@@ -2391,7 +2425,6 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
|||||||
renderMarkdown,
|
renderMarkdown,
|
||||||
ctrlCPressedOnce: ctrlCPressCount >= 1,
|
ctrlCPressedOnce: ctrlCPressCount >= 1,
|
||||||
ctrlDPressedOnce: ctrlDPressCount >= 1,
|
ctrlDPressedOnce: ctrlDPressCount >= 1,
|
||||||
showEscapePrompt,
|
|
||||||
shortcutsHelpVisible,
|
shortcutsHelpVisible,
|
||||||
cleanUiDetailsVisible,
|
cleanUiDetailsVisible,
|
||||||
isFocused,
|
isFocused,
|
||||||
@@ -2443,7 +2476,6 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
|||||||
embeddedShellFocused,
|
embeddedShellFocused,
|
||||||
showDebugProfiler,
|
showDebugProfiler,
|
||||||
customDialog,
|
customDialog,
|
||||||
copyModeEnabled,
|
|
||||||
transientMessage,
|
transientMessage,
|
||||||
bannerData,
|
bannerData,
|
||||||
bannerVisible,
|
bannerVisible,
|
||||||
@@ -2498,11 +2530,6 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
|||||||
initError,
|
initError,
|
||||||
pendingGeminiHistoryItems,
|
pendingGeminiHistoryItems,
|
||||||
thought,
|
thought,
|
||||||
shellModeActive,
|
|
||||||
inputHistory,
|
|
||||||
buffer,
|
|
||||||
inputWidth,
|
|
||||||
suggestionsWidth,
|
|
||||||
isInputActive,
|
isInputActive,
|
||||||
isResuming,
|
isResuming,
|
||||||
shouldShowIdePrompt,
|
shouldShowIdePrompt,
|
||||||
@@ -2518,7 +2545,6 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
|||||||
renderMarkdown,
|
renderMarkdown,
|
||||||
ctrlCPressCount,
|
ctrlCPressCount,
|
||||||
ctrlDPressCount,
|
ctrlDPressCount,
|
||||||
showEscapePrompt,
|
|
||||||
shortcutsHelpVisible,
|
shortcutsHelpVisible,
|
||||||
cleanUiDetailsVisible,
|
cleanUiDetailsVisible,
|
||||||
isFocused,
|
isFocused,
|
||||||
@@ -2570,7 +2596,6 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
|||||||
customDialog,
|
customDialog,
|
||||||
apiKeyDefaultValue,
|
apiKeyDefaultValue,
|
||||||
authState,
|
authState,
|
||||||
copyModeEnabled,
|
|
||||||
transientMessage,
|
transientMessage,
|
||||||
bannerData,
|
bannerData,
|
||||||
bannerVisible,
|
bannerVisible,
|
||||||
@@ -2757,32 +2782,34 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<UIStateContext.Provider value={uiState}>
|
<UIStateContext.Provider value={uiState}>
|
||||||
<UIActionsContext.Provider value={uiActions}>
|
<InputContext.Provider value={inputState}>
|
||||||
<ConfigContext.Provider value={config}>
|
<UIActionsContext.Provider value={uiActions}>
|
||||||
<AppContext.Provider
|
<ConfigContext.Provider value={config}>
|
||||||
value={{
|
<AppContext.Provider
|
||||||
version: props.version,
|
value={{
|
||||||
startupWarnings: props.startupWarnings || [],
|
version: props.version,
|
||||||
}}
|
startupWarnings: props.startupWarnings || [],
|
||||||
>
|
}}
|
||||||
<ToolActionsProvider
|
|
||||||
config={config}
|
|
||||||
toolCalls={allToolCalls}
|
|
||||||
isExpanded={isExpanded}
|
|
||||||
toggleExpansion={toggleExpansion}
|
|
||||||
toggleAllExpansion={toggleAllExpansion}
|
|
||||||
>
|
>
|
||||||
<ShellFocusContext.Provider value={isFocused}>
|
<ToolActionsProvider
|
||||||
<MouseProvider mouseEventsEnabled={mouseMode}>
|
config={config}
|
||||||
<ScrollProvider>
|
toolCalls={allToolCalls}
|
||||||
<App key={`app-${forceRerenderKey}`} />
|
isExpanded={isExpanded}
|
||||||
</ScrollProvider>
|
toggleExpansion={toggleExpansion}
|
||||||
</MouseProvider>
|
toggleAllExpansion={toggleAllExpansion}
|
||||||
</ShellFocusContext.Provider>
|
>
|
||||||
</ToolActionsProvider>
|
<ShellFocusContext.Provider value={isFocused}>
|
||||||
</AppContext.Provider>
|
<MouseProvider mouseEventsEnabled={mouseMode}>
|
||||||
</ConfigContext.Provider>
|
<ScrollProvider>
|
||||||
</UIActionsContext.Provider>
|
<App key={`app-${forceRerenderKey}`} />
|
||||||
|
</ScrollProvider>
|
||||||
|
</MouseProvider>
|
||||||
|
</ShellFocusContext.Provider>
|
||||||
|
</ToolActionsProvider>
|
||||||
|
</AppContext.Provider>
|
||||||
|
</ConfigContext.Provider>
|
||||||
|
</UIActionsContext.Provider>
|
||||||
|
</InputContext.Provider>
|
||||||
</UIStateContext.Provider>
|
</UIStateContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -55,6 +55,12 @@ Footer
|
|||||||
Gemini CLI v1.2.3
|
Gemini CLI v1.2.3
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Tips for getting started:
|
||||||
|
1. Create GEMINI.md files to customize your interactions
|
||||||
|
2. /help for more information
|
||||||
|
3. Ask coding questions, edit code or run commands
|
||||||
|
4. Be specific for the best results
|
||||||
Composer
|
Composer
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
@@ -124,13 +130,14 @@ HistoryItemDisplay
|
|||||||
│ │
|
│ │
|
||||||
│ ? ls list directory │
|
│ ? ls list directory │
|
||||||
│ │
|
│ │
|
||||||
│ ls │
|
│ ╭──────────────────────────────────────────────────────────────────────────────────────────────╮ │
|
||||||
│ Allow execution of: 'ls'? │
|
│ │ ls │ │
|
||||||
|
│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
|
||||||
|
│ Allow execution of [ls]? │
|
||||||
│ │
|
│ │
|
||||||
│ ● 1. Allow once │
|
│ ● 1. Allow once │
|
||||||
│ 2. Allow for this session │
|
│ 2. Allow for this session │
|
||||||
│ 3. No, suggest changes (esc) │
|
│ 3. No, suggest changes (esc) │
|
||||||
│ │
|
|
||||||
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
|
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
|
|
||||||
|
|
||||||
@@ -138,7 +145,6 @@ HistoryItemDisplay
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Notifications
|
Notifications
|
||||||
|
|
||||||
Composer
|
Composer
|
||||||
|
|||||||
@@ -12,253 +12,283 @@
|
|||||||
<rect x="351" y="0" width="549" height="17" fill="#141414" />
|
<rect x="351" y="0" width="549" height="17" fill="#141414" />
|
||||||
<rect x="0" y="17" width="900" height="17" fill="#141414" />
|
<rect x="0" y="17" width="900" height="17" fill="#141414" />
|
||||||
<text x="0" y="19" fill="#000000" textLength="900" lengthAdjust="spacingAndGlyphs">▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄</text>
|
<text x="0" y="19" fill="#000000" textLength="900" lengthAdjust="spacingAndGlyphs">▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄</text>
|
||||||
<text x="0" y="53" fill="#ffffaf" textLength="891" lengthAdjust="spacingAndGlyphs">╭─────────────────────────────────────────────────────────────────────────────────────────────────╮</text>
|
<text x="0" y="53" fill="#333333" textLength="891" lengthAdjust="spacingAndGlyphs">╭─────────────────────────────────────────────────────────────────────────────────────────────────╮</text>
|
||||||
<text x="0" y="70" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="70" fill="#ffffaf" textLength="135" lengthAdjust="spacingAndGlyphs" font-weight="bold">Action Required</text>
|
<text x="18" y="70" fill="#ffffaf" textLength="54" lengthAdjust="spacingAndGlyphs" font-weight="bold">? Edit</text>
|
||||||
<text x="882" y="70" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="882" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="87" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="882" y="87" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="18" y="87" fill="#333333" textLength="855" lengthAdjust="spacingAndGlyphs">╭─────────────────────────────────────────────────────────────────────────────────────────────╮</text>
|
||||||
<text x="0" y="104" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="882" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="104" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">?</text>
|
<text x="0" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="45" y="104" fill="#ffffff" textLength="36" lengthAdjust="spacingAndGlyphs" font-weight="bold">Edit</text>
|
<text x="18" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="90" y="104" fill="#afafaf" textLength="774" lengthAdjust="spacingAndGlyphs">packages/.../InputPrompt.tsx: return kittyProtocolSupporte... => return kittyProto</text>
|
<text x="36" y="104" fill="#afafaf" textLength="414" lengthAdjust="spacingAndGlyphs">... first 42 lines hidden (Ctrl+O to show) ...</text>
|
||||||
<text x="864" y="104" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">… </text>
|
<text x="864" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="882" y="104" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="882" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="121" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="882" y="121" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="18" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="138" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="36" y="121" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">43</text>
|
||||||
<text x="18" y="138" fill="#afafaf" textLength="414" lengthAdjust="spacingAndGlyphs">... first 44 lines hidden (Ctrl+O to show) ...</text>
|
<text x="81" y="121" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||||
<text x="882" y="138" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="135" y="121" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line43</text>
|
||||||
<text x="0" y="155" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="189" y="121" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||||
<text x="18" y="155" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">45</text>
|
<text x="216" y="121" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
<text x="63" y="155" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
<text x="252" y="121" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
<text x="117" y="155" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line45</text>
|
<text x="864" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="171" y="155" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
<text x="882" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="198" y="155" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
<text x="0" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="234" y="155" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
<text x="18" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="882" y="155" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="36" y="138" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">44</text>
|
||||||
<text x="0" y="172" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="81" y="138" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||||
<text x="18" y="172" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">46</text>
|
<text x="135" y="138" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line44</text>
|
||||||
<text x="63" y="172" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
<text x="189" y="138" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||||
<text x="117" y="172" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line46</text>
|
<text x="216" y="138" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
<text x="171" y="172" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
<text x="252" y="138" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
<text x="198" y="172" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
<text x="864" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="234" y="172" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
<text x="882" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="882" y="172" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="155" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="189" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="18" y="155" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="189" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">47</text>
|
<text x="36" y="155" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">45</text>
|
||||||
<text x="63" y="189" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
<text x="81" y="155" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||||
<text x="117" y="189" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line47</text>
|
<text x="135" y="155" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line45</text>
|
||||||
<text x="171" y="189" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
<text x="189" y="155" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||||
<text x="198" y="189" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
<text x="216" y="155" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
<text x="234" y="189" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
<text x="252" y="155" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
<text x="882" y="189" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="864" y="155" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="891" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">▄</text>
|
<text x="882" y="155" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="206" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="206" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">48</text>
|
<text x="18" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="63" y="206" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
<text x="36" y="172" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">46</text>
|
||||||
<text x="117" y="206" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line48</text>
|
<text x="81" y="172" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||||
<text x="171" y="206" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
<text x="135" y="172" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line46</text>
|
||||||
<text x="198" y="206" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
<text x="189" y="172" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||||
<text x="234" y="206" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
<text x="216" y="172" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
<text x="882" y="206" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="252" y="172" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
<text x="891" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
<text x="864" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="223" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="882" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="223" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">49</text>
|
<text x="0" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="63" y="223" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
<text x="18" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="117" y="223" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line49</text>
|
<text x="36" y="189" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">47</text>
|
||||||
<text x="171" y="223" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
<text x="81" y="189" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||||
<text x="198" y="223" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
<text x="135" y="189" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line47</text>
|
||||||
<text x="234" y="223" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
<text x="189" y="189" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||||
<text x="882" y="223" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="216" y="189" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
<text x="891" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
<text x="252" y="189" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
<text x="0" y="240" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="864" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="240" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">50</text>
|
<text x="882" y="189" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│▄</text>
|
||||||
<text x="63" y="240" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
<text x="0" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="117" y="240" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line50</text>
|
<text x="18" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="171" y="240" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
<text x="36" y="206" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">48</text>
|
||||||
<text x="198" y="240" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
<text x="81" y="206" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||||
<text x="234" y="240" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
<text x="135" y="206" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line48</text>
|
||||||
<text x="882" y="240" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="189" y="206" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||||
<text x="891" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
<text x="216" y="206" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
<text x="0" y="257" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="252" y="206" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
<text x="18" y="257" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">51</text>
|
<text x="864" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="63" y="257" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
<text x="882" y="206" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||||
<text x="117" y="257" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line51</text>
|
<text x="0" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="171" y="257" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
<text x="18" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="198" y="257" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
<text x="36" y="223" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">49</text>
|
||||||
<text x="234" y="257" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
<text x="81" y="223" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||||
<text x="882" y="257" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="135" y="223" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line49</text>
|
||||||
<text x="891" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
<text x="189" y="223" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||||
<text x="0" y="274" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="216" y="223" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
<text x="18" y="274" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">52</text>
|
<text x="252" y="223" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
<text x="63" y="274" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
<text x="864" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="117" y="274" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line52</text>
|
<text x="882" y="223" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||||
<text x="171" y="274" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
<text x="0" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="198" y="274" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
<text x="18" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="234" y="274" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
<text x="36" y="240" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">50</text>
|
||||||
<text x="882" y="274" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="81" y="240" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||||
<text x="891" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
<text x="135" y="240" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line50</text>
|
||||||
<text x="0" y="291" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="189" y="240" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||||
<text x="18" y="291" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">53</text>
|
<text x="216" y="240" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
<text x="63" y="291" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
<text x="252" y="240" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
<text x="117" y="291" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line53</text>
|
<text x="864" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="171" y="291" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
<text x="882" y="240" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||||
<text x="198" y="291" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
<text x="0" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="234" y="291" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
<text x="18" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="882" y="291" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="36" y="257" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">51</text>
|
||||||
<text x="891" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
<text x="81" y="257" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||||
<text x="0" y="308" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="135" y="257" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line51</text>
|
||||||
<text x="18" y="308" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">54</text>
|
<text x="189" y="257" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||||
<text x="63" y="308" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
<text x="216" y="257" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
<text x="117" y="308" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line54</text>
|
<text x="252" y="257" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
<text x="171" y="308" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
<text x="864" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="198" y="308" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
<text x="882" y="257" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||||
<text x="234" y="308" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
<text x="0" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="882" y="308" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="18" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="891" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
<text x="36" y="274" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">52</text>
|
||||||
<text x="0" y="325" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="81" y="274" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||||
<text x="18" y="325" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">55</text>
|
<text x="135" y="274" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line52</text>
|
||||||
<text x="63" y="325" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
<text x="189" y="274" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||||
<text x="117" y="325" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line55</text>
|
<text x="216" y="274" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
<text x="171" y="325" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
<text x="252" y="274" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
<text x="198" y="325" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
<text x="864" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="234" y="325" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
<text x="882" y="274" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||||
<text x="882" y="325" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="891" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
<text x="18" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="342" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="36" y="291" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">53</text>
|
||||||
<text x="18" y="342" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">56</text>
|
<text x="81" y="291" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||||
<text x="63" y="342" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
<text x="135" y="291" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line53</text>
|
||||||
<text x="117" y="342" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line56</text>
|
<text x="189" y="291" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||||
<text x="171" y="342" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
<text x="216" y="291" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
<text x="198" y="342" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
<text x="252" y="291" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
<text x="234" y="342" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
<text x="864" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="882" y="342" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="882" y="291" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||||
<text x="891" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
<text x="0" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="359" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="18" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="359" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">57</text>
|
<text x="36" y="308" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">54</text>
|
||||||
<text x="63" y="359" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
<text x="81" y="308" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||||
<text x="117" y="359" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line57</text>
|
<text x="135" y="308" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line54</text>
|
||||||
<text x="171" y="359" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
<text x="189" y="308" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||||
<text x="198" y="359" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
<text x="216" y="308" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
<text x="234" y="359" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
<text x="252" y="308" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
<text x="882" y="359" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="864" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="891" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
<text x="882" y="308" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||||
<text x="0" y="376" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="376" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">58</text>
|
<text x="18" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="63" y="376" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
<text x="36" y="325" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">55</text>
|
||||||
<text x="117" y="376" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line58</text>
|
<text x="81" y="325" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||||
<text x="171" y="376" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
<text x="135" y="325" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line55</text>
|
||||||
<text x="198" y="376" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
<text x="189" y="325" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||||
<text x="234" y="376" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
<text x="216" y="325" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
<text x="882" y="376" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="252" y="325" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
<text x="891" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
<text x="864" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="393" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="882" y="325" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||||
<text x="18" y="393" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">59</text>
|
<text x="0" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="63" y="393" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
<text x="18" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="117" y="393" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line59</text>
|
<text x="36" y="342" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">56</text>
|
||||||
<text x="171" y="393" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
<text x="81" y="342" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||||
<text x="198" y="393" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
<text x="135" y="342" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line56</text>
|
||||||
<text x="234" y="393" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
<text x="189" y="342" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||||
<text x="882" y="393" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="216" y="342" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
<text x="891" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
<text x="252" y="342" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
<text x="0" y="410" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="864" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="410" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">60</text>
|
<text x="882" y="342" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||||
<text x="63" y="410" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
<text x="0" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="117" y="410" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line60</text>
|
<text x="18" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="171" y="410" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
<text x="36" y="359" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">57</text>
|
||||||
<text x="198" y="410" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
<text x="81" y="359" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||||
<text x="234" y="410" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
<text x="135" y="359" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line57</text>
|
||||||
<text x="882" y="410" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="189" y="359" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||||
<text x="891" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
<text x="216" y="359" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
<text x="0" y="427" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="252" y="359" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
<rect x="18" y="425" width="18" height="17" fill="#5f0000" />
|
<text x="864" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="427" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">61</text>
|
<text x="882" y="359" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||||
<rect x="36" y="425" width="9" height="17" fill="#5f0000" />
|
<text x="0" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="45" y="425" width="9" height="17" fill="#5f0000" />
|
<text x="18" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="45" y="427" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
<text x="36" y="376" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">58</text>
|
||||||
|
<text x="81" y="376" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||||
|
<text x="135" y="376" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line58</text>
|
||||||
|
<text x="189" y="376" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||||
|
<text x="216" y="376" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
|
<text x="252" y="376" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
|
<text x="864" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="882" y="376" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||||
|
<text x="0" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="18" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="36" y="393" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">59</text>
|
||||||
|
<text x="81" y="393" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||||
|
<text x="135" y="393" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line59</text>
|
||||||
|
<text x="189" y="393" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||||
|
<text x="216" y="393" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
|
<text x="252" y="393" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
|
<text x="864" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="882" y="393" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||||
|
<text x="0" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="18" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="36" y="410" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">60</text>
|
||||||
|
<text x="81" y="410" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||||
|
<text x="135" y="410" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line60</text>
|
||||||
|
<text x="189" y="410" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||||
|
<text x="216" y="410" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
|
<text x="252" y="410" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
|
<text x="864" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="882" y="410" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||||
|
<text x="0" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="18" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<rect x="36" y="425" width="18" height="17" fill="#5f0000" />
|
||||||
|
<text x="36" y="427" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">61</text>
|
||||||
<rect x="54" y="425" width="9" height="17" fill="#5f0000" />
|
<rect x="54" y="425" width="9" height="17" fill="#5f0000" />
|
||||||
<rect x="63" y="425" width="9" height="17" fill="#5f0000" />
|
<rect x="63" y="425" width="9" height="17" fill="#5f0000" />
|
||||||
<rect x="72" y="425" width="54" height="17" fill="#5f0000" />
|
<text x="63" y="427" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
||||||
<text x="72" y="427" fill="#0000ee" textLength="54" lengthAdjust="spacingAndGlyphs">return</text>
|
<rect x="72" y="425" width="9" height="17" fill="#5f0000" />
|
||||||
<rect x="126" y="425" width="234" height="17" fill="#5f0000" />
|
<rect x="81" y="425" width="9" height="17" fill="#5f0000" />
|
||||||
<text x="126" y="427" fill="#e5e5e5" textLength="234" lengthAdjust="spacingAndGlyphs"> kittyProtocolSupporte...;</text>
|
<rect x="90" y="425" width="54" height="17" fill="#5f0000" />
|
||||||
<text x="882" y="427" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="90" y="427" fill="#0000ee" textLength="54" lengthAdjust="spacingAndGlyphs">return</text>
|
||||||
<text x="891" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
<rect x="144" y="425" width="234" height="17" fill="#5f0000" />
|
||||||
<text x="0" y="444" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="144" y="427" fill="#e5e5e5" textLength="234" lengthAdjust="spacingAndGlyphs"> kittyProtocolSupporte...;</text>
|
||||||
<rect x="18" y="442" width="18" height="17" fill="#005f00" />
|
<text x="864" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="444" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">61</text>
|
<text x="882" y="427" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||||
<rect x="36" y="442" width="9" height="17" fill="#005f00" />
|
<text x="0" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="45" y="442" width="9" height="17" fill="#005f00" />
|
<text x="18" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="45" y="444" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
<rect x="36" y="442" width="18" height="17" fill="#005f00" />
|
||||||
|
<text x="36" y="444" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">61</text>
|
||||||
<rect x="54" y="442" width="9" height="17" fill="#005f00" />
|
<rect x="54" y="442" width="9" height="17" fill="#005f00" />
|
||||||
<rect x="63" y="442" width="9" height="17" fill="#005f00" />
|
<rect x="63" y="442" width="9" height="17" fill="#005f00" />
|
||||||
<rect x="72" y="442" width="54" height="17" fill="#005f00" />
|
<text x="63" y="444" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||||
<text x="72" y="444" fill="#0000ee" textLength="54" lengthAdjust="spacingAndGlyphs">return</text>
|
<rect x="72" y="442" width="9" height="17" fill="#005f00" />
|
||||||
<rect x="126" y="442" width="234" height="17" fill="#005f00" />
|
<rect x="81" y="442" width="9" height="17" fill="#005f00" />
|
||||||
<text x="126" y="444" fill="#e5e5e5" textLength="234" lengthAdjust="spacingAndGlyphs"> kittyProtocolSupporte...;</text>
|
<rect x="90" y="442" width="54" height="17" fill="#005f00" />
|
||||||
<text x="882" y="444" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="90" y="444" fill="#0000ee" textLength="54" lengthAdjust="spacingAndGlyphs">return</text>
|
||||||
<text x="891" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
<rect x="144" y="442" width="234" height="17" fill="#005f00" />
|
||||||
<text x="0" y="461" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="144" y="444" fill="#e5e5e5" textLength="234" lengthAdjust="spacingAndGlyphs"> kittyProtocolSupporte...;</text>
|
||||||
<text x="18" y="461" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">62</text>
|
<text x="864" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="63" y="461" fill="#e5e5e5" textLength="180" lengthAdjust="spacingAndGlyphs"> buffer: TextBuffer;</text>
|
<text x="882" y="444" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||||
<text x="882" y="461" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="891" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
<text x="18" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="478" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="36" y="461" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">62</text>
|
||||||
<text x="18" y="478" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">63</text>
|
<text x="81" y="461" fill="#e5e5e5" textLength="180" lengthAdjust="spacingAndGlyphs"> buffer: TextBuffer;</text>
|
||||||
<text x="72" y="478" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs">onSubmit</text>
|
<text x="864" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="144" y="478" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs">: (</text>
|
<text x="882" y="461" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||||
<text x="171" y="478" fill="#ffffff" textLength="45" lengthAdjust="spacingAndGlyphs">value</text>
|
<text x="0" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="216" y="478" fill="#e5e5e5" textLength="18" lengthAdjust="spacingAndGlyphs">: </text>
|
<text x="18" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="234" y="478" fill="#00cdcd" textLength="54" lengthAdjust="spacingAndGlyphs">string</text>
|
<text x="36" y="478" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">63</text>
|
||||||
<text x="288" y="478" fill="#e5e5e5" textLength="45" lengthAdjust="spacingAndGlyphs">) => </text>
|
<text x="90" y="478" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs">onSubmit</text>
|
||||||
<text x="333" y="478" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">void</text>
|
<text x="162" y="478" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs">: (</text>
|
||||||
<text x="369" y="478" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
<text x="189" y="478" fill="#ffffff" textLength="45" lengthAdjust="spacingAndGlyphs">value</text>
|
||||||
<text x="882" y="478" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="234" y="478" fill="#e5e5e5" textLength="18" lengthAdjust="spacingAndGlyphs">: </text>
|
||||||
<text x="891" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
<text x="252" y="478" fill="#00cdcd" textLength="54" lengthAdjust="spacingAndGlyphs">string</text>
|
||||||
<text x="0" y="495" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="306" y="478" fill="#e5e5e5" textLength="45" lengthAdjust="spacingAndGlyphs">) => </text>
|
||||||
<text x="18" y="495" fill="#ffffff" textLength="162" lengthAdjust="spacingAndGlyphs">Apply this change?</text>
|
<text x="351" y="478" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">void</text>
|
||||||
<text x="882" y="495" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="387" y="478" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
<text x="891" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
<text x="864" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="512" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="882" y="478" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||||
<text x="882" y="512" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="891" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
<text x="18" y="495" fill="#333333" textLength="855" lengthAdjust="spacingAndGlyphs">╰─────────────────────────────────────────────────────────────────────────────────────────────╯</text>
|
||||||
<text x="0" y="529" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="882" y="495" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||||
<rect x="18" y="527" width="9" height="17" fill="#001a00" />
|
<text x="0" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="529" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
<text x="18" y="512" fill="#ffffff" textLength="162" lengthAdjust="spacingAndGlyphs">Apply this change?</text>
|
||||||
<rect x="27" y="527" width="9" height="17" fill="#001a00" />
|
<text x="882" y="512" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||||
<rect x="36" y="527" width="18" height="17" fill="#001a00" />
|
<text x="0" y="529" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="36" y="529" fill="#00cd00" textLength="18" lengthAdjust="spacingAndGlyphs">1.</text>
|
<text x="882" y="529" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||||
<rect x="54" y="527" width="9" height="17" fill="#001a00" />
|
<text x="0" y="546" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="63" y="527" width="90" height="17" fill="#001a00" />
|
<rect x="18" y="544" width="9" height="17" fill="#001a00" />
|
||||||
<text x="63" y="529" fill="#00cd00" textLength="90" lengthAdjust="spacingAndGlyphs">Allow once</text>
|
<text x="18" y="546" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
||||||
<rect x="153" y="527" width="288" height="17" fill="#001a00" />
|
<rect x="27" y="544" width="9" height="17" fill="#001a00" />
|
||||||
<text x="882" y="529" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<rect x="36" y="544" width="18" height="17" fill="#001a00" />
|
||||||
<text x="891" y="529" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
<text x="36" y="546" fill="#00cd00" textLength="18" lengthAdjust="spacingAndGlyphs">1.</text>
|
||||||
<text x="0" y="546" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<rect x="54" y="544" width="9" height="17" fill="#001a00" />
|
||||||
<text x="36" y="546" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">2.</text>
|
<rect x="63" y="544" width="90" height="17" fill="#001a00" />
|
||||||
<text x="63" y="546" fill="#ffffff" textLength="198" lengthAdjust="spacingAndGlyphs">Allow for this session</text>
|
<text x="63" y="546" fill="#00cd00" textLength="90" lengthAdjust="spacingAndGlyphs">Allow once</text>
|
||||||
<text x="882" y="546" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<rect x="153" y="544" width="603" height="17" fill="#001a00" />
|
||||||
<text x="891" y="546" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
<text x="882" y="546" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||||
<text x="0" y="563" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="563" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="36" y="563" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">3.</text>
|
<text x="36" y="563" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">2.</text>
|
||||||
<text x="63" y="563" fill="#ffffff" textLength="378" lengthAdjust="spacingAndGlyphs">Allow for this file in all future sessions</text>
|
<text x="63" y="563" fill="#ffffff" textLength="198" lengthAdjust="spacingAndGlyphs">Allow for this session</text>
|
||||||
<text x="882" y="563" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="882" y="563" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||||
<text x="891" y="563" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
<text x="0" y="580" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="580" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="36" y="580" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">3.</text>
|
||||||
<text x="36" y="580" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">4.</text>
|
<text x="63" y="580" fill="#ffffff" textLength="387" lengthAdjust="spacingAndGlyphs">Allow for this file in all future sessions </text>
|
||||||
<text x="63" y="580" fill="#ffffff" textLength="243" lengthAdjust="spacingAndGlyphs">Modify with external editor</text>
|
<text x="450" y="580" fill="#afafaf" textLength="306" lengthAdjust="spacingAndGlyphs">~/.gemini/policies/auto-saved.toml</text>
|
||||||
<text x="882" y="580" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="882" y="580" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||||
<text x="891" y="580" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
<text x="0" y="597" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="597" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="36" y="597" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">4.</text>
|
||||||
<text x="36" y="597" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">5.</text>
|
<text x="63" y="597" fill="#ffffff" textLength="243" lengthAdjust="spacingAndGlyphs">Modify with external editor</text>
|
||||||
<text x="63" y="597" fill="#ffffff" textLength="225" lengthAdjust="spacingAndGlyphs">No, suggest changes (esc)</text>
|
<text x="882" y="597" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||||
<text x="882" y="597" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="614" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="891" y="597" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
<text x="36" y="614" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">5.</text>
|
||||||
<text x="0" y="614" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="63" y="614" fill="#ffffff" textLength="225" lengthAdjust="spacingAndGlyphs">No, suggest changes (esc)</text>
|
||||||
<text x="882" y="614" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="882" y="614" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||||
<text x="891" y="614" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
<text x="0" y="631" fill="#333333" textLength="900" lengthAdjust="spacingAndGlyphs">╰─────────────────────────────────────────────────────────────────────────────────────────────────╯█</text>
|
||||||
<text x="0" y="631" fill="#ffffaf" textLength="891" lengthAdjust="spacingAndGlyphs">╰─────────────────────────────────────────────────────────────────────────────────────────────────╯</text>
|
|
||||||
<text x="891" y="631" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 29 KiB |
@@ -5,39 +5,39 @@ exports[`Full Terminal Tool Confirmation Snapshot > renders tool confirmation bo
|
|||||||
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
|
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
|
||||||
|
|
||||||
╭─────────────────────────────────────────────────────────────────────────────────────────────────╮
|
╭─────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
│ Action Required │
|
│ ? Edit │
|
||||||
│ │
|
│ ╭─────────────────────────────────────────────────────────────────────────────────────────────╮ │
|
||||||
│ ? Edit packages/.../InputPrompt.tsx: return kittyProtocolSupporte... => return kittyProto… │
|
│ │ ... first 42 lines hidden (Ctrl+O to show) ... │ │
|
||||||
│ │
|
│ │ 43 const line43 = true; │ │
|
||||||
│ ... first 44 lines hidden (Ctrl+O to show) ... │
|
│ │ 44 const line44 = true; │ │
|
||||||
│ 45 const line45 = true; │
|
│ │ 45 const line45 = true; │ │
|
||||||
│ 46 const line46 = true; │
|
│ │ 46 const line46 = true; │ │
|
||||||
│ 47 const line47 = true; │▄
|
│ │ 47 const line47 = true; │ │▄
|
||||||
│ 48 const line48 = true; │█
|
│ │ 48 const line48 = true; │ │█
|
||||||
│ 49 const line49 = true; │█
|
│ │ 49 const line49 = true; │ │█
|
||||||
│ 50 const line50 = true; │█
|
│ │ 50 const line50 = true; │ │█
|
||||||
│ 51 const line51 = true; │█
|
│ │ 51 const line51 = true; │ │█
|
||||||
│ 52 const line52 = true; │█
|
│ │ 52 const line52 = true; │ │█
|
||||||
│ 53 const line53 = true; │█
|
│ │ 53 const line53 = true; │ │█
|
||||||
│ 54 const line54 = true; │█
|
│ │ 54 const line54 = true; │ │█
|
||||||
│ 55 const line55 = true; │█
|
│ │ 55 const line55 = true; │ │█
|
||||||
│ 56 const line56 = true; │█
|
│ │ 56 const line56 = true; │ │█
|
||||||
│ 57 const line57 = true; │█
|
│ │ 57 const line57 = true; │ │█
|
||||||
│ 58 const line58 = true; │█
|
│ │ 58 const line58 = true; │ │█
|
||||||
│ 59 const line59 = true; │█
|
│ │ 59 const line59 = true; │ │█
|
||||||
│ 60 const line60 = true; │█
|
│ │ 60 const line60 = true; │ │█
|
||||||
│ 61 - return kittyProtocolSupporte...; │█
|
│ │ 61 - return kittyProtocolSupporte...; │ │█
|
||||||
│ 61 + return kittyProtocolSupporte...; │█
|
│ │ 61 + return kittyProtocolSupporte...; │ │█
|
||||||
│ 62 buffer: TextBuffer; │█
|
│ │ 62 buffer: TextBuffer; │ │█
|
||||||
│ 63 onSubmit: (value: string) => void; │█
|
│ │ 63 onSubmit: (value: string) => void; │ │█
|
||||||
|
│ ╰─────────────────────────────────────────────────────────────────────────────────────────────╯ │█
|
||||||
│ Apply this change? │█
|
│ Apply this change? │█
|
||||||
│ │█
|
│ │█
|
||||||
│ ● 1. Allow once │█
|
│ ● 1. Allow once │█
|
||||||
│ 2. Allow for this session │█
|
│ 2. Allow for this session │█
|
||||||
│ 3. Allow for this file in all future sessions │█
|
│ 3. Allow for this file in all future sessions ~/.gemini/policies/auto-saved.toml │█
|
||||||
│ 4. Modify with external editor │█
|
│ 4. Modify with external editor │█
|
||||||
│ 5. No, suggest changes (esc) │█
|
│ 5. No, suggest changes (esc) │█
|
||||||
│ │█
|
|
||||||
╰─────────────────────────────────────────────────────────────────────────────────────────────────╯█
|
╰─────────────────────────────────────────────────────────────────────────────────────────────────╯█
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ const createAnsiToken = (overrides: Partial<AnsiToken>): AnsiToken => ({
|
|||||||
underline: false,
|
underline: false,
|
||||||
dim: false,
|
dim: false,
|
||||||
inverse: false,
|
inverse: false,
|
||||||
|
isUninitialized: false,
|
||||||
fg: '#ffffff',
|
fg: '#ffffff',
|
||||||
bg: '#000000',
|
bg: '#000000',
|
||||||
...overrides,
|
...overrides,
|
||||||
|
|||||||
@@ -10,15 +10,20 @@ import {
|
|||||||
} from '../../test-utils/render.js';
|
} from '../../test-utils/render.js';
|
||||||
import type { LoadedSettings } from '../../config/settings.js';
|
import type { LoadedSettings } from '../../config/settings.js';
|
||||||
import { AppHeader } from './AppHeader.js';
|
import { AppHeader } from './AppHeader.js';
|
||||||
import { describe, it, expect, vi } from 'vitest';
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
import { makeFakeConfig } from '@google/gemini-cli-core';
|
import { makeFakeConfig } from '@google/gemini-cli-core';
|
||||||
import crypto from 'node:crypto';
|
import crypto from 'node:crypto';
|
||||||
|
import { _clearSessionBannersForTest } from '../hooks/useBanner.js';
|
||||||
|
|
||||||
vi.mock('../utils/terminalSetup.js', () => ({
|
vi.mock('../utils/terminalSetup.js', () => ({
|
||||||
getTerminalProgram: () => null,
|
getTerminalProgram: () => null,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('<AppHeader />', () => {
|
describe('<AppHeader />', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
_clearSessionBannersForTest();
|
||||||
|
});
|
||||||
|
|
||||||
it('should render the banner with default text', async () => {
|
it('should render the banner with default text', async () => {
|
||||||
const uiState = {
|
const uiState = {
|
||||||
history: [],
|
history: [],
|
||||||
|
|||||||
@@ -59,13 +59,20 @@ const NARROW_TERMINAL_BREAKPOINT = 60;
|
|||||||
export const AppHeader = ({ version, showDetails = true }: AppHeaderProps) => {
|
export const AppHeader = ({ version, showDetails = true }: AppHeaderProps) => {
|
||||||
const settings = useSettings();
|
const settings = useSettings();
|
||||||
const config = useConfig();
|
const config = useConfig();
|
||||||
const { terminalWidth, bannerData, bannerVisible, updateInfo } = useUIState();
|
const {
|
||||||
|
terminalWidth,
|
||||||
|
bannerData,
|
||||||
|
bannerVisible,
|
||||||
|
updateInfo,
|
||||||
|
isConfigInitialized,
|
||||||
|
isAuthenticating,
|
||||||
|
} = useUIState();
|
||||||
|
|
||||||
const { bannerText } = useBanner(bannerData);
|
const { bannerText } = useBanner(bannerData);
|
||||||
const { showTips } = useTips();
|
const { showTips } = useTips();
|
||||||
|
|
||||||
const authType = config.getContentGeneratorConfig()?.authType;
|
const authType = config.getContentGeneratorConfig()?.authType;
|
||||||
const loggedOut = !authType;
|
const loggedOut = isConfigInitialized && !isAuthenticating && !authType;
|
||||||
|
|
||||||
const showHeader = !(
|
const showHeader = !(
|
||||||
settings.merged.ui.hideBanner || config.getScreenReader()
|
settings.merged.ui.hideBanner || config.getScreenReader()
|
||||||
|
|||||||
@@ -245,20 +245,37 @@ const createMockConfig = (overrides = {}): Config =>
|
|||||||
...overrides,
|
...overrides,
|
||||||
}) as unknown as Config;
|
}) as unknown as Config;
|
||||||
|
|
||||||
|
import { InputContext, type InputState } from '../contexts/InputContext.js';
|
||||||
|
|
||||||
const renderComposer = async (
|
const renderComposer = async (
|
||||||
uiState: UIState,
|
uiState: UIState,
|
||||||
settings = createMockSettings({ ui: {} }),
|
settings = createMockSettings({ ui: {} }),
|
||||||
config = createMockConfig(),
|
config = createMockConfig(),
|
||||||
uiActions = createMockUIActions(),
|
uiActions = createMockUIActions(),
|
||||||
|
inputStateOverrides: Partial<InputState> = {},
|
||||||
) => {
|
) => {
|
||||||
|
const inputState = {
|
||||||
|
buffer: { text: '' } as unknown as TextBuffer,
|
||||||
|
userMessages: [],
|
||||||
|
shellModeActive: false,
|
||||||
|
showEscapePrompt: false,
|
||||||
|
copyModeEnabled: false,
|
||||||
|
inputWidth: 80,
|
||||||
|
suggestionsWidth: 40,
|
||||||
|
...(uiState as unknown as Partial<InputState>),
|
||||||
|
...inputStateOverrides,
|
||||||
|
};
|
||||||
|
|
||||||
const result = await render(
|
const result = await render(
|
||||||
<ConfigContext.Provider value={config as unknown as Config}>
|
<ConfigContext.Provider value={config as unknown as Config}>
|
||||||
<SettingsContext.Provider value={settings as unknown as LoadedSettings}>
|
<SettingsContext.Provider value={settings as unknown as LoadedSettings}>
|
||||||
<UIStateContext.Provider value={uiState}>
|
<InputContext.Provider value={inputState}>
|
||||||
<UIActionsContext.Provider value={uiActions}>
|
<UIStateContext.Provider value={uiState}>
|
||||||
<Composer isFocused={true} />
|
<UIActionsContext.Provider value={uiActions}>
|
||||||
</UIActionsContext.Provider>
|
<Composer isFocused={true} />
|
||||||
</UIStateContext.Provider>
|
</UIActionsContext.Provider>
|
||||||
|
</UIStateContext.Provider>
|
||||||
|
</InputContext.Provider>
|
||||||
</SettingsContext.Provider>
|
</SettingsContext.Provider>
|
||||||
</ConfigContext.Provider>,
|
</ConfigContext.Provider>,
|
||||||
);
|
);
|
||||||
@@ -541,7 +558,6 @@ describe('Composer', () => {
|
|||||||
const uiState = createMockUIState({
|
const uiState = createMockUIState({
|
||||||
ctrlCPressedOnce: false,
|
ctrlCPressedOnce: false,
|
||||||
ctrlDPressedOnce: false,
|
ctrlDPressedOnce: false,
|
||||||
showEscapePrompt: false,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const { lastFrame } = await renderComposer(uiState);
|
const { lastFrame } = await renderComposer(uiState);
|
||||||
@@ -631,7 +647,6 @@ describe('Composer', () => {
|
|||||||
async (mode) => {
|
async (mode) => {
|
||||||
const uiState = createMockUIState({
|
const uiState = createMockUIState({
|
||||||
showApprovalModeIndicator: mode,
|
showApprovalModeIndicator: mode,
|
||||||
shellModeActive: false,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const { lastFrame } = await renderComposer(uiState);
|
const { lastFrame } = await renderComposer(uiState);
|
||||||
@@ -641,11 +656,15 @@ describe('Composer', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
it('shows ShellModeIndicator when shell mode is active', async () => {
|
it('shows ShellModeIndicator when shell mode is active', async () => {
|
||||||
const uiState = createMockUIState({
|
const uiState = createMockUIState();
|
||||||
shellModeActive: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { lastFrame } = await renderComposer(uiState);
|
const { lastFrame } = await renderComposer(
|
||||||
|
uiState,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
{ shellModeActive: true },
|
||||||
|
);
|
||||||
|
|
||||||
expect(lastFrame()).toMatch(/ShellModeIndic[\s\S]*tor/);
|
expect(lastFrame()).toMatch(/ShellModeIndic[\s\S]*tor/);
|
||||||
});
|
});
|
||||||
@@ -724,11 +743,16 @@ describe('Composer', () => {
|
|||||||
it('shows Esc rewind prompt in minimal mode without showing full UI', async () => {
|
it('shows Esc rewind prompt in minimal mode without showing full UI', async () => {
|
||||||
const uiState = createMockUIState({
|
const uiState = createMockUIState({
|
||||||
cleanUiDetailsVisible: false,
|
cleanUiDetailsVisible: false,
|
||||||
showEscapePrompt: true,
|
|
||||||
history: [{ id: 1, type: 'user', text: 'msg' }],
|
history: [{ id: 1, type: 'user', text: 'msg' }],
|
||||||
});
|
});
|
||||||
|
|
||||||
const { lastFrame } = await renderComposer(uiState);
|
const { lastFrame } = await renderComposer(
|
||||||
|
uiState,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
{ showEscapePrompt: true },
|
||||||
|
);
|
||||||
const output = lastFrame();
|
const output = lastFrame();
|
||||||
expect(output).toContain('Press Esc again to rewind.');
|
expect(output).toContain('Press Esc again to rewind.');
|
||||||
expect(output).not.toContain('ContextSummaryDisplay');
|
expect(output).not.toContain('ContextSummaryDisplay');
|
||||||
@@ -828,11 +852,16 @@ describe('Composer', () => {
|
|||||||
describe('Shortcuts Hint', () => {
|
describe('Shortcuts Hint', () => {
|
||||||
it('restores shortcuts hint after 200ms debounce when buffer is empty', async () => {
|
it('restores shortcuts hint after 200ms debounce when buffer is empty', async () => {
|
||||||
const uiState = createMockUIState({
|
const uiState = createMockUIState({
|
||||||
buffer: { text: '' } as unknown as TextBuffer,
|
|
||||||
cleanUiDetailsVisible: false,
|
cleanUiDetailsVisible: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { lastFrame } = await renderComposer(uiState);
|
const { lastFrame } = await renderComposer(
|
||||||
|
uiState,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
{ buffer: { text: '' } as unknown as TextBuffer },
|
||||||
|
);
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
await vi.advanceTimersByTimeAsync(250);
|
await vi.advanceTimersByTimeAsync(250);
|
||||||
@@ -845,11 +874,16 @@ describe('Composer', () => {
|
|||||||
|
|
||||||
it('hides shortcuts hint when text is typed in buffer', async () => {
|
it('hides shortcuts hint when text is typed in buffer', async () => {
|
||||||
const uiState = createMockUIState({
|
const uiState = createMockUIState({
|
||||||
buffer: { text: 'hello' } as unknown as TextBuffer,
|
|
||||||
cleanUiDetailsVisible: false,
|
cleanUiDetailsVisible: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { lastFrame } = await renderComposer(uiState);
|
const { lastFrame } = await renderComposer(
|
||||||
|
uiState,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
{ buffer: { text: 'hello' } as unknown as TextBuffer },
|
||||||
|
);
|
||||||
|
|
||||||
expect(lastFrame()).not.toContain('press tab twice for more');
|
expect(lastFrame()).not.toContain('press tab twice for more');
|
||||||
expect(lastFrame()).not.toContain('? for shortcuts');
|
expect(lastFrame()).not.toContain('? for shortcuts');
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { useState, useEffect } from 'react';
|
|||||||
import { useConfig } from '../contexts/ConfigContext.js';
|
import { useConfig } from '../contexts/ConfigContext.js';
|
||||||
import { useSettings } from '../contexts/SettingsContext.js';
|
import { useSettings } from '../contexts/SettingsContext.js';
|
||||||
import { useUIState } from '../contexts/UIStateContext.js';
|
import { useUIState } from '../contexts/UIStateContext.js';
|
||||||
|
import { useInputState } from '../contexts/InputContext.js';
|
||||||
import { useUIActions } from '../contexts/UIActionsContext.js';
|
import { useUIActions } from '../contexts/UIActionsContext.js';
|
||||||
import { useVimMode } from '../contexts/VimModeContext.js';
|
import { useVimMode } from '../contexts/VimModeContext.js';
|
||||||
import { useAlternateBuffer } from '../hooks/useAlternateBuffer.js';
|
import { useAlternateBuffer } from '../hooks/useAlternateBuffer.js';
|
||||||
@@ -30,6 +31,7 @@ import { appEvents, AppEvent } from '../../utils/events.js';
|
|||||||
|
|
||||||
export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
||||||
const uiState = useUIState();
|
const uiState = useUIState();
|
||||||
|
const inputState = useInputState();
|
||||||
const uiActions = useUIActions();
|
const uiActions = useUIActions();
|
||||||
const settings = useSettings();
|
const settings = useSettings();
|
||||||
const config = useConfig();
|
const config = useConfig();
|
||||||
@@ -81,12 +83,12 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasToast = shouldShowToast(uiState);
|
const showToast = shouldShowToast(uiState, inputState);
|
||||||
const hideUiDetailsForSuggestions =
|
const hideUiDetailsForSuggestions =
|
||||||
suggestionsVisible && suggestionsPosition === 'above';
|
suggestionsVisible && suggestionsPosition === 'above';
|
||||||
|
|
||||||
// Mini Mode VIP Flags (Pure Content Triggers)
|
// Mini Mode VIP Flags (Pure Content Triggers)
|
||||||
const showMinimalToast = hasToast;
|
const showMinimalToast = showToast;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@@ -141,17 +143,12 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
|||||||
|
|
||||||
{uiState.isInputActive && (
|
{uiState.isInputActive && (
|
||||||
<InputPrompt
|
<InputPrompt
|
||||||
buffer={uiState.buffer}
|
|
||||||
inputWidth={uiState.inputWidth}
|
|
||||||
suggestionsWidth={uiState.suggestionsWidth}
|
|
||||||
onSubmit={uiActions.handleFinalSubmit}
|
onSubmit={uiActions.handleFinalSubmit}
|
||||||
userMessages={uiState.userMessages}
|
|
||||||
setBannerVisible={uiActions.setBannerVisible}
|
setBannerVisible={uiActions.setBannerVisible}
|
||||||
onClearScreen={uiActions.handleClearScreen}
|
onClearScreen={uiActions.handleClearScreen}
|
||||||
config={config}
|
config={config}
|
||||||
slashCommands={uiState.slashCommands || []}
|
slashCommands={uiState.slashCommands || []}
|
||||||
commandContext={uiState.commandContext}
|
commandContext={uiState.commandContext}
|
||||||
shellModeActive={uiState.shellModeActive}
|
|
||||||
setShellModeActive={uiActions.setShellModeActive}
|
setShellModeActive={uiActions.setShellModeActive}
|
||||||
approvalMode={uiState.showApprovalModeIndicator}
|
approvalMode={uiState.showApprovalModeIndicator}
|
||||||
onEscapePromptChange={uiActions.onEscapePromptChange}
|
onEscapePromptChange={uiActions.onEscapePromptChange}
|
||||||
@@ -165,7 +162,7 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
|||||||
? vimMode === 'INSERT'
|
? vimMode === 'INSERT'
|
||||||
? " Press 'Esc' for NORMAL mode."
|
? " Press 'Esc' for NORMAL mode."
|
||||||
: " Press 'i' for INSERT mode."
|
: " Press 'i' for INSERT mode."
|
||||||
: uiState.shellModeActive
|
: inputState.shellModeActive
|
||||||
? ' Type your shell command'
|
? ' Type your shell command'
|
||||||
: ' Type your message or @path/to/file'
|
: ' Type your message or @path/to/file'
|
||||||
}
|
}
|
||||||
@@ -173,7 +170,6 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
|||||||
streamingState={uiState.streamingState}
|
streamingState={uiState.streamingState}
|
||||||
suggestionsPosition={suggestionsPosition}
|
suggestionsPosition={suggestionsPosition}
|
||||||
onSuggestionsVisibilityChange={setSuggestionsVisible}
|
onSuggestionsVisibilityChange={setSuggestionsVisible}
|
||||||
copyModeEnabled={uiState.copyModeEnabled}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import type React from 'react';
|
|||||||
import { Box, Text } from 'ink';
|
import { Box, Text } from 'ink';
|
||||||
import { theme } from '../semantic-colors.js';
|
import { theme } from '../semantic-colors.js';
|
||||||
import { type IdeContext, type MCPServerConfig } from '@google/gemini-cli-core';
|
import { type IdeContext, type MCPServerConfig } from '@google/gemini-cli-core';
|
||||||
|
import { Command } from '../key/keyMatchers.js';
|
||||||
|
import { formatCommand } from '../key/keybindingUtils.js';
|
||||||
|
|
||||||
interface ContextSummaryDisplayProps {
|
interface ContextSummaryDisplayProps {
|
||||||
geminiMdFileCount: number;
|
geminiMdFileCount: number;
|
||||||
@@ -49,7 +51,7 @@ export const ContextSummaryDisplay: React.FC<ContextSummaryDisplayProps> = ({
|
|||||||
}
|
}
|
||||||
return `${openFileCount} open file${
|
return `${openFileCount} open file${
|
||||||
openFileCount > 1 ? 's' : ''
|
openFileCount > 1 ? 's' : ''
|
||||||
} (ctrl+g to view)`;
|
} (${formatCommand(Command.SHOW_IDE_CONTEXT_DETAIL)} to view)`;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const geminiMdText = (() => {
|
const geminiMdText = (() => {
|
||||||
|
|||||||
@@ -4,34 +4,36 @@
|
|||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { render } from '../../test-utils/render.js';
|
|
||||||
import { CopyModeWarning } from './CopyModeWarning.js';
|
import { CopyModeWarning } from './CopyModeWarning.js';
|
||||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
import { useUIState, type UIState } from '../contexts/UIStateContext.js';
|
import { renderWithProviders } from '../../test-utils/render.js';
|
||||||
|
import { useInputState } from '../contexts/InputContext.js';
|
||||||
|
|
||||||
vi.mock('../contexts/UIStateContext.js');
|
vi.mock('../contexts/InputContext.js');
|
||||||
|
|
||||||
describe('CopyModeWarning', () => {
|
describe('CopyModeWarning', () => {
|
||||||
const mockUseUIState = vi.mocked(useUIState);
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders nothing when copy mode is disabled', async () => {
|
it('renders nothing when copy mode is disabled', async () => {
|
||||||
mockUseUIState.mockReturnValue({
|
vi.mocked(useInputState).mockReturnValue({
|
||||||
copyModeEnabled: false,
|
copyModeEnabled: false,
|
||||||
} as unknown as UIState);
|
} as unknown as ReturnType<typeof useInputState>);
|
||||||
const { lastFrame, unmount } = await render(<CopyModeWarning />);
|
const { lastFrame, unmount } = await renderWithProviders(
|
||||||
|
<CopyModeWarning />,
|
||||||
|
);
|
||||||
expect(lastFrame({ allowEmpty: true })).toBe('');
|
expect(lastFrame({ allowEmpty: true })).toBe('');
|
||||||
unmount();
|
unmount();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders warning when copy mode is enabled', async () => {
|
it('renders warning when copy mode is enabled', async () => {
|
||||||
mockUseUIState.mockReturnValue({
|
vi.mocked(useInputState).mockReturnValue({
|
||||||
copyModeEnabled: true,
|
copyModeEnabled: true,
|
||||||
} as unknown as UIState);
|
} as unknown as ReturnType<typeof useInputState>);
|
||||||
const { lastFrame, unmount } = await render(<CopyModeWarning />);
|
const { lastFrame, unmount } = await renderWithProviders(
|
||||||
|
<CopyModeWarning />,
|
||||||
|
);
|
||||||
expect(lastFrame()).toContain('In Copy Mode');
|
expect(lastFrame()).toContain('In Copy Mode');
|
||||||
expect(lastFrame()).toContain('Use Page Up/Down to scroll');
|
expect(lastFrame()).toContain('Use Page Up/Down to scroll');
|
||||||
expect(lastFrame()).toContain('Press Ctrl+S or any other key to exit');
|
expect(lastFrame()).toContain('Press Ctrl+S or any other key to exit');
|
||||||
|
|||||||
@@ -6,11 +6,11 @@
|
|||||||
|
|
||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
import { Box, Text } from 'ink';
|
import { Box, Text } from 'ink';
|
||||||
import { useUIState } from '../contexts/UIStateContext.js';
|
import { useInputState } from '../contexts/InputContext.js';
|
||||||
import { theme } from '../semantic-colors.js';
|
import { theme } from '../semantic-colors.js';
|
||||||
|
|
||||||
export const CopyModeWarning: React.FC = () => {
|
export const CopyModeWarning: React.FC = () => {
|
||||||
const { copyModeEnabled } = useUIState();
|
const { copyModeEnabled } = useInputState();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box height={1}>
|
<Box height={1}>
|
||||||
|
|||||||
@@ -169,6 +169,11 @@ Implement a comprehensive authentication system with multiple providers.
|
|||||||
getUseTerminalBuffer: () => false,
|
getUseTerminalBuffer: () => false,
|
||||||
} as unknown as import('@google/gemini-cli-core').Config,
|
} as unknown as import('@google/gemini-cli-core').Config,
|
||||||
settings: createMockSettings({ ui: { useAlternateBuffer } }),
|
settings: createMockSettings({ ui: { useAlternateBuffer } }),
|
||||||
|
inputState: {
|
||||||
|
buffer: { text: '' } as never,
|
||||||
|
showEscapePrompt: false,
|
||||||
|
shellModeActive: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -472,6 +477,11 @@ Implement a comprehensive authentication system with multiple providers.
|
|||||||
settings: createMockSettings({
|
settings: createMockSettings({
|
||||||
ui: { useAlternateBuffer: useAlternateBuffer ?? true },
|
ui: { useAlternateBuffer: useAlternateBuffer ?? true },
|
||||||
}),
|
}),
|
||||||
|
inputState: {
|
||||||
|
buffer: { text: '' } as never,
|
||||||
|
showEscapePrompt: false,
|
||||||
|
shellModeActive: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -577,7 +587,7 @@ Implement a comprehensive authentication system with multiple providers.
|
|||||||
expect(onFeedback).not.toHaveBeenCalled();
|
expect(onFeedback).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('automatically submits feedback when Ctrl+X is used to edit the plan', async () => {
|
it('automatically submits feedback when Ctrl+G is used to edit the plan', async () => {
|
||||||
const { stdin, lastFrame } = await act(async () =>
|
const { stdin, lastFrame } = await act(async () =>
|
||||||
renderDialog({ useAlternateBuffer }),
|
renderDialog({ useAlternateBuffer }),
|
||||||
);
|
);
|
||||||
@@ -590,9 +600,9 @@ Implement a comprehensive authentication system with multiple providers.
|
|||||||
expect(lastFrame()).toContain('Add user authentication');
|
expect(lastFrame()).toContain('Add user authentication');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Press Ctrl+X
|
// Press Ctrl+G
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
writeKey(stdin, '\x18'); // Ctrl+X
|
writeKey(stdin, '\x07'); // Ctrl+G
|
||||||
});
|
});
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
|
|||||||
@@ -25,6 +25,11 @@ import { useKeypress } from '../hooks/useKeypress.js';
|
|||||||
import { Command } from '../key/keyMatchers.js';
|
import { Command } from '../key/keyMatchers.js';
|
||||||
import { formatCommand } from '../key/keybindingUtils.js';
|
import { formatCommand } from '../key/keybindingUtils.js';
|
||||||
import { useKeyMatchers } from '../hooks/useKeyMatchers.js';
|
import { useKeyMatchers } from '../hooks/useKeyMatchers.js';
|
||||||
|
import {
|
||||||
|
appEvents,
|
||||||
|
AppEvent,
|
||||||
|
TransientMessageType,
|
||||||
|
} from '../../utils/events.js';
|
||||||
|
|
||||||
export interface ExitPlanModeDialogProps {
|
export interface ExitPlanModeDialogProps {
|
||||||
planPath: string;
|
planPath: string;
|
||||||
@@ -173,6 +178,14 @@ export const ExitPlanModeDialog: React.FC<ExitPlanModeDialogProps> = ({
|
|||||||
void handleOpenEditor();
|
void handleOpenEditor();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (keyMatchers[Command.DEPRECATED_OPEN_EXTERNAL_EDITOR](key)) {
|
||||||
|
const cmdKey = formatCommand(Command.OPEN_EXTERNAL_EDITOR);
|
||||||
|
appEvents.emit(AppEvent.TransientMessage, {
|
||||||
|
message: `Use ${cmdKey} to open the external editor.`,
|
||||||
|
type: TransientMessageType.Hint,
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
{ isActive: true, priority: true },
|
{ isActive: true, priority: true },
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import { useUIState } from '../contexts/UIStateContext.js';
|
|||||||
import { useConfig } from '../contexts/ConfigContext.js';
|
import { useConfig } from '../contexts/ConfigContext.js';
|
||||||
import { useSettings } from '../contexts/SettingsContext.js';
|
import { useSettings } from '../contexts/SettingsContext.js';
|
||||||
import { useVimMode } from '../contexts/VimModeContext.js';
|
import { useVimMode } from '../contexts/VimModeContext.js';
|
||||||
|
import { useInputState } from '../contexts/InputContext.js';
|
||||||
import {
|
import {
|
||||||
ALL_ITEMS,
|
ALL_ITEMS,
|
||||||
type FooterItemId,
|
type FooterItemId,
|
||||||
@@ -173,6 +174,7 @@ interface FooterColumn {
|
|||||||
|
|
||||||
export const Footer: React.FC = () => {
|
export const Footer: React.FC = () => {
|
||||||
const uiState = useUIState();
|
const uiState = useUIState();
|
||||||
|
const { copyModeEnabled } = useInputState();
|
||||||
const config = useConfig();
|
const config = useConfig();
|
||||||
const settings = useSettings();
|
const settings = useSettings();
|
||||||
const { vimEnabled, vimMode } = useVimMode();
|
const { vimEnabled, vimMode } = useVimMode();
|
||||||
@@ -365,10 +367,7 @@ export const Footer: React.FC = () => {
|
|||||||
id,
|
id,
|
||||||
header,
|
header,
|
||||||
() => (
|
() => (
|
||||||
<MemoryUsageDisplay
|
<MemoryUsageDisplay color={itemColor} isActive={!copyModeEnabled} />
|
||||||
color={itemColor}
|
|
||||||
isActive={!uiState.copyModeEnabled}
|
|
||||||
/>
|
|
||||||
),
|
),
|
||||||
10,
|
10,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -134,6 +134,7 @@ export const HistoryItemDisplay: React.FC<HistoryItemDisplayProps> = ({
|
|||||||
<InfoMessage
|
<InfoMessage
|
||||||
text={itemForDisplay.text}
|
text={itemForDisplay.text}
|
||||||
secondaryText={itemForDisplay.secondaryText}
|
secondaryText={itemForDisplay.secondaryText}
|
||||||
|
source={itemForDisplay.source}
|
||||||
icon={itemForDisplay.icon}
|
icon={itemForDisplay.icon}
|
||||||
color={itemForDisplay.color}
|
color={itemForDisplay.color}
|
||||||
marginBottom={itemForDisplay.marginBottom}
|
marginBottom={itemForDisplay.marginBottom}
|
||||||
|
|||||||
@@ -5,13 +5,24 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
import { useCallback, useEffect, useState, useRef, useMemo } from 'react';
|
import {
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useState,
|
||||||
|
useRef,
|
||||||
|
useMemo,
|
||||||
|
Fragment,
|
||||||
|
} from 'react';
|
||||||
import clipboardy from 'clipboardy';
|
import clipboardy from 'clipboardy';
|
||||||
import { Box, Text, useStdout, type DOMElement } from 'ink';
|
import { Box, Text, useStdout, type DOMElement } from 'ink';
|
||||||
import { SuggestionsDisplay, MAX_WIDTH } from './SuggestionsDisplay.js';
|
import { SuggestionsDisplay, MAX_WIDTH } from './SuggestionsDisplay.js';
|
||||||
import { theme } from '../semantic-colors.js';
|
import { theme } from '../semantic-colors.js';
|
||||||
import { useInputHistory } from '../hooks/useInputHistory.js';
|
import { useInputHistory } from '../hooks/useInputHistory.js';
|
||||||
import { escapeAtSymbols } from '../hooks/atCommandProcessor.js';
|
import { escapeAtSymbols } from '../hooks/atCommandProcessor.js';
|
||||||
|
import {
|
||||||
|
ScrollableList,
|
||||||
|
type ScrollableListRef,
|
||||||
|
} from './shared/ScrollableList.js';
|
||||||
import { HalfLinePaddedBox } from './shared/HalfLinePaddedBox.js';
|
import { HalfLinePaddedBox } from './shared/HalfLinePaddedBox.js';
|
||||||
import {
|
import {
|
||||||
type TextBuffer,
|
type TextBuffer,
|
||||||
@@ -66,6 +77,7 @@ import { getSafeLowColorBackground } from '../themes/color-utils.js';
|
|||||||
import { isLowColorDepth } from '../utils/terminalUtils.js';
|
import { isLowColorDepth } from '../utils/terminalUtils.js';
|
||||||
import { useShellFocusState } from '../contexts/ShellFocusContext.js';
|
import { useShellFocusState } from '../contexts/ShellFocusContext.js';
|
||||||
import { useUIState } from '../contexts/UIStateContext.js';
|
import { useUIState } from '../contexts/UIStateContext.js';
|
||||||
|
import { useInputState } from '../contexts/InputContext.js';
|
||||||
import {
|
import {
|
||||||
appEvents,
|
appEvents,
|
||||||
AppEvent,
|
AppEvent,
|
||||||
@@ -95,19 +107,18 @@ export function isTerminalPasteTrusted(
|
|||||||
return kittyProtocolSupported;
|
return kittyProtocolSupported;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ScrollableItem =
|
||||||
|
| { type: 'visualLine'; lineText: string; absoluteVisualIdx: number }
|
||||||
|
| { type: 'ghostLine'; ghostLine: string; index: number };
|
||||||
|
|
||||||
export interface InputPromptProps {
|
export interface InputPromptProps {
|
||||||
buffer: TextBuffer;
|
|
||||||
onSubmit: (value: string) => void;
|
onSubmit: (value: string) => void;
|
||||||
userMessages: readonly string[];
|
|
||||||
onClearScreen: () => void;
|
onClearScreen: () => void;
|
||||||
config: Config;
|
config: Config;
|
||||||
slashCommands: readonly SlashCommand[];
|
slashCommands: readonly SlashCommand[];
|
||||||
commandContext: CommandContext;
|
commandContext: CommandContext;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
focus?: boolean;
|
focus?: boolean;
|
||||||
inputWidth: number;
|
|
||||||
suggestionsWidth: number;
|
|
||||||
shellModeActive: boolean;
|
|
||||||
setShellModeActive: (value: boolean) => void;
|
setShellModeActive: (value: boolean) => void;
|
||||||
approvalMode: ApprovalMode;
|
approvalMode: ApprovalMode;
|
||||||
onEscapePromptChange?: (showPrompt: boolean) => void;
|
onEscapePromptChange?: (showPrompt: boolean) => void;
|
||||||
@@ -120,7 +131,6 @@ export interface InputPromptProps {
|
|||||||
onQueueMessage?: (message: string) => void;
|
onQueueMessage?: (message: string) => void;
|
||||||
suggestionsPosition?: 'above' | 'below';
|
suggestionsPosition?: 'above' | 'below';
|
||||||
setBannerVisible: (visible: boolean) => void;
|
setBannerVisible: (visible: boolean) => void;
|
||||||
copyModeEnabled?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The input content, input container, and input suggestions list may have different widths
|
// The input content, input container, and input suggestions list may have different widths
|
||||||
@@ -191,18 +201,13 @@ export function tryTogglePasteExpansion(buffer: TextBuffer): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const InputPrompt: React.FC<InputPromptProps> = ({
|
export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||||
buffer,
|
|
||||||
onSubmit,
|
onSubmit,
|
||||||
userMessages,
|
|
||||||
onClearScreen,
|
onClearScreen,
|
||||||
config,
|
config,
|
||||||
slashCommands,
|
slashCommands,
|
||||||
commandContext,
|
commandContext,
|
||||||
placeholder = ' Type your message or @path/to/file',
|
placeholder = ' Type your message or @path/to/file',
|
||||||
focus = true,
|
focus = true,
|
||||||
inputWidth,
|
|
||||||
suggestionsWidth,
|
|
||||||
shellModeActive,
|
|
||||||
setShellModeActive,
|
setShellModeActive,
|
||||||
approvalMode,
|
approvalMode,
|
||||||
onEscapePromptChange,
|
onEscapePromptChange,
|
||||||
@@ -215,8 +220,16 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
|||||||
onQueueMessage,
|
onQueueMessage,
|
||||||
suggestionsPosition = 'below',
|
suggestionsPosition = 'below',
|
||||||
setBannerVisible,
|
setBannerVisible,
|
||||||
copyModeEnabled = false,
|
|
||||||
}) => {
|
}) => {
|
||||||
|
const inputState = useInputState();
|
||||||
|
const {
|
||||||
|
buffer,
|
||||||
|
userMessages,
|
||||||
|
shellModeActive,
|
||||||
|
copyModeEnabled,
|
||||||
|
inputWidth,
|
||||||
|
suggestionsWidth,
|
||||||
|
} = inputState;
|
||||||
const isHelpDismissKey = useIsHelpDismissKey();
|
const isHelpDismissKey = useIsHelpDismissKey();
|
||||||
const keyMatchers = useKeyMatchers();
|
const keyMatchers = useKeyMatchers();
|
||||||
const { stdout } = useStdout();
|
const { stdout } = useStdout();
|
||||||
@@ -268,6 +281,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
|||||||
const pasteTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
const pasteTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
const innerBoxRef = useRef<DOMElement>(null);
|
const innerBoxRef = useRef<DOMElement>(null);
|
||||||
const hasUserNavigatedSuggestions = useRef(false);
|
const hasUserNavigatedSuggestions = useRef(false);
|
||||||
|
const listRef = useRef<ScrollableListRef<ScrollableItem>>(null);
|
||||||
|
|
||||||
const [reverseSearchActive, setReverseSearchActive] = useState(false);
|
const [reverseSearchActive, setReverseSearchActive] = useState(false);
|
||||||
const [commandSearchActive, setCommandSearchActive] = useState(false);
|
const [commandSearchActive, setCommandSearchActive] = useState(false);
|
||||||
@@ -425,7 +439,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
|||||||
slashCommands,
|
slashCommands,
|
||||||
);
|
);
|
||||||
if (commandToExecute?.isSafeConcurrent) {
|
if (commandToExecute?.isSafeConcurrent) {
|
||||||
inputHistory.handleSubmit(trimmedMessage);
|
handleSubmitAndClear(trimmedMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -443,6 +457,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
|||||||
streamingState,
|
streamingState,
|
||||||
setQueueErrorMessage,
|
setQueueErrorMessage,
|
||||||
slashCommands,
|
slashCommands,
|
||||||
|
handleSubmitAndClear,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -556,7 +571,10 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
|||||||
if (isEmbeddedShellFocused) {
|
if (isEmbeddedShellFocused) {
|
||||||
setEmbeddedShellFocused(false);
|
setEmbeddedShellFocused(false);
|
||||||
}
|
}
|
||||||
const visualRow = buffer.visualScrollRow + relY;
|
const currentScrollTop = Math.round(
|
||||||
|
listRef.current?.getScrollState().scrollTop ?? buffer.visualScrollRow,
|
||||||
|
);
|
||||||
|
const visualRow = currentScrollTop + relY;
|
||||||
buffer.moveToVisualPosition(visualRow, relX);
|
buffer.moveToVisualPosition(visualRow, relX);
|
||||||
},
|
},
|
||||||
{ isActive: focus },
|
{ isActive: focus },
|
||||||
@@ -570,7 +588,10 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
|||||||
(_event, relX, relY) => {
|
(_event, relX, relY) => {
|
||||||
if (!isAlternateBuffer) return;
|
if (!isAlternateBuffer) return;
|
||||||
|
|
||||||
const visualLine = buffer.viewportVisualLines[relY];
|
const currentScrollTop = Math.round(
|
||||||
|
listRef.current?.getScrollState().scrollTop ?? buffer.visualScrollRow,
|
||||||
|
);
|
||||||
|
const visualLine = buffer.allVisualLines[currentScrollTop + relY];
|
||||||
if (!visualLine) return;
|
if (!visualLine) return;
|
||||||
|
|
||||||
// Even if we click past the end of the line, we might want to collapse an expanded paste
|
// Even if we click past the end of the line, we might want to collapse an expanded paste
|
||||||
@@ -578,10 +599,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
|||||||
|
|
||||||
const logicalPos = isPastEndOfLine
|
const logicalPos = isPastEndOfLine
|
||||||
? null
|
? null
|
||||||
: buffer.getLogicalPositionFromVisual(
|
: buffer.getLogicalPositionFromVisual(currentScrollTop + relY, relX);
|
||||||
buffer.visualScrollRow + relY,
|
|
||||||
relX,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Check for paste placeholder (collapsed state)
|
// Check for paste placeholder (collapsed state)
|
||||||
if (logicalPos) {
|
if (logicalPos) {
|
||||||
@@ -603,7 +621,9 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
|||||||
|
|
||||||
// If we didn't click a placeholder to expand, check if we are inside or after
|
// If we didn't click a placeholder to expand, check if we are inside or after
|
||||||
// an expanded paste region and collapse it.
|
// an expanded paste region and collapse it.
|
||||||
const row = buffer.visualScrollRow + relY;
|
const visualRow = currentScrollTop + relY;
|
||||||
|
const mapEntry = buffer.visualToLogicalMap[visualRow];
|
||||||
|
const row = mapEntry ? mapEntry[0] : visualRow;
|
||||||
const expandedId = buffer.getExpandedPasteAtLine(row);
|
const expandedId = buffer.getExpandedPasteAtLine(row);
|
||||||
if (expandedId) {
|
if (expandedId) {
|
||||||
buffer.togglePasteExpansion(
|
buffer.togglePasteExpansion(
|
||||||
@@ -1252,6 +1272,15 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (keyMatchers[Command.DEPRECATED_OPEN_EXTERNAL_EDITOR](key)) {
|
||||||
|
const cmdKey = formatCommand(Command.OPEN_EXTERNAL_EDITOR);
|
||||||
|
appEvents.emit(AppEvent.TransientMessage, {
|
||||||
|
message: `Use ${cmdKey} to open the external editor.`,
|
||||||
|
type: TransientMessageType.Hint,
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Ctrl+V for clipboard paste
|
// Ctrl+V for clipboard paste
|
||||||
if (keyMatchers[Command.PASTE_CLIPBOARD](key)) {
|
if (keyMatchers[Command.PASTE_CLIPBOARD](key)) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
@@ -1350,10 +1379,8 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
|||||||
priority: true,
|
priority: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const linesToRender = buffer.viewportVisualLines;
|
|
||||||
const [cursorVisualRowAbsolute, cursorVisualColAbsolute] =
|
const [cursorVisualRowAbsolute, cursorVisualColAbsolute] =
|
||||||
buffer.visualCursor;
|
buffer.visualCursor;
|
||||||
const scrollVisualRow = buffer.visualScrollRow;
|
|
||||||
|
|
||||||
const getGhostTextLines = useCallback(() => {
|
const getGhostTextLines = useCallback(() => {
|
||||||
if (
|
if (
|
||||||
@@ -1468,6 +1495,155 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
|||||||
|
|
||||||
const { inlineGhost, additionalLines } = getGhostTextLines();
|
const { inlineGhost, additionalLines } = getGhostTextLines();
|
||||||
|
|
||||||
|
const scrollableData = useMemo(() => {
|
||||||
|
const items: ScrollableItem[] = buffer.allVisualLines.map(
|
||||||
|
(lineText, index) => ({
|
||||||
|
type: 'visualLine',
|
||||||
|
lineText,
|
||||||
|
absoluteVisualIdx: index,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
additionalLines.forEach((ghostLine, index) => {
|
||||||
|
items.push({
|
||||||
|
type: 'ghostLine',
|
||||||
|
ghostLine,
|
||||||
|
index,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}, [buffer.allVisualLines, additionalLines]);
|
||||||
|
|
||||||
|
const renderItem = useCallback(
|
||||||
|
({ item }: { item: ScrollableItem; index: number }) => {
|
||||||
|
if (item.type === 'ghostLine') {
|
||||||
|
const padding = Math.max(0, inputWidth - stringWidth(item.ghostLine));
|
||||||
|
return (
|
||||||
|
<Box height={1}>
|
||||||
|
<Text color={theme.text.secondary}>
|
||||||
|
{item.ghostLine}
|
||||||
|
{' '.repeat(padding)}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { lineText, absoluteVisualIdx } = item;
|
||||||
|
// console.log('renderItem called with:', lineText);
|
||||||
|
const mapEntry = buffer.visualToLogicalMap[absoluteVisualIdx];
|
||||||
|
if (!mapEntry) return <Text> </Text>;
|
||||||
|
|
||||||
|
const isOnCursorLine =
|
||||||
|
focus && absoluteVisualIdx === cursorVisualRowAbsolute;
|
||||||
|
const renderedLine: React.ReactNode[] = [];
|
||||||
|
const [logicalLineIdx] = mapEntry;
|
||||||
|
const logicalLine = buffer.lines[logicalLineIdx] || '';
|
||||||
|
const transformations =
|
||||||
|
buffer.transformationsByLine[logicalLineIdx] ?? [];
|
||||||
|
const tokens = parseInputForHighlighting(
|
||||||
|
logicalLine,
|
||||||
|
logicalLineIdx,
|
||||||
|
transformations,
|
||||||
|
...(focus && buffer.cursor[0] === logicalLineIdx
|
||||||
|
? [buffer.cursor[1]]
|
||||||
|
: []),
|
||||||
|
);
|
||||||
|
const visualStartCol =
|
||||||
|
buffer.visualToTransformedMap[absoluteVisualIdx] ?? 0;
|
||||||
|
const visualEndCol = visualStartCol + cpLen(lineText);
|
||||||
|
const segments = parseSegmentsFromTokens(
|
||||||
|
tokens,
|
||||||
|
visualStartCol,
|
||||||
|
visualEndCol,
|
||||||
|
);
|
||||||
|
let charCount = 0;
|
||||||
|
segments.forEach((seg, segIdx) => {
|
||||||
|
const segLen = cpLen(seg.text);
|
||||||
|
let display = seg.text;
|
||||||
|
if (isOnCursorLine) {
|
||||||
|
const relCol = cursorVisualColAbsolute;
|
||||||
|
const segStart = charCount;
|
||||||
|
const segEnd = segStart + segLen;
|
||||||
|
if (relCol >= segStart && relCol < segEnd) {
|
||||||
|
const charToHighlight = cpSlice(
|
||||||
|
display,
|
||||||
|
relCol - segStart,
|
||||||
|
relCol - segStart + 1,
|
||||||
|
);
|
||||||
|
const highlighted = showCursor
|
||||||
|
? chalk.inverse(charToHighlight)
|
||||||
|
: charToHighlight;
|
||||||
|
display =
|
||||||
|
cpSlice(display, 0, relCol - segStart) +
|
||||||
|
highlighted +
|
||||||
|
cpSlice(display, relCol - segStart + 1);
|
||||||
|
}
|
||||||
|
charCount = segEnd;
|
||||||
|
} else {
|
||||||
|
charCount += segLen;
|
||||||
|
}
|
||||||
|
const color =
|
||||||
|
seg.type === 'command' || seg.type === 'file' || seg.type === 'paste'
|
||||||
|
? theme.text.accent
|
||||||
|
: theme.text.primary;
|
||||||
|
renderedLine.push(
|
||||||
|
<Text key={`token-${segIdx}`} color={color}>
|
||||||
|
{display}
|
||||||
|
</Text>,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const currentLineGhost = isOnCursorLine ? inlineGhost : '';
|
||||||
|
if (
|
||||||
|
isOnCursorLine &&
|
||||||
|
cursorVisualColAbsolute === cpLen(lineText) &&
|
||||||
|
!currentLineGhost
|
||||||
|
) {
|
||||||
|
renderedLine.push(
|
||||||
|
<Text key={`cursor-end-${cursorVisualColAbsolute}`}>
|
||||||
|
{showCursor ? chalk.inverse(' ') : ' '}
|
||||||
|
</Text>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const showCursorBeforeGhost =
|
||||||
|
focus &&
|
||||||
|
isOnCursorLine &&
|
||||||
|
cursorVisualColAbsolute === cpLen(lineText) &&
|
||||||
|
currentLineGhost;
|
||||||
|
return (
|
||||||
|
<Box height={1}>
|
||||||
|
<Text
|
||||||
|
terminalCursorFocus={showCursor && isOnCursorLine}
|
||||||
|
terminalCursorPosition={cpIndexToOffset(
|
||||||
|
lineText,
|
||||||
|
cursorVisualColAbsolute,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{renderedLine}
|
||||||
|
{showCursorBeforeGhost && (showCursor ? chalk.inverse(' ') : ' ')}
|
||||||
|
{currentLineGhost && (
|
||||||
|
<Text color={theme.text.secondary}>{currentLineGhost}</Text>
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[
|
||||||
|
buffer.visualToLogicalMap,
|
||||||
|
buffer.lines,
|
||||||
|
buffer.transformationsByLine,
|
||||||
|
buffer.cursor,
|
||||||
|
buffer.visualToTransformedMap,
|
||||||
|
focus,
|
||||||
|
cursorVisualRowAbsolute,
|
||||||
|
cursorVisualColAbsolute,
|
||||||
|
showCursor,
|
||||||
|
inlineGhost,
|
||||||
|
inputWidth,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
const useBackgroundColor = config.getUseBackgroundColor();
|
const useBackgroundColor = config.getUseBackgroundColor();
|
||||||
const isLowColor = isLowColorDepth();
|
const isLowColor = isLowColorDepth();
|
||||||
const terminalBg = theme.background.primary || 'black';
|
const terminalBg = theme.background.primary || 'black';
|
||||||
@@ -1485,6 +1661,46 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
|||||||
return false;
|
return false;
|
||||||
}, [useBackgroundColor, isLowColor, terminalBg]);
|
}, [useBackgroundColor, isLowColor, terminalBg]);
|
||||||
|
|
||||||
|
const prevCursorRef = useRef(buffer.visualCursor);
|
||||||
|
const prevTextRef = useRef(buffer.text);
|
||||||
|
|
||||||
|
// Effect to ensure cursor remains visible after interactions
|
||||||
|
useEffect(() => {
|
||||||
|
const cursorChanged = prevCursorRef.current !== buffer.visualCursor;
|
||||||
|
const textChanged = prevTextRef.current !== buffer.text;
|
||||||
|
|
||||||
|
prevCursorRef.current = buffer.visualCursor;
|
||||||
|
prevTextRef.current = buffer.text;
|
||||||
|
|
||||||
|
if (!cursorChanged && !textChanged) return;
|
||||||
|
|
||||||
|
if (!listRef.current || !focus) return;
|
||||||
|
const { scrollTop, innerHeight } = listRef.current.getScrollState();
|
||||||
|
if (innerHeight === 0) return;
|
||||||
|
|
||||||
|
const cursorVisualRow = buffer.visualCursor[0];
|
||||||
|
const actualScrollTop = Math.round(scrollTop);
|
||||||
|
|
||||||
|
// If cursor is out of the currently visible viewport...
|
||||||
|
if (
|
||||||
|
cursorVisualRow < actualScrollTop ||
|
||||||
|
cursorVisualRow >= actualScrollTop + innerHeight
|
||||||
|
) {
|
||||||
|
// Calculate minimal scroll to make it visible
|
||||||
|
let newScrollTop = actualScrollTop;
|
||||||
|
if (cursorVisualRow < actualScrollTop) {
|
||||||
|
newScrollTop = cursorVisualRow;
|
||||||
|
} else if (cursorVisualRow >= actualScrollTop + innerHeight) {
|
||||||
|
newScrollTop = cursorVisualRow - innerHeight + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
listRef.current.scrollToIndex({ index: newScrollTop });
|
||||||
|
}
|
||||||
|
}, [buffer.visualCursor, buffer.text, focus]);
|
||||||
|
|
||||||
|
const listBackgroundColor =
|
||||||
|
useLineFallback || !useBackgroundColor ? undefined : theme.background.input;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (onSuggestionsVisibilityChange) {
|
if (onSuggestionsVisibilityChange) {
|
||||||
onSuggestionsVisibilityChange(shouldShowSuggestions);
|
onSuggestionsVisibilityChange(shouldShowSuggestions);
|
||||||
@@ -1615,153 +1831,51 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
|||||||
<Text color={theme.text.secondary}>{placeholder}</Text>
|
<Text color={theme.text.secondary}>{placeholder}</Text>
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
linesToRender
|
<Box
|
||||||
.map((lineText: string, visualIdxInRenderedSet: number) => {
|
flexDirection="column"
|
||||||
const absoluteVisualIdx =
|
height={Math.min(buffer.viewportHeight, scrollableData.length)}
|
||||||
scrollVisualRow + visualIdxInRenderedSet;
|
width="100%"
|
||||||
const mapEntry = buffer.visualToLogicalMap[absoluteVisualIdx];
|
>
|
||||||
if (!mapEntry) return null;
|
{isAlternateBuffer ? (
|
||||||
|
<ScrollableList
|
||||||
const cursorVisualRow =
|
ref={listRef}
|
||||||
cursorVisualRowAbsolute - scrollVisualRow;
|
hasFocus={focus}
|
||||||
const isOnCursorLine =
|
data={scrollableData}
|
||||||
focus && visualIdxInRenderedSet === cursorVisualRow;
|
renderItem={renderItem}
|
||||||
|
estimatedItemHeight={() => 1}
|
||||||
const renderedLine: React.ReactNode[] = [];
|
fixedItemHeight={true}
|
||||||
|
keyExtractor={(item) =>
|
||||||
const [logicalLineIdx] = mapEntry;
|
item.type === 'visualLine'
|
||||||
const logicalLine = buffer.lines[logicalLineIdx] || '';
|
? `line-${item.absoluteVisualIdx}`
|
||||||
const transformations =
|
: `ghost-${item.index}`
|
||||||
buffer.transformationsByLine[logicalLineIdx] ?? [];
|
|
||||||
const tokens = parseInputForHighlighting(
|
|
||||||
logicalLine,
|
|
||||||
logicalLineIdx,
|
|
||||||
transformations,
|
|
||||||
...(focus && buffer.cursor[0] === logicalLineIdx
|
|
||||||
? [buffer.cursor[1]]
|
|
||||||
: []),
|
|
||||||
);
|
|
||||||
const startColInTransformed =
|
|
||||||
buffer.visualToTransformedMap[absoluteVisualIdx] ?? 0;
|
|
||||||
const visualStartCol = startColInTransformed;
|
|
||||||
const visualEndCol = visualStartCol + cpLen(lineText);
|
|
||||||
const segments = parseSegmentsFromTokens(
|
|
||||||
tokens,
|
|
||||||
visualStartCol,
|
|
||||||
visualEndCol,
|
|
||||||
);
|
|
||||||
let charCount = 0;
|
|
||||||
segments.forEach((seg, segIdx) => {
|
|
||||||
const segLen = cpLen(seg.text);
|
|
||||||
let display = seg.text;
|
|
||||||
|
|
||||||
if (isOnCursorLine) {
|
|
||||||
const relativeVisualColForHighlight =
|
|
||||||
cursorVisualColAbsolute;
|
|
||||||
const segStart = charCount;
|
|
||||||
const segEnd = segStart + segLen;
|
|
||||||
if (
|
|
||||||
relativeVisualColForHighlight >= segStart &&
|
|
||||||
relativeVisualColForHighlight < segEnd
|
|
||||||
) {
|
|
||||||
const charToHighlight = cpSlice(
|
|
||||||
display,
|
|
||||||
relativeVisualColForHighlight - segStart,
|
|
||||||
relativeVisualColForHighlight - segStart + 1,
|
|
||||||
);
|
|
||||||
const highlighted = showCursor
|
|
||||||
? chalk.inverse(charToHighlight)
|
|
||||||
: charToHighlight;
|
|
||||||
display =
|
|
||||||
cpSlice(
|
|
||||||
display,
|
|
||||||
0,
|
|
||||||
relativeVisualColForHighlight - segStart,
|
|
||||||
) +
|
|
||||||
highlighted +
|
|
||||||
cpSlice(
|
|
||||||
display,
|
|
||||||
relativeVisualColForHighlight - segStart + 1,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
charCount = segEnd;
|
|
||||||
} else {
|
|
||||||
// Advance the running counter even when not on cursor line
|
|
||||||
charCount += segLen;
|
|
||||||
}
|
}
|
||||||
|
width={inputWidth}
|
||||||
const color =
|
backgroundColor={listBackgroundColor}
|
||||||
seg.type === 'command' ||
|
containerHeight={Math.min(
|
||||||
seg.type === 'file' ||
|
buffer.viewportHeight,
|
||||||
seg.type === 'paste'
|
scrollableData.length,
|
||||||
? theme.text.accent
|
)}
|
||||||
: theme.text.primary;
|
/>
|
||||||
|
) : (
|
||||||
renderedLine.push(
|
scrollableData
|
||||||
<Text key={`token-${segIdx}`} color={color}>
|
.slice(
|
||||||
{display}
|
buffer.visualScrollRow,
|
||||||
</Text>,
|
buffer.visualScrollRow + buffer.viewportHeight,
|
||||||
);
|
)
|
||||||
});
|
.map((item, index) => {
|
||||||
|
const actualIndex = buffer.visualScrollRow + index;
|
||||||
const currentLineGhost = isOnCursorLine ? inlineGhost : '';
|
const key =
|
||||||
if (
|
item.type === 'visualLine'
|
||||||
isOnCursorLine &&
|
? `line-${item.absoluteVisualIdx}`
|
||||||
cursorVisualColAbsolute === cpLen(lineText)
|
: `ghost-${item.index}`;
|
||||||
) {
|
return (
|
||||||
if (!currentLineGhost) {
|
<Fragment key={key}>
|
||||||
renderedLine.push(
|
{renderItem({ item, index: actualIndex })}
|
||||||
<Text key={`cursor-end-${cursorVisualColAbsolute}`}>
|
</Fragment>
|
||||||
{showCursor ? chalk.inverse(' ') : ' '}
|
|
||||||
</Text>,
|
|
||||||
);
|
);
|
||||||
}
|
})
|
||||||
}
|
)}
|
||||||
|
</Box>
|
||||||
const showCursorBeforeGhost =
|
|
||||||
focus &&
|
|
||||||
isOnCursorLine &&
|
|
||||||
cursorVisualColAbsolute === cpLen(lineText) &&
|
|
||||||
currentLineGhost;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box key={`line-${visualIdxInRenderedSet}`} height={1}>
|
|
||||||
<Text
|
|
||||||
terminalCursorFocus={showCursor && isOnCursorLine}
|
|
||||||
terminalCursorPosition={cpIndexToOffset(
|
|
||||||
lineText,
|
|
||||||
cursorVisualColAbsolute,
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{renderedLine}
|
|
||||||
{showCursorBeforeGhost &&
|
|
||||||
(showCursor ? chalk.inverse(' ') : ' ')}
|
|
||||||
{currentLineGhost && (
|
|
||||||
<Text color={theme.text.secondary}>
|
|
||||||
{currentLineGhost}
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.concat(
|
|
||||||
additionalLines.map((ghostLine, index) => {
|
|
||||||
const padding = Math.max(
|
|
||||||
0,
|
|
||||||
inputWidth - stringWidth(ghostLine),
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<Text
|
|
||||||
key={`ghost-line-${index}`}
|
|
||||||
color={theme.text.secondary}
|
|
||||||
>
|
|
||||||
{ghostLine}
|
|
||||||
{' '.repeat(padding)}
|
|
||||||
</Text>
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -336,6 +336,10 @@ export const MainContent = () => {
|
|||||||
isAlternateBuffer,
|
isAlternateBuffer,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if (!uiState.isConfigInitialized) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (isAlternateBufferOrTerminalBuffer) {
|
if (isAlternateBufferOrTerminalBuffer) {
|
||||||
return scrollableList;
|
return scrollableList;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { describe, it, expect, vi } from 'vitest';
|
|||||||
import { StatsDisplay } from './StatsDisplay.js';
|
import { StatsDisplay } from './StatsDisplay.js';
|
||||||
import * as SessionContext from '../contexts/SessionContext.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';
|
import { ToolCallDecision, LlmRole } from '@google/gemini-cli-core';
|
||||||
|
|
||||||
// Mock the context to provide controlled data for testing
|
// Mock the context to provide controlled data for testing
|
||||||
vi.mock('../contexts/SessionContext.js', async (importOriginal) => {
|
vi.mock('../contexts/SessionContext.js', async (importOriginal) => {
|
||||||
@@ -131,6 +131,66 @@ describe('<StatsDisplay />', () => {
|
|||||||
expect(output).toMatchSnapshot();
|
expect(output).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('renders role breakdown correctly under models', async () => {
|
||||||
|
const metrics = createTestMetrics({
|
||||||
|
models: {
|
||||||
|
'gemini-2.5-flash': {
|
||||||
|
api: { totalRequests: 10, totalErrors: 0, totalLatencyMs: 10000 },
|
||||||
|
tokens: {
|
||||||
|
input: 1000,
|
||||||
|
prompt: 1200,
|
||||||
|
candidates: 2000,
|
||||||
|
total: 3200,
|
||||||
|
cached: 200,
|
||||||
|
thoughts: 0,
|
||||||
|
tool: 0,
|
||||||
|
},
|
||||||
|
roles: {
|
||||||
|
[LlmRole.MAIN]: {
|
||||||
|
totalRequests: 7,
|
||||||
|
totalErrors: 0,
|
||||||
|
totalLatencyMs: 7000,
|
||||||
|
tokens: {
|
||||||
|
input: 800,
|
||||||
|
prompt: 900,
|
||||||
|
candidates: 1500,
|
||||||
|
total: 2400,
|
||||||
|
cached: 100,
|
||||||
|
thoughts: 0,
|
||||||
|
tool: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[LlmRole.UTILITY_TOOL]: {
|
||||||
|
totalRequests: 3,
|
||||||
|
totalErrors: 0,
|
||||||
|
totalLatencyMs: 3000,
|
||||||
|
tokens: {
|
||||||
|
input: 200,
|
||||||
|
prompt: 300,
|
||||||
|
candidates: 500,
|
||||||
|
total: 800,
|
||||||
|
cached: 100,
|
||||||
|
thoughts: 0,
|
||||||
|
tool: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { lastFrame } = await renderWithMockedStats(metrics);
|
||||||
|
const output = lastFrame();
|
||||||
|
|
||||||
|
expect(output).toContain('gemini-2.5-flash');
|
||||||
|
expect(output).toContain('10'); // Total requests
|
||||||
|
expect(output).toContain('↳ main');
|
||||||
|
expect(output).toContain('7'); // main requests
|
||||||
|
expect(output).toContain('↳ utility_tool');
|
||||||
|
expect(output).toContain('3'); // tool requests
|
||||||
|
expect(output).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
it('renders all sections when all data is present', async () => {
|
it('renders all sections when all data is present', async () => {
|
||||||
const metrics = createTestMetrics({
|
const metrics = createTestMetrics({
|
||||||
models: {
|
models: {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { formatDuration } from '../utils/formatters.js';
|
|||||||
import {
|
import {
|
||||||
useSessionStats,
|
useSessionStats,
|
||||||
type ModelMetrics,
|
type ModelMetrics,
|
||||||
|
type RoleMetrics,
|
||||||
} from '../contexts/SessionContext.js';
|
} from '../contexts/SessionContext.js';
|
||||||
import {
|
import {
|
||||||
getStatusColor,
|
getStatusColor,
|
||||||
@@ -23,6 +24,7 @@ import {
|
|||||||
import { computeSessionStats } from '../utils/computeStats.js';
|
import { computeSessionStats } from '../utils/computeStats.js';
|
||||||
import { useSettings } from '../contexts/SettingsContext.js';
|
import { useSettings } from '../contexts/SettingsContext.js';
|
||||||
import type { QuotaStats } from '../types.js';
|
import type { QuotaStats } from '../types.js';
|
||||||
|
import { LlmRole } from '@google/gemini-cli-core';
|
||||||
|
|
||||||
// A more flexible and powerful StatRow component
|
// A more flexible and powerful StatRow component
|
||||||
interface StatRowProps {
|
interface StatRowProps {
|
||||||
@@ -77,6 +79,16 @@ interface ModelUsageTableProps {
|
|||||||
models: Record<string, ModelMetrics>;
|
models: Record<string, ModelMetrics>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ModelRow {
|
||||||
|
name: string;
|
||||||
|
displayName: string;
|
||||||
|
requests: number | string;
|
||||||
|
cachedTokens: string;
|
||||||
|
inputTokens: string;
|
||||||
|
outputTokens: string;
|
||||||
|
isSubRow: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
const ModelUsageTable: React.FC<ModelUsageTableProps> = ({ models }) => {
|
const ModelUsageTable: React.FC<ModelUsageTableProps> = ({ models }) => {
|
||||||
const nameWidth = 28;
|
const nameWidth = 28;
|
||||||
const requestsWidth = 8;
|
const requestsWidth = 8;
|
||||||
@@ -84,6 +96,46 @@ const ModelUsageTable: React.FC<ModelUsageTableProps> = ({ models }) => {
|
|||||||
const cacheReadsWidth = 14;
|
const cacheReadsWidth = 14;
|
||||||
const outputTokensWidth = 14;
|
const outputTokensWidth = 14;
|
||||||
|
|
||||||
|
const rows: ModelRow[] = [];
|
||||||
|
|
||||||
|
Object.entries(models).forEach(([name, metrics]) => {
|
||||||
|
rows.push({
|
||||||
|
name,
|
||||||
|
displayName: name,
|
||||||
|
requests: metrics.api.totalRequests,
|
||||||
|
cachedTokens: metrics.tokens.cached.toLocaleString(),
|
||||||
|
inputTokens: metrics.tokens.prompt.toLocaleString(),
|
||||||
|
outputTokens: metrics.tokens.candidates.toLocaleString(),
|
||||||
|
isSubRow: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (metrics.roles) {
|
||||||
|
const roleEntries = Object.entries(metrics.roles).filter(
|
||||||
|
(entry): entry is [string, RoleMetrics] =>
|
||||||
|
entry[1] !== undefined && entry[1].totalRequests > 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
roleEntries.sort(([a], [b]) => {
|
||||||
|
if (a === b) return 0;
|
||||||
|
if (a === LlmRole.MAIN) return -1;
|
||||||
|
if (b === LlmRole.MAIN) return 1;
|
||||||
|
return a.localeCompare(b);
|
||||||
|
});
|
||||||
|
|
||||||
|
roleEntries.forEach(([role, roleMetrics]) => {
|
||||||
|
rows.push({
|
||||||
|
name: `${name}-${role}`,
|
||||||
|
displayName: ` ↳ ${role}`,
|
||||||
|
requests: roleMetrics.totalRequests,
|
||||||
|
cachedTokens: roleMetrics.tokens.cached.toLocaleString(),
|
||||||
|
inputTokens: roleMetrics.tokens.prompt.toLocaleString(),
|
||||||
|
outputTokens: roleMetrics.tokens.candidates.toLocaleString(),
|
||||||
|
isSubRow: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box flexDirection="column" marginTop={1}>
|
<Box flexDirection="column" marginTop={1}>
|
||||||
<Text bold color={theme.text.primary}>
|
<Text bold color={theme.text.primary}>
|
||||||
@@ -131,31 +183,42 @@ const ModelUsageTable: React.FC<ModelUsageTableProps> = ({ models }) => {
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Rows */}
|
{/* Rows */}
|
||||||
{Object.entries(models).map(([name, modelMetrics]) => (
|
{rows.map((row) => (
|
||||||
<Box key={name}>
|
<Box key={row.name}>
|
||||||
<Box width={nameWidth}>
|
<Box width={nameWidth}>
|
||||||
<Text color={theme.text.primary} wrap="truncate-end">
|
<Text
|
||||||
{name}
|
color={row.isSubRow ? theme.text.secondary : theme.text.primary}
|
||||||
|
wrap="truncate-end"
|
||||||
|
>
|
||||||
|
{row.displayName}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Box width={requestsWidth} justifyContent="flex-end">
|
<Box width={requestsWidth} justifyContent="flex-end">
|
||||||
<Text color={theme.text.primary}>
|
<Text
|
||||||
{modelMetrics.api.totalRequests}
|
color={row.isSubRow ? theme.text.secondary : theme.text.primary}
|
||||||
|
>
|
||||||
|
{row.requests}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Box width={inputTokensWidth} justifyContent="flex-end">
|
<Box width={inputTokensWidth} justifyContent="flex-end">
|
||||||
<Text color={theme.text.primary}>
|
<Text
|
||||||
{modelMetrics.tokens.prompt.toLocaleString()}
|
color={row.isSubRow ? theme.text.secondary : theme.text.primary}
|
||||||
|
>
|
||||||
|
{row.inputTokens}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Box width={cacheReadsWidth} justifyContent="flex-end">
|
<Box width={cacheReadsWidth} justifyContent="flex-end">
|
||||||
<Text color={theme.text.primary}>
|
<Text
|
||||||
{modelMetrics.tokens.cached.toLocaleString()}
|
color={row.isSubRow ? theme.text.secondary : theme.text.primary}
|
||||||
|
>
|
||||||
|
{row.cachedTokens}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Box width={outputTokensWidth} justifyContent="flex-end">
|
<Box width={outputTokensWidth} justifyContent="flex-end">
|
||||||
<Text color={theme.text.primary}>
|
<Text
|
||||||
{modelMetrics.tokens.candidates.toLocaleString()}
|
color={row.isSubRow ? theme.text.secondary : theme.text.primary}
|
||||||
|
>
|
||||||
|
{row.outputTokens}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { StatusRow } from './StatusRow.js';
|
|||||||
import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest';
|
import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest';
|
||||||
import { useComposerStatus } from '../hooks/useComposerStatus.js';
|
import { useComposerStatus } from '../hooks/useComposerStatus.js';
|
||||||
import { type UIState } from '../contexts/UIStateContext.js';
|
import { type UIState } from '../contexts/UIStateContext.js';
|
||||||
import { type TextBuffer } from '../components/shared/text-buffer.js';
|
|
||||||
import { type SessionStatsState } from '../contexts/SessionContext.js';
|
import { type SessionStatsState } from '../contexts/SessionContext.js';
|
||||||
import { type ThoughtSummary } from '../types.js';
|
import { type ThoughtSummary } from '../types.js';
|
||||||
import { ApprovalMode } from '@google/gemini-cli-core';
|
import { ApprovalMode } from '@google/gemini-cli-core';
|
||||||
@@ -29,13 +29,11 @@ describe('<StatusRow />', () => {
|
|||||||
elapsedTime: 0,
|
elapsedTime: 0,
|
||||||
currentWittyPhrase: undefined,
|
currentWittyPhrase: undefined,
|
||||||
activeHooks: [],
|
activeHooks: [],
|
||||||
buffer: { text: '' } as unknown as TextBuffer,
|
|
||||||
sessionStats: { lastPromptTokenCount: 0 } as unknown as SessionStatsState,
|
sessionStats: { lastPromptTokenCount: 0 } as unknown as SessionStatsState,
|
||||||
shortcutsHelpVisible: false,
|
shortcutsHelpVisible: false,
|
||||||
contextFileNames: [],
|
contextFileNames: [],
|
||||||
showApprovalModeIndicator: ApprovalMode.DEFAULT,
|
showApprovalModeIndicator: ApprovalMode.DEFAULT,
|
||||||
allowPlanMode: false,
|
allowPlanMode: false,
|
||||||
shellModeActive: false,
|
|
||||||
renderMarkdown: true,
|
renderMarkdown: true,
|
||||||
currentModel: 'gemini-3',
|
currentModel: 'gemini-3',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -153,6 +153,8 @@ export const StatusNode: React.FC<{
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
import { useInputState } from '../contexts/InputContext.js';
|
||||||
|
|
||||||
export const StatusRow: React.FC<StatusRowProps> = ({
|
export const StatusRow: React.FC<StatusRowProps> = ({
|
||||||
showUiDetails,
|
showUiDetails,
|
||||||
isNarrow,
|
isNarrow,
|
||||||
@@ -162,6 +164,7 @@ export const StatusRow: React.FC<StatusRowProps> = ({
|
|||||||
hasPendingActionRequired,
|
hasPendingActionRequired,
|
||||||
}) => {
|
}) => {
|
||||||
const uiState = useUIState();
|
const uiState = useUIState();
|
||||||
|
const inputState = useInputState();
|
||||||
const settings = useSettings();
|
const settings = useSettings();
|
||||||
const {
|
const {
|
||||||
isInteractiveShellWaiting,
|
isInteractiveShellWaiting,
|
||||||
@@ -225,7 +228,7 @@ export const StatusRow: React.FC<StatusRowProps> = ({
|
|||||||
settings.merged.ui.showShortcutsHint &&
|
settings.merged.ui.showShortcutsHint &&
|
||||||
!hideUiDetailsForSuggestions &&
|
!hideUiDetailsForSuggestions &&
|
||||||
!hasPendingActionRequired &&
|
!hasPendingActionRequired &&
|
||||||
uiState.buffer.text.length === 0
|
inputState.buffer.text.length === 0
|
||||||
) {
|
) {
|
||||||
return showUiDetails ? '? for shortcuts' : 'press tab twice for more';
|
return showUiDetails ? '? for shortcuts' : 'press tab twice for more';
|
||||||
}
|
}
|
||||||
@@ -328,7 +331,7 @@ export const StatusRow: React.FC<StatusRowProps> = ({
|
|||||||
) : isInteractiveShellWaiting ? (
|
) : isInteractiveShellWaiting ? (
|
||||||
<Box width="100%" marginLeft={LAYOUT.INDICATOR_LEFT_MARGIN}>
|
<Box width="100%" marginLeft={LAYOUT.INDICATOR_LEFT_MARGIN}>
|
||||||
<Text color={theme.status.warning}>
|
<Text color={theme.status.warning}>
|
||||||
! Shell awaiting input (Tab to focus)
|
{INTERACTIVE_SHELL_WAITING_PHRASE}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
@@ -391,13 +394,14 @@ export const StatusRow: React.FC<StatusRowProps> = ({
|
|||||||
>
|
>
|
||||||
{showUiDetails ? (
|
{showUiDetails ? (
|
||||||
<>
|
<>
|
||||||
{!hideUiDetailsForSuggestions && !uiState.shellModeActive && (
|
{!hideUiDetailsForSuggestions &&
|
||||||
<ApprovalModeIndicator
|
!inputState.shellModeActive && (
|
||||||
approvalMode={uiState.showApprovalModeIndicator}
|
<ApprovalModeIndicator
|
||||||
allowPlanMode={uiState.allowPlanMode}
|
approvalMode={uiState.showApprovalModeIndicator}
|
||||||
/>
|
allowPlanMode={uiState.allowPlanMode}
|
||||||
)}
|
/>
|
||||||
{uiState.shellModeActive && (
|
)}
|
||||||
|
{inputState.shellModeActive && (
|
||||||
<Box marginLeft={LAYOUT.INDICATOR_LEFT_MARGIN}>
|
<Box marginLeft={LAYOUT.INDICATOR_LEFT_MARGIN}>
|
||||||
<ShellModeIndicator />
|
<ShellModeIndicator />
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -9,16 +9,24 @@ import { renderWithProviders } from '../../test-utils/render.js';
|
|||||||
import { ToastDisplay, shouldShowToast } from './ToastDisplay.js';
|
import { ToastDisplay, shouldShowToast } from './ToastDisplay.js';
|
||||||
import { TransientMessageType } from '../../utils/events.js';
|
import { TransientMessageType } from '../../utils/events.js';
|
||||||
import { type UIState } from '../contexts/UIStateContext.js';
|
import { type UIState } from '../contexts/UIStateContext.js';
|
||||||
|
import { type InputState } from '../contexts/InputContext.js';
|
||||||
import { type TextBuffer } from './shared/text-buffer.js';
|
import { type TextBuffer } from './shared/text-buffer.js';
|
||||||
import { type HistoryItem } from '../types.js';
|
import { type HistoryItem } from '../types.js';
|
||||||
|
|
||||||
const renderToastDisplay = async (uiState: Partial<UIState> = {}) =>
|
const renderToastDisplay = async (
|
||||||
|
uiState: Partial<UIState> = {},
|
||||||
|
inputState: Partial<InputState> = {},
|
||||||
|
) =>
|
||||||
renderWithProviders(<ToastDisplay />, {
|
renderWithProviders(<ToastDisplay />, {
|
||||||
uiState: {
|
uiState: {
|
||||||
buffer: { text: '' } as TextBuffer,
|
|
||||||
history: [] as HistoryItem[],
|
history: [] as HistoryItem[],
|
||||||
...uiState,
|
...uiState,
|
||||||
},
|
},
|
||||||
|
inputState: {
|
||||||
|
buffer: { text: '' } as TextBuffer,
|
||||||
|
showEscapePrompt: false,
|
||||||
|
...inputState,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('ToastDisplay', () => {
|
describe('ToastDisplay', () => {
|
||||||
@@ -27,86 +35,121 @@ describe('ToastDisplay', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('shouldShowToast', () => {
|
describe('shouldShowToast', () => {
|
||||||
const baseState: Partial<UIState> = {
|
const baseUIState: Partial<UIState> = {
|
||||||
ctrlCPressedOnce: false,
|
ctrlCPressedOnce: false,
|
||||||
transientMessage: null,
|
transientMessage: null,
|
||||||
ctrlDPressedOnce: false,
|
ctrlDPressedOnce: false,
|
||||||
showEscapePrompt: false,
|
|
||||||
buffer: { text: '' } as TextBuffer,
|
|
||||||
history: [] as HistoryItem[],
|
history: [] as HistoryItem[],
|
||||||
queueErrorMessage: null,
|
queueErrorMessage: null,
|
||||||
showIsExpandableHint: false,
|
showIsExpandableHint: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const baseInputState: Partial<InputState> = {
|
||||||
|
showEscapePrompt: false,
|
||||||
|
buffer: { text: '' } as TextBuffer,
|
||||||
|
};
|
||||||
|
|
||||||
it('returns false for default state', () => {
|
it('returns false for default state', () => {
|
||||||
expect(shouldShowToast(baseState as UIState)).toBe(false);
|
expect(
|
||||||
|
shouldShowToast(baseUIState as UIState, baseInputState as InputState),
|
||||||
|
).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns true when showIsExpandableHint is true', () => {
|
it('returns true when showIsExpandableHint is true', () => {
|
||||||
expect(
|
expect(
|
||||||
shouldShowToast({
|
shouldShowToast(
|
||||||
...baseState,
|
{
|
||||||
showIsExpandableHint: true,
|
...baseUIState,
|
||||||
} as UIState),
|
showIsExpandableHint: true,
|
||||||
|
} as UIState,
|
||||||
|
baseInputState as InputState,
|
||||||
|
),
|
||||||
).toBe(true);
|
).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns true when ctrlCPressedOnce is true', () => {
|
it('returns true when ctrlCPressedOnce is true', () => {
|
||||||
expect(
|
expect(
|
||||||
shouldShowToast({ ...baseState, ctrlCPressedOnce: true } as UIState),
|
shouldShowToast(
|
||||||
|
{ ...baseUIState, ctrlCPressedOnce: true } as UIState,
|
||||||
|
baseInputState as InputState,
|
||||||
|
),
|
||||||
).toBe(true);
|
).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns true when transientMessage is present', () => {
|
it('returns true when transientMessage is present', () => {
|
||||||
expect(
|
expect(
|
||||||
shouldShowToast({
|
shouldShowToast(
|
||||||
...baseState,
|
{
|
||||||
transientMessage: { text: 'test', type: TransientMessageType.Hint },
|
...baseUIState,
|
||||||
} as UIState),
|
transientMessage: { text: 'test', type: TransientMessageType.Hint },
|
||||||
|
} as UIState,
|
||||||
|
baseInputState as InputState,
|
||||||
|
),
|
||||||
).toBe(true);
|
).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns true when ctrlDPressedOnce is true', () => {
|
it('returns true when ctrlDPressedOnce is true', () => {
|
||||||
expect(
|
expect(
|
||||||
shouldShowToast({ ...baseState, ctrlDPressedOnce: true } as UIState),
|
shouldShowToast(
|
||||||
|
{ ...baseUIState, ctrlDPressedOnce: true } as UIState,
|
||||||
|
baseInputState as InputState,
|
||||||
|
),
|
||||||
).toBe(true);
|
).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns true when showEscapePrompt is true and buffer is NOT empty', () => {
|
it('returns true when showEscapePrompt is true and buffer is NOT empty', () => {
|
||||||
expect(
|
expect(
|
||||||
shouldShowToast({
|
shouldShowToast(
|
||||||
...baseState,
|
{
|
||||||
showEscapePrompt: true,
|
...baseUIState,
|
||||||
buffer: { text: 'some text' } as TextBuffer,
|
} as UIState,
|
||||||
} as UIState),
|
{
|
||||||
|
...baseInputState,
|
||||||
|
showEscapePrompt: true,
|
||||||
|
buffer: { text: 'some text' } as TextBuffer,
|
||||||
|
} as InputState,
|
||||||
|
),
|
||||||
).toBe(true);
|
).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns true when showEscapePrompt is true and history is NOT empty', () => {
|
it('returns true when showEscapePrompt is true and history is NOT empty', () => {
|
||||||
expect(
|
expect(
|
||||||
shouldShowToast({
|
shouldShowToast(
|
||||||
...baseState,
|
{
|
||||||
showEscapePrompt: true,
|
...baseUIState,
|
||||||
history: [{ id: '1' } as unknown as HistoryItem],
|
history: [{ id: '1' } as unknown as HistoryItem],
|
||||||
} as UIState),
|
} as UIState,
|
||||||
|
{
|
||||||
|
...baseInputState,
|
||||||
|
showEscapePrompt: true,
|
||||||
|
} as InputState,
|
||||||
|
),
|
||||||
).toBe(true);
|
).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns false when showEscapePrompt is true but buffer and history are empty', () => {
|
it('returns false when showEscapePrompt is true but buffer and history are empty', () => {
|
||||||
expect(
|
expect(
|
||||||
shouldShowToast({
|
shouldShowToast(
|
||||||
...baseState,
|
{
|
||||||
showEscapePrompt: true,
|
...baseUIState,
|
||||||
} as UIState),
|
} as UIState,
|
||||||
|
{
|
||||||
|
...baseInputState,
|
||||||
|
showEscapePrompt: true,
|
||||||
|
} as InputState,
|
||||||
|
),
|
||||||
).toBe(false);
|
).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns true when queueErrorMessage is present', () => {
|
it('returns true when queueErrorMessage is present', () => {
|
||||||
expect(
|
expect(
|
||||||
shouldShowToast({
|
shouldShowToast(
|
||||||
...baseState,
|
{
|
||||||
queueErrorMessage: 'error',
|
...baseUIState,
|
||||||
} as UIState),
|
queueErrorMessage: 'error',
|
||||||
|
} as UIState,
|
||||||
|
baseInputState as InputState,
|
||||||
|
),
|
||||||
).toBe(true);
|
).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -151,18 +194,25 @@ describe('ToastDisplay', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('renders Escape prompt when buffer is empty', async () => {
|
it('renders Escape prompt when buffer is empty', async () => {
|
||||||
const { lastFrame } = await renderToastDisplay({
|
const { lastFrame } = await renderToastDisplay(
|
||||||
showEscapePrompt: true,
|
{
|
||||||
history: [{ id: 1, type: 'user', text: 'test' }] as HistoryItem[],
|
history: [{ id: 1, type: 'user', text: 'test' }] as HistoryItem[],
|
||||||
});
|
},
|
||||||
|
{
|
||||||
|
showEscapePrompt: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
expect(lastFrame()).toMatchSnapshot();
|
expect(lastFrame()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders Escape prompt when buffer is NOT empty', async () => {
|
it('renders Escape prompt when buffer is NOT empty', async () => {
|
||||||
const { lastFrame } = await renderToastDisplay({
|
const { lastFrame } = await renderToastDisplay(
|
||||||
showEscapePrompt: true,
|
{},
|
||||||
buffer: { text: 'some text' } as TextBuffer,
|
{
|
||||||
});
|
showEscapePrompt: true,
|
||||||
|
buffer: { text: 'some text' } as TextBuffer,
|
||||||
|
},
|
||||||
|
);
|
||||||
expect(lastFrame()).toMatchSnapshot();
|
expect(lastFrame()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -8,15 +8,19 @@ import type React from 'react';
|
|||||||
import { Text } from 'ink';
|
import { Text } from 'ink';
|
||||||
import { theme } from '../semantic-colors.js';
|
import { theme } from '../semantic-colors.js';
|
||||||
import { useUIState, type UIState } from '../contexts/UIStateContext.js';
|
import { useUIState, type UIState } from '../contexts/UIStateContext.js';
|
||||||
|
import { useInputState, type InputState } from '../contexts/InputContext.js';
|
||||||
import { TransientMessageType } from '../../utils/events.js';
|
import { TransientMessageType } from '../../utils/events.js';
|
||||||
|
|
||||||
export function shouldShowToast(uiState: UIState): boolean {
|
export function shouldShowToast(
|
||||||
|
uiState: UIState,
|
||||||
|
inputState: InputState,
|
||||||
|
): boolean {
|
||||||
return (
|
return (
|
||||||
uiState.ctrlCPressedOnce ||
|
uiState.ctrlCPressedOnce ||
|
||||||
Boolean(uiState.transientMessage) ||
|
Boolean(uiState.transientMessage) ||
|
||||||
uiState.ctrlDPressedOnce ||
|
uiState.ctrlDPressedOnce ||
|
||||||
(uiState.showEscapePrompt &&
|
(inputState.showEscapePrompt &&
|
||||||
(uiState.buffer.text.length > 0 || uiState.history.length > 0)) ||
|
(inputState.buffer.text.length > 0 || uiState.history.length > 0)) ||
|
||||||
Boolean(uiState.queueErrorMessage) ||
|
Boolean(uiState.queueErrorMessage) ||
|
||||||
uiState.showIsExpandableHint
|
uiState.showIsExpandableHint
|
||||||
);
|
);
|
||||||
@@ -24,6 +28,7 @@ export function shouldShowToast(uiState: UIState): boolean {
|
|||||||
|
|
||||||
export const ToastDisplay: React.FC = () => {
|
export const ToastDisplay: React.FC = () => {
|
||||||
const uiState = useUIState();
|
const uiState = useUIState();
|
||||||
|
const inputState = useInputState();
|
||||||
|
|
||||||
if (uiState.ctrlCPressedOnce) {
|
if (uiState.ctrlCPressedOnce) {
|
||||||
return (
|
return (
|
||||||
@@ -46,8 +51,8 @@ export const ToastDisplay: React.FC = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uiState.showEscapePrompt) {
|
if (inputState.showEscapePrompt) {
|
||||||
const isPromptEmpty = uiState.buffer.text.length === 0;
|
const isPromptEmpty = inputState.buffer.text.length === 0;
|
||||||
const hasHistory = uiState.history.length > 0;
|
const hasHistory = uiState.history.length > 0;
|
||||||
|
|
||||||
if (isPromptEmpty && !hasHistory) {
|
if (isPromptEmpty && !hasHistory) {
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ describe('ToolConfirmationQueue', () => {
|
|||||||
const confirmingTool = {
|
const confirmingTool = {
|
||||||
tool: {
|
tool: {
|
||||||
callId: 'call-1',
|
callId: 'call-1',
|
||||||
name: 'ls',
|
name: 'run_shell_command',
|
||||||
description: 'list files',
|
description: 'list files',
|
||||||
status: CoreToolCallStatus.AwaitingApproval,
|
status: CoreToolCallStatus.AwaitingApproval,
|
||||||
confirmationDetails: {
|
confirmationDetails: {
|
||||||
@@ -98,15 +98,12 @@ describe('ToolConfirmationQueue', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const output = lastFrame();
|
const output = lastFrame();
|
||||||
expect(output).toContain('Action Required');
|
|
||||||
expect(output).toContain('1 of 3');
|
expect(output).toContain('1 of 3');
|
||||||
expect(output).toContain('ls'); // Tool name
|
expect(output).toContain('ls'); // Tool name
|
||||||
expect(output).toContain('list files'); // Tool description
|
expect(output).toContain('list files'); // Tool description
|
||||||
expect(output).toContain("Allow execution of: 'ls'?");
|
expect(output).toContain('Allow execution of [ls]?');
|
||||||
expect(output).toMatchSnapshot();
|
expect(output).toMatchSnapshot();
|
||||||
|
|
||||||
const stickyHeaderProps = vi.mocked(StickyHeader).mock.calls[0][0];
|
|
||||||
expect(stickyHeaderProps.borderColor).toBe(theme.status.warning);
|
|
||||||
unmount();
|
unmount();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -183,7 +180,7 @@ describe('ToolConfirmationQueue', () => {
|
|||||||
// availableContentHeight = Math.max(9 - 6, 4) = 4
|
// availableContentHeight = Math.max(9 - 6, 4) = 4
|
||||||
// MaxSizedBox in ToolConfirmationMessage will use 4
|
// MaxSizedBox in ToolConfirmationMessage will use 4
|
||||||
// It should show truncation message
|
// It should show truncation message
|
||||||
await waitFor(() => expect(lastFrame()).toContain('49 hidden (Ctrl+O)'));
|
await waitFor(() => expect(lastFrame()).toContain('48 hidden (Ctrl+O)'));
|
||||||
expect(lastFrame()).toMatchSnapshot();
|
expect(lastFrame()).toMatchSnapshot();
|
||||||
unmount();
|
unmount();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,7 +9,11 @@ import { Box, Text } from 'ink';
|
|||||||
import { theme } from '../semantic-colors.js';
|
import { theme } from '../semantic-colors.js';
|
||||||
import { useConfig } from '../contexts/ConfigContext.js';
|
import { useConfig } from '../contexts/ConfigContext.js';
|
||||||
import { ToolConfirmationMessage } from './messages/ToolConfirmationMessage.js';
|
import { ToolConfirmationMessage } from './messages/ToolConfirmationMessage.js';
|
||||||
import { ToolStatusIndicator, ToolInfo } from './messages/ToolShared.js';
|
import {
|
||||||
|
isShellTool,
|
||||||
|
ToolStatusIndicator,
|
||||||
|
ToolInfo,
|
||||||
|
} from './messages/ToolShared.js';
|
||||||
import { useUIState } from '../contexts/UIStateContext.js';
|
import { useUIState } from '../contexts/UIStateContext.js';
|
||||||
import type { ConfirmingToolState } from '../hooks/useConfirmingTool.js';
|
import type { ConfirmingToolState } from '../hooks/useConfirmingTool.js';
|
||||||
import { StickyHeader } from './StickyHeader.js';
|
import { StickyHeader } from './StickyHeader.js';
|
||||||
@@ -31,6 +35,16 @@ function getConfirmationHeader(
|
|||||||
return headers[details.type] ?? 'Action Required';
|
return headers[details.type] ?? 'Action Required';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getConfirmationLabel(
|
||||||
|
toolName: string,
|
||||||
|
details: SerializableConfirmationDetails | undefined,
|
||||||
|
): string {
|
||||||
|
if (details?.type === 'ask_user') return 'Questions';
|
||||||
|
if (details?.type === 'exit_plan_mode') return 'Implementation';
|
||||||
|
if (isShellTool(toolName)) return 'Shell';
|
||||||
|
return toolName;
|
||||||
|
}
|
||||||
|
|
||||||
interface ToolConfirmationQueueProps {
|
interface ToolConfirmationQueueProps {
|
||||||
confirmingTool: ConfirmingToolState;
|
confirmingTool: ConfirmingToolState;
|
||||||
}
|
}
|
||||||
@@ -58,22 +72,78 @@ export const ToolConfirmationQueue: React.FC<ToolConfirmationQueueProps> = ({
|
|||||||
? Math.max(uiAvailableHeight, 4)
|
? Math.max(uiAvailableHeight, 4)
|
||||||
: Math.floor(terminalHeight * 0.5);
|
: Math.floor(terminalHeight * 0.5);
|
||||||
|
|
||||||
|
const isShell = isShellTool(tool.name);
|
||||||
|
const isEdit = tool.confirmationDetails?.type === 'edit';
|
||||||
|
|
||||||
|
if (isShell || isEdit) {
|
||||||
|
// Use the new simplified layout for Shell and Edit tools
|
||||||
|
const borderColor = theme.border.default;
|
||||||
|
const availableContentHeight = constrainHeight
|
||||||
|
? Math.max(maxHeight - 3, 4)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const toolLabel = getConfirmationLabel(tool.name, tool.confirmationDetails);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
flexDirection="column"
|
||||||
|
width={mainAreaWidth}
|
||||||
|
flexShrink={0}
|
||||||
|
borderStyle="round"
|
||||||
|
borderColor={borderColor}
|
||||||
|
paddingX={1}
|
||||||
|
>
|
||||||
|
{/* Header Line */}
|
||||||
|
<Box justifyContent="space-between" marginBottom={0}>
|
||||||
|
<Box flexDirection="row" flexShrink={1} overflow="hidden">
|
||||||
|
<Text color={theme.status.warning} bold>
|
||||||
|
? {toolLabel}
|
||||||
|
{!isEdit && !!tool.description && ' '}
|
||||||
|
</Text>
|
||||||
|
{!isEdit && !!tool.description && (
|
||||||
|
<Box flexShrink={1} overflow="hidden">
|
||||||
|
<Text color={theme.text.primary} wrap="truncate-end">
|
||||||
|
{tool.description}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
{total > 1 && (
|
||||||
|
<Text color={theme.text.secondary}>
|
||||||
|
{index} of {total}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Interactive Area */}
|
||||||
|
<Box flexDirection="column">
|
||||||
|
<ToolConfirmationMessage
|
||||||
|
callId={tool.callId}
|
||||||
|
confirmationDetails={tool.confirmationDetails}
|
||||||
|
config={config}
|
||||||
|
getPreferredEditor={getPreferredEditor}
|
||||||
|
terminalWidth={mainAreaWidth - 4} // Adjust for parent border/padding
|
||||||
|
availableTerminalHeight={availableContentHeight}
|
||||||
|
toolName={tool.name}
|
||||||
|
isFocused={true}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore original logic for other tools
|
||||||
const isRoutine =
|
const isRoutine =
|
||||||
tool.confirmationDetails?.type === 'ask_user' ||
|
tool.confirmationDetails?.type === 'ask_user' ||
|
||||||
tool.confirmationDetails?.type === 'exit_plan_mode';
|
tool.confirmationDetails?.type === 'exit_plan_mode';
|
||||||
const borderColor = isRoutine ? theme.status.success : theme.status.warning;
|
const borderColor = isRoutine ? theme.status.success : theme.status.warning;
|
||||||
const hideToolIdentity = isRoutine;
|
const hideToolIdentity = isRoutine;
|
||||||
|
|
||||||
// ToolConfirmationMessage needs to know the height available for its OWN content.
|
|
||||||
// We subtract the lines used by the Queue wrapper:
|
|
||||||
// - 2 lines for the rounded border
|
|
||||||
// - 2 lines for the Header (text + margin)
|
|
||||||
// - 2 lines for Tool Identity (text + margin)
|
|
||||||
const availableContentHeight = constrainHeight
|
const availableContentHeight = constrainHeight
|
||||||
? Math.max(maxHeight - (hideToolIdentity ? 4 : 6), 4)
|
? Math.max(maxHeight - (hideToolIdentity ? 4 : 6), 4)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const content = (
|
return (
|
||||||
<Box flexDirection="column" width={mainAreaWidth} flexShrink={0}>
|
<Box flexDirection="column" width={mainAreaWidth} flexShrink={0}>
|
||||||
<StickyHeader
|
<StickyHeader
|
||||||
width={mainAreaWidth}
|
width={mainAreaWidth}
|
||||||
@@ -122,11 +192,6 @@ export const ToolConfirmationQueue: React.FC<ToolConfirmationQueueProps> = ({
|
|||||||
paddingX={1}
|
paddingX={1}
|
||||||
flexDirection="column"
|
flexDirection="column"
|
||||||
>
|
>
|
||||||
{/* Interactive Area */}
|
|
||||||
{/*
|
|
||||||
Note: We force isFocused={true} because if this component is rendered,
|
|
||||||
it effectively acts as a modal over the shell/composer.
|
|
||||||
*/}
|
|
||||||
<ToolConfirmationMessage
|
<ToolConfirmationMessage
|
||||||
callId={tool.callId}
|
callId={tool.callId}
|
||||||
confirmationDetails={tool.confirmationDetails}
|
confirmationDetails={tool.confirmationDetails}
|
||||||
@@ -134,6 +199,7 @@ export const ToolConfirmationQueue: React.FC<ToolConfirmationQueueProps> = ({
|
|||||||
getPreferredEditor={getPreferredEditor}
|
getPreferredEditor={getPreferredEditor}
|
||||||
terminalWidth={mainAreaWidth - 4} // Adjust for parent border/padding
|
terminalWidth={mainAreaWidth - 4} // Adjust for parent border/padding
|
||||||
availableTerminalHeight={availableContentHeight}
|
availableTerminalHeight={availableContentHeight}
|
||||||
|
toolName={tool.name}
|
||||||
isFocused={true}
|
isFocused={true}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -149,6 +215,4 @@ export const ToolConfirmationQueue: React.FC<ToolConfirmationQueueProps> = ({
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
||||||
return content;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
exports[`<ContextSummaryDisplay /> > should not render empty parts 1`] = `
|
exports[`<ContextSummaryDisplay /> > should not render empty parts 1`] = `
|
||||||
" 1 open file (ctrl+g to view)
|
" 1 open file (F4 to view)
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`<ContextSummaryDisplay /> > should render on a single line on a wide screen 1`] = `
|
exports[`<ContextSummaryDisplay /> > should render on a single line on a wide screen 1`] = `
|
||||||
" 1 open file (ctrl+g to view) · 1 GEMINI.md file · 1 MCP server · 1 skill
|
" 1 open file (F4 to view) · 1 GEMINI.md file · 1 MCP server · 1 skill
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`<ContextSummaryDisplay /> > should render on multiple lines on a narrow screen 1`] = `
|
exports[`<ContextSummaryDisplay /> > should render on multiple lines on a narrow screen 1`] = `
|
||||||
" 1 open file (ctrl+g to view) · 1 GEMINI.md file · 1 MCP server · 1 skill
|
" 1 open file (F4 to view) · 1 GEMINI.md file · 1 MCP server · 1 skill
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ Files to Modify
|
|||||||
Approves plan but requires confirmation for each tool
|
Approves plan but requires confirmation for each tool
|
||||||
3. Type your feedback...
|
3. Type your feedback...
|
||||||
|
|
||||||
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
|
Enter to select · ↑/↓ to navigate · Ctrl+G to edit plan · Esc to cancel
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ Files to Modify
|
|||||||
Approves plan but requires confirmation for each tool
|
Approves plan but requires confirmation for each tool
|
||||||
3. Type your feedback...
|
3. Type your feedback...
|
||||||
|
|
||||||
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
|
Enter to select · ↑/↓ to navigate · Ctrl+G to edit plan · Esc to cancel
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@ Implementation Steps
|
|||||||
Approves plan but requires confirmation for each tool
|
Approves plan but requires confirmation for each tool
|
||||||
3. Type your feedback...
|
3. Type your feedback...
|
||||||
|
|
||||||
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
|
Enter to select · ↑/↓ to navigate · Ctrl+G to edit plan · Esc to cancel
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -109,7 +109,7 @@ Files to Modify
|
|||||||
Approves plan but requires confirmation for each tool
|
Approves plan but requires confirmation for each tool
|
||||||
3. Type your feedback...
|
3. Type your feedback...
|
||||||
|
|
||||||
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
|
Enter to select · ↑/↓ to navigate · Ctrl+G to edit plan · Esc to cancel
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -136,7 +136,7 @@ Files to Modify
|
|||||||
Approves plan but requires confirmation for each tool
|
Approves plan but requires confirmation for each tool
|
||||||
3. Type your feedback...
|
3. Type your feedback...
|
||||||
|
|
||||||
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
|
Enter to select · ↑/↓ to navigate · Ctrl+G to edit plan · Esc to cancel
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -163,7 +163,7 @@ Files to Modify
|
|||||||
Approves plan but requires confirmation for each tool
|
Approves plan but requires confirmation for each tool
|
||||||
3. Type your feedback...
|
3. Type your feedback...
|
||||||
|
|
||||||
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
|
Enter to select · ↑/↓ to navigate · Ctrl+G to edit plan · Esc to cancel
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -216,7 +216,7 @@ Testing Strategy
|
|||||||
Approves plan but requires confirmation for each tool
|
Approves plan but requires confirmation for each tool
|
||||||
3. Type your feedback...
|
3. Type your feedback...
|
||||||
|
|
||||||
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
|
Enter to select · ↑/↓ to navigate · Ctrl+G to edit plan · Esc to cancel
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -243,6 +243,6 @@ Files to Modify
|
|||||||
Approves plan but requires confirmation for each tool
|
Approves plan but requires confirmation for each tool
|
||||||
3. Type your feedback...
|
3. Type your feedback...
|
||||||
|
|
||||||
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
|
Enter to select · ↑/↓ to navigate · Ctrl+G to edit plan · Esc to cancel
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -112,48 +112,7 @@ exports[`<HistoryItemDisplay /> > gemini items (alternateBuffer=false) > should
|
|||||||
|
|
||||||
exports[`<HistoryItemDisplay /> > gemini items (alternateBuffer=false) > should render a truncated gemini item 1`] = `
|
exports[`<HistoryItemDisplay /> > gemini items (alternateBuffer=false) > should render a truncated gemini item 1`] = `
|
||||||
"✦ Example code block:
|
"✦ Example code block:
|
||||||
1 Line 1
|
... 42 hidden (Ctrl+O) ...
|
||||||
2 Line 2
|
|
||||||
3 Line 3
|
|
||||||
4 Line 4
|
|
||||||
5 Line 5
|
|
||||||
6 Line 6
|
|
||||||
7 Line 7
|
|
||||||
8 Line 8
|
|
||||||
9 Line 9
|
|
||||||
10 Line 10
|
|
||||||
11 Line 11
|
|
||||||
12 Line 12
|
|
||||||
13 Line 13
|
|
||||||
14 Line 14
|
|
||||||
15 Line 15
|
|
||||||
16 Line 16
|
|
||||||
17 Line 17
|
|
||||||
18 Line 18
|
|
||||||
19 Line 19
|
|
||||||
20 Line 20
|
|
||||||
21 Line 21
|
|
||||||
22 Line 22
|
|
||||||
23 Line 23
|
|
||||||
24 Line 24
|
|
||||||
25 Line 25
|
|
||||||
26 Line 26
|
|
||||||
27 Line 27
|
|
||||||
28 Line 28
|
|
||||||
29 Line 29
|
|
||||||
30 Line 30
|
|
||||||
31 Line 31
|
|
||||||
32 Line 32
|
|
||||||
33 Line 33
|
|
||||||
34 Line 34
|
|
||||||
35 Line 35
|
|
||||||
36 Line 36
|
|
||||||
37 Line 37
|
|
||||||
38 Line 38
|
|
||||||
39 Line 39
|
|
||||||
40 Line 40
|
|
||||||
41 Line 41
|
|
||||||
42 Line 42
|
|
||||||
43 Line 43
|
43 Line 43
|
||||||
44 Line 44
|
44 Line 44
|
||||||
45 Line 45
|
45 Line 45
|
||||||
@@ -167,48 +126,7 @@ exports[`<HistoryItemDisplay /> > gemini items (alternateBuffer=false) > should
|
|||||||
|
|
||||||
exports[`<HistoryItemDisplay /> > gemini items (alternateBuffer=false) > should render a truncated gemini_content item 1`] = `
|
exports[`<HistoryItemDisplay /> > gemini items (alternateBuffer=false) > should render a truncated gemini_content item 1`] = `
|
||||||
" Example code block:
|
" Example code block:
|
||||||
1 Line 1
|
... 42 hidden (Ctrl+O) ...
|
||||||
2 Line 2
|
|
||||||
3 Line 3
|
|
||||||
4 Line 4
|
|
||||||
5 Line 5
|
|
||||||
6 Line 6
|
|
||||||
7 Line 7
|
|
||||||
8 Line 8
|
|
||||||
9 Line 9
|
|
||||||
10 Line 10
|
|
||||||
11 Line 11
|
|
||||||
12 Line 12
|
|
||||||
13 Line 13
|
|
||||||
14 Line 14
|
|
||||||
15 Line 15
|
|
||||||
16 Line 16
|
|
||||||
17 Line 17
|
|
||||||
18 Line 18
|
|
||||||
19 Line 19
|
|
||||||
20 Line 20
|
|
||||||
21 Line 21
|
|
||||||
22 Line 22
|
|
||||||
23 Line 23
|
|
||||||
24 Line 24
|
|
||||||
25 Line 25
|
|
||||||
26 Line 26
|
|
||||||
27 Line 27
|
|
||||||
28 Line 28
|
|
||||||
29 Line 29
|
|
||||||
30 Line 30
|
|
||||||
31 Line 31
|
|
||||||
32 Line 32
|
|
||||||
33 Line 33
|
|
||||||
34 Line 34
|
|
||||||
35 Line 35
|
|
||||||
36 Line 36
|
|
||||||
37 Line 37
|
|
||||||
38 Line 38
|
|
||||||
39 Line 39
|
|
||||||
40 Line 40
|
|
||||||
41 Line 41
|
|
||||||
42 Line 42
|
|
||||||
43 Line 43
|
43 Line 43
|
||||||
44 Line 44
|
44 Line 44
|
||||||
45 Line 45
|
45 Line 45
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ exports[`ShortcutsHelp > renders correctly in 'narrow' mode on 'linux' 1`] = `
|
|||||||
Ctrl+V paste images
|
Ctrl+V paste images
|
||||||
Alt+M raw markdown mode
|
Alt+M raw markdown mode
|
||||||
Ctrl+R reverse-search history
|
Ctrl+R reverse-search history
|
||||||
Ctrl+X open external editor
|
Ctrl+G open external editor
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ exports[`ShortcutsHelp > renders correctly in 'narrow' mode on 'mac' 1`] = `
|
|||||||
Ctrl+V paste images
|
Ctrl+V paste images
|
||||||
Option+M raw markdown mode
|
Option+M raw markdown mode
|
||||||
Ctrl+R reverse-search history
|
Ctrl+R reverse-search history
|
||||||
Ctrl+X open external editor
|
Ctrl+G open external editor
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ exports[`ShortcutsHelp > renders correctly in 'wide' mode on 'linux' 1`] = `
|
|||||||
Shortcuts See /help for more
|
Shortcuts See /help for more
|
||||||
! shell mode Shift+Tab cycle mode Ctrl+V paste images
|
! shell mode Shift+Tab cycle mode Ctrl+V paste images
|
||||||
@ select file or folder Ctrl+Y YOLO mode Alt+M raw markdown mode
|
@ select file or folder Ctrl+Y YOLO mode Alt+M raw markdown mode
|
||||||
Double Esc clear & rewind Ctrl+R reverse-search history Ctrl+X open external editor
|
Double Esc clear & rewind Ctrl+R reverse-search history Ctrl+G open external editor
|
||||||
Tab focus UI
|
Tab focus UI
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
@@ -47,7 +47,7 @@ exports[`ShortcutsHelp > renders correctly in 'wide' mode on 'mac' 1`] = `
|
|||||||
Shortcuts See /help for more
|
Shortcuts See /help for more
|
||||||
! shell mode Shift+Tab cycle mode Ctrl+V paste images
|
! shell mode Shift+Tab cycle mode Ctrl+V paste images
|
||||||
@ select file or folder Ctrl+Y YOLO mode Option+M raw markdown mode
|
@ select file or folder Ctrl+Y YOLO mode Option+M raw markdown mode
|
||||||
Double Esc clear & rewind Ctrl+R reverse-search history Ctrl+X open external editor
|
Double Esc clear & rewind Ctrl+R reverse-search history Ctrl+G open external editor
|
||||||
Tab focus UI
|
Tab focus UI
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -263,3 +263,32 @@ exports[`<StatsDisplay /> > renders only the Performance section in its zero sta
|
|||||||
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
|
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`<StatsDisplay /> > renders role breakdown correctly under models 1`] = `
|
||||||
|
"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
|
│ │
|
||||||
|
│ Session Stats │
|
||||||
|
│ │
|
||||||
|
│ Interaction Summary │
|
||||||
|
│ Session ID: test-session-id │
|
||||||
|
│ Tool Calls: 0 ( ✓ 0 x 0 ) │
|
||||||
|
│ Success Rate: 0.0% │
|
||||||
|
│ │
|
||||||
|
│ Performance │
|
||||||
|
│ Wall Time: 1s │
|
||||||
|
│ Agent Active: 10.0s │
|
||||||
|
│ » API Time: 10.0s (100.0%) │
|
||||||
|
│ » Tool Time: 0s (0.0%) │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ Model Usage │
|
||||||
|
│ Use /model to view model quota information │
|
||||||
|
│ │
|
||||||
|
│ Model Reqs Input Tokens Cache Reads Output Tokens │
|
||||||
|
│ ────────────────────────────────────────────────────────────────────────────────────────────── │
|
||||||
|
│ gemini-2.5-flash 10 1,200 200 2,000 │
|
||||||
|
│ ↳ main 7 900 100 1,500 │
|
||||||
|
│ ↳ utility_tool 3 300 100 500 │
|
||||||
|
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|||||||
@@ -1,130 +1,113 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="740" height="598" viewBox="0 0 740 598">
|
<svg xmlns="http://www.w3.org/2000/svg" width="740" height="428" viewBox="0 0 740 428">
|
||||||
<style>
|
<style>
|
||||||
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
|
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
|
||||||
</style>
|
</style>
|
||||||
<rect width="740" height="598" fill="#000000" />
|
<rect width="740" height="428" fill="#000000" />
|
||||||
<g transform="translate(10, 10)">
|
<g transform="translate(10, 10)">
|
||||||
<text x="0" y="2" fill="#ffffaf" textLength="720" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────╮</text>
|
<text x="0" y="2" fill="#333333" textLength="720" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────╮</text>
|
||||||
<text x="0" y="19" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="19" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="19" fill="#ffffaf" textLength="135" lengthAdjust="spacingAndGlyphs" font-weight="bold">Action Required</text>
|
<text x="18" y="19" fill="#ffffaf" textLength="81" lengthAdjust="spacingAndGlyphs" font-weight="bold">? Shell </text>
|
||||||
|
<text x="99" y="19" fill="#ffffff" textLength="396" lengthAdjust="spacingAndGlyphs">Executes a bash command with a deceptive URL</text>
|
||||||
<text x="648" y="19" fill="#afafaf" textLength="54" lengthAdjust="spacingAndGlyphs">3 of 3</text>
|
<text x="648" y="19" fill="#afafaf" textLength="54" lengthAdjust="spacingAndGlyphs">3 of 3</text>
|
||||||
<text x="711" y="19" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="711" y="19" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="36" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="36" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="36" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="18" y="36" fill="#afafaf" textLength="225" lengthAdjust="spacingAndGlyphs">... 6 hidden (Ctrl+O) ...</text>
|
||||||
<text x="0" y="53" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="711" y="36" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="53" fill="#87afff" textLength="9" lengthAdjust="spacingAndGlyphs">?</text>
|
<text x="0" y="53" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="45" y="53" fill="#ffffff" textLength="153" lengthAdjust="spacingAndGlyphs" font-weight="bold">run_shell_command</text>
|
<text x="18" y="53" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="207" y="53" fill="#afafaf" textLength="396" lengthAdjust="spacingAndGlyphs">Executes a bash command with a deceptive URL</text>
|
<text x="36" y="53" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||||
<text x="711" y="53" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="81" y="53" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 44"</text>
|
||||||
<text x="0" y="70" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="693" y="53" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="70" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="711" y="53" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="87" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="87" fill="#afafaf" textLength="225" lengthAdjust="spacingAndGlyphs">... 6 hidden (Ctrl+O) ...</text>
|
<text x="18" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="87" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="36" y="70" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||||
<text x="0" y="104" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="81" y="70" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 45"</text>
|
||||||
<text x="18" y="104" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
<text x="693" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="63" y="104" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 37"</text>
|
<text x="711" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="104" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="121" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="18" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="121" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
<text x="36" y="87" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||||
<text x="63" y="121" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 38"</text>
|
<text x="81" y="87" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 46"</text>
|
||||||
<text x="711" y="121" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="693" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="138" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="711" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="138" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
<text x="0" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="63" y="138" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 39"</text>
|
<text x="18" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="138" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="36" y="104" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||||
<text x="0" y="155" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="81" y="104" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 47"</text>
|
||||||
<text x="18" y="155" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
<text x="693" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="63" y="155" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 40"</text>
|
<text x="711" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="155" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="172" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="18" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="172" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
<text x="36" y="121" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||||
<text x="63" y="172" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 41"</text>
|
<text x="81" y="121" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 48"</text>
|
||||||
<text x="711" y="172" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="693" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="189" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="711" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="189" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
<text x="0" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="63" y="189" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 42"</text>
|
<text x="18" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="189" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="36" y="138" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||||
<text x="0" y="206" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="81" y="138" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 49"</text>
|
||||||
<text x="18" y="206" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
<text x="693" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="63" y="206" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 43"</text>
|
<text x="711" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="206" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="155" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="223" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="18" y="155" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="223" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
<text x="36" y="155" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||||
<text x="63" y="223" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 44"</text>
|
<text x="81" y="155" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 50"</text>
|
||||||
<text x="711" y="223" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="693" y="155" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="240" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="711" y="155" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="240" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
<text x="0" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="63" y="240" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 45"</text>
|
<text x="18" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="240" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="36" y="172" fill="#e5e5e5" textLength="189" lengthAdjust="spacingAndGlyphs">curl https://täst.com</text>
|
||||||
<text x="0" y="257" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="693" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="257" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
<text x="711" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="63" y="257" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 46"</text>
|
<text x="0" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="257" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="18" y="189" fill="#333333" textLength="684" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────╯</text>
|
||||||
<text x="0" y="274" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="711" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="274" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
<text x="0" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="63" y="274" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 47"</text>
|
<text x="711" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="274" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="291" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="18" y="223" fill="#ffffaf" textLength="18" lengthAdjust="spacingAndGlyphs">⚠ </text>
|
||||||
<text x="18" y="291" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
<text x="45" y="223" fill="#ffffaf" textLength="72" lengthAdjust="spacingAndGlyphs" font-weight="bold">Warning:</text>
|
||||||
<text x="63" y="291" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 48"</text>
|
<text x="117" y="223" fill="#ffffaf" textLength="243" lengthAdjust="spacingAndGlyphs"> Deceptive URL(s) detected:</text>
|
||||||
<text x="711" y="291" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="711" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="308" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="308" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
<text x="711" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="63" y="308" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 49"</text>
|
<text x="0" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="308" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="72" y="257" fill="#ffffaf" textLength="81" lengthAdjust="spacingAndGlyphs" font-weight="bold">Original:</text>
|
||||||
<text x="0" y="325" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="162" y="257" fill="#87afff" textLength="153" lengthAdjust="spacingAndGlyphs">https://täst.com/</text>
|
||||||
<text x="18" y="325" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
<text x="711" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="63" y="325" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 50"</text>
|
<text x="0" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="325" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="72" y="274" fill="#ffffaf" textLength="207" lengthAdjust="spacingAndGlyphs" font-weight="bold">Actual Host (Punycode):</text>
|
||||||
<text x="0" y="342" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="288" y="274" fill="#87afff" textLength="216" lengthAdjust="spacingAndGlyphs">https://xn--tst-qla.com/</text>
|
||||||
<text x="18" y="342" fill="#e5e5e5" textLength="189" lengthAdjust="spacingAndGlyphs">curl https://täst.com</text>
|
<text x="711" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="342" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="359" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="711" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="359" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="376" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="9" y="308" fill="#ffffff" textLength="180" lengthAdjust="spacingAndGlyphs"> Allow execution of </text>
|
||||||
<text x="18" y="376" fill="#ffffaf" textLength="18" lengthAdjust="spacingAndGlyphs">⚠ </text>
|
<text x="189" y="308" fill="#ffffaf" textLength="54" lengthAdjust="spacingAndGlyphs" font-weight="bold">[echo]</text>
|
||||||
<text x="45" y="376" fill="#ffffaf" textLength="72" lengthAdjust="spacingAndGlyphs" font-weight="bold">Warning:</text>
|
<text x="243" y="308" fill="#ffffff" textLength="468" lengthAdjust="spacingAndGlyphs">? </text>
|
||||||
<text x="117" y="376" fill="#ffffaf" textLength="243" lengthAdjust="spacingAndGlyphs"> Deceptive URL(s) detected:</text>
|
<text x="711" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="376" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="393" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="711" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="393" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="410" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<rect x="18" y="340" width="9" height="17" fill="#001a00" />
|
||||||
<text x="72" y="410" fill="#ffffaf" textLength="81" lengthAdjust="spacingAndGlyphs" font-weight="bold">Original:</text>
|
<text x="18" y="342" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
||||||
<text x="162" y="410" fill="#87afff" textLength="153" lengthAdjust="spacingAndGlyphs">https://täst.com/</text>
|
<rect x="27" y="340" width="9" height="17" fill="#001a00" />
|
||||||
<text x="711" y="410" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<rect x="36" y="340" width="18" height="17" fill="#001a00" />
|
||||||
<text x="0" y="427" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="36" y="342" fill="#00cd00" textLength="18" lengthAdjust="spacingAndGlyphs">1.</text>
|
||||||
<text x="72" y="427" fill="#ffffaf" textLength="207" lengthAdjust="spacingAndGlyphs" font-weight="bold">Actual Host (Punycode):</text>
|
<rect x="54" y="340" width="9" height="17" fill="#001a00" />
|
||||||
<text x="288" y="427" fill="#87afff" textLength="216" lengthAdjust="spacingAndGlyphs">https://xn--tst-qla.com/</text>
|
<rect x="63" y="340" width="90" height="17" fill="#001a00" />
|
||||||
<text x="711" y="427" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="63" y="342" fill="#00cd00" textLength="90" lengthAdjust="spacingAndGlyphs">Allow once</text>
|
||||||
<text x="0" y="444" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<rect x="153" y="340" width="135" height="17" fill="#001a00" />
|
||||||
<text x="711" y="444" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="711" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="461" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="461" fill="#ffffff" textLength="243" lengthAdjust="spacingAndGlyphs">Allow execution of: 'echo'?</text>
|
<text x="36" y="359" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">2.</text>
|
||||||
<text x="711" y="461" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="63" y="359" fill="#ffffff" textLength="198" lengthAdjust="spacingAndGlyphs">Allow for this session</text>
|
||||||
<text x="0" y="478" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="711" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="478" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="495" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="36" y="376" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">3.</text>
|
||||||
<rect x="18" y="493" width="9" height="17" fill="#001a00" />
|
<text x="63" y="376" fill="#ffffff" textLength="225" lengthAdjust="spacingAndGlyphs">No, suggest changes (esc)</text>
|
||||||
<text x="18" y="495" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
<text x="711" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="27" y="493" width="9" height="17" fill="#001a00" />
|
<text x="0" y="393" fill="#333333" textLength="720" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────╯</text>
|
||||||
<rect x="36" y="493" width="18" height="17" fill="#001a00" />
|
|
||||||
<text x="36" y="495" fill="#00cd00" textLength="18" lengthAdjust="spacingAndGlyphs">1.</text>
|
|
||||||
<rect x="54" y="493" width="9" height="17" fill="#001a00" />
|
|
||||||
<rect x="63" y="493" width="90" height="17" fill="#001a00" />
|
|
||||||
<text x="63" y="495" fill="#00cd00" textLength="90" lengthAdjust="spacingAndGlyphs">Allow once</text>
|
|
||||||
<rect x="153" y="493" width="135" height="17" fill="#001a00" />
|
|
||||||
<text x="711" y="495" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
|
||||||
<text x="0" y="512" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
|
||||||
<text x="36" y="512" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">2.</text>
|
|
||||||
<text x="63" y="512" fill="#ffffff" textLength="198" lengthAdjust="spacingAndGlyphs">Allow for this session</text>
|
|
||||||
<text x="711" y="512" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
|
||||||
<text x="0" y="529" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
|
||||||
<text x="36" y="529" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">3.</text>
|
|
||||||
<text x="63" y="529" fill="#ffffff" textLength="225" lengthAdjust="spacingAndGlyphs">No, suggest changes (esc)</text>
|
|
||||||
<text x="711" y="529" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
|
||||||
<text x="0" y="546" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
|
||||||
<text x="711" y="546" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
|
||||||
<text x="0" y="563" fill="#ffffaf" textLength="720" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────╯</text>
|
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 11 KiB |
@@ -4,455 +4,540 @@
|
|||||||
</style>
|
</style>
|
||||||
<rect width="740" height="683" fill="#000000" />
|
<rect width="740" height="683" fill="#000000" />
|
||||||
<g transform="translate(10, 10)">
|
<g transform="translate(10, 10)">
|
||||||
<text x="0" y="2" fill="#ffffaf" textLength="720" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────╮</text>
|
<text x="0" y="2" fill="#333333" textLength="720" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────╮</text>
|
||||||
<text x="0" y="19" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="19" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="19" fill="#ffffaf" textLength="135" lengthAdjust="spacingAndGlyphs" font-weight="bold">Action Required</text>
|
<text x="18" y="19" fill="#ffffaf" textLength="81" lengthAdjust="spacingAndGlyphs" font-weight="bold">? replace</text>
|
||||||
<text x="711" y="19" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="711" y="19" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="36" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="36" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="36" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="18" y="36" fill="#333333" textLength="684" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────╮</text>
|
||||||
<text x="0" y="53" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="711" y="36" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="53" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">?</text>
|
<text x="0" y="53" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="45" y="53" fill="#ffffff" textLength="63" lengthAdjust="spacingAndGlyphs" font-weight="bold">replace</text>
|
<text x="18" y="53" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="117" y="53" fill="#afafaf" textLength="234" lengthAdjust="spacingAndGlyphs">Replaces content in a file</text>
|
<text x="36" y="53" fill="#afafaf" textLength="234" lengthAdjust="spacingAndGlyphs">... 13 hidden (Ctrl+O) ...</text>
|
||||||
<text x="711" y="53" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="693" y="53" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="70" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="711" y="53" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="70" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="87" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="18" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="87" fill="#afafaf" textLength="234" lengthAdjust="spacingAndGlyphs">... 15 hidden (Ctrl+O) ...</text>
|
<rect x="36" y="68" width="9" height="17" fill="#005f00" />
|
||||||
<text x="711" y="87" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<rect x="45" y="68" width="9" height="17" fill="#005f00" />
|
||||||
<text x="0" y="104" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="45" y="70" fill="#afafaf" textLength="9" lengthAdjust="spacingAndGlyphs">7</text>
|
||||||
<rect x="18" y="102" width="9" height="17" fill="#005f00" />
|
<rect x="54" y="68" width="9" height="17" fill="#005f00" />
|
||||||
<rect x="27" y="102" width="9" height="17" fill="#005f00" />
|
<rect x="63" y="68" width="9" height="17" fill="#005f00" />
|
||||||
<text x="27" y="104" fill="#afafaf" textLength="9" lengthAdjust="spacingAndGlyphs">8</text>
|
<text x="63" y="70" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||||
|
<rect x="72" y="68" width="9" height="17" fill="#005f00" />
|
||||||
|
<rect x="81" y="68" width="45" height="17" fill="#005f00" />
|
||||||
|
<text x="81" y="70" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||||
|
<rect x="126" y="68" width="108" height="17" fill="#005f00" />
|
||||||
|
<text x="126" y="70" fill="#e5e5e5" textLength="108" lengthAdjust="spacingAndGlyphs"> newLine7 = </text>
|
||||||
|
<rect x="234" y="68" width="36" height="17" fill="#005f00" />
|
||||||
|
<text x="234" y="70" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
|
<rect x="270" y="68" width="9" height="17" fill="#005f00" />
|
||||||
|
<text x="270" y="70" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
|
<text x="693" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="711" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="18" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<rect x="36" y="85" width="9" height="17" fill="#5f0000" />
|
||||||
|
<rect x="45" y="85" width="9" height="17" fill="#5f0000" />
|
||||||
|
<text x="45" y="87" fill="#afafaf" textLength="9" lengthAdjust="spacingAndGlyphs">8</text>
|
||||||
|
<rect x="54" y="85" width="9" height="17" fill="#5f0000" />
|
||||||
|
<rect x="63" y="85" width="9" height="17" fill="#5f0000" />
|
||||||
|
<text x="63" y="87" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
||||||
|
<rect x="72" y="85" width="9" height="17" fill="#5f0000" />
|
||||||
|
<rect x="81" y="85" width="45" height="17" fill="#5f0000" />
|
||||||
|
<text x="81" y="87" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||||
|
<rect x="126" y="85" width="108" height="17" fill="#5f0000" />
|
||||||
|
<text x="126" y="87" fill="#e5e5e5" textLength="108" lengthAdjust="spacingAndGlyphs"> oldLine8 = </text>
|
||||||
|
<rect x="234" y="85" width="36" height="17" fill="#5f0000" />
|
||||||
|
<text x="234" y="87" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
|
<rect x="270" y="85" width="9" height="17" fill="#5f0000" />
|
||||||
|
<text x="270" y="87" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
|
<text x="693" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="711" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="18" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="36" y="102" width="9" height="17" fill="#005f00" />
|
<rect x="36" y="102" width="9" height="17" fill="#005f00" />
|
||||||
<rect x="45" y="102" width="9" height="17" fill="#005f00" />
|
<rect x="45" y="102" width="9" height="17" fill="#005f00" />
|
||||||
<text x="45" y="104" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
<text x="45" y="104" fill="#afafaf" textLength="9" lengthAdjust="spacingAndGlyphs">8</text>
|
||||||
<rect x="54" y="102" width="9" height="17" fill="#005f00" />
|
<rect x="54" y="102" width="9" height="17" fill="#005f00" />
|
||||||
<rect x="63" y="102" width="45" height="17" fill="#005f00" />
|
<rect x="63" y="102" width="9" height="17" fill="#005f00" />
|
||||||
<text x="63" y="104" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
<text x="63" y="104" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||||
<rect x="108" y="102" width="108" height="17" fill="#005f00" />
|
<rect x="72" y="102" width="9" height="17" fill="#005f00" />
|
||||||
<text x="108" y="104" fill="#e5e5e5" textLength="108" lengthAdjust="spacingAndGlyphs"> newLine8 = </text>
|
<rect x="81" y="102" width="45" height="17" fill="#005f00" />
|
||||||
<rect x="216" y="102" width="36" height="17" fill="#005f00" />
|
<text x="81" y="104" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||||
<text x="216" y="104" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
<rect x="126" y="102" width="108" height="17" fill="#005f00" />
|
||||||
<rect x="252" y="102" width="9" height="17" fill="#005f00" />
|
<text x="126" y="104" fill="#e5e5e5" textLength="108" lengthAdjust="spacingAndGlyphs"> newLine8 = </text>
|
||||||
<text x="252" y="104" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
<rect x="234" y="102" width="36" height="17" fill="#005f00" />
|
||||||
<text x="711" y="104" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="234" y="104" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
<text x="0" y="121" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<rect x="270" y="102" width="9" height="17" fill="#005f00" />
|
||||||
<rect x="18" y="119" width="9" height="17" fill="#5f0000" />
|
<text x="270" y="104" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
<rect x="27" y="119" width="9" height="17" fill="#5f0000" />
|
<text x="693" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="27" y="121" fill="#afafaf" textLength="9" lengthAdjust="spacingAndGlyphs">9</text>
|
<text x="711" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="18" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="36" y="119" width="9" height="17" fill="#5f0000" />
|
<rect x="36" y="119" width="9" height="17" fill="#5f0000" />
|
||||||
<rect x="45" y="119" width="9" height="17" fill="#5f0000" />
|
<rect x="45" y="119" width="9" height="17" fill="#5f0000" />
|
||||||
<text x="45" y="121" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
<text x="45" y="121" fill="#afafaf" textLength="9" lengthAdjust="spacingAndGlyphs">9</text>
|
||||||
<rect x="54" y="119" width="9" height="17" fill="#5f0000" />
|
<rect x="54" y="119" width="9" height="17" fill="#5f0000" />
|
||||||
<rect x="63" y="119" width="45" height="17" fill="#5f0000" />
|
<rect x="63" y="119" width="9" height="17" fill="#5f0000" />
|
||||||
<text x="63" y="121" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
<text x="63" y="121" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
||||||
<rect x="108" y="119" width="108" height="17" fill="#5f0000" />
|
<rect x="72" y="119" width="9" height="17" fill="#5f0000" />
|
||||||
<text x="108" y="121" fill="#e5e5e5" textLength="108" lengthAdjust="spacingAndGlyphs"> oldLine9 = </text>
|
<rect x="81" y="119" width="45" height="17" fill="#5f0000" />
|
||||||
<rect x="216" y="119" width="36" height="17" fill="#5f0000" />
|
<text x="81" y="121" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||||
<text x="216" y="121" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
<rect x="126" y="119" width="108" height="17" fill="#5f0000" />
|
||||||
<rect x="252" y="119" width="9" height="17" fill="#5f0000" />
|
<text x="126" y="121" fill="#e5e5e5" textLength="108" lengthAdjust="spacingAndGlyphs"> oldLine9 = </text>
|
||||||
<text x="252" y="121" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
<rect x="234" y="119" width="36" height="17" fill="#5f0000" />
|
||||||
<text x="711" y="121" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="234" y="121" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
<text x="0" y="138" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<rect x="270" y="119" width="9" height="17" fill="#5f0000" />
|
||||||
<rect x="18" y="136" width="9" height="17" fill="#005f00" />
|
<text x="270" y="121" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
<rect x="27" y="136" width="9" height="17" fill="#005f00" />
|
<text x="693" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="27" y="138" fill="#afafaf" textLength="9" lengthAdjust="spacingAndGlyphs">9</text>
|
<text x="711" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="18" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="36" y="136" width="9" height="17" fill="#005f00" />
|
<rect x="36" y="136" width="9" height="17" fill="#005f00" />
|
||||||
<rect x="45" y="136" width="9" height="17" fill="#005f00" />
|
<rect x="45" y="136" width="9" height="17" fill="#005f00" />
|
||||||
<text x="45" y="138" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
<text x="45" y="138" fill="#afafaf" textLength="9" lengthAdjust="spacingAndGlyphs">9</text>
|
||||||
<rect x="54" y="136" width="9" height="17" fill="#005f00" />
|
<rect x="54" y="136" width="9" height="17" fill="#005f00" />
|
||||||
<rect x="63" y="136" width="45" height="17" fill="#005f00" />
|
<rect x="63" y="136" width="9" height="17" fill="#005f00" />
|
||||||
<text x="63" y="138" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
<text x="63" y="138" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||||
<rect x="108" y="136" width="108" height="17" fill="#005f00" />
|
<rect x="72" y="136" width="9" height="17" fill="#005f00" />
|
||||||
<text x="108" y="138" fill="#e5e5e5" textLength="108" lengthAdjust="spacingAndGlyphs"> newLine9 = </text>
|
<rect x="81" y="136" width="45" height="17" fill="#005f00" />
|
||||||
<rect x="216" y="136" width="36" height="17" fill="#005f00" />
|
<text x="81" y="138" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||||
<text x="216" y="138" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
<rect x="126" y="136" width="108" height="17" fill="#005f00" />
|
||||||
<rect x="252" y="136" width="9" height="17" fill="#005f00" />
|
<text x="126" y="138" fill="#e5e5e5" textLength="108" lengthAdjust="spacingAndGlyphs"> newLine9 = </text>
|
||||||
<text x="252" y="138" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
<rect x="234" y="136" width="36" height="17" fill="#005f00" />
|
||||||
<text x="711" y="138" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="234" y="138" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
<text x="0" y="155" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<rect x="270" y="136" width="9" height="17" fill="#005f00" />
|
||||||
<rect x="18" y="153" width="18" height="17" fill="#5f0000" />
|
<text x="270" y="138" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
<text x="18" y="155" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">10</text>
|
<text x="693" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="36" y="153" width="9" height="17" fill="#5f0000" />
|
<text x="711" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="45" y="153" width="9" height="17" fill="#5f0000" />
|
<text x="0" y="155" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="45" y="155" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
<text x="18" y="155" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<rect x="36" y="153" width="18" height="17" fill="#5f0000" />
|
||||||
|
<text x="36" y="155" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">10</text>
|
||||||
<rect x="54" y="153" width="9" height="17" fill="#5f0000" />
|
<rect x="54" y="153" width="9" height="17" fill="#5f0000" />
|
||||||
<rect x="63" y="153" width="45" height="17" fill="#5f0000" />
|
<rect x="63" y="153" width="9" height="17" fill="#5f0000" />
|
||||||
<text x="63" y="155" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
<text x="63" y="155" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
||||||
<rect x="108" y="153" width="117" height="17" fill="#5f0000" />
|
<rect x="72" y="153" width="9" height="17" fill="#5f0000" />
|
||||||
<text x="108" y="155" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine10 = </text>
|
<rect x="81" y="153" width="45" height="17" fill="#5f0000" />
|
||||||
<rect x="225" y="153" width="36" height="17" fill="#5f0000" />
|
<text x="81" y="155" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||||
<text x="225" y="155" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
<rect x="126" y="153" width="117" height="17" fill="#5f0000" />
|
||||||
<rect x="261" y="153" width="9" height="17" fill="#5f0000" />
|
<text x="126" y="155" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine10 = </text>
|
||||||
<text x="261" y="155" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
<rect x="243" y="153" width="36" height="17" fill="#5f0000" />
|
||||||
<text x="711" y="155" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="243" y="155" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
<text x="0" y="172" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<rect x="279" y="153" width="9" height="17" fill="#5f0000" />
|
||||||
<rect x="18" y="170" width="18" height="17" fill="#005f00" />
|
<text x="279" y="155" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
<text x="18" y="172" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">10</text>
|
<text x="693" y="155" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="36" y="170" width="9" height="17" fill="#005f00" />
|
<text x="711" y="155" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="45" y="170" width="9" height="17" fill="#005f00" />
|
<text x="0" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="45" y="172" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
<text x="18" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<rect x="36" y="170" width="18" height="17" fill="#005f00" />
|
||||||
|
<text x="36" y="172" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">10</text>
|
||||||
<rect x="54" y="170" width="9" height="17" fill="#005f00" />
|
<rect x="54" y="170" width="9" height="17" fill="#005f00" />
|
||||||
<rect x="63" y="170" width="45" height="17" fill="#005f00" />
|
<rect x="63" y="170" width="9" height="17" fill="#005f00" />
|
||||||
<text x="63" y="172" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
<text x="63" y="172" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||||
<rect x="108" y="170" width="117" height="17" fill="#005f00" />
|
<rect x="72" y="170" width="9" height="17" fill="#005f00" />
|
||||||
<text x="108" y="172" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine10 = </text>
|
<rect x="81" y="170" width="45" height="17" fill="#005f00" />
|
||||||
<rect x="225" y="170" width="36" height="17" fill="#005f00" />
|
<text x="81" y="172" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||||
<text x="225" y="172" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
<rect x="126" y="170" width="117" height="17" fill="#005f00" />
|
||||||
<rect x="261" y="170" width="9" height="17" fill="#005f00" />
|
<text x="126" y="172" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine10 = </text>
|
||||||
<text x="261" y="172" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
<rect x="243" y="170" width="36" height="17" fill="#005f00" />
|
||||||
<text x="711" y="172" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="243" y="172" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
<text x="0" y="189" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<rect x="279" y="170" width="9" height="17" fill="#005f00" />
|
||||||
<rect x="18" y="187" width="18" height="17" fill="#5f0000" />
|
<text x="279" y="172" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
<text x="18" y="189" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">11</text>
|
<text x="693" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="36" y="187" width="9" height="17" fill="#5f0000" />
|
<text x="711" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="45" y="187" width="9" height="17" fill="#5f0000" />
|
<text x="0" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="45" y="189" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
<text x="18" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<rect x="36" y="187" width="18" height="17" fill="#5f0000" />
|
||||||
|
<text x="36" y="189" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">11</text>
|
||||||
<rect x="54" y="187" width="9" height="17" fill="#5f0000" />
|
<rect x="54" y="187" width="9" height="17" fill="#5f0000" />
|
||||||
<rect x="63" y="187" width="45" height="17" fill="#5f0000" />
|
<rect x="63" y="187" width="9" height="17" fill="#5f0000" />
|
||||||
<text x="63" y="189" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
<text x="63" y="189" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
||||||
<rect x="108" y="187" width="117" height="17" fill="#5f0000" />
|
<rect x="72" y="187" width="9" height="17" fill="#5f0000" />
|
||||||
<text x="108" y="189" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine11 = </text>
|
<rect x="81" y="187" width="45" height="17" fill="#5f0000" />
|
||||||
<rect x="225" y="187" width="36" height="17" fill="#5f0000" />
|
<text x="81" y="189" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||||
<text x="225" y="189" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
<rect x="126" y="187" width="117" height="17" fill="#5f0000" />
|
||||||
<rect x="261" y="187" width="9" height="17" fill="#5f0000" />
|
<text x="126" y="189" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine11 = </text>
|
||||||
<text x="261" y="189" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
<rect x="243" y="187" width="36" height="17" fill="#5f0000" />
|
||||||
<text x="711" y="189" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="243" y="189" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
<text x="0" y="206" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<rect x="279" y="187" width="9" height="17" fill="#5f0000" />
|
||||||
<rect x="18" y="204" width="18" height="17" fill="#005f00" />
|
<text x="279" y="189" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
<text x="18" y="206" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">11</text>
|
<text x="693" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="36" y="204" width="9" height="17" fill="#005f00" />
|
<text x="711" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="45" y="204" width="9" height="17" fill="#005f00" />
|
<text x="0" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="45" y="206" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
<text x="18" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<rect x="36" y="204" width="18" height="17" fill="#005f00" />
|
||||||
|
<text x="36" y="206" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">11</text>
|
||||||
<rect x="54" y="204" width="9" height="17" fill="#005f00" />
|
<rect x="54" y="204" width="9" height="17" fill="#005f00" />
|
||||||
<rect x="63" y="204" width="45" height="17" fill="#005f00" />
|
<rect x="63" y="204" width="9" height="17" fill="#005f00" />
|
||||||
<text x="63" y="206" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
<text x="63" y="206" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||||
<rect x="108" y="204" width="117" height="17" fill="#005f00" />
|
<rect x="72" y="204" width="9" height="17" fill="#005f00" />
|
||||||
<text x="108" y="206" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine11 = </text>
|
<rect x="81" y="204" width="45" height="17" fill="#005f00" />
|
||||||
<rect x="225" y="204" width="36" height="17" fill="#005f00" />
|
<text x="81" y="206" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||||
<text x="225" y="206" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
<rect x="126" y="204" width="117" height="17" fill="#005f00" />
|
||||||
<rect x="261" y="204" width="9" height="17" fill="#005f00" />
|
<text x="126" y="206" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine11 = </text>
|
||||||
<text x="261" y="206" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
<rect x="243" y="204" width="36" height="17" fill="#005f00" />
|
||||||
<text x="711" y="206" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="243" y="206" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
<text x="0" y="223" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<rect x="279" y="204" width="9" height="17" fill="#005f00" />
|
||||||
<rect x="18" y="221" width="18" height="17" fill="#5f0000" />
|
<text x="279" y="206" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
<text x="18" y="223" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">12</text>
|
<text x="693" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="36" y="221" width="9" height="17" fill="#5f0000" />
|
<text x="711" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="45" y="221" width="9" height="17" fill="#5f0000" />
|
<text x="0" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="45" y="223" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
<text x="18" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<rect x="36" y="221" width="18" height="17" fill="#5f0000" />
|
||||||
|
<text x="36" y="223" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">12</text>
|
||||||
<rect x="54" y="221" width="9" height="17" fill="#5f0000" />
|
<rect x="54" y="221" width="9" height="17" fill="#5f0000" />
|
||||||
<rect x="63" y="221" width="45" height="17" fill="#5f0000" />
|
<rect x="63" y="221" width="9" height="17" fill="#5f0000" />
|
||||||
<text x="63" y="223" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
<text x="63" y="223" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
||||||
<rect x="108" y="221" width="117" height="17" fill="#5f0000" />
|
<rect x="72" y="221" width="9" height="17" fill="#5f0000" />
|
||||||
<text x="108" y="223" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine12 = </text>
|
<rect x="81" y="221" width="45" height="17" fill="#5f0000" />
|
||||||
<rect x="225" y="221" width="36" height="17" fill="#5f0000" />
|
<text x="81" y="223" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||||
<text x="225" y="223" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
<rect x="126" y="221" width="117" height="17" fill="#5f0000" />
|
||||||
<rect x="261" y="221" width="9" height="17" fill="#5f0000" />
|
<text x="126" y="223" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine12 = </text>
|
||||||
<text x="261" y="223" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
<rect x="243" y="221" width="36" height="17" fill="#5f0000" />
|
||||||
<text x="711" y="223" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="243" y="223" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
<text x="0" y="240" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<rect x="279" y="221" width="9" height="17" fill="#5f0000" />
|
||||||
<rect x="18" y="238" width="18" height="17" fill="#005f00" />
|
<text x="279" y="223" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
<text x="18" y="240" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">12</text>
|
<text x="693" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="36" y="238" width="9" height="17" fill="#005f00" />
|
<text x="711" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="45" y="238" width="9" height="17" fill="#005f00" />
|
<text x="0" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="45" y="240" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
<text x="18" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<rect x="36" y="238" width="18" height="17" fill="#005f00" />
|
||||||
|
<text x="36" y="240" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">12</text>
|
||||||
<rect x="54" y="238" width="9" height="17" fill="#005f00" />
|
<rect x="54" y="238" width="9" height="17" fill="#005f00" />
|
||||||
<rect x="63" y="238" width="45" height="17" fill="#005f00" />
|
<rect x="63" y="238" width="9" height="17" fill="#005f00" />
|
||||||
<text x="63" y="240" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
<text x="63" y="240" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||||
<rect x="108" y="238" width="117" height="17" fill="#005f00" />
|
<rect x="72" y="238" width="9" height="17" fill="#005f00" />
|
||||||
<text x="108" y="240" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine12 = </text>
|
<rect x="81" y="238" width="45" height="17" fill="#005f00" />
|
||||||
<rect x="225" y="238" width="36" height="17" fill="#005f00" />
|
<text x="81" y="240" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||||
<text x="225" y="240" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
<rect x="126" y="238" width="117" height="17" fill="#005f00" />
|
||||||
<rect x="261" y="238" width="9" height="17" fill="#005f00" />
|
<text x="126" y="240" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine12 = </text>
|
||||||
<text x="261" y="240" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
<rect x="243" y="238" width="36" height="17" fill="#005f00" />
|
||||||
<text x="711" y="240" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="243" y="240" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
<text x="0" y="257" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<rect x="279" y="238" width="9" height="17" fill="#005f00" />
|
||||||
<rect x="18" y="255" width="18" height="17" fill="#5f0000" />
|
<text x="279" y="240" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
<text x="18" y="257" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">13</text>
|
<text x="693" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="36" y="255" width="9" height="17" fill="#5f0000" />
|
<text x="711" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="45" y="255" width="9" height="17" fill="#5f0000" />
|
<text x="0" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="45" y="257" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
<text x="18" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<rect x="36" y="255" width="18" height="17" fill="#5f0000" />
|
||||||
|
<text x="36" y="257" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">13</text>
|
||||||
<rect x="54" y="255" width="9" height="17" fill="#5f0000" />
|
<rect x="54" y="255" width="9" height="17" fill="#5f0000" />
|
||||||
<rect x="63" y="255" width="45" height="17" fill="#5f0000" />
|
<rect x="63" y="255" width="9" height="17" fill="#5f0000" />
|
||||||
<text x="63" y="257" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
<text x="63" y="257" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
||||||
<rect x="108" y="255" width="117" height="17" fill="#5f0000" />
|
<rect x="72" y="255" width="9" height="17" fill="#5f0000" />
|
||||||
<text x="108" y="257" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine13 = </text>
|
<rect x="81" y="255" width="45" height="17" fill="#5f0000" />
|
||||||
<rect x="225" y="255" width="36" height="17" fill="#5f0000" />
|
<text x="81" y="257" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||||
<text x="225" y="257" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
<rect x="126" y="255" width="117" height="17" fill="#5f0000" />
|
||||||
<rect x="261" y="255" width="9" height="17" fill="#5f0000" />
|
<text x="126" y="257" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine13 = </text>
|
||||||
<text x="261" y="257" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
<rect x="243" y="255" width="36" height="17" fill="#5f0000" />
|
||||||
<text x="711" y="257" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="243" y="257" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
<text x="0" y="274" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<rect x="279" y="255" width="9" height="17" fill="#5f0000" />
|
||||||
<rect x="18" y="272" width="18" height="17" fill="#005f00" />
|
<text x="279" y="257" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
<text x="18" y="274" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">13</text>
|
<text x="693" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="36" y="272" width="9" height="17" fill="#005f00" />
|
<text x="711" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="45" y="272" width="9" height="17" fill="#005f00" />
|
<text x="0" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="45" y="274" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
<text x="18" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<rect x="36" y="272" width="18" height="17" fill="#005f00" />
|
||||||
|
<text x="36" y="274" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">13</text>
|
||||||
<rect x="54" y="272" width="9" height="17" fill="#005f00" />
|
<rect x="54" y="272" width="9" height="17" fill="#005f00" />
|
||||||
<rect x="63" y="272" width="45" height="17" fill="#005f00" />
|
<rect x="63" y="272" width="9" height="17" fill="#005f00" />
|
||||||
<text x="63" y="274" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
<text x="63" y="274" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||||
<rect x="108" y="272" width="117" height="17" fill="#005f00" />
|
<rect x="72" y="272" width="9" height="17" fill="#005f00" />
|
||||||
<text x="108" y="274" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine13 = </text>
|
<rect x="81" y="272" width="45" height="17" fill="#005f00" />
|
||||||
<rect x="225" y="272" width="36" height="17" fill="#005f00" />
|
<text x="81" y="274" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||||
<text x="225" y="274" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
<rect x="126" y="272" width="117" height="17" fill="#005f00" />
|
||||||
<rect x="261" y="272" width="9" height="17" fill="#005f00" />
|
<text x="126" y="274" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine13 = </text>
|
||||||
<text x="261" y="274" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
<rect x="243" y="272" width="36" height="17" fill="#005f00" />
|
||||||
<text x="711" y="274" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="243" y="274" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
<text x="0" y="291" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<rect x="279" y="272" width="9" height="17" fill="#005f00" />
|
||||||
<rect x="18" y="289" width="18" height="17" fill="#5f0000" />
|
<text x="279" y="274" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
<text x="18" y="291" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">14</text>
|
<text x="693" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="36" y="289" width="9" height="17" fill="#5f0000" />
|
<text x="711" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="45" y="289" width="9" height="17" fill="#5f0000" />
|
<text x="0" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="45" y="291" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
<text x="18" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<rect x="36" y="289" width="18" height="17" fill="#5f0000" />
|
||||||
|
<text x="36" y="291" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">14</text>
|
||||||
<rect x="54" y="289" width="9" height="17" fill="#5f0000" />
|
<rect x="54" y="289" width="9" height="17" fill="#5f0000" />
|
||||||
<rect x="63" y="289" width="45" height="17" fill="#5f0000" />
|
<rect x="63" y="289" width="9" height="17" fill="#5f0000" />
|
||||||
<text x="63" y="291" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
<text x="63" y="291" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
||||||
<rect x="108" y="289" width="117" height="17" fill="#5f0000" />
|
<rect x="72" y="289" width="9" height="17" fill="#5f0000" />
|
||||||
<text x="108" y="291" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine14 = </text>
|
<rect x="81" y="289" width="45" height="17" fill="#5f0000" />
|
||||||
<rect x="225" y="289" width="36" height="17" fill="#5f0000" />
|
<text x="81" y="291" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||||
<text x="225" y="291" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
<rect x="126" y="289" width="117" height="17" fill="#5f0000" />
|
||||||
<rect x="261" y="289" width="9" height="17" fill="#5f0000" />
|
<text x="126" y="291" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine14 = </text>
|
||||||
<text x="261" y="291" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
<rect x="243" y="289" width="36" height="17" fill="#5f0000" />
|
||||||
<text x="711" y="291" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="243" y="291" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
<text x="0" y="308" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<rect x="279" y="289" width="9" height="17" fill="#5f0000" />
|
||||||
<rect x="18" y="306" width="18" height="17" fill="#005f00" />
|
<text x="279" y="291" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
<text x="18" y="308" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">14</text>
|
<text x="693" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="36" y="306" width="9" height="17" fill="#005f00" />
|
<text x="711" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="45" y="306" width="9" height="17" fill="#005f00" />
|
<text x="0" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="45" y="308" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
<text x="18" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<rect x="36" y="306" width="18" height="17" fill="#005f00" />
|
||||||
|
<text x="36" y="308" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">14</text>
|
||||||
<rect x="54" y="306" width="9" height="17" fill="#005f00" />
|
<rect x="54" y="306" width="9" height="17" fill="#005f00" />
|
||||||
<rect x="63" y="306" width="45" height="17" fill="#005f00" />
|
<rect x="63" y="306" width="9" height="17" fill="#005f00" />
|
||||||
<text x="63" y="308" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
<text x="63" y="308" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||||
<rect x="108" y="306" width="117" height="17" fill="#005f00" />
|
<rect x="72" y="306" width="9" height="17" fill="#005f00" />
|
||||||
<text x="108" y="308" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine14 = </text>
|
<rect x="81" y="306" width="45" height="17" fill="#005f00" />
|
||||||
<rect x="225" y="306" width="36" height="17" fill="#005f00" />
|
<text x="81" y="308" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||||
<text x="225" y="308" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
<rect x="126" y="306" width="117" height="17" fill="#005f00" />
|
||||||
<rect x="261" y="306" width="9" height="17" fill="#005f00" />
|
<text x="126" y="308" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine14 = </text>
|
||||||
<text x="261" y="308" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
<rect x="243" y="306" width="36" height="17" fill="#005f00" />
|
||||||
<text x="711" y="308" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="243" y="308" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
<text x="0" y="325" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<rect x="279" y="306" width="9" height="17" fill="#005f00" />
|
||||||
<rect x="18" y="323" width="18" height="17" fill="#5f0000" />
|
<text x="279" y="308" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
<text x="18" y="325" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">15</text>
|
<text x="693" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="36" y="323" width="9" height="17" fill="#5f0000" />
|
<text x="711" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="45" y="323" width="9" height="17" fill="#5f0000" />
|
<text x="0" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="45" y="325" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
<text x="18" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<rect x="36" y="323" width="18" height="17" fill="#5f0000" />
|
||||||
|
<text x="36" y="325" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">15</text>
|
||||||
<rect x="54" y="323" width="9" height="17" fill="#5f0000" />
|
<rect x="54" y="323" width="9" height="17" fill="#5f0000" />
|
||||||
<rect x="63" y="323" width="45" height="17" fill="#5f0000" />
|
<rect x="63" y="323" width="9" height="17" fill="#5f0000" />
|
||||||
<text x="63" y="325" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
<text x="63" y="325" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
||||||
<rect x="108" y="323" width="117" height="17" fill="#5f0000" />
|
<rect x="72" y="323" width="9" height="17" fill="#5f0000" />
|
||||||
<text x="108" y="325" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine15 = </text>
|
<rect x="81" y="323" width="45" height="17" fill="#5f0000" />
|
||||||
<rect x="225" y="323" width="36" height="17" fill="#5f0000" />
|
<text x="81" y="325" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||||
<text x="225" y="325" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
<rect x="126" y="323" width="117" height="17" fill="#5f0000" />
|
||||||
<rect x="261" y="323" width="9" height="17" fill="#5f0000" />
|
<text x="126" y="325" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine15 = </text>
|
||||||
<text x="261" y="325" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
<rect x="243" y="323" width="36" height="17" fill="#5f0000" />
|
||||||
<text x="711" y="325" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="243" y="325" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
<text x="0" y="342" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<rect x="279" y="323" width="9" height="17" fill="#5f0000" />
|
||||||
<rect x="18" y="340" width="18" height="17" fill="#005f00" />
|
<text x="279" y="325" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
<text x="18" y="342" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">15</text>
|
<text x="693" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="36" y="340" width="9" height="17" fill="#005f00" />
|
<text x="711" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="45" y="340" width="9" height="17" fill="#005f00" />
|
<text x="0" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="45" y="342" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
<text x="18" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<rect x="36" y="340" width="18" height="17" fill="#005f00" />
|
||||||
|
<text x="36" y="342" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">15</text>
|
||||||
<rect x="54" y="340" width="9" height="17" fill="#005f00" />
|
<rect x="54" y="340" width="9" height="17" fill="#005f00" />
|
||||||
<rect x="63" y="340" width="45" height="17" fill="#005f00" />
|
<rect x="63" y="340" width="9" height="17" fill="#005f00" />
|
||||||
<text x="63" y="342" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
<text x="63" y="342" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||||
<rect x="108" y="340" width="117" height="17" fill="#005f00" />
|
<rect x="72" y="340" width="9" height="17" fill="#005f00" />
|
||||||
<text x="108" y="342" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine15 = </text>
|
<rect x="81" y="340" width="45" height="17" fill="#005f00" />
|
||||||
<rect x="225" y="340" width="36" height="17" fill="#005f00" />
|
<text x="81" y="342" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||||
<text x="225" y="342" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
<rect x="126" y="340" width="117" height="17" fill="#005f00" />
|
||||||
<rect x="261" y="340" width="9" height="17" fill="#005f00" />
|
<text x="126" y="342" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine15 = </text>
|
||||||
<text x="261" y="342" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
<rect x="243" y="340" width="36" height="17" fill="#005f00" />
|
||||||
<text x="711" y="342" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="243" y="342" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
<text x="0" y="359" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<rect x="279" y="340" width="9" height="17" fill="#005f00" />
|
||||||
<rect x="18" y="357" width="18" height="17" fill="#5f0000" />
|
<text x="279" y="342" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
<text x="18" y="359" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">16</text>
|
<text x="693" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="36" y="357" width="9" height="17" fill="#5f0000" />
|
<text x="711" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="45" y="357" width="9" height="17" fill="#5f0000" />
|
<text x="0" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="45" y="359" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
<text x="18" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<rect x="36" y="357" width="18" height="17" fill="#5f0000" />
|
||||||
|
<text x="36" y="359" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">16</text>
|
||||||
<rect x="54" y="357" width="9" height="17" fill="#5f0000" />
|
<rect x="54" y="357" width="9" height="17" fill="#5f0000" />
|
||||||
<rect x="63" y="357" width="45" height="17" fill="#5f0000" />
|
<rect x="63" y="357" width="9" height="17" fill="#5f0000" />
|
||||||
<text x="63" y="359" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
<text x="63" y="359" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
||||||
<rect x="108" y="357" width="117" height="17" fill="#5f0000" />
|
<rect x="72" y="357" width="9" height="17" fill="#5f0000" />
|
||||||
<text x="108" y="359" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine16 = </text>
|
<rect x="81" y="357" width="45" height="17" fill="#5f0000" />
|
||||||
<rect x="225" y="357" width="36" height="17" fill="#5f0000" />
|
<text x="81" y="359" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||||
<text x="225" y="359" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
<rect x="126" y="357" width="117" height="17" fill="#5f0000" />
|
||||||
<rect x="261" y="357" width="9" height="17" fill="#5f0000" />
|
<text x="126" y="359" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine16 = </text>
|
||||||
<text x="261" y="359" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
<rect x="243" y="357" width="36" height="17" fill="#5f0000" />
|
||||||
<text x="711" y="359" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="243" y="359" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
<text x="0" y="376" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<rect x="279" y="357" width="9" height="17" fill="#5f0000" />
|
||||||
<rect x="18" y="374" width="18" height="17" fill="#005f00" />
|
<text x="279" y="359" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
<text x="18" y="376" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">16</text>
|
<text x="693" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="36" y="374" width="9" height="17" fill="#005f00" />
|
<text x="711" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="45" y="374" width="9" height="17" fill="#005f00" />
|
<text x="0" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="45" y="376" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
<text x="18" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<rect x="36" y="374" width="18" height="17" fill="#005f00" />
|
||||||
|
<text x="36" y="376" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">16</text>
|
||||||
<rect x="54" y="374" width="9" height="17" fill="#005f00" />
|
<rect x="54" y="374" width="9" height="17" fill="#005f00" />
|
||||||
<rect x="63" y="374" width="45" height="17" fill="#005f00" />
|
<rect x="63" y="374" width="9" height="17" fill="#005f00" />
|
||||||
<text x="63" y="376" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
<text x="63" y="376" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||||
<rect x="108" y="374" width="117" height="17" fill="#005f00" />
|
<rect x="72" y="374" width="9" height="17" fill="#005f00" />
|
||||||
<text x="108" y="376" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine16 = </text>
|
<rect x="81" y="374" width="45" height="17" fill="#005f00" />
|
||||||
<rect x="225" y="374" width="36" height="17" fill="#005f00" />
|
<text x="81" y="376" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||||
<text x="225" y="376" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
<rect x="126" y="374" width="117" height="17" fill="#005f00" />
|
||||||
<rect x="261" y="374" width="9" height="17" fill="#005f00" />
|
<text x="126" y="376" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine16 = </text>
|
||||||
<text x="261" y="376" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
<rect x="243" y="374" width="36" height="17" fill="#005f00" />
|
||||||
<text x="711" y="376" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="243" y="376" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
<text x="0" y="393" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<rect x="279" y="374" width="9" height="17" fill="#005f00" />
|
||||||
<rect x="18" y="391" width="18" height="17" fill="#5f0000" />
|
<text x="279" y="376" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
<text x="18" y="393" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">17</text>
|
<text x="693" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="36" y="391" width="9" height="17" fill="#5f0000" />
|
<text x="711" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="45" y="391" width="9" height="17" fill="#5f0000" />
|
<text x="0" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="45" y="393" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
<text x="18" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<rect x="36" y="391" width="18" height="17" fill="#5f0000" />
|
||||||
|
<text x="36" y="393" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">17</text>
|
||||||
<rect x="54" y="391" width="9" height="17" fill="#5f0000" />
|
<rect x="54" y="391" width="9" height="17" fill="#5f0000" />
|
||||||
<rect x="63" y="391" width="45" height="17" fill="#5f0000" />
|
<rect x="63" y="391" width="9" height="17" fill="#5f0000" />
|
||||||
<text x="63" y="393" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
<text x="63" y="393" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
||||||
<rect x="108" y="391" width="117" height="17" fill="#5f0000" />
|
<rect x="72" y="391" width="9" height="17" fill="#5f0000" />
|
||||||
<text x="108" y="393" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine17 = </text>
|
<rect x="81" y="391" width="45" height="17" fill="#5f0000" />
|
||||||
<rect x="225" y="391" width="36" height="17" fill="#5f0000" />
|
<text x="81" y="393" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||||
<text x="225" y="393" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
<rect x="126" y="391" width="117" height="17" fill="#5f0000" />
|
||||||
<rect x="261" y="391" width="9" height="17" fill="#5f0000" />
|
<text x="126" y="393" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine17 = </text>
|
||||||
<text x="261" y="393" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
<rect x="243" y="391" width="36" height="17" fill="#5f0000" />
|
||||||
<text x="711" y="393" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="243" y="393" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
<text x="0" y="410" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<rect x="279" y="391" width="9" height="17" fill="#5f0000" />
|
||||||
<rect x="18" y="408" width="18" height="17" fill="#005f00" />
|
<text x="279" y="393" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
<text x="18" y="410" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">17</text>
|
<text x="693" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="36" y="408" width="9" height="17" fill="#005f00" />
|
<text x="711" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="45" y="408" width="9" height="17" fill="#005f00" />
|
<text x="0" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="45" y="410" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
<text x="18" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<rect x="36" y="408" width="18" height="17" fill="#005f00" />
|
||||||
|
<text x="36" y="410" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">17</text>
|
||||||
<rect x="54" y="408" width="9" height="17" fill="#005f00" />
|
<rect x="54" y="408" width="9" height="17" fill="#005f00" />
|
||||||
<rect x="63" y="408" width="45" height="17" fill="#005f00" />
|
<rect x="63" y="408" width="9" height="17" fill="#005f00" />
|
||||||
<text x="63" y="410" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
<text x="63" y="410" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||||
<rect x="108" y="408" width="117" height="17" fill="#005f00" />
|
<rect x="72" y="408" width="9" height="17" fill="#005f00" />
|
||||||
<text x="108" y="410" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine17 = </text>
|
<rect x="81" y="408" width="45" height="17" fill="#005f00" />
|
||||||
<rect x="225" y="408" width="36" height="17" fill="#005f00" />
|
<text x="81" y="410" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||||
<text x="225" y="410" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
<rect x="126" y="408" width="117" height="17" fill="#005f00" />
|
||||||
<rect x="261" y="408" width="9" height="17" fill="#005f00" />
|
<text x="126" y="410" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine17 = </text>
|
||||||
<text x="261" y="410" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
<rect x="243" y="408" width="36" height="17" fill="#005f00" />
|
||||||
<text x="711" y="410" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="243" y="410" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
<text x="0" y="427" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<rect x="279" y="408" width="9" height="17" fill="#005f00" />
|
||||||
<rect x="18" y="425" width="18" height="17" fill="#5f0000" />
|
<text x="279" y="410" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
<text x="18" y="427" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">18</text>
|
<text x="693" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="36" y="425" width="9" height="17" fill="#5f0000" />
|
<text x="711" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="45" y="425" width="9" height="17" fill="#5f0000" />
|
<text x="0" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="45" y="427" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
<text x="18" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<rect x="36" y="425" width="18" height="17" fill="#5f0000" />
|
||||||
|
<text x="36" y="427" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">18</text>
|
||||||
<rect x="54" y="425" width="9" height="17" fill="#5f0000" />
|
<rect x="54" y="425" width="9" height="17" fill="#5f0000" />
|
||||||
<rect x="63" y="425" width="45" height="17" fill="#5f0000" />
|
<rect x="63" y="425" width="9" height="17" fill="#5f0000" />
|
||||||
<text x="63" y="427" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
<text x="63" y="427" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
||||||
<rect x="108" y="425" width="117" height="17" fill="#5f0000" />
|
<rect x="72" y="425" width="9" height="17" fill="#5f0000" />
|
||||||
<text x="108" y="427" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine18 = </text>
|
<rect x="81" y="425" width="45" height="17" fill="#5f0000" />
|
||||||
<rect x="225" y="425" width="36" height="17" fill="#5f0000" />
|
<text x="81" y="427" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||||
<text x="225" y="427" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
<rect x="126" y="425" width="117" height="17" fill="#5f0000" />
|
||||||
<rect x="261" y="425" width="9" height="17" fill="#5f0000" />
|
<text x="126" y="427" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine18 = </text>
|
||||||
<text x="261" y="427" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
<rect x="243" y="425" width="36" height="17" fill="#5f0000" />
|
||||||
<text x="711" y="427" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="243" y="427" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
<text x="0" y="444" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<rect x="279" y="425" width="9" height="17" fill="#5f0000" />
|
||||||
<rect x="18" y="442" width="18" height="17" fill="#005f00" />
|
<text x="279" y="427" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
<text x="18" y="444" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">18</text>
|
<text x="693" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="36" y="442" width="9" height="17" fill="#005f00" />
|
<text x="711" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="45" y="442" width="9" height="17" fill="#005f00" />
|
<text x="0" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="45" y="444" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
<text x="18" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<rect x="36" y="442" width="18" height="17" fill="#005f00" />
|
||||||
|
<text x="36" y="444" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">18</text>
|
||||||
<rect x="54" y="442" width="9" height="17" fill="#005f00" />
|
<rect x="54" y="442" width="9" height="17" fill="#005f00" />
|
||||||
<rect x="63" y="442" width="45" height="17" fill="#005f00" />
|
<rect x="63" y="442" width="9" height="17" fill="#005f00" />
|
||||||
<text x="63" y="444" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
<text x="63" y="444" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||||
<rect x="108" y="442" width="117" height="17" fill="#005f00" />
|
<rect x="72" y="442" width="9" height="17" fill="#005f00" />
|
||||||
<text x="108" y="444" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine18 = </text>
|
<rect x="81" y="442" width="45" height="17" fill="#005f00" />
|
||||||
<rect x="225" y="442" width="36" height="17" fill="#005f00" />
|
<text x="81" y="444" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||||
<text x="225" y="444" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
<rect x="126" y="442" width="117" height="17" fill="#005f00" />
|
||||||
<rect x="261" y="442" width="9" height="17" fill="#005f00" />
|
<text x="126" y="444" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine18 = </text>
|
||||||
<text x="261" y="444" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
<rect x="243" y="442" width="36" height="17" fill="#005f00" />
|
||||||
<text x="711" y="444" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="243" y="444" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
<text x="0" y="461" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<rect x="279" y="442" width="9" height="17" fill="#005f00" />
|
||||||
<rect x="18" y="459" width="18" height="17" fill="#5f0000" />
|
<text x="279" y="444" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
<text x="18" y="461" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">19</text>
|
<text x="693" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="36" y="459" width="9" height="17" fill="#5f0000" />
|
<text x="711" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="45" y="459" width="9" height="17" fill="#5f0000" />
|
<text x="0" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="45" y="461" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
<text x="18" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<rect x="36" y="459" width="18" height="17" fill="#5f0000" />
|
||||||
|
<text x="36" y="461" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">19</text>
|
||||||
<rect x="54" y="459" width="9" height="17" fill="#5f0000" />
|
<rect x="54" y="459" width="9" height="17" fill="#5f0000" />
|
||||||
<rect x="63" y="459" width="45" height="17" fill="#5f0000" />
|
<rect x="63" y="459" width="9" height="17" fill="#5f0000" />
|
||||||
<text x="63" y="461" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
<text x="63" y="461" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
||||||
<rect x="108" y="459" width="117" height="17" fill="#5f0000" />
|
<rect x="72" y="459" width="9" height="17" fill="#5f0000" />
|
||||||
<text x="108" y="461" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine19 = </text>
|
<rect x="81" y="459" width="45" height="17" fill="#5f0000" />
|
||||||
<rect x="225" y="459" width="36" height="17" fill="#5f0000" />
|
<text x="81" y="461" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||||
<text x="225" y="461" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
<rect x="126" y="459" width="117" height="17" fill="#5f0000" />
|
||||||
<rect x="261" y="459" width="9" height="17" fill="#5f0000" />
|
<text x="126" y="461" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine19 = </text>
|
||||||
<text x="261" y="461" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
<rect x="243" y="459" width="36" height="17" fill="#5f0000" />
|
||||||
<text x="711" y="461" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="243" y="461" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
<text x="0" y="478" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<rect x="279" y="459" width="9" height="17" fill="#5f0000" />
|
||||||
<rect x="18" y="476" width="18" height="17" fill="#005f00" />
|
<text x="279" y="461" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
<text x="18" y="478" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">19</text>
|
<text x="693" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="36" y="476" width="9" height="17" fill="#005f00" />
|
<text x="711" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="45" y="476" width="9" height="17" fill="#005f00" />
|
<text x="0" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="45" y="478" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
<text x="18" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<rect x="36" y="476" width="18" height="17" fill="#005f00" />
|
||||||
|
<text x="36" y="478" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">19</text>
|
||||||
<rect x="54" y="476" width="9" height="17" fill="#005f00" />
|
<rect x="54" y="476" width="9" height="17" fill="#005f00" />
|
||||||
<rect x="63" y="476" width="45" height="17" fill="#005f00" />
|
<rect x="63" y="476" width="9" height="17" fill="#005f00" />
|
||||||
<text x="63" y="478" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
<text x="63" y="478" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||||
<rect x="108" y="476" width="117" height="17" fill="#005f00" />
|
<rect x="72" y="476" width="9" height="17" fill="#005f00" />
|
||||||
<text x="108" y="478" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine19 = </text>
|
<rect x="81" y="476" width="45" height="17" fill="#005f00" />
|
||||||
<rect x="225" y="476" width="36" height="17" fill="#005f00" />
|
<text x="81" y="478" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||||
<text x="225" y="478" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
<rect x="126" y="476" width="117" height="17" fill="#005f00" />
|
||||||
<rect x="261" y="476" width="9" height="17" fill="#005f00" />
|
<text x="126" y="478" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine19 = </text>
|
||||||
<text x="261" y="478" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
<rect x="243" y="476" width="36" height="17" fill="#005f00" />
|
||||||
<text x="711" y="478" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="243" y="478" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
<text x="0" y="495" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<rect x="279" y="476" width="9" height="17" fill="#005f00" />
|
||||||
<rect x="18" y="493" width="18" height="17" fill="#5f0000" />
|
<text x="279" y="478" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
<text x="18" y="495" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">20</text>
|
<text x="693" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="36" y="493" width="9" height="17" fill="#5f0000" />
|
<text x="711" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="45" y="493" width="9" height="17" fill="#5f0000" />
|
<text x="0" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="45" y="495" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
<text x="18" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<rect x="36" y="493" width="18" height="17" fill="#5f0000" />
|
||||||
|
<text x="36" y="495" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">20</text>
|
||||||
<rect x="54" y="493" width="9" height="17" fill="#5f0000" />
|
<rect x="54" y="493" width="9" height="17" fill="#5f0000" />
|
||||||
<rect x="63" y="493" width="45" height="17" fill="#5f0000" />
|
<rect x="63" y="493" width="9" height="17" fill="#5f0000" />
|
||||||
<text x="63" y="495" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
<text x="63" y="495" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
||||||
<rect x="108" y="493" width="117" height="17" fill="#5f0000" />
|
<rect x="72" y="493" width="9" height="17" fill="#5f0000" />
|
||||||
<text x="108" y="495" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine20 = </text>
|
<rect x="81" y="493" width="45" height="17" fill="#5f0000" />
|
||||||
<rect x="225" y="493" width="36" height="17" fill="#5f0000" />
|
<text x="81" y="495" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||||
<text x="225" y="495" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
<rect x="126" y="493" width="117" height="17" fill="#5f0000" />
|
||||||
<rect x="261" y="493" width="9" height="17" fill="#5f0000" />
|
<text x="126" y="495" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine20 = </text>
|
||||||
<text x="261" y="495" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
<rect x="243" y="493" width="36" height="17" fill="#5f0000" />
|
||||||
<text x="711" y="495" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="243" y="495" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
<text x="0" y="512" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<rect x="279" y="493" width="9" height="17" fill="#5f0000" />
|
||||||
<rect x="18" y="510" width="18" height="17" fill="#005f00" />
|
<text x="279" y="495" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
<text x="18" y="512" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">20</text>
|
<text x="693" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="36" y="510" width="9" height="17" fill="#005f00" />
|
<text x="711" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="45" y="510" width="9" height="17" fill="#005f00" />
|
<text x="0" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="45" y="512" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
<text x="18" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<rect x="36" y="510" width="18" height="17" fill="#005f00" />
|
||||||
|
<text x="36" y="512" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">20</text>
|
||||||
<rect x="54" y="510" width="9" height="17" fill="#005f00" />
|
<rect x="54" y="510" width="9" height="17" fill="#005f00" />
|
||||||
<rect x="63" y="510" width="45" height="17" fill="#005f00" />
|
<rect x="63" y="510" width="9" height="17" fill="#005f00" />
|
||||||
<text x="63" y="512" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
<text x="63" y="512" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||||
<rect x="108" y="510" width="117" height="17" fill="#005f00" />
|
<rect x="72" y="510" width="9" height="17" fill="#005f00" />
|
||||||
<text x="108" y="512" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine20 = </text>
|
<rect x="81" y="510" width="45" height="17" fill="#005f00" />
|
||||||
<rect x="225" y="510" width="36" height="17" fill="#005f00" />
|
<text x="81" y="512" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||||
<text x="225" y="512" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
<rect x="126" y="510" width="117" height="17" fill="#005f00" />
|
||||||
<rect x="261" y="510" width="9" height="17" fill="#005f00" />
|
<text x="126" y="512" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine20 = </text>
|
||||||
<text x="261" y="512" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
<rect x="243" y="510" width="36" height="17" fill="#005f00" />
|
||||||
<text x="711" y="512" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="243" y="512" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||||
<text x="0" y="529" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<rect x="279" y="510" width="9" height="17" fill="#005f00" />
|
||||||
<text x="18" y="529" fill="#ffffff" textLength="162" lengthAdjust="spacingAndGlyphs">Apply this change?</text>
|
<text x="279" y="512" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||||
<text x="711" y="529" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="693" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="546" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="711" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="546" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="529" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="563" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="18" y="529" fill="#333333" textLength="684" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────╯</text>
|
||||||
<rect x="18" y="561" width="9" height="17" fill="#001a00" />
|
<text x="711" y="529" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="563" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
<text x="0" y="546" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="27" y="561" width="9" height="17" fill="#001a00" />
|
<text x="18" y="546" fill="#ffffff" textLength="162" lengthAdjust="spacingAndGlyphs">Apply this change?</text>
|
||||||
<rect x="36" y="561" width="18" height="17" fill="#001a00" />
|
<text x="711" y="546" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="36" y="563" fill="#00cd00" textLength="18" lengthAdjust="spacingAndGlyphs">1.</text>
|
<text x="0" y="563" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="54" y="561" width="9" height="17" fill="#001a00" />
|
<text x="711" y="563" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="63" y="561" width="90" height="17" fill="#001a00" />
|
<text x="0" y="580" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="63" y="563" fill="#00cd00" textLength="90" lengthAdjust="spacingAndGlyphs">Allow once</text>
|
<rect x="18" y="578" width="9" height="17" fill="#001a00" />
|
||||||
<rect x="153" y="561" width="153" height="17" fill="#001a00" />
|
<text x="18" y="580" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
||||||
<text x="711" y="563" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<rect x="27" y="578" width="9" height="17" fill="#001a00" />
|
||||||
<text x="0" y="580" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<rect x="36" y="578" width="18" height="17" fill="#001a00" />
|
||||||
<text x="36" y="580" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">2.</text>
|
<text x="36" y="580" fill="#00cd00" textLength="18" lengthAdjust="spacingAndGlyphs">1.</text>
|
||||||
<text x="63" y="580" fill="#ffffff" textLength="198" lengthAdjust="spacingAndGlyphs">Allow for this session</text>
|
<rect x="54" y="578" width="9" height="17" fill="#001a00" />
|
||||||
<text x="711" y="580" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<rect x="63" y="578" width="90" height="17" fill="#001a00" />
|
||||||
<text x="0" y="597" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="63" y="580" fill="#00cd00" textLength="90" lengthAdjust="spacingAndGlyphs">Allow once</text>
|
||||||
<text x="36" y="597" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">3.</text>
|
<rect x="153" y="578" width="153" height="17" fill="#001a00" />
|
||||||
<text x="63" y="597" fill="#ffffff" textLength="243" lengthAdjust="spacingAndGlyphs">Modify with external editor</text>
|
<text x="711" y="580" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="597" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="597" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="614" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="36" y="597" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">2.</text>
|
||||||
<text x="36" y="614" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">4.</text>
|
<text x="63" y="597" fill="#ffffff" textLength="198" lengthAdjust="spacingAndGlyphs">Allow for this session</text>
|
||||||
<text x="63" y="614" fill="#ffffff" textLength="225" lengthAdjust="spacingAndGlyphs">No, suggest changes (esc)</text>
|
<text x="711" y="597" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="614" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="614" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="631" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="36" y="614" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">3.</text>
|
||||||
<text x="711" y="631" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="63" y="614" fill="#ffffff" textLength="243" lengthAdjust="spacingAndGlyphs">Modify with external editor</text>
|
||||||
<text x="0" y="648" fill="#ffffaf" textLength="720" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────╯</text>
|
<text x="711" y="614" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="631" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="36" y="631" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">4.</text>
|
||||||
|
<text x="63" y="631" fill="#ffffff" textLength="225" lengthAdjust="spacingAndGlyphs">No, suggest changes (esc)</text>
|
||||||
|
<text x="711" y="631" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="648" fill="#333333" textLength="720" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────╯</text>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 46 KiB |
@@ -4,153 +4,217 @@
|
|||||||
</style>
|
</style>
|
||||||
<rect width="740" height="683" fill="#000000" />
|
<rect width="740" height="683" fill="#000000" />
|
||||||
<g transform="translate(10, 10)">
|
<g transform="translate(10, 10)">
|
||||||
<text x="0" y="2" fill="#ffffaf" textLength="720" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────╮</text>
|
<text x="0" y="2" fill="#333333" textLength="720" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────╮</text>
|
||||||
<text x="0" y="19" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="19" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="19" fill="#ffffaf" textLength="135" lengthAdjust="spacingAndGlyphs" font-weight="bold">Action Required</text>
|
<text x="18" y="19" fill="#ffffaf" textLength="81" lengthAdjust="spacingAndGlyphs" font-weight="bold">? Shell </text>
|
||||||
|
<text x="99" y="19" fill="#ffffff" textLength="207" lengthAdjust="spacingAndGlyphs">Executes a bash command</text>
|
||||||
<text x="648" y="19" fill="#afafaf" textLength="54" lengthAdjust="spacingAndGlyphs">2 of 3</text>
|
<text x="648" y="19" fill="#afafaf" textLength="54" lengthAdjust="spacingAndGlyphs">2 of 3</text>
|
||||||
<text x="711" y="19" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="711" y="19" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="36" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="36" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="36" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="18" y="36" fill="#333333" textLength="684" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────╮</text>
|
||||||
<text x="0" y="53" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="711" y="36" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="53" fill="#87afff" textLength="9" lengthAdjust="spacingAndGlyphs">?</text>
|
<text x="0" y="53" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="45" y="53" fill="#ffffff" textLength="153" lengthAdjust="spacingAndGlyphs" font-weight="bold">run_shell_command</text>
|
<text x="18" y="53" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="207" y="53" fill="#afafaf" textLength="207" lengthAdjust="spacingAndGlyphs">Executes a bash command</text>
|
<text x="36" y="53" fill="#afafaf" textLength="234" lengthAdjust="spacingAndGlyphs">... 22 hidden (Ctrl+O) ...</text>
|
||||||
<text x="711" y="53" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="693" y="53" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="70" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="711" y="53" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="70" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="87" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="18" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="87" fill="#afafaf" textLength="234" lengthAdjust="spacingAndGlyphs">... 24 hidden (Ctrl+O) ...</text>
|
<text x="36" y="70" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||||
<text x="711" y="87" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="81" y="70" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 23"</text>
|
||||||
<text x="0" y="104" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="693" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="104" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
<text x="711" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="63" y="104" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 25"</text>
|
<text x="0" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="104" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="18" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="121" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="36" y="87" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||||
<text x="18" y="121" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
<text x="81" y="87" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 24"</text>
|
||||||
<text x="63" y="121" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 26"</text>
|
<text x="693" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="121" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="711" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="138" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="138" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
<text x="18" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="63" y="138" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 27"</text>
|
<text x="36" y="104" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||||
<text x="711" y="138" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="81" y="104" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 25"</text>
|
||||||
<text x="0" y="155" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="693" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="155" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
<text x="711" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="63" y="155" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 28"</text>
|
<text x="0" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="155" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="18" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="172" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="36" y="121" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||||
<text x="18" y="172" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
<text x="81" y="121" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 26"</text>
|
||||||
<text x="63" y="172" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 29"</text>
|
<text x="693" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="172" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="711" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="189" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="189" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
<text x="18" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="63" y="189" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 30"</text>
|
<text x="36" y="138" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||||
<text x="711" y="189" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="81" y="138" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 27"</text>
|
||||||
<text x="0" y="206" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="693" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="206" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
<text x="711" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="63" y="206" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 31"</text>
|
<text x="0" y="155" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="206" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="18" y="155" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="223" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="36" y="155" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||||
<text x="18" y="223" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
<text x="81" y="155" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 28"</text>
|
||||||
<text x="63" y="223" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 32"</text>
|
<text x="693" y="155" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="223" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="711" y="155" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="240" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="240" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
<text x="18" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="63" y="240" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 33"</text>
|
<text x="36" y="172" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||||
<text x="711" y="240" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="81" y="172" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 29"</text>
|
||||||
<text x="0" y="257" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="693" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="257" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
<text x="711" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="63" y="257" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 34"</text>
|
<text x="0" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="257" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="18" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="274" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="36" y="189" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||||
<text x="18" y="274" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
<text x="81" y="189" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 30"</text>
|
||||||
<text x="63" y="274" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 35"</text>
|
<text x="693" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="274" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="711" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="291" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="291" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
<text x="18" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="63" y="291" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 36"</text>
|
<text x="36" y="206" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||||
<text x="711" y="291" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="81" y="206" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 31"</text>
|
||||||
<text x="0" y="308" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="693" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="308" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
<text x="711" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="63" y="308" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 37"</text>
|
<text x="0" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="308" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="18" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="325" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="36" y="223" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||||
<text x="18" y="325" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
<text x="81" y="223" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 32"</text>
|
||||||
<text x="63" y="325" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 38"</text>
|
<text x="693" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="325" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="711" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="342" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="342" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
<text x="18" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="63" y="342" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 39"</text>
|
<text x="36" y="240" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||||
<text x="711" y="342" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="81" y="240" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 33"</text>
|
||||||
<text x="0" y="359" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="693" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="359" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
<text x="711" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="63" y="359" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 40"</text>
|
<text x="0" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="359" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="18" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="376" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="36" y="257" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||||
<text x="18" y="376" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
<text x="81" y="257" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 34"</text>
|
||||||
<text x="63" y="376" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 41"</text>
|
<text x="693" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="376" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="711" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="393" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="393" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
<text x="18" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="63" y="393" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 42"</text>
|
<text x="36" y="274" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||||
<text x="711" y="393" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="81" y="274" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 35"</text>
|
||||||
<text x="0" y="410" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="693" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="410" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
<text x="711" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="63" y="410" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 43"</text>
|
<text x="0" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="410" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="18" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="427" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="36" y="291" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||||
<text x="18" y="427" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
<text x="81" y="291" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 36"</text>
|
||||||
<text x="63" y="427" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 44"</text>
|
<text x="693" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="427" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="711" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="444" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="444" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
<text x="18" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="63" y="444" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 45"</text>
|
<text x="36" y="308" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||||
<text x="711" y="444" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="81" y="308" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 37"</text>
|
||||||
<text x="0" y="461" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="693" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="461" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
<text x="711" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="63" y="461" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 46"</text>
|
<text x="0" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="461" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="18" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="478" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="36" y="325" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||||
<text x="18" y="478" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
<text x="81" y="325" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 38"</text>
|
||||||
<text x="63" y="478" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 47"</text>
|
<text x="693" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="478" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="711" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="495" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="495" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
<text x="18" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="63" y="495" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 48"</text>
|
<text x="36" y="342" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||||
<text x="711" y="495" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="81" y="342" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 39"</text>
|
||||||
<text x="0" y="512" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="693" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="512" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
<text x="711" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="63" y="512" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 49"</text>
|
<text x="0" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="512" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="18" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="529" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="36" y="359" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||||
<text x="18" y="529" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
<text x="81" y="359" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 40"</text>
|
||||||
<text x="63" y="529" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 50"</text>
|
<text x="693" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="529" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="711" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="546" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="546" fill="#ffffff" textLength="243" lengthAdjust="spacingAndGlyphs">Allow execution of: 'echo'?</text>
|
<text x="18" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="546" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="36" y="376" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||||
<text x="0" y="563" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="81" y="376" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 41"</text>
|
||||||
<text x="711" y="563" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="693" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="580" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="711" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="18" y="578" width="9" height="17" fill="#001a00" />
|
<text x="0" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="18" y="580" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
<text x="18" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="27" y="578" width="9" height="17" fill="#001a00" />
|
<text x="36" y="393" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||||
<rect x="36" y="578" width="18" height="17" fill="#001a00" />
|
<text x="81" y="393" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 42"</text>
|
||||||
<text x="36" y="580" fill="#00cd00" textLength="18" lengthAdjust="spacingAndGlyphs">1.</text>
|
<text x="693" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="54" y="578" width="9" height="17" fill="#001a00" />
|
<text x="711" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="63" y="578" width="90" height="17" fill="#001a00" />
|
<text x="0" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="63" y="580" fill="#00cd00" textLength="90" lengthAdjust="spacingAndGlyphs">Allow once</text>
|
<text x="18" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<rect x="153" y="578" width="135" height="17" fill="#001a00" />
|
<text x="36" y="410" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||||
<text x="711" y="580" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="81" y="410" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 43"</text>
|
||||||
<text x="0" y="597" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="693" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="36" y="597" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">2.</text>
|
<text x="711" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="63" y="597" fill="#ffffff" textLength="198" lengthAdjust="spacingAndGlyphs">Allow for this session</text>
|
<text x="0" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="597" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="18" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="614" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="36" y="427" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||||
<text x="36" y="614" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">3.</text>
|
<text x="81" y="427" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 44"</text>
|
||||||
<text x="63" y="614" fill="#ffffff" textLength="225" lengthAdjust="spacingAndGlyphs">No, suggest changes (esc)</text>
|
<text x="693" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="614" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="711" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="631" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="0" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="711" y="631" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
<text x="18" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
<text x="0" y="648" fill="#ffffaf" textLength="720" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────╯</text>
|
<text x="36" y="444" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||||
|
<text x="81" y="444" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 45"</text>
|
||||||
|
<text x="693" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="711" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="18" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="36" y="461" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||||
|
<text x="81" y="461" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 46"</text>
|
||||||
|
<text x="693" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="711" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="18" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="36" y="478" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||||
|
<text x="81" y="478" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 47"</text>
|
||||||
|
<text x="693" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="711" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="18" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="36" y="495" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||||
|
<text x="81" y="495" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 48"</text>
|
||||||
|
<text x="693" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="711" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="18" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="36" y="512" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||||
|
<text x="81" y="512" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 49"</text>
|
||||||
|
<text x="693" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="711" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="529" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="18" y="529" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="36" y="529" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||||
|
<text x="81" y="529" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 50"</text>
|
||||||
|
<text x="693" y="529" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="711" y="529" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="546" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="18" y="546" fill="#333333" textLength="684" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────╯</text>
|
||||||
|
<text x="711" y="546" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="563" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="9" y="563" fill="#ffffff" textLength="180" lengthAdjust="spacingAndGlyphs"> Allow execution of </text>
|
||||||
|
<text x="189" y="563" fill="#ffffaf" textLength="54" lengthAdjust="spacingAndGlyphs" font-weight="bold">[echo]</text>
|
||||||
|
<text x="243" y="563" fill="#ffffff" textLength="468" lengthAdjust="spacingAndGlyphs">? </text>
|
||||||
|
<text x="711" y="563" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="580" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="711" y="580" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="597" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<rect x="18" y="595" width="9" height="17" fill="#001a00" />
|
||||||
|
<text x="18" y="597" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
||||||
|
<rect x="27" y="595" width="9" height="17" fill="#001a00" />
|
||||||
|
<rect x="36" y="595" width="18" height="17" fill="#001a00" />
|
||||||
|
<text x="36" y="597" fill="#00cd00" textLength="18" lengthAdjust="spacingAndGlyphs">1.</text>
|
||||||
|
<rect x="54" y="595" width="9" height="17" fill="#001a00" />
|
||||||
|
<rect x="63" y="595" width="90" height="17" fill="#001a00" />
|
||||||
|
<text x="63" y="597" fill="#00cd00" textLength="90" lengthAdjust="spacingAndGlyphs">Allow once</text>
|
||||||
|
<rect x="153" y="595" width="135" height="17" fill="#001a00" />
|
||||||
|
<text x="711" y="597" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="614" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="36" y="614" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">2.</text>
|
||||||
|
<text x="63" y="614" fill="#ffffff" textLength="198" lengthAdjust="spacingAndGlyphs">Allow for this session</text>
|
||||||
|
<text x="711" y="614" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="631" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="36" y="631" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">3.</text>
|
||||||
|
<text x="63" y="631" fill="#ffffff" textLength="225" lengthAdjust="spacingAndGlyphs">No, suggest changes (esc)</text>
|
||||||
|
<text x="711" y="631" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||||
|
<text x="0" y="648" fill="#333333" textLength="720" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────╯</text>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 22 KiB |
@@ -2,32 +2,25 @@
|
|||||||
|
|
||||||
exports[`ToolConfirmationQueue > calculates availableContentHeight based on availableTerminalHeight from UI state 1`] = `
|
exports[`ToolConfirmationQueue > calculates availableContentHeight based on availableTerminalHeight from UI state 1`] = `
|
||||||
"╭──────────────────────────────────────────────────────────────────────────────╮
|
"╭──────────────────────────────────────────────────────────────────────────────╮
|
||||||
│ Action Required │
|
│ ? replace │
|
||||||
│ │
|
│ ╭──────────────────────────────────────────────────────────────────────────╮ │
|
||||||
│ ? replace edit file │
|
│ ╰─... 48 hidden (Ctrl+O) ...───────────────────────────────────────────────╯ │
|
||||||
│ │
|
|
||||||
│ ... 49 hidden (Ctrl+O) ... │
|
|
||||||
│ 50 line │
|
|
||||||
│ Apply this change? │
|
│ Apply this change? │
|
||||||
│ │
|
│ │
|
||||||
│ ● 1. Allow once │
|
│ ● 1. Allow once │
|
||||||
│ 2. Allow for this session │
|
│ 2. Allow for this session │
|
||||||
│ 3. Modify with external editor │
|
│ 3. Modify with external editor │
|
||||||
│ 4. No, suggest changes (esc) │
|
│ 4. No, suggest changes (esc) │
|
||||||
│ │
|
|
||||||
╰──────────────────────────────────────────────────────────────────────────────╯
|
╰──────────────────────────────────────────────────────────────────────────────╯
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`ToolConfirmationQueue > does not render expansion hint when constrainHeight is false 1`] = `
|
exports[`ToolConfirmationQueue > does not render expansion hint when constrainHeight is false 1`] = `
|
||||||
"╭──────────────────────────────────────────────────────────────────────────────╮
|
"╭──────────────────────────────────────────────────────────────────────────────╮
|
||||||
│ Action Required │
|
│ ? replace │
|
||||||
│ │
|
|
||||||
│ ? replace edit file │
|
|
||||||
│ │
|
|
||||||
│ ╭──────────────────────────────────────────────────────────────────────────╮ │
|
│ ╭──────────────────────────────────────────────────────────────────────────╮ │
|
||||||
│ │ │ │
|
│ │ │ │
|
||||||
│ │ No changes detected. │ │
|
│ │ No changes detected. │ │
|
||||||
│ │ │ │
|
│ │ │ │
|
||||||
│ ╰──────────────────────────────────────────────────────────────────────────╯ │
|
│ ╰──────────────────────────────────────────────────────────────────────────╯ │
|
||||||
│ Apply this change? │
|
│ Apply this change? │
|
||||||
@@ -36,131 +29,120 @@ exports[`ToolConfirmationQueue > does not render expansion hint when constrainHe
|
|||||||
│ 2. Allow for this session │
|
│ 2. Allow for this session │
|
||||||
│ 3. Modify with external editor │
|
│ 3. Modify with external editor │
|
||||||
│ 4. No, suggest changes (esc) │
|
│ 4. No, suggest changes (esc) │
|
||||||
│ │
|
|
||||||
╰──────────────────────────────────────────────────────────────────────────────╯
|
╰──────────────────────────────────────────────────────────────────────────────╯
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`ToolConfirmationQueue > height allocation and layout > should handle security warning height correctly 1`] = `
|
exports[`ToolConfirmationQueue > height allocation and layout > should handle security warning height correctly 1`] = `
|
||||||
"╭──────────────────────────────────────────────────────────────────────────────╮
|
"╭──────────────────────────────────────────────────────────────────────────────╮
|
||||||
│ Action Required 3 of 3 │
|
│ ? Shell Executes a bash command with a deceptive URL 3 of 3 │
|
||||||
│ │
|
|
||||||
│ ? run_shell_command Executes a bash command with a deceptive URL │
|
|
||||||
│ │
|
|
||||||
│ ... 6 hidden (Ctrl+O) ... │
|
│ ... 6 hidden (Ctrl+O) ... │
|
||||||
│ echo "Line 37" │
|
│ │ echo "Line 44" │ │
|
||||||
│ echo "Line 38" │
|
│ │ echo "Line 45" │ │
|
||||||
│ echo "Line 39" │
|
│ │ echo "Line 46" │ │
|
||||||
│ echo "Line 40" │
|
│ │ echo "Line 47" │ │
|
||||||
│ echo "Line 41" │
|
│ │ echo "Line 48" │ │
|
||||||
│ echo "Line 42" │
|
│ │ echo "Line 49" │ │
|
||||||
│ echo "Line 43" │
|
│ │ echo "Line 50" │ │
|
||||||
│ echo "Line 44" │
|
│ │ curl https://täst.com │ │
|
||||||
│ echo "Line 45" │
|
│ ╰──────────────────────────────────────────────────────────────────────────╯ │
|
||||||
│ echo "Line 46" │
|
|
||||||
│ echo "Line 47" │
|
|
||||||
│ echo "Line 48" │
|
|
||||||
│ echo "Line 49" │
|
|
||||||
│ echo "Line 50" │
|
|
||||||
│ curl https://täst.com │
|
|
||||||
│ │
|
│ │
|
||||||
│ ⚠ Warning: Deceptive URL(s) detected: │
|
│ ⚠ Warning: Deceptive URL(s) detected: │
|
||||||
│ │
|
│ │
|
||||||
│ Original: https://täst.com/ │
|
│ Original: https://täst.com/ │
|
||||||
│ Actual Host (Punycode): https://xn--tst-qla.com/ │
|
│ Actual Host (Punycode): https://xn--tst-qla.com/ │
|
||||||
│ │
|
│ │
|
||||||
│ Allow execution of: 'echo'? │
|
│ Allow execution of [echo]? │
|
||||||
│ │
|
│ │
|
||||||
│ ● 1. Allow once │
|
│ ● 1. Allow once │
|
||||||
│ 2. Allow for this session │
|
│ 2. Allow for this session │
|
||||||
│ 3. No, suggest changes (esc) │
|
│ 3. No, suggest changes (esc) │
|
||||||
│ │
|
|
||||||
╰──────────────────────────────────────────────────────────────────────────────╯
|
╰──────────────────────────────────────────────────────────────────────────────╯
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`ToolConfirmationQueue > height allocation and layout > should render the full queue wrapper with borders and content for large edit diffs 1`] = `
|
exports[`ToolConfirmationQueue > height allocation and layout > should render the full queue wrapper with borders and content for large edit diffs 1`] = `
|
||||||
"╭──────────────────────────────────────────────────────────────────────────────╮
|
"╭──────────────────────────────────────────────────────────────────────────────╮
|
||||||
│ Action Required │
|
│ ? replace │
|
||||||
│ │
|
│ ╭──────────────────────────────────────────────────────────────────────────╮ │
|
||||||
│ ? replace Replaces content in a file │
|
│ │ ... 13 hidden (Ctrl+O) ... │ │
|
||||||
│ │
|
│ │ 7 + const newLine7 = true; │ │
|
||||||
│ ... 15 hidden (Ctrl+O) ... │
|
│ │ 8 - const oldLine8 = true; │ │
|
||||||
│ 8 + const newLine8 = true; │
|
│ │ 8 + const newLine8 = true; │ │
|
||||||
│ 9 - const oldLine9 = true; │
|
│ │ 9 - const oldLine9 = true; │ │
|
||||||
│ 9 + const newLine9 = true; │
|
│ │ 9 + const newLine9 = true; │ │
|
||||||
│ 10 - const oldLine10 = true; │
|
│ │ 10 - const oldLine10 = true; │ │
|
||||||
│ 10 + const newLine10 = true; │
|
│ │ 10 + const newLine10 = true; │ │
|
||||||
│ 11 - const oldLine11 = true; │
|
│ │ 11 - const oldLine11 = true; │ │
|
||||||
│ 11 + const newLine11 = true; │
|
│ │ 11 + const newLine11 = true; │ │
|
||||||
│ 12 - const oldLine12 = true; │
|
│ │ 12 - const oldLine12 = true; │ │
|
||||||
│ 12 + const newLine12 = true; │
|
│ │ 12 + const newLine12 = true; │ │
|
||||||
│ 13 - const oldLine13 = true; │
|
│ │ 13 - const oldLine13 = true; │ │
|
||||||
│ 13 + const newLine13 = true; │
|
│ │ 13 + const newLine13 = true; │ │
|
||||||
│ 14 - const oldLine14 = true; │
|
│ │ 14 - const oldLine14 = true; │ │
|
||||||
│ 14 + const newLine14 = true; │
|
│ │ 14 + const newLine14 = true; │ │
|
||||||
│ 15 - const oldLine15 = true; │
|
│ │ 15 - const oldLine15 = true; │ │
|
||||||
│ 15 + const newLine15 = true; │
|
│ │ 15 + const newLine15 = true; │ │
|
||||||
│ 16 - const oldLine16 = true; │
|
│ │ 16 - const oldLine16 = true; │ │
|
||||||
│ 16 + const newLine16 = true; │
|
│ │ 16 + const newLine16 = true; │ │
|
||||||
│ 17 - const oldLine17 = true; │
|
│ │ 17 - const oldLine17 = true; │ │
|
||||||
│ 17 + const newLine17 = true; │
|
│ │ 17 + const newLine17 = true; │ │
|
||||||
│ 18 - const oldLine18 = true; │
|
│ │ 18 - const oldLine18 = true; │ │
|
||||||
│ 18 + const newLine18 = true; │
|
│ │ 18 + const newLine18 = true; │ │
|
||||||
│ 19 - const oldLine19 = true; │
|
│ │ 19 - const oldLine19 = true; │ │
|
||||||
│ 19 + const newLine19 = true; │
|
│ │ 19 + const newLine19 = true; │ │
|
||||||
│ 20 - const oldLine20 = true; │
|
│ │ 20 - const oldLine20 = true; │ │
|
||||||
│ 20 + const newLine20 = true; │
|
│ │ 20 + const newLine20 = true; │ │
|
||||||
|
│ ╰──────────────────────────────────────────────────────────────────────────╯ │
|
||||||
│ Apply this change? │
|
│ Apply this change? │
|
||||||
│ │
|
│ │
|
||||||
│ ● 1. Allow once │
|
│ ● 1. Allow once │
|
||||||
│ 2. Allow for this session │
|
│ 2. Allow for this session │
|
||||||
│ 3. Modify with external editor │
|
│ 3. Modify with external editor │
|
||||||
│ 4. No, suggest changes (esc) │
|
│ 4. No, suggest changes (esc) │
|
||||||
│ │
|
|
||||||
╰──────────────────────────────────────────────────────────────────────────────╯
|
╰──────────────────────────────────────────────────────────────────────────────╯
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`ToolConfirmationQueue > height allocation and layout > should render the full queue wrapper with borders and content for large exec commands 1`] = `
|
exports[`ToolConfirmationQueue > height allocation and layout > should render the full queue wrapper with borders and content for large exec commands 1`] = `
|
||||||
"╭──────────────────────────────────────────────────────────────────────────────╮
|
"╭──────────────────────────────────────────────────────────────────────────────╮
|
||||||
│ Action Required 2 of 3 │
|
│ ? Shell Executes a bash command 2 of 3 │
|
||||||
│ │
|
│ ╭──────────────────────────────────────────────────────────────────────────╮ │
|
||||||
│ ? run_shell_command Executes a bash command │
|
│ │ ... 22 hidden (Ctrl+O) ... │ │
|
||||||
│ │
|
│ │ echo "Line 23" │ │
|
||||||
│ ... 24 hidden (Ctrl+O) ... │
|
│ │ echo "Line 24" │ │
|
||||||
│ echo "Line 25" │
|
│ │ echo "Line 25" │ │
|
||||||
│ echo "Line 26" │
|
│ │ echo "Line 26" │ │
|
||||||
│ echo "Line 27" │
|
│ │ echo "Line 27" │ │
|
||||||
│ echo "Line 28" │
|
│ │ echo "Line 28" │ │
|
||||||
│ echo "Line 29" │
|
│ │ echo "Line 29" │ │
|
||||||
│ echo "Line 30" │
|
│ │ echo "Line 30" │ │
|
||||||
│ echo "Line 31" │
|
│ │ echo "Line 31" │ │
|
||||||
│ echo "Line 32" │
|
│ │ echo "Line 32" │ │
|
||||||
│ echo "Line 33" │
|
│ │ echo "Line 33" │ │
|
||||||
│ echo "Line 34" │
|
│ │ echo "Line 34" │ │
|
||||||
│ echo "Line 35" │
|
│ │ echo "Line 35" │ │
|
||||||
│ echo "Line 36" │
|
│ │ echo "Line 36" │ │
|
||||||
│ echo "Line 37" │
|
│ │ echo "Line 37" │ │
|
||||||
│ echo "Line 38" │
|
│ │ echo "Line 38" │ │
|
||||||
│ echo "Line 39" │
|
│ │ echo "Line 39" │ │
|
||||||
│ echo "Line 40" │
|
│ │ echo "Line 40" │ │
|
||||||
│ echo "Line 41" │
|
│ │ echo "Line 41" │ │
|
||||||
│ echo "Line 42" │
|
│ │ echo "Line 42" │ │
|
||||||
│ echo "Line 43" │
|
│ │ echo "Line 43" │ │
|
||||||
│ echo "Line 44" │
|
│ │ echo "Line 44" │ │
|
||||||
│ echo "Line 45" │
|
│ │ echo "Line 45" │ │
|
||||||
│ echo "Line 46" │
|
│ │ echo "Line 46" │ │
|
||||||
│ echo "Line 47" │
|
│ │ echo "Line 47" │ │
|
||||||
│ echo "Line 48" │
|
│ │ echo "Line 48" │ │
|
||||||
│ echo "Line 49" │
|
│ │ echo "Line 49" │ │
|
||||||
│ echo "Line 50" │
|
│ │ echo "Line 50" │ │
|
||||||
│ Allow execution of: 'echo'? │
|
│ ╰──────────────────────────────────────────────────────────────────────────╯ │
|
||||||
|
│ Allow execution of [echo]? │
|
||||||
│ │
|
│ │
|
||||||
│ ● 1. Allow once │
|
│ ● 1. Allow once │
|
||||||
│ 2. Allow for this session │
|
│ 2. Allow for this session │
|
||||||
│ 3. No, suggest changes (esc) │
|
│ 3. No, suggest changes (esc) │
|
||||||
│ │
|
|
||||||
╰──────────────────────────────────────────────────────────────────────────────╯
|
╰──────────────────────────────────────────────────────────────────────────────╯
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
@@ -209,24 +191,22 @@ exports[`ToolConfirmationQueue > renders ExitPlanMode tool confirmation with Suc
|
|||||||
│ Approves plan but requires confirmation for each tool │
|
│ Approves plan but requires confirmation for each tool │
|
||||||
│ 3. Type your feedback... │
|
│ 3. Type your feedback... │
|
||||||
│ │
|
│ │
|
||||||
│ Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel │
|
│ Enter to select · ↑/↓ to navigate · Ctrl+G to edit plan · Esc to cancel │
|
||||||
╰──────────────────────────────────────────────────────────────────────────────╯
|
╰──────────────────────────────────────────────────────────────────────────────╯
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`ToolConfirmationQueue > renders the confirming tool with progress indicator 1`] = `
|
exports[`ToolConfirmationQueue > renders the confirming tool with progress indicator 1`] = `
|
||||||
"╭──────────────────────────────────────────────────────────────────────────────╮
|
"╭──────────────────────────────────────────────────────────────────────────────╮
|
||||||
│ Action Required 1 of 3 │
|
│ ? Shell list files 1 of 3 │
|
||||||
│ │
|
│ ╭──────────────────────────────────────────────────────────────────────────╮ │
|
||||||
│ ? ls list files │
|
│ │ ls │ │
|
||||||
│ │
|
│ ╰──────────────────────────────────────────────────────────────────────────╯ │
|
||||||
│ ls │
|
│ Allow execution of [ls]? │
|
||||||
│ Allow execution of: 'ls'? │
|
|
||||||
│ │
|
│ │
|
||||||
│ ● 1. Allow once │
|
│ ● 1. Allow once │
|
||||||
│ 2. Allow for this session │
|
│ 2. Allow for this session │
|
||||||
│ 3. No, suggest changes (esc) │
|
│ 3. No, suggest changes (esc) │
|
||||||
│ │
|
|
||||||
╰──────────────────────────────────────────────────────────────────────────────╯
|
╰──────────────────────────────────────────────────────────────────────────────╯
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -48,15 +48,18 @@ index 0000000..e69de29
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
await waitFor(() =>
|
await waitFor(() =>
|
||||||
expect(mockColorizeCode).toHaveBeenCalledWith({
|
expect(mockColorizeCode).toHaveBeenCalledWith(
|
||||||
code: 'print("hello world")',
|
expect.objectContaining({
|
||||||
language: 'python',
|
code: 'print("hello world")',
|
||||||
availableHeight: undefined,
|
language: 'python',
|
||||||
maxWidth: 80,
|
availableHeight: undefined,
|
||||||
theme: undefined,
|
maxWidth: 80,
|
||||||
settings: expect.anything(),
|
theme: undefined,
|
||||||
disableColor: false,
|
settings: expect.anything(),
|
||||||
}),
|
disableColor: false,
|
||||||
|
paddingX: 0,
|
||||||
|
}),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -83,15 +86,18 @@ index 0000000..e69de29
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
await waitFor(() =>
|
await waitFor(() =>
|
||||||
expect(mockColorizeCode).toHaveBeenCalledWith({
|
expect(mockColorizeCode).toHaveBeenCalledWith(
|
||||||
code: 'some content',
|
expect.objectContaining({
|
||||||
language: null,
|
code: 'some content',
|
||||||
availableHeight: undefined,
|
language: null,
|
||||||
maxWidth: 80,
|
availableHeight: undefined,
|
||||||
theme: undefined,
|
maxWidth: 80,
|
||||||
settings: expect.anything(),
|
theme: undefined,
|
||||||
disableColor: false,
|
settings: expect.anything(),
|
||||||
}),
|
disableColor: false,
|
||||||
|
paddingX: 0,
|
||||||
|
}),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -114,15 +120,18 @@ index 0000000..e69de29
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
await waitFor(() =>
|
await waitFor(() =>
|
||||||
expect(mockColorizeCode).toHaveBeenCalledWith({
|
expect(mockColorizeCode).toHaveBeenCalledWith(
|
||||||
code: 'some text content',
|
expect.objectContaining({
|
||||||
language: null,
|
code: 'some text content',
|
||||||
availableHeight: undefined,
|
language: null,
|
||||||
maxWidth: 80,
|
availableHeight: undefined,
|
||||||
theme: undefined,
|
maxWidth: 80,
|
||||||
settings: expect.anything(),
|
theme: undefined,
|
||||||
disableColor: false,
|
settings: expect.anything(),
|
||||||
}),
|
disableColor: false,
|
||||||
|
paddingX: 0,
|
||||||
|
}),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export function parseDiffWithLineNumbers(diffContent: string): DiffLine[] {
|
|||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
const hunkMatch = line.match(hunkHeaderRegex);
|
const hunkMatch = line.match(hunkHeaderRegex);
|
||||||
if (hunkMatch) {
|
if (hunkMatch) {
|
||||||
|
currentOldLine = parseInt(hunkMatch[1], 10);
|
||||||
currentOldLine = parseInt(hunkMatch[1], 10);
|
currentOldLine = parseInt(hunkMatch[1], 10);
|
||||||
currentNewLine = parseInt(hunkMatch[2], 10);
|
currentNewLine = parseInt(hunkMatch[2], 10);
|
||||||
inHunk = true;
|
inHunk = true;
|
||||||
@@ -89,6 +90,7 @@ interface DiffRendererProps {
|
|||||||
terminalWidth: number;
|
terminalWidth: number;
|
||||||
theme?: Theme;
|
theme?: Theme;
|
||||||
disableColor?: boolean;
|
disableColor?: boolean;
|
||||||
|
paddingX?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_TAB_WIDTH = 4; // Spaces per tab for normalization
|
const DEFAULT_TAB_WIDTH = 4; // Spaces per tab for normalization
|
||||||
@@ -101,6 +103,7 @@ export const DiffRenderer: React.FC<DiffRendererProps> = ({
|
|||||||
terminalWidth,
|
terminalWidth,
|
||||||
theme,
|
theme,
|
||||||
disableColor = false,
|
disableColor = false,
|
||||||
|
paddingX = 0,
|
||||||
}) => {
|
}) => {
|
||||||
const settings = useSettings();
|
const settings = useSettings();
|
||||||
|
|
||||||
@@ -122,11 +125,7 @@ export const DiffRenderer: React.FC<DiffRendererProps> = ({
|
|||||||
|
|
||||||
if (parsedLines.length === 0) {
|
if (parsedLines.length === 0) {
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box padding={1}>
|
||||||
borderStyle="round"
|
|
||||||
borderColor={semanticTheme.border.default}
|
|
||||||
padding={1}
|
|
||||||
>
|
|
||||||
<Text dimColor>No changes detected.</Text>
|
<Text dimColor>No changes detected.</Text>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
@@ -162,12 +161,14 @@ export const DiffRenderer: React.FC<DiffRendererProps> = ({
|
|||||||
theme,
|
theme,
|
||||||
settings,
|
settings,
|
||||||
disableColor,
|
disableColor,
|
||||||
|
paddingX,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const key = filename ? `diff-box-${filename}` : undefined;
|
const key = filename ? `diff-box-${filename}` : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MaxSizedBox
|
<MaxSizedBox
|
||||||
|
paddingX={paddingX}
|
||||||
maxHeight={availableTerminalHeight}
|
maxHeight={availableTerminalHeight}
|
||||||
maxWidth={terminalWidth}
|
maxWidth={terminalWidth}
|
||||||
key={key}
|
key={key}
|
||||||
@@ -194,6 +195,7 @@ export const DiffRenderer: React.FC<DiffRendererProps> = ({
|
|||||||
settings,
|
settings,
|
||||||
tabWidth,
|
tabWidth,
|
||||||
disableColor,
|
disableColor,
|
||||||
|
paddingX,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return renderedOutput;
|
return renderedOutput;
|
||||||
@@ -239,12 +241,7 @@ export const renderDiffLines = ({
|
|||||||
|
|
||||||
if (displayableLines.length === 0) {
|
if (displayableLines.length === 0) {
|
||||||
return [
|
return [
|
||||||
<Box
|
<Box key="no-changes" padding={1}>
|
||||||
key="no-changes"
|
|
||||||
borderStyle="round"
|
|
||||||
borderColor={semanticTheme.border.default}
|
|
||||||
padding={1}
|
|
||||||
>
|
|
||||||
<Text dimColor>No changes detected.</Text>
|
<Text dimColor>No changes detected.</Text>
|
||||||
</Box>,
|
</Box>,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { RenderInline } from '../../utils/InlineMarkdownRenderer.js';
|
|||||||
interface InfoMessageProps {
|
interface InfoMessageProps {
|
||||||
text: string;
|
text: string;
|
||||||
secondaryText?: string;
|
secondaryText?: string;
|
||||||
|
source?: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
color?: string;
|
color?: string;
|
||||||
marginBottom?: number;
|
marginBottom?: number;
|
||||||
@@ -20,6 +21,7 @@ interface InfoMessageProps {
|
|||||||
export const InfoMessage: React.FC<InfoMessageProps> = ({
|
export const InfoMessage: React.FC<InfoMessageProps> = ({
|
||||||
text,
|
text,
|
||||||
secondaryText,
|
secondaryText,
|
||||||
|
source,
|
||||||
icon,
|
icon,
|
||||||
color,
|
color,
|
||||||
marginBottom,
|
marginBottom,
|
||||||
@@ -40,6 +42,9 @@ export const InfoMessage: React.FC<InfoMessageProps> = ({
|
|||||||
{index === text.split('\n').length - 1 && secondaryText && (
|
{index === text.split('\n').length - 1 && secondaryText && (
|
||||||
<Text color={theme.text.secondary}> {secondaryText}</Text>
|
<Text color={theme.text.secondary}> {secondaryText}</Text>
|
||||||
)}
|
)}
|
||||||
|
{index === text.split('\n').length - 1 && source && (
|
||||||
|
<Text color={theme.text.secondary}> [{source}]</Text>
|
||||||
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ describe('ToolConfirmationMessage Redirection', () => {
|
|||||||
getPreferredEditor={vi.fn()}
|
getPreferredEditor={vi.fn()}
|
||||||
availableTerminalHeight={30}
|
availableTerminalHeight={30}
|
||||||
terminalWidth={100}
|
terminalWidth={100}
|
||||||
|
toolName="shell"
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -293,8 +293,8 @@ describe('<ShellToolMessage />', () => {
|
|||||||
await waitUntilReady();
|
await waitUntilReady();
|
||||||
const frame = lastFrame();
|
const frame = lastFrame();
|
||||||
// Since it's Executing, it might still constrain to ACTIVE_SHELL_MAX_LINES (10)
|
// Since it's Executing, it might still constrain to ACTIVE_SHELL_MAX_LINES (10)
|
||||||
// Actually let's just assert on the behaviour that happens right now (which is 10 lines)
|
// Actually let's just assert on the behaviour that happens right now (which is 100 lines because we removed the terminalBuffer check)
|
||||||
expect(frame.match(/Line \d+/g)?.length).toBe(10);
|
expect(frame.match(/Line \d+/g)?.length).toBe(100);
|
||||||
unmount();
|
unmount();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ describe('ToolConfirmationMessage', () => {
|
|||||||
getPreferredEditor={vi.fn()}
|
getPreferredEditor={vi.fn()}
|
||||||
availableTerminalHeight={30}
|
availableTerminalHeight={30}
|
||||||
terminalWidth={80}
|
terminalWidth={80}
|
||||||
|
toolName="shell"
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -88,6 +89,7 @@ describe('ToolConfirmationMessage', () => {
|
|||||||
getPreferredEditor={vi.fn()}
|
getPreferredEditor={vi.fn()}
|
||||||
availableTerminalHeight={30}
|
availableTerminalHeight={30}
|
||||||
terminalWidth={80}
|
terminalWidth={80}
|
||||||
|
toolName="shell"
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -111,6 +113,7 @@ describe('ToolConfirmationMessage', () => {
|
|||||||
getPreferredEditor={vi.fn()}
|
getPreferredEditor={vi.fn()}
|
||||||
availableTerminalHeight={30}
|
availableTerminalHeight={30}
|
||||||
terminalWidth={80}
|
terminalWidth={80}
|
||||||
|
toolName="shell"
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -140,6 +143,7 @@ describe('ToolConfirmationMessage', () => {
|
|||||||
getPreferredEditor={vi.fn()}
|
getPreferredEditor={vi.fn()}
|
||||||
availableTerminalHeight={30}
|
availableTerminalHeight={30}
|
||||||
terminalWidth={80}
|
terminalWidth={80}
|
||||||
|
toolName="shell"
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -169,6 +173,7 @@ describe('ToolConfirmationMessage', () => {
|
|||||||
getPreferredEditor={vi.fn()}
|
getPreferredEditor={vi.fn()}
|
||||||
availableTerminalHeight={30}
|
availableTerminalHeight={30}
|
||||||
terminalWidth={80}
|
terminalWidth={80}
|
||||||
|
toolName="shell"
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -197,6 +202,7 @@ describe('ToolConfirmationMessage', () => {
|
|||||||
getPreferredEditor={vi.fn()}
|
getPreferredEditor={vi.fn()}
|
||||||
availableTerminalHeight={30}
|
availableTerminalHeight={30}
|
||||||
terminalWidth={80}
|
terminalWidth={80}
|
||||||
|
toolName="shell"
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -225,6 +231,7 @@ describe('ToolConfirmationMessage', () => {
|
|||||||
getPreferredEditor={vi.fn()}
|
getPreferredEditor={vi.fn()}
|
||||||
availableTerminalHeight={30}
|
availableTerminalHeight={30}
|
||||||
terminalWidth={80}
|
terminalWidth={80}
|
||||||
|
toolName="shell"
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -253,6 +260,7 @@ describe('ToolConfirmationMessage', () => {
|
|||||||
getPreferredEditor={vi.fn()}
|
getPreferredEditor={vi.fn()}
|
||||||
availableTerminalHeight={30}
|
availableTerminalHeight={30}
|
||||||
terminalWidth={80}
|
terminalWidth={80}
|
||||||
|
toolName="shell"
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
await result.waitUntilReady();
|
await result.waitUntilReady();
|
||||||
@@ -338,6 +346,7 @@ describe('ToolConfirmationMessage', () => {
|
|||||||
getPreferredEditor={vi.fn()}
|
getPreferredEditor={vi.fn()}
|
||||||
availableTerminalHeight={30}
|
availableTerminalHeight={30}
|
||||||
terminalWidth={80}
|
terminalWidth={80}
|
||||||
|
toolName="shell"
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -361,6 +370,7 @@ describe('ToolConfirmationMessage', () => {
|
|||||||
getPreferredEditor={vi.fn()}
|
getPreferredEditor={vi.fn()}
|
||||||
availableTerminalHeight={30}
|
availableTerminalHeight={30}
|
||||||
terminalWidth={80}
|
terminalWidth={80}
|
||||||
|
toolName="shell"
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -396,6 +406,7 @@ describe('ToolConfirmationMessage', () => {
|
|||||||
getPreferredEditor={vi.fn()}
|
getPreferredEditor={vi.fn()}
|
||||||
availableTerminalHeight={30}
|
availableTerminalHeight={30}
|
||||||
terminalWidth={80}
|
terminalWidth={80}
|
||||||
|
toolName="shell"
|
||||||
/>,
|
/>,
|
||||||
{
|
{
|
||||||
settings: createMockSettings({
|
settings: createMockSettings({
|
||||||
@@ -423,6 +434,7 @@ describe('ToolConfirmationMessage', () => {
|
|||||||
getPreferredEditor={vi.fn()}
|
getPreferredEditor={vi.fn()}
|
||||||
availableTerminalHeight={30}
|
availableTerminalHeight={30}
|
||||||
terminalWidth={80}
|
terminalWidth={80}
|
||||||
|
toolName="shell"
|
||||||
/>,
|
/>,
|
||||||
{
|
{
|
||||||
settings: createMockSettings({
|
settings: createMockSettings({
|
||||||
@@ -474,6 +486,7 @@ describe('ToolConfirmationMessage', () => {
|
|||||||
getPreferredEditor={vi.fn()}
|
getPreferredEditor={vi.fn()}
|
||||||
availableTerminalHeight={30}
|
availableTerminalHeight={30}
|
||||||
terminalWidth={80}
|
terminalWidth={80}
|
||||||
|
toolName="shell"
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -505,6 +518,7 @@ describe('ToolConfirmationMessage', () => {
|
|||||||
getPreferredEditor={vi.fn()}
|
getPreferredEditor={vi.fn()}
|
||||||
availableTerminalHeight={30}
|
availableTerminalHeight={30}
|
||||||
terminalWidth={80}
|
terminalWidth={80}
|
||||||
|
toolName="shell"
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -536,6 +550,7 @@ describe('ToolConfirmationMessage', () => {
|
|||||||
getPreferredEditor={vi.fn()}
|
getPreferredEditor={vi.fn()}
|
||||||
availableTerminalHeight={30}
|
availableTerminalHeight={30}
|
||||||
terminalWidth={80}
|
terminalWidth={80}
|
||||||
|
toolName="shell"
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -562,6 +577,7 @@ describe('ToolConfirmationMessage', () => {
|
|||||||
getPreferredEditor={vi.fn()}
|
getPreferredEditor={vi.fn()}
|
||||||
availableTerminalHeight={30}
|
availableTerminalHeight={30}
|
||||||
terminalWidth={80}
|
terminalWidth={80}
|
||||||
|
toolName="shell"
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -607,6 +623,7 @@ describe('ToolConfirmationMessage', () => {
|
|||||||
getPreferredEditor={vi.fn()}
|
getPreferredEditor={vi.fn()}
|
||||||
availableTerminalHeight={30}
|
availableTerminalHeight={30}
|
||||||
terminalWidth={80}
|
terminalWidth={80}
|
||||||
|
toolName="shell"
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -638,6 +655,7 @@ describe('ToolConfirmationMessage', () => {
|
|||||||
getPreferredEditor={vi.fn()}
|
getPreferredEditor={vi.fn()}
|
||||||
availableTerminalHeight={30}
|
availableTerminalHeight={30}
|
||||||
terminalWidth={80}
|
terminalWidth={80}
|
||||||
|
toolName="shell"
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -672,13 +690,14 @@ describe('ToolConfirmationMessage', () => {
|
|||||||
getPreferredEditor={vi.fn()}
|
getPreferredEditor={vi.fn()}
|
||||||
availableTerminalHeight={40}
|
availableTerminalHeight={40}
|
||||||
terminalWidth={80}
|
terminalWidth={80}
|
||||||
|
toolName="shell"
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
await waitUntilReady();
|
await waitUntilReady();
|
||||||
|
|
||||||
const outputLines = lastFrame().split('\n');
|
const outputLines = lastFrame().split('\n');
|
||||||
// Should use the entire terminal height minus 1 line for the "Press Ctrl+O to show more lines" hint
|
// Should use the entire terminal height
|
||||||
expect(outputLines.length).toBe(39);
|
expect(outputLines.length).toBe(40);
|
||||||
|
|
||||||
await expect({ lastFrame, generateSvg }).toMatchSvgSnapshot();
|
await expect({ lastFrame, generateSvg }).toMatchSvgSnapshot();
|
||||||
unmount();
|
unmount();
|
||||||
@@ -712,13 +731,14 @@ describe('ToolConfirmationMessage', () => {
|
|||||||
getPreferredEditor={vi.fn()}
|
getPreferredEditor={vi.fn()}
|
||||||
availableTerminalHeight={40}
|
availableTerminalHeight={40}
|
||||||
terminalWidth={80}
|
terminalWidth={80}
|
||||||
|
toolName="shell"
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
await waitUntilReady();
|
await waitUntilReady();
|
||||||
|
|
||||||
const outputLines = lastFrame().split('\n');
|
const outputLines = lastFrame().split('\n');
|
||||||
// Should use the entire terminal height minus 1 line for the "Press Ctrl+O to show more lines" hint
|
// Should use the entire terminal height
|
||||||
expect(outputLines.length).toBe(39);
|
expect(outputLines.length).toBe(40);
|
||||||
|
|
||||||
await expect({ lastFrame, generateSvg }).toMatchSvgSnapshot();
|
await expect({ lastFrame, generateSvg }).toMatchSvgSnapshot();
|
||||||
unmount();
|
unmount();
|
||||||
@@ -761,6 +781,7 @@ describe('ToolConfirmationMessage', () => {
|
|||||||
getPreferredEditor={vi.fn()}
|
getPreferredEditor={vi.fn()}
|
||||||
availableTerminalHeight={30}
|
availableTerminalHeight={30}
|
||||||
terminalWidth={80}
|
terminalWidth={80}
|
||||||
|
toolName="shell"
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -24,13 +24,14 @@ import {
|
|||||||
RadioButtonSelect,
|
RadioButtonSelect,
|
||||||
type RadioSelectItem,
|
type RadioSelectItem,
|
||||||
} from '../shared/RadioButtonSelect.js';
|
} from '../shared/RadioButtonSelect.js';
|
||||||
import { MaxSizedBox, MINIMUM_MAX_HEIGHT } from '../shared/MaxSizedBox.js';
|
import { MaxSizedBox } from '../shared/MaxSizedBox.js';
|
||||||
import {
|
import {
|
||||||
sanitizeForDisplay,
|
sanitizeForDisplay,
|
||||||
stripUnsafeCharacters,
|
stripUnsafeCharacters,
|
||||||
} from '../../utils/textUtils.js';
|
} from '../../utils/textUtils.js';
|
||||||
import { useKeypress } from '../../hooks/useKeypress.js';
|
import { useKeypress } from '../../hooks/useKeypress.js';
|
||||||
import { theme } from '../../semantic-colors.js';
|
import { theme } from '../../semantic-colors.js';
|
||||||
|
import { themeManager } from '../../themes/theme-manager.js';
|
||||||
import { useSettings } from '../../contexts/SettingsContext.js';
|
import { useSettings } from '../../contexts/SettingsContext.js';
|
||||||
import { Command } from '../../key/keyMatchers.js';
|
import { Command } from '../../key/keyMatchers.js';
|
||||||
import { formatCommand } from '../../key/keybindingUtils.js';
|
import { formatCommand } from '../../key/keybindingUtils.js';
|
||||||
@@ -44,6 +45,7 @@ import {
|
|||||||
type DeceptiveUrlDetails,
|
type DeceptiveUrlDetails,
|
||||||
} from '../../utils/urlSecurityUtils.js';
|
} from '../../utils/urlSecurityUtils.js';
|
||||||
import { useKeyMatchers } from '../../hooks/useKeyMatchers.js';
|
import { useKeyMatchers } from '../../hooks/useKeyMatchers.js';
|
||||||
|
import { isShellTool } from './ToolShared.js';
|
||||||
|
|
||||||
export interface ToolConfirmationMessageProps {
|
export interface ToolConfirmationMessageProps {
|
||||||
callId: string;
|
callId: string;
|
||||||
@@ -53,13 +55,9 @@ export interface ToolConfirmationMessageProps {
|
|||||||
isFocused?: boolean;
|
isFocused?: boolean;
|
||||||
availableTerminalHeight?: number;
|
availableTerminalHeight?: number;
|
||||||
terminalWidth: number;
|
terminalWidth: number;
|
||||||
|
toolName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const REDIRECTION_WARNING_NOTE_LABEL = 'Note: ';
|
|
||||||
const REDIRECTION_WARNING_NOTE_TEXT =
|
|
||||||
'Command contains redirection which can be undesirable.';
|
|
||||||
const REDIRECTION_WARNING_TIP_LABEL = 'Tip: '; // Padded to align with "Note: "
|
|
||||||
|
|
||||||
export const ToolConfirmationMessage: React.FC<
|
export const ToolConfirmationMessage: React.FC<
|
||||||
ToolConfirmationMessageProps
|
ToolConfirmationMessageProps
|
||||||
> = ({
|
> = ({
|
||||||
@@ -70,6 +68,7 @@ export const ToolConfirmationMessage: React.FC<
|
|||||||
isFocused = true,
|
isFocused = true,
|
||||||
availableTerminalHeight,
|
availableTerminalHeight,
|
||||||
terminalWidth,
|
terminalWidth,
|
||||||
|
toolName,
|
||||||
}) => {
|
}) => {
|
||||||
const keyMatchers = useKeyMatchers();
|
const keyMatchers = useKeyMatchers();
|
||||||
const { confirm, isDiffingEnabled } = useToolActions();
|
const { confirm, isDiffingEnabled } = useToolActions();
|
||||||
@@ -152,6 +151,7 @@ export const ToolConfirmationMessage: React.FC<
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const settings = useSettings();
|
const settings = useSettings();
|
||||||
|
const activeTheme = themeManager.getActiveTheme();
|
||||||
const allowPermanentApproval =
|
const allowPermanentApproval =
|
||||||
settings.merged.security.enablePermanentToolApproval &&
|
settings.merged.security.enablePermanentToolApproval &&
|
||||||
!config.getDisableAlwaysAllow();
|
!config.getDisableAlwaysAllow();
|
||||||
@@ -254,8 +254,6 @@ export const ToolConfirmationMessage: React.FC<
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (keyMatchers[Command.QUIT](key)) {
|
if (keyMatchers[Command.QUIT](key)) {
|
||||||
// Return false to let ctrl-C bubble up to AppContainer for exit flow.
|
|
||||||
// AppContainer will call cancelOngoingRequest which will cancel the tool.
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -398,7 +396,6 @@ export const ToolConfirmationMessage: React.FC<
|
|||||||
key: 'No, suggest changes (esc)',
|
key: 'No, suggest changes (esc)',
|
||||||
});
|
});
|
||||||
} else if (confirmationDetails.type === 'mcp') {
|
} else if (confirmationDetails.type === 'mcp') {
|
||||||
// mcp tool confirmation
|
|
||||||
options.push({
|
options.push({
|
||||||
label: 'Allow once',
|
label: 'Allow once',
|
||||||
value: ToolConfirmationOutcome.ProceedOnce,
|
value: ToolConfirmationOutcome.ProceedOnce,
|
||||||
@@ -449,40 +446,66 @@ export const ToolConfirmationMessage: React.FC<
|
|||||||
|
|
||||||
// Calculate the vertical space (in lines) consumed by UI elements
|
// Calculate the vertical space (in lines) consumed by UI elements
|
||||||
// surrounding the main body content.
|
// surrounding the main body content.
|
||||||
const PADDING_OUTER_Y = 1; // Main container has `paddingBottom={1}`.
|
const PADDING_OUTER_Y = 0;
|
||||||
const HEIGHT_QUESTION = 1; // The question text is one line.
|
const HEIGHT_QUESTION = 1;
|
||||||
const MARGIN_QUESTION_BOTTOM = 1; // Margin on the question container.
|
const MARGIN_QUESTION_TOP = 0;
|
||||||
const SECURITY_WARNING_BOTTOM_MARGIN = 1; // Margin on the securityWarnings container.
|
const MARGIN_QUESTION_BOTTOM = 1;
|
||||||
const SHOW_MORE_LINES_HEIGHT = 1; // The "Press Ctrl+O to show more lines" hint.
|
const SECURITY_WARNING_BOTTOM_MARGIN = 1;
|
||||||
|
const SHOW_MORE_LINES_HEIGHT = 1;
|
||||||
|
|
||||||
const optionsCount = getOptions().length;
|
const optionsCount = getOptions().length;
|
||||||
|
|
||||||
// The measured height includes the margin inside WarningMessage (1 line).
|
|
||||||
// We also add 1 line for the marginBottom on the securityWarnings container.
|
|
||||||
const securityWarningsHeight = deceptiveUrlWarningText
|
const securityWarningsHeight = deceptiveUrlWarningText
|
||||||
? measuredSecurityWarningsHeight + SECURITY_WARNING_BOTTOM_MARGIN
|
? measuredSecurityWarningsHeight + SECURITY_WARNING_BOTTOM_MARGIN
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
|
let extraInfoLines = 0;
|
||||||
|
if (confirmationDetails.type === 'sandbox_expansion') {
|
||||||
|
const { additionalPermissions } = confirmationDetails;
|
||||||
|
if (additionalPermissions?.network) extraInfoLines++;
|
||||||
|
extraInfoLines += additionalPermissions?.fileSystem?.read?.length || 0;
|
||||||
|
extraInfoLines += additionalPermissions?.fileSystem?.write?.length || 0;
|
||||||
|
} else if (confirmationDetails.type === 'exec') {
|
||||||
|
const executionProps = confirmationDetails;
|
||||||
|
const commandsToDisplay =
|
||||||
|
executionProps.commands && executionProps.commands.length > 0
|
||||||
|
? executionProps.commands
|
||||||
|
: [executionProps.command];
|
||||||
|
const containsRedirection = commandsToDisplay.some((cmd) =>
|
||||||
|
hasRedirection(cmd),
|
||||||
|
);
|
||||||
|
const isAutoEdit =
|
||||||
|
config.getApprovalMode() === ApprovalMode.YOLO ||
|
||||||
|
config.getApprovalMode() === ApprovalMode.AUTO_EDIT;
|
||||||
|
if (containsRedirection && !isAutoEdit) {
|
||||||
|
extraInfoLines = 1; // Warning line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const surroundingElementsHeight =
|
const surroundingElementsHeight =
|
||||||
PADDING_OUTER_Y +
|
PADDING_OUTER_Y +
|
||||||
HEIGHT_QUESTION +
|
HEIGHT_QUESTION +
|
||||||
|
MARGIN_QUESTION_TOP +
|
||||||
MARGIN_QUESTION_BOTTOM +
|
MARGIN_QUESTION_BOTTOM +
|
||||||
SHOW_MORE_LINES_HEIGHT +
|
SHOW_MORE_LINES_HEIGHT +
|
||||||
optionsCount +
|
optionsCount +
|
||||||
securityWarningsHeight;
|
securityWarningsHeight +
|
||||||
|
extraInfoLines;
|
||||||
|
|
||||||
return Math.max(availableTerminalHeight - surroundingElementsHeight, 1);
|
return Math.max(availableTerminalHeight - surroundingElementsHeight, 2);
|
||||||
}, [
|
}, [
|
||||||
availableTerminalHeight,
|
availableTerminalHeight,
|
||||||
handlesOwnUI,
|
handlesOwnUI,
|
||||||
getOptions,
|
getOptions,
|
||||||
measuredSecurityWarningsHeight,
|
measuredSecurityWarningsHeight,
|
||||||
deceptiveUrlWarningText,
|
deceptiveUrlWarningText,
|
||||||
|
confirmationDetails,
|
||||||
|
config,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const { question, bodyContent, options, securityWarnings, initialIndex } =
|
const { question, bodyContent, options, securityWarnings, initialIndex } =
|
||||||
useMemo<{
|
useMemo<{
|
||||||
question: string;
|
question: React.ReactNode;
|
||||||
bodyContent: React.ReactNode;
|
bodyContent: React.ReactNode;
|
||||||
options: Array<RadioSelectItem<ToolConfirmationOutcome>>;
|
options: Array<RadioSelectItem<ToolConfirmationOutcome>>;
|
||||||
securityWarnings: React.ReactNode;
|
securityWarnings: React.ReactNode;
|
||||||
@@ -490,7 +513,7 @@ export const ToolConfirmationMessage: React.FC<
|
|||||||
}>(() => {
|
}>(() => {
|
||||||
let bodyContent: React.ReactNode | null = null;
|
let bodyContent: React.ReactNode | null = null;
|
||||||
let securityWarnings: React.ReactNode | null = null;
|
let securityWarnings: React.ReactNode | null = null;
|
||||||
let question = '';
|
let question: React.ReactNode = '';
|
||||||
const options = getOptions();
|
const options = getOptions();
|
||||||
|
|
||||||
let initialIndex = 0;
|
let initialIndex = 0;
|
||||||
@@ -519,6 +542,8 @@ export const ToolConfirmationMessage: React.FC<
|
|||||||
securityWarnings = <WarningMessage text={deceptiveUrlWarningText} />;
|
securityWarnings = <WarningMessage text={deceptiveUrlWarningText} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const bodyHeight = availableBodyContentHeight();
|
||||||
|
|
||||||
if (confirmationDetails.type === 'ask_user') {
|
if (confirmationDetails.type === 'ask_user') {
|
||||||
bodyContent = (
|
bodyContent = (
|
||||||
<AskUserDialog
|
<AskUserDialog
|
||||||
@@ -530,7 +555,7 @@ export const ToolConfirmationMessage: React.FC<
|
|||||||
handleConfirm(ToolConfirmationOutcome.Cancel);
|
handleConfirm(ToolConfirmationOutcome.Cancel);
|
||||||
}}
|
}}
|
||||||
width={terminalWidth}
|
width={terminalWidth}
|
||||||
availableHeight={availableBodyContentHeight()}
|
availableHeight={bodyHeight}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
@@ -563,7 +588,7 @@ export const ToolConfirmationMessage: React.FC<
|
|||||||
handleConfirm(ToolConfirmationOutcome.Cancel);
|
handleConfirm(ToolConfirmationOutcome.Cancel);
|
||||||
}}
|
}}
|
||||||
width={terminalWidth}
|
width={terminalWidth}
|
||||||
availableHeight={availableBodyContentHeight()}
|
availableHeight={bodyHeight}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
@@ -578,85 +603,109 @@ export const ToolConfirmationMessage: React.FC<
|
|||||||
if (confirmationDetails.type === 'edit') {
|
if (confirmationDetails.type === 'edit') {
|
||||||
if (!confirmationDetails.isModifying) {
|
if (!confirmationDetails.isModifying) {
|
||||||
question = `Apply this change?`;
|
question = `Apply this change?`;
|
||||||
}
|
|
||||||
} else if (confirmationDetails.type === 'sandbox_expansion') {
|
|
||||||
question = `Allow sandbox expansion for: '${sanitizeForDisplay(confirmationDetails.rootCommand)}'?`;
|
|
||||||
} else if (confirmationDetails.type === 'exec') {
|
|
||||||
const executionProps = confirmationDetails;
|
|
||||||
|
|
||||||
if (executionProps.commands && executionProps.commands.length > 1) {
|
|
||||||
question = `Allow execution of ${executionProps.commands.length} commands?`;
|
|
||||||
} else {
|
|
||||||
question = `Allow execution of: '${sanitizeForDisplay(executionProps.rootCommand)}'?`;
|
|
||||||
}
|
|
||||||
} else if (confirmationDetails.type === 'info') {
|
|
||||||
question = `Do you want to proceed?`;
|
|
||||||
} else if (confirmationDetails.type === 'mcp') {
|
|
||||||
// mcp tool confirmation
|
|
||||||
const mcpProps = confirmationDetails;
|
|
||||||
question = `Allow execution of MCP tool "${sanitizeForDisplay(mcpProps.toolName)}" from server "${sanitizeForDisplay(mcpProps.serverName)}"?`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (confirmationDetails.type === 'edit') {
|
|
||||||
if (!confirmationDetails.isModifying) {
|
|
||||||
bodyContent = (
|
bodyContent = (
|
||||||
<DiffRenderer
|
<>
|
||||||
diffContent={stripUnsafeCharacters(confirmationDetails.fileDiff)}
|
<Box
|
||||||
filename={sanitizeForDisplay(confirmationDetails.fileName)}
|
borderStyle="round"
|
||||||
availableTerminalHeight={availableBodyContentHeight()}
|
borderColor={theme.border.default}
|
||||||
terminalWidth={terminalWidth}
|
paddingX={1}
|
||||||
/>
|
paddingY={0}
|
||||||
|
marginBottom={0}
|
||||||
|
>
|
||||||
|
<DiffRenderer
|
||||||
|
diffContent={stripUnsafeCharacters(
|
||||||
|
confirmationDetails.fileDiff,
|
||||||
|
)}
|
||||||
|
filename={sanitizeForDisplay(confirmationDetails.fileName)}
|
||||||
|
availableTerminalHeight={
|
||||||
|
bodyHeight !== undefined
|
||||||
|
? Math.max(bodyHeight - 2, 2)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
terminalWidth={Math.max(terminalWidth, 1) - 4}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (confirmationDetails.type === 'sandbox_expansion') {
|
} else if (confirmationDetails.type === 'sandbox_expansion') {
|
||||||
const { additionalPermissions } = confirmationDetails;
|
const { additionalPermissions, command, rootCommand } =
|
||||||
|
confirmationDetails;
|
||||||
const readPaths = additionalPermissions?.fileSystem?.read || [];
|
const readPaths = additionalPermissions?.fileSystem?.read || [];
|
||||||
const writePaths = additionalPermissions?.fileSystem?.write || [];
|
const writePaths = additionalPermissions?.fileSystem?.write || [];
|
||||||
const network = additionalPermissions?.network;
|
const network = additionalPermissions?.network;
|
||||||
|
const isShell = isShellTool(toolName);
|
||||||
|
|
||||||
|
const rootCmds = rootCommand
|
||||||
|
.split(',')
|
||||||
|
.map((c) => c.trim().split(/\s+/)[0])
|
||||||
|
.filter((c) => c && !c.startsWith('redirection'));
|
||||||
|
const commandNames = Array.from(new Set(rootCmds)).join(', ');
|
||||||
|
question = '';
|
||||||
|
|
||||||
bodyContent = (
|
bodyContent = (
|
||||||
<Box flexDirection="column" padding={1}>
|
<>
|
||||||
<Text color={theme.text.secondary} italic>
|
<Box
|
||||||
The agent is requesting additional sandbox permissions to execute
|
borderStyle="round"
|
||||||
this command:
|
borderColor={theme.border.default}
|
||||||
</Text>
|
paddingX={1}
|
||||||
<Box paddingY={1}>
|
paddingY={0}
|
||||||
<Text color={theme.text.secondary}>
|
marginBottom={0}
|
||||||
{sanitizeForDisplay(confirmationDetails.command)}
|
>
|
||||||
</Text>
|
{colorizeCode({
|
||||||
|
code: command.trim(),
|
||||||
|
language: 'bash',
|
||||||
|
maxWidth: Math.max(terminalWidth, 1) - 6,
|
||||||
|
settings,
|
||||||
|
theme: activeTheme,
|
||||||
|
hideLineNumbers: true,
|
||||||
|
availableHeight:
|
||||||
|
bodyHeight !== undefined
|
||||||
|
? Math.max(bodyHeight - 2, 2)
|
||||||
|
: undefined,
|
||||||
|
})}
|
||||||
</Box>
|
</Box>
|
||||||
{network && (
|
<Box flexDirection="column">
|
||||||
<Box>
|
<Text>
|
||||||
<Text color={theme.status.warning}>• Network Access</Text>
|
To run{' '}
|
||||||
</Box>
|
<Text
|
||||||
)}
|
color={isShell ? theme.status.warning : undefined}
|
||||||
{readPaths.length > 0 && (
|
bold={isShell}
|
||||||
<Box flexDirection="column">
|
>
|
||||||
<Text color={theme.status.success}>• Read Access:</Text>
|
[{sanitizeForDisplay(commandNames)}]
|
||||||
{readPaths.map((p, i) => (
|
</Text>
|
||||||
<Text key={i} color={theme.text.secondary}>
|
, allow access to the following?
|
||||||
{' '}
|
</Text>
|
||||||
{sanitizeForDisplay(p)}
|
{network && (
|
||||||
</Text>
|
<Text>
|
||||||
))}
|
<Text color={isShell ? theme.status.warning : undefined} bold>
|
||||||
</Box>
|
• Network:
|
||||||
)}
|
</Text>{' '}
|
||||||
{writePaths.length > 0 && (
|
All Urls
|
||||||
<Box flexDirection="column">
|
</Text>
|
||||||
<Text color={theme.status.error}>• Write Access:</Text>
|
)}
|
||||||
{writePaths.map((p, i) => (
|
{writePaths.length > 0 && (
|
||||||
<Text key={i} color={theme.text.secondary}>
|
<Text>
|
||||||
{' '}
|
<Text color={isShell ? theme.status.warning : undefined} bold>
|
||||||
{sanitizeForDisplay(p)}
|
• Write:
|
||||||
</Text>
|
</Text>{' '}
|
||||||
))}
|
{writePaths.map((p) => sanitizeForDisplay(p)).join(', ')}
|
||||||
</Box>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</Box>
|
{readPaths.length > 0 && (
|
||||||
|
<Text>
|
||||||
|
<Text color={isShell ? theme.status.warning : undefined} bold>
|
||||||
|
• Read:
|
||||||
|
</Text>{' '}
|
||||||
|
{readPaths.map((p) => sanitizeForDisplay(p)).join(', ')}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
} else if (confirmationDetails.type === 'exec') {
|
} else if (confirmationDetails.type === 'exec') {
|
||||||
const executionProps = confirmationDetails;
|
const executionProps = confirmationDetails;
|
||||||
|
const isShell = isShellTool(toolName);
|
||||||
const commandsToDisplay =
|
const commandsToDisplay =
|
||||||
executionProps.commands && executionProps.commands.length > 1
|
executionProps.commands && executionProps.commands.length > 1
|
||||||
? executionProps.commands
|
? executionProps.commands
|
||||||
@@ -664,80 +713,96 @@ export const ToolConfirmationMessage: React.FC<
|
|||||||
const containsRedirection = commandsToDisplay.some((cmd) =>
|
const containsRedirection = commandsToDisplay.some((cmd) =>
|
||||||
hasRedirection(cmd),
|
hasRedirection(cmd),
|
||||||
);
|
);
|
||||||
|
const isAutoEdit =
|
||||||
|
config.getApprovalMode() === ApprovalMode.YOLO ||
|
||||||
|
config.getApprovalMode() === ApprovalMode.AUTO_EDIT;
|
||||||
|
|
||||||
let bodyContentHeight = availableBodyContentHeight();
|
|
||||||
let warnings: React.ReactNode = null;
|
let warnings: React.ReactNode = null;
|
||||||
|
|
||||||
const isAutoEdit = config.getApprovalMode() === ApprovalMode.AUTO_EDIT;
|
|
||||||
if (containsRedirection && !isAutoEdit) {
|
if (containsRedirection && !isAutoEdit) {
|
||||||
// Calculate lines needed for Note and Tip
|
const tipText = `To auto-accept, press ${formatCommand(Command.CYCLE_APPROVAL_MODE)}`;
|
||||||
const safeWidth = Math.max(terminalWidth, 1);
|
|
||||||
const noteLength =
|
|
||||||
REDIRECTION_WARNING_NOTE_LABEL.length +
|
|
||||||
REDIRECTION_WARNING_NOTE_TEXT.length;
|
|
||||||
const tipText = `Toggle auto-edit (${formatCommand(Command.CYCLE_APPROVAL_MODE)}) to allow redirection in the future.`;
|
|
||||||
const tipLength =
|
|
||||||
REDIRECTION_WARNING_TIP_LABEL.length + tipText.length;
|
|
||||||
|
|
||||||
const noteLines = Math.ceil(noteLength / safeWidth);
|
|
||||||
const tipLines = Math.ceil(tipLength / safeWidth);
|
|
||||||
const spacerLines = 1;
|
|
||||||
const warningHeight = noteLines + tipLines + spacerLines;
|
|
||||||
|
|
||||||
if (bodyContentHeight !== undefined) {
|
|
||||||
bodyContentHeight = Math.max(
|
|
||||||
bodyContentHeight - warningHeight,
|
|
||||||
MINIMUM_MAX_HEIGHT,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
warnings = (
|
warnings = (
|
||||||
<>
|
<Box flexDirection="column" marginBottom={0}>
|
||||||
<Box height={1} />
|
<Text color={theme.text.primary}>
|
||||||
<Box>
|
Redirection detected.{' '}
|
||||||
<Text color={theme.text.primary}>
|
<Text color={theme.text.secondary}>{tipText}</Text>
|
||||||
<Text bold>{REDIRECTION_WARNING_NOTE_LABEL}</Text>
|
</Text>
|
||||||
{REDIRECTION_WARNING_NOTE_TEXT}
|
</Box>
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text color={theme.border.default}>
|
|
||||||
<Text bold>{REDIRECTION_WARNING_TIP_LABEL}</Text>
|
|
||||||
{tipText}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
bodyContent = (
|
const commandNames = Array.from(
|
||||||
<Box flexDirection="column">
|
new Set(
|
||||||
<MaxSizedBox
|
commandsToDisplay
|
||||||
maxHeight={bodyContentHeight}
|
.map((cmd) => cmd.trim().split(/\s+/)[0])
|
||||||
maxWidth={Math.max(terminalWidth, 1)}
|
.filter(Boolean),
|
||||||
|
),
|
||||||
|
).join(', ');
|
||||||
|
|
||||||
|
const allowQuestion = (
|
||||||
|
<Text>
|
||||||
|
Allow execution of{' '}
|
||||||
|
<Text
|
||||||
|
color={isShell ? theme.status.warning : undefined}
|
||||||
|
bold={isShell}
|
||||||
>
|
>
|
||||||
<Box flexDirection="column">
|
[{sanitizeForDisplay(commandNames)}]
|
||||||
{commandsToDisplay.map((cmd, idx) => (
|
</Text>
|
||||||
<Box
|
{'?'}
|
||||||
key={idx}
|
</Text>
|
||||||
flexDirection="column"
|
);
|
||||||
paddingBottom={idx < commandsToDisplay.length - 1 ? 1 : 0}
|
|
||||||
>
|
question = (
|
||||||
{colorizeCode({
|
<Box flexDirection="column">
|
||||||
code: cmd,
|
{allowQuestion}
|
||||||
language: 'bash',
|
|
||||||
maxWidth: Math.max(terminalWidth, 1),
|
|
||||||
settings,
|
|
||||||
hideLineNumbers: true,
|
|
||||||
})}
|
|
||||||
</Box>
|
|
||||||
))}
|
|
||||||
</Box>
|
|
||||||
</MaxSizedBox>
|
|
||||||
{warnings}
|
{warnings}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
bodyContent = (
|
||||||
|
<>
|
||||||
|
<Box
|
||||||
|
borderStyle="round"
|
||||||
|
borderColor={theme.border.default}
|
||||||
|
paddingX={1}
|
||||||
|
paddingY={0}
|
||||||
|
marginBottom={0}
|
||||||
|
>
|
||||||
|
<MaxSizedBox
|
||||||
|
maxHeight={
|
||||||
|
bodyHeight !== undefined
|
||||||
|
? Math.max(bodyHeight - 2, 2)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
maxWidth={Math.max(terminalWidth, 1) - 4}
|
||||||
|
>
|
||||||
|
<Box flexDirection="column">
|
||||||
|
{commandsToDisplay.map((cmd, idx) => (
|
||||||
|
<Box
|
||||||
|
key={idx}
|
||||||
|
flexDirection="column"
|
||||||
|
paddingBottom={idx < commandsToDisplay.length - 1 ? 1 : 0}
|
||||||
|
>
|
||||||
|
{colorizeCode({
|
||||||
|
code: cmd.trim(),
|
||||||
|
language: 'bash',
|
||||||
|
maxWidth: Math.max(terminalWidth, 1) - 6,
|
||||||
|
settings,
|
||||||
|
theme: activeTheme,
|
||||||
|
hideLineNumbers: true,
|
||||||
|
availableHeight:
|
||||||
|
bodyHeight !== undefined
|
||||||
|
? Math.max(bodyHeight - 2, 2)
|
||||||
|
: undefined,
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</MaxSizedBox>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
);
|
||||||
} else if (confirmationDetails.type === 'info') {
|
} else if (confirmationDetails.type === 'info') {
|
||||||
|
question = `Do you want to proceed?`;
|
||||||
const infoProps = confirmationDetails;
|
const infoProps = confirmationDetails;
|
||||||
const displayUrls =
|
const displayUrls =
|
||||||
infoProps.urls &&
|
infoProps.urls &&
|
||||||
@@ -768,8 +833,8 @@ export const ToolConfirmationMessage: React.FC<
|
|||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
} else if (confirmationDetails.type === 'mcp') {
|
} else if (confirmationDetails.type === 'mcp') {
|
||||||
// mcp tool confirmation
|
|
||||||
const mcpProps = confirmationDetails;
|
const mcpProps = confirmationDetails;
|
||||||
|
question = `Allow execution of MCP tool "${sanitizeForDisplay(mcpProps.toolName)}" from server "${sanitizeForDisplay(mcpProps.serverName)}"?`;
|
||||||
|
|
||||||
bodyContent = (
|
bodyContent = (
|
||||||
<Box flexDirection="column">
|
<Box flexDirection="column">
|
||||||
@@ -790,7 +855,26 @@ export const ToolConfirmationMessage: React.FC<
|
|||||||
(press {expandDetailsHintKey} to collapse MCP tool
|
(press {expandDetailsHintKey} to collapse MCP tool
|
||||||
details)
|
details)
|
||||||
</Text>
|
</Text>
|
||||||
<Text color={theme.text.link}>{mcpToolDetailsText}</Text>
|
<Box
|
||||||
|
borderStyle="round"
|
||||||
|
borderColor={theme.border.default}
|
||||||
|
paddingX={1}
|
||||||
|
paddingY={0}
|
||||||
|
marginBottom={0}
|
||||||
|
>
|
||||||
|
{colorizeCode({
|
||||||
|
code: mcpToolDetailsText || '',
|
||||||
|
language: 'json',
|
||||||
|
maxWidth: Math.max(terminalWidth, 1) - 4,
|
||||||
|
settings,
|
||||||
|
theme: activeTheme,
|
||||||
|
hideLineNumbers: true,
|
||||||
|
availableHeight:
|
||||||
|
bodyHeight !== undefined
|
||||||
|
? Math.max(bodyHeight - 2, 2)
|
||||||
|
: undefined,
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<Text color={theme.text.secondary}>
|
<Text color={theme.text.secondary}>
|
||||||
@@ -819,7 +903,9 @@ export const ToolConfirmationMessage: React.FC<
|
|||||||
isTrustedFolder,
|
isTrustedFolder,
|
||||||
allowPermanentApproval,
|
allowPermanentApproval,
|
||||||
settings,
|
settings,
|
||||||
|
activeTheme,
|
||||||
config,
|
config,
|
||||||
|
toolName,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const bodyOverflowDirection: 'top' | 'bottom' =
|
const bodyOverflowDirection: 'top' | 'bottom' =
|
||||||
@@ -827,6 +913,30 @@ export const ToolConfirmationMessage: React.FC<
|
|||||||
? 'bottom'
|
? 'bottom'
|
||||||
: 'top';
|
: 'top';
|
||||||
|
|
||||||
|
const renderRadioItem = useCallback(
|
||||||
|
(
|
||||||
|
item: RadioSelectItem<ToolConfirmationOutcome>,
|
||||||
|
{ titleColor }: { titleColor: string },
|
||||||
|
) => {
|
||||||
|
if (item.value === ToolConfirmationOutcome.ProceedAlwaysAndSave) {
|
||||||
|
return (
|
||||||
|
<Text color={titleColor} wrap="truncate">
|
||||||
|
{item.label}{' '}
|
||||||
|
<Text color={theme.text.secondary}>
|
||||||
|
~/.gemini/policies/auto-saved.toml
|
||||||
|
</Text>
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Text color={titleColor} wrap="truncate">
|
||||||
|
{item.label}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
if (confirmationDetails.type === 'edit') {
|
if (confirmationDetails.type === 'edit') {
|
||||||
if (confirmationDetails.isModifying) {
|
if (confirmationDetails.isModifying) {
|
||||||
return (
|
return (
|
||||||
@@ -849,13 +959,8 @@ export const ToolConfirmationMessage: React.FC<
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box flexDirection="column" paddingTop={0} paddingBottom={0}>
|
||||||
flexDirection="column"
|
{!!confirmationDetails.systemMessage && (
|
||||||
paddingTop={0}
|
|
||||||
paddingBottom={handlesOwnUI ? 0 : 1}
|
|
||||||
>
|
|
||||||
{/* System message from hook */}
|
|
||||||
{confirmationDetails.systemMessage && (
|
|
||||||
<Box marginBottom={1}>
|
<Box marginBottom={1}>
|
||||||
<Text color={theme.status.warning}>
|
<Text color={theme.status.warning}>
|
||||||
{confirmationDetails.systemMessage}
|
{confirmationDetails.systemMessage}
|
||||||
@@ -867,7 +972,11 @@ export const ToolConfirmationMessage: React.FC<
|
|||||||
bodyContent
|
bodyContent
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Box flexGrow={1} flexShrink={1} overflow="hidden">
|
<Box
|
||||||
|
flexShrink={1}
|
||||||
|
overflow="hidden"
|
||||||
|
marginBottom={!question && !securityWarnings ? 1 : 0}
|
||||||
|
>
|
||||||
<MaxSizedBox
|
<MaxSizedBox
|
||||||
maxHeight={availableBodyContentHeight()}
|
maxHeight={availableBodyContentHeight()}
|
||||||
maxWidth={terminalWidth}
|
maxWidth={terminalWidth}
|
||||||
@@ -887,9 +996,15 @@ export const ToolConfirmationMessage: React.FC<
|
|||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Box marginBottom={1} flexShrink={0}>
|
{!!question && (
|
||||||
<Text color={theme.text.primary}>{question}</Text>
|
<Box marginBottom={1} flexShrink={0}>
|
||||||
</Box>
|
{typeof question === 'string' ? (
|
||||||
|
<Text color={theme.text.primary}>{question}</Text>
|
||||||
|
) : (
|
||||||
|
question
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
<Box flexShrink={0}>
|
<Box flexShrink={0}>
|
||||||
<RadioButtonSelect
|
<RadioButtonSelect
|
||||||
@@ -897,6 +1012,7 @@ export const ToolConfirmationMessage: React.FC<
|
|||||||
onSelect={handleSelect}
|
onSelect={handleSelect}
|
||||||
isFocused={isFocused}
|
isFocused={isFocused}
|
||||||
initialIndex={initialIndex}
|
initialIndex={initialIndex}
|
||||||
|
renderItem={renderRadioItem}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { ToolGroupMessage } from './ToolGroupMessage.js';
|
|||||||
import {
|
import {
|
||||||
UPDATE_TOPIC_TOOL_NAME,
|
UPDATE_TOPIC_TOOL_NAME,
|
||||||
TOPIC_PARAM_TITLE,
|
TOPIC_PARAM_TITLE,
|
||||||
|
TOPIC_PARAM_SUMMARY,
|
||||||
TOPIC_PARAM_STRATEGIC_INTENT,
|
TOPIC_PARAM_STRATEGIC_INTENT,
|
||||||
makeFakeConfig,
|
makeFakeConfig,
|
||||||
CoreToolCallStatus,
|
CoreToolCallStatus,
|
||||||
@@ -292,7 +293,7 @@ describe('<ToolGroupMessage />', () => {
|
|||||||
name: UPDATE_TOPIC_TOOL_NAME,
|
name: UPDATE_TOPIC_TOOL_NAME,
|
||||||
args: {
|
args: {
|
||||||
[TOPIC_PARAM_TITLE]: 'Testing Topic',
|
[TOPIC_PARAM_TITLE]: 'Testing Topic',
|
||||||
summary: 'This is the summary',
|
[TOPIC_PARAM_SUMMARY]: 'This is the summary',
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -352,6 +352,7 @@ describe('<ToolMessage />', () => {
|
|||||||
underline: false,
|
underline: false,
|
||||||
dim: false,
|
dim: false,
|
||||||
inverse: false,
|
inverse: false,
|
||||||
|
isUninitialized: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
@@ -443,8 +444,8 @@ describe('<ToolMessage />', () => {
|
|||||||
constrainHeight: true,
|
constrainHeight: true,
|
||||||
},
|
},
|
||||||
width: 80,
|
width: 80,
|
||||||
config: makeFakeConfig({ useAlternateBuffer: false }),
|
config: makeFakeConfig({ useAlternateBuffer: true }),
|
||||||
settings: createMockSettings({ ui: { useAlternateBuffer: false } }),
|
settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
const output = lastFrame();
|
const output = lastFrame();
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { renderWithProviders } from '../../../test-utils/render.js';
|
import { renderWithProviders } from '../../../test-utils/render.js';
|
||||||
|
import { waitFor } from '../../../test-utils/async.js';
|
||||||
import { createMockSettings } from '../../../test-utils/settings.js';
|
import { createMockSettings } from '../../../test-utils/settings.js';
|
||||||
import { ToolResultDisplay } from './ToolResultDisplay.js';
|
import { ToolResultDisplay } from './ToolResultDisplay.js';
|
||||||
import { describe, it, expect, vi } from 'vitest';
|
import { describe, it, expect, vi } from 'vitest';
|
||||||
@@ -28,6 +29,7 @@ describe('ToolResultDisplay', () => {
|
|||||||
underline: false,
|
underline: false,
|
||||||
dim: false,
|
dim: false,
|
||||||
inverse: false,
|
inverse: false,
|
||||||
|
isUninitialized: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
@@ -179,6 +181,7 @@ describe('ToolResultDisplay', () => {
|
|||||||
underline: false,
|
underline: false,
|
||||||
dim: false,
|
dim: false,
|
||||||
inverse: false,
|
inverse: false,
|
||||||
|
isUninitialized: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
@@ -275,6 +278,7 @@ describe('ToolResultDisplay', () => {
|
|||||||
underline: false,
|
underline: false,
|
||||||
dim: false,
|
dim: false,
|
||||||
inverse: false,
|
inverse: false,
|
||||||
|
isUninitialized: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
@@ -287,6 +291,7 @@ describe('ToolResultDisplay', () => {
|
|||||||
underline: false,
|
underline: false,
|
||||||
dim: false,
|
dim: false,
|
||||||
inverse: false,
|
inverse: false,
|
||||||
|
isUninitialized: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
@@ -299,6 +304,7 @@ describe('ToolResultDisplay', () => {
|
|||||||
underline: false,
|
underline: false,
|
||||||
dim: false,
|
dim: false,
|
||||||
inverse: false,
|
inverse: false,
|
||||||
|
isUninitialized: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
@@ -311,6 +317,7 @@ describe('ToolResultDisplay', () => {
|
|||||||
underline: false,
|
underline: false,
|
||||||
dim: false,
|
dim: false,
|
||||||
inverse: false,
|
inverse: false,
|
||||||
|
isUninitialized: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
@@ -323,6 +330,7 @@ describe('ToolResultDisplay', () => {
|
|||||||
underline: false,
|
underline: false,
|
||||||
dim: false,
|
dim: false,
|
||||||
inverse: false,
|
inverse: false,
|
||||||
|
isUninitialized: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
@@ -344,9 +352,10 @@ describe('ToolResultDisplay', () => {
|
|||||||
|
|
||||||
expect(output).not.toContain('Line 1');
|
expect(output).not.toContain('Line 1');
|
||||||
expect(output).not.toContain('Line 2');
|
expect(output).not.toContain('Line 2');
|
||||||
expect(output).toContain('Line 3');
|
expect(output).not.toContain('Line 3');
|
||||||
expect(output).toContain('Line 4');
|
expect(output).toContain('Line 4');
|
||||||
expect(output).toContain('Line 5');
|
expect(output).toContain('Line 5');
|
||||||
|
expect(output).toContain('hidden');
|
||||||
expect(output).toMatchSnapshot();
|
expect(output).toMatchSnapshot();
|
||||||
unmount();
|
unmount();
|
||||||
});
|
});
|
||||||
@@ -362,6 +371,7 @@ describe('ToolResultDisplay', () => {
|
|||||||
underline: false,
|
underline: false,
|
||||||
dim: false,
|
dim: false,
|
||||||
inverse: false,
|
inverse: false,
|
||||||
|
isUninitialized: false,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
const renderResult = await renderWithProviders(
|
const renderResult = await renderWithProviders(
|
||||||
@@ -383,4 +393,86 @@ describe('ToolResultDisplay', () => {
|
|||||||
await expect(renderResult).toMatchSvgSnapshot();
|
await expect(renderResult).toMatchSvgSnapshot();
|
||||||
unmount();
|
unmount();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('stays scrolled to the bottom when lines are incrementally added', async () => {
|
||||||
|
const createAnsiLine = (text: string) => [
|
||||||
|
{
|
||||||
|
text,
|
||||||
|
fg: '',
|
||||||
|
bg: '',
|
||||||
|
bold: false,
|
||||||
|
italic: false,
|
||||||
|
underline: false,
|
||||||
|
dim: false,
|
||||||
|
inverse: false,
|
||||||
|
isUninitialized: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let currentLines: AnsiOutput = [];
|
||||||
|
|
||||||
|
// Start with 3 lines, max lines 5. It should fit without scrolling.
|
||||||
|
for (let i = 1; i <= 3; i++) {
|
||||||
|
currentLines.push(createAnsiLine(`Line ${i}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderResult = await renderWithProviders(
|
||||||
|
<ToolResultDisplay
|
||||||
|
resultDisplay={currentLines}
|
||||||
|
terminalWidth={80}
|
||||||
|
maxLines={5}
|
||||||
|
availableTerminalHeight={5}
|
||||||
|
overflowDirection="top"
|
||||||
|
/>,
|
||||||
|
{
|
||||||
|
config: makeFakeConfig({ useAlternateBuffer: false }),
|
||||||
|
settings: createMockSettings({ ui: { useAlternateBuffer: false } }),
|
||||||
|
uiState: { constrainHeight: true, terminalHeight: 10 },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const { waitUntilReady, rerender, lastFrame, unmount } = renderResult;
|
||||||
|
await waitUntilReady();
|
||||||
|
|
||||||
|
// Verify initial render has the first 3 lines
|
||||||
|
expect(lastFrame()).toContain('Line 1');
|
||||||
|
expect(lastFrame()).toContain('Line 3');
|
||||||
|
|
||||||
|
// Incrementally add lines up to 8. Max lines is 5.
|
||||||
|
// So by the end, it should only show lines 4-8.
|
||||||
|
for (let i = 4; i <= 8; i++) {
|
||||||
|
currentLines = [...currentLines, createAnsiLine(`Line ${i}`)];
|
||||||
|
rerender(
|
||||||
|
<ToolResultDisplay
|
||||||
|
resultDisplay={currentLines}
|
||||||
|
terminalWidth={80}
|
||||||
|
maxLines={5}
|
||||||
|
availableTerminalHeight={5}
|
||||||
|
overflowDirection="top"
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
// Wait for the new line to be rendered
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(lastFrame()).toContain(`Line ${i}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await waitUntilReady();
|
||||||
|
const output = lastFrame();
|
||||||
|
|
||||||
|
// The component should have automatically scrolled to the bottom.
|
||||||
|
// Lines 1, 2, 3, 4 should be scrolled out of view.
|
||||||
|
expect(output).not.toContain('Line 1');
|
||||||
|
expect(output).not.toContain('Line 2');
|
||||||
|
expect(output).not.toContain('Line 3');
|
||||||
|
expect(output).not.toContain('Line 4');
|
||||||
|
// Lines 5, 6, 7, 8 should be visible along with the truncation indicator.
|
||||||
|
expect(output).toContain('hidden');
|
||||||
|
expect(output).toContain('Line 5');
|
||||||
|
expect(output).toContain('Line 8');
|
||||||
|
|
||||||
|
expect(output).toMatchSnapshot();
|
||||||
|
|
||||||
|
unmount();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { DiffRenderer } from './DiffRenderer.js';
|
|||||||
import { MarkdownDisplay } from '../../utils/MarkdownDisplay.js';
|
import { MarkdownDisplay } from '../../utils/MarkdownDisplay.js';
|
||||||
import { AnsiOutputText, AnsiLineText } from '../AnsiOutput.js';
|
import { AnsiOutputText, AnsiLineText } from '../AnsiOutput.js';
|
||||||
import { SlicingMaxSizedBox } from '../shared/SlicingMaxSizedBox.js';
|
import { SlicingMaxSizedBox } from '../shared/SlicingMaxSizedBox.js';
|
||||||
|
import { MaxSizedBox } from '../shared/MaxSizedBox.js';
|
||||||
import { theme } from '../../semantic-colors.js';
|
import { theme } from '../../semantic-colors.js';
|
||||||
import {
|
import {
|
||||||
type AnsiOutput,
|
type AnsiOutput,
|
||||||
@@ -51,7 +52,7 @@ export const ToolResultDisplay: React.FC<ToolResultDisplayProps> = ({
|
|||||||
hasFocus = false,
|
hasFocus = false,
|
||||||
overflowDirection = 'top',
|
overflowDirection = 'top',
|
||||||
}) => {
|
}) => {
|
||||||
const { renderMarkdown } = useUIState();
|
const { renderMarkdown, constrainHeight } = useUIState();
|
||||||
const isAlternateBuffer = useAlternateBuffer();
|
const isAlternateBuffer = useAlternateBuffer();
|
||||||
|
|
||||||
const availableHeight = calculateToolContentMaxLines({
|
const availableHeight = calculateToolContentMaxLines({
|
||||||
@@ -209,30 +210,73 @@ export const ToolResultDisplay: React.FC<ToolResultDisplayProps> = ({
|
|||||||
|
|
||||||
if (Array.isArray(resultDisplay)) {
|
if (Array.isArray(resultDisplay)) {
|
||||||
const limit = maxLines ?? availableHeight ?? ACTIVE_SHELL_MAX_LINES;
|
const limit = maxLines ?? availableHeight ?? ACTIVE_SHELL_MAX_LINES;
|
||||||
const listHeight = Math.min(
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
const data = resultDisplay as AnsiOutput;
|
||||||
(resultDisplay as AnsiOutput).length,
|
|
||||||
limit,
|
|
||||||
);
|
|
||||||
|
|
||||||
const initialScrollIndex =
|
// Calculate list height: if not constrained, use full data length.
|
||||||
overflowDirection === 'bottom' ? 0 : SCROLL_TO_ITEM_END;
|
// If constrained (e.g. alternate buffer), limit to available height
|
||||||
|
// to ensure virtualization works and fits within the viewport.
|
||||||
|
const listHeight = !constrainHeight
|
||||||
|
? data.length
|
||||||
|
: Math.min(data.length, limit);
|
||||||
|
|
||||||
return (
|
if (isAlternateBuffer) {
|
||||||
<Box width={childWidth} flexDirection="column" maxHeight={listHeight}>
|
const initialScrollIndex =
|
||||||
<ScrollableList
|
overflowDirection === 'bottom' ? 0 : SCROLL_TO_ITEM_END;
|
||||||
width={childWidth}
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
return (
|
||||||
data={resultDisplay as AnsiOutput}
|
<Box width={childWidth} flexDirection="column" maxHeight={listHeight}>
|
||||||
renderItem={renderVirtualizedAnsiLine}
|
<ScrollableList
|
||||||
estimatedItemHeight={() => 1}
|
width={childWidth}
|
||||||
keyExtractor={keyExtractor}
|
containerHeight={listHeight}
|
||||||
initialScrollIndex={initialScrollIndex}
|
data={data}
|
||||||
hasFocus={hasFocus}
|
renderItem={renderVirtualizedAnsiLine}
|
||||||
fixedItemHeight={true}
|
estimatedItemHeight={() => 1}
|
||||||
/>
|
fixedItemHeight={true}
|
||||||
</Box>
|
keyExtractor={keyExtractor}
|
||||||
);
|
initialScrollIndex={initialScrollIndex}
|
||||||
|
hasFocus={hasFocus}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
let displayData = data;
|
||||||
|
let hiddenLines = 0;
|
||||||
|
|
||||||
|
if (constrainHeight && data.length > listHeight) {
|
||||||
|
hiddenLines = data.length - listHeight;
|
||||||
|
if (overflowDirection === 'top') {
|
||||||
|
displayData = data.slice(hiddenLines);
|
||||||
|
} else {
|
||||||
|
displayData = data.slice(0, listHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box width={childWidth} flexDirection="column">
|
||||||
|
<MaxSizedBox
|
||||||
|
maxHeight={constrainHeight ? listHeight : undefined}
|
||||||
|
maxWidth={childWidth}
|
||||||
|
overflowDirection={overflowDirection}
|
||||||
|
additionalHiddenLinesCount={hiddenLines}
|
||||||
|
>
|
||||||
|
{displayData.map((item, index) => {
|
||||||
|
const actualIndex =
|
||||||
|
(overflowDirection === 'top' ? hiddenLines : 0) + index;
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
key={keyExtractor(item, actualIndex)}
|
||||||
|
height={1}
|
||||||
|
overflow="hidden"
|
||||||
|
>
|
||||||
|
<AnsiLineText line={item} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</MaxSizedBox>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ASB Mode Handling (Interactive/Fullscreen)
|
// ASB Mode Handling (Interactive/Fullscreen)
|
||||||
|
|||||||
@@ -29,11 +29,12 @@ describe('ToolResultDisplay Overflow', () => {
|
|||||||
await waitUntilReady();
|
await waitUntilReady();
|
||||||
const output = lastFrame();
|
const output = lastFrame();
|
||||||
|
|
||||||
expect(output).not.toContain('Line 1');
|
expect(output).toContain('Line 1');
|
||||||
expect(output).not.toContain('Line 2');
|
expect(output).toContain('Line 2');
|
||||||
expect(output).toContain('Line 3');
|
expect(output).not.toContain('Line 3');
|
||||||
expect(output).toContain('Line 4');
|
expect(output).not.toContain('Line 4');
|
||||||
expect(output).toContain('Line 5');
|
expect(output).not.toContain('Line 5');
|
||||||
|
expect(output).toContain('hidden');
|
||||||
unmount();
|
unmount();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -57,9 +58,10 @@ describe('ToolResultDisplay Overflow', () => {
|
|||||||
|
|
||||||
expect(output).not.toContain('Line 1');
|
expect(output).not.toContain('Line 1');
|
||||||
expect(output).not.toContain('Line 2');
|
expect(output).not.toContain('Line 2');
|
||||||
expect(output).toContain('Line 3');
|
expect(output).not.toContain('Line 3');
|
||||||
expect(output).toContain('Line 4');
|
expect(output).toContain('Line 4');
|
||||||
expect(output).toContain('Line 5');
|
expect(output).toContain('Line 5');
|
||||||
|
expect(output).toContain('hidden');
|
||||||
unmount();
|
unmount();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -74,6 +76,7 @@ describe('ToolResultDisplay Overflow', () => {
|
|||||||
underline: false,
|
underline: false,
|
||||||
dim: false,
|
dim: false,
|
||||||
inverse: false,
|
inverse: false,
|
||||||
|
isUninitialized: false,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||||
@@ -94,11 +97,10 @@ describe('ToolResultDisplay Overflow', () => {
|
|||||||
|
|
||||||
expect(output).toContain('Line 1');
|
expect(output).toContain('Line 1');
|
||||||
expect(output).toContain('Line 2');
|
expect(output).toContain('Line 2');
|
||||||
expect(output).toContain('Line 3');
|
expect(output).not.toContain('Line 3');
|
||||||
expect(output).not.toContain('Line 4');
|
expect(output).not.toContain('Line 4');
|
||||||
expect(output).not.toContain('Line 5');
|
expect(output).not.toContain('Line 5');
|
||||||
// ScrollableList uses a scroll thumb rather than writing "hidden"
|
expect(output).toContain('hidden');
|
||||||
expect(output).toContain('█');
|
|
||||||
unmount();
|
unmount();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2026 Google LLC
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect, vi } from 'vitest';
|
||||||
|
import { TopicMessage } from './TopicMessage.js';
|
||||||
|
import { renderWithProviders } from '../../../test-utils/render.js';
|
||||||
|
import {
|
||||||
|
TOPIC_PARAM_TITLE,
|
||||||
|
TOPIC_PARAM_SUMMARY,
|
||||||
|
TOPIC_PARAM_STRATEGIC_INTENT,
|
||||||
|
CoreToolCallStatus,
|
||||||
|
UPDATE_TOPIC_TOOL_NAME,
|
||||||
|
} from '@google/gemini-cli-core';
|
||||||
|
|
||||||
|
describe('<TopicMessage />', () => {
|
||||||
|
const baseArgs = {
|
||||||
|
[TOPIC_PARAM_TITLE]: 'Test Topic',
|
||||||
|
[TOPIC_PARAM_STRATEGIC_INTENT]: 'This is the strategic intent.',
|
||||||
|
[TOPIC_PARAM_SUMMARY]:
|
||||||
|
'This is the detailed summary that should be expandable.',
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderTopic = async (
|
||||||
|
args: Record<string, unknown>,
|
||||||
|
height?: number,
|
||||||
|
toolActions?: {
|
||||||
|
isExpanded?: (callId: string) => boolean;
|
||||||
|
toggleExpansion?: (callId: string) => void;
|
||||||
|
},
|
||||||
|
) =>
|
||||||
|
renderWithProviders(
|
||||||
|
<TopicMessage
|
||||||
|
args={args}
|
||||||
|
terminalWidth={80}
|
||||||
|
availableTerminalHeight={height}
|
||||||
|
callId="test-topic"
|
||||||
|
name={UPDATE_TOPIC_TOOL_NAME}
|
||||||
|
description="Updating topic"
|
||||||
|
status={CoreToolCallStatus.Success}
|
||||||
|
confirmationDetails={undefined}
|
||||||
|
resultDisplay={undefined}
|
||||||
|
/>,
|
||||||
|
{ toolActions, mouseEventsEnabled: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
it('renders title and intent by default (collapsed)', async () => {
|
||||||
|
const { lastFrame } = await renderTopic(baseArgs, 40);
|
||||||
|
const frame = lastFrame();
|
||||||
|
expect(frame).toContain('Test Topic:');
|
||||||
|
expect(frame).toContain('This is the strategic intent.');
|
||||||
|
expect(frame).not.toContain('This is the detailed summary');
|
||||||
|
expect(frame).not.toContain('(ctrl+o to expand)');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders summary when globally expanded (Ctrl+O)', async () => {
|
||||||
|
const { lastFrame } = await renderTopic(baseArgs, undefined);
|
||||||
|
const frame = lastFrame();
|
||||||
|
expect(frame).toContain('Test Topic:');
|
||||||
|
expect(frame).toContain('This is the strategic intent.');
|
||||||
|
expect(frame).toContain('This is the detailed summary');
|
||||||
|
expect(frame).not.toContain('(ctrl+o to collapse)');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders summary when selectively expanded via context', async () => {
|
||||||
|
const isExpanded = vi.fn((id) => id === 'test-topic');
|
||||||
|
const { lastFrame } = await renderTopic(baseArgs, 40, { isExpanded });
|
||||||
|
const frame = lastFrame();
|
||||||
|
expect(frame).toContain('Test Topic:');
|
||||||
|
expect(frame).toContain('This is the detailed summary');
|
||||||
|
expect(frame).not.toContain('(ctrl+o to collapse)');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls toggleExpansion when clicked', async () => {
|
||||||
|
const toggleExpansion = vi.fn();
|
||||||
|
const { simulateClick } = await renderTopic(baseArgs, 40, {
|
||||||
|
toggleExpansion,
|
||||||
|
});
|
||||||
|
|
||||||
|
// In renderWithProviders, the component is wrapped in a Box with terminalWidth.
|
||||||
|
// The TopicMessage has marginLeft={2}.
|
||||||
|
// So col 5 should definitely hit the text content.
|
||||||
|
// row 1 is the first line of the TopicMessage.
|
||||||
|
await simulateClick(5, 1);
|
||||||
|
|
||||||
|
expect(toggleExpansion).toHaveBeenCalledWith('test-topic');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('falls back to summary if strategic_intent is missing', async () => {
|
||||||
|
const args = {
|
||||||
|
[TOPIC_PARAM_TITLE]: 'Test Topic',
|
||||||
|
[TOPIC_PARAM_SUMMARY]: 'Only summary is present.',
|
||||||
|
};
|
||||||
|
const { lastFrame } = await renderTopic(args, 40);
|
||||||
|
const frame = lastFrame();
|
||||||
|
expect(frame).toContain('Test Topic:');
|
||||||
|
expect(frame).toContain('Only summary is present.');
|
||||||
|
expect(frame).not.toContain('(ctrl+o to expand)');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders only strategic_intent if summary is missing', async () => {
|
||||||
|
const args = {
|
||||||
|
[TOPIC_PARAM_TITLE]: 'Test Topic',
|
||||||
|
[TOPIC_PARAM_STRATEGIC_INTENT]: 'Only intent is present.',
|
||||||
|
};
|
||||||
|
const { lastFrame } = await renderTopic(args, 40);
|
||||||
|
const frame = lastFrame();
|
||||||
|
expect(frame).toContain('Test Topic:');
|
||||||
|
expect(frame).toContain('Only intent is present.');
|
||||||
|
expect(frame).not.toContain('(ctrl+o to expand)');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -5,7 +5,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
import { Box, Text } from 'ink';
|
import { useEffect, useId, useRef, useCallback } from 'react';
|
||||||
|
import { Box, Text, type DOMElement } from 'ink';
|
||||||
import {
|
import {
|
||||||
UPDATE_TOPIC_TOOL_NAME,
|
UPDATE_TOPIC_TOOL_NAME,
|
||||||
UPDATE_TOPIC_DISPLAY_NAME,
|
UPDATE_TOPIC_DISPLAY_NAME,
|
||||||
@@ -15,31 +16,103 @@ import {
|
|||||||
} from '@google/gemini-cli-core';
|
} from '@google/gemini-cli-core';
|
||||||
import type { IndividualToolCallDisplay } from '../../types.js';
|
import type { IndividualToolCallDisplay } from '../../types.js';
|
||||||
import { theme } from '../../semantic-colors.js';
|
import { theme } from '../../semantic-colors.js';
|
||||||
|
import { useOverflowActions } from '../../contexts/OverflowContext.js';
|
||||||
|
import { useToolActions } from '../../contexts/ToolActionsContext.js';
|
||||||
|
import { useMouseClick } from '../../hooks/useMouseClick.js';
|
||||||
|
|
||||||
interface TopicMessageProps extends IndividualToolCallDisplay {
|
interface TopicMessageProps extends IndividualToolCallDisplay {
|
||||||
terminalWidth: number;
|
terminalWidth: number;
|
||||||
|
availableTerminalHeight?: number;
|
||||||
|
isExpandable?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const isTopicTool = (name: string): boolean =>
|
export const isTopicTool = (name: string): boolean =>
|
||||||
name === UPDATE_TOPIC_TOOL_NAME || name === UPDATE_TOPIC_DISPLAY_NAME;
|
name === UPDATE_TOPIC_TOOL_NAME || name === UPDATE_TOPIC_DISPLAY_NAME;
|
||||||
|
|
||||||
export const TopicMessage: React.FC<TopicMessageProps> = ({ args }) => {
|
export const TopicMessage: React.FC<TopicMessageProps> = ({
|
||||||
|
callId,
|
||||||
|
args,
|
||||||
|
availableTerminalHeight,
|
||||||
|
isExpandable = true,
|
||||||
|
}) => {
|
||||||
|
const { isExpanded: isExpandedInContext, toggleExpansion } = useToolActions();
|
||||||
|
|
||||||
|
// Expansion is active if either:
|
||||||
|
// 1. The individual callId is expanded in the ToolActionsContext
|
||||||
|
// 2. The entire turn is expanded (Ctrl+O) which sets availableTerminalHeight to undefined
|
||||||
|
const isExpanded =
|
||||||
|
(isExpandedInContext ? isExpandedInContext(callId) : false) ||
|
||||||
|
availableTerminalHeight === undefined;
|
||||||
|
|
||||||
|
const overflowActions = useOverflowActions();
|
||||||
|
const uniqueId = useId();
|
||||||
|
const overflowId = `topic-${uniqueId}`;
|
||||||
|
const containerRef = useRef<DOMElement>(null);
|
||||||
|
|
||||||
const rawTitle = args?.[TOPIC_PARAM_TITLE];
|
const rawTitle = args?.[TOPIC_PARAM_TITLE];
|
||||||
const title = typeof rawTitle === 'string' ? rawTitle : undefined;
|
const title = typeof rawTitle === 'string' ? rawTitle : undefined;
|
||||||
const rawIntent =
|
|
||||||
args?.[TOPIC_PARAM_STRATEGIC_INTENT] || args?.[TOPIC_PARAM_SUMMARY];
|
const rawStrategicIntent = args?.[TOPIC_PARAM_STRATEGIC_INTENT];
|
||||||
const intent = typeof rawIntent === 'string' ? rawIntent : undefined;
|
const strategicIntent =
|
||||||
|
typeof rawStrategicIntent === 'string' ? rawStrategicIntent : undefined;
|
||||||
|
|
||||||
|
const rawSummary = args?.[TOPIC_PARAM_SUMMARY];
|
||||||
|
const summary = typeof rawSummary === 'string' ? rawSummary : undefined;
|
||||||
|
|
||||||
|
// Top line intent: prefer strategic_intent, fallback to summary
|
||||||
|
const intent = strategicIntent || summary;
|
||||||
|
|
||||||
|
// Extra summary: only if both exist and are different (or just summary if we want to show it below)
|
||||||
|
const hasExtraSummary = !!(
|
||||||
|
strategicIntent &&
|
||||||
|
summary &&
|
||||||
|
strategicIntent !== summary
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleToggle = useCallback(() => {
|
||||||
|
if (toggleExpansion && hasExtraSummary) {
|
||||||
|
toggleExpansion(callId);
|
||||||
|
}
|
||||||
|
}, [toggleExpansion, hasExtraSummary, callId]);
|
||||||
|
|
||||||
|
useMouseClick(containerRef, handleToggle, {
|
||||||
|
isActive: isExpandable && hasExtraSummary,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Only register if there is more content (summary) and it's currently hidden
|
||||||
|
const hasHiddenContent = isExpandable && hasExtraSummary && !isExpanded;
|
||||||
|
|
||||||
|
if (hasHiddenContent && overflowActions) {
|
||||||
|
overflowActions.addOverflowingId(overflowId);
|
||||||
|
} else if (overflowActions) {
|
||||||
|
overflowActions.removeOverflowingId(overflowId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
overflowActions?.removeOverflowingId(overflowId);
|
||||||
|
};
|
||||||
|
}, [isExpandable, hasExtraSummary, isExpanded, overflowActions, overflowId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box flexDirection="row" marginLeft={2} flexWrap="wrap">
|
<Box ref={containerRef} flexDirection="column" marginLeft={2}>
|
||||||
<Text color={theme.text.primary} bold wrap="truncate-end">
|
<Box flexDirection="row" flexWrap="wrap">
|
||||||
{title || 'Topic'}
|
<Text color={theme.text.primary} bold wrap="truncate-end">
|
||||||
{intent && <Text>: </Text>}
|
{title || 'Topic'}
|
||||||
</Text>
|
{intent && <Text>: </Text>}
|
||||||
{intent && (
|
|
||||||
<Text color={theme.text.secondary} wrap="wrap">
|
|
||||||
{intent}
|
|
||||||
</Text>
|
</Text>
|
||||||
|
{intent && (
|
||||||
|
<Text color={theme.text.secondary} wrap="wrap">
|
||||||
|
{intent}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
{isExpanded && hasExtraSummary && summary && (
|
||||||
|
<Box marginTop={1} marginLeft={0}>
|
||||||
|
<Text color={theme.text.secondary} wrap="wrap">
|
||||||
|
{summary}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||