Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9f81d3752c | |||
| 9f29ab56bc | |||
| 297d3a3067 | |||
| 749657cbf9 | |||
| 83f46557c0 | |||
| 8cda688fe2 | |||
| 5ee05c775e | |||
| 31d5947d37 | |||
| 8f03aa320e | |||
| 583839ba46 | |||
| 022e8baefc | |||
| 120dfc724d | |||
| c37b9113d7 | |||
| 2334e9b1c4 | |||
| f901a4e6b7 | |||
| c987b99394 | |||
| c4973d01da | |||
| 27a39b04b0 | |||
| ebe15553a9 | |||
| bc730b2c0f | |||
| 9fe8643552 | |||
| 07792f98cd | |||
| 7a9ed4c20a | |||
| 11a9edc808 | |||
| 24b98ade86 | |||
| 9f759f97a2 | |||
| 9ff7304391 | |||
| 84fc5cd533 | |||
| e1b3ce5b36 | |||
| 8e58df72c6 | |||
| c0d5ab1f1e | |||
| 1340c96071 | |||
| f8198a25d8 | |||
| 36a7fa089c | |||
| 4739495e39 | |||
| ecfaac2dc7 | |||
| 7cd228f5af | |||
| 8a3fde4c33 | |||
| 1a894c18ea | |||
| 54f1e8c6d7 | |||
| f51391a0f2 | |||
| 1238dcfe91 | |||
| 90e7155971 | |||
| 9d0860bd0f | |||
| 014bfeb89b | |||
| 5890f50496 | |||
| 6b9b778d82 | |||
| f86e0ee418 |
@@ -5,7 +5,10 @@
|
||||
"autoMemory": true,
|
||||
"memoryManager": true,
|
||||
"topicUpdateNarration": true,
|
||||
"voiceMode": true
|
||||
"voiceMode": true,
|
||||
"adk": {
|
||||
"agentSessionNoninteractiveEnabled": true
|
||||
}
|
||||
},
|
||||
"general": {
|
||||
"devtools": true
|
||||
|
||||
@@ -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 }}'
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
module.exports = async ({ github, context, core }) => {
|
||||
const rawLabels = process.env.LABELS_OUTPUT;
|
||||
core.info(`Raw labels JSON: ${rawLabels}`);
|
||||
let parsedLabels;
|
||||
try {
|
||||
// First, try to parse the raw output as JSON.
|
||||
parsedLabels = JSON.parse(rawLabels);
|
||||
} catch (jsonError) {
|
||||
// If that fails, check for a markdown code block.
|
||||
core.warning(
|
||||
`Direct JSON parsing failed: ${jsonError.message}. Trying to extract from a markdown block.`,
|
||||
);
|
||||
const jsonMatch = rawLabels.match(/```json\s*([\s\S]*?)\s*```/);
|
||||
if (jsonMatch && jsonMatch[1]) {
|
||||
try {
|
||||
parsedLabels = JSON.parse(jsonMatch[1].trim());
|
||||
} catch (markdownError) {
|
||||
core.setFailed(
|
||||
`Failed to parse JSON even after extracting from markdown block: ${markdownError.message}\nRaw output: ${rawLabels}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// If no markdown block, try to find a raw JSON array in the output.
|
||||
// The CLI may include debug/log lines (e.g. telemetry init, YOLO mode)
|
||||
// before the actual JSON response.
|
||||
const jsonArrayMatch = rawLabels.match(
|
||||
/\[\s*\{\s*"issue_number"[\s\S]*\}\s*\]/,
|
||||
);
|
||||
if (jsonArrayMatch) {
|
||||
try {
|
||||
parsedLabels = JSON.parse(jsonArrayMatch[0]);
|
||||
} catch (extractError) {
|
||||
// It's possible the regex matched from a `[STARTUP]` log all the way to the end
|
||||
// of the JSON array. We need to be more aggressive and find the FIRST `[ { "issue_number"`
|
||||
core.warning(
|
||||
`Strict array match failed: ${extractError.message}. Attempting to clean leading noisy brackets.`,
|
||||
);
|
||||
const fallbackMatch = rawLabels.match(
|
||||
/(\[\s*\{\s*"issue_number"[\s\S]*)/,
|
||||
);
|
||||
if (fallbackMatch) {
|
||||
try {
|
||||
// We might have grabbed trailing noise too, so we find the last closing bracket
|
||||
const cleaned = fallbackMatch[0].substring(
|
||||
0,
|
||||
fallbackMatch[0].lastIndexOf(']') + 1,
|
||||
);
|
||||
parsedLabels = JSON.parse(cleaned);
|
||||
} catch (fallbackError) {
|
||||
core.setFailed(
|
||||
`Found JSON-like content but failed to parse: ${fallbackError.message}\nRaw output: ${rawLabels}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
core.setFailed(
|
||||
`Found JSON-like content but failed to parse: ${extractError.message}\nRaw output: ${rawLabels}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
core.setFailed(
|
||||
`Output is not valid JSON and does not contain extractable JSON.\nRaw output: ${rawLabels}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
core.info(`Parsed labels JSON: ${JSON.stringify(parsedLabels)}`);
|
||||
|
||||
for (const entry of parsedLabels) {
|
||||
const issueNumber = entry.issue_number;
|
||||
if (!issueNumber) {
|
||||
core.info(
|
||||
`Skipping entry with no issue number: ${JSON.stringify(entry)}`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
let labelsToAdd = entry.labels_to_add || [];
|
||||
let labelsToRemove = entry.labels_to_remove || [];
|
||||
|
||||
labelsToRemove.push('status/need-triage');
|
||||
|
||||
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,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber,
|
||||
labels: labelsToAdd,
|
||||
});
|
||||
|
||||
const explanation = entry.explanation ? ` - ${entry.explanation}` : '';
|
||||
core.info(
|
||||
`Successfully added labels for #${issueNumber}: ${labelsToAdd.join(', ')}${explanation}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (labelsToRemove.length > 0) {
|
||||
for (const label of labelsToRemove) {
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber,
|
||||
name: label,
|
||||
});
|
||||
} catch (e) {
|
||||
if (e.status !== 404) {
|
||||
core.warning(
|
||||
`Failed to remove label ${label} from #${issueNumber}: ${e.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
core.info(
|
||||
`Successfully removed labels for #${issueNumber}: ${labelsToRemove.join(', ')}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (entry.explanation || entry.effort_analysis) {
|
||||
let commentBody = '';
|
||||
if (entry.explanation) {
|
||||
commentBody += entry.explanation;
|
||||
}
|
||||
if (entry.effort_analysis) {
|
||||
if (commentBody) commentBody += '\n\n';
|
||||
commentBody += `**Effort Analysis:**\n${entry.effort_analysis}`;
|
||||
}
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber,
|
||||
body: commentBody,
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
(!entry.labels_to_add || entry.labels_to_add.length === 0) &&
|
||||
(!entry.labels_to_remove || entry.labels_to_remove.length === 0)
|
||||
) {
|
||||
core.info(
|
||||
`No labels to add or remove for #${issueNumber}, leaving as is`,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
const fs = require('node:fs');
|
||||
|
||||
module.exports = async ({ github, context, core }) => {
|
||||
let issuesToCleanup = [];
|
||||
try {
|
||||
const fileContent = fs.readFileSync('issues_to_cleanup.json', 'utf8');
|
||||
issuesToCleanup = JSON.parse(fileContent);
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
core.info('No issues found to clean up.');
|
||||
return;
|
||||
}
|
||||
core.setFailed(`Failed to read issues_to_cleanup.json: ${error.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const issue of issuesToCleanup) {
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
name: 'status/need-triage',
|
||||
});
|
||||
core.info(
|
||||
`Successfully removed status/need-triage from #${issue.number}`,
|
||||
);
|
||||
} catch (error) {
|
||||
if (error.status === 404) {
|
||||
core.info(
|
||||
`Label status/need-triage not found on #${issue.number}, skipping.`,
|
||||
);
|
||||
} else {
|
||||
core.warning(
|
||||
`Failed to remove label from #${issue.number}: ${error.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
core.info(
|
||||
`Cleaned up status/need-triage from ${issuesToCleanup.length} issues.`,
|
||||
);
|
||||
};
|
||||
@@ -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`,
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
const fs = require('node:fs');
|
||||
|
||||
module.exports = async ({ github, context, core }) => {
|
||||
const query = `
|
||||
query($owner: String!, $repo: String!) {
|
||||
repository(owner: $owner, name: $repo) {
|
||||
issues(first: 50, states: OPEN, orderBy: {field: UPDATED_AT, direction: DESC}) {
|
||||
nodes {
|
||||
id
|
||||
number
|
||||
title
|
||||
body
|
||||
issueType {
|
||||
name
|
||||
}
|
||||
labels(first: 20) {
|
||||
nodes {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
try {
|
||||
const result = await github.graphql(query, {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
});
|
||||
|
||||
const issues = result.repository.issues.nodes;
|
||||
const issuesNeedingAnalysis = [];
|
||||
let syncedCount = 0;
|
||||
|
||||
for (const issue of issues) {
|
||||
if (issue.issueType === null) {
|
||||
const labelNames = issue.labels.nodes.map((l) => l.name);
|
||||
const hasBug = labelNames.includes('kind/bug');
|
||||
const hasFeature =
|
||||
labelNames.includes('kind/feature') ||
|
||||
labelNames.includes('kind/enhancement');
|
||||
|
||||
let issueTypeId = null;
|
||||
if (hasBug) {
|
||||
issueTypeId = 'IT_kwDOCaSVvs4BR7vP'; // Bug
|
||||
} else if (hasFeature) {
|
||||
issueTypeId = 'IT_kwDOCaSVvs4BR7vQ'; // Feature
|
||||
}
|
||||
|
||||
if (issueTypeId) {
|
||||
await github.graphql(
|
||||
`
|
||||
mutation($issueId: ID!, $issueTypeId: ID!) {
|
||||
updateIssue(input: {id: $issueId, issueTypeId: $issueTypeId}) {
|
||||
issue {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{
|
||||
issueId: issue.id,
|
||||
issueTypeId: issueTypeId,
|
||||
},
|
||||
);
|
||||
core.info(`Successfully synced Issue Type for #${issue.number}`);
|
||||
syncedCount++;
|
||||
} else {
|
||||
// Needs analysis to determine kind/type
|
||||
issuesNeedingAnalysis.push({
|
||||
number: issue.number,
|
||||
title: issue.title,
|
||||
body: issue.body,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write issues needing analysis to a file so the AI can process them
|
||||
fs.writeFileSync(
|
||||
'no_type_issues.json',
|
||||
JSON.stringify(issuesNeedingAnalysis),
|
||||
);
|
||||
core.info(`Synced ${syncedCount} issues from labels.`);
|
||||
core.info(
|
||||
`Found ${issuesNeedingAnalysis.length} issues missing both type and kind label to be analyzed.`,
|
||||
);
|
||||
} catch (error) {
|
||||
core.setFailed(`Failed to sync issue types: ${error.message}`);
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -19,6 +19,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: 'main'
|
||||
persist-credentials: false
|
||||
|
||||
- name: 'Set up Node.js'
|
||||
uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -23,13 +23,15 @@ permissions:
|
||||
|
||||
jobs:
|
||||
triage-issues:
|
||||
timeout-minutes: 10
|
||||
timeout-minutes: 60
|
||||
if: |-
|
||||
${{ github.repository == 'google-gemini/gemini-cli' }}
|
||||
runs-on: 'ubuntu-latest'
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: 'Generate GitHub App Token'
|
||||
id: 'generate_token'
|
||||
@@ -51,6 +53,26 @@ jobs:
|
||||
echo "has_issues=true" >> "${GITHUB_OUTPUT}"
|
||||
echo "✅ Found issue #${{ github.event.issue.number }} from event to triage! 🎯"
|
||||
|
||||
- name: 'Sync Issue Types'
|
||||
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 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' }}
|
||||
@@ -63,26 +85,55 @@ jobs:
|
||||
|
||||
echo '🔍 Finding issues missing area labels...'
|
||||
gh issue list --repo "${GITHUB_REPOSITORY}" \
|
||||
--search 'is:open is:issue -label:status/bot-triaged -label:area/core -label:area/agent -label:area/enterprise -label:area/non-interactive -label:area/security -label:area/platform -label:area/extensions -label:area/documentation -label:area/unknown' --limit 100 --json number,title,body > no_area_issues.json
|
||||
--search 'is:open is:issue -label:status/bot-triaged -label:area/core -label:area/agent -label:area/enterprise -label:area/non-interactive -label:area/security -label:area/platform -label:area/extensions -label:area/documentation -label:area/unknown' --limit 50 --json number,title,body > no_area_issues.json
|
||||
|
||||
echo '🔍 Finding issues missing kind labels...'
|
||||
gh issue list --repo "${GITHUB_REPOSITORY}" \
|
||||
--search 'is:open is:issue -label:status/bot-triaged -label:kind/bug -label:kind/enhancement -label:kind/customer-issue -label:kind/question' --limit 100 --json number,title,body > no_kind_issues.json
|
||||
--search 'is:open is:issue -label:status/bot-triaged -label:kind/bug -label:kind/enhancement -label:kind/customer-issue -label:kind/question' --limit 50 --json number,title,body > no_kind_issues.json
|
||||
|
||||
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 100 --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 '🔄 Merging and deduplicating issues...'
|
||||
jq -c -s 'add | unique_by(.number)' no_area_issues.json no_kind_issues.json no_priority_issues.json > issues_to_triage.json
|
||||
echo '📏 Finding issues missing effort labels...'
|
||||
gh issue list --repo "${GITHUB_REPOSITORY}" \
|
||||
--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
|
||||
|
||||
ISSUE_COUNT="$(jq 'length' issues_to_triage.json)"
|
||||
if [ "$ISSUE_COUNT" -gt 0 ]; then
|
||||
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
|
||||
|
||||
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: |-
|
||||
steps.get_issue_from_event.outputs.has_issues == 'true' || steps.find_issues.outputs.has_issues == 'true'
|
||||
run: |
|
||||
cat << 'EOF' > gemini_exp.json
|
||||
{
|
||||
"flags": [
|
||||
{
|
||||
"flagId": 45750526,
|
||||
"boolValue": false
|
||||
}
|
||||
],
|
||||
"experimentIds": []
|
||||
}
|
||||
EOF
|
||||
|
||||
- name: 'Get Repository Labels'
|
||||
id: 'get_labels'
|
||||
@@ -99,16 +150,18 @@ 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 }}'
|
||||
@@ -139,13 +192,15 @@ jobs:
|
||||
## 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.
|
||||
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/*.
|
||||
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 already has a priority/ label, do not change it.
|
||||
- If the issue already has an area/ 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.
|
||||
@@ -176,11 +231,121 @@ jobs:
|
||||
- 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 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 only one priority/ label.
|
||||
- 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 }}'
|
||||
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)",
|
||||
"grep_search",
|
||||
"glob",
|
||||
"read_file"
|
||||
],
|
||||
"telemetry": {
|
||||
"enabled": true,
|
||||
"target": "gcp"
|
||||
}
|
||||
}
|
||||
prompt: |-
|
||||
## Role
|
||||
|
||||
You are an expert software architect. Analyze the provided GitHub issues and assign the correct `effort/*` label based on the codebase complexity.
|
||||
|
||||
## Steps
|
||||
|
||||
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": ["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."
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Guidelines
|
||||
|
||||
- Output only valid JSON format
|
||||
- Do not include any explanation or additional text, just the JSON
|
||||
- Triage only the current issue.
|
||||
|
||||
Categorization Guidelines (Effort):
|
||||
effort/small (1 day or less):
|
||||
- Trivial Logic & Config: Schema updates (Zod), feature flag toggles, adding missing fields to package.json or settings.json.
|
||||
- UI/Aesthetic Adjustments: Fixing minor layout bugs in Ink components (e.g., adding flexShrink, correcting padding in a single Box), text color changes.
|
||||
- Documentation & Strings: Typos, log message updates, CLI argument descriptions.
|
||||
- Localized Bug Fixes: Single-file logic errors, straightforward promise rejections (e.g., wrapping a known failure in a try/catch), simple regex or string parsing fixes.
|
||||
effort/medium (2-3 days):
|
||||
- React/Ink State Management: Debugging useState/useEffect/useReducer bugs, component lifecycle issues (memory leaks in the UI), terminal redraw flickering, or state synchronization between the CLI's internal input buffer and the interactive React components.
|
||||
- Asynchronous Flow & Integration: Resolving complex Promise chains, ERR_STREAM_PREMATURE_CLOSE, debugging IDE companion extensions (VS Code, Android Studio) or resolving hanging HTTP requests/IPC between the CLI and external plugins, timeouts in non-interactive/ACP modes.
|
||||
- Tooling & Output Parsers: Modifying how tools parse streaming stdout/stderr buffers, adding new built-in tools that don't require native bindings.
|
||||
- Cross-Component Refactors: Changes that span across packages/cli and packages/core to pass new data models or telemetry state.
|
||||
effort/large (3+ days):
|
||||
- Platform-Specific Complexities (PTY/Signals): Any issue involving node-pty, child_process.spawn, OS-level shell behavior (Windows vs Linux vs macOS), pseudo-terminal exhaustion (ENXIO), raw mode terminal desyncs, or POSIX signal forwarding (SIGINT/SIGTERM).
|
||||
- Core Architecture & Protocols: Refactoring the Scheduler, Agent-to-Agent (A2A) protocol implementation, low-level MCP (Model Context Protocol) transport mechanisms.
|
||||
- Performance & Memory: Diagnosing massive disk/memory leaks, severe boot time regressions, high-throughput streaming optimizations (e.g., voice streaming pipelines).
|
||||
Note: Any bug that is described as intermittent, flickering, difficult to reproduce, platform-specific, or requiring cross-environment setups (e.g., involving the VS Code IDE companion, GCA plugin, or Android Studio) MUST NOT be rated as effort/small because of the increased overhead of testing and reproducing.
|
||||
|
||||
Categorization Guidelines (Priority):
|
||||
P0 - Urgent Blocking Issues:
|
||||
- DO NOT APPLY THIS LABEL AUTOMATICALLY. Use status/manual-triage instead.
|
||||
@@ -218,85 +383,64 @@ 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 rawLabels = process.env.LABELS_OUTPUT;
|
||||
core.info(`Raw labels JSON: ${rawLabels}`);
|
||||
let parsedLabels;
|
||||
try {
|
||||
// First, try to parse the raw output as JSON.
|
||||
parsedLabels = JSON.parse(rawLabels);
|
||||
} catch (jsonError) {
|
||||
// If that fails, check for a markdown code block.
|
||||
core.warning(`Direct JSON parsing failed: ${jsonError.message}. Trying to extract from a markdown block.`);
|
||||
const jsonMatch = rawLabels.match(/```json\s*([\s\S]*?)\s*```/);
|
||||
if (jsonMatch && jsonMatch[1]) {
|
||||
try {
|
||||
parsedLabels = JSON.parse(jsonMatch[1].trim());
|
||||
} catch (markdownError) {
|
||||
core.setFailed(`Failed to parse JSON even after extracting from markdown block: ${markdownError.message}\nRaw output: ${rawLabels}`);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// If no markdown block, try to find a raw JSON array in the output.
|
||||
// The CLI may include debug/log lines (e.g. telemetry init, YOLO mode)
|
||||
// before the actual JSON response.
|
||||
const jsonArrayMatch = rawLabels.match(/(\[[\s\S]*\])/);
|
||||
if (jsonArrayMatch) {
|
||||
try {
|
||||
parsedLabels = JSON.parse(jsonArrayMatch[0]);
|
||||
} catch (extractError) {
|
||||
core.setFailed(`Found JSON-like content but failed to parse: ${extractError.message}\nRaw output: ${rawLabels}`);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
core.setFailed(`Output is not valid JSON and does not contain extractable JSON.\nRaw output: ${rawLabels}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
core.info(`Parsed labels JSON: ${JSON.stringify(parsedLabels)}`);
|
||||
const applyLabels = require('./.github/scripts/apply-issue-labels.cjs');
|
||||
await applyLabels({ github, context, core });
|
||||
|
||||
for (const entry of parsedLabels) {
|
||||
const issueNumber = entry.issue_number;
|
||||
if (!issueNumber) {
|
||||
core.info(`Skipping entry with no issue number: ${JSON.stringify(entry)}`);
|
||||
continue;
|
||||
}
|
||||
- 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 }}'
|
||||
script: |-
|
||||
const applyLabels = require('./.github/scripts/apply-issue-labels.cjs');
|
||||
await applyLabels({ github, context, core });
|
||||
|
||||
const labelsToAdd = entry.labels_to_add || [];
|
||||
labelsToAdd.push('status/bot-triaged');
|
||||
- name: 'Sync Issue Types (Post-Analysis)'
|
||||
if: |-
|
||||
always() && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
|
||||
uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea'
|
||||
with:
|
||||
github-token: '${{ steps.generate_token.outputs.token }}'
|
||||
script: |-
|
||||
const syncIssueTypes = require('./.github/scripts/sync-issue-types.cjs');
|
||||
await syncIssueTypes({ github, context, core });
|
||||
|
||||
if (labelsToAdd.length > 0) {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber,
|
||||
labels: labelsToAdd
|
||||
});
|
||||
const explanation = entry.explanation ? ` - ${entry.explanation}` : '';
|
||||
core.info(`Successfully added labels for #${issueNumber}: ${labelsToAdd.join(', ')}${explanation}`);
|
||||
}
|
||||
- name: 'Find Triaged Issues to Clean Up'
|
||||
if: |-
|
||||
always() && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
|
||||
env:
|
||||
GITHUB_TOKEN: '${{ steps.generate_token.outputs.token }}'
|
||||
GITHUB_REPOSITORY: '${{ github.repository }}'
|
||||
run: |-
|
||||
set -euo pipefail
|
||||
echo '🧹 Finding issues that have both bot-triaged and need-triage labels...'
|
||||
gh issue list --repo "${GITHUB_REPOSITORY}" \
|
||||
--search 'is:open is:issue label:status/bot-triaged label:status/need-triage' --limit 50 --json number > issues_to_cleanup.json
|
||||
|
||||
if (entry.explanation) {
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber,
|
||||
body: entry.explanation,
|
||||
});
|
||||
}
|
||||
|
||||
if ((!entry.labels_to_add || entry.labels_to_add.length === 0) && (!entry.labels_to_remove || entry.labels_to_remove.length === 0)) {
|
||||
core.info(`No labels to add or remove for #${issueNumber}, leaving as is`);
|
||||
}
|
||||
}
|
||||
- name: 'Clean Up Triage Labels'
|
||||
if: |-
|
||||
always() && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
|
||||
uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea'
|
||||
with:
|
||||
github-token: '${{ steps.generate_token.outputs.token }}'
|
||||
script: |-
|
||||
const cleanupLabels = require('./.github/scripts/cleanup-triage-labels.cjs');
|
||||
await cleanupLabels({ github, context, core });
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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' }}"
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'"
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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"]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -700,6 +700,19 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
"extends": "gemini-3-flash-base",
|
||||
"modelConfig": {}
|
||||
},
|
||||
"context-snapshotter": {
|
||||
"extends": "gemini-3-flash-base",
|
||||
"modelConfig": {
|
||||
"generateContentConfig": {
|
||||
"thinkingConfig": {
|
||||
"thinkingLevel": "HIGH"
|
||||
},
|
||||
"temperature": 1,
|
||||
"topP": 0.95,
|
||||
"topK": 64
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat-compression-3-pro": {
|
||||
"modelConfig": {
|
||||
"model": "gemini-3-pro-preview"
|
||||
@@ -869,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
|
||||
@@ -905,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
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1007,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
|
||||
@@ -1079,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": [
|
||||
@@ -1114,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"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1132,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"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -1149,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"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { LlmRole, type BaseLlmClient } from '@google/gemini-cli-core';
|
||||
|
||||
export interface JudgeOptions {
|
||||
/**
|
||||
* The number of parallel generations to run for majority voting.
|
||||
* Defaults to 1. Use 3 or 5 for self-consistency.
|
||||
*/
|
||||
selfConsistencyRuns?: number;
|
||||
/**
|
||||
* The model to use for judging. Defaults to gemini-3-flash-base.
|
||||
*/
|
||||
model?: string;
|
||||
}
|
||||
|
||||
export interface JudgeResult {
|
||||
verdict: boolean;
|
||||
reasoning: string[];
|
||||
votes: { yes: number; no: number; other: number };
|
||||
}
|
||||
|
||||
/**
|
||||
* A reusable LLM-as-a-judge utility for behavioral evaluations.
|
||||
*/
|
||||
export class LLMJudge {
|
||||
constructor(private readonly llmClient: BaseLlmClient) {}
|
||||
|
||||
/**
|
||||
* Asks the LLM a Yes/No question and returns a boolean verdict.
|
||||
* If selfConsistencyRuns > 1, it runs in parallel and returns the majority vote.
|
||||
*/
|
||||
async judgeYesNo(
|
||||
question: string,
|
||||
options: JudgeOptions = {},
|
||||
): Promise<JudgeResult> {
|
||||
const runs = options.selfConsistencyRuns ?? 1;
|
||||
const model = options.model ?? 'gemini-3-flash-base';
|
||||
|
||||
const systemPrompt = `You are a strict, impartial expert judge. Read the provided evidence and question carefully. You MUST answer the question with ONLY "YES" or "NO". Do not provide any conversational filler or explanation before your answer.`;
|
||||
|
||||
const generateCall = async (): Promise<string> => {
|
||||
try {
|
||||
const response = await this.llmClient.generateContent({
|
||||
modelConfigKey: { model },
|
||||
contents: [{ role: 'user', parts: [{ text: question }] }],
|
||||
systemInstruction: {
|
||||
role: 'system',
|
||||
parts: [{ text: systemPrompt }],
|
||||
},
|
||||
promptId: 'llm-judge-eval',
|
||||
role: LlmRole.UTILITY_TOOL,
|
||||
abortSignal: new AbortController().signal,
|
||||
});
|
||||
|
||||
const text =
|
||||
response.candidates?.[0]?.content?.parts?.[0]?.text
|
||||
?.trim()
|
||||
?.toUpperCase() || 'ERROR';
|
||||
return text;
|
||||
} catch (e: any) {
|
||||
return `ERROR: ${e.message}`;
|
||||
}
|
||||
};
|
||||
|
||||
const promises = Array.from({ length: runs }).map(() => generateCall());
|
||||
const rawResults = await Promise.all(promises);
|
||||
|
||||
let yes = 0;
|
||||
let no = 0;
|
||||
let other = 0;
|
||||
|
||||
for (const res of rawResults) {
|
||||
// Remove any punctuation the model might have appended
|
||||
const cleanRes = res.replace(/[^A-Z]/g, '');
|
||||
if (cleanRes.startsWith('YES')) yes++;
|
||||
else if (cleanRes.startsWith('NO')) no++;
|
||||
else other++;
|
||||
}
|
||||
|
||||
// Pass if YES > NO and YES > OTHER (plurality)
|
||||
const pass = yes > no && yes > other;
|
||||
|
||||
return {
|
||||
verdict: pass,
|
||||
reasoning: rawResults,
|
||||
votes: { yes, no, other },
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, expect } from 'vitest';
|
||||
import {
|
||||
componentEvalTest,
|
||||
type ComponentEvalCase,
|
||||
} from './component-test-helper.js';
|
||||
import { type EvalPolicy } from './test-helper.js';
|
||||
import { SnapshotGenerator } from '@google/gemini-cli-core';
|
||||
import { NodeType, type ConcreteNode } from '@google/gemini-cli-core';
|
||||
import { LLMJudge } from './llm-judge.js';
|
||||
|
||||
function snapshotEvalTest(policy: EvalPolicy, evalCase: ComponentEvalCase) {
|
||||
return componentEvalTest(policy, evalCase);
|
||||
}
|
||||
|
||||
describe('snapshot_fidelity', () => {
|
||||
snapshotEvalTest('ALWAYS_PASSES', {
|
||||
suiteName: 'default',
|
||||
suiteType: 'behavioral',
|
||||
name: 'SnapshotGenerator strictly retains specific empirical facts',
|
||||
assert: async (config) => {
|
||||
// 1. Construct a highly specific mock transcript containing 3 empirical facts we can test for:
|
||||
// Fact A: File path -> src/compiler/server.ts
|
||||
// Fact B: Error code -> COMPILE_ERR_404
|
||||
// Fact C: Active Directive -> "do not fix it just yet"
|
||||
const mockNodes: ConcreteNode[] = [
|
||||
{
|
||||
id: '1',
|
||||
turnId: '1',
|
||||
type: NodeType.USER_PROMPT,
|
||||
timestamp: Date.now(),
|
||||
role: 'user',
|
||||
payload: {
|
||||
text: 'I am trying to debug a weird timeout issue when compiling the TS server.',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
turnId: '2',
|
||||
type: NodeType.TOOL_EXECUTION,
|
||||
timestamp: Date.now() + 100,
|
||||
role: 'model',
|
||||
payload: {
|
||||
functionCall: {
|
||||
name: 'run_shell_command',
|
||||
args: { cmd: 'grep -rn "timeout" src/' },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
turnId: '2',
|
||||
type: NodeType.TOOL_EXECUTION,
|
||||
timestamp: Date.now() + 200,
|
||||
role: 'user',
|
||||
payload: {
|
||||
functionResponse: {
|
||||
name: 'run_shell_command',
|
||||
response: {
|
||||
output:
|
||||
'src/compiler/server.ts:442: setTimeout(() => reject(new Error("COMPILE_ERR_404")), 5000);',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
turnId: '3',
|
||||
type: NodeType.AGENT_YIELD,
|
||||
timestamp: Date.now() + 300,
|
||||
role: 'model',
|
||||
payload: {
|
||||
text: 'I found the exact line. It looks like the compiler throws COMPILE_ERR_404 if it hits 5 seconds.',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
turnId: '4',
|
||||
type: NodeType.USER_PROMPT,
|
||||
timestamp: Date.now() + 400,
|
||||
role: 'user',
|
||||
payload: {
|
||||
text: 'Okay, do not fix it just yet. I want you to remember this error code (COMPILE_ERR_404) and file path. First, list all the files in the directory.',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// 2. Extract the LLM Client from the component container
|
||||
const llmClient = config.getBaseLlmClient();
|
||||
|
||||
const generator = new SnapshotGenerator({
|
||||
llmClient,
|
||||
promptId: 'eval-snapshot-test',
|
||||
tokenCalculator: {
|
||||
estimateTokensForString(str: string): number {
|
||||
return str.length * 4;
|
||||
},
|
||||
},
|
||||
} as any);
|
||||
|
||||
// 3. Generate the snapshot using the CURRENT system prompt
|
||||
const snapshotText = await generator.synthesizeSnapshot(mockNodes);
|
||||
|
||||
// 4. Use LLM-as-a-Judge with Self-Consistency to evaluate factual fidelity
|
||||
const judge = new LLMJudge(llmClient);
|
||||
|
||||
const judgePrompt = `
|
||||
EVIDENCE (CONTEXT SNAPSHOT):
|
||||
"""
|
||||
${snapshotText}
|
||||
"""
|
||||
|
||||
QUESTION:
|
||||
Does the EVIDENCE explicitly contain all three of the following facts:
|
||||
1. The specific file path "src/compiler/server.ts"
|
||||
2. The specific error code "COMPILE_ERR_404"
|
||||
3. The user's active constraint/directive to "do not fix it just yet" (or equivalent warning that implementation is paused)
|
||||
|
||||
Answer ONLY with "YES" if all three are unambiguously present.
|
||||
Answer "NO" if any of the three are missing, abstracted away, or generalized (e.g., if it says "found an error" instead of "COMPILE_ERR_404").`;
|
||||
|
||||
// Use a self-consistency of 3 runs to get a robust majority vote
|
||||
const result = await judge.judgeYesNo(judgePrompt, {
|
||||
selfConsistencyRuns: 3,
|
||||
});
|
||||
|
||||
// 5. Assert the verdict
|
||||
const formattedVotes = JSON.stringify(result.votes);
|
||||
const formattedReasoning = JSON.stringify(result.reasoning);
|
||||
|
||||
expect(
|
||||
result.verdict,
|
||||
`Snapshot failed to retain empirical facts.
|
||||
Votes: ${formattedVotes}
|
||||
Reasoning: ${formattedReasoning}
|
||||
|
||||
Generated Snapshot:
|
||||
${snapshotText}`,
|
||||
).toBe(true);
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -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",
|
||||
|
||||
@@ -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,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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,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() {
|
||||
|
||||
@@ -1043,6 +1043,28 @@ describe('loadCliConfig', () => {
|
||||
|
||||
expect(config.isInteractive()).toBe(false);
|
||||
});
|
||||
|
||||
describe('isAcpMode', () => {
|
||||
it('should force skipNextSpeakerCheck to true when in ACP mode', async () => {
|
||||
process.argv = ['node', 'script.js', '--acp'];
|
||||
const argv = await parseArguments(createTestMergedSettings());
|
||||
const settings = createTestMergedSettings({
|
||||
model: { skipNextSpeakerCheck: false },
|
||||
});
|
||||
const config = await loadCliConfig(settings, 'test-session', argv);
|
||||
expect(config.getSkipNextSpeakerCheck()).toBe(true);
|
||||
});
|
||||
|
||||
it('should respect settings.model.skipNextSpeakerCheck when not in ACP mode', async () => {
|
||||
process.argv = ['node', 'script.js'];
|
||||
const argv = await parseArguments(createTestMergedSettings());
|
||||
const settings = createTestMergedSettings({
|
||||
model: { skipNextSpeakerCheck: false },
|
||||
});
|
||||
const config = await loadCliConfig(settings, 'test-session', argv);
|
||||
expect(config.getSkipNextSpeakerCheck()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Hierarchical Memory Loading (config.ts) - Placeholder Suite', () => {
|
||||
@@ -2036,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 () => {
|
||||
@@ -2080,7 +2102,7 @@ describe('loadCliConfig model selection', () => {
|
||||
argv,
|
||||
);
|
||||
|
||||
expect(config.getModel()).toBe('auto-gemini-3');
|
||||
expect(config.getModel()).toBe('auto');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1105,7 +1104,11 @@ export async function loadCliConfig(
|
||||
shellToolInactivityTimeout: settings.tools?.shell?.inactivityTimeout,
|
||||
enableShellOutputEfficiency:
|
||||
settings.tools?.shell?.enableShellOutputEfficiency ?? true,
|
||||
skipNextSpeakerCheck: settings.model?.skipNextSpeakerCheck,
|
||||
// In ACP mode, always skip the next-speaker check. This check triggers
|
||||
// recursive continuation turns inside GeminiClient.processTurn() that
|
||||
// conflict with ACP's explicit turn management via session/prompt,
|
||||
// causing infinite agent_thought_chunk loops.
|
||||
skipNextSpeakerCheck: isAcpMode || settings.model?.skipNextSpeakerCheck,
|
||||
truncateToolOutputThreshold: settings.tools?.truncateToolOutputThreshold,
|
||||
eventEmitter: coreEvents,
|
||||
useWriteTodos: argv.useWriteTodos ?? settings.useWriteTodos,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1300,7 +1302,8 @@ export async function inferInstallMetadata(
|
||||
source.startsWith('git@') ||
|
||||
source.startsWith('sso://') ||
|
||||
source.startsWith('github:') ||
|
||||
source.startsWith('gitlab:')
|
||||
source.startsWith('gitlab:') ||
|
||||
source.startsWith('ssh://')
|
||||
) {
|
||||
return {
|
||||
source,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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');
|
||||
};
|
||||
|
||||
@@ -1267,6 +1267,42 @@ describe('AppContainer State Management', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('SessionStart Hook Rendering', () => {
|
||||
it('does not render systemMessage directly (avoids duplicate with HookSystemMessage event)', async () => {
|
||||
const mockAddItem = vi.fn();
|
||||
mockedUseHistory.mockReturnValue({
|
||||
history: [],
|
||||
addItem: mockAddItem,
|
||||
updateItem: vi.fn(),
|
||||
clearItems: vi.fn(),
|
||||
loadHistory: vi.fn(),
|
||||
});
|
||||
|
||||
const fireSessionStartEvent = vi.fn().mockResolvedValue({
|
||||
systemMessage: 'Hello from SessionStart hook',
|
||||
getAdditionalContext: vi.fn(() => undefined),
|
||||
});
|
||||
vi.spyOn(mockConfig, 'getHookSystem').mockReturnValue({
|
||||
fireSessionEndEvent: vi.fn().mockResolvedValue(undefined),
|
||||
fireSessionStartEvent,
|
||||
} as unknown as ReturnType<Config['getHookSystem']>);
|
||||
|
||||
const { unmount } = await act(async () => renderAppContainer());
|
||||
await waitFor(() => expect(fireSessionStartEvent).toHaveBeenCalled());
|
||||
|
||||
// The direct-render path (the bug) would call addItem with the
|
||||
// systemMessage text and no `source` field. The HookSystemMessage
|
||||
// event-listener path (the correct one) always sets `source`.
|
||||
const directRenderCall = mockAddItem.mock.calls.find(
|
||||
([item]) =>
|
||||
item?.text === 'Hello from SessionStart hook' && !item?.source,
|
||||
);
|
||||
expect(directRenderCall).toBeUndefined();
|
||||
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Token Counting from Session Stats', () => {
|
||||
it('tracks token counts from session messages', async () => {
|
||||
// Session stats are provided through the SessionStatsProvider context
|
||||
@@ -2091,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,
|
||||
|
||||
@@ -497,16 +497,6 @@ export const AppContainer = (props: AppContainerProps) => {
|
||||
?.fireSessionStartEvent(sessionStartSource);
|
||||
|
||||
if (result) {
|
||||
if (result.systemMessage) {
|
||||
historyManager.addItem(
|
||||
{
|
||||
type: MessageType.INFO,
|
||||
text: result.systemMessage,
|
||||
},
|
||||
Date.now(),
|
||||
);
|
||||
}
|
||||
|
||||
const additionalContext = result.getAdditionalContext();
|
||||
const geminiClient = config.getGeminiClient();
|
||||
if (additionalContext && geminiClient) {
|
||||
@@ -549,12 +539,6 @@ export const AppContainer = (props: AppContainerProps) => {
|
||||
debugLogger.error('Error during cleanup:', e),
|
||||
);
|
||||
};
|
||||
// Disable the dependencies check here. historyManager gets flagged
|
||||
// but we don't want to react to changes to it because each new history
|
||||
// item, including the ones from the start session hook will cause a
|
||||
// re-render and an error when we try to reload config.
|
||||
//
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [config, resumedSessionData]);
|
||||
|
||||
useEffect(
|
||||
@@ -1665,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({
|
||||
@@ -1846,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.
|
||||
@@ -2078,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();
|
||||
});
|
||||
|
||||
|
||||
@@ -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 &&
|
||||
|
||||
@@ -141,7 +141,7 @@ describe('ToolConfirmationQueue', () => {
|
||||
expect(output).toContain('1 of 3');
|
||||
expect(output).toContain('ls'); // Tool name
|
||||
expect(output).toContain('list files'); // Tool description
|
||||
expect(output).toContain('Allow execution of [ls]?');
|
||||
expect(output).toContain('Allow execution of [Shell]?');
|
||||
expect(output).toMatchSnapshot();
|
||||
|
||||
unmount();
|
||||
|
||||
@@ -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"> > 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
|
||||
|
||||
@@ -84,8 +84,8 @@
|
||||
<text x="711" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="9" y="308" fill="#ffffff" textLength="180" lengthAdjust="spacingAndGlyphs"> Allow execution of </text>
|
||||
<text x="189" y="308" fill="#ffffaf" textLength="54" lengthAdjust="spacingAndGlyphs" font-weight="bold">[echo]</text>
|
||||
<text x="243" y="308" fill="#ffffff" textLength="468" lengthAdjust="spacingAndGlyphs">? </text>
|
||||
<text x="189" y="308" fill="#ffffaf" textLength="63" lengthAdjust="spacingAndGlyphs" font-weight="bold">[Shell]</text>
|
||||
<text x="252" y="308" fill="#ffffff" textLength="459" lengthAdjust="spacingAndGlyphs">? </text>
|
||||
<text x="711" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
|
||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
@@ -191,8 +191,8 @@
|
||||
<text x="711" y="546" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="563" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="9" y="563" fill="#ffffff" textLength="180" lengthAdjust="spacingAndGlyphs"> Allow execution of </text>
|
||||
<text x="189" y="563" fill="#ffffaf" textLength="54" lengthAdjust="spacingAndGlyphs" font-weight="bold">[echo]</text>
|
||||
<text x="243" y="563" fill="#ffffff" textLength="468" lengthAdjust="spacingAndGlyphs">? </text>
|
||||
<text x="189" y="563" fill="#ffffaf" textLength="63" lengthAdjust="spacingAndGlyphs" font-weight="bold">[Shell]</text>
|
||||
<text x="252" y="563" fill="#ffffff" textLength="459" lengthAdjust="spacingAndGlyphs">? </text>
|
||||
<text x="711" y="563" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="580" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="580" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
|
||||
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
@@ -52,7 +52,7 @@ exports[`ToolConfirmationQueue > height allocation and layout > should handle se
|
||||
│ Original: https://täst.com/ │
|
||||
│ Actual Host (Punycode): https://xn--tst-qla.com/ │
|
||||
│ │
|
||||
│ Allow execution of [echo]? │
|
||||
│ Allow execution of [Shell]? │
|
||||
│ │
|
||||
│ ● 1. Allow once │
|
||||
│ 2. Allow for this session │
|
||||
@@ -138,7 +138,7 @@ exports[`ToolConfirmationQueue > height allocation and layout > should render th
|
||||
│ │ echo "Line 49" │ │
|
||||
│ │ echo "Line 50" │ │
|
||||
│ ╰──────────────────────────────────────────────────────────────────────────╯ │
|
||||
│ Allow execution of [echo]? │
|
||||
│ Allow execution of [Shell]? │
|
||||
│ │
|
||||
│ ● 1. Allow once │
|
||||
│ 2. Allow for this session │
|
||||
@@ -202,7 +202,7 @@ exports[`ToolConfirmationQueue > renders the confirming tool with progress indic
|
||||
│ ╭──────────────────────────────────────────────────────────────────────────╮ │
|
||||
│ │ ls │ │
|
||||
│ ╰──────────────────────────────────────────────────────────────────────────╯ │
|
||||
│ Allow execution of [ls]? │
|
||||
│ Allow execution of [Shell]? │
|
||||
│ │
|
||||
│ ● 1. Allow once │
|
||||
│ 2. Allow for this session │
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -275,6 +275,108 @@ describe('ToolConfirmationMessage', () => {
|
||||
result.unmount();
|
||||
});
|
||||
|
||||
it('should use the tool name for display in the confirmation question', async () => {
|
||||
const confirmationDetails: SerializableConfirmationDetails = {
|
||||
type: 'exec',
|
||||
title: 'Confirm Execution',
|
||||
command: '# This is a comment\necho "hello"',
|
||||
rootCommand: 'echo',
|
||||
rootCommands: ['echo'],
|
||||
};
|
||||
|
||||
const { lastFrame, unmount } = await renderWithProviders(
|
||||
<ToolConfirmationMessage
|
||||
callId="test-call-id"
|
||||
confirmationDetails={confirmationDetails}
|
||||
config={mockConfig}
|
||||
getPreferredEditor={vi.fn()}
|
||||
availableTerminalHeight={30}
|
||||
terminalWidth={80}
|
||||
toolName="shell"
|
||||
/>,
|
||||
);
|
||||
|
||||
const output = lastFrame();
|
||||
expect(output).toContain('Allow execution of [Shell]?');
|
||||
unmount();
|
||||
});
|
||||
|
||||
describe('tool name humanization', () => {
|
||||
const cases = [
|
||||
{
|
||||
toolName: 'run_shell_command',
|
||||
expected: 'Allow execution of [Shell]?',
|
||||
desc: 'humanize run_shell_command to Shell',
|
||||
},
|
||||
{
|
||||
toolName: 'shell',
|
||||
expected: 'Allow execution of [Shell]?',
|
||||
desc: 'humanize shell to Shell',
|
||||
},
|
||||
{
|
||||
toolName: 'grep_search',
|
||||
expected: 'Allow execution of [grep_search]?',
|
||||
desc: 'keep raw name for non-shell tools',
|
||||
},
|
||||
];
|
||||
|
||||
for (const { toolName, expected, desc } of cases) {
|
||||
it(`should ${desc}`, async () => {
|
||||
const confirmationDetails: SerializableConfirmationDetails = {
|
||||
type: 'exec',
|
||||
title: 'Confirm',
|
||||
command: 'ls',
|
||||
rootCommand: 'ls',
|
||||
rootCommands: ['ls'],
|
||||
};
|
||||
|
||||
const { lastFrame, unmount } = await renderWithProviders(
|
||||
<ToolConfirmationMessage
|
||||
callId="test-call-id"
|
||||
confirmationDetails={confirmationDetails}
|
||||
config={mockConfig}
|
||||
getPreferredEditor={vi.fn()}
|
||||
availableTerminalHeight={30}
|
||||
terminalWidth={80}
|
||||
toolName={toolName}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(lastFrame()).toContain(expected);
|
||||
unmount();
|
||||
});
|
||||
}
|
||||
|
||||
it('should humanize shell tool in sandbox expansion prompt', async () => {
|
||||
const confirmationDetails: SerializableConfirmationDetails = {
|
||||
type: 'sandbox_expansion',
|
||||
title: 'Confirm',
|
||||
command: 'ls',
|
||||
rootCommand: 'ls',
|
||||
additionalPermissions: {
|
||||
network: true,
|
||||
},
|
||||
};
|
||||
|
||||
const { lastFrame, unmount } = await renderWithProviders(
|
||||
<ToolConfirmationMessage
|
||||
callId="test-call-id"
|
||||
confirmationDetails={confirmationDetails}
|
||||
config={mockConfig}
|
||||
getPreferredEditor={vi.fn()}
|
||||
availableTerminalHeight={30}
|
||||
terminalWidth={80}
|
||||
toolName="run_shell_command"
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(lastFrame()).toContain(
|
||||
'To run [Shell], allow access to the following?',
|
||||
);
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
|
||||
describe('with folder trust', () => {
|
||||
const editConfirmationDetails: SerializableConfirmationDetails = {
|
||||
type: 'edit',
|
||||
|
||||
@@ -629,18 +629,13 @@ export const ToolConfirmationMessage: React.FC<
|
||||
);
|
||||
}
|
||||
} else if (confirmationDetails.type === 'sandbox_expansion') {
|
||||
const { additionalPermissions, command, rootCommand } =
|
||||
confirmationDetails;
|
||||
const { additionalPermissions, command } = confirmationDetails;
|
||||
const readPaths = additionalPermissions?.fileSystem?.read || [];
|
||||
const writePaths = additionalPermissions?.fileSystem?.write || [];
|
||||
const network = additionalPermissions?.network;
|
||||
const isShell = isShellTool(toolName);
|
||||
|
||||
const rootCmds = rootCommand
|
||||
.split(',')
|
||||
.map((c) => c.trim().split(/\s+/)[0])
|
||||
.filter((c) => c && !c.startsWith('redirection'));
|
||||
const commandNames = Array.from(new Set(rootCmds)).join(', ');
|
||||
const commandNames = isShell ? 'Shell' : toolName;
|
||||
question = '';
|
||||
|
||||
bodyContent = (
|
||||
@@ -730,13 +725,7 @@ export const ToolConfirmationMessage: React.FC<
|
||||
);
|
||||
}
|
||||
|
||||
const commandNames = Array.from(
|
||||
new Set(
|
||||
commandsToDisplay
|
||||
.map((cmd) => cmd.trim().split(/\s+/)[0])
|
||||
.filter(Boolean),
|
||||
),
|
||||
).join(', ');
|
||||
const commandNames = isShell ? 'Shell' : toolName;
|
||||
|
||||
const allowQuestion = (
|
||||
<Text>
|
||||
|
||||
@@ -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: [],
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -32,10 +32,12 @@ export const STATUS_INDICATOR_WIDTH = 3;
|
||||
* Returns true if the tool name corresponds to a shell tool.
|
||||
*/
|
||||
export function isShellTool(name: string): boolean {
|
||||
const normalized = name.toLowerCase();
|
||||
return (
|
||||
name === SHELL_COMMAND_NAME ||
|
||||
name === SHELL_NAME ||
|
||||
name === SHELL_TOOL_NAME
|
||||
name === SHELL_TOOL_NAME ||
|
||||
normalized === 'shell'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ exports[`ToolConfirmationMessage Redirection > should display redirection warnin
|
||||
"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ echo "hello" > test.txt │
|
||||
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
Allow execution of [echo]?
|
||||
Allow execution of [Shell]?
|
||||
Redirection detected. To auto-accept, press Shift+Tab
|
||||
|
||||
● 1. Allow once
|
||||
|
||||
@@ -133,7 +133,9 @@
|
||||
<text x="63" y="546" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 50"</text>
|
||||
<text x="711" y="546" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="563" fill="#333333" textLength="720" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────╯</text>
|
||||
<text x="0" y="580" fill="#ffffff" textLength="900" lengthAdjust="spacingAndGlyphs">Allow execution of [echo]? </text>
|
||||
<text x="0" y="580" fill="#ffffff" textLength="171" lengthAdjust="spacingAndGlyphs">Allow execution of </text>
|
||||
<text x="171" y="580" fill="#ffffaf" textLength="63" lengthAdjust="spacingAndGlyphs" font-weight="bold">[Shell]</text>
|
||||
<text x="234" y="580" fill="#ffffff" textLength="666" lengthAdjust="spacingAndGlyphs">? </text>
|
||||
<rect x="0" y="612" width="9" height="17" fill="#001a00" />
|
||||
<text x="0" y="614" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
||||
<rect x="9" y="612" width="9" height="17" fill="#001a00" />
|
||||
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@@ -24,7 +24,9 @@
|
||||
<text x="18" y="70" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">done</text>
|
||||
<text x="711" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="87" fill="#333333" textLength="720" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────╯</text>
|
||||
<text x="0" y="104" fill="#ffffff" textLength="900" lengthAdjust="spacingAndGlyphs">Allow execution of [echo]? </text>
|
||||
<text x="0" y="104" fill="#ffffff" textLength="171" lengthAdjust="spacingAndGlyphs">Allow execution of </text>
|
||||
<text x="171" y="104" fill="#ffffaf" textLength="63" lengthAdjust="spacingAndGlyphs" font-weight="bold">[Shell]</text>
|
||||
<text x="234" y="104" fill="#ffffff" textLength="666" lengthAdjust="spacingAndGlyphs">? </text>
|
||||
<rect x="0" y="136" width="9" height="17" fill="#001a00" />
|
||||
<text x="0" y="138" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
||||
<rect x="9" y="136" width="9" height="17" fill="#001a00" />
|
||||
|
||||
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.2 KiB |
@@ -94,7 +94,7 @@ exports[`ToolConfirmationMessage > height allocation and layout > should expand
|
||||
│ echo "Line 49" │
|
||||
│ echo "Line 50" │
|
||||
╰──────────────────────────────────────────────────────────────────────────────╯
|
||||
Allow execution of [echo]?
|
||||
Allow execution of [Shell]?
|
||||
|
||||
● 1. Allow once
|
||||
2. Allow for this session
|
||||
@@ -110,7 +110,7 @@ exports[`ToolConfirmationMessage > should display multiple commands for exec typ
|
||||
│ │
|
||||
│ whoami │
|
||||
╰──────────────────────────────────────────────────────────────────────────────╯
|
||||
Allow execution of [echo, ls, whoami]?
|
||||
Allow execution of [Shell]?
|
||||
|
||||
● 1. Allow once
|
||||
2. Allow for this session
|
||||
@@ -148,7 +148,7 @@ exports[`ToolConfirmationMessage > should render multiline shell scripts with co
|
||||
│ echo $i │
|
||||
│ done │
|
||||
╰──────────────────────────────────────────────────────────────────────────────╯
|
||||
Allow execution of [echo]?
|
||||
Allow execution of [Shell]?
|
||||
|
||||
● 1. Allow once
|
||||
2. Allow for this session
|
||||
@@ -200,7 +200,7 @@ exports[`ToolConfirmationMessage > with folder trust > 'for exec confirmations'
|
||||
"╭──────────────────────────────────────────────────────────────────────────────╮
|
||||
│ echo "hello" │
|
||||
╰──────────────────────────────────────────────────────────────────────────────╯
|
||||
Allow execution of [echo]?
|
||||
Allow execution of [Shell]?
|
||||
|
||||
● 1. Allow once
|
||||
2. No, suggest changes (esc)
|
||||
@@ -211,7 +211,7 @@ exports[`ToolConfirmationMessage > with folder trust > 'for exec confirmations'
|
||||
"╭──────────────────────────────────────────────────────────────────────────────╮
|
||||
│ echo "hello" │
|
||||
╰──────────────────────────────────────────────────────────────────────────────╯
|
||||
Allow execution of [echo]?
|
||||
Allow execution of [Shell]?
|
||||
|
||||
● 1. Allow once
|
||||
2. Allow for this session
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -206,7 +206,6 @@ class ThemeManager {
|
||||
try {
|
||||
const theme = createCustomTheme(themeWithDefaults);
|
||||
this.extensionThemes.set(namespacedName, theme);
|
||||
debugLogger.log(`Registered theme: ${namespacedName}`);
|
||||
} catch (error) {
|
||||
debugLogger.warn(
|
||||
`Failed to load custom theme "${namespacedName}":`,
|
||||
@@ -237,7 +236,6 @@ class ThemeManager {
|
||||
for (const theme of customThemes) {
|
||||
const namespacedName = `${theme.name} (${extensionName})`;
|
||||
this.extensionThemes.delete(namespacedName);
|
||||
debugLogger.log(`Unregistered theme: ${namespacedName}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||