Compare commits

...

33 Commits

Author SHA1 Message Date
cynthialong0-0 9f81d3752c improve 2026-05-14 14:56:21 +00:00
cynthialong0-0 9f29ab56bc Merge branch 'main' into fix/ctrl-c-text 2026-05-13 10:41:07 -07:00
Coco Sheng 297d3a3067 fix(core): preserve OAuth refresh tokens during rotation and retrieval (#26924) 2026-05-13 17:19:05 +00:00
David Pierce 749657cbf9 feat(cli): merge Auto modes into a single Auto mode (#26714) 2026-05-13 16:55:43 +00:00
cynthialong0-0 83f46557c0 fix ctrl c conflict behavior 2026-05-13 16:46:27 +00:00
Adam Weidman 8cda688fe2 feat(core): change agent registration to first-wins and prioritize project (#26953) 2026-05-13 01:33:12 +00:00
gemini-cli-robot 5ee05c775e Changelog for v0.43.0-preview.0 (#26959)
Co-authored-by: gemini-cli-robot <224641728+gemini-cli-robot@users.noreply.github.com>
2026-05-13 00:01:24 +00:00
mahadevan 31d5947d37 Refactor: Eliminate no-unsafe-return suppressions via strict type validation (#20668)
Signed-off-by: M-DEV-1 <mahadevankizhakkedathu@gmail.com>
Co-authored-by: Tommaso Sciortino <sciortino@gmail.com>
2026-05-12 23:45:58 +00:00
gemini-cli-robot 8f03aa320e Changelog for v0.42.0 (#26958)
Co-authored-by: gemini-cli-robot <224641728+gemini-cli-robot@users.noreply.github.com>
2026-05-12 22:54:14 +00:00
gemini-cli-robot 583839ba46 chore(release): bump version to 0.44.0-nightly.20260512.g022e8baef (#26957) 2026-05-12 22:44:04 +00:00
Tommaso Sciortino 022e8baefc fix(ci): configure git remote with token for authentication (#26949) 2026-05-12 15:15:16 -07:00
Coco Sheng 120dfc724d fix(core): update read_file schema for v1 compatibility (#22183) (#26922) 2026-05-12 21:41:49 +00:00
Dev Randalpura c37b9113d7 fix(ui): fixed line wrap padding for selection lists (#26944) 2026-05-12 21:24:40 +00:00
Christian Gunderman 2334e9b1c4 Incremental refactor repo agent towards skills-based composition (#26717)
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-05-12 20:37:09 +00:00
Tommaso Sciortino f901a4e6b7 fix(ci): replace brittle --no-tag with explicit staging-tmp tag (#26940) 2026-05-12 13:39:54 -07:00
Adam Weidman c987b99394 refactor(core): introduce SubagentState enum for progress (#26934) 2026-05-12 18:58:25 +00:00
Coco Sheng c4973d01da ci: actively triage missing priority labels and intelligently clean up conflicting labels (#26865) 2026-05-12 18:33:55 +00:00
kevinjwang1 27a39b04b0 Enable NumericalRouter when using dynamic model configs (#26929) 2026-05-12 18:06:21 +00:00
Sandy Tao ebe15553a9 Exclude extension context from skill extraction agent (#26879) 2026-05-12 10:45:19 -07:00
Yulong Wu bc730b2c0f fix (telemetry): inject quota_project_id to prevent fallback to default oauth client (#26698)
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: Tommaso Sciortino <sciortino@gmail.com>
2026-05-12 17:02:15 +00:00
Gal Zahavi 9fe8643552 chore: update checkout action configuration in workflows (#26897) 2026-05-12 16:58:37 +00:00
joshualitt 07792f98cd feat(context): Introduce adaptive token calculator to more accurately calculate content sizes. (#26888) 2026-05-12 15:51:20 +00:00
Coco Sheng 7a9ed4c20a fix: respect explicit model selection after Flash quota exhaustion (#26759) (#26872) 2026-05-12 14:26:50 +00:00
Kuroda Kayn 11a9edc808 fix(cli): restore resume for legacy sessions (#26577)
Co-authored-by: Tommaso Sciortino <sciortino@gmail.com>
2026-05-12 00:28:47 +00:00
Adam Weidman 24b98ade86 fix(cli): enable adk non-interactive session (#26895) 2026-05-11 23:38:23 +00:00
Eswar809 9f759f97a2 fix(core): ignore .pak and .rpa game archive formats by default (#26884)
Co-authored-by: Tommaso Sciortino <sciortino@gmail.com>
2026-05-11 21:58:08 +00:00
Franco Pieri 9ff7304391 Allow Enter to select session while in search mode in /resume (#21523)
Co-authored-by: Tommaso Sciortino <sciortino@gmail.com>
2026-05-11 21:46:00 +00:00
Neil Nair 84fc5cd533 Fix/vscode run current file ts (#22894)
Co-authored-by: Spencer <spencertang@google.com>
2026-05-11 21:21:42 +00:00
Daniel Weis e1b3ce5b36 revert 6b9b778d82 (#26893) 2026-05-11 21:07:54 +00:00
Suhaan Raqeeb Khavas 8e58df72c6 fix: prevent EISDIR crash when customIgnoreFilePaths contains directories (#19868) (#19898)
Co-authored-by: Tommaso Sciortino <sciortino@gmail.com>
2026-05-11 20:46:08 +00:00
Dev Randalpura c0d5ab1f1e fix(ui): added a gutter width to the input prompt width calculation (#26882) 2026-05-11 20:26:48 +00:00
Coco Sheng 1340c96071 fix(core): handle malformed projects.json in ProjectRegistry (#26885) 2026-05-11 20:19:01 +00:00
Daniel Weis f8198a25d8 fix(routing): Refactor tool turn handling for the conversation history in NumericalClassifierStrategy to prevent 400 Bad Request (#26761) 2026-05-11 20:09:38 +00:00
187 changed files with 4292 additions and 1546 deletions
+4 -1
View File
@@ -5,7 +5,10 @@
"autoMemory": true,
"memoryManager": true,
"topicUpdateNarration": true,
"voiceMode": true
"voiceMode": true,
"adk": {
"agentSessionNoninteractiveEnabled": true
}
},
"general": {
"devtools": true
+10 -8
View File
@@ -114,13 +114,14 @@ runs:
BRANCH_NAME: '${{ steps.release_branch.outputs.BRANCH_NAME }}'
DRY_RUN: '${{ inputs.dry-run }}'
RELEASE_TAG: '${{ inputs.release-tag }}'
GIT_PUSH_TOKEN: '${{ inputs.github-release-token || inputs.github-token }}'
run: |-
set -e
git add package.json package-lock.json packages/*/package.json
git commit -m "chore(release): ${RELEASE_TAG}"
if [[ "${DRY_RUN}" == "false" ]]; then
echo "Pushing release branch to remote..."
git push --set-upstream origin "${BRANCH_NAME}" --follow-tags
git push "https://x-access-token:${GIT_PUSH_TOKEN}@github.com/${{ github.repository }}.git" "HEAD:${BRANCH_NAME}" --follow-tags
else
echo "Dry run enabled. Skipping push."
fi
@@ -174,9 +175,9 @@ runs:
npm publish \
--dry-run="${INPUTS_DRY_RUN}" \
--workspace="${INPUTS_CORE_PACKAGE_NAME}" \
--no-tag
--tag staging-tmp
if [[ "${INPUTS_DRY_RUN}" == "false" ]]; then
npm dist-tag rm ${INPUTS_CORE_PACKAGE_NAME} false
npm dist-tag rm ${INPUTS_CORE_PACKAGE_NAME} staging-tmp
fi
- name: '🔗 Install latest core package'
@@ -222,9 +223,9 @@ runs:
npm publish \
--dry-run="${INPUTS_DRY_RUN}" \
--workspace="${INPUTS_CLI_PACKAGE_NAME}" \
--no-tag
--tag staging-tmp
if [[ "${INPUTS_DRY_RUN}" == "false" ]]; then
npm dist-tag rm ${INPUTS_CLI_PACKAGE_NAME} false
npm dist-tag rm ${INPUTS_CLI_PACKAGE_NAME} staging-tmp
fi
- name: 'Get a2a-server Token'
@@ -249,9 +250,9 @@ runs:
npm publish \
--dry-run="${INPUTS_DRY_RUN}" \
--workspace="${INPUTS_A2A_PACKAGE_NAME}" \
--no-tag
--tag staging-tmp
if [[ "${INPUTS_DRY_RUN}" == "false" ]]; then
npm dist-tag rm ${INPUTS_A2A_PACKAGE_NAME} false
npm dist-tag rm ${INPUTS_A2A_PACKAGE_NAME} staging-tmp
fi
- name: '🔬 Verify NPM release by version'
@@ -336,7 +337,8 @@ runs:
shell: 'bash'
run: |
echo "Cleaning up release branch ${STEPS_RELEASE_BRANCH_OUTPUTS_BRANCH_NAME}..."
git push origin --delete "${STEPS_RELEASE_BRANCH_OUTPUTS_BRANCH_NAME}"
git push "https://x-access-token:${GIT_PUSH_TOKEN}@github.com/${{ github.repository }}.git" --delete "${STEPS_RELEASE_BRANCH_OUTPUTS_BRANCH_NAME}"
env:
GIT_PUSH_TOKEN: '${{ inputs.github-release-token || inputs.github-token }}'
STEPS_RELEASE_BRANCH_OUTPUTS_BRANCH_NAME: '${{ steps.release_branch.outputs.BRANCH_NAME }}'
+41 -4
View File
@@ -85,14 +85,51 @@ module.exports = async ({ github, context, core }) => {
continue;
}
const labelsToAdd = entry.labels_to_add || [];
labelsToAdd.push('status/bot-triaged');
let labelsToAdd = entry.labels_to_add || [];
let labelsToRemove = entry.labels_to_remove || [];
labelsToRemove.push('status/need-triage');
// Deduplicate array
if (labelsToAdd.includes('status/manual-triage')) {
// If the AI flagged it for manual triage, remove bot-triaged if it exists
labelsToRemove.push('status/bot-triaged');
// Ensure we don't accidentally try to add bot-triaged if the AI returned it
labelsToAdd = labelsToAdd.filter((l) => l !== 'status/bot-triaged');
} else {
// Standard successful bot triage
labelsToAdd.push('status/bot-triaged');
}
// Deduplicate arrays
labelsToAdd = [...new Set(labelsToAdd)];
labelsToRemove = [...new Set(labelsToRemove)];
// Enforce mutually exclusive area labels
const areaLabelsToAdd = labelsToAdd.filter((l) => l.startsWith('area/'));
if (areaLabelsToAdd.length > 1) {
core.warning(
`Issue #${issueNumber} has multiple area labels to add: ${areaLabelsToAdd.join(', ')}. Keeping only the first one.`,
);
const firstArea = areaLabelsToAdd[0];
labelsToAdd = labelsToAdd.filter(
(l) => !l.startsWith('area/') || l === firstArea,
);
}
// Enforce mutually exclusive priority labels
const priorityLabelsToAdd = labelsToAdd.filter((l) =>
l.startsWith('priority/'),
);
if (priorityLabelsToAdd.length > 1) {
core.warning(
`Issue #${issueNumber} has multiple priority labels to add: ${priorityLabelsToAdd.join(', ')}. Keeping only the first one.`,
);
const firstPriority = priorityLabelsToAdd[0];
labelsToAdd = labelsToAdd.filter(
(l) => !l.startsWith('priority/') || l === firstPriority,
);
}
if (labelsToAdd.length > 0) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
@@ -0,0 +1,60 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
const fs = require('node:fs');
module.exports = async ({ github, context, core }) => {
core.info('Fetching open issues to check for conflicting labels...');
const issues = await github.paginate(github.rest.issues.listForRepo, {
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
per_page: 100,
});
const conflictingLabelIssues = [];
for (const issue of issues) {
if (issue.pull_request) continue;
const areaLabels = issue.labels
.filter((l) => l.name && l.name.startsWith('area/'))
.map((l) => l.name);
const priorityLabels = issue.labels
.filter((l) => l.name && l.name.startsWith('priority/'))
.map((l) => l.name);
if (areaLabels.length > 1 || priorityLabels.length > 1) {
let message = `Issue #${issue.number} has conflicting labels:`;
if (areaLabels.length > 1)
message += ` multiple areas (${areaLabels.join(', ')}).`;
if (priorityLabels.length > 1)
message += ` multiple priorities (${priorityLabels.join(', ')}).`;
core.info(message);
conflictingLabelIssues.push({
number: issue.number,
title: issue.title,
body: issue.body || '',
});
}
}
// Limit to 50 to avoid overwhelming the AI in a single run
const issuesToProcess = conflictingLabelIssues.slice(0, 50);
fs.writeFileSync(
'conflicting_labels_issues.json',
JSON.stringify(issuesToProcess, null, 2),
);
core.info(
`Found ${conflictingLabelIssues.length} issues with conflicting labels. Wrote ${issuesToProcess.length} to conflicting_labels_issues.json`,
);
};
@@ -30,6 +30,7 @@ jobs:
uses: 'actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5' # ratchet:actions/checkout@v4
with:
ref: '${{ inputs.ref || github.ref }}'
persist-credentials: false
- name: 'Set up Node.js'
uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions/setup-node@v4
+4
View File
@@ -148,6 +148,7 @@ jobs:
with:
ref: '${{ needs.parse_run_context.outputs.sha }}'
repository: '${{ needs.parse_run_context.outputs.repository }}'
persist-credentials: false
- name: 'Set up Node.js ${{ matrix.node-version }}'
uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions-node@v4
@@ -193,6 +194,7 @@ jobs:
with:
ref: '${{ needs.parse_run_context.outputs.sha }}'
repository: '${{ needs.parse_run_context.outputs.repository }}'
persist-credentials: false
- name: 'Set up Node.js 20.x'
uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions-node@v4
@@ -233,6 +235,7 @@ jobs:
with:
ref: '${{ needs.parse_run_context.outputs.sha }}'
repository: '${{ needs.parse_run_context.outputs.repository }}'
persist-credentials: false
- name: 'Set up Node.js 20.x'
uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions-node@v4
@@ -314,6 +317,7 @@ jobs:
with:
ref: '${{ needs.parse_run_context.outputs.sha }}'
repository: '${{ needs.parse_run_context.outputs.repository }}'
persist-credentials: false
fetch-depth: 0
- name: 'Set up Node.js 20.x'
+10
View File
@@ -57,6 +57,7 @@ jobs:
- name: 'Checkout'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
with:
persist-credentials: false
ref: '${{ github.event.inputs.branch_ref || github.ref }}'
fetch-depth: 0
@@ -130,6 +131,8 @@ jobs:
steps:
- name: 'Checkout'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
with:
persist-credentials: false
- name: 'Link Checker'
uses: 'lycheeverse/lychee-action@885c65f3dc543b57c898c8099f4e08c8afd178a2' # ratchet: lycheeverse/lychee-action@v2.6.1
with:
@@ -157,6 +160,8 @@ jobs:
steps:
- name: 'Checkout'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
with:
persist-credentials: false
- name: 'Set up Node.js ${{ matrix.node-version }}'
uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions/setup-node@v4
@@ -252,6 +257,8 @@ jobs:
steps:
- name: 'Checkout'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
with:
persist-credentials: false
- name: 'Set up Node.js ${{ matrix.node-version }}'
uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions/setup-node@v4
@@ -339,6 +346,7 @@ jobs:
- name: 'Checkout'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
with:
persist-credentials: false
ref: '${{ github.event.inputs.branch_ref || github.ref }}'
- name: 'Initialize CodeQL'
@@ -363,6 +371,7 @@ jobs:
- name: 'Checkout'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
with:
persist-credentials: false
ref: '${{ github.event.inputs.branch_ref || github.ref }}'
fetch-depth: 1
@@ -390,6 +399,7 @@ jobs:
- name: 'Checkout'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
with:
persist-credentials: false
ref: '${{ github.event.inputs.branch_ref || github.ref }}'
- name: 'Set up Node.js 20.x'
+3
View File
@@ -43,6 +43,7 @@ jobs:
with:
ref: '${{ github.event.pull_request.head.sha }}'
repository: '${{ github.repository }}'
persist-credentials: false
- name: 'Set up Node.js ${{ matrix.node-version }}'
uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions-node@v4
@@ -86,6 +87,7 @@ jobs:
with:
ref: '${{ github.event.pull_request.head.sha }}'
repository: '${{ github.repository }}'
persist-credentials: false
- name: 'Set up Node.js 20.x'
uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions-node@v4
@@ -125,6 +127,7 @@ jobs:
with:
ref: '${{ github.event.pull_request.head.sha }}'
repository: '${{ github.repository }}'
persist-credentials: false
- name: 'Set up Node.js 20.x'
uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions-node@v4
+1
View File
@@ -19,6 +19,7 @@ jobs:
with:
fetch-depth: 0
ref: 'main'
persist-credentials: false
- name: 'Set up Node.js'
uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020'
+2
View File
@@ -24,6 +24,8 @@ jobs:
steps:
- name: 'Checkout'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
with:
persist-credentials: false
- name: 'Setup Pages'
uses: 'actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b' # ratchet:actions/configure-pages@v5
+2
View File
@@ -38,6 +38,7 @@ jobs:
with:
# Check out the trusted code from main for detection
fetch-depth: 0
persist-credentials: false
- name: 'Detect Steering Changes'
id: 'detect'
@@ -102,6 +103,7 @@ jobs:
# This only runs AFTER manual approval
ref: '${{ github.event.pull_request.head.sha }}'
fetch-depth: 0
persist-credentials: false
- name: 'Remove Approval Notification'
# Run even if other steps fail, to ensure we clean up the "Action Required" message
+4
View File
@@ -46,6 +46,8 @@ jobs:
steps:
- name: 'Checkout'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
with:
persist-credentials: false
- name: 'Set up Node.js'
uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions/setup-node@v4
@@ -105,6 +107,8 @@ jobs:
steps:
- name: 'Checkout'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
with:
persist-credentials: false
- name: 'Download Logs'
uses: 'actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806' # ratchet:actions/download-artifact@v4
@@ -48,6 +48,8 @@ jobs:
steps:
- name: 'Checkout'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
with:
persist-credentials: false
- name: 'Log in to GitHub Container Registry'
uses: 'docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1' # ratchet:docker/login-action@v3
@@ -90,6 +90,8 @@ jobs:
- name: 'Checkout'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
with:
persist-credentials: false
- name: 'Generate GitHub App Token'
id: 'generate_token'
+21 -14
View File
@@ -29,7 +29,7 @@ on:
default: false
concurrency:
group: '${{ github.workflow }}-${{ github.event.issue.number || github.event.pull_request.number || github.event.inputs.issue_number || github.ref }}'
group: '${{ github.workflow }}-${{ github.event.issue.number || github.event.inputs.issue_number || github.ref }}'
cancel-in-progress: true
jobs:
@@ -41,14 +41,12 @@ jobs:
github.event_name == 'schedule' ||
(github.event_name == 'workflow_dispatch' && github.event.inputs.run_interactive != 'true') ||
(github.event_name == 'workflow_dispatch' && github.event.inputs.run_interactive == 'true') ||
(github.event_name == 'issue_comment' && github.event.comment.user.login != 'gemini-cli[bot]' && contains(github.event.comment.body, '@gemini-cli') && contains(fromJSON('["COLLABORATOR", "MEMBER", "OWNER"]'), github.event.comment.author_association)) ||
(github.event_name == 'pull_request_review_comment' && github.event.comment.user.login != 'gemini-cli[bot]' && contains(github.event.comment.body, '@gemini-cli') && contains(fromJSON('["COLLABORATOR", "MEMBER", "OWNER"]'), github.event.comment.author_association))
(github.event_name == 'issue_comment' && github.event.comment.user.login != 'gemini-cli[bot]' && contains(github.event.comment.body, '@gemini-cli') && contains(fromJSON('["COLLABORATOR", "MEMBER", "OWNER"]'), github.event.comment.author_association))
)
# The reasoning phase is strictly readonly.
permissions:
contents: 'read'
issues: 'read'
pull-requests: 'read'
actions: 'read'
env:
GEMINI_CLI_TRUST_WORKSPACE: 'true'
@@ -57,7 +55,7 @@ jobs:
id: 'determine_ref'
env:
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
ISSUE_NUMBER: '${{ github.event.issue.number || github.event.pull_request.number || github.event.inputs.issue_number }}'
ISSUE_NUMBER: '${{ github.event.issue.number || github.event.inputs.issue_number }}'
run: |
REF="${{ github.ref }}"
if [ -n "$ISSUE_NUMBER" ]; then
@@ -125,11 +123,12 @@ jobs:
GEMINI_API_KEY: '${{ secrets.GEMINI_API_KEY }}'
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
GEMINI_MODEL: 'gemini-3-flash-preview'
GEMINI_CLI_HOME: 'tools/gemini-cli-bot'
ENABLE_PRS: "${{ github.event.inputs.enable_prs || 'false' }}"
TRIGGER_ISSUE_NUMBER: '${{ github.event.issue.number || github.event.inputs.issue_number }}'
TRIGGER_COMMENT_ID: '${{ github.event.comment.id || github.event.inputs.comment_id }}'
run: |
PROMPT_PATH="tools/gemini-cli-bot/brain/metrics.md"
PROMPT_PATH="tools/gemini-cli-bot/brain/scheduled.md"
if [ "${{ github.event_name }}" = "issue_comment" ] || [ "${{ github.event.inputs.run_interactive }}" = "true" ]; then
PROMPT_PATH="tools/gemini-cli-bot/brain/interactive.md"
export ENABLE_PRS="true"
@@ -152,9 +151,16 @@ jobs:
echo "</untrusted_context>" >> trigger_context.md
fi
cat trigger_context.md "$PROMPT_PATH" tools/gemini-cli-bot/brain/common.md > combined_prompt.md
if [ "$ENABLE_PRS" = "true" ]; then
echo "**System Directive**: PR creation is ENABLED for this run. You MUST activate the **'prs' skill** to stage your changes and generate a \`pr-description.md\` file if you are proposing fixes." >> trigger_context.md
echo "**CRITICAL System Directive**: You MUST ONLY propose and implement a **SINGLE** improvement or fix per run. Bundling unrelated changes (e.g., a documentation update and a script fix, or a metrics update and a logic fix) into a single PR is STRICTLY FORBIDDEN and will result in immediate rejection during the critique phase. If you identify multiple issues, pick the most impactful one and ignore the others for now." >> trigger_context.md
else
echo "**System Directive**: PR creation is DISABLED for this run. You MUST NOT stage files or attempt to create a PR description." >> trigger_context.md
fi
echo "" >> trigger_context.md
node bundle/gemini.js --policy tools/gemini-cli-bot/ci-policy.toml -p "$(cat combined_prompt.md)"
cat trigger_context.md "$PROMPT_PATH" > combined_prompt.md
node bundle/gemini.js --policy tools/gemini-cli-bot/ci-policy.toml --prompt="$(cat combined_prompt.md)"
if [ -n "$TRIGGER_ISSUE_NUMBER" ] && [ ! -s "issue-comment.md" ] && [ ! -s "pr-comment.md" ]; then
echo "Agent failed to respond. Generating fallback error message."
@@ -164,17 +170,18 @@ jobs:
fi
- name: 'Run Critique Phase'
if: "${{ github.event.inputs.enable_prs == 'true' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || github.event.inputs.run_interactive == 'true' }}"
if: "${{ github.event.inputs.enable_prs == 'true' || github.event_name == 'issue_comment' || github.event.inputs.run_interactive == 'true' }}"
env:
GEMINI_API_KEY: '${{ secrets.GEMINI_API_KEY }}'
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
GEMINI_MODEL: 'gemini-3-flash-preview'
GEMINI_CLI_HOME: 'tools/gemini-cli-bot'
run: |
if git diff --staged --quiet; then
echo "No changes staged. Skipping critique."
echo "[APPROVED]" > critique_result.txt
else
node bundle/gemini.js --policy tools/gemini-cli-bot/ci-policy.toml -p "$(cat tools/gemini-cli-bot/brain/critique.md)" 2>&1 | tee critique_output.log
node bundle/gemini.js --policy tools/gemini-cli-bot/ci-policy.toml --prompt="$(cat tools/gemini-cli-bot/.gemini/skills/critique/SKILL.md)" 2>&1 | tee critique_output.log
if [ "${PIPESTATUS[0]}" -eq 0 ] && grep -q "\[APPROVED\]" critique_output.log && ! grep -q "\[REJECTED\]" critique_output.log; then
echo "[APPROVED]" > critique_result.txt
@@ -185,7 +192,7 @@ jobs:
fi
- name: 'Generate Patch'
if: "${{ github.event.inputs.enable_prs == 'true' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || github.event.inputs.run_interactive == 'true' }}"
if: "${{ github.event.inputs.enable_prs == 'true' || github.event_name == 'issue_comment' || github.event.inputs.run_interactive == 'true' }}"
run: |
touch bot-changes.patch
touch pr-description.md
@@ -223,7 +230,7 @@ jobs:
steps:
- name: 'Generate GitHub App Token 🔑'
id: 'generate_token'
if: "${{ github.event.inputs.enable_prs == 'true' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || github.event.inputs.run_interactive == 'true' }}"
if: "${{ github.event.inputs.enable_prs == 'true' || github.event_name == 'issue_comment' || github.event.inputs.run_interactive == 'true' }}"
uses: 'actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b' # ratchet:actions/create-github-app-token@v2
with:
app-id: '${{ secrets.APP_ID }}'
@@ -238,7 +245,7 @@ jobs:
id: 'determine_ref'
env:
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
ISSUE_NUMBER: '${{ github.event.issue.number || github.event.pull_request.number || github.event.inputs.issue_number }}'
ISSUE_NUMBER: '${{ github.event.issue.number || github.event.inputs.issue_number }}'
run: |
REF="main"
if [ -n "$ISSUE_NUMBER" ]; then
@@ -263,7 +270,7 @@ jobs:
path: '${{ runner.temp }}/brain-data/'
- name: 'Create or Update PR'
if: "${{ github.event.inputs.enable_prs == 'true' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || github.event.inputs.run_interactive == 'true' }}"
if: "${{ github.event.inputs.enable_prs == 'true' || github.event_name == 'issue_comment' || github.event.inputs.run_interactive == 'true' }}"
env:
GH_TOKEN: '${{ steps.generate_token.outputs.token }}'
FALLBACK_PAT: '${{ secrets.GEMINI_CLI_ROBOT_GITHUB_PAT }}'
@@ -23,6 +23,7 @@ jobs:
- name: 'Checkout'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
with:
persist-credentials: false
fetch-depth: 0
- name: 'Setup Node.js'
@@ -33,6 +33,8 @@ jobs:
- name: 'Checkout repository'
uses: 'actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683' # ratchet:actions/checkout@v4
with:
persist-credentials: false
- name: 'Lifecycle Management'
uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea'
@@ -28,6 +28,8 @@ jobs:
steps:
- name: 'Checkout'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
with:
persist-credentials: false
- name: 'Log in to GitHub Container Registry'
uses: 'docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1' # ratchet:docker/login-action@v3
@@ -30,6 +30,8 @@ jobs:
steps:
- name: 'Checkout'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
with:
persist-credentials: false
- name: 'Generate GitHub App Token'
id: 'generate_token'
@@ -61,6 +63,16 @@ jobs:
const syncIssueTypes = require('./.github/scripts/sync-issue-types.cjs');
await syncIssueTypes({ github, context, core });
- name: 'Find Issues with Conflicting Labels'
if: |-
${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }}
uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea'
with:
github-token: '${{ steps.generate_token.outputs.token }}'
script: |-
const findConflictingLabels = require('./.github/scripts/find-conflicting-labels.cjs');
await findConflictingLabels({ github, context, core });
- name: 'Find untriaged issues'
if: |-
${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }}
@@ -81,22 +93,31 @@ jobs:
echo '🏷️ Finding issues missing priority labels...'
gh issue list --repo "${GITHUB_REPOSITORY}" \
--search 'is:open is:issue -label:status/bot-triaged -label:priority/p0 -label:priority/p1 -label:priority/p2 -label:priority/p3 -label:priority/unknown' --limit 50 --json number,title,body > no_priority_issues.json
--search 'is:open is:issue -label:priority/p0 -label:priority/p1 -label:priority/p2 -label:priority/p3 -label:priority/unknown' --limit 50 --json number,title,body > no_priority_issues.json
echo '📏 Finding issues missing effort labels...'
gh issue list --repo "${GITHUB_REPOSITORY}" \
--search 'is:open is:issue -label:status/bot-triaged -label:effort/small -label:effort/medium -label:effort/large label:area/core,area/extensions,area/site,area/non-interactive' --limit 20 --json number,title,body > no_effort_issues.json
--search 'is:open is:issue -label:effort/small -label:effort/medium -label:effort/large label:area/core,area/extensions,area/site,area/non-interactive' --limit 5 --json number,title,body > no_effort_issues.json
echo '🔄 Merging and deduplicating issues...'
jq -c -s 'add | unique_by(.number)' no_area_issues.json no_kind_issues.json no_priority_issues.json no_effort_issues.json no_type_issues.json > issues_to_triage.json
echo '🔄 Merging and deduplicating standard triage issues...'
if [ ! -f conflicting_labels_issues.json ]; then echo "[]" > conflicting_labels_issues.json; fi
jq -c -s 'add | unique_by(.number)' no_area_issues.json no_kind_issues.json no_priority_issues.json conflicting_labels_issues.json > standard_issues_to_triage.json
ISSUE_COUNT="$(jq 'length' issues_to_triage.json)"
if [ "$ISSUE_COUNT" -gt 0 ]; then
echo '📏 Deduplicating effort issues...'
jq -c -s 'add | unique_by(.number)' no_effort_issues.json > effort_issues_to_triage.json
STANDARD_COUNT="$(jq 'length' standard_issues_to_triage.json)"
EFFORT_COUNT="$(jq 'length' effort_issues_to_triage.json)"
if [ "$STANDARD_COUNT" -gt 0 ] || [ "$EFFORT_COUNT" -gt 0 ]; then
echo "has_issues=true" >> "${GITHUB_OUTPUT}"
echo "has_standard_issues=$([ "$STANDARD_COUNT" -gt 0 ] && echo 'true' || echo 'false')" >> "${GITHUB_OUTPUT}"
echo "has_effort_issues=$([ "$EFFORT_COUNT" -gt 0 ] && echo 'true' || echo 'false')" >> "${GITHUB_OUTPUT}"
else
echo "has_issues=false" >> "${GITHUB_OUTPUT}"
echo "has_standard_issues=false" >> "${GITHUB_OUTPUT}"
echo "has_effort_issues=false" >> "${GITHUB_OUTPUT}"
fi
echo "✅ Found ${ISSUE_COUNT} unique issues to triage! 🎯"
echo "✅ Found ${STANDARD_COUNT} standard issues and ${EFFORT_COUNT} effort issues to triage! 🎯"
- name: 'Create Gemini CLI Experiments Override'
if: |-
@@ -129,11 +150,128 @@ jobs:
core.info(`Found ${labelNames.length} labels: ${labelNames.join(', ')}`);
return labelNames;
- name: 'Run Gemini Issue Analysis'
- name: 'Run Standard Triage Analysis'
if: |-
steps.get_issue_from_event.outputs.has_issues == 'true' || steps.find_issues.outputs.has_issues == 'true'
steps.get_issue_from_event.outputs.has_issues == 'true' || steps.find_issues.outputs.has_standard_issues == 'true'
uses: 'google-github-actions/run-gemini-cli@a3bf79042542528e91937b3a3a6fbc4967ee3c31' # ratchet:google-github-actions/run-gemini-cli@v0
id: 'gemini_issue_analysis'
id: 'gemini_standard_issue_analysis'
env:
GITHUB_TOKEN: '' # Do not pass any auth token here since this runs on untrusted inputs
REPOSITORY: '${{ github.repository }}'
AVAILABLE_LABELS: '${{ steps.get_labels.outputs.available_labels }}'
GEMINI_CLI_TRUST_WORKSPACE: 'true'
GEMINI_EXP: 'gemini_exp.json'
GEMINI_STRICT_TELEMETRY_LIMITS: 'true'
with:
gcp_workload_identity_provider: '${{ vars.GCP_WIF_PROVIDER }}'
gcp_project_id: '${{ vars.GOOGLE_CLOUD_PROJECT }}'
gcp_location: '${{ vars.GOOGLE_CLOUD_LOCATION }}'
gcp_service_account: '${{ vars.SERVICE_ACCOUNT_EMAIL }}'
gemini_api_key: '${{ secrets.GEMINI_API_KEY }}'
use_vertex_ai: '${{ vars.GOOGLE_GENAI_USE_VERTEXAI }}'
use_gemini_code_assist: '${{ vars.GOOGLE_GENAI_USE_GCA }}'
settings: |-
{
"maxSessionTurns": 25,
"coreTools": [
"run_shell_command(echo)",
"read_file"
],
"telemetry": {
"enabled": true,
"target": "gcp"
}
}
prompt: |-
## Role
You are an issue triage assistant. Analyze issues and identify
appropriate labels. Use the available tools to gather information;
do not ask for information to be provided.
## Steps
1. You are only able to use the echo and read_file commands. Review the available labels in the environment variable: "${AVAILABLE_LABELS}".
2. Use the read_file tool to read the file "standard_issues_to_triage.json" which contains the JSON array of issues to triage.
3. Review the issue title, body and any comments provided in the JSON file.
4. Identify the most relevant labels from the existing labels, specifically focusing on area/*, kind/*, and priority/*.
5. Label Policy:
- If the issue already has a kind/ label, do not change it.
- If the issue has exactly ONE priority/ label, do not change it.
- If the issue is missing a priority/ label, OR if the issue currently has MULTIPLE priority/ labels, you must evaluate the issue's impact to determine exactly ONE priority level (priority/p0, priority/p1, priority/p2, priority/p3, or priority/unknown) based the guidelines. If you are fixing an issue with multiple priority/ labels, put the correct one in `labels_to_add` and put all the incorrect ones in `labels_to_remove`.
- If the issue has exactly ONE area/ label, do not change it.
- If the issue is missing an area/ label, OR if the issue currently has MULTIPLE area/ labels, select exactly ONE area/ label that best fits the issue. Issues MUST NOT have multiple area/ labels. If you are fixing an issue with multiple area/ labels, put the correct one in `labels_to_add` and put all the incorrect ones in `labels_to_remove`.
- If any of these are missing, select exactly ONE appropriate label for the missing category.
6. Identify other applicable labels based on the issue content, such as status/*, help wanted, good first issue, etc.
7. Give me a single short explanation about why you are selecting each label in the process.
8. Output a JSON array of objects, each containing the issue number
and the labels to add and remove, along with an explanation. For example:
```
[
{
"issue_number": 123,
"labels_to_add": ["area/core", "kind/bug", "priority/p2"],
"labels_to_remove": ["status/need-triage"],
"explanation": "This issue is a UI bug that needs to be addressed with medium priority."
}
]
```
If an issue cannot be classified, do not include it in the output array.
9. For each issue please check if CLI version is present, this is usually in the output of the /about command and will look like 0.1.5
- Anything more than 6 versions older than the most recent should add the status/need-retesting label
10. If you see that the issue doesn't look like it has sufficient information recommend the status/need-information label and leave a comment politely requesting the relevant information, eg.. if repro steps are missing request for repro steps. if version information is missing request for version information into the explanation section below.
11. If you think an issue might be a Priority/P0 do not apply the priority/p0 label. Instead apply a status/manual-triage label and include a note in your explanation.
12. If you are uncertain about a category, use the area/unknown, kind/question, or priority/unknown labels as appropriate. If you are extremely uncertain, apply the status/manual-triage label.
## Guidelines
- Output only valid JSON format
- Do not include any explanation or additional text, just the JSON
- Only use labels that already exist in the repository.
- Do not add comments or modify the issue content.
- Do not remove the following labels maintainer, help wanted or good first issue.
- Triage only the current issue.
- Identify exactly ONE area/ label. Do NOT assign multiple area/ labels to a single issue.
- Identify only one kind/ label (Do not apply kind/duplicate or kind/parent-issue)
- Identify exactly ONE priority/ label. Do NOT assign multiple priority/ labels to a single issue.
- Once you categorize the issue if it needs information bump down the priority by 1 eg.. a p0 would become a p1 a p1 would become a p2. P2 and P3 can stay as is in this scenario.
Categorization Guidelines (Priority):
P0 - Urgent Blocking Issues:
- Definition: Critical failures breaking core functionality for a large portion of users. Examples: CLI fails to launch globally, core commands (gemini run) crash on valid input, unhandled promise rejections on boot, critical security vulnerability.
- Note: You must apply status/manual-triage instead of priority/p0.
P1 - Critical but Workable:
- Definition: Severe issues without a reasonable workaround, significantly degrading the developer experience but not globally blocking. Examples: Specific tools failing consistently (e.g., `web_search` returns 500s), persistent PTY streaming hangs, memory leaks leading to OOM after short use.
P2 - Significant Issues:
- Definition: Affect some workflows but a clear workaround exists, or non-critical bugs. Examples: Theme flickering, confusing error messages, minor UI misalignment, failing to read deeply nested config files correctly.
P3 - Minor/Enhancements:
- Definition: Trivial bugs, typos, documentation requests, or feature requests.
Categorization Guidelines (Kind):
kind/bug: The issue is describing an unexpected behavior or failure in the application.
kind/enhancement: The issue is describing a feature request or an improvement to an existing feature.
kind/question: The issue is asking a question about how to use the CLI or about a specific feature.
Categorization Guidelines (Area):
area/agent: The "brain" of the CLI. Core agent logic, model quality, tool/function calling, memory, web search, generated code quality, sub-agents.
area/core: The fundamental CLI app. UI/UX, installation, OS compatibility, performance, command parsing, theming, flickering.
area/documentation: Website docs, READMEs, inline help text.
area/enterprise: Telemetry, Policy, Quota / Licensing
area/extensions: Gemini CLI extensions capability
area/non-interactive: GitHub Actions, SDK, 3P Integrations, Shell Scripting, Command line automation
area/platform: Platform specific behavior
area/security: Authentication, authorization, privacy, data leaks, credential storage.
- name: 'Stop Telemetry Collector'
if: |-
steps.find_issues.outputs.has_effort_issues == 'true'
run: 'docker rm -f gemini-telemetry-collector || true'
- name: 'Run Effort Triage Analysis'
if: |-
steps.find_issues.outputs.has_effort_issues == 'true'
uses: 'google-github-actions/run-gemini-cli@a3bf79042542528e91937b3a3a6fbc4967ee3c31' # ratchet:google-github-actions/run-gemini-cli@v0
id: 'gemini_effort_issue_analysis'
env:
GITHUB_TOKEN: '' # Do not pass any auth token here since this runs on untrusted inputs
REPOSITORY: '${{ github.repository }}'
@@ -166,57 +304,30 @@ jobs:
prompt: |-
## Role
You are an issue triage assistant. Analyze issues and identify
appropriate labels. Use the available tools to gather information;
do not ask for information to be provided.
You are an expert software architect. Analyze the provided GitHub issues and assign the correct `effort/*` label based on the codebase complexity.
## Steps
1. You are only able to use the echo and read_file commands. Review the available labels in the environment variable: "${AVAILABLE_LABELS}".
2. Use the read_file tool to read the file "issues_to_triage.json" which contains the JSON array of issues to triage.
3. Review the issue title, body and any comments provided in the JSON file.
4. Identify the most relevant labels from the existing labels, specifically focusing on area/*, kind/*, priority/*, and effort/*.
5. Label Policy:
- If the issue already has a kind/ label, do not change it.
- If the issue already has a priority/ label, do not change it.
- If the issue already has an area/ label, do not change it.
- If the issue already has an effort/ label, do not change it.
- If the issue is missing an effort/ label AND its area is area/core, area/extensions, area/site, or area/non-interactive, you must evaluate the architectural complexity to determine the effort level. You MUST NOT guess the root cause. You MUST actively use your codebase search tools (grep_search and glob) to search for keywords from the issue and explore the codebase. You must identify the specific files and components involved before deciding the effort. Do NOT evaluate or assign an effort/ label to issues in any other areas (such as area/agent).
- If any of these are missing, select exactly ONE appropriate label for the missing category.
6. Identify other applicable labels based on the issue content, such as status/*, help wanted, good first issue, etc.
7. Give me a single short explanation about why you are selecting each label in the process.
8. Output a JSON array of objects, each containing the issue number
and the labels to add and remove, along with an explanation. If you assigned an effort/ label, you MUST also include an effort_analysis field. This effort_analysis must be highly detailed, technical, and empirical. It MUST NOT contain vague guesses (e.g., avoid words like "likely points to" or "possibly"). You must explicitly cite the specific file paths and architectural mechanisms you discovered using your search tools, explain the root cause, and then explicitly state how that complexity maps to the chosen effort level guidelines. For example:
1. Use the read_file tool to read "effort_issues_to_triage.json".
2. For each issue in the array:
- You must evaluate the architectural complexity to determine the effort level. You MUST NOT guess the root cause. You MUST actively use your codebase search tools (grep_search and glob) to search for keywords from the issue and explore the codebase. You must identify the specific files and components involved before deciding the effort.
3. Output a JSON array of objects, each containing the issue number and the effort label to add, along with an explanation and an effort_analysis field. This effort_analysis must be highly detailed, technical, and empirical. It MUST NOT contain vague guesses (e.g., avoid words like "likely points to" or "possibly"). You must explicitly cite the specific file paths and architectural mechanisms you discovered using your search tools, explain the root cause, and then explicitly state how that complexity maps to the chosen effort level guidelines. For example:
```
[
{
"issue_number": 123,
"labels_to_add": ["area/core", "kind/bug", "priority/p2", "effort/small"],
"labels_to_remove": ["status/need-triage"],
"explanation": "This issue is a UI bug that needs to be addressed with medium priority.",
"labels_to_add": ["effort/small"],
"explanation": "This is a simple logic fix.",
"effort_analysis": "The `vscode-ide-companion` extension indiscriminately tracks active text editors via `vscode.window.onDidChangeActiveTextEditor` in `open-files-manager.ts`. When a user opens `.vscode/settings.json`, its content is sent to the CLI's context. The fix is highly localized to the VS Code companion extension's event listener. It involves adding a simple conditional check to exclude specific configuration files from the active editor tracking logic, which is a trivial logic adjustment with a clear root cause."
}
]
```
If an issue cannot be classified, do not include it in the output array.
9. For each issue please check if CLI version is present, this is usually in the output of the /about command and will look like 0.1.5
- Anything more than 6 versions older than the most recent should add the status/need-retesting label
10. If you see that the issue doesn't look like it has sufficient information recommend the status/need-information label and leave a comment politely requesting the relevant information, eg.. if repro steps are missing request for repro steps. if version information is missing request for version information into the explanation section below.
11. If you think an issue might be a Priority/P0 do not apply the priority/p0 label. Instead apply a status/manual-triage label and include a note in your explanation.
12. If you are uncertain about a category, use the area/unknown, kind/question, or priority/unknown labels as appropriate. If you are extremely uncertain, apply the status/manual-triage label.
## Guidelines
- Output only valid JSON format
- Do not include any explanation or additional text, just the JSON
- Only use labels that already exist in the repository.
- Do not add comments or modify the issue content.
- Do not remove the following labels maintainer, help wanted or good first issue.
- Triage only the current issue.
- Identify only one area/ label.
- Identify only one kind/ label (Do not apply kind/duplicate or kind/parent-issue)
- Identify only one priority/ label.
- Once you categorize the issue if it needs information bump down the priority by 1 eg.. a p0 would become a p1 a p1 would become a p2. P2 and P3 can stay as is in this scenario.
- Triage only the current issue.
Categorization Guidelines (Effort):
effort/small (1 day or less):
@@ -272,13 +383,29 @@ jobs:
- This product is designed to use different models eg.. using pro, downgrading to flash etc.
- When users report that they dont expect the model to change those would be categorized as feature requests.
- name: 'Apply Labels to Issues'
- name: 'Apply Standard Labels to Issues'
if: |-
${{ steps.gemini_issue_analysis.outcome == 'success' &&
steps.gemini_issue_analysis.outputs.summary != '[]' }}
${{ steps.gemini_standard_issue_analysis.outcome == 'success' &&
steps.gemini_standard_issue_analysis.outputs.summary != '[]' &&
steps.gemini_standard_issue_analysis.outputs.summary != '' }}
env:
REPOSITORY: '${{ github.repository }}'
LABELS_OUTPUT: '${{ steps.gemini_issue_analysis.outputs.summary }}'
LABELS_OUTPUT: '${{ steps.gemini_standard_issue_analysis.outputs.summary }}'
uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea'
with:
github-token: '${{ steps.generate_token.outputs.token }}'
script: |-
const applyLabels = require('./.github/scripts/apply-issue-labels.cjs');
await applyLabels({ github, context, core });
- name: 'Apply Effort Labels to Issues'
if: |-
${{ steps.gemini_effort_issue_analysis.outcome == 'success' &&
steps.gemini_effort_issue_analysis.outputs.summary != '[]' &&
steps.gemini_effort_issue_analysis.outputs.summary != '' }}
env:
REPOSITORY: '${{ github.repository }}'
LABELS_OUTPUT: '${{ steps.gemini_effort_issue_analysis.outputs.summary }}'
uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea'
with:
github-token: '${{ steps.generate_token.outputs.token }}'
@@ -21,6 +21,8 @@ jobs:
steps:
- name: 'Checkout'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
with:
persist-credentials: false
- name: 'Generate GitHub App Token'
id: 'generate_token'
@@ -19,6 +19,8 @@ jobs:
steps:
- name: 'Checkout'
uses: 'actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5' # ratchet:actions/checkout@v4
with:
persist-credentials: false
- name: 'Setup Node.js'
uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions/setup-node@v4
@@ -41,6 +43,8 @@ jobs:
steps:
- name: 'Checkout'
uses: 'actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5' # ratchet:actions/checkout@v4
with:
persist-credentials: false
- name: 'Setup Node.js'
uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions/setup-node@v4
+2
View File
@@ -17,6 +17,8 @@ jobs:
runs-on: 'ubuntu-latest'
steps:
- uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
with:
persist-credentials: false
- name: 'Link Checker'
id: 'lychee'
+2
View File
@@ -16,6 +16,8 @@ jobs:
steps:
- name: 'Checkout'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
with:
persist-credentials: false
- name: 'Set up Node.js'
uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions/setup-node@v4
+2
View File
@@ -16,6 +16,8 @@ jobs:
steps:
- name: 'Checkout'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
with:
persist-credentials: false
- name: 'Set up Node.js'
uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions/setup-node@v4
@@ -44,6 +44,7 @@ jobs:
with:
ref: '${{ github.ref }}'
fetch-depth: 0
persist-credentials: false
- name: 'Setup Node.js'
uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020'
+2
View File
@@ -65,11 +65,13 @@ jobs:
- name: 'Checkout'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8'
with:
persist-credentials: false
fetch-depth: 0
- name: 'Checkout Release Code'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8'
with:
persist-credentials: false
ref: '${{ github.event.inputs.ref }}'
path: 'release'
fetch-depth: 0
+2
View File
@@ -50,11 +50,13 @@ jobs:
- name: 'Checkout'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8'
with:
persist-credentials: false
fetch-depth: 0
- name: 'Checkout Release Code'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8'
with:
persist-credentials: false
ref: '${{ github.event.inputs.ref }}'
path: 'release'
fetch-depth: 0
+1
View File
@@ -31,6 +31,7 @@ jobs:
- name: 'Checkout repository'
uses: 'actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5' # ratchet:actions/checkout@v4
with:
persist-credentials: false
# The user-level skills need to be available to the workflow
fetch-depth: 0
ref: 'main'
@@ -17,6 +17,7 @@ jobs:
- name: 'Checkout'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8'
with:
persist-credentials: false
fetch-depth: 1
- name: 'Slash Command Dispatch'
@@ -54,6 +54,7 @@ jobs:
with:
ref: '${{ github.event.inputs.ref }}'
fetch-depth: 0
persist-credentials: false
- name: 'Setup Node.js'
uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions/setup-node@v4
@@ -64,6 +64,7 @@ jobs:
- name: 'Checkout'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8'
with:
persist-credentials: false
ref: "${{ github.event.inputs.workflow_ref || 'main' }}"
fetch-depth: 1
@@ -53,12 +53,14 @@ jobs:
- name: 'Checkout'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8'
with:
persist-credentials: false
fetch-depth: 0
fetch-tags: true
- name: 'Checkout Release Code'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8'
with:
persist-credentials: false
ref: '${{ github.event.inputs.release_ref }}'
path: 'release'
fetch-depth: 0
+10 -1
View File
@@ -55,6 +55,7 @@ jobs:
- name: 'Checkout'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8'
with:
persist-credentials: false
fetch-depth: 0
fetch-tags: true
@@ -171,11 +172,13 @@ jobs:
- name: 'Checkout Ref'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8'
with:
persist-credentials: false
ref: '${{ github.event.inputs.ref }}'
- name: 'Checkout correct SHA'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8'
with:
persist-credentials: false
ref: '${{ matrix.sha }}'
path: 'release'
fetch-depth: 0
@@ -216,11 +219,13 @@ jobs:
- name: 'Checkout Ref'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8'
with:
persist-credentials: false
ref: '${{ github.event.inputs.ref }}'
- name: 'Checkout correct SHA'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8'
with:
persist-credentials: false
ref: '${{ needs.calculate-versions.outputs.PREVIEW_SHA }}'
path: 'release'
fetch-depth: 0
@@ -288,11 +293,13 @@ jobs:
- name: 'Checkout Ref'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8'
with:
persist-credentials: false
ref: '${{ github.event.inputs.ref }}'
- name: 'Checkout correct SHA'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8'
with:
persist-credentials: false
ref: '${{ needs.calculate-versions.outputs.STABLE_SHA }}'
path: 'release'
fetch-depth: 0
@@ -360,6 +367,7 @@ jobs:
- name: 'Checkout Ref'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8'
with:
persist-credentials: false
ref: '${{ github.event.inputs.ref }}'
- name: 'Setup Node.js'
@@ -395,6 +403,7 @@ jobs:
BRANCH_NAME: '${{ steps.release_branch.outputs.BRANCH_NAME }}'
DRY_RUN: '${{ github.event.inputs.dry_run }}'
NEEDS_CALCULATE_VERSIONS_OUTPUTS_NEXT_NIGHTLY_VERSION: '${{ needs.calculate-versions.outputs.NEXT_NIGHTLY_VERSION }}'
GIT_PUSH_TOKEN: '${{ secrets.GEMINI_CLI_ROBOT_GITHUB_PAT }}'
run: |-
git add package.json packages/*/package.json
if [ -f package-lock.json ]; then
@@ -403,7 +412,7 @@ jobs:
git commit -m "chore(release): bump version to ${NEEDS_CALCULATE_VERSIONS_OUTPUTS_NEXT_NIGHTLY_VERSION}"
if [[ "${DRY_RUN}" == "false" ]]; then
echo "Pushing release branch to remote..."
git push --set-upstream origin "${BRANCH_NAME}"
git push "https://x-access-token:${GIT_PUSH_TOKEN}@github.com/${{ github.repository }}.git" "HEAD:${BRANCH_NAME}" --follow-tags
else
echo "Dry run enabled. Skipping push."
fi
+2 -1
View File
@@ -52,6 +52,7 @@ jobs:
- name: 'Checkout repository'
uses: 'actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955' # ratchet:actions/checkout@v4
with:
persist-credentials: false
ref: '${{ github.event.inputs.ref }}'
fetch-depth: 0
@@ -192,7 +193,7 @@ jobs:
run: |
echo "ROLLBACK_TAG=$ROLLBACK_TAG_NAME" >> "$GITHUB_OUTPUT"
git tag "$ROLLBACK_TAG_NAME" "${ORIGIN_HASH}"
git push origin --tags
git push "https://x-access-token:${GITHUB_TOKEN}@github.com/${{ github.repository }}.git" --tags
- name: 'Verify Rollback Tag Added'
if: "${{ github.event.inputs.dry-run == 'false' }}"
+1
View File
@@ -26,6 +26,7 @@ jobs:
- name: 'Checkout'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8'
with:
persist-credentials: false
ref: '${{ github.event.inputs.ref || github.sha }}'
fetch-depth: 0
- name: 'Push'
+1
View File
@@ -32,6 +32,7 @@ jobs:
with:
ref: '${{ github.event.inputs.ref || github.sha }}'
fetch-depth: 0
persist-credentials: false
- name: 'Install Dependencies'
run: 'npm ci'
- name: 'Build bundle'
+2
View File
@@ -34,6 +34,8 @@ jobs:
steps:
- name: 'Checkout'
uses: 'actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5' # ratchet:actions/checkout@v4
with:
persist-credentials: false
- name: 'Optimize Windows Performance'
if: "matrix.os == 'windows-latest'"
+2
View File
@@ -44,6 +44,8 @@ jobs:
shell: 'bash'
run: 'echo "${{ toJSON(vars) }}"'
- uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8'
with:
persist-credentials: false
- name: 'Verify release'
uses: './.github/actions/verify-release'
with:
+5 -1
View File
@@ -43,9 +43,13 @@
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"name": "CLI: Run Current File",
"runtimeExecutable": "node",
"runtimeArgs": ["--import", "tsx"],
"skipFiles": ["<node_internals>/**"],
"program": "${file}",
"cwd": "${workspaceFolder}",
"console": "integratedTerminal",
"outFiles": ["${workspaceFolder}/**/*.js"]
},
{
+16
View File
@@ -18,6 +18,22 @@ on GitHub.
| [Preview](preview.md) | Experimental features ready for early feedback. |
| [Stable](latest.md) | Stable, recommended for general use. |
## Announcements: v0.42.0 - 2026-05-12
- **Auto Memory Inbox:** Introduced a new inbox flow for Auto Memory with a
canonical-patch contract for seamless skill management
([#26338](https://github.com/google-gemini/gemini-cli/pull/26338) by
@SandyTao520).
- **Gemma 4 by Default:** Enabled Gemma 4 models by default via the Gemini API
for all users
([#26307](https://github.com/google-gemini/gemini-cli/pull/26307) by
@Abhijit-2592).
- **Voice Mode Enhancements:** Added wave animations and privacy/compliance UX
warnings for the Gemini Live backend
([#26284](https://github.com/google-gemini/gemini-cli/pull/26284) by
@devr0306, [#26454](https://github.com/google-gemini/gemini-cli/pull/26454) by
@cocosheng-g).
## Announcements: v0.41.0 - 2026-05-05
- **Real-time Voice Mode:** Implemented real-time voice mode with cloud and
+261 -108
View File
@@ -1,6 +1,6 @@
# Latest stable release: v0.41.0
# Latest stable release: v0.42.0
Released: May 05, 2026
Released: May 12, 2026
For most users, our latest stable release is the recommended release. Install
the latest stable version with:
@@ -11,119 +11,272 @@ npm install -g @google/gemini-cli
## Highlights
- **Real-time Voice Mode:** Introduced support for real-time voice interaction
with both cloud-based and local processing backends.
- **Enhanced Security:** Implemented mandatory workspace trust for headless
environments and secured the loading of `.env` configuration files.
- **Advanced Shell Validation:** Added a robust shell command validation layer
and a core tools allowlist to prevent unauthorized execution.
- **Improved Context Management:** Integrated a new `ContextManager` and
`AgentChatHistory` to provide more reliable and efficient session handling.
- **Auto-Memory Persistence:** Enabled the persistence of the auto-memory
scratchpad, allowing for seamless skill extraction across turns.
- **Auto Memory Inbox:** Introduced a new inbox flow for Auto Memory using a
canonical-patch contract, enabling more robust and manageable skill
extraction.
- **Gemma 4 Default:** Gemma 4 models are now enabled by default via the Gemini
API, providing improved performance and capabilities out of the box.
- **Voice Mode Polish:** Added wave animations for visual feedback and
privacy/compliance UX warnings specifically for the Gemini Live backend.
- **Session Management:** Added a `--delete` flag to the `/exit` command for
instant session deletion and introduced `/bug-memory` for easier heap
diagnostics.
- **Improved Reliability:** Reduced default API timeouts to 60s and implemented
retries for undici and premature stream closure errors.
## What's Changed
- chore(release): bump version to 0.41.0-nightly.20260423.gaa05b4583 by
- fix(cli): prevent automatic updates from switching to less stable channels by
@Adib234 in [#26132](https://github.com/google-gemini/gemini-cli/pull/26132)
- chore(release): bump version to 0.42.0-nightly.20260428.g59b2dea0e by
@gemini-cli-robot in
[#25847](https://github.com/google-gemini/gemini-cli/pull/25847)
- fix(core): only show `list` suggestion if the partial input is empty by
@cynthialong0-0 in
[#25821](https://github.com/google-gemini/gemini-cli/pull/25821)
- feat(cli): secure .env loading and enforce workspace trust in headless mode by
@ehedlund in [#25814](https://github.com/google-gemini/gemini-cli/pull/25814)
- fix: fatal hard-crash on loop detection via unhandled AbortError by @hsm207 in
[#20108](https://github.com/google-gemini/gemini-cli/pull/20108)
- update package-lock.json by @ehedlund in
[#25876](https://github.com/google-gemini/gemini-cli/pull/25876)
- feat(core): enhance shell command validation and add core tools allowlist by
@galz10 in [#25720](https://github.com/google-gemini/gemini-cli/pull/25720)
- fix(ui): corrected background color check in user message components by
@devr0306 in [#25880](https://github.com/google-gemini/gemini-cli/pull/25880)
- perf(core): fix slow boot by fetching experiments and quota asynchronously by
@spencer426 in
[#25758](https://github.com/google-gemini/gemini-cli/pull/25758)
- feat(core,cli): add support for Gemma 4 models (experimental) by @Abhijit-2592
in [#25604](https://github.com/google-gemini/gemini-cli/pull/25604)
- update FatalUntrustedWorkspaceError message to include doc link by @ehedlund
in [#25874](https://github.com/google-gemini/gemini-cli/pull/25874)
- docs: add Gemini CLI course link to README by @JayadityaGit in
[#25925](https://github.com/google-gemini/gemini-cli/pull/25925)
- feat(repo): add gemini-cli-bot metrics and workflows by @gundermanc in
[#25888](https://github.com/google-gemini/gemini-cli/pull/25888)
- fix(cli): allow output redirection for cli commands by @spencer426 in
[#25894](https://github.com/google-gemini/gemini-cli/pull/25894)
- fix(core): fail closed in YOLO mode when shell parsing fails for restricted
rules by @ehedlund in
[#25935](https://github.com/google-gemini/gemini-cli/pull/25935)
- fix(cli-ui): revert backspace handling to fix Windows regression by @scidomino
in [#25941](https://github.com/google-gemini/gemini-cli/pull/25941)
- feat(voice): implement real-time voice mode with cloud and local backends by
[#26142](https://github.com/google-gemini/gemini-cli/pull/26142)
- fix(cli): pass node arguments via NODE_OPTIONS during relaunch to support SEA
by @cocosheng-g in
[#26130](https://github.com/google-gemini/gemini-cli/pull/26130)
- fix(cli): handle DECKPAM keypad Enter sequences in terminal by @Gitanaskhan26
in [#26092](https://github.com/google-gemini/gemini-cli/pull/26092)
- docs(cli): point plan-mode session retention to actual /settings labels by
@ifitisit in [#25978](https://github.com/google-gemini/gemini-cli/pull/25978)
- fix(core): add missing oauth fields support in subagent parsing by
@abhipatel12 in
[#26141](https://github.com/google-gemini/gemini-cli/pull/26141)
- fix(core): disconnect extension-backed MCP clients in stopExtension by
@cocosheng-g in
[#26136](https://github.com/google-gemini/gemini-cli/pull/26136)
- Update documentation workflows with workspace trust by @g-samroberts in
[#26150](https://github.com/google-gemini/gemini-cli/pull/26150)
- refactor(acp): modularize monolithic acpClient into specialized files by
@sripasg in [#26143](https://github.com/google-gemini/gemini-cli/pull/26143)
- test: fix failures due to antigravity environment leakage by @adamfweidman in
[#26162](https://github.com/google-gemini/gemini-cli/pull/26162)
- fix(core): add explicit empty log guard in A2A pushMessage by @adamfweidman in
[#26198](https://github.com/google-gemini/gemini-cli/pull/26198)
- feat(cli): add --delete flag to /exit command for session deletion by
@AbdulTawabJuly in
[#19332](https://github.com/google-gemini/gemini-cli/pull/19332)
- test(core): add regression test for issue for ToolConfirmationResponse by
@Adib234 in [#26194](https://github.com/google-gemini/gemini-cli/pull/26194)
- Add the ability to @ mention the gemini robot. by @gundermanc in
[#26207](https://github.com/google-gemini/gemini-cli/pull/26207)
- test(evals): add EvalMetadata JSDoc annotations to older tests by @akh64bit in
[#26147](https://github.com/google-gemini/gemini-cli/pull/26147)
- fix(core): reduce default API timeout to 60s and enable retries for undici
timeouts by @Adib234 in
[#26191](https://github.com/google-gemini/gemini-cli/pull/26191)
- fix(core): distinguish fallback chains and fix maxAttempts for auto vs
explicit model selection by @adamfweidman in
[#26163](https://github.com/google-gemini/gemini-cli/pull/26163)
- fix(cli): handle InvalidStream event gracefully without throwing by
@adamfweidman in
[#26218](https://github.com/google-gemini/gemini-cli/pull/26218)
- ci(github-actions): switch to github app token and fix bot self-trigger by
@gundermanc in
[#26223](https://github.com/google-gemini/gemini-cli/pull/26223)
- Respect logPrompts flag for logging sensitive fields by @lp-peg in
[#26153](https://github.com/google-gemini/gemini-cli/pull/26153)
- fix: correct API key validation logic in handleApiKeySubmit by
@martin-hsu-test in
[#25453](https://github.com/google-gemini/gemini-cli/pull/25453)
- fix(agent): prevent exit_plan_mode from being called via shell by
@Abhijit-2592 in
[#24174](https://github.com/google-gemini/gemini-cli/pull/24174)
- Changelog for v0.39.0 by @gemini-cli-robot in
[#25848](https://github.com/google-gemini/gemini-cli/pull/25848)
- feat(memory): persist auto-memory scratchpad for skill extraction by
[#26230](https://github.com/google-gemini/gemini-cli/pull/26230)
- # Fix: Inconsistent Case-Sensitivity in GrepTool by @.github/workflows/gemini-cli-bot-pulse.yml[bot] in [#26235](https://github.com/google-gemini/gemini-cli/pull/26235)
- docs(core): add automated gemma setup guide by @Samee24 in
[#26233](https://github.com/google-gemini/gemini-cli/pull/26233)
- Allow non-https proxy urls to support container environments by @stevemk14ebr
in [#26234](https://github.com/google-gemini/gemini-cli/pull/26234)
- fix(bot): productivity and backlog optimizations by @gundermanc in
[#26236](https://github.com/google-gemini/gemini-cli/pull/26236)
- refactor(acp): delegate prompt turn processing logic to GeminiClient by
@sripasg in [#26222](https://github.com/google-gemini/gemini-cli/pull/26222)
- fix(cli): refine platform-specific undo/redo and smart bubbling for WSL by
@cocosheng-g in
[#26202](https://github.com/google-gemini/gemini-cli/pull/26202)
- fix: suppress duplicate extension warnings during startup by @cocosheng-g in
[#26208](https://github.com/google-gemini/gemini-cli/pull/26208)
- fix(cli): use byte length instead of string length for readStdin size limits
by @Adib234 in
[#26224](https://github.com/google-gemini/gemini-cli/pull/26224)
- fix(ui): made shell tool header wrap on Ctrl+O by @devr0306 in
[#26229](https://github.com/google-gemini/gemini-cli/pull/26229)
- Changelog for v0.41.0-preview.0 by @gemini-cli-robot in
[#26244](https://github.com/google-gemini/gemini-cli/pull/26244)
- Skip binary CLI relaunch by @ruomengz in
[#26261](https://github.com/google-gemini/gemini-cli/pull/26261)
- fix(cli): do not override GOOGLE_CLOUD_PROJECT in Cloud Shell when using
Vertex AI by @jackwotherspoon in
[#24455](https://github.com/google-gemini/gemini-cli/pull/24455)
- docs(cli): add skill discovery troubleshooting checklist to tutorial by
@pmenic in [#26018](https://github.com/google-gemini/gemini-cli/pull/26018)
- docs(policy-engine): link to tools reference for tool names and args by
@Aaxhirrr in [#22081](https://github.com/google-gemini/gemini-cli/pull/22081)
- Fix posting invalid response to a comment by @gundermanc in
[#26266](https://github.com/google-gemini/gemini-cli/pull/26266)
- fix(cli): prevent informational logs from polluting json output by
@cocosheng-g in
[#26264](https://github.com/google-gemini/gemini-cli/pull/26264)
- feat(ui): added microphone and updated placeholder for voice mode by @devr0306
in [#26270](https://github.com/google-gemini/gemini-cli/pull/26270)
- feat(cli): Add 'list' subcommand to '/commands' by @Jwhyee in
[#22324](https://github.com/google-gemini/gemini-cli/pull/22324)
- fix(core): ensure tool output cleanup on session deletion for legacy files by
@cocosheng-g in
[#26263](https://github.com/google-gemini/gemini-cli/pull/26263)
- Docs: Update Agent Skills documentation by @jkcinouye in
[#22388](https://github.com/google-gemini/gemini-cli/pull/22388)
- test(acp): add missing coverage for extensions command error paths by
@sahilkirad in
[#25313](https://github.com/google-gemini/gemini-cli/pull/25313)
- Changelog for v0.40.0 by @gemini-cli-robot in
[#26245](https://github.com/google-gemini/gemini-cli/pull/26245)
- fix: report AgentExecutionBlocked in non-interactive programmatic modes by
@cocosheng-g in
[#26262](https://github.com/google-gemini/gemini-cli/pull/26262)
- feat(extensions): add 'delete' as an alias for /extensions uninstall by
@martin-hsu-test in
[#25660](https://github.com/google-gemini/gemini-cli/pull/25660)
- fix(core): silently skip GEMINI.md paths that are directories (EISDIR) by
@martin-hsu-test in
[#25662](https://github.com/google-gemini/gemini-cli/pull/25662)
- fix(ci): checkout PR branch instead of main in bot workflow by @gundermanc in
[#26289](https://github.com/google-gemini/gemini-cli/pull/26289)
- fix(cli): use resolved sandbox state for auto-update check by @Adib234 in
[#26285](https://github.com/google-gemini/gemini-cli/pull/26285)
- # Metrics Integrity & Standardized Reporting (BT-01) by @.github/workflows/gemini-cli-bot-pulse.yml[bot] in [#26240](https://github.com/google-gemini/gemini-cli/pull/26240)
- Add Star History section to README by @bdmorgan in
[#26290](https://github.com/google-gemini/gemini-cli/pull/26290)
- Add Star History section to README by @bdmorgan in
[#26308](https://github.com/google-gemini/gemini-cli/pull/26308)
- Remove Star History section from README by @bdmorgan in
[#26309](https://github.com/google-gemini/gemini-cli/pull/26309)
- test(evals): add behavioral eval for file creation and write_file tool
selection by @akh64bit in
[#26292](https://github.com/google-gemini/gemini-cli/pull/26292)
- feat(config): enable Gemma 4 models by default via Gemini API by @Abhijit-2592
in [#26307](https://github.com/google-gemini/gemini-cli/pull/26307)
- fix(cli): insert voice transcription at cursor position instead of ap… by
@Zheyuan-Lin in
[#26287](https://github.com/google-gemini/gemini-cli/pull/26287)
- fix(ui): fix issue with box edges by @gundermanc in
[#26148](https://github.com/google-gemini/gemini-cli/pull/26148)
- fix(cli): respect .env override for GOOGLE_CLOUD_PROJECT by @DavidAPierce in
[#26288](https://github.com/google-gemini/gemini-cli/pull/26288)
- fix(ci): robust version checking in release verification by @scidomino in
[#26337](https://github.com/google-gemini/gemini-cli/pull/26337)
- fix(cli): enable daemon relaunch in binary and bundle keytar by @ruomengz in
[#26333](https://github.com/google-gemini/gemini-cli/pull/26333)
- fix(core): discourage unprompted git add . in prompt snippets by @akh64bit in
[#26220](https://github.com/google-gemini/gemini-cli/pull/26220)
- feat(ui): added wave animation for voice mode by @devr0306 in
[#26284](https://github.com/google-gemini/gemini-cli/pull/26284)
- fix(cli): prevent Escape from clearing input buffer (#17083) by @cocosheng-g
in [#26339](https://github.com/google-gemini/gemini-cli/pull/26339)
- fix(cli): undeprecate --prompt and correct positional query docs by @Adib234
in [#26329](https://github.com/google-gemini/gemini-cli/pull/26329)
- Metrics updates by @.github/workflows/gemini-cli-bot-pulse.yml[bot] in
[#26348](https://github.com/google-gemini/gemini-cli/pull/26348)
- fix(core): remove "System: Please continue." injection on InvalidStream events
by @SandyTao520 in
[#26340](https://github.com/google-gemini/gemini-cli/pull/26340)
- docs(policy-engine): add tool argument keys reference and shell policy
cross-links by @harshpujari in
[#25292](https://github.com/google-gemini/gemini-cli/pull/25292)
- fix(cli): resolve Ghostty/raw-mode False Cancellation in oauth flow by
@Aarchi-07 in [#25026](https://github.com/google-gemini/gemini-cli/pull/25026)
- fix(core): reset session-scoped state on resumption by @cocosheng-g in
[#26342](https://github.com/google-gemini/gemini-cli/pull/26342)
- Fix bulk of remaining issues with generalist profile by @joshualitt in
[#26073](https://github.com/google-gemini/gemini-cli/pull/26073)
- fix(core): make subagents aware of active approval modes by @akh64bit in
[#23608](https://github.com/google-gemini/gemini-cli/pull/23608)
- fix(acp): resolve agent mode disconnect and improve mode awareness by @sripasg
in [#26332](https://github.com/google-gemini/gemini-cli/pull/26332)
- docs(sdk): add JSDoc to exported interfaces in packages/sdk/src/types.ts by
@cocosheng-g in
[#26441](https://github.com/google-gemini/gemini-cli/pull/26441)
- perf: skip redundant GEMINI.md loading in partialConfig by @cocosheng-g in
[#26443](https://github.com/google-gemini/gemini-cli/pull/26443)
- Enhance React guidelines by @psinha40898 in
[#22667](https://github.com/google-gemini/gemini-cli/pull/22667)
- feat(core): reinforce Inquiry constraints to prevent unauthorized changes by
@akh64bit in [#26310](https://github.com/google-gemini/gemini-cli/pull/26310)
- revert: fix(ci): robust version checking in release verification (#26337) by
@scidomino in [#26450](https://github.com/google-gemini/gemini-cli/pull/26450)
- refactor(UI): created constants file for ThemeDialog by @devr0306 in
[#26446](https://github.com/google-gemini/gemini-cli/pull/26446)
- docs: fix GitHub capitalization in releases guide by @haosenwang1018 in
[#26379](https://github.com/google-gemini/gemini-cli/pull/26379)
- fix(cli): ensure branch indicator updates in sub-directories and worktrees by
@Adib234 in [#26330](https://github.com/google-gemini/gemini-cli/pull/26330)
- feat: add minimal V8 heap snapshot utility for memory diagnostics by
@cocosheng-g in
[#26440](https://github.com/google-gemini/gemini-cli/pull/26440)
- fix(hooks): preserve non-text parts in fromHookLLMRequest by @SandyTao520 in
[#26275](https://github.com/google-gemini/gemini-cli/pull/26275)
- fix(cli): allow early stdout when config is undefined by @cocosheng-g in
[#26453](https://github.com/google-gemini/gemini-cli/pull/26453)
- fix(cli)#21297: clear skills consent dialog before reload by @manavmax in
[#26431](https://github.com/google-gemini/gemini-cli/pull/26431)
- fix(cli): render LaTeX-style output as Unicode in the TUI by @dimssu in
[#25802](https://github.com/google-gemini/gemini-cli/pull/25802)
- fix(core): use close event instead of exit in child_process fallback by
@tusaryan in [#25695](https://github.com/google-gemini/gemini-cli/pull/25695)
- feat(voice): add privacy and compliance UX warning for Gemini Live backend by
@cocosheng-g in
[#26454](https://github.com/google-gemini/gemini-cli/pull/26454)
- feat(memory): add Auto Memory inbox flow with canonical-patch contract by
@SandyTao520 in
[#25873](https://github.com/google-gemini/gemini-cli/pull/25873)
- fix(cli): add missing response key to custom theme text schema by @gaurav0107
in [#25822](https://github.com/google-gemini/gemini-cli/pull/25822)
- fix(cli): provide manual update command when automatic update fails by
@cocosheng-g in
[#26052](https://github.com/google-gemini/gemini-cli/pull/26052)
- test(cli): add unit tests for restore ACP command (#23402) by @cocosheng-g in
[#26053](https://github.com/google-gemini/gemini-cli/pull/26053)
- fix(ui): better error messages for ECONNRESET and ETIMEDOUT by @devr0306 in
[#26059](https://github.com/google-gemini/gemini-cli/pull/26059)
- feat(core): wire up the new ContextManager and AgentChatHistory by @joshualitt
in [#25409](https://github.com/google-gemini/gemini-cli/pull/25409)
- fix(cli): ensure sandbox proxy cleanup and remove handler leaks by @ehedlund
in [#26065](https://github.com/google-gemini/gemini-cli/pull/26065)
- fix(cli): correct alternate buffer warning logic for JetBrains by @Adib234 in
[#26067](https://github.com/google-gemini/gemini-cli/pull/26067)
- fix(cli): make MCP ping optional in list command and use configured timeout by
@cocosheng-g in
[#26068](https://github.com/google-gemini/gemini-cli/pull/26068)
- fix(core): better error message for failed cloudshell-gca auth by @devr0306 in
[#26079](https://github.com/google-gemini/gemini-cli/pull/26079)
- feat(cli): provide manual session UUID via command line arg by @cocosheng-g in
[#26060](https://github.com/google-gemini/gemini-cli/pull/26060)
- Changelog for v0.40.0-preview.2 by @gemini-cli-robot in
[#25846](https://github.com/google-gemini/gemini-cli/pull/25846)
- (docs) update sandboxing documentation by @g-samroberts in
[#25930](https://github.com/google-gemini/gemini-cli/pull/25930)
- fix(core): enforce parallel task tracker updates by @anj-s in
[#24477](https://github.com/google-gemini/gemini-cli/pull/24477)
- Update policy so transient errors are not marked terminal by @DavidAPierce in
[#26066](https://github.com/google-gemini/gemini-cli/pull/26066)
- Implement bot that performs time-series metric analysis and suggests repo
management improvements by @gundermanc in
[#25945](https://github.com/google-gemini/gemini-cli/pull/25945)
- fix(core): handle non-string model flags in resolution by @Adib234 in
[#26069](https://github.com/google-gemini/gemini-cli/pull/26069)
- fix(ux): added error message for ENOTDIR by @devr0306 in
[#26128](https://github.com/google-gemini/gemini-cli/pull/26128)
- Changelog for v0.40.0-preview.3 by @gemini-cli-robot in
[#25904](https://github.com/google-gemini/gemini-cli/pull/25904)
- fix(cli): prevent ACP stdout pollution from SessionEnd hooks by @cocosheng-g
in [#26125](https://github.com/google-gemini/gemini-cli/pull/26125)
- feat(cli): support boolean and number casting for env vars in settings.json by
@cocosheng-g in
[#26118](https://github.com/google-gemini/gemini-cli/pull/26118)
- fix(cli): preserve Request headers in DevTools activity logger by @Adib234 in
[#26078](https://github.com/google-gemini/gemini-cli/pull/26078)
- fix(patch): cherry-pick 2194da2 to release/v0.41.0-preview.0-pr-26153 to patch
version v0.41.0-preview.0 and create version 0.41.0-preview.1 by
[#26338](https://github.com/google-gemini/gemini-cli/pull/26338)
- test(cleanup): fix temporary directory leaks in test suites by @Adib234 in
[#26217](https://github.com/google-gemini/gemini-cli/pull/26217)
- feat: add ignoreLocalEnv setting and --ignore-env flag (#2493) by @cocosheng-g
in [#26445](https://github.com/google-gemini/gemini-cli/pull/26445)
- docs(sdk): add JSDoc to all exported interfaces and types by @fauzan171 in
[#26277](https://github.com/google-gemini/gemini-cli/pull/26277)
- feat(cli): improve /agents refresh logging by @cocosheng-g in
[#26442](https://github.com/google-gemini/gemini-cli/pull/26442)
- Fix: make Dockerfile self-contained with multi-stage build by @Famous077 in
[#24277](https://github.com/google-gemini/gemini-cli/pull/24277)
- fix(core): filter unsupported multimodal types from tool responses by
@aishaneeshah in
[#26352](https://github.com/google-gemini/gemini-cli/pull/26352)
- fix(core): properly format markdown in AskUser tool by unescaping newlines by
@Adib234 in [#26349](https://github.com/google-gemini/gemini-cli/pull/26349)
- feat(bot): add actions spend metric script by @gundermanc in
[#26463](https://github.com/google-gemini/gemini-cli/pull/26463)
- feat(cli): add /bug-memory command and auto-capture heap snapshot in /bug by
@Anjaligarhwal in
[#25639](https://github.com/google-gemini/gemini-cli/pull/25639)
- fix(cli): make SkillInboxDialog fit and scroll in alternate buffer by
@SandyTao520 in
[#26455](https://github.com/google-gemini/gemini-cli/pull/26455)
- Robust Scale-Safe Lifecycle Consolidation by @gemini-cli-robot in
[#26355](https://github.com/google-gemini/gemini-cli/pull/26355)
- fix(ci): respect exempt labels when closing stale items by @gundermanc in
[#26475](https://github.com/google-gemini/gemini-cli/pull/26475)
- fix(cli): use os.homedir() for home directory warning check by @TirthNaik-99
in [#25890](https://github.com/google-gemini/gemini-cli/pull/25890)
- fix(a2a-server): resolve tool approval race condition and improve status
reporting by @kschaab in
[#26479](https://github.com/google-gemini/gemini-cli/pull/26479)
- fix(cli): prevent settings dialog border clipping using maxHeight by
@jackwotherspoon in
[#26507](https://github.com/google-gemini/gemini-cli/pull/26507)
- feat: allow queuing messages during compression (#24071) by @cocosheng-g in
[#26506](https://github.com/google-gemini/gemini-cli/pull/26506)
- fix(core): retry on ERR_STREAM_PREMATURE_CLOSE errors by @cocosheng-g in
[#26519](https://github.com/google-gemini/gemini-cli/pull/26519)
- fix(core): Minor fixes for generalist profile. by @joshualitt in
[#26357](https://github.com/google-gemini/gemini-cli/pull/26357)
- fix(patch): cherry-pick 3627f47 to release/v0.42.0-preview.0-pr-26542 to patch
version v0.42.0-preview.0 and create version 0.42.0-preview.1 by
@gemini-cli-robot in
[#26269](https://github.com/google-gemini/gemini-cli/pull/26269)
- fix(patch): cherry-pick 1d72a12 to release/v0.41.0-preview.1-pr-26479 to patch
version v0.41.0-preview.1 and create version 0.41.0-preview.2 by
[#26544](https://github.com/google-gemini/gemini-cli/pull/26544)
- fix(patch): cherry-pick 02995ba to release/v0.42.0-preview.1-pr-26568 to patch
version v0.42.0-preview.1 and create version 0.42.0-preview.2 by
@gemini-cli-robot in
[#26508](https://github.com/google-gemini/gemini-cli/pull/26508)
- fix(patch): cherry-pick 7cc19c2 to release/v0.41.0-preview.2-pr-26507 to patch
version v0.41.0-preview.2 and create version 0.41.0-preview.3 by
@gemini-cli-robot in
[#26530](https://github.com/google-gemini/gemini-cli/pull/26530)
[#26590](https://github.com/google-gemini/gemini-cli/pull/26590)
**Full Changelog**:
https://github.com/google-gemini/gemini-cli/compare/v0.40.1...v0.41.0
https://github.com/google-gemini/gemini-cli/compare/v0.41.2...v0.42.0
+178 -227
View File
@@ -1,6 +1,6 @@
# Preview release: v0.42.0-preview.2
# Preview release: v0.43.0-preview.0
Released: May 06, 2026
Released: May 12, 2026
Our preview release includes the latest, new, and experimental features. This
release may not be as stable as our [latest weekly release](latest.md).
@@ -13,233 +13,184 @@ npm install -g @google/gemini-cli@preview
## Highlights
- **Auto Memory Enhancements:** Introduced an Auto Memory inbox flow with a
canonical-patch contract for better memory management.
- **Improved Voice Mode:** Added a wave animation, microphone icon updates, and
privacy/compliance UX warnings for the Gemini Live backend.
- **New CLI Commands & Flags:** Added a `--delete` flag to the `/exit` command
for session deletion, a `list` subcommand to `/commands`, and a `/bug-memory`
command for heap snapshots.
- **Expanded Model Support:** Gemma 4 models are now enabled by default via the
Gemini API.
- **Enhanced Core Resilience:** Improved API resilience with reduced timeouts,
automatic retries for stream errors, and better handling of invalid stream
events.
- **Surgical Code Edits:** Steer models to use the `edit` tool for precise code
modifications, improving accuracy and reducing context usage.
- **Session Portability:** Added ability to export chat sessions to files and
import them via a new CLI flag, enabling session persistence and sharing.
- **Enhanced Security:** Introduced comprehensive shell command safety
evaluations and strengthened model steering to prevent unauthorized changes.
- **Context Management:** Implemented a new adaptive token calculator for more
accurate content size estimations and optimized context pipelines.
- **UX Improvements:** Enhanced tool call visibility with prefixed IDs and
improved the UI for session resumption and MCP list management.
## What's Changed
- fix(cli): prevent automatic updates from switching to less stable channels in
[#26132](https://github.com/google-gemini/gemini-cli/pull/26132)
- chore(release): bump version to 0.42.0-nightly.20260428.g59b2dea0e in
[#26142](https://github.com/google-gemini/gemini-cli/pull/26142)
- fix(cli): pass node arguments via NODE_OPTIONS during relaunch to support SEA
in [#26130](https://github.com/google-gemini/gemini-cli/pull/26130)
- fix(cli): handle DECKPAM keypad Enter sequences in terminal in
[#26092](https://github.com/google-gemini/gemini-cli/pull/26092)
- docs(cli): point plan-mode session retention to actual /settings labels in
[#25978](https://github.com/google-gemini/gemini-cli/pull/25978)
- fix(core): add missing oauth fields support in subagent parsing in
[#26141](https://github.com/google-gemini/gemini-cli/pull/26141)
- fix(core): disconnect extension-backed MCP clients in stopExtension in
[#26136](https://github.com/google-gemini/gemini-cli/pull/26136)
- Update documentation workflows with workspace trust in
[#26150](https://github.com/google-gemini/gemini-cli/pull/26150)
- refactor(acp): modularize monolithic acpClient into specialized files in
[#26143](https://github.com/google-gemini/gemini-cli/pull/26143)
- test: fix failures due to antigravity environment leakage in
[#26162](https://github.com/google-gemini/gemini-cli/pull/26162)
- fix(core): add explicit empty log guard in A2A pushMessage in
[#26198](https://github.com/google-gemini/gemini-cli/pull/26198)
- feat(cli): add --delete flag to /exit command for session deletion in
[#19332](https://github.com/google-gemini/gemini-cli/pull/19332)
- test(core): add regression test for issue for ToolConfirmationResponse in
[#26194](https://github.com/google-gemini/gemini-cli/pull/26194)
- Add the ability to @ mention the gemini robot. in
[#26207](https://github.com/google-gemini/gemini-cli/pull/26207)
- test(evals): add EvalMetadata JSDoc annotations to older tests in
[#26147](https://github.com/google-gemini/gemini-cli/pull/26147)
- fix(core): reduce default API timeout to 60s and enable retries for undici
timeouts in [#26191](https://github.com/google-gemini/gemini-cli/pull/26191)
- fix(core): distinguish fallback chains and fix maxAttempts for auto vs
explicit model selection in
[#26163](https://github.com/google-gemini/gemini-cli/pull/26163)
- fix(cli): handle InvalidStream event gracefully without throwing in
[#26218](https://github.com/google-gemini/gemini-cli/pull/26218)
- ci(github-actions): switch to github app token and fix bot self-trigger in
[#26223](https://github.com/google-gemini/gemini-cli/pull/26223)
- Respect logPrompts flag for logging sensitive fields in
[#26153](https://github.com/google-gemini/gemini-cli/pull/26153)
- fix: correct API key validation logic in handleApiKeySubmit in
[#25453](https://github.com/google-gemini/gemini-cli/pull/25453)
- fix(agent): prevent exit_plan_mode from being called via shell in
[#26230](https://github.com/google-gemini/gemini-cli/pull/26230)
- # Fix: Inconsistent Case-Sensitivity in GrepTool in [#26235](https://github.com/google-gemini/gemini-cli/pull/26235)
- docs(core): add automated gemma setup guide in
[#26233](https://github.com/google-gemini/gemini-cli/pull/26233)
- Allow non-https proxy urls to support container environments in
[#26234](https://github.com/google-gemini/gemini-cli/pull/26234)
- fix(bot): productivity and backlog optimizations in
[#26236](https://github.com/google-gemini/gemini-cli/pull/26236)
- refactor(acp): delegate prompt turn processing logic to GeminiClient in
[#26222](https://github.com/google-gemini/gemini-cli/pull/26222)
- fix(cli): refine platform-specific undo/redo and smart bubbling for WSL in
[#26202](https://github.com/google-gemini/gemini-cli/pull/26202)
- fix: suppress duplicate extension warnings during startup in
[#26208](https://github.com/google-gemini/gemini-cli/pull/26208)
- fix(cli): use byte length instead of string length for readStdin size limits
in [#26224](https://github.com/google-gemini/gemini-cli/pull/26224)
- fix(ui): made shell tool header wrap on Ctrl+O in
[#26229](https://github.com/google-gemini/gemini-cli/pull/26229)
- Changelog for v0.41.0-preview.0 in
[#26244](https://github.com/google-gemini/gemini-cli/pull/26244)
- Skip binary CLI relaunch in
[#26261](https://github.com/google-gemini/gemini-cli/pull/26261)
- fix(cli): do not override GOOGLE_CLOUD_PROJECT in Cloud Shell when using
Vertex AI in [#24455](https://github.com/google-gemini/gemini-cli/pull/24455)
- docs(cli): add skill discovery troubleshooting checklist to tutorial in
[#26018](https://github.com/google-gemini/gemini-cli/pull/26018)
- docs(policy-engine): link to tools reference for tool names and args in
[#22081](https://github.com/google-gemini/gemini-cli/pull/22081)
- Fix posting invalid response to a comment in
[#26266](https://github.com/google-gemini/gemini-cli/pull/26266)
- fix(cli): prevent informational logs from polluting json output in
[#26264](https://github.com/google-gemini/gemini-cli/pull/26264)
- feat(ui): added microphone and updated placeholder for voice mode in
[#26270](https://github.com/google-gemini/gemini-cli/pull/26270)
- feat(cli): Add 'list' subcommand to '/commands' in
[#22324](https://github.com/google-gemini/gemini-cli/pull/22324)
- fix(core): ensure tool output cleanup on session deletion for legacy files in
[#26263](https://github.com/google-gemini/gemini-cli/pull/26263)
- Docs: Update Agent Skills documentation in
[#22388](https://github.com/google-gemini/gemini-cli/pull/22388)
- test(acp): add missing coverage for extensions command error paths in
[#25313](https://github.com/google-gemini/gemini-cli/pull/25313)
- Changelog for v0.40.0 in
[#26245](https://github.com/google-gemini/gemini-cli/pull/26245)
- fix: report AgentExecutionBlocked in non-interactive programmatic modes in
[#26262](https://github.com/google-gemini/gemini-cli/pull/26262)
- feat(extensions): add 'delete' as an alias for /extensions uninstall in
[#25660](https://github.com/google-gemini/gemini-cli/pull/25660)
- fix(core): silently skip GEMINI.md paths that are directories (EISDIR) in
[#25662](https://github.com/google-gemini/gemini-cli/pull/25662)
- fix(ci): checkout PR branch instead of main in bot workflow in
[#26289](https://github.com/google-gemini/gemini-cli/pull/26289)
- fix(cli): use resolved sandbox state for auto-update check in
[#26285](https://github.com/google-gemini/gemini-cli/pull/26285)
- # Metrics Integrity & Standardized Reporting (BT-01) in [#26240](https://github.com/google-gemini/gemini-cli/pull/26240)
- Add Star History section to README in
[#26290](https://github.com/google-gemini/gemini-cli/pull/26290)
- Add Star History section to README in
[#26308](https://github.com/google-gemini/gemini-cli/pull/26308)
- Remove Star History section from README in
[#26309](https://github.com/google-gemini/gemini-cli/pull/26309)
- test(evals): add behavioral eval for file creation and write_file tool
selection in [#26292](https://github.com/google-gemini/gemini-cli/pull/26292)
- feat(config): enable Gemma 4 models by default via Gemini API in
[#26307](https://github.com/google-gemini/gemini-cli/pull/26307)
- fix(cli): insert voice transcription at cursor position instead of ap… in
[#26287](https://github.com/google-gemini/gemini-cli/pull/26287)
- fix(ui): fix issue with box edges in
[#26148](https://github.com/google-gemini/gemini-cli/pull/26148)
- fix(cli): respect .env override for GOOGLE_CLOUD_PROJECT in
[#26288](https://github.com/google-gemini/gemini-cli/pull/26288)
- fix(ci): robust version checking in release verification in
[#26337](https://github.com/google-gemini/gemini-cli/pull/26337)
- fix(cli): enable daemon relaunch in binary and bundle keytar in
[#26333](https://github.com/google-gemini/gemini-cli/pull/26333)
- fix(core): discourage unprompted git add . in prompt snippets in
[#26220](https://github.com/google-gemini/gemini-cli/pull/26220)
- feat(ui): added wave animation for voice mode in
[#26284](https://github.com/google-gemini/gemini-cli/pull/26284)
- fix(cli): prevent Escape from clearing input buffer (#17083) in
[#26339](https://github.com/google-gemini/gemini-cli/pull/26339)
- fix(cli): undeprecate --prompt and correct positional query docs in
[#26329](https://github.com/google-gemini/gemini-cli/pull/26329)
- Metrics updates in
[#26348](https://github.com/google-gemini/gemini-cli/pull/26348)
- fix(core): remove "System: Please continue." injection on InvalidStream events
in [#26340](https://github.com/google-gemini/gemini-cli/pull/26340)
- docs(policy-engine): add tool argument keys reference and shell policy
cross-links in
[#25292](https://github.com/google-gemini/gemini-cli/pull/25292)
- fix(cli): resolve Ghostty/raw-mode False Cancellation in oauth flow in
[#25026](https://github.com/google-gemini/gemini-cli/pull/25026)
- fix(core): reset session-scoped state on resumption in
[#26342](https://github.com/google-gemini/gemini-cli/pull/26342)
- Fix bulk of remaining issues with generalist profile in
[#26073](https://github.com/google-gemini/gemini-cli/pull/26073)
- fix(core): make subagents aware of active approval modes in
[#23608](https://github.com/google-gemini/gemini-cli/pull/23608)
- fix(acp): resolve agent mode disconnect and improve mode awareness in
[#26332](https://github.com/google-gemini/gemini-cli/pull/26332)
- docs(sdk): add JSDoc to exported interfaces in packages/sdk/src/types.ts in
[#26441](https://github.com/google-gemini/gemini-cli/pull/26441)
- perf: skip redundant GEMINI.md loading in partialConfig in
[#26443](https://github.com/google-gemini/gemini-cli/pull/26443)
- Enhance React guidelines in
[#22667](https://github.com/google-gemini/gemini-cli/pull/22667)
- feat(core): reinforce Inquiry constraints to prevent unauthorized changes in
[#26310](https://github.com/google-gemini/gemini-cli/pull/26310)
- revert: fix(ci): robust version checking in release verification (#26337) in
[#26450](https://github.com/google-gemini/gemini-cli/pull/26450)
- refactor(UI): created constants file for ThemeDialog in
[#26446](https://github.com/google-gemini/gemini-cli/pull/26446)
- docs: fix GitHub capitalization in releases guide in
[#26379](https://github.com/google-gemini/gemini-cli/pull/26379)
- fix(cli): ensure branch indicator updates in sub-directories and worktrees in
[#26330](https://github.com/google-gemini/gemini-cli/pull/26330)
- feat: add minimal V8 heap snapshot utility for memory diagnostics in
[#26440](https://github.com/google-gemini/gemini-cli/pull/26440)
- fix(hooks): preserve non-text parts in fromHookLLMRequest in
[#26275](https://github.com/google-gemini/gemini-cli/pull/26275)
- fix(cli): allow early stdout when config is undefined in
[#26453](https://github.com/google-gemini/gemini-cli/pull/26453)
- fix(cli)#21297: clear skills consent dialog before reload in
[#26431](https://github.com/google-gemini/gemini-cli/pull/26431)
- fix(cli): render LaTeX-style output as Unicode in the TUI in
[#25802](https://github.com/google-gemini/gemini-cli/pull/25802)
- fix(core): use close event instead of exit in child_process fallback in
[#25695](https://github.com/google-gemini/gemini-cli/pull/25695)
- feat(voice): add privacy and compliance UX warning for Gemini Live backend in
[#26454](https://github.com/google-gemini/gemini-cli/pull/26454)
- feat(memory): add Auto Memory inbox flow with canonical-patch contract in
[#26338](https://github.com/google-gemini/gemini-cli/pull/26338)
- test(cleanup): fix temporary directory leaks in test suites in
[#26217](https://github.com/google-gemini/gemini-cli/pull/26217)
- feat: add ignoreLocalEnv setting and --ignore-env flag (#2493) in
[#26445](https://github.com/google-gemini/gemini-cli/pull/26445)
- docs(sdk): add JSDoc to all exported interfaces and types in
[#26277](https://github.com/google-gemini/gemini-cli/pull/26277)
- feat(cli): improve /agents refresh logging in
[#26442](https://github.com/google-gemini/gemini-cli/pull/26442)
- Fix: make Dockerfile self-contained with multi-stage build in
[#24277](https://github.com/google-gemini/gemini-cli/pull/24277)
- fix(core): filter unsupported multimodal types from tool responses in
[#26352](https://github.com/google-gemini/gemini-cli/pull/26352)
- fix(core): properly format markdown in AskUser tool by unescaping newlines in
[#26349](https://github.com/google-gemini/gemini-cli/pull/26349)
- feat(bot): add actions spend metric script in
[#26463](https://github.com/google-gemini/gemini-cli/pull/26463)
- feat(cli): add /bug-memory command and auto-capture heap snapshot in /bug in
[#25639](https://github.com/google-gemini/gemini-cli/pull/25639)
- fix(cli): make SkillInboxDialog fit and scroll in alternate buffer in
[#26455](https://github.com/google-gemini/gemini-cli/pull/26455)
- Robust Scale-Safe Lifecycle Consolidation in
[#26355](https://github.com/google-gemini/gemini-cli/pull/26355)
- fix(ci): respect exempt labels when closing stale items in
[#26475](https://github.com/google-gemini/gemini-cli/pull/26475)
- fix(cli): use os.homedir() for home directory warning check in
[#25890](https://github.com/google-gemini/gemini-cli/pull/25890)
- fix(a2a-server): resolve tool approval race condition and improve status
reporting in [#26479](https://github.com/google-gemini/gemini-cli/pull/26479)
- fix(cli): prevent settings dialog border clipping using maxHeight in
[#26507](https://github.com/google-gemini/gemini-cli/pull/26507)
- feat: allow queuing messages during compression (#24071) in
[#26506](https://github.com/google-gemini/gemini-cli/pull/26506)
- fix(core): retry on ERR_STREAM_PREMATURE_CLOSE errors in
[#26519](https://github.com/google-gemini/gemini-cli/pull/26519)
- fix(core): Minor fixes for generalist profile. in
[#26357](https://github.com/google-gemini/gemini-cli/pull/26357)
- feat(core): steer model to use edit tool for surgical edits, fix a typo in
[#26480](https://github.com/google-gemini/gemini-cli/pull/26480)
- docs: clarify Auto Memory proposes memory updates and skills in
[#26527](https://github.com/google-gemini/gemini-cli/pull/26527)
- fix(core): reject numeric project IDs in GOOGLE_CLOUD_PROJECT (#24695) in
[#26532](https://github.com/google-gemini/gemini-cli/pull/26532)
- fix(core): remove unsafe type assertion suppressions in error utils in
[#19881](https://github.com/google-gemini/gemini-cli/pull/19881)
- fix(core): allow redirection in YOLO and AUTO_EDIT modes without sandboxing in
[#26542](https://github.com/google-gemini/gemini-cli/pull/26542)
- ci(release): build and attach unsigned macOS binaries to releases in
[#26462](https://github.com/google-gemini/gemini-cli/pull/26462)
- fix(core): Fix chat corruption bug in context manager. in
[#26534](https://github.com/google-gemini/gemini-cli/pull/26534)
- fix(cli): provide JSON output for AgentExecutionStopped in non-interactive
mode in [#26504](https://github.com/google-gemini/gemini-cli/pull/26504)
- feat(evals): add shell command safety evals in
[#26528](https://github.com/google-gemini/gemini-cli/pull/26528)
- fix(core): handle invalid custom plans directory gracefully in
[#26560](https://github.com/google-gemini/gemini-cli/pull/26560)
- fix(acp): move tool explanation from thought stream to tool call content in
[#26554](https://github.com/google-gemini/gemini-cli/pull/26554)
- fix(a2a-server): Resolve race condition in tool completion waiting in
[#26568](https://github.com/google-gemini/gemini-cli/pull/26568)
- fix(cli): randomize sandbox container names in
[#26014](https://github.com/google-gemini/gemini-cli/pull/26014)
- fix(core): Fix hysteresis in async context management pipelines. in
[#26452](https://github.com/google-gemini/gemini-cli/pull/26452)
- Tighten private Auto Memory patch allowlist in
[#26535](https://github.com/google-gemini/gemini-cli/pull/26535)
- fix(cli): hide read-only settings scopes in
[#26249](https://github.com/google-gemini/gemini-cli/pull/26249)
- fix(ci): preserve executable bit for mac binaries in
[#26600](https://github.com/google-gemini/gemini-cli/pull/26600)
- fix(cli): improve mcp list UX in untrusted folders in
[#26457](https://github.com/google-gemini/gemini-cli/pull/26457)
- fix(core): prevent silent hang during OAuth auth on headless Linux in
[#26571](https://github.com/google-gemini/gemini-cli/pull/26571)
- Changelog for v0.42.0-preview.0 in
[#26537](https://github.com/google-gemini/gemini-cli/pull/26537)
- ci: fix Argument list too long in triage workflows in
[#26603](https://github.com/google-gemini/gemini-cli/pull/26603)
- refactor(cli): migrate core tools to native ToolDisplay property and fix UI
rendering in [#25186](https://github.com/google-gemini/gemini-cli/pull/25186)
- don't wrap args unnecessarily in
[#26599](https://github.com/google-gemini/gemini-cli/pull/26599)
- fix(core): preserve system PATH in Git environment to fix ENOENT (#25034) in
[#26587](https://github.com/google-gemini/gemini-cli/pull/26587)
- fix(routing): fix resolveClassifierModel argument mismatch in
ApprovalModeStrategy in
[#26658](https://github.com/google-gemini/gemini-cli/pull/26658)
- docs: add vi mode shortcuts and clarify MCP/custom sandbox setup in
[#23853](https://github.com/google-gemini/gemini-cli/pull/23853)
- fix(ux): fixed issue with transcribed text not showing after releasing space
in [#26609](https://github.com/google-gemini/gemini-cli/pull/26609)
- ci: fix json parsing in scheduled triage workflow in
[#26656](https://github.com/google-gemini/gemini-cli/pull/26656)
- fix(cli): hide /memory add subcommand when memoryV2 is enabled in
[#26605](https://github.com/google-gemini/gemini-cli/pull/26605)
- fix: prevent false command conflicts when launching from home directory in
[#23069](https://github.com/google-gemini/gemini-cli/pull/23069)
- fix(core): cache model routing decision in LocalAgentExecutor in
[#26548](https://github.com/google-gemini/gemini-cli/pull/26548)
- Changelog for v0.42.0-preview.2 in
[#26597](https://github.com/google-gemini/gemini-cli/pull/26597)
- skip broken test in
[#26705](https://github.com/google-gemini/gemini-cli/pull/26705)
- feat: export session to file and import via flag in
[#26514](https://github.com/google-gemini/gemini-cli/pull/26514)
- Feat: Add Machine Hostname to CLI interface in
[#25637](https://github.com/google-gemini/gemini-cli/pull/25637)
- docs(extensions): refactor releasing guide and add update mechanisms in
[#26595](https://github.com/google-gemini/gemini-cli/pull/26595)
- fix(ci): fix maintainer identification in lifecycle manager in
[#26706](https://github.com/google-gemini/gemini-cli/pull/26706)
- fix(ui): added quotes around session id in resume tip in
[#26669](https://github.com/google-gemini/gemini-cli/pull/26669)
- Changelog for v0.41.0 in
[#26670](https://github.com/google-gemini/gemini-cli/pull/26670)
- refactor(core): agent session protocol changes in
[#26661](https://github.com/google-gemini/gemini-cli/pull/26661)
- fix(context): implement loose boundary policy for gc backstop. in
[#26594](https://github.com/google-gemini/gemini-cli/pull/26594)
- fix(core): throw explicit error on dropped tool responses in
[#26668](https://github.com/google-gemini/gemini-cli/pull/26668)
- fix: resolve "function response turn must come immediately after function
call" error in
[#26691](https://github.com/google-gemini/gemini-cli/pull/26691)
- fix(core): resolve parallel tool call streaming ID collision in
[#26646](https://github.com/google-gemini/gemini-cli/pull/26646)
- feat(core): add LocalSubagentProtocol behind AgentProtocol in
[#25302](https://github.com/google-gemini/gemini-cli/pull/25302)
- fix(cli): remove noisy theme registration logs from terminal in
[#25858](https://github.com/google-gemini/gemini-cli/pull/25858)
- ci: implement codebase-aware effort level triage in
[#26666](https://github.com/google-gemini/gemini-cli/pull/26666)
- feat(acp/core): prefix tool call IDs with tool names to support tool rendering
in ACP compliant IDEs. in
[#26676](https://github.com/google-gemini/gemini-cli/pull/26676)
- fix(mcp): treat GET 404 as 405 in StreamableHTTPClientTransport in
[#24847](https://github.com/google-gemini/gemini-cli/pull/24847)
- feat(core): add RemoteSubagentProtocol behind AgentProtocol in
[#25303](https://github.com/google-gemini/gemini-cli/pull/25303)
- feat(context): Improvements to the snapshotter. in
[#26655](https://github.com/google-gemini/gemini-cli/pull/26655)
- fix(context): Change snapshotter model config. in
[#26745](https://github.com/google-gemini/gemini-cli/pull/26745)
- fix(cli): allow installing extensions from ssh repo in
[#26274](https://github.com/google-gemini/gemini-cli/pull/26274)
- fix(cli): prevent duplicate SessionStart systemMessage render in
[#25827](https://github.com/google-gemini/gemini-cli/pull/25827)
- fix(cli/acp): prevent infinite thought loop in ACP mode by disablig
nextSpeakerCheck in
[#26874](https://github.com/google-gemini/gemini-cli/pull/26874)
- fix(cli): use static tool name in confirmation prompt to avoid parsing errors
in [#26866](https://github.com/google-gemini/gemini-cli/pull/26866)
- fix(routing): Refactor tool turn handling for the conversation history in
NumericalClassifierStrategy to prevent 400 Bad Request in
[#26761](https://github.com/google-gemini/gemini-cli/pull/26761)
- fix(core): handle malformed projects.json in ProjectRegistry in
[#26885](https://github.com/google-gemini/gemini-cli/pull/26885)
- fix(ui): added a gutter width to the input prompt width calculation in
[#26882](https://github.com/google-gemini/gemini-cli/pull/26882)
- fix: prevent EISDIR crash when customIgnoreFilePaths contains directories
(#19868) in [#19898](https://github.com/google-gemini/gemini-cli/pull/19898)
- revert 6b9b778d821728427eea07b1b97ba07378137d0b in
[#26893](https://github.com/google-gemini/gemini-cli/pull/26893)
- Fix/vscode run current file ts in
[#22894](https://github.com/google-gemini/gemini-cli/pull/22894)
- Allow Enter to select session while in search mode in /resume in
[#21523](https://github.com/google-gemini/gemini-cli/pull/21523)
- fix(core): ignore .pak and .rpa game archive formats by default in
[#26884](https://github.com/google-gemini/gemini-cli/pull/26884)
- fix(cli): enable adk non-interactive session in
[#26895](https://github.com/google-gemini/gemini-cli/pull/26895)
- fix(cli): restore resume for legacy sessions in
[#26577](https://github.com/google-gemini/gemini-cli/pull/26577)
- fix: respect explicit model selection after Flash quota exhaustion (#26759) in
[#26872](https://github.com/google-gemini/gemini-cli/pull/26872)
- feat(context): Introduce adaptive token calculator to more accurately
calculate content sizes. in
[#26888](https://github.com/google-gemini/gemini-cli/pull/26888)
- chore: update checkout action configuration in workflows in
[#26897](https://github.com/google-gemini/gemini-cli/pull/26897)
- fix (telemetry): inject quota_project_id to prevent fallback to default oauth
client in [#26698](https://github.com/google-gemini/gemini-cli/pull/26698)
- Exclude extension context from skill extraction agent in
[#26879](https://github.com/google-gemini/gemini-cli/pull/26879)
- Enable NumericalRouter when using dynamic model configs in
[#26929](https://github.com/google-gemini/gemini-cli/pull/26929)
- ci: actively triage missing priority labels and intelligently clean up
conflicting labels in
[#26865](https://github.com/google-gemini/gemini-cli/pull/26865)
- refactor(core): introduce SubagentState enum for progress in
[#26934](https://github.com/google-gemini/gemini-cli/pull/26934)
- fix(ci): replace brittle --no-tag with explicit staging-tmp tag in
[#26940](https://github.com/google-gemini/gemini-cli/pull/26940)
- Incremental refactor repo agent towards skills-based composition in
[#26717](https://github.com/google-gemini/gemini-cli/pull/26717)
- fix(ui): fixed line wrap padding for selection lists in
[#26944](https://github.com/google-gemini/gemini-cli/pull/26944)
- fix(core): update read_file schema for v1 compatibility (#22183) in
[#26922](https://github.com/google-gemini/gemini-cli/pull/26922)
- fix(ci): configure git remote with token for authentication in
[#26949](https://github.com/google-gemini/gemini-cli/pull/26949)
**Full Changelog**:
https://github.com/google-gemini/gemini-cli/compare/v0.41.0-preview.3...v0.42.0-preview.2
https://github.com/google-gemini/gemini-cli/compare/v0.42.0-preview.2...v0.43.0-preview.0
+56 -46
View File
@@ -882,9 +882,10 @@ their corresponding top-level category object in your `settings.json` file.
}
},
"auto": {
"displayName": "Auto",
"tier": "auto",
"isPreview": true,
"isVisible": false,
"isVisible": true,
"features": {
"thinking": true,
"multimodalToolUse": false
@@ -918,26 +919,16 @@ their corresponding top-level category object in your `settings.json` file.
}
},
"auto-gemini-3": {
"displayName": "Auto (Gemini 3)",
"tier": "auto",
"family": "gemini-3",
"isPreview": true,
"isVisible": true,
"dialogDescription": "Let Gemini CLI decide the best model for the task: gemini-3-pro, gemini-3-flash",
"features": {
"thinking": true,
"multimodalToolUse": false
}
"isVisible": false
},
"auto-gemini-2.5": {
"displayName": "Auto (Gemini 2.5)",
"tier": "auto",
"family": "gemini-2.5",
"isPreview": false,
"isVisible": true,
"dialogDescription": "Let Gemini CLI decide the best model for the task: gemini-2.5-pro, gemini-2.5-flash",
"features": {
"thinking": false,
"multimodalToolUse": false
}
"isVisible": false
}
}
```
@@ -1020,33 +1011,15 @@ their corresponding top-level category object in your `settings.json` file.
}
]
},
"auto-gemini-3": {
"default": "gemini-3-pro-preview",
"contexts": [
{
"condition": {
"hasAccessToPreview": false
},
"target": "gemini-2.5-pro"
},
{
"condition": {
"useGemini3_1": true,
"useCustomTools": true
},
"target": "gemini-3.1-pro-preview-customtools"
},
{
"condition": {
"useGemini3_1": true
},
"target": "gemini-3.1-pro-preview"
}
]
},
"auto": {
"default": "gemini-3-pro-preview",
"contexts": [
{
"condition": {
"releaseChannel": "stable"
},
"target": "gemini-2.5-pro"
},
{
"condition": {
"hasAccessToPreview": false
@@ -1092,9 +1065,6 @@ their corresponding top-level category object in your `settings.json` file.
}
]
},
"auto-gemini-2.5": {
"default": "gemini-2.5-pro"
},
"gemini-3.1-flash-lite-preview": {
"default": "gemini-3.1-flash-lite-preview",
"contexts": [
@@ -1127,6 +1097,33 @@ their corresponding top-level category object in your `settings.json` file.
"target": "gemini-3.1-flash-lite-preview"
}
]
},
"auto-gemini-3": {
"default": "gemini-3-pro-preview",
"contexts": [
{
"condition": {
"hasAccessToPreview": false
},
"target": "gemini-2.5-pro"
},
{
"condition": {
"useGemini3_1": true,
"useCustomTools": true
},
"target": "gemini-3.1-pro-preview-customtools"
},
{
"condition": {
"useGemini3_1": true
},
"target": "gemini-3.1-pro-preview"
}
]
},
"auto-gemini-2.5": {
"default": "gemini-2.5-pro"
}
}
```
@@ -1145,15 +1142,15 @@ their corresponding top-level category object in your `settings.json` file.
"contexts": [
{
"condition": {
"requestedModels": ["auto-gemini-2.5", "gemini-2.5-pro"]
"hasAccessToPreview": false
},
"target": "gemini-2.5-flash"
},
{
"condition": {
"requestedModels": ["auto-gemini-3", "gemini-3-pro-preview"]
"requestedModels": ["gemini-2.5-pro", "auto-gemini-2.5"]
},
"target": "gemini-3-flash-preview"
"target": "gemini-2.5-flash"
}
]
},
@@ -1162,7 +1159,20 @@ their corresponding top-level category object in your `settings.json` file.
"contexts": [
{
"condition": {
"requestedModels": ["auto-gemini-2.5", "gemini-2.5-pro"]
"hasAccessToPreview": false
},
"target": "gemini-2.5-pro"
},
{
"condition": {
"releaseChannel": "stable",
"requestedModels": ["auto"]
},
"target": "gemini-2.5-pro"
},
{
"condition": {
"requestedModels": ["gemini-2.5-pro", "auto-gemini-2.5"]
},
"target": "gemini-2.5-pro"
},
+9 -9
View File
@@ -1,12 +1,12 @@
{
"name": "@google/gemini-cli",
"version": "0.42.0-nightly.20260428.g59b2dea0e",
"version": "0.44.0-nightly.20260512.g022e8baef",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@google/gemini-cli",
"version": "0.42.0-nightly.20260428.g59b2dea0e",
"version": "0.44.0-nightly.20260512.g022e8baef",
"workspaces": [
"packages/*"
],
@@ -18077,7 +18077,7 @@
},
"packages/a2a-server": {
"name": "@google/gemini-cli-a2a-server",
"version": "0.42.0-nightly.20260428.g59b2dea0e",
"version": "0.44.0-nightly.20260512.g022e8baef",
"dependencies": {
"@a2a-js/sdk": "0.3.11",
"@google-cloud/storage": "^7.16.0",
@@ -18206,7 +18206,7 @@
},
"packages/cli": {
"name": "@google/gemini-cli",
"version": "0.42.0-nightly.20260428.g59b2dea0e",
"version": "0.44.0-nightly.20260512.g022e8baef",
"license": "Apache-2.0",
"dependencies": {
"@agentclientprotocol/sdk": "^0.16.1",
@@ -18354,7 +18354,7 @@
},
"packages/core": {
"name": "@google/gemini-cli-core",
"version": "0.42.0-nightly.20260428.g59b2dea0e",
"version": "0.44.0-nightly.20260512.g022e8baef",
"license": "Apache-2.0",
"dependencies": {
"@a2a-js/sdk": "0.3.11",
@@ -18665,7 +18665,7 @@
},
"packages/devtools": {
"name": "@google/gemini-cli-devtools",
"version": "0.42.0-nightly.20260428.g59b2dea0e",
"version": "0.44.0-nightly.20260512.g022e8baef",
"license": "Apache-2.0",
"dependencies": {
"ws": "^8.16.0"
@@ -18680,7 +18680,7 @@
},
"packages/sdk": {
"name": "@google/gemini-cli-sdk",
"version": "0.42.0-nightly.20260428.g59b2dea0e",
"version": "0.44.0-nightly.20260512.g022e8baef",
"license": "Apache-2.0",
"dependencies": {
"@google/gemini-cli-core": "file:../core",
@@ -18711,7 +18711,7 @@
},
"packages/test-utils": {
"name": "@google/gemini-cli-test-utils",
"version": "0.42.0-nightly.20260428.g59b2dea0e",
"version": "0.44.0-nightly.20260512.g022e8baef",
"license": "Apache-2.0",
"dependencies": {
"@google/gemini-cli-core": "file:../core",
@@ -18743,7 +18743,7 @@
},
"packages/vscode-ide-companion": {
"name": "gemini-cli-vscode-ide-companion",
"version": "0.42.0-nightly.20260428.g59b2dea0e",
"version": "0.44.0-nightly.20260512.g022e8baef",
"license": "LICENSE",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.23.0",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli",
"version": "0.42.0-nightly.20260428.g59b2dea0e",
"version": "0.44.0-nightly.20260512.g022e8baef",
"engines": {
"node": ">=20.0.0"
},
@@ -14,7 +14,7 @@
"url": "git+https://github.com/google-gemini/gemini-cli.git"
},
"config": {
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.42.0-nightly.20260428.g59b2dea0e"
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.44.0-nightly.20260512.g022e8baef"
},
"scripts": {
"start": "cross-env NODE_ENV=development node scripts/start.js",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli-a2a-server",
"version": "0.42.0-nightly.20260428.g59b2dea0e",
"version": "0.44.0-nightly.20260512.g022e8baef",
"description": "Gemini CLI A2A Server",
"repository": {
"type": "git",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli",
"version": "0.42.0-nightly.20260428.g59b2dea0e",
"version": "0.44.0-nightly.20260512.g022e8baef",
"description": "Gemini CLI",
"license": "Apache-2.0",
"repository": {
@@ -27,7 +27,7 @@
"dist"
],
"config": {
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.42.0-nightly.20260428.g59b2dea0e"
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.44.0-nightly.20260512.g022e8baef"
},
"dependencies": {
"@agentclientprotocol/sdk": "^0.16.1",
+5 -2
View File
@@ -60,8 +60,11 @@ export class AcpFileSystemService implements FileSystemService {
sessionId: this.sessionId,
});
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return response.content;
const content: unknown = response.content;
if (typeof content !== 'string') {
throw new Error('content must be a string'); // replace with other response type formats when modified in the future
}
return content;
} catch (err: unknown) {
this.normalizeFileSystemError(err);
}
@@ -19,6 +19,7 @@ import type * as acp from '@agentclientprotocol/sdk';
import {
AuthType,
type Config,
GEMINI_MODEL_ALIAS_AUTO,
type MessageBus,
type Storage,
} from '@google/gemini-cli-core';
@@ -208,7 +209,7 @@ describe('AcpSessionManager', () => {
expect(response.models?.availableModels).toEqual(
expect.arrayContaining([
expect.objectContaining({
modelId: 'auto-gemini-3',
modelId: GEMINI_MODEL_ALIAS_AUTO,
name: expect.stringContaining('Auto'),
}),
]),
+10 -17
View File
@@ -10,8 +10,7 @@ import {
type ToolCallConfirmationDetails,
Kind,
ApprovalMode,
DEFAULT_GEMINI_MODEL_AUTO,
PREVIEW_GEMINI_MODEL_AUTO,
GEMINI_MODEL_ALIAS_AUTO,
DEFAULT_GEMINI_MODEL,
DEFAULT_GEMINI_FLASH_MODEL,
DEFAULT_GEMINI_FLASH_LITE_MODEL,
@@ -23,6 +22,8 @@ import {
getDisplayString,
AuthType,
ToolConfirmationOutcome,
getChannelFromVersion,
getAutoModelDescription,
} from '@google/gemini-cli-core';
import type * as acp from '@agentclientprotocol/sdk';
import { z } from 'zod';
@@ -262,7 +263,7 @@ export function buildAvailableModels(
}>;
currentModelId: string;
} {
const preferredModel = config.getModel() || DEFAULT_GEMINI_MODEL_AUTO;
const preferredModel = config.getModel() || GEMINI_MODEL_ALIAS_AUTO;
const shouldShowPreviewModels = config.getHasAccessToPreviewModel();
const useGemini31 = config.getGemini31LaunchedSync?.() ?? false;
const useGemini31FlashLite =
@@ -271,6 +272,8 @@ export function buildAvailableModels(
const useCustomToolModel =
useGemini31 && selectedAuthType === AuthType.USE_GEMINI;
const releaseChannel = getChannelFromVersion(config.clientVersion);
// --- DYNAMIC PATH ---
if (
config.getExperimentalDynamicModelConfiguration?.() === true &&
@@ -281,6 +284,7 @@ export function buildAvailableModels(
useGemini3_1FlashLite: useGemini31FlashLite,
useCustomTools: useCustomToolModel,
hasAccessToPreview: shouldShowPreviewModels,
releaseChannel,
});
return {
@@ -292,23 +296,12 @@ export function buildAvailableModels(
// --- LEGACY PATH ---
const mainOptions = [
{
value: DEFAULT_GEMINI_MODEL_AUTO,
title: getDisplayString(DEFAULT_GEMINI_MODEL_AUTO),
description:
'Let Gemini CLI decide the best model for the task: gemini-2.5-pro, gemini-2.5-flash',
value: GEMINI_MODEL_ALIAS_AUTO,
title: getDisplayString(GEMINI_MODEL_ALIAS_AUTO),
description: getAutoModelDescription(releaseChannel, useGemini31),
},
];
if (shouldShowPreviewModels) {
mainOptions.unshift({
value: PREVIEW_GEMINI_MODEL_AUTO,
title: getDisplayString(PREVIEW_GEMINI_MODEL_AUTO),
description: useGemini31
? 'Let Gemini CLI decide the best model for the task: gemini-3.1-pro, gemini-3-flash'
: 'Let Gemini CLI decide the best model for the task: gemini-3-pro, gemini-3-flash',
});
}
const manualOptions = [
{
value: DEFAULT_GEMINI_MODEL,
@@ -27,7 +27,7 @@ export interface ConfigLogger {
export type RequestSettingCallback = (
setting: ExtensionSetting,
) => Promise<string>;
) => Promise<string | undefined>;
export type RequestConfirmationCallback = (message: string) => Promise<boolean>;
const defaultLogger: ConfigLogger = {
@@ -47,8 +47,7 @@ const defaultRequestConfirmation: RequestConfirmationCallback = async (
message,
initial: false,
});
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return response.confirm;
return typeof response.confirm === 'boolean' ? response.confirm : false;
};
export async function getExtensionManager() {
+2 -2
View File
@@ -2058,7 +2058,7 @@ describe('loadCliConfig model selection', () => {
argv,
);
expect(config.getModel()).toBe('auto-gemini-3');
expect(config.getModel()).toBe('auto');
});
it('always prefers model from argv', async () => {
@@ -2102,7 +2102,7 @@ describe('loadCliConfig model selection', () => {
argv,
);
expect(config.getModel()).toBe('auto-gemini-3');
expect(config.getModel()).toBe('auto');
});
});
+1 -2
View File
@@ -30,7 +30,6 @@ import {
loadServerHierarchicalMemory,
ASK_USER_TOOL_NAME,
getVersion,
PREVIEW_GEMINI_MODEL_AUTO,
type HierarchicalMemory,
coreEvents,
GEMINI_MODEL_ALIAS_AUTO,
@@ -866,7 +865,7 @@ export async function loadCliConfig(
interactive,
);
const defaultModel = PREVIEW_GEMINI_MODEL_AUTO;
const defaultModel = GEMINI_MODEL_ALIAS_AUTO;
const rawModel =
argv.model || process.env['GEMINI_MODEL'] || settings.model?.name;
+5 -3
View File
@@ -88,7 +88,9 @@ interface ExtensionManagerParams {
enabledExtensionOverrides?: string[];
settings: MergedSettings;
requestConsent: (consent: string) => Promise<boolean>;
requestSetting: ((setting: ExtensionSetting) => Promise<string>) | null;
requestSetting:
| ((setting: ExtensionSetting) => Promise<string | undefined>)
| null;
workspaceDir: string;
eventEmitter?: EventEmitter<ExtensionEvents>;
clientVersion?: string;
@@ -106,7 +108,7 @@ export class ExtensionManager extends ExtensionLoader {
private settings: MergedSettings;
private requestConsent: (consent: string) => Promise<boolean>;
private requestSetting:
| ((setting: ExtensionSetting) => Promise<string>)
| ((setting: ExtensionSetting) => Promise<string | undefined>)
| undefined;
private telemetryConfig: Config;
private workspaceDir: string;
@@ -161,7 +163,7 @@ export class ExtensionManager extends ExtensionLoader {
}
setRequestSetting(
requestSetting?: (setting: ExtensionSetting) => Promise<string>,
requestSetting?: (setting: ExtensionSetting) => Promise<string | undefined>,
): void {
this.requestSetting = requestSetting;
}
@@ -94,9 +94,8 @@ export class ExtensionRegistryClient {
fuzzy: true,
});
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const results = await fzf.find(query);
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return results.map((r: { item: RegistryExtension }) => r.item);
const results: Array<{ item: RegistryExtension }> = await fzf.find(query);
return results.map((r) => r.item);
}
async getExtension(id: string): Promise<RegistryExtension | undefined> {
@@ -8,6 +8,7 @@ import fs from 'node:fs';
import path from 'node:path';
import { coreEvents, type GeminiCLIExtension } from '@google/gemini-cli-core';
import { ExtensionStorage } from './storage.js';
import { z } from 'zod';
export interface ExtensionEnablementConfig {
overrides: string[];
@@ -179,8 +180,12 @@ export class ExtensionEnablementManager {
readConfig(): AllExtensionsEnablementConfig {
try {
const content = fs.readFileSync(this.configFilePath, 'utf-8');
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return JSON.parse(content);
const parsed: unknown = JSON.parse(content);
const schema = z.record(
z.string(),
z.object({ overrides: z.array(z.string()) }),
);
return schema.parse(parsed);
} catch (error) {
if (
error instanceof Error &&
@@ -62,7 +62,7 @@ export const getEnvFilePath = (
export async function maybePromptForSettings(
extensionConfig: ExtensionConfig,
extensionId: string,
requestSetting: (setting: ExtensionSetting) => Promise<string>,
requestSetting: (setting: ExtensionSetting) => Promise<string | undefined>,
previousExtensionConfig?: ExtensionConfig,
previousSettings?: Record<string, string>,
): Promise<void> {
@@ -106,7 +106,9 @@ export async function maybePromptForSettings(
settingsChanges.promptForEnv,
)) {
const answer = await requestSetting(setting);
allSettings[setting.envVar] = answer;
if (answer !== undefined) {
allSettings[setting.envVar] = answer;
}
}
const nonSensitiveSettings: Record<string, string> = {};
@@ -159,14 +161,13 @@ function formatEnvContent(settings: Record<string, string>): string {
export async function promptForSetting(
setting: ExtensionSetting,
): Promise<string> {
): Promise<string | undefined> {
const response = await prompts({
type: setting.sensitive ? 'password' : 'text',
name: 'value',
message: `${setting.name}\n${setting.description}`,
});
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return response.value;
return typeof response.value === 'string' ? response.value : undefined;
}
export async function getScopedEnvContents(
@@ -230,7 +231,7 @@ export async function updateSetting(
extensionConfig: ExtensionConfig,
extensionId: string,
settingKey: string,
requestSetting: (setting: ExtensionSetting) => Promise<string>,
requestSetting: (setting: ExtensionSetting) => Promise<string | undefined>,
scope: ExtensionSettingScope,
workspaceDir: string,
): Promise<void> {
@@ -250,6 +251,10 @@ export async function updateSetting(
}
const newValue = await requestSetting(settingToUpdate);
if (newValue === undefined) {
return;
}
const keychain = new KeychainTokenStorage(
getKeychainStorageName(extensionName, extensionId, scope, workspaceDir),
);
@@ -67,8 +67,7 @@ export function recursivelyHydrateStrings<T>(
}
if (Array.isArray(obj)) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
return obj.map((item) =>
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return (obj as unknown[]).map((item) =>
recursivelyHydrateStrings(item, values),
) as unknown as T;
}
+5 -1
View File
@@ -3471,7 +3471,11 @@ export const SETTINGS_SCHEMA_DEFINITIONS: Record<
family: { type: 'string' },
isPreview: { type: 'boolean' },
isVisible: { type: 'boolean' },
dialogDescription: { type: 'string' },
dialogDescription: {
type: 'string',
description:
"A description of the model to display in the model selection dialog. For the 'auto' alias, this value is dynamically generated and any value provided here will be ignored.",
},
features: {
type: 'object',
properties: {
+33
View File
@@ -1189,6 +1189,39 @@ describe('resolveSessionId', () => {
expect(sessionId).toBe('new-id');
expect(resumedSessionData).toBeUndefined();
});
it('should exit with FATAL_INPUT_ERROR when explicit resume session is missing', async () => {
vi.mocked(SessionSelector).mockImplementation(
() =>
({
resolveSession: vi
.fn()
.mockRejectedValue(SessionError.noSessionsFound()),
}) as unknown as InstanceType<typeof SessionSelector>,
);
const emitFeedbackSpy = vi.spyOn(coreEvents, 'emitFeedback');
const processExitSpy = vi
.spyOn(process, 'exit')
.mockImplementation((code) => {
throw new MockProcessExitError(code);
});
try {
await resolveSessionId('explicit-session-id');
} catch (e) {
if (!(e instanceof MockProcessExitError)) throw e;
}
expect(emitFeedbackSpy).toHaveBeenCalledWith(
'error',
expect.stringContaining('Error resuming session:'),
);
expect(processExitSpy).toHaveBeenCalledWith(ExitCodes.FATAL_INPUT_ERROR);
emitFeedbackSpy.mockRestore();
processExitSpy.mockRestore();
});
});
describe('gemini.tsx main function exit codes', () => {
+9 -3
View File
@@ -85,7 +85,11 @@ import { validateAuthMethod } from './config/auth.js';
import { runAcpClient } from './acp/acpStdioTransport.js';
import { validateNonInteractiveAuth } from './validateNonInterActiveAuth.js';
import { appEvents, AppEvent } from './utils/events.js';
import { SessionError, SessionSelector } from './utils/sessionUtils.js';
import {
RESUME_LATEST,
SessionError,
SessionSelector,
} from './utils/sessionUtils.js';
import { relaunchOnExitCode } from './utils/relaunch.js';
import { loadSandboxConfig } from './config/sandboxConfig.js';
@@ -309,8 +313,10 @@ export async function resolveSessionId(
};
} catch (error) {
if (error instanceof SessionError && error.code === 'NO_SESSIONS_FOUND') {
coreEvents.emitFeedback('warning', error.message);
return { sessionId: createSessionId() };
if (resumeArg === RESUME_LATEST) {
coreEvents.emitFeedback('warning', error.message);
return { sessionId: createSessionId() };
}
}
coreEvents.emitFeedback(
'error',
+3
View File
@@ -68,6 +68,9 @@ export async function runNonInteractive(
): Promise<void> {
const useAgentSession = params.config.getAgentSessionNoninteractiveEnabled();
if (useAgentSession) {
debugLogger.debug(
'[ADK] Running non-interactive mode with ADK agent session',
);
return runNonInteractiveAgentSession(params);
}
@@ -112,5 +112,11 @@ export const createMockCommandContext = (
return output;
};
return merge(defaultMocks, overrides);
const merged: unknown = merge(defaultMocks, overrides);
const isCommandContext = (val: unknown): val is CommandContext =>
typeof val === 'object' && val !== null;
if (isCommandContext(merged)) {
return merged;
}
throw new Error('Unreachable');
};
+62
View File
@@ -2127,6 +2127,68 @@ describe('AppContainer State Management', () => {
});
describe('CTRL+C', () => {
it('should not trigger quit flow when input buffer has text', async () => {
const setText = vi.fn();
mockedUseTextBuffer.mockReturnValue({
text: 'hello world',
setText,
lines: ['hello world'],
cursor: [0, 11],
handleInput: vi.fn().mockReturnValue(false),
});
await setupKeypressTest();
pressKey('\x03'); // Ctrl+C
expect(mockCancelOngoingRequest).not.toHaveBeenCalled();
expect(mockHandleSlashCommand).not.toHaveBeenCalled();
unmount();
});
it('should clear buffer and reset quit count when Ctrl+C is pressed with text', async () => {
const setText = vi.fn();
const bufferWithText = {
text: 'hello world',
setText,
lines: ['hello world'],
cursor: [0, 11],
handleInput: vi.fn().mockReturnValue(false),
};
const bufferEmpty = {
text: '',
setText,
lines: [''],
cursor: [0, 0],
handleInput: vi.fn().mockReturnValue(false),
};
// First press with text
mockedUseTextBuffer.mockReturnValue(bufferWithText);
await setupKeypressTest();
pressKey('\x03'); // Ctrl+C
expect(mockCancelOngoingRequest).not.toHaveBeenCalled();
expect(mockHandleSlashCommand).not.toHaveBeenCalled();
// Second press with empty buffer (simulating clear happened)
// Since we reset the count, this should be treated as the "first" press for quit flow
mockedUseTextBuffer.mockReturnValue(bufferEmpty);
pressKey('\x03'); // Ctrl+C
expect(mockCancelOngoingRequest).toHaveBeenCalledTimes(1);
expect(mockHandleSlashCommand).not.toHaveBeenCalled();
// Third press with empty buffer should finally quit
pressKey('\x03'); // Ctrl+C
expect(mockCancelOngoingRequest).toHaveBeenCalledTimes(2);
expect(mockHandleSlashCommand).toHaveBeenCalledWith(
'/quit',
undefined,
undefined,
false,
);
unmount();
});
it('should cancel ongoing request on first press', async () => {
mockedUseGeminiStream.mockReturnValue({
...DEFAULT_GEMINI_STREAM_MOCK,
+33 -5
View File
@@ -1649,11 +1649,14 @@ Logging in with Google... Restarting Gemini CLI to continue.
[config, handleSlashCommand],
);
const { pressCount: ctrlCPressCount, handlePress: handleCtrlCPress } =
useRepeatedKeyPress({
windowMs: WARNING_PROMPT_DURATION_MS,
onRepeat: handleExitRepeat,
});
const {
pressCount: ctrlCPressCount,
handlePress: handleCtrlCPress,
resetCount: resetCtrlCPress,
} = useRepeatedKeyPress({
windowMs: WARNING_PROMPT_DURATION_MS,
onRepeat: handleExitRepeat,
});
const { pressCount: ctrlDPressCount, handlePress: handleCtrlDPress } =
useRepeatedKeyPress({
@@ -1830,6 +1833,28 @@ Logging in with Google... Restarting Gemini CLI to continue.
return true;
}
if (
keyMatchers[Command.QUIT](key) &&
isInputActive &&
streamingState === StreamingState.Idle &&
bufferRef.current.text.length > 0
) {
// Prevent quit flow when there is text.
resetCtrlCPress();
if (keyMatchers[Command.CLEAR_INPUT](key)) {
// Let InputPrompt handle it as edit.clear.
// This preserves documented behavior and allows prompt-level state
// (completions/history) to reset correctly.
return false;
}
// If QUIT is not also CLEAR_INPUT, we should still clear the buffer
// to match user expectations for Ctrl+C (or equivalent).
bufferRef.current.setText('');
return true;
}
if (keyMatchers[Command.QUIT](key)) {
// If the user presses Ctrl+C, we want to cancel any ongoing requests.
// This should happen regardless of the count.
@@ -2062,6 +2087,9 @@ Logging in with Google... Restarting Gemini CLI to continue.
startRecording,
stopRecording,
mouseMode,
isInputActive,
streamingState,
resetCtrlCPress,
],
);
@@ -1581,4 +1581,71 @@ describe('AskUserDialog', () => {
expect(frame).toContain('1. Option 1');
});
});
it('indents multi-line descriptions correctly', async () => {
const questions: Question[] = [
{
question: 'Single choice?',
header: 'Indent Test',
type: QuestionType.CHOICE,
options: [
{
label: 'Option 1',
description:
'This is a very long description that is expected to wrap onto multiple lines in a narrow terminal. We want to ensure that all lines are correctly indented.',
},
],
multiSelect: false,
},
];
const { lastFrame, waitUntilReady } = await renderWithProviders(
<AskUserDialog
questions={questions}
onSubmit={vi.fn()}
onCancel={vi.fn()}
width={40} // Narrow width to force wrapping
/>,
{ width: 40 },
);
await waitFor(async () => {
await waitUntilReady();
// Snapshot will capture the visual alignment
expect(lastFrame()).toMatchSnapshot();
});
});
it('indents multi-line descriptions correctly in multi-select mode', async () => {
const questions: Question[] = [
{
question: 'Multi-select?',
header: 'Indent Test',
type: QuestionType.CHOICE,
options: [
{
label: 'Option 1',
description:
'This is a very long description that is expected to wrap onto multiple lines in a narrow terminal. We want to ensure that all lines are correctly indented even with checkboxes.',
},
],
multiSelect: true,
},
];
const { lastFrame, waitUntilReady } = await renderWithProviders(
<AskUserDialog
questions={questions}
onSubmit={vi.fn()}
onCancel={vi.fn()}
width={40} // Narrow width to force wrapping
/>,
{ width: 40 },
);
await waitFor(async () => {
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
});
});
@@ -1004,13 +1004,15 @@ const ChoiceQuestionView: React.FC<ChoiceQuestionViewProps> = ({
)}
</Box>
{optionItem.description && (
<Text color={theme.text.secondary} wrap="wrap">
{' '}
<RenderInline
text={optionItem.description}
defaultColor={theme.text.secondary}
/>
</Text>
// Padding aligns with option label: 4 for multi-select (checkbox + space), 1 for single-select
<Box paddingLeft={showCheck ? 4 : 1}>
<Text color={theme.text.secondary} wrap="wrap">
<RenderInline
text={optionItem.description}
defaultColor={theme.text.secondary}
/>
</Text>
</Box>
)}
</Box>
);
@@ -5332,6 +5332,34 @@ describe('InputPrompt', () => {
});
});
});
describe('terminal buffer rendering', () => {
it('does not clip the last char of a visual line whose width equals inputWidth', async () => {
const fullLine = '1234567890'; // 10 chars, exactly props.inputWidth
props.inputWidth = 10;
props.suggestionsWidth = 10;
vi.spyOn(props.config, 'getUseTerminalBuffer').mockReturnValue(true);
mockBuffer.text = fullLine;
mockBuffer.lines = [fullLine];
mockBuffer.allVisualLines = [fullLine];
mockBuffer.viewportVisualLines = [fullLine];
mockBuffer.visualToLogicalMap = [[0, 0]];
mockBuffer.visualToTransformedMap = [0];
mockBuffer.transformationsByLine = [[]];
mockBuffer.cursor = [0, fullLine.length];
mockBuffer.visualCursor = [0, fullLine.length];
const { lastFrame, unmount } = await renderWithProviders(
<TestInputPrompt {...props} />,
{ uiActions },
);
await waitFor(() => {
expect(clean(lastFrame())).toContain(fullLine);
});
unmount();
});
});
});
function clean(str: string | undefined): string {
@@ -93,6 +93,8 @@ import { useIsHelpDismissKey } from '../utils/shortcutsHelp.js';
import { useRepeatedKeyPress } from '../hooks/useRepeatedKeyPress.js';
import { useKeyMatchers } from '../hooks/useKeyMatchers.js';
const SCROLLBAR_GUTTER_WIDTH = 1;
/**
* Returns if the terminal can be trusted to handle paste events atomically
* rather than potentially sending multiple paste events separated by line
@@ -1868,7 +1870,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
? `line-${item.absoluteVisualIdx}`
: `ghost-${item.index}`
}
width={inputWidth}
width={inputWidth + SCROLLBAR_GUTTER_WIDTH}
backgroundColor={listBackgroundColor}
containerHeight={Math.min(
buffer.viewportHeight,
@@ -12,7 +12,7 @@ import { waitFor } from '../../test-utils/async.js';
import { createMockSettings } from '../../test-utils/settings.js';
import {
DEFAULT_GEMINI_MODEL,
DEFAULT_GEMINI_MODEL_AUTO,
GEMINI_MODEL_ALIAS_AUTO,
DEFAULT_GEMINI_FLASH_MODEL,
DEFAULT_GEMINI_FLASH_LITE_MODEL,
PREVIEW_GEMINI_MODEL,
@@ -93,7 +93,7 @@ describe('<ModelDialog />', () => {
beforeEach(() => {
vi.resetAllMocks();
mockGetModel.mockReturnValue(DEFAULT_GEMINI_MODEL_AUTO);
mockGetModel.mockReturnValue(GEMINI_MODEL_ALIAS_AUTO);
mockGetHasAccessToPreviewModel.mockReturnValue(false);
mockGetGemini31LaunchedSync.mockReturnValue(false);
mockGetGemini31FlashLiteLaunchedSync.mockReturnValue(false);
@@ -102,8 +102,7 @@ describe('<ModelDialog />', () => {
// Default implementation for getDisplayString
mockGetDisplayString.mockImplementation((val: string) => {
if (val === 'auto-gemini-2.5') return 'Auto (Gemini 2.5)';
if (val === 'auto-gemini-3') return 'Auto (Preview)';
if (val === 'auto') return 'Auto';
return val;
});
});
@@ -234,7 +233,7 @@ describe('<ModelDialog />', () => {
await waitFor(() => {
expect(mockSetModel).toHaveBeenCalledWith(
DEFAULT_GEMINI_MODEL_AUTO,
GEMINI_MODEL_ALIAS_AUTO,
true, // Session only by default
);
expect(mockOnClose).toHaveBeenCalled();
@@ -292,7 +291,7 @@ describe('<ModelDialog />', () => {
await waitFor(() => {
expect(mockSetModel).toHaveBeenCalledWith(
DEFAULT_GEMINI_MODEL_AUTO,
GEMINI_MODEL_ALIAS_AUTO,
false, // Persist enabled
);
expect(mockOnClose).toHaveBeenCalled();
@@ -355,7 +354,7 @@ describe('<ModelDialog />', () => {
mockGetModel.mockReturnValue(DEFAULT_GEMINI_MODEL);
mockGetDisplayString.mockImplementation((val: string) => {
if (val === DEFAULT_GEMINI_MODEL) return 'My Custom Model Display';
if (val === 'auto-gemini-2.5') return 'Auto (Gemini 2.5)';
if (val === 'auto') return 'Auto';
return val;
});
const { lastFrame, unmount } = await renderComponent();
@@ -369,9 +368,9 @@ describe('<ModelDialog />', () => {
mockGetHasAccessToPreviewModel.mockReturnValue(true);
});
it('shows Auto (Preview) in main view when access is granted', async () => {
it('shows Auto in main view when access is granted', async () => {
const { lastFrame, unmount } = await renderComponent();
expect(lastFrame()).toContain('Auto (Preview)');
expect(lastFrame()).toContain('Auto');
unmount();
});
+17 -18
View File
@@ -14,11 +14,10 @@ import {
PREVIEW_GEMINI_3_1_MODEL,
PREVIEW_GEMINI_FLASH_MODEL,
PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL,
PREVIEW_GEMINI_MODEL_AUTO,
DEFAULT_GEMINI_MODEL,
DEFAULT_GEMINI_FLASH_MODEL,
DEFAULT_GEMINI_FLASH_LITE_MODEL,
DEFAULT_GEMINI_MODEL_AUTO,
GEMINI_MODEL_ALIAS_AUTO,
GEMMA_4_31B_IT_MODEL,
GEMMA_4_26B_A4B_IT_MODEL,
ModelSlashCommandEvent,
@@ -27,6 +26,8 @@ import {
AuthType,
PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL,
isProModel,
getChannelFromVersion,
getAutoModelDescription,
} from '@google/gemini-cli-core';
import { useKeypress } from '../hooks/useKeypress.js';
import { theme } from '../semantic-colors.js';
@@ -63,7 +64,7 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
}, [config]);
// Determine the Preferred Model (read once when the dialog opens).
const preferredModel = config?.getModel() || DEFAULT_GEMINI_MODEL_AUTO;
const preferredModel = config?.getModel() || GEMINI_MODEL_ALIAS_AUTO;
const shouldShowPreviewModels = config?.getHasAccessToPreviewModel();
const useGemini31 = config?.getGemini31LaunchedSync?.() ?? false;
@@ -122,6 +123,11 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
{ isActive: true },
);
const releaseChannel = useMemo(
() => getChannelFromVersion(config?.clientVersion ?? ''),
[config?.clientVersion],
);
const mainOptions = useMemo(() => {
// --- DYNAMIC PATH ---
if (
@@ -136,6 +142,7 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
useCustomTools: useCustomToolModel,
hasAccessToPreview: shouldShowPreviewModels,
hasAccessToProModel,
releaseChannel,
});
const list = allOptions
@@ -161,11 +168,10 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
// --- LEGACY PATH ---
const list = [
{
value: DEFAULT_GEMINI_MODEL_AUTO,
title: getDisplayString(DEFAULT_GEMINI_MODEL_AUTO),
description:
'Let Gemini CLI decide the best model for the task: gemini-2.5-pro, gemini-2.5-flash',
key: DEFAULT_GEMINI_MODEL_AUTO,
value: GEMINI_MODEL_ALIAS_AUTO,
title: getDisplayString(GEMINI_MODEL_ALIAS_AUTO),
description: getAutoModelDescription(releaseChannel, useGemini31),
key: GEMINI_MODEL_ALIAS_AUTO,
},
{
value: 'Manual',
@@ -177,16 +183,6 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
},
];
if (shouldShowPreviewModels) {
list.unshift({
value: PREVIEW_GEMINI_MODEL_AUTO,
title: getDisplayString(PREVIEW_GEMINI_MODEL_AUTO),
description: useGemini31
? 'Let Gemini CLI decide the best model for the task: gemini-3.1-pro, gemini-3-flash'
: 'Let Gemini CLI decide the best model for the task: gemini-3-pro, gemini-3-flash',
key: PREVIEW_GEMINI_MODEL_AUTO,
});
}
return list;
}, [
config,
@@ -196,6 +192,7 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
useGemini31FlashLite,
useCustomToolModel,
hasAccessToProModel,
releaseChannel,
]);
const manualOptions = useMemo(() => {
@@ -212,6 +209,7 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
useCustomTools: useCustomToolModel,
hasAccessToPreview: shouldShowPreviewModels,
hasAccessToProModel,
releaseChannel,
});
return allOptions
@@ -304,6 +302,7 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
useGemini31FlashLite,
useCustomToolModel,
hasAccessToProModel,
releaseChannel,
config,
]);
@@ -562,6 +562,13 @@ export const useSessionBrowserInput = (
state.setActiveIndex(0);
state.setScrollOffset(0);
return true;
} else if (key.name === 'enter') {
const selectedSession =
state.filteredAndSortedSessions[state.activeIndex];
if (selectedSession && !selectedSession.isCurrentSession) {
onResumeSession(selectedSession);
}
return true;
} else if (
key.sequence &&
key.sequence.length === 1 &&
@@ -111,6 +111,42 @@ Enter to select · ↑/↓ to navigate · Esc to cancel
"
`;
exports[`AskUserDialog > indents multi-line descriptions correctly 1`] = `
"Single choice?
● 1. Option 1
This is a very long description
that is expected to wrap onto
multiple lines in a narrow
terminal. We want to ensure that
all lines are correctly indented.
2. Enter a custom value
Enter to select · ↑/↓ to navigate · Esc
to cancel
"
`;
exports[`AskUserDialog > indents multi-line descriptions correctly in multi-select mode 1`] = `
"Multi-select?
(Select all that apply)
● 1. [ ] Option 1
This is a very long description
that is expected to wrap onto
multiple lines in a narrow
terminal. We want to ensure
that all lines are correctly
indented even with checkboxes.
2. [ ] Enter a custom value
Done
Finish selection
Enter to select · ↑/↓ to navigate · Esc
to cancel
"
`;
exports[`AskUserDialog > renders question and options 1`] = `
"Which authentication method should we use?
@@ -188,7 +224,7 @@ exports[`AskUserDialog > verifies "All of the above" visual state with snapshot
1. [x] TypeScript
2. [x] ESLint
● 3. [x] All of the above
Select all options
Select all options
4. [ ] Enter a custom value
Done
Finish selection
@@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="71" viewBox="0 0 920 71">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="71" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#ffffff" textLength="900" lengthAdjust="spacingAndGlyphs">────────────────────────────────────────────────────────────────────────────────────────────────────</text>
<text x="0" y="19" fill="#ffffff" textLength="900" lengthAdjust="spacingAndGlyphs"> &gt; hello </text>
<text x="0" y="36" fill="#ffffff" textLength="900" lengthAdjust="spacingAndGlyphs">────────────────────────────────────────────────────────────────────────────────────────────────────</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -60,6 +60,12 @@ exports[`InputPrompt > Highlighting and Cursor Display > single-line scenarios >
────────────────────────────────────────────────────────────────────────────────────────────────────"
`;
exports[`InputPrompt > Highlighting and Cursor Display > single-line scenarios > should display cursor correctly 'at the end of the line' 2`] = `
"────────────────────────────────────────────────────────────────────────────────────────────────────
> hello
────────────────────────────────────────────────────────────────────────────────────────────────────"
`;
exports[`InputPrompt > Highlighting and Cursor Display > single-line scenarios > should display cursor correctly 'for multi-byte unicode characters' 1`] = `
"────────────────────────────────────────────────────────────────────────────────────────────────────
> hello 👍 world
@@ -168,6 +174,27 @@ exports[`InputPrompt > mouse interaction > should toggle paste expansion on doub
"
`;
exports[`InputPrompt > mouse interaction > should toggle paste expansion on double-click 4`] = `
"▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
> [Pasted Text: 10 lines]
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
"
`;
exports[`InputPrompt > mouse interaction > should toggle paste expansion on double-click 5`] = `
"▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
> [Pasted Text: 10 lines]
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
"
`;
exports[`InputPrompt > mouse interaction > should toggle paste expansion on double-click 6`] = `
"▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
> [Pasted Text: 10 lines]
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
"
`;
exports[`InputPrompt > multiline rendering > should correctly render multiline input including blank lines 1`] = `
"────────────────────────────────────────────────────────────────────────────────────────────────────
> hello
@@ -6,7 +6,11 @@
import { waitFor } from '../../../test-utils/async.js';
import { renderWithProviders } from '../../../test-utils/render.js';
import { SubagentGroupDisplay } from './SubagentGroupDisplay.js';
import { Kind, CoreToolCallStatus } from '@google/gemini-cli-core';
import {
Kind,
CoreToolCallStatus,
SubagentState,
} from '@google/gemini-cli-core';
import type { IndividualToolCallDisplay } from '../../types.js';
import { describe, it, expect, vi } from 'vitest';
import { Text } from 'ink';
@@ -27,12 +31,12 @@ describe('<SubagentGroupDisplay />', () => {
resultDisplay: {
isSubagentProgress: true,
agentName: 'api-monitor',
state: 'running',
state: SubagentState.RUNNING,
recentActivity: [
{
id: 'act-1',
type: 'tool_call',
status: 'running',
status: SubagentState.RUNNING,
content: '',
displayName: 'Action Required',
description: 'Verify server is running',
@@ -50,13 +54,13 @@ describe('<SubagentGroupDisplay />', () => {
resultDisplay: {
isSubagentProgress: true,
agentName: 'db-manager',
state: 'completed',
state: SubagentState.COMPLETED,
result: 'Database schema validated',
recentActivity: [
{
id: 'act-2',
type: 'thought',
status: 'completed',
status: SubagentState.COMPLETED,
content: 'Database schema validated',
},
],
@@ -13,6 +13,7 @@ import {
isSubagentProgress,
checkExhaustive,
type SubagentActivityItem,
SubagentState,
} from '@google/gemini-cli-core';
import {
SubagentProgressDisplay,
@@ -66,13 +67,13 @@ export const SubagentGroupDisplay: React.FC<SubagentGroupDisplayProps> = ({
const singleAgent = toolCalls[0].resultDisplay;
if (isSubagentProgress(singleAgent)) {
switch (singleAgent.state) {
case 'completed':
case SubagentState.COMPLETED:
headerText = 'Agent Completed';
break;
case 'cancelled':
case SubagentState.CANCELLED:
headerText = 'Agent Cancelled';
break;
case 'error':
case SubagentState.ERROR:
headerText = 'Agent Error';
break;
default:
@@ -88,8 +89,8 @@ export const SubagentGroupDisplay: React.FC<SubagentGroupDisplayProps> = ({
for (const tc of toolCalls) {
const progress = tc.resultDisplay;
if (isSubagentProgress(progress)) {
if (progress.state === 'completed') completedCount++;
else if (progress.state === 'running') runningCount++;
if (progress.state === SubagentState.COMPLETED) completedCount++;
else if (progress.state === SubagentState.RUNNING) runningCount++;
} else {
// It hasn't emitted progress yet, but it is "running"
runningCount++;
@@ -200,7 +201,7 @@ export const SubagentGroupDisplay: React.FC<SubagentGroupDisplayProps> = ({
let content = 'Starting...';
let formattedArgs: string | undefined;
if (progress.state === 'completed') {
if (progress.state === SubagentState.COMPLETED) {
if (
progress.terminateReason &&
progress.terminateReason !== 'GOAL'
@@ -223,18 +224,18 @@ export const SubagentGroupDisplay: React.FC<SubagentGroupDisplayProps> = ({
}
const displayArgs =
progress.state === 'completed' ? '' : formattedArgs;
progress.state === SubagentState.COMPLETED ? '' : formattedArgs;
const renderStatusIcon = () => {
const state = progress.state ?? 'running';
const state = progress.state ?? SubagentState.RUNNING;
switch (state) {
case 'running':
case SubagentState.RUNNING:
return <Text color={theme.text.primary}>!</Text>;
case 'completed':
case SubagentState.COMPLETED:
return <Text color={theme.status.success}></Text>;
case 'cancelled':
case SubagentState.CANCELLED:
return <Text color={theme.status.warning}></Text>;
case 'error':
case SubagentState.ERROR:
return <Text color={theme.status.error}></Text>;
default:
return checkExhaustive(state);
@@ -8,6 +8,7 @@ import { describe, it, expect } from 'vitest';
import { renderWithProviders } from '../../../test-utils/render.js';
import { SubagentHistoryMessage } from './SubagentHistoryMessage.js';
import type { HistoryItemSubagent } from '../../types.js';
import { SubagentState } from '@google/gemini-cli-core';
describe('SubagentHistoryMessage', () => {
const mockItem: HistoryItemSubagent = {
@@ -18,19 +19,19 @@ describe('SubagentHistoryMessage', () => {
id: '1',
type: 'thought',
content: 'Thinking about the problem',
status: 'completed',
status: SubagentState.COMPLETED,
},
{
id: '2',
type: 'tool_call',
content: 'Calling search_web',
status: 'running',
status: SubagentState.RUNNING,
},
{
id: '3',
type: 'tool_call',
content: 'Calling read_file fail',
status: 'error',
status: SubagentState.ERROR,
},
],
};
@@ -6,7 +6,7 @@
import { render, cleanup } from '../../../test-utils/render.js';
import { SubagentProgressDisplay } from './SubagentProgressDisplay.js';
import type { SubagentProgress } from '@google/gemini-cli-core';
import { type SubagentProgress, SubagentState } from '@google/gemini-cli-core';
import { describe, it, expect, vi, afterEach } from 'vitest';
describe('<SubagentProgressDisplay />', () => {
@@ -25,7 +25,7 @@ describe('<SubagentProgressDisplay />', () => {
type: 'tool_call',
content: 'run_shell_command',
args: '{"command": "echo hello", "description": "Say hello"}',
status: 'running',
status: SubagentState.RUNNING,
},
],
};
@@ -48,7 +48,7 @@ describe('<SubagentProgressDisplay />', () => {
displayName: 'RunShellCommand',
description: 'Executing echo hello',
args: '{"command": "echo hello"}',
status: 'running',
status: SubagentState.RUNNING,
},
],
};
@@ -69,7 +69,7 @@ describe('<SubagentProgressDisplay />', () => {
type: 'tool_call',
content: 'run_shell_command',
args: '{"command": "echo hello"}',
status: 'running',
status: SubagentState.RUNNING,
},
],
};
@@ -90,7 +90,7 @@ describe('<SubagentProgressDisplay />', () => {
type: 'tool_call',
content: 'write_file',
args: '{"file_path": "/tmp/test.txt", "content": "foo"}',
status: 'completed',
status: SubagentState.COMPLETED,
},
],
};
@@ -113,7 +113,7 @@ describe('<SubagentProgressDisplay />', () => {
type: 'tool_call',
content: 'run_shell_command',
args: JSON.stringify({ description: longDesc }),
status: 'running',
status: SubagentState.RUNNING,
},
],
};
@@ -133,7 +133,7 @@ describe('<SubagentProgressDisplay />', () => {
id: '5',
type: 'thought',
content: 'Thinking about life',
status: 'running',
status: SubagentState.RUNNING,
},
],
};
@@ -149,7 +149,7 @@ describe('<SubagentProgressDisplay />', () => {
isSubagentProgress: true,
agentName: 'TestAgent',
recentActivity: [],
state: 'cancelled',
state: SubagentState.CANCELLED,
};
const { lastFrame } = await render(
@@ -167,7 +167,7 @@ describe('<SubagentProgressDisplay />', () => {
id: '6',
type: 'thought',
content: 'Request cancelled.',
status: 'error',
status: SubagentState.ERROR,
},
],
};
@@ -188,7 +188,7 @@ describe('<SubagentProgressDisplay />', () => {
type: 'tool_call',
content: 'run_shell_command',
args: '{"command": "echo hello"}',
status: 'error',
status: SubagentState.ERROR,
},
],
};
@@ -9,9 +9,10 @@ import { Box, Text } from 'ink';
import { theme } from '../../semantic-colors.js';
import Spinner from 'ink-spinner';
import { MarkdownDisplay } from '../../utils/MarkdownDisplay.js';
import type {
SubagentProgress,
SubagentActivityItem,
import {
type SubagentProgress,
type SubagentActivityItem,
SubagentState,
} from '@google/gemini-cli-core';
import { TOOL_STATUS } from '../../constants.js';
import { STATUS_INDICATOR_WIDTH } from './ToolShared.js';
@@ -62,13 +63,13 @@ export const SubagentProgressDisplay: React.FC<
let headerText: string | undefined;
let headerColor = theme.text.secondary;
if (progress.state === 'cancelled') {
if (progress.state === SubagentState.CANCELLED) {
headerText = `Subagent ${progress.agentName} was cancelled.`;
headerColor = theme.status.warning;
} else if (progress.state === 'error') {
} else if (progress.state === SubagentState.ERROR) {
headerText = `Subagent ${progress.agentName} failed.`;
headerColor = theme.status.error;
} else if (progress.state === 'completed') {
} else if (progress.state === SubagentState.COMPLETED) {
headerText = `Subagent ${progress.agentName} completed.`;
headerColor = theme.status.success;
} else {
@@ -107,13 +108,13 @@ export const SubagentProgressDisplay: React.FC<
);
} else if (item.type === 'tool_call') {
const statusSymbol =
item.status === 'running' ? (
item.status === SubagentState.RUNNING ? (
<Spinner type="dots" />
) : item.status === 'completed' ? (
) : item.status === SubagentState.COMPLETED ? (
<Text color={theme.status.success}>
{TOOL_STATUS.SUCCESS}
</Text>
) : item.status === 'cancelled' ? (
) : item.status === SubagentState.CANCELLED ? (
<Text color={theme.status.warning} bold>
{TOOL_STATUS.CANCELED}
</Text>
@@ -135,7 +136,7 @@ export const SubagentProgressDisplay: React.FC<
<Text
bold
color={theme.text.primary}
strikethrough={item.status === 'cancelled'}
strikethrough={item.status === SubagentState.CANCELLED}
>
{item.displayName || item.content}
</Text>
@@ -144,7 +145,9 @@ export const SubagentProgressDisplay: React.FC<
<Text
color={theme.text.secondary}
wrap="truncate"
strikethrough={item.status === 'cancelled'}
strikethrough={
item.status === SubagentState.CANCELLED
}
>
{displayArgs}
</Text>
@@ -170,7 +173,7 @@ export const SubagentProgressDisplay: React.FC<
)}
<MarkdownDisplay
text={safeJsonToMarkdown(progress.result)}
isPending={progress.state !== 'completed'}
isPending={progress.state !== SubagentState.COMPLETED}
terminalWidth={terminalWidth}
/>
</Box>
@@ -13,6 +13,7 @@ import {
ApprovalMode,
WRITE_FILE_DISPLAY_NAME,
Kind,
SubagentState,
} from '@google/gemini-cli-core';
import os from 'node:os';
import { createMockSettings } from '../../../test-utils/settings.js';
@@ -76,7 +77,7 @@ describe('ToolGroupMessage Regression Tests', () => {
resultDisplay: {
isSubagentProgress: true,
agentName: 'TestAgent',
state: 'running',
state: SubagentState.RUNNING,
recentActivity: [],
},
}),
@@ -112,7 +113,7 @@ describe('ToolGroupMessage Regression Tests', () => {
resultDisplay: {
isSubagentProgress: true,
agentName: 'TestAgent',
state: 'completed',
state: SubagentState.COMPLETED,
recentActivity: [],
},
}),
+13 -11
View File
@@ -170,13 +170,13 @@ async function searchResourceCandidates(
selector: (candidate: ResourceSuggestionCandidate) => candidate.searchKey,
});
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const results = await fzf.find(normalizedPattern, {
limit: MAX_SUGGESTIONS_TO_SHOW * 3,
});
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return results.map(
(result: { item: ResourceSuggestionCandidate }) => result.item.suggestion,
const results: Array<{ item: ResourceSuggestionCandidate }> = await fzf.find(
normalizedPattern,
{
limit: MAX_SUGGESTIONS_TO_SHOW * 3,
},
);
return results.map((result) => result.item.suggestion);
}
async function searchAgentCandidates(
@@ -194,11 +194,13 @@ async function searchAgentCandidates(
selector: (s: Suggestion) => s.label,
});
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const results = await fzf.find(normalizedPattern, {
limit: MAX_SUGGESTIONS_TO_SHOW,
});
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return results.map((r: { item: Suggestion }) => r.item);
const results: Array<{ item: Suggestion }> = await fzf.find(
normalizedPattern,
{
limit: MAX_SUGGESTIONS_TO_SHOW,
},
);
return results.map((r) => r.item);
}
export function useAtCompletion(props: UseAtCompletionProps): void {
@@ -21,6 +21,7 @@ import {
ROOT_SCHEDULER_ID,
CoreToolCallStatus,
type WaitingToolCall,
SubagentState,
} from '@google/gemini-cli-core';
import { createMockMessageBus } from '@google/gemini-cli-core/src/test-utils/mock-message-bus.js';
@@ -630,7 +631,7 @@ describe('useToolScheduler', () => {
id: '1',
type: 'thought',
content: 'Thinking...',
status: 'running',
status: SubagentState.RUNNING,
},
});
});
@@ -648,7 +649,7 @@ describe('useToolScheduler', () => {
id: '2',
type: 'tool_call',
content: 'Calling tool',
status: 'completed',
status: SubagentState.COMPLETED,
},
});
});
@@ -697,7 +698,7 @@ describe('useToolScheduler', () => {
id: '1',
type: 'thought',
content: 'Thinking...',
status: 'running',
status: SubagentState.RUNNING,
},
});
});
@@ -716,7 +717,7 @@ describe('useToolScheduler', () => {
id: '1',
type: 'thought',
content: 'Thinking... Done!',
status: 'completed',
status: SubagentState.COMPLETED,
},
});
});
@@ -726,6 +727,8 @@ describe('useToolScheduler', () => {
expect(result.current[0][0].subagentHistory![0].content).toBe(
'Thinking... Done!',
);
expect(result.current[0][0].subagentHistory![0].status).toBe('completed');
expect(result.current[0][0].subagentHistory![0].status).toBe(
SubagentState.COMPLETED,
);
});
});
+5 -7
View File
@@ -174,7 +174,10 @@ export const TableRenderer: React.FC<TableRendererProps> = ({
}
// --- Pre-wrap and Optimize Widths ---
const actualColumnWidths = new Array(numColumns).fill(0);
const actualColumnWidths: number[] = [];
for (let i = 0; i < numColumns; i++) {
actualColumnWidths.push(0);
}
const wrapAndProcessRow = (row: StyledLine[]) => {
const rowResult: ProcessedLine[][] = [];
@@ -208,11 +211,7 @@ export const TableRenderer: React.FC<TableRendererProps> = ({
const wrappedRows = styledRows.map((row) => wrapAndProcessRow(row));
// Use the TIGHTEST widths that fit the wrapped content + padding
const adjustedWidths = actualColumnWidths.map(
(w) =>
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
w + COLUMN_PADDING,
);
const adjustedWidths = actualColumnWidths.map((w) => w + COLUMN_PADDING);
return { wrappedHeaders, wrappedRows, adjustedWidths };
}, [styledHeaders, styledRows, terminalWidth]);
@@ -263,7 +262,6 @@ export const TableRenderer: React.FC<TableRendererProps> = ({
isHeader = false,
): React.ReactNode => {
const renderedCells = cells.map((cell, index) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const width = adjustedWidths[index] || 0;
return renderCell(cell, width, isHeader);
});
+9 -7
View File
@@ -111,18 +111,20 @@ function resolveEnvVarsInObjectInternal<T>(
// Check for circular reference
if (visited.has(obj)) {
// Return a shallow copy to break the cycle
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
return [...obj] as unknown as T;
const copy: unknown = [...obj];
const isTArray = (val: unknown): val is T => Array.isArray(val);
if (isTArray(copy)) return copy;
throw new Error('Unreachable');
}
visited.add(obj);
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const result = obj.map((item) =>
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
const mapped: unknown = obj.map((item: unknown) =>
resolveEnvVarsInObjectInternal(item, visited, customEnv),
) as unknown as T;
);
visited.delete(obj);
return result;
const isTArray = (val: unknown): val is T => Array.isArray(val);
if (isTArray(mapped)) return mapped;
throw new Error('Unreachable');
}
if (typeof obj === 'object') {
+1 -2
View File
@@ -83,8 +83,7 @@ export const getLatestGitHubRelease = async (
if (!releaseTag) {
throw new Error(`Response did not include tag_name field`);
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return releaseTag;
return typeof releaseTag === 'string' ? releaseTag : '';
} catch (error) {
debugLogger.debug(
`Failed to determine latest run-gemini-cli release:`,
+1 -3
View File
@@ -29,8 +29,7 @@ export function tryParseJSON(input: string): object | null {
if (!checkInput(input)) return null;
const trimmed = input.trim();
try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const parsed = JSON.parse(trimmed);
const parsed: unknown = JSON.parse(trimmed);
if (parsed === null || typeof parsed !== 'object') {
return null;
}
@@ -40,7 +39,6 @@ export function tryParseJSON(input: string): object | null {
if (!Array.isArray(parsed) && Object.keys(parsed).length === 0) return null;
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return parsed;
} catch {
return null;
+114
View File
@@ -616,6 +616,120 @@ describe('SessionSelector', () => {
expect(sessions.length).toBe(1);
expect(sessions[0].id).toBe(mainSessionId);
});
it('should list legacy session JSON without timestamps (regression #18593)', async () => {
const sessionId = randomUUID();
const chatsDir = path.join(tmpDir, 'chats');
await fs.mkdir(chatsDir, { recursive: true });
const session = {
sessionId,
projectHash: 'test-hash',
messages: [
{
type: 'user',
content: 'Legacy session message',
id: 'msg1',
timestamp: '2024-01-01T10:00:00.000Z',
},
],
};
const filePath = path.join(
chatsDir,
`${SESSION_FILE_PREFIX}2024-01-01T10-00-${sessionId.slice(0, 8)}.json`,
);
await fs.writeFile(filePath, JSON.stringify(session, null, 2));
const fallbackTimestamp = new Date('2024-01-01T10:30:00.000Z');
await fs.utimes(filePath, fallbackTimestamp, fallbackTimestamp);
const sessionSelector = new SessionSelector(storage);
const sessions = await sessionSelector.listSessions();
expect(sessions.length).toBe(1);
expect(sessions[0].id).toBe(sessionId);
expect(sessions[0].startTime).toBe(fallbackTimestamp.toISOString());
expect(sessions[0].lastUpdated).toBe(fallbackTimestamp.toISOString());
});
it('should resolve legacy session JSON without timestamps by UUID (regression #18593)', async () => {
const sessionId = randomUUID();
const chatsDir = path.join(tmpDir, 'chats');
await fs.mkdir(chatsDir, { recursive: true });
const session = {
sessionId,
projectHash: 'test-hash',
messages: [
{
type: 'user',
content: 'Legacy session message',
id: 'msg1',
timestamp: '2024-01-01T10:00:00.000Z',
},
],
};
const filePath = path.join(
chatsDir,
`${SESSION_FILE_PREFIX}2024-01-01T10-00-${sessionId.slice(0, 8)}.json`,
);
await fs.writeFile(filePath, JSON.stringify(session, null, 2));
const fallbackTimestamp = new Date('2024-01-01T10:30:00.000Z');
await fs.utimes(filePath, fallbackTimestamp, fallbackTimestamp);
const sessionSelector = new SessionSelector(storage);
const result = await sessionSelector.resolveSession(sessionId);
expect(result.sessionData.sessionId).toBe(sessionId);
expect(result.sessionData.startTime).toBe(fallbackTimestamp.toISOString());
expect(result.sessionData.lastUpdated).toBe(
fallbackTimestamp.toISOString(),
);
});
it('should throw INVALID_SESSION_IDENTIFIER for a UUID that does not exist on disk at all', async () => {
const existingSessionId = randomUUID();
const nonExistentId = randomUUID();
const chatsDir = path.join(tmpDir, 'chats');
await fs.mkdir(chatsDir, { recursive: true });
const session = {
sessionId: existingSessionId,
projectHash: 'test-hash',
startTime: '2024-01-01T10:00:00.000Z',
lastUpdated: '2024-01-01T10:30:00.000Z',
messages: [
{
type: 'user',
content: 'Hello',
id: 'msg1',
timestamp: '2024-01-01T10:00:00.000Z',
},
],
};
await fs.writeFile(
path.join(
chatsDir,
`${SESSION_FILE_PREFIX}2024-01-01T10-00-${existingSessionId.slice(0, 8)}.json`,
),
JSON.stringify(session, null, 2),
);
const sessionSelector = new SessionSelector(storage);
await expect(sessionSelector.findSession(nonExistentId)).rejects.toSatisfy(
(error) => {
expect(error).toBeInstanceOf(SessionError);
expect((error as SessionError).code).toBe('INVALID_SESSION_IDENTIFIER');
return true;
},
);
});
});
describe('extractFirstUserMessage', () => {
+21 -8
View File
@@ -270,15 +270,23 @@ export const getAllSessionFiles = async (
}
// Validate required fields
if (
!content.sessionId ||
!content.startTime ||
!content.lastUpdated
) {
if (!content.sessionId) {
// Missing required fields - treat as corrupted
return { fileName: file, sessionInfo: null };
}
const fileTimestamp =
!content.startTime || !content.lastUpdated
? (
await fs.stat(filePath).catch(() => undefined)
)?.mtime.toISOString()
: undefined;
const fallbackTimestamp = fileTimestamp ?? new Date().toISOString();
const startTime =
content.startTime || content.lastUpdated || fallbackTimestamp;
const lastUpdated =
content.lastUpdated || content.startTime || fallbackTimestamp;
// Skip sessions that only contain system messages (info, error, warning)
if (!content.hasUserOrAssistantMessage) {
return { fileName: file, sessionInfo: null };
@@ -319,8 +327,8 @@ export const getAllSessionFiles = async (
id: content.sessionId,
file: file.replace(/\.jsonl?$/, ''),
fileName: file,
startTime: content.startTime,
lastUpdated: content.lastUpdated,
startTime,
lastUpdated,
messageCount: content.messageCount ?? content.messages.length,
displayName: content.summary
? stripUnsafeCharacters(content.summary)
@@ -546,12 +554,17 @@ export class SessionSelector {
if (!sessionData) {
throw new Error('Failed to load session data');
}
const normalizedSessionData = {
...sessionData,
startTime: sessionData.startTime || sessionInfo.startTime,
lastUpdated: sessionData.lastUpdated || sessionInfo.lastUpdated,
};
const displayInfo = `Session ${sessionInfo.index}: ${sessionInfo.firstUserMessage} (${sessionInfo.messageCount} messages, ${formatRelativeTime(sessionInfo.lastUpdated)})`;
return {
sessionPath,
sessionData,
sessionData: normalizedSessionData,
displayInfo,
};
} catch (error) {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli-core",
"version": "0.42.0-nightly.20260428.g59b2dea0e",
"version": "0.44.0-nightly.20260512.g022e8baef",
"description": "Gemini CLI Core",
"license": "Apache-2.0",
"repository": {
+4 -4
View File
@@ -16,7 +16,7 @@ import type {
AgentInterface,
} from '@a2a-js/sdk';
import type { SendMessageResult } from './a2a-client-manager.js';
import type { SubagentActivityItem } from './types.js';
import { type SubagentActivityItem, SubagentState } from './types.js';
export const AUTH_REQUIRED_MSG = `[Authorization Required] The agent has indicated it requires authorization to proceed. Please follow the agent's instructions.`;
@@ -143,7 +143,7 @@ export class A2AResultReassembler {
id: 'auth-required',
type: 'thought',
content: AUTH_REQUIRED_MSG,
status: 'running',
status: SubagentState.RUNNING,
});
}
@@ -152,7 +152,7 @@ export class A2AResultReassembler {
id: `msg-${index}`,
type: 'thought',
content: msg.trim(),
status: 'completed',
status: SubagentState.COMPLETED,
});
});
@@ -161,7 +161,7 @@ export class A2AResultReassembler {
id: 'pending',
type: 'thought',
content: 'Working...',
status: 'running',
status: SubagentState.RUNNING,
});
}
@@ -32,6 +32,7 @@ import {
type SubagentActivityItem,
AgentTerminateMode,
isToolActivityError,
SubagentState,
} from '../types.js';
import type { MessageBus } from '../../confirmation-bus/message-bus.js';
import { createBrowserAgentDefinition } from './browserAgentFactory.js';
@@ -123,7 +124,7 @@ export class BrowserAgentInvocation extends BaseToolInvocation<
isSubagentProgress: true,
agentName: this.agentName,
recentActivity: [],
state: 'running',
state: SubagentState.RUNNING,
};
updateOutput(initialProgress);
}
@@ -137,7 +138,7 @@ export class BrowserAgentInvocation extends BaseToolInvocation<
id: randomUUID(),
type: 'thought',
content: sanitizedMsg,
status: 'completed',
status: SubagentState.COMPLETED,
});
if (recentActivity.length > MAX_RECENT_ACTIVITY) {
recentActivity = recentActivity.slice(-MAX_RECENT_ACTIVITY);
@@ -146,7 +147,7 @@ export class BrowserAgentInvocation extends BaseToolInvocation<
isSubagentProgress: true,
agentName: this.agentName,
recentActivity: [...recentActivity],
state: 'running',
state: SubagentState.RUNNING,
} as SubagentProgress);
}
: undefined;
@@ -175,7 +176,7 @@ export class BrowserAgentInvocation extends BaseToolInvocation<
if (
lastItem &&
lastItem.type === 'thought' &&
lastItem.status === 'running'
lastItem.status === SubagentState.RUNNING
) {
lastItem.content = sanitizeThoughtContent(text);
} else {
@@ -183,7 +184,7 @@ export class BrowserAgentInvocation extends BaseToolInvocation<
id: randomUUID(),
type: 'thought',
content: sanitizeThoughtContent(text),
status: 'running',
status: SubagentState.RUNNING,
});
}
updated = true;
@@ -210,7 +211,7 @@ export class BrowserAgentInvocation extends BaseToolInvocation<
displayName,
description,
args,
status: 'running',
status: SubagentState.RUNNING,
});
updated = true;
break;
@@ -227,9 +228,11 @@ export class BrowserAgentInvocation extends BaseToolInvocation<
recentActivity[i].type === 'tool_call' &&
callId != null &&
recentActivity[i].id === callId &&
recentActivity[i].status === 'running'
recentActivity[i].status === SubagentState.RUNNING
) {
recentActivity[i].status = isError ? 'error' : 'completed';
recentActivity[i].status = isError
? SubagentState.ERROR
: SubagentState.COMPLETED;
updated = true;
break;
}
@@ -242,7 +245,9 @@ export class BrowserAgentInvocation extends BaseToolInvocation<
const callId = activity.data['callId']
? String(activity.data['callId'])
: undefined;
const newStatus = isCancellation ? 'cancelled' : 'error';
const newStatus = isCancellation
? SubagentState.CANCELLED
: SubagentState.ERROR;
if (callId) {
// Mark the specific tool as error/cancelled
@@ -250,7 +255,7 @@ export class BrowserAgentInvocation extends BaseToolInvocation<
if (
recentActivity[i].type === 'tool_call' &&
recentActivity[i].id === callId &&
recentActivity[i].status === 'running'
recentActivity[i].status === SubagentState.RUNNING
) {
recentActivity[i].status = newStatus;
updated = true;
@@ -260,7 +265,10 @@ export class BrowserAgentInvocation extends BaseToolInvocation<
} else {
// No specific tool — mark ALL running tool_call items
for (const item of recentActivity) {
if (item.type === 'tool_call' && item.status === 'running') {
if (
item.type === 'tool_call' &&
item.status === SubagentState.RUNNING
) {
item.status = newStatus;
updated = true;
}
@@ -293,7 +301,7 @@ export class BrowserAgentInvocation extends BaseToolInvocation<
isSubagentProgress: true,
agentName: this.agentName,
recentActivity: [...recentActivity],
state: 'running',
state: SubagentState.RUNNING,
};
updateOutput(progress);
}
@@ -330,13 +338,13 @@ ${output.result}`;
// GOAL = agent completed its task normally.
// ABORTED = user cancelled.
// Others (ERROR, MAX_TURNS, ERROR_NO_COMPLETE_TASK_CALL) = error.
let progressState: SubagentProgress['state'];
let progressState: SubagentState;
if (output.terminate_reason === AgentTerminateMode.ABORTED) {
progressState = 'cancelled';
progressState = SubagentState.CANCELLED;
} else if (output.terminate_reason === AgentTerminateMode.GOAL) {
progressState = 'completed';
progressState = SubagentState.COMPLETED;
} else {
progressState = 'error';
progressState = SubagentState.ERROR;
}
const progress: SubagentProgress = {
@@ -366,8 +374,8 @@ ${output.result}`;
// Mark any running items as error/cancelled
for (const item of recentActivity) {
if (item.status === 'running') {
item.status = isAbort ? 'cancelled' : 'error';
if (item.status === SubagentState.RUNNING) {
item.status = isAbort ? SubagentState.CANCELLED : SubagentState.ERROR;
}
}
@@ -375,7 +383,7 @@ ${output.result}`;
isSubagentProgress: true,
agentName: this.agentName,
recentActivity: [...recentActivity],
state: isAbort ? 'cancelled' : 'error',
state: isAbort ? SubagentState.CANCELLED : SubagentState.ERROR,
};
if (updateOutput) {
@@ -4199,6 +4199,49 @@ describe('LocalAgentExecutor', () => {
expect(memoryPart).toBeDefined();
expect(memoryPart?.text).toContain(mockMemory);
});
it('should omit extension context from session memory when disabled by the agent', async () => {
const definition = createTestDefinition();
definition.includeExtensionContext = false;
const executor = await LocalAgentExecutor.create(
definition,
mockConfig,
onActivity,
);
const getSessionMemorySpy = vi
.spyOn(mockConfig, 'getSessionMemory')
.mockImplementation(
(options?: { includeExtensionContext?: boolean }) =>
options?.includeExtensionContext === false
? '<loaded_context>\n<project_context>\nProject memory rule\n</project_context>\n</loaded_context>'
: '<loaded_context>\n<extension_context>\nExtension memory rule\n</extension_context>\n<project_context>\nProject memory rule\n</project_context>\n</loaded_context>',
);
vi.spyOn(mockConfig, 'isJitContextEnabled').mockReturnValue(true);
mockModelResponse([
{
name: COMPLETE_TASK_TOOL_NAME,
args: { finalResult: 'done' },
id: 'call1',
},
]);
await executor.run({ goal: 'test' }, signal);
expect(getSessionMemorySpy).toHaveBeenCalledWith({
includeExtensionContext: false,
});
const { message } = getMockMessageParams(0);
const parts = message as Part[];
const memoryPart = parts.find((p) =>
p.text?.includes('<loaded_context>'),
);
expect(memoryPart?.text).toContain('Project memory rule');
expect(memoryPart?.text).not.toContain('<extension_context>');
expect(memoryPart?.text).not.toContain('Extension memory rule');
});
});
});
});
+13 -4
View File
@@ -640,10 +640,19 @@ export class LocalAgentExecutor<TOutput extends z.ZodTypeAny> {
);
const formattedInitialHints = formatUserHintsForModel(initialHints);
// Inject loaded memory files (JIT + extension/project memory)
const environmentMemory = this.context.config.isJitContextEnabled?.()
? this.context.config.getSessionMemory()
: this.context.config.getEnvironmentMemory();
// Inject loaded memory files. Some background agents opt out of
// extension memory while still retaining project session context.
let environmentMemory: string;
if (this.context.config.isJitContextEnabled?.()) {
environmentMemory =
this.definition.includeExtensionContext === false
? this.context.config.getSessionMemory({
includeExtensionContext: false,
})
: this.context.config.getSessionMemory();
} else {
environmentMemory = this.context.config.getEnvironmentMemory();
}
const initialParts: Part[] = [];
if (environmentMemory) {
@@ -21,6 +21,7 @@ import {
type SubagentProgress,
SubagentActivityErrorType,
SUBAGENT_REJECTED_ERROR_PREFIX,
SubagentState,
} from './types.js';
import { LocalSubagentInvocation } from './local-invocation.js';
import { LocalAgentExecutor } from './local-executor.js';
@@ -215,7 +216,7 @@ describe('LocalSubagentInvocation', () => {
]);
const display = result.returnDisplay as SubagentProgress;
expect(display.isSubagentProgress).toBe(true);
expect(display.state).toBe('completed');
expect(display.state).toBe(SubagentState.COMPLETED);
expect(display.result).toBe('Analysis complete.');
expect(display.terminateReason).toBe(AgentTerminateMode.GOAL);
});
@@ -234,7 +235,7 @@ describe('LocalSubagentInvocation', () => {
const display = result.returnDisplay as SubagentProgress;
expect(display.isSubagentProgress).toBe(true);
expect(display.state).toBe('completed');
expect(display.state).toBe(SubagentState.COMPLETED);
expect(display.result).toBe('Partial progress...');
expect(display.terminateReason).toBe(AgentTerminateMode.TIMEOUT);
});
@@ -340,7 +341,7 @@ describe('LocalSubagentInvocation', () => {
expect.objectContaining({
type: 'thought',
content: 'Error: Failed',
status: 'error',
status: SubagentState.ERROR,
}),
);
});
@@ -376,7 +377,7 @@ describe('LocalSubagentInvocation', () => {
expect.objectContaining({
type: 'tool_call',
content: 'ls',
status: 'error',
status: SubagentState.ERROR,
}),
);
});
@@ -418,7 +419,7 @@ describe('LocalSubagentInvocation', () => {
expect.objectContaining({
type: 'tool_call',
content: 'ls',
status: 'cancelled',
status: SubagentState.CANCELLED,
}),
);
});
@@ -443,7 +444,7 @@ describe('LocalSubagentInvocation', () => {
expect(result.error).toBeUndefined();
const display = result.returnDisplay as SubagentProgress;
expect(display.isSubagentProgress).toBe(true);
expect(display.state).toBe('completed');
expect(display.state).toBe(SubagentState.COMPLETED);
expect(display.result).toBe('Done');
});
@@ -466,7 +467,7 @@ describe('LocalSubagentInvocation', () => {
expect.objectContaining({
type: 'thought',
content: `Error: ${error.message}`,
status: 'error',
status: SubagentState.ERROR,
}),
);
});
@@ -488,7 +489,7 @@ describe('LocalSubagentInvocation', () => {
expect(display.recentActivity).toContainEqual(
expect.objectContaining({
content: `Error: ${creationError.message}`,
status: 'error',
status: SubagentState.ERROR,
}),
);
});
+25 -19
View File
@@ -23,6 +23,7 @@ import {
SUBAGENT_REJECTED_ERROR_PREFIX,
SUBAGENT_CANCELLED_ERROR_MESSAGE,
isToolActivityError,
SubagentState,
} from './types.js';
import { randomUUID } from 'node:crypto';
import type { z } from 'zod';
@@ -117,7 +118,7 @@ export class LocalSubagentInvocation extends BaseToolInvocation<
isSubagentProgress: true,
agentName: this.definition.name,
recentActivity: [],
state: 'running',
state: SubagentState.RUNNING,
};
updateOutput(initialProgress);
}
@@ -137,7 +138,7 @@ export class LocalSubagentInvocation extends BaseToolInvocation<
if (
lastItem &&
lastItem.type === 'thought' &&
lastItem.status === 'running'
lastItem.status === SubagentState.RUNNING
) {
lastItem.content = sanitizeThoughtContent(text);
} else {
@@ -145,7 +146,7 @@ export class LocalSubagentInvocation extends BaseToolInvocation<
id: randomUUID(),
type: 'thought',
content: sanitizeThoughtContent(text),
status: 'running',
status: SubagentState.RUNNING,
});
}
updated = true;
@@ -174,7 +175,7 @@ export class LocalSubagentInvocation extends BaseToolInvocation<
displayName,
description,
args,
status: 'running',
status: SubagentState.RUNNING,
});
updated = true;
@@ -193,9 +194,11 @@ export class LocalSubagentInvocation extends BaseToolInvocation<
if (
recentActivity[i].type === 'tool_call' &&
recentActivity[i].content === name &&
recentActivity[i].status === 'running'
recentActivity[i].status === SubagentState.RUNNING
) {
recentActivity[i].status = isError ? 'error' : 'completed';
recentActivity[i].status = isError
? SubagentState.ERROR
: SubagentState.COMPLETED;
updated = true;
this.publishActivity(recentActivity[i]);
@@ -224,9 +227,9 @@ export class LocalSubagentInvocation extends BaseToolInvocation<
if (
recentActivity[i].type === 'tool_call' &&
recentActivity[i].content === toolName &&
recentActivity[i].status === 'running'
recentActivity[i].status === SubagentState.RUNNING
) {
recentActivity[i].status = 'cancelled';
recentActivity[i].status = SubagentState.CANCELLED;
updated = true;
break;
}
@@ -237,9 +240,9 @@ export class LocalSubagentInvocation extends BaseToolInvocation<
if (
recentActivity[i].type === 'tool_call' &&
recentActivity[i].content === toolName &&
recentActivity[i].status === 'running'
recentActivity[i].status === SubagentState.RUNNING
) {
recentActivity[i].status = 'error';
recentActivity[i].status = SubagentState.ERROR;
updated = true;
break;
}
@@ -253,7 +256,10 @@ export class LocalSubagentInvocation extends BaseToolInvocation<
isCancellation || isRejection
? sanitizedError
: `Error: ${sanitizedError}`,
status: isCancellation || isRejection ? 'cancelled' : 'error',
status:
isCancellation || isRejection
? SubagentState.CANCELLED
: SubagentState.ERROR,
});
updated = true;
break;
@@ -267,7 +273,7 @@ export class LocalSubagentInvocation extends BaseToolInvocation<
isSubagentProgress: true,
agentName: this.definition.name,
recentActivity: [...recentActivity], // Copy to avoid mutation issues
state: 'running',
state: SubagentState.RUNNING,
};
updateOutput(progress);
@@ -287,7 +293,7 @@ export class LocalSubagentInvocation extends BaseToolInvocation<
isSubagentProgress: true,
agentName: this.definition.name,
recentActivity: [...recentActivity],
state: 'cancelled',
state: SubagentState.CANCELLED,
};
if (updateOutput) {
@@ -303,7 +309,7 @@ export class LocalSubagentInvocation extends BaseToolInvocation<
isSubagentProgress: true,
agentName: this.definition.name,
recentActivity: [...recentActivity],
state: 'completed',
state: SubagentState.COMPLETED,
result: output.result,
terminateReason: output.terminate_reason,
};
@@ -334,8 +340,8 @@ ${output.result}`;
// Mark any running items as error/cancelled
for (const item of recentActivity) {
if (item.status === 'running') {
item.status = isAbort ? 'cancelled' : 'error';
if (item.status === SubagentState.RUNNING) {
item.status = isAbort ? SubagentState.CANCELLED : SubagentState.ERROR;
}
}
@@ -343,12 +349,12 @@ ${output.result}`;
// But only if it's NOT an abort, or if we want to show "Cancelled" as a thought
if (!isAbort) {
const lastActivity = recentActivity[recentActivity.length - 1];
if (!lastActivity || lastActivity.status !== 'error') {
if (!lastActivity || lastActivity.status !== SubagentState.ERROR) {
recentActivity.push({
id: randomUUID(),
type: 'thought',
content: `Error: ${errorMessage}`,
status: 'error',
status: SubagentState.ERROR,
});
// Maintain size limit
// No limit on UI events sent via bus
@@ -359,7 +365,7 @@ ${output.result}`;
isSubagentProgress: true,
agentName: this.definition.name,
recentActivity: [...recentActivity],
state: isAbort ? 'cancelled' : 'error',
state: isAbort ? SubagentState.CANCELLED : SubagentState.ERROR,
};
if (updateOutput) {
+10 -24
View File
@@ -250,11 +250,11 @@ describe('AgentRegistry', () => {
};
vi.mocked(tomlLoader.loadAgentsFromDirectory)
.mockResolvedValueOnce({ agents: [userAgent], errors: [] }) // User dir
.mockResolvedValueOnce({
agents: [projectAgent, uniqueProjectAgent],
errors: [],
}); // Project dir
}) // Project dir
.mockResolvedValueOnce({ agents: [userAgent], errors: [] }); // User dir
await registry.initialize();
@@ -1011,7 +1011,7 @@ describe('AgentRegistry', () => {
);
});
it('should overwrite an existing agent definition', async () => {
it('should NOT overwrite an existing agent definition', async () => {
await registry.testRegisterAgent(MOCK_AGENT_V1);
expect(registry.getDefinition('MockAgent')?.description).toBe(
'Mock Description V1',
@@ -1019,36 +1019,22 @@ describe('AgentRegistry', () => {
await registry.testRegisterAgent(MOCK_AGENT_V2);
expect(registry.getDefinition('MockAgent')?.description).toBe(
'Mock Description V2 (Updated)',
'Mock Description V1',
);
expect(registry.getAllDefinitions()).toHaveLength(1);
});
it('should log overwrites when in debug mode', async () => {
const debugConfig = makeMockedConfig({ debugMode: true });
const debugRegistry = new TestableAgentRegistry(debugConfig);
const debugLogSpy = vi
.spyOn(debugLogger, 'log')
.mockImplementation(() => {});
await debugRegistry.testRegisterAgent(MOCK_AGENT_V1);
await debugRegistry.testRegisterAgent(MOCK_AGENT_V2);
expect(debugLogSpy).toHaveBeenCalledWith(
`[AgentRegistry] Overriding agent 'MockAgent'`,
);
});
it('should not log overwrites when not in debug mode', async () => {
const debugLogSpy = vi
.spyOn(debugLogger, 'log')
it('should emit warning on duplicate agent definition', async () => {
const feedbackSpy = vi
.spyOn(coreEvents, 'emitFeedback')
.mockImplementation(() => {});
await registry.testRegisterAgent(MOCK_AGENT_V1);
await registry.testRegisterAgent(MOCK_AGENT_V2);
expect(debugLogSpy).not.toHaveBeenCalledWith(
`[AgentRegistry] Overriding agent 'MockAgent'`,
expect(feedbackSpy).toHaveBeenCalledWith(
'warning',
expect.stringContaining("Duplicate agent name 'MockAgent' detected"),
);
});
+36 -25
View File
@@ -169,31 +169,6 @@ export class AgentRegistry {
return;
}
// Load user-level agents: ~/.gemini/agents/
const userAgentsDir = Storage.getUserAgentsDir();
const userAgents = await loadAgentsFromDirectory(userAgentsDir);
for (const error of userAgents.errors) {
debugLogger.warn(
`[AgentRegistry] Error loading user agent: ${error.message}`,
);
const msg = `Agent loading error: ${error.message}`;
errors?.push(msg);
coreEvents.emitFeedback('error', msg);
}
await Promise.allSettled(
userAgents.agents.map(async (agent) => {
try {
this.ensureRemoteAgentHash(agent);
await this.registerAgent(agent, errors);
} catch (e) {
const msg = `Error registering user agent "${agent.name}": ${e instanceof Error ? e.message : String(e)}`;
debugLogger.warn(`[AgentRegistry] ${msg}`, e);
errors?.push(msg);
coreEvents.emitFeedback('error', msg);
}
}),
);
// Load project-level agents: .gemini/agents/ (relative to Project Root)
const folderTrustEnabled = this.config.getFolderTrust();
const isTrustedFolder = this.config.isTrustedFolder();
@@ -256,6 +231,31 @@ export class AgentRegistry {
);
}
// Load user-level agents: ~/.gemini/agents/
const userAgentsDir = Storage.getUserAgentsDir();
const userAgents = await loadAgentsFromDirectory(userAgentsDir);
for (const error of userAgents.errors) {
debugLogger.warn(
`[AgentRegistry] Error loading user agent: ${error.message}`,
);
const msg = `Agent loading error: ${error.message}`;
errors?.push(msg);
coreEvents.emitFeedback('error', msg);
}
await Promise.allSettled(
userAgents.agents.map(async (agent) => {
try {
this.ensureRemoteAgentHash(agent);
await this.registerAgent(agent, errors);
} catch (e) {
const msg = `Error registering user agent "${agent.name}": ${e instanceof Error ? e.message : String(e)}`;
debugLogger.warn(`[AgentRegistry] ${msg}`, e);
errors?.push(msg);
coreEvents.emitFeedback('error', msg);
}
}),
);
// Load agents from extensions
for (const extension of this.config.getExtensions()) {
if (extension.isActive && extension.agents) {
@@ -336,6 +336,17 @@ export class AgentRegistry {
definition: AgentDefinition<TOutput>,
errors?: string[],
): Promise<void> {
const existing = this.agents.get(definition.name);
if (existing && existing !== definition) {
coreEvents.emitFeedback(
'warning',
`Duplicate agent name '${definition.name}' detected. ` +
`The later definition will be ignored. ` +
`Rename one of the agents to avoid this conflict.`,
);
return;
}
if (definition.kind === 'local') {
this.registerLocalAgent(definition);
} else if (definition.kind === 'remote') {
@@ -20,7 +20,11 @@ import {
type A2AClientManager,
} from './a2a-client-manager.js';
import type { RemoteAgentDefinition, SubagentProgress } from './types.js';
import {
type RemoteAgentDefinition,
type SubagentProgress,
SubagentState,
} from './types.js';
import { createMockMessageBus } from '../test-utils/mock-message-bus.js';
import { A2AAuthProviderFactory } from './auth-provider/factory.js';
import type { A2AAuthProvider } from './auth-provider/types.js';
@@ -268,7 +272,9 @@ describe('RemoteAgentInvocation', () => {
abortSignal: new AbortController().signal,
});
expect(result.returnDisplay).toMatchObject({ state: 'error' });
expect(result.returnDisplay).toMatchObject({
state: SubagentState.ERROR,
});
expect((result.returnDisplay as SubagentProgress).result).toContain(
"Failed to create auth provider for agent 'test-agent'",
);
@@ -461,7 +467,7 @@ describe('RemoteAgentInvocation', () => {
expect(updateOutput).toHaveBeenCalledWith(
expect.objectContaining({
isSubagentProgress: true,
state: 'running',
state: SubagentState.RUNNING,
recentActivity: expect.arrayContaining([
expect.objectContaining({ content: 'Working...' }),
]),
@@ -470,7 +476,7 @@ describe('RemoteAgentInvocation', () => {
expect(updateOutput).toHaveBeenCalledWith(
expect.objectContaining({
isSubagentProgress: true,
state: 'completed',
state: SubagentState.COMPLETED,
result: 'HelloHello World',
}),
);
@@ -508,7 +514,9 @@ describe('RemoteAgentInvocation', () => {
abortSignal: controller.signal,
});
expect(result.returnDisplay).toMatchObject({ state: 'error' });
expect(result.returnDisplay).toMatchObject({
state: SubagentState.ERROR,
});
});
it('should handle errors gracefully', async () => {
@@ -533,7 +541,7 @@ describe('RemoteAgentInvocation', () => {
});
expect(result.returnDisplay).toMatchObject({
state: 'error',
state: SubagentState.ERROR,
result: expect.stringContaining('Network error'),
});
});
@@ -616,7 +624,7 @@ describe('RemoteAgentInvocation', () => {
expect(updateOutput).toHaveBeenCalledWith(
expect.objectContaining({
isSubagentProgress: true,
state: 'running',
state: SubagentState.RUNNING,
recentActivity: expect.arrayContaining([
expect.objectContaining({ content: 'Working...' }),
]),
@@ -625,7 +633,7 @@ describe('RemoteAgentInvocation', () => {
expect(updateOutput).toHaveBeenCalledWith(
expect.objectContaining({
isSubagentProgress: true,
state: 'completed',
state: SubagentState.COMPLETED,
result: 'Thinking...Final Answer',
}),
);
@@ -693,7 +701,7 @@ describe('RemoteAgentInvocation', () => {
expect(updateOutput).toHaveBeenCalledWith(
expect.objectContaining({
isSubagentProgress: true,
state: 'running',
state: SubagentState.RUNNING,
recentActivity: expect.arrayContaining([
expect.objectContaining({ content: 'Working...' }),
]),
@@ -702,7 +710,7 @@ describe('RemoteAgentInvocation', () => {
expect(updateOutput).toHaveBeenCalledWith(
expect.objectContaining({
isSubagentProgress: true,
state: 'completed',
state: SubagentState.COMPLETED,
result: 'Generating...\n\nArtifact (Result):\nPart 1 Part 2',
}),
);
@@ -760,7 +768,9 @@ describe('RemoteAgentInvocation', () => {
abortSignal: new AbortController().signal,
});
expect(result.returnDisplay).toMatchObject({ state: 'error' });
expect(result.returnDisplay).toMatchObject({
state: SubagentState.ERROR,
});
expect((result.returnDisplay as SubagentProgress).result).toContain(
a2aError.userMessage,
);
@@ -782,7 +792,9 @@ describe('RemoteAgentInvocation', () => {
abortSignal: new AbortController().signal,
});
expect(result.returnDisplay).toMatchObject({ state: 'error' });
expect(result.returnDisplay).toMatchObject({
state: SubagentState.ERROR,
});
expect((result.returnDisplay as SubagentProgress).result).toContain(
'Error calling remote agent: something unexpected',
);
@@ -813,7 +825,9 @@ describe('RemoteAgentInvocation', () => {
abortSignal: new AbortController().signal,
});
expect(result.returnDisplay).toMatchObject({ state: 'error' });
expect(result.returnDisplay).toMatchObject({
state: SubagentState.ERROR,
});
// Should contain both the partial output and the error message
expect(result.returnDisplay).toMatchObject({
result: expect.stringContaining('Partial response'),
@@ -17,6 +17,7 @@ import {
type RemoteAgentDefinition,
type AgentInputs,
type SubagentProgress,
SubagentState,
getAgentCardLoadOptions,
getRemoteAgentTargetUrl,
} from './types.js';
@@ -138,13 +139,13 @@ export class RemoteAgentInvocation extends BaseToolInvocation<
updateOutput({
isSubagentProgress: true,
agentName,
state: 'running',
state: SubagentState.RUNNING,
recentActivity: [
{
id: 'pending',
type: 'thought',
content: 'Working...',
status: 'running',
status: SubagentState.RUNNING,
},
],
});
@@ -193,7 +194,7 @@ export class RemoteAgentInvocation extends BaseToolInvocation<
updateOutput({
isSubagentProgress: true,
agentName,
state: 'running',
state: SubagentState.RUNNING,
recentActivity: reassembler.toActivityItems(),
result: reassembler.toString(),
});
@@ -225,7 +226,7 @@ export class RemoteAgentInvocation extends BaseToolInvocation<
const finalProgress: SubagentProgress = {
isSubagentProgress: true,
agentName,
state: 'completed',
state: SubagentState.COMPLETED,
result: finalOutput,
recentActivity: reassembler.toActivityItems(),
};
@@ -249,7 +250,7 @@ export class RemoteAgentInvocation extends BaseToolInvocation<
const errorProgress: SubagentProgress = {
isSubagentProgress: true,
agentName,
state: 'error',
state: SubagentState.ERROR,
result: fullDisplay,
recentActivity: reassembler.toActivityItems(),
};

Some files were not shown because too many files have changed in this diff Show More